aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-30 20:35:25 +0000
commit1b7b916b7631ddf73c38e3a0070d64e4636cb2f3 (patch)
treecd08258054db80bb9a11b1061bb091c70b76926a
parenteaa89c11d0f8aefbb512de769c6c82f61a8baca3 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.gz
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.tar.bz2
rneovim-1b7b916b7631ddf73c38e3a0070d64e4636cb2f3.zip
Merge remote-tracking branch 'upstream/master' into aucmd_textputpostaucmd_textputpost
-rw-r--r--.cirrus.yml8
-rw-r--r--.clang-format4
-rw-r--r--.clang-tidy85
-rw-r--r--.clangd2
-rwxr-xr-x.gitattributes11
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.yml13
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml2
-rw-r--r--.github/ISSUE_TEMPLATE/lsp_bug_report.yml44
-rw-r--r--.github/actions/cache/action.yml19
-rw-r--r--.github/dependabot.yml8
-rw-r--r--.github/labeler.yml4
-rwxr-xr-x.github/scripts/build_universal_macos.sh21
-rw-r--r--.github/scripts/close_unresponsive.js55
-rw-r--r--.github/scripts/env.ps1 (renamed from .github/workflows/env.ps1)2
-rwxr-xr-x.github/scripts/install_deps.sh41
-rwxr-xr-x.github/scripts/install_deps_ubuntu.sh19
-rw-r--r--.github/scripts/remove-reviewers.js10
-rw-r--r--.github/scripts/remove_response_label.js (renamed from .github/scripts/unstale.js)0
-rw-r--r--.github/scripts/reviews.js122
-rw-r--r--.github/workflows/add-reviewers.yml6
-rw-r--r--.github/workflows/api-docs-check.yml18
-rw-r--r--.github/workflows/api-docs.yml74
-rw-r--r--.github/workflows/backport.yml17
-rw-r--r--.github/workflows/build.yml94
-rw-r--r--.github/workflows/ci.yml354
-rw-r--r--.github/workflows/codeql.yml33
-rw-r--r--.github/workflows/coverity.yml6
-rwxr-xr-x.github/workflows/env.sh69
-rw-r--r--.github/workflows/issue-open-check.yml34
-rw-r--r--.github/workflows/labeler.yml15
-rw-r--r--.github/workflows/lintcommit.yml22
-rw-r--r--.github/workflows/news.yml4
-rw-r--r--.github/workflows/notes.md27
-rw-r--r--.github/workflows/optional.yml41
-rw-r--r--.github/workflows/release.yml160
-rw-r--r--.github/workflows/remove-reviewers.yml5
-rw-r--r--.github/workflows/response.yml35
-rw-r--r--.github/workflows/stale.yml42
-rw-r--r--.github/workflows/test.yml342
-rw-r--r--.github/workflows/vim-patches.yml8
-rw-r--r--.gitignore30
-rw-r--r--.luacheckrc7
-rw-r--r--.luarc.json31
-rw-r--r--.styluaignore4
-rw-r--r--CMakeLists.txt349
-rw-r--r--CMakePresets.json73
-rw-r--r--CONTRIBUTING.md190
-rw-r--r--LICENSE.txt4
-rw-r--r--MAINTAIN.md141
-rw-r--r--Makefile112
-rw-r--r--README.md5
-rwxr-xr-xci/before_cache.sh26
-rwxr-xr-xci/before_script.sh29
-rw-r--r--ci/common/build.sh78
-rwxr-xr-xci/common/submit_coverage.sh56
-rw-r--r--ci/common/suite.sh41
-rw-r--r--ci/common/test.sh175
-rwxr-xr-xci/install.sh20
-rwxr-xr-xci/run_tests.sh43
-rw-r--r--cmake.config/CMakeLists.txt100
-rw-r--r--cmake.config/config.h.in19
-rw-r--r--cmake.config/iwyu/c99.imp1031
-rw-r--r--cmake.config/iwyu/gcc.libc.imp226
-rw-r--r--cmake.config/iwyu/gcc.symbols.imp305
-rw-r--r--cmake.config/iwyu/mapping.imp254
-rw-r--r--cmake.config/iwyu/posix.imp33
-rw-r--r--cmake.config/pathdef.c.in6
-rw-r--r--cmake.config/versiondef.h.in4
-rw-r--r--cmake.config/versiondef_old.h.in21
-rw-r--r--cmake.deps/CMakeLists.txt206
-rw-r--r--cmake.deps/CMakePresets.json25
-rw-r--r--cmake.deps/cmake/BuildGettext.cmake12
-rw-r--r--cmake.deps/cmake/BuildLibiconv.cmake9
-rw-r--r--cmake.deps/cmake/BuildLibtermkey.cmake17
-rw-r--r--cmake.deps/cmake/BuildLibuv.cmake16
-rw-r--r--cmake.deps/cmake/BuildLibvterm.cmake9
-rw-r--r--cmake.deps/cmake/BuildLpeg.cmake32
-rw-r--r--cmake.deps/cmake/BuildLua.cmake32
-rw-r--r--cmake.deps/cmake/BuildLuajit.cmake57
-rw-r--r--cmake.deps/cmake/BuildLuarocks.cmake180
-rw-r--r--cmake.deps/cmake/BuildLuv.cmake56
-rw-r--r--cmake.deps/cmake/BuildMsgpack.cmake13
-rw-r--r--cmake.deps/cmake/BuildTreesitter.cmake15
-rw-r--r--cmake.deps/cmake/BuildTreesitterParsers.cmake44
-rw-r--r--cmake.deps/cmake/BuildUnibilium.cmake9
-rw-r--r--cmake.deps/cmake/CopyFilesGlob.cmake2
-rw-r--r--cmake.deps/cmake/GetBinaryDeps.cmake44
-rw-r--r--cmake.deps/cmake/GettextCMakeLists.txt11
-rw-r--r--cmake.deps/cmake/LibiconvCMakeLists.txt9
-rw-r--r--cmake.deps/cmake/LibtermkeyCMakeLists.txt37
-rw-r--r--cmake.deps/cmake/LibvtermCMakeLists.txt65
-rw-r--r--cmake.deps/cmake/LpegCMakeLists.txt13
-rw-r--r--cmake.deps/cmake/MarkdownParserCMakeLists.txt33
-rw-r--r--cmake.deps/cmake/RemoveFiles.cmake5
-rw-r--r--cmake.deps/cmake/TreesitterCMakeLists.txt8
-rw-r--r--cmake.deps/cmake/TreesitterParserCMakeLists.txt7
-rw-r--r--cmake.deps/cmake/libtermkeyCMakeLists.txt23
-rw-r--r--cmake.deps/deps.txt64
-rw-r--r--cmake.packaging/CMakeLists.txt8
-rw-r--r--cmake.packaging/WixPatch.xml24
-rw-r--r--cmake/CheckUncrustifyVersion.cmake13
-rw-r--r--cmake/ConvertPo.cmake2
-rw-r--r--cmake/Deps.cmake56
-rw-r--r--cmake/Find.cmake39
-rw-r--r--cmake/FindIconv.cmake25
-rw-r--r--cmake/FindLibLUV.cmake32
-rw-r--r--cmake/FindLibTermkey.cmake31
-rw-r--r--cmake/FindLibintl.cmake (renamed from cmake/FindLibIntl.cmake)53
-rw-r--r--cmake/FindLibtermkey.cmake9
-rw-r--r--cmake/FindLibuv.cmake (renamed from cmake/FindLibUV.cmake)40
-rw-r--r--cmake/FindLibvterm.cmake (renamed from cmake/Findlibvterm.cmake)21
-rw-r--r--cmake/FindLpeg.cmake9
-rw-r--r--cmake/FindLuaJit.cmake38
-rw-r--r--cmake/FindLuajit.cmake17
-rw-r--r--cmake/FindLuv.cmake6
-rw-r--r--cmake/FindMsgpack.cmake41
-rw-r--r--cmake/FindTreeSitter.cmake11
-rw-r--r--cmake/FindTreesitter.cmake29
-rw-r--r--cmake/FindUnibilium.cmake11
-rw-r--r--cmake/Findunibilium.cmake27
-rw-r--r--cmake/Format.cmake10
-rw-r--r--cmake/GenerateVersion.cmake7
-rw-r--r--cmake/GetCompileFlags.cmake57
-rw-r--r--cmake/InstallHelpers.cmake12
-rw-r--r--cmake/LibFindMacros.cmake269
-rw-r--r--cmake/LuaHelpers.cmake37
-rw-r--r--cmake/RunTests.cmake24
-rw-r--r--cmake/Util.cmake28
-rw-r--r--cmake/WindowsDllCopy.cmake11
-rw-r--r--codecov.yml27
-rwxr-xr-xcontrib/asan.sh5
-rw-r--r--contrib/flake.lock30
-rw-r--r--contrib/flake.nix58
-rw-r--r--contrib/local.mk.example8
-rw-r--r--contrib/luarc.json28
-rw-r--r--runtime/CMakeLists.txt37
-rw-r--r--runtime/autoload/cargo.vim149
-rw-r--r--runtime/autoload/cargo/quickfix.vim29
-rw-r--r--runtime/autoload/ccomplete.lua7
-rw-r--r--runtime/autoload/dist/vim.vim32
-rw-r--r--runtime/autoload/gzip.vim12
-rw-r--r--runtime/autoload/health.vim211
-rw-r--r--runtime/autoload/health/provider.vim771
-rw-r--r--runtime/autoload/javascriptcomplete.vim6
-rw-r--r--runtime/autoload/msgpack.vim8
-rw-r--r--runtime/autoload/netrw.vim319
-rw-r--r--runtime/autoload/netrwSettings.vim8
-rw-r--r--runtime/autoload/paste.vim5
-rw-r--r--runtime/autoload/phpcomplete.vim2
-rw-r--r--runtime/autoload/provider/clipboard.vim11
-rw-r--r--runtime/autoload/provider/node.vim18
-rw-r--r--runtime/autoload/provider/pythonx.vim9
-rw-r--r--runtime/autoload/python.vim9
-rw-r--r--runtime/autoload/rust.vim773
-rw-r--r--runtime/autoload/rust/debugging.vim105
-rw-r--r--runtime/autoload/rustfmt.vim300
-rw-r--r--runtime/autoload/shada.vim2
-rw-r--r--runtime/autoload/tar.vim112
-rw-r--r--runtime/autoload/tohtml.vim463
-rw-r--r--runtime/autoload/tutor.vim12
-rw-r--r--runtime/autoload/zip.vim17
-rw-r--r--runtime/bugreport.vim87
-rw-r--r--runtime/colors/README.txt4
-rw-r--r--runtime/colors/default.vim5
-rw-r--r--runtime/colors/habamax.vim149
-rw-r--r--runtime/colors/lunaperche.vim215
-rw-r--r--runtime/colors/quiet.vim441
-rw-r--r--runtime/colors/retrobox.vim882
-rw-r--r--runtime/colors/sorbet.vim448
-rw-r--r--runtime/colors/wildcharm.vim761
-rw-r--r--runtime/colors/zaibatsu.vim567
-rw-r--r--runtime/compiler/README.txt2
-rw-r--r--runtime/compiler/cargo.vim36
-rw-r--r--runtime/compiler/msvc.vim5
-rw-r--r--runtime/compiler/rustc.vim63
-rw-r--r--runtime/delmenu.vim5
-rw-r--r--runtime/doc/api.txt745
-rw-r--r--runtime/doc/arabic.txt2
-rw-r--r--runtime/doc/autocmd.txt152
-rw-r--r--runtime/doc/builtin.txt5771
-rw-r--r--runtime/doc/change.txt67
-rw-r--r--runtime/doc/channel.txt6
-rw-r--r--runtime/doc/cmdline.txt77
-rw-r--r--runtime/doc/deprecated.txt96
-rw-r--r--runtime/doc/dev_style.txt66
-rw-r--r--runtime/doc/develop.txt259
-rw-r--r--runtime/doc/diagnostic.txt119
-rw-r--r--runtime/doc/diff.txt11
-rw-r--r--runtime/doc/digraph.txt2620
-rw-r--r--runtime/doc/editing.txt123
-rw-r--r--runtime/doc/editorconfig.txt26
-rw-r--r--runtime/doc/eval.txt284
-rw-r--r--runtime/doc/filetype.txt188
-rw-r--r--runtime/doc/fold.txt13
-rw-r--r--runtime/doc/ft_ada.txt4
-rw-r--r--runtime/doc/ft_raku.txt8
-rw-r--r--runtime/doc/ft_rust.txt433
-rw-r--r--runtime/doc/ft_sql.txt4
-rw-r--r--runtime/doc/gui.txt13
-rw-r--r--runtime/doc/hebrew.txt19
-rw-r--r--runtime/doc/help.txt40
-rw-r--r--runtime/doc/helphelp.txt17
-rw-r--r--runtime/doc/if_perl.txt6
-rw-r--r--runtime/doc/if_pyth.txt65
-rw-r--r--runtime/doc/if_ruby.txt8
-rw-r--r--runtime/doc/indent.txt18
-rw-r--r--runtime/doc/index.txt28
-rw-r--r--runtime/doc/insert.txt42
-rw-r--r--runtime/doc/intro.txt50
-rw-r--r--runtime/doc/job_control.txt3
-rw-r--r--runtime/doc/lsp-extension.txt129
-rw-r--r--runtime/doc/lsp.txt1331
-rw-r--r--runtime/doc/lua-guide.txt90
-rw-r--r--runtime/doc/lua.txt3103
-rw-r--r--runtime/doc/luaref.txt606
-rw-r--r--runtime/doc/luvref.txt356
-rw-r--r--runtime/doc/map.txt108
-rw-r--r--runtime/doc/mbyte.txt147
-rw-r--r--runtime/doc/message.txt12
-rw-r--r--runtime/doc/motion.txt186
-rw-r--r--runtime/doc/news-0.9.txt323
-rw-r--r--runtime/doc/news.txt449
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt76
-rw-r--r--runtime/doc/options.txt1480
-rw-r--r--runtime/doc/pattern.txt42
-rw-r--r--runtime/doc/pi_gzip.txt10
-rw-r--r--runtime/doc/pi_health.txt28
-rw-r--r--runtime/doc/pi_msgpack.txt94
-rw-r--r--runtime/doc/pi_netrw.txt65
-rw-r--r--runtime/doc/pi_tar.txt29
-rw-r--r--runtime/doc/pi_zip.txt16
-rw-r--r--runtime/doc/provider.txt57
-rw-r--r--runtime/doc/quickfix.txt30
-rw-r--r--runtime/doc/quickref.txt8
-rw-r--r--runtime/doc/recover.txt11
-rw-r--r--runtime/doc/remote.txt2
-rw-r--r--runtime/doc/remote_plugin.txt8
-rw-r--r--runtime/doc/repeat.txt42
-rw-r--r--runtime/doc/russian.txt1
-rw-r--r--runtime/doc/scroll.txt18
-rw-r--r--runtime/doc/sign.txt415
-rw-r--r--runtime/doc/spell.txt15
-rw-r--r--runtime/doc/starting.txt147
-rw-r--r--runtime/doc/support.txt6
-rw-r--r--runtime/doc/syntax.txt199
-rw-r--r--runtime/doc/tagsrch.txt8
-rw-r--r--runtime/doc/term.txt44
-rw-r--r--runtime/doc/testing.txt201
-rw-r--r--runtime/doc/tips.txt36
-rw-r--r--runtime/doc/treesitter.txt905
-rw-r--r--runtime/doc/uganda.txt6
-rw-r--r--runtime/doc/ui.txt76
-rw-r--r--runtime/doc/undo.txt28
-rw-r--r--runtime/doc/userfunc.txt78
-rw-r--r--runtime/doc/usr_01.txt2
-rw-r--r--runtime/doc/usr_02.txt4
-rw-r--r--runtime/doc/usr_03.txt2
-rw-r--r--runtime/doc/usr_05.txt133
-rw-r--r--runtime/doc/usr_09.txt48
-rw-r--r--runtime/doc/usr_11.txt5
-rw-r--r--runtime/doc/usr_12.txt4
-rw-r--r--runtime/doc/usr_21.txt12
-rw-r--r--runtime/doc/usr_22.txt44
-rw-r--r--runtime/doc/usr_24.txt18
-rw-r--r--runtime/doc/usr_28.txt4
-rw-r--r--runtime/doc/usr_29.txt28
-rw-r--r--runtime/doc/usr_30.txt40
-rw-r--r--runtime/doc/usr_40.txt6
-rw-r--r--runtime/doc/usr_41.txt54
-rw-r--r--runtime/doc/usr_43.txt8
-rw-r--r--runtime/doc/usr_45.txt3
-rw-r--r--runtime/doc/various.txt39
-rw-r--r--runtime/doc/vi_diff.txt12
-rw-r--r--runtime/doc/vim_diff.txt420
-rw-r--r--runtime/doc/visual.txt3
-rw-r--r--runtime/doc/windows.txt18
-rw-r--r--runtime/filetype.lua15
-rw-r--r--runtime/ftoff.vim5
-rw-r--r--runtime/ftplugin.vim12
-rw-r--r--runtime/ftplugin/aap.vim5
-rw-r--r--runtime/ftplugin/abap.vim4
-rw-r--r--runtime/ftplugin/art.vim3
-rw-r--r--runtime/ftplugin/asm.vim6
-rw-r--r--runtime/ftplugin/awk.vim11
-rw-r--r--runtime/ftplugin/bash.vim4
-rw-r--r--runtime/ftplugin/btm.vim5
-rw-r--r--runtime/ftplugin/bzl.vim5
-rw-r--r--runtime/ftplugin/c.lua14
-rw-r--r--runtime/ftplugin/c.vim5
-rw-r--r--runtime/ftplugin/calender.lua1
-rw-r--r--runtime/ftplugin/changelog.vim12
-rw-r--r--runtime/ftplugin/checkhealth.vim3
-rw-r--r--runtime/ftplugin/corn.vim18
-rw-r--r--runtime/ftplugin/cpp.vim5
-rw-r--r--runtime/ftplugin/cs.lua1
-rw-r--r--runtime/ftplugin/csh.vim17
-rw-r--r--runtime/ftplugin/css.lua1
-rw-r--r--runtime/ftplugin/d.lua1
-rw-r--r--runtime/ftplugin/debchangelog.vim12
-rw-r--r--runtime/ftplugin/debcontrol.vim4
-rw-r--r--runtime/ftplugin/debsources.vim16
-rw-r--r--runtime/ftplugin/diff.vim5
-rw-r--r--runtime/ftplugin/dosbatch.vim18
-rw-r--r--runtime/ftplugin/dune.vim7
-rw-r--r--runtime/ftplugin/eruby.vim12
-rw-r--r--runtime/ftplugin/fennel.vim4
-rw-r--r--runtime/ftplugin/fish.vim18
-rw-r--r--runtime/ftplugin/forth.vim72
-rw-r--r--runtime/ftplugin/gpg.vim19
-rw-r--r--runtime/ftplugin/hare.vim34
-rw-r--r--runtime/ftplugin/haskell.vim1
-rw-r--r--runtime/ftplugin/heex.vim11
-rw-r--r--runtime/ftplugin/help.lua3
-rw-r--r--runtime/ftplugin/indent.lua1
-rw-r--r--runtime/ftplugin/ishd.vim23
-rw-r--r--runtime/ftplugin/json5.vim28
-rw-r--r--runtime/ftplugin/kotlin.vim33
-rw-r--r--runtime/ftplugin/livebook.vim9
-rw-r--r--runtime/ftplugin/logcheck.vim4
-rw-r--r--runtime/ftplugin/lprolog.vim12
-rw-r--r--runtime/ftplugin/lua.lua3
-rw-r--r--runtime/ftplugin/lua.vim5
-rw-r--r--runtime/ftplugin/luau.vim14
-rw-r--r--runtime/ftplugin/lynx.vim4
-rw-r--r--runtime/ftplugin/mail.vim5
-rw-r--r--runtime/ftplugin/make.vim5
-rw-r--r--runtime/ftplugin/modconf.vim18
-rw-r--r--runtime/ftplugin/muttrc.vim18
-rw-r--r--runtime/ftplugin/netrc.vim3
-rw-r--r--runtime/ftplugin/nginx.vim3
-rw-r--r--runtime/ftplugin/nix.vim17
-rw-r--r--runtime/ftplugin/objc.vim5
-rw-r--r--runtime/ftplugin/objdump.vim14
-rw-r--r--runtime/ftplugin/pbtxt.vim8
-rw-r--r--runtime/ftplugin/perl.vim5
-rw-r--r--runtime/ftplugin/pod.vim37
-rw-r--r--runtime/ftplugin/pymanifest.vim13
-rw-r--r--runtime/ftplugin/qml.vim31
-rw-r--r--runtime/ftplugin/quarto.vim1
-rw-r--r--runtime/ftplugin/query.lua31
-rw-r--r--runtime/ftplugin/r.vim4
-rw-r--r--runtime/ftplugin/readline.vim18
-rw-r--r--runtime/ftplugin/rhelp.vim4
-rw-r--r--runtime/ftplugin/rmd.vim29
-rw-r--r--runtime/ftplugin/rnoweb.vim25
-rw-r--r--runtime/ftplugin/rrst.vim6
-rw-r--r--runtime/ftplugin/rst.vim2
-rw-r--r--runtime/ftplugin/ruby.vim15
-rw-r--r--runtime/ftplugin/rust.vim252
-rw-r--r--runtime/ftplugin/scala.vim3
-rw-r--r--runtime/ftplugin/sed.vim29
-rw-r--r--runtime/ftplugin/sexplib.vim3
-rw-r--r--runtime/ftplugin/sh.vim28
-rw-r--r--runtime/ftplugin/solidity.vim15
-rw-r--r--runtime/ftplugin/sql.vim4
-rw-r--r--runtime/ftplugin/sshconfig.vim25
-rw-r--r--runtime/ftplugin/sudoers.vim18
-rw-r--r--runtime/ftplugin/swig.vim13
-rw-r--r--runtime/ftplugin/systemd.vim54
-rw-r--r--runtime/ftplugin/tcsh.vim20
-rw-r--r--runtime/ftplugin/tidy.vim2
-rw-r--r--runtime/ftplugin/toml.vim2
-rw-r--r--runtime/ftplugin/udevrules.vim18
-rw-r--r--runtime/ftplugin/unison.vim14
-rw-r--r--runtime/ftplugin/urlshortcut.vim20
-rw-r--r--runtime/ftplugin/usd.vim14
-rw-r--r--runtime/ftplugin/vhdl.vim76
-rw-r--r--runtime/ftplugin/vim.vim30
-rw-r--r--runtime/ftplugin/wat.vim (renamed from runtime/ftplugin/wast.vim)2
-rw-r--r--runtime/ftplugin/wget.vim4
-rw-r--r--runtime/ftplugin/wget2.vim4
-rw-r--r--runtime/ftplugin/xcompose.vim13
-rw-r--r--runtime/ftplugin/xdefaults.lua1
-rw-r--r--runtime/ftplugin/zig.vim10
-rw-r--r--runtime/ftplugin/zimbu.vim5
-rw-r--r--runtime/ftplugin/zsh.vim12
-rw-r--r--runtime/ftplugof.vim5
-rw-r--r--runtime/indent.vim9
-rw-r--r--runtime/indent/README.txt11
-rw-r--r--runtime/indent/aap.vim5
-rw-r--r--runtime/indent/bash.vim4
-rw-r--r--runtime/indent/c.vim5
-rw-r--r--runtime/indent/cdl.vim8
-rw-r--r--runtime/indent/ch.vim3
-rw-r--r--runtime/indent/cpp.vim5
-rw-r--r--runtime/indent/cuda.vim5
-rw-r--r--runtime/indent/dts.vim63
-rw-r--r--runtime/indent/dune.vim3
-rw-r--r--runtime/indent/erlang.vim2
-rw-r--r--runtime/indent/fish.vim85
-rw-r--r--runtime/indent/go.vim3
-rw-r--r--runtime/indent/hare.vim3
-rw-r--r--runtime/indent/html.vim4
-rw-r--r--runtime/indent/jsonc.vim3
-rw-r--r--runtime/indent/julia.vim7
-rw-r--r--runtime/indent/kotlin.vim60
-rw-r--r--runtime/indent/krl.vim4
-rw-r--r--runtime/indent/livebook.vim9
-rw-r--r--runtime/indent/logtalk.vim3
-rw-r--r--runtime/indent/lua.vim12
-rw-r--r--runtime/indent/luau.vim14
-rw-r--r--runtime/indent/mail.vim4
-rw-r--r--runtime/indent/nsis.vim2
-rw-r--r--runtime/indent/ocaml.vim5
-rw-r--r--runtime/indent/perl.vim3
-rw-r--r--runtime/indent/php.vim58
-rw-r--r--runtime/indent/python.vim7
-rw-r--r--runtime/indent/qml.vim59
-rw-r--r--runtime/indent/quarto.vim1
-rw-r--r--runtime/indent/r.vim22
-rw-r--r--runtime/indent/raku.vim3
-rw-r--r--runtime/indent/rapid.vim255
-rw-r--r--runtime/indent/rhelp.vim4
-rw-r--r--runtime/indent/rmd.vim6
-rw-r--r--runtime/indent/rnoweb.vim6
-rw-r--r--runtime/indent/rrst.vim4
-rw-r--r--runtime/indent/rst.vim3
-rw-r--r--runtime/indent/ruby.vim4
-rw-r--r--runtime/indent/rust.vim417
-rw-r--r--runtime/indent/scala.vim3
-rw-r--r--runtime/indent/solidity.vim14
-rw-r--r--runtime/indent/systemverilog.vim4
-rw-r--r--runtime/indent/testdir/dts.in46
-rw-r--r--runtime/indent/testdir/dts.ok46
-rw-r--r--runtime/indent/testdir/rapid.in266
-rw-r--r--runtime/indent/testdir/rapid.ok266
-rw-r--r--runtime/indent/testdir/runtest.vim1
-rw-r--r--runtime/indent/tex.vim4
-rw-r--r--runtime/indent/typescript.vim3
-rw-r--r--runtime/indent/typescriptreact.vim2
-rw-r--r--runtime/indent/verilog.vim3
-rw-r--r--runtime/indent/vhdl.vim3
-rw-r--r--runtime/indent/vim.vim5
-rw-r--r--runtime/indent/wat.vim (renamed from runtime/indent/wast.vim)2
-rw-r--r--runtime/indent/xhtml.vim5
-rw-r--r--runtime/indent/zimbu.vim5
-rw-r--r--runtime/indoff.vim5
-rw-r--r--runtime/keymap/accents.vim4
-rw-r--r--runtime/keymap/arabic_utf-8.vim12
-rw-r--r--runtime/keymap/greek_utf-8.vim2
-rw-r--r--runtime/keymap/russian-typograph.vim369
-rw-r--r--runtime/lua/_vim9script.lua6
-rw-r--r--runtime/lua/coxpcall.lua108
-rw-r--r--runtime/lua/editorconfig.lua37
-rw-r--r--runtime/lua/health.lua6
-rw-r--r--runtime/lua/man.lua236
-rw-r--r--runtime/lua/nvim/health.lua115
-rw-r--r--runtime/lua/provider/health.lua916
-rw-r--r--runtime/lua/vim/F.lua32
-rw-r--r--runtime/lua/vim/_defaults.lua314
-rw-r--r--runtime/lua/vim/_editor.lua493
-rw-r--r--runtime/lua/vim/_init_packages.lua9
-rw-r--r--runtime/lua/vim/_inspector.lua128
-rw-r--r--runtime/lua/vim/_meta.lua572
-rw-r--r--runtime/lua/vim/_meta/api.lua2102
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua267
-rw-r--r--runtime/lua/vim/_meta/base64.lua13
-rw-r--r--runtime/lua/vim/_meta/builtin.lua289
-rw-r--r--runtime/lua/vim/_meta/builtin_types.lua129
-rw-r--r--runtime/lua/vim/_meta/diff.lua70
-rw-r--r--runtime/lua/vim/_meta/json.lua39
-rw-r--r--runtime/lua/vim/_meta/lpeg.lua323
-rw-r--r--runtime/lua/vim/_meta/misc.lua15
-rw-r--r--runtime/lua/vim/_meta/mpack.lua15
-rw-r--r--runtime/lua/vim/_meta/options.lua7958
-rw-r--r--runtime/lua/vim/_meta/regex.lua36
-rw-r--r--runtime/lua/vim/_meta/spell.lua32
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua10689
-rw-r--r--runtime/lua/vim/_options.lua910
-rw-r--r--runtime/lua/vim/_system.lua374
-rw-r--r--runtime/lua/vim/_watch.lua223
-rw-r--r--runtime/lua/vim/diagnostic.lua224
-rw-r--r--runtime/lua/vim/filetype.lua1559
-rw-r--r--runtime/lua/vim/filetype/detect.lua729
-rw-r--r--runtime/lua/vim/filetype/options.lua88
-rw-r--r--runtime/lua/vim/fs.lua214
-rw-r--r--runtime/lua/vim/func.lua41
-rw-r--r--runtime/lua/vim/func/_memoize.lua59
-rw-r--r--runtime/lua/vim/health.lua298
-rw-r--r--runtime/lua/vim/highlight.lua123
-rw-r--r--runtime/lua/vim/iter.lua1013
-rw-r--r--runtime/lua/vim/keymap.lua98
-rw-r--r--runtime/lua/vim/loader.lua536
-rw-r--r--runtime/lua/vim/lsp.lua1063
-rw-r--r--runtime/lua/vim/lsp/_completion.lua236
-rw-r--r--runtime/lua/vim/lsp/_dynamic.lua109
-rw-r--r--runtime/lua/vim/lsp/_meta.lua22
-rw-r--r--runtime/lua/vim/lsp/_meta/protocol.lua4396
-rw-r--r--runtime/lua/vim/lsp/_snippet.lua500
-rw-r--r--runtime/lua/vim/lsp/_snippet_grammar.lua181
-rw-r--r--runtime/lua/vim/lsp/_watchfiles.lua251
-rw-r--r--runtime/lua/vim/lsp/buf.lua380
-rw-r--r--runtime/lua/vim/lsp/codelens.lua122
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua285
-rw-r--r--runtime/lua/vim/lsp/handlers.lua321
-rw-r--r--runtime/lua/vim/lsp/health.lua19
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua377
-rw-r--r--runtime/lua/vim/lsp/log.lua30
-rw-r--r--runtime/lua/vim/lsp/protocol.lua594
-rw-r--r--runtime/lua/vim/lsp/rpc.lua230
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua325
-rw-r--r--runtime/lua/vim/lsp/sync.lua25
-rw-r--r--runtime/lua/vim/lsp/tagfunc.lua46
-rw-r--r--runtime/lua/vim/lsp/util.lua834
-rw-r--r--runtime/lua/vim/re.lua271
-rw-r--r--runtime/lua/vim/secure.lua34
-rw-r--r--runtime/lua/vim/shared.lua605
-rw-r--r--runtime/lua/vim/snippet.lua591
-rw-r--r--runtime/lua/vim/termcap.lua62
-rw-r--r--runtime/lua/vim/text.lua32
-rw-r--r--runtime/lua/vim/treesitter.lua513
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua456
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua80
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua249
-rw-r--r--runtime/lua/vim/treesitter/_range.lua193
-rw-r--r--runtime/lua/vim/treesitter/dev.lua645
-rw-r--r--runtime/lua/vim/treesitter/health.lua30
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua183
-rw-r--r--runtime/lua/vim/treesitter/language.lua144
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua1041
-rw-r--r--runtime/lua/vim/treesitter/playground.lua186
-rw-r--r--runtime/lua/vim/treesitter/query.lua441
-rw-r--r--runtime/lua/vim/ui.lua111
-rw-r--r--runtime/lua/vim/ui/clipboard/osc52.lua75
-rw-r--r--runtime/lua/vim/uri.lua145
-rw-r--r--runtime/lua/vim/version.lua437
-rw-r--r--runtime/lua/vim/vimhelp.lua31
-rw-r--r--runtime/macros/editexisting.vim4
-rw-r--r--runtime/macros/less.vim5
-rw-r--r--runtime/makemenu.vim12
-rw-r--r--runtime/menu.vim11
-rw-r--r--runtime/mswin.vim10
-rw-r--r--runtime/nvim.appdata.xml4
-rw-r--r--runtime/optwin.vim30
-rw-r--r--runtime/pack/dist/opt/matchit/autoload/matchit.vim17
-rw-r--r--runtime/pack/dist/opt/matchit/doc/matchit.txt26
-rw-r--r--runtime/pack/dist/opt/matchit/plugin/matchit.vim105
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim742
-rw-r--r--runtime/pack/dist/opt/vimball/autoload/vimball.vim775
-rw-r--r--runtime/pack/dist/opt/vimball/doc/vimball.txt273
-rw-r--r--runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim43
-rw-r--r--runtime/plugin/editorconfig.lua2
-rw-r--r--runtime/plugin/gzip.vim5
-rw-r--r--runtime/plugin/health.vim1
-rw-r--r--runtime/plugin/man.lua2
-rw-r--r--runtime/plugin/matchparen.vim26
-rw-r--r--runtime/plugin/netrwPlugin.vim2
-rw-r--r--runtime/plugin/nvim.lua19
-rw-r--r--runtime/plugin/osc52.lua36
-rw-r--r--runtime/plugin/rplugin.vim4
-rw-r--r--runtime/plugin/tarPlugin.vim1
-rw-r--r--runtime/plugin/tohtml.vim56
-rw-r--r--runtime/plugin/zipPlugin.vim6
-rw-r--r--runtime/queries/bash/folds.scm8
-rw-r--r--runtime/queries/bash/highlights.scm145
-rw-r--r--runtime/queries/c/folds.scm20
-rw-r--r--runtime/queries/c/highlights.scm195
-rw-r--r--runtime/queries/c/injections.scm22
-rw-r--r--runtime/queries/help/injections.scm3
-rw-r--r--runtime/queries/lua/folds.scm10
-rw-r--r--runtime/queries/lua/highlights.scm86
-rw-r--r--runtime/queries/lua/injections.scm33
-rw-r--r--runtime/queries/markdown/folds.scm9
-rw-r--r--runtime/queries/markdown/highlights.scm71
-rw-r--r--runtime/queries/markdown/injections.scm25
-rw-r--r--runtime/queries/markdown_inline/highlights.scm102
-rw-r--r--runtime/queries/markdown_inline/injections.scm8
-rw-r--r--runtime/queries/python/folds.scm28
-rw-r--r--runtime/queries/python/highlights.scm351
-rw-r--r--runtime/queries/query/folds.scm6
-rw-r--r--runtime/queries/query/highlights.scm34
-rw-r--r--runtime/queries/vim/folds.scm4
-rw-r--r--runtime/queries/vim/highlights.scm4
-rw-r--r--runtime/queries/vim/injections.scm46
-rw-r--r--runtime/queries/vimdoc/highlights.scm (renamed from runtime/queries/help/highlights.scm)8
-rw-r--r--runtime/queries/vimdoc/injections.scm4
-rw-r--r--runtime/synmenu.vim12
-rw-r--r--runtime/syntax/2html.vim663
-rw-r--r--runtime/syntax/8th.vim2
-rw-r--r--runtime/syntax/README.txt12
-rw-r--r--runtime/syntax/a65.vim2
-rw-r--r--runtime/syntax/aap.vim5
-rw-r--r--runtime/syntax/abap.vim2
-rw-r--r--runtime/syntax/asm68k.vim2
-rw-r--r--runtime/syntax/automake.vim4
-rw-r--r--runtime/syntax/bash.vim4
-rw-r--r--runtime/syntax/bindzone.vim4
-rw-r--r--runtime/syntax/c.vim10
-rw-r--r--runtime/syntax/chaiscript.vim2
-rw-r--r--runtime/syntax/checkhealth.vim11
-rw-r--r--runtime/syntax/cmake.vim2
-rw-r--r--runtime/syntax/colortest.vim5
-rw-r--r--runtime/syntax/conf.vim5
-rw-r--r--runtime/syntax/crontab.vim14
-rw-r--r--runtime/syntax/css.vim6
-rw-r--r--runtime/syntax/ctrlh.vim5
-rw-r--r--runtime/syntax/deb822sources.vim63
-rw-r--r--runtime/syntax/debchangelog.vim59
-rw-r--r--runtime/syntax/debcontrol.vim6
-rw-r--r--runtime/syntax/debcopyright.vim4
-rw-r--r--runtime/syntax/debsources.vim50
-rw-r--r--runtime/syntax/dep3patch.vim4
-rw-r--r--runtime/syntax/diff.vim7
-rw-r--r--runtime/syntax/dosbatch.vim39
-rw-r--r--runtime/syntax/dosini.vim13
-rw-r--r--runtime/syntax/dts.vim5
-rw-r--r--runtime/syntax/editorconfig.vim15
-rw-r--r--runtime/syntax/euphoria4.vim2
-rw-r--r--runtime/syntax/fish.vim225
-rw-r--r--runtime/syntax/flexwiki.vim4
-rw-r--r--runtime/syntax/forth.vim569
-rw-r--r--runtime/syntax/fortran.vim53
-rw-r--r--runtime/syntax/freebasic.vim8
-rw-r--r--runtime/syntax/fstab.vim8
-rw-r--r--runtime/syntax/gdb.vim2
-rw-r--r--runtime/syntax/go.vim11
-rw-r--r--runtime/syntax/gp.vim47
-rw-r--r--runtime/syntax/gpg.vim237
-rw-r--r--runtime/syntax/groovy.vim2
-rw-r--r--runtime/syntax/haskell.vim2
-rw-r--r--runtime/syntax/help.vim5
-rw-r--r--runtime/syntax/hollywood.vim8
-rw-r--r--runtime/syntax/html.vim4
-rw-r--r--runtime/syntax/i3config.vim503
-rw-r--r--runtime/syntax/iss.vim56
-rw-r--r--runtime/syntax/javascript.vim6
-rw-r--r--runtime/syntax/json5.vim73
-rw-r--r--runtime/syntax/kotlin.vim157
-rw-r--r--runtime/syntax/krl.vim21
-rw-r--r--runtime/syntax/lc.vim31
-rw-r--r--runtime/syntax/ld.vim3
-rw-r--r--runtime/syntax/lite.vim2
-rw-r--r--runtime/syntax/livebook.vim8
-rw-r--r--runtime/syntax/logtalk.vim2
-rw-r--r--runtime/syntax/lss.vim2
-rw-r--r--runtime/syntax/luau.vim15
-rw-r--r--runtime/syntax/lynx.vim75
-rw-r--r--runtime/syntax/manual.vim5
-rw-r--r--runtime/syntax/masm.vim187
-rw-r--r--runtime/syntax/meson.vim8
-rw-r--r--runtime/syntax/model.vim7
-rw-r--r--runtime/syntax/modula3.vim4
-rw-r--r--runtime/syntax/mojo.vim316
-rw-r--r--runtime/syntax/muttrc.vim291
-rw-r--r--runtime/syntax/nasm.vim76
-rw-r--r--runtime/syntax/netrc.vim4
-rw-r--r--runtime/syntax/nginx.vim35
-rw-r--r--runtime/syntax/nix.vim10
-rw-r--r--runtime/syntax/nosyntax.vim5
-rw-r--r--runtime/syntax/ora.vim2
-rw-r--r--runtime/syntax/po.vim2
-rw-r--r--runtime/syntax/poefilter.vim4
-rw-r--r--runtime/syntax/ppd.vim4
-rw-r--r--runtime/syntax/pymanifest.vim44
-rw-r--r--runtime/syntax/python.vim38
-rw-r--r--runtime/syntax/python2.vim345
-rw-r--r--runtime/syntax/qf.vim5
-rw-r--r--runtime/syntax/qml.vim1130
-rw-r--r--runtime/syntax/quarto.vim17
-rw-r--r--runtime/syntax/r.vim48
-rw-r--r--runtime/syntax/rapid.vim687
-rw-r--r--runtime/syntax/rmd.vim278
-rw-r--r--runtime/syntax/ruby.vim26
-rw-r--r--runtime/syntax/rust.vim166
-rw-r--r--runtime/syntax/scala.vim2
-rw-r--r--runtime/syntax/sdc.vim2
-rw-r--r--runtime/syntax/sgml.vim2
-rw-r--r--runtime/syntax/sh.vim41
-rw-r--r--runtime/syntax/shared/README.txt2
-rw-r--r--runtime/syntax/shared/debversions.vim29
-rw-r--r--runtime/syntax/shared/typescriptcommon.vim8
-rw-r--r--runtime/syntax/slrnrc.vim2
-rw-r--r--runtime/syntax/solidity.vim10
-rw-r--r--runtime/syntax/spec.vim4
-rw-r--r--runtime/syntax/spup.vim2
-rw-r--r--runtime/syntax/sqlinformix.vim2
-rw-r--r--runtime/syntax/sqlj.vim2
-rw-r--r--runtime/syntax/squid.vim2
-rw-r--r--runtime/syntax/structurizr.vim5
-rw-r--r--runtime/syntax/swayconfig.vim212
-rw-r--r--runtime/syntax/swig.vim99
-rw-r--r--runtime/syntax/synload.vim9
-rw-r--r--runtime/syntax/syntax.vim5
-rw-r--r--runtime/syntax/tasm.vim2
-rw-r--r--runtime/syntax/template.vim5
-rw-r--r--runtime/syntax/tf.vim2
-rw-r--r--runtime/syntax/tpp.vim4
-rw-r--r--runtime/syntax/tsalt.vim6
-rw-r--r--runtime/syntax/tsscl.vim2
-rw-r--r--runtime/syntax/typescript.vim6
-rw-r--r--runtime/syntax/typescriptreact.vim6
-rw-r--r--runtime/syntax/unison.vim103
-rw-r--r--runtime/syntax/urlshortcut.vim14
-rw-r--r--runtime/syntax/vgrindefs.vim5
-rw-r--r--runtime/syntax/vim.vim6
-rw-r--r--runtime/syntax/viminfo.vim5
-rw-r--r--runtime/syntax/wast.vim84
-rw-r--r--runtime/syntax/wat.vim97
-rw-r--r--runtime/syntax/wget.vim294
-rw-r--r--runtime/syntax/wget2.vim56
-rw-r--r--runtime/syntax/xcompose.vim37
-rw-r--r--runtime/syntax/xf86conf.vim10
-rw-r--r--runtime/syntax/xpm.vim181
-rw-r--r--runtime/syntax/zig.vim27
-rw-r--r--runtime/syntax/zimbu.vim5
-rw-r--r--runtime/syntax/zserio.vim112
-rw-r--r--runtime/syntax/zsh.vim2
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor40
-rw-r--r--runtime/tutor/en/vim-01-beginner.tutor.json5
-rw-r--r--runtime/tutor/tutor.tutor10
-rw-r--r--runtime/tutor/tutor.tutor.json66
-rwxr-xr-xscripts/bump_deps.lua44
-rwxr-xr-xscripts/gen_eval_files.lua815
-rw-r--r--scripts/gen_help_html.lua241
-rw-r--r--scripts/gen_lsp.lua290
-rwxr-xr-xscripts/gen_vimdoc.py409
-rwxr-xr-xscripts/genappimage.sh2
-rw-r--r--scripts/lintcommit.lua112
-rw-r--r--scripts/lua2dox.lua798
-rwxr-xr-xscripts/pvscheck.sh495
-rwxr-xr-xscripts/update_terminfo.sh11
-rwxr-xr-xscripts/vim-patch.sh108
-rw-r--r--snap/snapcraft.yaml89
-rw-r--r--src/.asan-blacklist3
-rw-r--r--src/bit.c191
-rw-r--r--src/bit.h3
-rw-r--r--src/cjson/fpconv.c2
-rw-r--r--src/cjson/lua_cjson.c55
-rwxr-xr-xsrc/clint.py702
-rw-r--r--src/klib/khash.h730
-rw-r--r--src/klib/klist.h8
-rw-r--r--src/klib/kvec.h25
-rw-r--r--src/man/nvim.17
-rw-r--r--src/mpack/conv.c3
-rw-r--r--src/mpack/lmpack.c25
-rw-r--r--src/mpack/mpack_core.c3
-rw-r--r--src/mpack/object.c14
-rw-r--r--src/mpack/rpc.c3
-rw-r--r--src/nlua0.c18
-rw-r--r--[-rwxr-xr-x]src/nvim/CMakeLists.txt989
-rw-r--r--src/nvim/README.md50
-rw-r--r--src/nvim/api/autocmd.c600
-rw-r--r--src/nvim/api/autocmd.h9
-rw-r--r--src/nvim/api/buffer.c441
-rw-r--r--src/nvim/api/buffer.h12
-rw-r--r--src/nvim/api/command.c567
-rw-r--r--src/nvim/api/command.h11
-rw-r--r--src/nvim/api/deprecated.c288
-rw-r--r--src/nvim/api/deprecated.h8
-rw-r--r--src/nvim/api/extmark.c878
-rw-r--r--src/nvim/api/extmark.h20
-rw-r--r--src/nvim/api/keysets.lua229
-rw-r--r--src/nvim/api/keysets_defs.h315
-rw-r--r--src/nvim/api/options.c717
-rw-r--r--src/nvim/api/options.h12
-rw-r--r--src/nvim/api/private/converter.c30
-rw-r--r--src/nvim/api/private/converter.h9
-rw-r--r--src/nvim/api/private/defs.h25
-rw-r--r--src/nvim/api/private/dispatch.c3
-rw-r--r--src/nvim/api/private/dispatch.h12
-rw-r--r--src/nvim/api/private/helpers.c205
-rw-r--r--src/nvim/api/private/helpers.h66
-rw-r--r--src/nvim/api/private/validate.c76
-rw-r--r--src/nvim/api/private/validate.h96
-rw-r--r--src/nvim/api/tabpage.c4
-rw-r--r--src/nvim/api/tabpage.h6
-rw-r--r--src/nvim/api/ui.c236
-rw-r--r--src/nvim/api/ui.h14
-rw-r--r--src/nvim/api/ui_events.in.h11
-rw-r--r--src/nvim/api/vim.c688
-rw-r--r--src/nvim/api/vim.h9
-rw-r--r--src/nvim/api/vimscript.c195
-rw-r--r--src/nvim/api/vimscript.h9
-rw-r--r--src/nvim/api/win_config.c482
-rw-r--r--src/nvim/api/win_config.h9
-rw-r--r--src/nvim/api/window.c177
-rw-r--r--src/nvim/api/window.h7
-rw-r--r--src/nvim/arabic.c24
-rw-r--r--src/nvim/arabic.h6
-rw-r--r--src/nvim/arglist.c79
-rw-r--r--src/nvim/arglist.h12
-rw-r--r--src/nvim/arglist_defs.h19
-rw-r--r--src/nvim/ascii_defs.h (renamed from src/nvim/ascii.h)7
-rw-r--r--src/nvim/assert_defs.h (renamed from src/nvim/assert.h)8
-rw-r--r--src/nvim/auevents.lua9
-rw-r--r--src/nvim/autocmd.c1210
-rw-r--r--src/nvim/autocmd.h92
-rw-r--r--src/nvim/autocmd_defs.h71
-rw-r--r--src/nvim/base64.c215
-rw-r--r--src/nvim/base64.h7
-rw-r--r--src/nvim/buffer.c529
-rw-r--r--src/nvim/buffer.h81
-rw-r--r--src/nvim/buffer_defs.h454
-rw-r--r--src/nvim/buffer_updates.c42
-rw-r--r--src/nvim/buffer_updates.h12
-rw-r--r--src/nvim/bufwrite.c1943
-rw-r--r--src/nvim/bufwrite.h9
-rw-r--r--src/nvim/change.c262
-rw-r--r--src/nvim/change.h25
-rw-r--r--src/nvim/channel.c110
-rw-r--r--src/nvim/channel.h19
-rw-r--r--src/nvim/charset.c492
-rw-r--r--src/nvim/charset.h13
-rw-r--r--src/nvim/cmdexpand.c456
-rw-r--r--src/nvim/cmdexpand.h11
-rw-r--r--src/nvim/cmdexpand_defs.h113
-rw-r--r--src/nvim/cmdhist.c79
-rw-r--r--src/nvim/cmdhist.h9
-rw-r--r--src/nvim/context.c50
-rw-r--r--src/nvim/context.h6
-rw-r--r--src/nvim/cursor.c80
-rw-r--r--src/nvim/cursor.h10
-rw-r--r--src/nvim/cursor_shape.c66
-rw-r--r--src/nvim/cursor_shape.h14
-rw-r--r--src/nvim/debugger.c96
-rw-r--r--src/nvim/debugger.h8
-rw-r--r--src/nvim/decoration.c1026
-rw-r--r--src/nvim/decoration.h125
-rw-r--r--src/nvim/decoration_defs.h125
-rw-r--r--src/nvim/decoration_provider.c82
-rw-r--r--src/nvim/decoration_provider.h18
-rw-r--r--src/nvim/diff.c168
-rw-r--r--src/nvim/diff.h16
-rw-r--r--src/nvim/digraph.c101
-rw-r--r--src/nvim/digraph.h11
-rw-r--r--src/nvim/drawline.c3190
-rw-r--r--src/nvim/drawline.h27
-rw-r--r--src/nvim/drawscreen.c1031
-rw-r--r--src/nvim/drawscreen.h14
-rw-r--r--src/nvim/edit.c698
-rw-r--r--src/nvim/edit.h53
-rw-r--r--src/nvim/eval.c2885
-rw-r--r--src/nvim/eval.h53
-rw-r--r--src/nvim/eval.lua13263
-rw-r--r--src/nvim/eval/buffer.c43
-rw-r--r--src/nvim/eval/buffer.h9
-rw-r--r--src/nvim/eval/decode.c34
-rw-r--r--src/nvim/eval/decode.h12
-rw-r--r--src/nvim/eval/encode.c29
-rw-r--r--src/nvim/eval/encode.h11
-rw-r--r--src/nvim/eval/executor.c11
-rw-r--r--src/nvim/eval/executor.h8
-rw-r--r--src/nvim/eval/funcs.c2214
-rw-r--r--src/nvim/eval/funcs.h22
-rw-r--r--src/nvim/eval/gc.c3
-rw-r--r--src/nvim/eval/gc.h7
-rw-r--r--src/nvim/eval/typval.c1302
-rw-r--r--src/nvim/eval/typval.h117
-rw-r--r--src/nvim/eval/typval_defs.h109
-rw-r--r--src/nvim/eval/typval_encode.c.h109
-rw-r--r--src/nvim/eval/typval_encode.h53
-rw-r--r--src/nvim/eval/userfunc.c1161
-rw-r--r--src/nvim/eval/userfunc.h27
-rw-r--r--src/nvim/eval/vars.c1064
-rw-r--r--src/nvim/eval/vars.h12
-rw-r--r--src/nvim/eval/window.c113
-rw-r--r--src/nvim/eval/window.h11
-rw-r--r--src/nvim/event/defs.h5
-rw-r--r--src/nvim/event/libuv_process.c22
-rw-r--r--src/nvim/event/libuv_process.h4
-rw-r--r--src/nvim/event/loop.c11
-rw-r--r--src/nvim/event/loop.h9
-rw-r--r--src/nvim/event/multiqueue.c86
-rw-r--r--src/nvim/event/multiqueue.h4
-rw-r--r--src/nvim/event/process.c33
-rw-r--r--src/nvim/event/process.h13
-rw-r--r--src/nvim/event/rstream.c9
-rw-r--r--src/nvim/event/rstream.h4
-rw-r--r--src/nvim/event/signal.c4
-rw-r--r--src/nvim/event/signal.h4
-rw-r--r--src/nvim/event/socket.c29
-rw-r--r--src/nvim/event/socket.h4
-rw-r--r--src/nvim/event/stream.c14
-rw-r--r--src/nvim/event/stream.h4
-rw-r--r--src/nvim/event/time.c4
-rw-r--r--src/nvim/event/time.h4
-rw-r--r--src/nvim/event/wstream.c6
-rw-r--r--src/nvim/event/wstream.h4
-rw-r--r--src/nvim/ex_cmds.c757
-rw-r--r--src/nvim/ex_cmds.h17
-rw-r--r--src/nvim/ex_cmds.lua24
-rw-r--r--src/nvim/ex_cmds2.c103
-rw-r--r--src/nvim/ex_cmds2.h23
-rw-r--r--src/nvim/ex_cmds_defs.h120
-rw-r--r--src/nvim/ex_docmd.c974
-rw-r--r--src/nvim/ex_docmd.h43
-rw-r--r--src/nvim/ex_eval.c196
-rw-r--r--src/nvim/ex_eval.h8
-rw-r--r--src/nvim/ex_eval_defs.h60
-rw-r--r--src/nvim/ex_getln.c616
-rw-r--r--src/nvim/ex_getln.h13
-rw-r--r--src/nvim/ex_session.c150
-rw-r--r--src/nvim/ex_session.h9
-rw-r--r--src/nvim/extmark.c405
-rw-r--r--src/nvim/extmark.h44
-rw-r--r--src/nvim/extmark_defs.h21
-rw-r--r--src/nvim/file_search.c183
-rw-r--r--src/nvim/file_search.h6
-rw-r--r--src/nvim/fileio.c2308
-rw-r--r--src/nvim/fileio.h43
-rw-r--r--src/nvim/fold.c169
-rw-r--r--src/nvim/fold.h28
-rw-r--r--src/nvim/fold_defs.h15
-rw-r--r--src/nvim/func_attr.h29
-rw-r--r--src/nvim/garray.c6
-rw-r--r--src/nvim/garray.h24
-rw-r--r--src/nvim/garray_defs.h17
-rw-r--r--src/nvim/generators/c_grammar.lua13
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua258
-rw-r--r--[-rwxr-xr-x]src/nvim/generators/gen_api_ui_events.lua21
-rw-r--r--[-rwxr-xr-x]src/nvim/generators/gen_declarations.lua28
-rw-r--r--src/nvim/generators/gen_eval.lua53
-rw-r--r--src/nvim/generators/gen_events.lua36
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua29
-rw-r--r--src/nvim/generators/gen_keysets.lua80
-rw-r--r--src/nvim/generators/gen_options.lua61
-rw-r--r--src/nvim/generators/gen_vimvim.lua (renamed from scripts/genvimvim.lua)46
-rw-r--r--src/nvim/generators/preload.lua10
-rw-r--r--src/nvim/getchar.c683
-rw-r--r--src/nvim/getchar.h30
-rw-r--r--src/nvim/getchar_defs.h63
-rw-r--r--src/nvim/gettext.h11
-rw-r--r--src/nvim/globals.h952
-rw-r--r--src/nvim/grid.c885
-rw-r--r--src/nvim/grid.h59
-rw-r--r--src/nvim/grid_defs.h37
-rw-r--r--src/nvim/hashtab.c28
-rw-r--r--src/nvim/hashtab.h66
-rw-r--r--src/nvim/hashtab_defs.h59
-rw-r--r--src/nvim/help.c136
-rw-r--r--src/nvim/help.h8
-rw-r--r--src/nvim/highlight.c343
-rw-r--r--src/nvim/highlight.h15
-rw-r--r--src/nvim/highlight_defs.h48
-rw-r--r--src/nvim/highlight_group.c304
-rw-r--r--src/nvim/highlight_group.h10
-rw-r--r--src/nvim/iconv_defs.h (renamed from src/nvim/iconv.h)7
-rw-r--r--src/nvim/indent.c243
-rw-r--r--src/nvim/indent.h21
-rw-r--r--src/nvim/indent_c.c60
-rw-r--r--src/nvim/indent_c.h7
-rw-r--r--src/nvim/input.c32
-rw-r--r--src/nvim/input.h6
-rw-r--r--src/nvim/insexpand.c416
-rw-r--r--src/nvim/insexpand.h13
-rw-r--r--src/nvim/keycodes.c210
-rw-r--r--src/nvim/keycodes.h9
-rw-r--r--src/nvim/lib/queue.h5
-rw-r--r--src/nvim/lib/ringbuf.h6
-rw-r--r--src/nvim/linematch.c133
-rw-r--r--src/nvim/linematch.h9
-rw-r--r--src/nvim/locale.c377
-rw-r--r--src/nvim/locale.h10
-rw-r--r--src/nvim/log.c43
-rw-r--r--src/nvim/log.h51
-rw-r--r--src/nvim/lua/base64.c70
-rw-r--r--src/nvim/lua/base64.h7
-rw-r--r--src/nvim/lua/converter.c149
-rw-r--r--src/nvim/lua/converter.h17
-rw-r--r--src/nvim/lua/executor.c285
-rw-r--r--src/nvim/lua/executor.h21
-rw-r--r--src/nvim/lua/secure.c119
-rw-r--r--src/nvim/lua/secure.h7
-rw-r--r--src/nvim/lua/spell.c23
-rw-r--r--src/nvim/lua/spell.h9
-rw-r--r--src/nvim/lua/stdlib.c134
-rw-r--r--src/nvim/lua/stdlib.h7
-rw-r--r--src/nvim/lua/treesitter.c517
-rw-r--r--src/nvim/lua/treesitter.h11
-rw-r--r--src/nvim/lua/xdiff.c76
-rw-r--r--src/nvim/lua/xdiff.h9
-rw-r--r--src/nvim/macros_defs.h (renamed from src/nvim/macros.h)38
-rw-r--r--src/nvim/main.c313
-rw-r--r--src/nvim/main.h4
-rw-r--r--src/nvim/map.c284
-rw-r--r--src/nvim/map.h96
-rw-r--r--src/nvim/map_defs.h224
-rw-r--r--src/nvim/map_glyph_cache.c109
-rw-r--r--src/nvim/map_key_impl.c.h159
-rw-r--r--src/nvim/map_value_impl.c.h64
-rw-r--r--src/nvim/mapping.c669
-rw-r--r--src/nvim/mapping.h78
-rw-r--r--src/nvim/mapping_defs.h29
-rw-r--r--src/nvim/mark.c318
-rw-r--r--src/nvim/mark.h16
-rw-r--r--src/nvim/mark_defs.h12
-rw-r--r--src/nvim/marktree.c1636
-rw-r--r--src/nvim/marktree.h183
-rw-r--r--src/nvim/match.c125
-rw-r--r--src/nvim/match.h11
-rw-r--r--src/nvim/math.c6
-rw-r--r--src/nvim/math.h4
-rw-r--r--src/nvim/mbyte.c466
-rw-r--r--src/nvim/mbyte.h13
-rw-r--r--src/nvim/mbyte_defs.h12
-rw-r--r--src/nvim/memfile.c341
-rw-r--r--src/nvim/memfile.h27
-rw-r--r--src/nvim/memfile_defs.h105
-rw-r--r--src/nvim/memline.c1088
-rw-r--r--src/nvim/memline.h11
-rw-r--r--src/nvim/memline_defs.h16
-rw-r--r--src/nvim/memory.c63
-rw-r--r--src/nvim/memory.h42
-rw-r--r--src/nvim/memory_defs.h15
-rw-r--r--src/nvim/menu.c162
-rw-r--r--src/nvim/menu.h13
-rw-r--r--src/nvim/menu_defs.h7
-rw-r--r--src/nvim/message.c823
-rw-r--r--src/nvim/message.h97
-rw-r--r--src/nvim/mouse.c737
-rw-r--r--src/nvim/mouse.h16
-rw-r--r--src/nvim/move.c1049
-rw-r--r--src/nvim/move.h9
-rw-r--r--src/nvim/msgpack_rpc/channel.c263
-rw-r--r--src/nvim/msgpack_rpc/channel.h17
-rw-r--r--src/nvim/msgpack_rpc/channel_defs.h21
-rw-r--r--src/nvim/msgpack_rpc/helpers.c22
-rw-r--r--src/nvim/msgpack_rpc/helpers.h5
-rw-r--r--src/nvim/msgpack_rpc/server.c13
-rw-r--r--src/nvim/msgpack_rpc/server.h6
-rw-r--r--src/nvim/msgpack_rpc/unpacker.c118
-rw-r--r--src/nvim/msgpack_rpc/unpacker.h9
-rw-r--r--src/nvim/normal.c660
-rw-r--r--src/nvim/normal.h89
-rw-r--r--src/nvim/normal_defs.h75
-rw-r--r--src/nvim/ops.c750
-rw-r--r--src/nvim/ops.h146
-rw-r--r--src/nvim/option.c4799
-rw-r--r--src/nvim/option.h126
-rw-r--r--src/nvim/option_defs.h1129
-rw-r--r--src/nvim/option_vars.h948
-rw-r--r--src/nvim/options.lua12962
-rw-r--r--src/nvim/optionstr.c3090
-rw-r--r--src/nvim/optionstr.h11
-rw-r--r--src/nvim/os/dl.c5
-rw-r--r--src/nvim/os/dl.h7
-rw-r--r--src/nvim/os/env.c81
-rw-r--r--src/nvim/os/fileio.c21
-rw-r--r--src/nvim/os/fileio.h4
-rw-r--r--src/nvim/os/fs.c239
-rw-r--r--src/nvim/os/fs.h13
-rw-r--r--src/nvim/os/fs_defs.h5
-rw-r--r--src/nvim/os/input.c46
-rw-r--r--src/nvim/os/input.h11
-rw-r--r--src/nvim/os/lang.c337
-rw-r--r--src/nvim/os/lang.h8
-rw-r--r--src/nvim/os/mem.c3
-rw-r--r--src/nvim/os/nvim.manifest5
-rw-r--r--src/nvim/os/os.h31
-rw-r--r--src/nvim/os/os_defs.h44
-rw-r--r--src/nvim/os/os_win_console.c8
-rw-r--r--src/nvim/os/os_win_console.h5
-rw-r--r--src/nvim/os/process.c33
-rw-r--r--src/nvim/os/process.h11
-rw-r--r--src/nvim/os/pty_conpty_win.c11
-rw-r--r--src/nvim/os/pty_conpty_win.h11
-rw-r--r--src/nvim/os/pty_process.h4
-rw-r--r--src/nvim/os/pty_process_unix.c39
-rw-r--r--src/nvim/os/pty_process_unix.h6
-rw-r--r--src/nvim/os/pty_process_win.c16
-rw-r--r--src/nvim/os/pty_process_win.h6
-rw-r--r--src/nvim/os/shell.c92
-rw-r--r--src/nvim/os/shell.h8
-rw-r--r--src/nvim/os/signal.c18
-rw-r--r--src/nvim/os/signal.h4
-rw-r--r--src/nvim/os/stdpaths.c70
-rw-r--r--src/nvim/os/stdpaths_defs.h5
-rw-r--r--src/nvim/os/time.c71
-rw-r--r--src/nvim/os/time.h11
-rw-r--r--src/nvim/os/time_defs.h4
-rw-r--r--src/nvim/os/tty.c3
-rw-r--r--src/nvim/os/tty.h6
-rw-r--r--src/nvim/os/unix_defs.h16
-rw-r--r--src/nvim/os/users.c24
-rw-r--r--src/nvim/os/win_defs.h6
-rw-r--r--src/nvim/path.c166
-rw-r--r--src/nvim/path.h11
-rw-r--r--src/nvim/plines.c1126
-rw-r--r--src/nvim/plines.h25
-rw-r--r--src/nvim/po/CMakeLists.txt26
-rw-r--r--src/nvim/po/check.vim21
-rw-r--r--src/nvim/po/da.po2
-rw-r--r--src/nvim/po/eo.po2
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/fr.po2
-rw-r--r--src/nvim/po/tr.po1530
-rw-r--r--src/nvim/po/uk.po3829
-rw-r--r--src/nvim/popupmenu.c321
-rw-r--r--src/nvim/popupmenu.h12
-rw-r--r--src/nvim/pos_defs.h (renamed from src/nvim/pos.h)5
-rw-r--r--src/nvim/profile.c146
-rw-r--r--src/nvim/profile.h12
-rw-r--r--src/nvim/quickfix.c609
-rw-r--r--src/nvim/quickfix.h21
-rw-r--r--src/nvim/rbuffer.c6
-rw-r--r--src/nvim/rbuffer.h9
-rw-r--r--src/nvim/regexp.c13510
-rw-r--r--src/nvim/regexp.h17
-rw-r--r--src/nvim/regexp_bt.c5661
-rw-r--r--src/nvim/regexp_defs.h124
-rw-r--r--src/nvim/regexp_nfa.c7639
-rw-r--r--src/nvim/runtime.c925
-rw-r--r--src/nvim/runtime.h124
-rw-r--r--src/nvim/runtime_defs.h76
-rw-r--r--src/nvim/screen.c1110
-rw-r--r--src/nvim/screen.h19
-rw-r--r--src/nvim/search.c345
-rw-r--r--src/nvim/search.h18
-rw-r--r--src/nvim/sha256.c11
-rw-r--r--src/nvim/sha256.h8
-rw-r--r--src/nvim/shada.c423
-rw-r--r--src/nvim/shada.h6
-rw-r--r--src/nvim/sign.c1907
-rw-r--r--src/nvim/sign.h14
-rw-r--r--src/nvim/sign_defs.h64
-rw-r--r--src/nvim/spell.c462
-rw-r--r--src/nvim/spell.h25
-rw-r--r--src/nvim/spell_defs.h363
-rw-r--r--src/nvim/spellfile.c1070
-rw-r--r--src/nvim/spellfile.h11
-rw-r--r--src/nvim/spellsuggest.c777
-rw-r--r--src/nvim/spellsuggest.h6
-rw-r--r--src/nvim/state.c81
-rw-r--r--src/nvim/state.h19
-rw-r--r--src/nvim/state_defs.h45
-rw-r--r--src/nvim/statusline.c650
-rw-r--r--src/nvim/statusline.h15
-rw-r--r--src/nvim/statusline_defs.h59
-rw-r--r--src/nvim/strings.c1665
-rw-r--r--src/nvim/strings.h34
-rw-r--r--src/nvim/syntax.c283
-rw-r--r--src/nvim/syntax.h13
-rw-r--r--src/nvim/syntax_defs.h7
-rw-r--r--src/nvim/tag.c462
-rw-r--r--src/nvim/tag.h66
-rw-r--r--src/nvim/terminal.c196
-rw-r--r--src/nvim/terminal.h7
-rw-r--r--src/nvim/testdir/sautest/autoload/sourced.vim3
-rw-r--r--src/nvim/testdir/test_behave.vim29
-rw-r--r--src/nvim/testdir/test_blob.vim367
-rw-r--r--src/nvim/testdir/test_delete.vim110
-rw-r--r--src/nvim/testdir/test_expr.vim676
-rw-r--r--src/nvim/testdir/test_filter_map.vim108
-rw-r--r--src/nvim/testdir/test_listdict.vim1070
-rw-r--r--src/nvim/testdir/test_maparg.vim325
-rw-r--r--src/nvim/testdir/test_options.vim1304
-rw-r--r--src/nvim/testdir/test_python2.vim173
-rw-r--r--src/nvim/testdir/test_pyx2.vim81
-rw-r--r--src/nvim/testdir/test_scriptnames.vim32
-rw-r--r--src/nvim/testdir/test_scroll_opt.vim36
-rw-r--r--src/nvim/testdir/test_termcodes.vim63
-rw-r--r--src/nvim/testing.c157
-rw-r--r--src/nvim/testing.h7
-rw-r--r--src/nvim/textformat.c97
-rw-r--r--src/nvim/textformat.h8
-rw-r--r--src/nvim/textobject.c192
-rw-r--r--src/nvim/textobject.h10
-rw-r--r--src/nvim/tui/input.c386
-rw-r--r--src/nvim/tui/input.h32
-rw-r--r--src/nvim/tui/input_defs.h5
-rw-r--r--src/nvim/tui/terminfo.c5
-rw-r--r--src/nvim/tui/terminfo.h9
-rw-r--r--src/nvim/tui/terminfo_defs.h11
-rw-r--r--src/nvim/tui/tui.c483
-rw-r--r--src/nvim/tui/tui.h21
-rw-r--r--src/nvim/types_defs.h (renamed from src/nvim/types.h)25
-rw-r--r--src/nvim/ugrid.c11
-rw-r--r--src/nvim/ugrid.h28
-rw-r--r--src/nvim/ui.c136
-rw-r--r--src/nvim/ui.h22
-rw-r--r--src/nvim/ui_client.c38
-rw-r--r--src/nvim/ui_client.h32
-rw-r--r--src/nvim/ui_compositor.c66
-rw-r--r--src/nvim/ui_compositor.h7
-rw-r--r--src/nvim/undo.c295
-rw-r--r--src/nvim/undo.h11
-rw-r--r--src/nvim/undo_defs.h90
-rw-r--r--src/nvim/usercmd.c288
-rw-r--r--src/nvim/usercmd.h34
-rw-r--r--src/nvim/version.c454
-rw-r--r--src/nvim/version.h11
-rw-r--r--src/nvim/vim.h274
-rw-r--r--src/nvim/vim_defs.h41
-rw-r--r--src/nvim/viml/parser/expressions.c44
-rw-r--r--src/nvim/viml/parser/expressions.h8
-rw-r--r--src/nvim/viml/parser/parser.c3
-rw-r--r--src/nvim/viml/parser/parser.h5
-rw-r--r--src/nvim/window.c831
-rw-r--r--src/nvim/window.h83
-rw-r--r--src/nvim/winfloat.c289
-rw-r--r--src/nvim/winfloat.h8
-rw-r--r--src/uncrustify.cfg386
-rw-r--r--src/xdiff/xdiff.h8
-rw-r--r--test/.luarc.json28
-rw-r--r--test/CMakeLists.txt70
-rw-r--r--test/README.md42
-rw-r--r--test/benchmark/autocmd_spec.lua175
-rw-r--r--test/benchmark/bench_regexp_spec.lua2
-rw-r--r--test/benchmark/iter_spec.lua215
-rw-r--r--test/benchmark/treesitter_spec.lua4
-rw-r--r--test/busted/outputHandlers/nvim.lua12
-rw-r--r--test/busted_runner.lua1
-rw-r--r--test/client/msgpack_rpc_stream.lua112
-rw-r--r--test/client/session.lua198
-rw-r--r--test/client/uv_stream.lua169
-rw-r--r--test/compat.lua12
-rw-r--r--test/deprecated.lua2
-rw-r--r--test/functional/api/autocmd_spec.lua191
-rw-r--r--test/functional/api/buffer_spec.lua1276
-rw-r--r--test/functional/api/buffer_updates_spec.lua4
-rw-r--r--test/functional/api/command_spec.lua16
-rw-r--r--test/functional/api/extmark_spec.lua234
-rw-r--r--test/functional/api/highlight_spec.lua369
-rw-r--r--test/functional/api/keymap_spec.lua214
-rw-r--r--test/functional/api/proc_spec.lua12
-rw-r--r--test/functional/api/rpc_fixture.lua7
-rw-r--r--test/functional/api/server_notifications_spec.lua3
-rw-r--r--test/functional/api/server_requests_spec.lua19
-rw-r--r--test/functional/api/ui_spec.lua84
-rw-r--r--test/functional/api/version_spec.lua2
-rw-r--r--test/functional/api/vim_spec.lua876
-rw-r--r--test/functional/api/window_spec.lua354
-rw-r--r--test/functional/autocmd/autocmd_oldtest_spec.lua54
-rw-r--r--test/functional/autocmd/autocmd_spec.lua36
-rw-r--r--test/functional/autocmd/cursorhold_spec.lua6
-rw-r--r--test/functional/autocmd/cursormoved_spec.lua49
-rw-r--r--test/functional/autocmd/dirchanged_spec.lua4
-rw-r--r--test/functional/autocmd/focus_spec.lua5
-rw-r--r--test/functional/autocmd/modechanged_spec.lua39
-rw-r--r--test/functional/autocmd/safestate_spec.lua57
-rw-r--r--test/functional/autocmd/show_spec.lua41
-rw-r--r--test/functional/autocmd/tabnewentered_spec.lua12
-rw-r--r--test/functional/autocmd/termxx_spec.lua63
-rw-r--r--test/functional/autocmd/textchanged_spec.lua182
-rw-r--r--test/functional/autocmd/textyankpost_spec.lua2
-rw-r--r--test/functional/autocmd/win_scrolled_resized_spec.lua3
-rw-r--r--test/functional/core/channels_spec.lua2
-rw-r--r--test/functional/core/exit_spec.lua38
-rw-r--r--test/functional/core/fileio_spec.lua108
-rw-r--r--test/functional/core/job_spec.lua41
-rw-r--r--test/functional/core/main_spec.lua29
-rw-r--r--test/functional/core/path_spec.lua114
-rw-r--r--test/functional/core/remote_spec.lua41
-rw-r--r--test/functional/core/spellfile_spec.lua26
-rw-r--r--test/functional/core/startup_spec.lua401
-rw-r--r--test/functional/editor/K_spec.lua2
-rw-r--r--test/functional/editor/completion_spec.lua127
-rw-r--r--test/functional/editor/fold_spec.lua140
-rw-r--r--test/functional/editor/jump_spec.lua42
-rw-r--r--test/functional/editor/mark_spec.lua87
-rw-r--r--test/functional/editor/mode_cmdline_spec.lua28
-rw-r--r--test/functional/editor/mode_insert_spec.lua85
-rw-r--r--test/functional/editor/put_spec.lua41
-rw-r--r--test/functional/ex_cmds/append_spec.lua3
-rw-r--r--test/functional/ex_cmds/cd_spec.lua9
-rw-r--r--test/functional/ex_cmds/cmd_map_spec.lua6
-rw-r--r--test/functional/ex_cmds/dict_notifications_spec.lua2
-rw-r--r--test/functional/ex_cmds/excmd_spec.lua37
-rw-r--r--test/functional/ex_cmds/file_spec.lua7
-rw-r--r--test/functional/ex_cmds/help_spec.lua17
-rw-r--r--test/functional/ex_cmds/highlight_spec.lua30
-rw-r--r--test/functional/ex_cmds/ls_spec.lua2
-rw-r--r--test/functional/ex_cmds/make_spec.lua12
-rw-r--r--test/functional/ex_cmds/map_spec.lua4
-rw-r--r--test/functional/ex_cmds/mksession_spec.lua20
-rw-r--r--test/functional/ex_cmds/mkview_spec.lua6
-rw-r--r--test/functional/ex_cmds/oldfiles_spec.lua5
-rw-r--r--test/functional/ex_cmds/profile_spec.lua13
-rw-r--r--test/functional/ex_cmds/script_spec.lua5
-rw-r--r--test/functional/ex_cmds/source_spec.lua130
-rw-r--r--test/functional/ex_cmds/swapfile_preserve_recover_spec.lua235
-rw-r--r--test/functional/ex_cmds/trust_spec.lua117
-rw-r--r--test/functional/ex_cmds/verbose_spec.lua25
-rw-r--r--test/functional/ex_cmds/write_spec.lua12
-rw-r--r--test/functional/ex_cmds/wviminfo_spec.lua8
-rw-r--r--test/functional/fixtures/api_level_11.mpackbin0 -> 31225 bytes
-rw-r--r--test/functional/fixtures/autoload/health/broken.vim3
-rw-r--r--test/functional/fixtures/autoload/health/full_render.vim8
-rw-r--r--test/functional/fixtures/autoload/health/success1.vim6
-rw-r--r--test/functional/fixtures/autoload/health/success2.vim4
-rw-r--r--test/functional/fixtures/fake-lsp-server.lua62
-rw-r--r--test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim3
-rw-r--r--test/functional/fixtures/lua/test_plug/full_render/health.lua12
-rw-r--r--test/functional/fixtures/lua/test_plug/health/init.lua8
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule/health.lua8
-rw-r--r--test/functional/fixtures/lua/test_plug/submodule_failed/health.lua11
-rw-r--r--test/functional/fixtures/lua/test_plug/success1/health.lua10
-rw-r--r--test/functional/fixtures/lua/test_plug/success2/health.lua8
-rw-r--r--test/functional/fixtures/printargs-test.c3
-rw-r--r--test/functional/fixtures/printenv-test.c3
-rw-r--r--test/functional/fixtures/shell-test.c3
-rw-r--r--test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua2
-rw-r--r--test/functional/fixtures/streams-test.c3
-rw-r--r--test/functional/fixtures/tty-test.c11
-rw-r--r--test/functional/fixtures/wildpum/Xnamedir/XdirA/XdirB/XfileC (renamed from test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC)0
-rw-r--r--test/functional/fixtures/wildpum/Xnamedir/XdirA/XfileB (renamed from test/functional/fixtures/wildpum/Xdir/XdirA/XfileB)0
-rw-r--r--test/functional/fixtures/wildpum/Xnamedir/XfileA (renamed from test/functional/fixtures/wildpum/Xdir/XfileA)0
-rw-r--r--test/functional/helpers.lua102
-rw-r--r--test/functional/legacy/011_autocommands_spec.lua6
-rw-r--r--test/functional/legacy/012_directory_spec.lua28
-rw-r--r--test/functional/legacy/061_undo_tree_spec.lua17
-rw-r--r--test/functional/legacy/074_global_var_in_viminfo_spec.lua4
-rw-r--r--test/functional/legacy/088_conceal_tabs_spec.lua97
-rw-r--r--test/functional/legacy/assert_spec.lua8
-rw-r--r--test/functional/legacy/autochdir_spec.lua4
-rw-r--r--test/functional/legacy/autocmd_option_spec.lua148
-rw-r--r--test/functional/legacy/breakindent_spec.lua36
-rw-r--r--test/functional/legacy/buffer_spec.lua2
-rw-r--r--test/functional/legacy/cmdline_spec.lua25
-rw-r--r--test/functional/legacy/conceal_spec.lua587
-rw-r--r--test/functional/legacy/crash_spec.lua16
-rw-r--r--test/functional/legacy/debugger_spec.lua85
-rw-r--r--test/functional/legacy/delete_spec.lua2
-rw-r--r--test/functional/legacy/digraph_spec.lua6
-rw-r--r--test/functional/legacy/display_spec.lua190
-rw-r--r--test/functional/legacy/edit_spec.lua66
-rw-r--r--test/functional/legacy/eval_spec.lua10
-rw-r--r--test/functional/legacy/filechanged_spec.lua4
-rw-r--r--test/functional/legacy/glob2regpat_spec.lua8
-rw-r--r--test/functional/legacy/highlight_spec.lua (renamed from test/functional/legacy/051_highlight_spec.lua)34
-rw-r--r--test/functional/legacy/matchparen_spec.lua116
-rw-r--r--test/functional/legacy/memory_usage_spec.lua8
-rw-r--r--test/functional/legacy/messages_spec.lua119
-rw-r--r--test/functional/legacy/move_spec.lua49
-rw-r--r--test/functional/legacy/normal_spec.lua41
-rw-r--r--test/functional/legacy/options_spec.lua2
-rw-r--r--test/functional/legacy/prompt_buffer_spec.lua133
-rw-r--r--test/functional/legacy/put_spec.lua53
-rw-r--r--test/functional/legacy/scroll_opt_spec.lua1193
-rw-r--r--test/functional/legacy/search_spec.lua44
-rw-r--r--test/functional/legacy/search_stat_spec.lua64
-rw-r--r--test/functional/legacy/vimscript_spec.lua2
-rw-r--r--test/functional/legacy/visual_mode_spec.lua79
-rw-r--r--test/functional/legacy/visual_spec.lua69
-rw-r--r--test/functional/legacy/window_cmd_spec.lua110
-rw-r--r--test/functional/lua/api_spec.lua54
-rw-r--r--test/functional/lua/base64_spec.lua105
-rw-r--r--test/functional/lua/buffer_updates_spec.lua110
-rw-r--r--test/functional/lua/command_line_completion_spec.lua15
-rw-r--r--test/functional/lua/commands_spec.lua53
-rw-r--r--test/functional/lua/diagnostic_spec.lua102
-rw-r--r--test/functional/lua/ffi_spec.lua10
-rw-r--r--test/functional/lua/filetype_spec.lua37
-rw-r--r--test/functional/lua/fs_spec.lua58
-rw-r--r--test/functional/lua/help_spec.lua9
-rw-r--r--test/functional/lua/highlight_spec.lua2
-rw-r--r--test/functional/lua/inspector_spec.lua60
-rw-r--r--test/functional/lua/iter_spec.lua402
-rw-r--r--test/functional/lua/json_spec.lua111
-rw-r--r--test/functional/lua/loader_spec.lua56
-rw-r--r--test/functional/lua/loop_spec.lua18
-rw-r--r--test/functional/lua/luaeval_spec.lua18
-rw-r--r--test/functional/lua/overrides_spec.lua57
-rw-r--r--test/functional/lua/runtime_spec.lua212
-rw-r--r--test/functional/lua/secure_spec.lua27
-rw-r--r--test/functional/lua/snippet_spec.lua202
-rw-r--r--test/functional/lua/system_spec.lua100
-rw-r--r--test/functional/lua/text_spec.lua23
-rw-r--r--test/functional/lua/thread_spec.lua42
-rw-r--r--test/functional/lua/ui_event_spec.lua8
-rw-r--r--test/functional/lua/ui_spec.lua27
-rw-r--r--test/functional/lua/version_spec.lua273
-rw-r--r--test/functional/lua/vim_spec.lua487
-rw-r--r--test/functional/lua/watch_spec.lua178
-rw-r--r--test/functional/options/autochdir_spec.lua9
-rw-r--r--test/functional/options/cursorbind_spec.lua1
-rw-r--r--test/functional/options/defaults_spec.lua146
-rw-r--r--test/functional/options/keymap_spec.lua4
-rw-r--r--test/functional/options/mousescroll_spec.lua2
-rw-r--r--test/functional/options/num_options_spec.lua22
-rw-r--r--test/functional/options/pastetoggle_spec.lua90
-rw-r--r--test/functional/plugin/editorconfig_spec.lua19
-rw-r--r--test/functional/plugin/health_spec.lua131
-rw-r--r--test/functional/plugin/lsp/completion_spec.lua239
-rw-r--r--test/functional/plugin/lsp/diagnostic_spec.lua159
-rw-r--r--test/functional/plugin/lsp/helpers.lua4
-rw-r--r--test/functional/plugin/lsp/incremental_sync_spec.lua2
-rw-r--r--test/functional/plugin/lsp/inlay_hint_spec.lua204
-rw-r--r--test/functional/plugin/lsp/semantic_tokens_spec.lua450
-rw-r--r--test/functional/plugin/lsp/snippet_spec.lua261
-rw-r--r--test/functional/plugin/lsp/utils_spec.lua226
-rw-r--r--test/functional/plugin/lsp/watchfiles_spec.lua222
-rw-r--r--test/functional/plugin/lsp_spec.lua1198
-rw-r--r--test/functional/plugin/man_spec.lua54
-rw-r--r--test/functional/plugin/shada_spec.lua34
-rw-r--r--test/functional/plugin/tutor_spec.lua95
-rw-r--r--test/functional/provider/clipboard_spec.lua6
-rw-r--r--test/functional/provider/perl_spec.lua9
-rw-r--r--test/functional/provider/provider_spec.lua2
-rw-r--r--test/functional/provider/ruby_spec.lua3
-rw-r--r--test/functional/shada/buffers_spec.lua8
-rw-r--r--test/functional/shada/helpers.lua1
-rw-r--r--test/functional/shada/history_spec.lua9
-rw-r--r--test/functional/shada/marks_spec.lua46
-rw-r--r--test/functional/shada/merging_spec.lua2
-rw-r--r--test/functional/shada/shada_spec.lua82
-rw-r--r--test/functional/terminal/api_spec.lua8
-rw-r--r--test/functional/terminal/buffer_spec.lua158
-rw-r--r--test/functional/terminal/channel_spec.lua96
-rw-r--r--test/functional/terminal/cursor_spec.lua71
-rw-r--r--test/functional/terminal/edit_spec.lua7
-rw-r--r--test/functional/terminal/ex_terminal_spec.lua59
-rw-r--r--test/functional/terminal/helpers.lua9
-rw-r--r--test/functional/terminal/mouse_spec.lua78
-rw-r--r--test/functional/terminal/scrollback_spec.lua75
-rw-r--r--test/functional/terminal/tui_spec.lua921
-rw-r--r--test/functional/terminal/window_spec.lua45
-rw-r--r--test/functional/terminal/window_split_tab_spec.lua4
-rw-r--r--test/functional/treesitter/fold_spec.lua474
-rw-r--r--test/functional/treesitter/highlight_spec.lua291
-rw-r--r--test/functional/treesitter/language_spec.lua18
-rw-r--r--test/functional/treesitter/node_spec.lua28
-rw-r--r--test/functional/treesitter/parser_spec.lua573
-rw-r--r--test/functional/treesitter/utils_spec.lua17
-rw-r--r--test/functional/ui/bufhl_spec.lua11
-rw-r--r--test/functional/ui/cmdline_highlight_spec.lua17
-rw-r--r--test/functional/ui/cmdline_spec.lua181
-rw-r--r--test/functional/ui/cursor_spec.lua12
-rw-r--r--test/functional/ui/decorations_spec.lua3296
-rw-r--r--test/functional/ui/diff_spec.lua94
-rw-r--r--test/functional/ui/embed_spec.lua53
-rw-r--r--test/functional/ui/float_spec.lua3002
-rw-r--r--test/functional/ui/fold_spec.lua1306
-rw-r--r--test/functional/ui/highlight_spec.lua136
-rw-r--r--test/functional/ui/inccommand_spec.lua151
-rw-r--r--test/functional/ui/inccommand_user_spec.lua146
-rw-r--r--test/functional/ui/linematch_spec.lua230
-rw-r--r--test/functional/ui/messages_spec.lua190
-rw-r--r--test/functional/ui/mode_spec.lua22
-rw-r--r--test/functional/ui/mouse_spec.lua237
-rw-r--r--test/functional/ui/multibyte_spec.lua137
-rw-r--r--test/functional/ui/multigrid_spec.lua1608
-rw-r--r--test/functional/ui/options_spec.lua33
-rw-r--r--test/functional/ui/output_spec.lua7
-rw-r--r--test/functional/ui/popupmenu_spec.lua6213
-rw-r--r--test/functional/ui/quickfix_spec.lua2
-rw-r--r--test/functional/ui/screen.lua58
-rw-r--r--test/functional/ui/screen_basic_spec.lua217
-rw-r--r--test/functional/ui/searchhl_spec.lua111
-rw-r--r--test/functional/ui/sign_spec.lua44
-rw-r--r--test/functional/ui/spell_spec.lua366
-rw-r--r--test/functional/ui/statuscolumn_spec.lua517
-rw-r--r--test/functional/ui/statusline_spec.lua598
-rw-r--r--test/functional/ui/tabline_spec.lua73
-rw-r--r--test/functional/ui/title_spec.lua138
-rw-r--r--test/functional/ui/wildmode_spec.lua99
-rw-r--r--test/functional/ui/winbar_spec.lua66
-rw-r--r--test/functional/vimscript/api_functions_spec.lua50
-rw-r--r--test/functional/vimscript/buf_functions_spec.lua33
-rw-r--r--test/functional/vimscript/ctx_functions_spec.lua6
-rw-r--r--test/functional/vimscript/environ_spec.lua2
-rw-r--r--test/functional/vimscript/eval_spec.lua112
-rw-r--r--test/functional/vimscript/executable_spec.lua6
-rw-r--r--test/functional/vimscript/execute_spec.lua26
-rw-r--r--test/functional/vimscript/glob_spec.lua7
-rw-r--r--test/functional/vimscript/has_spec.lua21
-rw-r--r--test/functional/vimscript/input_spec.lua36
-rw-r--r--test/functional/vimscript/json_functions_spec.lua2
-rw-r--r--test/functional/vimscript/let_spec.lua14
-rw-r--r--test/functional/vimscript/map_functions_spec.lua12
-rw-r--r--test/functional/vimscript/null_spec.lua12
-rw-r--r--test/functional/vimscript/screenpos_spec.lua50
-rw-r--r--test/functional/vimscript/special_vars_spec.lua6
-rw-r--r--test/functional/vimscript/state_spec.lua86
-rw-r--r--test/functional/vimscript/system_spec.lua16
-rw-r--r--test/functional/vimscript/timer_spec.lua10
-rw-r--r--test/functional/vimscript/writefile_spec.lua19
-rw-r--r--test/helpers.lua88
-rw-r--r--test/lua_runner.lua83
-rw-r--r--test/old/memfile_test.c (renamed from src/nvim/testdir/samples/memfile_test.c)35
-rw-r--r--test/old/testdir/Make_all.mak (renamed from src/nvim/testdir/Make_all.mak)0
-rw-r--r--test/old/testdir/Makefile (renamed from src/nvim/testdir/Makefile)11
-rw-r--r--test/old/testdir/README.txt (renamed from src/nvim/testdir/README.txt)0
-rw-r--r--test/old/testdir/check.vim (renamed from src/nvim/testdir/check.vim)26
-rw-r--r--test/old/testdir/crash/bt_quickfix1_poc5
-rw-r--r--test/old/testdir/crash/bt_quickfix_poc9
-rw-r--r--test/old/testdir/crash/crash_scrollbar2
-rw-r--r--test/old/testdir/crash/editing_arg_idx_POC_1bin0 -> 398 bytes
-rw-r--r--test/old/testdir/crash/poc1bin0 -> 3264 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf1bin0 -> 1541 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf2bin0 -> 3238 bytes
-rw-r--r--test/old/testdir/crash/poc_huaf3bin0 -> 4053 bytes
-rw-r--r--test/old/testdir/crash/poc_tagfunc.vim6
-rw-r--r--test/old/testdir/crash/vim_msg_trunc_poc1
-rw-r--r--test/old/testdir/crash/vim_regsub_both10
-rw-r--r--test/old/testdir/crash/vim_regsub_both_pocbin0 -> 244 bytes
-rw-r--r--test/old/testdir/dotest.in (renamed from src/nvim/testdir/dotest.in)0
-rw-r--r--test/old/testdir/load.vim (renamed from src/nvim/testdir/load.vim)0
-rw-r--r--test/old/testdir/mouse.vim97
-rw-r--r--test/old/testdir/pyxfile/py2_magic.py (renamed from src/nvim/testdir/pyxfile/py2_magic.py)0
-rw-r--r--test/old/testdir/pyxfile/py2_shebang.py (renamed from src/nvim/testdir/pyxfile/py2_shebang.py)0
-rw-r--r--test/old/testdir/pyxfile/py3_magic.py (renamed from src/nvim/testdir/pyxfile/py3_magic.py)0
-rw-r--r--test/old/testdir/pyxfile/py3_shebang.py (renamed from src/nvim/testdir/pyxfile/py3_shebang.py)0
-rw-r--r--test/old/testdir/pyxfile/pyx.py (renamed from src/nvim/testdir/pyxfile/pyx.py)0
-rwxr-xr-xtest/old/testdir/runnvim.sh (renamed from src/nvim/testdir/runnvim.sh)4
-rw-r--r--test/old/testdir/runnvim.vim (renamed from src/nvim/testdir/runnvim.vim)6
-rw-r--r--test/old/testdir/runtest.vim (renamed from src/nvim/testdir/runtest.vim)187
-rw-r--r--test/old/testdir/samples/box.txt601
-rw-r--r--test/old/testdir/samples/matchparen.vim234
-rw-r--r--test/old/testdir/samples/quickfix.txt (renamed from src/nvim/testdir/samples/quickfix.txt)0
-rw-r--r--test/old/testdir/samples/re.freeze.txt (renamed from src/nvim/testdir/samples/re.freeze.txt)0
-rw-r--r--test/old/testdir/sautest/autoload/foo.vim (renamed from src/nvim/testdir/sautest/autoload/foo.vim)0
-rw-r--r--test/old/testdir/sautest/autoload/footest.vim (renamed from src/nvim/testdir/sautest/autoload/footest.vim)0
-rw-r--r--test/old/testdir/sautest/autoload/globone.vim (renamed from src/nvim/testdir/sautest/autoload/globone.vim)0
-rw-r--r--test/old/testdir/sautest/autoload/globtwo.vim (renamed from src/nvim/testdir/sautest/autoload/globtwo.vim)0
-rw-r--r--test/old/testdir/sautest/autoload/sourced.vim4
-rw-r--r--test/old/testdir/screendump.vim (renamed from src/nvim/testdir/screendump.vim)0
-rw-r--r--test/old/testdir/script_util.vim (renamed from src/nvim/testdir/script_util.vim)0
-rw-r--r--test/old/testdir/setup.vim (renamed from src/nvim/testdir/setup.vim)28
-rw-r--r--test/old/testdir/shared.vim (renamed from src/nvim/testdir/shared.vim)8
-rw-r--r--test/old/testdir/suite.sh10
-rw-r--r--test/old/testdir/summarize.vim (renamed from src/nvim/testdir/summarize.vim)0
-rw-r--r--test/old/testdir/term_util.vim (renamed from src/nvim/testdir/term_util.vim)0
-rw-r--r--test/old/testdir/test.sh89
-rw-r--r--test/old/testdir/test1.in (renamed from src/nvim/testdir/test1.in)0
-rw-r--r--test/old/testdir/test1.ok (renamed from src/nvim/testdir/test1.ok)0
-rw-r--r--test/old/testdir/test_alot.vim (renamed from src/nvim/testdir/test_alot.vim)2
-rw-r--r--test/old/testdir/test_alot_latin.vim (renamed from src/nvim/testdir/test_alot_latin.vim)0
-rw-r--r--test/old/testdir/test_alot_utf8.vim (renamed from src/nvim/testdir/test_alot_utf8.vim)0
-rw-r--r--test/old/testdir/test_arabic.vim (renamed from src/nvim/testdir/test_arabic.vim)6
-rw-r--r--test/old/testdir/test_arglist.vim (renamed from src/nvim/testdir/test_arglist.vim)57
-rw-r--r--test/old/testdir/test_assert.vim (renamed from src/nvim/testdir/test_assert.vim)180
-rw-r--r--test/old/testdir/test_autochdir.vim (renamed from src/nvim/testdir/test_autochdir.vim)19
-rw-r--r--test/old/testdir/test_autocmd.vim (renamed from src/nvim/testdir/test_autocmd.vim)479
-rw-r--r--test/old/testdir/test_autoload.vim (renamed from src/nvim/testdir/test_autoload.vim)0
-rw-r--r--test/old/testdir/test_backspace_opt.vim (renamed from src/nvim/testdir/test_backspace_opt.vim)18
-rw-r--r--test/old/testdir/test_backup.vim (renamed from src/nvim/testdir/test_backup.vim)6
-rw-r--r--test/old/testdir/test_blob.vim867
-rw-r--r--test/old/testdir/test_blockedit.vim (renamed from src/nvim/testdir/test_blockedit.vim)19
-rw-r--r--test/old/testdir/test_breakindent.vim (renamed from src/nvim/testdir/test_breakindent.vim)91
-rw-r--r--test/old/testdir/test_buffer.vim (renamed from src/nvim/testdir/test_buffer.vim)227
-rw-r--r--test/old/testdir/test_bufline.vim (renamed from src/nvim/testdir/test_bufline.vim)25
-rw-r--r--test/old/testdir/test_bufwintabinfo.vim (renamed from src/nvim/testdir/test_bufwintabinfo.vim)8
-rw-r--r--test/old/testdir/test_cd.vim (renamed from src/nvim/testdir/test_cd.vim)1
-rw-r--r--test/old/testdir/test_cdo.vim (renamed from src/nvim/testdir/test_cdo.vim)0
-rw-r--r--test/old/testdir/test_changedtick.vim (renamed from src/nvim/testdir/test_changedtick.vim)0
-rw-r--r--test/old/testdir/test_changelist.vim (renamed from src/nvim/testdir/test_changelist.vim)20
-rw-r--r--test/old/testdir/test_charsearch.vim (renamed from src/nvim/testdir/test_charsearch.vim)5
-rw-r--r--test/old/testdir/test_charsearch_utf8.vim (renamed from src/nvim/testdir/test_charsearch_utf8.vim)7
-rw-r--r--test/old/testdir/test_checkpath.vim (renamed from src/nvim/testdir/test_checkpath.vim)0
-rw-r--r--test/old/testdir/test_cindent.vim (renamed from src/nvim/testdir/test_cindent.vim)24
-rw-r--r--test/old/testdir/test_cjk_linebreak.vim (renamed from src/nvim/testdir/test_cjk_linebreak.vim)0
-rw-r--r--test/old/testdir/test_clientserver.vim (renamed from src/nvim/testdir/test_clientserver.vim)21
-rw-r--r--test/old/testdir/test_close_count.vim (renamed from src/nvim/testdir/test_close_count.vim)0
-rw-r--r--test/old/testdir/test_cmdline.vim (renamed from src/nvim/testdir/test_cmdline.vim)563
-rw-r--r--test/old/testdir/test_cmdwin.vim96
-rw-r--r--test/old/testdir/test_codestyle.vim63
-rw-r--r--test/old/testdir/test_command_count.vim (renamed from src/nvim/testdir/test_command_count.vim)0
-rw-r--r--test/old/testdir/test_comments.vim (renamed from src/nvim/testdir/test_comments.vim)0
-rw-r--r--test/old/testdir/test_comparators.vim (renamed from src/nvim/testdir/test_comparators.vim)0
-rw-r--r--test/old/testdir/test_compiler.vim (renamed from src/nvim/testdir/test_compiler.vim)11
-rw-r--r--test/old/testdir/test_conceal.vim (renamed from src/nvim/testdir/test_conceal.vim)146
-rw-r--r--test/old/testdir/test_const.vim (renamed from src/nvim/testdir/test_const.vim)0
-rw-r--r--test/old/testdir/test_cpoptions.vim (renamed from src/nvim/testdir/test_cpoptions.vim)105
-rw-r--r--test/old/testdir/test_crash.vim155
-rw-r--r--test/old/testdir/test_cursor_func.vim (renamed from src/nvim/testdir/test_cursor_func.vim)127
-rw-r--r--test/old/testdir/test_cursorline.vim (renamed from src/nvim/testdir/test_cursorline.vim)23
-rw-r--r--test/old/testdir/test_curswant.vim (renamed from src/nvim/testdir/test_curswant.vim)0
-rw-r--r--test/old/testdir/test_debugger.vim (renamed from src/nvim/testdir/test_debugger.vim)341
-rw-r--r--test/old/testdir/test_delete.vim133
-rw-r--r--test/old/testdir/test_diffmode.vim (renamed from src/nvim/testdir/test_diffmode.vim)124
-rw-r--r--test/old/testdir/test_digraph.vim (renamed from src/nvim/testdir/test_digraph.vim)16
-rw-r--r--test/old/testdir/test_display.vim (renamed from src/nvim/testdir/test_display.vim)158
-rw-r--r--test/old/testdir/test_edit.vim (renamed from src/nvim/testdir/test_edit.vim)127
-rw-r--r--test/old/testdir/test_environ.vim (renamed from src/nvim/testdir/test_environ.vim)2
-rw-r--r--test/old/testdir/test_erasebackword.vim (renamed from src/nvim/testdir/test_erasebackword.vim)0
-rw-r--r--test/old/testdir/test_escaped_glob.vim (renamed from src/nvim/testdir/test_escaped_glob.vim)0
-rw-r--r--test/old/testdir/test_eval_stuff.vim (renamed from src/nvim/testdir/test_eval_stuff.vim)98
-rw-r--r--test/old/testdir/test_ex_equal.vim (renamed from src/nvim/testdir/test_ex_equal.vim)17
-rw-r--r--test/old/testdir/test_ex_mode.vim (renamed from src/nvim/testdir/test_ex_mode.vim)46
-rw-r--r--test/old/testdir/test_ex_undo.vim (renamed from src/nvim/testdir/test_ex_undo.vim)0
-rw-r--r--test/old/testdir/test_ex_z.vim (renamed from src/nvim/testdir/test_ex_z.vim)9
-rw-r--r--test/old/testdir/test_excmd.vim (renamed from src/nvim/testdir/test_excmd.vim)5
-rw-r--r--test/old/testdir/test_exec_while_if.vim (renamed from src/nvim/testdir/test_exec_while_if.vim)0
-rw-r--r--test/old/testdir/test_execute_func.vim (renamed from src/nvim/testdir/test_execute_func.vim)27
-rw-r--r--test/old/testdir/test_exists.vim (renamed from src/nvim/testdir/test_exists.vim)0
-rw-r--r--test/old/testdir/test_exists_autocmd.vim (renamed from src/nvim/testdir/test_exists_autocmd.vim)0
-rw-r--r--test/old/testdir/test_exit.vim (renamed from src/nvim/testdir/test_exit.vim)12
-rw-r--r--test/old/testdir/test_expand.vim (renamed from src/nvim/testdir/test_expand.vim)2
-rw-r--r--test/old/testdir/test_expand_func.vim (renamed from src/nvim/testdir/test_expand_func.vim)0
-rw-r--r--test/old/testdir/test_expr.vim929
-rw-r--r--test/old/testdir/test_expr_utf8.vim (renamed from src/nvim/testdir/test_expr_utf8.vim)10
-rw-r--r--test/old/testdir/test_file_perm.vim (renamed from src/nvim/testdir/test_file_perm.vim)0
-rw-r--r--test/old/testdir/test_file_size.vim (renamed from src/nvim/testdir/test_file_size.vim)0
-rw-r--r--test/old/testdir/test_filechanged.vim (renamed from src/nvim/testdir/test_filechanged.vim)0
-rw-r--r--test/old/testdir/test_fileformat.vim (renamed from src/nvim/testdir/test_fileformat.vim)7
-rw-r--r--test/old/testdir/test_filetype.vim (renamed from src/nvim/testdir/test_filetype.vim)471
-rw-r--r--test/old/testdir/test_filter_cmd.vim (renamed from src/nvim/testdir/test_filter_cmd.vim)4
-rw-r--r--test/old/testdir/test_filter_map.vim242
-rw-r--r--test/old/testdir/test_find_complete.vim (renamed from src/nvim/testdir/test_find_complete.vim)0
-rw-r--r--test/old/testdir/test_findfile.vim (renamed from src/nvim/testdir/test_findfile.vim)0
-rw-r--r--test/old/testdir/test_fixeol.vim (renamed from src/nvim/testdir/test_fixeol.vim)0
-rw-r--r--test/old/testdir/test_flatten.vim (renamed from src/nvim/testdir/test_flatten.vim)27
-rw-r--r--test/old/testdir/test_float_func.vim (renamed from src/nvim/testdir/test_float_func.vim)5
-rw-r--r--test/old/testdir/test_fnameescape.vim (renamed from src/nvim/testdir/test_fnameescape.vim)0
-rw-r--r--test/old/testdir/test_fnamemodify.vim (renamed from src/nvim/testdir/test_fnamemodify.vim)0
-rw-r--r--test/old/testdir/test_fold.vim (renamed from src/nvim/testdir/test_fold.vim)30
-rw-r--r--test/old/testdir/test_format.vim361
-rw-r--r--test/old/testdir/test_functions.vim (renamed from src/nvim/testdir/test_functions.vim)944
-rw-r--r--test/old/testdir/test_ga.vim (renamed from src/nvim/testdir/test_ga.vim)0
-rw-r--r--test/old/testdir/test_getcwd.vim (renamed from src/nvim/testdir/test_getcwd.vim)0
-rw-r--r--test/old/testdir/test_getvar.vim (renamed from src/nvim/testdir/test_getvar.vim)6
-rw-r--r--test/old/testdir/test_gf.vim (renamed from src/nvim/testdir/test_gf.vim)61
-rw-r--r--test/old/testdir/test_glob2regpat.vim (renamed from src/nvim/testdir/test_glob2regpat.vim)5
-rw-r--r--test/old/testdir/test_global.vim (renamed from src/nvim/testdir/test_global.vim)0
-rw-r--r--test/old/testdir/test_gn.vim (renamed from src/nvim/testdir/test_gn.vim)0
-rw-r--r--test/old/testdir/test_goto.vim (renamed from src/nvim/testdir/test_goto.vim)0
-rw-r--r--test/old/testdir/test_gui.vim (renamed from src/nvim/testdir/test_gui.vim)0
-rw-r--r--test/old/testdir/test_help.vim (renamed from src/nvim/testdir/test_help.vim)0
-rw-r--r--test/old/testdir/test_help_tagjump.vim (renamed from src/nvim/testdir/test_help_tagjump.vim)22
-rw-r--r--test/old/testdir/test_hide.vim (renamed from src/nvim/testdir/test_hide.vim)0
-rw-r--r--test/old/testdir/test_highlight.vim (renamed from src/nvim/testdir/test_highlight.vim)90
-rw-r--r--test/old/testdir/test_history.vim (renamed from src/nvim/testdir/test_history.vim)13
-rw-r--r--test/old/testdir/test_hlsearch.vim (renamed from src/nvim/testdir/test_hlsearch.vim)42
-rw-r--r--test/old/testdir/test_increment.vim (renamed from src/nvim/testdir/test_increment.vim)16
-rw-r--r--test/old/testdir/test_increment_dbcs.vim (renamed from src/nvim/testdir/test_increment_dbcs.vim)0
-rw-r--r--test/old/testdir/test_indent.vim (renamed from src/nvim/testdir/test_indent.vim)0
-rw-r--r--test/old/testdir/test_input.vim (renamed from src/nvim/testdir/test_input.vim)0
-rw-r--r--test/old/testdir/test_ins_complete.vim (renamed from src/nvim/testdir/test_ins_complete.vim)242
-rw-r--r--test/old/testdir/test_ins_complete_no_halt.vim (renamed from src/nvim/testdir/test_ins_complete_no_halt.vim)0
-rw-r--r--test/old/testdir/test_interrupt.vim (renamed from src/nvim/testdir/test_interrupt.vim)0
-rw-r--r--test/old/testdir/test_join.vim (renamed from src/nvim/testdir/test_join.vim)0
-rw-r--r--test/old/testdir/test_jumplist.vim (renamed from src/nvim/testdir/test_jumplist.vim)66
-rw-r--r--test/old/testdir/test_lambda.vim (renamed from src/nvim/testdir/test_lambda.vim)1
-rw-r--r--test/old/testdir/test_langmap.vim (renamed from src/nvim/testdir/test_langmap.vim)0
-rw-r--r--test/old/testdir/test_largefile.vim (renamed from src/nvim/testdir/test_largefile.vim)0
-rw-r--r--test/old/testdir/test_let.vim (renamed from src/nvim/testdir/test_let.vim)236
-rw-r--r--test/old/testdir/test_lineending.vim (renamed from src/nvim/testdir/test_lineending.vim)0
-rw-r--r--test/old/testdir/test_lispindent.vim (renamed from src/nvim/testdir/test_lispindent.vim)0
-rw-r--r--test/old/testdir/test_listchars.vim (renamed from src/nvim/testdir/test_listchars.vim)220
-rw-r--r--test/old/testdir/test_listdict.vim1425
-rw-r--r--test/old/testdir/test_listlbr.vim (renamed from src/nvim/testdir/test_listlbr.vim)88
-rw-r--r--test/old/testdir/test_listlbr_utf8.vim (renamed from src/nvim/testdir/test_listlbr_utf8.vim)85
-rw-r--r--test/old/testdir/test_makeencoding.py (renamed from src/nvim/testdir/test_makeencoding.py)0
-rw-r--r--test/old/testdir/test_makeencoding.vim (renamed from src/nvim/testdir/test_makeencoding.vim)2
-rw-r--r--test/old/testdir/test_map_functions.vim618
-rw-r--r--test/old/testdir/test_mapping.vim (renamed from src/nvim/testdir/test_mapping.vim)533
-rw-r--r--test/old/testdir/test_marks.vim (renamed from src/nvim/testdir/test_marks.vim)0
-rw-r--r--test/old/testdir/test_match.vim (renamed from src/nvim/testdir/test_match.vim)5
-rw-r--r--test/old/testdir/test_matchadd_conceal.vim (renamed from src/nvim/testdir/test_matchadd_conceal.vim)8
-rw-r--r--test/old/testdir/test_matchadd_conceal_utf8.vim (renamed from src/nvim/testdir/test_matchadd_conceal_utf8.vim)0
-rw-r--r--test/old/testdir/test_matchfuzzy.vim (renamed from src/nvim/testdir/test_matchfuzzy.vim)35
-rw-r--r--test/old/testdir/test_matchparen.vim87
-rw-r--r--test/old/testdir/test_menu.vim (renamed from src/nvim/testdir/test_menu.vim)2
-rw-r--r--test/old/testdir/test_messages.vim (renamed from src/nvim/testdir/test_messages.vim)80
-rw-r--r--test/old/testdir/test_method.vim (renamed from src/nvim/testdir/test_method.vim)2
-rw-r--r--test/old/testdir/test_mksession.vim (renamed from src/nvim/testdir/test_mksession.vim)233
-rw-r--r--test/old/testdir/test_mksession_utf8.vim (renamed from src/nvim/testdir/test_mksession_utf8.vim)0
-rw-r--r--test/old/testdir/test_modeline.vim (renamed from src/nvim/testdir/test_modeline.vim)2
-rw-r--r--test/old/testdir/test_move.vim (renamed from src/nvim/testdir/test_move.vim)24
-rw-r--r--test/old/testdir/test_nested_function.vim (renamed from src/nvim/testdir/test_nested_function.vim)11
-rw-r--r--test/old/testdir/test_normal.vim (renamed from src/nvim/testdir/test_normal.vim)527
-rw-r--r--test/old/testdir/test_number.vim (renamed from src/nvim/testdir/test_number.vim)4
-rw-r--r--test/old/testdir/test_options.vim2212
-rw-r--r--test/old/testdir/test_packadd.vim (renamed from src/nvim/testdir/test_packadd.vim)78
-rw-r--r--test/old/testdir/test_partial.vim (renamed from src/nvim/testdir/test_partial.vim)51
-rw-r--r--test/old/testdir/test_paste.vim (renamed from src/nvim/testdir/test_paste.vim)24
-rw-r--r--test/old/testdir/test_perl.vim (renamed from src/nvim/testdir/test_perl.vim)15
-rw-r--r--test/old/testdir/test_plus_arg_edit.vim (renamed from src/nvim/testdir/test_plus_arg_edit.vim)0
-rw-r--r--test/old/testdir/test_popup.vim (renamed from src/nvim/testdir/test_popup.vim)112
-rw-r--r--test/old/testdir/test_preview.vim (renamed from src/nvim/testdir/test_preview.vim)0
-rw-r--r--test/old/testdir/test_profile.vim (renamed from src/nvim/testdir/test_profile.vim)3
-rw-r--r--test/old/testdir/test_prompt_buffer.vim (renamed from src/nvim/testdir/test_prompt_buffer.vim)55
-rw-r--r--test/old/testdir/test_put.vim (renamed from src/nvim/testdir/test_put.vim)51
-rw-r--r--test/old/testdir/test_python3.vim (renamed from src/nvim/testdir/test_python3.vim)102
-rw-r--r--test/old/testdir/test_pyx3.vim (renamed from src/nvim/testdir/test_pyx3.vim)30
-rw-r--r--test/old/testdir/test_quickfix.vim (renamed from src/nvim/testdir/test_quickfix.vim)612
-rw-r--r--test/old/testdir/test_quotestar.vim (renamed from src/nvim/testdir/test_quotestar.vim)1
-rw-r--r--test/old/testdir/test_random.vim (renamed from src/nvim/testdir/test_random.vim)0
-rw-r--r--test/old/testdir/test_recover.vim (renamed from src/nvim/testdir/test_recover.vim)60
-rw-r--r--test/old/testdir/test_regex_char_classes.vim (renamed from src/nvim/testdir/test_regex_char_classes.vim)0
-rw-r--r--test/old/testdir/test_regexp_latin.vim (renamed from src/nvim/testdir/test_regexp_latin.vim)0
-rw-r--r--test/old/testdir/test_regexp_utf8.vim (renamed from src/nvim/testdir/test_regexp_utf8.vim)0
-rw-r--r--test/old/testdir/test_registers.vim (renamed from src/nvim/testdir/test_registers.vim)31
-rw-r--r--test/old/testdir/test_reltime.vim (renamed from src/nvim/testdir/test_reltime.vim)1
-rw-r--r--test/old/testdir/test_rename.vim (renamed from src/nvim/testdir/test_rename.vim)0
-rw-r--r--test/old/testdir/test_retab.vim (renamed from src/nvim/testdir/test_retab.vim)0
-rw-r--r--test/old/testdir/test_ruby.vim (renamed from src/nvim/testdir/test_ruby.vim)30
-rw-r--r--test/old/testdir/test_scriptnames.vim110
-rw-r--r--test/old/testdir/test_scroll_opt.vim951
-rw-r--r--test/old/testdir/test_scrollbind.vim (renamed from src/nvim/testdir/test_scrollbind.vim)4
-rw-r--r--test/old/testdir/test_search.vim (renamed from src/nvim/testdir/test_search.vim)55
-rw-r--r--test/old/testdir/test_search_stat.vim (renamed from src/nvim/testdir/test_search_stat.vim)42
-rw-r--r--test/old/testdir/test_searchpos.vim (renamed from src/nvim/testdir/test_searchpos.vim)0
-rw-r--r--test/old/testdir/test_selectmode.vim (renamed from src/nvim/testdir/test_selectmode.vim)116
-rw-r--r--test/old/testdir/test_set.vim (renamed from src/nvim/testdir/test_set.vim)0
-rw-r--r--test/old/testdir/test_sha256.vim (renamed from src/nvim/testdir/test_sha256.vim)0
-rw-r--r--test/old/testdir/test_shell.vim (renamed from src/nvim/testdir/test_shell.vim)51
-rw-r--r--test/old/testdir/test_shift.vim (renamed from src/nvim/testdir/test_shift.vim)0
-rw-r--r--test/old/testdir/test_signals.vim (renamed from src/nvim/testdir/test_signals.vim)4
-rw-r--r--test/old/testdir/test_signs.vim (renamed from src/nvim/testdir/test_signs.vim)134
-rw-r--r--test/old/testdir/test_sleep.vim (renamed from src/nvim/testdir/test_sleep.vim)0
-rw-r--r--test/old/testdir/test_smartindent.vim (renamed from src/nvim/testdir/test_smartindent.vim)0
-rw-r--r--test/old/testdir/test_sort.vim (renamed from src/nvim/testdir/test_sort.vim)1
-rw-r--r--test/old/testdir/test_source.vim (renamed from src/nvim/testdir/test_source.vim)0
-rw-r--r--test/old/testdir/test_source_utf8.vim (renamed from src/nvim/testdir/test_source_utf8.vim)0
-rw-r--r--test/old/testdir/test_spell.vim (renamed from src/nvim/testdir/test_spell.vim)124
-rw-r--r--test/old/testdir/test_spell_utf8.vim (renamed from src/nvim/testdir/test_spell_utf8.vim)1
-rw-r--r--test/old/testdir/test_spellfile.vim (renamed from src/nvim/testdir/test_spellfile.vim)20
-rw-r--r--test/old/testdir/test_startup.vim (renamed from src/nvim/testdir/test_startup.vim)57
-rw-r--r--test/old/testdir/test_startup_utf8.vim (renamed from src/nvim/testdir/test_startup_utf8.vim)2
-rw-r--r--test/old/testdir/test_stat.vim (renamed from src/nvim/testdir/test_stat.vim)0
-rw-r--r--test/old/testdir/test_statusline.vim (renamed from src/nvim/testdir/test_statusline.vim)34
-rw-r--r--test/old/testdir/test_substitute.vim (renamed from src/nvim/testdir/test_substitute.vim)555
-rw-r--r--test/old/testdir/test_suspend.vim (renamed from src/nvim/testdir/test_suspend.vim)2
-rw-r--r--test/old/testdir/test_swap.vim (renamed from src/nvim/testdir/test_swap.vim)26
-rw-r--r--test/old/testdir/test_syn_attr.vim (renamed from src/nvim/testdir/test_syn_attr.vim)0
-rw-r--r--test/old/testdir/test_syntax.vim (renamed from src/nvim/testdir/test_syntax.vim)10
-rw-r--r--test/old/testdir/test_system.vim (renamed from src/nvim/testdir/test_system.vim)0
-rw-r--r--test/old/testdir/test_tab.vim (renamed from src/nvim/testdir/test_tab.vim)0
-rw-r--r--test/old/testdir/test_tabline.vim (renamed from src/nvim/testdir/test_tabline.vim)26
-rw-r--r--test/old/testdir/test_tabpage.vim (renamed from src/nvim/testdir/test_tabpage.vim)59
-rw-r--r--test/old/testdir/test_tagcase.vim (renamed from src/nvim/testdir/test_tagcase.vim)0
-rw-r--r--test/old/testdir/test_tagfunc.vim (renamed from src/nvim/testdir/test_tagfunc.vim)4
-rw-r--r--test/old/testdir/test_tagjump.vim (renamed from src/nvim/testdir/test_tagjump.vim)10
-rw-r--r--test/old/testdir/test_taglist.vim (renamed from src/nvim/testdir/test_taglist.vim)0
-rw-r--r--test/old/testdir/test_termcodes.vim1062
-rw-r--r--test/old/testdir/test_termdebug.vim228
-rw-r--r--test/old/testdir/test_textformat.vim (renamed from src/nvim/testdir/test_textformat.vim)0
-rw-r--r--test/old/testdir/test_textobjects.vim (renamed from src/nvim/testdir/test_textobjects.vim)0
-rw-r--r--test/old/testdir/test_timers.vim (renamed from src/nvim/testdir/test_timers.vim)23
-rw-r--r--test/old/testdir/test_true_false.vim (renamed from src/nvim/testdir/test_true_false.vim)0
-rw-r--r--test/old/testdir/test_trycatch.vim (renamed from src/nvim/testdir/test_trycatch.vim)62
-rw-r--r--test/old/testdir/test_undo.vim (renamed from src/nvim/testdir/test_undo.vim)80
-rw-r--r--test/old/testdir/test_unlet.vim (renamed from src/nvim/testdir/test_unlet.vim)0
-rw-r--r--test/old/testdir/test_user_func.vim (renamed from src/nvim/testdir/test_user_func.vim)390
-rw-r--r--test/old/testdir/test_usercommands.vim (renamed from src/nvim/testdir/test_usercommands.vim)17
-rw-r--r--test/old/testdir/test_utf8.vim (renamed from src/nvim/testdir/test_utf8.vim)21
-rw-r--r--test/old/testdir/test_utf8_comparisons.vim (renamed from src/nvim/testdir/test_utf8_comparisons.vim)0
-rw-r--r--test/old/testdir/test_vartabs.vim (renamed from src/nvim/testdir/test_vartabs.vim)0
-rw-r--r--test/old/testdir/test_version.vim (renamed from src/nvim/testdir/test_version.vim)0
-rw-r--r--test/old/testdir/test_viminfo.vim (renamed from src/nvim/testdir/test_viminfo.vim)0
-rw-r--r--test/old/testdir/test_vimscript.vim (renamed from src/nvim/testdir/test_vimscript.vim)189
-rw-r--r--test/old/testdir/test_virtualedit.vim (renamed from src/nvim/testdir/test_virtualedit.vim)112
-rw-r--r--test/old/testdir/test_visual.vim (renamed from src/nvim/testdir/test_visual.vim)61
-rw-r--r--test/old/testdir/test_winbuf_close.vim (renamed from src/nvim/testdir/test_winbuf_close.vim)0
-rw-r--r--test/old/testdir/test_window_cmd.vim (renamed from src/nvim/testdir/test_window_cmd.vim)182
-rw-r--r--test/old/testdir/test_window_id.vim (renamed from src/nvim/testdir/test_window_id.vim)0
-rw-r--r--test/old/testdir/test_windows_home.vim (renamed from src/nvim/testdir/test_windows_home.vim)0
-rw-r--r--test/old/testdir/test_wnext.vim (renamed from src/nvim/testdir/test_wnext.vim)0
-rw-r--r--test/old/testdir/test_wordcount.vim (renamed from src/nvim/testdir/test_wordcount.vim)0
-rw-r--r--test/old/testdir/test_writefile.vim (renamed from src/nvim/testdir/test_writefile.vim)73
-rw-r--r--test/old/testdir/unix.vim (renamed from src/nvim/testdir/unix.vim)0
-rw-r--r--test/old/testdir/view_util.vim (renamed from src/nvim/testdir/view_util.vim)0
-rw-r--r--test/old/testdir/vim9.vim (renamed from src/nvim/testdir/vim9.vim)36
-rw-r--r--test/symbolic/klee/nvim/charset.c172
-rw-r--r--test/symbolic/klee/nvim/garray.c195
-rw-r--r--test/symbolic/klee/nvim/gettext.c4
-rw-r--r--test/symbolic/klee/nvim/keymap.c559
-rw-r--r--test/symbolic/klee/nvim/mbyte.c266
-rw-r--r--test/symbolic/klee/nvim/memory.c101
-rwxr-xr-xtest/symbolic/klee/run.sh102
-rw-r--r--test/symbolic/klee/viml_expressions_lexer.c105
-rw-r--r--test/symbolic/klee/viml_expressions_parser.c117
-rw-r--r--test/unit/buffer_spec.lua276
-rw-r--r--test/unit/charset/vim_str2nr_spec.lua2
-rw-r--r--test/unit/eval/helpers.lua5
-rw-r--r--test/unit/eval/typval_spec.lua313
-rw-r--r--test/unit/fixtures/multiqueue.c11
-rw-r--r--test/unit/fixtures/rbuffer.c5
-rw-r--r--test/unit/formatc.lua2
-rw-r--r--test/unit/helpers.lua339
-rw-r--r--test/unit/keycodes_spec.lua22
-rw-r--r--test/unit/marktree_spec.lua339
-rw-r--r--test/unit/mbyte_spec.lua243
-rw-r--r--test/unit/message_spec.lua4
-rw-r--r--test/unit/msgpack_spec.lua75
-rw-r--r--test/unit/optionstr_spec.lua4
-rw-r--r--test/unit/os/env_spec.lua2
-rw-r--r--test/unit/os/fileio_spec.lua69
-rw-r--r--test/unit/os/fs_spec.lua128
-rw-r--r--test/unit/os/shell_spec.lua2
-rw-r--r--test/unit/path_spec.lua81
-rw-r--r--test/unit/preload.lua1
-rw-r--r--test/unit/preprocess.lua178
-rw-r--r--test/unit/set.lua47
-rw-r--r--test/unit/statusline_spec.lua282
-rw-r--r--test/unit/strings_spec.lua93
-rw-r--r--test/unit/tempfile_spec.lua3
-rw-r--r--test/unit/tui_spec.lua162
-rw-r--r--test/unit/undo_spec.lua32
1781 files changed, 219546 insertions, 104199 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
index d9b67eafe7..ab4bead7fb 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -1,21 +1,19 @@
env:
CIRRUS_CLONE_DEPTH: '2'
LANG: en_US.UTF-8
- CMAKE_EXTRA_FLAGS: -DCI_BUILD=ON -DMIN_LOG_LEVEL=3
freebsd_task:
name: FreeBSD
only_if: $BRANCH != "master"
freebsd_instance:
- image_family: freebsd-13-1
+ image_family: freebsd-13-2
timeout_in: 30m
install_script:
- - pkg update -f
- - pkg install -y cmake gmake ninja libtool automake pkgconf unzip wget gettext python libffi git
+ - pkg install -y cmake gmake ninja unzip wget gettext python git
build_deps_script:
- gmake deps
build_script:
- - gmake CMAKE_EXTRA_FLAGS="${CMAKE_EXTRA_FLAGS}" nvim
+ - gmake CMAKE_EXTRA_FLAGS="-DCI_BUILD=ON" nvim
workaround_script:
# Run tests as user "cirrus" instead of root. This is required for the
# permission-related tests to work correctly.
diff --git a/.clang-format b/.clang-format
index a31d753217..9f45ac32e2 100644
--- a/.clang-format
+++ b/.clang-format
@@ -14,7 +14,7 @@ PenaltyReturnTypeOnItsOwnLine: 200
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
-BinPackParameters: false
+BinPackParameters: true
BreakBeforeBinaryOperators: true
BreakBeforeTernaryOperators: true
ContinuationIndentWidth: 2
@@ -23,7 +23,7 @@ AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: No
AlwaysBreakTemplateDeclarations: No
AlignEscapedNewlines: DontAlign
-BinPackArguments: false
+BinPackArguments: true
BraceWrapping:
AfterClass: false
AfterControlStatement: false
diff --git a/.clang-tidy b/.clang-tidy
index 1fe87ba501..927909cf8d 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -1,42 +1,71 @@
+WarningsAsErrors: '*,-clang-diagnostic-unused-function'
Checks: >
- -*,
+ Enable all warnings by default. This ensures we don't miss new and useful
+ warnings when a new version of clang-tidy is dropped.
- bugprone-*,
- google-*,
- misc-*,
- modernize-*,
- performance-*,
- portability-*,
- readability-*,
+ IMPORTANT
+ clang-tidy doesn't support comments but we can simulate comments by just
+ writing text directly here. These are then interpreted as warnings and will
+ be dropped. As long as you start every sentence with a capital letter and
+ don't use commas in your "comments" you should be fine,
+ *,
+ Untriaged warnings. Please categorize them accordingly if you find a relevant
+ section for it,
-bugprone-assignment-in-if-condition,
- -bugprone-branch-clone,
- -bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
- -bugprone-macro-parentheses,
- -bugprone-narrowing-conversions,
-bugprone-not-null-terminated-result,
- -bugprone-reserved-identifier,
- -bugprone-sizeof-expression,
- -bugprone-suspicious-include,
-bugprone-suspicious-memory-comparison,
- -bugprone-unused-return-value,
- -google-readability-braces-around-statements,
- -google-readability-function-size,
- -misc-misplaced-const,
- -misc-no-recursion,
+ -bugprone-switch-missing-default-case,
+ -cert-env33-c,
+ -cert-err33-c,
+ -cert-err34-c,
+ -concurrency-mt-unsafe,
+ -cppcoreguidelines-narrowing-conversions,
+
+ Warnings that may be useful, but are too inconsistent to enable by default
+ May yield useful results with some manual triaging,
+ -bugprone-branch-clone,
+ -bugprone-macro-parentheses,
+ -bugprone-sizeof-expression,
+ -hicpp-multiway-paths-covered,
+ -hicpp-signed-bitwise,
-misc-unused-parameters,
-modernize-macro-to-enum,
- -performance-no-int-to-ptr,
- -readability-avoid-const-params-in-decls,
- -readability-braces-around-statements,
-readability-else-after-return,
- -readability-function-cognitive-complexity,
-readability-function-size,
- -readability-identifier-length,
-readability-isolate-declaration,
+
+ Warnings that are rarely useful,
+ -altera-*, Checks related to OpenCL programming for FPGAs. Not relevant,
+ -android-*,
+ -bugprone-easily-swappable-parameters,
+ -bugprone-swapped-arguments,
+ -clang-analyzer-*, Already covered by the cmake target "clang-analyzer",
+ -cppcoreguidelines-avoid-non-const-global-variables,
+ -cppcoreguidelines-init-variables,
+ -llvm-header-guard, We use #pragma once,
+ -llvmlibc-restrict-system-libc-headers, We want to use glibc,
+ -misc-header-include-cycle, Looks useful but redundant with IWYU. We may replace IWYU with this one day,
+ -misc-include-cleaner, Looks useful but redundant with IWYU. We may replace IWYU with this one day,
+ -misc-misplaced-const,
+ -misc-no-recursion,
+ -performance-no-int-to-ptr,
+ -readability-function-cognitive-complexity,
+ -readability-identifier-length,
-readability-magic-numbers,
- -readability-misleading-indentation,
- -readability-redundant-declaration,
- -readability-redundant-function-ptr-dereference,
+ -readability-redundant-declaration, Conflicts with our header generation scripts,
-readability-suspicious-call-argument,
+
+ Aliases. These are just duplicates of other warnings and should always be ignored,
+ -bugprone-narrowing-conversions,
+ -cert-dcl37-c,
+ -cert-dcl51-cpp,
+ -cert-exp42-c,
+ -cert-flp37-c,
+ -cert-msc24-c,
+ -cert-msc33-c,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -google-readability-function-size,
+ -hicpp-function-size,
+ -llvm-else-after-return,
diff --git a/.clangd b/.clangd
index d7911aaf64..1bb663fd8b 100644
--- a/.clangd
+++ b/.clangd
@@ -1,2 +1,4 @@
CompileFlags:
CompilationDatabase: build/ # Search build/ directory for compile_commands.json
+Diagnostics:
+ UnusedIncludes: None
diff --git a/.gitattributes b/.gitattributes
index 1770c9b164..75f92454e3 100755
--- a/.gitattributes
+++ b/.gitattributes
@@ -3,14 +3,15 @@
*CMakeLists.txt linguist-language=CMake
runtime/doc/* linguist-documentation
+runtime/doc/builtin.txt linguist-generated
+
+runtime/lua/vim/_meta/vimfn.lua linguist-generated
+runtime/lua/vim/_meta/api.lua linguist-generated
+runtime/lua/vim/_meta/api_keysets.lua linguist-generated
+runtime/lua/vim/_meta/options.lua linguist-generated
src/xdiff/** linguist-vendored
src/cjson/** linguist-vendored
src/unicode/** linguist-vendored
-src/nvim/testdir/test42.in diff
-
.github/ export-ignore
-.travis.yml export-ignore
-codecov.yml export-ignore
-.builds/ export-ignore
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 5fd7bc37b6..c07ae66c6f 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -6,28 +6,33 @@ body:
- type: markdown
attributes:
value: |
- _Before reporting:_ search [existing issues](https://github.com/neovim/neovim/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ). Usage questions such as "How do I...?" belong on the [Neovim Discourse](https://neovim.discourse.group/c/7-category/7) and will be closed.
+ *Before reporting:*
+ - Confirm the problem is reproducible on [**master**](https://github.com/neovim/neovim/releases/nightly) or [**latest stable**](https://github.com/neovim/neovim/releases/stable) release
+ - run `make distclean` when encountering build issues
+ - search [existing issues](https://github.com/neovim/neovim/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
+ - check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ)
+ Usage or "How to" questions belong on the [stackoverflow](https://vi.stackexchange.com/) and will be closed.
- type: textarea
attributes:
- label: "Describe the bug"
+ label: "Problem"
description: "Describe the current behavior. May include logs, images, or videos."
validations:
required: true
-
- type: textarea
attributes:
label: "Steps to reproduce"
description: |
- For build failures: list the exact steps including CMake flags (if any).
- For startup or shell-related problems: try `env -i TERM=ansi-256color "$(which nvim)"`.
+ - See [Minimal-reproduction-template](https://github.com/neovim/neovim/wiki/Minimal-reproduction-template) for how to create a minimal configuration.
+ - Please do **not** include a package manager in the reproduction steps.
placeholder: |
nvim --clean
:edit foo
yiwp
validations:
required: true
-
- type: textarea
attributes:
label: "Expected behavior"
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 1e717ba6c5..f25732f90d 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question
- url: https://neovim.discourse.group/
+ url: https://vi.stackexchange.com/
about: Ask questions about configuration and usage of Neovim
diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
index 88867ce644..bdad12e412 100644
--- a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml
@@ -6,27 +6,14 @@ body:
- type: markdown
attributes:
value: |
- _Before reporting:_ search [existing issues](https://github.com/neovim/neovim/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ). Usage questions such as "How do I...?" or "Why isn't X language server/feature working?" belong on the [Neovim Discourse](https://neovim.discourse.group/c/7-category/7) and will be closed.
+ _Before reporting:_ search [existing issues](https://github.com/neovim/neovim/issues?q=is%3Aissue+is%3Aopen+label%3Abug) and check the [FAQ](https://github.com/neovim/neovim/wiki/FAQ). Usage questions or "Why isn't X language server/feature working?" belong on [stackoverflow](https://vi.stackexchange.com/) and will be closed.
- - type: input
- attributes:
- label: "Neovim version (nvim -v)"
- placeholder: "0.6.0 commit db1b0ee3b30f"
- validations:
- required: true
- - type: input
- attributes:
- label: "Language server name/version"
- placeholder: "rls 0.5.2"
- validations:
- required: true
- - type: input
+ - type: textarea
attributes:
- label: "Operating system/version"
- placeholder: "emacs 23"
+ label: "Problem"
+ description: "Describe the bug caused by the Nvim LSP client."
validations:
required: true
-
- type: textarea
attributes:
label: 'Steps to reproduce using "nvim -u minimal_init.lua"'
@@ -46,7 +33,7 @@ body:
pattern = pattern,
callback = function(args)
local match = vim.fs.find(root_markers, { path = args.file, upward = true })[1]
- local root_dir = match and vim.fn.fnamemodify(match, ':p:h') or vim.NIL
+ local root_dir = match and vim.fn.fnamemodify(match, ':p:h') or nil
vim.lsp.start({
name = 'bugged-ls',
cmd = cmd,
@@ -65,14 +52,29 @@ body:
_Note_: if the issue is with an autocompletion or other LSP plugin, report to that plugin's issue tracker.
validations:
required: true
-
- type: textarea
attributes:
label: "Expected behavior"
description: "Describe the behavior you expect. May include logs, images, or videos."
- - type: textarea
+
+ - type: input
+ attributes:
+ label: "Neovim version (nvim -v)"
+ placeholder: "0.6.0 commit db1b0ee3b30f"
+ validations:
+ required: true
+ - type: input
+ attributes:
+ label: "Language server name/version"
+ placeholder: "rls 0.5.2"
+ validations:
+ required: true
+ - type: input
attributes:
- label: "Actual behavior"
+ label: "Operating system/version"
+ placeholder: "emacs 23"
+ validations:
+ required: true
- type: input
attributes:
diff --git a/.github/actions/cache/action.yml b/.github/actions/cache/action.yml
index 858045c02a..d668c7a7cc 100644
--- a/.github/actions/cache/action.yml
+++ b/.github/actions/cache/action.yml
@@ -3,20 +3,31 @@ description: "This action caches neovim dependencies"
runs:
using: "composite"
steps:
- - run: echo "CACHE_KEY=${{ github.job }}-${{ github.base_ref }}" >> $GITHUB_ENV
+ - run: echo "CACHE_KEY=${{ github.workflow }}" >> $GITHUB_ENV
+ shell: bash
+
+ - run: echo "CACHE_KEY=${{ github.job }}" >> $GITHUB_ENV
shell: bash
- if: ${{ matrix }}
run: echo "CACHE_KEY=$CACHE_KEY-${{ join(matrix.*, '-') }}" >> $GITHUB_ENV
shell: bash
+ - if: ${{ matrix.build }}
+ run: echo "CACHE_KEY=$CACHE_KEY-${{ join(matrix.build.*, '-') }}" >> $GITHUB_ENV
+ shell: bash
+
+ - id: image
+ run: echo "version=$ImageVersion" >> $GITHUB_OUTPUT
+ shell: bash
+
# Avoid using '**/CMakeLists.txt' (or any pattern starting with '**/') even
# if it makes the expression below simpler. hashFiles() has a timer that
# will fail the job if it times out, which can happen if there are too many
# files to search through.
- uses: actions/cache@v3
with:
- path: ${{ env.CACHE_NVIM_DEPS_DIR }}
- key: ${{ env.CACHE_KEY }}-${{ hashFiles('cmake**', 'ci/**',
- '.github/workflows/ci.yml', 'CMakeLists.txt',
+ path: .deps
+ key: ${{ env.CACHE_KEY }}-${{ steps.image.outputs.version }}-${{ hashFiles('cmake**',
+ '.github/**', 'CMakeLists.txt',
'runtime/CMakeLists.txt', 'src/nvim/**/CMakeLists.txt') }}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..e96fd5286f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "daily"
+ commit-message:
+ prefix: "ci"
diff --git a/.github/labeler.yml b/.github/labeler.yml
index ad48287246..111fc5f373 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -2,10 +2,6 @@
- runtime/lua/vim/lsp.lua
- runtime/lua/vim/lsp/*
-"lua":
- - runtime/lua/**/*
- - src/nvim/lua/*
-
"tui":
- src/nvim/tui/tui.*
diff --git a/.github/scripts/build_universal_macos.sh b/.github/scripts/build_universal_macos.sh
new file mode 100755
index 0000000000..d07c395cd6
--- /dev/null
+++ b/.github/scripts/build_universal_macos.sh
@@ -0,0 +1,21 @@
+#!/bin/bash -e
+
+MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -f1 -d.)"
+export MACOSX_DEPLOYMENT_TARGET
+cmake -S cmake.deps -B .deps -G Ninja \
+ -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} \
+ -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \
+ -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 \
+ -D CMAKE_FIND_FRAMEWORK=NEVER
+cmake --build .deps
+cmake -B build -G Ninja \
+ -D CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} \
+ -D CMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} \
+ -D CMAKE_OSX_ARCHITECTURES=arm64\;x86_64 \
+ -D CMAKE_FIND_FRAMEWORK=NEVER
+cmake --build build
+# Make sure we build everything for M1 as well
+for macho in build/bin/* build/lib/nvim/parser/*.so; do
+ lipo -info "$macho" | grep -q arm64 || exit 1
+done
+cpack --config build/CPackConfig.cmake
diff --git a/.github/scripts/close_unresponsive.js b/.github/scripts/close_unresponsive.js
new file mode 100644
index 0000000000..f0e8bbe93e
--- /dev/null
+++ b/.github/scripts/close_unresponsive.js
@@ -0,0 +1,55 @@
+function labeledEvent(data) {
+ return data.event === "labeled" && data.label.name === "needs:response";
+}
+
+const numberOfDaysLimit = 30;
+const close_message = `This has been closed since a request for information has \
+not been answered for ${numberOfDaysLimit} days. It can be reopened when the \
+requested information is provided.`;
+
+module.exports = async ({ github, context }) => {
+ const owner = context.repo.owner;
+ const repo = context.repo.repo;
+
+ const issues = await github.rest.issues.listForRepo({
+ owner: owner,
+ repo: repo,
+ labels: "needs:response",
+ });
+ const numbers = issues.data.map((e) => e.number);
+
+ for (const number of numbers) {
+ const events = await github.paginate(
+ github.rest.issues.listEventsForTimeline,
+ {
+ owner: owner,
+ repo: repo,
+ issue_number: number,
+ },
+ (response) => response.data.filter(labeledEvent)
+ );
+
+ const latest_response_label = events[events.length - 1];
+
+ const created_at = new Date(latest_response_label.created_at);
+ const now = new Date();
+ const diff = now - created_at;
+ const diffDays = diff / (1000 * 60 * 60 * 24);
+
+ if (diffDays > numberOfDaysLimit) {
+ github.rest.issues.update({
+ owner: owner,
+ repo: repo,
+ issue_number: number,
+ state: "closed",
+ });
+
+ github.rest.issues.createComment({
+ owner: owner,
+ repo: repo,
+ issue_number: number,
+ body: close_message,
+ });
+ }
+ }
+};
diff --git a/.github/workflows/env.ps1 b/.github/scripts/env.ps1
index 8ac267f2f9..d1dba5d558 100644
--- a/.github/workflows/env.ps1
+++ b/.github/scripts/env.ps1
@@ -1,3 +1,5 @@
+# This script enables Developer Command Prompt
+# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
$installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if ($installationPath -and (Test-Path "$installationPath\Common7\Tools\vsdevcmd.bat")) {
& "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x64 -no_logo && set" | ForEach-Object {
diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh
new file mode 100755
index 0000000000..9a782e9698
--- /dev/null
+++ b/.github/scripts/install_deps.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+
+while (($# > 0)); do
+ case $1 in
+ --test) # install test dependencies
+ TEST=1
+ shift
+ ;;
+ esac
+done
+
+os=$(uname -s)
+if [[ $os == Linux ]]; then
+ sudo apt-get update
+ sudo apt-get install -y build-essential cmake curl gettext ninja-build
+
+ if [[ $CC == clang ]]; then
+ DEFAULT_CLANG_VERSION=$(echo | clang -dM -E - | grep __clang_major | awk '{print $3}')
+ CLANG_VERSION=17
+ if ((DEFAULT_CLANG_VERSION >= CLANG_VERSION)); then
+ echo "Default clang version is $DEFAULT_CLANG_VERSION, which equal or larger than wanted version $CLANG_VERSION. Aborting!"
+ exit 1
+ fi
+
+ wget https://apt.llvm.org/llvm.sh
+ chmod +x llvm.sh
+ sudo ./llvm.sh $CLANG_VERSION
+ sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-$CLANG_VERSION 100
+ sudo update-alternatives --set clang /usr/bin/clang-$CLANG_VERSION
+ fi
+
+ if [[ -n $TEST ]]; then
+ sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb
+ fi
+elif [[ $os == Darwin ]]; then
+ brew update --quiet
+ brew install ninja
+ if [[ -n $TEST ]]; then
+ brew install cpanminus
+ fi
+fi
diff --git a/.github/scripts/install_deps_ubuntu.sh b/.github/scripts/install_deps_ubuntu.sh
deleted file mode 100755
index 012409ba4a..0000000000
--- a/.github/scripts/install_deps_ubuntu.sh
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-
-PACKAGES=(
- autoconf
- automake
- build-essential
- cmake
- cpanminus
- curl
- gettext
- libtool-bin
- locales-all
- ninja-build
- pkg-config
- unzip
-)
-
-sudo apt-get update
-sudo apt-get install -y "${PACKAGES[@]}"
diff --git a/.github/scripts/remove-reviewers.js b/.github/scripts/remove-reviewers.js
index 40a8eca423..9e44e4ac86 100644
--- a/.github/scripts/remove-reviewers.js
+++ b/.github/scripts/remove-reviewers.js
@@ -1,18 +1,16 @@
-module.exports = async ({github, context}) => {
+module.exports = async ({ github, context }) => {
const requestedReviewers = await github.rest.pulls.listRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
- pull_number: context.issue.number
+ pull_number: context.issue.number,
});
- const reviewers = requestedReviewers.data.users.map(e => e.login)
- const team_reviewers = requestedReviewers.data.teams.map(e => e.name);
+ const reviewers = requestedReviewers.data.users.map((e) => e.login);
github.rest.pulls.removeRequestedReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers: reviewers,
- team_reviewers: team_reviewers
});
-}
+};
diff --git a/.github/scripts/unstale.js b/.github/scripts/remove_response_label.js
index f645fca5cb..f645fca5cb 100644
--- a/.github/scripts/unstale.js
+++ b/.github/scripts/remove_response_label.js
diff --git a/.github/scripts/reviews.js b/.github/scripts/reviews.js
index cc6aaa1e8b..d28d91c2f6 100644
--- a/.github/scripts/reviews.js
+++ b/.github/scripts/reviews.js
@@ -1,108 +1,118 @@
-module.exports = async ({github, context}) => {
+module.exports = async ({ github, context }) => {
const pr_data = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
- pull_number: context.issue.number
- })
- const labels = pr_data.data.labels.map(e => e.name)
+ pull_number: context.issue.number,
+ });
+ const labels = pr_data.data.labels.map((e) => e.name);
+ const reviewers = new Set();
+
+ if (labels.includes("api")) {
+ reviewers.add("bfredl");
+ reviewers.add("famiu");
+ }
- const reviewers = new Set()
- const team_reviewers = new Array()
- if (labels.includes('api')) {
- reviewers.add("bfredl")
- reviewers.add("muniter")
+ if (labels.includes("build")) {
+ reviewers.add("dundargoc");
+ reviewers.add("jamessan");
+ reviewers.add("justinmk");
}
- if (labels.includes('build')) {
- reviewers.add("jamessan")
- reviewers.add("justinmk")
+ if (labels.includes("ci")) {
+ reviewers.add("dundargoc");
+ reviewers.add("jamessan");
+ reviewers.add("justinmk");
}
- if (labels.includes('ci')) {
- team_reviewers.push('ci');
+ if (labels.includes("column")) {
+ reviewers.add("lewis6991");
}
- if (labels.includes('column')) {
- reviewers.add("lewis6991")
+ if (labels.includes("dependencies")) {
+ reviewers.add("jamessan");
}
- if (labels.includes('dependencies')) {
- reviewers.add("jamessan")
+ if (labels.includes("diagnostic")) {
+ reviewers.add("gpanders");
}
- if (labels.includes('diagnostic')) {
- reviewers.add("gpanders")
+ if (labels.includes("diff")) {
+ reviewers.add("lewis6991");
}
- if (labels.includes('diff')) {
- reviewers.add("lewis6991")
+ if (labels.includes("distribution")) {
+ reviewers.add("jamessan");
}
- if (labels.includes('distribution')) {
- reviewers.add("jamessan")
+ if (labels.includes("documentation")) {
+ reviewers.add("clason");
}
- if (labels.includes('documentation')) {
- reviewers.add("clason")
+ if (labels.includes("extmarks")) {
+ reviewers.add("bfredl");
}
- if (labels.includes('extmarks')) {
- reviewers.add("bfredl")
+ if (labels.includes("filetype")) {
+ reviewers.add("clason");
+ reviewers.add("gpanders");
+ reviewers.add("smjonas");
}
- if (labels.includes('filetype')) {
- reviewers.add("clason")
- reviewers.add("gpanders")
- reviewers.add("smjonas")
+ if (labels.includes("lsp")) {
+ reviewers.add("folke");
+ reviewers.add("MariaSolOs");
+ reviewers.add("mfussenegger");
}
- if (labels.includes('lsp')) {
- team_reviewers.push('lsp');
+ if (labels.includes("options")) {
+ reviewers.add("famiu");
}
- if (labels.includes('platform:nix')) {
- reviewers.add("teto")
+ if (labels.includes("platform:nix")) {
+ reviewers.add("teto");
}
- if (labels.includes('project-management')) {
- reviewers.add("bfredl")
- reviewers.add("justinmk")
+ if (labels.includes("project-management")) {
+ reviewers.add("bfredl");
+ reviewers.add("justinmk");
}
- if (labels.includes('refactor')) {
- reviewers.add("bfredl")
+ if (labels.includes("statusline")) {
+ reviewers.add("famiu");
}
- if (labels.includes('test')) {
- reviewers.add("justinmk")
+ if (labels.includes("test")) {
+ reviewers.add("justinmk");
}
- if (labels.includes('treesitter')) {
- team_reviewers.push('treesitter');
+ if (labels.includes("treesitter")) {
+ reviewers.add("bfredl");
+ reviewers.add("clason");
+ reviewers.add("lewis6991");
}
- if (labels.includes('typo')) {
- reviewers.add("dundargoc")
+ if (labels.includes("typo")) {
+ reviewers.add("dundargoc");
}
- if (labels.includes('ui')) {
- reviewers.add("bfredl")
+ if (labels.includes("ui")) {
+ reviewers.add("bfredl");
+ reviewers.add("famiu");
}
- if (labels.includes('vim-patch')) {
- reviewers.add("seandewar")
- reviewers.add("zeertzjq")
+ if (labels.includes("vim-patch")) {
+ reviewers.add("seandewar");
+ reviewers.add("zeertzjq");
}
// Remove person that opened the PR since they can't review themselves
- const pr_opener = pr_data.data.user.login
- reviewers.delete(pr_opener)
+ const pr_opener = pr_data.data.user.login;
+ reviewers.delete(pr_opener);
github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
reviewers: Array.from(reviewers),
- team_reviewers: team_reviewers
});
-}
+};
diff --git a/.github/workflows/add-reviewers.yml b/.github/workflows/add-reviewers.yml
index f1abab5c53..22c68b6ef7 100644
--- a/.github/workflows/add-reviewers.yml
+++ b/.github/workflows/add-reviewers.yml
@@ -2,6 +2,7 @@ name: "Request reviews"
on:
pull_request_target:
types: [labeled, ready_for_review, reopened]
+ workflow_call:
jobs:
request-reviewer:
if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
@@ -9,11 +10,10 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: 'Request reviewers'
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
- github-token: ${{ secrets.TEAM_REVIEW }}
script: |
const script = require('./.github/scripts/reviews.js')
await script({github, context})
diff --git a/.github/workflows/api-docs-check.yml b/.github/workflows/api-docs-check.yml
deleted file mode 100644
index 0a57df7c33..0000000000
--- a/.github/workflows/api-docs-check.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-name: Missing API docs
-on:
- pull_request:
- branches-ignore:
- - 'marvim/api-doc-update**'
- paths:
- - 'src/nvim/api/*.[ch]'
- - 'runtime/lua/**.lua'
- - 'runtime/doc/**'
-
-jobs:
- call-regen-api-docs:
- permissions:
- contents: write
- pull-requests: write
- uses: ./.github/workflows/api-docs.yml
- with:
- check_only: true
diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml
index fa8a7dbca0..6f8fe107d2 100644
--- a/.github/workflows/api-docs.yml
+++ b/.github/workflows/api-docs.yml
@@ -1,74 +1,34 @@
-# Autogenerate the API docs on new commit to important branches
-# Also work as a check for PR's to not forget committing their doc changes
-# called from api-docs-check.yml
-name: Autogenerate API docs
+# Check if any PR needs to run the autogenerate script
+name: Autogenerate API docs and types
on:
- push:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'src/nvim/api/*.[ch]'
+ - 'src/nvim/eval.lua'
- 'runtime/lua/**.lua'
- 'runtime/doc/**'
- branches:
- - 'master'
- - 'release-[0-9]+.[0-9]+'
- workflow_dispatch:
- workflow_call:
- inputs:
- check_only:
- type: boolean
- default: false
- required: false
jobs:
- regen-api-docs:
+ regen-api-docs-and-types:
runs-on: ubuntu-latest
+ if: github.event.pull_request.draft == false
permissions:
contents: write
pull-requests: write
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- - uses: rhysd/action-setup-vim@v1
- with:
- neovim: true
- version: nightly
- - uses: actions/checkout@v3
- with:
- # Fetch depth 0 is required if called through workflow_call. In order
- # to create a PR we need to access other branches, which requires a
- # full clone.
- fetch-depth: 0
-
+ - uses: actions/checkout@v4
- name: Install dependencies
run: |
- sudo apt-get update
- sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y doxygen python3 python3-msgpack
-
- - name: Setup git config
- run: |
- git config --global user.name 'marvim'
- git config --global user.email 'marvim@users.noreply.github.com'
-
- - run: printf 'DOC_BRANCH=marvim/api-doc-update/%s\n' ${GITHUB_REF#refs/heads/} >> $GITHUB_ENV
+ ./.github/scripts/install_deps.sh
+ sudo apt-get install -y doxygen python3-msgpack
+ - uses: ./.github/actions/cache
- name: Generate docs
- id: docs
- run: |
- git checkout -b ${DOC_BRANCH}
- python3 scripts/gen_vimdoc.py
- printf 'UPDATED_DOCS=%s\n' $([ -z "$(git diff)" ]; echo $?) >> $GITHUB_OUTPUT
-
- - name: FAIL, PR has not committed doc changes
- if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && inputs.check_only }}
- run: |
- echo "Job failed, run ./scripts/gen_vimdoc.py and commit your doc changes"
- echo "The doc generation produces the following changes:"
- git diff --color --exit-code
-
- - name: Automatic PR
- if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && !inputs.check_only }}
run: |
- git add -u
- git commit -m 'docs: regenerate [skip ci]'
- git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${DOC_BRANCH}
- gh pr create --draft --fill --base ${GITHUB_REF#refs/heads/} --head ${DOC_BRANCH} || true
+ make doc
+ if [ -n "$(git status --porcelain)" ]; then
+ echo "::error::Job failed, run 'make doc' and commit your doc changes."
+ echo "::error::The doc generation produces the following changes:"
+ git diff --color --exit-code
+ fi
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 019fb20689..321cd02b0c 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -8,20 +8,9 @@ jobs:
contents: write
pull-requests: write
name: Backport Pull Request
- if: >
- github.repository_owner == 'neovim' && (
- github.event_name == 'pull_request_target' &&
- github.event.pull_request.merged
- )
+ if: github.event.pull_request.merged
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- with:
- # required to find all branches
- fetch-depth: 0
- ref: ${{ github.event.pull_request.head.sha }}
+ - uses: actions/checkout@v4
- name: Create backport PRs
- uses: zeebe-io/backport-action@v0
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- github_workspace: ${{ github.workspace }}
+ uses: korthout/backport-action@v2
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000000..d6e11fcdcb
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,94 @@
+name: build
+on:
+ pull_request:
+ branches:
+ - 'master'
+ - 'release-[0-9]+.[0-9]+'
+ paths:
+ - '**.cmake'
+ - '**/CMakeLists.txt'
+ - '**/CMakePresets.json'
+ - 'cmake.*/**'
+ - '.github/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.head_ref }}
+ cancel-in-progress: true
+
+env:
+ BIN_DIR: ${{ github.workspace }}/bin
+ INSTALL_PREFIX: ${{ github.workspace }}/nvim-install
+
+jobs:
+ macos-universal:
+ runs-on: macos-latest
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+ - run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV
+
+ - name: Build universal binary
+ run: ./.github/scripts/build_universal_macos.sh
+
+ old-cmake:
+ name: Test oldest supported cmake
+ runs-on: ubuntu-22.04
+ timeout-minutes: 15
+ env:
+ CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
+ CMAKE_VERSION: '3.10.0'
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+
+ - name: Set up environment
+ run: echo "$BIN_DIR" >> $GITHUB_PATH
+
+
+ - name: Install minimum required version of cmake
+ run: |
+ curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
+ mkdir -p "$BIN_DIR" /opt/cmake-custom
+ chmod a+x /tmp/cmake-installer.sh
+ /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
+ ln -sfn /opt/cmake-custom/bin/cmake "$BIN_DIR/cmake"
+ cmake_version="$(cmake --version | head -1)"
+ echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
+ echo "Unexpected CMake version: $cmake_version"
+ exit 1
+ }
+
+ - name: Build dependencies
+ run: make deps
+
+ - name: Build
+ run: make CMAKE_FLAGS="-D CI_BUILD=ON -D CMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX"
+
+ - name: Install
+ run: make install
+
+ use-existing-src:
+ name: Test USE_EXISTING_SRC_DIR=ON builds with no network access
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+
+ - name: Build bundled dependencies
+ run: make deps
+
+ - name: Clean bundled dependencies à la neovim/deps
+ run: |
+ rm -rf ./build
+ find .deps .deps/build -maxdepth 1 '!' \( -name .deps -o -name build -o -name src \) -exec rm -r '{}' +
+ cd .deps/build/src
+ rm -rf ./*-build
+ rm -rf ./*-stamp/*-{configure,build,install,done}
+ for d in *; do (cd "$d"; rm -rf ./autom4te.cache; make clean || true; make distclean || true); done
+
+ - name: Re-build bundled dependencies with no network access
+ run: unshare --map-root-user --net make deps DEPS_CMAKE_FLAGS=-DUSE_EXISTING_SRC_DIR=ON
+
+ - name: Build
+ run: make CMAKE_FLAGS="-D CI_BUILD=ON"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 83ee4f0358..0000000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,354 +0,0 @@
-name: CI
-on:
- push:
- branches:
- - 'master'
- - 'release-[0-9]+.[0-9]+'
- pull_request:
- branches:
- - 'master'
- - 'release-[0-9]+.[0-9]+'
- paths-ignore:
- - 'contrib/**'
-
-# Cancel any in-progress CI runs for a PR if it is updated
-concurrency:
- group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
- cancel-in-progress: true
-
-env:
- UNCRUSTIFY_VERSION: uncrustify-0.75.0
- # TEST_FILE: test/functional/core/startup_spec.lua
- # TEST_FILTER: foo
-
-jobs:
- lint:
- if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
- runs-on: ubuntu-22.04
- timeout-minutes: 10
- env:
- CC: gcc
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh lint
-
- - name: Install apt packages
- run: |
- ./.github/scripts/install_deps_ubuntu.sh
- sudo apt-get install -y lua-check
-
- - name: Cache uncrustify
- id: cache-uncrustify
- uses: actions/cache@v3
- with:
- path: ${{ env.CACHE_UNCRUSTIFY }}
- key: ${{ env.UNCRUSTIFY_VERSION }}
-
- - name: Clone uncrustify
- if: steps.cache-uncrustify.outputs.cache-hit != 'true'
- uses: actions/checkout@v3
- with:
- repository: uncrustify/uncrustify
- ref: ${{ env.UNCRUSTIFY_VERSION }}
- path: uncrustify
-
- - name: Install uncrustify
- if: steps.cache-uncrustify.outputs.cache-hit != 'true'
- run: |
- source_dir=uncrustify
- build_dir=uncrustify/build
- cmake -S $source_dir -B $build_dir -G Ninja -DCMAKE_BUILD_TYPE=Release
- cmake --build $build_dir
- mkdir -p $HOME/.cache
- cp $build_dir/uncrustify ${{ env.CACHE_UNCRUSTIFY }}
-
- - uses: ./.github/actions/cache
-
- - name: Build third-party deps
- run: ./ci/before_script.sh
-
- - if: "!cancelled()"
- name: Determine if run should be aborted
- id: abort_job
- run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: lintstylua
- uses: JohnnyMorganz/stylua-action@v2
- with:
- token: ${{ secrets.GITHUB_TOKEN }}
- version: latest
- args: --check runtime/
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: luacheck
- run: |
- cmake -B $BUILD_DIR -G Ninja
- cmake --build $BUILD_DIR --target lintlua-luacheck
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: lintsh
- run: make lintsh
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: uncrustify
- run: |
- ${{ env.CACHE_UNCRUSTIFY }} -c ./src/uncrustify.cfg -q --replace --no-backup $(find ./src/nvim -name "*.[ch]")
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: suggester / uncrustify
- uses: reviewdog/action-suggester@v1
- with:
- github_token: ${{ secrets.GITHUB_TOKEN }}
- tool_name: uncrustify
- cleanup: false
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: check uncrustify
- run: |
- git diff --color --exit-code
-
- - name: Cache dependencies
- run: ./ci/before_cache.sh
-
- lintc:
- # This job tests two things: it lints the code but also builds neovim using
- # system dependencies instead of bundled dependencies. This is to make sure
- # we are able to build neovim without pigeonholing ourselves into specifics
- # of the bundled dependencies.
-
- if: (github.event_name == 'pull_request' && github.base_ref == 'master') || (github.event_name == 'push' && github.ref == 'refs/heads/master')
- runs-on: ubuntu-22.04
- timeout-minutes: 10
- env:
- CC: gcc
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh lintc
-
- - name: Install apt packages
- run: |
- sudo add-apt-repository ppa:neovim-ppa/stable
- ./.github/scripts/install_deps_ubuntu.sh
- sudo apt-get install -y \
- libluajit-5.1-dev \
- libmsgpack-dev \
- libtermkey-dev \
- libtree-sitter-dev \
- libunibilium-dev \
- libuv1-dev \
- lua-busted \
- lua-filesystem \
- lua-inspect \
- lua-lpeg \
- lua-nvim \
- luajit
- # libvterm-dev \
- # lua-luv-dev
-
- # Remove comments from packages once we start using these external
- # dependencies. See env.sh for more context.
-
- - uses: ./.github/actions/cache
-
- - name: Build third-party deps
- run: ./ci/before_script.sh
-
- - name: Build nvim
- run: make
-
- - if: "!cancelled()"
- name: Determine if run should be aborted
- id: abort_job
- run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: clint.py
- run: cmake --build build --target lintc-clint
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: check-single-includes
- run: make check-single-includes
-
- - name: Cache dependencies
- run: ./ci/before_cache.sh
-
- posix:
- name: ${{ matrix.runner }} ${{ matrix.flavor }} (cc=${{ matrix.cc }})
- strategy:
- fail-fast: false
- matrix:
- include:
- - flavor: asan
- cc: clang
- runner: ubuntu-22.04
- os: linux
- - flavor: tsan
- cc: clang
- runner: ubuntu-22.04
- os: linux
- - flavor: uchar
- cc: gcc
- runner: ubuntu-22.04
- os: linux
- - cc: clang
- runner: macos-12
- os: osx
-
- # functionaltest-lua is our dumping ground for non-mainline configurations.
- # 1. Check that the tests pass with PUC Lua instead of LuaJIT.
- # 2. Use as oldest/minimum versions of dependencies/build tools we
- # still explicitly support so we don't accidentally rely on
- # features that is only available on later versions.
- # 3. No treesitter parsers installed.
- - flavor: functionaltest-lua
- cc: gcc
- runner: ubuntu-22.04
- os: linux
- cmake: minimum_required
- runs-on: ${{ matrix.runner }}
- timeout-minutes: 45
- env:
- CC: ${{ matrix.cc }}
- CI_OS_NAME: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v3
-
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh ${{ matrix.flavor }}
-
- - name: Install apt packages
- if: matrix.os == 'linux'
- run: ./.github/scripts/install_deps_ubuntu.sh
-
- - name: Install minimum required version of cmake
- if: matrix.cmake == 'minimum_required'
- env:
- CMAKE_URL: 'https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh'
- CMAKE_VERSION: '3.10.0'
- shell: bash
- run: |
- curl --retry 5 --silent --show-error --fail -o /tmp/cmake-installer.sh "$CMAKE_URL"
- mkdir -p "$HOME/.local/bin" /opt/cmake-custom
- chmod a+x /tmp/cmake-installer.sh
- /tmp/cmake-installer.sh --prefix=/opt/cmake-custom --skip-license
- ln -sfn /opt/cmake-custom/bin/cmake "$HOME/.local/bin/cmake"
- cmake_version="$(cmake --version | head -1)"
- echo "$cmake_version" | grep -qF "cmake version $CMAKE_VERSION" || {
- echo "Unexpected CMake version: $cmake_version"
- exit 1
- }
-
- - name: Install brew packages
- if: matrix.os == 'osx'
- run: |
- brew update --quiet
- brew install automake cpanminus ninja
-
- - name: Setup interpreter packages
- run: ./ci/install.sh
-
- - uses: ./.github/actions/cache
-
- - name: Build third-party deps
- run: ./ci/before_script.sh
-
- - name: Build
- run: ./ci/run_tests.sh build_nvim
-
- - if: "!cancelled()"
- name: Determine if run should be aborted
- id: abort_job
- run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT
-
- - if: matrix.flavor != 'tsan' && matrix.flavor != 'functionaltest-lua' && (success() || failure() && steps.abort_job.outputs.status == 'success')
- name: Unittests
- run: ./ci/run_tests.sh unittests
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: Functionaltests
- run: ./ci/run_tests.sh functionaltests
-
- - if: matrix.flavor != 'tsan' && (success() || failure() && steps.abort_job.outputs.status == 'success')
- name: Oldtests
- run: ./ci/run_tests.sh oldtests
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: Install nvim
- run: ./ci/run_tests.sh install_nvim
-
- - name: Cache dependencies
- run: ./ci/before_cache.sh
-
- windows:
- runs-on: windows-2019
- timeout-minutes: 45
- env:
- DEPS_BUILD_DIR: ${{ github.workspace }}/nvim-deps
- CACHE_NVIM_DEPS_DIR: ${{ github.workspace }}/nvim-deps
- DEPS_PREFIX: ${{ github.workspace }}/nvim-deps/usr
- name: windows (MSVC_64)
- steps:
- - uses: actions/checkout@v3
-
- - uses: ./.github/actions/cache
-
- - name: Set env
- run: ./.github/workflows/env.ps1
-
- - name: Build deps
- run: |
- cmake -S cmake.deps -B $env:DEPS_BUILD_DIR -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo'
- cmake --build $env:DEPS_BUILD_DIR
-
- - name: Build nvim
- run: |
- cmake -B build -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX" -DCI_BUILD=ON
- cmake --build build
-
- - name: Install test deps
- run: |
- $PSNativeCommandArgumentPassing = 'Legacy'
-
- & build\bin\nvim.exe "--version"
-
- # Ensure that the "win32" feature is set.
- & build\bin\nvim -u NONE --headless -c 'exe !has(\"win32\").\"cq\"'
-
- python -m pip install pynvim
- # Sanity check
- python -c "import pynvim; print(str(pynvim))"
-
- gem.cmd install --pre neovim
- Get-Command -CommandType Application neovim-ruby-host.bat
-
- node --version
- npm.cmd --version
-
- npm.cmd install -g neovim
- Get-Command -CommandType Application neovim-node-host.cmd
- npm.cmd link neovim
-
- - if: "!cancelled()"
- name: Determine if run should be aborted
- id: abort_job
- run: |
- "status=${{ job.status }}" >> $env:GITHUB_OUTPUT
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: Run functionaltests
- run: cmake --build build --target functionaltest
-
- - if: success() || failure() && steps.abort_job.outputs.status == 'success'
- name: Run oldtests
- run: |
- # Add MSYS to path, required for e.g. `find` used in test scripts.
- # But would break functionaltests, where its `more` would be used then.
- $OldPath = $env:PATH
- $env:PATH = "C:\msys64\usr\bin;$env:PATH"
- & "C:\msys64\mingw64\bin\mingw32-make.exe" -C $(Convert-Path src\nvim\testdir) VERBOSE=1
- $env:PATH = $OldPath
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index a11a87f93a..365c3fdf56 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,9 +1,15 @@
name: "CodeQL"
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
on:
- schedule:
- - cron: '42 0 * * 0'
- workflow_dispatch:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ "master" ]
jobs:
analyze:
name: Analyze
@@ -13,28 +19,21 @@ jobs:
contents: read
security-events: write
- strategy:
- fail-fast: false
- matrix:
- language: [ 'cpp' ]
-
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- - name: Setup common environment variables
- run: ./.github/workflows/env.sh
-
- - name: Install apt packages
- run: ./.github/scripts/install_deps_ubuntu.sh
+ - name: Install dependencies
+ run: ./.github/scripts/install_deps.sh
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
- languages: ${{ matrix.language }}
+ languages: cpp
+
+ - uses: ./.github/actions/cache
- - if: matrix.language == 'cpp'
- run: make
+ - run: make
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
index 87e2cb1453..3eed1e51df 100644
--- a/.github/workflows/coverity.yml
+++ b/.github/workflows/coverity.yml
@@ -8,12 +8,10 @@ jobs:
scan:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Install dependencies
- run: |
- sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential cmake gettext libtool-bin locales ninja-build pkg-config unzip
+ run: ./.github/scripts/install_deps.sh
- name: Download Coverity
run: |
diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh
deleted file mode 100755
index 42a355da44..0000000000
--- a/.github/workflows/env.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/bash
-set -e -u
-
-FLAVOR=${1:-}
-
-cat <<EOF >> "$GITHUB_PATH"
-$HOME/.local/bin
-EOF
-
-cat <<EOF >> "$GITHUB_ENV"
-CI_BUILD_DIR=$GITHUB_WORKSPACE
-BUILD_DIR=$GITHUB_WORKSPACE/build
-DEPS_BUILD_DIR=$HOME/nvim-deps
-INSTALL_PREFIX=$HOME/nvim-install
-LOG_DIR=$GITHUB_WORKSPACE/build/log
-NVIM_LOG_FILE=$GITHUB_WORKSPACE/build/.nvimlog
-VALGRIND_LOG=$GITHUB_WORKSPACE/build/log/valgrind-%p.log
-CACHE_NVIM_DEPS_DIR=$HOME/.cache/nvim-deps
-CACHE_MARKER=$HOME/.cache/nvim-deps/.ci_cache_marker
-CACHE_UNCRUSTIFY=$HOME/.cache/uncrustify
-EOF
-
-DEPS_CMAKE_FLAGS=
-FUNCTIONALTEST=functionaltest
-BUILD_FLAGS="CMAKE_FLAGS=-DCI_BUILD=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX:PATH=$HOME/nvim-install -DBUSTED_OUTPUT_TYPE=nvim -DDEPS_PREFIX=$HOME/nvim-deps/usr -DMIN_LOG_LEVEL=3"
-
-case "$FLAVOR" in
- asan)
- cat <<EOF >> "$GITHUB_ENV"
-CLANG_SANITIZER=ASAN_UBSAN
-ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan:intercept_tls_get_addr=0
-UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan
-EOF
- ;;
- tsan)
- cat <<EOF >> "$GITHUB_ENV"
-TSAN_OPTIONS=log_path=$GITHUB_WORKSPACE/build/log/tsan
-CLANG_SANITIZER=TSAN
-EOF
- ;;
- uchar)
- cat <<EOF >> "$GITHUB_ENV"
-BUILD_UCHAR=1
-EOF
- ;;
- lintc)
-# Re-enable once system deps are available
-# BUILD_FLAGS="$BUILD_FLAGS -DLIBLUV_LIBRARY:FILEPATH=/usr/lib/$(dpkg-architecture -qDEB_HOST_MULTIARCH)/lua/5.1/luv.so -DLIBLUV_INCLUDE_DIR:PATH=/usr/include/lua5.1"
-
- # Ideally all dependencies should external for this job, but some
- # dependencies don't have the required version available. We use the
- # bundled versions for these with the hopes of being able to remove them
- # later on.
- DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED=OFF -DUSE_BUNDLED_LUV=ON -DUSE_BUNDLED_LIBVTERM=ON"
- ;;
- functionaltest-lua)
- BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON"
- FUNCTIONALTEST=functionaltest-lua
- DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUAJIT=OFF"
- ;;
- *)
- ;;
-esac
-
-cat <<EOF >> "$GITHUB_ENV"
-$BUILD_FLAGS
-DEPS_CMAKE_FLAGS=$DEPS_CMAKE_FLAGS
-FUNCTIONALTEST=$FUNCTIONALTEST
-EOF
diff --git a/.github/workflows/issue-open-check.yml b/.github/workflows/issue-open-check.yml
new file mode 100644
index 0000000000..eac1c2ee4d
--- /dev/null
+++ b/.github/workflows/issue-open-check.yml
@@ -0,0 +1,34 @@
+name: Issue Open Check
+
+on:
+ issues:
+ types: [opened]
+
+jobs:
+ issue-open-check:
+ permissions:
+ issues: write
+ runs-on: ubuntu-latest
+ steps:
+ - name: check issue title
+ id: check-issue
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const title = context.payload.issue.title;
+ const titleSplit = title.split(/\s+/).map(e => e.toLowerCase());
+ const keywords = ['api', 'treesitter', 'ui', 'lsp'];
+ var match = new Set();
+ for (const keyword of keywords) {
+ if (titleSplit.includes(keyword)) {
+ match.add(keyword)
+ }
+ }
+ if (match.size !== 0) {
+ github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ labels: Array.from(match)
+ })
+ }
diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml
index 60689029a3..52682d93dd 100644
--- a/.github/workflows/labeler.yml
+++ b/.github/workflows/labeler.yml
@@ -3,7 +3,6 @@ on:
pull_request_target:
types: [opened]
jobs:
-
triage:
runs-on: ubuntu-latest
permissions:
@@ -12,10 +11,10 @@ jobs:
steps:
- uses: actions/labeler@v4
with:
- repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: ""
type-scope:
+ needs: triage
runs-on: ubuntu-latest
permissions:
contents: write
@@ -34,17 +33,7 @@ jobs:
run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" || true
request-reviewer:
- if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false
- runs-on: ubuntu-latest
needs: ["triage", "type-scope"]
permissions:
pull-requests: write
- steps:
- - uses: actions/checkout@v3
- - name: 'Request reviewers'
- uses: actions/github-script@v6
- with:
- github-token: ${{ secrets.TEAM_REVIEW }}
- script: |
- const script = require('./.github/scripts/reviews.js')
- await script({github, context})
+ uses: ./.github/workflows/add-reviewers.yml
diff --git a/.github/workflows/lintcommit.yml b/.github/workflows/lintcommit.yml
index a7a227865d..8f56c057ae 100644
--- a/.github/workflows/lintcommit.yml
+++ b/.github/workflows/lintcommit.yml
@@ -8,16 +8,20 @@ jobs:
lint-commits:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- path: pr_nvim
- - uses: rhysd/action-setup-vim@v1
- with:
- neovim: 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})"
+
+ - run: ./.github/scripts/install_deps.sh
+ - uses: ./.github/actions/cache
+ - name: Build
+ run: |
+ cmake -S cmake.deps --preset ci
+ cmake --build .deps
+ cmake --preset ci
+ cmake --build build
+
+ - name: lintcommit
+ run: cmake --build build --target lintcommit
diff --git a/.github/workflows/news.yml b/.github/workflows/news.yml
index 11807e9b42..d4f8e5ad65 100644
--- a/.github/workflows/news.yml
+++ b/.github/workflows/news.yml
@@ -1,13 +1,15 @@
name: "news.txt check"
on:
pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
branches:
- 'master'
jobs:
check:
runs-on: ubuntu-latest
+ if: github.event.pull_request.draft == false
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
diff --git a/.github/workflows/notes.md b/.github/workflows/notes.md
index d752f10609..8c518b0bdb 100644
--- a/.github/workflows/notes.md
+++ b/.github/workflows/notes.md
@@ -9,14 +9,14 @@ ${NVIM_VERSION}
#### Zip
1. Download **nvim-win64.zip**
-2. Extract the zip.
-3. Run `nvim-qt.exe`
+2. Extract the zip
+3. Run `nvim.exe` on your CLI of choice
#### MSI
1. Download **nvim-win64.msi**
2. Run the MSI
-3. Search and run `nvim-qt.exe` or run `nvim.exe` on your CLI of choice.
+3. Run `nvim.exe` on your CLI of choice
### macOS
@@ -27,17 +27,9 @@ ${NVIM_VERSION}
### Linux (x64)
-#### Tarball
-
-1. Download **nvim-linux64.tar.gz**
-2. Extract: `tar xzvf nvim-linux64.tar.gz`
-3. Run `./nvim-linux64/bin/nvim`
-
-#### Debian Package
-
-1. Download **nvim-linux64.deb**
-2. Install the package using `sudo apt install ./nvim-linux64.deb`
-3. Run `nvim`
+Minimum glibc version to run these releases is 2.31. People requiring releases
+that work on older glibc versions can find them at
+https://github.com/neovim/neovim-releases.
#### AppImage
1. Download **nvim.appimage**
@@ -48,6 +40,12 @@ ${NVIM_VERSION}
./squashfs-root/usr/bin/nvim
```
+#### Tarball
+
+1. Download **nvim-linux64.tar.gz**
+2. Extract: `tar xzvf nvim-linux64.tar.gz`
+3. Run `./nvim-linux64/bin/nvim`
+
### Other
- Install by [package manager](https://github.com/neovim/neovim/wiki/Installing-Neovim)
@@ -56,7 +54,6 @@ ${NVIM_VERSION}
```
${SHA_LINUX_64_TAR}
-${SHA_LINUX_64_DEB}
${SHA_APP_IMAGE}
${SHA_APP_IMAGE_ZSYNC}
${SHA_MACOS}
diff --git a/.github/workflows/optional.yml b/.github/workflows/optional.yml
new file mode 100644
index 0000000000..ebaf23d29b
--- /dev/null
+++ b/.github/workflows/optional.yml
@@ -0,0 +1,41 @@
+name: optional
+on:
+ pull_request:
+ types: [labeled, opened, synchronize, reopened]
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+env:
+ INSTALL_PREFIX: ${{ github.workspace }}/nvim-install
+ # TEST_FILE: test/functional/shada
+ # TEST_FILTER: foo
+
+jobs:
+ s390x:
+ if: contains(github.event.pull_request.labels.*.name, 'ci-s390x')
+ strategy:
+ fail-fast: false
+ matrix:
+ test: [functionaltest, oldtest]
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
+ - uses: docker://multiarch/ubuntu-core:s390x-focal
+ with:
+ args: >
+ bash -c
+ "
+ apt-get -y update &&
+ DEBIAN_FRONTEND=noninteractive apt-get -y install build-essential cmake curl gettext ninja-build locales-all cpanminus git attr libattr1-dev &&
+ git clone --depth=1 https://github.com/neovim/neovim.git &&
+ cd neovim &&
+ git fetch origin ${{ github.ref }}:pr &&
+ git switch pr &&
+ cmake -S cmake.deps -B .deps -G Ninja -D USE_BUNDLED_LUAJIT=OFF -D USE_BUNDLED_LUA=ON &&
+ cmake --build .deps &&
+ cmake -B build -G Ninja -D PREFER_LUA=ON &&
+ make ${{ matrix.test }}
+ "
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1df33962e5..b19019d06d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -17,124 +17,57 @@ on:
jobs:
linux:
runs-on: ubuntu-20.04
+ env:
+ CC: gcc-10
outputs:
version: ${{ steps.build.outputs.version }}
- release: ${{ steps.build.outputs.release }}
steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Install dependencies
- run: |
- sudo apt-get update
- sudo apt-get install -y autoconf automake build-essential cmake gettext libtool-bin locales ninja-build pkg-config unzip
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
- run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV
+ run: |
+ echo 'NVIM_BUILD_TYPE=Release' >> $GITHUB_ENV
+ echo 'APPIMAGE_TAG=latest' >> $GITHUB_ENV
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
- run: printf 'NVIM_BUILD_TYPE=RelWithDebInfo\n' >> $GITHUB_ENV
- - name: Build release
- id: build
run: |
- CC=gcc-10 make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH="
- printf 'version<<END\n' >> $GITHUB_OUTPUT
- ./build/bin/nvim --version | head -n 3 >> $GITHUB_OUTPUT
- printf 'END\n' >> $GITHUB_OUTPUT
- printf 'release=%s\n' "$(./build/bin/nvim --version | head -n 1)" >> $GITHUB_OUTPUT
- make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install
- cd "$GITHUB_WORKSPACE/build/"
- cpack -C $NVIM_BUILD_TYPE
+ echo 'NVIM_BUILD_TYPE=RelWithDebInfo' >> $GITHUB_ENV
+ echo 'APPIMAGE_TAG=nightly' >> $GITHUB_ENV
+ - name: appimage
+ run: ./scripts/genappimage.sh ${APPIMAGE_TAG}
+ - name: tar.gz
+ run: cpack --config build/CPackConfig.cmake -G TGZ
- uses: actions/upload-artifact@v3
with:
- name: nvim-linux64
+ name: appimage
path: |
- build/nvim-linux64.tar.gz
- build/nvim-linux64.deb
+ build/bin/nvim.appimage
+ build/bin/nvim.appimage.zsync
retention-days: 1
-
- appimage:
- runs-on: ubuntu-20.04
- container:
- image: ubuntu:18.04
- options: --privileged # Privileged mode is needed to load fuse module.
- steps:
- - name: Prepare container
- run: |
- apt-get update
- apt-get install -y software-properties-common
- add-apt-repository -y ppa:ubuntu-toolchain-r/test # For gcc-10.
- add-apt-repository -y ppa:git-core/ppa # For git>=2.18.
- apt-get update
- apt-get install -y git gcc-10
- apt-get install -y fuse libfuse2 # For linuxdeploy.
- # Workaround for https://github.com/actions/checkout/issues/766.
- git config --global --add safe.directory "$GITHUB_WORKSPACE"
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Install dependencies
- run: |
- apt-get update
- apt-get install -y autoconf automake build-essential cmake gettext libtool-bin locales ninja-build pkg-config unzip
- - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
- run: CC=gcc-10 make appimage-latest
- - if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
- run: CC=gcc-10 make appimage-nightly
- uses: actions/upload-artifact@v3
with:
- name: appimage
+ name: nvim-linux64
path: |
- build/bin/nvim.appimage
- build/bin/nvim.appimage.zsync
+ build/nvim-linux64.tar.gz
retention-days: 1
+ - name: Export version
+ id: build
+ run: |
+ printf 'version<<END\n' >> $GITHUB_OUTPUT
+ ./build/bin/nvim --version | head -n 3 >> $GITHUB_OUTPUT
+ printf 'END\n' >> $GITHUB_OUTPUT
macOS:
runs-on: macos-11
steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Install brew packages
- run: |
- brew update --quiet
- brew install automake ninja
+ - uses: actions/checkout@v4
+ - name: Install dependencies
+ run: ./.github/scripts/install_deps.sh
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
run: printf 'NVIM_BUILD_TYPE=RelWithDebInfo\n' >> $GITHUB_ENV
- - name: Provision universal `libintl`
- run: |
- GETTEXT_PREFIX="$(brew --prefix gettext)"
- printf 'GETTEXT_PREFIX=%s\n' "$GETTEXT_PREFIX" >> $GITHUB_ENV
- bottle_tag="arm64_big_sur"
- brew fetch --bottle-tag="$bottle_tag" gettext
- cd "$(mktemp -d)"
- tar xf "$(brew --cache)"/**/*gettext*${bottle_tag}*.tar.gz
- lipo gettext/*/lib/libintl.a "${GETTEXT_PREFIX}/lib/libintl.a" -create -output libintl.a
- mv -f libintl.a /usr/local/lib/
- - name: Ensure static linkage to `libintl`
- run: |
- # We're about to mangle `gettext`, so let's remove any potentially broken
- # installs (e.g. curl, git) as those could interfere with our build.
- brew uninstall $(brew uses --installed --recursive gettext)
- brew unlink gettext
- ln -sf "$(brew --prefix)/opt/$(readlink "${GETTEXT_PREFIX}")/bin"/* /usr/local/bin/
- ln -sf "$(brew --prefix)/opt/$(readlink "${GETTEXT_PREFIX}")/include"/* /usr/local/include/
- rm -f "$GETTEXT_PREFIX"
- - name: Build release
- run: |
- export MACOSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -f1 -d.)"
- OSX_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} -DCMAKE_OSX_ARCHITECTURES=arm64\;x86_64"
- make CMAKE_BUILD_TYPE=${NVIM_BUILD_TYPE} \
- CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX:PATH= $OSX_FLAGS" \
- DEPS_CMAKE_FLAGS="$OSX_FLAGS"
- make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-macos" install
- cd "$GITHUB_WORKSPACE/build/"
- # Make sure we build everything for M1 as well
- for macho in bin/* lib/nvim/parser/*.so
- do
- lipo -info "$macho" | grep -q arm64 || exit 1
- done
- cpack -C "$NVIM_BUILD_TYPE"
+ - name: Build universal binary
+ run: ./.github/scripts/build_universal_macos.sh
- uses: actions/upload-artifact@v3
with:
name: nvim-macos
@@ -143,24 +76,17 @@ jobs:
windows:
runs-on: windows-2019
- env:
- DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }}
- DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }}
- CMAKE_BUILD_TYPE: "RelWithDebInfo"
name: windows (MSVC_64)
steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set env
- run: ./.github/workflows/env.ps1
+ - uses: actions/checkout@v4
+ - run: .github/scripts/env.ps1
- name: Build deps
run: |
- cmake -S cmake.deps -B $env:DEPS_BUILD_DIR -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo'
- cmake --build $env:DEPS_BUILD_DIR
+ cmake -S cmake.deps -B .deps -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo'
+ cmake --build .deps
- name: build package
run: |
- cmake -B build -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo' -DDEPS_PREFIX="$env:DEPS_PREFIX"
+ cmake -B build -G Ninja -DCMAKE_BUILD_TYPE='RelWithDebInfo'
cmake --build build --target package
- uses: actions/upload-artifact@v3
with:
@@ -171,7 +97,7 @@ jobs:
retention-days: 1
publish:
- needs: [linux, appimage, macOS, windows]
+ needs: [linux, macOS, windows]
runs-on: ubuntu-latest
env:
GH_REPO: ${{ github.repository }}
@@ -181,7 +107,7 @@ jobs:
steps:
# Must perform checkout first, since it deletes the target directory
# before running, and would therefore delete the downloaded artifacts
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- uses: actions/download-artifact@v3
@@ -215,8 +141,6 @@ jobs:
cd ./nvim-linux64
sha256sum nvim-linux64.tar.gz > nvim-linux64.tar.gz.sha256sum
echo "SHA_LINUX_64_TAR=$(cat nvim-linux64.tar.gz.sha256sum)" >> $GITHUB_ENV
- sha256sum nvim-linux64.deb > nvim-linux64.deb.sha256sum
- echo "SHA_LINUX_64_DEB=$(cat nvim-linux64.deb.sha256sum)" >> $GITHUB_ENV
- name: Generate App Image SHA256 checksums
run: |
cd ./appimage
@@ -256,22 +180,24 @@ jobs:
steps:
- if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly')
name: Publish stable
- uses: vedantmgoyal2009/winget-releaser@v1
+ uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: Neovim.Neovim
release-tag: ${{ github.event.inputs.tag_name || github.ref_name }}
token: ${{ secrets.WINGET_TOKEN }}
+ - name: Fetch nightly build msi from previous job
+ if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
+ uses: actions/download-artifact@v3
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
- name: Get nightly version
+ name: Get version from nightly build msi
id: get-version
run: |
- Invoke-WebRequest https://github.com/neovim/neovim/releases/download/nightly/nvim-win64.msi -OutFile setup.msi
Install-Module -Name 'Carbon.Windows.Installer' -Force
- $VERSION = (Get-CMsi (Resolve-Path .\setup.msi).Path).ProductVersion
+ $VERSION = (Get-CMsi (Resolve-Path .\nvim-win64\nvim-win64.msi).Path).ProductVersion
"version=$VERSION" >> $env:GITHUB_OUTPUT
- if: github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name == 'nightly')
name: Publish nightly
- uses: vedantmgoyal2009/winget-releaser@v1
+ uses: vedantmgoyal2009/winget-releaser@v2
with:
identifier: Neovim.Neovim.Nightly
version: ${{ steps.get-version.outputs.version }}
diff --git a/.github/workflows/remove-reviewers.yml b/.github/workflows/remove-reviewers.yml
index 7ab3ef568c..3fe7493b93 100644
--- a/.github/workflows/remove-reviewers.yml
+++ b/.github/workflows/remove-reviewers.yml
@@ -8,11 +8,10 @@ jobs:
permissions:
pull-requests: write
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: 'Remove reviewers'
- uses: actions/github-script@v6
+ uses: actions/github-script@v7
with:
- github-token: ${{ secrets.TEAM_REVIEW }}
script: |
const script = require('./.github/scripts/remove-reviewers.js')
await script({github, context})
diff --git a/.github/workflows/response.yml b/.github/workflows/response.yml
new file mode 100644
index 0000000000..663ae6ad87
--- /dev/null
+++ b/.github/workflows/response.yml
@@ -0,0 +1,35 @@
+name: no_response
+on:
+ schedule:
+ - cron: '30 1 * * *' # Run every day at 01:30
+ workflow_dispatch:
+ issue_comment:
+
+jobs:
+ close:
+ if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/github-script@v7
+ with:
+ script: |
+ const script = require('./.github/scripts/close_unresponsive.js')
+ await script({github, context})
+
+ remove_label:
+ if: github.event_name == 'issue_comment'
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ pull-requests: write
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/github-script@v7
+ with:
+ script: |
+ const script = require('./.github/scripts/remove_response_label.js')
+ await script({github, context})
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
deleted file mode 100644
index c1d3ee3ff3..0000000000
--- a/.github/workflows/stale.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: 'Close stale issues and PRs'
-on:
- schedule:
- - cron: '30 1 * * *' # Run every day at 01:30
- workflow_dispatch:
- issue_comment:
-
-jobs:
- close:
- if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
- runs-on: ubuntu-latest
- permissions:
- issues: write
- pull-requests: write
- steps:
- - uses: actions/stale@v7
- with:
- days-before-close: 30
- days-before-stale: -1
- stale-issue-label: needs:response
- stale-pr-label: needs:response
- remove-stale-when-updated: false
- close-issue-message: "This issue has been closed since a request for
- information has not been answered for 30 days. It can be reopened
- when the requested information is provided."
- close-pr-message: "This PR has been closed since a request for
- changes has not been answered for 30 days. It can be reopened when
- the requested changes are provided."
-
- remove-label:
- if: github.event_name == 'issue_comment'
- runs-on: ubuntu-latest
- permissions:
- issues: write
- pull-requests: write
- steps:
- - uses: actions/checkout@v3
- - uses: actions/github-script@v6
- with:
- script: |
- const script = require('./.github/scripts/unstale.js')
- await script({github, context})
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000000..acf0f195b9
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,342 @@
+name: test
+on:
+ push:
+ branches:
+ - 'master'
+ - 'release-[0-9]+.[0-9]+'
+ pull_request:
+ branches:
+ - 'master'
+ - 'release-[0-9]+.[0-9]+'
+ paths-ignore:
+ - 'contrib/**'
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+env:
+ ASAN_OPTIONS: detect_leaks=1:check_initialization_order=1:log_path=${{ github.workspace }}/build/log/asan:intercept_tls_get_addr=0
+ BIN_DIR: ${{ github.workspace }}/bin
+ BUILD_DIR: ${{ github.workspace }}/build
+ INSTALL_PREFIX: ${{ github.workspace }}/nvim-install
+ LOG_DIR: ${{ github.workspace }}/build/log
+ NVIM_LOG_FILE: ${{ github.workspace }}/build/.nvimlog
+ TSAN_OPTIONS: log_path=${{ github.workspace }}/build/log/tsan
+ VALGRIND_LOG: ${{ github.workspace }}/build/log/valgrind-%p.log
+ # TEST_FILE: test/functional/core/startup_spec.lua
+ # TEST_FILTER: foo
+
+jobs:
+ lint:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 10
+ env:
+ CC: clang
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+ - uses: ./.github/actions/cache
+
+ - name: Install stylua
+ run: |
+ URL=$(curl -L https://api.github.com/repos/JohnnyMorganz/StyLua/releases/latest | jq -r '.assets[] | select(.name == "stylua-linux-x86_64.zip") | .browser_download_url')
+ wget --directory-prefix="$BIN_DIR" "$URL"
+ (cd "$BIN_DIR"; unzip stylua*.zip)
+ echo "$BIN_DIR" >> $GITHUB_PATH
+
+ - name: Build third-party deps
+ run: |
+ cmake -S cmake.deps -B .deps -G Ninja
+ cmake --build .deps
+
+ - run: cmake -B build -G Ninja -D CI_LINT=ON
+
+ - if: "!cancelled()"
+ name: Determine if run should be aborted
+ id: abort_job
+ run: echo "status=${{ job.status }}" >> $GITHUB_OUTPUT
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: stylua
+ run: cmake --build build --target lintlua-stylua
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: luacheck
+ run: cmake --build build --target lintlua-luacheck
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: lintsh
+ run: cmake --build build --target lintsh
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: clint.py
+ run: cmake --build build --target lintc-clint
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: clang-tidy
+ run: cmake --build build --target lintc-clang-tidy
+
+ - if: success() || failure() && steps.abort_job.outputs.status == 'success'
+ name: uncrustify
+ run: cmake --build build --target lintc-uncrustify
+
+ clang-analyzer:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 20
+ env:
+ CC: clang
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+ - uses: ./.github/actions/cache
+ - name: Build third-party deps
+ run: |
+ cmake -S cmake.deps --preset ci
+ cmake --build .deps
+ cmake --preset ci
+ - run: cmake --build build --target clang-analyzer
+
+ posix:
+ name: ${{ matrix.build.runner }} ${{ matrix.build.flavor }} ${{ matrix.build.cc }} ${{ matrix.test }}
+ strategy:
+ fail-fast: false
+ matrix:
+ build:
+ [
+ { runner: ubuntu-22.04, flavor: asan, cc: clang, flags: -D ENABLE_ASAN_UBSAN=ON },
+ { runner: ubuntu-22.04, flavor: tsan, cc: clang, flags: -D ENABLE_TSAN=ON },
+ { runner: ubuntu-22.04, cc: gcc },
+ { runner: macos-12, cc: clang, flags: -D CMAKE_FIND_FRAMEWORK=NEVER, deps_flags: -D CMAKE_FIND_FRAMEWORK=NEVER },
+ { runner: ubuntu-22.04, flavor: functionaltest-lua, cc: gcc, deps_flags: -D USE_BUNDLED_LUAJIT=OFF -D USE_BUNDLED_LUA=ON, flags: -D PREFER_LUA=ON },
+ ]
+ test: [unittest, functionaltest, oldtest]
+ exclude:
+ - test: unittest
+ build: { flavor: tsan }
+ - test: unittest
+ build: { flavor: functionaltest-lua }
+ - test: oldtest
+ build: { flavor: tsan }
+ runs-on: ${{ matrix.build.runner }}
+ timeout-minutes: 45
+ env:
+ CC: ${{ matrix.build.cc }}
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh --test
+ - uses: ./.github/actions/cache
+
+ - name: Set up environment
+ run: |
+ ulimit -c unlimited
+ echo "$BIN_DIR" >> $GITHUB_PATH
+
+ - name: Create log dir
+ run: mkdir -p "$LOG_DIR"
+
+ # FIXME(dundargoc): this workaround is needed for macos as the python3
+ # provider tests suddenly started to become extremely flaky, and this
+ # removes the flakiness for some reason.
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+
+ - if: ${{ matrix.test != 'unittest' }}
+ name: Set up interpreter packages
+ run: |
+ # Use default CC to avoid compilation problems when installing Python modules.
+ echo "Install neovim module for Python."
+ CC=cc python3 -m pip -q install --user --upgrade pynvim
+
+ echo "Install neovim RubyGem."
+ gem install --no-document --bindir "$BIN_DIR" --user-install --pre neovim
+
+ echo "Install neovim npm package"
+ npm install -g neovim
+ npm link neovim
+
+ sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log"
+ perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'
+
+ - name: Build third-party deps
+ run: |
+ cmake -S cmake.deps --preset ci ${{ matrix.build.deps_flags }}
+ cmake --build .deps
+
+ - name: Build
+ run: |
+ cmake --preset ci -D CMAKE_INSTALL_PREFIX:PATH=$INSTALL_PREFIX ${{ matrix.build.flags }}
+ cmake --build build
+
+ - name: ${{ matrix.test }}
+ timeout-minutes: 20
+ run: make ${{ matrix.test }}
+
+ - name: Install
+ run: |
+ cmake --install build
+ "$INSTALL_PREFIX/bin/nvim" --version
+ if ! "$INSTALL_PREFIX/bin/nvim" -u NONE -e -c ':help' -c ':qall'; then
+ echo "Running ':help' in the installed nvim failed."
+ echo "Maybe the helptags have not been generated properly."
+ echo 'Failed running :help'
+ exit 1
+ fi
+
+ # Check that all runtime files were installed
+ for file in $(git -C runtime ls-files '*.vim' '*.ps' '*.dict' '*.py' '*.tutor' '*.awk' '*.sh' '*.bat'); do
+ if ! test -e "$INSTALL_PREFIX/share/nvim/runtime/$file"; then
+ printf "It appears that %s is not installed." "$file"
+ exit 1
+ fi
+ done
+
+ # Check that generated syntax file has function names, #5060.
+ genvimsynf=syntax/vim/generated.vim
+ gpat='syn keyword vimFuncName .*eval'
+ if ! grep -q "$gpat" "$INSTALL_PREFIX/share/nvim/runtime/$genvimsynf"; then
+ echo "It appears that $genvimsynf does not contain $gpat."
+ exit 1
+ fi
+
+ - if: '!cancelled()'
+ name: Show logs
+ run: cat $(find "$LOG_DIR" -type f)
+
+ windows:
+ runs-on: windows-2022
+ timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ test: [functional, old]
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ./.github/actions/cache
+ - run: .github/scripts/env.ps1
+
+ - name: Build deps
+ run: |
+ cmake -S cmake.deps -B .deps -G Ninja -D CMAKE_BUILD_TYPE='RelWithDebInfo'
+ cmake --build .deps
+
+ - name: Build
+ run: |
+ cmake --preset ci -D CMAKE_BUILD_TYPE='RelWithDebInfo'
+ cmake --build build
+
+ - name: Install test deps
+ run: |
+ $PSNativeCommandArgumentPassing = 'Legacy'
+
+ & build\bin\nvim.exe "--version"
+
+ # Ensure that the "win32" feature is set.
+ & build\bin\nvim -u NONE --headless -c 'exe !has(\"win32\").\"cq\"'
+
+ python -m pip install pynvim
+ # Sanity check
+ python -c "import pynvim; print(str(pynvim))"
+
+ node --version
+ npm.cmd --version
+
+ npm.cmd install -g neovim
+ Get-Command -CommandType Application neovim-node-host.cmd
+ npm.cmd link neovim
+
+ - if: ${{ matrix.test == 'functional' }}
+ name: functionaltest
+ timeout-minutes: 20
+ run: cmake --build build --target functionaltest
+
+ - if: ${{ matrix.test == 'old' }}
+ uses: msys2/setup-msys2@v2
+ with:
+ update: true
+ pacboy: >-
+ make:p gcc:p diffutils:p
+ release: false
+
+ - if: ${{ matrix.test == 'old' }}
+ name: oldtest
+ shell: msys2 {0}
+ run: |
+ cd test/old/testdir
+ mingw32-make VERBOSE=1
+
+ # This job tests the following things:
+ # - Check if Release, MinSizeRel and RelWithDebInfo compiles correctly.
+ # - Test the above build types with the GCC compiler specifically.
+ # Empirically the difference in warning levels between GCC and other
+ # compilers is particularly big.
+ # - Test if the build works with multi-config generators. We mostly use
+ # single-config generators so it's nice to have a small sanity check for
+ # multi-config.
+ build-types:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 10
+ env:
+ CC: gcc
+ steps:
+ - uses: actions/checkout@v4
+ - run: ./.github/scripts/install_deps.sh
+ - uses: ./.github/actions/cache
+
+ - name: Build third-party deps
+ run: |
+ cmake -S cmake.deps -B .deps -G "Ninja Multi-Config"
+ cmake --build .deps
+
+ - name: Configure
+ run: cmake --preset ci -G "Ninja Multi-Config"
+
+ - name: Release
+ run: cmake --build build --config Release
+
+ - name: RelWithDebInfo
+ run: cmake --build build --config RelWithDebInfo
+
+ - name: MinSizeRel
+ run: cmake --build build --config MinSizeRel
+
+ with-external-deps:
+ runs-on: ubuntu-22.04
+ timeout-minutes: 10
+ env:
+ CC: gcc
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install dependencies
+ run: |
+ sudo add-apt-repository ppa:neovim-ppa/stable
+ ./.github/scripts/install_deps.sh
+ sudo apt-get install -y \
+ libluajit-5.1-dev \
+ libmsgpack-dev \
+ libtermkey-dev \
+ libunibilium-dev \
+ libuv1-dev \
+ lua-filesystem \
+ lua-lpeg \
+ luajit \
+ lua-luv-dev
+ # libtree-sitter-dev \
+ # libvterm-dev
+
+ # Remove comments from packages once we start using these external
+ # dependencies.
+
+ - uses: ./.github/actions/cache
+
+ - name: Build third-party deps
+ run: |
+ cmake -S cmake.deps --preset external_deps
+ cmake --build .deps
+
+ - name: Build
+ run: |
+ cmake --preset ci
+ cmake --build build
diff --git a/.github/workflows/vim-patches.yml b/.github/workflows/vim-patches.yml
index 159eb09e7c..711ddae815 100644
--- a/.github/workflows/vim-patches.yml
+++ b/.github/workflows/vim-patches.yml
@@ -11,15 +11,15 @@ jobs:
contents: write
pull-requests: write
env:
- VIM_SOURCE_DIR: ${{ format('{0}/vim-src', github.workspace) }}
+ VIM_SOURCE_DIR: ${{ github.workspace }}/vim-src
VERSION_BRANCH: marvim/ci-version-update
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
with:
repository: vim/vim
path: ${{ env.VIM_SOURCE_DIR }}
@@ -34,7 +34,7 @@ jobs:
mv nvim.appimage $HOME/.local/bin/nvim
printf '%s\n' "$HOME/.local/bin" >> $GITHUB_PATH
- - name: Setup git config
+ - name: Set up git config
run: |
git config --global user.name 'marvim'
git config --global user.email 'marvim@users.noreply.github.com'
diff --git a/.gitignore b/.gitignore
index 57acde8722..f1661e1f00 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,8 @@
# Tools
+/.direnv/
/venv/
compile_commands.json
-/.luarc.json
+/.envrc
# IDEs
/.vs/
@@ -36,21 +37,22 @@ compile_commands.json
*.rej
# Generated by old (Vim) tests.
-/src/nvim/testdir/del
-/src/nvim/testdir/test*.out
-/src/nvim/testdir/test*.res
-/src/nvim/testdir/test*.log
-/src/nvim/testdir/messages
-/src/nvim/testdir/viminfo
-/src/nvim/testdir/test.ok
-/src/nvim/testdir/*.failed
-/src/nvim/testdir/X*
-/src/nvim/testdir/valgrind.*
-/src/nvim/testdir/.gdbinit
+/test/old/testdir/del
+/test/old/testdir/test*.out
+/test/old/testdir/test*.res
+/test/old/testdir/test*.log
+/test/old/testdir/messages
+/test/old/testdir/starttime
+/test/old/testdir/viminfo
+/test/old/testdir/test.ok
+/test/old/testdir/*.failed
+/test/old/testdir/X*
+/test/old/testdir/valgrind.*
+/test/old/testdir/.gdbinit
/runtime/indent/testdir/*.out
+runtime/indent/testdir/*.fail
-# Generated by src/nvim/testdir/runnvim.sh.
-/src/nvim/testdir/*.tlog
+# Generated by test/old/testdir/runnvim.sh.
+/test/old/testdir/*.tlog
# Generated by unit tests.
/test/includes/post/
diff --git a/.luacheckrc b/.luacheckrc
index 8ef4f5ea66..701f461dc4 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -19,8 +19,10 @@ cache = true
ignore = {
"631", -- max_line_length
"212/_.*", -- unused argument, for vars with "_" prefix
+ "214", -- used variable with unused hint ("_" prefix)
"121", -- setting read-only global variable 'vim'
"122", -- setting read-only field of global variable 'vim'
+ "581", -- negation of a relational operator- operator can be flipped (not for tables)
}
-- Global objects defined by the C code
@@ -41,4 +43,9 @@ globals = {
exclude_files = {
'test/functional/fixtures/lua/syntax_error.lua',
+ 'runtime/lua/vim/treesitter/_meta.lua',
+ 'runtime/lua/vim/_meta/vimfn.lua',
+ 'runtime/lua/vim/_meta/api.lua',
+ 'runtime/lua/vim/re.lua',
+ 'src/nvim/eval.lua',
}
diff --git a/.luarc.json b/.luarc.json
new file mode 100644
index 0000000000..5a014a26ea
--- /dev/null
+++ b/.luarc.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
+ "runtime": {
+ "version": "LuaJIT"
+ },
+ "workspace": {
+ "library": [
+ "runtime/lua",
+ "${3rd}/busted/library",
+ "${3rd}/luv/library"
+ ],
+ "ignoreDir": [
+ "test"
+ ],
+ "checkThirdParty": false
+ },
+ "diagnostics": {
+ "groupFileStatus": {
+ "strict": "Opened",
+ "strong": "Opened"
+ },
+ "groupSeverity": {
+ "strong": "Warning",
+ "strict": "Warning"
+ },
+ "unusedLocalExclude": [ "_*" ],
+ "disable": [
+ "luadoc-miss-see-name"
+ ]
+ }
+}
diff --git a/.styluaignore b/.styluaignore
index c1871de90a..c9303e07ce 100644
--- a/.styluaignore
+++ b/.styluaignore
@@ -1,3 +1,7 @@
+/build
+/runtime/lua/coxpcall.lua
+/runtime/lua/vim/_meta
+/runtime/lua/vim/re.lua
/scripts
/src
/test
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9c0d999ab8..475a1a2c3e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,7 +3,7 @@
# best practices (3.0+): https://gist.github.com/mbinna/c61dbb39bca0e4fb7d1f73b0d66a4fd1
# pitfalls: https://izzys.casa/2019/02/everything-you-never-wanted-to-know-about-cmake/
-# Version should match the tested CMAKE_URL in .github/workflows/ci.yml.
+# Version should match the tested CMAKE_URL in .github/workflows/build.yml.
cmake_minimum_required(VERSION 3.10)
# Can be removed once minimum version is at least 3.15
@@ -16,45 +16,49 @@ project(nvim C)
if(POLICY CMP0075)
cmake_policy(SET CMP0075 NEW)
endif()
+if(POLICY CMP0135)
+ cmake_policy(SET CMP0135 NEW)
+endif()
# Point CMake at any custom modules we may ship
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
include(CheckCCompilerFlag)
include(CheckCSourceCompiles)
+include(CheckLibraryExists)
+include(ExternalProject)
+include(FindPackageHandleStandardArgs)
+include(GNUInstallDirs)
+
+include(Deps)
+include(Find)
include(InstallHelpers)
-include(LuaHelpers) # Find Lua interpreter
include(PreventInTreeBuilds)
include(Util)
-set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
+set_directory_properties(PROPERTIES
+ EP_PREFIX "${DEPS_BUILD_DIR}")
-set_property(GLOBAL PROPERTY USE_FOLDERS ON)
+set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
find_program(CCACHE_PRG ccache)
if(CCACHE_PRG)
set(CMAKE_C_COMPILER_LAUNCHER ${CMAKE_COMMAND} -E env CCACHE_SLOPPINESS=pch_defines,time_macros ${CCACHE_PRG})
endif()
+if(NOT CI_BUILD)
+ set(CMAKE_INSTALL_MESSAGE NEVER)
+endif()
+
+if(${CMAKE_VERSION} VERSION_LESS 3.20)
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+endif()
+
# Prefer our bundled versions of dependencies.
if(DEFINED ENV{DEPS_BUILD_DIR})
- if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
- # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that
- # *contain* system include paths. To avoid this, we prefix what would be
- # "/usr/include" as "/_usr/include".
- # This check is also performed in the cmake.deps/CMakeLists.txt and in the
- # else clause following here.
- # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794
- set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/_usr" CACHE PATH "Path prefix for finding dependencies")
- else()
- set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies")
- endif()
+ set(DEPS_PREFIX "$ENV{DEPS_BUILD_DIR}/usr" CACHE PATH "Path prefix for finding dependencies")
else()
- if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
- set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/_usr" CACHE PATH "Path prefix for finding dependencies")
- else()
- set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies")
- endif()
+ set(DEPS_PREFIX "${CMAKE_CURRENT_SOURCE_DIR}/.deps/usr" CACHE PATH "Path prefix for finding dependencies")
# When running from within CLion or Visual Studio,
# build bundled dependencies automatically.
if(NOT EXISTS ${DEPS_PREFIX}
@@ -65,15 +69,15 @@ else()
file(MAKE_DIRECTORY ${DEPS_BUILD_DIR})
execute_process(
COMMAND ${CMAKE_COMMAND} -G ${CMAKE_GENERATOR}
- -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
- -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
- -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
- -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS}
- -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}
- -DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL}
- -DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO}
- -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}
- -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
+ -D CMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
+ -D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
+ -D CMAKE_C_COMPILER=${CMAKE_C_COMPILER}
+ -D CMAKE_C_FLAGS=${CMAKE_C_FLAGS}
+ -D CMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}
+ -D CMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL}
+ -D CMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO}
+ -D CMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}
+ -D CMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}
${PROJECT_SOURCE_DIR}/cmake.deps
WORKING_DIRECTORY ${DEPS_BUILD_DIR})
execute_process(
@@ -84,9 +88,8 @@ else()
endif()
list(INSERT CMAKE_PREFIX_PATH 0 ${DEPS_PREFIX})
-set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${DEPS_PREFIX}/lib/pkgconfig")
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+if(APPLE)
# If the macOS deployment target is not set manually (via $MACOSX_DEPLOYMENT_TARGET),
# fall back to local system version. Needs to be done both here and in cmake.deps.
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
@@ -98,7 +101,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message(STATUS "Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
-if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+if(WIN32 OR APPLE)
# Ignore case when comparing filenames on Windows and Mac.
set(CASE_INSENSITIVE_FILENAME TRUE)
# Enable fixing case-insensitive filenames for Windows and Mac.
@@ -117,94 +120,47 @@ endif()
message(STATUS "CMAKE_INSTALL_PREFIX=${CMAKE_INSTALL_PREFIX}")
set_default_buildtype()
+get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(NOT isMultiConfig)
+ # Unlike build dependencies in cmake.deps, we assume we want dev dependencies
+ # such as Uncrustify to always be built with Release.
+ list(APPEND DEPS_CMAKE_ARGS -D CMAKE_BUILD_TYPE=Release)
+endif()
# If not in a git repo (e.g., a tarball) these tokens define the complete
# version string, else they are combined with the result of `git describe`.
set(NVIM_VERSION_MAJOR 0)
-set(NVIM_VERSION_MINOR 9)
+set(NVIM_VERSION_MINOR 10)
set(NVIM_VERSION_PATCH 0)
set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers
# API level
-set(NVIM_API_LEVEL 11) # Bump this after any API change.
+set(NVIM_API_LEVEL 12) # Bump this after any API change.
set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change.
set(NVIM_API_PRERELEASE true)
-set(NVIM_VERSION_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
-# NVIM_VERSION_CFLAGS set further below.
-
-# Log level (MIN_LOG_LEVEL in log.h)
-if("${MIN_LOG_LEVEL}" MATCHES "^$")
- # Minimize logging for release-type builds.
- if(CMAKE_BUILD_TYPE STREQUAL "Release"
- OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo"
- OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
- message(STATUS "MIN_LOG_LEVEL not specified, default is 3 (ERROR) for release builds")
- set(MIN_LOG_LEVEL 3)
- else()
- message(STATUS "MIN_LOG_LEVEL not specified, default is 1 (INFO)")
- set(MIN_LOG_LEVEL 1)
- endif()
-else()
- if(NOT MIN_LOG_LEVEL MATCHES "^[0-3]$")
- message(FATAL_ERROR "invalid MIN_LOG_LEVEL: " ${MIN_LOG_LEVEL})
- endif()
- message(STATUS "MIN_LOG_LEVEL=${MIN_LOG_LEVEL}")
-endif()
-
-# Default to -O2 on release builds.
-if(CMAKE_C_FLAGS_RELEASE MATCHES "-O3")
- message(STATUS "Replacing -O3 in CMAKE_C_FLAGS_RELEASE with -O2")
- string(REPLACE "-O3" "-O2" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
-endif()
-
-if(CMAKE_C_COMPILER_ID MATCHES "GNU")
- check_c_compiler_flag(-Og HAS_OG_FLAG)
-else()
- set(HAS_OG_FLAG 0)
-endif()
-
-#
# Build-type: RelWithDebInfo
-#
-if(HAS_OG_FLAG)
- set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Og -g")
+# /Og means something different in MSVC
+if(CMAKE_C_COMPILER_ID MATCHES "GNU" OR CMAKE_C_COMPILER_ID MATCHES "Clang")
+ set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Og -g")
endif()
# We _want_ assertions in RelWithDebInfo build-type.
if(CMAKE_C_FLAGS_RELWITHDEBINFO MATCHES DNDEBUG)
string(REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+ string(REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}")
+ string(REPLACE " " " " CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO}") # Remove duplicate whitespace
endif()
-# gcc 4.0+ sets _FORTIFY_SOURCE=2 automatically. This currently
-# does not work with Neovim due to some uses of dynamically-sized structures.
-# https://github.com/neovim/neovim/issues/223
-
-# Include the build type's default flags in the check for _FORTIFY_SOURCE,
-# otherwise we may incorrectly identify the level as acceptable and find out
-# later that it was not when optimizations were enabled. CFLAGS is applied
-# even though you don't see it in CMAKE_REQUIRED_FLAGS.
-set(INIT_FLAGS_NAME CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE})
-string(TOUPPER ${INIT_FLAGS_NAME} INIT_FLAGS_NAME)
-if(${INIT_FLAGS_NAME})
- set(CMAKE_REQUIRED_FLAGS "${${INIT_FLAGS_NAME}}")
-endif()
-
-option(LOG_LIST_ACTIONS "Add list actions logging" OFF)
-
-option(CLANG_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF)
-option(CLANG_MSAN "Enable Clang memory sanitizer for nvim binary." OFF)
-option(CLANG_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
+option(ENABLE_ASAN_UBSAN "Enable Clang address & undefined behavior sanitizer for nvim binary." OFF)
+option(ENABLE_MSAN "Enable Clang memory sanitizer for nvim binary." OFF)
+option(ENABLE_TSAN "Enable Clang thread sanitizer for nvim binary." OFF)
-if((CLANG_ASAN_UBSAN AND CLANG_MSAN)
- OR (CLANG_ASAN_UBSAN AND CLANG_TSAN)
- OR (CLANG_MSAN AND CLANG_TSAN))
+if((ENABLE_ASAN_UBSAN AND ENABLE_MSAN)
+ OR (ENABLE_ASAN_UBSAN AND ENABLE_TSAN)
+ OR (ENABLE_MSAN AND ENABLE_TSAN))
message(FATAL_ERROR "Sanitizers cannot be enabled simultaneously")
endif()
-if((CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) AND NOT CMAKE_C_COMPILER_ID MATCHES "Clang")
- message(FATAL_ERROR "Sanitizers are only supported for Clang")
-endif()
-
# Place targets in bin/ or lib/ for all build configurations
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
@@ -216,30 +172,13 @@ foreach(CFGNAME ${CMAKE_CONFIGURATION_TYPES})
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CFGNAME} ${CMAKE_BINARY_DIR}/lib)
endforeach()
-set(LUA_DEPENDENCIES lpeg mpack bit)
-if(NOT LUA_PRG)
- foreach(CURRENT_LUA_PRG luajit lua5.1 lua5.2 lua)
- unset(_CHECK_LUA_PRG CACHE)
- unset(LUA_PRG_WORKS)
- find_program(_CHECK_LUA_PRG ${CURRENT_LUA_PRG})
-
- if(_CHECK_LUA_PRG)
- check_lua_deps(${_CHECK_LUA_PRG} "${LUA_DEPENDENCIES}" LUA_PRG_WORKS)
- if(LUA_PRG_WORKS)
- set(LUA_PRG "${_CHECK_LUA_PRG}" CACHE FILEPATH "Path to a program.")
- break()
- endif()
- endif()
- endforeach()
- unset(_CHECK_LUA_PRG CACHE)
-else()
- check_lua_deps(${LUA_PRG} "${LUA_DEPENDENCIES}" LUA_PRG_WORKS)
+if(NOT PREFER_LUA)
+ find_program(LUA_PRG NAMES luajit)
endif()
-
-if(NOT LUA_PRG_WORKS)
+find_program(LUA_PRG NAMES lua5.1 lua5.2 lua)
+if(NOT LUA_PRG)
message(FATAL_ERROR "Failed to find a Lua 5.1-compatible interpreter")
endif()
-
message(STATUS "Using Lua interpreter: ${LUA_PRG}")
# Some of the code generation still relies on stable table ordering in order to
@@ -250,11 +189,9 @@ message(STATUS "Using Lua interpreter: ${LUA_PRG}")
if(NOT LUA_GEN_PRG)
set(LUA_GEN_PRG "${LUA_PRG}" CACHE FILEPATH "Path to the lua used for code generation.")
endif()
-
message(STATUS "Using Lua interpreter for code generation: ${LUA_GEN_PRG}")
option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" ON)
-
if(COMPILE_LUA AND NOT WIN32)
if(PREFER_LUA)
foreach(CURRENT_LUAC_PRG luac5.1 luac)
@@ -271,34 +208,38 @@ if(COMPILE_LUA AND NOT WIN32)
endif()
endif()
endif()
-
if(LUAC_PRG)
message(STATUS "Using Lua compiler: ${LUAC_PRG}")
endif()
-#
# Lint
-#
-find_program(LUACHECK_PRG luacheck)
-find_program(STYLUA_PRG stylua)
-find_program(UNCRUSTIFY_PRG uncrustify)
-find_program(SHELLCHECK_PRG shellcheck)
+option(CI_LINT "Abort if lint programs not found" OFF)
+if(CI_LINT)
+ set(LINT_REQUIRED "REQUIRED")
+endif()
+find_program(SHELLCHECK_PRG shellcheck ${LINT_REQUIRED})
+find_program(STYLUA_PRG stylua ${LINT_REQUIRED})
add_glob_target(
- REQUIRED
TARGET lintlua-luacheck
- COMMAND ${LUACHECK_PRG}
- FLAGS -q
+ COMMAND $<TARGET_FILE:nvim>
+ FLAGS -ll ${PROJECT_SOURCE_DIR}/test/lua_runner.lua ${CMAKE_BINARY_DIR}/usr luacheck -q
GLOB_DIRS runtime/ scripts/ src/ test/
GLOB_PAT *.lua
+ EXCLUDE
+ runtime/lua/vim/_meta/.*
+ runtime/lua/coxpcall.lua
TOUCH_STRATEGY SINGLE)
+add_dependencies(lintlua-luacheck lua-dev-deps)
add_glob_target(
TARGET lintlua-stylua
COMMAND ${STYLUA_PRG}
- FLAGS --color=always --check
+ FLAGS --color=always --check --respect-ignores
GLOB_DIRS runtime/
GLOB_PAT *.lua
+ EXCLUDE
+ /runtime/lua/vim/_meta
TOUCH_STRATEGY SINGLE)
add_custom_target(lintlua)
@@ -308,26 +249,18 @@ add_glob_target(
TARGET lintsh
COMMAND ${SHELLCHECK_PRG}
FLAGS -x -a
- GLOB_DIRS scripts ci
+ GLOB_DIRS scripts
GLOB_PAT *.sh
- EXCLUDE
- scripts/pvscheck.sh
- ci/common
- ci/snap
TOUCH_STRATEGY SINGLE)
add_custom_target(lintcommit
- COMMAND ${PROJECT_BINARY_DIR}/bin/nvim -u NONE -es -c [[lua require('scripts.lintcommit').main({trace=false})]]
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
- VERBATIM)
+ COMMAND $<TARGET_FILE:nvim> -u NONE -l ${PROJECT_SOURCE_DIR}/scripts/lintcommit.lua main)
add_dependencies(lintcommit nvim)
add_custom_target(lint)
-add_dependencies(lint check-single-includes lintc lintlua lintsh lintcommit)
+add_dependencies(lint lintc lintlua lintsh lintcommit)
-#
# Format
-#
add_custom_target(formatlua
COMMAND ${CMAKE_COMMAND}
-D FORMAT_PRG=${STYLUA_PRG}
@@ -342,113 +275,10 @@ install_helper(
FILES ${CMAKE_SOURCE_DIR}/src/man/nvim.1
DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
-#
-# Go down the tree.
-#
-
add_subdirectory(src/nvim)
-get_directory_property(NVIM_VERSION_CFLAGS DIRECTORY src/nvim DEFINITION NVIM_VERSION_CFLAGS)
add_subdirectory(cmake.config)
-add_subdirectory(test/functional/fixtures) # compile test programs
add_subdirectory(runtime)
-get_directory_property(GENERATED_HELP_TAGS DIRECTORY runtime DEFINITION GENERATED_HELP_TAGS)
-if(WIN32)
- install_helper(
- FILES ${DEPS_PREFIX}/share/nvim-qt/runtime/plugin/nvim_gui_shim.vim
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim-qt/runtime/plugin)
-endif()
-
-# Setup busted.
-find_program(BUSTED_PRG NAMES busted busted.bat)
-find_program(BUSTED_LUA_PRG busted-lua)
-if(NOT BUSTED_OUTPUT_TYPE)
- set(BUSTED_OUTPUT_TYPE "nvim")
-endif()
-
-# Setup some test-related bits. We do this after going down the tree because we
-# need some of the targets.
-if(BUSTED_PRG)
- get_target_property(TEST_INCLUDE_DIRS main_lib INTERFACE_INCLUDE_DIRECTORIES)
-
- set(UNITTEST_PREREQS nvim)
- set(FUNCTIONALTEST_PREREQS nvim printenv-test printargs-test shell-test pwsh-test streams-test tty-test ${GENERATED_HELP_TAGS})
- set(BENCHMARK_PREREQS nvim tty-test)
-
- check_lua_module(${LUA_PRG} "ffi" LUA_HAS_FFI)
- if(LUA_HAS_FFI)
- add_custom_target(unittest
- COMMAND ${CMAKE_COMMAND}
- -DBUSTED_PRG=${BUSTED_PRG}
- -DLUA_PRG=${LUA_PRG}
- -DNVIM_PRG=$<TARGET_FILE:nvim>
- -DWORKING_DIR=${CMAKE_CURRENT_SOURCE_DIR}
- -DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
- -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
- -DBUILD_DIR=${CMAKE_BINARY_DIR}
- -DTEST_TYPE=unit
- -DCIRRUS_CI=$ENV{CIRRUS_CI}
- -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
- DEPENDS ${UNITTEST_PREREQS}
- USES_TERMINAL)
- set_target_properties(unittest PROPERTIES FOLDER test)
- else()
- message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}")
- endif()
-
- configure_file(
- ${CMAKE_SOURCE_DIR}/test/cmakeconfig/paths.lua.in
- ${CMAKE_BINARY_DIR}/test/cmakeconfig/paths.lua)
-
- add_custom_target(functionaltest
- COMMAND ${CMAKE_COMMAND}
- -DBUSTED_PRG=${BUSTED_PRG}
- -DLUA_PRG=${LUA_PRG}
- -DNVIM_PRG=$<TARGET_FILE:nvim>
- -DWORKING_DIR=${CMAKE_CURRENT_SOURCE_DIR}
- -DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
- -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
- -DBUILD_DIR=${CMAKE_BINARY_DIR}
- -DTEST_TYPE=functional
- -DCIRRUS_CI=$ENV{CIRRUS_CI}
- -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
- DEPENDS ${FUNCTIONALTEST_PREREQS}
- USES_TERMINAL)
- set_target_properties(functionaltest PROPERTIES FOLDER test)
-
- add_custom_target(benchmark
- COMMAND ${CMAKE_COMMAND}
- -DBUSTED_PRG=${BUSTED_PRG}
- -DLUA_PRG=${LUA_PRG}
- -DNVIM_PRG=$<TARGET_FILE:nvim>
- -DWORKING_DIR=${CMAKE_CURRENT_SOURCE_DIR}
- -DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
- -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
- -DBUILD_DIR=${CMAKE_BINARY_DIR}
- -DTEST_TYPE=benchmark
- -DCIRRUS_CI=$ENV{CIRRUS_CI}
- -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
- DEPENDS ${BENCHMARK_PREREQS}
- USES_TERMINAL)
- set_target_properties(benchmark PROPERTIES FOLDER test)
-endif()
-
-if(BUSTED_LUA_PRG)
- add_custom_target(functionaltest-lua
- COMMAND ${CMAKE_COMMAND}
- -DBUSTED_PRG=${BUSTED_LUA_PRG}
- -DLUA_PRG=${LUA_PRG}
- -DNVIM_PRG=$<TARGET_FILE:nvim>
- -DWORKING_DIR=${CMAKE_CURRENT_SOURCE_DIR}
- -DBUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
- -DTEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/test
- -DBUILD_DIR=${CMAKE_BINARY_DIR}
- -DTEST_TYPE=functional
- -DCIRRUS_CI=$ENV{CIRRUS_CI}
- -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
- DEPENDS ${FUNCTIONALTEST_PREREQS}
- USES_TERMINAL)
- set_target_properties(functionaltest-lua PROPERTIES FOLDER test)
-endif()
+add_subdirectory(test)
add_custom_target(uninstall
COMMAND ${CMAKE_COMMAND} -P ${PROJECT_SOURCE_DIR}/cmake/UninstallHelper.cmake)
@@ -456,3 +286,28 @@ add_custom_target(uninstall
if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR})
add_subdirectory(cmake.packaging)
endif()
+
+ExternalProject_Add(uncrustify
+ URL https://github.com/uncrustify/uncrustify/archive/uncrustify-0.78.1.tar.gz
+ URL_HASH SHA256=ecaf4c0adca14c36dfffa30bc28e69865115ecd602c90eb16a8cddccb41caad2
+ DOWNLOAD_NO_PROGRESS TRUE
+ DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/uncrustify
+ CMAKE_ARGS ${DEPS_CMAKE_ARGS}
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ EXCLUDE_FROM_ALL TRUE)
+
+option(USE_BUNDLED_BUSTED "Use bundled busted" ON)
+if(USE_BUNDLED_BUSTED)
+ ExternalProject_Add(lua-dev-deps
+ URL https://github.com/neovim/deps/raw/5a1f71cceb24990a0b15fd9a472a5f549f019248/opt/lua-dev-deps.tar.gz
+ URL_HASH SHA256=27db2495f5eddc7fc191701ec9b291486853530c6125609d3197d03481e8d5a2
+ DOWNLOAD_NO_PROGRESS TRUE
+ DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/lua-dev-deps
+ SOURCE_DIR ${DEPS_SHARE_DIR}
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ""
+ EXCLUDE_FROM_ALL TRUE)
+else()
+ add_custom_target(lua-dev-deps)
+endif()
diff --git a/CMakePresets.json b/CMakePresets.json
index 62abb2697f..1d214d7801 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -1,5 +1,5 @@
{
- "version": 6,
+ "version": 3,
"configurePresets": [
{
"name": "base",
@@ -35,79 +35,20 @@
"inherits": ["base"]
},
{
- "name": "windows-default",
- "displayName": "Windows x64 RelWithDebInfo",
- "description": "Sets Ninja generator, enables optimizations with debug information for x64",
- "generator": "Ninja",
- "cacheVariables": {
- "CMAKE_BUILD_TYPE": "RelWithDebInfo"
- },
- "architecture": {
- "value": "x64",
- "strategy": "external"
- },
- "vendor": {
- "microsoft.com/VisualStudioSettings/CMake/1.0": {
- "hostOS": ["Windows"]
- }
- },
- "condition": {
- "type": "equals",
- "lhs": "${hostSystemName}",
- "rhs": "Windows"
- },
- "inherits": ["base"]
- },
- {
"name": "iwyu",
"displayName": "IWYU",
- "description": "Run include-what-you-use with the compiler",
+ "description": "Run include-what-you-use",
"cacheVariables": {
"ENABLE_IWYU": "ON"
},
"inherits": ["base"]
- }
- ],
- "buildPresets": [
- {
- "name": "default",
- "configurePreset": "default"
- },
- {
- "name": "debug",
- "configurePreset": "debug"
- },
- {
- "name": "release",
- "configurePreset": "release"
- },
- {
- "name": "windows-default",
- "configurePreset": "windows-default",
- "condition": {
- "type": "equals",
- "lhs": "${hostSystemName}",
- "rhs": "Windows"
- }
},
{
- "name": "iwyu",
- "configurePreset": "iwyu"
- }
- ],
- "workflowPresets": [
- {
- "name": "iwyu",
- "steps": [
- {
- "type": "configure",
- "name": "iwyu"
- },
- {
- "type": "build",
- "name": "iwyu"
- }
- ]
+ "name": "ci",
+ "cacheVariables": {
+ "CI_BUILD": "ON"
+ },
+ "inherits": ["base"]
}
]
}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f1c7ca1cb3..4f9c0d720d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,9 +8,7 @@ If you want to help but don't know where to start, here are some
low-risk/isolated tasks:
- Try a [complexity:low] issue.
-- Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or
- [Coverity](#coverity).
-- [Improve documentation](#documenting)
+- Fix bugs found by [Clang](#clang-scan-build) or [Coverity](#coverity).
- [Merge a Vim patch] (requires strong familiarity with Vim)
- NOTE: read the above link before sending improvements to "runtime files" (anything in `runtime/`).
- Vimscript and documentation files are (mostly) maintained by [Vim](https://github.com/vim/vim), not Nvim.
@@ -33,18 +31,18 @@ Reporting problems
Developer guidelines
--------------------
-- Read [:help dev](https://neovim.io/doc/user/develop.html#dev) if you are working on Nvim core.
+- Read [:help dev](https://neovim.io/doc/user/develop.html#dev) and [:help dev-doc][dev-doc-guide] if you are working on Nvim core.
- Read [:help dev-ui](https://neovim.io/doc/user/develop.html#dev-ui) if you are developing a UI.
- Read [:help dev-api-client](https://neovim.io/doc/user/develop.html#dev-api-client) if you are developing an API client.
- Install `ninja` for faster builds of Nvim.
- ```
+ ```bash
sudo apt-get install ninja-build
make distclean
make # Nvim build system uses ninja automatically, if available.
```
- Install `ccache` for faster rebuilds of Nvim. Nvim will use it automatically
if it's found. To disable caching use:
- ```
+ ```bash
CCACHE_DISABLE=true make
```
@@ -55,12 +53,8 @@ Pull requests (PRs)
- Your PR must include [test coverage][run-tests].
- Avoid cosmetic changes to unrelated files in the same commit.
- Use a [feature branch][git-feature-branch] instead of the master branch.
-- Use a _rebase workflow_ for small PRs.
+- Use a _rebase workflow_ for all PRs.
- After addressing review comments, it's fine to force-push.
-- Use a _merge workflow_ (as opposed to "rebase") for big, high-risk PRs.
- - Merge `master` into your PR when there are conflicts or when master
- introduces breaking changes.
- - Do not edit commits that come before the merge commit.
### Merging to master
@@ -98,7 +92,7 @@ the VCS/git logs more valuable. The general structure of a commit message is:
```
- Prefix the commit subject with one of these [_types_](https://github.com/commitizen/conventional-commit-types/blob/master/index.json):
- - `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `test`, `vim-patch`, `dist`
+ - `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `test`, `vim-patch`
- You can **ignore this for "fixup" commits** or any commits you expect to be squashed.
- Append optional scope to _type_ such as `(lsp)`, `(treesitter)`, `(float)`, …
- _Description_ shouldn't start with a capital letter or end in a period.
@@ -115,6 +109,11 @@ the VCS/git logs more valuable. The general structure of a commit message is:
BREAKING CHANGE: refactor to use Python 3 features since Python 2 is no longer supported.
```
+### News
+
+High level release notes are maintained in [news.txt](runtime/doc/news.txt). A PR is not required to add a news item
+but is generally recommended.
+
### Automated builds (CI)
Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Actions].
@@ -124,13 +123,13 @@ Each pull request must pass the automated builds on [Cirrus CI] and [GitHub Acti
- If any tests fail, the build will fail. See [test/README.md#running-tests][run-tests] to run tests locally.
- CI runs [ASan] and other analyzers.
- To run valgrind locally: `VALGRIND=1 make test`
- - To run Clang ASan/UBSan locally: `CC=clang make CMAKE_FLAGS="-DCLANG_ASAN_UBSAN=ON"`
-- The [lint](#lint) build checks modified lines _and their immediate
- neighbors_, to encourage incrementally updating the legacy style to meet our
- [style](#style). (See [#3174][3174] for background.)
+ - To run ASan/UBSan locally: `CC=clang make CMAKE_FLAGS="-DENABLE_ASAN_UBSAN=ON"`.
+ Note that MSVC requires Release or RelWithDebInfo build type to work properly.
+- The [lint](#lint) build checks that the code is formatted correctly and
+ passes various linter checks.
- CI for FreeBSD runs on [Cirrus CI].
- To see CI results faster in your PR, you can temporarily set `TEST_FILE` in
- [ci.yml](https://github.com/neovim/neovim/blob/e35b9020b16985eee26e942f9a3f6b045bc3809b/.github/workflows/ci.yml#L205).
+ [test.yml](https://github.com/neovim/neovim/blob/e35b9020b16985eee26e942f9a3f6b045bc3809b/.github/workflows/test.yml#L29).
### Clang scan-build
@@ -138,30 +137,15 @@ View the [Clang report] to see potential bugs found by the Clang
[scan-build](https://clang-analyzer.llvm.org/scan-build.html) analyzer.
- Search the Neovim commit history to find examples:
- ```
+ ```bash
git log --oneline --no-merges --grep clang
```
- To verify a fix locally, run `scan-build` like this:
- ```
+ ```bash
rm -rf build/
scan-build --use-analyzer=/usr/bin/clang make
```
-### PVS-Studio
-
-View the [PVS report](https://neovim.io/doc/reports/pvs/PVS-studio.html.d/) to
-see potential bugs found by [PVS Studio](https://www.viva64.com/en/pvs-studio/).
-
-- Use this format for commit messages (where `{id}` is the PVS warning-id)):
- ```
- fix(PVS/V{id}): {description}
- ```
-- Search the Neovim commit history to find examples:
- ```
- git log --oneline --no-merges --grep PVS
- ```
-- Try `./scripts/pvscheck.sh` to run PVS locally.
-
### Coverity
[Coverity](https://scan.coverity.com/projects/neovim-neovim) runs against the
@@ -173,21 +157,21 @@ master build. To view the defects, just request access; you will be approved.
fix(coverity/{id}): {description}
```
- Search the Neovim commit history to find examples:
- ```
+ ```bash
git log --oneline --no-merges --grep coverity
```
-### Clang sanitizers (ASAN and UBSAN)
+### Sanitizers (ASAN and UBSAN)
ASAN/UBSAN can be used to detect memory errors and other common forms of undefined behavior at runtime in debug builds.
- To build Neovim with sanitizers enabled, use
```
- rm -rf build && CMAKE_EXTRA_FLAGS="-DCMAKE_C_COMPILER=clang -DCLANG_ASAN_UBSAN=1" make
+ rm -rf build && CMAKE_EXTRA_FLAGS="-DCMAKE_C_COMPILER=clang -DENABLE_ASAN_UBSAN=1" make
```
- When running Neovim, use
```
- UBSAN_OPTIONS=print_stacktrace=1 ASAN_OPTIONS=log_path=/tmp/nvim_asan nvim args...
+ ASAN_OPTIONS=log_path=/tmp/nvim_asan nvim args...
```
- If Neovim exits unexpectedly, check `/tmp/nvim_asan.{PID}` (or your preferred `log_path`) for log files with error messages.
@@ -199,33 +183,27 @@ Coding
You can run the linter locally by:
- make lint
-
-The lint step downloads the [master error list] and excludes them, so only lint
-errors related to the local changes are reported.
-
-You can lint a single file (but this will _not_ exclude legacy errors):
-
- ./src/clint.py src/nvim/ops.c
+```bash
+make lint
+```
### Style
- You can format files by using:
- ```
- make format
+ ```bash
+ make format # or formatc, formatlua
```
This will format changed Lua and C files with all appropriate flags set.
- Style rules are (mostly) defined by `src/uncrustify.cfg` which tries to match
the [style-guide]. To use the Nvim `gq` command with `uncrustify`:
- ```
+ ```vim
if !empty(findfile('src/uncrustify.cfg', ';'))
setlocal formatprg=uncrustify\ -q\ -l\ C\ -c\ src/uncrustify.cfg\ --no-backup
endif
```
- The required version of `uncrustify` is specified in `uncrustify.cfg`.
- There is also `.clang-format` which has drifted from the [style-guide], but
is available for reference. To use the Nvim `gq` command with `clang-format`:
- ```
+ ```vim
if !empty(findfile('.clang-format', ';'))
setlocal formatprg=clang-format\ -style=file
endif
@@ -233,18 +211,14 @@ You can lint a single file (but this will _not_ exclude legacy errors):
### Navigate
-- Set `blame.ignoreRevsFile` to ignore [noise commits](https://github.com/neovim/neovim/commit/2d240024acbd68c2d3f82bc72cb12b1a4928c6bf) in git blame:
- ```
+- Set `blame.ignoreRevsFile` to ignore [noisy commits](https://github.com/neovim/neovim/commit/2d240024acbd68c2d3f82bc72cb12b1a4928c6bf) in git blame:
+ ```bash
git config blame.ignoreRevsFile .git-blame-ignore-revs
```
- Recommendation is to use **[clangd]**.
Can use the maintained config in [nvim-lspconfig/clangd].
- Explore the source code [on the web](https://sourcegraph.com/github.com/neovim/neovim).
-- If using [lua-language-server], symlink `contrib/luarc.json` into the
- project root:
-
- $ ln -s contrib/luarc.json .luarc.json
### Includes
@@ -252,62 +226,92 @@ For managing includes in C files, use [include-what-you-use].
- [Install include-what-you-use][include-what-you-use-install]
- To see which includes needs fixing use the cmake preset `iwyu`:
- ```
- cmake --workflow --preset iwyu
+ ```bash
+ cmake --preset iwyu
+ cmake --build build
```
- There's also a make target that automatically fixes the suggestions from
IWYU:
- ```
+ ```bash
make iwyu
```
See [#549][549] for more details.
-Documenting
------------
+### Lua runtime files
-Many parts of the `:help` documentation are autogenerated from C or Lua docstrings using the `./scripts/gen_vimdoc.py` script.
-You can filter the regeneration based on the target (api, lua, or lsp), or the file you changed, that need a doc refresh using `./scripts/gen_vimdoc.py -t <target>`.
+Most of the Lua core [`runtime/`](./runtime) modules are precompiled to
+bytecode, so changes to those files won't get used unless you rebuild Nvim or
+by passing `--luamod-dev` and `$VIMRUNTIME`. For example, try adding a function
+to `runtime/lua/vim/_editor.lua` then:
-## Lua docstrings
+```bash
+VIMRUNTIME=./runtime ./build/bin/nvim --luamod-dev
+```
-Lua documentation uses a subset of [EmmyLua] annotations. A rough outline of a function documentation is
+Documentation
+-------------
-```lua
---- {Brief}
----
---- {Long explanation}
----
----@param arg1 type {description}
----@param arg2 type {description}
-{...}
----
----@return type {description}
-```
+Read [:help dev-doc][dev-doc-guide] to understand the expected documentation style and conventions.
-If possible, always add type information (`table`, `string`, `number`, ...). Multiple valid types are separated by a bar (`string|table`). Indicate optional parameters via `type|nil`.
+### Generating :help
-If a function in your Lua module should not be documented (e.g. internal function or local function), you should set the doc comment to:
+Many `:help` docs are autogenerated from (C or Lua) docstrings. To generate the documentation run:
-```
----@private
+```bash
+make doc
```
-Mark functions that are deprecated as
-```
----@deprecated
-```
+If you need to modify or debug the documentation flow, these are the main files:
+- `./scripts/gen_vimdoc.py`:
+ Main doc generator. Drives doxygen to generate xml files, and scrapes those
+ xml files to render vimdoc files.
+- `./scripts/lua2dox.lua`:
+ Used by `gen_vimdoc.py` to transform Lua files into a format compatible with doxygen.
+- `./scripts/gen_eval_files.lua`:
+ Generates documentation and Lua type files from metadata files:
+ ```
+ runtime/lua/vim/* => runtime/doc/lua.txt
+ runtime/lua/vim/* => runtime/doc/lua.txt
+ runtime/lua/vim/lsp/ => runtime/doc/lsp.txt
+ src/nvim/api/* => runtime/doc/api.txt
+ src/nvim/eval.lua => runtime/doc/builtin.txt
+ src/nvim/options.lua => runtime/doc/options.txt
+ ```
+
+### Lua docstrings
+
+Use [LuaLS] annotations in Lua docstrings to annotate parameter types, return
+types, etc. See [:help dev-doc-lua][dev-doc-lua].
+
+- The template for function documentation is:
+ ```lua
+ --- {Brief}
+ ---
+ --- {Long explanation}
+ ---
+ --- @param arg1 type {description}
+ --- @param arg2 type {description}
+ --- ...
+ ---
+ --- @return type {description}
+ ```
+- If possible, add type information (`table`, `string`, `number`, ...). Multiple valid types are separated by a bar (`string|table`). Indicate optional parameters via `type|nil`.
+- If a function in your Lua module should _not_ be documented, add `@nodoc`.
+- If the function is internal or otherwise non-public add `@private`.
+ - Private functions usually should be underscore-prefixed (named "_foo", not "foo").
+- Mark deprecated functions with `@deprecated`.
Reviewing
---------
-To help review pull requests, start with [this checklist][review-checklist].
-
Reviewing can be done on GitHub, but you may find it easier to do locally.
Using [GitHub CLI][gh], you can create a new branch with the contents of a pull
request, e.g. [#1820][1820]:
- gh pr checkout https://github.com/neovim/neovim/pull/1820
+```bash
+gh pr checkout https://github.com/neovim/neovim/pull/1820
+```
Use [`git log -p master..FETCH_HEAD`][git-history-filtering] to list all
commits in the feature branch which aren't in the `master` branch; `-p`
@@ -325,25 +329,21 @@ as context, use the `-W` argument as well.
[Merge a Vim patch]: https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-Vim
[complexity:low]: https://github.com/neovim/neovim/issues?q=is%3Aopen+is%3Aissue+label%3Acomplexity%3Alow
[conventional_commits]: https://www.conventionalcommits.org
-[EmmyLua]: https://github.com/sumneko/lua-language-server/wiki/Annotations
+[dev-doc-guide]: https://neovim.io/doc/user/develop.html#dev-doc
+[dev-doc-lua]: https://neovim.io/doc/user/develop.html#dev-lua-doc
+[LuaLS]: https://github.com/LuaLS/lua-language-server/wiki/Annotations
[gcc-warnings]: https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
[gh]: https://cli.github.com/
[git-bisect]: http://git-scm.com/book/en/v2/Git-Tools-Debugging-with-Git
[git-feature-branch]: https://www.atlassian.com/git/tutorials/comparing-workflows
[git-history-filtering]: https://www.atlassian.com/git/tutorials/git-log/filtering-the-commit-history
-[git-history-rewriting]: http://git-scm.com/book/en/v2/Git-Tools-Rewriting-History
-[git-rebasing]: http://git-scm.com/book/en/v2/Git-Branching-Rebasing
[github-issues]: https://github.com/neovim/neovim/issues
[include-what-you-use-install]: https://github.com/include-what-you-use/include-what-you-use#how-to-install
[include-what-you-use]: https://github.com/include-what-you-use/include-what-you-use#using-with-cmake
[lua-language-server]: https://github.com/sumneko/lua-language-server/
-[master error list]: https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint/errors.json
[nvim-lspconfig/clangd]: https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md#clangd
[pr-draft]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request
[pr-ready]: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request
-[review-checklist]: https://github.com/neovim/neovim/wiki/Code-review-checklist
[run-tests]: https://github.com/neovim/neovim/blob/master/test/README.md#running-tests
[style-guide]: https://neovim.io/doc/user/dev_style.html#dev-style
-[uncrustify]: http://uncrustify.sourceforge.net/
-[wiki-contribute-help]: https://github.com/neovim/neovim/wiki/contribute-%3Ahelp
[wiki-faq]: https://github.com/neovim/neovim/wiki/FAQ
diff --git a/LICENSE.txt b/LICENSE.txt
index 45a4a34c56..dfba86698f 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -233,8 +233,8 @@ II) It is allowed to distribute a modified (or extended) version of Vim,
maintainer will do with your changes and under what license they
will be distributed is negotiable. If there has been no negotiation
then this license, or a later version, also applies to your changes.
- The current maintainer is Bram Moolenaar <Bram@vim.org>. If this
- changes it will be announced in appropriate places (most likely
+ The current maintainers are listed here: https://github.com/orgs/vim/people.
+ If this changes it will be announced in appropriate places (most likely
vim.sf.net, www.vim.org and/or comp.editors). When it is completely
impossible to contact the maintainer, the obligation to send him
your changes ceases. Once the maintainer has confirmed that he has
diff --git a/MAINTAIN.md b/MAINTAIN.md
index 95a3916535..ff5f3fe5a1 100644
--- a/MAINTAIN.md
+++ b/MAINTAIN.md
@@ -25,7 +25,7 @@ The forecasting problem might be solved with an explicit priority system (like
Bram's todo.txt). Meanwhile the Neovim priority system is defined by:
* PRs nearing completion.
-* Issue labels. E.g. the `+plan` label increases the ticket's priority merely
+* Issue labels. E.g. the `has:plan` label increases the ticket's priority merely
for having a plan written down: it is _closer to completion_ than tickets
without a plan.
* Comment activity or new information.
@@ -60,25 +60,75 @@ has a major bug:
Neovim automation includes a [backport bot](https://github.com/zeebe-io/backport-action).
Trigger the action by labeling a PR with `backport release-X.Y`. See `.github/workflows/backport.yml`.
+Deprecating and removing features
+---------------------------------
+
+Neovim inherits many features and design decisions from Vim, not all of which
+align with the goals of this project. It is sometimes desired or necessary to
+remove existing features, or refactor parts of the code that would change
+user's workflow. In these cases, a deprecation policy is needed to properly
+inform users of the change.
+
+When a (non-experimental) feature is slated to be removed it should:
+
+1. Be _soft_ deprecated in the _next_ release
+ - Use of the deprecated feature will still work.
+ - This means deprecating via documentation and annotation (`@deprecated`) only.
+ - Include a note in `news.txt` under `DEPRECATIONS`.
+2. Be _hard_ deprecated in a following a release in which it was soft deprecated.
+ - Use of the deprecated feature will still work but should issue a warning
+ (typically via `vim.deprecate()` for Lua features).
+ - Features implemented in Vimscript or in C will need bespoke implementations
+ to communicate to users that the feature is deprecated
+3. Be removed in a release following the release in which it was hard deprecated
+ - Usually this will be the next release, but it may be a later release if a
+ longer deprecation cycle is desired
+ - If possible, keep the feature as a stub (e.g. function API) and issue an error
+ when it is accessed.
+
+Example:
+
+```
+ Deprecation Removal
+ ┆ ┆ ┆
+ ┆ Soft ┆ Hard ┆
+ ┆ Deprecation ┆ Deprecation ┆
+ ┆ Period ┆ Preiod ┆
+ ────────────────────────────────────────────────────────────
+Version: 0.10 0.11 0.12
+ ────────────────────────────────────────────────────────────
+ Old code Old code Old code
+ + +
+ New code New code New code
+```
+
+Feature removals which may benefit from community input or further discussion
+should also have a tracking issue (which should be linked to in the release
+notes).
+
+Exceptions to this policy may be made (for experimental subsystems or when
+there is broad consensus among maintainers). The rationale for the exception
+should be stated explicitly and publicly.
+
Third-party dependencies
------------------------
-These "bundled" dependencies can be updated by bumping their versions in `cmake.deps/CMakeLists.txt`.
+These "bundled" dependencies can be updated by bumping their versions in `cmake.deps/deps.txt`.
Some can be auto-bumped by `scripts/bump_deps.lua`.
* [LuaJIT](https://github.com/LuaJIT/LuaJIT)
* [Lua](https://www.lua.org/download.html)
* [Luv](https://github.com/luvit/luv)
+ * When bumping, also sync [our bundled documentation](https://github.com/neovim/neovim/blob/master/runtime/doc/luvref.txt) with [the upstream documentation](https://github.com/luvit/luv/blob/master/docs.md).
* [gettext](https://ftp.gnu.org/pub/gnu/gettext/)
* [libiconv](https://ftp.gnu.org/pub/gnu/libiconv)
* [libtermkey](https://github.com/neovim/libtermkey)
* [libuv](https://github.com/libuv/libuv)
* [libvterm](http://www.leonerd.org.uk/code/libvterm/)
* [lua-compat](https://github.com/keplerproject/lua-compat-5.3)
-* [msys2](https://github.com/msys2/MINGW-packages) (for mingw Windows build)
- * Changes to mingw can [break our mingw build](https://github.com/msys2/MINGW-packages/issues/9946).
* [tree-sitter](https://github.com/tree-sitter/tree-sitter)
* [unibilium](https://github.com/neovim/unibilium)
+* [treesitter parsers](https://github.com/neovim/neovim/blob/7e97c773e3ba78fcddbb2a0b9b0d572c8210c83e/cmake.deps/deps.txt#L47-L62)
### Vendored dependencies
@@ -88,45 +138,66 @@ These dependencies are "vendored" (inlined), we must update the sources manually
* send improvements upstream!
* `src/xdiff/`: [xdiff](https://github.com/git/git/tree/master/xdiff)
* `src/cjson/`: [lua-cjson](https://github.com/openresty/lua-cjson)
-* `src/nvim/lib/`: [Klib](https://github.com/attractivechaos/klib)
+* `src/klib/`: [Klib](https://github.com/attractivechaos/klib)
* `runtime/lua/vim/inspect.lua`: [inspect.lua](https://github.com/kikito/inspect.lua)
* `src/nvim/tui/terminfo_defs.h`: terminfo definitions
* Run `scripts/update_terminfo.sh` to update these definitions.
-* [treesitter parsers](https://github.com/neovim/neovim/blob/fcc24e43e0b5f9d801a01ff2b8f78ce8c16dd551/cmake.deps/CMakeLists.txt#L197-L210)
+* `runtime/lua/vim/lsp/_meta/protocol.lua`: LSP specification
+ * Run `scripts/gen_lsp.lua` to update.
+* `runtime/lua/vim/_meta/lpeg.lua`: LPeg definitions.
+ * Refer to [`LuaCATS/lpeg`](https://github.com/LuaCATS/lpeg) for updates.
+* `runtime/lua/vim/re.lua`: LPeg regex module.
+ * Vendored from LPeg. Needs to be updated when LPeg is updated.
+* `src/bit.c`: only for PUC lua: port of `require'bit'` from luajit https://bitop.luajit.org/
+* `runtime/lua/coxpcall.lua`: coxpcall (only needed for PUC lua, builtin to luajit)
### Forks
We may maintain forks, if we are waiting on upstream changes: https://github.com/neovim/neovim/wiki/Deps
-CI
---------------
-
-### General
-
-As our CI is primarily dependent on GitHub Actions at the moment, then so will
-our CI strategy be. The following guidelines have worked well for us so far:
-
-* Never use a macOS runner if an Ubuntu or a Windows runner can be used
- instead. This is because macOS runners have a [tighter restrictions on the
- number of concurrent jobs](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits).
-
-### Runner versions
-
-* For special-purpose jobs where the runner version doesn't really matter,
- prefer `-latest` tags so we don't need to manually bump the versions. An
- example of a special-purpose workflow is `labeler.yml`.
-
-* For our testing jobs, which is currently only `ci.yml`, prefer to use the
- latest stable (i.e. non-beta) version explicitly. Avoid using the `-latest`
- tags here as it makes it difficult to determine from an unrelated PR if a
- failure is due to the PR itself or due to GitHub bumping the `-latest` tag
- without our knowledge. There's also a high risk that automatic bumping the CI
- versions will fail due to manual work being required from experience.
-
-* For our release job, which is `release.yml`, prefer to use the oldest stable
- (i.e. non-deprecated) versions available. The reason is that we're trying to
- produce images that work in the broadest number of environments, and
- therefore want to use older releases.
+Non-technical dependencies
+--------------------------
+
+* GitHub users:
+ * https://github.com/marvim
+ * https://github.com/nvim-winget
+* Domain names (held in https://namecheap.com):
+ * neovim.org
+ * neovim.io
+ * packspec.org
+ * pkgjson.org
+* DNS for the above domains is managed in https://cloudflare.com (not the domain registrar)
+
+Automation (CI)
+---------------
+
+### Backup
+
+Discussions from issues and PRs are backed up here:
+https://github.com/neovim/neovim-backup
+
+### Development guidelines
+
+* CI and automation jobs are primarily driven by GitHub Actions.
+* Avoid macOS if an Ubuntu or a Windows runner can be used instead. This is
+ because macOS runners have [tighter restrictions on the number of concurrent
+ jobs](https://docs.github.com/en/actions/learn-github-actions/usage-limits-billing-and-administration#usage-limits).
+
+* Runner versions:
+ * For special-purpose jobs where the runner version doesn't really matter,
+ prefer `-latest` tags so we don't need to manually bump the versions. An
+ example of a special-purpose workflow is `labeler.yml`.
+ * For our testing jobs, which are in `test.yml` and `build.yml`, prefer to
+ use the latest stable (i.e. non-beta) version explicitly. Avoid using the
+ `-latest` tags here as it makes it difficult to determine from an
+ unrelated PR if a failure is due to the PR itself or due to GitHub bumping
+ the `-latest` tag without our knowledge. There's also a high risk that
+ automatically bumping the CI versions will fail due to manual work being
+ required from experience.
+ * For our release job, which is `release.yml`, prefer to use the oldest
+ stable (i.e. non-deprecated) versions available. The reason is that we're
+ trying to produce images that work in the broadest number of environments,
+ and therefore want to use older releases.
See also
--------
diff --git a/Makefile b/Makefile
index be1eef92de..3781d06a6c 100644
--- a/Makefile
+++ b/Makefile
@@ -53,7 +53,6 @@ ifeq (,$(BUILD_TOOL))
endif
endif
-
# Only need to handle Ninja here. Make will inherit the VERBOSE variable, and the -j, -l, and -n flags.
ifeq ($(CMAKE_GENERATOR),Ninja)
ifneq ($(VERBOSE),)
@@ -114,37 +113,124 @@ build/.ran-deps-cmake::
# TODO: cmake 3.2+ add_custom_target() has a USES_TERMINAL flag.
oldtest: | nvim build/runtime/doc/tags
- +$(SINGLE_MAKE) -C src/nvim/testdir clean
+ +$(SINGLE_MAKE) -C test/old/testdir clean
ifeq ($(strip $(TEST_FILE)),)
- +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) $(MAKEOVERRIDES)
+ +$(SINGLE_MAKE) -C test/old/testdir NVIM_PRG=$(NVIM_PRG) $(MAKEOVERRIDES)
else
@# Handle TEST_FILE=test_foo{,.res,.vim}.
- +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst %.vim,%,$(patsubst %.res,%,$(TEST_FILE)))
+ +$(SINGLE_MAKE) -C test/old/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst %.vim,%,$(patsubst %.res,%,$(TEST_FILE)))
endif
# Build oldtest by specifying the relative .vim filename.
.PHONY: phony_force
-src/nvim/testdir/%.vim: phony_force
- +$(SINGLE_MAKE) -C src/nvim/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst src/nvim/testdir/%.vim,%,$@)
+test/old/testdir/%.vim: phony_force nvim
+ +$(SINGLE_MAKE) -C test/old/testdir NVIM_PRG=$(NVIM_PRG) SCRIPTS= $(MAKEOVERRIDES) $(patsubst test/old/testdir/%.vim,%,$@)
functionaltest-lua: | nvim
- $(BUILD_TOOL) -C build $@
+ $(BUILD_TOOL) -C build functionaltest
FORMAT=formatc formatlua format
-LINT=lintlua lintsh lintc check-single-includes lintcommit lint
+LINT=lintlua lintsh lintc clang-analyzer lintcommit lint
TEST=functionaltest unittest
-generated-sources benchmark uninstall $(FORMAT) $(LINT) $(TEST): | build/.ran-cmake
+generated-sources benchmark $(FORMAT) $(LINT) $(TEST) doc: | build/.ran-cmake
$(CMAKE_PRG) --build build --target $@
test: $(TEST)
+# The ignored header files should be synced with the `check_includes_ignore`
+# array in src/clint.py
iwyu: build/.ran-cmake
- cmake --workflow --fresh --preset iwyu > build/iwyu.log
- iwyu-fix-includes --only_re="src/nvim" --ignore_re="src/nvim/(auto|map.h|eval/encode.c)" --safe_headers < build/iwyu.log
+ cmake --preset iwyu
+ cmake --build build > build/iwyu.log
+ iwyu-fix-includes --only_re="src/nvim" --ignore_re="(src/nvim/eval/encode.c|src/nvim/auto/|src/nvim/os/lang.c|src/nvim/map.c\
+ |src/nvim/api/extmark.h\
+ |src/nvim/api/private/dispatch.h\
+ |src/nvim/api/private/helpers.h\
+ |src/nvim/api/private/validate.h\
+ |src/nvim/api/ui.h\
+ |src/nvim/ascii_defs.h\
+ |src/nvim/assert_defs.h\
+ |src/nvim/autocmd.h\
+ |src/nvim/autocmd_defs.h\
+ |src/nvim/buffer.h\
+ |src/nvim/buffer_defs.h\
+ |src/nvim/channel.h\
+ |src/nvim/charset.h\
+ |src/nvim/cmdexpand.h\
+ |src/nvim/cmdhist.h\
+ |src/nvim/decoration.h\
+ |src/nvim/diff.h\
+ |src/nvim/drawline.h\
+ |src/nvim/drawscreen.h\
+ |src/nvim/eval.h\
+ |src/nvim/eval/encode.h\
+ |src/nvim/eval/typval.h\
+ |src/nvim/eval/typval_defs.h\
+ |src/nvim/eval/userfunc.h\
+ |src/nvim/eval/window.h\
+ |src/nvim/event/libuv_process.h\
+ |src/nvim/event/loop.h\
+ |src/nvim/event/multiqueue.h\
+ |src/nvim/event/process.h\
+ |src/nvim/event/rstream.h\
+ |src/nvim/event/signal.h\
+ |src/nvim/event/socket.h\
+ |src/nvim/event/stream.h\
+ |src/nvim/event/time.h\
+ |src/nvim/event/wstream.h\
+ |src/nvim/ex_cmds.h\
+ |src/nvim/ex_cmds_defs.h\
+ |src/nvim/ex_docmd.h\
+ |src/nvim/extmark.h\
+ |src/nvim/file_search.h\
+ |src/nvim/fileio.h\
+ |src/nvim/fold.h\
+ |src/nvim/garray.h\
+ |src/nvim/getchar.h\
+ |src/nvim/globals.h\
+ |src/nvim/grid.h\
+ |src/nvim/highlight.h\
+ |src/nvim/highlight_group.h\
+ |src/nvim/input.h\
+ |src/nvim/insexpand.h\
+ |src/nvim/keycodes.h\
+ |src/nvim/log.h\
+ |src/nvim/lua/executor.h\
+ |src/nvim/main.h\
+ |src/nvim/mark.h\
+ |src/nvim/mouse.h\
+ |src/nvim/move.h\
+ |src/nvim/msgpack_rpc/channel.h\
+ |src/nvim/msgpack_rpc/channel_defs.h\
+ |src/nvim/msgpack_rpc/helpers.h\
+ |src/nvim/msgpack_rpc/unpacker.h\
+ |src/nvim/option.h\
+ |src/nvim/os/fileio.h\
+ |src/nvim/os/input.h\
+ |src/nvim/os/pty_conpty_win.h\
+ |src/nvim/os/pty_process_unix.h\
+ |src/nvim/os/pty_process_win.h\
+ |src/nvim/path.h\
+ |src/nvim/plines.h\
+ |src/nvim/popupmenu.h\
+ |src/nvim/search.h\
+ |src/nvim/spell.h\
+ |src/nvim/syntax.h\
+ |src/nvim/textobject.h\
+ |src/nvim/tui/input.h\
+ |src/nvim/tui/tui.h\
+ |src/nvim/ui.h\
+ |src/nvim/ui_client.h\
+ |src/nvim/ui_compositor.h\
+ |src/nvim/viml/parser/expressions.h\
+ |src/nvim/viml/parser/parser.h\
+ |src/nvim/window.h\
+ )" --nosafe_headers < build/iwyu.log
cmake -B build -U ENABLE_IWYU
+ cmake --build build
clean:
+test -d build && $(BUILD_TOOL) -C build clean || true
- $(MAKE) -C src/nvim/testdir clean
+ $(MAKE) -C test/old/testdir clean
$(MAKE) -C runtime/indent clean
distclean:
@@ -173,4 +259,4 @@ $(DEPS_BUILD_DIR)/%: phony_force
$(BUILD_TOOL) -C $(DEPS_BUILD_DIR) $(patsubst $(DEPS_BUILD_DIR)/%,%,$@)
endif
-.PHONY: test clean distclean nvim libnvim cmake deps install appimage checkprefix benchmark uninstall $(FORMAT) $(LINT) $(TEST)
+.PHONY: test clean distclean nvim libnvim cmake deps install appimage checkprefix benchmark $(FORMAT) $(LINT) $(TEST)
diff --git a/README.md b/README.md
index 3f2e158900..be8b3178f3 100644
--- a/README.md
+++ b/README.md
@@ -5,10 +5,8 @@
<a href="https://app.element.io/#/room/#neovim:matrix.org">Chat</a>
</h1>
-[![GitHub CI](https://github.com/neovim/neovim/actions/workflows/ci.yml/badge.svg?event=push&branch=master)](https://github.com/neovim/neovim/actions?query=workflow%3ACI+branch%3Amaster+event%3Apush)
[![Coverity Scan analysis](https://scan.coverity.com/projects/2227/badge.svg)](https://scan.coverity.com/projects/2227)
[![Clang analysis](https://neovim.io/doc/reports/clang/badge.svg)](https://neovim.io/doc/reports/clang)
-[![PVS-Studio analysis](https://neovim.io/doc/reports/pvs/badge.svg)](https://neovim.io/doc/reports/pvs/PVS-studio.html.d)
[![Packages](https://repology.org/badge/tiny-repos/neovim.svg)](https://repology.org/metapackage/neovim)
[![Debian CI](https://badges.debian.net/badges/debian/testing/neovim/version.svg)](https://buildd.debian.org/neovim)
[![Downloads](https://img.shields.io/github/downloads/neovim/neovim/total.svg?maxAge=2592001)](https://github.com/neovim/neovim/releases/)
@@ -76,14 +74,13 @@ See [`:help nvim-from-vim`](https://neovim.io/doc/user/nvim.html#nvim-from-vim)
Project layout
--------------
- ├─ ci/ build automation
├─ cmake/ CMake utils
├─ cmake.config/ CMake defines
├─ cmake.deps/ subproject to fetch and build dependencies (optional)
├─ runtime/ plugins and docs
├─ src/nvim/ application source code (see src/nvim/README.md)
│ ├─ api/ API subsystem
- │ ├─ eval/ VimL subsystem
+ │ ├─ eval/ Vimscript subsystem
│ ├─ event/ event-loop subsystem
│ ├─ generators/ code generation (pre-compilation)
│ ├─ lib/ generic data structures
diff --git a/ci/before_cache.sh b/ci/before_cache.sh
deleted file mode 100755
index 3daeb04793..0000000000
--- a/ci/before_cache.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-set -o pipefail
-
-CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/build.sh"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/suite.sh"
-
-mkdir -p "${HOME}/.cache"
-
-echo "before_cache.sh: cache size"
-du -chd 1 "${HOME}/.cache" | sort -rh | head -20
-
-# Update the third-party dependency cache only if the build was successful.
-if ended_successfully && [ -d "${DEPS_BUILD_DIR}" ]; then
- # Do not cache downloads. They should not be needed with up-to-date deps.
- rm -rf "${DEPS_BUILD_DIR}/build/downloads"
- rm -rf "${CACHE_NVIM_DEPS_DIR}"
- mv "${DEPS_BUILD_DIR}" "${CACHE_NVIM_DEPS_DIR}"
-
- touch "${CACHE_MARKER}"
- echo "Updated third-party dependencies (timestamp: $(_stat "${CACHE_MARKER}"))."
-fi
diff --git a/ci/before_script.sh b/ci/before_script.sh
deleted file mode 100755
index 066789af36..0000000000
--- a/ci/before_script.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-set -o pipefail
-
-CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/build.sh"
-
-# Test some of the configuration variables.
-if [[ -n "${GCOV}" ]] && [[ ! $(type -P "${GCOV}") ]]; then
- echo "\$GCOV: '${GCOV}' is not executable."
- exit 1
-fi
-if [[ -n "${LLVM_SYMBOLIZER}" ]] && [[ ! $(type -P "${LLVM_SYMBOLIZER}") ]]; then
- echo "\$LLVM_SYMBOLIZER: '${LLVM_SYMBOLIZER}' is not executable."
- exit 1
-fi
-
-# Compile dependencies.
-build_deps
-
-# Install cluacov for Lua coverage.
-if [[ "$USE_LUACOV" == 1 ]]; then
- "${DEPS_BUILD_DIR}/usr/bin/luarocks" install cluacov
-fi
-
-rm -rf "${LOG_DIR}"
-mkdir -p "${LOG_DIR}"
diff --git a/ci/common/build.sh b/ci/common/build.sh
deleted file mode 100644
index 95972aab13..0000000000
--- a/ci/common/build.sh
+++ /dev/null
@@ -1,78 +0,0 @@
-_stat() {
- if test "${CI_OS_NAME}" = osx ; then
- stat -f %Sm "${@}"
- else
- stat -c %y "${@}"
- fi
-}
-
-top_make() {
- printf '%78s\n' ' ' | tr ' ' '='
- ninja "$@"
-}
-
-build_make() {
- top_make -C "${BUILD_DIR}" "$@"
-}
-
-build_deps() {
- if test "${FUNCTIONALTEST}" = "functionaltest-lua" ; then
- DEPS_CMAKE_FLAGS="${DEPS_CMAKE_FLAGS} -DUSE_BUNDLED_LUA=ON"
- fi
-
- mkdir -p "${DEPS_BUILD_DIR}"
-
- # Use cached dependencies if $CACHE_MARKER exists.
- if test -f "${CACHE_MARKER}"; then
- echo "Using third-party dependencies from cache (last update: $(_stat "${CACHE_MARKER}"))."
- cp -a "${CACHE_NVIM_DEPS_DIR}"/. "${DEPS_BUILD_DIR}"
- fi
-
- # Even if we're using cached dependencies, run CMake and make to
- # update CMake configuration and update to newer deps versions.
- cd "${DEPS_BUILD_DIR}"
- echo "Configuring with '${DEPS_CMAKE_FLAGS}'."
- # shellcheck disable=SC2086
- CC= cmake -G Ninja ${DEPS_CMAKE_FLAGS} "${CI_BUILD_DIR}/cmake.deps/"
-
- if ! top_make; then
- exit 1
- fi
-
- cd "${CI_BUILD_DIR}"
-}
-
-build_nvim() {
- check_core_dumps --delete quiet
-
- if test -n "${CLANG_SANITIZER}" ; then
- CMAKE_FLAGS="${CMAKE_FLAGS} -DCLANG_${CLANG_SANITIZER}=ON"
- fi
-
- mkdir -p "${BUILD_DIR}"
- cd "${BUILD_DIR}"
- echo "Configuring with '${CMAKE_FLAGS} $*'."
- # shellcheck disable=SC2086
- cmake -G Ninja ${CMAKE_FLAGS} "$@" "${CI_BUILD_DIR}"
-
- echo "Building nvim."
- if ! top_make nvim ; then
- exit 1
- fi
-
- if test "$CLANG_SANITIZER" != "TSAN" ; then
- echo "Building libnvim."
- if ! top_make libnvim ; then
- exit 1
- fi
- fi
-
- # Invoke nvim to trigger *San early.
- if ! (bin/nvim --version && bin/nvim -u NONE -e -cq | cat -vet) ; then
- check_sanitizer "${LOG_DIR}"
- exit 1
- fi
- check_sanitizer "${LOG_DIR}"
-
- cd "${CI_BUILD_DIR}"
-}
diff --git a/ci/common/submit_coverage.sh b/ci/common/submit_coverage.sh
deleted file mode 100755
index f781ca8e5e..0000000000
--- a/ci/common/submit_coverage.sh
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/bin/sh
-# Collect and submit coverage reports.
-#
-# Args:
-# $1: Flag(s) for codecov, separated by comma.
-
-set -e
-
-# Change to grandparent dir (POSIXly).
-CDPATH='' cd -P -- "$(dirname -- "$0")/../.." || exit
-
-echo "=== running submit_coverage in $PWD: $* ==="
-"$GCOV" --version
-
-# Download/install codecov-bash and gcovr once.
-codecov_sh="${TEMP:-/tmp}/codecov.bash"
-if ! [ -f "$codecov_sh" ]; then
- curl --retry 5 --silent --fail -o "$codecov_sh" https://codecov.io/bash
- chmod +x "$codecov_sh"
-
- python -m pip install --quiet --user gcovr
-fi
-
-(
- cd build
- python -m gcovr --branches --exclude-unreachable-branches --print-summary -j 2 --exclude '.*/auto/.*' --root .. --delete -o ../coverage.xml --xml
-)
-
-# Upload to codecov.
-# -X gcov: disable gcov, done manually above.
-# -X fix: disable fixing of reports (not necessary, rather slow)
-# -Z: exit non-zero on failure
-# -F: flag(s)
-# NOTE: ignoring flags for now, since this causes timeouts on codecov.io then,
-# which they know about for about a year already...
-# Flags must match pattern ^[\w\,]+$ ("," as separator).
-codecov_flags="$(uname -s),${1}"
-codecov_flags=$(echo "$codecov_flags" | sed 's/[^,_a-zA-Z0-9]/_/g')
-if ! "$codecov_sh" -f coverage.xml -X gcov -X fix -Z -F "${codecov_flags}"; then
- echo "codecov upload failed."
-fi
-
-# Cleanup always, especially collected data.
-find . \( -name '*.gcov' -o -name '*.gcda' \) -ls -delete | wc -l
-rm -f coverage.xml
-
-# Upload Lua coverage (generated manually on AppVeyor/Windows).
-if [ "$USE_LUACOV" = 1 ] && [ "$1" != "oldtest" ]; then
- if [ -x "${DEPS_BUILD_DIR}/usr/bin/luacov" ]; then
- "${DEPS_BUILD_DIR}/usr/bin/luacov"
- fi
- if ! "$codecov_sh" -f luacov.report.out -X gcov -X fix -Z -F "lua,${codecov_flags}"; then
- echo "codecov upload failed."
- fi
- rm luacov.stats.out
-fi
diff --git a/ci/common/suite.sh b/ci/common/suite.sh
deleted file mode 100644
index c81261d2e7..0000000000
--- a/ci/common/suite.sh
+++ /dev/null
@@ -1,41 +0,0 @@
-# Test success marker. If END_MARKER file exists, we know that all tests
-# finished. If FAIL_SUMMARY_FILE exists we know that some tests failed, this
-# file will contain information about failed tests. Build is considered
-# successful if tests ended without any of them failing.
-END_MARKER="$BUILD_DIR/.tests_finished"
-FAIL_SUMMARY_FILE="$BUILD_DIR/.test_errors"
-
-fail() {
- local test_name="$1"
- local message="$2"
-
- : "${message:=Test $test_name failed}"
-
- local full_msg="$test_name :: $message"
- echo "${full_msg}" >> "${FAIL_SUMMARY_FILE}"
- echo "Failed: $full_msg"
- export FAILED=1
-}
-
-ended_successfully() {
- if test -f "${FAIL_SUMMARY_FILE}" ; then
- echo 'Test failed, complete summary:'
- cat "${FAIL_SUMMARY_FILE}"
-
- if [[ "$GITHUB_ACTIONS" == "true" ]]; then
- rm -f "$FAIL_SUMMARY_FILE"
- fi
-
- return 1
- fi
- if ! test -f "${END_MARKER}" ; then
- echo 'ended_successfully called before end marker was touched'
- return 1
- fi
- return 0
-}
-
-end_tests() {
- touch "${END_MARKER}"
- ended_successfully
-}
diff --git a/ci/common/test.sh b/ci/common/test.sh
deleted file mode 100644
index 326ec162c3..0000000000
--- a/ci/common/test.sh
+++ /dev/null
@@ -1,175 +0,0 @@
-. "${CI_DIR}/common/build.sh"
-. "${CI_DIR}/common/suite.sh"
-
-submit_coverage() {
- if [ -n "${GCOV}" ]; then
- "${CI_DIR}/common/submit_coverage.sh" "$@" || echo 'codecov upload failed.'
- fi
-}
-
-print_core() {
- local app="$1"
- local core="$2"
- if test "$app" = quiet ; then
- echo "Found core $core"
- return 0
- fi
- echo "======= Core file $core ======="
- if test "${CI_OS_NAME}" = osx ; then
- lldb -Q -o "bt all" -f "${app}" -c "${core}"
- else
- gdb -n -batch -ex 'thread apply all bt full' "${app}" -c "${core}"
- fi
-}
-
-check_core_dumps() {
- local del=
- if test "$1" = "--delete" ; then
- del=1
- shift
- fi
- local app="${1:-${BUILD_DIR}/bin/nvim}"
- local cores
- if test "${CI_OS_NAME}" = osx ; then
- cores="$(find /cores/ -type f -print)"
- local _sudo='sudo'
- else
- cores="$(find ./ -type f \( -name 'core.*' -o -name core -o -name nvim.core \) -print)"
- local _sudo=
- fi
-
- if test -z "${cores}" ; then
- return
- fi
- local core
- for core in $cores; do
- if test "$del" = "1" ; then
- print_core "$app" "$core" >&2
- "$_sudo" rm "$core"
- else
- print_core "$app" "$core"
- fi
- done
- if test "$app" != quiet ; then
- fail 'cores' 'Core dumps found'
- fi
-}
-
-check_logs() {
- # Iterate through each log to remove an useless warning.
- # shellcheck disable=SC2044
- for log in $(find "${1}" -type f -name "${2}"); do
- sed -i "${log}" \
- -e '/Warning: noted but unhandled ioctl/d' \
- -e '/could cause spurious value errors to appear/d' \
- -e '/See README_MISSING_SYSCALL_OR_IOCTL for guidance/d'
- done
-
- # Now do it again, but only consider files with size > 0.
- local err=""
- # shellcheck disable=SC2044
- for log in $(find "${1}" -type f -name "${2}" -size +0); do
- cat "${log}"
- err=1
- rm "${log}"
- done
- if test -n "${err}" ; then
- fail 'logs' 'Runtime errors detected.'
- fi
-}
-
-valgrind_check() {
- check_logs "${1}" "valgrind-*"
-}
-
-check_sanitizer() {
- if test -n "${CLANG_SANITIZER}"; then
- check_logs "${1}" "*san.*" | ${SYMBOLIZER:-cat}
- fi
-}
-
-unittests() {(
- ulimit -c unlimited || true
- if ! build_make unittest ; then
- fail 'unittests' 'Unit tests failed'
- fi
- submit_coverage unittest
- check_core_dumps "$(command -v luajit)"
-)}
-
-functionaltests() {(
- ulimit -c unlimited || true
- if ! build_make "${FUNCTIONALTEST}"; then
- fail 'functionaltests' 'Functional tests failed'
- fi
- submit_coverage functionaltest
- check_sanitizer "${LOG_DIR}"
- valgrind_check "${LOG_DIR}"
- check_core_dumps
-)}
-
-oldtests() {(
- ulimit -c unlimited || true
- if ! make oldtest; then
- reset
- fail 'oldtests' 'Legacy tests failed'
- fi
- submit_coverage oldtest
- check_sanitizer "${LOG_DIR}"
- valgrind_check "${LOG_DIR}"
- check_core_dumps
-)}
-
-check_runtime_files() {(
- local test_name="$1" ; shift
- local message="$1" ; shift
- local tst="$1" ; shift
-
- cd runtime
- for file in $(git ls-files "$@") ; do
- # Check that test is not trying to work with files with spaces/etc
- # Prefer failing the build over using more robust construct because files
- # with IFS are not welcome.
- if ! test -e "$file" ; then
- fail "$test_name" "It appears that $file is only a part of the file name"
- fi
- if ! test "$tst" "$INSTALL_PREFIX/share/nvim/runtime/$file" ; then
- fail "$test_name" "$(printf "%s%s" "$message" "$file")"
- fi
- done
-)}
-
-install_nvim() {(
- if ! build_make install ; then
- fail 'install' 'make install failed'
- exit 1
- fi
-
- "${INSTALL_PREFIX}/bin/nvim" --version
- if ! "${INSTALL_PREFIX}/bin/nvim" -u NONE -e -c ':help' -c ':qall' ; then
- echo "Running ':help' in the installed nvim failed."
- echo "Maybe the helptags have not been generated properly."
- fail 'help' 'Failed running :help'
- fi
-
- # Check that all runtime files were installed
- check_runtime_files \
- 'runtime-install' \
- 'It appears that %s is not installed.' \
- -e \
- '*.vim' '*.ps' '*.dict' '*.py' '*.tutor'
-
- # Check that some runtime files are installed and are executables
- check_runtime_files \
- 'not-exe' \
- 'It appears that %s is not installed or is not executable.' \
- -x \
- '*.awk' '*.sh' '*.bat'
-
- # Check that generated syntax file has function names, #5060.
- local genvimsynf=syntax/vim/generated.vim
- local gpat='syn keyword vimFuncName .*eval'
- if ! grep -q "$gpat" "${INSTALL_PREFIX}/share/nvim/runtime/$genvimsynf" ; then
- fail 'funcnames' "It appears that $genvimsynf does not contain $gpat."
- fi
-)}
diff --git a/ci/install.sh b/ci/install.sh
deleted file mode 100755
index 5925cc7b02..0000000000
--- a/ci/install.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-set -o pipefail
-
-# Use default CC to avoid compilation problems when installing Python modules.
-echo "Install neovim module for Python."
-CC=cc python3 -m pip -q install --user --upgrade pynvim
-
-echo "Install neovim RubyGem."
-gem install --no-document --bindir "$HOME/.local/bin" --user-install --pre neovim
-
-echo "Install neovim npm package"
-npm install -g neovim
-npm link neovim
-
-if [[ $CI_OS_NAME != osx ]]; then
- sudo cpanm -n Neovim::Ext || cat "$HOME/.cpanm/build.log"
- perl -W -e 'use Neovim::Ext; print $Neovim::Ext::VERSION'
-fi
diff --git a/ci/run_tests.sh b/ci/run_tests.sh
deleted file mode 100755
index 0ef7080628..0000000000
--- a/ci/run_tests.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/usr/bin/env bash
-
-set -e
-set -o pipefail
-
-CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/build.sh"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/test.sh"
-# shellcheck source-path=SCRIPTDIR
-source "${CI_DIR}/common/suite.sh"
-
-rm -f "$END_MARKER"
-
-# Run all tests (with some caveats) if no input argument is given
-if (($# == 0)); then
- tests=('build_nvim')
-
- # Additional threads aren't created in the unit/old tests
- if test "$CLANG_SANITIZER" != "TSAN"; then
- if test "${FUNCTIONALTEST}" != "functionaltest-lua"; then
- tests+=('unittests')
- fi
- tests+=('oldtests')
- fi
-
- tests+=('functionaltests' 'install_nvim')
-else
- tests=("$@")
-fi
-
-for i in "${tests[@]}"; do
- eval "$i" || fail "$i"
-done
-
-end_tests
-
-if [[ -s "${GCOV_ERROR_FILE}" ]]; then
- echo '=== Unexpected gcov errors: ==='
- cat "${GCOV_ERROR_FILE}"
- exit 1
-fi
diff --git a/cmake.config/CMakeLists.txt b/cmake.config/CMakeLists.txt
index 6de86cbaf2..ed405c602e 100644
--- a/cmake.config/CMakeLists.txt
+++ b/cmake.config/CMakeLists.txt
@@ -4,6 +4,7 @@ include(CheckFunctionExists)
include(CheckIncludeFiles)
include(CheckCSourceRuns)
include(CheckCSourceCompiles)
+include(TestBigEndian)
check_c_source_compiles("
#include <execinfo.h>
@@ -28,34 +29,26 @@ int main(void)
check_type_size("int" SIZEOF_INT LANGUAGE C)
check_type_size("long" SIZEOF_LONG LANGUAGE C)
check_type_size("intmax_t" SIZEOF_INTMAX_T LANGUAGE C)
-check_type_size("int32_t" SIZEOF_INT32_T LANGUAGE C)
check_type_size("size_t" SIZEOF_SIZE_T LANGUAGE C)
-check_type_size("long long" SIZEOF_LONG_LONG LANGUAGE C)
check_type_size("void *" SIZEOF_VOID_PTR LANGUAGE C)
check_symbol_exists(_NSGetEnviron crt_externs.h HAVE__NSGETENVIRON)
# Headers
check_include_files(langinfo.h HAVE_LANGINFO_H)
-check_include_files(locale.h HAVE_LOCALE_H)
-check_include_files(pwd.h HAVE_PWD_H)
check_include_files(strings.h HAVE_STRINGS_H)
check_include_files(sys/utsname.h HAVE_SYS_UTSNAME_H)
check_include_files(termios.h HAVE_TERMIOS_H)
check_include_files(sys/uio.h HAVE_SYS_UIO_H)
check_include_files(sys/sdt.h HAVE_SYS_SDT_H)
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ check_include_files(sys/xattr.h HAVE_XATTR)
+endif()
# Functions
check_function_exists(fseeko HAVE_FSEEKO)
-check_function_exists(getpwent HAVE_GETPWENT)
-check_function_exists(getpwnam HAVE_GETPWNAM)
-check_function_exists(getpwuid HAVE_GETPWUID)
check_function_exists(readv HAVE_READV)
-check_function_exists(opendir HAVE_OPENDIR)
check_function_exists(readlink HAVE_READLINK)
-check_function_exists(setpgid HAVE_SETPGID)
-check_function_exists(setsid HAVE_SETSID)
-check_function_exists(sigaction HAVE_SIGACTION)
check_function_exists(strnlen HAVE_STRNLEN)
check_function_exists(strcasecmp HAVE_STRCASECMP)
check_function_exists(strncasecmp HAVE_STRNCASECMP)
@@ -64,22 +57,28 @@ check_function_exists(strptime HAVE_STRPTIME)
check_c_source_compiles("
#include <sys/types.h>
#include <dirent.h>
+#include <sys/file.h>
int main(void)
{
DIR *dir = opendir(\"dirname\");
dirfd(dir);
+ flock(10, LOCK_SH);
return 0;
}
-" HAVE_DIRFD)
+" HAVE_DIRFD_AND_FLOCK)
+
check_c_source_compiles("
-#include <sys/file.h>
+#include <pwd.h>
int main(void)
{
- flock(10, LOCK_SH);
+ getpwent();
+ getpwuid(0);
+ getpwnam(\"root\");
return 0;
}
-" HAVE_FLOCK)
+" HAVE_PWD_FUNCS)
+
if(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
check_c_source_compiles("
@@ -101,11 +100,13 @@ if(HAVE_LANGINFO_H)
endif()
check_include_files("endian.h" HAVE_ENDIAN_H)
-check_include_files("sys/endian.h" HAVE_SYS_ENDIAN_H)
set(ENDIAN_INCLUDE_FILE "endian.h")
-if(HAVE_SYS_ENDIAN_H AND NOT HAVE_ENDIAN_H)
- set(ENDIAN_INCLUDE_FILE "sys/endian.h")
+if(NOT HAVE_ENDIAN_H)
+ check_include_files("sys/endian.h" HAVE_SYS_ENDIAN_H)
+ if (HAVE_SYS_ENDIAN_H)
+ set(ENDIAN_INCLUDE_FILE "sys/endian.h")
+ endif()
endif()
set(SI "#include <stdint.h>\n")
@@ -130,63 +131,30 @@ endif()
if("${HAVE_BE64TOH_MACROS}" OR "${HAVE_BE64TOH_FUNC}")
set(HAVE_BE64TOH 1)
endif()
-if (NOT "${HAVE_BE64TOH}")
- if (NOT "${CMAKE_CROSSCOMPILING}")
- # It is safe to make ORDER_BIG_ENDIAN not defined if
- # - HAVE_BE64TOH is true. In this case be64toh will be used unconditionally in
- # any case and ORDER_BIG_ENDIAN will not be examined.
- # - CMAKE_CROSSCOMPILING *and* HAVE_BE64TOH are both false. In this case
- # be64toh function which uses cycle and arithmetic operations is used which
- # will work regardless of endianness. Function is sub-optimal though.
- check_c_source_runs("
- ${SI}
- ${MS}
- char *s = (char *) &i;
- return (
- s[0] == 0x01
- && s[1] == 0x02
- && s[2] == 0x03
- && s[3] == 0x04
- && s[4] == 0x05
- && s[5] == 0x06
- && s[6] == 0x07
- && s[7] == 0x08) ? 0 : 1;
- ${ME}"
- ORDER_BIG_ENDIAN)
- endif()
-endif()
-# generate configuration header and update include directories
+test_big_endian(ORDER_BIG_ENDIAN)
+
configure_file (
"${PROJECT_SOURCE_DIR}/cmake.config/config.h.in"
"${PROJECT_BINARY_DIR}/cmake.config/auto/config.h"
)
-# generate version definitions
-configure_file (
- "${PROJECT_SOURCE_DIR}/cmake.config/versiondef.h.in"
- "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef.h"
- )
-
-# generate pathdef.c
-find_program(WHOAMI_PROG whoami)
-find_program(HOSTNAME_PROG hostname)
-
-if (DEFINED ENV{USERNAME})
- set(USERNAME $ENV{USERNAME})
-elseif (NOT DEFINED USERNAME AND EXISTS ${WHOAMI_PROG})
- execute_process(COMMAND ${WHOAMI_PROG}
- OUTPUT_STRIP_TRAILING_WHITESPACE
- OUTPUT_VARIABLE USERNAME)
+# Prevent double space in --version output if CMAKE_C_FLAGS is empty
+set(C_FLAGS_VERSION_OUTPUT ${CMAKE_C_FLAGS})
+if(C_FLAGS_VERSION_OUTPUT)
+ string(PREPEND C_FLAGS_VERSION_OUTPUT " ")
endif()
-if (DEFINED ENV{HOSTNAME})
- set(HOSTNAME $ENV{HOSTNAME})
-elseif (EXISTS ${HOSTNAME_PROG})
- execute_process(COMMAND ${HOSTNAME_PROG}
- OUTPUT_STRIP_TRAILING_WHITESPACE
- OUTPUT_VARIABLE HOSTNAME)
+
+if(${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.15)
+ configure_file(versiondef.h.in auto/versiondef.h.gen)
+else()
+ configure_file(versiondef_old.h.in auto/versiondef.h.gen)
endif()
+file(GENERATE
+ OUTPUT "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef-$<CONFIG>.h"
+ INPUT "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef.h.gen")
+
configure_file (
"${PROJECT_SOURCE_DIR}/cmake.config/pathdef.c.in"
"${PROJECT_BINARY_DIR}/cmake.config/auto/pathdef.c"
diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in
index 4669e42c0f..fb12a7c558 100644
--- a/cmake.config/config.h.in
+++ b/cmake.config/config.h.in
@@ -3,9 +3,7 @@
#cmakedefine SIZEOF_INT @SIZEOF_INT@
#cmakedefine SIZEOF_INTMAX_T @SIZEOF_INTMAX_T@
-#cmakedefine SIZEOF_INT32_T @SIZEOF_INT32_T@
#cmakedefine SIZEOF_LONG @SIZEOF_LONG@
-#cmakedefine SIZEOF_LONG_LONG @SIZEOF_LONG_LONG@
#cmakedefine SIZEOF_SIZE_T @SIZEOF_SIZE_T@
#if @SIZEOF_VOID_PTR@ == 8
@@ -19,23 +17,17 @@
#cmakedefine HAVE__NSGETENVIRON
#cmakedefine HAVE_FD_CLOEXEC
#cmakedefine HAVE_FSEEKO
-#cmakedefine HAVE_GETPWENT
-#cmakedefine HAVE_GETPWNAM
-#cmakedefine HAVE_GETPWUID
#cmakedefine HAVE_LANGINFO_H
-#cmakedefine HAVE_LOCALE_H
#cmakedefine HAVE_NL_LANGINFO_CODESET
#cmakedefine HAVE_NL_MSG_CAT_CNTR
-#cmakedefine HAVE_PWD_H
+#cmakedefine HAVE_PWD_FUNCS
#cmakedefine HAVE_READLINK
-#cmakedefine HAVE_SETPGID
-#cmakedefine HAVE_SETSID
-#cmakedefine HAVE_SIGACTION
#cmakedefine HAVE_STRNLEN
#cmakedefine HAVE_STRCASECMP
#cmakedefine HAVE_STRINGS_H
#cmakedefine HAVE_STRNCASECMP
#cmakedefine HAVE_STRPTIME
+#cmakedefine HAVE_XATTR
#cmakedefine HAVE_SYS_SDT_H
#cmakedefine HAVE_SYS_UTSNAME_H
#cmakedefine HAVE_SYS_WAIT_H
@@ -51,14 +43,9 @@
# undef HAVE_SYS_UIO_H
# endif
#endif
-#cmakedefine HAVE_DIRFD
-#cmakedefine HAVE_FLOCK
+#cmakedefine HAVE_DIRFD_AND_FLOCK
#cmakedefine HAVE_FORKPTY
-#ifndef UNIT_TESTING
-#cmakedefine LOG_LIST_ACTIONS
-#endif
-
#cmakedefine HAVE_BE64TOH
#cmakedefine ORDER_BIG_ENDIAN
#define ENDIAN_INCLUDE_FILE <@ENDIAN_INCLUDE_FILE@>
diff --git a/cmake.config/iwyu/c99.imp b/cmake.config/iwyu/c99.imp
new file mode 100644
index 0000000000..234fc71625
--- /dev/null
+++ b/cmake.config/iwyu/c99.imp
@@ -0,0 +1,1031 @@
+# Symbols defined in the C spec.
+# These are taken from https://port70.net/~nsz/c/c99/n1256.html#B
+
+[
+ # B.1 Diagnostics <assert.h>
+ { symbol: ["NDEBUG", private, "<assert.h>", public ] },
+ { symbol: ["void assert(scalar expression);", private, "<assert.h>", public ] },
+
+ # B.2 Complex <complex.h>
+ { symbol: ["complex", private, "<complex.h>", public ] },
+ { symbol: ["_Complex_I", private, "<complex.h>", public ] },
+ { symbol: ["imaginary", private, "<complex.h>", public ] },
+ { symbol: ["_Imaginary_I", private, "<complex.h>", public ] },
+ { symbol: ["I", private, "<complex.h>", public ] },
+ { symbol: ["cacos", private, "<complex.h>", public ] },
+ { symbol: ["cacosf", private, "<complex.h>", public ] },
+ { symbol: ["cacosl", private, "<complex.h>", public ] },
+ { symbol: ["casin", private, "<complex.h>", public ] },
+ { symbol: ["casinf", private, "<complex.h>", public ] },
+ { symbol: ["casinl", private, "<complex.h>", public ] },
+ { symbol: ["catan", private, "<complex.h>", public ] },
+ { symbol: ["catanf", private, "<complex.h>", public ] },
+ { symbol: ["catanl", private, "<complex.h>", public ] },
+ { symbol: ["ccos", private, "<complex.h>", public ] },
+ { symbol: ["ccosf", private, "<complex.h>", public ] },
+ { symbol: ["ccosl", private, "<complex.h>", public ] },
+ { symbol: ["csin", private, "<complex.h>", public ] },
+ { symbol: ["csinf", private, "<complex.h>", public ] },
+ { symbol: ["csinl", private, "<complex.h>", public ] },
+ { symbol: ["ctan", private, "<complex.h>", public ] },
+ { symbol: ["ctanf", private, "<complex.h>", public ] },
+ { symbol: ["ctanl", private, "<complex.h>", public ] },
+ { symbol: ["cacosh", private, "<complex.h>", public ] },
+ { symbol: ["cacoshf", private, "<complex.h>", public ] },
+ { symbol: ["cacoshl", private, "<complex.h>", public ] },
+ { symbol: ["casinh", private, "<complex.h>", public ] },
+ { symbol: ["casinhf", private, "<complex.h>", public ] },
+ { symbol: ["casinhl", private, "<complex.h>", public ] },
+ { symbol: ["catanh", private, "<complex.h>", public ] },
+ { symbol: ["catanhf", private, "<complex.h>", public ] },
+ { symbol: ["catanhl", private, "<complex.h>", public ] },
+ { symbol: ["ccosh", private, "<complex.h>", public ] },
+ { symbol: ["ccoshf", private, "<complex.h>", public ] },
+ { symbol: ["ccoshl", private, "<complex.h>", public ] },
+ { symbol: ["csinh", private, "<complex.h>", public ] },
+ { symbol: ["csinhf", private, "<complex.h>", public ] },
+ { symbol: ["csinhl", private, "<complex.h>", public ] },
+ { symbol: ["ctanh", private, "<complex.h>", public ] },
+ { symbol: ["ctanhf", private, "<complex.h>", public ] },
+ { symbol: ["ctanhl", private, "<complex.h>", public ] },
+ { symbol: ["cexp", private, "<complex.h>", public ] },
+ { symbol: ["cexpf", private, "<complex.h>", public ] },
+ { symbol: ["cexpl", private, "<complex.h>", public ] },
+ { symbol: ["clog", private, "<complex.h>", public ] },
+ { symbol: ["clogf", private, "<complex.h>", public ] },
+ { symbol: ["clogl", private, "<complex.h>", public ] },
+ { symbol: ["cabs", private, "<complex.h>", public ] },
+ { symbol: ["cabsf", private, "<complex.h>", public ] },
+ { symbol: ["cabsl", private, "<complex.h>", public ] },
+ { symbol: ["cpow", private, "<complex.h>", public ] },
+ { symbol: ["cpowf", private, "<complex.h>", public ] },
+ { symbol: ["cpowl", private, "<complex.h>", public ] },
+ { symbol: ["csqrt", private, "<complex.h>", public ] },
+ { symbol: ["csqrtf", private, "<complex.h>", public ] },
+ { symbol: ["csqrtl", private, "<complex.h>", public ] },
+ { symbol: ["carg", private, "<complex.h>", public ] },
+ { symbol: ["cargf", private, "<complex.h>", public ] },
+ { symbol: ["cargl", private, "<complex.h>", public ] },
+ { symbol: ["cimag", private, "<complex.h>", public ] },
+ { symbol: ["cimagf", private, "<complex.h>", public ] },
+ { symbol: ["cimagl", private, "<complex.h>", public ] },
+ { symbol: ["conj", private, "<complex.h>", public ] },
+ { symbol: ["conjf", private, "<complex.h>", public ] },
+ { symbol: ["conjl", private, "<complex.h>", public ] },
+ { symbol: ["cproj", private, "<complex.h>", public ] },
+ { symbol: ["cprojf", private, "<complex.h>", public ] },
+ { symbol: ["cprojl", private, "<complex.h>", public ] },
+ { symbol: ["creal", private, "<complex.h>", public ] },
+ { symbol: ["crealf", private, "<complex.h>", public ] },
+ { symbol: ["creall", private, "<complex.h>", public ] },
+
+ #B.3 Character handling <ctype.h>
+ { symbol: ["isalnum", private, "<ctype.h>", public ] },
+ { symbol: ["isalpha", private, "<ctype.h>", public ] },
+ { symbol: ["isblank", private, "<ctype.h>", public ] },
+ { symbol: ["iscntrl", private, "<ctype.h>", public ] },
+ { symbol: ["isdigit", private, "<ctype.h>", public ] },
+ { symbol: ["isgraph", private, "<ctype.h>", public ] },
+ { symbol: ["islower", private, "<ctype.h>", public ] },
+ { symbol: ["isprint", private, "<ctype.h>", public ] },
+ { symbol: ["ispunct", private, "<ctype.h>", public ] },
+ { symbol: ["isspace", private, "<ctype.h>", public ] },
+ { symbol: ["isupper", private, "<ctype.h>", public ] },
+ { symbol: ["isxdigit", private, "<ctype.h>", public ] },
+ { symbol: ["tolower", private, "<ctype.h>", public ] },
+ { symbol: ["toupper", private, "<ctype.h>", public ] },
+
+ # B.4 Errors <errno.h>
+ { symbol: ["EDOM", private, "<errno.h>", public ] },
+ { symbol: ["EILSEQ", private, "<errno.h>", public ] },
+ { symbol: ["ERANGE", private, "<errno.h>", public ] },
+ { symbol: ["errno", private, "<errno.h>", public ] },
+
+ # B.5 Floating-point environment <fenv.h>
+ { symbol: ["fenv_t", private, "<fenv.h>", public ] },
+ { symbol: ["fexcept_t", private, "<fenv.h>", public ] },
+ { symbol: ["FE_DIVBYZERO", private, "<fenv.h>", public ] },
+ { symbol: ["FE_INEXACT", private, "<fenv.h>", public ] },
+ { symbol: ["FE_INVALID", private, "<fenv.h>", public ] },
+ { symbol: ["FE_OVERFLOW", private, "<fenv.h>", public ] },
+ { symbol: ["FE_UNDERFLOW", private, "<fenv.h>", public ] },
+ { symbol: ["FE_ALL_EXCEPT", private, "<fenv.h>", public ] },
+ { symbol: ["FE_DOWNWARD", private, "<fenv.h>", public ] },
+ { symbol: ["FE_TONEAREST", private, "<fenv.h>", public ] },
+ { symbol: ["FE_TOWARDZERO", private, "<fenv.h>", public ] },
+ { symbol: ["FE_UPWARD", private, "<fenv.h>", public ] },
+ { symbol: ["FE_DFL_ENV", private, "<fenv.h>", public ] },
+ { symbol: ["feclearexcept", private, "<fenv.h>", public ] },
+ { symbol: ["fegetexceptflag", private, "<fenv.h>", public ] },
+ { symbol: ["feraiseexcept", private, "<fenv.h>", public ] },
+ { symbol: ["fesetexceptflag", private, "<fenv.h>", public ] },
+ { symbol: ["fetestexcept", private, "<fenv.h>", public ] },
+ { symbol: ["fegetround", private, "<fenv.h>", public ] },
+ { symbol: ["fesetround", private, "<fenv.h>", public ] },
+ { symbol: ["fegetenv", private, "<fenv.h>", public ] },
+ { symbol: ["feholdexcept", private, "<fenv.h>", public ] },
+ { symbol: ["fesetenv", private, "<fenv.h>", public ] },
+ { symbol: ["feupdateenv", private, "<fenv.h>", public ] },
+
+ # B.6 Characteristics of floating types <float.h>
+ { symbol: ["FLT_ROUNDS", private, "<float.h>", public ] },
+ { symbol: ["FLT_EVAL_METHOD", private, "<float.h>", public ] },
+ { symbol: ["FLT_RADIX", private, "<float.h>", public ] },
+ { symbol: ["FLT_MANT_DIG", private, "<float.h>", public ] },
+ { symbol: ["DBL_MANT_DIG", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MANT_DIG", private, "<float.h>", public ] },
+ { symbol: ["DECIMAL_DIG", private, "<float.h>", public ] },
+ { symbol: ["FLT_DIG", private, "<float.h>", public ] },
+ { symbol: ["DBL_DIG", private, "<float.h>", public ] },
+ { symbol: ["LDBL_DIG", private, "<float.h>", public ] },
+ { symbol: ["FLT_MIN_EXP", private, "<float.h>", public ] },
+ { symbol: ["DBL_MIN_EXP", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MIN_EXP", private, "<float.h>", public ] },
+ { symbol: ["FLT_MIN_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["DBL_MIN_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MIN_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["FLT_MAX_EXP", private, "<float.h>", public ] },
+ { symbol: ["DBL_MAX_EXP", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MAX_EXP", private, "<float.h>", public ] },
+ { symbol: ["FLT_MAX_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["DBL_MAX_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MAX_10_EXP", private, "<float.h>", public ] },
+ { symbol: ["FLT_MAX", private, "<float.h>", public ] },
+ { symbol: ["DBL_MAX", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MAX", private, "<float.h>", public ] },
+ { symbol: ["FLT_EPSILON", private, "<float.h>", public ] },
+ { symbol: ["DBL_EPSILON", private, "<float.h>", public ] },
+ { symbol: ["LDBL_EPSILON", private, "<float.h>", public ] },
+ { symbol: ["FLT_MIN", private, "<float.h>", public ] },
+ { symbol: ["DBL_MIN", private, "<float.h>", public ] },
+ { symbol: ["LDBL_MIN", private, "<float.h>", public ] },
+
+ # B.7 Format conversion of integer types <inttypes.h>
+ { symbol: ["PRId8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIi8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIo8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIu8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIx8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIX8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNd8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNi8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNo8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNu8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNx8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxLEAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxFAST8", private, "<inttypes.h>", public ] },
+ { symbol: ["PRId16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIi16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIo16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIu16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIx16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIX16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNd16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNi16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNo16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNu16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNx16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxLEAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxFAST16", private, "<inttypes.h>", public ] },
+ { symbol: ["PRId32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIi32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIo32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIu32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIx32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIX32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNd32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNi32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNo32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNu32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNx32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxLEAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxFAST32", private, "<inttypes.h>", public ] },
+ { symbol: ["PRId64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIi64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIo64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIu64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIx64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIX64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNd64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNi64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNo64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNu64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNx64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxLEAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxFAST64", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxMAX", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIdPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIiPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIoPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIuPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIxPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["PRIXPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNdPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNiPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNoPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNuPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["SCNxPTR", private, "<inttypes.h>", public ] },
+ { symbol: ["imaxdiv_t", private, "<inttypes.h>", public ] },
+ { symbol: ["imaxabs", private, "<inttypes.h>", public ] },
+ { symbol: ["imaxdiv", private, "<inttypes.h>", public ] },
+ { symbol: ["strtoimax", private, "<inttypes.h>", public ] },
+ { symbol: ["strtoumax", private, "<inttypes.h>", public ] },
+ { symbol: ["wcstoimax", private, "<inttypes.h>", public ] },
+ { symbol: ["wcstoumax", private, "<inttypes.h>", public ] },
+
+ # B.8 Alternative spellings <iso646.h>
+ { symbol: ["and", private, "<iso646.h>", public ] },
+ { symbol: ["and_eq", private, "<iso646.h>", public ] },
+ { symbol: ["bitand", private, "<iso646.h>", public ] },
+ { symbol: ["bitor", private, "<iso646.h>", public ] },
+ { symbol: ["compl", private, "<iso646.h>", public ] },
+ { symbol: ["not", private, "<iso646.h>", public ] },
+ { symbol: ["not_eq", private, "<iso646.h>", public ] },
+ { symbol: ["or", private, "<iso646.h>", public ] },
+ { symbol: ["or_eq", private, "<iso646.h>", public ] },
+ { symbol: ["xor", private, "<iso646.h>", public ] },
+ { symbol: ["xor_eq", private, "<iso646.h>", public ] },
+
+ # B.9 Sizes of integer types <limits.h>
+ { symbol: ["CHAR_BIT", private, "<limits.h>", public ] },
+ { symbol: ["SCHAR_MIN", private, "<limits.h>", public ] },
+ { symbol: ["SCHAR_MAX", private, "<limits.h>", public ] },
+ { symbol: ["UCHAR_MAX", private, "<limits.h>", public ] },
+ { symbol: ["CHAR_MIN", private, "<limits.h>", public ] },
+ { symbol: ["CHAR_MAX", private, "<limits.h>", public ] },
+ { symbol: ["MB_LEN_MAX", private, "<limits.h>", public ] },
+ { symbol: ["SHRT_MIN", private, "<limits.h>", public ] },
+ { symbol: ["SHRT_MAX", private, "<limits.h>", public ] },
+ { symbol: ["USHRT_MAX", private, "<limits.h>", public ] },
+ { symbol: ["INT_MIN", private, "<limits.h>", public ] },
+ { symbol: ["INT_MAX", private, "<limits.h>", public ] },
+ { symbol: ["UINT_MAX", private, "<limits.h>", public ] },
+ { symbol: ["LONG_MIN", private, "<limits.h>", public ] },
+ { symbol: ["LONG_MAX", private, "<limits.h>", public ] },
+ { symbol: ["ULONG_MAX", private, "<limits.h>", public ] },
+ { symbol: ["LLONG_MIN", private, "<limits.h>", public ] },
+ { symbol: ["LLONG_MAX", private, "<limits.h>", public ] },
+ { symbol: ["ULLONG_MAX", private, "<limits.h>", public ] },
+
+ # B.10 Localization <locale.h>
+ { symbol: ["lconv", private, "<locale.h>", public ] },
+ { symbol: ["NULL", private, "<locale.h>", public ] },
+ { symbol: ["LC_ALL", private, "<locale.h>", public ] },
+ { symbol: ["LC_COLLATE", private, "<locale.h>", public ] },
+ { symbol: ["LC_CTYPE", private, "<locale.h>", public ] },
+ { symbol: ["LC_MONETARY", private, "<locale.h>", public ] },
+ { symbol: ["LC_NUMERIC", private, "<locale.h>", public ] },
+ { symbol: ["LC_TIME", private, "<locale.h>", public ] },
+ { symbol: ["setlocale", private, "<locale.h>", public ] },
+ { symbol: ["localeconv", private, "<locale.h>", public ] },
+
+ # B.11 Mathematics <math.h>
+ { symbol: ["float_t", private, "<math.h>", public ] },
+ { symbol: ["double_t", private, "<math.h>", public ] },
+ { symbol: ["HUGE_VAL", private, "<math.h>", public ] },
+ { symbol: ["HUGE_VALF", private, "<math.h>", public ] },
+ { symbol: ["HUGE_VALL", private, "<math.h>", public ] },
+ { symbol: ["INFINITY", private, "<math.h>", public ] },
+ { symbol: ["NAN", private, "<math.h>", public ] },
+ { symbol: ["FP_INFINITE", private, "<math.h>", public ] },
+ { symbol: ["FP_NAN", private, "<math.h>", public ] },
+ { symbol: ["FP_NORMAL", private, "<math.h>", public ] },
+ { symbol: ["FP_SUBNORMAL", private, "<math.h>", public ] },
+ { symbol: ["FP_ZERO", private, "<math.h>", public ] },
+ { symbol: ["FP_FAST_FMA", private, "<math.h>", public ] },
+ { symbol: ["FP_FAST_FMAF", private, "<math.h>", public ] },
+ { symbol: ["FP_FAST_FMAL", private, "<math.h>", public ] },
+ { symbol: ["FP_ILOGB0", private, "<math.h>", public ] },
+ { symbol: ["FP_ILOGBNAN", private, "<math.h>", public ] },
+ { symbol: ["MATH_ERRNO", private, "<math.h>", public ] },
+ { symbol: ["MATH_ERREXCEPT", private, "<math.h>", public ] },
+ { symbol: ["math_errhandling", private, "<math.h>", public ] },
+ { symbol: ["fpclassify", private, "<math.h>", public ] },
+ { symbol: ["isfinite", private, "<math.h>", public ] },
+ { symbol: ["isinf", private, "<math.h>", public ] },
+ { symbol: ["isnan", private, "<math.h>", public ] },
+ { symbol: ["isnormal", private, "<math.h>", public ] },
+ { symbol: ["signbit", private, "<math.h>", public ] },
+ { symbol: ["acos", private, "<math.h>", public ] },
+ { symbol: ["acosf", private, "<math.h>", public ] },
+ { symbol: ["acosl", private, "<math.h>", public ] },
+ { symbol: ["asin", private, "<math.h>", public ] },
+ { symbol: ["asinf", private, "<math.h>", public ] },
+ { symbol: ["asinl", private, "<math.h>", public ] },
+ { symbol: ["atan", private, "<math.h>", public ] },
+ { symbol: ["atanf", private, "<math.h>", public ] },
+ { symbol: ["atanl", private, "<math.h>", public ] },
+ { symbol: ["atan2", private, "<math.h>", public ] },
+ { symbol: ["atan2f", private, "<math.h>", public ] },
+ { symbol: ["atan2l", private, "<math.h>", public ] },
+ { symbol: ["cos", private, "<math.h>", public ] },
+ { symbol: ["cosf", private, "<math.h>", public ] },
+ { symbol: ["cosl", private, "<math.h>", public ] },
+ { symbol: ["sin", private, "<math.h>", public ] },
+ { symbol: ["sinf", private, "<math.h>", public ] },
+ { symbol: ["sinl", private, "<math.h>", public ] },
+ { symbol: ["tan", private, "<math.h>", public ] },
+ { symbol: ["tanf", private, "<math.h>", public ] },
+ { symbol: ["tanl", private, "<math.h>", public ] },
+ { symbol: ["acosh", private, "<math.h>", public ] },
+ { symbol: ["acoshf", private, "<math.h>", public ] },
+ { symbol: ["acoshl", private, "<math.h>", public ] },
+ { symbol: ["asinh", private, "<math.h>", public ] },
+ { symbol: ["asinhf", private, "<math.h>", public ] },
+ { symbol: ["asinhl", private, "<math.h>", public ] },
+ { symbol: ["atanh", private, "<math.h>", public ] },
+ { symbol: ["atanhf", private, "<math.h>", public ] },
+ { symbol: ["atanhl", private, "<math.h>", public ] },
+ { symbol: ["cosh", private, "<math.h>", public ] },
+ { symbol: ["coshf", private, "<math.h>", public ] },
+ { symbol: ["coshl", private, "<math.h>", public ] },
+ { symbol: ["sinh", private, "<math.h>", public ] },
+ { symbol: ["sinhf", private, "<math.h>", public ] },
+ { symbol: ["sinhl", private, "<math.h>", public ] },
+ { symbol: ["tanh", private, "<math.h>", public ] },
+ { symbol: ["tanhf", private, "<math.h>", public ] },
+ { symbol: ["tanhl", private, "<math.h>", public ] },
+ { symbol: ["exp", private, "<math.h>", public ] },
+ { symbol: ["expf", private, "<math.h>", public ] },
+ { symbol: ["expl", private, "<math.h>", public ] },
+ { symbol: ["exp2", private, "<math.h>", public ] },
+ { symbol: ["exp2f", private, "<math.h>", public ] },
+ { symbol: ["exp2l", private, "<math.h>", public ] },
+ { symbol: ["expm1", private, "<math.h>", public ] },
+ { symbol: ["expm1f", private, "<math.h>", public ] },
+ { symbol: ["expm1l", private, "<math.h>", public ] },
+ { symbol: ["frexp", private, "<math.h>", public ] },
+ { symbol: ["frexpf", private, "<math.h>", public ] },
+ { symbol: ["frexpl", private, "<math.h>", public ] },
+ { symbol: ["ilogb", private, "<math.h>", public ] },
+ { symbol: ["ilogbf", private, "<math.h>", public ] },
+ { symbol: ["ilogbl", private, "<math.h>", public ] },
+ { symbol: ["ldexp", private, "<math.h>", public ] },
+ { symbol: ["ldexpf", private, "<math.h>", public ] },
+ { symbol: ["ldexpl", private, "<math.h>", public ] },
+ { symbol: ["log", private, "<math.h>", public ] },
+ { symbol: ["logf", private, "<math.h>", public ] },
+ { symbol: ["logl", private, "<math.h>", public ] },
+ { symbol: ["log10", private, "<math.h>", public ] },
+ { symbol: ["log10f", private, "<math.h>", public ] },
+ { symbol: ["log10l", private, "<math.h>", public ] },
+ { symbol: ["log1p", private, "<math.h>", public ] },
+ { symbol: ["log1pf", private, "<math.h>", public ] },
+ { symbol: ["log1pl", private, "<math.h>", public ] },
+ { symbol: ["log2", private, "<math.h>", public ] },
+ { symbol: ["log2f", private, "<math.h>", public ] },
+ { symbol: ["log2l", private, "<math.h>", public ] },
+ { symbol: ["logb", private, "<math.h>", public ] },
+ { symbol: ["logbf", private, "<math.h>", public ] },
+ { symbol: ["logbl", private, "<math.h>", public ] },
+ { symbol: ["modf", private, "<math.h>", public ] },
+ { symbol: ["modff", private, "<math.h>", public ] },
+ { symbol: ["modfl", private, "<math.h>", public ] },
+ { symbol: ["scalbn", private, "<math.h>", public ] },
+ { symbol: ["scalbnf", private, "<math.h>", public ] },
+ { symbol: ["scalbnl", private, "<math.h>", public ] },
+ { symbol: ["scalbln", private, "<math.h>", public ] },
+ { symbol: ["scalblnf", private, "<math.h>", public ] },
+ { symbol: ["scalblnl", private, "<math.h>", public ] },
+ { symbol: ["cbrt", private, "<math.h>", public ] },
+ { symbol: ["cbrtf", private, "<math.h>", public ] },
+ { symbol: ["cbrtl", private, "<math.h>", public ] },
+ { symbol: ["fabs", private, "<math.h>", public ] },
+ { symbol: ["fabsf", private, "<math.h>", public ] },
+ { symbol: ["fabsl", private, "<math.h>", public ] },
+ { symbol: ["hypot", private, "<math.h>", public ] },
+ { symbol: ["hypotf", private, "<math.h>", public ] },
+ { symbol: ["hypotl", private, "<math.h>", public ] },
+ { symbol: ["pow", private, "<math.h>", public ] },
+ { symbol: ["powf", private, "<math.h>", public ] },
+ { symbol: ["powl", private, "<math.h>", public ] },
+ { symbol: ["sqrt", private, "<math.h>", public ] },
+ { symbol: ["sqrtf", private, "<math.h>", public ] },
+ { symbol: ["sqrtl", private, "<math.h>", public ] },
+ { symbol: ["erf", private, "<math.h>", public ] },
+ { symbol: ["erff", private, "<math.h>", public ] },
+ { symbol: ["erfl", private, "<math.h>", public ] },
+ { symbol: ["erfc", private, "<math.h>", public ] },
+ { symbol: ["erfcf", private, "<math.h>", public ] },
+ { symbol: ["erfcl", private, "<math.h>", public ] },
+ { symbol: ["lgamma", private, "<math.h>", public ] },
+ { symbol: ["lgammaf", private, "<math.h>", public ] },
+ { symbol: ["lgammal", private, "<math.h>", public ] },
+ { symbol: ["tgamma", private, "<math.h>", public ] },
+ { symbol: ["tgammaf", private, "<math.h>", public ] },
+ { symbol: ["tgammal", private, "<math.h>", public ] },
+ { symbol: ["ceil", private, "<math.h>", public ] },
+ { symbol: ["ceilf", private, "<math.h>", public ] },
+ { symbol: ["ceill", private, "<math.h>", public ] },
+ { symbol: ["floor", private, "<math.h>", public ] },
+ { symbol: ["floorf", private, "<math.h>", public ] },
+ { symbol: ["floorl", private, "<math.h>", public ] },
+ { symbol: ["nearbyint", private, "<math.h>", public ] },
+ { symbol: ["nearbyintf", private, "<math.h>", public ] },
+ { symbol: ["nearbyintl", private, "<math.h>", public ] },
+ { symbol: ["rint", private, "<math.h>", public ] },
+ { symbol: ["rintf", private, "<math.h>", public ] },
+ { symbol: ["rintl", private, "<math.h>", public ] },
+ { symbol: ["lrint", private, "<math.h>", public ] },
+ { symbol: ["lrintf", private, "<math.h>", public ] },
+ { symbol: ["lrintl", private, "<math.h>", public ] },
+ { symbol: ["llrint", private, "<math.h>", public ] },
+ { symbol: ["llrintf", private, "<math.h>", public ] },
+ { symbol: ["llrintl", private, "<math.h>", public ] },
+ { symbol: ["round", private, "<math.h>", public ] },
+ { symbol: ["roundf", private, "<math.h>", public ] },
+ { symbol: ["roundl", private, "<math.h>", public ] },
+ { symbol: ["lround", private, "<math.h>", public ] },
+ { symbol: ["lroundf", private, "<math.h>", public ] },
+ { symbol: ["lroundl", private, "<math.h>", public ] },
+ { symbol: ["llround", private, "<math.h>", public ] },
+ { symbol: ["llroundf", private, "<math.h>", public ] },
+ { symbol: ["llroundl", private, "<math.h>", public ] },
+ { symbol: ["trunc", private, "<math.h>", public ] },
+ { symbol: ["truncf", private, "<math.h>", public ] },
+ { symbol: ["truncl", private, "<math.h>", public ] },
+ { symbol: ["fmod", private, "<math.h>", public ] },
+ { symbol: ["fmodf", private, "<math.h>", public ] },
+ { symbol: ["fmodl", private, "<math.h>", public ] },
+ { symbol: ["remainder", private, "<math.h>", public ] },
+ { symbol: ["remainderf", private, "<math.h>", public ] },
+ { symbol: ["remainderl", private, "<math.h>", public ] },
+ { symbol: ["remquo", private, "<math.h>", public ] },
+ { symbol: ["remquof", private, "<math.h>", public ] },
+ { symbol: ["remquol", private, "<math.h>", public ] },
+ { symbol: ["copysign", private, "<math.h>", public ] },
+ { symbol: ["copysignf", private, "<math.h>", public ] },
+ { symbol: ["copysignl", private, "<math.h>", public ] },
+ { symbol: ["nan", private, "<math.h>", public ] },
+ { symbol: ["nanf", private, "<math.h>", public ] },
+ { symbol: ["nanl", private, "<math.h>", public ] },
+ { symbol: ["nextafter", private, "<math.h>", public ] },
+ { symbol: ["nextafterf", private, "<math.h>", public ] },
+ { symbol: ["nextafterl", private, "<math.h>", public ] },
+ { symbol: ["nexttoward", private, "<math.h>", public ] },
+ { symbol: ["nexttowardf", private, "<math.h>", public ] },
+ { symbol: ["nexttowardl", private, "<math.h>", public ] },
+ { symbol: ["fdim", private, "<math.h>", public ] },
+ { symbol: ["fdimf", private, "<math.h>", public ] },
+ { symbol: ["fdiml", private, "<math.h>", public ] },
+ { symbol: ["fmax", private, "<math.h>", public ] },
+ { symbol: ["fmaxf", private, "<math.h>", public ] },
+ { symbol: ["fmaxl", private, "<math.h>", public ] },
+ { symbol: ["fmin", private, "<math.h>", public ] },
+ { symbol: ["fminf", private, "<math.h>", public ] },
+ { symbol: ["fminl", private, "<math.h>", public ] },
+ { symbol: ["fma", private, "<math.h>", public ] },
+ { symbol: ["fmaf", private, "<math.h>", public ] },
+ { symbol: ["fmal", private, "<math.h>", public ] },
+ { symbol: ["isgreater", private, "<math.h>", public ] },
+ { symbol: ["isgreaterequal", private, "<math.h>", public ] },
+ { symbol: ["isless", private, "<math.h>", public ] },
+ { symbol: ["islessequal", private, "<math.h>", public ] },
+ { symbol: ["islessgreater", private, "<math.h>", public ] },
+ { symbol: ["isunordered", private, "<math.h>", public ] },
+
+ # B.12 Nonlocal jumps <setjmp.h>
+ { symbol: ["jmp_buf", private, "<setjmp.h>", public ] },
+ { symbol: ["setjmp", private, "<setjmp.h>", public ] },
+ { symbol: ["longjmp", private, "<setjmp.h>", public ] },
+
+ # B.13 Signal handling <signal.h>
+ { symbol: ["sig_atomic_t", private, "<signal.h>", public ] },
+ { symbol: ["SIG_DFL", private, "<signal.h>", public ] },
+ { symbol: ["SIG_ERR", private, "<signal.h>", public ] },
+ { symbol: ["SIG_IGN", private, "<signal.h>", public ] },
+ { symbol: ["SIGABRT", private, "<signal.h>", public ] },
+ { symbol: ["SIGFPE", private, "<signal.h>", public ] },
+ { symbol: ["SIGILL", private, "<signal.h>", public ] },
+ { symbol: ["SIGINT", private, "<signal.h>", public ] },
+ { symbol: ["SIGSEGV", private, "<signal.h>", public ] },
+ { symbol: ["SIGTERM", private, "<signal.h>", public ] },
+ { symbol: ["signal", private, "<signal.h>", public ] },
+ { symbol: ["raise", private, "<signal.h>", public ] },
+
+ # B.14 Variable arguments <stdarg.h>
+ { symbol: ["va_list", private, "<stdarg.h>", public ] },
+ { symbol: ["va_arg", private, "<stdarg.h>", public ] },
+ { symbol: ["va_copy", private, "<stdarg.h>", public ] },
+ { symbol: ["va_end", private, "<stdarg.h>", public ] },
+ { symbol: ["va_start", private, "<stdarg.h>", public ] },
+
+ # B.15 Boolean type and values <stdbool.h>
+ { symbol: ["bool", private, "<stdbool.h>", public ] },
+ { symbol: ["true", private, "<stdbool.h>", public ] },
+ { symbol: ["false", private, "<stdbool.h>", public ] },
+ { symbol: ["__bool_true_false_are_defined", private, "<stdbool.h>", public ] },
+
+ # B.16 Common definitions <stddef.h>
+ { symbol: ["ptrdiff_t", private, "<stddef.h>", public ] },
+ { symbol: ["size_t", private, "<stddef.h>", public ] },
+ { symbol: ["wchar_t", private, "<stddef.h>", public ] },
+ { symbol: ["NULL", private, "<stddef.h>", public ] },
+ { symbol: ["offsetof", private, "<stddef.h>", public ] },
+
+ # B.17 Integer types <stdint.h>
+ { symbol: ["int8_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint8_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_least8_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_least8_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_fast8_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_fast8_t", private, "<stdint.h>", public ] },
+ { symbol: ["INT8_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST8_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_LEAST8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST8_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_FAST8_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT8_C", private, "<stdint.h>", public ] },
+ { symbol: ["UINT8_C", private, "<stdint.h>", public ] },
+ { symbol: ["int16_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint16_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_least16_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_least16_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_fast16_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_fast16_t", private, "<stdint.h>", public ] },
+ { symbol: ["INT16_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST16_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_LEAST16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST16_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_FAST16_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT16_C", private, "<stdint.h>", public ] },
+ { symbol: ["UINT16_C", private, "<stdint.h>", public ] },
+ { symbol: ["int32_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint32_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_least32_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_least32_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_fast32_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_fast32_t", private, "<stdint.h>", public ] },
+ { symbol: ["INT32_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST32_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_LEAST32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST32_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_FAST32_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT32_C", private, "<stdint.h>", public ] },
+ { symbol: ["UINT32_C", private, "<stdint.h>", public ] },
+ { symbol: ["int64_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint64_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_least64_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_least64_t", private, "<stdint.h>", public ] },
+ { symbol: ["int_fast64_t", private, "<stdint.h>", public ] },
+ { symbol: ["uint_fast64_t", private, "<stdint.h>", public ] },
+ { symbol: ["INT64_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST64_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_LEAST64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_LEAST64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST64_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INT_FAST64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINT_FAST64_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INT64_C", private, "<stdint.h>", public ] },
+ { symbol: ["UINT64_C", private, "<stdint.h>", public ] },
+ { symbol: ["intptr_t", private, "<stdint.h>", public ] },
+ { symbol: ["uintptr_t", private, "<stdint.h>", public ] },
+ { symbol: ["intmax_t", private, "<stdint.h>", public ] },
+ { symbol: ["uintmax_t", private, "<stdint.h>", public ] },
+ { symbol: ["INTPTR_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INTPTR_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINTPTR_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INTMAX_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["INTMAX_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["UINTMAX_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["PTRDIFF_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["PTRDIFF_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["SIG_ATOMIC_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["SIG_ATOMIC_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["SIZE_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["WCHAR_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["WCHAR_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["WINT_MIN", private, "<stdint.h>", public ] },
+ { symbol: ["WINT_MAX", private, "<stdint.h>", public ] },
+ { symbol: ["INTMAX_C", private, "<stdint.h>", public ] },
+ { symbol: ["UINTMAX_C", private, "<stdint.h>", public ] },
+
+ # B.18 Input/output <stdio.h>
+ { symbol: ["size_t", private, "<stdio.h>", public ] },
+ { symbol: ["FILE", private, "<stdio.h>", public ] },
+ { symbol: ["fpos_t", private, "<stdio.h>", public ] },
+ { symbol: ["NULL", private, "<stdio.h>", public ] },
+ { symbol: ["_IOFBF", private, "<stdio.h>", public ] },
+ { symbol: ["_IOLBF", private, "<stdio.h>", public ] },
+ { symbol: ["_IONBF", private, "<stdio.h>", public ] },
+ { symbol: ["BUFSIZ", private, "<stdio.h>", public ] },
+ { symbol: ["EOF", private, "<stdio.h>", public ] },
+ { symbol: ["FOPEN_MAX", private, "<stdio.h>", public ] },
+ { symbol: ["FILENAME_MAX", private, "<stdio.h>", public ] },
+ { symbol: ["L_tmpnam", private, "<stdio.h>", public ] },
+ { symbol: ["SEEK_CUR", private, "<stdio.h>", public ] },
+ { symbol: ["SEEK_END", private, "<stdio.h>", public ] },
+ { symbol: ["SEEK_SET", private, "<stdio.h>", public ] },
+ { symbol: ["TMP_MAX", private, "<stdio.h>", public ] },
+ { symbol: ["stderr", private, "<stdio.h>", public ] },
+ { symbol: ["stdin", private, "<stdio.h>", public ] },
+ { symbol: ["stdout", private, "<stdio.h>", public ] },
+ { symbol: ["remove", private, "<stdio.h>", public ] },
+ { symbol: ["rename", private, "<stdio.h>", public ] },
+ { symbol: ["tmpfile", private, "<stdio.h>", public ] },
+ { symbol: ["tmpnam", private, "<stdio.h>", public ] },
+ { symbol: ["fclose", private, "<stdio.h>", public ] },
+ { symbol: ["fflush", private, "<stdio.h>", public ] },
+ { symbol: ["fopen", private, "<stdio.h>", public ] },
+ { symbol: ["freopen", private, "<stdio.h>", public ] },
+ { symbol: ["setbuf", private, "<stdio.h>", public ] },
+ { symbol: ["setvbuf", private, "<stdio.h>", public ] },
+ { symbol: ["fprintf", private, "<stdio.h>", public ] },
+ { symbol: ["fscanf", private, "<stdio.h>", public ] },
+ { symbol: ["printf", private, "<stdio.h>", public ] },
+ { symbol: ["scanf", private, "<stdio.h>", public ] },
+ { symbol: ["snprintf", private, "<stdio.h>", public ] },
+ { symbol: ["sprintf", private, "<stdio.h>", public ] },
+ { symbol: ["sscanf", private, "<stdio.h>", public ] },
+ { symbol: ["vfprintf", private, "<stdio.h>", public ] },
+ { symbol: ["vfscanf", private, "<stdio.h>", public ] },
+ { symbol: ["vprintf", private, "<stdio.h>", public ] },
+ { symbol: ["vscanf", private, "<stdio.h>", public ] },
+ { symbol: ["vsnprintf", private, "<stdio.h>", public ] },
+ { symbol: ["vsprintf", private, "<stdio.h>", public ] },
+ { symbol: ["vsscanf", private, "<stdio.h>", public ] },
+ { symbol: ["fgetc", private, "<stdio.h>", public ] },
+ { symbol: ["fgets", private, "<stdio.h>", public ] },
+ { symbol: ["fputc", private, "<stdio.h>", public ] },
+ { symbol: ["fputs", private, "<stdio.h>", public ] },
+ { symbol: ["getc", private, "<stdio.h>", public ] },
+ { symbol: ["getchar", private, "<stdio.h>", public ] },
+ { symbol: ["gets", private, "<stdio.h>", public ] },
+ { symbol: ["putc", private, "<stdio.h>", public ] },
+ { symbol: ["putchar", private, "<stdio.h>", public ] },
+ { symbol: ["puts", private, "<stdio.h>", public ] },
+ { symbol: ["ungetc", private, "<stdio.h>", public ] },
+ { symbol: ["fread", private, "<stdio.h>", public ] },
+ { symbol: ["fwrite", private, "<stdio.h>", public ] },
+ { symbol: ["fgetpos", private, "<stdio.h>", public ] },
+ { symbol: ["fseek", private, "<stdio.h>", public ] },
+ { symbol: ["fsetpos", private, "<stdio.h>", public ] },
+ { symbol: ["ftell", private, "<stdio.h>", public ] },
+ { symbol: ["rewind", private, "<stdio.h>", public ] },
+ { symbol: ["clearerr", private, "<stdio.h>", public ] },
+ { symbol: ["feof", private, "<stdio.h>", public ] },
+ { symbol: ["ferror", private, "<stdio.h>", public ] },
+ { symbol: ["perror", private, "<stdio.h>", public ] },
+
+ # B.19 General utilities <stdlib.h>
+ { symbol: ["size_t", private, "<stdlib.h>", public ] },
+ { symbol: ["wchar_t", private, "<stdlib.h>", public ] },
+ { symbol: ["div_t", private, "<stdlib.h>", public ] },
+ { symbol: ["ldiv_t", private, "<stdlib.h>", public ] },
+ { symbol: ["lldiv_t", private, "<stdlib.h>", public ] },
+ { symbol: ["NULL", private, "<stdlib.h>", public ] },
+ { symbol: ["EXIT_FAILURE", private, "<stdlib.h>", public ] },
+ { symbol: ["EXIT_SUCCESS", private, "<stdlib.h>", public ] },
+ { symbol: ["RAND_MAX", private, "<stdlib.h>", public ] },
+ { symbol: ["MB_CUR_MAX", private, "<stdlib.h>", public ] },
+ { symbol: ["atof", private, "<stdlib.h>", public ] },
+ { symbol: ["atoi", private, "<stdlib.h>", public ] },
+ { symbol: ["atol", private, "<stdlib.h>", public ] },
+ { symbol: ["atoll", private, "<stdlib.h>", public ] },
+ { symbol: ["strtod", private, "<stdlib.h>", public ] },
+ { symbol: ["strtof", private, "<stdlib.h>", public ] },
+ { symbol: ["strtold", private, "<stdlib.h>", public ] },
+ { symbol: ["strtol", private, "<stdlib.h>", public ] },
+ { symbol: ["strtoll", private, "<stdlib.h>", public ] },
+ { symbol: ["strtoul", private, "<stdlib.h>", public ] },
+ { symbol: ["strtoull", private, "<stdlib.h>", public ] },
+ { symbol: ["rand", private, "<stdlib.h>", public ] },
+ { symbol: ["srand", private, "<stdlib.h>", public ] },
+ { symbol: ["calloc", private, "<stdlib.h>", public ] },
+ { symbol: ["free", private, "<stdlib.h>", public ] },
+ { symbol: ["malloc", private, "<stdlib.h>", public ] },
+ { symbol: ["realloc", private, "<stdlib.h>", public ] },
+ { symbol: ["abort", private, "<stdlib.h>", public ] },
+ { symbol: ["atexit", private, "<stdlib.h>", public ] },
+ { symbol: ["exit", private, "<stdlib.h>", public ] },
+ { symbol: ["_Exit", private, "<stdlib.h>", public ] },
+ { symbol: ["getenv", private, "<stdlib.h>", public ] },
+ { symbol: ["system", private, "<stdlib.h>", public ] },
+ { symbol: ["bsearch", private, "<stdlib.h>", public ] },
+ { symbol: ["qsort", private, "<stdlib.h>", public ] },
+ { symbol: ["abs", private, "<stdlib.h>", public ] },
+ { symbol: ["labs", private, "<stdlib.h>", public ] },
+ { symbol: ["llabs", private, "<stdlib.h>", public ] },
+ { symbol: ["div", private, "<stdlib.h>", public ] },
+ { symbol: ["ldiv", private, "<stdlib.h>", public ] },
+ { symbol: ["lldiv", private, "<stdlib.h>", public ] },
+ { symbol: ["mblen", private, "<stdlib.h>", public ] },
+ { symbol: ["mbtowc", private, "<stdlib.h>", public ] },
+ { symbol: ["wctomb", private, "<stdlib.h>", public ] },
+ { symbol: ["mbstowcs", private, "<stdlib.h>", public ] },
+ { symbol: ["wcstombs", private, "<stdlib.h>", public ] },
+
+ # B.20 String handling <string.h>
+ { symbol: ["size_t", private, "<string.h>", public ] },
+ { symbol: ["NULL", private, "<string.h>", public ] },
+ { symbol: ["memcpy", private, "<string.h>", public ] },
+ { symbol: ["memmove", private, "<string.h>", public ] },
+ { symbol: ["strcpy", private, "<string.h>", public ] },
+ { symbol: ["strncpy", private, "<string.h>", public ] },
+ { symbol: ["strcat", private, "<string.h>", public ] },
+ { symbol: ["strncat", private, "<string.h>", public ] },
+ { symbol: ["memcmp", private, "<string.h>", public ] },
+ { symbol: ["strcmp", private, "<string.h>", public ] },
+ { symbol: ["strcoll", private, "<string.h>", public ] },
+ { symbol: ["strncmp", private, "<string.h>", public ] },
+ { symbol: ["strxfrm", private, "<string.h>", public ] },
+ { symbol: ["memchr", private, "<string.h>", public ] },
+ { symbol: ["strchr", private, "<string.h>", public ] },
+ { symbol: ["strcspn", private, "<string.h>", public ] },
+ { symbol: ["strpbrk", private, "<string.h>", public ] },
+ { symbol: ["strrchr", private, "<string.h>", public ] },
+ { symbol: ["strspn", private, "<string.h>", public ] },
+ { symbol: ["strstr", private, "<string.h>", public ] },
+ { symbol: ["strtok", private, "<string.h>", public ] },
+ { symbol: ["memset", private, "<string.h>", public ] },
+ { symbol: ["strerror", private, "<string.h>", public ] },
+ { symbol: ["strlen", private, "<string.h>", public ] },
+
+ # B.21 Type-generic math <tgmath.h>
+ { symbol: ["acos", private, "<tgmath.h>", public ] },
+ { symbol: ["asin", private, "<tgmath.h>", public ] },
+ { symbol: ["atan", private, "<tgmath.h>", public ] },
+ { symbol: ["acosh", private, "<tgmath.h>", public ] },
+ { symbol: ["asinh", private, "<tgmath.h>", public ] },
+ { symbol: ["atanh", private, "<tgmath.h>", public ] },
+ { symbol: ["cos", private, "<tgmath.h>", public ] },
+ { symbol: ["sin", private, "<tgmath.h>", public ] },
+ { symbol: ["tan", private, "<tgmath.h>", public ] },
+ { symbol: ["cosh", private, "<tgmath.h>", public ] },
+ { symbol: ["sinh", private, "<tgmath.h>", public ] },
+ { symbol: ["tanh", private, "<tgmath.h>", public ] },
+ { symbol: ["exp", private, "<tgmath.h>", public ] },
+ { symbol: ["log", private, "<tgmath.h>", public ] },
+ { symbol: ["pow", private, "<tgmath.h>", public ] },
+ { symbol: ["sqrt", private, "<tgmath.h>", public ] },
+ { symbol: ["fabs", private, "<tgmath.h>", public ] },
+ { symbol: ["atan2", private, "<tgmath.h>", public ] },
+ { symbol: ["cbrt", private, "<tgmath.h>", public ] },
+ { symbol: ["ceil", private, "<tgmath.h>", public ] },
+ { symbol: ["copysign", private, "<tgmath.h>", public ] },
+ { symbol: ["erf", private, "<tgmath.h>", public ] },
+ { symbol: ["erfc", private, "<tgmath.h>", public ] },
+ { symbol: ["exp2", private, "<tgmath.h>", public ] },
+ { symbol: ["expm1", private, "<tgmath.h>", public ] },
+ { symbol: ["fdim", private, "<tgmath.h>", public ] },
+ { symbol: ["floor", private, "<tgmath.h>", public ] },
+ { symbol: ["fma", private, "<tgmath.h>", public ] },
+ { symbol: ["fmax", private, "<tgmath.h>", public ] },
+ { symbol: ["fmin", private, "<tgmath.h>", public ] },
+ { symbol: ["fmod", private, "<tgmath.h>", public ] },
+ { symbol: ["frexp", private, "<tgmath.h>", public ] },
+ { symbol: ["hypot", private, "<tgmath.h>", public ] },
+ { symbol: ["ilogb", private, "<tgmath.h>", public ] },
+ { symbol: ["ldexp", private, "<tgmath.h>", public ] },
+ { symbol: ["lgamma", private, "<tgmath.h>", public ] },
+ { symbol: ["llrint", private, "<tgmath.h>", public ] },
+ { symbol: ["llround", private, "<tgmath.h>", public ] },
+ { symbol: ["log10", private, "<tgmath.h>", public ] },
+ { symbol: ["log1p", private, "<tgmath.h>", public ] },
+ { symbol: ["log2", private, "<tgmath.h>", public ] },
+ { symbol: ["logb", private, "<tgmath.h>", public ] },
+ { symbol: ["lrint", private, "<tgmath.h>", public ] },
+ { symbol: ["lround", private, "<tgmath.h>", public ] },
+ { symbol: ["nearbyint", private, "<tgmath.h>", public ] },
+ { symbol: ["nextafter", private, "<tgmath.h>", public ] },
+ { symbol: ["nexttoward", private, "<tgmath.h>", public ] },
+ { symbol: ["remainder", private, "<tgmath.h>", public ] },
+ { symbol: ["remquo", private, "<tgmath.h>", public ] },
+ { symbol: ["rint", private, "<tgmath.h>", public ] },
+ { symbol: ["round", private, "<tgmath.h>", public ] },
+ { symbol: ["scalbn", private, "<tgmath.h>", public ] },
+ { symbol: ["scalbln", private, "<tgmath.h>", public ] },
+ { symbol: ["tgamma", private, "<tgmath.h>", public ] },
+ { symbol: ["trunc", private, "<tgmath.h>", public ] },
+ { symbol: ["carg", private, "<tgmath.h>", public ] },
+ { symbol: ["cimag", private, "<tgmath.h>", public ] },
+ { symbol: ["conj", private, "<tgmath.h>", public ] },
+ { symbol: ["cproj", private, "<tgmath.h>", public ] },
+ { symbol: ["creal", private, "<tgmath.h>", public ] },
+
+ # B.22 Date and time <time.h>
+ { symbol: ["NULL", private, "<time.h>", public ] },
+ { symbol: ["CLOCKS_PER_SEC", private, "<time.h>", public ] },
+ { symbol: ["size_t", private, "<time.h>", public ] },
+ { symbol: ["clock_t", private, "<time.h>", public ] },
+ { symbol: ["time_t", private, "<time.h>", public ] },
+ { symbol: ["tm", private, "<time.h>", public ] },
+ { symbol: ["clock", private, "<time.h>", public ] },
+ { symbol: ["difftime", private, "<time.h>", public ] },
+ { symbol: ["mktime", private, "<time.h>", public ] },
+ { symbol: ["time", private, "<time.h>", public ] },
+ { symbol: ["asctime", private, "<time.h>", public ] },
+ { symbol: ["ctime", private, "<time.h>", public ] },
+ { symbol: ["gmtime", private, "<time.h>", public ] },
+ { symbol: ["localtime", private, "<time.h>", public ] },
+ { symbol: ["strftime", private, "<time.h>", public ] },
+
+ # B.23 Extended multibyte/wide character utilities <wchar.h>
+ { symbol: ["wchar_t", private, "<wchar.h>", public ] },
+ { symbol: ["wint_t", private, "<wchar.h>", public ] },
+ { symbol: ["WCHAR_MAX", private, "<wchar.h>", public ] },
+ { symbol: ["size_t", private, "<wchar.h>", public ] },
+ { symbol: ["tm", private, "<wchar.h>", public ] },
+ { symbol: ["WCHAR_MIN", private, "<wchar.h>", public ] },
+ { symbol: ["mbstate_t", private, "<wchar.h>", public ] },
+ { symbol: ["NULL", private, "<wchar.h>", public ] },
+ { symbol: ["WEOF", private, "<wchar.h>", public ] },
+ { symbol: ["fwprintf", private, "<wchar.h>", public ] },
+ { symbol: ["fwscanf", private, "<wchar.h>", public ] },
+ { symbol: ["swprintf", private, "<wchar.h>", public ] },
+ { symbol: ["swscanf", private, "<wchar.h>", public ] },
+ { symbol: ["vfwprintf", private, "<wchar.h>", public ] },
+ { symbol: ["vfwscanf", private, "<wchar.h>", public ] },
+ { symbol: ["vswprintf", private, "<wchar.h>", public ] },
+ { symbol: ["vswscanf", private, "<wchar.h>", public ] },
+ { symbol: ["vwprintf", private, "<wchar.h>", public ] },
+ { symbol: ["vwscanf", private, "<wchar.h>", public ] },
+ { symbol: ["wprintf", private, "<wchar.h>", public ] },
+ { symbol: ["wscanf", private, "<wchar.h>", public ] },
+ { symbol: ["fgetwc", private, "<wchar.h>", public ] },
+ { symbol: ["fgetws", private, "<wchar.h>", public ] },
+ { symbol: ["fputwc", private, "<wchar.h>", public ] },
+ { symbol: ["fputws", private, "<wchar.h>", public ] },
+ { symbol: ["fwide", private, "<wchar.h>", public ] },
+ { symbol: ["getwc", private, "<wchar.h>", public ] },
+ { symbol: ["getwchar", private, "<wchar.h>", public ] },
+ { symbol: ["putwc", private, "<wchar.h>", public ] },
+ { symbol: ["putwchar", private, "<wchar.h>", public ] },
+ { symbol: ["ungetwc", private, "<wchar.h>", public ] },
+ { symbol: ["wcstod", private, "<wchar.h>", public ] },
+ { symbol: ["wcstof", private, "<wchar.h>", public ] },
+ { symbol: ["wcstold", private, "<wchar.h>", public ] },
+ { symbol: ["wcstol", private, "<wchar.h>", public ] },
+ { symbol: ["wcstoll", private, "<wchar.h>", public ] },
+ { symbol: ["wcstoul", private, "<wchar.h>", public ] },
+ { symbol: ["wcstoull", private, "<wchar.h>", public ] },
+ { symbol: ["wcscpy", private, "<wchar.h>", public ] },
+ { symbol: ["wcsncpy", private, "<wchar.h>", public ] },
+ { symbol: ["wmemcpy", private, "<wchar.h>", public ] },
+ { symbol: ["wmemmove", private, "<wchar.h>", public ] },
+ { symbol: ["wcscat", private, "<wchar.h>", public ] },
+ { symbol: ["wcsncat", private, "<wchar.h>", public ] },
+ { symbol: ["wcscmp", private, "<wchar.h>", public ] },
+ { symbol: ["wcscoll", private, "<wchar.h>", public ] },
+ { symbol: ["wcsncmp", private, "<wchar.h>", public ] },
+ { symbol: ["wcsxfrm", private, "<wchar.h>", public ] },
+ { symbol: ["wmemcmp", private, "<wchar.h>", public ] },
+ { symbol: ["wcschr", private, "<wchar.h>", public ] },
+ { symbol: ["wcscspn", private, "<wchar.h>", public ] },
+ { symbol: ["wcspbrk", private, "<wchar.h>", public ] },
+ { symbol: ["wcsrchr", private, "<wchar.h>", public ] },
+ { symbol: ["wcsspn", private, "<wchar.h>", public ] },
+ { symbol: ["wcsstr", private, "<wchar.h>", public ] },
+ { symbol: ["wcstok", private, "<wchar.h>", public ] },
+ { symbol: ["wmemchr", private, "<wchar.h>", public ] },
+ { symbol: ["wcslen", private, "<wchar.h>", public ] },
+ { symbol: ["wmemset", private, "<wchar.h>", public ] },
+ { symbol: ["wcsftime", private, "<wchar.h>", public ] },
+ { symbol: ["btowc", private, "<wchar.h>", public ] },
+ { symbol: ["wctob", private, "<wchar.h>", public ] },
+ { symbol: ["mbsinit", private, "<wchar.h>", public ] },
+ { symbol: ["mbrlen", private, "<wchar.h>", public ] },
+ { symbol: ["mbrtowc", private, "<wchar.h>", public ] },
+ { symbol: ["wcrtomb", private, "<wchar.h>", public ] },
+ { symbol: ["mbsrtowcs", private, "<wchar.h>", public ] },
+ { symbol: ["wcsrtombs", private, "<wchar.h>", public ] },
+
+ # B.24 Wide character classification and mapping utilities <wctype.h>
+ { symbol: ["wint_t", private, "<wctype.h>", public ] },
+ { symbol: ["wctrans_t", private, "<wctype.h>", public ] },
+ { symbol: ["wctype_t", private, "<wctype.h>", public ] },
+ { symbol: ["WEOF", private, "<wctype.h>", public ] },
+ { symbol: ["iswalnum", private, "<wctype.h>", public ] },
+ { symbol: ["iswalpha", private, "<wctype.h>", public ] },
+ { symbol: ["iswblank", private, "<wctype.h>", public ] },
+ { symbol: ["iswcntrl", private, "<wctype.h>", public ] },
+ { symbol: ["iswdigit", private, "<wctype.h>", public ] },
+ { symbol: ["iswgraph", private, "<wctype.h>", public ] },
+ { symbol: ["iswlower", private, "<wctype.h>", public ] },
+ { symbol: ["iswprint", private, "<wctype.h>", public ] },
+ { symbol: ["iswpunct", private, "<wctype.h>", public ] },
+ { symbol: ["iswspace", private, "<wctype.h>", public ] },
+ { symbol: ["iswupper", private, "<wctype.h>", public ] },
+ { symbol: ["iswxdigit", private, "<wctype.h>", public ] },
+ { symbol: ["iswctype", private, "<wctype.h>", public ] },
+ { symbol: ["wctype", private, "<wctype.h>", public ] },
+ { symbol: ["towlower", private, "<wctype.h>", public ] },
+ { symbol: ["towupper", private, "<wctype.h>", public ] },
+ { symbol: ["towctrans", private, "<wctype.h>", public ] },
+ { symbol: ["wctrans", private, "<wctype.h>", public ] },
+
+ # inttypes.h includes stdint.h
+ # https://port70.net/~nsz/c/c99/n1256.html#7.8p1
+ { include: [ "<stdint.h>", public, "<inttypes.h>", public ] },
+
+ # tgmath.h includes math.h and complex.h
+ # https://port70.net/~nsz/c/c99/n1256.html#7.22p1
+ { include: [ "<math.h>", public, "<tgmath.h>", public ] },
+ { include: [ "<complex.h>", public, "<tgmath.h>", public ] },
+]
+
+# vim: set ft=toml:
diff --git a/cmake.config/iwyu/gcc.libc.imp b/cmake.config/iwyu/gcc.libc.imp
deleted file mode 100644
index 1dd0ad42c7..0000000000
--- a/cmake.config/iwyu/gcc.libc.imp
+++ /dev/null
@@ -1,226 +0,0 @@
-# This was initially taken from the IWYU repository:
-# github.com/include-what-you-use/include-what-you-use/blob/164b8fe7597805ae55f029ecf6580dc46a74c7ed/gcc.libc.imp
-# It has useful mappings that are normally enabled by default, but there are
-# other default mappings that conflict with our builds. The best solution seems
-# to be to disable all defaults, import the defaults from the IWYU
-# repo and modify the rules that conflict with our build.
-#
-# TODO(dundargoc): Check if there is a way to disable specific builtin maps as
-# to avoid this file entirely.
-
-# Mappings for GNU libc
-# ( cd /usr/include && grep '^ *# *include' {sys/,net/,}* | perl -nle 'm/^([^:]+).*<([^>]+)>/ && print qq@ { include: [ "<$2>", private, "<$1>", public ] },@' | grep bits/ | sort )
-# When I saw more than one mapping for these, I typically picked
-# what I thought was the "best" one.
-[
- { include: [ "<bits/a.out.h>", private, "<a.out.h>", public ] },
- { include: [ "<bits/auxv.h>", private, "<sys/auxv.h>", public ] },
- { include: [ "<bits/byteswap.h>", private, "<byteswap.h>", public ] },
- { include: [ "<bits/cmathcalls.h>", private, "<complex.h>", public ] },
- { include: [ "<bits/confname.h>", private, "<unistd.h>", private ] },
- { include: [ "<bits/dirent.h>", private, "<dirent.h>", public ] },
- { include: [ "<bits/dlfcn.h>", private, "<dlfcn.h>", public ] },
- { include: [ "<bits/elfclass.h>", private, "<link.h>", public ] },
- { include: [ "<bits/endian.h>", private, "<endian.h>", public ] },
- { include: [ "<bits/environments.h>", private, "<unistd.h>", private ] },
- { include: [ "<bits/epoll.h>", private, "<sys/epoll.h>", public ] },
- { include: [ "<bits/errno.h>", private, "<errno.h>", public ] },
- { include: [ "<bits/error.h>", private, "<error.h>", public ] },
- { include: [ "<bits/eventfd.h>", private, "<sys/eventfd.h>", public ] },
- { include: [ "<bits/fcntl.h>", private, "<fcntl.h>", public ] },
- { include: [ "<bits/fcntl2.h>", private, "<fcntl.h>", public ] },
- { include: [ "<bits/fenv.h>", private, "<fenv.h>", public ] },
- { include: [ "<bits/fenvinline.h>", private, "<fenv.h>", public ] },
- { include: [ "<bits/huge_val.h>", private, "<math.h>", public ] },
- { include: [ "<bits/huge_valf.h>", private, "<math.h>", public ] },
- { include: [ "<bits/huge_vall.h>", private, "<math.h>", public ] },
- { include: [ "<bits/hwcap.h>", private, "<sys/auxv.h>", public ] },
- { include: [ "<bits/inf.h>", private, "<math.h>", public ] },
- { include: [ "<bits/inotify.h>", private, "<sys/inotify.h>", public ] },
- { include: [ "<bits/ioctl-types.h>", private, "<sys/ioctl.h>", public ] },
- { include: [ "<bits/ioctls.h>", private, "<sys/ioctl.h>", public ] },
- { include: [ "<bits/ipc.h>", private, "<sys/ipc.h>", public ] },
- { include: [ "<bits/ipctypes.h>", private, "<sys/ipc.h>", public ] },
- { include: [ "<bits/libio-ldbl.h>", private, "<libio.h>", public ] },
- { include: [ "<bits/link.h>", private, "<link.h>", public ] },
- { include: [ "<bits/locale.h>", private, "<locale.h>", public ] },
- { include: [ "<bits/math-finite.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mathcalls.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mathdef.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mathinline.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mman.h>", private, "<sys/mman.h>", public ] },
- { include: [ "<bits/mman-shared.h>", private, "<sys/mman.h>", public ] },
- { include: [ "<bits/monetary-ldbl.h>", private, "<monetary.h>", public ] },
- { include: [ "<bits/mqueue.h>", private, "<mqueue.h>", public ] },
- { include: [ "<bits/mqueue2.h>", private, "<mqueue.h>", public ] },
- { include: [ "<bits/msq.h>", private, "<sys/msg.h>", public ] },
- { include: [ "<bits/nan.h>", private, "<math.h>", public ] },
- { include: [ "<bits/netdb.h>", private, "<netdb.h>", private ] },
- { include: [ "<bits/param.h>", private, "<sys/param.h>", public ] },
- { include: [ "<bits/poll.h>", private, "<sys/poll.h>", private ] },
- { include: [ "<bits/poll2.h>", private, "<sys/poll.h>", private ] },
- { include: [ "<bits/posix1_lim.h>", private, "<limits.h>", public ] },
- { include: [ "<bits/posix2_lim.h>", private, "<limits.h>", public ] },
- { include: [ "<bits/posix_opt.h>", private, "<unistd.h>", private ] },
- { include: [ "<bits/printf-ldbl.h>", private, "<printf.h>", public ] },
- { include: [ "<bits/pthreadtypes.h>", private, "<pthread.h>", private ] },
- { include: [ "<bits/resource.h>", private, "<sys/resource.h>", public ] },
- { include: [ "<bits/sched.h>", private, "<sched.h>", public ] },
- { include: [ "<bits/select.h>", private, "<sys/select.h>", public ] },
- { include: [ "<bits/select2.h>", private, "<sys/select.h>", public ] },
- { include: [ "<bits/sem.h>", private, "<sys/sem.h>", public ] },
- { include: [ "<bits/semaphore.h>", private, "<semaphore.h>", public ] },
- { include: [ "<bits/setjmp.h>", private, "<setjmp.h>", public ] },
- { include: [ "<bits/setjmp2.h>", private, "<setjmp.h>", public ] },
- { include: [ "<bits/shm.h>", private, "<sys/shm.h>", public ] },
- { include: [ "<bits/sigaction.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/sigcontext.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/siginfo.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/signum.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/sigset.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/sigstack.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/sigthread.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/sockaddr.h>", private, "<sys/un.h>", public ] },
- { include: [ "<bits/socket.h>", private, "<sys/socket.h>", private ] },
- { include: [ "<bits/socket2.h>", private, "<sys/socket.h>", private ] },
- { include: [ "<bits/socket_type.h>", private, "<sys/socket.h>", private ] },
- { include: [ "<bits/stab.def>", private, "<stab.h>", public ] },
- { include: [ "<bits/stat.h>", private, "<sys/stat.h>", public ] },
- { include: [ "<bits/statfs.h>", private, "<sys/statfs.h>", public ] },
- { include: [ "<bits/statvfs.h>", private, "<sys/statvfs.h>", public ] },
- { include: [ "<bits/stdio-ldbl.h>", private, "<stdio.h>", public ] },
- { include: [ "<bits/stdio-lock.h>", private, "<libio.h>", public ] },
- { include: [ "<bits/stdio.h>", private, "<stdio.h>", public ] },
- { include: [ "<bits/stdio2.h>", private, "<stdio.h>", public ] },
- { include: [ "<bits/stdio_lim.h>", private, "<stdio.h>", public ] },
- { include: [ "<bits/stdlib-bsearch.h>", private, "<stdlib.h>", public ] },
- { include: [ "<bits/stdlib-float.h>", private, "<stdlib.h>", public ] },
- { include: [ "<bits/stdlib-ldbl.h>", private, "<stdlib.h>", public ] },
- { include: [ "<bits/stdlib.h>", private, "<stdlib.h>", public ] },
- { include: [ "<bits/string.h>", private, "<string.h>", public ] },
- { include: [ "<bits/string2.h>", private, "<string.h>", public ] },
- { include: [ "<bits/string3.h>", private, "<string.h>", public ] },
- { include: [ "<bits/stropts.h>", private, "<stropts.h>", public ] },
- { include: [ "<bits/struct_stat.h>", private, "<sys/stat.h>", public ] },
- { include: [ "<bits/struct_stat.h>", private, "<ftw.h>", public ] },
- { include: [ "<bits/sys_errlist.h>", private, "<stdio.h>", public ] },
- { include: [ "<bits/syscall.h>", private, "<sys/syscall.h>", public ] },
- { include: [ "<bits/sysctl.h>", private, "<sys/sysctl.h>", public ] },
- { include: [ "<bits/syslog-ldbl.h>", private, "<sys/syslog.h>", private ] },
- { include: [ "<bits/syslog-path.h>", private, "<sys/syslog.h>", private ] },
- { include: [ "<bits/syslog.h>", private, "<sys/syslog.h>", private ] },
- { include: [ "<bits/termios.h>", private, "<termios.h>", private ] },
- { include: [ "<bits/termios-c_lflag.h>", private, "<termios.h>", private ] },
- { include: [ "<bits/termios-struct.h>", private, "<termios.h>", private ] },
- { include: [ "<bits/termios-tcflow.h>", private, "<termios.h>", private ] },
- { include: [ "<bits/time.h>", private, "<time.h>", public ] },
- { include: [ "<bits/time.h>", private, "<sys/time.h>", public ] },
- { include: [ "<bits/timerfd.h>", private, "<sys/timerfd.h>", public ] },
- { include: [ "<bits/timex.h>", private, "<sys/timex.h>", public ] },
- { include: [ "<bits/types.h>", private, "<sys/types.h>", public ] },
- { include: [ "<bits/types/siginfo_t.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/types/siginfo_t.h>", private, "<sys/wait.h>", public ] },
- { include: [ "<bits/uio.h>", private, "<sys/uio.h>", public ] },
- { include: [ "<bits/unistd.h>", private, "<unistd.h>", private ] },
- { include: [ "<bits/ustat.h>", private, "<sys/ustat.h>", private ] },
- { include: [ "<bits/utmp.h>", private, "<utmp.h>", public ] },
- { include: [ "<bits/utmpx.h>", private, "<utmpx.h>", public ] },
- { include: [ "<bits/utsname.h>", private, "<sys/utsname.h>", public ] },
- { include: [ "<bits/waitflags.h>", private, "<sys/wait.h>", public ] },
- { include: [ "<bits/waitstatus.h>", private, "<sys/wait.h>", public ] },
- { include: [ "<bits/wchar-ldbl.h>", private, "<wchar.h>", public ] },
- { include: [ "<bits/wchar.h>", private, "<wchar.h>", public ] },
- { include: [ "<bits/wchar2.h>", private, "<wchar.h>", public ] },
- { include: [ "<bits/wordsize.h>", private, "<limits.h>", public ] },
- { include: [ "<bits/xopen_lim.h>", private, "<limits.h>", public ] },
- { include: [ "<bits/xtitypes.h>", private, "<stropts.h>", public ] },
- # Sometimes libc tells you what mapping to do via an '#error':
- # # error "Never use <bits/dlfcn.h> directly; include <dlfcn.h> instead."
- # or
- # # error "Never include <bits/socket_type.h> directly; use <sys/socket.h> instead."
- # ( cd /usr/include && grep -R '^ *# *error "Never use\|include' * | perl -nle 'm/<([^>]+).*directly.*<([^>]+)/ && print qq@ { include: [ "<$1>", private, "<$2>", public ] },@' | sort )
- { include: [ "<bits/a.out.h>", private, "<a.out.h>", public ] },
- { include: [ "<bits/byteswap-16.h>", private, "<byteswap.h>", public ] },
- { include: [ "<bits/byteswap.h>", private, "<byteswap.h>", public ] },
- { include: [ "<bits/cmathcalls.h>", private, "<complex.h>", public ] },
- { include: [ "<bits/confname.h>", private, "<unistd.h>", private ] },
- { include: [ "<bits/dirent.h>", private, "<dirent.h>", public ] },
- { include: [ "<bits/dlfcn.h>", private, "<dlfcn.h>", public ] },
- { include: [ "<bits/elfclass.h>", private, "<link.h>", public ] },
- { include: [ "<bits/endian.h>", private, "<endian.h>", public ] },
- { include: [ "<bits/epoll.h>", private, "<sys/epoll.h>", public ] },
- { include: [ "<bits/eventfd.h>", private, "<sys/eventfd.h>", public ] },
- { include: [ "<bits/fcntl-linux.h>", private, "<fcntl.h>", public ] },
- { include: [ "<bits/fcntl.h>", private, "<fcntl.h>", public ] },
- { include: [ "<bits/fenv.h>", private, "<fenv.h>", public ] },
- { include: [ "<bits/huge_val.h>", private, "<math.h>", public ] },
- { include: [ "<bits/huge_valf.h>", private, "<math.h>", public ] },
- { include: [ "<bits/huge_vall.h>", private, "<math.h>", public ] },
- { include: [ "<bits/in.h>", private, "<netinet/in.h>", private ] },
- { include: [ "<bits/inf.h>", private, "<math.h>", public ] },
- { include: [ "<bits/inotify.h>", private, "<sys/inotify.h>", public ] },
- { include: [ "<bits/ioctl-types.h>", private, "<sys/ioctl.h>", public ] },
- { include: [ "<bits/ioctls.h>", private, "<sys/ioctl.h>", public ] },
- { include: [ "<bits/ipc.h>", private, "<sys/ipc.h>", public ] },
- { include: [ "<bits/ipctypes.h>", private, "<sys/ipc.h>", public ] },
- { include: [ "<bits/locale.h>", private, "<locale.h>", public ] },
- { include: [ "<bits/math-finite.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mathdef.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mathinline.h>", private, "<math.h>", public ] },
- { include: [ "<bits/mman-linux.h>", private, "<sys/mman.h>", public ] },
- { include: [ "<bits/mman.h>", private, "<sys/mman.h>", public ] },
- { include: [ "<bits/mqueue.h>", private, "<mqueue.h>", public ] },
- { include: [ "<bits/msq.h>", private, "<sys/msg.h>", public ] },
- { include: [ "<bits/nan.h>", private, "<math.h>", public ] },
- { include: [ "<bits/param.h>", private, "<sys/param.h>", public ] },
- { include: [ "<bits/poll.h>", private, "<sys/poll.h>", private ] },
- { include: [ "<bits/predefs.h>", private, "<features.h>", public ] },
- { include: [ "<bits/resource.h>", private, "<sys/resource.h>", public ] },
- { include: [ "<bits/select.h>", private, "<sys/select.h>", public ] },
- { include: [ "<bits/semaphore.h>", private, "<semaphore.h>", public ] },
- { include: [ "<bits/sigcontext.h>", private, "<signal.h>", public ] },
- { include: [ "<bits/signalfd.h>", private, "<sys/signalfd.h>", public ] },
- { include: [ "<bits/stdlib-float.h>", private, "<stdlib.h>", public ] },
- { include: [ "<bits/string.h>", private, "<string.h>", public ] },
- { include: [ "<bits/string2.h>", private, "<string.h>", public ] },
- { include: [ "<bits/string3.h>", private, "<string.h>", public ] },
- { include: [ "<bits/timerfd.h>", private, "<sys/timerfd.h>", public ] },
- { include: [ "<bits/typesizes.h>", private, "<sys/types.h>", public ] },
- # Top-level #includes that just forward to another file:
- # $ for i in /usr/include/*; do [ -f $i ] } && [ `wc -l < $i` = 1 ] } && echo $i; done
- # (poll.h, syscall.h, syslog.h, ustat.h, wait.h).
- # For each file, I looked at the list of canonical header files --
- # http://www.opengroup.org/onlinepubs/9699919799/idx/head.html --
- # to decide which of the two files is canonical. If neither is
- # on the POSIX.1 1998 list, I just choose the top-level one.
- { include: [ "<sys/poll.h>", private, "<poll.h>", public ] },
- { include: [ "<sys/syslog.h>", private, "<syslog.h>", public ] },
- { include: [ "<sys/ustat.h>", private, "<ustat.h>", public ] },
- { include: [ "<wait.h>", private, "<sys/wait.h>", public ] },
- # These are all files in bits/ that delegate to asm/ and linux/ to
- # do all (or lots) of the work. Note these are private->private.
- # $ for i in /usr/include/bits/*; do for dir in asm linux; do grep -H -e $dir/`basename $i` $i; done; done
- { include: [ "<linux/errno.h>", private, "<bits/errno.h>", private ] },
- { include: [ "<asm/ioctls.h>", private, "<bits/ioctls.h>", private ] },
- { include: [ "<asm/socket.h>", private, "<bits/socket.h>", private ] },
- { include: [ "<linux/socket.h>", private, "<bits/socket.h>", private ] },
- # Some asm files have 32- and 64-bit variants:
- # $ ls /usr/include/asm/*_{32,64}.h
- { include: [ "<asm/posix_types_32.h>", private, "<asm/posix_types.h>", public ] },
- { include: [ "<asm/posix_types_64.h>", private, "<asm/posix_types.h>", public ] },
- { include: [ "<asm/unistd_32.h>", private, "<asm/unistd.h>", private ] },
- { include: [ "<asm/unistd_64.h>", private, "<asm/unistd.h>", private ] },
- # I don't know what grep would have found these. I found them
- # via user report.
- { include: [ "<asm/errno.h>", private, "<errno.h>", public ] },
- { include: [ "<asm/errno-base.h>", private, "<errno.h>", public ] },
- { include: [ "<asm/ptrace-abi.h>", private, "<asm/ptrace.h>", public ] },
- { include: [ "<asm/unistd.h>", private, "<sys/syscall.h>", public ] },
- { include: [ "<linux/limits.h>", private, "<limits.h>", public ] }, # PATH_MAX
- { include: [ "<linux/prctl.h>", private, "<sys/prctl.h>", public ] },
- { include: [ "<sys/ucontext.h>", private, "<ucontext.h>", public ] },
- # Exports guaranteed by the C standard
- { include: [ "<stdint.h>", public, "<inttypes.h>", public ] },
-]
-
-# vim: set ft=toml:
diff --git a/cmake.config/iwyu/gcc.symbols.imp b/cmake.config/iwyu/gcc.symbols.imp
deleted file mode 100644
index 6066de9d09..0000000000
--- a/cmake.config/iwyu/gcc.symbols.imp
+++ /dev/null
@@ -1,305 +0,0 @@
-# This was initially taken from the IWYU repository:
-# github.com/include-what-you-use/include-what-you-use/blob/164b8fe7597805ae55f029ecf6580dc46a74c7ed/gcc.symbols.imp
-# It has useful mappings that are normally enabled by default, but there are
-# other default mappings that conflict with our builds. The best solution seems
-# to be to disable all defaults, import the defaults from the IWYU repo and
-# modify the rules that conflict with our build.
-#
-# TODO(dundargoc): Check if there is a way to disable specific builtin maps as
-# to avoid this file entirely.
-
-# For library symbols that can be defined in more than one header
-# file, maps from symbol-name to legitimate header files.
-# This list was generated via
-# grep -R '__.*_defined' /usr/include | perl -nle 'm,/usr/include/([^:]*):#\s*\S+ __(.*)_defined, and print qq@ { symbol: [ "$2", public, "<$1>", public ] },@' | sort -u
-# I ignored all entries that only appeared once on the list (eg uint32_t).
-# I then added in NULL, which according to [diff.null] C.2.2.3, can
-# be defined in <clocale>, <cstddef>, <cstdio>, <cstdlib>,
-# <cstring>, <ctime>, or <cwchar>. We also allow their C
-# equivalents.
-# In each case, I ordered them so <sys/types.h> was first, if it was
-# an option for this type. That's the preferred #include all else
-# equal. The same goes for <stdint.h>. The visibility on the
-# symbol-name is ignored; by convention we always set it to private.
-[
- { symbol: [ "aiocb", private, "<aio.h>", public ] },
- { symbol: [ "blkcnt_t", private, "<sys/types.h>", public ] },
- { symbol: [ "blkcnt_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "blksize_t", private, "<sys/types.h>", public ] },
- { symbol: [ "blksize_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "cc_t", private, "<termios.h>", private ] },
- { symbol: [ "clock_t", private, "<sys/types.h>", public ] },
- { symbol: [ "clock_t", private, "<sys/time.h>", public ] },
- { symbol: [ "clock_t", private, "<time.h>", public ] },
- { symbol: [ "clockid_t", private, "<sys/types.h>", public ] },
- { symbol: [ "clockid_t", private, "<time.h>", public ] },
- { symbol: [ "daddr_t", private, "<sys/types.h>", public ] },
- { symbol: [ "daddr_t", private, "<rpc/types.h>", public ] },
- { symbol: [ "dev_t", private, "<sys/types.h>", public ] },
- { symbol: [ "dev_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "div_t", private, "<stdlib.h>", public ] },
- { symbol: [ "double_t", private, "<math.h>", public ] },
- { symbol: [ "error_t", private, "<errno.h>", public ] },
- { symbol: [ "error_t", private, "<argp.h>", public ] },
- { symbol: [ "error_t", private, "<argz.h>", public ] },
- { symbol: [ "fd_set", private, "<sys/select.h>", public ] },
- { symbol: [ "fd_set", private, "<sys/time.h>", public ] },
- { symbol: [ "fenv_t", private, "<fenv.h>", public ] },
- { symbol: [ "fexcept_t", private, "<fenv.h>", public ] },
- { symbol: [ "FILE", private, "<stdio.h>", public ] },
- { symbol: [ "FILE", private, "<wchar.h>", public ] },
- { symbol: [ "float_t", private, "<math.h>", public ] },
- { symbol: [ "fsblkcnt_t", private, "<sys/types.h>", public ] },
- { symbol: [ "fsblkcnt_t", private, "<sys/statvfs.h>", public ] },
- { symbol: [ "fsfilcnt_t", private, "<sys/types.h>", public ] },
- { symbol: [ "fsfilcnt_t", private, "<sys/statvfs.h>", public ] },
- { symbol: [ "getopt", private, "<unistd.h>", private ] },
- { symbol: [ "gid_t", private, "<sys/types.h>", public ] },
- { symbol: [ "gid_t", private, "<grp.h>", public ] },
- { symbol: [ "gid_t", private, "<pwd.h>", public ] },
- { symbol: [ "gid_t", private, "<signal.h>", public ] },
- { symbol: [ "gid_t", private, "<stropts.h>", public ] },
- { symbol: [ "gid_t", private, "<sys/ipc.h>", public ] },
- { symbol: [ "gid_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "gid_t", private, "<unistd.h>", private ] },
- { symbol: [ "htonl", private, "<arpa/inet.h>", private ] },
- { symbol: [ "htons", private, "<arpa/inet.h>", private ] },
- { symbol: [ "id_t", private, "<sys/types.h>", public ] },
- { symbol: [ "id_t", private, "<sys/resource.h>", public ] },
- { symbol: [ "imaxdiv_t", private, "<inttypes.h>", public ] },
- { symbol: [ "intmax_t", private, "<stdint.h>", public ] },
- { symbol: [ "uintmax_t", private, "<stdint.h>", public ] },
- { symbol: [ "ino64_t", private, "<sys/types.h>", public ] },
- { symbol: [ "ino64_t", private, "<dirent.h>", public ] },
- { symbol: [ "ino_t", private, "<sys/types.h>", public ] },
- { symbol: [ "ino_t", private, "<dirent.h>", public ] },
- { symbol: [ "ino_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "int8_t", private, "<stdint.h>", public ] },
- { symbol: [ "int16_t", private, "<stdint.h>", public ] },
- { symbol: [ "int32_t", private, "<stdint.h>", public ] },
- { symbol: [ "int64_t", private, "<stdint.h>", public ] },
- { symbol: [ "uint8_t", private, "<stdint.h>", public ] },
- { symbol: [ "uint16_t", private, "<stdint.h>", public ] },
- { symbol: [ "uint32_t", private, "<stdint.h>", public ] },
- { symbol: [ "uint64_t", private, "<stdint.h>", public ] },
- { symbol: [ "intptr_t", private, "<stdint.h>", public ] },
- { symbol: [ "uintptr_t", private, "<stdint.h>", public ] },
- { symbol: [ "iovec", private, "<sys/uio.h>", public ] },
- { symbol: [ "iovec", private, "<sys/socket.h>", private ] },
- { symbol: [ "itimerspec", private, "<time.h>", public ] },
- { symbol: [ "itimerspec", private, "<sys/timerfd.h>", public ] },
- { symbol: [ "key_t", private, "<sys/types.h>", public ] },
- { symbol: [ "key_t", private, "<sys/ipc.h>", public ] },
- { symbol: [ "lconv", private, "<locale.h>", public ] },
- { symbol: [ "ldiv_t", private, "<stdlib.h>", public ] },
- { symbol: [ "lldiv_t", private, "<stdlib.h>", public ] },
- { symbol: [ "max_align_t", private, "<stddef.h>", public ] },
- { symbol: [ "mode_t", private, "<sys/types.h>", public ] },
- { symbol: [ "mode_t", private, "<fcntl.h>", public ] },
- { symbol: [ "mode_t", private, "<ndbm.h>", public ] },
- { symbol: [ "mode_t", private, "<spawn.h>", public ] },
- { symbol: [ "mode_t", private, "<sys/ipc.h>", public ] },
- { symbol: [ "mode_t", private, "<sys/mman.h>", public ] },
- { symbol: [ "mode_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "nlink_t", private, "<sys/types.h>", public ] },
- { symbol: [ "nlink_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "ntohl", private, "<arpa/inet.h>", private ] },
- { symbol: [ "ntohs", private, "<arpa/inet.h>", private ] },
- { symbol: [ "off64_t", private, "<sys/types.h>", public ] },
- { symbol: [ "off64_t", private, "<unistd.h>", private ] },
- { symbol: [ "off_t", private, "<sys/types.h>", public ] },
- { symbol: [ "off_t", private, "<aio.h>", public ] },
- { symbol: [ "off_t", private, "<fcntl.h>", public ] },
- { symbol: [ "off_t", private, "<stdio.h>", public ] },
- { symbol: [ "off_t", private, "<sys/mman.h>", public ] },
- { symbol: [ "off_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "off_t", private, "<unistd.h>", private ] },
- { symbol: [ "optarg", private, "<unistd.h>", private ] },
- { symbol: [ "opterr", private, "<unistd.h>", private ] },
- { symbol: [ "optind", private, "<unistd.h>", private ] },
- { symbol: [ "optopt", private, "<unistd.h>", private ] },
- { symbol: [ "pid_t", private, "<sys/types.h>", public ] },
- { symbol: [ "pid_t", private, "<fcntl.h>", public ] },
- { symbol: [ "pid_t", private, "<sched.h>", public ] },
- { symbol: [ "pid_t", private, "<signal.h>", public ] },
- { symbol: [ "pid_t", private, "<spawn.h>", public ] },
- { symbol: [ "pid_t", private, "<sys/msg.h>", public ] },
- { symbol: [ "pid_t", private, "<sys/sem.h>", public ] },
- { symbol: [ "pid_t", private, "<sys/shm.h>", public ] },
- { symbol: [ "pid_t", private, "<sys/wait.h>", public ] },
- { symbol: [ "pid_t", private, "<termios.h>", private ] },
- { symbol: [ "pid_t", private, "<time.h>", public ] },
- { symbol: [ "pid_t", private, "<unistd.h>", private ] },
- { symbol: [ "pid_t", private, "<utmpx.h>", public ] },
- { symbol: [ "ptrdiff_t", private, "<stddef.h>", public ] },
- { symbol: [ "regex_t", private, "<regex.h>", public ] },
- { symbol: [ "regmatch_t", private, "<regex.h>", public ] },
- { symbol: [ "regoff_t", private, "<regex.h>", public ] },
- { symbol: [ "sigevent", private, "<signal.h>", public ] },
- { symbol: [ "sigevent", private, "<aio.h>", public ] },
- { symbol: [ "sigevent", private, "<mqueue.h>", public ] },
- { symbol: [ "sigevent", private, "<time.h>", public ] },
- { symbol: [ "siginfo_t", private, "<signal.h>", public ] },
- { symbol: [ "siginfo_t", private, "<sys/wait.h>", public ] },
- { symbol: [ "sigset_t", private, "<signal.h>", public ] },
- { symbol: [ "sigset_t", private, "<spawn.h>", public ] },
- { symbol: [ "sigset_t", private, "<sys/select.h>", public ] },
- { symbol: [ "sigval", private, "<signal.h>", public ] },
- { symbol: [ "sockaddr", private, "<sys/socket.h>", private ] },
- { symbol: [ "socklen_t", private, "<sys/socket.h>", private ] },
- { symbol: [ "socklen_t", private, "<netdb.h>", private ] },
- { symbol: [ "ssize_t", private, "<sys/types.h>", public ] },
- { symbol: [ "ssize_t", private, "<aio.h>", public ] },
- { symbol: [ "ssize_t", private, "<monetary.h>", public ] },
- { symbol: [ "ssize_t", private, "<mqueue.h>", public ] },
- { symbol: [ "ssize_t", private, "<stdio.h>", public ] },
- { symbol: [ "ssize_t", private, "<sys/msg.h>", public ] },
- { symbol: [ "ssize_t", private, "<sys/socket.h>", private ] },
- { symbol: [ "ssize_t", private, "<sys/uio.h>", public ] },
- { symbol: [ "ssize_t", private, "<unistd.h>", private ] },
- { symbol: [ "stat", private, "<sys/stat.h>", public ] },
- { symbol: [ "stat", private, "<ftw.h>", public ] },
- { symbol: [ "suseconds_t", private, "<sys/types.h>", public ] },
- { symbol: [ "suseconds_t", private, "<sys/select.h>", public ] },
- { symbol: [ "suseconds_t", private, "<sys/time.h>", public ] },
- { symbol: [ "time_t", private, "<time.h>", public ] },
- { symbol: [ "time_t", private, "<sched.h>", public ] },
- { symbol: [ "time_t", private, "<sys/msg.h>", public ] },
- { symbol: [ "time_t", private, "<sys/select.h>", public ] },
- { symbol: [ "time_t", private, "<sys/sem.h>", public ] },
- { symbol: [ "time_t", private, "<sys/shm.h>", public ] },
- { symbol: [ "time_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "time_t", private, "<sys/time.h>", public ] },
- { symbol: [ "time_t", private, "<sys/types.h>", public ] },
- { symbol: [ "time_t", private, "<utime.h>", public ] },
- { symbol: [ "timer_t", private, "<sys/types.h>", public ] },
- { symbol: [ "timer_t", private, "<time.h>", public ] },
- { symbol: [ "timespec", private, "<time.h>", public ] },
- { symbol: [ "timespec", private, "<aio.h>", public ] },
- { symbol: [ "timespec", private, "<mqueue.h>", public ] },
- { symbol: [ "timespec", private, "<sched.h>", public ] },
- { symbol: [ "timespec", private, "<signal.h>", public ] },
- { symbol: [ "timespec", private, "<sys/select.h>", public ] },
- { symbol: [ "timespec", private, "<sys/stat.h>", public ] },
- { symbol: [ "timeval", private, "<sys/time.h>", public ] },
- { symbol: [ "timeval", private, "<sys/resource.h>", public ] },
- { symbol: [ "timeval", private, "<sys/select.h>", public ] },
- { symbol: [ "timeval", private, "<utmpx.h>", public ] },
- { symbol: [ "tm", private, "<time.h>", public ] },
- { symbol: [ "u_char", private, "<sys/types.h>", public ] },
- { symbol: [ "u_char", private, "<rpc/types.h>", public ] },
- { symbol: [ "uid_t", private, "<sys/types.h>", public ] },
- { symbol: [ "uid_t", private, "<pwd.h>", public ] },
- { symbol: [ "uid_t", private, "<signal.h>", public ] },
- { symbol: [ "uid_t", private, "<stropts.h>", public ] },
- { symbol: [ "uid_t", private, "<sys/ipc.h>", public ] },
- { symbol: [ "uid_t", private, "<sys/stat.h>", public ] },
- { symbol: [ "uid_t", private, "<unistd.h>", private ] },
- { symbol: [ "useconds_t", private, "<sys/types.h>", public ] },
- { symbol: [ "useconds_t", private, "<unistd.h>", private ] },
- { symbol: [ "wchar_t", private, "<stddef.h>", public ] },
- { symbol: [ "wchar_t", private, "<stdlib.h>", public ] },
- { symbol: [ "size_t", private, "<stddef.h>", public ] },
- { symbol: [ "size_t", private, "<aio.h>", public ] },
- { symbol: [ "size_t", private, "<glob.h>", public ] },
- { symbol: [ "size_t", private, "<grp.h>", public ] },
- { symbol: [ "size_t", private, "<iconv.h>", public ] },
- { symbol: [ "size_t", private, "<monetary.h>", public ] },
- { symbol: [ "size_t", private, "<mqueue.h>", public ] },
- { symbol: [ "size_t", private, "<ndbm.h>", public ] },
- { symbol: [ "size_t", private, "<pwd.h>", public ] },
- { symbol: [ "size_t", private, "<regex.h>", public ] },
- { symbol: [ "size_t", private, "<search.h>", public ] },
- { symbol: [ "size_t", private, "<signal.h>", public ] },
- { symbol: [ "size_t", private, "<stdio.h>", public ] },
- { symbol: [ "size_t", private, "<stdlib.h>", public ] },
- { symbol: [ "size_t", private, "<string.h>", public ] },
- { symbol: [ "size_t", private, "<strings.h>", public ] },
- { symbol: [ "size_t", private, "<sys/mman.h>", public ] },
- { symbol: [ "size_t", private, "<sys/msg.h>", public ] },
- { symbol: [ "size_t", private, "<sys/sem.h>", public ] },
- { symbol: [ "size_t", private, "<sys/shm.h>", public ] },
- { symbol: [ "size_t", private, "<sys/socket.h>", private ] },
- { symbol: [ "size_t", private, "<sys/types.h>", public ] },
- { symbol: [ "size_t", private, "<sys/uio.h>", public ] },
- { symbol: [ "size_t", private, "<time.h>", public ] },
- { symbol: [ "size_t", private, "<uchar.h>", public ] },
- { symbol: [ "size_t", private, "<unistd.h>", private ] },
- { symbol: [ "size_t", private, "<wchar.h>", public ] },
- { symbol: [ "size_t", private, "<wordexp.h>", public ] },
- # Macros that can be defined in more than one file, don't have the
- # same __foo_defined guard that other types do, so the grep above
- # doesn't discover them. Until I figure out a better way, I just
- # add them in by hand as I discover them.
- { symbol: [ "EOF", private, "<stdio.h>", public ] },
- { symbol: [ "EOF", private, "<libio.h>", public ] },
- { symbol: [ "FILE", private, "<stdio.h>", public ] },
- { symbol: [ "MAP_POPULATE", private, "<sys/mman.h>", public ] },
- { symbol: [ "MAP_POPULATE", private, "<linux/mman.h>", public ] },
- { symbol: [ "MAP_STACK", private, "<sys/mman.h>", public ] },
- { symbol: [ "MAP_STACK", private, "<linux/mman.h>", public ] },
- { symbol: [ "MAXHOSTNAMELEN", private, "<sys/param.h>", public ] },
- { symbol: [ "MAXHOSTNAMELEN", private, "<protocols/timed.h>", public ] },
- { symbol: [ "SIGABRT", private, "<signal.h>", public ] },
- { symbol: [ "SIGCHLD", private, "<signal.h>", public ] },
- { symbol: [ "SIGCHLD", private, "<linux/signal.h>", public ] },
- { symbol: [ "va_list", private, "<stdarg.h>", public ] },
- { symbol: [ "va_list", private, "<stdio.h>", public ] },
- { symbol: [ "va_list", private, "<wchar.h>", public ] },
- # These are symbols that could be defined in either stdlib.h or
- # malloc.h, but we always want the stdlib location.
- { symbol: [ "malloc", private, "<stdlib.h>", public ] },
- { symbol: [ "calloc", private, "<stdlib.h>", public ] },
- { symbol: [ "realloc", private, "<stdlib.h>", public ] },
- { symbol: [ "free", private, "<stdlib.h>", public ] },
- # Entries for NULL
- { symbol: [ "NULL", private, "<stddef.h>", public ] }, # 'canonical' location for NULL
- { symbol: [ "NULL", private, "<clocale>", public ] },
- { symbol: [ "NULL", private, "<cstddef>", public ] },
- { symbol: [ "NULL", private, "<cstdio>", public ] },
- { symbol: [ "NULL", private, "<cstdlib>", public ] },
- { symbol: [ "NULL", private, "<cstring>", public ] },
- { symbol: [ "NULL", private, "<ctime>", public ] },
- { symbol: [ "NULL", private, "<cwchar>", public ] },
- { symbol: [ "NULL", private, "<locale.h>", public ] },
- { symbol: [ "NULL", private, "<stdio.h>", public ] },
- { symbol: [ "NULL", private, "<stdlib.h>", public ] },
- { symbol: [ "NULL", private, "<string.h>", public ] },
- { symbol: [ "NULL", private, "<time.h>", public ] },
- { symbol: [ "NULL", private, "<unistd.h>", private ] },
- { symbol: [ "NULL", private, "<wchar.h>", public ] },
-
- # Kludge time: almost all STL types take an allocator, but they
- # almost always use the default value. Usually we detect that
- # and don't try to do IWYU, but sometimes it passes through.
- # For instance, when adding two strings, we end up calling
- # template<_CharT,_Traits,_Alloc> ... operator+(
- # basic_string<_CharT,_Traits,_Alloc>, ...)
- # These look like normal template args to us, so we see they're
- # used and declare an iwyu dependency, even though we don't need
- # to #include the traits or alloc type ourselves. The surest way
- # to deal with this is to just say that everyone provides
- # std::allocator. We can add more here at need.
- { symbol: [ "std::allocator", private, "<memory>", public ] },
- { symbol: [ "std::allocator", private, "<string>", public ] },
- { symbol: [ "std::allocator", private, "<vector>", public ] },
- { symbol: [ "std::allocator", private, "<map>", public ] },
- { symbol: [ "std::allocator", private, "<set>", public ] },
- # A similar kludge for std::char_traits. basic_string,
- # basic_ostream and basic_istream have this as a default template
- # argument, and sometimes it bleeds through when clang desugars the
- # string/ostream/istream type.
- { symbol: [ "std::char_traits", private, "<string>", public ] },
- { symbol: [ "std::char_traits", private, "<ostream>", public ] },
- { symbol: [ "std::char_traits", private, "<istream>", public ] },
-
- { symbol: [ "std::size_t", private, "<cstddef>", public ] },
- { symbol: [ "std::size_t", private, "<cstdio>", public ] },
- { symbol: [ "std::size_t", private, "<cstdlib>", public ] },
- { symbol: [ "std::size_t", private, "<cstring>", public ] },
- { symbol: [ "std::size_t", private, "<ctime>", public ] },
- { symbol: [ "std::size_t", private, "<cuchar>", public ] },
- { symbol: [ "std::size_t", private, "<cwchar>", public ] }
-]
-
-# vim: set ft=toml:
diff --git a/cmake.config/iwyu/mapping.imp b/cmake.config/iwyu/mapping.imp
index 4e55aa9875..4056070958 100644
--- a/cmake.config/iwyu/mapping.imp
+++ b/cmake.config/iwyu/mapping.imp
@@ -1,238 +1,30 @@
[
- # Generated to normal headers: header.h.generated.h -> nvim/header.h
- { include: [ '"api/autocmd.h.generated.h"', private, '"nvim/api/autocmd.h"', public ] },
- { include: [ '"api/buffer.h.generated.h"', private, '"nvim/api/buffer.h"', public ] },
- { include: [ '"api/command.h.generated.h"', private, '"nvim/api/command.h"', public ] },
- { include: [ '"api/deprecated.h.generated.h"', private, '"nvim/api/deprecated.h"', public ] },
- { include: [ '"api/extmark.h.generated.h"', private, '"nvim/api/extmark.h"', public ] },
- { include: [ '"api/options.h.generated.h"', private, '"nvim/api/options.h"', public ] },
- { include: [ '"api/private/converter.h.generated.h"', private, '"nvim/api/private/converter.h"', public ] },
- { include: [ '"api/private/dispatch.h.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] },
- { include: [ '"api/private/helpers.h.generated.h"', private, '"nvim/api/private/helpers.h"', public ] },
- { include: [ '"api/tabpage.h.generated.h"', private, '"nvim/api/tabpage.h"', public ] },
- { include: [ '"api/ui.h.generated.h"', private, '"nvim/api/ui.h"', public ] },
- { include: [ '"api/vim.h.generated.h"', private, '"nvim/api/vim.h"', public ] },
- { include: [ '"api/vimscript.h.generated.h"', private, '"nvim/api/vimscript.h"', public ] },
- { include: [ '"api/win_config.h.generated.h"', private, '"nvim/api/win_config.h"', public ] },
- { include: [ '"api/window.h.generated.h"', private, '"nvim/api/window.h"', public ] },
- { include: [ '"arabic.h.generated.h"', private, '"nvim/arabic.h"', public ] },
- { include: [ '"arglist.h.generated.h"', private, '"nvim/arglist.h"', public ] },
- { include: [ '"autocmd.h.generated.h"', private, '"nvim/autocmd.h"', public ] },
- { include: [ '"buffer.h.generated.h"', private, '"nvim/buffer.h"', public ] },
- { include: [ '"buffer_updates.h.generated.h"', private, '"nvim/buffer_updates.h"', public ] },
- { include: [ '"change.h.generated.h"', private, '"nvim/change.h"', public ] },
- { include: [ '"channel.h.generated.h"', private, '"nvim/channel.h"', public ] },
- { include: [ '"charset.h.generated.h"', private, '"nvim/charset.h"', public ] },
- { include: [ '"cmdexpand.h.generated.h"', private, '"nvim/cmdexpand.h"', public ] },
- { include: [ '"cmdhist.h.generated.h"', private, '"nvim/cmdhist.h"', public ] },
- { include: [ '"context.h.generated.h"', private, '"nvim/context.h"', public ] },
- { include: [ '"cursor.h.generated.h"', private, '"nvim/cursor.h"', public ] },
- { include: [ '"cursor_shape.h.generated.h"', private, '"nvim/cursor_shape.h"', public ] },
- { include: [ '"debugger.h.generated.h"', private, '"nvim/debugger.h"', public ] },
- { include: [ '"decoration.h.generated.h"', private, '"nvim/decoration.h"', public ] },
- { include: [ '"decoration_provider.h.generated.h"', private, '"nvim/decoration_provider.h"', public ] },
- { include: [ '"diff.h.generated.h"', private, '"nvim/diff.h"', public ] },
- { include: [ '"digraph.h.generated.h"', private, '"nvim/digraph.h"', public ] },
- { include: [ '"drawline.h.generated.h"', private, '"nvim/drawline.h"', public ] },
- { include: [ '"drawscreen.h.generated.h"', private, '"nvim/drawscreen.h"', public ] },
- { include: [ '"edit.h.generated.h"', private, '"nvim/edit.h"', public ] },
- { include: [ '"eval.h.generated.h"', private, '"nvim/eval.h"', public ] },
- { include: [ '"eval/buffer.h.generated.h"', private, '"nvim/eval/buffer.h"', public ] },
- { include: [ '"eval/decode.h.generated.h"', private, '"nvim/eval/decode.h"', public ] },
- { include: [ '"eval/encode.h.generated.h"', private, '"nvim/eval/encode.h"', public ] },
- { include: [ '"eval/executor.h.generated.h"', private, '"nvim/eval/executor.h"', public ] },
- { include: [ '"eval/funcs.h.generated.h"', private, '"nvim/eval/funcs.h"', public ] },
- { include: [ '"eval/typval.h.generated.h"', private, '"nvim/eval/typval.h"', public ] },
- { include: [ '"eval/userfunc.h.generated.h"', private, '"nvim/eval/userfunc.h"', public ] },
- { include: [ '"eval/vars.h.generated.h"', private, '"nvim/eval/vars.h"', public ] },
- { include: [ '"eval/window.h.generated.h"', private, '"nvim/eval/window.h"', public ] },
- { include: [ '"event/libuv_process.h.generated.h"', private, '"nvim/event/libuv_process.h"', public ] },
- { include: [ '"event/loop.h.generated.h"', private, '"nvim/event/loop.h"', public ] },
- { include: [ '"event/multiqueue.h.generated.h"', private, '"nvim/event/multiqueue.h"', public ] },
- { include: [ '"event/process.h.generated.h"', private, '"nvim/event/process.h"', public ] },
- { include: [ '"event/rstream.h.generated.h"', private, '"nvim/event/rstream.h"', public ] },
- { include: [ '"event/signal.h.generated.h"', private, '"nvim/event/signal.h"', public ] },
- { include: [ '"event/socket.h.generated.h"', private, '"nvim/event/socket.h"', public ] },
- { include: [ '"event/stream.h.generated.h"', private, '"nvim/event/stream.h"', public ] },
- { include: [ '"event/time.h.generated.h"', private, '"nvim/event/time.h"', public ] },
- { include: [ '"event/wstream.h.generated.h"', private, '"nvim/event/wstream.h"', public ] },
- { include: [ '"ex_cmds.h.generated.h"', private, '"nvim/ex_cmds.h"', public ] },
- { include: [ '"ex_cmds2.h.generated.h"', private, '"nvim/ex_cmds2.h"', public ] },
- { include: [ '"ex_docmd.h.generated.h"', private, '"nvim/ex_docmd.h"', public ] },
- { include: [ '"ex_eval.h.generated.h"', private, '"nvim/ex_eval.h"', public ] },
- { include: [ '"ex_getln.h.generated.h"', private, '"nvim/ex_getln.h"', public ] },
- { include: [ '"ex_session.h.generated.h"', private, '"nvim/ex_session.h"', public ] },
- { include: [ '"extmark.h.generated.h"', private, '"nvim/extmark.h"', public ] },
- { include: [ '"file_search.h.generated.h"', private, '"nvim/file_search.h"', public ] },
- { include: [ '"fileio.h.generated.h"', private, '"nvim/fileio.h"', public ] },
- { include: [ '"fold.h.generated.h"', private, '"nvim/fold.h"', public ] },
- { include: [ '"garray.h.generated.h"', private, '"nvim/garray.h"', public ] },
- { include: [ '"getchar.h.generated.h"', private, '"nvim/getchar.h"', public ] },
- { include: [ '"grid.h.generated.h"', private, '"nvim/grid.h"', public ] },
- { include: [ '"hashtab.h.generated.h"', private, '"nvim/hashtab.h"', public ] },
- { include: [ '"help.h.generated.h"', private, '"nvim/help.h"', public ] },
- { include: [ '"highlight.h.generated.h"', private, '"nvim/highlight.h"', public ] },
- { include: [ '"highlight_group.h.generated.h"', private, '"nvim/highlight_group.h"', public ] },
- { include: [ '"if_cscope.h.generated.h"', private, '"nvim/if_cscope.h"', public ] },
- { include: [ '"indent.h.generated.h"', private, '"nvim/indent.h"', public ] },
- { include: [ '"indent_c.h.generated.h"', private, '"nvim/indent_c.h"', public ] },
- { include: [ '"input.h.generated.h"', private, '"nvim/input.h"', public ] },
- { include: [ '"insexpand.h.generated.h"', private, '"nvim/insexpand.h"', public ] },
- { include: [ '"keycodes.h.generated.h"', private, '"nvim/keycodes.h"', public ] },
- { include: [ '"linematch.h.generated.h"', private, '"nvim/linematch.h"', public ] },
- { include: [ '"locale.h.generated.h"', private, '"nvim/locale.h"', public ] },
- { include: [ '"log.h.generated.h"', private, '"nvim/log.h"', public ] },
- { include: [ '"lua/converter.h.generated.h"', private, '"nvim/lua/converter.h"', public ] },
- { include: [ '"lua/executor.h.generated.h"', private, '"nvim/lua/executor.h"', public ] },
- { include: [ '"lua/spell.h.generated.h"', private, '"nvim/lua/spell.h"', public ] },
- { include: [ '"lua/stdlib.h.generated.h"', private, '"nvim/lua/stdlib.h"', public ] },
- { include: [ '"lua/treesitter.h.generated.h"', private, '"nvim/lua/treesitter.h"', public ] },
- { include: [ '"lua/xdiff.h.generated.h"', private, '"nvim/lua/xdiff.h"', public ] },
- { include: [ '"main.h.generated.h"', private, '"nvim/main.h"', public ] },
- { include: [ '"mapping.h.generated.h"', private, '"nvim/mapping.h"', public ] },
- { include: [ '"mark.h.generated.h"', private, '"nvim/mark.h"', public ] },
- { include: [ '"marktree.h.generated.h"', private, '"nvim/marktree.h"', public ] },
- { include: [ '"match.h.generated.h"', private, '"nvim/match.h"', public ] },
- { include: [ '"math.h.generated.h"', private, '"nvim/math.h"', public ] },
- { include: [ '"mbyte.h.generated.h"', private, '"nvim/mbyte.h"', public ] },
- { include: [ '"memfile.h.generated.h"', private, '"nvim/memfile.h"', public ] },
- { include: [ '"memline.h.generated.h"', private, '"nvim/memline.h"', public ] },
- { include: [ '"memory.h.generated.h"', private, '"nvim/memory.h"', public ] },
- { include: [ '"menu.h.generated.h"', private, '"nvim/menu.h"', public ] },
- { include: [ '"message.h.generated.h"', private, '"nvim/message.h"', public ] },
- { include: [ '"mouse.h.generated.h"', private, '"nvim/mouse.h"', public ] },
- { include: [ '"move.h.generated.h"', private, '"nvim/move.h"', public ] },
- { include: [ '"msgpack_rpc/channel.h.generated.h"', private, '"nvim/msgpack_rpc/channel.h"', public ] },
- { include: [ '"msgpack_rpc/helpers.h.generated.h"', private, '"nvim/msgpack_rpc/helpers.h"', public ] },
- { include: [ '"msgpack_rpc/server.h.generated.h"', private, '"nvim/msgpack_rpc/server.h"', public ] },
- { include: [ '"msgpack_rpc/unpacker.h.generated.h"', private, '"nvim/msgpack_rpc/unpacker.h"', public ] },
- { include: [ '"normal.h.generated.h"', private, '"nvim/normal.h"', public ] },
- { include: [ '"ops.h.generated.h"', private, '"nvim/ops.h"', public ] },
- { include: [ '"option.h.generated.h"', private, '"nvim/option.h"', public ] },
- { include: [ '"optionstr.h.generated.h"', private, '"nvim/optionstr.h"', public ] },
- { include: [ '"os/dl.h.generated.h"', private, '"nvim/os/dl.h"', public ] },
- { include: [ '"os/fileio.h.generated.h"', private, '"nvim/os/fileio.h"', public ] },
- { include: [ '"os/fs.h.generated.h"', private, '"nvim/os/fs.h"', public ] },
- { include: [ '"os/input.h.generated.h"', private, '"nvim/os/input.h"', public ] },
- { include: [ '"os/lang.h.generated.h"', private, '"nvim/os/lang.h"', public ] },
- { include: [ '"os/process.h.generated.h"', private, '"nvim/os/process.h"', public ] },
- { include: [ '"os/pty_process_unix.h.generated.h"', private, '"nvim/os/pty_process_unix.h"', private ] },
- { include: [ '"os/shell.h.generated.h"', private, '"nvim/os/shell.h"', public ] },
- { include: [ '"os/signal.h.generated.h"', private, '"nvim/os/signal.h"', public ] },
- { include: [ '"os/time.h.generated.h"', private, '"nvim/os/time.h"', public ] },
- { include: [ '"path.h.generated.h"', private, '"nvim/path.h"', public ] },
- { include: [ '"plines.h.generated.h"', private, '"nvim/plines.h"', public ] },
- { include: [ '"popupmenu.h.generated.h"', private, '"nvim/popupmenu.h"', public ] },
- { include: [ '"profile.h.generated.h"', private, '"nvim/profile.h"', public ] },
- { include: [ '"quickfix.h.generated.h"', private, '"nvim/quickfix.h"', public ] },
- { include: [ '"rbuffer.h.generated.h"', private, '"nvim/rbuffer.h"', public ] },
- { include: [ '"regexp.h.generated.h"', private, '"nvim/regexp.h"', public ] },
- { include: [ '"runtime.h.generated.h"', private, '"nvim/runtime.h"', public ] },
- { include: [ '"screen.h.generated.h"', private, '"nvim/screen.h"', public ] },
- { include: [ '"search.h.generated.h"', private, '"nvim/search.h"', public ] },
- { include: [ '"sha256.h.generated.h"', private, '"nvim/sha256.h"', public ] },
- { include: [ '"shada.h.generated.h"', private, '"nvim/shada.h"', public ] },
- { include: [ '"sign.h.generated.h"', private, '"nvim/sign.h"', public ] },
- { include: [ '"spell.h.generated.h"', private, '"nvim/spell.h"', public ] },
- { include: [ '"spellfile.h.generated.h"', private, '"nvim/spellfile.h"', public ] },
- { include: [ '"spellsuggest.h.generated.h"', private, '"nvim/spellsuggest.h"', public ] },
- { include: [ '"state.h.generated.h"', private, '"nvim/state.h"', public ] },
- { include: [ '"statusline.h.generated.h"', private, '"nvim/statusline.h"', public ] },
- { include: [ '"strings.h.generated.h"', private, '"nvim/strings.h"', public ] },
- { include: [ '"syntax.h.generated.h"', private, '"nvim/syntax.h"', public ] },
- { include: [ '"tag.h.generated.h"', private, '"nvim/tag.h"', public ] },
- { include: [ '"terminal.h.generated.h"', private, '"nvim/terminal.h"', public ] },
- { include: [ '"testing.h.generated.h"', private, '"nvim/testing.h"', public ] },
- { include: [ '"textformat.h.generated.h"', private, '"nvim/textformat.h"', public ] },
- { include: [ '"textobject.h.generated.h"', private, '"nvim/textobject.h"', public ] },
- { include: [ '"tui/input.h.generated.h"', private, '"nvim/tui/input.h"', public ] },
- { include: [ '"tui/terminfo.h.generated.h"', private, '"nvim/tui/terminfo.h"', public ] },
- { include: [ '"tui/tui.h.generated.h"', private, '"nvim/tui/tui.h"', public ] },
- { include: [ '"ugrid.h.generated.h"', private, '"nvim/ugrid.h"', public ] },
- { include: [ '"ui.h.generated.h"', private, '"nvim/ui.h"', public ] },
- { include: [ '"ui_bridge.h.generated.h"', private, '"nvim/ui_bridge.h"', public ] },
- { include: [ '"ui_client.h.generated.h"', private, '"nvim/ui_client.h"', public ] },
- { include: [ '"ui_compositor.h.generated.h"', private, '"nvim/ui_compositor.h"', public ] },
- { include: [ '"undo.h.generated.h"', private, '"nvim/undo.h"', public ] },
- { include: [ '"usercmd.h.generated.h"', private, '"nvim/usercmd.h"', public ] },
- { include: [ '"version.h.generated.h"', private, '"nvim/version.h"', public ] },
- { include: [ '"viml/parser/expressions.h.generated.h"', private, '"nvim/viml/parser/expressions.h"', public ] },
- { include: [ '"viml/parser/parser.h.generated.h"', private, '"nvim/viml/parser/parser.h"', public ] },
- { include: [ '"window.h.generated.h"', private, '"nvim/window.h"', public ] },
+ { ref: "c99.imp" },
+ { ref: "posix.imp" },
- # Generated to normal headers with a different name: header.h.generated.h -> nvim/some_other_header.h
- { include: [ '"api/private/dispatch_wrappers.h.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] },
- { include: [ '"auevents_enum.generated.h"', private, '"nvim/autocmd.h"', public ] },
- { include: [ '"ex_cmds_enum.generated.h"', private, '"nvim/ex_cmds_defs.h"', public ] },
- { include: [ '"keysets.h.generated.h"', private, '"nvim/api/private/helpers.h"', public ] },
- { include: [ '"keysets_defs.generated.h"', private, '"nvim/api/private/defs.h"', public ] },
- { include: [ '"nvim/os/pty_process_unix.h"', private, '"nvim/os/pty_process.h"', public ] },
- { include: [ '"nvim/os/pty_process_win.h"', private, '"nvim/os/pty_process.h"', public ] },
- { include: [ '"nvim/os/unix_defs.h"', private, '"nvim/os/os_defs.h"', public ] },
- { include: [ '"nvim/os/win_defs.h"', private, '"nvim/os/os_defs.h"', public ] },
- { include: [ '"os/env.h.generated.h"', private, '"nvim/os/os.h"', public ] },
- { include: [ '"os/fs.h.generated.h"', private, '"nvim/os/os.h"', public ] },
- { include: [ '"os/mem.h.generated.h"', private, '"nvim/os/os.h"', public ] },
- { include: [ '"os/stdpaths.h.generated.h"', private, '"nvim/os/os.h"', public ] },
- { include: [ '"os/users.h.generated.h"', private, '"nvim/os/os.h"', public ] },
- { include: [ '"regexp_bt.h.generated.h"', private, '"nvim/regexp.h"', public ] },
- { include: [ '"ui_events_call.h.generated.h"', private, '"nvim/ui.h"', public ] },
- { include: [ '"ui_events_client.h.generated.h"', private, '"nvim/ui_client.h"', public ] },
- { include: [ '"ui_events_remote.generated.h"', private, '"nvim/api/ui.h"', public ] },
- { include: [ '"ui_events_remote.h.generated.h"', private, '"nvim/api/ui.h"', public ] },
+ { symbol: [ "FUNC_ATTR_ALLOC_ALIGN", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_ALLOC_SIZE", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_ALLOC_SIZE_PROD", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_ALWAYS_INLINE", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_CONST", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_MALLOC", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NONNULL_ALL", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NONNULL_ARG", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NONNULL_RET", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NORETURN", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NO_SANITIZE_ADDRESS", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_NO_SANITIZE_UNDEFINED", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_PRINTF", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_PURE", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_UNUSED", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "FUNC_ATTR_WARN_UNUSED_RESULT", private, '"nvim/func_attr.h"', public ] },
+ { symbol: [ "MAX", private, '"nvim/macros_defs.h"', public ] },
+ { symbol: [ "MIN", private, '"nvim/macros_defs.h"', public ] },
+ { symbol: [ "extern_proc", private, '<uv.h>', public ] },
+ { symbol: [ "iovec", private, '<sys/uio.h>', public ] },
- # Def to normal headers: nvim/header_defs.h -> nvim/header.h
- #
- # This is a public to public mapping, meaning that while IWYU can use the
- # headers on the left, it will use the headers on the right if possible. This
- # isn't explicitly mentioned in the IWYU docs, this is just my interpretation
- # of its behavior.
- { include: [ '"nvim/buffer_defs.h"', public, '"nvim/buffer.h"', public ] },
- { include: [ '"nvim/ex_cmds_defs.h"', public, '"nvim/ex_cmds.h"', public ] },
- { include: [ '"nvim/ex_eval_defs.h"', public, '"nvim/ex_eval.h"', public ] },
- { include: [ '"nvim/extmark_defs.h"', public, '"nvim/extmark.h"', public ] },
- { include: [ '"nvim/grid_defs.h"', public, '"nvim/grid.h"', public ] },
- { include: [ '"nvim/highlight_defs.h"', public, '"nvim/highlight.h"', public ] },
- { include: [ '"nvim/map_defs.h"', public, '"nvim/map.h"', public ] },
- { include: [ '"nvim/mark_defs.h"', public, '"nvim/mark.h"', public ] },
- { include: [ '"nvim/mbyte_defs.h"', public, '"nvim/mbyte.h"', public ] },
- { include: [ '"nvim/memfile_defs.h"', public, '"nvim/memfile.h"', public ] },
- { include: [ '"nvim/memline_defs.h"', public, '"nvim/memline.h"', public ] },
- { include: [ '"nvim/menu_defs.h"', public, '"nvim/menu.h"', public ] },
- { include: [ '"nvim/msgpack/channel_defs.h"', public, '"nvim/msgpack/channel.h"', public ] },
- { include: [ '"nvim/option_defs.h"', public, '"nvim/option.h"', public ] },
- { include: [ '"nvim/os/fs_defs.h"', public, '"nvim/os/fs.h"', public ] },
- { include: [ '"nvim/os/os_defs.h"', public, '"nvim/os/os.h"', public ] },
- { include: [ '"nvim/regexp_defs.h"', public, '"nvim/regexp.h"', public ] },
- { include: [ '"nvim/sign_defs.h"', public, '"nvim/sign.h"', public ] },
- { include: [ '"nvim/spell_defs.h"', public, '"nvim/spell.h"', public ] },
- { include: [ '"nvim/statusline_defs.h"', public, '"nvim/statusline.h"', public ] },
- { include: [ '"nvim/syntax_defs.h"', public, '"nvim/syntax.h"', public ] },
- { include: [ '"nvim/tui/input_defs.h"', public, '"nvim/tui/input.h"', public ] },
- { include: [ '"nvim/undo_defs.h"', public, '"nvim/undo.h"', public ] },
-
- # Third party headers
- { include: [ "<bits/types/wint_t.h>", private, "<wchar.h>", public ] },
- { include: [ '<arpa/inet.h>', private, '<uv/unix.h>', private ] },
- { include: [ '<bits/termios-c_cc.h>', private, '<termios.h>', private ] },
- { include: [ '<bits/termios-c_cflag.h>', private, '<termios.h>', private ] },
- { include: [ '<bits/termios-c_iflag.h>', private, '<termios.h>', private ] },
- { include: [ '<bits/termios-c_oflag.h>', private, '<termios.h>', private ] },
- { include: [ '<libintl.h>', private, '"nvim/gettext.h"', public ] },
- { include: [ '<netdb.h>', private, '<uv/unix.h>', private ] },
- { include: [ '<netinet/in.h>', private, '<uv/unix.h>', private ] },
- { include: [ '<pthread.h>', private, '"nvim/os/unix_defs.h"', private ] },
- { include: [ '<sys/socket.h>', private, '<uv/unix.h>', private ] },
- { include: [ '<termios.h>', private, '"nvim/os/unix_defs.h"', private ] },
- { include: [ '<unistd.h>', private, '"nvim/os/unix_defs.h"', private ] },
+ { include: [ '"keysets_defs.generated.h"', private, '"nvim/api/private/dispatch.h"', public ] },
{ include: [ '<uv/unix.h>', private, '<uv.h>', public ] },
-
- # Symbols
- { symbol: [ "MAX", private, '"nvim/macros.h"', public ] },
- { symbol: [ "MIN", private, '"nvim/macros.h"', public ] },
- { symbol: [ "SEEK_END", private, '<stdio.h>', public ] },
- { symbol: [ "SEEK_SET", private, '<stdio.h>', public ] },
- { symbol: [ "time_fd", private, '"nvim/globals.h"', public ] },
]
# vim: set ft=toml:
diff --git a/cmake.config/iwyu/posix.imp b/cmake.config/iwyu/posix.imp
new file mode 100644
index 0000000000..2eabd3063b
--- /dev/null
+++ b/cmake.config/iwyu/posix.imp
@@ -0,0 +1,33 @@
+[
+ { include: [ "<arm/limits.h>", private, "<limits.h>", public ] },
+ { include: [ "<asm/ioctls.h>", private, "<bits/ioctls.h>", private ] },
+ { include: [ "<bits/termios-c_lflag.h>", private, "<termios.h>", public ] },
+ { include: [ "<bits/termios-struct.h>", private, "<termios.h>", public ] },
+ { include: [ "<sys/errno.h>", private, "<errno.h>", public ] },
+ { include: [ "<sys/fcntl.h>", private, "<fcntl.h>", public ] },
+ { include: [ "<sys/signal.h>", private, "<signal.h>", public ] },
+ { include: [ "<sys/termios.h>", private, "<termios.h>", public ] },
+ { include: [ '<asm/errno-base.h>', private, '<errno.h>', public ] },
+ { include: [ '<asm/errno.h>', private, '<errno.h>', public ] },
+ { include: [ '<bits/termios-c_cc.h>', private, '<termios.h>', public ] },
+ { include: [ '<bits/termios-c_cflag.h>', private, '<termios.h>', public ] },
+ { include: [ '<bits/termios-c_iflag.h>', private, '<termios.h>', public ] },
+ { include: [ '<bits/termios-c_oflag.h>', private, '<termios.h>', public ] },
+ { include: [ '<sys/unistd.h>', private, '<unistd.h>', public ] },
+
+ { symbol: ["SOCK_STREAM", private, "<sys/socket.h>", public ] },
+ { symbol: ["SSIZE_MAX", private, "<limits.h>", public ] },
+ { symbol: ["S_IREAD", private, "<sys/stat.h>", public ] },
+ { symbol: ["S_IWRITE", private, "<sys/stat.h>", public ] },
+ { symbol: ["_POSIX_VDISABLE", private, "<unistd.h>", public ] },
+ { symbol: ["flock", private, "<sys/file.h>", public ] },
+ { symbol: ["iovec", private, "<sys/uio.h>", public ] },
+ { symbol: ["mode_t", private, "<sys/types.h>", public ] },
+ { symbol: ["ntohs", private, "<arpa/inet.h>", public ] },
+ { symbol: ["pthread_sigmask", private, "<signal.h>", public ] },
+ { symbol: ["sigset_t", private, "<signal.h>", public ] },
+ { symbol: ["ssize_t", private, "<sys/types.h>", public ] },
+ { symbol: ["uid_t", private, "<sys/types.h>", public ] },
+]
+
+# vim: set ft=toml:
diff --git a/cmake.config/pathdef.c.in b/cmake.config/pathdef.c.in
index 5d6dfa6b9f..773fa1d592 100644
--- a/cmake.config/pathdef.c.in
+++ b/cmake.config/pathdef.c.in
@@ -1,8 +1,4 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-#include "${PROJECT_SOURCE_DIR}/src/nvim/vim.h"
+#include "${PROJECT_SOURCE_DIR}/src/nvim/vim_defs.h"
char *default_vim_dir = "${CMAKE_INSTALL_FULL_DATAROOTDIR}/nvim";
char *default_vimruntime_dir = "";
char *default_lib_dir = "${CMAKE_INSTALL_FULL_LIBDIR}/nvim";
-char *compiled_user = "${USERNAME}";
-char *compiled_sys = "${HOSTNAME}";
diff --git a/cmake.config/versiondef.h.in b/cmake.config/versiondef.h.in
index 22cad87249..bda21b27ec 100644
--- a/cmake.config/versiondef.h.in
+++ b/cmake.config/versiondef.h.in
@@ -15,7 +15,7 @@
#define NVIM_API_LEVEL_COMPAT @NVIM_API_LEVEL_COMPAT@
#define NVIM_API_PRERELEASE @NVIM_API_PRERELEASE@
-#define NVIM_VERSION_CFLAGS "@NVIM_VERSION_CFLAGS@"
-#define NVIM_VERSION_BUILD_TYPE "@NVIM_VERSION_BUILD_TYPE@"
+#define NVIM_VERSION_CFLAGS "${CMAKE_C_COMPILER}${C_FLAGS_VERSION_OUTPUT} $<$<CONFIG:Debug>:${CMAKE_C_FLAGS_DEBUG}>$<$<CONFIG:Release>:${CMAKE_C_FLAGS_RELEASE}>$<$<CONFIG:RelWithDebInfo>:${CMAKE_C_FLAGS_RELWITHDEBINFO}>$<$<CONFIG:MinSizeRel>:${CMAKE_C_FLAGS_MINSIZEREL}> $<JOIN:$<TARGET_PROPERTY:nvim,COMPILE_OPTIONS>, > -D$<JOIN:$<TARGET_PROPERTY:nvim,COMPILE_DEFINITIONS>, -D> -I$<JOIN:$<REMOVE_DUPLICATES:$<TARGET_PROPERTY:nvim,INCLUDE_DIRECTORIES>>, -I>"
+#define NVIM_VERSION_BUILD_TYPE "$<CONFIG>"
#endif // AUTO_VERSIONDEF_H
diff --git a/cmake.config/versiondef_old.h.in b/cmake.config/versiondef_old.h.in
new file mode 100644
index 0000000000..4e21e0c7be
--- /dev/null
+++ b/cmake.config/versiondef_old.h.in
@@ -0,0 +1,21 @@
+#ifndef AUTO_VERSIONDEF_H
+#define AUTO_VERSIONDEF_H
+
+#define NVIM_VERSION_MAJOR @NVIM_VERSION_MAJOR@
+#define NVIM_VERSION_MINOR @NVIM_VERSION_MINOR@
+#define NVIM_VERSION_PATCH @NVIM_VERSION_PATCH@
+#define NVIM_VERSION_PRERELEASE "@NVIM_VERSION_PRERELEASE@"
+
+#cmakedefine NVIM_VERSION_MEDIUM "@NVIM_VERSION_MEDIUM@"
+#ifndef NVIM_VERSION_MEDIUM
+# include "auto/versiondef_git.h"
+#endif
+
+#define NVIM_API_LEVEL @NVIM_API_LEVEL@
+#define NVIM_API_LEVEL_COMPAT @NVIM_API_LEVEL_COMPAT@
+#define NVIM_API_PRERELEASE @NVIM_API_PRERELEASE@
+
+#define NVIM_VERSION_CFLAGS "${CMAKE_C_COMPILER}${C_FLAGS_VERSION_OUTPUT} $<$<CONFIG:Debug>:${CMAKE_C_FLAGS_DEBUG}>$<$<CONFIG:Release>:${CMAKE_C_FLAGS_RELEASE}>$<$<CONFIG:RelWithDebInfo>:${CMAKE_C_FLAGS_RELWITHDEBINFO}>$<$<CONFIG:MinSizeRel>:${CMAKE_C_FLAGS_MINSIZEREL}> $<JOIN:$<TARGET_PROPERTY:nvim,COMPILE_OPTIONS>, > -D$<JOIN:$<TARGET_PROPERTY:nvim,COMPILE_DEFINITIONS>, -D> -I$<JOIN:$<TARGET_PROPERTY:nvim,INCLUDE_DIRECTORIES>, -I>"
+#define NVIM_VERSION_BUILD_TYPE "$<CONFIG>"
+
+#endif // AUTO_VERSIONDEF_H
diff --git a/cmake.deps/CMakeLists.txt b/cmake.deps/CMakeLists.txt
index 90ae91463d..aa4a34709a 100644
--- a/cmake.deps/CMakeLists.txt
+++ b/cmake.deps/CMakeLists.txt
@@ -10,21 +10,17 @@ endif()
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" "${PROJECT_SOURCE_DIR}/../cmake")
include(CheckCCompilerFlag)
-include(Util)
-
-set(DEPS_CMAKE_ARGS
- -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
- -DCMAKE_GENERATOR=${CMAKE_GENERATOR}
- -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}
- -DCMAKE_POSITION_INDEPENDENT_CODE=ON)
+include(ExternalProject)
+include(FindPackageHandleStandardArgs)
-set(DEPS_CMAKE_CACHE_ARGS -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES})
+include(Deps)
+include(Find)
+include(Util)
set_default_buildtype()
-
get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(NOT isMultiConfig)
- list(APPEND DEPS_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
+ list(APPEND DEPS_CMAKE_ARGS -D CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
endif()
set(DEFAULT_MAKE_CFLAGS CFLAGS+=-g)
@@ -34,23 +30,7 @@ if(HAS_OG_FLAG)
set(DEFAULT_MAKE_CFLAGS CFLAGS+=-Og ${DEFAULT_MAKE_CFLAGS})
endif()
-if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
- # pkg-config 29.2 has a bug on OpenBSD which causes it to drop any paths that
- # *contain* system include paths. To avoid this, we prefix what would be
- # "/usr/include" as "/_usr/include".
- # This check is also performed in the root CMakeLists.txt
- # https://github.com/neovim/neovim/pull/14745#issuecomment-860201794
- set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/_usr" CACHE PATH "Dependencies install directory.")
-else()
- set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr" CACHE PATH "Dependencies install directory.")
-endif()
-
-list(APPEND DEPS_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR})
-
-set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin" CACHE PATH "Dependencies binary install directory.")
-set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib" CACHE PATH "Dependencies library install directory.")
-set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build" CACHE PATH "Dependencies build directory.")
-set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads" CACHE PATH "Dependencies download directory.")
+set(DEPS_INCLUDE_FLAGS "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.1")
option(USE_BUNDLED "Use bundled dependencies." ON)
@@ -60,9 +40,9 @@ option(USE_BUNDLED_LIBVTERM "Use the bundled libvterm." ${USE_BUNDLED})
option(USE_BUNDLED_LIBUV "Use the bundled libuv." ${USE_BUNDLED})
option(USE_BUNDLED_MSGPACK "Use the bundled msgpack." ${USE_BUNDLED})
option(USE_BUNDLED_LUAJIT "Use the bundled version of luajit." ${USE_BUNDLED})
-option(USE_BUNDLED_LUAROCKS "Use the bundled version of luarocks." ${USE_BUNDLED})
option(USE_BUNDLED_LUV "Use the bundled version of luv." ${USE_BUNDLED})
-#XXX(tarruda): Lua is only used for debugging the functional test client, no
+option(USE_BUNDLED_LPEG "Use the bundled lpeg." ${USE_BUNDLED})
+#XXX(tarruda): Lua is only used for debugging the functional test client, don't
# build it unless explicitly requested
option(USE_BUNDLED_LUA "Use the bundled version of lua." OFF)
option(USE_BUNDLED_TS_PARSERS "Use the bundled treesitter parsers." ${USE_BUNDLED})
@@ -78,53 +58,9 @@ endif()
option(USE_EXISTING_SRC_DIR "Skip download of deps sources in case of existing source directory." OFF)
-find_package(Git)
-if(NOT Git_FOUND)
- message(FATAL_ERROR "Git is required to apply patches.")
-endif()
-
-if(UNIX)
- find_program(MAKE_PRG NAMES gmake make)
- if(NOT MAKE_PRG)
- message(FATAL_ERROR "GNU Make is required to build the dependencies.")
- else()
- message(STATUS "Found GNU Make at ${MAKE_PRG}")
- endif()
-endif()
-
-# When using make, use the $(MAKE) variable to avoid warning about the job
-# server.
-if(CMAKE_GENERATOR MATCHES "Makefiles")
- set(MAKE_PRG "$(MAKE)")
-endif()
-
-if(MINGW AND CMAKE_GENERATOR MATCHES "Ninja")
- find_program(MAKE_PRG NAMES mingw32-make)
- if(NOT MAKE_PRG)
- message(FATAL_ERROR "GNU Make for mingw32 is required to build the dependencies.")
- else()
- message(STATUS "Found GNU Make for mingw32: ${MAKE_PRG}")
- endif()
-endif()
-
-set(DEPS_C_COMPILER "${CMAKE_C_COMPILER}")
-
-if(CMAKE_OSX_SYSROOT)
- set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -isysroot${CMAKE_OSX_SYSROOT}")
-endif()
-
-if(CMAKE_OSX_ARCHITECTURES)
- # The LuaJIT build does not like being passed multiple `-arch` flags
- # so we handle a universal build the old-fashioned way.
- set(LUAJIT_C_COMPILER "${DEPS_C_COMPILER}")
- foreach(ARCH IN LISTS CMAKE_OSX_ARCHITECTURES)
- set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -arch ${ARCH}")
- endforeach()
-endif()
-
# If the macOS deployment target is not set manually (via $MACOSX_DEPLOYMENT_TARGET),
# fall back to local system version. Needs to be done here and in top-level CMakeLists.txt.
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+if(APPLE)
if(NOT CMAKE_OSX_DEPLOYMENT_TARGET)
execute_process(COMMAND sw_vers -productVersion
OUTPUT_VARIABLE MACOS_VERSION
@@ -134,71 +70,24 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
message(STATUS "Using deployment target ${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
-include(ExternalProject)
-set_directory_properties(PROPERTIES EP_PREFIX "${DEPS_BUILD_DIR}")
-
-set(LIBUV_URL https://github.com/libuv/libuv/archive/f610339f74f7f0fcd183533d2c965ce1468b44c6.tar.gz)
-set(LIBUV_SHA256 d5f22303ba44ac60d3232b1977b404d23a349ae4e8cb83f00e7122fafe38d8c9)
-
-set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-4.0.0/msgpack-c-4.0.0.tar.gz)
-set(MSGPACK_SHA256 420fe35e7572f2a168d17e660ef981a589c9cbe77faa25eb34a520e1fcc032c8)
-
-# https://github.com/LuaJIT/LuaJIT/tree/v2.1
-set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/d0e88930ddde28ff662503f9f20facf34f7265aa.tar.gz)
-set(LUAJIT_SHA256 aa354d1265814db5a1ee9dfff6049e19b148e1fd818f1ecfa4fcf2b19f6e4dd9)
-
-set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz)
-set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333)
-
-set(LUAROCKS_URL https://github.com/luarocks/luarocks/archive/v3.8.0.tar.gz)
-set(LUAROCKS_SHA256 ab6612ca9ab87c6984871d2712d05525775e8b50172701a0a1cabddf76de2be7)
-
-set(UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz)
-set(UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487)
-
-set(LIBTERMKEY_URL https://www.leonerd.org.uk/code/libtermkey/libtermkey-0.22.tar.gz)
-set(LIBTERMKEY_SHA256 6945bd3c4aaa83da83d80a045c5563da4edd7d0374c62c0d35aec09eb3014600)
-
-set(LIBVTERM_URL https://www.leonerd.org.uk/code/libvterm/libvterm-0.3.1.tar.gz)
-set(LIBVTERM_SHA256 25a8ad9c15485368dfd0a8a9dca1aec8fea5c27da3fa74ec518d5d3787f0c397)
-
-set(LUV_VERSION 1.44.2-1)
-set(LUV_URL https://github.com/luvit/luv/archive/80c8c00baebe3e994d1616d4b54097c2d6e14834.tar.gz)
-set(LUV_SHA256 bcd3ffc7bc80053ab0ec87f0fe83c7632c7619879b2eb75fba88ddb3bee620e8)
-
-set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz)
-set(LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416)
-
-# cat.exe curl.exe curl-ca-bundle.crt diff.exe tee.exe xxd.exe
-set(WINTOOLS_URL https://github.com/neovim/deps/raw/c1e7dd8de9e1b18d11dcfa0a192cd029262e5303/opt/win32tools.zip)
-set(WINTOOLS_SHA256 3c4c490a3d392ceeb1347cb77cc821a31900b688a2189276d3a1131a3f21daf1)
-
-set(WINGUI_URL https://github.com/equalsraf/neovim-qt/releases/download/v0.2.17/neovim-qt.zip)
-set(WINGUI_SHA256 502e386eef677c2c2e0c11d8cbb27f3e12b4d96818369417e8da4129c4580c25)
-
-set(WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.0.4/win32yank-x64.zip)
-set(WIN32YANK_X86_64_SHA256 33a747a92da60fb65e668edbf7661d3d902411a2d545fe9dc08623cecd142a20)
-
-set(GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.20.1.tar.gz)
-set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c)
-
-set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz)
-set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178)
-
-set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.2.tar.gz)
-set(TREESITTER_C_SHA256 af66fde03feb0df4faf03750102a0d265b007e5d957057b6b293c13116a70af2 )
-
-set(TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.14.tar.gz)
-set(TREESITTER_LUA_SHA256 930d0370dc15b66389869355c8e14305b9ba7aafd36edbfdb468c8023395016d)
-
-set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/55ff1b080c09edeced9b748cf4c16d0b49d17fb9.tar.gz)
-set(TREESITTER_VIM_SHA256 1b1cd39e33c8fb02fa7fe3977e844883c2a8508a7edd621f2d21e39a9aeefa92)
-
-set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v1.3.0.tar.gz)
-set(TREESITTER_HELP_SHA256 f33f6d49c7d71feb2fd68ef2b2684da150f9f8e486ad9726213631d673942331)
-
-set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/eb970a83a107cbaba52e31588355c0b09b3a308a.tar.gz)
-set(TREESITTER_SHA256 58063721182f182fb9ec5481794f2ba594e52d6c2497e0214570535054dfc78d)
+set_directory_properties(PROPERTIES
+ EP_PREFIX "${DEPS_BUILD_DIR}"
+ CMAKE_CONFIGURE_DEPENDS deps.txt)
+
+file(READ deps.txt DEPENDENCIES)
+STRING(REGEX REPLACE "\n" ";" DEPENDENCIES "${DEPENDENCIES}")
+foreach(dep ${DEPENDENCIES})
+ STRING(REGEX REPLACE " " ";" dep "${dep}")
+ list(GET dep 0 name)
+ list(GET dep 1 value)
+ if(NOT ${name})
+ # _URL variables must NOT be set when USE_EXISTING_SRC_DIR is set,
+ # otherwise ExternalProject will try to re-download the sources.
+ if(NOT USE_EXISTING_SRC_DIR)
+ set(${name} ${value})
+ endif()
+ endif()
+endforeach()
if(USE_BUNDLED_UNIBILIUM)
include(BuildUnibilium)
@@ -231,14 +120,14 @@ if(USE_BUNDLED_LUA)
include(BuildLua)
endif()
-if(USE_BUNDLED_LUAROCKS)
- include(BuildLuarocks)
-endif()
-
if(USE_BUNDLED_LUV)
include(BuildLuv)
endif()
+if(USE_BUNDLED_LPEG)
+ include(BuildLpeg)
+endif()
+
if(USE_BUNDLED_GETTEXT)
include(BuildGettext)
endif()
@@ -258,33 +147,10 @@ endif()
if(WIN32)
include(GetBinaryDeps)
- GetBinaryDep(TARGET wintools
- INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory . ${DEPS_INSTALL_DIR}/bin)
-
- GetBinaryDep(TARGET wingui
- INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory bin ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E copy_directory share ${DEPS_INSTALL_DIR}/share)
+ GetExecutable(TARGET cat)
+ GetExecutable(TARGET tee)
+ GetExecutable(TARGET xxd)
GetBinaryDep(TARGET win32yank_X86_64
- INSTALL_COMMAND ${CMAKE_COMMAND} -E copy win32yank.exe ${DEPS_INSTALL_DIR}/bin)
-endif()
-
-# clean-shared-libraries removes ${DEPS_INSTALL_DIR}/lib/nvim/parser/c.dll,
-# resulting in MSVC build failure in CI.
-if (MSVC)
- set(ALL_DEPS ${THIRD_PARTY_DEPS})
-else()
- add_custom_target(clean-shared-libraries
- COMMAND ${CMAKE_COMMAND}
- -DREMOVE_FILE_GLOB=${DEPS_INSTALL_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}*${CMAKE_SHARED_LIBRARY_SUFFIX}*
- -P ${PROJECT_SOURCE_DIR}/cmake/RemoveFiles.cmake
- DEPENDS ${THIRD_PARTY_DEPS}
- )
- set(ALL_DEPS clean-shared-libraries)
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E copy win32yank.exe ${DEPS_BIN_DIR})
endif()
-
-# TODO(justinmk): does anyone use this target?
-add_custom_target(third-party ALL
- COMMAND ${CMAKE_COMMAND} -E touch .third-party
- DEPENDS ${ALL_DEPS}
-)
diff --git a/cmake.deps/CMakePresets.json b/cmake.deps/CMakePresets.json
new file mode 100644
index 0000000000..f399dad217
--- /dev/null
+++ b/cmake.deps/CMakePresets.json
@@ -0,0 +1,25 @@
+{
+ "version": 3,
+ "configurePresets": [
+ {
+ "name": "base",
+ "generator": "Ninja",
+ "binaryDir": "${sourceDir}/../.deps",
+ "hidden": true
+ },
+ {
+ "name": "ci",
+ "inherits": ["base"]
+ },
+ {
+ "name": "external_deps",
+ "description": "Build neovim with external deps on ubuntu",
+ "cacheVariables": {
+ "USE_BUNDLED":"OFF",
+ "USE_BUNDLED_LIBVTERM":"ON",
+ "USE_BUNDLED_TS":"ON"
+ },
+ "inherits": ["base"]
+ }
+ ]
+}
diff --git a/cmake.deps/cmake/BuildGettext.cmake b/cmake.deps/cmake/BuildGettext.cmake
index 1f9fd38702..33cfbe1a22 100644
--- a/cmake.deps/cmake/BuildGettext.cmake
+++ b/cmake.deps/cmake/BuildGettext.cmake
@@ -1,24 +1,20 @@
if(MSVC)
- if(USE_EXISTING_SRC_DIR)
- unset(GETTEXT_URL)
- endif()
ExternalProject_Add(gettext
URL ${GETTEXT_URL}
URL_HASH SHA256=${GETTEXT_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/gettext
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/GettextCMakeLists.txt
${DEPS_BUILD_DIR}/src/gettext/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- -DLIBICONV_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include
- -DLIBICONV_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libcharset${CMAKE_STATIC_LIBRARY_SUFFIX}$<SEMICOLON>${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libiconv${CMAKE_STATIC_LIBRARY_SUFFIX}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
+ -D LIBICONV_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include
+ -D LIBICONV_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libcharset${CMAKE_STATIC_LIBRARY_SUFFIX}$<SEMICOLON>${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libiconv${CMAKE_STATIC_LIBRARY_SUFFIX}
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
else()
message(FATAL_ERROR "Trying to build gettext in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}")
endif()
-list(APPEND THIRD_PARTY_DEPS gettext)
if(USE_BUNDLED_LIBICONV)
add_dependencies(gettext libiconv)
endif()
diff --git a/cmake.deps/cmake/BuildLibiconv.cmake b/cmake.deps/cmake/BuildLibiconv.cmake
index 4b9c07ed6c..362e9b4609 100644
--- a/cmake.deps/cmake/BuildLibiconv.cmake
+++ b/cmake.deps/cmake/BuildLibiconv.cmake
@@ -1,19 +1,14 @@
if(MSVC)
- if(USE_EXISTING_SRC_DIR)
- unset(LIBICONV_URL)
- endif()
ExternalProject_Add(libiconv
URL ${LIBICONV_URL}
URL_HASH SHA256=${LIBICONV_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libiconv
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibiconvCMakeLists.txt
${DEPS_BUILD_DIR}/src/libiconv/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
else()
message(FATAL_ERROR "Trying to build libiconv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}")
endif()
-
-list(APPEND THIRD_PARTY_DEPS libiconv)
diff --git a/cmake.deps/cmake/BuildLibtermkey.cmake b/cmake.deps/cmake/BuildLibtermkey.cmake
index 6457a864ba..185b276def 100644
--- a/cmake.deps/cmake/BuildLibtermkey.cmake
+++ b/cmake.deps/cmake/BuildLibtermkey.cmake
@@ -1,18 +1,13 @@
-if(USE_EXISTING_SRC_DIR)
- unset(LIBTERMKEY_URL)
-endif()
ExternalProject_Add(libtermkey
URL ${LIBTERMKEY_URL}
URL_HASH SHA256=${LIBTERMKEY_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libtermkey
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
- ${CMAKE_CURRENT_SOURCE_DIR}/cmake/libtermkeyCMakeLists.txt
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibtermkeyCMakeLists.txt
${DEPS_BUILD_DIR}/src/libtermkey/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" # Hack to avoid -rdynamic in Mingw
- -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include
- -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS libtermkey)
+ -D CMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" # Hack to avoid -rdynamic in Mingw
+ -D UNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include
+ -D UNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX}
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildLibuv.cmake b/cmake.deps/cmake/BuildLibuv.cmake
index eb88458644..e7f7fdf253 100644
--- a/cmake.deps/cmake/BuildLibuv.cmake
+++ b/cmake.deps/cmake/BuildLibuv.cmake
@@ -1,15 +1,11 @@
-if(USE_EXISTING_SRC_DIR)
- unset(LIBUV_URL)
-endif()
ExternalProject_Add(libuv
URL ${LIBUV_URL}
URL_HASH SHA256=${LIBUV_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libuv
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- -DCMAKE_INSTALL_LIBDIR=lib
- -DBUILD_TESTING=OFF
- -DLIBUV_BUILD_SHARED=OFF
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS libuv)
+ -D CMAKE_INSTALL_LIBDIR=lib
+ -D BUILD_TESTING=OFF
+ -D LIBUV_BUILD_SHARED=OFF
+ -D UV_LINT_W4=OFF
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildLibvterm.cmake b/cmake.deps/cmake/BuildLibvterm.cmake
index b75987eb24..63f5872cb2 100644
--- a/cmake.deps/cmake/BuildLibvterm.cmake
+++ b/cmake.deps/cmake/BuildLibvterm.cmake
@@ -1,15 +1,10 @@
-if(USE_EXISTING_SRC_DIR)
- unset(LIBVTERM_URL)
-endif()
ExternalProject_Add(libvterm
URL ${LIBVTERM_URL}
URL_HASH SHA256=${LIBVTERM_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/libvterm
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibvtermCMakeLists.txt
${DEPS_BUILD_DIR}/src/libvterm/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS libvterm)
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildLpeg.cmake b/cmake.deps/cmake/BuildLpeg.cmake
new file mode 100644
index 0000000000..bba23fe42b
--- /dev/null
+++ b/cmake.deps/cmake/BuildLpeg.cmake
@@ -0,0 +1,32 @@
+set(LPEG_INCLUDE_FLAGS ${DEPS_INCLUDE_FLAGS})
+
+if(NOT USE_BUNDLED_LUAJIT AND NOT USE_BUNDLED_LUA)
+ find_package(Luajit)
+ if(LUAJIT_FOUND)
+ string(CONCAT LPEG_INCLUDE_FLAGS ${DEPS_INCLUDE_FLAGS} " -I${LUAJIT_INCLUDE_DIR}")
+ else()
+ find_package(Lua 5.1 EXACT)
+ if(LUA_FOUND)
+ string(CONCAT LPEG_INCLUDE_FLAGS ${DEPS_INCLUDE_FLAGS} " -I${LUA_INCLUDE_DIR}")
+ endif()
+ endif()
+endif()
+
+list(APPEND LPEG_CMAKE_ARGS -DCMAKE_C_FLAGS=${LPEG_INCLUDE_FLAGS})
+
+ExternalProject_Add(lpeg
+ URL ${LPEG_URL}
+ URL_HASH SHA256=${LPEG_SHA256}
+ DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/lpeg
+ PATCH_COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/LpegCMakeLists.txt
+ ${DEPS_BUILD_DIR}/src/lpeg/CMakeLists.txt
+ CMAKE_ARGS ${DEPS_CMAKE_ARGS} ${LPEG_CMAKE_ARGS}
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
+
+if(USE_BUNDLED_LUAJIT)
+ add_dependencies(lpeg luajit)
+elseif(USE_BUNDLED_LUA)
+ add_dependencies(lpeg lua)
+endif()
diff --git a/cmake.deps/cmake/BuildLua.cmake b/cmake.deps/cmake/BuildLua.cmake
index b5ac8368a6..96b7a903f9 100644
--- a/cmake.deps/cmake/BuildLua.cmake
+++ b/cmake.deps/cmake/BuildLua.cmake
@@ -1,13 +1,13 @@
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LUA_TARGET linux)
-elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+elseif(APPLE)
set(LUA_TARGET macosx)
elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
set(LUA_TARGET freebsd)
elseif(CMAKE_SYSTEM_NAME MATCHES "BSD")
- set(CMAKE_LUA_TARGET bsd)
+ set(LUA_TARGET bsd)
elseif(CMAKE_SYSTEM_NAME MATCHES "^MINGW")
- set(CMAKE_LUA_TARGET mingw)
+ set(LUA_TARGET mingw)
else()
if(UNIX)
set(LUA_TARGET posix)
@@ -16,10 +16,10 @@ else()
endif()
endif()
-set(LUA_CFLAGS "-O0 -g3 -fPIC")
+set(LUA_CFLAGS "-O2 -g3 -fPIC")
set(LUA_LDFLAGS "")
-if(CLANG_ASAN_UBSAN)
+if(ENABLE_ASAN_UBSAN)
set(LUA_CFLAGS "${LUA_CFLAGS} -fsanitize=address")
set(LUA_CFLAGS "${LUA_CFLAGS} -fno-omit-frame-pointer")
set(LUA_CFLAGS "${LUA_CFLAGS} -fno-optimize-sibling-calls")
@@ -40,30 +40,12 @@ set(LUA_CONFIGURE_COMMAND
-i ${DEPS_BUILD_DIR}/src/lua/src/luaconf.h)
set(LUA_INSTALL_TOP_ARG "INSTALL_TOP=${DEPS_INSTALL_DIR}")
-message(STATUS "Lua target is ${LUA_TARGET}")
-
-if(USE_EXISTING_SRC_DIR)
- unset(LUA_URL)
-endif()
ExternalProject_Add(lua
URL ${LUA_URL}
URL_HASH SHA256=${LUA_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/lua
CONFIGURE_COMMAND "${LUA_CONFIGURE_COMMAND}"
BUILD_IN_SOURCE 1
BUILD_COMMAND ${MAKE_PRG} ${LUA_INSTALL_TOP_ARG} ${LUA_TARGET}
- INSTALL_COMMAND ${MAKE_PRG} ${LUA_INSTALL_TOP_ARG} install)
-
-list(APPEND THIRD_PARTY_DEPS lua)
-
-set(BUSTED ${DEPS_INSTALL_DIR}/bin/busted)
-set(BUSTED_LUA ${BUSTED}-lua)
-
-add_custom_command(OUTPUT ${BUSTED_LUA}
- COMMAND sed -e 's/^exec/exec $$LUA_DEBUGGER/' -e 's/jit//g' < ${BUSTED} > ${BUSTED_LUA} && chmod +x ${BUSTED_LUA}
- DEPENDS lua busted ${BUSTED})
-add_custom_target(busted-lua
- DEPENDS ${DEPS_INSTALL_DIR}/bin/busted-lua)
-
-list(APPEND THIRD_PARTY_DEPS busted-lua)
+ INSTALL_COMMAND ${MAKE_PRG} ${LUA_INSTALL_TOP_ARG} install
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildLuajit.cmake b/cmake.deps/cmake/BuildLuajit.cmake
index 1476ac31f4..aa4c8e9293 100644
--- a/cmake.deps/cmake/BuildLuajit.cmake
+++ b/cmake.deps/cmake/BuildLuajit.cmake
@@ -15,30 +15,20 @@ function(BuildLuajit)
set(_luajit_TARGET "luajit")
endif()
- if(USE_EXISTING_SRC_DIR)
- unset(LUAJIT_URL)
- endif()
ExternalProject_Add(${_luajit_TARGET}
URL ${LUAJIT_URL}
URL_HASH SHA256=${LUAJIT_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/luajit
CONFIGURE_COMMAND "${_luajit_CONFIGURE_COMMAND}"
BUILD_IN_SOURCE 1
BUILD_COMMAND "${_luajit_BUILD_COMMAND}"
INSTALL_COMMAND "${_luajit_INSTALL_COMMAND}"
- DEPENDS "${_luajit_DEPENDS}")
-
- # Create symlink for development version manually.
- if(UNIX)
- add_custom_command(
- TARGET ${_luajit_TARGET}
- COMMAND ${CMAKE_COMMAND} -E create_symlink luajit-2.1.0-beta3 ${DEPS_BIN_DIR}/${_luajit_TARGET})
- endif()
+ DEPENDS "${_luajit_DEPENDS}"
+ ${EXTERNALPROJECT_OPTIONS})
endfunction()
check_c_compiler_flag(-fno-stack-check HAS_NO_STACK_CHECK)
-if(CMAKE_SYSTEM_NAME MATCHES "Darwin" AND HAS_NO_STACK_CHECK)
+if(APPLE AND HAS_NO_STACK_CHECK)
set(NO_STACK_CHECK "CFLAGS+=-fno-stack-check")
else()
set(NO_STACK_CHECK "")
@@ -48,7 +38,7 @@ if(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
else()
set(AMD64_ABI "")
endif()
-set(BUILDCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC
+set(BUILDCMD_UNIX ${MAKE_PRG} -j CFLAGS=-fPIC
CFLAGS+=-DLUA_USE_APICHECK
CFLAGS+=-funwind-tables
${NO_STACK_CHECK}
@@ -58,7 +48,7 @@ set(BUILDCMD_UNIX ${MAKE_PRG} CFLAGS=-fPIC
# Setting MACOSX_DEPLOYMENT_TARGET is mandatory for LuaJIT; use version set by
# cmake.deps/CMakeLists.txt (either environment variable or current system version).
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+if(APPLE)
set(DEPLOYMENT_TARGET "MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
@@ -69,6 +59,11 @@ if((UNIX AND NOT APPLE) OR (APPLE AND NOT CMAKE_OSX_ARCHITECTURES))
elseif(CMAKE_OSX_ARCHITECTURES AND APPLE)
+ set(LUAJIT_C_COMPILER "${CMAKE_C_COMPILER}")
+ if(CMAKE_OSX_SYSROOT)
+ set(LUAJIT_C_COMPILER "${LUAJIT_C_COMPILER} -isysroot${CMAKE_OSX_SYSROOT}")
+ endif()
+
# Passing multiple `-arch` flags to the LuaJIT build will cause it to fail.
# To get a working universal build, we build each requested architecture slice
# individually then `lipo` them all up.
@@ -116,37 +111,33 @@ elseif(MINGW)
# Build a DLL too
COMMAND ${LUAJIT_MAKE_PRG} CC=${DEPS_C_COMPILER} BUILDMODE=dynamic
- INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/luajit.exe ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/luajit.exe ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_LIB_DIR}
# Luarocks searches for lua51.dll in lib
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_INSTALL_DIR}/lib
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/libluajit.a ${DEPS_INSTALL_DIR}/lib
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_LIB_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/libluajit.a ${DEPS_LIB_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.1
COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.1 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake
- COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin/lua/jit
- COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/bin/lua/jit
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/share/luajit-2.1/jit
)
elseif(MSVC)
BuildLuaJit(
BUILD_COMMAND ${CMAKE_COMMAND} -E chdir ${DEPS_BUILD_DIR}/src/luajit/src ${DEPS_BUILD_DIR}/src/luajit/src/msvcbuild.bat
- INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/luajit.exe ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_INSTALL_DIR}/bin
- COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/lib
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/luajit.exe ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.dll ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_LIB_DIR}
# Luarocks searches for lua51.lib
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/lua51.lib
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_LIB_DIR}/lua51.lib
# Luv searches for luajit.lib
- COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/luajit.lib
+ COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_LIB_DIR}/luajit.lib
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.1
COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.1 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake
- COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin/lua/jit
- COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/bin/lua/jit
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/share/luajit-2.1/jit
)
else()
message(FATAL_ERROR "Trying to build luajit in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}")
endif()
-
-list(APPEND THIRD_PARTY_DEPS luajit)
diff --git a/cmake.deps/cmake/BuildLuarocks.cmake b/cmake.deps/cmake/BuildLuarocks.cmake
deleted file mode 100644
index b84ce34d45..0000000000
--- a/cmake.deps/cmake/BuildLuarocks.cmake
+++ /dev/null
@@ -1,180 +0,0 @@
-# Luarocks recipe. Luarocks is only required when building Neovim.
-# NOTE: LuaRocks rocks need to "DEPENDS" on the previous module, because
-# running luarocks in parallel will break, e.g. when some rocks have
-# the same dependency..
-
-option(USE_BUNDLED_BUSTED "Use the bundled version of busted to run tests." ON)
-
-# The luarocks binary location
-set(LUAROCKS_BINARY ${DEPS_BIN_DIR}/luarocks)
-
-# Arguments for calls to 'luarocks build'
-if(NOT MSVC)
- # In MSVC don't pass the compiler/linker to luarocks, the bundled
- # version already knows, and passing them here breaks the build
- set(LUAROCKS_BUILDARGS CC=${DEPS_C_COMPILER} LD=${DEPS_C_COMPILER})
-endif()
-
-# Lua version, used with rocks directories.
-# Defaults to 5.1 for bundled LuaJIT/Lua.
-set(LUA_VERSION "5.1")
-
-if(UNIX)
-
- if(USE_BUNDLED_LUAJIT)
- list(APPEND LUAROCKS_OPTS
- --with-lua=${DEPS_INSTALL_DIR}
- --with-lua-include=${DEPS_INSTALL_DIR}/include/luajit-2.1
- --with-lua-interpreter=luajit)
- elseif(USE_BUNDLED_LUA)
- list(APPEND LUAROCKS_OPTS
- --with-lua=${DEPS_INSTALL_DIR})
- else()
- find_package(LuaJit)
- if(LUAJIT_FOUND)
- list(APPEND LUAROCKS_OPTS
- --with-lua-include=${LUAJIT_INCLUDE_DIRS}
- --with-lua-interpreter=luajit)
- endif()
-
- # Get LUA_VERSION used with rocks output.
- if(LUAJIT_FOUND)
- set(LUA_EXE "luajit")
- else()
- set(LUA_EXE "lua")
- endif()
- execute_process(
- COMMAND ${LUA_EXE} -e "print(string.sub(_VERSION, 5))"
- OUTPUT_VARIABLE LUA_VERSION
- ERROR_VARIABLE ERR
- RESULT_VARIABLE RES)
- if(NOT RES EQUAL 0)
- message(FATAL_ERROR "Could not get LUA_VERSION with ${LUA_EXE}: ${ERR}")
- endif()
- endif()
-
- set(LUAROCKS_CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/src/luarocks/configure
- --prefix=${DEPS_INSTALL_DIR} --force-config ${LUAROCKS_OPTS})
- set(LUAROCKS_INSTALL_COMMAND ${MAKE_PRG} -j1 bootstrap)
-elseif(MSVC OR MINGW)
-
- if(MINGW)
- set(COMPILER_FLAG /MW)
- elseif(MSVC)
- set(COMPILER_FLAG /MSVC)
- endif()
-
- # Ignore USE_BUNDLED_LUAJIT - always ON for native Win32
- set(LUAROCKS_INSTALL_COMMAND install.bat /FORCECONFIG /NOREG /NOADMIN /Q /F
- /LUA ${DEPS_INSTALL_DIR}
- /LIB ${DEPS_LIB_DIR}
- /BIN ${DEPS_BIN_DIR}
- /INC ${DEPS_INSTALL_DIR}/include/luajit-2.1
- /P ${DEPS_INSTALL_DIR}/luarocks /TREE ${DEPS_INSTALL_DIR}
- /SCRIPTS ${DEPS_BIN_DIR}
- /CMOD ${DEPS_BIN_DIR}
- ${COMPILER_FLAG}
- /LUAMOD ${DEPS_BIN_DIR}/lua)
-
- set(LUAROCKS_BINARY ${DEPS_INSTALL_DIR}/luarocks/luarocks.bat)
-else()
- message(FATAL_ERROR "Trying to build luarocks in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}")
-endif()
-
-if(USE_EXISTING_SRC_DIR)
- unset(LUAROCKS_URL)
-endif()
-ExternalProject_Add(luarocks
- URL ${LUAROCKS_URL}
- URL_HASH SHA256=${LUAROCKS_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
- DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/luarocks
- BUILD_IN_SOURCE 1
- CONFIGURE_COMMAND "${LUAROCKS_CONFIGURE_COMMAND}"
- BUILD_COMMAND ""
- INSTALL_COMMAND "${LUAROCKS_INSTALL_COMMAND}")
-
-list(APPEND THIRD_PARTY_DEPS luarocks)
-
-if(USE_BUNDLED_LUAJIT)
- add_dependencies(luarocks luajit)
-elseif(USE_BUNDLED_LUA)
- add_dependencies(luarocks lua)
-endif()
-set(ROCKS_DIR ${DEPS_LIB_DIR}/luarocks/rocks-${LUA_VERSION})
-
-# mpack
-add_custom_command(OUTPUT ${ROCKS_DIR}/mpack
- COMMAND ${LUAROCKS_BINARY} build mpack 1.0.8-0 ${LUAROCKS_BUILDARGS}
- DEPENDS luarocks)
-add_custom_target(mpack DEPENDS ${ROCKS_DIR}/mpack)
-list(APPEND THIRD_PARTY_DEPS mpack)
-
-# lpeg
-add_custom_command(OUTPUT ${ROCKS_DIR}/lpeg
- COMMAND ${LUAROCKS_BINARY} build lpeg 1.0.2-1 ${LUAROCKS_BUILDARGS}
- DEPENDS mpack)
-add_custom_target(lpeg DEPENDS ${ROCKS_DIR}/lpeg)
-list(APPEND THIRD_PARTY_DEPS lpeg)
-
-if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA)
- # luabitop
- add_custom_command(OUTPUT ${ROCKS_DIR}/luabitop
- COMMAND ${LUAROCKS_BINARY} build luabitop 1.0.2-3 ${LUAROCKS_BUILDARGS}
- DEPENDS lpeg)
- add_custom_target(luabitop DEPENDS ${ROCKS_DIR}/luabitop)
- list(APPEND THIRD_PARTY_DEPS luabitop)
-endif()
-
-if(USE_BUNDLED_BUSTED)
- if((NOT USE_BUNDLED_LUAJIT) AND USE_BUNDLED_LUA)
- set(PENLIGHT_DEPENDS luabitop)
- else()
- set(PENLIGHT_DEPENDS lpeg)
- endif()
-
- # penlight
- add_custom_command(OUTPUT ${ROCKS_DIR}/penlight
- COMMAND ${LUAROCKS_BINARY} build penlight 1.5.4-1 ${LUAROCKS_BUILDARGS}
- DEPENDS ${PENLIGHT_DEPENDS})
- add_custom_target(penlight DEPENDS ${ROCKS_DIR}/penlight)
-
- # busted
- if(WIN32)
- set(BUSTED_EXE "${DEPS_BIN_DIR}/busted.bat")
- set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck.bat")
- else()
- set(BUSTED_EXE "${DEPS_BIN_DIR}/busted")
- set(LUACHECK_EXE "${DEPS_BIN_DIR}/luacheck")
- endif()
- add_custom_command(OUTPUT ${BUSTED_EXE}
- COMMAND ${LUAROCKS_BINARY} build busted 2.1.1 ${LUAROCKS_BUILDARGS}
- DEPENDS penlight)
- add_custom_target(busted DEPENDS ${BUSTED_EXE})
-
- # luacheck
- add_custom_command(OUTPUT ${LUACHECK_EXE}
- COMMAND ${LUAROCKS_BINARY} build luacheck 0.23.0-1 ${LUAROCKS_BUILDARGS}
- DEPENDS busted)
- add_custom_target(luacheck DEPENDS ${LUACHECK_EXE})
-
- # luv
- set(LUV_DEPS luacheck)
- if(USE_BUNDLED_LUV)
- set(NVIM_CLIENT_DEPS luacheck luv-static lua-compat-5.3)
- else()
- add_custom_command(OUTPUT ${ROCKS_DIR}/luv
- COMMAND ${LUAROCKS_BINARY} build luv ${LUV_VERSION} ${LUAROCKS_BUILDARGS}
- DEPENDS luacheck)
- add_custom_target(luv DEPENDS ${ROCKS_DIR}/luv)
- set(NVIM_CLIENT_DEPS luv)
- endif()
-
- # nvim-client: https://github.com/neovim/lua-client
- add_custom_command(OUTPUT ${ROCKS_DIR}/nvim-client
- COMMAND ${LUAROCKS_BINARY} build nvim-client 0.2.4-1 ${LUAROCKS_BUILDARGS}
- DEPENDS ${NVIM_CLIENT_DEPS})
- add_custom_target(nvim-client DEPENDS ${ROCKS_DIR}/nvim-client)
-
- list(APPEND THIRD_PARTY_DEPS busted luacheck nvim-client)
-endif()
diff --git a/cmake.deps/cmake/BuildLuv.cmake b/cmake.deps/cmake/BuildLuv.cmake
index 38c0503c5b..004fea7790 100644
--- a/cmake.deps/cmake/BuildLuv.cmake
+++ b/cmake.deps/cmake/BuildLuv.cmake
@@ -1,71 +1,57 @@
-set(LUV_INCLUDE_FLAGS
- "-I${DEPS_INSTALL_DIR}/include -I${DEPS_INSTALL_DIR}/include/luajit-2.1")
-
set(LUV_CMAKE_ARGS
- -DLUA_BUILD_TYPE=System
- -DLUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3
- -DWITH_SHARED_LIBUV=ON
- -DBUILD_SHARED_LIBS=OFF
- -DBUILD_STATIC_LIBS=ON
- -DBUILD_MODULE=OFF)
+ -D LUA_BUILD_TYPE=System
+ -D LUA_COMPAT53_DIR=${DEPS_BUILD_DIR}/src/lua-compat-5.3
+ -D WITH_SHARED_LIBUV=ON
+ -D BUILD_STATIC_LIBS=ON
+ -D BUILD_MODULE=OFF)
if(USE_BUNDLED_LUAJIT)
- list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=LuaJit)
+ list(APPEND LUV_CMAKE_ARGS -D WITH_LUA_ENGINE=LuaJit)
elseif(USE_BUNDLED_LUA)
- list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=Lua)
+ list(APPEND LUV_CMAKE_ARGS -D WITH_LUA_ENGINE=Lua)
else()
- find_package(LuaJit)
+ find_package(Luajit)
if(LUAJIT_FOUND)
- list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=LuaJit)
+ list(APPEND LUV_CMAKE_ARGS -D WITH_LUA_ENGINE=LuaJit)
else()
- list(APPEND LUV_CMAKE_ARGS -DWITH_LUA_ENGINE=Lua)
+ list(APPEND LUV_CMAKE_ARGS -D WITH_LUA_ENGINE=Lua)
endif()
endif()
if(USE_BUNDLED_LIBUV)
- list(APPEND LUV_CMAKE_ARGS
- -DCMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR}
- -DLIBUV_LIBRARIES=uv_a)
+ list(APPEND LUV_CMAKE_ARGS -D CMAKE_PREFIX_PATH=${DEPS_INSTALL_DIR})
endif()
-list(APPEND LUV_CMAKE_ARGS
- "-DCMAKE_C_FLAGS:STRING=${LUV_INCLUDE_FLAGS}")
+list(APPEND LUV_CMAKE_ARGS "-DCMAKE_C_FLAGS:STRING=${DEPS_INCLUDE_FLAGS} -w")
if(CMAKE_GENERATOR MATCHES "Unix Makefiles" AND
(CMAKE_SYSTEM_NAME MATCHES ".*BSD" OR CMAKE_SYSTEM_NAME MATCHES "DragonFly"))
- list(APPEND LUV_CMAKE_ARGS -DCMAKE_MAKE_PROGRAM=gmake)
+ list(APPEND LUV_CMAKE_ARGS -D CMAKE_MAKE_PROGRAM=gmake)
endif()
-if(USE_EXISTING_SRC_DIR)
- unset(LUA_COMPAT53_URL)
-endif()
ExternalProject_Add(lua-compat-5.3
URL ${LUA_COMPAT53_URL}
URL_HASH SHA256=${LUA_COMPAT53_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/lua-compat-5.3
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
- INSTALL_COMMAND "")
+ INSTALL_COMMAND ""
+ ${EXTERNALPROJECT_OPTIONS})
-if(USE_EXISTING_SRC_DIR)
- unset(LUV_URL)
-endif()
-ExternalProject_Add(luv-static
+ExternalProject_Add(luv
DEPENDS lua-compat-5.3
URL ${LUV_URL}
URL_HASH SHA256=${LUV_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/luv
SOURCE_DIR ${DEPS_BUILD_DIR}/src/luv
CMAKE_ARGS ${DEPS_CMAKE_ARGS} ${LUV_CMAKE_ARGS}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
-list(APPEND THIRD_PARTY_DEPS luv-static)
if(USE_BUNDLED_LUAJIT)
- add_dependencies(luv-static luajit)
+ add_dependencies(luv luajit)
elseif(USE_BUNDLED_LUA)
- add_dependencies(luv-static lua)
+ add_dependencies(luv lua)
endif()
if(USE_BUNDLED_LIBUV)
- add_dependencies(luv-static libuv)
+ add_dependencies(luv libuv)
endif()
diff --git a/cmake.deps/cmake/BuildMsgpack.cmake b/cmake.deps/cmake/BuildMsgpack.cmake
index 431420fb62..f60bdad5c9 100644
--- a/cmake.deps/cmake/BuildMsgpack.cmake
+++ b/cmake.deps/cmake/BuildMsgpack.cmake
@@ -1,14 +1,9 @@
-if(USE_EXISTING_SRC_DIR)
- unset(MSGPACK_URL)
-endif()
ExternalProject_Add(msgpack
URL ${MSGPACK_URL}
URL_HASH SHA256=${MSGPACK_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/msgpack
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- -DMSGPACK_BUILD_TESTS=OFF
- -DMSGPACK_BUILD_EXAMPLES=OFF
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS msgpack)
+ -D MSGPACK_BUILD_TESTS=OFF
+ -D MSGPACK_BUILD_EXAMPLES=OFF
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildTreesitter.cmake b/cmake.deps/cmake/BuildTreesitter.cmake
index d906e6aa59..c17773ae9d 100644
--- a/cmake.deps/cmake/BuildTreesitter.cmake
+++ b/cmake.deps/cmake/BuildTreesitter.cmake
@@ -1,16 +1,11 @@
-if(USE_EXISTING_SRC_DIR)
- unset(TREESITTER_URL)
-endif()
-ExternalProject_Add(tree-sitter
+ExternalProject_Add(treesitter
URL ${TREESITTER_URL}
URL_HASH SHA256=${TREESITTER_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
- DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/tree-sitter
+ DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/treesitter
INSTALL_DIR ${DEPS_INSTALL_DIR}
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/TreesitterCMakeLists.txt
- ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt
+ ${DEPS_BUILD_DIR}/src/treesitter/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS tree-sitter)
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/BuildTreesitterParsers.cmake b/cmake.deps/cmake/BuildTreesitterParsers.cmake
index d62b19d97d..eddc896e69 100644
--- a/cmake.deps/cmake/BuildTreesitterParsers.cmake
+++ b/cmake.deps/cmake/BuildTreesitterParsers.cmake
@@ -1,22 +1,40 @@
-function(BuildTSParser LANG TS_URL TS_SHA256 TS_CMAKE_FILE)
- set(NAME treesitter-${LANG})
- if(USE_EXISTING_SRC_DIR)
- unset(TS_URL)
+# Helper function to download treesitter parsers
+#
+# Single value arguments:
+# LANG - Parser language
+# CMAKE_FILE - Cmake file to build the parser with. Defaults to
+# TreesitterParserCMakeLists.txt.
+function(BuildTSParser)
+ cmake_parse_arguments(TS
+ ""
+ "LANG;CMAKE_FILE"
+ ""
+ ${ARGN})
+
+ if(NOT TS_CMAKE_FILE)
+ set(TS_CMAKE_FILE TreesitterParserCMakeLists.txt)
endif()
+
+ set(NAME treesitter-${TS_LANG})
+ string(TOUPPER "TREESITTER_${TS_LANG}_URL" URL_VARNAME)
+ set(URL ${${URL_VARNAME}})
+ string(TOUPPER "TREESITTER_${TS_LANG}_SHA256" HASH_VARNAME)
+ set(HASH ${${HASH_VARNAME}})
+
ExternalProject_Add(${NAME}
- URL ${TS_URL}
- URL_HASH SHA256=${TS_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
+ URL ${URL}
+ URL_HASH SHA256=${HASH}
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/${NAME}
PATCH_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/${TS_CMAKE_FILE}
${DEPS_BUILD_DIR}/src/${NAME}/CMakeLists.txt
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- -DPARSERLANG=${LANG}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
+ -D PARSERLANG=${TS_LANG}
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
endfunction()
-BuildTSParser(c ${TREESITTER_C_URL} ${TREESITTER_C_SHA256} TreesitterParserCMakeLists.txt)
-BuildTSParser(lua ${TREESITTER_LUA_URL} ${TREESITTER_LUA_SHA256} TreesitterParserCMakeLists.txt)
-BuildTSParser(vim ${TREESITTER_VIM_URL} ${TREESITTER_VIM_SHA256} TreesitterParserCMakeLists.txt)
-BuildTSParser(help ${TREESITTER_HELP_URL} ${TREESITTER_HELP_SHA256} TreesitterParserCMakeLists.txt)
+foreach(lang c lua vim vimdoc query python bash)
+ BuildTSParser(LANG ${lang})
+endforeach()
+BuildTSParser(LANG markdown CMAKE_FILE MarkdownParserCMakeLists.txt)
diff --git a/cmake.deps/cmake/BuildUnibilium.cmake b/cmake.deps/cmake/BuildUnibilium.cmake
index 9a8caf89d1..9f1871aaf5 100644
--- a/cmake.deps/cmake/BuildUnibilium.cmake
+++ b/cmake.deps/cmake/BuildUnibilium.cmake
@@ -1,12 +1,7 @@
-if(USE_EXISTING_SRC_DIR)
- unset(UNIBILIUM_URL)
-endif()
ExternalProject_Add(unibilium
URL ${UNIBILIUM_URL}
URL_HASH SHA256=${UNIBILIUM_SHA256}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/unibilium
CMAKE_ARGS ${DEPS_CMAKE_ARGS}
- CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS})
-
-list(APPEND THIRD_PARTY_DEPS unibilium)
+ CMAKE_CACHE_ARGS ${DEPS_CMAKE_CACHE_ARGS}
+ ${EXTERNALPROJECT_OPTIONS})
diff --git a/cmake.deps/cmake/CopyFilesGlob.cmake b/cmake.deps/cmake/CopyFilesGlob.cmake
index 8950ead1e5..7d0f4fc289 100644
--- a/cmake.deps/cmake/CopyFilesGlob.cmake
+++ b/cmake.deps/cmake/CopyFilesGlob.cmake
@@ -14,7 +14,7 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${TO})
file(GLOB files ${FROM_GLOB})
foreach(file ${files})
execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${file} ${TO} RESULT_VARIABLE rv)
- if(NOT rv EQUAL 0)
+ if(rv)
message(FATAL_ERROR "Error copying ${file}")
endif()
endforeach()
diff --git a/cmake.deps/cmake/GetBinaryDeps.cmake b/cmake.deps/cmake/GetBinaryDeps.cmake
index da4376998b..2f1e237588 100644
--- a/cmake.deps/cmake/GetBinaryDeps.cmake
+++ b/cmake.deps/cmake/GetBinaryDeps.cmake
@@ -5,35 +5,49 @@
# install root.
function(GetBinaryDep)
cmake_parse_arguments(_gettool
- "BUILD_IN_SOURCE"
+ ""
"TARGET"
"INSTALL_COMMAND"
${ARGN})
- if(NOT _gettool_TARGET OR NOT _gettool_INSTALL_COMMAND)
- message(FATAL_ERROR "Must pass INSTALL_COMMAND and TARGET")
- endif()
-
string(TOUPPER "${_gettool_TARGET}_URL" URL_VARNAME)
string(TOUPPER "${_gettool_TARGET}_SHA256" HASH_VARNAME)
set(URL ${${URL_VARNAME}})
set(HASH ${${HASH_VARNAME}})
- if(NOT URL OR NOT HASH )
- message(FATAL_ERROR "${URL_VARNAME} and ${HASH_VARNAME} must be set")
- endif()
- if(USE_EXISTING_SRC_DIR)
- unset(URL)
- endif()
ExternalProject_Add(${_gettool_TARGET}
URL ${URL}
URL_HASH SHA256=${HASH}
- DOWNLOAD_NO_PROGRESS TRUE
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}
CONFIGURE_COMMAND ""
BUILD_IN_SOURCE 1
BUILD_COMMAND ""
- INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin
- COMMAND "${_gettool_INSTALL_COMMAND}")
- list(APPEND THIRD_PARTY_DEPS ${__gettool_TARGET})
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_BIN_DIR}
+ COMMAND "${_gettool_INSTALL_COMMAND}"
+ ${EXTERNALPROJECT_OPTIONS})
+endfunction()
+
+# Download executable and move it to DEPS_BIN_DIR
+function(GetExecutable)
+ cmake_parse_arguments(ARG
+ ""
+ "TARGET"
+ ""
+ ${ARGN})
+
+ string(TOUPPER "${ARG_TARGET}_URL" URL_VARNAME)
+ string(TOUPPER "${ARG_TARGET}_SHA256" HASH_VARNAME)
+ set(URL ${${URL_VARNAME}})
+ set(HASH ${${HASH_VARNAME}})
+
+ ExternalProject_Add(${ARG_TARGET}
+ URL ${URL}
+ URL_HASH SHA256=${HASH}
+ DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}
+ DOWNLOAD_NO_EXTRACT TRUE
+ CONFIGURE_COMMAND ""
+ BUILD_COMMAND ""
+ INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_BIN_DIR}
+ COMMAND ${CMAKE_COMMAND} -E copy <DOWNLOADED_FILE> ${DEPS_BIN_DIR}
+ ${EXTERNALPROJECT_OPTIONS})
endfunction()
diff --git a/cmake.deps/cmake/GettextCMakeLists.txt b/cmake.deps/cmake/GettextCMakeLists.txt
index 26f060ec08..e40a73d0c2 100644
--- a/cmake.deps/cmake/GettextCMakeLists.txt
+++ b/cmake.deps/cmake/GettextCMakeLists.txt
@@ -1,6 +1,14 @@
cmake_minimum_required(VERSION 3.10)
+
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
project(gettext C)
+add_compile_options(-w)
+set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4006")
+
# Adds PREFIX to each item in LIST
macro(PREFIX_LIST_ITEMS LIST PREFIX)
string(REPLACE ";" ";${PREFIX}" ${LIST} ";${${LIST}}")
@@ -73,8 +81,7 @@ set_property(TARGET libintl APPEND PROPERTY COMPILE_DEFINITIONS
NO_XMALLOC
set_relocation_prefix=libintl_set_relocation_prefix
relocate=libintl_relocate
- HAVE_CONFIG_H
- _CRT_SECURE_NO_WARNINGS)
+ HAVE_CONFIG_H)
file(READ gettext-tools/config.h.in CONFIG_CONTENT)
diff --git a/cmake.deps/cmake/LibiconvCMakeLists.txt b/cmake.deps/cmake/LibiconvCMakeLists.txt
index f6a23db864..ec80c93e71 100644
--- a/cmake.deps/cmake/LibiconvCMakeLists.txt
+++ b/cmake.deps/cmake/LibiconvCMakeLists.txt
@@ -1,6 +1,13 @@
cmake_minimum_required(VERSION 3.10)
+
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
project(libiconv C)
+add_compile_options(-w)
+
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/srclib
@@ -31,7 +38,7 @@ set(USE_MBSTATE_T 0)
configure_file(libcharset/include/localcharset.h.build.in localcharset.h)
configure_file(include/iconv.h.build.in iconv.h)
-add_definitions(-DLIBDIR -D_CRT_SECURE_NO_WARNINGS)
+add_definitions(-DLIBDIR)
add_library(libcharset libcharset/lib/localcharset.c)
diff --git a/cmake.deps/cmake/LibtermkeyCMakeLists.txt b/cmake.deps/cmake/LibtermkeyCMakeLists.txt
new file mode 100644
index 0000000000..24d67a7ba5
--- /dev/null
+++ b/cmake.deps/cmake/LibtermkeyCMakeLists.txt
@@ -0,0 +1,37 @@
+cmake_minimum_required(VERSION 3.10)
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
+project(libtermkey C)
+
+add_compile_options(-w)
+
+if(EXISTS ${PROJECT_SOURCE_DIR}/termkey.h.in)
+ file(STRINGS Makefile TERMKEY_VERSION_MAJOR REGEX "VERSION_MAJOR")
+ string(REGEX MATCH "[0-9]+" TERMKEY_VERSION_MAJOR ${TERMKEY_VERSION_MAJOR})
+
+ file(STRINGS Makefile TERMKEY_VERSION_MINOR REGEX "VERSION_MINOR")
+ string(REGEX MATCH "[0-9]+" TERMKEY_VERSION_MINOR ${TERMKEY_VERSION_MINOR})
+
+ file(READ termkey.h.in TERMKEY_TEXT)
+ string(REPLACE "@@VERSION_MAJOR@@" "${TERMKEY_VERSION_MAJOR}" TERMKEY_TEXT "${TERMKEY_TEXT}")
+ string(REPLACE "@@VERSION_MINOR@@" "${TERMKEY_VERSION_MINOR}" TERMKEY_TEXT "${TERMKEY_TEXT}")
+ file(WRITE termkey.h "${TERMKEY_TEXT}")
+endif()
+
+add_library(termkey termkey.c driver-csi.c driver-ti.c)
+
+target_compile_definitions(termkey PRIVATE HAVE_UNIBILIUM)
+target_include_directories(termkey PRIVATE SYSTEM ${UNIBILIUM_INCLUDE_DIRS})
+
+set_target_properties(termkey PROPERTIES
+ PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/termkey.h)
+target_link_libraries(termkey PRIVATE ${UNIBILIUM_LIBRARIES})
+
+include(GNUInstallDirs)
+install(TARGETS termkey
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+
+# vim: set ft=cmake:
diff --git a/cmake.deps/cmake/LibvtermCMakeLists.txt b/cmake.deps/cmake/LibvtermCMakeLists.txt
index 777ce6c54c..519926e542 100644
--- a/cmake.deps/cmake/LibvtermCMakeLists.txt
+++ b/cmake.deps/cmake/LibvtermCMakeLists.txt
@@ -1,13 +1,66 @@
cmake_minimum_required(VERSION 3.10)
-project(libvterm LANGUAGES C)
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
+project(libvterm C)
+
+add_compile_options(-w)
include(GNUInstallDirs)
-if(MSVC)
- add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE)
-else()
- add_compile_options(-std=c99)
-endif()
+set(DECDRAWING [[
+ static const struct StaticTableEncoding encoding_DECdrawing = {
+ { .decode = &decode_table },
+ {
+ [0x60] = 0x25C6,
+ [0x61] = 0x2592,
+ [0x62] = 0x2409,
+ [0x63] = 0x240C,
+ [0x64] = 0x240D,
+ [0x65] = 0x240A,
+ [0x66] = 0x00B0,
+ [0x67] = 0x00B1,
+ [0x68] = 0x2424,
+ [0x69] = 0x240B,
+ [0x6a] = 0x2518,
+ [0x6b] = 0x2510,
+ [0x6c] = 0x250C,
+ [0x6d] = 0x2514,
+ [0x6e] = 0x253C,
+ [0x6f] = 0x23BA,
+ [0x70] = 0x23BB,
+ [0x71] = 0x2500,
+ [0x72] = 0x23BC,
+ [0x73] = 0x23BD,
+ [0x74] = 0x251C,
+ [0x75] = 0x2524,
+ [0x76] = 0x2534,
+ [0x77] = 0x252C,
+ [0x78] = 0x2502,
+ [0x79] = 0x2A7D,
+ [0x7a] = 0x2A7E,
+ [0x7b] = 0x03C0,
+ [0x7c] = 0x2260,
+ [0x7d] = 0x00A3,
+ [0x7e] = 0x00B7,
+ }
+ };
+]]
+)
+
+set(UK [[
+ static const struct StaticTableEncoding encoding_uk = {
+ { .decode = &decode_table },
+ {
+ [0x23] = 0x00a3,
+ }
+ };
+]]
+)
+
+file(WRITE src/encoding/DECdrawing.inc "${DECDRAWING}")
+file(WRITE src/encoding/uk.inc "${UK}")
include_directories(${CMAKE_SOURCE_DIR}/include)
include_directories(${CMAKE_BINARY_DIR})
diff --git a/cmake.deps/cmake/LpegCMakeLists.txt b/cmake.deps/cmake/LpegCMakeLists.txt
new file mode 100644
index 0000000000..a3b3327cf7
--- /dev/null
+++ b/cmake.deps/cmake/LpegCMakeLists.txt
@@ -0,0 +1,13 @@
+cmake_minimum_required(VERSION 3.10)
+project (lpeg C)
+
+include(GNUInstallDirs)
+
+file(GLOB LPEG_SOURCES ${CMAKE_SOURCE_DIR}/*.c)
+add_library(lpeg ${LPEG_SOURCES})
+
+target_compile_options(lpeg PRIVATE -w)
+
+install(TARGETS lpeg ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+# vim: set ft=cmake:
diff --git a/cmake.deps/cmake/MarkdownParserCMakeLists.txt b/cmake.deps/cmake/MarkdownParserCMakeLists.txt
new file mode 100644
index 0000000000..a5917334c9
--- /dev/null
+++ b/cmake.deps/cmake/MarkdownParserCMakeLists.txt
@@ -0,0 +1,33 @@
+cmake_minimum_required(VERSION 3.10)
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
+project(${PARSERLANG} C)
+
+add_compile_options(-w)
+set(CMAKE_C_STANDARD 99)
+
+add_library(markdown MODULE
+tree-sitter-markdown/src/parser.c
+tree-sitter-markdown/src/scanner.c)
+target_include_directories(markdown
+ PRIVATE
+ tree-sitter-markdown/src)
+
+add_library(markdown_inline MODULE
+tree-sitter-markdown-inline/src/parser.c
+tree-sitter-markdown-inline/src/scanner.c)
+target_include_directories(markdown_inline
+ PRIVATE
+ tree-sitter-markdown-inline/src)
+
+set_target_properties(
+ markdown markdown_inline
+ PROPERTIES
+ PREFIX ""
+)
+
+install(TARGETS markdown markdown_inline LIBRARY DESTINATION lib/nvim/parser)
+
+# vim: set ft=cmake:
diff --git a/cmake.deps/cmake/RemoveFiles.cmake b/cmake.deps/cmake/RemoveFiles.cmake
deleted file mode 100644
index 88e2bc70a6..0000000000
--- a/cmake.deps/cmake/RemoveFiles.cmake
+++ /dev/null
@@ -1,5 +0,0 @@
-file(GLOB_RECURSE FILES_TO_REMOVE ${REMOVE_FILE_GLOB})
-
-if(FILES_TO_REMOVE)
- file(REMOVE ${FILES_TO_REMOVE})
-endif()
diff --git a/cmake.deps/cmake/TreesitterCMakeLists.txt b/cmake.deps/cmake/TreesitterCMakeLists.txt
index 49fb19c96a..3d80ffc331 100644
--- a/cmake.deps/cmake/TreesitterCMakeLists.txt
+++ b/cmake.deps/cmake/TreesitterCMakeLists.txt
@@ -1,5 +1,11 @@
cmake_minimum_required(VERSION 3.10)
-project(tree-sitter LANGUAGES C)
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
+project(treesitter C)
+
+add_compile_options(-w)
add_library(tree-sitter lib/src/lib.c)
target_include_directories(tree-sitter
diff --git a/cmake.deps/cmake/TreesitterParserCMakeLists.txt b/cmake.deps/cmake/TreesitterParserCMakeLists.txt
index 9bdf500aa7..bdb3f05c11 100644
--- a/cmake.deps/cmake/TreesitterParserCMakeLists.txt
+++ b/cmake.deps/cmake/TreesitterParserCMakeLists.txt
@@ -1,7 +1,12 @@
cmake_minimum_required(VERSION 3.10)
+# Can be removed once minimum version is at least 3.15
+if(POLICY CMP0092)
+ cmake_policy(SET CMP0092 NEW)
+endif()
project(parser C)
-set(CMAKE_C_STANDARD 99)
+add_compile_options(-w)
+
file(GLOB source_files src/*.c)
add_library(parser
diff --git a/cmake.deps/cmake/libtermkeyCMakeLists.txt b/cmake.deps/cmake/libtermkeyCMakeLists.txt
deleted file mode 100644
index 26c9d7730b..0000000000
--- a/cmake.deps/cmake/libtermkeyCMakeLists.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-cmake_minimum_required(VERSION 3.10)
-project(libtermkey)
-
-add_definitions(-D _CRT_SECURE_NO_WARNINGS)
-add_definitions(-DHAVE_UNIBILIUM)
-if(NOT MSVC)
- add_compile_options(-std=c99)
-endif()
-
-include_directories(${PROJECT_BINARY_DIR}/t)
-include_directories(SYSTEM ${UNIBILIUM_INCLUDE_DIRS})
-
-add_library(termkey termkey.c driver-csi.c driver-ti.c)
-set_target_properties(termkey PROPERTIES
- PUBLIC_HEADER ${PROJECT_SOURCE_DIR}/termkey.h)
-target_link_libraries(termkey ${UNIBILIUM_LIBRARIES})
-
-include(GNUInstallDirs)
-install(TARGETS termkey
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
-
-# vim: set ft=cmake:
diff --git a/cmake.deps/deps.txt b/cmake.deps/deps.txt
new file mode 100644
index 0000000000..7007ceff27
--- /dev/null
+++ b/cmake.deps/deps.txt
@@ -0,0 +1,64 @@
+LIBUV_URL https://github.com/libuv/libuv/archive/v1.47.0.tar.gz
+LIBUV_SHA256 d50af7e6d72526db137e66fad812421c8a1cae09d146b0ec2bb9a22c5f23ba93
+
+MSGPACK_URL https://github.com/msgpack/msgpack-c/archive/c-6.0.0.tar.gz
+MSGPACK_SHA256 af6f3cf25edb220aa2140b09bb5bdd73ddf00938194bd94ebe5c92090cccb466
+
+LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/43d0a19158ceabaa51b0462c1ebc97612b420a2e.tar.gz
+LUAJIT_SHA256 4fefa19bc5600928fb13c928bf5325eaa1c78f2c1738a8ac9552154ef178bb9a
+
+LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz
+LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333
+
+UNIBILIUM_URL https://github.com/neovim/unibilium/archive/d72c3598e7ac5d1ebf86ee268b8b4ed95c0fa628.tar.gz
+UNIBILIUM_SHA256 9c4747c862ab5e3076dcf8fa8f0ea7a6b50f20ec5905618b9536655596797487
+
+LIBTERMKEY_URL https://github.com/neovim/libtermkey/archive/dcb198a85c520ce38450a5970c97f8ff03b50f0e.tar.gz
+LIBTERMKEY_SHA256 5eb3e50af54312817bd56fa63b9f8dbd12ec11cedbcf8a7aa0fd79950cc83259
+
+LIBVTERM_URL https://github.com/neovim/libvterm/archive/v0.3.3.tar.gz
+LIBVTERM_SHA256 0babe3ab42c354925dadede90d352f054aa9c4ae6842ea803a20c9741e172e56
+
+LUV_URL https://github.com/luvit/luv/archive/dcd1a1cad5b05634a7691402d6ca2f214fb4ae76.tar.gz
+LUV_SHA256 b68c73ed233918da7e0b34b57c6bac0490e6c6f1b12c1051266b6ad9efa780d0
+
+LPEG_URL https://github.com/neovim/deps/raw/d495ee6f79e7962a53ad79670cb92488abe0b9b4/opt/lpeg-1.1.0.tar.gz
+LPEG_SHA256 4b155d67d2246c1ffa7ad7bc466c1ea899bbc40fef0257cc9c03cecbaed4352a
+
+LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz
+LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416
+
+CAT_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/cat.exe
+CAT_SHA256 93b8d307bb15af3968920bdea3beb869a49d166f9164853c58a4e6ffdcae61c6
+TEE_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/tee.exe
+TEE_SHA256 950eea4e17fa3a7e89fa2c55374037b5797b3f1a54fea1304634884ab42ec14d
+XXD_URL https://github.com/neovim/deps/raw/21c5e8bdda33521a6ed497b315e03265a2785cbc/opt/xxd.exe
+XXD_SHA256 7a581e3882d28161cc52850f9a11d634b3eaf2c029276f093c1ed4c90e45a10c
+
+WIN32YANK_X86_64_URL https://github.com/equalsraf/win32yank/releases/download/v0.1.1/win32yank-x64.zip
+WIN32YANK_X86_64_SHA256 247c9a05b94387a884b49d3db13f806b1677dfc38020f955f719be6902260cd6
+
+GETTEXT_URL https://ftp.gnu.org/pub/gnu/gettext/gettext-0.20.1.tar.gz
+GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47f5c
+
+LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.17.tar.gz
+LIBICONV_SHA256 8f74213b56238c85a50a5329f77e06198771e70dd9a739779f4c02f65d971313
+
+TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.6.tar.gz
+TREESITTER_C_SHA256 44989072e1a9cd3d8f16e9cc3e726af50de6d3017f1231bdf3303b8cc007424e
+TREESITTER_LUA_URL https://github.com/MunifTanjim/tree-sitter-lua/archive/v0.0.19.tar.gz
+TREESITTER_LUA_SHA256 974230f212d0049fce8e881b88b18eebbd05f1fd0edd16fe4ba5bdd2bcd78d08
+TREESITTER_VIM_URL https://github.com/neovim/tree-sitter-vim/archive/v0.3.0.tar.gz
+TREESITTER_VIM_SHA256 403acec3efb7cdb18ff3d68640fc823502a4ffcdfbb71cec3f98aa786c21cbe2
+TREESITTER_VIMDOC_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v2.0.1.tar.gz
+TREESITTER_VIMDOC_SHA256 61e165df29778dc0c9277c2a7bc67447cc4e1bed36ca916a2f476dd25ce3260e
+TREESITTER_QUERY_URL https://github.com/nvim-treesitter/tree-sitter-query/archive/v0.1.0.tar.gz
+TREESITTER_QUERY_SHA256 e2b806f80e8bf1c4f4e5a96248393fe6622fc1fc6189d6896d269658f67f914c
+TREESITTER_PYTHON_URL https://github.com/tree-sitter/tree-sitter-python/archive/v0.20.4.tar.gz
+TREESITTER_PYTHON_SHA256 1e38c991832f461c0da8ca222fbe5be3b82b868fe34025f0295206b5e5789d7a
+TREESITTER_BASH_URL https://github.com/tree-sitter/tree-sitter-bash/archive/493646764e7ad61ce63ce3b8c59ebeb37f71b841.tar.gz
+TREESITTER_BASH_SHA256 99ebe9f2886efecc1a5e9e1360d804a1b49ad89976a66bb5c3871539cca5fb7e
+TREESITTER_MARKDOWN_URL https://github.com/MDeiml/tree-sitter-markdown/archive/v0.1.7.tar.gz
+TREESITTER_MARKDOWN_SHA256 7d0e7f7ed4516ed0816f9c304e2e7fa93b2c16f9280416c2fb64dc4efd9c5f83
+TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/46af27796a76c72d8466627d499f2bca4af958ee.tar.gz
+TREESITTER_SHA256 2e35fa4c4d66c34d04fdb4a3a6c3abe01a05dff571ed9553b15aabf25d870f69
diff --git a/cmake.packaging/CMakeLists.txt b/cmake.packaging/CMakeLists.txt
index df43f2806a..fa815da7f9 100644
--- a/cmake.packaging/CMakeLists.txt
+++ b/cmake.packaging/CMakeLists.txt
@@ -1,6 +1,7 @@
set(CPACK_PACKAGE_NAME "Neovim")
set(CPACK_PACKAGE_VENDOR "neovim.io")
set(CPACK_PACKAGE_FILE_NAME "nvim")
+set(CPACK_PACKAGE_DIRECTORY ${PROJECT_BINARY_DIR})
# From the GitHub About section
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Vim-fork focused on extensibility and usability.")
@@ -34,8 +35,11 @@ if(WIN32)
set(CPACK_WIX_UPGRADE_GUID "207A1A70-7B0C-418A-A153-CA6883E38F4D")
set(CPACK_WIX_PRODUCT_ICON ${PROJECT_SOURCE_DIR}/runtime/neovim.ico)
- # We use a wix patch to add further options to the installer. At present, it's just to add neovim to the path
- # on installation, however, it can be extended.
+ # Create start menu and desktop shortcuts
+ set(CPACK_WIX_PROGRAM_MENU_FOLDER "${CPACK_PACKAGE_NAME}")
+ set(CPACK_PACKAGE_EXECUTABLES "nvim" "Neovim")
+
+ # We use a wix patch to add further options to the installer.
# See: https://cmake.org/cmake/help/v3.7/module/CPackWIX.html#variable:CPACK_WIX_PATCH_FILE
list(APPEND CPACK_WIX_EXTENSIONS WixUtilExtension)
list(APPEND CPACK_WIX_PATCH_FILE ${CMAKE_CURRENT_LIST_DIR}/WixPatch.xml)
diff --git a/cmake.packaging/WixPatch.xml b/cmake.packaging/WixPatch.xml
index 3cbbb04850..1196f4f335 100644
--- a/cmake.packaging/WixPatch.xml
+++ b/cmake.packaging/WixPatch.xml
@@ -1,14 +1,14 @@
<CPackWiXPatch>
- <!-- Fragment ID is from: <your build dir>/_CPack_Packages/win64/WIX/files.wxs -->
- <CPackWiXFragment Id="CM_CP_bin.nvim.exe">
- <Environment
- Id='UpdatePath'
- Name='PATH'
- Action='set'
- Permanent='no'
- System='yes'
- Part='last'
- Value='[INSTALL_ROOT]bin'
- />
- </CPackWiXFragment>
+ <!-- Fragment ID is from: <your build dir>/_CPack_Packages/win64/WIX/files.wxs -->
+ <CPackWiXFragment Id="CM_CP_bin.nvim.exe">
+ <Environment
+ Id='UpdatePath'
+ Name='PATH'
+ Action='set'
+ Permanent='no'
+ System='no'
+ Part='last'
+ Value='[INSTALL_ROOT]bin'
+ />
+ </CPackWiXFragment>
</CPackWiXPatch>
diff --git a/cmake/CheckUncrustifyVersion.cmake b/cmake/CheckUncrustifyVersion.cmake
deleted file mode 100644
index 4812c24ace..0000000000
--- a/cmake/CheckUncrustifyVersion.cmake
+++ /dev/null
@@ -1,13 +0,0 @@
-if(UNCRUSTIFY_PRG)
- execute_process(COMMAND uncrustify --version
- OUTPUT_VARIABLE user_version
- OUTPUT_STRIP_TRAILING_WHITESPACE)
- string(REGEX REPLACE "[A-Za-z_#-]" "" user_version ${user_version})
-
- file(STRINGS ${CONFIG_FILE} required_version LIMIT_COUNT 1)
- string(REGEX REPLACE "[A-Za-z_# -]" "" required_version ${required_version})
-
- if(NOT user_version STREQUAL required_version)
- message(FATAL_ERROR "Wrong uncrustify version! Required version is ${required_version} but found ${user_version}")
- endif()
-endif()
diff --git a/cmake/ConvertPo.cmake b/cmake/ConvertPo.cmake
index 2282b96f56..202cd3fbb3 100644
--- a/cmake/ConvertPo.cmake
+++ b/cmake/ConvertPo.cmake
@@ -6,7 +6,7 @@ execute_process(
OUTPUT_VARIABLE trans
ERROR_VARIABLE err
RESULT_VARIABLE res)
-if(NOT res EQUAL 0)
+if(res)
message(FATAL_ERROR "iconv failed to run correctly: ${err}")
endif()
diff --git a/cmake/Deps.cmake b/cmake/Deps.cmake
new file mode 100644
index 0000000000..9966e42084
--- /dev/null
+++ b/cmake/Deps.cmake
@@ -0,0 +1,56 @@
+set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr")
+set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin")
+set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib")
+set(DEPS_SHARE_DIR "${DEPS_INSTALL_DIR}/share/lua/5.1")
+
+set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build")
+set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads")
+
+set(DEPS_CMAKE_ARGS
+ -D CMAKE_C_COMPILER=${CMAKE_C_COMPILER}
+ -D CMAKE_C_STANDARD=99
+ -D CMAKE_GENERATOR=${CMAKE_GENERATOR}
+ -D CMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM}
+ -D BUILD_SHARED_LIBS=OFF
+ -D CMAKE_POSITION_INDEPENDENT_CODE=ON
+ -D CMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR})
+if(APPLE)
+ list(APPEND DEPS_CMAKE_ARGS -D CMAKE_FIND_FRAMEWORK=${CMAKE_FIND_FRAMEWORK})
+endif()
+
+set(DEPS_CMAKE_CACHE_ARGS -DCMAKE_OSX_ARCHITECTURES:STRING=${CMAKE_OSX_ARCHITECTURES})
+set(EXTERNALPROJECT_OPTIONS DOWNLOAD_NO_PROGRESS TRUE)
+
+# MAKE_PRG
+if(UNIX)
+ find_program(MAKE_PRG NAMES gmake make)
+ if(NOT MAKE_PRG)
+ message(FATAL_ERROR "GNU Make is required to build the dependencies.")
+ else()
+ message(STATUS "Found GNU Make at ${MAKE_PRG}")
+ endif()
+endif()
+# When using make, use the $(MAKE) variable to avoid warning about the job
+# server.
+if(CMAKE_GENERATOR MATCHES "Makefiles")
+ set(MAKE_PRG "$(MAKE)")
+endif()
+if(MINGW AND CMAKE_GENERATOR MATCHES "Ninja")
+ find_program(MAKE_PRG NAMES mingw32-make)
+ if(NOT MAKE_PRG)
+ message(FATAL_ERROR "GNU Make for mingw32 is required to build the dependencies.")
+ else()
+ message(STATUS "Found GNU Make for mingw32: ${MAKE_PRG}")
+ endif()
+endif()
+
+# DEPS_C_COMPILER
+set(DEPS_C_COMPILER "${CMAKE_C_COMPILER}")
+if(CMAKE_OSX_SYSROOT)
+ set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -isysroot${CMAKE_OSX_SYSROOT}")
+endif()
+if(CMAKE_OSX_ARCHITECTURES)
+ foreach(ARCH IN LISTS CMAKE_OSX_ARCHITECTURES)
+ set(DEPS_C_COMPILER "${DEPS_C_COMPILER} -arch ${ARCH}")
+ endforeach()
+endif()
diff --git a/cmake/Find.cmake b/cmake/Find.cmake
new file mode 100644
index 0000000000..b363052510
--- /dev/null
+++ b/cmake/Find.cmake
@@ -0,0 +1,39 @@
+# Functions to aid the built-in find_ functions
+
+# Same as find_path, but always search in .deps directory first and then everything else.
+function(find_path2)
+ find_path_nvim(${ARGV})
+ find_path(${ARGV})
+endfunction()
+
+function(find_path_nvim)
+ set(CMAKE_FIND_FRAMEWORK NEVER)
+ set(CMAKE_FIND_APPBUNDLE NEVER)
+ find_path(${ARGV} NO_CMAKE_SYSTEM_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH)
+endfunction()
+
+# Same as find_library, but with the following search order:
+# 1. Only search in .deps directory. Only search for static libraries.
+# 2. Only search in .deps directory. Search all libraries
+# 3. Search everywhere, all libraries
+function(find_library2)
+ find_library_nvim(STATIC ${ARGV})
+ find_library_nvim(${ARGV})
+ find_library(${ARGV})
+endfunction()
+
+function(find_library_nvim)
+ cmake_parse_arguments(ARG
+ "STATIC"
+ ""
+ ""
+ ${ARGN})
+ list(REMOVE_ITEM ARGN STATIC)
+
+ if(ARG_STATIC)
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_STATIC_LIBRARY_SUFFIX})
+ endif()
+ set(CMAKE_FIND_FRAMEWORK NEVER)
+ set(CMAKE_FIND_APPBUNDLE NEVER)
+ find_library(${ARGN} NO_CMAKE_SYSTEM_PATH NO_CMAKE_ENVIRONMENT_PATH NO_SYSTEM_ENVIRONMENT_PATH)
+endfunction()
diff --git a/cmake/FindIconv.cmake b/cmake/FindIconv.cmake
index 63290fe889..e607c59cf6 100644
--- a/cmake/FindIconv.cmake
+++ b/cmake/FindIconv.cmake
@@ -1,21 +1,14 @@
# TODO(dundargoc): FindIconv is shipped by default on cmake version 3.11+. This
# file can be removed once we decide to upgrade minimum cmake version.
-# - Try to find iconv
-# Once done, this will define
-#
-# Iconv_FOUND - system has iconv
-# Iconv_INCLUDE_DIRS - the iconv include directories
-# Iconv_LIBRARIES - link these to use iconv
-
-include(LibFindMacros)
-
-find_path(ICONV_INCLUDE_DIR NAMES iconv.h)
-find_library(ICONV_LIBRARY NAMES iconv libiconv)
-
-set(Iconv_PROCESS_INCLUDES ICONV_INCLUDE_DIR)
+find_path2(ICONV_INCLUDE_DIR NAMES iconv.h)
+find_library2(ICONV_LIBRARY NAMES iconv libiconv)
+find_package_handle_standard_args(Iconv DEFAULT_MSG
+ ICONV_INCLUDE_DIR)
+mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARY)
+
+add_library(iconv INTERFACE)
+target_include_directories(iconv SYSTEM BEFORE INTERFACE ${ICONV_INCLUDE_DIR})
if(ICONV_LIBRARY)
- set(Iconv_PROCESS_LIBS ICONV_LIBRARY)
+ target_link_libraries(iconv INTERFACE ${ICONV_LIBRARY})
endif()
-
-libfind_process(Iconv)
diff --git a/cmake/FindLibLUV.cmake b/cmake/FindLibLUV.cmake
deleted file mode 100644
index 23b62b66d3..0000000000
--- a/cmake/FindLibLUV.cmake
+++ /dev/null
@@ -1,32 +0,0 @@
-# - Try to find luv
-# Once done this will define
-# LIBLUV_FOUND - System has libluv
-# LIBLUV_INCLUDE_DIRS - The libluv include directories
-# LIBLUV_LIBRARIES - The libraries needed to use libluv
-
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_check_modules(PC_LIBLUV QUIET luv)
-endif()
-
-set(LIBLUV_DEFINITIONS ${PC_LIBLUV_CFLAGS_OTHER})
-
-find_path(LIBLUV_INCLUDE_DIR luv/luv.h
- PATHS ${PC_LIBLUV_INCLUDEDIR} ${PC_LIBLUV_INCLUDE_DIRS})
-
-# Explicitly look for luv.so. #10407
-list(APPEND LIBLUV_NAMES luv_a luv libluv_a luv${CMAKE_SHARED_LIBRARY_SUFFIX})
-
-find_library(LIBLUV_LIBRARY NAMES ${LIBLUV_NAMES}
- HINTS ${PC_LIBLUV_LIBDIR} ${PC_LIBLUV_LIBRARY_DIRS})
-
-set(LIBLUV_LIBRARIES ${LIBLUV_LIBRARY})
-set(LIBLUV_INCLUDE_DIRS ${LIBLUV_INCLUDE_DIR})
-
-include(FindPackageHandleStandardArgs)
-# handle the QUIETLY and REQUIRED arguments and set LIBLUV_FOUND to TRUE
-# if all listed variables are TRUE
-find_package_handle_standard_args(LibLUV DEFAULT_MSG
- LIBLUV_LIBRARY LIBLUV_INCLUDE_DIR)
-
-mark_as_advanced(LIBLUV_INCLUDE_DIR LIBLUV_LIBRARY)
diff --git a/cmake/FindLibTermkey.cmake b/cmake/FindLibTermkey.cmake
deleted file mode 100644
index 3e0c7f1bfd..0000000000
--- a/cmake/FindLibTermkey.cmake
+++ /dev/null
@@ -1,31 +0,0 @@
-# - Try to find libtermkey
-# Once done this will define
-# LIBTERMKEY_FOUND - System has libtermkey
-# LIBTERMKEY_INCLUDE_DIRS - The libtermkey include directories
-# LIBTERMKEY_LIBRARIES - The libraries needed to use libtermkey
-
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_check_modules(PC_LIBTERMKEY QUIET termkey)
-endif()
-
-set(LIBTERMKEY_DEFINITIONS ${PC_LIBTERMKEY_CFLAGS_OTHER})
-
-find_path(LIBTERMKEY_INCLUDE_DIR termkey.h
- PATHS ${PC_LIBTERMKEY_INCLUDEDIR} ${PC_LIBTERMKEY_INCLUDE_DIRS})
-
-list(APPEND LIBTERMKEY_NAMES termkey)
-
-find_library(LIBTERMKEY_LIBRARY NAMES ${LIBTERMKEY_NAMES}
- HINTS ${PC_LIBTERMKEY_LIBDIR} ${PC_LIBTERMKEY_LIBRARY_DIRS})
-
-set(LIBTERMKEY_LIBRARIES ${LIBTERMKEY_LIBRARY})
-set(LIBTERMKEY_INCLUDE_DIRS ${LIBTERMKEY_INCLUDE_DIR})
-
-include(FindPackageHandleStandardArgs)
-# handle the QUIETLY and REQUIRED arguments and set LIBTERMKEY_FOUND to TRUE
-# if all listed variables are TRUE
-find_package_handle_standard_args(LibTermkey DEFAULT_MSG
- LIBTERMKEY_LIBRARY LIBTERMKEY_INCLUDE_DIR)
-
-mark_as_advanced(LIBTERMKEY_INCLUDE_DIR LIBTERMKEY_LIBRARY)
diff --git a/cmake/FindLibIntl.cmake b/cmake/FindLibintl.cmake
index 8cc5cb7dd2..630a3545fc 100644
--- a/cmake/FindLibIntl.cmake
+++ b/cmake/FindLibintl.cmake
@@ -1,42 +1,34 @@
-# - Try to find libintl
-# Once done, this will define
-#
-# LibIntl_FOUND - system has libintl
-# LibIntl_INCLUDE_DIRS - the libintl include directories
-# LibIntl_LIBRARIES - link these to use libintl
-
include(CheckCSourceCompiles)
include(CheckVariableExists)
-include(LibFindMacros)
# Append custom gettext path to CMAKE_PREFIX_PATH
-# if installed via Mac Hombrew
-if (CMAKE_HOST_APPLE)
- find_program(HOMEBREW_PROG brew)
- if (EXISTS ${HOMEBREW_PROG})
- execute_process(COMMAND ${HOMEBREW_PROG} --prefix gettext
+# if installed via Mac Homebrew
+if (APPLE)
+ find_program(HOMEBREW_PRG brew)
+ if (EXISTS ${HOMEBREW_PRG})
+ execute_process(COMMAND ${HOMEBREW_PRG} --prefix gettext
OUTPUT_STRIP_TRAILING_WHITESPACE
OUTPUT_VARIABLE HOMEBREW_GETTEXT_PREFIX)
list(APPEND CMAKE_PREFIX_PATH "${HOMEBREW_GETTEXT_PREFIX}")
endif()
endif()
-find_path(LibIntl_INCLUDE_DIR
+find_path(LIBINTL_INCLUDE_DIR
NAMES libintl.h
PATH_SUFFIXES gettext
)
-find_library(LibIntl_LIBRARY
+find_library(LIBINTL_LIBRARY
NAMES intl libintl
)
-if (LibIntl_INCLUDE_DIR)
- list(APPEND CMAKE_REQUIRED_INCLUDES "${LibIntl_INCLUDE_DIR}")
+if (LIBINTL_INCLUDE_DIR)
+ list(APPEND CMAKE_REQUIRED_INCLUDES "${LIBINTL_INCLUDE_DIR}")
endif()
# On some systems (linux+glibc) libintl is passively available.
# So only specify the library if one was found.
-if (LibIntl_LIBRARY)
- list(APPEND CMAKE_REQUIRED_LIBRARIES "${LibIntl_LIBRARY}")
+if (LIBINTL_LIBRARY)
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "${LIBINTL_LIBRARY}")
endif()
if (MSVC)
list(APPEND CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY})
@@ -44,7 +36,7 @@ endif()
# On macOS, if libintl is a static library then we also need
# to link libiconv and CoreFoundation.
-get_filename_component(LibIntl_EXT "${LibIntl_LIBRARY}" EXT)
+get_filename_component(LibIntl_EXT "${LIBINTL_LIBRARY}" EXT)
if (APPLE AND (LibIntl_EXT STREQUAL ".a"))
set(LibIntl_STATIC TRUE)
find_library(CoreFoundation_FRAMEWORK CoreFoundation)
@@ -67,25 +59,22 @@ endif()
if (LibIntl_STATIC)
list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${ICONV_LIBRARY}" "${CoreFoundation_FRAMEWORK}")
endif()
-if (LibIntl_INCLUDE_DIR)
- list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${LibIntl_INCLUDE_DIR}")
+if (LIBINTL_INCLUDE_DIR)
+ list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${LIBINTL_INCLUDE_DIR}")
endif()
-if (LibIntl_LIBRARY)
- list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${LibIntl_LIBRARY}")
+if (LIBINTL_LIBRARY)
+ list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${LIBINTL_LIBRARY}")
endif()
+set(REQUIRED_VARIABLES LIBINTL_LIBRARY LIBINTL_INCLUDE_DIR)
if (HAVE_WORKING_LIBINTL)
# On some systems (linux+glibc) libintl is passively available.
# If HAVE_WORKING_LIBINTL then we consider the requirement satisfied.
- # Unset REQUIRED so that libfind_process(LibIntl) can proceed.
- if(LibIntl_FIND_REQUIRED)
- unset(LibIntl_FIND_REQUIRED)
- endif()
- set(LibIntl_FIND_QUIETLY ON)
+ unset(REQUIRED_VARIABLES)
check_variable_exists(_nl_msg_cat_cntr HAVE_NL_MSG_CAT_CNTR)
endif()
-set(LibIntl_PROCESS_INCLUDES LibIntl_INCLUDE_DIR)
-set(LibIntl_PROCESS_LIBS LibIntl_LIBRARY)
-libfind_process(LibIntl)
+find_package_handle_standard_args(Libintl DEFAULT_MSG
+ ${REQUIRED_VARIABLES})
+mark_as_advanced(LIBINTL_LIBRARY LIBINTL_INCLUDE_DIR)
diff --git a/cmake/FindLibtermkey.cmake b/cmake/FindLibtermkey.cmake
new file mode 100644
index 0000000000..8039ae7994
--- /dev/null
+++ b/cmake/FindLibtermkey.cmake
@@ -0,0 +1,9 @@
+find_path2(LIBTERMKEY_INCLUDE_DIR termkey.h)
+find_library2(LIBTERMKEY_LIBRARY NAMES termkey)
+find_package_handle_standard_args(Libtermkey DEFAULT_MSG
+ LIBTERMKEY_LIBRARY LIBTERMKEY_INCLUDE_DIR)
+mark_as_advanced(LIBTERMKEY_INCLUDE_DIR LIBTERMKEY_LIBRARY)
+
+add_library(libtermkey INTERFACE)
+target_include_directories(libtermkey SYSTEM BEFORE INTERFACE ${LIBTERMKEY_INCLUDE_DIR})
+target_link_libraries(libtermkey INTERFACE ${LIBTERMKEY_LIBRARY})
diff --git a/cmake/FindLibUV.cmake b/cmake/FindLibuv.cmake
index d6c4e9cbef..19315388dd 100644
--- a/cmake/FindLibUV.cmake
+++ b/cmake/FindLibuv.cmake
@@ -1,35 +1,7 @@
-# - Try to find libuv
-# Once done, this will define
-#
-# LIBUV_FOUND - system has libuv
-# LIBUV_INCLUDE_DIRS - the libuv include directories
-# LIBUV_LIBRARIES - link these to use libuv
+find_path2(LIBUV_INCLUDE_DIR uv.h)
+find_library2(LIBUV_LIBRARY NAMES uv_a uv)
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_check_modules(PC_LIBUV QUIET libuv)
-endif()
-
-find_path(LIBUV_INCLUDE_DIR uv.h
- HINTS ${PC_LIBUV_INCLUDEDIR} ${PC_LIBUV_INCLUDE_DIRS})
-
-list(APPEND LIBUV_NAMES uv_a uv)
-
-find_library(LIBUV_LIBRARY NAMES ${LIBUV_NAMES}
- HINTS ${PC_LIBUV_LIBDIR} ${PC_LIBUV_LIBRARY_DIRS})
-
-mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY)
-
-if(PC_LIBUV_LIBRARIES)
- list(REMOVE_ITEM PC_LIBUV_LIBRARIES uv)
-endif()
-
-set(LIBUV_LIBRARIES ${LIBUV_LIBRARY} ${PC_LIBUV_LIBRARIES})
-set(LIBUV_INCLUDE_DIRS ${LIBUV_INCLUDE_DIR})
-
-# Deal with the fact that libuv.pc is missing important dependency information.
-
-include(CheckLibraryExists)
+set(LIBUV_LIBRARIES ${LIBUV_LIBRARY})
check_library_exists(dl dlopen "dlfcn.h" HAVE_LIBDL)
if(HAVE_LIBDL)
@@ -83,11 +55,7 @@ if(Threads_FOUND)
list(APPEND LIBUV_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
endif()
-include(FindPackageHandleStandardArgs)
-
-# handle the QUIETLY and REQUIRED arguments and set LIBUV_FOUND to TRUE
-# if all listed variables are TRUE
-find_package_handle_standard_args(LibUV DEFAULT_MSG
+find_package_handle_standard_args(Libuv DEFAULT_MSG
LIBUV_LIBRARY LIBUV_INCLUDE_DIR)
mark_as_advanced(LIBUV_INCLUDE_DIR LIBUV_LIBRARY)
diff --git a/cmake/Findlibvterm.cmake b/cmake/FindLibvterm.cmake
index d8536ca894..68c2646d47 100644
--- a/cmake/Findlibvterm.cmake
+++ b/cmake/FindLibvterm.cmake
@@ -1,5 +1,5 @@
-find_path(LIBVTERM_INCLUDE_DIR vterm.h)
-find_library(LIBVTERM_LIBRARY vterm)
+find_path2(LIBVTERM_INCLUDE_DIR vterm.h)
+find_library2(LIBVTERM_LIBRARY vterm)
if(LIBVTERM_INCLUDE_DIR AND EXISTS "${LIBVTERM_INCLUDE_DIR}/vterm.h")
file(STRINGS ${LIBVTERM_INCLUDE_DIR}/vterm.h VTERM_VERSION_MAJOR REGEX "#define VTERM_VERSION_MAJOR")
@@ -8,13 +8,24 @@ if(LIBVTERM_INCLUDE_DIR AND EXISTS "${LIBVTERM_INCLUDE_DIR}/vterm.h")
file(STRINGS ${LIBVTERM_INCLUDE_DIR}/vterm.h VTERM_VERSION_MINOR REGEX "#define VTERM_VERSION_MINOR")
string(REGEX MATCH "[0-9]+" VTERM_VERSION_MINOR ${VTERM_VERSION_MINOR})
- set(VTERM_VERSION ${VTERM_VERSION_MAJOR}.${VTERM_VERSION_MINOR})
+ file(STRINGS ${LIBVTERM_INCLUDE_DIR}/vterm.h VTERM_VERSION_PATCH REGEX "#define VTERM_VERSION_PATCH")
+
+ # The following is needed to give a coherent error for versions 0.3.2 and
+ # smaller.
+ if(VTERM_VERSION_PATCH)
+ string(REGEX MATCH "[0-9]+" VTERM_VERSION_PATCH ${VTERM_VERSION_PATCH})
+ string(PREPEND VTERM_VERSION_PATCH ".")
+ endif()
+
+ set(VTERM_VERSION ${VTERM_VERSION_MAJOR}.${VTERM_VERSION_MINOR}${VTERM_VERSION_PATCH})
endif()
-find_package_handle_standard_args(libvterm
+find_package_handle_standard_args(Libvterm
REQUIRED_VARS LIBVTERM_INCLUDE_DIR LIBVTERM_LIBRARY
VERSION_VAR VTERM_VERSION)
add_library(libvterm INTERFACE)
-target_include_directories(libvterm SYSTEM BEFORE INTERFACE INTERFACE ${LIBVTERM_INCLUDE_DIR})
+target_include_directories(libvterm SYSTEM BEFORE INTERFACE ${LIBVTERM_INCLUDE_DIR})
target_link_libraries(libvterm INTERFACE ${LIBVTERM_LIBRARY})
+
+mark_as_advanced(LIBVTERM_INCLUDE_DIR LIBVTERM_LIBRARY)
diff --git a/cmake/FindLpeg.cmake b/cmake/FindLpeg.cmake
new file mode 100644
index 0000000000..3d0ff5929d
--- /dev/null
+++ b/cmake/FindLpeg.cmake
@@ -0,0 +1,9 @@
+find_library2(LPEG_LIBRARY NAMES lpeg_a lpeg liblpeg_a lpeg${CMAKE_SHARED_LIBRARY_SUFFIX} PATH_SUFFIXES lua/5.1)
+
+find_package_handle_standard_args(Lpeg DEFAULT_MSG LPEG_LIBRARY)
+mark_as_advanced(LPEG_LIBRARY)
+
+# Workaround: use an imported library to prevent cmake from modifying library
+# link path. See #23395.
+add_library(lpeg UNKNOWN IMPORTED)
+set_target_properties(lpeg PROPERTIES IMPORTED_LOCATION ${LPEG_LIBRARY})
diff --git a/cmake/FindLuaJit.cmake b/cmake/FindLuaJit.cmake
deleted file mode 100644
index 72795afefd..0000000000
--- a/cmake/FindLuaJit.cmake
+++ /dev/null
@@ -1,38 +0,0 @@
-# - Try to find luajit
-# Once done this will define
-# LUAJIT_FOUND - System has luajit
-# LUAJIT_INCLUDE_DIRS - The luajit include directories
-# LUAJIT_LIBRARIES - The libraries needed to use luajit
-
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_check_modules(PC_LUAJIT QUIET luajit)
-endif()
-
-set(LUAJIT_DEFINITIONS ${PC_LUAJIT_CFLAGS_OTHER})
-
-find_path(LUAJIT_INCLUDE_DIR luajit.h
- PATHS ${PC_LUAJIT_INCLUDEDIR} ${PC_LUAJIT_INCLUDE_DIRS}
- PATH_SUFFIXES luajit-2.0 luajit-2.1)
-
-if(MSVC)
- list(APPEND LUAJIT_NAMES lua51)
-elseif(MINGW)
- list(APPEND LUAJIT_NAMES libluajit libluajit-5.1)
-else()
- list(APPEND LUAJIT_NAMES luajit-5.1)
-endif()
-
-find_library(LUAJIT_LIBRARY NAMES ${LUAJIT_NAMES}
- PATHS ${PC_LUAJIT_LIBDIR} ${PC_LUAJIT_LIBRARY_DIRS})
-
-set(LUAJIT_LIBRARIES ${LUAJIT_LIBRARY})
-set(LUAJIT_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIR})
-
-include(FindPackageHandleStandardArgs)
-# handle the QUIETLY and REQUIRED arguments and set LUAJIT_FOUND to TRUE
-# if all listed variables are TRUE
-find_package_handle_standard_args(LuaJit DEFAULT_MSG
- LUAJIT_LIBRARY LUAJIT_INCLUDE_DIR)
-
-mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIBRARY)
diff --git a/cmake/FindLuajit.cmake b/cmake/FindLuajit.cmake
new file mode 100644
index 0000000000..457ceafdd4
--- /dev/null
+++ b/cmake/FindLuajit.cmake
@@ -0,0 +1,17 @@
+find_path2(LUAJIT_INCLUDE_DIR luajit.h
+ PATH_SUFFIXES luajit-2.1)
+
+if(MSVC)
+ list(APPEND LUAJIT_NAMES lua51)
+elseif(MINGW)
+ list(APPEND LUAJIT_NAMES libluajit libluajit-5.1)
+else()
+ list(APPEND LUAJIT_NAMES luajit-5.1)
+endif()
+
+find_library2(LUAJIT_LIBRARY NAMES ${LUAJIT_NAMES})
+
+find_package_handle_standard_args(Luajit DEFAULT_MSG
+ LUAJIT_LIBRARY LUAJIT_INCLUDE_DIR)
+
+mark_as_advanced(LUAJIT_INCLUDE_DIR LUAJIT_LIBRARY)
diff --git a/cmake/FindLuv.cmake b/cmake/FindLuv.cmake
new file mode 100644
index 0000000000..a4408fe659
--- /dev/null
+++ b/cmake/FindLuv.cmake
@@ -0,0 +1,6 @@
+find_path2(LUV_INCLUDE_DIR luv/luv.h PATH_SUFFIXES lua5.1)
+find_library2(LUV_LIBRARY NAMES luv_a luv libluv_a luv${CMAKE_SHARED_LIBRARY_SUFFIX} PATH_SUFFIXES lua/5.1)
+
+find_package_handle_standard_args(Luv DEFAULT_MSG
+ LUV_LIBRARY LUV_INCLUDE_DIR)
+mark_as_advanced(LUV_INCLUDE_DIR LUV_LIBRARY)
diff --git a/cmake/FindMsgpack.cmake b/cmake/FindMsgpack.cmake
index 26eb19d498..9ef18122ab 100644
--- a/cmake/FindMsgpack.cmake
+++ b/cmake/FindMsgpack.cmake
@@ -1,20 +1,4 @@
-# - Try to find msgpack
-# Once done this will define
-# MSGPACK_FOUND - System has msgpack
-# MSGPACK_INCLUDE_DIRS - The msgpack include directories
-# MSGPACK_LIBRARIES - The libraries needed to use msgpack
-
-find_package(PkgConfig)
-if (PKG_CONFIG_FOUND)
- pkg_search_module(PC_MSGPACK QUIET
- msgpackc>=${Msgpack_FIND_VERSION}
- msgpack>=${Msgpack_FIND_VERSION})
-endif()
-
-set(MSGPACK_DEFINITIONS ${PC_MSGPACK_CFLAGS_OTHER})
-
-find_path(MSGPACK_INCLUDE_DIR msgpack/version_master.h
- HINTS ${PC_MSGPACK_INCLUDEDIR} ${PC_MSGPACK_INCLUDE_DIRS})
+find_path2(MSGPACK_INCLUDE_DIR msgpack/version_master.h)
if(MSGPACK_INCLUDE_DIR)
file(READ ${MSGPACK_INCLUDE_DIR}/msgpack/version_master.h msgpack_version_h)
@@ -26,28 +10,15 @@ else()
set(MSGPACK_VERSION_STRING)
endif()
-if(MSVC)
- # The import library for the msgpack DLL has a different name
- list(APPEND MSGPACK_NAMES msgpackc_import)
-else()
- list(APPEND MSGPACK_NAMES msgpackc msgpack)
-endif()
-
-find_library(MSGPACK_LIBRARY NAMES ${MSGPACK_NAMES}
- # Check each directory for all names to avoid using headers/libraries from
- # different places.
- NAMES_PER_DIR
- HINTS ${PC_MSGPACK_LIBDIR} ${PC_MSGPACK_LIBRARY_DIRS})
+find_library2(MSGPACK_LIBRARY NAMES msgpackc msgpack msgpackc_import msgpack-c
+ NAMES_PER_DIR)
mark_as_advanced(MSGPACK_INCLUDE_DIR MSGPACK_LIBRARY)
-set(MSGPACK_LIBRARIES ${MSGPACK_LIBRARY})
-set(MSGPACK_INCLUDE_DIRS ${MSGPACK_INCLUDE_DIR})
-
-include(FindPackageHandleStandardArgs)
-# handle the QUIETLY and REQUIRED arguments and set MSGPACK_FOUND to TRUE
-# if all listed variables are TRUE
find_package_handle_standard_args(Msgpack
REQUIRED_VARS MSGPACK_LIBRARY MSGPACK_INCLUDE_DIR
VERSION_VAR MSGPACK_VERSION_STRING)
+add_library(msgpack INTERFACE)
+target_include_directories(msgpack SYSTEM BEFORE INTERFACE ${MSGPACK_INCLUDE_DIR})
+target_link_libraries(msgpack INTERFACE ${MSGPACK_LIBRARY})
diff --git a/cmake/FindTreeSitter.cmake b/cmake/FindTreeSitter.cmake
deleted file mode 100644
index ae0928e9f7..0000000000
--- a/cmake/FindTreeSitter.cmake
+++ /dev/null
@@ -1,11 +0,0 @@
-# - Try to find tree-sitter
-# Once done, this will define
-#
-# TreeSitter_FOUND - system has tree-sitter
-# TreeSitter_INCLUDE_DIRS - the tree-sitter include directories
-# TreeSitter_LIBRARIES - link these to use tree-sitter
-
-include(LibFindMacros)
-
-libfind_pkg_detect(TreeSitter tree-sitter FIND_PATH tree_sitter/api.h FIND_LIBRARY tree-sitter)
-libfind_process(TreeSitter)
diff --git a/cmake/FindTreesitter.cmake b/cmake/FindTreesitter.cmake
new file mode 100644
index 0000000000..8dac13337b
--- /dev/null
+++ b/cmake/FindTreesitter.cmake
@@ -0,0 +1,29 @@
+find_path2(TREESITTER_INCLUDE_DIR tree_sitter/api.h)
+find_library2(TREESITTER_LIBRARY NAMES tree-sitter)
+find_package_handle_standard_args(Treesitter DEFAULT_MSG
+ TREESITTER_LIBRARY TREESITTER_INCLUDE_DIR)
+mark_as_advanced(TREESITTER_LIBRARY TREESITTER_INCLUDE_DIR)
+
+add_library(treesitter INTERFACE)
+target_include_directories(treesitter SYSTEM BEFORE INTERFACE ${TREESITTER_INCLUDE_DIR})
+target_link_libraries(treesitter INTERFACE ${TREESITTER_LIBRARY})
+
+# TODO(lewis6991): remove when min TS version is 0.20.9
+list(APPEND CMAKE_REQUIRED_INCLUDES "${TREESITTER_INCLUDE_DIR}")
+list(APPEND CMAKE_REQUIRED_LIBRARIES "${TREESITTER_LIBRARY}")
+check_c_source_compiles("
+#include <tree_sitter/api.h>
+int
+main(void)
+{
+ TSQueryCursor *cursor = ts_query_cursor_new();
+ ts_query_cursor_set_max_start_depth(cursor, 32);
+ return 0;
+}
+" TS_HAS_SET_MAX_START_DEPTH)
+list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${TREESITTER_INCLUDE_DIR}")
+list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${TREESITTER_LIBRARY}")
+
+if(TS_HAS_SET_MAX_START_DEPTH)
+ target_compile_definitions(treesitter INTERFACE NVIM_TS_HAS_SET_MAX_START_DEPTH)
+endif()
diff --git a/cmake/FindUnibilium.cmake b/cmake/FindUnibilium.cmake
new file mode 100644
index 0000000000..4f62815793
--- /dev/null
+++ b/cmake/FindUnibilium.cmake
@@ -0,0 +1,11 @@
+find_path2(UNIBILIUM_INCLUDE_DIR unibilium.h)
+find_library2(UNIBILIUM_LIBRARY unibilium)
+
+find_package_handle_standard_args(Unibilium
+ REQUIRED_VARS UNIBILIUM_INCLUDE_DIR UNIBILIUM_LIBRARY)
+
+add_library(unibilium INTERFACE)
+target_include_directories(unibilium SYSTEM BEFORE INTERFACE ${UNIBILIUM_INCLUDE_DIR})
+target_link_libraries(unibilium INTERFACE ${UNIBILIUM_LIBRARY})
+
+mark_as_advanced(UNIBILIUM_INCLUDE_DIR UNIBILIUM_LIBRARY)
diff --git a/cmake/Findunibilium.cmake b/cmake/Findunibilium.cmake
deleted file mode 100644
index 7bfbcba942..0000000000
--- a/cmake/Findunibilium.cmake
+++ /dev/null
@@ -1,27 +0,0 @@
-find_path(UNIBILIUM_INCLUDE_DIR unibilium.h)
-find_library(UNIBILIUM_LIBRARY unibilium)
-
-find_package_handle_standard_args(unibilium
- REQUIRED_VARS UNIBILIUM_INCLUDE_DIR UNIBILIUM_LIBRARY)
-
-add_library(unibilium INTERFACE)
-target_include_directories(unibilium SYSTEM BEFORE INTERFACE ${UNIBILIUM_INCLUDE_DIR})
-target_link_libraries(unibilium INTERFACE ${UNIBILIUM_LIBRARY})
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIR}")
-list(APPEND CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARY}")
-check_c_source_compiles("
-#include <unibilium.h>
-
-int
-main(void)
-{
- unibi_str_from_var(unibi_var_from_str(\"\"));
- return unibi_num_from_var(unibi_var_from_num(0));
-}
-" UNIBI_HAS_VAR_FROM)
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${UNIBILIUM_INCLUDE_DIR}")
-list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${UNIBILIUM_LIBRARY}")
-if(UNIBI_HAS_VAR_FROM)
- target_compile_definitions(unibilium INTERFACE NVIM_UNIBI_HAS_VAR_FROM)
-endif()
diff --git a/cmake/Format.cmake b/cmake/Format.cmake
index 4115e66705..7097e5766f 100644
--- a/cmake/Format.cmake
+++ b/cmake/Format.cmake
@@ -1,35 +1,33 @@
# Returns a list of all files that has been changed in current branch compared
# to master branch. This includes unstaged, staged and committed files.
function(get_changed_files outvar)
- set(default_branch master)
-
execute_process(
COMMAND git branch --show-current
OUTPUT_VARIABLE current_branch
OUTPUT_STRIP_TRAILING_WHITESPACE)
execute_process(
- COMMAND git merge-base ${default_branch} ${current_branch}
+ COMMAND git merge-base master HEAD
OUTPUT_VARIABLE ancestor_commit
OUTPUT_STRIP_TRAILING_WHITESPACE)
# Changed files that have been committed
execute_process(
- COMMAND git diff --name-only ${ancestor_commit}...${current_branch}
+ COMMAND git diff --diff-filter=d --name-only ${ancestor_commit}...${current_branch}
OUTPUT_VARIABLE committed_files
OUTPUT_STRIP_TRAILING_WHITESPACE)
separate_arguments(committed_files NATIVE_COMMAND ${committed_files})
# Unstaged files
execute_process(
- COMMAND git diff --name-only
+ COMMAND git diff --diff-filter=d --name-only
OUTPUT_VARIABLE unstaged_files
OUTPUT_STRIP_TRAILING_WHITESPACE)
separate_arguments(unstaged_files NATIVE_COMMAND ${unstaged_files})
# Staged files
execute_process(
- COMMAND git diff --cached --name-only
+ COMMAND git diff --diff-filter=d --cached --name-only
OUTPUT_VARIABLE staged_files
OUTPUT_STRIP_TRAILING_WHITESPACE)
separate_arguments(staged_files NATIVE_COMMAND ${staged_files})
diff --git a/cmake/GenerateVersion.cmake b/cmake/GenerateVersion.cmake
index c092645140..a52dca970f 100644
--- a/cmake/GenerateVersion.cmake
+++ b/cmake/GenerateVersion.cmake
@@ -5,14 +5,17 @@ execute_process(
COMMAND git --git-dir=${NVIM_SOURCE_DIR}/.git --work-tree=${NVIM_SOURCE_DIR} describe --first-parent --dirty --always
OUTPUT_VARIABLE GIT_TAG
OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
RESULT_VARIABLE RES)
-
if(RES)
message(STATUS "Using NVIM_VERSION: ${NVIM_VERSION}")
file(WRITE "${OUTPUT}" "")
return()
endif()
+# Extract build info: "v0.9.0-145-g0f9113907" => "g0f9113907"
+string(REGEX REPLACE ".*\\-" "" NVIM_VERSION_BUILD "${GIT_TAG}")
+
# `git describe` annotates the most recent tagged release; for pre-release
# builds we append that to the dev version.
if(NVIM_VERSION_PRERELEASE)
@@ -23,7 +26,7 @@ if(NVIM_VERSION_PRERELEASE)
set(NVIM_VERSION "${NVIM_VERSION}-${NVIM_VERSION_GIT}")
endif()
-set(NVIM_VERSION_STRING "#define NVIM_VERSION_MEDIUM \"${NVIM_VERSION}\"\n")
+set(NVIM_VERSION_STRING "#define NVIM_VERSION_MEDIUM \"${NVIM_VERSION}\"\n#define NVIM_VERSION_BUILD \"${NVIM_VERSION_BUILD}\"\n")
string(SHA1 CURRENT_VERSION_HASH "${NVIM_VERSION_STRING}")
if(EXISTS ${OUTPUT})
diff --git a/cmake/GetCompileFlags.cmake b/cmake/GetCompileFlags.cmake
deleted file mode 100644
index 9b3c053871..0000000000
--- a/cmake/GetCompileFlags.cmake
+++ /dev/null
@@ -1,57 +0,0 @@
-function(get_compile_flags _compile_flags)
- string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
- set(compile_flags ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${build_type}})
-
- # Get flags set by target_compile_options().
- get_target_property(opt main_lib INTERFACE_COMPILE_OPTIONS)
- if(opt)
- list(APPEND compile_flags ${opt})
- endif()
-
- get_target_property(opt nvim COMPILE_OPTIONS)
- if(opt)
- list(APPEND compile_flags ${opt})
- endif()
-
- # Get flags set by target_compile_definitions().
- get_target_property(defs main_lib INTERFACE_COMPILE_DEFINITIONS)
- if(defs)
- foreach(def ${defs})
- list(APPEND compile_flags "-D${def}")
- endforeach()
- endif()
-
- get_target_property(defs nvim COMPILE_DEFINITIONS)
- if(defs)
- foreach(def ${defs})
- list(APPEND compile_flags "-D${def}")
- endforeach()
- endif()
-
- # Get include directories.
- get_target_property(dirs main_lib INTERFACE_INCLUDE_DIRECTORIES)
- if(dirs)
- foreach(dir ${dirs})
- list(APPEND compile_flags "-I${dir}")
- endforeach()
- endif()
-
- get_target_property(dirs main_lib INTERFACE_SYSTEM_INCLUDE_DIRECTORIES)
- if(dirs)
- foreach(dir ${dirs})
- list(APPEND compile_flags "-I${dir}")
- endforeach()
- endif()
-
- get_target_property(dirs nvim INCLUDE_DIRECTORIES)
- if(dirs)
- foreach(dir ${dirs})
- list(APPEND compile_flags "-I${dir}")
- endforeach()
- endif()
-
- list(REMOVE_DUPLICATES compile_flags)
- string(REPLACE ";" " " compile_flags "${compile_flags}")
-
- set(${_compile_flags} "${compile_flags}" PARENT_SCOPE)
-endfunction()
diff --git a/cmake/InstallHelpers.cmake b/cmake/InstallHelpers.cmake
index 3786c4177f..63bf2bb73b 100644
--- a/cmake/InstallHelpers.cmake
+++ b/cmake/InstallHelpers.cmake
@@ -7,9 +7,6 @@ if(CMAKE_SYSTEM_NAME MATCHES "BSD" AND NOT DEFINED CMAKE_INSTALL_MANDIR)
endif()
endif()
-# For $CMAKE_INSTALL_{DATAROOT,MAN, ...}DIR
-include(GNUInstallDirs)
-
# This will create any directories that need to be created in the destination
# path with the typical owner, group, and user permissions--independent of the
# umask setting.
@@ -167,12 +164,3 @@ function(glob_wrapper outvar)
endif()
set(${outvar} ${${outvar}} PARENT_SCOPE)
endfunction()
-
-function(globrecurse_wrapper outvar root)
- if(${CMAKE_VERSION} VERSION_LESS 3.12)
- file(GLOB_RECURSE ${outvar} RELATIVE ${root} ${ARGN})
- else()
- file(GLOB_RECURSE ${outvar} CONFIGURE_DEPENDS RELATIVE ${root} ${ARGN})
- endif()
- set(${outvar} ${${outvar}} PARENT_SCOPE)
-endfunction()
diff --git a/cmake/LibFindMacros.cmake b/cmake/LibFindMacros.cmake
deleted file mode 100644
index 726a8631f0..0000000000
--- a/cmake/LibFindMacros.cmake
+++ /dev/null
@@ -1,269 +0,0 @@
-# Version 2.2
-# Public Domain, originally written by Lasse Kärkkäinen <tronic>
-# Maintained at https://github.com/Tronic/cmake-modules
-# Please send your improvements as pull requests on Github.
-
-# Find another package and make it a dependency of the current package.
-# This also automatically forwards the "REQUIRED" argument.
-# Usage: libfind_package(<prefix> <another package> [extra args to find_package])
-macro (libfind_package PREFIX PKG)
- set(${PREFIX}_args ${PKG} ${ARGN})
- if (${PREFIX}_FIND_REQUIRED)
- set(${PREFIX}_args ${${PREFIX}_args} REQUIRED)
- endif()
- find_package(${${PREFIX}_args})
- set(${PREFIX}_DEPENDENCIES ${${PREFIX}_DEPENDENCIES};${PKG})
- unset(${PREFIX}_args)
-endmacro()
-
-# A simple wrapper to make pkg-config searches a bit easier.
-# Works the same as CMake's internal pkg_check_modules but is always quiet.
-macro (libfind_pkg_check_modules)
- find_package(PkgConfig QUIET)
- if (PKG_CONFIG_FOUND)
- pkg_check_modules(${ARGN} QUIET)
- endif()
-endmacro()
-
-# Avoid useless copy&pasta by doing what most simple libraries do anyway:
-# pkg-config, find headers, find library.
-# Usage: libfind_pkg_detect(<prefix> <pkg-config args> FIND_PATH <name> [other args] FIND_LIBRARY <name> [other args])
-# E.g. libfind_pkg_detect(SDL2 sdl2 FIND_PATH SDL.h PATH_SUFFIXES SDL2 FIND_LIBRARY SDL2)
-function (libfind_pkg_detect PREFIX)
- # Parse arguments
- set(argname pkgargs)
- foreach (i ${ARGN})
- if ("${i}" STREQUAL "FIND_PATH")
- set(argname pathargs)
- elseif ("${i}" STREQUAL "FIND_LIBRARY")
- set(argname libraryargs)
- else()
- set(${argname} ${${argname}} ${i})
- endif()
- endforeach()
- if (NOT pkgargs)
- message(FATAL_ERROR "libfind_pkg_detect requires at least a pkg_config package name to be passed.")
- endif()
- # Find library
- libfind_pkg_check_modules(${PREFIX}_PKGCONF ${pkgargs})
- if (pathargs)
- find_path(${PREFIX}_INCLUDE_DIR NAMES ${pathargs} HINTS ${${PREFIX}_PKGCONF_INCLUDE_DIRS})
- endif()
- if (libraryargs)
- find_library(${PREFIX}_LIBRARY NAMES ${libraryargs} HINTS ${${PREFIX}_PKGCONF_LIBRARY_DIRS})
- endif()
- # Read pkg-config version
- if (${PREFIX}_PKGCONF_VERSION)
- set(${PREFIX}_VERSION ${${PREFIX}_PKGCONF_VERSION} PARENT_SCOPE)
- endif()
-endfunction()
-
-# Extracts a version #define from a version.h file, output stored to <PREFIX>_VERSION.
-# Usage: libfind_version_header(Foobar foobar/version.h FOOBAR_VERSION_STR)
-# Fourth argument "QUIET" may be used for silently testing different define names.
-# This function does nothing if the version variable is already defined.
-function (libfind_version_header PREFIX VERSION_H DEFINE_NAME)
- # Skip processing if we already have a version or if the include dir was not found
- if (${PREFIX}_VERSION OR NOT ${PREFIX}_INCLUDE_DIR)
- return()
- endif()
- set(quiet ${${PREFIX}_FIND_QUIETLY})
- # Process optional arguments
- foreach(arg ${ARGN})
- if (arg STREQUAL "QUIET")
- set(quiet TRUE)
- else()
- message(AUTHOR_WARNING "Unknown argument ${arg} to libfind_version_header ignored.")
- endif()
- endforeach()
- # Read the header and parse for version number
- set(filename "${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
- if (NOT EXISTS ${filename})
- if (NOT quiet)
- message(AUTHOR_WARNING "Unable to find ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
- endif()
- return()
- endif()
- file(READ "${filename}" header)
- string(REGEX REPLACE ".*#[ \t]*define[ \t]*${DEFINE_NAME}[ \t]*\"([^\n]*)\".*" "\\1" match "${header}")
- # No regex match?
- if (match STREQUAL header)
- if (NOT quiet)
- message(AUTHOR_WARNING "Unable to find \#define ${DEFINE_NAME} \"<version>\" from ${${PREFIX}_INCLUDE_DIR}/${VERSION_H}")
- endif()
- return()
- endif()
- # Export the version string
- set(${PREFIX}_VERSION "${match}" PARENT_SCOPE)
-endfunction()
-
-# Do the final processing once the paths have been detected.
-# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
-# all the variables, each of which contain one include directory.
-# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
-# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
-# Also handles errors in case library detection was required, etc.
-function (libfind_process PREFIX)
- # Skip processing if already processed during this configuration run
- if (${PREFIX}_FOUND)
- return()
- endif()
-
- set(found TRUE) # Start with the assumption that the package was found
-
- # Did we find any files? Did we miss includes? These are for formatting better error messages.
- set(some_files FALSE)
- set(missing_headers FALSE)
-
- # Shorthands for some variables that we need often
- set(quiet ${${PREFIX}_FIND_QUIETLY})
- set(required ${${PREFIX}_FIND_REQUIRED})
- set(exactver ${${PREFIX}_FIND_VERSION_EXACT})
- set(findver "${${PREFIX}_FIND_VERSION}")
- set(version "${${PREFIX}_VERSION}")
-
- # Lists of config option names (all, includes, libs)
- unset(configopts)
- set(includeopts ${${PREFIX}_PROCESS_INCLUDES})
- set(libraryopts ${${PREFIX}_PROCESS_LIBS})
-
- # Process deps to add to
- foreach (i ${PREFIX} ${${PREFIX}_DEPENDENCIES})
- if (DEFINED ${i}_INCLUDE_OPTS OR DEFINED ${i}_LIBRARY_OPTS)
- # The package seems to export option lists that we can use, woohoo!
- list(APPEND includeopts ${${i}_INCLUDE_OPTS})
- list(APPEND libraryopts ${${i}_LIBRARY_OPTS})
- else()
- # If plural forms don't exist or they equal singular forms
- if ((NOT DEFINED ${i}_INCLUDE_DIRS AND NOT DEFINED ${i}_LIBRARIES) OR
- (${i}_INCLUDE_DIR STREQUAL ${i}_INCLUDE_DIRS AND ${i}_LIBRARY STREQUAL ${i}_LIBRARIES))
- # Singular forms can be used
- if (DEFINED ${i}_INCLUDE_DIR)
- list(APPEND includeopts ${i}_INCLUDE_DIR)
- endif()
- if (DEFINED ${i}_LIBRARY)
- list(APPEND libraryopts ${i}_LIBRARY)
- endif()
- else()
- # Oh no, we don't know the option names
- message(FATAL_ERROR "We couldn't determine config variable names for ${i} includes and libs. Aieeh!")
- endif()
- endif()
- endforeach()
-
- if (includeopts)
- list(REMOVE_DUPLICATES includeopts)
- endif()
-
- if (libraryopts)
- list(REMOVE_DUPLICATES libraryopts)
- endif()
-
- string(REGEX REPLACE ".*[ ;]([^ ;]*(_INCLUDE_DIRS|_LIBRARIES))" "\\1" tmp "${includeopts} ${libraryopts}")
- if (NOT tmp STREQUAL "${includeopts} ${libraryopts}")
- message(AUTHOR_WARNING "Plural form ${tmp} found in config options of ${PREFIX}. This works as before but is now deprecated. Please only use singular forms INCLUDE_DIR and LIBRARY, and update your find scripts for LibFindMacros > 2.0 automatic dependency system (most often you can simply remove the PROCESS variables entirely).")
- endif()
-
- # Include/library names separated by spaces (notice: not CMake lists)
- unset(includes)
- unset(libs)
-
- # Process all includes and set found false if any are missing
- foreach (i ${includeopts})
- list(APPEND configopts ${i})
- if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND")
- list(APPEND includes "${${i}}")
- else()
- set(found FALSE)
- set(missing_headers TRUE)
- endif()
- endforeach()
-
- # Process all libraries and set found false if any are missing
- foreach (i ${libraryopts})
- list(APPEND configopts ${i})
- if (NOT "${${i}}" STREQUAL "${i}-NOTFOUND")
- list(APPEND libs "${${i}}")
- else()
- set (found FALSE)
- endif()
- endforeach()
-
- # Version checks
- if (found AND findver)
- if (NOT version)
- message(WARNING "The find module for ${PREFIX} does not provide version information, so we'll just assume that it is OK. Please fix the module or remove package version requirements to get rid of this warning.")
- elseif (version VERSION_LESS findver OR (exactver AND NOT version VERSION_EQUAL findver))
- set(found FALSE)
- set(version_unsuitable TRUE)
- endif()
- endif()
-
- # If all-OK, hide all config options, export variables, print status and exit
- if (found)
- foreach (i ${configopts})
- mark_as_advanced(${i})
- endforeach()
- if (NOT quiet)
- message(STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
- if (LIBFIND_DEBUG)
- message(STATUS " ${PREFIX}_DEPENDENCIES=${${PREFIX}_DEPENDENCIES}")
- message(STATUS " ${PREFIX}_INCLUDE_OPTS=${includeopts}")
- message(STATUS " ${PREFIX}_INCLUDE_DIRS=${includes}")
- message(STATUS " ${PREFIX}_LIBRARY_OPTS=${libraryopts}")
- message(STATUS " ${PREFIX}_LIBRARIES=${libs}")
- endif()
- endif()
- set (${PREFIX}_INCLUDE_OPTS ${includeopts} PARENT_SCOPE)
- set (${PREFIX}_LIBRARY_OPTS ${libraryopts} PARENT_SCOPE)
- set (${PREFIX}_INCLUDE_DIRS ${includes} PARENT_SCOPE)
- set (${PREFIX}_LIBRARIES ${libs} PARENT_SCOPE)
- set (${PREFIX}_FOUND TRUE PARENT_SCOPE)
- return()
- endif()
-
- # Format messages for debug info and the type of error
- set(vars "Relevant CMake configuration variables:\n")
- foreach (i ${configopts})
- mark_as_advanced(CLEAR ${i})
- set(val ${${i}})
- if ("${val}" STREQUAL "${i}-NOTFOUND")
- set (val "<not found>")
- elseif (val AND NOT EXISTS ${val})
- set (val "${val} (does not exist)")
- else()
- set(some_files TRUE)
- endif()
- set(vars "${vars} ${i}=${val}\n")
- endforeach()
- set(vars "${vars}You may use CMake GUI, cmake -D or ccmake to modify the values. Delete CMakeCache.txt to discard all values and force full re-detection if necessary.\n")
- if (version_unsuitable)
- set(msg "${PREFIX} ${${PREFIX}_VERSION} was found but")
- if (exactver)
- set(msg "${msg} only version ${findver} is acceptable.")
- else()
- set(msg "${msg} version ${findver} is the minimum requirement.")
- endif()
- else()
- if (missing_headers)
- set(msg "We could not find development headers for ${PREFIX}. Do you have the necessary dev package installed?")
- elseif (some_files)
- set(msg "We only found some files of ${PREFIX}, not all of them. Perhaps your installation is incomplete or maybe we just didn't look in the right place?")
- if(findver)
- set(msg "${msg} This could also be caused by incompatible version (if it helps, at least ${PREFIX} ${findver} should work).")
- endif()
- else()
- set(msg "We were unable to find package ${PREFIX}.")
- endif()
- endif()
-
- # Fatal error out if REQUIRED
- if (required)
- set(msg "REQUIRED PACKAGE NOT FOUND\n${msg} This package is REQUIRED and you need to install it or adjust CMake configuration in order to continue building ${CMAKE_PROJECT_NAME}.")
- message(FATAL_ERROR "${msg}\n${vars}")
- endif()
- # Otherwise just print a nasty warning
- if (NOT quiet)
- message(WARNING "WARNING: MISSING PACKAGE\n${msg} This package is NOT REQUIRED and you may ignore this warning but by doing so you may miss some functionality of ${CMAKE_PROJECT_NAME}. \n${vars}")
- endif()
-endfunction()
diff --git a/cmake/LuaHelpers.cmake b/cmake/LuaHelpers.cmake
deleted file mode 100644
index 0239460f2b..0000000000
--- a/cmake/LuaHelpers.cmake
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Functions to help checking for a Lua interpreter
-#
-
-# Check if a module is available in Lua
-function(check_lua_module LUA_PRG_PATH MODULE RESULT_VAR)
- execute_process(COMMAND ${LUA_PRG_PATH} -l "${MODULE}" -e ""
- RESULT_VARIABLE module_missing)
- if(module_missing)
- set(${RESULT_VAR} False PARENT_SCOPE)
- else()
- set(${RESULT_VAR} True PARENT_SCOPE)
- endif()
-endfunction()
-
-# Check Lua interpreter for dependencies
-function(check_lua_deps LUA_PRG_PATH MODULES RESULT_VAR)
- # Check if the lua interpreter at the given path
- # satisfies all Neovim dependencies
- message(STATUS "Checking Lua interpreter: ${LUA_PRG_PATH}")
- if(NOT EXISTS ${LUA_PRG_PATH})
- message(STATUS
- "[${LUA_PRG_PATH}] file not found")
- endif()
-
- foreach(module ${MODULES})
- check_lua_module(${LUA_PRG_PATH} ${module} has_module)
- if(NOT has_module)
- message(STATUS
- "[${LUA_PRG_PATH}] The '${module}' lua package is required for building Neovim")
- set(${RESULT_VAR} False PARENT_SCOPE)
- return()
- endif()
- endforeach()
-
- set(${RESULT_VAR} True PARENT_SCOPE)
-endfunction()
diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake
index d724f43a5f..8d5b0d2402 100644
--- a/cmake/RunTests.cmake
+++ b/cmake/RunTests.cmake
@@ -1,13 +1,5 @@
# Set LC_ALL to meet expectations of some locale-sensitive tests.
set(ENV{LC_ALL} "en_US.UTF-8")
-
-if(POLICY CMP0012)
- # Avoid policy warning due to CI=true. This is needed even if the main
- # project has already set this policy as project settings aren't inherited
- # when using cmake script mode (-P).
- cmake_policy(SET CMP0012 NEW)
-endif()
-
set(ENV{VIMRUNTIME} ${WORKING_DIR}/runtime)
set(ENV{NVIM_RPLUGIN_MANIFEST} ${BUILD_DIR}/Xtest_rplugin_manifest)
set(ENV{XDG_CONFIG_HOME} ${BUILD_DIR}/Xtest_xdg/config)
@@ -72,15 +64,15 @@ endif()
set(ENV{SYSTEM_NAME} ${CMAKE_HOST_SYSTEM_NAME}) # used by test/helpers.lua.
-# TODO: eventually always use NVIM_PRG as the runner
-if("${TEST_TYPE}" STREQUAL "unit")
- set(RUNNER_PRG ${NVIM_PRG} -ll ${WORKING_DIR}/test/busted_runner.lua)
-else()
- set(RUNNER_PRG ${BUSTED_PRG})
+if(NOT WIN32)
+ # Tests assume POSIX "sh" and may fail if SHELL=fish. #24941 #6172
+ set(ENV{SHELL} sh)
endif()
execute_process(
- COMMAND ${RUNNER_PRG} -v -o test.busted.outputHandlers.${BUSTED_OUTPUT_TYPE}
+ # Note: because of "-ll" (low-level interpreter mode), some modules like
+ # _editor.lua are not loaded.
+ COMMAND ${NVIM_PRG} -ll ${WORKING_DIR}/test/lua_runner.lua ${DEPS_INSTALL_DIR} busted -v -o test.busted.outputHandlers.${BUSTED_OUTPUT_TYPE}
--lazy --helper=${TEST_DIR}/${TEST_TYPE}/preload.lua
--lpath=${BUILD_DIR}/?.lua
--lpath=${WORKING_DIR}/runtime/lua/?.lua
@@ -96,7 +88,7 @@ execute_process(
file(GLOB RM_FILES ${BUILD_DIR}/Xtest_*)
file(REMOVE_RECURSE ${RM_FILES})
-if(NOT res EQUAL 0)
+if(res)
message(STATUS "Tests exited non-zero: ${res}")
if("${err}" STREQUAL "")
message(STATUS "No output to stderr.")
@@ -105,7 +97,7 @@ if(NOT res EQUAL 0)
endif()
# Dump the logfile on CI (if not displayed and moved already).
- if($ENV{CI})
+ if(CI_BUILD)
if(EXISTS $ENV{NVIM_LOG_FILE} AND NOT EXISTS $ENV{NVIM_LOG_FILE}.displayed)
file(READ $ENV{NVIM_LOG_FILE} out)
message(STATUS "$NVIM_LOG_FILE: $ENV{NVIM_LOG_FILE}\n${out}")
diff --git a/cmake/Util.cmake b/cmake/Util.cmake
index e15b44d29a..01d34d6752 100644
--- a/cmake/Util.cmake
+++ b/cmake/Util.cmake
@@ -4,7 +4,6 @@
# depends on the value of TOUCH_STRATEGY.
#
# Options:
-# REQUIRED - Abort if COMMAND doesn't exist.
#
# Single value arguments:
# TARGET - Name of the target
@@ -53,7 +52,7 @@
# files.
function(add_glob_target)
cmake_parse_arguments(ARG
- "REQUIRED"
+ ""
"TARGET;COMMAND;GLOB_PAT;TOUCH_STRATEGY"
"FLAGS;FILES;GLOB_DIRS;EXCLUDE"
${ARGN}
@@ -61,14 +60,8 @@ function(add_glob_target)
if(NOT ARG_COMMAND)
add_custom_target(${ARG_TARGET})
- if(ARG_REQUIRED)
- add_custom_command(TARGET ${ARG_TARGET}
- COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET}: ${ARG_COMMAND} not found"
- COMMAND false)
- else()
- add_custom_command(TARGET ${ARG_TARGET}
- COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found")
- endif()
+ add_custom_command(TARGET ${ARG_TARGET}
+ COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found")
return()
endif()
@@ -169,8 +162,8 @@ endfunction()
# cmake -B build
# cmake --build build --config Release
#
-# Passing CMAKE_BUILD_TYPE for multi-config generators will now not only
-# not be used, but also generate a warning for the user.
+# Passing CMAKE_BUILD_TYPE for multi-config generators will not only not be
+# used, but also generate a warning for the user.
function(set_default_buildtype)
set(allowableBuildTypes Debug Release MinSizeRel RelWithDebInfo)
@@ -193,3 +186,14 @@ function(set_default_buildtype)
endif()
endif()
endfunction()
+
+# Check if a module is available in Lua
+function(check_lua_module LUA_PRG_PATH MODULE RESULT_VAR)
+ execute_process(COMMAND ${LUA_PRG_PATH} -l "${MODULE}" -e ""
+ RESULT_VARIABLE module_missing)
+ if(module_missing)
+ set(${RESULT_VAR} FALSE PARENT_SCOPE)
+ else()
+ set(${RESULT_VAR} TRUE PARENT_SCOPE)
+ endif()
+endfunction()
diff --git a/cmake/WindowsDllCopy.cmake b/cmake/WindowsDllCopy.cmake
index fbbabf3a0c..c972d88f57 100644
--- a/cmake/WindowsDllCopy.cmake
+++ b/cmake/WindowsDllCopy.cmake
@@ -6,13 +6,13 @@
# - CMAKE_PREFIX_PATH: A list of directories to search for dependencies
if(NOT DEFINED BINARY)
- message(FATAL_ERROR "Missing required argument -DBINARY=")
+ message(FATAL_ERROR "Missing required argument -D BINARY=")
endif()
if(NOT DEFINED DST)
- message(FATAL_ERROR "Missing required arguments -DDST=")
+ message(FATAL_ERROR "Missing required arguments -D DST=")
endif()
if(NOT DEFINED CMAKE_PREFIX_PATH)
- message(FATAL_ERROR "Missing required arguments -DCMAKE_PREFIX_PATH=")
+ message(FATAL_ERROR "Missing required arguments -D CMAKE_PREFIX_PATH=")
endif()
include(GetPrerequisites)
@@ -23,8 +23,9 @@ foreach(DLL_NAME ${DLLS})
message(FATAL_ERROR "Unable to find dependency ${DLL_NAME}")
endif()
- message("Copying ${DLL_NAME} to ${DST}")
+ if(CI_BUILD)
+ message("Copying ${DLL_NAME} to ${DST}")
+ endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${DLL_PATH} ${DST})
unset(DLL_PATH CACHE)
endforeach()
-
diff --git a/codecov.yml b/codecov.yml
deleted file mode 100644
index a83fd916ee..0000000000
--- a/codecov.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-# To validate:
-# cat codecov.yml | curl --data-binary @- https://codecov.io/validate
-
-codecov:
- notify:
- require_ci_to_pass: no
- ci:
- - appveyor
- - travis
- - !neovim-qb.szakmeister.net
-
-coverage:
- precision: 2
- round: down
- range: "70...100"
-
- status:
- project:
- default:
- threshold: 1
- patch:
- default:
- threshold: 1
- only_pulls: true
- changes: no
-
-comment: off
diff --git a/contrib/asan.sh b/contrib/asan.sh
index 7e7dffa1af..6354d65514 100755
--- a/contrib/asan.sh
+++ b/contrib/asan.sh
@@ -13,10 +13,7 @@ export CC='clang'
# Change to detect_leaks=1 to detect memory leaks (slower).
export ASAN_OPTIONS="detect_leaks=0:log_path=$log_path/asan"
-# Show backtraces in the logs.
-export UBSAN_OPTIONS="print_stacktrace=1"
-
-make -C "$root_path" CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
+make -C "$root_path" CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON"
VIMRUNTIME="$root_path"/runtime "$root_path"/build/bin/nvim
# Need to manually reset terminal to avoid mangled output, nvim does not
diff --git a/contrib/flake.lock b/contrib/flake.lock
index c6dfb4642b..554dc22bdb 100644
--- a/contrib/flake.lock
+++ b/contrib/flake.lock
@@ -1,12 +1,15 @@
{
"nodes": {
"flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
"locked": {
- "lastModified": 1667395993,
- "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+ "lastModified": 1685518550,
+ "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+ "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
"type": "github"
},
"original": {
@@ -17,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1671983799,
- "narHash": "sha256-Z2Ro6hFPZHkBqkVXY5/aBUzxi5xizQGvuHQ9+T5B/ks=",
+ "lastModified": 1686226982,
+ "narHash": "sha256-nLuiPoeiVfqqzeq9rmXxpybh77VS37dsY/k8N2LoxVg=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "fad51abd42ca17a60fc1d4cb9382e2d79ae31836",
+ "rev": "a64b73e07d4aa65cfcbda29ecf78eaf9e72e44bd",
"type": "github"
},
"original": {
@@ -36,6 +39,21 @@
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
}
},
"root": "root",
diff --git a/contrib/flake.nix b/contrib/flake.nix
index 0898c943d7..57e56d0fdb 100644
--- a/contrib/flake.nix
+++ b/contrib/flake.nix
@@ -7,15 +7,64 @@
};
outputs = { self, nixpkgs, flake-utils }:
+ let
+ inherit (builtins)
+ elemAt
+ foldl'
+ mapAttrs
+ match
+ readFile
+ ;
+ inherit (nixpkgs.lib)
+ const
+ flip
+ pipe
+ remove
+ splitString
+ toLower
+ ;
+ in
{
overlay = final: prev: {
- neovim = final.neovim-unwrapped.overrideAttrs (oa: rec {
+ neovim = (final.neovim-unwrapped.override {
+ treesitter-parsers = pipe ../cmake.deps/deps.txt [
+ readFile
+ (splitString "\n")
+ (map (match "TREESITTER_([A-Z_]+)_(URL|SHA256)[[:space:]]+([^[:space:]]+)[[:space:]]*"))
+ (remove null)
+ (flip foldl' { }
+ (acc: matches:
+ let
+ lang = toLower (elemAt matches 0);
+ type = toLower (elemAt matches 1);
+ value = elemAt matches 2;
+ in
+ acc // {
+ ${lang} = acc.${lang} or { } // {
+ ${type} = value;
+ };
+ }))
+ (mapAttrs (const final.fetchurl))
+ (self: self // {
+ markdown = final.stdenv.mkDerivation {
+ inherit (self.markdown) name;
+ src = self.markdown;
+ installPhase = ''
+ mv tree-sitter-markdown $out
+ '';
+ };
+ })
+ ];
+ }).overrideAttrs (oa: rec {
version = self.shortRev or "dirty";
src = ../.;
- preConfigure = ''
+ preConfigure = oa.preConfigure or "" + ''
sed -i cmake.config/versiondef.h.in -e 's/@NVIM_VERSION_PRERELEASE@/-dev-${version}/'
'';
+ nativeBuildInputs = oa.nativeBuildInputs ++ [
+ final.libiconv
+ ];
});
# a development binary to help debug issues
@@ -33,7 +82,6 @@
NIX_CFLAGS_COMPILE = " -ggdb -Og";
cmakeBuildType = "Debug";
- cmakeFlags = oa.cmakeFlags ++ [ "-DMIN_LOG_LEVEL=0" ];
disallowedReferences = [ ];
});
@@ -45,12 +93,11 @@
}).overrideAttrs (oa: {
cmakeFlags = oa.cmakeFlags ++ [
"-DLUACHECK_PRG=${luacheck}/bin/luacheck"
- "-DMIN_LOG_LEVEL=0"
"-DENABLE_LTO=OFF"
] ++ final.lib.optionals final.stdenv.isLinux [
# https://github.com/google/sanitizers/wiki/AddressSanitizerFlags
# https://clang.llvm.org/docs/AddressSanitizer.html#symbolizing-the-reports
- "-DCLANG_ASAN_UBSAN=ON"
+ "-DENABLE_ASAN_UBSAN=ON"
];
});
};
@@ -105,7 +152,6 @@
# ASAN_OPTIONS=detect_leaks=1
export ASAN_OPTIONS="log_path=./test.log:abort_on_error=1"
- export UBSAN_OPTIONS=print_stacktrace=1
# for treesitter functionaltests
mkdir -p runtime/parser
diff --git a/contrib/local.mk.example b/contrib/local.mk.example
index 20396e86ae..bda2c1b4dc 100644
--- a/contrib/local.mk.example
+++ b/contrib/local.mk.example
@@ -31,9 +31,10 @@
# often. You can disable it explicitly:
# CMAKE_EXTRA_FLAGS += -DENABLE_LTO=OFF
-# Log levels: 0 (DEBUG), 1 (INFO), 2 (WARNING), 3 (ERROR)
-# Default is 1 (INFO) unless CMAKE_BUILD_TYPE is Release or RelWithDebInfo.
-# CMAKE_EXTRA_FLAGS += -DMIN_LOG_LEVEL=1
+# Log levels: DEBUG, INFO, WARNING, ERROR
+# For Debug builds all log levels are used
+# For Release and RelWithDebInfo builds only WARNING and ERROR are used, unless:
+# CMAKE_EXTRA_FLAGS += -DLOG_DEBUG
# By default, nvim uses bundled versions of its required third-party
# dependencies.
@@ -45,7 +46,6 @@
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBUV=OFF
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LIBVTERM=OFF
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LUAJIT=OFF
-# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_LUAROCKS=OFF
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_MSGPACK=OFF
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_UNIBILIUM=OFF
# DEPS_CMAKE_FLAGS += -DUSE_BUNDLED_UTF8PROC=OFF
diff --git a/contrib/luarc.json b/contrib/luarc.json
deleted file mode 100644
index ebad0581b9..0000000000
--- a/contrib/luarc.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "runtime.version": "LuaJIT",
- "diagnostics": {
- "enable": true,
- "globals": [
- "vim",
- "describe",
- "pending",
- "it",
- "before_each",
- "after_each",
- "setup",
- "teardown",
- "finally",
- "lfs"
- ]
- },
- "workspace": {
- "library": [
- "runtime/lua",
- "${3rd}/lfs/library"
- ],
- "checkThirdParty": false,
- "maxPreload": 2000,
- "preloadFileSize": 1000
- },
- "telemetry.enable": false
-}
diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt
index 581a4545db..0077604141 100644
--- a/runtime/CMakeLists.txt
+++ b/runtime/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(SYN_VIM_GENERATOR ${PROJECT_SOURCE_DIR}/scripts/genvimvim.lua)
+set(SYN_VIM_GENERATOR ${PROJECT_SOURCE_DIR}/src/nvim/generators/gen_vimvim.lua)
set(GENERATED_RUNTIME_DIR ${PROJECT_BINARY_DIR}/runtime)
set(GENERATED_SYN_VIM ${GENERATED_RUNTIME_DIR}/syntax/vim/generated.vim)
set(GENERATED_HELP_TAGS ${GENERATED_RUNTIME_DIR}/doc/tags)
@@ -9,10 +9,13 @@ file(MAKE_DIRECTORY ${GENERATED_RUNTIME_DIR})
file(MAKE_DIRECTORY ${GENERATED_RUNTIME_DIR}/syntax)
file(MAKE_DIRECTORY ${GENERATED_RUNTIME_DIR}/syntax/vim)
+get_directory_property(LUA_GEN DIRECTORY ${PROJECT_SOURCE_DIR}/src/nvim DEFINITION LUA_GEN)
+get_directory_property(LUA_GEN_DEPS DIRECTORY ${PROJECT_SOURCE_DIR}/src/nvim DEFINITION LUA_GEN_DEPS)
+
add_custom_command(OUTPUT ${GENERATED_SYN_VIM}
- COMMAND ${LUA_PRG} ${SYN_VIM_GENERATOR}
- ${PROJECT_SOURCE_DIR}/src/nvim ${GENERATED_SYN_VIM} ${FUNCS_DATA}
+ COMMAND ${LUA_GEN} ${SYN_VIM_GENERATOR} ${GENERATED_SYN_VIM} ${FUNCS_DATA}
DEPENDS
+ ${LUA_GEN_DEPS}
${SYN_VIM_GENERATOR}
${PROJECT_SOURCE_DIR}/src/nvim/ex_cmds.lua
${PROJECT_SOURCE_DIR}/src/nvim/auevents.lua
@@ -116,23 +119,17 @@ install_helper(
FILES ${CMAKE_CURRENT_SOURCE_DIR}/nvim.png
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps)
-install_helper(
- FILES ${CMAKE_CURRENT_SOURCE_DIR}/neovim.ico
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime)
-
-globrecurse_wrapper(RUNTIME_PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR} *.awk *.sh *.bat)
+glob_wrapper(RUNTIME_ROOT_FILES *.vim *.lua *.ico)
+install_helper(FILES ${RUNTIME_ROOT_FILES}
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/)
-foreach(PROG ${RUNTIME_PROGRAMS})
- get_filename_component(BASEDIR ${PROG} DIRECTORY)
- install_helper(PROGRAMS ${PROG}
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/${BASEDIR})
+glob_wrapper(RUNTIME_DIRS */)
+foreach(D ${RUNTIME_DIRS})
+ if(IS_DIRECTORY ${D})
+ install_helper(DIRECTORY ${D}
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/)
+ endif()
endforeach()
-globrecurse_wrapper(RUNTIME_FILES ${CMAKE_CURRENT_SOURCE_DIR}
- *.vim *.lua *.scm *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json)
-
-foreach(F ${RUNTIME_FILES})
- get_filename_component(BASEDIR ${F} DIRECTORY)
- install_helper(FILES ${F}
- DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/${BASEDIR})
-endforeach()
+# only foo.sh script in runtime/
+install_helper(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/macros/less.sh DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/macros/)
diff --git a/runtime/autoload/cargo.vim b/runtime/autoload/cargo.vim
new file mode 100644
index 0000000000..6696b3105f
--- /dev/null
+++ b/runtime/autoload/cargo.vim
@@ -0,0 +1,149 @@
+" Last Modified: 2023-09-11
+
+function! cargo#Load()
+ " Utility call to get this script loaded, for debugging
+endfunction
+
+function! cargo#cmd(args) abort
+ " Trim trailing spaces. This is necessary since :terminal command parses
+ " trailing spaces as an empty argument.
+ let args = substitute(a:args, '\s\+$', '', '')
+ if exists('g:cargo_shell_command_runner')
+ let cmd = g:cargo_shell_command_runner
+ elseif has('terminal')
+ let cmd = 'terminal'
+ elseif has('nvim')
+ let cmd = 'noautocmd new | terminal'
+ else
+ let cmd = '!'
+ endif
+ execute cmd 'cargo' args
+endfunction
+
+function! s:nearest_cargo(...) abort
+ " If the second argument is not specified, the first argument determines
+ " whether we will start from the current directory or the directory of the
+ " current buffer, otherwise, we start with the provided path on the
+ " second argument.
+
+ let l:is_getcwd = get(a:, 1, 0)
+ if l:is_getcwd
+ let l:starting_path = get(a:, 2, getcwd())
+ else
+ let l:starting_path = get(a:, 2, expand('%:p:h'))
+ endif
+
+ return findfile('Cargo.toml', l:starting_path . ';')
+endfunction
+
+function! cargo#nearestCargo(is_getcwd) abort
+ return s:nearest_cargo(a:is_getcwd)
+endfunction
+
+function! cargo#nearestWorkspaceCargo(is_getcwd) abort
+ let l:nearest = s:nearest_cargo(a:is_getcwd)
+ while l:nearest !=# ''
+ for l:line in readfile(l:nearest, '', 0x100)
+ if l:line =~# '\V[workspace]'
+ return l:nearest
+ endif
+ endfor
+ let l:next = fnamemodify(l:nearest, ':p:h:h')
+ let l:nearest = s:nearest_cargo(0, l:next)
+ endwhile
+ return ''
+endfunction
+
+function! cargo#nearestRootCargo(is_getcwd) abort
+ " Try to find a workspace Cargo.toml, and if not found, take the nearest
+ " regular Cargo.toml
+ let l:workspace_cargo = cargo#nearestWorkspaceCargo(a:is_getcwd)
+ if l:workspace_cargo !=# ''
+ return l:workspace_cargo
+ endif
+ return s:nearest_cargo(a:is_getcwd)
+endfunction
+
+
+function! cargo#build(args)
+ call cargo#cmd("build " . a:args)
+endfunction
+
+function! cargo#check(args)
+ call cargo#cmd("check " . a:args)
+endfunction
+
+function! cargo#clean(args)
+ call cargo#cmd("clean " . a:args)
+endfunction
+
+function! cargo#doc(args)
+ call cargo#cmd("doc " . a:args)
+endfunction
+
+function! cargo#new(args)
+ call cargo#cmd("new " . a:args)
+ cd `=a:args`
+endfunction
+
+function! cargo#init(args)
+ call cargo#cmd("init " . a:args)
+endfunction
+
+function! cargo#run(args)
+ call cargo#cmd("run " . a:args)
+endfunction
+
+function! cargo#test(args)
+ call cargo#cmd("test " . a:args)
+endfunction
+
+function! cargo#bench(args)
+ call cargo#cmd("bench " . a:args)
+endfunction
+
+function! cargo#update(args)
+ call cargo#cmd("update " . a:args)
+endfunction
+
+function! cargo#search(args)
+ call cargo#cmd("search " . a:args)
+endfunction
+
+function! cargo#publish(args)
+ call cargo#cmd("publish " . a:args)
+endfunction
+
+function! cargo#install(args)
+ call cargo#cmd("install " . a:args)
+endfunction
+
+function! cargo#runtarget(args)
+ let l:filename = expand('%:p')
+ let l:read_manifest = system('cargo read-manifest')
+ let l:metadata = json_decode(l:read_manifest)
+ let l:targets = get(l:metadata, 'targets', [])
+ let l:did_run = 0
+ for l:target in l:targets
+ let l:src_path = get(l:target, 'src_path', '')
+ let l:kinds = get(l:target, 'kind', [])
+ let l:name = get(l:target, 'name', '')
+ if l:src_path == l:filename
+ if index(l:kinds, 'example') != -1
+ let l:did_run = 1
+ call cargo#run("--example " . shellescape(l:name) . " " . a:args)
+ return
+ elseif index(l:kinds, 'bin') != -1
+ let l:did_run = 1
+ call cargo#run("--bin " . shellescape(l:name) . " " . a:args)
+ return
+ endif
+ endif
+ endfor
+ if l:did_run != 1
+ call cargo#run(a:args)
+ return
+ endif
+endfunction
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/autoload/cargo/quickfix.vim b/runtime/autoload/cargo/quickfix.vim
new file mode 100644
index 0000000000..f2a006f6c5
--- /dev/null
+++ b/runtime/autoload/cargo/quickfix.vim
@@ -0,0 +1,29 @@
+" Last Modified: 2023-09-11
+
+function! cargo#quickfix#CmdPre() abort
+ if &filetype ==# 'rust' && get(b:, 'current_compiler', '') ==# 'cargo' &&
+ \ &makeprg =~ '\V\^cargo\ \.\*'
+ " Preserve the current directory, and 'lcd' to the nearest Cargo file.
+ let b:rust_compiler_cargo_qf_has_lcd = haslocaldir()
+ let b:rust_compiler_cargo_qf_prev_cd = getcwd()
+ let b:rust_compiler_cargo_qf_prev_cd_saved = 1
+ let l:nearest = fnamemodify(cargo#nearestRootCargo(0), ':h')
+ execute 'lchdir! '.l:nearest
+ else
+ let b:rust_compiler_cargo_qf_prev_cd_saved = 0
+ endif
+endfunction
+
+function! cargo#quickfix#CmdPost() abort
+ if exists("b:rust_compiler_cargo_qf_prev_cd_saved") && b:rust_compiler_cargo_qf_prev_cd_saved
+ " Restore the current directory.
+ if b:rust_compiler_cargo_qf_has_lcd
+ execute 'lchdir! '.b:rust_compiler_cargo_qf_prev_cd
+ else
+ execute 'chdir! '.b:rust_compiler_cargo_qf_prev_cd
+ endif
+ let b:rust_compiler_cargo_qf_prev_cd_saved = 0
+ endif
+endfunction
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/autoload/ccomplete.lua b/runtime/autoload/ccomplete.lua
index f4a3eabd9a..ce85e84f7a 100644
--- a/runtime/autoload/ccomplete.lua
+++ b/runtime/autoload/ccomplete.lua
@@ -24,10 +24,11 @@ local SearchMembers = nil
-- vim9script
-- # Vim completion script
--- # Language: C
--- # Maintainer: Bram Moolenaar <Bram@vim.org>
+-- # Language: C
+-- # Maintainer: The Vim Project <https://github.com/vim/vim>
+-- # Last Change: 2023 Aug 10
-- # Rewritten in Vim9 script by github user lacygoill
--- # Last Change: 2022 Jan 31
+-- # Former Maintainer: Bram Moolenaar <Bram@vim.org>
prepended = ''
grepCache = vim.empty_dict()
diff --git a/runtime/autoload/dist/vim.vim b/runtime/autoload/dist/vim.vim
new file mode 100644
index 0000000000..021244c93b
--- /dev/null
+++ b/runtime/autoload/dist/vim.vim
@@ -0,0 +1,32 @@
+" Vim runtime support library,
+" runs the vim9 script version or legacy script version
+" on demand (mostly for Neovim compatability)
+"
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Nov 04
+
+
+" enable the zip and gzip plugin by default, if not set
+if !exists('g:zip_exec')
+ let g:zip_exec = 1
+endif
+
+if !exists('g:gzip_exec')
+ let g:gzip_exec = 1
+endif
+
+if !has('vim9script')
+ function dist#vim#IsSafeExecutable(filetype, executable)
+ let cwd = getcwd()
+ return get(g:, a:filetype .. '_exec', get(g:, 'plugin_exec', 0)) &&
+ \ (fnamemodify(exepath(a:executable), ':p:h') !=# cwd
+ \ || (split($PATH, has('win32') ? ';' : ':')->index(cwd) != -1 &&
+ \ cwd != '.'))
+ endfunction
+
+ finish
+endif
+
+def dist#vim#IsSafeExecutable(filetype: string, executable: string): bool
+ return dist#vim9#IsSafeExecutable(filetype, executable)
+enddef
diff --git a/runtime/autoload/gzip.vim b/runtime/autoload/gzip.vim
index e4adec0947..26b1cda034 100644
--- a/runtime/autoload/gzip.vim
+++ b/runtime/autoload/gzip.vim
@@ -1,6 +1,7 @@
" Vim autoload file for editing compressed files.
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2016 Sep 28
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" These functions are used by the gzip plugin.
@@ -9,12 +10,17 @@
fun s:check(cmd)
let name = substitute(a:cmd, '\(\S*\).*', '\1', '')
if !exists("s:have_" . name)
+ " safety check, don't execute anything from the current directory
+ let f = dist#vim#IsSafeExecutable('gzip', name)
+ if !f
+ echoerr "Warning: NOT executing " .. name .. " from current directory!"
+ endif
let e = executable(name)
if e < 0
let r = system(name . " --version")
let e = (r !~ "not found" && r != "")
endif
- exe "let s:have_" . name . "=" . e
+ exe "let s:have_" . name . "=" . (e && f)
endif
exe "return s:have_" . name
endfun
diff --git a/runtime/autoload/health.vim b/runtime/autoload/health.vim
index 5fd4627b11..a7dbedab08 100644
--- a/runtime/autoload/health.vim
+++ b/runtime/autoload/health.vim
@@ -1,207 +1,40 @@
-" Runs the specified healthchecks.
-" Runs all discovered healthchecks if a:plugin_names is empty.
-function! health#check(plugin_names) abort
- let healthchecks = empty(a:plugin_names)
- \ ? s:discover_healthchecks()
- \ : s:get_healthcheck(a:plugin_names)
-
- " Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
- let emptybuf = (bufnr('$') == 1 && empty(getline(1)) && 1 == line('$'))
- execute (emptybuf ? 'buffer' : 'tab sbuffer') nvim_create_buf(v:true, v:true)
- if bufexists('health://')
- bwipe health://
- endif
- file health://
- setfiletype checkhealth
-
- if empty(healthchecks)
- call setline(1, 'ERROR: No healthchecks found.')
- else
- redraw|echo 'Running healthchecks...'
- for name in sort(keys(healthchecks))
- let [func, type] = healthchecks[name]
- let s:output = []
- try
- if func == ''
- throw 'healthcheck_not_found'
- endif
- eval type == 'v' ? call(func, []) : luaeval(func)
- " in the event the healthcheck doesn't return anything
- " (the plugin author should avoid this possibility)
- if len(s:output) == 0
- throw 'healthcheck_no_return_value'
- endif
- catch
- let s:output = [] " Clear the output
- if v:exception =~# 'healthcheck_not_found'
- call health#report_error('No healthcheck found for "'.name.'" plugin.')
- elseif v:exception =~# 'healthcheck_no_return_value'
- call health#report_error('The healthcheck report for "'.name.'" plugin is empty.')
- else
- call health#report_error(printf(
- \ "Failed to run healthcheck for \"%s\" plugin. Exception:\n%s\n%s",
- \ name, v:throwpoint, v:exception))
- endif
- endtry
- let header = [repeat('=', 78), name .. ': ' .. func, '']
- " remove empty line after header from report_start
- let s:output = s:output[0] == '' ? s:output[1:] : s:output
- let s:output = header + s:output + ['']
- call append('$', s:output)
- redraw
- endfor
+function! s:deprecate(type) abort
+ let deprecate = v:lua.vim.deprecate('health#report_' . a:type, 'vim.health.' . a:type, '0.11')
+ redraw | echo 'Running healthchecks...'
+ if deprecate isnot v:null
+ call v:lua.vim.health.warn(deprecate)
endif
-
- " Clear the 'Running healthchecks...' message.
- redraw|echo ''
-endfunction
-
-function! s:collect_output(output)
- let s:output += split(a:output, "\n", 1)
endfunction
-" Starts a new report.
function! health#report_start(name) abort
- call s:collect_output(printf("\n%s ~", a:name))
+ call v:lua.vim.health.start(a:name)
+ call s:deprecate('start')
endfunction
-" Indents lines *except* line 1 of a string if it contains newlines.
-function! s:indent_after_line1(s, columns) abort
- let lines = split(a:s, "\n", 0)
- if len(lines) < 2 " We do not indent line 1, so nothing to do.
- return a:s
- endif
- for i in range(1, len(lines)-1) " Indent lines after the first.
- let lines[i] = substitute(lines[i], '^\s*', repeat(' ', a:columns), 'g')
- endfor
- return join(lines, "\n")
+function! health#report_info(msg) abort
+ call v:lua.vim.health.info(a:msg)
+ call s:deprecate('info')
endfunction
-" Changes ':h clipboard' to ':help |clipboard|'.
-function! s:help_to_link(s) abort
- return substitute(a:s, '\v:h%[elp] ([^|][^"\r\n ]+)', ':help |\1|', 'g')
+function! health#report_ok(msg) abort
+ call v:lua.vim.health.ok(a:msg)
+ call s:deprecate('ok')
endfunction
-" Format a message for a specific report item.
-" a:1: Optional advice (string or list)
-function! s:format_report_message(status, msg, ...) abort " {{{
- let output = '- ' .. a:status .. (empty(a:status) ? '' : ' ') .. s:indent_after_line1(a:msg, 2)
-
- " Optional parameters
+function! health#report_warn(msg, ...) abort
if a:0 > 0
- let advice = type(a:1) == type('') ? [a:1] : a:1
- if type(advice) != type([])
- throw 'a:1: expected String or List'
- endif
-
- " Report each suggestion
- if !empty(advice)
- let output .= "\n - ADVICE:"
- for suggestion in advice
- let output .= "\n - " . s:indent_after_line1(suggestion, 6)
- endfor
- endif
- endif
-
- return s:help_to_link(output)
-endfunction " }}}
-
-" Reports a message as a listitem in the current section.
-function! health#report_info(msg) abort " {{{
- call s:collect_output(s:format_report_message('', a:msg))
-endfunction " }}}
-
-" Reports a successful healthcheck.
-function! health#report_ok(msg) abort " {{{
- call s:collect_output(s:format_report_message('OK', a:msg))
-endfunction " }}}
-
-" Reports a health warning.
-" a:1: Optional advice (string or list)
-function! health#report_warn(msg, ...) abort " {{{
- if a:0 > 0
- call s:collect_output(s:format_report_message('WARNING', a:msg, a:1))
+ call v:lua.vim.health.warn(a:msg, a:1)
else
- call s:collect_output(s:format_report_message('WARNING', a:msg))
+ call v:lua.vim.health.warn(a:msg)
endif
-endfunction " }}}
+ call s:deprecate('warn')
+endfunction
-" Reports a failed healthcheck.
-" a:1: Optional advice (string or list)
-function! health#report_error(msg, ...) abort " {{{
+function! health#report_error(msg, ...) abort
if a:0 > 0
- call s:collect_output(s:format_report_message('ERROR', a:msg, a:1))
+ call v:lua.vim.health.error(a:msg, a:1)
else
- call s:collect_output(s:format_report_message('ERROR', a:msg))
+ call v:lua.vim.health.error(a:msg)
endif
-endfunction " }}}
-
-" From a path return a list [{name}, {func}, {type}] representing a healthcheck
-function! s:filepath_to_healthcheck(path) abort
- if a:path =~# 'vim$'
- let name = matchstr(a:path, '\zs[^\/]*\ze\.vim$')
- let func = 'health#'.name.'#check'
- let type = 'v'
- else
- let base_path = substitute(a:path,
- \ '.*lua[\/]\(.\{-}\)[\/]health\([\/]init\)\?\.lua$',
- \ '\1', '')
- let name = substitute(base_path, '[\/]', '.', 'g')
- let func = 'require("'.name.'.health").check()'
- let type = 'l'
- endif
- return [name, func, type]
-endfunction
-
-function! s:discover_healthchecks() abort
- return s:get_healthcheck('*')
-endfunction
-
-" Returns Dictionary {name: [func, type], ..} representing healthchecks
-function! s:get_healthcheck(plugin_names) abort
- let health_list = s:get_healthcheck_list(a:plugin_names)
- let healthchecks = {}
- for c in health_list
- let normalized_name = substitute(c[0], '-', '_', 'g')
- let existent = get(healthchecks, normalized_name, [])
- " Prefer Lua over vim entries
- if existent != [] && existent[2] == 'l'
- continue
- else
- let healthchecks[normalized_name] = c
- endif
- endfor
- let output = {}
- for v in values(healthchecks)
- let output[v[0]] = v[1:]
- endfor
- try
- " vim.health is not a healthcheck, skip it
- call remove(output, 'vim')
- catch
- endtry
- return output
-endfunction
-
-" Returns list of lists [ [{name}, {func}, {type}] ] representing healthchecks
-function! s:get_healthcheck_list(plugin_names) abort
- let healthchecks = []
- let plugin_names = type('') == type(a:plugin_names)
- \ ? split(a:plugin_names, ' ', v:false)
- \ : a:plugin_names
- for p in plugin_names
- " support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
- let p = substitute(p, '\.', '/', 'g')
- let p = substitute(p, '*$', '**', 'g') " find all submodule e.g vim*
- let paths = nvim_get_runtime_file('autoload/health/'.p.'.vim', v:true)
- \ + nvim_get_runtime_file('lua/**/'.p.'/health/init.lua', v:true)
- \ + nvim_get_runtime_file('lua/**/'.p.'/health.lua', v:true)
- if len(paths) == 0
- let healthchecks += [[p, '', '']] " healthcheck not found
- else
- let healthchecks += map(uniq(sort(paths)),
- \'<SID>filepath_to_healthcheck(v:val)')
- end
- endfor
- return healthchecks
+ call s:deprecate('error')
endfunction
diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim
deleted file mode 100644
index d104bcfd67..0000000000
--- a/runtime/autoload/health/provider.vim
+++ /dev/null
@@ -1,771 +0,0 @@
-let s:shell_error = 0
-
-function! s:is_bad_response(s) abort
- return a:s =~? '\v(^unable)|(^error)|(^outdated)'
-endfunction
-
-function! s:trim(s) abort
- return substitute(a:s, '^\_s*\|\_s*$', '', 'g')
-endfunction
-
-" Convert '\' to '/'. Collapse '//' and '/./'.
-function! s:normalize_path(s) abort
- return substitute(substitute(a:s, '\', '/', 'g'), '/\./\|/\+', '/', 'g')
-endfunction
-
-" Returns TRUE if `cmd` exits with success, else FALSE.
-function! s:cmd_ok(cmd) abort
- call system(a:cmd)
- return v:shell_error == 0
-endfunction
-
-" Simple version comparison.
-function! s:version_cmp(a, b) abort
- let a = split(a:a, '\.', 0)
- let b = split(a:b, '\.', 0)
-
- for i in range(len(a))
- if str2nr(a[i]) > str2nr(b[i])
- return 1
- elseif str2nr(a[i]) < str2nr(b[i])
- return -1
- endif
- endfor
-
- return 0
-endfunction
-
-" Handler for s:system() function.
-function! s:system_handler(jobid, data, event) dict abort
- if a:event ==# 'stderr'
- if self.add_stderr_to_output
- let self.output .= join(a:data, '')
- else
- let self.stderr .= join(a:data, '')
- endif
- elseif a:event ==# 'stdout'
- let self.output .= join(a:data, '')
- elseif a:event ==# 'exit'
- let s:shell_error = a:data
- endif
-endfunction
-
-" Attempts to construct a shell command from an args list.
-" Only for display, to help users debug a failed command.
-function! s:shellify(cmd) abort
- if type(a:cmd) != type([])
- return a:cmd
- endif
- return join(map(copy(a:cmd),
- \'v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val'), ' ')
-endfunction
-
-" Run a system command and timeout after 30 seconds.
-function! s:system(cmd, ...) abort
- let stdin = a:0 ? a:1 : ''
- let ignore_error = a:0 > 2 ? a:3 : 0
- let opts = {
- \ 'add_stderr_to_output': a:0 > 1 ? a:2 : 0,
- \ 'output': '',
- \ 'stderr': '',
- \ 'on_stdout': function('s:system_handler'),
- \ 'on_stderr': function('s:system_handler'),
- \ 'on_exit': function('s:system_handler'),
- \ }
- let jobid = jobstart(a:cmd, opts)
-
- if jobid < 1
- call health#report_error(printf('Command error (job=%d): `%s` (in %s)',
- \ jobid, s:shellify(a:cmd), string(getcwd())))
- let s:shell_error = 1
- return opts.output
- endif
-
- if !empty(stdin)
- call jobsend(jobid, stdin)
- endif
-
- let res = jobwait([jobid], 30000)
- if res[0] == -1
- call health#report_error(printf('Command timed out: %s', s:shellify(a:cmd)))
- call jobstop(jobid)
- elseif s:shell_error != 0 && !ignore_error
- let emsg = printf("Command error (job=%d, exit code %d): `%s` (in %s)",
- \ jobid, s:shell_error, s:shellify(a:cmd), string(getcwd()))
- if !empty(opts.output)
- let emsg .= "\noutput: " . opts.output
- end
- if !empty(opts.stderr)
- let emsg .= "\nstderr: " . opts.stderr
- end
- call health#report_error(emsg)
- endif
-
- return opts.output
-endfunction
-
-function! s:systemlist(cmd, ...) abort
- let stdout = split(s:system(a:cmd, a:0 ? a:1 : ''), "\n")
- if a:0 > 1 && !empty(a:2)
- return filter(stdout, '!empty(v:val)')
- endif
- return stdout
-endfunction
-
-" Fetch the contents of a URL.
-function! s:download(url) abort
- let has_curl = executable('curl')
- if has_curl && system(['curl', '-V']) =~# 'Protocols:.*https'
- let rv = s:system(['curl', '-sL', a:url], '', 1, 1)
- return s:shell_error ? 'curl error with '.a:url.': '.s:shell_error : rv
- elseif executable('python')
- let script = "
- \try:\n
- \ from urllib.request import urlopen\n
- \except ImportError:\n
- \ from urllib2 import urlopen\n
- \\n
- \response = urlopen('".a:url."')\n
- \print(response.read().decode('utf8'))\n
- \"
- let rv = s:system(['python', '-c', script])
- return empty(rv) && s:shell_error
- \ ? 'python urllib.request error: '.s:shell_error
- \ : rv
- endif
- return 'missing `curl` '
- \ .(has_curl ? '(with HTTPS support) ' : '')
- \ .'and `python`, cannot make web request'
-endfunction
-
-" Check for clipboard tools.
-function! s:check_clipboard() abort
- call health#report_start('Clipboard (optional)')
-
- if !empty($TMUX) && executable('tmux') && executable('pbpaste') && !s:cmd_ok('pbpaste')
- let tmux_version = matchstr(system('tmux -V'), '\d\+\.\d\+')
- call health#report_error('pbcopy does not work with tmux version: '.tmux_version,
- \ ['Install tmux 2.6+. https://superuser.com/q/231130',
- \ 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233'])
- endif
-
- let clipboard_tool = provider#clipboard#Executable()
- if exists('g:clipboard') && empty(clipboard_tool)
- call health#report_error(
- \ provider#clipboard#Error(),
- \ ["Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."])
- elseif empty(clipboard_tool)
- call health#report_warn(
- \ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.',
- \ [':help clipboard'])
- else
- call health#report_ok('Clipboard tool found: '. clipboard_tool)
- endif
-endfunction
-
-" Get the latest Nvim Python client (pynvim) version from PyPI.
-function! s:latest_pypi_version() abort
- let pypi_version = 'unable to get pypi response'
- let pypi_response = s:download('https://pypi.python.org/pypi/pynvim/json')
- if !empty(pypi_response)
- try
- let pypi_data = json_decode(pypi_response)
- catch /E474/
- return 'error: '.pypi_response
- endtry
- let pypi_version = get(get(pypi_data, 'info', {}), 'version', 'unable to parse')
- endif
- return pypi_version
-endfunction
-
-" Get version information using the specified interpreter. The interpreter is
-" used directly in case breaking changes were introduced since the last time
-" Nvim's Python client was updated.
-"
-" Returns: [
-" {python executable version},
-" {current nvim version},
-" {current pypi nvim status},
-" {installed version status}
-" ]
-function! s:version_info(python) abort
- let pypi_version = s:latest_pypi_version()
- let python_version = s:trim(s:system([
- \ a:python,
- \ '-c',
- \ 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))',
- \ ]))
-
- if empty(python_version)
- let python_version = 'unable to parse '.a:python.' response'
- endif
-
- let nvim_path = s:trim(s:system([
- \ a:python, '-c',
- \ 'import sys; ' .
- \ 'sys.path = [p for p in sys.path if p != ""]; ' .
- \ 'import neovim; print(neovim.__file__)']))
- if s:shell_error || empty(nvim_path)
- return [python_version, 'unable to load neovim Python module', pypi_version,
- \ nvim_path]
- endif
-
- " Assuming that multiple versions of a package are installed, sort them
- " numerically in descending order.
- function! s:compare(metapath1, metapath2) abort
- let a = matchstr(fnamemodify(a:metapath1, ':p:h:t'), '[0-9.]\+')
- let b = matchstr(fnamemodify(a:metapath2, ':p:h:t'), '[0-9.]\+')
- return a == b ? 0 : a > b ? 1 : -1
- endfunction
-
- " Try to get neovim.VERSION (added in 0.1.11dev).
- let nvim_version = s:system([a:python, '-c',
- \ 'from neovim import VERSION as v; '.
- \ 'print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))'],
- \ '', 1, 1)
- if empty(nvim_version)
- let nvim_version = 'unable to find pynvim module version'
- let base = fnamemodify(nvim_path, ':h')
- let metas = glob(base.'-*/METADATA', 1, 1)
- \ + glob(base.'-*/PKG-INFO', 1, 1)
- \ + glob(base.'.egg-info/PKG-INFO', 1, 1)
- let metas = sort(metas, 's:compare')
-
- if !empty(metas)
- for meta_line in readfile(metas[0])
- if meta_line =~# '^Version:'
- let nvim_version = matchstr(meta_line, '^Version: \zs\S\+')
- break
- endif
- endfor
- endif
- endif
-
- let nvim_path_base = fnamemodify(nvim_path, ':~:h')
- let version_status = 'unknown; '.nvim_path_base
- if !s:is_bad_response(nvim_version) && !s:is_bad_response(pypi_version)
- if s:version_cmp(nvim_version, pypi_version) == -1
- let version_status = 'outdated; from '.nvim_path_base
- else
- let version_status = 'up to date'
- endif
- endif
-
- return [python_version, nvim_version, pypi_version, version_status]
-endfunction
-
-" Check the Python interpreter's usability.
-function! s:check_bin(bin) abort
- if !filereadable(a:bin) && (!has('win32') || !filereadable(a:bin.'.exe'))
- call health#report_error(printf('"%s" was not found.', a:bin))
- return 0
- elseif executable(a:bin) != 1
- call health#report_error(printf('"%s" is not executable.', a:bin))
- return 0
- endif
- return 1
-endfunction
-
-" Check "loaded" var for given a:provider.
-" Returns 1 if the caller should return (skip checks).
-function! s:disabled_via_loaded_var(provider) abort
- let loaded_var = 'g:loaded_'.a:provider.'_provider'
- if exists(loaded_var) && !exists('*provider#'.a:provider.'#Call')
- let v = eval(loaded_var)
- if 0 is v
- call health#report_info('Disabled ('.loaded_var.'='.v.').')
- return 1
- else
- call health#report_info('Disabled ('.loaded_var.'='.v.'). This might be due to some previous error.')
- endif
- endif
- return 0
-endfunction
-
-function! s:check_python() abort
- call health#report_start('Python 3 provider (optional)')
-
- let pyname = 'python3'
- let python_exe = ''
- let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : ''
- let host_prog_var = pyname.'_host_prog'
- let python_multiple = []
-
- if s:disabled_via_loaded_var(pyname)
- return
- endif
-
- let [pyenv, pyenv_root] = s:check_for_pyenv()
-
- if exists('g:'.host_prog_var)
- call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var)))
- endif
-
- let [pyname, pythonx_warnings] = provider#pythonx#Detect(3)
-
- if empty(pyname)
- call health#report_warn('No Python executable found that can `import neovim`. '
- \ . 'Using the first available executable for diagnostics.')
- elseif exists('g:'.host_prog_var)
- let python_exe = pyname
- endif
-
- " No Python executable could `import neovim`, or host_prog_var was used.
- if !empty(pythonx_warnings)
- call health#report_warn(pythonx_warnings, ['See :help provider-python for more information.',
- \ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim'])
-
- elseif !empty(pyname) && empty(python_exe)
- if !exists('g:'.host_prog_var)
- call health#report_info(printf('`g:%s` is not set. Searching for '
- \ . '%s in the environment.', host_prog_var, pyname))
- endif
-
- if !empty(pyenv)
- let python_exe = s:trim(s:system([pyenv, 'which', pyname], '', 1))
-
- if empty(python_exe)
- call health#report_warn(printf('pyenv could not find %s.', pyname))
- endif
- endif
-
- if empty(python_exe)
- let python_exe = exepath(pyname)
-
- if exists('$PATH')
- for path in split($PATH, has('win32') ? ';' : ':')
- let path_bin = s:normalize_path(path.'/'.pyname)
- if path_bin != s:normalize_path(python_exe)
- \ && index(python_multiple, path_bin) == -1
- \ && executable(path_bin)
- call add(python_multiple, path_bin)
- endif
- endfor
-
- if len(python_multiple)
- " This is worth noting since the user may install something
- " that changes $PATH, like homebrew.
- call health#report_info(printf('Multiple %s executables found. '
- \ . 'Set `g:%s` to avoid surprises.', pyname, host_prog_var))
- endif
-
- if python_exe =~# '\<shims\>'
- call health#report_warn(printf('`%s` appears to be a pyenv shim.', python_exe), [
- \ '`pyenv` is not in $PATH, your pyenv installation is broken. '
- \ .'Set `g:'.host_prog_var.'` to avoid surprises.',
- \ ])
- endif
- endif
- endif
- endif
-
- if !empty(python_exe) && !exists('g:'.host_prog_var)
- if empty(venv) && !empty(pyenv)
- \ && !empty(pyenv_root) && resolve(python_exe) !~# '^'.pyenv_root.'/'
- call health#report_warn('pyenv is not set up optimally.', [
- \ printf('Create a virtualenv specifically '
- \ . 'for Nvim using pyenv, and set `g:%s`. This will avoid '
- \ . 'the need to install the pynvim module in each '
- \ . 'version/virtualenv.', host_prog_var)
- \ ])
- elseif !empty(venv)
- if !empty(pyenv_root)
- let venv_root = pyenv_root
- else
- let venv_root = fnamemodify(venv, ':h')
- endif
-
- if resolve(python_exe) !~# '^'.venv_root.'/'
- call health#report_warn('Your virtualenv is not set up optimally.', [
- \ printf('Create a virtualenv specifically '
- \ . 'for Nvim and use `g:%s`. This will avoid '
- \ . 'the need to install the pynvim module in each '
- \ . 'virtualenv.', host_prog_var)
- \ ])
- endif
- endif
- endif
-
- if empty(python_exe) && !empty(pyname)
- " An error message should have already printed.
- call health#report_error(printf('`%s` was not found.', pyname))
- elseif !empty(python_exe) && !s:check_bin(python_exe)
- let python_exe = ''
- endif
-
- " Diagnostic output
- call health#report_info('Executable: ' . (empty(python_exe) ? 'Not found' : python_exe))
- if len(python_multiple)
- for path_bin in python_multiple
- call health#report_info('Other python executable: ' . path_bin)
- endfor
- endif
-
- if empty(python_exe)
- " No Python executable can import 'neovim'. Check if any Python executable
- " can import 'pynvim'. If so, that Python failed to import 'neovim' as
- " well, which is most probably due to a failed pip upgrade:
- " https://github.com/neovim/neovim/wiki/Following-HEAD#20181118
- let [pynvim_exe, errors] = provider#pythonx#DetectByModule('pynvim', 3)
- if !empty(pynvim_exe)
- call health#report_error(
- \ 'Detected pip upgrade failure: Python executable can import "pynvim" but '
- \ . 'not "neovim": '. pynvim_exe,
- \ "Use that Python version to reinstall \"pynvim\" and optionally \"neovim\".\n"
- \ . pynvim_exe ." -m pip uninstall pynvim neovim\n"
- \ . pynvim_exe ." -m pip install pynvim\n"
- \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software")
- endif
- else
- let [majorpyversion, current, latest, status] = s:version_info(python_exe)
-
- if 3 != str2nr(majorpyversion)
- call health#report_warn('Unexpected Python version.' .
- \ ' This could lead to confusing error messages.')
- endif
-
- call health#report_info('Python version: ' . majorpyversion)
-
- if s:is_bad_response(status)
- call health#report_info(printf('pynvim version: %s (%s)', current, status))
- else
- call health#report_info(printf('pynvim version: %s', current))
- endif
-
- if s:is_bad_response(current)
- call health#report_error(
- \ "pynvim is not installed.\nError: ".current,
- \ ['Run in shell: '. python_exe .' -m pip install pynvim'])
- endif
-
- if s:is_bad_response(latest)
- call health#report_warn('Could not contact PyPI to get latest version.')
- call health#report_error('HTTP request failed: '.latest)
- elseif s:is_bad_response(status)
- call health#report_warn(printf('Latest pynvim is NOT installed: %s', latest))
- elseif !s:is_bad_response(current)
- call health#report_ok(printf('Latest pynvim is installed.'))
- endif
- endif
-endfunction
-
-" Check if pyenv is available and a valid pyenv root can be found, then return
-" their respective paths. If either of those is invalid, return two empty
-" strings, effectivly ignoring pyenv.
-function! s:check_for_pyenv() abort
- let pyenv_path = resolve(exepath('pyenv'))
-
- if empty(pyenv_path)
- return ['', '']
- endif
-
- call health#report_info('pyenv: Path: '. pyenv_path)
-
- let pyenv_root = exists('$PYENV_ROOT') ? resolve($PYENV_ROOT) : ''
-
- if empty(pyenv_root)
- let pyenv_root = s:trim(s:system([pyenv_path, 'root']))
- call health#report_info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.')
- endif
-
- if !isdirectory(pyenv_root)
- call health#report_warn(
- \ printf('pyenv: Root does not exist: %s. '
- \ . 'Ignoring pyenv for all following checks.', pyenv_root))
- return ['', '']
- endif
-
- call health#report_info('pyenv: Root: '.pyenv_root)
-
- return [pyenv_path, pyenv_root]
-endfunction
-
-" Resolves Python executable path by invoking and checking `sys.executable`.
-function! s:python_exepath(invocation) abort
- return s:normalize_path(system(fnameescape(a:invocation)
- \ . ' -c "import sys; sys.stdout.write(sys.executable)"'))
-endfunction
-
-" Checks that $VIRTUAL_ENV Python executables are found at front of $PATH in
-" Nvim and subshells.
-function! s:check_virtualenv() abort
- call health#report_start('Python virtualenv')
- if !exists('$VIRTUAL_ENV')
- call health#report_ok('no $VIRTUAL_ENV')
- return
- endif
- let errors = []
- " Keep hints as dict keys in order to discard duplicates.
- let hints = {}
- " The virtualenv should contain some Python executables, and those
- " executables should be first both on Nvim's $PATH and the $PATH of
- " subshells launched from Nvim.
- let bin_dir = has('win32') ? '/Scripts' : '/bin'
- let venv_bins = glob($VIRTUAL_ENV . bin_dir . '/python*', v:true, v:true)
- " XXX: Remove irrelevant executables found in bin/.
- let venv_bins = filter(venv_bins, 'v:val !~# "python-config"')
- if len(venv_bins)
- for venv_bin in venv_bins
- let venv_bin = s:normalize_path(venv_bin)
- let py_bin_basename = fnamemodify(venv_bin, ':t')
- let nvim_py_bin = s:python_exepath(exepath(py_bin_basename))
- let subshell_py_bin = s:python_exepath(py_bin_basename)
- if venv_bin !=# nvim_py_bin
- call add(errors, '$PATH yields this '.py_bin_basename.' executable: '.nvim_py_bin)
- let hint = '$PATH ambiguities arise if the virtualenv is not '
- \.'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, '
- \.'check that invoking Python from the command line launches the correct one, '
- \.'then relaunch Nvim.'
- let hints[hint] = v:true
- endif
- if venv_bin !=# subshell_py_bin
- call add(errors, '$PATH in subshells yields this '
- \.py_bin_basename . ' executable: '.subshell_py_bin)
- let hint = '$PATH ambiguities in subshells typically are '
- \.'caused by your shell config overriding the $PATH previously set by the '
- \.'virtualenv. Either prevent them from doing so, or use this workaround: '
- \.'https://vi.stackexchange.com/a/34996'
- let hints[hint] = v:true
- endif
- endfor
- else
- call add(errors, 'no Python executables found in the virtualenv '.bin_dir.' directory.')
- endif
-
- let msg = '$VIRTUAL_ENV is set to: '.$VIRTUAL_ENV
- if len(errors)
- if len(venv_bins)
- let msg .= "\nAnd its ".bin_dir.' directory contains: '
- \.join(map(venv_bins, "fnamemodify(v:val, ':t')"), ', ')
- endif
- let conj = "\nBut "
- for error in errors
- let msg .= conj.error
- let conj = "\nAnd "
- endfor
- let msg .= "\nSo invoking Python may lead to unexpected results."
- call health#report_warn(msg, keys(hints))
- else
- call health#report_info(msg)
- call health#report_info('Python version: '
- \.system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"'))
- call health#report_ok('$VIRTUAL_ENV provides :!python.')
- endif
-endfunction
-
-function! s:check_ruby() abort
- call health#report_start('Ruby provider (optional)')
-
- if s:disabled_via_loaded_var('ruby')
- return
- endif
-
- if !executable('ruby') || !executable('gem')
- call health#report_warn(
- \ '`ruby` and `gem` must be in $PATH.',
- \ ['Install Ruby and verify that `ruby` and `gem` commands work.'])
- return
- endif
- call health#report_info('Ruby: '. s:system(['ruby', '-v']))
-
- let [host, err] = provider#ruby#Detect()
- if empty(host)
- call health#report_warn('`neovim-ruby-host` not found.',
- \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.',
- \ 'Run `gem environment` to ensure the gem bin directory is in $PATH.',
- \ 'If you are using rvm/rbenv/chruby, try "rehashing".',
- \ 'See :help g:ruby_host_prog for non-standard gem installations.',
- \ 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim'])
- return
- endif
- call health#report_info('Host: '. host)
-
- let latest_gem_cmd = has('win32') ? 'cmd /c gem list -ra "^^neovim$"' : 'gem list -ra ^neovim$'
- let latest_gem = s:system(split(latest_gem_cmd))
- if s:shell_error || empty(latest_gem)
- call health#report_error('Failed to run: '. latest_gem_cmd,
- \ ["Make sure you're connected to the internet.",
- \ 'Are you behind a firewall or proxy?'])
- return
- endif
- let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found')
-
- 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: '. join(current_gem_cmd),
- \ ['Report this issue with the output of: ', join(current_gem_cmd)])
- return
- endif
-
- if s:version_cmp(current_gem, latest_gem) == -1
- call health#report_warn(
- \ printf('Gem "neovim" is out-of-date. Installed: %s, latest: %s',
- \ current_gem, latest_gem),
- \ ['Run in shell: gem update neovim'])
- else
- call health#report_ok('Latest "neovim" gem is installed: '. current_gem)
- endif
-endfunction
-
-function! s:check_node() abort
- call health#report_start('Node.js provider (optional)')
-
- if s:disabled_via_loaded_var('node')
- return
- endif
-
- if !executable('node') || (!executable('npm') && !executable('yarn') && !executable('pnpm'))
- call health#report_warn(
- \ '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.',
- \ ['Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.'])
- return
- endif
- 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)
- " Skip further checks, they are nonsense if nodejs is too old.
- return
- endif
- if !provider#node#can_inspect()
- call health#report_warn('node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.')
- endif
-
- let [host, err] = provider#node#Detect()
- if empty(host)
- call health#report_warn('Missing "neovim" npm (or yarn, pnpm) package.',
- \ ['Run in shell: npm install -g neovim',
- \ 'Run in shell (if you use yarn): yarn global add neovim',
- \ 'Run in shell (if you use pnpm): pnpm install -g neovim',
- \ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim'])
- return
- endif
- call health#report_info('Nvim node.js host: '. host)
-
- let manager = 'npm'
- if executable('yarn')
- let manager = 'yarn'
- elseif executable('pnpm')
- let manager = 'pnpm'
- endif
-
- let latest_npm_cmd = has('win32') ?
- \ 'cmd /c '. manager .' info neovim --json' :
- \ manager .' info neovim --json'
- let latest_npm = s:system(split(latest_npm_cmd))
- if s:shell_error || empty(latest_npm)
- call health#report_error('Failed to run: '. latest_npm_cmd,
- \ ["Make sure you're connected to the internet.",
- \ 'Are you behind a firewall or proxy?'])
- return
- endif
- try
- let pkg_data = json_decode(latest_npm)
- catch /E474/
- return 'error: '.latest_npm
- endtry
- let latest_npm = get(get(pkg_data, 'dist-tags', {}), 'latest', 'unable to parse')
-
- 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: '. join(current_npm_cmd),
- \ ['Report this issue with the output of: ', join(current_npm_cmd)])
- return
- endif
-
- if s:version_cmp(current_npm, latest_npm) == -1
- call health#report_warn(
- \ printf('Package "neovim" is out-of-date. Installed: %s, latest: %s',
- \ current_npm, latest_npm),
- \ ['Run in shell: npm install -g neovim',
- \ 'Run in shell (if you use yarn): yarn global add neovim',
- \ 'Run in shell (if you use pnpm): pnpm install -g neovim'])
- else
- call health#report_ok('Latest "neovim" npm/yarn/pnpm package is installed: '. current_npm)
- endif
-endfunction
-
-function! s:check_perl() abort
- call health#report_start('Perl provider (optional)')
-
- if s:disabled_via_loaded_var('perl')
- return
- endif
-
- let [perl_exec, perl_warnings] = provider#perl#Detect()
- if empty(perl_exec)
- if !empty(perl_warnings)
- call health#report_warn(perl_warnings, ['See :help provider-perl for more information.',
- \ 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim'])
- else
- call health#report_warn('No usable perl executable found')
- endif
- return
- endif
-
- call health#report_info('perl executable: '. perl_exec)
-
- " we cannot use cpanm that is on the path, as it may not be for the perl
- " set with g:perl_host_prog
- call s:system([perl_exec, '-W', '-MApp::cpanminus', '-e', ''])
- if s:shell_error
- return [perl_exec, '"App::cpanminus" module is not installed']
- endif
-
- let latest_cpan_cmd = [perl_exec,
- \ '-MApp::cpanminus::fatscript', '-e',
- \ 'my $app = App::cpanminus::script->new;
- \ $app->parse_options ("--info", "-q", "Neovim::Ext");
- \ exit $app->doit']
-
- let latest_cpan = s:system(latest_cpan_cmd)
- if s:shell_error || empty(latest_cpan)
- call health#report_error('Failed to run: '. join(latest_cpan_cmd, " "),
- \ ["Make sure you're connected to the internet.",
- \ 'Are you behind a firewall or proxy?'])
- return
- elseif latest_cpan[0] ==# '!'
- let cpanm_errs = split(latest_cpan, '!')
- if cpanm_errs[0] =~# "Can't write to "
- call health#report_warn(cpanm_errs[0], cpanm_errs[1:-2])
- " Last line is the package info
- let latest_cpan = cpanm_errs[-1]
- else
- call health#report_error('Unknown warning from command: ' . latest_cpan_cmd, cpanm_errs)
- return
- endif
- endif
- let latest_cpan = matchstr(latest_cpan, '\(\.\?\d\)\+')
- if empty(latest_cpan)
- call health#report_error('Cannot parse version number from cpanm output: ' . latest_cpan)
- return
- endif
-
- 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: '. join(current_cpan_cmd),
- \ ['Report this issue with the output of: ', join(current_cpan_cmd)])
- return
- endif
-
- if s:version_cmp(current_cpan, latest_cpan) == -1
- call health#report_warn(
- \ printf('Module "Neovim::Ext" is out-of-date. Installed: %s, latest: %s',
- \ current_cpan, latest_cpan),
- \ ['Run in shell: cpanm -n Neovim::Ext'])
- else
- call health#report_ok('Latest "Neovim::Ext" cpan module is installed: '. current_cpan)
- endif
-endfunction
-
-function! health#provider#check() abort
- call s:check_clipboard()
- call s:check_python()
- call s:check_virtualenv()
- call s:check_ruby()
- call s:check_node()
- call s:check_perl()
-endfunction
diff --git a/runtime/autoload/javascriptcomplete.vim b/runtime/autoload/javascriptcomplete.vim
index 29b6b16254..3ec3b50490 100644
--- a/runtime/autoload/javascriptcomplete.vim
+++ b/runtime/autoload/javascriptcomplete.vim
@@ -156,8 +156,8 @@ function! javascriptcomplete#CompleteJS(findstart, base)
\ 'text', 'vLink']
let bodys = bodyprop
" Document - document.
- let docuprop = ['anchors', 'body', 'characterSet', 'doctype',
- \ 'documentElement', 'documentURI', 'embeds', 'fonts', 'forms',
+ let docuprop = ['anchors', 'applets', 'body', 'characterSet', 'childNodes',
+ \ 'doctype', 'documentElement', 'documentURI', 'embeds', 'fonts', 'forms',
\ 'head', 'hidden', 'images', 'implementation', 'lastStyleSheetSet',
\ 'links', 'plugins', 'preferredStyleSheetSet', 'scripts',
\ 'scrollingElement', 'selectedStyleSheetSet', 'styleSheetSets',
@@ -171,7 +171,7 @@ function! javascriptcomplete#CompleteJS(findstart, base)
\ 'createEvent', 'createExpression', 'createNSResolver',
\ 'createNodeIterator', 'createProcessingInstruction', 'createRange',
\ 'createTextNode', 'createTouchList', 'createTreeWalker',
- \ 'enableStyleSheetsForSet', 'evaluate', 'focus', 'getElementById',
+ \ 'enableStyleSheetsForSet', 'evaluate', 'focus',
\ 'getElementById', 'getElementsByClassName', 'getElementsByName',
\ 'getElementsByTagName', 'getElementsByTagNameNS',
\ 'hasStorageAccess', 'importNode', 'onClick', 'onDblClick',
diff --git a/runtime/autoload/msgpack.vim b/runtime/autoload/msgpack.vim
index 7f98a5b230..18dcd1e6a6 100644
--- a/runtime/autoload/msgpack.vim
+++ b/runtime/autoload/msgpack.vim
@@ -101,8 +101,8 @@ function s:msgpack_init_python() abort
" @return Formatted timestamp.
"
" @warning Without +python or +python3 this function does not work correctly.
- " The VimL code contains “reference” implementation which does not
- " really work because of precision loss.
+ " The Vimscript code contains “reference” implementation which does
+ " not really work because of precision loss.
function s:msgpack_dict_strftime(format, timestamp)
return msgpack#strftime(a:format, +msgpack#int_dict_to_str(a:timestamp))
endfunction
@@ -541,8 +541,8 @@ let s:MSGPACK_SPECIAL_OBJECTS = {
\}
""
-" Convert msgpack object dumped by msgpack#string() to a VimL object suitable
-" for msgpackdump().
+" Convert msgpack object dumped by msgpack#string() to a Vimscript object
+" suitable for msgpackdump().
"
" @param[in] s String to evaluate.
" @param[in] special_objs Additional special objects, in the same format as
diff --git a/runtime/autoload/netrw.vim b/runtime/autoload/netrw.vim
index 2fcf0b32c7..b8092ebeeb 100644
--- a/runtime/autoload/netrw.vim
+++ b/runtime/autoload/netrw.vim
@@ -1,7 +1,10 @@
" netrw.vim: Handles file transfer and remote directory listing across
" AUTOLOAD SECTION
-" Date: Aug 16, 2021
-" Version: 171
+" Date: May 03, 2023
+" Version: 173a
+" Last Change:
+" 2023 Nov 21 by Vim Project: ignore wildignore when expanding $COMSPEC (v173a)
+" 2023 Nov 22 by Vim Project: fix handling of very long filename on longlist style (v173a)
" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
" GetLatestVimScripts: 1075 1 :AutoInstall: netrw.vim
" Copyright: Copyright (C) 2016 Charles E. Campbell {{{1
@@ -43,7 +46,7 @@ if exists("s:needspatches")
endfor
endif
-let g:loaded_netrw = "v171"
+let g:loaded_netrw = "v173"
if !exists("s:NOTE")
let s:NOTE = 0
let s:WARNING = 1
@@ -208,14 +211,11 @@ let g:netrw_localcopycmdopt = ""
let g:netrw_localcopydircmdopt = ""
let g:netrw_localmkdiropt = ""
let g:netrw_localmovecmdopt = ""
-let g:netrw_localrmdiropt = ""
" ---------------------------------------------------------------------
" Default values for netrw's global protocol variables {{{2
-if (v:version > 802 || (v:version == 802 && has("patch486"))) && has("balloon_eval") && !exists("s:initbeval") && !exists("g:netrw_nobeval") && has("syntax") && exists("g:syntax_on") && has("mouse")
- call s:NetrwInit("g:netrw_use_errorwindow",2)
-else
- call s:NetrwInit("g:netrw_use_errorwindow",1)
+if !exists("g:netrw_use_errorwindow")
+ let g:netrw_use_errorwindow = 0
endif
if !exists("g:netrw_dav_cmd")
@@ -401,7 +401,7 @@ if !exists("g:netrw_localcopycmd")
if g:netrw_cygwin
let g:netrw_localcopycmd= "cp"
else
- let g:netrw_localcopycmd = expand("$COMSPEC")
+ let g:netrw_localcopycmd = expand("$COMSPEC", v:true)
let g:netrw_localcopycmdopt= " /c copy"
endif
elseif has("unix") || has("macunix")
@@ -416,7 +416,7 @@ if !exists("g:netrw_localcopydircmd")
let g:netrw_localcopydircmd = "cp"
let g:netrw_localcopydircmdopt= " -R"
else
- let g:netrw_localcopydircmd = expand("$COMSPEC")
+ let g:netrw_localcopydircmd = expand("$COMSPEC", v:true)
let g:netrw_localcopydircmdopt= " /c xcopy /e /c /h /i /k"
endif
elseif has("unix")
@@ -437,7 +437,7 @@ if has("win32") || has("win95") || has("win64") || has("win16")
if g:netrw_cygwin
call s:NetrwInit("g:netrw_localmkdir","mkdir")
else
- let g:netrw_localmkdir = expand("$COMSPEC")
+ let g:netrw_localmkdir = expand("$COMSPEC", v:true)
let g:netrw_localmkdiropt= " /c mkdir"
endif
else
@@ -453,7 +453,7 @@ if !exists("g:netrw_localmovecmd")
if g:netrw_cygwin
let g:netrw_localmovecmd= "mv"
else
- let g:netrw_localmovecmd = expand("$COMSPEC")
+ let g:netrw_localmovecmd = expand("$COMSPEC", v:true)
let g:netrw_localmovecmdopt= " /c move"
endif
elseif has("unix") || has("macunix")
@@ -1137,7 +1137,6 @@ fun! netrw#Explore(indx,dosplit,style,...)
2match none
if exists("s:explore_match") | unlet s:explore_match | endif
if exists("s:explore_prvdir") | unlet s:explore_prvdir | endif
- echo " "
" call Decho("cleared explore match list",'~'.expand("<slnum>"))
endif
@@ -1151,6 +1150,11 @@ endfun
" ---------------------------------------------------------------------
" netrw#Lexplore: toggle Explorer window, keeping it on the left of the current tab {{{2
+" Uses g:netrw_chgwin : specifies the window where Lexplore files are to be opened
+" t:netrw_lexposn : winsaveview() output (used on Lexplore window)
+" t:netrw_lexbufnr: the buffer number of the Lexplore buffer (internal to this function)
+" s:lexplore_win : window number of Lexplore window (serves to indicate which window is a Lexplore window)
+" w:lexplore_buf : buffer number of Lexplore window (serves to indicate which window is a Lexplore window)
fun! netrw#Lexplore(count,rightside,...)
" call Dfunc("netrw#Lexplore(count=".a:count." rightside=".a:rightside.",...) a:0=".a:0." ft=".&ft)
let curwin= winnr()
@@ -1167,6 +1171,8 @@ fun! netrw#Lexplore(count,rightside,...)
" call Decho("exe Explore ".fnameescape(a:1),'~'.expand("<slnum>"))
exe "Explore ".fnameescape(a1)
exe curwin."wincmd w"
+ let s:lexplore_win= curwin
+ let w:lexplore_buf= bufnr("%")
if exists("t:netrw_lexposn")
" call Decho("forgetting t:netrw_lexposn",'~'.expand("<slnum>"))
unlet t:netrw_lexposn
@@ -1241,7 +1247,7 @@ fun! netrw#Lexplore(count,rightside,...)
let t:netrw_lexbufnr = bufnr("%")
" done to prevent build-up of hidden buffers due to quitting and re-invocation of :Lexplore.
" Since the intended use of :Lexplore is to have an always-present explorer window, the extra
- " effort to mis-use :Lex is warranted.
+ " effort to prevent mis-use of :Lex is warranted.
set bh=wipe
" call Decho("let t:netrw_lexbufnr=".t:netrw_lexbufnr)
" call Decho("t:netrw_lexposn".(exists("t:netrw_lexposn")? string(t:netrw_lexposn) : " n/a"))
@@ -1645,7 +1651,7 @@ fun! s:NetrwOptionsSave(vt)
endif
let {a:vt}netrw_fokeep = &l:fo " formatoptions
let {a:vt}netrw_gdkeep = &l:gd " gdefault
- let {a:vt}netrw_gokeep = &l:go " guioptions
+ let {a:vt}netrw_gokeep = &go " guioptions
let {a:vt}netrw_hidkeep = &l:hidden
let {a:vt}netrw_imkeep = &l:im
let {a:vt}netrw_iskkeep = &l:isk
@@ -1711,8 +1717,9 @@ fun! s:NetrwOptionsSafe(islocal)
if &cpo =~ 'a' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'a','','g')) | endif
if &cpo =~ 'A' | call s:NetrwSetSafeSetting("&cpo",substitute(&cpo,'A','','g')) | endif
setl fo=nroql2
- " call s:NetrwSetSafeSetting("&go","begmr")
- if &go =~ '\ca' | call s:NetrwSetSafeSetting("&go",substitute(&go,'\ca','','g')) | endif
+ if &go =~ 'a' | set go-=a | endif
+ if &go =~ 'A' | set go-=A | endif
+ if &go =~ 'P' | set go-=P | endif
call s:NetrwSetSafeSetting("&l:hid",0)
call s:NetrwSetSafeSetting("&l:im",0)
setl isk+=@ isk+=* isk+=/
@@ -1751,11 +1758,13 @@ fun! s:NetrwOptionsRestore(vt)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>"))
if !exists("{a:vt}netrw_optionsave")
" call Decho("case ".a:vt."netrw_optionsave : doesn't exist",'~'.expand("<slnum>"))
- if !isdirectory(expand('%'))
+ if filereadable(expand("%"))
" call Decho("..doing filetype detect anyway")
- filetype detect
+ filetype detect
+" call Decho("..settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>"))
+ else
+ setl ft=netrw
endif
-" call Decho("..settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>"))
" call Decho("..ro=".&l:ro." ma=".&l:ma." mod=".&l:mod." wrap=".&l:wrap." (filename<".expand("%")."> win#".winnr()." ft<".&ft.">)",'~'.expand("<slnum>"))
" call Dret("s:NetrwOptionsRestore : ".a:vt."netrw_optionsave doesn't exist")
return
@@ -1795,7 +1804,7 @@ fun! s:NetrwOptionsRestore(vt)
" call Decho("(s:NetrwOptionsRestore) #4 lines=".&lines)
call s:NetrwRestoreSetting(a:vt."netrw_fokeep" ,"&l:fo")
call s:NetrwRestoreSetting(a:vt."netrw_gdkeep" ,"&l:gd")
- call s:NetrwRestoreSetting(a:vt."netrw_gokeep" ,"&l:go")
+ call s:NetrwRestoreSetting(a:vt."netrw_gokeep" ,"&go")
call s:NetrwRestoreSetting(a:vt."netrw_hidkeep" ,"&l:hidden")
" call Decho("(s:NetrwOptionsRestore) #5 lines=".&lines)
call s:NetrwRestoreSetting(a:vt."netrw_imkeep" ,"&l:im")
@@ -1861,11 +1870,9 @@ fun! s:NetrwOptionsRestore(vt)
" were having their filetype detect-generated settings overwritten by
" NetrwOptionRestore.
if &ft != "netrw"
- if !isdirectory(expand('%'))
-" call Decho("before: filetype detect (ft=".&ft.")",'~'.expand("<slnum>"))
- filetype detect
-" call Decho("after : filetype detect (ft=".&ft.")",'~'.expand("<slnum>"))
- endif
+" call Decho("before: filetype detect (ft=".&ft.")",'~'.expand("<slnum>"))
+ filetype detect
+" call Decho("after : filetype detect (ft=".&ft.")",'~'.expand("<slnum>"))
endif
" call Decho("(s:NetrwOptionsRestore) lines=".&lines)
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo." a:vt=".a:vt,'~'.expand("<slnum>"))
@@ -1909,7 +1916,7 @@ endfun
" Doing this means that netrw will not come up as having changed a
" setting last when it really didn't actually change it.
"
-" Used by s:NetrwOptionsRestore() to restore each netrw-senstive setting
+" Used by s:NetrwOptionsRestore() to restore each netrw-sensitive setting
" keepvars are set up by s:NetrwOptionsSave
fun! s:NetrwRestoreSetting(keepvar,setting)
""" call Dfunc("s:NetrwRestoreSetting(a:keepvar<".a:keepvar."> a:setting<".a:setting.">)")
@@ -2940,13 +2947,19 @@ fun! s:NetrwGetFile(readcmd, tfile, method)
" rename buffer back to remote filename
call s:NetrwBufRename(rfile)
+ " Jan 19, 2022: COMBAK -- bram problem with https://github.com/vim/vim/pull/9554.diff filetype
" Detect filetype of local version of remote file.
" Note that isk must not include a "/" for scripts.vim
" to process this detection correctly.
-" call Decho("detect filetype of local version of remote file",'~'.expand("<slnum>"))
- let iskkeep= &l:isk
+" call Decho("detect filetype of local version of remote file<".rfile.">",'~'.expand("<slnum>"))
+" call Decho("..did_filetype()=".did_filetype())
+" setl ft=
+" call Decho("..initial filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
+ let iskkeep= &isk
setl isk-=/
- let &l:isk= iskkeep
+ filetype detect
+" call Decho("..local filetype<".&ft."> for buf#".bufnr()."<".bufname().">")
+ let &isk= iskkeep
" call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
let line1 = 1
let line2 = line("$")
@@ -3458,6 +3471,11 @@ fun! s:NetrwBookHistHandler(chg,curdir)
echo "bookmarked the current directory"
endif
+ try
+ call s:NetrwBookHistSave()
+ catch
+ endtry
+
elseif a:chg == 1
" change to the bookmarked directory
" call Decho("(user: <".v:count."gb>) change to the bookmarked directory",'~'.expand("<slnum>"))
@@ -3602,6 +3620,11 @@ fun! s:NetrwBookHistHandler(chg,curdir)
" call Decho("g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("<slnum>"))
endif
" call Decho("resulting g:netrw_bookmarklist=".string(g:netrw_bookmarklist),'~'.expand("<slnum>"))
+
+ try
+ call s:NetrwBookHistSave()
+ catch
+ endtry
endif
call s:NetrwBookmarkMenu()
call s:NetrwTgtMenu()
@@ -4227,7 +4250,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
endif
" call Decho(" bufnum#".bufnum,'~'.expand("<slnum>"))
- " hijack the current buffer
+ " highjack the current buffer
" IF the buffer already has the desired name
" AND it is empty
let curbuf = bufname("%")
@@ -4235,7 +4258,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
let curbuf = getcwd()
endif
" call Dredir("ls!","NetrwGetFile (renamed buffer back to remote filename<".rfile."> : expand(%)<".expand("%").">)")
-" call Decho("deciding if netrw may hijack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand("<slnum>"))
+" call Decho("deciding if netrw may highjack the current buffer#".bufnr("%")."<".curbuf.">",'~'.expand("<slnum>"))
" call Decho("..dirname<".dirname."> IF dirname == bufname",'~'.expand("<slnum>"))
" call Decho("..curbuf<".curbuf.">",'~'.expand("<slnum>"))
" call Decho("..line($)=".line("$")." AND this is 1",'~'.expand("<slnum>"))
@@ -4244,7 +4267,7 @@ fun! s:NetrwGetBuffer(islocal,dirname)
" call Dret("s:NetrwGetBuffer 0<cleared buffer> : highjacking buffer#".bufnr("%"))
return 0
else " DEBUG
-" call Decho("..did NOT hijack buffer",'~'.expand("<slnum>"))
+" call Decho("..did NOT highjack buffer",'~'.expand("<slnum>"))
endif
" Aug 14, 2021: was thinking about looking for a [No Name] buffer here and using it, but that might cause problems
@@ -4280,19 +4303,25 @@ fun! s:NetrwGetBuffer(islocal,dirname)
else " Re-use the buffer
" call Decho("--re-use buffer#".bufnum." (bufnum#".bufnum.">=0 AND bufexists(".bufnum.")=".bufexists(bufnum)."!=0)",'~'.expand("<slnum>"))
+ " ignore all events
let eikeep= &ei
setl ei=all
- if getline(2) =~# '^" Netrw Directory Listing'
-" call Decho(" getline(2)<".getline(2).'> matches "Netrw Directory Listing" : using keepalt b '.bufnum,'~'.expand("<slnum>"))
- exe "sil! NetrwKeepj noswapfile keepalt b ".bufnum
+
+ if &ft == "netrw"
+" call Decho("buffer type is netrw; not using keepalt with b ".bufnum)
+ exe "sil! NetrwKeepj noswapfile b ".bufnum
+" call Dredir("ls!","one")
else
-" call Decho(" getline(2)<".getline(2).'> does not match "Netrw Directory Listing" : using b '.bufnum,'~'.expand("<slnum>"))
- exe "sil! NetrwKeepj noswapfile keepalt b ".bufnum
+" call Decho("buffer type is not netrw; using keepalt with b ".bufnum)
+ call s:NetrwEditBuf(bufnum)
+" call Dredir("ls!","two")
endif
" call Decho(" line($)=".line("$"),'~'.expand("<slnum>"))
if bufname("%") == '.'
call s:NetrwBufRename(getcwd())
endif
+
+ " restore ei
let &ei= eikeep
if line("$") <= 1 && getline(1) == ""
@@ -4943,7 +4972,7 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" the point where netrw actually edits the (local) file
" if its local only: LocalBrowseCheck() doesn't edit a file, but NetrwBrowse() will
- " no keepalt to support :e # to return to a directory listing
+ " use keepalt to support :e # to return to a directory listing
if !&mod
" if e the new file would fail due to &mod, then don't change any of the flags
let dolockout= 1
@@ -4954,12 +4983,8 @@ fun! s:NetrwBrowseChgDir(islocal,newdir,...)
" others like c-^ to return to the netrw buffer
" Apr 30, 2020: used to have e! here. That can cause loss of a modified file,
" so emit error E37 instead.
- if exists("g:netrw_altfile") && g:netrw_altfile
- exe "NetrwKeepj keepalt e ".fnameescape(dirname)
- else
- exe "NetrwKeepj e ".fnameescape(dirname)
- endif
-" call Decho("edit-a-file: after e! ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod,'~'.expand("<slnum>"))
+ call s:NetrwEditFile("e","",dirname)
+" call Decho("edit-a-file: after e ".dirname.": hidden=".&hidden." bufhidden<".&bufhidden."> mod=".&mod,'~'.expand("<slnum>"))
" COMBAK -- cuc cul related
call s:NetrwCursor(1)
if &hidden || &bufhidden == "hide"
@@ -5300,8 +5325,8 @@ fun! netrw#BrowseX(fname,remote)
" g:Netrw_corehandler is a List of function references (see :help Funcref)
" call Decho("g:Netrw_corehandler is a List",'~'.expand("<slnum>"))
for Fncref in g:Netrw_corehandler
- if type(FncRef) == 2
- call FncRef(a:fname)
+ if type(Fncref) == 2
+ call Fncref(a:fname)
endif
endfor
endif
@@ -5374,6 +5399,8 @@ fun! netrw#BrowseX(fname,remote)
else
let redir= &srr . "/dev/null"
endif
+ else
+ let redir= ""
endif
" call Decho("set up redirection: redir{".redir."} srr{".&srr."}",'~'.expand("<slnum>"))
@@ -5490,7 +5517,7 @@ fun! netrw#BrowseX(fname,remote)
" cleanup: remove temporary file,
" delete current buffer if success with handler,
" return to prior buffer (directory listing)
- " Feb 12, 2008: had to de-activiate removal of
+ " Feb 12, 2008: had to de-activate removal of
" temporary file because it wasn't getting seen.
" if remote == 1 && fname != a:fname
"" call Decho("deleting temporary file<".fname.">",'~'.expand("<slnum>"))
@@ -5663,12 +5690,39 @@ fun! s:NetrwClearExplore()
if exists("w:netrw_explore_list") |unlet w:netrw_explore_list |endif
if exists("w:netrw_explore_bufnr") |unlet w:netrw_explore_bufnr |endif
" redraw!
- echo " "
- echo " "
" call Dret("s:NetrwClearExplore")
endfun
" ---------------------------------------------------------------------
+" s:NetrwEditBuf: decides whether or not to use keepalt to edit a buffer {{{2
+fun! s:NetrwEditBuf(bufnum)
+" call Dfunc("s:NetrwEditBuf(fname<".a:bufnum.">)")
+ if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+" call Decho("exe sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum))
+ exe "sil! NetrwKeepj keepalt noswapfile b ".fnameescape(a:bufnum)
+ else
+" call Decho("exe sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum))
+ exe "sil! NetrwKeepj noswapfile b ".fnameescape(a:bufnum)
+ endif
+" call Dret("s:NetrwEditBuf")
+endfun
+
+" ---------------------------------------------------------------------
+" s:NetrwEditFile: decides whether or not to use keepalt to edit a file {{{2
+" NetrwKeepj [keepalt] <OPT> <CMD> <FILENAME>
+fun! s:NetrwEditFile(cmd,opt,fname)
+" call Dfunc("s:NetrwEditFile(cmd<".a:cmd.">,opt<".a:opt.">,fname<".a:fname.">) ft<".&ft.">")
+ if exists("g:netrw_altfile") && g:netrw_altfile && &ft == "netrw"
+" call Decho("exe NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+ exe "NetrwKeepj keepalt ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+ else
+" call Decho("exe NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname))
+ exe "NetrwKeepj ".a:opt." ".a:cmd." ".fnameescape(a:fname)
+ endif
+" call Dret("s:NetrwEditFile")
+endfun
+
+" ---------------------------------------------------------------------
" s:NetrwExploreListUniq: {{{2
fun! s:NetrwExploreListUniq(explist)
" call Dfunc("s:NetrwExploreListUniq(explist<".string(a:explist).">)")
@@ -7278,8 +7332,7 @@ fun! s:NetrwMarkFileDiff(islocal)
exe "NetrwKeepj e ".fnameescape(fname)
diffthis
elseif cnt == 2 || cnt == 3
- vsplit
- wincmd l
+ below vsplit
" call Decho("diffthis: ".fname,'~'.expand("<slnum>"))
exe "NetrwKeepj e ".fnameescape(fname)
diffthis
@@ -7756,8 +7809,16 @@ fun! s:NetrwMarkFileMove(islocal)
" call Decho("movecmd<".movecmd."> (#3 linux or cygwin)",'~'.expand("<slnum>"))
endif
for fname in s:netrwmarkfilelist_{bufnr("%")}
+ if g:netrw_keepdir
+ " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
+ let fname= b:netrw_curdir."/".fname
+ endif
if !g:netrw_cygwin && (has("win32") || has("win95") || has("win64") || has("win16"))
let fname= substitute(fname,'/','\\','g')
+ if g:netrw_keepdir
+ " Jul 19, 2022: fixing file move when g:netrw_keepdir is 1
+ let fname= b:netrw_curdir."\\".fname
+ endif
endif
" call Decho("system(".movecmd." ".s:ShellEscape(fname)." ".tgt.")",'~'.expand("<slnum>"))
let ret= system(movecmd.g:netrw_localmovecmdopt." ".s:ShellEscape(fname)." ".tgt)
@@ -8475,21 +8536,28 @@ endfun
" choice = 2 : didn't save modified file, opened window
" choice = 3 : cancel open
fun! s:NetrwPrevWinOpen(islocal)
-" call Dfunc("s:NetrwPrevWinOpen(islocal=".a:islocal.")")
+" call Dfunc("s:NetrwPrevWinOpen(islocal=".a:islocal.") win#".winnr())
let ykeep= @@
" grab a copy of the b:netrw_curdir to pass it along to newly split windows
let curdir = b:netrw_curdir
+" call Decho("COMBAK#1: mod=".&mod." win#".winnr())
" get last window number and the word currently under the cursor
let origwin = winnr()
let lastwinnr = winnr("$")
- let curword = s:NetrwGetWord()
- let choice = 0
- let s:prevwinopen= 1 " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it
+" call Decho("origwin#".origwin." lastwinnr#".lastwinnr)
+" call Decho("COMBAK#2: mod=".&mod." win#".winnr())
+ let curword = s:NetrwGetWord()
+ let choice = 0
+ let s:prevwinopen= 1 " lets s:NetrwTreeDir() know that NetrwPrevWinOpen called it (s:NetrwTreeDir() will unlet s:prevwinopen)
+" call Decho("COMBAK#3: mod=".&mod." win#".winnr())
let s:treedir = s:NetrwTreeDir(a:islocal)
+" call Decho("COMBAK#4: mod=".&mod." win#".winnr())
let curdir = s:treedir
+" call Decho("COMBAK#5: mod=".&mod." win#".winnr())
" call Decho("winnr($)#".lastwinnr." curword<".curword.">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#6: mod=".&mod." win#".winnr())
let didsplit = 0
if lastwinnr == 1
@@ -8512,11 +8580,26 @@ fun! s:NetrwPrevWinOpen(islocal)
" call Decho("did split",'~'.expand("<slnum>"))
else
+" call Decho("COMBAK#7: mod=".&mod." win#".winnr())
NetrwKeepj call s:SaveBufVars()
+" call Decho("COMBAK#8: mod=".&mod." win#".winnr())
let eikeep= &ei
+" call Decho("COMBAK#9: mod=".&mod." win#".winnr())
setl ei=all
+" call Decho("COMBAK#10: mod=".&mod." win#".winnr())
wincmd p
+" call Decho("COMBAK#11: mod=".&mod)
" call Decho("wincmd p (now in win#".winnr().") curdir<".curdir.">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#12: mod=".&mod)
+
+ if exists("s:lexplore_win") && s:lexplore_win == winnr()
+ " whoops -- user trying to open file in the Lexplore window.
+ " Use Lexplore's opening-file window instead.
+" call Decho("whoops -- user trying to open file in Lexplore Window. Use win#".g:netrw_chgwin." instead")
+" exe g:netrw_chgwin."wincmd w"
+ wincmd p
+ call s:NetrwBrowse(0,s:NetrwBrowseChgDir(0,s:NetrwGetWord()))
+ endif
" prevwinnr: the window number of the "prev" window
" prevbufnr: the buffer number of the buffer in the "prev" window
@@ -8526,8 +8609,10 @@ fun! s:NetrwPrevWinOpen(islocal)
let prevbufname = bufname("%")
let prevmod = &mod
let bnrcnt = 0
+" call Decho("COMBAK#13: mod=".&mod." win#".winnr())
NetrwKeepj call s:RestoreBufVars()
" call Decho("after wincmd p: win#".winnr()." win($)#".winnr("$")." origwin#".origwin." &mod=".&mod." bufname(%)<".bufname("%")."> prevbufnr=".prevbufnr,'~'.expand("<slnum>"))
+" call Decho("COMBAK#14: mod=".&mod." win#".winnr())
" if the previous window's buffer has been changed (ie. its modified flag is set),
" and it doesn't appear in any other extant window, then ask the
@@ -8537,6 +8622,7 @@ fun! s:NetrwPrevWinOpen(islocal)
windo if winbufnr(0) == prevbufnr | let bnrcnt=bnrcnt+1 | endif
" call Decho("prevbufnr=".prevbufnr." bnrcnt=".bnrcnt." buftype=".&bt." winnr()=".winnr()." prevwinnr#".prevwinnr,'~'.expand("<slnum>"))
exe prevwinnr."wincmd w"
+" call Decho("COMBAK#15: mod=".&mod." win#".winnr())
if bnrcnt == 1 && &hidden == 0
" only one copy of the modified buffer in a window, and
@@ -8544,6 +8630,7 @@ fun! s:NetrwPrevWinOpen(islocal)
let choice = confirm("Save modified buffer<".prevbufname."> first?","&Yes\n&No\n&Cancel")
" call Decho("prevbufname<".prevbufname."> choice=".choice." current-winnr#".winnr(),'~'.expand("<slnum>"))
let &ei= eikeep
+" call Decho("COMBAK#16: mod=".&mod." win#".winnr())
if choice == 1
" Yes -- write file & then browse
@@ -8576,6 +8663,7 @@ fun! s:NetrwPrevWinOpen(islocal)
endif
let &ei= eikeep
endif
+" call Decho("COMBAK#17: mod=".&mod." win#".winnr())
" restore b:netrw_curdir (window split/enew may have lost it)
let b:netrw_curdir= curdir
@@ -9263,19 +9351,23 @@ fun! s:NetrwTreeDir(islocal)
if exists("s:prevwinopen")
unlet s:prevwinopen
endif
+" call Decho("COMBAK#18 : mod=".&mod." win#".winnr())
if !exists("b:netrw_curdir") || b:netrw_curdir == ""
let b:netrw_curdir= getcwd()
endif
let treedir = b:netrw_curdir
" call Decho("set initial treedir<".treedir.">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#19 : mod=".&mod." win#".winnr())
let s:treecurpos= winsaveview()
" call Decho("saving posn to s:treecurpos<".string(s:treecurpos).">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#20 : mod=".&mod." win#".winnr())
if exists("w:netrw_liststyle") && w:netrw_liststyle == s:TREELIST
" call Decho("w:netrw_liststyle is TREELIST:",'~'.expand("<slnum>"))
" call Decho("line#".line(".")." getline(.)<".getline('.')."> treecurpos<".string(s:treecurpos).">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#21 : mod=".&mod." win#".winnr())
" extract tree directory if on a line specifying a subdirectory (ie. ends with "/")
let curline= substitute(getline('.'),"\t -->.*$",'','')
@@ -9291,6 +9383,7 @@ fun! s:NetrwTreeDir(islocal)
" call Decho("do not extract tree subdirectory from current line and set treedir to empty",'~'.expand("<slnum>"))
let treedir= ""
endif
+" call Decho("COMBAK#22 : mod=".&mod." win#".winnr())
" detect user attempting to close treeroot
" call Decho("check if user is attempting to close treeroot",'~'.expand("<slnum>"))
@@ -9306,10 +9399,12 @@ fun! s:NetrwTreeDir(islocal)
" else " Decho
" call Decho(".user not attempting to close treeroot",'~'.expand("<slnum>"))
endif
+" call Decho("COMBAK#23 : mod=".&mod." win#".winnr())
" call Decho("islocal=".a:islocal." curline<".curline.">",'~'.expand("<slnum>"))
let potentialdir= s:NetrwFile(substitute(curline,'^'.s:treedepthstring.'\+ \(.*\)@$','\1',''))
" call Decho("potentialdir<".potentialdir."> isdir=".isdirectory(potentialdir),'~'.expand("<slnum>"))
+" call Decho("COMBAK#24 : mod=".&mod." win#".winnr())
" COMBAK: a symbolic link may point anywhere -- so it will be used to start a new treetop
" if a:islocal && curline =~ '@$' && isdirectory(s:NetrwFile(potentialdir))
@@ -9323,10 +9418,12 @@ fun! s:NetrwTreeDir(islocal)
let treedir = s:NetrwTreePath(w:netrw_treetop)
" endif
endif
+" call Decho("COMBAK#25 : mod=".&mod." win#".winnr())
" sanity maintenance: keep those //s away...
let treedir= substitute(treedir,'//$','/','')
" call Decho("treedir<".treedir.">",'~'.expand("<slnum>"))
+" call Decho("COMBAK#26 : mod=".&mod." win#".winnr())
" call Dret("s:NetrwTreeDir <".treedir."> : (side effect) s:treecurpos<".(exists("s:treecurpos")? string(s:treecurpos) : 'n/a').">")
return treedir
@@ -10687,7 +10784,8 @@ fun! netrw#LocalBrowseCheck(dirname)
" call Dfunc("netrw#LocalBrowseCheck(dirname<".a:dirname.">)")
" call Decho("isdir<".a:dirname."> =".isdirectory(s:NetrwFile(a:dirname)).((exists("s:treeforceredraw")? " treeforceredraw" : "")).'~'.expand("<slnum>"))
" call Decho("settings buf#".bufnr("%")."<".bufname("%").">: ".((&l:ma == 0)? "no" : "")."ma ".((&l:mod == 0)? "no" : "")."mod ".((&l:bl == 0)? "no" : "")."bl ".((&l:ro == 0)? "no" : "")."ro fo=".&l:fo,'~'.expand("<slnum>"))
-" call Dredir("ls!","netrw#LocalBrowseCheck")
+ " getting E930: Cannot use :redir inside execute
+"" call Dredir("ls!","netrw#LocalBrowseCheck")
" call Decho("tab#".tabpagenr()." win#".winnr()." buf#".bufnr("%")."<".bufname("%")."> line#".line(".")." col#".col(".")." winline#".winline()." wincol#".wincol(),'~'.expand("<slnum>"))
" call Decho("current buffer#".bufnr("%")."<".bufname("%")."> ft=".&ft,'~'.expand("<slnum>"))
@@ -10980,13 +11078,16 @@ fun! s:LocalListing()
" call Decho("pfile <".pfile.">",'~'.expand("<slnum>"))
if w:netrw_liststyle == s:LONGLIST
+ let longfile= printf("%-".g:netrw_maxfilenamelen."S",pfile)
let sz = getfsize(filename)
- let fsz = strpart(" ",1,15-strlen(sz)).sz
+ let szlen = 15 - (strdisplaywidth(longfile) - g:netrw_maxfilenamelen)
+ let szlen = (szlen > 0) ? szlen : 0
+
if g:netrw_sizestyle =~# "[hH]"
let sz= s:NetrwHumanReadable(sz)
endif
- let longfile= printf("%-".(g:netrw_maxfilenamelen+1)."s",pfile)
- let pfile = longfile.sz." ".strftime(g:netrw_timefmt,getftime(filename))
+ let fsz = printf("%".szlen."S",sz)
+ let pfile = longfile." ".fsz." ".strftime(g:netrw_timefmt,getftime(filename))
" call Decho("longlist support: sz=".sz." fsz=".fsz,'~'.expand("<slnum>"))
endif
@@ -10996,7 +11097,7 @@ fun! s:LocalListing()
" call Decho("implementing g:netrw_sort_by=".g:netrw_sort_by." (time)")
" call Decho("getftime(".filename.")=".getftime(filename),'~'.expand("<slnum>"))
let t = getftime(filename)
- let ft = strpart("000000000000000000",1,18-strlen(t)).t
+ let ft = printf("%018d",t)
" call Decho("exe NetrwKeepj put ='".ft.'/'.pfile."'",'~'.expand("<slnum>"))
let ftpfile= ft.'/'.pfile
sil! NetrwKeepj put=ftpfile
@@ -11006,10 +11107,7 @@ fun! s:LocalListing()
" call Decho("implementing g:netrw_sort_by=".g:netrw_sort_by." (size)")
" call Decho("getfsize(".filename.")=".getfsize(filename),'~'.expand("<slnum>"))
let sz = getfsize(filename)
- if g:netrw_sizestyle =~# "[hH]"
- let sz= s:NetrwHumanReadable(sz)
- endif
- let fsz = strpart("000000000000000000",1,18-strlen(sz)).sz
+ let fsz = printf("%018d",sz)
" call Decho("exe NetrwKeepj put ='".fsz.'/'.filename."'",'~'.expand("<slnum>"))
let fszpfile= fsz.'/'.pfile
sil! NetrwKeepj put =fszpfile
@@ -11285,34 +11383,8 @@ fun! s:NetrwLocalRmFile(path,fname,all)
let rmfile= substitute(rmfile,'[\/]$','','e')
if all || ok =~# 'y\%[es]' || ok == ""
- if v:version < 704 || (v:version == 704 && !has("patch1107"))
-" " call Decho("1st attempt: system(netrw#WinPath(".g:netrw_localrmdir.') '.s:ShellEscape(rmfile).')','~'.expand("<slnum>"))
- call system(netrw#WinPath(g:netrw_localrmdir).' '.s:ShellEscape(rmfile))
-" " call Decho("v:shell_error=".v:shell_error,'~'.expand("<slnum>"))
-
- if v:shell_error != 0
-" " call Decho("2nd attempt to remove directory<".rmfile.">",'~'.expand("<slnum>"))
- let errcode= s:NetrwDelete(rmfile)
-" " call Decho("errcode=".errcode,'~'.expand("<slnum>"))
-
- if errcode != 0
- if has("unix")
-" " call Decho("3rd attempt to remove directory<".rmfile.">",'~'.expand("<slnum>"))
- call system("rm ".s:ShellEscape(rmfile))
- if v:shell_error != 0 && !exists("g:netrw_quiet")
- call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",34)
- let ok="no"
- endif
- elseif !exists("g:netrw_quiet")
- call netrw#ErrorMsg(s:ERROR,"unable to remove directory<".rmfile."> -- is it empty?",35)
- let ok="no"
- endif
- endif
- endif
- else
- if delete(rmfile,"d")
- call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103)
- endif
+ if delete(rmfile,"d")
+ call netrw#ErrorMsg(s:ERROR,"unable to delete directory <".rmfile.">!",103)
endif
endif
endif
@@ -11868,9 +11940,9 @@ fun! s:NetrwBufRemover(bufid)
" call Decho("buf#".a:bufid." has name <".bufname(a:bufid).">","~".expand("<slnum>"))
" call Decho("buf#".a:bufid." has winid#".bufwinid(a:bufid),"~".expand("<slnum>"))
- if a:bufid > 1 && !buflisted(a:bufid) && bufname(a:bufid) == "" && bufwinid(a:bufid) == -1
+ if a:bufid > 1 && !buflisted(a:bufid) && bufloaded(a:bufid) && bufname(a:bufid) == "" && bufwinid(a:bufid) == -1
" call Decho("(s:NetrwBufRemover) removing buffer#".a:bufid,"~".expand("<slnum>"))
- exe "bd! ".a:bufid
+ exe "sil! bd! ".a:bufid
endif
" call Dret("s:NetrwBufRemover")
@@ -11907,13 +11979,13 @@ fun! s:NetrwEnew(...)
if exists("b:netrw_prvdir") |let netrw_prvdir = b:netrw_prvdir |endif
NetrwKeepj call s:NetrwOptionsRestore("w:")
-" call Decho("generate a buffer with NetrwKeepj keepalt enew!",'~'.expand("<slnum>"))
+" call Decho("generate a buffer with NetrwKeepj enew!",'~'.expand("<slnum>"))
" when tree listing uses file TreeListing... a new buffer is made.
" Want the old buffer to be unlisted.
" COMBAK: this causes a problem, see P43
" setl nobl
let netrw_keepdiff= &l:diff
- noswapfile NetrwKeepj keepalt enew!
+ call s:NetrwEditFile("enew!","","")
let &l:diff= netrw_keepdiff
" call Decho("bufnr($)=".bufnr("$")."<".bufname(bufnr("$"))."> winnr($)=".winnr("$"),'~'.expand("<slnum>"))
NetrwKeepj call s:NetrwOptionsSave("w:")
@@ -11962,7 +12034,7 @@ endfun
" s:NetrwExe: executes a string using "!" {{{2
fun! s:NetrwExe(cmd)
" call Dfunc("s:NetrwExe(a:cmd<".a:cmd.">)")
- if has("win32") && &shell !~? 'cmd' && !g:netrw_cygwin
+ if has("win32") && &shell !~? 'cmd\|pwsh\|powershell' && !g:netrw_cygwin
" call Decho("using win32:",expand("<slnum>"))
let savedShell=[&shell,&shellcmdflag,&shellxquote,&shellxescape,&shellquote,&shellpipe,&shellredir,&shellslash]
set shell& shellcmdflag& shellxquote& shellxescape&
@@ -12631,3 +12703,54 @@ unlet s:keepcpo
" Modelines: {{{1
" ===============
" vim:ts=8 fdm=marker
+" doing autoload/netrw.vim version v172g ~57
+" varname<g:netrw_dirhistcnt> value=0 ~1
+" varname<s:THINLIST> value=0 ~1
+" varname<s:LONGLIST> value=1 ~1
+" varname<s:WIDELIST> value=2 ~1
+" varname<s:TREELIST> value=3 ~1
+" varname<s:MAXLIST> value=4 ~1
+" varname<g:netrw_use_errorwindow> value=2 ~1
+" varname<g:netrw_http_xcmd> value=-q -O ~1
+" varname<g:netrw_http_put_cmd> value=curl -T ~1
+" varname<g:netrw_keepj> value=keepj ~1
+" varname<g:netrw_rcp_cmd> value=rcp ~1
+" varname<g:netrw_rsync_cmd> value=rsync ~1
+" varname<g:netrw_rsync_sep> value=/ ~1
+" varname<g:netrw_scp_cmd> value=scp -q ~1
+" varname<g:netrw_sftp_cmd> value=sftp ~1
+" varname<g:netrw_ssh_cmd> value=ssh ~1
+" varname<g:netrw_alto> value=0 ~1
+" varname<g:netrw_altv> value=1 ~1
+" varname<g:netrw_banner> value=1 ~1
+" varname<g:netrw_browse_split> value=0 ~1
+" varname<g:netrw_bufsettings> value=noma nomod nonu nobl nowrap ro nornu ~1
+" varname<g:netrw_chgwin> value=-1 ~1
+" varname<g:netrw_clipboard> value=1 ~1
+" varname<g:netrw_compress> value=gzip ~1
+" varname<g:netrw_ctags> value=ctags ~1
+" varname<g:netrw_cursor> value=2 ~1
+" (netrw) COMBAK: cuc=0 cul=0 initialization of s:netrw_cu[cl]
+" varname<g:netrw_cygdrive> value=/cygdrive ~1
+" varname<s:didstarstar> value=0 ~1
+" varname<g:netrw_dirhistcnt> value=0 ~1
+" varname<g:netrw_decompress> value={ ".gz" : "gunzip", ".bz2" : "bunzip2", ".zip" : "unzip", ".tar" : "tar -xf", ".xz" : "unxz" } ~1
+" varname<g:netrw_dirhistmax> value=10 ~1
+" varname<g:netrw_errorlvl> value=0 ~1
+" varname<g:netrw_fastbrowse> value=1 ~1
+" varname<g:netrw_ftp_browse_reject> value=^total\s\+\d\+$\|^Trying\s\+\d\+.*$\|^KERBEROS_V\d rejected\|^Security extensions not\|No such file\|: connect to address [0-9a-fA-F:]*: No route to host$ ~1
+" varname<g:netrw_ftpmode> value=binary ~1
+" varname<g:netrw_hide> value=1 ~1
+" varname<g:netrw_keepdir> value=1 ~1
+" varname<g:netrw_list_hide> value= ~1
+" varname<g:netrw_localmkdir> value=mkdir ~1
+" varname<g:netrw_remote_mkdir> value=mkdir ~1
+" varname<g:netrw_liststyle> value=0 ~1
+" varname<g:netrw_markfileesc> value=*./[\~ ~1
+" varname<g:netrw_maxfilenamelen> value=32 ~1
+" varname<g:netrw_menu> value=1 ~1
+" varname<g:netrw_mkdir_cmd> value=ssh USEPORT HOSTNAME mkdir ~1
+" varname<g:netrw_mousemaps> value=1 ~1
+" varname<g:netrw_retmap> value=0 ~1
+" varname<g:netrw_chgperm> value=chmod PERM FILENAME ~1
+" varname<g:netrw_preview> value=0 ~1
diff --git a/runtime/autoload/netrwSettings.vim b/runtime/autoload/netrwSettings.vim
index 61c0ef739e..d65f83144e 100644
--- a/runtime/autoload/netrwSettings.vim
+++ b/runtime/autoload/netrwSettings.vim
@@ -1,7 +1,7 @@
" netrwSettings.vim: makes netrw settings simpler
-" Date: Aug 12, 2021
+" Date: Nov 15, 2021
" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
-" Version: 17 ASTRO-ONLY
+" Version: 18
" Copyright: Copyright (C) 1999-2007 Charles E. Campbell {{{1
" Permission is hereby granted to use and distribute this code,
" with or without modifications, provided that this copyright
@@ -19,7 +19,7 @@
if exists("g:loaded_netrwSettings") || &cp
finish
endif
-let g:loaded_netrwSettings = "v17"
+let g:loaded_netrwSettings = "v18"
if v:version < 700
echohl WarningMsg
echo "***warning*** this version of netrwSettings needs vim 7.0"
@@ -159,8 +159,6 @@ fun! netrwSettings#NetrwSettings()
put = 'let g:netrw_localmkdiropt = '.g:netrw_localmkdiropt
put = 'let g:netrw_localmovecmd = '.g:netrw_localmovecmd
put = 'let g:netrw_localmovecmdopt = '.g:netrw_localmovecmdopt
- put = 'let g:netrw_localrmdir = '.g:netrw_localrmdir
- put = 'let g:netrw_localrmdiropt = '.g:netrw_localrmdiropt
put = 'let g:netrw_maxfilenamelen = '.g:netrw_maxfilenamelen
put = 'let g:netrw_menu = '.g:netrw_menu
put = 'let g:netrw_mousemaps = '.g:netrw_mousemaps
diff --git a/runtime/autoload/paste.vim b/runtime/autoload/paste.vim
index 2d787e7a1d..1ba336c2b3 100644
--- a/runtime/autoload/paste.vim
+++ b/runtime/autoload/paste.vim
@@ -1,6 +1,7 @@
" Vim support file to help with paste mappings and menus
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Jan 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Define the string to use for items that are present both in Edit, Popup and
" Toolbar menu. Also used in mswin.vim and macmap.vim.
diff --git a/runtime/autoload/phpcomplete.vim b/runtime/autoload/phpcomplete.vim
index f9aa15c827..2556ac857e 100644
--- a/runtime/autoload/phpcomplete.vim
+++ b/runtime/autoload/phpcomplete.vim
@@ -2924,7 +2924,7 @@ endfor
" builtin class information
let g:php_builtin_object_functions = {}
-" When completing for 'everyting imaginable' (no class context, not a
+" When completing for 'everything imaginable' (no class context, not a
" variable) we need a list of built-in classes in a format of {'classname':''}
" for performance reasons we precompile this too
let g:php_builtin_classnames = {}
diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim
index 98c80f1843..05f6bdb871 100644
--- a/runtime/autoload/provider/clipboard.vim
+++ b/runtime/autoload/provider/clipboard.vim
@@ -25,7 +25,8 @@ function! s:selection.on_exit(jobid, data, event) abort
if self.owner == a:jobid
let self.owner = 0
endif
- if a:data != 0
+ " Don't print if exit code is >= 128 ( exit is 128+SIGNUM if by signal (e.g. 143 on SIGTERM))
+ if a:data > 0 && a:data < 128
echohl WarningMsg
echomsg 'clipboard: error invoking '.get(self.argv, 0, '?').': '.join(self.stderr)
echohl None
@@ -92,9 +93,9 @@ function! provider#clipboard#Executable() abort
let s:cache_enabled = 0
return 'pbcopy'
elseif !empty($WAYLAND_DISPLAY) && executable('wl-copy') && executable('wl-paste')
- let s:copy['+'] = ['wl-copy', '--foreground', '--type', 'text/plain']
+ let s:copy['+'] = ['wl-copy', '--type', 'text/plain']
let s:paste['+'] = ['wl-paste', '--no-newline']
- let s:copy['*'] = ['wl-copy', '--foreground', '--primary', '--type', 'text/plain']
+ let s:copy['*'] = ['wl-copy', '--primary', '--type', 'text/plain']
let s:paste['*'] = ['wl-paste', '--no-newline', '--primary']
return 'wl-copy'
elseif !empty($WAYLAND_DISPLAY) && executable('waycopy') && executable('waypaste')
@@ -145,8 +146,8 @@ function! provider#clipboard#Executable() abort
let s:paste['*'] = s:paste['+']
return 'termux-clipboard'
elseif !empty($TMUX) && executable('tmux')
- let ver = matchlist(systemlist(['tmux', '-V'])[0], '\vtmux %(next-)?(\d+)\.(\d+)')
- if len(ver) >= 3 && (ver[1] > 3 || (ver[1] == 3 && ver[2] >= 2))
+ let tmux_v = v:lua.vim.version.parse(system(['tmux', '-V']))
+ if !empty(tmux_v) && !v:lua.vim.version.lt(tmux_v, [3,2,0])
let s:copy['+'] = ['tmux', 'load-buffer', '-w', '-']
else
let s:copy['+'] = ['tmux', 'load-buffer', '-']
diff --git a/runtime/autoload/provider/node.vim b/runtime/autoload/provider/node.vim
index 87af0094fe..3e7b8b4ef9 100644
--- a/runtime/autoload/provider/node.vim
+++ b/runtime/autoload/provider/node.vim
@@ -3,7 +3,7 @@ if exists('g:loaded_node_provider')
endif
let g:loaded_node_provider = 1
-function! s:is_minimum_version(version, min_major, min_minor) abort
+function! s:is_minimum_version(version, min_version) abort
if empty(a:version)
let nodejs_version = get(split(system(['node', '-v']), "\n"), 0, '')
if v:shell_error || nodejs_version[0] !=# 'v'
@@ -15,11 +15,7 @@ function! s:is_minimum_version(version, min_major, min_minor) abort
" Remove surrounding junk. Example: 'v4.12.0' => '4.12.0'
let nodejs_version = matchstr(nodejs_version, '\(\d\.\?\)\+')
" [major, minor, patch]
- let v_list = split(nodejs_version, '\.')
- return len(v_list) == 3
- \ && ((str2nr(v_list[0]) > str2nr(a:min_major))
- \ || (str2nr(v_list[0]) == str2nr(a:min_major)
- \ && str2nr(v_list[1]) >= str2nr(a:min_minor)))
+ return !v:lua.vim.version.lt(nodejs_version, a:min_version)
endfunction
let s:NodeHandler = {
@@ -43,20 +39,20 @@ function! provider#node#can_inspect() abort
if v:shell_error || ver[0] !=# 'v'
return 0
endif
- return (ver[1] ==# '6' && s:is_minimum_version(ver, 6, 12))
- \ || s:is_minimum_version(ver, 7, 6)
+ return (ver[1] ==# '6' && s:is_minimum_version(ver, '6.12.0'))
+ \ || s:is_minimum_version(ver, '7.6.0')
endfunction
function! provider#node#Detect() abort
- let minver = [6, 0]
+ let minver = '6.0.0'
if exists('g:node_host_prog')
return [expand(g:node_host_prog, v:true), '']
endif
if !executable('node')
return ['', 'node not found (or not executable)']
endif
- if !s:is_minimum_version(v:null, minver[0], minver[1])
- return ['', printf('node version %s.%s not found', minver[0], minver[1])]
+ if !s:is_minimum_version(v:null, minver)
+ return ['', printf('node version %s not found', minver)]
endif
let npm_opts = {}
diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim
index 6211b457d6..48b96c699a 100644
--- a/runtime/autoload/provider/pythonx.vim
+++ b/runtime/autoload/provider/pythonx.vim
@@ -26,7 +26,7 @@ endfunction
function! s:get_python_candidates(major_version) abort
return {
- \ 3: ['python3', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python']
+ \ 3: ['python3', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python']
\ }[a:major_version]
endfunction
@@ -61,12 +61,11 @@ endfunction
" Returns array: [prog_exitcode, prog_version]
function! s:import_module(prog, module) abort
- let prog_version = system([a:prog, '-c' , printf(
- \ 'import sys; ' .
+ let prog_version = system([a:prog, '-W', 'ignore', '-c', printf(
+ \ 'import sys, importlib.util; ' .
\ 'sys.path = [p for p in sys.path if p != ""]; ' .
\ 'sys.stdout.write(str(sys.version_info[0]) + "." + str(sys.version_info[1])); ' .
- \ 'import pkgutil; ' .
- \ 'exit(2*int(pkgutil.get_loader("%s") is None))',
+ \ 'sys.exit(2 * int(importlib.util.find_spec("%s") is None))',
\ a:module)])
return [v:shell_error, prog_version]
endfunction
diff --git a/runtime/autoload/python.vim b/runtime/autoload/python.vim
index 1eaad09ef5..d5f4862363 100644
--- a/runtime/autoload/python.vim
+++ b/runtime/autoload/python.vim
@@ -22,8 +22,7 @@ let s:maxoff = 50 " maximum number of lines to look backwards for ()
function s:SearchBracket(fromlnum, flags)
return searchpairpos('[[({]', '', '[])}]', a:flags,
\ {-> synstack('.', col('.'))
- \ ->map({_, id -> id->synIDattr('name')})
- \ ->match('\%(Comment\|Todo\|String\)$') >= 0},
+ \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\|String\)$'}) >= 0},
\ [0, a:fromlnum - s:maxoff]->max(), g:python_indent.searchpair_timeout)
endfunction
@@ -157,15 +156,13 @@ function python#GetIndent(lnum, ...)
" the start of the comment. synID() is slow, a linear search would take
" too long on a long line.
if synstack(plnum, pline_len)
- \ ->map({_, id -> id->synIDattr('name')})
- \ ->match('\%(Comment\|Todo\)$') >= 0
+ \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
let min = 1
let max = pline_len
while min < max
let col = (min + max) / 2
if synstack(plnum, col)
- \ ->map({_, id -> id->synIDattr('name')})
- \ ->match('\%(Comment\|Todo\)$') >= 0
+ \ ->indexof({_, id -> synIDattr(id, 'name') =~ '\%(Comment\|Todo\)$'}) >= 0
let max = col
else
let min = col + 1
diff --git a/runtime/autoload/rust.vim b/runtime/autoload/rust.vim
index 34a3b41773..5ccbf4b382 100644
--- a/runtime/autoload/rust.vim
+++ b/runtime/autoload/rust.vim
@@ -1,207 +1,258 @@
-" Author: Kevin Ballard
" Description: Helper functions for Rust commands/mappings
-" Last Modified: May 27, 2014
+" Last Modified: 2023-09-11
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
+function! rust#Load()
+ " Utility call to get this script loaded, for debugging
+endfunction
+
+function! rust#GetConfigVar(name, default)
+ " Local buffer variable with same name takes predeence over global
+ if has_key(b:, a:name)
+ return get(b:, a:name)
+ endif
+ if has_key(g:, a:name)
+ return get(g:, a:name)
+ endif
+ return a:default
+endfunction
+
+" Include expression {{{1
+
+function! rust#IncludeExpr(fname) abort
+ " Remove leading 'crate::' to deal with 2018 edition style 'use'
+ " statements
+ let l:fname = substitute(a:fname, '^crate::', '', '')
+
+ " Remove trailing colons arising from lines like
+ "
+ " use foo::{Bar, Baz};
+ let l:fname = substitute(l:fname, ':\+$', '', '')
+
+ " Replace '::' with '/'
+ let l:fname = substitute(l:fname, '::', '/', 'g')
+
+ " When we have
+ "
+ " use foo::bar::baz;
+ "
+ " we can't tell whether baz is a module or a function; and we can't tell
+ " which modules correspond to files.
+ "
+ " So we work our way up, trying
+ "
+ " foo/bar/baz.rs
+ " foo/bar.rs
+ " foo.rs
+ while l:fname !=# '.'
+ let l:path = findfile(l:fname)
+ if !empty(l:path)
+ return l:fname
+ endif
+ let l:fname = fnamemodify(l:fname, ':h')
+ endwhile
+ return l:fname
+endfunction
+
" Jump {{{1
function! rust#Jump(mode, function) range
- let cnt = v:count1
- normal! m'
- if a:mode ==# 'v'
- norm! gv
- endif
- let foldenable = &foldenable
- set nofoldenable
- while cnt > 0
- execute "call <SID>Jump_" . a:function . "()"
- let cnt = cnt - 1
- endwhile
- let &foldenable = foldenable
+ let cnt = v:count1
+ normal! m'
+ if a:mode ==# 'v'
+ norm! gv
+ endif
+ let foldenable = &foldenable
+ set nofoldenable
+ while cnt > 0
+ execute "call <SID>Jump_" . a:function . "()"
+ let cnt = cnt - 1
+ endwhile
+ let &foldenable = foldenable
endfunction
function! s:Jump_Back()
- call search('{', 'b')
- keepjumps normal! w99[{
+ call search('{', 'b')
+ keepjumps normal! w99[{
endfunction
function! s:Jump_Forward()
- normal! j0
- call search('{', 'b')
- keepjumps normal! w99[{%
- call search('{')
+ normal! j0
+ call search('{', 'b')
+ keepjumps normal! w99[{%
+ call search('{')
endfunction
" Run {{{1
function! rust#Run(bang, args)
- let args = s:ShellTokenize(a:args)
- if a:bang
- let idx = index(l:args, '--')
- if idx != -1
- let rustc_args = idx == 0 ? [] : l:args[:idx-1]
- let args = l:args[idx+1:]
- else
- let rustc_args = l:args
- let args = []
- endif
- else
- let rustc_args = []
- endif
-
- let b:rust_last_rustc_args = l:rustc_args
- let b:rust_last_args = l:args
-
- call s:WithPath(function("s:Run"), rustc_args, args)
+ let args = s:ShellTokenize(a:args)
+ if a:bang
+ let idx = index(l:args, '--')
+ if idx != -1
+ let rustc_args = idx == 0 ? [] : l:args[:idx-1]
+ let args = l:args[idx+1:]
+ else
+ let rustc_args = l:args
+ let args = []
+ endif
+ else
+ let rustc_args = []
+ endif
+
+ let b:rust_last_rustc_args = l:rustc_args
+ let b:rust_last_args = l:args
+
+ call s:WithPath(function("s:Run"), rustc_args, args)
endfunction
function! s:Run(dict, rustc_args, args)
- let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r')
- if has('win32')
- let exepath .= '.exe'
- endif
-
- let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
- let rustc_args = [relpath, '-o', exepath] + a:rustc_args
-
- let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
-
- let pwd = a:dict.istemp ? a:dict.tmpdir : ''
- let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)')))
- if output != ''
- echohl WarningMsg
- echo output
- echohl None
- endif
- if !v:shell_error
- exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)'))
- endif
+ let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r')
+ if has('win32')
+ let exepath .= '.exe'
+ endif
+
+ let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
+ let rustc_args = [relpath, '-o', exepath] + a:rustc_args
+
+ let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
+
+ let pwd = a:dict.istemp ? a:dict.tmpdir : ''
+ let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)')))
+ if output !=# ''
+ echohl WarningMsg
+ echo output
+ echohl None
+ endif
+ if !v:shell_error
+ exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)'))
+ endif
endfunction
" Expand {{{1
function! rust#Expand(bang, args)
- let args = s:ShellTokenize(a:args)
- if a:bang && !empty(l:args)
- let pretty = remove(l:args, 0)
- else
- let pretty = "expanded"
- endif
- call s:WithPath(function("s:Expand"), pretty, args)
+ let args = s:ShellTokenize(a:args)
+ if a:bang && !empty(l:args)
+ let pretty = remove(l:args, 0)
+ else
+ let pretty = "expanded"
+ endif
+ call s:WithPath(function("s:Expand"), pretty, args)
endfunction
function! s:Expand(dict, pretty, args)
- try
- let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
-
- if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)'
- let flag = '--xpretty'
- else
- let flag = '--pretty'
- endif
- let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
- let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args
- let pwd = a:dict.istemp ? a:dict.tmpdir : ''
- let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)')))
- if v:shell_error
- echohl WarningMsg
- echo output
- echohl None
- else
- new
- silent put =output
- 1
- d
- setl filetype=rust
- setl buftype=nofile
- setl bufhidden=hide
- setl noswapfile
- " give the buffer a nice name
- let suffix = 1
- let basename = fnamemodify(a:dict.path, ':t:r')
- while 1
- let bufname = basename
- if suffix > 1 | let bufname .= ' ('.suffix.')' | endif
- let bufname .= '.pretty.rs'
- if bufexists(bufname)
- let suffix += 1
- continue
- endif
- exe 'silent noautocmd keepalt file' fnameescape(bufname)
- break
- endwhile
- endif
- endtry
+ try
+ let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
+
+ if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)'
+ let flag = '--xpretty'
+ else
+ let flag = '--pretty'
+ endif
+ let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
+ let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args
+ let pwd = a:dict.istemp ? a:dict.tmpdir : ''
+ let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)')))
+ if v:shell_error
+ echohl WarningMsg
+ echo output
+ echohl None
+ else
+ new
+ silent put =output
+ 1
+ d
+ setl filetype=rust
+ setl buftype=nofile
+ setl bufhidden=hide
+ setl noswapfile
+ " give the buffer a nice name
+ let suffix = 1
+ let basename = fnamemodify(a:dict.path, ':t:r')
+ while 1
+ let bufname = basename
+ if suffix > 1 | let bufname .= ' ('.suffix.')' | endif
+ let bufname .= '.pretty.rs'
+ if bufexists(bufname)
+ let suffix += 1
+ continue
+ endif
+ exe 'silent noautocmd keepalt file' fnameescape(bufname)
+ break
+ endwhile
+ endif
+ endtry
endfunction
function! rust#CompleteExpand(lead, line, pos)
- if a:line[: a:pos-1] =~ '^RustExpand!\s*\S*$'
- " first argument and it has a !
- let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"]
- if !empty(a:lead)
- call filter(list, "v:val[:len(a:lead)-1] == a:lead")
- endif
- return list
- endif
-
- return glob(escape(a:lead, "*?[") . '*', 0, 1)
+ if a:line[: a:pos-1] =~# '^RustExpand!\s*\S*$'
+ " first argument and it has a !
+ let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"]
+ if !empty(a:lead)
+ call filter(list, "v:val[:len(a:lead)-1] == a:lead")
+ endif
+ return list
+ endif
+
+ return glob(escape(a:lead, "*?[") . '*', 0, 1)
endfunction
" Emit {{{1
function! rust#Emit(type, args)
- let args = s:ShellTokenize(a:args)
- call s:WithPath(function("s:Emit"), a:type, args)
+ let args = s:ShellTokenize(a:args)
+ call s:WithPath(function("s:Emit"), a:type, args)
endfunction
function! s:Emit(dict, type, args)
- try
- let output_path = a:dict.tmpdir.'/output'
-
- let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
-
- let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
- let args = [relpath, '--emit', a:type, '-o', output_path] + a:args
- let pwd = a:dict.istemp ? a:dict.tmpdir : ''
- let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)')))
- if output != ''
- echohl WarningMsg
- echo output
- echohl None
- endif
- if !v:shell_error
- new
- exe 'silent keepalt read' fnameescape(output_path)
- 1
- d
- if a:type == "llvm-ir"
- setl filetype=llvm
- let extension = 'll'
- elseif a:type == "asm"
- setl filetype=asm
- let extension = 's'
- endif
- setl buftype=nofile
- setl bufhidden=hide
- setl noswapfile
- if exists('l:extension')
- " give the buffer a nice name
- let suffix = 1
- let basename = fnamemodify(a:dict.path, ':t:r')
- while 1
- let bufname = basename
- if suffix > 1 | let bufname .= ' ('.suffix.')' | endif
- let bufname .= '.'.extension
- if bufexists(bufname)
- let suffix += 1
- continue
- endif
- exe 'silent noautocmd keepalt file' fnameescape(bufname)
- break
- endwhile
- endif
- endif
- endtry
+ try
+ let output_path = a:dict.tmpdir.'/output'
+
+ let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
+
+ let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path)
+ let args = [relpath, '--emit', a:type, '-o', output_path] + a:args
+ let pwd = a:dict.istemp ? a:dict.tmpdir : ''
+ let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)')))
+ if output !=# ''
+ echohl WarningMsg
+ echo output
+ echohl None
+ endif
+ if !v:shell_error
+ new
+ exe 'silent keepalt read' fnameescape(output_path)
+ 1
+ d
+ if a:type ==# "llvm-ir"
+ setl filetype=llvm
+ let extension = 'll'
+ elseif a:type ==# "asm"
+ setl filetype=asm
+ let extension = 's'
+ endif
+ setl buftype=nofile
+ setl bufhidden=hide
+ setl noswapfile
+ if exists('l:extension')
+ " give the buffer a nice name
+ let suffix = 1
+ let basename = fnamemodify(a:dict.path, ':t:r')
+ while 1
+ let bufname = basename
+ if suffix > 1 | let bufname .= ' ('.suffix.')' | endif
+ let bufname .= '.'.extension
+ if bufexists(bufname)
+ let suffix += 1
+ continue
+ endif
+ exe 'silent noautocmd keepalt file' fnameescape(bufname)
+ break
+ endwhile
+ endif
+ endif
+ endtry
endfunction
" Utility functions {{{1
@@ -219,145 +270,154 @@ endfunction
" existing path of the current buffer. If the path is inside of {dict.tmpdir}
" then it is guaranteed to have a '.rs' extension.
function! s:WithPath(func, ...)
- let buf = bufnr('')
- let saved = {}
- let dict = {}
- try
- let saved.write = &write
- set write
- let dict.path = expand('%')
- let pathisempty = empty(dict.path)
-
- " Always create a tmpdir in case the wrapped command wants it
- let dict.tmpdir = tempname()
- call mkdir(dict.tmpdir)
-
- if pathisempty || !saved.write
- let dict.istemp = 1
- " if we're doing this because of nowrite, preserve the filename
- if !pathisempty
- let filename = expand('%:t:r').".rs"
- else
- let filename = 'unnamed.rs'
- endif
- let dict.tmpdir_relpath = filename
- let dict.path = dict.tmpdir.'/'.filename
-
- let saved.mod = &mod
- set nomod
-
- silent exe 'keepalt write! ' . fnameescape(dict.path)
- if pathisempty
- silent keepalt 0file
- endif
- else
- let dict.istemp = 0
- update
- endif
-
- call call(a:func, [dict] + a:000)
- finally
- if bufexists(buf)
- for [opt, value] in items(saved)
- silent call setbufvar(buf, '&'.opt, value)
- unlet value " avoid variable type mismatches
- endfor
- endif
- if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif
- endtry
+ let buf = bufnr('')
+ let saved = {}
+ let dict = {}
+ try
+ let saved.write = &write
+ set write
+ let dict.path = expand('%')
+ let pathisempty = empty(dict.path)
+
+ " Always create a tmpdir in case the wrapped command wants it
+ let dict.tmpdir = tempname()
+ call mkdir(dict.tmpdir)
+
+ if pathisempty || !saved.write
+ let dict.istemp = 1
+ " if we're doing this because of nowrite, preserve the filename
+ if !pathisempty
+ let filename = expand('%:t:r').".rs"
+ else
+ let filename = 'unnamed.rs'
+ endif
+ let dict.tmpdir_relpath = filename
+ let dict.path = dict.tmpdir.'/'.filename
+
+ let saved.mod = &modified
+ set nomodified
+
+ silent exe 'keepalt write! ' . fnameescape(dict.path)
+ if pathisempty
+ silent keepalt 0file
+ endif
+ else
+ let dict.istemp = 0
+ update
+ endif
+
+ call call(a:func, [dict] + a:000)
+ finally
+ if bufexists(buf)
+ for [opt, value] in items(saved)
+ silent call setbufvar(buf, '&'.opt, value)
+ unlet value " avoid variable type mismatches
+ endfor
+ endif
+ if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif
+ endtry
endfunction
function! rust#AppendCmdLine(text)
- call setcmdpos(getcmdpos())
- let cmd = getcmdline() . a:text
- return cmd
+ call setcmdpos(getcmdpos())
+ let cmd = getcmdline() . a:text
+ return cmd
endfunction
" Tokenize the string according to sh parsing rules
function! s:ShellTokenize(text)
- " states:
- " 0: start of word
- " 1: unquoted
- " 2: unquoted backslash
- " 3: double-quote
- " 4: double-quoted backslash
- " 5: single-quote
- let l:state = 0
- let l:current = ''
- let l:args = []
- for c in split(a:text, '\zs')
- if l:state == 0 || l:state == 1 " unquoted
- if l:c ==# ' '
- if l:state == 0 | continue | endif
- call add(l:args, l:current)
- let l:current = ''
- let l:state = 0
- elseif l:c ==# '\'
- let l:state = 2
- elseif l:c ==# '"'
- let l:state = 3
- elseif l:c ==# "'"
- let l:state = 5
- else
- let l:current .= l:c
- let l:state = 1
- endif
- elseif l:state == 2 " unquoted backslash
- if l:c !=# "\n" " can it even be \n?
- let l:current .= l:c
- endif
- let l:state = 1
- elseif l:state == 3 " double-quote
- if l:c ==# '\'
- let l:state = 4
- elseif l:c ==# '"'
- let l:state = 1
- else
- let l:current .= l:c
- endif
- elseif l:state == 4 " double-quoted backslash
- if stridx('$`"\', l:c) >= 0
- let l:current .= l:c
- elseif l:c ==# "\n" " is this even possible?
- " skip it
- else
- let l:current .= '\'.l:c
- endif
- let l:state = 3
- elseif l:state == 5 " single-quoted
- if l:c == "'"
- let l:state = 1
- else
- let l:current .= l:c
- endif
- endif
- endfor
- if l:state != 0
- call add(l:args, l:current)
- endif
- return l:args
+ " states:
+ " 0: start of word
+ " 1: unquoted
+ " 2: unquoted backslash
+ " 3: double-quote
+ " 4: double-quoted backslash
+ " 5: single-quote
+ let l:state = 0
+ let l:current = ''
+ let l:args = []
+ for c in split(a:text, '\zs')
+ if l:state == 0 || l:state == 1 " unquoted
+ if l:c ==# ' '
+ if l:state == 0 | continue | endif
+ call add(l:args, l:current)
+ let l:current = ''
+ let l:state = 0
+ elseif l:c ==# '\'
+ let l:state = 2
+ elseif l:c ==# '"'
+ let l:state = 3
+ elseif l:c ==# "'"
+ let l:state = 5
+ else
+ let l:current .= l:c
+ let l:state = 1
+ endif
+ elseif l:state == 2 " unquoted backslash
+ if l:c !=# "\n" " can it even be \n?
+ let l:current .= l:c
+ endif
+ let l:state = 1
+ elseif l:state == 3 " double-quote
+ if l:c ==# '\'
+ let l:state = 4
+ elseif l:c ==# '"'
+ let l:state = 1
+ else
+ let l:current .= l:c
+ endif
+ elseif l:state == 4 " double-quoted backslash
+ if stridx('$`"\', l:c) >= 0
+ let l:current .= l:c
+ elseif l:c ==# "\n" " is this even possible?
+ " skip it
+ else
+ let l:current .= '\'.l:c
+ endif
+ let l:state = 3
+ elseif l:state == 5 " single-quoted
+ if l:c ==# "'"
+ let l:state = 1
+ else
+ let l:current .= l:c
+ endif
+ endif
+ endfor
+ if l:state != 0
+ call add(l:args, l:current)
+ endif
+ return l:args
endfunction
function! s:RmDir(path)
- " sanity check; make sure it's not empty, /, or $HOME
- if empty(a:path)
- echoerr 'Attempted to delete empty path'
- return 0
- elseif a:path == '/' || a:path == $HOME
- echoerr 'Attempted to delete protected path: ' . a:path
- return 0
- endif
- return system("rm -rf " . shellescape(a:path))
+ " sanity check; make sure it's not empty, /, or $HOME
+ if empty(a:path)
+ echoerr 'Attempted to delete empty path'
+ return 0
+ elseif a:path ==# '/' || a:path ==# $HOME
+ let l:path = expand(a:path)
+ if l:path ==# '/' || l:path ==# $HOME
+ echoerr 'Attempted to delete protected path: ' . a:path
+ return 0
+ endif
+ endif
+
+ if !isdirectory(a:path)
+ return 0
+ endif
+
+ " delete() returns 0 when removing file successfully
+ return delete(a:path, 'rf') == 0
endfunction
" Executes {cmd} with the cwd set to {pwd}, without changing Vim's cwd.
" If {pwd} is the empty string then it doesn't change the cwd.
function! s:system(pwd, cmd)
- let cmd = a:cmd
- if !empty(a:pwd)
- let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd
- endif
- return system(cmd)
+ let cmd = a:cmd
+ if !empty(a:pwd)
+ let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd
+ endif
+ return system(cmd)
endfunction
" Playpen Support {{{1
@@ -366,10 +426,10 @@ endfunction
" http://github.com/mattn/gist-vim
function! s:has_webapi()
if !exists("*webapi#http#post")
- try
- call webapi#http#post()
- catch
- endtry
+ try
+ call webapi#http#post()
+ catch
+ endtry
endif
return exists("*webapi#http#post")
endfunction
@@ -381,35 +441,130 @@ function! rust#Play(count, line1, line2, ...) abort
let l:rust_shortener_url = get(g:, 'rust_shortener_url', 'https://is.gd/')
if !s:has_webapi()
- echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None
- return
+ echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None
+ return
endif
let bufname = bufname('%')
if a:count < 1
- let content = join(getline(a:line1, a:line2), "\n")
+ let content = join(getline(a:line1, a:line2), "\n")
else
- let save_regcont = @"
- let save_regtype = getregtype('"')
- silent! normal! gvy
- let content = @"
- call setreg('"', save_regcont, save_regtype)
+ let save_regcont = @"
+ let save_regtype = getregtype('"')
+ silent! normal! gvy
+ let content = @"
+ call setreg('"', save_regcont, save_regtype)
endif
- let body = l:rust_playpen_url."?code=".webapi#http#encodeURI(content)
+ let url = l:rust_playpen_url."?code=".webapi#http#encodeURI(content)
- if strlen(body) > 5000
- echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(body).')' | echohl None
- return
+ if strlen(url) > 5000
+ echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(url).')' | echohl None
+ return
endif
- let payload = "format=simple&url=".webapi#http#encodeURI(body)
+ let payload = "format=simple&url=".webapi#http#encodeURI(url)
let res = webapi#http#post(l:rust_shortener_url.'create.php', payload, {})
- let url = res.content
+ if res.status[0] ==# '2'
+ let url = res.content
+ endif
+
+ let footer = ''
+ if exists('g:rust_clip_command')
+ call system(g:rust_clip_command, url)
+ if !v:shell_error
+ let footer = ' (copied to clipboard)'
+ endif
+ endif
+ redraw | echomsg 'Done: '.url.footer
+endfunction
+
+" Run a test under the cursor or all tests {{{1
+
+" Finds a test function name under the cursor. Returns empty string when a
+" test function is not found.
+function! s:SearchTestFunctionNameUnderCursor() abort
+ let cursor_line = line('.')
- redraw | echomsg 'Done: '.url
+ " Find #[test] attribute
+ if search('\m\C#\[test\]', 'bcW') is 0
+ return ''
+ endif
+
+ " Move to an opening brace of the test function
+ let test_func_line = search('\m\C^\s*fn\s\+\h\w*\s*(.\+{$', 'eW')
+ if test_func_line is 0
+ return ''
+ endif
+
+ " Search the end of test function (closing brace) to ensure that the
+ " cursor position is within function definition
+ if maparg('<Plug>(MatchitNormalForward)') ==# ''
+ keepjumps normal! %
+ else
+ " Prefer matchit.vim official plugin to native % since the plugin
+ " provides better behavior than original % (#391)
+ " To load the plugin, run:
+ " :packadd matchit
+ execute 'keepjumps' 'normal' "\<Plug>(MatchitNormalForward)"
+ endif
+ if line('.') < cursor_line
+ return ''
+ endif
+
+ return matchstr(getline(test_func_line), '\m\C^\s*fn\s\+\zs\h\w*')
+endfunction
+
+function! rust#Test(mods, winsize, all, options) abort
+ let manifest = findfile('Cargo.toml', expand('%:p:h') . ';')
+ if manifest ==# ''
+ return rust#Run(1, '--test ' . a:options)
+ endif
+
+ " <count> defaults to 0, but we prefer an empty string
+ let winsize = a:winsize ? a:winsize : ''
+
+ if has('terminal')
+ if has('patch-8.0.910')
+ let cmd = printf('%s noautocmd %snew | terminal ++curwin ', a:mods, winsize)
+ else
+ let cmd = printf('%s terminal ', a:mods)
+ endif
+ elseif has('nvim')
+ let cmd = printf('%s noautocmd %snew | terminal ', a:mods, winsize)
+ else
+ let cmd = '!'
+ let manifest = shellescape(manifest)
+ endif
+
+ if a:all
+ if a:options ==# ''
+ execute cmd . 'cargo test --manifest-path' manifest
+ else
+ execute cmd . 'cargo test --manifest-path' manifest a:options
+ endif
+ return
+ endif
+
+ let saved = getpos('.')
+ try
+ let func_name = s:SearchTestFunctionNameUnderCursor()
+ finally
+ call setpos('.', saved)
+ endtry
+ if func_name ==# ''
+ echohl ErrorMsg
+ echomsg 'No test function was found under the cursor. Please add ! to command if you want to run all tests'
+ echohl None
+ return
+ endif
+ if a:options ==# ''
+ execute cmd . 'cargo test --manifest-path' manifest func_name
+ else
+ execute cmd . 'cargo test --manifest-path' manifest func_name a:options
+ endif
endfunction
" }}}1
-" vim: set noet sw=8 ts=8:
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/autoload/rust/debugging.vim b/runtime/autoload/rust/debugging.vim
new file mode 100644
index 0000000000..0e84183172
--- /dev/null
+++ b/runtime/autoload/rust/debugging.vim
@@ -0,0 +1,105 @@
+" Last Modified: 2023-09-11
+
+" For debugging, inspired by https://github.com/w0rp/rust/blob/master/autoload/rust/debugging.vim
+
+let s:global_variable_list = [
+ \ '_rustfmt_autosave_because_of_config',
+ \ 'ftplugin_rust_source_path',
+ \ 'loaded_syntastic_rust_cargo_checker',
+ \ 'loaded_syntastic_rust_filetype',
+ \ 'loaded_syntastic_rust_rustc_checker',
+ \ 'rust_bang_comment_leader',
+ \ 'rust_cargo_avoid_whole_workspace',
+ \ 'rust_clip_command',
+ \ 'rust_conceal',
+ \ 'rust_conceal_mod_path',
+ \ 'rust_conceal_pub',
+ \ 'rust_fold',
+ \ 'rust_last_args',
+ \ 'rust_last_rustc_args',
+ \ 'rust_original_delimitMate_excluded_regions',
+ \ 'rust_playpen_url',
+ \ 'rust_prev_delimitMate_quotes',
+ \ 'rust_recent_nearest_cargo_tol',
+ \ 'rust_recent_root_cargo_toml',
+ \ 'rust_recommended_style',
+ \ 'rust_set_conceallevel',
+ \ 'rust_set_conceallevel=1',
+ \ 'rust_set_foldmethod',
+ \ 'rust_set_foldmethod=1',
+ \ 'rust_shortener_url',
+ \ 'rustc_makeprg_no_percent',
+ \ 'rustc_path',
+ \ 'rustfmt_autosave',
+ \ 'rustfmt_autosave_if_config_present',
+ \ 'rustfmt_command',
+ \ 'rustfmt_emit_files',
+ \ 'rustfmt_fail_silently',
+ \ 'rustfmt_options',
+ \ 'syntastic_extra_filetypes',
+ \ 'syntastic_rust_cargo_fname',
+ \]
+
+function! s:Echo(message) abort
+ execute 'echo a:message'
+endfunction
+
+function! s:EchoGlobalVariables() abort
+ for l:key in s:global_variable_list
+ if l:key !~# '^_'
+ call s:Echo('let g:' . l:key . ' = ' . string(get(g:, l:key, v:null)))
+ endif
+
+ if has_key(b:, l:key)
+ call s:Echo('let b:' . l:key . ' = ' . string(b:[l:key]))
+ endif
+ endfor
+endfunction
+
+function! rust#debugging#Info() abort
+ call cargo#Load()
+ call rust#Load()
+ call rustfmt#Load()
+ call s:Echo('rust.vim Global Variables:')
+ call s:Echo('')
+ call s:EchoGlobalVariables()
+
+ silent let l:output = system(g:rustfmt_command . ' --version')
+ echo l:output
+
+ let l:rustc = exists("g:rustc_path") ? g:rustc_path : "rustc"
+ silent let l:output = system(l:rustc . ' --version')
+ echo l:output
+
+ silent let l:output = system('cargo --version')
+ echo l:output
+
+ version
+
+ if exists(":SyntasticInfo")
+ echo "----"
+ echo "Info from Syntastic:"
+ execute "SyntasticInfo"
+ endif
+endfunction
+
+function! rust#debugging#InfoToClipboard() abort
+ redir @"
+ silent call rust#debugging#Info()
+ redir END
+
+ call s:Echo('RustInfo copied to your clipboard')
+endfunction
+
+function! rust#debugging#InfoToFile(filename) abort
+ let l:expanded_filename = expand(a:filename)
+
+ redir => l:output
+ silent call rust#debugging#Info()
+ redir END
+
+ call writefile(split(l:output, "\n"), l:expanded_filename)
+ call s:Echo('RustInfo written to ' . l:expanded_filename)
+endfunction
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/autoload/rustfmt.vim b/runtime/autoload/rustfmt.vim
index a689b5e00d..8fd3858178 100644
--- a/runtime/autoload/rustfmt.vim
+++ b/runtime/autoload/rustfmt.vim
@@ -1,107 +1,261 @@
" Author: Stephen Sugden <stephen@stephensugden.com>
+" Last Modified: 2023-09-11
"
" Adapted from https://github.com/fatih/vim-go
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
if !exists("g:rustfmt_autosave")
- let g:rustfmt_autosave = 0
+ let g:rustfmt_autosave = 0
endif
if !exists("g:rustfmt_command")
- let g:rustfmt_command = "rustfmt"
+ let g:rustfmt_command = "rustfmt"
endif
if !exists("g:rustfmt_options")
- let g:rustfmt_options = ""
+ let g:rustfmt_options = ""
endif
if !exists("g:rustfmt_fail_silently")
- let g:rustfmt_fail_silently = 0
+ let g:rustfmt_fail_silently = 0
+endif
+
+function! rustfmt#DetectVersion()
+ " Save rustfmt '--help' for feature inspection
+ silent let s:rustfmt_help = system(g:rustfmt_command . " --help")
+ let s:rustfmt_unstable_features = s:rustfmt_help =~# "--unstable-features"
+
+ " Build a comparable rustfmt version variable out of its `--version` output:
+ silent let l:rustfmt_version_full = system(g:rustfmt_command . " --version")
+ let l:rustfmt_version_list = matchlist(l:rustfmt_version_full,
+ \ '\vrustfmt ([0-9]+[.][0-9]+[.][0-9]+)')
+ if len(l:rustfmt_version_list) < 3
+ let s:rustfmt_version = "0"
+ else
+ let s:rustfmt_version = l:rustfmt_version_list[1]
+ endif
+ return s:rustfmt_version
+endfunction
+
+call rustfmt#DetectVersion()
+
+if !exists("g:rustfmt_emit_files")
+ let g:rustfmt_emit_files = s:rustfmt_version >= "0.8.2"
+endif
+
+if !exists("g:rustfmt_file_lines")
+ let g:rustfmt_file_lines = s:rustfmt_help =~# "--file-lines JSON"
endif
let s:got_fmt_error = 0
+function! rustfmt#Load()
+ " Utility call to get this script loaded, for debugging
+endfunction
+
+function! s:RustfmtWriteMode()
+ if g:rustfmt_emit_files
+ return "--emit=files"
+ else
+ return "--write-mode=overwrite"
+ endif
+endfunction
+
+function! s:RustfmtConfigOptions()
+ let l:rustfmt_toml = findfile('rustfmt.toml', expand('%:p:h') . ';')
+ if l:rustfmt_toml !=# ''
+ return '--config-path '.shellescape(fnamemodify(l:rustfmt_toml, ":p"))
+ endif
+
+ let l:_rustfmt_toml = findfile('.rustfmt.toml', expand('%:p:h') . ';')
+ if l:_rustfmt_toml !=# ''
+ return '--config-path '.shellescape(fnamemodify(l:_rustfmt_toml, ":p"))
+ endif
+
+ " Default to edition 2018 in case no rustfmt.toml was found.
+ return '--edition 2018'
+endfunction
+
function! s:RustfmtCommandRange(filename, line1, line2)
- let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]}
- return printf("%s %s --write-mode=overwrite --file-lines '[%s]'", g:rustfmt_command, g:rustfmt_options, json_encode(l:arg))
+ if g:rustfmt_file_lines == 0
+ echo "--file-lines is not supported in the installed `rustfmt` executable"
+ return
+ endif
+
+ let l:arg = {"file": shellescape(a:filename), "range": [a:line1, a:line2]}
+ let l:write_mode = s:RustfmtWriteMode()
+ let l:rustfmt_config = s:RustfmtConfigOptions()
+
+ " FIXME: When --file-lines gets to be stable, add version range checking
+ " accordingly.
+ let l:unstable_features = s:rustfmt_unstable_features ? '--unstable-features' : ''
+
+ let l:cmd = printf("%s %s %s %s %s --file-lines '[%s]' %s", g:rustfmt_command,
+ \ l:write_mode, g:rustfmt_options,
+ \ l:unstable_features, l:rustfmt_config,
+ \ json_encode(l:arg), shellescape(a:filename))
+ return l:cmd
endfunction
-function! s:RustfmtCommand(filename)
- return g:rustfmt_command . " --write-mode=overwrite " . g:rustfmt_options . " " . shellescape(a:filename)
+function! s:RustfmtCommand()
+ let write_mode = g:rustfmt_emit_files ? '--emit=stdout' : '--write-mode=display'
+ let config = s:RustfmtConfigOptions()
+ return join([g:rustfmt_command, write_mode, config, g:rustfmt_options])
endfunction
-function! s:RunRustfmt(command, curw, tmpname)
- if exists("*systemlist")
- let out = systemlist(a:command)
- else
- let out = split(system(a:command), '\r\?\n')
- endif
-
- if v:shell_error == 0 || v:shell_error == 3
- " remove undo point caused via BufWritePre
- try | silent undojoin | catch | endtry
-
- " Replace current file with temp file, then reload buffer
- call rename(a:tmpname, expand('%'))
- silent edit!
- let &syntax = &syntax
-
- " only clear location list if it was previously filled to prevent
- " clobbering other additions
- if s:got_fmt_error
- let s:got_fmt_error = 0
- call setloclist(0, [])
- lwindow
- endif
- elseif g:rustfmt_fail_silently == 0
- " otherwise get the errors and put them in the location list
- let errors = []
-
- for line in out
- " src/lib.rs:13:5: 13:10 error: expected `,`, or `}`, found `value`
- let tokens = matchlist(line, '^\(.\{-}\):\(\d\+\):\(\d\+\):\s*\(\d\+:\d\+\s*\)\?\s*error: \(.*\)')
- if !empty(tokens)
- call add(errors, {"filename": @%,
- \"lnum": tokens[2],
- \"col": tokens[3],
- \"text": tokens[5]})
- endif
- endfor
-
- if empty(errors)
- % | " Couldn't detect rustfmt error format, output errors
- endif
-
- if !empty(errors)
- call setloclist(0, errors, 'r')
- echohl Error | echomsg "rustfmt returned error" | echohl None
- endif
-
- let s:got_fmt_error = 1
- lwindow
- " We didn't use the temp file, so clean up
- call delete(a:tmpname)
- endif
-
- call winrestview(a:curw)
+function! s:DeleteLines(start, end) abort
+ silent! execute a:start . ',' . a:end . 'delete _'
endfunction
-function! rustfmt#FormatRange(line1, line2)
- let l:curw = winsaveview()
- let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt"
- call writefile(getline(1, '$'), l:tmpname)
+function! s:RunRustfmt(command, tmpname, from_writepre)
+ let l:view = winsaveview()
+
+ let l:stderr_tmpname = tempname()
+ call writefile([], l:stderr_tmpname)
+
+ let l:command = a:command . ' 2> ' . l:stderr_tmpname
- let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2)
+ if a:tmpname ==# ''
+ " Rustfmt in stdin/stdout mode
- call s:RunRustfmt(command, l:curw, l:tmpname)
+ " chdir to the directory of the file
+ let l:has_lcd = haslocaldir()
+ let l:prev_cd = getcwd()
+ execute 'lchdir! '.expand('%:h')
+
+ let l:buffer = getline(1, '$')
+ if exists("*systemlist")
+ silent let out = systemlist(l:command, l:buffer)
+ else
+ silent let out = split(system(l:command,
+ \ join(l:buffer, "\n")), '\r\?\n')
+ endif
+ else
+ if exists("*systemlist")
+ silent let out = systemlist(l:command)
+ else
+ silent let out = split(system(l:command), '\r\?\n')
+ endif
+ endif
+
+ let l:stderr = readfile(l:stderr_tmpname)
+
+ call delete(l:stderr_tmpname)
+
+ let l:open_lwindow = 0
+ if v:shell_error == 0
+ if a:from_writepre
+ " remove undo point caused via BufWritePre
+ try | silent undojoin | catch | endtry
+ endif
+
+ if a:tmpname ==# ''
+ let l:content = l:out
+ else
+ " take the tmpfile's content, this is better than rename
+ " because it preserves file modes.
+ let l:content = readfile(a:tmpname)
+ endif
+
+ call s:DeleteLines(len(l:content), line('$'))
+ call setline(1, l:content)
+
+ " only clear location list if it was previously filled to prevent
+ " clobbering other additions
+ if s:got_fmt_error
+ let s:got_fmt_error = 0
+ call setloclist(0, [])
+ let l:open_lwindow = 1
+ endif
+ elseif g:rustfmt_fail_silently == 0 && !a:from_writepre
+ " otherwise get the errors and put them in the location list
+ let l:errors = []
+
+ let l:prev_line = ""
+ for l:line in l:stderr
+ " error: expected one of `;` or `as`, found `extern`
+ " --> src/main.rs:2:1
+ let tokens = matchlist(l:line, '^\s\+-->\s\(.\{-}\):\(\d\+\):\(\d\+\)$')
+ if !empty(tokens)
+ call add(l:errors, {"filename": @%,
+ \"lnum": tokens[2],
+ \"col": tokens[3],
+ \"text": l:prev_line})
+ endif
+ let l:prev_line = l:line
+ endfor
+
+ if !empty(l:errors)
+ call setloclist(0, l:errors, 'r')
+ echohl Error | echomsg "rustfmt returned error" | echohl None
+ else
+ echo "rust.vim: was not able to parse rustfmt messages. Here is the raw output:"
+ echo "\n"
+ for l:line in l:stderr
+ echo l:line
+ endfor
+ endif
+
+ let s:got_fmt_error = 1
+ let l:open_lwindow = 1
+ endif
+
+ " Restore the current directory if needed
+ if a:tmpname ==# ''
+ if l:has_lcd
+ execute 'lchdir! '.l:prev_cd
+ else
+ execute 'chdir! '.l:prev_cd
+ endif
+ endif
+
+ " Open lwindow after we have changed back to the previous directory
+ if l:open_lwindow == 1
+ lwindow
+ endif
+
+ call winrestview(l:view)
+endfunction
+
+function! rustfmt#FormatRange(line1, line2)
+ let l:tmpname = tempname()
+ call writefile(getline(1, '$'), l:tmpname)
+ let command = s:RustfmtCommandRange(l:tmpname, a:line1, a:line2)
+ call s:RunRustfmt(command, l:tmpname, v:false)
+ call delete(l:tmpname)
endfunction
function! rustfmt#Format()
- let l:curw = winsaveview()
- let l:tmpname = expand("%:p:h") . "/." . expand("%:p:t") . ".rustfmt"
- call writefile(getline(1, '$'), l:tmpname)
+ call s:RunRustfmt(s:RustfmtCommand(), '', v:false)
+endfunction
- let command = s:RustfmtCommand(l:tmpname)
+function! rustfmt#Cmd()
+ " Mainly for debugging
+ return s:RustfmtCommand()
+endfunction
+
+function! rustfmt#PreWrite()
+ if !filereadable(expand("%@"))
+ return
+ endif
+ if rust#GetConfigVar('rustfmt_autosave_if_config_present', 0)
+ if findfile('rustfmt.toml', '.;') !=# '' || findfile('.rustfmt.toml', '.;') !=# ''
+ let b:rustfmt_autosave = 1
+ let b:_rustfmt_autosave_because_of_config = 1
+ endif
+ else
+ if has_key(b:, '_rustfmt_autosave_because_of_config')
+ unlet b:_rustfmt_autosave_because_of_config
+ unlet b:rustfmt_autosave
+ endif
+ endif
+
+ if !rust#GetConfigVar("rustfmt_autosave", 0)
+ return
+ endif
- call s:RunRustfmt(command, l:curw, l:tmpname)
+ call s:RunRustfmt(s:RustfmtCommand(), '', v:true)
endfunction
+
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/autoload/shada.vim b/runtime/autoload/shada.vim
index 87acc515ee..ae718ad2e7 100644
--- a/runtime/autoload/shada.vim
+++ b/runtime/autoload/shada.vim
@@ -487,7 +487,7 @@ let s:SHADA_ENTRY_OBJECT_SEQUENCE = ['type', 'timestamp', 'length', 'data']
""
" Convert list returned by msgpackparse() to a list of ShaDa objects
"
-" @param[in] mpack List of VimL objects returned by msgpackparse().
+" @param[in] mpack List of Vimscript objects returned by msgpackparse().
"
" @return List of dictionaries with keys type, timestamp, length and data. Each
" dictionary describes one ShaDa entry.
diff --git a/runtime/autoload/tar.vim b/runtime/autoload/tar.vim
index e495e8262a..e242fe98e3 100644
--- a/runtime/autoload/tar.vim
+++ b/runtime/autoload/tar.vim
@@ -1,7 +1,7 @@
" tar.vim: Handles browsing tarfiles
" AUTOLOAD PORTION
-" Date: Jan 07, 2020
-" Version: 32
+" Date: Nov 14, 2023
+" Version: 32b (with modifications from the Vim Project)
" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
" License: Vim License (see vim's :help license)
"
@@ -22,7 +22,7 @@
if &cp || exists("g:loaded_tar")
finish
endif
-let g:loaded_tar= "v32"
+let g:loaded_tar= "v32a"
if v:version < 702
echohl WarningMsg
echo "***warning*** this version of tar needs vim 7.2"
@@ -208,18 +208,24 @@ fun! tar#Browse(tarfile)
" call Dret("tar#Browse : a:tarfile<".a:tarfile.">")
return
endif
- if line("$") == curlast || ( line("$") == (curlast + 1) && getline("$") =~# '\c\%(warning\|error\|inappropriate\|unrecognized\)')
- redraw!
- echohl WarningMsg | echo "***warning*** (tar#Browse) ".a:tarfile." doesn't appear to be a tar file" | echohl None
- keepj sil! %d
- let eikeep= &ei
- set ei=BufReadCmd,FileReadCmd
- exe "r ".fnameescape(a:tarfile)
- let &ei= eikeep
- keepj sil! 1d
-" call Dret("tar#Browse : a:tarfile<".a:tarfile.">")
- return
- endif
+ "
+ " The following should not be neccessary, since in case of errors the
+ " previous if statement should have caught the problem (because tar exited
+ " with a non-zero exit code).
+ " if line("$") == curlast || ( line("$") == (curlast + 1) &&
+ " \ getline("$") =~# '\c\<\%(warning\|error\|inappropriate\|unrecognized\)\>' &&
+ " \ getline("$") =~ '\s' )
+ " redraw!
+ " echohl WarningMsg | echo "***warning*** (tar#Browse) ".a:tarfile." doesn't appear to be a tar file" | echohl None
+ " keepj sil! %d
+ " let eikeep= &ei
+ " set ei=BufReadCmd,FileReadCmd
+ " exe "r ".fnameescape(a:tarfile)
+ " let &ei= eikeep
+ " keepj sil! 1d
+ " call Dret("tar#Browse : a:tarfile<".a:tarfile.">")
+ " return
+ " endif
" set up maps supported for tar
setlocal noma nomod ro
@@ -735,82 +741,6 @@ fun! s:Rmdir(fname)
" call Dret("Rmdir")
endfun
-" ---------------------------------------------------------------------
-" tar#Vimuntar: installs a tarball in the user's .vim / vimfiles directory {{{2
-fun! tar#Vimuntar(...)
-" call Dfunc("tar#Vimuntar() a:0=".a:0." a:1<".(exists("a:1")? a:1 : "-n/a-").">")
- let tarball = expand("%")
-" call Decho("tarball<".tarball.">")
- let tarbase = substitute(tarball,'\..*$','','')
-" call Decho("tarbase<".tarbase.">")
- let tarhome = expand("%:p")
- if has("win32") || has("win95") || has("win64") || has("win16")
- let tarhome= substitute(tarhome,'\\','/','g')
- endif
- let tarhome= substitute(tarhome,'/[^/]*$','','')
-" call Decho("tarhome<".tarhome.">")
- let tartail = expand("%:t")
-" call Decho("tartail<".tartail.">")
- let curdir = getcwd()
-" call Decho("curdir <".curdir.">")
- " set up vimhome
- if a:0 > 0 && a:1 != ""
- let vimhome= a:1
- else
- let vimhome= vimball#VimballHome()
- endif
-" call Decho("vimhome<".vimhome.">")
-
-" call Decho("curdir<".curdir."> vimhome<".vimhome.">")
- if simplify(curdir) != simplify(vimhome)
- " copy (possibly compressed) tarball to .vim/vimfiles
-" call Decho(netrw#WinPath(g:tar_copycmd)." ".shellescape(tartail)." ".shellescape(vimhome))
- call system(netrw#WinPath(g:tar_copycmd)." ".shellescape(tartail)." ".shellescape(vimhome))
-" call Decho("exe cd ".fnameescape(vimhome))
- exe "cd ".fnameescape(vimhome)
- endif
-" call Decho("getcwd<".getcwd().">")
-
- " if necessary, decompress the tarball; then, extract it
- if tartail =~ '\.tgz'
- if executable("gunzip")
- silent exe "!gunzip ".shellescape(tartail)
- elseif executable("gzip")
- silent exe "!gzip -d ".shellescape(tartail)
- else
- echoerr "unable to decompress<".tartail."> on this system"
- if simplify(curdir) != simplify(tarhome)
- " remove decompressed tarball, restore directory
-" call Decho("delete(".tartail.".tar)")
- call delete(tartail.".tar")
-" call Decho("exe cd ".fnameescape(curdir))
- exe "cd ".fnameescape(curdir)
- endif
-" call Dret("tar#Vimuntar")
- return
- endif
- else
- call vimball#Decompress(tartail,0)
- endif
- let extractcmd= netrw#WinPath(g:tar_extractcmd)
-" call Decho("system(".extractcmd." ".shellescape(tarbase.".tar").")")
- call system(extractcmd." ".shellescape(tarbase.".tar"))
-
- " set up help
- if filereadable("doc/".tarbase.".txt")
-" call Decho("exe helptags ".getcwd()."/doc")
- exe "helptags ".getcwd()."/doc"
- endif
-
- if simplify(tarhome) != simplify(vimhome)
- " remove decompressed tarball, restore directory
- call delete(vimhome."/".tarbase.".tar")
- exe "cd ".fnameescape(curdir)
- endif
-
-" call Dret("tar#Vimuntar")
-endfun
-
" =====================================================================
" Modelines And Restoration: {{{1
let &cpo= s:keepcpo
diff --git a/runtime/autoload/tohtml.vim b/runtime/autoload/tohtml.vim
index 4ae17815ba..b1693efc5d 100644
--- a/runtime/autoload/tohtml.vim
+++ b/runtime/autoload/tohtml.vim
@@ -1,6 +1,6 @@
" Vim autoload file for the tohtml plugin.
" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2019 Aug 16
+" Last Change: 2023 Sep 03
"
" Additional contributors:
"
@@ -307,7 +307,7 @@ func! tohtml#Convert2HTML(line1, line2) "{{{
let g:html_diff_win_num = 0
for window in win_list
" switch to the next buffer to convert
- exe ":" . bufwinnr(window) . "wincmd w"
+ exe ":" .. bufwinnr(window) .. "wincmd w"
" figure out whether current charset and encoding will work, if not
" default to UTF-8
@@ -351,68 +351,70 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
let s:old_magic = &magic
set magic
- if s:settings.use_xhtml
- if s:settings.encoding != ""
- let xml_line = "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>"
- else
- let xml_line = "<?xml version=\"1.0\"?>"
+ let html = []
+ if !s:settings.no_doc
+ if s:settings.use_xhtml
+ if s:settings.encoding != ""
+ let xml_line = "<?xml version=\"1.0\" encoding=\"" .. s:settings.encoding .. "\"?>"
+ else
+ let xml_line = "<?xml version=\"1.0\"?>"
+ endif
+ let tag_close = ' />'
endif
- let tag_close = ' />'
- endif
- let style = [s:settings.use_xhtml ? "" : '-->']
- let body_line = ''
+ let style = [s:settings.use_xhtml ? "" : '-->']
+ let body_line = ''
- let html = []
- let s:html5 = 0
- if s:settings.use_xhtml
- call add(html, xml_line)
- endif
- if s:settings.use_xhtml
- call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
- call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">')
- elseif s:settings.use_css && !s:settings.no_pre
- call add(html, "<!DOCTYPE html>")
- call add(html, '<html>')
- let s:html5 = 1
- else
- call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
- call add(html, ' "http://www.w3.org/TR/html4/loose.dtd">')
- call add(html, '<html>')
- endif
- call add(html, '<head>')
-
- " include encoding as close to the top as possible, but only if not already
- " contained in XML information
- if s:settings.encoding != "" && !s:settings.use_xhtml
- if s:html5
- call add(html, '<meta charset="' . s:settings.encoding . '"' . tag_close)
+ let s:html5 = 0
+ if s:settings.use_xhtml
+ call add(html, xml_line)
+ endif
+ if s:settings.use_xhtml
+ call add(html, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">")
+ call add(html, '<html xmlns="http://www.w3.org/1999/xhtml">')
+ elseif s:settings.use_css && !s:settings.no_pre
+ call add(html, "<!DOCTYPE html>")
+ call add(html, '<html>')
+ let s:html5 = 1
else
- call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . tag_close)
+ call add(html, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"')
+ call add(html, ' "http://www.w3.org/TR/html4/loose.dtd">')
+ call add(html, '<html>')
+ endif
+ call add(html, '<head>')
+
+ " include encoding as close to the top as possible, but only if not already
+ " contained in XML information
+ if s:settings.encoding != "" && !s:settings.use_xhtml
+ if s:html5
+ call add(html, '<meta charset="' .. s:settings.encoding .. '"' .. tag_close)
+ else
+ call add(html, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. tag_close)
+ endif
endif
- endif
- call add(html, '<title>diff</title>')
- call add(html, '<meta name="Generator" content="Vim/'.v:version/100.'.'.v:version%100.'"'.tag_close)
- call add(html, '<meta name="plugin-version" content="'.g:loaded_2html_plugin.'"'.tag_close)
- call add(html, '<meta name="settings" content="'.
- \ join(filter(keys(s:settings),'s:settings[v:val]'),',').
- \ ',prevent_copy='.s:settings.prevent_copy.
- \ ',use_input_for_pc='.s:settings.use_input_for_pc.
- \ '"'.tag_close)
- call add(html, '<meta name="colorscheme" content="'.
- \ (exists('g:colors_name')
- \ ? g:colors_name
- \ : 'none'). '"'.tag_close)
-
- call add(html, '</head>')
- let body_line_num = len(html)
- call add(html, '<body'.(s:settings.line_ids ? ' onload="JumpToLine();"' : '').'>')
- call add(html, "<table ".(s:settings.use_css? "" : "border='1' width='100%' ")."id='vimCodeElement".s:settings.id_suffix."'>")
+ call add(html, '<title>diff</title>')
+ call add(html, '<meta name="Generator" content="Vim/'..v:version/100..'.'..v:version%100..'"'..tag_close)
+ call add(html, '<meta name="plugin-version" content="'..g:loaded_2html_plugin..'"'..tag_close)
+ call add(html, '<meta name="settings" content="'.
+ \ join(filter(keys(s:settings),'s:settings[v:val]'),',').
+ \ ',prevent_copy='..s:settings.prevent_copy.
+ \ ',use_input_for_pc='..s:settings.use_input_for_pc.
+ \ '"'..tag_close)
+ call add(html, '<meta name="colorscheme" content="'.
+ \ (exists('g:colors_name')
+ \ ? g:colors_name
+ \ : 'none').. '"'..tag_close)
+
+ call add(html, '</head>')
+ let body_line_num = len(html)
+ call add(html, '<body'..(s:settings.line_ids ? ' onload="JumpToLine();"' : '')..'>')
+ endif
+ call add(html, "<table "..(s:settings.use_css? "" : "border='1' width='100%' ").."id='vimCodeElement"..s:settings.id_suffix.."'>")
call add(html, '<tr>')
for buf in a:win_list
- call add(html, '<th>'.bufname(buf).'</th>')
+ call add(html, '<th>'..bufname(buf)..'</th>')
endfor
call add(html, '</tr><tr>')
@@ -421,7 +423,7 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
for buf in a:buf_list
let temp = []
- exe bufwinnr(buf) . 'wincmd w'
+ exe bufwinnr(buf) .. 'wincmd w'
" If text is folded because of user foldmethod settings, etc. we don't want
" to act on everything in a fold by mistake.
@@ -430,47 +432,53 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
" When not using CSS or when using xhtml, the <body> line can be important.
" Assume it will be the same for all buffers and grab it from the first
" buffer. Similarly, need to grab the body end line as well.
- if body_line == ''
- 1
- call search('<body')
- let body_line = getline('.')
- $
- call search('</body>', 'b')
- let s:body_end_line = getline('.')
- endif
+ if !s:settings.no_doc
+ if body_line == ''
+ 1
+ call search('<body')
+ let body_line = getline('.')
+ $
+ call search('</body>', 'b')
+ let s:body_end_line = getline('.')
+ endif
- " Grab the style information. Some of this will be duplicated so only insert
- " it if it's not already there. {{{
- 1
- let style_start = search('^<style\( type="text/css"\)\?>')
- 1
- let style_end = search('^</style>')
- if style_start > 0 && style_end > 0
- let buf_styles = getline(style_start + 1, style_end - 1)
- for a_style in buf_styles
- if index(style, a_style) == -1
- if diff_style_start == 0
- if a_style =~ '\<Diff\(Change\|Text\|Add\|Delete\)'
- let diff_style_start = len(style)-1
+ " Grab the style information. Some of this will be duplicated so only insert
+ " it if it's not already there. {{{
+ 1
+ let style_start = search('^<style\( type="text/css"\)\?>')
+ 1
+ let style_end = search('^</style>')
+ if style_start > 0 && style_end > 0
+ let buf_styles = getline(style_start + 1, style_end - 1)
+ for a_style in buf_styles
+ if index(style, a_style) == -1
+ if diff_style_start == 0
+ if a_style =~ '\<Diff\(Change\|Text\|Add\|Delete\)'
+ let diff_style_start = len(style)-1
+ endif
endif
+ call insert(style, a_style, insert_index)
+ let insert_index += 1
endif
- call insert(style, a_style, insert_index)
- let insert_index += 1
- endif
- endfor
- endif " }}}
+ endfor
+ endif " }}}
- " everything new will get added before the diff styles so diff highlight
- " properly overrides normal highlight
- if diff_style_start != 0
- let insert_index = diff_style_start
- endif
+ " everything new will get added before the diff styles so diff highlight
+ " properly overrides normal highlight
+ if diff_style_start != 0
+ let insert_index = diff_style_start
+ endif
- " Delete those parts that are not needed so we can include the rest into the
- " resulting table.
- 1,/^<body.*\%(\n<!--.*-->\_s\+.*id='oneCharWidth'.*\_s\+.*id='oneInputWidth'.*\_s\+.*id='oneEmWidth'\)\?\zs/d_
- $
- ?</body>?,$d_
+ " Delete those parts that are not needed so we can include the rest into the
+ " resulting table.
+ 1,/^<body.*\%(\n<!--.*-->\_s\+.*id='oneCharWidth'.*\_s\+.*id='oneInputWidth'.*\_s\+.*id='oneEmWidth'\)\?\zs/d_
+ $
+ ?</body>?,$d_
+ elseif !s:settings.no_modeline
+ " remove modeline from source files if it is included and we haven't deleted
+ " due to removing html footer already
+ $d
+ endif
let temp = getline(1,'$')
" clean out id on the main content container because we already set it on
" the table
@@ -478,7 +486,11 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
" undo deletion of start and end part
" so we can later save the file as valid html
" TODO: restore using grabbed lines if undolevel is 1?
- normal! 2u
+ if !s:settings.no_doc
+ normal! 2u
+ elseif !s:settings.no_modeline
+ normal! u
+ endif
if s:settings.use_css
call add(html, '<td><div>')
elseif s:settings.use_xhtml
@@ -495,29 +507,35 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
quit!
endfor
- let html[body_line_num] = body_line
+ if !s:settings.no_doc
+ let html[body_line_num] = body_line
+ endif
call add(html, '</tr>')
call add(html, '</table>')
- call add(html, s:body_end_line)
- call add(html, '</html>')
+ if !s:settings.no_doc
+ call add(html, s:body_end_line)
+ call add(html, '</html>')
+ endif
" The generated HTML is admittedly ugly and takes a LONG time to fold.
" Make sure the user doesn't do syntax folding when loading a generated file,
" using a modeline.
- call add(html, '<!-- vim: set foldmethod=manual : -->')
+ if !s:settings.no_modeline
+ call add(html, '<!-- vim: set foldmethod=manual : -->')
+ endif
let i = 1
- let name = "Diff" . (s:settings.use_xhtml ? ".xhtml" : ".html")
+ let name = "Diff" .. (s:settings.use_xhtml ? ".xhtml" : ".html")
" Find an unused file name if current file name is already in use
while filereadable(name)
- let name = substitute(name, '\d*\.x\?html$', '', '') . i . '.' . fnamemodify(copy(name), ":t:e")
+ let name = substitute(name, '\d*\.x\?html$', '', '') .. i .. '.' .. fnamemodify(copy(name), ":t:e")
let i += 1
endwhile
let s:ei_sav = &eventignore
set eventignore+=FileType
- exe "topleft new " . name
+ exe "topleft new " .. name
let &eventignore=s:ei_sav
unlet s:ei_sav
@@ -542,129 +560,131 @@ func! tohtml#Diff2HTML(win_list, buf_list) "{{{
call append(0, html)
- if len(style) > 0
- 1
- let style_start = search('^</head>')-1
+ if !s:settings.no_doc
+ if len(style) > 0
+ 1
+ let style_start = search('^</head>')-1
- " add required javascript in reverse order so we can just call append again
- " and again without adjusting {{{
+ " add required javascript in reverse order so we can just call append again
+ " and again without adjusting {{{
- let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
+ let s:uses_script = s:settings.dynamic_folds || s:settings.line_ids
- " insert script closing tag if needed
- if s:uses_script
- call append(style_start, [
- \ '',
- \ s:settings.use_xhtml ? '//]]>' : '-->',
- \ "</script>"
- \ ])
- endif
+ " insert script closing tag if needed
+ if s:uses_script
+ call append(style_start, [
+ \ '',
+ \ s:settings.use_xhtml ? '//]]>' : '-->',
+ \ "</script>"
+ \ ])
+ endif
- " insert javascript to get IDs from line numbers, and to open a fold before
- " jumping to any lines contained therein
- if s:settings.line_ids
- call append(style_start, [
- \ " /* Always jump to new location even if the line was hidden inside a fold, or",
- \ " * we corrected the raw number to a line ID.",
- \ " */",
- \ " if (lineElem) {",
- \ " lineElem.scrollIntoView(true);",
- \ " }",
- \ " return true;",
- \ "}",
- \ "if ('onhashchange' in window) {",
- \ " window.onhashchange = JumpToLine;",
- \ "}"
- \ ])
+ " insert javascript to get IDs from line numbers, and to open a fold before
+ " jumping to any lines contained therein
+ if s:settings.line_ids
+ call append(style_start, [
+ \ " /* Always jump to new location even if the line was hidden inside a fold, or",
+ \ " * we corrected the raw number to a line ID.",
+ \ " */",
+ \ " if (lineElem) {",
+ \ " lineElem.scrollIntoView(true);",
+ \ " }",
+ \ " return true;",
+ \ "}",
+ \ "if ('onhashchange' in window) {",
+ \ " window.onhashchange = JumpToLine;",
+ \ "}"
+ \ ])
- if s:settings.dynamic_folds
+ if s:settings.dynamic_folds
+ call append(style_start, [
+ \ "",
+ \ " /* navigate upwards in the DOM tree to open all folds containing the line */",
+ \ " var node = lineElem;",
+ \ " while (node && node.id != 'vimCodeElement"..s:settings.id_suffix.."')",
+ \ " {",
+ \ " if (node.className == 'closed-fold')",
+ \ " {",
+ \ " /* toggle open the fold ID (remove window ID) */",
+ \ " toggleFold(node.id.substr(4));",
+ \ " }",
+ \ " node = node.parentNode;",
+ \ " }",
+ \ ])
+ endif
+ endif
+
+ if s:settings.line_ids
call append(style_start, [
\ "",
- \ " /* navigate upwards in the DOM tree to open all folds containing the line */",
- \ " var node = lineElem;",
- \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')",
- \ " {",
- \ " if (node.className == 'closed-fold')",
- \ " {",
- \ " /* toggle open the fold ID (remove window ID) */",
- \ " toggleFold(node.id.substr(4));",
- \ " }",
- \ " node = node.parentNode;",
+ \ "/* function to open any folds containing a jumped-to line before jumping to it */",
+ \ "function JumpToLine()",
+ \ "{",
+ \ " var lineNum;",
+ \ " lineNum = window.location.hash;",
+ \ " lineNum = lineNum.substr(1); /* strip off '#' */",
+ \ "",
+ \ " if (lineNum.indexOf('L') == -1) {",
+ \ " lineNum = 'L'+lineNum;",
\ " }",
+ \ " if (lineNum.indexOf('W') == -1) {",
+ \ " lineNum = 'W1'+lineNum;",
+ \ " }",
+ \ " var lineElem = document.getElementById(lineNum);"
\ ])
endif
- endif
- if s:settings.line_ids
- call append(style_start, [
- \ "",
- \ "/* function to open any folds containing a jumped-to line before jumping to it */",
- \ "function JumpToLine()",
- \ "{",
- \ " var lineNum;",
- \ " lineNum = window.location.hash;",
- \ " lineNum = lineNum.substr(1); /* strip off '#' */",
- \ "",
- \ " if (lineNum.indexOf('L') == -1) {",
- \ " lineNum = 'L'+lineNum;",
- \ " }",
- \ " if (lineNum.indexOf('W') == -1) {",
- \ " lineNum = 'W1'+lineNum;",
- \ " }",
- \ " var lineElem = document.getElementById(lineNum);"
- \ ])
- endif
+ " Insert javascript to toggle matching folds open and closed in all windows,
+ " if dynamic folding is active.
+ if s:settings.dynamic_folds
+ call append(style_start, [
+ \ " function toggleFold(objID)",
+ \ " {",
+ \ " for (win_num = 1; win_num <= "..len(a:buf_list).."; win_num++)",
+ \ " {",
+ \ " var fold;",
+ \ ' fold = document.getElementById("win"+win_num+objID);',
+ \ " if(fold.className == 'closed-fold')",
+ \ " {",
+ \ " fold.className = 'open-fold';",
+ \ " }",
+ \ " else if (fold.className == 'open-fold')",
+ \ " {",
+ \ " fold.className = 'closed-fold';",
+ \ " }",
+ \ " }",
+ \ " }",
+ \ ])
+ endif
- " Insert javascript to toggle matching folds open and closed in all windows,
- " if dynamic folding is active.
- if s:settings.dynamic_folds
- call append(style_start, [
- \ " function toggleFold(objID)",
- \ " {",
- \ " for (win_num = 1; win_num <= ".len(a:buf_list)."; win_num++)",
- \ " {",
- \ " var fold;",
- \ ' fold = document.getElementById("win"+win_num+objID);',
- \ " if(fold.className == 'closed-fold')",
- \ " {",
- \ " fold.className = 'open-fold';",
- \ " }",
- \ " else if (fold.className == 'open-fold')",
- \ " {",
- \ " fold.className = 'closed-fold';",
- \ " }",
- \ " }",
- \ " }",
- \ ])
- endif
+ if s:uses_script
+ " insert script tag if needed
+ call append(style_start, [
+ \ "<script" .. (s:html5 ? "" : " type='text/javascript'") .. ">",
+ \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
+ endif
- if s:uses_script
- " insert script tag if needed
- call append(style_start, [
- \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">",
- \ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
+ " Insert styles from all the generated html documents and additional styles
+ " for the table-based layout of the side-by-side diff. The diff should take
+ " up the full browser window (but not more), and be static in size,
+ " horizontally scrollable when the lines are too long. Otherwise, the diff
+ " is pretty useless for really long lines. {{{
+ if s:settings.use_css
+ call append(style_start,
+ \ ['<style' .. (s:html5 ? '' : 'type="text/css"') .. '>']+
+ \ style+
+ \ [ s:settings.use_xhtml ? '' : '<!--',
+ \ 'table { table-layout: fixed; }',
+ \ 'html, body, table, tbody { width: 100%; margin: 0; padding: 0; }',
+ \ 'table, td, th { border: 1px solid; }',
+ \ 'td { vertical-align: top; }',
+ \ 'th, td { width: '..printf("%.1f",100.0/len(a:win_list))..'%; }',
+ \ 'td div { overflow: auto; }',
+ \ s:settings.use_xhtml ? '' : '-->',
+ \ '</style>'
+ \])
+ endif "}}}
endif
-
- " Insert styles from all the generated html documents and additional styles
- " for the table-based layout of the side-by-side diff. The diff should take
- " up the full browser window (but not more), and be static in size,
- " horizontally scrollable when the lines are too long. Otherwise, the diff
- " is pretty useless for really long lines. {{{
- if s:settings.use_css
- call append(style_start,
- \ ['<style' . (s:html5 ? '' : 'type="text/css"') . '>']+
- \ style+
- \ [ s:settings.use_xhtml ? '' : '<!--',
- \ 'table { table-layout: fixed; }',
- \ 'html, body, table, tbody { width: 100%; margin: 0; padding: 0; }',
- \ 'table, td, th { border: 1px solid; }',
- \ 'td { vertical-align: top; }',
- \ 'th, td { width: '.printf("%.1f",100.0/len(a:win_list)).'%; }',
- \ 'td div { overflow: auto; }',
- \ s:settings.use_xhtml ? '' : '-->',
- \ '</style>'
- \])
- endif "}}}
endif
let &paste = s:old_paste
@@ -674,7 +694,7 @@ endfunc "}}}
" Gets a single user option and sets it in the passed-in Dict, or gives it the
" default value if the option doesn't actually exist.
func! tohtml#GetOption(settings, option, default) "{{{
- if exists('g:html_'.a:option)
+ if exists('g:html_'..a:option)
let a:settings[a:option] = g:html_{a:option}
else
let a:settings[a:option] = a:default
@@ -693,10 +713,11 @@ func! tohtml#GetUserSettings() "{{{
let user_settings = {}
" Define the correct option if the old option name exists and we haven't
- " already defined the correct one. Maybe I'll put out a warning message about
- " this sometime and remove the old option entirely at some even later time,
- " but for now just silently accept the old option.
+ " already defined the correct one.
if exists('g:use_xhtml') && !exists("g:html_use_xhtml")
+ echohl WarningMsg
+ echomsg "Warning: g:use_xhtml is deprecated, use g:html_use_xhtml"
+ echohl None
let g:html_use_xhtml = g:use_xhtml
endif
@@ -719,7 +740,7 @@ func! tohtml#GetUserSettings() "{{{
call tohtml#GetOption(user_settings, 'whole_filler', 0 )
call tohtml#GetOption(user_settings, 'use_xhtml', 0 )
call tohtml#GetOption(user_settings, 'line_ids', user_settings.number_lines )
- call tohtml#GetOption(user_settings, 'use_input_for_pc', 'fallback')
+ call tohtml#GetOption(user_settings, 'use_input_for_pc', 'none')
" }}}
" override those settings that need it {{{
@@ -834,16 +855,16 @@ func! tohtml#GetUserSettings() "{{{
if user_settings.use_css
if exists("g:html_prevent_copy")
if user_settings.dynamic_folds && !user_settings.no_foldcolumn && g:html_prevent_copy =~# 'f'
- let user_settings.prevent_copy .= 'f'
+ let user_settings.prevent_copy ..= 'f'
endif
if user_settings.number_lines && g:html_prevent_copy =~# 'n'
- let user_settings.prevent_copy .= 'n'
+ let user_settings.prevent_copy ..= 'n'
endif
if &diff && g:html_prevent_copy =~# 'd'
- let user_settings.prevent_copy .= 'd'
+ let user_settings.prevent_copy ..= 'd'
endif
if !user_settings.ignore_folding && g:html_prevent_copy =~# 't'
- let user_settings.prevent_copy .= 't'
+ let user_settings.prevent_copy ..= 't'
endif
else
let user_settings.prevent_copy = ""
@@ -855,10 +876,10 @@ func! tohtml#GetUserSettings() "{{{
" enforce valid values for use_input_for_pc
if user_settings.use_input_for_pc !~# 'fallback\|none\|all'
- let user_settings.use_input_for_pc = 'fallback'
+ let user_settings.use_input_for_pc = 'none'
echohl WarningMsg
- echomsg '2html: "' . g:html_use_input_for_pc . '" is not valid for g:html_use_input_for_pc'
- echomsg '2html: defaulting to "' . user_settings.use_input_for_pc . '"'
+ echomsg '2html: "' .. g:html_use_input_for_pc .. '" is not valid for g:html_use_input_for_pc'
+ echomsg '2html: defaulting to "' .. user_settings.use_input_for_pc .. '"'
echohl None
sleep 3
endif
diff --git a/runtime/autoload/tutor.vim b/runtime/autoload/tutor.vim
index 4da4213826..a546f7007e 100644
--- a/runtime/autoload/tutor.vim
+++ b/runtime/autoload/tutor.vim
@@ -29,7 +29,7 @@ function! tutor#MouseDoubleClick()
if foldclosed(line('.')) > -1
normal! zo
else
- if match(getline('.'), '^#\{1,} ') > -1
+ if match(getline('.'), '^#\{1,} ') > -1 && foldlevel(line('.')) > 0
silent normal! zc
else
call tutor#FollowLink(0)
@@ -220,6 +220,7 @@ function! tutor#TutorCmd(tutor_name)
call tutor#SetupVim()
exe "edit ".l:to_open
+ call tutor#ApplyTransform()
endfunction
function! tutor#TutorCmdComplete(lead,line,pos)
@@ -227,3 +228,12 @@ function! tutor#TutorCmdComplete(lead,line,pos)
let l:names = uniq(sort(map(l:tutors, 'fnamemodify(v:val, ":t:r")'), 's:Sort'))
return join(l:names, "\n")
endfunction
+
+function! tutor#ApplyTransform()
+ if has('win32')
+ sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\2/g
+ else
+ sil! %s/{unix:(\(.\{-}\)),win:(\(.\{-}\))}/\1/g
+ endif
+ normal! gg0
+endfunction
diff --git a/runtime/autoload/zip.vim b/runtime/autoload/zip.vim
index bc9b62ddb0..e61293c357 100644
--- a/runtime/autoload/zip.vim
+++ b/runtime/autoload/zip.vim
@@ -1,7 +1,7 @@
" zip.vim: Handles browsing zipfiles
" AUTOLOAD PORTION
-" Date: Nov 08, 2021
-" Version: 32
+" Date: Mar 12, 2023
+" Version: 33
" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
" License: Vim License (see vim's :help license)
" Copyright: Copyright (C) 2005-2019 Charles E. Campbell {{{1
@@ -20,7 +20,7 @@
if &cp || exists("g:loaded_zip")
finish
endif
-let g:loaded_zip= "v32"
+let g:loaded_zip= "v33"
if v:version < 702
echohl WarningMsg
echo "***warning*** this version of zip needs vim 7.2 or later"
@@ -57,6 +57,11 @@ if !exists("g:zip_extractcmd")
let g:zip_extractcmd= g:zip_unzipcmd
endif
+if !dist#vim#IsSafeExecutable('zip', g:zip_unzipcmd)
+ echoerr "Warning: NOT executing " .. g:zip_unzipcmd .. " from current directory!"
+ finish
+endif
+
" ----------------
" Functions: {{{1
" ----------------
@@ -160,10 +165,14 @@ endfun
" ---------------------------------------------------------------------
" ZipBrowseSelect: {{{2
fun! s:ZipBrowseSelect()
-" call Dfunc("ZipBrowseSelect() zipfile<".b:zipfile."> curfile<".expand("%").">")
+ " call Dfunc("ZipBrowseSelect() zipfile<".((exists("b:zipfile"))? b:zipfile : "n/a")."> curfile<".expand("%").">")
let repkeep= &report
set report=10
let fname= getline(".")
+ if !exists("b:zipfile")
+" call Dret("ZipBrowseSelect : b:zipfile doesn't exist!")
+ return
+ endif
" sanity check
if fname =~ '^"'
diff --git a/runtime/bugreport.vim b/runtime/bugreport.vim
deleted file mode 100644
index 27761ca011..0000000000
--- a/runtime/bugreport.vim
+++ /dev/null
@@ -1,87 +0,0 @@
-:" Use this script to create the file "bugreport.txt", which contains
-:" information about the environment of a possible bug in Vim.
-:"
-:" Maintainer: Bram Moolenaar <Bram@vim.org>
-:" Last change: 2019 Jan 27
-:"
-:" To use inside Vim:
-:" :so $VIMRUNTIME/bugreport.vim
-:" Or, from the command line:
-:" vim -s $VIMRUNTIME/bugreport.vim
-:"
-:" The "if 1" lines are to avoid error messages when expression evaluation is
-:" not compiled in.
-:"
-:if 1
-: let more_save = &more
-:endif
-:set nomore
-:if has("unix")
-: !echo "uname -a" >bugreport.txt
-: !uname -a >>bugreport.txt
-:endif
-:redir >>bugreport.txt
-:version
-:if 1
-: func <SID>CheckDir(n)
-: if isdirectory(a:n)
-: echo 'directory "' . a:n . '" exists'
-: else
-: echo 'directory "' . a:n . '" does NOT exist'
-: endif
-: endfun
-: func <SID>CheckFile(n)
-: if filereadable(a:n)
-: echo '"' . a:n . '" is readable'
-: else
-: echo '"' . a:n . '" is NOT readable'
-: endif
-: endfun
-: echo "--- Directories and Files ---"
-: echo '$VIM = "' . $VIM . '"'
-: call <SID>CheckDir($VIM)
-: echo '$VIMRUNTIME = "' . $VIMRUNTIME . '"'
-: call <SID>CheckDir($VIMRUNTIME)
-: call <SID>CheckFile(&helpfile)
-: call <SID>CheckFile(fnamemodify(&helpfile, ":h") . "/tags")
-: call <SID>CheckFile($VIMRUNTIME . "/menu.vim")
-: call <SID>CheckFile($VIMRUNTIME . "/filetype.vim")
-: call <SID>CheckFile($VIMRUNTIME . "/syntax/synload.vim")
-: delfun <SID>CheckDir
-: delfun <SID>CheckFile
-: echo "--- Scripts sourced ---"
-: scriptnames
-:endif
-:set all
-:if has("autocmd")
-: au
-:endif
-:if 1
-: echo "--- Normal/Visual mode mappings ---"
-:endif
-:map
-:if 1
-: echo "--- Insert/Command-line mode mappings ---"
-:endif
-:map!
-:if 1
-: echo "--- Abbreviations ---"
-:endif
-:ab
-:if 1
-: echo "--- Highlighting ---"
-:endif
-:highlight
-:if 1
-: echo "--- Variables ---"
-:endif
-:if 1
-: let
-:endif
-:redir END
-:set more&
-:if 1
-: let &more = more_save
-: unlet more_save
-:endif
-:e bugreport.txt
diff --git a/runtime/colors/README.txt b/runtime/colors/README.txt
index 4ea8e5e640..bc6b8f0965 100644
--- a/runtime/colors/README.txt
+++ b/runtime/colors/README.txt
@@ -111,11 +111,11 @@ please check the following items:
- Do not use hard coded escape sequences, these will not work in other
terminals. Always use #RRGGBB for the GUI.
-- When targetting 8-16 colors terminals, don't count on "darkblue" to be blue
+- When targeting 8-16 colors terminals, don't count on "darkblue" to be blue
and dark, or on "2" to be even vaguely reddish. Names are more portable
than numbers, though.
-- When targetting 256 colors terminals, prefer colors 16-255 to colors 0-15
+- When targeting 256 colors terminals, prefer colors 16-255 to colors 0-15
for the same reason.
- Typographic attributes (bold, italic, underline, reverse, etc.) are not
diff --git a/runtime/colors/default.vim b/runtime/colors/default.vim
index 70311571db..d2960fa78b 100644
--- a/runtime/colors/default.vim
+++ b/runtime/colors/default.vim
@@ -1,6 +1,7 @@
" Vim color file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2001 Jul 23
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This is the default color scheme. It doesn't define the Normal
" highlighting, it uses whatever the colors used to be.
diff --git a/runtime/colors/habamax.vim b/runtime/colors/habamax.vim
index f6aa5609b1..c84354ca03 100644
--- a/runtime/colors/habamax.vim
+++ b/runtime/colors/habamax.vim
@@ -4,7 +4,7 @@
" Maintainer: Maxim Kim <habamax@gmail.com>
" Website: https://github.com/vim/colorschemes
" License: Same as Vim
-" Last Updated: Fri 02 Sep 2022 09:45:11 MSK
+" Last Updated: Fri 24 Mar 2023 20:28:06 AEDT
" Generated by Colortemplate v2.2.0
@@ -40,38 +40,6 @@ hi! link vimParenSep Normal
hi! link vimCommentString Comment
hi! link gitCommitSummary Title
hi! link markdownUrl String
-hi! link elixirOperator Statement
-hi! link elixirKeyword Statement
-hi! link elixirBlockDefinition Statement
-hi! link elixirDefine Statement
-hi! link elixirPrivateDefine Statement
-hi! link elixirGuard Statement
-hi! link elixirPrivateGuard Statement
-hi! link elixirModuleDefine Statement
-hi! link elixirProtocolDefine Statement
-hi! link elixirImplDefine Statement
-hi! link elixirRecordDefine Statement
-hi! link elixirPrivateRecordDefine Statement
-hi! link elixirMacroDefine Statement
-hi! link elixirPrivateMacroDefine Statement
-hi! link elixirDelegateDefine Statement
-hi! link elixirOverridableDefine Statement
-hi! link elixirExceptionDefine Statement
-hi! link elixirCallbackDefine Statement
-hi! link elixirStructDefine Statement
-hi! link elixirExUnitMacro Statement
-hi! link elixirInclude Statement
-hi! link elixirAtom PreProc
-hi! link elixirDocTest String
-hi ALEErrorSign guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE
-hi ALEInfoSign guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE
-hi ALEWarningSign guifg=#af87af guibg=NONE gui=NONE cterm=NONE
-hi ALEError guifg=#1c1c1c guibg=#d75f5f gui=NONE cterm=NONE
-hi ALEVirtualTextError guifg=#1c1c1c guibg=#d75f5f gui=NONE cterm=NONE
-hi ALEWarning guifg=#1c1c1c guibg=#af87af gui=NONE cterm=NONE
-hi ALEVirtualTextWarning guifg=#1c1c1c guibg=#af87af gui=NONE cterm=NONE
-hi ALEInfo guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE
-hi ALEVirtualTextInfo guifg=#d7d787 guibg=NONE gui=NONE cterm=NONE
hi Normal guifg=#bcbcbc guibg=#1c1c1c gui=NONE cterm=NONE
hi Statusline guifg=#1c1c1c guibg=#9e9e9e gui=NONE cterm=NONE
hi StatuslineNC guifg=#1c1c1c guibg=#767676 gui=NONE cterm=NONE
@@ -92,10 +60,14 @@ hi SpecialKey guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi FoldColumn guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi Visual guifg=#1c1c1c guibg=#87afaf gui=NONE cterm=NONE
hi VisualNOS guifg=#1c1c1c guibg=#5f8787 gui=NONE cterm=NONE
-hi Pmenu guifg=NONE guibg=#262626 gui=NONE cterm=NONE
+hi Pmenu guifg=NONE guibg=#3a3a3a gui=NONE cterm=NONE
hi PmenuThumb guifg=NONE guibg=#767676 gui=NONE cterm=NONE
hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi PmenuSel guifg=#1c1c1c guibg=#afaf87 gui=NONE cterm=NONE
+hi PmenuKind guifg=#d7875f guibg=#3a3a3a gui=NONE cterm=NONE
+hi PmenuKindSel guifg=#d75f5f guibg=#afaf87 gui=NONE cterm=NONE
+hi PmenuExtra guifg=#767676 guibg=#3a3a3a gui=NONE cterm=NONE
+hi PmenuExtraSel guifg=#1c1c1c guibg=#afaf87 gui=NONE cterm=NONE
hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error guifg=#d75f5f guibg=#1c1c1c gui=reverse cterm=reverse
hi ErrorMsg guifg=#d75f5f guibg=#1c1c1c gui=reverse cterm=reverse
@@ -104,7 +76,7 @@ hi MoreMsg guifg=#87af87 guibg=NONE gui=NONE cterm=NONE
hi Question guifg=#afaf87 guibg=NONE gui=NONE cterm=NONE
hi WarningMsg guifg=#d7875f guibg=NONE gui=NONE cterm=NONE
hi Todo guifg=#d7d787 guibg=#1c1c1c gui=reverse cterm=reverse
-hi MatchParen guifg=#5f8787 guibg=#1c1c1c gui=reverse cterm=reverse
+hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold
hi Search guifg=#1c1c1c guibg=#87af87 gui=NONE cterm=NONE
hi IncSearch guifg=#1c1c1c guibg=#ffaf5f gui=NONE cterm=NONE
hi CurSearch guifg=#1c1c1c guibg=#afaf87 gui=NONE cterm=NONE
@@ -116,7 +88,7 @@ hi lCursor guifg=#1c1c1c guibg=#5fff00 gui=NONE cterm=NONE
hi CursorLine guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi CursorColumn guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi Folded guifg=#9e9e9e guibg=#262626 gui=NONE cterm=NONE
-hi ColorColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE
+hi ColorColumn guifg=NONE guibg=#3a3a3a gui=NONE cterm=NONE
hi SpellBad guifg=NONE guibg=NONE guisp=#d75f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline
hi SpellCap guifg=NONE guibg=NONE guisp=#5f87af gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline
hi SpellLocal guifg=NONE guibg=NONE guisp=#87af87 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=underline
@@ -136,13 +108,13 @@ hi Directory guifg=#87afaf guibg=NONE gui=bold cterm=bold
hi Conceal guifg=#767676 guibg=NONE gui=NONE cterm=NONE
hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi Debug guifg=#5f8787 guibg=NONE gui=NONE cterm=NONE
-hi DiffAdd guifg=#000000 guibg=#87af87 gui=NONE cterm=NONE
+hi DiffAdd guifg=#dadada guibg=#5f875f gui=NONE cterm=NONE
hi DiffDelete guifg=#af875f guibg=NONE gui=NONE cterm=NONE
hi diffAdded guifg=#87af87 guibg=NONE gui=NONE cterm=NONE
hi diffRemoved guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE
hi diffSubname guifg=#af87af guibg=NONE gui=NONE cterm=NONE
-hi DiffText guifg=#000000 guibg=#d7d7d7 gui=NONE cterm=NONE
-hi DiffChange guifg=#000000 guibg=#afafaf gui=NONE cterm=NONE
+hi DiffText guifg=#dadada guibg=#878787 gui=NONE cterm=NONE
+hi DiffChange guifg=#bcbcbc guibg=#5f5f5f gui=NONE cterm=NONE
if s:t_Co >= 256
hi! link Terminal Normal
@@ -163,38 +135,6 @@ if s:t_Co >= 256
hi! link vimCommentString Comment
hi! link gitCommitSummary Title
hi! link markdownUrl String
- hi! link elixirOperator Statement
- hi! link elixirKeyword Statement
- hi! link elixirBlockDefinition Statement
- hi! link elixirDefine Statement
- hi! link elixirPrivateDefine Statement
- hi! link elixirGuard Statement
- hi! link elixirPrivateGuard Statement
- hi! link elixirModuleDefine Statement
- hi! link elixirProtocolDefine Statement
- hi! link elixirImplDefine Statement
- hi! link elixirRecordDefine Statement
- hi! link elixirPrivateRecordDefine Statement
- hi! link elixirMacroDefine Statement
- hi! link elixirPrivateMacroDefine Statement
- hi! link elixirDelegateDefine Statement
- hi! link elixirOverridableDefine Statement
- hi! link elixirExceptionDefine Statement
- hi! link elixirCallbackDefine Statement
- hi! link elixirStructDefine Statement
- hi! link elixirExUnitMacro Statement
- hi! link elixirInclude Statement
- hi! link elixirAtom PreProc
- hi! link elixirDocTest String
- hi ALEErrorSign ctermfg=167 ctermbg=NONE cterm=NONE
- hi ALEInfoSign ctermfg=186 ctermbg=NONE cterm=NONE
- hi ALEWarningSign ctermfg=139 ctermbg=NONE cterm=NONE
- hi ALEError ctermfg=234 ctermbg=167 cterm=NONE
- hi ALEVirtualTextError ctermfg=234 ctermbg=167 cterm=NONE
- hi ALEWarning ctermfg=234 ctermbg=139 cterm=NONE
- hi ALEVirtualTextWarning ctermfg=234 ctermbg=139 cterm=NONE
- hi ALEInfo ctermfg=186 ctermbg=NONE cterm=NONE
- hi ALEVirtualTextInfo ctermfg=186 ctermbg=NONE cterm=NONE
hi Normal ctermfg=250 ctermbg=234 cterm=NONE
hi Statusline ctermfg=234 ctermbg=247 cterm=NONE
hi StatuslineNC ctermfg=234 ctermbg=243 cterm=NONE
@@ -215,10 +155,14 @@ if s:t_Co >= 256
hi FoldColumn ctermfg=240 ctermbg=NONE cterm=NONE
hi Visual ctermfg=234 ctermbg=109 cterm=NONE
hi VisualNOS ctermfg=234 ctermbg=66 cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=235 cterm=NONE
+ hi Pmenu ctermfg=NONE ctermbg=237 cterm=NONE
hi PmenuThumb ctermfg=NONE ctermbg=243 cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
hi PmenuSel ctermfg=234 ctermbg=144 cterm=NONE
+ hi PmenuKind ctermfg=173 ctermbg=237 cterm=NONE
+ hi PmenuKindSel ctermfg=167 ctermbg=144 cterm=NONE
+ hi PmenuExtra ctermfg=243 ctermbg=237 cterm=NONE
+ hi PmenuExtraSel ctermfg=234 ctermbg=144 cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=167 ctermbg=234 cterm=reverse
hi ErrorMsg ctermfg=167 ctermbg=234 cterm=reverse
@@ -227,7 +171,7 @@ if s:t_Co >= 256
hi Question ctermfg=144 ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=173 ctermbg=NONE cterm=NONE
hi Todo ctermfg=186 ctermbg=234 cterm=reverse
- hi MatchParen ctermfg=66 ctermbg=234 cterm=reverse
+ hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold
hi Search ctermfg=234 ctermbg=108 cterm=NONE
hi IncSearch ctermfg=234 ctermbg=215 cterm=NONE
hi CurSearch ctermfg=234 ctermbg=144 cterm=NONE
@@ -237,7 +181,7 @@ if s:t_Co >= 256
hi CursorLine ctermfg=NONE ctermbg=236 cterm=NONE
hi CursorColumn ctermfg=NONE ctermbg=236 cterm=NONE
hi Folded ctermfg=247 ctermbg=235 cterm=NONE
- hi ColorColumn ctermfg=NONE ctermbg=235 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=237 cterm=NONE
hi SpellBad ctermfg=167 ctermbg=NONE cterm=underline
hi SpellCap ctermfg=67 ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=108 ctermbg=NONE cterm=underline
@@ -257,27 +201,18 @@ if s:t_Co >= 256
hi Conceal ctermfg=243 ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi Debug ctermfg=66 ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=16 ctermbg=108 cterm=NONE
+ hi DiffAdd ctermfg=253 ctermbg=65 cterm=NONE
hi DiffDelete ctermfg=137 ctermbg=NONE cterm=NONE
hi diffAdded ctermfg=108 ctermbg=NONE cterm=NONE
hi diffRemoved ctermfg=167 ctermbg=NONE cterm=NONE
hi diffSubname ctermfg=139 ctermbg=NONE cterm=NONE
- hi DiffText ctermfg=16 ctermbg=188 cterm=NONE
- hi DiffChange ctermfg=16 ctermbg=145 cterm=NONE
+ hi DiffText ctermfg=253 ctermbg=102 cterm=NONE
+ hi DiffChange ctermfg=250 ctermbg=59 cterm=NONE
unlet s:t_Co
finish
endif
if s:t_Co >= 16
- hi ALEErrorSign ctermfg=darkred ctermbg=NONE cterm=NONE
- hi ALEInfoSign ctermfg=yellow ctermbg=NONE cterm=NONE
- hi ALEWarningSign ctermfg=darkmagenta ctermbg=NONE cterm=NONE
- hi ALEError ctermfg=black ctermbg=darkred cterm=NONE
- hi ALEVirtualTextError ctermfg=black ctermbg=darkred cterm=NONE
- hi ALEWarning ctermfg=black ctermbg=darkmagenta cterm=NONE
- hi ALEVirtualTextWarning ctermfg=black ctermbg=darkmagenta cterm=NONE
- hi ALEInfo ctermfg=yellow ctermbg=NONE cterm=NONE
- hi ALEVirtualTextInfo ctermfg=yellow ctermbg=NONE cterm=NONE
hi Normal ctermfg=white ctermbg=black cterm=NONE
hi Statusline ctermfg=black ctermbg=gray cterm=NONE
hi StatuslineNC ctermfg=black ctermbg=darkgray cterm=NONE
@@ -298,10 +233,14 @@ if s:t_Co >= 16
hi FoldColumn ctermfg=darkgrey ctermbg=NONE cterm=NONE
hi Visual ctermfg=black ctermbg=cyan cterm=NONE
hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=darkgrey cterm=NONE
- hi PmenuThumb ctermfg=NONE ctermbg=darkgray cterm=NONE
- hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=gray cterm=NONE
+ hi PmenuThumb ctermfg=gray ctermbg=black cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=gray cterm=NONE
hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=gray cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=darkgray ctermbg=gray cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=darkred ctermbg=black cterm=reverse
hi ErrorMsg ctermfg=darkred ctermbg=black cterm=reverse
@@ -310,7 +249,7 @@ if s:t_Co >= 16
hi Question ctermfg=darkyellow ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=red ctermbg=NONE cterm=NONE
hi Todo ctermfg=yellow ctermbg=black cterm=reverse
- hi MatchParen ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi MatchParen ctermfg=magenta ctermbg=NONE cterm=bold
hi Search ctermfg=black ctermbg=darkgreen cterm=NONE
hi IncSearch ctermfg=black ctermbg=red cterm=NONE
hi CurSearch ctermfg=black ctermbg=darkyellow cterm=NONE
@@ -340,13 +279,13 @@ if s:t_Co >= 16
hi Conceal ctermfg=darkgray ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi Debug ctermfg=darkcyan ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=black ctermbg=darkgreen cterm=NONE
+ hi DiffAdd ctermfg=white ctermbg=darkgreen cterm=NONE
hi DiffDelete ctermfg=darkyellow ctermbg=NONE cterm=NONE
hi diffAdded ctermfg=darkgreen ctermbg=NONE cterm=NONE
hi diffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE
hi diffSubname ctermfg=darkmagenta ctermbg=NONE cterm=NONE
- hi DiffText ctermfg=black ctermbg=lightgrey cterm=NONE
- hi DiffChange ctermfg=black ctermbg=darkgray cterm=NONE
+ hi DiffText ctermfg=white ctermbg=lightgrey cterm=NONE
+ hi DiffChange ctermfg=white ctermbg=darkgray cterm=NONE
unlet s:t_Co
finish
endif
@@ -370,12 +309,16 @@ if s:t_Co >= 8
hi EndOfBuffer ctermfg=gray ctermbg=NONE cterm=bold
hi SpecialKey ctermfg=gray ctermbg=NONE cterm=bold
hi FoldColumn ctermfg=gray ctermbg=NONE cterm=bold
- hi Visual ctermfg=NONE ctermbg=NONE cterm=reverse
- hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi VisualNOS ctermfg=black ctermbg=darkcyan cterm=NONE
hi Pmenu ctermfg=black ctermbg=gray cterm=NONE
hi PmenuThumb ctermfg=gray ctermbg=black cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=gray cterm=NONE
hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=gray cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=black ctermbg=gray cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=darkred ctermbg=gray cterm=bold,reverse
hi ErrorMsg ctermfg=darkred ctermbg=gray cterm=bold,reverse
@@ -384,7 +327,7 @@ if s:t_Co >= 8
hi Question ctermfg=darkyellow ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=darkred ctermbg=NONE cterm=NONE
hi Todo ctermfg=darkyellow ctermbg=black cterm=reverse
- hi MatchParen ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi MatchParen ctermfg=magenta ctermbg=NONE cterm=bold
hi Search ctermfg=black ctermbg=darkgreen cterm=NONE
hi IncSearch ctermfg=black ctermbg=darkyellow cterm=NONE
hi CurSearch ctermfg=black ctermbg=darkyellow cterm=NONE
@@ -414,7 +357,7 @@ if s:t_Co >= 8
hi Conceal ctermfg=gray ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi Debug ctermfg=darkcyan ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=black ctermbg=darkgreen cterm=NONE
+ hi DiffAdd ctermfg=white ctermbg=darkgreen cterm=NONE
hi DiffDelete ctermfg=darkyellow ctermbg=NONE cterm=NONE
hi diffAdded ctermfg=darkgreen ctermbg=NONE cterm=NONE
hi diffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE
@@ -511,17 +454,19 @@ endif
" Color: color07 #9E9E9E 247 gray
" Color: color15 #BCBCBC 250 white
" Color: colorLine #303030 236 darkgrey
-" Color: colorB #262626 235 darkgrey
+" Color: colorB #3a3a3a 237 darkgrey
+" Color: colorF #262626 235 darkgrey
" Color: colorNonT #585858 240 darkgrey
" Color: colorC #FFAF5F 215 red
" Color: colorlC #5FFF00 82 green
" Color: colorV #1F3F5F 109 cyan
-" Color: diffAdd #87AF87 108 darkgreen
+" Color: colorMP #ff00af 199 magenta
+" Color: diffAdd #5f875f 65 darkgreen
" Color: diffDelete #af875f 137 darkyellow
-" Color: diffChange #AFAFAF 145 darkgray
-" Color: diffText #D7D7D7 188 lightgrey
+" Color: diffChange #5f5f5f 59 darkgray
+" Color: diffText #878787 102 lightgrey
" Color: black #000000 16 black
-" Color: white #FFFFFF 231 white
+" Color: white #dadada 253 white
" Term colors: color00 color01 color02 color03 color04 color05 color06 color07
" Term colors: color08 color09 color10 color11 color12 color13 color14 color15
" vim: et ts=2 sw=2
diff --git a/runtime/colors/lunaperche.vim b/runtime/colors/lunaperche.vim
index 2954f622aa..44c75c40cb 100644
--- a/runtime/colors/lunaperche.vim
+++ b/runtime/colors/lunaperche.vim
@@ -4,7 +4,7 @@
" Maintainer: Maxim Kim <habamax@gmail.com>
" Website: https://www.github.com/vim/colorschemes
" License: Vim License (see `:help license`)
-" Last Updated: Fri 16 Sep 2022 13:15:33 MSK
+" Last Updated: Sun 26 Mar 2023 23:04:18 AEDT
" Generated by Colortemplate v2.2.0
@@ -16,24 +16,8 @@ let s:t_Co = &t_Co
hi! link helpVim Title
hi! link helpHeader Title
hi! link helpHyperTextJump Underlined
-hi! link fugitiveSymbolicRef PreProc
-hi! link fugitiveHeading Statement
-hi! link fugitiveStagedHeading Statement
-hi! link fugitiveUnstagedHeading Statement
-hi! link fugitiveUntrackedHeading Statement
-hi! link fugitiveStagedModifier PreProc
-hi! link fugitiveUnstagedModifier PreProc
-hi! link fugitiveHash Constant
hi! link diffFile PreProc
hi! link markdownHeadingDelimiter Special
-hi! link rstSectionDelimiter Statement
-hi! link rstDirective PreProc
-hi! link rstHyperlinkReference Special
-hi! link rstFieldName Constant
-hi! link rstDelimiter Special
-hi! link rstInterpretedText Special
-hi! link rstCodeBlock Normal
-hi! link rstLiteralBlock rstCodeBlock
hi! link markdownUrl String
hi! link colortemplateKey Statement
hi! link xmlTagName Statement
@@ -51,30 +35,6 @@ hi! link vimParenSep Normal
hi! link vimOption Normal
hi! link vimCommentString Comment
hi! link pythonInclude Statement
-hi! link elixirOperator Statement
-hi! link elixirKeyword Statement
-hi! link elixirBlockDefinition Statement
-hi! link elixirDefine Statement
-hi! link elixirPrivateDefine Statement
-hi! link elixirGuard Statement
-hi! link elixirPrivateGuard Statement
-hi! link elixirModuleDefine Statement
-hi! link elixirProtocolDefine Statement
-hi! link elixirImplDefine Statement
-hi! link elixirRecordDefine Statement
-hi! link elixirPrivateRecordDefine Statement
-hi! link elixirMacroDefine Statement
-hi! link elixirPrivateMacroDefine Statement
-hi! link elixirDelegateDefine Statement
-hi! link elixirOverridableDefine Statement
-hi! link elixirExceptionDefine Statement
-hi! link elixirCallbackDefine Statement
-hi! link elixirStructDefine Statement
-hi! link elixirExUnitMacro Statement
-hi! link elixirInclude Statement
-hi! link elixirVariable Special
-hi! link elixirAtom Constant
-hi! link elixirDocTest String
hi! link shQuote Constant
hi! link shNoQuote Normal
hi! link shTestOpr Normal
@@ -138,12 +98,16 @@ if &background ==# 'dark'
hi LineNr guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi NonText guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi FoldColumn guifg=#585858 guibg=NONE gui=NONE cterm=NONE
- hi EndOfBuffer guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi SpecialKey guifg=#585858 guibg=NONE gui=NONE cterm=NONE
- hi Pmenu guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE
- hi PmenuSel guifg=NONE guibg=#444444 gui=NONE cterm=NONE
+ hi EndOfBuffer guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=NONE guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuSel guifg=NONE guibg=#4e4e4e gui=NONE cterm=NONE
hi PmenuThumb guifg=NONE guibg=#c6c6c6 gui=NONE cterm=NONE
hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind guifg=#ff5f5f guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#ff5f5f guibg=#4e4e4e gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#767676 guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#767676 guibg=#4e4e4e gui=NONE cterm=NONE
hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error guifg=#ffffff guibg=#ff5f5f gui=NONE cterm=NONE
hi ErrorMsg guifg=#ffffff guibg=#ff5f5f gui=NONE cterm=NONE
@@ -165,8 +129,8 @@ if &background ==# 'dark'
hi VisualNOS guifg=#000000 guibg=#5fafaf gui=NONE cterm=NONE
hi CursorLine guifg=NONE guibg=#262626 gui=NONE cterm=NONE
hi CursorColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE
- hi Folded guifg=#767676 guibg=#1c1c1c gui=NONE cterm=NONE
- hi ColorColumn guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE
+ hi Folded guifg=#767676 guibg=#303030 gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi SpellBad guifg=NONE guibg=NONE guisp=#ff5f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
hi SpellCap guifg=NONE guibg=NONE guisp=#5fafaf gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
hi SpellLocal guifg=NONE guibg=NONE guisp=#5faf5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -184,9 +148,9 @@ if &background ==# 'dark'
hi Directory guifg=#5fafff guibg=NONE gui=bold cterm=bold
hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
- hi DiffAdd guifg=#000000 guibg=#af87af gui=NONE cterm=NONE
- hi DiffChange guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE
- hi DiffText guifg=#000000 guibg=#5fd7d7 gui=NONE cterm=NONE
+ hi DiffAdd guifg=#c6c6c6 guibg=#875f87 gui=NONE cterm=NONE
+ hi DiffChange guifg=#c6c6c6 guibg=#5f5f5f gui=NONE cterm=NONE
+ hi DiffText guifg=#afffff guibg=#5f8787 gui=NONE cterm=NONE
hi DiffDelete guifg=#d78787 guibg=NONE gui=NONE cterm=NONE
hi diffAdded guifg=#5fd75f guibg=NONE gui=NONE cterm=NONE
hi diffRemoved guifg=#d78787 guibg=NONE gui=NONE cterm=NONE
@@ -230,10 +194,14 @@ else
hi FoldColumn guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE
hi EndOfBuffer guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE
hi SpecialKey guifg=#9e9e9e guibg=NONE gui=NONE cterm=NONE
- hi Pmenu guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi Pmenu guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
hi PmenuSel guifg=NONE guibg=#c6c6c6 gui=NONE cterm=NONE
hi PmenuThumb guifg=NONE guibg=#767676 gui=NONE cterm=NONE
hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind guifg=#af0000 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#af0000 guibg=#c6c6c6 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#767676 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#767676 guibg=#c6c6c6 gui=NONE cterm=NONE
hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE
hi ErrorMsg guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE
@@ -253,10 +221,10 @@ else
hi Visual guifg=#ffffff guibg=#5f87af gui=NONE cterm=NONE
hi MatchParen guifg=NONE guibg=#c5e7c5 gui=NONE cterm=NONE
hi VisualNOS guifg=#ffffff guibg=#008787 gui=NONE cterm=NONE
- hi CursorLine guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
- hi CursorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
- hi Folded guifg=#767676 guibg=#eeeeee gui=NONE cterm=NONE
- hi ColorColumn guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi CursorLine guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi CursorColumn guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi Folded guifg=#767676 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
hi SpellBad guifg=NONE guibg=NONE guisp=#af0000 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
hi SpellCap guifg=NONE guibg=NONE guisp=#005f5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
hi SpellLocal guifg=NONE guibg=NONE guisp=#008700 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -302,24 +270,8 @@ if s:t_Co >= 256
hi! link helpVim Title
hi! link helpHeader Title
hi! link helpHyperTextJump Underlined
- hi! link fugitiveSymbolicRef PreProc
- hi! link fugitiveHeading Statement
- hi! link fugitiveStagedHeading Statement
- hi! link fugitiveUnstagedHeading Statement
- hi! link fugitiveUntrackedHeading Statement
- hi! link fugitiveStagedModifier PreProc
- hi! link fugitiveUnstagedModifier PreProc
- hi! link fugitiveHash Constant
hi! link diffFile PreProc
hi! link markdownHeadingDelimiter Special
- hi! link rstSectionDelimiter Statement
- hi! link rstDirective PreProc
- hi! link rstHyperlinkReference Special
- hi! link rstFieldName Constant
- hi! link rstDelimiter Special
- hi! link rstInterpretedText Special
- hi! link rstCodeBlock Normal
- hi! link rstLiteralBlock rstCodeBlock
hi! link markdownUrl String
hi! link colortemplateKey Statement
hi! link xmlTagName Statement
@@ -337,30 +289,6 @@ if s:t_Co >= 256
hi! link vimOption Normal
hi! link vimCommentString Comment
hi! link pythonInclude Statement
- hi! link elixirOperator Statement
- hi! link elixirKeyword Statement
- hi! link elixirBlockDefinition Statement
- hi! link elixirDefine Statement
- hi! link elixirPrivateDefine Statement
- hi! link elixirGuard Statement
- hi! link elixirPrivateGuard Statement
- hi! link elixirModuleDefine Statement
- hi! link elixirProtocolDefine Statement
- hi! link elixirImplDefine Statement
- hi! link elixirRecordDefine Statement
- hi! link elixirPrivateRecordDefine Statement
- hi! link elixirMacroDefine Statement
- hi! link elixirPrivateMacroDefine Statement
- hi! link elixirDelegateDefine Statement
- hi! link elixirOverridableDefine Statement
- hi! link elixirExceptionDefine Statement
- hi! link elixirCallbackDefine Statement
- hi! link elixirStructDefine Statement
- hi! link elixirExUnitMacro Statement
- hi! link elixirInclude Statement
- hi! link elixirVariable Special
- hi! link elixirAtom Constant
- hi! link elixirDocTest String
hi! link shQuote Constant
hi! link shNoQuote Normal
hi! link shTestOpr Normal
@@ -417,12 +345,16 @@ if s:t_Co >= 256
hi LineNr ctermfg=240 ctermbg=NONE cterm=NONE
hi NonText ctermfg=240 ctermbg=NONE cterm=NONE
hi FoldColumn ctermfg=240 ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=240 ctermbg=NONE cterm=NONE
hi SpecialKey ctermfg=240 ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=234 cterm=NONE
- hi PmenuSel ctermfg=NONE ctermbg=238 cterm=NONE
+ hi EndOfBuffer ctermfg=240 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=NONE ctermbg=236 cterm=NONE
+ hi PmenuSel ctermfg=NONE ctermbg=239 cterm=NONE
hi PmenuThumb ctermfg=NONE ctermbg=251 cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind ctermfg=203 ctermbg=236 cterm=NONE
+ hi PmenuKindSel ctermfg=203 ctermbg=239 cterm=NONE
+ hi PmenuExtra ctermfg=243 ctermbg=236 cterm=NONE
+ hi PmenuExtraSel ctermfg=243 ctermbg=239 cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=231 ctermbg=203 cterm=NONE
hi ErrorMsg ctermfg=231 ctermbg=203 cterm=NONE
@@ -442,8 +374,8 @@ if s:t_Co >= 256
hi VisualNOS ctermfg=16 ctermbg=73 cterm=NONE
hi CursorLine ctermfg=NONE ctermbg=235 cterm=NONE
hi CursorColumn ctermfg=NONE ctermbg=235 cterm=NONE
- hi Folded ctermfg=243 ctermbg=234 cterm=NONE
- hi ColorColumn ctermfg=NONE ctermbg=234 cterm=NONE
+ hi Folded ctermfg=243 ctermbg=236 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=236 cterm=NONE
hi SpellBad ctermfg=203 ctermbg=NONE cterm=underline
hi SpellCap ctermfg=73 ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=77 ctermbg=NONE cterm=underline
@@ -461,9 +393,9 @@ if s:t_Co >= 256
hi Directory ctermfg=75 ctermbg=NONE cterm=bold
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=16 ctermbg=139 cterm=NONE
- hi DiffChange ctermfg=16 ctermbg=252 cterm=NONE
- hi DiffText ctermfg=16 ctermbg=80 cterm=NONE
+ hi DiffAdd ctermfg=251 ctermbg=96 cterm=NONE
+ hi DiffChange ctermfg=251 ctermbg=59 cterm=NONE
+ hi DiffText ctermfg=159 ctermbg=66 cterm=NONE
hi DiffDelete ctermfg=174 ctermbg=NONE cterm=NONE
hi diffAdded ctermfg=77 ctermbg=NONE cterm=NONE
hi diffRemoved ctermfg=174 ctermbg=NONE cterm=NONE
@@ -500,10 +432,14 @@ if s:t_Co >= 256
hi FoldColumn ctermfg=247 ctermbg=NONE cterm=NONE
hi EndOfBuffer ctermfg=247 ctermbg=NONE cterm=NONE
hi SpecialKey ctermfg=247 ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=255 cterm=NONE
+ hi Pmenu ctermfg=NONE ctermbg=254 cterm=NONE
hi PmenuSel ctermfg=NONE ctermbg=251 cterm=NONE
hi PmenuThumb ctermfg=NONE ctermbg=243 cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind ctermfg=124 ctermbg=254 cterm=NONE
+ hi PmenuKindSel ctermfg=124 ctermbg=251 cterm=NONE
+ hi PmenuExtra ctermfg=243 ctermbg=254 cterm=NONE
+ hi PmenuExtraSel ctermfg=243 ctermbg=251 cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=231 ctermbg=160 cterm=NONE
hi ErrorMsg ctermfg=231 ctermbg=160 cterm=NONE
@@ -521,10 +457,10 @@ if s:t_Co >= 256
hi Visual ctermfg=231 ctermbg=67 cterm=NONE
hi MatchParen ctermfg=30 ctermbg=231 cterm=reverse
hi VisualNOS ctermfg=231 ctermbg=30 cterm=NONE
- hi CursorLine ctermfg=NONE ctermbg=254 cterm=NONE
- hi CursorColumn ctermfg=NONE ctermbg=254 cterm=NONE
- hi Folded ctermfg=243 ctermbg=255 cterm=NONE
- hi ColorColumn ctermfg=NONE ctermbg=255 cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=255 cterm=NONE
+ hi CursorColumn ctermfg=NONE ctermbg=255 cterm=NONE
+ hi Folded ctermfg=243 ctermbg=254 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=254 cterm=NONE
hi SpellBad ctermfg=124 ctermbg=NONE cterm=underline
hi SpellCap ctermfg=23 ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=28 ctermbg=NONE cterm=underline
@@ -585,12 +521,16 @@ if s:t_Co >= 16
hi LineNr ctermfg=grey ctermbg=NONE cterm=NONE
hi NonText ctermfg=grey ctermbg=NONE cterm=NONE
hi FoldColumn ctermfg=grey ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=grey ctermbg=NONE cterm=NONE
hi SpecialKey ctermfg=grey ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=black ctermbg=darkgrey cterm=NONE
- hi PmenuSel ctermfg=black ctermbg=darkcyan cterm=NONE
- hi PmenuThumb ctermfg=NONE ctermbg=grey cterm=NONE
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkcyan cterm=NONE
+ hi PmenuExtra ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkcyan cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=white ctermbg=red cterm=NONE
hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE
@@ -629,8 +569,8 @@ if s:t_Co >= 16
hi Directory ctermfg=blue ctermbg=NONE cterm=bold
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
- hi DiffChange ctermfg=black ctermbg=lightgray cterm=NONE
+ hi DiffAdd ctermfg=white ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=white ctermbg=darkgreen cterm=NONE
hi DiffText ctermfg=black ctermbg=cyan cterm=NONE
hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE
hi diffAdded ctermfg=green ctermbg=NONE cterm=NONE
@@ -669,9 +609,13 @@ if s:t_Co >= 16
hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=NONE
hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
- hi PmenuSel ctermfg=black ctermbg=darkcyan cterm=NONE
- hi PmenuThumb ctermfg=NONE ctermbg=darkgrey cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkcyan cterm=NONE
+ hi PmenuExtra ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkcyan cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=white ctermbg=red cterm=NONE
hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE
@@ -751,14 +695,18 @@ if s:t_Co >= 8
hi QuickFixLine ctermfg=grey ctermbg=darkblue cterm=bold
hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold
hi LineNr ctermfg=darkyellow ctermbg=NONE cterm=NONE
- hi NonText ctermfg=black ctermbg=NONE cterm=NONE
- hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=black ctermbg=NONE cterm=NONE
- hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=grey cterm=NONE
+ hi NonText ctermfg=grey ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=grey ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=grey ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=grey ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
hi PmenuSel ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkcyan cterm=NONE
+ hi PmenuExtra ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkcyan cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=grey ctermbg=darkred cterm=NONE
hi ErrorMsg ctermfg=grey ctermbg=darkred cterm=NONE
@@ -796,8 +744,8 @@ if s:t_Co >= 8
hi Directory ctermfg=darkblue ctermbg=NONE cterm=bold
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
- hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
- hi DiffChange ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi DiffAdd ctermfg=white ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=white ctermbg=darkgreen cterm=NONE
hi DiffText ctermfg=black ctermbg=grey cterm=NONE
hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE
else
@@ -818,10 +766,14 @@ if s:t_Co >= 8
hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE
hi EndOfBuffer ctermfg=black ctermbg=NONE cterm=NONE
hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=NONE ctermbg=black cterm=NONE
+ hi Pmenu ctermfg=grey ctermbg=black cterm=NONE
hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
hi PmenuSel ctermfg=NONE ctermbg=darkcyan cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=black cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkcyan cterm=NONE
+ hi PmenuExtra ctermfg=grey ctermbg=black cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkcyan cterm=NONE
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
hi Error ctermfg=grey ctermbg=darkred cterm=NONE
hi ErrorMsg ctermfg=grey ctermbg=darkred cterm=NONE
@@ -956,20 +908,21 @@ endif
" Color: color15 #FFFFFF 231 white
" Color: colorDimWhite #E4E4E4 254 grey
" Color: colorLine #262626 235 darkgrey
-" Color: colorB #1C1C1C 234 darkgrey
+" Color: colorB #303030 236 darkgrey
" Color: colorNonT #585858 240 grey
" Color: colorTab #585858 240 grey
" Color: colorC #FFFFFF 231 white
" Color: colorlC #FF5FFF 207 magenta
" Color: colorV #005F87 24 darkblue
" Color: colorMP #C5E7C5 30 darkcyan
-" Color: colorPMenuSel #444444 238 darkcyan
+" Color: colorPMenuSel #4e4e4e 239 darkcyan
" Color: colorDim #878787 102 grey
-" Color: diffAdd #AF87AF 139 darkmagenta
+" Color: diffAdd #875f87 96 darkmagenta
" Color: diffDelete #D78787 174 darkred
-" Color: diffChange #D0D0D0 252 lightgray
-" Color: diffText #5FD7D7 80 cyan
-" Color: fgDiff #000000 16 black
+" Color: diffChange #5f5f5f 59 darkgreen
+" Color: diffText #5f8787 66 cyan
+" Color: fgDiffText #afffff 159 black
+" Color: fgDiff #C6C6C6 251 white
" Term colors: color00 color01 color02 color03 color04 color05 color06 color07
" Term colors: color08 color09 color10 color11 color12 color13 color14 color15
" Background: light
@@ -989,8 +942,8 @@ endif
" Color: color14 #008787 30 cyan
" Color: color07 #808080 244 grey
" Color: color15 #FFFFFF 231 white
-" Color: colorLine #E4E4E4 254 grey
-" Color: colorB #EEEEEE 255 grey
+" Color: colorLine #EEEEEE 255 grey
+" Color: colorB #E4E4E4 254 grey
" Color: colorNonT #9E9E9E 247 darkgrey
" Color: colorTab #BCBCBC 250 lightgrey
" Color: colorC #000000 16 black
diff --git a/runtime/colors/quiet.vim b/runtime/colors/quiet.vim
index d286839250..9ab313b837 100644
--- a/runtime/colors/quiet.vim
+++ b/runtime/colors/quiet.vim
@@ -1,10 +1,10 @@
" Name: quiet
-" Description: `monochrome`, but less ugly, with diffs, searches, a few other niceties, and both light and dark versions.
-" Author: neutaaaaan <neutaaaaan-gh@protonmail.com>
-" Maintainer: neutaaaaan <neutaaaaan-gh@protonmail.com>
+" Description: A mostly monochrome colorscheme, with a few niceties.
+" Author: Maxence Weynans <neutaaaaan@gmail.com>
+" Maintainer: Maxence Weynans <neutaaaaan@gmail.com>
" Website: https://github.com/vim/colorschemes
" License: Vim License (see `:help license`)`
-" Last Updated: Fri 16 Sep 2022 09:52:50 MSK
+" Last Updated: Wed 15 Mar 2023 06:45:06 PM CET
" Generated by Colortemplate v2.2.0
@@ -48,61 +48,66 @@ hi! link debugPC CursorLine
if &background ==# 'dark'
if (has('termguicolors') && &termguicolors) || has('gui_running')
- let g:terminal_ansi_colors = ['#080808', '#d7005f', '#00af5f', '#d78700', '#0087d7', '#d787d7', '#00afaf', '#dadada', '#707070', '#ff005f', '#00d75f', '#ffaf00', '#5fafff', '#ff87ff', '#00d7d7', '#ffffff']
+ let g:terminal_ansi_colors = ['#000000', '#d7005f', '#00af5f', '#d78700', '#0087d7', '#d787d7', '#00afaf', '#dadada', '#707070', '#ff005f', '#00d75f', '#ffaf00', '#5fafff', '#ff87ff', '#00d7d7', '#ffffff']
" Nvim uses g:terminal_color_{0-15} instead
for i in range(g:terminal_ansi_colors->len())
let g:terminal_color_{i} = g:terminal_ansi_colors[i]
endfor
endif
- hi Normal guifg=#dadada guibg=#080808 gui=NONE cterm=NONE
+ hi Normal guifg=#dadada guibg=#000000 gui=NONE cterm=NONE
hi ColorColumn guifg=NONE guibg=#1c1c1c gui=NONE cterm=NONE
hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CurSearch guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
hi CursorColumn guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi CursorLine guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi CursorLineNr guifg=#dadada guibg=#303030 gui=NONE cterm=NONE
- hi DiffAdd guifg=#00af00 guibg=#080808 gui=reverse cterm=reverse
- hi DiffChange guifg=#87afd7 guibg=#080808 gui=reverse cterm=reverse
- hi DiffDelete guifg=#d75f5f guibg=#080808 gui=reverse cterm=reverse
- hi DiffText guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse
+ hi DiffAdd guifg=#00af00 guibg=#000000 gui=reverse cterm=reverse
+ hi DiffChange guifg=#87afd7 guibg=#000000 gui=reverse cterm=reverse
+ hi DiffDelete guifg=#d75f5f guibg=#000000 gui=reverse cterm=reverse
+ hi DiffText guifg=#d787d7 guibg=#000000 gui=reverse cterm=reverse
hi Directory guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi EndOfBuffer guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi ErrorMsg guifg=#dadada guibg=#080808 gui=reverse cterm=reverse
+ hi EndOfBuffer guifg=#707070 guibg=NONE gui=NONE cterm=NONE
+ hi ErrorMsg guifg=#dadada guibg=#000000 gui=reverse cterm=reverse
hi FoldColumn guifg=#707070 guibg=NONE gui=NONE cterm=NONE
- hi Folded guifg=#707070 guibg=#080808 gui=NONE cterm=NONE
- hi IncSearch guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse
- hi LineNr guifg=#444444 guibg=NONE gui=NONE cterm=NONE
+ hi Folded guifg=#707070 guibg=#000000 gui=NONE cterm=NONE
+ hi IncSearch guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
+ hi LineNr guifg=#585858 guibg=NONE gui=NONE cterm=NONE
hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold
hi ModeMsg guifg=#dadada guibg=NONE gui=bold cterm=bold
hi MoreMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE
hi NonText guifg=#707070 guibg=NONE gui=NONE cterm=NONE
- hi Pmenu guifg=#080808 guibg=#87afd7 gui=NONE cterm=NONE
- hi PmenuSbar guifg=#dadada guibg=#707070 gui=NONE cterm=NONE
- hi PmenuSel guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE
- hi PmenuThumb guifg=#dadada guibg=#d787d7 gui=NONE cterm=NONE
+ hi Pmenu guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi PmenuKind guifg=#000000 guibg=#a8a8a8 gui=bold cterm=bold
+ hi PmenuSbar guifg=#707070 guibg=#585858 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#000000 guibg=#dadada gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#000000 guibg=#dadada gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#000000 guibg=#dadada gui=bold cterm=bold
+ hi PmenuThumb guifg=#dadada guibg=#dadada gui=NONE cterm=NONE
hi Question guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi QuickFixLine guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse
- hi Search guifg=#00afff guibg=#080808 gui=reverse cterm=reverse
+ hi QuickFixLine guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
+ hi Search guifg=#00afff guibg=#000000 gui=reverse cterm=reverse
hi SignColumn guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi SpecialKey guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+ hi SpecialKey guifg=#707070 guibg=NONE gui=bold cterm=bold
hi SpellBad guifg=#d7005f guibg=NONE guisp=#d7005f gui=undercurl cterm=underline
hi SpellCap guifg=#0087d7 guibg=NONE guisp=#0087d7 gui=undercurl cterm=underline
hi SpellLocal guifg=#d787d7 guibg=NONE guisp=#d787d7 gui=undercurl cterm=underline
hi SpellRare guifg=#00afaf guibg=NONE guisp=#00afaf gui=undercurl cterm=underline
- hi StatusLine guifg=#080808 guibg=#dadada gui=bold cterm=bold
- hi StatusLineNC guifg=#707070 guibg=#080808 gui=reverse cterm=reverse
- hi TabLine guifg=#707070 guibg=#080808 gui=reverse cterm=reverse
+ hi StatusLine guifg=#000000 guibg=#dadada gui=bold cterm=bold
+ hi StatusLineNC guifg=#707070 guibg=#000000 gui=reverse cterm=reverse
+ hi TabLine guifg=#707070 guibg=#000000 gui=reverse cterm=reverse
hi TabLineFill guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi TabLineSel guifg=#080808 guibg=#dadada gui=bold cterm=bold
+ hi TabLineSel guifg=#000000 guibg=#dadada gui=bold cterm=bold
hi Title guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit guifg=#707070 guibg=#080808 gui=NONE cterm=NONE
- hi Visual guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse
+ hi VertSplit guifg=#707070 guibg=#000000 gui=NONE cterm=NONE
+ hi Visual guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
hi VisualNOS guifg=NONE guibg=#303030 gui=NONE cterm=NONE
hi WarningMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi WildMenu guifg=#00afff guibg=#080808 gui=bold cterm=bold
+ hi WildMenu guifg=#00afff guibg=#000000 gui=bold cterm=bold
hi Comment guifg=#707070 guibg=NONE gui=bold cterm=bold
hi Constant guifg=#dadada guibg=NONE gui=NONE cterm=NONE
- hi Error guifg=#ff005f guibg=#080808 gui=bold,reverse cterm=bold,reverse
+ hi Error guifg=#ff005f guibg=#000000 gui=bold,reverse cterm=bold,reverse
hi Identifier guifg=#dadada guibg=NONE gui=NONE cterm=NONE
hi Ignore guifg=#dadada guibg=NONE gui=NONE cterm=NONE
hi PreProc guifg=#dadada guibg=NONE gui=NONE cterm=NONE
@@ -111,131 +116,141 @@ if &background ==# 'dark'
hi Todo guifg=#00ffaf guibg=NONE gui=bold,reverse cterm=bold,reverse
hi Type guifg=#dadada guibg=NONE gui=NONE cterm=NONE
hi Underlined guifg=#dadada guibg=NONE gui=underline cterm=underline
- hi CursorIM guifg=#080808 guibg=#afff00 gui=NONE cterm=NONE
- hi ToolbarLine guifg=NONE guibg=#080808 gui=NONE cterm=NONE
- hi ToolbarButton guifg=#dadada guibg=#080808 gui=bold cterm=bold
+ hi CursorIM guifg=#000000 guibg=#afff00 gui=NONE cterm=NONE
+ hi ToolbarLine guifg=NONE guibg=#000000 gui=NONE cterm=NONE
+ hi ToolbarButton guifg=#dadada guibg=#000000 gui=bold cterm=bold
else
" Light background
if (has('termguicolors') && &termguicolors) || has('gui_running')
- let g:terminal_ansi_colors = ['#080808', '#af0000', '#005f00', '#af5f00', '#005faf', '#870087', '#008787', '#d7d7d7', '#626262', '#d70000', '#008700', '#d78700', '#0087d7', '#af00af', '#00afaf', '#ffffff']
+ let g:terminal_ansi_colors = ['#000000', '#af0000', '#005f00', '#af5f00', '#005faf', '#870087', '#008787', '#d7d7d7', '#626262', '#d70000', '#008700', '#d78700', '#0087d7', '#af00af', '#00afaf', '#ffffff']
" Nvim uses g:terminal_color_{0-15} instead
for i in range(g:terminal_ansi_colors->len())
let g:terminal_color_{i} = g:terminal_ansi_colors[i]
endfor
endif
- hi Normal guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE
+ hi Normal guifg=#000000 guibg=#d7d7d7 gui=NONE cterm=NONE
hi ColorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CurSearch guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
hi CursorColumn guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
hi CursorLine guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
- hi CursorLineNr guifg=#080808 guibg=#eeeeee gui=NONE cterm=NONE
- hi DiffAdd guifg=#87d787 guibg=#080808 gui=reverse cterm=reverse
- hi DiffChange guifg=#afafd7 guibg=#080808 gui=reverse cterm=reverse
- hi DiffDelete guifg=#d78787 guibg=#080808 gui=reverse cterm=reverse
- hi DiffText guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse
- hi Directory guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi EndOfBuffer guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi ErrorMsg guifg=#080808 guibg=#d7d7d7 gui=reverse cterm=reverse
+ hi CursorLineNr guifg=#000000 guibg=#eeeeee gui=NONE cterm=NONE
+ hi DiffAdd guifg=#87d787 guibg=#000000 gui=reverse cterm=reverse
+ hi DiffChange guifg=#afafd7 guibg=#000000 gui=reverse cterm=reverse
+ hi DiffDelete guifg=#d78787 guibg=#000000 gui=reverse cterm=reverse
+ hi DiffText guifg=#d787d7 guibg=#000000 gui=reverse cterm=reverse
+ hi Directory guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi EndOfBuffer guifg=#626262 guibg=NONE gui=NONE cterm=NONE
+ hi ErrorMsg guifg=#000000 guibg=#d7d7d7 gui=reverse cterm=reverse
hi FoldColumn guifg=#626262 guibg=NONE gui=NONE cterm=NONE
hi Folded guifg=#626262 guibg=#d7d7d7 gui=NONE cterm=NONE
- hi IncSearch guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse
+ hi IncSearch guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
hi LineNr guifg=#a8a8a8 guibg=NONE gui=NONE cterm=NONE
hi MatchParen guifg=#ff00af guibg=#d7d7d7 gui=bold cterm=bold
- hi ModeMsg guifg=#080808 guibg=NONE gui=bold cterm=bold
- hi MoreMsg guifg=#080808 guibg=NONE gui=NONE cterm=NONE
+ hi ModeMsg guifg=#000000 guibg=NONE gui=bold cterm=bold
+ hi MoreMsg guifg=#000000 guibg=NONE gui=NONE cterm=NONE
hi NonText guifg=#626262 guibg=NONE gui=NONE cterm=NONE
- hi Pmenu guifg=#080808 guibg=#afafd7 gui=NONE cterm=NONE
- hi PmenuSbar guifg=#080808 guibg=#626262 gui=NONE cterm=NONE
- hi PmenuSel guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE
- hi PmenuThumb guifg=#080808 guibg=#d787d7 gui=NONE cterm=NONE
- hi Question guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi QuickFixLine guifg=#d787d7 guibg=#080808 gui=reverse cterm=reverse
- hi Search guifg=#00afff guibg=#080808 gui=reverse cterm=reverse
- hi SignColumn guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi SpecialKey guifg=#080808 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi PmenuKind guifg=#000000 guibg=#a8a8a8 gui=bold cterm=bold
+ hi PmenuSbar guifg=#000000 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#d7d7d7 guibg=#000000 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#d7d7d7 guibg=#000000 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#d7d7d7 guibg=#000000 gui=bold cterm=bold
+ hi PmenuThumb guifg=#000000 guibg=#000000 gui=NONE cterm=NONE
+ hi Question guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi QuickFixLine guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
+ hi Search guifg=#00afff guibg=#000000 gui=reverse cterm=reverse
+ hi SignColumn guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialKey guifg=#626262 guibg=NONE gui=bold cterm=bold
hi SpellBad guifg=#af0000 guibg=#d7d7d7 guisp=#af0000 gui=undercurl cterm=underline
hi SpellCap guifg=#005faf guibg=#d7d7d7 guisp=#005faf gui=undercurl cterm=underline
hi SpellLocal guifg=#870087 guibg=#d7d7d7 guisp=#870087 gui=undercurl cterm=underline
hi SpellRare guifg=#008787 guibg=#d7d7d7 guisp=#008787 gui=undercurl cterm=underline
- hi StatusLine guifg=#eeeeee guibg=#080808 gui=bold cterm=bold
- hi StatusLineNC guifg=#080808 guibg=#a8a8a8 gui=NONE cterm=NONE
- hi TabLine guifg=#080808 guibg=#a8a8a8 gui=NONE cterm=NONE
- hi TabLineFill guifg=#080808 guibg=#d7d7d7 gui=NONE cterm=NONE
- hi TabLineSel guifg=#eeeeee guibg=#080808 gui=bold cterm=bold
+ hi StatusLine guifg=#eeeeee guibg=#000000 gui=bold cterm=bold
+ hi StatusLineNC guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi TabLine guifg=#000000 guibg=#a8a8a8 gui=NONE cterm=NONE
+ hi TabLineFill guifg=#000000 guibg=#d7d7d7 gui=NONE cterm=NONE
+ hi TabLineSel guifg=#eeeeee guibg=#000000 gui=bold cterm=bold
hi Title guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
hi VertSplit guifg=#626262 guibg=#d7d7d7 gui=NONE cterm=NONE
- hi Visual guifg=#ffaf00 guibg=#080808 gui=reverse cterm=reverse
+ hi Visual guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
hi VisualNOS guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
- hi WarningMsg guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi WildMenu guifg=#080808 guibg=#eeeeee gui=bold cterm=bold
- hi Comment guifg=#080808 guibg=NONE gui=bold cterm=bold
- hi Constant guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Error guifg=#ff005f guibg=#080808 gui=bold,reverse cterm=bold,reverse
- hi Identifier guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Ignore guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi PreProc guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Special guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Statement guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Todo guifg=#00ffaf guibg=#080808 gui=bold,reverse cterm=bold,reverse
- hi Type guifg=#080808 guibg=NONE gui=NONE cterm=NONE
- hi Underlined guifg=#080808 guibg=NONE gui=underline cterm=underline
- hi CursorIM guifg=#080808 guibg=#afff00 gui=NONE cterm=NONE
+ hi WarningMsg guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi WildMenu guifg=#000000 guibg=#eeeeee gui=bold cterm=bold
+ hi Comment guifg=#000000 guibg=NONE gui=bold cterm=bold
+ hi Constant guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Error guifg=#ff005f guibg=#000000 gui=bold,reverse cterm=bold,reverse
+ hi Identifier guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Ignore guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi PreProc guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Special guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Statement guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Todo guifg=#00ffaf guibg=#000000 gui=bold,reverse cterm=bold,reverse
+ hi Type guifg=#000000 guibg=NONE gui=NONE cterm=NONE
+ hi Underlined guifg=#000000 guibg=NONE gui=underline cterm=underline
+ hi CursorIM guifg=#000000 guibg=#afff00 gui=NONE cterm=NONE
hi ToolbarLine guifg=NONE guibg=#d7d7d7 gui=NONE cterm=NONE
- hi ToolbarButton guifg=#080808 guibg=#d7d7d7 gui=bold cterm=bold
+ hi ToolbarButton guifg=#000000 guibg=#d7d7d7 gui=bold cterm=bold
endif
if s:t_Co >= 256
if &background ==# 'dark'
- hi Normal ctermfg=253 ctermbg=232 cterm=NONE
+ hi Normal ctermfg=253 ctermbg=16 cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=234 cterm=NONE
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CurSearch ctermfg=207 ctermbg=16 cterm=reverse
hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
hi CursorColumn ctermfg=NONE ctermbg=236 cterm=NONE
hi CursorLine ctermfg=NONE ctermbg=236 cterm=NONE
hi CursorLineNr ctermfg=253 ctermbg=236 cterm=NONE
- hi DiffAdd ctermfg=34 ctermbg=232 cterm=reverse
- hi DiffChange ctermfg=110 ctermbg=232 cterm=reverse
- hi DiffDelete ctermfg=167 ctermbg=232 cterm=reverse
- hi DiffText ctermfg=176 ctermbg=232 cterm=reverse
+ hi DiffAdd ctermfg=34 ctermbg=16 cterm=reverse
+ hi DiffChange ctermfg=110 ctermbg=16 cterm=reverse
+ hi DiffDelete ctermfg=167 ctermbg=16 cterm=reverse
+ hi DiffText ctermfg=176 ctermbg=16 cterm=reverse
hi Directory ctermfg=253 ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=253 ctermbg=NONE cterm=NONE
- hi ErrorMsg ctermfg=253 ctermbg=232 cterm=reverse
+ hi EndOfBuffer ctermfg=242 ctermbg=NONE cterm=NONE
+ hi ErrorMsg ctermfg=253 ctermbg=16 cterm=reverse
hi FoldColumn ctermfg=242 ctermbg=NONE cterm=NONE
- hi Folded ctermfg=242 ctermbg=232 cterm=NONE
- hi IncSearch ctermfg=214 ctermbg=232 cterm=reverse
- hi LineNr ctermfg=238 ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=242 ctermbg=16 cterm=NONE
+ hi IncSearch ctermfg=214 ctermbg=16 cterm=reverse
+ hi LineNr ctermfg=240 ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold
hi ModeMsg ctermfg=253 ctermbg=NONE cterm=bold
hi MoreMsg ctermfg=253 ctermbg=NONE cterm=NONE
hi NonText ctermfg=242 ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=232 ctermbg=110 cterm=NONE
- hi PmenuSbar ctermfg=253 ctermbg=242 cterm=NONE
- hi PmenuSel ctermfg=232 ctermbg=176 cterm=NONE
- hi PmenuThumb ctermfg=253 ctermbg=176 cterm=NONE
+ hi Pmenu ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuExtra ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuKind ctermfg=16 ctermbg=248 cterm=bold
+ hi PmenuSbar ctermfg=242 ctermbg=240 cterm=NONE
+ hi PmenuSel ctermfg=16 ctermbg=253 cterm=NONE
+ hi PmenuExtraSel ctermfg=16 ctermbg=253 cterm=NONE
+ hi PmenuKindSel ctermfg=16 ctermbg=253 cterm=bold
+ hi PmenuThumb ctermfg=253 ctermbg=253 cterm=NONE
hi Question ctermfg=253 ctermbg=NONE cterm=NONE
- hi QuickFixLine ctermfg=176 ctermbg=232 cterm=reverse
- hi Search ctermfg=39 ctermbg=232 cterm=reverse
+ hi QuickFixLine ctermfg=207 ctermbg=16 cterm=reverse
+ hi Search ctermfg=39 ctermbg=16 cterm=reverse
hi SignColumn ctermfg=253 ctermbg=NONE cterm=NONE
- hi SpecialKey ctermfg=253 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=242 ctermbg=NONE cterm=bold
hi SpellBad ctermfg=161 ctermbg=NONE cterm=underline
hi SpellCap ctermfg=32 ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=176 ctermbg=NONE cterm=underline
hi SpellRare ctermfg=37 ctermbg=NONE cterm=underline
- hi StatusLine ctermfg=232 ctermbg=253 cterm=bold
- hi StatusLineNC ctermfg=242 ctermbg=232 cterm=reverse
- hi TabLine ctermfg=242 ctermbg=232 cterm=reverse
+ hi StatusLine ctermfg=16 ctermbg=253 cterm=bold
+ hi StatusLineNC ctermfg=242 ctermbg=16 cterm=reverse
+ hi TabLine ctermfg=242 ctermbg=16 cterm=reverse
hi TabLineFill ctermfg=253 ctermbg=NONE cterm=NONE
- hi TabLineSel ctermfg=232 ctermbg=253 cterm=bold
+ hi TabLineSel ctermfg=16 ctermbg=253 cterm=bold
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit ctermfg=242 ctermbg=232 cterm=NONE
- hi Visual ctermfg=214 ctermbg=232 cterm=reverse
+ hi VertSplit ctermfg=242 ctermbg=16 cterm=NONE
+ hi Visual ctermfg=214 ctermbg=16 cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=236 cterm=NONE
hi WarningMsg ctermfg=253 ctermbg=NONE cterm=NONE
- hi WildMenu ctermfg=39 ctermbg=232 cterm=bold
+ hi WildMenu ctermfg=39 ctermbg=16 cterm=bold
hi Comment ctermfg=242 ctermbg=NONE cterm=bold
hi Constant ctermfg=253 ctermbg=NONE cterm=NONE
- hi Error ctermfg=197 ctermbg=232 cterm=bold,reverse
+ hi Error ctermfg=197 ctermbg=16 cterm=bold,reverse
hi Identifier ctermfg=253 ctermbg=NONE cterm=NONE
hi Ignore ctermfg=253 ctermbg=NONE cterm=NONE
hi PreProc ctermfg=253 ctermbg=NONE cterm=NONE
@@ -244,71 +259,76 @@ if s:t_Co >= 256
hi Todo ctermfg=49 ctermbg=NONE cterm=bold,reverse
hi Type ctermfg=253 ctermbg=NONE cterm=NONE
hi Underlined ctermfg=253 ctermbg=NONE cterm=underline
- hi CursorIM ctermfg=232 ctermbg=154 cterm=NONE
- hi ToolbarLine ctermfg=NONE ctermbg=232 cterm=NONE
- hi ToolbarButton ctermfg=253 ctermbg=232 cterm=bold
+ hi CursorIM ctermfg=16 ctermbg=154 cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=16 cterm=NONE
+ hi ToolbarButton ctermfg=253 ctermbg=16 cterm=bold
else
" Light background
- hi Normal ctermfg=232 ctermbg=188 cterm=NONE
+ hi Normal ctermfg=16 ctermbg=188 cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=254 cterm=NONE
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CurSearch ctermfg=207 ctermbg=16 cterm=reverse
hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
hi CursorColumn ctermfg=NONE ctermbg=255 cterm=NONE
hi CursorLine ctermfg=NONE ctermbg=255 cterm=NONE
- hi CursorLineNr ctermfg=232 ctermbg=255 cterm=NONE
- hi DiffAdd ctermfg=114 ctermbg=232 cterm=reverse
- hi DiffChange ctermfg=146 ctermbg=232 cterm=reverse
- hi DiffDelete ctermfg=174 ctermbg=232 cterm=reverse
- hi DiffText ctermfg=176 ctermbg=232 cterm=reverse
- hi Directory ctermfg=232 ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=232 ctermbg=NONE cterm=NONE
- hi ErrorMsg ctermfg=232 ctermbg=188 cterm=reverse
+ hi CursorLineNr ctermfg=16 ctermbg=255 cterm=NONE
+ hi DiffAdd ctermfg=114 ctermbg=16 cterm=reverse
+ hi DiffChange ctermfg=146 ctermbg=16 cterm=reverse
+ hi DiffDelete ctermfg=174 ctermbg=16 cterm=reverse
+ hi DiffText ctermfg=176 ctermbg=16 cterm=reverse
+ hi Directory ctermfg=16 ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=241 ctermbg=NONE cterm=NONE
+ hi ErrorMsg ctermfg=16 ctermbg=188 cterm=reverse
hi FoldColumn ctermfg=241 ctermbg=NONE cterm=NONE
hi Folded ctermfg=241 ctermbg=188 cterm=NONE
- hi IncSearch ctermfg=214 ctermbg=232 cterm=reverse
+ hi IncSearch ctermfg=214 ctermbg=16 cterm=reverse
hi LineNr ctermfg=248 ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=199 ctermbg=188 cterm=bold
- hi ModeMsg ctermfg=232 ctermbg=NONE cterm=bold
- hi MoreMsg ctermfg=232 ctermbg=NONE cterm=NONE
+ hi ModeMsg ctermfg=16 ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=16 ctermbg=NONE cterm=NONE
hi NonText ctermfg=241 ctermbg=NONE cterm=NONE
- hi Pmenu ctermfg=232 ctermbg=146 cterm=NONE
- hi PmenuSbar ctermfg=232 ctermbg=241 cterm=NONE
- hi PmenuSel ctermfg=232 ctermbg=176 cterm=NONE
- hi PmenuThumb ctermfg=232 ctermbg=176 cterm=NONE
- hi Question ctermfg=232 ctermbg=NONE cterm=NONE
- hi QuickFixLine ctermfg=176 ctermbg=232 cterm=reverse
- hi Search ctermfg=39 ctermbg=232 cterm=reverse
- hi SignColumn ctermfg=232 ctermbg=NONE cterm=NONE
- hi SpecialKey ctermfg=232 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuExtra ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuKind ctermfg=16 ctermbg=248 cterm=bold
+ hi PmenuSbar ctermfg=16 ctermbg=254 cterm=NONE
+ hi PmenuSel ctermfg=188 ctermbg=16 cterm=NONE
+ hi PmenuExtraSel ctermfg=188 ctermbg=16 cterm=NONE
+ hi PmenuKindSel ctermfg=188 ctermbg=16 cterm=bold
+ hi PmenuThumb ctermfg=16 ctermbg=16 cterm=NONE
+ hi Question ctermfg=16 ctermbg=NONE cterm=NONE
+ hi QuickFixLine ctermfg=207 ctermbg=16 cterm=reverse
+ hi Search ctermfg=39 ctermbg=16 cterm=reverse
+ hi SignColumn ctermfg=16 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=241 ctermbg=NONE cterm=bold
hi SpellBad ctermfg=124 ctermbg=188 cterm=underline
hi SpellCap ctermfg=25 ctermbg=188 cterm=underline
hi SpellLocal ctermfg=90 ctermbg=188 cterm=underline
hi SpellRare ctermfg=30 ctermbg=188 cterm=underline
- hi StatusLine ctermfg=255 ctermbg=232 cterm=bold
- hi StatusLineNC ctermfg=232 ctermbg=248 cterm=NONE
- hi TabLine ctermfg=232 ctermbg=248 cterm=NONE
- hi TabLineFill ctermfg=232 ctermbg=188 cterm=NONE
- hi TabLineSel ctermfg=255 ctermbg=232 cterm=bold
+ hi StatusLine ctermfg=255 ctermbg=16 cterm=bold
+ hi StatusLineNC ctermfg=16 ctermbg=248 cterm=NONE
+ hi TabLine ctermfg=16 ctermbg=248 cterm=NONE
+ hi TabLineFill ctermfg=16 ctermbg=188 cterm=NONE
+ hi TabLineSel ctermfg=255 ctermbg=16 cterm=bold
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
hi VertSplit ctermfg=241 ctermbg=188 cterm=NONE
- hi Visual ctermfg=214 ctermbg=232 cterm=reverse
+ hi Visual ctermfg=214 ctermbg=16 cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=255 cterm=NONE
- hi WarningMsg ctermfg=232 ctermbg=NONE cterm=NONE
- hi WildMenu ctermfg=232 ctermbg=255 cterm=bold
- hi Comment ctermfg=232 ctermbg=NONE cterm=bold
- hi Constant ctermfg=232 ctermbg=NONE cterm=NONE
- hi Error ctermfg=197 ctermbg=232 cterm=bold,reverse
- hi Identifier ctermfg=232 ctermbg=NONE cterm=NONE
- hi Ignore ctermfg=232 ctermbg=NONE cterm=NONE
- hi PreProc ctermfg=232 ctermbg=NONE cterm=NONE
- hi Special ctermfg=232 ctermbg=NONE cterm=NONE
- hi Statement ctermfg=232 ctermbg=NONE cterm=NONE
- hi Todo ctermfg=49 ctermbg=232 cterm=bold,reverse
- hi Type ctermfg=232 ctermbg=NONE cterm=NONE
- hi Underlined ctermfg=232 ctermbg=NONE cterm=underline
- hi CursorIM ctermfg=232 ctermbg=154 cterm=NONE
+ hi WarningMsg ctermfg=16 ctermbg=NONE cterm=NONE
+ hi WildMenu ctermfg=16 ctermbg=255 cterm=bold
+ hi Comment ctermfg=16 ctermbg=NONE cterm=bold
+ hi Constant ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Error ctermfg=197 ctermbg=16 cterm=bold,reverse
+ hi Identifier ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=16 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=49 ctermbg=16 cterm=bold,reverse
+ hi Type ctermfg=16 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=16 ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=16 ctermbg=154 cterm=NONE
hi ToolbarLine ctermfg=NONE ctermbg=188 cterm=NONE
- hi ToolbarButton ctermfg=232 ctermbg=188 cterm=bold
+ hi ToolbarButton ctermfg=16 ctermbg=188 cterm=bold
endif
unlet s:t_Co
finish
@@ -316,6 +336,19 @@ endif
if s:t_Co >= 16
if &background ==# 'dark'
+ hi Comment ctermfg=darkgrey ctermbg=NONE cterm=bold
+ hi CurSearch ctermfg=magenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=yellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi Search ctermfg=cyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi TabLine ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi VertSplit ctermfg=darkgrey ctermbg=NONE cterm=NONE
hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -328,43 +361,35 @@ if s:t_Co >= 16
hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
- hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline
- hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
- hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
- hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
hi Question ctermfg=NONE ctermbg=NONE cterm=standout
hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
- hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse
- hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
- hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
- hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
- hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
hi Constant ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Error ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi PreProc ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -378,6 +403,18 @@ if s:t_Co >= 16
hi ToolbarButton ctermfg=NONE ctermbg=NONE cterm=bold,reverse
else
" Light background
+ hi CurSearch ctermfg=magenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=yellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi Search ctermfg=cyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi TabLine ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi VertSplit ctermfg=darkgrey ctermbg=NONE cterm=NONE
hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -390,43 +427,36 @@ if s:t_Co >= 16
hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
- hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline
- hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
- hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
- hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
hi Question ctermfg=NONE ctermbg=NONE cterm=standout
hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
- hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse
- hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
- hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
- hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
hi Constant ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Error ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi PreProc ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -445,6 +475,19 @@ endif
if s:t_Co >= 8
if &background ==# 'dark'
+ hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
+ hi CurSearch ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -457,43 +500,35 @@ if s:t_Co >= 8
hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
- hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline
- hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
- hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
- hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
hi Question ctermfg=NONE ctermbg=NONE cterm=standout
hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
- hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse
- hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
- hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
- hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
- hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
hi Constant ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Error ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi PreProc ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -507,6 +542,18 @@ if s:t_Co >= 8
hi ToolbarButton ctermfg=NONE ctermbg=NONE cterm=bold,reverse
else
" Light background
+ hi CurSearch ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -519,43 +566,36 @@ if s:t_Co >= 8
hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
- hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
- hi IncSearch ctermfg=darkyellow ctermbg=black cterm=bold,reverse,underline
- hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
- hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
- hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
hi Question ctermfg=NONE ctermbg=NONE cterm=standout
hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
- hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
hi SignColumn ctermfg=NONE ctermbg=NONE cterm=reverse
- hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
- hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
- hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
- hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
hi Constant ctermfg=NONE ctermbg=NONE cterm=NONE
- hi Error ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
hi PreProc ctermfg=NONE ctermbg=NONE cterm=NONE
@@ -641,7 +681,7 @@ if s:t_Co >= 0
endif
" Background: dark
-" Color: dark0 #080808 232 black
+" Color: dark0 #000000 16 black
" Color: dark1 #d7005f 161 darkred
" Color: dark2 #00af5f 35 darkgreen
" Color: dark3 #d78700 172 darkyellow
@@ -669,13 +709,15 @@ endif
" Color: uiamber #ffaf00 214 darkyellow
" Color: uiblack #303030 236 darkgrey
" Color: yasogrey #1c1c1c 234 black
-" Color: linenrblack #444444 238 darkgrey
+" Color: linenrblack #585858 240 darkgrey
+" Color: uicursearch #ff5fff 207 magenta
+" Color: invisigrey #a8a8a8 248 darkgrey
" Color: errorred #ff005f 197 red
" Term colors: dark0 dark1 dark2 dark3 dark4 dark5 dark6 dark7
" Term colors: dark8 dark9 dark10 dark11 dark12 dark13 dark14 dark15
" Background: light
" Color: brightwhite #eeeeee 255 grey
-" Color: light0 #080808 232 black
+" Color: light0 #000000 16 black
" Color: light1 #af0000 124 darkred
" Color: light2 #005f00 22 darkgreen
" Color: light3 #af5f00 130 darkyellow
@@ -703,6 +745,7 @@ endif
" Color: uiamber #ffaf00 214 yellow
" Color: invisigrey #a8a8a8 248 darkgrey
" Color: yasogrey #e4e4e4 254 grey
+" Color: uicursearch #ff5fff 207 magenta
" Color: errorred #ff005f 197 red
" Term colors: light0 light1 light2 light3 light4 light5 light6 light7
" Term colors: light8 light9 light10 light11 light12 light13 light14 light15
diff --git a/runtime/colors/retrobox.vim b/runtime/colors/retrobox.vim
new file mode 100644
index 0000000000..c0c4f9f6c2
--- /dev/null
+++ b/runtime/colors/retrobox.vim
@@ -0,0 +1,882 @@
+" Name: Retro Box
+" Description: Retro groove color scheme similar to gruvbox originally designed by morhetz <morhetz@gmail.com>
+" Author: Maxim Kim <habamax@gmail.com>, ported from gruvbox8 of Lifepillar <lifepillar@lifepillar.me>
+" Maintainer: Maxim Kim <habamax@gmail.com>, ported from gruvbox8 of Lifepillar <lifepillar@lifepillar.me>
+" Website: https://www.github.com/vim/colorschemes
+" License: Vim License (see `:help license`)
+" Last Updated: Sun 12 Mar 2023 15:14:04 AEDT
+
+" Generated by Colortemplate v2.2.0
+
+hi clear
+let g:colors_name = 'retrobox'
+
+let s:t_Co = &t_Co
+
+hi! link CursorColumn CursorLine
+hi! link StatusLineTerm StatusLine
+hi! link StatusLineTermNC StatusLineNC
+hi! link VisualNOS Visual
+hi! link Tag Special
+hi! link lCursor Cursor
+hi! link MessageWindow PMenu
+hi! link PopupNotification Todo
+hi! link CurSearch Search
+
+if &background ==# 'dark'
+ if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#1c1c1c', '#cc241d', '#98971a', '#d79921', '#458588', '#b16286', '#689d6a', '#a89984', '#928374', '#fb4934', '#b8bb26', '#fabd2f', '#83a598', '#d3869b', '#8ec07c', '#ebdbb2']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+ endif
+ hi Normal guifg=#ebdbb2 guibg=#1c1c1c gui=NONE cterm=NONE
+ hi CursorLineNr guifg=#fabd2f guibg=#303030 gui=NONE cterm=NONE
+ hi FoldColumn guifg=#928374 guibg=#1c1c1c gui=NONE cterm=NONE
+ hi SignColumn guifg=#928374 guibg=#1c1c1c gui=NONE cterm=NONE
+ hi VertSplit guifg=#303030 guibg=#1c1c1c gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#000000 gui=NONE cterm=NONE
+ hi Comment guifg=#928374 guibg=NONE gui=NONE cterm=NONE
+ hi CursorLine guifg=NONE guibg=#303030 gui=NONE cterm=NONE
+ hi Error guifg=#fb4934 guibg=#1c1c1c gui=bold,reverse cterm=bold,reverse
+ hi ErrorMsg guifg=#1c1c1c guibg=#fb4934 gui=bold cterm=bold
+ hi Folded guifg=#928374 guibg=#121212 gui=NONE cterm=NONE
+ hi LineNr guifg=#7c6f64 guibg=NONE gui=NONE cterm=NONE
+ hi MatchParen guifg=NONE guibg=#504945 gui=bold,underline cterm=bold,underline
+ hi NonText guifg=#504945 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=#ebdbb2 guibg=#3c3836 gui=NONE cterm=NONE
+ hi PmenuSbar guifg=NONE guibg=#3c3836 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#3c3836 guibg=#83a598 gui=bold cterm=bold
+ hi PmenuThumb guifg=NONE guibg=#7c6f64 gui=NONE cterm=NONE
+ hi PmenuKind guifg=#fb4934 guibg=#3c3836 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#fb4934 guibg=#83a598 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#a89984 guibg=#3c3836 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#303030 guibg=#83a598 gui=NONE cterm=NONE
+ hi SpecialKey guifg=#928374 guibg=NONE gui=NONE cterm=NONE
+ hi StatusLine guifg=#504945 guibg=#ebdbb2 gui=bold,reverse cterm=bold,reverse
+ hi StatusLineNC guifg=#3c3836 guibg=#a89984 gui=reverse cterm=reverse
+ hi TabLine guifg=#a89984 guibg=#3c3836 gui=NONE cterm=NONE
+ hi TabLineFill guifg=#ebdbb2 guibg=#3c3836 gui=NONE cterm=NONE
+ hi TabLineSel guifg=#fbf1c7 guibg=#1c1c1c gui=bold cterm=bold
+ hi ToolbarButton guifg=#fbf1c7 guibg=#303030 gui=bold cterm=bold
+ hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual guifg=#1c1c1c guibg=#83a598 gui=NONE cterm=NONE
+ hi WildMenu guifg=#83a598 guibg=#504945 gui=bold cterm=bold
+ hi EndOfBuffer guifg=#504945 guibg=NONE gui=NONE cterm=NONE
+ hi Conceal guifg=#83a598 guibg=NONE gui=NONE cterm=NONE
+ hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd guifg=#b8bb26 guibg=#1c1c1c gui=reverse cterm=reverse
+ hi DiffChange guifg=#8ec07c guibg=#1c1c1c gui=reverse cterm=reverse
+ hi DiffDelete guifg=#fb4934 guibg=#1c1c1c gui=reverse cterm=reverse
+ hi DiffText guifg=#fabd2f guibg=#1c1c1c gui=reverse cterm=reverse
+ hi Directory guifg=#b8bb26 guibg=NONE gui=bold cterm=bold
+ hi IncSearch guifg=#fe8019 guibg=#1c1c1c gui=reverse cterm=reverse
+ hi ModeMsg guifg=#fabd2f guibg=NONE gui=bold cterm=bold
+ hi MoreMsg guifg=#fabd2f guibg=NONE gui=bold cterm=bold
+ hi Question guifg=#fe8019 guibg=NONE gui=bold cterm=bold
+ hi Search guifg=#98971a guibg=#1c1c1c gui=reverse cterm=reverse
+ hi QuickFixLine guifg=#8ec07c guibg=#1c1c1c gui=reverse cterm=reverse
+ hi SpellBad guifg=#fb4934 guibg=NONE guisp=#fb4934 gui=undercurl cterm=underline
+ hi SpellCap guifg=#83a598 guibg=NONE guisp=#83a598 gui=undercurl cterm=underline
+ hi SpellLocal guifg=#8ec07c guibg=NONE guisp=#8ec07c gui=undercurl cterm=underline
+ hi SpellRare guifg=#d3869b guibg=NONE guisp=#d3869b gui=undercurl cterm=underline
+ hi Title guifg=#b8bb26 guibg=NONE gui=bold cterm=bold
+ hi WarningMsg guifg=#fb4934 guibg=NONE gui=bold cterm=bold
+ hi Boolean guifg=#d3869b guibg=NONE gui=NONE cterm=NONE
+ hi Character guifg=#d3869b guibg=NONE gui=NONE cterm=NONE
+ hi Conditional guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Constant guifg=#d3869b guibg=NONE gui=NONE cterm=NONE
+ hi Define guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi Debug guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Delimiter guifg=#fe8019 guibg=NONE gui=NONE cterm=NONE
+ hi Error guifg=#fb4934 guibg=#1c1c1c gui=bold,reverse cterm=bold,reverse
+ hi Exception guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Float guifg=#d3869b guibg=NONE gui=NONE cterm=NONE
+ hi Function guifg=#b8bb26 guibg=NONE gui=bold cterm=bold
+ hi Identifier guifg=#83a598 guibg=NONE gui=NONE cterm=NONE
+ hi Ignore guifg=fg guibg=NONE gui=NONE cterm=NONE
+ hi Include guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi Keyword guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Label guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Macro guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi Number guifg=#d3869b guibg=NONE gui=NONE cterm=NONE
+ hi Operator guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi PreCondit guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi PreProc guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi Repeat guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialChar guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialComment guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi Statement guifg=#fb4934 guibg=NONE gui=NONE cterm=NONE
+ hi StorageClass guifg=#fe8019 guibg=NONE gui=NONE cterm=NONE
+ hi Special guifg=#fe8019 guibg=NONE gui=NONE cterm=NONE
+ hi String guifg=#b8bb26 guibg=NONE gui=NONE cterm=NONE
+ hi Structure guifg=#8ec07c guibg=NONE gui=NONE cterm=NONE
+ hi Todo guifg=fg guibg=#1c1c1c gui=bold cterm=bold
+ hi Type guifg=#fabd2f guibg=NONE gui=NONE cterm=NONE
+ hi Typedef guifg=#fabd2f guibg=NONE gui=NONE cterm=NONE
+ hi Underlined guifg=#83a598 guibg=NONE gui=underline cterm=underline
+ hi CursorIM guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+else
+ " Light background
+ if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#3c3836', '#cc241d', '#98971a', '#d79921', '#458588', '#b16286', '#689d6a', '#7c6f64', '#928374', '#9d0006', '#79740e', '#b57614', '#076678', '#8f3f71', '#427b58', '#fbf1c7']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+ endif
+ hi Normal guifg=#3c3836 guibg=#fbf1c7 gui=NONE cterm=NONE
+ hi CursorLineNr guifg=#b57614 guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi FoldColumn guifg=#928374 guibg=#fbf1c7 gui=NONE cterm=NONE
+ hi SignColumn guifg=#3c3836 guibg=#fbf1c7 gui=NONE cterm=NONE
+ hi VertSplit guifg=#bdae93 guibg=#fbf1c7 gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#ebe1b7 gui=NONE cterm=NONE
+ hi Comment guifg=#928374 guibg=NONE gui=NONE cterm=NONE
+ hi CursorLine guifg=NONE guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi Error guifg=#9d0006 guibg=#fbf1c7 gui=bold,reverse cterm=bold,reverse
+ hi ErrorMsg guifg=#fbf1c7 guibg=#9d0006 gui=bold cterm=bold
+ hi Folded guifg=#928374 guibg=#ffffd7 gui=NONE cterm=NONE
+ hi LineNr guifg=#a89984 guibg=NONE gui=NONE cterm=NONE
+ hi MatchParen guifg=NONE guibg=#e5d4b1 gui=bold,underline cterm=bold,underline
+ hi NonText guifg=#e5d4b1 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=#3c3836 guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi PmenuSbar guifg=NONE guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#e5d4b1 guibg=#076678 gui=bold cterm=bold
+ hi PmenuThumb guifg=NONE guibg=#a89984 gui=NONE cterm=NONE
+ hi PmenuKind guifg=#9d0006 guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#9d0006 guibg=#076678 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#7c6f64 guibg=#e5d4b1 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#bdae93 guibg=#076678 gui=NONE cterm=NONE
+ hi SpecialKey guifg=#928374 guibg=NONE gui=NONE cterm=NONE
+ hi StatusLine guifg=#bdae93 guibg=#3c3836 gui=bold,reverse cterm=bold,reverse
+ hi StatusLineNC guifg=#ebdbb2 guibg=#3c3836 gui=reverse cterm=reverse
+ hi TabLine guifg=#665c54 guibg=#ebdbb2 gui=NONE cterm=NONE
+ hi TabLineFill guifg=#ebdbb2 guibg=#ebdbb2 gui=NONE cterm=NONE
+ hi TabLineSel guifg=#282828 guibg=#fbf1c7 gui=bold cterm=bold
+ hi ToolbarButton guifg=#282828 guibg=#bdae93 gui=bold cterm=bold
+ hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual guifg=#fbf1c7 guibg=#076678 gui=NONE cterm=NONE
+ hi WildMenu guifg=#076678 guibg=#e5d4b1 gui=bold cterm=bold
+ hi EndOfBuffer guifg=#e5d4b1 guibg=NONE gui=NONE cterm=NONE
+ hi Conceal guifg=#076678 guibg=NONE gui=NONE cterm=NONE
+ hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd guifg=#79740e guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi DiffChange guifg=#427b58 guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi DiffDelete guifg=#9d0006 guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi DiffText guifg=#b57614 guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi Directory guifg=#79740e guibg=NONE gui=bold cterm=bold
+ hi IncSearch guifg=#ff5f00 guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi ModeMsg guifg=#3c3836 guibg=NONE gui=bold cterm=bold
+ hi MoreMsg guifg=#3c3836 guibg=NONE gui=bold cterm=bold
+ hi Question guifg=#ff5f00 guibg=NONE gui=bold cterm=bold
+ hi Search guifg=#98971a guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi QuickFixLine guifg=#427b58 guibg=#fbf1c7 gui=reverse cterm=reverse
+ hi SpellBad guifg=#9d0006 guibg=NONE guisp=#9d0006 gui=undercurl cterm=underline
+ hi SpellCap guifg=#076678 guibg=NONE guisp=#076678 gui=undercurl cterm=underline
+ hi SpellLocal guifg=#427b58 guibg=NONE guisp=#427b58 gui=undercurl cterm=underline
+ hi SpellRare guifg=#8f3f71 guibg=NONE guisp=#8f3f71 gui=undercurl cterm=underline
+ hi Title guifg=#79740e guibg=NONE gui=bold cterm=bold
+ hi WarningMsg guifg=#9d0006 guibg=NONE gui=bold cterm=bold
+ hi Boolean guifg=#8f3f71 guibg=NONE gui=NONE cterm=NONE
+ hi Character guifg=#8f3f71 guibg=NONE gui=NONE cterm=NONE
+ hi Conditional guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Constant guifg=#8f3f71 guibg=NONE gui=NONE cterm=NONE
+ hi Define guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi Debug guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Delimiter guifg=#ff5f00 guibg=NONE gui=NONE cterm=NONE
+ hi Error guifg=#9d0006 guibg=#fbf1c7 gui=bold,reverse cterm=bold,reverse
+ hi Exception guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Float guifg=#8f3f71 guibg=NONE gui=NONE cterm=NONE
+ hi Function guifg=#79740e guibg=NONE gui=bold cterm=bold
+ hi Identifier guifg=#076678 guibg=NONE gui=NONE cterm=NONE
+ hi Ignore guifg=fg guibg=NONE gui=NONE cterm=NONE
+ hi Include guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi Keyword guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Label guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Macro guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi Number guifg=#8f3f71 guibg=NONE gui=NONE cterm=NONE
+ hi Operator guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi PreCondit guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi PreProc guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi Repeat guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialChar guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialComment guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi Statement guifg=#9d0006 guibg=NONE gui=NONE cterm=NONE
+ hi StorageClass guifg=#ff5f00 guibg=NONE gui=NONE cterm=NONE
+ hi Special guifg=#ff5f00 guibg=NONE gui=NONE cterm=NONE
+ hi String guifg=#79740e guibg=NONE gui=NONE cterm=NONE
+ hi Structure guifg=#427b58 guibg=NONE gui=NONE cterm=NONE
+ hi Todo guifg=fg guibg=#fbf1c7 gui=bold cterm=bold
+ hi Type guifg=#b57614 guibg=NONE gui=NONE cterm=NONE
+ hi Typedef guifg=#b57614 guibg=NONE gui=NONE cterm=NONE
+ hi Underlined guifg=#076678 guibg=NONE gui=underline cterm=underline
+ hi CursorIM guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+endif
+
+if s:t_Co >= 256
+ if &background ==# 'dark'
+ hi Normal ctermfg=187 ctermbg=234 cterm=NONE
+ hi CursorLineNr ctermfg=214 ctermbg=236 cterm=NONE
+ hi FoldColumn ctermfg=102 ctermbg=234 cterm=NONE
+ hi SignColumn ctermfg=102 ctermbg=234 cterm=NONE
+ hi VertSplit ctermfg=236 ctermbg=234 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=16 cterm=NONE
+ hi Comment ctermfg=102 ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=236 cterm=NONE
+ hi Error ctermfg=203 ctermbg=234 cterm=bold,reverse
+ hi ErrorMsg ctermfg=234 ctermbg=203 cterm=bold
+ hi Folded ctermfg=102 ctermbg=233 cterm=NONE
+ hi LineNr ctermfg=243 ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=239 cterm=bold,underline
+ hi NonText ctermfg=239 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=187 ctermbg=237 cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=237 cterm=NONE
+ hi PmenuSel ctermfg=237 ctermbg=109 cterm=bold
+ hi PmenuThumb ctermfg=NONE ctermbg=243 cterm=NONE
+ hi PmenuKind ctermfg=203 ctermbg=237 cterm=NONE
+ hi PmenuKindSel ctermfg=203 ctermbg=109 cterm=NONE
+ hi PmenuExtra ctermfg=102 ctermbg=237 cterm=NONE
+ hi PmenuExtraSel ctermfg=236 ctermbg=109 cterm=NONE
+ hi SpecialKey ctermfg=102 ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=239 ctermbg=187 cterm=bold,reverse
+ hi StatusLineNC ctermfg=237 ctermbg=102 cterm=reverse
+ hi TabLine ctermfg=102 ctermbg=237 cterm=NONE
+ hi TabLineFill ctermfg=187 ctermbg=237 cterm=NONE
+ hi TabLineSel ctermfg=230 ctermbg=234 cterm=bold
+ hi ToolbarButton ctermfg=230 ctermbg=236 cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=234 ctermbg=109 cterm=NONE
+ hi WildMenu ctermfg=109 ctermbg=239 cterm=bold
+ hi EndOfBuffer ctermfg=239 ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=109 ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=142 ctermbg=234 cterm=reverse
+ hi DiffChange ctermfg=107 ctermbg=234 cterm=reverse
+ hi DiffDelete ctermfg=203 ctermbg=234 cterm=reverse
+ hi DiffText ctermfg=214 ctermbg=234 cterm=reverse
+ hi Directory ctermfg=142 ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=208 ctermbg=234 cterm=reverse
+ hi ModeMsg ctermfg=214 ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=214 ctermbg=NONE cterm=bold
+ hi Question ctermfg=208 ctermbg=NONE cterm=bold
+ hi Search ctermfg=100 ctermbg=234 cterm=reverse
+ hi QuickFixLine ctermfg=107 ctermbg=234 cterm=reverse
+ hi SpellBad ctermfg=203 ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=109 ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=107 ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=175 ctermbg=NONE cterm=underline
+ hi Title ctermfg=142 ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=203 ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=175 ctermbg=NONE cterm=NONE
+ hi Character ctermfg=175 ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=175 ctermbg=NONE cterm=NONE
+ hi Define ctermfg=107 ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=208 ctermbg=NONE cterm=NONE
+ hi Error ctermfg=203 ctermbg=234 cterm=bold,reverse
+ hi Exception ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Float ctermfg=175 ctermbg=NONE cterm=NONE
+ hi Function ctermfg=142 ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=109 ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=107 ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Label ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=107 ctermbg=NONE cterm=NONE
+ hi Number ctermfg=175 ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=107 ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=107 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=107 ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=203 ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=203 ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=203 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=203 ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=208 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=208 ctermbg=NONE cterm=NONE
+ hi String ctermfg=142 ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=107 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=234 cterm=bold
+ hi Type ctermfg=214 ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=214 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=109 ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ else
+ " Light background
+ hi Normal ctermfg=237 ctermbg=230 cterm=NONE
+ hi CursorLineNr ctermfg=172 ctermbg=188 cterm=NONE
+ hi FoldColumn ctermfg=102 ctermbg=230 cterm=NONE
+ hi SignColumn ctermfg=237 ctermbg=230 cterm=NONE
+ hi VertSplit ctermfg=144 ctermbg=230 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=229 cterm=NONE
+ hi Comment ctermfg=102 ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=188 cterm=NONE
+ hi Error ctermfg=124 ctermbg=230 cterm=bold,reverse
+ hi ErrorMsg ctermfg=230 ctermbg=124 cterm=bold
+ hi Folded ctermfg=102 ctermbg=231 cterm=NONE
+ hi LineNr ctermfg=137 ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=188 cterm=bold,underline
+ hi NonText ctermfg=188 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=237 ctermbg=188 cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=188 cterm=NONE
+ hi PmenuSel ctermfg=188 ctermbg=23 cterm=bold
+ hi PmenuThumb ctermfg=NONE ctermbg=137 cterm=NONE
+ hi PmenuKind ctermfg=124 ctermbg=188 cterm=NONE
+ hi PmenuKindSel ctermfg=124 ctermbg=23 cterm=NONE
+ hi PmenuExtra ctermfg=243 ctermbg=188 cterm=NONE
+ hi PmenuExtraSel ctermfg=144 ctermbg=23 cterm=NONE
+ hi SpecialKey ctermfg=102 ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=144 ctermbg=237 cterm=bold,reverse
+ hi StatusLineNC ctermfg=187 ctermbg=237 cterm=reverse
+ hi TabLine ctermfg=59 ctermbg=187 cterm=NONE
+ hi TabLineFill ctermfg=187 ctermbg=187 cterm=NONE
+ hi TabLineSel ctermfg=235 ctermbg=230 cterm=bold
+ hi ToolbarButton ctermfg=235 ctermbg=144 cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=230 ctermbg=23 cterm=NONE
+ hi WildMenu ctermfg=23 ctermbg=188 cterm=bold
+ hi EndOfBuffer ctermfg=188 ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=23 ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=64 ctermbg=230 cterm=reverse
+ hi DiffChange ctermfg=29 ctermbg=230 cterm=reverse
+ hi DiffDelete ctermfg=124 ctermbg=230 cterm=reverse
+ hi DiffText ctermfg=172 ctermbg=230 cterm=reverse
+ hi Directory ctermfg=64 ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=202 ctermbg=230 cterm=reverse
+ hi ModeMsg ctermfg=237 ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=237 ctermbg=NONE cterm=bold
+ hi Question ctermfg=202 ctermbg=NONE cterm=bold
+ hi Search ctermfg=100 ctermbg=230 cterm=reverse
+ hi QuickFixLine ctermfg=29 ctermbg=230 cterm=reverse
+ hi SpellBad ctermfg=124 ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=23 ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=29 ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=126 ctermbg=NONE cterm=underline
+ hi Title ctermfg=64 ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=124 ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=126 ctermbg=NONE cterm=NONE
+ hi Character ctermfg=126 ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=126 ctermbg=NONE cterm=NONE
+ hi Define ctermfg=29 ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=202 ctermbg=NONE cterm=NONE
+ hi Error ctermfg=124 ctermbg=230 cterm=bold,reverse
+ hi Exception ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Float ctermfg=126 ctermbg=NONE cterm=NONE
+ hi Function ctermfg=64 ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=23 ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=29 ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Label ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=29 ctermbg=NONE cterm=NONE
+ hi Number ctermfg=126 ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=29 ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=29 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=29 ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=124 ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=124 ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=124 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=124 ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=202 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=202 ctermbg=NONE cterm=NONE
+ hi String ctermfg=64 ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=29 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=230 cterm=bold
+ hi Type ctermfg=172 ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=172 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=23 ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 16
+ if &background ==# 'dark'
+ hi Normal ctermfg=White ctermbg=Black cterm=NONE
+ hi ColorColumn ctermfg=Black ctermbg=DarkYellow cterm=NONE
+ hi Comment ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorLineNr ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=Black cterm=reverse
+ hi ErrorMsg ctermfg=Black ctermbg=Red cterm=NONE
+ hi FoldColumn ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi LineNr ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=Black cterm=bold,underline
+ hi NonText ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=White ctermbg=DarkGray cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=DarkGray cterm=NONE
+ hi PmenuSel ctermfg=Black ctermbg=Blue cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=Blue cterm=NONE
+ hi PmenuKind ctermfg=DarkRed ctermbg=DarkGray cterm=NONE
+ hi PmenuKindSel ctermfg=DarkRed ctermbg=Blue cterm=NONE
+ hi PmenuExtra ctermfg=gray ctermbg=DarkGray cterm=NONE
+ hi PmenuExtraSel ctermfg=Black ctermbg=Blue cterm=NONE
+ hi SignColumn ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=gray ctermbg=Black cterm=bold,reverse
+ hi StatusLineNC ctermfg=gray ctermbg=Black cterm=reverse
+ hi TabLine ctermfg=Black ctermbg=DarkGray cterm=NONE
+ hi TabLineFill ctermfg=Black ctermbg=DarkGray cterm=NONE
+ hi TabLineSel ctermfg=White ctermbg=Black cterm=bold
+ hi ToolbarButton ctermfg=White ctermbg=DarkGray cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=Black ctermbg=gray cterm=NONE
+ hi Visual ctermfg=Black ctermbg=Blue cterm=NONE
+ hi WildMenu ctermfg=White ctermbg=Black cterm=bold
+ hi EndOfBuffer ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=Green ctermbg=Black cterm=reverse
+ hi DiffChange ctermfg=Cyan ctermbg=Black cterm=reverse
+ hi DiffDelete ctermfg=Red ctermbg=Black cterm=reverse
+ hi DiffText ctermfg=Yellow ctermbg=Black cterm=reverse
+ hi Directory ctermfg=Green ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=Magenta ctermbg=Black cterm=reverse
+ hi ModeMsg ctermfg=Yellow ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=Yellow ctermbg=NONE cterm=bold
+ hi Question ctermfg=Magenta ctermbg=NONE cterm=bold
+ hi Search ctermfg=DarkGreen ctermbg=Black cterm=reverse
+ hi QuickFixLine ctermfg=Cyan ctermbg=Black cterm=reverse
+ hi SpellBad ctermfg=Red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=Blue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=Cyan ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=Magenta ctermbg=NONE cterm=underline
+ hi Title ctermfg=Green ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=Red ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Character ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Define ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=Black cterm=bold,reverse
+ hi Exception ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Float ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Function ctermfg=Green ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Label ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Number ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=Red ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Special ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi String ctermfg=Green ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=Black cterm=bold
+ hi Type ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=Blue ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ else
+ " Light background
+ hi Normal ctermfg=Black ctermbg=White cterm=NONE
+ hi ColorColumn ctermfg=White ctermbg=Grey cterm=NONE
+ hi Comment ctermfg=DarkGray ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorLineNr ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=White cterm=reverse
+ hi ErrorMsg ctermfg=White ctermbg=Red cterm=NONE
+ hi FoldColumn ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi LineNr ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=White cterm=bold,underline
+ hi NonText ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=Black ctermbg=Grey cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=Grey cterm=NONE
+ hi PmenuSel ctermfg=White ctermbg=Blue cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=Blue cterm=NONE
+ hi PmenuKind ctermfg=DarkRed ctermbg=Grey cterm=NONE
+ hi PmenuKindSel ctermfg=DarkRed ctermbg=Blue cterm=NONE
+ hi PmenuExtra ctermfg=DarkGray ctermbg=Grey cterm=NONE
+ hi PmenuExtraSel ctermfg=White ctermbg=Blue cterm=NONE
+ hi SignColumn ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=DarkGray ctermbg=White cterm=bold,reverse
+ hi StatusLineNC ctermfg=Grey ctermbg=DarkGray cterm=reverse
+ hi TabLine ctermfg=DarkGray ctermbg=Grey cterm=NONE
+ hi TabLineFill ctermfg=White ctermbg=Grey cterm=NONE
+ hi TabLineSel ctermfg=DarkGray ctermbg=White cterm=bold
+ hi ToolbarButton ctermfg=Black ctermbg=Grey cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=DarkGray ctermbg=Grey cterm=NONE
+ hi Visual ctermfg=White ctermbg=Blue cterm=NONE
+ hi WildMenu ctermfg=Black ctermbg=White cterm=bold
+ hi EndOfBuffer ctermfg=Grey ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=Green ctermbg=White cterm=reverse
+ hi DiffChange ctermfg=Cyan ctermbg=White cterm=reverse
+ hi DiffDelete ctermfg=Red ctermbg=White cterm=reverse
+ hi DiffText ctermfg=Yellow ctermbg=White cterm=reverse
+ hi Directory ctermfg=Green ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=Magenta ctermbg=White cterm=reverse
+ hi ModeMsg ctermfg=Black ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=Black ctermbg=NONE cterm=bold
+ hi Question ctermfg=Magenta ctermbg=NONE cterm=bold
+ hi Search ctermfg=DarkGreen ctermbg=White cterm=reverse
+ hi QuickFixLine ctermfg=Cyan ctermbg=White cterm=reverse
+ hi SpellBad ctermfg=Red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=Blue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=Cyan ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=Magenta ctermbg=NONE cterm=underline
+ hi Title ctermfg=Green ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=Red ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Character ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Define ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=White cterm=bold,reverse
+ hi Exception ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Float ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Function ctermfg=Green ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Label ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Number ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=Red ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Special ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi String ctermfg=Green ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=White cterm=bold
+ hi Type ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=Blue ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 8
+ if &background ==# 'dark'
+ hi Normal ctermfg=gray ctermbg=Black cterm=NONE
+ hi ColorColumn ctermfg=Black ctermbg=gray cterm=NONE
+ hi Comment ctermfg=gray ctermbg=NONE cterm=bold
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorLineNr ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=Black cterm=reverse
+ hi ErrorMsg ctermfg=Black ctermbg=Red cterm=NONE
+ hi FoldColumn ctermfg=gray ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=gray ctermbg=NONE cterm=NONE
+ hi LineNr ctermfg=gray ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=gray ctermbg=NONE cterm=bold,underline
+ hi NonText ctermfg=gray ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=DarkGray ctermbg=White cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=DarkGray cterm=NONE
+ hi PmenuSel ctermfg=Black ctermbg=Blue cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=Blue cterm=NONE
+ hi PmenuKind ctermfg=Red ctermbg=White cterm=NONE
+ hi PmenuKindSel ctermfg=Red ctermbg=Blue cterm=NONE
+ hi PmenuExtra ctermfg=DarkGray ctermbg=White cterm=NONE
+ hi PmenuExtraSel ctermfg=DarkGray ctermbg=Blue cterm=NONE
+ hi SignColumn ctermfg=gray ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=gray ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=gray ctermbg=Black cterm=bold,reverse
+ hi StatusLineNC ctermfg=gray ctermbg=Black cterm=reverse
+ hi TabLine ctermfg=Black ctermbg=gray cterm=NONE
+ hi TabLineFill ctermfg=Black ctermbg=gray cterm=NONE
+ hi TabLineSel ctermfg=gray ctermbg=Black cterm=NONE
+ hi ToolbarButton ctermfg=Black ctermbg=gray cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=Black ctermbg=gray cterm=NONE
+ hi Visual ctermfg=Black ctermbg=Blue cterm=NONE
+ hi WildMenu ctermfg=Blue ctermbg=DarkGray cterm=bold
+ hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=Green ctermbg=Black cterm=reverse
+ hi DiffChange ctermfg=Cyan ctermbg=Black cterm=reverse
+ hi DiffDelete ctermfg=Red ctermbg=Black cterm=reverse
+ hi DiffText ctermfg=Yellow ctermbg=Black cterm=reverse
+ hi Directory ctermfg=Green ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=Magenta ctermbg=Black cterm=reverse
+ hi ModeMsg ctermfg=Yellow ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=Yellow ctermbg=NONE cterm=bold
+ hi Question ctermfg=Magenta ctermbg=NONE cterm=bold
+ hi Search ctermfg=DarkGreen ctermbg=Black cterm=reverse
+ hi QuickFixLine ctermfg=Cyan ctermbg=Black cterm=reverse
+ hi SpellBad ctermfg=Red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=Blue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=Cyan ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=Magenta ctermbg=NONE cterm=underline
+ hi Title ctermfg=Green ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=Red ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Character ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Define ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=Black cterm=bold,reverse
+ hi Exception ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Float ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Function ctermfg=Green ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Label ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Number ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=Red ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Special ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi String ctermfg=Green ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=Black cterm=bold
+ hi Type ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=Blue ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ else
+ " Light background
+ hi Normal ctermfg=Black ctermbg=Grey cterm=NONE
+ hi ColorColumn ctermfg=White ctermbg=Black cterm=NONE
+ hi Comment ctermfg=Black ctermbg=NONE cterm=bold
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorLineNr ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=White cterm=reverse
+ hi ErrorMsg ctermfg=White ctermbg=Red cterm=NONE
+ hi FoldColumn ctermfg=Black ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=Black ctermbg=NONE cterm=NONE
+ hi LineNr ctermfg=Black ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=Black ctermbg=NONE cterm=bold,underline
+ hi NonText ctermfg=Black ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=Grey ctermbg=Black cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=Grey cterm=NONE
+ hi PmenuSel ctermfg=White ctermbg=Blue cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=Blue cterm=NONE
+ hi PmenuKind ctermfg=Red ctermbg=Black cterm=NONE
+ hi PmenuKindSel ctermfg=Red ctermbg=Blue cterm=NONE
+ hi PmenuExtra ctermfg=Grey ctermbg=Black cterm=NONE
+ hi PmenuExtraSel ctermfg=Grey ctermbg=Blue cterm=NONE
+ hi SignColumn ctermfg=Black ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=Black ctermbg=NONE cterm=NONE
+ hi StatusLine ctermfg=Black ctermbg=White cterm=bold,reverse
+ hi StatusLineNC ctermfg=Black ctermbg=White cterm=reverse
+ hi TabLine ctermfg=White ctermbg=Black cterm=NONE
+ hi TabLineFill ctermfg=White ctermbg=Black cterm=NONE
+ hi TabLineSel ctermfg=Black ctermbg=White cterm=NONE
+ hi ToolbarButton ctermfg=White ctermbg=Black cterm=bold
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=White ctermbg=Black cterm=NONE
+ hi Visual ctermfg=White ctermbg=Blue cterm=NONE
+ hi WildMenu ctermfg=Blue ctermbg=Grey cterm=bold
+ hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Conceal ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi DiffAdd ctermfg=Green ctermbg=White cterm=reverse
+ hi DiffChange ctermfg=Cyan ctermbg=White cterm=reverse
+ hi DiffDelete ctermfg=Red ctermbg=White cterm=reverse
+ hi DiffText ctermfg=Yellow ctermbg=White cterm=reverse
+ hi Directory ctermfg=Green ctermbg=NONE cterm=bold
+ hi IncSearch ctermfg=Magenta ctermbg=White cterm=reverse
+ hi ModeMsg ctermfg=Black ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=Black ctermbg=NONE cterm=bold
+ hi Question ctermfg=Magenta ctermbg=NONE cterm=bold
+ hi Search ctermfg=DarkGreen ctermbg=White cterm=reverse
+ hi QuickFixLine ctermfg=Cyan ctermbg=White cterm=reverse
+ hi SpellBad ctermfg=Red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=Blue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=Cyan ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=Magenta ctermbg=NONE cterm=underline
+ hi Title ctermfg=Green ctermbg=NONE cterm=bold
+ hi WarningMsg ctermfg=Red ctermbg=NONE cterm=bold
+ hi Boolean ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Character ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Conditional ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Define ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Debug ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Delimiter ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Error ctermfg=Red ctermbg=White cterm=bold,reverse
+ hi Exception ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Float ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Function ctermfg=Green ctermbg=NONE cterm=bold
+ hi Identifier ctermfg=Blue ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=fg ctermbg=NONE cterm=NONE
+ hi Include ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Keyword ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Label ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Macro ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Number ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Operator ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreCondit ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Repeat ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialChar ctermfg=Red ctermbg=NONE cterm=NONE
+ hi SpecialComment ctermfg=Red ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=Red ctermbg=NONE cterm=NONE
+ hi StorageClass ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi Special ctermfg=Magenta ctermbg=NONE cterm=NONE
+ hi String ctermfg=Green ctermbg=NONE cterm=NONE
+ hi Structure ctermfg=Cyan ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=fg ctermbg=White cterm=bold
+ hi Type ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Typedef ctermfg=Yellow ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=Blue ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=reverse
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 0
+ hi Normal term=NONE
+ hi ColorColumn term=reverse
+ hi Conceal term=NONE
+ hi Cursor term=reverse
+ hi CursorColumn term=NONE
+ hi CursorLine term=underline
+ hi CursorLineNr term=bold
+ hi DiffAdd term=reverse
+ hi DiffChange term=NONE
+ hi DiffDelete term=reverse
+ hi DiffText term=reverse
+ hi Directory term=NONE
+ hi EndOfBuffer term=NONE
+ hi ErrorMsg term=bold,reverse
+ hi FoldColumn term=NONE
+ hi Folded term=NONE
+ hi IncSearch term=bold,reverse,underline
+ hi LineNr term=NONE
+ hi MatchParen term=bold,underline
+ hi ModeMsg term=bold
+ hi MoreMsg term=NONE
+ hi NonText term=NONE
+ hi Pmenu term=reverse
+ hi PmenuSbar term=reverse
+ hi PmenuSel term=bold
+ hi PmenuThumb term=NONE
+ hi Question term=standout
+ hi Search term=reverse
+ hi SignColumn term=reverse
+ hi SpecialKey term=bold
+ hi SpellBad term=underline
+ hi SpellCap term=underline
+ hi SpellLocal term=underline
+ hi SpellRare term=underline
+ hi StatusLine term=bold,reverse
+ hi StatusLineNC term=bold,underline
+ hi TabLine term=bold,underline
+ hi TabLineFill term=NONE
+ hi Terminal term=NONE
+ hi TabLineSel term=bold,reverse
+ hi Title term=NONE
+ hi VertSplit term=NONE
+ hi Visual term=reverse
+ hi VisualNOS term=NONE
+ hi WarningMsg term=standout
+ hi WildMenu term=bold
+ hi CursorIM term=NONE
+ hi ToolbarLine term=reverse
+ hi ToolbarButton term=bold,reverse
+ hi CurSearch term=reverse
+ hi CursorLineFold term=underline
+ hi CursorLineSign term=underline
+ hi Comment term=bold
+ hi Constant term=NONE
+ hi Error term=bold,reverse
+ hi Identifier term=NONE
+ hi Ignore term=NONE
+ hi PreProc term=NONE
+ hi Special term=NONE
+ hi Statement term=NONE
+ hi Todo term=bold,reverse
+ hi Type term=NONE
+ hi Underlined term=underline
+ unlet s:t_Co
+ finish
+endif
+
+" Color: neutralred #cc241d 160 DarkRed
+" Color: neutralgreen #98971a 100 DarkGreen
+" Color: neutralyellow #d79921 172 DarkYellow
+" Color: neutralblue #458588 66 DarkBlue
+" Color: neutralpurple #b16286 132 DarkMagenta
+" Color: neutralaqua #689d6a 71 DarkCyan
+" Color: neutralorange #d65d0e 166 LightRed
+" Background: dark
+" Color: bg0 #1c1c1c 234 Black
+" Color: bg1 #3c3836 237 DarkGray
+" Color: bg2 #504945 239 DarkGray
+" Color: bg3 #303030 236
+" Color: bg4 #7c6f64 243
+" Color: bg5 #000000 16 DarkGray
+" Color: bg6 #121212 233 DarkGray
+" Color: fg0 #fbf1c7 230 White
+" Color: fg1 #ebdbb2 187 White
+" Color: fg2 #d5c4a1 187
+" Color: fg3 #bdae93 144
+" Color: fg4 #a89984 102 gray
+" Color: grey #928374 102 DarkGray
+" Color: red #fb4934 203 Red
+" Color: green #b8bb26 142 Green
+" Color: yellow #fabd2f 214 Yellow
+" Color: blue #83a598 109 Blue
+" Color: purple #d3869b 175 Magenta
+" Color: aqua #8ec07c 107 Cyan
+" Color: orange #fe8019 208 Magenta
+" Term colors: bg0 neutralred neutralgreen neutralyellow neutralblue neutralpurple neutralaqua fg4
+" Term colors: grey red green yellow blue purple aqua fg1
+" Background: light
+" Color: bg0 #fbf1c7 230 White
+" Color: bg1 #ebdbb2 187 Grey
+" Color: bg2 #e5d4b1 188 Grey
+" Color: bg3 #bdae93 144
+" Color: bg4 #a89984 137
+" Color: bg5 #ebe1b7 229 Grey
+" Color: bg6 #ffffd7 231 Grey
+" Color: fg0 #282828 235 DarkGray
+" Color: fg1 #3c3836 237 Black
+" Color: fg2 #503836 237
+" Color: fg3 #665c54 59
+" Color: fg4 #7c6f64 243 Black
+" Color: grey #928374 102 DarkGray
+" Color: red #9d0006 124 Red
+" Color: green #79740e 64 Green
+" Color: yellow #b57614 172 Yellow
+" Color: blue #076678 23 Blue
+" Color: purple #8f3f71 126 Magenta
+" Color: aqua #427b58 29 Cyan
+" Color: orange #ff5f00 202 Magenta
+" Term colors: fg1 neutralred neutralgreen neutralyellow neutralblue neutralpurple neutralaqua fg4
+" Term colors: grey red green yellow blue purple aqua bg0
+" Background: any
+" vim: et ts=2 sw=2
diff --git a/runtime/colors/sorbet.vim b/runtime/colors/sorbet.vim
new file mode 100644
index 0000000000..38cb141243
--- /dev/null
+++ b/runtime/colors/sorbet.vim
@@ -0,0 +1,448 @@
+" Name: sorbet
+" Description: A shallow grave, a monument to the ruined age.
+" Author: Maxence Weynans <neutaaaaan@gmail.com>
+" Maintainer: Maxence Weynans <neutaaaaan@gmail.com>
+" Website: https://github.com/vim/colorschemes
+" License: Vim License (see `:help license`)`
+" Last Updated: Wed 15 Mar 2023 05:40:19 PM CET
+
+" Generated by Colortemplate v2.2.0
+
+set background=dark
+
+hi clear
+let g:colors_name = 'sorbet'
+
+let s:t_Co = &t_Co
+
+hi! link Terminal Normal
+hi! link StatusLineTerm StatusLine
+hi! link StatusLineTermNC StatusLineNC
+hi! link MessageWindow Pmenu
+hi! link PopupNotification Todo
+hi! link Boolean Constant
+hi! link Character Constant
+hi! link Conditional Statement
+hi! link Define PreProc
+hi! link Debug Special
+hi! link Delimiter Special
+hi! link ErrorMsg Error
+hi! link Exception Statement
+hi! link Float Constant
+hi! link Function Identifier
+hi! link Include PreProc
+hi! link Keyword Statement
+hi! link Label Statement
+hi! link Macro PreProc
+hi! link Number Constant
+hi! link Operator Statement
+hi! link PreCondit PreProc
+hi! link Repeat Statement
+hi! link SpecialChar Special
+hi! link SpecialComment Special
+hi! link StorageClass Type
+hi! link Structure Type
+hi! link Tag Special
+hi! link Typedef Type
+hi! link lCursor Cursor
+hi! link debugPC CursorLine
+
+if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#000000', '#d75f5f', '#87d75f', '#d7af5f', '#87afd7', '#af87d7', '#5fafaf', '#dadada', '#707070', '#ff5f5f', '#87ff5f', '#ffd75f', '#87d7ff', '#d787ff', '#5fd7d7', '#ffffff']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+endif
+hi Normal guifg=#dadada guibg=#161821 gui=NONE cterm=NONE
+hi ColorColumn guifg=NONE guibg=#262831 gui=NONE cterm=NONE
+hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+hi CurSearch guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
+hi Cursor guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+hi CursorColumn guifg=NONE guibg=#363841 gui=NONE cterm=NONE
+hi CursorLine guifg=NONE guibg=#363841 gui=NONE cterm=NONE
+hi CursorLineNr guifg=#d7d7ff guibg=#363841 gui=NONE cterm=NONE
+hi DiffAdd guifg=#00af5f guibg=#000000 gui=reverse cterm=reverse
+hi DiffChange guifg=#87afff guibg=#000000 gui=reverse cterm=reverse
+hi DiffDelete guifg=#d7005f guibg=#000000 gui=reverse cterm=reverse
+hi DiffText guifg=#ff87ff guibg=#000000 gui=reverse cterm=reverse
+hi Directory guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi EndOfBuffer guifg=#5f5f87 guibg=NONE gui=NONE cterm=NONE
+hi FoldColumn guifg=#8787af guibg=NONE gui=NONE cterm=NONE
+hi Folded guifg=#5f5f87 guibg=#161821 gui=NONE cterm=NONE
+hi IncSearch guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
+hi LineNr guifg=#5f5f87 guibg=NONE gui=NONE cterm=NONE
+hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold
+hi ModeMsg guifg=#dadada guibg=NONE gui=bold cterm=bold
+hi MoreMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi NonText guifg=#707070 guibg=NONE gui=NONE cterm=NONE
+hi Pmenu guifg=#000000 guibg=#a6a8b1 gui=NONE cterm=NONE
+hi PmenuExtra guifg=#000000 guibg=#a6a8b1 gui=NONE cterm=NONE
+hi PmenuKind guifg=#000000 guibg=#a6a8b1 gui=bold cterm=bold
+hi PmenuSbar guifg=#707070 guibg=#5f5f87 gui=NONE cterm=NONE
+hi PmenuSel guifg=#000000 guibg=#d7d7ff gui=NONE cterm=NONE
+hi PmenuExtraSel guifg=#000000 guibg=#d7d7ff gui=NONE cterm=NONE
+hi PmenuKindSel guifg=#000000 guibg=#d7d7ff gui=bold cterm=bold
+hi PmenuThumb guifg=#dadada guibg=#d7d7ff gui=NONE cterm=NONE
+hi Question guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi QuickFixLine guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
+hi Search guifg=#00afff guibg=#000000 gui=reverse cterm=reverse
+hi SignColumn guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi SpecialKey guifg=#5f5f87 guibg=NONE gui=bold cterm=bold
+hi SpellBad guifg=#d75f5f guibg=NONE guisp=#d75f5f gui=undercurl cterm=underline
+hi SpellCap guifg=#87afd7 guibg=NONE guisp=#87afd7 gui=undercurl cterm=underline
+hi SpellLocal guifg=#af87d7 guibg=NONE guisp=#af87d7 gui=undercurl cterm=underline
+hi SpellRare guifg=#5fafaf guibg=NONE guisp=#5fafaf gui=undercurl cterm=underline
+hi StatusLine guifg=#000000 guibg=#d7d7ff gui=bold cterm=bold
+hi StatusLineNC guifg=#8787af guibg=#000000 gui=reverse cterm=reverse
+hi TabLine guifg=#8787af guibg=#000000 gui=reverse cterm=reverse
+hi TabLineFill guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi TabLineSel guifg=#000000 guibg=#d7d7ff gui=bold cterm=bold
+hi Title guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+hi VertSplit guifg=#5f5f87 guibg=NONE gui=NONE cterm=NONE
+hi Visual guifg=#ffaf00 guibg=#000000 gui=reverse cterm=reverse
+hi VisualNOS guifg=NONE guibg=#363841 gui=NONE cterm=NONE
+hi WarningMsg guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi WildMenu guifg=#d7d7ff guibg=#161821 gui=bold cterm=bold
+hi Comment guifg=#af87d7 guibg=NONE gui=NONE cterm=NONE
+hi Constant guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE
+hi Error guifg=#ff5f5f guibg=#000000 gui=bold,reverse cterm=bold,reverse
+hi Identifier guifg=#87d75f guibg=NONE gui=NONE cterm=NONE
+hi Ignore guifg=#dadada guibg=NONE gui=NONE cterm=NONE
+hi PreProc guifg=#87afd7 guibg=NONE gui=NONE cterm=NONE
+hi Special guifg=#5fafaf guibg=NONE gui=NONE cterm=NONE
+hi Statement guifg=#87afd7 guibg=NONE gui=NONE cterm=NONE
+hi String guifg=#d7af5f guibg=NONE gui=NONE cterm=NONE
+hi Todo guifg=#5fd7af guibg=NONE gui=bold,reverse cterm=bold,reverse
+hi Type guifg=#87afd7 guibg=NONE gui=NONE cterm=NONE
+hi Underlined guifg=#dadada guibg=NONE gui=underline cterm=underline
+hi CursorIM guifg=#000000 guibg=#afff00 gui=NONE cterm=NONE
+hi ToolbarLine guifg=NONE guibg=#000000 gui=NONE cterm=NONE
+hi ToolbarButton guifg=#dadada guibg=#000000 gui=bold cterm=bold
+hi DiffRemoved guifg=#d75f5f guibg=NONE gui=NONE cterm=NONE
+hi debugBreakpoint guifg=#8787af guibg=#000000 gui=bold,reverse cterm=bold,reverse
+
+if s:t_Co >= 256
+ hi Normal ctermfg=253 ctermbg=233 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=235 cterm=NONE
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CurSearch ctermfg=207 ctermbg=16 cterm=reverse
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi CursorColumn ctermfg=NONE ctermbg=237 cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=237 cterm=NONE
+ hi CursorLineNr ctermfg=189 ctermbg=237 cterm=NONE
+ hi DiffAdd ctermfg=35 ctermbg=16 cterm=reverse
+ hi DiffChange ctermfg=111 ctermbg=16 cterm=reverse
+ hi DiffDelete ctermfg=161 ctermbg=16 cterm=reverse
+ hi DiffText ctermfg=213 ctermbg=16 cterm=reverse
+ hi Directory ctermfg=253 ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=60 ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=103 ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=60 ctermbg=233 cterm=NONE
+ hi IncSearch ctermfg=214 ctermbg=16 cterm=reverse
+ hi LineNr ctermfg=60 ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold
+ hi ModeMsg ctermfg=253 ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=253 ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=242 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuExtra ctermfg=16 ctermbg=248 cterm=NONE
+ hi PmenuKind ctermfg=16 ctermbg=248 cterm=bold
+ hi PmenuSbar ctermfg=242 ctermbg=60 cterm=NONE
+ hi PmenuSel ctermfg=16 ctermbg=189 cterm=NONE
+ hi PmenuExtraSel ctermfg=16 ctermbg=189 cterm=NONE
+ hi PmenuKindSel ctermfg=16 ctermbg=189 cterm=bold
+ hi PmenuThumb ctermfg=253 ctermbg=189 cterm=NONE
+ hi Question ctermfg=253 ctermbg=NONE cterm=NONE
+ hi QuickFixLine ctermfg=207 ctermbg=16 cterm=reverse
+ hi Search ctermfg=39 ctermbg=16 cterm=reverse
+ hi SignColumn ctermfg=253 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=60 ctermbg=NONE cterm=bold
+ hi SpellBad ctermfg=167 ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=110 ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=140 ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=73 ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=16 ctermbg=189 cterm=bold
+ hi StatusLineNC ctermfg=103 ctermbg=16 cterm=reverse
+ hi TabLine ctermfg=103 ctermbg=16 cterm=reverse
+ hi TabLineFill ctermfg=253 ctermbg=NONE cterm=NONE
+ hi TabLineSel ctermfg=16 ctermbg=189 cterm=bold
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=60 ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=214 ctermbg=16 cterm=reverse
+ hi VisualNOS ctermfg=NONE ctermbg=237 cterm=NONE
+ hi WarningMsg ctermfg=253 ctermbg=NONE cterm=NONE
+ hi WildMenu ctermfg=189 ctermbg=233 cterm=bold
+ hi Comment ctermfg=140 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=167 ctermbg=NONE cterm=NONE
+ hi Error ctermfg=203 ctermbg=16 cterm=bold,reverse
+ hi Identifier ctermfg=113 ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=253 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=110 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=73 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=110 ctermbg=NONE cterm=NONE
+ hi String ctermfg=179 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=79 ctermbg=NONE cterm=bold,reverse
+ hi Type ctermfg=110 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=253 ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=16 ctermbg=154 cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=16 cterm=NONE
+ hi ToolbarButton ctermfg=253 ctermbg=16 cterm=bold
+ hi DiffRemoved ctermfg=167 ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=103 ctermbg=16 cterm=bold,reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 16
+ hi CurSearch ctermfg=magenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=yellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi Search ctermfg=cyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi TabLine ctermfg=darkgrey ctermbg=NONE cterm=reverse
+ hi VertSplit ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold
+ hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse
+ hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
+ hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Question ctermfg=NONE ctermbg=NONE cterm=standout
+ hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
+ hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Comment ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
+ hi Identifier ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi String ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkgreen ctermbg=black cterm=bold,reverse
+ hi Type ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi ToolbarButton ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi DiffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 8
+ hi CurSearch ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi EndOfBuffer ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Search ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi SpecialKey ctermfg=NONE ctermbg=NONE cterm=bold
+ hi StatusLineNC ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi TabLine ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi VertSplit ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Normal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi CursorColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi CursorLineNr ctermfg=NONE ctermbg=NONE cterm=bold
+ hi DiffAdd ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi DiffChange ctermfg=darkblue ctermbg=black cterm=reverse
+ hi DiffDelete ctermfg=darkred ctermbg=black cterm=reverse
+ hi DiffText ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi Directory ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuExtra ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi PmenuKind ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi PmenuSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuExtraSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuKindSel ctermfg=NONE ctermbg=NONE cterm=bold
+ hi PmenuThumb ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Question ctermfg=NONE ctermbg=NONE cterm=standout
+ hi QuickFixLine ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellBad ctermfg=darkred ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=darkblue ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=darkmagenta ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=darkcyan ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi TabLineFill ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi TabLineSel ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi VisualNOS ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=standout
+ hi WildMenu ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Comment ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi Error ctermfg=darkred ctermbg=black cterm=bold,reverse
+ hi Identifier ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi String ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkgreen ctermbg=black cterm=bold,reverse
+ hi Type ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorIM ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi ToolbarButton ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ hi DiffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=NONE ctermbg=NONE cterm=bold,reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 0
+ hi Normal term=NONE
+ hi ColorColumn term=reverse
+ hi Conceal term=NONE
+ hi Cursor term=reverse
+ hi CursorColumn term=NONE
+ hi CursorLine term=underline
+ hi CursorLineNr term=bold
+ hi DiffAdd term=reverse
+ hi DiffChange term=NONE
+ hi DiffDelete term=reverse
+ hi DiffText term=reverse
+ hi Directory term=NONE
+ hi EndOfBuffer term=NONE
+ hi ErrorMsg term=bold,reverse
+ hi FoldColumn term=NONE
+ hi Folded term=NONE
+ hi IncSearch term=bold,reverse,underline
+ hi LineNr term=NONE
+ hi MatchParen term=bold,underline
+ hi ModeMsg term=bold
+ hi MoreMsg term=NONE
+ hi NonText term=NONE
+ hi Pmenu term=reverse
+ hi PmenuSbar term=reverse
+ hi PmenuSel term=bold
+ hi PmenuThumb term=NONE
+ hi Question term=standout
+ hi Search term=reverse
+ hi SignColumn term=reverse
+ hi SpecialKey term=bold
+ hi SpellBad term=underline
+ hi SpellCap term=underline
+ hi SpellLocal term=underline
+ hi SpellRare term=underline
+ hi StatusLine term=bold,reverse
+ hi StatusLineNC term=bold,underline
+ hi TabLine term=bold,underline
+ hi TabLineFill term=NONE
+ hi Terminal term=NONE
+ hi TabLineSel term=bold,reverse
+ hi Title term=NONE
+ hi VertSplit term=NONE
+ hi Visual term=reverse
+ hi VisualNOS term=NONE
+ hi WarningMsg term=standout
+ hi WildMenu term=bold
+ hi CursorIM term=NONE
+ hi ToolbarLine term=reverse
+ hi ToolbarButton term=bold,reverse
+ hi CurSearch term=reverse
+ hi CursorLineFold term=underline
+ hi CursorLineSign term=underline
+ hi Comment term=bold
+ hi Constant term=NONE
+ hi Error term=bold,reverse
+ hi Identifier term=NONE
+ hi Ignore term=NONE
+ hi PreProc term=NONE
+ hi Special term=NONE
+ hi Statement term=NONE
+ hi Todo term=bold,reverse
+ hi Type term=NONE
+ hi Underlined term=underline
+ unlet s:t_Co
+ finish
+endif
+
+" Background: dark
+" Color: guibg #161821 233 black
+" Color: statusline #d7d7ff 189 white
+" Color: statuslineNC #8787af 103 grey
+" Color: darkuipurple #5f5f87 60 grey
+" Color: dark0 #000000 16 black
+" Color: dark1 #d75f5f 167 darkred
+" Color: dark2 #87d75f 113 darkgreen
+" Color: dark3 #d7af5f 179 darkyellow
+" Color: dark4 #87afd7 110 darkblue
+" Color: dark5 #af87d7 140 darkmagenta
+" Color: dark6 #5fafaf 73 darkcyan
+" Color: dark7 #dadada 253 grey
+" Color: dark8 #707070 242 darkgrey
+" Color: dark9 #ff5f5f 203 red
+" Color: dark10 #87ff5f 119 green
+" Color: dark11 #ffd75f 221 yellow
+" Color: dark12 #87d7ff 117 blue
+" Color: dark13 #d787ff 177 magenta
+" Color: dark14 #5fd7d7 80 cyan
+" Color: dark15 #ffffff 231 white
+" Color: diffred #d7005f 161 darkred
+" Color: diffgreen #00af5f 35 darkgreen
+" Color: diffblue #87afff 111 darkblue
+" Color: diffpink #ff87ff 213 darkmagenta
+" Color: uipink #ff00af 199 magenta
+" Color: uilime #afff00 154 green
+" Color: uiteal #5fd7af 79 green
+" Color: uiblue #00afff 39 blue
+" Color: uipurple #af00ff 129 darkmagenta
+" Color: uiamber #ffaf00 214 darkyellow
+" Color: uiblack #363841 237 darkgrey
+" Color: yasogrey #262831 235 black
+" Color: linenrblack #585858 240 darkgrey
+" Color: uicursearch #ff5fff 207 magenta
+" Color: invisigrey #a6a8b1 248 darkgrey
+" Color: errorred #ff5f5f 203 red
+" Term colors: dark0 dark1 dark2 dark3 dark4 dark5 dark6 dark7
+" Term colors: dark8 dark9 dark10 dark11 dark12 dark13 dark14 dark15
+" vim: et ts=2 sw=2
diff --git a/runtime/colors/wildcharm.vim b/runtime/colors/wildcharm.vim
new file mode 100644
index 0000000000..0f95ec88bb
--- /dev/null
+++ b/runtime/colors/wildcharm.vim
@@ -0,0 +1,761 @@
+" Name: Wild Charm
+" Description: Vibrant and playful, at least one popular AI thinks it is.
+" Author: Maxim Kim <habamax@gmail.com>
+" Maintainer: Maxim Kim <habamax@gmail.com>
+" Website: https://github.com/vim/colorschemes
+" License: Same as Vim
+" Last Updated: Sun 12 Mar 2023 18:46:06 AEDT
+
+" Generated by Colortemplate v2.2.0
+
+hi clear
+let g:colors_name = 'wildcharm'
+
+let s:t_Co = &t_Co
+
+hi! link Terminal Normal
+hi! link StatuslineTerm Statusline
+hi! link StatuslineTermNC StatuslineNC
+hi! link LineNrAbove LineNr
+hi! link LineNrBelow LineNr
+hi! link MessageWindow PMenu
+hi! link PopupNotification Todo
+hi! link CurSearch IncSearch
+if &background ==# 'dark'
+ if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#000000', '#d7005f', '#00af5f', '#d78700', '#0087d7', '#d787d7', '#00afaf', '#d0d0d0', '#767676', '#ff5f87', '#00d75f', '#ffaf00', '#00afff', '#ff87ff', '#00d7d7', '#ffffff']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+ endif
+ hi Normal guifg=#d0d0d0 guibg=#000000 gui=NONE cterm=NONE
+ hi Statusline guifg=#d0d0d0 guibg=#000000 gui=reverse cterm=reverse
+ hi StatuslineNC guifg=#767676 guibg=#000000 gui=reverse cterm=reverse
+ hi VertSplit guifg=#767676 guibg=#767676 gui=NONE cterm=NONE
+ hi TabLine guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE
+ hi TabLineFill guifg=NONE guibg=#767676 gui=NONE cterm=NONE
+ hi TabLineSel guifg=#ffffff guibg=#000000 gui=NONE cterm=NONE
+ hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE
+ hi QuickFixLine guifg=#000000 guibg=#ff87ff gui=NONE cterm=NONE
+ hi CursorLineNr guifg=#ffffff guibg=NONE gui=bold cterm=bold
+ hi LineNr guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi NonText guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi FoldColumn guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialKey guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi EndOfBuffer guifg=#585858 guibg=NONE gui=NONE cterm=NONE
+ hi EndOfBuffer guifg=#767676 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=#d0d0d0 guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#000000 guibg=#ffaf00 gui=NONE cterm=NONE
+ hi PmenuThumb guifg=NONE guibg=#d0d0d0 gui=NONE cterm=NONE
+ hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind guifg=#ff5f87 guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#d7005f guibg=#ffaf00 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#767676 guibg=#303030 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#000000 guibg=#ffaf00 gui=NONE cterm=NONE
+ hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error guifg=#ffffff guibg=#d7005f gui=NONE cterm=NONE
+ hi ErrorMsg guifg=#ffffff guibg=#d7005f gui=NONE cterm=NONE
+ hi ModeMsg guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg guifg=#00d75f guibg=NONE gui=NONE cterm=NONE
+ hi Question guifg=#ff87ff guibg=NONE gui=NONE cterm=NONE
+ hi WarningMsg guifg=#ffaf00 guibg=NONE gui=NONE cterm=NONE
+ hi Todo guifg=#875fff guibg=#000000 gui=reverse cterm=reverse
+ hi Search guifg=#000000 guibg=#00d75f gui=NONE cterm=NONE
+ hi IncSearch guifg=#000000 guibg=#ffaf00 gui=NONE cterm=NONE
+ hi WildMenu guifg=#000000 guibg=#ffaf00 gui=NONE cterm=NONE
+ hi debugPC guifg=#0087d7 guibg=NONE gui=reverse cterm=reverse
+ hi debugBreakpoint guifg=#00afaf guibg=NONE gui=reverse cterm=reverse
+ hi Cursor guifg=#ffffff guibg=#000000 gui=reverse cterm=reverse
+ hi lCursor guifg=#ff5fff guibg=#000000 gui=reverse cterm=reverse
+ hi Visual guifg=#000000 guibg=#0087d7 gui=NONE cterm=NONE
+ hi VisualNOS guifg=#000000 guibg=#00afff gui=NONE cterm=NONE
+ hi CursorLine guifg=NONE guibg=#262626 gui=NONE cterm=NONE
+ hi CursorColumn guifg=NONE guibg=#262626 gui=NONE cterm=NONE
+ hi Folded guifg=#767676 guibg=#303030 gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#303030 gui=NONE cterm=NONE
+ hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold
+ hi SpellBad guifg=NONE guibg=NONE guisp=#ff5f87 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellCap guifg=NONE guibg=NONE guisp=#00afaf gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellLocal guifg=NONE guibg=NONE guisp=#00af5f gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellRare guifg=NONE guibg=NONE guisp=#ff87ff gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Comment guifg=#767676 guibg=NONE gui=NONE cterm=NONE
+ hi Constant guifg=#ff5f87 guibg=NONE gui=NONE cterm=NONE
+ hi String guifg=#00d75f guibg=NONE gui=NONE cterm=NONE
+ hi Identifier guifg=#ff87ff guibg=NONE gui=NONE cterm=NONE
+ hi Statement guifg=#00afff guibg=NONE gui=NONE cterm=NONE
+ hi Type guifg=#ffaf00 guibg=NONE gui=NONE cterm=NONE
+ hi PreProc guifg=#00d7d7 guibg=NONE gui=NONE cterm=NONE
+ hi Special guifg=#875fff guibg=NONE gui=NONE cterm=NONE
+ hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory guifg=#00afff guibg=NONE gui=bold cterm=bold
+ hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd guifg=#afffaf guibg=#5f875f gui=NONE cterm=NONE
+ hi DiffChange guifg=#d0d0d0 guibg=#5f5f5f gui=NONE cterm=NONE
+ hi DiffText guifg=#afffff guibg=#5f8787 gui=NONE cterm=NONE
+ hi DiffDelete guifg=#ffafaf guibg=#875f5f gui=NONE cterm=NONE
+ hi diffAdded guifg=#00af5f guibg=NONE gui=NONE cterm=NONE
+ hi diffRemoved guifg=#d7005f guibg=NONE gui=NONE cterm=NONE
+else
+ " Light background
+ if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#000000', '#af0000', '#008700', '#af5f00', '#005faf', '#870087', '#008787', '#8a8a8a', '#808080', '#d70000', '#5faf5f', '#d78700', '#0087d7', '#af00af', '#00afaf', '#ffffff']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+ endif
+ hi Normal guifg=#000000 guibg=#ffffff gui=NONE cterm=NONE
+ hi Statusline guifg=#ffffff guibg=#5f5f5f gui=NONE cterm=NONE
+ hi StatuslineNC guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE
+ hi VertSplit guifg=#d0d0d0 guibg=#d0d0d0 gui=NONE cterm=NONE
+ hi TabLine guifg=#000000 guibg=#d0d0d0 gui=NONE cterm=NONE
+ hi TabLineFill guifg=NONE guibg=#808080 gui=NONE cterm=NONE
+ hi TabLineSel guifg=#ffffff guibg=#000000 gui=reverse cterm=reverse
+ hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton guifg=#ffffff guibg=#000000 gui=NONE cterm=NONE
+ hi QuickFixLine guifg=#ffffff guibg=#870087 gui=NONE cterm=NONE
+ hi CursorLineNr guifg=#000000 guibg=NONE gui=bold cterm=bold
+ hi LineNr guifg=#b2b2b2 guibg=NONE gui=NONE cterm=NONE
+ hi NonText guifg=#b2b2b2 guibg=NONE gui=NONE cterm=NONE
+ hi FoldColumn guifg=#b2b2b2 guibg=NONE gui=NONE cterm=NONE
+ hi EndOfBuffer guifg=#b2b2b2 guibg=NONE gui=NONE cterm=NONE
+ hi SpecialKey guifg=#b2b2b2 guibg=NONE gui=NONE cterm=NONE
+ hi Pmenu guifg=#000000 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuSel guifg=#ffffff guibg=#d78700 gui=NONE cterm=NONE
+ hi PmenuThumb guifg=NONE guibg=#808080 gui=NONE cterm=NONE
+ hi PmenuSbar guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind guifg=#d70000 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuKindSel guifg=#af0000 guibg=#d78700 gui=NONE cterm=NONE
+ hi PmenuExtra guifg=#808080 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi PmenuExtraSel guifg=#ffffff guibg=#d78700 gui=NONE cterm=NONE
+ hi SignColumn guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE
+ hi ErrorMsg guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE
+ hi ModeMsg guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg guifg=#008700 guibg=NONE gui=NONE cterm=NONE
+ hi Question guifg=#870087 guibg=NONE gui=NONE cterm=NONE
+ hi WarningMsg guifg=#af5f00 guibg=NONE gui=NONE cterm=NONE
+ hi Todo guifg=#8700ff guibg=#ffffff gui=reverse cterm=reverse
+ hi Search guifg=#ffffff guibg=#008700 gui=NONE cterm=NONE
+ hi IncSearch guifg=#ffffff guibg=#d78700 gui=NONE cterm=NONE
+ hi WildMenu guifg=#ffffff guibg=#d78700 gui=NONE cterm=NONE
+ hi debugPC guifg=#005faf guibg=NONE gui=reverse cterm=reverse
+ hi debugBreakpoint guifg=#008787 guibg=NONE gui=reverse cterm=reverse
+ hi Cursor guifg=#000000 guibg=#ffffff gui=reverse cterm=reverse
+ hi lCursor guifg=#ff00ff guibg=#000000 gui=reverse cterm=reverse
+ hi Visual guifg=#ffffff guibg=#0087d7 gui=NONE cterm=NONE
+ hi VisualNOS guifg=#ffffff guibg=#005faf gui=NONE cterm=NONE
+ hi CursorLine guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi CursorColumn guifg=NONE guibg=#eeeeee gui=NONE cterm=NONE
+ hi Folded guifg=#808080 guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi ColorColumn guifg=NONE guibg=#e4e4e4 gui=NONE cterm=NONE
+ hi MatchParen guifg=#ff00af guibg=NONE gui=bold cterm=bold
+ hi SpellBad guifg=NONE guibg=NONE guisp=#af0000 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellCap guifg=NONE guibg=NONE guisp=#008787 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellLocal guifg=NONE guibg=NONE guisp=#008700 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi SpellRare guifg=NONE guibg=NONE guisp=#af00af gui=undercurl ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Comment guifg=#8a8a8a guibg=NONE gui=NONE cterm=NONE
+ hi Constant guifg=#af0000 guibg=NONE gui=NONE cterm=NONE
+ hi String guifg=#008700 guibg=NONE gui=NONE cterm=NONE
+ hi Identifier guifg=#870087 guibg=NONE gui=NONE cterm=NONE
+ hi Statement guifg=#005faf guibg=NONE gui=NONE cterm=NONE
+ hi Type guifg=#af5f00 guibg=NONE gui=NONE cterm=NONE
+ hi PreProc guifg=#008787 guibg=NONE gui=NONE cterm=NONE
+ hi Special guifg=#8700ff guibg=NONE gui=NONE cterm=NONE
+ hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title guifg=NONE guibg=NONE gui=bold ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory guifg=#005faf guibg=NONE gui=bold cterm=bold
+ hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd guifg=#005f00 guibg=#afd7af gui=NONE cterm=NONE
+ hi DiffChange guifg=#262626 guibg=#dadada gui=NONE cterm=NONE
+ hi DiffText guifg=#005f5f guibg=#afd7d7 gui=NONE cterm=NONE
+ hi DiffDelete guifg=#875f5f guibg=#ffd7d7 gui=NONE cterm=NONE
+ hi diffAdded guifg=#008700 guibg=NONE gui=NONE cterm=NONE
+ hi diffRemoved guifg=#d70000 guibg=NONE gui=NONE cterm=NONE
+endif
+
+if s:t_Co >= 256
+ hi! link Terminal Normal
+ hi! link StatuslineTerm Statusline
+ hi! link StatuslineTermNC StatuslineNC
+ hi! link LineNrAbove LineNr
+ hi! link LineNrBelow LineNr
+ hi! link MessageWindow PMenu
+ hi! link PopupNotification Todo
+ hi! link CurSearch IncSearch
+ if &background ==# 'dark'
+ hi Normal ctermfg=252 ctermbg=16 cterm=NONE
+ hi Statusline ctermfg=252 ctermbg=16 cterm=reverse
+ hi StatuslineNC ctermfg=243 ctermbg=16 cterm=reverse
+ hi VertSplit ctermfg=243 ctermbg=243 cterm=NONE
+ hi TabLine ctermfg=16 ctermbg=252 cterm=NONE
+ hi TabLineFill ctermfg=NONE ctermbg=243 cterm=NONE
+ hi TabLineSel ctermfg=231 ctermbg=16 cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=16 ctermbg=231 cterm=NONE
+ hi QuickFixLine ctermfg=16 ctermbg=213 cterm=NONE
+ hi CursorLineNr ctermfg=231 ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=240 ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=240 ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=240 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=240 ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=240 ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=243 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=252 ctermbg=236 cterm=NONE
+ hi PmenuSel ctermfg=16 ctermbg=214 cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=252 cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind ctermfg=204 ctermbg=236 cterm=NONE
+ hi PmenuKindSel ctermfg=161 ctermbg=214 cterm=NONE
+ hi PmenuExtra ctermfg=243 ctermbg=236 cterm=NONE
+ hi PmenuExtraSel ctermfg=16 ctermbg=214 cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=231 ctermbg=161 cterm=NONE
+ hi ErrorMsg ctermfg=231 ctermbg=161 cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=41 ctermbg=NONE cterm=NONE
+ hi Question ctermfg=213 ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=214 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=99 ctermbg=16 cterm=reverse
+ hi Search ctermfg=16 ctermbg=41 cterm=NONE
+ hi IncSearch ctermfg=16 ctermbg=214 cterm=NONE
+ hi WildMenu ctermfg=16 ctermbg=214 cterm=NONE
+ hi debugPC ctermfg=32 ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=37 ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=16 ctermbg=32 cterm=NONE
+ hi VisualNOS ctermfg=16 ctermbg=39 cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=235 cterm=NONE
+ hi CursorColumn ctermfg=NONE ctermbg=235 cterm=NONE
+ hi Folded ctermfg=243 ctermbg=236 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=236 cterm=NONE
+ hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold
+ hi SpellBad ctermfg=161 ctermbg=231 cterm=reverse
+ hi SpellCap ctermfg=37 ctermbg=16 cterm=reverse
+ hi SpellLocal ctermfg=41 ctermbg=16 cterm=reverse
+ hi SpellRare ctermfg=213 ctermbg=16 cterm=reverse
+ hi Comment ctermfg=243 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=204 ctermbg=NONE cterm=NONE
+ hi String ctermfg=41 ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=213 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=39 ctermbg=NONE cterm=NONE
+ hi Type ctermfg=214 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=44 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=99 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=39 ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=157 ctermbg=65 cterm=NONE
+ hi DiffChange ctermfg=252 ctermbg=59 cterm=NONE
+ hi DiffText ctermfg=159 ctermbg=66 cterm=NONE
+ hi DiffDelete ctermfg=217 ctermbg=95 cterm=NONE
+ hi diffAdded ctermfg=35 ctermbg=NONE cterm=NONE
+ hi diffRemoved ctermfg=161 ctermbg=NONE cterm=NONE
+ else
+ " Light background
+ hi Normal ctermfg=16 ctermbg=231 cterm=NONE
+ hi Statusline ctermfg=231 ctermbg=59 cterm=NONE
+ hi StatuslineNC ctermfg=16 ctermbg=252 cterm=NONE
+ hi VertSplit ctermfg=252 ctermbg=252 cterm=NONE
+ hi TabLine ctermfg=16 ctermbg=252 cterm=NONE
+ hi TabLineFill ctermfg=NONE ctermbg=240 cterm=NONE
+ hi TabLineSel ctermfg=231 ctermbg=16 cterm=reverse
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=231 ctermbg=16 cterm=NONE
+ hi QuickFixLine ctermfg=231 ctermbg=90 cterm=NONE
+ hi CursorLineNr ctermfg=16 ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=249 ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=249 ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=249 ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=249 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=249 ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=16 ctermbg=254 cterm=NONE
+ hi PmenuSel ctermfg=231 ctermbg=172 cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=240 cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuKind ctermfg=160 ctermbg=254 cterm=NONE
+ hi PmenuKindSel ctermfg=124 ctermbg=172 cterm=NONE
+ hi PmenuExtra ctermfg=240 ctermbg=254 cterm=NONE
+ hi PmenuExtraSel ctermfg=231 ctermbg=172 cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=231 ctermbg=160 cterm=NONE
+ hi ErrorMsg ctermfg=231 ctermbg=160 cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=28 ctermbg=NONE cterm=NONE
+ hi Question ctermfg=90 ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=130 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=93 ctermbg=231 cterm=reverse
+ hi Search ctermfg=231 ctermbg=28 cterm=NONE
+ hi IncSearch ctermfg=231 ctermbg=172 cterm=NONE
+ hi WildMenu ctermfg=231 ctermbg=172 cterm=NONE
+ hi debugPC ctermfg=25 ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=30 ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=231 ctermbg=32 cterm=NONE
+ hi VisualNOS ctermfg=231 ctermbg=25 cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=255 cterm=NONE
+ hi CursorColumn ctermfg=NONE ctermbg=255 cterm=NONE
+ hi Folded ctermfg=240 ctermbg=254 cterm=NONE
+ hi ColorColumn ctermfg=NONE ctermbg=254 cterm=NONE
+ hi MatchParen ctermfg=199 ctermbg=NONE cterm=bold
+ hi SpellBad ctermfg=160 ctermbg=231 cterm=reverse
+ hi SpellCap ctermfg=30 ctermbg=231 cterm=reverse
+ hi SpellLocal ctermfg=28 ctermbg=231 cterm=reverse
+ hi SpellRare ctermfg=127 ctermbg=231 cterm=reverse
+ hi Comment ctermfg=245 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=124 ctermbg=NONE cterm=NONE
+ hi String ctermfg=28 ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=90 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=25 ctermbg=NONE cterm=NONE
+ hi Type ctermfg=130 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=30 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=93 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=25 ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=22 ctermbg=151 cterm=NONE
+ hi DiffChange ctermfg=235 ctermbg=253 cterm=NONE
+ hi DiffText ctermfg=23 ctermbg=152 cterm=NONE
+ hi DiffDelete ctermfg=95 ctermbg=224 cterm=NONE
+ hi diffAdded ctermfg=28 ctermbg=NONE cterm=NONE
+ hi diffRemoved ctermfg=160 ctermbg=NONE cterm=NONE
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 16
+ if &background ==# 'dark'
+ hi Normal ctermfg=grey ctermbg=black cterm=NONE
+ hi Statusline ctermfg=grey ctermbg=black cterm=reverse
+ hi StatuslineNC ctermfg=darkgrey ctermbg=black cterm=reverse
+ hi VertSplit ctermfg=darkgrey ctermbg=darkgrey cterm=NONE
+ hi TabLine ctermfg=black ctermbg=grey cterm=NONE
+ hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE
+ hi TabLineSel ctermfg=white ctermbg=black cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=black ctermbg=white cterm=NONE
+ hi QuickFixLine ctermfg=black ctermbg=magenta cterm=NONE
+ hi CursorLineNr ctermfg=white ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=grey ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=grey ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=grey ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=grey ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=darkgrey ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=white ctermbg=darkred cterm=NONE
+ hi ErrorMsg ctermfg=white ctermbg=darkred cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=green ctermbg=NONE cterm=NONE
+ hi Question ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=yellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkred ctermbg=black cterm=reverse
+ hi Search ctermfg=black ctermbg=green cterm=NONE
+ hi IncSearch ctermfg=black ctermbg=yellow cterm=NONE
+ hi WildMenu ctermfg=black ctermbg=yellow cterm=NONE
+ hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=black ctermbg=darkblue cterm=NONE
+ hi VisualNOS ctermfg=black ctermbg=blue cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=black ctermbg=yellow cterm=NONE
+ hi Folded ctermfg=black ctermbg=NONE cterm=bold
+ hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi SpellBad ctermfg=darkred ctermbg=white cterm=reverse
+ hi SpellCap ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi SpellLocal ctermfg=green ctermbg=black cterm=reverse
+ hi SpellRare ctermfg=magenta ctermbg=black cterm=reverse
+ hi Comment ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=red ctermbg=NONE cterm=NONE
+ hi String ctermfg=green ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=blue ctermbg=NONE cterm=NONE
+ hi Type ctermfg=yellow ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=blue ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=black ctermbg=lightgray cterm=NONE
+ hi DiffText ctermfg=black ctermbg=cyan cterm=NONE
+ hi DiffDelete ctermfg=black ctermbg=darkred cterm=NONE
+ hi diffAdded ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi diffRemoved ctermfg=darkred ctermbg=NONE cterm=NONE
+ else
+ " Light background
+ hi Normal ctermfg=black ctermbg=white cterm=NONE
+ hi Statusline ctermfg=white ctermbg=darkgrey cterm=NONE
+ hi StatuslineNC ctermfg=black ctermbg=lightgrey cterm=NONE
+ hi VertSplit ctermfg=lightgrey ctermbg=lightgrey cterm=NONE
+ hi TabLine ctermfg=black ctermbg=lightgrey cterm=NONE
+ hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE
+ hi TabLineSel ctermfg=white ctermbg=black cterm=reverse
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=white ctermbg=black cterm=NONE
+ hi QuickFixLine ctermfg=white ctermbg=darkmagenta cterm=NONE
+ hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=darkgrey ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=white ctermbg=red cterm=NONE
+ hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Question ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkred ctermbg=white cterm=reverse
+ hi Search ctermfg=white ctermbg=darkgreen cterm=NONE
+ hi IncSearch ctermfg=white ctermbg=yellow cterm=NONE
+ hi WildMenu ctermfg=white ctermbg=yellow cterm=NONE
+ hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=white ctermbg=blue cterm=NONE
+ hi VisualNOS ctermfg=white ctermbg=darkblue cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=black ctermbg=yellow cterm=NONE
+ hi Folded ctermfg=black ctermbg=NONE cterm=bold
+ hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi SpellBad ctermfg=red ctermbg=white cterm=reverse
+ hi SpellCap ctermfg=darkcyan ctermbg=white cterm=reverse
+ hi SpellLocal ctermfg=darkgreen ctermbg=white cterm=reverse
+ hi SpellRare ctermfg=magenta ctermbg=white cterm=reverse
+ hi Comment ctermfg=darkgrey ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi String ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=darkblue ctermbg=NONE cterm=NONE
+ hi Type ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=darkblue ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=black ctermbg=lightgray cterm=NONE
+ hi DiffText ctermfg=black ctermbg=cyan cterm=NONE
+ hi DiffDelete ctermfg=black ctermbg=darkred cterm=NONE
+ hi diffAdded ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi diffRemoved ctermfg=red ctermbg=NONE cterm=NONE
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 8
+ if &background ==# 'dark'
+ hi Normal ctermfg=grey ctermbg=black cterm=NONE
+ hi Statusline ctermfg=grey ctermbg=black cterm=bold,reverse
+ hi StatuslineNC ctermfg=black ctermbg=grey cterm=NONE
+ hi VertSplit ctermfg=grey ctermbg=grey cterm=NONE
+ hi TabLine ctermfg=grey ctermbg=black cterm=reverse
+ hi TabLineFill ctermfg=NONE ctermbg=grey cterm=NONE
+ hi TabLineSel ctermfg=grey ctermbg=black cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=grey ctermbg=black cterm=bold,reverse
+ hi QuickFixLine ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=black ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=grey ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=grey cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=black ctermbg=grey cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=grey ctermbg=darkred cterm=NONE
+ hi ErrorMsg ctermfg=grey ctermbg=darkred cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Question ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkred ctermbg=black cterm=reverse
+ hi Search ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi WildMenu ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=black ctermbg=darkblue cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi VisualNOS ctermfg=black ctermbg=darkblue cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi Folded ctermfg=black ctermbg=NONE cterm=bold
+ hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SpellBad ctermfg=darkred ctermbg=NONE cterm=reverse
+ hi SpellCap ctermfg=darkcyan ctermbg=NONE cterm=reverse
+ hi SpellLocal ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi SpellRare ctermfg=darkmagenta ctermbg=NONE cterm=reverse
+ hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi String ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=darkblue ctermbg=NONE cterm=bold
+ hi Type ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=darkblue ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi DiffText ctermfg=black ctermbg=grey cterm=NONE
+ hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE
+ else
+ " Light background
+ hi Normal ctermfg=black ctermbg=grey cterm=NONE
+ hi Statusline ctermfg=grey ctermbg=black cterm=bold
+ hi StatuslineNC ctermfg=grey ctermbg=darkgrey cterm=NONE
+ hi VertSplit ctermfg=black ctermbg=black cterm=NONE
+ hi TabLine ctermfg=black ctermbg=grey cterm=reverse
+ hi TabLineFill ctermfg=NONE ctermbg=darkgrey cterm=NONE
+ hi TabLineSel ctermfg=black ctermbg=grey cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=grey ctermbg=black cterm=bold
+ hi QuickFixLine ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi CursorLineNr ctermfg=black ctermbg=NONE cterm=bold
+ hi LineNr ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi NonText ctermfg=black ctermbg=NONE cterm=NONE
+ hi FoldColumn ctermfg=black ctermbg=NONE cterm=NONE
+ hi EndOfBuffer ctermfg=black ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=black ctermbg=NONE cterm=NONE
+ hi Pmenu ctermfg=grey ctermbg=black cterm=NONE
+ hi PmenuThumb ctermfg=NONE ctermbg=darkgreen cterm=NONE
+ hi PmenuSbar ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi PmenuSel ctermfg=NONE ctermbg=darkyellow cterm=NONE
+ hi PmenuKind ctermfg=darkred ctermbg=black cterm=NONE
+ hi PmenuKindSel ctermfg=darkred ctermbg=darkyellow cterm=NONE
+ hi PmenuExtra ctermfg=grey ctermbg=black cterm=NONE
+ hi PmenuExtraSel ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SignColumn ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Error ctermfg=white ctermbg=darkred cterm=NONE
+ hi ErrorMsg ctermfg=white ctermbg=darkred cterm=NONE
+ hi ModeMsg ctermfg=NONE ctermbg=NONE cterm=bold
+ hi MoreMsg ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Question ctermfg=darkmagenta ctermbg=NONE cterm=NONE
+ hi WarningMsg ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=darkred ctermbg=black cterm=reverse
+ hi Search ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi WildMenu ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi debugPC ctermfg=darkblue ctermbg=NONE cterm=reverse
+ hi debugBreakpoint ctermfg=darkcyan ctermbg=NONE cterm=reverse
+ hi Visual ctermfg=grey ctermbg=darkblue cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=bold,underline
+ hi VisualNOS ctermfg=black ctermbg=darkblue cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi Folded ctermfg=black ctermbg=NONE cterm=bold
+ hi ColorColumn ctermfg=black ctermbg=darkyellow cterm=NONE
+ hi SpellBad ctermfg=darkred ctermbg=black cterm=reverse
+ hi SpellCap ctermfg=darkcyan ctermbg=black cterm=reverse
+ hi SpellLocal ctermfg=darkgreen ctermbg=black cterm=reverse
+ hi SpellRare ctermfg=darkmagenta ctermbg=black cterm=reverse
+ hi Comment ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Constant ctermfg=darkred ctermbg=NONE cterm=NONE
+ hi String ctermfg=darkgreen ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=darkblue ctermbg=NONE cterm=bold
+ hi Type ctermfg=darkyellow ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi Title ctermfg=NONE ctermbg=NONE cterm=bold
+ hi Directory ctermfg=darkblue ctermbg=NONE cterm=bold
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi DiffAdd ctermfg=black ctermbg=darkmagenta cterm=NONE
+ hi DiffChange ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi DiffText ctermfg=grey ctermbg=black cterm=NONE
+ hi DiffDelete ctermfg=darkred ctermbg=NONE cterm=NONE
+ endif
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 0
+ hi Normal term=NONE
+ hi ColorColumn term=reverse
+ hi Conceal term=NONE
+ hi Cursor term=reverse
+ hi CursorColumn term=NONE
+ hi CursorLine term=underline
+ hi CursorLineNr term=bold
+ hi DiffAdd term=reverse
+ hi DiffChange term=NONE
+ hi DiffDelete term=reverse
+ hi DiffText term=reverse
+ hi Directory term=NONE
+ hi EndOfBuffer term=NONE
+ hi ErrorMsg term=bold,reverse
+ hi FoldColumn term=NONE
+ hi Folded term=NONE
+ hi IncSearch term=bold,reverse,underline
+ hi LineNr term=NONE
+ hi MatchParen term=bold,underline
+ hi ModeMsg term=bold
+ hi MoreMsg term=NONE
+ hi NonText term=NONE
+ hi Pmenu term=reverse
+ hi PmenuSbar term=reverse
+ hi PmenuSel term=bold
+ hi PmenuThumb term=NONE
+ hi Question term=standout
+ hi Search term=reverse
+ hi SignColumn term=reverse
+ hi SpecialKey term=bold
+ hi SpellBad term=underline
+ hi SpellCap term=underline
+ hi SpellLocal term=underline
+ hi SpellRare term=underline
+ hi StatusLine term=bold,reverse
+ hi StatusLineNC term=bold,underline
+ hi TabLine term=bold,underline
+ hi TabLineFill term=NONE
+ hi Terminal term=NONE
+ hi TabLineSel term=bold,reverse
+ hi Title term=NONE
+ hi VertSplit term=NONE
+ hi Visual term=reverse
+ hi VisualNOS term=NONE
+ hi WarningMsg term=standout
+ hi WildMenu term=bold
+ hi CursorIM term=NONE
+ hi ToolbarLine term=reverse
+ hi ToolbarButton term=bold,reverse
+ hi CurSearch term=reverse
+ hi CursorLineFold term=underline
+ hi CursorLineSign term=underline
+ hi Comment term=bold
+ hi Constant term=NONE
+ hi Error term=bold,reverse
+ hi Identifier term=NONE
+ hi Ignore term=NONE
+ hi PreProc term=NONE
+ hi Special term=NONE
+ hi Statement term=NONE
+ hi Todo term=bold,reverse
+ hi Type term=NONE
+ hi Underlined term=underline
+ unlet s:t_Co
+ finish
+endif
+
+" Background: any
+" Background: dark
+" Color: color00 #000000 16 black
+" Color: color08 #767676 243 darkgrey
+" Color: color01 #d7005f 161 darkred
+" Color: color09 #ff5f87 204 red
+" Color: color02 #00af5f 35 darkgreen
+" Color: color10 #00d75f 41 green
+" Color: color03 #d78700 172 darkyellow
+" Color: color11 #ffaf00 214 yellow
+" Color: color04 #0087d7 32 darkblue
+" Color: color12 #00afff 39 blue
+" Color: color05 #d787d7 176 darkmagenta
+" Color: color13 #ff87ff 213 magenta
+" Color: color06 #00afaf 37 darkcyan
+" Color: color14 #00d7d7 44 cyan
+" Color: color07 #d0d0d0 252 grey
+" Color: color15 #ffffff 231 white
+" Color: color16 #875fff 99 darkred
+" Color: colorLine #262626 235 darkgrey
+" Color: colorB #303030 236 darkgrey
+" Color: colorNonT #585858 240 grey
+" Color: colorTab #585858 240 grey
+" Color: colorC #ffffff 231 white
+" Color: colorlC #ff5fff 207 magenta
+" Color: colorDim #878787 102 grey
+" Color: colorMP #ff00af 199 magenta
+" Color: diffAdd #5f875f 65 darkmagenta
+" Color: diffAddFg #afffaf 157 black
+" Color: diffDelete #875f5f 95 darkred
+" Color: diffDeleteFg #ffafaf 217 black
+" Color: diffChange #5f5f5f 59 lightgray
+" Color: diffChangeFg #d0d0d0 252 black
+" Color: diffText #5f8787 66 cyan
+" Color: diffTextFg #afffff 159 black
+" Term colors: color00 color01 color02 color03 color04 color05 color06 color07
+" Term colors: color08 color09 color10 color11 color12 color13 color14 color15
+" Background: light
+" Color: color00 #000000 16 black
+" Color: color08 #808080 240 darkgrey
+" Color: color01 #af0000 124 darkred
+" Color: color09 #d70000 160 red
+" Color: color02 #008700 28 darkgreen
+" Color: color10 #5faf5f 71 green
+" Color: color03 #af5f00 130 darkyellow
+" Color: color11 #d78700 172 yellow
+" Color: color04 #005faf 25 darkblue
+" Color: color12 #0087d7 32 blue
+" Color: color05 #870087 90 darkmagenta
+" Color: color13 #af00af 127 magenta
+" Color: color06 #008787 30 darkcyan
+" Color: color14 #00afaf 37 cyan
+" Color: color07 #8a8a8a 245 grey
+" Color: color15 #ffffff 231 white
+" Color: color16 #8700ff 93 darkred
+" Color: colorCm #8a8a8a 245 darkgrey
+" Color: colorLine #EEEEEE 255 grey
+" Color: colorB #E4E4E4 254 grey
+" Color: colorNonT #b2b2b2 249 darkgrey
+" Color: colorTab #d0d0d0 252 lightgrey
+" Color: colorC #000000 16 black
+" Color: colorlC #FF00FF 201 magenta
+" Color: colorV #5F87AF 67 darkblue
+" Color: colorDim #626262 241 darkgrey
+" Color: colorSt #5f5f5f 59 darkgrey
+" Color: colorMP #ff00af 199 magenta
+" Color: diffAdd #afd7af 151 darkmagenta
+" Color: diffAddFg #005f00 22 black
+" Color: diffDelete #ffd7d7 224 darkred
+" Color: diffDeleteFg #875f5f 95 black
+" Color: diffChange #dadada 253 lightgray
+" Color: diffChangeFg #262626 235 black
+" Color: diffText #afd7d7 152 cyan
+" Color: diffTextFg #005f5f 23 black
+" Term colors: color00 color01 color02 color03 color04 color05 color06 color07
+" Term colors: color08 color09 color10 color11 color12 color13 color14 color15
+" Background: any
+" vim: et ts=2 sw=2
diff --git a/runtime/colors/zaibatsu.vim b/runtime/colors/zaibatsu.vim
new file mode 100644
index 0000000000..e029f1451b
--- /dev/null
+++ b/runtime/colors/zaibatsu.vim
@@ -0,0 +1,567 @@
+" Name: zaibatsu
+" Description: "If you believe the journalists, he's the single wealthiest individual, period. As rich as some zaibatsu. But there's the catch, really: is he an individual? In the sense that you are, or I am? No." -- William Gibson, Count Zero
+" Author: Romain Lafourcade <romainlafourcade@gmail.com>
+" Maintainer: Romain Lafourcade <romainlafourcade@gmail.com>
+" Website: https://github.com/vim/colorschemes
+" License: Same as Vim
+" Last Updated: Sun Mar 12 17:21:15 2023
+
+" Generated by Colortemplate v2.2.0
+
+set background=dark
+
+hi clear
+let g:colors_name = 'zaibatsu'
+
+let s:t_Co = &t_Co
+
+if (has('termguicolors') && &termguicolors) || has('gui_running')
+ let g:terminal_ansi_colors = ['#0e0024', '#d7005f', '#00af5f', '#ffaf00', '#5f5fff', '#d700ff', '#00afff', '#d7d5db', '#878092', '#ff5faf', '#00d700', '#ffd700', '#8787ff', '#ff87ff', '#00ffff', '#ffffff']
+ " Nvim uses g:terminal_color_{0-15} instead
+ for i in range(g:terminal_ansi_colors->len())
+ let g:terminal_color_{i} = g:terminal_ansi_colors[i]
+ endfor
+endif
+hi Normal guifg=#ffffff guibg=#0e0024 gui=NONE cterm=NONE
+hi EndOfBuffer guifg=#afafff guibg=#0e0024 gui=NONE cterm=NONE
+hi NonText guifg=#878092 guibg=#0e0024 gui=NONE cterm=NONE
+hi SpellBad guifg=NONE guibg=NONE guisp=#ff5faf gui=undercurl ctermfg=NONE ctermbg=NONE cterm=undercurl
+hi SpellCap guifg=NONE guibg=NONE guisp=#87ff00 gui=undercurl ctermfg=NONE ctermbg=NONE cterm=undercurl
+hi SpellLocal guifg=NONE guibg=NONE guisp=#ffffff gui=undercurl ctermfg=NONE ctermbg=NONE cterm=undercurl
+hi SpellRare guifg=NONE guibg=NONE guisp=#ffafff gui=undercurl ctermfg=NONE ctermbg=NONE cterm=undercurl
+hi StatusLine guifg=#0e0024 guibg=#ffffff gui=NONE cterm=NONE
+hi StatusLineNC guifg=#0e0024 guibg=#afaab6 gui=NONE cterm=NONE
+hi Pmenu guifg=#0e0024 guibg=#ffffff gui=NONE cterm=NONE
+hi PmenuSbar guifg=#d7d5db guibg=#d7d5db gui=NONE cterm=NONE
+hi PmenuSel guifg=#0e0024 guibg=#afafff gui=NONE cterm=NONE
+hi PmenuThumb guifg=#878092 guibg=#878092 gui=NONE cterm=NONE
+hi PmenuExtra guifg=#878092 guibg=#ffffff gui=NONE cterm=NONE
+hi! link PmenuExtraSel PmenuSel
+hi PmenuKind guifg=#878092 guibg=#ffffff gui=NONE cterm=NONE
+hi! link PmenuKindSel PmenuSel
+hi ColorColumn guifg=NONE guibg=#510039 gui=NONE cterm=NONE
+hi CursorLine guifg=NONE guibg=#362b49 gui=NONE cterm=NONE
+hi CursorColumn guifg=NONE guibg=#362b49 gui=NONE cterm=NONE
+hi WildMenu guifg=#0e0024 guibg=#afafff gui=NONE cterm=NONE
+hi Conceal guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+hi Cursor guifg=#0e0024 guibg=#ffff5f gui=NONE cterm=NONE
+hi CursorIM guifg=#0e0024 guibg=#ffff5f gui=NONE cterm=NONE
+hi Error guifg=#d70000 guibg=#ffffff gui=reverse cterm=reverse
+hi ErrorMsg guifg=#ffffff guibg=#d70000 gui=NONE cterm=NONE
+hi FoldColumn guifg=#00afff guibg=NONE gui=NONE cterm=NONE
+hi Folded guifg=#ffaf00 guibg=#0e0024 gui=reverse cterm=reverse
+hi IncSearch guifg=#ffaf00 guibg=#0e0024 gui=reverse cterm=reverse
+hi LineNr guifg=#afafff guibg=NONE gui=NONE cterm=NONE
+hi MatchParen guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+hi ModeMsg guifg=#0e0024 guibg=#87ff00 gui=NONE cterm=NONE
+hi MoreMsg guifg=#87ffff guibg=NONE gui=NONE cterm=NONE
+hi Question guifg=#87ff00 guibg=NONE gui=NONE cterm=NONE
+hi QuickFixLine guifg=#ffafff guibg=#0e0024 gui=reverse cterm=reverse
+hi Search guifg=#87ffff guibg=#0e0024 gui=reverse cterm=reverse
+hi SignColumn guifg=#00afff guibg=NONE gui=NONE cterm=NONE
+hi SpecialKey guifg=#878092 guibg=NONE gui=NONE cterm=NONE
+hi ToolbarButton guifg=#ffffff guibg=#5e556d gui=NONE cterm=NONE
+hi ToolbarLine guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+hi VertSplit guifg=#afafff guibg=NONE gui=NONE cterm=NONE
+hi Visual guifg=#0e0024 guibg=#5fd7ff gui=NONE cterm=NONE
+hi VisualNOS guifg=#0e0024 guibg=#ffffff gui=NONE cterm=NONE
+hi WarningMsg guifg=#ffafff guibg=NONE gui=NONE cterm=NONE
+hi debugBreakpoint guifg=#87ff00 guibg=#5f00d7 gui=reverse cterm=reverse
+hi debugPC guifg=#87ffff guibg=#5f00d7 gui=reverse cterm=reverse
+hi Directory guifg=#87ffff guibg=NONE gui=NONE cterm=NONE
+hi Title guifg=NONE guibg=NONE gui=NONE ctermfg=NONE ctermbg=NONE cterm=NONE
+hi Comment guifg=#afafff guibg=NONE gui=NONE cterm=NONE
+hi Constant guifg=#ffff5f guibg=NONE gui=NONE cterm=NONE
+hi Identifier guifg=#87ffff guibg=NONE gui=NONE cterm=NONE
+hi Ignore guifg=#878092 guibg=NONE gui=NONE cterm=NONE
+hi PreProc guifg=#00afff guibg=NONE gui=NONE cterm=NONE
+hi Special guifg=#87ff00 guibg=NONE gui=NONE cterm=NONE
+hi Statement guifg=#ffafff guibg=NONE gui=NONE cterm=NONE
+hi Todo guifg=NONE guibg=NONE gui=reverse ctermfg=NONE ctermbg=NONE cterm=reverse
+hi Type guifg=#ff5faf guibg=NONE gui=NONE cterm=NONE
+hi Underlined guifg=NONE guibg=NONE gui=underline ctermfg=NONE ctermbg=NONE cterm=underline
+hi! link CurSearch IncSearch
+hi! link CursorLineFold CursorLine
+hi! link CursorLineNr CursorLine
+hi! link CursorLineSign CursorLine
+hi! link LineNrAbove LineNr
+hi! link LineNrBelow LineNr
+hi! link StatusLineTerm StatusLine
+hi! link StatusLineTermNC StatusLineNC
+hi! link TabLine StatusLineNC
+hi! link TabLineFill StatusLineNC
+hi! link TabLineSel StatusLine
+hi! link Terminal Normal
+hi! link lCursor Cursor
+hi! link Boolean Constant
+hi! link Character Constant
+hi! link Conditional Statement
+hi! link Define PreProc
+hi! link Delimiter Special
+hi! link Exception Statement
+hi! link Float Constant
+hi! link Function Identifier
+hi! link Include PreProc
+hi! link Keyword Statement
+hi! link Label Statement
+hi! link Macro PreProc
+hi! link Number Constant
+hi! link Operator Statement
+hi! link PreCondit PreProc
+hi! link Repeat Statement
+hi! link SpecialChar Special
+hi! link SpecialComment Special
+hi! link StorageClass Type
+hi! link String Constant
+hi! link Structure Type
+hi! link Tag Special
+hi! link Typedef Type
+hi! link Terminal Normal
+hi! link MessageWindow Pmenu
+hi! link PopupNotification Todo
+hi DiffAdd guifg=#87ff00 guibg=#362b49 gui=reverse cterm=reverse
+hi DiffChange guifg=#afafff guibg=#362b49 gui=reverse cterm=reverse
+hi DiffText guifg=#d7d5db guibg=#362b49 gui=reverse cterm=reverse
+hi DiffDelete guifg=#ff5faf guibg=#362b49 gui=reverse cterm=reverse
+
+if s:t_Co >= 256
+ hi Normal ctermfg=231 ctermbg=16 cterm=NONE
+ hi EndOfBuffer ctermfg=147 ctermbg=16 cterm=NONE
+ hi NonText ctermfg=103 ctermbg=16 cterm=NONE
+ hi SpellBad ctermfg=205 ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=118 ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=231 ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=219 ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=16 ctermbg=231 cterm=NONE
+ hi StatusLineNC ctermfg=16 ctermbg=146 cterm=NONE
+ hi Pmenu ctermfg=16 ctermbg=231 cterm=NONE
+ hi PmenuSbar ctermfg=189 ctermbg=189 cterm=NONE
+ hi PmenuSel ctermfg=16 ctermbg=147 cterm=NONE
+ hi PmenuThumb ctermfg=103 ctermbg=103 cterm=NONE
+ hi PmenuExtra ctermfg=103 ctermbg=231 cterm=NONE
+ hi! link PmenuExtraSel PmenuSel
+ hi PmenuKind ctermfg=103 ctermbg=231 cterm=NONE
+ hi! link PmenuKindSel PmenuSel
+ hi ColorColumn ctermfg=NONE ctermbg=52 cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=237 cterm=NONE
+ hi CursorColumn ctermfg=NONE ctermbg=237 cterm=NONE
+ hi WildMenu ctermfg=16 ctermbg=147 cterm=NONE
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=16 ctermbg=227 cterm=NONE
+ hi CursorIM ctermfg=16 ctermbg=227 cterm=NONE
+ hi Error ctermfg=160 ctermbg=231 cterm=reverse
+ hi ErrorMsg ctermfg=231 ctermbg=160 cterm=NONE
+ hi FoldColumn ctermfg=39 ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=214 ctermbg=16 cterm=reverse
+ hi IncSearch ctermfg=214 ctermbg=16 cterm=reverse
+ hi LineNr ctermfg=147 ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi ModeMsg ctermfg=16 ctermbg=118 cterm=NONE
+ hi MoreMsg ctermfg=123 ctermbg=NONE cterm=NONE
+ hi Question ctermfg=118 ctermbg=NONE cterm=NONE
+ hi QuickFixLine ctermfg=219 ctermbg=16 cterm=reverse
+ hi Search ctermfg=123 ctermbg=16 cterm=reverse
+ hi SignColumn ctermfg=39 ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=103 ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=231 ctermbg=60 cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=147 ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=16 ctermbg=81 cterm=NONE
+ hi VisualNOS ctermfg=16 ctermbg=231 cterm=NONE
+ hi WarningMsg ctermfg=219 ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=118 ctermbg=56 cterm=reverse
+ hi debugPC ctermfg=123 ctermbg=56 cterm=reverse
+ hi Directory ctermfg=123 ctermbg=NONE cterm=NONE
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Comment ctermfg=147 ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=227 ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=123 ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=103 ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=39 ctermbg=NONE cterm=NONE
+ hi Special ctermfg=118 ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=219 ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Type ctermfg=205 ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi! link CurSearch IncSearch
+ hi! link CursorLineFold CursorLine
+ hi! link CursorLineNr CursorLine
+ hi! link CursorLineSign CursorLine
+ hi! link LineNrAbove LineNr
+ hi! link LineNrBelow LineNr
+ hi! link StatusLineTerm StatusLine
+ hi! link StatusLineTermNC StatusLineNC
+ hi! link TabLine StatusLineNC
+ hi! link TabLineFill StatusLineNC
+ hi! link TabLineSel StatusLine
+ hi! link Terminal Normal
+ hi! link lCursor Cursor
+ hi! link Boolean Constant
+ hi! link Character Constant
+ hi! link Conditional Statement
+ hi! link Define PreProc
+ hi! link Delimiter Special
+ hi! link Exception Statement
+ hi! link Float Constant
+ hi! link Function Identifier
+ hi! link Include PreProc
+ hi! link Keyword Statement
+ hi! link Label Statement
+ hi! link Macro PreProc
+ hi! link Number Constant
+ hi! link Operator Statement
+ hi! link PreCondit PreProc
+ hi! link Repeat Statement
+ hi! link SpecialChar Special
+ hi! link SpecialComment Special
+ hi! link StorageClass Type
+ hi! link String Constant
+ hi! link Structure Type
+ hi! link Tag Special
+ hi! link Typedef Type
+ hi! link Terminal Normal
+ hi! link MessageWindow Pmenu
+ hi! link PopupNotification Todo
+ hi DiffAdd ctermfg=118 ctermbg=237 cterm=reverse
+ hi DiffChange ctermfg=147 ctermbg=237 cterm=reverse
+ hi DiffText ctermfg=189 ctermbg=237 cterm=reverse
+ hi DiffDelete ctermfg=205 ctermbg=237 cterm=reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 16
+ hi Normal ctermfg=white ctermbg=black cterm=NONE
+ hi EndOfBuffer ctermfg=blue ctermbg=black cterm=NONE
+ hi NonText ctermfg=darkgray ctermbg=black cterm=NONE
+ hi SpellBad ctermfg=red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=green ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=white ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=magenta ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=white ctermbg=black cterm=reverse
+ hi StatusLineNC ctermfg=darkgray ctermbg=gray cterm=reverse
+ hi Pmenu ctermfg=black ctermbg=white cterm=NONE
+ hi PmenuSbar ctermfg=gray ctermbg=gray cterm=NONE
+ hi PmenuSel ctermfg=white ctermbg=blue cterm=NONE
+ hi PmenuThumb ctermfg=darkgray ctermbg=darkgray cterm=NONE
+ hi PmenuExtra ctermfg=darkgray ctermbg=white cterm=NONE
+ hi! link PmenuExtraSel PmenuSel
+ hi PmenuKind ctermfg=darkgray ctermbg=white cterm=NONE
+ hi! link PmenuKindSel PmenuSel
+ hi ColorColumn ctermfg=white ctermbg=darkred cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=NONE ctermbg=blue cterm=NONE
+ hi WildMenu ctermfg=white ctermbg=blue cterm=NONE
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=black ctermbg=yellow cterm=NONE
+ hi CursorIM ctermfg=black ctermbg=yellow cterm=NONE
+ hi Error ctermfg=red ctermbg=white cterm=reverse
+ hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE
+ hi FoldColumn ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=blue ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi ModeMsg ctermfg=black ctermbg=green cterm=NONE
+ hi MoreMsg ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Question ctermfg=green ctermbg=NONE cterm=NONE
+ hi QuickFixLine ctermfg=magenta ctermbg=black cterm=reverse
+ hi Search ctermfg=cyan ctermbg=black cterm=reverse
+ hi SignColumn ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=darkgray ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=white ctermbg=darkgray cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=blue ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi VisualNOS ctermfg=black ctermbg=white cterm=NONE
+ hi WarningMsg ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=green ctermbg=darkblue cterm=reverse
+ hi debugPC ctermfg=cyan ctermbg=darkblue cterm=reverse
+ hi Directory ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Comment ctermfg=blue ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=yellow ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=darkgray ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=green ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Type ctermfg=red ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi! link CurSearch IncSearch
+ hi! link CursorLineFold CursorLine
+ hi! link CursorLineNr CursorLine
+ hi! link CursorLineSign CursorLine
+ hi! link LineNrAbove LineNr
+ hi! link LineNrBelow LineNr
+ hi! link StatusLineTerm StatusLine
+ hi! link StatusLineTermNC StatusLineNC
+ hi! link TabLine StatusLineNC
+ hi! link TabLineFill StatusLineNC
+ hi! link TabLineSel StatusLine
+ hi! link Terminal Normal
+ hi! link lCursor Cursor
+ hi! link Boolean Constant
+ hi! link Character Constant
+ hi! link Conditional Statement
+ hi! link Define PreProc
+ hi! link Delimiter Special
+ hi! link Exception Statement
+ hi! link Float Constant
+ hi! link Function Identifier
+ hi! link Include PreProc
+ hi! link Keyword Statement
+ hi! link Label Statement
+ hi! link Macro PreProc
+ hi! link Number Constant
+ hi! link Operator Statement
+ hi! link PreCondit PreProc
+ hi! link Repeat Statement
+ hi! link SpecialChar Special
+ hi! link SpecialComment Special
+ hi! link StorageClass Type
+ hi! link String Constant
+ hi! link Structure Type
+ hi! link Tag Special
+ hi! link Typedef Type
+ hi! link Terminal Normal
+ hi! link MessageWindow Pmenu
+ hi! link PopupNotification Todo
+ hi DiffAdd ctermfg=darkgreen ctermbg=white cterm=reverse
+ hi DiffChange ctermfg=darkblue ctermbg=white cterm=reverse
+ hi DiffText ctermfg=grey ctermbg=black cterm=reverse
+ hi DiffDelete ctermfg=darkmagenta ctermbg=white cterm=reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 8
+ hi Normal ctermfg=gray ctermbg=black cterm=NONE
+ hi EndOfBuffer ctermfg=blue ctermbg=black cterm=NONE
+ hi NonText ctermfg=darkgray ctermbg=black cterm=NONE
+ hi SpellBad ctermfg=red ctermbg=NONE cterm=underline
+ hi SpellCap ctermfg=green ctermbg=NONE cterm=underline
+ hi SpellLocal ctermfg=white ctermbg=NONE cterm=underline
+ hi SpellRare ctermfg=magenta ctermbg=NONE cterm=underline
+ hi StatusLine ctermfg=white ctermbg=black cterm=reverse
+ hi StatusLineNC ctermfg=darkgray ctermbg=gray cterm=bold,reverse
+ hi Pmenu ctermfg=black ctermbg=white cterm=NONE
+ hi PmenuSbar ctermfg=white ctermbg=white cterm=NONE
+ hi PmenuSel ctermfg=white ctermbg=blue cterm=NONE
+ hi PmenuThumb ctermfg=blue ctermbg=blue cterm=NONE
+ hi! link PmenuExtra Pmenu
+ hi! link PmenuExtraSel PmenuSel
+ hi! link PmenuKind Pmenu
+ hi! link PmenuKindSel PmenuSel
+ hi ColorColumn ctermfg=white ctermbg=darkred cterm=NONE
+ hi CursorLine ctermfg=NONE ctermbg=NONE cterm=underline
+ hi CursorColumn ctermfg=NONE ctermbg=blue cterm=NONE
+ hi WildMenu ctermfg=white ctermbg=blue cterm=NONE
+ hi Conceal ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Cursor ctermfg=black ctermbg=yellow cterm=NONE
+ hi CursorIM ctermfg=black ctermbg=yellow cterm=NONE
+ hi Error ctermfg=red ctermbg=white cterm=reverse
+ hi ErrorMsg ctermfg=white ctermbg=red cterm=NONE
+ hi FoldColumn ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Folded ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi IncSearch ctermfg=darkyellow ctermbg=black cterm=reverse
+ hi LineNr ctermfg=blue ctermbg=NONE cterm=NONE
+ hi MatchParen ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi ModeMsg ctermfg=black ctermbg=green cterm=NONE
+ hi MoreMsg ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Question ctermfg=green ctermbg=NONE cterm=NONE
+ hi QuickFixLine ctermfg=magenta ctermbg=black cterm=reverse
+ hi Search ctermfg=cyan ctermbg=black cterm=reverse
+ hi SignColumn ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi SpecialKey ctermfg=darkgray ctermbg=NONE cterm=NONE
+ hi ToolbarButton ctermfg=white ctermbg=darkgray cterm=NONE
+ hi ToolbarLine ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi VertSplit ctermfg=blue ctermbg=NONE cterm=NONE
+ hi Visual ctermfg=black ctermbg=darkcyan cterm=NONE
+ hi VisualNOS ctermfg=black ctermbg=white cterm=NONE
+ hi WarningMsg ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi debugBreakpoint ctermfg=green ctermbg=darkblue cterm=reverse
+ hi debugPC ctermfg=cyan ctermbg=darkblue cterm=reverse
+ hi Directory ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Title ctermfg=NONE ctermbg=NONE cterm=NONE
+ hi Comment ctermfg=blue ctermbg=NONE cterm=NONE
+ hi Constant ctermfg=yellow ctermbg=NONE cterm=NONE
+ hi Identifier ctermfg=cyan ctermbg=NONE cterm=NONE
+ hi Ignore ctermfg=darkgray ctermbg=NONE cterm=NONE
+ hi PreProc ctermfg=darkcyan ctermbg=NONE cterm=NONE
+ hi Special ctermfg=green ctermbg=NONE cterm=NONE
+ hi Statement ctermfg=magenta ctermbg=NONE cterm=NONE
+ hi Todo ctermfg=NONE ctermbg=NONE cterm=reverse
+ hi Type ctermfg=red ctermbg=NONE cterm=NONE
+ hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline
+ hi! link CurSearch IncSearch
+ hi! link CursorLineFold CursorLine
+ hi! link CursorLineNr CursorLine
+ hi! link CursorLineSign CursorLine
+ hi! link LineNrAbove LineNr
+ hi! link LineNrBelow LineNr
+ hi! link StatusLineTerm StatusLine
+ hi! link StatusLineTermNC StatusLineNC
+ hi! link TabLine StatusLineNC
+ hi! link TabLineFill StatusLineNC
+ hi! link TabLineSel StatusLine
+ hi! link Terminal Normal
+ hi! link lCursor Cursor
+ hi! link Boolean Constant
+ hi! link Character Constant
+ hi! link Conditional Statement
+ hi! link Define PreProc
+ hi! link Delimiter Special
+ hi! link Exception Statement
+ hi! link Float Constant
+ hi! link Function Identifier
+ hi! link Include PreProc
+ hi! link Keyword Statement
+ hi! link Label Statement
+ hi! link Macro PreProc
+ hi! link Number Constant
+ hi! link Operator Statement
+ hi! link PreCondit PreProc
+ hi! link Repeat Statement
+ hi! link SpecialChar Special
+ hi! link SpecialComment Special
+ hi! link StorageClass Type
+ hi! link String Constant
+ hi! link Structure Type
+ hi! link Tag Special
+ hi! link Typedef Type
+ hi! link Terminal Normal
+ hi! link MessageWindow Pmenu
+ hi! link PopupNotification Todo
+ hi DiffAdd ctermfg=darkgreen ctermbg=white cterm=reverse
+ hi DiffChange ctermfg=darkblue ctermbg=white cterm=reverse
+ hi DiffText ctermfg=grey ctermbg=black cterm=reverse
+ hi DiffDelete ctermfg=darkmagenta ctermbg=white cterm=reverse
+ unlet s:t_Co
+ finish
+endif
+
+if s:t_Co >= 0
+ hi Normal term=NONE
+ hi ColorColumn term=reverse
+ hi Conceal term=NONE
+ hi Cursor term=reverse
+ hi CursorColumn term=NONE
+ hi CursorLine term=underline
+ hi CursorLineNr term=bold
+ hi DiffAdd term=reverse
+ hi DiffChange term=NONE
+ hi DiffDelete term=reverse
+ hi DiffText term=reverse
+ hi Directory term=NONE
+ hi EndOfBuffer term=NONE
+ hi ErrorMsg term=bold,reverse
+ hi FoldColumn term=NONE
+ hi Folded term=NONE
+ hi IncSearch term=bold,reverse,underline
+ hi LineNr term=NONE
+ hi MatchParen term=bold,underline
+ hi ModeMsg term=bold
+ hi MoreMsg term=NONE
+ hi NonText term=NONE
+ hi Pmenu term=reverse
+ hi PmenuSbar term=reverse
+ hi PmenuSel term=bold
+ hi PmenuThumb term=NONE
+ hi Question term=standout
+ hi Search term=reverse
+ hi SignColumn term=reverse
+ hi SpecialKey term=bold
+ hi SpellBad term=underline
+ hi SpellCap term=underline
+ hi SpellLocal term=underline
+ hi SpellRare term=underline
+ hi StatusLine term=bold,reverse
+ hi StatusLineNC term=bold,underline
+ hi TabLine term=bold,underline
+ hi TabLineFill term=NONE
+ hi Terminal term=NONE
+ hi TabLineSel term=bold,reverse
+ hi Title term=NONE
+ hi VertSplit term=NONE
+ hi Visual term=reverse
+ hi VisualNOS term=NONE
+ hi WarningMsg term=standout
+ hi WildMenu term=bold
+ hi CursorIM term=NONE
+ hi ToolbarLine term=reverse
+ hi ToolbarButton term=bold,reverse
+ hi CurSearch term=reverse
+ hi CursorLineFold term=underline
+ hi CursorLineSign term=underline
+ hi Comment term=bold
+ hi Constant term=NONE
+ hi Error term=bold,reverse
+ hi Identifier term=NONE
+ hi Ignore term=NONE
+ hi PreProc term=NONE
+ hi Special term=NONE
+ hi Statement term=NONE
+ hi Todo term=bold,reverse
+ hi Type term=NONE
+ hi Underlined term=underline
+ unlet s:t_Co
+ finish
+endif
+
+" Background: dark
+" Color: background #0e0024 16 black
+" Color: darkred #510039 52 darkred
+" Color: darkgreen #5faf00 70 darkgreen
+" Color: darkyellow #ffaf00 214 darkyellow
+" Color: darkblue #5f00d7 56 darkblue
+" Color: darkmagenta #d700ff 165 darkmagenta
+" Color: darkcyan #00afff 39 darkcyan
+" Color: red #ff5faf 205 red
+" Color: green #87ff00 118 green
+" Color: yellow #ffff5f 227 yellow
+" Color: blue #afafff 147 blue
+" Color: magenta #ffafff 219 magenta
+" Color: cyan #87ffff 123 cyan
+" Color: white #ffffff 231 white
+" Color: neutral1 #D7D5DB 189 gray
+" Color: neutral2 #AFAAB6 146 gray
+" Color: neutral3 #878092 103 darkgray
+" Color: neutral4 #5E556D 60 darkgray
+" Color: neutral5 #362B49 237 darkgray
+" Color: error #d70000 160 red
+" Color: visual #5FD7FF 81 darkcyan
+" Color: Xdarkred #d7005f ~
+" Color: Xdarkgreen #00af5f ~
+" Color: Xdarkblue #5f5fff ~
+" Color: Xdarkwhite #d7d7d7 ~
+" Color: Xgrey #878787 ~
+" Color: Xbrightgreen #00d700 ~
+" Color: Xbrightyellow #ffd700 ~
+" Color: Xbrightblue #8787ff ~
+" Color: Xbrightmagenta #ff87ff ~
+" Color: Xbrightcyan #00ffff ~
+" Term colors: background
+" Term colors: Xdarkred
+" Term colors: Xdarkgreen
+" Term colors: darkyellow
+" Term colors: Xdarkblue
+" Term colors: darkmagenta
+" Term colors: darkcyan
+" Term colors: neutral1
+" Term colors: neutral3
+" Term colors: red
+" Term colors: Xbrightgreen
+" Term colors: Xbrightyellow
+" Term colors: Xbrightblue
+" Term colors: Xbrightmagenta
+" Term colors: Xbrightcyan
+" Term colors: white
+" Color: bgDiffA #5F875F 65 darkgreen
+" Color: bgDiffT #C6C6C6 251 grey
+" Color: fgDiffW #FFFFFF 231 white
+" Color: fgDiffB #000000 16 black
+" Color: bgDiffC8 #5F87AF 67 darkblue
+" Color: bgDiffD8 #AF5FAF 133 darkmagenta
+" vim: et ts=2 sw=2
diff --git a/runtime/compiler/README.txt b/runtime/compiler/README.txt
index 3f0b97b83e..dccf4a9762 100644
--- a/runtime/compiler/README.txt
+++ b/runtime/compiler/README.txt
@@ -8,4 +8,4 @@ If you want to write your own compiler plugin, have a look at the other files
for how to do it, the format is simple.
If you think a compiler plugin you have written is useful for others, please
-send it to Bram@vim.org.
+send it to the vim-dev mailing list: <vim-dev@vim.org>
diff --git a/runtime/compiler/cargo.vim b/runtime/compiler/cargo.vim
index bd48666bc9..aa9b01e93c 100644
--- a/runtime/compiler/cargo.vim
+++ b/runtime/compiler/cargo.vim
@@ -1,35 +1,51 @@
" Vim compiler file
" Compiler: Cargo Compiler
" Maintainer: Damien Radtke <damienradtke@gmail.com>
-" Latest Revision: 2014 Sep 24
+" Latest Revision: 2023-09-11
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
if exists('current_compiler')
- finish
+ finish
endif
runtime compiler/rustc.vim
let current_compiler = "cargo"
+" vint: -ProhibitAbbreviationOption
let s:save_cpo = &cpo
set cpo&vim
+" vint: +ProhibitAbbreviationOption
if exists(':CompilerSet') != 2
- command -nargs=* CompilerSet setlocal <args>
+ command -nargs=* CompilerSet setlocal <args>
endif
if exists('g:cargo_makeprg_params')
- execute 'CompilerSet makeprg=cargo\ '.escape(g:cargo_makeprg_params, ' \|"').'\ $*'
+ execute 'CompilerSet makeprg=cargo\ '.escape(g:cargo_makeprg_params, ' \|"').'\ $*'
else
- CompilerSet makeprg=cargo\ $*
+ CompilerSet makeprg=cargo\ $*
endif
+augroup RustCargoQuickFixHooks
+ autocmd!
+ autocmd QuickFixCmdPre make call cargo#quickfix#CmdPre()
+ autocmd QuickFixCmdPost make call cargo#quickfix#CmdPost()
+augroup END
+
" Ignore general cargo progress messages
CompilerSet errorformat+=
- \%-G%\\s%#Downloading%.%#,
- \%-G%\\s%#Compiling%.%#,
- \%-G%\\s%#Finished%.%#,
- \%-G%\\s%#error:\ Could\ not\ compile\ %.%#,
- \%-G%\\s%#To\ learn\ more\\,%.%#
+ \%-G%\\s%#Downloading%.%#,
+ \%-G%\\s%#Checking%.%#,
+ \%-G%\\s%#Compiling%.%#,
+ \%-G%\\s%#Finished%.%#,
+ \%-G%\\s%#error:\ Could\ not\ compile\ %.%#,
+ \%-G%\\s%#To\ learn\ more\\,%.%#,
+ \%-G%\\s%#For\ more\ information\ about\ this\ error\\,%.%#,
+ \%-Gnote:\ Run\ with\ \`RUST_BACKTRACE=%.%#,
+ \%.%#panicked\ at\ \\'%m\\'\\,\ %f:%l:%c
+" vint: -ProhibitAbbreviationOption
let &cpo = s:save_cpo
unlet s:save_cpo
+" vint: +ProhibitAbbreviationOption
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/compiler/msvc.vim b/runtime/compiler/msvc.vim
index efe36c4da2..0d5660c103 100644
--- a/runtime/compiler/msvc.vim
+++ b/runtime/compiler/msvc.vim
@@ -1,7 +1,8 @@
" Vim compiler file
" Compiler: Microsoft Visual C
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2014 Sep 20
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("current_compiler")
finish
diff --git a/runtime/compiler/rustc.vim b/runtime/compiler/rustc.vim
index 5e5b9a4e0a..efcf24ed80 100644
--- a/runtime/compiler/rustc.vim
+++ b/runtime/compiler/rustc.vim
@@ -1,46 +1,57 @@
" Vim compiler file
" Compiler: Rust Compiler
" Maintainer: Chris Morgan <me@chrismorgan.info>
-" Latest Revision: 2013 Jul 12
+" Latest Revision: 2023-09-11
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
if exists("current_compiler")
- finish
+ finish
endif
let current_compiler = "rustc"
-let s:cpo_save = &cpo
+" vint: -ProhibitAbbreviationOption
+let s:save_cpo = &cpo
set cpo&vim
+" vint: +ProhibitAbbreviationOption
if exists(":CompilerSet") != 2
- command -nargs=* CompilerSet setlocal <args>
+ command -nargs=* CompilerSet setlocal <args>
endif
-if exists("g:rustc_makeprg_no_percent") && g:rustc_makeprg_no_percent != 0
- CompilerSet makeprg=rustc
+if get(g:, 'rustc_makeprg_no_percent', 0)
+ CompilerSet makeprg=rustc
else
- CompilerSet makeprg=rustc\ \%:S
+ if has('patch-7.4.191')
+ CompilerSet makeprg=rustc\ \%:S
+ else
+ CompilerSet makeprg=rustc\ \"%\"
+ endif
endif
-" Old errorformat (before nightly 2016/08/10)
+" New errorformat (after nightly 2016/08/10)
CompilerSet errorformat=
- \%f:%l:%c:\ %t%*[^:]:\ %m,
- \%f:%l:%c:\ %*\\d:%*\\d\ %t%*[^:]:\ %m,
- \%-G%f:%l\ %s,
- \%-G%*[\ ]^,
- \%-G%*[\ ]^%*[~],
- \%-G%*[\ ]...
+ \%-G,
+ \%-Gerror:\ aborting\ %.%#,
+ \%-Gerror:\ Could\ not\ compile\ %.%#,
+ \%Eerror:\ %m,
+ \%Eerror[E%n]:\ %m,
+ \%Wwarning:\ %m,
+ \%Inote:\ %m,
+ \%C\ %#-->\ %f:%l:%c,
+ \%E\ \ left:%m,%C\ right:%m\ %f:%l:%c,%Z
-" New errorformat (after nightly 2016/08/10)
+" Old errorformat (before nightly 2016/08/10)
CompilerSet errorformat+=
- \%-G,
- \%-Gerror:\ aborting\ %.%#,
- \%-Gerror:\ Could\ not\ compile\ %.%#,
- \%Eerror:\ %m,
- \%Eerror[E%n]:\ %m,
- \%Wwarning:\ %m,
- \%Inote:\ %m,
- \%C\ %#-->\ %f:%l:%c
-
-let &cpo = s:cpo_save
-unlet s:cpo_save
+ \%f:%l:%c:\ %t%*[^:]:\ %m,
+ \%f:%l:%c:\ %*\\d:%*\\d\ %t%*[^:]:\ %m,
+ \%-G%f:%l\ %s,
+ \%-G%*[\ ]^,
+ \%-G%*[\ ]^%*[~],
+ \%-G%*[\ ]...
+
+" vint: -ProhibitAbbreviationOption
+let &cpo = s:save_cpo
+unlet s:save_cpo
+" vint: +ProhibitAbbreviationOption
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/delmenu.vim b/runtime/delmenu.vim
index ef663c73b4..b614851d89 100644
--- a/runtime/delmenu.vim
+++ b/runtime/delmenu.vim
@@ -1,8 +1,9 @@
" This Vim script deletes all the menus, so that they can be redefined.
" Warning: This also deletes all menus defined by the user!
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Dec 10
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
aunmenu *
tlunmenu *
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt
index 95a929b808..ba3b7c0915 100644
--- a/runtime/doc/api.txt
+++ b/runtime/doc/api.txt
@@ -7,7 +7,7 @@
Nvim API *API* *api*
Nvim exposes a powerful API that can be used by plugins and external processes
-via |RPC|, |Lua| and VimL (|eval-api|).
+via |RPC|, |Lua| and Vimscript (|eval-api|).
Applications can also embed libnvim to work with the C API directly.
@@ -17,8 +17,15 @@ Applications can also embed libnvim to work with the C API directly.
API Usage *api-rpc* *RPC* *rpc*
*msgpack-rpc*
-RPC is the typical way to control Nvim programmatically. Nvim implements the
-MessagePack-RPC protocol:
+RPC is the main way to control Nvim programmatically. Nvim implements the
+MessagePack-RPC protocol with these extra (out-of-spec) constraints:
+
+1. Responses must be given in reverse order of requests (like "unwinding
+ a stack").
+2. Nvim processes all messages (requests and notifications) in the order they
+ are received.
+
+MessagePack-RPC specification:
https://github.com/msgpack-rpc/msgpack-rpc/blob/master/spec.md
https://github.com/msgpack/msgpack/blob/0b8f5ac/spec.md
@@ -202,7 +209,7 @@ any of these approaches:
nvim --api-info | python -c 'import msgpack, sys, yaml; yaml.dump(msgpack.unpackb(sys.stdin.buffer.read()), sys.stdout)'
<
3. Use the |api_info()| Vimscript function. >vim
- :lua print(vim.inspect(vim.fn.api_info()))
+ :lua vim.print(vim.fn.api_info())
< Example using |filter()| to exclude non-deprecated API functions: >vim
:new|put =map(filter(api_info().functions, '!has_key(v:val,''deprecated_since'')'), 'v:val.name')
@@ -273,7 +280,7 @@ nvim_buf_lines_event[{buf}, {changedtick}, {firstline}, {lastline}, {linedata},
changed but not the buffer contents. {linedata} contains the changed
screen lines. This happens when 'inccommand' shows a buffer preview.
- Properties:~
+ Properties: ~
{buf} API buffer handle (buffer number)
{changedtick} value of |b:changedtick| for the buffer. If you send an
@@ -306,7 +313,7 @@ nvim_buf_changedtick_event[{buf}, {changedtick}] *nvim_buf_changedtick_event*
When |b:changedtick| was incremented but no text was changed. Relevant for
undo/redo.
- Properties:~
+ Properties: ~
{buf} API buffer handle (buffer number)
{changedtick} new value of |b:changedtick| for the buffer
@@ -319,7 +326,7 @@ nvim_buf_detach_event[{buf}] *nvim_buf_detach_event*
|:checktime| or 'autoread'.
- Generally: whenever the buffer contents are unloaded from memory.
- Properties:~
+ Properties: ~
{buf} API buffer handle (buffer number)
@@ -356,6 +363,7 @@ In-process Lua plugins can receive buffer updates in the form of Lua
callbacks. These callbacks are called frequently in various contexts;
|textlock| prevents changing buffer contents and window layout (use
|vim.schedule()| to defer such operations to the main loop instead).
+Moving the cursor is allowed, but it is restored afterwards.
|nvim_buf_attach()| will take keyword args for the callbacks. "on_lines" will
receive parameters ("lines", {buf}, {changedtick}, {firstline}, {lastline},
@@ -399,6 +407,23 @@ external highlighter plugin wants to add many highlights in a batch,
performance can be improved by calling |nvim_buf_add_highlight()| as an
asynchronous notification, after first (synchronously) requesting a source id.
+|nvim_buf_add_highlight()| adds highlights as |extmarks|. If highlights need to
+be tracked or manipulated after adding them, it is better to use
+|nvim_buf_set_extmark()| directly, as this function returns the placed |extmark|
+id. Thus, instead of >lua
+ vim.api.nvim_buf_add_highlight(buf, ns_id, hl_group, line, col_start, col_end)
+<
+use >lua
+ -- create the highlight through an extmark
+ extid = vim.api.nvim_buf_set_extmark(buf, ns_id, line, col_start, {end_col = col_end, hl_group = hl_group})
+
+ -- example: modify the extmark's highlight group
+ vim.api.nvim_buf_set_extmark(buf, ns_id, line, col_start, {end_col = col_end, hl_group = NEW_HL_GROUP, id = extid})
+
+ -- example: change the highlight's position
+ vim.api.nvim_buf_set_extmark(buf, ns_id, NEW_LINE, col_start, {end_col = col_end, hl_group = NEW_HL_GROUP, id = extid})
+<
+
Example using the Python API client (|pynvim|):
>python
src = vim.new_highlight_source()
@@ -451,6 +476,11 @@ Buffer text can be highlighted by typical mechanisms (syntax highlighting,
options from the current window; specify `style=minimal` in |nvim_open_win()|
to disable various visual features such as the 'number' column.
+Other highlight groups specific to floating windows:
+- |hl-FloatBorder| for window's border
+- |hl-FloatTitle| for window's title
+- |hl-FloatFooter| for window's footer
+
Currently, floating windows don't support some widgets like scrollbar.
The output of |:mksession| does not include commands for restoring floating
@@ -464,7 +494,7 @@ Example: create a float with scratch buffer: >vim
\ 'row': 1, 'anchor': 'NW', 'style': 'minimal'}
let win = nvim_open_win(buf, 0, opts)
" optional: change highlight, otherwise Pmenu is used
- call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')
+ call nvim_set_option_value('winhl', 'Normal:MyHighlight', {'win': win})
<
==============================================================================
@@ -558,7 +588,7 @@ nvim__get_runtime({pat}, {all}, {*opts}) *nvim__get_runtime()*
Parameters: ~
• {pat} pattern of files to search for
• {all} whether to return all matches or only the first
- • {opts} is_lua: only search lua subdirs
+ • {opts} is_lua: only search Lua subdirs
Return: ~
list of absolute paths to the found files
@@ -615,6 +645,10 @@ nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()*
NB: if your UI doesn't use hlstate, this will not return hlstate first
time.
+nvim__invalidate_glyph_cache() *nvim__invalidate_glyph_cache()*
+ For testing. The condition in schar_cache_clear_if_full is hard to reach,
+ so this function can be used to force a cache clear in a test.
+
nvim__stats() *nvim__stats()*
Gets internal stats.
@@ -679,7 +713,7 @@ nvim_create_buf({listed}, {scratch}) *nvim_create_buf()*
Buffer handle, or 0 on error
See also: ~
- buf_open_scratch
+ • buf_open_scratch
nvim_del_current_line() *nvim_del_current_line()*
Deletes the current line.
@@ -693,13 +727,13 @@ nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()*
To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|.
See also: ~
- |nvim_set_keymap()|
+ • |nvim_set_keymap()|
nvim_del_mark({name}) *nvim_del_mark()*
Deletes an uppercase/file named mark. See |mark-motions|.
- Note:
- fails with error if a lowercase or buffer local named mark is used.
+ Note: ~
+ • Lowercase name (or other buffer-local mark) is an error.
Parameters: ~
• {name} Mark name
@@ -708,8 +742,8 @@ nvim_del_mark({name}) *nvim_del_mark()*
true if the mark was deleted, else false.
See also: ~
- |nvim_buf_del_mark()|
- |nvim_get_mark()|
+ • |nvim_buf_del_mark()|
+ • |nvim_get_mark()|
nvim_del_var({name}) *nvim_del_var()*
Removes a global (g:) variable.
@@ -746,7 +780,7 @@ nvim_err_writeln({str}) *nvim_err_writeln()*
• {str} Message
See also: ~
- nvim_err_write()
+ • nvim_err_write()
nvim_eval_statusline({str}, {*opts}) *nvim_eval_statusline()*
Evaluates statusline string.
@@ -768,6 +802,8 @@ nvim_eval_statusline({str}, {*opts}) *nvim_eval_statusline()*
• use_tabline: (boolean) Evaluate tabline instead of
statusline. When true, {winid} is ignored. Mutually
exclusive with {use_winbar}.
+ • use_statuscol_lnum: (number) Evaluate statuscolumn for this
+ line number instead of statusline.
Return: ~
Dictionary containing statusline information, with these keys:
@@ -820,8 +856,8 @@ nvim_feedkeys({keys}, {mode}, {escape_ks}) *nvim_feedkeys()*
true otherwise.
See also: ~
- feedkeys()
- vim_strsave_escape_ks
+ • feedkeys()
+ • vim_strsave_escape_ks
nvim_get_api_info() *nvim_get_api_info()*
Returns a 2-tuple (Array), where item 0 is the current channel id and item
@@ -918,37 +954,48 @@ nvim_get_current_win() *nvim_get_current_win()*
Return: ~
Window handle
-nvim_get_hl_by_id({hl_id}, {rgb}) *nvim_get_hl_by_id()*
- Gets a highlight definition by id. |hlID()|
-
- Parameters: ~
- • {hl_id} Highlight id as returned by |hlID()|
- • {rgb} Export RGB colors
-
- Return: ~
- Highlight definition map
-
- See also: ~
- nvim_get_hl_by_name
+nvim_get_hl({ns_id}, {*opts}) *nvim_get_hl()*
+ Gets all or specific highlight groups in a namespace.
-nvim_get_hl_by_name({name}, {rgb}) *nvim_get_hl_by_name()*
- Gets a highlight definition by name.
+ Note: ~
+ • When the `link` attribute is defined in the highlight definition map,
+ other attributes will not be taking effect (see |:hi-link|).
Parameters: ~
- • {name} Highlight group name
- • {rgb} Export RGB colors
+ • {ns_id} Get highlight groups for namespace ns_id
+ |nvim_get_namespaces()|. Use 0 to get global highlight groups
+ |:highlight|.
+ • {opts} Options dict:
+ • name: (string) Get a highlight definition by name.
+ • id: (integer) Get a highlight definition by id.
+ • link: (boolean, default true) Show linked group name
+ instead of effective definition |:hi-link|.
+ • create: (boolean, default true) When highlight group
+ doesn't exist create it.
Return: ~
- Highlight definition map
-
- See also: ~
- nvim_get_hl_by_id
+ Highlight groups as a map from group name to a highlight definition
+ map as in |nvim_set_hl()|, or only a single highlight definition map
+ if requested by name or id.
nvim_get_hl_id_by_name({name}) *nvim_get_hl_id_by_name()*
Gets a highlight group by name
similar to |hlID()|, but allocates a new ID if not present.
+nvim_get_hl_ns({*opts}) *nvim_get_hl_ns()*
+ Gets the active highlight namespace.
+
+ Parameters: ~
+ • {opts} Optional parameters
+ • winid: (number) |window-ID| for retrieving a window's
+ highlight namespace. A value of -1 is returned when
+ |nvim_win_set_hl_ns()| has not been called for the window
+ (or was called with a namespace of -1).
+
+ Return: ~
+ Namespace id, or -1
+
nvim_get_keymap({mode}) *nvim_get_keymap()*
Gets a list of global (non-buffer-local) |mapping| definitions.
@@ -960,13 +1007,14 @@ nvim_get_keymap({mode}) *nvim_get_keymap()*
"buffer" key is always zero.
nvim_get_mark({name}, {opts}) *nvim_get_mark()*
- Return a tuple (row, col, buffer, buffername) representing the position of
- the uppercase/file named mark. See |mark-motions|.
+ Returns a `(row, col, buffer, buffername)` tuple representing the position
+ of the uppercase/file named mark. "End of line" column position is
+ returned as |v:maxcol| (big number). See |mark-motions|.
Marks are (1,0)-indexed. |api-indexing|
- Note:
- fails with error if a lowercase or buffer local named mark is used.
+ Note: ~
+ • Lowercase name (or other buffer-local mark) is an error.
Parameters: ~
• {name} Mark name
@@ -977,8 +1025,8 @@ nvim_get_mark({name}, {opts}) *nvim_get_mark()*
not set.
See also: ~
- |nvim_buf_set_mark()|
- |nvim_del_mark()|
+ • |nvim_buf_set_mark()|
+ • |nvim_del_mark()|
nvim_get_mode() *nvim_get_mode()*
Gets the current mode. |mode()| "blocking" is true if Nvim is waiting for
@@ -1047,12 +1095,10 @@ nvim_input({keys}) *nvim_input()*
On execution error: does not fail, but updates v:errmsg.
- Note:
- |keycodes| like <CR> are translated, so "<" is special. To input a
+ Note: ~
+ • |keycodes| like <CR> are translated, so "<" is special. To input a
literal "<", send <LT>.
-
- Note:
- For mouse events use |nvim_input_mouse()|. The pseudokey form
+ • For mouse events use |nvim_input_mouse()|. The pseudokey form
"<LeftMouse><col,row>" is deprecated since |api-level| 6.
Attributes: ~
@@ -1072,8 +1118,8 @@ nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col})
Non-blocking: does not wait on any result, but queues the event to be
processed soon by the event loop.
- Note:
- Currently this doesn't support "scripting" multiple mouse events by
+ Note: ~
+ • Currently this doesn't support "scripting" multiple mouse events by
calling it multiple times in a loop: the intermediate mouse positions
will be ignored. It should be used to implement real-time mouse input
in a GUI. The deprecated pseudokey form ("<LeftMouse><col,row>") of
@@ -1113,7 +1159,7 @@ nvim_list_chans() *nvim_list_chans()*
specified at |nvim_get_chan_info()|.
nvim_list_runtime_paths() *nvim_list_runtime_paths()*
- Gets the paths contained in 'runtimepath'.
+ Gets the paths contained in |runtime-search-path|.
Return: ~
List of paths
@@ -1173,10 +1219,13 @@ nvim_open_term({buffer}, {opts}) *nvim_open_term()*
|nvim_chan_send()| can be called immediately to process sequences in a
virtual terminal having the intended size.
+ Attributes: ~
+ not allowed when |textlock| is active
+
Parameters: ~
• {buffer} the buffer to use (expected to be empty)
• {opts} Optional parameters.
- • on_input: lua callback for input sent, i e keypresses in
+ • on_input: Lua callback for input sent, i e keypresses in
terminal mode. Note: keypresses are sent raw as they would
be to the pty master end. For instance, a carriage return
is sent as a "\r", not as a "\n". |textlock| applies. It
@@ -1252,8 +1301,8 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special})
• {special} Replace |keycodes|, e.g. <CR> becomes a "\r" char.
See also: ~
- replace_termcodes
- cpoptions
+ • replace_termcodes
+ • cpoptions
*nvim_select_popupmenu_item()*
nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
@@ -1286,8 +1335,8 @@ nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes})
appropriate. Example: library first identifies the channel, then a plugin
using that library later identifies itself.
- Note:
- "Something is better than nothing". You don't need to include all the
+ Note: ~
+ • "Something is better than nothing". You don't need to include all the
fields.
Attributes: ~
@@ -1307,7 +1356,11 @@ nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes})
• {type} Must be one of the following values. Client libraries
should default to "remote" unless overridden by the
user.
- • "remote" remote client connected to Nvim.
+ • "remote" remote client connected "Nvim flavored"
+ MessagePack-RPC (responses must be in reverse order of
+ requests). |msgpack-rpc|
+ • "msgpack-rpc" remote client connected to Nvim via
+ fully MessagePack-RPC compliant protocol.
• "ui" gui frontend
• "embedder" application using Nvim as a component (for
example, IDE/editor implementing a vim mode).
@@ -1338,7 +1391,7 @@ nvim_set_current_buf({buffer}) *nvim_set_current_buf()*
Sets the current buffer.
Attributes: ~
- not allowed when |textlock| is active
+ not allowed when |textlock| is active or in the |cmdwin|
Parameters: ~
• {buffer} Buffer handle
@@ -1362,7 +1415,7 @@ nvim_set_current_tabpage({tabpage}) *nvim_set_current_tabpage()*
Sets the current tabpage.
Attributes: ~
- not allowed when |textlock| is active
+ not allowed when |textlock| is active or in the |cmdwin|
Parameters: ~
• {tabpage} Tabpage handle
@@ -1371,7 +1424,7 @@ nvim_set_current_win({window}) *nvim_set_current_win()*
Sets the current window.
Attributes: ~
- not allowed when |textlock| is active
+ not allowed when |textlock| is active or in the |cmdwin|
Parameters: ~
• {window} Window handle
@@ -1379,21 +1432,24 @@ nvim_set_current_win({window}) *nvim_set_current_win()*
nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()*
Sets a highlight group.
- Note:
- Unlike the `:highlight` command which can update a highlight group,
- this function completely replaces the definition. For example:
+ Note: ~
+ • Unlike the `:highlight` command which can update a highlight group, this
+ function completely replaces the definition. For example:
`nvim_set_hl(0, 'Visual', {})` will clear the highlight group
'Visual'.
-
- Note:
- The fg and bg keys also accept the string values `"fg"` or `"bg"`
- which act as aliases to the corresponding foreground and background
- values of the Normal group. If the Normal group has not been defined,
- using these values results in an error.
+ • The fg and bg keys also accept the string values `"fg"` or `"bg"` which
+ act as aliases to the corresponding foreground and background values
+ of the Normal group. If the Normal group has not been defined, using
+ these values results in an error.
+ • If `link` is used in combination with other attributes; only the `link`
+ will take effect (see |:hi-link|).
Parameters: ~
• {ns_id} Namespace id for this highlight |nvim_create_namespace()|.
Use 0 to set a highlight group globally |:highlight|.
+ Highlights from non-global namespaces are not active by
+ default, use |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to
+ activate them.
• {name} Highlight group name, e.g. "ErrorMsg"
• {val} Highlight definition map, accepts the following keys:
• fg (or foreground): color name or "#RRGGBB", see note.
@@ -1419,16 +1475,19 @@ nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()*
• cterm: cterm attribute map, like |highlight-args|. If not
set, cterm attributes will match those from the attribute
map documented above.
+ • force: if true force update the highlight group when it
+ exists.
nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()*
- Set active namespace for highlights. This can be set for a single window,
- see |nvim_win_set_hl_ns()|.
+ Set active namespace for highlights defined with |nvim_set_hl()|. This can
+ be set for a single window, see |nvim_win_set_hl_ns()|.
Parameters: ~
• {ns_id} the namespace to use
nvim_set_hl_ns_fast({ns_id}) *nvim_set_hl_ns_fast()*
- Set active namespace for highlights while redrawing.
+ Set active namespace for highlights defined with |nvim_set_hl()| while
+ redrawing.
This function meant to be called while redrawing, primarily from
|nvim_set_decoration_provider()| on_win and on_line callbacks, which are
@@ -1458,19 +1517,20 @@ nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()*
Parameters: ~
• {mode} Mode short-name (map command prefix: "n", "i", "v", "x", …) or
- "!" for |:map!|, or empty string for |:map|.
+ "!" for |:map!|, or empty string for |:map|. "ia", "ca" or
+ "!a" for abbreviation in Insert mode, Cmdline mode, or both,
+ respectively
• {lhs} Left-hand-side |{lhs}| of the mapping.
• {rhs} Right-hand-side |{rhs}| of the mapping.
- • {opts} Optional parameters map: keys are |:map-arguments|, values are
- booleans (default false). Accepts all |:map-arguments| as keys
- excluding |<buffer>| but including |:noremap| and "desc".
- Unknown key is an error. "desc" can be used to give a
- description to the mapping. When called from Lua, also accepts
- a "callback" key that takes a Lua function to call when the
- mapping is executed. When "expr" is true, "replace_keycodes"
- (boolean) can be used to replace keycodes in the resulting
- string (see |nvim_replace_termcodes()|), and a Lua callback
- returning `nil` is equivalent to returning an empty string.
+ • {opts} Optional parameters map: Accepts all |:map-arguments| as keys
+ except |<buffer>|, values are booleans (default false). Also:
+ • "noremap" disables |recursive_mapping|, like |:noremap|
+ • "desc" human-readable description.
+ • "callback" Lua function called in place of {rhs}.
+ • "replace_keycodes" (boolean) When "expr" is true, replace
+ keycodes in the resulting string (see
+ |nvim_replace_termcodes()|). Returning nil from the Lua
+ "callback" is equivalent to returning an empty string.
nvim_set_var({name}, {value}) *nvim_set_var()*
Sets a global (g:) variable.
@@ -1520,22 +1580,22 @@ Vimscript Functions *api-vimscript*
*nvim_call_dict_function()*
nvim_call_dict_function({dict}, {fn}, {args})
- Calls a VimL |Dictionary-function| with the given arguments.
+ Calls a Vimscript |Dictionary-function| with the given arguments.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
Parameters: ~
- • {dict} Dictionary, or String evaluating to a VimL |self| dict
- • {fn} Name of the function defined on the VimL dict
+ • {dict} Dictionary, or String evaluating to a Vimscript |self| dict
+ • {fn} Name of the function defined on the Vimscript dict
• {args} Function arguments packed in an Array
Return: ~
Result of the function call
nvim_call_function({fn}, {args}) *nvim_call_function()*
- Calls a VimL function with the given arguments.
+ Calls a Vimscript function with the given arguments.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
Parameters: ~
• {fn} Function to call
@@ -1547,54 +1607,56 @@ nvim_call_function({fn}, {args}) *nvim_call_function()*
nvim_command({command}) *nvim_command()*
Executes an Ex command.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
- Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple
- lines of Vim script or an Ex command directly, use |nvim_exec()|. To
- construct an Ex command using a structured format and then execute it, use
- |nvim_cmd()|. To modify an Ex command before evaluating it, use
- |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
+ Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate
+ multiple lines of Vim script or an Ex command directly, use
+ |nvim_exec2()|. To construct an Ex command using a structured format and
+ then execute it, use |nvim_cmd()|. To modify an Ex command before
+ evaluating it, use |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
Parameters: ~
• {command} Ex command string
nvim_eval({expr}) *nvim_eval()*
- Evaluates a VimL |expression|. Dictionaries and Lists are recursively
+ Evaluates a Vimscript |expression|. Dictionaries and Lists are recursively
expanded.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
Parameters: ~
- • {expr} VimL expression string
+ • {expr} Vimscript expression string
Return: ~
Evaluation result or expanded object
-nvim_exec({src}, {output}) *nvim_exec()*
+nvim_exec2({src}, {*opts}) *nvim_exec2()*
Executes Vimscript (multiline block of Ex commands), like anonymous
|:source|.
Unlike |nvim_command()| this function supports heredocs, script-scope
(s:), etc.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
Parameters: ~
- • {src} Vimscript code
- • {output} Capture and return all (non-error, non-shell |:!|) output
+ • {src} Vimscript code
+ • {opts} Optional parameters.
+ • output: (boolean, default false) Whether to capture and
+ return all (non-error, non-shell |:!|) output.
Return: ~
- Output (non-error, non-shell |:!|) if `output` is true, else empty
- string.
+ Dictionary containing information about execution, with these keys:
+ • output: (string|nil) Output if `opts.output` is true.
See also: ~
- |execute()|
- |nvim_command()|
- |nvim_cmd()|
+ • |execute()|
+ • |nvim_command()|
+ • |nvim_cmd()|
*nvim_parse_expression()*
nvim_parse_expression({expr}, {flags}, {highlight})
- Parse a VimL expression.
+ Parse a Vimscript expression.
Attributes: ~
|api-fast|
@@ -1638,8 +1700,8 @@ nvim_parse_expression({expr}, {flags}, {highlight})
stringified without "kExprNode" prefix.
• "start": a pair [line, column] describing where node is
"started" where "line" is always 0 (will not be 0 if you will be
- using nvim_parse_viml() on e.g. ":let", but that is not present
- yet). Both elements are Integers.
+ using this API on e.g. ":let", but that is not present yet).
+ Both elements are Integers.
• "len": “length” of the node. This and "start" are there for
debugging purposes primary (debugging parser and providing debug
information).
@@ -1678,13 +1740,13 @@ Command Functions *api-command*
*nvim_buf_create_user_command()*
nvim_buf_create_user_command({buffer}, {name}, {command}, {*opts})
- Create a new user command |user-commands| in the given buffer.
+ Creates a buffer-local command |user-commands|.
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer.
See also: ~
- nvim_create_user_command
+ • nvim_create_user_command
*nvim_buf_del_user_command()*
nvim_buf_del_user_command({buffer}, {name})
@@ -1722,7 +1784,7 @@ nvim_cmd({*cmd}, {*opts}) *nvim_cmd()*
example, instead of `vim.cmd.bdelete{ count = 2 }`, you may do
`vim.cmd.bdelete(2)`.
- On execution error: fails with VimL error, updates v:errmsg.
+ On execution error: fails with Vimscript error, updates v:errmsg.
Parameters: ~
• {cmd} Command to execute. Must be a Dictionary that can contain the
@@ -1738,22 +1800,19 @@ nvim_cmd({*cmd}, {*opts}) *nvim_cmd()*
empty string.
See also: ~
- |nvim_exec()|
- |nvim_command()|
+ • |nvim_exec2()|
+ • |nvim_command()|
*nvim_create_user_command()*
nvim_create_user_command({name}, {command}, {*opts})
- Create a new user command |user-commands|
+ Creates a global |user-commands| command.
- {name} is the name of the new command. The name must begin with an
- uppercase letter.
-
- {command} is the replacement text or Lua function to execute.
+ For Lua usage see |lua-guide-commands-create|.
Example: >vim
- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
- :SayHello
- Hello world!
+ :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+ :SayHello
+ Hello world!
<
Parameters: ~
@@ -1769,6 +1828,7 @@ nvim_create_user_command({name}, {command}, {*opts})
• fargs: (table) The args split by unescaped whitespace
(when more than one argument is allowed), if any
|<f-args>|
+ • nargs: (string) Number of arguments |:command-nargs|
• bang: (boolean) "true" if the command was executed with a
! modifier |<bang>|
• line1: (number) The starting line of the command range
@@ -1783,19 +1843,20 @@ nvim_create_user_command({name}, {command}, {*opts})
• smods: (table) Command modifiers in a structured format.
Has the same structure as the "mods" key of
|nvim_parse_cmd()|.
- • {opts} Optional command attributes. See |command-attributes| for
- more details. To use boolean attributes (such as
- |:command-bang| or |:command-bar|) set the value to "true".
- In addition to the string options listed in
- |:command-complete|, the "complete" key also accepts a Lua
- function which works like the "customlist" completion mode
- |:command-completion-customlist|. Additional parameters:
- • desc: (string) Used for listing the command when a Lua
- function is used for {command}.
- • force: (boolean, default true) Override any previous
- definition.
- • preview: (function) Preview callback for 'inccommand'
- |:command-preview|
+ • {opts} Optional |command-attributes|.
+ • Set boolean attributes such as |:command-bang| or
+ |:command-bar| to true (but not |:command-buffer|, use
+ |nvim_buf_create_user_command()| instead).
+ • "complete" |:command-complete| also accepts a Lua
+ function which works like
+ |:command-completion-customlist|.
+ • Other parameters:
+ • desc: (string) Used for listing the command when a Lua
+ function is used for {command}.
+ • force: (boolean, default true) Override any previous
+ definition.
+ • preview: (function) Preview callback for 'inccommand'
+ |:command-preview|
nvim_del_user_command({name}) *nvim_del_user_command()*
Delete a user-defined command.
@@ -1814,6 +1875,9 @@ nvim_get_commands({*opts}) *nvim_get_commands()*
Return: ~
Map of maps describing commands.
+ See also: ~
+ • |nvim_get_all_options_info()|
+
nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()*
Parse command line.
@@ -1889,45 +1953,20 @@ nvim_parse_cmd({str}, {opts}) *nvim_parse_cmd()*
==============================================================================
Options Functions *api-options*
-nvim_buf_get_option({buffer}, {name}) *nvim_buf_get_option()*
- Gets a buffer option value
-
- Parameters: ~
- • {buffer} Buffer handle, or 0 for current buffer
- • {name} Option name
-
- Return: ~
- Option value
-
-nvim_buf_set_option({buffer}, {name}, {value}) *nvim_buf_set_option()*
- Sets a buffer option value. Passing `nil` as value deletes the option
- (only works if there's a global fallback)
-
- Parameters: ~
- • {buffer} Buffer handle, or 0 for current buffer
- • {name} Option name
- • {value} Option value
-
nvim_get_all_options_info() *nvim_get_all_options_info()*
Gets the option information for all options.
The dictionary has the full option names as keys and option metadata
- dictionaries as detailed at |nvim_get_option_info()|.
+ dictionaries as detailed at |nvim_get_option_info2()|.
Return: ~
dictionary of all options
-nvim_get_option({name}) *nvim_get_option()*
- Gets the global value of an option.
-
- Parameters: ~
- • {name} Option name
-
- Return: ~
- Option value (global)
+ See also: ~
+ • |nvim_get_commands()|
-nvim_get_option_info({name}) *nvim_get_option_info()*
- Gets the option information for one option
+nvim_get_option_info2({name}, {*opts}) *nvim_get_option_info2()*
+ Gets the option information for one option from arbitrary buffer or window
Resulting dictionary has keys:
• name: Name of the option (like 'filetype')
@@ -1943,8 +1982,19 @@ nvim_get_option_info({name}) *nvim_get_option_info()*
• commalist: List of comma separated values
• flaglist: List of single char flags
+ When {scope} is not provided, the last set information applies to the
+ local value in the current buffer or window if it is available, otherwise
+ the global value information is returned. This behavior can be disabled by
+ explicitly specifying {scope} in the {opts} table.
+
Parameters: ~
• {name} Option name
+ • {opts} Optional parameters
+ • scope: One of "global" or "local". Analogous to |:setglobal|
+ and |:setlocal|, respectively.
+ • win: |window-ID|. Used for getting window local options.
+ • buf: Buffer number. Used for getting buffer local options.
+ Implies {scope} is "local".
Return: ~
Option Information
@@ -1963,17 +2013,14 @@ nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()*
• win: |window-ID|. Used for getting window local options.
• buf: Buffer number. Used for getting buffer local options.
Implies {scope} is "local".
+ • filetype: |filetype|. Used to get the default option for a
+ specific filetype. Cannot be used with any other option.
+ Note: this will trigger |ftplugin| and all |FileType|
+ autocommands for the corresponding filetype.
Return: ~
Option value
-nvim_set_option({name}, {value}) *nvim_set_option()*
- Sets the global value of an option.
-
- Parameters: ~
- • {name} Option name
- • {value} New option value
-
*nvim_set_option_value()*
nvim_set_option_value({name}, {value}, {*opts})
Sets the value of an option. The behavior of this function matches that of
@@ -1991,25 +2038,6 @@ nvim_set_option_value({name}, {value}, {*opts})
• win: |window-ID|. Used for setting window local option.
• buf: Buffer number. Used for setting buffer local option.
-nvim_win_get_option({window}, {name}) *nvim_win_get_option()*
- Gets a window option value
-
- Parameters: ~
- • {window} Window handle, or 0 for current window
- • {name} Option name
-
- Return: ~
- Option value
-
-nvim_win_set_option({window}, {name}, {value}) *nvim_win_set_option()*
- Sets a window option value. Passing `nil` as value deletes the option
- (only works if there's a global fallback)
-
- Parameters: ~
- • {window} Window handle, or 0 for current window
- • {name} Option name
- • {value} Option value
-
==============================================================================
Buffer Functions *api-buffer*
@@ -2017,7 +2045,7 @@ Buffer Functions *api-buffer*
For more information on buffers, see |buffers|.
-Unloaded Buffers:~
+Unloaded Buffers: ~
Buffers may be unloaded by the |:bunload| command or the buffer's
|'bufhidden'| option. When a buffer is unloaded its file contents are
@@ -2032,10 +2060,14 @@ whether a buffer is loaded.
nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
Activates buffer-update events on a channel, or as Lua callbacks.
- Example (Lua): capture buffer updates in a global `events` variable (use "print(vim.inspect(events))" to see its contents): >lua
- events = {}
- vim.api.nvim_buf_attach(0, false, {
- on_lines=function(...) table.insert(events, {...}) end})
+ Example (Lua): capture buffer updates in a global `events` variable (use
+ "vim.print(events)" to see its contents): >lua
+ events = {}
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines = function(...)
+ table.insert(events, {...})
+ end,
+ })
<
Parameters: ~
@@ -2057,7 +2089,7 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
• deleted_codepoints (if `utf_sizes` is true)
• deleted_codeunits (if `utf_sizes` is true)
- • on_bytes: lua callback invoked on change. This
+ • on_bytes: Lua callback invoked on change. This
callback receives more granular information about the
change compared to on_lines. Return `true` to detach. Args:
• the string "bytes"
@@ -2099,8 +2131,8 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()*
otherwise True. TODO: LUA_API_NO_EVAL
See also: ~
- |nvim_buf_detach()|
- |api-buffer-updates-lua|
+ • |nvim_buf_detach()|
+ • |api-buffer-updates-lua|
nvim_buf_call({buffer}, {fun}) *nvim_buf_call()*
call a function with buffer as temporary current buffer
@@ -2112,20 +2144,20 @@ nvim_buf_call({buffer}, {fun}) *nvim_buf_call()*
a temporary scratch window (called the "autocmd window" for historical
reasons) will be used.
- This is useful e.g. to call vimL functions that only work with the current
- buffer/window currently, like |termopen()|.
+ This is useful e.g. to call Vimscript functions that only work with the
+ current buffer/window currently, like |termopen()|.
Attributes: ~
Lua |vim.api| only
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
- • {fun} Function to call inside the buffer (currently lua callable
+ • {fun} Function to call inside the buffer (currently Lua callable
only)
Return: ~
- Return value of function. NB: will deepcopy lua values currently, use
- upvalues to send lua references in and out.
+ Return value of function. NB: will deepcopy Lua values currently, use
+ upvalues to send Lua references in and out.
nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
Unmaps a buffer-local |mapping| for the given mode.
@@ -2134,13 +2166,13 @@ nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()*
• {buffer} Buffer handle, or 0 for current buffer
See also: ~
- |nvim_del_keymap()|
+ • |nvim_del_keymap()|
nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()*
Deletes a named mark in the buffer. See |mark-motions|.
- Note:
- only deletes marks set in the buffer, if the mark is not set in the
+ Note: ~
+ • only deletes marks set in the buffer, if the mark is not set in the
buffer it will return false.
Parameters: ~
@@ -2151,8 +2183,8 @@ nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()*
true if the mark was deleted, else false.
See also: ~
- |nvim_buf_set_mark()|
- |nvim_del_mark()|
+ • |nvim_buf_set_mark()|
+ • |nvim_del_mark()|
nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()*
Removes a buffer-scoped (b:) variable
@@ -2165,7 +2197,7 @@ nvim_buf_delete({buffer}, {opts}) *nvim_buf_delete()*
Deletes the buffer. See |:bwipeout|
Attributes: ~
- not allowed when |textlock| is active
+ not allowed when |textlock| is active or in the |cmdwin|
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
@@ -2187,8 +2219,8 @@ nvim_buf_detach({buffer}) *nvim_buf_detach()*
True.
See also: ~
- |nvim_buf_attach()|
- |api-lua-detach| for detaching Lua callbacks
+ • |nvim_buf_attach()|
+ • |api-lua-detach| for detaching Lua callbacks
nvim_buf_get_changedtick({buffer}) *nvim_buf_get_changedtick()*
Gets a changed tick of a buffer
@@ -2231,7 +2263,8 @@ nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
Array of lines, or empty array for unloaded buffer.
nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
- Returns a tuple (row,col) representing the position of the named mark. See
+ Returns a `(row,col)` tuple representing the position of the named mark.
+ "End of line" column position is returned as |v:maxcol| (big number). See
|mark-motions|.
Marks are (1,0)-indexed. |api-indexing|
@@ -2245,8 +2278,8 @@ nvim_buf_get_mark({buffer}, {name}) *nvim_buf_get_mark()*
uppercase/file mark set in another buffer.
See also: ~
- |nvim_buf_set_mark()|
- |nvim_buf_del_mark()|
+ • |nvim_buf_set_mark()|
+ • |nvim_buf_del_mark()|
nvim_buf_get_name({buffer}) *nvim_buf_get_name()*
Gets the full file name for the buffer
@@ -2322,8 +2355,8 @@ nvim_buf_is_loaded({buffer}) *nvim_buf_is_loaded()*
nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()*
Checks if a buffer is valid.
- Note:
- Even if a buffer is valid it may have been unloaded. See |api-buffer|
+ Note: ~
+ • Even if a buffer is valid it may have been unloaded. See |api-buffer|
for more info about unloaded buffers.
Parameters: ~
@@ -2349,7 +2382,7 @@ nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {*opts})
• {buffer} Buffer handle, or 0 for current buffer
See also: ~
- |nvim_set_keymap()|
+ • |nvim_set_keymap()|
*nvim_buf_set_lines()*
nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement})
@@ -2376,7 +2409,7 @@ nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement})
• {replacement} Array of lines to use as replacement
See also: ~
- |nvim_buf_set_text()|
+ • |nvim_buf_set_text()|
*nvim_buf_set_mark()*
nvim_buf_set_mark({buffer}, {name}, {line}, {col}, {opts})
@@ -2385,8 +2418,8 @@ nvim_buf_set_mark({buffer}, {name}, {line}, {col}, {opts})
Marks are (1,0)-indexed. |api-indexing|
- Note:
- Passing 0 as line deletes the mark
+ Note: ~
+ • Passing 0 as line deletes the mark
Parameters: ~
• {buffer} Buffer to set the mark on
@@ -2399,8 +2432,8 @@ nvim_buf_set_mark({buffer}, {name}, {line}, {col}, {opts})
true if the mark was set, else false.
See also: ~
- |nvim_buf_del_mark()|
- |nvim_buf_get_mark()|
+ • |nvim_buf_del_mark()|
+ • |nvim_buf_get_mark()|
nvim_buf_set_name({buffer}, {name}) *nvim_buf_set_name()*
Sets the full file name for a buffer
@@ -2428,6 +2461,11 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire
lines.
+ Prefer |nvim_put()| if you want to insert text at the cursor position.
+
+ Attributes: ~
+ not allowed when |textlock| is active
+
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
• {start_row} First line index
@@ -2437,7 +2475,8 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col},
• {replacement} Array of lines to use as replacement
See also: ~
- |nvim_buf_set_lines()|
+ • |nvim_buf_set_lines()|
+ • |nvim_put()|
nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
Sets a buffer-scoped (b:) variable
@@ -2523,43 +2562,50 @@ nvim_buf_get_extmark_by_id({buffer}, {ns_id}, {id}, {opts})
• {id} Extmark id
• {opts} Optional parameters. Keys:
• details: Whether to include the details dict
+ • hl_name: Whether to include highlight group name instead
+ of id, true if omitted
Return: ~
0-indexed (row, col) tuple or empty list () if extmark id was absent
*nvim_buf_get_extmarks()*
-nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
- Gets |extmarks| in "traversal order" from a |charwise| region defined by
- buffer positions (inclusive, 0-indexed |api-indexing|).
+nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {*opts})
+ Gets |extmarks| (including |signs|) in "traversal order" from a |charwise|
+ region defined by buffer positions (inclusive, 0-indexed |api-indexing|).
Region can be given as (row,col) tuples, or valid extmark ids (whose
positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
respectively, thus the following are equivalent: >lua
- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
+ vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+ vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
<
If `end` is less than `start`, traversal works backwards. (Useful with
`limit`, to get the first marks prior to a given position.)
+ Note: when using extmark ranges (marks with a end_row/end_col position)
+ the `overlap` option might be useful. Otherwise only the start position of
+ an extmark will be considered.
+
Example: >lua
- local a = vim.api
- local pos = a.nvim_win_get_cursor(0)
- local ns = a.nvim_create_namespace('my-plugin')
- -- Create new extmark at line 1, column 1.
- local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {})
- -- Create new extmark at line 3, column 1.
- local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {})
- -- Get extmarks only from line 3.
- local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
- -- Get all marks in this buffer + namespace.
- local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
- print(vim.inspect(ms))
+ local api = vim.api
+ local pos = api.nvim_win_get_cursor(0)
+ local ns = api.nvim_create_namespace('my-plugin')
+ -- Create new extmark at line 1, column 1.
+ local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {})
+ -- Create new extmark at line 3, column 1.
+ local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {})
+ -- Get extmarks only from line 3.
+ local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+ -- Get all marks in this buffer + namespace.
+ local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+ vim.print(ms)
<
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
- • {ns_id} Namespace id from |nvim_create_namespace()|
+ • {ns_id} Namespace id from |nvim_create_namespace()| or -1 for all
+ namespaces
• {start} Start of range: a 0-indexed (row, col) or valid extmark id
(whose position defines the bound). |api-indexing|
• {end} End of range (inclusive): a 0-indexed (row, col) or valid
@@ -2567,7 +2613,13 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts})
|api-indexing|
• {opts} Optional parameters. Keys:
• limit: Maximum number of marks to return
- • details Whether to include the details dict
+ • details: Whether to include the details dict
+ • hl_name: Whether to include highlight group name instead
+ of id, true if omitted
+ • overlap: Also include marks which overlap the range, even
+ if their start position is less than `start`
+ • type: Filter marks by type: "highlight", "sign",
+ "virt_text" and "virt_lines"
Return: ~
List of [extmark_id, row, col] tuples in "traversal order".
@@ -2585,6 +2637,11 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
Using the optional arguments, it is possible to use this to highlight a
range of text, and also to associate virtual text to the mark.
+ If present, the position defined by `end_col` and `end_row` should be
+ after the start position in order for the extmark to cover a range. An
+ earlier end position is not an error, but then it behaves like an empty
+ range (no highlighting).
+
Parameters: ~
• {buffer} Buffer handle, or 0 for current buffer
• {ns_id} Namespace id from |nvim_create_namespace()|
@@ -2608,23 +2665,28 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
string or as an integer, the latter which can be obtained
using |nvim_get_hl_id_by_name()|.
• virt_text_pos : position of virtual text. Possible values:
- • "eol": right after eol character (default)
+ • "eol": right after eol character (default).
• "overlay": display over the specified column, without
shifting the underlying text.
• "right_align": display right aligned in the window.
+ • "inline": display at the specified column, and shift the
+ buffer text to the right as needed.
• virt_text_win_col : position the virtual text at a fixed
- window column (starting from the first text column)
+ window column (starting from the first text column of the
+ screen line) instead of "virt_text_pos".
• virt_text_hide : hide the virtual text when the background
- text is selected or hidden due to horizontal scroll
- 'nowrap'
+ text is selected or hidden because of scrolling with
+ 'nowrap' or 'smoothscroll'. Currently only affects
+ "overlay" virt_text.
• hl_mode : control how highlights are combined with the
highlights of the text. Currently only affects virt_text
highlights, but might affect `hl_group` in later versions.
• "replace": only show the virt_text color. This is the
- default
- • "combine": combine with background text color
- • "blend": blend with background text color.
+ default.
+ • "combine": combine with background text color.
+ • "blend": blend with background text color. Not supported
+ for "inline" virt_text.
• virt_lines : virtual lines to add next to this mark This
should be an array over lines, where each line in turn is
@@ -2644,11 +2706,17 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts})
buffer.
• right_gravity : boolean that indicates the direction the
extmark will be shifted in when new text is inserted (true
- for right, false for left). defaults to true.
+ for right, false for left). Defaults to true.
• end_right_gravity : boolean that indicates the direction
the extmark end position (if it exists) will be shifted in
when new text is inserted (true for right, false for
left). Defaults to false.
+ • undo_restore : Restore the exact position of the mark if
+ text around the mark was deleted and then restored by
+ undo. Defaults to true.
+ • invalidate : boolean that indicates whether to hide the
+ extmark if the entirety of its range is deleted. If
+ "undo_restore" is false, the extmark is deleted instead.
• priority: a priority value for the highlight group or sign
attribute. For example treesitter highlighting uses a
value of 100.
@@ -2712,14 +2780,14 @@ nvim_get_namespaces() *nvim_get_namespaces()*
nvim_set_decoration_provider({ns_id}, {*opts})
Set or change decoration provider for a |namespace|
- This is a very general purpose interface for having lua callbacks being
+ This is a very general purpose interface for having Lua callbacks being
triggered during the redraw code.
The expected usage is to set |extmarks| for the currently redrawn buffer.
|nvim_buf_set_extmark()| can be called to add marks on a per-window or
per-lines basis. Use the `ephemeral` key to only use the mark for the
current screen redraw (the callback will be called again for the next
- redraw ).
+ redraw).
Note: this function should not be called often. Rather, the callbacks
themselves can be used to throttle unneeded callbacks. the `on_start`
@@ -2731,11 +2799,14 @@ nvim_set_decoration_provider({ns_id}, {*opts})
for the extmarks set/modified inside the callback anyway.
Note: doing anything other than setting extmarks is considered
- experimental. Doing things like changing options are not expliticly
+ experimental. Doing things like changing options are not explicitly
forbidden, but is likely to have unexpected consequences (such as 100% CPU
consumption). doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is
quite dubious for the moment.
+ Note: It is not allowed to remove or update extmarks in 'on_line'
+ callbacks.
+
Attributes: ~
Lua |vim.api| only
@@ -2747,7 +2818,9 @@ nvim_set_decoration_provider({ns_id}, {*opts})
• on_buf: called for each buffer being redrawn (before window
callbacks) ["buf", bufnr, tick]
• on_win: called when starting to redraw a specific window.
- ["win", winid, bufnr, topline, botline_guess]
+ botline_guess is an approximation that does not exceed the
+ last line number. ["win", winid, bufnr, topline,
+ botline_guess]
• on_line: called for each buffer line being redrawn. (The
interaction with fold lines is subject to change) ["win",
winid, bufnr, row]
@@ -2765,16 +2838,16 @@ nvim_win_call({window}, {fun}) *nvim_win_call()*
Parameters: ~
• {window} Window handle, or 0 for current window
- • {fun} Function to call inside the window (currently lua callable
+ • {fun} Function to call inside the window (currently Lua callable
only)
Return: ~
- Return value of function. NB: will deepcopy lua values currently, use
- upvalues to send lua references in and out.
+ Return value of function. NB: will deepcopy Lua values currently, use
+ upvalues to send Lua references in and out.
See also: ~
- |win_execute()|
- |nvim_buf_call()|
+ • |win_execute()|
+ • |nvim_buf_call()|
nvim_win_close({window}, {force}) *nvim_win_close()*
Closes the window (like |:close| with a |window-ID|).
@@ -2815,6 +2888,9 @@ nvim_win_get_cursor({window}) *nvim_win_get_cursor()*
Return: ~
(row, col) tuple
+ See also: ~
+ • |getcurpos()|
+
nvim_win_get_height({window}) *nvim_win_get_height()*
Gets the window height
@@ -2919,8 +2995,9 @@ nvim_win_set_height({window}, {height}) *nvim_win_set_height()*
• {height} Height as a count of rows
nvim_win_set_hl_ns({window}, {ns_id}) *nvim_win_set_hl_ns()*
- Set highlight namespace for a window. This will use highlights defined in
- this namespace, but fall back to global highlights (ns=0) when missing.
+ Set highlight namespace for a window. This will use highlights defined
+ with |nvim_set_hl()| for this namespace, but fall back to global
+ highlights (ns=0) when missing.
This takes precedence over the 'winhighlight' option.
@@ -2943,6 +3020,40 @@ nvim_win_set_width({window}, {width}) *nvim_win_set_width()*
• {window} Window handle, or 0 for current window
• {width} Width as a count of columns
+nvim_win_text_height({window}, {*opts}) *nvim_win_text_height()*
+ Computes the number of screen lines occupied by a range of text in a given
+ window. Works for off-screen text and takes folds into account.
+
+ Diff filler or virtual lines above a line are counted as a part of that
+ line, unless the line is on "start_row" and "start_vcol" is specified.
+
+ Diff filler or virtual lines below the last buffer line are counted in the
+ result when "end_row" is omitted.
+
+ Line indexing is similar to |nvim_buf_get_text()|.
+
+ Parameters: ~
+ • {window} Window handle, or 0 for current window.
+ • {opts} Optional parameters:
+ • start_row: Starting line index, 0-based inclusive. When
+ omitted start at the very top.
+ • end_row: Ending line index, 0-based inclusive. When
+ omitted end at the very bottom.
+ • start_vcol: Starting virtual column index on "start_row",
+ 0-based inclusive, rounded down to full screen lines. When
+ omitted include the whole line.
+ • end_vcol: Ending virtual column index on "end_row",
+ 0-based exclusive, rounded up to full screen lines. When
+ omitted include the whole line.
+
+ Return: ~
+ Dictionary containing text height information, with these keys:
+ • all: The total number of screen lines occupied by the range.
+ • fill: The number of diff filler or virtual lines among them.
+
+ See also: ~
+ • |virtcol()| for text width.
+
==============================================================================
Win_Config Functions *api-win_config*
@@ -2980,6 +3091,7 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
Example (Lua): buffer-relative float (travels as buffer is scrolled) >lua
vim.api.nvim_open_win(0, false,
{relative='win', width=12, height=3, bufpos={100,10}})
+ })
<
Attributes: ~
@@ -3035,8 +3147,8 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
In general, values below 100 are recommended, unless
there is a good reason to overshadow builtin elements.
- • style: Configure the appearance of the window. Currently
- only takes one non-empty value:
+ • style: (optional) Configure the appearance of the window.
+ Currently only supports one value:
• "minimal" Nvim will display the window with many UI
options disabled. This is useful when displaying a
temporary float where the text should not be edited.
@@ -3059,7 +3171,7 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
• "shadow": A drop shadow effect by blending with the
background.
• If it is an array, it should have a length of eight or
- any divisor of eight. The array will specifify the eight
+ any divisor of eight. The array will specify the eight
chars building up the border in a clockwise fashion
starting with the top-left corner. As an example, the
double box style could be specified as [ "╔", "═" ,"╗",
@@ -3075,14 +3187,24 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()*
specified by character: [ ["+", "MyCorner"], ["x",
"MyBorder"] ].
- • title: Title (optional) in window border, String or list.
- List is [text, highlight] tuples. if is string the default
- highlight group is `FloatTitle`.
- • title_pos: Title position must set with title option.
- value can be of `left` `center` `right` default is left.
+ • title: Title (optional) in window border, string or list.
+ List should consist of `[text, highlight]` tuples. If
+ string, the default highlight group is `FloatTitle`.
+ • title_pos: Title position. Must be set with `title`
+ option. Value can be one of "left", "center", or "right".
+ Default is `"left"`.
+ • footer: Footer (optional) in window border, string or
+ list. List should consist of `[text, highlight]` tuples.
+ If string, the default highlight group is `FloatFooter`.
+ • footer_pos: Footer position. Must be set with `footer`
+ option. Value can be one of "left", "center", or "right".
+ Default is `"left"`.
• noautocmd: If true then no buffer-related autocommand
events such as |BufEnter|, |BufLeave| or |BufWinEnter| may
fire from calling this function.
+ • fixed: If true when anchor is NW or SW, the float window
+ would be kept fixed even if the window would be truncated.
+ • hide: If true the floating window will be hidden.
Return: ~
Window handle, or 0 on error
@@ -3112,7 +3234,7 @@ nvim_win_set_config({window}, {*config}) *nvim_win_set_config()*
• {config} Map defining the window configuration, see |nvim_open_win()|
See also: ~
- |nvim_open_win()|
+ • |nvim_open_win()|
==============================================================================
@@ -3185,8 +3307,8 @@ nvim_tabpage_set_var({tabpage}, {name}, {value})
Autocmd Functions *api-autocmd*
nvim_clear_autocmds({*opts}) *nvim_clear_autocmds()*
- Clear all autocommands that match the corresponding {opts}. To delete a
- particular autocmd, see |nvim_del_autocmd()|.
+ Clears all autocommands selected by {opts}. To delete autocmds see
+ |nvim_del_autocmd()|.
Parameters: ~
• {opts} Parameters
@@ -3230,7 +3352,7 @@ nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()*
Integer id of the created group.
See also: ~
- |autocmd-groups|
+ • |autocmd-groups|
nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()*
Creates an |autocommand| event handler, defined by `callback` (Lua function or Vimscript function name string) or `command` (Ex command string).
@@ -3239,7 +3361,7 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()*
vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
pattern = {"*.c", "*.h"},
callback = function(ev)
- print(string.format('event fired: s', vim.inspect(ev)))
+ print(string.format('event fired: %s', vim.inspect(ev)))
end
})
<
@@ -3251,9 +3373,9 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()*
})
<
- Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like
- "$HOME" and "~" must be expanded explicitly: >lua
- pattern = vim.fn.expand("~") .. "/some/path/*.py"
+ Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|),
+ thus names like "$HOME" and "~" must be expanded explicitly: >lua
+ pattern = vim.fn.expand("~") .. "/some/path/*.py"
<
Parameters: ~
@@ -3295,8 +3417,8 @@ nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()*
Autocommand id (number)
See also: ~
- |autocommand|
- |nvim_del_autocmd()|
+ • |autocommand|
+ • |nvim_del_autocmd()|
nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()*
Delete an autocommand group by id.
@@ -3311,8 +3433,8 @@ nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()*
• {id} Integer The id of the group.
See also: ~
- |nvim_del_augroup_by_name()|
- |nvim_create_augroup()|
+ • |nvim_del_augroup_by_name()|
+ • |nvim_create_augroup()|
nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()*
Delete an autocommand group by name.
@@ -3325,18 +3447,13 @@ nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()*
• {name} String The name of the group.
See also: ~
- |autocmd-groups|
+ • |autocmd-groups|
nvim_del_autocmd({id}) *nvim_del_autocmd()*
- Delete an autocommand by id.
-
- NOTE: Only autocommands created via the API have an id.
+ Deletes an autocommand by id.
Parameters: ~
- • {id} Integer The id returned by nvim_create_autocmd
-
- See also: ~
- |nvim_create_autocmd()|
+ • {id} Integer Autocommand id returned by |nvim_create_autocmd()|
nvim_exec_autocmds({event}, {*opts}) *nvim_exec_autocmds()*
Execute all autocommands for {event} that match the corresponding {opts}
@@ -3357,23 +3474,23 @@ nvim_exec_autocmds({event}, {*opts}) *nvim_exec_autocmds()*
callback. See |nvim_create_autocmd()| for details.
See also: ~
- |:doautocmd|
+ • |:doautocmd|
nvim_get_autocmds({*opts}) *nvim_get_autocmds()*
Get all autocommands that match the corresponding {opts}.
These examples will get autocommands matching ALL the given criteria: >lua
- -- Matches all criteria
- autocommands = vim.api.nvim_get_autocmds({
- group = "MyGroup",
- event = {"BufEnter", "BufWinEnter"},
- pattern = {"*.c", "*.h"}
- })
-
- -- All commands from one group
- autocommands = vim.api.nvim_get_autocmds({
- group = "MyGroup",
- })
+ -- Matches all criteria
+ autocommands = vim.api.nvim_get_autocmds({
+ group = "MyGroup",
+ event = {"BufEnter", "BufWinEnter"},
+ pattern = {"*.c", "*.h"}
+ })
+
+ -- All commands from one group
+ autocommands = vim.api.nvim_get_autocmds({
+ group = "MyGroup",
+ })
<
NOTE: When multiple patterns or events are provided, it will find all the
@@ -3420,8 +3537,8 @@ nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()*
Implies that the client is ready to show the UI. Adds the client to the
list of UIs. |nvim_list_uis()|
- Note:
- If multiple UI clients are attached, the global screen dimensions
+ Note: ~
+ • If multiple UI clients are attached, the global screen dimensions
degrade to the smallest client. E.g. if client A requests 80x40 but
client B requests 200x100, the global screen has size 80x40.
@@ -3484,6 +3601,22 @@ nvim_ui_set_option({name}, {value}) *nvim_ui_set_option()*
Attributes: ~
|RPC| only
+nvim_ui_term_event({event}, {value}) *nvim_ui_term_event()*
+ Tells Nvim when a terminal event has occurred
+
+ The following terminal events are supported:
+
+ • "termresponse": The terminal sent an OSC or DCS response sequence to
+ Nvim. The payload is the received response. Sets |v:termresponse| and
+ fires |TermResponse|.
+
+ Attributes: ~
+ |RPC| only
+
+ Parameters: ~
+ • {event} Event name
+ • {payload} Event payload
+
nvim_ui_try_resize({width}, {height}) *nvim_ui_try_resize()*
TODO: Documentation
diff --git a/runtime/doc/arabic.txt b/runtime/doc/arabic.txt
index 0a80edb981..f9d7ee1a8a 100644
--- a/runtime/doc/arabic.txt
+++ b/runtime/doc/arabic.txt
@@ -231,7 +231,7 @@ o Enable Arabic settings [short-cut]
- While in Left-to-right mode, enter ':set rl' in the command line
('rl' is the abbreviation for rightleft).
- - Put the ':set rl' line in your vimrc file to start Vim in
+ - Put the ':set rl' line in your vimrc file to start Vim in
right-to-left mode permanently.
+ Arabic right-to-left command-line Mode
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index 8cc4754880..c6f6559e37 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -16,7 +16,7 @@ For a basic explanation, see section |40.3| in the user manual.
You can specify commands to be executed automatically when reading or writing
a file, when entering or leaving a buffer or window, and when exiting Vim.
For example, you can create an autocommand to set the 'cindent' option for
-files matching *.c. You can also use autocommands to implement advanced
+files matching `*.c`. You can also use autocommands to implement advanced
features, such as editing compressed files (see |gzip-example|). The usual
place to put autocommands is in your vimrc file.
@@ -118,7 +118,7 @@ manually. Mostly the screen will not scroll up, thus there is no hit-enter
prompt. When one command outputs two messages this can happen anyway.
==============================================================================
-3. Removing autocommands *autocmd-remove*
+3. Removing autocommands *autocmd!* *autocmd-remove*
:au[tocmd]! [group] {event} {aupat} [++once] [++nested] {cmd}
Remove all autocommands associated with {event} and
@@ -197,7 +197,7 @@ For READING FILES there are four kinds of events possible:
Vim uses only one of these four kinds when reading a file. The "Pre" and
"Post" events are both triggered, before and after reading the file.
-Note that the autocommands for the *ReadPre events and all the Filter events
+Note that the autocommands for the "*ReadPre" events and all the Filter events
are not allowed to change the current buffer (you will get an error message if
this happens). This is to prevent the file to be read into the wrong buffer.
@@ -212,28 +212,27 @@ events.
Nvim recognizes the following events. Names are case-insensitive.
*BufAdd*
-BufAdd Just after creating a new buffer which is
- added to the buffer list, or adding a buffer
- to the buffer list, a buffer in the buffer
- list was renamed.
- Not triggered for the initial buffers created
- during startup.
+BufAdd After adding a new buffer or existing unlisted
+ buffer to the buffer list (except during
+ startup, see |VimEnter|), or renaming a listed
+ buffer.
Before |BufEnter|.
- NOTE: Current buffer "%" may be different from
- the buffer being created "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
*BufDelete*
BufDelete Before deleting a buffer from the buffer list.
The BufUnload may be called first (if the
buffer was loaded).
Also used just before a buffer in the buffer
list is renamed.
- NOTE: Current buffer "%" may be different from
- the buffer being deleted "<afile>" and "<abuf>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
Do not change to another buffer.
*BufEnter*
-BufEnter After entering a buffer. Useful for setting
- options for a file type. Also executed when
- starting to edit a buffer.
+BufEnter After entering (visiting, switching-to) a new
+ or existing buffer. Useful for setting
+ filetype options. Compare |BufNew| which
+ does not trigger for existing buffers.
After |BufAdd|.
After |BufReadPost|.
*BufFilePost*
@@ -248,8 +247,8 @@ BufHidden Before a buffer becomes hidden: when there are
the buffer is not unloaded or deleted.
Not used for ":qa" or ":q" when exiting Vim.
- NOTE: current buffer "%" may be different from
- the buffer being unloaded "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
*BufLeave*
BufLeave Before leaving to another buffer. Also when
leaving or closing the current window and the
@@ -260,12 +259,14 @@ BufLeave Before leaving to another buffer. Also when
BufModifiedSet After the `'modified'` value of a buffer has
been changed.
*BufNew*
-BufNew Just after creating a new buffer. Also used
- just after a buffer has been renamed. When
- the buffer is added to the buffer list BufAdd
- will be triggered too.
- NOTE: Current buffer "%" may be different from
- the buffer being created "<afile>".
+BufNew After creating a new buffer (except during
+ startup, see |VimEnter|) or renaming an
+ existing buffer. Unlike |BufEnter|, visiting
+ (switching to) an existing buffer will not
+ trigger this again.
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
+ See also |BufAdd|, |BufNewFile|.
*BufNewFile*
BufNewFile When starting to edit a file that doesn't
exist. Can be used to read in a skeleton
@@ -292,14 +293,14 @@ BufReadPre When starting to edit a new buffer, before
reading the file into the buffer. Not used
if the file doesn't exist.
*BufUnload*
-BufUnload Before unloading a buffer, when the text in
+BufUnload Before unloading a buffer, when the text in
the buffer is going to be freed.
After BufWritePost.
Before BufDelete.
Triggers for all loaded buffers when Vim is
going to exit.
- NOTE: Current buffer "%" may be different from
- the buffer being unloaded "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
Do not switch buffers or windows!
Not triggered when exiting and v:dying is 2 or
more.
@@ -319,8 +320,8 @@ BufWinLeave Before a buffer is removed from a window.
Not when it's still visible in another window.
Also triggered when exiting.
Before BufUnload, BufHidden.
- NOTE: Current buffer "%" may be different from
- the buffer being unloaded "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
Not triggered when exiting and v:dying is 2 or
more.
*BufWipeout*
@@ -330,8 +331,8 @@ BufWipeout Before completely deleting a buffer. The
buffer list). Also used just before a buffer
is renamed (also when it's not in the buffer
list).
- NOTE: Current buffer "%" may be different from
- the buffer being deleted "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>", "<abuf>". |<buffer=abuf>|
Do not change to another buffer.
*BufWrite* *BufWritePre*
BufWrite or BufWritePre Before writing the whole buffer to a file.
@@ -380,6 +381,7 @@ CmdlineChanged After a change was made to the text inside
CmdlineEnter After entering the command-line (including
non-interactive use of ":" in a mapping: use
|<Cmd>| instead to avoid this).
+ The pattern is matched against |cmdline-char|.
<afile> expands to the |cmdline-char|.
Sets these |v:event| keys:
cmdlevel
@@ -425,7 +427,7 @@ ColorSchemePre Before loading a color scheme. |:colorscheme|
Useful to setup removing things added by a
color scheme, before another one is loaded.
-CompleteChanged *CompleteChanged*
+CompleteChanged *CompleteChanged*
After each time the Insert mode completion
menu changed. Not fired on popup menu hide,
use |CompleteDonePre| or |CompleteDone| for
@@ -504,8 +506,7 @@ CursorMoved After the cursor was moved in Normal or Visual
"x", "rx" or "p".
Not always triggered when there is typeahead,
while executing commands in a script file, or
- when an operator is pending. Always triggered
- when moving to another window.
+ when an operator is pending.
For an example see |match-parens|.
Note: Cannot be skipped with |:noautocmd|.
Careful: This is triggered very often, don't
@@ -589,7 +590,7 @@ FileChangedShell When Vim notices that the modification time of
Triggered for each changed file, after:
- executing a shell command
- |:checktime|
- - |FocusGained|
+ - |FocusGained|
Not used when 'autoread' is set and the buffer
was not changed. If a FileChangedShell
@@ -597,8 +598,8 @@ FileChangedShell When Vim notices that the modification time of
prompt is not given.
|v:fcs_reason| indicates what happened. Set
|v:fcs_choice| to control what happens next.
- NOTE: Current buffer "%" may be different from
- the buffer that was changed "<afile>".
+ NOTE: Current buffer "%" is not the target
+ buffer "<afile>" and "<abuf>". |<buffer=abuf>|
*E246* *E811*
Cannot switch, jump to or delete buffers.
Non-recursive (event cannot trigger itself).
@@ -703,7 +704,6 @@ InsertCharPre When a character is typed in Insert mode,
inserted literally.
Cannot change the text. |textlock|
- Not triggered when 'paste' is set.
*InsertEnter*
InsertEnter Just before starting Insert mode. Also for
Replace mode and Virtual Replace mode. The
@@ -775,11 +775,13 @@ OptionSet After setting an option (except during
the option. Similarly |v:option_oldglobal| is
only set when |:set| or |:setglobal| was used.
- Note that when setting a |global-local| string
- option with |:set|, then |v:option_old| is the
- old global value. However, for all other kinds
- of options (local string options, global-local
- number options, ...) it is the old local
+ This does not set |<abuf>|, you could use
+ |bufnr()|.
+
+ Note that when setting a |global-local| option
+ with |:set|, then |v:option_old| is the old
+ global value. However, for all options that
+ are not global-local it is the old local
value.
OptionSet is not triggered on startup and for
@@ -859,6 +861,27 @@ RecordingLeave When a macro stops recording.
Sets these |v:event| keys:
regcontents
regname
+ *SafeState*
+SafeState When nothing is pending, going to wait for the
+ user to type a character.
+ This will not be triggered when:
+ - an operator is pending
+ - a register was entered with "r
+ - halfway executing a command
+ - executing a mapping
+ - there is typeahead
+ - Insert mode completion is active
+ - Command line completion is active
+ You can use `mode()` to find out what state
+ Vim is in. That may be:
+ - VIsual mode
+ - Normal mode
+ - Insert mode
+ - Command-line mode
+ Depending on what you want to do, you may also
+ check more with `state()`, e.g. whether the
+ screen was scrolled for messages.
+
*SessionLoadPost*
SessionLoadPost After loading the session file created using
the |:mksession| command.
@@ -964,12 +987,25 @@ TermClose When a |terminal| job ends.
Sets these |v:event| keys:
status
*TermResponse*
-TermResponse After the response to t_RV is received from
- the terminal. The value of |v:termresponse|
- can be used to do things depending on the
- terminal version. May be triggered halfway
- through another event (file I/O, a shell
- command, or anything else that takes time).
+TermResponse When Nvim receives an OSC or DCS response from
+ the terminal. Sets |v:termresponse|. When used
+ from Lua, the response string is included in
+ the "data" field of the autocommand callback.
+ May be triggered halfway through another event
+ (file I/O, a shell command, or anything else
+ that takes time). Example: >lua
+
+ -- Query the terminal palette for the RGB value of color 1
+ -- (red) using OSC 4
+ vim.api.nvim_create_autocmd('TermResponse', {
+ once = true,
+ callback = function(args)
+ local resp = args.data
+ local r, g, b = resp:match("\x1b%]4;1;rgb:(%w+)/(%w+)/(%w+)")
+ end,
+ })
+ io.stdout:write("\x1b]4;1;?\x1b\\")
+<
*TextChanged*
TextChanged After a change was made to the text in the
current buffer in Normal mode. That is after
@@ -999,7 +1035,7 @@ TextChangedT After a change was made to the text in the
*TextYankPost*
TextYankPost Just after a |yank| or |deleting| command, but not
if the black hole register |quote_| is used nor
- for |setreg()|. Pattern must be *.
+ for |setreg()|. Pattern must be "*".
Sets these |v:event| keys:
inclusive
operator
@@ -1162,7 +1198,7 @@ Set the 'cindent' option for C files in the /vim/src directory. >
If you have a link from "/tmp/test.c" to "/home/nobody/vim/src/test.c", and
you start editing "/tmp/test.c", this autocommand will match.
-Note: To match part of a path, but not from the root directory, use a '*' as
+Note: To match part of a path, but not from the root directory, use a "*" as
the first character. Example: >
:autocmd BufRead */doc/*.txt set tw=78
This autocommand will for example be executed for "/tmp/doc/xx.txt" and
@@ -1415,8 +1451,8 @@ When there is a matching "*Cmd" autocommand, it is assumed it will do the
writing. No further writing is done and the other events are not triggered.
|Cmd-event|
-Note that the *WritePost commands should undo any changes to the buffer that
-were caused by the *WritePre commands; otherwise, writing the file will have
+Note that the "*WritePost" commands should undo any changes to the buffer that
+were caused by the "*WritePre" commands; otherwise, writing the file will have
the side effect of changing the buffer.
Before executing the autocommands, the buffer from which the lines are to be
@@ -1424,15 +1460,15 @@ written temporarily becomes the current buffer. Unless the autocommands
change the current buffer or delete the previously current buffer, the
previously current buffer is made the current buffer again.
-The *WritePre and *AppendPre autocommands must not delete the buffer from
+The "*WritePre" and "*AppendPre" autocommands must not delete the buffer from
which the lines are to be written.
The '[ and '] marks have a special position:
-- Before the *ReadPre event the '[ mark is set to the line just above where
+- Before the "*ReadPre" event the '[ mark is set to the line just above where
the new lines will be inserted.
-- Before the *ReadPost event the '[ mark is set to the first line that was
+- Before the "*ReadPost" event the '[ mark is set to the first line that was
just read, the '] mark to the last line.
-- Before executing the *WriteCmd, *WritePre and *AppendPre autocommands the '[
+- Before executing the "*WriteCmd", "*WritePre" and "*AppendPre" autocommands the '[
mark is set to the first line that will be written, the '] mark to the last
line.
Careful: '[ and '] change when using commands that change the buffer.
@@ -1540,7 +1576,7 @@ To read a skeleton (template) file when opening a new file: >
:autocmd BufNewFile *.h 0r ~/vim/skeleton.h
:autocmd BufNewFile *.java 0r ~/vim/skeleton.java
-To insert the current date and time in a *.html file when writing it: >
+To insert the current date and time in a "*.html" file when writing it: >
:autocmd BufWritePre,FileWritePre *.html ks|call LastMod()|'s
:fun LastMod()
diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt
index 2fc7dce7fc..6ffb514487 100644
--- a/runtime/doc/builtin.txt
+++ b/runtime/doc/builtin.txt
@@ -1,7 +1,7 @@
*builtin.txt* Nvim
- VIM REFERENCE MANUAL by Bram Moolenaar
+ NVIM REFERENCE MANUAL
Builtin functions *vimscript-functions* *builtin-functions*
@@ -9,633 +9,62 @@ Builtin functions *vimscript-functions* *builtin-functions*
For functions grouped by what they are used for see |function-list|.
Type |gO| to see the table of contents.
-
-==============================================================================
-1. Overview *builtin-function-list*
-
-Use CTRL-] on the function name to jump to the full explanation.
-
-USAGE RESULT DESCRIPTION ~
-
-abs({expr}) Float or Number absolute value of {expr}
-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}, {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
-argv({nr} [, {winid}]) String {nr} entry of the argument list
-argv([-1, {winid}]) List the argument list
-asin({expr}) Float arc sine of {expr}
-assert_beeps({cmd}) Number assert {cmd} causes a beep
-assert_equal({exp}, {act} [, {msg}])
- Number assert {exp} is equal to {act}
-assert_equalfile({fname-one}, {fname-two} [, {msg}])
- Number assert file contents are equal
-assert_exception({error} [, {msg}])
- Number assert {error} is in v:exception
-assert_fails({cmd} [, {error}]) Number assert {cmd} fails
-assert_false({actual} [, {msg}])
- Number assert {actual} is false
-assert_inrange({lower}, {upper}, {actual} [, {msg}])
- Number assert {actual} is inside the range
-assert_match({pat}, {text} [, {msg}])
- Number assert {pat} matches {text}
-assert_nobeep({cmd}) Number assert {cmd} does not cause a beep
-assert_notequal({exp}, {act} [, {msg}])
- Number assert {exp} is not equal {act}
-assert_notmatch({pat}, {text} [, {msg}])
- Number assert {pat} not matches {text}
-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({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
-bufadd({name}) Number add a buffer to the buffer list
-bufexists({expr}) Number |TRUE| if buffer {expr} exists
-buflisted({expr}) Number |TRUE| if buffer {expr} is listed
-bufload({expr}) Number load buffer {expr} if not loaded yet
-bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded
-bufname([{expr}]) String Name of the buffer {expr}
-bufnr([{expr} [, {create}]]) Number Number of the buffer {expr}
-bufwinid({expr}) Number |window-ID| of buffer {expr}
-bufwinnr({expr}) Number window number of buffer {expr}
-byte2line({byte}) Number line number at byte count {byte}
-byteidx({expr}, {nr}) Number byte index of {nr}th char in {expr}
-byteidxcomp({expr}, {nr}) Number byte index of {nr}th char in {expr}
-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
-chansend({id}, {data}) Number Writes {data} to channel
-char2nr({expr} [, {utf8}]) Number ASCII/UTF-8 value of first char in {expr}
-charclass({string}) Number character class of {string}
-charcol({expr} [, {winid}]) 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} [, {winid}]) 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
-complete_info([{what}]) Dict get current completion information
-confirm({msg} [, {choices} [, {default} [, {type}]]])
- Number number of choice picked by user
-copy({expr}) any make a shallow copy of {expr}
-cos({expr}) Float cosine of {expr}
-cosh({expr}) Float hyperbolic cosine of {expr}
-count({comp}, {expr} [, {ic} [, {start}]])
- Number count how many {expr} are in {comp}
-ctxget([{index}]) Dict return the |context| dict at {index}
-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}
-ctxsize() Number return |context-stack| size
-cursor({lnum}, {col} [, {off}])
- Number move cursor to {lnum}, {col}, {off}
-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}])
- Number delete lines from buffer {buf}
-dictwatcheradd({dict}, {pattern}, {callback})
- Start watching a dictionary
-dictwatcherdel({dict}, {pattern}, {callback})
- Stop watching a dictionary
-did_filetype() Number |TRUE| if FileType autocommand event used
-diff_filler({lnum}) Number diff filler lines about {lnum}
-diff_hlID({lnum}, {col}) Number diff highlighting at {lnum}/{col}
-digraph_get({chars}) String get the |digraph| of {chars}
-digraph_getlist([{listall}]) List get all |digraph|s
-digraph_set({chars}, {digraph}) Boolean register |digraph|
-digraph_setlist({digraphlist}) Boolean register multiple |digraph|s
-empty({expr}) Number |TRUE| if {expr} is empty
-environ() Dict return environment variables
-escape({string}, {chars}) String escape {chars} in {string} with '\'
-eval({string}) any evaluate {string} into its value
-eventhandler() Number |TRUE| if inside an event handler
-executable({expr}) Number 1 if executable {expr} exists
-execute({command}) String execute and capture output of {command}
-exepath({expr}) String full path of the command {expr}
-exists({expr}) Number |TRUE| if {expr} exists
-exp({expr}) Float exponential of {expr}
-expand({expr} [, {nosuf} [, {list}]])
- any expand special keywords in {expr}
-expandcmd({string} [, {options}])
- String expand {string} like with `:edit`
-extend({expr1}, {expr2} [, {expr3}])
- List/Dict insert items of {expr2} into {expr1}
-feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer
-filereadable({file}) Number |TRUE| if {file} is a readable file
-filewritable({file}) Number |TRUE| if {file} is a writable file
-filter({expr1}, {expr2}) List/Dict remove items from {expr1} where
- {expr2} is 0
-finddir({name} [, {path} [, {count}]])
- String find directory {name} in {path}
-findfile({name} [, {path} [, {count}]])
- String find file {name} in {path}
-flatten({list} [, {maxdepth}]) List flatten {list} up to {maxdepth} levels
-float2nr({expr}) Number convert Float {expr} to a Number
-floor({expr}) Float round {expr} down
-fmod({expr1}, {expr2}) Float remainder of {expr1} / {expr2}
-fnameescape({fname}) String escape special characters in {fname}
-fnamemodify({fname}, {mods}) String modify file name
-foldclosed({lnum}) Number first line of fold at {lnum} if closed
-foldclosedend({lnum}) Number last line of fold at {lnum} if closed
-foldlevel({lnum}) Number fold level at {lnum}
-foldtext() String line displayed for closed fold
-foldtextresult({lnum}) String text for closed fold at {lnum}
-fullcommand({name}) String get full command from {name}
-funcref({name} [, {arglist}] [, {dict}])
- Funcref reference to function {name}
-function({name} [, {arglist}] [, {dict}])
- Funcref named reference to function {name}
-garbagecollect([{atexit}]) none free memory, breaking cyclic references
-get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def}
-get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def}
-get({func}, {what}) any get property of funcref/partial {func}
-getbufinfo([{buf}]) List information about buffers
-getbufline({buf}, {lnum} [, {end}])
- List lines {lnum} to {end} of buffer {buf}
-getbufoneline({buf}, {lnum}) String line {lnum} of buffer {buf}
-getbufvar({buf}, {varname} [, {def}])
- any variable {varname} in buffer {buf}
-getcellwidths() List get character cell width overrides
-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
-getcmdcompltype() String return the type of the current
- command-line completion
-getcmdline() String return the current command-line
-getcmdpos() Number return cursor position in command-line
-getcmdscreenpos() Number return cursor screen position in
- command-line
-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([{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
-getfperm({fname}) String file permissions of file {fname}
-getfsize({fname}) Number size in bytes of file {fname}
-getftime({fname}) Number last modification time of file
-getftype({fname}) String description of type of file {fname}
-getjumplist([{winnr} [, {tabnr}]])
- List list of jump list items
-getline({lnum}) String line {lnum} of current buffer
-getline({lnum}, {end}) List lines {lnum} to {end} of current buffer
-getloclist({nr}) List list of location list items
-getloclist({nr}, {what}) Dict get specific location list properties
-getmarklist([{buf}]) List list of global/local marks
-getmatches([{win}]) List list of current matches
-getmousepos() Dict last known mouse position
-getpid() Number process ID of Vim
-getpos({expr}) List position of cursor, mark, etc.
-getqflist() List list of quickfix items
-getqflist({what}) Dict get specific quickfix list properties
-getreg([{regname} [, 1 [, {list}]]])
- String or List contents of a register
-getreginfo([{regname}]) Dict information about a register
-getregtype([{regname}]) String type of a register
-gettabinfo([{expr}]) List list of tab pages
-gettabvar({nr}, {varname} [, {def}])
- any variable {varname} in tab {nr} or {def}
-gettabwinvar({tabnr}, {winnr}, {name} [, {def}])
- any {name} in {winnr} in tab page {tabnr}
-gettagstack([{nr}]) Dict get the tag stack of window {nr}
-gettext({text}) String lookup translation of {text}
-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
-getwinvar({nr}, {varname} [, {def}])
- any variable {varname} in window {nr}
-glob({expr} [, {nosuf} [, {list} [, {alllinks}]]])
- any expand file wildcards in {expr}
-glob2regpat({expr}) String convert a glob pat into a search pat
-globpath({path}, {expr} [, {nosuf} [, {list} [, {alllinks}]]])
- String do glob({expr}) for all dirs in {path}
-has({feature}) Number |TRUE| if feature {feature} supported
-has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key}
-haslocaldir([{winnr} [, {tabnr}]])
- Number |TRUE| if the window executed |:lcd| or
- the tab executed |:tcd|
-hasmapto({what} [, {mode} [, {abbr}]])
- Number |TRUE| if mapping to {what} exists
-histadd({history}, {item}) Number add an item to a history
-histdel({history} [, {item}]) Number remove an item from a history
-histget({history} [, {index}]) String get the item {index} from a history
-histnr({history}) Number highest index of a history
-hlID({name}) Number syntax ID of highlight group {name}
-hlexists({name}) Number |TRUE| if highlight group {name} exists
-hostname() String name of the machine Vim is running on
-iconv({expr}, {from}, {to}) String convert encoding of {expr}
-indent({lnum}) Number indent of line {lnum}
-index({object}, {expr} [, {start} [, {ic}]])
- Number index in {object} where {expr} appears
-input({prompt} [, {text} [, {completion}]])
- String get input from the user
-inputlist({textlist}) Number let the user pick from a choice list
-inputrestore() Number restore typeahead
-inputsave() Number save and clear typeahead
-inputsecret({prompt} [, {text}])
- String like input() but hiding the text
-insert({object}, {item} [, {idx}])
- List insert {item} in {object} [before {idx}]
-interrupt() none interrupt script execution
-invert({expr}) Number bitwise invert
-isdirectory({directory}) Number |TRUE| if {directory} is a directory
-isinf({expr}) Number determine if {expr} is infinity value
- (positive or negative)
-islocked({expr}) Number |TRUE| if {expr} is locked
-isnan({expr}) Number |TRUE| if {expr} is NaN
-id({expr}) String identifier of the container
-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
-jobstop({id}) Number Stops a job
-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
-keys({dict}) List keys in {dict}
-keytrans({string}) String translate internal keycodes to a form
- that can be used by |:map|
-len({expr}) Number the length of {expr}
-libcall({lib}, {func}, {arg}) String call {func} in library {lib} with {arg}
-libcallnr({lib}, {func}, {arg}) Number idem, but return a Number
-line({expr} [, {winid}]) Number line nr of cursor, last line or mark
-line2byte({lnum}) Number byte count of line {lnum}
-lispindent({lnum}) Number Lisp indent for line {lnum}
-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
-map({expr1}, {expr2}) List/Dict change each item in {expr1} to {expr}
-maparg({name} [, {mode} [, {abbr} [, {dict}]]])
- String or Dict
- rhs of mapping {name} in mode {mode}
-mapcheck({name} [, {mode} [, {abbr}]])
- String check for mappings matching {name}
-mapset({mode}, {abbr}, {dict})
- none restore mapping from |maparg()| result
-match({expr}, {pat} [, {start} [, {count}]])
- Number position where {pat} matches in {expr}
-matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
- Number highlight {pattern} with {group}
-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}]])
- Number position where {pat} ends in {expr}
-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}]])
- String {count}th match of {pat} in {expr}
-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}
-menu_info({name} [, {mode}]) Dict get menu item information
-min({expr}) Number minimum value of items in {expr}
-mkdir({name} [, {path} [, {prot}]])
- Number create directory {name}
-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}
-nvim_...({args}...) any call nvim |api| functions
-or({expr}, {expr}) Number bitwise OR
-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}
-printf({fmt}, {expr1}...) String format text
-prompt_getprompt({buf}) String get prompt text
-prompt_setcallback({buf}, {expr}) none set prompt callback function
-prompt_setinterrupt({buf}, {text}) none set prompt interrupt function
-prompt_setprompt({buf}, {text}) none set prompt text
-pum_getpos() Dict position and size of pum if visible
-pumvisible() Number whether popup menu is visible
-py3eval({expr}) any evaluate |python3| expression
-pyeval({expr}) any evaluate |Python| 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}
-readblob({fname}) Blob read a |Blob| from {fname}
-readdir({dir} [, {expr}]) List file names in {dir} selected by {expr}
-readfile({fname} [, {type} [, {max}]])
- List get list of lines from file {fname}
-reduce({object}, {func} [, {initial}])
- any reduce {object} using {func}
-reg_executing() String get the executing register name
-reg_recorded() String get the last recorded register name
-reg_recording() String get the recording register name
-reltime([{start} [, {end}]]) List get time value
-reltimefloat({time}) Float turn the time value into a Float
-reltimestr({time}) String turn time value into a String
-remove({list}, {idx} [, {end}]) any/List
- remove items {idx}-{end} from {list}
-remove({blob}, {idx} [, {end}]) Number/Blob
- remove bytes {idx}-{end} from {blob}
-remove({dict}, {key}) any remove entry {key} from {dict}
-rename({from}, {to}) Number rename (move) file from {from} to {to}
-repeat({expr}, {count}) String repeat {expr} {count} times
-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}...])
- Sends an |RPC| notification to {channel}
-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
-screenchars({row}, {col}) List List of characters at screen position
-screencol() Number current cursor column
-screenpos({winid}, {lnum}, {col}) Dict screen row and col of a text character
-screenrow() Number current cursor row
-screenstring({row}, {col}) String characters at screen position
-search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
- Number search for {pattern}
-searchcount([{options}]) Dict Get or update the last search count
-searchdecl({name} [, {global} [, {thisblock}]])
- Number search for variable declaration
-searchpair({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
- Number search for other end of start/end pair
-searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [...]]])
- List search for other end of start/end pair
-searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
- List search for {pattern}
-serverlist() String get a list of available servers
-setbufline({expr}, {lnum}, {text})
- Number set line {lnum} to {text} in buffer
- {expr}
-setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val}
-setcellwidths({list}) none set character cell width overrides
-setcharpos({expr}, {list}) Number set the {expr} position to {list}
-setcharsearch({dict}) Dict set character search from {dict}
-setcmdline({str} [, {pos}]) Number set command-line
-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}
-setloclist({nr}, {list} [, {action}])
- Number modify location list using {list}
-setloclist({nr}, {list}, {action}, {what})
- Number modify specific location list props
-setmatches({list} [, {win}]) Number restore a list of matches
-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
-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}
-settagstack({nr}, {dict} [, {action}])
- Number modify tag stack using {dict}
-setwinvar({nr}, {varname}, {val}) set {varname} in window {nr} to {val}
-sha256({string}) String SHA256 checksum of {string}
-shellescape({string} [, {special}])
- String escape {string} for use as shell
- command argument
-shiftwidth([{col}]) Number effective value of 'shiftwidth'
-sign_define({name} [, {dict}]) Number define or update a sign
-sign_define({list}) List define or update a list of signs
-sign_getdefined([{name}]) List get a list of defined signs
-sign_getplaced([{buf} [, {dict}]])
- List get a list of placed signs
-sign_jump({id}, {group}, {buf})
- Number jump to a sign
-sign_place({id}, {group}, {name}, {buf} [, {dict}])
- Number place a sign
-sign_placelist({list}) List place a list of signs
-sign_undefine([{name}]) Number undefine a sign
-sign_undefine({list}) List undefine a list of signs
-sign_unplace({group} [, {dict}])
- Number unplace a sign
-sign_unplacelist({list}) List unplace a list of signs
-simplify({filename}) String simplify filename as much as possible
-sin({expr}) Float sine of {expr}
-sinh({expr}) Float hyperbolic sine of {expr}
-sockconnect({mode}, {address} [, {opts}])
- Number Connects to socket
-sort({list} [, {func} [, {dict}]])
- List sort {list}, using {func} to compare
-soundfold({word}) String sound-fold {word}
-spellbadword() String badly spelled word at cursor
-spellsuggest({word} [, {max} [, {capital}]])
- List spelling suggestions
-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
-str2list({expr} [, {utf8}]) List convert each character of {expr} to
- ASCII/UTF-8 value
-str2nr({expr} [, {base} [, {quoted}]])
- Number convert String to Number
-strcharlen({expr}) Number character length of the String {expr}
-strcharpart({str}, {start} [, {len}])
- String {len} characters of {str} at
- character {start}
-strchars({expr} [, {skipcc}]) Number character count of the String {expr}
-strdisplaywidth({expr} [, {col}]) Number display length of the String {expr}
-strftime({format} [, {time}]) String format time with a specified format
-strgetchar({str}, {index}) Number get char {index} from {str}
-stridx({haystack}, {needle} [, {start}])
- Number index of {needle} in {haystack}
-string({expr}) String String representation of {expr} value
-strlen({expr}) Number length of the String {expr}
-strpart({str}, {start} [, {len} [, {chars}]])
- String {len} bytes/chars of {str} at
- byte {start}
-strptime({format}, {timestring})
- Number Convert {timestring} to unix timestamp
-strridx({haystack}, {needle} [, {start}])
- Number last index of {needle} in {haystack}
-strtrans({expr}) String translate string to make it printable
-strwidth({expr}) Number display cell length of the String {expr}
-submatch({nr} [, {list}]) String or List
- specific match in ":s" or substitute()
-substitute({expr}, {pat}, {sub}, {flags})
- String all {pat} in {expr} replaced with {sub}
-swapinfo({fname}) Dict information about swap file {fname}
-swapname({buf}) String swap file of buffer {buf}
-synID({lnum}, {col}, {trans}) Number syntax ID at {lnum} and {col}
-synIDattr({synID}, {what} [, {mode}])
- String attribute {what} of syntax ID {synID}
-synIDtrans({synID}) Number translated syntax ID of {synID}
-synconcealed({lnum}, {col}) List info about concealing
-synstack({lnum}, {col}) List stack of syntax IDs at {lnum} and {col}
-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}])
- Number number of current window in tab page
-tagfiles() List tags files used
-taglist({expr} [, {filename}]) List list of tags matching {expr}
-tan({expr}) Float tangent of {expr}
-tanh({expr}) Float hyperbolic tangent of {expr}
-tempname() String name for a temporary file
-test_garbagecollect_now() none free memory right now for testing
-timer_info([{id}]) List information about timers
-timer_pause({id}, {pause}) none pause or unpause a timer
-timer_start({time}, {callback} [, {options}])
- Number create a timer
-timer_stop({timer}) none stop a timer
-timer_stopall() none stop all timers
-tolower({expr}) String the String {expr} switched to lowercase
-toupper({expr}) String the String {expr} switched to uppercase
-tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr}
- to chars in {tostr}
-trim({text} [, {mask} [, {dir}]])
- String trim characters in {mask} from {text}
-trunc({expr}) Float truncate Float {expr}
-type({name}) Number type of variable {name}
-undofile({name}) String undo file name for {name}
-undotree() List undo file tree
-uniq({list} [, {func} [, {dict}]])
- List remove adjacent duplicates from a list
-values({dict}) List values in {dict}
-virtcol({expr}) Number screen column of cursor or mark
-virtcol2col({winid}, {lnum}, {col})
- Number byte index of a character on screen
-visualmode([expr]) String last visual mode used
-wait({timeout}, {condition} [, {interval}])
- Number Wait until {condition} is satisfied
-wildmenumode() Number whether 'wildmenu' mode is active
-win_execute({id}, {command} [, {silent}])
- String execute {command} in window {id}
-win_findbuf({bufnr}) List find windows containing {bufnr}
-win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab}
-win_gettype([{nr}]) String type of window {nr}
-win_gotoid({expr}) Number go to |window-ID| {expr}
-win_id2tabwin({expr}) List get tab and window nr from |window-ID|
-win_id2win({expr}) Number get window nr from |window-ID|
-win_move_separator({nr}) Number move window vertical separator
-win_move_statusline({nr}) Number move window status line
-win_screenpos({nr}) List get screen position of window {nr}
-win_splitmove({nr}, {target} [, {options}])
- Number move window {nr} to split of {target}
-winbufnr({nr}) Number buffer number of window {nr}
-wincol() Number window column of the cursor
-windowsversion() String MS-Windows OS version
-winheight({nr}) Number height of window {nr}
-winlayout([{tabnr}]) List layout of windows in tab {tabnr}
-winline() Number window line of the cursor
-winnr([{expr}]) Number number of current window
-winrestcmd() String returns command to restore window sizes
-winrestview({dict}) none restore view of current window
-winsaveview() Dict save view of current window
-winwidth({nr}) Number width of window {nr}
-wordcount() Dict get byte/char/word statistics
-writefile({object}, {fname} [, {flags}])
- Number write |Blob| or |List| of lines to file
-xor({expr}, {expr}) Number bitwise XOR
-
==============================================================================
-2. Details *builtin-function-details*
-
-Not all functions are here, some have been moved to a help file covering the
-specific functionality.
+1. Details *builtin-function-details*
-abs({expr}) *abs()*
+abs({expr}) *abs()*
Return the absolute value of {expr}. When {expr} evaluates to
a |Float| abs() returns a |Float|. When {expr} can be
converted to a |Number| abs() returns a |Number|. Otherwise
abs() gives an error message and returns -1.
- Examples: >
+ Examples: >vim
echo abs(1.456)
-< 1.456 >
+< 1.456 >vim
echo abs(-5.456)
-< 5.456 >
+< 5.456 >vim
echo abs(-4)
< 4
- Can also be used as a |method|: >
- Compute()->abs()
-
-acos({expr}) *acos()*
+acos({expr}) *acos()*
Return the arc cosine of {expr} measured in radians, as a
|Float| in the range of [0, pi].
{expr} must evaluate to a |Float| or a |Number| in the range
[-1, 1].
Returns NaN if {expr} is outside the range [-1, 1]. Returns
0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo acos(0)
-< 1.570796 >
- :echo acos(-0.5)
+ Examples: >vim
+ echo acos(0)
+< 1.570796 >vim
+ echo acos(-0.5)
< 2.094395
- Can also be used as a |method|: >
- Compute()->acos()
-
-add({object}, {expr}) *add()*
+add({object}, {expr}) *add()*
Append the item {expr} to |List| or |Blob| {object}. Returns
- the resulting |List| or |Blob|. Examples: >
- :let alist = add([1, 2, 3], item)
- :call add(mylist, "woodstock")
+ the resulting |List| or |Blob|. Examples: >vim
+ let alist = add([1, 2, 3], item)
+ call add(mylist, "woodstock")
< Note that when {expr} is a |List| it is appended as a single
item. Use |extend()| to concatenate |Lists|.
When {object} is a |Blob| then {expr} must be a number.
Use |insert()| to add an item at another position.
Returns 1 if {object} is not a |List| or a |Blob|.
- Can also be used as a |method|: >
- mylist->add(val1)->add(val2)
-
-and({expr}, {expr}) *and()*
+and({expr}, {expr}) *and()*
Bitwise AND on the two arguments. The arguments are converted
to a number. A List, Dict or Float argument causes an error.
- Example: >
- :let flag = and(bits, 0x80)
-< Can also be used as a |method|: >
- :let flag = bits->and(0x80)
+ Also see `or()` and `xor()`.
+ Example: >vim
+ let flag = and(bits, 0x80)
+<
-api_info() *api_info()*
+api_info() *api_info()*
Returns Dictionary of |api-metadata|.
- View it in a nice human-readable format: >
- :lua print(vim.inspect(vim.fn.api_info()))
+ View it in a nice human-readable format: >vim
+ lua vim.print(vim.fn.api_info())
+<
-append({lnum}, {text}) *append()*
+append({lnum}, {text}) *append()*
When {text} is a |List|: Append each item of the |List| as a
text line below line {lnum} in the current buffer.
Otherwise append {text} as one text line below line {lnum} in
@@ -644,14 +73,13 @@ append({lnum}, {text}) *append()*
{lnum} can be zero to insert a line before the first one.
{lnum} is used like with |getline()|.
Returns 1 for failure ({lnum} out of range or out of memory),
- 0 for success. Example: >
- :let failed = append(line('$'), "# THE END")
- :let failed = append(0, ["Chapter 1", "the beginning"])
-
-< Can also be used as a |method| after a List: >
- mylist->append(lnum)
+ 0 for success. When {text} is an empty list zero is returned,
+ no matter the value of {lnum}. Example: >vim
+ let failed = append(line('$'), "# THE END")
+ let failed = append(0, ["Chapter 1", "the beginning"])
+<
-appendbufline({buf}, {lnum}, {text}) *appendbufline()*
+appendbufline({buf}, {lnum}, {text}) *appendbufline()*
Like |append()| but append the text in buffer {expr}.
This function works only for loaded buffers. First call
@@ -667,13 +95,12 @@ appendbufline({buf}, {lnum}, {text}) *appendbufline()*
On success 0 is returned, on failure 1 is returned.
If {buf} is not a valid buffer or {lnum} is not valid, an
- error message is given. Example: >
- :let failed = appendbufline(13, 0, "# THE START")
-<
- Can also be used as a |method| after a List: >
- mylist->appendbufline(buf, lnum)
+ error message is given. Example: >vim
+ let failed = appendbufline(13, 0, "# THE START")
+< However, when {text} is an empty list then no error is given
+ for an invalid {lnum}, since {lnum} isn't actually used.
-argc([{winid}]) *argc()*
+argc([{winid}]) *argc()*
The result is the number of files in the argument list. See
|arglist|.
If {winid} is not supplied, the argument list of the current
@@ -683,12 +110,11 @@ argc([{winid}]) *argc()*
list is used: either the window number or the window ID.
Returns -1 if the {winid} argument is invalid.
- *argidx()*
-argidx() The result is the current index in the argument list. 0 is
+argidx() *argidx()*
+ The result is the current index in the argument list. 0 is
the first file. argc() - 1 is the last one. See |arglist|.
- *arglistid()*
-arglistid([{winnr} [, {tabnr}]])
+arglistid([{winnr} [, {tabnr}]]) *arglistid()*
Return the argument list ID. This is a number which
identifies the argument list being used. Zero is used for the
global argument list. See |arglist|.
@@ -700,16 +126,15 @@ arglistid([{winnr} [, {tabnr}]])
page.
{winnr} can be the window number or the |window-ID|.
- *argv()*
-argv([{nr} [, {winid}]])
+argv([{nr} [, {winid}]]) *argv()*
The result is the {nr}th file in the argument list. See
- |arglist|. "argv(0)" is the first one. Example: >
- :let i = 0
- :while i < argc()
- : let f = escape(fnameescape(argv(i)), '.')
- : exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>'
- : let i = i + 1
- :endwhile
+ |arglist|. "argv(0)" is the first one. Example: >vim
+ let i = 0
+ while i < argc()
+ let f = escape(fnameescape(argv(i)), '.')
+ exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>'
+ let i = i + 1
+ endwhile
< Without the {nr} argument, or when {nr} is -1, a |List| with
the whole |arglist| is returned.
@@ -720,57 +145,193 @@ argv([{nr} [, {winid}]])
the argument list. Returns an empty List if the {winid}
argument is invalid.
-asin({expr}) *asin()*
+asin({expr}) *asin()*
Return the arc sine of {expr} measured in radians, as a |Float|
in the range of [-pi/2, pi/2].
{expr} must evaluate to a |Float| or a |Number| in the range
[-1, 1].
Returns NaN if {expr} is outside the range [-1, 1]. Returns
0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo asin(0.8)
-< 0.927295 >
- :echo asin(-0.5)
+ Examples: >vim
+ echo asin(0.8)
+< 0.927295 >vim
+ echo asin(-0.5)
< -0.523599
- Can also be used as a |method|: >
- Compute()->asin()
-
-
-assert_ functions are documented here: |assert-functions-details|
-
-
-atan({expr}) *atan()*
+assert_beeps({cmd}) *assert_beeps()*
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce a beep or visual bell.
+ Also see |assert_fails()|, |assert_nobeep()| and
+ |assert-return|.
+
+assert_equal({expected}, {actual} [, {msg}]) *assert_equal()*
+ When {expected} and {actual} are not equal an error message is
+ added to |v:errors| and 1 is returned. Otherwise zero is
+ returned. |assert-return|
+ The error is in the form "Expected {expected} but got
+ {actual}". When {msg} is present it is prefixed to that.
+
+ There is no automatic conversion, the String "4" is different
+ from the Number 4. And the number 4 is different from the
+ Float 4.0. The value of 'ignorecase' is not used here, case
+ always matters.
+ Example: >vim
+ assert_equal('foo', 'bar')
+< Will result in a string to be added to |v:errors|:
+ test.vim line 12: Expected 'foo' but got 'bar' ~
+
+assert_equalfile({fname-one}, {fname-two}) *assert_equalfile()*
+ When the files {fname-one} and {fname-two} do not contain
+ exactly the same text an error message is added to |v:errors|.
+ Also see |assert-return|.
+ When {fname-one} or {fname-two} does not exist the error will
+ mention that.
+
+assert_exception({error} [, {msg}]) *assert_exception()*
+ When v:exception does not contain the string {error} an error
+ message is added to |v:errors|. Also see |assert-return|.
+ This can be used to assert that a command throws an exception.
+ Using the error number, followed by a colon, avoids problems
+ with translations: >vim
+ try
+ commandthatfails
+ call assert_false(1, 'command should have failed')
+ catch
+ call assert_exception('E492:')
+ endtry
+<
+
+ *assert_fails()*
+assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]])
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce an error or when {error} is not found in the
+ error message. Also see |assert-return|.
+
+ When {error} is a string it must be found literally in the
+ first reported error. Most often this will be the error code,
+ including the colon, e.g. "E123:". >vim
+ assert_fails('bad cmd', 'E987:')
+<
+ When {error} is a |List| with one or two strings, these are
+ used as patterns. The first pattern is matched against the
+ first reported error: >vim
+ assert_fails('cmd', ['E987:.*expected bool'])
+< The second pattern, if present, is matched against the last
+ reported error. To only match the last error use an empty
+ string for the first error: >vim
+ assert_fails('cmd', ['', 'E987:'])
+<
+ If {msg} is empty then it is not used. Do this to get the
+ default message when passing the {lnum} argument.
+
+ When {lnum} is present and not negative, and the {error}
+ argument is present and matches, then this is compared with
+ the line number at which the error was reported. That can be
+ the line number in a function or in a script.
+
+ When {context} is present it is used as a pattern and matched
+ against the context (script name or function name) where
+ {lnum} is located in.
+
+ Note that beeping is not considered an error, and some failing
+ commands only beep. Use |assert_beeps()| for those.
+
+assert_false({actual} [, {msg}]) *assert_false()*
+ When {actual} is not false an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ The error is in the form "Expected False but got {actual}".
+ When {msg} is present it is prepended to that.
+ Also see |assert-return|.
+
+ A value is false when it is zero. When {actual} is not a
+ number the assert fails.
+
+assert_inrange({lower}, {upper}, {actual} [, {msg}]) *assert_inrange()*
+ This asserts number and |Float| values. When {actual} is lower
+ than {lower} or higher than {upper} an error message is added
+ to |v:errors|. Also see |assert-return|.
+ The error is in the form "Expected range {lower} - {upper},
+ but got {actual}". When {msg} is present it is prefixed to
+ that.
+
+assert_match({pattern}, {actual} [, {msg}]) *assert_match()*
+ When {pattern} does not match {actual} an error message is
+ added to |v:errors|. Also see |assert-return|.
+ The error is in the form "Pattern {pattern} does not match
+ {actual}". When {msg} is present it is prefixed to that.
+
+ {pattern} is used as with |expr-=~|: The matching is always done
+ like 'magic' was set and 'cpoptions' is empty, no matter what
+ the actual value of 'magic' or 'cpoptions' is.
+
+ {actual} is used as a string, automatic conversion applies.
+ Use "^" and "$" to match with the start and end of the text.
+ Use both to match the whole text.
+
+ Example: >vim
+ assert_match('^f.*o$', 'foobar')
+< Will result in a string to be added to |v:errors|:
+ test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
+
+assert_nobeep({cmd}) *assert_nobeep()*
+ Run {cmd} and add an error message to |v:errors| if it
+ produces a beep or visual bell.
+ Also see |assert_beeps()|.
+
+assert_notequal({expected}, {actual} [, {msg}]) *assert_notequal()*
+ The opposite of `assert_equal()`: add an error message to
+ |v:errors| when {expected} and {actual} are equal.
+ Also see |assert-return|.
+
+assert_notmatch({pattern}, {actual} [, {msg}]) *assert_notmatch()*
+ The opposite of `assert_match()`: add an error message to
+ |v:errors| when {pattern} matches {actual}.
+ Also see |assert-return|.
+
+assert_report({msg}) *assert_report()*
+ Report a test failure directly, using String {msg}.
+ Always returns one.
+
+assert_true({actual} [, {msg}]) *assert_true()*
+ When {actual} is not true an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ Also see |assert-return|.
+ A value is |TRUE| when it is a non-zero number or |v:true|.
+ When {actual} is not a number or |v:true| the assert fails.
+ When {msg} is given it precedes the default message.
+
+atan({expr}) *atan()*
Return the principal value of the arc tangent of {expr}, in
the range [-pi/2, +pi/2] radians, as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo atan(100)
-< 1.560797 >
- :echo atan(-4.01)
+ Examples: >vim
+ echo atan(100)
+< 1.560797 >vim
+ echo atan(-4.01)
< -1.326405
- Can also be used as a |method|: >
- Compute()->atan()
-
-atan2({expr1}, {expr2}) *atan2()*
+atan2({expr1}, {expr2}) *atan2()*
Return the arc tangent of {expr1} / {expr2}, measured in
radians, as a |Float| in the range [-pi, pi].
{expr1} and {expr2} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
|Number|.
- Examples: >
- :echo atan2(-1, 1)
-< -0.785398 >
- :echo atan2(1, -1)
+ Examples: >vim
+ echo atan2(-1, 1)
+< -0.785398 >vim
+ echo atan2(1, -1)
< 2.356194
- Can also be used as a |method|: >
- Compute()->atan2(1)
+blob2list({blob}) *blob2list()*
+ Return a List containing the number value of each byte in Blob
+ {blob}. Examples: >vim
+ blob2list(0z0102.0304) " returns [1, 2, 3, 4]
+ blob2list(0z) " returns []
+< Returns an empty List on error. |list2blob()| does the
+ opposite.
- *browse()*
-browse({save}, {title}, {initdir}, {default})
+browse({save}, {title}, {initdir}, {default}) *browse()*
Put up a file requester. This only works when "has("browse")"
returns |TRUE| (only in some GUI versions).
The input fields are:
@@ -781,8 +342,7 @@ browse({save}, {title}, {initdir}, {default})
An empty string is returned when the "Cancel" button is hit,
something went wrong, or browsing is not possible.
- *browsedir()*
-browsedir({title}, {initdir})
+browsedir({title}, {initdir}) *browsedir()*
Put up a directory requester. This only works when
"has("browse")" returns |TRUE| (only in some GUI versions).
On systems where a directory browser is not supported a file
@@ -794,7 +354,7 @@ browsedir({title}, {initdir})
When the "Cancel" button is hit, something went wrong, or
browsing is not possible, an empty string is returned.
-bufadd({name}) *bufadd()*
+bufadd({name}) *bufadd()*
Add a buffer to the buffer list with name {name} (must be a
String).
If a buffer for file {name} already exists, return that buffer
@@ -802,15 +362,13 @@ bufadd({name}) *bufadd()*
created buffer. When {name} is an empty string then a new
buffer is always created.
The buffer will not have 'buflisted' set and not be loaded
- yet. To add some text to the buffer use this: >
+ yet. To add some text to the buffer use this: >vim
let bufnr = bufadd('someName')
call bufload(bufnr)
call setbufline(bufnr, 1, ['some', 'text'])
< Returns 0 on error.
- Can also be used as a |method|: >
- let bufnr = 'somename'->bufadd()
-bufexists({buf}) *bufexists()*
+bufexists({buf}) *bufexists()*
The result is a Number, which is |TRUE| if a buffer called
{buf} exists.
If the {buf} argument is a number, buffer numbers are used.
@@ -832,39 +390,27 @@ bufexists({buf}) *bufexists()*
Use "bufexists(0)" to test for the existence of an alternate
file name.
- Can also be used as a |method|: >
- let exists = 'somename'->bufexists()
-
-buflisted({buf}) *buflisted()*
+buflisted({buf}) *buflisted()*
The result is a Number, which is |TRUE| if a buffer called
{buf} exists and is listed (has the 'buflisted' option set).
The {buf} argument is used like with |bufexists()|.
- Can also be used as a |method|: >
- let listed = 'somename'->buflisted()
-
-bufload({buf}) *bufload()*
+bufload({buf}) *bufload()*
Ensure the buffer {buf} is loaded. When the buffer name
refers to an existing file then the file is read. Otherwise
the buffer will be empty. If the buffer was already loaded
then there is no change. If the buffer is not related to a
- file the no file is read (e.g., when 'buftype' is "nofile").
+ file then no file is read (e.g., when 'buftype' is "nofile").
If there is an existing swap file for the file of the buffer,
there will be no dialog, the buffer will be loaded anyway.
The {buf} argument is used like with |bufexists()|.
- Can also be used as a |method|: >
- eval 'somename'->bufload()
-
-bufloaded({buf}) *bufloaded()*
+bufloaded({buf}) *bufloaded()*
The result is a Number, which is |TRUE| if a buffer called
{buf} exists and is loaded (shown in a window or hidden).
The {buf} argument is used like with |bufexists()|.
- Can also be used as a |method|: >
- let loaded = 'somename'->bufloaded()
-
-bufname([{buf}]) *bufname()*
+bufname([{buf}]) *bufname()*
The result is the name of a buffer. Mostly as it is displayed
by the `:ls` command, but not using special names such as
"[No Name]".
@@ -885,65 +431,53 @@ bufname([{buf}]) *bufname()*
with a listed buffer, that one is returned. Next unlisted
buffers are searched for.
If the {buf} is a String, but you want to use it as a buffer
- number, force it to be a Number by adding zero to it: >
- :echo bufname("3" + 0)
-< Can also be used as a |method|: >
- echo bufnr->bufname()
-
+ number, force it to be a Number by adding zero to it: >vim
+ echo bufname("3" + 0)
< If the buffer doesn't exist, or doesn't have a name, an empty
- string is returned. >
- bufname("#") alternate buffer name
- bufname(3) name of buffer 3
- bufname("%") name of current buffer
- bufname("file2") name of buffer where "file2" matches.
-<
- *bufnr()*
-bufnr([{buf} [, {create}]])
+ string is returned. >vim
+ echo bufname("#") " alternate buffer name
+ echo bufname(3) " name of buffer 3
+ echo bufname("%") " name of current buffer
+ echo bufname("file2") " name of buffer where "file2" matches.
+<
+
+bufnr([{buf} [, {create}]]) *bufnr()*
The result is the number of a buffer, as it is displayed by
the `:ls` command. For the use of {buf}, see |bufname()|
above.
If the buffer doesn't exist, -1 is returned. Or, if the
{create} argument is present and TRUE, a new, unlisted,
buffer is created and its number is returned.
- bufnr("$") is the last buffer: >
- :let last_buffer = bufnr("$")
+ bufnr("$") is the last buffer: >vim
+ let last_buffer = bufnr("$")
< The result is a Number, which is the highest buffer number
of existing buffers. Note that not all buffers with a smaller
number necessarily exist, because ":bwipeout" may have removed
them. Use bufexists() to test for the existence of a buffer.
- Can also be used as a |method|: >
- echo bufref->bufnr()
-
-bufwinid({buf}) *bufwinid()*
+bufwinid({buf}) *bufwinid()*
The result is a Number, which is the |window-ID| of the first
window associated with buffer {buf}. For the use of {buf},
see |bufname()| above. If buffer {buf} doesn't exist or
- there is no such window, -1 is returned. Example: >
+ there is no such window, -1 is returned. Example: >vim
- echo "A window containing buffer 1 is " .. (bufwinid(1))
+ echo "A window containing buffer 1 is " .. (bufwinid(1))
<
Only deals with the current tab page. See |win_findbuf()| for
finding more.
- Can also be used as a |method|: >
- FindBuffer()->bufwinid()
-
-bufwinnr({buf}) *bufwinnr()*
+bufwinnr({buf}) *bufwinnr()*
Like |bufwinid()| but return the window number instead of the
|window-ID|.
If buffer {buf} doesn't exist or there is no such window, -1
- is returned. Example: >
+ is returned. Example: >vim
- echo "A window containing buffer 1 is " .. (bufwinnr(1))
+ echo "A window containing buffer 1 is " .. (bufwinnr(1))
< The number can be used with |CTRL-W_w| and ":wincmd w"
|:wincmd|.
- Can also be used as a |method|: >
- FindBuffer()->bufwinnr()
-
-byte2line({byte}) *byte2line()*
+byte2line({byte}) *byte2line()*
Return the line number that contains the character at byte
count {byte} in the current buffer. This includes the
end-of-line character, depending on the 'fileformat' option
@@ -953,10 +487,7 @@ byte2line({byte}) *byte2line()*
Returns -1 if the {byte} value is invalid.
- Can also be used as a |method|: >
- GetOffset()->byte2line()
-
-byteidx({expr}, {nr}) *byteidx()*
+byteidx({expr}, {nr} [, {utf16}]) *byteidx()*
Return byte index of the {nr}th character in the String
{expr}. Use zero for the first character, it then returns
zero.
@@ -966,10 +497,17 @@ byteidx({expr}, {nr}) *byteidx()*
length is added to the preceding base character. See
|byteidxcomp()| below for counting composing characters
separately.
- Example : >
+ When {utf16} is present and TRUE, {nr} is used as the UTF-16
+ index in the String {expr} instead of as the character index.
+ The UTF-16 index is the index in the string when it is encoded
+ with 16-bit words. If the specified UTF-16 index is in the
+ middle of a character (e.g. in a 4-byte character), then the
+ byte index of the first byte in the character is returned.
+ Refer to |string-offset-encoding| for more information.
+ Example : >vim
echo matchstr(str, ".", byteidx(str, 3))
< will display the fourth character. Another way to do the
- same: >
+ same: >vim
let s = strpart(str, byteidx(str, 3))
echo strpart(s, 0, byteidx(s, 1))
< Also see |strgetchar()| and |strcharpart()|.
@@ -977,13 +515,17 @@ byteidx({expr}, {nr}) *byteidx()*
If there are less than {nr} characters -1 is returned.
If there are exactly {nr} characters the length of the string
in bytes is returned.
+ See |charidx()| and |utf16idx()| for getting the character and
+ UTF-16 index respectively from the byte index.
+ Examples: >vim
+ echo byteidx('a😊😊', 2) " returns 5
+ echo byteidx('a😊😊', 2, 1) " returns 1
+ echo byteidx('a😊😊', 3, 1) " returns 5
+<
- Can also be used as a |method|: >
- GetName()->byteidx(idx)
-
-byteidxcomp({expr}, {nr}) *byteidxcomp()*
+byteidxcomp({expr}, {nr} [, {utf16}]) *byteidxcomp()*
Like byteidx(), except that a composing character is counted
- as a separate character. Example: >
+ as a separate character. Example: >vim
let s = 'e' .. nr2char(0x301)
echo byteidx(s, 1)
echo byteidxcomp(s, 1)
@@ -992,10 +534,7 @@ byteidxcomp({expr}, {nr}) *byteidxcomp()*
character is 3 bytes), the second echo results in 1 ('e' is
one byte).
- Can also be used as a |method|: >
- GetName()->byteidxcomp(idx)
-
-call({func}, {arglist} [, {dict}]) *call()* *E699*
+call({func}, {arglist} [, {dict}]) *call()* *E699*
Call function {func} with the items in |List| {arglist} as
arguments.
{func} can either be a |Funcref| or the name of a function.
@@ -1004,36 +543,21 @@ call({func}, {arglist} [, {dict}]) *call()* *E699*
{dict} is for functions with the "dict" attribute. It will be
used to set the local variable "self". |Dictionary-function|
- Can also be used as a |method|: >
- GetFunc()->call([arg, arg], dict)
-
-ceil({expr}) *ceil()*
+ceil({expr}) *ceil()*
Return the smallest integral value greater than or equal to
{expr} as a |Float| (round up).
{expr} must evaluate to a |Float| or a |Number|.
- Examples: >
+ Examples: >vim
echo ceil(1.456)
-< 2.0 >
+< 2.0 >vim
echo ceil(-5.456)
-< -5.0 >
+< -5.0 >vim
echo ceil(4.0)
< 4.0
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Can also be used as a |method|: >
- Compute()->ceil()
-
-changenr() *changenr()*
- Return the number of the most recent change. This is the same
- number as what is displayed with |:undolist| and can be used
- with the |:undo| command.
- When a change was made it is the number of that change. After
- redo it is the number of the redone change. After undo it is
- one less than the number of the undone change.
- Returns 0 if the undo list is empty.
-
-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
@@ -1043,7 +567,16 @@ chanclose({id} [, {stream}]) *chanclose()*
For a socket, there is only one stream, and {stream} should be
omitted.
-chansend({id}, {data}) *chansend()*
+changenr() *changenr()*
+ Return the number of the most recent change. This is the same
+ number as what is displayed with |:undolist| and can be used
+ with the |:undo| command.
+ When a change was made it is the number of that change. After
+ redo it is the number of the redone change. After undo it is
+ one less than the number of the undone change.
+ Returns 0 if the undo list is empty.
+
+chansend({id}, {data}) *chansend()*
Send data to channel {id}. For a job, it writes it to the
stdin of the process. For the stdio channel |channel-stdio|,
it writes to Nvim's stdout. Returns the number of bytes
@@ -1053,23 +586,22 @@ chansend({id}, {data}) *chansend()*
{data} may be a string, string convertible, |Blob|, or a list.
If {data} is a list, the items will be joined by newlines; any
newlines in an item will be sent as NUL. To send a final
- newline, include a final empty string. Example: >
- :call chansend(id, ["abc", "123\n456", ""])
-< will send "abc<NL>123<NUL>456<NL>".
+ newline, include a final empty string. Example: >vim
+ call chansend(id, ["abc", "123\n456", ""])
+< will send "abc<NL>123<NUL>456<NL>".
chansend() writes raw data, not RPC messages. If the channel
was created with `"rpc":v:true` then the channel expects RPC
messages, use |rpcnotify()| and |rpcrequest()| instead.
-
-char2nr({string} [, {utf8}]) *char2nr()*
+char2nr({string} [, {utf8}]) *char2nr()*
Return Number value of the first char in {string}.
- Examples: >
- char2nr(" ") returns 32
- char2nr("ABC") returns 65
- char2nr("á") returns 225
- char2nr("á"[0]) returns 195
- char2nr("\<M-x>") returns 128
+ Examples: >vim
+ echo char2nr(" ") " returns 32
+ echo char2nr("ABC") " returns 65
+ echo char2nr("á") " returns 225
+ echo char2nr("á"[0]) " returns 195
+ echo char2nr("\<M-x>") " returns 128
< Non-ASCII characters are always treated as UTF-8 characters.
{utf8} is ignored, it exists only for backwards-compatibility.
A combining character is a separate character.
@@ -1077,10 +609,7 @@ char2nr({string} [, {utf8}]) *char2nr()*
Returns 0 if {string} is not a |String|.
- Can also be used as a |method|: >
- GetChar()->char2nr()
-
-charclass({string}) *charclass()*
+charclass({string}) *charclass()*
Return the character class of the first character in {string}.
The character class is one of:
0 blank
@@ -1091,46 +620,50 @@ charclass({string}) *charclass()*
The class is used in patterns and word motions.
Returns 0 if {string} is not a |String|.
-
-charcol({expr} [, {winid}]) *charcol()*
+charcol({expr} [, {winid}]) *charcol()*
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
+ With the cursor on '세' in line 5 with text "여보세요": >vim
+ echo charcol('.') " returns 3
+ echo col('.') " returns 7
-< Can also be used as a |method|: >
- GetPos()->col()
-<
- *charidx()*
-charidx({string}, {idx} [, {countcc}])
+charidx({string}, {idx} [, {countcc} [, {utf16}]]) *charidx()*
Return the character index of the byte at {idx} in {string}.
The index of the first character is zero.
If there are no multibyte characters the returned value is
equal to {idx}.
+
When {countcc} is omitted or |FALSE|, then composing characters
- are not counted separately, their byte length is
- added to the preceding base character.
+ are not counted separately, their byte length is added to the
+ preceding base character.
When {countcc} is |TRUE|, then composing characters are
counted as separate characters.
- Returns -1 if the arguments are invalid or if {idx} is greater
- than the index of the last byte in {string}. An error is
- given if the first argument is not a string, the second
- argument is not a number or when the third argument is present
- and is not zero or one.
+
+ When {utf16} is present and TRUE, {idx} is used as the UTF-16
+ index in the String {expr} instead of as the byte index.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes. If there are exactly {idx} bytes the length
+ of the string in characters is returned.
+
+ An error is given and -1 is returned if the first argument is
+ not a string, the second argument is not a number or when the
+ third argument is present and is not zero or one.
+
See |byteidx()| and |byteidxcomp()| for getting the byte index
- from the character index.
- Examples: >
- echo charidx('áb́ć', 3) returns 1
- echo charidx('áb́ć', 6, 1) returns 4
- echo charidx('áb́ć', 16) returns -1
+ from the character index and |utf16idx()| for getting the
+ UTF-16 index from the character index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo charidx('áb́ć', 3) " returns 1
+ echo charidx('áb́ć', 6, 1) " returns 4
+ echo charidx('áb́ć', 16) " returns -1
+ echo charidx('a😊😊', 4, 0, 1) " returns 2
<
- Can also be used as a |method|: >
- GetName()->charidx(idx)
-chdir({dir}) *chdir()*
+chdir({dir}) *chdir()*
Change the current working directory to {dir}. The scope of
the directory change depends on the directory of the current
window:
@@ -1145,17 +678,14 @@ chdir({dir}) *chdir()*
this to another chdir() to restore the directory.
On failure, returns an empty string.
- Example: >
+ Example: >vim
let save_dir = chdir(newdir)
if save_dir != ""
" ... do some work
call chdir(save_dir)
endif
-< Can also be used as a |method|: >
- GetDir()->chdir()
-<
-cindent({lnum}) *cindent()*
+cindent({lnum}) *cindent()*
Get the amount of indent for line {lnum} according the C
indenting rules, as with 'cindent'.
The indent is counted in spaces, the value of 'tabstop' is
@@ -1163,19 +693,13 @@ cindent({lnum}) *cindent()*
When {lnum} is invalid -1 is returned.
See |C-indenting|.
- Can also be used as a |method|: >
- GetLnum()->cindent()
-
-clearmatches([{win}]) *clearmatches()*
+clearmatches([{win}]) *clearmatches()*
Clears all matches previously defined for the current window
by |matchadd()| and the |:match| commands.
If {win} is specified, use the window with this number or
window ID instead of the current window.
- Can also be used as a |method|: >
- GetWin()->clearmatches()
-<
-col({expr} [, {winid}) *col()*
+col({expr} [, {winid}]) *col()*
The result is a Number, which is the byte index of the column
position given with {expr}. The accepted positions are:
. the cursor position
@@ -1198,11 +722,11 @@ col({expr} [, {winid}) *col()*
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
- col("$") length of cursor line plus one
- col("'t") column of mark t
- col("'" .. markname) column of mark markname
+ Examples: >vim
+ echo col(".") " column of cursor
+ echo col("$") " length of cursor line plus one
+ echo col("'t") " column of mark t
+ echo col("'" .. markname) " column of mark markname
< The first column is 1. Returns 0 if {expr} is invalid or when
the window with ID {winid} is not found.
For an uppercase mark the column may actually be in another
@@ -1210,14 +734,10 @@ col({expr} [, {winid}) *col()*
For the cursor position, when 'virtualedit' is active, the
column is one higher if the cursor is after the end of the
line. Also, when using a <Cmd> mapping the cursor isn't
- moved, this can be used to obtain the column in Insert mode: >
- :imap <F2> <Cmd>echo col(".").."\n"<CR>
-
-< Can also be used as a |method|: >
- GetPos()->col()
-<
+ moved, this can be used to obtain the column in Insert mode: >vim
+ imap <F2> <Cmd>echo col(".").."\n"<CR>
-complete({startcol}, {matches}) *complete()* *E785*
+complete({startcol}, {matches}) *complete()* *E785*
Set the matches for Insert mode completion.
Can only be used in Insert mode. You need to use a mapping
with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O
@@ -1235,23 +755,19 @@ complete({startcol}, {matches}) *complete()* *E785*
The match can be selected with CTRL-N and CTRL-P as usual with
Insert mode completion. The popup menu will appear if
specified, see |ins-completion-menu|.
- Example: >
- inoremap <F5> <C-R>=ListMonths()<CR>
-
- func! ListMonths()
- call complete(col('.'), ['January', 'February', 'March',
- \ 'April', 'May', 'June', 'July', 'August', 'September',
- \ 'October', 'November', 'December'])
- return ''
- endfunc
+ Example: >vim
+ inoremap <F5> <C-R>=ListMonths()<CR>
+
+ func ListMonths()
+ call complete(col('.'), ['January', 'February', 'March',
+ \ 'April', 'May', 'June', 'July', 'August', 'September',
+ \ 'October', 'November', 'December'])
+ return ''
+ endfunc
< This isn't very useful, but it shows how it works. Note that
an empty string is returned to avoid a zero being inserted.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetMatches()->complete(col('.'))
-
-complete_add({expr}) *complete_add()*
+complete_add({expr}) *complete_add()*
Add {expr} to the list of matches. Only to be used by the
function specified with the 'completefunc' option.
Returns 0 for failure (empty string or out of memory),
@@ -1260,10 +776,7 @@ complete_add({expr}) *complete_add()*
See |complete-functions| for an explanation of {expr}. It is
the same as one item in the list that 'omnifunc' would return.
- Can also be used as a |method|: >
- GetMoreMatches()->complete_add()
-
-complete_check() *complete_check()*
+complete_check() *complete_check()*
Check for a key typed while looking for completion matches.
This is to be used when looking for matches takes some time.
Returns |TRUE| when searching for matches is to be aborted,
@@ -1271,8 +784,7 @@ complete_check() *complete_check()*
Only to be used by the function specified with the
'completefunc' option.
-
-complete_info([{what}]) *complete_info()*
+complete_info([{what}]) *complete_info()*
Returns a |Dictionary| with information about Insert mode
completion. See |ins-completion|.
The items are:
@@ -1322,7 +834,7 @@ complete_info([{what}]) *complete_info()*
Returns an empty |Dictionary| on error.
- Examples: >
+ Examples: >vim
" Get all items
call complete_info()
" Get only 'mode'
@@ -1330,11 +842,7 @@ complete_info([{what}]) *complete_info()*
" Get only 'mode' and 'pum_visible'
call complete_info(['mode', 'pum_visible'])
-< Can also be used as a |method|: >
- GetItems()->complete_info()
-<
- *confirm()*
-confirm({msg} [, {choices} [, {default} [, {type}]]])
+confirm({msg} [, {choices} [, {default} [, {type}]]]) *confirm()*
confirm() offers the user a dialog, from which a choice can be
made. It returns the number of the choice. For the first
choice this is 1.
@@ -1346,11 +854,11 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
some systems the string is wrapped when it doesn't fit.
{choices} is a String, with the individual choices separated
- by '\n', e.g. >
+ by '\n', e.g. >vim
confirm("Save changes?", "&Yes\n&No\n&Cancel")
< The letter after the '&' is the shortcut key for that choice.
Thus you can type 'c' to select "Cancel". The shortcut does
- not need to be the first letter: >
+ not need to be the first letter: >vim
confirm("file has been modified", "&Save\nSave &All")
< For the console, the first letter of each choice is used as
the default shortcut key. Case is ignored.
@@ -1369,7 +877,7 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
If the user aborts the dialog by pressing <Esc>, CTRL-C,
or another valid interrupt key, confirm() returns 0.
- An example: >
+ An example: >vim
let choice = confirm("What do you want?",
\ "&Apples\n&Oranges\n&Bananas", 2)
if choice == 0
@@ -1386,11 +894,8 @@ confirm({msg} [, {choices} [, {default} [, {type}]]])
don't fit, a vertical layout is used anyway. For some systems
the horizontal layout is always used.
- Can also be used as a |method|in: >
- BuildMessage()->confirm("&Yes\n&No")
-<
- *copy()*
-copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't
+copy({expr}) *copy()*
+ Make a copy of {expr}. For Numbers and Strings this isn't
different from using {expr} directly.
When {expr} is a |List| a shallow copy is created. This means
that the original |List| can be changed without changing the
@@ -1398,37 +903,29 @@ copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't
changing an item changes the contents of both |Lists|.
A |Dictionary| is copied in a similar way as a |List|.
Also see |deepcopy()|.
- Can also be used as a |method|: >
- mylist->copy()
-cos({expr}) *cos()*
+cos({expr}) *cos()*
Return the cosine of {expr}, measured in radians, as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo cos(100)
-< 0.862319 >
- :echo cos(-4.01)
+ Examples: >vim
+ echo cos(100)
+< 0.862319 >vim
+ echo cos(-4.01)
< -0.646043
- Can also be used as a |method|: >
- Compute()->cos()
-
-cosh({expr}) *cosh()*
+cosh({expr}) *cosh()*
Return the hyperbolic cosine of {expr} as a |Float| in the range
[1, inf].
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo cosh(0.5)
-< 1.127626 >
- :echo cosh(-0.5)
+ Examples: >vim
+ echo cosh(0.5)
+< 1.127626 >vim
+ echo cosh(-0.5)
< -1.127626
- Can also be used as a |method|: >
- Compute()->cosh()
-
-count({comp}, {expr} [, {ic} [, {start}]]) *count()*
+count({comp}, {expr} [, {ic} [, {start}]]) *count()* *E706*
Return the number of times an item with value {expr} appears
in |String|, |List| or |Dictionary| {comp}.
@@ -1441,36 +938,33 @@ count({comp}, {expr} [, {ic} [, {start}]]) *count()*
occurrences of {expr} is returned. Zero is returned when
{expr} is an empty string.
- Can also be used as a |method|: >
- mylist->count(val)
-<
-ctxget([{index}]) *ctxget()*
+ctxget([{index}]) *ctxget()*
Returns a |Dictionary| representing the |context| at {index}
from the top of the |context-stack| (see |context-dict|).
If {index} is not given, it is assumed to be 0 (i.e.: top).
-ctxpop() *ctxpop()*
+ctxpop() *ctxpop()*
Pops and restores the |context| at the top of the
|context-stack|.
-ctxpush([{types}]) *ctxpush()*
+ctxpush([{types}]) *ctxpush()*
Pushes the current editor state (|context|) on the
|context-stack|.
If {types} is given and is a |List| of |String|s, it specifies
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|).
If {index} is not given, it is assumed to be 0 (i.e.: top).
-ctxsize() *ctxsize()*
+ctxsize() *ctxsize()*
Returns the size of the |context-stack|.
-cursor({lnum}, {col} [, {off}]) *cursor()*
-cursor({list})
+cursor({lnum}, {col} [, {off}])
+cursor({list}) *cursor()*
Positions the cursor at the column (byte count) {col} in the
line {lnum}. The first column is one.
@@ -1482,7 +976,7 @@ 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
+ To position the cursor using {col} as the character count, use
|setcursorcharpos()|.
Does not change the jumplist.
@@ -1502,10 +996,7 @@ cursor({list})
position within a <Tab> or after the last character.
Returns 0 when the position could be set, -1 otherwise.
- Can also be used as a |method|: >
- GetCursorPos()->cursor()
-
-debugbreak({pid}) *debugbreak()*
+debugbreak({pid}) *debugbreak()*
Specifically used to interrupt a program being debugged. It
will cause process {pid} to get a SIGTRAP. Behavior for other
processes is undefined. See |terminal-debug|.
@@ -1514,10 +1005,7 @@ debugbreak({pid}) *debugbreak()*
Returns |TRUE| if successfully interrupted the program.
Otherwise returns |FALSE|.
- Can also be used as a |method|: >
- GetPid()->debugbreak()
-
-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
@@ -1537,10 +1025,7 @@ deepcopy({expr} [, {noref}]) *deepcopy()* *E698*
{noref} set to 1 will fail.
Also see |copy()|.
- Can also be used as a |method|: >
- GetObject()->deepcopy()
-
-delete({fname} [, {flags}]) *delete()*
+delete({fname} [, {flags}]) *delete()*
Without {flags} or with {flags} empty: Deletes the file by the
name {fname}.
@@ -1559,10 +1044,7 @@ delete({fname} [, {flags}]) *delete()*
operation was successful and -1/true when the deletion failed
or partly failed.
- 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.
@@ -1576,10 +1058,7 @@ deletebufline({buf}, {first} [, {last}]) *deletebufline()*
when using |line()| this refers to the current buffer. Use "$"
to refer to the last line in buffer {buf}.
- Can also be used as a |method|: >
- GetBuffer()->deletebufline(1)
-<
-dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()*
+dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()*
Adds a watcher to a dictionary. A dictionary watcher is
identified by three components:
@@ -1590,7 +1069,7 @@ dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()*
After this is called, every change on {dict} and on keys
matching {pattern} will result in {callback} being invoked.
- For example, to watch all global variables: >
+ For example, to watch all global variables: >vim
silent! call dictwatcherdel(g:, '*', 'OnDictChanged')
function! OnDictChanged(d,k,z)
echomsg string(a:k) string(a:z)
@@ -1619,13 +1098,13 @@ dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()*
This function can be used by plugins to implement options with
validation and parsing logic.
-dictwatcherdel({dict}, {pattern}, {callback}) *dictwatcherdel()*
+dictwatcherdel({dict}, {pattern}, {callback}) *dictwatcherdel()*
Removes a watcher added with |dictwatcheradd()|. All three
arguments must match the ones passed to |dictwatcheradd()| in
order for the watcher to be successfully deleted.
- *did_filetype()*
-did_filetype() Returns |TRUE| when autocommands are being executed and the
+did_filetype() *did_filetype()*
+ Returns |TRUE| when autocommands are being executed and the
FileType event has been triggered at least once. Can be used
to avoid triggering the FileType event again in the scripts
that detect the file type. |FileType|
@@ -1636,7 +1115,7 @@ did_filetype() Returns |TRUE| when autocommands are being executed and the
editing another buffer to set 'filetype' and load a syntax
file.
-diff_filler({lnum}) *diff_filler()*
+diff_filler({lnum}) *diff_filler()*
Returns the number of filler lines above line {lnum}.
These are the lines that were inserted at this point in
another diff'ed window. These filler lines are shown in the
@@ -1645,10 +1124,7 @@ diff_filler({lnum}) *diff_filler()*
line, "'m" mark m, etc.
Returns 0 if the current window is not in diff mode.
- Can also be used as a |method|: >
- GetLnum()->diff_filler()
-
-diff_hlID({lnum}, {col}) *diff_hlID()*
+diff_hlID({lnum}, {col}) *diff_hlID()*
Returns the highlight ID for diff mode at line {lnum} column
{col} (byte index). When the current line does not have a
diff change zero is returned.
@@ -1659,11 +1135,7 @@ diff_hlID({lnum}, {col}) *diff_hlID()*
The highlight ID can be used with |synIDattr()| to obtain
syntax information about the highlighting.
- Can also be used as a |method|: >
- GetLnum()->diff_hlID(col)
-<
-
-digraph_get({chars}) *digraph_get()* *E1214*
+digraph_get({chars}) *digraph_get()* *E1214*
Return the digraph of {chars}. This should be a string with
exactly two characters. If {chars} are not just two
characters, or the digraph of {chars} does not exist, an error
@@ -1671,37 +1143,31 @@ digraph_get({chars}) *digraph_get()* *E1214*
Also see |digraph_getlist()|.
- Examples: >
+ Examples: >vim
" Get a built-in digraph
- :echo digraph_get('00') " Returns '∞'
+ echo digraph_get('00') " Returns '∞'
" Get a user-defined digraph
- :call digraph_set('aa', 'あ')
- :echo digraph_get('aa') " Returns 'あ'
-<
- Can also be used as a |method|: >
- GetChars()->digraph_get()
+ call digraph_set('aa', 'あ')
+ echo digraph_get('aa') " Returns 'あ'
<
-digraph_getlist([{listall}]) *digraph_getlist()*
+digraph_getlist([{listall}]) *digraph_getlist()*
Return a list of digraphs. If the {listall} argument is given
and it is TRUE, return all digraphs, including the default
digraphs. Otherwise, return only user-defined digraphs.
Also see |digraph_get()|.
- Examples: >
+ Examples: >vim
" Get user-defined digraphs
- :echo digraph_getlist()
+ echo digraph_getlist()
" Get all the digraphs, including default digraphs
- :echo digraph_getlist(1)
-<
- Can also be used as a |method|: >
- GetNumber()->digraph_getlist()
+ echo digraph_getlist(1)
<
-digraph_set({chars}, {digraph}) *digraph_set()*
+digraph_set({chars}, {digraph}) *digraph_set()*
Add digraph {chars} to the list. {chars} must be a string
with two characters. {digraph} is a string with one UTF-8
encoded character. *E1215*
@@ -1715,33 +1181,33 @@ digraph_set({chars}, {digraph}) *digraph_set()*
If you want to define multiple digraphs at once, you can use
|digraph_setlist()|.
- Example: >
+ Example: >vim
call digraph_set(' ', 'あ')
<
- Can be used as a |method|: >
+ Can be used as a |method|: >vim
GetString()->digraph_set('あ')
<
-digraph_setlist({digraphlist}) *digraph_setlist()*
+digraph_setlist({digraphlist}) *digraph_setlist()*
Similar to |digraph_set()| but this function can add multiple
digraphs at once. {digraphlist} is a list composed of lists,
where each list contains two strings with {chars} and
{digraph} as in |digraph_set()|. *E1216*
- Example: >
+ Example: >vim
call digraph_setlist([['aa', 'あ'], ['ii', 'い']])
<
- It is similar to the following: >
+ It is similar to the following: >vim
for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']]
call digraph_set(chars, digraph)
endfor
< Except that the function returns after the first error,
following digraphs will not be added.
- Can be used as a |method|: >
+ Can be used as a |method|: >vim
GetList()->digraph_setlist()
<
-empty({expr}) *empty()*
+empty({expr}) *empty()*
Return the Number 1 if {expr} is empty, zero otherwise.
- A |List| or |Dictionary| is empty when it does not have any
items.
@@ -1750,45 +1216,37 @@ empty({expr}) *empty()*
- |v:false| and |v:null| are empty, |v:true| is not.
- A |Blob| is empty when its length is zero.
- Can also be used as a |method|: >
- mylist->empty()
-
-environ() *environ()*
+environ() *environ()*
Return all of environment variables as dictionary. You can
- check if an environment variable exists like this: >
- :echo has_key(environ(), 'HOME')
+ check if an environment variable exists like this: >vim
+ echo has_key(environ(), 'HOME')
< Note that the variable name may be CamelCase; to ignore case
- use this: >
- :echo index(keys(environ()), 'HOME', 0, 1) != -1
+ use this: >vim
+ echo index(keys(environ()), 'HOME', 0, 1) != -1
+<
-escape({string}, {chars}) *escape()*
+escape({string}, {chars}) *escape()*
Escape the characters in {chars} that occur in {string} with a
- backslash. Example: >
- :echo escape('c:\program files\vim', ' \')
+ backslash. Example: >vim
+ echo escape('c:\program files\vim', ' \')
< results in: >
c:\\program\ files\\vim
< Also see |shellescape()| and |fnameescape()|.
- Can also be used as a |method|: >
- GetText()->escape(' \')
-<
- *eval()*
-eval({string}) Evaluate {string} and return the result. Especially useful to
+eval({string}) *eval()*
+ Evaluate {string} and return the result. Especially useful to
turn the result of |string()| back into the original value.
This works for Numbers, Floats, Strings, Blobs and composites
of them. Also works for |Funcref|s that refer to existing
functions.
- Can also be used as a |method|: >
- argv->join()->eval()
-
-eventhandler() *eventhandler()*
+eventhandler() *eventhandler()*
Returns 1 when inside an event handler. That is that Vim got
interrupted while waiting for the user to type a character,
e.g., when dropping a file on Vim. This means interactive
commands cannot be used. Otherwise zero is returned.
-executable({expr}) *executable()*
+executable({expr}) *executable()*
This function checks if an executable with the name {expr}
exists. {expr} must be the name of the program without any
arguments.
@@ -1811,16 +1269,14 @@ executable({expr}) *executable()*
-1 not implemented on this system
|exepath()| can be used to get the full path of an executable.
- Can also be used as a |method|: >
- GetCommand()->executable()
-
-execute({command} [, {silent}]) *execute()*
+execute({command} [, {silent}]) *execute()*
Execute {command} and capture its output.
If {command} is a |String|, returns {command} output.
If {command} is a |List|, returns concatenated outputs.
- Examples: >
+ Line continuations in {command} are not recognized.
+ Examples: >vim
echo execute('echon "foo"')
-< foo >
+< foo >vim
echo execute(['echon "foo"', 'echon "bar"'])
< foobar
@@ -1831,7 +1287,7 @@ execute({command} [, {silent}]) *execute()*
The default is "silent". Note that with "silent!", unlike
`:redir`, error messages are dropped.
- To get a list of lines use |split()| on the result: >
+ To get a list of lines use `split()` on the result: >vim
execute('args')->split("\n")
< This function is not available in the |sandbox|.
@@ -1841,20 +1297,14 @@ execute({command} [, {silent}]) *execute()*
To execute a command in another window than the current one
use `win_execute()`.
- Can also be used as a |method|: >
- GetCommand()->execute()
-
-exepath({expr}) *exepath()*
+exepath({expr}) *exepath()*
Returns the full path of {expr} if it is an executable and
given as a (partial or full) path or is found in $PATH.
Returns empty string otherwise.
If {expr} starts with "./" the |current-directory| is used.
- Can also be used as a |method|: >
- GetCommand()->exepath()
-<
- *exists()*
-exists({expr}) The result is a Number, which is |TRUE| if {expr} is
+exists({expr}) *exists()*
+ The result is a Number, which is |TRUE| if {expr} is
defined, zero otherwise.
For checking for a supported feature use |has()|.
@@ -1867,11 +1317,11 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
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]")
+ expression. E.g.: >vim
+ let l = [1, 2, 3]
+ echo exists("l[5]")
+< 0 >vim
+ echo exists("l[xx]")
< E121: Undefined variable: xx
0
&option-name Vim option (only checks if it exists,
@@ -1880,7 +1330,7 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
$ENVNAME environment variable (could also be
done by comparing with an empty
string)
- *funcname built-in function (see |functions|)
+ `*funcname` built-in function (see |functions|)
or user defined function (see
|user-function|). Also works for a
variable that is a Funcref.
@@ -1911,54 +1361,48 @@ exists({expr}) The result is a Number, which is |TRUE| if {expr} is
##event autocommand for this event is
supported.
- Examples: >
- exists("&mouse")
- exists("$HOSTNAME")
- exists("*strftime")
- exists("*s:MyFunc")
- exists("*MyFunc")
- exists("bufcount")
- exists(":Make")
- exists("#CursorHold")
- exists("#BufReadPre#*.gz")
- exists("#filetypeindent")
- exists("#filetypeindent#FileType")
- exists("#filetypeindent#FileType#*")
- exists("##ColorScheme")
+ Examples: >vim
+ echo exists("&mouse")
+ echo exists("$HOSTNAME")
+ echo exists("*strftime")
+ echo exists("*s:MyFunc")
+ echo exists("*MyFunc")
+ echo exists("bufcount")
+ echo exists(":Make")
+ echo exists("#CursorHold")
+ echo exists("#BufReadPre#*.gz")
+ echo exists("#filetypeindent")
+ echo exists("#filetypeindent#FileType")
+ echo exists("#filetypeindent#FileType#*")
+ echo exists("##ColorScheme")
< There must be no space between the symbol (&/$/*/#) and the
name.
There must be no extra characters after the name, although in
a few cases this is ignored. That may become stricter in the
future, thus don't count on it!
- Working example: >
- exists(":make")
-< NOT working example: >
- exists(":make install")
+ Working example: >vim
+ echo exists(":make")
+< NOT working example: >vim
+ echo exists(":make install")
< Note that the argument must be a string, not the name of the
- variable itself. For example: >
- exists(bufcount)
+ variable itself. For example: >vim
+ echo exists(bufcount)
< This doesn't check for existence of the "bufcount" variable,
but gets the value of "bufcount", and checks if that exists.
- Can also be used as a |method|: >
- Varname()->exists()
-
-exp({expr}) *exp()*
+exp({expr}) *exp()*
Return the exponential of {expr} as a |Float| in the range
[0, inf].
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo exp(2)
-< 7.389056 >
- :echo exp(-1)
+ Examples: >vim
+ echo exp(2)
+< 7.389056 >vim
+ echo exp(-1)
< 0.367879
- Can also be used as a |method|: >
- Compute()->exp()
-
-expand({string} [, {nosuf} [, {list}]]) *expand()*
+expand({string} [, {nosuf} [, {list}]]) *expand()*
Expand wildcards and the following special keywords in
{string}. 'wildignorecase' applies.
@@ -2003,18 +1447,18 @@ expand({string} [, {nosuf} [, {list}]]) *expand()*
:r root (one extension removed)
:e extension only
- Example: >
- :let &tags = expand("%:p:h") .. "/tags"
+ Example: >vim
+ let &tags = expand("%:p:h") .. "/tags"
< Note that when expanding a string that starts with '%', '#' or
- '<', any following text is ignored. This does NOT work: >
- :let doesntwork = expand("%:h.bak")
-< Use this: >
- :let doeswork = expand("%:h") .. ".bak"
+ '<', any following text is ignored. This does NOT work: >vim
+ let doesntwork = expand("%:h.bak")
+< Use this: >vim
+ let doeswork = expand("%:h") .. ".bak"
< Also note that expanding "<cfile>" and others only returns the
referenced file name without further expansion. If "<cfile>"
is "~/.cshrc", you need to do another expand() to have the
- "~/" expanded into the path of the home directory: >
- :echo expand(expand("<cfile>"))
+ "~/" expanded into the path of the home directory: >vim
+ echo expand(expand("<cfile>"))
<
There cannot be white space between the variables and the
following modifier. The |fnamemodify()| function can be used
@@ -2034,8 +1478,8 @@ expand({string} [, {nosuf} [, {list}]]) *expand()*
{nosuf} argument is given and it is |TRUE|.
Names for non-existing files are included. The "**" item can
be used to search in a directory tree. For example, to find
- all "README" files in the current directory and below: >
- :echo expand("**/README")
+ all "README" files in the current directory and below: >vim
+ echo expand("**/README")
<
expand() can also be used to expand variables and environment
variables that are only known in a shell. But this can be
@@ -2049,10 +1493,7 @@ expand({string} [, {nosuf} [, {list}]]) *expand()*
See |glob()| for finding existing files. See |system()| for
getting the raw output of an external command.
- Can also be used as a |method|: >
- Getpattern()->expand()
-
-expandcmd({string} [, {options}]) *expandcmd()*
+expandcmd({string} [, {options}]) *expandcmd()*
Expand special items in String {string} like what is done for
an Ex command such as `:edit`. This expands special keywords,
like with |expand()|, and environment variables, anywhere in
@@ -2068,15 +1509,15 @@ expandcmd({string} [, {options}]) *expandcmd()*
Returns the expanded string. If an error is encountered
during expansion, the unmodified {string} is returned.
- Example: >
- :echo expandcmd('make %<.o')
+ Example: >vim
+ echo expandcmd('make %<.o')
+< >
make /path/runtime/doc/builtin.o
- :echo expandcmd('make %<.o', {'errmsg': v:true})
+< >vim
+ echo expandcmd('make %<.o', {'errmsg': v:true})
<
- Can also be used as a |method|: >
- GetCommand()->expandcmd()
-<
-extend({expr1}, {expr2} [, {expr3}]) *extend()*
+
+extend({expr1}, {expr2} [, {expr3}]) *extend()*
{expr1} and {expr2} must be both |Lists| or both
|Dictionaries|.
@@ -2085,16 +1526,16 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()*
item with index {expr3} in {expr1}. When {expr3} is zero
insert before the first item. When {expr3} is equal to
len({expr1}) then {expr2} is appended.
- Examples: >
- :echo sort(extend(mylist, [7, 5]))
- :call extend(mylist, [2, 3], 1)
+ Examples: >vim
+ echo sort(extend(mylist, [7, 5]))
+ call extend(mylist, [2, 3], 1)
< When {expr1} is the same List as {expr2} then the number of
items copied is equal to the original length of the List.
E.g., when {expr3} is 1 you get N new copies of the first item
(where N is the original length of the List).
Use |add()| to concatenate one item to a list. To concatenate
- two lists into a new list use the + operator: >
- :let newlist = [1, 2, 3] + [4, 5]
+ two lists into a new list use the + operator: >vim
+ let newlist = [1, 2, 3] + [4, 5]
<
If they are |Dictionaries|:
Add all entries from {expr2} to {expr1}.
@@ -2112,10 +1553,12 @@ extend({expr1}, {expr2} [, {expr3}]) *extend()*
fails.
Returns {expr1}. Returns 0 on error.
- Can also be used as a |method|: >
- mylist->extend(otherlist)
+extendnew({expr1}, {expr2} [, {expr3}]) *extendnew()*
+ Like |extend()| but instead of adding items to {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged.
-feedkeys({string} [, {mode}]) *feedkeys()*
+feedkeys({string} [, {mode}]) *feedkeys()*
Characters in {string} are queued for processing as if they
come from a mapping or were typed by the user.
@@ -2160,39 +1603,35 @@ feedkeys({string} [, {mode}]) *feedkeys()*
Return value is always 0.
- Can also be used as a |method|: >
- GetInput()->feedkeys()
-
-filereadable({file}) *filereadable()*
+filereadable({file}) *filereadable()*
The result is a Number, which is |TRUE| when a file with the
name {file} exists, and can be read. If {file} doesn't exist,
or is a directory, the result is |FALSE|. {file} is any
expression, which is used as a String.
If you don't care about the file being readable you can use
|glob()|.
- {file} is used as-is, you may want to expand wildcards first: >
+ {file} is used as-is, you may want to expand wildcards first: >vim
echo filereadable('~/.vimrc')
+< >
0
+< >vim
echo filereadable(expand('~/.vimrc'))
+< >
1
+<
-< Can also be used as a |method|: >
- GetName()->filereadable()
-
-filewritable({file}) *filewritable()*
+filewritable({file}) *filewritable()*
The result is a Number, which is 1 when a file with the
name {file} exists, and can be written. If {file} doesn't
exist, or is not writable, the result is 0. If {file} is a
directory, and we can write to it, the result is 2.
- Can also be used as a |method|: >
- GetName()->filewritable()
-
-filter({expr1}, {expr2}) *filter()*
- {expr1} must be a |List|, |Blob|, or a |Dictionary|.
+filter({expr1}, {expr2}) *filter()*
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
For each item in {expr1} evaluate {expr2} and when the result
- is zero remove the item from the |List| or |Dictionary|. For a
- |Blob| each byte is removed.
+ is zero or false remove the item from the |List| or
+ |Dictionary|. Similarly for each byte in a |Blob| and each
+ character in a |String|.
{expr2} must be a |string| or |Funcref|.
@@ -2200,13 +1639,13 @@ filter({expr1}, {expr2}) *filter()*
of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the
- current byte.
-
- Examples: >
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Examples: >vim
call filter(mylist, 'v:val !~ "OLD"')
-< Removes the items where "OLD" appears. >
+< Removes the items where "OLD" appears. >vim
call filter(mydict, 'v:key >= 8')
-< Removes the items with a key below 8. >
+< Removes the items with a key below 8. >vim
call filter(var, 0)
< Removes all the items, thus clears the |List| or |Dictionary|.
@@ -2218,30 +1657,29 @@ filter({expr1}, {expr2}) *filter()*
1. the key or the index of the current item.
2. the value of the current item.
The function must return |TRUE| if the item should be kept.
- Example that keeps the odd items of a list: >
+ Example that keeps the odd items of a list: >vim
func Odd(idx, val)
return a:idx % 2 == 1
endfunc
call filter(mylist, function('Odd'))
-< It is shorter when using a |lambda|: >
+< It is shorter when using a |lambda|: >vim
call filter(myList, {idx, val -> idx * val <= 42})
-< If you do not use "val" you can leave it out: >
+< If you do not use "val" you can leave it out: >vim
call filter(myList, {idx -> idx % 2 == 1})
<
- The operation is done in-place. If you want a |List| or
- |Dictionary| to remain unmodified make a copy first: >
- :let l = filter(copy(mylist), 'v:val =~ "KEEP"')
+ For a |List| and a |Dictionary| the operation is done
+ in-place. If you want it to remain unmodified make a copy
+ first: >vim
+ let l = filter(copy(mylist), 'v:val =~ "KEEP"')
-< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
- filtered. When an error is encountered while evaluating
- {expr2} no further items in {expr1} are processed. When
- {expr2} is a Funcref errors inside a function are ignored,
+< Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
unless it was defined with the "abort" flag.
- Can also be used as a |method|: >
- mylist->filter(expr2)
-
-finddir({name} [, {path} [, {count}]]) *finddir()*
+finddir({name} [, {path} [, {count}]]) *finddir()*
Find directory {name} in {path}. Supports both downwards and
upwards recursive directory searches. See |file-searching|
for the syntax of {path}.
@@ -2259,25 +1697,19 @@ finddir({name} [, {path} [, {count}]]) *finddir()*
This is quite similar to the ex-command `:find`.
- Can also be used as a |method|: >
- GetName()->finddir()
-
-findfile({name} [, {path} [, {count}]]) *findfile()*
+findfile({name} [, {path} [, {count}]]) *findfile()*
Just like |finddir()|, but find a file instead of a directory.
Uses 'suffixesadd'.
- Example: >
- :echo findfile("tags.vim", ".;")
+ Example: >vim
+ echo findfile("tags.vim", ".;")
< Searches from the directory of the current file upwards until
it finds the file "tags.vim".
- Can also be used as a |method|: >
- GetName()->findfile()
-
-flatten({list} [, {maxdepth}]) *flatten()*
+flatten({list} [, {maxdepth}]) *flatten()*
Flatten {list} up to {maxdepth} levels. Without {maxdepth}
the result is a |List| without nesting, as if {maxdepth} is
a very large number.
- The {list} is changed in place, make a copy first if you do
+ The {list} is changed in place, use |flattennew()| if you do
not want that.
*E900*
{maxdepth} means how deep in nested lists changes are made.
@@ -2286,16 +1718,16 @@ flatten({list} [, {maxdepth}]) *flatten()*
If there is an error the number zero is returned.
- Example: >
- :echo flatten([1, [2, [3, 4]], 5])
-< [1, 2, 3, 4, 5] >
- :echo flatten([1, [2, [3, 4]], 5], 1)
+ Example: >vim
+ echo flatten([1, [2, [3, 4]], 5])
+< [1, 2, 3, 4, 5] >vim
+ echo flatten([1, [2, [3, 4]], 5], 1)
< [1, 2, [3, 4], 5]
- Can also be used as a |method|: >
- mylist->flatten()
-<
-float2nr({expr}) *float2nr()*
+flattennew({list} [, {maxdepth}]) *flattennew()*
+ Like |flatten()| but first make a copy of {list}.
+
+float2nr({expr}) *float2nr()*
Convert {expr} to a Number by omitting the part after the
decimal point.
{expr} must evaluate to a |Float| or a |Number|.
@@ -2305,38 +1737,32 @@ float2nr({expr}) *float2nr()*
64-bit Number support is enabled, 0x7fffffffffffffff or
-0x7fffffffffffffff). NaN results in -0x80000000 (or when
64-bit Number support is enabled, -0x8000000000000000).
- Examples: >
+ Examples: >vim
echo float2nr(3.95)
-< 3 >
+< 3 >vim
echo float2nr(-23.45)
-< -23 >
+< -23 >vim
echo float2nr(1.0e100)
-< 2147483647 (or 9223372036854775807) >
+< 2147483647 (or 9223372036854775807) >vim
echo float2nr(-1.0e150)
-< -2147483647 (or -9223372036854775807) >
+< -2147483647 (or -9223372036854775807) >vim
echo float2nr(1.0e-100)
< 0
- Can also be used as a |method|: >
- Compute()->float2nr()
-
-floor({expr}) *floor()*
+floor({expr}) *floor()*
Return the largest integral value less than or equal to
{expr} as a |Float| (round down).
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
+ Examples: >vim
echo floor(1.856)
-< 1.0 >
+< 1.0 >vim
echo floor(-5.456)
-< -6.0 >
+< -6.0 >vim
echo floor(4.0)
< 4.0
- Can also be used as a |method|: >
- Compute()->floor()
-
-fmod({expr1}, {expr2}) *fmod()*
+fmod({expr1}, {expr2}) *fmod()*
Return the remainder of {expr1} / {expr2}, even if the
division is not representable. Returns {expr1} - i * {expr2}
for some integer i such that if {expr2} is non-zero, the
@@ -2346,18 +1772,15 @@ fmod({expr1}, {expr2}) *fmod()*
{expr1} and {expr2} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
|Number|.
- Examples: >
- :echo fmod(12.33, 1.22)
-< 0.13 >
- :echo fmod(-12.33, 1.22)
+ Examples: >vim
+ echo fmod(12.33, 1.22)
+< 0.13 >vim
+ echo fmod(-12.33, 1.22)
< -0.13
- Can also be used as a |method|: >
- Compute()->fmod(1.22)
-
-fnameescape({string}) *fnameescape()*
+fnameescape({string}) *fnameescape()*
Escape {string} for use as file name command argument. All
- characters that have a special meaning, such as '%' and '|'
+ characters that have a special meaning, such as `'%'` and `'|'`
are escaped with a backslash.
For most systems the characters escaped are
" \t\n*?[{`$\\%#'\"|!<". For systems where a backslash
@@ -2365,21 +1788,19 @@ fnameescape({string}) *fnameescape()*
A leading '+' and '>' is also escaped (special after |:edit|
and |:write|). And a "-" by itself (special after |:cd|).
Returns an empty string on error.
- Example: >
- :let fname = '+some str%nge|name'
- :exe "edit " .. fnameescape(fname)
-< results in executing: >
+ Example: >vim
+ let fname = '+some str%nge|name'
+ exe "edit " .. fnameescape(fname)
+< results in executing: >vim
edit \+some\ str\%nge\|name
<
- Can also be used as a |method|: >
- GetName()->fnameescape()
-fnamemodify({fname}, {mods}) *fnamemodify()*
+fnamemodify({fname}, {mods}) *fnamemodify()*
Modify file name {fname} according to {mods}. {mods} is a
string of characters like it is used for file names on the
command line. See |filename-modifiers|.
- Example: >
- :echo fnamemodify("main.c", ":p:h")
+ Example: >vim
+ echo fnamemodify("main.c", ":p:h")
< results in: >
/home/user/vim/vim/src
< If {mods} is empty or an unsupported modifier is used then
@@ -2391,30 +1812,21 @@ fnamemodify({fname}, {mods}) *fnamemodify()*
Note: Environment variables don't work in {fname}, use
|expand()| first then.
- Can also be used as a |method|: >
- GetName()->fnamemodify(':p:h')
-
-foldclosed({lnum}) *foldclosed()*
+foldclosed({lnum}) *foldclosed()*
The result is a Number. If the line {lnum} is in a closed
fold, the result is the number of the first line in that fold.
If the line {lnum} is not in a closed fold, -1 is returned.
{lnum} is used like with |getline()|. Thus "." is the current
line, "'m" mark m, etc.
- Can also be used as a |method|: >
- GetLnum()->foldclosed()
-
-foldclosedend({lnum}) *foldclosedend()*
+foldclosedend({lnum}) *foldclosedend()*
The result is a Number. If the line {lnum} is in a closed
fold, the result is the number of the last line in that fold.
If the line {lnum} is not in a closed fold, -1 is returned.
{lnum} is used like with |getline()|. Thus "." is the current
line, "'m" mark m, etc.
- Can also be used as a |method|: >
- GetLnum()->foldclosedend()
-
-foldlevel({lnum}) *foldlevel()*
+foldlevel({lnum}) *foldlevel()*
The result is a Number, which is the foldlevel of line {lnum}
in the current buffer. For nested folds the deepest level is
returned. If there is no fold at line {lnum}, zero is
@@ -2426,11 +1838,8 @@ foldlevel({lnum}) *foldlevel()*
{lnum} is used like with |getline()|. Thus "." is the current
line, "'m" mark m, etc.
- Can also be used as a |method|: >
- GetLnum()->foldlevel()
-<
- *foldtext()*
-foldtext() Returns a String, to be displayed for a closed fold. This is
+foldtext() *foldtext()*
+ Returns a String, to be displayed for a closed fold. This is
the default function used for the 'foldtext' option and should
only be called from evaluating 'foldtext'. It uses the
|v:foldstart|, |v:foldend| and |v:folddashes| variables.
@@ -2446,7 +1855,7 @@ foldtext() Returns a String, to be displayed for a closed fold. This is
setting.
Returns an empty string when there is no fold.
-foldtextresult({lnum}) *foldtextresult()*
+foldtextresult({lnum}) *foldtextresult()*
Returns the text that is displayed for the closed fold at line
{lnum}. Evaluates 'foldtext' in the appropriate context.
When there is no closed fold at {lnum} an empty string is
@@ -2455,10 +1864,8 @@ foldtextresult({lnum}) *foldtextresult()*
line, "'m" mark m, etc.
Useful when exporting folded text, e.g., to HTML.
- Can also be used as a |method|: >
- GetLnum()->foldtextresult()
-<
-fullcommand({name}) *fullcommand()*
+
+fullcommand({name}) *fullcommand()*
Get the full command name from a short abbreviated command
name; see |20.2| for details on command abbreviations.
@@ -2470,11 +1877,7 @@ fullcommand({name}) *fullcommand()*
For example `fullcommand('s')`, `fullcommand('sub')`,
`fullcommand(':%substitute')` all return "substitute".
- Can also be used as a |method|: >
- GetName()->fullcommand()
-<
- *funcref()*
-funcref({name} [, {arglist}] [, {dict}])
+funcref({name} [, {arglist}] [, {dict}]) *funcref()*
Just like |function()|, but the returned Funcref will lookup
the function by reference, not by name. This matters when the
function {name} is redefined later.
@@ -2486,18 +1889,14 @@ funcref({name} [, {arglist}] [, {dict}])
instead). {name} cannot be a builtin function.
Returns 0 on error.
- Can also be used as a |method|: >
- GetFuncname()->funcref([arg])
-<
- *function()* *partial* *E700* *E922* *E923*
-function({name} [, {arglist}] [, {dict}])
+function({name} [, {arglist}] [, {dict}]) *function()* *partial* *E700* *E923*
Return a |Funcref| variable that refers to function {name}.
{name} can be the name of a user defined function or an
internal function.
{name} can also be a Funcref or a partial. When it is a
partial the dict stored in it will be used and the {dict}
- argument is not allowed. E.g.: >
+ argument is not allowed. E.g.: >vim
let FuncWithArg = function(dict.Func, [arg])
let Broken = function(dict.Func, [arg], dict)
<
@@ -2510,38 +1909,41 @@ function({name} [, {arglist}] [, {dict}])
the Funcref and will be used when the Funcref is called.
The arguments are passed to the function in front of other
- arguments, but after any argument from |method|. Example: >
+ arguments, but after any argument from |method|. Example: >vim
func Callback(arg1, arg2, name)
"...
+ endfunc
let Partial = function('Callback', ['one', 'two'])
"...
call Partial('name')
-< Invokes the function as with: >
+< Invokes the function as with: >vim
call Callback('one', 'two', 'name')
-< With a |method|: >
+< With a |method|: >vim
func Callback(one, two, three)
"...
+ endfunc
let Partial = function('Callback', ['two'])
"...
eval 'one'->Partial('three')
-< Invokes the function as with: >
+< Invokes the function as with: >vim
call Callback('one', 'two', 'three')
< The function() call can be nested to add more arguments to the
Funcref. The extra arguments are appended to the list of
- arguments. Example: >
+ arguments. Example: >vim
func Callback(arg1, arg2, name)
"...
+ endfunc
let Func = function('Callback', ['one'])
let Func2 = function(Func, ['two'])
"...
call Func2('name')
-< Invokes the function as with: >
+< Invokes the function as with: >vim
call Callback('one', 'two', 'name')
< The Dictionary is only useful when calling a "dict" function.
- In that case the {dict} is passed in as "self". Example: >
+ In that case the {dict} is passed in as "self". Example: >vim
function Callback() dict
echo "called for " .. self.name
endfunction
@@ -2552,26 +1954,24 @@ function({name} [, {arglist}] [, {dict}])
call Func() " will echo: called for example
< The use of function() is not needed when there are no extra
arguments, these two are equivalent, if Callback() is defined
- as context.Callback(): >
+ as context.Callback(): >vim
let Func = function('Callback', context)
let Func = context.Callback
-< The argument list and the Dictionary can be combined: >
+< The argument list and the Dictionary can be combined: >vim
function Callback(arg1, count) dict
"...
+ endfunction
let context = {"name": "example"}
let Func = function('Callback', ['one'], context)
"...
call Func(500)
-< Invokes the function as with: >
+< Invokes the function as with: >vim
call context.Callback('one', 500)
<
Returns 0 on error.
- Can also be used as a |method|: >
- GetFuncname()->function([arg])
-
-garbagecollect([{atexit}]) *garbagecollect()*
+garbagecollect([{atexit}]) *garbagecollect()*
Cleanup unused |Lists| and |Dictionaries| that have circular
references.
@@ -2591,23 +1991,24 @@ garbagecollect([{atexit}]) *garbagecollect()*
it's safe to perform. This is when waiting for the user to
type a character.
-get({list}, {idx} [, {default}]) *get()*
+get({list}, {idx} [, {default}]) *get()*
Get item {idx} from |List| {list}. When this item is not
available return {default}. Return zero when {default} is
omitted.
- Can also be used as a |method|: >
- mylist->get(idx)
+
get({blob}, {idx} [, {default}])
Get byte {idx} from |Blob| {blob}. When this byte is not
available return {default}. Return -1 when {default} is
omitted.
+
get({dict}, {key} [, {default}])
Get item with key {key} from |Dictionary| {dict}. When this
item is not available return {default}. Return zero when
- {default} is omitted. Useful example: >
+ {default} is omitted. Useful example: >vim
let val = get(g:, 'var_name', 'default')
< This gets the value of g:var_name if it exists, and uses
"default" when it does not exist.
+
get({func}, {what})
Get item {what} from Funcref {func}. Possible values for
{what} are:
@@ -2617,9 +2018,8 @@ get({func}, {what})
"args" The list with arguments
Returns zero on error.
- *getbufinfo()*
getbufinfo([{buf}])
-getbufinfo([{dict}])
+getbufinfo([{dict}]) *getbufinfo()*
Get information about buffers as a List of Dictionaries.
Without an argument information about all the buffers is
@@ -2653,8 +2053,8 @@ getbufinfo([{dict}])
displayed in the window in the past.
If you want the line number of the
last known cursor position in a given
- window, use |line()|: >
- :echo line('.', {winid})
+ window, use |line()|: >vim
+ echo line('.', {winid})
<
linecount Number of lines in the buffer (only
valid when loaded)
@@ -2671,25 +2071,21 @@ getbufinfo([{dict}])
windows List of |window-ID|s that display this
buffer
- Examples: >
+ Examples: >vim
for buf in getbufinfo()
echo buf.name
endfor
for buf in getbufinfo({'buflisted':1})
if buf.changed
- ....
+ " ....
endif
endfor
<
- To get buffer-local options use: >
+ To get buffer-local options use: >vim
getbufvar({bufnr}, '&option_name')
<
- Can also be used as a |method|: >
- GetBufnr()->getbufinfo()
-<
- *getbufline()*
-getbufline({buf}, {lnum} [, {end}])
+getbufline({buf}, {lnum} [, {end}]) *getbufline()*
Return a |List| with the lines starting from {lnum} to {end}
(inclusive) in the buffer {buf}. If {end} is omitted, a
|List| with only the line {lnum} is returned. See
@@ -2711,18 +2107,14 @@ getbufline({buf}, {lnum} [, {end}])
This function works only for loaded buffers. For unloaded and
non-existing buffers, an empty |List| is returned.
- Example: >
- :let lines = getbufline(bufnr("myfile"), 1, "$")
+ Example: >vim
+ let lines = getbufline(bufnr("myfile"), 1, "$")
-< Can also be used as a |method|: >
- GetBufnr()->getbufline(lnum)
-<
- *getbufoneline()*
-getbufoneline({buf}, {lnum})
+getbufoneline({buf}, {lnum}) *getbufoneline()*
Just like `getbufline()` but only get one line and return it
as a string.
-getbufvar({buf}, {varname} [, {def}]) *getbufvar()*
+getbufvar({buf}, {varname} [, {def}]) *getbufvar()*
The result is the value of option or local buffer variable
{varname} in buffer {buf}. Note that the name without "b:"
must be used.
@@ -2739,21 +2131,17 @@ getbufvar({buf}, {varname} [, {def}]) *getbufvar()*
For the use of {buf}, see |bufname()| above.
When the buffer or variable doesn't exist {def} or an empty
string is returned, there is no error message.
- Examples: >
- :let bufmodified = getbufvar(1, "&mod")
- :echo "todo myvar = " .. getbufvar("todo", "myvar")
+ Examples: >vim
+ let bufmodified = getbufvar(1, "&mod")
+ echo "todo myvar = " .. getbufvar("todo", "myvar")
-< Can also be used as a |method|: >
- GetBufnr()->getbufvar(varname)
-<
-getcellwidths() *getcellwidths()*
+getcellwidths() *getcellwidths()*
Returns a |List| of cell widths of character ranges overridden
by |setcellwidths()|. The format is equal to the argument of
|setcellwidths()|. If no character ranges have their cell
widths overridden, an empty List is returned.
-
-getchangelist([{buf}]) *getchangelist()*
+getchangelist([{buf}]) *getchangelist()*
Returns the |changelist| for the buffer {buf}. For the use
of {buf}, see |bufname()| above. If buffer {buf} doesn't
exist, an empty list is returned.
@@ -2769,10 +2157,7 @@ getchangelist([{buf}]) *getchangelist()*
position refers to the position in the list. For other
buffers, it is set to the length of the list.
- Can also be used as a |method|: >
- GetBufnr()->getchangelist()
-
-getchar([expr]) *getchar()*
+getchar([expr]) *getchar()*
Get a single character from the user or input stream.
If [expr] is omitted, wait until a character is available.
If [expr] is 0, only get a character when one is available.
@@ -2806,7 +2191,7 @@ getchar([expr]) *getchar()*
|v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|.
|getmousepos()| can also be used. Mouse move events will be
ignored.
- This example positions the mouse as it would normally happen: >
+ This example positions the mouse as it would normally happen: >vim
let c = getchar()
if c == "\<LeftMouse>" && v:mouse_win > 0
exe v:mouse_win .. "wincmd w"
@@ -2821,22 +2206,23 @@ getchar([expr]) *getchar()*
There is no mapping for the character.
Key codes are replaced, thus when the user presses the <Del>
key you get the code for the <Del> key, not the raw character
- sequence. Examples: >
+ sequence. Examples: >vim
getchar() == "\<Del>"
getchar() == "\<S-Left>"
-< This example redefines "f" to ignore case: >
- :nmap f :call FindChar()<CR>
- :function FindChar()
- : let c = nr2char(getchar())
- : while col('.') < col('$') - 1
- : normal l
- : if getline('.')[col('.') - 1] ==? c
- : break
- : endif
- : endwhile
- :endfunction
-<
-getcharmod() *getcharmod()*
+< This example redefines "f" to ignore case: >vim
+ nmap f :call FindChar()<CR>
+ function FindChar()
+ let c = nr2char(getchar())
+ while col('.') < col('$') - 1
+ normal l
+ if getline('.')[col('.') - 1] ==? c
+ break
+ endif
+ endwhile
+ endfunction
+<
+
+getcharmod() *getcharmod()*
The result is a Number which is the state of the modifiers for
the last obtained character with getchar() or in another way.
These values are added together:
@@ -2852,21 +2238,21 @@ getcharmod() *getcharmod()*
character itself are obtained. Thus Shift-a results in "A"
without a modifier. Returns 0 if no modifiers are used.
- *getcharpos()*
-getcharpos({expr})
+getcharpos({expr}) *getcharpos()*
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.
+ If |getpos()| returns a very large column number, equal to
+ |v:maxcol|, then getcharpos() will return the character index
+ of the last character.
Example:
- With the cursor on '세' in line 5 with text "여보세요": >
+ With the cursor on '세' in line 5 with text "여보세요": >vim
getcharpos('.') returns [0, 5, 3, 0]
getpos('.') returns [0, 5, 7, 0]
<
- Can also be used as a |method|: >
- GetMark()->getcharpos()
-getcharsearch() *getcharsearch()*
+getcharsearch() *getcharsearch()*
Return the current character search information as a {dict}
with the following entries:
@@ -2881,13 +2267,12 @@ getcharsearch() *getcharsearch()*
This can be useful to always have |;| and |,| search
forward/backward regardless of the direction of the previous
- character search: >
- :nnoremap <expr> ; getcharsearch().forward ? ';' : ','
- :nnoremap <expr> , getcharsearch().forward ? ',' : ';'
+ character search: >vim
+ nnoremap <expr> ; getcharsearch().forward ? ';' : ','
+ nnoremap <expr> , getcharsearch().forward ? ',' : ';'
< Also see |setcharsearch()|.
-
-getcharstr([expr]) *getcharstr()*
+getcharstr([expr]) *getcharstr()*
Get a single character from the user or input stream as a
string.
If [expr] is omitted, wait until a character is available.
@@ -2899,7 +2284,7 @@ getcharstr([expr]) *getcharstr()*
Otherwise this works like |getchar()|, except that a number
result is converted to a string.
-getcmdcompltype() *getcmdcompltype()*
+getcmdcompltype() *getcmdcompltype()*
Return the type of the current command-line completion.
Only works when the command line is being edited, thus
requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
@@ -2908,18 +2293,18 @@ getcmdcompltype() *getcmdcompltype()*
|setcmdline()|.
Returns an empty string when completion is not defined.
-getcmdline() *getcmdline()*
+getcmdline() *getcmdline()*
Return the current command-line. Only works when the command
line is being edited, thus requires use of |c_CTRL-\_e| or
|c_CTRL-R_=|.
- Example: >
- :cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
+ Example: >vim
+ cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
< Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and
|setcmdline()|.
Returns an empty string when entering a password or using
|inputsecret()|.
-getcmdpos() *getcmdpos()*
+getcmdpos() *getcmdpos()*
Return the position of the cursor in the command line as a
byte count. The first column is 1.
Only works when editing the command line, thus requires use of
@@ -2928,7 +2313,7 @@ getcmdpos() *getcmdpos()*
Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
|setcmdline()|.
-getcmdscreenpos() *getcmdscreenpos()*
+getcmdscreenpos() *getcmdscreenpos()*
Return the screen position of the cursor in the command line
as a byte count. The first column is 1.
Instead of |getcmdpos()|, it adds the prompt position.
@@ -2938,7 +2323,7 @@ getcmdscreenpos() *getcmdscreenpos()*
Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and
|setcmdline()|.
-getcmdtype() *getcmdtype()*
+getcmdtype() *getcmdtype()*
Return the current command-line type. Possible return values
are:
: normal Ex command
@@ -2946,19 +2331,19 @@ getcmdtype() *getcmdtype()*
/ forward search command
? backward search command
@ |input()| command
- - |:insert| or |:append| command
+ `-` |:insert| or |:append| command
= |i_CTRL-R_=|
Only works when editing the command line, thus requires use of
|c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
Returns an empty string otherwise.
Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|.
-getcmdwintype() *getcmdwintype()*
+getcmdwintype() *getcmdwintype()*
Return the current |command-line-window| type. Possible return
values are the same as |getcmdtype()|. Returns an empty string
when not in the command-line window.
-getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
+getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
Return a list of command-line completion matches. The String
{type} argument specifies what for. The following completion
types are supported:
@@ -2966,13 +2351,14 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
arglist file names in argument list
augroup autocmd groups
buffer buffer names
- behave |:behave| suboptions
breakpoint |:breakadd| and |:breakdel| suboptions
cmdline |cmdline-completion| result
color color schemes
command Ex command
compiler compilers
- diff_buffer |:diffget| and |:diffput| completion
+ custom,{func} custom completion, defined via {func}
+ customlist,{func} custom completion, defined via {func}
+ diff_buffer |:diffget| and |:diffput| completion
dir directory names
environment environment variable names
event autocommand events
@@ -3019,22 +2405,19 @@ getcompletion({pat}, {type} [, {filtered}]) *getcompletion()*
If {type} is "cmdline", then the |cmdline-completion| result is
returned. For example, to complete the possible values after
- a ":call" command: >
+ a ":call" command: >vim
echo getcompletion('call ', 'cmdline')
<
If there are no matches, an empty list is returned. An
invalid value for {type} produces an error.
- Can also be used as a |method|: >
- GetPattern()->getcompletion('color')
-<
- *getcurpos()*
-getcurpos([{winid}])
+getcurpos([{winid}]) *getcurpos()*
Get the position of the cursor. This is like getpos('.'), but
- includes an extra "curswant" in the list:
+ includes an extra "curswant" item in the list:
[0, lnum, col, off, curswant] ~
The "curswant" number is the preferred column when moving the
- cursor vertically. Also see |getcursorcharpos()| and
+ cursor vertically. After |$| command it will be a very large
+ number equal to |v:maxcol|. 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
@@ -3046,37 +2429,31 @@ getcurpos([{winid}])
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: >
+ This can be used to save and restore the cursor position: >vim
let save_cursor = getcurpos()
MoveTheCursorAround
call setpos('.', save_cursor)
< Note that this only works within the window. See
|winrestview()| for restoring more state.
- Can also be used as a |method|: >
- GetWinid()->getcurpos()
-<
- *getcursorcharpos()*
-getcursorcharpos([{winid}])
+getcursorcharpos([{winid}]) *getcursorcharpos()*
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]
+ With the cursor on '보' in line 3 with text "여보세요": >vim
+ 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()*
+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
ignored.
Tabs and windows are identified by their respective numbers,
0 means current tab or window. Missing tab number implies 0.
- Thus the following are equivalent: >
+ Thus the following are equivalent: >vim
getcwd(0)
getcwd(0, 0)
< If {winnr} is -1 it is ignored, only the tab is resolved.
@@ -3085,22 +2462,16 @@ getcwd([{winnr} [, {tabnr}]]) *getcwd()*
directory is returned.
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
- Can also be used as a |method|: >
- GetWinnr()->getcwd()
-
-getenv({name}) *getenv()*
+getenv({name}) *getenv()*
Return the value of environment variable {name}. The {name}
- argument is a string, without a leading '$'. Example: >
+ argument is a string, without a leading '$'. Example: >vim
myHome = getenv('HOME')
< When the variable does not exist |v:null| is returned. That
is different from a variable set to an empty string.
See also |expr-env|.
- Can also be used as a |method|: >
- GetVarname()->getenv()
-
-getfontname([{name}]) *getfontname()*
+getfontname([{name}]) *getfontname()*
Without an argument returns the name of the normal font being
used. Like what is used for the Normal highlight group
|hl-Normal|.
@@ -3112,7 +2483,7 @@ getfontname([{name}]) *getfontname()*
gvimrc file. Use the |GUIEnter| autocommand to use this
function just after the GUI has started.
-getfperm({fname}) *getfperm()*
+getfperm({fname}) *getfperm()*
The result is a String, which is the read, write, and execute
permissions of the given file {fname}.
If {fname} does not exist or its directory cannot be read, an
@@ -3121,18 +2492,15 @@ getfperm({fname}) *getfperm()*
"rwx" flags represent, in turn, the permissions of the owner
of the file, the group the file belongs to, and other users.
If a user does not have a given permission the flag for this
- is replaced with the string "-". Examples: >
- :echo getfperm("/etc/passwd")
- :echo getfperm(expand("~/.config/nvim/init.vim"))
+ is replaced with the string "-". Examples: >vim
+ echo getfperm("/etc/passwd")
+ echo getfperm(expand("~/.config/nvim/init.vim"))
< This will hopefully (from a security point of view) display
the string "rw-r--r--" or even "rw-------".
- Can also be used as a |method|: >
- GetFilename()->getfperm()
-<
For setting permissions use |setfperm()|.
-getfsize({fname}) *getfsize()*
+getfsize({fname}) *getfsize()*
The result is a Number, which is the size in bytes of the
given file {fname}.
If {fname} is a directory, 0 is returned.
@@ -3140,20 +2508,14 @@ getfsize({fname}) *getfsize()*
If the size of {fname} is too big to fit in a Number then -2
is returned.
- Can also be used as a |method|: >
- GetFilename()->getfsize()
-
-getftime({fname}) *getftime()*
+getftime({fname}) *getftime()*
The result is a Number, which is the last modification time of
the given file {fname}. The value is measured as seconds
since 1st Jan 1970, and may be passed to strftime(). See also
|localtime()| and |strftime()|.
If the file {fname} can't be found -1 is returned.
- Can also be used as a |method|: >
- GetFilename()->getftime()
-
-getftype({fname}) *getftype()*
+getftype({fname}) *getftype()*
The result is a String, which is a description of the kind of
file of the given file {fname}.
If {fname} does not exist an empty string is returned.
@@ -3167,16 +2529,13 @@ getftype({fname}) *getftype()*
Socket "socket"
FIFO "fifo"
All other "other"
- Example: >
+ Example: >vim
getftype("/home")
< Note that a type such as "link" will only be returned on
systems that support it. On some systems only "dir" and
"file" are returned.
- Can also be used as a |method|: >
- GetFilename()->getftype()
-
-getjumplist([{winnr} [, {tabnr}]]) *getjumplist()*
+getjumplist([{winnr} [, {tabnr}]]) *getjumplist()*
Returns the |jumplist| for the specified window.
Without arguments use the current window.
@@ -3196,17 +2555,13 @@ getjumplist([{winnr} [, {tabnr}]]) *getjumplist()*
filename filename if available
lnum line number
- Can also be used as a |method|: >
- GetWinnr()->getjumplist()
-
-< *getline()*
-getline({lnum} [, {end}])
+getline({lnum} [, {end}]) *getline()*
Without {end} the result is a String, which is line {lnum}
- from the current buffer. Example: >
+ from the current buffer. Example: >vim
getline(1)
< When {lnum} is a String that doesn't start with a
digit, |line()| is called to translate the String into a Number.
- To get the line under the cursor: >
+ To get the line under the cursor: >vim
getline(".")
< When {lnum} is a number smaller than 1 or bigger than the
number of lines in the buffer, an empty string is returned.
@@ -3217,18 +2572,15 @@ getline({lnum} [, {end}])
{end} is used in the same way as {lnum}.
Non-existing lines are silently omitted.
When {end} is before {lnum} an empty |List| is returned.
- Example: >
- :let start = line('.')
- :let end = search("^$") - 1
- :let lines = getline(start, end)
-
-< Can also be used as a |method|: >
- ComputeLnum()->getline()
+ Example: >vim
+ let start = line('.')
+ let end = search("^$") - 1
+ let lines = getline(start, end)
< To get lines from another buffer see |getbufline()| and
|getbufoneline()|
-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.
@@ -3255,12 +2607,12 @@ getloclist({nr} [, {what}]) *getloclist()*
location list for the window {nr}.
Returns an empty Dictionary if window {nr} does not exist.
- Examples (See also |getqflist-examples|): >
- :echo getloclist(3, {'all': 0})
- :echo getloclist(5, {'filewinid': 0})
-
+ Examples (See also |getqflist-examples|): >vim
+ echo getloclist(3, {'all': 0})
+ echo getloclist(5, {'filewinid': 0})
+<
-getmarklist([{buf}]) *getmarklist()*
+getmarklist([{buf}]) *getmarklist()*
Without the {buf} argument returns a |List| with information
about all the global marks. |mark|
@@ -3279,10 +2631,7 @@ getmarklist([{buf}]) *getmarklist()*
Refer to |getpos()| for getting information about a specific
mark.
- Can also be used as a |method|: >
- GetBufnr()->getmarklist()
-
-getmatches([{win}]) *getmatches()*
+getmatches([{win}]) *getmatches()*
Returns a |List| with all matches previously defined for the
current window by |matchadd()| and the |:match| commands.
|getmatches()| is useful in combination with |setmatches()|,
@@ -3291,24 +2640,31 @@ getmatches([{win}]) *getmatches()*
If {win} is specified, use the window with this number or
window ID instead of the current window. If {win} is invalid,
an empty list is returned.
- Example: >
- :echo getmatches()
-< [{"group": "MyGroup1", "pattern": "TODO",
+ Example: >vim
+ echo getmatches()
+< >
+ [{"group": "MyGroup1", "pattern": "TODO",
"priority": 10, "id": 1}, {"group": "MyGroup2",
- "pattern": "FIXME", "priority": 10, "id": 2}] >
- :let m = getmatches()
- :call clearmatches()
- :echo getmatches()
-< [] >
- :call setmatches(m)
- :echo getmatches()
-< [{"group": "MyGroup1", "pattern": "TODO",
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+< >vim
+ let m = getmatches()
+ call clearmatches()
+ echo getmatches()
+< >
+ []
+< >vim
+ call setmatches(m)
+ echo getmatches()
+< >
+ [{"group": "MyGroup1", "pattern": "TODO",
"priority": 10, "id": 1}, {"group": "MyGroup2",
- "pattern": "FIXME", "priority": 10, "id": 2}] >
- :unlet m
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+< >vim
+ unlet m
<
-getmousepos() *getmousepos()*
- Returns a Dictionary with the last known position of the
+
+getmousepos() *getmousepos()*
+ Returns a |Dictionary| with the last known position of the
mouse. This can be used in a mapping for a mouse click. The
items are:
screenrow screen row
@@ -3318,6 +2674,8 @@ getmousepos() *getmousepos()*
wincol column inside "winid"
line text line inside "winid"
column text column inside "winid"
+ coladd offset (in screen columns) from the
+ start of the clicked char
All numbers are 1-based.
If not over a window, e.g. when in the command line, then only
@@ -3336,12 +2694,12 @@ getmousepos() *getmousepos()*
When using |getchar()| the Vim variables |v:mouse_lnum|,
|v:mouse_col| and |v:mouse_winid| also provide these values.
- *getpid()*
-getpid() Return a Number which is the process ID of the Vim process.
+getpid() *getpid()*
+ Return a Number which is the process ID of the Vim process.
This is a unique number, until Vim exits.
- *getpos()*
-getpos({expr}) Get the position for String {expr}. For possible values of
+getpos({expr}) *getpos()*
+ Get the position for String {expr}. For possible values of
{expr} see |line()|. For getting the cursor position see
|getcurpos()|.
The result is a |List| with four numbers:
@@ -3356,23 +2714,20 @@ getpos({expr}) Get the position for String {expr}. For possible values of
character.
Note that for '< and '> Visual mode matters: when it is "V"
(visual line mode) the column of '< is zero and the column of
- '> is a large number.
+ '> is a large number equal to |v:maxcol|.
The column number in the returned List is the byte position
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".
+ A very large column number equal to |v:maxcol| can be returned,
+ in which case it means "after the end of the line".
If {expr} is invalid, returns a list with all zeros.
- This can be used to save and restore the position of a mark: >
+ This can be used to save and restore the position of a mark: >vim
let save_a_mark = getpos("'a")
- ...
+ " ...
call setpos("'a", save_a_mark)
< Also see |getcharpos()|, |getcurpos()| and |setpos()|.
- Can also be used as a |method|: >
- GetMark()->getpos()
-
-getqflist([{what}]) *getqflist()*
+getqflist([{what}]) *getqflist()*
Returns a |List| with all the current quickfix errors. Each
list item is a dictionary with these entries:
bufnr number of buffer that has the file name, use
@@ -3390,6 +2745,9 @@ getqflist([{what}]) *getqflist()*
text description of the error
type type of the error, 'E', '1', etc.
valid |TRUE|: recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
When there is no error list or it's empty, an empty list is
returned. Quickfix list entries with a non-existing buffer
@@ -3398,11 +2756,11 @@ getqflist([{what}]) *getqflist()*
you may need to explicitly check for zero).
Useful application: Find pattern matches in multiple files and
- do something with them: >
- :vimgrep /theword/jg *.c
- :for d in getqflist()
- : echo bufname(d.bufnr) ':' d.lnum '=' d.text
- :endfor
+ do something with them: >vim
+ vimgrep /theword/jg *.c
+ for d in getqflist()
+ echo bufname(d.bufnr) ':' d.lnum '=' d.text
+ endfor
<
If the optional {what} dictionary argument is supplied, then
returns only the items listed in {what} as a dictionary. The
@@ -3467,15 +2825,16 @@ getqflist([{what}]) *getqflist()*
to "".
winid quickfix |window-ID|. If not present, set to 0
- Examples (See also |getqflist-examples|): >
- :echo getqflist({'all': 1})
- :echo getqflist({'nr': 2, 'title': 1})
- :echo getqflist({'lines' : ["F1:10:L10"]})
+ Examples (See also |getqflist-examples|): >vim
+ echo getqflist({'all': 1})
+ echo getqflist({'nr': 2, 'title': 1})
+ echo getqflist({'lines' : ["F1:10:L10"]})
<
-getreg([{regname} [, 1 [, {list}]]]) *getreg()*
+
+getreg([{regname} [, 1 [, {list}]]]) *getreg()*
The result is a String, which is the contents of register
- {regname}. Example: >
- :let cliptext = getreg('*')
+ {regname}. Example: >vim
+ let cliptext = getreg('*')
< When register {regname} was not set the result is an empty
string.
The {regname} argument must be a string.
@@ -3495,10 +2854,7 @@ getreg([{regname} [, 1 [, {list}]]]) *getreg()*
If {regname} is not specified, |v:register| is used.
- Can also be used as a |method|: >
- GetRegname()->getreg()
-
-getreginfo([{regname}]) *getreginfo()*
+getreginfo([{regname}]) *getreginfo()*
Returns detailed information about register {regname} as a
Dictionary with the following entries:
regcontents List of lines contained in register
@@ -3522,10 +2878,7 @@ getreginfo([{regname}]) *getreginfo()*
If {regname} is not specified, |v:register| is used.
The returned Dictionary can be passed to |setreg()|.
- Can also be used as a |method|: >
- GetRegname()->getreginfo()
-
-getregtype([{regname}]) *getregtype()*
+getregtype([{regname}]) *getregtype()*
The result is a String, which is type of register {regname}.
The value will be one of:
"v" for |charwise| text
@@ -3536,10 +2889,44 @@ getregtype([{regname}]) *getregtype()*
The {regname} argument is a string. If {regname} is not
specified, |v:register| is used.
- Can also be used as a |method|: >
- GetRegname()->getregtype()
-
-gettabinfo([{tabnr}]) *gettabinfo()*
+getscriptinfo([{opts}]) *getscriptinfo()*
+ Returns a |List| with information about all the sourced Vim
+ scripts in the order they were sourced, like what
+ `:scriptnames` shows.
+
+ The optional Dict argument {opts} supports the following
+ optional items:
+ name Script name match pattern. If specified,
+ and "sid" is not specified, information about
+ scripts with a name that match the pattern
+ "name" are returned.
+ sid Script ID |<SID>|. If specified, only
+ information about the script with ID "sid" is
+ returned and "name" is ignored.
+
+ Each item in the returned List is a |Dict| with the following
+ items:
+ autoload Always set to FALSE.
+ functions List of script-local function names defined in
+ the script. Present only when a particular
+ script is specified using the "sid" item in
+ {opts}.
+ name Vim script file name.
+ sid Script ID |<SID>|.
+ variables A dictionary with the script-local variables.
+ Present only when a particular script is
+ specified using the "sid" item in {opts}.
+ Note that this is a copy, the value of
+ script-local variables cannot be changed using
+ this dictionary.
+ version Vim script version, always 1
+
+ Examples: >vim
+ echo getscriptinfo({'name': 'myscript'})
+ echo getscriptinfo({'sid': 15}).variables
+<
+
+gettabinfo([{tabnr}]) *gettabinfo()*
If {tabnr} is not specified, then information about all the
tab pages is returned as a |List|. Each List item is a
|Dictionary|. Otherwise, {tabnr} specifies the tab page
@@ -3552,10 +2939,7 @@ gettabinfo([{tabnr}]) *gettabinfo()*
tabpage-local variables
windows List of |window-ID|s in the tab page.
- Can also be used as a |method|: >
- GetTabnr()->gettabinfo()
-
-gettabvar({tabnr}, {varname} [, {def}]) *gettabvar()*
+gettabvar({tabnr}, {varname} [, {def}]) *gettabvar()*
Get the value of a tab-local variable {varname} in tab page
{tabnr}. |t:var|
Tabs are numbered starting with one.
@@ -3565,10 +2949,7 @@ gettabvar({tabnr}, {varname} [, {def}]) *gettabvar()*
When the tab or variable doesn't exist {def} or an empty
string is returned, there is no error message.
- Can also be used as a |method|: >
- GetTabnr()->gettabvar(varname)
-
-gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()*
+gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()*
Get the value of window-local variable {varname} in window
{winnr} in tab page {tabnr}.
The {varname} argument is a string. When {varname} is empty a
@@ -3587,17 +2968,15 @@ gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()*
or buffer-local variable.
When the tab, window or variable doesn't exist {def} or an
empty string is returned, there is no error message.
- Examples: >
- :let list_is_on = gettabwinvar(1, 2, '&list')
- :echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
+ Examples: >vim
+ let list_is_on = gettabwinvar(1, 2, '&list')
+ echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
<
- To obtain all window-local variables use: >
+ To obtain all window-local variables use: >vim
gettabwinvar({tabnr}, {winnr}, '&')
+<
-< Can also be used as a |method|: >
- GetTabnr()->gettabwinvar(winnr, varname)
-
-gettagstack([{winnr}]) *gettagstack()*
+gettagstack([{winnr}]) *gettagstack()*
The result is a Dict, which is the tag stack of window {winnr}.
{winnr} can be the window number or the |window-ID|.
When {winnr} is not specified, the current window is used.
@@ -3625,11 +3004,7 @@ gettagstack([{winnr}]) *gettagstack()*
See |tagstack| for more information about the tag stack.
- Can also be used as a |method|: >
- GetWinnr()->gettagstack()
-
-
-gettext({text}) *gettext()*
+gettext({text}) *gettext()*
Translate String {text} if possible.
This is mainly for use in the distributed Vim scripts. When
generating message translations the {text} is extracted by
@@ -3640,8 +3015,7 @@ gettext({text}) *gettext()*
xgettext does not understand escaping in single quoted
strings.
-
-getwininfo([{winid}]) *getwininfo()*
+getwininfo([{winid}]) *getwininfo()*
Returns information about windows as a |List| with Dictionaries.
If {winid} is given Information about the window with that ID
@@ -3675,10 +3049,7 @@ getwininfo([{winid}]) *getwininfo()*
winrow topmost screen line of the window;
"row" from |win_screenpos()|
- Can also be used as a |method|: >
- GetWinnr()->getwininfo()
-
-getwinpos([{timeout}]) *getwinpos()*
+getwinpos([{timeout}]) *getwinpos()*
The result is a |List| with two numbers, the result of
|getwinposx()| and |getwinposy()| combined:
[x-pos, y-pos]
@@ -3689,7 +3060,7 @@ getwinpos([{timeout}]) *getwinpos()*
When using a value less than 10 and no response is received
within that time, a previously reported position is returned,
if available. This can be used to poll for the position and
- do some work in the meantime: >
+ do some work in the meantime: >vim
while 1
let res = getwinpos(1)
if res[0] >= 0
@@ -3698,31 +3069,26 @@ getwinpos([{timeout}]) *getwinpos()*
" Do some work here
endwhile
<
- Can also be used as a |method|: >
- GetTimeout()->getwinpos()
-<
- *getwinposx()*
-getwinposx() The result is a Number, which is the X coordinate in pixels of
+
+getwinposx() *getwinposx()*
+ The result is a Number, which is the X coordinate in pixels of
the left hand side of the GUI Vim window. The result will be
-1 if the information is not available.
The value can be used with `:winpos`.
- *getwinposy()*
-getwinposy() The result is a Number, which is the Y coordinate in pixels of
+getwinposy() *getwinposy()*
+ The result is a Number, which is the Y coordinate in pixels of
the top of the GUI Vim window. The result will be -1 if the
information is not available.
The value can be used with `:winpos`.
-getwinvar({winnr}, {varname} [, {def}]) *getwinvar()*
+getwinvar({winnr}, {varname} [, {def}]) *getwinvar()*
Like |gettabwinvar()| for the current tabpage.
- Examples: >
- :let list_is_on = getwinvar(2, '&list')
- :echo "myvar = " .. getwinvar(1, 'myvar')
+ Examples: >vim
+ let list_is_on = getwinvar(2, '&list')
+ echo "myvar = " .. getwinvar(1, 'myvar')
-< Can also be used as a |method|: >
- GetWinnr()->getwinvar(varname)
-<
-glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) *glob()*
+glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) *glob()*
Expand the file wildcards in {expr}. See |wildcards| for the
use of special characters.
@@ -3749,37 +3115,35 @@ glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) *glob()*
|TRUE| then all symbolic links are included.
For most systems backticks can be used to get files names from
- any external command. Example: >
- :let tagfiles = glob("`find . -name tags -print`")
- :let &tags = substitute(tagfiles, "\n", ",", "g")
+ any external command. Example: >vim
+ let tagfiles = glob("`find . -name tags -print`")
+ let &tags = substitute(tagfiles, "\n", ",", "g")
< The result of the program inside the backticks should be one
item per line. Spaces inside an item are allowed.
See |expand()| for expanding special Vim variables. See
|system()| for getting the raw output of an external command.
- Can also be used as a |method|: >
- GetExpr()->glob()
-
-glob2regpat({string}) *glob2regpat()*
+glob2regpat({string}) *glob2regpat()*
Convert a file pattern, as used by glob(), into a search
pattern. The result can be used to match with a string that
- is a file name. E.g. >
+ is a file name. E.g. >vim
if filename =~ glob2regpat('Make*.mak')
-< This is equivalent to: >
+ " ...
+ endif
+< This is equivalent to: >vim
if filename =~ '^Make.*\.mak$'
+ " ...
+ endif
< When {string} is an empty string the result is "^$", match an
empty string.
Note that the result depends on the system. On MS-Windows
a backslash usually means a path separator.
- Can also be used as a |method|: >
- GetExpr()->glob2regpat()
-< *globpath()*
-globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])
+globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]]) *globpath()*
Perform glob() for String {expr} on all directories in {path}
- and concatenate the results. Example: >
- :echo globpath(&rtp, "syntax/c.vim")
+ and concatenate the results. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim")
<
{path} is a comma-separated list of directory names. Each
directory name is prepended to {expr} and expanded like with
@@ -3799,35 +3163,31 @@ globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])
with all matching files. The advantage of using a List is, you
also get filenames containing newlines correctly. Otherwise
the result is a String and when there are several matches,
- they are separated by <NL> characters. Example: >
- :echo globpath(&rtp, "syntax/c.vim", 0, 1)
+ they are separated by <NL> characters. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim", 0, 1)
<
{allinks} is used as with |glob()|.
The "**" item can be used to search in a directory tree.
For example, to find all "README.txt" files in the directories
- in 'runtimepath' and below: >
- :echo globpath(&rtp, "**/README.txt")
+ in 'runtimepath' and below: >vim
+ echo globpath(&rtp, "**/README.txt")
< Upwards search and limiting the depth of "**" is not
supported, thus using 'path' will not always work properly.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetExpr()->globpath(&rtp)
-<
- *has()*
-has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
+has({feature}) *has()*
+ Returns 1 if {feature} is supported, 0 otherwise. The
{feature} argument is a feature name like "nvim-0.2.1" or
"win32", see below. See also |exists()|.
- To get the system name use |vim.loop|.os_uname() in Lua: >
- :lua print(vim.loop.os_uname().sysname)
+ To get the system name use |vim.uv|.os_uname() in Lua: >lua
+ print(vim.uv.os_uname().sysname)
< If the code has a syntax error then Vimscript may skip the
rest of the line. Put |:if| and |:endif| on separate lines to
- avoid the syntax error: >
+ avoid the syntax error: >vim
if has('feature')
- let x = this->breaks->without->the->feature
+ let x = this_breaks_without_the_feature()
endif
<
Vim's compile-time feature-names (prefixed with "+") are not
@@ -3836,12 +3196,16 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
Feature names can be:
1. Nvim version. For example the "nvim-0.2.1" feature means
- that Nvim is version 0.2.1 or later: >
- :if has("nvim-0.2.1")
+ that Nvim is version 0.2.1 or later: >vim
+ if has("nvim-0.2.1")
+ " ...
+ endif
< 2. Runtime condition or other pseudo-feature. For example the
- "win32" feature checks if the current system is Windows: >
- :if has("win32")
+ "win32" feature checks if the current system is Windows: >vim
+ if has("win32")
+ " ...
+ endif
< *feature-list*
List of supported pseudo-feature names:
acl |ACL| support.
@@ -3849,6 +3213,7 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
clipboard |clipboard| provider is available.
fname_case Case in file names matters (for Darwin and MS-Windows
this is not present).
+ gui_running Nvim has a GUI.
iconv Can use |iconv()| for conversion.
linux Linux system.
mac MacOS system.
@@ -3866,43 +3231,41 @@ has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The
*has-patch*
3. Vim patch. For example the "patch123" feature means that
- Vim patch 123 at the current |v:version| was included: >
- :if v:version > 602 || v:version == 602 && has("patch148")
+ Vim patch 123 at the current |v:version| was included: >vim
+ if v:version > 602 || v:version == 602 && has("patch148")
+ " ...
+ endif
< 4. Vim version. For example the "patch-7.4.237" feature means
- that Nvim is Vim-compatible to version 7.4.237 or later. >
- :if has("patch-7.4.237")
-
+ that Nvim is Vim-compatible to version 7.4.237 or later. >vim
+ if has("patch-7.4.237")
+ " ...
+ endif
+<
-has_key({dict}, {key}) *has_key()*
+has_key({dict}, {key}) *has_key()*
The result is a Number, which is TRUE if |Dictionary| {dict}
has an entry with key {key}. FALSE otherwise. The {key}
argument is a string.
- 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.
Tabs and windows are identified by their respective numbers,
0 means current tab or window. Missing argument implies 0.
- Thus the following are equivalent: >
- haslocaldir()
- haslocaldir(0)
- haslocaldir(0, 0)
+ Thus the following are equivalent: >vim
+ echo haslocaldir()
+ echo haslocaldir(0)
+ echo haslocaldir(0, 0)
< With {winnr} use that window in the current tabpage.
With {winnr} and {tabnr} use the window in that tabpage.
{winnr} can be the window number or the |window-ID|.
If {winnr} is -1 it is ignored, only the tab is resolved.
Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
- Can also be used as a |method|: >
- GetWinnr()->haslocaldir()
-
-hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()*
+hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()*
The result is a Number, which is TRUE if there is a mapping
that contains {what} in somewhere in the rhs (what it is
mapped to) and this mapping exists in one of the modes
@@ -3926,17 +3289,14 @@ hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()*
When {mode} is omitted, "nvo" is used.
This function is useful to check if a mapping already exists
- to a function in a Vim script. Example: >
- :if !hasmapto('\ABCdoit')
- : map <Leader>d \ABCdoit
- :endif
+ to a function in a Vim script. Example: >vim
+ if !hasmapto('\ABCdoit')
+ map <Leader>d \ABCdoit
+ endif
< This installs the mapping to "\ABCdoit" only if there isn't
already a mapping to "\ABCdoit".
- Can also be used as a |method|: >
- GetRHS()->hasmapto()
-
-histadd({history}, {item}) *histadd()*
+histadd({history}, {item}) *histadd()*
Add the String {item} to the history {history} which can be
one of: *hist-names*
"cmd" or ":" command line history
@@ -3952,16 +3312,12 @@ histadd({history}, {item}) *histadd()*
The result is a Number: TRUE if the operation was successful,
otherwise FALSE is returned.
- Example: >
- :call histadd("input", strftime("%Y %b %d"))
- :let date=input("Enter date: ")
+ Example: >vim
+ call histadd("input", strftime("%Y %b %d"))
+ let date=input("Enter date: ")
< This function is not available in the |sandbox|.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetHistory()->histadd('search')
-
-histdel({history} [, {item}]) *histdel()*
+histdel({history} [, {item}]) *histdel()*
Clear {history}, i.e. delete all its entries. See |hist-names|
for the possible values of {history}.
@@ -3977,26 +3333,24 @@ histdel({history} [, {item}]) *histdel()*
is returned.
Examples:
- Clear expression register history: >
- :call histdel("expr")
+ Clear expression register history: >vim
+ call histdel("expr")
<
- Remove all entries starting with "*" from the search history: >
- :call histdel("/", '^\*')
+ Remove all entries starting with "*" from the search history: >vim
+ call histdel("/", '^\*')
<
- The following three are equivalent: >
- :call histdel("search", histnr("search"))
- :call histdel("search", -1)
- :call histdel("search", '^' .. histget("search", -1) .. '$')
+ The following three are equivalent: >vim
+ call histdel("search", histnr("search"))
+ call histdel("search", -1)
+ call histdel("search", '^' .. histget("search", -1) .. '$')
<
To delete the last search pattern and use the last-but-one for
- the "n" command and 'hlsearch': >
- :call histdel("search", -1)
- :let @/ = histget("search", -1)
+ the "n" command and 'hlsearch': >vim
+ call histdel("search", -1)
+ let @/ = histget("search", -1)
<
- Can also be used as a |method|: >
- GetHistory()->histdel()
-histget({history} [, {index}]) *histget()*
+histget({history} [, {index}]) *histget()*
The result is a String, the entry with Number {index} from
{history}. See |hist-names| for the possible values of
{history}, and |:history-indexing| for {index}. If there is
@@ -4004,55 +3358,45 @@ histget({history} [, {index}]) *histget()*
omitted, the most recent item from the history is used.
Examples:
- Redo the second last search from history. >
- :execute '/' .. histget("search", -2)
+ Redo the second last search from history. >vim
+ execute '/' .. histget("search", -2)
< Define an Ex command ":H {num}" that supports re-execution of
- the {num}th entry from the output of |:history|. >
- :command -nargs=1 H execute histget("cmd", 0+<args>)
+ the {num}th entry from the output of |:history|. >vim
+ command -nargs=1 H execute histget("cmd", 0+<args>)
<
- Can also be used as a |method|: >
- GetHistory()->histget()
-histnr({history}) *histnr()*
+histnr({history}) *histnr()*
The result is the Number of the current entry in {history}.
See |hist-names| for the possible values of {history}.
If an error occurred, -1 is returned.
- Example: >
- :let inp_index = histnr("expr")
+ Example: >vim
+ let inp_index = histnr("expr")
-< Can also be used as a |method|: >
- GetHistory()->histnr()
+hlID({name}) *hlID()*
+ The result is a Number, which is the ID of the highlight group
+ with name {name}. When the highlight group doesn't exist,
+ zero is returned.
+ This can be used to retrieve information about the highlight
+ group. For example, to get the background color of the
+ "Comment" group: >vim
+ echo synIDattr(synIDtrans(hlID("Comment")), "bg")
<
-hlexists({name}) *hlexists()*
+
+hlexists({name}) *hlexists()*
The result is a Number, which is TRUE if a highlight group
called {name} exists. This is when the group has been
defined in some way. Not necessarily when highlighting has
been defined for it, it may also have been used for a syntax
item.
- Can also be used as a |method|: >
- GetName()->hlexists()
-<
- *hlID()*
-hlID({name}) The result is a Number, which is the ID of the highlight group
- with name {name}. When the highlight group doesn't exist,
- zero is returned.
- This can be used to retrieve information about the highlight
- group. For example, to get the background color of the
- "Comment" group: >
- :echo synIDattr(synIDtrans(hlID("Comment")), "bg")
-<
- Can also be used as a |method|: >
- GetName()->hlID()
-
-hostname() *hostname()*
+hostname() *hostname()*
The result is a String, which is the name of the machine on
which Vim is currently running. Machine names greater than
256 characters long are truncated.
-iconv({string}, {from}, {to}) *iconv()*
+iconv({string}, {from}, {to}) *iconv()*
The result is a String, which is the text {string} converted
from encoding {from} to encoding {to}.
When the conversion completely fails an empty string is
@@ -4064,42 +3408,97 @@ iconv({string}, {from}, {to}) *iconv()*
from/to UCS-2 is automatically changed to use UTF-8. You
cannot use UCS-2 in a string anyway, because of the NUL bytes.
- Can also be used as a |method|: >
- GetText()->iconv('latin1', 'utf-8')
-<
- *indent()*
-indent({lnum}) The result is a Number, which is indent of line {lnum} in the
+id({expr}) *id()*
+ Returns a |String| which is a unique identifier of the
+ container type (|List|, |Dict|, |Blob| and |Partial|). It is
+ guaranteed that for the mentioned types `id(v1) ==# id(v2)`
+ returns true iff `type(v1) == type(v2) && v1 is v2`.
+ Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and
+ `v:_null_blob` have the same `id()` with different types
+ because they are internally represented as NULL pointers.
+ `id()` returns a hexadecimal representanion of the pointers to
+ the containers (i.e. like `0x994a40`), same as `printf("%p",
+ {expr})`, but it is advised against counting on the exact
+ format of the return value.
+
+ It is not guaranteed that `id(no_longer_existing_container)`
+ will not be equal to some other `id()`: new containers may
+ reuse identifiers of the garbage-collected ones.
+
+indent({lnum}) *indent()*
+ The result is a Number, which is indent of line {lnum} in the
current buffer. The indent is counted in spaces, the value
of 'tabstop' is relevant. {lnum} is used just like in
|getline()|.
When {lnum} is invalid -1 is returned.
- Can also be used as a |method|: >
- GetLnum()->indent()
+index({object}, {expr} [, {start} [, {ic}]]) *index()*
+ Find {expr} in {object} and return its index. See
+ |indexof()| for using a lambda to select the item.
-index({object}, {expr} [, {start} [, {ic}]]) *index()*
If {object} is a |List| return the lowest index where the item
has a value equal to {expr}. There is no automatic
conversion, so the String "4" is different from the Number 4.
And the Number 4 is different from the Float 4.0. The value
- of 'ignorecase' is not used here, case always matters.
+ of 'ignorecase' is not used here, case matters as indicated by
+ the {ic} argument.
If {object} is a |Blob| return the lowest index where the byte
value is equal to {expr}.
If {start} is given then start looking at the item with index
{start} (may be negative for an item relative to the end).
+
When {ic} is given and it is |TRUE|, ignore case. Otherwise
case must match.
+
-1 is returned when {expr} is not found in {object}.
- Example: >
- :let idx = index(words, "the")
- :if index(numbers, 123) >= 0
+ Example: >vim
+ let idx = index(words, "the")
+ if index(numbers, 123) >= 0
+ " ...
+ endif
+
+indexof({object}, {expr} [, {opts}]) *indexof()*
+ Returns the index of an item in {object} where {expr} is
+ v:true. {object} must be a |List| or a |Blob|.
-< Can also be used as a |method|: >
- GetObject()->index(what)
+ If {object} is a |List|, evaluate {expr} for each item in the
+ List until the expression is v:true and return the index of
+ this item.
+
+ If {object} is a |Blob| evaluate {expr} for each byte in the
+ Blob until the expression is v:true and return the index of
+ this byte.
+
+ {expr} must be a |string| or |Funcref|.
+
+ If {expr} is a |string|: If {object} is a |List|, inside
+ {expr} |v:key| has the index of the current List item and
+ |v:val| has the value of the item. If {object} is a |Blob|,
+ inside {expr} |v:key| has the index of the current byte and
+ |v:val| has the byte value.
+
+ If {expr} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ The function must return |TRUE| if the item is found and the
+ search should stop.
+
+ The optional argument {opts} is a Dict and supports the
+ following items:
+ startidx start evaluating {expr} at the item with this
+ index; may be negative for an item relative to
+ the end
+ Returns -1 when {expr} evaluates to v:false for all the items.
+ Example: >vim
+ let l = [#{n: 10}, #{n: 20}, #{n: 30}]
+ echo indexof(l, "v:val.n == 20")
+ echo indexof(l, {i, v -> v.n == 30})
+ echo indexof(l, "v:val.n == 20", #{startidx: 1})
+
+input({prompt} [, {text} [, {completion}]]) *input()*
-input({prompt} [, {text} [, {completion}]]) *input()*
input({opts})
The result is a String, which is whatever the user typed on
the command-line. The {prompt} argument is either a prompt
@@ -4121,22 +3520,22 @@ input({opts})
The input is entered just like a command-line, with the same
editing commands and mappings. There is a separate history
for lines typed for input().
- Example: >
- :if input("Coffee or beer? ") == "beer"
- : echo "Cheers!"
- :endif
+ Example: >vim
+ if input("Coffee or beer? ") == "beer"
+ echo "Cheers!"
+ endif
<
If the optional {text} argument is present and not empty, this
is used for the default reply, as if the user typed this.
- Example: >
- :let color = input("Color? ", "white")
+ Example: >vim
+ let color = input("Color? ", "white")
< The optional {completion} argument specifies the type of
completion supported for the input. Without it completion is
not performed. The supported completion types are the same as
that can be supplied to a user-defined command using the
"-complete=" argument. Refer to |:command-completion| for
- more information. Example: >
+ more information. Example: >vim
let fname = input("File: ", "", "file")
< *input()-highlight* *E5400* *E5402*
@@ -4157,7 +3556,7 @@ input({opts})
sections must be ordered so that next hl_start_col is greater
then or equal to previous hl_end_col.
- Example (try some input with parentheses): >
+ Example (try some input with parentheses): >vim
highlight RBP1 guibg=Red ctermbg=red
highlight RBP2 guibg=Yellow ctermbg=yellow
highlight RBP3 guibg=Green ctermbg=green
@@ -4202,18 +3601,15 @@ input({opts})
that further characters follow in the mapping, e.g., by using
|:execute| or |:normal|.
- Example with a mapping: >
- :nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR>
- :function GetFoo()
- : call inputsave()
- : let g:Foo = input("enter search pattern: ")
- : call inputrestore()
- :endfunction
-
-< Can also be used as a |method|: >
- GetPrompt()->input()
+ Example with a mapping: >vim
+ nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR>
+ function GetFoo()
+ call inputsave()
+ let g:Foo = input("enter search pattern: ")
+ call inputrestore()
+ endfunction
-inputlist({textlist}) *inputlist()*
+inputlist({textlist}) *inputlist()*
{textlist} must be a |List| of strings. This |List| is
displayed, one string per line. The user will be prompted to
enter a number, which is returned.
@@ -4226,20 +3622,17 @@ inputlist({textlist}) *inputlist()*
Make sure {textlist} has less than 'lines' entries, otherwise
it won't work. It's a good idea to put the entry number at
the start of the string. And put a prompt in the first item.
- Example: >
+ Example: >vim
let color = inputlist(['Select color:', '1. red',
\ '2. green', '3. blue'])
-< Can also be used as a |method|: >
- GetChoices()->inputlist()
-
-inputrestore() *inputrestore()*
+inputrestore() *inputrestore()*
Restore typeahead that was saved with a previous |inputsave()|.
Should be called the same number of times inputsave() is
called. Calling it more often is harmless though.
Returns TRUE when there is nothing to restore, FALSE otherwise.
-inputsave() *inputsave()*
+inputsave() *inputsave()*
Preserve typeahead (also from mappings) and clear it, so that
a following prompt gets input from the user. Should be
followed by a matching inputrestore() after the prompt. Can
@@ -4247,7 +3640,7 @@ inputsave() *inputsave()*
many inputrestore() calls.
Returns TRUE when out of memory, FALSE otherwise.
-inputsecret({prompt} [, {text}]) *inputsecret()*
+inputsecret({prompt} [, {text}]) *inputsecret()*
This function acts much like the |input()| function with but
two exceptions:
a) the user's response will be displayed as a sequence of
@@ -4258,10 +3651,7 @@ inputsecret({prompt} [, {text}]) *inputsecret()*
typed on the command-line in response to the issued prompt.
NOTE: Command-line completion is not supported.
- Can also be used as a |method|: >
- GetPrompt()->inputsecret()
-
-insert({object}, {item} [, {idx}]) *insert()*
+insert({object}, {item} [, {idx}]) *insert()*
When {object} is a |List| or a |Blob| insert {item} at the start
of it.
@@ -4270,129 +3660,96 @@ insert({object}, {item} [, {idx}]) *insert()*
like omitting {idx}. A negative {idx} is also possible, see
|list-index|. -1 inserts just before the last item.
- Returns the resulting |List| or |Blob|. Examples: >
- :let mylist = insert([2, 3, 5], 1)
- :call insert(mylist, 4, -1)
- :call insert(mylist, 6, len(mylist))
+ Returns the resulting |List| or |Blob|. Examples: >vim
+ let mylist = insert([2, 3, 5], 1)
+ call insert(mylist, 4, -1)
+ call insert(mylist, 6, len(mylist))
< The last example can be done simpler with |add()|.
Note that when {item} is a |List| it is inserted as a single
item. Use |extend()| to concatenate |Lists|.
- Can also be used as a |method|: >
- mylist->insert(item)
-
-interrupt() *interrupt()*
+interrupt() *interrupt()*
Interrupt script execution. It works more or less like the
user typing CTRL-C, most commands won't execute and control
returns to the user. This is useful to abort execution
- from lower down, e.g. in an autocommand. Example: >
- :function s:check_typoname(file)
- : if fnamemodify(a:file, ':t') == '['
- : echomsg 'Maybe typo'
- : call interrupt()
- : endif
- :endfunction
- :au BufWritePre * call s:check_typoname(expand('<amatch>'))
-
-invert({expr}) *invert()*
+ from lower down, e.g. in an autocommand. Example: >vim
+ function s:check_typoname(file)
+ if fnamemodify(a:file, ':t') == '['
+ echomsg 'Maybe typo'
+ call interrupt()
+ endif
+ endfunction
+ au BufWritePre * call s:check_typoname(expand('<amatch>'))
+<
+
+invert({expr}) *invert()*
Bitwise invert. The argument is converted to a number. A
- List, Dict or Float argument causes an error. Example: >
- :let bits = invert(bits)
-< Can also be used as a |method|: >
- :let bits = bits->invert()
+ List, Dict or Float argument causes an error. Example: >vim
+ let bits = invert(bits)
+<
-isdirectory({directory}) *isdirectory()*
+isdirectory({directory}) *isdirectory()*
The result is a Number, which is |TRUE| when a directory
with the name {directory} exists. If {directory} doesn't
exist, or isn't a directory, the result is |FALSE|. {directory}
is any expression, which is used as a String.
- Can also be used as a |method|: >
- GetName()->isdirectory()
-
-isinf({expr}) *isinf()*
+isinf({expr}) *isinf()*
Return 1 if {expr} is a positive infinity, or -1 a negative
- infinity, otherwise 0. >
- :echo isinf(1.0 / 0.0)
-< 1 >
- :echo isinf(-1.0 / 0.0)
+ infinity, otherwise 0. >vim
+ echo isinf(1.0 / 0.0)
+< 1 >vim
+ echo isinf(-1.0 / 0.0)
< -1
- Can also be used as a |method|: >
- Compute()->isinf()
-
-islocked({expr}) *islocked()* *E786*
+islocked({expr}) *islocked()* *E786*
The result is a Number, which is |TRUE| when {expr} is the
name of a locked variable.
The string argument {expr} must be the name of a variable,
|List| item or |Dictionary| entry, not the variable itself!
- Example: >
- :let alist = [0, ['a', 'b'], 2, 3]
- :lockvar 1 alist
- :echo islocked('alist') " 1
- :echo islocked('alist[1]') " 0
+ Example: >vim
+ let alist = [0, ['a', 'b'], 2, 3]
+ lockvar 1 alist
+ echo islocked('alist') " 1
+ echo islocked('alist[1]') " 0
< When {expr} is a variable that does not exist you get an error
message. Use |exists()| to check for existence.
- Can also be used as a |method|: >
- GetName()->islocked()
-
-id({expr}) *id()*
- Returns a |String| which is a unique identifier of the
- container type (|List|, |Dict|, |Blob| and |Partial|). It is
- guaranteed that for the mentioned types `id(v1) ==# id(v2)`
- returns true iff `type(v1) == type(v2) && v1 is v2`.
- Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and
- |v:_null_blob| have the same `id()` with different types
- because they are internally represented as NULL pointers.
- `id()` returns a hexadecimal representanion of the pointers to
- the containers (i.e. like `0x994a40`), same as `printf("%p",
- {expr})`, but it is advised against counting on the exact
- format of the return value.
-
- It is not guaranteed that `id(no_longer_existing_container)`
- will not be equal to some other `id()`: new containers may
- reuse identifiers of the garbage-collected ones.
+isnan({expr}) *isnan()*
+ Return |TRUE| if {expr} is a float with value NaN. >vim
+ echo isnan(0.0 / 0.0)
+< 1
-items({dict}) *items()*
+items({dict}) *items()*
Return a |List| with all the key-value pairs of {dict}. Each
|List| item is a list with two items: the key of a {dict}
entry and the value of this entry. The |List| is in arbitrary
order. Also see |keys()| and |values()|.
- Example: >
+ Example: >vim
for [key, value] in items(mydict)
echo key .. ': ' .. value
endfor
-< Can also be used as a |method|: >
- mydict->items()
-
-isnan({expr}) *isnan()*
- Return |TRUE| if {expr} is a float with value NaN. >
- echo isnan(0.0 / 0.0)
-< 1
-
- Can also be used as a |method|: >
- Compute()->isnan()
-
-jobpid({job}) *jobpid()*
+jobpid({job}) *jobpid()*
Return the PID (process id) of |job-id| {job}.
-jobresize({job}, {width}, {height}) *jobresize()*
+jobresize({job}, {width}, {height}) *jobresize()*
Resize the pseudo terminal window of |job-id| {job} to {width}
columns and {height} rows.
Fails if the job was not started with `"pty":v:true`.
-jobstart({cmd} [, {opts}]) *jobstart()*
+jobstart({cmd} [, {opts}]) *jobstart()*
+ Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+
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: >
- :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+ If {cmd} is a String it runs in the 'shell', like this: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
< (See |shell-unquoting| for details.)
- Example: >
- :call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+ Example: >vim
+ call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
<
Returns |job-id| on success, 0 on invalid arguments (or job
table is full), -1 if {cmd}[0] or 'shell' is not executable.
@@ -4405,10 +3762,10 @@ jobstart({cmd} [, {opts}]) *jobstart()*
NOTE: on Windows if {cmd} is a List:
- cmd[0] must be an executable (not a "built-in"). If it is
- in $PATH it can be called by name, without an extension: >
- :call jobstart(['ping', 'neovim.io'])
-< If it is a full or partial path, extension is required: >
- :call jobstart(['System32\ping.exe', 'neovim.io'])
+ in $PATH it can be called by name, without an extension: >vim
+ call jobstart(['ping', 'neovim.io'])
+< If it is a full or partial path, extension is required: >vim
+ call jobstart(['System32\ping.exe', 'neovim.io'])
< - {cmd} is collapsed to a string of quoted args as expected
by CommandLineToArgvW https://msdn.microsoft.com/bb776391
unless cmd[0] is some form of "cmd.exe".
@@ -4440,11 +3797,9 @@ jobstart({cmd} [, {opts}]) *jobstart()*
stdout data.
|on_stderr|: (function) Callback invoked when the job emits
stderr data.
- overlapped: (boolean) Set FILE_FLAG_OVERLAPPED for the
- standard input/output passed to the child process.
- Normally you do not need to set this.
- (Only available on MS-Windows, On other
- platforms, this option is silently ignored.)
+ overlapped: (boolean) Sets FILE_FLAG_OVERLAPPED for the
+ stdio passed to the child process. Only on
+ MS-Windows; ignored on other platforms.
pty: (boolean) Connect the job to a new pseudo
terminal, and its streams to the master file
descriptor. `on_stdout` receives all output,
@@ -4470,7 +3825,7 @@ jobstart({cmd} [, {opts}]) *jobstart()*
- -1 if {cmd}[0] is not executable.
See also |job-control|, |channel|, |msgpack-rpc|.
-jobstop({id}) *jobstop()*
+jobstop({id}) *jobstop()*
Stop |job-id| {id} by sending SIGTERM to the job process. If
the process does not terminate after a timeout then SIGKILL
will be sent. When the job terminates its |on_exit| handler
@@ -4480,14 +3835,14 @@ 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.
{timeout} is the maximum waiting time in milliseconds. If
omitted or -1, wait forever.
- Timeout of 0 can be used to check the status of a job: >
+ Timeout of 0 can be used to check the status of a job: >vim
let running = jobwait([{job-id}], 0)[0] == -1
<
During jobwait() callbacks for jobs not in the {jobs} list may
@@ -4501,21 +3856,18 @@ jobwait({jobs} [, {timeout}]) *jobwait()*
-2 if the job was interrupted (by |CTRL-C|)
-3 if the job-id is invalid
-join({list} [, {sep}]) *join()*
+join({list} [, {sep}]) *join()*
Join the items in {list} together into one String.
When {sep} is specified it is put in between the items. If
{sep} is omitted a single space is used.
Note that {sep} is not added at the end. You might want to
- add it there too: >
+ add it there too: >vim
let lines = join(mylist, "\n") .. "\n"
< String items are used as-is. |Lists| and |Dictionaries| are
converted into a string like with |string()|.
The opposite function is |split()|.
- Can also be used as a |method|: >
- mylist->join()
-
-json_decode({expr}) *json_decode()*
+json_decode({expr}) *json_decode()*
Convert {expr} from JSON object. Accepts |readfile()|-style
list as the input, as well as regular string. May output any
Vim value. In the following cases it will output
@@ -4531,10 +3883,7 @@ json_decode({expr}) *json_decode()*
recommended and the only one required to be supported.
Non-UTF-8 characters are an error.
- Can also be used as a |method|: >
- ReadObject()->json_decode()
-
-json_encode({expr}) *json_encode()*
+json_encode({expr}) *json_encode()*
Convert {expr} into a JSON string. Accepts
|msgpack-special-dict| as the input. Will not convert
|Funcref|s, mappings with non-string keys (can be created as
@@ -4546,28 +3895,19 @@ json_encode({expr}) *json_encode()*
or special escapes like "\t", other are dumped as-is.
|Blob|s are converted to arrays of the individual bytes.
- Can also be used as a |method|: >
- GetObject()->json_encode()
-
-keys({dict}) *keys()*
+keys({dict}) *keys()*
Return a |List| with all the keys of {dict}. The |List| is in
arbitrary order. Also see |items()| and |values()|.
- Can also be used as a |method|: >
- mydict->keys()
-
-keytrans({string}) *keytrans()*
+keytrans({string}) *keytrans()*
Turn the internal byte representation of keys into a form that
- can be used for |:map|. E.g. >
- :let xx = "\<C-Home>"
- :echo keytrans(xx)
+ can be used for |:map|. E.g. >vim
+ let xx = "\<C-Home>"
+ echo keytrans(xx)
< <C-Home>
- Can also be used as a |method|: >
- "\<C-Home>"->keytrans()
-
-< *len()* *E701*
-len({expr}) The result is a Number, which is the length of the argument.
+len({expr}) *len()* *E701*
+ The result is a Number, which is the length of the argument.
When {expr} is a String or a Number the length in bytes is
used, as with |strlen()|.
When {expr} is a |List| the number of items in the |List| is
@@ -4577,11 +3917,7 @@ len({expr}) The result is a Number, which is the length of the argument.
|Dictionary| is returned.
Otherwise an error is given and returns zero.
- Can also be used as a |method|: >
- mylist->len()
-
-< *libcall()* *E364* *E368*
-libcall({libname}, {funcname}, {argument})
+libcall({libname}, {funcname}, {argument}) *libcall()* *E364* *E368*
Call function {funcname} in the run-time library {libname}
with single argument {argument}.
This is useful to call functions in a library that you
@@ -4620,27 +3956,19 @@ libcall({libname}, {funcname}, {argument})
the DLL is not in the usual places.
For Unix: When compiling your own plugins, remember that the
object code must be compiled as position-independent ('PIC').
- Examples: >
- :echo libcall("libc.so", "getenv", "HOME")
+ Examples: >vim
+ echo libcall("libc.so", "getenv", "HOME")
-< Can also be used as a |method|, the base is passed as the
- third argument: >
- GetValue()->libcall("libc.so", "getenv")
-<
- *libcallnr()*
-libcallnr({libname}, {funcname}, {argument})
+libcallnr({libname}, {funcname}, {argument}) *libcallnr()*
Just like |libcall()|, but used for a function that returns an
int instead of a string.
- Examples: >
- :echo libcallnr("/usr/lib/libc.so", "getpid", "")
- :call libcallnr("libc.so", "printf", "Hello World!\n")
- :call libcallnr("libc.so", "sleep", 10)
-<
- Can also be used as a |method|, the base is passed as the
- third argument: >
- GetValue()->libcallnr("libc.so", "printf")
+ Examples: >vim
+ echo libcallnr("/usr/lib/libc.so", "getpid", "")
+ call libcallnr("libc.so", "printf", "Hello World!\n")
+ call libcallnr("libc.so", "sleep", 10)
<
-line({expr} [, {winid}]) *line()*
+
+line({expr} [, {winid}]) *line()*
The result is a Number, which is the line number of the file
position given with {expr}. The {expr} argument is a string.
The accepted positions are:
@@ -4663,116 +3991,110 @@ line({expr} [, {winid}]) *line()*
With the optional {winid} argument the values are obtained for
that window instead of the current window.
Returns 0 for invalid values of {expr} and {winid}.
- Examples: >
- line(".") line number of the cursor
- line(".", winid) idem, in window "winid"
- line("'t") line number of mark t
- line("'" .. marker) line number of mark marker
+ Examples: >vim
+ echo line(".") " line number of the cursor
+ echo line(".", winid) " idem, in window "winid"
+ echo line("'t") " line number of mark t
+ echo 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()
-
-line2byte({lnum}) *line2byte()*
+line2byte({lnum}) *line2byte()*
Return the byte count from the start of the buffer for line
{lnum}. This includes the end-of-line character, depending on
the 'fileformat' option for the current buffer. The first
line returns 1. UTF-8 encoding is used, 'fileencoding' is
ignored. This can also be used to get the byte count for the
- line just below the last line: >
- line2byte(line("$") + 1)
+ line just below the last line: >vim
+ echo line2byte(line("$") + 1)
< This is the buffer size plus one. If 'fileencoding' is empty
it is the file size plus one. {lnum} is used like with
|getline()|. When {lnum} is invalid -1 is returned.
Also see |byte2line()|, |go| and |:goto|.
- Can also be used as a |method|: >
- GetLnum()->line2byte()
-
-lispindent({lnum}) *lispindent()*
+lispindent({lnum}) *lispindent()*
Get the amount of indent for line {lnum} according the lisp
indenting rules, as with 'lisp'.
The indent is counted in spaces, the value of 'tabstop' is
relevant. {lnum} is used just like in |getline()|.
When {lnum} is invalid, -1 is returned.
- Can also be used as a |method|: >
- GetLnum()->lispindent()
+list2blob({list}) *list2blob()*
+ Return a Blob concatenating all the number values in {list}.
+ Examples: >vim
+ echo list2blob([1, 2, 3, 4]) " returns 0z01020304
+ echo list2blob([]) " returns 0z
+< Returns an empty Blob on error. If one of the numbers is
+ negative or more than 255 error *E1239* is given.
+
+ |blob2list()| does the opposite.
-list2str({list} [, {utf8}]) *list2str()*
+list2str({list} [, {utf8}]) *list2str()*
Convert each number in {list} to a character string can
- concatenate them all. Examples: >
- list2str([32]) returns " "
- list2str([65, 66, 67]) returns "ABC"
-< The same can be done (slowly) with: >
- join(map(list, {nr, val -> nr2char(val)}), '')
+ concatenate them all. Examples: >vim
+ echo list2str([32]) " returns " "
+ echo list2str([65, 66, 67]) " returns "ABC"
+< The same can be done (slowly) with: >vim
+ echo join(map(list, {nr, val -> nr2char(val)}), '')
< |str2list()| does the opposite.
UTF-8 encoding is always used, {utf8} option has no effect,
and exists only for backwards-compatibility.
- With UTF-8 composing characters work as expected: >
- list2str([97, 769]) returns "á"
+ With UTF-8 composing characters work as expected: >vim
+ echo list2str([97, 769]) " returns "á"
<
Returns an empty string on error.
- Can also be used as a |method|: >
- GetList()->list2str()
-
-localtime() *localtime()*
+localtime() *localtime()*
Return the current time, measured as seconds since 1st Jan
1970. See also |strftime()|, |strptime()| and |getftime()|.
-
-log({expr}) *log()*
+log({expr}) *log()*
Return the natural logarithm (base e) of {expr} as a |Float|.
{expr} must evaluate to a |Float| or a |Number| in the range
(0, inf].
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo log(10)
-< 2.302585 >
- :echo log(exp(5))
+ Examples: >vim
+ echo log(10)
+< 2.302585 >vim
+ echo log(exp(5))
< 5.0
- Can also be used as a |method|: >
- Compute()->log()
-
-log10({expr}) *log10()*
+log10({expr}) *log10()*
Return the logarithm of Float {expr} to base 10 as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo log10(1000)
-< 3.0 >
- :echo log10(0.01)
+ Examples: >vim
+ echo log10(1000)
+< 3.0 >vim
+ echo log10(0.01)
< -2.0
- Can also be used as a |method|: >
- Compute()->log10()
-
-luaeval({expr} [, {expr}])
+luaeval({expr} [, {expr}]) *luaeval()*
Evaluate Lua expression {expr} and return its result converted
to Vim data structures. See |lua-eval| for more details.
- Can also be used as a |method|: >
- GetExpr()->luaeval()
-
-map({expr1}, {expr2}) *map()*
- {expr1} must be a |List|, |Blob| or |Dictionary|.
- Replace each item in {expr1} with the result of evaluating
- {expr2}. For a |Blob| each byte is replaced.
+map({expr1}, {expr2}) *map()*
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ When {expr1} is a |List|| or |Dictionary|, replace each
+ item in {expr1} with the result of evaluating {expr2}.
+ For a |Blob| each byte is replaced.
+ For a |String|, each character, including composing
+ characters, is replaced.
+ If the item type changes you may want to use |mapnew()| to
+ create a new List or Dictionary.
- {expr2} must be a |string| or |Funcref|.
+ {expr2} must be a |String| or |Funcref|.
- If {expr2} is a |string|, inside {expr2} |v:val| has the value
+ If {expr2} is a |String|, inside {expr2} |v:val| has the value
of the current item. For a |Dictionary| |v:key| has the key
of the current item and for a |List| |v:key| has the index of
the current item. For a |Blob| |v:key| has the index of the
- current byte.
- Example: >
- :call map(mylist, '"> " .. v:val .. " <"')
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Example: >vim
+ call map(mylist, '"> " .. v:val .. " <"')
< This puts "> " before and " <" after each item in "mylist".
Note that {expr2} is the result of an expression and is then
@@ -4784,37 +4106,35 @@ map({expr1}, {expr2}) *map()*
1. The key or the index of the current item.
2. the value of the current item.
The function must return the new value of the item. Example
- that changes each value by "key-value": >
+ that changes each value by "key-value": >vim
func KeyValue(key, val)
return a:key .. '-' .. a:val
endfunc
call map(myDict, function('KeyValue'))
-< It is shorter when using a |lambda|: >
+< It is shorter when using a |lambda|: >vim
call map(myDict, {key, val -> key .. '-' .. val})
-< If you do not use "val" you can leave it out: >
+< If you do not use "val" you can leave it out: >vim
call map(myDict, {key -> 'item: ' .. key})
-< If you do not use "key" you can use a short name: >
+< If you do not use "key" you can use a short name: >vim
call map(myDict, {_, val -> 'item: ' .. val})
<
- The operation is done in-place. If you want a |List| or
- |Dictionary| to remain unmodified make a copy first: >
- :let tlist = map(copy(mylist), ' v:val .. "\t"')
+ The operation is done in-place for a |List| and |Dictionary|.
+ If you want it to remain unmodified make a copy first: >vim
+ let tlist = map(copy(mylist), ' v:val .. "\t"')
-< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was
- filtered. When an error is encountered while evaluating
- {expr2} no further items in {expr1} are processed. When
- {expr2} is a Funcref errors inside a function are ignored,
+< Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
unless it was defined with the "abort" flag.
- Can also be used as a |method|: >
- mylist->map(expr2)
-
-
-maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
+maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
When {dict} is omitted or zero: Return the rhs of mapping
{name} in mode {mode}. The returned String has special
characters translated like in the output of the ":map" command
- listing.
+ listing. When {dict} is TRUE a dictionary is returned, see
+ below. To get a list of all mappings see |maplist()|.
When there is no mapping for {name}, an empty String is
returned if {dict} is FALSE, otherwise returns an empty Dict.
@@ -4842,11 +4162,11 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
When {dict} is there and it is |TRUE| return a dictionary
containing all the information of the mapping with the
- following items:
+ following items: *mapping-dict*
"lhs" The {lhs} of the mapping as it would be typed
"lhsraw" The {lhs} of the mapping as raw bytes
"lhsrawalt" The {lhs} of the mapping as raw bytes, alternate
- form, only present when it differs from "lhsraw"
+ form, only present when it differs from "lhsraw"
"rhs" The {rhs} of the mapping as typed.
"silent" 1 for a |:map-silent| mapping, else 0.
"noremap" 1 if the {rhs} of the mapping is not remappable.
@@ -4860,10 +4180,17 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
"!" Insert and Commandline mode
(|mapmode-ic|)
"sid" The script local ID, used for <sid> mappings
- (|<SID>|).
+ (|<SID>|). Negative for special contexts.
+ "scriptversion" The version of the script, always 1.
"lnum" The line number in "sid", zero if unknown.
"nowait" Do not wait for other, longer mappings.
(|:map-<nowait>|).
+ "abbr" True if this is an |abbreviation|.
+ "mode_bits" Nvim's internal binary representation of "mode".
+ |mapset()| ignores this; only "mode" is used.
+ See |maplist()| for usage examples. The values
+ are from src/nvim/state_defs.h and may change in
+ the future.
The dictionary can be used to restore a mapping with
|mapset()|.
@@ -4871,13 +4198,10 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
The mappings local to the current buffer are checked first,
then the global mappings.
This function can be used to map a key even when it's already
- mapped, and have it do the original mapping too. Sketch: >
+ mapped, and have it do the original mapping too. Sketch: >vim
exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n')
-< Can also be used as a |method|: >
- GetKey()->maparg('n')
-
-mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()*
+mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()*
Check if there is a mapping that matches with {name} in mode
{mode}. See |maparg()| for {mode} and special names in
{name}.
@@ -4903,33 +4227,87 @@ mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()*
The mappings local to the current buffer are checked first,
then the global mappings.
This function can be used to check if a mapping can be added
- without being ambiguous. Example: >
- :if mapcheck("_vv") == ""
- : map _vv :set guifont=7x13<CR>
- :endif
+ without being ambiguous. Example: >vim
+ if mapcheck("_vv") == ""
+ map _vv :set guifont=7x13<CR>
+ endif
< This avoids adding the "_vv" mapping when there already is a
mapping for "_v" or for "_vvv".
- Can also be used as a |method|: >
- GetKey()->mapcheck('n')
-
-mapset({mode}, {abbr}, {dict}) *mapset()*
- Restore a mapping from a dictionary returned by |maparg()|.
- {mode} and {abbr} should be the same as for the call to
- |maparg()|. *E460*
+maplist([{abbr}]) *maplist()*
+ Returns a |List| of all mappings. Each List item is a |Dict|,
+ the same as what is returned by |maparg()|, see
+ |mapping-dict|. When {abbr} is there and it is |TRUE| use
+ abbreviations instead of mappings.
+
+ Example to show all mappings with "MultiMatch" in rhs: >vim
+ echo maplist()->filter({_, m ->
+ \ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
+ \ })
+< It can be tricky to find mappings for particular |:map-modes|.
+ |mapping-dict|'s "mode_bits" can simplify this. For example,
+ the mode_bits for Normal, Insert or Command-line modes are
+ 0x19. To find all the mappings available in those modes you
+ can do: >vim
+ let saved_maps = []
+ for m in maplist()
+ if and(m.mode_bits, 0x19) != 0
+ eval saved_maps->add(m)
+ endif
+ endfor
+ echo saved_maps->mapnew({_, m -> m.lhs})
+< The values of the mode_bits are defined in Nvim's
+ src/nvim/state_defs.h file and they can be discovered at
+ runtime using |:map-commands| and "maplist()". Example: >vim
+ omap xyzzy <Nop>
+ let op_bit = maplist()->filter(
+ \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+ ounmap xyzzy
+ echo printf("Operator-pending mode bit: 0x%x", op_bit)
+
+mapnew({expr1}, {expr2}) *mapnew()*
+ Like |map()| but instead of replacing items in {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged. Items can still be changed by {expr2}, if you
+ don't want that use |deepcopy()| first.
+
+mapset({mode}, {abbr}, {dict}) *mapset()*
+ Restore a mapping from a dictionary, possibly returned by
+ |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
+ is true, is set on the current buffer; it is up to the caller
+ to ensure that the intended buffer is the current buffer. This
+ feature allows copying mappings from one buffer to another.
+ The dict.mode value may restore a single mapping that covers
+ more than one mode, like with mode values of '!', ' ', "nox",
+ or 'v'. *E1276*
+
+ In the first form, {mode} and {abbr} should be the same as
+ for the call to |maparg()|. *E460*
{mode} is used to define the mode in which the mapping is set,
not the "mode" entry in {dict}.
- Example for saving and restoring a mapping: >
+ Example for saving and restoring a mapping: >vim
let save_map = maparg('K', 'n', 0, 1)
nnoremap K somethingelse
- ...
+ " ...
call mapset('n', 0, save_map)
< Note that if you are going to replace a map in several modes,
- e.g. with `:map!`, you need to save the mapping for all of
- them, since they can differ.
-
+ e.g. with `:map!`, you need to save/restore the mapping for
+ all of them, when they might differ.
+
+ In the second form, with {dict} as the only argument, mode
+ and abbr are taken from the dict.
+ Example: >vim
+ let save_maps = maplist()->filter(
+ \ {_, m -> m.lhs == 'K'})
+ nnoremap K somethingelse
+ cnoremap K somethingelse2
+ " ...
+ unmap K
+ for d in save_maps
+ call mapset(d)
+ endfor
-match({expr}, {pat} [, {start} [, {count}]]) *match()*
+match({expr}, {pat} [, {start} [, {count}]]) *match()*
When {expr} is a |List| then this returns the index of the
first item where {pat} matches. Each item is used as a
String, |Lists| and |Dictionaries| are used as echoed.
@@ -4942,27 +4320,27 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()*
If there is no match -1 is returned.
For getting submatches see |matchlist()|.
- Example: >
- :echo match("testing", "ing") " results in 4
- :echo match([1, 'x'], '\a') " results in 1
+ Example: >vim
+ echo match("testing", "ing") " results in 4
+ echo match([1, 'x'], '\a') " results in 1
< See |string-match| for how {pat} is used.
*strpbrk()*
- Vim doesn't have a strpbrk() function. But you can do: >
- :let sepidx = match(line, '[.,;: \t]')
+ Vim doesn't have a strpbrk() function. But you can do: >vim
+ let sepidx = match(line, '[.,;: \t]')
< *strcasestr()*
Vim doesn't have a strcasestr() function. But you can add
- "\c" to the pattern to ignore case: >
- :let idx = match(haystack, '\cneedle')
+ "\c" to the pattern to ignore case: >vim
+ let idx = match(haystack, '\cneedle')
<
If {start} is given, the search starts from byte index
{start} in a String or item {start} in a |List|.
The result, however, is still the index counted from the
- first character/item. Example: >
- :echo match("testing", "ing", 2)
-< result is again "4". >
- :echo match("testing", "ing", 4)
-< result is again "4". >
- :echo match("testing", "t", 2)
+ first character/item. Example: >vim
+ echo match("testing", "ing", 2)
+< result is again "4". >vim
+ echo match("testing", "ing", 4)
+< result is again "4". >vim
+ echo match("testing", "t", 2)
< result is "3".
For a String, if {start} > 0 then it is like the string starts
{start} bytes later, thus "^" will match at {start}. Except
@@ -4976,7 +4354,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()*
When {count} is given use the {count}th match. When a match
is found in a String the search for the next one starts one
- character further. Thus this example results in 1: >
+ character further. Thus this example results in 1: >vim
echo match("testing", "..", 0, 2)
< In a |List| the search continues in the next item.
Note that when {count} is added the way {start} works changes,
@@ -4991,11 +4369,7 @@ match({expr}, {pat} [, {start} [, {count}]]) *match()*
zero matches at the start instead of a number of matches
further down in the text.
- Can also be used as a |method|: >
- GetText()->match('word')
- GetList()->match('word')
-<
- *matchadd()* *E798* *E799* *E801* *E957*
+ *matchadd()* *E798* *E799* *E801* *E957*
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
@@ -5044,21 +4418,17 @@ matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])
Returns -1 on error.
- Example: >
- :highlight MyGroup ctermbg=green guibg=green
- :let m = matchadd("MyGroup", "TODO")
-< Deletion of the pattern: >
- :call matchdelete(m)
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchadd("MyGroup", "TODO")
+< Deletion of the pattern: >vim
+ call matchdelete(m)
< A list of matches defined by |matchadd()| and |:match| are
available from |getmatches()|. All matches can be deleted in
one operation by |clearmatches()|.
- Can also be used as a |method|: >
- GetGroup()->matchadd('TODO')
-<
- *matchaddpos()*
-matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])
+matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) *matchaddpos()*
Same as |matchadd()|, but requires a list of positions {pos}
instead of a pattern. This command is faster than |matchadd()|
because it does not require to handle regular expressions and
@@ -5086,19 +4456,16 @@ matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])
Returns -1 on error.
- Example: >
- :highlight MyGroup ctermbg=green guibg=green
- :let m = matchaddpos("MyGroup", [[23, 24], 34])
-< Deletion of the pattern: >
- :call matchdelete(m)
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchaddpos("MyGroup", [[23, 24], 34])
+< Deletion of the pattern: >vim
+ call matchdelete(m)
< Matches added by |matchaddpos()| are returned by
|getmatches()|.
- Can also be used as a |method|: >
- GetGroup()->matchaddpos([23, 11])
-
-matcharg({nr}) *matcharg()*
+matcharg({nr}) *matcharg()*
Selects the {nr} match item, as set with a |:match|,
|:2match| or |:3match| command.
Return a |List| with two elements:
@@ -5110,10 +4477,7 @@ matcharg({nr}) *matcharg()*
Highlighting matches using the |:match| commands are limited
to three matches. |matchadd()| does not have this limitation.
- Can also be used as a |method|: >
- GetMatch()->matcharg()
-
-matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803*
+matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803*
Deletes a match with ID {id} previously defined by |matchadd()|
or one of the |:match| commands. Returns 0 if successful,
otherwise -1. See example for |matchadd()|. All matches can
@@ -5121,32 +4485,26 @@ matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803*
If {win} is specified, use the window with this number or
window ID instead of the current window.
- Can also be used as a |method|: >
- GetMatch()->matchdelete()
-
-matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()*
+matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()*
Same as |match()|, but return the index of first character
- after the match. Example: >
- :echo matchend("testing", "ing")
+ after the match. Example: >vim
+ echo matchend("testing", "ing")
< results in "7".
*strspn()* *strcspn()*
Vim doesn't have a strspn() or strcspn() function, but you can
- do it with matchend(): >
- :let span = matchend(line, '[a-zA-Z]')
- :let span = matchend(line, '[^a-zA-Z]')
+ do it with matchend(): >vim
+ let span = matchend(line, '[a-zA-Z]')
+ let span = matchend(line, '[^a-zA-Z]')
< Except that -1 is returned when there are no matches.
- The {start}, if given, has the same meaning as for |match()|. >
- :echo matchend("testing", "ing", 2)
-< results in "7". >
- :echo matchend("testing", "ing", 5)
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchend("testing", "ing", 2)
+< results in "7". >vim
+ echo matchend("testing", "ing", 5)
< result is "-1".
When {expr} is a |List| the result is equal to |match()|.
- Can also be used as a |method|: >
- GetText()->matchend('word')
-
-matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
+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.
@@ -5187,29 +4545,29 @@ matchfuzzy({list}, {str} [, {dict}]) *matchfuzzy()*
Refer to |fuzzy-matching| 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'})
+ Example: >vim
+ echo matchfuzzy(["clay", "crow"], "cay")
+< results in ["clay"]. >vim
+ echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
+< results in a list of buffer names fuzzy matching "ndl". >vim
+ echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'})
< results in a list of buffer information dicts with buffer
- names fuzzy matching "ndl". >
- :echo getbufinfo()->matchfuzzy("spl",
+ names fuzzy matching "ndl". >vim
+ 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',
+ names fuzzy matching "spl". >vim
+ echo v:oldfiles->matchfuzzy("test")
+< results in a list of file names fuzzy matching "test". >vim
+ let l = readfile("buffer.c")->matchfuzzy("str")
+< results in a list of lines in "buffer.c" fuzzy matching "str". >vim
+ echo ['one two', 'two one']->matchfuzzy('two one')
+< results in `['two one', 'one two']` . >vim
+ echo ['one two', 'two one']->matchfuzzy('two one',
\ {'matchseq': 1})
-< results in ['two one'].
+< results in `['two one']`.
-matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()*
+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
@@ -5222,95 +4580,81 @@ matchfuzzypos({list}, {str} [, {dict}]) *matchfuzzypos()*
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}]
+ Example: >vim
+ echo matchfuzzypos(['testing'], 'tsg')
+< results in [["testing"], [[0, 2, 6]], [99]] >vim
+ echo matchfuzzypos(['clay', 'lacy'], 'la')
+< results in [["lacy", "clay"], [[0, 1], [1, 2]], [153, 133]] >vim
+ echo [{'text': 'hello', 'id' : 10}]
\ ->matchfuzzypos('ll', {'key' : 'text'})
-< results in [[{"id": 10, "text": "hello"}], [[2, 3]], [127]]
+< results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]`
-matchlist({expr}, {pat} [, {start} [, {count}]]) *matchlist()*
+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
return. Following items are submatches, like "\1", "\2", etc.
in |:substitute|. When an optional submatch didn't match an
- empty string is used. Example: >
+ empty string is used. Example: >vim
echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)')
< Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', '']
When there is no match an empty list is returned.
You can pass in a List, but that is not very useful.
- Can also be used as a |method|: >
- GetText()->matchlist('word')
-
-matchstr({expr}, {pat} [, {start} [, {count}]]) *matchstr()*
- Same as |match()|, but return the matched string. Example: >
- :echo matchstr("testing", "ing")
+matchstr({expr}, {pat} [, {start} [, {count}]]) *matchstr()*
+ Same as |match()|, but return the matched string. Example: >vim
+ echo matchstr("testing", "ing")
< results in "ing".
When there is no match "" is returned.
- The {start}, if given, has the same meaning as for |match()|. >
- :echo matchstr("testing", "ing", 2)
-< results in "ing". >
- :echo matchstr("testing", "ing", 5)
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstr("testing", "ing", 2)
+< results in "ing". >vim
+ echo matchstr("testing", "ing", 5)
< result is "".
When {expr} is a |List| then the matching item is returned.
The type isn't changed, it's not necessarily a String.
- Can also be used as a |method|: >
- GetText()->matchstr('word')
-
-matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()*
+matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()*
Same as |matchstr()|, but return the matched string, the start
- position and the end position of the match. Example: >
- :echo matchstrpos("testing", "ing")
+ position and the end position of the match. Example: >vim
+ echo matchstrpos("testing", "ing")
< results in ["ing", 4, 7].
When there is no match ["", -1, -1] is returned.
- The {start}, if given, has the same meaning as for |match()|. >
- :echo matchstrpos("testing", "ing", 2)
-< results in ["ing", 4, 7]. >
- :echo matchstrpos("testing", "ing", 5)
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstrpos("testing", "ing", 2)
+< results in ["ing", 4, 7]. >vim
+ echo matchstrpos("testing", "ing", 5)
< result is ["", -1, -1].
When {expr} is a |List| then the matching item, the index
of first item where {pat} matches, the start position and the
- end position of the match are returned. >
- :echo matchstrpos([1, '__x'], '\a')
+ end position of the match are returned. >vim
+ echo matchstrpos([1, '__x'], '\a')
< result is ["x", 1, 2, 3].
The type isn't changed, it's not necessarily a String.
- Can also be used as a |method|: >
- GetText()->matchstrpos('word')
-<
-
- *max()*
-max({expr}) Return the maximum value of all items in {expr}. Example: >
+max({expr}) *max()*
+ Return the maximum value of all items in {expr}. Example: >vim
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
- an error. An empty |List| or |Dictionary| results in zero.
-
- Can also be used as a |method|: >
- mylist->max()
+ an error. An empty |List| or |Dictionary| results in zero.
-
-menu_get({path} [, {modes}]) *menu_get()*
+menu_get({path} [, {modes}]) *menu_get()*
Returns a |List| of |Dictionaries| describing |menus| (defined
by |:menu|, |:amenu|, …), including |hidden-menus|.
{path} matches a menu by name, or all menus if {path} is an
- empty string. Example: >
- :echo menu_get('File','')
- :echo menu_get('')
+ empty string. Example: >vim
+ echo menu_get('File','')
+ echo menu_get('')
<
{modes} is a string of zero or more modes (see |maparg()| or
|creating-menus| for the list of modes). "a" means "all".
- Example: >
+ Example: >vim
nnoremenu &Test.Test inormal
inoremenu Test.Test insert
vnoremenu Test.Test x
@@ -5344,7 +4688,7 @@ menu_get({path} [, {modes}]) *menu_get()*
} ]
<
-menu_info({name} [, {mode}]) *menu_info()*
+menu_info({name} [, {mode}]) *menu_info()*
Return information about the specified menu {name} in
mode {mode}. The menu name should be specified without the
shortcut character ('&'). If {name} is "", then the top-level
@@ -5396,9 +4740,9 @@ menu_info({name} [, {mode}]) *menu_info()*
Returns an empty dictionary if the menu item is not found.
- Examples: >
- :echo menu_info('Edit.Cut')
- :echo menu_info('File.Save', 'n')
+ Examples: >vim
+ echo menu_info('Edit.Cut')
+ echo menu_info('File.Save', 'n')
" Display the entire menu hierarchy in a buffer
func ShowMenu(name, pfx)
@@ -5414,12 +4758,9 @@ menu_info({name} [, {mode}]) *menu_info()*
call ShowMenu(topmenu, '')
endfor
<
- Can also be used as a |method|: >
- GetMenuName()->menu_info('v')
-
-< *min()*
-min({expr}) Return the minimum value of all items in {expr}. Example: >
+min({expr}) *min()*
+ Return the minimum value of all items in {expr}. Example: >vim
echo min([apples, pears, oranges])
< {expr} can be a |List| or a |Dictionary|. For a Dictionary,
@@ -5428,42 +4769,55 @@ min({expr}) Return the minimum value of all items in {expr}. Example: >
items in {expr} cannot be used as a Number this results in
an error. An empty |List| or |Dictionary| results in zero.
- Can also be used as a |method|: >
- mylist->min()
-
-< *mkdir()* *E739*
-mkdir({name} [, {path} [, {prot}]])
+mkdir({name} [, {flags} [, {prot}]]) *mkdir()* *E739*
Create directory {name}.
- If {path} is "p" then intermediate directories are created as
- necessary. Otherwise it must be "".
+ When {flags} is present it must be a string. An empty string
+ has no effect.
+
+ If {flags} contains "p" then intermediate directories are
+ created as necessary.
+ If {flags} contains "D" then {name} is deleted at the end of
+ the current function, as with: >vim
+ defer delete({name}, 'd')
+<
+ If {flags} contains "R" then {name} is deleted recursively at
+ the end of the current function, as with: >vim
+ defer delete({name}, 'rf')
+< Note that when {name} has more than one part and "p" is used
+ some directories may already exist. Only the first one that
+ is created and what it contains is scheduled to be deleted.
+ E.g. when using: >vim
+ call mkdir('subdir/tmp/autoload', 'pR')
+< and "subdir" already exists then "subdir/tmp" will be
+ scheduled for deletion, like with: >vim
+ defer delete('subdir/tmp', 'rf')
+<
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
unreadable for others.
{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)
+ /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: >vim
+ 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
+ If you try to create an existing directory with {flags} set to
"p" mkdir() will silently exit.
The function result is a Number, which is TRUE if the call was
successful or FALSE if the directory creation failed or partly
failed.
- Can also be used as a |method|: >
- GetName()->mkdir()
-<
- *mode()*
-mode([expr]) Return a string that indicates the current mode.
+mode([expr]) *mode()*
+ Return a string that indicates the current mode.
If [expr] is supplied and it evaluates to a non-zero Number or
a non-empty String (|non-zero-arg|), then the full mode is
returned, otherwise only the first letter is returned.
+ Also see |state()|.
n Normal
no Operator-pending
@@ -5496,7 +4850,9 @@ mode([expr]) Return a string that indicates the current mode.
Rvc Virtual Replace mode completion |compl-generic|
Rvx Virtual Replace mode |i_CTRL-X| completion
c Command-line editing
+ cr Command-line editing overstrike mode |c_<Insert>|
cv Vim Ex mode |gQ|
+ cvr Vim Ex mode while in overstrike mode |c_<Insert>|
r Hit-enter prompt
rm The -- more -- prompt
r? A |:confirm| query of some sort
@@ -5510,15 +4866,12 @@ mode([expr]) Return a string that indicates the current mode.
the leading character(s).
Also see |visualmode()|.
- Can also be used as a |method|: >
- DoFull()->mode()
-
-msgpackdump({list} [, {type}]) *msgpackdump()*
- Convert a list of VimL objects to msgpack. Returned value is a
+msgpackdump({list} [, {type}]) *msgpackdump()*
+ Convert a list of Vimscript objects to msgpack. Returned value is a
|readfile()|-style list. When {type} contains "B", a |Blob| is
- returned instead. Example: >
+ returned instead. Example: >vim
call writefile(msgpackdump([{}]), 'fname.mpack', 'b')
-< or, using a |Blob|: >
+< or, using a |Blob|: >vim
call writefile(msgpackdump([{}], 'B'), 'fname.mpack')
<
This will write the single 0x80 byte to a `fname.mpack` file
@@ -5532,10 +4885,10 @@ msgpackdump({list} [, {type}]) *msgpackdump()*
4. Other strings and |Blob|s are always dumped as BIN strings.
5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
-msgpackparse({data}) *msgpackparse()*
+msgpackparse({data}) *msgpackparse()*
Convert a |readfile()|-style list or a |Blob| to a list of
- VimL objects.
- Example: >
+ Vimscript objects.
+ Example: >vim
let fname = expand('~/.config/nvim/shada/main.shada')
let mpack = readfile(fname, 'b')
let shada_objects = msgpackparse(mpack)
@@ -5606,36 +4959,31 @@ msgpackparse({data}) *msgpackparse()*
representing extension type. Second is
|readfile()|-style list of strings.
-nextnonblank({lnum}) *nextnonblank()*
+nextnonblank({lnum}) *nextnonblank()*
Return the line number of the first line at or below {lnum}
- that is not blank. Example: >
- if getline(nextnonblank(1)) =~ "Java"
+ that is not blank. Example: >vim
+ if getline(nextnonblank(1)) =~ "Java" | endif
< When {lnum} is invalid or there is no non-blank line at or
below it, zero is returned.
{lnum} is used like with |getline()|.
See also |prevnonblank()|.
- Can also be used as a |method|: >
- GetLnum()->nextnonblank()
-
-nr2char({expr} [, {utf8}]) *nr2char()*
+nr2char({expr} [, {utf8}]) *nr2char()*
Return a string with a single character, which has the number
- value {expr}. Examples: >
- nr2char(64) returns "@"
- nr2char(32) returns " "
-< Example for "utf-8": >
- nr2char(300) returns I with bow character
-< UTF-8 encoding is always used, {utf8} option has no effect,
+ value {expr}. Examples: >vim
+ echo nr2char(64) " returns '@'
+ echo nr2char(32) " returns ' '
+< Example for "utf-8": >vim
+ echo nr2char(300) " returns I with bow character
+<
+ UTF-8 encoding is always used, {utf8} option has no effect,
and exists only for backwards-compatibility.
Note that a NUL character in the file is specified with
nr2char(10), because NULs are represented with newline
characters. nr2char(0) is a real NUL and terminates the
string, thus results in an empty string.
- Can also be used as a |method|: >
- GetNumber()->nr2char()
-
-nvim_...({...}) *E5555* *nvim_...()* *eval-api*
+nvim_...({...}) *nvim_...()* *E5555* *eval-api*
Call nvim |api| functions. The type checking of arguments will
be stricter than for most other builtins. For instance,
if Integer is expected, a |Number| must be passed in, a
@@ -5646,39 +4994,33 @@ nvim_...({...}) *E5555* *nvim_...()* *eval-api*
also take the numerical value 0 to indicate the current
(focused) object.
-or({expr}, {expr}) *or()*
+or({expr}, {expr}) *or()*
Bitwise OR on the two arguments. The arguments are converted
to a number. A List, Dict or Float argument causes an error.
Also see `and()` and `xor()`.
- Example: >
- :let bits = or(bits, 0x80)
-< Can also be used as a |method|: >
- :let bits = bits->or(0x80)
+ Example: >vim
+ let bits = or(bits, 0x80)
< Rationale: The reason this is a function and not using the "|"
character like many languages, is that Vi has always used "|"
to separate commands. In many places it would not be clear if
"|" is an operator or a command separator.
-
-pathshorten({path} [, {len}]) *pathshorten()*
+pathshorten({path} [, {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 {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')
+ letters). Leading '~' and '.' characters are kept. Examples: >vim
+ echo pathshorten('~/.config/nvim/autoload/file1.vim')
< ~/.c/n/a/file1.vim ~
->
- :echo pathshorten('~/.config/nvim/autoload/file2.vim', 2)
+>vim
+ echo pathshorten('~/.config/nvim/autoload/file2.vim', 2)
< ~/.co/nv/au/file2.vim ~
It doesn't matter if the path exists or not.
Returns an empty string on error.
- Can also be used as a |method|: >
- GetDirectories()->pathshorten()
-
-perleval({expr}) *perleval()*
+perleval({expr}) *perleval()*
Evaluate |perl| expression {expr} and return its result
converted to Vim data structures.
Numbers and strings are returned as they are (strings are
@@ -5689,49 +5031,40 @@ perleval({expr}) *perleval()*
Note: If you want an array or hash, {expr} must return a
reference to it.
- Example: >
- :echo perleval('[1 .. 4]')
+ Example: >vim
+ echo perleval('[1 .. 4]')
< [1, 2, 3, 4]
- Can also be used as a |method|: >
- GetExpr()->perleval()
-
-pow({x}, {y}) *pow()*
+pow({x}, {y}) *pow()*
Return the power of {x} to the exponent {y} as a |Float|.
{x} and {y} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {x} or {y} is not a |Float| or a |Number|.
- Examples: >
- :echo pow(3, 3)
-< 27.0 >
- :echo pow(2, 16)
-< 65536.0 >
- :echo pow(32, 0.20)
+ Examples: >vim
+ echo pow(3, 3)
+< 27.0 >vim
+ echo pow(2, 16)
+< 65536.0 >vim
+ echo pow(32, 0.20)
< 2.0
- Can also be used as a |method|: >
- Compute()->pow(3)
-
-prevnonblank({lnum}) *prevnonblank()*
+prevnonblank({lnum}) *prevnonblank()*
Return the line number of the first line at or above {lnum}
- that is not blank. Example: >
+ that is not blank. Example: >vim
let ind = indent(prevnonblank(v:lnum - 1))
< When {lnum} is invalid or there is no non-blank line at or
above it, zero is returned.
{lnum} is used like with |getline()|.
Also see |nextnonblank()|.
- Can also be used as a |method|: >
- GetLnum()->prevnonblank()
-
-printf({fmt}, {expr1} ...) *printf()*
+printf({fmt}, {expr1} ...) *printf()*
Return a String with {fmt}, where "%" items are replaced by
- the formatted form of their respective arguments. Example: >
- printf("%4d: E%d %.30s", lnum, errno, msg)
+ the formatted form of their respective arguments. Example: >vim
+ echo printf("%4d: E%d %.30s", lnum, errno, msg)
< May result in:
" 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~
When used as a |method| the base is passed as the second
- argument: >
+ argument: >vim
Compute()->printf("result: %d")
<
You can use `call()` to pass the items as a list.
@@ -5767,7 +5100,11 @@ printf({fmt}, {expr1} ...) *printf()*
The "%" starts a conversion specification. The following
arguments appear in sequence:
- % [flags] [field-width] [.precision] type
+ % [pos-argument] [flags] [field-width] [.precision] type
+
+ pos-argument
+ At most one positional argument specifier. These
+ take the form {n$}, where n is >= 1.
flags
Zero or more of the following flags:
@@ -5827,15 +5164,21 @@ printf({fmt}, {expr1} ...) *printf()*
be applied, see below.
A field width or precision, or both, may be indicated by an
- asterisk '*' instead of a digit string. In this case, a
+ asterisk "*" instead of a digit string. In this case, a
Number argument supplies the field width or precision. A
negative field width is treated as a left adjustment flag
followed by a positive field width; a negative precision is
- treated as though it were missing. Example: >
- :echo printf("%d: %.*s", nr, width, line)
+ treated as though it were missing. Example: >vim
+ echo printf("%d: %.*s", nr, width, line)
< This limits the length of the text used from "line" to
"width" bytes.
+ If the argument to be formatted is specified using a posional
+ argument specifier, and a '*' is used to indicate that a
+ number argument is to be used to specify the width or
+ precision, the argument(s) to be used must also be specified
+ using a {n$} positional argument specifier. See |printf-$|.
+
The conversion specifiers and their meanings are:
*printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X*
@@ -5852,8 +5195,13 @@ printf({fmt}, {expr1} ...) *printf()*
than the field width, the field is expanded to contain
the conversion result.
The 'h' modifier indicates the argument is 16 bits.
- The 'l' modifier indicates the argument is 32 bits.
- The 'L' modifier indicates the argument is 64 bits.
+ The 'l' modifier indicates the argument is a long
+ integer. The size will be 32 bits or 64 bits
+ depending on your platform.
+ The "ll" modifier indicates the argument is 64 bits.
+ The b and B conversion specifiers never take a width
+ modifier and always assume their argument is a 64 bit
+ integer.
Generally, these modifiers are not useful. They are
ignored when type is known from the argument.
@@ -5887,7 +5235,7 @@ printf({fmt}, {expr1} ...) *printf()*
(out of range or dividing by zero) results in "inf"
or "-inf" with %f (INF or -INF with %F).
"0.0 / 0.0" results in "nan" with %f (NAN with %F).
- Example: >
+ Example: >vim
echo printf("%.2f", 12.115)
< 12.12
Note that roundoff depends on the system libraries.
@@ -5923,17 +5271,119 @@ printf({fmt}, {expr1} ...) *printf()*
of "%" items. If there are not sufficient or too many
arguments an error is given. Up to 18 arguments can be used.
-prompt_getprompt({buf}) *prompt_getprompt()*
+ *printf-$*
+ In certain languages, error and informative messages are
+ more readable when the order of words is different from the
+ corresponding message in English. To accommodate translations
+ having a different word order, positional arguments may be
+ used to indicate this. For instance: >vim
+
+ #, c-format
+ msgid "%s returning %s"
+ msgstr "waarde %2$s komt terug van %1$s"
+<
+ In this example, the sentence has its 2 string arguments
+ reversed in the output. >vim
+
+ echo printf(
+ "In The Netherlands, vim's creator's name is: %1$s %2$s",
+ "Bram", "Moolenaar")
+< In The Netherlands, vim's creator's name is: Bram Moolenaar >vim
+
+ echo printf(
+ "In Belgium, vim's creator's name is: %2$s %1$s",
+ "Bram", "Moolenaar")
+< In Belgium, vim's creator's name is: Moolenaar Bram
+
+ Width (and precision) can be specified using the '*' specifier.
+ In this case, you must specify the field width position in the
+ argument list. >vim
+
+ echo printf("%1$*2$.*3$d", 1, 2, 3)
+< 001 >vim
+ echo printf("%2$*3$.*1$d", 1, 2, 3)
+< 2 >vim
+ echo printf("%3$*1$.*2$d", 1, 2, 3)
+< 03 >vim
+ echo printf("%1$*2$.*3$g", 1.4142, 2, 3)
+< 1.414
+
+ You can mix specifying the width and/or precision directly
+ and via positional arguments: >vim
+
+ echo printf("%1$4.*2$f", 1.4142135, 6)
+< 1.414214 >vim
+ echo printf("%1$*2$.4f", 1.4142135, 6)
+< 1.4142 >vim
+ echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
+< 1.41
+
+ *E1500*
+ You cannot mix positional and non-positional arguments: >vim
+ echo printf("%s%1$s", "One", "Two")
+< E1500: Cannot mix positional and non-positional arguments:
+ %s%1$s
+
+ *E1501*
+ You cannot skip a positional argument in a format string: >vim
+ echo printf("%3$s%1$s", "One", "Two", "Three")
+< E1501: format argument 2 unused in $-style format:
+ %3$s%1$s
+
+ *E1502*
+ You can re-use a [field-width] (or [precision]) argument: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+< 1 at width 2 is: 01
+
+ However, you can't use it as a different type: >vim
+ echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+< E1502: Positional argument 2 used as field width reused as
+ different type: long int/int
+
+ *E1503*
+ When a positional argument is used, but not the correct number
+ or arguments is given, an error is raised: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+< E1503: Positional argument 3 out of bounds: %1$d at width
+ %2$d is: %01$*2$.*3$d
+
+ Only the first error is reported: >vim
+ echo printf("%01$*2$.*3$d %4$d", 1, 2)
+< E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+ %4$d
+
+ *E1504*
+ A positional argument can be used more than once: >vim
+ echo printf("%1$s %2$s %1$s", "One", "Two")
+< One Two One
+
+ However, you can't use a different type the second time: >vim
+ echo printf("%1$s %2$s %1$d", "One", "Two")
+< E1504: Positional argument 1 type used inconsistently:
+ int/string
+
+ *E1505*
+ Various other errors that lead to a format string being
+ wrongly formatted lead to: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.3$d", 1, 2)
+< E1505: Invalid format specifier: %1$d at width %2$d is:
+ %01$*2$.3$d
+
+ *E1507*
+ This internal error indicates that the logic to parse a
+ positional format argument ran into a problem that couldn't be
+ otherwise reported. Please file a bug against Vim if you run
+ into this, copying the exact format string and parameters that
+ were used.
+
+prompt_getprompt({buf}) *prompt_getprompt()*
Returns the effective prompt text for buffer {buf}. {buf} can
be a buffer name or number. See |prompt-buffer|.
If the buffer doesn't exist or isn't a prompt buffer, an empty
string is returned.
- Can also be used as a |method|: >
- GetBuffer()->prompt_getprompt()
-
-prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
+prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
Set prompt callback for buffer {buf} to {expr}. When {expr}
is an empty string the callback is removed. This has only
effect if {buf} has 'buftype' set to "prompt".
@@ -5949,23 +5399,23 @@ prompt_setcallback({buf}, {expr}) *prompt_setcallback()*
The callback is invoked with one argument, which is the text
that was entered at the prompt. This can be an empty string
if the user only typed Enter.
- Example: >
- call prompt_setcallback(bufnr(''), function('s:TextEntered'))
+ Example: >vim
func s:TextEntered(text)
if a:text == 'exit' || a:text == 'quit'
stopinsert
+ " Reset 'modified' to allow the buffer to be closed.
+ " We assume there is nothing useful to be saved.
+ set nomodified
close
else
+ " Do something useful with "a:text". In this example
+ " we just repeat it.
call append(line('$') - 1, 'Entered: "' .. a:text .. '"')
- " Reset 'modified' to allow the buffer to be closed.
- set nomodified
endif
endfunc
+ call prompt_setcallback(bufnr(), function('s:TextEntered'))
-< Can also be used as a |method|: >
- GetBuffer()->prompt_setcallback(callback)
-
-prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()*
+prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()*
Set a callback for buffer {buf} to {expr}. When {expr} is an
empty string the callback is removed. This has only effect if
{buf} has 'buftype' set to "prompt".
@@ -5974,20 +5424,15 @@ prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()*
mode. Without setting a callback Vim will exit Insert mode,
as in any buffer.
- Can also be used as a |method|: >
- GetBuffer()->prompt_setinterrupt(callback)
-
-prompt_setprompt({buf}, {text}) *prompt_setprompt()*
+prompt_setprompt({buf}, {text}) *prompt_setprompt()*
Set prompt for buffer {buf} to {text}. You most likely want
{text} to end in a space.
The result is only visible if {buf} has 'buftype' set to
- "prompt". Example: >
+ "prompt". Example: >vim
call prompt_setprompt(bufnr(''), 'command: ')
<
- Can also be used as a |method|: >
- GetBuffer()->prompt_setprompt('command: ')
-pum_getpos() *pum_getpos()*
+pum_getpos() *pum_getpos()*
If the popup menu (see |ins-completion-menu|) is not visible,
returns an empty |Dictionary|, otherwise, returns a
|Dictionary| with the following keys:
@@ -6000,13 +5445,13 @@ pum_getpos() *pum_getpos()*
The values are the same as in |v:event| during |CompleteChanged|.
-pumvisible() *pumvisible()*
+pumvisible() *pumvisible()*
Returns non-zero when the popup menu is visible, zero
otherwise. See |ins-completion-menu|.
This can be used to avoid some things that would remove the
popup menu.
-py3eval({expr}) *py3eval()*
+py3eval({expr}) *py3eval()*
Evaluate Python expression {expr} and return its result
converted to Vim data structures.
Numbers and strings are returned as they are (strings are
@@ -6016,11 +5461,7 @@ py3eval({expr}) *py3eval()*
Dictionaries are represented as Vim |Dictionary| type with
keys converted to strings.
- Can also be used as a |method|: >
- GetExpr()->py3eval()
-<
- *E858* *E859*
-pyeval({expr}) *pyeval()*
+pyeval({expr}) *pyeval()* *E858* *E859*
Evaluate Python expression {expr} and return its result
converted to Vim data structures.
Numbers and strings are returned as they are (strings are
@@ -6029,20 +5470,29 @@ pyeval({expr}) *pyeval()*
Dictionaries are represented as Vim |Dictionary| type,
non-string keys result in error.
- Can also be used as a |method|: >
- GetExpr()->pyeval()
-
-pyxeval({expr}) *pyxeval()*
+pyxeval({expr}) *pyxeval()*
Evaluate Python expression {expr} and return its result
converted to Vim data structures.
Uses Python 2 or 3, see |python_x| and 'pyxversion'.
See also: |pyeval()|, |py3eval()|
- Can also be used as a |method|: >
- GetExpr()->pyxeval()
+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.
+ Returns -1 if {expr} is invalid.
+
+ Examples: >vim
+ echo rand()
+ let seed = srand()
+ echo rand(seed)
+ echo rand(seed) % 16 " random number 0 - 15
<
- *E726* *E727*
-range({expr} [, {max} [, {stride}]]) *range()*
+
+range({expr} [, {max} [, {stride}]]) *range()* *E726* *E727*
Returns a |List| with Numbers:
- If only {expr} is specified: [0, 1, ..., {expr} - 1]
- If {max} is specified: [{expr}, {expr} + 1, ..., {max}]
@@ -6052,45 +5502,40 @@ range({expr} [, {max} [, {stride}]]) *range()*
When the maximum is one before the start the result is an
empty list. When the maximum is more than one before the
start this is an error.
- Examples: >
- range(4) " [0, 1, 2, 3]
- range(2, 4) " [2, 3, 4]
- range(2, 9, 3) " [2, 5, 8]
- range(2, -2, -1) " [2, 1, 0, -1, -2]
- range(0) " []
- range(2, 0) " error!
-<
- 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.
- Returns -1 if {expr} is invalid.
-
- 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()
+ Examples: >vim
+ echo range(4) " [0, 1, 2, 3]
+ echo range(2, 4) " [2, 3, 4]
+ echo range(2, 9, 3) " [2, 5, 8]
+ echo range(2, -2, -1) " [2, 1, 0, -1, -2]
+ echo range(0) " []
+ echo range(2, 0) " error!
<
-readblob({fname}) *readblob()*
+readblob({fname} [, {offset} [, {size}]]) *readblob()*
Read file {fname} in binary mode and return a |Blob|.
- When the file can't be opened an error message is given and
+ If {offset} is specified, read the file from the specified
+ offset. If it is a negative value, it is used as an offset
+ from the end of the file. E.g., to read the last 12 bytes: >vim
+ echo readblob('file.bin', -12)
+< If {size} is specified, only the specified size will be read.
+ E.g. to read the first 100 bytes of a file: >vim
+ echo readblob('file.bin', 0, 100)
+< If {size} is -1 or omitted, the whole data starting from
+ {offset} will be read.
+ This can be also used to read the data from a character device
+ on Unix when {size} is explicitly set. Only if the device
+ supports seeking {offset} can be used. Otherwise it should be
+ zero. E.g. to read 10 bytes from a serial console: >vim
+ echo readblob('/dev/ttyS0', 0, 10)
+< When the file can't be opened an error message is given and
the result is an empty |Blob|.
+ When the offset is beyond the end of the file the result is an
+ empty blob.
+ When trying to read more bytes than are available the result
+ is truncated.
Also see |readfile()| and |writefile()|.
-
- *readdir()*
-readdir({directory} [, {expr}])
+readdir({directory} [, {expr}]) *readdir()*
Return a list with file and directory names in {directory}.
You can also use |glob()| if you don't need to do complicated
things, such as limiting the number of matches.
@@ -6105,27 +5550,22 @@ readdir({directory} [, {expr}])
to the list.
Each time {expr} is evaluated |v:val| is set to the entry name.
When {expr} is a function the name is passed as the argument.
- For example, to get a list of files ending in ".txt": >
- readdir(dirname, {n -> n =~ '.txt$'})
-< To skip hidden and backup files: >
- readdir(dirname, {n -> n !~ '^\.\|\~$'})
-
-< If you want to get a directory tree: >
- function! s:tree(dir)
- return {a:dir : map(readdir(a:dir),
+ For example, to get a list of files ending in ".txt": >vim
+ echo readdir(dirname, {n -> n =~ '.txt$'})
+< To skip hidden and backup files: >vim
+ echo readdir(dirname, {n -> n !~ '^\.\|\~$'})
+
+< If you want to get a directory tree: >vim
+ function! s:tree(dir)
+ return {a:dir : map(readdir(a:dir),
\ {_, x -> isdirectory(x) ?
\ {x : s:tree(a:dir .. '/' .. x)} : x})}
- endfunction
- echo s:tree(".")
+ endfunction
+ echo s:tree(".")
<
Returns an empty List on error.
- Can also be used as a |method|: >
- GetDirName()->readdir()
-<
-
- *readfile()*
-readfile({fname} [, {type} [, {max}]])
+readfile({fname} [, {type} [, {max}]]) *readfile()*
Read file {fname} and return a |List|, each line of the file
as an item. Lines are broken at NL characters. Macintosh
files separated with CR will result in a single long line
@@ -6141,10 +5581,10 @@ readfile({fname} [, {type} [, {max}]])
- Any UTF-8 byte order mark is removed from the text.
When {max} is given this specifies the maximum number of lines
to be read. Useful if you only want to check the first ten
- lines of a file: >
- :for line in readfile(fname, '', 10)
- : if line =~ 'Date' | echo line | endif
- :endfor
+ lines of a file: >vim
+ for line in readfile(fname, '', 10)
+ if line =~ 'Date' | echo line | endif
+ endfor
< When {max} is negative -{max} lines from the end of the file
are returned, or as many as there are.
When {max} is zero the result is an empty list.
@@ -6158,45 +5598,41 @@ readfile({fname} [, {type} [, {max}]])
the result is an empty list.
Also see |writefile()|.
- Can also be used as a |method|: >
- GetFileName()->readfile()
-
-reduce({object}, {func} [, {initial}]) *reduce()* *E998*
+reduce({object}, {func} [, {initial}]) *reduce()* *E998*
{func} is called for every item in {object}, which can be a
- |List| or a |Blob|. {func} is called with two arguments: the
- result so far and current item. After processing all items
- the result is returned.
+ |String|, |List| or a |Blob|. {func} is called with two
+ arguments: the result so far and current item. After
+ processing all items the result is returned.
{initial} is the initial result. When omitted, the first item
in {object} is used and {func} is first called for the second
item. If {initial} is not given and {object} is empty no
result can be computed, an E998 error is given.
- Examples: >
+ Examples: >vim
echo reduce([1, 3, 5], { acc, val -> acc + val })
echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
echo reduce(0z1122, { acc, val -> 2 * acc + val })
+ echo reduce('xyz', { acc, val -> acc .. ',' .. val })
<
- Can also be used as a |method|: >
- echo mylist->reduce({ acc, val -> acc + val }, 0)
-reg_executing() *reg_executing()*
+reg_executing() *reg_executing()*
Returns the single letter name of the register being executed.
Returns an empty string when no register is being executed.
See |@|.
-reg_recorded() *reg_recorded()*
+reg_recorded() *reg_recorded()*
Returns the single letter name of the last recorded register.
Returns an empty string when nothing was recorded yet.
See |q| and |Q|.
-reg_recording() *reg_recording()*
+reg_recording() *reg_recording()*
Returns the single letter name of the register being recorded.
Returns an empty string when not recording. See |q|.
reltime()
reltime({start})
-reltime({start}, {end}) *reltime()*
+reltime({start}, {end}) *reltime()*
Return an item that represents a time value. The item is a
list with items that depend on the system.
The item can be passed to |reltimestr()| to convert it to a
@@ -6214,12 +5650,9 @@ reltime({start}, {end}) *reltime()*
The {start} and {end} arguments must be values returned by
reltime(). Returns zero on error.
- Can also be used as a |method|: >
- GetStart()->reltime()
-<
Note: |localtime()| returns the current (non-relative) time.
-reltimefloat({time}) *reltimefloat()*
+reltimefloat({time}) *reltimefloat()*
Return a Float that represents the time value of {time}.
Unit of time is seconds.
Example:
@@ -6230,28 +5663,22 @@ reltimefloat({time}) *reltimefloat()*
Also see |profiling|.
If there is an error an empty string is returned
- Can also be used as a |method|: >
- reltime(start)->reltimefloat()
-
-reltimestr({time}) *reltimestr()*
+reltimestr({time}) *reltimestr()*
Return a String that represents the time value of {time}.
This is the number of seconds, a dot and the number of
- microseconds. Example: >
+ microseconds. Example: >vim
let start = reltime()
call MyFunction()
echo reltimestr(reltime(start))
< Note that overhead for the commands will be added to the time.
Leading spaces are used to make the string align nicely. You
- can use split() to remove it. >
+ can use split() to remove it. >vim
echo split(reltimestr(reltime(start)))[0]
< Also see |profiling|.
If there is an error an empty string is returned
- Can also be used as a |method|: >
- reltime(start)->reltimestr()
-<
remove({list}, {idx})
-remove({list}, {idx}, {end}) *remove()*
+remove({list}, {idx}, {end}) *remove()*
Without {end}: Remove the item at {idx} from |List| {list} and
return the item.
With {end}: Remove items from {idx} to {end} (inclusive) and
@@ -6260,15 +5687,12 @@ remove({list}, {idx}, {end}) *remove()*
points to an item before {idx} this is an error.
See |list-index| for possible values of {idx} and {end}.
Returns zero on error.
- Example: >
- :echo "last item: " .. remove(mylist, -1)
- :call remove(mylist, 0, 9)
+ Example: >vim
+ echo "last item: " .. remove(mylist, -1)
+ call remove(mylist, 0, 9)
<
Use |delete()| to remove a file.
- Can also be used as a |method|: >
- mylist->remove(idx)
-
remove({blob}, {idx})
remove({blob}, {idx}, {end})
Without {end}: Remove the byte at {idx} from |Blob| {blob} and
@@ -6278,18 +5702,19 @@ remove({blob}, {idx}, {end})
byte as {end} a |Blob| with one byte is returned. When {end}
points to a byte before {idx} this is an error.
Returns zero on error.
- Example: >
- :echo "last byte: " .. remove(myblob, -1)
- :call remove(mylist, 0, 9)
+ Example: >vim
+ echo "last byte: " .. remove(myblob, -1)
+ call remove(mylist, 0, 9)
+<
remove({dict}, {key})
Remove the entry from {dict} with key {key} and return it.
- Example: >
- :echo "removed " .. remove(dict, "one")
+ Example: >vim
+ echo "removed " .. remove(dict, "one")
< If there is no {key} in {dict} this is an error.
Returns zero on error.
-rename({from}, {to}) *rename()*
+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
result is a Number, which is 0 if the file was renamed
@@ -6297,23 +5722,17 @@ rename({from}, {to}) *rename()*
NOTE: If {to} exists it is overwritten without warning.
This function is not available in the |sandbox|.
- Can also be used as a |method|: >
- GetOldName()->rename(newname)
-
-repeat({expr}, {count}) *repeat()*
+repeat({expr}, {count}) *repeat()*
Repeat {expr} {count} times and return the concatenated
- result. Example: >
- :let separator = repeat('-', 80)
+ result. Example: >vim
+ let separator = repeat('-', 80)
< When {count} is zero or negative the result is empty.
- When {expr} is a |List| the result is {expr} concatenated
- {count} times. Example: >
- :let longlist = repeat(['a', 'b'], 3)
+ When {expr} is a |List| or a |Blob| the result is {expr}
+ concatenated {count} times. Example: >vim
+ let longlist = repeat(['a', 'b'], 3)
< Results in ['a', 'b', 'a', 'b', 'a', 'b'].
- Can also be used as a |method|: >
- mylist->repeat(count)
-
-resolve({filename}) *resolve()* *E655*
+resolve({filename}) *resolve()* *E655*
On MS-Windows, when {filename} is a shortcut (a .lnk file),
returns the path the shortcut points to in a simplified form.
On Unix, repeat resolving symbolic links in all path
@@ -6326,56 +5745,53 @@ resolve({filename}) *resolve()* *E655*
current directory (provided the result is still a relative
path name) and also keeps a trailing path separator.
- Can also be used as a |method|: >
- GetName()->resolve()
-<
- *reverse()*
-reverse({object})
- Reverse the order of items in {object} in-place.
- {object} can be a |List| or a |Blob|.
- Returns {object}.
- Returns zero if {object} is not a List or a Blob.
- If you want an object to remain unmodified make a copy first: >
- :let revlist = reverse(copy(mylist))
-< Can also be used as a |method|: >
- mylist->reverse()
-
-round({expr}) *round()*
+reverse({object}) *reverse()*
+ Reverse the order of items in {object}. {object} can be a
+ |List|, a |Blob| or a |String|. For a List and a Blob the
+ items are reversed in-place and {object} is returned.
+ For a String a new String is returned.
+ Returns zero if {object} is not a List, Blob or a String.
+ If you want a List or Blob to remain unmodified make a copy
+ first: >vim
+ let revlist = reverse(copy(mylist))
+<
+
+round({expr}) *round()*
Round off {expr} to the nearest integral value and return it
as a |Float|. If {expr} lies halfway between two integral
values, then use the larger one (away from zero).
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
+ Examples: >vim
echo round(0.456)
-< 0.0 >
+< 0.0 >vim
echo round(4.5)
-< 5.0 >
+< 5.0 >vim
echo round(-4.5)
< -5.0
- 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")
+ Example: >vim
+ 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)
+ Example: >vim
+ let result = rpcrequest(rpc_chan, "func", 1, 2, 3)
+<
-rpcstart({prog} [, {argv}]) *rpcstart()*
- Deprecated. Replace >
- :let id = rpcstart('prog', ['arg1', 'arg2'])
-< with >
- :let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true})
+rpcstart({prog} [, {argv}]) *rpcstart()*
+ Deprecated. Replace >vim
+ let id = rpcstart('prog', ['arg1', 'arg2'])
+< with >vim
+ let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true})
+<
-rubyeval({expr}) *rubyeval()*
+rubyeval({expr}) *rubyeval()*
Evaluate Ruby expression {expr} and return its result
converted to Vim data structures.
Numbers, floats and strings are returned as they are (strings
@@ -6385,19 +5801,13 @@ rubyeval({expr}) *rubyeval()*
Other objects are represented as strings resulted from their
"Object#to_s" method.
- Can also be used as a |method|: >
- GetRubyExpr()->rubyeval()
-
-screenattr({row}, {col}) *screenattr()*
+screenattr({row}, {col}) *screenattr()*
Like |screenchar()|, but return the attribute. This is a rather
arbitrary number that can only be used to compare to the
attribute at other positions.
Returns -1 when row or col is out of range.
- Can also be used as a |method|: >
- GetRow()->screenattr(col)
-
-screenchar({row}, {col}) *screenchar()*
+screenchar({row}, {col}) *screenchar()*
The result is a Number, which is the character at position
[row, col] on the screen. This works for every possible
screen position, also status lines, window separators and the
@@ -6407,20 +5817,14 @@ screenchar({row}, {col}) *screenchar()*
This is mainly to be used for testing.
Returns -1 when row or col is out of range.
- Can also be used as a |method|: >
- GetRow()->screenchar(col)
-
-screenchars({row}, {col}) *screenchars()*
- The result is a List of Numbers. The first number is the same
+screenchars({row}, {col}) *screenchars()*
+ The result is a |List| of Numbers. The first number is the same
as what |screenchar()| returns. Further numbers are
composing characters on top of the base character.
This is mainly to be used for testing.
Returns an empty List when row or col is out of range.
- Can also be used as a |method|: >
- GetRow()->screenchars(col)
-
-screencol() *screencol()*
+screencol() *screencol()*
The result is a Number, which is the current screen column of
the cursor. The leftmost column has number 1.
This function is mainly used for testing.
@@ -6429,12 +5833,13 @@ screencol() *screencol()*
in a command (e.g. ":echo screencol()") it will return the
column inside the command line, which is 1 when the command is
executed. To get the cursor position in the file use one of
- the following mappings: >
+ the following mappings: >vim
nnoremap <expr> GG ":echom " .. screencol() .. "\n"
nnoremap <silent> GG :echom screencol()<CR>
noremap GG <Cmd>echom screencol()<Cr>
<
-screenpos({winid}, {lnum}, {col}) *screenpos()*
+
+screenpos({winid}, {lnum}, {col}) *screenpos()*
The result is a Dict with the screen position of the text
character in window {winid} at buffer line {lnum} and column
{col}. {col} is a one-based byte index.
@@ -6454,12 +5859,11 @@ screenpos({winid}, {lnum}, {col}) *screenpos()*
as if 'conceallevel' is zero. You can set the cursor to the
right position and use |screencol()| to get the value with
|conceal| taken into account.
+ If the position is in a closed fold the screen position of the
+ first character is returned, {col} is not used.
Returns an empty Dict if {winid} is invalid.
- Can also be used as a |method|: >
- GetWinid()->screenpos(lnum, col)
-
-screenrow() *screenrow()*
+screenrow() *screenrow()*
The result is a Number, which is the current screen row of the
cursor. The top line has number one.
This function is mainly used for testing.
@@ -6467,7 +5871,7 @@ screenrow() *screenrow()*
Note: Same restrictions as with |screencol()|.
-screenstring({row}, {col}) *screenstring()*
+screenstring({row}, {col}) *screenstring()*
The result is a String that contains the base character and
any composing characters at position [row, col] on the screen.
This is like |screenchars()| but returning a String with the
@@ -6475,11 +5879,7 @@ screenstring({row}, {col}) *screenstring()*
This is mainly to be used for testing.
Returns an empty String when row or col is out of range.
- Can also be used as a |method|: >
- GetRow()->screenstring(col)
-<
- *search()*
-search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
+search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]]) *search()*
Search for regexp pattern {pattern}. The search starts at the
cursor position (you can use |cursor()| to set it).
@@ -6520,7 +5920,7 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
When the {stopline} argument is given then the search stops
after searching this line. This is useful to restrict the
- search to a range of lines. Examples: >
+ search to a range of lines. Examples: >vim
let match = search('(', 'b', line("w0"))
let end = search('END', '', line("w$"))
< When {stopline} is used and it is not zero this also implies
@@ -6551,24 +5951,24 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
The cursor will be positioned at the match, unless the 'n'
flag is used.
- Example (goes over all files in the argument list): >
- :let n = 1
- :while n <= argc() " loop over all files in arglist
- : exe "argument " .. n
- : " start at the last char in the file and wrap for the
- : " first search to find match at start of file
- : normal G$
- : let flags = "w"
- : while search("foo", flags) > 0
- : s/foo/bar/g
- : let flags = "W"
- : endwhile
- : update " write the file if modified
- : let n = n + 1
- :endwhile
-<
- Example for using some flags: >
- :echo search('\<if\|\(else\)\|\(endif\)', 'ncpe')
+ Example (goes over all files in the argument list): >vim
+ let n = 1
+ while n <= argc() " loop over all files in arglist
+ exe "argument " .. n
+ " start at the last char in the file and wrap for the
+ " first search to find match at start of file
+ normal G$
+ let flags = "w"
+ while search("foo", flags) > 0
+ s/foo/bar/g
+ let flags = "W"
+ endwhile
+ update " write the file if modified
+ let n = n + 1
+ endwhile
+<
+ Example for using some flags: >vim
+ echo search('\<if\|\(else\)\|\(endif\)', 'ncpe')
< This will search for the keywords "if", "else", and "endif"
under or after the cursor. Because of the 'p' flag, it
returns 1, 2, or 3 depending on which keyword is found, or 0
@@ -6580,15 +5980,12 @@ search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
without the 'e' flag if the cursor is on the "f" of "if".
The 'n' flag tells the function not to move the cursor.
- Can also be used as a |method|: >
- GetPattern()->search()
-
-searchcount([{options}]) *searchcount()*
+searchcount([{options}]) *searchcount()*
Get or update the last search count, like what is displayed
without the "S" flag in 'shortmess'. This works even if
'shortmess' does contain the "S" flag.
- This returns a Dictionary. The dictionary is empty if the
+ This returns a |Dictionary|. The dictionary is empty if the
previous pattern was not set and "pattern" was not specified.
key type meaning ~
@@ -6608,7 +6005,7 @@ searchcount([{options}]) *searchcount()*
this function with `recompute: 0` . This sometimes returns
wrong information because |n| and |N|'s maximum count is 99.
If it exceeded 99 the result must be max count + 1 (100). If
- you want to get correct information, specify `recompute: 1`: >
+ you want to get correct information, specify `recompute: 1`: >vim
" result == maxcount + 1 (100) when many matches
let result = searchcount(#{recompute: 0})
@@ -6617,7 +6014,7 @@ searchcount([{options}]) *searchcount()*
" to 1)
let result = searchcount()
<
- The function is useful to add the count to 'statusline': >
+ The function is useful to add the count to 'statusline': >vim
function! LastSearchCount() abort
let result = searchcount(#{recompute: 0})
if empty(result)
@@ -6646,7 +6043,7 @@ searchcount([{options}]) *searchcount()*
" \ '%{v:hlsearch ? LastSearchCount() : ""}'
<
You can also update the search count, which can be useful in a
- |CursorMoved| or |CursorMovedI| autocommand: >
+ |CursorMoved| or |CursorMovedI| autocommand: >vim
autocmd CursorMoved,CursorMovedI *
\ let s:searchcount_timer = timer_start(
@@ -6660,7 +6057,7 @@ searchcount([{options}]) *searchcount()*
endfunction
<
This can also be used to count matched texts with specified
- pattern in the current buffer using "pattern": >
+ pattern in the current buffer using "pattern": >vim
" Count '\<foo\>' in this buffer
" (Note that it also updates search count)
@@ -6670,7 +6067,7 @@ searchcount([{options}]) *searchcount()*
" search again
call searchcount()
<
- {options} must be a Dictionary. It can contain:
+ {options} must be a |Dictionary|. It can contain:
key type meaning ~
recompute |Boolean| if |TRUE|, recompute the count
like |n| or |N| was executed.
@@ -6684,7 +6081,7 @@ searchcount([{options}]) *searchcount()*
and different with |@/|.
this works as same as the
below command is executed
- before calling this function >
+ before calling this function >vim
let @/ = pattern
< (default: |@/|)
timeout |Number| 0 or negative number is no
@@ -6704,10 +6101,7 @@ searchcount([{options}]) *searchcount()*
value. see |cursor()|, |getpos()|
(default: cursor's position)
- Can also be used as a |method|: >
- GetSearchOpts()->searchcount()
-<
-searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()*
+searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()*
Search for the declaration of {name}.
With a non-zero {global} argument it works like |gD|, find
@@ -6720,17 +6114,14 @@ searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()*
Moves the cursor to the found match.
Returns zero for success, non-zero for failure.
- Example: >
+ Example: >vim
if searchdecl('myvar') == 0
echo getline('.')
endif
<
- Can also be used as a |method|: >
- GetName()->searchdecl()
-<
- *searchpair()*
-searchpair({start}, {middle}, {end} [, {flags} [, {skip}
- [, {stopline} [, {timeout}]]]])
+
+ *searchpair()*
+searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])
Search for the match of a nested start-end pair. This can be
used to find the "endif" that matches an "if", while other
if/endif pairs in between are ignored.
@@ -6745,8 +6136,8 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip}
must not contain \( \) pairs. Use of \%( \) is allowed. When
{middle} is not empty, it is found when searching from either
direction, but only when not in a nested start-end pair. A
- typical use is: >
- searchpair('\<if\>', '\<else\>', '\<endif\>')
+ typical use is: >vim
+ echo searchpair('\<if\>', '\<else\>', '\<endif\>')
< By leaving {middle} empty the "else" is skipped.
{flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with
@@ -6776,7 +6167,7 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip}
The search starts exactly at the cursor. A match with
{start}, {middle} or {end} at the next character, in the
- direction of searching, is the first one found. Example: >
+ direction of searching, is the first one found. Example: >vim
if 1
if 2
endif 2
@@ -6792,9 +6183,9 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip}
that when the cursor is inside a match with the end it finds
the matching start.
- Example, to find the "endif" command in a Vim script: >
+ Example, to find the "endif" command in a Vim script: >vim
- :echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W',
+ echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W',
\ 'getline(".") =~ "^\\s*\""')
< The cursor must be at or after the "if" for which a match is
@@ -6803,56 +6194,54 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip}
catches comments at the start of a line, not after a command.
Also, a word "en" or "if" halfway through a line is considered
a match.
- Another example, to search for the matching "{" of a "}": >
+ Another example, to search for the matching "{" of a "}": >vim
- :echo searchpair('{', '', '}', 'bW')
+ echo searchpair('{', '', '}', 'bW')
< This works when the cursor is at or before the "}" for which a
match is to be found. To reject matches that syntax
- highlighting recognized as strings: >
+ highlighting recognized as strings: >vim
- :echo searchpair('{', '', '}', 'bW',
- \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')
+ echo searchpair('{', '', '}', 'bW',
+ \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')
<
- *searchpairpos()*
-searchpairpos({start}, {middle}, {end} [, {flags} [, {skip}
- [, {stopline} [, {timeout}]]]])
+
+ *searchpairpos()*
+searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])
Same as |searchpair()|, but returns a |List| with the line and
column position of the match. The first element of the |List|
is the line number and the second element is the byte index of
the column position of the match. If no match is found,
- returns [0, 0]. >
+ returns [0, 0]. >vim
- :let [lnum,col] = searchpairpos('{', '', '}', 'n')
+ let [lnum,col] = searchpairpos('{', '', '}', 'n')
<
See |match-parens| for a bigger and more useful example.
- *searchpos()*
+ *searchpos()*
searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])
Same as |search()|, but returns a |List| with the line and
column position of the match. The first element of the |List|
is the line number and the second element is the byte index of
the column position of the match. If no match is found,
returns [0, 0].
- Example: >
- :let [lnum, col] = searchpos('mypattern', 'n')
+ Example: >vim
+ let [lnum, col] = searchpos('mypattern', 'n')
< When the 'p' flag is given then there is an extra item with
- the sub-pattern match number |search()-sub-match|. Example: >
- :let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np')
+ the sub-pattern match number |search()-sub-match|. Example: >vim
+ let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np')
< In this example "submatch" is 2 when a lowercase letter is
found |/\l|, 3 when an uppercase letter is found |/\u|.
- Can also be used as a |method|: >
- GetPattern()->searchpos()
-
-serverlist() *serverlist()*
+serverlist() *serverlist()*
Returns a list of server addresses, or empty if all servers
were stopped. |serverstart()| |serverstop()|
- Example: >
- :echo serverlist()
+ Example: >vim
+ echo serverlist()
+<
-serverstart([{address}]) *serverstart()*
+serverstart([{address}]) *serverstart()*
Opens a socket or named pipe at {address} and listens for
|RPC| messages. Clients can send |API| commands to the
returned address to control Nvim.
@@ -6865,32 +6254,34 @@ serverstart([{address}]) *serverstart()*
assigns a random port).
- Else {address} is the path to a named pipe (except on Windows).
- If {address} has no slashes ("/") it is treated as the
- "name" part of a generated path in this format: >
+ "name" part of a generated path in this format: >vim
stdpath("run").."/{name}.{pid}.{counter}"
-< - If {address} is omitted the name is "nvim". >
- :echo serverstart()
+< - If {address} is omitted the name is "nvim". >vim
+ echo serverstart()
+< >
=> /tmp/nvim.bram/oknANW/nvim.15430.5
-
-< Example bash command to list all Nvim servers: >
+<
+ Example bash command to list all Nvim servers: >bash
ls ${XDG_RUNTIME_DIR:-${TMPDIR}nvim.${USER}}/*/nvim.*.0
-< Example named pipe: >
+< Example named pipe: >vim
if has('win32')
echo serverstart('\\.\pipe\nvim-pipe-1234')
else
echo serverstart('nvim.sock')
endif
<
- Example TCP/IP address: >
+ Example TCP/IP address: >vim
echo serverstart('::1:12345')
+<
-serverstop({address}) *serverstop()*
+serverstop({address}) *serverstop()*
Closes the pipe or socket at {address}.
Returns TRUE if {address} is valid, else FALSE.
If |v:servername| is stopped it is set to the next available
address in |serverlist()|.
-setbufline({buf}, {lnum}, {text}) *setbufline()*
+setbufline({buf}, {lnum}, {text}) *setbufline()*
Set line {lnum} to {text} in buffer {buf}. This works like
|setline()| for the specified buffer.
@@ -6899,9 +6290,10 @@ setbufline({buf}, {lnum}, {text}) *setbufline()*
To insert lines use |appendbufline()|.
- {text} can be a string to set one line, or a list of strings
- to set multiple lines. If the list extends below the last
- line then those lines are added.
+ {text} can be a string to set one line, or a List of strings
+ to set multiple lines. If the List extends below the last
+ line then those lines are added. If the List is empty then
+ nothing is changed and zero is returned.
For the use of {buf}, see |bufname()| above.
@@ -6914,11 +6306,7 @@ setbufline({buf}, {lnum}, {text}) *setbufline()*
If {buf} is not a valid buffer or {lnum} is not valid, an
error message is given.
- Can also be used as a |method|, the base is passed as the
- third argument: >
- GetText()->setbufline(buf, lnum)
-
-setbufvar({buf}, {varname}, {val}) *setbufvar()*
+setbufvar({buf}, {varname}, {val}) *setbufvar()*
Set option or local variable {varname} in buffer {buf} to
{val}.
This also works for a global or local window option, but it
@@ -6927,21 +6315,16 @@ setbufvar({buf}, {varname}, {val}) *setbufvar()*
For the use of {buf}, see |bufname()| above.
The {varname} argument is a string.
Note that the variable name without "b:" must be used.
- Examples: >
- :call setbufvar(1, "&mod", 1)
- :call setbufvar("todo", "myvar", "foobar")
+ Examples: >vim
+ call setbufvar(1, "&mod", 1)
+ call setbufvar("todo", "myvar", "foobar")
< This function is not available in the |sandbox|.
- Can also be used as a |method|, the base is passed as the
- third argument: >
- GetValue()->setbufvar(buf, varname)
-
-
-setcellwidths({list}) *setcellwidths()*
+setcellwidths({list}) *setcellwidths()*
Specify overrides for cell widths of character ranges. This
tells Vim how wide characters are when displayed in the
terminal, counted in screen cells. The values override
- 'ambiwidth'. Example: >
+ 'ambiwidth'. Example: >vim
call setcellwidths([
\ [0x111, 0x111, 1],
\ [0x2194, 0x2199, 2],
@@ -6957,12 +6340,12 @@ setcellwidths({list}) *setcellwidths()*
{width} must be either 1 or 2, indicating the character width
in screen cells. *E1112*
An error is given if the argument is invalid, also when a
- range overlaps with another. *E1113*
+ range overlaps with another. *E1113*
If the new value causes 'fillchars' or 'listchars' to become
invalid it is rejected and an error is given.
- To clear the overrides pass an empty {list}: >
+ To clear the overrides pass an empty {list}: >vim
call setcellwidths([])
< You can use the script $VIMRUNTIME/tools/emoji_list.vim to see
@@ -6971,22 +6354,18 @@ setcellwidths({list}) *setcellwidths()*
match with what Vim knows about each emoji. If it doesn't
look right you need to adjust the {list} argument.
-
-setcharpos({expr}, {list}) *setcharpos()*
+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: >
+ With the text "여보세요" in line 8: >vim
call setcharpos('.', [0, 8, 4, 0])
-< positions the cursor on the fourth character '요'. >
+< positions the cursor on the fourth character '요'. >vim
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()*
+setcharsearch({dict}) *setcharsearch()*
Set the current character search information to {dict},
which contains one or more of the following entries:
@@ -7000,26 +6379,20 @@ setcharsearch({dict}) *setcharsearch()*
character search
This can be useful to save/restore a user's character search
- from a script: >
- :let prevsearch = getcharsearch()
- :" Perform a command which clobbers user's search
- :call setcharsearch(prevsearch)
+ from a script: >vim
+ let prevsearch = getcharsearch()
+ " Perform a command which clobbers user's search
+ call setcharsearch(prevsearch)
< Also see |getcharsearch()|.
- Can also be used as a |method|: >
- SavedSearch()->setcharsearch()
-
-setcmdline({str} [, {pos}]) *setcmdline()*
+setcmdline({str} [, {pos}]) *setcmdline()*
Set the command line to {str} and set the cursor position to
{pos}.
If {pos} is omitted, the cursor is positioned after the text.
Returns 0 when successful, 1 when not editing the command
line.
- Can also be used as a |method|: >
- GetText()->setcmdline()
-
-setcmdpos({pos}) *setcmdpos()*
+setcmdpos({pos}) *setcmdpos()*
Set the cursor position in the command line to byte position
{pos}. The first position is 1.
Use |getcmdpos()| to obtain the current position.
@@ -7034,36 +6407,26 @@ setcmdpos({pos}) *setcmdpos()*
Returns 0 when successful, 1 when not editing the command
line.
- Can also be used as a |method|: >
- GetPos()->setcmdpos()
-
-setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()*
-setcursorcharpos({list})
+setcursorcharpos({lnum}, {col} [, {off}])
+setcursorcharpos({list}) *setcursorcharpos()*
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: >
+ With the text "여보세요" in line 4: >vim
call setcursorcharpos(4, 3)
-< positions the cursor on the third character '세'. >
+< positions the cursor on the third character '세'. >vim
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: >
+setenv({name}, {val}) *setenv()*
+ Set environment variable {name} to {val}. Example: >vim
call setenv('HOME', '/home/myhome')
< When {val} is |v:null| the environment variable is deleted.
See also |expr-env|.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetPath()->setenv('PATH')
-
-setfperm({fname}, {mode}) *setfperm()* *chmod*
+setfperm({fname}, {mode}) *setfperm()* *chmod*
Set the file permissions for {fname} to {mode}.
{mode} must be a string with 9 characters. It is of the form
"rwxrwxrwx", where each group of "rwx" flags represent, in
@@ -7078,12 +6441,9 @@ setfperm({fname}, {mode}) *setfperm()* *chmod*
Returns non-zero for success, zero for failure.
- Can also be used as a |method|: >
- GetFilename()->setfperm(mode)
-<
To read permissions see |getfperm()|.
-setline({lnum}, {text}) *setline()*
+setline({lnum}, {text}) *setline()*
Set line {lnum} of the current buffer to {text}. To insert
lines use |append()|. To set lines in another buffer use
|setbufline()|.
@@ -7092,29 +6452,26 @@ setline({lnum}, {text}) *setline()*
When {lnum} is just below the last line the {text} will be
added below the last line.
{text} can be any type or a List of any type, each item is
- converted to a String.
+ converted to a String. When {text} is an empty List then
+ nothing is changed and FALSE is returned.
If this succeeds, FALSE is returned. If this fails (most likely
because {lnum} is invalid) TRUE is returned.
- Example: >
- :call setline(5, strftime("%c"))
+ Example: >vim
+ call setline(5, strftime("%c"))
< When {text} is a |List| then line {lnum} and following lines
- will be set to the items in the list. Example: >
- :call setline(5, ['aaa', 'bbb', 'ccc'])
-< This is equivalent to: >
- :for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']]
- : call setline(n, l)
- :endfor
+ will be set to the items in the list. Example: >vim
+ call setline(5, ['aaa', 'bbb', 'ccc'])
+< This is equivalent to: >vim
+ for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']]
+ call setline(n, l)
+ endfor
< Note: The '[ and '] marks are not set.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetText()->setline(lnum)
-
-setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()*
+setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()*
Create or replace or add to 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.
@@ -7130,11 +6487,7 @@ setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()*
only the items listed in {what} are set. Refer to |setqflist()|
for the list of supported keys in {what}.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetLoclist()->setloclist(winnr)
-
-setmatches({list} [, {win}]) *setmatches()*
+setmatches({list} [, {win}]) *setmatches()*
Restores a list of matches saved by |getmatches()| for the
current window. Returns 0 if successful, otherwise -1. All
current matches are cleared before the list is restored. See
@@ -7142,11 +6495,7 @@ setmatches({list} [, {win}]) *setmatches()*
If {win} is specified, use the window with this number or
window ID instead of the current window.
- Can also be used as a |method|: >
- GetMatches()->setmatches()
-<
- *setpos()*
-setpos({expr}, {list})
+setpos({expr}, {list}) *setpos()*
Set the position for String {expr}. Possible values:
. the cursor
'x mark x
@@ -7195,10 +6544,7 @@ setpos({expr}, {list})
also set the preferred column. Also see the "curswant" key in
|winrestview()|.
- Can also be used as a |method|: >
- GetPosition()->setpos('.')
-
-setqflist({list} [, {action} [, {what}]]) *setqflist()*
+setqflist({list} [, {action} [, {what}]]) *setqflist()*
Create or replace or add to the quickfix list.
If the optional {what} dictionary argument is supplied, then
@@ -7228,6 +6574,9 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
text description of the error
type single-character error type, 'E', 'W', etc.
valid recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
The "col", "vcol", "nr", "type" and "text" entries are
optional. Either "lnum" or "pattern" entry can be used to
@@ -7251,8 +6600,8 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
'r' The items from the current quickfix list are replaced
with the items from {list}. This can also be used to
- clear the list: >
- :call setqflist([], 'r')
+ clear the list: >vim
+ call setqflist([], 'r')
<
'f' All the quickfix lists in the quickfix stack are
freed.
@@ -7298,10 +6647,10 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
list is modified, "id" should be used instead of "nr" to
specify the list.
- Examples (See also |setqflist-examples|): >
- :call setqflist([], 'r', {'title': 'My search'})
- :call setqflist([], 'r', {'nr': 2, 'title': 'Errors'})
- :call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]})
+ Examples (See also |setqflist-examples|): >vim
+ call setqflist([], 'r', {'title': 'My search'})
+ call setqflist([], 'r', {'nr': 2, 'title': 'Errors'})
+ call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]})
<
Returns zero for success, -1 for failure.
@@ -7309,12 +6658,7 @@ setqflist({list} [, {action} [, {what}]]) *setqflist()*
independent of the 'errorformat' setting. Use a command like
`:cc 1` to jump to the first position.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetErrorlist()->setqflist()
-<
- *setreg()*
-setreg({regname}, {value} [, {options}])
+setreg({regname}, {value} [, {options}]) *setreg()*
Set the register {regname} to {value}.
If {regname} is "" or "@", the unnamed register '"' is used.
The {regname} argument is a string.
@@ -7346,35 +6690,31 @@ setreg({regname}, {value} [, {options}])
set search and expression registers. Lists containing no
items act like empty strings.
- Examples: >
- :call setreg(v:register, @*)
- :call setreg('*', @%, 'ac')
- :call setreg('a', "1\n2\n3", 'b5')
- :call setreg('"', { 'points_to': 'a'})
+ Examples: >vim
+ call setreg(v:register, @*)
+ call setreg('*', @%, 'ac')
+ call setreg('a', "1\n2\n3", 'b5')
+ call setreg('"', { 'points_to': 'a'})
< This example shows using the functions to save and restore a
- register: >
- :let var_a = getreginfo()
- :call setreg('a', var_a)
-< or: >
- :let var_a = getreg('a', 1, 1)
- :let var_amode = getregtype('a')
- ....
- :call setreg('a', var_a, var_amode)
+ register: >vim
+ let var_a = getreginfo()
+ call setreg('a', var_a)
+< or: >vim
+ let var_a = getreg('a', 1, 1)
+ let var_amode = getregtype('a')
+ " ....
+ call setreg('a', var_a, var_amode)
< Note: you may not reliably restore register value
without using the third argument to |getreg()| as without it
newlines are represented as newlines AND Nul bytes are
represented as newlines as well, see |NL-used-for-Nul|.
You can also change the type of a register by appending
- nothing: >
- :call setreg('a', '', 'al')
-
-< Can also be used as a |method|, the base is passed as the
- second argument: >
- GetText()->setreg('a')
+ nothing: >vim
+ call setreg('a', '', 'al')
-settabvar({tabnr}, {varname}, {val}) *settabvar()*
+settabvar({tabnr}, {varname}, {val}) *settabvar()*
Set tab-local variable {varname} to {val} in tab page {tabnr}.
|t:var|
The {varname} argument is a string.
@@ -7382,11 +6722,7 @@ settabvar({tabnr}, {varname}, {val}) *settabvar()*
Tabs are numbered starting with one.
This function is not available in the |sandbox|.
- Can also be used as a |method|, the base is passed as the
- third argument: >
- GetValue()->settabvar(tab, name)
-
-settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()*
+settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()*
Set option or local variable {varname} in window {winnr} to
{val}.
Tabs are numbered starting with one. For the current tabpage
@@ -7397,16 +6733,12 @@ settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()*
doesn't work for a global or local buffer variable.
For a local buffer option the global value is unchanged.
Note that the variable name without "w:" must be used.
- Examples: >
- :call settabwinvar(1, 1, "&list", 0)
- :call settabwinvar(3, 2, "myvar", "foobar")
+ Examples: >vim
+ call settabwinvar(1, 1, "&list", 0)
+ call settabwinvar(3, 2, "myvar", "foobar")
< This function is not available in the |sandbox|.
- Can also be used as a |method|, the base is passed as the
- fourth argument: >
- GetValue()->settabwinvar(tab, winnr, name)
-
-settagstack({nr}, {dict} [, {action}]) *settagstack()*
+settagstack({nr}, {dict} [, {action}]) *settagstack()*
Modify the tag stack of the window {nr} using {dict}.
{nr} can be the window number or the |window-ID|.
@@ -7430,37 +6762,27 @@ settagstack({nr}, {dict} [, {action}]) *settagstack()*
Returns zero for success, -1 for failure.
Examples (for more examples see |tagstack-examples|):
- Empty the tag stack of window 3: >
+ Empty the tag stack of window 3: >vim
call settagstack(3, {'items' : []})
-< Save and restore the tag stack: >
+< Save and restore the tag stack: >vim
let stack = gettagstack(1003)
" do something else
call settagstack(1003, stack)
unlet stack
<
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetStack()->settagstack(winnr)
-setwinvar({nr}, {varname}, {val}) *setwinvar()*
+setwinvar({nr}, {varname}, {val}) *setwinvar()*
Like |settabwinvar()| for the current tab page.
- Examples: >
- :call setwinvar(1, "&list", 0)
- :call setwinvar(2, "myvar", "foobar")
+ Examples: >vim
+ call setwinvar(1, "&list", 0)
+ call setwinvar(2, "myvar", "foobar")
-< Can also be used as a |method|, the base is passed as the
- third argument: >
- GetValue()->setwinvar(winnr, name)
-
-sha256({string}) *sha256()*
+sha256({string}) *sha256()*
Returns a String with 64 hex characters, which is the SHA256
checksum of {string}.
- Can also be used as a |method|: >
- GetText()->sha256()
-
-shellescape({string} [, {special}]) *shellescape()*
+shellescape({string} [, {special}]) *shellescape()*
Escape {string} for use as a shell command argument.
On Windows when 'shellslash' is not set, encloses {string} in
@@ -7484,21 +6806,18 @@ shellescape({string} [, {special}]) *shellescape()*
be escaped because in fish it is used as an escape character
inside single quotes.
- Example of use with a |:!| command: >
- :exe '!dir ' .. shellescape(expand('<cfile>'), 1)
+ Example of use with a |:!| command: >vim
+ exe '!dir ' .. shellescape(expand('<cfile>'), 1)
< This results in a directory listing for the file under the
- cursor. Example of use with |system()|: >
- :call system("chmod +w -- " .. shellescape(expand("%")))
+ cursor. Example of use with |system()|: >vim
+ call system("chmod +w -- " .. shellescape(expand("%")))
< See also |::S|.
- Can also be used as a |method|: >
- GetCommand()->shellescape()
-
-shiftwidth([{col}]) *shiftwidth()*
+shiftwidth([{col}]) *shiftwidth()*
Returns the effective value of 'shiftwidth'. This is the
'shiftwidth' value unless it is zero, in which case it is the
'tabstop' value. To be backwards compatible in indent
- plugins, use this: >
+ plugins, use this: >vim
if exists('*shiftwidth')
func s:sw()
return shiftwidth()
@@ -7515,12 +6834,370 @@ shiftwidth([{col}]) *shiftwidth()*
'vartabstop' feature. If no {col} argument is given, column 1
will be assumed.
- Can also be used as a |method|: >
- GetColumn()->shiftwidth()
+sign_define({name} [, {dict}])
+sign_define({list}) *sign_define()*
+ Define a new sign named {name} or modify the attributes of an
+ existing sign. This is similar to the |:sign-define| command.
+
+ Prefix {name} with a unique text to avoid name collisions.
+ There is no {group} like with placing signs.
+
+ The {name} can be a String or a Number. The optional {dict}
+ argument specifies the sign attributes. The following values
+ are supported:
+ icon full path to the bitmap file for the sign.
+ linehl highlight group used for the whole line the
+ sign is placed in.
+ numhl highlight group used for the line number where
+ the sign is placed.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled.
+
+ If the sign named {name} already exists, then the attributes
+ of the sign are updated.
+
+ The one argument {list} can be used to define a list of signs.
+ Each list item is a dictionary with the above items in {dict}
+ and a "name" item for the sign name.
+
+ Returns 0 on success and -1 on failure. When the one argument
+ {list} is used, then returns a List of values one for each
+ defined sign.
+
+ Examples: >vim
+ call sign_define("mySign", {
+ \ "text" : "=>",
+ \ "texthl" : "Error",
+ \ "linehl" : "Search"})
+ call sign_define([
+ \ {'name' : 'sign1',
+ \ 'text' : '=>'},
+ \ {'name' : 'sign2',
+ \ 'text' : '!!'}
+ \ ])
+<
+
+sign_getdefined([{name}]) *sign_getdefined()*
+ Get a list of defined signs and their attributes.
+ This is similar to the |:sign-list| command.
+
+ If the {name} is not supplied, then a list of all the defined
+ signs is returned. Otherwise the attribute of the specified
+ sign is returned.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ icon full path to the bitmap file of the sign
+ linehl highlight group used for the whole line the
+ sign is placed in; not present if not set.
+ name name of the sign
+ numhl highlight group used for the line number where
+ the sign is placed; not present if not set.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item; not
+ present if not set.
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled; not present if not
+ set.
+
+ Returns an empty List if there are no signs and when {name} is
+ not found.
+
+ Examples: >vim
+ " Get a list of all the defined signs
+ echo sign_getdefined()
+
+ " Get the attribute of the sign named mySign
+ echo sign_getdefined("mySign")
+<
+
+sign_getplaced([{buf} [, {dict}]]) *sign_getplaced()*
+ Return a list of signs placed in a buffer or all the buffers.
+ This is similar to the |:sign-place-list| command.
+
+ If the optional buffer name {buf} is specified, then only the
+ list of signs placed in that buffer is returned. For the use
+ of {buf}, see |bufname()|. The optional {dict} can contain
+ the following entries:
+ group select only signs in this group
+ id select sign with this identifier
+ lnum select signs placed in this line. For the use
+ of {lnum}, see |line()|.
+ If {group} is "*", then signs in all the groups including the
+ global group are returned. If {group} is not supplied or is an
+ empty string, then only signs in the global group are
+ returned. If no arguments are supplied, then signs in the
+ global group placed in all the buffers are returned.
+ See |sign-group|.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ bufnr number of the buffer with the sign
+ signs list of signs placed in {bufnr}. Each list
+ item is a dictionary with the below listed
+ entries
+
+ The dictionary for each sign contains the following entries:
+ group sign group. Set to '' for the global group.
+ id identifier of the sign
+ lnum line number where the sign is placed
+ name name of the defined sign
+ priority sign priority
+
+ The returned signs in a buffer are ordered by their line
+ number and priority.
+
+ Returns an empty list on failure or if there are no placed
+ signs.
+
+ Examples: >vim
+ " Get a List of signs placed in eval.c in the
+ " global group
+ echo sign_getplaced("eval.c")
+
+ " Get a List of signs in group 'g1' placed in eval.c
+ echo sign_getplaced("eval.c", {'group' : 'g1'})
+
+ " Get a List of signs placed at line 10 in eval.c
+ echo sign_getplaced("eval.c", {'lnum' : 10})
+
+ " Get sign with identifier 10 placed in a.py
+ echo sign_getplaced("a.py", {'id' : 10})
+
+ " Get sign with id 20 in group 'g1' placed in a.py
+ echo sign_getplaced("a.py", {'group' : 'g1',
+ \ 'id' : 20})
+
+ " Get a List of all the placed signs
+ echo sign_getplaced()
+<
+
+sign_jump({id}, {group}, {buf}) *sign_jump()*
+ Open the buffer {buf} or jump to the window that contains
+ {buf} and position the cursor at sign {id} in group {group}.
+ This is similar to the |:sign-jump| command.
+
+ If {group} is an empty string, then the global group is used.
+ For the use of {buf}, see |bufname()|.
+
+ Returns the line number of the sign. Returns -1 if the
+ arguments are invalid.
+
+ Example: >vim
+ " Jump to sign 10 in the current buffer
+ call sign_jump(10, '', '')
+<
+
+sign_place({id}, {group}, {name}, {buf} [, {dict}]) *sign_place()*
+ Place the sign defined as {name} at line {lnum} in file or
+ buffer {buf} and assign {id} and {group} to sign. This is
+ similar to the |:sign-place| command.
+
+ If the sign identifier {id} is zero, then a new identifier is
+ allocated. Otherwise the specified number is used. {group} is
+ the sign group name. To use the global sign group, use an
+ empty string. {group} functions as a namespace for {id}, thus
+ two groups can use the same IDs. Refer to |sign-identifier|
+ and |sign-group| for more information.
+
+ {name} refers to a defined sign.
+ {buf} refers to a buffer name or number. For the accepted
+ values, see |bufname()|.
-sign_ functions are documented here: |sign-functions-details|
+ The optional {dict} argument supports the following entries:
+ lnum line number in the file or buffer
+ {buf} where the sign is to be placed.
+ For the accepted values, see |line()|.
+ priority priority of the sign. See
+ |sign-priority| for more information.
-simplify({filename}) *simplify()*
+ If the optional {dict} is not specified, then it modifies the
+ placed sign {id} in group {group} to use the defined sign
+ {name}.
+
+ Returns the sign identifier on success and -1 on failure.
+
+ Examples: >vim
+ " Place a sign named sign1 with id 5 at line 20 in
+ " buffer json.c
+ call sign_place(5, '', 'sign1', 'json.c',
+ \ {'lnum' : 20})
+
+ " Updates sign 5 in buffer json.c to use sign2
+ call sign_place(5, '', 'sign2', 'json.c')
+
+ " Place a sign named sign3 at line 30 in
+ " buffer json.c with a new identifier
+ let id = sign_place(0, '', 'sign3', 'json.c',
+ \ {'lnum' : 30})
+
+ " Place a sign named sign4 with id 10 in group 'g3'
+ " at line 40 in buffer json.c with priority 90
+ call sign_place(10, 'g3', 'sign4', 'json.c',
+ \ {'lnum' : 40, 'priority' : 90})
+<
+
+sign_placelist({list}) *sign_placelist()*
+ Place one or more signs. This is similar to the
+ |sign_place()| function. The {list} argument specifies the
+ List of signs to place. Each list item is a dict with the
+ following sign attributes:
+ buffer Buffer name or number. For the accepted
+ values, see |bufname()|.
+ group Sign group. {group} functions as a namespace
+ for {id}, thus two groups can use the same
+ IDs. If not specified or set to an empty
+ string, then the global group is used. See
+ |sign-group| for more information.
+ id Sign identifier. If not specified or zero,
+ then a new unique identifier is allocated.
+ Otherwise the specified number is used. See
+ |sign-identifier| for more information.
+ lnum Line number in the buffer where the sign is to
+ be placed. For the accepted values, see
+ |line()|.
+ name Name of the sign to place. See |sign_define()|
+ for more information.
+ priority Priority of the sign. When multiple signs are
+ placed on a line, the sign with the highest
+ priority is used. If not specified, the
+ default value of 10 is used. See
+ |sign-priority| for more information.
+
+ If {id} refers to an existing sign, then the existing sign is
+ modified to use the specified {name} and/or {priority}.
+
+ Returns a List of sign identifiers. If failed to place a
+ sign, the corresponding list item is set to -1.
+
+ Examples: >vim
+ " Place sign s1 with id 5 at line 20 and id 10 at line
+ " 30 in buffer a.c
+ let [n1, n2] = sign_placelist([
+ \ {'id' : 5,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 20},
+ \ {'id' : 10,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 30}
+ \ ])
+
+ " Place sign s1 in buffer a.c at line 40 and 50
+ " with auto-generated identifiers
+ let [n1, n2] = sign_placelist([
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 40},
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 50}
+ \ ])
+<
+
+sign_undefine([{name}])
+sign_undefine({list}) *sign_undefine()*
+ Deletes a previously defined sign {name}. This is similar to
+ the |:sign-undefine| command. If {name} is not supplied, then
+ deletes all the defined signs.
+
+ The one argument {list} can be used to undefine a list of
+ signs. Each list item is the name of a sign.
+
+ Returns 0 on success and -1 on failure. For the one argument
+ {list} call, returns a list of values one for each undefined
+ sign.
+
+ Examples: >vim
+ " Delete a sign named mySign
+ call sign_undefine("mySign")
+
+ " Delete signs 'sign1' and 'sign2'
+ call sign_undefine(["sign1", "sign2"])
+
+ " Delete all the signs
+ call sign_undefine()
+<
+
+sign_unplace({group} [, {dict}]) *sign_unplace()*
+ Remove a previously placed sign in one or more buffers. This
+ is similar to the |:sign-unplace| command.
+
+ {group} is the sign group name. To use the global sign group,
+ use an empty string. If {group} is set to "*", then all the
+ groups including the global group are used.
+ The signs in {group} are selected based on the entries in
+ {dict}. The following optional entries in {dict} are
+ supported:
+ buffer buffer name or number. See |bufname()|.
+ id sign identifier
+ If {dict} is not supplied, then all the signs in {group} are
+ removed.
+
+ Returns 0 on success and -1 on failure.
+
+ Examples: >vim
+ " Remove sign 10 from buffer a.vim
+ call sign_unplace('', {'buffer' : "a.vim", 'id' : 10})
+
+ " Remove sign 20 in group 'g1' from buffer 3
+ call sign_unplace('g1', {'buffer' : 3, 'id' : 20})
+
+ " Remove all the signs in group 'g2' from buffer 10
+ call sign_unplace('g2', {'buffer' : 10})
+
+ " Remove sign 30 in group 'g3' from all the buffers
+ call sign_unplace('g3', {'id' : 30})
+
+ " Remove all the signs placed in buffer 5
+ call sign_unplace('*', {'buffer' : 5})
+
+ " Remove the signs in group 'g4' from all the buffers
+ call sign_unplace('g4')
+
+ " Remove sign 40 from all the buffers
+ call sign_unplace('*', {'id' : 40})
+
+ " Remove all the placed signs from all the buffers
+ call sign_unplace('*')
+
+sign_unplacelist({list}) *sign_unplacelist()*
+ Remove previously placed signs from one or more buffers. This
+ is similar to the |sign_unplace()| function.
+
+ The {list} argument specifies the List of signs to remove.
+ Each list item is a dict with the following sign attributes:
+ buffer buffer name or number. For the accepted
+ values, see |bufname()|. If not specified,
+ then the specified sign is removed from all
+ the buffers.
+ group sign group name. If not specified or set to an
+ empty string, then the global sign group is
+ used. If set to "*", then all the groups
+ including the global group are used.
+ id sign identifier. If not specified, then all
+ the signs in the specified group are removed.
+
+ Returns a List where an entry is set to 0 if the corresponding
+ sign was successfully removed or -1 on failure.
+
+ Example: >vim
+ " Remove sign with id 10 from buffer a.vim and sign
+ " with id 20 from buffer b.vim
+ call sign_unplacelist([
+ \ {'id' : 10, 'buffer' : "a.vim"},
+ \ {'id' : 20, 'buffer' : 'b.vim'},
+ \ ])
+<
+
+simplify({filename}) *simplify()*
Simplify the file name as much as possible without changing
the meaning. Shortcuts (on MS-Windows) or symbolic links (on
Unix) are not resolved. If the first path component in
@@ -7529,7 +7206,7 @@ simplify({filename}) *simplify()*
not removed either. On Unix "//path" is unchanged, but
"///path" is simplified to "/path" (this follows the Posix
standard).
- Example: >
+ Example: >vim
simplify("./dir/.././/file/") == "./file/"
< Note: The combination "dir/.." is only removed if "dir" is
a searchable directory or does not exist. On Unix, it is also
@@ -7537,42 +7214,42 @@ simplify({filename}) *simplify()*
directory. In order to resolve all the involved symbolic
links before simplifying the path name, use |resolve()|.
- Can also be used as a |method|: >
- GetName()->simplify()
-
-sin({expr}) *sin()*
+sin({expr}) *sin()*
Return the sine of {expr}, measured in radians, as a |Float|.
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo sin(100)
-< -0.506366 >
- :echo sin(-4.01)
+ Examples: >vim
+ echo sin(100)
+< -0.506366 >vim
+ echo sin(-4.01)
< 0.763301
- Can also be used as a |method|: >
- Compute()->sin()
-
-sinh({expr}) *sinh()*
+sinh({expr}) *sinh()*
Return the hyperbolic sine of {expr} as a |Float| in the range
[-inf, inf].
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo sinh(0.5)
-< 0.521095 >
- :echo sinh(-0.9)
+ Examples: >vim
+ echo sinh(0.5)
+< 0.521095 >vim
+ echo sinh(-0.9)
< -1.026517
- Can also be used as a |method|: >
- Compute()->sinh()
+slice({expr}, {start} [, {end}]) *slice()*
+ Similar to using a |slice| "expr[start : end]", but "end" is
+ used exclusive. And for a string the indexes are used as
+ character indexes instead of byte indexes.
+ Also, composing characters are not counted.
+ When {end} is omitted the slice continues to the last item.
+ When {end} is -1 the last item is omitted.
+ Returns an empty value if {start} or {end} are invalid.
-sockconnect({mode}, {address} [, {opts}]) *sockconnect()*
+sockconnect({mode}, {address} [, {opts}]) *sockconnect()*
Connect a socket to an address. If {mode} is "pipe" then
{address} should be the path of a local domain socket (on
unix) or named pipe (on Windows). If {mode} is "tcp" then
{address} should be of the form "host:port" where the host
- should be an ip adderess or host name, and port the port
+ should be an ip address or host name, and port the port
number.
For "pipe" mode, see |luv-pipe-handle|. For "tcp" mode, see
@@ -7592,50 +7269,51 @@ sockconnect({mode}, {address} [, {opts}]) *sockconnect()*
- The channel ID on success (greater than zero)
- 0 on invalid arguments or connection failure.
-sort({list} [, {func} [, {dict}]]) *sort()* *E702*
+sort({list} [, {how} [, {dict}]]) *sort()* *E702*
Sort the items in {list} in-place. Returns {list}.
- If you want a list to remain unmodified make a copy first: >
- :let sortedlist = sort(copy(mylist))
+ If you want a list to remain unmodified make a copy first: >vim
+ let sortedlist = sort(copy(mylist))
-< When {func} is omitted, is empty or zero, then sort() uses the
+< When {how} is omitted or is a string, then sort() uses the
string representation of each item to sort on. Numbers sort
after Strings, |Lists| after Numbers. For sorting text in the
current buffer use |:sort|.
- When {func} is given and it is '1' or 'i' then case is
- ignored.
+ When {how} is given and it is 'i' then case is ignored.
+ For backwards compatibility, the value one can be used to
+ ignore case. Zero means to not ignore case.
- When {func} is given and it is 'l' then the current collation
+ When {how} is given and it is 'l' then the current collation
locale is used for ordering. Implementation details: strcoll()
is used to compare strings. See |:language| check or set the
collation locale. |v:collate| can also be used to check the
current locale. Sorting using the locale typically ignores
- case. Example: >
+ case. Example: >vim
" ö is sorted similarly to o with English locale.
- :language collate en_US.UTF8
- :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ language collate en_US.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
< ['n', 'o', 'O', 'ö', 'p', 'z'] ~
->
+>vim
" ö is sorted after z with Swedish locale.
- :language collate sv_SE.UTF8
- :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ language collate sv_SE.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
< ['n', 'o', 'O', 'p', 'z', 'ö'] ~
This does not work properly on Mac.
- When {func} is given and it is 'n' then all items will be
+ When {how} is given and it is 'n' then all items will be
sorted numerical (Implementation detail: this uses the
strtod() function to parse numbers, Strings, Lists, Dicts and
Funcrefs will be considered as being 0).
- When {func} is given and it is 'N' then all items will be
+ When {how} is given and it is 'N' then all items will be
sorted numerical. This is like 'n' but a string containing
digits will be used as the number they represent.
- When {func} is given and it is 'f' then all items will be
+ When {how} is given and it is 'f' then all items will be
sorted numerical. All values must be a Number or a Float.
- When {func} is a |Funcref| or a function name, this function
+ When {how} is a |Funcref| or a function name, this function
is called to compare items. The function is invoked with two
items as argument and must return zero if they are equal, 1 or
bigger if the first one sorts after the second one, -1 or
@@ -7649,26 +7327,22 @@ sort({list} [, {func} [, {dict}]]) *sort()* *E702*
on numbers, text strings will sort next to each other, in the
same order as they were originally.
- Can also be used as a |method|: >
- mylist->sort()
-< Also see |uniq()|.
-
- Example: >
+ Example: >vim
func MyCompare(i1, i2)
return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
endfunc
eval mylist->sort("MyCompare")
< A shorter compare version for this specific simple case, which
- ignores overflow: >
+ ignores overflow: >vim
func MyCompare(i1, i2)
return a:i1 - a:i2
endfunc
-< For a simple expression you can use a lambda: >
+< For a simple expression you can use a lambda: >vim
eval mylist->sort({i1, i2 -> i1 - i2})
<
- *soundfold()*
-soundfold({word})
+
+soundfold({word}) *soundfold()*
Return the sound-folded equivalent of {word}. Uses the first
language in 'spelllang' for the current window that supports
soundfolding. 'spell' must be set. When no sound folding is
@@ -7676,11 +7350,7 @@ soundfold({word})
This can be used for making spelling suggestions. Note that
the method can be quite slow.
- Can also be used as a |method|: >
- GetWord()->soundfold()
-<
- *spellbadword()*
-spellbadword([{sentence}])
+spellbadword([{sentence}]) *spellbadword()*
Without argument: The result is the badly spelled word under
or after the cursor. The cursor is moved to the start of the
bad word. When no bad word is found in the cursor line the
@@ -7697,18 +7367,14 @@ spellbadword([{sentence}])
"rare" rare word
"local" word only valid in another region
"caps" word should start with Capital
- Example: >
+ Example: >vim
echo spellbadword("the quik brown fox")
< ['quik', 'bad'] ~
The spelling information for the current window and the value
of 'spelllang' are used.
- Can also be used as a |method|: >
- GetText()->spellbadword()
-<
- *spellsuggest()*
-spellsuggest({word} [, {max} [, {capital}]])
+spellsuggest({word} [, {max} [, {capital}]]) *spellsuggest()*
Return a |List| with spelling suggestions to replace {word}.
When {max} is given up to this number of suggestions are
returned. Otherwise up to 25 suggestions are returned.
@@ -7729,10 +7395,7 @@ spellsuggest({word} [, {max} [, {capital}]])
The spelling information for the current window is used. The
values of 'spelllang' and 'spellsuggest' are used.
- Can also be used as a |method|: >
- GetWord()->spellsuggest()
-
-split({string} [, {pattern} [, {keepempty}]]) *split()*
+split({string} [, {pattern} [, {keepempty}]]) *split()*
Make a |List| out of {string}. When {pattern} is omitted or
empty each white-separated sequence of characters becomes an
item.
@@ -7743,38 +7406,34 @@ split({string} [, {pattern} [, {keepempty}]]) *split()*
{keepempty} argument is given and it's non-zero.
Other empty items are kept when {pattern} matches at least one
character or when {keepempty} is non-zero.
- Example: >
- :let words = split(getline('.'), '\W\+')
-< To split a string in individual characters: >
- :for c in split(mystring, '\zs')
+ Example: >vim
+ let words = split(getline('.'), '\W\+')
+< To split a string in individual characters: >vim
+ for c in split(mystring, '\zs') | endfor
< If you want to keep the separator you can also use '\zs' at
- the end of the pattern: >
- :echo split('abc:def:ghi', ':\zs')
-< ['abc:', 'def:', 'ghi'] ~
- Splitting a table where the first element can be empty: >
- :let items = split(line, ':', 1)
+ the end of the pattern: >vim
+ echo split('abc:def:ghi', ':\zs')
+< >
+ ['abc:', 'def:', 'ghi']
+<
+ Splitting a table where the first element can be empty: >vim
+ let items = split(line, ':', 1)
< The opposite function is |join()|.
- Can also be used as a |method|: >
- GetString()->split()
-
-sqrt({expr}) *sqrt()*
+sqrt({expr}) *sqrt()*
Return the non-negative square root of Float {expr} as a
|Float|.
{expr} must evaluate to a |Float| or a |Number|. When {expr}
is negative the result is NaN (Not a Number). Returns 0.0 if
{expr} is not a |Float| or a |Number|.
- Examples: >
- :echo sqrt(100)
-< 10.0 >
- :echo sqrt(-4.01)
+ Examples: >vim
+ echo sqrt(100)
+< 10.0 >vim
+ echo sqrt(-4.01)
< str2float("nan")
NaN may be different, it depends on system libraries.
- Can also be used as a |method|: >
- Compute()->sqrt()
-
-srand([{expr}]) *srand()*
+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)
@@ -7783,15 +7442,43 @@ srand([{expr}]) *srand()*
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()*
+ Examples: >vim
+ let seed = srand()
+ let seed = srand(userinput)
+ echo rand(seed)
+<
+
+state([{what}]) *state()*
+ Return a string which contains characters indicating the
+ current state. Mostly useful in callbacks that want to do
+ work that may not always be safe. Roughly this works like:
+ - callback uses state() to check if work is safe to do.
+ Yes: then do it right away.
+ No: add to work queue and add a |SafeState| autocommand.
+ - When SafeState is triggered and executes your autocommand,
+ check with `state()` if the work can be done now, and if yes
+ remove it from the queue and execute.
+ Remove the autocommand if the queue is now empty.
+ Also see |mode()|.
+
+ When {what} is given only characters in this string will be
+ added. E.g, this checks if the screen has scrolled: >vim
+ if state('s') == ''
+ " screen has not scrolled
+<
+ These characters indicate the state, generally indicating that
+ something is busy:
+ m halfway a mapping, :normal command, feedkeys() or
+ stuffed command
+ o operator pending, e.g. after |d|
+ a Insert mode autocomplete active
+ x executing an autocommand
+ S not triggering SafeState, e.g. after |f| or a count
+ c callback invoked, including timer (repeats for
+ recursiveness up to "ccc")
+ s screen has scrolled for messages
+
+stdioopen({opts}) *stdioopen()*
With |--headless| this opens stdin and stdout as a |channel|.
May be called only once. See |channel-stdio|. stderr is not
handled by this function, see |v:stderr|.
@@ -7812,8 +7499,7 @@ stdioopen({opts}) *stdioopen()*
- |channel-id| on success (value is always 1)
- 0 on invalid arguments
-
-stdpath({what}) *stdpath()* *E6100*
+stdpath({what}) *stdpath()* *E6100*
Returns |standard-path| locations of various default files and
directories.
@@ -7831,11 +7517,11 @@ stdpath({what}) *stdpath()* *E6100*
state String Session state directory: storage for file
drafts, swap, undo, |shada|.
- Example: >
- :echo stdpath("config")
-
+ Example: >vim
+ echo stdpath("config")
+<
-str2float({string} [, {quoted}]) *str2float()*
+str2float({string} [, {quoted}]) *str2float()*
Convert String {string} to a Float. This mostly works the
same as when using a floating point number in an expression,
see |floating-point-format|. But it's a bit more permissive.
@@ -7849,30 +7535,24 @@ str2float({string} [, {quoted}]) *str2float()*
The decimal point is always '.', no matter what the locale is
set to. A comma ends the number: "12,345.67" is converted to
12.0. You can strip out thousands separators with
- |substitute()|: >
+ |substitute()|: >vim
let f = str2float(substitute(text, ',', '', 'g'))
<
Returns 0.0 if the conversion fails.
- Can also be used as a |method|: >
- let f = text->substitute(',', '', 'g')->str2float()
-
-str2list({string} [, {utf8}]) *str2list()*
+str2list({string} [, {utf8}]) *str2list()*
Return a list containing the number values which represent
- each character in String {string}. Examples: >
- str2list(" ") returns [32]
- str2list("ABC") returns [65, 66, 67]
+ each character in String {string}. Examples: >vim
+ echo str2list(" ") " returns [32]
+ echo str2list("ABC") " returns [65, 66, 67]
< |list2str()| does the opposite.
UTF-8 encoding is always used, {utf8} option has no effect,
and exists only for backwards-compatibility.
- With UTF-8 composing characters are handled properly: >
- str2list("á") returns [97, 769]
+ With UTF-8 composing characters are handled properly: >vim
+ echo str2list("á") " returns [97, 769]
-< Can also be used as a |method|: >
- GetString()->str2list()
-
-str2nr({string} [, {base}]) *str2nr()*
+str2nr({string} [, {base}]) *str2nr()*
Convert string {string} to a number.
{base} is the conversion base, it can be 2, 8, 10 or 16.
When {quoted} is present and non-zero then embedded single
@@ -7880,7 +7560,7 @@ str2nr({string} [, {base}]) *str2nr()*
When {base} is omitted base 10 is used. This also means that
a leading zero doesn't cause octal conversion to be used, as
- with the default String to Number conversion. Example: >
+ with the default String to Number conversion. Example: >vim
let nr = str2nr('0123')
<
When {base} is 16 a leading "0x" or "0X" is ignored. With a
@@ -7891,11 +7571,7 @@ str2nr({string} [, {base}]) *str2nr()*
Returns 0 if {string} is empty or on error.
- Can also be used as a |method|: >
- GetText()->str2nr()
-
-
-strcharlen({string}) *strcharlen()*
+strcharlen({string}) *strcharlen()*
The result is a Number, which is the number of characters
in String {string}. Composing characters are ignored.
|strchars()| can count the number of characters, counting
@@ -7905,26 +7581,22 @@ strcharlen({string}) *strcharlen()*
Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
- Can also be used as a |method|: >
- GetText()->strcharlen()
-
-
-strcharpart({src}, {start} [, {len}]) *strcharpart()*
+strcharpart({src}, {start} [, {len} [, {skipcc}]]) *strcharpart()*
Like |strpart()| but using character index and length instead
- of byte index and length. Composing characters are counted
- separately.
+ of byte index and length.
+ When {skipcc} is omitted or zero, composing characters are
+ counted separately.
+ When {skipcc} set to 1, Composing characters are ignored,
+ similar to |slice()|.
When a character index is used where a character does not
- exist it is assumed to be one character. For example: >
- strcharpart('abc', -1, 2)
+ exist it is omitted and counted as one character. For
+ example: >vim
+ echo strcharpart('abc', -1, 2)
< results in 'a'.
Returns an empty string on error.
- Can also be used as a |method|: >
- GetText()->strcharpart(5)
-
-
-strchars({string} [, {skipcc}]) *strchars()*
+strchars({string} [, {skipcc}]) *strchars()*
The result is a Number, which is the number of characters
in String {string}.
When {skipcc} is omitted or zero, composing characters are
@@ -7937,7 +7609,7 @@ strchars({string} [, {skipcc}]) *strchars()*
Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
{skipcc} is only available after 7.4.755. For backward
- compatibility, you can define a wrapper function: >
+ compatibility, you can define a wrapper function: >vim
if has("patch-7.4.755")
function s:strchars(str, skipcc)
return strchars(a:str, a:skipcc)
@@ -7952,10 +7624,8 @@ strchars({string} [, {skipcc}]) *strchars()*
endfunction
endif
<
- Can also be used as a |method|: >
- GetText()->strchars()
-strdisplaywidth({string} [, {col}]) *strdisplaywidth()*
+strdisplaywidth({string} [, {col}]) *strdisplaywidth()*
The result is a Number, which is the number of display cells
String {string} occupies on the screen when it starts at {col}
(first column is zero). When {col} is omitted zero is used.
@@ -7969,10 +7639,7 @@ strdisplaywidth({string} [, {col}]) *strdisplaywidth()*
Returns zero on error.
Also see |strlen()|, |strwidth()| and |strchars()|.
- Can also be used as a |method|: >
- GetText()->strdisplaywidth()
-
-strftime({format} [, {time}]) *strftime()*
+strftime({format} [, {time}]) *strftime()*
The result is a String, which is a formatted date and time, as
specified by the {format} string. The given {time} is used,
or the current time if no time is given. The accepted
@@ -7981,18 +7648,15 @@ strftime({format} [, {time}]) *strftime()*
format. The maximum length of the result is 80 characters.
See also |localtime()|, |getftime()| and |strptime()|.
The language can be changed with the |:language| command.
- Examples: >
- :echo strftime("%c") Sun Apr 27 11:49:23 1997
- :echo strftime("%Y %b %d %X") 1997 Apr 27 11:53:25
- :echo strftime("%y%m%d %T") 970427 11:53:55
- :echo strftime("%H:%M") 11:55
- :echo strftime("%c", getftime("file.c"))
- Show mod time of file.c.
-
-< Can also be used as a |method|: >
- GetFormat()->strftime()
-
-strgetchar({str}, {index}) *strgetchar()*
+ Examples: >vim
+ echo strftime("%c") " Sun Apr 27 11:49:23 1997
+ echo strftime("%Y %b %d %X") " 1997 Apr 27 11:53:25
+ echo strftime("%y%m%d %T") " 970427 11:53:55
+ echo strftime("%H:%M") " 11:55
+ echo strftime("%c", getftime("file.c"))
+ " Show mod time of file.c.
+
+strgetchar({str}, {index}) *strgetchar()*
Get a Number corresponding to the character at {index} in
{str}. This uses a zero-based character index, not a byte
index. Composing characters are considered separate
@@ -8001,33 +7665,27 @@ strgetchar({str}, {index}) *strgetchar()*
Returns -1 if {index} is invalid.
Also see |strcharpart()| and |strchars()|.
- Can also be used as a |method|: >
- GetText()->strgetchar(5)
-
-stridx({haystack}, {needle} [, {start}]) *stridx()*
+stridx({haystack}, {needle} [, {start}]) *stridx()*
The result is a Number, which gives the byte index in
{haystack} of the first occurrence of the String {needle}.
If {start} is specified, the search starts at index {start}.
- This can be used to find a second match: >
- :let colon1 = stridx(line, ":")
- :let colon2 = stridx(line, ":", colon1 + 1)
+ This can be used to find a second match: >vim
+ let colon1 = stridx(line, ":")
+ let colon2 = stridx(line, ":", colon1 + 1)
< The search is done case-sensitive.
For pattern searches use |match()|.
-1 is returned if the {needle} does not occur in {haystack}.
See also |strridx()|.
- Examples: >
- :echo stridx("An Example", "Example") 3
- :echo stridx("Starting point", "Start") 0
- :echo stridx("Starting point", "start") -1
+ Examples: >vim
+ echo stridx("An Example", "Example") " 3
+ echo stridx("Starting point", "Start") " 0
+ echo stridx("Starting point", "start") " -1
< *strstr()* *strchr()*
stridx() works similar to the C function strstr(). When used
with a single character it works similar to strchr().
- Can also be used as a |method|: >
- GetHaystack()->stridx(needle)
-<
- *string()*
-string({expr}) Return {expr} converted to a String. If {expr} is a Number,
+string({expr}) *string()*
+ Return {expr} converted to a String. If {expr} is a Number,
Float, String, Blob or a composition of them, then the result
can be parsed back with |eval()|.
{expr} type result ~
@@ -8038,7 +7696,7 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
Funcref `function('name')`
Blob 0z00112233.44556677.8899
List [item, item]
- Dictionary {key: value, key: value}
+ Dictionary `{key: value, key: value}`
Note that in String values the ' character is doubled.
Also see |strtrans()|.
Note 2: Output format is mostly compatible with YAML, except
@@ -8050,10 +7708,7 @@ string({expr}) Return {expr} converted to a String. If {expr} is a Number,
method, use |msgpackdump()| or |json_encode()| if you need to
share data with other application.
- Can also be used as a |method|: >
- mylist->string()
-
-strlen({string}) *strlen()*
+strlen({string}) *strlen()*
The result is a Number, which is the length of the String
{string} in bytes.
If the argument is a Number it is first converted to a String.
@@ -8062,10 +7717,7 @@ strlen({string}) *strlen()*
|strchars()|.
Also see |len()|, |strdisplaywidth()| and |strwidth()|.
- Can also be used as a |method|: >
- GetString()->strlen()
-
-strpart({src}, {start} [, {len} [, {chars}]]) *strpart()*
+strpart({src}, {start} [, {len} [, {chars}]]) *strpart()*
The result is a String, which is part of {src}, starting from
byte {start}, with the byte length {len}.
When {chars} is present and TRUE then {len} is the number of
@@ -8078,22 +7730,19 @@ strpart({src}, {start} [, {len} [, {chars}]]) *strpart()*
When bytes are selected which do not exist, this doesn't
result in an error, the bytes are simply omitted.
If {len} is missing, the copy continues from {start} till the
- end of the {src}. >
- strpart("abcdefg", 3, 2) == "de"
- strpart("abcdefg", -2, 4) == "ab"
- strpart("abcdefg", 5, 4) == "fg"
- strpart("abcdefg", 3) == "defg"
+ end of the {src}. >vim
+ echo strpart("abcdefg", 3, 2) " returns 'de'
+ echo strpart("abcdefg", -2, 4) " returns 'ab'
+ echo strpart("abcdefg", 5, 4) " returns 'fg'
+ echo strpart("abcdefg", 3) " returns 'defg'
< Note: To get the first character, {start} must be 0. For
- example, to get the character under the cursor: >
+ example, to get the character under the cursor: >vim
strpart(getline("."), col(".") - 1, 1, v:true)
<
Returns an empty string on error.
- Can also be used as a |method|: >
- GetText()->strpart(5)
-
-strptime({format}, {timestring}) *strptime()*
+strptime({format}, {timestring}) *strptime()*
The result is a Number, which is a unix timestamp representing
the date and time in {timestring}, which is expected to match
the format specified in {format}.
@@ -8109,52 +7758,63 @@ strptime({format}, {timestring}) *strptime()*
result.
See also |strftime()|.
- Examples: >
- :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23")
-< 862156163 >
- :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55"))
-< Sun Apr 27 11:53:55 1997 >
- :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600)
+ Examples: >vim
+ echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23")
+< 862156163 >vim
+ echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55"))
+< Sun Apr 27 11:53:55 1997 >vim
+ echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600)
< Sun Apr 27 12:53:55 1997
- Can also be used as a |method|: >
- GetFormat()->strptime(timestring)
-<
-strridx({haystack}, {needle} [, {start}]) *strridx()*
+strridx({haystack}, {needle} [, {start}]) *strridx()*
The result is a Number, which gives the byte index in
{haystack} of the last occurrence of the String {needle}.
When {start} is specified, matches beyond this index are
ignored. This can be used to find a match before a previous
- match: >
- :let lastcomma = strridx(line, ",")
- :let comma2 = strridx(line, ",", lastcomma - 1)
+ match: >vim
+ let lastcomma = strridx(line, ",")
+ let comma2 = strridx(line, ",", lastcomma - 1)
< The search is done case-sensitive.
For pattern searches use |match()|.
-1 is returned if the {needle} does not occur in {haystack}.
If the {needle} is empty the length of {haystack} is returned.
- See also |stridx()|. Examples: >
- :echo strridx("an angry armadillo", "an") 3
+ See also |stridx()|. Examples: >vim
+ echo strridx("an angry armadillo", "an") 3
< *strrchr()*
When used with a single character it works similar to the C
function strrchr().
- Can also be used as a |method|: >
- GetHaystack()->strridx(needle)
-
-strtrans({string}) *strtrans()*
+strtrans({string}) *strtrans()*
The result is a String, which is {string} with all unprintable
characters translated into printable characters |'isprint'|.
- Like they are shown in a window. Example: >
+ Like they are shown in a window. Example: >vim
echo strtrans(@a)
< This displays a newline in register a as "^@" instead of
starting a new line.
Returns an empty string on error.
- Can also be used as a |method|: >
- GetString()->strtrans()
+strutf16len({string} [, {countcc}]) *strutf16len()*
+ The result is a Number, which is the number of UTF-16 code
+ units in String {string} (after converting it to UTF-16).
-strwidth({string}) *strwidth()*
+ When {countcc} is TRUE, composing characters are counted
+ separately.
+ When {countcc} is omitted or FALSE, composing characters are
+ ignored.
+
+ Returns zero on error.
+
+ Also see |strlen()| and |strcharlen()|.
+ Examples: >vim
+ echo strutf16len('a') " returns 1
+ echo strutf16len('©') " returns 1
+ echo strutf16len('😊') " returns 2
+ echo strutf16len('ą́') " returns 1
+ echo strutf16len('ą́', v:true) " returns 3
+<
+
+strwidth({string}) *strwidth()*
The result is a Number, which is the number of display cells
String {string} occupies. A Tab character is counted as one
cell, alternatively use |strdisplaywidth()|.
@@ -8163,10 +7823,7 @@ strwidth({string}) *strwidth()*
Returns zero on error.
Also see |strlen()|, |strdisplaywidth()| and |strchars()|.
- Can also be used as a |method|: >
- GetString()->strwidth()
-
-submatch({nr} [, {list}]) *submatch()* *E935*
+submatch({nr} [, {list}]) *submatch()* *E935*
Only for an expression in a |:substitute| command or
substitute() function.
Returns the {nr}th submatch of the matched text. When {nr}
@@ -8188,16 +7845,13 @@ submatch({nr} [, {list}]) *submatch()* *E935*
Returns an empty string or list on error.
- Examples: >
- :s/\d\+/\=submatch(0) + 1/
- :echo substitute(text, '\d\+', '\=submatch(0) + 1', '')
+ Examples: >vim
+ s/\d\+/\=submatch(0) + 1/
+ echo substitute(text, '\d\+', '\=submatch(0) + 1', '')
< This finds the first number in the line and adds one to it.
A line break is included as a newline character.
- Can also be used as a |method|: >
- GetNr()->submatch()
-
-substitute({string}, {pat}, {sub}, {flags}) *substitute()*
+substitute({string}, {pat}, {sub}, {flags}) *substitute()*
The result is a String, which is a copy of {string}, in which
the first match of {pat} is replaced with {sub}.
When {flags} is "g", all matches of {pat} in {string} are
@@ -8219,39 +7873,47 @@ substitute({string}, {pat}, {sub}, {flags}) *substitute()*
When {pat} does not match in {string}, {string} is returned
unmodified.
- Example: >
- :let &path = substitute(&path, ",\\=[^,]*$", "", "")
-< This removes the last component of the 'path' option. >
- :echo substitute("testing", ".*", "\\U\\0", "")
+ Example: >vim
+ let &path = substitute(&path, ",\\=[^,]*$", "", "")
+< This removes the last component of the 'path' option. >vim
+ echo substitute("testing", ".*", "\\U\\0", "")
< results in "TESTING".
When {sub} starts with "\=", the remainder is interpreted as
- an expression. See |sub-replace-expression|. Example: >
- :echo substitute(s, '%\(\x\x\)',
+ an expression. See |sub-replace-expression|. Example: >vim
+ echo substitute(s, '%\(\x\x\)',
\ '\=nr2char("0x" .. submatch(1))', 'g')
< When {sub} is a Funcref that function is called, with one
- optional argument. Example: >
- :echo substitute(s, '%\(\x\x\)', SubNr, 'g')
+ optional argument. Example: >vim
+ echo substitute(s, '%\(\x\x\)', SubNr, 'g')
< The optional argument is a list which contains the whole
matched string and up to nine submatches, like what
- |submatch()| returns. Example: >
- :echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g')
+ |submatch()| returns. Example: >vim
+ echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g')
< Returns an empty string on error.
- Can also be used as a |method|: >
- GetString()->substitute(pat, sub, flags)
-
-swapinfo({fname}) *swapinfo()*
+swapfilelist() *swapfilelist()*
+ Returns a list of swap file names, like what "vim -r" shows.
+ See the |-r| command argument. The 'directory' option is used
+ for the directories to inspect. If you only want to get a
+ list of swap files in the current directory then temporarily
+ set 'directory' to a dot: >vim
+ let save_dir = &directory
+ let &directory = '.'
+ let swapfiles = swapfilelist()
+ let &directory = save_dir
+
+swapinfo({fname}) *swapinfo()*
The result is a dictionary, which holds information about the
swapfile {fname}. The available fields are:
version Vim version
user user name
host host name
fname original file name
- pid PID of the Vim process that created the swap
- file
+ pid PID of the Nvim process that created the swap
+ file, or zero if not running.
mtime last modification time in seconds
inode Optional: INODE number of the file
dirty 1 if file was modified, 0 if not
@@ -8261,20 +7923,14 @@ swapinfo({fname}) *swapinfo()*
Not a swap file: does not contain correct block ID
Magic number mismatch: Info in first block is invalid
- Can also be used as a |method|: >
- GetFilename()->swapinfo()
-
-swapname({buf}) *swapname()*
+swapname({buf}) *swapname()*
The result is the swap file path of the buffer {buf}.
For the use of {buf}, see |bufname()| above.
If buffer {buf} is the current buffer, the result is equal to
|:swapname| (unless there is no swap file).
If buffer {buf} has no swap file, returns an empty string.
- Can also be used as a |method|: >
- GetBufname()->swapname()
-
-synID({lnum}, {col}, {trans}) *synID()*
+synID({lnum}, {col}, {trans}) *synID()*
The result is a Number, which is the syntax ID at the position
{lnum} and {col} in the current window.
The syntax ID can be used with |synIDattr()| and
@@ -8296,11 +7952,11 @@ synID({lnum}, {col}, {trans}) *synID()*
Returns zero on error.
- Example (echoes the name of the syntax item under the cursor): >
- :echo synIDattr(synID(line("."), col("."), 1), "name")
+ Example (echoes the name of the syntax item under the cursor): >vim
+ echo synIDattr(synID(line("."), col("."), 1), "name")
<
-synIDattr({synID}, {what} [, {mode}]) *synIDattr()*
+synIDattr({synID}, {what} [, {mode}]) *synIDattr()*
The result is a String, which is the {what} attribute of
syntax ID {synID}. This can be used to obtain information
about a syntax item.
@@ -8339,13 +7995,14 @@ synIDattr({synID}, {what} [, {mode}]) *synIDattr()*
Returns an empty string on error.
Example (echoes the color of the syntax item under the
- cursor): >
- :echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg")
+ cursor): >vim
+ echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg")
+<
+ Can also be used as a |method|: >vim
+ echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
<
- Can also be used as a |method|: >
- :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
-synIDtrans({synID}) *synIDtrans()*
+synIDtrans({synID}) *synIDtrans()*
The result is a Number, which is the translated syntax ID of
{synID}. This is the syntax group ID of what is being used to
highlight the character. Highlight links given with
@@ -8353,10 +8010,7 @@ synIDtrans({synID}) *synIDtrans()*
Returns zero on error.
- Can also be used as a |method|: >
- :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
-
-synconcealed({lnum}, {col}) *synconcealed()*
+synconcealed({lnum}, {col}) *synconcealed()*
The result is a |List| with currently three items:
1. The first item in the list is 0 if the character at the
position {lnum} and {col} is not part of a concealable
@@ -8381,8 +8035,7 @@ synconcealed({lnum}, {col}) *synconcealed()*
synconcealed(lnum, 5) [1, 'X', 2]
synconcealed(lnum, 6) [0, '', 0]
-
-synstack({lnum}, {col}) *synstack()*
+synstack({lnum}, {col}) *synstack()*
Return a |List|, which is the stack of syntax items at the
position {lnum} and {col} in the current window. {lnum} is
used like with |getline()|. Each item in the List is an ID
@@ -8392,7 +8045,7 @@ synstack({lnum}, {col}) *synstack()*
returns, unless not the whole item is highlighted or it is a
transparent item.
This function is useful for debugging a syntax file.
- Example that shows the syntax stack under the cursor: >
+ Example that shows the syntax stack under the cursor: >vim
for id in synstack(line("."), col("."))
echo synIDattr(id, "name")
endfor
@@ -8401,13 +8054,15 @@ synstack({lnum}, {col}) *synstack()*
character in a line and the first column in an empty line are
valid positions.
-system({cmd} [, {input}]) *system()* *E677*
+system({cmd} [, {input}]) *system()* *E677*
+ Note: Prefer |vim.system()| in Lua.
+
Gets the output of {cmd} as a |string| (|systemlist()| returns
a |List|) and sets |v:shell_error| to the error code.
{cmd} is treated as in |jobstart()|:
If {cmd} is a List it runs directly (no 'shell').
- If {cmd} is a String it runs in the 'shell', like this: >
- :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+ If {cmd} is a String it runs in the 'shell', like this: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
< Not to be used for interactive commands.
@@ -8415,8 +8070,8 @@ system({cmd} [, {input}]) *system()* *E677*
- <CR><NL> is replaced with <NL>
- NUL characters are replaced with SOH (0x01)
- Example: >
- :echo system(['ls', expand('%:h')])
+ Example: >vim
+ echo system(['ls', expand('%:h')])
< If {input} is a string it is written to a pipe and passed as
stdin to the command. The string is written as-is, line
@@ -8430,8 +8085,8 @@ system({cmd} [, {input}]) *system()* *E677*
terminated by NL (and NUL where the text has NL).
*E5677*
Note: system() cannot write to or read from backgrounded ("&")
- shell commands, e.g.: >
- :echo system("cat - &", "foo")
+ shell commands, e.g.: >vim
+ echo system("cat - &", "foo")
< which is equivalent to: >
$ echo foo | bash -c 'cat - &'
< The pipes are disconnected (unless overridden by shell
@@ -8441,17 +8096,14 @@ system({cmd} [, {input}]) *system()* *E677*
Note: Use |shellescape()| or |::S| with |expand()| or
|fnamemodify()| to escape special characters in a command
argument. 'shellquote' and 'shellxquote' must be properly
- configured. Example: >
- :echo system('ls '..shellescape(expand('%:h')))
- :echo system('ls '..expand('%:h:S'))
+ configured. Example: >vim
+ echo system('ls '..shellescape(expand('%:h')))
+ echo system('ls '..expand('%:h:S'))
< Unlike ":!cmd" there is no automatic check for changed files.
Use |:checktime| to force a check.
- Can also be used as a |method|: >
- :echo GetCmd()->system()
-
-systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()*
+systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()*
Same as |system()|, but returns a |List| with lines (parts of
output separated by NL) with NULs transformed into NLs. Output
is the same as |readfile()| will output with {binary} argument
@@ -8460,31 +8112,25 @@ systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()*
Note that on MS-Windows you may get trailing CR characters.
To see the difference between "echo hello" and "echo -n hello"
- use |system()| and |split()|: >
+ use |system()| and |split()|: >vim
echo split(system('echo hello'), '\n', 1)
<
Returns an empty string on error.
- Can also be used as a |method|: >
- :echo GetCmd()->systemlist()
-
-tabpagebuflist([{arg}]) *tabpagebuflist()*
+tabpagebuflist([{arg}]) *tabpagebuflist()*
The result is a |List|, where each item is the number of the
buffer associated with each window in the current tab page.
{arg} specifies the number of the tab page to be used. When
omitted the current tab page is used.
When {arg} is invalid the number zero is returned.
- To get a list of all buffers in all tabs use this: >
+ To get a list of all buffers in all tabs use this: >vim
let buflist = []
for i in range(tabpagenr('$'))
call extend(buflist, tabpagebuflist(i + 1))
endfor
< Note that a buffer may appear in more than one window.
- Can also be used as a |method|: >
- GetTabpage()->tabpagebuflist()
-
-tabpagenr([{arg}]) *tabpagenr()*
+tabpagenr([{arg}]) *tabpagenr()*
The result is a Number, which is the number of the current
tab page. The first tab page has number 1.
@@ -8498,7 +8144,7 @@ tabpagenr([{arg}]) *tabpagenr()*
Returns zero on error.
-tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()*
+tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()*
Like |winnr()| but for tab page {tabarg}.
{tabarg} specifies the number of tab page to be used.
{arg} is used like with |winnr()|:
@@ -8506,20 +8152,16 @@ tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()*
the window which will be used when going to this tab page.
- When "$" the number of windows is returned.
- When "#" the previous window nr is returned.
- Useful examples: >
+ Useful examples: >vim
tabpagewinnr(1) " current window of tab page 1
tabpagewinnr(4, '$') " number of windows in tab page 4
< When {tabarg} is invalid zero is returned.
- Can also be used as a |method|: >
- GetTabpage()->tabpagewinnr()
-<
- *tagfiles()*
-tagfiles() Returns a |List| with the file names used to search for tags
+tagfiles() *tagfiles()*
+ Returns a |List| with the file names used to search for tags
for the current buffer. This is the 'tags' option expanded.
-
-taglist({expr} [, {filename}]) *taglist()*
+taglist({expr} [, {filename}]) *taglist()*
Returns a |List| of tags matching the regular expression {expr}.
If {filename} is passed it is used to prioritize the results
@@ -8562,60 +8204,59 @@ taglist({expr} [, {filename}]) *taglist()*
located by Vim. Refer to |tags-file-format| for the format of
the tags file generated by the different ctags tools.
- Can also be used as a |method|: >
- GetTagpattern()->taglist()
-
-tempname() *tempname()* *temp-file-name*
- The result is a String, which is the name of a file that
- doesn't exist. It can be used for a temporary file. Example: >
- :let tmpfile = tempname()
- :exe "redir > " .. tmpfile
-< For Unix, the file will be in a private directory |tempfile|.
- For MS-Windows forward slashes are used when the 'shellslash'
- option is set or when 'shellcmdflag' starts with '-'.
-
-termopen({cmd} [, {opts}]) *termopen()*
- Spawns {cmd} in a new pseudo-terminal session connected
- to the current (unmodified) buffer. Parameters and behavior
- are the same as |jobstart()| except "pty", "width", "height",
- and "TERM" are ignored: "height" and "width" are taken from
- the current window.
- Returns the same values as |jobstart()|.
-
- Terminal environment is initialized as in ||jobstart-env|,
- except $TERM is set to "xterm-256color". Full behavior is
- described in |terminal|.
-
-tan({expr}) *tan()*
+tan({expr}) *tan()*
Return the tangent of {expr}, measured in radians, as a |Float|
in the range [-inf, inf].
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo tan(10)
-< 0.648361 >
- :echo tan(-4.01)
+ Examples: >vim
+ echo tan(10)
+< 0.648361 >vim
+ echo tan(-4.01)
< -1.181502
- Can also be used as a |method|: >
- Compute()->tan()
-
-tanh({expr}) *tanh()*
+tanh({expr}) *tanh()*
Return the hyperbolic tangent of {expr} as a |Float| in the
range [-1, 1].
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
- :echo tanh(0.5)
-< 0.462117 >
- :echo tanh(-1)
+ Examples: >vim
+ echo tanh(0.5)
+< 0.462117 >vim
+ echo tanh(-1)
< -0.761594
- Can also be used as a |method|: >
- Compute()->tanh()
+tempname() *tempname()*
+ Generates a (non-existent) filename located in the Nvim root
+ |tempdir|. Scripts can use the filename as a temporary file.
+ Example: >vim
+ let tmpfile = tempname()
+ exe "redir > " .. tmpfile
<
- *timer_info()*
-timer_info([{id}])
+
+termopen({cmd} [, {opts}]) *termopen()*
+ Spawns {cmd} in a new pseudo-terminal session connected
+ to the current (unmodified) buffer. Parameters and behavior
+ are the same as |jobstart()| except "pty", "width", "height",
+ and "TERM" are ignored: "height" and "width" are taken from
+ the current window. Note that termopen() implies a "pty" arg
+ to jobstart(), and thus has the implications documented at
+ |jobstart()|.
+
+ Returns the same values as jobstart().
+
+ Terminal environment is initialized as in |jobstart-env|,
+ except $TERM is set to "xterm-256color". Full behavior is
+ described in |terminal|.
+
+test_garbagecollect_now() *test_garbagecollect_now()*
+ Like |garbagecollect()|, but executed right away. This must
+ only be called directly to avoid any structure to exist
+ internally, and |v:testing| must have been set before calling
+ any function.
+
+
+timer_info([{id}]) *timer_info()*
Return a list with information about timers.
When {id} is given only information about this timer is
returned. When timer {id} does not exist an empty list is
@@ -8630,10 +8271,7 @@ timer_info([{id}])
-1 means forever
"callback" the callback
- Can also be used as a |method|: >
- GetTimer()->timer_info()
-<
-timer_pause({timer}, {paused}) *timer_pause()*
+timer_pause({timer}, {paused}) *timer_pause()*
Pause or unpause a timer. A paused timer does not invoke its
callback when its time expires. Unpausing a timer may cause
the callback to be invoked almost immediately if enough time
@@ -8646,16 +8284,14 @@ timer_pause({timer}, {paused}) *timer_pause()*
String, then the timer is paused, otherwise it is unpaused.
See |non-zero-arg|.
- Can also be used as a |method|: >
- GetTimer()->timer_pause(1)
-<
- *timer_start()* *timer* *timers*
-timer_start({time}, {callback} [, {options}])
+timer_start({time}, {callback} [, {options}]) *timer_start()* *timer*
Create a timer and return the timer ID.
{time} is the waiting time in milliseconds. This is the
minimum time before invoking the callback. When the system is
busy or Vim is not waiting for input the time will be longer.
+ Zero can be used to execute the callback when Vim is back in
+ the main loop.
{callback} is the function to call. It can be the name of a
function or a |Funcref|. It is called with one argument, which
@@ -8670,7 +8306,7 @@ timer_start({time}, {callback} [, {options}])
Returns -1 on error.
- Example: >
+ Example: >vim
func MyHandler(timer)
echo 'Handler called'
endfunc
@@ -8678,41 +8314,27 @@ timer_start({time}, {callback} [, {options}])
\ {'repeat': 3})
< This invokes MyHandler() three times at 500 msec intervals.
- Can also be used as a |method|: >
- GetMsec()->timer_start(callback)
-
-< Not available in the |sandbox|.
-
-timer_stop({timer}) *timer_stop()*
+timer_stop({timer}) *timer_stop()*
Stop a timer. The timer callback will no longer be invoked.
{timer} is an ID returned by timer_start(), thus it must be a
Number. If {timer} does not exist there is no error.
- Can also be used as a |method|: >
- GetTimer()->timer_stop()
-<
-timer_stopall() *timer_stopall()*
+timer_stopall() *timer_stopall()*
Stop all timers. The timer callbacks will no longer be
invoked. Useful if some timers is misbehaving. If there are
no timers there is no error.
-tolower({expr}) *tolower()*
+tolower({expr}) *tolower()*
The result is a copy of the String given, with all uppercase
characters turned into lowercase (just like applying |gu| to
the string). Returns an empty string on error.
- Can also be used as a |method|: >
- GetText()->tolower()
-
-toupper({expr}) *toupper()*
+toupper({expr}) *toupper()*
The result is a copy of the String given, with all lowercase
characters turned into uppercase (just like applying |gU| to
the string). Returns an empty string on error.
- Can also be used as a |method|: >
- GetText()->toupper()
-
-tr({src}, {fromstr}, {tostr}) *tr()*
+tr({src}, {fromstr}, {tostr}) *tr()*
The result is a copy of the {src} string with all characters
which appear in {fromstr} replaced by the character in that
position in the {tostr} string. Thus the first character in
@@ -8722,90 +8344,81 @@ tr({src}, {fromstr}, {tostr}) *tr()*
Returns an empty string on error.
- Examples: >
+ Examples: >vim
echo tr("hello there", "ht", "HT")
-< returns "Hello THere" >
+< returns "Hello THere" >vim
echo tr("<blob>", "<>", "{}")
< returns "{blob}"
- Can also be used as a |method|: >
- GetText()->tr(from, to)
-
-trim({text} [, {mask} [, {dir}]]) *trim()*
+trim({text} [, {mask} [, {dir}]]) *trim()*
Return {text} as a String where any character in {mask} is
removed from the beginning and/or end of {text}.
- If {mask} is not given, {mask} is all characters up to 0x20,
- which includes Tab, space, NL and CR, plus the non-breaking
- space character 0xa0.
+
+ If {mask} is not given, or is an empty string, {mask} is all
+ characters up to 0x20, which includes Tab, space, NL and CR,
+ plus the non-breaking space character 0xa0.
+
The optional {dir} argument specifies where to remove the
characters:
0 remove from the beginning and end of {text}
1 remove only at the beginning of {text}
2 remove only at the end of {text}
When omitted both ends are trimmed.
+
This function deals with multibyte characters properly.
Returns an empty string on error.
- Examples: >
+ Examples: >vim
echo trim(" some text ")
-< returns "some text" >
+< returns "some text" >vim
echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") .. "_TAIL"
-< returns "RESERVE_TAIL" >
+< returns "RESERVE_TAIL" >vim
echo trim("rm<Xrm<>X>rrm", "rm<>")
-< returns "Xrm<>X" (characters in the middle are not removed) >
+< returns "Xrm<>X" (characters in the middle are not removed) >vim
echo trim(" vim ", " ", 2)
< returns " vim"
- Can also be used as a |method|: >
- GetText()->trim()
-
-trunc({expr}) *trunc()*
+trunc({expr}) *trunc()*
Return the largest integral value with magnitude less than or
equal to {expr} as a |Float| (truncate towards zero).
{expr} must evaluate to a |Float| or a |Number|.
Returns 0.0 if {expr} is not a |Float| or a |Number|.
- Examples: >
+ Examples: >vim
echo trunc(1.456)
-< 1.0 >
+< 1.0 >vim
echo trunc(-5.456)
-< -5.0 >
+< -5.0 >vim
echo trunc(4.0)
< 4.0
- Can also be used as a |method|: >
- Compute()->trunc()
-
-type({expr}) *type()*
+type({expr}) *type()*
The result is a Number representing the type of {expr}.
Instead of using the number directly, it is better to use the
v:t_ variable that has the value:
- Number: 0 (|v:t_number|)
- String: 1 (|v:t_string|)
- Funcref: 2 (|v:t_func|)
- List: 3 (|v:t_list|)
- Dictionary: 4 (|v:t_dict|)
- Float: 5 (|v:t_float|)
- Boolean: 6 (|v:true| and |v:false|)
- Null: 7 (|v:null|)
- Blob: 10 (|v:t_blob|)
- For backward compatibility, this method can be used: >
- :if type(myvar) == type(0)
- :if type(myvar) == type("")
- :if type(myvar) == type(function("tr"))
- :if type(myvar) == type([])
- :if type(myvar) == type({})
- :if type(myvar) == type(0.0)
- :if type(myvar) == type(v:true)
+ Number: 0 |v:t_number|
+ String: 1 |v:t_string|
+ Funcref: 2 |v:t_func|
+ List: 3 |v:t_list|
+ Dictionary: 4 |v:t_dict|
+ Float: 5 |v:t_float|
+ Boolean: 6 |v:t_bool| (|v:false| and |v:true|)
+ Null: 7 (|v:null|)
+ Blob: 10 |v:t_blob|
+ For backward compatibility, this method can be used: >vim
+ if type(myvar) == type(0) | endif
+ if type(myvar) == type("") | endif
+ if type(myvar) == type(function("tr")) | endif
+ if type(myvar) == type([]) | endif
+ if type(myvar) == type({}) | endif
+ if type(myvar) == type(0.0) | endif
+ if type(myvar) == type(v:true) | endif
< In place of checking for |v:null| type it is better to check
- for |v:null| directly as it is the only value of this type: >
- :if myvar is v:null
-< To check if the v:t_ variables exist use this: >
- :if exists('v:t_number')
-
-< Can also be used as a |method|: >
- mylist->type()
+ for |v:null| directly as it is the only value of this type: >vim
+ if myvar is v:null | endif
+< To check if the v:t_ variables exist use this: >vim
+ if exists('v:t_number') | endif
-undofile({name}) *undofile()*
+undofile({name}) *undofile()*
Return the name of the undo file that would be used for a file
with name {name} when writing. This uses the 'undodir'
option, finding directories that exist. It does not check if
@@ -8816,12 +8429,10 @@ undofile({name}) *undofile()*
buffer without a file name will not write an undo file.
Useful in combination with |:wundo| and |:rundo|.
- Can also be used as a |method|: >
- GetFilename()->undofile()
-
-undotree() *undotree()*
- Return the current state of the undo tree in a dictionary with
- the following items:
+undotree([{buf}]) *undotree()*
+ Return the current state of the undo tree for the current
+ buffer, or for a specific buffer if {buf} is given. The
+ result is a dictionary with the following items:
"seq_last" The highest undo sequence number used.
"seq_cur" The sequence number of the current position in
the undo tree. This differs from "seq_last"
@@ -8862,28 +8473,50 @@ undotree() *undotree()*
blocks. Each item may again have an "alt"
item.
-uniq({list} [, {func} [, {dict}]]) *uniq()* *E882*
+uniq({list} [, {func} [, {dict}]]) *uniq()* *E882*
Remove second and succeeding copies of repeated adjacent
{list} items in-place. Returns {list}. If you want a list
- to remain unmodified make a copy first: >
- :let newlist = uniq(copy(mylist))
+ to remain unmodified make a copy first: >vim
+ let newlist = uniq(copy(mylist))
< The default compare function uses the string representation of
each item. For the use of {func} and {dict} see |sort()|.
Returns zero if {list} is not a |List|.
- Can also be used as a |method|: >
- mylist->uniq()
+utf16idx({string}, {idx} [, {countcc} [, {charidx}]]) *utf16idx()*
+ Same as |charidx()| but returns the UTF-16 code unit index of
+ the byte at {idx} in {string} (after converting it to UTF-16).
+
+ When {charidx} is present and TRUE, {idx} is used as the
+ character index in the String {string} instead of as the byte
+ index.
+ An {idx} in the middle of a UTF-8 sequence is rounded
+ downwards to the beginning of that sequence.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes in {string}. If there are exactly {idx} bytes
+ the length of the string in UTF-16 code units is returned.
-values({dict}) *values()*
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the UTF-16 index and |charidx()| for getting the
+ character index from the UTF-16 index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo utf16idx('a😊😊', 3) " returns 2
+ echo utf16idx('a😊😊', 7) " returns 4
+ echo utf16idx('a😊😊', 1, 0, 1) " returns 2
+ echo utf16idx('a😊😊', 2, 0, 1) " returns 4
+ echo utf16idx('aą́c', 6) " returns 2
+ echo utf16idx('aą́c', 6, 1) " returns 4
+ echo utf16idx('a😊😊', 9) " returns -1
+<
+
+values({dict}) *values()*
Return a |List| with all the values of {dict}. The |List| is
in arbitrary order. Also see |items()| and |keys()|.
Returns zero if {dict} is not a |Dict|.
- Can also be used as a |method|: >
- mydict->values()
-
-virtcol({expr}) *virtcol()*
+virtcol({expr} [, {list} [, {winid}]]) *virtcol()*
The result is a Number, which is the screen column of the file
position given with {expr}. That is, the last screen position
occupied by the character at that position, when the screen
@@ -8892,13 +8525,17 @@ virtcol({expr}) *virtcol()*
the <Tab>. For example, for a <Tab> in column 1, with 'ts'
set to 8, it returns 8. |conceal| is ignored.
For the byte position use |col()|.
+
For the use of {expr} see |col()|.
- When 'virtualedit' is used {expr} can be [lnum, col, off], where
- "off" is the offset in screen columns from the start of the
- character. E.g., a position within a <Tab> or after the last
- character. When "off" is omitted zero is used.
- When Virtual editing is active in the current mode, a position
- beyond the end of the line can be returned. |'virtualedit'|
+
+ When 'virtualedit' is used {expr} can be [lnum, col, off],
+ where "off" is the offset in screen columns from the start of
+ the character. E.g., a position within a <Tab> or after the
+ last character. When "off" is omitted zero is used. When
+ Virtual editing is active in the current mode, a position
+ beyond the end of the line can be returned. Also see
+ |'virtualedit'|
+
The accepted positions are:
. the cursor position
$ the end of the cursor line (the result is the
@@ -8910,28 +8547,44 @@ virtcol({expr}) *virtcol()*
cursor is the end). When not in Visual mode
returns the cursor position. Differs from |'<| in
that it's updated right away.
+
+ If {list} is present and non-zero then virtcol() returns a
+ List with the first and last screen position occupied by the
+ character.
+
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+
Note that only marks in the current file can be used.
- Examples: >
- virtcol(".") with text "foo^Lbar", with cursor on the "^L", returns 5
- virtcol("$") with text "foo^Lbar", returns 9
- virtcol("'t") with text " there", with 't at 'h', returns 6
-< The first column is 1. 0 is returned for an error.
+ Examples: >vim
+ " With text "foo^Lbar" and cursor on the "^L":
+
+ echo virtcol(".") " returns 5
+ echo virtcol(".", 1) " returns [4, 5]
+ echo virtcol("$") " returns 9
+
+ " With text " there", with 't at 'h':
+
+ echo virtcol("'t") " returns 6
+< The first column is 1. 0 or [0, 0] is returned for an error.
A more advanced example that echoes the maximum length of
- all lines: >
+ all lines: >vim
echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
-< Can also be used as a |method|: >
- GetPos()->virtcol()
-
-virtcol2col({winid}, {lnum}, {col}) *virtcol2col()*
+virtcol2col({winid}, {lnum}, {col}) *virtcol2col()*
The result is a Number, which is the byte index of the
character in window {winid} at buffer line {lnum} and virtual
column {col}.
+ If buffer line {lnum} is an empty line, 0 is returned.
+
If {col} is greater than the last virtual column in line
{lnum}, then the byte index of the character at the last
virtual column is returned.
+ For a multi-byte character, the column number of the first
+ byte in the character is returned.
+
The {winid} argument can be the window number or the
|window-ID|. If this is zero, then the current window is used.
@@ -8940,18 +8593,15 @@ virtcol2col({winid}, {lnum}, {col}) *virtcol2col()*
See also |screenpos()|, |virtcol()| and |col()|.
- Can also be used as a |method|: >
- GetWinid()->virtcol2col(lnum, col)
-
-visualmode([{expr}]) *visualmode()*
+visualmode([{expr}]) *visualmode()*
The result is a String, which describes the last Visual mode
used in the current buffer. Initially it returns an empty
string, but once Visual mode has been used, it returns "v",
"V", or "<CTRL-V>" (a single CTRL-V character) for
character-wise, line-wise, or block-wise Visual mode
respectively.
- Example: >
- :exe "normal " .. visualmode()
+ Example: >vim
+ exe "normal " .. visualmode()
< This enters the same Visual mode as before. It is also useful
in scripts if you wish to act differently depending on the
Visual mode that was used.
@@ -8961,7 +8611,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.
@@ -8977,24 +8627,24 @@ wait({timeout}, {condition} [, {interval}]) *wait()*
-2 if the function was interrupted (by |CTRL-C|)
-3 if an error occurred
-wildmenumode() *wildmenumode()*
+wildmenumode() *wildmenumode()*
Returns |TRUE| when the wildmenu is active and |FALSE|
otherwise. See 'wildmenu' and 'wildmode'.
This can be used in mappings to handle the 'wildcharm' option
gracefully. (Makes only sense with |mapmode-c| mappings).
- For example to make <c-j> work like <down> in wildmode, use: >
- :cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
+ For example to make <c-j> work like <down> in wildmode, use: >vim
+ cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
<
(Note, this needs the 'wildcharm' option set appropriately).
-win_execute({id}, {command} [, {silent}]) *win_execute()*
+win_execute({id}, {command} [, {silent}]) *win_execute()*
Like `execute()` but in the context of window {id}.
The window will temporarily be made the current window,
without triggering autocommands or changing directory. When
executing {command} autocommands will be triggered, this may
- have unexpected side effects. Use |:noautocmd| if needed.
- Example: >
+ have unexpected side effects. Use `:noautocmd` if needed.
+ Example: >vim
call win_execute(winid, 'syntax enable')
< Doing the same with `setwinvar()` would not trigger
autocommands and not actually show syntax highlighting.
@@ -9002,18 +8652,11 @@ win_execute({id}, {command} [, {silent}]) *win_execute()*
When window {id} does not exist then no error is given and
an empty string is returned.
- Can also be used as a |method|, the base is passed as the
- second argument: >
- GetCommand()->win_execute(winid)
-
-win_findbuf({bufnr}) *win_findbuf()*
+win_findbuf({bufnr}) *win_findbuf()*
Returns a |List| with |window-ID|s for windows that contain
buffer {bufnr}. When there is none the list is empty.
- Can also be used as a |method|: >
- GetBufnr()->win_findbuf()
-
-win_getid([{win} [, {tab}]]) *win_getid()*
+win_getid([{win} [, {tab}]]) *win_getid()*
Get the |window-ID| for the specified window.
When {win} is missing use the current window.
With {win} this is the window number. The top window has
@@ -9022,10 +8665,7 @@ win_getid([{win} [, {tab}]]) *win_getid()*
number {tab}. The first tab has number one.
Return zero if the window cannot be found.
- Can also be used as a |method|: >
- GetWinnr()->win_getid()
-
-win_gettype([{nr}]) *win_gettype()*
+win_gettype([{nr}]) *win_gettype()*
Return the type of the window:
"autocmd" autocommand window. Temporary window
used to execute autocommands.
@@ -9043,33 +8683,21 @@ win_gettype([{nr}]) *win_gettype()*
Also see the 'buftype' option.
- Can also be used as a |method|: >
- GetWinid()->win_gettype()
-<
-win_gotoid({expr}) *win_gotoid()*
+win_gotoid({expr}) *win_gotoid()*
Go to window with ID {expr}. This may also change the current
tabpage.
Return TRUE if successful, FALSE if the window cannot be found.
- Can also be used as a |method|: >
- GetWinid()->win_gotoid()
-
-win_id2tabwin({expr}) *win_id2tabwin()*
+win_id2tabwin({expr}) *win_id2tabwin()*
Return a list with the tab number and window number of window
with ID {expr}: [tabnr, winnr].
Return [0, 0] if the window cannot be found.
- Can also be used as a |method|: >
- GetWinid()->win_id2tabwin()
-
-win_id2win({expr}) *win_id2win()*
+win_id2win({expr}) *win_id2win()*
Return the window number of window with ID {expr}.
Return 0 if the window cannot be found in the current tabpage.
- Can also be used as a |method|: >
- GetWinid()->win_id2win()
-
-win_move_separator({nr}, {offset}) *win_move_separator()*
+win_move_separator({nr}, {offset}) *win_move_separator()*
Move window {nr}'s vertical separator (i.e., the right border)
by {offset} columns, as if being dragged by the mouse. {nr}
can be a window number or |window-ID|. A positive {offset}
@@ -9084,10 +8712,7 @@ win_move_separator({nr}, {offset}) *win_move_separator()*
window, since it has no separator on the right.
Only works for the current tab page. *E1308*
- Can also be used as a |method|: >
- GetWinnr()->win_move_separator(offset)
-
-win_move_statusline({nr}, {offset}) *win_move_statusline()*
+win_move_statusline({nr}, {offset}) *win_move_statusline()*
Move window {nr}'s status line (i.e., the bottom border) by
{offset} rows, as if being dragged by the mouse. {nr} can be a
window number or |window-ID|. A positive {offset} moves down
@@ -9099,10 +8724,7 @@ win_move_statusline({nr}, {offset}) *win_move_statusline()*
be found and FALSE otherwise.
Only works for the current tab page.
- Can also be used as a |method|: >
- GetWinnr()->win_move_statusline(offset)
-
-win_screenpos({nr}) *win_screenpos()*
+win_screenpos({nr}) *win_screenpos()*
Return the screen position of window {nr} as a list with two
numbers: [row, col]. The first window always has position
[1, 1], unless there is a tabline, then it is [2, 1].
@@ -9111,10 +8733,7 @@ win_screenpos({nr}) *win_screenpos()*
Returns [0, 0] if the window cannot be found in the current
tabpage.
- Can also be used as a |method|: >
- GetWinid()->win_screenpos()
-<
-win_splitmove({nr}, {target} [, {options}]) *win_splitmove()*
+win_splitmove({nr}, {target} [, {options}]) *win_splitmove()*
Move the window {nr} to a new split of the window {target}.
This is similar to moving to {target}, creating a new window
using |:split| but having the same contents as window {nr}, and
@@ -9134,48 +8753,39 @@ win_splitmove({nr}, {target} [, {options}]) *win_splitmove()*
present, the values of 'splitbelow' and
'splitright' are used.
- Can also be used as a |method|: >
- GetWinid()->win_splitmove(target)
-<
- *winbufnr()*
-winbufnr({nr}) The result is a Number, which is the number of the buffer
+winbufnr({nr}) *winbufnr()*
+ The result is a Number, which is the number of the buffer
associated with window {nr}. {nr} can be the window number or
the |window-ID|.
When {nr} is zero, the number of the buffer in the current
window is returned.
When window {nr} doesn't exist, -1 is returned.
- Example: >
- :echo "The file in the current window is " .. bufname(winbufnr(0))
+ Example: >vim
+ echo "The file in the current window is " .. bufname(winbufnr(0))
<
- Can also be used as a |method|: >
- FindWindow()->winbufnr()->bufname()
-<
- *wincol()*
-wincol() The result is a Number, which is the virtual column of the
+
+wincol() *wincol()*
+ The result is a Number, which is the virtual column of the
cursor in the window. This is counting screen cells from the
left side of the window. The leftmost column is one.
- *windowsversion()*
-windowsversion()
+windowsversion() *windowsversion()*
The result is a String. For MS-Windows it indicates the OS
version. E.g, Windows 10 is "10.0", Windows 8 is "6.2",
Windows XP is "5.1". For non-MS-Windows systems the result is
an empty string.
-winheight({nr}) *winheight()*
+winheight({nr}) *winheight()*
The result is a Number, which is the height of window {nr}.
{nr} can be the window number or the |window-ID|.
When {nr} is zero, the height of the current window is
returned. When window {nr} doesn't exist, -1 is returned.
An existing window always has a height of zero or more.
This excludes any window toolbar line.
- Examples: >
- :echo "The current window has " .. winheight(0) .. " lines."
+ Examples: >vim
+ echo "The current window has " .. winheight(0) .. " lines."
-< Can also be used as a |method|: >
- GetWinid()->winheight()
-<
-winlayout([{tabnr}]) *winlayout()*
+winlayout([{tabnr}]) *winlayout()*
The result is a nested List containing the layout of windows
in a tabpage.
@@ -9183,40 +8793,44 @@ winlayout([{tabnr}]) *winlayout()*
with number {tabnr}. If the tabpage {tabnr} is not found,
returns an empty list.
- For a leaf window, it returns:
+ For a leaf window, it returns: >
["leaf", {winid}]
+<
For horizontally split windows, which form a column, it
- returns:
+ returns: >
["col", [{nested list of windows}]]
- For vertically split windows, which form a row, it returns:
+< For vertically split windows, which form a row, it returns: >
["row", [{nested list of windows}]]
-
- Example: >
+<
+ Example: >vim
" Only one window in the tab page
- :echo winlayout()
+ echo winlayout()
+< >
['leaf', 1000]
+< >vim
" Two horizontally split windows
- :echo winlayout()
+ echo winlayout()
+< >
['col', [['leaf', 1000], ['leaf', 1001]]]
+< >vim
" The second tab page, with three horizontally split
" windows, with two vertically split windows in the
" middle window
- :echo winlayout(2)
+ echo winlayout(2)
+< >
['col', [['leaf', 1002], ['row', [['leaf', 1003],
['leaf', 1001]]], ['leaf', 1000]]]
<
- Can also be used as a |method|: >
- GetTabnr()->winlayout()
-<
- *winline()*
-winline() The result is a Number, which is the screen line of the cursor
+
+winline() *winline()*
+ The result is a Number, which is the screen line of the cursor
in the window. This is counting screen lines from the top of
the window. The first line is one.
If the cursor was moved the view on the file will be updated
first, this may cause a scroll.
- *winnr()*
-winnr([{arg}]) The result is a Number, which is the number of the current
+winnr([{arg}]) *winnr()*
+ The result is a Number, which is the number of the current
window. The top window has number 1.
Returns zero for a popup window.
@@ -9239,32 +8853,29 @@ winnr([{arg}]) The result is a Number, which is the number of the current
|:wincmd|.
When {arg} is invalid an error is given and zero is returned.
Also see |tabpagewinnr()| and |win_getid()|.
- Examples: >
+ Examples: >vim
let window_count = winnr('$')
let prev_window = winnr('#')
let wnum = winnr('3k')
-< Can also be used as a |method|: >
- GetWinval()->winnr()
-<
- *winrestcmd()*
-winrestcmd() Returns a sequence of |:resize| commands that should restore
+winrestcmd() *winrestcmd()*
+ Returns a sequence of |:resize| commands that should restore
the current window sizes. Only works properly when no windows
are opened or closed and the current window and tab page is
unchanged.
- Example: >
- :let cmd = winrestcmd()
- :call MessWithWindowSizes()
- :exe cmd
+ Example: >vim
+ let cmd = winrestcmd()
+ call MessWithWindowSizes()
+ exe cmd
<
- *winrestview()*
-winrestview({dict})
+
+winrestview({dict}) *winrestview()*
Uses the |Dictionary| returned by |winsaveview()| to restore
the view of the current window.
Note: The {dict} does not have to contain all values, that are
returned by |winsaveview()|. If values are missing, those
- settings won't be restored. So you can use: >
- :call winrestview({'curswant': 4})
+ settings won't be restored. So you can use: >vim
+ call winrestview({'curswant': 4})
<
This will only set the curswant value (the column the cursor
wants to move on vertical movements) of the cursor to column 5
@@ -9274,11 +8885,8 @@ winrestview({dict})
If you have changed the values the result is unpredictable.
If the window size changed the result won't be the same.
- Can also be used as a |method|: >
- GetView()->winrestview()
-<
- *winsaveview()*
-winsaveview() Returns a |Dictionary| that contains information to restore
+winsaveview() *winsaveview()*
+ Returns a |Dictionary| that contains information to restore
the view of the current window. Use |winrestview()| to
restore the view.
This is useful if you have a mapping that jumps around in the
@@ -9289,10 +8897,14 @@ winsaveview() Returns a |Dictionary| that contains information to restore
The return value includes:
lnum cursor line number
col cursor column (Note: the first column
- zero, as opposed to what getpos()
+ zero, as opposed to what |getcurpos()|
returns)
coladd cursor column offset for 'virtualedit'
- curswant column for vertical movement
+ curswant column for vertical movement (Note:
+ the first column is zero, as opposed
+ to what |getcurpos()| returns). After
+ |$| command it will be a very large
+ number equal to |v:maxcol|.
topline first line in the window
topfill filler lines, only in diff mode
leftcol first column displayed; only used when
@@ -9300,25 +8912,21 @@ winsaveview() Returns a |Dictionary| that contains information to restore
skipcol columns skipped
Note that no option values are saved.
-
-winwidth({nr}) *winwidth()*
+winwidth({nr}) *winwidth()*
The result is a Number, which is the width of window {nr}.
{nr} can be the window number or the |window-ID|.
When {nr} is zero, the width of the current window is
returned. When window {nr} doesn't exist, -1 is returned.
An existing window always has a width of zero or more.
- Examples: >
- :echo "The current window has " .. winwidth(0) .. " columns."
- :if winwidth(0) <= 50
- : 50 wincmd |
- :endif
+ Examples: >vim
+ echo "The current window has " .. winwidth(0) .. " columns."
+ if winwidth(0) <= 50
+ 50 wincmd |
+ endif
< For getting the terminal or screen size, see the 'columns'
option.
- Can also be used as a |method|: >
- GetWinid()->winwidth()
-
-wordcount() *wordcount()*
+wordcount() *wordcount()*
The result is a dictionary of byte/chars/word statistics for
the current buffer. This is the same info as provided by
|g_CTRL-G|
@@ -9339,57 +8947,62 @@ wordcount() *wordcount()*
visual_words Number of words visually selected
(only in Visual mode)
-
- *writefile()*
-writefile({object}, {fname} [, {flags}])
+writefile({object}, {fname} [, {flags}]) *writefile()*
When {object} is a |List| write it to file {fname}. Each list
item is separated with a NL. Each list item must be a String
or Number.
- When {flags} contains "b" then binary mode is used: There will
- not be a NL after the last list item. An empty item at the
- end does cause the last line in the file to end in a NL.
+ All NL characters are replaced with a NUL character.
+ Inserting CR characters needs to be done before passing {list}
+ to writefile().
When {object} is a |Blob| write the bytes to file {fname}
- unmodified.
+ unmodified, also when binary mode is not specified.
- When {flags} contains "a" then append mode is used, lines are
- appended to the file: >
- :call writefile(["foo"], "event.log", "a")
- :call writefile(["bar"], "event.log", "a")
+ {flags} must be a String. These characters are recognized:
+
+ 'b' Binary mode is used: There will not be a NL after the
+ last list item. An empty item at the end does cause the
+ last line in the file to end in a NL.
+
+ 'a' Append mode is used, lines are appended to the file: >vim
+ call writefile(["foo"], "event.log", "a")
+ call writefile(["bar"], "event.log", "a")
<
- When {flags} contains "S" fsync() call is not used, with "s"
- it is used, 'fsync' option applies by default. No fsync()
- means that writefile() will finish faster, but writes may be
- left in OS buffers and not yet written to disk. Such changes
- will disappear if system crashes before OS does writing.
+ 'D' Delete the file when the current function ends. This
+ works like: >vim
+ defer delete({fname})
+< Fails when not in a function. Also see |:defer|.
+
+ 's' fsync() is called after writing the file. This flushes
+ the file to disk, if possible. This takes more time but
+ avoids losing the file if the system crashes.
+
+ 'S' fsync() is not called, even when 'fsync' is set.
+
+ When {flags} does not contain "S" or "s" then fsync() is
+ called if the 'fsync' option is set.
- All NL characters are replaced with a NUL character.
- Inserting CR characters needs to be done before passing {list}
- to writefile().
An existing file is overwritten, if possible.
+
When the write fails -1 is returned, otherwise 0. There is an
error message if the file can't be created or when writing
fails.
- Also see |readfile()|.
- To copy a file byte for byte: >
- :let fl = readfile("foo", "b")
- :call writefile(fl, "foocopy", "b")
-< Can also be used as a |method|: >
- GetText()->writefile("thefile")
+ Also see |readfile()|.
+ To copy a file byte for byte: >vim
+ let fl = readfile("foo", "b")
+ call writefile(fl, "foocopy", "b")
-xor({expr}, {expr}) *xor()*
+xor({expr}, {expr}) *xor()*
Bitwise XOR on the two arguments. The arguments are converted
to a number. A List, Dict or Float argument causes an error.
Also see `and()` and `or()`.
- Example: >
- :let bits = xor(bits, 0x80)
-<
- Can also be used as a |method|: >
- :let bits = bits->xor(0x80)
+ Example: >vim
+ let bits = xor(bits, 0x80)
<
+
==============================================================================
-3. Matching a pattern in a String *string-match*
+2. Matching a pattern in a String *string-match*
This is common between several functions. A regexp pattern as explained at
|pattern| is normally used to find a match in the buffer lines. When a
@@ -9397,14 +9010,14 @@ pattern is used to find a match in a String, almost everything works in the
same way. The difference is that a String is handled like it is one line.
When it contains a "\n" character, this is not seen as a line break for the
pattern. It can be matched with a "\n" in the pattern, or with ".". Example:
->
- :let a = "aaaa\nxxxx"
- :echo matchstr(a, "..\n..")
- aa
- xx
- :echo matchstr(a, "a.x")
- a
- x
+>vim
+ let a = "aaaa\nxxxx"
+ echo matchstr(a, "..\n..")
+ " aa
+ " xx
+ echo matchstr(a, "a.x")
+ " a
+ " x
Don't forget that "^" will only match at the first character of the String and
"$" at the last character of the string. They don't match after or before a
diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt
index 990ba3d8fd..e1bb7c5fc7 100644
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -196,6 +196,7 @@ gR Enter Virtual Replace mode: Each character you type
*v_r*
{Visual}r{char} Replace all selected characters by {char}.
+ CTRL-C will be inserted literally.
*v_C*
{Visual}["x]C Delete the highlighted lines [into register x] and
@@ -276,7 +277,9 @@ gr{char} Replace the virtual characters under the cursor with
{char}. This replaces in screen space, not file
space. See |gR| and |Virtual-Replace-mode| for more
details. As with |r| a count may be given.
- {char} can be entered like with |r|.
+ {char} can be entered like with |r|, but characters
+ that have a special meaning in Insert mode, such as
+ most CTRL-keys, cannot be used.
*digraph-arg*
The argument for Normal mode commands like |r| and |t| is a single character.
@@ -425,6 +428,11 @@ Note similarly, when 'nrformats' includes "bin", binary numbers with a leading
'0x' or '0X' can be interpreted as hexadecimal rather than binary since '0b'
are valid hexadecimal digits.
+When the number under the cursor is too big to fit into 64 bits, it will be
+rounded off to the nearest number that can be represented, and the
+addition/subtraction is skipped. E.g. CTRL-X on 18446744073709551616 results
+in 18446744073709551615. Same for larger numbers, such as 18446744073709551618.
+
The CTRL-A command is very useful in a macro. Example: Use the following
steps to make a numbered list.
@@ -574,18 +582,29 @@ with ".". Vim does not recognize a comment (starting with '"') after the
{Visual}= Filter the highlighted lines like with ={motion}.
- *tempfile* *setuid*
-Vim uses temporary files for filtering, generating diffs and also for
-tempname(). For Unix, the file will be in a private directory (only
-accessible by the current user) to avoid security problems (e.g., a symlink
-attack or other people reading your file). When Vim exits the directory and
-all files in it are deleted. When Vim has the setuid bit set this may cause
-problems, the temp file is owned by the setuid user but the filter command
-probably runs as the original user.
-Directory for temporary files is created in the first possible directory of:
+ *tempdir* *tempfile* *setuid*
+Nvim uses temporary files for filtering and generating diffs. Plugins also
+commonly use |tempname()| for their own purposes. On the first request for
+a temporary file, Nvim creates a common directory (the "Nvim tempdir"), to
+serve as storage for all temporary files (including `stdpath("run")` files
+|$XDG_RUNTIME_DIR|) in the current session.
+
+The Nvim tempdir is created in the first available system tempdir:
Unix: $TMPDIR, /tmp, current-dir, $HOME.
Windows: $TMPDIR, $TMP, $TEMP, $USERPROFILE, current-dir.
+On unix the tempdir is created with permissions 0700 (only accessible by the
+current user) to avoid security problems (e.g. symlink attacks). On exit,
+Nvim deletes the tempdir and its contents.
+ *E5431*
+If you see an error or |log| message like: >
+ E5431: tempdir disappeared (2 times)
+this means an external process on your system deleted the Nvim tempdir.
+Typically this is caused by "antivirus" or a misconfigured cleanup service.
+
+If Nvim has the setuid bit set this may cause problems: the temp file
+is owned by the setuid user but the filter command probably runs as the
+original user.
4.2 Substitute *:substitute*
@@ -600,9 +619,9 @@ Directory for temporary files is created in the first possible directory of:
current line only. When [count] is given, replace in
[count] lines, starting with the last line in [range].
When [range] is omitted start in the current line.
- *E939*
- [count] must be a positive number. Also see
- |cmdline-ranges|.
+ *E939* *E1510*
+ [count] must be a positive number (max 2147483647)
+ Also see |cmdline-ranges|.
See |:s_flags| for [flags].
The delimiter doesn't need to be /, see
@@ -976,7 +995,7 @@ inside of strings can change! Also see 'softtabstop' option. >
< to display registers '1' and 'a'. Spaces are allowed
in {arg}.
- *:di* *:display*
+ *:di* *:dis* *:display*
:di[splay] [arg] Same as :registers.
*y* *yank*
@@ -1472,7 +1491,7 @@ white space). Three types of comments can be used:
lines. An example is this list with dashes.
- Three-piece comments that have a start string, an end string, and optional
lines in between. The strings for the start, middle and end are different.
- An example is the C style comment:
+ An example is the C style comment: >
/*
* this is a C comment
*/
@@ -1547,20 +1566,20 @@ When you hit Return in a C-comment, Vim will insert the middle comment leader
for the new line: " * ". To close this comment you just have to type "/"
before typing anything else on the new line. This will replace the
middle-comment leader with the end-comment leader and apply any specified
-alignment, leaving just " */". There is no need to hit Backspace first.
+alignment, leaving just `" */"`. There is no need to hit Backspace first.
When there is a match with a middle part, but there also is a matching end
part which is longer, the end part is used. This makes a C style comment work
without requiring the middle part to end with a space.
Here is an example of alignment flags at work to make a comment stand out
-(kind of looks like a 1 too). Consider comment string: >
+(kind of looks like a 1 too). Consider comment string: >vim
:set comments=sr:/***,m:**,ex-2:******/
-<
+>
/*** ~
**<--right aligned from "r" flag ~
** ~
-offset 2 spaces for the "-2" flag--->** ~
+ offset 2 spaces for the "-2" flag-->** ~
******/ ~
In this case, the first comment was typed, then return was pressed 4 times,
then "/" was pressed to end the comment.
@@ -1712,8 +1731,6 @@ Note that when 'textwidth' is 0, Vim does no automatic formatting anyway (but
does insert comment leaders according to the 'comments' option). An exception
is when the 'a' flag is present. |auto-format|
-Note that when 'paste' is on, Vim does no formatting at all.
-
Note that 'textwidth' can be non-zero even if Vim never performs auto-wrapping;
'textwidth' is still useful for formatting with "gq".
@@ -1725,10 +1742,10 @@ happens with formatting and auto-wrapping. Opening a line after a line
starting with "/*" or "*" and containing "*/", will cause no comment leader to
be inserted, and the indent of the new line is taken from the line containing
the start of the comment.
-E.g.:
- /* ~
- * Your typical comment. ~
- */ ~
+E.g.: >
+ /*
+ * Your typical comment.
+ */
The indent on this line is the same as the start of the above
comment.
diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt
index 1c52b2d692..7184151cda 100644
--- a/runtime/doc/channel.txt
+++ b/runtime/doc/channel.txt
@@ -82,7 +82,7 @@ only bytes can be written to Nvim's own stderr.
thus the first and last items in the {data} list may be partial lines.
Empty string completes the previous partial line. Examples (not including
the final `['']` emitted at EOF):
- - `foobar` may arrive as `['fo'], ['obar']`
+ - `foobar` may arrive as `['fo'], ['obar']`
- `foo\nbar` may arrive as
- `['foo','bar']`
- or `['foo',''], ['bar']`
@@ -117,7 +117,7 @@ simple example, echoing some data through a cat-process:
call chansend(id, "hello!")
<
-Here is a example of setting a buffer to the result of grep, but only after
+Here is an example of setting a buffer to the result of grep, but only after
all data has been processed:
>vim
function! s:OnEvent(id, data, event) dict
@@ -151,7 +151,7 @@ from the host TTY, or if Nvim is |--headless| it uses default values: >vim
When channels are opened with the `rpc` option set to true, the channel can be
used for remote method calls in both directions, see |msgpack-rpc|. Note that
rpc channels are implicitly trusted and the process at the other end can
-invoke any |api| function!
+invoke any |API| function!
==============================================================================
4. Standard IO channel *channel-stdio*
diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt
index b4923b0d70..8bed8a9ffc 100644
--- a/runtime/doc/cmdline.txt
+++ b/runtime/doc/cmdline.txt
@@ -81,9 +81,11 @@ CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL
modifier into the key.
*c_<Left>* *c_Left*
-<Left> cursor left
+<Left> cursor left. See 'wildmenu' for behavior during wildmenu
+ completion mode.
*c_<Right>* *c_Right*
-<Right> cursor right
+<Right> cursor right. See 'wildmenu' for behavior during wildmenu
+ completion mode.
*c_<S-Left>*
<S-Left> or <C-Left> *c_<C-Left>*
cursor one WORD left
@@ -93,14 +95,15 @@ CTRL-SHIFT-Q Works just like CTRL-V, but do not try to include the CTRL
CTRL-B or <Home> *c_CTRL-B* *c_<Home>* *c_Home*
cursor to beginning of command-line
CTRL-E or <End> *c_CTRL-E* *c_<End>* *c_End*
- cursor to end of command-line
+ cursor to end of command-line. See 'wildmenu' for behavior
+ during wildmenu completion mode.
*c_<LeftMouse>*
<LeftMouse> Move the cursor to the position of the mouse click.
*c_<MiddleMouse>*
<MiddleMouse> Paste the contents of the clipboard (for X11 the primary
- selection). This is similar to using CTRL-R *, but no CR
+ selection). This is similar to using `CTRL-R *`, but no CR
characters are inserted between lines.
CTRL-H *c_<BS>* *c_CTRL-H* *c_BS*
@@ -143,7 +146,7 @@ CTRL-R {register} *c_CTRL-R* *c_<C-R>*
the last delete or yank
'%' the current file name
'#' the alternate file name
- '*' the clipboard contents (X11: primary selection)
+ "*" the clipboard contents (X11: primary selection)
'+' the clipboard contents
'/' the last search pattern
':' the last command-line
@@ -190,8 +193,8 @@ CTRL-R CTRL-L *c_CTRL-R_CTRL-L* *c_<C-R>_<C-L>*
*c_CTRL-R_CTRL-R* *c_<C-R>_<C-R>*
*c_CTRL-R_CTRL-O* *c_<C-R>_<C-O>*
-CTRL-R CTRL-R {register CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L}
-CTRL-R CTRL-O {register CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L}
+CTRL-R CTRL-R `{register CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L}`
+CTRL-R CTRL-O `{register CTRL-F CTRL-P CTRL-W CTRL-A CTRL-L}`
Insert register or object under the cursor. Works like
|c_CTRL-R| but inserts the text literally. For example, if
register a contains "xy^Hz" (where ^H is a backspace),
@@ -226,6 +229,7 @@ CTRL-\ e {expr} *c_CTRL-\_e*
CTRL-Y When there is a modeless selection, copy the selection into
the clipboard.
If there is no selection CTRL-Y is inserted as a character.
+ See 'wildmenu' for behavior during wildmenu completion mode.
*c_CTRL-Z*
CTRL-Z Trigger 'wildmode'. Same as 'wildcharm', but always available.
@@ -248,10 +252,12 @@ CTRL-C quit command-line without executing
*c_<Up>* *c_Up*
<Up> recall older command-line from history, whose beginning
- matches the current command-line (see below).
+ matches the current command-line (see below). See 'wildmenu'
+ for behavior during wildmenu completion mode.
*c_<Down>* *c_Down*
<Down> recall more recent command-line from history, whose beginning
- matches the current command-line (see below).
+ matches the current command-line (see below). See 'wildmenu'
+ for behavior during wildmenu completion mode.
*c_<S-Up>* *c_<PageUp>*
<S-Up> or <PageUp>
@@ -333,6 +339,7 @@ terminals)
A positive number represents the absolute index of an entry
as it is given in the first column of a :history listing.
This number remains fixed even if other entries are deleted.
+ (see |E1510|)
A negative number means the relative position of an entry,
counted from the newest entry (which has index -1) backwards.
@@ -358,6 +365,7 @@ When editing the command-line, a few commands can be used to complete the
word before the cursor. This is available for:
- Command names: At the start of the command-line.
+- |++opt| values.
- Tags: Only after the ":tag" command.
- File names: Only after a command that accepts a file name or a setting for
an option that can be set to a file name. This is called file name
@@ -431,19 +439,24 @@ CTRL-T When 'incsearch' is set, entering a search pattern for "/" or
keyboard T is above G.
The 'wildchar' option defaults to <Tab> (CTRL-E when in Vi compatible mode; in
-a previous version <Esc> was used). In the pattern standard wildcards '*' and
-'?' are accepted when matching file names. '*' matches any string, '?'
+a previous version <Esc> was used). In the pattern standard wildcards "*" and
+'?' are accepted when matching file names. "*" matches any string, '?'
matches exactly one character.
When repeating 'wildchar' or CTRL-N you cycle through the matches, eventually
ending up back to what was typed. If the first match is not what you wanted,
you can use <S-Tab> or CTRL-P to go straight back to what you typed.
-The 'wildignorecase' option can be set to ignore case in filenames.
-
The 'wildmenu' option can be set to show the matches just above the command
line.
+The 'wildoptions' option provides additional configuration to use a popup menu
+for 'wildmenu', and to use fuzzy matching.
+
+The 'wildignorecase' option can be set to ignore case in filenames. For
+completing other texts (e.g. command names), the 'ignorecase' option is used
+instead (fuzzy matching always ignores case, however).
+
If you like tcsh's autolist completion, you can use this mapping:
:cnoremap X <C-L><C-D>
(Where X is the command key to use, <C-L> is CTRL-L and <C-D> is CTRL-D)
@@ -493,16 +506,26 @@ example, to match only files that end in ".c": >
:e *.c$
This will not match a file ending in ".cpp". Without the "$" it does match.
-The old value of an option can be obtained by hitting 'wildchar' just after
-the '='. For example, typing 'wildchar' after ":set dir=" will insert the
-current value of 'dir'. This overrules file name completion for the options
-that take a file name.
-
If you would like using <S-Tab> for CTRL-P in an xterm, put this command in
your .cshrc: >
xmodmap -e "keysym Tab = Tab Find"
And this in your vimrc: >
:cmap <Esc>[1~ <C-P>
+< *complete-set-option*
+When setting an option using |:set=|, the old value of an option can be
+obtained by hitting 'wildchar' just after the '='. For example, typing
+'wildchar' after ":set dir=" will insert the current value of 'dir'. This
+overrules file name completion for the options that take a file name.
+
+When using |:set=|, |:set+=|, or |:set^=|, string options that have
+pre-defined names or syntax (e.g. 'diffopt', 'listchars') or are a list of
+single-character flags (e.g. 'shortmess') will also present a list of possible
+values for completion when using 'wildchar'.
+
+When using |:set-=|, comma-separated options like 'diffopt' or 'backupdir'
+will show each item separately. Flag list options like 'shortmess' will show
+both the entire old value and the individual flags. Otherwise completion will
+just fill in with the entire old value.
==============================================================================
3. Ex command-lines *cmdline-lines*
@@ -584,6 +607,7 @@ followed by another Vim command:
:registers
:read !
:sign
+ :tabdo
:terminal
:vglobal
:windo
@@ -731,7 +755,7 @@ An example for subtracting (which isn't very useful): >
On this text:
1 one ~
2 two ~
- 3 three FOLDED~
+ 3 three FOLDED ~
4 four FOLDED ~
5 five FOLDED ~
6 six FOLDED ~
@@ -891,7 +915,7 @@ Note: these are typed literally, they are not special keys!
to form a C expression. E.g., when the cursor is on "arg"
of "ptr->arg" then the result is "ptr->arg"; when the
cursor is on "]" of "list[idx]" then the result is
- "list[idx]". This is used for |v:beval_text|.
+ "list[idx]".
*:<cfile>* *<cfile>*
<cfile> is replaced with the path name under the cursor (like what
|gf| uses)
@@ -901,9 +925,10 @@ Note: these are typed literally, they are not special keys!
write. *E495*
*:<abuf>* *<abuf>*
<abuf> When executing autocommands, is replaced with the currently
- effective buffer number (for ":r file" and ":so file" it is
- the current buffer, the file being read/sourced is not in a
- buffer). *E496*
+ effective buffer number. It is not set for all events,
+ also see |bufnr()|. For ":r file" and ":so file" it is the
+ current buffer, the file being read/sourced is not in a
+ buffer. *E496*
*:<amatch>* *<amatch>*
<amatch> When executing autocommands, is replaced with the match for
which this autocommand was executed. *E497*
@@ -979,7 +1004,7 @@ These modifiers can be given, in this order:
precede any :r or :e.
:r Root of the file name (the last extension removed). When
there is only an extension (file name that starts with '.',
- e.g., ".nvimrc"), it is not removed. Can be repeated to
+ e.g., ".nvimrc"), it is not removed. Can be repeated to
remove several extensions (last one first).
:e Extension of the file name. Only makes sense when used alone.
When there is no extension the result is empty.
@@ -1056,7 +1081,7 @@ But expansion is only done if there are any wildcards before expanding the
'%', '#', etc.. This avoids expanding wildcards inside a file name. If you
want to expand the result of <cfile>, add a wildcard character to it.
Examples: (alternate file name is "?readme?")
- command expands to ~
+ command expands to >
:e # :e ?readme?
:e `ls #` :e {files matching "?readme?"}
:e #.* :e {files matching "?readme?.*"}
@@ -1228,6 +1253,6 @@ The character used for the pattern indicates the type of command-line:
? backward search string
= expression for "= |expr-register|
@ string for |input()|
- - text for |:insert| or |:append|
+ `-` text for |:insert| or |:append|
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt
index 1bdd13ac0c..0a07f06c75 100644
--- a/runtime/doc/deprecated.txt
+++ b/runtime/doc/deprecated.txt
@@ -15,8 +15,18 @@ Deprecated features
API
- *nvim_buf_clear_highlight()* Use |nvim_buf_clear_namespace()| instead.
- *nvim_buf_set_virtual_text()* Use |nvim_buf_set_extmark()| instead.
-- *nvim_command_output()* Use |nvim_exec()| instead.
+- *nvim_command_output()* Use |nvim_exec2()| instead.
- *nvim_execute_lua()* Use |nvim_exec_lua()| instead.
+- *nvim_get_hl_by_name()* Use |nvim_get_hl()| instead.
+- *nvim_get_hl_by_id()* Use |nvim_get_hl()| instead.
+- *nvim_exec()* Use |nvim_exec2()| instead.
+- *nvim_get_option_info()* Use |nvim_get_option_info2()| instead.
+- *nvim_buf_get_option()* Use |nvim_get_option_value()| instead.
+- *nvim_buf_set_option()* Use |nvim_set_option_value()| instead.
+- *nvim_get_option()* Use |nvim_get_option_value()| instead.
+- *nvim_set_option()* Use |nvim_set_option_value()| instead.
+- *nvim_win_get_option()* Use |nvim_get_option_value()| instead.
+- *nvim_win_set_option()* Use |nvim_set_option_value()| instead.
COMMANDS
- *:rv* *:rviminfo* Deprecated alias to |:rshada| command.
@@ -50,11 +60,11 @@ FUNCTIONS
- *buffer_name()* Obsolete name for |bufname()|.
- *buffer_number()* Obsolete name for |bufnr()|.
- *file_readable()* Obsolete name for |filereadable()|.
-- *health#report_error* Use Lua |vim.health.report_error()| instead.
-- *health#report_info* Use Lua |vim.health.report_info()| instead.
-- *health#report_ok* Use Lua |vim.health.report_ok()| instead.
-- *health#report_start* Use Lua |vim.health.report_start()| instead.
-- *health#report_warn* Use Lua |vim.health.report_warn()| instead.
+- *health#report_error* *vim.health.report_error()* Use |vim.health.error()| instead.
+- *health#report_info* *vim.health.report_info()* Use |vim.health.info()| instead.
+- *health#report_ok* *vim.health.report_ok()* Use |vim.health.ok()| instead.
+- *health#report_start* *vim.health.report_start()* Use |vim.health.start()| instead.
+- *health#report_warn* *vim.health.report_warn()* Use |vim.health.warn()| instead.
- *highlight_exists()* Obsolete name for |hlexists()|.
- *highlightID()* Obsolete name for |hlID()|.
- *inputdialog()* Use |input()| instead.
@@ -107,20 +117,53 @@ internally and are no longer exposed as part of the API. Instead, use
- *vim.lsp.diagnostic.set_virtual_text()*
LSP FUNCTIONS
-- *vim.lsp.buf.range_code_action()* Use |vim.lsp.buf.code_action()| with
- the `range` parameter.
-- *vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead.
-- *vim.lsp.util.set_qflist()* Use |setqflist()| instead.
-- *vim.lsp.util.set_loclist()* Use |setloclist()| instead.
-- *vim.lsp.buf_get_clients()* Use |vim.lsp.get_active_clients()| with
- {buffer = bufnr} instead.
-- *vim.lsp.buf.formatting()* Use |vim.lsp.buf.format()| with
- {async = true} instead.
-- *vim.lsp.buf.range_formatting()* Use |vim.lsp.formatexpr()|
- or |vim.lsp.buf.format()| instead.
+- *vim.lsp.buf.server_ready()*
+ Use |LspAttach| instead, depending on your use-case. "Server ready" is not
+ part of the LSP spec, so the Nvim LSP client cannot meaningfully implement
+ it. "Ready" is ambiguous because:
+ - Language servers may finish analyzing the workspace, but edits can always
+ re-trigger analysis/builds.
+ - Language servers can serve some requests even while processing changes.
+- *vim.lsp.buf.range_code_action()* Use |vim.lsp.buf.code_action()| with
+ the `range` parameter.
+- *vim.lsp.util.diagnostics_to_items()* Use |vim.diagnostic.toqflist()| instead.
+- *vim.lsp.util.set_qflist()* Use |setqflist()| instead.
+- *vim.lsp.util.set_loclist()* Use |setloclist()| instead.
+- *vim.lsp.buf_get_clients()* Use |vim.lsp.get_clients()| with
+ {buffer=bufnr} instead.
+- *vim.lsp.buf.formatting()* Use |vim.lsp.buf.format()| with
+ {async=true} instead.
+- *vim.lsp.buf.formatting_sync()* Use |vim.lsp.buf.format()| with
+ {async=false} instead.
+- *vim.lsp.buf.range_formatting()* Use |vim.lsp.formatexpr()|
+ or |vim.lsp.buf.format()| instead.
+- *vim.lsp.util.get_progress_messages()* Use |vim.lsp.status()| or access
+ `progress` of |vim.lsp.client|
+- *vim.lsp.get_active_clients()* Use |vim.lsp.get_clients()|
+- *vim.lsp.for_each_buffer_client()* Use |vim.lsp.get_clients()|
+- *vim.lsp.util.trim_empty_lines()* Use |vim.split()| with `trimempty` instead.
+- *vim.lsp.util.try_trim_markdown_code_blocks()*
+- *vim.lsp.util.set_lines()*
+- *vim.lsp.util.extract_completion_items()*
+- *vim.lsp.util.parse_snippet()*
+- *vim.lsp.util.text_document_completion_list_to_complete_items()*
+
+TREESITTER FUNCTIONS
+- *vim.treesitter.language.require_language()* Use |vim.treesitter.language.add()|
+ instead.
+- *vim.treesitter.get_node_at_pos()* Use |vim.treesitter.get_node()|
+ instead.
+- *vim.treesitter.get_node_at_cursor()* Use |vim.treesitter.get_node()|
+ and |TSNode:type()| instead.
+- *vim.treesitter.query.get_query()* Use |vim.treesitter.query.get()|
+ instead.
+- *LanguageTree:for_each_child()* Use |LanguageTree:children()|
+ (non-recursive) instead.
LUA
-- *vim.register_keystroke_callback()* Use |vim.on_key()| instead.
+- vim.register_keystroke_callback() Use |vim.on_key()| instead.
+- *vim.pretty_print()* Use |vim.print()| instead.
+- *vim.loop* Use |vim.uv| instead.
NORMAL COMMANDS
- *]f* *[f* Same as "gf".
@@ -137,6 +180,21 @@ OPTIONS
- 'viewoptions' Flags "unix", "slash" are ignored and always enabled.
- *'viminfo'* Deprecated alias to 'shada' option.
- *'viminfofile'* Deprecated alias to 'shadafile' option.
+- *'paste'* *'nopaste'* Just Paste It.™ The 'paste' option is obsolete:
+ |paste| is handled automatically when you paste text
+ using your terminal's or GUI's paste feature
+ (CTRL-SHIFT-v, CMD-v (macOS), middle-click, …).
+ Enables "paste mode":
+ - Disables mappings in Insert, Cmdline mode.
+ - Disables abbreviations.
+ - Resets 'autoindent' 'expandtab' 'revins' 'ruler'
+ 'showmatch' 'smartindent' 'smarttab' 'softtabstop'
+ 'textwidth' 'wrapmargin'.
+ - Treats 'formatoptions' as empty.
+ - Disables the effect of these options:
+ - 'cindent'
+ - 'indentexpr'
+ - 'lisp'
UI EXTENSIONS
- *ui-wildmenu* Use |ui-cmdline| with |ui-popupmenu| instead. Enabled
@@ -144,6 +202,8 @@ UI EXTENSIONS
- `["wildmenu_show", items]`
- `["wildmenu_select", selected]`
- `["wildmenu_hide"]`
+- *term_background* Unused. The terminal background color is now detected
+ by the Nvim core directly instead of the TUI.
VARIABLES
- *b:terminal_job_pid* PID of the top-level process in a |:terminal|.
diff --git a/runtime/doc/dev_style.txt b/runtime/doc/dev_style.txt
index b96b01dbff..cb28f1a845 100644
--- a/runtime/doc/dev_style.txt
+++ b/runtime/doc/dev_style.txt
@@ -32,19 +32,13 @@ but we nonetheless keep things as they are in order to preserve consistency.
Header Files *dev-style-header*
-The #define Guard ~
+Header guard ~
-All header files should have `#define` guards to prevent multiple inclusion.
-The format of the symbol name should be `NVIM_<DIRECTORY>_<FILE>_H`.
+All header files should start with `#pragma once` to prevent multiple inclusion.
In foo/bar.h:
>c
- #ifndef NVIM_FOO_BAR_H
- #define NVIM_FOO_BAR_H
-
- ...
-
- #endif // NVIM_FOO_BAR_H
+ #pragma once
<
@@ -245,15 +239,13 @@ contain only non-static function declarations. >c
// src/nvim/foo.h file
- #ifndef NVIM_FOO_H
- #define NVIM_FOO_H
+ #pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "foo.h.generated.h"
#endif
- #endif // NVIM_FOO_H
64-bit Portability ~
@@ -846,7 +838,7 @@ Annotate non-trivial fall-through between cases.
If not conditional on an enumerated value, switch statements should always
have a `default` case (in the case of an enumerated value, the compiler will
warn you if any values are not handled). If the default case should never
-execute, simply `assert`: >c
+execute, simply use `abort()`: >c
switch (var) {
case 0:
@@ -856,8 +848,54 @@ execute, simply `assert`: >c
...
break;
default:
- assert(false);
+ abort();
+ }
+
+Switch statements that are conditional on an enumerated value should not have
+a `default` case if it is exhaustive. Explicit case labels are preferred over
+`default`, even if it leads to multiple case labels for the same code. For
+example, instead of: >c
+
+ case A:
+ ...
+ case B:
+ ...
+ case C:
+ ...
+ default:
+ ...
+
+You should use: >c
+
+ case A:
+ ...
+ case B:
+ ...
+ case C:
+ ...
+ case D:
+ case E:
+ case F:
+ ...
+
+Certain compilers do not recognize an exhaustive enum switch statement as
+exhaustive, which causes compiler warnings when there is a return statement in
+every case of a switch statement, but no catch-all return statement. To fix
+these spurious errors, you are advised to use `UNREACHABLE` after the switch
+statement to explicitly tell the compiler that the switch statement always
+returns and any code after it is unreachable. For example: >c
+
+ enum { A, B, C } var;
+ ...
+ switch (var) {
+ case A:
+ return 1;
+ case B:
+ return 2;
+ case C:
+ return 3;
}
+ UNREACHABLE;
Return Values ~
diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt
index ff48ae3e26..f1d74326c7 100644
--- a/runtime/doc/develop.txt
+++ b/runtime/doc/develop.txt
@@ -81,7 +81,7 @@ include the kitchen sink... but it's good for plumbing."
Developer guidelines *dev-guidelines*
-PROVIDERS *dev-provider*
+PROVIDERS *dev-provider*
A primary goal of Nvim is to allow extension of the editor without special
knowledge in the core. Some core functions are delegated to "providers"
@@ -99,8 +99,8 @@ Examples:
scripting is performed by an external host process implemented in ~2k lines
of Python.
-The provider framework invokes VimL from C. It is composed of two functions
-in eval.c:
+The provider framework invokes Vimscript from C. It is composed of two
+functions in eval.c:
- eval_call_provider(name, method, arguments, discard): calls
provider#{name}#Call with the method and arguments. If discard is true, any
@@ -130,8 +130,24 @@ DOCUMENTATION *dev-doc*
(docstrings, user manual, website materials, newsletters, …). Don't mince
words. Personality and flavor, used sparingly, are welcome--but in general,
optimize for the reader's time and energy: be "precise yet concise".
- - See https://developers.google.com/style/tone
- Prefer the active voice: "Foo does X", not "X is done by Foo".
+ - "The words you choose are an essential part of the user experience."
+ https://developer.apple.com/design/human-interface-guidelines/writing
+ - "...without being overly colloquial or frivolous."
+ https://developers.google.com/style/tone
+- Write docstrings (as opposed to inline comments) with present tense ("Gets"),
+ not imperative ("Get"). This tends to reduce ambiguity and improve clarity
+ by describing "What" instead of "How". >
+ GOOD:
+ /// Gets a highlight definition.
+ BAD:
+ /// Get a highlight definition.
+- Avoid starting docstrings with "The" or "A" unless needed to avoid
+ ambiguity. This is a visual aid and reduces noise. >
+ GOOD:
+ /// @param dirname Path fragment before `pend`
+ BAD:
+ /// @param dirname The path fragment before `pend`
- Vim differences:
- Do not prefix help tags with "nvim-". Use |vim_diff.txt| to catalog
differences from Vim; no other distinction is necessary.
@@ -143,13 +159,6 @@ DOCUMENTATION *dev-doc*
not "the user host terminal".
- Use "tui-" to prefix help tags related to the host terminal, and "TUI"
in prose if possible.
-- Docstrings: do not start parameter descriptions with "The" or "A" unless it
- is critical to avoid ambiguity. >
- GOOD:
- /// @param dirname Path fragment before `pend`
- BAD:
- /// @param dirname The path fragment before `pend`
-<
Documentation format ~
@@ -159,14 +168,14 @@ https://neovim.io/doc/user ).
Strict "vimdoc" subset:
-- Use lists (like this!) prefixed with "-", "*", or "•", for adjacent lines
- that you don't want auto-wrapped. Lists are always rendered with "flow"
- (soft-wrapped) layout instead of preformatted (hard-wrapped) layout common
- in legacy :help docs.
+- Use lists (like this!) prefixed with "-" or "•", for adjacent lines that you
+ don't want to auto-wrap. Lists are always rendered with "flow" layout
+ (soft-wrapped) instead of preformatted (hard-wrapped) layout common in
+ legacy :help docs.
- Limitation: currently the parser https://github.com/neovim/tree-sitter-vimdoc
does not understand numbered listitems, so use a bullet symbol (- or •)
- before numbered items, e.g. "- 1." instead of "1.".
-- Separate blocks (paragraphs) of content by a blank line(s).
+ before numbered items, e.g. "• 1." instead of "1.".
+- Separate blocks (paragraphs) of content by a blank line.
- Do not use indentation in random places—that prevents the page from using
"flow" layout. If you need a preformatted section, put it in
a |help-codeblock| starting with ">".
@@ -183,7 +192,7 @@ Docstring format:
`@note`, `@param`, `@returns`
- Limited markdown is supported.
- List-items start with `-` (useful to nest or "indent")
-- Use `<pre>` for code samples.
+- Use ``` for code samples.
Code samples can be annotated as `vim` or `lua`
Example: the help for |nvim_open_win()| is generated from a docstring defined
@@ -193,10 +202,16 @@ in src/nvim/api/win_config.c like this: >
/// ...
///
/// Example (Lua): window-relative float
- /// <pre>lua
- /// vim.api.nvim_open_win(0, false,
- /// {relative='win', row=3, col=3, width=12, height=3})
- /// </pre>
+ ///
+ /// ```lua
+ /// vim.api.nvim_open_win(0, false, {
+ /// relative='win',
+ /// row=3,
+ /// col=3,
+ /// width=12,
+ /// height=3,
+ /// })
+ /// ```
///
/// @param buffer Buffer to display
/// @param enter Enter the window
@@ -217,13 +232,13 @@ Lua documentation lives in the source code, as docstrings on the function
definitions. The |lua-vim| :help is generated from the docstrings.
Docstring format:
-- Lines in the main description start with `---`
-- Special tokens start with `---@` followed by the token name:
- `---@see`, `---@param`, `---@returns`
+- Use LuaCATS annotations: https://luals.github.io/wiki/annotations/
- Limited markdown is supported.
- List-items start with `-` (useful to nest or "indent")
-- Use `<pre>` for code samples.
+- Use ``` for code samples.
Code samples can be annotated as `vim` or `lua`
+- Use `@nodoc` to prevent documentation generation.
+- Files which has `@meta` are only used for typing and documentation.
Example: the help for |vim.paste()| is generated from a docstring decorating
vim.paste in runtime/lua/vim/_editor.lua like this: >
@@ -232,21 +247,24 @@ vim.paste in runtime/lua/vim/_editor.lua like this: >
--- (such as the |TUI|) pastes text into the editor.
---
--- Example: To remove ANSI color codes when pasting:
- --- <pre>lua
+ ---
+ --- ```lua
--- vim.paste = (function()
--- local overridden = vim.paste
--- ...
--- end)()
- --- </pre>
+ --- ```
---
- ---@see |paste|
+ --- @see |paste|
---
- ---@param lines ...
- ---@param phase ...
- ---@returns false if client should cancel the paste.
+ --- @param lines ...
+ --- @param phase ...
+ --- @returns false if client should cancel the paste.
+
+LUA STDLIB DESIGN GUIDELINES *dev-lua*
-LUA *dev-lua*
+See also |dev-naming|.
- Keep the core Lua modules |lua-stdlib| simple. Avoid elaborate OOP or
pseudo-OOP designs. Plugin authors just want functions to call, not a big,
@@ -255,10 +273,38 @@ LUA *dev-lua*
tables or values are easier to serialize, easier to construct from literals,
easier to inspect and print, and inherently compatible with all Lua plugins.
(This guideline doesn't apply to opaque, non-data objects like `vim.cmd`.)
+- stdlib functions should follow these common patterns:
+ - accept iterable instead of table
+ - exception: in some cases iterable doesn't make sense, e.g. spair() sorts
+ the input by definition, so there is no reason for it to accept an
+ iterable, because the input needs to be "hydrated", it can't operate on
+ a "stream".
+ - return iterable instead of table
+ - mimic the pairs() or ipairs() interface if the function is intended to be
+ used in a "for" loop.
+
+Interface conventions ~
+
+- When accepting a buffer id, etc., 0 means "current buffer", nil means "all
+ buffers". Likewise for window id, tabpage id, etc.
+ - Examples: |vim.lsp.codelens.clear()| |vim.diagnostic.enable()|
+- Any function signature that accepts a callback function should define the
+ callback as the LAST parameter, if possible. This improves readability of
+ calls by placing the less "noisy" arguments near the start. >
+ GOOD:
+ filter(table, opts, function() … end)
+ BAD:
+ filter(function() … end, table, opts)
+API DESIGN GUIDELINES *dev-api*
-API *dev-api*
+See also |dev-naming|.
+- When adding an API, check the following:
+ - What precedents did you draw from? How does your solution compare to them?
+ - Does your new API allow future expansion? How? Or why not?
+ - Is the new API similar to existing APIs? Do we need to deprecate the old ones?
+ - Did you cross-reference related concepts in the docs?
- Avoid "mutually exclusive" parameters--via constraints or limitations, if
necessary. For example nvim_create_autocmd() has mutually exclusive
"callback" and "command" args; but the "command" arg could be eliminated by
@@ -266,66 +312,102 @@ API *dev-api*
"callback" arg as an Ex command (which can call Vimscript functions). The
"buffer" arg could also be eliminated by treating a number "pattern" as
a buffer number.
+- Avoid functions that depend on cursor position, current buffer, etc. Instead
+ the function should take a position parameter, buffer parameter, etc.
- *dev-api-naming*
-Use this format to name new RPC |API| functions:
+Where things go ~
- nvim_{thing}_{action}_{arbitrary-qualifiers}
+- API (libnvim/RPC): exposes low-level internals, or fundamental things (such
+ as `nvim_exec_lua()`) needed by clients or C consumers.
+- Lua stdlib = high-level functionality that builds on top of the API.
-If the function acts on an object then {thing} is the name of that object
-(e.g. "buf" or "win"). If the function operates in a "global" context then
-{thing} is usually omitted (but consider "namespacing" your global operations
-with a {thing} that groups functions under a common concept).
-
-Use existing common {action} names if possible:
- - add Append to, or insert into, a collection
- - call Call a function
- - create Create a new (non-trivial) thing
- - del Delete a thing (or group of things)
- - eval Evaluate an expression
- - exec Execute code
- - fmt Format
- - get Get things (often by a query)
- - open Open
- - parse Parse something into a structured form
- - set Set a thing (or group of things)
+NAMING GUIDELINES *dev-naming*
+
+Naming is exceedingly important: the name of a thing is the primary interface
+for uses it, discusses it, searches for it, shares it... Consistent
+naming in the stdlib, API, and UI helps both users and developers discover and
+intuitively understand related concepts ("families"), and reduces cognitive
+burden. Discoverability encourages code re-use and likewise avoids redundant,
+overlapping mechanisms, which reduces code surface-area, and thereby minimizes
+bugs...
+
+Naming conventions ~
+
+In general, look for precedent when choosing a name, that is, look at existing
+(non-deprecated) functions. In particular, see below...
+
+ *dev-name-common*
+Use existing common {verb} names (actions) if possible:
+ - add: Appends or inserts into a collection
+ - attach: Listens to something to get events from it (TODO: rename to "on"?)
+ - call: Calls a function
+ - clear: Clears state but does not destroy the container
+ - create: Creates a new (non-trivial) thing (TODO: rename to "def"?)
+ - del: Deletes a thing (or group of things)
+ - detach: Dispose attached listener (TODO: rename to "un"?)
+ - eval: Evaluates an expression
+ - exec: Executes code
+ - fmt: Formats
+ - get: Gets things (often by a query)
+ - inspect: Presents a high-level, often interactive, view
+ - open: Opens something (a buffer, window, …)
+ - parse: Parses something into a structured form
+ - set: Sets a thing (or group of things)
+ - try_{verb}: Best-effort operation, failure returns null or error obj
Do NOT use these deprecated verbs:
- - list Redundant with "get"
+ - list: Redundant with "get"
+ - show: Redundant with "print", "echo"
+ - notify: Redundant with "print", "echo"
-Use consistent names for {thing} (nouns) in API functions: buffer is called
+Use consistent names for {noun} (nouns) in API functions: buffer is called
"buf" everywhere, not "buffer" in some places and "buf" in others.
- - buf Buffer
- - chan |channel|
- - cmd Command
- - cmdline Command-line UI or input
- - fn Function
- - hl Highlight
- - pos Position
- - proc System process
- - tabpage Tabpage
- - win Window
+ - buf: Buffer
+ - chan: |channel|
+ - cmd: Command
+ - cmdline: Command-line UI or input
+ - fn: Function
+ - hl: Highlight
+ - pos: Position
+ - proc: System process
+ - tabpage: Tabpage
+ - win: Window
Do NOT use these deprecated nouns:
- buffer
+ - callback Use on_foo instead
- command
- window
-Example:
- `nvim_get_keymap('v')` operates in a global context (first parameter is not
- a Buffer). The "get" {action} indicates that it gets anything matching the
- given filter parameter. There is no need for a "list" action because
- `nvim_get_keymap('')` (i.e., empty filter) returns all items.
+ *dev-name-events*
+Use the "on_" prefix to name event-handling callbacks and also the interface for
+"registering" such handlers (on_key). The dual nature is acceptable to avoid
+a confused collection of naming conventions for these related concepts.
-Example:
- `nvim_buf_del_mark` acts on a `Buffer` object (the first parameter)
- and uses the "del" {action}.
+Editor |events| (autocommands) are historically named like: >
+ {Noun}{Event}
-Use this format to name new API events:
- nvim_{thing}_{event}_event
+Use this format to name API (RPC) events: >
+ nvim_{noun}_{event-name}_event
+
+Example: >
+ nvim_buf_changedtick_event
+<
+ *dev-name-api*
+Use this format to name new RPC |API| functions: >
+ nvim_{noun}_{verb}_{arbitrary-qualifiers}
+
+If the function acts on an object then {noun} is the name of that object
+(e.g. "buf" or "win"). If the function operates in a "global" context then
+{noun} is usually omitted (but consider "namespacing" your global operations
+with a {noun} that groups functions under a common concept).
-Example:
- `nvim_buf_changedtick_event`
+- Example: `nvim_get_keymap('v')` operates in a global context (first
+ parameter is not a Buffer). The "get" verb indicates that it gets anything
+ matching the given filter parameter. A "list" verb is unnecessary because
+ `nvim_get_keymap('')` (empty filter) returns all items.
+- Example: `nvim_buf_del_mark` acts on a `Buffer` object (the first parameter)
+ and uses the "del" {verb}.
API-CLIENT *dev-api-client*
@@ -366,7 +448,7 @@ Examples of API-client package names:
- GOOD: nvim-racket
- GOOD: pynvim
- BAD: python-client
-- BAD: neovim
+- BAD: neovim_
API client implementation guidelines ~
@@ -391,7 +473,7 @@ API client implementation guidelines ~
https://github.com/msgpack-rpc/msgpack-rpc
-EXTERNAL UI *dev-ui*
+EXTERNAL UI *dev-ui*
External UIs should be aware of the |api-contract|. In particular, future
versions of Nvim may add new items to existing events. The API is strongly
@@ -415,21 +497,8 @@ External UIs are expected to implement these common features:
- Consider the "option_set" |ui-global| event as a hint for other GUI
behaviors. Various UI-related options ('guifont', 'ambiwidth', …) are
published in this event. See also "mouse_on", "mouse_off".
-
-
-NAMING *dev-naming*
-
-Naming is important. Consistent naming in the API and UI helps both users and
-developers discover and intuitively understand related concepts ("families"),
-and reduces cognitive burden. Discoverability encourages code re-use and
-likewise avoids redundant, overlapping mechanisms, which reduces code
-surface-area, and thereby minimizes bugs...
-
-Naming conventions ~
-
-Use the "on_" prefix to name event handlers and also the interface for
-"registering" such handlers (on_key). The dual nature is acceptable to avoid
-a confused collection of naming conventions for these related concepts.
+- UIs generally should NOT set |$NVIM_APPNAME| (unless explicitly requested by
+ the user).
vim:tw=78:ts=8:sw=4:et:ft=help:norl:
diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt
index 7066a3739a..7f5c809ac3 100644
--- a/runtime/doc/diagnostic.txt
+++ b/runtime/doc/diagnostic.txt
@@ -40,15 +40,15 @@ requires a namespace.
*diagnostic-structure*
A diagnostic is a Lua table with the following keys. Required keys are
-indicated with (*):
+indicated with (+):
bufnr: Buffer number
- lnum(*): The starting line of the diagnostic
+ lnum(+): The starting line of the diagnostic
end_lnum: The final line of the diagnostic
- col(*): The starting column of the diagnostic
+ col(+): The starting column of the diagnostic
end_col: The final column of the diagnostic
severity: The severity of the diagnostic |vim.diagnostic.severity|
- message(*): The diagnostic text
+ message(+): The diagnostic text
source: The source of the diagnostic
code: The diagnostic code
user_data: Arbitrary data plugins or users can add
@@ -66,7 +66,7 @@ The "severity" key in a diagnostic is one of the values defined in
vim.diagnostic.severity.HINT
Functions that take a severity as an optional parameter (e.g.
-|vim.diagnostic.get()|) accept one of two forms:
+|vim.diagnostic.get()|) accept one of three forms:
1. A single |vim.diagnostic.severity| value: >lua
@@ -75,8 +75,17 @@ Functions that take a severity as an optional parameter (e.g.
2. A table with a "min" or "max" key (or both): >lua
vim.diagnostic.get(0, { severity = { min = vim.diagnostic.severity.WARN } })
+<
+ This form allows users to specify a range of severities.
+
+3. A list-like table: >lua
-The latter form allows users to specify a range of severities.
+ vim.diagnostic.get(0, { severity = {
+ vim.diagnostic.severity.WARN,
+ vim.diagnostic.severity.INFO,
+ } })
+<
+ This form allows users to filter for specific severities
==============================================================================
HANDLERS *diagnostic-handlers*
@@ -295,6 +304,14 @@ DiagnosticSignHint
DiagnosticSignOk
Used for "Ok" signs in sign column.
+ *hl-DiagnosticDeprecated*
+DiagnosticDeprecated
+ Used for deprecated or obsolete code.
+
+ *hl-DiagnosticUnnecessary*
+DiagnosticUnnecessary
+ Used for unnecessary or unused code.
+
==============================================================================
SIGNS *diagnostic-signs*
@@ -325,7 +342,7 @@ Example: >lua
vim.api.nvim_create_autocmd('DiagnosticChanged', {
callback = function(args)
local diagnostics = args.data.diagnostics
- vim.pretty_print(diagnostics)
+ vim.print(diagnostics)
end,
})
<
@@ -342,19 +359,17 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
followed by namespace configuration, and finally global configuration.
For example, if a user enables virtual text globally with >lua
-
- vim.diagnostic.config({ virtual_text = true })
+ vim.diagnostic.config({ virtual_text = true })
<
and a diagnostic producer sets diagnostics with >lua
-
- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
+ vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
<
then virtual text will not be enabled for those diagnostics.
- Note:
- Each of the configuration options below accepts one of the following:
+ Note: ~
+ • Each of the configuration options below accepts one of the following:
• `false`: Disable this feature
• `true`: Enable this feature, use default settings.
• `table`: Enable this feature with overrides. Use an empty table to
@@ -374,7 +389,10 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
• virtual_text: (default true) Use virtual text for
diagnostics. If multiple diagnostics are set for a
namespace, one prefix per diagnostic + the last
- diagnostic message are shown. Options:
+ diagnostic message are shown. In addition to the
+ options listed below, the "virt_text" options of
+ |nvim_buf_set_extmark()| may also be used here (e.g.
+ "virt_text_pos" and "hl_mode"). Options:
• severity: Only show virtual text for diagnostics
matching the given severity |diagnostic-severity|
• source: (boolean or string) Include the diagnostic
@@ -384,8 +402,14 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
always show the diagnostic source.
• spacing: (number) Amount of empty spaces inserted at
the beginning of the virtual text.
- • prefix: (string) Prepend diagnostic message with
- prefix.
+ • prefix: (string or function) prepend diagnostic
+ message with prefix. If a function, it must have the
+ signature (diagnostic, i, total) -> string, where
+ {diagnostic} is of type |diagnostic-structure|, {i}
+ is the index of the diagnostic being evaluated, and
+ {total} is the total number of diagnostics for the
+ line. This can be used to render diagnostic symbols
+ or error codes.
• suffix: (string or function) Append diagnostic
message with suffix. If a function, it must have the
signature (diagnostic) -> string, where {diagnostic}
@@ -423,32 +447,32 @@ config({opts}, {namespace}) *vim.diagnostic.config()*
severities are displayed before lower severities (e.g.
ERROR is displayed before WARN). Options:
• reverse: (boolean) Reverse sort order
- • {namespace} (number|nil) Update the options for the given namespace.
+ • {namespace} (integer|nil) Update the options for the given namespace.
When omitted, update the global diagnostic options.
disable({bufnr}, {namespace}) *vim.diagnostic.disable()*
Disable diagnostics in the given buffer.
Parameters: ~
- • {bufnr} (number|nil) Buffer number, or 0 for current buffer. When
- omitted, disable diagnostics in all buffers.
- • {namespace} (number|nil) Only disable diagnostics for the given
+ • {bufnr} (integer|nil) Buffer number, or 0 for current buffer.
+ When omitted, disable diagnostics in all buffers.
+ • {namespace} (integer|nil) Only disable diagnostics for the given
namespace.
enable({bufnr}, {namespace}) *vim.diagnostic.enable()*
Enable diagnostics in the given buffer.
Parameters: ~
- • {bufnr} (number|nil) Buffer number, or 0 for current buffer. When
- omitted, enable diagnostics in all buffers.
- • {namespace} (number|nil) Only enable diagnostics for the given
+ • {bufnr} (integer|nil) Buffer number, or 0 for current buffer.
+ When omitted, enable diagnostics in all buffers.
+ • {namespace} (integer|nil) Only enable diagnostics for the given
namespace.
fromqflist({list}) *vim.diagnostic.fromqflist()*
Convert a list of quickfix items to a list of diagnostics.
Parameters: ~
- • {list} (table) A list of quickfix items from |getqflist()| or
+ • {list} table[] List of quickfix items from |getqflist()| or
|getloclist()|.
Return: ~
@@ -457,9 +481,12 @@ fromqflist({list}) *vim.diagnostic.fromqflist()*
get({bufnr}, {opts}) *vim.diagnostic.get()*
Get current diagnostics.
+ Modifying diagnostics in the returned table has no effect. To set
+ diagnostics in a buffer, use |vim.diagnostic.set()|.
+
Parameters: ~
- • {bufnr} (number|nil) Buffer number to get diagnostics from. Use 0 for
- current buffer or nil for all buffers.
+ • {bufnr} (integer|nil) Buffer number to get diagnostics from. Use 0
+ for current buffer or nil for all buffers.
• {opts} (table|nil) A table with the following keys:
• namespace: (number) Limit diagnostics to the given
namespace.
@@ -467,13 +494,13 @@ get({bufnr}, {opts}) *vim.diagnostic.get()*
• severity: See |diagnostic-severity|.
Return: ~
- Diagnostic [] table A list of diagnostic items |diagnostic-structure|.
+ Diagnostic [] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr` , `end_lnum` , `end_col` , and `severity` are guaranteed to be present.
get_namespace({namespace}) *vim.diagnostic.get_namespace()*
Get namespace metadata.
Parameters: ~
- • {namespace} (number) Diagnostic namespace
+ • {namespace} (integer) Diagnostic namespace
Return: ~
(table) Namespace metadata
@@ -560,17 +587,17 @@ hide({namespace}, {bufnr}) *vim.diagnostic.hide()*
|vim.diagnostic.disable()|.
Parameters: ~
- • {namespace} (number|nil) Diagnostic namespace. When omitted, hide diagnostics from all
+ • {namespace} (integer|nil) Diagnostic namespace. When omitted, hide diagnostics from all
namespaces.
- • {bufnr} (number|nil) Buffer number, or 0 for current buffer. When
- omitted, hide diagnostics in all buffers.
+ • {bufnr} (integer|nil) Buffer number, or 0 for current buffer.
+ When omitted, hide diagnostics in all buffers.
is_disabled({bufnr}, {namespace}) *vim.diagnostic.is_disabled()*
Check whether diagnostics are disabled in a given buffer.
Parameters: ~
- • {bufnr} (number|nil) Buffer number, or 0 for current buffer.
- • {namespace} (number|nil) Diagnostic namespace. When omitted, checks if all diagnostics are
+ • {bufnr} (integer|nil) Buffer number, or 0 for current buffer.
+ • {namespace} (integer|nil) Diagnostic namespace. When omitted, checks if all diagnostics are
disabled in {bufnr}. Otherwise, only checks if
diagnostics from {namespace} are disabled.
@@ -582,16 +609,14 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults})
Parse a diagnostic from a string.
For example, consider a line of output from a linter: >
-
- WARNING filename:27:3: Variable 'foo' does not exist
+ WARNING filename:27:3: Variable 'foo' does not exist
<
This can be parsed into a diagnostic |diagnostic-structure| with: >lua
-
- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
- local groups = { "severity", "lnum", "col", "message" }
- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
+ local s = "WARNING filename:27:3: Variable 'foo' does not exist"
+ local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
+ local groups = { "severity", "lnum", "col", "message" }
+ vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
<
Parameters: ~
@@ -663,7 +688,7 @@ open_float({opts}, {...}) *vim.diagnostic.open_float()*
from |vim.diagnostic.config()|.
Return: ~
- number|nil, number|nil: ({float_bufnr}, {win_id})
+ integer|nil, integer|nil: ({float_bufnr}, {win_id})
reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
Remove all diagnostics from the given namespace.
@@ -674,17 +699,17 @@ reset({namespace}, {bufnr}) *vim.diagnostic.reset()*
re-displayed, use |vim.diagnostic.hide()|.
Parameters: ~
- • {namespace} (number|nil) Diagnostic namespace. When omitted, remove diagnostics from all
+ • {namespace} (integer|nil) Diagnostic namespace. When omitted, remove diagnostics from all
namespaces.
- • {bufnr} (number|nil) Remove diagnostics for the given buffer.
+ • {bufnr} (integer|nil) Remove diagnostics for the given buffer.
When omitted, diagnostics are removed for all buffers.
set({namespace}, {bufnr}, {diagnostics}, {opts}) *vim.diagnostic.set()*
Set diagnostics for the given namespace and buffer.
Parameters: ~
- • {namespace} (number) The diagnostic namespace
- • {bufnr} (number) Buffer number
+ • {namespace} (integer) The diagnostic namespace
+ • {bufnr} (integer) Buffer number
• {diagnostics} (table) A list of diagnostic items
|diagnostic-structure|
• {opts} (table|nil) Display options to pass to
@@ -723,9 +748,9 @@ show({namespace}, {bufnr}, {diagnostics}, {opts})
Display diagnostics for the given namespace and buffer.
Parameters: ~
- • {namespace} (number|nil) Diagnostic namespace. When omitted, show diagnostics from all
+ • {namespace} (integer|nil) Diagnostic namespace. When omitted, show diagnostics from all
namespaces.
- • {bufnr} (number|nil) Buffer number, or 0 for current buffer.
+ • {bufnr} (integer|nil) Buffer number, or 0 for current buffer.
When omitted, show diagnostics in all buffers.
• {diagnostics} (table|nil) The diagnostics to display. When omitted,
use the saved diagnostics for the given namespace and
diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt
index 382d025d3c..2f174a404e 100644
--- a/runtime/doc/diff.txt
+++ b/runtime/doc/diff.txt
@@ -125,7 +125,7 @@ file for a moment and come back to the same file and be in diff mode again.
buffers.
The `:diffoff` command resets the relevant options to the values they had when
-using `:diffsplit`, `:diffpatch` , `:diffthis`. or starting Vim in diff mode.
+using `:diffsplit`, `:diffpatch`, `:diffthis`. or starting Vim in diff mode.
When using `:diffoff` twice the last saved values are restored.
Otherwise they are set to their default value:
@@ -396,7 +396,9 @@ If the 'diffexpr' expression starts with s: or |<SID>|, then it is replaced
with the script ID (|local-function|). Example: >
set diffexpr=s:MyDiffExpr()
set diffexpr=<SID>SomeDiffExpr()
-<
+Otherwise, the expression is evaluated in the context of the script where the
+option was set, thus script-local items are available.
+
*E810* *E97*
Vim will do a test if the diff output looks alright. If it doesn't, you will
get an error message. Possible causes:
@@ -452,5 +454,8 @@ If the 'patchexpr' expression starts with s: or |<SID>|, then it is replaced
with the script ID (|local-function|). Example: >
set patchexpr=s:MyPatchExpr()
set patchexpr=<SID>SomePatchExpr()
-<
+Otherwise, the expression is evaluated in the context of the script where the
+option was set, thus script-local items are available.
+
+
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/digraph.txt b/runtime/doc/digraph.txt
index ce0a929bc1..1e91e5e4b8 100644
--- a/runtime/doc/digraph.txt
+++ b/runtime/doc/digraph.txt
@@ -168,1312 +168,1322 @@ ROUBLE
The rouble sign was added in 2014 as 0x20bd. Vim supports the digraphs =R and
=P for this. Note that R= and P= are other characters.
+QUADRUPLE PRIME
+
+The quadruple prime using the digraph 4' was added in 2023. Although it is
+not part of RFC 1345, it supplements the existing digraph implementation as
+there already exist digraphs for PRIME, DOUBLE PRIME and TRIPLE PRIME using
+the 1', 2' and 3' digraphs.
+
*digraph-table* *digraph-table-mbyte*
-char digraph hex dec official name ~
-^@ NU 0x00 0 NULL (NUL)
-^A SH 0x01 1 START OF HEADING (SOH)
-^B SX 0x02 2 START OF TEXT (STX)
-^C EX 0x03 3 END OF TEXT (ETX)
-^D ET 0x04 4 END OF TRANSMISSION (EOT)
-^E EQ 0x05 5 ENQUIRY (ENQ)
-^F AK 0x06 6 ACKNOWLEDGE (ACK)
-^G BL 0x07 7 BELL (BEL)
-^H BS 0x08 8 BACKSPACE (BS)
-^I HT 0x09 9 CHARACTER TABULATION (HT)
-^@ LF 0x0a 10 LINE FEED (LF)
-^K VT 0x0b 11 LINE TABULATION (VT)
-^L FF 0x0c 12 FORM FEED (FF)
-^M CR 0x0d 13 CARRIAGE RETURN (CR)
-^N SO 0x0e 14 SHIFT OUT (SO)
-^O SI 0x0f 15 SHIFT IN (SI)
-^P DL 0x10 16 DATALINK ESCAPE (DLE)
-^Q D1 0x11 17 DEVICE CONTROL ONE (DC1)
-^R D2 0x12 18 DEVICE CONTROL TWO (DC2)
-^S D3 0x13 19 DEVICE CONTROL THREE (DC3)
-^T D4 0x14 20 DEVICE CONTROL FOUR (DC4)
-^U NK 0x15 21 NEGATIVE ACKNOWLEDGE (NAK)
-^V SY 0x16 22 SYNCHRONOUS IDLE (SYN)
-^W EB 0x17 23 END OF TRANSMISSION BLOCK (ETB)
-^X CN 0x18 24 CANCEL (CAN)
-^Y EM 0x19 25 END OF MEDIUM (EM)
-^Z SB 0x1a 26 SUBSTITUTE (SUB)
-^[ EC 0x1b 27 ESCAPE (ESC)
-^\ FS 0x1c 28 FILE SEPARATOR (IS4)
-^] GS 0x1d 29 GROUP SEPARATOR (IS3)
-^^ RS 0x1e 30 RECORD SEPARATOR (IS2)
-^_ US 0x1f 31 UNIT SEPARATOR (IS1)
+>
+ char digraph hex dec official name
+ ^@ NU 0x00 0 NULL (NUL)
+ ^A SH 0x01 1 START OF HEADING (SOH)
+ ^B SX 0x02 2 START OF TEXT (STX)
+ ^C EX 0x03 3 END OF TEXT (ETX)
+ ^D ET 0x04 4 END OF TRANSMISSION (EOT)
+ ^E EQ 0x05 5 ENQUIRY (ENQ)
+ ^F AK 0x06 6 ACKNOWLEDGE (ACK)
+ ^G BL 0x07 7 BELL (BEL)
+ ^H BS 0x08 8 BACKSPACE (BS)
+ ^I HT 0x09 9 CHARACTER TABULATION (HT)
+ ^@ LF 0x0a 10 LINE FEED (LF)
+ ^K VT 0x0b 11 LINE TABULATION (VT)
+ ^L FF 0x0c 12 FORM FEED (FF)
+ ^M CR 0x0d 13 CARRIAGE RETURN (CR)
+ ^N SO 0x0e 14 SHIFT OUT (SO)
+ ^O SI 0x0f 15 SHIFT IN (SI)
+ ^P DL 0x10 16 DATALINK ESCAPE (DLE)
+ ^Q D1 0x11 17 DEVICE CONTROL ONE (DC1)
+ ^R D2 0x12 18 DEVICE CONTROL TWO (DC2)
+ ^S D3 0x13 19 DEVICE CONTROL THREE (DC3)
+ ^T D4 0x14 20 DEVICE CONTROL FOUR (DC4)
+ ^U NK 0x15 21 NEGATIVE ACKNOWLEDGE (NAK)
+ ^V SY 0x16 22 SYNCHRONOUS IDLE (SYN)
+ ^W EB 0x17 23 END OF TRANSMISSION BLOCK (ETB)
+ ^X CN 0x18 24 CANCEL (CAN)
+ ^Y EM 0x19 25 END OF MEDIUM (EM)
+ ^Z SB 0x1a 26 SUBSTITUTE (SUB)
+ ^[ EC 0x1b 27 ESCAPE (ESC)
+ ^\ FS 0x1c 28 FILE SEPARATOR (IS4)
+ ^] GS 0x1d 29 GROUP SEPARATOR (IS3)
+ ^^ RS 0x1e 30 RECORD SEPARATOR (IS2)
+ ^_ US 0x1f 31 UNIT SEPARATOR (IS1)
SP 0x20 32 SPACE
-# Nb 0x23 35 NUMBER SIGN
-$ DO 0x24 36 DOLLAR SIGN
-@ At 0x40 64 COMMERCIAL AT
-[ <( 0x5b 91 LEFT SQUARE BRACKET
-\ // 0x5c 92 REVERSE SOLIDUS
-] )> 0x5d 93 RIGHT SQUARE BRACKET
-^ '> 0x5e 94 CIRCUMFLEX ACCENT
-` '! 0x60 96 GRAVE ACCENT
-{ (! 0x7b 123 LEFT CURLY BRACKET
-| !! 0x7c 124 VERTICAL LINE
-} !) 0x7d 125 RIGHT CURLY BRACKET
-~ '? 0x7e 126 TILDE
-^? DT 0x7f 127 DELETE (DEL)
-~@ PA 0x80 128 PADDING CHARACTER (PAD)
-~A HO 0x81 129 HIGH OCTET PRESET (HOP)
-~B BH 0x82 130 BREAK PERMITTED HERE (BPH)
-~C NH 0x83 131 NO BREAK HERE (NBH)
-~D IN 0x84 132 INDEX (IND)
-~E NL 0x85 133 NEXT LINE (NEL)
-~F SA 0x86 134 START OF SELECTED AREA (SSA)
-~G ES 0x87 135 END OF SELECTED AREA (ESA)
-~H HS 0x88 136 CHARACTER TABULATION SET (HTS)
-~I HJ 0x89 137 CHARACTER TABULATION WITH JUSTIFICATION (HTJ)
-~J VS 0x8a 138 LINE TABULATION SET (VTS)
-~K PD 0x8b 139 PARTIAL LINE FORWARD (PLD)
-~L PU 0x8c 140 PARTIAL LINE BACKWARD (PLU)
-~M RI 0x8d 141 REVERSE LINE FEED (RI)
-~N S2 0x8e 142 SINGLE-SHIFT TWO (SS2)
-~O S3 0x8f 143 SINGLE-SHIFT THREE (SS3)
-~P DC 0x90 144 DEVICE CONTROL STRING (DCS)
-~Q P1 0x91 145 PRIVATE USE ONE (PU1)
-~R P2 0x92 146 PRIVATE USE TWO (PU2)
-~S TS 0x93 147 SET TRANSMIT STATE (STS)
-~T CC 0x94 148 CANCEL CHARACTER (CCH)
-~U MW 0x95 149 MESSAGE WAITING (MW)
-~V SG 0x96 150 START OF GUARDED AREA (SPA)
-~W EG 0x97 151 END OF GUARDED AREA (EPA)
-~X SS 0x98 152 START OF STRING (SOS)
-~Y GC 0x99 153 SINGLE GRAPHIC CHARACTER INTRODUCER (SGCI)
-~Z SC 0x9a 154 SINGLE CHARACTER INTRODUCER (SCI)
-~[ CI 0x9b 155 CONTROL SEQUENCE INTRODUCER (CSI)
-~\ ST 0x9c 156 STRING TERMINATOR (ST)
-~] OC 0x9d 157 OPERATING SYSTEM COMMAND (OSC)
-~^ PM 0x9e 158 PRIVACY MESSAGE (PM)
-~_ AC 0x9f 159 APPLICATION PROGRAM COMMAND (APC)
-| NS 0xa0 160 NO-BREAK SPACE
-¡ !I 0xa1 161 INVERTED EXCLAMATION MARK
-¢ Ct 0xa2 162 CENT SIGN
-£ Pd 0xa3 163 POUND SIGN
-¤ Cu 0xa4 164 CURRENCY SIGN
-¥ Ye 0xa5 165 YEN SIGN
-¦ BB 0xa6 166 BROKEN BAR
-§ SE 0xa7 167 SECTION SIGN
-¨ ': 0xa8 168 DIAERESIS
-© Co 0xa9 169 COPYRIGHT SIGN
-ª -a 0xaa 170 FEMININE ORDINAL INDICATOR
-« << 0xab 171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
-¬ NO 0xac 172 NOT SIGN
-­ -- 0xad 173 SOFT HYPHEN
-® Rg 0xae 174 REGISTERED SIGN
-¯ 'm 0xaf 175 MACRON
-° DG 0xb0 176 DEGREE SIGN
-± +- 0xb1 177 PLUS-MINUS SIGN
-² 2S 0xb2 178 SUPERSCRIPT TWO
-³ 3S 0xb3 179 SUPERSCRIPT THREE
-´ '' 0xb4 180 ACUTE ACCENT
-µ My 0xb5 181 MICRO SIGN
-¶ PI 0xb6 182 PILCROW SIGN
-· .M 0xb7 183 MIDDLE DOT
-¸ ', 0xb8 184 CEDILLA
-¹ 1S 0xb9 185 SUPERSCRIPT ONE
-º -o 0xba 186 MASCULINE ORDINAL INDICATOR
-» >> 0xbb 187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
-¼ 14 0xbc 188 VULGAR FRACTION ONE QUARTER
-½ 12 0xbd 189 VULGAR FRACTION ONE HALF
-¾ 34 0xbe 190 VULGAR FRACTION THREE QUARTERS
-¿ ?I 0xbf 191 INVERTED QUESTION MARK
-À A! 0xc0 192 LATIN CAPITAL LETTER A WITH GRAVE
-Á A' 0xc1 193 LATIN CAPITAL LETTER A WITH ACUTE
-Â A> 0xc2 194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
-Ã A? 0xc3 195 LATIN CAPITAL LETTER A WITH TILDE
-Ä A: 0xc4 196 LATIN CAPITAL LETTER A WITH DIAERESIS
-Å AA 0xc5 197 LATIN CAPITAL LETTER A WITH RING ABOVE
-Æ AE 0xc6 198 LATIN CAPITAL LETTER AE
-Ç C, 0xc7 199 LATIN CAPITAL LETTER C WITH CEDILLA
-È E! 0xc8 200 LATIN CAPITAL LETTER E WITH GRAVE
-É E' 0xc9 201 LATIN CAPITAL LETTER E WITH ACUTE
-Ê E> 0xca 202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
-Ë E: 0xcb 203 LATIN CAPITAL LETTER E WITH DIAERESIS
-Ì I! 0xcc 204 LATIN CAPITAL LETTER I WITH GRAVE
-Í I' 0xcd 205 LATIN CAPITAL LETTER I WITH ACUTE
-Î I> 0xce 206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
-Ï I: 0xcf 207 LATIN CAPITAL LETTER I WITH DIAERESIS
-Ð D- 0xd0 208 LATIN CAPITAL LETTER ETH (Icelandic)
-Ñ N? 0xd1 209 LATIN CAPITAL LETTER N WITH TILDE
-Ò O! 0xd2 210 LATIN CAPITAL LETTER O WITH GRAVE
-Ó O' 0xd3 211 LATIN CAPITAL LETTER O WITH ACUTE
-Ô O> 0xd4 212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
-Õ O? 0xd5 213 LATIN CAPITAL LETTER O WITH TILDE
-Ö O: 0xd6 214 LATIN CAPITAL LETTER O WITH DIAERESIS
-× *X 0xd7 215 MULTIPLICATION SIGN
-Ø O/ 0xd8 216 LATIN CAPITAL LETTER O WITH STROKE
-Ù U! 0xd9 217 LATIN CAPITAL LETTER U WITH GRAVE
-Ú U' 0xda 218 LATIN CAPITAL LETTER U WITH ACUTE
-Û U> 0xdb 219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
-Ü U: 0xdc 220 LATIN CAPITAL LETTER U WITH DIAERESIS
-Ý Y' 0xdd 221 LATIN CAPITAL LETTER Y WITH ACUTE
-Þ TH 0xde 222 LATIN CAPITAL LETTER THORN (Icelandic)
-ß ss 0xdf 223 LATIN SMALL LETTER SHARP S (German)
-à a! 0xe0 224 LATIN SMALL LETTER A WITH GRAVE
-á a' 0xe1 225 LATIN SMALL LETTER A WITH ACUTE
-â a> 0xe2 226 LATIN SMALL LETTER A WITH CIRCUMFLEX
-ã a? 0xe3 227 LATIN SMALL LETTER A WITH TILDE
-ä a: 0xe4 228 LATIN SMALL LETTER A WITH DIAERESIS
-å aa 0xe5 229 LATIN SMALL LETTER A WITH RING ABOVE
-æ ae 0xe6 230 LATIN SMALL LETTER AE
-ç c, 0xe7 231 LATIN SMALL LETTER C WITH CEDILLA
-è e! 0xe8 232 LATIN SMALL LETTER E WITH GRAVE
-é e' 0xe9 233 LATIN SMALL LETTER E WITH ACUTE
-ê e> 0xea 234 LATIN SMALL LETTER E WITH CIRCUMFLEX
-ë e: 0xeb 235 LATIN SMALL LETTER E WITH DIAERESIS
-ì i! 0xec 236 LATIN SMALL LETTER I WITH GRAVE
-í i' 0xed 237 LATIN SMALL LETTER I WITH ACUTE
-î i> 0xee 238 LATIN SMALL LETTER I WITH CIRCUMFLEX
-ï i: 0xef 239 LATIN SMALL LETTER I WITH DIAERESIS
-ð d- 0xf0 240 LATIN SMALL LETTER ETH (Icelandic)
-ñ n? 0xf1 241 LATIN SMALL LETTER N WITH TILDE
-ò o! 0xf2 242 LATIN SMALL LETTER O WITH GRAVE
-ó o' 0xf3 243 LATIN SMALL LETTER O WITH ACUTE
-ô o> 0xf4 244 LATIN SMALL LETTER O WITH CIRCUMFLEX
-õ o? 0xf5 245 LATIN SMALL LETTER O WITH TILDE
-ö o: 0xf6 246 LATIN SMALL LETTER O WITH DIAERESIS
-÷ -: 0xf7 247 DIVISION SIGN
-ø o/ 0xf8 248 LATIN SMALL LETTER O WITH STROKE
-ù u! 0xf9 249 LATIN SMALL LETTER U WITH GRAVE
-ú u' 0xfa 250 LATIN SMALL LETTER U WITH ACUTE
-û u> 0xfb 251 LATIN SMALL LETTER U WITH CIRCUMFLEX
-ü u: 0xfc 252 LATIN SMALL LETTER U WITH DIAERESIS
-ý y' 0xfd 253 LATIN SMALL LETTER Y WITH ACUTE
-þ th 0xfe 254 LATIN SMALL LETTER THORN (Icelandic)
-ÿ y: 0xff 255 LATIN SMALL LETTER Y WITH DIAERESIS
-Ā A- 0100 0256 LATIN CAPITAL LETTER A WITH MACRON
-ā a- 0101 0257 LATIN SMALL LETTER A WITH MACRON
-Ă A( 0102 0258 LATIN CAPITAL LETTER A WITH BREVE
-ă a( 0103 0259 LATIN SMALL LETTER A WITH BREVE
-Ą A; 0104 0260 LATIN CAPITAL LETTER A WITH OGONEK
-ą a; 0105 0261 LATIN SMALL LETTER A WITH OGONEK
-Ć C' 0106 0262 LATIN CAPITAL LETTER C WITH ACUTE
-ć c' 0107 0263 LATIN SMALL LETTER C WITH ACUTE
-Ĉ C> 0108 0264 LATIN CAPITAL LETTER C WITH CIRCUMFLEX
-ĉ c> 0109 0265 LATIN SMALL LETTER C WITH CIRCUMFLEX
-Ċ C. 010A 0266 LATIN CAPITAL LETTER C WITH DOT ABOVE
-ċ c. 010B 0267 LATIN SMALL LETTER C WITH DOT ABOVE
-Č C< 010C 0268 LATIN CAPITAL LETTER C WITH CARON
-č c< 010D 0269 LATIN SMALL LETTER C WITH CARON
-Ď D< 010E 0270 LATIN CAPITAL LETTER D WITH CARON
-ď d< 010F 0271 LATIN SMALL LETTER D WITH CARON
-Đ D/ 0110 0272 LATIN CAPITAL LETTER D WITH STROKE
-đ d/ 0111 0273 LATIN SMALL LETTER D WITH STROKE
-Ē E- 0112 0274 LATIN CAPITAL LETTER E WITH MACRON
-ē e- 0113 0275 LATIN SMALL LETTER E WITH MACRON
-Ĕ E( 0114 0276 LATIN CAPITAL LETTER E WITH BREVE
-ĕ e( 0115 0277 LATIN SMALL LETTER E WITH BREVE
-Ė E. 0116 0278 LATIN CAPITAL LETTER E WITH DOT ABOVE
-ė e. 0117 0279 LATIN SMALL LETTER E WITH DOT ABOVE
-Ę E; 0118 0280 LATIN CAPITAL LETTER E WITH OGONEK
-ę e; 0119 0281 LATIN SMALL LETTER E WITH OGONEK
-Ě E< 011A 0282 LATIN CAPITAL LETTER E WITH CARON
-ě e< 011B 0283 LATIN SMALL LETTER E WITH CARON
-Ĝ G> 011C 0284 LATIN CAPITAL LETTER G WITH CIRCUMFLEX
-ĝ g> 011D 0285 LATIN SMALL LETTER G WITH CIRCUMFLEX
-Ğ G( 011E 0286 LATIN CAPITAL LETTER G WITH BREVE
-ğ g( 011F 0287 LATIN SMALL LETTER G WITH BREVE
-Ġ G. 0120 0288 LATIN CAPITAL LETTER G WITH DOT ABOVE
-ġ g. 0121 0289 LATIN SMALL LETTER G WITH DOT ABOVE
-Ģ G, 0122 0290 LATIN CAPITAL LETTER G WITH CEDILLA
-ģ g, 0123 0291 LATIN SMALL LETTER G WITH CEDILLA
-Ĥ H> 0124 0292 LATIN CAPITAL LETTER H WITH CIRCUMFLEX
-ĥ h> 0125 0293 LATIN SMALL LETTER H WITH CIRCUMFLEX
-Ħ H/ 0126 0294 LATIN CAPITAL LETTER H WITH STROKE
-ħ h/ 0127 0295 LATIN SMALL LETTER H WITH STROKE
-Ĩ I? 0128 0296 LATIN CAPITAL LETTER I WITH TILDE
-ĩ i? 0129 0297 LATIN SMALL LETTER I WITH TILDE
-Ī I- 012A 0298 LATIN CAPITAL LETTER I WITH MACRON
-ī i- 012B 0299 LATIN SMALL LETTER I WITH MACRON
-Ĭ I( 012C 0300 LATIN CAPITAL LETTER I WITH BREVE
-ĭ i( 012D 0301 LATIN SMALL LETTER I WITH BREVE
-Į I; 012E 0302 LATIN CAPITAL LETTER I WITH OGONEK
-į i; 012F 0303 LATIN SMALL LETTER I WITH OGONEK
-İ I. 0130 0304 LATIN CAPITAL LETTER I WITH DOT ABOVE
-ı i. 0131 0305 LATIN SMALL LETTER DOTLESS I
-IJ IJ 0132 0306 LATIN CAPITAL LIGATURE IJ
-ij ij 0133 0307 LATIN SMALL LIGATURE IJ
-Ĵ J> 0134 0308 LATIN CAPITAL LETTER J WITH CIRCUMFLEX
-ĵ j> 0135 0309 LATIN SMALL LETTER J WITH CIRCUMFLEX
-Ķ K, 0136 0310 LATIN CAPITAL LETTER K WITH CEDILLA
-ķ k, 0137 0311 LATIN SMALL LETTER K WITH CEDILLA
-ĸ kk 0138 0312 LATIN SMALL LETTER KRA
-Ĺ L' 0139 0313 LATIN CAPITAL LETTER L WITH ACUTE
-ĺ l' 013A 0314 LATIN SMALL LETTER L WITH ACUTE
-Ļ L, 013B 0315 LATIN CAPITAL LETTER L WITH CEDILLA
-ļ l, 013C 0316 LATIN SMALL LETTER L WITH CEDILLA
-Ľ L< 013D 0317 LATIN CAPITAL LETTER L WITH CARON
-ľ l< 013E 0318 LATIN SMALL LETTER L WITH CARON
-Ŀ L. 013F 0319 LATIN CAPITAL LETTER L WITH MIDDLE DOT
-ŀ l. 0140 0320 LATIN SMALL LETTER L WITH MIDDLE DOT
-Ł L/ 0141 0321 LATIN CAPITAL LETTER L WITH STROKE
-ł l/ 0142 0322 LATIN SMALL LETTER L WITH STROKE
-Ń N' 0143 0323 LATIN CAPITAL LETTER N WITH ACUTE `
-ń n' 0144 0324 LATIN SMALL LETTER N WITH ACUTE `
-Ņ N, 0145 0325 LATIN CAPITAL LETTER N WITH CEDILLA `
-ņ n, 0146 0326 LATIN SMALL LETTER N WITH CEDILLA `
-Ň N< 0147 0327 LATIN CAPITAL LETTER N WITH CARON `
-ň n< 0148 0328 LATIN SMALL LETTER N WITH CARON `
-ʼn 'n 0149 0329 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE `
-Ŋ NG 014A 0330 LATIN CAPITAL LETTER ENG
-ŋ ng 014B 0331 LATIN SMALL LETTER ENG
-Ō O- 014C 0332 LATIN CAPITAL LETTER O WITH MACRON
-ō o- 014D 0333 LATIN SMALL LETTER O WITH MACRON
-Ŏ O( 014E 0334 LATIN CAPITAL LETTER O WITH BREVE
-ŏ o( 014F 0335 LATIN SMALL LETTER O WITH BREVE
-Ő O" 0150 0336 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
-ő o" 0151 0337 LATIN SMALL LETTER O WITH DOUBLE ACUTE
-Œ OE 0152 0338 LATIN CAPITAL LIGATURE OE
-œ oe 0153 0339 LATIN SMALL LIGATURE OE
-Ŕ R' 0154 0340 LATIN CAPITAL LETTER R WITH ACUTE
-ŕ r' 0155 0341 LATIN SMALL LETTER R WITH ACUTE
-Ŗ R, 0156 0342 LATIN CAPITAL LETTER R WITH CEDILLA
-ŗ r, 0157 0343 LATIN SMALL LETTER R WITH CEDILLA
-Ř R< 0158 0344 LATIN CAPITAL LETTER R WITH CARON
-ř r< 0159 0345 LATIN SMALL LETTER R WITH CARON
-Ś S' 015A 0346 LATIN CAPITAL LETTER S WITH ACUTE
-ś s' 015B 0347 LATIN SMALL LETTER S WITH ACUTE
-Ŝ S> 015C 0348 LATIN CAPITAL LETTER S WITH CIRCUMFLEX
-ŝ s> 015D 0349 LATIN SMALL LETTER S WITH CIRCUMFLEX
-Ş S, 015E 0350 LATIN CAPITAL LETTER S WITH CEDILLA
-ş s, 015F 0351 LATIN SMALL LETTER S WITH CEDILLA
-Š S< 0160 0352 LATIN CAPITAL LETTER S WITH CARON
-š s< 0161 0353 LATIN SMALL LETTER S WITH CARON
-Ţ T, 0162 0354 LATIN CAPITAL LETTER T WITH CEDILLA
-ţ t, 0163 0355 LATIN SMALL LETTER T WITH CEDILLA
-Ť T< 0164 0356 LATIN CAPITAL LETTER T WITH CARON
-ť t< 0165 0357 LATIN SMALL LETTER T WITH CARON
-Ŧ T/ 0166 0358 LATIN CAPITAL LETTER T WITH STROKE
-ŧ t/ 0167 0359 LATIN SMALL LETTER T WITH STROKE
-Ũ U? 0168 0360 LATIN CAPITAL LETTER U WITH TILDE
-ũ u? 0169 0361 LATIN SMALL LETTER U WITH TILDE
-Ū U- 016A 0362 LATIN CAPITAL LETTER U WITH MACRON
-ū u- 016B 0363 LATIN SMALL LETTER U WITH MACRON
-Ŭ U( 016C 0364 LATIN CAPITAL LETTER U WITH BREVE
-ŭ u( 016D 0365 LATIN SMALL LETTER U WITH BREVE
-Ů U0 016E 0366 LATIN CAPITAL LETTER U WITH RING ABOVE
-ů u0 016F 0367 LATIN SMALL LETTER U WITH RING ABOVE
-Ű U" 0170 0368 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
-ű u" 0171 0369 LATIN SMALL LETTER U WITH DOUBLE ACUTE
-Ų U; 0172 0370 LATIN CAPITAL LETTER U WITH OGONEK
-ų u; 0173 0371 LATIN SMALL LETTER U WITH OGONEK
-Ŵ W> 0174 0372 LATIN CAPITAL LETTER W WITH CIRCUMFLEX
-ŵ w> 0175 0373 LATIN SMALL LETTER W WITH CIRCUMFLEX
-Ŷ Y> 0176 0374 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
-ŷ y> 0177 0375 LATIN SMALL LETTER Y WITH CIRCUMFLEX
-Ÿ Y: 0178 0376 LATIN CAPITAL LETTER Y WITH DIAERESIS
-Ź Z' 0179 0377 LATIN CAPITAL LETTER Z WITH ACUTE
-ź z' 017A 0378 LATIN SMALL LETTER Z WITH ACUTE
-Ż Z. 017B 0379 LATIN CAPITAL LETTER Z WITH DOT ABOVE
-ż z. 017C 0380 LATIN SMALL LETTER Z WITH DOT ABOVE
-Ž Z< 017D 0381 LATIN CAPITAL LETTER Z WITH CARON
-ž z< 017E 0382 LATIN SMALL LETTER Z WITH CARON
-Ơ O9 01A0 0416 LATIN CAPITAL LETTER O WITH HORN
-ơ o9 01A1 0417 LATIN SMALL LETTER O WITH HORN
-Ƣ OI 01A2 0418 LATIN CAPITAL LETTER OI
-ƣ oi 01A3 0419 LATIN SMALL LETTER OI
-Ʀ yr 01A6 0422 LATIN LETTER YR
-Ư U9 01AF 0431 LATIN CAPITAL LETTER U WITH HORN
-ư u9 01B0 0432 LATIN SMALL LETTER U WITH HORN
-Ƶ Z/ 01B5 0437 LATIN CAPITAL LETTER Z WITH STROKE
-ƶ z/ 01B6 0438 LATIN SMALL LETTER Z WITH STROKE
-Ʒ ED 01B7 0439 LATIN CAPITAL LETTER EZH
-Ǎ A< 01CD 0461 LATIN CAPITAL LETTER A WITH CARON
-ǎ a< 01CE 0462 LATIN SMALL LETTER A WITH CARON
-Ǐ I< 01CF 0463 LATIN CAPITAL LETTER I WITH CARON
-ǐ i< 01D0 0464 LATIN SMALL LETTER I WITH CARON
-Ǒ O< 01D1 0465 LATIN CAPITAL LETTER O WITH CARON
-ǒ o< 01D2 0466 LATIN SMALL LETTER O WITH CARON
-Ǔ U< 01D3 0467 LATIN CAPITAL LETTER U WITH CARON
-ǔ u< 01D4 0468 LATIN SMALL LETTER U WITH CARON
-Ǟ A1 01DE 0478 LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
-ǟ a1 01DF 0479 LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
-Ǡ A7 01E0 0480 LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
-ǡ a7 01E1 0481 LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
-Ǣ A3 01E2 0482 LATIN CAPITAL LETTER AE WITH MACRON
-ǣ a3 01E3 0483 LATIN SMALL LETTER AE WITH MACRON
-Ǥ G/ 01E4 0484 LATIN CAPITAL LETTER G WITH STROKE
-ǥ g/ 01E5 0485 LATIN SMALL LETTER G WITH STROKE
-Ǧ G< 01E6 0486 LATIN CAPITAL LETTER G WITH CARON
-ǧ g< 01E7 0487 LATIN SMALL LETTER G WITH CARON
-Ǩ K< 01E8 0488 LATIN CAPITAL LETTER K WITH CARON
-ǩ k< 01E9 0489 LATIN SMALL LETTER K WITH CARON
-Ǫ O; 01EA 0490 LATIN CAPITAL LETTER O WITH OGONEK
-ǫ o; 01EB 0491 LATIN SMALL LETTER O WITH OGONEK
-Ǭ O1 01EC 0492 LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
-ǭ o1 01ED 0493 LATIN SMALL LETTER O WITH OGONEK AND MACRON
-Ǯ EZ 01EE 0494 LATIN CAPITAL LETTER EZH WITH CARON
-ǯ ez 01EF 0495 LATIN SMALL LETTER EZH WITH CARON
-ǰ j< 01F0 0496 LATIN SMALL LETTER J WITH CARON
-Ǵ G' 01F4 0500 LATIN CAPITAL LETTER G WITH ACUTE
-ǵ g' 01F5 0501 LATIN SMALL LETTER G WITH ACUTE
-ʿ ;S 02BF 0703 MODIFIER LETTER LEFT HALF RING
-ˇ '< 02C7 0711 CARON
-˘ '( 02D8 0728 BREVE
-˙ '. 02D9 0729 DOT ABOVE
-˚ '0 02DA 0730 RING ABOVE
-˛ '; 02DB 0731 OGONEK
-˝ '" 02DD 0733 DOUBLE ACUTE ACCENT
-Ά A% 0386 0902 GREEK CAPITAL LETTER ALPHA WITH TONOS
-Έ E% 0388 0904 GREEK CAPITAL LETTER EPSILON WITH TONOS
-Ή Y% 0389 0905 GREEK CAPITAL LETTER ETA WITH TONOS
-Ί I% 038A 0906 GREEK CAPITAL LETTER IOTA WITH TONOS
-Ό O% 038C 0908 GREEK CAPITAL LETTER OMICRON WITH TONOS
-Ύ U% 038E 0910 GREEK CAPITAL LETTER UPSILON WITH TONOS
-Ώ W% 038F 0911 GREEK CAPITAL LETTER OMEGA WITH TONOS
-ΐ i3 0390 0912 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
-Α A* 0391 0913 GREEK CAPITAL LETTER ALPHA
-Β B* 0392 0914 GREEK CAPITAL LETTER BETA
-Γ G* 0393 0915 GREEK CAPITAL LETTER GAMMA
-Δ D* 0394 0916 GREEK CAPITAL LETTER DELTA
-Ε E* 0395 0917 GREEK CAPITAL LETTER EPSILON
-Ζ Z* 0396 0918 GREEK CAPITAL LETTER ZETA
-Η Y* 0397 0919 GREEK CAPITAL LETTER ETA
-Θ H* 0398 0920 GREEK CAPITAL LETTER THETA
-Ι I* 0399 0921 GREEK CAPITAL LETTER IOTA
-Κ K* 039A 0922 GREEK CAPITAL LETTER KAPPA
-Λ L* 039B 0923 GREEK CAPITAL LETTER LAMDA
-Μ M* 039C 0924 GREEK CAPITAL LETTER MU
-Ν N* 039D 0925 GREEK CAPITAL LETTER NU
-Ξ C* 039E 0926 GREEK CAPITAL LETTER XI
-Ο O* 039F 0927 GREEK CAPITAL LETTER OMICRON
-Π P* 03A0 0928 GREEK CAPITAL LETTER PI
-Ρ R* 03A1 0929 GREEK CAPITAL LETTER RHO
-Σ S* 03A3 0931 GREEK CAPITAL LETTER SIGMA
-Τ T* 03A4 0932 GREEK CAPITAL LETTER TAU
-Υ U* 03A5 0933 GREEK CAPITAL LETTER UPSILON
-Φ F* 03A6 0934 GREEK CAPITAL LETTER PHI
-Χ X* 03A7 0935 GREEK CAPITAL LETTER CHI
-Ψ Q* 03A8 0936 GREEK CAPITAL LETTER PSI
-Ω W* 03A9 0937 GREEK CAPITAL LETTER OMEGA
-Ϊ J* 03AA 0938 GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
-Ϋ V* 03AB 0939 GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
-ά a% 03AC 0940 GREEK SMALL LETTER ALPHA WITH TONOS
-έ e% 03AD 0941 GREEK SMALL LETTER EPSILON WITH TONOS
-ή y% 03AE 0942 GREEK SMALL LETTER ETA WITH TONOS
-ί i% 03AF 0943 GREEK SMALL LETTER IOTA WITH TONOS
-ΰ u3 03B0 0944 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
-α a* 03B1 0945 GREEK SMALL LETTER ALPHA
-β b* 03B2 0946 GREEK SMALL LETTER BETA
-γ g* 03B3 0947 GREEK SMALL LETTER GAMMA
-δ d* 03B4 0948 GREEK SMALL LETTER DELTA
-ε e* 03B5 0949 GREEK SMALL LETTER EPSILON
-ζ z* 03B6 0950 GREEK SMALL LETTER ZETA
-η y* 03B7 0951 GREEK SMALL LETTER ETA
-θ h* 03B8 0952 GREEK SMALL LETTER THETA
-ι i* 03B9 0953 GREEK SMALL LETTER IOTA
-κ k* 03BA 0954 GREEK SMALL LETTER KAPPA
-λ l* 03BB 0955 GREEK SMALL LETTER LAMDA
-μ m* 03BC 0956 GREEK SMALL LETTER MU
-ν n* 03BD 0957 GREEK SMALL LETTER NU
-ξ c* 03BE 0958 GREEK SMALL LETTER XI
-ο o* 03BF 0959 GREEK SMALL LETTER OMICRON
-π p* 03C0 0960 GREEK SMALL LETTER PI
-ρ r* 03C1 0961 GREEK SMALL LETTER RHO
-ς *s 03C2 0962 GREEK SMALL LETTER FINAL SIGMA
-σ s* 03C3 0963 GREEK SMALL LETTER SIGMA
-τ t* 03C4 0964 GREEK SMALL LETTER TAU
-υ u* 03C5 0965 GREEK SMALL LETTER UPSILON
-φ f* 03C6 0966 GREEK SMALL LETTER PHI
-χ x* 03C7 0967 GREEK SMALL LETTER CHI
-ψ q* 03C8 0968 GREEK SMALL LETTER PSI
-ω w* 03C9 0969 GREEK SMALL LETTER OMEGA
-ϊ j* 03CA 0970 GREEK SMALL LETTER IOTA WITH DIALYTIKA
-ϋ v* 03CB 0971 GREEK SMALL LETTER UPSILON WITH DIALYTIKA
-ό o% 03CC 0972 GREEK SMALL LETTER OMICRON WITH TONOS
-ύ u% 03CD 0973 GREEK SMALL LETTER UPSILON WITH TONOS
-ώ w% 03CE 0974 GREEK SMALL LETTER OMEGA WITH TONOS
-Ϙ 'G 03D8 0984 GREEK LETTER ARCHAIC KOPPA
-ϙ ,G 03D9 0985 GREEK SMALL LETTER ARCHAIC KOPPA
-Ϛ T3 03DA 0986 GREEK LETTER STIGMA
-ϛ t3 03DB 0987 GREEK SMALL LETTER STIGMA
-Ϝ M3 03DC 0988 GREEK LETTER DIGAMMA
-ϝ m3 03DD 0989 GREEK SMALL LETTER DIGAMMA
-Ϟ K3 03DE 0990 GREEK LETTER KOPPA
-ϟ k3 03DF 0991 GREEK SMALL LETTER KOPPA
-Ϡ P3 03E0 0992 GREEK LETTER SAMPI
-ϡ p3 03E1 0993 GREEK SMALL LETTER SAMPI
-ϴ '% 03F4 1012 GREEK CAPITAL THETA SYMBOL
-ϵ j3 03F5 1013 GREEK LUNATE EPSILON SYMBOL
-Ё IO 0401 1025 CYRILLIC CAPITAL LETTER IO
-Ђ D% 0402 1026 CYRILLIC CAPITAL LETTER DJE
-Ѓ G% 0403 1027 CYRILLIC CAPITAL LETTER GJE
-Є IE 0404 1028 CYRILLIC CAPITAL LETTER UKRAINIAN IE
-Ѕ DS 0405 1029 CYRILLIC CAPITAL LETTER DZE
-І II 0406 1030 CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
-Ї YI 0407 1031 CYRILLIC CAPITAL LETTER YI
-Ј J% 0408 1032 CYRILLIC CAPITAL LETTER JE
-Љ LJ 0409 1033 CYRILLIC CAPITAL LETTER LJE
-Њ NJ 040A 1034 CYRILLIC CAPITAL LETTER NJE
-Ћ Ts 040B 1035 CYRILLIC CAPITAL LETTER TSHE
-Ќ KJ 040C 1036 CYRILLIC CAPITAL LETTER KJE
-Ў V% 040E 1038 CYRILLIC CAPITAL LETTER SHORT U
-Џ DZ 040F 1039 CYRILLIC CAPITAL LETTER DZHE
-А A= 0410 1040 CYRILLIC CAPITAL LETTER A
-Б B= 0411 1041 CYRILLIC CAPITAL LETTER BE
-В V= 0412 1042 CYRILLIC CAPITAL LETTER VE
-Г G= 0413 1043 CYRILLIC CAPITAL LETTER GHE
-Д D= 0414 1044 CYRILLIC CAPITAL LETTER DE
-Е E= 0415 1045 CYRILLIC CAPITAL LETTER IE
-Ж Z% 0416 1046 CYRILLIC CAPITAL LETTER ZHE
-З Z= 0417 1047 CYRILLIC CAPITAL LETTER ZE
-И I= 0418 1048 CYRILLIC CAPITAL LETTER I
-Й J= 0419 1049 CYRILLIC CAPITAL LETTER SHORT I
-К K= 041A 1050 CYRILLIC CAPITAL LETTER KA
-Л L= 041B 1051 CYRILLIC CAPITAL LETTER EL
-М M= 041C 1052 CYRILLIC CAPITAL LETTER EM
-Н N= 041D 1053 CYRILLIC CAPITAL LETTER EN
-О O= 041E 1054 CYRILLIC CAPITAL LETTER O
-П P= 041F 1055 CYRILLIC CAPITAL LETTER PE
-Р R= 0420 1056 CYRILLIC CAPITAL LETTER ER
-С S= 0421 1057 CYRILLIC CAPITAL LETTER ES
-Т T= 0422 1058 CYRILLIC CAPITAL LETTER TE
-У U= 0423 1059 CYRILLIC CAPITAL LETTER U
-Ф F= 0424 1060 CYRILLIC CAPITAL LETTER EF
-Х H= 0425 1061 CYRILLIC CAPITAL LETTER HA
-Ц C= 0426 1062 CYRILLIC CAPITAL LETTER TSE
-Ч C% 0427 1063 CYRILLIC CAPITAL LETTER CHE
-Ш S% 0428 1064 CYRILLIC CAPITAL LETTER SHA
-Щ Sc 0429 1065 CYRILLIC CAPITAL LETTER SHCHA
-Ъ =" 042A 1066 CYRILLIC CAPITAL LETTER HARD SIGN
-Ы Y= 042B 1067 CYRILLIC CAPITAL LETTER YERU
-Ь %" 042C 1068 CYRILLIC CAPITAL LETTER SOFT SIGN
-Э JE 042D 1069 CYRILLIC CAPITAL LETTER E
-Ю JU 042E 1070 CYRILLIC CAPITAL LETTER YU
-Я JA 042F 1071 CYRILLIC CAPITAL LETTER YA
-а a= 0430 1072 CYRILLIC SMALL LETTER A
-б b= 0431 1073 CYRILLIC SMALL LETTER BE
-в v= 0432 1074 CYRILLIC SMALL LETTER VE
-г g= 0433 1075 CYRILLIC SMALL LETTER GHE
-д d= 0434 1076 CYRILLIC SMALL LETTER DE
-е e= 0435 1077 CYRILLIC SMALL LETTER IE
-ж z% 0436 1078 CYRILLIC SMALL LETTER ZHE
-з z= 0437 1079 CYRILLIC SMALL LETTER ZE
-и i= 0438 1080 CYRILLIC SMALL LETTER I
-й j= 0439 1081 CYRILLIC SMALL LETTER SHORT I
-к k= 043A 1082 CYRILLIC SMALL LETTER KA
-л l= 043B 1083 CYRILLIC SMALL LETTER EL
-м m= 043C 1084 CYRILLIC SMALL LETTER EM
-н n= 043D 1085 CYRILLIC SMALL LETTER EN
-о o= 043E 1086 CYRILLIC SMALL LETTER O
-п p= 043F 1087 CYRILLIC SMALL LETTER PE
-р r= 0440 1088 CYRILLIC SMALL LETTER ER
-с s= 0441 1089 CYRILLIC SMALL LETTER ES
-т t= 0442 1090 CYRILLIC SMALL LETTER TE
-у u= 0443 1091 CYRILLIC SMALL LETTER U
-ф f= 0444 1092 CYRILLIC SMALL LETTER EF
-х h= 0445 1093 CYRILLIC SMALL LETTER HA
-ц c= 0446 1094 CYRILLIC SMALL LETTER TSE
-ч c% 0447 1095 CYRILLIC SMALL LETTER CHE
-ш s% 0448 1096 CYRILLIC SMALL LETTER SHA
-щ sc 0449 1097 CYRILLIC SMALL LETTER SHCHA
-ъ =' 044A 1098 CYRILLIC SMALL LETTER HARD SIGN
-ы y= 044B 1099 CYRILLIC SMALL LETTER YERU
-ь %' 044C 1100 CYRILLIC SMALL LETTER SOFT SIGN
-э je 044D 1101 CYRILLIC SMALL LETTER E
-ю ju 044E 1102 CYRILLIC SMALL LETTER YU
-я ja 044F 1103 CYRILLIC SMALL LETTER YA
-ё io 0451 1105 CYRILLIC SMALL LETTER IO
-ђ d% 0452 1106 CYRILLIC SMALL LETTER DJE
-ѓ g% 0453 1107 CYRILLIC SMALL LETTER GJE
-є ie 0454 1108 CYRILLIC SMALL LETTER UKRAINIAN IE
-ѕ ds 0455 1109 CYRILLIC SMALL LETTER DZE
-і ii 0456 1110 CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
-ї yi 0457 1111 CYRILLIC SMALL LETTER YI
-ј j% 0458 1112 CYRILLIC SMALL LETTER JE
-љ lj 0459 1113 CYRILLIC SMALL LETTER LJE
-њ nj 045A 1114 CYRILLIC SMALL LETTER NJE
-ћ ts 045B 1115 CYRILLIC SMALL LETTER TSHE
-ќ kj 045C 1116 CYRILLIC SMALL LETTER KJE
-ў v% 045E 1118 CYRILLIC SMALL LETTER SHORT U
-џ dz 045F 1119 CYRILLIC SMALL LETTER DZHE
-Ѣ Y3 0462 1122 CYRILLIC CAPITAL LETTER YAT
-ѣ y3 0463 1123 CYRILLIC SMALL LETTER YAT
-Ѫ O3 046A 1130 CYRILLIC CAPITAL LETTER BIG YUS
-ѫ o3 046B 1131 CYRILLIC SMALL LETTER BIG YUS
-Ѳ F3 0472 1138 CYRILLIC CAPITAL LETTER FITA
-ѳ f3 0473 1139 CYRILLIC SMALL LETTER FITA
-Ѵ V3 0474 1140 CYRILLIC CAPITAL LETTER IZHITSA
-ѵ v3 0475 1141 CYRILLIC SMALL LETTER IZHITSA
-Ҁ C3 0480 1152 CYRILLIC CAPITAL LETTER KOPPA
-ҁ c3 0481 1153 CYRILLIC SMALL LETTER KOPPA
-Ґ G3 0490 1168 CYRILLIC CAPITAL LETTER GHE WITH UPTURN
-ґ g3 0491 1169 CYRILLIC SMALL LETTER GHE WITH UPTURN
-א A+ 05D0 1488 HEBREW LETTER ALEF
-ב B+ 05D1 1489 HEBREW LETTER BET
-ג G+ 05D2 1490 HEBREW LETTER GIMEL
-ד D+ 05D3 1491 HEBREW LETTER DALET
-ה H+ 05D4 1492 HEBREW LETTER HE
-ו W+ 05D5 1493 HEBREW LETTER VAV
-ז Z+ 05D6 1494 HEBREW LETTER ZAYIN
-ח X+ 05D7 1495 HEBREW LETTER HET
-ט Tj 05D8 1496 HEBREW LETTER TET
-י J+ 05D9 1497 HEBREW LETTER YOD
-ך K% 05DA 1498 HEBREW LETTER FINAL KAF
-כ K+ 05DB 1499 HEBREW LETTER KAF
-ל L+ 05DC 1500 HEBREW LETTER LAMED
-ם M% 05DD 1501 HEBREW LETTER FINAL MEM
-מ M+ 05DE 1502 HEBREW LETTER MEM
-ן N% 05DF 1503 HEBREW LETTER FINAL NUN `
-נ N+ 05E0 1504 HEBREW LETTER NUN `
-ס S+ 05E1 1505 HEBREW LETTER SAMEKH
-ע E+ 05E2 1506 HEBREW LETTER AYIN
-ף P% 05E3 1507 HEBREW LETTER FINAL PE
-פ P+ 05E4 1508 HEBREW LETTER PE
-ץ Zj 05E5 1509 HEBREW LETTER FINAL TSADI
-צ ZJ 05E6 1510 HEBREW LETTER TSADI
-ק Q+ 05E7 1511 HEBREW LETTER QOF
-ר R+ 05E8 1512 HEBREW LETTER RESH
-ש Sh 05E9 1513 HEBREW LETTER SHIN
-ת T+ 05EA 1514 HEBREW LETTER TAV
-، ,+ 060C 1548 ARABIC COMMA
-؛ ;+ 061B 1563 ARABIC SEMICOLON
-؟ ?+ 061F 1567 ARABIC QUESTION MARK
-ء H' 0621 1569 ARABIC LETTER HAMZA
-آ aM 0622 1570 ARABIC LETTER ALEF WITH MADDA ABOVE
-أ aH 0623 1571 ARABIC LETTER ALEF WITH HAMZA ABOVE
-ؤ wH 0624 1572 ARABIC LETTER WAW WITH HAMZA ABOVE
-إ ah 0625 1573 ARABIC LETTER ALEF WITH HAMZA BELOW
-ئ yH 0626 1574 ARABIC LETTER YEH WITH HAMZA ABOVE
-ا a+ 0627 1575 ARABIC LETTER ALEF
-ب b+ 0628 1576 ARABIC LETTER BEH
-ة tm 0629 1577 ARABIC LETTER TEH MARBUTA
-ت t+ 062A 1578 ARABIC LETTER TEH
-ث tk 062B 1579 ARABIC LETTER THEH
-ج g+ 062C 1580 ARABIC LETTER JEEM
-ح hk 062D 1581 ARABIC LETTER HAH
-خ x+ 062E 1582 ARABIC LETTER KHAH
-د d+ 062F 1583 ARABIC LETTER DAL
-ذ dk 0630 1584 ARABIC LETTER THAL
-ر r+ 0631 1585 ARABIC LETTER REH
-ز z+ 0632 1586 ARABIC LETTER ZAIN
-س s+ 0633 1587 ARABIC LETTER SEEN
-ش sn 0634 1588 ARABIC LETTER SHEEN
-ص c+ 0635 1589 ARABIC LETTER SAD
-ض dd 0636 1590 ARABIC LETTER DAD
-ط tj 0637 1591 ARABIC LETTER TAH
-ظ zH 0638 1592 ARABIC LETTER ZAH
-ع e+ 0639 1593 ARABIC LETTER AIN
-غ i+ 063A 1594 ARABIC LETTER GHAIN
-ـ ++ 0640 1600 ARABIC TATWEEL
-ف f+ 0641 1601 ARABIC LETTER FEH
-ق q+ 0642 1602 ARABIC LETTER QAF
-ك k+ 0643 1603 ARABIC LETTER KAF
-ل l+ 0644 1604 ARABIC LETTER LAM
-م m+ 0645 1605 ARABIC LETTER MEEM
-ن n+ 0646 1606 ARABIC LETTER NOON
-ه h+ 0647 1607 ARABIC LETTER HEH
-و w+ 0648 1608 ARABIC LETTER WAW
-ى j+ 0649 1609 ARABIC LETTER ALEF MAKSURA
-ي y+ 064A 1610 ARABIC LETTER YEH
-ً :+ 064B 1611 ARABIC FATHATAN
-ٌ "+ 064C 1612 ARABIC DAMMATAN
-ٍ =+ 064D 1613 ARABIC KASRATAN
-َ /+ 064E 1614 ARABIC FATHA
-ُ '+ 064F 1615 ARABIC DAMMA
-ِ 1+ 0650 1616 ARABIC KASRA
-ّ 3+ 0651 1617 ARABIC SHADDA
-ْ 0+ 0652 1618 ARABIC SUKUN
-ٰ aS 0670 1648 ARABIC LETTER SUPERSCRIPT ALEF
-پ p+ 067E 1662 ARABIC LETTER PEH
-ڤ v+ 06A4 1700 ARABIC LETTER VEH
-گ gf 06AF 1711 ARABIC LETTER GAF
-۰ 0a 06F0 1776 EXTENDED ARABIC-INDIC DIGIT ZERO
-۱ 1a 06F1 1777 EXTENDED ARABIC-INDIC DIGIT ONE
-۲ 2a 06F2 1778 EXTENDED ARABIC-INDIC DIGIT TWO
-۳ 3a 06F3 1779 EXTENDED ARABIC-INDIC DIGIT THREE
-۴ 4a 06F4 1780 EXTENDED ARABIC-INDIC DIGIT FOUR
-۵ 5a 06F5 1781 EXTENDED ARABIC-INDIC DIGIT FIVE
-۶ 6a 06F6 1782 EXTENDED ARABIC-INDIC DIGIT SIX
-۷ 7a 06F7 1783 EXTENDED ARABIC-INDIC DIGIT SEVEN
-۸ 8a 06F8 1784 EXTENDED ARABIC-INDIC DIGIT EIGHT
-۹ 9a 06F9 1785 EXTENDED ARABIC-INDIC DIGIT NINE
-Ḃ B. 1E02 7682 LATIN CAPITAL LETTER B WITH DOT ABOVE
-ḃ b. 1E03 7683 LATIN SMALL LETTER B WITH DOT ABOVE
-Ḇ B_ 1E06 7686 LATIN CAPITAL LETTER B WITH LINE BELOW
-ḇ b_ 1E07 7687 LATIN SMALL LETTER B WITH LINE BELOW
-Ḋ D. 1E0A 7690 LATIN CAPITAL LETTER D WITH DOT ABOVE
-ḋ d. 1E0B 7691 LATIN SMALL LETTER D WITH DOT ABOVE
-Ḏ D_ 1E0E 7694 LATIN CAPITAL LETTER D WITH LINE BELOW
-ḏ d_ 1E0F 7695 LATIN SMALL LETTER D WITH LINE BELOW
-Ḑ D, 1E10 7696 LATIN CAPITAL LETTER D WITH CEDILLA
-ḑ d, 1E11 7697 LATIN SMALL LETTER D WITH CEDILLA
-Ḟ F. 1E1E 7710 LATIN CAPITAL LETTER F WITH DOT ABOVE
-ḟ f. 1E1F 7711 LATIN SMALL LETTER F WITH DOT ABOVE
-Ḡ G- 1E20 7712 LATIN CAPITAL LETTER G WITH MACRON
-ḡ g- 1E21 7713 LATIN SMALL LETTER G WITH MACRON
-Ḣ H. 1E22 7714 LATIN CAPITAL LETTER H WITH DOT ABOVE
-ḣ h. 1E23 7715 LATIN SMALL LETTER H WITH DOT ABOVE
-Ḧ H: 1E26 7718 LATIN CAPITAL LETTER H WITH DIAERESIS
-ḧ h: 1E27 7719 LATIN SMALL LETTER H WITH DIAERESIS
-Ḩ H, 1E28 7720 LATIN CAPITAL LETTER H WITH CEDILLA
-ḩ h, 1E29 7721 LATIN SMALL LETTER H WITH CEDILLA
-Ḱ K' 1E30 7728 LATIN CAPITAL LETTER K WITH ACUTE
-ḱ k' 1E31 7729 LATIN SMALL LETTER K WITH ACUTE
-Ḵ K_ 1E34 7732 LATIN CAPITAL LETTER K WITH LINE BELOW
-ḵ k_ 1E35 7733 LATIN SMALL LETTER K WITH LINE BELOW
-Ḻ L_ 1E3A 7738 LATIN CAPITAL LETTER L WITH LINE BELOW
-ḻ l_ 1E3B 7739 LATIN SMALL LETTER L WITH LINE BELOW
-Ḿ M' 1E3E 7742 LATIN CAPITAL LETTER M WITH ACUTE
-ḿ m' 1E3F 7743 LATIN SMALL LETTER M WITH ACUTE
-Ṁ M. 1E40 7744 LATIN CAPITAL LETTER M WITH DOT ABOVE
-ṁ m. 1E41 7745 LATIN SMALL LETTER M WITH DOT ABOVE
-Ṅ N. 1E44 7748 LATIN CAPITAL LETTER N WITH DOT ABOVE `
-ṅ n. 1E45 7749 LATIN SMALL LETTER N WITH DOT ABOVE `
-Ṉ N_ 1E48 7752 LATIN CAPITAL LETTER N WITH LINE BELOW `
-ṉ n_ 1E49 7753 LATIN SMALL LETTER N WITH LINE BELOW `
-Ṕ P' 1E54 7764 LATIN CAPITAL LETTER P WITH ACUTE
-ṕ p' 1E55 7765 LATIN SMALL LETTER P WITH ACUTE
-Ṗ P. 1E56 7766 LATIN CAPITAL LETTER P WITH DOT ABOVE
-ṗ p. 1E57 7767 LATIN SMALL LETTER P WITH DOT ABOVE
-Ṙ R. 1E58 7768 LATIN CAPITAL LETTER R WITH DOT ABOVE
-ṙ r. 1E59 7769 LATIN SMALL LETTER R WITH DOT ABOVE
-Ṟ R_ 1E5E 7774 LATIN CAPITAL LETTER R WITH LINE BELOW
-ṟ r_ 1E5F 7775 LATIN SMALL LETTER R WITH LINE BELOW
-Ṡ S. 1E60 7776 LATIN CAPITAL LETTER S WITH DOT ABOVE
-ṡ s. 1E61 7777 LATIN SMALL LETTER S WITH DOT ABOVE
-Ṫ T. 1E6A 7786 LATIN CAPITAL LETTER T WITH DOT ABOVE
-ṫ t. 1E6B 7787 LATIN SMALL LETTER T WITH DOT ABOVE
-Ṯ T_ 1E6E 7790 LATIN CAPITAL LETTER T WITH LINE BELOW
-ṯ t_ 1E6F 7791 LATIN SMALL LETTER T WITH LINE BELOW
-Ṽ V? 1E7C 7804 LATIN CAPITAL LETTER V WITH TILDE
-ṽ v? 1E7D 7805 LATIN SMALL LETTER V WITH TILDE
-Ẁ W! 1E80 7808 LATIN CAPITAL LETTER W WITH GRAVE
-ẁ w! 1E81 7809 LATIN SMALL LETTER W WITH GRAVE
-Ẃ W' 1E82 7810 LATIN CAPITAL LETTER W WITH ACUTE
-ẃ w' 1E83 7811 LATIN SMALL LETTER W WITH ACUTE
-Ẅ W: 1E84 7812 LATIN CAPITAL LETTER W WITH DIAERESIS
-ẅ w: 1E85 7813 LATIN SMALL LETTER W WITH DIAERESIS
-Ẇ W. 1E86 7814 LATIN CAPITAL LETTER W WITH DOT ABOVE
-ẇ w. 1E87 7815 LATIN SMALL LETTER W WITH DOT ABOVE
-Ẋ X. 1E8A 7818 LATIN CAPITAL LETTER X WITH DOT ABOVE
-ẋ x. 1E8B 7819 LATIN SMALL LETTER X WITH DOT ABOVE
-Ẍ X: 1E8C 7820 LATIN CAPITAL LETTER X WITH DIAERESIS
-ẍ x: 1E8D 7821 LATIN SMALL LETTER X WITH DIAERESIS
-Ẏ Y. 1E8E 7822 LATIN CAPITAL LETTER Y WITH DOT ABOVE
-ẏ y. 1E8F 7823 LATIN SMALL LETTER Y WITH DOT ABOVE
-Ẑ Z> 1E90 7824 LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
-ẑ z> 1E91 7825 LATIN SMALL LETTER Z WITH CIRCUMFLEX
-Ẕ Z_ 1E94 7828 LATIN CAPITAL LETTER Z WITH LINE BELOW
-ẕ z_ 1E95 7829 LATIN SMALL LETTER Z WITH LINE BELOW
-ẖ h_ 1E96 7830 LATIN SMALL LETTER H WITH LINE BELOW
-ẗ t: 1E97 7831 LATIN SMALL LETTER T WITH DIAERESIS
-ẘ w0 1E98 7832 LATIN SMALL LETTER W WITH RING ABOVE
-ẙ y0 1E99 7833 LATIN SMALL LETTER Y WITH RING ABOVE
-Ả A2 1EA2 7842 LATIN CAPITAL LETTER A WITH HOOK ABOVE
-ả a2 1EA3 7843 LATIN SMALL LETTER A WITH HOOK ABOVE
-Ẻ E2 1EBA 7866 LATIN CAPITAL LETTER E WITH HOOK ABOVE
-ẻ e2 1EBB 7867 LATIN SMALL LETTER E WITH HOOK ABOVE
-Ẽ E? 1EBC 7868 LATIN CAPITAL LETTER E WITH TILDE
-ẽ e? 1EBD 7869 LATIN SMALL LETTER E WITH TILDE
-Ỉ I2 1EC8 7880 LATIN CAPITAL LETTER I WITH HOOK ABOVE
-ỉ i2 1EC9 7881 LATIN SMALL LETTER I WITH HOOK ABOVE
-Ỏ O2 1ECE 7886 LATIN CAPITAL LETTER O WITH HOOK ABOVE
-ỏ o2 1ECF 7887 LATIN SMALL LETTER O WITH HOOK ABOVE
-Ủ U2 1EE6 7910 LATIN CAPITAL LETTER U WITH HOOK ABOVE
-ủ u2 1EE7 7911 LATIN SMALL LETTER U WITH HOOK ABOVE
-Ỳ Y! 1EF2 7922 LATIN CAPITAL LETTER Y WITH GRAVE
-ỳ y! 1EF3 7923 LATIN SMALL LETTER Y WITH GRAVE
-Ỷ Y2 1EF6 7926 LATIN CAPITAL LETTER Y WITH HOOK ABOVE
-ỷ y2 1EF7 7927 LATIN SMALL LETTER Y WITH HOOK ABOVE
-Ỹ Y? 1EF8 7928 LATIN CAPITAL LETTER Y WITH TILDE
-ỹ y? 1EF9 7929 LATIN SMALL LETTER Y WITH TILDE
-ἀ ;' 1F00 7936 GREEK SMALL LETTER ALPHA WITH PSILI
-ἁ ,' 1F01 7937 GREEK SMALL LETTER ALPHA WITH DASIA
-ἂ ;! 1F02 7938 GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA
-ἃ ,! 1F03 7939 GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA
-ἄ ?; 1F04 7940 GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA
-ἅ ?, 1F05 7941 GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA
-ἆ !: 1F06 7942 GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI
-ἇ ?: 1F07 7943 GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
-  1N 2002 8194 EN SPACE
-  1M 2003 8195 EM SPACE
-  3M 2004 8196 THREE-PER-EM SPACE
-  4M 2005 8197 FOUR-PER-EM SPACE
-  6M 2006 8198 SIX-PER-EM SPACE
-  1T 2009 8201 THIN SPACE
-  1H 200A 8202 HAIR SPACE
-‐ -1 2010 8208 HYPHEN
-– -N 2013 8211 EN DASH `
-— -M 2014 8212 EM DASH
-― -3 2015 8213 HORIZONTAL BAR
-‖ !2 2016 8214 DOUBLE VERTICAL LINE
-‗ =2 2017 8215 DOUBLE LOW LINE
-‘ '6 2018 8216 LEFT SINGLE QUOTATION MARK
-’ '9 2019 8217 RIGHT SINGLE QUOTATION MARK
-‚ .9 201A 8218 SINGLE LOW-9 QUOTATION MARK
-‛ 9' 201B 8219 SINGLE HIGH-REVERSED-9 QUOTATION MARK
-“ "6 201C 8220 LEFT DOUBLE QUOTATION MARK
-” "9 201D 8221 RIGHT DOUBLE QUOTATION MARK
-„ :9 201E 8222 DOUBLE LOW-9 QUOTATION MARK
-‟ 9" 201F 8223 DOUBLE HIGH-REVERSED-9 QUOTATION MARK
-† /- 2020 8224 DAGGER
-‡ /= 2021 8225 DOUBLE DAGGER
-• oo 2022 8226 BULLET
-‥ .. 2025 8229 TWO DOT LEADER
-… ,. 2026 8230 HORIZONTAL ELLIPSIS
-‰ %0 2030 8240 PER MILLE SIGN
-′ 1' 2032 8242 PRIME
-″ 2' 2033 8243 DOUBLE PRIME
-‴ 3' 2034 8244 TRIPLE PRIME
-‵ 1" 2035 8245 REVERSED PRIME
-‶ 2" 2036 8246 REVERSED DOUBLE PRIME
-‷ 3" 2037 8247 REVERSED TRIPLE PRIME
-‸ Ca 2038 8248 CARET
-‹ <1 2039 8249 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
-› >1 203A 8250 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
-※ :X 203B 8251 REFERENCE MARK
-‾ '- 203E 8254 OVERLINE
-⁄ /f 2044 8260 FRACTION SLASH
-⁰ 0S 2070 8304 SUPERSCRIPT ZERO
-⁴ 4S 2074 8308 SUPERSCRIPT FOUR
-⁵ 5S 2075 8309 SUPERSCRIPT FIVE
-⁶ 6S 2076 8310 SUPERSCRIPT SIX
-⁷ 7S 2077 8311 SUPERSCRIPT SEVEN
-⁸ 8S 2078 8312 SUPERSCRIPT EIGHT
-⁹ 9S 2079 8313 SUPERSCRIPT NINE
-⁺ +S 207A 8314 SUPERSCRIPT PLUS SIGN
-⁻ -S 207B 8315 SUPERSCRIPT MINUS
-⁼ =S 207C 8316 SUPERSCRIPT EQUALS SIGN
-⁽ (S 207D 8317 SUPERSCRIPT LEFT PARENTHESIS
-⁾ )S 207E 8318 SUPERSCRIPT RIGHT PARENTHESIS
-ⁿ nS 207F 8319 SUPERSCRIPT LATIN SMALL LETTER N `
-₀ 0s 2080 8320 SUBSCRIPT ZERO
-₁ 1s 2081 8321 SUBSCRIPT ONE
-₂ 2s 2082 8322 SUBSCRIPT TWO
-₃ 3s 2083 8323 SUBSCRIPT THREE
-₄ 4s 2084 8324 SUBSCRIPT FOUR
-₅ 5s 2085 8325 SUBSCRIPT FIVE
-₆ 6s 2086 8326 SUBSCRIPT SIX
-₇ 7s 2087 8327 SUBSCRIPT SEVEN
-₈ 8s 2088 8328 SUBSCRIPT EIGHT
-₉ 9s 2089 8329 SUBSCRIPT NINE
-₊ +s 208A 8330 SUBSCRIPT PLUS SIGN
-₋ -s 208B 8331 SUBSCRIPT MINUS
-₌ =s 208C 8332 SUBSCRIPT EQUALS SIGN
-₍ (s 208D 8333 SUBSCRIPT LEFT PARENTHESIS
-₎ )s 208E 8334 SUBSCRIPT RIGHT PARENTHESIS
-₤ Li 20A4 8356 LIRA SIGN
-₧ Pt 20A7 8359 PESETA SIGN
-₩ W= 20A9 8361 WON SIGN
-€ Eu 20AC 8364 EURO SIGN
-₽ =R 20BD 8381 ROUBLE SIGN
-₽ =P 20BD 8381 ROUBLE SIGN
-℃ oC 2103 8451 DEGREE CELSIUS
-℅ co 2105 8453 CARE OF
-℉ oF 2109 8457 DEGREE FAHRENHEIT
-№ N0 2116 8470 NUMERO SIGN
-℗ PO 2117 8471 SOUND RECORDING COPYRIGHT
-℞ Rx 211E 8478 PRESCRIPTION TAKE
-℠ SM 2120 8480 SERVICE MARK
-™ TM 2122 8482 TRADE MARK SIGN
-Ω Om 2126 8486 OHM SIGN
-Å AO 212B 8491 ANGSTROM SIGN
-⅓ 13 2153 8531 VULGAR FRACTION ONE THIRD
-⅔ 23 2154 8532 VULGAR FRACTION TWO THIRDS
-⅕ 15 2155 8533 VULGAR FRACTION ONE FIFTH
-⅖ 25 2156 8534 VULGAR FRACTION TWO FIFTHS
-⅗ 35 2157 8535 VULGAR FRACTION THREE FIFTHS
-⅘ 45 2158 8536 VULGAR FRACTION FOUR FIFTHS
-⅙ 16 2159 8537 VULGAR FRACTION ONE SIXTH
-⅚ 56 215A 8538 VULGAR FRACTION FIVE SIXTHS
-⅛ 18 215B 8539 VULGAR FRACTION ONE EIGHTH
-⅜ 38 215C 8540 VULGAR FRACTION THREE EIGHTHS
-⅝ 58 215D 8541 VULGAR FRACTION FIVE EIGHTHS
-⅞ 78 215E 8542 VULGAR FRACTION SEVEN EIGHTHS
-Ⅰ 1R 2160 8544 ROMAN NUMERAL ONE
-Ⅱ 2R 2161 8545 ROMAN NUMERAL TWO
-Ⅲ 3R 2162 8546 ROMAN NUMERAL THREE
-Ⅳ 4R 2163 8547 ROMAN NUMERAL FOUR
-Ⅴ 5R 2164 8548 ROMAN NUMERAL FIVE
-Ⅵ 6R 2165 8549 ROMAN NUMERAL SIX
-Ⅶ 7R 2166 8550 ROMAN NUMERAL SEVEN
-Ⅷ 8R 2167 8551 ROMAN NUMERAL EIGHT
-Ⅸ 9R 2168 8552 ROMAN NUMERAL NINE
-Ⅹ aR 2169 8553 ROMAN NUMERAL TEN
-Ⅺ bR 216A 8554 ROMAN NUMERAL ELEVEN
-Ⅻ cR 216B 8555 ROMAN NUMERAL TWELVE
-ⅰ 1r 2170 8560 SMALL ROMAN NUMERAL ONE
-ⅱ 2r 2171 8561 SMALL ROMAN NUMERAL TWO
-ⅲ 3r 2172 8562 SMALL ROMAN NUMERAL THREE
-ⅳ 4r 2173 8563 SMALL ROMAN NUMERAL FOUR
-ⅴ 5r 2174 8564 SMALL ROMAN NUMERAL FIVE
-ⅵ 6r 2175 8565 SMALL ROMAN NUMERAL SIX
-ⅶ 7r 2176 8566 SMALL ROMAN NUMERAL SEVEN
-ⅷ 8r 2177 8567 SMALL ROMAN NUMERAL EIGHT
-ⅸ 9r 2178 8568 SMALL ROMAN NUMERAL NINE
-ⅹ ar 2179 8569 SMALL ROMAN NUMERAL TEN
-ⅺ br 217A 8570 SMALL ROMAN NUMERAL ELEVEN
-ⅻ cr 217B 8571 SMALL ROMAN NUMERAL TWELVE
-← <- 2190 8592 LEFTWARDS ARROW
-↑ -! 2191 8593 UPWARDS ARROW
-→ -> 2192 8594 RIGHTWARDS ARROW
-↓ -v 2193 8595 DOWNWARDS ARROW
-↔ <> 2194 8596 LEFT RIGHT ARROW
-↕ UD 2195 8597 UP DOWN ARROW
-⇐ <= 21D0 8656 LEFTWARDS DOUBLE ARROW
-⇒ => 21D2 8658 RIGHTWARDS DOUBLE ARROW
-⇔ == 21D4 8660 LEFT RIGHT DOUBLE ARROW
-∀ FA 2200 8704 FOR ALL
-∂ dP 2202 8706 PARTIAL DIFFERENTIAL
-∃ TE 2203 8707 THERE EXISTS
-∅ /0 2205 8709 EMPTY SET
-∆ DE 2206 8710 INCREMENT
-∇ NB 2207 8711 NABLA
-∈ (- 2208 8712 ELEMENT OF
-∋ -) 220B 8715 CONTAINS AS MEMBER
-∏ *P 220F 8719 N-ARY PRODUCT `
-∑ +Z 2211 8721 N-ARY SUMMATION `
-− -2 2212 8722 MINUS SIGN
-∓ -+ 2213 8723 MINUS-OR-PLUS SIGN
-∗ *- 2217 8727 ASTERISK OPERATOR
-∘ Ob 2218 8728 RING OPERATOR
-∙ Sb 2219 8729 BULLET OPERATOR
-√ RT 221A 8730 SQUARE ROOT
-∝ 0( 221D 8733 PROPORTIONAL TO
-∞ 00 221E 8734 INFINITY
-∟ -L 221F 8735 RIGHT ANGLE
-∠ -V 2220 8736 ANGLE
-∥ PP 2225 8741 PARALLEL TO
-∧ AN 2227 8743 LOGICAL AND
-∨ OR 2228 8744 LOGICAL OR
-∩ (U 2229 8745 INTERSECTION
-∪ )U 222A 8746 UNION
-∫ In 222B 8747 INTEGRAL
-∬ DI 222C 8748 DOUBLE INTEGRAL
-∮ Io 222E 8750 CONTOUR INTEGRAL
-∴ .: 2234 8756 THEREFORE
-∵ :. 2235 8757 BECAUSE
-∶ :R 2236 8758 RATIO
-∷ :: 2237 8759 PROPORTION
-∼ ?1 223C 8764 TILDE OPERATOR
-∾ CG 223E 8766 INVERTED LAZY S
-≃ ?- 2243 8771 ASYMPTOTICALLY EQUAL TO
-≅ ?= 2245 8773 APPROXIMATELY EQUAL TO
-≈ ?2 2248 8776 ALMOST EQUAL TO
-≌ =? 224C 8780 ALL EQUAL TO
-≓ HI 2253 8787 IMAGE OF OR APPROXIMATELY EQUAL TO
-≠ != 2260 8800 NOT EQUAL TO
-≡ =3 2261 8801 IDENTICAL TO
-≤ =< 2264 8804 LESS-THAN OR EQUAL TO
-≥ >= 2265 8805 GREATER-THAN OR EQUAL TO
-≪ <* 226A 8810 MUCH LESS-THAN
-≫ *> 226B 8811 MUCH GREATER-THAN
-≮ !< 226E 8814 NOT LESS-THAN
-≯ !> 226F 8815 NOT GREATER-THAN
-⊂ (C 2282 8834 SUBSET OF
-⊃ )C 2283 8835 SUPERSET OF
-⊆ (_ 2286 8838 SUBSET OF OR EQUAL TO
-⊇ )_ 2287 8839 SUPERSET OF OR EQUAL TO
-⊙ 0. 2299 8857 CIRCLED DOT OPERATOR
-⊚ 02 229A 8858 CIRCLED RING OPERATOR
-⊥ -T 22A5 8869 UP TACK
-⋅ .P 22C5 8901 DOT OPERATOR
-⋮ :3 22EE 8942 VERTICAL ELLIPSIS
-⋯ .3 22EF 8943 MIDLINE HORIZONTAL ELLIPSIS
-⌂ Eh 2302 8962 HOUSE
-⌈ <7 2308 8968 LEFT CEILING
-⌉ >7 2309 8969 RIGHT CEILING
-⌊ 7< 230A 8970 LEFT FLOOR
-⌋ 7> 230B 8971 RIGHT FLOOR
-⌐ NI 2310 8976 REVERSED NOT SIGN
-⌒ (A 2312 8978 ARC
-⌕ TR 2315 8981 TELEPHONE RECORDER
-⌠ Iu 2320 8992 TOP HALF INTEGRAL
-⌡ Il 2321 8993 BOTTOM HALF INTEGRAL
-〈 </ 2329 9001 LEFT-POINTING ANGLE BRACKET
-〉 /> 232A 9002 RIGHT-POINTING ANGLE BRACKET
-␣ Vs 2423 9251 OPEN BOX
-⑀ 1h 2440 9280 OCR HOOK
-⑁ 3h 2441 9281 OCR CHAIR
-⑂ 2h 2442 9282 OCR FORK
-⑃ 4h 2443 9283 OCR INVERTED FORK
-⑆ 1j 2446 9286 OCR BRANCH BANK IDENTIFICATION
-⑇ 2j 2447 9287 OCR AMOUNT OF CHECK
-⑈ 3j 2448 9288 OCR DASH
-⑉ 4j 2449 9289 OCR CUSTOMER ACCOUNT NUMBER
-⒈ 1. 2488 9352 DIGIT ONE FULL STOP
-⒉ 2. 2489 9353 DIGIT TWO FULL STOP
-⒊ 3. 248A 9354 DIGIT THREE FULL STOP
-⒋ 4. 248B 9355 DIGIT FOUR FULL STOP
-⒌ 5. 248C 9356 DIGIT FIVE FULL STOP
-⒍ 6. 248D 9357 DIGIT SIX FULL STOP
-⒎ 7. 248E 9358 DIGIT SEVEN FULL STOP
-⒏ 8. 248F 9359 DIGIT EIGHT FULL STOP
-⒐ 9. 2490 9360 DIGIT NINE FULL STOP
-─ hh 2500 9472 BOX DRAWINGS LIGHT HORIZONTAL
-━ HH 2501 9473 BOX DRAWINGS HEAVY HORIZONTAL
-│ vv 2502 9474 BOX DRAWINGS LIGHT VERTICAL
-┃ VV 2503 9475 BOX DRAWINGS HEAVY VERTICAL
-┄ 3- 2504 9476 BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL
-┅ 3_ 2505 9477 BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL
-┆ 3! 2506 9478 BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL
-┇ 3/ 2507 9479 BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL
-┈ 4- 2508 9480 BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL
-┉ 4_ 2509 9481 BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL
-┊ 4! 250A 9482 BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL
-┋ 4/ 250B 9483 BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL
-┌ dr 250C 9484 BOX DRAWINGS LIGHT DOWN AND RIGHT
-┍ dR 250D 9485 BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY
-┎ Dr 250E 9486 BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT
-┏ DR 250F 9487 BOX DRAWINGS HEAVY DOWN AND RIGHT
-┐ dl 2510 9488 BOX DRAWINGS LIGHT DOWN AND LEFT
-┑ dL 2511 9489 BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY
-┒ Dl 2512 9490 BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT
-┓ LD 2513 9491 BOX DRAWINGS HEAVY DOWN AND LEFT
-└ ur 2514 9492 BOX DRAWINGS LIGHT UP AND RIGHT
-┕ uR 2515 9493 BOX DRAWINGS UP LIGHT AND RIGHT HEAVY
-┖ Ur 2516 9494 BOX DRAWINGS UP HEAVY AND RIGHT LIGHT
-┗ UR 2517 9495 BOX DRAWINGS HEAVY UP AND RIGHT
-┘ ul 2518 9496 BOX DRAWINGS LIGHT UP AND LEFT
-┙ uL 2519 9497 BOX DRAWINGS UP LIGHT AND LEFT HEAVY
-┚ Ul 251A 9498 BOX DRAWINGS UP HEAVY AND LEFT LIGHT
-┛ UL 251B 9499 BOX DRAWINGS HEAVY UP AND LEFT
-├ vr 251C 9500 BOX DRAWINGS LIGHT VERTICAL AND RIGHT
-┝ vR 251D 9501 BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY
-┠ Vr 2520 9504 BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT
-┣ VR 2523 9507 BOX DRAWINGS HEAVY VERTICAL AND RIGHT
-┤ vl 2524 9508 BOX DRAWINGS LIGHT VERTICAL AND LEFT
-┥ vL 2525 9509 BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY
-┨ Vl 2528 9512 BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT
-┫ VL 252B 9515 BOX DRAWINGS HEAVY VERTICAL AND LEFT
-┬ dh 252C 9516 BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
-┯ dH 252F 9519 BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY
-┰ Dh 2530 9520 BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT
-┳ DH 2533 9523 BOX DRAWINGS HEAVY DOWN AND HORIZONTAL
-┴ uh 2534 9524 BOX DRAWINGS LIGHT UP AND HORIZONTAL
-┷ uH 2537 9527 BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY
-┸ Uh 2538 9528 BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT
-┻ UH 253B 9531 BOX DRAWINGS HEAVY UP AND HORIZONTAL
-┼ vh 253C 9532 BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
-┿ vH 253F 9535 BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY
-╂ Vh 2542 9538 BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT
-╋ VH 254B 9547 BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL
-╱ FD 2571 9585 BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
-╲ BD 2572 9586 BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
-▀ TB 2580 9600 UPPER HALF BLOCK
-▄ LB 2584 9604 LOWER HALF BLOCK
-█ FB 2588 9608 FULL BLOCK
-▌ lB 258C 9612 LEFT HALF BLOCK
-▐ RB 2590 9616 RIGHT HALF BLOCK
-░ .S 2591 9617 LIGHT SHADE
-▒ :S 2592 9618 MEDIUM SHADE
-▓ ?S 2593 9619 DARK SHADE
-■ fS 25A0 9632 BLACK SQUARE
-□ OS 25A1 9633 WHITE SQUARE
-▢ RO 25A2 9634 WHITE SQUARE WITH ROUNDED CORNERS
-▣ Rr 25A3 9635 WHITE SQUARE CONTAINING BLACK SMALL SQUARE
-▤ RF 25A4 9636 SQUARE WITH HORIZONTAL FILL
-▥ RY 25A5 9637 SQUARE WITH VERTICAL FILL
-▦ RH 25A6 9638 SQUARE WITH ORTHOGONAL CROSSHATCH FILL
-▧ RZ 25A7 9639 SQUARE WITH UPPER LEFT TO LOWER RIGHT FILL
-▨ RK 25A8 9640 SQUARE WITH UPPER RIGHT TO LOWER LEFT FILL
-▩ RX 25A9 9641 SQUARE WITH DIAGONAL CROSSHATCH FILL
-▪ sB 25AA 9642 BLACK SMALL SQUARE
-▬ SR 25AC 9644 BLACK RECTANGLE
-▭ Or 25AD 9645 WHITE RECTANGLE
-▲ UT 25B2 9650 BLACK UP-POINTING TRIANGLE
-△ uT 25B3 9651 WHITE UP-POINTING TRIANGLE
-▶ PR 25B6 9654 BLACK RIGHT-POINTING TRIANGLE
-▷ Tr 25B7 9655 WHITE RIGHT-POINTING TRIANGLE
-▼ Dt 25BC 9660 BLACK DOWN-POINTING TRIANGLE
-▽ dT 25BD 9661 WHITE DOWN-POINTING TRIANGLE
-◀ PL 25C0 9664 BLACK LEFT-POINTING TRIANGLE
-◁ Tl 25C1 9665 WHITE LEFT-POINTING TRIANGLE
-◆ Db 25C6 9670 BLACK DIAMOND
-◇ Dw 25C7 9671 WHITE DIAMOND
-◊ LZ 25CA 9674 LOZENGE
-○ 0m 25CB 9675 WHITE CIRCLE
-◎ 0o 25CE 9678 BULLSEYE
-● 0M 25CF 9679 BLACK CIRCLE
-◐ 0L 25D0 9680 CIRCLE WITH LEFT HALF BLACK
-◑ 0R 25D1 9681 CIRCLE WITH RIGHT HALF BLACK
-◘ Sn 25D8 9688 INVERSE BULLET
-◙ Ic 25D9 9689 INVERSE WHITE CIRCLE
-◢ Fd 25E2 9698 BLACK LOWER RIGHT TRIANGLE
-◣ Bd 25E3 9699 BLACK LOWER LEFT TRIANGLE
-★ *2 2605 9733 BLACK STAR
-☆ *1 2606 9734 WHITE STAR
-☜ <H 261C 9756 WHITE LEFT POINTING INDEX
-☞ >H 261E 9758 WHITE RIGHT POINTING INDEX
-☺ 0u 263A 9786 WHITE SMILING FACE
-☻ 0U 263B 9787 BLACK SMILING FACE
-☼ SU 263C 9788 WHITE SUN WITH RAYS
-♀ Fm 2640 9792 FEMALE SIGN
-♂ Ml 2642 9794 MALE SIGN
-♠ cS 2660 9824 BLACK SPADE SUIT
-♡ cH 2661 9825 WHITE HEART SUIT
-♢ cD 2662 9826 WHITE DIAMOND SUIT
-♣ cC 2663 9827 BLACK CLUB SUIT
-♩ Md 2669 9833 QUARTER NOTE `
-♪ M8 266A 9834 EIGHTH NOTE `
-♫ M2 266B 9835 BEAMED EIGHTH NOTES
-♭ Mb 266D 9837 MUSIC FLAT SIGN
-♮ Mx 266E 9838 MUSIC NATURAL SIGN
-♯ MX 266F 9839 MUSIC SHARP SIGN
-✓ OK 2713 10003 CHECK MARK
-✗ XX 2717 10007 BALLOT X
-✠ -X 2720 10016 MALTESE CROSS
-  IS 3000 12288 IDEOGRAPHIC SPACE
-、 ,_ 3001 12289 IDEOGRAPHIC COMMA
-。 ._ 3002 12290 IDEOGRAPHIC FULL STOP
-〃 +" 3003 12291 DITTO MARK
-〄 +_ 3004 12292 JAPANESE INDUSTRIAL STANDARD SYMBOL
-々 *_ 3005 12293 IDEOGRAPHIC ITERATION MARK
-〆 ;_ 3006 12294 IDEOGRAPHIC CLOSING MARK
-〇 0_ 3007 12295 IDEOGRAPHIC NUMBER ZERO
-《 <+ 300A 12298 LEFT DOUBLE ANGLE BRACKET
-》 >+ 300B 12299 RIGHT DOUBLE ANGLE BRACKET
-「 <' 300C 12300 LEFT CORNER BRACKET
-」 >' 300D 12301 RIGHT CORNER BRACKET
-『 <" 300E 12302 LEFT WHITE CORNER BRACKET
-』 >" 300F 12303 RIGHT WHITE CORNER BRACKET
-【 (" 3010 12304 LEFT BLACK LENTICULAR BRACKET
-】 )" 3011 12305 RIGHT BLACK LENTICULAR BRACKET
-〒 =T 3012 12306 POSTAL MARK
-〓 =_ 3013 12307 GETA MARK
-〔 (' 3014 12308 LEFT TORTOISE SHELL BRACKET
-〕 )' 3015 12309 RIGHT TORTOISE SHELL BRACKET
-〖 (I 3016 12310 LEFT WHITE LENTICULAR BRACKET
-〗 )I 3017 12311 RIGHT WHITE LENTICULAR BRACKET
-〜 -? 301C 12316 WAVE DASH
-ぁ A5 3041 12353 HIRAGANA LETTER SMALL A
-あ a5 3042 12354 HIRAGANA LETTER A
-ぃ I5 3043 12355 HIRAGANA LETTER SMALL I
-い i5 3044 12356 HIRAGANA LETTER I
-ぅ U5 3045 12357 HIRAGANA LETTER SMALL U
-う u5 3046 12358 HIRAGANA LETTER U
-ぇ E5 3047 12359 HIRAGANA LETTER SMALL E
-え e5 3048 12360 HIRAGANA LETTER E
-ぉ O5 3049 12361 HIRAGANA LETTER SMALL O
-お o5 304A 12362 HIRAGANA LETTER O
-か ka 304B 12363 HIRAGANA LETTER KA
-が ga 304C 12364 HIRAGANA LETTER GA
-き ki 304D 12365 HIRAGANA LETTER KI
-ぎ gi 304E 12366 HIRAGANA LETTER GI
-く ku 304F 12367 HIRAGANA LETTER KU
-ぐ gu 3050 12368 HIRAGANA LETTER GU
-け ke 3051 12369 HIRAGANA LETTER KE
-げ ge 3052 12370 HIRAGANA LETTER GE
-こ ko 3053 12371 HIRAGANA LETTER KO
-ご go 3054 12372 HIRAGANA LETTER GO
-さ sa 3055 12373 HIRAGANA LETTER SA
-ざ za 3056 12374 HIRAGANA LETTER ZA
-し si 3057 12375 HIRAGANA LETTER SI
-じ zi 3058 12376 HIRAGANA LETTER ZI
-す su 3059 12377 HIRAGANA LETTER SU
-ず zu 305A 12378 HIRAGANA LETTER ZU
-せ se 305B 12379 HIRAGANA LETTER SE
-ぜ ze 305C 12380 HIRAGANA LETTER ZE
-そ so 305D 12381 HIRAGANA LETTER SO
-ぞ zo 305E 12382 HIRAGANA LETTER ZO
-た ta 305F 12383 HIRAGANA LETTER TA
-だ da 3060 12384 HIRAGANA LETTER DA
-ち ti 3061 12385 HIRAGANA LETTER TI
-ぢ di 3062 12386 HIRAGANA LETTER DI
-っ tU 3063 12387 HIRAGANA LETTER SMALL TU
-つ tu 3064 12388 HIRAGANA LETTER TU
-づ du 3065 12389 HIRAGANA LETTER DU
-て te 3066 12390 HIRAGANA LETTER TE
-で de 3067 12391 HIRAGANA LETTER DE
-と to 3068 12392 HIRAGANA LETTER TO
-ど do 3069 12393 HIRAGANA LETTER DO
-な na 306A 12394 HIRAGANA LETTER NA
-に ni 306B 12395 HIRAGANA LETTER NI
-ぬ nu 306C 12396 HIRAGANA LETTER NU
-ね ne 306D 12397 HIRAGANA LETTER NE
-の no 306E 12398 HIRAGANA LETTER NO
-は ha 306F 12399 HIRAGANA LETTER HA
-ば ba 3070 12400 HIRAGANA LETTER BA
-ぱ pa 3071 12401 HIRAGANA LETTER PA
-ひ hi 3072 12402 HIRAGANA LETTER HI
-び bi 3073 12403 HIRAGANA LETTER BI
-ぴ pi 3074 12404 HIRAGANA LETTER PI
-ふ hu 3075 12405 HIRAGANA LETTER HU
-ぶ bu 3076 12406 HIRAGANA LETTER BU
-ぷ pu 3077 12407 HIRAGANA LETTER PU
-へ he 3078 12408 HIRAGANA LETTER HE
-べ be 3079 12409 HIRAGANA LETTER BE
-ぺ pe 307A 12410 HIRAGANA LETTER PE
-ほ ho 307B 12411 HIRAGANA LETTER HO
-ぼ bo 307C 12412 HIRAGANA LETTER BO
-ぽ po 307D 12413 HIRAGANA LETTER PO
-ま ma 307E 12414 HIRAGANA LETTER MA
-み mi 307F 12415 HIRAGANA LETTER MI
-む mu 3080 12416 HIRAGANA LETTER MU
-め me 3081 12417 HIRAGANA LETTER ME
-も mo 3082 12418 HIRAGANA LETTER MO
-ゃ yA 3083 12419 HIRAGANA LETTER SMALL YA
-や ya 3084 12420 HIRAGANA LETTER YA
-ゅ yU 3085 12421 HIRAGANA LETTER SMALL YU
-ゆ yu 3086 12422 HIRAGANA LETTER YU
-ょ yO 3087 12423 HIRAGANA LETTER SMALL YO
-よ yo 3088 12424 HIRAGANA LETTER YO
-ら ra 3089 12425 HIRAGANA LETTER RA
-り ri 308A 12426 HIRAGANA LETTER RI
-る ru 308B 12427 HIRAGANA LETTER RU
-れ re 308C 12428 HIRAGANA LETTER RE
-ろ ro 308D 12429 HIRAGANA LETTER RO
-ゎ wA 308E 12430 HIRAGANA LETTER SMALL WA
-わ wa 308F 12431 HIRAGANA LETTER WA
-ゐ wi 3090 12432 HIRAGANA LETTER WI
-ゑ we 3091 12433 HIRAGANA LETTER WE
-を wo 3092 12434 HIRAGANA LETTER WO
-ん n5 3093 12435 HIRAGANA LETTER N `
-ゔ vu 3094 12436 HIRAGANA LETTER VU
-゛ "5 309B 12443 KATAKANA-HIRAGANA VOICED SOUND MARK
-゜ 05 309C 12444 KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
-ゝ *5 309D 12445 HIRAGANA ITERATION MARK
-ゞ +5 309E 12446 HIRAGANA VOICED ITERATION MARK
-ァ a6 30A1 12449 KATAKANA LETTER SMALL A
-ア A6 30A2 12450 KATAKANA LETTER A
-ィ i6 30A3 12451 KATAKANA LETTER SMALL I
-イ I6 30A4 12452 KATAKANA LETTER I
-ゥ u6 30A5 12453 KATAKANA LETTER SMALL U
-ウ U6 30A6 12454 KATAKANA LETTER U
-ェ e6 30A7 12455 KATAKANA LETTER SMALL E
-エ E6 30A8 12456 KATAKANA LETTER E
-ォ o6 30A9 12457 KATAKANA LETTER SMALL O
-オ O6 30AA 12458 KATAKANA LETTER O
-カ Ka 30AB 12459 KATAKANA LETTER KA
-ガ Ga 30AC 12460 KATAKANA LETTER GA
-キ Ki 30AD 12461 KATAKANA LETTER KI
-ギ Gi 30AE 12462 KATAKANA LETTER GI
-ク Ku 30AF 12463 KATAKANA LETTER KU
-グ Gu 30B0 12464 KATAKANA LETTER GU
-ケ Ke 30B1 12465 KATAKANA LETTER KE
-ゲ Ge 30B2 12466 KATAKANA LETTER GE
-コ Ko 30B3 12467 KATAKANA LETTER KO
-ゴ Go 30B4 12468 KATAKANA LETTER GO
-サ Sa 30B5 12469 KATAKANA LETTER SA
-ザ Za 30B6 12470 KATAKANA LETTER ZA
-シ Si 30B7 12471 KATAKANA LETTER SI
-ジ Zi 30B8 12472 KATAKANA LETTER ZI
-ス Su 30B9 12473 KATAKANA LETTER SU
-ズ Zu 30BA 12474 KATAKANA LETTER ZU
-セ Se 30BB 12475 KATAKANA LETTER SE
-ゼ Ze 30BC 12476 KATAKANA LETTER ZE
-ソ So 30BD 12477 KATAKANA LETTER SO
-ゾ Zo 30BE 12478 KATAKANA LETTER ZO
-タ Ta 30BF 12479 KATAKANA LETTER TA
-ダ Da 30C0 12480 KATAKANA LETTER DA
-チ Ti 30C1 12481 KATAKANA LETTER TI
-ヂ Di 30C2 12482 KATAKANA LETTER DI
-ッ TU 30C3 12483 KATAKANA LETTER SMALL TU
-ツ Tu 30C4 12484 KATAKANA LETTER TU
-ヅ Du 30C5 12485 KATAKANA LETTER DU
-テ Te 30C6 12486 KATAKANA LETTER TE
-デ De 30C7 12487 KATAKANA LETTER DE
-ト To 30C8 12488 KATAKANA LETTER TO
-ド Do 30C9 12489 KATAKANA LETTER DO
-ナ Na 30CA 12490 KATAKANA LETTER NA
-ニ Ni 30CB 12491 KATAKANA LETTER NI
-ヌ Nu 30CC 12492 KATAKANA LETTER NU
-ネ Ne 30CD 12493 KATAKANA LETTER NE
-ノ No 30CE 12494 KATAKANA LETTER NO
-ハ Ha 30CF 12495 KATAKANA LETTER HA
-バ Ba 30D0 12496 KATAKANA LETTER BA
-パ Pa 30D1 12497 KATAKANA LETTER PA
-ヒ Hi 30D2 12498 KATAKANA LETTER HI
-ビ Bi 30D3 12499 KATAKANA LETTER BI
-ピ Pi 30D4 12500 KATAKANA LETTER PI
-フ Hu 30D5 12501 KATAKANA LETTER HU
-ブ Bu 30D6 12502 KATAKANA LETTER BU
-プ Pu 30D7 12503 KATAKANA LETTER PU
-ヘ He 30D8 12504 KATAKANA LETTER HE
-ベ Be 30D9 12505 KATAKANA LETTER BE
-ペ Pe 30DA 12506 KATAKANA LETTER PE
-ホ Ho 30DB 12507 KATAKANA LETTER HO
-ボ Bo 30DC 12508 KATAKANA LETTER BO
-ポ Po 30DD 12509 KATAKANA LETTER PO
-マ Ma 30DE 12510 KATAKANA LETTER MA
-ミ Mi 30DF 12511 KATAKANA LETTER MI
-ム Mu 30E0 12512 KATAKANA LETTER MU
-メ Me 30E1 12513 KATAKANA LETTER ME
-モ Mo 30E2 12514 KATAKANA LETTER MO
-ャ YA 30E3 12515 KATAKANA LETTER SMALL YA
-ヤ Ya 30E4 12516 KATAKANA LETTER YA
-ュ YU 30E5 12517 KATAKANA LETTER SMALL YU
-ユ Yu 30E6 12518 KATAKANA LETTER YU
-ョ YO 30E7 12519 KATAKANA LETTER SMALL YO
-ヨ Yo 30E8 12520 KATAKANA LETTER YO
-ラ Ra 30E9 12521 KATAKANA LETTER RA
-リ Ri 30EA 12522 KATAKANA LETTER RI
-ル Ru 30EB 12523 KATAKANA LETTER RU
-レ Re 30EC 12524 KATAKANA LETTER RE
-ロ Ro 30ED 12525 KATAKANA LETTER RO
-ヮ WA 30EE 12526 KATAKANA LETTER SMALL WA
-ワ Wa 30EF 12527 KATAKANA LETTER WA
-ヰ Wi 30F0 12528 KATAKANA LETTER WI
-ヱ We 30F1 12529 KATAKANA LETTER WE
-ヲ Wo 30F2 12530 KATAKANA LETTER WO
-ン N6 30F3 12531 KATAKANA LETTER N `
-ヴ Vu 30F4 12532 KATAKANA LETTER VU
-ヵ KA 30F5 12533 KATAKANA LETTER SMALL KA
-ヶ KE 30F6 12534 KATAKANA LETTER SMALL KE
-ヷ Va 30F7 12535 KATAKANA LETTER VA
-ヸ Vi 30F8 12536 KATAKANA LETTER VI
-ヹ Ve 30F9 12537 KATAKANA LETTER VE
-ヺ Vo 30FA 12538 KATAKANA LETTER VO
-・ .6 30FB 12539 KATAKANA MIDDLE DOT
-ー -6 30FC 12540 KATAKANA-HIRAGANA PROLONGED SOUND MARK
-ヽ *6 30FD 12541 KATAKANA ITERATION MARK
-ヾ +6 30FE 12542 KATAKANA VOICED ITERATION MARK
-ㄅ b4 3105 12549 BOPOMOFO LETTER B
-ㄆ p4 3106 12550 BOPOMOFO LETTER P
-ㄇ m4 3107 12551 BOPOMOFO LETTER M
-ㄈ f4 3108 12552 BOPOMOFO LETTER F
-ㄉ d4 3109 12553 BOPOMOFO LETTER D
-ㄊ t4 310A 12554 BOPOMOFO LETTER T
-ㄋ n4 310B 12555 BOPOMOFO LETTER N `
-ㄌ l4 310C 12556 BOPOMOFO LETTER L
-ㄍ g4 310D 12557 BOPOMOFO LETTER G
-ㄎ k4 310E 12558 BOPOMOFO LETTER K
-ㄏ h4 310F 12559 BOPOMOFO LETTER H
-ㄐ j4 3110 12560 BOPOMOFO LETTER J
-ㄑ q4 3111 12561 BOPOMOFO LETTER Q
-ㄒ x4 3112 12562 BOPOMOFO LETTER X
-ㄓ zh 3113 12563 BOPOMOFO LETTER ZH
-ㄔ ch 3114 12564 BOPOMOFO LETTER CH
-ㄕ sh 3115 12565 BOPOMOFO LETTER SH
-ㄖ r4 3116 12566 BOPOMOFO LETTER R
-ㄗ z4 3117 12567 BOPOMOFO LETTER Z
-ㄘ c4 3118 12568 BOPOMOFO LETTER C
-ㄙ s4 3119 12569 BOPOMOFO LETTER S
-ㄚ a4 311A 12570 BOPOMOFO LETTER A
-ㄛ o4 311B 12571 BOPOMOFO LETTER O
-ㄜ e4 311C 12572 BOPOMOFO LETTER E
-ㄞ ai 311E 12574 BOPOMOFO LETTER AI
-ㄟ ei 311F 12575 BOPOMOFO LETTER EI
-ㄠ au 3120 12576 BOPOMOFO LETTER AU
-ㄡ ou 3121 12577 BOPOMOFO LETTER OU
-ㄢ an 3122 12578 BOPOMOFO LETTER AN
-ㄣ en 3123 12579 BOPOMOFO LETTER EN
-ㄤ aN 3124 12580 BOPOMOFO LETTER ANG
-ㄥ eN 3125 12581 BOPOMOFO LETTER ENG
-ㄦ er 3126 12582 BOPOMOFO LETTER ER
-ㄧ i4 3127 12583 BOPOMOFO LETTER I
-ㄨ u4 3128 12584 BOPOMOFO LETTER U
-ㄩ iu 3129 12585 BOPOMOFO LETTER IU
-ㄪ v4 312A 12586 BOPOMOFO LETTER V
-ㄫ nG 312B 12587 BOPOMOFO LETTER NG
-ㄬ gn 312C 12588 BOPOMOFO LETTER GN
-㈠ 1c 3220 12832 PARENTHESIZED IDEOGRAPH ONE
-㈡ 2c 3221 12833 PARENTHESIZED IDEOGRAPH TWO
-㈢ 3c 3222 12834 PARENTHESIZED IDEOGRAPH THREE
-㈣ 4c 3223 12835 PARENTHESIZED IDEOGRAPH FOUR
-㈤ 5c 3224 12836 PARENTHESIZED IDEOGRAPH FIVE
-㈥ 6c 3225 12837 PARENTHESIZED IDEOGRAPH SIX
-㈦ 7c 3226 12838 PARENTHESIZED IDEOGRAPH SEVEN
-㈧ 8c 3227 12839 PARENTHESIZED IDEOGRAPH EIGHT
-㈨ 9c 3228 12840 PARENTHESIZED IDEOGRAPH NINE
-ff ff FB00 64256 LATIN SMALL LIGATURE FF
-fi fi FB01 64257 LATIN SMALL LIGATURE FI
-fl fl FB02 64258 LATIN SMALL LIGATURE FL
-ſt ft FB05 64261 LATIN SMALL LIGATURE LONG S T
-st st FB06 64262 LATIN SMALL LIGATURE ST
+ # Nb 0x23 35 NUMBER SIGN
+ $ DO 0x24 36 DOLLAR SIGN
+ @ At 0x40 64 COMMERCIAL AT
+ [ <( 0x5b 91 LEFT SQUARE BRACKET
+ \ // 0x5c 92 REVERSE SOLIDUS
+ ] )> 0x5d 93 RIGHT SQUARE BRACKET
+ ^ '> 0x5e 94 CIRCUMFLEX ACCENT
+ ` '! 0x60 96 GRAVE ACCENT
+ { (! 0x7b 123 LEFT CURLY BRACKET
+ | !! 0x7c 124 VERTICAL LINE
+ } !) 0x7d 125 RIGHT CURLY BRACKET
+ ~ '? 0x7e 126 TILDE
+ ^? DT 0x7f 127 DELETE (DEL)
+ ~@ PA 0x80 128 PADDING CHARACTER (PAD)
+ ~A HO 0x81 129 HIGH OCTET PRESET (HOP)
+ ~B BH 0x82 130 BREAK PERMITTED HERE (BPH)
+ ~C NH 0x83 131 NO BREAK HERE (NBH)
+ ~D IN 0x84 132 INDEX (IND)
+ ~E NL 0x85 133 NEXT LINE (NEL)
+ ~F SA 0x86 134 START OF SELECTED AREA (SSA)
+ ~G ES 0x87 135 END OF SELECTED AREA (ESA)
+ ~H HS 0x88 136 CHARACTER TABULATION SET (HTS)
+ ~I HJ 0x89 137 CHARACTER TABULATION WITH JUSTIFICATION (HTJ)
+ ~J VS 0x8a 138 LINE TABULATION SET (VTS)
+ ~K PD 0x8b 139 PARTIAL LINE FORWARD (PLD)
+ ~L PU 0x8c 140 PARTIAL LINE BACKWARD (PLU)
+ ~M RI 0x8d 141 REVERSE LINE FEED (RI)
+ ~N S2 0x8e 142 SINGLE-SHIFT TWO (SS2)
+ ~O S3 0x8f 143 SINGLE-SHIFT THREE (SS3)
+ ~P DC 0x90 144 DEVICE CONTROL STRING (DCS)
+ ~Q P1 0x91 145 PRIVATE USE ONE (PU1)
+ ~R P2 0x92 146 PRIVATE USE TWO (PU2)
+ ~S TS 0x93 147 SET TRANSMIT STATE (STS)
+ ~T CC 0x94 148 CANCEL CHARACTER (CCH)
+ ~U MW 0x95 149 MESSAGE WAITING (MW)
+ ~V SG 0x96 150 START OF GUARDED AREA (SPA)
+ ~W EG 0x97 151 END OF GUARDED AREA (EPA)
+ ~X SS 0x98 152 START OF STRING (SOS)
+ ~Y GC 0x99 153 SINGLE GRAPHIC CHARACTER INTRODUCER (SGCI)
+ ~Z SC 0x9a 154 SINGLE CHARACTER INTRODUCER (SCI)
+ ~[ CI 0x9b 155 CONTROL SEQUENCE INTRODUCER (CSI)
+ ~\ ST 0x9c 156 STRING TERMINATOR (ST)
+ ~] OC 0x9d 157 OPERATING SYSTEM COMMAND (OSC)
+ ~^ PM 0x9e 158 PRIVACY MESSAGE (PM)
+ ~_ AC 0x9f 159 APPLICATION PROGRAM COMMAND (APC)
+ | NS 0xa0 160 NO-BREAK SPACE
+ ¡ !I 0xa1 161 INVERTED EXCLAMATION MARK
+ ¢ Ct 0xa2 162 CENT SIGN
+ £ Pd 0xa3 163 POUND SIGN
+ ¤ Cu 0xa4 164 CURRENCY SIGN
+ ¥ Ye 0xa5 165 YEN SIGN
+ ¦ BB 0xa6 166 BROKEN BAR
+ § SE 0xa7 167 SECTION SIGN
+ ¨ ': 0xa8 168 DIAERESIS
+ © Co 0xa9 169 COPYRIGHT SIGN
+ ª -a 0xaa 170 FEMININE ORDINAL INDICATOR
+ « << 0xab 171 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ ¬ NO 0xac 172 NOT SIGN
+ ­ -- 0xad 173 SOFT HYPHEN
+ ® Rg 0xae 174 REGISTERED SIGN
+ ¯ 'm 0xaf 175 MACRON
+ ° DG 0xb0 176 DEGREE SIGN
+ ± +- 0xb1 177 PLUS-MINUS SIGN
+ ² 2S 0xb2 178 SUPERSCRIPT TWO
+ ³ 3S 0xb3 179 SUPERSCRIPT THREE
+ ´ '' 0xb4 180 ACUTE ACCENT
+ µ My 0xb5 181 MICRO SIGN
+ ¶ PI 0xb6 182 PILCROW SIGN
+ · .M 0xb7 183 MIDDLE DOT
+ ¸ ', 0xb8 184 CEDILLA
+ ¹ 1S 0xb9 185 SUPERSCRIPT ONE
+ º -o 0xba 186 MASCULINE ORDINAL INDICATOR
+ » >> 0xbb 187 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ ¼ 14 0xbc 188 VULGAR FRACTION ONE QUARTER
+ ½ 12 0xbd 189 VULGAR FRACTION ONE HALF
+ ¾ 34 0xbe 190 VULGAR FRACTION THREE QUARTERS
+ ¿ ?I 0xbf 191 INVERTED QUESTION MARK
+ À A! 0xc0 192 LATIN CAPITAL LETTER A WITH GRAVE
+ Á A' 0xc1 193 LATIN CAPITAL LETTER A WITH ACUTE
+ Â A> 0xc2 194 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ Ã A? 0xc3 195 LATIN CAPITAL LETTER A WITH TILDE
+ Ä A: 0xc4 196 LATIN CAPITAL LETTER A WITH DIAERESIS
+ Å AA 0xc5 197 LATIN CAPITAL LETTER A WITH RING ABOVE
+ Æ AE 0xc6 198 LATIN CAPITAL LETTER AE
+ Ç C, 0xc7 199 LATIN CAPITAL LETTER C WITH CEDILLA
+ È E! 0xc8 200 LATIN CAPITAL LETTER E WITH GRAVE
+ É E' 0xc9 201 LATIN CAPITAL LETTER E WITH ACUTE
+ Ê E> 0xca 202 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ Ë E: 0xcb 203 LATIN CAPITAL LETTER E WITH DIAERESIS
+ Ì I! 0xcc 204 LATIN CAPITAL LETTER I WITH GRAVE
+ Í I' 0xcd 205 LATIN CAPITAL LETTER I WITH ACUTE
+ Î I> 0xce 206 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ Ï I: 0xcf 207 LATIN CAPITAL LETTER I WITH DIAERESIS
+ Ð D- 0xd0 208 LATIN CAPITAL LETTER ETH (Icelandic)
+ Ñ N? 0xd1 209 LATIN CAPITAL LETTER N WITH TILDE
+ Ò O! 0xd2 210 LATIN CAPITAL LETTER O WITH GRAVE
+ Ó O' 0xd3 211 LATIN CAPITAL LETTER O WITH ACUTE
+ Ô O> 0xd4 212 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ Õ O? 0xd5 213 LATIN CAPITAL LETTER O WITH TILDE
+ Ö O: 0xd6 214 LATIN CAPITAL LETTER O WITH DIAERESIS
+ × *X 0xd7 215 MULTIPLICATION SIGN
+ Ø O/ 0xd8 216 LATIN CAPITAL LETTER O WITH STROKE
+ Ù U! 0xd9 217 LATIN CAPITAL LETTER U WITH GRAVE
+ Ú U' 0xda 218 LATIN CAPITAL LETTER U WITH ACUTE
+ Û U> 0xdb 219 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ Ü U: 0xdc 220 LATIN CAPITAL LETTER U WITH DIAERESIS
+ Ý Y' 0xdd 221 LATIN CAPITAL LETTER Y WITH ACUTE
+ Þ TH 0xde 222 LATIN CAPITAL LETTER THORN (Icelandic)
+ ß ss 0xdf 223 LATIN SMALL LETTER SHARP S (German)
+ à a! 0xe0 224 LATIN SMALL LETTER A WITH GRAVE
+ á a' 0xe1 225 LATIN SMALL LETTER A WITH ACUTE
+ â a> 0xe2 226 LATIN SMALL LETTER A WITH CIRCUMFLEX
+ ã a? 0xe3 227 LATIN SMALL LETTER A WITH TILDE
+ ä a: 0xe4 228 LATIN SMALL LETTER A WITH DIAERESIS
+ å aa 0xe5 229 LATIN SMALL LETTER A WITH RING ABOVE
+ æ ae 0xe6 230 LATIN SMALL LETTER AE
+ ç c, 0xe7 231 LATIN SMALL LETTER C WITH CEDILLA
+ è e! 0xe8 232 LATIN SMALL LETTER E WITH GRAVE
+ é e' 0xe9 233 LATIN SMALL LETTER E WITH ACUTE
+ ê e> 0xea 234 LATIN SMALL LETTER E WITH CIRCUMFLEX
+ ë e: 0xeb 235 LATIN SMALL LETTER E WITH DIAERESIS
+ ì i! 0xec 236 LATIN SMALL LETTER I WITH GRAVE
+ í i' 0xed 237 LATIN SMALL LETTER I WITH ACUTE
+ î i> 0xee 238 LATIN SMALL LETTER I WITH CIRCUMFLEX
+ ï i: 0xef 239 LATIN SMALL LETTER I WITH DIAERESIS
+ ð d- 0xf0 240 LATIN SMALL LETTER ETH (Icelandic)
+ ñ n? 0xf1 241 LATIN SMALL LETTER N WITH TILDE
+ ò o! 0xf2 242 LATIN SMALL LETTER O WITH GRAVE
+ ó o' 0xf3 243 LATIN SMALL LETTER O WITH ACUTE
+ ô o> 0xf4 244 LATIN SMALL LETTER O WITH CIRCUMFLEX
+ õ o? 0xf5 245 LATIN SMALL LETTER O WITH TILDE
+ ö o: 0xf6 246 LATIN SMALL LETTER O WITH DIAERESIS
+ ÷ -: 0xf7 247 DIVISION SIGN
+ ø o/ 0xf8 248 LATIN SMALL LETTER O WITH STROKE
+ ù u! 0xf9 249 LATIN SMALL LETTER U WITH GRAVE
+ ú u' 0xfa 250 LATIN SMALL LETTER U WITH ACUTE
+ û u> 0xfb 251 LATIN SMALL LETTER U WITH CIRCUMFLEX
+ ü u: 0xfc 252 LATIN SMALL LETTER U WITH DIAERESIS
+ ý y' 0xfd 253 LATIN SMALL LETTER Y WITH ACUTE
+ þ th 0xfe 254 LATIN SMALL LETTER THORN (Icelandic)
+ ÿ y: 0xff 255 LATIN SMALL LETTER Y WITH DIAERESIS
+ Ā A- 0100 0256 LATIN CAPITAL LETTER A WITH MACRON
+ ā a- 0101 0257 LATIN SMALL LETTER A WITH MACRON
+ Ă A( 0102 0258 LATIN CAPITAL LETTER A WITH BREVE
+ ă a( 0103 0259 LATIN SMALL LETTER A WITH BREVE
+ Ą A; 0104 0260 LATIN CAPITAL LETTER A WITH OGONEK
+ ą a; 0105 0261 LATIN SMALL LETTER A WITH OGONEK
+ Ć C' 0106 0262 LATIN CAPITAL LETTER C WITH ACUTE
+ ć c' 0107 0263 LATIN SMALL LETTER C WITH ACUTE
+ Ĉ C> 0108 0264 LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+ ĉ c> 0109 0265 LATIN SMALL LETTER C WITH CIRCUMFLEX
+ Ċ C. 010A 0266 LATIN CAPITAL LETTER C WITH DOT ABOVE
+ ċ c. 010B 0267 LATIN SMALL LETTER C WITH DOT ABOVE
+ Č C< 010C 0268 LATIN CAPITAL LETTER C WITH CARON
+ č c< 010D 0269 LATIN SMALL LETTER C WITH CARON
+ Ď D< 010E 0270 LATIN CAPITAL LETTER D WITH CARON
+ ď d< 010F 0271 LATIN SMALL LETTER D WITH CARON
+ Đ D/ 0110 0272 LATIN CAPITAL LETTER D WITH STROKE
+ đ d/ 0111 0273 LATIN SMALL LETTER D WITH STROKE
+ Ē E- 0112 0274 LATIN CAPITAL LETTER E WITH MACRON
+ ē e- 0113 0275 LATIN SMALL LETTER E WITH MACRON
+ Ĕ E( 0114 0276 LATIN CAPITAL LETTER E WITH BREVE
+ ĕ e( 0115 0277 LATIN SMALL LETTER E WITH BREVE
+ Ė E. 0116 0278 LATIN CAPITAL LETTER E WITH DOT ABOVE
+ ė e. 0117 0279 LATIN SMALL LETTER E WITH DOT ABOVE
+ Ę E; 0118 0280 LATIN CAPITAL LETTER E WITH OGONEK
+ ę e; 0119 0281 LATIN SMALL LETTER E WITH OGONEK
+ Ě E< 011A 0282 LATIN CAPITAL LETTER E WITH CARON
+ ě e< 011B 0283 LATIN SMALL LETTER E WITH CARON
+ Ĝ G> 011C 0284 LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+ ĝ g> 011D 0285 LATIN SMALL LETTER G WITH CIRCUMFLEX
+ Ğ G( 011E 0286 LATIN CAPITAL LETTER G WITH BREVE
+ ğ g( 011F 0287 LATIN SMALL LETTER G WITH BREVE
+ Ġ G. 0120 0288 LATIN CAPITAL LETTER G WITH DOT ABOVE
+ ġ g. 0121 0289 LATIN SMALL LETTER G WITH DOT ABOVE
+ Ģ G, 0122 0290 LATIN CAPITAL LETTER G WITH CEDILLA
+ ģ g, 0123 0291 LATIN SMALL LETTER G WITH CEDILLA
+ Ĥ H> 0124 0292 LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+ ĥ h> 0125 0293 LATIN SMALL LETTER H WITH CIRCUMFLEX
+ Ħ H/ 0126 0294 LATIN CAPITAL LETTER H WITH STROKE
+ ħ h/ 0127 0295 LATIN SMALL LETTER H WITH STROKE
+ Ĩ I? 0128 0296 LATIN CAPITAL LETTER I WITH TILDE
+ ĩ i? 0129 0297 LATIN SMALL LETTER I WITH TILDE
+ Ī I- 012A 0298 LATIN CAPITAL LETTER I WITH MACRON
+ ī i- 012B 0299 LATIN SMALL LETTER I WITH MACRON
+ Ĭ I( 012C 0300 LATIN CAPITAL LETTER I WITH BREVE
+ ĭ i( 012D 0301 LATIN SMALL LETTER I WITH BREVE
+ Į I; 012E 0302 LATIN CAPITAL LETTER I WITH OGONEK
+ į i; 012F 0303 LATIN SMALL LETTER I WITH OGONEK
+ İ I. 0130 0304 LATIN CAPITAL LETTER I WITH DOT ABOVE
+ ı i. 0131 0305 LATIN SMALL LETTER DOTLESS I
+ IJ IJ 0132 0306 LATIN CAPITAL LIGATURE IJ
+ ij ij 0133 0307 LATIN SMALL LIGATURE IJ
+ Ĵ J> 0134 0308 LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+ ĵ j> 0135 0309 LATIN SMALL LETTER J WITH CIRCUMFLEX
+ Ķ K, 0136 0310 LATIN CAPITAL LETTER K WITH CEDILLA
+ ķ k, 0137 0311 LATIN SMALL LETTER K WITH CEDILLA
+ ĸ kk 0138 0312 LATIN SMALL LETTER KRA
+ Ĺ L' 0139 0313 LATIN CAPITAL LETTER L WITH ACUTE
+ ĺ l' 013A 0314 LATIN SMALL LETTER L WITH ACUTE
+ Ļ L, 013B 0315 LATIN CAPITAL LETTER L WITH CEDILLA
+ ļ l, 013C 0316 LATIN SMALL LETTER L WITH CEDILLA
+ Ľ L< 013D 0317 LATIN CAPITAL LETTER L WITH CARON
+ ľ l< 013E 0318 LATIN SMALL LETTER L WITH CARON
+ Ŀ L. 013F 0319 LATIN CAPITAL LETTER L WITH MIDDLE DOT
+ ŀ l. 0140 0320 LATIN SMALL LETTER L WITH MIDDLE DOT
+ Ł L/ 0141 0321 LATIN CAPITAL LETTER L WITH STROKE
+ ł l/ 0142 0322 LATIN SMALL LETTER L WITH STROKE
+ Ń N' 0143 0323 LATIN CAPITAL LETTER N WITH ACUTE `
+ ń n' 0144 0324 LATIN SMALL LETTER N WITH ACUTE `
+ Ņ N, 0145 0325 LATIN CAPITAL LETTER N WITH CEDILLA `
+ ņ n, 0146 0326 LATIN SMALL LETTER N WITH CEDILLA `
+ Ň N< 0147 0327 LATIN CAPITAL LETTER N WITH CARON `
+ ň n< 0148 0328 LATIN SMALL LETTER N WITH CARON `
+ ʼn 'n 0149 0329 LATIN SMALL LETTER N PRECEDED BY APOSTROPHE `
+ Ŋ NG 014A 0330 LATIN CAPITAL LETTER ENG
+ ŋ ng 014B 0331 LATIN SMALL LETTER ENG
+ Ō O- 014C 0332 LATIN CAPITAL LETTER O WITH MACRON
+ ō o- 014D 0333 LATIN SMALL LETTER O WITH MACRON
+ Ŏ O( 014E 0334 LATIN CAPITAL LETTER O WITH BREVE
+ ŏ o( 014F 0335 LATIN SMALL LETTER O WITH BREVE
+ Ő O" 0150 0336 LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+ ő o" 0151 0337 LATIN SMALL LETTER O WITH DOUBLE ACUTE
+ Œ OE 0152 0338 LATIN CAPITAL LIGATURE OE
+ œ oe 0153 0339 LATIN SMALL LIGATURE OE
+ Ŕ R' 0154 0340 LATIN CAPITAL LETTER R WITH ACUTE
+ ŕ r' 0155 0341 LATIN SMALL LETTER R WITH ACUTE
+ Ŗ R, 0156 0342 LATIN CAPITAL LETTER R WITH CEDILLA
+ ŗ r, 0157 0343 LATIN SMALL LETTER R WITH CEDILLA
+ Ř R< 0158 0344 LATIN CAPITAL LETTER R WITH CARON
+ ř r< 0159 0345 LATIN SMALL LETTER R WITH CARON
+ Ś S' 015A 0346 LATIN CAPITAL LETTER S WITH ACUTE
+ ś s' 015B 0347 LATIN SMALL LETTER S WITH ACUTE
+ Ŝ S> 015C 0348 LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+ ŝ s> 015D 0349 LATIN SMALL LETTER S WITH CIRCUMFLEX
+ Ş S, 015E 0350 LATIN CAPITAL LETTER S WITH CEDILLA
+ ş s, 015F 0351 LATIN SMALL LETTER S WITH CEDILLA
+ Š S< 0160 0352 LATIN CAPITAL LETTER S WITH CARON
+ š s< 0161 0353 LATIN SMALL LETTER S WITH CARON
+ Ţ T, 0162 0354 LATIN CAPITAL LETTER T WITH CEDILLA
+ ţ t, 0163 0355 LATIN SMALL LETTER T WITH CEDILLA
+ Ť T< 0164 0356 LATIN CAPITAL LETTER T WITH CARON
+ ť t< 0165 0357 LATIN SMALL LETTER T WITH CARON
+ Ŧ T/ 0166 0358 LATIN CAPITAL LETTER T WITH STROKE
+ ŧ t/ 0167 0359 LATIN SMALL LETTER T WITH STROKE
+ Ũ U? 0168 0360 LATIN CAPITAL LETTER U WITH TILDE
+ ũ u? 0169 0361 LATIN SMALL LETTER U WITH TILDE
+ Ū U- 016A 0362 LATIN CAPITAL LETTER U WITH MACRON
+ ū u- 016B 0363 LATIN SMALL LETTER U WITH MACRON
+ Ŭ U( 016C 0364 LATIN CAPITAL LETTER U WITH BREVE
+ ŭ u( 016D 0365 LATIN SMALL LETTER U WITH BREVE
+ Ů U0 016E 0366 LATIN CAPITAL LETTER U WITH RING ABOVE
+ ů u0 016F 0367 LATIN SMALL LETTER U WITH RING ABOVE
+ Ű U" 0170 0368 LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+ ű u" 0171 0369 LATIN SMALL LETTER U WITH DOUBLE ACUTE
+ Ų U; 0172 0370 LATIN CAPITAL LETTER U WITH OGONEK
+ ų u; 0173 0371 LATIN SMALL LETTER U WITH OGONEK
+ Ŵ W> 0174 0372 LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+ ŵ w> 0175 0373 LATIN SMALL LETTER W WITH CIRCUMFLEX
+ Ŷ Y> 0176 0374 LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+ ŷ y> 0177 0375 LATIN SMALL LETTER Y WITH CIRCUMFLEX
+ Ÿ Y: 0178 0376 LATIN CAPITAL LETTER Y WITH DIAERESIS
+ Ź Z' 0179 0377 LATIN CAPITAL LETTER Z WITH ACUTE
+ ź z' 017A 0378 LATIN SMALL LETTER Z WITH ACUTE
+ Ż Z. 017B 0379 LATIN CAPITAL LETTER Z WITH DOT ABOVE
+ ż z. 017C 0380 LATIN SMALL LETTER Z WITH DOT ABOVE
+ Ž Z< 017D 0381 LATIN CAPITAL LETTER Z WITH CARON
+ ž z< 017E 0382 LATIN SMALL LETTER Z WITH CARON
+ Ơ O9 01A0 0416 LATIN CAPITAL LETTER O WITH HORN
+ ơ o9 01A1 0417 LATIN SMALL LETTER O WITH HORN
+ Ƣ OI 01A2 0418 LATIN CAPITAL LETTER OI
+ ƣ oi 01A3 0419 LATIN SMALL LETTER OI
+ Ʀ yr 01A6 0422 LATIN LETTER YR
+ Ư U9 01AF 0431 LATIN CAPITAL LETTER U WITH HORN
+ ư u9 01B0 0432 LATIN SMALL LETTER U WITH HORN
+ Ƶ Z/ 01B5 0437 LATIN CAPITAL LETTER Z WITH STROKE
+ ƶ z/ 01B6 0438 LATIN SMALL LETTER Z WITH STROKE
+ Ʒ ED 01B7 0439 LATIN CAPITAL LETTER EZH
+ Ǎ A< 01CD 0461 LATIN CAPITAL LETTER A WITH CARON
+ ǎ a< 01CE 0462 LATIN SMALL LETTER A WITH CARON
+ Ǐ I< 01CF 0463 LATIN CAPITAL LETTER I WITH CARON
+ ǐ i< 01D0 0464 LATIN SMALL LETTER I WITH CARON
+ Ǒ O< 01D1 0465 LATIN CAPITAL LETTER O WITH CARON
+ ǒ o< 01D2 0466 LATIN SMALL LETTER O WITH CARON
+ Ǔ U< 01D3 0467 LATIN CAPITAL LETTER U WITH CARON
+ ǔ u< 01D4 0468 LATIN SMALL LETTER U WITH CARON
+ Ǟ A1 01DE 0478 LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON
+ ǟ a1 01DF 0479 LATIN SMALL LETTER A WITH DIAERESIS AND MACRON
+ Ǡ A7 01E0 0480 LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON
+ ǡ a7 01E1 0481 LATIN SMALL LETTER A WITH DOT ABOVE AND MACRON
+ Ǣ A3 01E2 0482 LATIN CAPITAL LETTER AE WITH MACRON
+ ǣ a3 01E3 0483 LATIN SMALL LETTER AE WITH MACRON
+ Ǥ G/ 01E4 0484 LATIN CAPITAL LETTER G WITH STROKE
+ ǥ g/ 01E5 0485 LATIN SMALL LETTER G WITH STROKE
+ Ǧ G< 01E6 0486 LATIN CAPITAL LETTER G WITH CARON
+ ǧ g< 01E7 0487 LATIN SMALL LETTER G WITH CARON
+ Ǩ K< 01E8 0488 LATIN CAPITAL LETTER K WITH CARON
+ ǩ k< 01E9 0489 LATIN SMALL LETTER K WITH CARON
+ Ǫ O; 01EA 0490 LATIN CAPITAL LETTER O WITH OGONEK
+ ǫ o; 01EB 0491 LATIN SMALL LETTER O WITH OGONEK
+ Ǭ O1 01EC 0492 LATIN CAPITAL LETTER O WITH OGONEK AND MACRON
+ ǭ o1 01ED 0493 LATIN SMALL LETTER O WITH OGONEK AND MACRON
+ Ǯ EZ 01EE 0494 LATIN CAPITAL LETTER EZH WITH CARON
+ ǯ ez 01EF 0495 LATIN SMALL LETTER EZH WITH CARON
+ ǰ j< 01F0 0496 LATIN SMALL LETTER J WITH CARON
+ Ǵ G' 01F4 0500 LATIN CAPITAL LETTER G WITH ACUTE
+ ǵ g' 01F5 0501 LATIN SMALL LETTER G WITH ACUTE
+ ʿ ;S 02BF 0703 MODIFIER LETTER LEFT HALF RING
+ ˇ '< 02C7 0711 CARON
+ ˘ '( 02D8 0728 BREVE
+ ˙ '. 02D9 0729 DOT ABOVE
+ ˚ '0 02DA 0730 RING ABOVE
+ ˛ '; 02DB 0731 OGONEK
+ ˝ '" 02DD 0733 DOUBLE ACUTE ACCENT
+ Ά A% 0386 0902 GREEK CAPITAL LETTER ALPHA WITH TONOS
+ Έ E% 0388 0904 GREEK CAPITAL LETTER EPSILON WITH TONOS
+ Ή Y% 0389 0905 GREEK CAPITAL LETTER ETA WITH TONOS
+ Ί I% 038A 0906 GREEK CAPITAL LETTER IOTA WITH TONOS
+ Ό O% 038C 0908 GREEK CAPITAL LETTER OMICRON WITH TONOS
+ Ύ U% 038E 0910 GREEK CAPITAL LETTER UPSILON WITH TONOS
+ Ώ W% 038F 0911 GREEK CAPITAL LETTER OMEGA WITH TONOS
+ ΐ i3 0390 0912 GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+ Α A* 0391 0913 GREEK CAPITAL LETTER ALPHA
+ Β B* 0392 0914 GREEK CAPITAL LETTER BETA
+ Γ G* 0393 0915 GREEK CAPITAL LETTER GAMMA
+ Δ D* 0394 0916 GREEK CAPITAL LETTER DELTA
+ Ε E* 0395 0917 GREEK CAPITAL LETTER EPSILON
+ Ζ Z* 0396 0918 GREEK CAPITAL LETTER ZETA
+ Η Y* 0397 0919 GREEK CAPITAL LETTER ETA
+ Θ H* 0398 0920 GREEK CAPITAL LETTER THETA
+ Ι I* 0399 0921 GREEK CAPITAL LETTER IOTA
+ Κ K* 039A 0922 GREEK CAPITAL LETTER KAPPA
+ Λ L* 039B 0923 GREEK CAPITAL LETTER LAMDA (aka LAMBDA)
+ Μ M* 039C 0924 GREEK CAPITAL LETTER MU
+ Ν N* 039D 0925 GREEK CAPITAL LETTER NU
+ Ξ C* 039E 0926 GREEK CAPITAL LETTER XI
+ Ο O* 039F 0927 GREEK CAPITAL LETTER OMICRON
+ Π P* 03A0 0928 GREEK CAPITAL LETTER PI
+ Ρ R* 03A1 0929 GREEK CAPITAL LETTER RHO
+ Σ S* 03A3 0931 GREEK CAPITAL LETTER SIGMA
+ Τ T* 03A4 0932 GREEK CAPITAL LETTER TAU
+ Υ U* 03A5 0933 GREEK CAPITAL LETTER UPSILON
+ Φ F* 03A6 0934 GREEK CAPITAL LETTER PHI
+ Χ X* 03A7 0935 GREEK CAPITAL LETTER CHI
+ Ψ Q* 03A8 0936 GREEK CAPITAL LETTER PSI
+ Ω W* 03A9 0937 GREEK CAPITAL LETTER OMEGA
+ Ϊ J* 03AA 0938 GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+ Ϋ V* 03AB 0939 GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+ ά a% 03AC 0940 GREEK SMALL LETTER ALPHA WITH TONOS
+ έ e% 03AD 0941 GREEK SMALL LETTER EPSILON WITH TONOS
+ ή y% 03AE 0942 GREEK SMALL LETTER ETA WITH TONOS
+ ί i% 03AF 0943 GREEK SMALL LETTER IOTA WITH TONOS
+ ΰ u3 03B0 0944 GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+ α a* 03B1 0945 GREEK SMALL LETTER ALPHA
+ β b* 03B2 0946 GREEK SMALL LETTER BETA
+ γ g* 03B3 0947 GREEK SMALL LETTER GAMMA
+ δ d* 03B4 0948 GREEK SMALL LETTER DELTA
+ ε e* 03B5 0949 GREEK SMALL LETTER EPSILON
+ ζ z* 03B6 0950 GREEK SMALL LETTER ZETA
+ η y* 03B7 0951 GREEK SMALL LETTER ETA
+ θ h* 03B8 0952 GREEK SMALL LETTER THETA
+ ι i* 03B9 0953 GREEK SMALL LETTER IOTA
+ κ k* 03BA 0954 GREEK SMALL LETTER KAPPA
+ λ l* 03BB 0955 GREEK SMALL LETTER LAMDA (aka LAMBDA)
+ μ m* 03BC 0956 GREEK SMALL LETTER MU
+ ν n* 03BD 0957 GREEK SMALL LETTER NU
+ ξ c* 03BE 0958 GREEK SMALL LETTER XI
+ ο o* 03BF 0959 GREEK SMALL LETTER OMICRON
+ π p* 03C0 0960 GREEK SMALL LETTER PI
+ ρ r* 03C1 0961 GREEK SMALL LETTER RHO
+ ς *s 03C2 0962 GREEK SMALL LETTER FINAL SIGMA
+ σ s* 03C3 0963 GREEK SMALL LETTER SIGMA
+ τ t* 03C4 0964 GREEK SMALL LETTER TAU
+ υ u* 03C5 0965 GREEK SMALL LETTER UPSILON
+ φ f* 03C6 0966 GREEK SMALL LETTER PHI
+ χ x* 03C7 0967 GREEK SMALL LETTER CHI
+ ψ q* 03C8 0968 GREEK SMALL LETTER PSI
+ ω w* 03C9 0969 GREEK SMALL LETTER OMEGA
+ ϊ j* 03CA 0970 GREEK SMALL LETTER IOTA WITH DIALYTIKA
+ ϋ v* 03CB 0971 GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+ ό o% 03CC 0972 GREEK SMALL LETTER OMICRON WITH TONOS
+ ύ u% 03CD 0973 GREEK SMALL LETTER UPSILON WITH TONOS
+ ώ w% 03CE 0974 GREEK SMALL LETTER OMEGA WITH TONOS
+ Ϙ 'G 03D8 0984 GREEK LETTER ARCHAIC KOPPA
+ ϙ ,G 03D9 0985 GREEK SMALL LETTER ARCHAIC KOPPA
+ Ϛ T3 03DA 0986 GREEK LETTER STIGMA
+ ϛ t3 03DB 0987 GREEK SMALL LETTER STIGMA
+ Ϝ M3 03DC 0988 GREEK LETTER DIGAMMA
+ ϝ m3 03DD 0989 GREEK SMALL LETTER DIGAMMA
+ Ϟ K3 03DE 0990 GREEK LETTER KOPPA
+ ϟ k3 03DF 0991 GREEK SMALL LETTER KOPPA
+ Ϡ P3 03E0 0992 GREEK LETTER SAMPI
+ ϡ p3 03E1 0993 GREEK SMALL LETTER SAMPI
+ ϴ '% 03F4 1012 GREEK CAPITAL THETA SYMBOL
+ ϵ j3 03F5 1013 GREEK LUNATE EPSILON SYMBOL
+ Ё IO 0401 1025 CYRILLIC CAPITAL LETTER IO
+ Ђ D% 0402 1026 CYRILLIC CAPITAL LETTER DJE
+ Ѓ G% 0403 1027 CYRILLIC CAPITAL LETTER GJE
+ Є IE 0404 1028 CYRILLIC CAPITAL LETTER UKRAINIAN IE
+ Ѕ DS 0405 1029 CYRILLIC CAPITAL LETTER DZE
+ І II 0406 1030 CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+ Ї YI 0407 1031 CYRILLIC CAPITAL LETTER YI
+ Ј J% 0408 1032 CYRILLIC CAPITAL LETTER JE
+ Љ LJ 0409 1033 CYRILLIC CAPITAL LETTER LJE
+ Њ NJ 040A 1034 CYRILLIC CAPITAL LETTER NJE
+ Ћ Ts 040B 1035 CYRILLIC CAPITAL LETTER TSHE
+ Ќ KJ 040C 1036 CYRILLIC CAPITAL LETTER KJE
+ Ў V% 040E 1038 CYRILLIC CAPITAL LETTER SHORT U
+ Џ DZ 040F 1039 CYRILLIC CAPITAL LETTER DZHE
+ А A= 0410 1040 CYRILLIC CAPITAL LETTER A
+ Б B= 0411 1041 CYRILLIC CAPITAL LETTER BE
+ В V= 0412 1042 CYRILLIC CAPITAL LETTER VE
+ Г G= 0413 1043 CYRILLIC CAPITAL LETTER GHE
+ Д D= 0414 1044 CYRILLIC CAPITAL LETTER DE
+ Е E= 0415 1045 CYRILLIC CAPITAL LETTER IE
+ Ж Z% 0416 1046 CYRILLIC CAPITAL LETTER ZHE
+ З Z= 0417 1047 CYRILLIC CAPITAL LETTER ZE
+ И I= 0418 1048 CYRILLIC CAPITAL LETTER I
+ Й J= 0419 1049 CYRILLIC CAPITAL LETTER SHORT I
+ К K= 041A 1050 CYRILLIC CAPITAL LETTER KA
+ Л L= 041B 1051 CYRILLIC CAPITAL LETTER EL
+ М M= 041C 1052 CYRILLIC CAPITAL LETTER EM
+ Н N= 041D 1053 CYRILLIC CAPITAL LETTER EN
+ О O= 041E 1054 CYRILLIC CAPITAL LETTER O
+ П P= 041F 1055 CYRILLIC CAPITAL LETTER PE
+ Р R= 0420 1056 CYRILLIC CAPITAL LETTER ER
+ С S= 0421 1057 CYRILLIC CAPITAL LETTER ES
+ Т T= 0422 1058 CYRILLIC CAPITAL LETTER TE
+ У U= 0423 1059 CYRILLIC CAPITAL LETTER U
+ Ф F= 0424 1060 CYRILLIC CAPITAL LETTER EF
+ Х H= 0425 1061 CYRILLIC CAPITAL LETTER HA
+ Ц C= 0426 1062 CYRILLIC CAPITAL LETTER TSE
+ Ч C% 0427 1063 CYRILLIC CAPITAL LETTER CHE
+ Ш S% 0428 1064 CYRILLIC CAPITAL LETTER SHA
+ Щ Sc 0429 1065 CYRILLIC CAPITAL LETTER SHCHA
+ Ъ =" 042A 1066 CYRILLIC CAPITAL LETTER HARD SIGN
+ Ы Y= 042B 1067 CYRILLIC CAPITAL LETTER YERU
+ Ь %" 042C 1068 CYRILLIC CAPITAL LETTER SOFT SIGN
+ Э JE 042D 1069 CYRILLIC CAPITAL LETTER E
+ Ю JU 042E 1070 CYRILLIC CAPITAL LETTER YU
+ Я JA 042F 1071 CYRILLIC CAPITAL LETTER YA
+ а a= 0430 1072 CYRILLIC SMALL LETTER A
+ б b= 0431 1073 CYRILLIC SMALL LETTER BE
+ в v= 0432 1074 CYRILLIC SMALL LETTER VE
+ г g= 0433 1075 CYRILLIC SMALL LETTER GHE
+ д d= 0434 1076 CYRILLIC SMALL LETTER DE
+ е e= 0435 1077 CYRILLIC SMALL LETTER IE
+ ж z% 0436 1078 CYRILLIC SMALL LETTER ZHE
+ з z= 0437 1079 CYRILLIC SMALL LETTER ZE
+ и i= 0438 1080 CYRILLIC SMALL LETTER I
+ й j= 0439 1081 CYRILLIC SMALL LETTER SHORT I
+ к k= 043A 1082 CYRILLIC SMALL LETTER KA
+ л l= 043B 1083 CYRILLIC SMALL LETTER EL
+ м m= 043C 1084 CYRILLIC SMALL LETTER EM
+ н n= 043D 1085 CYRILLIC SMALL LETTER EN
+ о o= 043E 1086 CYRILLIC SMALL LETTER O
+ п p= 043F 1087 CYRILLIC SMALL LETTER PE
+ р r= 0440 1088 CYRILLIC SMALL LETTER ER
+ с s= 0441 1089 CYRILLIC SMALL LETTER ES
+ т t= 0442 1090 CYRILLIC SMALL LETTER TE
+ у u= 0443 1091 CYRILLIC SMALL LETTER U
+ ф f= 0444 1092 CYRILLIC SMALL LETTER EF
+ х h= 0445 1093 CYRILLIC SMALL LETTER HA
+ ц c= 0446 1094 CYRILLIC SMALL LETTER TSE
+ ч c% 0447 1095 CYRILLIC SMALL LETTER CHE
+ ш s% 0448 1096 CYRILLIC SMALL LETTER SHA
+ щ sc 0449 1097 CYRILLIC SMALL LETTER SHCHA
+ ъ =' 044A 1098 CYRILLIC SMALL LETTER HARD SIGN
+ ы y= 044B 1099 CYRILLIC SMALL LETTER YERU
+ ь %' 044C 1100 CYRILLIC SMALL LETTER SOFT SIGN
+ э je 044D 1101 CYRILLIC SMALL LETTER E
+ ю ju 044E 1102 CYRILLIC SMALL LETTER YU
+ я ja 044F 1103 CYRILLIC SMALL LETTER YA
+ ё io 0451 1105 CYRILLIC SMALL LETTER IO
+ ђ d% 0452 1106 CYRILLIC SMALL LETTER DJE
+ ѓ g% 0453 1107 CYRILLIC SMALL LETTER GJE
+ є ie 0454 1108 CYRILLIC SMALL LETTER UKRAINIAN IE
+ ѕ ds 0455 1109 CYRILLIC SMALL LETTER DZE
+ і ii 0456 1110 CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+ ї yi 0457 1111 CYRILLIC SMALL LETTER YI
+ ј j% 0458 1112 CYRILLIC SMALL LETTER JE
+ љ lj 0459 1113 CYRILLIC SMALL LETTER LJE
+ њ nj 045A 1114 CYRILLIC SMALL LETTER NJE
+ ћ ts 045B 1115 CYRILLIC SMALL LETTER TSHE
+ ќ kj 045C 1116 CYRILLIC SMALL LETTER KJE
+ ў v% 045E 1118 CYRILLIC SMALL LETTER SHORT U
+ џ dz 045F 1119 CYRILLIC SMALL LETTER DZHE
+ Ѣ Y3 0462 1122 CYRILLIC CAPITAL LETTER YAT
+ ѣ y3 0463 1123 CYRILLIC SMALL LETTER YAT
+ Ѫ O3 046A 1130 CYRILLIC CAPITAL LETTER BIG YUS
+ ѫ o3 046B 1131 CYRILLIC SMALL LETTER BIG YUS
+ Ѳ F3 0472 1138 CYRILLIC CAPITAL LETTER FITA
+ ѳ f3 0473 1139 CYRILLIC SMALL LETTER FITA
+ Ѵ V3 0474 1140 CYRILLIC CAPITAL LETTER IZHITSA
+ ѵ v3 0475 1141 CYRILLIC SMALL LETTER IZHITSA
+ Ҁ C3 0480 1152 CYRILLIC CAPITAL LETTER KOPPA
+ ҁ c3 0481 1153 CYRILLIC SMALL LETTER KOPPA
+ Ґ G3 0490 1168 CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+ ґ g3 0491 1169 CYRILLIC SMALL LETTER GHE WITH UPTURN
+ א A+ 05D0 1488 HEBREW LETTER ALEF
+ ב B+ 05D1 1489 HEBREW LETTER BET
+ ג G+ 05D2 1490 HEBREW LETTER GIMEL
+ ד D+ 05D3 1491 HEBREW LETTER DALET
+ ה H+ 05D4 1492 HEBREW LETTER HE
+ ו W+ 05D5 1493 HEBREW LETTER VAV
+ ז Z+ 05D6 1494 HEBREW LETTER ZAYIN
+ ח X+ 05D7 1495 HEBREW LETTER HET
+ ט Tj 05D8 1496 HEBREW LETTER TET
+ י J+ 05D9 1497 HEBREW LETTER YOD
+ ך K% 05DA 1498 HEBREW LETTER FINAL KAF
+ כ K+ 05DB 1499 HEBREW LETTER KAF
+ ל L+ 05DC 1500 HEBREW LETTER LAMED
+ ם M% 05DD 1501 HEBREW LETTER FINAL MEM
+ מ M+ 05DE 1502 HEBREW LETTER MEM
+ ן N% 05DF 1503 HEBREW LETTER FINAL NUN `
+ נ N+ 05E0 1504 HEBREW LETTER NUN `
+ ס S+ 05E1 1505 HEBREW LETTER SAMEKH
+ ע E+ 05E2 1506 HEBREW LETTER AYIN
+ ף P% 05E3 1507 HEBREW LETTER FINAL PE
+ פ P+ 05E4 1508 HEBREW LETTER PE
+ ץ Zj 05E5 1509 HEBREW LETTER FINAL TSADI
+ צ ZJ 05E6 1510 HEBREW LETTER TSADI
+ ק Q+ 05E7 1511 HEBREW LETTER QOF
+ ר R+ 05E8 1512 HEBREW LETTER RESH
+ ש Sh 05E9 1513 HEBREW LETTER SHIN
+ ת T+ 05EA 1514 HEBREW LETTER TAV
+ ، ,+ 060C 1548 ARABIC COMMA
+ ؛ ;+ 061B 1563 ARABIC SEMICOLON
+ ؟ ?+ 061F 1567 ARABIC QUESTION MARK
+ ء H' 0621 1569 ARABIC LETTER HAMZA
+ آ aM 0622 1570 ARABIC LETTER ALEF WITH MADDA ABOVE
+ أ aH 0623 1571 ARABIC LETTER ALEF WITH HAMZA ABOVE
+ ؤ wH 0624 1572 ARABIC LETTER WAW WITH HAMZA ABOVE
+ إ ah 0625 1573 ARABIC LETTER ALEF WITH HAMZA BELOW
+ ئ yH 0626 1574 ARABIC LETTER YEH WITH HAMZA ABOVE
+ ا a+ 0627 1575 ARABIC LETTER ALEF
+ ب b+ 0628 1576 ARABIC LETTER BEH
+ ة tm 0629 1577 ARABIC LETTER TEH MARBUTA
+ ت t+ 062A 1578 ARABIC LETTER TEH
+ ث tk 062B 1579 ARABIC LETTER THEH
+ ج g+ 062C 1580 ARABIC LETTER JEEM
+ ح hk 062D 1581 ARABIC LETTER HAH
+ خ x+ 062E 1582 ARABIC LETTER KHAH
+ د d+ 062F 1583 ARABIC LETTER DAL
+ ذ dk 0630 1584 ARABIC LETTER THAL
+ ر r+ 0631 1585 ARABIC LETTER REH
+ ز z+ 0632 1586 ARABIC LETTER ZAIN
+ س s+ 0633 1587 ARABIC LETTER SEEN
+ ش sn 0634 1588 ARABIC LETTER SHEEN
+ ص c+ 0635 1589 ARABIC LETTER SAD
+ ض dd 0636 1590 ARABIC LETTER DAD
+ ط tj 0637 1591 ARABIC LETTER TAH
+ ظ zH 0638 1592 ARABIC LETTER ZAH
+ ع e+ 0639 1593 ARABIC LETTER AIN
+ غ i+ 063A 1594 ARABIC LETTER GHAIN
+ ـ ++ 0640 1600 ARABIC TATWEEL
+ ف f+ 0641 1601 ARABIC LETTER FEH
+ ق q+ 0642 1602 ARABIC LETTER QAF
+ ك k+ 0643 1603 ARABIC LETTER KAF
+ ل l+ 0644 1604 ARABIC LETTER LAM
+ م m+ 0645 1605 ARABIC LETTER MEEM
+ ن n+ 0646 1606 ARABIC LETTER NOON
+ ه h+ 0647 1607 ARABIC LETTER HEH
+ و w+ 0648 1608 ARABIC LETTER WAW
+ ى j+ 0649 1609 ARABIC LETTER ALEF MAKSURA
+ ي y+ 064A 1610 ARABIC LETTER YEH
+ ً :+ 064B 1611 ARABIC FATHATAN
+ ٌ "+ 064C 1612 ARABIC DAMMATAN
+ ٍ =+ 064D 1613 ARABIC KASRATAN
+ َ /+ 064E 1614 ARABIC FATHA
+ ُ '+ 064F 1615 ARABIC DAMMA
+ ِ 1+ 0650 1616 ARABIC KASRA
+ ّ 3+ 0651 1617 ARABIC SHADDA
+ ْ 0+ 0652 1618 ARABIC SUKUN
+ ٰ aS 0670 1648 ARABIC LETTER SUPERSCRIPT ALEF
+ پ p+ 067E 1662 ARABIC LETTER PEH
+ ڤ v+ 06A4 1700 ARABIC LETTER VEH
+ گ gf 06AF 1711 ARABIC LETTER GAF
+ ۰ 0a 06F0 1776 EXTENDED ARABIC-INDIC DIGIT ZERO
+ ۱ 1a 06F1 1777 EXTENDED ARABIC-INDIC DIGIT ONE
+ ۲ 2a 06F2 1778 EXTENDED ARABIC-INDIC DIGIT TWO
+ ۳ 3a 06F3 1779 EXTENDED ARABIC-INDIC DIGIT THREE
+ ۴ 4a 06F4 1780 EXTENDED ARABIC-INDIC DIGIT FOUR
+ ۵ 5a 06F5 1781 EXTENDED ARABIC-INDIC DIGIT FIVE
+ ۶ 6a 06F6 1782 EXTENDED ARABIC-INDIC DIGIT SIX
+ ۷ 7a 06F7 1783 EXTENDED ARABIC-INDIC DIGIT SEVEN
+ ۸ 8a 06F8 1784 EXTENDED ARABIC-INDIC DIGIT EIGHT
+ ۹ 9a 06F9 1785 EXTENDED ARABIC-INDIC DIGIT NINE
+ Ḃ B. 1E02 7682 LATIN CAPITAL LETTER B WITH DOT ABOVE
+ ḃ b. 1E03 7683 LATIN SMALL LETTER B WITH DOT ABOVE
+ Ḇ B_ 1E06 7686 LATIN CAPITAL LETTER B WITH LINE BELOW
+ ḇ b_ 1E07 7687 LATIN SMALL LETTER B WITH LINE BELOW
+ Ḋ D. 1E0A 7690 LATIN CAPITAL LETTER D WITH DOT ABOVE
+ ḋ d. 1E0B 7691 LATIN SMALL LETTER D WITH DOT ABOVE
+ Ḏ D_ 1E0E 7694 LATIN CAPITAL LETTER D WITH LINE BELOW
+ ḏ d_ 1E0F 7695 LATIN SMALL LETTER D WITH LINE BELOW
+ Ḑ D, 1E10 7696 LATIN CAPITAL LETTER D WITH CEDILLA
+ ḑ d, 1E11 7697 LATIN SMALL LETTER D WITH CEDILLA
+ Ḟ F. 1E1E 7710 LATIN CAPITAL LETTER F WITH DOT ABOVE
+ ḟ f. 1E1F 7711 LATIN SMALL LETTER F WITH DOT ABOVE
+ Ḡ G- 1E20 7712 LATIN CAPITAL LETTER G WITH MACRON
+ ḡ g- 1E21 7713 LATIN SMALL LETTER G WITH MACRON
+ Ḣ H. 1E22 7714 LATIN CAPITAL LETTER H WITH DOT ABOVE
+ ḣ h. 1E23 7715 LATIN SMALL LETTER H WITH DOT ABOVE
+ Ḧ H: 1E26 7718 LATIN CAPITAL LETTER H WITH DIAERESIS
+ ḧ h: 1E27 7719 LATIN SMALL LETTER H WITH DIAERESIS
+ Ḩ H, 1E28 7720 LATIN CAPITAL LETTER H WITH CEDILLA
+ ḩ h, 1E29 7721 LATIN SMALL LETTER H WITH CEDILLA
+ Ḱ K' 1E30 7728 LATIN CAPITAL LETTER K WITH ACUTE
+ ḱ k' 1E31 7729 LATIN SMALL LETTER K WITH ACUTE
+ Ḵ K_ 1E34 7732 LATIN CAPITAL LETTER K WITH LINE BELOW
+ ḵ k_ 1E35 7733 LATIN SMALL LETTER K WITH LINE BELOW
+ Ḻ L_ 1E3A 7738 LATIN CAPITAL LETTER L WITH LINE BELOW
+ ḻ l_ 1E3B 7739 LATIN SMALL LETTER L WITH LINE BELOW
+ Ḿ M' 1E3E 7742 LATIN CAPITAL LETTER M WITH ACUTE
+ ḿ m' 1E3F 7743 LATIN SMALL LETTER M WITH ACUTE
+ Ṁ M. 1E40 7744 LATIN CAPITAL LETTER M WITH DOT ABOVE
+ ṁ m. 1E41 7745 LATIN SMALL LETTER M WITH DOT ABOVE
+ Ṅ N. 1E44 7748 LATIN CAPITAL LETTER N WITH DOT ABOVE `
+ ṅ n. 1E45 7749 LATIN SMALL LETTER N WITH DOT ABOVE `
+ Ṉ N_ 1E48 7752 LATIN CAPITAL LETTER N WITH LINE BELOW `
+ ṉ n_ 1E49 7753 LATIN SMALL LETTER N WITH LINE BELOW `
+ Ṕ P' 1E54 7764 LATIN CAPITAL LETTER P WITH ACUTE
+ ṕ p' 1E55 7765 LATIN SMALL LETTER P WITH ACUTE
+ Ṗ P. 1E56 7766 LATIN CAPITAL LETTER P WITH DOT ABOVE
+ ṗ p. 1E57 7767 LATIN SMALL LETTER P WITH DOT ABOVE
+ Ṙ R. 1E58 7768 LATIN CAPITAL LETTER R WITH DOT ABOVE
+ ṙ r. 1E59 7769 LATIN SMALL LETTER R WITH DOT ABOVE
+ Ṟ R_ 1E5E 7774 LATIN CAPITAL LETTER R WITH LINE BELOW
+ ṟ r_ 1E5F 7775 LATIN SMALL LETTER R WITH LINE BELOW
+ Ṡ S. 1E60 7776 LATIN CAPITAL LETTER S WITH DOT ABOVE
+ ṡ s. 1E61 7777 LATIN SMALL LETTER S WITH DOT ABOVE
+ Ṫ T. 1E6A 7786 LATIN CAPITAL LETTER T WITH DOT ABOVE
+ ṫ t. 1E6B 7787 LATIN SMALL LETTER T WITH DOT ABOVE
+ Ṯ T_ 1E6E 7790 LATIN CAPITAL LETTER T WITH LINE BELOW
+ ṯ t_ 1E6F 7791 LATIN SMALL LETTER T WITH LINE BELOW
+ Ṽ V? 1E7C 7804 LATIN CAPITAL LETTER V WITH TILDE
+ ṽ v? 1E7D 7805 LATIN SMALL LETTER V WITH TILDE
+ Ẁ W! 1E80 7808 LATIN CAPITAL LETTER W WITH GRAVE
+ ẁ w! 1E81 7809 LATIN SMALL LETTER W WITH GRAVE
+ Ẃ W' 1E82 7810 LATIN CAPITAL LETTER W WITH ACUTE
+ ẃ w' 1E83 7811 LATIN SMALL LETTER W WITH ACUTE
+ Ẅ W: 1E84 7812 LATIN CAPITAL LETTER W WITH DIAERESIS
+ ẅ w: 1E85 7813 LATIN SMALL LETTER W WITH DIAERESIS
+ Ẇ W. 1E86 7814 LATIN CAPITAL LETTER W WITH DOT ABOVE
+ ẇ w. 1E87 7815 LATIN SMALL LETTER W WITH DOT ABOVE
+ Ẋ X. 1E8A 7818 LATIN CAPITAL LETTER X WITH DOT ABOVE
+ ẋ x. 1E8B 7819 LATIN SMALL LETTER X WITH DOT ABOVE
+ Ẍ X: 1E8C 7820 LATIN CAPITAL LETTER X WITH DIAERESIS
+ ẍ x: 1E8D 7821 LATIN SMALL LETTER X WITH DIAERESIS
+ Ẏ Y. 1E8E 7822 LATIN CAPITAL LETTER Y WITH DOT ABOVE
+ ẏ y. 1E8F 7823 LATIN SMALL LETTER Y WITH DOT ABOVE
+ Ẑ Z> 1E90 7824 LATIN CAPITAL LETTER Z WITH CIRCUMFLEX
+ ẑ z> 1E91 7825 LATIN SMALL LETTER Z WITH CIRCUMFLEX
+ Ẕ Z_ 1E94 7828 LATIN CAPITAL LETTER Z WITH LINE BELOW
+ ẕ z_ 1E95 7829 LATIN SMALL LETTER Z WITH LINE BELOW
+ ẖ h_ 1E96 7830 LATIN SMALL LETTER H WITH LINE BELOW
+ ẗ t: 1E97 7831 LATIN SMALL LETTER T WITH DIAERESIS
+ ẘ w0 1E98 7832 LATIN SMALL LETTER W WITH RING ABOVE
+ ẙ y0 1E99 7833 LATIN SMALL LETTER Y WITH RING ABOVE
+ Ả A2 1EA2 7842 LATIN CAPITAL LETTER A WITH HOOK ABOVE
+ ả a2 1EA3 7843 LATIN SMALL LETTER A WITH HOOK ABOVE
+ Ẻ E2 1EBA 7866 LATIN CAPITAL LETTER E WITH HOOK ABOVE
+ ẻ e2 1EBB 7867 LATIN SMALL LETTER E WITH HOOK ABOVE
+ Ẽ E? 1EBC 7868 LATIN CAPITAL LETTER E WITH TILDE
+ ẽ e? 1EBD 7869 LATIN SMALL LETTER E WITH TILDE
+ Ỉ I2 1EC8 7880 LATIN CAPITAL LETTER I WITH HOOK ABOVE
+ ỉ i2 1EC9 7881 LATIN SMALL LETTER I WITH HOOK ABOVE
+ Ỏ O2 1ECE 7886 LATIN CAPITAL LETTER O WITH HOOK ABOVE
+ ỏ o2 1ECF 7887 LATIN SMALL LETTER O WITH HOOK ABOVE
+ Ủ U2 1EE6 7910 LATIN CAPITAL LETTER U WITH HOOK ABOVE
+ ủ u2 1EE7 7911 LATIN SMALL LETTER U WITH HOOK ABOVE
+ Ỳ Y! 1EF2 7922 LATIN CAPITAL LETTER Y WITH GRAVE
+ ỳ y! 1EF3 7923 LATIN SMALL LETTER Y WITH GRAVE
+ Ỷ Y2 1EF6 7926 LATIN CAPITAL LETTER Y WITH HOOK ABOVE
+ ỷ y2 1EF7 7927 LATIN SMALL LETTER Y WITH HOOK ABOVE
+ Ỹ Y? 1EF8 7928 LATIN CAPITAL LETTER Y WITH TILDE
+ ỹ y? 1EF9 7929 LATIN SMALL LETTER Y WITH TILDE
+ ἀ ;' 1F00 7936 GREEK SMALL LETTER ALPHA WITH PSILI
+ ἁ ,' 1F01 7937 GREEK SMALL LETTER ALPHA WITH DASIA
+ ἂ ;! 1F02 7938 GREEK SMALL LETTER ALPHA WITH PSILI AND VARIA
+ ἃ ,! 1F03 7939 GREEK SMALL LETTER ALPHA WITH DASIA AND VARIA
+ ἄ ?; 1F04 7940 GREEK SMALL LETTER ALPHA WITH PSILI AND OXIA
+ ἅ ?, 1F05 7941 GREEK SMALL LETTER ALPHA WITH DASIA AND OXIA
+ ἆ !: 1F06 7942 GREEK SMALL LETTER ALPHA WITH PSILI AND PERISPOMENI
+ ἇ ?: 1F07 7943 GREEK SMALL LETTER ALPHA WITH DASIA AND PERISPOMENI
+   1N 2002 8194 EN SPACE
+   1M 2003 8195 EM SPACE
+   3M 2004 8196 THREE-PER-EM SPACE
+   4M 2005 8197 FOUR-PER-EM SPACE
+   6M 2006 8198 SIX-PER-EM SPACE
+   1T 2009 8201 THIN SPACE
+   1H 200A 8202 HAIR SPACE
+ ‐ -1 2010 8208 HYPHEN
+ – -N 2013 8211 EN DASH `
+ — -M 2014 8212 EM DASH
+ ― -3 2015 8213 HORIZONTAL BAR
+ ‖ !2 2016 8214 DOUBLE VERTICAL LINE
+ ‗ =2 2017 8215 DOUBLE LOW LINE
+ ‘ '6 2018 8216 LEFT SINGLE QUOTATION MARK
+ ’ '9 2019 8217 RIGHT SINGLE QUOTATION MARK
+ ‚ .9 201A 8218 SINGLE LOW-9 QUOTATION MARK
+ ‛ 9' 201B 8219 SINGLE HIGH-REVERSED-9 QUOTATION MARK
+ “ "6 201C 8220 LEFT DOUBLE QUOTATION MARK
+ ” "9 201D 8221 RIGHT DOUBLE QUOTATION MARK
+ „ :9 201E 8222 DOUBLE LOW-9 QUOTATION MARK
+ ‟ 9" 201F 8223 DOUBLE HIGH-REVERSED-9 QUOTATION MARK
+ † /- 2020 8224 DAGGER
+ ‡ /= 2021 8225 DOUBLE DAGGER
+ • oo 2022 8226 BULLET
+ ‥ .. 2025 8229 TWO DOT LEADER
+ … ,. 2026 8230 HORIZONTAL ELLIPSIS
+ ‰ %0 2030 8240 PER MILLE SIGN
+ ′ 1' 2032 8242 PRIME
+ ″ 2' 2033 8243 DOUBLE PRIME
+ ‴ 3' 2034 8244 TRIPLE PRIME
+ ⁗ 4' 2057 8279 QUADRUPLE PRIME
+ ‵ 1" 2035 8245 REVERSED PRIME
+ ‶ 2" 2036 8246 REVERSED DOUBLE PRIME
+ ‷ 3" 2037 8247 REVERSED TRIPLE PRIME
+ ‸ Ca 2038 8248 CARET
+ ‹ <1 2039 8249 SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+ › >1 203A 8250 SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+ ※ :X 203B 8251 REFERENCE MARK
+ ‾ '- 203E 8254 OVERLINE
+ ⁄ /f 2044 8260 FRACTION SLASH
+ ⁰ 0S 2070 8304 SUPERSCRIPT ZERO
+ ⁴ 4S 2074 8308 SUPERSCRIPT FOUR
+ ⁵ 5S 2075 8309 SUPERSCRIPT FIVE
+ ⁶ 6S 2076 8310 SUPERSCRIPT SIX
+ ⁷ 7S 2077 8311 SUPERSCRIPT SEVEN
+ ⁸ 8S 2078 8312 SUPERSCRIPT EIGHT
+ ⁹ 9S 2079 8313 SUPERSCRIPT NINE
+ ⁺ +S 207A 8314 SUPERSCRIPT PLUS SIGN
+ ⁻ -S 207B 8315 SUPERSCRIPT MINUS
+ ⁼ =S 207C 8316 SUPERSCRIPT EQUALS SIGN
+ ⁽ (S 207D 8317 SUPERSCRIPT LEFT PARENTHESIS
+ ⁾ )S 207E 8318 SUPERSCRIPT RIGHT PARENTHESIS
+ ⁿ nS 207F 8319 SUPERSCRIPT LATIN SMALL LETTER N `
+ ₀ 0s 2080 8320 SUBSCRIPT ZERO
+ ₁ 1s 2081 8321 SUBSCRIPT ONE
+ ₂ 2s 2082 8322 SUBSCRIPT TWO
+ ₃ 3s 2083 8323 SUBSCRIPT THREE
+ ₄ 4s 2084 8324 SUBSCRIPT FOUR
+ ₅ 5s 2085 8325 SUBSCRIPT FIVE
+ ₆ 6s 2086 8326 SUBSCRIPT SIX
+ ₇ 7s 2087 8327 SUBSCRIPT SEVEN
+ ₈ 8s 2088 8328 SUBSCRIPT EIGHT
+ ₉ 9s 2089 8329 SUBSCRIPT NINE
+ ₊ +s 208A 8330 SUBSCRIPT PLUS SIGN
+ ₋ -s 208B 8331 SUBSCRIPT MINUS
+ ₌ =s 208C 8332 SUBSCRIPT EQUALS SIGN
+ ₍ (s 208D 8333 SUBSCRIPT LEFT PARENTHESIS
+ ₎ )s 208E 8334 SUBSCRIPT RIGHT PARENTHESIS
+ ₤ Li 20A4 8356 LIRA SIGN
+ ₧ Pt 20A7 8359 PESETA SIGN
+ ₩ W= 20A9 8361 WON SIGN
+ € Eu 20AC 8364 EURO SIGN
+ ₽ =R 20BD 8381 ROUBLE SIGN
+ ₽ =P 20BD 8381 ROUBLE SIGN
+ ℃ oC 2103 8451 DEGREE CELSIUS
+ ℅ co 2105 8453 CARE OF
+ ℉ oF 2109 8457 DEGREE FAHRENHEIT
+ № N0 2116 8470 NUMERO SIGN
+ ℗ PO 2117 8471 SOUND RECORDING COPYRIGHT
+ ℞ Rx 211E 8478 PRESCRIPTION TAKE
+ ℠ SM 2120 8480 SERVICE MARK
+ ™ TM 2122 8482 TRADE MARK SIGN
+ Ω Om 2126 8486 OHM SIGN
+ Å AO 212B 8491 ANGSTROM SIGN
+ ⅓ 13 2153 8531 VULGAR FRACTION ONE THIRD
+ ⅔ 23 2154 8532 VULGAR FRACTION TWO THIRDS
+ ⅕ 15 2155 8533 VULGAR FRACTION ONE FIFTH
+ ⅖ 25 2156 8534 VULGAR FRACTION TWO FIFTHS
+ ⅗ 35 2157 8535 VULGAR FRACTION THREE FIFTHS
+ ⅘ 45 2158 8536 VULGAR FRACTION FOUR FIFTHS
+ ⅙ 16 2159 8537 VULGAR FRACTION ONE SIXTH
+ ⅚ 56 215A 8538 VULGAR FRACTION FIVE SIXTHS
+ ⅛ 18 215B 8539 VULGAR FRACTION ONE EIGHTH
+ ⅜ 38 215C 8540 VULGAR FRACTION THREE EIGHTHS
+ ⅝ 58 215D 8541 VULGAR FRACTION FIVE EIGHTHS
+ ⅞ 78 215E 8542 VULGAR FRACTION SEVEN EIGHTHS
+ Ⅰ 1R 2160 8544 ROMAN NUMERAL ONE
+ Ⅱ 2R 2161 8545 ROMAN NUMERAL TWO
+ Ⅲ 3R 2162 8546 ROMAN NUMERAL THREE
+ Ⅳ 4R 2163 8547 ROMAN NUMERAL FOUR
+ Ⅴ 5R 2164 8548 ROMAN NUMERAL FIVE
+ Ⅵ 6R 2165 8549 ROMAN NUMERAL SIX
+ Ⅶ 7R 2166 8550 ROMAN NUMERAL SEVEN
+ Ⅷ 8R 2167 8551 ROMAN NUMERAL EIGHT
+ Ⅸ 9R 2168 8552 ROMAN NUMERAL NINE
+ Ⅹ aR 2169 8553 ROMAN NUMERAL TEN
+ Ⅺ bR 216A 8554 ROMAN NUMERAL ELEVEN
+ Ⅻ cR 216B 8555 ROMAN NUMERAL TWELVE
+ ⅰ 1r 2170 8560 SMALL ROMAN NUMERAL ONE
+ ⅱ 2r 2171 8561 SMALL ROMAN NUMERAL TWO
+ ⅲ 3r 2172 8562 SMALL ROMAN NUMERAL THREE
+ ⅳ 4r 2173 8563 SMALL ROMAN NUMERAL FOUR
+ ⅴ 5r 2174 8564 SMALL ROMAN NUMERAL FIVE
+ ⅵ 6r 2175 8565 SMALL ROMAN NUMERAL SIX
+ ⅶ 7r 2176 8566 SMALL ROMAN NUMERAL SEVEN
+ ⅷ 8r 2177 8567 SMALL ROMAN NUMERAL EIGHT
+ ⅸ 9r 2178 8568 SMALL ROMAN NUMERAL NINE
+ ⅹ ar 2179 8569 SMALL ROMAN NUMERAL TEN
+ ⅺ br 217A 8570 SMALL ROMAN NUMERAL ELEVEN
+ ⅻ cr 217B 8571 SMALL ROMAN NUMERAL TWELVE
+ ← <- 2190 8592 LEFTWARDS ARROW
+ ↑ -! 2191 8593 UPWARDS ARROW
+ → -> 2192 8594 RIGHTWARDS ARROW
+ ↓ -v 2193 8595 DOWNWARDS ARROW
+ ↔ <> 2194 8596 LEFT RIGHT ARROW
+ ↕ UD 2195 8597 UP DOWN ARROW
+ ⇐ <= 21D0 8656 LEFTWARDS DOUBLE ARROW
+ ⇒ => 21D2 8658 RIGHTWARDS DOUBLE ARROW
+ ⇔ == 21D4 8660 LEFT RIGHT DOUBLE ARROW
+ ∀ FA 2200 8704 FOR ALL
+ ∂ dP 2202 8706 PARTIAL DIFFERENTIAL
+ ∃ TE 2203 8707 THERE EXISTS
+ ∅ /0 2205 8709 EMPTY SET
+ ∆ DE 2206 8710 INCREMENT
+ ∇ NB 2207 8711 NABLA
+ ∈ (- 2208 8712 ELEMENT OF
+ ∋ -) 220B 8715 CONTAINS AS MEMBER
+ ∏ *P 220F 8719 N-ARY PRODUCT `
+ ∑ +Z 2211 8721 N-ARY SUMMATION `
+ − -2 2212 8722 MINUS SIGN
+ ∓ -+ 2213 8723 MINUS-OR-PLUS SIGN
+ ∗ *- 2217 8727 ASTERISK OPERATOR
+ ∘ Ob 2218 8728 RING OPERATOR
+ ∙ Sb 2219 8729 BULLET OPERATOR
+ √ RT 221A 8730 SQUARE ROOT
+ ∝ 0( 221D 8733 PROPORTIONAL TO
+ ∞ 00 221E 8734 INFINITY
+ ∟ -L 221F 8735 RIGHT ANGLE
+ ∠ -V 2220 8736 ANGLE
+ ∥ PP 2225 8741 PARALLEL TO
+ ∧ AN 2227 8743 LOGICAL AND
+ ∨ OR 2228 8744 LOGICAL OR
+ ∩ (U 2229 8745 INTERSECTION
+ ∪ )U 222A 8746 UNION
+ ∫ In 222B 8747 INTEGRAL
+ ∬ DI 222C 8748 DOUBLE INTEGRAL
+ ∮ Io 222E 8750 CONTOUR INTEGRAL
+ ∴ .: 2234 8756 THEREFORE
+ ∵ :. 2235 8757 BECAUSE
+ ∶ :R 2236 8758 RATIO
+ ∷ :: 2237 8759 PROPORTION
+ ∼ ?1 223C 8764 TILDE OPERATOR
+ ∾ CG 223E 8766 INVERTED LAZY S
+ ≃ ?- 2243 8771 ASYMPTOTICALLY EQUAL TO
+ ≅ ?= 2245 8773 APPROXIMATELY EQUAL TO
+ ≈ ?2 2248 8776 ALMOST EQUAL TO
+ ≌ =? 224C 8780 ALL EQUAL TO
+ ≓ HI 2253 8787 IMAGE OF OR APPROXIMATELY EQUAL TO
+ ≠ != 2260 8800 NOT EQUAL TO
+ ≡ =3 2261 8801 IDENTICAL TO
+ ≤ =< 2264 8804 LESS-THAN OR EQUAL TO
+ ≥ >= 2265 8805 GREATER-THAN OR EQUAL TO
+ ≪ <* 226A 8810 MUCH LESS-THAN
+ ≫ *> 226B 8811 MUCH GREATER-THAN
+ ≮ !< 226E 8814 NOT LESS-THAN
+ ≯ !> 226F 8815 NOT GREATER-THAN
+ ⊂ (C 2282 8834 SUBSET OF
+ ⊃ )C 2283 8835 SUPERSET OF
+ ⊆ (_ 2286 8838 SUBSET OF OR EQUAL TO
+ ⊇ )_ 2287 8839 SUPERSET OF OR EQUAL TO
+ ⊙ 0. 2299 8857 CIRCLED DOT OPERATOR
+ ⊚ 02 229A 8858 CIRCLED RING OPERATOR
+ ⊥ -T 22A5 8869 UP TACK
+ ⋅ .P 22C5 8901 DOT OPERATOR
+ ⋮ :3 22EE 8942 VERTICAL ELLIPSIS
+ ⋯ .3 22EF 8943 MIDLINE HORIZONTAL ELLIPSIS
+ ⌂ Eh 2302 8962 HOUSE
+ ⌈ <7 2308 8968 LEFT CEILING
+ ⌉ >7 2309 8969 RIGHT CEILING
+ ⌊ 7< 230A 8970 LEFT FLOOR
+ ⌋ 7> 230B 8971 RIGHT FLOOR
+ ⌐ NI 2310 8976 REVERSED NOT SIGN
+ ⌒ (A 2312 8978 ARC
+ ⌕ TR 2315 8981 TELEPHONE RECORDER
+ ⌠ Iu 2320 8992 TOP HALF INTEGRAL
+ ⌡ Il 2321 8993 BOTTOM HALF INTEGRAL
+ 〈 </ 2329 9001 LEFT-POINTING ANGLE BRACKET
+ 〉 /> 232A 9002 RIGHT-POINTING ANGLE BRACKET
+ ␣ Vs 2423 9251 OPEN BOX
+ ⑀ 1h 2440 9280 OCR HOOK
+ ⑁ 3h 2441 9281 OCR CHAIR
+ ⑂ 2h 2442 9282 OCR FORK
+ ⑃ 4h 2443 9283 OCR INVERTED FORK
+ ⑆ 1j 2446 9286 OCR BRANCH BANK IDENTIFICATION
+ ⑇ 2j 2447 9287 OCR AMOUNT OF CHECK
+ ⑈ 3j 2448 9288 OCR DASH
+ ⑉ 4j 2449 9289 OCR CUSTOMER ACCOUNT NUMBER
+ ⒈ 1. 2488 9352 DIGIT ONE FULL STOP
+ ⒉ 2. 2489 9353 DIGIT TWO FULL STOP
+ ⒊ 3. 248A 9354 DIGIT THREE FULL STOP
+ ⒋ 4. 248B 9355 DIGIT FOUR FULL STOP
+ ⒌ 5. 248C 9356 DIGIT FIVE FULL STOP
+ ⒍ 6. 248D 9357 DIGIT SIX FULL STOP
+ ⒎ 7. 248E 9358 DIGIT SEVEN FULL STOP
+ ⒏ 8. 248F 9359 DIGIT EIGHT FULL STOP
+ ⒐ 9. 2490 9360 DIGIT NINE FULL STOP
+ ─ hh 2500 9472 BOX DRAWINGS LIGHT HORIZONTAL
+ ━ HH 2501 9473 BOX DRAWINGS HEAVY HORIZONTAL
+ │ vv 2502 9474 BOX DRAWINGS LIGHT VERTICAL
+ ┃ VV 2503 9475 BOX DRAWINGS HEAVY VERTICAL
+ ┄ 3- 2504 9476 BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL
+ ┅ 3_ 2505 9477 BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL
+ ┆ 3! 2506 9478 BOX DRAWINGS LIGHT TRIPLE DASH VERTICAL
+ ┇ 3/ 2507 9479 BOX DRAWINGS HEAVY TRIPLE DASH VERTICAL
+ ┈ 4- 2508 9480 BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL
+ ┉ 4_ 2509 9481 BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL
+ ┊ 4! 250A 9482 BOX DRAWINGS LIGHT QUADRUPLE DASH VERTICAL
+ ┋ 4/ 250B 9483 BOX DRAWINGS HEAVY QUADRUPLE DASH VERTICAL
+ ┌ dr 250C 9484 BOX DRAWINGS LIGHT DOWN AND RIGHT
+ ┍ dR 250D 9485 BOX DRAWINGS DOWN LIGHT AND RIGHT HEAVY
+ ┎ Dr 250E 9486 BOX DRAWINGS DOWN HEAVY AND RIGHT LIGHT
+ ┏ DR 250F 9487 BOX DRAWINGS HEAVY DOWN AND RIGHT
+ ┐ dl 2510 9488 BOX DRAWINGS LIGHT DOWN AND LEFT
+ ┑ dL 2511 9489 BOX DRAWINGS DOWN LIGHT AND LEFT HEAVY
+ ┒ Dl 2512 9490 BOX DRAWINGS DOWN HEAVY AND LEFT LIGHT
+ ┓ LD 2513 9491 BOX DRAWINGS HEAVY DOWN AND LEFT
+ └ ur 2514 9492 BOX DRAWINGS LIGHT UP AND RIGHT
+ ┕ uR 2515 9493 BOX DRAWINGS UP LIGHT AND RIGHT HEAVY
+ ┖ Ur 2516 9494 BOX DRAWINGS UP HEAVY AND RIGHT LIGHT
+ ┗ UR 2517 9495 BOX DRAWINGS HEAVY UP AND RIGHT
+ ┘ ul 2518 9496 BOX DRAWINGS LIGHT UP AND LEFT
+ ┙ uL 2519 9497 BOX DRAWINGS UP LIGHT AND LEFT HEAVY
+ ┚ Ul 251A 9498 BOX DRAWINGS UP HEAVY AND LEFT LIGHT
+ ┛ UL 251B 9499 BOX DRAWINGS HEAVY UP AND LEFT
+ ├ vr 251C 9500 BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ ┝ vR 251D 9501 BOX DRAWINGS VERTICAL LIGHT AND RIGHT HEAVY
+ ┠ Vr 2520 9504 BOX DRAWINGS VERTICAL HEAVY AND RIGHT LIGHT
+ ┣ VR 2523 9507 BOX DRAWINGS HEAVY VERTICAL AND RIGHT
+ ┤ vl 2524 9508 BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ ┥ vL 2525 9509 BOX DRAWINGS VERTICAL LIGHT AND LEFT HEAVY
+ ┨ Vl 2528 9512 BOX DRAWINGS VERTICAL HEAVY AND LEFT LIGHT
+ ┫ VL 252B 9515 BOX DRAWINGS HEAVY VERTICAL AND LEFT
+ ┬ dh 252C 9516 BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ ┯ dH 252F 9519 BOX DRAWINGS DOWN LIGHT AND HORIZONTAL HEAVY
+ ┰ Dh 2530 9520 BOX DRAWINGS DOWN HEAVY AND HORIZONTAL LIGHT
+ ┳ DH 2533 9523 BOX DRAWINGS HEAVY DOWN AND HORIZONTAL
+ ┴ uh 2534 9524 BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ ┷ uH 2537 9527 BOX DRAWINGS UP LIGHT AND HORIZONTAL HEAVY
+ ┸ Uh 2538 9528 BOX DRAWINGS UP HEAVY AND HORIZONTAL LIGHT
+ ┻ UH 253B 9531 BOX DRAWINGS HEAVY UP AND HORIZONTAL
+ ┼ vh 253C 9532 BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ ┿ vH 253F 9535 BOX DRAWINGS VERTICAL LIGHT AND HORIZONTAL HEAVY
+ ╂ Vh 2542 9538 BOX DRAWINGS VERTICAL HEAVY AND HORIZONTAL LIGHT
+ ╋ VH 254B 9547 BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL
+ ╱ FD 2571 9585 BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
+ ╲ BD 2572 9586 BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
+ ▀ TB 2580 9600 UPPER HALF BLOCK
+ ▄ LB 2584 9604 LOWER HALF BLOCK
+ █ FB 2588 9608 FULL BLOCK
+ ▌ lB 258C 9612 LEFT HALF BLOCK
+ ▐ RB 2590 9616 RIGHT HALF BLOCK
+ ░ .S 2591 9617 LIGHT SHADE
+ ▒ :S 2592 9618 MEDIUM SHADE
+ ▓ ?S 2593 9619 DARK SHADE
+ ■ fS 25A0 9632 BLACK SQUARE
+ □ OS 25A1 9633 WHITE SQUARE
+ ▢ RO 25A2 9634 WHITE SQUARE WITH ROUNDED CORNERS
+ ▣ Rr 25A3 9635 WHITE SQUARE CONTAINING BLACK SMALL SQUARE
+ ▤ RF 25A4 9636 SQUARE WITH HORIZONTAL FILL
+ ▥ RY 25A5 9637 SQUARE WITH VERTICAL FILL
+ ▦ RH 25A6 9638 SQUARE WITH ORTHOGONAL CROSSHATCH FILL
+ ▧ RZ 25A7 9639 SQUARE WITH UPPER LEFT TO LOWER RIGHT FILL
+ ▨ RK 25A8 9640 SQUARE WITH UPPER RIGHT TO LOWER LEFT FILL
+ ▩ RX 25A9 9641 SQUARE WITH DIAGONAL CROSSHATCH FILL
+ ▪ sB 25AA 9642 BLACK SMALL SQUARE
+ ▬ SR 25AC 9644 BLACK RECTANGLE
+ ▭ Or 25AD 9645 WHITE RECTANGLE
+ ▲ UT 25B2 9650 BLACK UP-POINTING TRIANGLE
+ △ uT 25B3 9651 WHITE UP-POINTING TRIANGLE
+ ▶ PR 25B6 9654 BLACK RIGHT-POINTING TRIANGLE
+ ▷ Tr 25B7 9655 WHITE RIGHT-POINTING TRIANGLE
+ ▼ Dt 25BC 9660 BLACK DOWN-POINTING TRIANGLE
+ ▽ dT 25BD 9661 WHITE DOWN-POINTING TRIANGLE
+ ◀ PL 25C0 9664 BLACK LEFT-POINTING TRIANGLE
+ ◁ Tl 25C1 9665 WHITE LEFT-POINTING TRIANGLE
+ ◆ Db 25C6 9670 BLACK DIAMOND
+ ◇ Dw 25C7 9671 WHITE DIAMOND
+ ◊ LZ 25CA 9674 LOZENGE
+ ○ 0m 25CB 9675 WHITE CIRCLE
+ ◎ 0o 25CE 9678 BULLSEYE
+ ● 0M 25CF 9679 BLACK CIRCLE
+ ◐ 0L 25D0 9680 CIRCLE WITH LEFT HALF BLACK
+ ◑ 0R 25D1 9681 CIRCLE WITH RIGHT HALF BLACK
+ ◘ Sn 25D8 9688 INVERSE BULLET
+ ◙ Ic 25D9 9689 INVERSE WHITE CIRCLE
+ ◢ Fd 25E2 9698 BLACK LOWER RIGHT TRIANGLE
+ ◣ Bd 25E3 9699 BLACK LOWER LEFT TRIANGLE
+ ★ *2 2605 9733 BLACK STAR
+ ☆ *1 2606 9734 WHITE STAR
+ ☜ <H 261C 9756 WHITE LEFT POINTING INDEX
+ ☞ >H 261E 9758 WHITE RIGHT POINTING INDEX
+ ☺ 0u 263A 9786 WHITE SMILING FACE
+ ☻ 0U 263B 9787 BLACK SMILING FACE
+ ☼ SU 263C 9788 WHITE SUN WITH RAYS
+ ♀ Fm 2640 9792 FEMALE SIGN
+ ♂ Ml 2642 9794 MALE SIGN
+ ♠ cS 2660 9824 BLACK SPADE SUIT
+ ♡ cH 2661 9825 WHITE HEART SUIT
+ ♢ cD 2662 9826 WHITE DIAMOND SUIT
+ ♣ cC 2663 9827 BLACK CLUB SUIT
+ ♩ Md 2669 9833 QUARTER NOTE `
+ ♪ M8 266A 9834 EIGHTH NOTE `
+ ♫ M2 266B 9835 BEAMED EIGHTH NOTES
+ ♭ Mb 266D 9837 MUSIC FLAT SIGN
+ ♮ Mx 266E 9838 MUSIC NATURAL SIGN
+ ♯ MX 266F 9839 MUSIC SHARP SIGN
+ ✓ OK 2713 10003 CHECK MARK
+ ✗ XX 2717 10007 BALLOT X
+ ✠ -X 2720 10016 MALTESE CROSS
+   IS 3000 12288 IDEOGRAPHIC SPACE
+ 、 ,_ 3001 12289 IDEOGRAPHIC COMMA
+ 。 ._ 3002 12290 IDEOGRAPHIC FULL STOP
+ 〃 +" 3003 12291 DITTO MARK
+ 〄 +_ 3004 12292 JAPANESE INDUSTRIAL STANDARD SYMBOL
+ 々 *_ 3005 12293 IDEOGRAPHIC ITERATION MARK
+ 〆 ;_ 3006 12294 IDEOGRAPHIC CLOSING MARK
+ 〇 0_ 3007 12295 IDEOGRAPHIC NUMBER ZERO
+ 《 <+ 300A 12298 LEFT DOUBLE ANGLE BRACKET
+ 》 >+ 300B 12299 RIGHT DOUBLE ANGLE BRACKET
+ 「 <' 300C 12300 LEFT CORNER BRACKET
+ 」 >' 300D 12301 RIGHT CORNER BRACKET
+ 『 <" 300E 12302 LEFT WHITE CORNER BRACKET
+ 』 >" 300F 12303 RIGHT WHITE CORNER BRACKET
+ 【 (" 3010 12304 LEFT BLACK LENTICULAR BRACKET
+ 】 )" 3011 12305 RIGHT BLACK LENTICULAR BRACKET
+ 〒 =T 3012 12306 POSTAL MARK
+ 〓 =_ 3013 12307 GETA MARK
+ 〔 (' 3014 12308 LEFT TORTOISE SHELL BRACKET
+ 〕 )' 3015 12309 RIGHT TORTOISE SHELL BRACKET
+ 〖 (I 3016 12310 LEFT WHITE LENTICULAR BRACKET
+ 〗 )I 3017 12311 RIGHT WHITE LENTICULAR BRACKET
+ 〜 -? 301C 12316 WAVE DASH
+ ぁ A5 3041 12353 HIRAGANA LETTER SMALL A
+ あ a5 3042 12354 HIRAGANA LETTER A
+ ぃ I5 3043 12355 HIRAGANA LETTER SMALL I
+ い i5 3044 12356 HIRAGANA LETTER I
+ ぅ U5 3045 12357 HIRAGANA LETTER SMALL U
+ う u5 3046 12358 HIRAGANA LETTER U
+ ぇ E5 3047 12359 HIRAGANA LETTER SMALL E
+ え e5 3048 12360 HIRAGANA LETTER E
+ ぉ O5 3049 12361 HIRAGANA LETTER SMALL O
+ お o5 304A 12362 HIRAGANA LETTER O
+ か ka 304B 12363 HIRAGANA LETTER KA
+ が ga 304C 12364 HIRAGANA LETTER GA
+ き ki 304D 12365 HIRAGANA LETTER KI
+ ぎ gi 304E 12366 HIRAGANA LETTER GI
+ く ku 304F 12367 HIRAGANA LETTER KU
+ ぐ gu 3050 12368 HIRAGANA LETTER GU
+ け ke 3051 12369 HIRAGANA LETTER KE
+ げ ge 3052 12370 HIRAGANA LETTER GE
+ こ ko 3053 12371 HIRAGANA LETTER KO
+ ご go 3054 12372 HIRAGANA LETTER GO
+ さ sa 3055 12373 HIRAGANA LETTER SA
+ ざ za 3056 12374 HIRAGANA LETTER ZA
+ し si 3057 12375 HIRAGANA LETTER SI
+ じ zi 3058 12376 HIRAGANA LETTER ZI
+ す su 3059 12377 HIRAGANA LETTER SU
+ ず zu 305A 12378 HIRAGANA LETTER ZU
+ せ se 305B 12379 HIRAGANA LETTER SE
+ ぜ ze 305C 12380 HIRAGANA LETTER ZE
+ そ so 305D 12381 HIRAGANA LETTER SO
+ ぞ zo 305E 12382 HIRAGANA LETTER ZO
+ た ta 305F 12383 HIRAGANA LETTER TA
+ だ da 3060 12384 HIRAGANA LETTER DA
+ ち ti 3061 12385 HIRAGANA LETTER TI
+ ぢ di 3062 12386 HIRAGANA LETTER DI
+ っ tU 3063 12387 HIRAGANA LETTER SMALL TU
+ つ tu 3064 12388 HIRAGANA LETTER TU
+ づ du 3065 12389 HIRAGANA LETTER DU
+ て te 3066 12390 HIRAGANA LETTER TE
+ で de 3067 12391 HIRAGANA LETTER DE
+ と to 3068 12392 HIRAGANA LETTER TO
+ ど do 3069 12393 HIRAGANA LETTER DO
+ な na 306A 12394 HIRAGANA LETTER NA
+ に ni 306B 12395 HIRAGANA LETTER NI
+ ぬ nu 306C 12396 HIRAGANA LETTER NU
+ ね ne 306D 12397 HIRAGANA LETTER NE
+ の no 306E 12398 HIRAGANA LETTER NO
+ は ha 306F 12399 HIRAGANA LETTER HA
+ ば ba 3070 12400 HIRAGANA LETTER BA
+ ぱ pa 3071 12401 HIRAGANA LETTER PA
+ ひ hi 3072 12402 HIRAGANA LETTER HI
+ び bi 3073 12403 HIRAGANA LETTER BI
+ ぴ pi 3074 12404 HIRAGANA LETTER PI
+ ふ hu 3075 12405 HIRAGANA LETTER HU
+ ぶ bu 3076 12406 HIRAGANA LETTER BU
+ ぷ pu 3077 12407 HIRAGANA LETTER PU
+ へ he 3078 12408 HIRAGANA LETTER HE
+ べ be 3079 12409 HIRAGANA LETTER BE
+ ぺ pe 307A 12410 HIRAGANA LETTER PE
+ ほ ho 307B 12411 HIRAGANA LETTER HO
+ ぼ bo 307C 12412 HIRAGANA LETTER BO
+ ぽ po 307D 12413 HIRAGANA LETTER PO
+ ま ma 307E 12414 HIRAGANA LETTER MA
+ み mi 307F 12415 HIRAGANA LETTER MI
+ む mu 3080 12416 HIRAGANA LETTER MU
+ め me 3081 12417 HIRAGANA LETTER ME
+ も mo 3082 12418 HIRAGANA LETTER MO
+ ゃ yA 3083 12419 HIRAGANA LETTER SMALL YA
+ や ya 3084 12420 HIRAGANA LETTER YA
+ ゅ yU 3085 12421 HIRAGANA LETTER SMALL YU
+ ゆ yu 3086 12422 HIRAGANA LETTER YU
+ ょ yO 3087 12423 HIRAGANA LETTER SMALL YO
+ よ yo 3088 12424 HIRAGANA LETTER YO
+ ら ra 3089 12425 HIRAGANA LETTER RA
+ り ri 308A 12426 HIRAGANA LETTER RI
+ る ru 308B 12427 HIRAGANA LETTER RU
+ れ re 308C 12428 HIRAGANA LETTER RE
+ ろ ro 308D 12429 HIRAGANA LETTER RO
+ ゎ wA 308E 12430 HIRAGANA LETTER SMALL WA
+ わ wa 308F 12431 HIRAGANA LETTER WA
+ ゐ wi 3090 12432 HIRAGANA LETTER WI
+ ゑ we 3091 12433 HIRAGANA LETTER WE
+ を wo 3092 12434 HIRAGANA LETTER WO
+ ん n5 3093 12435 HIRAGANA LETTER N `
+ ゔ vu 3094 12436 HIRAGANA LETTER VU
+ ゛ "5 309B 12443 KATAKANA-HIRAGANA VOICED SOUND MARK
+ ゜ 05 309C 12444 KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
+ ゝ *5 309D 12445 HIRAGANA ITERATION MARK
+ ゞ +5 309E 12446 HIRAGANA VOICED ITERATION MARK
+ ァ a6 30A1 12449 KATAKANA LETTER SMALL A
+ ア A6 30A2 12450 KATAKANA LETTER A
+ ィ i6 30A3 12451 KATAKANA LETTER SMALL I
+ イ I6 30A4 12452 KATAKANA LETTER I
+ ゥ u6 30A5 12453 KATAKANA LETTER SMALL U
+ ウ U6 30A6 12454 KATAKANA LETTER U
+ ェ e6 30A7 12455 KATAKANA LETTER SMALL E
+ エ E6 30A8 12456 KATAKANA LETTER E
+ ォ o6 30A9 12457 KATAKANA LETTER SMALL O
+ オ O6 30AA 12458 KATAKANA LETTER O
+ カ Ka 30AB 12459 KATAKANA LETTER KA
+ ガ Ga 30AC 12460 KATAKANA LETTER GA
+ キ Ki 30AD 12461 KATAKANA LETTER KI
+ ギ Gi 30AE 12462 KATAKANA LETTER GI
+ ク Ku 30AF 12463 KATAKANA LETTER KU
+ グ Gu 30B0 12464 KATAKANA LETTER GU
+ ケ Ke 30B1 12465 KATAKANA LETTER KE
+ ゲ Ge 30B2 12466 KATAKANA LETTER GE
+ コ Ko 30B3 12467 KATAKANA LETTER KO
+ ゴ Go 30B4 12468 KATAKANA LETTER GO
+ サ Sa 30B5 12469 KATAKANA LETTER SA
+ ザ Za 30B6 12470 KATAKANA LETTER ZA
+ シ Si 30B7 12471 KATAKANA LETTER SI
+ ジ Zi 30B8 12472 KATAKANA LETTER ZI
+ ス Su 30B9 12473 KATAKANA LETTER SU
+ ズ Zu 30BA 12474 KATAKANA LETTER ZU
+ セ Se 30BB 12475 KATAKANA LETTER SE
+ ゼ Ze 30BC 12476 KATAKANA LETTER ZE
+ ソ So 30BD 12477 KATAKANA LETTER SO
+ ゾ Zo 30BE 12478 KATAKANA LETTER ZO
+ タ Ta 30BF 12479 KATAKANA LETTER TA
+ ダ Da 30C0 12480 KATAKANA LETTER DA
+ チ Ti 30C1 12481 KATAKANA LETTER TI
+ ヂ Di 30C2 12482 KATAKANA LETTER DI
+ ッ TU 30C3 12483 KATAKANA LETTER SMALL TU
+ ツ Tu 30C4 12484 KATAKANA LETTER TU
+ ヅ Du 30C5 12485 KATAKANA LETTER DU
+ テ Te 30C6 12486 KATAKANA LETTER TE
+ デ De 30C7 12487 KATAKANA LETTER DE
+ ト To 30C8 12488 KATAKANA LETTER TO
+ ド Do 30C9 12489 KATAKANA LETTER DO
+ ナ Na 30CA 12490 KATAKANA LETTER NA
+ ニ Ni 30CB 12491 KATAKANA LETTER NI
+ ヌ Nu 30CC 12492 KATAKANA LETTER NU
+ ネ Ne 30CD 12493 KATAKANA LETTER NE
+ ノ No 30CE 12494 KATAKANA LETTER NO
+ ハ Ha 30CF 12495 KATAKANA LETTER HA
+ バ Ba 30D0 12496 KATAKANA LETTER BA
+ パ Pa 30D1 12497 KATAKANA LETTER PA
+ ヒ Hi 30D2 12498 KATAKANA LETTER HI
+ ビ Bi 30D3 12499 KATAKANA LETTER BI
+ ピ Pi 30D4 12500 KATAKANA LETTER PI
+ フ Hu 30D5 12501 KATAKANA LETTER HU
+ ブ Bu 30D6 12502 KATAKANA LETTER BU
+ プ Pu 30D7 12503 KATAKANA LETTER PU
+ ヘ He 30D8 12504 KATAKANA LETTER HE
+ ベ Be 30D9 12505 KATAKANA LETTER BE
+ ペ Pe 30DA 12506 KATAKANA LETTER PE
+ ホ Ho 30DB 12507 KATAKANA LETTER HO
+ ボ Bo 30DC 12508 KATAKANA LETTER BO
+ ポ Po 30DD 12509 KATAKANA LETTER PO
+ マ Ma 30DE 12510 KATAKANA LETTER MA
+ ミ Mi 30DF 12511 KATAKANA LETTER MI
+ ム Mu 30E0 12512 KATAKANA LETTER MU
+ メ Me 30E1 12513 KATAKANA LETTER ME
+ モ Mo 30E2 12514 KATAKANA LETTER MO
+ ャ YA 30E3 12515 KATAKANA LETTER SMALL YA
+ ヤ Ya 30E4 12516 KATAKANA LETTER YA
+ ュ YU 30E5 12517 KATAKANA LETTER SMALL YU
+ ユ Yu 30E6 12518 KATAKANA LETTER YU
+ ョ YO 30E7 12519 KATAKANA LETTER SMALL YO
+ ヨ Yo 30E8 12520 KATAKANA LETTER YO
+ ラ Ra 30E9 12521 KATAKANA LETTER RA
+ リ Ri 30EA 12522 KATAKANA LETTER RI
+ ル Ru 30EB 12523 KATAKANA LETTER RU
+ レ Re 30EC 12524 KATAKANA LETTER RE
+ ロ Ro 30ED 12525 KATAKANA LETTER RO
+ ヮ WA 30EE 12526 KATAKANA LETTER SMALL WA
+ ワ Wa 30EF 12527 KATAKANA LETTER WA
+ ヰ Wi 30F0 12528 KATAKANA LETTER WI
+ ヱ We 30F1 12529 KATAKANA LETTER WE
+ ヲ Wo 30F2 12530 KATAKANA LETTER WO
+ ン N6 30F3 12531 KATAKANA LETTER N `
+ ヴ Vu 30F4 12532 KATAKANA LETTER VU
+ ヵ KA 30F5 12533 KATAKANA LETTER SMALL KA
+ ヶ KE 30F6 12534 KATAKANA LETTER SMALL KE
+ ヷ Va 30F7 12535 KATAKANA LETTER VA
+ ヸ Vi 30F8 12536 KATAKANA LETTER VI
+ ヹ Ve 30F9 12537 KATAKANA LETTER VE
+ ヺ Vo 30FA 12538 KATAKANA LETTER VO
+ ・ .6 30FB 12539 KATAKANA MIDDLE DOT
+ ー -6 30FC 12540 KATAKANA-HIRAGANA PROLONGED SOUND MARK
+ ヽ *6 30FD 12541 KATAKANA ITERATION MARK
+ ヾ +6 30FE 12542 KATAKANA VOICED ITERATION MARK
+ ㄅ b4 3105 12549 BOPOMOFO LETTER B
+ ㄆ p4 3106 12550 BOPOMOFO LETTER P
+ ㄇ m4 3107 12551 BOPOMOFO LETTER M
+ ㄈ f4 3108 12552 BOPOMOFO LETTER F
+ ㄉ d4 3109 12553 BOPOMOFO LETTER D
+ ㄊ t4 310A 12554 BOPOMOFO LETTER T
+ ㄋ n4 310B 12555 BOPOMOFO LETTER N `
+ ㄌ l4 310C 12556 BOPOMOFO LETTER L
+ ㄍ g4 310D 12557 BOPOMOFO LETTER G
+ ㄎ k4 310E 12558 BOPOMOFO LETTER K
+ ㄏ h4 310F 12559 BOPOMOFO LETTER H
+ ㄐ j4 3110 12560 BOPOMOFO LETTER J
+ ㄑ q4 3111 12561 BOPOMOFO LETTER Q
+ ㄒ x4 3112 12562 BOPOMOFO LETTER X
+ ㄓ zh 3113 12563 BOPOMOFO LETTER ZH
+ ㄔ ch 3114 12564 BOPOMOFO LETTER CH
+ ㄕ sh 3115 12565 BOPOMOFO LETTER SH
+ ㄖ r4 3116 12566 BOPOMOFO LETTER R
+ ㄗ z4 3117 12567 BOPOMOFO LETTER Z
+ ㄘ c4 3118 12568 BOPOMOFO LETTER C
+ ㄙ s4 3119 12569 BOPOMOFO LETTER S
+ ㄚ a4 311A 12570 BOPOMOFO LETTER A
+ ㄛ o4 311B 12571 BOPOMOFO LETTER O
+ ㄜ e4 311C 12572 BOPOMOFO LETTER E
+ ㄞ ai 311E 12574 BOPOMOFO LETTER AI
+ ㄟ ei 311F 12575 BOPOMOFO LETTER EI
+ ㄠ au 3120 12576 BOPOMOFO LETTER AU
+ ㄡ ou 3121 12577 BOPOMOFO LETTER OU
+ ㄢ an 3122 12578 BOPOMOFO LETTER AN
+ ㄣ en 3123 12579 BOPOMOFO LETTER EN
+ ㄤ aN 3124 12580 BOPOMOFO LETTER ANG
+ ㄥ eN 3125 12581 BOPOMOFO LETTER ENG
+ ㄦ er 3126 12582 BOPOMOFO LETTER ER
+ ㄧ i4 3127 12583 BOPOMOFO LETTER I
+ ㄨ u4 3128 12584 BOPOMOFO LETTER U
+ ㄩ iu 3129 12585 BOPOMOFO LETTER IU
+ ㄪ v4 312A 12586 BOPOMOFO LETTER V
+ ㄫ nG 312B 12587 BOPOMOFO LETTER NG
+ ㄬ gn 312C 12588 BOPOMOFO LETTER GN
+ ㈠ 1c 3220 12832 PARENTHESIZED IDEOGRAPH ONE
+ ㈡ 2c 3221 12833 PARENTHESIZED IDEOGRAPH TWO
+ ㈢ 3c 3222 12834 PARENTHESIZED IDEOGRAPH THREE
+ ㈣ 4c 3223 12835 PARENTHESIZED IDEOGRAPH FOUR
+ ㈤ 5c 3224 12836 PARENTHESIZED IDEOGRAPH FIVE
+ ㈥ 6c 3225 12837 PARENTHESIZED IDEOGRAPH SIX
+ ㈦ 7c 3226 12838 PARENTHESIZED IDEOGRAPH SEVEN
+ ㈧ 8c 3227 12839 PARENTHESIZED IDEOGRAPH EIGHT
+ ㈨ 9c 3228 12840 PARENTHESIZED IDEOGRAPH NINE
+ ff ff FB00 64256 LATIN SMALL LIGATURE FF
+ fi fi FB01 64257 LATIN SMALL LIGATURE FI
+ fl fl FB02 64258 LATIN SMALL LIGATURE FL
+ ſt ft FB05 64261 LATIN SMALL LIGATURE LONG S T
+ st st FB06 64262 LATIN SMALL LIGATURE ST
+<
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt
index f77db5fab3..7df2eb9742 100644
--- a/runtime/doc/editing.txt
+++ b/runtime/doc/editing.txt
@@ -169,33 +169,26 @@ If you want to keep the changed buffer without saving it, switch on the
2. Editing a file *edit-a-file*
*:e* *:edit* *reload*
-:e[dit] [++opt] [+cmd] Edit the current file. This is useful to re-edit the
+:e[dit][!] [++opt] [+cmd]
+ Edit the current file. This is useful to re-edit the
current file, when it has been changed outside of Vim.
- This fails when changes have been made to the current
- buffer and 'autowriteall' isn't set or the file can't
- be written.
- Also see |++opt| and |+cmd|.
-
*:edit!* *discard*
-:e[dit]! [++opt] [+cmd]
- Edit the current file always. Discard any changes to
- the current buffer. This is useful if you want to
- start all over again.
+ If [!] is given, unsaved changes in the current buffer
+ are discarded. Without [!] the command fails if there
+ are unsaved changes, unless 'autowriteall' is set and
+ the file can be written.
Also see |++opt| and |+cmd|.
*:edit_f*
-:e[dit] [++opt] [+cmd] {file}
+:e[dit][!] [++opt] [+cmd] {file}
Edit {file}.
- This fails when changes have been made to the current
- buffer, unless 'hidden' is set or 'autowriteall' is
- set and the file can be written.
- Also see |++opt| and |+cmd|.
-
*:edit!_f*
-:e[dit]! [++opt] [+cmd] {file}
- Edit {file} always. Discard any changes to the
- current buffer.
+ If [!] is given, unsaved changes in the current buffer
+ are discarded. Without [!] the command fails if there
+ are unsaved changes, unless 'hidden' is set or
+ 'autowriteall' is set and the file can be written.
Also see |++opt| and |+cmd|.
+
*:edit_#* *:e#*
:e[dit] [++opt] [+cmd] #[count]
Edit the [count]th buffer (as shown by |:files|).
@@ -356,7 +349,9 @@ as a wildcard when "[" is in the 'isfname' option. A simple way to avoid this
is to use "path\[[]abc]", this matches the file "path\[abc]".
*starstar-wildcard*
-Expanding "**" is possible on Unix, Win32, macOS and a few other systems.
+Expanding "**" is possible on Unix, Win32, macOS and a few other systems (but
+it may depend on your 'shell' setting on Unix and macOS. It's known to work
+correctly for zsh; for bash this requires at least bash version >= 4.X).
This allows searching a directory tree. This goes up to 100 directories deep.
Note there are some commands where this works slightly differently, see
|file-searching|.
@@ -539,10 +534,10 @@ set to "dos", otherwise it is set to "unix". When 'fileformats' includes
"mac".
If the 'fileformat' option is set to "dos" on non-MS-Windows systems the
-message "[dos format]" is shown to remind you that something unusual is
-happening. On MS-Windows systems you get the message "[unix format]" if
-'fileformat' is set to "unix". On all systems but the Macintosh you get the
-message "[mac format]" if 'fileformat' is set to "mac".
+message "[dos]" is shown to remind you that something unusual is happening. On
+MS-Windows systems you get the message "[unix]" if 'fileformat' is set to
+"unix". On all systems you get the message "[mac]" if 'fileformat' is set to
+"mac".
If the 'fileformats' option is empty and DOS format is used, but while reading
a file some lines did not end in <CR><NL>, "[CR missing]" will be included in
@@ -804,8 +799,8 @@ first line (the last line in Ex mode).
*{arglist}*
The wildcards in the argument list are expanded and the file names are sorted.
-Thus you can use the command "vim *.c" to edit all the C files. From within
-Vim the command ":n *.c" does the same.
+Thus you can use the command `vim *.c` to edit all the C files. From within
+Vim the command `:n *.c` does the same.
White space is used to separate file names. Put a backslash before a space or
tab to include it in a file name. E.g., to edit the single file "foo bar": >
@@ -820,12 +815,10 @@ by the shell before executing the find program.
When there is an argument list you can see which file you are editing in the
title of the window (if there is one and 'title' is on) and with the file
message you get with the "CTRL-G" command. You will see something like
- (file 4 of 11)
-If 'shortmess' contains 'f' it will be
(4 of 11)
If you are not really editing the file at the current position in the argument
list it will be
- (file (4) of 11)
+ ((4) of 11)
This means that you are position 4 in the argument list, but not editing the
fourth file in the argument list. This happens when you do ":e file".
@@ -889,7 +882,7 @@ Example: >
:args *.c
:argdo set ff=unix | update
This sets the 'fileformat' option to "unix" and writes the file if it is now
-changed. This is done for all *.c files.
+changed. This is done for all `*.c` files.
Example: >
:args *.[ch]
@@ -1058,14 +1051,14 @@ lost the original file.
*DOS-format-write*
If the 'fileformat' is "dos", <CR><NL> is used for <EOL>. This is default
-for Windows. On other systems the message "[dos format]" is shown to
+for Windows. On other systems the message "[dos]" is shown to
remind you that an unusual <EOL> was used.
*Unix-format-write*
If the 'fileformat' is "unix", <NL> is used for <EOL>. On Windows
-the message "[unix format]" is shown.
+the message "[unix]" is shown.
*Mac-format-write*
-If the 'fileformat' is "mac", <CR> is used for <EOL>. On non-Mac systems the
-message "[mac format]" is shown.
+If the 'fileformat' is "mac", <CR> is used for <EOL>. The
+message "[mac]" is shown.
See also |file-formats| and the 'fileformat' and 'fileformats' options.
@@ -1078,6 +1071,13 @@ will get the ACL info of the original file.
The ACL info is also used to check if a file is read-only (when opening the
file).
+ *xattr* *E1506* *E1508* *E1509*
+xattr stands for Extended Attributes. It is an advanced way to save metadata
+alongside the file in the filesystem. It depends on the actual filesystem
+being used and Vim supports it only on a Linux system.
+ Vim attempts to preserve the extended attribute info when writing a file.
+The backup file will get the extended attribute of the original file.
+
*read-only-share*
When MS-Windows shares a drive on the network it can be marked as read-only.
This means that even if the file read-only attribute is absent, and the ACL
@@ -1217,10 +1217,10 @@ MULTIPLE WINDOWS AND BUFFERS *window-exit*
*:confirm* *:conf*
:conf[irm] {command} Execute {command}, and use a dialog when an
operation has to be confirmed. Can be used on the
- |:q|, |:qa| and |:w| commands (the latter to override
- a read-only setting), and any other command that can
- fail in such a way, such as |:only|, |:buffer|,
- |:bdelete|, etc.
+ |:edit|, |:q|, |:qa| and |:w| commands (the latter to
+ override a read-only setting), and any commands that
+ can fail because of unsaved changes, such as |:only|,
+ |:buffer|, |:bdelete|, etc.
Examples: >
:confirm w foo
@@ -1245,8 +1245,8 @@ If you want to always use ":confirm", set the 'confirm' option.
|:diffsplit|, |:diffpatch|, |:pedit|, |:redir|,
|:source|, |:update|, |:visual|, |:vsplit|,
and |:qall| if 'confirm' is set.
- {only in Win32 GUI, in console `browse edit` works
- if the FileExplorer autocommand group exists}
+ Note: only in Win32 GUI; in console `:browse edit`
+ works if the FileExplorer autocommand group exists.
When ":browse" is not possible you get an error
message. If {command} doesn't support browsing, the
{command} is executed without a dialog.
@@ -1581,7 +1581,7 @@ There are three different types of searching:
so they work on all operating systems. Note that "**" only acts as a
special wildcard when it is at the start of a name.
- The usage of '*' is quite simple: It matches 0 or more characters. In a
+ The usage of "*" is quite simple: It matches 0 or more characters. In a
search pattern this would be ".*". Note that the "." is not used for file
searching.
@@ -1668,29 +1668,26 @@ There are three different types of searching:
==============================================================================
12. Trusted Files *trust*
-Nvim has the ability to execute arbitrary code through the 'exrc' option. In
-order to prevent executing code from untrusted sources, Nvim has the concept of
-"trusted files". An untrusted file will not be executed without the user's
-consent, and a user can permanently mark a file as trusted or untrusted using
-the |:trust| command or the |vim.secure.read()| function.
+Nvim executes arbitrary code found on the filesystem if 'exrc' is enabled. To
+prevent executing malicious code, only "trusted files" are executed. You can
+mark a file as trusted or untrusted using the |:trust| command or the
+|vim.secure.read()| function.
*:trust* *E5570*
-:trust [++deny] [++remove] [{file}]
-
- Manage files in the trust database. Without any options
- or arguments, :trust adds the file associated with the
- current buffer to the trust database, along with the
- SHA256 hash of its contents.
-
- [++deny] marks the file associated with the current
- buffer (or {file}, if given) as denied; no prompts will
- be displayed to the user and the file will never be
- executed.
-
- [++remove] removes the file associated with the current
- buffer (or {file}, if given) from the trust database.
- Future attempts to read the file in a secure setting
- (i.e. with 'exrc' or |vim.secure.read()|) will prompt
- the user if the file is trusted.
+:trust [++deny] [++remove] [file]
+
+ Manage trusted files. Without ++ options, :trust marks
+ the current buffer as trusted, keyed on a hash of its
+ contents. The trust list is stored on disk, Nvim will
+ re-use it after restarting.
+
+ [++deny] marks [file] (or current buffer if no [file]) as
+ untrusted: it will never be executed, 'exrc' will
+ ignore it.
+
+ [++remove] removes [file] (or current buffer if no
+ [file]) from the trust list. When the file is
+ discovered by 'exrc' or |vim.secure.read()|, the user
+ will be asked whether to trust or deny the file.
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/editorconfig.txt b/runtime/doc/editorconfig.txt
index 04a057e5ff..7c6e8fb95f 100644
--- a/runtime/doc/editorconfig.txt
+++ b/runtime/doc/editorconfig.txt
@@ -6,25 +6,21 @@
EditorConfig integration *editorconfig*
-Nvim natively supports EditorConfig. When a file is opened, Nvim searches
-upward through all of the parent directories of that file looking for
-".editorconfig" files. Each of these is parsed and any properties that match
-the opened file are applied.
-
-For more information on EditorConfig, see https://editorconfig.org/.
+Nvim supports EditorConfig. When a file is opened, Nvim searches all parent
+directories of that file for ".editorconfig" files, parses them, and applies
+any properties that match the opened file. Think of it like 'modeline' for an
+entire (recursive) directory. For more information see
+https://editorconfig.org/.
*g:editorconfig* *b:editorconfig*
-EditorConfig integration can be disabled globally by adding >lua
+EditorConfig is enabled by default. To disable it, add to your config: >lua
vim.g.editorconfig = false
<
-to the user's |init.lua| file (or the Vimscript equivalent to |init.vim|). It
-can also be disabled per-buffer by setting the |b:editorconfig| buffer-local
-variable to `false`.
+(Vimscript: `let g:editorconfig = v:false`). It can also be disabled
+per-buffer by setting the |b:editorconfig| buffer-local variable to `false`.
-When Nvim finds a valid .editorconfig file it will store the applied
-properties in the buffer variable |b:editorconfig| if it was not already set to
-`false` by the user.
+Nvim stores the applied properties in |b:editorconfig| if it is not `false`.
*editorconfig-properties*
The following properties are supported by default:
@@ -52,7 +48,7 @@ indent_style One of "tab" or "space". Sets the 'expandtab' option.
indent_size A number indicating the size of a single indent.
Alternatively, use the value "tab" to use the value of
the tab_width property. Sets the 'shiftwidth' and
- 'softtabstop'.
+ 'softtabstop' options.
*editorconfig_insert_final_newline*
insert_final_newline "true" or "false" to ensure the file always has a
@@ -88,4 +84,4 @@ Example: >lua
vim.b[bufnr].foo = val
end
<
- vim:tw=78:ts=8:noet:ft=help:norl:
+ vim:tw=78:ts=8:et:sw=4:ft=help:norl:
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 58759a6053..a73932be00 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -39,7 +39,7 @@ List An ordered sequence of items, see |List| for details.
Dictionary An associative, unordered array: Each entry has a key and a
value. |Dictionary|
- Examples:
+ Examples: >
{"blue": "#0000ff", "red": "#ff0000"}
#{blue: "#0000ff", red: "#ff0000"}
@@ -93,7 +93,27 @@ non-zero number it means TRUE: >
:" executed
To test for a non-empty string, use empty(): >
:if !empty("foo")
-<
+
+< *falsy* *truthy*
+An expression can be used as a condition, ignoring the type and only using
+whether the value is "sort of true" or "sort of false". Falsy is:
+ the number zero
+ empty string, blob, list or dictionary
+Other values are truthy. Examples:
+ 0 falsy
+ 1 truthy
+ -1 truthy
+ 0.0 falsy
+ 0.1 truthy
+ '' falsy
+ 'x' truthy
+ [] falsy
+ [0] truthy
+ {} falsy
+ #{x: 1} truthy
+ 0z falsy
+ 0z00 truthy
+
*non-zero-arg*
Function arguments often behave slightly different from |TRUE|: If the
argument is present and it evaluates to a non-zero Number, |v:true| or a
@@ -119,7 +139,7 @@ You will not get an error if you try to change the type of a variable.
1.2 Function references ~
- *Funcref* *E695* *E718*
+ *Funcref* *E695* *E718* *E1192*
A Funcref variable is obtained with the |function()| function, the |funcref()|
function or created with the lambda expression |expr-lambda|. It can be used
in an expression in the place of a function name, before the parenthesis
@@ -250,6 +270,9 @@ similar to -1. >
:let shortlist = mylist[2:2] " List with one item: [3]
:let otherlist = mylist[:] " make a copy of the List
+Notice that the last index is inclusive. If you prefer using an exclusive
+index use the |slice()| method.
+
If the first index is beyond the last item of the List or the second item is
before the first item, the result is an empty list. There is no error
message.
@@ -818,19 +841,19 @@ Expression syntax summary, from least to most significant:
|expr9| number number constant
"string" string constant, backslash is special
- 'string' string constant, ' is doubled
+ `'string'` string constant, ' is doubled
[expr1, ...] |List|
- {expr1: expr1, ...} |Dictionary|
+ `{expr1: expr1, ...}` |Dictionary|
#{key: expr1, ...} |Dictionary|
&option option value
(expr1) nested expression
variable internal variable
va{ria}ble internal variable with curly braces
$VAR environment variable
- @r contents of register 'r'
+ @r contents of register "r"
function(expr1, ...) function call
func{ti}on(expr1, ...) function call with curly braces
- {args -> expr1} lambda expression
+ `{args -> expr1}` lambda expression
"..." indicates that the operations in this level can be concatenated.
@@ -841,9 +864,12 @@ All expressions within one level are parsed from left to right.
------------------------------------------------------------------------------
-expr1 *expr1* *ternary* *E109*
+expr1 *expr1* *ternary* *falsy-operator* *??* *E109*
+
+The ternary operator: expr2 ? expr1 : expr1
+The falsy operator: expr2 ?? expr1
-expr2 ? expr1 : expr1
+Ternary operator ~
The expression before the '?' is evaluated to a number. If it evaluates to
|TRUE|, the result is the value of the expression between the '?' and ':',
@@ -866,6 +892,23 @@ To keep this readable, using |line-continuation| is suggested: >
You should always put a space before the ':', otherwise it can be mistaken for
use in a variable such as "a:1".
+Falsy operator ~
+
+This is also known as the "null coalescing operator", but that's too
+complicated, thus we just call it the falsy operator.
+
+The expression before the '??' is evaluated. If it evaluates to
+|truthy|, this is used as the result. Otherwise the expression after the '??'
+is evaluated and used as the result. This is most useful to have a default
+value for an expression that may result in zero or empty: >
+ echo theList ?? 'list is empty'
+ echo GetName() ?? 'unknown'
+
+These are similar, but not equal: >
+ expr2 ?? expr1
+ expr2 ? expr2 : expr1
+In the second line "expr2" is evaluated twice.
+
------------------------------------------------------------------------------
expr2 and expr3 *expr2* *expr3*
@@ -1044,7 +1087,7 @@ That works, since the String "190" is automatically converted to the Number
1 . 90 * 90.0
Should be read as: >
1 . (90 * 90.0)
-Since '.' has lower precedence than '*'. This does NOT work, since this
+Since '.' has lower precedence than "*". This does NOT work, since this
attempts to concatenate a Float and a String.
When dividing a Number by zero the result depends on the value:
@@ -1112,6 +1155,8 @@ text column numbers start with one! Example, to get the byte under the
cursor: >
:let c = getline(".")[col(".") - 1]
+Index zero gives the first byte. Careful: text column numbers start with one!
+
If the length of the String is less than the index, the result is an empty
String. A negative index always results in an empty string (reason: backward
compatibility). Use [-1:] to get the last byte.
@@ -1136,6 +1181,9 @@ In legacy Vim script the indexes are byte indexes. This doesn't recognize
multibyte encodings, see |byteidx()| for computing the indexes. If expr8 is
a Number it is first converted to a String.
+The item at index expr1b is included, it is inclusive. For an exclusive index
+use the |slice()| function.
+
If expr1a is omitted zero is used. If expr1b is omitted the length of the
string minus one is used.
@@ -1196,7 +1244,7 @@ Note that the dot is also used for String concatenation. To avoid confusion
always put spaces around the dot for String concatenation.
-expr8(expr1, ...) |Funcref| function call
+expr8(expr1, ...) |Funcref| function call *E1085*
When expr8 is a |Funcref| type variable, invoke the function it refers to.
@@ -1367,6 +1415,60 @@ to be doubled. These two commands are equivalent: >
------------------------------------------------------------------------------
+interpolated-string *$quote* *interpolated-string*
+
+$"string" interpolated string constant *expr-$quote*
+$'string' interpolated literal string constant *expr-$'*
+
+Interpolated strings are an extension of the |string| and |literal-string|,
+allowing the inclusion of Vim script expressions (see |expr1|). Any
+expression returning a value can be enclosed between curly braces. The value
+is converted to a string. All the text and results of the expressions
+are concatenated to make a new string.
+ *E1278*
+To include an opening brace '{' or closing brace '}' in the string content
+double it. For double quoted strings using a backslash also works. A single
+closing brace '}' will result in an error.
+
+Examples: >
+ let your_name = input("What's your name? ")
+< What's your name? Peter ~
+>
+ echo
+ echo $"Hello, {your_name}!"
+< Hello, Peter! ~
+>
+ echo $"The square root of {{9}} is {sqrt(9)}"
+< The square root of {9} is 3.0 ~
+
+ *string-offset-encoding*
+A string consists of multiple characters. UTF-8 uses one byte for ASCII
+characters, two bytes for other latin characters and more bytes for other
+characters.
+
+A string offset can count characters or bytes. Other programs may use
+UTF-16 encoding (16-bit words) and an offset of UTF-16 words. Some functions
+use byte offsets, usually for UTF-8 encoding. Other functions use character
+offsets, in which case the encoding doesn't matter.
+
+The different offsets for the string "a©😊" are below:
+
+ UTF-8 offsets:
+ [0]: 61, [1]: C2, [2]: A9, [3]: F0, [4]: 9F, [5]: 98, [6]: 8A
+ UTF-16 offsets:
+ [0]: 0061, [1]: 00A9, [2]: D83D, [3]: DE0A
+ UTF-32 (character) offsets:
+ [0]: 00000061, [1]: 000000A9, [2]: 0001F60A
+
+You can use the "g8" and "ga" commands on a character to see the
+decimal/hex/octal values.
+
+The functions |byteidx()|, |utf16idx()| and |charidx()| can be used to convert
+between these indices. The functions |strlen()|, |strutf16len()| and
+|strcharlen()| return the number of bytes, UTF-16 code units and characters in
+a string respectively.
+
+------------------------------------------------------------------------------
option *expr-option* *E112* *E113*
&option option value, local value if possible
@@ -1445,7 +1547,7 @@ See below |functions|.
------------------------------------------------------------------------------
lambda expression *expr-lambda* *lambda*
-{args -> expr1} lambda expression
+`{args -> expr1}` lambda expression *E451*
A lambda expression creates a new unnamed function which returns the result of
evaluating |expr1|. Lambda expressions differ from |user-function|s in
@@ -1670,38 +1772,6 @@ v:argv The command line arguments Vim was invoked with. This is a
list of strings. The first item is the Vim command.
See |v:progpath| for the command with full path.
- *v:beval_col* *beval_col-variable*
-v:beval_col The number of the column, over which the mouse pointer is.
- This is the byte index in the |v:beval_lnum| line.
- Only valid while evaluating the 'balloonexpr' option.
-
- *v:beval_bufnr* *beval_bufnr-variable*
-v:beval_bufnr The number of the buffer, over which the mouse pointer is. Only
- valid while evaluating the 'balloonexpr' option.
-
- *v:beval_lnum* *beval_lnum-variable*
-v:beval_lnum The number of the line, over which the mouse pointer is. Only
- valid while evaluating the 'balloonexpr' option.
-
- *v:beval_text* *beval_text-variable*
-v:beval_text The text under or after the mouse pointer. Usually a word as
- it is useful for debugging a C program. 'iskeyword' applies,
- but a dot and "->" before the position is included. When on a
- ']' the text before it is used, including the matching '[' and
- word before it. When on a Visual area within one line the
- highlighted text is used. Also see |<cexpr>|.
- Only valid while evaluating the 'balloonexpr' option.
-
- *v:beval_winnr* *beval_winnr-variable*
-v:beval_winnr The number of the window, over which the mouse pointer is. Only
- valid while evaluating the 'balloonexpr' option. The first
- window has number zero (unlike most other places where a
- window gets a number).
-
- *v:beval_winid* *beval_winid-variable*
-v:beval_winid The |window-ID| of the window, over which the mouse pointer
- is. Otherwise like v:beval_winnr.
-
*v:char* *char-variable*
v:char Argument for evaluating 'formatexpr' and used for the typed
character when using <expr> in an abbreviation |:map-<expr>|.
@@ -1776,7 +1846,7 @@ v:ctype The current locale setting for characters of the runtime
v:dying Normally zero. When a deadly signal is caught it's set to
one. When multiple signals are caught the number increases.
Can be used in an autocommand to check if Vim didn't
- terminate normally. {only works on Unix}
+ terminate normally.
Example: >
:au VimLeave * if v:dying | echo "\nAAAAaaaarrrggghhhh!!!\n" | endif
< Note: if another deadly signal is caught when v:dying is one,
@@ -1826,7 +1896,7 @@ v:event Dictionary of event data for the current |autocommand|. Valid
abort Whether the event triggered during
an aborting condition (e.g. |c_Esc| or
|c_CTRL-C| for |CmdlineLeave|).
- chan |channel-id| or 0 for "internal".
+ chan |channel-id|
cmdlevel Level of cmdline.
cmdtype Type of cmdline, |cmdline-char|.
cwd Current working directory.
@@ -1849,18 +1919,18 @@ v:event Dictionary of event data for the current |autocommand|. Valid
completed_item Current selected complete item on
|CompleteChanged|, Is `{}` when no complete
item selected.
- height Height of popup menu on |CompleteChanged|
- width width of popup menu on |CompleteChanged|
- row Row count of popup menu on |CompleteChanged|,
+ height Height of popup menu on |CompleteChanged|
+ width width of popup menu on |CompleteChanged|
+ row Row count of popup menu on |CompleteChanged|,
relative to screen.
- col Col count of popup menu on |CompleteChanged|,
+ col Col count of popup menu on |CompleteChanged|,
relative to screen.
- size Total number of completion items on
+ size Total number of completion items on
|CompleteChanged|.
- scrollbar Is |v:true| if popup menu have scrollbar, or
+ scrollbar Is |v:true| if popup menu have scrollbar, or
|v:false| if not.
- changed_window Is |v:true| if the the event fired
- while changing window (or tab) on |DirChanged|.
+ changed_window Is |v:true| if the event fired while
+ changing window (or tab) on |DirChanged|.
status Job status or exit code, -1 means "unknown". |TermClose|
*v:exception* *exception-variable*
@@ -2011,6 +2081,11 @@ v:lnum Line number for the 'foldexpr' |fold-expr|, 'formatexpr',
v:lua Prefix for calling Lua functions from expressions.
See |v:lua-call| for more information.
+ *v:maxcol* *maxcol-variable*
+v:maxcol Maximum line length. Depending on where it is used it can be
+ screen columns, characters or bytes. The value currently is
+ 2147483647 on all systems.
+
*v:mouse_win* *mouse_win-variable*
v:mouse_win Window number for a mouse click obtained with |getchar()|.
First window has number 1, like with |winnr()|. The value is
@@ -2135,7 +2210,7 @@ v:register The name of the register in effect for the current normal mode
(use this in custom commands that take a register).
If none is supplied it is the default register '"', unless
'clipboard' contains "unnamed" or "unnamedplus", then it is
- '*' or '+'.
+ "*" or '+'.
Also see |getreg()| and |setreg()|
*v:relnum* *relnum-variable*
@@ -2201,12 +2276,13 @@ v:stderr |channel-id| corresponding to stderr. The value is always 2;
:call chansend(v:stderr, "error: toaster empty\n")
<
*v:swapname* *swapname-variable*
-v:swapname Only valid when executing |SwapExists| autocommands: Name of
- the swap file found. Read-only.
+v:swapname Name of the swapfile found.
+ Only valid during |SwapExists| event.
+ Read-only.
*v:swapchoice* *swapchoice-variable*
v:swapchoice |SwapExists| autocommands can set this to the selected choice
- for handling an existing swap file:
+ for handling an existing swapfile:
'o' Open read-only
'e' Edit anyway
'r' Recover
@@ -2242,18 +2318,10 @@ v:t_string Value of |String| type. Read-only. See: |type()|
v:t_blob Value of |Blob| type. Read-only. See: |type()|
*v:termresponse* *termresponse-variable*
-v:termresponse The escape sequence returned by the terminal for the DA
- (request primary device attributes) control sequence. It is
- set when Vim receives an escape sequence that starts with ESC
- [ or CSI and ends in a 'c', with only digits, ';' and '.' in
- between.
- When this option is set, the TermResponse autocommand event is
- fired, so that you can react to the response from the
- terminal.
- The response from a new xterm is: "<Esc>[ Pp ; Pv ; Pc c". Pp
- is the terminal type: 0 for vt100 and 1 for vt220. Pv is the
- patch level (since this was introduced in patch 95, it's
- always 95 or bigger). Pc is always zero.
+v:termresponse The value of the most recent OSC or DCS escape sequence
+ received by Nvim from the terminal. This can be read in a
+ |TermResponse| event handler after querying the terminal using
+ another escape sequence.
*v:testing* *testing-variable*
v:testing Must be set before using `test_garbagecollect_now()`.
@@ -2418,7 +2486,7 @@ This does NOT work: >
*:let/=* *:let%=* *:let.=* *:let..=* *E734*
:let {var} += {expr1} Like ":let {var} = {var} + {expr1}".
:let {var} -= {expr1} Like ":let {var} = {var} - {expr1}".
-:let {var} *= {expr1} Like ":let {var} = {var} * {expr1}".
+`:let {var} *= {expr1}` Like ":let {var} = {var} * {expr1}".
:let {var} /= {expr1} Like ":let {var} = {var} / {expr1}".
:let {var} %= {expr1} Like ":let {var} = {var} % {expr1}".
:let {var} .= {expr1} Like ":let {var} = {var} . {expr1}".
@@ -2526,15 +2594,31 @@ This does NOT work: >
|List| item.
*:let=<<* *:let-heredoc*
- *E990* *E991* *E172* *E221*
-:let {var-name} =<< [trim] {endmarker}
+ *E990* *E991* *E172* *E221* *E1145*
+:let {var-name} =<< [trim] [eval] {endmarker}
text...
text...
{endmarker}
Set internal variable {var-name} to a |List|
containing the lines of text bounded by the string
- {endmarker}. The lines of text is used as a
- |literal-string|.
+ {endmarker}.
+
+ If "eval" is not specified, then each line of text is
+ used as a |literal-string|, except that single quotes
+ does not need to be doubled.
+ If "eval" is specified, then any Vim expression in the
+ form {expr} is evaluated and the result replaces the
+ expression, like with |interpolated-string|.
+ Example where $HOME is expanded: >
+ let lines =<< trim eval END
+ some text
+ See the file {$HOME}/.vimrc
+ more text
+ END
+< There can be multiple Vim expressions in a single line
+ but an expression cannot span multiple lines. If any
+ expression evaluation fails, then the assignment fails.
+
{endmarker} must not contain white space.
{endmarker} cannot start with a lower case character.
The last line should end only with the {endmarker}
@@ -2584,6 +2668,13 @@ text...
1 2 3 4
5 6 7 8
DATA
+
+ let code =<< trim eval CODE
+ let v = {10 + 20}
+ let h = "{$HOME}"
+ let s = "{Str1()} abc {Str2()}"
+ let n = {MyFunc(3, 4)}
+ CODE
<
*E121*
:let {var-name} .. List the value of variable {var-name}. Multiple
@@ -2700,8 +2791,8 @@ text...
Example with [depth] 0: >
let mylist = [1, 2, 3]
lockvar 0 mylist
- let mylist[0] = 77 " OK
- call add(mylist, 4] " OK
+ let mylist[0] = 77 " OK
+ call add(mylist, 4) " OK
let mylist = [7, 8, 9] " Error!
< *E743*
For unlimited depth use [!] and omit [depth].
@@ -4172,10 +4263,10 @@ The input is in the variable "line", the results in the variables "file",
getting the scriptnames in a Dictionary ~
*scriptnames-dictionary*
-The |:scriptnames| command can be used to get a list of all script files that
-have been sourced. There is no equivalent function or variable for this
-(because it's rarely needed). In case you need to manipulate the list this
-code can be used: >
+The `:scriptnames` command can be used to get a list of all script files that
+have been sourced. There is also the `getscriptinfo()` function, but the
+information returned is not exactly the same. In case you need to manipulate
+the output of `scriptnames` this code can be used: >
" Get the output of ":scriptnames" in the scriptnames_output variable.
let scriptnames_output = ''
redir => scriptnames_output
@@ -4244,8 +4335,8 @@ Textlock *textlock*
In a few situations it is not allowed to change the text in the buffer, jump
to another window and some other things that might confuse or break what Vim
is currently doing. This mostly applies to things that happen when Vim is
-actually doing something else. For example, evaluating the 'balloonexpr' may
-happen any moment the mouse cursor is resting at some position.
+actually doing something else. For example, a TextYankPost autocommand cannot
+edit the text it is yanking.
This is not allowed when the textlock is active:
- changing the buffer text
@@ -4255,6 +4346,33 @@ This is not allowed when the textlock is active:
- etc.
==============================================================================
+Vim script library *vim-script-library*
+
+Vim comes bundled with a Vim script library, that can be used by runtime,
+script authors. Currently, it only includes very few functions, but it may
+grow over time.
+
+ *dist#vim*
+The functions make use of the autoloaded prefix "dist#vim".
+
+The following functions are available:
+
+dist#vim#IsSafeExecutable(filetype, executable) ~
+
+This function takes a filetype and an executable and checks whether it is safe
+to execute the given executable. For security reasons users may not want to
+have Vim execute random executables or may have forbidden to do so for
+specific filetypes by setting the "<filetype>_exec" variable (|plugin_exec|).
+
+It returns |TRUE| or |FALSE| to indicate whether the plugin should run the given
+exectuable. It takes the following arguments:
+
+ argument type ~
+
+ filetype string
+ executable string
+
+==============================================================================
Command-line expressions highlighting *expr-highlight*
Expressions entered by the user in |i_CTRL-R_=|, |c_CTRL-\_e|, |quote=| are
diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt
index a53c287d48..ed21dc1c37 100644
--- a/runtime/doc/filetype.txt
+++ b/runtime/doc/filetype.txt
@@ -54,9 +54,9 @@ you can either set the 'filetype' option manually, or add a modeline to your
file. Example, for an IDL file use the command: >
:set filetype=idl
-or add this |modeline| to the file:
- /* vim: set filetype=idl : */ ~
-
+or add this |modeline| to the file: >
+ /* vim: set filetype=idl : */
+<
*:filetype-plugin-on*
You can enable loading the plugin files for specific file types with: >
:filetype plugin on
@@ -136,37 +136,42 @@ what kind of file it is. This doesn't always work. A number of global
variables can be used to overrule the filetype used for certain extensions:
file name variable ~
- *.asa g:filetype_asa |ft-aspvbs-syntax| |ft-aspperl-syntax|
- *.asm g:asmsyntax |ft-asm-syntax|
- *.asp g:filetype_asp |ft-aspvbs-syntax| |ft-aspperl-syntax|
- *.bas g:filetype_bas |ft-basic-syntax|
- *.cfg g:filetype_cfg
- *.cls g:filetype_cls
- *.csh g:filetype_csh |ft-csh-syntax|
- *.dat g:filetype_dat
- *.frm g:filetype_frm |ft-form-syntax|
- *.fs g:filetype_fs |ft-forth-syntax|
- *.i g:filetype_i |ft-progress-syntax|
- *.inc g:filetype_inc
- *.lsl g:filetype_lsl
- *.m g:filetype_m |ft-mathematica-syntax|
- *.mod g:filetype_mod
- *.p g:filetype_p |ft-pascal-syntax|
- *.pl g:filetype_pl
- *.pp g:filetype_pp |ft-pascal-syntax|
- *.prg g:filetype_prg
- *.r g:filetype_r
- *.sig g:filetype_sig
- *.sql g:filetype_sql |ft-sql-syntax|
- *.src g:filetype_src
- *.sys g:filetype_sys
- *.sh g:bash_is_sh |ft-sh-syntax|
- *.tex g:tex_flavor |ft-tex-plugin|
- *.w g:filetype_w |ft-cweb-syntax|
+ `*.asa` g:filetype_asa |ft-aspperl-syntax|
+ |ft-aspvbs-syntax|
+ `*.asm` g:asmsyntax |ft-asm-syntax|
+ `*.asp` g:filetype_asp |ft-aspperl-syntax|
+ |ft-aspvbs-syntax|
+ `*.bas` g:filetype_bas |ft-basic-syntax|
+ `*.cfg` g:filetype_cfg
+ `*.cls` g:filetype_cls
+ `*.csh` g:filetype_csh |ft-csh-syntax|
+ `*.dat` g:filetype_dat
+ `*.f` g:filetype_f |ft-forth-syntax|
+ `*.frm` g:filetype_frm |ft-form-syntax|
+ `*.fs` g:filetype_fs |ft-forth-syntax|
+ `*.h` g:c_syntax_for_h |ft-c-syntax|
+ `*.i` g:filetype_i |ft-progress-syntax|
+ `*.inc` g:filetype_inc
+ `*.lsl` g:filetype_lsl
+ `*.m` g:filetype_m |ft-mathematica-syntax|
+ `*.mod` g:filetype_mod
+ `*.p` g:filetype_p |ft-pascal-syntax|
+ `*.pl` g:filetype_pl
+ `*.pp` g:filetype_pp |ft-pascal-syntax|
+ `*.prg` g:filetype_prg
+ `*.r` g:filetype_r
+ `*.sig` g:filetype_sig
+ `*.sql` g:filetype_sql |ft-sql-syntax|
+ `*.src` g:filetype_src
+ `*.sys` g:filetype_sys
+ `*.sh` g:bash_is_sh |ft-sh-syntax|
+ `*.tex` g:tex_flavor |ft-tex-plugin|
+ `*.typ` g:filetype_typ
+ `*.w` g:filetype_w |ft-cweb-syntax|
For a few filetypes the global variable is used only when the filetype could
not be detected:
- *.r g:filetype_r |ft-rexx-syntax|
+ `*.r` g:filetype_r |ft-rexx-syntax|
*filetype-ignore*
To avoid that certain files are being inspected, the g:ft_ignore_pat variable
@@ -356,7 +361,7 @@ ways to change this:
You must create a new filetype plugin in a directory early in
'runtimepath'. For Unix, for example you could use this file: >
vim ~/.config/nvim/ftplugin/fortran.vim
-< You can set those settings and mappings that you would like to add. Note
+< You can set those settings and mappings that you would like to add. Note
that the global plugin will be loaded after this, it may overrule the
settings that you do here. If this is the case, you need to use one of the
following two methods.
@@ -365,7 +370,7 @@ ways to change this:
You must put the copy in a directory early in 'runtimepath'. For Unix, for
example, you could do this: >
cp $VIMRUNTIME/ftplugin/fortran.vim ~/.config/nvim/ftplugin/fortran.vim
-< Then you can edit the copied file to your liking. Since the b:did_ftplugin
+< Then you can edit the copied file to your liking. Since the b:did_ftplugin
variable will be set, the global plugin will not be loaded.
A disadvantage of this method is that when the distributed plugin gets
improved, you will have to copy and modify it again.
@@ -374,17 +379,30 @@ ways to change this:
You must create a new filetype plugin in a directory from the end of
'runtimepath'. For Unix, for example, you could use this file: >
vim ~/.config/nvim/after/ftplugin/fortran.vim
-< In this file you can change just those settings that you want to change.
+< In this file you can change just those settings that you want to change.
==============================================================================
3. Docs for the default filetype plugins. *ftplugin-docs*
+ *plugin_exec* *g:plugin_exec*
+Enable executing of external commands. This was done historically for e.g.
+the perl filetype plugin (and a few others) to set the search path.
+Disabled by default for security reasons: >
+ :let g:plugin_exec = 1
+It is also possible to enable this only for certain filetypes: >
+ :let g:<filetype>_exec = 1
+So to enable this only for ruby, set the following variable: >
+ :let g:ruby_exec = 1
+
+If both, the global `plugin_exec` and the `<filetype>_exec` specific variable
+are set, the filetype specific variable should have precedent.
+
AWK *ft-awk-plugin*
Support for features specific to GNU Awk, like @include, can be enabled by
setting: >
- let g:awk_is_gawk = 1
+ :let g:awk_is_gawk = 1
CHANGELOG *ft-changelog-plugin*
@@ -618,7 +636,7 @@ To use Nvim as a manpager: >
export MANPAGER='nvim +Man!'
Note that when running `man` from the shell and with that `MANPAGER` in your
-environment, `man` will pre-format the manpage using `groff`. Thus, Neovim
+environment, `man` will pre-format the manpage using `groff`. Thus, Nvim
will inevitably display the manual page as it was passed to it from stdin. One
of the caveats of this is that the width will _always_ be hard-wrapped and not
soft wrapped as with `g:man_hardwrap=0`. You can set in your environment: >
@@ -634,7 +652,10 @@ MARKDOWN *ft-markdown-plugin*
To enable folding use this: >
let g:markdown_folding = 1
-<
+
+'expandtab' will be set by default. If you do not want that use this: >
+ let g:markdown_recommended_style = 0
+
PDF *ft-pdf-plugin*
@@ -660,6 +681,19 @@ To disable this behavior, set the following variable in your vimrc: >
let g:python_recommended_style = 0
+QUERY *ft-query-plugin*
+
+
+Linting of tree-sitter queries for installed parsers using
+|vim.treesitter.query.lint()| is enabled by default on `BufEnter` and
+`BufWrite`. To change the events that trigger linting, use >lua
+
+ vim.g.query_lint_on = { 'InsertLeave', 'TextChanged' }
+<
+To disable linting completely, set >lua
+
+ vim.g.query_lint_on = {}
+<
QF QUICKFIX *qf.vim* *ft-qf-plugin*
@@ -708,7 +742,7 @@ file: |pi_spec.txt|.
SHADA *ft-shada*
-Allows editing binary |shada-file|s in a nice way. Opened binary files are
+Allows editing binary |shada-file|s in a nice way. Opened binary files are
displayed in the following format: >
Type with timestamp YYYY-mm-ddTHH:MM:SS:
@@ -723,31 +757,31 @@ displayed in the following format: >
# Unexpected type: type instead of map
= {msgpack-value}
-Filetype plugin defines all |Cmd-event|s. Defined |SourceCmd| event makes
-"source file.shada" be equivalent to "|:rshada| file.shada". |BufWriteCmd|,
-|FileWriteCmd| and |FileAppendCmd| events are affected by the following
+Filetype plugin defines all |Cmd-event|s. Defined |SourceCmd| event makes
+"source file.shada" be equivalent to "|:rshada| file.shada". |BufWriteCmd|,
+|FileWriteCmd| and |FileAppendCmd| events are affected by the following
settings:
-*g:shada#keep_old_header* Boolean, if set to false all header entries
+*g:shada#keep_old_header* Boolean, if set to false all header entries
are ignored when writing. Defaults to 1.
-*g:shada#add_own_header* Boolean, if set to true first written entry
- will always be header entry with two values in
- a map with attached data: |v:version| attached
- to "version" key and "shada.vim" attached to
+*g:shada#add_own_header* Boolean, if set to true first written entry
+ will always be header entry with two values in
+ a map with attached data: |v:version| attached
+ to "version" key and "shada.vim" attached to
"generator" key. Defaults to 1.
Format description:
-1. `#` starts a comment. Lines starting with space characters and then `#`
- are ignored. Plugin may only add comment lines to indicate some errors in
- ShaDa format. Lines containing no non-whitespace characters are also
+1. `#` starts a comment. Lines starting with space characters and then `#`
+ are ignored. Plugin may only add comment lines to indicate some errors in
+ ShaDa format. Lines containing no non-whitespace characters are also
ignored.
-2. Each entry starts with line that has format "{type} with timestamp
- {timestamp}:". {timestamp} is |strftime()|-formatted string representing
+2. Each entry starts with line that has format "{type} with timestamp
+ {timestamp}:". {timestamp} is |strftime()|-formatted string representing
actual Unix timestamp value. First strftime() argument is equal to
- `%Y-%m-%dT%H:%M:%S`. When writing this timestamp is parsed using
- |msgpack#strptime()|, with caching (it remembers which timestamp produced
- particular strftime() output and uses this value if you did not change
+ `%Y-%m-%dT%H:%M:%S`. When writing this timestamp is parsed using
+ |msgpack#strptime()|, with caching (it remembers which timestamp produced
+ particular strftime() output and uses this value if you did not change
timestamp). {type} is one of
1 - Header
2 - Search pattern
@@ -762,28 +796,28 @@ Format description:
11 - Change
* - Unknown (0x{type-hex})
- Each type may be represented using Unknown entry: "Jump with timestamp ..."
+ Each type may be represented using Unknown entry: "Jump with timestamp ..."
is the same as "Unknown (0x8) with timestamp ....".
3. After header there is one of the following lines:
- 1. " % Key__ Description__ Value": map header. After mapping header
- follows a table which may contain comments and lines consisting of
- " +", key, description and |{msgpack-value}|. Key is separated by at
- least two spaces with description, description is separated by at least
- two spaces with the value. Each key in the map must be at most as wide
- as "Key__" header: "Key" allows at most 3-byte keys, "Key__" allows at
- most 5-byte keys. If keys actually occupy less bytes then the rest is
- filled with spaces. Keys cannot be empty, end with spaces, contain two
- consequent spaces inside of them or contain multibyte characters (use
- "=" format if you need this). Descriptions have the same restrictions
- on width and contents, except that empty descriptions are allowed.
+ 1. " % Key__ Description__ Value": map header. After mapping header
+ follows a table which may contain comments and lines consisting of
+ " +", key, description and |{msgpack-value}|. Key is separated by at
+ least two spaces with description, description is separated by at least
+ two spaces with the value. Each key in the map must be at most as wide
+ as "Key__" header: "Key" allows at most 3-byte keys, "Key__" allows at
+ most 5-byte keys. If keys actually occupy less bytes then the rest is
+ filled with spaces. Keys cannot be empty, end with spaces, contain two
+ consequent spaces inside of them or contain multibyte characters (use
+ "=" format if you need this). Descriptions have the same restrictions
+ on width and contents, except that empty descriptions are allowed.
Description column may be omitted.
- When writing description is ignored. Keys with values |msgpack#equal|
- to default ones are ignored. Order of keys is preserved. All keys are
+ When writing description is ignored. Keys with values |msgpack#equal|
+ to default ones are ignored. Order of keys is preserved. All keys are
treated as strings (not binary strings).
- Note: specifically for buffer list entries it is allowed to have more
- then one map header. Each map header starts a new map entry inside
+ Note: specifically for buffer list entries it is allowed to have more
+ then one map header. Each map header starts a new map entry inside
buffer list because ShaDa buffer list entry is an array of maps. I.e. >
Buffer list with timestamp 1970-01-01T00:00:00:
@@ -811,7 +845,7 @@ Format description:
Buffer list with timestamp 1970-01-01T00:00:00:
= [{="f": "/foo"}, {="f": "/bar"}]
<
- Note 2: specifically for register entries special syntax for arrays was
+ Note 2: specifically for register entries special syntax for arrays was
designed: >
Register with timestamp 1970-01-01T00:00:00:
@@ -826,10 +860,10 @@ Format description:
% Key Description Value
+ rc contents ["line1", "line2"]
<
- Such syntax is automatically used if array representation appears to be
+ Such syntax is automatically used if array representation appears to be
too lengthy.
- 2. " @ Description__ Value": array header. Same as map, but key is
- omitted and description cannot be omitted. Array entries start with
+ 2. " @ Description__ Value": array header. Same as map, but key is
+ omitted and description cannot be omitted. Array entries start with
" -". Example: >
History entry with timestamp 1970-01-01T00:00:00:
@@ -844,8 +878,8 @@ Format description:
= [SEARCH, "foo", '/']
<
Note: special array syntax for register entries is not recognized here.
- 3. " = {msgpack-value}": raw values. |{msgpack-value}| in this case may
- have absolutely any type. Special array syntax for register entries is
+ 3. " = {msgpack-value}": raw values. |{msgpack-value}| in this case may
+ have absolutely any type. Special array syntax for register entries is
not recognized here as well.
@@ -863,7 +897,7 @@ file: |ft_sql.txt|.
TEX *ft-tex-plugin* *g:tex_flavor*
-If the first line of a *.tex file has the form >
+If the first line of a `*.tex` file has the form >
%&<format>
then this determined the file type: plaintex (for plain TeX), context (for
ConTeXt), or tex (for LaTeX). Otherwise, the file is searched for keywords to
diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt
index 35a3be35fb..8f7393f5e3 100644
--- a/runtime/doc/fold.txt
+++ b/runtime/doc/fold.txt
@@ -195,7 +195,7 @@ non-matching marker pairs. Example: >
/* funcB() {{{2 */
void funcB() {}
-
+< *{{{* *}}}*
A fold starts at a "{{{" marker. The following number specifies the fold
level. What happens depends on the difference between the current fold level
and the level given by the marker:
@@ -253,7 +253,7 @@ This does not work properly when:
know what to do.
- Folds nearby use a level number in their marker which gets in the way.
- The line is inside a comment, 'commentstring' isn't empty and nested
- comments don't work. For example with C: adding /* {{{ */ inside a comment
+ comments don't work. For example with C: adding `/* {{{ */` inside a comment
will truncate the existing comment. Either put the marker before or after
the comment, or add the marker manually.
Generally it's not a good idea to let Vim create markers when you already have
@@ -346,7 +346,8 @@ zC Close all folds under the cursor recursively. Folds that
'foldenable' will be set.
*za*
-za When on a closed fold: open it. When folds are nested, you
+za Summary: Toggle the fold under the cursor.
+ When on a closed fold: open it. When folds are nested, you
may have to use "za" several times. When a count is given,
that many closed folds are opened.
When on an open fold: close it and set 'foldenable'. This
@@ -519,8 +520,10 @@ expression. It can use these special Vim variables:
foldlevel.
v:foldlevel the foldlevel of the fold
-In the result a TAB is replaced with a space and unprintable characters are
-made into printable characters.
+If the result is a |List|, it is parsed and drawn like "overlay" virtual text
+(see |nvim_buf_set_extmark()|), otherwise the result is converted to a string
+where a TAB is replaced with a space and unprintable characters are made into
+printable characters.
The resulting line is truncated to fit in the window, it never wraps.
When there is room after the text, it is filled with the character specified
diff --git a/runtime/doc/ft_ada.txt b/runtime/doc/ft_ada.txt
index 3321228009..3b7d6a73dd 100644
--- a/runtime/doc/ft_ada.txt
+++ b/runtime/doc/ft_ada.txt
@@ -112,8 +112,8 @@ NOTE: "gnat xref -v" is very tricky to use as it has almost no diagnostic
your .ali files.
2) "gnat xref -v ../Include/adacl.ads" won't work - use the "gnat xref -v
-aI../Include adacl.ads" instead.
-3) "gnat xref -v -aI../Include *.ad?" won't work - use "cd ../Include" and
- then "gnat xref -v *.ad?"
+3) `gnat xref -v -aI../Include *.ad?` won't work - use "cd ../Include" and
+ then `gnat xref -v *.ad?`
4) Project manager support is completely broken - don't even try "gnat xref
-Padacl.gpr".
5) Vim is faster when the tags file is sorted - use "sort --unique
diff --git a/runtime/doc/ft_raku.txt b/runtime/doc/ft_raku.txt
index 3d1179ed4e..c9bbe4bd04 100644
--- a/runtime/doc/ft_raku.txt
+++ b/runtime/doc/ft_raku.txt
@@ -35,10 +35,10 @@ Some of them are available with standard Vim digraphs:
(- ∈ ?= ≅ != ≠ ~
-) ∋ ?- ≃ ~
-The Greek alphabet is available with '*' followed by a similar Latin symbol:
- *p π ~
- *t τ ~
- *X × ~
+The Greek alphabet is available with "*" followed by a similar Latin symbol: >
+ *p π
+ *t τ
+ *X ×
Numbers, subscripts and superscripts are available with 's' and 'S':
0s ₀ 0S ⁰ ~
diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt
index a5d5b558dc..159ff7d5f6 100644
--- a/runtime/doc/ft_rust.txt
+++ b/runtime/doc/ft_rust.txt
@@ -1,72 +1,74 @@
-*ft_rust.txt* Nvim
-
-This is documentation for the Rust filetype plugin.
+*ft_rust.txt* Filetype plugin for Rust
==============================================================================
-CONTENTS *rust*
+CONTENTS *rust*
-1. Introduction |rust-intro|
-2. Settings |rust-settings|
-3. Commands |rust-commands|
-4. Mappings |rust-mappings|
+1. Introduction |rust-intro|
+2. Settings |rust-settings|
+3. Commands |rust-commands|
+4. Mappings |rust-mappings|
==============================================================================
-INTRODUCTION *rust-intro*
+INTRODUCTION *rust-intro*
This plugin provides syntax and supporting functionality for the Rust
-filetype.
+filetype. It requires Vim 8 or higher for full functionality. Some commands
+will not work on earlier versions.
==============================================================================
-SETTINGS *rust-settings*
+SETTINGS *rust-settings*
This plugin has a few variables you can define in your vimrc that change the
behavior of the plugin.
- *g:rustc_path*
-g:rustc_path~
+Some variables can be set buffer local (`:b` prefix), and the buffer local
+will take precedence over the global `g:` counterpart.
+
+ *g:rustc_path*
+g:rustc_path ~
Set this option to the path to rustc for use in the |:RustRun| and
- |:RustExpand| commands. If unset, "rustc" will be located in $PATH: >
- let g:rustc_path = $HOME .. "/bin/rustc"
+ |:RustExpand| commands. If unset, `rustc` will be located in $PATH: >vim
+ let g:rustc_path = $HOME."/bin/rustc"
<
- *g:rustc_makeprg_no_percent*
-g:rustc_makeprg_no_percent~
- Set this option to 1 to have 'makeprg' default to "rustc" instead of
- "rustc %": >
+ *g:rustc_makeprg_no_percent*
+g:rustc_makeprg_no_percent ~
+ Set this option to 1 to have 'makeprg' default to `rustc` instead of
+ `rustc %`: >vim
let g:rustc_makeprg_no_percent = 1
<
- *g:rust_conceal*
-g:rust_conceal~
- Set this option to turn on the basic |conceal| support: >
+ *g:rust_conceal*
+g:rust_conceal ~
+ Set this option to turn on the basic |conceal| support: >vim
let g:rust_conceal = 1
<
- *g:rust_conceal_mod_path*
-g:rust_conceal_mod_path~
+ *g:rust_conceal_mod_path*
+g:rust_conceal_mod_path ~
Set this option to turn on |conceal| for the path connecting token
- "::": >
+ "::": >vim
let g:rust_conceal_mod_path = 1
<
- *g:rust_conceal_pub*
-g:rust_conceal_pub~
- Set this option to turn on |conceal| for the "pub" token: >
+ *g:rust_conceal_pub*
+g:rust_conceal_pub ~
+ Set this option to turn on |conceal| for the "pub" token: >vim
let g:rust_conceal_pub = 1
<
- *g:rust_recommended_style*
-g:rust_recommended_style~
- Set this option to enable vim indentation and textwidth settings to
- conform to style conventions of the rust standard library (i.e. use 4
- spaces for indents and sets 'textwidth' to 99). This option is enabled
- by default. To disable it: >
+ *g:rust_recommended_style*
+g:rust_recommended_style ~
+ Set this option to enable vim indentation and textwidth settings to
+ conform to style conventions of the rust standard library (i.e. use 4
+ spaces for indents and sets 'textwidth' to 99). This option is enabled
+ by default. To disable it: >vim
let g:rust_recommended_style = 0
<
- *g:rust_fold*
-g:rust_fold~
- Set this option to turn on |folding|: >
+ *g:rust_fold*
+g:rust_fold ~
+ Set this option to turn on |folding|: >vim
let g:rust_fold = 1
<
Value Effect ~
@@ -77,62 +79,296 @@ g:rust_fold~
global value (all folds are closed by default).
*g:rust_bang_comment_leader*
-g:rust_bang_comment_leader~
+g:rust_bang_comment_leader ~
Set this option to 1 to preserve the leader on multi-line doc comments
- using the /*! syntax: >
+ using the `/*!` syntax: >vim
let g:rust_bang_comment_leader = 1
<
- *g:ftplugin_rust_source_path*
-g:ftplugin_rust_source_path~
+ *g:rust_use_custom_ctags_defs*
+g:rust_use_custom_ctags_defs ~
+ Set this option to 1 if you have customized ctags definitions for Rust
+ and do not wish for those included with rust.vim to be used: >vim
+ let g:rust_use_custom_ctags_defs = 1
+<
+
+ NOTE: rust.vim's built-in definitions are only used for the Tagbar Vim
+ plugin, if you have it installed, AND if Universal Ctags is not
+ detected. This is because Universal Ctags already has built-in
+ support for Rust when used with Tagbar.
+
+ Also, note that when using ctags other than Universal Ctags, it is not
+ automatically used when generating |tags| files that Vim can use to
+ navigate to definitions across different source files. Feel free to
+ copy `rust.vim/ctags/rust.ctags` into your own `~/.ctags` if you wish
+ to generate |tags| files.
+
+ *g:ftplugin_rust_source_path*
+g:ftplugin_rust_source_path ~
Set this option to a path that should be prepended to 'path' for Rust
- source files: >
- let g:ftplugin_rust_source_path = $HOME .. '/dev/rust'
+ source files: >vim
+ let g:ftplugin_rust_source_path = $HOME . '/dev/rust'
<
*g:rustfmt_command*
-g:rustfmt_command~
+g:rustfmt_command ~
Set this option to the name of the "rustfmt" executable in your $PATH. If
- not specified it defaults to "rustfmt" : >
+ not specified it defaults to "rustfmt" : >vim
let g:rustfmt_command = 'rustfmt'
<
*g:rustfmt_autosave*
-g:rustfmt_autosave~
+g:rustfmt_autosave ~
Set this option to 1 to run |:RustFmt| automatically when saving a
- buffer. If not specified it defaults to 0 : >
+ buffer. If not specified it defaults to 0 : >vim
let g:rustfmt_autosave = 0
<
+ There is also a buffer-local b:rustfmt_autosave that can be set for
+ the same purpose, and can override the global setting.
+
+ *g:rustfmt_autosave_if_config_present*
+g:rustfmt_autosave_if_config_present ~
+ Set this option to 1 to have *b:rustfmt_autosave* be set automatically
+ if a `rustfmt.toml` file is present in any parent directly leading to
+ the file being edited. If not set, default to 0: >vim
+ let g:rustfmt_autosave_if_config_present = 0
+<
+ This is useful to have `rustfmt` only execute on save, on projects
+ that have `rustfmt.toml` configuration.
+
+ There is also a buffer-local b:rustfmt_autosave_if_config_present
+ that can be set for the same purpose, which can overrides the global
+ setting.
+
*g:rustfmt_fail_silently*
-g:rustfmt_fail_silently~
+g:rustfmt_fail_silently ~
Set this option to 1 to prevent "rustfmt" from populating the
- |location-list| with errors. If not specified it defaults to 0: >
+ |location-list| with errors. If not specified it defaults to 0: >vim
let g:rustfmt_fail_silently = 0
<
*g:rustfmt_options*
-g:rustfmt_options~
+g:rustfmt_options ~
Set this option to a string of options to pass to "rustfmt". The
write-mode is already set to "overwrite". If not specified it
- defaults to '' : >
+ defaults to '' : >vim
let g:rustfmt_options = ''
<
+ *g:rustfmt_emit_files*
+g:rustfmt_emit_files ~
+ If not specified rust.vim tries to detect the right parameter to
+ pass to rustfmt based on its reported version. Otherwise, it
+ determines whether to run rustfmt with '--emit=files' (when 1 is
+ provided) instead of '--write-mode=overwrite'. >vim
+ let g:rustfmt_emit_files = 0
+<
*g:rust_playpen_url*
-g:rust_playpen_url~
- Set this option to override the URL for the playpen to use: >
+g:rust_playpen_url ~
+ Set this option to override the url for the playpen to use: >vim
let g:rust_playpen_url = 'https://play.rust-lang.org/'
<
*g:rust_shortener_url*
-g:rust_shortener_url~
- Set this option to override the URL for the URL shortener: >
+g:rust_shortener_url ~
+ Set this option to override the url for the url shortener: >vim
let g:rust_shortener_url = 'https://is.gd/'
<
+ *g:rust_clip_command*
+g:rust_clip_command ~
+ Set this option to the command used in your OS to copy the Rust Play
+ url to the clipboard: >vim
+ let g:rust_clip_command = 'xclip -selection clipboard'
+<
+ *g:cargo_makeprg_params*
+g:cargo_makeprg_params ~
+ Set this option to the string of parameters to pass to cargo. If not
+ specified it defaults to `$*` : >vim
+ let g:cargo_makeprg_params = 'build'
+<
+
+ *g:cargo_shell_command_runner*
+g:cargo_shell_command_runner ~
+ Set this option to change how to run shell commands for cargo commands
+ |:Cargo|, |:Cbuild|, |:Crun|, ...
+ By default, |:terminal| is used to run shell command in terminal window
+ asynchronously. But if you prefer |:!| for running the commands, it can
+ be specified: >vim
+ let g:cargo_shell_command_runner = '!'
+<
+
+------------------------------------------------------------------------------
+Integration with Syntastic *rust-syntastic*
+
+This plugin automatically integrates with the Syntastic checker. There are two
+checkers provided: `rustc`, and `cargo`. The latter invokes `cargo` in order to
+build code, and the former delivers a single edited '.rs' file as a compilation
+target directly to the Rust compiler, `rustc`.
+
+Because Cargo is almost exclusively being used for building Rust code these
+days, `cargo` is the default checker. >vim
+
+ let g:syntastic_rust_checkers = ['cargo']
+<
+If you would like to change it, you can set `g:syntastic_rust_checkers` to a
+different value.
+ *g:rust_cargo_avoid_whole_workspace*
+ *b:rust_cargo_avoid_whole_workspace*
+g:rust_cargo_avoid_whole_workspace ~
+ When editing a crate that is part of a Cargo workspace, and this
+ option is set to 1 (the default), then `cargo` will be executed
+ directly in that crate directory instead of in the workspace
+ directory. Setting 0 prevents this behavior - however be aware that if
+ you are working in large workspace, Cargo commands may take more time,
+ plus the Syntastic error list may include all the crates in the
+ workspace. >vim
+ let g:rust_cargo_avoid_whole_workspace = 0
+<
+ *g:rust_cargo_check_all_targets*
+ *b:rust_cargo_check_all_targets*
+g:rust_cargo_check_all_targets ~
+ When set to 1, the `--all-targets` option will be passed to cargo when
+ Syntastic executes it, allowing the linting of all targets under the
+ package.
+ The default is 0.
+
+ *g:rust_cargo_check_all_features*
+ *b:rust_cargo_check_all_features*
+g:rust_cargo_check_all_features ~
+ When set to 1, the `--all-features` option will be passed to cargo when
+ Syntastic executes it, allowing the linting of all features of the
+ package.
+ The default is 0.
+
+ *g:rust_cargo_check_examples*
+ *b:rust_cargo_check_examples*
+g:rust_cargo_check_examples ~
+ When set to 1, the `--examples` option will be passed to cargo when
+ Syntastic executes it, to prevent the exclusion of examples from
+ linting. The examples are normally under the `examples/` directory of
+ the crate.
+ The default is 0.
+
+ *g:rust_cargo_check_tests*
+ *b:rust_cargo_check_tests*
+g:rust_cargo_check_tests ~
+ When set to 1, the `--tests` option will be passed to cargo when
+ Syntastic executes it, to prevent the exclusion of tests from linting.
+ The tests are normally under the `tests/` directory of the crate.
+ The default is 0.
+
+ *g:rust_cargo_check_benches*
+ *b:rust_cargo_check_benches*
+g:rust_cargo_check_benches ~
+ When set to 1, the `--benches` option will be passed to cargo when
+ Syntastic executes it. The benches are normally under the `benches/`
+ directory of the crate.
+ The default is 0.
+
+------------------------------------------------------------------------------
+Integration with auto-pairs *rust-auto-pairs*
+
+This plugin automatically configures the auto-pairs plugin not to duplicate
+single quotes, which are used more often for lifetime annotations than for
+single character literals.
+
+ *g:rust_keep_autopairs_default*
+g:rust_keep_autopairs_default ~
+
+ Don't override auto-pairs default for the Rust filetype. The default
+ is 0.
==============================================================================
-COMMANDS *rust-commands*
+COMMANDS *rust-commands*
+
+Invoking Cargo ~
+
+This plug defines very simple shortcuts for invoking Cargo from with Vim.
+
+:Cargo <args> *:Cargo*
+ Runs `cargo` with the provided arguments.
+
+:Cbuild <args> *:Cbuild*
+ Shortcut for `cargo build` .
+
+:Cclean <args> *:Cclean*
+ Shortcut for `cargo clean` .
+
+:Cdoc <args> *:Cdoc*
+ Shortcut for `cargo doc` .
+
+:Cinit <args> *:Cinit*
+ Shortcut for `cargo init` .
+
+:Crun <args> *:Crun*
+ Shortcut for `cargo run` .
+
+:Ctest <args> *:Ctest*
+ Shortcut for `cargo test` .
+
+:Cupdate <args> *:Cupdate*
+ Shortcut for `cargo update` .
+
+:Cbench <args> *:Cbench*
+ Shortcut for `cargo bench` .
+
+:Csearch <args> *:Csearch*
+ Shortcut for `cargo search` .
+
+:Cpublish <args> *:Cpublish*
+ Shortcut for `cargo publish` .
+
+:Cinstall <args> *:Cinstall*
+ Shortcut for `cargo install` .
+
+:Cruntarget <args> *:Cruntarget*
+ Shortcut for `cargo run --bin` or `cargo run --example`,
+ depending on the currently open buffer.
+
+Formatting ~
+
+:RustFmt *:RustFmt*
+ Runs |g:rustfmt_command| on the current buffer. If
+ |g:rustfmt_options| is set then those will be passed to the
+ executable.
+
+ If |g:rustfmt_fail_silently| is 0 (the default) then it
+ will populate the |location-list| with the errors from
+ |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1
+ then it will not populate the |location-list|.
-:RustRun [args] *:RustRun*
+:RustFmtRange *:RustFmtRange*
+ Runs |g:rustfmt_command| with selected range. See
+ |:RustFmt| for any other information.
+
+
+Playpen integration ~
+
+:RustPlay *:RustPlay*
+ This command will only work if you have web-api.vim installed
+ (available at https://github.com/mattn/webapi-vim). It sends the
+ current selection, or if nothing is selected, the entirety of the
+ current buffer to the Rust playpen, and emits a message with the
+ shortened URL to the playpen.
+
+ |g:rust_playpen_url| is the base URL to the playpen, by default
+ "https://play.rust-lang.org/".
+
+ |g:rust_shortener_url| is the base url for the shorterner, by
+ default "https://is.gd/"
+
+ |g:rust_clip_command| is the command to run to copy the
+ playpen url to the clipboard of your system.
+
+
+Evaluation of a single Rust file ~
+
+NOTE: These commands are useful only when working with standalone Rust files,
+which is usually not the case for common Rust development. If you wish to
+building Rust crates from with Vim can should use Vim's make, Syntastic, or
+functionality from other plugins.
+
+
+:RustRun [args] *:RustRun*
:RustRun! [rustc-args] [--] [args]
Compiles and runs the current file. If it has unsaved changes,
it will be saved first using |:update|. If the current file is
@@ -150,26 +386,26 @@ COMMANDS *rust-commands*
If |g:rustc_path| is defined, it is used as the path to rustc.
Otherwise it is assumed rustc can be found in $PATH.
-:RustExpand [args] *:RustExpand*
+:RustExpand [args] *:RustExpand*
:RustExpand! [TYPE] [args]
- Expands the current file using --pretty and displays the
+ Expands the current file using `--pretty` and displays the
results in a new split. If the current file has unsaved
changes, it will be saved first using |:update|. If the
current file is an unnamed buffer, it will be written to a
temporary file first.
The arguments given to |:RustExpand| will be passed to rustc.
- This is largely intended for specifying various --cfg
+ This is largely intended for specifying various `--cfg`
configurations.
If ! is specified, the first argument is the expansion type to
- pass to rustc --pretty. Otherwise it will default to
+ pass to `rustc --pretty` . Otherwise it will default to
"expanded".
If |g:rustc_path| is defined, it is used as the path to rustc.
Otherwise it is assumed rustc can be found in $PATH.
-:RustEmitIr [args] *:RustEmitIr*
+:RustEmitIr [args] *:RustEmitIr*
Compiles the current file to LLVM IR and displays the results
in a new split. If the current file has unsaved changes, it
will be saved first using |:update|. If the current file is an
@@ -180,7 +416,7 @@ COMMANDS *rust-commands*
If |g:rustc_path| is defined, it is used as the path to rustc.
Otherwise it is assumed rustc can be found in $PATH.
-:RustEmitAsm [args] *:RustEmitAsm*
+:RustEmitAsm [args] *:RustEmitAsm*
Compiles the current file to assembly and displays the results
in a new split. If the current file has unsaved changes, it
will be saved first using |:update|. If the current file is an
@@ -191,49 +427,52 @@ COMMANDS *rust-commands*
If |g:rustc_path| is defined, it is used as the path to rustc.
Otherwise it is assumed rustc can be found in $PATH.
-:RustPlay *:RustPlay*
- This command will only work if you have web-api.vim installed
- (available at https://github.com/mattn/webapi-vim). It sends the
- current selection, or if nothing is selected, the entirety of the
- current buffer to the Rust playpen, and emits a message with the
- shortened URL to the playpen.
- |g:rust_playpen_url| is the base URL to the playpen, by default
- "https://play.rust-lang.org/".
+Running test(s) ~
- |g:rust_shortener_url| is the base URL for the shortener, by
- default "https://is.gd/"
+:[N]RustTest[!] [options] *:RustTest*
+ Runs a test under the cursor when the current buffer is in a
+ cargo project with "cargo test" command. If the command did
+ not find any test function under the cursor, it stops with an
+ error message.
-:RustFmt *:RustFmt*
- Runs |g:rustfmt_command| on the current buffer. If
- |g:rustfmt_options| is set then those will be passed to the
- executable.
+ When N is given, adjust the size of the new window to N lines
+ or columns.
- If |g:rustfmt_fail_silently| is 0 (the default) then it
- will populate the |location-list| with the errors from
- |g:rustfmt_command|. If |g:rustfmt_fail_silently| is set to 1
- then it will not populate the |location-list|.
+ When ! is given, runs all tests regardless of current cursor
+ position.
-:RustFmtRange *:RustFmtRange*
- Runs |g:rustfmt_command| with selected range. See
- |:RustFmt| for any other information.
+ When [options] is given, it is passed to "cargo" command
+ arguments.
-==============================================================================
-MAPPINGS *rust-mappings*
+ When the current buffer is outside cargo project, the command
+ runs `rustc --test` command instead of "cargo test" as
+ fallback. All tests are run regardless of adding ! since there
+ is no way to run specific test function with rustc. [options]
+ is passed to `rustc` command arguments in the case.
-This plugin defines mappings for |[[| and |]]| to support hanging indents.
+ Takes optional modifiers (see |<mods>|): >vim
+ :tab RustTest
+ :belowright 16RustTest
+ :leftabove vert 80RustTest
+<
+rust.vim Debugging ~
-It also has a few other mappings:
+:RustInfo *:RustInfo*
+ Emits debugging info of the Vim Rust plugin.
- *rust_<D-r>*
-<D-r> Executes |:RustRun| with no arguments.
- Note: This binding is only available in MacVim.
+:RustInfoToClipboard *:RustInfoClipboard*
+ Saves debugging info of the Vim Rust plugin to the default
+ register.
- *rust_<D-R>*
-<D-R> Populates the command line with |:RustRun|! using the
- arguments given to the last invocation, but does not
- execute it.
- Note: This binding is only available in MacVim.
+:RustInfoToFile [filename] *:RustInfoToFile*
+ Saves debugging info of the Vim Rust plugin to the given file,
+ overwriting it.
==============================================================================
- vim:tw=78:sw=4:ts=8:noet:ft=help:norl:
+MAPPINGS *rust-mappings*
+
+This plugin defines mappings for |[[| and |]]| to support hanging indents.
+
+
+vim:tw=78:sw=4:noet:ts=8:ft=help:norl:
diff --git a/runtime/doc/ft_sql.txt b/runtime/doc/ft_sql.txt
index 21a244e67a..68e26d2a5b 100644
--- a/runtime/doc/ft_sql.txt
+++ b/runtime/doc/ft_sql.txt
@@ -728,7 +728,7 @@ You can create as many additional key maps as you like. Generally, the maps
will be specifying different syntax highlight groups.
If you do not wish the default maps created or the key choices do not work on
-your platform (often a case on *nix) you define the following variable in
+your platform (often a case on unix) you define the following variable in
your |init.vim|: >
let g:omni_sql_no_default_maps = 1
@@ -738,7 +738,7 @@ which allows you to make customizations without changing the files that are
included with the Vim distribution. If you wish to customize the maps
create an after/ftplugin/sql.vim (see |after-directory|) and place the same
maps from the ftplugin/sql.vim in it using your own key strokes. <C-C> was
-chosen since it will work on both Windows and *nix platforms. On the windows
+chosen since it will work on both Windows and unix platforms. On the windows
platform you can also use <C-Space> or ALT keys.
diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt
index 1fce9fa491..bf62dba539 100644
--- a/runtime/doc/gui.txt
+++ b/runtime/doc/gui.txt
@@ -33,7 +33,7 @@ Example: this sets "g:gui" to the value of the UI's "rgb" field: >
remembered until the window is opened. The position is
adjusted to make the window fit on the screen (if possible).
- *:win* *:winsize* *E465*
+ *:wi* *:win* *:winsize* *E465*
:win[size] {width} {height}
Set the window height to {width} by {height} characters.
Obsolete, use ":set lines=11 columns=22".
@@ -466,12 +466,11 @@ If no argument is given after :menu at all, then ALL menu items are shown
for the appropriate mode (e.g., Command-line mode for :cmenu).
Special characters in the list, just before the rhs:
-* The menu was defined with "nore" to disallow remapping.
-& The menu was defined with "<script>" to allow remapping script-local
- mappings only.
-s The menu was defined with "<silent>" to avoid showing what it is
- mapped to when triggered.
-- The menu was disabled.
+• * Menu was defined with "nore" to disallow remapping.
+• & Menu was defined with "<script>" to allow remapping script-local mappings.
+• s Menu was defined with "<silent>" to avoid showing what it is mapped to
+ when triggered.
+• - Menu was disabled.
Note that hitting <Tab> while entering a menu name after a menu command may
be used to complete the name of the menu item.
diff --git a/runtime/doc/hebrew.txt b/runtime/doc/hebrew.txt
index 76266d777f..e38385e13a 100644
--- a/runtime/doc/hebrew.txt
+++ b/runtime/doc/hebrew.txt
@@ -30,17 +30,10 @@ Details
+ 'rightleft' ('rl') sets window orientation to right-to-left. This means
that the logical text 'ABC' will be displayed as 'CBA', and will start
drawing at the right edge of the window, not the left edge.
- + 'hkmap' ('hk') sets keyboard mapping to Hebrew, in insert/replace modes.
- + 'aleph' ('al'), numeric, holds the decimal code of Aleph, for keyboard
- mapping.
- + 'hkmapp' ('hkp') sets keyboard mapping to "phonetic hebrew"
-
- NOTE: these three ('hkmap', 'hkmapp' and 'aleph') are obsolete. You should
- use ":set keymap=hebrewp" instead.
-
- + 'delcombine' ('deco'), boolean, if editing UTF-8 encoded Hebrew, allows
- one to remove the niqud or te`amim by pressing 'x' on a character (with
- associated niqud).
+ + 'keymap' ('kmp') sets keyboard mapping. use values "hebrew" or "heprewp"
+ (the latter option enables phonetic mapping)
+ + 'delcombine' ('deco'), boolean, allows one to remove the niqud or
+ te`amim by pressing 'x' on a character (with associated niqud).
+ 'rightleftcmd' ('rlc') makes the command-prompt for searches show up on
the right side. It only takes effect if the window is 'rightleft'.
@@ -62,10 +55,10 @@ Details
+ CTRL-_ in insert/replace modes toggles 'revins' and 'hkmap' as follows:
- When in rightleft window, 'revins' and 'nohkmap' are toggled, since
+ When in rightleft window, 'revins' is toggled, since
English will likely be inserted in this case.
- When in norightleft window, 'revins' 'hkmap' are toggled, since Hebrew
+ When in norightleft window, 'revins' is toggled, since Hebrew
will likely be inserted in this case.
CTRL-_ moves the cursor to the end of the typed text.
diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt
index 07f898f99c..b8526b55e9 100644
--- a/runtime/doc/help.txt
+++ b/runtime/doc/help.txt
@@ -100,12 +100,12 @@ ADVANCED EDITING
------------------------------------------------------------------------------
API (EXTENSIBILITY/SCRIPTING/PLUGINS)
-|api| Nvim API via RPC, Lua and VimL
+|api| Nvim API via RPC, Lua and Vimscript
|ui| Nvim UI protocol
|lua-guide| Nvim Lua guide
|lua| Lua API
|luaref| Lua reference manual
-|luvref| Luv (|vim.loop|) reference manual
+|luvref| Luv (|vim.uv|) reference manual
|autocmd| Event handlers
|job-control| Spawn and control multiple processes
|channel| Nvim asynchronous IO
@@ -120,15 +120,15 @@ PROGRAMMING LANGUAGE SUPPORT
|lsp| Language Server Protocol (LSP)
|diagnostic-api| Diagnostic framework
|treesitter| Incremental syntax parsing
-|indent.txt| automatic indenting for C and other languages
+|indent.txt| automatic indenting for C and other languages
|syntax| syntax highlighting
|filetype| Settings for specific types of files
|quickfix| Commands for a quick edit-compile-fix cycle
-|ft_ada.txt| Ada filetype plugin
-|ft_ps1.txt| PowerShell filetype plugin
-|ft_raku.txt| Raku filetype plugin
-|ft_rust.txt| Rust filetype plugin
-|ft_sql.txt| SQL filetype plugin
+|ft_ada.txt| Ada filetype plugin
+|ft_ps1.txt| PowerShell filetype plugin
+|ft_raku.txt| Raku filetype plugin
+|ft_rust.txt| Rust filetype plugin
+|ft_sql.txt| SQL filetype plugin
------------------------------------------------------------------------------
UI
@@ -169,19 +169,19 @@ DEVELOPING NVIM
|dev-style| Development style guidelines
|debug.txt| Debugging Vim itself
- *standard-plugin-list*
Standard plugins ~
-|matchit.txt| Extended |%| matching
-|pi_gzip.txt| Reading and writing compressed files
-|pi_health.txt| Healthcheck framework
-|pi_msgpack.txt| msgpack utilities
-|pi_netrw.txt| Reading and writing files over a network
-|pi_paren.txt| Highlight matching parens
-|pi_spec.txt| Filetype plugin to work with rpm spec files
-|pi_tar.txt| Tar file explorer
-|pi_zip.txt| Zip archive explorer
-
-LOCAL ADDITIONS: *local-additions*
+ *standard-plugin-list*
+|pi_gzip.txt| Reading and writing compressed files
+|pi_health.txt| Healthcheck framework
+|pi_msgpack.txt| msgpack utilities
+|pi_netrw.txt| Reading and writing files over a network
+|pi_paren.txt| Highlight matching parens
+|pi_spec.txt| Filetype plugin to work with rpm spec files
+|pi_tar.txt| Tar file explorer
+|pi_zip.txt| Zip archive explorer
+
+Local additions ~
+ *local-additions*
------------------------------------------------------------------------------
*bars* Bars example
diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt
index da307dd241..efcc6afae9 100644
--- a/runtime/doc/helphelp.txt
+++ b/runtime/doc/helphelp.txt
@@ -167,7 +167,7 @@ The initial height of the help window can be set with the 'helpheight' option
*help-buffer-options*
When the help buffer is created, several local options are set to make sure
the help text is displayed as it was intended:
- 'iskeyword' nearly all ASCII chars except ' ', '*', '"' and '|'
+ 'iskeyword' nearly all ASCII chars except ' ', "*", '"' and '|'
'foldmethod' "manual"
'tabstop' 8
'arabic' off
@@ -317,9 +317,9 @@ standard Vim help files, except for the first line. If you are writing a new
help file it's best to copy one of the existing files and use it as a
template.
-The first line in a help file should have the following format:
+The first line in a help file should have the following format: >
-*plugin_name.txt* {short description of the plugin}
+ *plugin_name.txt* {short description of the plugin}
The first field is a help tag where ":help plugin_name" will jump to. The
remainder of the line, after a Tab, describes the plugin purpose in a short
@@ -357,10 +357,11 @@ parameter, surround it in backticks, eg. `~/.path/to/init.vim`.
HIGHLIGHTING
-To define a column heading, use a tilde character at the end of the line.
-This will highlight the column heading in a different color. E.g.
+To define a column heading, use a tilde character at the end of the line,
+preceded by a space. This will highlight the column heading in a different
+color. E.g.
-Column heading~
+Column heading ~
To separate sections in a help file, place a series of '=' characters in a
line starting from the first column. The section separator line is highlighted
@@ -383,8 +384,8 @@ The following are highlighted differently in a Vim help file:
The word "Note", "Notes" and similar automagically receive distinctive
highlighting. So do these:
- *Todo something to do
- *Error something wrong
+ Todo something to do
+ Error something wrong
You can find the details in $VIMRUNTIME/syntax/help.vim
diff --git a/runtime/doc/if_perl.txt b/runtime/doc/if_perl.txt
index 3787ca69ba..1fa96e2657 100644
--- a/runtime/doc/if_perl.txt
+++ b/runtime/doc/if_perl.txt
@@ -19,7 +19,7 @@ See |provider-perl| for more information.
working: >
:perl print "Hello"
-:[range]perl << [endmarker]
+:[range]perl << [trim] [{endmarker}]
{script}
{endmarker}
Execute perl script {script}.
@@ -108,7 +108,7 @@ to the next.
==============================================================================
2. The VIM module *perl-vim*
-Perl code gets all of its access to Neovim via the "VIM" module.
+Perl code gets all of its access to Nvim via the "VIM" module.
Overview >
print "Hello" # displays a message
@@ -116,7 +116,7 @@ Overview >
VIM::SetOption("ai") # sets a vim option
$nbuf = VIM::Buffers() # returns the number of buffers
@buflist = VIM::Buffers() # returns array of all buffers
- $mybuf = (VIM::Buffers('a.c'))[0] # returns buffer object for 'a.c'
+ $mybuf = (VIM::Buffers('a.c'))[0] # returns buffer object for 'a.c'
@winlist = VIM::Windows() # returns array of all windows
$nwin = VIM::Windows() # returns the number of windows
($success, $v) = VIM::Eval('&path') # $v: option 'path', $success: 1
diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt
index 4c184ddf94..0836ec54d8 100644
--- a/runtime/doc/if_pyth.txt
+++ b/runtime/doc/if_pyth.txt
@@ -17,26 +17,25 @@ Commands *python-commands*
:[range]py[thon] {stmt}
Execute Python statement {stmt}. A simple check if
the `:python` command is working: >vim
- :python print "Hello"
+ :python print("Hello")
-:[range]py[thon] << [endmarker]
+:[range]py[thon] << [trim] [{endmarker}]
{script}
{endmarker}
Execute Python script {script}. Useful for including
python code in Vim scripts. Requires Python, see
|script-here|.
-The {endmarker} below the {script} must NOT be preceded by any white space.
-
If [endmarker] is omitted from after the "<<", a dot '.' must be used after
-{script}, like for the |:append| and |:insert| commands.
+{script}, like for the |:append| and |:insert| commands. Refer to
+|:let-heredoc| for more information.
Example: >vim
function! IcecreamInitialize()
python << EOF
class StrawberryIcecream:
def __call__(self):
- print 'EAT ME'
+ print('EAT ME')
EOF
endfunction
@@ -101,11 +100,9 @@ To pass arguments you need to set sys.argv[] explicitly. Example: >vim
Here are some examples *python-examples*
>vim
-
:python from vim import *
- :python from string import upper
- :python current.line = upper(current.line)
- :python print "Hello"
+ :python current.line = str.upper(current.line)
+ :python print("Hello")
:python str = current.buffer[42]
Note that changes (such as the "import" statements) persist from one command
@@ -113,26 +110,16 @@ to the next, just like the Python REPL.
*script-here*
When using a script language in-line, you might want to skip this when the
-language isn't supported. Note that this mechanism doesn't work:
+language isn't supported.
>vim
if has('python')
python << EOF
- this will NOT work!
+ print("python works")
EOF
endif
-
-Instead, put the Python command in a function and call that function:
->vim
- if has('python')
- function DefPython()
- python << EOF
- this works
- EOF
- endfunction
- call DefPython()
- endif
-
-Note that "EOF" must be at the start of the line.
+<
+Note that "EOF" must be at the start of the line without preceding white
+space.
==============================================================================
The vim module *python-vim*
@@ -144,7 +131,7 @@ module before using it: >vim
:python import vim
Overview >vim
- :py print "Hello" # displays a message
+ :py print("Hello") # displays a message
:py vim.command(cmd) # execute an Ex command
:py w = vim.windows[n] # gets window "n"
:py cw = vim.current.window # gets the current window
@@ -176,10 +163,6 @@ vim.command(str) *python-command*
# Note the use of single quotes to delimit a string containing
# double quotes
normal('"a2dd"aP')
-< *E659*
- The ":python" command cannot be used recursively with Python 2.2 and
- older. This only works with Python 2.3 and later: >vim
- :py vim.command("python print 'Hello again Python'")
vim.eval(str) *python-eval*
Evaluates the expression str using the vim internal expression
@@ -191,8 +174,8 @@ vim.eval(str) *python-eval*
Examples: >vim
:py text_width = vim.eval("&tw")
:py str = vim.eval("12+12") # NB result is a string! Use
- # string.atoi() to convert to
- # a number.
+ # int() to convert to a
+ # number.
vim.strwidth(str) *python-strwidth*
Like |strwidth()|: returns number of display cells str occupies, tab
@@ -204,8 +187,8 @@ vim.foreach_rtp(callable) *python-foreach_rtp*
are no longer paths. If stopped in case callable returned non-None,
vim.foreach_rtp function returns the value returned by callable.
-vim.chdir(*args, **kwargs) *python-chdir*
-vim.fchdir(*args, **kwargs) *python-fchdir*
+`vim.chdir(*args, **kwargs)` *python-chdir*
+`vim.fchdir(*args, **kwargs)` *python-fchdir*
Run os.chdir or os.fchdir, then all appropriate vim stuff.
Note: you should not use these functions directly, use os.chdir and
os.fchdir instead. Behavior of vim.fchdir is undefined in case
@@ -468,7 +451,7 @@ A trailing '\n' is allowed and ignored, so that you can do: >vim
Buffer object type is available using "Buffer" attribute of vim module.
Examples (assume b is the current buffer) >vim
- :py print b.name # write the buffer file name
+ :py print(b.name) # write the buffer file name
:py b[0] = "hello!!!" # replace the top line
:py b[:] = None # delete the whole buffer
:py del b[:] # delete the whole buffer
@@ -592,17 +575,17 @@ Python 3 *python3*
As Python 3 is the only supported version in Nvim, "python" is synonymous
with "python3" in the current version. However, code that aims to support older
-versions of Neovim, as well as Vim, should prefer to use "python3"
-variants explicitly if Python 3 is required.
+versions of Nvim, as well as Vim, should prefer to use "python3" variants
+explicitly if Python 3 is required.
*:py3* *:python3*
:[range]py3 {stmt}
-:[range]py3 << [endmarker]
+:[range]py3 << [trim] [{endmarker}]
{script}
{endmarker}
:[range]python3 {stmt}
-:[range]python3 << [endmarker]
+:[range]python3 << [trim] [{endmarker}]
{script}
{endmarker}
The `:py3` and `:python3` commands work similar to `:python`. A
@@ -629,7 +612,7 @@ You can test if Python is available with: >vim
if has('pythonx')
echo 'there is Python'
endif
- if has('python3')
+ if has('python3')
echo 'there is Python 3.x'
endif
@@ -652,7 +635,7 @@ To see what version of Python is being used: >vim
:pyx print(sys.version)
<
*:pyxfile* *python_x-special-comments*
-`:pyxfile` works the same as `:py3file`.
+`:pyxfile` works the same as `:py3file`.
*:pyxdo*
`:pyxdo` works the same as `:py3do`.
diff --git a/runtime/doc/if_ruby.txt b/runtime/doc/if_ruby.txt
index d88f59eb73..a5aaac1a5f 100644
--- a/runtime/doc/if_ruby.txt
+++ b/runtime/doc/if_ruby.txt
@@ -19,15 +19,15 @@ downloading Ruby there.
:rub[y] {cmd} Execute Ruby command {cmd}. A command to try it out: >
:ruby print "Hello"
-:rub[y] << [endmarker]
+:rub[y] << [trim] [{endmarker}]
{script}
{endmarker}
Execute Ruby script {script}.
- The {endmarker} after {script} must NOT be preceded by
- any white space.
If [endmarker] is omitted, it defaults to a dot '.'
- like for the |:append| and |:insert| commands.
+ like for the |:append| and |:insert| commands. Refer
+ to |:let-heredoc| for more information.
+
This form of the |:ruby| command is mainly useful for
including ruby code in vim scripts.
diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt
index f3e196b426..853facdaa0 100644
--- a/runtime/doc/indent.txt
+++ b/runtime/doc/indent.txt
@@ -112,7 +112,7 @@ e Reindent a line that starts with "else" when you type the second 'e'.
=~word Like =word, but ignore case.
If you really want to reindent when you type 'o', 'O', 'e', '0', '<', '>',
-'*', ':' or '!', use "<o>", "<O>", "<e>", "<0>", "<<>", "<>>", "<*>", "<:>" or
+"*", ':' or '!', use "<o>", "<O>", "<e>", "<0>", "<<>", "<>>", "<*>", "<:>" or
"<!>", respectively, for those keys.
For an emacs-style indent mode where lines aren't indented every time you
@@ -553,9 +553,9 @@ The examples below assume a 'shiftwidth' of 4.
20 lines).
*cino-star*
- *N Vim searches for unclosed comments at most N lines away. This
+ `*N` Vim searches for unclosed comments at most N lines away. This
limits the time needed to search for the start of a comment.
- If your /* */ comments stop indenting after N lines this is the
+ If your `/* */` comments stop indenting after N lines this is the
value you will want to change.
(default 70 lines).
@@ -574,7 +574,7 @@ The examples below assume a 'shiftwidth' of 4.
them like every other preprocessor directive.
-The defaults, spelled out in full, are:
+The defaults, spelled out in full, are: >
cinoptions=>s,e0,n0,f0,{0,}0,^0,L-1,:s,=s,l0,b0,gs,hs,N0,E0,ps,ts,is,+s,
c3,C0,/0,(2s,us,U0,w0,W0,k0,m0,j0,J0,)20,*70,#0,P0
@@ -1060,19 +1060,19 @@ be configured by setting the following keys in the |Dictionary|
b:sh_indent_defaults to a specific amount or to a |Funcref| that references a
function that will return the amount desired:
-b:sh_indent_options['default'] Default amount of indent.
+b:sh_indent_options["default"] Default amount of indent.
-b:sh_indent_options['continuation-line']
+b:sh_indent_options["continuation-line"]
Amount of indent to add to a continued line.
-b:sh_indent_options['case-labels']
+b:sh_indent_options["case-labels"]
Amount of indent to add for case labels.
(not actually implemented)
-b:sh_indent_options['case-statements']
+b:sh_indent_options["case-statements"]
Amount of indent to add for case statements.
-b:sh_indent_options['case-breaks']
+b:sh_indent_options["case-breaks"]
Amount of indent to add (or more likely
remove) for case breaks.
diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt
index a6aa036b55..75b12fece2 100644
--- a/runtime/doc/index.txt
+++ b/runtime/doc/index.txt
@@ -220,7 +220,7 @@ tag char note action in Normal mode ~
|CTRL-\_CTRL-N| CTRL-\ CTRL-N go to Normal mode (no-op)
|CTRL-\_CTRL-G| CTRL-\ CTRL-G go to Normal mode (no-op)
CTRL-\ a - z reserved for extensions
- CTRL-\ others not used
+ CTRL-\ others not used
|CTRL-]| CTRL-] :ta to ident under cursor
|CTRL-^| CTRL-^ edit Nth alternate file (equivalent to
":e #N")
@@ -233,7 +233,7 @@ tag char note action in Normal mode ~
2 filter Nmove text through the {filter}
command
|!!| !!{filter} 2 filter N lines through the {filter} command
-|quote| "{register} use {register} for next delete, yank or put
+|quote| "{register} use {register} for next delete, yank or put
({.%#:} only work with put)
|#| # 1 search backward for the Nth occurrence of
the ident under the cursor
@@ -475,7 +475,7 @@ tag command action in op-pending and Visual mode ~
|v_a)| a) same as ab
|v_a<| a< "a <>" from '<' to the matching '>'
|v_a>| a> same as a<
-|v_aB| aB "a Block" from "[{" to "]}" (with brackets)
+|v_aB| aB "a Block" from `[{` to `]}` (with brackets)
|v_aW| aW "a WORD" (with white space)
|v_a[| a[ "a []" from '[' to the matching ']'
|v_a]| a] same as a[
@@ -493,7 +493,7 @@ tag command action in op-pending and Visual mode ~
|v_i)| i) same as ib
|v_i<| i< "inner <>" from '<' to the matching '>'
|v_i>| i> same as i<
-|v_iB| iB "inner Block" from "[{" and "]}"
+|v_iB| iB "inner Block" from `[{` and `]}`
|v_iW| iW "inner WORD"
|v_i[| i[ "inner []" from '[' to the matching ']'
|v_i]| i] same as i[
@@ -1096,6 +1096,22 @@ tag command action in Command-line editing mode ~
|c_<Insert>| <Insert> toggle insert/overstrike mode
|c_<LeftMouse>| <LeftMouse> cursor at mouse click
+commands in wildmenu mode (see 'wildmenu')
+
+ <Up> move up to parent
+ <Down> move down to submenu
+ <Left> select the previous match
+ <Right> select the next match
+ <CR> move into submenu when doing menu completion
+ CTRL-E stop completion and go back to original text
+ CTRL-Y accept selected match and stop completion
+ other stop completion and insert the typed character
+
+commands in wildmenu mode with 'wildoptions' set to "pum"
+
+ <PageUp> select a match several entries back
+ <PageDown> select a match several entries forward
+
==============================================================================
5. Terminal mode *terminal-mode-index*
@@ -1110,7 +1126,7 @@ to terminal mode.
You found it, Arthur! *holy-grail*
==============================================================================
-6. EX commands *ex-commands* *ex-cmd-index* *:index*
+6. EX commands *Ex-commands* *ex-cmd-index* *:index*
This is a brief but complete listing of all the ":" commands, without
mentioning any arguments. The optional part of the command name is inside [].
@@ -1159,7 +1175,6 @@ tag command action ~
|:badd| :bad[d] add buffer to the buffer list
|:balt| :balt like ":badd" but also set the alternate file
|:bdelete| :bd[elete] remove a buffer from the buffer list
-|:behave| :be[have] set mouse and selection behavior
|:belowright| :bel[owright] make split window appear right or below
|:bfirst| :bf[irst] go to first buffer in the buffer list
|:blast| :bl[ast] go to last buffer in the buffer list
@@ -1289,6 +1304,7 @@ tag command action ~
|:execute| :exe[cute] execute result of expressions
|:exit| :exi[t] same as ":xit"
|:exusage| :exu[sage] overview of Ex commands
+|:fclose| :fc[lose] close floating window
|:file| :f[ile] show or set the current file name
|:files| :files list all files in the buffer list
|:filetype| :filet[ype] switch file type detection on/off
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt
index e608b431f2..ce2ec36ca3 100644
--- a/runtime/doc/insert.txt
+++ b/runtime/doc/insert.txt
@@ -114,12 +114,16 @@ CTRL-R {register} *i_CTRL-R*
the last delete or yank
'%' the current file name
'#' the alternate file name
- '*' the clipboard contents (X11: primary selection)
+ "*" the clipboard contents (X11: primary selection)
'+' the clipboard contents
'/' the last search pattern
':' the last command-line
'.' the last inserted text
+ *i_CTRL-R_-*
'-' the last small (less than a line) delete
+ register. This is repeatable using |.| since
+ it remembers the register to put instead of
+ the literal text to insert.
*i_CTRL-R_=*
'=' the expression register: you are prompted to
enter an expression (see |expression|)
@@ -221,11 +225,11 @@ CTRL-Y Insert the character which is above the cursor.
able to copy characters from a long line.
*i_CTRL-_*
-CTRL-_ Switch between languages, as follows:
- - When in a rightleft window, revins and nohkmap are toggled,
+CTRL-_ Switch between insert direction, by toggling 'revins', as follows:
+ - When in a rightleft window, 'revins' is toggled,
since English will likely be inserted in this case.
- - When in a norightleft window, revins and hkmap are toggled,
- since Hebrew will likely be inserted in this case.
+ - When in a norightleft window, 'revins' is toggled,
+ since a rightleft language will likely be inserted in this case.
CTRL-_ moves the cursor to the end of the typed text.
@@ -1081,6 +1085,26 @@ Stop completion *compl-stop*
CTRL-X CTRL-Z Stop completion without changing the text.
+AUTO-COMPLETION *compl-autocomplete*
+
+To get basic "autocompletion" without installing a plugin, try this script: >lua
+
+ local triggers = {"."}
+ vim.api.nvim_create_autocmd("InsertCharPre", {
+ buffer = vim.api.nvim_get_current_buf(),
+ callback = function()
+ if vim.fn.pumvisible() == 1 or vim.fn.state("m") == "m" then
+ return
+ end
+ local char = vim.v.char
+ if vim.list_contains(triggers, char) then
+ local key = vim.keycode("<C-x><C-n>")
+ vim.api.nvim_feedkeys(key, "m", false)
+ end
+ end
+ })
+<
+
FUNCTIONS FOR FINDING COMPLETIONS *complete-functions*
This applies to 'completefunc', 'thesaurusfunc' and 'omnifunc'.
@@ -1101,8 +1125,8 @@ cursor column will be replaced with the matches. If the returned value is
larger than the cursor column, the cursor column is used.
Negative return values:
- -2 To cancel silently and stay in completion mode.
- -3 To cancel silently and leave completion mode.
+ -2 To cancel silently and stay in completion mode.
+ -3 To cancel silently and leave completion mode.
Another negative value: completion starts at the cursor column
On the second invocation the arguments are:
@@ -1154,7 +1178,7 @@ items:
item with the same word is already present.
empty when non-zero this match will be added even when it is
an empty string
- user_data custom data which is associated with the item and
+ user_data custom data which is associated with the item and
available in |v:completed_item|; it can be any type;
defaults to an empty string
@@ -1392,7 +1416,7 @@ other versions of HTML. Features:
- after "<" complete tag name depending on context (no div suggestion inside
of an a tag); '/>' indicates empty tags
- inside of tag complete proper attributes (no width attribute for an a tag);
- show also type of attribute; '*' indicates required attributes
+ show also type of attribute; "*" indicates required attributes
- when attribute has limited number of possible values help to complete them
- complete names of entities
- complete values of "class" and "id" attributes with data obtained from
diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt
index 6bf6850ccf..85115fc22b 100644
--- a/runtime/doc/intro.txt
+++ b/runtime/doc/intro.txt
@@ -22,8 +22,7 @@ is not located in the default place. You can jump to subjects like with tags:
Use CTRL-] to jump to a subject under the cursor, use CTRL-T to jump back.
*pronounce*
-Vim is pronounced as one word, like Jim. So Nvim is N-Jim, which sounds like
-"Ninja". Starting Nvim is like performing a roundhouse kick.
+Vim is pronounced as one word, like Jim. So Nvim is "En-Vim", two syllables.
This manual is a reference for all Nvim editor and API features. It is not an
introduction; instead for beginners, there is a hands-on |tutor| and a user
@@ -83,14 +82,14 @@ For the most recent information about sponsoring look on the Vim web site:
https://www.vim.org/sponsor/
-Neovim development is funded separately from Vim:
+Nvim development is funded separately from Vim:
https://neovim.io/#sponsor
==============================================================================
-Credits *credits*
+Credits *credits*
-Most of Vim was written by Bram Moolenaar <Bram@vim.org>.
+Most of Vim was written by Bram Moolenaar <Bram@vim.org> |Bram-Moolenaar|.
Parts of the documentation come from several Vi manuals, written by:
W.N. Joy
@@ -121,7 +120,7 @@ Vim would never have become what it is now, without the help of these people!
Eric Fischer Mac port, 'cindent', and other improvements
Benji Fisher Answering lots of user questions
Bill Foster Athena GUI port (later removed)
- Google Lets me work on Vim one day a week
+ Google Let Bram work on Vim one day a week
Loic Grenie xvim (ideas for multi windows version)
Sven Guckes Vim promoter and previous WWW page maintainer
Darren Hiebert Exuberant ctags
@@ -194,6 +193,19 @@ Elvis Another Vi clone, made by Steve Kirkendall. Very compact but isn't
Vim Nvim is based on Vim. https://www.vim.org/
==============================================================================
+Bram Moolenaar *Bram* *Moolenaar* *Bram-Moolenaar* *brammool*
+
+Nvim is a fork of the Vim ("Vi IMproved") text editor, which was originally
+developed by Bram Moolenaar. Searching his name within the source code of
+Nvim will reveal just how much of his work still remains in Nvim.
+On August 3, 2023, he passed away at the age of 62. If Vim or Nvim have been
+of use to you in your life, please read |Uganda| and consider honoring his
+memory however you may see fit.
+
+Obituary Articles: https://github.com/vim/vim/discussions/12742
+Say Farewell: https://github.com/vim/vim/discussions/12737
+
+==============================================================================
Notation *notation*
When syntax highlighting is used to read this, text that is not typed
@@ -358,7 +370,7 @@ notation meaning equivalent decimal value(s) ~
<kOrigin> keypad origin (middle) *keypad-origin*
<kPageUp> keypad page-up (upper right) *keypad-page-up*
<kPageDown> keypad page-down (lower right) *keypad-page-down*
-<kDel> keypad delete *keypad-delete*
+<kDel> keypad delete *keypad-delete*
<kPlus> keypad + *keypad-plus*
<kMinus> keypad - *keypad-minus*
<kMultiply> keypad * *keypad-multiply*
@@ -372,6 +384,7 @@ notation meaning equivalent decimal value(s) ~
<C-…> control-key *control* *ctrl* *<C-*
<M-…> alt-key or meta-key *META* *ALT* *<M-*
<A-…> same as <M-…> *<A-*
+<T-…> meta-key when it's not alt *<T-*
<D-…> command-key or "super" key *<D-*
----------------------------------------------------------------------- ~
@@ -382,10 +395,10 @@ Note:
- If numlock is on the |TUI| receives plain ASCII values, so mapping <k0>,
<k1>, ..., <k9> and <kPoint> will not work.
- Nvim supports mapping multibyte chars with modifiers such as `<M-ä>`. Which
- combinations actually work depends on the the UI or host terminal.
+ combinations actually work depends on the UI or host terminal.
- When a key is pressed using a meta or alt modifier and no mapping exists for
that keypress, Nvim may behave as though <Esc> was pressed before the key.
-- It is possible to notate combined modifiers (e.g. <C-A-T> for CTRL-ALT-T),
+- It is possible to notate combined modifiers (e.g. <M-C-T> for CTRL-ALT-T),
but your terminal must encode the input for that to work. |tui-input|
*<>*
@@ -517,16 +530,15 @@ CTRL-O in Insert mode you get a beep but you are still in Insert mode, type
<Esc> again.
*i_esc*
- TO mode ~
- Normal Visual Select Insert Replace Cmd-line Ex ~
-FROM mode ~
-Normal v V ^V `*4` *1 R gR : / ? ! Q
-Visual `*2` ^G c C -- : --
-Select `*5` ^O ^G `*6` -- -- --
-Insert <Esc> -- -- <Insert> -- --
-Replace <Esc> -- -- <Insert> -- --
-Command-line `*3` -- -- :start -- --
-Ex :vi -- -- -- -- --
+ FROM mode TO mode ~
+ Normal Visual Select Insert Replace Cmd-line Ex >
+ Normal v V ^V *4 *1 R gR : / ? ! Q
+ Visual *2 ^G c C -- : --
+ Select *5 ^O ^G *6 -- -- --
+ Insert <Esc> -- -- <Insert> -- --
+ Replace <Esc> -- -- <Insert> -- --
+ Command-line `*3` -- -- :start -- --
+ Ex :vi -- -- -- -- --
-- not possible
diff --git a/runtime/doc/job_control.txt b/runtime/doc/job_control.txt
index 37a4e2ebb1..d99c76ab22 100644
--- a/runtime/doc/job_control.txt
+++ b/runtime/doc/job_control.txt
@@ -134,9 +134,6 @@ To send data to the job's stdin, use |chansend()|: >vim
:call chansend(job1, "invalid-command\n")
:call chansend(job1, "exit\n")
<
-A job may be killed with |jobstop()|: >vim
- :call jobstop(job1)
-<
A job may be killed at any time with the |jobstop()| function:
>vim
:call jobstop(job1)
diff --git a/runtime/doc/lsp-extension.txt b/runtime/doc/lsp-extension.txt
deleted file mode 100644
index fe72e9eb18..0000000000
--- a/runtime/doc/lsp-extension.txt
+++ /dev/null
@@ -1,129 +0,0 @@
-*lsp-extension.txt* LSP Extension
-
- NVIM REFERENCE MANUAL
-
-
-The `vim.lsp` Lua module is a framework for building LSP plugins.
-
- 1. Start with |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|.
- 2. Peek at the API: >vim
- :lua print(vim.inspect(vim.lsp))
-< 3. See |lsp-extension-example| for a full example.
-
-================================================================================
-LSP EXAMPLE *lsp-extension-example*
-
-This example is for plugin authors or users who want a lot of control. If you
-are just getting started see |lsp-quickstart|.
-
-For more advanced configurations where just filtering by filetype isn't
-sufficient, you can use the `vim.lsp.start_client()` and
-`vim.lsp.buf_attach_client()` commands to easily customize the configuration
-however you please. For example, if you want to do your own filtering, or
-start a new LSP client based on the root directory for working with multiple
-projects in a single session. To illustrate, the following is a fully working
-Lua example.
-
-The example will:
-1. Check for each new buffer whether or not we want to start an LSP client.
-2. Try to find a root directory by ascending from the buffer's path.
-3. Create a new LSP for that root directory if one doesn't exist.
-4. Attach the buffer to the client for that root directory.
-
->lua
- -- Some path manipulation utilities
- local function is_dir(filename)
- local stat = vim.loop.fs_stat(filename)
- return stat and stat.type == 'directory' or false
- end
-
- local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
- -- Assumes filepath is a file.
- local function dirname(filepath)
- local is_changed = false
- local result = filepath:gsub(path_sep.."([^"..path_sep.."]+)$", function()
- is_changed = true
- return ""
- end)
- return result, is_changed
- end
-
- local function path_join(...)
- return table.concat(vim.tbl_flatten {...}, path_sep)
- end
-
- -- Ascend the buffer's path until we find the rootdir.
- -- is_root_path is a function which returns bool
- local function buffer_find_root_dir(bufnr, is_root_path)
- local bufname = vim.api.nvim_buf_get_name(bufnr)
- if vim.fn.filereadable(bufname) == 0 then
- return nil
- end
- local dir = bufname
- -- Just in case our algorithm is buggy, don't infinite loop.
- for _ = 1, 100 do
- local did_change
- dir, did_change = dirname(dir)
- if is_root_path(dir, bufname) then
- return dir, bufname
- end
- -- If we can't ascend further, then stop looking.
- if not did_change then
- return nil
- end
- end
- end
-
- -- A table to store our root_dir to client_id lookup. We want one LSP per
- -- root directory, and this is how we assert that.
- local javascript_lsps = {}
- -- Which filetypes we want to consider.
- local javascript_filetypes = {
- ["javascript.jsx"] = true;
- ["javascript"] = true;
- ["typescript"] = true;
- ["typescript.jsx"] = true;
- }
-
- -- Create a template configuration for a server to start, minus the root_dir
- -- which we will specify later.
- local javascript_lsp_config = {
- name = "javascript";
- cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") };
- }
-
- -- This needs to be global so that we can call it from the autocmd.
- function check_start_javascript_lsp()
- local bufnr = vim.api.nvim_get_current_buf()
- -- Filter which files we are considering.
- if not javascript_filetypes[vim.api.nvim_buf_get_option(bufnr, 'filetype')] then
- return
- end
- -- Try to find our root directory. We will define this as a directory which contains
- -- node_modules. Another choice would be to check for `package.json`, or for `.git`.
- local root_dir = buffer_find_root_dir(bufnr, function(dir)
- return is_dir(path_join(dir, 'node_modules'))
- -- return vim.fn.filereadable(path_join(dir, 'package.json')) == 1
- -- return is_dir(path_join(dir, '.git'))
- end)
- -- We couldn't find a root directory, so ignore this file.
- if not root_dir then return end
-
- -- Check if we have a client already or start and store it.
- local client_id = javascript_lsps[root_dir]
- if not client_id then
- local new_config = vim.tbl_extend("error", javascript_lsp_config, {
- root_dir = root_dir;
- })
- client_id = vim.lsp.start_client(new_config)
- javascript_lsps[root_dir] = client_id
- end
- -- Finally, attach to the buffer to track changes. This will do nothing if we
- -- are already attached.
- vim.lsp.buf_attach_client(bufnr, client_id)
- end
-
- vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]]
-<
-
- vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index 215515a2d9..5e97628f42 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -24,57 +24,62 @@ QUICKSTART *lsp-quickstart*
Nvim provides an LSP client, but the servers are provided by third parties.
Follow these steps to get LSP features:
- 1. Install language servers using your package manager or by
- following the upstream installation instruction.
+1. Install language servers using your package manager or by following the
+ upstream installation instruction. You can find language servers here:
+ https://microsoft.github.io/language-server-protocol/implementors/servers/
- A list of language servers is available at:
+2. Configure the LSP client per language server. See |vim.lsp.start()| or use
+ this minimal example as a guide: >lua
- https://microsoft.github.io/language-server-protocol/implementors/servers/
-
- 2. Configure the LSP client per language server.
- A minimal example:
->lua
vim.lsp.start({
name = 'my-server-name',
cmd = {'name-of-language-server-executable'},
root_dir = vim.fs.dirname(vim.fs.find({'setup.py', 'pyproject.toml'}, { upward = true })[1]),
})
<
- See |vim.lsp.start()| for details.
+3. Check that the server attached to the buffer: >
+ :lua =vim.lsp.get_clients()
- 3. Configure keymaps and autocmds to utilize LSP features.
- See |lsp-config|.
+4. Configure keymaps and autocmds to use LSP features. See |lsp-config|.
*lsp-config*
-
-Starting a LSP client will automatically report diagnostics via
-|vim.diagnostic|. Read |vim.diagnostic.config()| to learn how to customize the
-display.
-
-It also sets some buffer options if the options are otherwise empty and if the
-language server supports the functionality.
-
-- 'omnifunc' is set to |vim.lsp.omnifunc()|. This allows to trigger completion
- using |i_CTRL-X_CTRL-O|
+ *lsp-defaults*
+When the LSP client starts it enables diagnostics |vim.diagnostic| (see
+|vim.diagnostic.config()| to customize). It also sets various default options,
+listed below, if (1) the language server supports the functionality and (2)
+the options are empty or were set by the builtin runtime (ftplugin) files. The
+options are not restored when the LSP client is stopped or detached.
+
+- 'omnifunc' is set to |vim.lsp.omnifunc()|, use |i_CTRL-X_CTRL-O| to trigger
+ completion.
- 'tagfunc' is set to |vim.lsp.tagfunc()|. This enables features like
go-to-definition, |:tjump|, and keymaps like |CTRL-]|, |CTRL-W_]|,
|CTRL-W_}| to utilize the language server.
-- 'formatexpr' is set to |vim.lsp.formatexpr()| if both 'formatprg' and
- 'formatexpr' are empty. This allows to format lines via |gq| if the language
- server supports it.
+- 'formatexpr' is set to |vim.lsp.formatexpr()|, so you can format lines via
+ |gq| if the language server supports it.
+ - To opt out of this use |gw| instead of gq, or set 'formatexpr' on LspAttach.
+- |K| is mapped to |vim.lsp.buf.hover()| unless |'keywordprg'| is customized or
+ a custom keymap for `K` exists.
+
+ *lsp-defaults-disable*
+To override the above defaults, set or unset the options on |LspAttach|: >lua
+ vim.api.nvim_create_autocmd('LspAttach', {
+ callback = function(ev)
+ vim.bo[ev.buf].formatexpr = nil
+ vim.bo[ev.buf].omnifunc = nil
+ vim.keymap.del("n", "K", { buffer = ev.buf })
+ end,
+ })
-To use other LSP features like hover, rename, etc. you can setup some
-additional keymaps. It's recommended to setup them in a |LspAttach| autocmd to
-ensure they're only active if there is a LSP client running. An example:
->lua
+To use other LSP features like hover, rename, etc. you can set other keymaps
+on |LspAttach|. Example: >lua
vim.api.nvim_create_autocmd('LspAttach', {
callback = function(args)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
end,
})
-<
-The most used functions are:
+The most common functions are:
- |vim.lsp.buf.hover()|
- |vim.lsp.buf.format()|
@@ -98,11 +103,9 @@ calls behind capability checks:
<
To learn what capabilities are available you can run the following command in
-a buffer with a started LSP client:
+a buffer with a started LSP client: >vim
->vim
- :lua =vim.lsp.get_active_clients()[1].server_capabilities
-<
+ :lua =vim.lsp.get_clients()[1].server_capabilities
Full list of features provided by default can be found in |lsp-buf|.
@@ -110,35 +113,28 @@ Full list of features provided by default can be found in |lsp-buf|.
FAQ *lsp-faq*
- Q: How to force-reload LSP?
- A: Stop all clients, then reload the buffer. >vim
-
- :lua vim.lsp.stop_client(vim.lsp.get_active_clients())
+- A: Stop all clients, then reload the buffer. >vim
+ :lua vim.lsp.stop_client(vim.lsp.get_clients())
:edit
- Q: Why isn't completion working?
- A: In the buffer where you want to use LSP, check that 'omnifunc' is set to
- "v:lua.vim.lsp.omnifunc": >vim
-
- :verbose set omnifunc?
-
-< Some other plugin may be overriding the option. To avoid that, you could
- set the option in an |after-directory| ftplugin, e.g.
- "after/ftplugin/python.vim".
+- A: In the buffer where you want to use LSP, check that 'omnifunc' is set to
+ "v:lua.vim.lsp.omnifunc": `:verbose set omnifunc?`
+ - Some other plugin may be overriding the option. To avoid that you could
+ set the option in an |after-directory| ftplugin, e.g.
+ "after/ftplugin/python.vim".
- Q: How do I run a request synchronously (e.g. for formatting on file save)?
- A: Check if the function has an `async` parameter and set the value to
- false.
-
- E.g. code formatting: >vim
+- A: Check if the function has an `async` parameter and set the value to
+ false. E.g. code formatting: >vim
" Auto-format *.rs (rust) files prior to saving them
" (async = false is the default for format)
autocmd BufWritePre *.rs lua vim.lsp.buf.format({ async = false })
-
<
*lsp-vs-treesitter*
- Q: How do LSP and Treesitter compare?
- A: LSP requires a client and language server. The language server uses
+- A: LSP requires a client and language server. The language server uses
semantic analysis to understand code at a project level. This provides
language servers with the ability to rename across files, find
definitions in external libraries and more.
@@ -161,128 +157,85 @@ The `vim.lsp.buf_…` functions perform operations for all LSP clients attached
to the given buffer. |lsp-buf|
LSP request/response handlers are implemented as Lua functions (see
-|lsp-handler|). The |vim.lsp.handlers| table defines default handlers used
-when creating a new client. Keys are LSP method names: >vim
-
- :lua print(vim.inspect(vim.tbl_keys(vim.lsp.handlers)))
-<
+|lsp-handler|).
*lsp-method*
-Methods are the names of requests and notifications as defined by the LSP
-specification. These LSP requests/notifications are defined by default:
-
- callHierarchy/incomingCalls
- callHierarchy/outgoingCalls
- textDocument/codeAction
- textDocument/completion
- textDocument/declaration*
- textDocument/definition
- textDocument/documentHighlight
- textDocument/documentSymbol
- textDocument/formatting
- textDocument/hover
- textDocument/implementation*
- textDocument/publishDiagnostics
- textDocument/rangeFormatting
- textDocument/references
- textDocument/rename
- textDocument/signatureHelp
- textDocument/typeDefinition*
- window/logMessage
- window/showMessage
- window/showDocument
- window/showMessageRequest
- workspace/applyEdit
- workspace/symbol
-
-* NOTE: These are sometimes not implemented by servers.
-
- *lsp-handler*
+Requests and notifications defined by the LSP specification are referred to as
+"LSP methods". The Nvim LSP client provides default handlers in the global
+|vim.lsp.handlers| table, you can list them with this command: >vim
-lsp-handlers are functions with special signatures that are designed to handle
-responses and notifications from LSP servers.
-
-For |lsp-request|, each |lsp-handler| has this signature: >
-
- function(err, result, ctx, config)
+ :lua vim.print(vim.tbl_keys(vim.lsp.handlers))
<
- Parameters: ~
- {err} (table|nil)
- When the language server is unable to complete a
- request, a table with information about the error is
- sent. Otherwise, it is `nil`. See |lsp-response|.
- {result} (Result | Params | nil)
- When the language server is able to successfully
- complete a request, this contains the `result` key of
- the response. See |lsp-response|.
- {ctx} (table)
- Context describes additional calling state associated
- with the handler. It consists of the following key,
- value pairs:
-
- {method} (string)
- The |lsp-method| name.
- {client_id} (number)
- The ID of the |vim.lsp.client|.
- {bufnr} (Buffer)
- Buffer handle, or 0 for current.
- {params} (table|nil)
- The parameters used in the original
- request which resulted in this handler
- call.
- {config} (table)
- Configuration for the handler.
-
- Each handler can define its own configuration table
- that allows users to customize the behavior of a
- particular handler.
-
- To configure a particular |lsp-handler|, see:
- |lsp-handler-configuration|
-
-
- Returns: ~
- The |lsp-handler| can respond by returning two values: `result, err`
- Where `err` must be shaped like an RPC error:
- `{ code, message, data? }`
+They are also listed below. Note that handlers depend on server support: they
+won't run if your server doesn't support them.
+
+- callHierarchy/incomingCalls
+- callHierarchy/outgoingCalls
+- textDocument/codeAction
+- textDocument/completion
+- textDocument/declaration*
+- textDocument/definition
+- textDocument/diagnostic
+- textDocument/documentHighlight
+- textDocument/documentSymbol
+- textDocument/formatting
+- textDocument/hover
+- textDocument/implementation*
+- textDocument/inlayHint
+- textDocument/publishDiagnostics
+- textDocument/rangeFormatting
+- textDocument/references
+- textDocument/rename
+- textDocument/semanticTokens/full
+- textDocument/semanticTokens/full/delta
+- textDocument/signatureHelp
+- textDocument/typeDefinition*
+- window/logMessage
+- window/showMessage
+- window/showDocument
+- window/showMessageRequest
+- workspace/applyEdit
+- workspace/configuration
+- workspace/executeCommand
+- workspace/inlayHint/refresh
+- workspace/symbol
+- workspace/workspaceFolders
- You can use |vim.lsp.rpc.rpc_response_error()| to create this object.
+ *lsp-handler*
+LSP handlers are functions that handle |lsp-response|s to requests made by Nvim
+to the server. (Notifications, as opposed to requests, are fire-and-forget:
+there is no response, so they can't be handled. |lsp-notification|)
-For |lsp-notification|, each |lsp-handler| has this signature: >
+Each response handler has this signature: >
- function(err, result, ctx, config)
+ function(err, result, ctx, config)
<
Parameters: ~
- {err} (nil)
- This is always `nil`.
- See |lsp-notification|
- {result} (Result)
- This contains the `params` key of the notification.
- See |lsp-notification|
- {ctx} (table)
- Context describes additional calling state associated
- with the handler. It consists of the following key,
- value pairs:
-
- {method} (string)
- The |lsp-method| name.
- {client_id} (number)
- The ID of the |vim.lsp.client|.
- {config} (table)
- Configuration for the handler.
-
- Each handler can define its own configuration table
- that allows users to customize the behavior of a
- particular handler.
-
- For an example, see:
- |vim.lsp.diagnostic.on_publish_diagnostics()|
-
- To configure a particular |lsp-handler|, see:
- |lsp-handler-configuration|
+ - {err} (table|nil) Error info dict, or `nil` if the request
+ completed.
+ - {result} (Result | Params | nil) `result` key of the |lsp-response| or
+ `nil` if the request failed.
+ - {ctx} (table) Table of calling state associated with the
+ handler, with these keys:
+ - {method} (string) |lsp-method| name.
+ - {client_id} (number) |vim.lsp.client| identifier.
+ - {bufnr} (Buffer) Buffer handle.
+ - {params} (table|nil) Request parameters table.
+ - {version} (number) Document version at time of
+ request. Handlers can compare this to the
+ current document version to check if the
+ response is "stale". See also |b:changedtick|.
+ - {config} (table) Handler-defined configuration table, which allows
+ users to customize handler behavior.
+ For an example, see:
+ |vim.lsp.diagnostic.on_publish_diagnostics()|
+ To configure a particular |lsp-handler|, see:
+ |lsp-handler-configuration|
Returns: ~
- The |lsp-handler|'s return value will be ignored.
+ Two values `result, err` where `err` is shaped like an RPC error: >
+ { code, message, data? }
+< You can use |vim.lsp.rpc.rpc_response_error()| to create this object.
*lsp-handler-configuration*
@@ -355,42 +308,37 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method
Handlers can be set by:
- Setting a field in vim.lsp.handlers. *vim.lsp.handlers*
- vim.lsp.handlers is a global table that contains the default mapping of
- |lsp-method| names to |lsp-handlers|.
-
- To override the handler for the `"textDocument/definition"` method: >lua
+ vim.lsp.handlers is a global table that contains the default mapping of
+ |lsp-method| names to |lsp-handlers|. To override the handler for the
+ `"textDocument/definition"` method: >lua
vim.lsp.handlers["textDocument/definition"] = my_custom_default_definition
<
-- The {handlers} parameter for |vim.lsp.start_client()|.
- This will set the |lsp-handler| as the default handler for this server.
+- The {handlers} parameter of |vim.lsp.start()|. This sets the default
+ |lsp-handler| for the server being started. Example: >lua
- For example: >lua
-
- vim.lsp.start_client {
+ vim.lsp.start {
..., -- Other configuration omitted.
handlers = {
["textDocument/definition"] = my_custom_server_definition
},
}
-- The {handler} parameter for |vim.lsp.buf_request()|.
- This will set the |lsp-handler| ONLY for the current request.
+- The {handler} parameter of |vim.lsp.buf_request_all()|. This sets
+ the |lsp-handler| ONLY for the given request(s). Example: >lua
- For example: >lua
-
- vim.lsp.buf_request(
+ vim.lsp.buf_request_all(
0,
"textDocument/definition",
- definition_params,
- my_request_custom_definition
+ my_request_params,
+ my_handler
)
<
In summary, the |lsp-handler| will be chosen based on the current |lsp-method|
in the following order:
-1. Handler passed to |vim.lsp.buf_request()|, if any.
-2. Handler defined in |vim.lsp.start_client()|, if any.
+1. Handler passed to |vim.lsp.buf_request_all()|, if any.
+2. Handler defined in |vim.lsp.start()|, if any.
3. Handler defined in |vim.lsp.handlers|, if any.
*vim.lsp.log_levels*
@@ -411,12 +359,12 @@ name: >lua
<
*lsp-response*
-For the format of the response message, see:
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
+LSP response shape:
+https://microsoft.github.io/language-server-protocol/specifications/specification-current/#responseMessage
*lsp-notification*
-For the format of the notification message, see:
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
+LSP notification shape:
+https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
*lsp-on-list-handler*
@@ -459,6 +407,8 @@ LspReferenceText used for highlighting "text" references
LspReferenceRead used for highlighting "read" references
*hl-LspReferenceWrite*
LspReferenceWrite used for highlighting "write" references
+ *hl-LspInlayHint*
+LspInlayHint used for highlighting inlay hints
*lsp-highlight-codelens*
@@ -482,13 +432,78 @@ LspSignatureActiveParameter
Used to highlight the active parameter in the signature help. See
|vim.lsp.handlers.signature_help()|.
+------------------------------------------------------------------------------
+LSP SEMANTIC HIGHLIGHTS *lsp-semantic-highlight*
+
+When available, the LSP client highlights code using |lsp-semantic_tokens|,
+which are another way that LSP servers can provide information about source
+code. Note that this is in addition to treesitter syntax highlighting;
+semantic highlighting does not replace syntax highlighting.
+
+The server will typically provide one token per identifier in the source code.
+The token will have a `type` such as "function" or "variable", and 0 or more
+`modifier`s such as "readonly" or "deprecated." The standard types and
+modifiers are described here:
+https://microsoft.github.io/language-server-protocol/specification/#textDocument_semanticTokens
+LSP servers may also use off-spec types and modifiers.
+
+The LSP client adds one or more highlights for each token. The highlight
+groups are derived from the token's type and modifiers:
+ • `@lsp.type.<type>.<ft>` for the type
+ • `@lsp.mod.<mod>.<ft>` for each modifier
+ • `@lsp.typemod.<type>.<mod>.<ft>` for each modifier
+Use |:Inspect| to view the highlights for a specific token. Use |:hi| or
+|nvim_set_hl()| to change the appearance of semantic highlights: >vim
+
+ hi @lsp.type.function guifg=Yellow " function names are yellow
+ hi @lsp.type.variable.lua guifg=Green " variables in lua are green
+ hi @lsp.mod.deprecated gui=strikethrough " deprecated is crossed out
+ hi @lsp.typemod.function.async guifg=Blue " async functions are blue
+<
+The value |vim.highlight.priorities|`.semantic_tokens` is the priority of the
+`@lsp.type.*` highlights. The `@lsp.mod.*` and `@lsp.typemod.*` highlights
+have priorities one and two higher, respectively.
+
+You can disable semantic highlights by clearing the highlight groups: >lua
+
+ -- Hide semantic highlights for functions
+ vim.api.nvim_set_hl(0, '@lsp.type.function', {})
+
+ -- Hide all semantic highlights
+ for _, group in ipairs(vim.fn.getcompletion("@lsp", "highlight")) do
+ vim.api.nvim_set_hl(0, group, {})
+ end
+<
+You probably want these inside a |ColorScheme| autocommand.
+
+Use |LspTokenUpdate| and |vim.lsp.semantic_tokens.highlight_token()| for more
+complex highlighting.
+
+The following groups are linked by default to standard |group-name|s:
+>
+ @lsp.type.class Structure
+ @lsp.type.decorator Function
+ @lsp.type.enum Structure
+ @lsp.type.enumMember Constant
+ @lsp.type.function Function
+ @lsp.type.interface Structure
+ @lsp.type.macro Macro
+ @lsp.type.method Function
+ @lsp.type.namespace Structure
+ @lsp.type.parameter Identifier
+ @lsp.type.property Identifier
+ @lsp.type.struct Structure
+ @lsp.type.type Type
+ @lsp.type.typeParameter TypeDef
+ @lsp.type.variable Identifier
+<
==============================================================================
EVENTS *lsp-events*
- *LspAttach*
-After an LSP client attaches to a buffer. The |autocmd-pattern| is the
-name of the buffer. When used from Lua, the client ID is passed to the
-callback in the "data" table. Example: >lua
+LspAttach *LspAttach*
+ After an LSP client attaches to a buffer. The |autocmd-pattern| is the
+ name of the buffer. When used from Lua, the client ID is passed to the
+ callback in the "data" table. Example: >lua
vim.api.nvim_create_autocmd("LspAttach", {
callback = function(args)
@@ -503,10 +518,11 @@ callback in the "data" table. Example: >lua
end,
})
<
- *LspDetach*
-Just before an LSP client detaches from a buffer. The |autocmd-pattern| is the
-name of the buffer. When used from Lua, the client ID is passed to the
-callback in the "data" table. Example: >lua
+
+LspDetach *LspDetach*
+ Just before an LSP client detaches from a buffer. The |autocmd-pattern|
+ is the name of the buffer. When used from Lua, the client ID is passed
+ to the callback in the "data" table. Example: >lua
vim.api.nvim_create_autocmd("LspDetach", {
callback = function(args)
@@ -516,20 +532,105 @@ callback in the "data" table. Example: >lua
end,
})
<
-Also the following |User| |autocommand|s are provided:
-LspProgressUpdate *LspProgressUpdate*
- Upon receipt of a progress notification from the server. See
- |vim.lsp.util.get_progress_messages()|.
+LspNotify *LspNotify*
+ This event is triggered after each successful notification sent to an
+ LSP server.
+
+ When used from Lua, the client_id, LSP method, and parameters are sent
+ in the "data" table. Example: >lua
+
+ vim.api.nvim_create_autocmd('LspNotify', {
+ callback = function(args)
+ local bufnr = args.buf
+ local client_id = args.data.client_id
+ local method = args.data.method
+ local params = args.data.params
+
+ -- do something with the notification
+ if method == 'textDocument/...' then
+ update_buffer(bufnr)
+ end
+ end,
+ })
+<
+
+LspProgress *LspProgress*
+ Upon receipt of a progress notification from the server. Notifications can
+ be polled from a `progress` ring buffer of a |vim.lsp.client| or use
+ |vim.lsp.status()| to get an aggregate message
+
+ If the server sends a "work done progress", the `pattern` is set to `kind`
+ (one of `begin`, `report` or `end`).
+
+ When used from Lua, the event contains a `data` table with `client_id` and
+ `result` properties. `result` will contain the request params sent by the
+ server.
+
+ Example: >vim
+ autocmd LspProgress * redrawstatus
+<
LspRequest *LspRequest*
- After a change to the active set of pending LSP requests. See {requests}
- in |vim.lsp.client|.
+ For each request sent to an LSP server, this event is triggered for
+ every change to the request's status. The status can be one of
+ `pending`, `complete`, or `cancel` and is sent as the {type} on the
+ "data" table passed to the callback function.
+
+ It triggers when the initial request is sent ({type} == `pending`) and
+ when the LSP server responds ({type} == `complete`). If a cancellation
+ is requested using `client.cancel_request(request_id)`, then this event
+ will trigger with {type} == `cancel`.
+
+ When used from Lua, the client ID, request ID, and request are sent in
+ the "data" table. See {requests} in |vim.lsp.client| for details on the
+ {request} value. If the request type is `complete`, the request will be
+ deleted from the client's pending requests table immediately after
+ calling the event's callbacks. Example: >lua
+
+ vim.api.nvim_create_autocmd('LspRequest', {
+ callback = function(args)
+ local bufnr = args.buf
+ local client_id = args.data.client_id
+ local request_id = args.data.request_id
+ local request = args.data.request
+ if request.type == 'pending' then
+ -- do something with pending requests
+ track_pending(client_id, bufnr, request_id, request)
+ elseif request.type == 'cancel' then
+ -- do something with pending cancel requests
+ track_canceling(client_id, bufnr, request_id, request)
+ elseif request.type == 'complete' then
+ -- do something with finished requests. this pending
+ -- request entry is about to be removed since it is complete
+ track_finish(client_id, bufnr, request_id, request)
+ end
+ end,
+ })
+<
+
+LspTokenUpdate *LspTokenUpdate*
+ When a visible semantic token is sent or updated by the LSP server, or
+ when an existing token becomes visible for the first time. The
+ |autocmd-pattern| is the name of the buffer. When used from Lua, the
+ token and client ID are passed to the callback in the "data" table. The
+ token fields are documented in |vim.lsp.semantic_tokens.get_at_pos()|.
+ Example:
+ >lua
-Example: >vim
- autocmd User LspProgressUpdate redrawstatus
- autocmd User LspRequest redrawstatus
+ vim.api.nvim_create_autocmd('LspTokenUpdate', {
+ callback = function(args)
+ local token = args.data.token
+ if token.type == 'variable' and not token.modifiers.readonly then
+ vim.lsp.semantic_tokens.highlight_token(
+ token, args.buf, args.data.client_id, 'MyMutableVariableHighlight'
+ )
+ end
+ end,
+ })
<
+ Note: doing anything other than calling
+ |vim.lsp.semantic_tokens.highlight_token()| is considered experimental.
==============================================================================
Lua module: vim.lsp *lsp-core*
@@ -541,8 +642,12 @@ buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()*
Without calling this, the server won't be notified of changes to a buffer.
Parameters: ~
- • {bufnr} (number) Buffer handle, or 0 for current
- • {client_id} (number) Client id
+ • {bufnr} (integer) Buffer handle, or 0 for current
+ • {client_id} (integer) Client id
+
+ Return: ~
+ (boolean) success `true` if client was attached successfully; `false`
+ otherwise
buf_detach_client({bufnr}, {client_id}) *vim.lsp.buf_detach_client()*
Detaches client from the specified buffer. Note: While the server is
@@ -550,66 +655,65 @@ buf_detach_client({bufnr}, {client_id}) *vim.lsp.buf_detach_client()*
send notifications should it ignore this notification.
Parameters: ~
- • {bufnr} (number) Buffer handle, or 0 for current
- • {client_id} (number) Client id
+ • {bufnr} (integer) Buffer handle, or 0 for current
+ • {client_id} (integer) Client id
buf_is_attached({bufnr}, {client_id}) *vim.lsp.buf_is_attached()*
Checks if a buffer is attached for a particular client.
Parameters: ~
- • {bufnr} (number) Buffer handle, or 0 for current
- • {client_id} (number) the client id
+ • {bufnr} (integer) Buffer handle, or 0 for current
+ • {client_id} (integer) the client id
buf_notify({bufnr}, {method}, {params}) *vim.lsp.buf_notify()*
Send a notification to a server
Parameters: ~
- • {bufnr} (number|nil) The number of the buffer
+ • {bufnr} (integer|nil) The number of the buffer
• {method} (string) Name of the request method
• {params} (any) Arguments to send to the server
Return: ~
- true if any client returns true; false otherwise
+ (boolean) success true if any client returns true; false otherwise
*vim.lsp.buf_request_all()*
-buf_request_all({bufnr}, {method}, {params}, {callback})
- Sends an async request for all active clients attached to the buffer.
- Executes the callback on the combined result. Parameters are the same as
- |vim.lsp.buf_request()| but the return result and callback are different.
+buf_request_all({bufnr}, {method}, {params}, {handler})
+ Sends an async request for all active clients attached to the buffer and
+ executes the `handler` callback with the combined result.
Parameters: ~
- • {bufnr} (number) Buffer handle, or 0 for current.
- • {method} (string) LSP method name
- • {params} (table|nil) Parameters to send to the server
- • {callback} (function) The callback to call when all requests are
- finished.
+ • {bufnr} (integer) Buffer handle, or 0 for current.
+ • {method} (string) LSP method name
+ • {params} (table|nil) Parameters to send to the server
+ • {handler} (function) Handler called after all requests are completed.
+ Server results are passed as a `client_id:result` map.
Return: ~
- (function) A function that will cancel all requests which is the same
- as the one returned from `buf_request`.
+ (function) cancel Function that cancels all requests.
*vim.lsp.buf_request_sync()*
buf_request_sync({bufnr}, {method}, {params}, {timeout_ms})
Sends a request to all server and waits for the response of all of them.
Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the
- result. Parameters are the same as |vim.lsp.buf_request()| but the return
- result is different. Wait maximum of {timeout_ms} (default 1000) ms.
+ result. Parameters are the same as |vim.lsp.buf_request_all()| but the
+ result is different. Waits a maximum of {timeout_ms} (default 1000) ms.
Parameters: ~
- • {bufnr} (number) Buffer handle, or 0 for current.
+ • {bufnr} (integer) Buffer handle, or 0 for current.
• {method} (string) LSP method name
• {params} (table|nil) Parameters to send to the server
- • {timeout_ms} (number|nil) Maximum time in milliseconds to wait for a
+ • {timeout_ms} (integer|nil) Maximum time in milliseconds to wait for a
result. Defaults to 1000
- Return: ~
- Map of client_id:request_result. On timeout, cancel or error, returns
- `(nil, err)` where `err` is a string describing the failure reason.
+ Return (multiple): ~
+ (table) result Map of client_id:request_result.
+ (string|nil) err On timeout, cancel, or error, `err` is a string
+ describing the failure reason, and `result` is nil.
client() *vim.lsp.client*
LSP client object. You can get an active client object via
- |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|.
+ |vim.lsp.get_client_by_id()| or |vim.lsp.get_clients()|.
• Methods:
• request(method, params, [handler], bufnr) Sends a request to the
@@ -658,35 +762,42 @@ client() *vim.lsp.client*
server. Entries are key-value pairs with the key being the request ID
while the value is a table with `type`, `bufnr`, and `method`
key-value pairs. `type` is either "pending" for an active request, or
- "cancel" for a cancel request.
+ "cancel" for a cancel request. It will be "complete" ephemerally while
+ executing |LspRequest| autocmds when replies are received from the
+ server.
• {config} (table): copy of the table that was passed by the user to
|vim.lsp.start_client()|.
• {server_capabilities} (table): Response from the server sent on
`initialize` describing the server's capabilities.
+ • {progress} A ring buffer (|vim.ringbuf()|) containing progress
+ messages sent by the server.
client_is_stopped({client_id}) *vim.lsp.client_is_stopped()*
Checks whether a client is stopped.
Parameters: ~
- • {client_id} (number)
+ • {client_id} (integer)
Return: ~
- true if client is stopped, false otherwise.
+ (boolean) stopped true if client is stopped, false otherwise.
- *vim.lsp.for_each_buffer_client()*
-for_each_buffer_client({bufnr}, {fn})
- Invokes a function for each LSP client attached to a buffer.
+commands *vim.lsp.commands*
+ Registry for client side commands. This is an extension point for plugins
+ to handle custom commands which are not part of the core language server
+ protocol specification.
- Parameters: ~
- • {bufnr} (number) Buffer number
- • {fn} (function) Function to run on each client attached to buffer
- {bufnr}. The function takes the client, client ID, and buffer
- number as arguments. Example: >lua
+ The registry is a table where the key is a unique command name, and the
+ value is a function which is called if any LSP action (code action, code
+ lenses, ...) triggers the command.
- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr)
- print(vim.inspect(client))
- end)
-<
+ If a LSP response contains a command for which no matching entry is
+ available in this registry, the command will be executed via the LSP
+ server using `workspace/executeCommand`.
+
+ The first argument to the function will be the `Command`: Command title:
+ String command: String arguments?: any[]
+
+ The second argument is the `ctx` of |lsp-handler|
formatexpr({opts}) *vim.lsp.formatexpr()*
Provides an interface between the built-in client and a `formatexpr`
@@ -694,8 +805,8 @@ formatexpr({opts}) *vim.lsp.formatexpr()*
Currently only supports a single client. This can be set via `setlocal
formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in
- `on_attach` via `vim.api.nvim_buf_set_option(bufnr, 'formatexpr',
- 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`.
+ `on_attach` via `vim.bo[bufnr].formatexpr =
+ 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'`.
Parameters: ~
• {opts} (table) options for customizing the formatting expression
@@ -703,62 +814,64 @@ formatexpr({opts}) *vim.lsp.formatexpr()*
• timeout_ms (default 500ms). The timeout period for the
formatting request.
-get_active_clients({filter}) *vim.lsp.get_active_clients()*
- Get active clients.
-
- Parameters: ~
- • {filter} (table|nil) A table with key-value pairs used to filter the
- returned clients. The available keys are:
- • id (number): Only return clients with the given id
- • bufnr (number): Only return clients attached to this
- buffer
- • name (string): Only return clients with the given name
-
- Return: ~
- (table) List of |vim.lsp.client| objects
-
*vim.lsp.get_buffers_by_client_id()*
get_buffers_by_client_id({client_id})
Returns list of buffers attached to client_id.
Parameters: ~
- • {client_id} (number) client id
+ • {client_id} (integer) client id
Return: ~
- (list) of buffer ids
+ integer[] buffers list of buffer ids
get_client_by_id({client_id}) *vim.lsp.get_client_by_id()*
Gets a client by id, or nil if the id is invalid. The returned client may
not yet be fully initialized.
Parameters: ~
- • {client_id} (number) client id
+ • {client_id} (integer) client id
+
+ Return: ~
+ (nil|lsp.Client) client rpc object
+
+get_clients({filter}) *vim.lsp.get_clients()*
+ Get active clients.
+
+ Parameters: ~
+ • {filter} (table|nil) A table with key-value pairs used to filter the
+ returned clients. The available keys are:
+ • id (number): Only return clients with the given id
+ • bufnr (number): Only return clients attached to this
+ buffer
+ • name (string): Only return clients with the given name
+ • method (string): Only return clients supporting the given
+ method
Return: ~
- |vim.lsp.client| object, or nil
+ lsp.Client []: List of |vim.lsp.client| objects
get_log_path() *vim.lsp.get_log_path()*
Gets the path of the logfile used by the LSP client.
Return: ~
- (String) Path to logfile.
+ (string) path to log file
omnifunc({findstart}, {base}) *vim.lsp.omnifunc()*
Implements 'omnifunc' compatible LSP completion.
Parameters: ~
- • {findstart} (number) 0 or 1, decides behavior
- • {base} (number) findstart=0, text to match against
+ • {findstart} (integer) 0 or 1, decides behavior
+ • {base} (integer) findstart=0, text to match against
Return: ~
- (number) Decided by {findstart}:
+ integer|table Decided by {findstart}:
• findstart=0: column where the completion starts, or -2 or -3
• findstart=1: list of matches (actually just calls |complete()|)
See also: ~
- |complete-functions|
- |complete-items|
- |CompleteDone|
+ • |complete-functions|
+ • |complete-items|
+ • |CompleteDone|
set_log_level({level}) *vim.lsp.set_log_level()*
Sets the global log level for LSP logging.
@@ -770,10 +883,10 @@ set_log_level({level}) *vim.lsp.set_log_level()*
Use `lsp.log_levels` for reverse lookup.
Parameters: ~
- • {level} (number|string) the case insensitive level name or number
+ • {level} (integer|string) the case insensitive level name or number
See also: ~
- |vim.lsp.log_levels|
+ • |vim.lsp.log_levels|
start({config}, {opts}) *vim.lsp.start()*
Create a new LSP client and start a language server or reuses an already
@@ -781,12 +894,11 @@ start({config}, {opts}) *vim.lsp.start()*
the current buffer to the client.
Example: >lua
-
- vim.lsp.start({
- name = 'my-server-name',
- cmd = {'name-of-language-server-executable'},
- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
- })
+ vim.lsp.start({
+ name = 'my-server-name',
+ cmd = {'name-of-language-server-executable'},
+ root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
+ })
<
See |vim.lsp.start_client()| for all available options. The most important
@@ -818,7 +930,7 @@ start({config}, {opts}) *vim.lsp.start()*
Parameters: ~
• {config} (table) Same configuration as documented in
|vim.lsp.start_client()|
- • {opts} nil|table Optional keyword arguments:
+ • {opts} (nil|lsp.StartOpts) Optional keyword arguments:
• reuse_client (fun(client: client, config: table): boolean)
Predicate used to decide if a client should be re-used.
Used on all running clients. The default implementation
@@ -827,7 +939,7 @@ start({config}, {opts}) *vim.lsp.start()*
re-using a client (0 for current).
Return: ~
- (number|nil) client_id
+ (integer|nil) client_id
start_client({config}) *vim.lsp.start_client()*
Starts and initializes a client with the given configuration.
@@ -835,24 +947,23 @@ start_client({config}) *vim.lsp.start_client()*
Field `cmd` in {config} is required.
Parameters: ~
- • {config} (table) Configuration for the server:
- • cmd: (table|string|fun(dispatchers: table):table) command
- string or list treated like |jobstart()|. The command must
- launch the language server process. `cmd` can also be a
- function that creates an RPC client. The function receives
- a dispatchers table and must return a table with the
- functions `request`, `notify`, `is_closing` and
+ • {config} ( lsp.ClientConfig ) Configuration for the server:
+ • cmd: (string[]|fun(dispatchers: table):table) command a
+ list of strings treated like |jobstart()|. The command
+ must launch the language server process. `cmd` can also be
+ a function that creates an RPC client. The function
+ receives a dispatchers table and must return a table with
+ the functions `request`, `notify`, `is_closing` and
`terminate` See |vim.lsp.rpc.request()| and
|vim.lsp.rpc.notify()| For TCP there is a built-in rpc
client factory: |vim.lsp.rpc.connect()|
• cmd_cwd: (string, default=|getcwd()|) Directory to launch
the `cmd` process. Not related to `root_dir`.
• cmd_env: (table) Environment flags to pass to the LSP on
- spawn. Can be specified using keys like a map or as a list
- with `k=v` pairs or both. Non-string values are coerced to string.
- Example: >
+ spawn. Must be specified using a table. Non-string values
+ are coerced to string. Example: >
- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+ { PORT = 8080; HOST = "0.0.0.0"; }
<
• detached: (boolean, default true) Daemonize the server
process so that it runs in a separate process group from
@@ -869,8 +980,7 @@ start_client({config}) *vim.lsp.start_client()*
passed to the language server on initialization. Hint: use
make_client_capabilities() and modify its result.
• Note: To send an empty dictionary use
- `{[vim.type_idx]=vim.types.dictionary}`, else it will be
- encoded as an array.
+ |vim.empty_dict()|, else it will be encoded as an array.
• handlers: Map of language server method names to
|lsp-handler|
@@ -912,8 +1022,8 @@ start_client({config}) *vim.lsp.start_client()*
if `capabilities.offsetEncoding` was sent to it. You can
only modify the `client.offset_encoding` here before any
notifications are sent. Most language servers expect to be
- sent client specified settings after initialization.
- Neovim does not make this assumption. A
+ sent client specified settings after initialization. Nvim
+ does not make this assumption. A
`workspace/didChangeConfiguration` notification should be
sent to the server during on_init.
• on_exit Callback (code, signal, client_id) invoked on
@@ -946,27 +1056,34 @@ start_client({config}) *vim.lsp.start_client()*
initialization.
Return: ~
- Client id. |vim.lsp.get_client_by_id()| Note: client may not be fully
- initialized. Use `on_init` to do any actions once the client has been
- initialized.
+ (integer|nil) client_id. |vim.lsp.get_client_by_id()| Note: client may
+ not be fully initialized. Use `on_init` to do any actions once the
+ client has been initialized.
+
+status() *vim.lsp.status()*
+ Consumes the latest progress messages from all clients and formats them as
+ a string. Empty if there are no clients or if no new messages
+
+ Return: ~
+ (string)
stop_client({client_id}, {force}) *vim.lsp.stop_client()*
Stops a client(s).
- You can also use the `stop()` function on a |vim.lsp.client| object. To stop all clients: >lua
-
- vim.lsp.stop_client(vim.lsp.get_active_clients())
+ You can also use the `stop()` function on a |vim.lsp.client| object. To
+ stop all clients: >lua
+ vim.lsp.stop_client(vim.lsp.get_clients())
<
By default asks the server to shutdown, unless stop was requested already
for this client, then force-shutdown is attempted.
Parameters: ~
- • {client_id} number|table id or |vim.lsp.client| object, or list
+ • {client_id} integer|table id or |vim.lsp.client| object, or list
thereof
• {force} (boolean|nil) shutdown forcefully
-tagfunc({...}) *vim.lsp.tagfunc()*
+tagfunc({pattern}, {flags}) *vim.lsp.tagfunc()*
Provides an interface between the built-in client and 'tagfunc'.
When used with normal mode commands (e.g. |CTRL-]|) this will invoke the
@@ -979,7 +1096,7 @@ tagfunc({...}) *vim.lsp.tagfunc()*
• {flags} (string) See |tag-function|
Return: ~
- A list of matching tags
+ table[] tags A list of matching tags
with({handler}, {override_config}) *vim.lsp.with()*
Function to manage overriding defaults for LSP handlers.
@@ -1008,7 +1125,8 @@ code_action({options}) *vim.lsp.buf.code_action()*
• {options} (table|nil) Optional table which holds the following
optional fields:
• context: (table|nil) Corresponds to `CodeActionContext` of the LSP specification:
- • diagnostics (table|nil): LSP`Diagnostic[]` . Inferred from the current position if not provided.
+ • diagnostics (table|nil): LSP `Diagnostic[]`. Inferred
+ from the current position if not provided.
• only (table|nil): List of LSP `CodeActionKind`s used to
filter the code actions. Most language servers support
values like `refactor` or `quickfix`.
@@ -1023,38 +1141,39 @@ code_action({options}) *vim.lsp.buf.code_action()*
• range: (table|nil) Range for which code actions should be
requested. If in visual mode this defaults to the active
selection. Table must contain `start` and `end` keys with
- {row, col} tuples using mark-like indexing. See
+ {row,col} tuples using mark-like indexing. See
|api-indexing|
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
- vim.lsp.protocol.constants.CodeActionTriggerKind
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
+ • vim.lsp.protocol.CodeActionTriggerKind
completion({context}) *vim.lsp.buf.completion()*
Retrieves the completion items at the current cursor position. Can only be
called in Insert mode.
Parameters: ~
- • {context} (context support not yet implemented) Additional
+ • {context} (table) (context support not yet implemented) Additional
information about the context in which a completion was
triggered (how it was triggered, and by which trigger
character, if applicable)
See also: ~
- vim.lsp.protocol.constants.CompletionTriggerKind
+ • vim.lsp.protocol.CompletionTriggerKind
declaration({options}) *vim.lsp.buf.declaration()*
Jumps to the declaration of the symbol under the cursor.
- Note:
- Many servers do not implement this method. Generally, see
+
+ Note: ~
+ • Many servers do not implement this method. Generally, see
|vim.lsp.buf.definition()| instead.
Parameters: ~
• {options} (table|nil) additional options
• reuse_win: (boolean) Jump to existing window if buffer is
already open.
- • on_list: (function) handler for list results. See
- |lsp-on-list-handler|
+ • on_list: (function) |lsp-on-list-handler| replacing the
+ default handler. Called for any non-empty result.
definition({options}) *vim.lsp.buf.definition()*
Jumps to the definition of the symbol under the cursor.
@@ -1063,16 +1182,16 @@ definition({options}) *vim.lsp.buf.definition()*
• {options} (table|nil) additional options
• reuse_win: (boolean) Jump to existing window if buffer is
already open.
- • on_list: (function) handler for list results. See
- |lsp-on-list-handler|
+ • on_list: (function) |lsp-on-list-handler| replacing the
+ default handler. Called for any non-empty result.
document_highlight() *vim.lsp.buf.document_highlight()*
Send request to the server to resolve document highlights for the current
text document position. This request can be triggered by a key mapping or
- by events such as `CursorHold` , e.g.: >vim
- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
+ by events such as `CursorHold`, e.g.: >vim
+ autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
+ autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
+ autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
<
Note: Usage of |vim.lsp.buf.document_highlight()| requires the following
@@ -1095,19 +1214,18 @@ execute_command({command_params}) *vim.lsp.buf.execute_command()*
• {command_params} (table) A valid `ExecuteCommandParams` object
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
format({options}) *vim.lsp.buf.format()*
Formats a buffer using the attached (and optionally filtered) language
server clients.
Parameters: ~
- • {options} table|nil Optional table which holds the following optional
- fields:
+ • {options} (table|nil) Optional table which holds the following
+ optional fields:
• formatting_options (table|nil): Can be used to specify
FormattingOptions. Some unspecified options will be
- automatically derived from the current Neovim options.
- See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions
+ automatically derived from the current Nvim options. See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
• timeout_ms (integer|nil, default 1000): Time in
milliseconds to block for formatting requests. No effect
if async=true
@@ -1116,12 +1234,12 @@ format({options}) *vim.lsp.buf.format()*
buffer (0).
• filter (function|nil): Predicate used to filter clients.
Receives a client as argument and must return a boolean.
- Clients matching the predicate are included. Example: • >lua
+ Clients matching the predicate are included. Example: >lua
- -- Never request typescript-language-server for formatting
- vim.lsp.buf.format {
- filter = function(client) return client.name ~= "tsserver" end
- }
+ -- Never request typescript-language-server for formatting
+ vim.lsp.buf.format {
+ filter = function(client) return client.name ~= "tsserver" end
+ }
<
• async boolean|nil If true the method won't block.
Defaults to false. Editing the buffer while formatting
@@ -1131,7 +1249,7 @@ format({options}) *vim.lsp.buf.format()*
• name (string|nil): Restrict formatting to the client with
name (client.name) matching this field.
• range (table|nil) Range to format. Table must contain
- `start` and `end` keys with {row, col} tuples using (1,0)
+ `start` and `end` keys with {row,col} tuples using (1,0)
indexing. Defaults to current selection in visual mode
Defaults to `nil` in other modes, formatting the full
buffer
@@ -1146,8 +1264,8 @@ implementation({options}) *vim.lsp.buf.implementation()*
Parameters: ~
• {options} (table|nil) additional options
- • on_list: (function) handler for list results. See
- |lsp-on-list-handler|
+ • on_list: (function) |lsp-on-list-handler| replacing the
+ default handler. Called for any non-empty result.
incoming_calls() *vim.lsp.buf.incoming_calls()*
Lists all the call sites of the symbol under the cursor in the |quickfix|
@@ -1173,7 +1291,7 @@ references({context}, {options}) *vim.lsp.buf.references()*
|lsp-on-list-handler|
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
*vim.lsp.buf.remove_workspace_folder()*
remove_workspace_folder({workspace_folder})
@@ -1193,13 +1311,6 @@ rename({new_name}, {options}) *vim.lsp.buf.rename()*
• name (string|nil): Restrict clients used for rename to
ones where client.name matches this field.
-server_ready() *vim.lsp.buf.server_ready()*
- Checks whether the language servers attached to the current buffer are
- ready.
-
- Return: ~
- `true` if server responds.
-
signature_help() *vim.lsp.buf.signature_help()*
Displays signature information about the symbol under the cursor in a
floating window.
@@ -1211,8 +1322,8 @@ type_definition({options}) *vim.lsp.buf.type_definition()*
• {options} (table|nil) additional options
• reuse_win: (boolean) Jump to existing window if buffer is
already open.
- • on_list: (function) handler for list results. See
- |lsp-on-list-handler|
+ • on_list: (function) |lsp-on-list-handler| replacing the
+ default handler. Called for any non-empty result.
workspace_symbol({query}, {options}) *vim.lsp.buf.workspace_symbol()*
Lists all symbols in the current workspace in the quickfix window.
@@ -1222,7 +1333,7 @@ workspace_symbol({query}, {options}) *vim.lsp.buf.workspace_symbol()*
string means no filtering is done.
Parameters: ~
- • {query} (string, optional)
+ • {query} (string|nil) optional
• {options} (table|nil) additional options
• on_list: (function) handler for list results. See
|lsp-on-list-handler|
@@ -1231,12 +1342,43 @@ workspace_symbol({query}, {options}) *vim.lsp.buf.workspace_symbol()*
==============================================================================
Lua module: vim.lsp.diagnostic *lsp-diagnostic*
-get_namespace({client_id}) *vim.lsp.diagnostic.get_namespace()*
+ *vim.lsp.diagnostic.get_namespace()*
+get_namespace({client_id}, {is_pull})
Get the diagnostic namespace associated with an LSP client
- |vim.diagnostic|.
+ |vim.diagnostic| for diagnostics
+
+ Parameters: ~
+ • {client_id} (integer) The id of the LSP client
+ • {is_pull} boolean? Whether the namespace is for a pull or push
+ client. Defaults to push
+
+ *vim.lsp.diagnostic.on_diagnostic()*
+on_diagnostic({_}, {result}, {ctx}, {config})
+ |lsp-handler| for the method "textDocument/diagnostic"
+
+ See |vim.diagnostic.config()| for configuration options. Handler-specific
+ configuration can be set using |vim.lsp.with()|: >lua
+ vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with(
+ vim.lsp.diagnostic.on_diagnostic, {
+ -- Enable underline, use default values
+ underline = true,
+ -- Enable virtual text, override spacing to 4
+ virtual_text = {
+ spacing = 4,
+ },
+ -- Use a function to dynamically turn signs off
+ -- and on, using buffer local variables
+ signs = function(namespace, bufnr)
+ return vim.b[bufnr].show_signs == true
+ end,
+ -- Disable a feature
+ update_in_insert = false,
+ }
+ )
+<
Parameters: ~
- • {client_id} (number) The id of the LSP client
+ • {config} (table) Configuration table (see |vim.diagnostic.config()|).
*vim.lsp.diagnostic.on_publish_diagnostics()*
on_publish_diagnostics({_}, {result}, {ctx}, {config})
@@ -1244,24 +1386,23 @@ on_publish_diagnostics({_}, {result}, {ctx}, {config})
See |vim.diagnostic.config()| for configuration options. Handler-specific
configuration can be set using |vim.lsp.with()|: >lua
-
- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
- vim.lsp.diagnostic.on_publish_diagnostics, {
- -- Enable underline, use default values
- underline = true,
- -- Enable virtual text, override spacing to 4
- virtual_text = {
- spacing = 4,
- },
- -- Use a function to dynamically turn signs off
- -- and on, using buffer local variables
- signs = function(namespace, bufnr)
- return vim.b[bufnr].show_signs == true
- end,
- -- Disable a feature
- update_in_insert = false,
- }
- )
+ vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
+ vim.lsp.diagnostic.on_publish_diagnostics, {
+ -- Enable underline, use default values
+ underline = true,
+ -- Enable virtual text, override spacing to 4
+ virtual_text = {
+ spacing = 4,
+ },
+ -- Use a function to dynamically turn signs off
+ -- and on, using buffer local variables
+ signs = function(namespace, bufnr)
+ return vim.b[bufnr].show_signs == true
+ end,
+ -- Disable a feature
+ update_in_insert = false,
+ }
+ )
<
Parameters: ~
@@ -1275,25 +1416,26 @@ clear({client_id}, {bufnr}) *vim.lsp.codelens.clear()*
Clear the lenses
Parameters: ~
- • {client_id} (number|nil) filter by client_id. All clients if nil
- • {bufnr} (number|nil) filter by buffer. All buffers if nil
+ • {client_id} (integer|nil) filter by client_id. All clients if nil
+ • {bufnr} (integer|nil) filter by buffer. All buffers if nil
display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()*
Display the lenses using virtual text
Parameters: ~
- • {lenses} (table) of lenses to display (`CodeLens[] | null`)
- • {bufnr} (number)
- • {client_id} (number)
+ • {lenses} lsp.CodeLens[]|nil lenses to display
+ • {bufnr} (integer)
+ • {client_id} (integer)
get({bufnr}) *vim.lsp.codelens.get()*
Return all lenses for the given buffer
Parameters: ~
- • {bufnr} (number) Buffer number. 0 can be used for the current buffer.
+ • {bufnr} (integer) Buffer number. 0 can be used for the current
+ buffer.
Return: ~
- (table) (`CodeLens[]`)
+ lsp.CodeLens[]
*vim.lsp.codelens.on_codelens()*
on_codelens({err}, {result}, {ctx}, {_})
@@ -1305,7 +1447,7 @@ refresh() *vim.lsp.codelens.refresh()*
It is recommended to trigger this using an autocmd or via keymap.
Example: >vim
- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+ autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
<
run() *vim.lsp.codelens.run()*
@@ -1315,9 +1457,64 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()*
Store lenses for a specific buffer and client
Parameters: ~
- • {lenses} (table) of lenses to store (`CodeLens[] | null`)
- • {bufnr} (number)
- • {client_id} (number)
+ • {lenses} lsp.CodeLens[]|nil lenses to store
+ • {bufnr} (integer)
+ • {client_id} (integer)
+
+
+==============================================================================
+Lua module: vim.lsp.inlay_hint *lsp-inlay_hint*
+
+enable({bufnr}, {enable}) *vim.lsp.inlay_hint.enable()*
+ Enable/disable/toggle inlay hints for a buffer
+
+ Note: ~
+ This API is pre-release (unstable).
+
+ Parameters: ~
+ • {bufnr} (integer|nil) Buffer handle, or 0 or nil for current
+ • {enable} (boolean|nil) true/nil to enable, false to disable
+
+get({filter}) *vim.lsp.inlay_hint.get()*
+ Get the list of inlay hints, (optionally) restricted by buffer or range.
+
+ Example usage: >lua
+ local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
+
+ local client = vim.lsp.get_client_by_id(hint.client_id)
+ resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
+ vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
+
+ location = resolved_hint.label[1].location
+ client.request('textDocument/hover', {
+ textDocument = { uri = location.uri },
+ position = location.range.start,
+ })
+<
+
+ Note: ~
+ This API is pre-release (unstable).
+
+ Parameters: ~
+ • {filter} vim.lsp.inlay_hint.get.filter ? Optional filters |kwargs|:
+ • bufnr (integer?): 0 for current buffer
+ • range (lsp.Range?)
+
+ Return: ~
+ vim.lsp.inlay_hint.get.ret [] Each list item is a table with the following fields:
+ • bufnr (integer)
+ • client_id (integer)
+ • inlay_hint (lsp.InlayHint)
+
+is_enabled({bufnr}) *vim.lsp.inlay_hint.is_enabled()*
+ Note: ~
+ This API is pre-release (unstable).
+
+ Parameters: ~
+ • {bufnr} (integer|nil) Buffer handle, or 0 or nil for current
+
+ Return: ~
+ (boolean)
==============================================================================
@@ -1330,7 +1527,8 @@ force_refresh({bufnr}) *vim.lsp.semantic_tokens.force_refresh()*
highlighting (|vim.lsp.semantic_tokens.start()| has been called for it)
Parameters: ~
- • {bufnr} (nil|number) default: current buffer
+ • {bufnr} (integer|nil) filter by buffer. All buffers if nil, current
+ buffer if 0
*vim.lsp.semantic_tokens.get_at_pos()*
get_at_pos({bufnr}, {row}, {col})
@@ -1338,12 +1536,39 @@ get_at_pos({bufnr}, {row}, {col})
arguments, returns the token under the cursor.
Parameters: ~
- • {bufnr} (number|nil) Buffer number (0 for current buffer, default)
- • {row} (number|nil) Position row (default cursor position)
- • {col} (number|nil) Position column (default cursor position)
+ • {bufnr} (integer|nil) Buffer number (0 for current buffer, default)
+ • {row} (integer|nil) Position row (default cursor position)
+ • {col} (integer|nil) Position column (default cursor position)
Return: ~
- (table|nil) List of tokens at position
+ (table|nil) List of tokens at position. Each token has the following
+ fields:
+ • line (integer) line number, 0-based
+ • start_col (integer) start column, 0-based
+ • end_col (integer) end column, 0-based
+ • type (string) token type as string, e.g. "variable"
+ • modifiers (table) token modifiers as a set. E.g., { static = true,
+ readonly = true }
+
+ *vim.lsp.semantic_tokens.highlight_token()*
+highlight_token({token}, {bufnr}, {client_id}, {hl_group}, {opts})
+ Highlight a semantic token.
+
+ Apply an extmark with a given highlight group for a semantic token. The
+ mark will be deleted by the semantic token engine when appropriate; for
+ example, when the LSP sends updated tokens. This function is intended for
+ use inside |LspTokenUpdate| callbacks.
+
+ Parameters: ~
+ • {token} (table) a semantic token, found as `args.data.token` in
+ |LspTokenUpdate|.
+ • {bufnr} (integer) the buffer to highlight
+ • {client_id} (integer) The ID of the |vim.lsp.client|
+ • {hl_group} (string) Highlight group name
+ • {opts} (table|nil) Optional parameters.
+ • priority: (integer|nil) Priority for the applied
+ extmark. Defaults to
+ `vim.highlight.priorities.semantic_tokens + 3`
start({bufnr}, {client_id}, {opts}) *vim.lsp.semantic_tokens.start()*
Start the semantic token highlighting engine for the given buffer with the
@@ -1354,15 +1579,14 @@ start({bufnr}, {client_id}, {opts}) *vim.lsp.semantic_tokens.start()*
server that supports it, you can delete the semanticTokensProvider table
from the {server_capabilities} of your client in your |LspAttach| callback
or your configuration's `on_attach` callback: >lua
-
- client.server_capabilities.semanticTokensProvider = nil
+ client.server_capabilities.semanticTokensProvider = nil
<
Parameters: ~
- • {bufnr} (number)
- • {client_id} (number)
+ • {bufnr} (integer)
+ • {client_id} (integer)
• {opts} (nil|table) Optional keyword arguments
- • debounce (number, default: 200): Debounce token
+ • debounce (integer, default: 200): Debounce token
requests to the server by the given number in
milliseconds
@@ -1376,8 +1600,8 @@ stop({bufnr}, {client_id}) *vim.lsp.semantic_tokens.stop()*
from the buffer.
Parameters: ~
- • {bufnr} (number)
- • {client_id} (number)
+ • {bufnr} (integer)
+ • {client_id} (integer)
==============================================================================
@@ -1385,41 +1609,44 @@ Lua module: vim.lsp.handlers *lsp-handlers*
hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()*
|lsp-handler| for the method "textDocument/hover" >lua
-
- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
- vim.lsp.handlers.hover, {
- -- Use a sharp border with `FloatBorder` highlights
- border = "single",
- -- add the title in hover float window
- title = "hover"
- }
- )
+ vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
+ vim.lsp.handlers.hover, {
+ -- Use a sharp border with `FloatBorder` highlights
+ border = "single",
+ -- add the title in hover float window
+ title = "hover"
+ }
+ )
<
Parameters: ~
• {config} (table) Configuration table.
• border: (default=nil)
• Add borders to the floating window
- • See |nvim_open_win()|
+ • See |vim.lsp.util.open_floating_preview()| for more
+ options.
*vim.lsp.handlers.signature_help()*
signature_help({_}, {result}, {ctx}, {config})
- |lsp-handler| for the method "textDocument/signatureHelp". The active
- parameter is highlighted with |hl-LspSignatureActiveParameter|. >lua
-
- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
- vim.lsp.handlers.signature_help, {
- -- Use a sharp border with `FloatBorder` highlights
- border = "single"
- }
- )
+ |lsp-handler| for the method "textDocument/signatureHelp".
+
+ The active parameter is highlighted with |hl-LspSignatureActiveParameter|. >lua
+ vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
+ vim.lsp.handlers.signature_help, {
+ -- Use a sharp border with `FloatBorder` highlights
+ border = "single"
+ }
+ )
<
Parameters: ~
+ • {result} (table) Response from the language server
+ • {ctx} (table) Client context
• {config} (table) Configuration table.
• border: (default=nil)
• Add borders to the floating window
- • See |nvim_open_win()|
+ • See |vim.lsp.util.open_floating_preview()| for more
+ options
==============================================================================
@@ -1432,11 +1659,11 @@ apply_text_document_edit({text_document_edit}, {index}, {offset_encoding})
Parameters: ~
• {text_document_edit} (table) a `TextDocumentEdit` object
- • {index} (number) Optional index of the edit, if from a
+ • {index} (integer) Optional index of the edit, if from a
list of edits (or nil, if not from a list)
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
*vim.lsp.util.apply_text_edits()*
apply_text_edits({text_edits}, {bufnr}, {offset_encoding})
@@ -1444,11 +1671,11 @@ apply_text_edits({text_edits}, {bufnr}, {offset_encoding})
Parameters: ~
• {text_edits} (table) list of `TextEdit` objects
- • {bufnr} (number) Buffer id
+ • {bufnr} (integer) Buffer id
• {offset_encoding} (string) utf-8|utf-16|utf-32
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
*vim.lsp.util.apply_workspace_edit()*
apply_workspace_edit({workspace_edit}, {offset_encoding})
@@ -1462,35 +1689,35 @@ buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()*
Removes document highlights from a buffer.
Parameters: ~
- • {bufnr} (number) Buffer id
+ • {bufnr} (integer|nil) Buffer id
*vim.lsp.util.buf_highlight_references()*
buf_highlight_references({bufnr}, {references}, {offset_encoding})
Shows a list of document highlights for a certain buffer.
Parameters: ~
- • {bufnr} (number) Buffer id
+ • {bufnr} (integer) Buffer id
• {references} (table) List of `DocumentHighlight` objects to
highlight
• {offset_encoding} (string) One of "utf-8", "utf-16", "utf-32".
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent
+ • https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
*vim.lsp.util.character_offset()*
character_offset({buf}, {row}, {col}, {offset_encoding})
Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer.
Parameters: ~
- • {buf} (number) buffer number (0 for current)
+ • {buf} (integer) buffer number (0 for current)
• {row} 0-indexed line
• {col} 0-indexed byte offset in line
- • {offset_encoding} (string) utf-8|utf-16|utf-32|nil defaults to
+ • {offset_encoding} (string) utf-8|utf-16|utf-32 defaults to
`offset_encoding` of first client of `buf`
Return: ~
- (number, number) `offset_encoding` index of the character in line
- {row} column {col} in buffer {buf}
+ (integer) `offset_encoding` index of the character in line {row}
+ column {col} in buffer {buf}
*vim.lsp.util.convert_input_to_markdown_lines()*
convert_input_to_markdown_lines({input}, {contents})
@@ -1499,58 +1726,50 @@ convert_input_to_markdown_lines({input}, {contents})
window for `textDocument/hover`, for parsing the result of
`textDocument/signatureHelp`, and potentially others.
+ Note that if the input is of type `MarkupContent` and its kind is
+ `plaintext`, then the corresponding value is returned without further
+ modifications.
+
Parameters: ~
• {input} (`MarkedString` | `MarkedString[]` | `MarkupContent`)
• {contents} (table|nil) List of strings to extend with converted
lines. Defaults to {}.
Return: ~
- {contents}, extended with lines of converted markdown.
+ string[] extended with lines of converted markdown.
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
*vim.lsp.util.convert_signature_help_to_markdown_lines()*
convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers})
- Converts `textDocument/SignatureHelp` response to markdown lines.
+ Converts `textDocument/signatureHelp` response to markdown lines.
Parameters: ~
- • {signature_help} Response of `textDocument/SignatureHelp`
- • {ft} optional filetype that will be use as the `lang` for
- the label markdown code block
- • {triggers} optional list of trigger characters from the lsp
+ • {signature_help} (table) Response of `textDocument/SignatureHelp`
+ • {ft} (string|nil) filetype that will be use as the `lang`
+ for the label markdown code block
+ • {triggers} (table|nil) list of trigger characters from the lsp
server. used to better determine parameter offsets
- Return: ~
- (list) of lines of converted markdown.
-
- See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
-
- *vim.lsp.util.extract_completion_items()*
-extract_completion_items({result})
- Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null.
-
- Parameters: ~
- • {result} (table) The result of a `textDocument/completion` request
-
- Return: ~
- (table) List of completion items
+ Return (multiple): ~
+ (table|nil) table list of lines of converted markdown.
+ (table|nil) table of active hl
See also: ~
- https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()*
Returns indentation size.
Parameters: ~
- • {bufnr} (number|nil) Buffer handle, defaults to current
+ • {bufnr} (integer|nil) Buffer handle, defaults to current
Return: ~
- (number) indentation size
+ (integer) indentation size
See also: ~
- 'shiftwidth'
+ • 'shiftwidth'
*vim.lsp.util.jump_to_location()*
jump_to_location({location}, {offset_encoding}, {reuse_win})
@@ -1558,7 +1777,7 @@ jump_to_location({location}, {offset_encoding}, {reuse_win})
Parameters: ~
• {location} (table) (`Location`|`LocationLink`)
- • {offset_encoding} "utf-8" | "utf-16" | "utf-32"
+ • {offset_encoding} (string|nil) utf-8|utf-16|utf-32
• {reuse_win} (boolean|nil) Jump to existing window if buffer is
already open.
@@ -1570,13 +1789,17 @@ locations_to_items({locations}, {offset_encoding})
Returns the items with the byte position calculated correctly and in
sorted order, for display in quickfix and location lists.
+ The `user_data` field of each resulting item will contain the original
+ `Location` or `LocationLink` it was computed from.
+
The result can be passed to the {list} argument of |setqflist()| or
|setloclist()|.
Parameters: ~
• {locations} (table) list of `Location`s or `LocationLink`s
• {offset_encoding} (string) offset_encoding for locations
- utf-8|utf-16|utf-32
+ utf-8|utf-16|utf-32 default to first client of
+ buffer
Return: ~
(table) list of items
@@ -1585,11 +1808,11 @@ lookup_section({settings}, {section}) *vim.lsp.util.lookup_section()*
Helper function to return nested values in language server settings
Parameters: ~
- • {settings} a table of language server settings
- • {section} a string indicating the field of the settings table
+ • {settings} (table) language server settings
+ • {section} string indicating the field of the settings table
Return: ~
- (table or string) The value of settings accessed via section
+ table|string The value of settings accessed via section
*vim.lsp.util.make_floating_popup_options()*
make_floating_popup_options({width}, {height}, {opts})
@@ -1597,15 +1820,22 @@ make_floating_popup_options({width}, {height}, {opts})
table can be passed to |nvim_open_win()|.
Parameters: ~
- • {width} (number) window width (in character cells)
- • {height} (number) window height (in character cells)
- • {opts} (table, optional)
- • offset_x (number) offset to add to `col`
- • offset_y (number) offset to add to `row`
+ • {width} (integer) window width (in character cells)
+ • {height} (integer) window height (in character cells)
+ • {opts} (table) optional
+ • offset_x (integer) offset to add to `col`
+ • offset_y (integer) offset to add to `row`
• border (string or table) override `border`
• focusable (string or table) override `focusable`
• zindex (string or table) override `zindex`, defaults to 50
• relative ("mouse"|"cursor") defaults to "cursor"
+ • anchor_bias ("auto"|"above"|"below") defaults to "auto"
+ • "auto": place window based on which side of the cursor
+ has more lines
+ • "above": place the window above the cursor unless there
+ are not enough lines to display the full window height.
+ • "below": place the window below the cursor unless there
+ are not enough lines to display the full window height.
Return: ~
(table) Options
@@ -1622,7 +1852,7 @@ make_formatting_params({options})
`DocumentFormattingParams` object
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
*vim.lsp.util.make_given_range_params()*
make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding})
@@ -1630,18 +1860,18 @@ make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding})
similar to |vim.lsp.util.make_range_params()|.
Parameters: ~
- • {start_pos} number[]|nil {row, col} mark-indexed position.
+ • {start_pos} integer[]|nil {row,col} mark-indexed position.
Defaults to the start of the last visual selection.
- • {end_pos} number[]|nil {row, col} mark-indexed position.
+ • {end_pos} integer[]|nil {row,col} mark-indexed position.
Defaults to the end of the last visual selection.
- • {bufnr} (number|nil) buffer handle or 0 for current,
+ • {bufnr} (integer|nil) buffer handle or 0 for current,
defaults to current
• {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults to
`offset_encoding` of first client of `bufnr`
Return: ~
- { textDocument = { uri = `current_file_uri` }, range = { start =
- `start_position`, end = `end_position` } }
+ (table) { textDocument = { uri = `current_file_uri` }, range = { start
+ = `start_position`, end = `end_position` } }
*vim.lsp.util.make_position_params()*
make_position_params({window}, {offset_encoding})
@@ -1649,17 +1879,17 @@ make_position_params({window}, {offset_encoding})
cursor position.
Parameters: ~
- • {window} (number|nil) window handle or 0 for current,
+ • {window} (integer|nil) window handle or 0 for current,
defaults to current
• {offset_encoding} (string|nil) utf-8|utf-16|utf-32|nil defaults to
`offset_encoding` of first client of buffer of
`window`
Return: ~
- `TextDocumentPositionParams` object
+ (table) `TextDocumentPositionParams` object
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
*vim.lsp.util.make_range_params()*
make_range_params({window}, {offset_encoding})
@@ -1669,36 +1899,36 @@ make_range_params({window}, {offset_encoding})
`textDocument/rangeFormatting`.
Parameters: ~
- • {window} (number|nil) window handle or 0 for current,
+ • {window} (integer|nil) window handle or 0 for current,
defaults to current
• {offset_encoding} "utf-8"|"utf-16"|"utf-32"|nil defaults to
`offset_encoding` of first client of buffer of
`window`
Return: ~
- { textDocument = { uri = `current_file_uri` }, range = { start =
- `current_position`, end = `current_position` } }
+ (table) { textDocument = { uri = `current_file_uri` }, range = { start
+ = `current_position`, end = `current_position` } }
*vim.lsp.util.make_text_document_params()*
make_text_document_params({bufnr})
Creates a `TextDocumentIdentifier` object for the current buffer.
Parameters: ~
- • {bufnr} (number|nil) Buffer handle, defaults to current
+ • {bufnr} (integer|nil) Buffer handle, defaults to current
Return: ~
- `TextDocumentIdentifier`
+ (table) `TextDocumentIdentifier`
See also: ~
- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier
*vim.lsp.util.make_workspace_params()*
make_workspace_params({added}, {removed})
Create the workspace params
Parameters: ~
- • {added}
- • {removed}
+ • {added} (table)
+ • {removed} (table)
*vim.lsp.util.open_floating_preview()*
open_floating_preview({contents}, {syntax}, {opts})
@@ -1707,18 +1937,16 @@ open_floating_preview({contents}, {syntax}, {opts})
Parameters: ~
• {contents} (table) of lines to show in window
• {syntax} (string) of syntax to set for opened buffer
- • {opts} (table) with optional fields (additional keys are passed
- on to |nvim_open_win()|)
- • height: (number) height of floating window
- • width: (number) width of floating window
+ • {opts} (table) with optional fields (additional keys are filtered
+ with |vim.lsp.util.make_floating_popup_options()| before
+ they are passed on to |nvim_open_win()|)
+ • height: (integer) height of floating window
+ • width: (integer) width of floating window
• wrap: (boolean, default true) wrap long lines
- • wrap_at: (number) character to wrap at for computing
+ • wrap_at: (integer) character to wrap at for computing
height when wrap is enabled
- • max_width: (number) maximal width of floating window
- • max_height: (number) maximal height of floating window
- • pad_top: (number) number of lines to pad contents at top
- • pad_bottom: (number) number of lines to pad contents at
- bottom
+ • max_width: (integer) maximal width of floating window
+ • max_height: (integer) maximal height of floating window
• focus_id: (string) if a popup with this id is opened,
then focus it
• close_events: (table) list of events that closes the
@@ -1728,18 +1956,9 @@ open_floating_preview({contents}, {syntax}, {opts})
{focusable} is also `true`, focus an existing floating
window with the same {focus_id}
- Return: ~
- bufnr,winnr buffer and window number of the newly created floating
- preview window
-
-parse_snippet({input}) *vim.lsp.util.parse_snippet()*
- Parses snippets in a completion entry.
-
- Parameters: ~
- • {input} (string) unparsed snippet
-
- Return: ~
- (string) parsed snippet
+ Return (multiple): ~
+ (integer) bufnr of newly created float window
+ (integer) winid of newly created float window preview window
preview_location({location}, {opts}) *vim.lsp.util.preview_location()*
Previews a location in a floating window
@@ -1750,10 +1969,11 @@ preview_location({location}, {opts}) *vim.lsp.util.preview_location()*
definition)
Parameters: ~
- • {location} a single `Location` or `LocationLink`
+ • {location} (table) a single `Location` or `LocationLink`
- Return: ~
- (bufnr,winnr) buffer and window number of floating window or nil
+ Return (multiple): ~
+ (integer|nil) buffer id of float window
+ (integer|nil) window id of float window
rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()*
Rename old_fname to new_fname
@@ -1761,27 +1981,13 @@ rename({old_fname}, {new_fname}, {opts}) *vim.lsp.util.rename()*
Parameters: ~
• {opts} (table)
-set_lines({lines}, {A}, {B}, {new_lines}) *vim.lsp.util.set_lines()*
- Replaces text in a range with new text.
-
- CAUTION: Changes in-place!
-
- Parameters: ~
- • {lines} (table) Original list of strings
- • {A} (table) Start position; a 2-tuple of {line, col} numbers
- • {B} (table) End position; a 2-tuple of {line, col} numbers
- • {new_lines} A list of strings to replace the original
-
- Return: ~
- (table) The modified {lines} object
-
*vim.lsp.util.show_document()*
show_document({location}, {offset_encoding}, {opts})
Shows document and optionally jumps to the location.
Parameters: ~
• {location} (table) (`Location`|`LocationLink`)
- • {offset_encoding} "utf-8" | "utf-16" | "utf-32"
+ • {offset_encoding} (string|nil) utf-8|utf-16|utf-32
• {opts} (table|nil) options
• reuse_win (boolean) Jump to existing window if
buffer is already open.
@@ -1805,63 +2011,22 @@ stylize_markdown({bufnr}, {contents}, {opts})
Parameters: ~
• {contents} (table) of lines to show in window
- • {opts} dictionary with optional fields
+ • {opts} (table) with optional fields
• height of floating window
• width of floating window
• wrap_at character to wrap at for computing height
• max_width maximal width of floating window
• max_height maximal height of floating window
- • pad_top number of lines to pad contents at top
- • pad_bottom number of lines to pad contents at bottom
• separator insert separator after code block
Return: ~
- width,height size of float
+ (table) stripped content
symbols_to_items({symbols}, {bufnr}) *vim.lsp.util.symbols_to_items()*
Converts symbols to quickfix list items.
Parameters: ~
- • {symbols} DocumentSymbol[] or SymbolInformation[]
-
- *vim.lsp.util.text_document_completion_list_to_complete_items()*
-text_document_completion_list_to_complete_items({result}, {prefix})
- Turns the result of a `textDocument/completion` request into
- vim-compatible |complete-items|.
-
- Parameters: ~
- • {result} The result of a `textDocument/completion` call, e.g. from
- |vim.lsp.buf.completion()|, which may be one of
- `CompletionItem[]`, `CompletionList` or `null`
- • {prefix} (string) the prefix to filter the completion items
-
- Return: ~
- { matches = complete-items table, incomplete = bool }
-
- See also: ~
- |complete-items|
-
-trim_empty_lines({lines}) *vim.lsp.util.trim_empty_lines()*
- Removes empty lines from the beginning and end.
-
- Parameters: ~
- • {lines} (table) list of lines to trim
-
- Return: ~
- (table) trimmed list of lines
-
- *vim.lsp.util.try_trim_markdown_code_blocks()*
-try_trim_markdown_code_blocks({lines})
- Accepts markdown lines and tries to reduce them to a filetype if they
- comprise just a single code block.
-
- CAUTION: Modifies the input in-place!
-
- Parameters: ~
- • {lines} (table) list of lines
-
- Return: ~
- (string) filetype or "markdown" if it was unchanged.
+ • {symbols} (table) DocumentSymbol[] or SymbolInformation[]
==============================================================================
@@ -1877,7 +2042,7 @@ get_level() *vim.lsp.log.get_level()*
Gets the current log level.
Return: ~
- (string) current log level
+ (integer) current log level
set_format_func({handle}) *vim.lsp.log.set_format_func()*
Sets formatting function used to format logs
@@ -1890,13 +2055,13 @@ set_level({level}) *vim.lsp.log.set_level()*
Sets the current log level.
Parameters: ~
- • {level} (string|number) One of `vim.lsp.log.levels`
+ • {level} (string|integer) One of `vim.lsp.log.levels`
should_log({level}) *vim.lsp.log.should_log()*
Checks whether the level is sufficient for logging.
Parameters: ~
- • {level} (number) log level
+ • {level} (integer) log level
Return: ~
(bool) true if would log, false if not
@@ -1911,7 +2076,7 @@ connect({host}, {port}) *vim.lsp.rpc.connect()*
Parameters: ~
• {host} (string)
- • {port} (number)
+ • {port} (integer)
Return: ~
(function)
@@ -1933,7 +2098,7 @@ notify({method}, {params}) *vim.lsp.rpc.notify()*
• {params} (table|nil) Parameters for the invoked LSP method
Return: ~
- (bool) `true` if notification could be sent, `false` if not
+ (boolean) `true` if notification could be sent, `false` if not
*vim.lsp.rpc.request()*
request({method}, {params}, {callback}, {notify_reply_callback})
@@ -1943,20 +2108,21 @@ request({method}, {params}, {callback}, {notify_reply_callback})
• {method} (string) The invoked LSP method
• {params} (table|nil) Parameters for the invoked LSP
method
- • {callback} (function) Callback to invoke
+ • {callback} fun(err: lsp.ResponseError | nil, result:
+ any) Callback to invoke
• {notify_reply_callback} (function|nil) Callback to invoke as soon as
a request is no longer pending
Return: ~
- (bool, number) `(true, message_id)` if request could be sent, `false`
- if not
+ (boolean) success, integer|nil request_id true, message_id if request
+ could be sent, `false` if not
*vim.lsp.rpc.rpc_response_error()*
rpc_response_error({code}, {message}, {data})
Creates an RPC response object/table.
Parameters: ~
- • {code} (number) RPC error code defined in
+ • {code} (integer) RPC error code defined in
`vim.lsp.protocol.ErrorCodes`
• {message} (string|nil) arbitrary message to send to server
• {data} any|nil arbitrary data to send to server
@@ -1986,8 +2152,7 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params})
for LSP server process
Return: ~
- Client RPC object.
- Methods:
+ (table|nil) Client RPC object, with these methods:
• `notify()` |vim.lsp.rpc.notify()|
• `request()` |vim.lsp.rpc.request()|
• `is_closing()` returns a boolean indicating if the RPC is closing.
@@ -1995,27 +2160,6 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params})
==============================================================================
-Lua module: vim.lsp.sync *lsp-sync*
-
- *vim.lsp.sync.compute_diff()*
-compute_diff({___MissingCloseParenHere___})
- Returns the range table for the difference between prev and curr lines
-
- Parameters: ~
- • {prev_lines} (table) list of lines
- • {curr_lines} (table) list of lines
- • {firstline} (number) line to begin search for first difference
- • {lastline} (number) line to begin search in old_lines for last
- difference
- • {new_lastline} (number) line to begin search in new_lines for last
- difference
- • {offset_encoding} (string) encoding requested by language server
-
- Return: ~
- (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent
-
-
-==============================================================================
Lua module: vim.lsp.protocol *lsp-protocol*
*vim.lsp.protocol.make_client_capabilities()*
@@ -2023,6 +2167,15 @@ make_client_capabilities()
Gets a new ClientCapabilities object describing the LSP client
capabilities.
+ Return: ~
+ lsp.ClientCapabilities
+
+Methods *vim.lsp.protocol.Methods*
+ LSP method names.
+
+ See also: ~
+ • https://microsoft.github.io/language-server-protocol/specifications/specification-current/#metaModel
+
*vim.lsp.protocol.resolve_capabilities()*
resolve_capabilities({server_capabilities})
Creates a normalized object describing LSP server capabilities.
@@ -2032,6 +2185,6 @@ resolve_capabilities({server_capabilities})
server
Return: ~
- (table) Normalized table of capabilities
+ (table|nil) Normalized table of capabilities
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/lua-guide.txt b/runtime/doc/lua-guide.txt
index b971a7d2ad..c15b1b0495 100644
--- a/runtime/doc/lua-guide.txt
+++ b/runtime/doc/lua-guide.txt
@@ -10,16 +10,16 @@
==============================================================================
Introduction *lua-guide*
-This guide will go through the basics of using Lua in Neovim. It is not meant
+This guide will go through the basics of using Lua in Nvim. It is not meant
to be a comprehensive encyclopedia of all available features, nor will it
detail all intricacies. Think of it as a survival kit -- the bare minimum
-needed to know to comfortably get started on using Lua in Neovim.
+needed to know to comfortably get started on using Lua in Nvim.
An important thing to note is that this isn't a guide to the Lua language
-itself. Rather, this is a guide on how to configure and modify Neovim through
+itself. Rather, this is a guide on how to configure and modify Nvim through
the Lua language and the functions we provide to help with this. Take a look
at |luaref| and |lua-concepts| if you'd like to learn more about Lua itself.
-Similarly, this guide assumes some familiarity with the basics of Neovim
+Similarly, this guide assumes some familiarity with the basics of Nvim
(commands, options, mappings, autocommands), which are covered in the
|user-manual|.
@@ -27,22 +27,22 @@ Similarly, this guide assumes some familiarity with the basics of Neovim
Some words on the API *lua-guide-api*
The purpose of this guide is to introduce the different ways of interacting
-with Neovim through Lua (the "API"). This API consists of three different
+with Nvim through Lua (the "API"). This API consists of three different
layers:
-1. The "Vim API" inherited from Vim: |ex-commands| and |builtin-functions| as
+1. The "Vim API" inherited from Vim: |Ex-commands| and |builtin-functions| as
well as |user-function|s in Vimscript. These are accessed through |vim.cmd()|
and |vim.fn| respectively, which are discussed under |lua-guide-vimscript|
below.
-2. The "Neovim API" written in C for use in remote plugins and GUIs; see |api|.
+2. The "Nvim API" written in C for use in remote plugins and GUIs; see |api|.
These functions are accessed through |vim.api|.
3. The "Lua API" written in and specifically for Lua. These are any other
functions accessible through `vim.*` not mentioned already; see |lua-stdlib|.
This distinction is important, as API functions inherit behavior from their
-original layer: For example, Neovim API functions always need all arguments to
+original layer: For example, Nvim API functions always need all arguments to
be specified even if Lua itself allows omitting arguments (which are then
passed as `nil`); and Vim API functions can use 0-based indexing even if Lua
arrays are 1-indexed by default.
@@ -58,7 +58,7 @@ convenient to use from Lua.
==============================================================================
Using Lua *lua-guide-using-Lua*
-To run Lua code from the Neovim command line, use the |:lua| command:
+To run Lua code from the Nvim command line, use the |:lua| command:
>vim
:lua print("Hello!")
<
@@ -69,10 +69,10 @@ local keyword are not accessible outside of the command. This won't work:
:lua print(foo)
" prints "nil" instead of "1"
<
-You can also use `:lua=`, which is the same as `:lua vim.pretty_print(...)`,
-to conveniently check the value of a variable or a table:
->lua
- :lua=package
+You can also use `:lua=`, which is equivalent to `:lua vim.print(...)`, to
+conveniently check the value of a variable or a table:
+>vim
+ :lua =package
<
To run a Lua script in an external file, you can use the |:source| command
exactly like for a Vimscript file:
@@ -80,7 +80,7 @@ exactly like for a Vimscript file:
:source ~/programs/baz/myluafile.lua
<
Finally, you can include Lua code in a Vimscript file by putting it inside a
-|lua-heredoc| block:
+|:lua-heredoc| block:
>vim
lua << EOF
local tbl = {1, 2, 3}
@@ -92,7 +92,7 @@ Finally, you can include Lua code in a Vimscript file by putting it inside a
------------------------------------------------------------------------------
Using Lua files on startup *lua-guide-config*
-Neovim supports using `init.vim` or `init.lua` as the configuration file, but
+Nvim supports using `init.vim` or `init.lua` as the configuration file, but
not both at the same time. This should be placed in your |config| directory,
which is typically `~/.config/nvim` for Linux, BSD, or macOS, and
`~/AppData/Local/nvim/` for Windows. Note that you can use Lua in `init.vim`
@@ -114,10 +114,10 @@ Let's assume you have the following directory structure:
|-- after/
|-- ftplugin/
|-- lua/
- | |-- myluamodule.lua
- | |-- other_modules/
- | |-- anothermodule.lua
- | |-- init.lua
+ | |-- myluamodule.lua
+ | |-- other_modules/
+ | |-- anothermodule.lua
+ | |-- init.lua
|-- plugin/
|-- syntax/
|-- init.vim
@@ -167,8 +167,8 @@ the cache manually first:
<
------------------------------------------------------------------------------
See also:
-• |lua-require|
-• |luaref-pcall()|
+• |lua-module-load|
+• |pcall()|
==============================================================================
Using Vim commands and functions from Lua *lua-guide-vimscript*
@@ -186,7 +186,7 @@ Note that special characters will need to be escaped with backslashes:
>lua
vim.cmd("%s/\\Vfoo/bar/g")
<
-An alternative is to use a literal string (see |luaref-literal|) delimited by
+An alternative is to use a literal string (see |lua-literal|) delimited by
double brackets `[[ ]]` as in
>lua
vim.cmd([[%s/\Vfoo/bar/g]])
@@ -199,8 +199,8 @@ this allows you to pass multiple commands to a single call of |vim.cmd()|:
highlight link Warning Error
]])
<
-This is the converse of |lua-heredoc| and allows you to include Vimscript code in
-your `init.lua`.
+This is the converse of |:lua-heredoc| and allows you to include Vimscript
+code in your `init.lua`.
If you want to build your Vim command programmatically, the following form can
be useful (all these are equivalent to the corresponding line above):
@@ -218,7 +218,7 @@ Vimscript are automatically converted:
print(vim.fn.printf('Hello from %s', 'Lua'))
local reversed_list = vim.fn.reverse({ 'a', 'b', 'c' })
- print(vim.inspect(reversed_list)) -- { "c", "b", "a" }
+ vim.print(reversed_list) -- { "c", "b", "a" }
local function print_stdout(chan_id, data, name)
print(data[1])
@@ -261,7 +261,7 @@ Data types are converted automatically. For example:
key2 = 300
}
- print(vim.inspect(vim.g.some_global_variable))
+ vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 300 }
<
You can target specific buffers (via number), windows (via |window-ID|), or
@@ -279,7 +279,7 @@ Note that you cannot directly change fields of array variables. This won't
work:
>lua
vim.g.some_global_variable.key2 = 400
- vim.pretty_print(vim.g.some_global_variable)
+ vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 300 }
<
Instead, you need to create an intermediate Lua table and change this:
@@ -287,7 +287,7 @@ Instead, you need to create an intermediate Lua table and change this:
local temp_table = vim.g.some_global_variable
temp_table.key2 = 400
vim.g.some_global_variable = temp_table
- vim.pretty_print(vim.g.some_global_variable)
+ vim.print(vim.g.some_global_variable)
--> { key1 = "value", key2 = 400 }
<
To delete a variable, simply set it to `nil`:
@@ -350,7 +350,7 @@ use |vim.opt:get()|:
--> {...} (big table)
print(vim.opt.smarttab:get())
--> false
- vim.pretty_print(vim.opt.listchars:get())
+ vim.print(vim.opt.listchars:get())
--> { space = '_', tab = '>~' }
<
------------------------------------------------------------------------------
@@ -363,7 +363,7 @@ and `:let &listchars='space:_,tab:>~'`:
• |vim.o|: behaves like |:set|
• |vim.go|: behaves like |:setglobal|
• |vim.bo|: for buffer-scoped options
-• |vim.wo|: for window-scoped options
+• |vim.wo|: for window-scoped options (can be double indexed)
For example:
>lua
@@ -386,6 +386,9 @@ window is used:
>lua
vim.bo[4].expandtab = true -- sets expandtab to true in buffer 4
vim.wo.number = true -- sets number to true in current window
+ vim.wo[0].number = true -- same as above
+ vim.wo[0][0].number = true -- sets number to true in current buffer
+ -- in current window only
print(vim.wo[0].number) --> true
<
------------------------------------------------------------------------------
@@ -459,9 +462,9 @@ useful options:
vim.keymap.set('n', '<Leader>pl1', require('plugin').action,
{ desc = 'Execute action from plugin' })
<
-• `remap`: By default, all mappings are nonrecursive by default (i.e.,
- |vim.keymap.set()| behaves like |:noremap|). If the {rhs} is itself a mapping
- that should be executed, set `remap = true`: >lua
+• `remap`: By default, all mappings are nonrecursive (i.e., |vim.keymap.set()|
+ behaves like |:noremap|). If the {rhs} is itself a mapping that should be
+ executed, set `remap = true`: >lua
vim.keymap.set('n', '<Leader>ex1', '<cmd>echo "Example 1"<cr>')
-- add a shorter mapping
vim.keymap.set('n', 'e', '<Leader>ex1', { remap = true })
@@ -488,7 +491,7 @@ Autocommands *lua-guide-autocommands*
An |autocommand| is a Vim command or a Lua function that is automatically
executed whenever one or more |events| are triggered, e.g., when a file is
read or written, or when a window is created. These are accessible from Lua
-through the Neovim API.
+through the Nvim API.
------------------------------------------------------------------------------
Creating autocommands *lua-guide-autocommand-create*
@@ -530,7 +533,7 @@ Examples:
})
<
-Neovim will always call a Lua function with a single table containing information
+Nvim will always call a Lua function with a single table containing information
about the triggered autocommand. The most useful keys are
• `match`: a string that matched the `pattern` (see |<amatch>|)
• `buf`: the number of the buffer the event was triggered in (see |<abuf>|)
@@ -539,7 +542,7 @@ about the triggered autocommand. The most useful keys are
For example, this allows you to set buffer-local mappings for some filetypes:
>lua
- vim.api.nvim.create_autocmd("FileType", {
+ vim.api.nvim_create_autocmd("FileType", {
pattern = "lua",
callback = function(args)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = args.buf })
@@ -624,9 +627,9 @@ in a different file:
>lua
local mygroup = vim.api.nvim_create_augroup('vimrc', { clear = false })
vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, {
- pattern = '*.html',
+ pattern = '*.c',
group = mygroup,
- command = 'set shiftwidth=4',
+ command = 'set noexpandtab',
})
<
------------------------------------------------------------------------------
@@ -657,7 +660,7 @@ See also
• |nvim_exec_autocmds()|: execute all matching autocommands
==============================================================================
-User commands *lua-guide-usercommands*
+User commands *lua-guide-commands*
|user-commands| are custom Vim commands that call a Vimscript or Lua function.
Just like built-in commands, they can have arguments, act on ranges, or have
@@ -665,11 +668,10 @@ custom completion of arguments. As these are most useful for plugins, we will
cover only the basics of this advanced topic.
------------------------------------------------------------------------------
-Creating user commands *lua-guide-usercommands-create*
+Creating user commands *lua-guide-commands-create*
-User commands can be created through the Neovim API with
-`vim.api.`|nvim_create_user_command()|. This function takes three mandatory
-arguments:
+User commands can be created via |nvim_create_user_command()|. This function
+takes three mandatory arguments:
• a string that is the name of the command (which must start with an uppercase
letter to distinguish it from builtin commands);
• a string containing Vim commands or a Lua function that is executed when the
@@ -734,7 +736,7 @@ the remaining arguments are the same as for |nvim_create_user_command()|:
{ nargs = 1 })
<
------------------------------------------------------------------------------
-Deleting user commands *lua-guide-usercommands-delete*
+Deleting user commands *lua-guide-commands-delete*
User commands can be deleted with `vim.api.`|nvim_del_user_command()|. The only
argument is the name of the command:
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt
index 47249a484b..a35d70cae8 100644
--- a/runtime/doc/lua.txt
+++ b/runtime/doc/lua.txt
@@ -14,7 +14,7 @@ INTRODUCTION *lua-intro*
The Lua 5.1 script engine is builtin and always available. Try this command to
get an idea of what lurks beneath: >vim
- :lua print(vim.inspect(package.loaded))
+ :lua vim.print(package.loaded)
Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the
"editor stdlib" (|builtin-functions| and |Ex-commands|) and the |API|, all of
@@ -33,6 +33,22 @@ Lua 5.1, not worry about forward-compatibility with future Lua versions. If
Nvim ever ships with Lua 5.4+, a Lua 5.1 compatibility shim will be provided
so that old plugins continue to work transparently.
+ *lua-luajit*
+Nvim is built with luajit on platforms which support it, which provides
+extra functionality. Lua code in |init.lua| and plugins can assume its presence
+on installations on common platforms. For maximum compatibility with less
+common platforms, availability can be checked using the `jit` global variable: >lua
+ if jit then
+ -- code for luajit
+ else
+ -- code for plain lua 5.1
+ end
+<
+ *lua-bit*
+In particular, the luajit "bit" extension module is _always_ available.
+A fallback implementation is included when nvim is built with PUC Lua 5.1,
+and will be transparently used when `require("bit")` is invoked.
+
==============================================================================
LUA CONCEPTS AND IDIOMS *lua-concepts*
@@ -46,15 +62,32 @@ programming": tables, closures, and coroutines.
https://www.lua.org/doc/cacm2018.pdf
- Tables are the "object" or container datastructure: they represent both
lists and maps, you can extend them to represent your own datatypes and
- change their behavior using |luaref-metatable| (like Python's "datamodel").
+ change their behavior using |metatable|s (like Python's "datamodel").
- EVERY scope in Lua is a closure: a function is a closure, a module is
- a closure, a `do` block (|luaref-do|) is a closure--and they all work the
- same. A Lua module is literally just a big closure discovered on the "path"
+ a closure, a `do` block (|lua-do|) is a closure--and they all work the same.
+ A Lua module is literally just a big closure discovered on the "path"
(where your modules are found: |package.cpath|).
- Stackful coroutines enable cooperative multithreading, generators, and
versatile control for both Lua and its host (Nvim).
- *lua-call-function*
+ *iterator*
+An iterator is just a function that can be called repeatedly to get the "next"
+value of a collection (or any other |iterable|). This interface is expected by
+|for-in| loops, produced by |pairs()|, supported by |vim.iter|, etc.
+https://www.lua.org/pil/7.1.html
+
+ *iterable*
+An "iterable" is anything that |vim.iter()| can consume: tables, dicts, lists,
+iterator functions, tables implementing the |__call()| metamethod, and
+|vim.iter()| objects.
+
+ *list-iterator*
+Iterators on |lua-list| tables have a "middle" and "end", whereas iterators in
+general may be logically infinite. Therefore some |vim.iter| operations (e.g.
+|Iter:rev()|) make sense only on list-like tables (which are finite by
+definition).
+
+ *lua-function-call*
Lua functions can be called in multiple ways. Consider the function: >lua
local foo = function(a, b)
print("A: ", a)
@@ -67,21 +100,19 @@ The first way to call this function is: >lua
-- A: 1
-- B: 2
-This way of calling a function is familiar from most scripting languages.
-In Lua, any missing arguments are passed as `nil`. Example: >lua
+This way of calling a function is familiar from most scripting languages. In
+Lua, any missing arguments are passed as `nil`, and extra parameters are
+silently discarded. Example: >lua
foo(1)
-- ==== Result ====
-- A: 1
-- B: nil
-
-Furthermore it is not an error if extra parameters are passed, they are just
-discarded.
-
+<
*kwargs*
When calling a function, you can omit the parentheses if the function takes
exactly one string literal (`"foo"`) or table literal (`{1,2,3}`). The latter
-is often used to approximate "named parameters" ("kwargs" or "keyword args")
-as in languages like Python and C#. Example: >lua
+is often used to mimic "named parameters" ("kwargs" or "keyword args") as in
+languages like Python and C#. Example: >lua
local func_with_opts = function(opts)
local will_do_foo = opts.foo
local filename = opts.filename
@@ -92,21 +123,16 @@ as in languages like Python and C#. Example: >lua
<
There's nothing special going on here except that parentheses are treated as
whitespace. But visually, this small bit of sugar gets reasonably close to
-a "keyword args" interface.
-
-It is of course also valid to call the function with parentheses: >lua
- func_with_opts({ foo = true, filename = "hello.world" })
-<
-Nvim tends to prefer the keyword args style.
+a "keyword args" interface. Nvim code tends to prefer this style.
------------------------------------------------------------------------------
LUA PATTERNS *lua-patterns*
Lua intentionally does not support regular expressions, instead it has limited
-"patterns" |luaref-patterns| which avoid the performance pitfalls of extended
+"patterns" |lua-pattern| which avoid the performance pitfalls of extended
regex. Lua scripts can also use Vim regex via |vim.regex()|.
-These examples use |string.match()| to demonstrate Lua patterns: >lua
+Examples: >lua
print(string.match("foo123bar123", "%d+"))
-- 123
@@ -118,7 +144,7 @@ These examples use |string.match()| to demonstrate Lua patterns: >lua
-- .bar
==============================================================================
-IMPORTING LUA MODULES *require()* *lua-require*
+IMPORTING LUA MODULES *lua-module-load*
Modules are searched for under the directories specified in 'runtimepath', in
the order they appear. Any "." in the module name is treated as a directory
@@ -132,7 +158,7 @@ back to Lua's default search mechanism. The first script found is run and
The return value is cached after the first call to `require()` for each module,
with subsequent calls returning the cached value without searching for, or
-executing any script. For further details on `require()`, see |luaref-require()|.
+executing any script. For further details see |require()|.
For example, if 'runtimepath' is `foo,bar` and |package.cpath| was
`./?.so;./?.dll` at startup, `require('mod')` searches these paths in order
@@ -217,11 +243,11 @@ command calls. The |lua-stdlib| modules, user modules, and anything else on
The Lua print() function redirects its output to the Nvim message area, with
arguments separated by " " (space) instead of "\t" (tab).
- *:lua*
+ *:lua=* *:lua*
:lua {chunk}
Executes Lua chunk {chunk}. If {chunk} starts with "=" the rest of the
- chunk is evaluated as an expression and printed. `:lua =expr` is
- equivalent to `:lua print(vim.inspect(expr))`
+ chunk is evaluated as an expression and printed. `:lua =expr` or `:=expr` is
+ equivalent to `:lua print(vim.inspect(expr))`.
Examples: >vim
:lua vim.api.nvim_command('echo "Hello, Nvim!"')
@@ -231,12 +257,12 @@ arguments separated by " " (space) instead of "\t" (tab).
:lua =jit.version
<
*:lua-heredoc*
-:lua << [endmarker]
+:lua << [trim] [{endmarker}]
{script}
{endmarker}
- Executes Lua script {script} from within Vimscript. {endmarker} must NOT
- be preceded by whitespace. You can omit [endmarker] after the "<<" and use
- a dot "." after {script} (similar to |:append|, |:insert|).
+ Executes Lua script {script} from within Vimscript. You can omit
+ [endmarker] after the "<<" and use a dot "." after {script} (similar to
+ |:append|, |:insert|). Refer to |:let-heredoc| for more information.
Example: >vim
function! CurrentLineInfo()
@@ -280,7 +306,7 @@ arguments separated by " " (space) instead of "\t" (tab).
<
==============================================================================
-luaeval() *lua-eval* *luaeval()*
+luaeval() *lua-eval*
The (dual) equivalent of "vim.eval" for passing Lua values to Nvim is
"luaeval". "luaeval" takes an expression string and an optional argument used
@@ -305,14 +331,16 @@ Example: >vim
:echo luaeval('string.match(_A, "[a-z]+")', 'XYXfoo123')
" foo
<
+ *lua-table-ambiguous*
Lua tables are used as both dictionaries and lists, so it is impossible to
determine whether empty table is meant to be empty list or empty dictionary.
Additionally Lua does not have integer numbers. To distinguish between these
cases there is the following agreement:
-
+ *lua-list*
0. Empty table is empty list.
-1. Table with N incrementally growing integral numbers, starting from 1 and
- ending with N is considered to be a list.
+1. Table with N consecutive integer indices starting from 1 and ending with
+ N is considered a list. See also |list-iterator|.
+ *lua-dict*
2. Table with string keys, none of which contains NUL byte, is considered to
be a dictionary.
3. Table with string keys, at least one of which contains NUL byte, is also
@@ -383,7 +411,7 @@ For example consider the following Lua omnifunc handler: >lua
return {'stuff', 'steam', 'strange things'}
end
end
- vim.api.nvim_buf_set_option(0, 'omnifunc', 'v:lua.mymod.omnifunc')
+ vim.bo[buf].omnifunc = 'v:lua.mymod.omnifunc'
Note: The module ("mymod" in the above example) must either be a Lua global,
or use require() as shown above to access it from a package.
@@ -405,7 +433,7 @@ is unnecessary.
You can peek at the module properties: >vim
- :lua print(vim.inspect(vim))
+ :lua vim.print(vim)
Result is something like this: >
@@ -431,29 +459,24 @@ Note that underscore-prefixed functions (e.g. "_os_proc_children") are
internal/private and must not be used by plugins.
------------------------------------------------------------------------------
-VIM.LOOP *lua-loop* *vim.loop*
+VIM.UV *lua-loop* *vim.uv*
-`vim.loop` exposes all features of the Nvim event-loop. This is a low-level
-API that provides functionality for networking, filesystem, and process
-management. Try this command to see available functions: >vim
-
- :lua print(vim.inspect(vim.loop))
-<
-Internally, `vim.loop` wraps the "luv" Lua bindings for the LibUV library;
-see |luv-intro| for a full reference manual.
+`vim.uv` exposes the "luv" Lua bindings for the libUV library that Nvim uses
+for networking, filesystem, and process management, see |luvref.txt|.
+In particular, it allows interacting with the main Nvim |luv-event-loop|.
*E5560* *lua-loop-callbacks*
It is an error to directly invoke `vim.api` functions (except |api-fast|) in
-`vim.loop` callbacks. For example, this is an error: >lua
+`vim.uv` callbacks. For example, this is an error: >lua
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(1000, 0, function()
vim.api.nvim_command('echomsg "test"')
end)
<
To avoid the error use |vim.schedule_wrap()| to defer the callback: >lua
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(1000, 0, vim.schedule_wrap(function()
vim.api.nvim_command('echomsg "test"')
end))
@@ -466,7 +489,7 @@ Example: repeating timer
2. Execute it with ":luafile %". >lua
-- Create a timer handle (implementation detail: uv_timer_t).
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
local i = 0
-- Waits 1000ms, then repeats every 750ms until timer:close().
timer:start(1000, 750, function()
@@ -486,7 +509,7 @@ Example: File-change detection *watch-file*
5. Observe that the file reloads in Nvim (because on_change() calls
|:checktime|). >lua
- local w = vim.loop.new_fs_event()
+ local w = vim.uv.new_fs_event()
local function on_change(err, fname, status)
-- Do work...
vim.api.nvim_command('checktime')
@@ -510,11 +533,11 @@ Example: TCP echo-server *tcp-server*
4. Connect from any TCP client (e.g. "nc 0.0.0.0 36795"): >lua
local function create_server(host, port, on_connect)
- local server = vim.loop.new_tcp()
+ local server = vim.uv.new_tcp()
server:bind(host, port)
server:listen(128, function(err)
assert(not err, err) -- Check for errors.
- local sock = vim.loop.new_tcp()
+ local sock = vim.uv.new_tcp()
server:accept(sock) -- Accept client connection.
on_connect(sock) -- Start reading messages.
end)
@@ -535,105 +558,139 @@ Example: TCP echo-server *tcp-server*
Multithreading *lua-loop-threading*
Plugins can perform work in separate (os-level) threads using the threading
-APIs in luv, for instance `vim.loop.new_thread`. Note that every thread
-gets its own separate lua interpreter state, with no access to lua globals
+APIs in luv, for instance `vim.uv.new_thread`. Note that every thread
+gets its own separate Lua interpreter state, with no access to Lua globals
in the main thread. Neither can the state of the editor (buffers, windows,
etc) be directly accessed from threads.
A subset of the `vim.*` API is available in threads. This includes:
-- `vim.loop` with a separate event loop per thread.
+- `vim.uv` with a separate event loop per thread.
- `vim.mpack` and `vim.json` (useful for serializing messages between threads)
-- `require` in threads can use lua packages from the global |package.path|
+- `require` in threads can use Lua packages from the global |package.path|
- `print()` and `vim.inspect`
- `vim.diff`
-- most utility functions in `vim.*` for working with pure lua values
+- most utility functions in `vim.*` for working with pure Lua values
like `vim.split`, `vim.tbl_*`, `vim.list_*`, and so on.
- `vim.is_thread()` returns true from a non-main thread.
------------------------------------------------------------------------------
-VIM.HIGHLIGHT *lua-highlight*
+VIM.LPEG *lua-lpeg*
+
+ *vim.lpeg* *vim.re*
+The Lpeg library for parsing expression grammars is being included as
+`vim.lpeg` (https://www.inf.puc-rio.br/~roberto/lpeg/). In addition, its regex-like
+interface is available as `vim.re` (https://www.inf.puc-rio.br/~roberto/lpeg/re.html).
-Nvim includes a function for highlighting a selection on yank (see for example
-https://github.com/machakann/vim-highlightedyank). To enable it, add
->vim
+==============================================================================
+VIM.HIGHLIGHT *vim.highlight*
+
+
+Nvim includes a function for highlighting a selection on yank.
+
+To enable it, add the following to your `init.vim`: >vim
au TextYankPost * silent! lua vim.highlight.on_yank()
+
<
-to your `init.vim`. You can customize the highlight group and the duration of
-the highlight via
->vim
+
+You can customize the highlight group and the duration of the highlight
+via: >vim
au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
+
<
-If you want to exclude visual selections from highlighting on yank, use
->vim
+
+If you want to exclude visual selections from highlighting on yank, use: >vim
au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false}
+
<
+
vim.highlight.on_yank({opts}) *vim.highlight.on_yank()*
- Highlights the yanked text. The fields of the optional dict {opts}
- control the highlight:
- - {higroup} highlight group for yanked region (default |hl-IncSearch|)
- - {timeout} time in ms before highlight is cleared (default `150`)
- - {on_macro} highlight when executing macro (default `false`)
- - {on_visual} highlight when yanking visual selection (default `true`)
- - {event} event structure (default |v:event|)
-
-vim.highlight.range({bufnr}, {ns}, {hlgroup}, {start}, {finish}, {opts})
- *vim.highlight.range()*
+ Highlight the yanked text
+
+ Parameters: ~
+ • {opts} (table|nil) Optional parameters
+ • higroup highlight group for yanked region (default
+ "IncSearch")
+ • timeout time in ms before highlight is cleared (default 150)
+ • on_macro highlight when executing macro (default false)
+ • on_visual highlight when yanking visual selection (default
+ true)
+ • event event structure (default vim.v.event)
+ • priority integer priority (default
+ |vim.highlight.priorities|`.user`)
+
+vim.highlight.priorities *vim.highlight.priorities*
+ Table with default priorities used for highlighting:
+ • `syntax`: `50`, used for standard syntax highlighting
+ • `treesitter`: `100`, used for tree-sitter-based highlighting
+ • `semantic_tokens`: `125`, used for LSP semantic token highlighting
+ • `diagnostics`: `150`, used for code analysis such as diagnostics
+ • `user`: `200`, used for user-triggered highlights such as LSP document
+ symbols or `on_yank` autocommands
+ *vim.highlight.range()*
+vim.highlight.range({bufnr}, {ns}, {higroup}, {start}, {finish}, {opts})
Apply highlight group to range of text.
- Parameters: ~
- • {bufnr} buffer number
- • {ns} namespace for highlights
- • {hlgroup} highlight group name
- • {start} starting position (tuple {line,col})
- • {finish} finish position (tuple {line,col})
- • {opts} optional parameters:
- • `regtype`: type of range (characterwise, linewise,
- or blockwise, see |setreg()|), default `'v'`
- • `inclusive`: range includes end position,
- default `false`
- • `priority`: priority of highlight, default
- `vim.highlight.user` (see below)
+ Parameters: ~
+ • {bufnr} (integer) Buffer number to apply highlighting to
+ • {ns} (integer) Namespace to add highlight to
+ • {higroup} (string) Highlight group to use for highlighting
+ • {start} integer[]|string Start of region as a (line, column) tuple
+ or string accepted by |getpos()|
+ • {finish} integer[]|string End of region as a (line, column) tuple or
+ string accepted by |getpos()|
+ • {opts} (table|nil) Optional parameters
+ • regtype type of range (see |setreg()|, default charwise)
+ • inclusive boolean indicating whether the range is
+ end-inclusive (default false)
+ • priority number indicating priority of highlight (default
+ priorities.user)
-vim.highlight.priorities *vim.highlight.priorities*
- Table with default priorities used for highlighting:
- • `syntax`: `50`, used for standard syntax highlighting
- • `treesitter`: `100`, used for tree-sitter-based highlighting
- • `semantic_tokens`: `125`, used for LSP semantic token highlighting
- • `diagnostics`: `150`, used for code analysis such as diagnostics
- • `user`: `200`, used for user-triggered highlights such as LSP document
- symbols or `on_yank` autocommands
+==============================================================================
+VIM.REGEX *vim.regex*
-------------------------------------------------------------------------------
-VIM.REGEX *lua-regex*
-Vim regexes can be used directly from lua. Currently they only allow
+Vim regexes can be used directly from Lua. Currently they only allow
matching within a single line.
+
vim.regex({re}) *vim.regex()*
Parse the Vim regex {re} and return a regex object. Regexes are "magic"
and case-sensitive by default, regardless of 'magic' and 'ignorecase'.
They can be controlled with flags, see |/magic| and |/ignorecase|.
-Methods on the regex object:
+ Parameters: ~
+ • {re} (string)
-regex:match_str({str}) *regex:match_str()*
- Match the string against the regex. If the string should match the regex
- precisely, surround the regex with `^` and `$`. If the was a match, the
- byte indices for the beginning and end of the match is returned. When
- there is no match, `nil` is returned. As any integer is truth-y,
- `regex:match()` can be directly used as a condition in an if-statement.
+ Return: ~
+ vim.regex
-regex:match_line({bufnr}, {line_idx} [, {start}, {end}]) *regex:match_line()*
+ *regex:match_line()*
+vim.regex:match_line({bufnr}, {line_idx}, {start}, {end_})
Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end}
are supplied, match only this byte index range. Otherwise see
|regex:match_str()|. If {start} is used, then the returned byte indices
will be relative {start}.
-------------------------------------------------------------------------------
-VIM.DIFF *lua-diff*
+ Parameters: ~
+ • {bufnr} (integer)
+ • {line_idx} (integer)
+ • {start} (integer|nil)
+ • {end_} (integer|nil)
+
+vim.regex:match_str({str}) *regex:match_str()*
+ Match the string against the regex. If the string should match the regex
+ precisely, surround the regex with `^` and `$` . If there was a match, the byte indices for the beginning and end of the
+ match are returned. When there is no match, `nil` is returned. Because any integer is "truthy", `regex:match_str()` can be directly used as a condition in an if-statement.
+
+ Parameters: ~
+ • {str} (string)
+
+
+==============================================================================
+VIM.DIFF *vim.diff*
vim.diff({a}, {b}, {opts}) *vim.diff()*
Run diff on strings {a} and {b}. Any indices returned by this function,
@@ -653,82 +710,135 @@ vim.diff({a}, {b}, {opts}) *vim.diff()*
-- {1, 1, 1, 2}
-- }
<
+
Parameters: ~
- • {a} First string to compare
- • {b} Second string to compare
- • {opts} Optional parameters:
- • `on_hunk` (callback):
- Invoked for each hunk in the diff. Return a negative number
- to cancel the callback for any remaining hunks.
- Args:
- • `start_a` (integer): Start line of hunk in {a}.
- • `count_a` (integer): Hunk size in {a}.
- • `start_b` (integer): Start line of hunk in {b}.
- • `count_b` (integer): Hunk size in {b}.
- • `result_type` (string): Form of the returned diff:
- • "unified": (default) String in unified format.
- • "indices": Array of hunk locations.
- Note: This option is ignored if `on_hunk` is used.
- • `linematch` (boolean): Run linematch on the resulting hunks
- from xdiff. Requires `result_type = indices`, ignored
- otherwise.
- • `algorithm` (string):
- Diff algorithm to use. Values:
- • "myers" the default algorithm
- • "minimal" spend extra time to generate the
- smallest possible diff
- • "patience" patience diff algorithm
- • "histogram" histogram diff algorithm
- • `ctxlen` (integer): Context length
- • `interhunkctxlen` (integer):
- Inter hunk context length
- • `ignore_whitespace` (boolean):
- Ignore whitespace
- • `ignore_whitespace_change` (boolean):
- Ignore whitespace change
- • `ignore_whitespace_change_at_eol` (boolean)
- Ignore whitespace change at end-of-line.
- • `ignore_cr_at_eol` (boolean)
- Ignore carriage return at end-of-line
- • `ignore_blank_lines` (boolean)
- Ignore blank lines
- • `indent_heuristic` (boolean):
- Use the indent heuristic for the internal
- diff library.
-
- Return: ~
- See {opts.result_type}. nil if {opts.on_hunk} is given.
+ • {a} (string) First string to compare
+ • {b} (string) Second string to compare
+ • {opts} table<string,any> Optional parameters:
+ • `on_hunk` (callback): Invoked for each hunk in the diff. Return a
+ negative number to cancel the callback for any remaining
+ hunks. Args:
+ • `start_a` (integer): Start line of hunk in {a}.
+ • `count_a` (integer): Hunk size in {a}.
+ • `start_b` (integer): Start line of hunk in {b}.
+ • `count_b` (integer): Hunk size in {b}.
+
+ • `result_type` (string): Form of the returned diff:
+ • "unified": (default) String in unified format.
+ • "indices": Array of hunk locations. Note: This option is
+ ignored if `on_hunk` is used.
+
+ • `linematch` (boolean|integer): Run linematch on the
+ resulting hunks from xdiff. When integer, only hunks upto
+ this size in lines are run through linematch. Requires
+ `result_type = indices`, ignored otherwise.
+ • `algorithm` (string): Diff algorithm to use. Values:
+ • "myers" the default algorithm
+ • "minimal" spend extra time to generate the smallest
+ possible diff
+ • "patience" patience diff algorithm
+ • "histogram" histogram diff algorithm
+
+ • `ctxlen` (integer): Context length
+ • `interhunkctxlen` (integer): Inter hunk context length
+ • `ignore_whitespace` (boolean): Ignore whitespace
+ • `ignore_whitespace_change` (boolean): Ignore whitespace
+ change
+ • `ignore_whitespace_change_at_eol` (boolean) Ignore
+ whitespace change at end-of-line.
+ • `ignore_cr_at_eol` (boolean) Ignore carriage return at
+ end-of-line
+ • `ignore_blank_lines` (boolean) Ignore blank lines
+ • `indent_heuristic` (boolean): Use the indent heuristic for
+ the internal diff library.
-------------------------------------------------------------------------------
-VIM.MPACK *lua-mpack*
+ Return: ~
+ string|table|nil See {opts.result_type}. `nil` if {opts.on_hunk} is
+ given.
-The *vim.mpack* module provides encoding and decoding of Lua objects to and
-from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
-vim.mpack.encode({obj}) *vim.mpack.encode*
- Encodes (or "packs") Lua object {obj} as msgpack in a Lua string.
+==============================================================================
+VIM.MPACK *vim.mpack*
+
-vim.mpack.decode({str}) *vim.mpack.decode*
+This module provides encoding and decoding of Lua objects to and from
+msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
+
+vim.mpack.decode({str}) *vim.mpack.decode()*
Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object.
-------------------------------------------------------------------------------
-VIM.JSON *lua-json*
+ Parameters: ~
+ • {str} (string)
-The *vim.json* module provides encoding and decoding of Lua objects to and
-from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
+vim.mpack.encode({obj}) *vim.mpack.encode()*
+ Encodes (or "packs") Lua object {obj} as msgpack in a Lua string.
-vim.json.encode({obj}) *vim.json.encode*
- Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
-vim.json.decode({str}[, {opts}]) *vim.json.decode*
+==============================================================================
+VIM.JSON *vim.json*
+
+
+This module provides encoding and decoding of Lua objects to and from
+JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
+
+vim.json.decode({str}, {opts}) *vim.json.decode()*
Decodes (or "unpacks") the JSON-encoded {str} to a Lua object.
- {opts} is a table with the key `luanil = { object: bool, array: bool }`
- that controls whether `null` in JSON objects or arrays should be converted
- to Lua `nil` instead of `vim.NIL`.
+ • Decodes JSON "null" as |vim.NIL| (controllable by {opts}, see below).
+ • Decodes empty object as |vim.empty_dict()|.
+ • Decodes empty array as `{}` (empty Lua table).
-------------------------------------------------------------------------------
-VIM.SPELL *lua-spell*
+ Example: >lua
+ vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}'))
+ -- { bar = {}, foo = vim.empty_dict(), zub = vim.NIL }
+<
+
+ Parameters: ~
+ • {str} (string) Stringified JSON data.
+ • {opts} table<string,any>|nil Options table with keys:
+ • luanil: (table) Table with keys:
+ • object: (boolean) When true, converts `null` in JSON
+ objects to Lua `nil` instead of |vim.NIL|.
+ • array: (boolean) When true, converts `null` in JSON arrays
+ to Lua `nil` instead of |vim.NIL|.
+
+ Return: ~
+ any
+
+vim.json.encode({obj}) *vim.json.encode()*
+ Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
+
+ Parameters: ~
+ • {obj} any
+
+ Return: ~
+ (string)
+
+
+==============================================================================
+VIM.BASE64 *vim.base64*
+
+vim.base64.decode({str}) *vim.base64.decode()*
+ Decode a Base64 encoded string.
+
+ Parameters: ~
+ • {str} (string) Base64 encoded string
+
+ Return: ~
+ (string) Decoded string
+
+vim.base64.encode({str}) *vim.base64.encode()*
+ Encode {str} using Base64.
+
+ Parameters: ~
+ • {str} (string) String to encode
+
+ Return: ~
+ (string) Encoded string
+
+
+==============================================================================
+VIM.SPELL *vim.spell*
vim.spell.check({str}) *vim.spell.check()*
Check {str} for spelling errors. Similar to the Vimscript function
@@ -745,268 +855,359 @@ vim.spell.check({str}) *vim.spell.check()*
-- {'quik', 'bad', 5}
-- }
<
+
Parameters: ~
- • {str} String to spell check.
+ • {str} (string)
Return: ~
- List of tuples with three items:
- - The badly spelled word.
- - The type of the spelling error:
- "bad" spelling mistake
- "rare" rare word
- "local" word only valid in another region
- "caps" word should start with Capital
- - The position in {str} where the word begins.
+ `{[1]: string, [2]: string, [3]: string}[]` List of tuples with three items:
+ • The badly spelled word.
+ • The type of the spelling error: "bad" spelling mistake "rare" rare
+ word "local" word only valid in another region "caps" word should
+ start with Capital
+ • The position in {str} where the word begins.
-------------------------------------------------------------------------------
-VIM *lua-builtin*
-vim.api.{func}({...}) *vim.api*
+==============================================================================
+VIM *vim.builtin*
+
+
+vim.api.{func}({...}) *vim.api*
Invokes Nvim |API| function {func} with arguments {...}.
Example: call the "nvim_get_current_line()" API function: >lua
print(tostring(vim.api.nvim_get_current_line()))
-vim.version() *vim.version*
- Gets the version of the current Nvim build.
-
-vim.in_fast_event() *vim.in_fast_event()*
- Returns true if the code is executing as part of a "fast" event handler,
- where most of the API is disabled. These are low-level events (e.g.
- |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input.
- When this is `false` most API functions are callable (but may be subject
- to other restrictions such as |textlock|).
-
-vim.NIL *vim.NIL*
+vim.NIL *vim.NIL*
Special value representing NIL in |RPC| and |v:null| in Vimscript
conversion, and similar cases. Lua `nil` cannot be used as part of a Lua
table representing a Dictionary or Array, because it is treated as
missing: `{"foo", nil}` is the same as `{"foo"}`.
+vim.type_idx *vim.type_idx*
+ Type index for use in |lua-special-tbl|. Specifying one of the values from
+ |vim.types| allows typing the empty table (it is unclear whether empty Lua
+ table represents empty list or empty array) and forcing integral numbers
+ to be |Float|. See |lua-special-tbl| for more details.
+
+vim.val_idx *vim.val_idx*
+ Value index for tables representing |Float|s. A table representing
+ floating-point value 1.0 looks like this: >lua
+ {
+ [vim.type_idx] = vim.types.float,
+ [vim.val_idx] = 1.0,
+ }
+< See also |vim.type_idx| and |lua-special-tbl|.
+
+vim.types *vim.types*
+ Table with possible values for |vim.type_idx|. Contains two sets of
+ key-value pairs: first maps possible values for |vim.type_idx| to
+ human-readable strings, second maps human-readable type names to values
+ for |vim.type_idx|. Currently contains pairs for `float`, `array` and
+ `dictionary` types.
+
+ Note: One must expect that values corresponding to `vim.types.float`,
+ `vim.types.array` and `vim.types.dictionary` fall under only two following
+ assumptions:
+ 1. Value may serve both as a key and as a value in a table. Given the
+ properties of Lua tables this basically means “value is not `nil`”.
+ 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
+ same as `value`.
+ No other restrictions are put on types, and it is not guaranteed that
+ values corresponding to `vim.types.float`, `vim.types.array` and
+ `vim.types.dictionary` will not change or that `vim.types` table will only
+ contain values for these three types.
+
+ *log_levels* *vim.log.levels*
+Log levels are one of the values defined in `vim.log.levels`:
+
+ vim.log.levels.DEBUG
+ vim.log.levels.ERROR
+ vim.log.levels.INFO
+ vim.log.levels.TRACE
+ vim.log.levels.WARN
+ vim.log.levels.OFF
+
vim.empty_dict() *vim.empty_dict()*
- Creates a special empty table (marked with a metatable), which Nvim to an
- empty dictionary when translating Lua values to Vimscript or API types.
- Nvim by default converts an empty table `{}` without this metatable to an
- list/array.
+ Creates a special empty table (marked with a metatable), which Nvim
+ converts to an empty dictionary when translating Lua values to Vimscript
+ or API types. Nvim by default converts an empty table `{}` without this
+ metatable to an list/array.
Note: If numeric keys are present in the table, Nvim ignores the metatable
marker and converts the dict to a list/array anyway.
-vim.rpcnotify({channel}, {method} [, {args}...]) *vim.rpcnotify()*
+vim.iconv({str}, {from}, {to}, {opts}) *vim.iconv()*
+ The result is a String, which is the text {str} converted from encoding
+ {from} to encoding {to}. When the conversion fails `nil` is returned. When
+ some characters could not be converted they are replaced with "?". The
+ encoding names are whatever the iconv() library function can accept, see
+ ":Man 3 iconv".
+
+ Parameters: ~
+ • {str} (string) Text to convert
+ • {from} (number) Encoding of {str}
+ • {to} (number) Target encoding
+ • {opts} table<string,any>|nil
+
+ Return: ~
+ (string|nil) Converted string if conversion succeeds, `nil` otherwise.
+
+vim.in_fast_event() *vim.in_fast_event()*
+ Returns true if the code is executing as part of a "fast" event handler,
+ where most of the API is disabled. These are low-level events (e.g.
+ |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input.
+ When this is `false` most API functions are callable (but may be subject
+ to other restrictions such as |textlock|).
+
+vim.rpcnotify({channel}, {method}, {args}, {...}) *vim.rpcnotify()*
Sends {event} to {channel} via |RPC| and returns immediately. If {channel}
is 0, the event is broadcast to all channels.
This function also works in a fast callback |lua-loop-callbacks|.
-vim.rpcrequest({channel}, {method} [, {args}...]) *vim.rpcrequest()*
+ Parameters: ~
+ • {channel} (integer)
+ • {method} (string)
+ • {args} any[]|nil
+ • {...} any|nil
+
+vim.rpcrequest({channel}, {method}, {args}, {...}) *vim.rpcrequest()*
Sends a request to {channel} to invoke {method} via |RPC| and blocks until
a response is received.
Note: NIL values as part of the return value is represented as |vim.NIL|
special value
-vim.stricmp({a}, {b}) *vim.stricmp()*
- Compares strings case-insensitively. Returns 0, 1 or -1 if strings are
- equal, {a} is greater than {b} or {a} is lesser than {b}, respectively.
+ Parameters: ~
+ • {channel} (integer)
+ • {method} (string)
+ • {args} any[]|nil
+ • {...} any|nil
-vim.str_utfindex({str} [, {index}]) *vim.str_utfindex()*
- Convert byte index to UTF-32 and UTF-16 indices. If {index} is not
- supplied, the length of the string is used. All indices are zero-based.
- Returns two values: the UTF-32 and UTF-16 indices respectively.
+vim.schedule({fn}) *vim.schedule()*
+ Schedules {fn} to be invoked soon by the main event-loop. Useful to avoid
+ |textlock| or other temporary restrictions.
- Embedded NUL bytes are treated as terminating the string. Invalid UTF-8
- bytes, and embedded surrogates are counted as one code point each. An
- {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
- that sequence.
+ Parameters: ~
+ • {fn} (function)
-vim.str_byteindex({str}, {index} [, {use_utf16}]) *vim.str_byteindex()*
+vim.str_byteindex({str}, {index}, {use_utf16}) *vim.str_byteindex()*
Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not
supplied, it defaults to false (use UTF-32). Returns the byte index.
- Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|.
- An {index} in the middle of a UTF-16 sequence is rounded upwards to
- the end of that sequence.
+ Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|. An {index}
+ in the middle of a UTF-16 sequence is rounded upwards to the end of that
+ sequence.
-vim.iconv({str}, {from}, {to}[, {opts}]) *vim.iconv()*
- The result is a String, which is the text {str} converted from
- encoding {from} to encoding {to}. When the conversion fails `nil` is
- returned. When some characters could not be converted they
- are replaced with "?".
- The encoding names are whatever the iconv() library function
- can accept, see ":Man 3 iconv".
+ Parameters: ~
+ • {str} (string)
+ • {index} (number)
+ • {use_utf16} any|nil
+
+vim.str_utf_end({str}, {index}) *vim.str_utf_end()*
+ Gets the distance (in bytes) from the last byte of the codepoint
+ (character) that {index} points to.
- Parameters: ~
- • {str} (string) Text to convert
- • {from} (string) Encoding of {str}
- • {to} (string) Target encoding
+ Examples: >lua
+ -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8)
- Returns: ~
- Converted string if conversion succeeds, `nil` otherwise.
+ -- Returns 0 because the index is pointing at the last byte of a character
+ vim.str_utf_end('æ', 2)
-vim.schedule({callback}) *vim.schedule()*
- Schedules {callback} to be invoked soon by the main event-loop. Useful
- to avoid |textlock| or other temporary restrictions.
+ -- Returns 1 because the index is pointing at the penultimate byte of a character
+ vim.str_utf_end('æ', 1)
+<
+ Parameters: ~
+ • {str} (string)
+ • {index} (number)
+
+ Return: ~
+ (number)
-vim.defer_fn({fn}, {timeout}) *vim.defer_fn*
- Defers calling {fn} until {timeout} ms passes. Use to do a one-shot timer
- that calls {fn}.
+vim.str_utf_pos({str}) *vim.str_utf_pos()*
+ Gets a list of the starting byte positions of each UTF-8 codepoint in the
+ given string.
- Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions are
- safe to call.
+ Embedded NUL bytes are treated as terminating the string.
Parameters: ~
- • {fn} Callback to call once {timeout} expires
- • {timeout} Time in ms to wait before calling {fn}
+ • {str} (string)
- Returns: ~
- |vim.loop|.new_timer() object
+ Return: ~
+ (table)
-vim.wait({time} [, {callback}, {interval}, {fast_only}]) *vim.wait()*
- Wait for {time} in milliseconds until {callback} returns `true`.
+vim.str_utf_start({str}, {index}) *vim.str_utf_start()*
+ Gets the distance (in bytes) from the starting byte of the codepoint
+ (character) that {index} points to.
- Executes {callback} immediately and at approximately {interval}
- milliseconds (default 200). Nvim still processes other events during
- this time.
+ The result can be added to {index} to get the starting byte of a
+ character.
+
+ Examples: >lua
+ -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8)
+
+ -- Returns 0 because the index is pointing at the first byte of a character
+ vim.str_utf_start('æ', 1)
+
+ -- Returns -1 because the index is pointing at the second byte of a character
+ vim.str_utf_start('æ', 2)
+<
Parameters: ~
- • {time} Number of milliseconds to wait
- • {callback} Optional callback. Waits until {callback} returns true
- • {interval} (Approximate) number of milliseconds to wait between polls
- • {fast_only} If true, only |api-fast| events will be processed.
- If called from while in an |api-fast| event, will
- automatically be set to `true`.
+ • {str} (string)
+ • {index} (number)
- Returns: ~
- If {callback} returns `true` during the {time}:
- `true, nil`
+ Return: ~
+ (number)
- If {callback} never returns `true` during the {time}:
- `false, -1`
+vim.str_utfindex({str}, {index}) *vim.str_utfindex()*
+ Convert byte index to UTF-32 and UTF-16 indices. If {index} is not
+ supplied, the length of the string is used. All indices are zero-based.
- If {callback} is interrupted during the {time}:
- `false, -2`
+ Embedded NUL bytes are treated as terminating the string. Invalid UTF-8
+ bytes, and embedded surrogates are counted as one code point each. An
+ {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
+ that sequence.
- If {callback} errors, the error is raised.
+ Parameters: ~
+ • {str} (string)
+ • {index} (number|nil)
- Examples: >lua
+ Return (multiple): ~
+ (integer) UTF-32 index
+ (integer) UTF-16 index
- ---
- -- Wait for 100 ms, allowing other events to process
- vim.wait(100, function() end)
+vim.stricmp({a}, {b}) *vim.stricmp()*
+ Compares strings case-insensitively.
- ---
- -- Wait for 100 ms or until global variable set.
- vim.wait(100, function() return vim.g.waiting_for_var end)
+ Parameters: ~
+ • {a} (string)
+ • {b} (string)
- ---
- -- Wait for 1 second or until global variable set, checking every ~500 ms
- vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
+ Return: ~
+ 0|1|-1 if strings are equal, {a} is greater than {b} or {a} is lesser
+ than {b}, respectively.
+
+vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
+ Attach to ui events, similar to |nvim_ui_attach()| but receive events as
+ Lua callback. Can be used to implement screen elements like popupmenu or
+ message handling in Lua.
+
+ {options} should be a dictionary-like table, where `ext_...` options
+ should be set to true to receive events for the respective external
+ element.
+
+ {callback} receives event name plus additional parameters. See
+ |ui-popupmenu| and the sections below for event format for respective
+ events.
+
+ WARNING: This api is considered experimental. Usability will vary for
+ different screen elements. In particular `ext_messages` behavior is
+ subject to further changes and usability improvements. This is expected to
+ be used to handle messages when setting 'cmdheight' to zero (which is
+ likewise experimental).
- ---
- -- Schedule a function to set a value in 100ms
- vim.defer_fn(function() vim.g.timer_result = true end, 100)
+ Example (stub for a |ui-popupmenu| implementation): >lua
+ ns = vim.api.nvim_create_namespace('my_fancy_pum')
- -- Would wait ten seconds if results blocked. Actually only waits 100 ms
- if vim.wait(10000, function() return vim.g.timer_result end) then
- print('Only waiting a little bit of time!')
- end
+ vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...)
+ if event == "popupmenu_show" then
+ local items, selected, row, col, grid = ...
+ print("display pum ", #items)
+ elseif event == "popupmenu_select" then
+ local selected = ...
+ print("selected", selected)
+ elseif event == "popupmenu_hide" then
+ print("FIN")
+ end
+ end)
<
-vim.ui_attach({ns}, {options}, {callback}) *vim.ui_attach()*
- Attach to ui events, similar to |nvim_ui_attach()| but receive events
- as lua callback. Can be used to implement screen elements like
- popupmenu or message handling in lua.
+ Parameters: ~
+ • {ns} (integer)
+ • {options} table<string, any>
+ • {callback} fun()
- {options} should be a dictionary-like table, where `ext_...` options should
- be set to true to receive events for the respective external element.
+vim.ui_detach({ns}) *vim.ui_detach()*
+ Detach a callback previously attached with |vim.ui_attach()| for the given
+ namespace {ns}.
- {callback} receives event name plus additional parameters. See |ui-popupmenu|
- and the sections below for event format for respective events.
+ Parameters: ~
+ • {ns} (integer)
- WARNING: This api is considered experimental. Usability will vary for
- different screen elements. In particular `ext_messages` behavior is subject
- to further changes and usability improvements. This is expected to be
- used to handle messages when setting 'cmdheight' to zero (which is
- likewise experimental).
+vim.wait({time}, {callback}, {interval}, {fast_only}) *vim.wait()*
+ Wait for {time} in milliseconds until {callback} returns `true`.
- Example (stub for a |ui-popupmenu| implementation): >lua
+ Executes {callback} immediately and at approximately {interval}
+ milliseconds (default 200). Nvim still processes other events during this
+ time.
- ns = vim.api.nvim_create_namespace('my_fancy_pum')
-
- vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...)
- if event == "popupmenu_show" then
- local items, selected, row, col, grid = ...
- print("display pum ", #items)
- elseif event == "popupmenu_select" then
- local selected = ...
- print("selected", selected)
- elseif event == "popupmenu_hide" then
- print("FIN")
- end
- end)
+ Cannot be called while in an |api-fast| event.
-vim.ui_detach({ns}) *vim.ui_detach()*
- Detach a callback previously attached with |vim.ui_attach()| for the
- given namespace {ns}.
+ Examples: >lua
+ ---
+ -- Wait for 100 ms, allowing other events to process
+ vim.wait(100, function() end)
-vim.type_idx *vim.type_idx*
- Type index for use in |lua-special-tbl|. Specifying one of the values from
- |vim.types| allows typing the empty table (it is unclear whether empty Lua
- table represents empty list or empty array) and forcing integral numbers
- to be |Float|. See |lua-special-tbl| for more details.
+ ---
+ -- Wait for 100 ms or until global variable set.
+ vim.wait(100, function() return vim.g.waiting_for_var end)
-vim.val_idx *vim.val_idx*
- Value index for tables representing |Float|s. A table representing
- floating-point value 1.0 looks like this: >lua
- {
- [vim.type_idx] = vim.types.float,
- [vim.val_idx] = 1.0,
- }
-< See also |vim.type_idx| and |lua-special-tbl|.
+ ---
+ -- Wait for 1 second or until global variable set, checking every ~500 ms
+ vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
-vim.types *vim.types*
- Table with possible values for |vim.type_idx|. Contains two sets of
- key-value pairs: first maps possible values for |vim.type_idx| to
- human-readable strings, second maps human-readable type names to values
- for |vim.type_idx|. Currently contains pairs for `float`, `array` and
- `dictionary` types.
+ ---
+ -- Schedule a function to set a value in 100ms
+ vim.defer_fn(function() vim.g.timer_result = true end, 100)
- Note: One must expect that values corresponding to `vim.types.float`,
- `vim.types.array` and `vim.types.dictionary` fall under only two following
- assumptions:
- 1. Value may serve both as a key and as a value in a table. Given the
- properties of Lua tables this basically means “value is not `nil`”.
- 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
- same as `value`.
- No other restrictions are put on types, and it is not guaranteed that
- values corresponding to `vim.types.float`, `vim.types.array` and
- `vim.types.dictionary` will not change or that `vim.types` table will only
- contain values for these three types.
+ -- Would wait ten seconds if results blocked. Actually only waits 100 ms
+ if vim.wait(10000, function() return vim.g.timer_result end) then
+ print('Only waiting a little bit of time!')
+ end
+<
- *log_levels* *vim.log.levels*
-Log levels are one of the values defined in `vim.log.levels`:
+ Parameters: ~
+ • {time} (integer) Number of milliseconds to wait
+ • {callback} fun():|nil boolean Optional callback. Waits until
+ {callback} returns true
+ • {interval} (integer|nil) (Approximate) number of milliseconds to
+ wait between polls
+ • {fast_only} (boolean|nil) If true, only |api-fast| events will be
+ processed.
- vim.log.levels.DEBUG
- vim.log.levels.ERROR
- vim.log.levels.INFO
- vim.log.levels.TRACE
- vim.log.levels.WARN
- vim.log.levels.OFF
+ Return: ~
+ boolean, nil|-1|-2
+ • If {callback} returns `true` during the {time}: `true, nil`
+ • If {callback} never returns `true` during the {time}: `false, -1`
+ • If {callback} is interrupted during the {time}: `false, -2`
+ • If {callback} errors, the error is raised.
-------------------------------------------------------------------------------
+
+==============================================================================
LUA-VIMSCRIPT BRIDGE *lua-vimscript*
-Nvim Lua provides an interface to Vimscript variables and functions, and
-editor commands and options.
-See also https://github.com/nanotee/nvim-lua-guide.
+Nvim Lua provides an interface or "bridge" to Vimscript variables and
+functions, and editor commands and options.
+
+Objects passed over this bridge are COPIED (marshalled): there are no
+"references". |lua-guide-variables| For example, using `vim.fn.remove()`
+on a Lua list copies the list object to Vimscript and does NOT modify the
+Lua list: >lua
+ local list = { 1, 2, 3 }
+ vim.fn.remove(list, 0)
+ vim.print(list) --> "{ 1, 2, 3 }"
+
+<
vim.call({func}, {...}) *vim.call()*
Invokes |vim-function| or |user-function| {func} with arguments {...}.
See also |vim.fn|.
Equivalent to: >lua
vim.fn[func]({...})
-
+<
vim.cmd({command})
See |vim.cmd()|.
@@ -1079,16 +1280,7 @@ vim.v *vim.v*
|v:| variables.
Invalid or unset key returns `nil`.
-vim.env *vim.env*
- Environment variables defined in the editor session.
- See |expand-env| and |:let-environment| for the Vimscript behavior.
- Invalid or unset key returns `nil`.
- Example: >lua
- vim.env.FOO = 'bar'
- print(vim.env.TERM)
-<
-
- *lua-options*
+` ` *lua-options*
*lua-vim-options*
*lua-vim-set*
*lua-vim-setlocal*
@@ -1111,66 +1303,10 @@ window-scoped options. Note that this must NOT be confused with
|local-options| and |:setlocal|. There is also |vim.go| that only accesses the
global value of a |global-local| option, see |:setglobal|.
-vim.o *vim.o*
- Get or set |options|. Like `:set`. Invalid key is an error.
-
- Note: this works on both buffer-scoped and window-scoped options using the
- current buffer and window.
-
- Example: >lua
- vim.o.cmdheight = 4
- print(vim.o.columns)
- print(vim.o.foo) -- error: invalid key
-<
-vim.go *vim.go*
- Get or set global |options|. Like `:setglobal`. Invalid key is
- an error.
-
- Note: this is different from |vim.o| because this accesses the global
- option value and thus is mostly useful for use with |global-local|
- options.
-
- Example: >lua
- vim.go.cmdheight = 4
- print(vim.go.columns)
- print(vim.go.bar) -- error: invalid key
-<
-vim.bo[{bufnr}] *vim.bo*
- Get or set buffer-scoped |options| for the buffer with number {bufnr}.
- Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
- buffer is used. Invalid {bufnr} or key is an error.
-
- Note: this is equivalent to both `:set` and `:setlocal`.
-
- Example: >lua
- local bufnr = vim.api.nvim_get_current_buf()
- vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true
- print(vim.bo.comments)
- print(vim.bo.baz) -- error: invalid key
-<
-vim.wo[{winid}] *vim.wo*
- Get or set window-scoped |options| for the window with handle {winid}.
- Like `:set`. If [{winid}] is omitted then the current window is used.
- Invalid {winid} or key is an error.
-
- Note: this does not access |local-options| (`:setlocal`) instead use: >lua
- nvim_get_option_value(OPTION, { scope = 'local', win = winid })
- nvim_set_option_value(OPTION, VALUE, { scope = 'local', win = winid }
-<
- Example: >lua
- local winid = vim.api.nvim_get_current_win()
- vim.wo[winid].number = true -- same as vim.wo.number = true
- print(vim.wo.foldmarker)
- print(vim.wo.quux) -- error: invalid key
-<
-
-
-
- *vim.opt_local*
+` ` *vim.opt_local*
*vim.opt_global*
*vim.opt*
-
A special interface |vim.opt| exists for conveniently interacting with list-
and map-style option from Lua: It allows accessing them as Lua tables and
offers object-oriented method for adding and removing entries.
@@ -1223,26 +1359,33 @@ which is accessed through |vim.opt:get()|:
print(vim.o.wildignore)
<
In Lua using `vim.opt`: >lua
- vim.pretty_print(vim.opt.wildignore:get())
+ vim.print(vim.opt.wildignore:get())
<
In any of the above examples, to replicate the behavior |:setlocal|, use
`vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use
`vim.opt_global`.
+Option:append({value}) *vim.opt:append()*
+ Append a value to string-style options. See |:set+=|
+ These are equivalent: >lua
+ vim.opt.formatoptions:append('j')
+ vim.opt.formatoptions = vim.opt.formatoptions + 'j'
+<
- *vim.opt:get()*
-Option:get()
+ Parameters: ~
+ • {value} (string) Value to append
- Returns a lua-representation of the option. Boolean, number and string
+Option:get() *vim.opt:get()*
+ Returns a Lua-representation of the option. Boolean, number and string
values will be returned in exactly the same fashion.
For values that are comma-separated lists, an array will be returned with
the values as entries in the array: >lua
vim.cmd [[set wildignore=*.pyc,*.o]]
- vim.pretty_print(vim.opt.wildignore:get())
+ vim.print(vim.opt.wildignore:get())
-- { "*.pyc", "*.o", }
for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do
@@ -1251,22 +1394,24 @@ Option:get()
-- Will ignore: *.pyc
-- Will ignore: *.o
<
+
For values that are comma-separated maps, a table will be returned with
the names as keys and the values as entries: >lua
vim.cmd [[set listchars=space:_,tab:>~]]
- vim.pretty_print(vim.opt.listchars:get())
+ vim.print(vim.opt.listchars:get())
-- { space = "_", tab = ">~", }
for char, representation in pairs(vim.opt.listchars:get()) do
print(char, "=>", representation)
end
<
+
For values that are lists of flags, a set will be returned with the flags
as keys and `true` as entries. >lua
vim.cmd [[set formatoptions=njtcroql]]
- vim.pretty_print(vim.opt.formatoptions:get())
+ vim.print(vim.opt.formatoptions:get())
-- { n = true, j = true, c = true, ... }
local format_opts = vim.opt.formatoptions:get()
@@ -1274,27 +1419,22 @@ Option:get()
print("J is enabled!")
end
<
- *vim.opt:append()*
-Option:append(value)
- Append a value to string-style options. See |:set+=|
-
- These are equivalent: >lua
- vim.opt.formatoptions:append('j')
- vim.opt.formatoptions = vim.opt.formatoptions + 'j'
-<
- *vim.opt:prepend()*
-Option:prepend(value)
+ Return: ~
+ string|integer|boolean|nil value of option
+Option:prepend({value}) *vim.opt:prepend()*
Prepend a value to string-style options. See |:set^=|
These are equivalent: >lua
vim.opt.wildignore:prepend('*.o')
vim.opt.wildignore = vim.opt.wildignore ^ '*.o'
<
- *vim.opt:remove()*
-Option:remove(value)
+ Parameters: ~
+ • {value} (string) Value to prepend
+
+Option:remove({value}) *vim.opt:remove()*
Remove a value from string-style options. See |:set-=|
These are equivalent: >lua
@@ -1302,93 +1442,191 @@ Option:remove(value)
vim.opt.wildignore = vim.opt.wildignore - '*.pyc'
<
-==============================================================================
-Lua module: vim *lua-vim*
+ Parameters: ~
+ • {value} (string) Value to remove
-cmd({command}) *vim.cmd()*
- Execute Vim script commands.
+vim.bo *vim.bo*
+ Get or set buffer-scoped |options| for the buffer with number {bufnr}.
+ Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
+ buffer is used. Invalid {bufnr} or key is an error.
- Note that `vim.cmd` can be indexed with a command name to return a
- callable function to the command.
+ Note: this is equivalent to both `:set` and `:setlocal`.
+
+ Example: >lua
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true
+ print(vim.bo.comments)
+ print(vim.bo.baz) -- error: invalid key
+<
+
+vim.env *vim.env*
+ Environment variables defined in the editor session. See |expand-env| and
+ |:let-environment| for the Vimscript behavior. Invalid or unset key
+ returns `nil`.
+
+ Example: >lua
+ vim.env.FOO = 'bar'
+ print(vim.env.TERM)
+<
+
+ Parameters: ~
+ • {var} (string)
+
+vim.go *vim.go*
+ Get or set global |options|. Like `:setglobal`. Invalid key is an error.
+
+ Note: this is different from |vim.o| because this accesses the global
+ option value and thus is mostly useful for use with |global-local|
+ options.
Example: >lua
+ vim.go.cmdheight = 4
+ print(vim.go.columns)
+ print(vim.go.bar) -- error: invalid key
+<
- vim.cmd('echo 42')
- vim.cmd([[
- augroup My_group
- autocmd!
- autocmd FileType c setlocal cindent
- augroup END
- ]])
+vim.o *vim.o*
+ Get or set |options|. Like `:set`. Invalid key is an error.
- -- Ex command :echo "foo"
- -- Note string literals need to be double quoted.
- vim.cmd('echo "foo"')
- vim.cmd { cmd = 'echo', args = { '"foo"' } }
- vim.cmd.echo({ args = { '"foo"' } })
- vim.cmd.echo('"foo"')
+ Note: this works on both buffer-scoped and window-scoped options using the
+ current buffer and window.
- -- Ex command :write! myfile.txt
- vim.cmd('write! myfile.txt')
- vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true }
- vim.cmd.write { args = { "myfile.txt" }, bang = true }
- vim.cmd.write { "myfile.txt", bang = true }
+ Example: >lua
+ vim.o.cmdheight = 4
+ print(vim.o.columns)
+ print(vim.o.foo) -- error: invalid key
+<
+
+vim.wo *vim.wo*
+ Get or set window-scoped |options| for the window with handle {winid} and
+ buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
+ `:set` otherwise. If [{winid}] is omitted then the current window is used.
+ Invalid {winid}, {bufnr} or key is an error.
- -- Ex command :colorscheme blue
- vim.cmd('colorscheme blue')
- vim.cmd.colorscheme('blue')
+ Note: only {bufnr} with value `0` (the current buffer in the window) is
+ supported.
+
+ Example: >lua
+ local winid = vim.api.nvim_get_current_win()
+ vim.wo[winid].number = true -- same as vim.wo.number = true
+ print(vim.wo.foldmarker)
+ print(vim.wo.quux) -- error: invalid key
+ vim.wo[winid][0].spell = false -- like ':setlocal nospell'
+<
+
+
+==============================================================================
+Lua module: vim *lua-vim*
+
+vim.cmd *vim.cmd()*
+ Executes Vim script commands.
+
+ Note that `vim.cmd` can be indexed with a command name to return a
+ callable function to the command.
+
+ Example: >lua
+ vim.cmd('echo 42')
+ vim.cmd([[
+ augroup My_group
+ autocmd!
+ autocmd FileType c setlocal cindent
+ augroup END
+ ]])
+
+ -- Ex command :echo "foo"
+ -- Note string literals need to be double quoted.
+ vim.cmd('echo "foo"')
+ vim.cmd { cmd = 'echo', args = { '"foo"' } }
+ vim.cmd.echo({ args = { '"foo"' } })
+ vim.cmd.echo('"foo"')
+
+ -- Ex command :write! myfile.txt
+ vim.cmd('write! myfile.txt')
+ vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true }
+ vim.cmd.write { args = { "myfile.txt" }, bang = true }
+ vim.cmd.write { "myfile.txt", bang = true }
+
+ -- Ex command :colorscheme blue
+ vim.cmd('colorscheme blue')
+ vim.cmd.colorscheme('blue')
<
Parameters: ~
• {command} string|table Command(s) to execute. If a string, executes
multiple lines of Vim script at once. In this case, it is
- an alias to |nvim_exec()|, where `output` is set to false.
- Thus it works identical to |:source|. If a table, executes
- a single command. In this case, it is an alias to
+ an alias to |nvim_exec2()|, where `opts.output` is set to
+ false. Thus it works identical to |:source|. If a table,
+ executes a single command. In this case, it is an alias to
|nvim_cmd()| where `opts` is empty.
See also: ~
- |ex-cmd-index|
-
- *vim.connection_failure_errmsg()*
-connection_failure_errmsg({consequence})
- TODO: Documentation
+ • |ex-cmd-index|
-defer_fn({fn}, {timeout}) *vim.defer_fn()*
- Defers calling `fn` until `timeout` ms passes.
+vim.defer_fn({fn}, {timeout}) *vim.defer_fn()*
+ Defers calling {fn} until {timeout} ms passes.
- Use to do a one-shot timer that calls `fn` Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions
- are safe to call.
+ Use to do a one-shot timer that calls {fn} Note: The {fn} is
+ |vim.schedule_wrap()|ped automatically, so API functions are safe to call.
Parameters: ~
• {fn} (function) Callback to call once `timeout` expires
- • {timeout} integer Number of milliseconds to wait before calling `fn`
+ • {timeout} (integer) Number of milliseconds to wait before calling
+ `fn`
Return: ~
(table) timer luv timer object
*vim.deprecate()*
-deprecate({name}, {alternative}, {version}, {plugin}, {backtrace})
- Display a deprecation notification to the user.
+vim.deprecate({name}, {alternative}, {version}, {plugin}, {backtrace})
+ Shows a deprecation message to the user.
Parameters: ~
- • {name} string Deprecated function.
- • {alternative} (string|nil) Preferred alternative function.
- • {version} string Version in which the deprecated function will be
- removed.
- • {plugin} string|nil Plugin name that the function will be
- removed from. Defaults to "Nvim".
+ • {name} string Deprecated feature (function, API, etc.).
+ • {alternative} (string|nil) Suggested alternative feature.
+ • {version} string Version when the deprecated function will be removed.
+ • {plugin} string|nil Name of the plugin that owns the deprecated
+ feature. Defaults to "Nvim".
• {backtrace} boolean|nil Prints backtrace. Defaults to true.
-inspect({object}, {options}) *vim.inspect()*
+ Return: ~
+ (string|nil) Deprecated message, or nil if no message was shown.
+
+vim.inspect *vim.inspect()*
Gets a human-readable representation of the given object.
+ Return: ~
+ (string)
+
See also: ~
- https://github.com/kikito/inspect.lua
- https://github.com/mpeterv/vinspect
+ • |vim.print()|
+ • https://github.com/kikito/inspect.lua
+ • https://github.com/mpeterv/vinspect
-notify({msg}, {level}, {opts}) *vim.notify()*
- Display a notification to the user.
+vim.keycode({str}) *vim.keycode()*
+ Translates keycodes.
+
+ Example: >lua
+ local k = vim.keycode
+ vim.g.mapleader = k'<bs>'
+<
+
+ Parameters: ~
+ • {str} (string) String to be converted.
+
+ Return: ~
+ (string)
+
+ See also: ~
+ • |nvim_replace_termcodes()|
+
+vim.lua_omnifunc({find_start}, {_}) *vim.lua_omnifunc()*
+ Omnifunc for completing Lua values from the runtime Lua interpreter,
+ similar to the builtin completion for the `:lua` command.
+
+ Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a Lua buffer.
+
+vim.notify({msg}, {level}, {opts}) *vim.notify()*
+ Displays a notification to the user.
This function can be overridden by plugins to display notifications using
a custom provider (such as the system notification provider). By default,
@@ -1396,66 +1634,60 @@ notify({msg}, {level}, {opts}) *vim.notify()*
Parameters: ~
• {msg} (string) Content of the notification to show to the user.
- • {level} (number|nil) One of the values from |vim.log.levels|.
+ • {level} (integer|nil) One of the values from |vim.log.levels|.
• {opts} (table|nil) Optional parameters. Unused by default.
-notify_once({msg}, {level}, {opts}) *vim.notify_once()*
- Display a notification only one time.
+vim.notify_once({msg}, {level}, {opts}) *vim.notify_once()*
+ Displays a notification only one time.
Like |vim.notify()|, but subsequent calls with the same message will not
display a notification.
Parameters: ~
• {msg} (string) Content of the notification to show to the user.
- • {level} (number|nil) One of the values from |vim.log.levels|.
+ • {level} (integer|nil) One of the values from |vim.log.levels|.
• {opts} (table|nil) Optional parameters. Unused by default.
Return: ~
(boolean) true if message was displayed, else false
-on_key({fn}, {ns_id}) *vim.on_key()*
+vim.on_key({fn}, {ns_id}) *vim.on_key()*
Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
yes every, input key.
The Nvim command-line option |-w| is related but does not support
callbacks and cannot be toggled dynamically.
- Note:
- {fn} will not be cleared by |nvim_buf_clear_namespace()|
-
- Note:
- {fn} will receive the keys after mappings have been evaluated
+ Note: ~
+ • {fn} will be removed on error.
+ • {fn} will not be cleared by |nvim_buf_clear_namespace()|
+ • {fn} will receive the keys after mappings have been evaluated
Parameters: ~
- • {fn} (function) Callback function. It should take one string
- argument. On each key press, Nvim passes the key char to
- fn(). |i_CTRL-V| If {fn} is nil, it removes the callback for
- the associated {ns_id}
- • {ns_id} number? Namespace ID. If nil or 0, generates and returns a
+ • {fn} fun(key: string) Function invoked on every key press.
+ |i_CTRL-V| Returning nil removes the callback associated with
+ namespace {ns_id}.
+ • {ns_id} integer? Namespace ID. If nil or 0, generates and returns a
new |nvim_create_namespace()| id.
Return: ~
- (number) Namespace id associated with {fn}. Or count of all callbacks
+ (integer) Namespace id associated with {fn}. Or count of all callbacks
if on_key() is called without arguments.
- Note:
- {fn} will be removed if an error occurs while calling.
-
-paste({lines}, {phase}) *vim.paste()*
+vim.paste({lines}, {phase}) *vim.paste()*
Paste handler, invoked by |nvim_paste()| when a conforming UI (such as the
|TUI|) pastes text into the editor.
Example: To remove ANSI color codes when pasting: >lua
-
- vim.paste = (function(overridden)
- return function(lines, phase)
- for i,line in ipairs(lines) do
- -- Scrub ANSI color codes from paste input.
- lines[i] = line:gsub('\27%[[0-9;mK]+', '')
- end
- overridden(lines, phase)
- end
- end)(vim.paste)
+ vim.paste = (function(overridden)
+ return function(lines, phase)
+ for i,line in ipairs(lines) do
+ -- Scrub ANSI color codes from paste input.
+ lines[i] = line:gsub('\27%[[0-9;mK]+', '')
+ end
+ overridden(lines, phase)
+ end
+ end)(vim.paste)
<
Parameters: ~
@@ -1468,68 +1700,163 @@ paste({lines}, {phase}) *vim.paste()*
• 3: ends the paste (exactly once)
Return: ~
- (boolean) # false if client should cancel the paste.
+ (boolean) result false if client should cancel the paste.
See also: ~
- |paste| @alias paste_phase -1 | 1 | 2 | 3
+ • |paste| @alias paste_phase -1 | 1 | 2 | 3
-pretty_print({...}) *vim.pretty_print()*
- Prints given arguments in human-readable format. Example: >lua
- -- Print highlight group Normal and store it's contents in a variable.
- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
+vim.print({...}) *vim.print()*
+ "Pretty prints" the given arguments and returns them unmodified.
+
+ Example: >lua
+ local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))
<
Return: ~
- any # given arguments.
+ any given arguments.
See also: ~
- |vim.inspect()|
+ • |vim.inspect()|
+ • |:=|
-region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()*
- Get a table of lines with start, end columns for a region marked by two
- points
+ *vim.region()*
+vim.region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive})
+ Gets a dict of line segment ("chunk") positions for the region from `pos1`
+ to `pos2`.
+
+ Input and output positions are byte positions, (0,0)-indexed. "End of
+ line" column position (for example, |linewise| visual selection) is
+ returned as |v:maxcol| (big number).
Parameters: ~
- • {bufnr} (number) of buffer
- • {pos1} integer[] (line, column) tuple marking beginning of
- region
- • {pos2} integer[] (line, column) tuple marking end of region
- • {regtype} (string) type of selection, see |setreg()|
- • {inclusive} (boolean) indicating whether the selection is
- end-inclusive
+ • {bufnr} (integer) Buffer number, or 0 for current buffer
+ • {pos1} integer[]|string Start of region as a (line, column)
+ tuple or |getpos()|-compatible string
+ • {pos2} integer[]|string End of region as a (line, column) tuple
+ or |getpos()|-compatible string
+ • {regtype} (string) |setreg()|-style selection type
+ • {inclusive} (boolean) Controls whether the ending column is inclusive
+ (see also 'selection').
Return: ~
- (table) region Table of the form `{linenr = {startcol,endcol}}`
+ (table) region Dict of the form `{linenr = {startcol,endcol}}`.
+ `endcol` is exclusive, and whole lines are returned as
+ `{startcol,endcol} = {0,-1}`.
+
+vim.schedule_wrap({fn}) *vim.schedule_wrap()*
+ Returns a function which calls {fn} via |vim.schedule()|.
-schedule_wrap({cb}) *vim.schedule_wrap()*
- Defers callback `cb` until the Nvim API is safe to call.
+ The returned function passes all arguments to {fn}.
+
+ Example: >lua
+ function notify_readable(_err, readable)
+ vim.notify("readable? " .. tostring(readable))
+ end
+ vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable))
+<
Parameters: ~
- • {cb} (function)
+ • {fn} (function)
Return: ~
(function)
See also: ~
- |lua-loop-callbacks|
- |vim.schedule()|
- |vim.in_fast_event()|
+ • |lua-loop-callbacks|
+ • |vim.schedule()|
+ • |vim.in_fast_event()|
+
+vim.system({cmd}, {opts}, {on_exit}) *vim.system()*
+ Runs a system command or throws an error if {cmd} cannot be run.
+
+ Examples: >lua
+ local on_exit = function(obj)
+ print(obj.code)
+ print(obj.signal)
+ print(obj.stdout)
+ print(obj.stderr)
+ end
+
+ -- Runs asynchronously:
+ vim.system({'echo', 'hello'}, { text = true }, on_exit)
+
+ -- Runs synchronously:
+ local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
+ -- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
+<
+
+ See |uv.spawn()| for more details. Note: unlike |uv.spawn()|, vim.system
+ throws an error if {cmd} cannot be run.
+
+ Parameters: ~
+ • {cmd} (string[]) Command to execute
+ • {opts} (SystemOpts|nil) Options:
+ • cwd: (string) Set the current working directory for the
+ sub-process.
+ • env: table<string,string> Set environment variables for
+ the new process. Inherits the current environment with
+ `NVIM` set to |v:servername|.
+ • clear_env: (boolean) `env` defines the job environment
+ exactly, instead of merging current environment.
+ • stdin: (string|string[]|boolean) If `true`, then a pipe
+ to stdin is opened and can be written to via the
+ `write()` method to SystemObj. If string or string[] then
+ will be written to stdin and closed. Defaults to `false`.
+ • stdout: (boolean|function) Handle output from stdout.
+ When passed as a function must have the signature
+ `fun(err: string, data: string)`. Defaults to `true`
+ • stderr: (boolean|function) Handle output from stderr.
+ When passed as a function must have the signature
+ `fun(err: string, data: string)`. Defaults to `true`.
+ • text: (boolean) Handle stdout and stderr as text.
+ Replaces `\r\n` with `\n`.
+ • timeout: (integer) Run the command with a time limit.
+ Upon timeout the process is sent the TERM signal (15) and
+ the exit code is set to 124.
+ • detach: (boolean) If true, spawn the child process in a
+ detached state - this will make it a process group
+ leader, and will effectively enable the child to keep
+ running after the parent exits. Note that the child
+ process will still keep the parent's event loop alive
+ unless the parent process calls |uv.unref()| on the
+ child's process handle.
+ • {on_exit} (function|nil) Called when subprocess exits. When provided,
+ the command runs asynchronously. Receives SystemCompleted
+ object, see return of SystemObj:wait().
+
+ Return: ~
+ vim.SystemObj Object with the fields:
+ • pid (integer) Process ID
+ • wait (fun(timeout: integer|nil): SystemCompleted) Wait for the
+ process to complete. Upon timeout the process is sent the KILL
+ signal (9) and the exit code is set to 124. Cannot be called in
+ |api-fast|.
+ • SystemCompleted is an object with the fields:
+ • code: (integer)
+ • signal: (integer)
+ • stdout: (string), nil if stdout argument is passed
+ • stderr: (string), nil if stderr argument is passed
+
+ • kill (fun(signal: integer|string))
+ • write (fun(data: string|nil)) Requires `stdin=true`. Pass `nil` to
+ close the stream.
+ • is_closing (fun(): boolean)
==============================================================================
-Lua module: inspector *lua-inspector*
+Lua module: vim.inspector *vim.inspector*
-inspect_pos({bufnr}, {row}, {col}, {filter}) *vim.inspect_pos()*
+vim.inspect_pos({bufnr}, {row}, {col}, {filter}) *vim.inspect_pos()*
Get all the items at a given buffer position.
Can also be pretty-printed with `:Inspect!`. *:Inspect!*
Parameters: ~
- • {bufnr} (number|nil) defaults to the current buffer
- • {row} (number|nil) row to inspect, 0-based. Defaults to the row of
- the current cursor
- • {col} (number|nil) col to inspect, 0-based. Defaults to the col of
- the current cursor
+ • {bufnr} (integer|nil) defaults to the current buffer
+ • {row} (integer|nil) row to inspect, 0-based. Defaults to the row
+ of the current cursor
+ • {col} (integer|nil) col to inspect, 0-based. Defaults to the col
+ of the current cursor
• {filter} (table|nil) a table with key-value pairs to filter the items
• syntax (boolean): include syntax based highlight groups
(defaults to true)
@@ -1552,23 +1879,23 @@ inspect_pos({bufnr}, {row}, {col}, {filter}) *vim.inspect_pos()*
• row: the row used to get the items
• col: the col used to get the items
-show_pos({bufnr}, {row}, {col}, {filter}) *vim.show_pos()*
+vim.show_pos({bufnr}, {row}, {col}, {filter}) *vim.show_pos()*
Show all the items at a given buffer position.
Can also be shown with `:Inspect`. *:Inspect*
Parameters: ~
- • {bufnr} (number|nil) defaults to the current buffer
- • {row} (number|nil) row to inspect, 0-based. Defaults to the row of
- the current cursor
- • {col} (number|nil) col to inspect, 0-based. Defaults to the col of
- the current cursor
+ • {bufnr} (integer|nil) defaults to the current buffer
+ • {row} (integer|nil) row to inspect, 0-based. Defaults to the row
+ of the current cursor
+ • {col} (integer|nil) col to inspect, 0-based. Defaults to the col
+ of the current cursor
• {filter} (table|nil) see |vim.inspect_pos()|
-deep_equal({a}, {b}) *vim.deep_equal()*
+vim.deep_equal({a}, {b}) *vim.deep_equal()*
Deep compare values for equality
Tables are compared recursively unless they both provide the `eq` metamethod. All other types are compared using the equality `==` operator.
@@ -1580,7 +1907,7 @@ deep_equal({a}, {b}) *vim.deep_equal()*
Return: ~
(boolean) `true` if values are equals, else `false`
-deepcopy({orig}) *vim.deepcopy()*
+vim.deepcopy({orig}) *vim.deepcopy()*
Returns a deep copy of the given object. Non-table objects are copied as
in a typical Lua assignment, whereas table objects are copied recursively.
Functions are naively copied, so functions in the copied table point to
@@ -1593,30 +1920,24 @@ deepcopy({orig}) *vim.deepcopy()*
Return: ~
(table) Table of copied keys and (nested) values.
-defaulttable({create}) *vim.defaulttable()*
- Creates a table whose members are automatically created when accessed, if
- they don't already exist.
-
- They mimic defaultdict in python.
+vim.defaulttable({createfn}) *vim.defaulttable()*
+ Creates a table whose missing keys are provided by {createfn} (like
+ Python's "defaultdict").
- If {create} is `nil`, this will create a defaulttable whose constructor
- function is this function, effectively allowing to create nested tables on
- the fly:
-
- >lua
-
- local a = vim.defaulttable()
- a.b.c = 1
+ If {createfn} is `nil` it defaults to defaulttable() itself, so accessing
+ nested keys creates nested tables: >lua
+ local a = vim.defaulttable()
+ a.b.c = 1
<
Parameters: ~
- • {create} (function|nil) The function called to create a missing
- value.
+ • {createfn} function?(key:any):any Provides the value for a missing
+ `key`.
Return: ~
- (table) Empty table with metamethod
+ (table) Empty table with `__index` metamethod.
-endswith({s}, {suffix}) *vim.endswith()*
+vim.endswith({s}, {suffix}) *vim.endswith()*
Tests if `s` ends with `suffix`.
Parameters: ~
@@ -1626,25 +1947,42 @@ endswith({s}, {suffix}) *vim.endswith()*
Return: ~
(boolean) `true` if `suffix` is a suffix of `s`
-gsplit({s}, {sep}, {plain}) *vim.gsplit()*
- Splits a string at each instance of a separator.
+vim.gsplit({s}, {sep}, {opts}) *vim.gsplit()*
+ Gets an |iterator| that splits a string at each instance of a separator,
+ in "lazy" fashion (as opposed to |vim.split()| which is "eager").
+
+ Example: >lua
+ for s in vim.gsplit(':aa::b:', ':', {plain=true}) do
+ print(s)
+ end
+<
+
+ If you want to also inspect the separator itself (instead of discarding
+ it), use |string.gmatch()|. Example: >lua
+ for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do
+ print(('word: %s num: %s'):format(word, num))
+ end
+<
Parameters: ~
- • {s} (string) String to split
- • {sep} (string) Separator or pattern
- • {plain} (boolean|nil) If `true` use `sep` literally (passed to
- string.find)
+ • {s} (string) String to split
+ • {sep} (string) Separator or pattern
+ • {opts} (table|nil) Keyword arguments |kwargs|:
+ • plain: (boolean) Use `sep` literally (as in string.find).
+ • trimempty: (boolean) Discard empty segments at start and end
+ of the sequence.
Return: ~
(function) Iterator over the split components
See also: ~
- |vim.split()|
- |luaref-patterns|
- https://www.lua.org/pil/20.2.html
- http://lua-users.org/wiki/StringLibraryTutorial
+ • |string.gmatch()|
+ • |vim.split()|
+ • |lua-patterns|
+ • https://www.lua.org/pil/20.2.html
+ • http://lua-users.org/wiki/StringLibraryTutorial
-is_callable({f}) *vim.is_callable()*
+vim.is_callable({f}) *vim.is_callable()*
Returns true if object `f` can be called as a function.
Parameters: ~
@@ -1653,7 +1991,20 @@ is_callable({f}) *vim.is_callable()*
Return: ~
(boolean) `true` if `f` is callable, else `false`
-list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
+vim.list_contains({t}, {value}) *vim.list_contains()*
+ Checks if a list-like table (integer keys without gaps) contains `value`.
+
+ Parameters: ~
+ • {t} (table) Table to check (must be list-like, not validated)
+ • {value} any Value to compare
+
+ Return: ~
+ (boolean) `true` if `t` contains `value`
+
+ See also: ~
+ • |vim.tbl_contains()| for checking values in general tables
+
+vim.list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
Extends a list-like table with the values of another list-like table.
NOTE: This mutates dst!
@@ -1661,28 +2012,28 @@ list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()*
Parameters: ~
• {dst} (table) List which will be modified and appended to
• {src} (table) List from which values will be inserted
- • {start} (number|nil) Start index on src. Defaults to 1
- • {finish} (number|nil) Final index on src. Defaults to `#src`
+ • {start} (integer|nil) Start index on src. Defaults to 1
+ • {finish} (integer|nil) Final index on src. Defaults to `#src`
Return: ~
(table) dst
See also: ~
- |vim.tbl_extend()|
+ • |vim.tbl_extend()|
-list_slice({list}, {start}, {finish}) *vim.list_slice()*
+vim.list_slice({list}, {start}, {finish}) *vim.list_slice()*
Creates a copy of a table containing only elements from start to end
(inclusive)
Parameters: ~
• {list} (list) Table
- • {start} (number|nil) Start range of slice
- • {finish} (number|nil) End range of slice
+ • {start} (integer|nil) Start range of slice
+ • {finish} (integer|nil) End range of slice
Return: ~
(list) Copy of table sliced from start to finish (inclusive)
-pesc({s}) *vim.pesc()*
+vim.pesc({s}) *vim.pesc()*
Escapes magic chars in |lua-patterns|.
Parameters: ~
@@ -1692,47 +2043,97 @@ pesc({s}) *vim.pesc()*
(string) %-escaped pattern string
See also: ~
- https://github.com/rxi/lume
+ • https://github.com/rxi/lume
+
+vim.ringbuf({size}) *vim.ringbuf()*
+ Create a ring buffer limited to a maximal number of items. Once the buffer
+ is full, adding a new entry overrides the oldest entry. >lua
+ local ringbuf = vim.ringbuf(4)
+ ringbuf:push("a")
+ ringbuf:push("b")
+ ringbuf:push("c")
+ ringbuf:push("d")
+ ringbuf:push("e") -- overrides "a"
+ print(ringbuf:pop()) -- returns "b"
+ print(ringbuf:pop()) -- returns "c"
+
+ -- Can be used as iterator. Pops remaining items:
+ for val in ringbuf do
+ print(val)
+ end
+<
+
+ Returns a Ringbuf instance with the following methods:
-spairs({t}) *vim.spairs()*
- Enumerate a table sorted by its keys.
+ • |Ringbuf:push()|
+ • |Ringbuf:pop()|
+ • |Ringbuf:peek()|
+ • |Ringbuf:clear()|
Parameters: ~
- • {t} (table) List-like table
+ • {size} (integer)
+
+ Return: ~
+ (table)
+
+vim.Ringbuf:clear() *Ringbuf:clear()*
+ Clear all items.
+
+vim.Ringbuf:peek() *Ringbuf:peek()*
+ Returns the first unread item without removing it
+
+ Return: ~
+ any?|nil
+
+vim.Ringbuf:pop() *Ringbuf:pop()*
+ Removes and returns the first unread item
+
+ Return: ~
+ any?|nil
+
+vim.Ringbuf:push({item}) *Ringbuf:push()*
+ Adds an item, overriding the oldest item if the buffer is full.
+
+ Parameters: ~
+ • {item} any
+
+vim.spairs({t}) *vim.spairs()*
+ Enumerates key-value pairs of a table, ordered by key.
+
+ Parameters: ~
+ • {t} (table) Dict-like table
Return: ~
- iterator over sorted keys and their values
+ (function) |for-in| iterator over sorted keys and their values
See also: ~
- Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua
+ • Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua
-split({s}, {sep}, {kwargs}) *vim.split()*
- Splits a string at each instance of a separator.
+vim.split({s}, {sep}, {opts}) *vim.split()*
+ Splits a string at each instance of a separator and returns the result as
+ a table (unlike |vim.gsplit()|).
Examples: >lua
-
- split(":aa::b:", ":") --> {'','aa','','b',''}
- split("axaby", "ab?") --> {'','x','y'}
- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'}
- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
+ split(":aa::b:", ":") --> {'','aa','','b',''}
+ split("axaby", "ab?") --> {'','x','y'}
+ split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'}
+ split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
<
Parameters: ~
- • {s} (string) String to split
- • {sep} (string) Separator or pattern
- • {kwargs} (table|nil) Keyword arguments:
- • plain: (boolean) If `true` use `sep` literally (passed to
- string.find)
- • trimempty: (boolean) If `true` remove empty items from the
- front and back of the list
+ • {s} (string) String to split
+ • {sep} (string) Separator or pattern
+ • {opts} (table|nil) Keyword arguments |kwargs| accepted by
+ |vim.gsplit()|
Return: ~
string[] List of split components
See also: ~
- |vim.gsplit()|
+ • |vim.gsplit()|
+ • |string.gmatch()|
-startswith({s}, {prefix}) *vim.startswith()*
+vim.startswith({s}, {prefix}) *vim.startswith()*
Tests if `s` starts with `prefix`.
Parameters: ~
@@ -1742,7 +2143,7 @@ startswith({s}, {prefix}) *vim.startswith()*
Return: ~
(boolean) `true` if `prefix` is a prefix of `s`
-tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()*
+vim.tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()*
Add the reverse lookup values to an existing table. For example:
`tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`
@@ -1754,36 +2155,47 @@ tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()*
Return: ~
(table) o
-tbl_contains({t}, {value}) *vim.tbl_contains()*
- Checks if a list-like (vector) table contains `value`.
+vim.tbl_contains({t}, {value}, {opts}) *vim.tbl_contains()*
+ Checks if a table contains a given value, specified either directly or via
+ a predicate that is checked for each value.
+
+ Example: >lua
+ vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
+ return vim.deep_equal(v, { 'b', 'c' })
+ end, { predicate = true })
+ -- true
+<
Parameters: ~
• {t} (table) Table to check
- • {value} any Value to compare
+ • {value} any Value to compare or predicate function reference
+ • {opts} (table|nil) Keyword arguments |kwargs|:
+ • predicate: (boolean) `value` is a function reference to be
+ checked (default false)
Return: ~
(boolean) `true` if `t` contains `value`
-tbl_count({t}) *vim.tbl_count()*
- Counts the number of non-nil values in table `t`.
-
- >lua
+ See also: ~
+ • |vim.list_contains()| for checking values in list-like tables
- vim.tbl_count({ a=1, b=2 }) --> 2
- vim.tbl_count({ 1, 2 }) --> 2
+vim.tbl_count({t}) *vim.tbl_count()*
+ Counts the number of non-nil values in table `t`. >lua
+ vim.tbl_count({ a=1, b=2 }) --> 2
+ vim.tbl_count({ 1, 2 }) --> 2
<
Parameters: ~
• {t} (table) Table
Return: ~
- (number) Number of non-nil values in table
+ (integer) Number of non-nil values in table
See also: ~
- https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
+ • https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
-tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()*
- Merges recursively two or more map-like tables.
+vim.tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()*
+ Merges recursively two or more tables.
Parameters: ~
• {behavior} (string) Decides what to do if a key is found in more than
@@ -1791,16 +2203,16 @@ tbl_deep_extend({behavior}, {...}) *vim.tbl_deep_extend()*
• "error": raise an error
• "keep": use value from the leftmost map
• "force": use value from the rightmost map
- • {...} (table) Two or more map-like tables
+ • {...} (table) Two or more tables
Return: ~
(table) Merged table
See also: ~
- |vim.tbl_extend()|
+ • |vim.tbl_extend()|
-tbl_extend({behavior}, {...}) *vim.tbl_extend()*
- Merges two or more map-like tables.
+vim.tbl_extend({behavior}, {...}) *vim.tbl_extend()*
+ Merges two or more tables.
Parameters: ~
• {behavior} (string) Decides what to do if a key is found in more than
@@ -1808,15 +2220,15 @@ tbl_extend({behavior}, {...}) *vim.tbl_extend()*
• "error": raise an error
• "keep": use value from the leftmost map
• "force": use value from the rightmost map
- • {...} (table) Two or more map-like tables
+ • {...} (table) Two or more tables
Return: ~
(table) Merged table
See also: ~
- |extend()|
+ • |extend()|
-tbl_filter({func}, {t}) *vim.tbl_filter()*
+vim.tbl_filter({func}, {t}) *vim.tbl_filter()*
Filter a table using a predicate function
Parameters: ~
@@ -1826,7 +2238,7 @@ tbl_filter({func}, {t}) *vim.tbl_filter()*
Return: ~
(table) Table of filtered values
-tbl_flatten({t}) *vim.tbl_flatten()*
+vim.tbl_flatten({t}) *vim.tbl_flatten()*
Creates a copy of a list-like table such that any nested tables are
"unrolled" and appended to the result.
@@ -1837,27 +2249,45 @@ tbl_flatten({t}) *vim.tbl_flatten()*
(table) Flattened copy of the given list-like table
See also: ~
- From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+ • From https://github.com/premake/premake-core/blob/master/src/base/table.lua
-tbl_get({o}, {...}) *vim.tbl_get()*
+vim.tbl_get({o}, {...}) *vim.tbl_get()*
Index into a table (first argument) via string keys passed as subsequent
arguments. Return `nil` if the key does not exist.
Examples: >lua
-
- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true
- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil
+ vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true
+ vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil
<
Parameters: ~
• {o} (table) Table to index
- • {...} (string) Optional strings (0 or more, variadic) via which to
- index the table
+ • {...} any Optional keys (0 or more, variadic) via which to index the
+ table
Return: ~
any Nested value indexed by key (if it exists), else nil
-tbl_isempty({t}) *vim.tbl_isempty()*
+vim.tbl_isarray({t}) *vim.tbl_isarray()*
+ Tests if `t` is an "array": a table indexed only by integers (potentially non-contiguous).
+
+ If the indexes start from 1 and are contiguous then the array is also a
+ list. |vim.tbl_islist()|
+
+ Empty table `{}` is an array, unless it was created by |vim.empty_dict()|
+ or returned as a dict-like |API| or Vimscript result, for example from
+ |rpcrequest()| or |vim.fn|.
+
+ Parameters: ~
+ • {t} (table)
+
+ Return: ~
+ (boolean) `true` if array-like table, else `false`.
+
+ See also: ~
+ • https://github.com/openresty/luajit2#tableisarray
+
+vim.tbl_isempty({t}) *vim.tbl_isempty()*
Checks if a table is empty.
Parameters: ~
@@ -1867,22 +2297,26 @@ tbl_isempty({t}) *vim.tbl_isempty()*
(boolean) `true` if `t` is empty
See also: ~
- https://github.com/premake/premake-core/blob/master/src/base/table.lua
+ • https://github.com/premake/premake-core/blob/master/src/base/table.lua
-tbl_islist({t}) *vim.tbl_islist()*
- Tests if a Lua table can be treated as an array.
+vim.tbl_islist({t}) *vim.tbl_islist()*
+ Tests if `t` is a "list": a table indexed only by contiguous integers starting from 1 (what |lua-length| calls a "regular
+ array").
- Empty table `{}` is assumed to be an array, unless it was created by
- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result,
- for example from |rpcrequest()| or |vim.fn|.
+ Empty table `{}` is a list, unless it was created by |vim.empty_dict()| or
+ returned as a dict-like |API| or Vimscript result, for example from
+ |rpcrequest()| or |vim.fn|.
Parameters: ~
- • {t} (table) Table
+ • {t} (table)
Return: ~
- (boolean) `true` if array-like table, else `false`
+ (boolean) `true` if list-like table, else `false`.
+
+ See also: ~
+ • |vim.tbl_isarray()|
-tbl_keys({t}) *vim.tbl_keys()*
+vim.tbl_keys({t}) *vim.tbl_keys()*
Return a list of all keys used in a table. However, the order of the
return table of keys is not guaranteed.
@@ -1893,9 +2327,9 @@ tbl_keys({t}) *vim.tbl_keys()*
(list) List of keys
See also: ~
- From https://github.com/premake/premake-core/blob/master/src/base/table.lua
+ • From https://github.com/premake/premake-core/blob/master/src/base/table.lua
-tbl_map({func}, {t}) *vim.tbl_map()*
+vim.tbl_map({func}, {t}) *vim.tbl_map()*
Apply a function to all values of a table.
Parameters: ~
@@ -1905,7 +2339,7 @@ tbl_map({func}, {t}) *vim.tbl_map()*
Return: ~
(table) Table of transformed values
-tbl_values({t}) *vim.tbl_values()*
+vim.tbl_values({t}) *vim.tbl_values()*
Return a list of all values used in a table. However, the order of the
return table of values is not guaranteed.
@@ -1915,7 +2349,7 @@ tbl_values({t}) *vim.tbl_values()*
Return: ~
(list) List of values
-trim({s}) *vim.trim()*
+vim.trim({s}) *vim.trim()*
Trim whitespace (Lua pattern "%s") from both sides of a string.
Parameters: ~
@@ -1925,43 +2359,40 @@ trim({s}) *vim.trim()*
(string) String with whitespace removed from its beginning and end
See also: ~
- |luaref-patterns|
- https://www.lua.org/pil/20.2.html
+ • |lua-patterns|
+ • https://www.lua.org/pil/20.2.html
-validate({opt}) *vim.validate()*
+vim.validate({opt}) *vim.validate()*
Validates a parameter specification (types and values).
Usage example: >lua
-
- function user.new(name, age, hobbies)
- vim.validate{
- name={name, 'string'},
- age={age, 'number'},
- hobbies={hobbies, 'table'},
- }
- ...
- end
+ function user.new(name, age, hobbies)
+ vim.validate{
+ name={name, 'string'},
+ age={age, 'number'},
+ hobbies={hobbies, 'table'},
+ }
+ ...
+ end
<
Examples with explicit argument values (can be run directly): >lua
+ vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
+ --> NOP (success)
- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
- --> NOP (success)
-
- vim.validate{arg1={1, 'table'}}
- --> error('arg1: expected table, got number')
+ vim.validate{arg1={1, 'table'}}
+ --> error('arg1: expected table, got number')
- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
- --> error('arg1: expected even number, got 3')
+ vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
+ --> error('arg1: expected even number, got 3')
<
If multiple types are valid they can be given as a list. >lua
+ vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
+ -- NOP (success)
- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
- --> NOP (success)
-
- vim.validate{arg1={1, {'string', table'}}}
- --> error('arg1: expected string|table, got number')
+ vim.validate{arg1={1, {'string', 'table'}}}
+ -- error('arg1: expected string|table, got number')
<
Parameters: ~
@@ -1984,19 +2415,85 @@ validate({opt}) *vim.validate()*
==============================================================================
-Lua module: uri *lua-uri*
+Lua module: vim.loader *vim.loader*
+
+vim.loader.disable() *vim.loader.disable()*
+ Disables the experimental Lua module loader:
+ • removes the loaders
+ • adds the default Nvim loader
+
+vim.loader.enable() *vim.loader.enable()*
+ Enables the experimental Lua module loader:
+ • overrides loadfile
+ • adds the Lua loader using the byte-compilation cache
+ • adds the libs loader
+ • removes the default Nvim loader
+
+vim.loader.find({modname}, {opts}) *vim.loader.find()*
+ Finds Lua modules for the given module name.
+
+ Parameters: ~
+ • {modname} (string) Module name, or `"*"` to find the top-level
+ modules instead
+ • {opts} (table|nil) Options for finding a module:
+ • rtp: (boolean) Search for modname in the runtime path
+ (defaults to `true`)
+ • paths: (string[]) Extra paths to search for modname
+ (defaults to `{}`)
+ • patterns: (string[]) List of patterns to use when
+ searching for modules. A pattern is a string added to the
+ basename of the Lua module being searched. (defaults to
+ `{"/init.lua", ".lua"}`)
+ • all: (boolean) Return all matches instead of just the
+ first one (defaults to `false`)
+
+ Return: ~
+ (list) A list of results with the following properties:
+ • modpath: (string) the path to the module
+ • modname: (string) the name of the module
+ • stat: (table|nil) the fs_stat of the module path. Won't be returned
+ for `modname="*"`
+
+vim.loader.reset({path}) *vim.loader.reset()*
+ Resets the cache for the path, or all the paths if path is nil.
+
+ Parameters: ~
+ • {path} string? path to reset
+
+
+==============================================================================
+Lua module: vim.uri *vim.uri*
+
+vim.uri_decode({str}) *vim.uri_decode()*
+ URI-decodes a string containing percent escapes.
+
+ Parameters: ~
+ • {str} (string) string to decode
+
+ Return: ~
+ (string) decoded string
+
+vim.uri_encode({str}, {rfc}) *vim.uri_encode()*
+ URI-encodes a string using percent escapes.
+
+ Parameters: ~
+ • {str} (string) string to encode
+ • {rfc} "rfc2396" | "rfc2732" | "rfc3986" | nil
+
+ Return: ~
+ (string) encoded string
-uri_from_bufnr({bufnr}) *vim.uri_from_bufnr()*
- Get a URI from a bufnr
+vim.uri_from_bufnr({bufnr}) *vim.uri_from_bufnr()*
+ Gets a URI from a bufnr.
Parameters: ~
- • {bufnr} (number)
+ • {bufnr} (integer)
Return: ~
(string) URI
-uri_from_fname({path}) *vim.uri_from_fname()*
- Get a URI from a file path.
+vim.uri_from_fname({path}) *vim.uri_from_fname()*
+ Gets a URI from a file path.
Parameters: ~
• {path} (string) Path to file
@@ -2004,18 +2501,18 @@ uri_from_fname({path}) *vim.uri_from_fname()*
Return: ~
(string) URI
-uri_to_bufnr({uri}) *vim.uri_to_bufnr()*
- Get the buffer for a uri. Creates a new unloaded buffer if no buffer for
+vim.uri_to_bufnr({uri}) *vim.uri_to_bufnr()*
+ Gets the buffer for a uri. Creates a new unloaded buffer if no buffer for
the uri already exists.
Parameters: ~
• {uri} (string)
Return: ~
- (number) bufnr
+ (integer) bufnr
-uri_to_fname({uri}) *vim.uri_to_fname()*
- Get a filename from a URI
+vim.uri_to_fname({uri}) *vim.uri_to_fname()*
+ Gets a filename from a URI.
Parameters: ~
• {uri} (string)
@@ -2025,16 +2522,16 @@ uri_to_fname({uri}) *vim.uri_to_fname()*
==============================================================================
-Lua module: ui *lua-ui*
+Lua module: vim.ui *vim.ui*
-input({opts}, {on_confirm}) *vim.ui.input()*
- Prompts the user for input
+vim.ui.input({opts}, {on_confirm}) *vim.ui.input()*
+ Prompts the user for input, allowing arbitrary (potentially asynchronous)
+ work until `on_confirm`.
Example: >lua
-
- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
- vim.o.shiftwidth = tonumber(input)
- end)
+ vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
+ vim.o.shiftwidth = tonumber(input)
+ end)
<
Parameters: ~
@@ -2052,23 +2549,46 @@ input({opts}, {on_confirm}) *vim.ui.input()*
typed (it might be an empty string if nothing was
entered), or `nil` if the user aborted the dialog.
-select({items}, {opts}, {on_choice}) *vim.ui.select()*
- Prompts the user to pick a single item from a collection of entries
+vim.ui.open({path}) *vim.ui.open()*
+ Opens `path` with the system default handler (macOS `open`, Windows
+ `explorer.exe`, Linux `xdg-open`, …), or returns (but does not show) an
+ error message on failure.
- Example: >lua
+ Expands "~/" and environment variables in filesystem paths.
- vim.ui.select({ 'tabs', 'spaces' }, {
- prompt = 'Select tabs or spaces:',
- format_item = function(item)
- return "I'd like to choose " .. item
- end,
- }, function(choice)
- if choice == 'spaces' then
- vim.o.expandtab = true
- else
- vim.o.expandtab = false
- end
- end)
+ Examples: >lua
+ vim.ui.open("https://neovim.io/")
+ vim.ui.open("~/path/to/file")
+ vim.ui.open("$VIMRUNTIME")
+<
+
+ Parameters: ~
+ • {path} (string) Path or URL to open
+
+ Return (multiple): ~
+ vim.SystemCompleted|nil Command result, or nil if not found.
+ (string|nil) Error message on failure
+
+ See also: ~
+ • |vim.system()|
+
+vim.ui.select({items}, {opts}, {on_choice}) *vim.ui.select()*
+ Prompts the user to pick from a list of items, allowing arbitrary
+ (potentially asynchronous) work until `on_choice`.
+
+ Example: >lua
+ vim.ui.select({ 'tabs', 'spaces' }, {
+ prompt = 'Select tabs or spaces:',
+ format_item = function(item)
+ return "I'd like to choose " .. item
+ end,
+ }, function(choice)
+ if choice == 'spaces' then
+ vim.o.expandtab = true
+ else
+ vim.o.expandtab = false
+ end
+ end)
<
Parameters: ~
@@ -2089,9 +2609,9 @@ select({items}, {opts}, {on_choice}) *vim.ui.select()*
==============================================================================
-Lua module: filetype *lua-filetype*
+Lua module: vim.filetype *vim.filetype*
-add({filetypes}) *vim.filetype.add()*
+vim.filetype.add({filetypes}) *vim.filetype.add()*
Add new filetype mappings.
Filetype mappings can be added either by extension or by filename (either
@@ -2107,7 +2627,8 @@ add({filetypes}) *vim.filetype.add()*
matched pattern, if any) and should return a string that will be used as
the buffer's filetype. Optionally, the function can return a second
function value which, when called, modifies the state of the buffer. This
- can be used to, for example, set filetype-specific buffer variables.
+ can be used to, for example, set filetype-specific buffer variables. This
+ function will be called by Nvim before setting the buffer's filetype.
Filename patterns can specify an optional priority to resolve cases when a
file path matches multiple patterns. Higher priorities are matched first.
@@ -2119,65 +2640,86 @@ add({filetypes}) *vim.filetype.add()*
See $VIMRUNTIME/lua/vim/filetype.lua for more examples.
Example: >lua
-
- vim.filetype.add({
- extension = {
- foo = 'fooscript',
- bar = function(path, bufnr)
- if some_condition() then
- return 'barscript', function(bufnr)
- -- Set a buffer variable
- vim.b[bufnr].barscript_version = 2
+ vim.filetype.add({
+ extension = {
+ foo = 'fooscript',
+ bar = function(path, bufnr)
+ if some_condition() then
+ return 'barscript', function(bufnr)
+ -- Set a buffer variable
+ vim.b[bufnr].barscript_version = 2
+ end
end
- end
- return 'bar'
- end,
- },
- filename = {
- ['.foorc'] = 'toml',
- ['/etc/foo/config'] = 'toml',
- },
- pattern = {
- ['.*/etc/foo/.*'] = 'fooscript',
- -- Using an optional priority
- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } },
- -- A pattern containing an environment variable
- ['${XDG_CONFIG_HOME}/foo/git'] = 'git',
- ['README.(a+)$'] = function(path, bufnr, ext)
- if ext == 'md' then
- return 'markdown'
- elseif ext == 'rst' then
- return 'rst'
- end
- end,
- },
- })
+ return 'bar'
+ end,
+ },
+ filename = {
+ ['.foorc'] = 'toml',
+ ['/etc/foo/config'] = 'toml',
+ },
+ pattern = {
+ ['.*&zwj;/etc/foo/.*'] = 'fooscript',
+ -- Using an optional priority
+ ['.*&zwj;/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } },
+ -- A pattern containing an environment variable
+ ['${XDG_CONFIG_HOME}/foo/git'] = 'git',
+ ['README.(%a+)$'] = function(path, bufnr, ext)
+ if ext == 'md' then
+ return 'markdown'
+ elseif ext == 'rst' then
+ return 'rst'
+ end
+ end,
+ },
+ })
<
To add a fallback match on contents, use >lua
-
- vim.filetype.add {
- pattern = {
- ['.*'] = {
- priority = -math.huge,
- function(path, bufnr)
- local content = vim.filetype.getlines(bufnr, 1)
- if vim.filetype.matchregex(content, [[^#!.*\<mine\>]]) then
- return 'mine'
- elseif vim.filetype.matchregex(content, [[\<drawing\>]]) then
- return 'drawing'
- end
- end,
- },
- },
- }
+ vim.filetype.add {
+ pattern = {
+ ['.*'] = {
+ priority = -math.huge,
+ function(path, bufnr)
+ local content = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1] or ''
+ if vim.regex([[^#!.*\\<mine\\>]]):match_str(content) ~= nil then
+ return 'mine'
+ elseif vim.regex([[\\<drawing\\>]]):match_str(content) ~= nil then
+ return 'drawing'
+ end
+ end,
+ },
+ },
+ }
<
Parameters: ~
• {filetypes} (table) A table containing new filetype maps (see
example).
-match({args}) *vim.filetype.match()*
+ *vim.filetype.get_option()*
+vim.filetype.get_option({filetype}, {option})
+ Get the default option value for a {filetype}.
+
+ The returned value is what would be set in a new buffer after 'filetype'
+ is set, meaning it should respect all FileType autocmds and ftplugin
+ files.
+
+ Example: >lua
+ vim.filetype.get_option('vim', 'commentstring')
+<
+
+ Note: this uses |nvim_get_option_value()| but caches the result. This
+ means |ftplugin| and |FileType| autocommands are only triggered once and
+ may not reflect later changes.
+
+ Parameters: ~
+ • {filetype} (string) Filetype
+ • {option} (string) Option name
+
+ Return: ~
+ string|boolean|integer: Option value
+
+vim.filetype.match({args}) *vim.filetype.match()*
Perform filetype detection.
The filetype can be detected using one of three methods:
@@ -2192,21 +2734,18 @@ match({args}) *vim.filetype.match()*
the filetype.
Each of the three options is specified using a key to the single argument
- of this function. Example:
-
- >lua
-
- -- Using a buffer number
- vim.filetype.match({ buf = 42 })
+ of this function. Example: >lua
+ -- Using a buffer number
+ vim.filetype.match({ buf = 42 })
- -- Override the filename of the given buffer
- vim.filetype.match({ buf = 42, filename = 'foo.c' })
+ -- Override the filename of the given buffer
+ vim.filetype.match({ buf = 42, filename = 'foo.c' })
- -- Using a filename without a buffer
- vim.filetype.match({ filename = 'main.lua' })
+ -- Using a filename without a buffer
+ vim.filetype.match({ filename = 'main.lua' })
- -- Using file contents
- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
+ -- Using file contents
+ vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
<
Parameters: ~
@@ -2225,7 +2764,7 @@ match({args}) *vim.filetype.match()*
contents to use for matching. Can be used with {filename}.
Mutually exclusive with {buf}.
- Return: ~
+ Return (multiple): ~
(string|nil) If a match was found, the matched filetype.
(function|nil) A function that modifies buffer state when called (for
example, to set some filetype specific buffer variables). The function
@@ -2233,138 +2772,145 @@ match({args}) *vim.filetype.match()*
==============================================================================
-Lua module: keymap *lua-keymap*
+Lua module: vim.keymap *vim.keymap*
-del({modes}, {lhs}, {opts}) *vim.keymap.del()*
+vim.keymap.del({modes}, {lhs}, {opts}) *vim.keymap.del()*
Remove an existing mapping. Examples: >lua
+ vim.keymap.del('n', 'lhs')
- vim.keymap.del('n', 'lhs')
-
- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
+ vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
<
Parameters: ~
• {opts} (table|nil) A table of optional arguments:
- • buffer: (number or boolean) Remove a mapping from the given
- buffer. When "true" or 0, use the current buffer.
+ • "buffer": (integer|boolean) Remove a mapping from the given
+ buffer. When `0` or `true`, use the current buffer.
See also: ~
- |vim.keymap.set()|
-
-set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
- Add a new |mapping|. Examples: >lua
-
- -- Can add mapping to Lua functions
- vim.keymap.set('n', 'lhs', function() print("real lua function") end)
-
- -- Can use it to map multiple modes
- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true })
-
- -- Can add mapping for specific buffer
- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
-
- -- Expr mappings
- vim.keymap.set('i', '<Tab>', function()
- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
- end, { expr = true })
- -- <Plug> mappings
- vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)')
-<
-
- Note that in a mapping like: >lua
-
- vim.keymap.set('n', 'asdf', require('jkl').my_fun)
-<
-
- the `require('jkl')` gets evaluated during this call in order to access the function. If you
- want to avoid this cost at startup you can wrap it in a function, for
- example: >lua
-
- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
+ • |vim.keymap.set()|
+
+vim.keymap.set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()*
+ Adds a new |mapping|. Examples: >lua
+ -- Map to a Lua function:
+ vim.keymap.set('n', 'lhs', function() print("real lua function") end)
+ -- Map to multiple modes:
+ vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true })
+ -- Buffer-local mapping:
+ vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
+ -- Expr mapping:
+ vim.keymap.set('i', '<Tab>', function()
+ return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
+ end, { expr = true })
+ -- <Plug> mapping:
+ vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)')
<
Parameters: ~
- • {mode} string|table Same mode short names as |nvim_set_keymap()|. Can
+ • {mode} string|table Mode short-name, see |nvim_set_keymap()|. Can
also be list of modes to create mapping on multiple modes.
• {lhs} (string) Left-hand side |{lhs}| of the mapping.
- • {rhs} string|function Right-hand side |{rhs}| of the mapping. Can
- also be a Lua function.
- • {opts} (table|nil) A table of |:map-arguments|.
- • Accepts options accepted by the {opts} parameter in
- |nvim_set_keymap()|, with the following notable differences:
- • replace_keycodes: Defaults to `true` if "expr" is `true`.
- • noremap: Always overridden with the inverse of "remap"
- (see below).
-
- • In addition to those options, the table accepts the
- following keys:
- • buffer: (number or boolean) Add a mapping to the given
- buffer. When `0` or `true`, use the current buffer.
- • remap: (boolean) Make the mapping recursive. This is the
- inverse of the "noremap" option from |nvim_set_keymap()|.
- Defaults to `false`.
+ • {rhs} string|function Right-hand side |{rhs}| of the mapping, can be
+ a Lua function.
+ • {opts} (table|nil) Table of |:map-arguments|.
+ • Same as |nvim_set_keymap()| {opts}, except:
+ • "replace_keycodes" defaults to `true` if "expr" is `true`.
+ • "noremap": inverse of "remap" (see below).
+
+ • Also accepts:
+ • "buffer": (integer|boolean) Creates buffer-local mapping,
+ `0` or `true` for current buffer.
+ • "remap": (boolean) Make the mapping recursive. Inverse of
+ "noremap". Defaults to `false`.
See also: ~
- |nvim_set_keymap()|
+ • |nvim_set_keymap()|
+ • |maparg()|
+ • |mapcheck()|
+ • |mapset()|
==============================================================================
-Lua module: fs *lua-fs*
+Lua module: vim.fs *vim.fs*
-basename({file}) *vim.fs.basename()*
- Return the basename of the given file or directory
+vim.fs.basename({file}) *vim.fs.basename()*
+ Return the basename of the given path
Parameters: ~
- • {file} (string) File or directory
+ • {file} (string) Path
Return: ~
- (string) Basename of {file}
+ (string|nil) Basename of {file}
-dir({path}, {opts}) *vim.fs.dir()*
- Return an iterator over the files and directories located in {path}
+vim.fs.dir({path}, {opts}) *vim.fs.dir()*
+ Return an iterator over the items located in {path}
Parameters: ~
• {path} (string) An absolute or relative path to the directory to
iterate over. The path is first normalized
|vim.fs.normalize()|.
- • {opts} table|nil Optional keyword arguments:
+ • {opts} (table|nil) Optional keyword arguments:
• depth: integer|nil How deep the traverse (default 1)
• skip: (fun(dir_name: string): boolean)|nil Predicate to
control traversal. Return false to stop searching the
current directory. Only useful when depth > 1
Return: ~
- Iterator over files and directories in {path}. Each iteration yields
- two values: name and type. Each "name" is the basename of the file or
- directory relative to {path}. Type is one of "file" or "directory".
+ Iterator over items in {path}. Each iteration yields two values:
+ "name" and "type". "name" is the basename of the item relative to
+ {path}. "type" is one of the following: "file", "directory", "link",
+ "fifo", "socket", "char", "block", "unknown".
-dirname({file}) *vim.fs.dirname()*
- Return the parent directory of the given file or directory
+vim.fs.dirname({file}) *vim.fs.dirname()*
+ Return the parent directory of the given path
Parameters: ~
- • {file} (string) File or directory
+ • {file} (string) Path
Return: ~
- (string) Parent directory of {file}
+ (string|nil) Parent directory of {file}
-find({names}, {opts}) *vim.fs.find()*
- Find files or directories in the given path.
+vim.fs.find({names}, {opts}) *vim.fs.find()*
+ Find files or directories (or other items as specified by `opts.type`) in
+ the given path.
- Finds any files or directories given in {names} starting from {path}. If
- {upward} is "true" then the search traverses upward through parent
- directories; otherwise, the search traverses downward. Note that downward
- searches are recursive and may search through many directories! If {stop}
- is non-nil, then the search stops when the directory given in {stop} is
- reached. The search terminates when {limit} (default 1) matches are found.
- The search can be narrowed to find only files or only directories by
- specifying {type} to be "file" or "directory", respectively.
+ Finds items given in {names} starting from {path}. If {upward} is "true"
+ then the search traverses upward through parent directories; otherwise,
+ the search traverses downward. Note that downward searches are recursive
+ and may search through many directories! If {stop} is non-nil, then the
+ search stops when the directory given in {stop} is reached. The search
+ terminates when {limit} (default 1) matches are found. You can set {type}
+ to "file", "directory", "link", "socket", "char", "block", or "fifo" to
+ narrow the search to find only that type.
+
+ Examples: >lua
+ -- location of Cargo.toml from the current buffer's path
+ local cargo = vim.fs.find('Cargo.toml', {
+ upward = true,
+ stop = vim.uv.os_homedir(),
+ path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)),
+ })
+
+ -- list all test directories under the runtime directory
+ local test_dirs = vim.fs.find(
+ {'test', 'tst', 'testdir'},
+ {limit = math.huge, type = 'directory', path = './runtime/'}
+ )
+
+ -- get all files ending with .cpp or .hpp inside lib/
+ local cpp_hpp = vim.fs.find(function(name, path)
+ return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$')
+ end, {limit = math.huge, type = 'file'})
+<
Parameters: ~
- • {names} (string|table|fun(name: string): boolean) Names of the files
- and directories to find. Must be base names, paths and globs
- are not supported. The function is called per file and
- directory within the traversed directories to test if they
- match {names}.
+ • {names} (string|string[]|fun(name: string, path: string): boolean)
+ Names of the items to find. Must be base names, paths and
+ globs are not supported when {names} is a string or a table.
+ If {names} is a function, it is called for each traversed
+ item with args:
+ • name: base name of the current item
+ • path: full path of the current item The function should
+ return `true` if the given item is considered a match.
• {opts} (table) Optional keyword arguments:
• path (string): Path to begin searching from. If omitted,
the |current-directory| is used.
@@ -2373,70 +2919,81 @@ find({names}, {opts}) *vim.fs.find()*
directories (recursively).
• stop (string): Stop searching when this directory is
reached. The directory itself is not searched.
- • type (string): Find only files ("file") or directories
- ("directory"). If omitted, both files and directories that
- match {names} are included.
+ • type (string): Find only items of the given type. If
+ omitted, all items that match {names} are included.
• limit (number, default 1): Stop the search after finding
this many matches. Use `math.huge` to place no limit on the
number of matches.
Return: ~
- (table) Normalized paths |vim.fs.normalize()| of all matching files or
- directories
+ (string[]) Normalized paths |vim.fs.normalize()| of all matching items
+
+vim.fs.joinpath({...}) *vim.fs.joinpath()*
+ Concatenate directories and/or file paths into a single path with
+ normalization (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+
+ Parameters: ~
+ • {...} (string)
+
+ Return: ~
+ (string)
-normalize({path}) *vim.fs.normalize()*
+vim.fs.normalize({path}, {opts}) *vim.fs.normalize()*
Normalize a path to a standard format. A tilde (~) character at the
beginning of the path is expanded to the user's home directory and any
backslash (\) characters are converted to forward slashes (/). Environment
variables are also expanded.
Examples: >lua
+ vim.fs.normalize('C:\\\\Users\\\\jdoe')
+ -- 'C:/Users/jdoe'
- vim.fs.normalize('C:\\Users\\jdoe')
- --> 'C:/Users/jdoe'
-
- vim.fs.normalize('~/src/neovim')
- --> '/home/jdoe/src/neovim'
+ vim.fs.normalize('~/src/neovim')
+ -- '/home/jdoe/src/neovim'
- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
- --> '/Users/jdoe/.config/nvim/init.vim'
+ vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+ -- '/Users/jdoe/.config/nvim/init.vim'
<
Parameters: ~
• {path} (string) Path to normalize
+ • {opts} (table|nil) Options:
+ • expand_env: boolean Expand environment variables (default:
+ true)
Return: ~
(string) Normalized path
-parents({start}) *vim.fs.parents()*
- Iterate over all the parents of the given file or directory.
+vim.fs.parents({start}) *vim.fs.parents()*
+ Iterate over all the parents of the given path.
Example: >lua
+ local root_dir
+ for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
+ if vim.fn.isdirectory(dir .. "/.git") == 1 then
+ root_dir = dir
+ break
+ end
+ end
- local root_dir
- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
- if vim.fn.isdirectory(dir .. "/.git") == 1 then
- root_dir = dir
- break
- end
- end
-
- if root_dir then
- print("Found git repository at", root_dir)
- end
+ if root_dir then
+ print("Found git repository at", root_dir)
+ end
<
Parameters: ~
- • {start} (string) Initial file or directory.
+ • {start} (string) Initial path.
- Return: ~
- (function) Iterator
+ Return (multiple): ~
+ fun(_, dir: string): string? Iterator
+ nil
+ (string|nil)
==============================================================================
-Lua module: secure *lua-secure*
+Lua module: vim.secure *vim.secure*
-read({path}) *vim.secure.read()*
+vim.secure.read({path}) *vim.secure.read()*
Attempt to read the file at {path} prompting the user if the file should
be trusted. The user's choice is persisted in a trust database at
$XDG_STATE_HOME/nvim/trust.
@@ -2449,9 +3006,9 @@ read({path}) *vim.secure.read()*
trusted, or nil otherwise.
See also: ~
- |:trust|
+ • |:trust|
-trust({opts}) *vim.secure.trust()*
+vim.secure.trust({opts}) *vim.secure.trust()*
Manage the trust database.
The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
@@ -2467,9 +3024,743 @@ trust({opts}) *vim.secure.trust()*
• bufnr (number|nil): Buffer number to update. Mutually
exclusive with {path}.
+ Return (multiple): ~
+ (boolean) success true if operation was successful
+ (string) msg full path if operation was successful, else error message
+
+
+==============================================================================
+Lua module: vim.version *vim.version*
+
+
+The `vim.version` module provides functions for comparing versions and
+ranges conforming to the
+
+https://semver.org
+
+spec. Plugins, and plugin managers, can use this to check available tools
+and dependencies on the current system.
+
+Example: >lua
+ local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false})
+ if vim.version.gt(v, {3, 2, 0}) then
+ -- ...
+ end
+
+<
+
+*vim.version()* returns the version of the current Nvim process.
+
+VERSION RANGE SPEC *version-range*
+
+A version "range spec" defines a semantic version range which can be
+tested against a version, using |vim.version.range()|.
+
+Supported range specs are shown in the following table. Note: suffixed
+versions (1.2.3-rc1) are not matched. >
+ 1.2.3 is 1.2.3
+ =1.2.3 is 1.2.3
+ >1.2.3 greater than 1.2.3
+ <1.2.3 before 1.2.3
+ >=1.2.3 at least 1.2.3
+ ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3"
+ ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3"
+ ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special)
+ ^0.0.1 is =0.0.1 (0.0.x is special)
+ ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0)
+ ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0)
+ ^1 is >=1.0.0 <2.0.0 "compatible with 1"
+ ~1 same "reasonably close to 1"
+ 1.x same
+ 1.* same
+ 1 same
+ * any version
+ x same
+
+ 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4
+
+ Partial right: missing pieces treated as x (2.3 => 2.3.x).
+ 1.2.3 - 2.3 is >=1.2.3 <2.4.0
+ 1.2.3 - 2 is >=1.2.3 <3.0.0
+
+ Partial left: missing pieces treated as 0 (1.2 => 1.2.0).
+ 1.2 - 2.3.0 is 1.2.0 - 2.3.0
+
+<
+
+vim.version.cmp({v1}, {v2}) *vim.version.cmp()*
+ Parses and compares two version objects (the result of
+ |vim.version.parse()|, or specified literally as a `{major, minor, patch}`
+ tuple, e.g. `{1, 0, 3}`).
+
+ Example: >lua
+ if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then
+ -- ...
+ end
+ local v1 = vim.version.parse('1.0.3-pre')
+ local v2 = vim.version.parse('0.2.1')
+ if vim.version.cmp(v1, v2) == 0 then
+ -- ...
+ end
+<
+
+ Note: ~
+ • Per semver, build metadata is ignored when comparing two
+ otherwise-equivalent versions.
+
+ Parameters: ~
+ • {v1} Version|number[] Version object.
+ • {v2} Version|number[] Version to compare with `v1` .
+
+ Return: ~
+ (integer) -1 if `v1 < v2`, 0 if `v1 == v2`, 1 if `v1 > v2`.
+
+vim.version.eq({v1}, {v2}) *vim.version.eq()*
+ Returns `true` if the given versions are equal. See |vim.version.cmp()| for usage.
+
+ Parameters: ~
+ • {v1} Version|number[]
+ • {v2} Version|number[]
+
+ Return: ~
+ (boolean)
+
+vim.version.gt({v1}, {v2}) *vim.version.gt()*
+ Returns `true` if `v1 > v2` . See |vim.version.cmp()| for usage.
+
+ Parameters: ~
+ • {v1} Version|number[]
+ • {v2} Version|number[]
+
+ Return: ~
+ (boolean)
+
+vim.version.last({versions}) *vim.version.last()*
+ TODO: generalize this, move to func.lua
+
+ Parameters: ~
+ • {versions} Version []
+
+ Return: ~
+ Version ?|nil
+
+vim.version.lt({v1}, {v2}) *vim.version.lt()*
+ Returns `true` if `v1 < v2` . See |vim.version.cmp()| for usage.
+
+ Parameters: ~
+ • {v1} Version|number[]
+ • {v2} Version|number[]
+
+ Return: ~
+ (boolean)
+
+vim.version.parse({version}, {opts}) *vim.version.parse()*
+ Parses a semantic version string and returns a version object which can be
+ used with other `vim.version` functions. For example "1.0.1-rc1+build.2"
+ returns: >
+ { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" }
+<
+
+ Parameters: ~
+ • {version} (string) Version string to parse.
+ • {opts} (table|nil) Optional keyword arguments:
+ • strict (boolean): Default false. If `true`, no coercion
+ is attempted on input not conforming to semver v2.0.0. If
+ `false`, `parse()` attempts to coerce input such as
+ "1.0", "0-x", "tmux 3.2a" into valid versions.
+
+ Return: ~
+ (table|nil) parsed_version Version object or `nil` if input is invalid.
+
+ See also: ~
+ • # https://semver.org/spec/v2.0.0.html
+
+vim.version.range({spec}) *vim.version.range()*
+ Parses a semver |version-range| "spec" and returns a range object: >
+ {
+ from: Version
+ to: Version
+ has(v: string|Version)
+ }
+<
+
+ `:has()` checks if a version is in the range (inclusive `from`, exclusive
+ `to`).
+
+ Example: >lua
+ local r = vim.version.range('1.0.0 - 2.0.0')
+ print(r:has('1.9.9')) -- true
+ print(r:has('2.0.0')) -- false
+ print(r:has(vim.version())) -- check against current Nvim version
+<
+
+ Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly: >lua
+ local r = vim.version.range('1.0.0 - 2.0.0')
+ print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to))
+<
+
+ Parameters: ~
+ • {spec} (string) Version range "spec"
+
+ See also: ~
+ • # https://github.com/npm/node-semver#ranges
+
+
+==============================================================================
+Lua module: vim.iter *vim.iter*
+
+
+*vim.iter()* is an interface for |iterable|s: it wraps a table or function
+argument into an *Iter* object with methods (such as |Iter:filter()| and
+|Iter:map()|) that transform the underlying source data. These methods can
+be chained to create iterator "pipelines": the output of each pipeline
+stage is input to the next stage. The first stage depends on the type
+passed to `vim.iter()`:
+
+• List tables (arrays, |lua-list|) yield only the value of each element.
+ • Use |Iter:enumerate()| to also pass the index to the next stage.
+ • Or initialize with ipairs(): `vim.iter(ipairs(…))`.
+
+• Non-list tables (|lua-dict|) yield both the key and value of each
+ element.
+• Function |iterator|s yield all values returned by the underlying
+ function.
+• Tables with a |__call()| metamethod are treated as function iterators.
+
+The iterator pipeline terminates when the underlying |iterable| is
+exhausted (for function iterators this means it returned nil).
+
+Note: `vim.iter()` scans table input to decide if it is a list or a dict;
+to avoid this cost you can wrap the table with an iterator e.g.
+`vim.iter(ipairs({…}))`, but that precludes the use of |list-iterator|
+operations such as |Iter:rev()|).
+
+Examples: >lua
+ local it = vim.iter({ 1, 2, 3, 4, 5 })
+ it:map(function(v)
+ return v * 3
+ end)
+ it:rev()
+ it:skip(2)
+ it:totable()
+ -- { 9, 6, 3 }
+
+ -- ipairs() is a function iterator which returns both the index (i) and the value (v)
+ vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+ if i > 2 then return v end
+ end):totable()
+ -- { 3, 4, 5 }
+
+ local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+ it:map(function(s) return tonumber(s) end)
+ for i, d in it:enumerate() do
+ print(string.format("Column %d is %d", i, d))
+ end
+ -- Column 1 is 1
+ -- Column 2 is 2
+ -- Column 3 is 3
+ -- Column 4 is 4
+ -- Column 5 is 5
+
+ vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+ return k == 'z'
+ end)
+ -- true
+
+ local rb = vim.ringbuf(3)
+ rb:push("a")
+ rb:push("b")
+ vim.iter(rb):totable()
+ -- { "a", "b" }
+
+<
+
+In addition to the |vim.iter()| function, the |vim.iter| module provides
+convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
+
+filter({f}, {src}, {...}) *vim.iter.filter()*
+ Filters a table or other |iterable|. >lua
+ -- Equivalent to:
+ vim.iter(src):filter(f):totable()
+<
+
+ Parameters: ~
+ • {f} function(...):bool Filter function. Accepts the current
+ iterator or table values as arguments and returns true if those
+ values should be kept in the final table
+ • {src} table|function Table or iterator function to filter
+
+ Return: ~
+ (table)
+
+ See also: ~
+ • |Iter:filter()|
+
+Iter:all({pred}) *Iter:all()*
+ Returns true if all items in the iterator match the given predicate.
+
+ Parameters: ~
+ • {pred} function(...):bool Predicate function. Takes all values
+ returned from the previous stage in the pipeline as arguments
+ and returns true if the predicate matches.
+
+Iter:any({pred}) *Iter:any()*
+ Returns true if any of the items in the iterator match the given
+ predicate.
+
+ Parameters: ~
+ • {pred} function(...):bool Predicate function. Takes all values
+ returned from the previous stage in the pipeline as arguments
+ and returns true if the predicate matches.
+
+Iter:each({f}) *Iter:each()*
+ Calls a function once for each item in the pipeline, draining the
+ iterator.
+
+ For functions with side effects. To modify the values in the iterator, use
+ |Iter:map()|.
+
+ Parameters: ~
+ • {f} function(...) Function to execute for each item in the pipeline.
+ Takes all of the values returned by the previous stage in the
+ pipeline as arguments.
+
+Iter:enumerate() *Iter:enumerate()*
+ Yields the item index (count) and value for each item of an iterator
+ pipeline.
+
+ For list tables, this is more efficient: >lua
+ vim.iter(ipairs(t))
+<
+
+ instead of: >lua
+ vim.iter(t):enumerate()
+<
+
+ Example: >lua
+ local it = vim.iter(vim.gsplit('abc', '')):enumerate()
+ it:next()
+ -- 1 'a'
+ it:next()
+ -- 2 'b'
+ it:next()
+ -- 3 'c'
+<
+
+ Return: ~
+ Iter
+
+Iter:filter({f}) *Iter:filter()*
+ Filters an iterator pipeline.
+
+ Example: >lua
+ local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
+<
+
+ Parameters: ~
+ • {f} function(...):bool Takes all values returned from the previous
+ stage in the pipeline and returns false or nil if the current
+ iterator element should be removed.
+
+ Return: ~
+ Iter
+
+Iter:find({f}) *Iter:find()*
+ Find the first value in the iterator that satisfies the given predicate.
+
+ Advances the iterator. Returns nil and drains the iterator if no value is
+ found.
+
+ Examples: >lua
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:find(12)
+ -- 12
+
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:find(20)
+ -- nil
+
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:find(function(v) return v % 4 == 0 end)
+ -- 12
+<
+
+ Return: ~
+ any
+
+Iter:fold({init}, {f}) *Iter:fold()*
+ Folds ("reduces") an iterator into a single value.
+
+ Examples: >lua
+ -- Create a new table with only even values
+ local t = { a = 1, b = 2, c = 3, d = 4 }
+ local it = vim.iter(t)
+ it:filter(function(k, v) return v % 2 == 0 end)
+ it:fold({}, function(t, k, v)
+ t[k] = v
+ return t
+ end)
+ -- { b = 2, d = 4 }
+<
+
+ Parameters: ~
+ • {init} any Initial value of the accumulator.
+ • {f} function(acc:any, ...):A Accumulation function.
+
+ Return: ~
+ any
+
+Iter:last() *Iter:last()*
+ Drains the iterator and returns the last item.
+
+ Example: >lua
+ local it = vim.iter(vim.gsplit('abcdefg', ''))
+ it:last()
+ -- 'g'
+
+ local it = vim.iter({ 3, 6, 9, 12, 15 })
+ it:last()
+ -- 15
+<
+
+ Return: ~
+ any
+
+Iter:map({f}) *Iter:map()*
+ Maps the items of an iterator pipeline to the values returned by `f`.
+
+ If the map function returns nil, the value is filtered from the iterator.
+
+ Example: >lua
+ local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
+ if v % 2 == 0 then
+ return v * 3
+ end
+ end)
+ it:totable()
+ -- { 6, 12 }
+<
+
+ Parameters: ~
+ • {f} function(...):any Mapping function. Takes all values returned
+ from the previous stage in the pipeline as arguments and returns
+ one or more new values, which are used in the next pipeline
+ stage. Nil return values are filtered from the output.
+
+ Return: ~
+ Iter
+
+Iter:next() *Iter:next()*
+ Gets the next value from the iterator.
+
+ Example: >lua
+ local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
+ it:next()
+ -- 1
+ it:next()
+ -- 2
+ it:next()
+ -- 3
+<
+
+ Return: ~
+ any
+
+Iter:nextback() *Iter:nextback()*
+ "Pops" a value from a |list-iterator| (gets the last value and decrements
+ the tail).
+
+ Example: >lua
+ local it = vim.iter({1, 2, 3, 4})
+ it:nextback()
+ -- 4
+ it:nextback()
+ -- 3
+<
+
+ Return: ~
+ any
+
+Iter:nth({n}) *Iter:nth()*
+ Gets the nth value of an iterator (and advances to it).
+
+ Example: >lua
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:nth(2)
+ -- 6
+ it:nth(2)
+ -- 12
+<
+
+ Parameters: ~
+ • {n} (number) The index of the value to return.
+
+ Return: ~
+ any
+
+Iter:nthback({n}) *Iter:nthback()*
+ Gets the nth value from the end of a |list-iterator| (and advances to it).
+
+ Example: >lua
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:nthback(2)
+ -- 9
+ it:nthback(2)
+ -- 3
+<
+
+ Parameters: ~
+ • {n} (number) The index of the value to return.
+
+ Return: ~
+ any
+
+Iter:peek() *Iter:peek()*
+ Gets the next value in a |list-iterator| without consuming it.
+
+ Example: >lua
+ local it = vim.iter({ 3, 6, 9, 12 })
+ it:peek()
+ -- 3
+ it:peek()
+ -- 3
+ it:next()
+ -- 3
+<
+
+ Return: ~
+ any
+
+Iter:peekback() *Iter:peekback()*
+ Gets the last value of a |list-iterator| without consuming it.
+
+ See also |Iter:last()|.
+
+ Example: >lua
+ local it = vim.iter({1, 2, 3, 4})
+ it:peekback()
+ -- 4
+ it:peekback()
+ -- 4
+ it:nextback()
+ -- 4
+<
+
+ Return: ~
+ any
+
+Iter:rev() *Iter:rev()*
+ Reverses a |list-iterator| pipeline.
+
+ Example: >lua
+ local it = vim.iter({ 3, 6, 9, 12 }):rev()
+ it:totable()
+ -- { 12, 9, 6, 3 }
+<
+
+ Return: ~
+ Iter
+
+Iter:rfind({f}) *Iter:rfind()*
+ Gets the first value in a |list-iterator| that satisfies a predicate,
+ starting from the end.
+
+ Advances the iterator. Returns nil and drains the iterator if no value is
+ found.
+
+ Examples: >lua
+ local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
+ it:rfind(1)
+ -- 5 1
+ it:rfind(1)
+ -- 1 1
+<
+
+ Return: ~
+ any
+
+ See also: ~
+ • Iter.find
+
+Iter:skip({n}) *Iter:skip()*
+ Skips `n` values of an iterator pipeline.
+
+ Example: >lua
+ local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
+ it:next()
+ -- 9
+<
+
+ Parameters: ~
+ • {n} (number) Number of values to skip.
+
+ Return: ~
+ Iter
+
+Iter:skipback({n}) *Iter:skipback()*
+ Skips `n` values backwards from the end of a |list-iterator| pipeline.
+
+ Example: >lua
+ local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2)
+ it:next()
+ -- 1
+ it:nextback()
+ -- 3
+<
+
+ Parameters: ~
+ • {n} (number) Number of values to skip.
+
+ Return: ~
+ Iter
+
+Iter:slice({first}, {last}) *Iter:slice()*
+ Sets the start and end of a |list-iterator| pipeline.
+
+ Equivalent to `:skip(first - 1):skipback(len - last + 1)`.
+
+ Parameters: ~
+ • {first} (number)
+ • {last} (number)
+
+ Return: ~
+ Iter
+
+Iter:totable() *Iter:totable()*
+ Collect the iterator into a table.
+
+ The resulting table depends on the initial source in the iterator
+ pipeline. List-like tables and function iterators will be collected into a
+ list-like table. If multiple values are returned from the final stage in
+ the iterator pipeline, each value will be included in a table.
+
+ Examples: >lua
+ vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
+ -- { 100, 20, 50 }
+
+ vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable()
+ -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
+
+ vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
+ -- { { 'a', 1 }, { 'c', 3 } }
+<
+
+ The generated table is a list-like table with consecutive, numeric
+ indices. To create a map-like table with arbitrary keys, use
+ |Iter:fold()|.
+
+ Return: ~
+ (table)
+
+map({f}, {src}, {...}) *vim.iter.map()*
+ Maps a table or other |iterable|. >lua
+ -- Equivalent to:
+ vim.iter(src):map(f):totable()
+<
+
+ Parameters: ~
+ • {f} function(...):?any Map function. Accepts the current iterator
+ or table values as arguments and returns one or more new
+ values. Nil values are removed from the final table.
+ • {src} table|function Table or iterator function to filter
+
+ Return: ~
+ (table)
+
+ See also: ~
+ • |Iter:map()|
+
+totable({f}, {...}) *vim.iter.totable()*
+ Collects an |iterable| into a table. >lua
+ -- Equivalent to:
+ vim.iter(f):totable()
+<
+
+ Parameters: ~
+ • {f} (function) Iterator function
+
+ Return: ~
+ (table)
+
+
+==============================================================================
+Lua module: vim.snippet *vim.snippet*
+
+vim.snippet.active() *vim.snippet.active()*
+ Returns `true` if there's an active snippet in the current buffer.
+
+ Return: ~
+ (boolean)
+
+vim.snippet.exit() *vim.snippet.exit()*
+ Exits the current snippet.
+
+vim.snippet.expand({input}) *vim.snippet.expand()*
+ Expands the given snippet text. Refer to https://microsoft.github.io/language-server-protocol/specification/#snippet_syntax for the specification of valid input.
+
+ Tabstops are highlighted with hl-SnippetTabstop.
+
+ Parameters: ~
+ • {input} (string)
+
+vim.snippet.jump({direction}) *vim.snippet.jump()*
+ Jumps within the active snippet in the given direction. If the jump isn't
+ possible, the function call does nothing.
+
+ You can use this function to navigate a snippet as follows: >lua
+ vim.keymap.set({ 'i', 's' }, '<Tab>', function()
+ if vim.snippet.jumpable(1) then
+ return '<cmd>lua vim.snippet.jump(1)<cr>'
+ else
+ return '<Tab>'
+ end
+ end, { expr = true })
+<
+
+ Parameters: ~
+ • {direction} (vim.snippet.Direction) Navigation direction. -1 for
+ previous, 1 for next.
+
+vim.snippet.jumpable({direction}) *vim.snippet.jumpable()*
+ Returns `true` if there is an active snippet which can be jumped in the
+ given direction. You can use this function to navigate a snippet as
+ follows: >lua
+ vim.keymap.set({ 'i', 's' }, '<Tab>', function()
+ if vim.snippet.jumpable(1) then
+ return '<cmd>lua vim.snippet.jump(1)<cr>'
+ else
+ return '<Tab>'
+ end
+ end, { expr = true })
+<
+
+ Parameters: ~
+ • {direction} (vim.snippet.Direction) Navigation direction. -1 for
+ previous, 1 for next.
+
+ Return: ~
+ (boolean)
+
+
+==============================================================================
+Lua module: vim.text *vim.text*
+
+vim.text.hexdecode({enc}) *vim.text.hexdecode()*
+ Hex decode a string.
+
+ Parameters: ~
+ • {enc} (string) String to decode
+
+ Return: ~
+ (string) Decoded string
+
+vim.text.hexencode({str}) *vim.text.hexencode()*
+ Hex encode a string.
+
+ Parameters: ~
+ • {str} (string) String to encode
+
Return: ~
- (boolean, string) success, msg:
- • true and full path of target file if operation was successful
- • false and error message on failure
+ (string) Hex encoded string
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/luaref.txt b/runtime/doc/luaref.txt
index aafdd5c43e..467b5760cf 100644
--- a/runtime/doc/luaref.txt
+++ b/runtime/doc/luaref.txt
@@ -1,5 +1,5 @@
*luaref.txt* Nvim
- *luaref* *Lua-Reference*
+ *luaref*
LUA REFERENCE MANUAL
@@ -16,14 +16,14 @@
Copyright (c) 2006 Lua.org, PUC-Rio.
- See |luaref-doc| for information on this manual.
- See |luaref-copyright| for copyright and licenses.
+ See |lua-ref-doc| for information on this manual.
+ See |lua-ref-copyright| for copyright and licenses.
Type |gO| to see the table of contents.
==============================================================================
-1 INTRODUCTION *luaref-intro*
+1 INTRODUCTION *luaref-intro*
Lua is an extension programming language designed to support general
procedural programming with data description facilities. It also offers good
@@ -47,13 +47,13 @@ Lua's official web site, www.lua.org.
Like any other reference manual, this document is dry in places. For a
discussion of the decisions behind the design of Lua, see references at
-|luaref-bibliography|. For a detailed introduction to programming in Lua, see
+|lua-ref-bibliography|. For a detailed introduction to programming in Lua, see
Roberto's book, Programming in Lua.
Lua means "moon" in Portuguese and is pronounced LOO-ah.
==============================================================================
-2 THE LANGUAGE *luaref-language*
+2 THE LANGUAGE *lua-language*
This section describes the lexis, the syntax, and the semantics of Lua. In
other words, this section describes which tokens are valid, how they can be
@@ -63,9 +63,9 @@ The language constructs will be explained using the usual extended BNF
notation, in which `{ a }` means 0 or more `a`'s, and `[ a ]` means an optional `a`.
==============================================================================
-2.1 Lexical Conventions *luaref-langLexConv*
+2.1 Lexical Conventions *lua-lexical*
- *luaref-names* *luaref-identifiers*
+ *lua-names* *lua-identifiers*
Names (also called identifiers) in Lua can be any string of letters, digits,
and underscores, not beginning with a digit. This coincides with the
definition of identifiers in most languages. (The definition of letter depends
@@ -92,7 +92,7 @@ The following strings denote other tokens:
( ) { } [ ]
; : , . .. ...
<
- *luaref-literal*
+ *lua-literal*
Literal strings can be delimited by matching single or double quotes, and can
contain the following C-like escape sequences:
@@ -146,14 +146,14 @@ coded as 49), the five literals below denote the same string:
alo
123"]==]
<
- *luaref-numconstant*
+ *lua-numconstant*
A numerical constant may be written with an optional decimal part and an
optional decimal exponent. Lua also accepts integer hexadecimal constants, by
prefixing them with `0x`. Examples of valid numerical constants are
>
3 3.0 3.1416 314.16e-2 0.31416E1 0xff 0x56
<
- *luaref-comment*
+ *lua-comment*
A comment starts with a double hyphen (`--`) anywhere outside a string. If the
text immediately after `--` is not an opening long bracket, the comment is a
short comment, which runs until the end of the line. Otherwise, it is a long
@@ -161,7 +161,7 @@ comment, which runs until the corresponding closing long bracket. Long
comments are frequently used to disable code temporarily.
==============================================================================
-2.2 Values and Types *luaref-langValTypes*
+2.2 Values and Types *lua-values*
Lua is a dynamically typed language. This means that variables do not have
types; only values do. There are no type definitions in the language. All
@@ -171,9 +171,9 @@ All values in Lua are first-class values. This means that all values can be
stored in variables, passed as arguments to other functions, and returned as
results.
- *luaref-types* *luaref-nil*
- *luaref-true* *luaref-false*
- *luaref-number* *luaref-string*
+ *lua-types* *lua-nil*
+ *lua-true* *lua-false*
+ *lua-number* *lua-string*
There are eight basic types in Lua: `nil`, `boolean`, `number`, `string`,
`function`, `userdata`, `thread`, and `table`. Nil is the type of the value
`nil`, whose main property is to be different from any other value; it usually
@@ -184,27 +184,27 @@ numbers. (It is easy to build Lua interpreters that use other internal
representations for numbers, such as single-precision float or long integers;
see file `luaconf.h`.) String represents arrays of characters. Lua is 8-bit
clean: strings may contain any 8-bit character, including embedded zeros
-(`\0`) (see |luaref-literal|).
+(`\0`) (see |lua-literal|).
Lua can call (and manipulate) functions written in Lua and functions written
-in C (see |luaref-langFuncCalls|).
+in C (see |lua-function|).
- *luaref-userdatatype*
+ *lua-userdatatype*
The type userdata is provided to allow arbitrary C data to be stored in Lua
variables. This type corresponds to a block of raw memory and has no
pre-defined operations in Lua, except assignment and identity test. However,
by using metatables, the programmer can define operations for userdata values
-(see |luaref-langMetatables|). Userdata values cannot be created or modified
-in Lua, only through the C API. This guarantees the integrity of data owned by
-the host program.
+(see |lua-metatable|). Userdata values cannot be created or modified in Lua,
+only through the C API. This guarantees the integrity of data owned by the
+host program.
- *luaref-thread*
+ *lua-thread*
The type `thread` represents independent threads of execution and it is used to
-implement coroutines (see |luaref-langCoro|). Do not confuse Lua threads with
+implement coroutines (see |lua-coroutine|). Do not confuse Lua threads with
operating-system threads. Lua supports coroutines on all systems, even those
that do not support threads.
- *luaref-table*
+ *lua-table*
The type `table` implements associative arrays, that is, arrays that can be
indexed not only with numbers, but with any value (except `nil`). Tables can
be heterogeneous; that is, they can contain values of all types (except
@@ -213,11 +213,11 @@ used to represent ordinary arrays, symbol tables, sets, records, graphs,
trees, etc. To represent records, Lua uses the field name as an index. The
language supports this representation by providing `a.name` as syntactic sugar
for `a["name"]`. There are several convenient ways to create tables in Lua
-(see |luaref-langTableConst|).
+(see |lua-tableconstructor|).
Like indices, the value of a table field can be of any type (except `nil`). In
particular, because functions are first-class values, table fields may contain
-functions. Thus tables may also carry methods (see |luaref-langFuncDefs|).
+functions. Thus tables may also carry methods (see |lua-function-define|).
Tables, functions, threads and (full) userdata values are objects: variables
do not actually contain these values, only references to them. Assignment,
@@ -225,10 +225,10 @@ parameter passing, and function returns always manipulate references to such
values; these operations do not imply any kind of copy.
The library function `type` returns a string describing the type of a given
-value (see |luaref-type()|).
+value (see |lua-type()|).
------------------------------------------------------------------------------
-2.2.1 Coercion *luaref-langCoercion*
+2.2.1 Coercion *lua-coercion*
Lua provides automatic conversion between string and number values at run
time. Any arithmetic operation applied to a string tries to convert that
@@ -239,7 +239,7 @@ converted to strings, use the `format` function from the string library (see
|string.format()|).
==============================================================================
-2.3 Variables *luaref-langVariables*
+2.3 Variables *lua-variables*
Variables are places that store values. There are three kinds of variables in
Lua: global variables, local variables, and table fields.
@@ -249,12 +249,12 @@ function's formal parameter, which is a particular form of local variable):
>
var ::= Name
<
-Name denotes identifiers, as defined in |luaref-langLexConv|.
+Name denotes identifiers, as defined in |lua-lexical|.
Any variable is assumed to be global unless explicitly declared as a local
-(see |luaref-langLocalDec|). Local variables are lexically scoped: local
+(see |lua-local|). Local variables are lexically scoped: local
variables can be freely accessed by functions defined inside their scope (see
-|luaref-langVisibRules|).
+|lua-visibility|).
Before the first assignment to a variable, its value is `nil`.
@@ -265,21 +265,21 @@ Square brackets are used to index a table:
The first expression (`prefixexp`) should result in a table value; the second
expression (`exp`) identifies a specific entry inside that table. The
expression denoting the table to be indexed has a restricted syntax; see
-|luaref-langExpressions| for details.
+|lua-expressions| for details.
The syntax `var.NAME` is just syntactic sugar for `var["NAME"]` :
>
var ::= prefixexp . Name
<
All global variables live as fields in ordinary Lua tables, called environment
-tables or simply environments (see |luaref-langEnvironments|). Each function
+tables or simply environments (see |lua-environments|). Each function
has its own reference to an environment, so that all global variables in this
function will refer to this environment table. When a function is created, it
inherits the environment from the function that created it. To get the
environment table of a Lua function, you call `getfenv` (see
-|lua_getfenv()|). To replace it, you call `setfenv` (see |luaref-setfenv()|).
+|lua_getfenv()|). To replace it, you call `setfenv` (see |setfenv()|).
(You can only manipulate the environment of C functions through the debug
-library; see |luaref-libDebug|.)
+library; see |lua-lib-debug|.)
An access to a global variable `x` is equivalent to `_env.x`, which in turn is
equivalent to
@@ -291,19 +291,19 @@ not defined in Lua. We use it here only for explanatory purposes.)
The meaning of accesses to global variables and table fields can be changed
via metatables. An access to an indexed variable `t[i]` is equivalent to a
-call `gettable_event(t,i)`. (See |luaref-langMetatables| for a complete
-description of the `gettable_event` function. This function is not defined or
-callable in Lua. We use it here only for explanatory purposes.)
+call `gettable_event(t,i)`. (See |lua-metatable| for a complete description of
+the `gettable_event` function. This function is not defined or callable in
+Lua. We use it here only for explanatory purposes.)
==============================================================================
-2.4 Statements *luaref-langStats*
+2.4 Statements *lua-statement*
Lua supports an almost conventional set of statements, similar to those in
Pascal or C. This set includes assignment, control structures, function
calls, and variable declarations.
------------------------------------------------------------------------------
-2.4.1 Chunks *luaref-chunk* *luaref-langChunks*
+2.4.1 Chunks *lua-chunk*
The unit of execution of Lua is called a chunk. A chunk is simply a sequence
of statements, which are executed sequentially. Each statement can be
@@ -314,7 +314,7 @@ optionally followed by a semicolon:
There are no empty statements and thus `;;` is not legal.
Lua handles a chunk as the body of an anonymous function with a variable
-number of arguments (see |luaref-langFuncDefs|). As such, chunks can define
+number of arguments (see |lua-function-define|). As such, chunks can define
local variables, receive arguments, and return values.
A chunk may be stored in a file or in a string inside the host program. When a
@@ -327,24 +327,24 @@ details. Programs in source and compiled forms are interchangeable; Lua
automatically detects the file type and acts accordingly.
------------------------------------------------------------------------------
-2.4.2 Blocks *luaref-block* *luaref-langBlocks*
+2.4.2 Blocks *lua-block*
A block is a list of statements; syntactically, a block is the same as a
chunk:
>
block ::= chunk
<
- *luaref-do* *luaref-end*
+ *lua-do* *lua-end*
A block may be explicitly delimited to produce a single statement:
>
stat ::= do block end
<
Explicit blocks are useful to control the scope of variable declarations.
Explicit blocks are also sometimes used to add a `return` or `break` statement
-in the middle of another block (see |luaref-langContStructs|).
+in the middle of another block (see |lua-control|).
------------------------------------------------------------------------------
-2.4.3 Assignment *luaref-langAssign*
+2.4.3 Assignment *lua-assign*
Lua allows multiple assignment. Therefore, the syntax for assignment defines a
list of variables on the left side and a list of expressions on the right
@@ -354,7 +354,7 @@ side. The elements in both lists are separated by commas:
varlist1 ::= var { , var }
explist1 ::= exp { , exp }
<
-Expressions are discussed in |luaref-langExpressions|.
+Expressions are discussed in |lua-expressions|.
Before the assignment, the list of values is adjusted to the length of the
list of variables. If there are more values than needed, the excess values are
@@ -362,7 +362,7 @@ thrown away. If there are fewer values than needed, the list is extended with
as many `nil`s as needed. If the list of expressions ends with a function
call, then all values returned by this call enter in the list of values,
before the adjustment (except when the call is enclosed in parentheses; see
-|luaref-langExpressions|).
+|lua-expressions|).
The assignment statement first evaluates all its expressions and only then are
the assignments performed. Thus the code
@@ -379,9 +379,9 @@ exchanges the values of `x` and `y`.
The meaning of assignments to global variables and table fields can be changed
via metatables. An assignment to an indexed variable `t[i] = val` is
-equivalent to `settable_event(t,i,val)`. (See |luaref-langMetatables| for a
-complete description of the `settable_event` function. This function is not
-defined or callable in Lua. We use it here only for explanatory purposes.)
+equivalent to `settable_event(t,i,val)`. (See |lua-metatable| for a complete
+description of the `settable_event` function. This function is not defined or
+callable in Lua. We use it here only for explanatory purposes.)
An assignment to a global variable `x = val` is equivalent to the
assignment `_env.x = val`, which in turn is equivalent to
@@ -392,10 +392,10 @@ where `_env` is the environment of the running function. (The `_env` variable is
not defined in Lua. We use it here only for explanatory purposes.)
------------------------------------------------------------------------------
-2.4.4 Control Structures *luaref-langContStructs*
+2.4.4 Control Structures *lua-control*
- *luaref-if* *luaref-then* *luaref-else* *luaref-elseif*
- *luaref-while* *luaref-repeat* *luaref-until*
+ *lua-if* *lua-then* *lua-else* *lua-elseif*
+ *lua-while* *lua-repeat* *lua-until*
The control structures `if`, `while`, and `repeat` have the usual meaning and
familiar syntax:
>
@@ -404,7 +404,7 @@ familiar syntax:
stat ::= if exp then block { elseif exp then block }
[ else block ] end
<
-Lua also has a `for` statement, in two flavors (see |luaref-langForStat|).
+Lua also has a `for` statement, in two flavors (see |lua-for|).
The condition expression of a control structure may return any value.
Both `false` and `nil` are considered false. All values different
@@ -415,14 +415,14 @@ In the `repeat-until` loop, the inner block does not end at the `until` keyword,
but only after the condition. So, the condition can refer to local variables
declared inside the loop block.
- *luaref-return*
+ *lua-return*
The `return` statement is used to return values from a function or a chunk
(which is just a function). Functions and chunks may return more than one
value, so the syntax for the `return` statement is
`stat ::=` `return` `[explist1]`
- *luaref-break*
+ *lua-break*
The `break` statement is used to terminate the execution of a `while`, `repeat`,
or `for` loop, skipping to the next statement after the loop:
@@ -437,7 +437,7 @@ middle of a block, then an explicit inner block can be used, as in the idioms
the last statements in their (inner) blocks.
------------------------------------------------------------------------------
-2.4.5 For Statement *luaref-for* *luaref-langForStat*
+2.4.5 For Statement *for* *lua-for*
The `for` statement has two forms: one numeric and one generic.
@@ -477,8 +477,8 @@ Note the following:
after the `for` ends or is broken. If you need this value, assign it to
another variable before breaking or exiting the loop.
- *luaref-in*
-The generic `for` statement works over functions, called iterators. On each
+ *for-in*
+The generic `for` statement works over functions, called |iterator|s. On each
iteration, the iterator function is called to produce a new value, stopping
when this new value is `nil`. The generic `for` loop has the following syntax:
>
@@ -513,17 +513,17 @@ Note the following:
them to other variables before breaking or exiting the loop.
------------------------------------------------------------------------------
-2.4.6 Function Calls as Statements *luaref-langFuncStat*
+2.4.6 Function Calls as Statements *lua-funcstatement*
To allow possible side-effects, function calls can be executed as statements:
>
stat ::= functioncall
<
In this case, all returned values are thrown away. Function calls are
-explained in |luaref-langFuncCalls|.
+explained in |lua-function|.
------------------------------------------------------------------------------
-2.4.7 Local Declarations *luaref-local* *luaref-langLocalDec*
+2.4.7 Local Declarations *lua-local*
Local variables may be declared anywhere inside a block. The declaration may
include an initial assignment:
@@ -532,18 +532,17 @@ include an initial assignment:
namelist ::= Name { , Name }
<
If present, an initial assignment has the same semantics of a multiple
-assignment (see |luaref-langAssign|). Otherwise, all variables are initialized
+assignment (see |lua-assign|). Otherwise, all variables are initialized
with `nil`.
-A chunk is also a block (see |luaref-langChunks|), and so local variables can be
+A chunk is also a block (see |lua-chunk|), and so local variables can be
declared in a chunk outside any explicit block. The scope of such local
variables extends until the end of the chunk.
-The visibility rules for local variables are explained in
-|luaref-langVisibRules|.
+The visibility rules for local variables are explained in |lua-visibility|.
==============================================================================
-2.5 Expressions *luaref-langExpressions*
+2.5 Expressions *lua-expressions*
The basic expressions in Lua are the following:
>
@@ -558,22 +557,21 @@ The basic expressions in Lua are the following:
exp ::= unop exp
prefixexp ::= var | functioncall | ( exp )
<
-Numbers and literal strings are explained in |luaref-langLexConv|; variables are
-explained in |luaref-langVariables|; function definitions are explained in
-|luaref-langFuncDefs|; function calls are explained in |luaref-langFuncCalls|;
-table constructors are explained in |luaref-langTableConst|. Vararg expressions,
+Numbers and literal strings are explained in |lua-lexical|; variables are
+explained in |lua-variables|; function definitions are explained in
+|lua-function-define|; function calls are explained in |lua-function|;
+table constructors are explained in |lua-tableconstructor|. Vararg expressions,
denoted by three dots (`...`), can only be used inside vararg functions;
-they are explained in |luaref-langFuncDefs|.
+they are explained in |lua-function-define|.
-Binary operators comprise arithmetic operators (see |luaref-langArithOp|),
-relational operators (see |luaref-langRelOp|), logical operators (see
-|luaref-langLogOp|), and the concatenation operator (see |luaref-langConcat|).
-Unary operators comprise the unary minus (see |luaref-langArithOp|), the unary
-`not` (see |luaref-langLogOp|), and the unary length operator (see
-|luaref-langLength|).
+Binary operators comprise arithmetic operators (see |lua-arithmetic|),
+relational operators (see |lua-relational|), logical operators (see
+|lua-logicalop|), and the concatenation operator (see |lua-concat|).
+Unary operators comprise the unary minus (see |lua-arithmetic|), the unary
+`not` (see |lua-logicalop|), and the unary length operator (see |lua-length|).
Both function calls and vararg expressions may result in multiple values. If
-the expression is used as a statement (see |luaref-langFuncStat|)
+the expression is used as a statement (see |lua-funcstatement|)
(only possible for function calls), then its return list is adjusted to zero
elements, thus discarding all returned values. If the expression is used as
the last (or the only) element of a list of expressions, then no adjustment is
@@ -606,12 +604,12 @@ An expression enclosed in parentheses always results in only one value. Thus,
return any values.)
------------------------------------------------------------------------------
-2.5.1 Arithmetic Operators *luaref-langArithOp*
+2.5.1 Arithmetic Operators *lua-arithmetic*
Lua supports the usual arithmetic operators: the binary `+` (addition),
`-` (subtraction), `*` (multiplication), `/` (division), `%` (modulo)
and `^` (exponentiation); and unary `-` (negation). If the operands are numbers,
-or strings that can be converted to numbers (see |luaref-langCoercion|), then all
+or strings that can be converted to numbers (see |lua-coercion|), then all
operations have the usual meaning. Exponentiation works for any exponent. For
instance, `x^(-0.5)` computes the inverse of the square root of `x`. Modulo is
defined as
@@ -622,7 +620,7 @@ That is, it is the remainder of a division that rounds the quotient towards
minus infinity.
------------------------------------------------------------------------------
-2.5.2 Relational Operators *luaref-langRelOp*
+2.5.2 Relational Operators *lua-relational*
The relational operators in Lua are
>
@@ -639,9 +637,9 @@ create a new object (a table, userdata, or function), this new object is
different from any previously existing object.
You can change the way that Lua compares tables and userdata using the "eq"
-metamethod (see |luaref-langMetatables|).
+metamethod (see |lua-metatable|).
-The conversion rules of coercion |luaref-langCoercion| do not apply to
+The conversion rules of coercion |lua-coercion| do not apply to
equality comparisons. Thus, `"0"==0` evaluates to `false`, and `t[0]` and
`t["0"]` denote different entries in a table.
@@ -650,19 +648,19 @@ The operator `~=` is exactly the negation of equality (`==`).
The order operators work as follows. If both arguments are numbers, then they
are compared as such. Otherwise, if both arguments are strings, then their
values are compared according to the current locale. Otherwise, Lua tries to
-call the "lt" or the "le" metamethod (see |luaref-langMetatables|).
+call the "lt" or the "le" metamethod (see |lua-metatable|).
------------------------------------------------------------------------------
-2.5.3 Logical Operators *luaref-langLogOp*
+2.5.3 Logical Operators *lua-logicalop*
The logical operators in Lua are
>
and or not
<
-Like the control structures (see |luaref-langContStructs|), all logical operators
+Like the control structures (see |lua-control|), all logical operators
consider both `false` and `nil` as false and anything else as true.
- *luaref-not* *luaref-and* *luaref-or*
+ *lua-not* *lua-and* *lua-or*
The negation operator `not` always returns `false` or `true`. The conjunction
operator `and` returns its first argument if this value is `false` or `nil`;
otherwise, `and` returns its second argument. The disjunction
@@ -683,15 +681,15 @@ evaluated only if necessary. Here are some examples:
(In this manual, `-->` indicates the result of the preceding expression.)
------------------------------------------------------------------------------
-2.5.4 Concatenation *luaref-langConcat*
+2.5.4 Concatenation *lua-concat*
The string concatenation operator in Lua is denoted by two dots (`..`).
If both operands are strings or numbers, then they are converted to strings
-according to the rules mentioned in |luaref-langCoercion|. Otherwise, the
-"concat" metamethod is called (see |luaref-langMetatables|).
+according to the rules mentioned in |lua-coercion|. Otherwise, the
+"concat" metamethod is called (see |lua-metatable|).
------------------------------------------------------------------------------
-2.5.5 The Length Operator *luaref-langLength*
+2.5.5 The Length Operator *lua-#* *lua-length*
The length operator is denoted by the unary operator `#`. The length of a
string is its number of bytes (that is, the usual meaning of string length
@@ -706,7 +704,7 @@ indices that directly precedes a `nil` value (that is, it may consider any
such `nil` value as the end of the array).
------------------------------------------------------------------------------
-2.5.6 Precedence *luaref-langPrec*
+2.5.6 Precedence *lua-precedence*
Operator precedence in Lua follows the table below, from lower to higher
priority:
@@ -725,7 +723,7 @@ The concatenation (`..`) and exponentiation (`^`) operators are right
associative. All other binary operators are left associative.
------------------------------------------------------------------------------
-2.5.7 Table Constructors *luaref-langTableConst*
+2.5.7 Table Constructors *lua-tableconstructor*
Table constructors are expressions that create tables. Every time a
constructor is evaluated, a new table is created. Constructors can be used to
@@ -761,14 +759,14 @@ is equivalent to
<
If the last field in the list has the form `exp` and the expression is a
function call, then all values returned by the call enter the list
-consecutively (see |luaref-langFuncCalls|). To avoid this, enclose the function
-call in parentheses (see |luaref-langExpressions|).
+consecutively (see |lua-function|). To avoid this, enclose the function
+call in parentheses (see |lua-expressions|).
The field list may have an optional trailing separator, as a convenience for
machine-generated code.
------------------------------------------------------------------------------
-2.5.8 Function Calls *luaref-function* *luaref-langFuncCalls*
+2.5.8 Function Calls *lua-function*
A function call in Lua has the following syntax:
>
@@ -778,7 +776,7 @@ In a function call, first `prefixexp` and `args` are evaluated. If the value
of `prefixexp` has type `function`, then this function is called with the given
arguments. Otherwise, the `prefixexp` "call" metamethod is called, having as
first parameter the value of `prefixexp`, followed by the original call
-arguments (see |luaref-langMetatables|).
+arguments (see |lua-metatable|).
The form
>
@@ -810,7 +808,7 @@ Lua would see that as a single statement, `a = f(g).x(a)`. So, if you want two
statements, you must add a semi-colon between them. If you actually want to
call `f`, you must remove the line break before `(g)`.
- *luaref-tailcall*
+ *lua-tailcall*
A call of the form `return` `functioncall` is called a tail call. Lua
implements proper tail calls (or proper tail recursion): in a tail call, the
called function reuses the stack entry of the calling function. Therefore,
@@ -829,7 +827,7 @@ of the following examples are tail calls:
<
------------------------------------------------------------------------------
-2.5.9 Function Definitions *luaref-langFuncDefs*
+2.5.9 Function Definitions *lua-function-define*
The syntax for function definition is
>
@@ -873,7 +871,7 @@ not to
(This only makes a difference when the body of the function contains
references to `f`.)
- *luaref-closure*
+ *lua-closure*
A function definition is an executable expression, whose value has type
`function`. When Lua pre-compiles a chunk, all its function bodies are
pre-compiled too. Then, whenever Lua executes the function definition, the
@@ -887,7 +885,7 @@ values:
>
parlist1 ::= namelist [ , ... ] | ...
<
- *luaref-vararg*
+ *lua-vararg*
When a function is called, the list of arguments is adjusted to the length of
the list of parameters, unless the function is a variadic or vararg function,
which is indicated by three dots (`...`) at the end of its parameter list. A
@@ -922,11 +920,11 @@ vararg expression:
g(3, 4, 5, 8) a=3, b=4, ... --> 5 8
g(5, r()) a=5, b=1, ... --> 2 3
<
-Results are returned using the `return` statement (see |luaref-langContStructs|).
+Results are returned using the `return` statement (see |lua-control|).
If control reaches the end of a function without encountering
a `return` statement, then the function returns with no results.
- *luaref-colonsyntax*
+ *lua-colonsyntax*
The colon syntax is used for defining methods, that is, functions that have an
implicit extra parameter `self`. Thus, the statement
@@ -937,7 +935,7 @@ is syntactic sugar for
`t.a.b.c:f = function (self, (` `params` `)` `body` `end`
==============================================================================
-2.6 Visibility Rules *luaref-langVisibRules*
+2.6 Visibility Rules *lua-visibility*
Lua is a lexically scoped language. The scope of variables begins at the first
statement after their declaration and lasts until the end of the innermost
@@ -959,7 +957,7 @@ block that includes the declaration. Consider the following example:
Notice that, in a declaration like `local x = x`, the new `x` being declared is
not in scope yet, and so the second `x` refers to the outside variable.
- *luaref-upvalue*
+ *lua-upvalue*
Because of the lexical scoping rules, local variables can be freely accessed
by functions defined inside their scope. A local variable used by an inner
function is called an upvalue, or external local variable, inside the inner
@@ -980,7 +978,7 @@ function). Each of these closures uses a different `y` variable, while all of
them share the same `x`.
==============================================================================
-2.7 Error Handling *luaref-langError*
+2.7 Error Handling *lua-errors*
Because Lua is an embedded extension language, all Lua actions start from
C code in the host program calling a function from the Lua library (see
@@ -989,11 +987,11 @@ execution, control returns to C, which can take appropriate measures (such as
print an error message).
Lua code can explicitly generate an error by calling the `error` function (see
-|luaref-error()|). If you need to catch errors in Lua, you can use
-the `pcall` function (see |luaref-pcall()|).
+|error()|). If you need to catch errors in Lua, you can use the `pcall`
+function (see |pcall()|).
==============================================================================
-2.8 Metatables *luaref-metatable* *luaref-langMetatables*
+2.8 Metatables *metatable* *lua-metatable*
Every value in Lua may have a metatable. This metatable is an ordinary Lua
table that defines the behavior of the original table and userdata under
@@ -1008,10 +1006,10 @@ previous example, the event is "add" and the metamethod is the function that
performs the addition.
You can query the metatable of any value through the `getmetatable` function
-(see |luaref-getmetatable()|).
+(see |getmetatable()|).
You can replace the metatable of tables through the `setmetatable` function (see
-|luaref-setmetatable()|). You cannot change the metatable of other types from Lua
+|setmetatable()|). You cannot change the metatable of other types from Lua
(except using the debug library); you must use the C API for that.
Tables and userdata have individual metatables (although multiple tables and
@@ -1037,7 +1035,7 @@ by a Lua function describing how the interpreter executes that operation.
The code shown here in Lua is only illustrative; the real behavior is hard
coded in the interpreter and it is much more efficient than this simulation.
All functions used in these descriptions (`rawget`, `tonumber`, etc.) are
-described in |luaref-libBasic|. In particular, to retrieve the metamethod of a
+described in |lua-lib-core|. In particular, to retrieve the metamethod of a
given object, we use the expression
>
metatable(obj)[event]
@@ -1307,7 +1305,7 @@ called when Lua calls a value.
<
==============================================================================
-2.9 Environments *luaref-environment* *luaref-langEnvironments*
+2.9 Environments *lua-environments*
Besides metatables, objects of types thread, function, and userdata have
another table associated with them, called their environment. Like metatables,
@@ -1319,16 +1317,15 @@ convenience feature for programmers to associate a table to a userdata.
Environments associated with threads are called global environments. They are
used as the default environment for their threads and non-nested functions
-created by the thread (through `loadfile` |luaref-loadfile()|, `loadstring`
-|luaref-loadstring()| or `load` |luaref-load()|) and can be directly accessed by C
-code (see |luaref-apiPseudoIndices|).
+created by the thread (through |loadfile()|, |loadstring()| or |load()|) and
+can be directly accessed by C code (see |lua-pseudoindex|).
Environments associated with C functions can be directly accessed by C code
-(see |luaref-apiPseudoIndices|). They are used as the default environment for
+(see |lua-pseudoindex|). They are used as the default environment for
other C functions created by the function.
Environments associated with Lua functions are used to resolve all accesses to
-global variables within the function (see |luaref-langVariables|). They are
+global variables within the function (see |lua-variables|). They are
used as the default environment for other Lua functions created by the
function.
@@ -1339,7 +1336,7 @@ environment of other objects (userdata, C functions, other threads) you must
use the C API.
==============================================================================
-2.10 Garbage Collection *luaref-langGC*
+2.10 Garbage Collection *lua-gc*
Lua performs automatic memory management. This means that you do not have to
worry neither about allocating memory for new objects nor about freeing it
@@ -1367,16 +1364,16 @@ The default, 2, means that the collector runs at "twice" the speed of memory
allocation.
You can change these numbers by calling `lua_gc` (see |lua_gc()|) in C or
-`collectgarbage` (see |luaref-collectgarbage()|) in Lua. Both get percentage
-points as arguments (so an argument of 100 means a real value of 1). With
-these functions you can also control the collector directly (e.g., stop and
-restart it).
+`collectgarbage` (see |collectgarbage()|) in Lua. Both get percentage points
+as arguments (so an argument of 100 means a real value of 1). With these
+functions you can also control the collector directly (e.g., stop and restart
+it).
------------------------------------------------------------------------------
-2.10.1 Garbage-Collection Metamethods *luaref-langGCMeta*
+2.10.1 Garbage-Collection Metamethods *lua-gc-meta*
Using the C API, you can set garbage-collector metamethods for userdata (see
-|luaref-langMetatables|). These metamethods are also called finalizers.
+|lua-metatable|). These metamethods are also called finalizers.
Finalizers allow you to coordinate Lua's garbage collection with external
resource management (such as closing files, network or database connections,
or freeing your own memory).
@@ -1400,7 +1397,7 @@ cycle. That is, the first finalizer to be called is the one associated with
the userdata created last in the program.
------------------------------------------------------------------------------
-2.10.2 - Weak Tables *luaref-weaktable* *luaref-langWeaktables*
+2.10.2 - Weak Tables *lua-weaktable*
A weak table is a table whose elements are weak references. A weak reference
is ignored by the garbage collector. In other words, if the only references to
@@ -1422,7 +1419,7 @@ field `__mode`. Otherwise, the weak behavior of the tables controlled by this
metatable is undefined.
==============================================================================
-2.11 Coroutines *luaref-coroutine* *luaref-langCoro*
+2.11 Coroutines *lua-coroutine*
Lua supports coroutines, also called collaborative multithreading. A coroutine
in Lua represents an independent thread of execution. Unlike threads in
@@ -1502,7 +1499,7 @@ When you run it, it produces the following output:
<
==============================================================================
-3 THE APPLICATION PROGRAM INTERFACE *luaref-API*
+3 THE APPLICATION PROGRAM INTERFACE *lua-API*
This section describes the C API for Lua, that is, the set of C functions
available to the host program to communicate with Lua. All API functions and
@@ -1519,7 +1516,7 @@ Lua with a proper definition for the macro `luai_apicheck`,in file
`luaconf.h`.
==============================================================================
-3.1 The Stack *luaref-stack* *luaref-apiStack*
+3.1 The Stack *lua-stack* *lua-apiStack*
Lua uses a virtual stack to pass values to and from C. Each element in this
stack represents a Lua value (`nil`, number, string, etc.).
@@ -1528,9 +1525,9 @@ Whenever Lua calls C, the called function gets a new stack, which is
independent of previous stacks and of stacks of C functions that are still
active. This stack initially contains any arguments to the C function and it
is where the C function pushes its results to be returned to the caller (see
-|lua_CFunction()|).
+|lua_CFunction|).
- *luaref-stackindex*
+ *lua-stackindex*
For convenience, most query operations in the API do not follow a strict stack
discipline. Instead, they can refer to any element in the stack by using an
index: a positive index represents an absolute stack position (starting at 1);
@@ -1543,7 +1540,7 @@ We say that an index is valid if it lies between 1 and the stack top (that is,
if `1 <= abs(index) <= top`).
==============================================================================
-3.2 Stack Size *luaref-apiStackSize*
+3.2 Stack Size *lua-apiStackSize*
When you interact with Lua API, you are responsible for ensuring consistency.
In particular, you are responsible for controlling stack overflow. You can
@@ -1565,13 +1562,13 @@ we define an acceptable index as follows:
Note that 0 is never an acceptable index.
==============================================================================
-3.3 Pseudo-Indices *luaref-pseudoindex* *luaref-apiPseudoIndices*
+3.3 Pseudo-Indices *lua-pseudoindex*
Unless otherwise noted, any function that accepts valid indices can also be
called with pseudo-indices, which represent some Lua values that are
accessible to the C code but which are not in the stack. Pseudo-indices are
used to access the thread environment, the function environment, the registry,
-and the upvalues of a C function (see |luaref-apiCClosures|).
+and the upvalues of a C function (see |lua-cclosure|).
The thread environment (where global variables live) is always at pseudo-index
`LUA_GLOBALSINDEX`. The environment of the running C function is always at
@@ -1585,7 +1582,7 @@ global variable, do
<
==============================================================================
-3.4 C Closures *luaref-cclosure* *luaref-apiCClosures*
+3.4 C Closures *lua-cclosure*
When a C function is created, it is possible to associate some values with it,
thus creating a C closure; these values are called upvalues and are accessible
@@ -1599,7 +1596,7 @@ where `n` is greater than the number of upvalues of the current function,
produces an acceptable (but invalid) index.
==============================================================================
-3.5 Registry *luaref-registry* *luaref-apiRegistry*
+3.5 Registry *lua-registry*
Lua provides a registry, a pre-defined table that can be used by any C code to
store whatever Lua value it needs to store. This table is always located at
@@ -1614,7 +1611,7 @@ implemented by the auxiliary library, and therefore should not be used for
other purposes.
==============================================================================
-3.6 Error Handling in C *luaref-apiError*
+3.6 Error Handling in C *lua-apiError*
Internally, Lua uses the C `longjmp` facility to handle errors. (You can also
choose to use exceptions if you use C++; see file `luaconf.h`.) When Lua faces
@@ -1634,11 +1631,11 @@ Inside a C function you can raise an error by calling `lua_error` (see
|lua_error()|).
==============================================================================
-3.7 Functions and Types *luaref-apiFunctions*
+3.7 Functions and Types *lua-apiFunctions*
Here we list all functions and types from the C API in alphabetical order.
-lua_Alloc *lua_Alloc()*
+lua_Alloc *lua_Alloc*
>c
typedef void * (*lua_Alloc) (void *ud,
void *ptr,
@@ -1736,7 +1733,7 @@ lua_call *lua_call()*
to its original configuration. This is considered good programming
practice.
-lua_CFunction *luaref-cfunction* *lua_CFunction()*
+lua_CFunction *lua-cfunction* *lua_CFunction*
>c
typedef int (*lua_CFunction) (lua_State *L);
<
@@ -1755,7 +1752,7 @@ lua_CFunction *luaref-cfunction* *lua_CFunction()*
be properly discarded by Lua. Like a Lua function, a C function called
by Lua can also return many results.
- *luaref-cfunctionexample*
+ *lua-cfunctionexample*
As an example, the following function receives a variable number of
numerical arguments and returns their average and sum:
>c
@@ -1805,7 +1802,7 @@ lua_concat *lua_concat()*
leaves the result at the top. If `n` is 1, the result is the single
string on the stack (that is, the function does nothing); if `n` is 0,
the result is the empty string. Concatenation is done following the
- usual semantics of Lua (see |luaref-langConcat|).
+ usual semantics of Lua (see |lua-concat|).
lua_cpcall *lua_cpcall()*
>c
@@ -1836,7 +1833,7 @@ lua_dump *lua_dump()*
of the stack and produces a binary chunk that, if loaded again,
results in a function equivalent to the one dumped. As it produces
parts of the chunk, `lua_dump` calls function `writer` (see
- |lua_Writer()|) with the given `data` to write them.
+ |lua_Writer|) with the given `data` to write them.
The value returned is the error code returned by the last call to the
writer; 0 means no errors.
@@ -1884,12 +1881,12 @@ lua_gc *lua_gc()*
function returns 1 if the step finished a
garbage-collection cycle.
- `LUA_GCSETPAUSE` sets `data` /100 as the new value for the
- `pause` of the collector (see |luaref-langGC|).
+ `pause` of the collector (see |lua-gc|).
The function returns the previous value of the
pause.
- `LUA_GCSETSTEPMUL`sets `data` /100 as the new value for the
`step` `multiplier` of the collector (see
- |luaref-langGC|). The function returns the
+ |lua-gc|). The function returns the
previous value of the step multiplier.
lua_getallocf *lua_getallocf()*
@@ -1913,7 +1910,7 @@ lua_getfield *lua_getfield()*
<
Pushes onto the stack the value `t[k]`, where `t` is the value at the
given valid index `index`. As in Lua, this function may trigger a
- metamethod for the "index" event (see |luaref-langMetatables|).
+ metamethod for the "index" event (see |lua-metatable|).
lua_getglobal *lua_getglobal()*
>c
@@ -1944,7 +1941,7 @@ lua_gettable *lua_gettable()*
This function pops the key from the stack (putting the resulting value
in its place). As in Lua, this function may trigger a metamethod for
- the "index" event (see |luaref-langMetatables|).
+ the "index" event (see |lua-metatable|).
lua_gettop *lua_gettop()*
>c
@@ -1963,7 +1960,7 @@ lua_insert *lua_insert()*
elements above this index to open space. Cannot be called with a
pseudo-index, because a pseudo-index is not an actual stack position.
-lua_Integer *lua_Integer()*
+lua_Integer *lua_Integer*
>c
typedef ptrdiff_t lua_Integer;
<
@@ -2072,11 +2069,11 @@ lua_load *lua_load()*
and loads it accordingly (see program `luac`).
The `lua_load` function uses a user-supplied `reader` function to read
- the chunk (see |lua_Reader()|). The `data` argument is an opaque
+ the chunk (see |lua_Reader|). The `data` argument is an opaque
value passed to the reader function.
The `chunkname` argument gives a name to the chunk, which is used for
- error messages and in debug information (see |luaref-apiDebug|).
+ error messages and in debug information (see |lua-apiDebug|).
lua_newstate *lua_newstate()*
>c
@@ -2101,7 +2098,7 @@ lua_newthread *lua_newthread()*
lua_State *lua_newthread (lua_State *L);
<
Creates a new thread, pushes it on the stack, and returns a pointer to
- a `lua_State` (see |lua_State()|) that represents this new
+ a `lua_State` (see |lua_State|) that represents this new
thread. The new state returned by this function shares with the
original state all global objects (such as tables), but has an
independent execution stack.
@@ -2116,7 +2113,7 @@ lua_newuserdata *lua_newuserdata()*
This function allocates a new block of memory with the given size,
pushes onto the stack a new full userdata with the block address, and
returns this address.
- *luaref-userdata*
+ *userdata*
Userdata represents C values in Lua. A full userdata represents a
block of memory. It is an object (like a table): you must create it,
it can have its own metatable, and you can detect when it is being
@@ -2136,7 +2133,7 @@ lua_next *lua_next()*
no more elements in the table, then `lua_next` returns 0 (and pushes
nothing).
- *luaref-tabletraversal*
+ *lua-tabletraversal*
A typical traversal looks like this:
>c
/* table is in the stack at index 't' */
@@ -2155,7 +2152,7 @@ lua_next *lua_next()*
key is actually a string. Recall that `lua_tolstring` `changes` the
value at the given index; this confuses the next call to `lua_next`.
-lua_Number *lua_Number()*
+lua_Number *lua_Number*
>c
typedef double lua_Number;
<
@@ -2228,7 +2225,7 @@ lua_pushcclosure *lua_pushcclosure()*
Pushes a new C closure onto the stack.
When a C function is created, it is possible to associate some values
- with it, thus creating a C closure (see |luaref-apiCClosures|); these
+ with it, thus creating a C closure (see |lua-cclosure|); these
values are then accessible to the function whenever it is called. To
associate values with a C function, first these values should be
pushed onto the stack (when there are multiple values, the first value
@@ -2247,7 +2244,7 @@ lua_pushcfunction *lua_pushcfunction()*
Any function to be registered in Lua must follow the correct protocol
to receive its parameters and return its results (see
- |lua_CFunction()|).
+ |lua_CFunction|).
`lua_pushcfunction` is defined as a macro:
>c
@@ -2284,7 +2281,7 @@ lua_pushlightuserdata *lua_pushlightuserdata()*
void lua_pushlightuserdata (lua_State *L, void *p);
<
Pushes a light userdata onto the stack.
- *luaref-lightuserdata*
+ *lua-lightuserdata*
Userdata represents C values in Lua. A light userdata represents a
pointer. It is a value (like a number): you do not create it, it has
no individual metatable, and it is not collected (as it was never
@@ -2386,7 +2383,7 @@ lua_rawseti *lua_rawseti()*
This function pops the value from the stack. The assignment is raw;
that is, it does not invoke metamethods.
-lua_Reader *lua_Reader()*
+lua_Reader *lua_Reader*
>c
typedef const char * (*lua_Reader) (lua_State *L,
void *data,
@@ -2476,7 +2473,7 @@ lua_setfield *lua_setfield()*
This function pops the value from the stack. As in Lua, this function
may trigger a metamethod for the "newindex" event (see
- |luaref-langMetatables|).
+ |lua-metatable|).
lua_setglobal *lua_setglobal()*
>c
@@ -2505,7 +2502,7 @@ lua_settable *lua_settable()*
This function pops both the key and the value from the stack. As in
Lua, this function may trigger a metamethod for the "newindex" event
- (see |luaref-langMetatables|).
+ (see |lua-metatable|).
lua_settop *lua_settop()*
>c
@@ -2516,7 +2513,7 @@ lua_settop *lua_settop()*
elements are filled with `nil`. If `index` is 0, then all stack
elements are removed.
-lua_State *lua_State()*
+lua_State *lua_State*
>c
typedef struct lua_State lua_State;
<
@@ -2561,9 +2558,9 @@ lua_tointeger *lua_tointeger()*
lua_Integer lua_tointeger (lua_State *L, int idx);
<
Converts the Lua value at the given acceptable index to the signed
- integral type `lua_Integer` (see |lua_Integer()|). The Lua value
+ integral type `lua_Integer` (see |lua_Integer|). The Lua value
must be a number or a string convertible to a number (see
- |luaref-langCoercion|); otherwise, `lua_tointeger` returns 0.
+ |lua-coercion|); otherwise, `lua_tointeger` returns 0.
If the number is not an integer, it is truncated in some non-specified
way.
@@ -2592,8 +2589,8 @@ lua_tonumber *lua_tonumber()*
lua_Number lua_tonumber (lua_State *L, int index);
<
Converts the Lua value at the given acceptable index to the C type
- `lua_Number` (see |lua_Number()|). The Lua value must be a number
- or a string convertible to a number (see |luaref-langCoercion|);
+ `lua_Number` (see |lua_Number|). The Lua value must be a number
+ or a string convertible to a number (see |lua-coercion|);
otherwise, `lua_tonumber` returns 0.
lua_topointer *lua_topointer()*
@@ -2620,7 +2617,7 @@ lua_tothread *lua_tothread()*
lua_State *lua_tothread (lua_State *L, int index);
<
Converts the value at the given acceptable index to a Lua thread
- (represented as `lua_State*` |lua_State()|). This value must be a
+ (represented as `lua_State*` |lua_State|). This value must be a
thread; otherwise, the function returns `NULL`.
lua_touserdata *lua_touserdata()*
@@ -2649,7 +2646,7 @@ lua_typename *lua_typename()*
Returns the name of the type encoded by the value `tp`, which must be
one the values returned by `lua_type`.
-lua_Writer *lua_Writer()*
+lua_Writer *lua_Writer*
>c
typedef int (*lua_Writer) (lua_State *L,
const void* p,
@@ -2690,7 +2687,7 @@ lua_yield *lua_yield()*
parameter `nresults` is the number of values from the stack that are
passed as results to `lua_resume`.
- *luaref-stackexample*
+ *lua-stackexample*
As an example of stack manipulation, if the stack starts as
`10 20 30 40 50*` (from bottom to top; the `*` marks the top), then
>
@@ -2706,14 +2703,14 @@ lua_yield *lua_yield()*
<
==============================================================================
-3.8 The Debug Interface *luaref-apiDebug*
+3.8 The Debug Interface *lua-apiDebug*
Lua has no built-in debugging facilities. Instead, it offers a special
interface by means of functions and hooks. This interface allows the
construction of different kinds of debuggers, profilers, and other tools that
need "inside information" from the interpreter.
-lua_Debug *lua_Debug()*
+lua_Debug *lua_Debug*
>c
typedef struct lua_Debug {
@@ -2737,7 +2734,7 @@ function. `lua_getstack` (see |lua_getstack()|) fills only the private part
of this structure, for later use. To fill the other fields of `lua_Debug` with
useful information, call `lua_getinfo` (see |lua_getinfo()|).
-The fields of `lua_Debug` have the following meaning:
+The fields of `lua_Debug` have the following meaning:
- `source` If the function was defined in a string, then `source` is
that string. If the function was defined in a file, then
@@ -2794,7 +2791,7 @@ lua_getinfo *lua_getinfo()*
To get information about a function invocation, the parameter `ar`
must be a valid activation record that was filled by a previous call
to `lua_getstack` (see |lua_getstack()|) or given as argument to
- a hook (see |lua_Hook()|).
+ a hook (see |lua_Hook|).
To get information about a function you push it onto the stack and
start the `what` string with the character `>`. (In that case,
@@ -2832,7 +2829,7 @@ lua_getlocal *lua_getlocal()*
Gets information about a local variable of a given activation record.
The parameter `ar` must be a valid activation record that was filled
by a previous call to `lua_getstack` (see |lua_getstack()|) or
- given as argument to a hook (see |lua_Hook()|). The index `n`
+ given as argument to a hook (see |lua_Hook|). The index `n`
selects which local variable to inspect (1 is the first parameter or
active local variable, and so on, until the last active local
variable). `lua_getlocal` pushes the variable's value onto the stack
@@ -2851,7 +2848,7 @@ lua_getstack *lua_getstack()*
<
Gets information about the interpreter runtime stack.
- This function fills parts of a `lua_Debug` (see |lua_Debug()|)
+ This function fills parts of a `lua_Debug` (see |lua_Debug|)
structure with an identification of the `activation record` of the
function executing at a given level. Level 0 is the current running
function, whereas level `n+1` is the function that has called level
@@ -2874,7 +2871,7 @@ lua_getupvalue *lua_getupvalue()*
number of upvalues. For C functions, this function uses the empty
string `""` as a name for all upvalues.
-lua_Hook *lua_Hook()*
+lua_Hook *lua_Hook*
>c
typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);
<
@@ -2950,7 +2947,7 @@ lua_setupvalue *lua_setupvalue()*
Returns `NULL` (and pops nothing) when the index is greater than the
number of upvalues.
- *luaref-debugexample*
+ *lua-debugexample*
As an example, the following function lists the names of all local
variables and upvalues for a function at a given level of the stack:
>c
@@ -2976,7 +2973,7 @@ lua_setupvalue *lua_setupvalue()*
<
==============================================================================
-4 THE AUXILIARY LIBRARY *luaref-aux*
+4 THE AUXILIARY LIBRARY *lua-aux*
The auxiliary library provides several convenient functions to interface C
with Lua. While the basic API provides the primitive functions for all
@@ -2996,7 +2993,7 @@ message is formatted for arguments (e.g., "bad argument #1"), you should not
use these functions for other stack values.
==============================================================================
-4.1 Functions and Types *luaref-auxFunctions*
+4.1 Functions and Types *lua-auxFunctions*
Here we list all functions and types from the auxiliary library in
alphabetical order.
@@ -3005,20 +3002,20 @@ luaL_addchar *luaL_addchar()*
>c
void luaL_addchar (luaL_Buffer *B, char c);
<
- Adds the character `c` to the buffer `B` (see |luaL_Buffer()|).
+ Adds the character `c` to the buffer `B` (see |luaL_Buffer|).
luaL_addlstring *luaL_addlstring()*
>c
void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l);
<
Adds the string pointed to by `s` with length `l` to the buffer `B`
- (see |luaL_Buffer()|). The string may contain embedded zeros.
+ (see |luaL_Buffer|). The string may contain embedded zeros.
luaL_addsize *luaL_addsize()*
>c
void luaL_addsize (luaL_Buffer *B, size_t n);
<
- Adds to the buffer `B` (see |luaL_Buffer()|) a string of length
+ Adds to the buffer `B` (see |luaL_Buffer|) a string of length
`n` previously copied to the buffer area (see
|luaL_prepbuffer()|).
@@ -3027,14 +3024,14 @@ luaL_addstring *luaL_addstring()*
void luaL_addstring (luaL_Buffer *B, const char *s);
<
Adds the zero-terminated string pointed to by `s` to the buffer `B`
- (see |luaL_Buffer()|). The string may not contain embedded zeros.
+ (see |luaL_Buffer|). The string may not contain embedded zeros.
luaL_addvalue *luaL_addvalue()*
>c
void luaL_addvalue (luaL_Buffer *B);
<
Adds the value at the top of the stack to the buffer `B` (see
- |luaL_Buffer()|). Pops the value.
+ |luaL_Buffer|). Pops the value.
This is the only function on string buffers that can (and must) be
called with an extra element on the stack, which is the value to be
@@ -3065,7 +3062,7 @@ luaL_argerror *luaL_argerror()*
This function never returns, but it is an idiom to use it in C
functions as `return luaL_argerror(` `args` `)`.
-luaL_Buffer *luaL_Buffer()*
+luaL_Buffer *luaL_Buffer*
>c
typedef struct luaL_Buffer luaL_Buffer;
<
@@ -3099,7 +3096,7 @@ luaL_buffinit *luaL_buffinit()*
void luaL_buffinit (lua_State *L, luaL_Buffer *B);
<
Initializes a buffer `B`. This function does not allocate any space;
- the buffer must be declared as a variable (see |luaL_Buffer()|).
+ the buffer must be declared as a variable (see |luaL_Buffer|).
luaL_callmeta *luaL_callmeta()*
>c
@@ -3133,7 +3130,7 @@ luaL_checkinteger *luaL_checkinteger()*
lua_Integer luaL_checkinteger (lua_State *L, int narg);
<
Checks whether the function argument `narg` is a number and returns
- this number cast to a `lua_Integer` (see |lua_Integer()|).
+ this number cast to a `lua_Integer` (see |lua_Integer|).
luaL_checklong *luaL_checklong()*
>c
@@ -3154,7 +3151,7 @@ luaL_checknumber *luaL_checknumber()*
lua_Number luaL_checknumber (lua_State *L, int narg);
<
Checks whether the function argument `narg` is a number and returns
- this number (see |lua_Number()|).
+ this number (see |lua_Number|).
luaL_checkoption *luaL_checkoption()*
>c
@@ -3334,7 +3331,7 @@ luaL_openlibs *luaL_openlibs()*
void luaL_openlibs (lua_State *L);
<
Opens all standard Lua libraries into the given state. See also
- |luaref-openlibs| for details on how to open individual libraries.
+ |lua-openlibs| for details on how to open individual libraries.
luaL_optint *luaL_optint()*
>c
@@ -3351,7 +3348,7 @@ luaL_optinteger *luaL_optinteger()*
lua_Integer d);
<
If the function argument `narg` is a number, returns this number cast
- to a `lua_Integer` (see |lua_Integer()|). If this argument is
+ to a `lua_Integer` (see |lua_Integer|). If this argument is
absent or is `nil`, returns `d`. Otherwise, raises an error.
luaL_optlong *luaL_optlong()*
@@ -3398,7 +3395,7 @@ luaL_prepbuffer *luaL_prepbuffer()*
char *luaL_prepbuffer (luaL_Buffer *B);
<
Returns an address to a space of size `LUAL_BUFFERSIZE` where you can
- copy a string to be added to buffer `B` (see |luaL_Buffer()|).
+ copy a string to be added to buffer `B` (see |luaL_Buffer|).
After copying the string into this space you must call `luaL_addsize`
(see |luaL_addsize()|) with the size of the string to actually
add it to the buffer.
@@ -3428,7 +3425,7 @@ luaL_ref *luaL_ref()*
constant `LUA_REFNIL`. The constant `LUA_NOREF` is guaranteed to be
different from any reference returned by `luaL_ref`.
-luaL_Reg *luaL_Reg()*
+luaL_Reg *luaL_Reg*
>c
typedef struct luaL_Reg {
const char *name;
@@ -3449,7 +3446,7 @@ luaL_register *luaL_register()*
Opens a library.
When called with `libname` equal to `NULL`, it simply registers all
- functions in the list `l` (see |luaL_Reg()|) into the table on
+ functions in the list `l` (see |luaL_Reg|) into the table on
the top of the stack.
When called with a non-null `libname`, `luaL_register` creates a new
@@ -3507,7 +3504,7 @@ luaL_where *luaL_where()*
This function is used to build a prefix for error messages.
==============================================================================
-5 STANDARD LIBRARIES *luaref-Lib*
+5 STANDARD LIBRARIES *lua-lib*
The standard libraries provide useful functions that are implemented directly
through the C API. Some of these functions provide essential services to the
@@ -3531,7 +3528,7 @@ separate C modules. Currently, Lua has the following standard libraries:
Except for the basic and package libraries, each library provides all its
functions as fields of a global table or as methods of its objects.
- *luaref-openlibs*
+ *lua-openlibs*
To have access to these libraries, the C host program should call the
`luaL_openlibs` function, which opens all standard libraries (see
|luaL_openlibs()|). Alternatively, the host program can open the libraries
@@ -3544,18 +3541,18 @@ declared in `lualib.h` and should not be called directly: you must call them
like any other Lua C function, e.g., by using `lua_call` (see |lua_call()|).
==============================================================================
-5.1 Basic Functions *luaref-libBasic*
+5.1 Basic Functions *lua-lib-core*
The basic library provides some core functions to Lua. If you do not include
this library in your application, you should check carefully whether you need
to provide implementations for some of its facilities.
-assert({v} [, {message}]) *luaref-assert()*
+assert({v} [, {message}]) *assert()*
Issues an error when the value of its argument `v` is false (i.e., `nil` or
`false`); otherwise, returns all its arguments. `message` is an error message;
when absent, it defaults to "assertion failed!"
-collectgarbage({opt} [, {arg}]) *luaref-collectgarbage()*
+collectgarbage({opt} [, {arg}]) *collectgarbage()*
This function is a generic interface to the garbage collector. It
performs different functions according to its first argument, {opt}:
@@ -3569,18 +3566,18 @@ collectgarbage({opt} [, {arg}]) *luaref-collectgarbage()*
you must experimentally tune the value of {arg}. Returns
`true` if the step finished a collection cycle.
`"setpause"` sets {arg} /100 as the new value for the `pause` of
- the collector (see |luaref-langGC|).
+ the collector (see |lua-gc|).
`"setstepmul"` sets {arg} /100 as the new value for the `step
- multiplier` of the collector (see |luaref-langGC|).
+ multiplier` of the collector (see |lua-gc|).
-dofile({filename}) *luaref-dofile()*
+dofile({filename}) *dofile()*
Opens the named file and executes its contents as a Lua chunk. When
called without arguments, `dofile` executes the contents of the
standard input (`stdin`). Returns all values returned by the chunk. In
case of errors, `dofile` propagates the error to its caller (that is,
`dofile` does not run in protected mode).
-error({message} [, {level}]) *luaref-error()*
+error({message} [, {level}]) *error()*
Terminates the last protected function called and returns `message` as
the error message. Function {error} never returns.
@@ -3592,27 +3589,27 @@ error({message} [, {level}]) *luaref-error()*
a level 0 avoids the addition of error position information to the
message.
-_G *luaref-_G()*
+_G *_G*
A global variable (not a function) that holds the global environment
(that is, `_G._G = _G`). Lua itself does not use this variable;
changing its value does not affect any environment, nor vice-versa.
(Use `setfenv` to change environments.)
-getfenv({f}) *luaref-getfenv()*
+getfenv({f}) *getfenv()*
Returns the current environment in use by the function. {f} can be a
Lua function or a number that specifies the function at that stack
level: Level 1 is the function calling `getfenv`. If the given
function is not a Lua function, or if {f} is 0, `getfenv` returns the
global environment. The default for {f} is 1.
-getmetatable({object}) *luaref-getmetatable()*
+getmetatable({object}) *getmetatable()*
If {object} does not have a metatable, returns `nil`. Otherwise, if
the object's metatable has a `"__metatable"` field, returns the
associated value. Otherwise, returns the metatable of the given
object.
-ipairs({t}) *luaref-ipairs()*
- Returns three values: an iterator function, the table {t}, and 0, so
+ipairs({t}) *ipairs()*
+ Returns three values: an |iterator| function, the table {t}, and 0, so
that the construction
`for i,v in ipairs(t) do` `body` `end`
@@ -3620,7 +3617,7 @@ ipairs({t}) *luaref-ipairs()*
will iterate over the pairs (`1,t[1]`), (`2,t[2]`), ..., up to the
first integer key absent from the table.
-load({func} [, {chunkname}]) *luaref-load()*
+load({func} [, {chunkname}]) *load()*
Loads a chunk using function {func} to get its pieces. Each call to
{func} must return a string that concatenates with previous results. A
return of `nil` (or no value) signals the end of the chunk.
@@ -3632,12 +3629,12 @@ load({func} [, {chunkname}]) *luaref-load()*
{chunkname} is used as the chunk name for error messages and debug
information.
-loadfile([{filename}]) *luaref-loadfile()*
- Similar to `load` (see |luaref-load()|), but gets the chunk from file
+loadfile([{filename}]) *loadfile()*
+ Similar to `load` (see |load()|), but gets the chunk from file
{filename} or from the standard input, if no file name is given.
-loadstring({string} [, {chunkname}]) *luaref-loadstring()*
- Similar to `load` (see |luaref-load()|), but gets the chunk from the
+loadstring({string} [, {chunkname}]) *loadstring()*
+ Similar to `load` (see |load()|), but gets the chunk from the
given {string}.
To load and run a given string, use the idiom
@@ -3645,7 +3642,7 @@ loadstring({string} [, {chunkname}]) *luaref-loadstring()*
assert(loadstring(s))()
<
-next({table} [, {index}]) *luaref-next()*
+next({table} [, {index}]) *next()*
Allows a program to traverse all fields of a table. Its first argument
is a table and its second argument is an index in this table. `next`
returns the next index of the table and its associated value. When
@@ -3655,23 +3652,23 @@ next({table} [, {index}]) *luaref-next()*
argument is absent, then it is interpreted as `nil`. In particular,
you can use `next(t)` to check whether a table is empty.
- The order in which the indices are enumerated is not specified, `even
- for` `numeric indices`. (To traverse a table in numeric order, use a
- numerical `for` or the `ipairs` |luaref-ipairs()| function.)
+ The order in which the indices are enumerated is not specified, even
+ for numeric indices. (To traverse a table in numeric order, use a
+ numerical `for` or the |ipairs()| function.)
The behavior of `next` is `undefined` if, during the traversal, you
assign any value to a non-existent field in the table. You may however
modify existing fields. In particular, you may clear existing fields.
-pairs({t}) *luaref-pairs()*
- Returns three values: the `next` |luaref-next()| function, the table
- {t}, and `nil`, so that the construction
+pairs({t}) *pairs()*
+ Returns three values: the |next()| function, the table {t}, and `nil`,
+ so that the construction
`for k,v in pairs(t) do` `body` `end`
will iterate over all key-value pairs of table {t}.
-pcall({f}, {arg1}, {...}) *luaref-pcall()*
+pcall({f}, {arg1}, {...}) *pcall()*
Calls function {f} with the given arguments in `protected mode`. This
means that any error inside {f} is not propagated; instead, `pcall`
catches the error and returns a status code. Its first result is the
@@ -3680,34 +3677,34 @@ pcall({f}, {arg1}, {...}) *luaref-pcall()*
after this first result. In case of any error, `pcall` returns `false`
plus the error message.
-print({...}) *luaref-print()*
+print({...}) *print()*
Receives any number of arguments, and prints their values to `stdout`,
- using the `tostring` |luaref-tostring()| function to convert them to
+ using the `tostring` |tostring()| function to convert them to
strings. `print` is not intended for formatted output, but only as a
quick way to show a value, typically for debugging. For formatted
output, use `string.format` (see |string.format()|).
-rawequal({v1}, {v2}) *luaref-rawequal()*
+rawequal({v1}, {v2}) *rawequal()*
Checks whether {v1} is equal to {v2}, without invoking any metamethod.
Returns a boolean.
-rawget({table}, {index}) *luaref-rawget()*
+rawget({table}, {index}) *rawget()*
Gets the real value of `table[index]`, without invoking any
metamethod. {table} must be a table; {index} may be any value.
-rawset({table}, {index}, {value}) *luaref-rawset()*
+rawset({table}, {index}, {value}) *rawset()*
Sets the real value of `table[index]` to {value}, without invoking any
metamethod. {table} must be a table, {index} any value different from
`nil`, and {value} any Lua value.
This function returns {table}.
-select({index}, {...}) *luaref-select()*
+select({index}, {...}) *select()*
If {index} is a number, returns all arguments after argument number
{index}. Otherwise, {index} must be the string `"#"`, and `select`
returns the total number of extra arguments it received.
-setfenv({f}, {table}) *luaref-setfenv()*
+setfenv({f}, {table}) *setfenv()*
Sets the environment to be used by the given function. {f} can be a
Lua function or a number that specifies the function at that stack
level: Level 1 is the function calling `setfenv`. `setfenv` returns
@@ -3716,7 +3713,7 @@ setfenv({f}, {table}) *luaref-setfenv()*
As a special case, when {f} is 0 `setfenv` changes the environment of
the running thread. In this case, `setfenv` returns no values.
-setmetatable({table}, {metatable}) *luaref-setmetatable()*
+setmetatable({table}, {metatable}) *setmetatable()*
Sets the metatable for the given table. (You cannot change the
metatable of other types from Lua, only from C.) If {metatable} is
`nil`, removes the metatable of the given table. If the original
@@ -3724,7 +3721,7 @@ setmetatable({table}, {metatable}) *luaref-setmetatable()*
This function returns {table}.
-tonumber({e} [, {base}]) *luaref-tonumber()*
+tonumber({e} [, {base}]) *tonumber()*
Tries to convert its argument to a number. If the argument is already
a number or a string convertible to a number, then `tonumber` returns
this number; otherwise, it returns `nil`.
@@ -3734,26 +3731,26 @@ tonumber({e} [, {base}]) *luaref-tonumber()*
10, the letter `A` (in either upper or lower case) represents 10, `B`
represents 11, and so forth, with `Z'` representing 35. In base 10
(the default), the number may have a decimal part, as well as an
- optional exponent part (see |luaref-langLexConv|). In other bases,
+ optional exponent part (see |lua-lexical|). In other bases,
only unsigned integers are accepted.
-tostring({e}) *luaref-tostring()*
+tostring({e}) *tostring()*
Receives an argument of any type and converts it to a string in a
reasonable format. For complete control of how numbers are converted,
use `string.format` (see |string.format()|).
- *__tostring*
+ *__tostring*
If the metatable of {e} has a `"__tostring"` field, `tostring` calls
the corresponding value with {e} as argument, and uses the result of
the call as its result.
-type({v}) *luaref-type()*
+type({v}) *lua-type()*
Returns the type of its only argument, coded as a string. The possible
results of this function are `"nil"` (a string, not the value `nil`),
`"number"`, `"string"`, `"boolean`, `"table"`, `"function"`,
`"thread"`, and `"userdata"`.
-unpack({list} [, {i} [, {j}]]) *luaref-unpack()*
+unpack({list} [, {i} [, {j}]]) *unpack()*
Returns the elements from the given table. This function is equivalent
to
>lua
@@ -3761,16 +3758,16 @@ unpack({list} [, {i} [, {j}]]) *luaref-unpack()*
<
except that the above code can be written only for a fixed number of
elements. By default, {i} is 1 and {j} is the length of the list, as
- defined by the length operator(see |luaref-langLength|).
+ defined by the length operator (see |lua-length|).
-_VERSION *luaref-_VERSION()*
+_VERSION *_VERSION*
A global variable (not a function) that holds a string containing the
current interpreter version. The current contents of this string is
`"Lua 5.1"` .
-xpcall({f}, {err}) *luaref-xpcall()*
- This function is similar to `pcall` (see |luaref-pcall()|), except that
- you can set a new error handler.
+xpcall({f}, {err}) *xpcall()*
+ This function is similar to `pcall` (see |pcall()|), except that you
+ can set a new error handler.
`xpcall` calls function {f} in protected mode, using {err} as the
error handler. Any error inside {f} is not propagated; instead,
@@ -3782,10 +3779,10 @@ xpcall({f}, {err}) *luaref-xpcall()*
`false` plus the result from {err}.
==============================================================================
-5.2 Coroutine Manipulation *luaref-libCoro*
+5.2 Coroutine Manipulation *lua-lib-coroutine*
The operations related to coroutines comprise a sub-library of the basic
-library and come inside the table `coroutine`. See |luaref-langCoro| for a
+library and come inside the table `coroutine`. See |lua-coroutine| for a
general description of coroutines.
coroutine.create({f}) *coroutine.create()*
@@ -3826,18 +3823,18 @@ coroutine.wrap({f}) *coroutine.wrap()*
coroutine.yield({...}) *coroutine.yield()*
Suspends the execution of the calling coroutine. The coroutine cannot
- be running a C function, a metamethod, or an iterator. Any arguments
+ be running a C function, a metamethod, or an |iterator|. Any arguments
to `yield` are passed as extra results to `resume`.
==============================================================================
-5.3 - Modules *luaref-libModule*
+5.3 Modules *lua-modules*
The package library provides basic facilities for loading and building modules
in Lua. It exports two of its functions directly in the global environment:
-`require` and `module` (see |luaref-require()| and |luaref-module()|). Everything else is
+`require` and `module` (see |require()| and |module()|). Everything else is
exported in a table `package`.
-module({name} [, {...}]) *luaref-module()*
+module({name} [, {...}]) *module()*
Creates a module. If there is a table in `package.loaded[name]`, this
table is the module. Otherwise, if there is a global table `t` with
the given name, this table is the module. Otherwise creates a new
@@ -3847,8 +3844,7 @@ module({name} [, {...}]) *luaref-module()*
`t._PACKAGE` with the package name (the full module name minus last
component; see below). Finally, `module` sets `t` as the new
environment of the current function and the new value of
- `package.loaded[name]`, so that `require` (see |luaref-require()|)
- returns `t`.
+ `package.loaded[name]`, so that |require()| returns `t`.
If {name} is a compound name (that is, one with components separated
by dots), `module` creates (or reuses, if they already exist) tables
@@ -3858,7 +3854,7 @@ module({name} [, {...}]) *luaref-module()*
This function may receive optional `options` after the module name,
where each option is a function to be applied over the module.
-require({modname}) *luaref-require()*
+require({modname}) *require()*
Loads the given module. The function starts by looking into the
`package.loaded` table to determine whether {modname} is already
loaded. If it is, then `require` returns the value stored at
@@ -3901,7 +3897,7 @@ require({modname}) *luaref-require()*
If there is any error loading or running the module, or if it cannot
find any loader for the module, then `require` signals an error.
-package.cpath *package.cpath*
+package.cpath *package.cpath*
The path used by `require` to search for a C loader.
Lua initializes the C path `package.cpath` in the same way it
@@ -3914,11 +3910,11 @@ package.loaded *package.loaded()*
When you require a module `modname` and `package.loaded[modname]` is
not false, `require` simply returns the value stored there.
-package.loadlib({libname}, {funcname}) *package.loadlib()*
+package.loadlib({libname}, {funcname}) *package.loadlib()*
Dynamically links the host program with the C library {libname}.
Inside this library, looks for a function {funcname} and returns this
function as a C function. (So, {funcname} must follow the protocol
- (see |lua_CFunction()|)).
+ (see |lua_CFunction|)).
This is a low-level function. It completely bypasses the package and
module system. Unlike `require`, it does not perform any path
@@ -3931,7 +3927,7 @@ package.loadlib({libname}, {funcname}) *package.loadlib()*
available on some platforms (Windows, Linux, Mac OS X, Solaris, BSD,
plus other Unix systems that support the `dlfcn` standard).
-package.path *package.path*
+package.path *package.path*
The path used by `require` to search for a Lua loader.
At start-up, Lua initializes this variable with the value of the
@@ -3952,7 +3948,7 @@ package.path *package.path*
order.
package.preload *package.preload()*
- A table to store loaders for specific modules (see |luaref-require()|).
+ A table to store loaders for specific modules (see |require()|).
package.seeall({module}) *package.seeall()*
Sets a metatable for {module} with its `__index` field referring to
@@ -3960,7 +3956,7 @@ package.seeall({module}) *package.seeall()*
global environment. To be used as an option to function {module}.
==============================================================================
-5.4 - String Manipulation *luaref-libString*
+5.4 String Manipulation *lua-lib-string*
This library provides generic functions for string manipulation, such as
finding and extracting substrings, and pattern matching. When indexing a
@@ -3992,7 +3988,7 @@ string.char({...}) *string.char()*
string.dump({function}) *string.dump()*
Returns a string containing a binary representation of the given
- function, so that a later |luaref-loadstring()| on this string returns a
+ function, so that a later |loadstring()| on this string returns a
copy of the function. {function} must be a Lua function without
upvalues.
@@ -4036,7 +4032,7 @@ string.format({formatstring}, {...}) *string.format()*
This function does not accept string values containing embedded zeros.
string.gmatch({s}, {pattern}) *string.gmatch()*
- Returns an iterator function that, each time it is called, returns the
+ Returns an |iterator| function that, each time it is called, returns the
next captures from {pattern} over string {s}.
If {pattern} specifies no captures, then the whole match is produced
@@ -4060,7 +4056,7 @@ string.gmatch({s}, {pattern}) *string.gmatch()*
end
<
-string.gsub({s}, {pattern}, {repl} [, {n}]) *string.gsub()*
+string.gsub({s}, {pattern}, {repl} [, {n}]) *string.gsub()*
Returns a copy of {s} in which all occurrences of the {pattern} have
been replaced by a replacement string specified by {repl}, which may
be a string, a table, or a function. `gsub` also returns, as its
@@ -4101,16 +4097,16 @@ string.gsub({s}, {pattern}, {repl} [, {n}]) *string.gsub()*
x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
--> x="world hello Lua from"
- x = string.gsub("home = `HOME, user = ` USER", "%$(%w+)", os.getenv)
+ x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
--> x="home = /home/roberto, user = roberto"
- x = string.gsub("4+5 = `return 4+5` ", "% `(.-)%` ", function (s)
+ x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
return loadstring(s)()
end)
--> x="4+5 = 9"
local t = {name="lua", version="5.1"}
- x = string.gsub(" `name%-` version.tar.gz", "%$(%w+)", t)
+ x = string.gsub("$name%-$version.tar.gz", "%$(%w+)", t)
--> x="lua-5.1.tar.gz"
<
@@ -4154,7 +4150,7 @@ string.upper({s}) *string.upper()*
locale.
------------------------------------------------------------------------------
-5.4.1 Patterns *luaref-patterns* *luaref-libStringPat*
+5.4.1 Patterns *lua-patterns
A character class is used to represent a set of characters. The following
combinations are allowed in describing a character class:
@@ -4199,9 +4195,8 @@ instance, `%S` represents all non-space characters.
The definitions of letter, space, and other character groups depend on the
current locale. In particular, the class `[a-z]` may not be equivalent to `%l`.
- *luaref-patternitem*
-Pattern Item:~
--------------
+PATTERN ITEM *lua-patternitem*
+
A pattern item may be
- a single character class, which matches any single character in the
@@ -4226,17 +4221,15 @@ A pattern item may be
`y` where the count reaches 0. For instance, the item `%b()` matches
expressions with balanced parentheses.
- *luaref-pattern*
-Pattern:~
---------
+PATTERN *lua-pattern*
+
A pattern is a sequence of pattern items. A `^` at the beginning of a pattern
anchors the match at the beginning of the subject string. A `$` at the end of
a pattern anchors the match at the end of the subject string. At other
positions, `^` and `$` have no special meaning and represent themselves.
- *luaref-capture*
-Captures:~
----------
+CAPTURES *lua-capture*
+
A pattern may contain sub-patterns enclosed in parentheses; they describe
captures. When a match succeeds, the substrings of the subject string that
match captures are stored (captured) for future use. Captures are numbered
@@ -4252,7 +4245,7 @@ string `"flaaap"`, there will be two captures: 3 and 5.
A pattern cannot contain embedded zeros. Use `%z` instead.
==============================================================================
-5.5 Table Manipulation *luaref-libTable*
+5.5 Table Manipulation *lua-lib-table*
This library provides generic functions for table manipulation. It provides
all its functions inside the table `table`.
@@ -4268,15 +4261,15 @@ table.concat({table} [, {sep} [, {i} [, {j}]]]) *table.concat()*
for {j} is the length of the table. If {i} is greater than {j},
returns the empty string.
-table.foreach({table}, {f}) *table.foreach()*
+table.foreach({table}, {f}) *table.foreach()*
Executes the given {f} over all elements of {table}. For each element,
{f} is called with the index and respective value as arguments. If {f}
returns a non-`nil` value, then the loop is broken, and this value is
returned as the final value of `table.foreach`.
- See |luaref-next()| for extra information about table traversals.
+ See |next()| for extra information about table traversals.
-table.foreachi({table}, {f}) *table.foreachi()*
+table.foreachi({table}, {f}) *table.foreachi()*
Executes the given {f} over the numerical indices of {table}. For each
index, {f} is called with the index and respective value as arguments.
Indices are visited in sequential order, from 1 to `n`, where `n` is
@@ -4288,7 +4281,7 @@ table.insert({table}, [{pos},] {value}) *table.insert()*
Inserts element {value} at position {pos} in {table}, shifting up
other elements to open space, if necessary. The default value for
{pos} is `n+1`, where `n` is the length of the table (see
- |luaref-langLength|), so that a call `table.insert(t,x)` inserts `x`
+ |lua-length|), so that a call `table.insert(t,x)` inserts `x`
at the end of table `t`.
table.maxn({table}) *table.maxn()*
@@ -4296,27 +4289,26 @@ table.maxn({table}) *table.maxn()*
zero if the table has no positive numerical indices. (To do its job
this function does a linear traversal of the whole table.)
-table.remove({table} [, {pos}]) *table.remove()*
+table.remove({table} [, {pos}]) *table.remove()*
Removes from {table} the element at position {pos}, shifting down
other elements to close the space, if necessary. Returns the value of
the removed element. The default value for {pos} is `n`, where `n` is
- the length of the table (see |luaref-langLength|), so that a call
+ the length of the table (see |lua-length|), so that a call
`table.remove(t)` removes the last element of table `t`.
-table.sort({table} [, {comp}]) *table.sort()*
+table.sort({table} [, {comp}]) *table.sort()*
Sorts table elements in a given order, `in-place`, from `table[1]` to
- `table[n]`, where `n` is the length of the table (see
- |luaref-langLength|). If {comp} is given, then it must be a function
- that receives two table elements, and returns true when the first is
- less than the second (so that `not comp(a[i+1],a[i])` will be true
- after the sort). If {comp} is not given, then the standard Lua
- operator `<` is used instead.
+ `table[n]`, where `n` is the length of the table (see |lua-length|).
+ If {comp} is given, then it must be a function that receives two table
+ elements, and returns true when the first is less than the second (so
+ that `not comp(a[i+1],a[i])` will be true after the sort). If {comp}
+ is not given, then the standard Lua operator `<` is used instead.
The sort algorithm is `not` stable, that is, elements considered equal by the
given order may have their relative positions changed by the sort.
==============================================================================
-5.6 Mathematical Functions *luaref-libMath*
+5.6 Mathematical Functions *lua-lib-math*
This library is an interface to most of the functions of the standard C math
library. It provides all its functions inside the table `math`.
@@ -4364,7 +4356,7 @@ math.frexp({x}) *math.frexp()*
absolute value of `m` is in the range `[0.5, 1)` (or zero when {x} is
zero).
-math.huge *math.huge()*
+math.huge *math.huge*
The value `HUGE_VAL`, a value larger than or equal to any other
numerical value.
@@ -4387,7 +4379,7 @@ math.modf({x}) *math.modf()*
Returns two numbers, the integral part of {x} and the fractional part
of {x}.
-math.pi *math.pi()*
+math.pi *math.pi*
The value of `pi`.
math.pow({x}, {y}) *math.pow()*
@@ -4429,7 +4421,7 @@ math.tanh({x}) *math.tanh()*
Returns the hyperbolic tangent of {x}.
==============================================================================
-5.6 Input and Output Facilities *luaref-libIO*
+5.6 Input and Output Facilities *lua-lib-io*
The I/O library provides two different styles for file manipulation. The first
one uses implicit file descriptors; that is, there are operations to set a
@@ -4467,7 +4459,7 @@ io.input([{file}]) *io.input()*
an error code.
io.lines([{filename}]) *io.lines()*
- Opens the given file name in read mode and returns an iterator
+ Opens the given file name in read mode and returns an |iterator|
function that, each time it is called, returns a new line from the
file. Therefore, the construction
@@ -4527,16 +4519,16 @@ io.type({obj}) *io.type()*
io.write({...}) *io.write()*
Equivalent to `io.output():write`.
-file:close() *luaref-file:close()*
+file:close() *file:close()*
Closes `file`. Note that files are automatically closed when their
handles are garbage collected, but that takes an unpredictable amount
of time to happen.
-file:flush() *luaref-file:flush()*
+file:flush() *file:flush()*
Saves any written data to `file`.
-file:lines() *luaref-file:lines()*
- Returns an iterator function that, each time it is called, returns a
+file:lines() *file:lines()*
+ Returns an |iterator| function that, each time it is called, returns a
new line from the file. Therefore, the construction
`for line in file:lines() do` `body` `end`
@@ -4544,7 +4536,7 @@ file:lines() *luaref-file:lines()*
will iterate over all lines of the file. (Unlike `io.lines`, this
function does not close the file when the loop ends.)
-file:read({...}) *luaref-file:read()*
+file:read({...}) *file:read()*
Reads the file `file`, according to the given formats, which specify
what to read. For each format, the function returns a string (or a
number) with the characters read, or `nil` if it cannot read data with
@@ -4563,7 +4555,7 @@ file:read({...}) *luaref-file:read()*
returning `nil` on end of file. If number is zero, it reads
nothing and returns an empty string, or `nil` on end of file.
-file:seek([{whence}] [, {offset}]) *luaref-file:seek()*
+file:seek([{whence}] [, {offset}]) *file:seek()*
Sets and gets the file position, measured from the beginning of the
file, to the position given by {offset} plus a base specified by the
string {whence}, as follows:
@@ -4583,7 +4575,7 @@ file:seek([{whence}] [, {offset}]) *luaref-file:seek()*
`file:seek("end")` sets the position to the end of the file, and
returns its size.
-file:setvbuf({mode} [, {size}]) *luaref-file:setvbuf()*
+file:setvbuf({mode} [, {size}]) *file:setvbuf()*
Sets the buffering mode for an output file. There are three available
modes:
@@ -4599,14 +4591,14 @@ file:setvbuf({mode} [, {size}]) *luaref-file:setvbuf()*
For the last two cases, {size} specifies the size of the buffer, in
bytes. The default is an appropriate size.
-file:write({...}) *luaref-file:write()*
+file:write({...}) *file:write()*
Writes the value of each of its arguments to `file`. The arguments
must be strings or numbers. To write other values, use `tostring`
- |luaref-tostring()| or `string.format` |string.format()| before
+ |tostring()| or `string.format` |string.format()| before
`write`.
==============================================================================
-5.8 Operating System Facilities *luaref-libOS*
+5.8 Operating System Facilities *lua-lib-os*
This library is implemented through table `os`.
@@ -4691,7 +4683,7 @@ os.tmpname() *os.tmpname()*
removed when no longer needed.
==============================================================================
-5.9 The Debug Library *luaref-libDebug*
+5.9 The Debug Library *lua-lib-debug*
This library provides the functionality of the debug interface to Lua
programs. You should exert care when using this library. The functions
@@ -4765,7 +4757,7 @@ debug.getmetatable({object}) *debug.getmetatable()*
have a metatable.
debug.getregistry() *debug.getregistry()*
- Returns the registry table (see |luaref-apiRegistry|).
+ Returns the registry table (see |lua-registry|).
debug.getupvalue({func}, {up}) *debug.getupvalue()*
This function returns the name and the value of the upvalue with index
@@ -4826,7 +4818,7 @@ debug.traceback([{thread},] [{message}] [,{level}]) *debug.traceback()*
(default is 1, the function calling `traceback`).
==============================================================================
-A BIBLIOGRAPHY *luaref-bibliography*
+A BIBLIOGRAPHY *lua-ref-bibliography*
This help file is a minor adaptation from this main reference:
@@ -4852,7 +4844,7 @@ Lua is discussed in these references:
"Proc. of V Brazilian Symposium on Programming Languages" (2001) B-14-B-28.
==============================================================================
-B COPYRIGHT AND LICENSES *luaref-copyright*
+B COPYRIGHT AND LICENSES *lua-ref-copyright*
This help file has the same copyright and license as Lua 5.1 and the Lua 5.1
manual:
@@ -4878,12 +4870,12 @@ copies or substantial portions of the Software.
SOFTWARE.
==============================================================================
-C LUAREF DOC *luarefvim* *luarefvimdoc* *luaref-help* *luaref-doc*
+C LUAREF DOC *lua-ref-doc*
This is a Vim help file containing a reference for Lua 5.1, and it is -- with
a few exceptions and adaptations -- a copy of the Lua 5.1 Reference Manual
-(see |luaref-bibliography|). For usage information, refer to
-|luaref-doc|. For copyright information, see |luaref-copyright|.
+(see |lua-ref-bibliography|). For usage information, refer to
+|lua-ref-doc|. For copyright information, see |lua-ref-copyright|.
The main ideas and concepts on how to implement this reference were taken from
Christian Habermann's CRefVim project
diff --git a/runtime/doc/luvref.txt b/runtime/doc/luvref.txt
index 859e75e4af..915b69efe3 100644
--- a/runtime/doc/luvref.txt
+++ b/runtime/doc/luvref.txt
@@ -3,10 +3,10 @@
LUV REFERENCE MANUAL
- *luvref*
+ *luvref*
This file documents the Lua bindings for the LibUV library which is used for
-Nvim's event-loop and is accessible from Lua via |vim.loop| (e.g., |uv.version()|
-is exposed as `vim.loop.version()`).
+Nvim's event-loop and is accessible from Lua via |vim.uv| (e.g., |uv.version()|
+is exposed as `vim.uv.version()`).
For information about this manual, see |luv-credits|.
@@ -24,12 +24,12 @@ be used in other Lua environments.
More information about the core libuv library can be found at the original
libuv documentation page (https://docs.libuv.org/).
-TCP Echo Server Example~
+TCP Echo Server Example ~
Here is a small example showing a TCP echo server:
>lua
- local uv = vim.loop
+ local uv = vim.uv
local server = uv.new_tcp()
server:bind("127.0.0.1", 1337)
@@ -51,15 +51,15 @@ Here is a small example showing a TCP echo server:
uv.run() -- an explicit run call is necessary outside of luvit
<
-Module Layout~
+Module Layout ~
The luv library contains a single Lua module referred to hereafter as `uv` for
simplicity. This module consists mostly of functions with names corresponding
to their original libuv versions. For example, the libuv function
-`uv_tcp_bind` has a luv version at |uv.tcp_bind()|. Currently, only one
-non-function field exists: `uv.constants`, which is a table.
+`uv_tcp_bind` has a luv version at |uv.tcp_bind()|. Currently, only two
+non-function fields exists: `uv.constants` and `uv.errno`, which are tables.
-Functions vs Methods~
+Functions vs Methods ~
In addition to having simple functions, luv provides an optional method-style
API. For example, `uv.tcp_bind(server, host, port)` can alternatively be
@@ -67,7 +67,7 @@ called as `server:bind(host, port)` . Note that the first argument `server`
becomes the object and `tcp_` is removed from the function name. Method forms
are documented below where they exist.
-Synchronous vs Asynchronous Functions~
+Synchronous vs Asynchronous Functions ~
Functions that accept a callback are asynchronous. These functions may
immediately return results to the caller to indicate their initial status, but
@@ -82,7 +82,7 @@ Some (generally FS and DNS) functions can behave either synchronously or
asynchronously. If a callback is provided to these functions, they behave
asynchronously; if no callback is provided, they behave synchronously.
-Pseudo-Types~
+Pseudo-Types ~
Some unique types are defined. These are not actual types in Lua, but they are
used here to facilitate documenting consistent behavior:
@@ -91,7 +91,7 @@ used here to facilitate documenting consistent behavior:
metamethod
- `buffer`: a `string` or a sequential `table` of `string`s
- `threadargs`: variable arguments (`...`) of type `nil`, `boolean`, `number`,
- `string`, or `userdata`
+ `string`, or `userdata`; number of arguments limited to 9.
==============================================================================
CONTENTS *luv-contents*
@@ -133,10 +133,10 @@ module.
==============================================================================
ERROR HANDLING *luv-error-handling*
-In libuv, errors are negative numbered constants; however, these errors and
-the functions used to handle them are not exposed to luv users. Instead, if an
-internal error is encountered, the luv function will return to the caller an
-assertable `nil, err, name` tuple.
+In libuv, errors are negative numbered constants; however, while those errors
+are exposed through `uv.errno`, the functions used to handle them are not
+exposed to luv users. Instead, if an internal error is encountered, the luv
+function will return to the caller an assertable `nil, err, name` tuple.
- `nil` idiomatically indicates failure
- `err` is a string with the format `{name}: {message}`
@@ -151,6 +151,94 @@ When a function is called successfully, it will return either a value that is
relevant to the operation of the function, or the integer `0` to indicate
success, or sometimes nothing at all. These cases are documented below.
+`uv.errno` *uv.errno*
+
+A table value which exposes error constants as a map, where the key is the
+error name (without the `UV_` prefix) and its value is a negative number.
+See Libuv's "Error constants" page for further details.
+(https://docs.libuv.org/en/v1.x/errors.html#error-constants)
+
+- `E2BIG`: argument list too long.
+- `EACCES`: permission denied.
+- `EADDRINUSE`: address already in use.
+- `EADDRNOTAVAIL`: address not available.
+- `EAFNOSUPPORT`: address family not supported.
+- `EAGAIN`: resource temporarily unavailable.
+- `EAI_ADDRFAMILY`: address family not supported.
+- `EAI_AGAIN`: temporary failure.
+- `EAI_BADFLAGS`: bad ai_flags value.
+- `EAI_BADHINTS`: invalid value for hints.
+- `EAI_CANCELED`: request canceled.
+- `EAI_FAIL`: permanent failure.
+- `EAI_FAMILY`: ai_family not supported.
+- `EAI_MEMORY`: out of memory.
+- `EAI_NODATA`: no address.
+- `EAI_NONAME`: unknown node or service.
+- `EAI_OVERFLOW`: argument buffer overflow.
+- `EAI_PROTOCOL`: resolved protocol is unknown.
+- `EAI_SERVICE`: service not available for socket type.
+- `EAI_SOCKTYPE`: socket type not supported.
+- `EALREADY`: connection already in progress.
+- `EBADF`: bad file descriptor.
+- `EBUSY`: resource busy or locked.
+- `ECANCELED`: operation canceled.
+- `ECHARSET`: invalid Unicode character.
+- `ECONNABORTED`: software caused connection abort.
+- `ECONNREFUSED`: connection refused.
+- `ECONNRESET`: connection reset by peer.
+- `EDESTADDRREQ`: destination address required.
+- `EEXIST`: file already exists.
+- `EFAULT`: bad address in system call argument.
+- `EFBIG`: file too large.
+- `EHOSTUNREACH`: host is unreachable.
+- `EINTR`: interrupted system call.
+- `EINVAL`: invalid argument.
+- `EIO`: i/o error.
+- `EISCONN`: socket is already connected.
+- `EISDIR`: illegal operation on a directory.
+- `ELOOP`: too many symbolic links encountered.
+- `EMFILE`: too many open files.
+- `EMSGSIZE`: message too long.
+- `ENAMETOOLONG`: name too long.
+- `ENETDOWN`: network is down.
+- `ENETUNREACH`: network is unreachable.
+- `ENFILE`: file table overflow.
+- `ENOBUFS`: no buffer space available.
+- `ENODEV`: no such device.
+- `ENOENT`: no such file or directory.
+- `ENOMEM`: not enough memory.
+- `ENONET`: machine is not on the network.
+- `ENOPROTOOPT`: protocol not available.
+- `ENOSPC`: no space left on device.
+- `ENOSYS`: function not implemented.
+- `ENOTCONN`: socket is not connected.
+- `ENOTDIR`: not a directory.
+- `ENOTEMPTY`: directory not empty.
+- `ENOTSOCK`: socket operation on non-socket.
+- `ENOTSUP`: operation not supported on socket.
+- `EOVERFLOW`: value too large for defined data type.
+- `EPERM`: operation not permitted.
+- `EPIPE`: broken pipe.
+- `EPROTO`: protocol error.
+- `EPROTONOSUPPORT`: protocol not supported.
+- `EPROTOTYPE`: protocol wrong type for socket.
+- `ERANGE`: result too large.
+- `EROFS`: read-only file system.
+- `ESHUTDOWN`: cannot send after transport endpoint shutdown.
+- `ESPIPE`: invalid seek.
+- `ESRCH`: no such process.
+- `ETIMEDOUT`: connection timed out.
+- `ETXTBSY`: text file is busy.
+- `EXDEV`: cross-device link not permitted.
+- `UNKNOWN`: unknown error.
+- `EOF`: end of file.
+- `ENXIO`: no such device or address.
+- `EMLINK`: too many links.
+- `ENOTTY`: inappropriate ioctl for device.
+- `EFTYPE`: inappropriate file type or format.
+- `EILSEQ`: illegal byte sequence.
+- `ESOCKTNOSUPPORT`: socket type not supported.
+
==============================================================================
VERSION CHECKING *luv-version-checking*
@@ -1213,11 +1301,11 @@ uv.spawn({path}, {options}, {on_exit}) *uv.spawn()*
The `options` table accepts the following fields:
- `options.args` - Command line arguments as a list of
- string. The first string should be the path to the
- program. On Windows, this uses CreateProcess which
- concatenates the arguments into a string. This can cause
- some strange errors. (See `options.verbatim` below for
- Windows.)
+ strings. The first string should not be the path to the
+ program, since that is already provided via `path`. On
+ Windows, this uses CreateProcess which concatenates the
+ arguments into a string. This can cause some strange
+ errors (see `options.verbatim` below for Windows).
- `options.stdio` - Set the file descriptors that will be
made available to the child process. The convention is
that the first entries are stdin, stdout, and stderr.
@@ -1267,7 +1355,7 @@ uv.process_kill({process}, {signum}) *uv.process_kill()*
Parameters:
- `process`: `uv_process_t userdata`
- - `signum`: `integer` or `string`
+ - `signum`: `integer` or `string` or `nil` (default: `sigterm`)
Sends the specified signal to the given process handle. Check
the documentation on |uv_signal_t| for signal support,
@@ -1279,7 +1367,7 @@ uv.kill({pid}, {signum}) *uv.kill()*
Parameters:
- `pid`: `integer`
- - `signum`: `integer` or `string`
+ - `signum`: `integer` or `string` or `nil` (default: `sigterm`)
Sends the specified signal to the given PID. Check the
documentation on |uv_signal_t| for signal support, specially
@@ -1976,6 +2064,69 @@ uv.pipe({read_flags}, {write_flags}) *uv.pipe()*
end)
<
+uv.pipe_bind2({pipe}, {name}, {flags}) *uv.pipe_bind2()*
+
+ > method form `pipe:pipe_bind(name, flags)`
+
+ Parameters:
+ - `pipe`: `uv_pipe_t userdata`
+ - `name`: `string`
+ - `flags`: `integer` or `table` or `nil`(default: 0)
+
+ Flags:
+ - If `type(flags)` is `number`, it must be `0` or
+ `uv.constants.PIPE_NO_TRUNCATE`.
+ - If `type(flags)` is `table`, it must be `{}` or
+ `{ no_trunate = true|false }`.
+ - If `type(flags)` is `nil`, it use default value `0`.
+ - Returns `EINVAL` for unsupported flags without performing the
+ bind.
+
+ Bind the pipe to a file path (Unix) or a name (Windows).
+
+ Supports Linux abstract namespace sockets. namelen must include
+ the leading '\0' byte but not the trailing nul byte.
+
+ Returns: `0` or `fail`
+
+ *Note*:
+ 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path)
+ bytes, typically between 92 and 108 bytes.
+ 2. New in version 1.46.0.
+
+uv.pipe_connect2(pipe, name, [flags], [callback]) *uv.pipe_connect2()*
+
+ > method form `pipe:connect2(name, [flags], [callback])`
+
+ Parameters:
+ - `pipe`: `uv_pipe_t userdata`
+ - `name`: `string`
+ - `flags`: `integer` or `table` or `nil`(default: 0)
+ - `callback`: `callable` or `nil`
+ - `err`: `nil` or `string`
+
+ `Flags`:
+
+ - If `type(flags)` is `number`, it must be `0` or
+ `uv.constants.PIPE_NO_TRUNCATE`.
+ - If `type(flags)` is `table`, it must be `{}` or
+ `{ no_trunate = true|false }`.
+ - If `type(flags)` is `nil`, it use default value `0`.
+ - Returns `EINVAL` for unsupported flags without performing the
+ bind operation.
+
+ Connect to the Unix domain socket or the named pipe.
+
+ Supports Linux abstract namespace sockets. namelen must include
+ the leading nul byte but not the trailing nul byte.
+
+ Returns: `uv_connect_t userdata` or `fail`
+
+ *Note*:
+ 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path)
+ bytes, typically between 92 and 108 bytes.
+ 2. New in version 1.46.0.
+
==============================================================================
`uv_tty_t` — TTY handle *luv-tty-handle* *uv_tty_t*
@@ -2488,7 +2639,7 @@ uv.fs_poll_start({fs_poll}, {path}, {interval}, {callback}) *uv.fs_poll_start()*
> method form `fs_poll:start(path, interval, callback)`
Parameters:
- - `fs_event`: `uv_fs_event_t userdata`
+ - `fs_poll`: `uv_fs_poll_t userdata`
- `path`: `string`
- `interval`: `integer`
- `callback`: `callable`
@@ -2829,7 +2980,7 @@ uv.fs_fstat({fd} [, {callback}]) *uv.fs_fstat()*
uv.fs_lstat({path} [, {callback}]) *uv.fs_lstat()*
Parameters:
- - `fd`: `integer`
+ - `path`: `string`
- `callback`: `callable` (async version) or `nil` (sync
version)
- `err`: `nil` or `string`
@@ -3272,14 +3423,17 @@ file system operations, as well as `getaddrinfo` and `getnameinfo` requests.
uv.new_work({work_callback}, {after_work_callback}) *uv.new_work()*
Parameters:
- - `work_callback`: `function`
+ - `work_callback`: `function` or `string`
- `...`: `threadargs` passed to/from
`uv.queue_work(work_ctx, ...)`
- `after_work_callback`: `function`
- `...`: `threadargs` returned from `work_callback`
Creates and initializes a new `luv_work_ctx_t` (not
- `uv_work_t`). Returns the Lua userdata wrapping it.
+ `uv_work_t`).
+ `work_callback` is a Lua function or a string containing Lua
+ code or bytecode dumped from a function. Returns the Lua
+ userdata wrapping it.
Returns: `luv_work_ctx_t userdata`
@@ -3380,19 +3534,22 @@ uv.new_thread([{options}, ] {entry}, {...}) *uv.new_thread()*
Parameters:
- `options`: `table` or `nil`
- `stack_size`: `integer` or `nil`
- - `entry`: `function`
+ - `entry`: `function` or `string`
- `...`: `threadargs` passed to `entry`
Creates and initializes a `luv_thread_t` (not `uv_thread_t`).
Returns the Lua userdata wrapping it and asynchronously
- executes `entry`, which can be either a Lua function or a Lua
- function dumped to a string. Additional arguments `...` are
- passed to the `entry` function and an optional `options` table
- may be provided. Currently accepted `option` fields are
- `stack_size`.
+ executes `entry`, which can be either a Lua function or a
+ string containing Lua code or bytecode dumped from a function.
+ Additional arguments `...` are passed to the `entry` function
+ and an optional `options` table may be provided. Currently
+ accepted `option` fields are `stack_size`.
Returns: `luv_thread_t userdata` or `fail`
+ Note: unsafe, please make sure that the thread's end of life
+ is before Lua state is closed.
+
uv.thread_equal({thread}, {other_thread}) *uv.thread_equal()*
> method form `thread:equal(other_thread)`
@@ -3405,6 +3562,73 @@ uv.thread_equal({thread}, {other_thread}) *uv.thread_equal()*
This function is equivalent to the `__eq` metamethod.
Returns: `boolean`
+ *uv.thread_setaffinity()*
+uv.thread_setaffinity({thread}, {affinity} [, {get_old_affinity}])
+
+ > method form `thread:setaffinity(affinity, [get_old_affinity])`
+
+ Parameters:
+ - `thread`: `luv_thread_t userdata`
+ - `affinity`: `table`
+ - `[1, 2, 3, ..., n]` : `boolean`
+ - `get_old_affinity`: `boolean`
+
+ Sets the specified thread's affinity setting.
+
+ `affinity` must be a table where each of the keys are a CPU
+ number and the values are booleans that represent whether the
+ `thread` should be eligible to run on that CPU. If the length
+ of the `affinity` table is not greater than or equal to
+ |uv.cpumask_size()|, any CPU numbers missing from the table
+ will have their affinity set to `false`. If setting the
+ affinity of more than |uv.cpumask_size()| CPUs is desired,
+ `affinity` must be an array-like table with no gaps, since
+ `#affinity` will be used as the `cpumask_size` if it is
+ greater than |uv.cpumask_size()|.
+
+ If `get_old_affinity` is `true`, the previous affinity
+ settings for the `thread` will be returned. Otherwise, `true`
+ is returned after a successful call.
+
+ Note: Thread affinity setting is not atomic on Windows.
+ Unsupported on macOS.
+
+ Returns: `table` or `boolean` or `fail`
+ - `[1, 2, 3, ..., n]` : `boolean`
+
+
+uv.thread_getaffinity({thread} [, {mask_size}]) *uv.thread_getaffinity()*
+
+ > method form `thread:getaffinity([mask_size])`
+
+ Parameters:
+ - `thread`: `luv_thread_t userdata`
+ - `mask_size`: `integer`
+
+ Gets the specified thread's affinity setting.
+
+ If `mask_size` is provided, it must be greater than or equal
+ to `uv.cpumask_size()`. If the `mask_size` parameter is
+ omitted, then the return of `uv.cpumask_size()` will be used.
+ Returns an array-like table where each of the keys correspond
+ to a CPU number and the values are booleans that represent
+ whether the `thread` is eligible to run on that CPU.
+
+ Note: Thread affinity getting is not atomic on Windows.
+ Unsupported on macOS.
+
+ Returns: `table` or `fail`
+ - `[1, 2, 3, ..., n]` : `boolean`
+
+uv.thread_getcpu() *uv.thread_getcpu()*
+
+ Gets the CPU number on which the calling thread is running.
+
+ Note: The first CPU will be returned as the number 1, not 0.
+ This allows for the number to correspond with the table keys
+ used in `uv.thread_getaffinity` and `uv.thread_setaffinity`.
+
+ Returns: `integer` or `fail`
uv.thread_self() *uv.thread_self()*
@@ -3494,6 +3718,16 @@ uv.get_constrained_memory() *uv.get_constrained_memory()*
Returns: `number`
+uv.get_available_memory() *uv.get_available_memory()*
+
+ Gets the amount of free memory that is still available to the
+ process (in bytes). This differs from `uv.get_free_memory()`
+ in that it takes into account any limits imposed by the OS. If
+ there is no such constraint, or the constraint is unknown, the
+ amount returned will be identical to `uv.get_free_memory()`.
+
+ Returns: `number`
+
uv.resident_set_memory() *uv.resident_set_memory()*
Returns the resident set size (RSS) for the current process.
@@ -3558,6 +3792,14 @@ uv.cpu_info() *uv.cpu_info()*
- `idle` : `number`
- `irq` : `number`
+uv.cpumask_size() *uv.cpumask_size()*
+
+ Returns the maximum size of the mask used for process/thread
+ affinities, or `ENOTSUP` if affinities are not supported on
+ the current platform.
+
+ Returns: `integer` or `fail`
+
uv.getpid() *uv.getpid()*
DEPRECATED: Please use |uv.os_getpid()| instead.
@@ -3614,6 +3856,25 @@ uv.hrtime() *uv.hrtime()*
Returns: `number`
+uv.clock_gettime({clock_id}) *uv.clock_gettime()*
+
+ Parameters:
+ - `clock_id`: `string`
+
+ Obtain the current system time from a high-resolution
+ real-time or monotonic clock source. `clock_id` can be the
+ string `"monotonic"` or `"realtime"`.
+
+ The real-time clock counts from the UNIX epoch (1970-01-01)
+ and is subject to time adjustments; it can jump back in time.
+
+ The monotonic clock counts from an arbitrary point in the past
+ and never jumps back in time.
+
+ Returns: `table` or `fail`
+ - `sec`: `integer`
+ - `nsec`: `integer`
+
uv.uptime() *uv.uptime()*
Returns the current system uptime in seconds.
@@ -3752,7 +4013,12 @@ uv.os_setenv({name}, {value}) *uv.os_setenv()*
WARNING: This function is not thread safe.
-uv.os_unsetenv() *uv.os_unsetenv()*
+uv.os_unsetenv({name}) *uv.os_unsetenv()*
+
+ Parameters:
+ - `name`: `string`
+
+ Unsets the environmental variable specified by `name`.
Returns: `boolean` or `fail`
@@ -3885,14 +4151,28 @@ uv.metrics_idle_time() *uv.metrics_idle_time()*
Returns: `number`
+uv.metrics_info() *uv.metrics_info()*
+
+ Get the metrics table from current set of event loop metrics.
+ It is recommended to retrieve these metrics in a `prepare`
+ callback (see |uv.new_prepare()|, |uv.prepare_start()|) in order
+ to make sure there are no inconsistencies with the metrics
+ counters.
+
+ Returns: `table`
+
+ - `loop_count` : `integer`
+ - `events` : `integer`
+ - `events_waiting` : `integer`
+
==============================================================================
CREDITS *luv-credits*
-This document is a reformatted version of the LUV documentation, based on
-commit c51e705 (5 May 2022) of the luv repository
-https://github.com/luvit/luv/commit/c51e7052ec4f0a25058f70c1b4ee99dd36180e59.
+This document is a reformatted version of the LUV documentation, up-to-date
+with commit dcd1a1c (23 Aug 2023) of the luv repository
+https://github.com/luvit/luv/commit/dcd1a1cad5b05634a7691402d6ca2f214fb4ae76.
-Included from https://github.com/nanotee/luv-vimdocs with kind permission.
+Based on https://github.com/nanotee/luv-vimdocs with kind permission.
vim:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt
index cb8b162eb6..6f61259af0 100644
--- a/runtime/doc/map.txt
+++ b/runtime/doc/map.txt
@@ -290,7 +290,7 @@ Therefore the following is blocked for <expr> mappings:
- Moving the cursor is allowed, but it is restored afterwards.
- If the cmdline is changed, the old text and cursor position are restored.
If you want the mapping to do any of these let the returned characters do
-that. (Or use a |<Cmd>| mapping instead.)
+that, or use a |<Cmd>| mapping instead.
You can use getchar(), it consumes typeahead if there is any. E.g., if you
have these mappings: >
@@ -324,20 +324,22 @@ be seen as a special key.
*<Cmd>* *:map-cmd*
The <Cmd> pseudokey begins a "command mapping", which executes the command
-directly (without changing modes). Where you might use ":...<CR>" in the
+directly without changing modes. Where you might use ":...<CR>" in the
{rhs} of a mapping, you can instead use "<Cmd>...<CR>".
Example: >
- noremap x <Cmd>echo mode(1)<cr>
+ noremap x <Cmd>echo mode(1)<CR>
<
-This is more flexible than `:<C-U>` in visual and operator-pending mode, or
-`<C-O>:` in insert-mode, because the commands are executed directly in the
-current mode (instead of always going to normal-mode). Visual-mode is
+This is more flexible than `:<C-U>` in Visual and Operator-pending mode, or
+`<C-O>:` in Insert mode, because the commands are executed directly in the
+current mode, instead of always going to Normal mode. Visual mode is
preserved, so tricks with |gv| are not needed. Commands can be invoked
-directly in cmdline-mode (which would otherwise require timer hacks).
+directly in Command-line mode (which would otherwise require timer hacks).
+Example of using <Cmd> halfway Insert mode: >
+ nnoremap <F3> aText <Cmd>echo mode(1)<CR> Added<Esc>
Unlike <expr> mappings, there are no special restrictions on the <Cmd>
command: it is executed as if an (unrestricted) |autocommand| was invoked
-or an async event event was processed.
+or an async event was processed.
Note:
- Because <Cmd> avoids mode-changes (unlike ":") it does not trigger
@@ -347,10 +349,10 @@ Note:
- The command is not echo'ed, no need for <silent>.
- The {rhs} is not subject to abbreviations nor to other mappings, even if the
mapping is recursive.
-- In Visual mode you can use `line('v')` and `col('v')` to get one end of the
+- In Visual mode you can use `line('v')` and `col('v')` to get one end of the
Visual area, the cursor is at the other end.
- *E5520*
+ *E1255* *E1136*
<Cmd> commands must terminate, that is, they must be followed by <CR> in the
{rhs} of the mapping definition. |Command-line| mode is never entered.
@@ -540,28 +542,10 @@ See |:verbose-cmd| for more information.
1.5 MAPPING SPECIAL KEYS *:map-special-keys*
-There are two ways to map a special key:
-1. The Vi-compatible method: Map the key code. Often this is a sequence that
- starts with <Esc>. To enter a mapping like this you type ":map " and then
- you have to type CTRL-V before hitting the function key. Note that when
- the key code for the key is in the |terminfo| entry, it will automatically
- be translated into the internal code and become the second way of mapping.
-2. The second method is to use the internal code for the function key. To
- enter such a mapping type CTRL-K and then hit the function key, or use
- the form "#1", "#2", .. "#9", "#0", "<Up>", "<S-Down>", "<S-F7>", etc.
- (see table of keys |key-notation|, all keys from <Up> can be used). The
- first ten function keys can be defined in two ways: Just the number, like
- "#2", and with "<F>", like "<F2>". Both stand for function key 2. "#0"
- refers to function key 10.
-
-DETAIL: Vim first checks if a sequence from the keyboard is mapped. If it
-isn't the terminal key codes are tried. If a terminal code is found it is
-replaced with the internal code. Then the check for a mapping is done again
-(so you can map an internal code to something else). What is written into the
-script file depends on what is recognized. If the terminal key code was
-recognized as a mapping the key code itself is written to the script file. If
-it was recognized as a terminal code the internal code is written to the
-script file.
+To map a function key, use the internal code for it. To enter such a mapping
+type CTRL-K and then hit the function key, or use the form "<F2>", "<F10>",
+"<Up>", "<S-Down>", "<S-F7>", etc. (see table of keys |key-notation|, all keys
+from <Up> can be used).
1.6 SPECIAL CHARACTERS *:map-special-chars*
@@ -654,6 +638,7 @@ not to be matched with any key sequence. This is useful in plugins
*<MouseMove>*
The special key name "<MouseMove>" can be used to handle mouse movement. It
needs to be enabled with 'mousemoveevent'.
+The |getmousepos()| function can be used to obtain the mouse position.
*<Char>* *<Char->*
To map a character by its decimal, octal or hexadecimal number the <Char>
@@ -695,8 +680,7 @@ this (see |<>|). Example: >
:map _ls :!ls -l %:S<CR>:echo "the end"<CR>
To avoid mapping of the characters you type in insert or Command-line mode,
-type a CTRL-V first. The mapping in Insert mode is disabled if the 'paste'
-option is on.
+type a CTRL-V first.
*map-error*
Note that when an error is encountered (that causes an error message or might
cause a beep) the rest of the mapping is not executed. This is Vi-compatible.
@@ -764,7 +748,7 @@ option). After that it assumes that the 'q' is to be interpreted as such. If
you type slowly, or your system is slow, reset the 'timeout' option. Then you
might want to set the 'ttimeout' option.
- *map-precedence*
+ *map-precedence*
Buffer-local mappings (defined using |:map-<buffer>|) take precedence over
global mappings. When a buffer-local mapping is the same as a global mapping,
Vim will use the buffer-local mapping. In addition, Vim will use a complete
@@ -838,6 +822,11 @@ in the original Vi, you would get back the text before the first undo).
1.10 MAPPING ALT-KEYS *:map-alt-keys*
+For a readable mapping command the <A-k> form can be used. Note that <A-k>
+and <A-K> are different, the latter will use an upper case letter. Actually,
+<A-K> and <A-S-K> are the same. Instead of "A" you can use "M". If you have
+an actual Meta modifier key, please see |:map-meta-keys|.
+
In the GUI Nvim handles the |ALT| key itself, thus mapping keys with ALT
should always work. But in a terminal Nvim gets a sequence of bytes and has
to figure out whether ALT was pressed. Terminals may use ESC to indicate that
@@ -847,7 +836,21 @@ milliseconds, the ESC is interpreted as:
otherwise it is interpreted as two key presses:
<ESC> {key}
-1.11 MAPPING AN OPERATOR *:map-operator*
+1.11 MAPPING META-KEYS *:map-meta-keys*
+
+Mapping keys with the Meta modifier works very similar to using the Alt key.
+What key on your keyboard produces the Meta modifier depends on your keyboard
+and configuration.
+
+Note that mapping <M-a> actually is for using the Alt key. That can be
+confusing! It cannot be changed, it would not be backwards compatible.
+
+For the Meta modifier the "T" character is used. For example, to map Meta-b
+in Insert mode: >
+ :imap <T-b> terrible
+
+
+1.12 MAPPING AN OPERATOR *:map-operator*
An operator is used before a {motion} command. To define your own operator
you must create a mapping that first sets the 'operatorfunc' option and then
@@ -952,7 +955,7 @@ operator to add quotes around text in the current line: >
\ ->setline(".")}'<CR>g@
==============================================================================
-2. Abbreviations *abbreviations* *Abbreviations*
+2. Abbreviations *abbreviation* *abbreviations* *Abbreviations*
Abbreviations are used in Insert mode, Replace mode and Command-line mode.
If you enter a word that is an abbreviation, it is replaced with the word it
@@ -984,7 +987,7 @@ non-id The "non-id" type ends in a non-keyword character, the other
Examples of strings that cannot be abbreviations: "a.b", "#def", "a b", "_$r"
An abbreviation is only recognized when you type a non-keyword character.
-This can also be the <Esc> that ends insert mode or the <CR> that ends a
+This can also be the <Esc> that ends Insert mode or the <CR> that ends a
command. The non-keyword character which ends the abbreviation is inserted
after the expanded abbreviation. An exception to this is the character <C-]>,
which is used to expand an abbreviation without inserting any extra
@@ -1048,10 +1051,7 @@ typed after an abbreviation: >
There are no default abbreviations.
Abbreviations are never recursive. You can use ":ab f f-o-o" without any
-problem. But abbreviations can be mapped. {some versions of Vi support
-recursive abbreviations, for no apparent reason}
-
-Abbreviations are disabled if the 'paste' option is on.
+problem. But abbreviations can be mapped.
*:abbreviate-local* *:abbreviate-<buffer>*
Just like mappings, abbreviations can be local to a buffer. This is mostly
@@ -1186,12 +1186,14 @@ functions used in one script use the same name as in other scripts. To avoid
this, they can be made local to the script.
*<SID>* *<SNR>* *E81*
-The string "<SID>" can be used in a mapping or menu.
+The string "<SID>" can be used in a mapping or menu. This is useful if you
+have a script-local function that you want to call from a mapping in the same
+script.
When executing the map command, Vim will replace "<SID>" with the special
key code <SNR>, followed by a number that's unique for the script, and an
underscore. Example: >
:map <SID>Add
-could define a mapping "<SNR>23_Add".
+would define a mapping "<SNR>23_Add".
When defining a function in a script, "s:" can be prepended to the name to
make it local to the script. But when a mapping is executed from outside of
@@ -1322,6 +1324,11 @@ can have arguments, or have a range specified. Arguments are subject to
completion as filenames, buffers, etc. Exactly how this works depends upon the
command's attributes, which are specified when the command is defined.
+When defining a user command in a script, it will be able to call functions
+local to the script and use mappings local to the script. When the user
+invokes the user command, it will run in the context of the script it was
+defined in. This matters if |<SID>| is used in a command.
+
There are a number of attributes, split into four categories: argument
handling, completion behavior, range handling, and special cases. The
attributes are described below, by category.
@@ -1383,7 +1390,7 @@ completion can be enabled:
-complete=highlight highlight groups
-complete=history :history suboptions
-complete=locale locale names (as output of locale -a)
- -complete=lua Lua expression
+ -complete=lua Lua expression |:lua|
-complete=mapclear buffer argument
-complete=mapping mapping name
-complete=menu menus
@@ -1484,7 +1491,7 @@ which by default correspond to the current line, last line and the whole
buffer, relate to arguments, (loaded) buffers, windows or tab pages.
Possible values are (second column is the short name used in listing):
- -addr=lines Range of lines (this is the default for -range)
+ -addr=lines Range of lines (this is the default for -range)
-addr=arguments arg Range for arguments
-addr=buffers buf Range for buffers (also not loaded buffers)
-addr=loaded_buffers load Range for loaded buffers
@@ -1538,6 +1545,7 @@ supports incremental command preview:
-- If invoked as a preview callback, performs 'inccommand' preview by
-- highlighting trailing whitespace in the current buffer.
local function trim_space_preview(opts, preview_ns, preview_buf)
+ vim.cmd('hi clear Whitespace')
local line1 = opts.line1
local line2 = opts.line2
local buf = vim.api.nvim_get_current_buf()
@@ -1722,7 +1730,11 @@ remains unmodified. Also see |f-args-example| below. Overview:
XX a\\\ b 'a\ b'
XX a\\\\b 'a\\b'
XX a\\\\ b 'a\\', 'b'
+ XX [nothing]
+
+Note that if the "no arguments" situation is to be handled, you have to make
+sure that the function can be called without arguments.
Examples for user commands: >
@@ -1770,9 +1782,5 @@ errors and the "update" command to write modified buffers): >
This will invoke: >
:call Allargs("%s/foo/bar/ge|update")
<
-When defining a user command in a script, it will be able to call functions
-local to the script and use mappings local to the script. When the user
-invokes the user command, it will run in the context of the script it was
-defined in. This matters if |<SID>| is used in a command.
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt
index 99dfa54218..0a7e0baad3 100644
--- a/runtime/doc/mbyte.txt
+++ b/runtime/doc/mbyte.txt
@@ -358,150 +358,6 @@ conversion needs to be done. These conversions are supported:
Try getting another iconv() implementation.
==============================================================================
-Input on X11 *mbyte-XIM*
-
-X INPUT METHOD (XIM) BACKGROUND *XIM* *xim* *x-input-method*
-
-XIM is an international input module for X. There are two kinds of structures,
-Xlib unit type and |IM-server| (Input-Method server) type. |IM-server| type
-is suitable for complex input, such as CJK.
-
-- IM-server
- *IM-server*
- In |IM-server| type input structures, the input event is handled by either
- of the two ways: FrontEnd system and BackEnd system. In the FrontEnd
- system, input events are snatched by the |IM-server| first, then |IM-server|
- give the application the result of input. On the other hand, the BackEnd
- system works reverse order. MS-Windows adopt BackEnd system. In X, most of
- |IM-server|s adopt FrontEnd system. The demerit of BackEnd system is the
- large overhead in communication, but it provides safe synchronization with
- no restrictions on applications.
-
-- Conversion Server
- *conversion-server*
- Some system needs additional server: conversion server. Most of Japanese
- |IM-server|s need it, Kana-Kanji conversion server. For Chinese inputting,
- it depends on the method of inputting, in some methods, PinYin or ZhuYin to
- HanZi conversion server is needed. For Korean inputting, if you want to
- input Hanja, Hangul-Hanja conversion server is needed.
-
- For example, the Japanese inputting process is divided into 2 steps. First
- we pre-input Hira-gana, second Kana-Kanji conversion. There are so many
- Kanji characters (6349 Kanji characters are defined in JIS X 0208) and the
- number of Hira-gana characters are 76. So, first, we pre-input text as
- pronounced in Hira-gana, second, we convert Hira-gana to Kanji or Kata-Kana,
- if needed. There are some Kana-Kanji conversion server: jserver
- (distributed with Wnn, see below) and canna. Canna can be found at:
- http://canna.sourceforge.jp/
-
-There is a good input system: Wnn4.2. Wnn 4.2 contains,
- xwnmo (|IM-server|)
- jserver (Japanese Kana-Kanji conversion server)
- cserver (Chinese PinYin or ZhuYin to simplified HanZi conversion server)
- tserver (Chinese PinYin or ZhuYin to traditional HanZi conversion server)
- kserver (Hangul-Hanja conversion server)
-Wnn 4.2 for several systems can be found at various places on the internet.
-Use the RPM or port for your system.
-
-
-- Input Style
- *xim-input-style*
- When inputting CJK, there are four areas:
- 1. The area to display of the input while it is being composed
- 2. The area to display the currently active input mode.
- 3. The area to display the next candidate for the selection.
- 4. The area to display other tools.
-
- The third area is needed when converting. For example, in Japanese
- inputting, multiple Kanji characters could have the same pronunciation, so
- a sequence of Hira-gana characters could map to a distinct sequence of Kanji
- characters.
-
- The first and second areas are defined in international input of X with the
- names of "Preedit Area", "Status Area" respectively. The third and fourth
- areas are not defined and are left to be managed by the |IM-server|. In the
- international input, four input styles have been defined using combinations
- of Preedit Area and Status Area: |OnTheSpot|, |OffTheSpot|, |OverTheSpot|
- and |Root|.
-
- Currently, GUI Vim supports three styles, |OverTheSpot|, |OffTheSpot| and
- |Root|.
-
-*. on-the-spot *OnTheSpot*
- Preedit Area and Status Area are performed by the client application in
- the area of application. The client application is directed by the
- |IM-server| to display all pre-edit data at the location of text
- insertion. The client registers callbacks invoked by the input method
- during pre-editing.
-*. over-the-spot *OverTheSpot*
- Status Area is created in a fixed position within the area of application,
- in case of Vim, the position is the additional status line. Preedit Area
- is made at present input position of application. The input method
- displays pre-edit data in a window which it brings up directly over the
- text insertion position.
-*. off-the-spot *OffTheSpot*
- Preedit Area and Status Area are performed in the area of application, in
- case of Vim, the area is additional status line. The client application
- provides display windows for the pre-edit data to the input method which
- displays into them directly.
-*. root-window *Root*
- Preedit Area and Status Area are outside of the application. The input
- method displays all pre-edit data in a separate area of the screen in a
- window specific to the input method.
-
-
-USING XIM *multibyte-input* *E284* *E285* *E286* *E287*
- *E288* *E289*
-
-Note that Display and Input are independent. It is possible to see your
-language even though you have no input method for it. But when your Display
-method doesn't match your Input method, the text will be displayed wrong.
-
-To input your language you should run the |IM-server| which supports your
-language and |conversion-server| if needed.
-
-The next 3 lines should be put in your ~/.Xdefaults file. They are common for
-all X applications which uses |XIM|. If you already use |XIM|, you can skip
-this. >
-
- *international: True
- *.inputMethod: your_input_server_name
- *.preeditType: your_input_style
-<
-input_server_name is your |IM-server| name (check your |IM-server|
- manual).
-your_input_style is one of |OverTheSpot|, |OffTheSpot|, |Root|. See
- also |xim-input-style|.
-
-*international may not be necessary if you use X11R6.
-*.inputMethod and *.preeditType are optional if you use X11R6.
-
-For example, when you are using kinput2 as |IM-server|, >
-
- *international: True
- *.inputMethod: kinput2
- *.preeditType: OverTheSpot
-<
-When using |OverTheSpot|, GUI Vim always connects to the IM Server even in
-Normal mode, so you can input your language with commands like "f" and "r".
-But when using one of the other two methods, GUI Vim connects to the IM Server
-only if it is not in Normal mode.
-
-If your IM Server does not support |OverTheSpot|, and if you want to use your
-language with some Normal mode command like "f" or "r", then you should use a
-localized xterm or an xterm which supports |XIM|
-
-If needed, you can set the XMODIFIERS environment variable:
-
- sh: export XMODIFIERS="@im=input_server_name"
- csh: setenv XMODIFIERS "@im=input_server_name"
-
-For example, when you are using kinput2 as |IM-server| and sh, >
-
- export XMODIFIERS="@im=kinput2"
-<
-
-==============================================================================
Input with a keymap *mbyte-keymap*
When the keyboard doesn't produce the characters you want to enter in your
@@ -790,7 +646,8 @@ widespread as file format.
A composing or combining character is used to change the meaning of the
character before it. The combining characters are drawn on top of the
preceding character.
-Up to six combining characters can be displayed.
+Too big combined characters cannot be displayed, but they can still be
+inspected using the |g8| and |ga| commands described below.
When editing text a composing character is mostly considered part of the
preceding character. For example "x" will delete a character and its
following composing characters by default.
diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt
index dffdb5950f..9f06e8c931 100644
--- a/runtime/doc/message.txt
+++ b/runtime/doc/message.txt
@@ -127,6 +127,8 @@ This happens when an Ex command executes an Ex command that executes an Ex
command, etc. The limit is 200 or the value of 'maxfuncdepth', whatever is
larger. When it's more there probably is an endless loop. Probably a
|:execute| or |:source| command is involved.
+Can also happen with a recursive callback function (|channel-callback|).
+A limit of 20 is used here.
*E254* >
Cannot allocate color {name}
@@ -327,7 +329,7 @@ You can switch the 'write' option on with ":set write".
*E25* >
Nvim does not have a built-in GUI
-Neovim does not have a built in GUI, so `:gvim` and `:gui` don't work.
+Nvim does not have a built in GUI, so `:gvim` and `:gui` don't work.
*E49* >
Invalid scroll size
@@ -820,13 +822,13 @@ Type effect ~
the clipboard ("* and "+ registers)
{menu-entry} what the menu is defined to in
Cmdline-mode.
- <LeftMouse> next page (*)
+ <LeftMouse> next page*
Any other key causes the meaning of the keys to be displayed.
-(*) Clicking the left mouse button only works:
- - For the GUI: in the last line of the screen.
- - When 'r' is included in 'mouse' (but then selecting text won't work).
+* Clicking the left mouse button only works:
+ - For the GUI: in the last line of the screen.
+ - When 'r' is included in 'mouse' (but then selecting text won't work).
Note: The typed key is directly obtained from the terminal, it is not mapped
diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt
index 929efee19f..aa18e44225 100644
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -233,8 +233,8 @@ gM Like "g0", but to halfway the text of the line.
Thus "10gM" is near the start of the text and "90gM"
is near the end of the text.
- *g$* *g<End>*
-g$ or g<End> When lines wrap ('wrap' on): To the last character of
+ *g$*
+g$ When lines wrap ('wrap' on): To the last character of
the screen line and [count - 1] screen lines downward
|inclusive|. Differs from "$" when a line is wider
than the screen.
@@ -247,6 +247,10 @@ g$ or g<End> When lines wrap ('wrap' on): To the last character of
When 'virtualedit' is enabled moves to the end of the
screen line.
+ *g<End>* *g<kEnd>*
+g<End> Like |g$| but to the last non-blank character
+ instead of the last character.
+
*bar*
| To screen column [count] in the current line.
|exclusive| motion. Ceci n'est pas une pipe.
@@ -566,14 +570,16 @@ a] *v_a]* *v_a[* *a]* *a[*
a[ "a [] block", select [count] '[' ']' blocks. This
goes backwards to the [count] unclosed '[', and finds
the matching ']'. The enclosed text is selected,
- including the '[' and ']'.
+ including the '[' and ']'. The |cpo-M| option flag
+ is used to handle escaped brackets.
When used in Visual mode it is made charwise.
i] *v_i]* *v_i[* *i]* *i[*
i[ "inner [] block", select [count] '[' ']' blocks. This
goes backwards to the [count] unclosed '[', and finds
the matching ']'. The enclosed text is selected,
- excluding the '[' and ']'.
+ excluding the '[' and ']'. The |cpo-M| option flag
+ is used to handle escaped brackets.
When used in Visual mode it is made charwise.
a) *v_a)* *a)* *a(*
@@ -581,7 +587,8 @@ a( *vab* *v_ab* *v_a(* *ab*
ab "a block", select [count] blocks, from "[count] [(" to
the matching ')', including the '(' and ')' (see
|[(|). Does not include white space outside of the
- parenthesis.
+ parenthesis. The |cpo-M| option flag is used to
+ handle escaped parenthesis.
When used in Visual mode it is made charwise.
i) *v_i)* *i)* *i(*
@@ -589,19 +596,22 @@ i( *vib* *v_ib* *v_i(* *ib*
ib "inner block", select [count] blocks, from "[count] [("
to the matching ')', excluding the '(' and ')' (see
|[(|). If the cursor is not inside a () block, then
- find the next "(".
+ find the next "(". The |cpo-M| option flag
+ is used to handle escaped parenthesis.
When used in Visual mode it is made charwise.
a> *v_a>* *v_a<* *a>* *a<*
a< "a <> block", select [count] <> blocks, from the
[count]'th unmatched '<' backwards to the matching
- '>', including the '<' and '>'.
+ '>', including the '<' and '>'. The |cpo-M| option flag
+ is used to handle escaped '<' and '>'.
When used in Visual mode it is made charwise.
i> *v_i>* *v_i<* *i>* *i<*
i< "inner <> block", select [count] <> blocks, from
the [count]'th unmatched '<' backwards to the matching
- '>', excluding the '<' and '>'.
+ '>', excluding the '<' and '>'. The |cpo-M| option flag
+ is used to handle escaped '<' and '>'.
When used in Visual mode it is made charwise.
*v_at* *at*
@@ -620,16 +630,18 @@ it "inner tag block", select [count] tag blocks, from the
a} *v_a}* *a}* *a{*
a{ *v_aB* *v_a{* *aB*
-aB "a Block", select [count] Blocks, from "[count] [{" to
- the matching '}', including the '{' and '}' (see
- |[{|).
+aB "a Block", select [count] Blocks, from `[count] [{` to
+ the matching "}", including the "{" and "}" (see
+ |[{|). The |cpo-M| option flag is used to handle
+ escaped braces.
When used in Visual mode it is made charwise.
i} *v_i}* *i}* *i{*
i{ *v_iB* *v_i{* *iB*
-iB "inner Block", select [count] Blocks, from "[count] [{"
- to the matching '}', excluding the '{' and '}' (see
- |[{|).
+iB "inner Block", select [count] Blocks, from `[count] [{`
+ to the matching "}", excluding the "{" and "}" (see
+ |[{|). The |cpo-M| option flag is used to handle
+ escaped braces.
When used in Visual mode it is made charwise.
a" *v_aquote* *aquote*
@@ -656,6 +668,7 @@ i` *v_i`* *i`*
Special case: With a count of 2 the quotes are
included, but no extra white space as with a"/a'/a`.
+ *o_object-select*
When used after an operator:
For non-block objects:
For the "a" commands: The operator applies to the object and the white
@@ -671,6 +684,7 @@ For a block object:
the surrounding braces are excluded. For the "a" commands, the braces
are included.
+ *v_object-select*
When used in Visual mode:
When start and end of the Visual area are the same (just after typing "v"):
One object is selected, the same as for using an operator.
@@ -892,7 +906,7 @@ was made yet in the current file.
for each opened file.
Only one position is remembered per buffer, not one
for each window. As long as the buffer is visible in
- a window the position won't be changed. Mark is also
+ a window the position won't be changed. Mark is also
reset when |:wshada| is run.
*'^* *`^*
@@ -1026,6 +1040,12 @@ CTRL-O Go to [count] Older cursor position in jump list
CTRL-I Go to [count] newer cursor position in jump list
(not a motion command).
+ NOTE: In the GUI and in a terminal supporting
+ |tui-modifyOtherKeys| or |tui-csiu|, CTRL-I can be
+ mapped separately from <Tab>, on the condition that
+ both keys are mapped, otherwise the mapping applies to
+ both.
+
*:ju* *:jumps*
:ju[mps] Print the jump list (not a motion command).
@@ -1038,14 +1058,14 @@ can go to cursor positions before older jumps, and back again. Thus you can
move up and down the list. There is a separate jump list for each window.
The maximum number of entries is fixed at 100.
-For example, after three jump commands you have this jump list:
-
- jump line col file/text ~
- 3 1 0 some text ~
- 2 70 0 another line ~
- 1 1154 23 end. ~
- > ~
+For example, after three jump commands you have this jump list: >
+ jump line col file/text
+ 3 1 0 some text
+ 2 70 0 another line
+ 1 1154 23 end.
+ >
+<
The "file/text" column shows the file name, or the text at the jump if it is
in the current file (an indent is removed and a long line is truncated to fit
in the window).
@@ -1054,14 +1074,14 @@ The marker ">" indicates the current position in the jumplist. It may not be
shown when filtering the |:jumps| command using |:filter|
You are currently in line 1167. If you then use the CTRL-O command, the
-cursor is put in line 1154. This results in:
-
- jump line col file/text ~
- 2 1 0 some text ~
- 1 70 0 another line ~
- > 0 1154 23 end. ~
- 1 1167 0 foo bar ~
+cursor is put in line 1154. This results in: >
+ jump line col file/text
+ 2 1 0 some text
+ 1 70 0 another line
+ > 0 1154 23 end.
+ 1 1167 0 foo bar
+<
The pointer will be set at the last used jump position. The next CTRL-O
command will use the entry above it, the next CTRL-I command will use the
entry below it. If the pointer is below the last entry, this indicates that
@@ -1085,15 +1105,15 @@ command. You can explicitly add a jump by setting the ' mark with "m'". Note
that calling setpos() does not do this.
After the CTRL-O command that got you into line 1154 you could give another
-jump command (e.g., "G"). The jump list would then become:
-
- jump line col file/text ~
- 4 1 0 some text ~
- 3 70 0 another line ~
- 2 1167 0 foo bar ~
- 1 1154 23 end. ~
- > ~
-
+jump command (e.g., "G"). The jump list would then become: >
+
+ jump line col file/text
+ 4 1 0 some text
+ 3 70 0 another line
+ 2 1167 0 foo bar
+ 1 1154 23 end.
+ >
+<
The line numbers will be adjusted for deleted and inserted lines. This fails
if you stop editing a file without writing, like with ":n!".
@@ -1103,60 +1123,44 @@ If you have included the ' item in the 'shada' option the jumplist will be
stored in the ShaDa file and restored when starting Vim.
*jumplist-stack*
-When jumpoptions includes "stack", the jumplist behaves like the history in a
-web browser and like the tag stack. When jumping to a new location from the
-middle of the jumplist, the locations after the current position will be
-discarded.
-
-This behavior corresponds to the following situation in a web browser.
-Navigate to first.com, second.com, third.com, fourth.com and then fifth.com.
-Then navigate backwards twice so that third.com is displayed. At that point,
-the history is:
-- first.com
-- second.com
-- third.com <--
-- fourth.com
-- fifth.com
-
-Finally, navigate to a different webpage, new.com. The history is
-- first.com
-- second.com
-- third.com
-- new.com <--
-
-When the jumpoptions includes "stack", this is the behavior of Nvim as well.
-That is, given a jumplist like the following in which CTRL-O has been used to
-move back three times to location X
-
- jump line col file/text
- 2 1260 8 src/nvim/mark.c <-- location X-2
- 1 685 0 src/nvim/option_defs.h <-- location X-1
-> 0 462 36 src/nvim/option_defs.h <-- location X
- 1 479 39 src/nvim/option_defs.h
- 2 213 2 src/nvim/mark.c
- 3 181 0 src/nvim/mark.c
-
+When 'jumpoptions' option includes "stack", the jumplist behaves like the tag
+stack. When jumping to a new location from the middle of the jumplist, the
+locations after the current position will be discarded. With this option set
+you can move through a tree of jump locations. When going back up a branch and
+then down another branch, CTRL-O still takes you further up the tree.
+
+Given a jumplist like the following in which CTRL-O has been used to move back
+three times to location X: >
+
+ jump line col file/text
+ 2 1260 8 mark.c <-- location X-2
+ 1 685 0 eval.c <-- location X-1
+ > 0 462 36 eval.c <-- location X
+ 1 479 39 eval.c
+ 2 213 2 mark.c
+ 3 181 0 mark.c
+<
jumping to (new) location Y results in the locations after the current
-locations being removed:
-
- jump line col file/text
- 3 1260 8 src/nvim/mark.c
- 2 685 0 src/nvim/option_defs.h
- 1 462 36 src/nvim/option_defs.h <-- location X
->
+locations being removed: >
+ jump line col file/text
+ 3 1260 8 mark.c <-- location X-2
+ 2 685 0 eval.c <-- location X-1
+ 1 462 36 eval.c <-- location X
+ >
+<
Then, when yet another location Z is jumped to, the new location Y appears
directly after location X in the jumplist and location X remains in the same
-position relative to the locations (X-1, X-2, etc., ...) that had been before it
-prior to the original jump from X to Y:
-
- jump line col file/text
- 4 1260 8 src/nvim/mark.c <-- location X-2
- 3 685 0 src/nvim/option_defs.h <-- location X-1
- 2 462 36 src/nvim/option_defs.h <-- location X
- 1 100 0 src/nvim/option_defs.h <-- location Y
->
-
+position relative to the locations (X-1, X-2, etc., ...) that had been before
+it prior to the original jump from X to Y: >
+
+ jump line col file/text
+ 4 1260 8 mark.c <-- location X-2
+ 3 685 0 eval.c <-- location X-1
+ 2 462 36 eval.c <-- location X
+ 1 100 0 buffer.c <-- location Y
+ >
+<
CHANGE LIST JUMPS *changelist* *change-list-jumps* *E664*
When making a change the cursor position is remembered. One position is
@@ -1230,7 +1234,7 @@ remembered.
([{}]) parenthesis or (curly/square) brackets
(this can be changed with the
'matchpairs' option)
- /* */ start or end of C-style comment
+ `/* */` start or end of C-style comment
#if, #ifdef, #else, #elif, #endif
C preprocessor conditionals (when the
cursor is on the # or no ([{
@@ -1279,9 +1283,9 @@ remembered.
|exclusive| motion.
The above four commands can be used to go to the start or end of the current
-code block. It is like doing "%" on the '(', ')', '{' or '}' at the other
+code block. It is like doing "%" on the "(", ")", "{" or "}" at the other
end of the code block, but you can do this from anywhere in the code block.
-Very useful for C programs. Example: When standing on "case x:", "[{" will
+Very useful for C programs. Example: When standing on "case x:", `[{` will
bring you back to the switch statement.
*]m*
diff --git a/runtime/doc/news-0.9.txt b/runtime/doc/news-0.9.txt
new file mode 100644
index 0000000000..789bc9e0bc
--- /dev/null
+++ b/runtime/doc/news-0.9.txt
@@ -0,0 +1,323 @@
+*news-0.9.txt* Nvim
+
+
+ NVIM REFERENCE MANUAL
+
+
+Notable changes in Nvim 0.9 from 0.8 *news-0.9*
+
+ Type |gO| to see the table of contents.
+
+==============================================================================
+BREAKING CHANGES
+
+The following changes may require adaptations in user config or plugins.
+
+• Cscope support is now removed (see |cscope| and |nvim-removed|):
+ - Commands removed:
+ - `:cscope`
+ - `:lcscope`
+ - `:scscope`
+ - `:cstag`
+ - Options removed:
+ - `cscopepathcomp`
+ - `cscopeprg`
+ - `cscopequickfix`
+ - `cscoperelative`
+ - `cscopetag`
+ - `cscopetagorder`
+ - `cscopeverbose`
+ - Eval functions removed:
+ - `cscope_connection()`
+
+ Note: support for |ctags| remains with no plans to remove.
+
+ See https://github.com/neovim/neovim/pull/20545 for more information.
+
+• `:hardcopy` is now removed (see |hardcopy| and |nvim-removed|):
+ - Commands removed:
+ - `:hardcopy`
+ - Options removed:
+ - `printdevice`
+ - `printencoding`
+ - `printexpr`
+ - `printfont`
+ - `printheader`
+ - `printmbcharset`
+
+• 'paste' option is now deprecated and 'pastetoggle' is removed. |paste| works
+ automatically in GUI and terminal (TUI) Nvim. Just Paste It.™
+
+• Changes to |vim.treesitter.get_node_text()|:
+ - It now returns `string`, as opposed to `string|string[]|nil`.
+ - The `concat` option has been removed as it was not consistently applied.
+ - Invalid ranges now cause an error instead of returning `nil`.
+
+• `help` treesitter parser was renamed to `vimdoc`. The only user-visible
+ change is that language-specific highlight groups need to be renamed from
+ `@foo.help` to `@foo.vimdoc`.
+
+• The default value of 'commentstring' is now empty instead of "/*%s*/".
+
+• libiconv and intl are now required build dependencies.
+
+==============================================================================
+NEW FEATURES
+
+The following new APIs or features were added.
+
+• Treesitter syntax highlighting for `help` files now supports highlighted
+ code examples. To enable, create a `.config/nvim/ftplugin/help.lua` with
+ the contents >lua
+ vim.treesitter.start()
+<
+ Note: Highlighted code examples are only available in the Nvim manual, not
+ in help files taken from Vim. The treesitter `vimdoc` parser is also work in
+ progress and not guaranteed to correctly highlight every help file in the
+ wild.
+
+• Added support for semantic token highlighting to the LSP client. This
+ functionality is enabled by default when a client that supports this feature
+ is attached to a buffer. Opt-out can be performed by deleting the
+ `semanticTokensProvider` from the LSP client's {server_capabilities} in the
+ `LspAttach` callback.
+
+ See |lsp-semantic-highlight| for more information.
+
+• |vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items
+ at a given buffer position. Currently this includes treesitter captures,
+ LSP semantic tokens, syntax groups and extmarks.
+
+• |vim.treesitter.inspect_tree()| and |:InspectTree| opens a split window
+ showing a text representation of the nodes in a language tree for the current
+ buffer.
+
+• |'statuscolumn'| option to customize the area to the side of a window,
+ normally containing the fold, sign and number columns. This new option follows
+ the 'statusline' syntax and can be used to transform the line numbers, create
+ mouse click callbacks for |signs|, introduce a custom margin or separator etc.
+
+• |vim.secure.trust()|, |:trust| allows the user to manage files in trust database.
+ |vim.secure.read()| reads a file and prompts the user if it should be
+ trusted and, if so, returns the file's contents. Used by 'exrc'
+
+• EditorConfig support is now builtin. This is enabled by default and happens
+ automatically. To disable it, users should add >lua
+
+ vim.g.editorconfig = false
+<
+ (or the Vimscript equivalent) to their |config| file.
+
+• A new environment variable named NVIM_APPNAME enables configuring the
+ directories where Nvim should find its configuration and state files. See
+ `:help $NVIM_APPNAME` .
+
+• Added support for running Lua scripts from shell using |-l|. >
+ nvim -l foo.lua --arg1 --arg2
+< Also works with stdin: >
+ echo "print(42)" | nvim -l -
+
+• Added an omnifunc implementation for Lua, |vim.lua_omnifunc()|
+
+• Added a new experimental |vim.loader| that byte-compiles and caches Lua files.
+ To enable the new loader, add the following at the top of your |init.lua|: >lua
+ vim.loader.enable()
+
+• Added |vim.version| for parsing and comparing version strings conforming to
+ the semver specification.
+
+• When using Nvim inside tmux 3.2 or later, the default clipboard provider
+ will now copy to the system clipboard. |provider-clipboard|
+
+• |'showcmdloc'| option to display the 'showcmd' information in the
+ status line or tab line. A new %S statusline item is available to place
+ the 'showcmd' text in a custom 'statusline'. Useful for when |'cmdheight'|
+ is set to 0.
+
+• |'splitkeep'| option to control the scroll behavior of horizontal splits.
+
+• |'diffopt'| now includes a `linematch` option to enable a second-stage diff
+ on individual hunks to provide much more accurate diffs. This option is also
+ available to |vim.diff()|
+
+ See https://github.com/neovim/neovim/pull/14537.
+
+• |--remote-ui| option was added to connect to a remote instance and display
+ in it in a |TUI| in the local terminal. This can be used run a headless nvim
+ instance in the background and display its UI on demand, which previously
+ only was possible using an external UI implementation.
+
+• Added a |vim.lsp.codelens.clear()| function to clear codelenses.
+
+• Added support for the `willSave` and `willSaveWaitUntil` capabilities to the
+ LSP client. `willSaveWaitUntil` allows a server to modify a document before it
+ gets saved. Example use-cases by language servers include removing unused
+ imports, or formatting the file.
+
+• Added preliminary support for the `workspace/didChangeWatchedFiles` capability
+ to the LSP client to notify servers of file changes on disk. The feature is
+ disabled by default and can be enabled by setting the
+ `workspace.didChangeWatchedFiles.dynamicRegistration=true` capability.
+
+• |vim.diagnostic| now supports LSP DiagnosticsTag.
+ See: https://microsoft.github.io/language-server-protocol/specification/#diagnosticTag
+
+• |vim.diagnostic.is_disabled()| checks if diagnostics are disabled in a given
+ buffer or namespace.
+
+• Treesitter captures can now be transformed by directives. This will allow
+ more complicated dynamic language injections.
+
+• |vim.treesitter.get_node_text()| now accepts a `metadata` option for
+ writing custom directives using |vim.treesitter.query.add_directive()|.
+
+• |vim.treesitter.language.add()| replaces `vim.treesitter.language.require_language`.
+
+• |vim.treesitter.foldexpr()| can be used for 'foldexpr' to use treesitter for folding.
+
+• Expanded the TSNode API with:
+ - |TSNode:tree()|
+ - |TSNode:has_changes()|
+ - |TSNode:extra()|
+ - |TSNode:equal()|
+
+ Additionally |TSNode:range()| now takes an optional {include_bytes} argument.
+
+• Treesitter injection queries now use the format described at
+ https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection .
+ Support for the previous format will be removed in a future release.
+
+• Added |nvim_get_hl()| for getting highlight group definitions in a format
+ compatible with |nvim_set_hl()|.
+
+• |vim.filetype.get_option()| to get the default option value for a specific
+ filetype. This is a wrapper around |nvim_get_option_value()| with caching.
+
+• `require'bit'` is now always available |lua-bit|
+
+==============================================================================
+CHANGED FEATURES
+
+The following changes to existing APIs or features add new behavior.
+
+• 'exrc' now supports `.nvim.lua` file.
+• 'exrc' is no longer marked deprecated.
+
+• The |TUI| is changed to run in a separate process (previously, a separate
+ thread was used). This is not supposed to be a visible change to the user,
+ but might be the cause of subtle changes of behavior and bugs.
+
+ Previously, the TUI could be disabled as a build time feature (+tui/-tui),
+ resulting in a nvim binary which only could be run headless or embedded
+ in an external process. As of this version, TUI is always available.
+
+• Vim's `has('gui_running')` is now supported as a way for plugins to check if
+ a GUI (not the |TUI|) is attached to Nvim. |has()|
+
+• |msgsep| is now always enabled even if 'display' doesn't contain the "msgsep"
+ flag. It is no longer possible to scroll the whole screen when showing
+ messages longer than 'cmdheight'.
+
+• API calls now show more information about where an exception happened.
+
+• The `win_viewport` UI event now contains information about virtual lines,
+ meaning that smooth scrolling can now be implemented more consistently.
+
+• The `:= {expr}` syntax can be used to evaluate a Lua expression, as
+ a shorter form of `:lua ={expr}`. `:=` and `:[range]=` without argument
+ are unchanged. However `:=#` and similar variants using |ex-flags|
+ are no longer supported.
+
+• Unsaved changes are now preserved rather than discarded when |channel-stdio|
+ is closed.
+
+• |nvim_open_win()| now accepts a relative `mouse` option to open a floating win
+ relative to the mouse. Note that the mouse doesn't update frequently without
+ setting `vim.o.mousemoveevent = true`
+
+• |nvim_eval_statusline()| supports evaluating the |'statuscolumn'| through a
+ new `opts` field: `use_statuscol_lnum`.
+
+• |nvim_buf_get_extmarks()| now accepts a -1 `ns_id` to request extmarks from
+ all namespaces and adds the namespace id to the details array.
+ Other missing properties have been added to the details array and marks can
+ be filtered by type.
+
+• |vim.diagnostic.open_float()| (and therefore |vim.diagnostic.config()|) now
+ accepts a `suffix` option which, by default, renders LSP error codes.
+ Similarly, the `virtual_text` configuration in |vim.diagnostic.config()| now
+ has a `suffix` option which does nothing by default.
+
+• |vim.fs.dir()| now has a `opts` argument with a depth field to allow
+ recursively searching a directory tree.
+
+• |vim.gsplit()| supports all features of |vim.split()|.
+
+• |:highlight| now supports an additional attribute "altfont".
+
+• |:Man| manpage viewer supports manpage names containing spaces.
+
+• |nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu.
+
+• |nvim_list_uis()| reports all |ui-option| fields.
+
+• |nvim_get_option_value()| now has a `filetype` option so it can return the
+ default option for a specific filetype.
+
+• build: Several improvements were made to make the code generation scripts more
+ deterministic, and a `LUA_GEN_PRG` build parameter has been introduced to
+ allow for a workaround for some remaining reproducibility problems.
+
+==============================================================================
+REMOVED FEATURES
+
+The following deprecated functions or APIs were removed.
+
+• `filetype.vim` is removed in favor of |vim.filetype|
+ (Note that filetype logic and tests still align with Vim, so additions or
+ changes need to be contributed there first.)
+ See https://github.com/neovim/neovim/pull/20674.
+
+• 'hkmap', 'hkmapp' and 'aleph' options were removed. Use 'keymap' option instead.
+
+• |LanguageTree:parse()| no longer returns changed regions. Please use the
+ `on_changedtree` callbacks instead.
+
+• `vim.highlight.create()`, `vim.highlight.link()` were removed, use |nvim_set_hl()| instead.
+
+• `require'health'` was removed. Use |vim.health| instead.
+
+==============================================================================
+DEPRECATIONS
+
+The following functions are now deprecated and will be removed in the next
+release.
+
+• |vim.treesitter.language.add()| replaces `vim.treesitter.language.require_language()`
+
+• |vim.treesitter.get_node_at_pos()| and |vim.treesitter.get_node_at_cursor()|
+ are both deprecated in favor of |vim.treesitter.get_node()|.
+
+• `vim.api.nvim_get_hl_by_name()`, `vim.api.nvim_get_hl_by_id()` were deprecated, use |nvim_get_hl()| instead.
+
+• The following top level Treesitter functions have been moved:
+ `vim.treesitter.inspect_language()` -> `vim.treesitter.language.inspect()`
+ `vim.treesitter.get_query_files()` -> `vim.treesitter.query.get_files()`
+ `vim.treesitter.set_query()` -> `vim.treesitter.query.set()`
+ `vim.treesitter.query.set_query()` -> `vim.treesitter.query.set()`
+ `vim.treesitter.get_query()` -> `vim.treesitter.query.get()`
+ `vim.treesitter.query.get_query()` -> `vim.treesitter.query.get()`
+ `vim.treesitter.parse_query()` -> `vim.treesitter.query.parse()`
+ `vim.treesitter.query.parse_query()` -> `vim.treesitter.query.parse()`
+ `vim.treesitter.add_predicate()` -> `vim.treesitter.query.add_predicate()`
+ `vim.treesitter.add_directive()` -> `vim.treesitter.query.add_directive()`
+ `vim.treesitter.list_predicates()` -> `vim.treesitter.query.list_predicates()`
+ `vim.treesitter.list_directives()` -> `vim.treesitter.query.list_directives()`
+ `vim.treesitter.query.get_range()` -> `vim.treesitter.get_range()`
+ `vim.treesitter.query.get_node_text()` -> `vim.treesitter.get_node_text()`
+
+• |nvim_exec()| is now deprecated in favor of |nvim_exec2()|.
+
+• Renamed |vim.pretty_print()| to |vim.print()|.
+
+ vim:tw=78:ts=8:sw=2:et:ft=help:norl:
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 5c234677ef..825e5ba41f 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -1,10 +1,12 @@
*news.txt* Nvim
- NVIM REFERENCE MANUAL
+ NVIM REFERENCE MANUAL
-Notable changes in Nvim 0.9 from 0.8 *news*
+Notable changes in Nvim 0.10 from 0.9 *news*
+
+For changes in Nvim 0.9, see |news-0.9|.
Type |gO| to see the table of contents.
@@ -13,182 +15,373 @@ BREAKING CHANGES *news-breaking*
The following changes may require adaptations in user config or plugins.
-• Cscope support is now removed (see |cscope| and |nvim-features-removed|):
- - Commands removed:
- - `:cscope`
- - `:lcscope`
- - `:scscope`
- - `:cstag`
- - Options removed:
- - `cscopepathcomp`
- - `cscopeprg`
- - `cscopequickfix`
- - `cscoperelative`
- - `cscopetag`
- - `cscopetagorder`
- - `cscopeverbose`
- - Eval functions removed:
- - `cscope_connection()`
-
- Note: support for |ctags| remains with no plans to remove.
-
- See https://github.com/neovim/neovim/pull/20545 for more information.
-
-• `:hardcopy` is now removed (see |hardcopy| and |nvim-features-removed|):
- - Commands removed:
- - `:hardcopy`
- - Options removed:
- - `printdevice`
- - `printencoding`
- - `printexpr`
- - `printfont`
- - `printheader`
- - `printmbcharset`
-
-• libiconv is now a required build dependency.
+• In some cases, the cursor in the Nvim |TUI| would blink even without
+ configuring 'guicursor' as mentioned in |cursor-blinking|. This was a bug
+ that has now been fixed. If your cursor has stopped blinking, add the
+ following (or similar, adapted to user preference) to your |config| file: >vim
+
+ set guicursor+=n-v-c:blinkon500-blinkoff500
+<
+• |vim.tbl_islist()| now checks whether a table is actually list-like (i.e.,
+ has integer keys without gaps and starting from 1). For the previous
+ behavior (only check for integer keys, allow gaps or not starting with 1),
+ use |vim.tbl_isarray()|.
+
+• "#" followed by a digit no longer stands for a function key at the start of
+ the lhs of a mapping.
+
+• `:behave` was removed.
+ - If you used `:behave xterm`, the following is equivalent: >vim
+
+ set mousemodel=extend
+<
+ - If you used `:behave mswin`, the following is equivalent: >vim
+
+ set selection=exclusive
+ set selectmode=mouse,key
+ set mousemodel=popup
+ set keymodel=startsel,stopsel
+<
+• When switching windows, |CursorMoved| autocommands trigger when Nvim is back
+ in the main loop rather than immediately. This is more compatible with Vim.
+
+• |-l| ensures output ends with a newline if the script prints messages and
+ doesn't cause Nvim to exit.
+
+• |LspRequest| and LspProgressUpdate (renamed to |LspProgress|) autocmds were
+ promoted from a |User| autocmd to first class citizen.
+
+• Renamed `vim.treesitter.playground` to `vim.treesitter.dev`.
+
+• Removed functions from the |vim.json| module:
+ • Unnecessary, undocumented functions which caused global side-effects.
+ • `vim.json.null` is redundant with `vim.NIL`.
+ • `vim.json.array_mt` (and related) is redundant with `vim.empty_dict()`.
+
+• Removed some Vim 5.0<= option compatibilities:
+ • |'backspace'| no longer supports number values. Instead:
+ • for `backspace=0` set `backspace=` (empty)
+ • for `backspace=1` set `backspace=indent,eol`
+ • for `backspace=2` set `backspace=indent,eol,start` (default behavior in Nvim)
+ • for `backspace=3` set `backspace=indent,eol,nostop`
+ • |'backupdir'| and |'directory'| will no longer remove a `>` at the start
+ of the option.
+
+• |LanguageTree:parse()| will no longer parse injections by default and
+ now requires an explicit range argument to be passed. If injections are
+ required, provide an explicit range via `parser:parse({ start_row, end_row })`.
+
+• Float window support hide and show by setting `hide` on `nvim_open_win` and
+ `nvim_win_set_config`.
+
+• |vim.lsp.util.parse_snippet()| will now strictly follow the snippet grammar
+ defined by LSP, and hence previously parsed snippets might now be considered
+ invalid input.
+
+• |OptionSet| autocommand args |v:option_new|, |v:option_old|,
+ |v:option_oldlocal|, |v:option_oldglobal| now have the type of the option
+ instead of always being strings. |v:option_old| is now the old global value
+ for all global-local options, instead of just string global-local options.
+
+• Local value for a global-local number/boolean option is now unset when
+ the option is set (e.g. using |:set| or |nvim_set_option_value()|) without a
+ scope, which means they now behave the same way as string options.
+
+• Signs placed through the legacy |sign-commands| are now stored and displayed
+ as |extmarks| internally. Along with the following changes:
+ • A sign placed twice in the same group with the same identifier will be moved.
+ • Legacy signs are always deleted along with the line it is placed on.
+ • Legacy and extmark signs will show up in both |:sign-place-list| and |nvim_buf_get_extmarks()|.
+ • Legacy and extmark signs are displayed and listed with the same priority:
+ line number -> priority -> sign id -> recently placed
+
+==============================================================================
+BREAKING CHANGES IN HEAD *news-breaking-dev*
+
+The following breaking changes were made during the development cycle to
+unreleased features on Nvim HEAD.
+
+• ...
+• ...
==============================================================================
NEW FEATURES *news-features*
-The following new APIs or features were added.
+The following new APIs and features were added.
+
+• Performance:
+ • 'diffopt' "linematch" scoring algorithm now favours larger and less groups
+ https://github.com/neovim/neovim/pull/23611
+ • Treesitter highlighting now parses injections incrementally during
+ screen redraws only for the line range being rendered. This significantly
+ improves performance in large files with many injections.
+
+• |vim.iter()| provides a generic iterator interface for tables and Lua
+ iterators |for-in|.
+
+• Added |vim.ringbuf()| to create ring buffers.
-• |nvim_open_win()| now accepts a relative `mouse` option to open a floating win
- relative to the mouse. Note that the mouse doesn't update frequently without
- setting `vim.o.mousemoveevent = true`
+• Added |vim.keycode()| for translating keycodes in a string.
-• EditorConfig support is now builtin. This is enabled by default and happens
- automatically. To disable it, users should add >lua
+• |'smoothscroll'| option to scroll by screen line rather than by text line
+ when |'wrap'| is set.
- vim.g.editorconfig = false
-<
- (or the Vimscript equivalent) to their |config| file.
+• Added inline virtual text support to |nvim_buf_set_extmark()|.
-• Run Lua scripts from your shell using |-l|. >
- nvim -l foo.lua --arg1 --arg2
-< Also works with stdin: >
- echo "print(42)" | nvim -l -
+• 'foldtext' now supports virtual text format. |fold-foldtext|
-• Added a |vim.lsp.codelens.clear()| function to clear codelenses.
+• The terminal buffer now supports reflow (wrapped lines adapt when the buffer
+ is resized horizontally). Note: Lines that are not visible and kept in
+ |'scrollback'| are not reflown.
-• |vim.inspect_pos()|, |vim.show_pos()| and |:Inspect| allow a user to get or show items
- at a given buffer position. Currently this includes treesitter captures,
- semantic tokens, syntax groups and extmarks.
+• |vim.system()| for running system commands.
-• Added support for semantic token highlighting to the LSP client. This
- functionality is enabled by default when a client that supports this feature
- is attached to a buffer. Opt-out can be performed by deleting the
- `semanticTokensProvider` from the LSP client's {server_capabilities} in the
- `LspAttach` callback.
+• Added |nvim_win_text_height()| to compute the number of screen lines occupied
+ by a range of text in a given window.
- See |lsp-semantic_tokens| for more information.
+• |nvim_set_keymap()| and |nvim_del_keymap()| now support abbreviations.
-• |vim.treesitter.show_tree()| opens a split window showing a text
- representation of the nodes in a language tree for the current buffer.
+• Better cmdline completion for string option value. |complete-set-option|
-• Added support for the `willSave` and `willSaveWaitUntil` capabilities to the
- LSP client. `willSaveWaitUntil` allows a server to modify a document before it
- gets saved. Example use-cases by language servers include removing unused
- imports, or formatting the file.
+• Builtin TUI can now recognize "super" (|<D-|) and "meta" (|<T-|) modifiers in a
+ terminal emulator that supports |tui-csiu|.
-• Treesitter syntax highlighting for `help` files now supports highlighted
- code examples. To enable, create a `.config/nvim/ftplugin/help.lua` with
- the contents >lua
- vim.treesitter.start()
-<
- Note: Highlighted code examples are only available in the Nvim manual, not
- in help files taken from Vim. The treesitter `help` parser is also work in
- progress and not guaranteed to correctly highlight every help file in the
- wild.
+• Editor
+ • By default, the swapfile "ATTENTION" |E325| dialog is skipped if the
+ swapfile is owned by a running Nvim process, instead of prompting. If you
+ always want the swapfile dialog, delete the default SwapExists handler:
+ `autocmd! nvim_swapfile`. |default-autocmds|
+
+• LSP
+ • LSP method names are available in |vim.lsp.protocol.Methods|.
+ • Implemented LSP inlay hints: |lsp-inlay_hint|
+ https://microsoft.github.io/language-server-protocol/specification/#textDocument_inlayHint
+ • Implemented pull diagnostic textDocument/diagnostic: |vim.lsp.diagnostic.on_diagnostic()|
+ https://microsoft.github.io/language-server-protocol/specification/#textDocument_diagnostic
+ • Added |vim.lsp.status()| to consume the last progress messages as a string.
+ • LSP client now always saves and restores named buffer marks when applying
+ text edits.
+ • LSP client now supports the `positionEncoding` server capability. If a server
+ responds with the `positionEncoding` capability in its initialization
+ response, Nvim automatically sets the client's `offset_encoding` field.
+ • Dynamic registration of LSP capabilities. An implication of this change is
+ that checking a client's `server_capabilities` is no longer a sufficient
+ indicator to see if a server supports a feature. Instead use
+ `client.supports_method(<method>)`. It considers both the dynamic
+ capabilities and static `server_capabilities`.
+ • Added a new `anchor_bias` option to |lsp-handlers| to aid in positioning of
+ floating windows.
+
+• Treesitter
+ • Bundled parsers and queries (highlight, folds) for Markdown, Python, and
+ Bash.
+ • Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by
+ default).
+ • |Query:iter_matches()| now has the ability to set the maximum start depth
+ for matches.
+ • `@injection.language` now has smarter resolution and will now fallback to language aliases and/or attempt lower case variants of the text.
+ language via aliases (e.g., filetype) registered via
+ `vim.treesitter.language.register`.
+ • The `#set!` directive now supports `injection.self` and `injection.parent` for injecting either the current node's language
+ or the parent LanguageTree's language, respectively.
+ • Added `vim.treesitter.query.edit()`, for live editing of treesitter
+ queries.
+ • Improved error messages for query parsing.
+ • Added |vim.treesitter.foldtext()| to apply treesitter highlighting to
+ foldtext.
+
+• |vim.ui.open()| opens URIs using the system default handler (macOS `open`,
+ Windows `explorer`, Linux `xdg-open`, etc.)
+
+• |vim.wo| can now be double indexed for |:setlocal| behaviour. Currently only
+ `0` for the buffer index is currently supported.
-• |vim.secure.trust()|, |:trust| allows the user to manage files in trust
- database.
+• Lua type annotations for:
+ • `vim.*`
+ • `vim.fn.*`
+ • `vim.api.*`
-• |vim.diagnostic.open_float()| (and therefore |vim.diagnostic.config()|) now
- accepts a `suffix` option which, by default, renders LSP error codes.
- Similarly, the `virtual_text` configuration in |vim.diagnostic.config()| now
- has a `suffix` option which does nothing by default.
+• Improved messages for type errors in `vim.api.*` calls (including `opts` params)
-• |vim.fs.dir()| now has a `opts` argument with a depth field to allow
- recursively searching a directory tree.
+• Functions that take a severity as an optional parameter (e.g.
+ |vim.diagnostic.get()|) now also accept a list of severities |vim.diagnostic.severity|
-• |vim.secure.read()| reads a file and prompts the user if it should be
- trusted and, if so, returns the file's contents.
+• New RPC client type `msgpack-rpc` is added for `nvim_set_client_info` to
+ support fully MessagePack-RPC compliant clients.
-• When using Nvim inside tmux 3.2 or later, the default clipboard provider
- will now copy to the system clipboard. |provider-clipboard|
+• Floating windows can now show footer with new `footer` and `footer_pos`
+ config fields. Uses |hl-FloatFooter| by default.
-• |'showcmdloc'| option to display the 'showcmd' information in the
- status line or tab line. A new %S statusline item is available to place
- the 'showcmd' text in a custom 'statusline'. Useful for when |'cmdheight'|
- is set to 0.
+• The |:terminal| command now accepts some |:command-modifiers| (specifically
+ |:horizontal| and those that affect splitting a window).
-• |'splitkeep'| option to control the scroll behavior of horizontal splits.
+• |vim.lsp.util.locations_to_items()| sets the `user_data` of each item to the
+ original LSP `Location` or `LocationLink`.
-• |'statuscolumn'| option to customize the area to the side of a window,
- normally containing the fold, sign and number columns. This new option follows
- the 'statusline' syntax and can be used to transform the line numbers, create
- mouse click callbacks for |signs|, introduce a custom margin or separator etc.
+• |$NVIM_APPNAME| can be set to a relative path instead of only a name.
-• |nvim_select_popupmenu_item()| now supports |cmdline-completion| popup menu.
+• Added |:fclose| command.
-• |'diffopt'| now includes a `linematch` option to enable a second-stage diff
- on individual hunks to provide much more accurate diffs. This option is also
- available to |vim.diff()|
+• Added |vim.snippet| for snippet expansion support.
- See https://github.com/neovim/neovim/pull/14537.
+• 'complete' option supports "f" flag for completing buffer names.
-• |vim.diagnostic.is_disabled()| checks if diagnostics are disabled in a given
- buffer or namespace.
+• Added |vim.base64.encode()| and |vim.base64.decode()| for encoding and decoding
+ strings using Base64 encoding.
-• |--remote-ui| option was added to connect to a remote instance and display
- in it in a |TUI| in the local terminal. This can be used run a headless nvim
- instance in the background and display its UI on demand, which previously
- only was possible using an external UI implementation.
+• The |TermResponse| autocommand event can be used with |v:termresponse| to
+ read escape sequence responses from the terminal.
-• Several improvements were made to make the code generation scripts more
- deterministic, and a `LUA_GEN_PRG` build parameter has been introduced to
- allow for a workaround for some remaining reproducibility problems.
+• A clipboard provider which uses OSC 52 to copy the selection to the system
+ clipboard is now bundled by default and will be automatically enabled under
+ certain conditions. |clipboard-osc52|
-• |:highlight| now supports an additional attribute "altfont".
+• The 'termsync' option asks the terminal emulator to buffer screen updates
+ until the redraw cycle is complete. Requires support from the terminal.
+
+• Added |vim.text.hexencode()| and |vim.text.hexdecode()| to convert strings
+ to and from byte representations.
==============================================================================
-CHANGED FEATURES *news-changes*
+CHANGED FEATURES *news-changed*
The following changes to existing APIs or features add new behavior.
-• 'exrc' now supports `.nvim.lua` file.
-• 'exrc' is no longer marked deprecated.
+• |vim.tbl_contains()| now works for general tables and allows specifying a
+ predicate function that is checked for each value. (Use |vim.list_contains()|
+ for checking list-like tables (integer keys without gaps) for literal values.)
-• The |TUI| is changed to run in a separate process (previously, a separate
- thread was used). This is not supposed to be a visible change to the user,
- but might be the cause of subtle changes of behavior and bugs.
+• |vim.region()| can use a string accepted by |getpos()| as position.
- Previously, the TUI could be disabled as a build time feature (+tui/-tui),
- resulting in a nvim binary which only could be run headless or embedded
- in an external process. As of this version, TUI is always available.
+• vim.diagnostic.config() now accepts a function for the virtual_text.prefix
+ option, which allows for rendering e.g., diagnostic severities differently.
-• API calls now show more information about where an exception happened.
+• Defaults:
+ • On Windows 'isfname' does not include ":". Drive letters are handled
+ correctly without it. (Use |gF| for filepaths suffixed with ":line:col").
+ • 'comments' includes "fb:•".
+ • 'shortmess' includes the "C" flag.
+ • Automatic linting of treesitter query files (see |ft-query-plugin|).
+ Can be disabled via: >lua
+ vim.g.query_lint_on = {}
+<
+ • Enabled treesitter highlighting for treesitter query files.
+
+• The `workspace/didChangeWatchedFiles` LSP client capability is now enabled
+ by default.
+
+• |LspRequest| autocmd callbacks now contain additional information about the LSP
+ request status update that occurred.
+
+• `:source` without arguments treats a buffer with its 'filetype' set to "lua"
+ as Lua code regardless of its extension.
+
+• |:checkhealth| buffer now implements |folding|. The initial folding status is
+ defined by the 'foldenable' option.
+
+• |:Man| now respects 'wrapmargin'
+
+• |gx| now uses |vim.ui.open()| and not netrw. To customize, you can redefine
+ `vim.ui.open` or remap `gx`. To continue using netrw (deprecated): >vim
+ :call netrw#BrowseX(expand(exists("g:netrw_gx")? g:netrw_gx : '<cfile>'), netrw#CheckIfRemote())<CR>
+
+• |vim.lsp.start()| now maps |K| to use |vim.lsp.buf.hover()| if the server
+ supports it, unless |'keywordprg'| was customized before calling
+ |vim.lsp.start()|.
+
+• Terminal buffers started with no arguments (and use 'shell') close
+ automatically if the job exited without error, eliminating the (often
+ unwanted) "[Process exited 0]" message.
+
+• |vim.diagnostic.config()| now accepts virtual text relevant options to
+ |nvim_buf_set_extmark()| (e.g. "virt_text_pos" and "hl_mode") in its
+ "virtual_text" table, which gives users more control over how diagnostic
+ virtual text is displayed.
+
+• Extmarks now fully support multi-line ranges, and a single extmark can be
+ used to highlight a range of arbitrary length. The |nvim_buf_set_extmark()|
+ API function already allowed you to define such ranges, but highlight regions
+ were not rendered consistently for a range that covers more than one line break.
+ This has now been fixed. Signs defined as part of a multi-line extmark also
+ apply to every line in the range, not just the first.
+ In addition, |nvim_buf_get_extmarks()| has gained an "overlap" option to
+ return such ranges even if they started before the specified position.
+
+• Extmarks can opt-out of precise undo tracking using the new "undo_restore"
+ flag to |nvim_buf_set_extmark()|
+
+• Extmarks can be automatically hidden or removed using the new "invalidate"
+ flag to |nvim_buf_set_extmark()|
+
+• LSP hover and signature help now use Treesitter for highlighting of Markdown
+ content.
+ Note that syntax highlighting of code examples requires a matching parser
+ and may be affected by custom queries.
+
+• Support for rendering multibyte characters using composing characters has been
+ enhanced. The maximum limit have been increased from 1+6 codepoints to
+ 31 bytes, which is guaranteed to fit all chars from before but often more.
+
+ NOTE: the regexp engine still has a hard-coded limit of considering
+ 6 composing chars only.
+
+• |vim.wait()| is no longer allowed to be called in |api-fast|.
==============================================================================
REMOVED FEATURES *news-removed*
The following deprecated functions or APIs were removed.
-• It is no longer possible to scroll the whole screen when showing messages
- longer than 'cmdheight'. |msgsep| is now always enabled even if 'display'
- doesn't contain the "msgsep" flag.
+• Vimball support is removed.
+ - :Vimuntar command removed.
-• `filetype.vim` is removed in favor of |lua-filetype|
- (Note that filetype logic and tests still align with Vim, so additions or
- changes need to be contributed there first.)
- See https://github.com/neovim/neovim/pull/20674.
+• Support for legacy treesitter injection queries is removed.
+
+• Removed 'shortmess' flags:
+ - |shm-f|. Always uses "(3 of 5)", never "(file 3 of 5)"
+ - |shm-i|. Always use "[noeol]".
+ - |shm-x|. Always use "[dos]", "[unix]" and "[mac]"
+ - |shm-n|. Always use "[New]".
==============================================================================
DEPRECATIONS *news-deprecations*
-The following functions are now deprecated and will be removed in the next
+The following functions are now deprecated and will be removed in a future
release.
-
+• Checkhealth functions:
+ - |health#report_error|, |vim.health.report_error()| Use |vim.health.error()| instead.
+ - |health#report_info|, |vim.health.report_info()| Use |vim.health.info()| instead.
+ - |health#report_ok|, |vim.health.report_ok()| Use |vim.health.ok()| instead.
+ - |health#report_start|, |vim.health.report_start()| Use |vim.health.start()| instead.
+ - |health#report_warn|, |vim.health.report_warn()| Use |vim.health.warn()| instead.
+
+• |API| functions:
+ - |nvim_buf_get_option()| Use |nvim_get_option_value()| instead.
+ - |nvim_buf_set_option()| Use |nvim_set_option_value()| instead.
+ - |nvim_get_option()| Use |nvim_get_option_value()| instead.
+ - |nvim_set_option()| Use |nvim_set_option_value()| instead.
+ - |nvim_win_get_option()| Use |nvim_get_option_value()| instead.
+ - |nvim_win_set_option()| Use |nvim_set_option_value()| instead.
+
+• vim.lsp functions:
+ - |vim.lsp.util.get_progress_messages()| Use |vim.lsp.status()| instead.
+ - |vim.lsp.get_active_clients()| Use |vim.lsp.get_clients()| instead.
+ - |vim.lsp.for_each_buffer_client()| Use |vim.lsp.get_clients()| instead.
+ - |vim.lsp.util.trim_empty_lines()| Use |vim.split()| with `trimempty` instead.
+ - |vim.lsp.util.try_trim_markdown_code_blocks()|
+ - |vim.lsp.util.set_lines()|
+ - |vim.lsp.util.extract_completion_items()|
+ - |vim.lsp.util.parse_snippet()|
+ - |vim.lsp.util.text_document_completion_list_to_complete_items()|
+
+• `vim.loop` has been renamed to `vim.uv`.
+
+• vim.treesitter.languagetree functions:
+ - |LanguageTree:for_each_child()| Use |LanguageTree:children()| (non-recursive) instead.
+
+• The "term_background" UI option |ui-ext-options| is deprecated and no longer
+ populated. Background color detection is now performed in Lua by the Nvim
+ core, not the TUI.
vim:tw=78:ts=8:sw=2:et:ft=help:norl:
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index 96f99528ed..dbc14f5a44 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -115,7 +115,7 @@ You can change the defaults with a TermOpen autocommand: >vim
TERMINAL COLORS ~
The `{g,b}:terminal_color_x` variables control the terminal color palette,
-where `x` is the color index between 0 and 255 inclusive. The variables are
+where `x` is the color index between 0 and 15 inclusive. The variables are
read during |TermOpen|. The value must be a color name or hexadecimal string.
Example: >vim
let g:terminal_color_4 = '#ff0000'
@@ -239,7 +239,7 @@ gdb window and use a "print" command, e.g.: >
print *eap
If mouse pointer movements are working, Vim will also show a balloon when the
mouse rests on text that can be evaluated by gdb.
-You can also use the "K" mapping that will either use neovim floating windows
+You can also use the "K" mapping that will either use Nvim floating windows
if available to show the results or print below the status bar.
Now go back to the source window and put the cursor on the first line after
@@ -317,6 +317,18 @@ This is similar to using "print" in the gdb window.
You can usually shorten `:Evaluate` to `:Ev`.
+Navigating stack frames ~
+ *termdebug-frames* *:Frame* *:Up* *:Down*
+ `:Frame` [frame] select frame [frame], which is a frame number,
+ address, or function name (default: current frame)
+ `:Up` [count] go up [count] frames (default: 1; the frame that
+ called the current)
+ `+` same (see |termdebug_map_plus| to disable)
+ `:Down` [count] go down [count] frames (default: 1; the frame called
+ by the current)
+ `-` same (see |termdebug_map_minus| to disable)
+
+
Other commands ~
*termdebug-commands*
*:Gdb* jump to the gdb window
@@ -325,7 +337,9 @@ Other commands ~
isn't one
*:Asm* jump to the window with the disassembly, create it if there
isn't one
-
+ *:Var* jump to the window with the local and argument variables,
+ create it if there isn't one. This window updates whenever the
+ program is stopped
Events ~
*termdebug-events*
@@ -385,20 +399,48 @@ Prompt mode can be used with: >vim
If there is no g:termdebug_config you can use: >vim
let g:termdebug_use_prompt = 1
<
- *termdebug_map_K*
-The K key is normally mapped to :Evaluate. If you do not want this use: >vim
+Mappings ~
+ *termdebug_map_K* *termdebug-mappings*
+The K key is normally mapped to |:Evaluate| unless a buffer local (|:map-local|)
+mapping to K already exists. If you do not want this use: >vim
let g:termdebug_config['map_K'] = 0
If there is no g:termdebug_config you can use: >vim
let g:termdebug_map_K = 0
<
+ *termdebug_map_minus*
+The - key is normally mapped to |:Down| unless a buffer local mapping to the -
+key already exists. If you do not want this use: >vim
+ let g:termdebug_config['map_minus'] = 0
+<
+ *termdebug_map_plus*
+The + key is normally mapped to |:Up| unless a buffer local mapping to the +
+key already exists. If you do not want this use: >vim
+ let g:termdebug_config['map_plus'] = 0
+<
*termdebug_disasm_window*
-If you want the Asm window shown by default, set the flag to 1.
-the "disasm_window_height" entry can be used to set the window height: >vim
+If you want the Asm window shown by default, set the "disasm_window" flag to
+1. The "disasm_window_height" entry can be used to set the window height: >vim
let g:termdebug_config['disasm_window'] = 1
let g:termdebug_config['disasm_window_height'] = 15
If there is no g:termdebug_config you can use: >vim
let g:termdebug_disasm_window = 15
Any value greater than 1 will set the Asm window height to that value.
+If the current window has enough horizontal space, it will be vertically split
+and the Asm window will be shown side by side with the source code window (and
+the height option won't be used).
+
+ *termdebug_variables_window*
+If you want the Var window shown by default, set the "variables_window" flag
+to 1. The "variables_window_height" entry can be used to set the window
+height: >vim
+ let g:termdebug_config['variables_window'] = 1
+ let g:termdebug_config['variables_window_height'] = 15
+If there is no g:termdebug_config you can use: >vim
+ let g:termdebug_variables_window = 15
+Any value greater than 1 will set the Var window height to that value.
+If the current window has enough horizontal space, it will be vertically split
+and the Var window will be shown side by side with the source code window (and
+the height options won't be used).
Communication ~
*termdebug-communication*
@@ -428,14 +470,14 @@ If the command needs an argument use a List: >vim
If there is no g:termdebug_config you can use: >vim
let g:termdebugger = ['rr', 'replay', '--']
-To not use neovim floating windows for previewing variable evaluation, set the
+To not use Nvim floating windows for previewing variable evaluation, set the
`g:termdebug_useFloatingHover` variable like this: >vim
let g:termdebug_useFloatingHover = 0
If you are a mouse person, you can also define a mapping using your right
click to one of the terminal command like evaluate the variable under the
cursor: >vim
- nnoremap <RightMouse> :Evaluate<CR>
+ nnoremap <RightMouse> :Evaluate<CR>
or set/unset a breakpoint: >vim
nnoremap <RightMouse> :Break<CR>
@@ -451,7 +493,7 @@ The function will be called with the list of arguments so far, and a second
argument that is the name of the pty.
*gdb-version*
Only debuggers fully compatible with gdb will work. Vim uses the GDB/MI
-interface. The "new-ui" command requires gdb version 7.12 or later. if you
+interface. The "new-ui" command requires gdb version 7.12 or later. If you
get this error:
Undefined command: "new-ui". Try "help".~
Then your gdb is too old.
@@ -493,6 +535,20 @@ If there is no g:termdebug_config you can use: >vim
let g:termdebug_popup = 0
+Change default signs ~
+ *termdebug_signs*
+Termdebug uses the hex number of the breakpoint ID in the signcolumn to
+represent breakpoints. if it is greater than "0xFF", then it will be displayed
+as "F+", due to we really only have two screen cells for the sign.
+
+If you want to customize the breakpoint signs: >vim
+ let g:termdebug_config['sign'] = '>>'
+If there is no g:terminal_config yet you can use: >vim
+ let g:termdebug_config = {'sign': '>>'}
+
+After this, breakpoints will be displayed as `>>` in the signcolumn.
+
+
Vim window width ~
*termdebug_wide*
To change the width of the Vim window when debugging starts and use a vertical
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index b1af90a604..f47093782c 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -52,14 +52,16 @@ achieve special effects. These options come in three forms:
'lines'
Warning: This may have a lot of side effects.
- *:set-args* *E487* *E521*
+ *:set-args* *:set=* *E487* *E521*
:se[t] {option}={value} or
:se[t] {option}:{value}
Set string or number option to {value}.
For numeric options the value can be given in decimal,
hex (preceded with 0x) or octal (preceded with '0').
The old value can be inserted by typing 'wildchar' (by
- default this is a <Tab>). See |cmdline-completion|.
+ default this is a <Tab>). Many string options with
+ fixed syntax also support completing known values.
+ See |cmdline-completion| and |complete-set-option|.
White space between {option} and '=' is allowed and
will be ignored. White space between '=' and {value}
is not allowed.
@@ -93,6 +95,9 @@ achieve special effects. These options come in three forms:
When the option is a list of flags, {value} must be
exactly as they appear in the option. Remove flags
one by one to avoid problems.
+ The individual values from a comma separated list or
+ list of flags can be inserted by typing 'wildchar'.
+ See |complete-set-option|.
Also see |:set-args| above.
The {option} arguments to ":set" may be repeated. For example: >
@@ -131,10 +136,26 @@ To include white space in a string option value it has to be preceded with a
backslash. To include a backslash you have to use two. Effectively this
means that the number of backslashes in an option value is halved (rounded
down).
+In options 'path', 'cdpath', and 'tags', spaces have to be preceded with three
+backslashes instead because they can be separated by either commas or spaces.
+Comma-separated options like 'backupdir' and 'tags' will also require commas
+to be escaped with two backslashes, whereas this is not needed for
+non-comma-separated ones like 'makeprg'.
+When setting options using |:let| and |literal-string|, you need to use one
+fewer layer of backslash.
A few examples: >
- :set tags=tags\ /usr/tags results in "tags /usr/tags"
- :set tags=tags\\,file results in "tags\,file"
- :set tags=tags\\\ file results in "tags\ file"
+ :set makeprg=make\ file results in "make file"
+ :let &makeprg='make file' (same as above)
+ :set makeprg=make\\\ file results in "make\ file"
+ :set tags=tags\ /usr/tags results in "tags" and "/usr/tags"
+ :set tags=tags\\\ file results in "tags file"
+ :let &tags='tags\ file' (same as above)
+
+ :set makeprg=make,file results in "make,file"
+ :set makeprg=make\\,file results in "make\,file"
+ :set tags=tags,file results in "tags" and "file"
+ :set tags=tags\\,file results in "tags,file"
+ :let &tags='tags\,file' (same as above)
The "|" character separates a ":set" command from a following command. To
include the "|" in the option value, use "\|" instead. This example sets the
@@ -189,6 +210,8 @@ opt+=val" the expansion is done before the adding or removing.
Handling of local options *local-options*
+Note: The following also applies to |global-local| options.
+
Some of the options only apply to a window or buffer. Each window or buffer
has its own copy of this option, thus each can have its own value. This
allows you to set 'list' in one window but not in another. And set
@@ -238,6 +261,30 @@ The options local to a window are remembered for each buffer. This also
happens when the buffer is not loaded, but they are lost when the buffer is
wiped out |:bwipe|.
+Special local window options *local-noglobal*
+
+The following local window options won't be copied over when new windows are
+created, thus they behave slightly differently:
+
+ Option Reason ~
+ 'previewwindow' there can only be a single one
+ 'scroll' specific to existing window
+ 'winfixheight' specific to existing window
+ 'winfixwidth' specific to existing window
+
+Special local buffer options
+
+The following local buffer options won't be copied over when new buffers are
+created, thus they behave slightly differently:
+
+ Option Reason ~
+ 'filetype' explicitly set by autocommands
+ 'syntax' explicitly set by autocommands
+ 'bufhidden' denote |special-buffers|
+ 'buftype' denote |special-buffers|
+ 'readonly' will be detected automatically
+ 'modified' will be detected automatically
+
*:setl* *:setlocal*
:setl[ocal][!] ... Like ":set" but set only the value local to the
current buffer or window. Not all options have a
@@ -311,7 +358,6 @@ used. Thus it does the same as: >
Note: In the future more global options can be made |global-local|. Using
":setlocal" on a global option might work differently then.
-
*option-value-function*
Some options ('completefunc', 'omnifunc', 'operatorfunc', 'quickfixtextfunc',
'tagfunc' and 'thesaurusfunc') are set to a function name or a function
@@ -401,20 +447,15 @@ the system, mostly it is something like 256 or 1024 characters.
==============================================================================
2. Automatically setting options *auto-setting*
-Besides changing options with the ":set" command, there are three alternatives
-to set options automatically for one or more files:
-
-1. When starting Vim initializations are read from various places. See
- |initialization|. Most of them are performed for all editing sessions,
- and some of them depend on the directory where Vim is started.
- You can create an initialization file with |:mkvimrc|, |:mkview| and
- |:mksession|.
-2. If you start editing a new file, the automatic commands are executed.
- This can be used to set options for files matching a particular pattern and
- many other things. See |autocommand|.
-3. If you start editing a new file, and the 'modeline' option is on, a
- number of lines at the beginning and end of the file are checked for
- modelines. This is explained here.
+Besides changing options with the ":set" command, you can set options
+automatically in various ways:
+
+1. With a |config| file or a |startup| argument. You can create an
+ initialization file with |:mkvimrc|, |:mkview| and |:mksession|.
+2. |autocommand|s executed when you edit a file.
+3. ".nvim.lua" files in the current directory, if 'exrc' is enabled.
+4. |editorconfig| in the current buffer's directory or ancestors.
+5. 'modeline' settings found at the beginning or end of the file. See below.
*modeline* *vim:* *vi:* *ex:* *E520*
There are two forms of modelines. The first form:
@@ -580,16 +621,6 @@ supported use something like this: >
*E355*
A jump table for the options with a short description can be found at |Q_op|.
- *'aleph'* *'al'* *aleph* *Aleph*
-'aleph' 'al' number (default 224)
- global
- The ASCII code for the first letter of the Hebrew alphabet. The
- routine that maps the keyboard in Hebrew mode, both in Insert mode
- (when hkmap is set) and on the command-line (when hitting CTRL-_)
- outputs the Hebrew characters in the range [aleph..aleph+26].
- aleph=128 applies to PC code, and aleph=224 applies to ISO 8859-8.
- See |rileft.txt|.
-
*'allowrevins'* *'ari'* *'noallowrevins'* *'noari'*
'allowrevins' 'ari' boolean (default off)
global
@@ -599,7 +630,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'revins'.
*'ambiwidth'* *'ambw'*
-'ambiwidth' 'ambw' string (default: "single")
+'ambiwidth' 'ambw' string (default "single")
global
Tells Vim what to do with characters with East Asian Width Class
Ambiguous (such as Euro, Registered Sign, Copyright Sign, Greek
@@ -632,18 +663,8 @@ A jump table for the options with a short description can be found at |Q_op|.
set to one of CJK locales. See Unicode Standard Annex #11
(https://www.unicode.org/reports/tr11).
- *'autochdir'* *'acd'* *'noautochdir'* *'noacd'*
-'autochdir' 'acd' boolean (default off)
- global
- When on, Vim will change the current working directory whenever you
- open a file, switch buffers, delete a buffer or open/close a window.
- It will change to the directory containing the file which was opened
- or selected. When a buffer has no name it also has no directory, thus
- the current directory won't change when navigating to it.
- Note: When this option is on some plugins may not work.
-
*'arabic'* *'arab'* *'noarabic'* *'noarab'*
-'arabic' 'arab' boolean (default off)
+'arabic' 'arab' boolean (default off)
local to window
This option can be set to start editing Arabic text.
Setting this option will:
@@ -660,9 +681,8 @@ A jump table for the options with a short description can be found at |Q_op|.
option).
Also see |arabic.txt|.
- *'arabicshape'* *'arshape'*
- *'noarabicshape'* *'noarshape'*
-'arabicshape' 'arshape' boolean (default on)
+ *'arabicshape'* *'arshape'* *'noarabicshape'* *'noarshape'*
+'arabicshape' 'arshape' boolean (default on)
global
When on and 'termbidi' is off, the required visual character
corrections that need to take place for displaying the Arabic language
@@ -677,6 +697,16 @@ A jump table for the options with a short description can be found at |Q_op|.
Arabic is a complex language which requires other settings, for
further details see |arabic.txt|.
+ *'autochdir'* *'acd'* *'noautochdir'* *'noacd'*
+'autochdir' 'acd' boolean (default off)
+ global
+ When on, Vim will change the current working directory whenever you
+ open a file, switch buffers, delete a buffer or open/close a window.
+ It will change to the directory containing the file which was opened
+ or selected. When a buffer has no name it also has no directory, thus
+ the current directory won't change when navigating to it.
+ Note: When this option is on some plugins may not work.
+
*'autoindent'* *'ai'* *'noautoindent'* *'noai'*
'autoindent' 'ai' boolean (default on)
local to buffer
@@ -691,13 +721,8 @@ A jump table for the options with a short description can be found at |Q_op|.
line.
When 'smartindent' or 'cindent' is on the indent is changed in
a different way.
- The 'autoindent' option is reset when the 'paste' option is set and
- restored when 'paste' is reset.
- {small difference from Vi: After the indent is deleted when typing
- <Esc> or <CR>, the cursor position when moving up or down is after the
- deleted indent; Vi puts the cursor somewhere in the deleted indent}.
- *'autoread'* *'ar'* *'noautoread'* *'noar'*
+ *'autoread'* *'ar'* *'noautoread'* *'noar'*
'autoread' 'ar' boolean (default on)
global or local to buffer |global-local|
When a file has been detected to have been changed outside of Vim and
@@ -709,7 +734,8 @@ A jump table for the options with a short description can be found at |Q_op|.
using the global value: >
:set autoread<
<
- *'autowrite'* *'aw'* *'noautowrite'* *'noaw'*
+
+ *'autowrite'* *'aw'* *'noautowrite'* *'noaw'*
'autowrite' 'aw' boolean (default off)
global
Write the contents of the file, if it has been modified, on each
@@ -723,8 +749,11 @@ A jump table for the options with a short description can be found at |Q_op|.
'autowriteall' for that.
Some buffers will not be written, specifically when 'buftype' is
"nowrite", "nofile", "terminal" or "prompt".
+ USE WITH CARE: If you make temporary changes to a buffer that you
+ don't want to be saved this option may cause it to be saved anyway.
+ Renaming the buffer with ":file {name}" may help avoid this.
- *'autowriteall'* *'awa'* *'noautowriteall'* *'noawa'*
+ *'autowriteall'* *'awa'* *'noautowriteall'* *'noawa'*
'autowriteall' 'awa' boolean (default off)
global
Like 'autowrite', but also used for commands ":edit", ":enew", ":quit",
@@ -732,7 +761,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Setting this option also implies that Vim behaves like 'autowrite' has
been set.
- *'background'* *'bg'*
+ *'background'* *'bg'*
'background' 'bg' string (default "dark")
global
When set to "dark" or "light", adjusts the default color groups for
@@ -762,7 +791,7 @@ A jump table for the options with a short description can be found at |Q_op|.
option, you must load syntax.vim again to see the result. This can be
done with ":syntax on".
- *'backspace'* *'bs'*
+ *'backspace'* *'bs'*
'backspace' 'bs' string (default "indent,eol,start")
global
Influences the working of <BS>, <Del>, CTRL-W and CTRL-U in Insert
@@ -779,13 +808,6 @@ A jump table for the options with a short description can be found at |Q_op|.
When the value is empty, Vi compatible backspacing is used, none of
the ways mentioned for the items above are possible.
- For backwards compatibility with version 5.4 and earlier:
- value effect ~
- 0 same as ":set backspace=" (Vi compatible)
- 1 same as ":set backspace=indent,eol"
- 2 same as ":set backspace=indent,eol,start"
- 3 same as ":set backspace=indent,eol,nostop"
-
*'backup'* *'bk'* *'nobackup'* *'nobk'*
'backup' 'bk' boolean (default off)
global
@@ -801,7 +823,7 @@ A jump table for the options with a short description can be found at |Q_op|.
oldest version of a file.
*'backupcopy'* *'bkc'*
-'backupcopy' 'bkc' string (default: "auto")
+'backupcopy' 'bkc' string (default "auto")
global or local to buffer |global-local|
When writing a file and a backup is made, this option tells how it's
done. This is a comma-separated list of words.
@@ -897,8 +919,7 @@ A jump table for the options with a short description can be found at |Q_op|.
- Careful with '\' characters, type one before a space, type two to
get one in the option (see |option-backslash|), for example: >
:set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
-< - For backwards compatibility with Vim version 3.0 a '>' at the start
- of the option is removed.
+<
See also 'backup' and 'writebackup' options.
If you want to hide your backup files on Unix, consider this value: >
:set backupdir=./.backup,~/.backup,.,/tmp
@@ -910,7 +931,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'backupext'* *'bex'* *E589*
+ *'backupext'* *'bex'* *E589*
'backupext' 'bex' string (default "~")
global
String which is appended to a file name to make the name of the
@@ -918,7 +939,7 @@ A jump table for the options with a short description can be found at |Q_op|.
accidentally overwriting existing files with a backup file. You might
prefer using ".bak", but make sure that you don't have files with
".bak" that you want to keep.
- Only normal file name characters can be used; "/\*?[|<>" are illegal.
+ Only normal file name characters can be used; `/\*?[|<>` are illegal.
If you like to keep a lot of backups, you could use a BufWritePre
autocommand to change 'backupext' just before writing the file to
@@ -927,9 +948,9 @@ A jump table for the options with a short description can be found at |Q_op|.
< Use 'backupdir' to put the backup in a different directory.
*'backupskip'* *'bsk'*
-'backupskip' 'bsk' string (default: "$TMPDIR/*,$TMP/*,$TEMP/*"
- Unix: "/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"
- Mac: "/private/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*")
+'backupskip' 'bsk' string (default "$TMPDIR/*,$TMP/*,$TEMP/*"
+ Unix: "/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"
+ Mac: "/private/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*")
global
A list of file patterns. When one of the patterns matches with the
name of the file which is written, no backup file is created. Both
@@ -945,7 +966,7 @@ A jump table for the options with a short description can be found at |Q_op|.
backups if you don't care about losing the file.
Note that environment variables are not expanded. If you want to use
- $HOME you must expand it explicitly, e.g.: >
+ $HOME you must expand it explicitly, e.g.: >vim
:let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
< Note that the default also makes sure that "crontab -e" works (when a
@@ -990,7 +1011,7 @@ A jump table for the options with a short description can be found at |Q_op|.
indicate that an error occurred. It can be silenced by adding the
"error" keyword.
- *'binary'* *'bin'* *'nobinary'* *'nobin'*
+ *'binary'* *'bin'* *'nobinary'* *'nobin'*
'binary' 'bin' boolean (default off)
local to buffer
This option should be set before editing a binary file. You can also
@@ -1020,7 +1041,7 @@ A jump table for the options with a short description can be found at |Q_op|.
the last line if there is none; this would make the file longer). See
the 'endofline' option.
- *'bomb'* *'nobomb'*
+ *'bomb'* *'nobomb'*
'bomb' boolean (default off)
local to buffer
When writing a file and the following conditions are met, a BOM (Byte
@@ -1046,14 +1067,14 @@ A jump table for the options with a short description can be found at |Q_op|.
break if 'linebreak' is on. Only works for ASCII characters.
*'breakindent'* *'bri'* *'nobreakindent'* *'nobri'*
-'breakindent' 'bri' boolean (default off)
+'breakindent' 'bri' boolean (default off)
local to window
Every wrapped line will continue visually indented (same amount of
space as the beginning of that line), thus preserving horizontal blocks
of text.
- *'breakindentopt'* *'briopt'*
-'breakindentopt' 'briopt' string (default empty)
+ *'breakindentopt'* *'briopt'*
+'breakindentopt' 'briopt' string (default "")
local to window
Settings for 'breakindent'. It can consist of the following optional
items and must be separated by a comma:
@@ -1084,7 +1105,7 @@ A jump table for the options with a short description can be found at |Q_op|.
(default: off)
*'browsedir'* *'bsdir'*
-'browsedir' 'bsdir' string (default: "last")
+'browsedir' 'bsdir' string (default "last")
global
Which directory to use for the file browser:
last Use same directory as with last file browser, where a
@@ -1094,8 +1115,8 @@ A jump table for the options with a short description can be found at |Q_op|.
{path} Use the specified directory
*'bufhidden'* *'bh'*
-'bufhidden' 'bh' string (default: "")
- local to buffer
+'bufhidden' 'bh' string (default "")
+ local to buffer |local-noglobal|
This option specifies what happens when a buffer is no longer
displayed in a window:
<empty> follow the global 'hidden' option
@@ -1117,7 +1138,7 @@ A jump table for the options with a short description can be found at |Q_op|.
special kinds of buffers. See |special-buffers|.
*'buflisted'* *'bl'* *'nobuflisted'* *'nobl'* *E85*
-'buflisted' 'bl' boolean (default: on)
+'buflisted' 'bl' boolean (default on)
local to buffer
When this option is set, the buffer shows up in the buffer list. If
it is reset it is not used for ":bnext", "ls", the Buffers menu, etc.
@@ -1126,8 +1147,8 @@ A jump table for the options with a short description can be found at |Q_op|.
But not when moving to a buffer with ":buffer".
*'buftype'* *'bt'* *E382*
-'buftype' 'bt' string (default: "")
- local to buffer
+'buftype' 'bt' string (default "")
+ local to buffer |local-noglobal|
The value of this option specifies the type of a buffer:
<empty> normal buffer
acwrite buffer will always be written with |BufWriteCmd|s
@@ -1174,7 +1195,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|FileWriteCmd| or |FileAppendCmd| autocommands.
*'casemap'* *'cmp'*
-'casemap' 'cmp' string (default: "internal,keepascii")
+'casemap' 'cmp' string (default "internal,keepascii")
global
Specifies details about changing the case of letters. It may contain
these words, separated by a comma:
@@ -1186,16 +1207,18 @@ A jump table for the options with a short description can be found at |Q_op|.
case mapping, the current locale is not effective.
This probably only matters for Turkish.
- *'cdhome'* *'cdh'*
-'cdhome' 'cdh' boolean (default: off)
+ *'cdhome'* *'cdh'* *'nocdhome'* *'nocdh'*
+'cdhome' 'cdh' boolean (default off)
global
When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
current working directory to the |$HOME| directory like in Unix.
When off, those commands just print the current directory name.
On Unix this option has no effect.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
- *'cdpath'* *'cd'* *E344* *E346*
-'cdpath' 'cd' string (default: equivalent to $CDPATH or ",,")
+ *'cdpath'* *'cd'* *E344* *E346*
+'cdpath' 'cd' string (default equivalent to $CDPATH or ",,")
global
This is a list of directories which will be searched when using the
|:cd|, |:tcd| and |:lcd| commands, provided that the directory being
@@ -1213,8 +1236,8 @@ A jump table for the options with a short description can be found at |Q_op|.
security reasons.
(parts of 'cdpath' can be passed to the shell to expand file names).
- *'cedit'*
-'cedit' string (default: CTRL-F)
+ *'cedit'*
+'cedit' string (default CTRL-F)
global
The key used in Command-line Mode to open the command-line window.
Only non-printable keys are allowed.
@@ -1225,15 +1248,15 @@ A jump table for the options with a short description can be found at |Q_op|.
< |Nvi| also has this option, but it only uses the first character.
See |cmdwin|.
- *'channel'*
-'channel' number (default: 0)
+ *'channel'*
+'channel' number (default 0)
local to buffer
|channel| connected to the buffer, or 0 if no channel is connected.
In a |:terminal| buffer this is the terminal channel.
Read-only.
*'charconvert'* *'ccv'* *E202* *E214* *E513*
-'charconvert' 'ccv' string (default "")
+'charconvert' 'ccv' string (default "")
global
An expression that is used for character encoding conversion. It is
evaluated when a file that is to be read or has been written has a
@@ -1268,7 +1291,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'cindent'* *'cin'* *'nocindent'* *'nocin'*
+ *'cindent'* *'cin'* *'nocindent'* *'nocin'*
'cindent' 'cin' boolean (default off)
local to buffer
Enables automatic C program indenting. See 'cinkeys' to set the keys
@@ -1281,9 +1304,8 @@ A jump table for the options with a short description can be found at |Q_op|.
See |C-indenting|.
When you don't like the way 'cindent' works, try the 'smartindent'
option or 'indentexpr'.
- This option is not used when 'paste' is set.
- *'cinkeys'* *'cink'*
+ *'cinkeys'* *'cink'*
'cinkeys' 'cink' string (default "0{,0},0),0],:,0#,!^F,o,O,e")
local to buffer
A list of keys that, when typed in Insert mode, cause reindenting of
@@ -1299,6 +1321,14 @@ A jump table for the options with a short description can be found at |Q_op|.
program. See |cinoptions-values| for the values of this option, and
|C-indenting| for info on C indenting in general.
+ *'cinscopedecls'* *'cinsd'*
+'cinscopedecls' 'cinsd' string (default "public,protected,private")
+ local to buffer
+ Keywords that are interpreted as a C++ scope declaration by |cino-g|.
+ Useful e.g. for working with the Qt framework that defines additional
+ scope declarations "signals", "public slots" and "private slots": >
+ set cinscopedecls+=signals,public\ slots,private\ slots
+<
*'cinwords'* *'cinw'*
'cinwords' 'cinw' string (default "if,else,while,do,for,switch")
@@ -1310,15 +1340,7 @@ A jump table for the options with a short description can be found at |Q_op|.
matter, include the keyword both the uppercase and lowercase:
"if,If,IF".
- *'cinscopedecls'* *'cinsd'*
-'cinscopedecls' 'cinsd' string (default "public,protected,private")
- local to buffer
- Keywords that are interpreted as a C++ scope declaration by |cino-g|.
- Useful e.g. for working with the Qt framework that defines additional
- scope declarations "signals", "public slots" and "private slots": >
- set cinscopedecls+=signals,public\ slots,private\ slots
-
-< *'clipboard'* *'cb'*
+ *'clipboard'* *'cb'*
'clipboard' 'cb' string (default "")
global
This option is a list of comma-separated names.
@@ -1342,11 +1364,11 @@ A jump table for the options with a short description can be found at |Q_op|.
register. When "unnamed" is also included to the
option, yank and delete operations (but not put)
will additionally copy the text into register
- '*'. See |clipboard|.
+ "*". See |clipboard|.
*'cmdheight'* *'ch'*
'cmdheight' 'ch' number (default 1)
- global or local to tab page
+ global or local to tab page |global-local|
Number of screen lines to use for the command-line. Helps avoiding
|hit-enter| prompts.
The value of this option is stored with the tab page, so that each tab
@@ -1376,7 +1398,7 @@ A jump table for the options with a short description can be found at |Q_op|.
The screen column can be an absolute number, or a number preceded with
'+' or '-', which is added to or subtracted from 'textwidth'. >
- :set cc=+1 " highlight column after 'textwidth'
+ :set cc=+1 " highlight column after 'textwidth'
:set cc=+1,+2,+3 " highlight three columns after 'textwidth'
:hi ColorColumn ctermbg=lightgrey guibg=lightgrey
<
@@ -1400,22 +1422,21 @@ A jump table for the options with a short description can be found at |Q_op|.
< Minimum value is 12, maximum value is 10000.
*'comments'* *'com'* *E524* *E525*
-'comments' 'com' string (default
- "s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-")
+'comments' 'com' string (default "s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•")
local to buffer
A comma-separated list of strings that can start a comment line. See
|format-comments|. See |option-backslash| about using backslashes to
insert a space.
*'commentstring'* *'cms'* *E537*
-'commentstring' 'cms' string (default "/*%s*/")
+'commentstring' 'cms' string (default "")
local to buffer
A template for a comment. The "%s" in the value is replaced with the
- comment text. Currently only used to add markers for folding, see
- |fold-marker|.
+ comment text. For example, C uses "/*%s*/". Currently only used to
+ add markers for folding, see |fold-marker|.
*'complete'* *'cpt'* *E535*
-'complete' 'cpt' string (default: ".,w,b,u,t")
+'complete' 'cpt' string (default ".,w,b,u,t")
local to buffer
This option specifies how keyword completion |ins-completion| works
when CTRL-P or CTRL-N are used. It is also used for whole-line
@@ -1439,6 +1460,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|i_CTRL-X_CTRL-D|
] tag completion
t same as "]"
+ f scan the buffer names (as opposed to buffer contents)
Unloaded buffers are not loaded, thus their autocmds |:autocmd| are
not executed, this may lead to unexpected completions from some files
@@ -1450,7 +1472,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions).
*'completefunc'* *'cfu'*
-'completefunc' 'cfu' string (default: empty)
+'completefunc' 'cfu' string (default "")
local to buffer
This option specifies a function to be used for Insert mode completion
with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
@@ -1461,23 +1483,8 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'completeslash'* *'csl'*
-'completeslash' 'csl' string (default: "")
- local to buffer
- {only for MS-Windows}
- When this option is set it overrules 'shellslash' for completion:
- - When this option is set to "slash", a forward slash is used for path
- completion in insert mode. This is useful when editing HTML tag, or
- Makefile with 'noshellslash' on MS-Windows.
- - When this option is set to "backslash", backslash is used. This is
- useful when editing a batch file with 'shellslash' set on MS-Windows.
- - When this option is empty, same character is used as for
- 'shellslash'.
- For Insert mode completion the buffer-local value is used. For
- command line completion the global value is used.
-
*'completeopt'* *'cot'*
-'completeopt' 'cot' string (default: "menu,preview")
+'completeopt' 'cot' string (default "menu,preview")
global
A comma-separated list of options for Insert mode completion
|ins-completion|. The supported values are:
@@ -1508,9 +1515,23 @@ A jump table for the options with a short description can be found at |Q_op|.
select one from the menu. Only works in combination with
"menu" or "menuone".
+ *'completeslash'* *'csl'*
+'completeslash' 'csl' string (default "")
+ local to buffer
+ only for MS-Windows
+ When this option is set it overrules 'shellslash' for completion:
+ - When this option is set to "slash", a forward slash is used for path
+ completion in insert mode. This is useful when editing HTML tag, or
+ Makefile with 'noshellslash' on MS-Windows.
+ - When this option is set to "backslash", backslash is used. This is
+ useful when editing a batch file with 'shellslash' set on MS-Windows.
+ - When this option is empty, same character is used as for
+ 'shellslash'.
+ For Insert mode completion the buffer-local value is used. For
+ command line completion the global value is used.
- *'concealcursor'* *'cocu'*
-'concealcursor' 'cocu' string (default: "")
+ *'concealcursor'* *'cocu'*
+'concealcursor' 'cocu' string (default "")
local to window
Sets the modes in which text in the cursor line can also be concealed.
When the current mode is listed then concealing happens just like in
@@ -1528,9 +1549,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Keep in mind that the cursor position is not always where it's
displayed. E.g., when moving vertically it may change column.
-
*'conceallevel'* *'cole'*
-'conceallevel' 'cole' number (default 0)
+'conceallevel' 'cole' number (default 0)
local to window
Determine how text with the "conceal" syntax attribute |:syn-conceal|
is shown:
@@ -1552,7 +1572,7 @@ A jump table for the options with a short description can be found at |Q_op|.
option.
*'confirm'* *'cf'* *'noconfirm'* *'nocf'*
-'confirm' 'cf' boolean (default off)
+'confirm' 'cf' boolean (default off)
global
When 'confirm' is on, certain operations that would normally
fail because of unsaved changes to a buffer, e.g. ":q" and ":e",
@@ -1577,7 +1597,7 @@ A jump table for the options with a short description can be found at |Q_op|.
See 'preserveindent'.
*'cpoptions'* *'cpo'* *cpo*
-'cpoptions' 'cpo' string (default: "aABceFs_")
+'cpoptions' 'cpo' string (default "aABceFs_")
global
A sequence of single character flags. When a character is present
this indicates Vi-compatible behavior. This is used for things where
@@ -1811,7 +1831,7 @@ A jump table for the options with a short description can be found at |Q_op|.
whitespace following the word in the motion.
*'cursorbind'* *'crb'* *'nocursorbind'* *'nocrb'*
-'cursorbind' 'crb' boolean (default off)
+'cursorbind' 'crb' boolean (default off)
local to window
When this option is set, as the cursor in the current
window moves other cursorbound windows (windows that also have
@@ -1821,7 +1841,6 @@ A jump table for the options with a short description can be found at |Q_op|.
inserted and deleted lines (though not characters within a line) are
taken into account.
-
*'cursorcolumn'* *'cuc'* *'nocursorcolumn'* *'nocuc'*
'cursorcolumn' 'cuc' boolean (default off)
local to window
@@ -1842,9 +1861,8 @@ A jump table for the options with a short description can be found at |Q_op|.
When Visual mode is active the highlighting isn't used to make it
easier to see the selected text.
-
- *'cursorlineopt'* *'culopt'*
-'cursorlineopt' 'culopt' string (default: "number,line")
+ *'cursorlineopt'* *'culopt'*
+'cursorlineopt' 'culopt' string (default "both")
local to window
Comma-separated list of settings for how 'cursorline' is displayed.
Valid values:
@@ -1860,8 +1878,7 @@ A jump table for the options with a short description can be found at |Q_op|.
"line" and "screenline" cannot be used together.
-
- *'debug'*
+ *'debug'*
'debug' string (default "")
global
These values can be used:
@@ -1876,17 +1893,16 @@ A jump table for the options with a short description can be found at |Q_op|.
'indentexpr'.
*'define'* *'def'*
-'define' 'def' string (default "^\s*#\s*define")
+'define' 'def' string (default "")
global or local to buffer |global-local|
Pattern to be used to find a macro definition. It is a search
pattern, just like for the "/" command. This option is used for the
commands like "[i" and "[d" |include-search|. The 'isident' option is
- used to recognize the defined name after the match:
+ used to recognize the defined name after the match: >
{match with 'define'}{non-ID chars}{defined name}{non-ID char}
- See |option-backslash| about inserting backslashes to include a space
+< See |option-backslash| about inserting backslashes to include a space
or backslash.
- The default value is for C programs. For C++ this value would be
- useful, to include const type declarations: >
+ For C++ this value would be useful, to include const type declarations: >
^\(#\s*define\|[a-z]*\s*const\s*[a-z]*\)
< You can also use "\ze" just before the name and continue the pattern
to check what is following. E.g. for Javascript, if a function is
@@ -1900,7 +1916,7 @@ A jump table for the options with a short description can be found at |Q_op|.
<
*'delcombine'* *'deco'* *'nodelcombine'* *'nodeco'*
-'delcombine' 'deco' boolean (default off)
+'delcombine' 'deco' boolean (default off)
global
If editing Unicode and this option is set, backspace and Normal mode
"x" delete each combining character on its own. When it is off (the
@@ -1938,13 +1954,13 @@ A jump table for the options with a short description can be found at |Q_op|.
uses another default.
Backticks cannot be used in this option for security reasons.
- *'diff'* *'nodiff'*
+ *'diff'* *'nodiff'*
'diff' boolean (default off)
local to window
Join the current window in the group of windows that shows differences
between files. See |diff-mode|.
- *'dex'* *'diffexpr'*
+ *'diffexpr'* *'dex'*
'diffexpr' 'dex' string (default "")
global
Expression which is evaluated to obtain a diff file (either ed-style
@@ -1952,7 +1968,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'dip'* *'diffopt'*
+ *'diffopt'* *'dip'*
'diffopt' 'dip' string (default "internal,filler,closeoff")
global
Option settings for diff mode. It can consist of the following items.
@@ -1969,7 +1985,8 @@ A jump table for the options with a short description can be found at |Q_op|.
When omitted a context of six lines is used.
When using zero the context is actually one,
since folds require a line in between, also
- for a deleted line.
+ for a deleted line. Set it to a very large
+ value (999999) to disable folding completely.
See |fold-diff|.
iblank Ignore changes where lines are all blank. Adds
@@ -2045,7 +2062,7 @@ A jump table for the options with a short description can be found at |Q_op|.
hunks of up to 30 lines each, or a 3 buffer
diff with hunks of up to 20 lines each.
- algorithm:{text} Use the specified diff algorithm with the
+ algorithm:{text} Use the specified diff algorithm with the
internal diff engine. Currently supported
algorithms are:
myers the default algorithm
@@ -2060,7 +2077,8 @@ A jump table for the options with a short description can be found at |Q_op|.
:set diffopt=internal,filler,foldcolumn:3
:set diffopt-=internal " do NOT use the internal diff parser
<
- *'digraph'* *'dg'* *'nodigraph'* *'nodg'*
+
+ *'digraph'* *'dg'* *'nodigraph'* *'nodg'*
'digraph' 'dg' boolean (default off)
global
Enable the entering of digraphs in Insert mode with {char1} <BS>
@@ -2073,7 +2091,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Possible items:
- The swap file will be created in the first directory where this is
- possible. If it is not possible in any directory, but last
+ possible. If it is not possible in any directory, but last
directory listed in the option does not exist, it is created.
- Empty means that no swap file will be used (recovery is
impossible!) and no |E303| error will be given.
@@ -2102,23 +2120,17 @@ A jump table for the options with a short description can be found at |Q_op|.
- Careful with '\' characters, type one before a space, type two to
get one in the option (see |option-backslash|), for example: >
:set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
-< - For backwards compatibility with Vim version 3.0 a '>' at the start
- of the option is removed.
- Using "." first in the list is recommended. This means that editing
- the same file twice will result in a warning. Using "/tmp" on Unix is
- discouraged: When the system crashes you lose the swap file.
- "/var/tmp" is often not cleared when rebooting, thus is a better
- choice than "/tmp". But others on the computer may be able to see the
- files, and it can contain a lot of files, your swap files get lost in
- the crowd. That is why a "tmp" directory in your home directory is
- tried first.
- The use of |:set+=| and |:set-=| is preferred when adding or removing
- directories from the list. This avoids problems when a future version
- uses another default.
+<
+ Editing the same file twice will result in a warning. Using "/tmp" on
+ is discouraged: if the system crashes you lose the swap file. And
+ others on the computer may be able to see the files.
+ Use |:set+=| and |:set-=| when adding or removing directories from the
+ list, this avoids problems if the Nvim default is changed.
+
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'display'* *'dy'*
+ *'display'* *'dy'*
'display' 'dy' string (default "lastline")
global
Change the way text is displayed. This is a comma-separated list of
@@ -2147,8 +2159,8 @@ A jump table for the options with a short description can be found at |Q_op|.
hor horizontally, height of windows is not affected
both width and height of windows is affected
- *'emoji'* *'emo'* *'noemoji'* *'noemo'*
-'emoji' 'emo' boolean (default: on)
+ *'emoji'* *'emo'* *'noemoji'* *'noemo'*
+'emoji' 'emo' boolean (default on)
global
When on all Unicode emoji characters are considered to be full width.
This excludes "text emoji" characters, which are normally displayed as
@@ -2156,8 +2168,9 @@ A jump table for the options with a short description can be found at |Q_op|.
and it has been determined on trial-and-error basis. Use the
|setcellwidths()| function to change the behavior.
- *'encoding'* *'enc'* *E543*
-'encoding' 'enc'
+ *'encoding'* *'enc'*
+'encoding' 'enc' string (default "utf-8")
+ global
String-encoding used internally and for |RPC| communication.
Always UTF-8.
@@ -2189,7 +2202,7 @@ A jump table for the options with a short description can be found at |Q_op|.
be kept. But you can change it if you want to.
See |eol-and-eof| for example settings.
- *'equalalways'* *'ea'* *'noequalalways'* *'noea'*
+ *'equalalways'* *'ea'* *'noequalalways'* *'noea'*
'equalalways' 'ea' boolean (default on)
global
When on, all the windows are automatically made the same size after
@@ -2228,7 +2241,7 @@ A jump table for the options with a short description can be found at |Q_op|.
or do nothing. See 'belloff' to finetune when to ring the bell.
*'errorfile'* *'ef'*
-'errorfile' 'ef' string (default: "errors.err")
+'errorfile' 'ef' string (default "errors.err")
global
Name of the errorfile for the QuickFix mode (see |:cf|).
When the "-q" command-line argument is used, 'errorfile' is set to the
@@ -2254,34 +2267,31 @@ A jump table for the options with a short description can be found at |Q_op|.
Otherwise this is a comma-separated list of event names. Example: >
:set ei=WinEnter,WinLeave
<
- *'expandtab'* *'et'* *'noexpandtab'* *'noet'*
+
+ *'expandtab'* *'et'* *'noexpandtab'* *'noet'*
'expandtab' 'et' boolean (default off)
local to buffer
In Insert mode: Use the appropriate number of spaces to insert a
<Tab>. Spaces are used in indents with the '>' and '<' commands and
when 'autoindent' is on. To insert a real tab when 'expandtab' is
on, use CTRL-V<Tab>. See also |:retab| and |ins-expandtab|.
- This option is reset when the 'paste' option is set and restored when
- the 'paste' option is reset.
*'exrc'* *'ex'* *'noexrc'* *'noex'*
-'exrc' 'ex' boolean (default off)
+'exrc' 'ex' boolean (default off)
global
- Enables the reading of .nvim.lua, .nvimrc, and .exrc files in the current
- directory.
-
- The file is only sourced if the user indicates the file is trusted. If
- it is, the SHA256 hash of the file contents and the full path of the
- file are persisted to a trust database. The user is only prompted
- again if the file contents change. See |vim.secure.read()|.
+ Automatically execute .nvim.lua, .nvimrc, and .exrc files in the
+ current directory, if the file is in the |trust| list. Use |:trust| to
+ manage trusted files. See also |vim.secure.read()|.
- Use |:trust| to manage the trusted file database.
+ Compare 'exrc' to |editorconfig|:
+ - 'exrc' can execute any code; editorconfig only specifies settings.
+ - 'exrc' is Nvim-specific; editorconfig works in other editors.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'fileencoding'* *'fenc'* *E213*
-'fileencoding' 'fenc' string (default: "")
+ *'fileencoding'* *'fenc'* *E213*
+'fileencoding' 'fenc' string (default "")
local to buffer
File-content encoding for the current buffer. Conversion is done with
iconv() or as specified with 'charconvert'.
@@ -2322,7 +2332,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be changed when 'modifiable' is off.
*'fileencodings'* *'fencs'*
-'fileencodings' 'fencs' string (default: "ucs-bom,utf-8,default,latin1")
+'fileencodings' 'fencs' string (default "ucs-bom,utf-8,default,latin1")
global
This is a list of character encodings considered when starting to edit
an existing file. When a file is read, Vim tries to use the first
@@ -2371,9 +2381,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Setting this option does not have an effect until the next time a file
is read.
- *'fileformat'* *'ff'*
-'fileformat' 'ff' string (Windows default: "dos",
- Unix default: "unix")
+ *'fileformat'* *'ff'*
+'fileformat' 'ff' string (default Windows: "dos", Unix: "unix")
local to buffer
This gives the <EOL> of the current buffer, which is used for
reading/writing the buffer from/to a file:
@@ -2391,10 +2400,8 @@ A jump table for the options with a short description can be found at |Q_op|.
option is set, because the file would be different when written.
This option cannot be changed when 'modifiable' is off.
- *'fileformats'* *'ffs'*
-'fileformats' 'ffs' string (default:
- Win32: "dos,unix",
- Unix: "unix,dos")
+ *'fileformats'* *'ffs'*
+'fileformats' 'ffs' string (default Windows: "dos,unix", Unix: "unix,dos")
global
This gives the end-of-line (<EOL>) formats that will be tried when
starting to edit a new buffer and when reading a file into an existing
@@ -2444,14 +2451,14 @@ A jump table for the options with a short description can be found at |Q_op|.
*'fileignorecase'* *'fic'* *'nofileignorecase'* *'nofic'*
'fileignorecase' 'fic' boolean (default on for systems where case in file
- names is normally ignored)
+ names is normally ignored)
global
When set case is ignored when using file names and directories.
See 'wildignorecase' for only ignoring case when doing completion.
- *'filetype'* *'ft'*
-'filetype' 'ft' string (default: "")
- local to buffer
+ *'filetype'* *'ft'*
+'filetype' 'ft' string (default "")
+ local to buffer |local-noglobal|
When this option is set, the FileType autocommand event is triggered.
All autocommands that match with the value of this option will be
executed. Thus the value of 'filetype' is used in place of the file
@@ -2472,7 +2479,7 @@ A jump table for the options with a short description can be found at |Q_op|.
one dot may appear.
This option is not copied to another buffer, independent of the 's' or
'S' flag in 'cpoptions'.
- Only normal file name characters can be used, "/\*?[|<>" are illegal.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
*'fillchars'* *'fcs'*
'fillchars' 'fcs' string (default "")
@@ -2483,8 +2490,8 @@ A jump table for the options with a short description can be found at |Q_op|.
and the value of that item:
item default Used for ~
- stl ' ' or '^' statusline of the current window
- stlnc ' ' or '=' statusline of the non-current windows
+ stl ' ' statusline of the current window
+ stlnc ' ' statusline of the non-current windows
wbr ' ' window bar
horiz '─' or '-' horizontal separators |:split|
horizup '┴' or '-' upwards facing horizontal separator
@@ -2503,9 +2510,7 @@ A jump table for the options with a short description can be found at |Q_op|.
eob '~' empty lines at the end of a buffer
lastline '@' 'display' contains lastline/truncate
- Any one that is omitted will fall back to the default. For "stl" and
- "stlnc" the space will be used when there is highlighting, '^' or '='
- otherwise.
+ Any one that is omitted will fall back to the default.
Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and
"verthoriz" are only used when 'laststatus' is 3, since only vertical
@@ -2516,10 +2521,8 @@ A jump table for the options with a short description can be found at |Q_op|.
default to single-byte alternatives.
Example: >
- :set fillchars=stl:^,stlnc:=,vert:│,fold:·,diff:-
-< This is similar to the default, except that these characters will also
- be used when there is highlighting.
-
+ :set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
+<
For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
single-byte and multibyte characters are supported. But double-width
characters are not supported.
@@ -2553,25 +2556,25 @@ A jump table for the options with a short description can be found at |Q_op|.
See |eol-and-eof| for example settings.
*'foldclose'* *'fcl'*
-'foldclose' 'fcl' string (default "")
+'foldclose' 'fcl' string (default "")
global
When set to "all", a fold is closed when the cursor isn't in it and
its level is higher than 'foldlevel'. Useful if you want folds to
automatically close when moving out of them.
*'foldcolumn'* *'fdc'*
-'foldcolumn' 'fdc' string (default "0")
+'foldcolumn' 'fdc' string (default "0")
local to window
When and how to draw the foldcolumn. Valid values are:
"auto": resize to the minimum amount of folds to display.
"auto:[1-9]": resize to accommodate multiple folds up to the
selected level
- 0: to disable foldcolumn
+ "0": to disable foldcolumn
"[1-9]": to display a fixed number of columns
See |folding|.
*'foldenable'* *'fen'* *'nofoldenable'* *'nofen'*
-'foldenable' 'fen' boolean (default on)
+'foldenable' 'fen' boolean (default on)
local to window
When off, all folds are open. This option can be used to quickly
switch between showing all text unfolded and viewing the text with
@@ -2582,10 +2585,12 @@ A jump table for the options with a short description can be found at |Q_op|.
See |folding|.
*'foldexpr'* *'fde'*
-'foldexpr' 'fde' string (default: "0")
+'foldexpr' 'fde' string (default "0")
local to window
The expression used for when 'foldmethod' is "expr". It is evaluated
- for each line to obtain its fold level. See |fold-expr|.
+ for each line to obtain its fold level. The context is set to the
+ script where 'foldexpr' was set, script-local items can be accessed.
+ See |fold-expr| for the usage.
The expression will be evaluated in the |sandbox| if set from a
modeline, see |sandbox-option|.
@@ -2596,7 +2601,7 @@ A jump table for the options with a short description can be found at |Q_op|.
evaluating 'foldexpr' |textlock|.
*'foldignore'* *'fdi'*
-'foldignore' 'fdi' string (default: "#")
+'foldignore' 'fdi' string (default "#")
local to window
Used only when 'foldmethod' is "indent". Lines starting with
characters in 'foldignore' will get their fold level from surrounding
@@ -2604,7 +2609,7 @@ A jump table for the options with a short description can be found at |Q_op|.
The default "#" works well for C programs. See |fold-indent|.
*'foldlevel'* *'fdl'*
-'foldlevel' 'fdl' number (default: 0)
+'foldlevel' 'fdl' number (default 0)
local to window
Sets the fold level: Folds with a higher level will be closed.
Setting this option to zero will close all folds. Higher numbers will
@@ -2612,8 +2617,8 @@ A jump table for the options with a short description can be found at |Q_op|.
This option is set by commands like |zm|, |zM| and |zR|.
See |fold-foldlevel|.
- *'foldlevelstart'* *'fdls'*
-'foldlevelstart' 'fdls' number (default: -1)
+ *'foldlevelstart'* *'fdls'*
+'foldlevelstart' 'fdls' number (default -1)
global
Sets 'foldlevel' when starting to edit another buffer in a window.
Useful to always start editing with all folds closed (value zero),
@@ -2625,8 +2630,8 @@ A jump table for the options with a short description can be found at |Q_op|.
overrule the 'foldlevel' value for specific files.
When the value is negative, it is not used.
- *'foldmarker'* *'fmr'* *E536*
-'foldmarker' 'fmr' string (default: "{{{,}}}")
+ *'foldmarker'* *'fmr'* *E536*
+'foldmarker' 'fmr' string (default "{{{,}}}")
local to window
The start and end marker used when 'foldmethod' is "marker". There
must be one comma, which separates the start and end marker. The
@@ -2634,7 +2639,7 @@ A jump table for the options with a short description can be found at |Q_op|.
See |fold-marker|.
*'foldmethod'* *'fdm'*
-'foldmethod' 'fdm' string (default: "manual")
+'foldmethod' 'fdm' string (default "manual")
local to window
The kind of folding used for the current window. Possible values:
|fold-manual| manual Folds are created manually.
@@ -2645,7 +2650,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|fold-diff| diff Fold text that is not changed.
*'foldminlines'* *'fml'*
-'foldminlines' 'fml' number (default: 1)
+'foldminlines' 'fml' number (default 1)
local to window
Sets the number of screen lines above which a fold can be displayed
closed. Also for manually closed folds. With the default value of
@@ -2656,15 +2661,14 @@ A jump table for the options with a short description can be found at |Q_op|.
than 'foldminlines', a following "zc" may close a containing fold.
*'foldnestmax'* *'fdn'*
-'foldnestmax' 'fdn' number (default: 20)
+'foldnestmax' 'fdn' number (default 20)
local to window
Sets the maximum nesting of folds for the "indent" and "syntax"
methods. This avoids that too many folds will be created. Using more
than 20 doesn't work, because the internal limit is 20.
*'foldopen'* *'fdo'*
-'foldopen' 'fdo' string (default: "block,hor,mark,percent,quickfix,
- search,tag,undo")
+'foldopen' 'fdo' string (default "block,hor,mark,percent,quickfix,search,tag,undo")
global
Specifies for which type of commands folds will be opened, if the
command moves the cursor into a closed fold. It is a comma-separated
@@ -2675,7 +2679,7 @@ A jump table for the options with a short description can be found at |Q_op|.
item commands ~
all any
- block "(", "{", "[[", "[{", etc.
+ block (, {, [[, [{, etc.
hor horizontal movements: "l", "w", "fx", etc.
insert any command in Insert mode
jump far jumps: "G", "gg", etc.
@@ -2698,10 +2702,12 @@ A jump table for the options with a short description can be found at |Q_op|.
set the 'foldclose' option to "all".
*'foldtext'* *'fdt'*
-'foldtext' 'fdt' string (default: "foldtext()")
+'foldtext' 'fdt' string (default "foldtext()")
local to window
An expression which is used to specify the text displayed for a closed
- fold. See |fold-foldtext|.
+ fold. The context is set to the script where 'foldexpr' was set,
+ script-local items can be accessed. See |fold-foldtext| for the
+ usage.
The expression will be evaluated in the |sandbox| if set from a
modeline, see |sandbox-option|.
@@ -2711,7 +2717,7 @@ A jump table for the options with a short description can be found at |Q_op|.
evaluating 'foldtext' |textlock|.
*'formatexpr'* *'fex'*
-'formatexpr' 'fex' string (default "")
+'formatexpr' 'fex' string (default "")
local to buffer
Expression which is evaluated to format a range of lines for the |gq|
operator or automatic formatting (see 'formatoptions'). When this
@@ -2742,15 +2748,17 @@ A jump table for the options with a short description can be found at |Q_op|.
the script ID (|local-function|). Example: >
set formatexpr=s:MyFormatExpr()
set formatexpr=<SID>SomeFormatExpr()
-<
+< Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
The expression will be evaluated in the |sandbox| when set from a
modeline, see |sandbox-option|. That stops the option from working,
since changing the buffer text is not allowed.
This option cannot be set in a modeline when 'modelineexpr' is off.
NOTE: This option is set to "" when 'compatible' is set.
- *'formatlistpat'* *'flp'*
-'formatlistpat' 'flp' string (default: "^\s*\d\+[\]:.)}\t ]\s*")
+ *'formatlistpat'* *'flp'*
+'formatlistpat' 'flp' string (default "^\s*\d\+[\]:.)}\t ]\s*")
local to buffer
A pattern that is used to recognize a list header. This is used for
the "n" flag in 'formatoptions'.
@@ -2762,18 +2770,18 @@ A jump table for the options with a short description can be found at |Q_op|.
The default recognizes a number, followed by an optional punctuation
character and white space.
- *'formatoptions'* *'fo'*
-'formatoptions' 'fo' string (default: "tcqj")
+ *'formatoptions'* *'fo'*
+'formatoptions' 'fo' string (default "tcqj")
local to buffer
This is a sequence of letters which describes how automatic
- formatting is to be done. See |fo-table|. When the 'paste' option is
- on, no formatting is done (like 'formatoptions' is empty). Commas can
- be inserted for readability.
+ formatting is to be done.
+ See |fo-table| for possible values and |gq| for how to format text.
+ Commas can be inserted for readability.
To avoid problems with flags that are added in the future, use the
"+=" and "-=" feature of ":set" |add-option-flags|.
*'formatprg'* *'fp'*
-'formatprg' 'fp' string (default "")
+'formatprg' 'fp' string (default "")
global or local to buffer |global-local|
The name of an external program that will be used to format the lines
selected with the |gq| operator. The program must take the input on
@@ -2788,11 +2796,11 @@ A jump table for the options with a short description can be found at |Q_op|.
security reasons.
*'fsync'* *'fs'* *'nofsync'* *'nofs'*
-'fsync' 'fs' boolean (default off)
+'fsync' 'fs' boolean (default on)
global
When on, the OS function fsync() will be called after saving a file
- (|:write|, |writefile()|, …), |swap-file| and |shada-file|. This
- flushes the file to disk, ensuring that it is safely written.
+ (|:write|, |writefile()|, …), |swap-file|, |undo-persistence| and |shada-file|.
+ This flushes the file to disk, ensuring that it is safely written.
Slow on some systems: writing buffers, quitting Nvim, and other
operations may sometimes take a few seconds.
@@ -2805,7 +2813,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
+ *'gdefault'* *'gd'* *'nogdefault'* *'nogd'*
'gdefault' 'gd' boolean (default off)
global
When on, the ":substitute" flag 'g' is default on. This means that
@@ -2831,7 +2839,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'grepprg'* *'gp'*
'grepprg' 'gp' string (default "grep -n ",
- Unix: "grep -n $* /dev/null")
+ Unix: "grep -n $* /dev/null")
global or local to buffer |global-local|
Program to use for the |:grep| command. This option may contain '%'
and '#' characters, which are expanded like when used in a command-
@@ -2849,7 +2857,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'guicursor'* *'gcr'* *E545* *E546* *E548* *E549*
+ *'guicursor'* *'gcr'* *E545* *E546* *E548* *E549*
'guicursor' 'gcr' string (default "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20")
global
Configures the cursor style for each mode. Works in the GUI and many
@@ -2915,10 +2923,10 @@ A jump table for the options with a short description can be found at |Q_op|.
n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20
In Normal et al. modes, use a block cursor
with the default colors defined by the host
- terminal. In Insert-likes modes, use
+ terminal. In Insert-like modes, use
a vertical bar cursor with colors from
- "Cursor" highlight group. In Replace-likes
- modes, use a underline cursor with
+ "Cursor" highlight group. In Replace-like
+ modes, use an underline cursor with
default colors.
i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150
In Insert and Command-line Insert mode, use a
@@ -2935,8 +2943,8 @@ A jump table for the options with a short description can be found at |Q_op|.
:highlight Cursor gui=reverse guifg=NONE guibg=NONE
:highlight Cursor gui=NONE guifg=bg guibg=fg
<
- *'guifont'* *'gfn'*
- *E235* *E596*
+
+ *'guifont'* *'gfn'* *E235* *E596*
'guifont' 'gfn' string (default "")
global
This is a list of fonts which will be used for the GUI version of Vim.
@@ -2996,6 +3004,7 @@ A jump table for the options with a short description can be found at |Q_op|.
:set guifont=courier_new:h12:w5:b:cRUSSIAN
:set guifont=Andale_Mono:h7.5:w4.5
<
+
*'guifontwide'* *'gfw'* *E231* *E533* *E534*
'guifontwide' 'gfw' string (default "")
global
@@ -3111,7 +3120,7 @@ A jump table for the options with a short description can be found at |Q_op|.
removing GUI components.
*'guitablabel'* *'gtl'*
-'guitablabel' 'gtl' string (default empty)
+'guitablabel' 'gtl' string (default "")
global
When non-empty describes the text to use in a label of the GUI tab
pages line. When empty and when the result is empty Vim will use a
@@ -3128,7 +3137,7 @@ A jump table for the options with a short description can be found at |Q_op|.
used.
*'guitabtooltip'* *'gtt'*
-'guitabtooltip' 'gtt' string (default empty)
+'guitabtooltip' 'gtt' string (default "")
global
When non-empty describes the text to use in a tooltip for the GUI tab
pages line. When empty Vim will use a default tooltip.
@@ -3139,7 +3148,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'helpfile'* *'hf'*
'helpfile' 'hf' string (default (MS-Windows) "$VIMRUNTIME\doc\help.txt"
- (others) "$VIMRUNTIME/doc/help.txt")
+ (others) "$VIMRUNTIME/doc/help.txt")
global
Name of the main help file. All distributed help files should be
placed together in one directory. Additionally, all "doc" directories
@@ -3161,7 +3170,7 @@ A jump table for the options with a short description can be found at |Q_op|.
set to 'helpheight'. Set to zero to disable.
*'helplang'* *'hlg'*
-'helplang' 'hlg' string (default: messages language or empty)
+'helplang' 'hlg' string (default messages language or empty)
global
Comma-separated list of languages. Vim will use the first language
for which the desired help can be found. The English help will always
@@ -3176,7 +3185,7 @@ A jump table for the options with a short description can be found at |Q_op|.
try to find the tag in the current language before using this option.
See |help-translated|.
- *'hidden'* *'hid'* *'nohidden'* *'nohid'*
+ *'hidden'* *'hid'* *'nohidden'* *'nohid'*
'hidden' 'hid' boolean (default on)
global
When off a buffer is unloaded (including loss of undo information)
@@ -3195,28 +3204,14 @@ A jump table for the options with a short description can be found at |Q_op|.
'hidden' is set for one command with ":hide {command}" |:hide|.
*'history'* *'hi'*
-'history' 'hi' number (default: 10000)
+'history' 'hi' number (default 10000)
global
A history of ":" commands, and a history of previous search patterns
is remembered. This option decides how many entries may be stored in
each of these histories (see |cmdline-editing|).
The maximum value is 10000.
- *'hkmap'* *'hk'* *'nohkmap'* *'nohk'*
-'hkmap' 'hk' boolean (default off)
- global
- When on, the keyboard is mapped for the Hebrew character set.
- Normally you would set 'allowrevins' and use CTRL-_ in insert mode to
- toggle this option. See |rileft.txt|.
-
- *'hkmapp'* *'hkp'* *'nohkmapp'* *'nohkp'*
-'hkmapp' 'hkp' boolean (default off)
- global
- When on, phonetic keyboard mapping is used. 'hkmap' must also be on.
- This is useful if you have a non-Hebrew keyboard.
- See |rileft.txt|.
-
- *'hlsearch'* *'hls'* *'nohlsearch'* *'nohls'*
+ *'hlsearch'* *'hls'* *'nohlsearch'* *'nohls'*
'hlsearch' 'hls' boolean (default on)
global
When there is a previous search pattern, highlight all its matches.
@@ -3248,7 +3243,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Overridden by the 'iconstring' option.
Only works if the terminal supports setting window icons.
- *'iconstring'*
+ *'iconstring'*
'iconstring' string (default "")
global
When this option is not empty, it will be used for the icon text of
@@ -3262,14 +3257,14 @@ A jump table for the options with a short description can be found at |Q_op|.
*'ignorecase'* *'ic'* *'noignorecase'* *'noic'*
'ignorecase' 'ic' boolean (default off)
global
- Ignore case in search patterns. Also used when searching in the tags
- file.
+ Ignore case in search patterns, |cmdline-completion|, when
+ searching in the tags file, and |expr-==|.
Also see 'smartcase' and 'tagcase'.
Can be overruled by using "\c" or "\C" in the pattern, see
|/ignorecase|.
- *'imcmdline'* *'imc'* *'noimcmdline'* *'noimc'*
-'imcmdline' 'imc' boolean (default off)
+ *'imcmdline'* *'imc'* *'noimcmdline'* *'noimc'*
+'imcmdline' 'imc' boolean (default off)
global
When set the Input Method is always on when starting to edit a command
line, unless entering a search pattern (see 'imsearch' for that).
@@ -3277,8 +3272,8 @@ A jump table for the options with a short description can be found at |Q_op|.
English characters directly, e.g., when it's used to type accented
characters with dead keys.
- *'imdisable'* *'imd'* *'noimdisable'* *'noimd'*
-'imdisable' 'imd' boolean (default off, on for some systems (SGI))
+ *'imdisable'* *'imd'* *'noimdisable'* *'noimd'*
+'imdisable' 'imd' boolean (default off, on for some systems (SGI))
global
When set the Input Method is never used. This is useful to disable
the IM when it doesn't work properly.
@@ -3286,7 +3281,7 @@ A jump table for the options with a short description can be found at |Q_op|.
may change in later releases.
*'iminsert'* *'imi'*
-'iminsert' 'imi' number (default 0)
+'iminsert' 'imi' number (default 0)
local to buffer
Specifies whether :lmap or an Input Method (IM) is to be used in
Insert mode. Valid values:
@@ -3304,7 +3299,7 @@ A jump table for the options with a short description can be found at |Q_op|.
It is also used for the argument of commands like "r" and "f".
*'imsearch'* *'ims'*
-'imsearch' 'ims' number (default -1)
+'imsearch' 'ims' number (default -1)
local to buffer
Specifies whether :lmap or an Input Method (IM) is to be used when
entering a search pattern. Valid values:
@@ -3321,7 +3316,6 @@ A jump table for the options with a short description can be found at |Q_op|.
*'inccommand'* *'icm'*
'inccommand' 'icm' string (default "nosplit")
global
-
When nonempty, shows the effects of |:substitute|, |:smagic|,
|:snomagic| and user commands with the |:command-preview| flag as you
type.
@@ -3337,12 +3331,11 @@ A jump table for the options with a short description can be found at |Q_op|.
|Command-line-mode| is done.
*'include'* *'inc'*
-'include' 'inc' string (default "^\s*#\s*include")
+'include' 'inc' string (default "")
global or local to buffer |global-local|
Pattern to be used to find an include command. It is a search
- pattern, just like for the "/" command (See |pattern|). The default
- value is for C programs. This option is used for the commands "[i",
- "]I", "[d", etc.
+ pattern, just like for the "/" command (See |pattern|). This option
+ is used for the commands "[i", "]I", "[d", etc.
Normally the 'isfname' option is used to recognize the file name that
comes after the matched pattern. But if "\zs" appears in the pattern
then the text matched from "\zs" to the end, or until "\ze" if it
@@ -3369,9 +3362,11 @@ A jump table for the options with a short description can be found at |Q_op|.
If the expression starts with s: or |<SID>|, then it is replaced with
the script ID (|local-function|). Example: >
- set includeexpr=s:MyIncludeExpr(v:fname)
- set includeexpr=<SID>SomeIncludeExpr(v:fname)
-<
+ setlocal includeexpr=s:MyIncludeExpr(v:fname)
+ setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
+< Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
The expression will be evaluated in the |sandbox| when set from a
modeline, see |sandbox-option|.
This option cannot be set in a modeline when 'modelineexpr' is off.
@@ -3379,7 +3374,7 @@ A jump table for the options with a short description can be found at |Q_op|.
It is not allowed to change text or jump to another window while
evaluating 'includeexpr' |textlock|.
- *'incsearch'* *'is'* *'noincsearch'* *'nois'*
+ *'incsearch'* *'is'* *'noincsearch'* *'nois'*
'incsearch' 'is' boolean (default on)
global
While typing a search command, show where the pattern, as it was typed
@@ -3423,15 +3418,17 @@ A jump table for the options with a short description can be found at |Q_op|.
When this option is not empty, it overrules the 'cindent' and
'smartindent' indenting. When 'lisp' is set, this option is
is only used when 'lispoptions' contains "expr:1".
- When 'paste' is set this option is not used for indenting.
The expression is evaluated with |v:lnum| set to the line number for
which the indent is to be computed. The cursor is also in this line
when the expression is evaluated (but it may be moved around).
+
If the expression starts with s: or |<SID>|, then it is replaced with
the script ID (|local-function|). Example: >
set indentexpr=s:MyIndentExpr()
set indentexpr=<SID>SomeIndentExpr()
-<
+< Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
The expression must return the number of spaces worth of indent. It
can return "-1" to keep the current indent (this means 'autoindent' is
used for the indent).
@@ -3453,7 +3450,6 @@ A jump table for the options with a short description can be found at |Q_op|.
It is not allowed to change text or jump to another window while
evaluating 'indentexpr' |textlock|.
-
*'indentkeys'* *'indk'*
'indentkeys' 'indk' string (default "0{,0},0),0],:,0#,!^F,o,O,e")
local to buffer
@@ -3476,8 +3472,8 @@ A jump table for the options with a short description can be found at |Q_op|.
*'isfname'* *'isf'*
'isfname' 'isf' string (default for Windows:
- "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,="
- otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,=")
+ "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,="
+ otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,=")
global
The characters specified by this option are included in file names and
path names. Filenames are used for commands like "gf", "[i" and in
@@ -3527,8 +3523,8 @@ A jump table for the options with a short description can be found at |Q_op|.
*'isident'* *'isi'*
'isident' 'isi' string (default for Windows:
- "@,48-57,_,128-167,224-235"
- otherwise: "@,48-57,_,192-255")
+ "@,48-57,_,128-167,224-235"
+ otherwise: "@,48-57,_,192-255")
global
The characters given by this option are included in identifiers.
Identifiers are used in recognizing environment variables and after a
@@ -3541,7 +3537,7 @@ A jump table for the options with a short description can be found at |Q_op|.
change 'iskeyword' instead.
*'iskeyword'* *'isk'*
-'iskeyword' 'isk' string (default: @,48-57,_,192-255)
+'iskeyword' 'isk' string (default "@,48-57,_,192-255")
local to buffer
Keywords are used in searching and recognizing with many commands:
"w", "*", "[i", etc. It is also used for "\k" in a |pattern|. See
@@ -3550,14 +3546,14 @@ A jump table for the options with a short description can be found at |Q_op|.
that is not white space or punctuation).
For C programs you could use "a-z,A-Z,48-57,_,.,-,>".
For a help file it is set to all non-blank printable characters except
- '*', '"' and '|' (so that CTRL-] on a command finds the help for that
+ "*", '"' and '|' (so that CTRL-] on a command finds the help for that
command).
When the 'lisp' option is on the '-' character is always included.
This option also influences syntax highlighting, unless the syntax
uses |:syn-iskeyword|.
*'isprint'* *'isp'*
-'isprint' 'isp' string (default: "@,161-255")
+'isprint' 'isp' string (default "@,161-255")
global
The characters given by this option are displayed directly on the
screen. It is also used for "\p" in a |pattern|. The characters from
@@ -3586,37 +3582,36 @@ A jump table for the options with a short description can be found at |Q_op|.
Unprintable and zero-width Unicode characters are displayed as <xxxx>.
There is no option to specify these characters.
+ *'joinspaces'* *'js'* *'nojoinspaces'* *'nojs'*
+'joinspaces' 'js' boolean (default off)
+ global
+ Insert two spaces after a '.', '?' and '!' with a join command.
+ Otherwise only one space is inserted.
+
*'jumpoptions'* *'jop'*
'jumpoptions' 'jop' string (default "")
global
List of words that change the behavior of the |jumplist|.
- stack Make the jumplist behave like the tagstack or like a
- web browser. Relative location of entries in the
- jumplist is preserved at the cost of discarding
- subsequent entries when navigating backwards in the
- jumplist and then jumping to a location.
- |jumplist-stack|
+ stack Make the jumplist behave like the tagstack.
+ Relative location of entries in the jumplist is
+ preserved at the cost of discarding subsequent entries
+ when navigating backwards in the jumplist and then
+ jumping to a location. |jumplist-stack|
view When moving through the jumplist, |changelist|,
|alternate-file| or using |mark-motions| try to
restore the |mark-view| in which the action occurred.
- *'joinspaces'* *'js'* *'nojoinspaces'* *'nojs'*
-'joinspaces' 'js' boolean (default off)
- global
- Insert two spaces after a '.', '?' and '!' with a join command.
- Otherwise only one space is inserted.
-
- *'keymap'* *'kmp'* *E544*
+ *'keymap'* *'kmp'*
'keymap' 'kmp' string (default "")
local to buffer
Name of a keyboard mapping. See |mbyte-keymap|.
Setting this option to a valid keymap name has the side effect of
setting 'iminsert' to one, so that the keymap becomes effective.
'imsearch' is also set to one, unless it was -1
- Only normal file name characters can be used, "/\*?[|<>" are illegal.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
- *'keymodel'* *'km'*
+ *'keymodel'* *'km'*
'keymodel' 'km' string (default "")
global
List of comma-separated words, which enable special things that keys
@@ -3627,9 +3622,8 @@ A jump table for the options with a short description can be found at |Q_op|.
stopsel Using a not-shifted special key stops selection.
Special keys in this context are the cursor keys, <End>, <Home>,
<PageUp> and <PageDown>.
- The 'keymodel' option is set by the |:behave| command.
- *'keywordprg'* *'kp'*
+ *'keywordprg'* *'kp'*
'keywordprg' 'kp' string (default ":Man", Windows: ":help")
global or local to buffer |global-local|
Program to use for the |K| command. Environment variables are
@@ -3690,7 +3684,7 @@ A jump table for the options with a short description can be found at |Q_op|.
allowing to switch between mappings for different languages/encodings.
Use a mapping to avoid having to type it each time!
- *'langmenu'* *'lm'*
+ *'langmenu'* *'lm'*
'langmenu' 'lm' string (default "")
global
Language to use for menu translation. Tells which file is loaded
@@ -3700,7 +3694,7 @@ A jump table for the options with a short description can be found at |Q_op|.
matter what $LANG is set to: >
:set langmenu=nl_NL.ISO_8859-1
< When 'langmenu' is empty, |v:lang| is used.
- Only normal file name characters can be used, "/\*?[|<>" are illegal.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
If your $LANG is set to a non-English language but you do want to use
the English menus: >
:set langmenu=none
@@ -3713,13 +3707,13 @@ A jump table for the options with a short description can be found at |Q_op|.
< Warning: This deletes all menus that you defined yourself!
*'langremap'* *'lrm'* *'nolangremap'* *'nolrm'*
-'langremap' 'lrm' boolean (default off)
+'langremap' 'lrm' boolean (default off)
global
When off, setting 'langmap' does not apply to characters resulting from
a mapping. If setting 'langmap' disables some of your mappings, make
sure this option is off.
- *'laststatus'* *'ls'*
+ *'laststatus'* *'ls'*
'laststatus' 'ls' number (default 2)
global
The value of this option influences when the last window will have a
@@ -3755,7 +3749,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Note that <Tab> characters after an <EOL> are mostly not displayed
with the right amount of white space.
- *'lines'* *E593*
+ *'lines'* *E593*
'lines' number (default 24 or terminal height)
global
Number of lines of the Vim window.
@@ -3772,7 +3766,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'linespace'* *'lsp'*
'linespace' 'lsp' number (default 0)
global
- {only in the GUI}
+ only in the GUI
Number of pixel lines inserted between characters. Useful if the font
uses the full character cell height, making lines touch each other.
When non-zero there is room for underlining.
@@ -3792,7 +3786,6 @@ A jump table for the options with a short description can be found at |Q_op|.
The '-' character is included in keyword characters. Redefines the
"=" operator to use this same indentation algorithm rather than
calling an external program if 'equalprg' is empty.
- This option is not used when 'paste' is set.
*'lispoptions'* *'lop'*
'lispoptions' 'lop' string (default "")
@@ -3822,14 +3815,14 @@ A jump table for the options with a short description can be found at |Q_op|.
The cursor is displayed at the start of the space a Tab character
occupies, not at the end as usual in Normal mode. To get this cursor
position while displaying Tabs with spaces, use: >
- :set list lcs=tab:\ \
+ :set list lcs=tab:\ \
<
Note that list mode will also affect formatting (set with 'textwidth'
or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
changing the way tabs are displayed.
*'listchars'* *'lcs'*
-'listchars' 'lcs' string (default: "tab:> ,trail:-,nbsp:+")
+'listchars' 'lcs' string (default "tab:> ,trail:-,nbsp:+")
global or local to window |global-local|
Strings to use in 'list' mode and for the |:list| command. It is a
comma-separated list of string settings.
@@ -3864,8 +3857,8 @@ A jump table for the options with a short description can be found at |Q_op|.
are left blank.
*lcs-multispace*
multispace:c...
- One or more characters to use cyclically to show for
- multiple consecutive spaces. Overrides the "space"
+ One or more characters to use cyclically to show for
+ multiple consecutive spaces. Overrides the "space"
setting, except for single spaces. When omitted, the
"space" setting is used. For example,
`:set listchars=multispace:---+` shows ten consecutive
@@ -3928,7 +3921,7 @@ A jump table for the options with a short description can be found at |Q_op|.
"precedes". |hl-Whitespace| for "nbsp", "space", "tab", "multispace",
"lead" and "trail".
- *'lpl'* *'nolpl'* *'loadplugins'* *'noloadplugins'*
+ *'loadplugins'* *'lpl'* *'noloadplugins'* *'nolpl'*
'loadplugins' 'lpl' boolean (default on)
global
When on the plugin scripts are loaded when starting up |load-plugins|.
@@ -3949,7 +3942,7 @@ A jump table for the options with a short description can be found at |Q_op|.
when you want to |/\M|.
*'makeef'* *'mef'*
-'makeef' 'mef' string (default: "")
+'makeef' 'mef' string (default "")
global
Name of the errorfile for the |:make| command (see |:make_makeprg|)
and the |:grep| command.
@@ -3963,7 +3956,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'makeencoding'* *'menc'*
+ *'makeencoding'* *'menc'*
'makeencoding' 'menc' string (default "")
global or local to buffer |global-local|
Encoding used for reading the output of external commands. When empty,
@@ -3977,6 +3970,7 @@ A jump table for the options with a short description can be found at |Q_op|.
setting to the system locale encoding. Example: >
:set makeencoding=char " system locale is used
<
+
*'makeprg'* *'mp'*
'makeprg' 'mp' string (default "make")
global or local to buffer |global-local|
@@ -4033,7 +4027,7 @@ A jump table for the options with a short description can be found at |Q_op|.
command recursion, see |E169|.
See also |:function|.
- *'maxmapdepth'* *'mmd'* *E223*
+ *'maxmapdepth'* *'mmd'* *E223*
'maxmapdepth' 'mmd' number (default 1000)
global
Maximum number of times a mapping is done without resulting in a
@@ -4074,9 +4068,9 @@ A jump table for the options with a short description can be found at |Q_op|.
per word depends very much on how similar the words are, that's why
this tuning is complicated.
- There are three numbers, separated by commas:
+ There are three numbers, separated by commas: >
{start},{inc},{added}
-
+<
For most languages the uncompressed word tree fits in memory. {start}
gives the amount of memory in Kbyte that can be used before any
compression is done. It should be a bit smaller than the amount of
@@ -4101,17 +4095,18 @@ A jump table for the options with a short description can be found at |Q_op|.
< If you have less than 512 Mbyte |:mkspell| may fail for some
languages, no matter what you set 'mkspellmem' to.
- This option cannot be set from a |modeline| or in the |sandbox|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
- *'modeline'* *'ml'* *'nomodeline'* *'noml'*
-'modeline' 'ml' boolean (default: on (off for root))
+ *'modeline'* *'ml'* *'nomodeline'* *'noml'*
+'modeline' 'ml' boolean (default on (off for root))
local to buffer
If 'modeline' is on 'modelines' gives the number of lines that is
checked for set commands. If 'modeline' is off or 'modelines' is zero
no lines are checked. See |modeline|.
- *'modelineexpr'* *'mle'* *'nomodelineexpr'* *'nomle'*
-'modelineexpr' 'mle' boolean (default: off)
+ *'modelineexpr'* *'mle'* *'nomodelineexpr'* *'nomle'*
+'modelineexpr' 'mle' boolean (default off)
global
When on allow some options that are an expression to be set in the
modeline. Check the option for whether it is affected by
@@ -4126,8 +4121,8 @@ A jump table for the options with a short description can be found at |Q_op|.
checked for set commands. If 'modeline' is off or 'modelines' is zero
no lines are checked. See |modeline|.
- *'modifiable'* *'ma'* *'nomodifiable'* *'noma'*
- *E21*
+
+ *'modifiable'* *'ma'* *'nomodifiable'* *'noma'* *E21*
'modifiable' 'ma' boolean (default on)
local to buffer
When off the buffer contents cannot be changed. The 'fileformat' and
@@ -4136,7 +4131,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'modified'* *'mod'* *'nomodified'* *'nomod'*
'modified' 'mod' boolean (default off)
- local to buffer
+ local to buffer |local-noglobal|
When on, the buffer is considered to be modified. This option is set
when:
1. A change was made to the text since it was last written. Using the
@@ -4160,16 +4155,15 @@ A jump table for the options with a short description can be found at |Q_op|.
when using "rA" on an "A".
*'more'* *'nomore'*
-'more' boolean (default: on)
+'more' boolean (default on)
global
When on, listings pause when the whole screen is filled. You will get
the |more-prompt|. When this option is off there are no pauses, the
listing continues until finished.
- *'mouse'*
+ *'mouse'*
'mouse' string (default "nvi")
global
-
Enables mouse support. For example, to enable the mouse in Normal mode
and Visual mode: >
:set mouse=nv
@@ -4207,22 +4201,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'mousehide' hide mouse pointer while typing text
'selectmode' whether to start Select mode or Visual mode
- The :behave command provides some "profiles" for mouse behavior.
- *:behave* *:be*
- :be[have] {model} Set behavior for mouse and selection. Valid
- arguments are:
- mswin MS-Windows behavior
- xterm Xterm behavior
-
- Using ":behave" changes these options:
- option mswin xterm ~
- 'selectmode' "mouse,key" ""
- 'mousemodel' "popup" "extend"
- 'keymodel' "startsel,stopsel" ""
- 'selection' "exclusive" "inclusive"
-
-
- *'mousefocus'* *'mousef'* *'nomousefocus'* *'nomousef'*
+ *'mousefocus'* *'mousef'* *'nomousefocus'* *'nomousef'*
'mousefocus' 'mousef' boolean (default off)
global
The window that the mouse pointer is on is automatically activated.
@@ -4231,10 +4210,10 @@ A jump table for the options with a short description can be found at |Q_op|.
default because it makes using the pull down menus a little goofy, as
a pointer transit may activate a window unintentionally.
- *'mousehide'* *'mh'* *'nomousehide'* *'nomh'*
+ *'mousehide'* *'mh'* *'nomousehide'* *'nomh'*
'mousehide' 'mh' boolean (default on)
global
- {only works in the GUI}
+ only in the GUI
When on, the mouse pointer is hidden when characters are typed.
The mouse pointer is restored when the mouse is moved.
@@ -4289,10 +4268,8 @@ A jump table for the options with a short description can be found at |Q_op|.
"g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click)
"g<RightMouse>" is "<C-RightMouse> ("CTRL-T")
- The 'mousemodel' option is set by the |:behave| command.
-
- *'mousemoveevent'* *'mousemev'*
-'mousemoveevent' 'mousemev' boolean (default off)
+ *'mousemoveevent'* *'mousemev'* *'nomousemoveevent'* *'nomousemev'*
+'mousemoveevent' 'mousemev' boolean (default off)
global
When on, mouse move events are delivered to the input queue and are
available for mapping. The default, off, avoids the mouse movement
@@ -4300,12 +4277,13 @@ A jump table for the options with a short description can be found at |Q_op|.
Warning: Setting this option can make pending mappings to be aborted
when the mouse is moved.
- *'mousescroll'*
+ *'mousescroll'* *E5080*
'mousescroll' string (default "ver:3,hor:6")
global
This option controls the number of lines / columns to scroll by when
- scrolling with a mouse. The option is a comma separated list of parts.
- Each part consists of a direction and a count as follows:
+ scrolling with a mouse wheel (|scroll-mouse-wheel|). The option is
+ a comma-separated list. Each part consists of a direction and a count
+ as follows:
direction:count,direction:count
Direction is one of either "hor" or "ver". "hor" controls horizontal
scrolling and "ver" controls vertical scrolling. Count sets the amount
@@ -4322,7 +4300,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'mouseshape'* *'mouses'* *E547*
'mouseshape' 'mouses' string (default "i:beam,r:beam,s:updown,sd:cross,
- m:no,ml:up-arrow,v:rightup-arrow")
+ m:no,ml:up-arrow,v:rightup-arrow")
global
This option tells Vim what the mouse pointer should look like in
different modes. The option is a comma-separated list of parts, much
@@ -4389,7 +4367,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Defines the maximum time in msec between two mouse clicks for the
second click to be recognized as a multi click.
- *'nrformats'* *'nf'*
+ *'nrformats'* *'nf'*
'nrformats' 'nf' string (default "bin,hex")
local to buffer
This defines what bases Vim will consider for numbers when using the
@@ -4437,14 +4415,15 @@ A jump table for the options with a short description can be found at |Q_op|.
'nonu' 'nu' 'nonu' 'nu'
'nornu' 'nornu' 'rnu' 'rnu'
->
+ >
|apple | 1 apple | 2 apple | 2 apple
|pear | 2 pear | 1 pear | 1 pear
|nobody | 3 nobody | 0 nobody |3 nobody
|there | 4 there | 1 there | 1 there
<
+
*'numberwidth'* *'nuw'*
-'numberwidth' 'nuw' number (default: 4)
+'numberwidth' 'nuw' number (default 4)
local to window
Minimal number of columns to use for the line number. Only relevant
when the 'number' or 'relativenumber' option is set or printing lines
@@ -4458,7 +4437,7 @@ A jump table for the options with a short description can be found at |Q_op|.
The minimum value is 1, the maximum value is 20.
*'omnifunc'* *'ofu'*
-'omnifunc' 'ofu' string (default: empty)
+'omnifunc' 'ofu' string (default "")
local to buffer
This option specifies a function to be used for Insert mode omni
completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
@@ -4471,20 +4450,18 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
-
- *'opendevice'* *'odev'* *'noopendevice'* *'noodev'*
+ *'opendevice'* *'odev'* *'noopendevice'* *'noodev'*
'opendevice' 'odev' boolean (default off)
global
- {only for Windows}
+ only for Windows
Enable reading and writing from devices. This may get Vim stuck on a
device that can be opened but doesn't actually do the I/O. Therefore
it is off by default.
Note that on Windows editing "aux.h", "lpt1.txt" and the like also
result in editing a device.
-
- *'operatorfunc'* *'opfunc'*
-'operatorfunc' 'opfunc' string (default: empty)
+ *'operatorfunc'* *'opfunc'*
+'operatorfunc' 'opfunc' string (default "")
global
This option specifies a function to be called by the |g@| operator.
See |:map-operator| for more info and an example. The value can be
@@ -4494,10 +4471,13 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'packpath'* *'pp'*
-'packpath' 'pp' string (default: see 'runtimepath')
- Directories used to find packages. See |packages| and |rtp-packages|.
-
+ *'packpath'* *'pp'*
+'packpath' 'pp' string (default see 'runtimepath')
+ global
+ Directories used to find packages.
+ See |packages| and |packages-runtimepath|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
*'paragraphs'* *'para'*
'paragraphs' 'para' string (default "IPLPPPQPP TPHPLIPpLpItpplpipbp")
@@ -4505,82 +4485,13 @@ A jump table for the options with a short description can be found at |Q_op|.
Specifies the nroff macros that separate paragraphs. These are pairs
of two letters (see |object-motions|).
- *'paste'* *'nopaste'*
-'paste' boolean (default off)
- global
- This option is obsolete; |bracketed-paste-mode| is built-in.
-
- Put Vim in Paste mode. This is useful if you want to cut or copy
- some text from one window and paste it in Vim. This will avoid
- unexpected effects.
- Setting this option is useful when using Vim in a terminal, where Vim
- cannot distinguish between typed text and pasted text. In the GUI, Vim
- knows about pasting and will mostly do the right thing without 'paste'
- being set. The same is true for a terminal where Vim handles the
- mouse clicks itself.
- This option is reset when starting the GUI. Thus if you set it in
- your vimrc it will work in a terminal, but not in the GUI. Setting
- 'paste' in the GUI has side effects: e.g., the Paste toolbar button
- will no longer work in Insert mode, because it uses a mapping.
- When the 'paste' option is switched on (also when it was already on):
- - mapping in Insert mode and Command-line mode is disabled
- - abbreviations are disabled
- - 'autoindent' is reset
- - 'expandtab' is reset
- - 'hkmap' is reset
- - 'revins' is reset
- - 'ruler' is reset
- - 'showmatch' is reset
- - 'smarttab' is reset
- - 'softtabstop' is set to 0
- - 'textwidth' is set to 0
- - 'wrapmargin' is set to 0
- - 'varsofttabstop' is made empty
- These options keep their value, but their effect is disabled:
- - 'cindent'
- - 'formatoptions' is used like it is empty
- - 'indentexpr'
- - 'lisp'
- - 'smartindent'
- NOTE: When you start editing another file while the 'paste' option is
- on, settings from the modelines or autocommands may change the
- settings again, causing trouble when pasting text. You might want to
- set the 'paste' option again.
- When the 'paste' option is reset the mentioned options are restored to
- the value before the moment 'paste' was switched from off to on.
- Resetting 'paste' before ever setting it does not have any effect.
- Since mapping doesn't work while 'paste' is active, you need to use
- the 'pastetoggle' option to toggle the 'paste' option with some key.
-
- *'pastetoggle'* *'pt'*
-'pastetoggle' 'pt' string (default "")
- global
- When non-empty, specifies the key sequence that toggles the 'paste'
- option. This is like specifying a mapping: >
- :map {keys} :set invpaste<CR>
-< Where {keys} is the value of 'pastetoggle'.
- The difference is that it will work even when 'paste' is set.
- 'pastetoggle' works in Insert mode and Normal mode, but not in
- Command-line mode.
- Mappings are checked first, thus overrule 'pastetoggle'. However,
- when 'paste' is on mappings are ignored in Insert mode, thus you can do
- this: >
- :map <F10> :set paste<CR>
- :map <F11> :set nopaste<CR>
- :imap <F10> <C-O>:set paste<CR>
- :imap <F11> <nop>
- :set pastetoggle=<F11>
-< This will make <F10> start paste mode and <F11> stop paste mode.
- Note that typing <F10> in paste mode inserts "<F10>", since in paste
- mode everything is inserted literally, except the 'pastetoggle' key
- sequence.
- When the value has several bytes 'ttimeoutlen' applies.
-
- *'pex'* *'patchexpr'*
+ *'patchexpr'* *'pex'*
'patchexpr' 'pex' string (default "")
global
Expression which is evaluated to apply a patch to a file and generate
the resulting new version of the file. See |diff-patchexpr|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
*'patchmode'* *'pm'* *E205* *E206*
'patchmode' 'pm' string (default "")
@@ -4600,11 +4511,10 @@ A jump table for the options with a short description can be found at |Q_op|.
Using 'patchmode' for compressed files appends the extension at the
end (e.g., "file.gz.orig"), thus the resulting name isn't always
recognized as a compressed file.
- Only normal file name characters can be used, "/\*?[|<>" are illegal.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
*'path'* *'pa'* *E343* *E345* *E347* *E854*
-'path' 'pa' string (default on Unix: ".,/usr/include,,"
- other systems: ".,,")
+'path' 'pa' string (default ".,,")
global or local to buffer |global-local|
This is a list of directories which will be searched when using the
|gf|, [f, ]f, ^Wf, |:find|, |:sfind|, |:tabfind| and other commands,
@@ -4613,9 +4523,9 @@ A jump table for the options with a short description can be found at |Q_op|.
option may be relative or absolute.
- Use commas to separate directory names: >
:set path=.,/usr/local/include,/usr/include
-< - Spaces can also be used to separate directory names (for backwards
- compatibility with version 3.0). To have a space in a directory
- name, precede it with an extra backslash, and escape the space: >
+< - Spaces can also be used to separate directory names. To have a
+ space in a directory name, precede it with an extra backslash, and
+ escape the space: >
:set path=.,/dir/with\\\ space
< - To include a comma in a directory name precede it with an extra
backslash: >
@@ -4654,7 +4564,7 @@ A jump table for the options with a short description can be found at |Q_op|.
< Replace the ';' with a ':' or whatever separator is used. Note that
this doesn't work when $INCL contains a comma or white space.
- *'preserveindent'* *'pi'* *'nopreserveindent'* *'nopi'*
+ *'preserveindent'* *'pi'* *'nopreserveindent'* *'nopi'*
'preserveindent' 'pi' boolean (default off)
local to buffer
When changing the indent of the current line, preserve as much of the
@@ -4670,16 +4580,15 @@ A jump table for the options with a short description can be found at |Q_op|.
Also see 'copyindent'.
Use |:retab| to clean up white space.
- *'previewheight'* *'pvh'*
-'previewheight' 'pvh' number (default 12)
+ *'previewheight'* *'pvh'*
+'previewheight' 'pvh' number (default 12)
global
Default height for a preview window. Used for |:ptag| and associated
commands. Used for |CTRL-W_}| when no count is given.
- *'previewwindow'* *'nopreviewwindow'*
- *'pvw'* *'nopvw'* *E590*
-'previewwindow' 'pvw' boolean (default off)
- local to window
+ *'previewwindow'* *'pvw'* *'nopreviewwindow'* *'nopvw'* *E590*
+'previewwindow' 'pvw' boolean (default off)
+ local to window |local-noglobal|
Identifies the preview window. Only one window can have this option
set. It's normally not set directly, but by using one of the commands
|:ptag|, |:pedit|, etc.
@@ -4723,8 +4632,8 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'quickfixtextfunc'* *'qftf'*
-'quickfixtextfunc' 'qftf' string (default "")
+ *'quickfixtextfunc'* *'qftf'*
+'quickfixtextfunc' 'qftf' string (default "")
global
This option specifies a function to be used to get the text to display
in the quickfix and location list windows. This can be used to
@@ -4747,9 +4656,9 @@ A jump table for the options with a short description can be found at |Q_op|.
the following character will be skipped. The default value makes the
text "foo\"bar\\" considered to be one string.
- *'readonly'* *'ro'* *'noreadonly'* *'noro'*
+ *'readonly'* *'ro'* *'noreadonly'* *'noro'*
'readonly' 'ro' boolean (default off)
- local to buffer
+ local to buffer |local-noglobal|
If on, writes fail unless you use a '!'. Protects you from
accidentally overwriting a file. Default on when Vim is started
in read-only mode ("vim -R") or when the executable is called "view".
@@ -4760,12 +4669,12 @@ A jump table for the options with a short description can be found at |Q_op|.
See 'modifiable' for disallowing changes to the buffer.
*'redrawdebug'* *'rdb'*
-'redrawdebug' 'rdb' string (default '')
+'redrawdebug' 'rdb' string (default "")
global
Flags to change the way redrawing works, for debugging purposes.
Most useful with 'writedelay' set to some reasonable value.
Supports the following flags:
- compositor Indicate what redraws come from the compositor
+ compositor Indicate each redraw event handled by the compositor
by briefly flashing the redrawn regions in colors
indicating the redraw type. These are the highlight
groups used (and their default colors):
@@ -4777,6 +4686,11 @@ A jump table for the options with a short description can be found at |Q_op|.
RedrawDebugRecompose guibg=Red redraw generated by the
compositor itself, due to a
grid being moved or deleted.
+ line introduce a delay after each line drawn on the screen.
+ When using the TUI or another single-grid UI, "compositor"
+ gives more information and should be preferred (every
+ line is processed as a separate event by the compositor)
+ flush introduce a delay after each "flush" event.
nothrottle Turn off throttling of the message grid. This is an
optimization that joins many small scrolls to one
larger scroll when drawing the message area (with
@@ -4786,7 +4700,7 @@ A jump table for the options with a short description can be found at |Q_op|.
useful when running nvim inside a debugger (and
the test suite).
nodelta Send all internally redrawn cells to the UI, even if
- they are unchanged from the already displayed state.
+ they are unchanged from the already displayed state.
*'redrawtime'* *'rdt'*
'redrawtime' 'rdt' number (default 2000)
@@ -4838,7 +4752,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'number', see |number_relativenumber| for all combinations of the two
options.
- *'report'*
+ *'report'*
'report' number (default 2)
global
Threshold for reporting number of lines changed. When the number of
@@ -4853,10 +4767,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Inserting characters in Insert mode will work backwards. See "typing
backwards" |ins-reverse|. This option can be toggled with the CTRL-_
command in Insert mode, when 'allowrevins' is set.
- This option is reset when 'paste' is set and restored when 'paste' is
- reset.
- *'rightleft'* *'rl'* *'norightleft'* *'norl'*
+ *'rightleft'* *'rl'* *'norightleft'* *'norl'*
'rightleft' 'rl' boolean (default off)
local to window
When on, display orientation becomes right-to-left, i.e., characters
@@ -4869,7 +4781,7 @@ A jump table for the options with a short description can be found at |Q_op|.
and left-to-right strings so that both sets are displayed properly
in different windows). Also see |rileft.txt|.
- *'rightleftcmd'* *'rlc'*
+ *'rightleftcmd'* *'rlc'*
'rightleftcmd' 'rlc' string (default "search")
local to window
Each word in this option enables the command line editing to work in
@@ -4880,7 +4792,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This is useful for languages such as Hebrew, Arabic and Farsi.
The 'rightleft' option must be set for 'rightleftcmd' to take effect.
- *'ruler'* *'ru'* *'noruler'* *'noru'*
+ *'ruler'* *'ru'* *'noruler'* *'noru'*
'ruler' 'ru' boolean (default on)
global
Show the line and column number of the cursor position, separated by a
@@ -4903,13 +4815,11 @@ A jump table for the options with a short description can be found at |Q_op|.
separated with a dash.
For an empty line "0-1" is shown.
For an empty buffer the line number will also be zero: "0,0-1".
- This option is reset when 'paste' is set and restored when 'paste' is
- reset.
If you don't want to see the ruler all the time but want to know where
you are, use "g CTRL-G" |g_CTRL-G|.
*'rulerformat'* *'ruf'*
-'rulerformat' 'ruf' string (default empty)
+'rulerformat' 'ruf' string (default "")
global
When this option is not empty, it determines the content of the ruler
string, as displayed for the 'ruler' option.
@@ -4921,24 +4831,25 @@ A jump table for the options with a short description can be found at |Q_op|.
Example: >
:set rulerformat=%15(%c%V\ %p%%%)
<
- *'runtimepath'* *'rtp'* *vimfiles*
-'runtimepath' 'rtp' string (default: "$XDG_CONFIG_HOME/nvim,
- $XDG_CONFIG_DIRS[1]/nvim,
- $XDG_CONFIG_DIRS[2]/nvim,
- …
- $XDG_DATA_HOME/nvim[-data]/site,
- $XDG_DATA_DIRS[1]/nvim/site,
- $XDG_DATA_DIRS[2]/nvim/site,
- …
- $VIMRUNTIME,
- …
- $XDG_DATA_DIRS[2]/nvim/site/after,
- $XDG_DATA_DIRS[1]/nvim/site/after,
- $XDG_DATA_HOME/nvim[-data]/site/after,
- …
- $XDG_CONFIG_DIRS[2]/nvim/after,
- $XDG_CONFIG_DIRS[1]/nvim/after,
- $XDG_CONFIG_HOME/nvim/after")
+
+ *'runtimepath'* *'rtp'* *vimfiles*
+'runtimepath' 'rtp' string (default "$XDG_CONFIG_HOME/nvim,
+ $XDG_CONFIG_DIRS[1]/nvim,
+ $XDG_CONFIG_DIRS[2]/nvim,
+ …
+ $XDG_DATA_HOME/nvim[-data]/site,
+ $XDG_DATA_DIRS[1]/nvim/site,
+ $XDG_DATA_DIRS[2]/nvim/site,
+ …
+ $VIMRUNTIME,
+ …
+ $XDG_DATA_DIRS[2]/nvim/site/after,
+ $XDG_DATA_DIRS[1]/nvim/site/after,
+ $XDG_DATA_HOME/nvim[-data]/site/after,
+ …
+ $XDG_CONFIG_DIRS[2]/nvim/after,
+ $XDG_CONFIG_DIRS[1]/nvim/after,
+ $XDG_CONFIG_HOME/nvim/after")
global
List of directories to be searched for these runtime files:
filetype.lua filetypes |new-filetype|
@@ -4955,7 +4866,7 @@ A jump table for the options with a short description can be found at |Q_op|.
pack/ packages |:packadd|
parser/ |treesitter| syntax parsers
plugin/ plugin scripts |write-plugin|
- query/ |treesitter| queries
+ queries/ |treesitter| queries
rplugin/ |remote-plugin| scripts
spell/ spell checking files |spell|
syntax/ syntax files |mysyntaxfile|
@@ -4966,7 +4877,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Defaults are setup to search these locations:
1. Your home directory, for personal preferences.
Given by `stdpath("config")`. |$XDG_CONFIG_HOME|
- 2. Directories which must contain configuration files according to
+ 2. Directories which must contain configuration files according to
|xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
preferences from system administrator.
3. Data home directory, for plugins installed by user.
@@ -5008,8 +4919,8 @@ A jump table for the options with a short description can be found at |Q_op|.
With |--clean| the home directory entries are not included.
*'scroll'* *'scr'*
-'scroll' 'scr' number (default: half the window height)
- local to window
+'scroll' 'scr' number (default half the window height)
+ local to window |local-noglobal|
Number of lines to scroll with CTRL-U and CTRL-D commands. Will be
set to half the number of lines in the window when the window size
changes. This may happen when enabling the |status-line| or
@@ -5019,19 +4930,22 @@ A jump table for the options with a short description can be found at |Q_op|.
height with ":set scroll=0".
*'scrollback'* *'scbk'*
-'scrollback' 'scbk' number (default: 10000)
+'scrollback' 'scbk' number (default 10000)
local to buffer
Maximum number of lines kept beyond the visible screen. Lines at the
top are deleted if new lines exceed this limit.
Minimum is 1, maximum is 100000.
Only in |terminal| buffers.
+ Note: Lines that are not visible and kept in scrollback are not
+ reflown when the terminal buffer is resized horizontally.
+
*'scrollbind'* *'scb'* *'noscrollbind'* *'noscb'*
-'scrollbind' 'scb' boolean (default off)
+'scrollbind' 'scb' boolean (default off)
local to window
- See also |scroll-binding|. When this option is set, the current
- window scrolls as other scrollbind windows (windows that also have
- this option set) scroll. This option is useful for viewing the
+ See also |scroll-binding|. When this option is set, scrolling the
+ current window also scrolls other scrollbind windows (windows that
+ also have this option set). This option is useful for viewing the
differences between two versions of a file, see 'diff'.
See |'scrollopt'| for options that determine how this option should be
interpreted.
@@ -5122,8 +5036,6 @@ A jump table for the options with a short description can be found at |Q_op|.
backwards, you cannot include the last character of a line, when
starting in Normal mode and 'virtualedit' empty.
- The 'selection' option is set by the |:behave| command.
-
*'selectmode'* *'slm'*
'selectmode' 'slm' string (default "")
global
@@ -5134,11 +5046,9 @@ A jump table for the options with a short description can be found at |Q_op|.
key when using shifted special keys
cmd when using "v", "V" or CTRL-V
See |Select-mode|.
- The 'selectmode' option is set by the |:behave| command.
- *'sessionoptions'* *'ssop'*
-'sessionoptions' 'ssop' string (default: "blank,buffers,curdir,folds,
- help,tabpages,winsize,terminal")
+ *'sessionoptions'* *'ssop'*
+'sessionoptions' 'ssop' string (default "blank,buffers,curdir,folds,help,tabpages,winsize,terminal")
global
Changes the effect of the |:mksession| command. It is a comma-
separated list of words. Each word enables saving and restoring
@@ -5177,10 +5087,11 @@ A jump table for the options with a short description can be found at |Q_op|.
filenames are stored as absolute paths.
If you leave out "options" many things won't work well after restoring
the session.
- *'shada'* *'sd'* *E526* *E527* *E528*
+
+ *'shada'* *'sd'* *E526* *E527* *E528*
'shada' 'sd' string (default for
- Win32: !,'100,<50,s10,h,rA:,rB:
- others: !,'100,<50,s10,h)
+ Win32: !,'100,<50,s10,h,rA:,rB:
+ others: !,'100,<50,s10,h)
global
When non-empty, the shada file is read upon startup and written
when exiting Vim (see |shada-file|). The string should be a comma-
@@ -5206,8 +5117,8 @@ A jump table for the options with a short description can be found at |Q_op|.
% When included, save and restore the buffer list. If Vim is
started with a file name argument, the buffer list is not
restored. If Vim is started without a file name argument, the
- buffer list is restored from the shada file. Quickfix
- ('buftype'), unlisted ('buflisted'), unnamed and buffers on
+ buffer list is restored from the shada file. Quickfix
+ ('buftype'), unlisted ('buflisted'), unnamed and buffers on
removable media (|shada-r|) are not saved.
When followed by a number, the number specifies the maximum
number of buffers that are stored. Without a number all
@@ -5235,8 +5146,8 @@ A jump table for the options with a short description can be found at |Q_op|.
@ Maximum number of items in the input-line history to be
saved. When not included, the value of 'history' is used.
*shada-c*
- c Dummy option, kept for compatibility reasons. Has no actual
- effect: ShaDa always uses UTF-8 and 'encoding' value is fixed
+ c Dummy option, kept for compatibility reasons. Has no actual
+ effect: ShaDa always uses UTF-8 and 'encoding' value is fixed
to UTF-8 as well.
*shada-f*
f Whether file marks need to be stored. If zero, file marks ('0
@@ -5261,13 +5172,13 @@ A jump table for the options with a short description can be found at |Q_op|.
could use "ra:,rb:". You can also use it for temp files,
e.g., for Unix: "r/tmp". Case is ignored.
*shada-s*
- s Maximum size of an item contents in KiB. If zero then nothing
- is saved. Unlike Vim this applies to all items, except for
- the buffer list and header. Full item size is off by three
- unsigned integers: with `s10` maximum item size may be 1 byte
- (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
- integer) + 3 bytes (item size: up to 16-bit integer because
- 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
+ s Maximum size of an item contents in KiB. If zero then nothing
+ is saved. Unlike Vim this applies to all items, except for
+ the buffer list and header. Full item size is off by three
+ unsigned integers: with `s10` maximum item size may be 1 byte
+ (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
+ integer) + 3 bytes (item size: up to 16-bit integer because
+ 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
contents size) = 10253 bytes.
Example: >
@@ -5277,7 +5188,7 @@ A jump table for the options with a short description can be found at |Q_op|.
edited.
<1000 Contents of registers (up to 1000 lines each) will be
remembered.
- s100 Items with contents occupying more then 100 KiB are
+ s100 Items with contents occupying more then 100 KiB are
skipped.
:0 Command-line history will not be saved.
n~/nvim/shada The name of the file to use is "~/nvim/shada".
@@ -5293,8 +5204,8 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'shadafile'* *'sdf'*
-'shadafile' 'sdf' string (default: "")
+ *'shadafile'* *'sdf'*
+'shadafile' 'sdf' string (default "")
global
When non-empty, overrides the file name used for |shada| (viminfo).
When equal to "NONE" no shada file will be read or written.
@@ -5316,44 +5227,44 @@ A jump table for the options with a short description can be found at |Q_op|.
If the name of the shell contains a space, you need to enclose it in
quotes. Example with quotes: >
:set shell=\"c:\program\ files\unix\sh.exe\"\ -f
-< Note the backslash before each quote (to avoid starting a comment) and
- each space (to avoid ending the option value), so better use |:let-&|
+< Note the backslash before each quote (to avoid starting a comment) and
+ each space (to avoid ending the option value), so better use |:let-&|
like this: >
:let &shell='"C:\Program Files\unix\sh.exe" -f'
-< Also note that the "-f" is not inside the quotes, because it is not
+< Also note that the "-f" is not inside the quotes, because it is not
part of the command name.
*shell-unquoting*
Rules regarding quotes:
- 1. Option is split on space and tab characters that are not inside
- quotes: "abc def" runs shell named "abc" with additional argument
- "def", '"abc def"' runs shell named "abc def" with no additional
- arguments (here and below: additional means “additional to
+ 1. Option is split on space and tab characters that are not inside
+ quotes: "abc def" runs shell named "abc" with additional argument
+ "def", '"abc def"' runs shell named "abc def" with no additional
+ arguments (here and below: additional means “additional to
'shellcmdflag'”).
- 2. Quotes in option may be present in any position and any number:
- '"abc"', '"a"bc', 'a"b"c', 'ab"c"' and '"a"b"c"' are all equivalent
+ 2. Quotes in option may be present in any position and any number:
+ '"abc"', '"a"bc', 'a"b"c', 'ab"c"' and '"a"b"c"' are all equivalent
to just "abc".
- 3. Inside quotes backslash preceding backslash means one backslash.
- Backslash preceding quote means one quote. Backslash preceding
- anything else means backslash and next character literally:
- '"a\\b"' is the same as "a\b", '"a\\"b"' runs shell named literally
+ 3. Inside quotes backslash preceding backslash means one backslash.
+ Backslash preceding quote means one quote. Backslash preceding
+ anything else means backslash and next character literally:
+ '"a\\b"' is the same as "a\b", '"a\\"b"' runs shell named literally
'a"b', '"a\b"' is the same as "a\b" again.
- 4. Outside of quotes backslash always means itself, it cannot be used
+ 4. Outside of quotes backslash always means itself, it cannot be used
to escape quote: 'a\"b"' is the same as "a\b".
- Note that such processing is done after |:set| did its own round of
+ Note that such processing is done after |:set| did its own round of
unescaping, so to keep yourself sane use |:let-&| like shown above.
*shell-powershell*
To use PowerShell: >
let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
- let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;'
- let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode'
- let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode'
+ let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
+ let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
+ let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
set shellquote= shellxquote=
< This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
*'shellcmdflag'* *'shcf'*
-'shellcmdflag' 'shcf' string (default: "-c"; Windows: "/s /c")
+'shellcmdflag' 'shcf' string (default "-c"; Windows: "/s /c")
global
Flag passed to the shell to execute "!" and ":!" commands; e.g.,
`bash.exe -c ls` or `cmd.exe /s /c "dir"`. For MS-Windows, the
@@ -5362,14 +5273,13 @@ A jump table for the options with a short description can be found at |Q_op|.
On Unix it can have more than one flag. Each white space separated
part is passed as an argument to the shell command.
See |option-backslash| about including spaces and backslashes.
- See |shell-unquoting| which talks about separating this option into
+ See |shell-unquoting| which talks about separating this option into
multiple arguments.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
*'shellpipe'* *'sp'*
-'shellpipe' 'sp' string (default ">", ">%s 2>&1", "| tee", "|& tee" or
- "2>&1| tee")
+'shellpipe' 'sp' string (default ">", "| tee", "|& tee" or "2>&1| tee")
global
String to be used to put the output of the ":make" command in the
error file. See also |:make_makeprg|. See |option-backslash| about
@@ -5377,8 +5287,8 @@ A jump table for the options with a short description can be found at |Q_op|.
The name of the temporary file can be represented by "%s" if necessary
(the file name is appended automatically if no %s appears in the value
of this option).
- For MS-Windows the default is ">%s 2>&1". The output is directly
- saved in a file and not echoed to the screen.
+ For MS-Windows the default is "2>&1| tee". The stdout and stderr are
+ saved in a file and echoed to the screen.
For Unix the default is "| tee". The stdout of the compiler is saved
in a file and echoed to the screen. If the 'shell' option is "csh" or
"tcsh" after initializations, the default becomes "|& tee". If the
@@ -5401,8 +5311,8 @@ A jump table for the options with a short description can be found at |Q_op|.
security reasons.
*'shellquote'* *'shq'*
-'shellquote' 'shq' string (default: ""; Windows, when 'shell'
- contains "sh" somewhere: "\"")
+'shellquote' 'shq' string (default ""; Windows, when 'shell'
+ contains "sh" somewhere: "\"")
global
Quoting character(s), put around the command passed to the shell, for
the "!" and ":!" commands. The redirection is kept outside of the
@@ -5444,7 +5354,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'shellslash'* *'ssl'* *'noshellslash'* *'nossl'*
'shellslash' 'ssl' boolean (default off)
global
- {only for MS-Windows}
+ only for MS-Windows
When set, a forward slash is used when expanding file names. This is
useful when a Unix-like shell is used instead of cmd.exe. Backward
slashes can still be typed, but they are changed to forward slashes by
@@ -5472,14 +5382,16 @@ A jump table for the options with a short description can be found at |Q_op|.
|system()| does not respect this option, it always uses pipes.
*'shellxescape'* *'sxe'*
-'shellxescape' 'sxe' string (default: "")
+'shellxescape' 'sxe' string (default "")
global
When 'shellxquote' is set to "(" then the characters listed in this
option will be escaped with a '^' character. This makes it possible
to execute most external commands with cmd.exe.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
*'shellxquote'* *'sxq'*
-'shellxquote' 'sxq' string (default: "", Windows: "\"")
+'shellxquote' 'sxq' string (default "", Windows: "\"")
global
Quoting character(s), put around the command passed to the shell, for
the "!" and ":!" commands. Includes the redirection. See
@@ -5503,27 +5415,21 @@ A jump table for the options with a short description can be found at |Q_op|.
local to buffer
Number of spaces to use for each step of (auto)indent. Used for
|'cindent'|, |>>|, |<<|, etc.
- When zero the 'ts' value will be used. Use the |shiftwidth()|
+ When zero the 'tabstop' value will be used. Use the |shiftwidth()|
function to get the effective shiftwidth value.
- *'shortmess'* *'shm'*
-'shortmess' 'shm' string (default "filnxtToOF")
+ *'shortmess'* *'shm'* *E1336*
+'shortmess' 'shm' string (default "ltToOCF")
global
This option helps to avoid all the |hit-enter| prompts caused by file
messages, for example with CTRL-G, and to avoid some other messages.
It is a list of flags:
flag meaning when present ~
- f use "(3 of 5)" instead of "(file 3 of 5)" *shm-f*
- i use "[noeol]" instead of "[Incomplete last line]" *shm-i*
l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l*
m use "[+]" instead of "[Modified]" *shm-m*
- n use "[New]" instead of "[New File]" *shm-n*
r use "[RO]" instead of "[readonly]" *shm-r*
w use "[w]" instead of "written" for file write message *shm-w*
and "[a]" instead of "appended" for ':w >> file' command
- x use "[dos]" instead of "[dos format]", "[unix]" *shm-x*
- instead of "[unix format]" and "[mac]" instead of "[mac
- format]"
a all of the above abbreviations *shm-a*
o overwrite message for writing a file with subsequent *shm-o*
@@ -5545,7 +5451,7 @@ A jump table for the options with a short description can be found at |Q_op|.
A don't give the "ATTENTION" message when an existing *shm-A*
swap file is found
I don't give the intro message when starting Vim, *shm-I*
- see |:intro|
+ see |:intro|
c don't give |ins-completion-menu| messages; for *shm-c*
example, "-- XXX completion (YYY)", "match 1 of 2", "The only
match", "Pattern not found", "Back at original", etc.
@@ -5554,8 +5460,8 @@ A jump table for the options with a short description can be found at |Q_op|.
q use "recording" instead of "recording @a" *shm-q*
F don't give the file info when editing a file, like *shm-F*
`:silent` was used for the command
- S do not show search count message when searching, e.g. *shm-S*
- "[1/5]"
+ S do not show search count message when searching, e.g. *shm-S*
+ "[1/5]"
This gives you the opportunity to avoid that a change between buffers
requires you to hit <Enter>, but still gives as useful a message as
@@ -5566,14 +5472,12 @@ A jump table for the options with a short description can be found at |Q_op|.
shm=a Abbreviation, but no loss of information.
shm=at Abbreviation, and truncate message when necessary.
- *'showbreak'* *'sbr'* *E595*
+ *'showbreak'* *'sbr'* *E595*
'showbreak' 'sbr' string (default "")
global or local to window |global-local|
String to put at the start of lines that have been wrapped. Useful
values are "> " or "+++ ": >
- :set showbreak=>\
-< Note the backslash to escape the trailing space. It's easier like
- this: >
+ :let &showbreak = "> "
:let &showbreak = '+++ '
< Only printable single-cell characters are allowed, excluding <Tab> and
comma (in a future version the comma might be used to separate the
@@ -5586,8 +5490,9 @@ A jump table for the options with a short description can be found at |Q_op|.
set and you want no value in the current window use NONE: >
:setlocal showbreak=NONE
<
- *'showcmd'* *'sc'* *'noshowcmd'* *'nosc'*
-'showcmd' 'sc' boolean (default: on)
+
+ *'showcmd'* *'sc'* *'noshowcmd'* *'nosc'*
+'showcmd' 'sc' boolean (default on)
global
Show (partial) command in the last line of the screen. Set this
option off if your terminal is slow.
@@ -5601,7 +5506,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This information can be displayed in an alternative location using the
'showcmdloc' option, useful when 'cmdheight' is 0.
- *'showcmdloc'* *'sloc'*
+ *'showcmdloc'* *'sloc'*
'showcmdloc' 'sloc' string (default "last")
global
This option can be used to display the (partially) entered command in
@@ -5617,7 +5522,7 @@ A jump table for the options with a short description can be found at |Q_op|.
displayed in a convenient location.
*'showfulltag'* *'sft'* *'noshowfulltag'* *'nosft'*
-'showfulltag' 'sft' boolean (default off)
+'showfulltag' 'sft' boolean (default off)
global
When completing a word in insert mode (see |ins-completion|) from the
tags file, show both the tag name and a tidied-up form of the search
@@ -5628,7 +5533,7 @@ A jump table for the options with a short description can be found at |Q_op|.
'completeopt', because the completion from the search pattern may not
match the typed text.
- *'showmatch'* *'sm'* *'noshowmatch'* *'nosm'*
+ *'showmatch'* *'sm'* *'noshowmatch'* *'nosm'*
'showmatch' 'sm' boolean (default off)
global
When a bracket is inserted, briefly jump to the matching one. The
@@ -5636,8 +5541,6 @@ A jump table for the options with a short description can be found at |Q_op|.
show the match can be set with 'matchtime'.
A Beep is given if there is no match (no matter if the match can be
seen or not).
- This option is reset when 'paste' is set and restored when 'paste' is
- reset.
When the 'm' flag is not included in 'cpoptions', typing a character
will immediately move the cursor back to where it belongs.
See the "sm" field in 'guicursor' for setting the cursor shape and
@@ -5649,8 +5552,8 @@ A jump table for the options with a short description can be found at |Q_op|.
around |pi_paren.txt|.
Note: Use of the short form is rated PG.
- *'showmode'* *'smd'* *'noshowmode'* *'nosmd'*
-'showmode' 'smd' boolean (default: on)
+ *'showmode'* *'smd'* *'noshowmode'* *'nosmd'*
+'showmode' 'smd' boolean (default on)
global
If in Insert, Replace or Visual mode put a message on the last line.
The |hl-ModeMsg| highlight group determines the highlighting.
@@ -5677,8 +5580,8 @@ A jump table for the options with a short description can be found at |Q_op|.
When using a slow terminal set it to a large number or 0. Not used
for "zh" and "zl" commands.
- *'sidescrolloff'* *'siso'*
-'sidescrolloff' 'siso' number (default 0)
+ *'sidescrolloff'* *'siso'*
+'sidescrolloff' 'siso' number (default 0)
global or local to window |global-local|
The minimal number of screen columns to keep to the left and to the
right of the cursor if 'nowrap' is set. Setting this option to a
@@ -5700,11 +5603,12 @@ A jump table for the options with a short description can be found at |Q_op|.
:set nowrap sidescroll=1 listchars=extends:>,precedes:<
:set sidescrolloff=1
<
+
*'signcolumn'* *'scl'*
'signcolumn' 'scl' string (default "auto")
local to window
When and how to draw the signcolumn. Valid values are:
- "auto" only when there is a sign to display
+ "auto" only when there is a sign to display
"auto:[1-9]" resize to accommodate multiple signs up to the
given number (maximum 9), e.g. "auto:4"
"auto:[1-8]-[2-9]"
@@ -5713,20 +5617,13 @@ A jump table for the options with a short description can be found at |Q_op|.
at least the given minimum (maximum 8) fixed
space. The minimum number should always be less
than the maximum number, e.g. "auto:2-5"
- "no" never
- "yes" always
+ "no" never
+ "yes" always
"yes:[1-9]" always, with fixed space for signs up to the given
number (maximum 9), e.g. "yes:3"
"number" display signs in the 'number' column. If the number
column is not present, then behaves like "auto".
- Note regarding "orphaned signs": with signcolumn numbers higher than
- 1, deleting lines will also remove the associated signs automatically,
- in contrast to the default Vim behavior of keeping and grouping them.
- This is done in order for the signcolumn appearance not appear weird
- during line deletion.
-
-
*'smartcase'* *'scs'* *'nosmartcase'* *'noscs'*
'smartcase' 'scs' boolean (default off)
global
@@ -5737,7 +5634,7 @@ A jump table for the options with a short description can be found at |Q_op|.
"*" and "#" you can make 'smartcase' used by doing a "/" command,
recalling the search pattern from history and hitting <Enter>.
- *'smartindent'* *'si'* *'nosmartindent'* *'nosi'*
+ *'smartindent'* *'si'* *'nosmartindent'* *'nosi'*
'smartindent' 'si' boolean (default off)
local to buffer
Do smart autoindenting when starting a new line. Works for C-like
@@ -5759,10 +5656,8 @@ A jump table for the options with a short description can be found at |Q_op|.
mapping: ":inoremap # X^H#", where ^H is entered with CTRL-V CTRL-H.
When using the ">>" command, lines starting with '#' are not shifted
right.
- This option is reset when 'paste' is set and restored when 'paste' is
- reset.
- *'smarttab'* *'sta'* *'nosmarttab'* *'nosta'*
+ *'smarttab'* *'sta'* *'nosmarttab'* *'nosta'*
'smarttab' 'sta' boolean (default on)
global
When on, a <Tab> in front of a line inserts blanks according to
@@ -5775,10 +5670,20 @@ A jump table for the options with a short description can be found at |Q_op|.
What gets inserted (a <Tab> or spaces) depends on the 'expandtab'
option. Also see |ins-expandtab|. When 'expandtab' is not set, the
number of spaces is minimized by using <Tab>s.
- This option is reset when 'paste' is set and restored when 'paste' is
- reset.
- *'softtabstop'* *'sts'*
+ *'smoothscroll'* *'sms'* *'nosmoothscroll'* *'nosms'*
+'smoothscroll' 'sms' boolean (default off)
+ local to window
+ Scrolling works with screen lines. When 'wrap' is set and the first
+ line in the window wraps part of it may not be visible, as if it is
+ above the window. "<<<" is displayed at the start of the first line,
+ highlighted with |hl-NonText|.
+ You may also want to add "lastline" to the 'display' option to show as
+ much of the last line as possible.
+ NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
+ and scrolling with the mouse.
+
+ *'softtabstop'* *'sts'*
'softtabstop' 'sts' number (default 0)
local to buffer
Number of spaces that a <Tab> counts for while performing editing
@@ -5789,8 +5694,6 @@ A jump table for the options with a short description can be found at |Q_op|.
commands like "x" still work on the actual characters.
When 'sts' is zero, this feature is off.
When 'sts' is negative, the value of 'shiftwidth' is used.
- 'softtabstop' is set to 0 when the 'paste' option is set and restored
- when 'paste' is reset.
See also |ins-expandtab|. When 'expandtab' is not set, the number of
spaces is minimized by using <Tab>s.
The 'L' flag in 'cpoptions' changes how tabs are used when 'list' is
@@ -5806,7 +5709,7 @@ A jump table for the options with a short description can be found at |Q_op|.
The languages are specified with 'spelllang'.
*'spellcapcheck'* *'spc'*
-'spellcapcheck' 'spc' string (default "[.?!]\_[\])'" \t]\+")
+'spellcapcheck' 'spc' string (default "[.?!]\_[\])'"\t ]\+")
local to buffer
Pattern to locate the end of a sentence. The following word will be
checked to start with a capital letter. If not then it is highlighted
@@ -5819,11 +5722,12 @@ A jump table for the options with a short description can be found at |Q_op|.
|set-spc-auto|.
*'spellfile'* *'spf'*
-'spellfile' 'spf' string (default empty)
+'spellfile' 'spf' string (default "")
local to buffer
Name of the word list file where words are added for the |zg| and |zw|
commands. It must end in ".{encoding}.add". You need to include the
path, otherwise the file is placed in the current directory.
+ The path may include characters from 'isfname', space, comma and '@'.
*E765*
It may also be a comma-separated list of names. A count before the
|zg| and |zw| commands can be used to access each. This allows using
@@ -5962,14 +5866,13 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
-
*'splitbelow'* *'sb'* *'nosplitbelow'* *'nosb'*
'splitbelow' 'sb' boolean (default off)
global
When on, splitting a window will put the new window below the current
one. |:split|
- *'splitkeep'* *'spk'*
+ *'splitkeep'* *'spk'*
'splitkeep' 'spk' string (default "cursor")
global
The value of this option determines the scroll behavior when opening,
@@ -5991,21 +5894,22 @@ A jump table for the options with a short description can be found at |Q_op|.
When on, splitting a window will put the new window right of the
current one. |:vsplit|
- *'startofline'* *'sol'* *'nostartofline'* *'nosol'*
+ *'startofline'* *'sol'* *'nostartofline'* *'nosol'*
'startofline' 'sol' boolean (default off)
global
When "on" the commands listed below move the cursor to the first
non-blank of the line. When off the cursor is kept in the same column
- (if possible). This applies to the commands: CTRL-D, CTRL-U, CTRL-B,
- CTRL-F, "G", "H", "M", "L", gg, and to the commands "d", "<<" and ">>"
- with a linewise operator, with "%" with a count and to buffer changing
- commands (CTRL-^, :bnext, :bNext, etc.). Also for an Ex command that
- only has a line number, e.g., ":25" or ":+".
+ (if possible). This applies to the commands:
+ - CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg"
+ - "d", "<<" and ">>" with a linewise operator
+ - "%" with a count
+ - buffer changing commands (CTRL-^, :bnext, :bNext, etc.)
+ - Ex commands that only have a line number, e.g., ":25" or ":+".
In case of buffer changing commands the cursor is placed at the column
where it was the last time the buffer was edited.
- *'statuscolumn'* *'stc'*
-'statuscolumn' 'stc' string (default: empty)
+ *'statuscolumn'* *'stc'*
+'statuscolumn' 'stc' string (default "")
local to window
EXPERIMENTAL
When non-empty, this option determines the content of the area to the
@@ -6058,8 +5962,8 @@ A jump table for the options with a short description can be found at |Q_op|.
< WARNING: this expression is evaluated for each screen line so defining
an expensive expression can negatively affect render performance.
- *'statusline'* *'stl'* *E540* *E542*
-'statusline' 'stl' string (default empty)
+ *'statusline'* *'stl'* *E540* *E542*
+'statusline' 'stl' string (default "")
global or local to window |global-local|
When non-empty, this option determines the content of the status line.
Also see |status-line|.
@@ -6163,41 +6067,45 @@ A jump table for the options with a short description can be found at |Q_op|.
( - Start of item group. Can be used for setting the width and
alignment of a section. Must be followed by %) somewhere.
) - End of item group. No width fields allowed.
- T N For 'tabline': start of tab page N label. Use %T or %X to end
- the label. Clicking this label with left mouse button switches
+ T N For 'tabline': start of tab page N label. Use %T or %X to end
+ the label. Clicking this label with left mouse button switches
to the specified tab page.
- X N For 'tabline': start of close tab N label. Use %X or %T to end
- the label, e.g.: %3Xclose%X. Use %999X for a "close current
- tab" label. Clicking this label with left mouse button closes
+ X N For 'tabline': start of close tab N label. Use %X or %T to end
+ the label, e.g.: %3Xclose%X. Use %999X for a "close current
+ tab" label. Clicking this label with left mouse button closes
specified tab page.
- @ N Start of execute function label. Use %X or %T to
- end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
- label runs specified function: in the example when clicking once
- using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
- ' ')" expression will be run. Function receives the
+ @ N Start of execute function label. Use %X or %T to
+ end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
+ label runs specified function: in the example when clicking once
+ using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
+ ' ')" expression will be run. Function receives the
following arguments in order:
1. minwid field value or zero if no N was specified
2. number of mouse clicks to detect multiple clicks
- 3. mouse button used: "l", "r" or "m" for left, right or middle
- button respectively; one should not rely on third argument
- being only "l", "r" or "m": any other non-empty string value
- that contains only ASCII lower case letters may be expected
+ 3. mouse button used: "l", "r" or "m" for left, right or middle
+ button respectively; one should not rely on third argument
+ being only "l", "r" or "m": any other non-empty string value
+ that contains only ASCII lower case letters may be expected
for other mouse buttons
- 4. modifiers pressed: string which contains "s" if shift
- modifier was pressed, "c" for control, "a" for alt and "m"
- for meta; currently if modifier is not pressed string
- contains space instead, but one should not rely on presence
- of spaces or specific order of modifiers: use |stridx()| to
- test whether some modifier is present; string is guaranteed
- to contain only ASCII letters and spaces, one letter per
- modifier; "?" modifier may also be present, but its presence
- is a bug that denotes that new mouse button recognition was
- added without modifying code that reacts on mouse clicks on
+ 4. modifiers pressed: string which contains "s" if shift
+ modifier was pressed, "c" for control, "a" for alt and "m"
+ for meta; currently if modifier is not pressed string
+ contains space instead, but one should not rely on presence
+ of spaces or specific order of modifiers: use |stridx()| to
+ test whether some modifier is present; string is guaranteed
+ to contain only ASCII letters and spaces, one letter per
+ modifier; "?" modifier may also be present, but its presence
+ is a bug that denotes that new mouse button recognition was
+ added without modifying code that reacts on mouse clicks on
this label.
+ Use |getmousepos()|.winid in the specified function to get the
+ corresponding window id of the clicked item.
< - Where to truncate line if too long. Default is at the start.
No width fields allowed.
- = - Separation point between alignment sections. Each section will
- be separated by an equal number of spaces.
+ = - Separation point between alignment sections. Each section will
+ be separated by an equal number of spaces. With one %= what
+ comes after it will be right-aligned. With two %= there is a
+ middle part, with white space left and right of it.
No width fields allowed.
# - Set highlight group. The name must follow and then a # again.
Thus use %#HLname# for highlight group HLname. The same
@@ -6205,8 +6113,8 @@ A jump table for the options with a short description can be found at |Q_op|.
windows.
* - Set highlight group to User{N}, where {N} is taken from the
minwid field, e.g. %1*. Restore normal highlight with %* or %0*.
- The difference between User{N} and StatusLine will be applied
- to StatusLineNC for the statusline of non-current windows.
+ The difference between User{N} and StatusLine will be applied to
+ StatusLineNC for the statusline of non-current windows.
The number N must be between 1 and 9. See |hl-User1..9|
When displaying a flag, Vim removes the leading comma, if any, when
@@ -6266,6 +6174,7 @@ A jump table for the options with a short description can be found at |Q_op|.
: if exists(a:var) | return a:val | else | return '' | endif
:endfunction
<
+
*'suffixes'* *'su'*
'suffixes' 'su' string (default ".bak,~,.o,.h,.info,.swp,.obj")
global
@@ -6287,8 +6196,9 @@ A jump table for the options with a short description can be found at |Q_op|.
file for the "gf", "[I", etc. commands. Example: >
:set suffixesadd=.java
<
+
*'swapfile'* *'swf'* *'noswapfile'* *'noswf'*
-'swapfile' 'swf' boolean (default on)
+'swapfile' 'swf' boolean (default on)
local to buffer
Use a swapfile for the buffer. This option can be reset when a
swapfile is not wanted for a specific buffer. For example, with
@@ -6313,16 +6223,18 @@ A jump table for the options with a short description can be found at |Q_op|.
'switchbuf' 'swb' string (default "uselast")
global
This option controls the behavior when switching between buffers.
- Mostly for |quickfix| commands some values are also used for other
- commands, as mentioned below.
+ This option is checked, when
+ - jumping to errors with the |quickfix| commands (|:cc|, |:cn|, |:cp|,
+ etc.).
+ - jumping to a tag using the |:stag| command.
+ - opening a file using the |CTRL-W_f| or |CTRL-W_F| command.
+ - jumping to a buffer using a buffer split command (e.g. |:sbuffer|,
+ |:sbnext|, or |:sbrewind|).
Possible values (comma-separated list):
- useopen If included, jump to the first open window that
- contains the specified buffer (if there is one).
- Otherwise: Do not examine other windows.
- This setting is checked with |quickfix| commands, when
- jumping to errors (":cc", ":cn", "cp", etc.). It is
- also used in all buffer related split commands, for
- example ":sbuffer", ":sbnext", or ":sbrewind".
+ useopen If included, jump to the first open window in the
+ current tab page that contains the specified buffer
+ (if there is one). Otherwise: Do not examine other
+ windows.
usetab Like "useopen", but also consider windows in other tab
pages.
split If included, split the current window before loading
@@ -6347,8 +6259,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Set to zero to remove the limit.
*'syntax'* *'syn'*
-'syntax' 'syn' string (default empty)
- local to buffer
+'syntax' 'syn' string (default "")
+ local to buffer |local-noglobal|
When this option is set, the syntax with this name is loaded, unless
syntax highlighting has been switched off with ":syntax off".
Otherwise this option does not always reflect the current syntax (the
@@ -6371,10 +6283,10 @@ A jump table for the options with a short description can be found at |Q_op|.
Syntax autocommand event is triggered with the value as argument.
This option is not copied to another buffer, independent of the 's' or
'S' flag in 'cpoptions'.
- Only normal file name characters can be used, "/\*?[|<>" are illegal.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
*'tabline'* *'tal'*
-'tabline' 'tal' string (default empty)
+'tabline' 'tal' string (default "")
global
When non-empty, this option determines the content of the tab pages
line at the top of the Vim window. When empty Vim will use a default
@@ -6397,14 +6309,12 @@ A jump table for the options with a short description can be found at |Q_op|.
Keep in mind that only one of the tab pages is the current one, others
are invisible and you can't jump to their windows.
-
*'tabpagemax'* *'tpm'*
'tabpagemax' 'tpm' number (default 50)
global
Maximum number of tab pages to be opened by the |-p| command line
argument or the ":tab all" command. |tabpage|
-
*'tabstop'* *'ts'*
'tabstop' 'ts' number (default 8)
local to buffer
@@ -6420,13 +6330,25 @@ A jump table for the options with a short description can be found at |Q_op|.
(or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim
will use a mix of tabs and spaces, but typing <Tab> and <BS> will
behave like a tab appears every 4 (or 3) characters.
- 2. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use
+ This is the recommended way, the file will look the same with other
+ tools and when listing it in a terminal.
+ 2. Set 'softtabstop' and 'shiftwidth' to whatever you prefer and use
+ 'expandtab'. This way you will always insert spaces. The
+ formatting will never be messed up when 'tabstop' is changed (leave
+ it at 8 just in case). The file will be a bit larger.
+ You do need to check if no Tabs exist in the file. You can get rid
+ of them by first setting 'expandtab' and using `%retab!`, making
+ sure the value of 'tabstop' is set correctly.
+ 3. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use
'expandtab'. This way you will always insert spaces. The
formatting will never be messed up when 'tabstop' is changed.
- 3. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use a
+ You do need to check if no Tabs exist in the file, just like in the
+ item just above.
+ 4. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use a
|modeline| to set these values when editing the file again. Only
- works when using Vim to edit the file.
- 4. Always set 'tabstop' and 'shiftwidth' to the same value, and
+ works when using Vim to edit the file, other tools assume a tabstop
+ is worth 8 spaces.
+ 5. Always set 'tabstop' and 'shiftwidth' to the same value, and
'noexpandtab'. This should then work (for initial indents only)
for any tabstop setting that people use. It might be nice to have
tabs after the first non-blank inserted as spaces if you do this
@@ -6454,7 +6376,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Linear searching is done anyway, for one file, when Vim finds a line
at the start of the file indicating that it's not sorted: >
- !_TAG_FILE_SORTED 0 /some comment/
+ !_TAG_FILE_SORTED 0 /some comment/
< [The whitespace before and after the '0' must be a single <Tab>]
When a binary search was done and no match was found in any of the
@@ -6488,7 +6410,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option doesn't affect commands that find all matching tags (e.g.,
command-line completion and ":help").
- *'tagcase'* *'tc'*
+ *'tagcase'* *'tc'*
'tagcase' 'tc' string (default "followic")
global or local to buffer |global-local|
This option specifies how case is handled when searching the tags
@@ -6499,8 +6421,8 @@ A jump table for the options with a short description can be found at |Q_op|.
match Match case
smart Ignore case unless an upper case letter is used
- *'tagfunc'* *'tfu'*
-'tagfunc' 'tfu' string (default: empty)
+ *'tagfunc'* *'tfu'*
+'tagfunc' 'tfu' string (default "")
local to buffer
This option specifies a function to be used to perform tag searches.
The function gets the tag pattern and should return a List of matching
@@ -6508,6 +6430,8 @@ A jump table for the options with a short description can be found at |Q_op|.
function and an example. The value can be the name of a function, a
|lambda| or a |Funcref|. See |option-value-function| for more
information.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
*'taglength'* *'tl'*
'taglength' 'tl' number (default 0)
@@ -6515,7 +6439,7 @@ A jump table for the options with a short description can be found at |Q_op|.
If non-zero, tags are significant up to this number of characters.
*'tagrelative'* *'tr'* *'notagrelative'* *'notr'*
-'tagrelative' 'tr' boolean (default: on)
+'tagrelative' 'tr' boolean (default on)
global
If on and using a tags file in another directory, file names in that
tags file are relative to the directory where the tags file is.
@@ -6524,8 +6448,8 @@ A jump table for the options with a short description can be found at |Q_op|.
'tags' 'tag' string (default "./tags;,tags")
global or local to buffer |global-local|
Filenames for the tag command, separated by spaces or commas. To
- include a space or comma in a file name, precede it with a backslash
- (see |option-backslash| about including spaces and backslashes).
+ include a space or comma in a file name, precede it with backslashes
+ (see |option-backslash| about including spaces/commas and backslashes).
When a file name starts with "./", the '.' is replaced with the path
of the current file. But only when the 'd' flag is not included in
'cpoptions'. Environment variables are expanded |:set_env|. Also see
@@ -6541,7 +6465,7 @@ A jump table for the options with a short description can be found at |Q_op|.
file names from the list. This avoids problems when a future version
uses another default.
- *'tagstack'* *'tgst'* *'notagstack'* *'notgst'*
+ *'tagstack'* *'tgst'* *'notagstack'* *'notgst'*
'tagstack' 'tgst' boolean (default on)
global
When on, the |tagstack| is used normally. When off, a ":tag" or
@@ -6552,9 +6476,8 @@ A jump table for the options with a short description can be found at |Q_op|.
Resetting this option is useful when using a ":tag" command in a
mapping which should not change the tagstack.
- *'termbidi'* *'tbidi'*
- *'notermbidi'* *'notbidi'*
-'termbidi' 'tbidi' boolean (default off)
+ *'termbidi'* *'tbidi'* *'notermbidi'* *'notbidi'*
+'termbidi' 'tbidi' boolean (default off)
global
The terminal is in charge of Bi-directionality of text (as specified
by Unicode). The terminal is also expected to do the required shaping
@@ -6565,15 +6488,15 @@ A jump table for the options with a short description can be found at |Q_op|.
'arabicshape' is ignored, but 'rightleft' isn't changed automatically.
For further details see |arabic.txt|.
- *'termguicolors'* *'tgc'* *'notermguicolors'* *'notgc'*
-'termguicolors' 'tgc' boolean (default off)
+ *'termguicolors'* *'tgc'* *'notermguicolors'* *'notgc'*
+'termguicolors' 'tgc' boolean (default off)
global
Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight|
attributes instead of "cterm" attributes. |guifg|
Requires an ISO-8613-3 compatible terminal.
- *'termpastefilter'* *'tpf'*
-'termpastefilter' 'tpf' string (default: "BS,HT,ESC,DEL")
+ *'termpastefilter'* *'tpf'*
+'termpastefilter' 'tpf' string (default "BS,HT,ESC,DEL")
global
A comma-separated list of options for specifying control characters
to be removed from the text pasted into the terminal window. The
@@ -6594,6 +6517,13 @@ A jump table for the options with a short description can be found at |Q_op|.
C1 Control characters 0x80...0x9F
+ *'termsync'* *'notermsync'*
+'termsync' boolean (default on)
+ global
+ If the host terminal supports it, buffer all screen updates
+ made during a redraw cycle so that each screen is displayed in
+ the terminal all at once. This can prevent tearing or flickering
+ when the terminal updates faster than Nvim can redraw.
*'textwidth'* *'tw'*
'textwidth' 'tw' number (default 0)
@@ -6601,8 +6531,6 @@ A jump table for the options with a short description can be found at |Q_op|.
Maximum width of text that is being inserted. A longer line will be
broken after white space to get this width. A zero value disables
this.
- 'textwidth' is set to 0 when the 'paste' option is set and restored
- when 'paste' is reset.
When 'textwidth' is zero, 'wrapmargin' may be used. See also
'formatoptions' and |ins-textwidth|.
When 'formatexpr' is set it will be used to break the line.
@@ -6625,8 +6553,8 @@ A jump table for the options with a short description can be found at |Q_op|.
another default. Backticks cannot be used in this option for security
reasons.
- *'thesaurusfunc'* *'tsrfu'*
-'thesaurusfunc' 'tsrfu' string (default: empty)
+ *'thesaurusfunc'* *'tsrfu'*
+'thesaurusfunc' 'tsrfu' string (default "")
global or local to buffer |global-local|
This option specifies a function to be used for thesaurus completion
with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
@@ -6636,46 +6564,24 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'tildeop'* *'top'* *'notildeop'* *'notop'*
+ *'tildeop'* *'top'* *'notildeop'* *'notop'*
'tildeop' 'top' boolean (default off)
global
When on: The tilde command "~" behaves like an operator.
*'timeout'* *'to'* *'notimeout'* *'noto'*
-'timeout' 'to' boolean (default on)
+'timeout' 'to' boolean (default on)
global
This option and 'timeoutlen' determine the behavior when part of a
mapped key sequence has been received. For example, if <c-f> is
pressed and 'timeout' is set, Nvim will wait 'timeoutlen' milliseconds
for any key that can follow <c-f> in a mapping.
- *'ttimeout'* *'nottimeout'*
-'ttimeout' boolean (default on)
- global
- This option and 'ttimeoutlen' determine the behavior when part of a
- key code sequence has been received by the |TUI|.
-
- For example if <Esc> (the \x1b byte) is received and 'ttimeout' is
- set, Nvim waits 'ttimeoutlen' milliseconds for the terminal to
- complete a key code sequence. If no input arrives before the timeout,
- a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
-
- On very slow systems this may fail, causing cursor keys not to work
- sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
- Nvim will wait for the next character to arrive after an <Esc>.
-
*'timeoutlen'* *'tm'*
'timeoutlen' 'tm' number (default 1000)
global
Time in milliseconds to wait for a mapped sequence to complete.
- *'ttimeoutlen'* *'ttm'*
-'ttimeoutlen' 'ttm' number (default 50)
- global
- Time in milliseconds to wait for a key code sequence to complete. Also
- used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has
- been typed.
-
*'title'* *'notitle'*
'title' boolean (default off)
global
@@ -6691,7 +6597,7 @@ A jump table for the options with a short description can be found at |Q_op|.
(path) is the path of the file being edited
- NVIM the server name |v:servername| or "NVIM"
- *'titlelen'*
+ *'titlelen'*
'titlelen' number (default 85)
global
Gives the percentage of 'columns' to use for the length of the window
@@ -6704,14 +6610,15 @@ A jump table for the options with a short description can be found at |Q_op|.
values from 1 to 30000 percent can be used.
'titlelen' is also used for the 'titlestring' option.
- *'titleold'*
+ *'titleold'*
'titleold' string (default "")
global
If not empty, this option will be used to set the window title when
exiting. Only if 'title' is enabled.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
- *'titlestring'*
+
+ *'titlestring'*
'titlestring' string (default "")
global
When this option is not empty, it will be used for the title of the
@@ -6734,7 +6641,29 @@ A jump table for the options with a short description can be found at |Q_op|.
NOTE: Use of special characters in 'titlestring' may cause the display
to be garbled (e.g., when it contains a CR or NL character).
- *'undodir'* *'udir'* *E5003*
+ *'ttimeout'* *'nottimeout'*
+'ttimeout' boolean (default on)
+ global
+ This option and 'ttimeoutlen' determine the behavior when part of a
+ key code sequence has been received by the |TUI|.
+
+ For example if <Esc> (the \x1b byte) is received and 'ttimeout' is
+ set, Nvim waits 'ttimeoutlen' milliseconds for the terminal to
+ complete a key code sequence. If no input arrives before the timeout,
+ a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
+
+ On very slow systems this may fail, causing cursor keys not to work
+ sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
+ Nvim will wait for the next character to arrive after an <Esc>.
+
+ *'ttimeoutlen'* *'ttm'*
+'ttimeoutlen' 'ttm' number (default 50)
+ global
+ Time in milliseconds to wait for a key code sequence to complete. Also
+ used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has
+ been typed.
+
+ *'undodir'* *'udir'* *E5003*
'undodir' 'udir' string (default "$XDG_STATE_HOME/nvim/undo//")
global
List of directory names for undo files, separated with commas.
@@ -6758,7 +6687,7 @@ A jump table for the options with a short description can be found at |Q_op|.
though the trailing slashes are present (see 'backupdir' for what this
means).
- *'undofile'* *'noundofile'* *'udf'* *'noudf'*
+ *'undofile'* *'udf'* *'noundofile'* *'noudf'*
'undofile' 'udf' boolean (default off)
local to buffer
When on, Vim automatically saves undo history to an undo file when
@@ -6807,7 +6736,7 @@ A jump table for the options with a short description can be found at |Q_op|.
this option to a lower value if you run out of memory.
*'updatecount'* *'uc'*
-'updatecount' 'uc' number (default: 200)
+'updatecount' 'uc' number (default 200)
global
After typing this many characters the swap file will be written to
disk. When zero, no swap file will be created at all (see chapter on
@@ -6890,7 +6819,7 @@ A jump table for the options with a short description can be found at |Q_op|.
If 'verbosefile' is set then the verbose messages are not displayed.
*'verbosefile'* *'vfile'*
-'verbosefile' 'vfile' string (default empty)
+'verbosefile' 'vfile' string (default "")
global
When not empty all messages are written in a file with this name.
When the file exists messages are appended.
@@ -6899,16 +6828,18 @@ A jump table for the options with a short description can be found at |Q_op|.
Setting 'verbosefile' to a new value is like making it empty first.
The difference with |:redir| is that verbose messages are not
displayed when 'verbosefile' is set.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
*'viewdir'* *'vdir'*
-'viewdir' 'vdir' string (default: "$XDG_STATE_HOME/nvim/view//")
+'viewdir' 'vdir' string (default "$XDG_STATE_HOME/nvim/view//")
global
Name of the directory where to store files for |:mkview|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
*'viewoptions'* *'vop'*
-'viewoptions' 'vop' string (default: "folds,cursor,curdir")
+'viewoptions' 'vop' string (default "folds,cursor,curdir")
global
Changes the effect of the |:mkview| command. It is a comma-separated
list of words. Each word enables saving and restoring something:
@@ -6923,7 +6854,7 @@ A jump table for the options with a short description can be found at |Q_op|.
slash |deprecated| Always enabled. Uses "/" in filenames.
unix |deprecated| Always enabled. Uses "\n" line endings.
- *'virtualedit'* *'ve'*
+ *'virtualedit'* *'ve'*
'virtualedit' 've' string (default "")
global or local to window |global-local|
A comma-separated list of these words:
@@ -6953,7 +6884,7 @@ A jump table for the options with a short description can be found at |Q_op|.
not get a warning for it.
When combined with other words, "none" is ignored.
- *'visualbell'* *'vb'* *'novisualbell'* *'novb'* *beep*
+ *'visualbell'* *'vb'* *'novisualbell'* *'novb'*
'visualbell' 'vb' boolean (default off)
global
Use visual bell instead of beeping. Also see 'errorbells'.
@@ -6965,7 +6896,7 @@ A jump table for the options with a short description can be found at |Q_op|.
has been changed.
*'whichwrap'* *'ww'*
-'whichwrap' 'ww' string (default: "b,s")
+'whichwrap' 'ww' string (default "b,s")
global
Allow specified keys that move the cursor left/right to move to the
previous/next line when the cursor is on the first/last character in
@@ -6995,7 +6926,7 @@ A jump table for the options with a short description can be found at |Q_op|.
makes "dl", "cl", "yl" etc. work normally.
*'wildchar'* *'wc'*
-'wildchar' 'wc' number (default: <Tab>)
+'wildchar' 'wc' number (default <Tab>)
global
Character you have to type to start wildcard expansion in the
command-line, as specified with 'wildmode'.
@@ -7003,12 +6934,14 @@ A jump table for the options with a short description can be found at |Q_op|.
The character is not recognized when used inside a macro. See
'wildcharm' for that.
Some keys will not work, such as CTRL-C, <CR> and Enter.
+ <Esc> can be used, but hitting it twice in a row will still exit
+ command-line as a failsafe measure.
Although 'wc' is a number option, you can set it to a special key: >
:set wc=<Tab>
<
*'wildcharm'* *'wcm'*
-'wildcharm' 'wcm' number (default: none (0))
+'wildcharm' 'wcm' number (default 0)
global
'wildcharm' works exactly like 'wildchar', except that it is
recognized when used inside a macro. You can find "spare" command-line
@@ -7034,8 +6967,7 @@ A jump table for the options with a short description can be found at |Q_op|.
a pattern from the list. This avoids problems when a future version
uses another default.
-
- *'wildignorecase'* *'wic'* *'nowildignorecase'* *'nowic'*
+ *'wildignorecase'* *'wic'* *'nowildignorecase'* *'nowic'*
'wildignorecase' 'wic' boolean (default off)
global
When set case is ignored when completing file names and directories.
@@ -7043,8 +6975,7 @@ A jump table for the options with a short description can be found at |Q_op|.
Does not apply when the shell is used to expand wildcards, which
happens when there are special characters.
-
- *'wildmenu'* *'wmnu'* *'nowildmenu'* *'nowmnu'*
+ *'wildmenu'* *'wmnu'* *'nowildmenu'* *'nowmnu'*
'wildmenu' 'wmnu' boolean (default on)
global
When 'wildmenu' is on, command-line completion operates in an enhanced
@@ -7062,18 +6993,21 @@ A jump table for the options with a short description can be found at |Q_op|.
a completion.
While the menu is active these keys have special meanings:
-
- CTRL-Y - accept the currently selected match and stop
- completion.
- CTRL-E - end completion, go back to what was there before
- selecting a match.
+ CTRL-P - go to the previous entry
+ CTRL-N - go to the next entry
<Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
+ <PageUp> - select a match several entries back
+ <PageDown> - select a match several entries further
+ <Up> - in filename/menu name completion: move up into
+ parent directory or parent menu.
<Down> - in filename/menu name completion: move into a
subdirectory or submenu.
<CR> - in menu completion, when the cursor is just after a
dot: move into a submenu.
- <Up> - in filename/menu name completion: move up into
- parent directory or parent menu.
+ CTRL-E - end completion, go back to what was there before
+ selecting a match.
+ CTRL-Y - accept the currently selected match and stop
+ completion.
If you want <Left> and <Right> to move the cursor instead of selecting
a different match, use this: >
@@ -7083,7 +7017,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|hl-WildMenu| highlights the current match.
*'wildmode'* *'wim'*
-'wildmode' 'wim' string (default: "full")
+'wildmode' 'wim' string (default "full")
global
Completion mode that is used for the character specified with
'wildchar'. It is a comma-separated list of up to four parts. Each
@@ -7153,7 +7087,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'winaltkeys'* *'wak'*
'winaltkeys' 'wak' string (default "menu")
global
- {only used in Win32}
+ only used in Win32
Some GUI versions allow the access to menu entries by using the ALT
key in combination with a character that appears underlined in the
menu. This conflicts with the use of the ALT key for mappings and
@@ -7170,7 +7104,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option is not used for <F10>; on Win32.
*'winbar'* *'wbr'*
-'winbar' 'wbr' string (default empty)
+'winbar' 'wbr' string (default "")
global or local to window |global-local|
When non-empty, this option enables the window bar and determines its
contents. The window bar is a bar that's shown at the top of every
@@ -7187,7 +7121,7 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set in a modeline when 'modelineexpr' is off.
*'winblend'* *'winbl'*
-'winblend' 'winbl' number (default 0)
+'winblend' 'winbl' number (default 0)
local to window
Enables pseudo-transparency for a floating window. Valid values are in
the range of 0 for fully opaque window (disabled) to 100 for fully
@@ -7195,8 +7129,8 @@ A jump table for the options with a short description can be found at |Q_op|.
UI-dependent. Works best with RGB colors. 'termguicolors'
- *'window'* *'wi'*
-'window' 'wi' number (default screen height - 1)
+ *'window'* *'wi'*
+'window' 'wi' number (default screen height - 1)
global
Window height used for |CTRL-F| and |CTRL-B| when there is only one
window and the value is smaller than 'lines' minus one. The screen
@@ -7208,6 +7142,21 @@ A jump table for the options with a short description can be found at |Q_op|.
Note: Do not confuse this with the height of the Vim window, use
'lines' for that.
+ *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'*
+'winfixheight' 'wfh' boolean (default off)
+ local to window |local-noglobal|
+ Keep the window height when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the
+ |preview-window| and |quickfix-window|.
+ The height may be changed anyway when running out of room.
+
+ *'winfixwidth'* *'wfw'* *'nowinfixwidth'* *'nowfw'*
+'winfixwidth' 'wfw' boolean (default off)
+ local to window |local-noglobal|
+ Keep the window width when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|.
+ The width may be changed anyway when running out of room.
+
*'winheight'* *'wh'* *E591*
'winheight' 'wh' number (default 1)
global
@@ -7228,8 +7177,8 @@ A jump table for the options with a short description can be found at |Q_op|.
'winheight' applies to the current window. Use 'winminheight' to set
the minimal height for other windows.
- *'winhighlight'* *'winhl'*
-'winhighlight' 'winhl' string (default empty)
+ *'winhighlight'* *'winhl'*
+'winhighlight' 'winhl' string (default "")
local to window
Window-local highlights. Comma-delimited list of highlight
|group-name| pairs "{hl-from}:{hl-to},..." where each {hl-from} is
@@ -7248,20 +7197,6 @@ A jump table for the options with a short description can be found at |Q_op|.
Example: show a different color for non-current windows: >
set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
<
- *'winfixheight'* *'wfh'* *'nowinfixheight'* *'nowfh'*
-'winfixheight' 'wfh' boolean (default off)
- local to window
- Keep the window height when windows are opened or closed and
- 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the
- |preview-window| and |quickfix-window|.
- The height may be changed anyway when running out of room.
-
- *'winfixwidth'* *'wfw'* *'nowinfixwidth'* *'nowfw'*
-'winfixwidth' 'wfw' boolean (default off)
- local to window
- Keep the window width when windows are opened or closed and
- 'equalalways' is set. Also for |CTRL-W_=|.
- The width may be changed anyway when running out of room.
*'winminheight'* *'wmh'*
'winminheight' 'wmh' number (default 1)
@@ -7333,13 +7268,13 @@ A jump table for the options with a short description can be found at |Q_op|.
When 'textwidth' is non-zero, this option is not used.
See also 'formatoptions' and |ins-textwidth|.
- *'wrapscan'* *'ws'* *'nowrapscan'* *'nows'*
-'wrapscan' 'ws' boolean (default on) *E384* *E385*
+ *'wrapscan'* *'ws'* *'nowrapscan'* *'nows'* *E384* *E385*
+'wrapscan' 'ws' boolean (default on)
global
Searches wrap around the end of the file. Also applies to |]s| and
|[s|, searching for spelling mistakes.
- *'write'* *'nowrite'*
+ *'write'* *'nowrite'*
'write' boolean (default on)
global
Allows writing files. When not set, writing a file is not allowed.
@@ -7348,12 +7283,12 @@ A jump table for the options with a short description can be found at |Q_op|.
argument. Filtering text is still possible, even though this requires
writing a temporary file.
- *'writeany'* *'wa'* *'nowriteany'* *'nowa'*
+ *'writeany'* *'wa'* *'nowriteany'* *'nowa'*
'writeany' 'wa' boolean (default off)
global
Allows writing to any file with no need for "!" override.
- *'writebackup'* *'wb'* *'nowritebackup'* *'nowb'*
+ *'writebackup'* *'wb'* *'nowritebackup'* *'nowb'*
'writebackup' 'wb' boolean (default on)
global
Make a backup before overwriting a file. The backup is removed after
@@ -7372,8 +7307,7 @@ A jump table for the options with a short description can be found at |Q_op|.
*'writedelay'* *'wd'*
'writedelay' 'wd' number (default 0)
global
- The number of milliseconds to wait for each character sent to the
- screen. When positive, characters are sent to the UI one by one.
- See 'redrawdebug' for more options. For debugging purposes.
+ Only takes effect together with 'redrawdebug'.
+ The number of milliseconds to wait after each line or each flush
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt
index 5357aaa3f1..17136ee650 100644
--- a/runtime/doc/pattern.txt
+++ b/runtime/doc/pattern.txt
@@ -72,7 +72,7 @@ N Repeat the latest "/" or "?" [count] times in
command "/\<keyword\>". |exclusive|
'ignorecase' is used, 'smartcase' is not.
*v_star-default*
- In Visual mode, search forward for the current selection.
+{Visual}* In Visual mode, search forward for the current selection.
|default-mappings|
*#*
@@ -81,7 +81,7 @@ N Repeat the latest "/" or "?" [count] times in
backspace, try using "stty erase <BS>" before starting
Vim (<BS> is CTRL-H or a real backspace).
*v_#-default*
- In Visual mode, search backward for the current selection.
+{Visual}# In Visual mode, search backward for the current selection.
|default-mappings|
*gstar*
@@ -97,6 +97,8 @@ g# Like "#", but don't put "\<" and "\>" around the word.
*gd*
gd Goto local Declaration. When the cursor is on a local
variable, this command will jump to its declaration.
+ This was made to work for C code, in other languages
+ it may not work well.
First Vim searches for the start of the current
function, just like "[[". If it is not found the
search stops in line 1. If it is found, Vim goes back
@@ -430,8 +432,6 @@ after: \v \m \M \V matches ~
\\ \\ \\ \\ literal backslash
\{ { { { literal curly brace
-{only Vim supports \m, \M, \v and \V}
-
If you want to you can make a pattern immune to the 'magic' option being set
or not by putting "\m" or "\M" at the start of the pattern.
@@ -453,7 +453,7 @@ More explanation and examples below, follow the links. *E64* *E871*
\{n} \{n} n exactly
\{n,} \{n,} at least n as many as possible
\{,m} \{,m} 0 to m as many as possible
- \{} \{} 0 or more as many as possible (same as *)
+ \{} \{} 0 or more as many as possible (same as "*")
|/\{-| \{-n,m} \{-n,m} n to m as few as possible
\{-n} \{-n} n exactly
@@ -631,7 +631,7 @@ overview.
\{n} Matches n of the preceding atom
\{n,} Matches at least n of the preceding atom, as many as possible
\{,m} Matches 0 to m of the preceding atom, as many as possible
-\{} Matches 0 or more of the preceding atom, as many as possible (like *)
+\{} Matches 0 or more of the preceding atom, as many as possible (like "*")
*/\{-*
\{-n,m} matches n to m of the preceding atom, as few as possible
\{-n} matches n of the preceding atom
@@ -1065,7 +1065,7 @@ match ASCII characters, as indicated by the range.
\(\) A pattern enclosed by escaped parentheses. */\(* */\(\)* */\)*
E.g., "\(^a\)" matches 'a' at the start of a line.
- There can only be ten of these. You can use "\%(" to add more, but
+ There can only be nine of these. You can use "\%(" to add more, but
not counting it as a sub-expression.
*E51* *E54* *E55* *E872* *E873*
@@ -1129,21 +1129,21 @@ x A single character, with no special meaning, matches itself
are supported:
Name Func Contents ~
*[:alnum:]* [:alnum:] isalnum ASCII letters and digits
-*[:alpha:]* [:alpha:] isalpha ASCII letters
-*[:blank:]* [:blank:] space and tab
-*[:cntrl:]* [:cntrl:] iscntrl ASCII control characters
-*[:digit:]* [:digit:] decimal digits '0' to '9'
+*[:alpha:]* [:alpha:] isalpha ASCII letters
+*[:blank:]* [:blank:] space and tab
+*[:cntrl:]* [:cntrl:] iscntrl ASCII control characters
+*[:digit:]* [:digit:] decimal digits '0' to '9'
*[:graph:]* [:graph:] isgraph ASCII printable characters excluding
space
*[:lower:]* [:lower:] (1) lowercase letters (all letters when
'ignorecase' is used)
-*[:print:]* [:print:] (2) printable characters including space
+*[:print:]* [:print:] (2) printable characters including space
*[:punct:]* [:punct:] ispunct ASCII punctuation characters
-*[:space:]* [:space:] whitespace characters: space, tab, CR,
+*[:space:]* [:space:] whitespace characters: space, tab, CR,
NL, vertical tab, form feed
*[:upper:]* [:upper:] (3) uppercase letters (all letters when
'ignorecase' is used)
-*[:xdigit:]* [:xdigit:] hexadecimal digits: 0-9, a-f, A-F
+*[:xdigit:]* [:xdigit:] hexadecimal digits: 0-9, a-f, A-F
*[:return:]* [:return:] the <CR> character
*[:tab:]* [:tab:] the <Tab> character
*[:escape:]* [:escape:] the <Esc> character
@@ -1254,7 +1254,6 @@ letters only.
When "\c" appears anywhere in the pattern, the whole pattern is handled like
'ignorecase' is on. The actual value of 'ignorecase' and 'smartcase' is
ignored. "\C" does the opposite: Force matching case for the whole pattern.
-{only Vim supports \c and \C}
Note that 'ignorecase', "\c" and "\C" are not used for the character classes.
Examples:
@@ -1296,7 +1295,6 @@ will probably never match.
When "\Z" appears anywhere in the pattern, all composing characters are
ignored. Thus only the base characters need to match, the composing
characters may be different and the number of composing characters may differ.
-Only relevant when 'encoding' is "utf-8".
Exception: If the pattern starts with one or more composing characters, these
must match.
*/\%C*
@@ -1337,11 +1335,10 @@ difference between them is mostly just notation; here's a summary of where
they differ:
Capability in Vimspeak in Perlspeak ~
-----------------------------------------------------------------
force case insensitivity \c (?i)
force case sensitivity \C (?-i)
backref-less grouping \%(atom\) (?:atom)
-conservative quantifiers \{-n,m} *?, +?, ??, {}?
+conservative quantifiers \{-n,m} `*?,` +?, ??, {}?
0-width match atom\@= (?=atom)
0-width non-match atom\@! (?!atom)
0-width preceding match atom\@<= (?<=atom)
@@ -1445,10 +1442,11 @@ Finally, these constructs are unique to Perl:
Just like |:match| above, but set a separate match. Thus
there can be three matches active at the same time. The match
with the lowest number has priority if several match at the
- same position.
- The ":3match" command is used by the |matchparen| plugin. You
- are suggested to use ":match" for manual matching and
- ":2match" for another plugin.
+ same position. It uses the match id 3.
+ The ":3match" command is used by (older Vims) |matchparen|
+ plugin. You are suggested to use ":match" for manual matching
+ and ":2match" for another plugin or even better make use of
+ the more flexible |matchadd()| (and similar) functions instead.
==============================================================================
11. Fuzzy matching *fuzzy-matching*
diff --git a/runtime/doc/pi_gzip.txt b/runtime/doc/pi_gzip.txt
index 0363a8e34a..93a2388c26 100644
--- a/runtime/doc/pi_gzip.txt
+++ b/runtime/doc/pi_gzip.txt
@@ -12,6 +12,14 @@ The functionality mentioned here is a |standard-plugin|.
This plugin is only available if 'compatible' is not set.
You can avoid loading this plugin by setting the "loaded_gzip" variable: >
:let loaded_gzip = 1
+<
+ *g:gzip_exec*
+
+For security reasons, one may prevent that Vim runs executables automatically
+when opening a buffer. This option (default: "1") can be used to prevent
+executing the executables command when set to "0": >
+ :let g:gzip_exec = 0
+<
==============================================================================
1. Autocommands *gzip-autocmd*
@@ -19,7 +27,7 @@ You can avoid loading this plugin by setting the "loaded_gzip" variable: >
The plugin installs autocommands to intercept reading and writing of files
with these extensions:
- extension compression ~
+ extension compression >
*.Z compress (Lempel-Ziv)
*.gz gzip
*.bz2 bzip2
diff --git a/runtime/doc/pi_health.txt b/runtime/doc/pi_health.txt
index 5ba5d1beef..bcc933d8b2 100644
--- a/runtime/doc/pi_health.txt
+++ b/runtime/doc/pi_health.txt
@@ -12,7 +12,7 @@ any other environment conditions that a plugin might care about. Nvim ships
with healthchecks for configuration, performance, python support, ruby
support, clipboard support, and more.
-To run all healthchecks, use: >
+To run all healthchecks, use: >vim
:checkhealth
<
@@ -21,7 +21,7 @@ Plugin authors are encouraged to write new healthchecks. |health-dev|
==============================================================================
Commands *health-commands*
- *:che* *:checkhealth* *:CheckHealth*
+ *:che* *:checkhealth*
:che[ckhealth] Run all healthchecks.
*E5009*
Nvim depends on |$VIMRUNTIME|, 'runtimepath' and 'packpath' to
@@ -32,17 +32,17 @@ Commands *health-commands*
:che[ckhealth] {plugins}
Run healthcheck(s) for one or more plugins. E.g. to run only
- the standard Nvim healthcheck: >
+ the standard Nvim healthcheck: >vim
:checkhealth nvim
<
To run the healthchecks for the "foo" and "bar" plugins
(assuming they are on 'runtimepath' and they have implemented
- the Lua `require("foo.health").check()` interface): >
+ the Lua `require("foo.health").check()` interface): >vim
:checkhealth foo bar
<
To run healthchecks for Lua submodules, use dot notation or
"*" to refer to all submodules. For example Nvim provides
- `vim.lsp` and `vim.treesitter`: >
+ `vim.lsp` and `vim.treesitter`: >vim
:checkhealth vim.lsp vim.treesitter
:checkhealth vim*
<
@@ -52,22 +52,22 @@ Functions *health-functions* *vim.health*
The Lua "health" module can be used to create new healthchecks. To get started
see |health-dev|.
-vim.health.report_start({name}) *vim.health.report_start()*
+vim.health.start({name}) *vim.health.start()*
Starts a new report. Most plugins should call this only once, but if
you want different sections to appear in your report, call this once
per section.
-vim.health.report_info({msg}) *vim.health.report_info()*
+vim.health.info({msg}) *vim.health.info()*
Reports an informational message.
-vim.health.report_ok({msg}) *vim.health.report_ok()*
+vim.health.ok({msg}) *vim.health.ok()*
Reports a "success" message.
-vim.health.report_warn({msg} [, {advice}]) *vim.health.report_warn()*
+vim.health.warn({msg} [, {advice}]) *vim.health.warn()*
Reports a warning. {advice} is an optional list of suggestions to
present to the user.
-vim.health.report_error({msg} [, {advice}]) *vim.health.report_error()*
+vim.health.error({msg} [, {advice}]) *vim.health.error()*
Reports an error. {advice} is an optional list of suggestions to
present to the user.
@@ -100,17 +100,17 @@ All such health modules must return a Lua table containing a `check()`
function.
Copy this sample code into `lua/foo/health.lua`, replacing "foo" in the path
-with your plugin name: >
+with your plugin name: >lua
local M = {}
M.check = function()
- vim.health.report_start("my_plugin report")
+ vim.health.start("foo report")
-- make sure setup function parameters are ok
if check_setup() then
- vim.health.report_ok("Setup is correct")
+ vim.health.ok("Setup is correct")
else
- vim.health.report_error("Setup is incorrect")
+ vim.health.error("Setup is incorrect")
end
-- do some more checking
-- ...
diff --git a/runtime/doc/pi_msgpack.txt b/runtime/doc/pi_msgpack.txt
index 24a31f1de7..e900af97a8 100644
--- a/runtime/doc/pi_msgpack.txt
+++ b/runtime/doc/pi_msgpack.txt
@@ -3,13 +3,13 @@
Author: Nikolay Pavlov <kp-pav@yandex.ru>
Copyright: (c) 2015 by Nikolay Pavlov
-The Apache license applies to the files in this package, including
-runtime/autoload/msgpack.vim, runtime/doc/pi_msgpack.txt and
-test/functional/plugin/msgpack_spec.lua. Like anything else that's free,
-msgpack.vim and its associated files are provided *as is* and comes with no
-warranty of any kind, either expressed or implied. No guarantees of
-merchantability. No guarantees of suitability for any purpose. By using this
-plugin, you agree that in no event will the copyright holder be liable for any
+The Apache license applies to the files in this package, including
+runtime/autoload/msgpack.vim, runtime/doc/pi_msgpack.txt and
+test/functional/plugin/msgpack_spec.lua. Like anything else that's free,
+msgpack.vim and its associated files are provided as is and comes with no
+warranty of any kind, either expressed or implied. No guarantees of
+merchantability. No guarantees of suitability for any purpose. By using this
+plugin, you agree that in no event will the copyright holder be liable for any
damages resulting from the use of this software. Use at your own risk!
==============================================================================
@@ -35,7 +35,7 @@ damages resulting from the use of this software. Use at your own risk!
==============================================================================
2. Msgpack.vim introduction *msgpack.vim-intro*
-This plugin contains utility functions to be used in conjunction with
+This plugin contains utility functions to be used in conjunction with
|msgpackdump()| and |msgpackparse()| functions.
==============================================================================
@@ -43,13 +43,13 @@ This plugin contains utility functions to be used in conjunction with
FUNCTION ARGUMENTS *msgpack.vim-arguments*
-Disambiguation of arguments described below. Note: if e.g. function is listed
-as accepting |{msgpack-integer}| (or anything else) it means that function
+Disambiguation of arguments described below. Note: if e.g. function is listed
+as accepting |{msgpack-integer}| (or anything else) it means that function
does not check whether argument matches its description.
-*{msgpack-value}* Either |msgpack-special-dict| or a regular value, but
+*{msgpack-value}* Either |msgpack-special-dict| or a regular value, but
not function reference.
-*{msgpack-integer}* Any value for which |msgpack#type()| will return
+*{msgpack-integer}* Any value for which |msgpack#type()| will return
"integer".
*{msgpack-special-int}* |msgpack-special-dict| representing integer.
@@ -57,87 +57,87 @@ msgpack#is_int({msgpack-value}) *msgpack#is_int()*
Returns 1 if given {msgpack-value} is integer value, 0 otherwise.
msgpack#is_uint({msgpack-value}) *msgpack#is_uint()*
- Returns 1 if given {msgpack-value} is integer value greater or equal
+ Returns 1 if given {msgpack-value} is integer value greater or equal
to zero, 0 otherwise.
*msgpack#strftime*
msgpack#strftime({format}, {msgpack-integer}) *msgpack#strftime()*
- Same as |strftime()|, but second argument may be
+ Same as |strftime()|, but second argument may be
|msgpack-special-dict|. Requires |Python| to really work with
|msgpack-special-dict|s.
*msgpack#strptime*
msgpack#strptime({format}, {time}) *msgpack#strptime()*
- Reverse of |msgpack#strftime()|: for any time and format
- |msgpack#equal|( |msgpack#strptime|(format, |msgpack#strftime|(format,
+ Reverse of |msgpack#strftime()|: for any time and format
+ |msgpack#equal|( |msgpack#strptime|(format, |msgpack#strftime|(format,
time)), time) be true. Requires ||Python|, without it only supports
non-|msgpack-special-dict| nonnegative times and format equal to
`%Y-%m-%dT%H:%M:%S`.
msgpack#int_dict_to_str({msgpack-special-int}) *msgpack#int_dict_to_str()*
- Function which converts |msgpack-special-dict| integer value to
- a hexadecimal value like 0x1234567890ABCDEF (always returns exactly 16
+ Function which converts |msgpack-special-dict| integer value to
+ a hexadecimal value like 0x1234567890ABCDEF (always returns exactly 16
hexadecimal digits).
msgpack#special_type({msgpack-value}) *msgpack#special_type()*
- Returns zero if {msgpack-value} is not |msgpack-special-dict|. If it
- is it returns name of the key in |v:msgpack_types| which represents
+ Returns zero if {msgpack-value} is not |msgpack-special-dict|. If it
+ is it returns name of the key in |v:msgpack_types| which represents
{msgpack-value} type.
msgpack#type({msgpack-value}) *msgpack#type()*
- Returns name of the key in |v:msgpack_types| that represents
- {msgpack-value} type. Never returns zero: this function returns
- msgpack type which will be dumped by |msgpackdump()| should it receive
+ Returns name of the key in |v:msgpack_types| that represents
+ {msgpack-value} type. Never returns zero: this function returns
+ msgpack type which will be dumped by |msgpackdump()| should it receive
a list with single {msgpack-value} as input.
msgpack#deepcopy({msgpack-value}) *msgpack#deepcopy()*
- Like |deepcopy()|, but works correctly with |msgpack-special-dict|
- values. Plain |deepcopy()| will destroy all types in
- |msgpack-special-dict| values because it will copy _TYPE key values,
+ Like |deepcopy()|, but works correctly with |msgpack-special-dict|
+ values. Plain |deepcopy()| will destroy all types in
+ |msgpack-special-dict| values because it will copy _TYPE key values,
while they should be preserved.
msgpack#string({msgpack-value}) *msgpack#string()*
- Like |string()|, but saves information about msgpack types. Values
- dumped by msgpack#string may be read back by |msgpack#eval()|.
+ Like |string()|, but saves information about msgpack types. Values
+ dumped by msgpack#string may be read back by |msgpack#eval()|.
Returns is the following:
- - Dictionaries are dumped as "{key1: value1, key2: value2}". Note:
- msgpack allows any values in keys, so with some
- |msgpack-special-dict| values |msgpack#string()| may produce even
+ - Dictionaries are dumped as "{key1: value1, key2: value2}". Note:
+ msgpack allows any values in keys, so with some
+ |msgpack-special-dict| values |msgpack#string()| may produce even
"{{1: 2}: 3, [4]: 5}".
- Lists are dumped as "[value1, value2]".
- Strings are dumped as
1. `"abc"`: binary string.
2. `="abc"`: string.
- 3. `+(10)"ext"`: extension strings (10 may be replaced with any
+ 3. `+(10)"ext"`: extension strings (10 may be replaced with any
8-bit signed integer).
- Inside strings the following escape sequences may be present: "\0"
- (represents NUL byte), "\n" (represents line feed) and "\""
+ Inside strings the following escape sequences may be present: "\0"
+ (represents NUL byte), "\n" (represents line feed) and "\""
(represents double quote).
- - Floating-point and integer values are dumped using |string()| or
+ - Floating-point and integer values are dumped using |string()| or
|msgpack#int_dict_to_str()|.
- Booleans are dumped as "TRUE" or "FALSE".
- Nil values are dumped as "NIL".
msgpack#eval({string}, {dict}) *msgpack#eval()*
- Transforms string created by |msgpack#string()| into a value suitable
- for |msgpackdump()|. Second argument allows adding special values
- that start with head characters (|/\h|) and contain only word
- characters (|/\w|). Built-in special values are "TRUE", "FALSE",
- "NIL", "nan" and "inf" and they cannot be overridden. Map values are
- always evaluated to |msgpack-special-dict| values, as well as
+ Transforms string created by |msgpack#string()| into a value suitable
+ for |msgpackdump()|. Second argument allows adding special values
+ that start with head characters (|/\h|) and contain only word
+ characters (|/\w|). Built-in special values are "TRUE", "FALSE",
+ "NIL", "nan" and "inf" and they cannot be overridden. Map values are
+ always evaluated to |msgpack-special-dict| values, as well as
hexadecimal digits. When evaluating maps order of keys is preserved.
- Note that in addition to regular integer representations that may be
- obtained using |msgpack#string()| msgpack#eval() also supports C-style
- “character” integer constants like `'/'` (equivalent to
+ Note that in addition to regular integer representations that may be
+ obtained using |msgpack#string()| msgpack#eval() also supports C-style
+ “character” integer constants like `'/'` (equivalent to
`char2nr('/')`: `47`). This also allows `'\0'` (number is decimal).
*msgpack#equal*
msgpack#equal({msgpack-value}, {msgpack-value}) *msgpack#equal()*
- Returns 1 if given values are equal, 0 otherwise. When comparing
- msgpack map values order of keys is ignored. Comparing
- |msgpack-special-dict| with equivalent non-special-dict value
+ Returns 1 if given values are equal, 0 otherwise. When comparing
+ msgpack map values order of keys is ignored. Comparing
+ |msgpack-special-dict| with equivalent non-special-dict value
evaluates to 1.
==============================================================================
diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt
index 5167b4baf7..85ac290361 100644
--- a/runtime/doc/pi_netrw.txt
+++ b/runtime/doc/pi_netrw.txt
@@ -91,7 +91,6 @@ Copyright: Copyright (C) 2017 Charles E Campbell *netrw-copyright*
Marked Files: Grep..................................|netrw-mg|
Marked Files: Hiding and Unhiding by Suffix.........|netrw-mh|
Marked Files: Moving................................|netrw-mm|
- Marked Files: Printing..............................|netrw-mp|
Marked Files: Sourcing..............................|netrw-ms|
Marked Files: Setting the Target Directory..........|netrw-mt|
Marked Files: Tagging...............................|netrw-mT|
@@ -208,7 +207,7 @@ EXTERNAL APPLICATIONS AND PROTOCOLS *netrw-externapp* {{{2
http: g:netrw_http_cmd = "links" elseif links is available
http: g:netrw_http_cmd = "curl" elseif curl is available
http: g:netrw_http_cmd = "wget" elseif wget is available
- http: g:netrw_http_cmd = "fetch" elseif fetch is available
+ http: g:netrw_http_cmd = "fetch" elseif fetch is available
http: *g:netrw_http_put_cmd* = "curl -T"
rcp: *g:netrw_rcp_cmd* = "rcp"
rsync: *g:netrw_rsync_cmd* = "rsync" (see |g:netrw_rsync_sep|)
@@ -440,12 +439,10 @@ settings are described below, in |netrw-browser-options|, and in
*g:netrw_use_errorwindow* =2: messages from netrw will use a popup window
Move the mouse and pause to remove the popup window.
- (default value if popup windows are available)
=1 : messages from netrw will use a separate one
line window. This window provides reliable
delivery of messages.
- (default value if popup windows are not available)
- =0 : messages from netrw will use echoerr ;
+ =0 : (default) messages from netrw will use echoerr ;
messages don't always seem to show up this
way, but one doesn't have to quit the window.
@@ -1085,8 +1082,8 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
<c-tab> Shrink/expand a netrw/explore window |netrw-c-tab|
- Makes Netrw go up one directory |netrw--|
a Cycles between normal display, |netrw-a|
- hiding (suppress display of files matching g:netrw_list_hide)
- and showing (display only files which match g:netrw_list_hide)
+ hiding (suppress display of files matching g:netrw_list_hide)
+ and showing (display only files which match g:netrw_list_hide)
cd Make browsing directory the current directory |netrw-cd|
C Setting the editing window |netrw-C|
d Make a directory |netrw-d|
@@ -1108,7 +1105,6 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
mg Apply vimgrep to marked files |netrw-mg|
mh Toggle marked file suffices' presence on hiding list |netrw-mh|
mm Move marked files to marked-file target directory |netrw-mm|
- mp Print marked files |netrw-mp|
mr Mark files using a shell-style |regexp| |netrw-mr|
mt Current browsing directory becomes markfile target |netrw-mt|
mT Apply ctags to marked files |netrw-mT|
@@ -1118,7 +1114,7 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
mX Apply arbitrary shell command to marked files en bloc|netrw-mX|
mz Compress/decompress marked files |netrw-mz|
o Enter the file/directory under the cursor in a new |netrw-o|
- browser window. A horizontal split is used.
+ browser window. A horizontal split is used.
O Obtain a file specified by cursor |netrw-O|
p Preview the file |netrw-p|
P Browse in the previously used window |netrw-P|
@@ -1134,7 +1130,7 @@ QUICK REFERENCE: MAPS *netrw-browse-maps* {{{2
u Change to recently-visited directory |netrw-u|
U Change to subsequently-visited directory |netrw-U|
v Enter the file/directory under the cursor in a new |netrw-v|
- browser window. A vertical split is used.
+ browser window. A vertical split is used.
x View file with an associated program |netrw-x|
X Execute filename under cursor via |system()| |netrw-X|
@@ -2154,7 +2150,6 @@ The following netrw maps make use of marked files:
|netrw-mF| Unmark marked files
|netrw-mg| Apply vimgrep to marked files
|netrw-mm| Move marked files to target
- |netrw-mp| Print marked files
|netrw-ms| Netrw will source marked files
|netrw-mt| Set target for |netrw-mm| and |netrw-mc|
|netrw-mT| Generate tags using marked files
@@ -2271,7 +2266,7 @@ Example:
...
-MARKED FILES, ARBITRARY SHELL COMMAND, EN BLOC *netrw-mX* {{{2
+MARKED FILES, ARBITRARY SHELL COMMAND, EN BLOC *netrw-mX* {{{2
(See |netrw-mf| and |netrw-mr| for how to mark files)
(uses the global marked-file list)
@@ -2611,7 +2606,7 @@ your browsing preferences. (see also: |netrw-settings|)
Used to change access permission for a file.
*g:netrw_clipboard* =1
- By default, netrw will attempt to insure that
+ By default, netrw will attempt to insure that
the clipboard's values will remain unchanged.
However, some users report that they have
speed problems with this; consequently, this
@@ -2759,7 +2754,7 @@ your browsing preferences. (see also: |netrw-settings|)
escaped before applying glob()
*g:netrw_gx* ="<cfile>"
- This option controls how gx (|netrw-gx|) picks
+ This option controls how gx (|netrw-gx|) picks
up the text under the cursor. See |expand()|
for possibilities.
@@ -2824,11 +2819,11 @@ your browsing preferences. (see also: |netrw-settings|)
directory (|netrw-mt|, |netrw-mc|)
*g:netrw_localcopycmdopt* ='' Linux/Unix/MacOS/Cygwin
- =' \c copy' Windows
+ =' \c copy' Windows
Options for the |g:netrw_localcopycmd|
*g:netrw_localcopydircmd* ="cp" Linux/Unix/MacOS/Cygwin
- =expand("$COMSPEC") Windows
+ =expand("$COMSPEC") Windows
Copies directories to target directory.
(|netrw-mc|, |netrw-mt|)
@@ -2854,17 +2849,13 @@ your browsing preferences. (see also: |netrw-settings|)
Options for |g:netrw_localmovecmd|
*g:netrw_localrmdir* ="rmdir" Linux/Unix/MacOS/Cygwin
- =expand("$COMSPEC") Windows
+ =expand("$COMSPEC") Windows
Remove directory command (rmdir)
This variable is only used if your vim is
earlier than 7.4 or if your vim doesn't
have patch#1107. Otherwise, |delete()|
is used with the "d" option.
- *g:netrw_localrmdiropt* ="" Linux/Unix/MacOS/Cygwin
- =" /c rmdir" Windows
- Options for |g:netrw_localrmdir|
-
*g:netrw_maxfilenamelen* =32 by default, selected so as to make long
listings fit on 80 column displays.
If your screen is wider, and you have file
@@ -2890,10 +2881,10 @@ your browsing preferences. (see also: |netrw-settings|)
(see |'ballooneval'|)
*g:netrw_sizestyle* not defined: actual bytes (default)
- ="b" : actual bytes (default)
- ="h" : human-readable (ex. 5k, 4m, 3g)
+ ="b" : actual bytes (default)
+ ="h" : human-readable (ex. 5k, 4m, 3g)
uses 1000 base
- ="H" : human-readable (ex. 5K, 4M, 3G)
+ ="H" : human-readable (ex. 5K, 4M, 3G)
uses 1024 base
The long listing (|netrw-i|) and query-file
maps (|netrw-qf|) will display file size
@@ -2941,7 +2932,7 @@ your browsing preferences. (see also: |netrw-settings|)
default: "NETRWSERVER"
*g:netrw_sort_by* sort by "name", "time", "size", or
- "exten".
+ "exten".
default: "name"
*g:netrw_sort_direction* sorting direction: "normal" or "reverse"
@@ -2998,7 +2989,7 @@ your browsing preferences. (see also: |netrw-settings|)
.vim/after/syntax/netrw.vim.
< The netrwGray highlighting is set up by
netrw when >
- * netrwGray has not been previously
+ * netrwGray has not been previously
defined
* the gui is running
< As an example, I myself use a dark-background
@@ -3256,7 +3247,7 @@ If there are marked files: (see |netrw-mf|)
name, applying that substitute, and renaming each file to the result.
As an example : >
- mr [query: reply with *.c]
+ mr [query: reply with *.c]
R [query: reply with s/^\(.*\)\.c$/\1.cpp/]
<
This example will mark all "*.c" files and then rename them to "*.cpp"
@@ -3265,7 +3256,7 @@ If there are marked files: (see |netrw-mf|)
The ctrl-X character has special meaning for renaming files: >
- <c-x> : a single ctrl-x tells netrw to ignore the portion of the response
+ <c-x> : a single ctrl-x tells netrw to ignore the portion of the response
lying between the last '/' and the ctrl-x.
<c-x><c-x> : a pair of contiguous ctrl-x's tells netrw to ignore any
@@ -3833,7 +3824,7 @@ netrw:
Decho.vim is provided as a "vimball". You
should edit the Decho.vba.gz file and source it in: >
- vim Decho.vba.gz
+ vim Decho.vba.gz
:so %
:q
<
@@ -3875,7 +3866,7 @@ netrw:
To save the file: under linux, the output will be in a separate
remote server window; in it, just save the file with >
- :w! DBG
+ :w! DBG
< Under a vim that doesn't support clientserver, your debugging
output will appear in another tab: >
@@ -3901,6 +3892,8 @@ netrw:
==============================================================================
12. History *netrw-history* {{{1
+ v172: Apr 22, 2023 * removed g:netrw_localrmdiropt
+ removed g:netrw_localrmdir
v171: Oct 09, 2020 * included code in s:NetrwOptionsSafe()
to allow |'bh'| to be set to delete when
rather than hide when g:netrw_fastbrowse
@@ -3985,8 +3978,10 @@ netrw:
Nov 09, 2016 * Broke apart the command from the options,
mostly for Windows. Introduced new netrw
settings: |g:netrw_localcopycmdopt|
- |g:netrw_localcopydircmdopt| |g:netrw_localmkdiropt|
- |g:netrw_localmovecmdopt| |g:netrw_localrmdiropt|
+ |g:netrw_localcopydircmdopt|
+ |g:netrw_localmkdiropt|
+ |g:netrw_localmovecmdopt|
+ g:netrw_localrmdiropt
Nov 21, 2016 * (mattn) provided a patch for preview; swapped
winwidth() with winheight()
Nov 22, 2016 * (glacambre) reported that files containing
@@ -4129,7 +4124,7 @@ netrw:
The "<nowait>" modifier has been included
with most of netrw's mappings to avoid that
delay.
- Jun 26, 2015 * |netrw-gn| mapping implemted
+ Jun 26, 2015 * |netrw-gn| mapping implemented
* :Ntree NotADir resulted in having
the tree listing expand in the error messages
window. Fixed.
@@ -4336,8 +4331,8 @@ netrw:
Dec 24, 2013 * (esquifit) asked that netrw allow the
/cygdrive prefix be a user-alterable
parameter.
- Jan 02, 2014 * Fixed a problem with netrw-based ballon
- evaluation (ie. netrw#NetrwBaloonHelp()
+ Jan 02, 2014 * Fixed a problem with netrw-based balloon
+ evaluation (ie. netrw#NetrwBalloonHelp()
not having been loaded error messages)
Jan 03, 2014 * Fixed a problem with tree listings
* New command installed: |:Ntree|
diff --git a/runtime/doc/pi_tar.txt b/runtime/doc/pi_tar.txt
index 2230b82dec..dd1edb5707 100644
--- a/runtime/doc/pi_tar.txt
+++ b/runtime/doc/pi_tar.txt
@@ -11,7 +11,7 @@ Copyright 2005-2017: *tar-copyright*
package, including tarPlugin.vim, tar.vim, and pi_tar.txt. Like
anything else that's except use "tar.vim" instead of "VIM". Like
anything else that's free, tar.vim and its associated files are
- provided *as is* and comes with no warranty of any kind, either
+ provided as is and comes with no warranty of any kind, either
expressed or implied. No guarantees of merchantability. No
guarantees of suitability for any purpose. By using this plugin, you
agree that in no event will the copyright holder be liable for any
@@ -27,29 +27,12 @@ Copyright 2005-2017: *tar-copyright*
==============================================================================
2. Usage *tar-usage* *tar-manual*
- When one edits a *.tar file, this plugin will handle displaying a
+ When one edits a `*.tar` file, this plugin will handle displaying a
contents page. Select a file to edit by moving the cursor atop
the desired file, then hit the <return> key. After editing, one may
also write to the file. Currently, one may not make a new file in
tar archives via the plugin.
- *:Vimuntar*
- VIMUNTAR~
-
- :Vimuntar [vimhome]
-
- This command copies, if necessary, the tarball to the .vim or vimfiles
- directory using the first writable directory in the |'runtimepath'|
- when no [vimhome] is specified. Otherwise, the [vimhome] argument
- allows the user to specify that directory, instead.
-
- The copy is done using the command in *g:tar_copycmd* , which is >
- cp for cygwin, unix, macunix
- copy for windows (32, 95, 64, 16)
-< The extraction is done with the command specified with
- *g:tar_extractcmd* , which by default is >
- "tar -xf"
-<
*:TarDiff*
DIFFERENCING SUPPORT~
@@ -61,8 +44,8 @@ Copyright 2005-2017: *tar-copyright*
the file mentioned in the tarball. If the current directory is not
correct for that path, :TarDiff will fail to find the associated file.
- If the [filename] is given, that that filename (and path) will be used
- to specify the associated file.
+ If the [filename] is given, that filename (and path) will be used to
+ specify the associated file.
PREVENTING LOADING~
@@ -138,8 +121,8 @@ Copyright 2005-2017: *tar-copyright*
May 28, 2008 * various security improvements. Now requires
patch 299 which provides the fnameescape()
function
- May 30, 2008 * allows one to view *.gz and *.bz2 files that
- are in *.tar files.
+ May 30, 2008 * allows one to view `*.gz` and `*.bz2` files that
+ are in `*.tar` files.
v12 Sep 07, 2007 * &shq now used if not the empty string for
g:tar_shq
v10 May 02, 2006 * now using "redraw then echo" to show messages,
diff --git a/runtime/doc/pi_zip.txt b/runtime/doc/pi_zip.txt
index 9b531d78b4..a8e1b8df10 100644
--- a/runtime/doc/pi_zip.txt
+++ b/runtime/doc/pi_zip.txt
@@ -10,7 +10,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
The VIM LICENSE (see |copyright|) applies to the files in this
package, including zipPlugin.vim, zip.vim, and pi_zip.vim. except use
"zip.vim" instead of "VIM". Like anything else that's free, zip.vim
- and its associated files are provided *as is* and comes with no
+ and its associated files are provided as is and comes with no
warranty of any kind, either expressed or implied. No guarantees of
merchantability. No guarantees of suitability for any purpose. By
using this plugin, you agree that in no event will the copyright
@@ -27,7 +27,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
==============================================================================
2. Usage *zip-usage* *zip-manual*
- When one edits a *.zip file, this plugin will handle displaying a
+ When one edits a `*.zip` file, this plugin will handle displaying a
contents page. Select a file to edit by moving the cursor atop
the desired file, then hit the <return> key. After editing, one may
also write to the file. Currently, one may not make a new file in
@@ -70,6 +70,13 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
extract a file from a zip archive. By default, >
let g:zip_extractcmd= g:zip_unzipcmd
<
+ *g:zip_exec*
+ For security reasons, one may prevent that Vim runs executables
+ automatically when opening a buffer. This option (default: "1")
+ can be used to prevent executing the "unzip" command when set to
+ "0": >
+ let g:zip_exec=0
+<
PREVENTING LOADING~
If for some reason you do not wish to use vim to examine zipped files,
@@ -102,6 +109,7 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
==============================================================================
4. History *zip-history* {{{1
+ v33 Dec 07, 2021 * `*.xlam` mentioned twice in zipPlugin
v32 Oct 22, 2021 * to avoid an issue with a vim 8.2 patch, zipfile: has
been changed to zipfile:// . This often shows up
as zipfile:/// with zipped files that are root-based.
@@ -113,8 +121,8 @@ Copyright: Copyright (C) 2005-2015 Charles E Campbell *zip-copyright*
the command actually to be attempted in zip#Read()
and zip#Write()
* added the extraction of a file capability
- Nov 30, 2015 * added *.epub to the |g:zipPlugin_ext| list
- Sep 13, 2016 * added *.apk to the |g:zipPlugin_ext| list and
+ Nov 30, 2015 * added `*.epub` to the |g:zipPlugin_ext| list
+ Sep 13, 2016 * added `*.apk` to the |g:zipPlugin_ext| list and
sorted the suffices.
v27 Jul 02, 2013 * sanity check: zipfile must have "PK" as its first
two bytes.
diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt
index 5375d971f0..b8182347f8 100644
--- a/runtime/doc/provider.txt
+++ b/runtime/doc/provider.txt
@@ -4,7 +4,7 @@
NVIM REFERENCE MANUAL by Thiago de Arruda
-Providers *provider*
+Providers *provider*
Nvim delegates some features to dynamic "providers". This document describes
the providers and how to install them.
@@ -66,7 +66,7 @@ To disable Python 3 support: >vim
PYTHON VIRTUALENVS ~
*python-virtualenv*
If you plan to use per-project virtualenvs often, you should assign one
-virtualenv for Neovim and hard-code the interpreter path via
+virtualenv for Nvim and hard-code the interpreter path via
|g:python3_host_prog| so that the "pynvim" package is not required
for each virtualenv.
@@ -82,7 +82,7 @@ The last command reports the interpreter path, add it to your init.vim: >vim
See also: https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim
==============================================================================
-Ruby integration *provider-ruby*
+Ruby integration *provider-ruby*
Nvim supports Ruby |remote-plugin|s and the Vim legacy |ruby-vim| interface
(which is itself implemented as a Nvim remote-plugin).
@@ -169,7 +169,7 @@ can be slow. To avoid this, set g:node_host_prog to the host path: >vim
let g:node_host_prog = '/usr/local/bin/neovim-node-host'
<
==============================================================================
-Clipboard integration *provider-clipboard* *clipboard*
+Clipboard integration *provider-clipboard* *clipboard*
Nvim has no direct connection to the system clipboard. Instead it depends on
a |provider| which transparently uses shell commands to communicate with the
@@ -182,15 +182,15 @@ the "+" and/or "*" registers explicitly): >vim
See 'clipboard' for details and options.
*clipboard-tool*
-The presence of a working clipboard tool implicitly enables the '+' and '*'
+The presence of a working clipboard tool implicitly enables the '+' and "*"
registers. Nvim looks for these clipboard tools, in order of priority:
- |g:clipboard|
- pbcopy, pbpaste (macOS)
- wl-copy, wl-paste (if $WAYLAND_DISPLAY is set)
- waycopy, waypaste (if $WAYLAND_DISPLAY is set)
- - xclip (if $DISPLAY is set)
- xsel (if $DISPLAY is set)
+ - xclip (if $DISPLAY is set)
- lemonade (for SSH) https://github.com/pocke/lemonade
- doitclient (for SSH) https://www.chiark.greenend.org.uk/~sgtatham/doit/
- win32yank (Windows)
@@ -253,9 +253,50 @@ For Windows WSL, try this g:clipboard definition:
\ },
\ 'cache_enabled': 0,
\ }
-
+<
+ *clipboard-osc52*
+Nvim bundles a clipboard provider that allows copying to the system clipboard
+using OSC 52. OSC 52 is an Operating System Command control sequence that
+writes the copied text to the terminal emulator. If the terminal emulator
+supports OSC 52 then it will write the copied text into the system clipboard.
+
+Nvim will attempt to automatically determine if the host terminal emulator
+supports the OSC 52 sequence and enable the OSC 52 clipboard provider if it
+does as long as all of the following are true:
+
+ • Nvim is running in the |TUI|
+ • |g:clipboard| is unset
+ • 'clipboard' is not set to "unnamed" or "unnamedplus"
+ • $SSH_TTY is set
+
+If any of the above conditions are not met then the OSC 52 clipboard provider
+will not be used by default and Nvim will fall back to discovering a
+|clipboard-tool| through the usual process.
+
+To force Nvim to use the OSC 52 provider you can use the following
+|g:clipboard| definition: >lua
+
+ vim.g.clipboard = {
+ name = 'OSC 52',
+ copy = {
+ ['+'] = require('vim.ui.clipboard.osc52').copy('+'),
+ ['*'] = require('vim.ui.clipboard.osc52').copy('*'),
+ },
+ paste = {
+ ['+'] = require('vim.ui.clipboard.osc52').paste('+'),
+ ['*'] = require('vim.ui.clipboard.osc52').paste('*'),
+ },
+ }
+<
+Note that not all terminal emulators support reading from the system clipboard
+(and even for those that do, users should be aware of the security
+implications), so using OSC 52 for pasting may not be possible (and not
+necessary, because you can |paste| instead using your system paste function).
+Users may need to configure their terminal emulator to allow reading from the
+clipboard.
+<
==============================================================================
-Paste *provider-paste* *paste*
+Paste *provider-paste* *paste*
"Paste" is a separate concept from |clipboard|: paste means "dump a bunch of
text to the editor", whereas clipboard provides features like |quote+| to get
diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index b1f7c927cc..4428ff2f65 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -353,8 +353,6 @@ processing a quickfix or location list command, it will be aborted.
If numbers [from] and/or [to] are given, the respective
range of errors is listed. A negative number counts
from the last error backwards, -1 being the last error.
- The 'switchbuf' settings are respected when jumping
- to a buffer.
The |:filter| command can be used to display only the
quickfix entries matching a supplied pattern. The
pattern is matched against the filename, module name,
@@ -869,11 +867,11 @@ lists. They set one of the existing error lists as the current one.
*:chistory* *:chi*
:[count]chi[story] Show the list of error lists. The current list is
- marked with ">". The output looks like:
- error list 1 of 3; 43 errors :make ~
- > error list 2 of 3; 0 errors :helpgrep tag ~
- error list 3 of 3; 15 errors :grep ex_help *.c ~
-
+ marked with ">". The output looks like: >
+ error list 1 of 3; 43 errors :make
+ > error list 2 of 3; 0 errors :helpgrep tag
+ error list 3 of 3; 15 errors :grep ex_help *.c
+<
When [count] is given, then the count'th quickfix
list is made the current list. Example: >
" Make the 4th quickfix list current
@@ -958,7 +956,7 @@ or simpler >
"$*" can be given multiple times, for example: >
:set makeprg=gcc\ -o\ $*\ $*
-The 'shellpipe' option defaults to ">%s 2>&1" for Win32.
+The 'shellpipe' option defaults to "2>&1| tee" for Win32.
This means that the output of the compiler is saved in a file and not shown on
the screen directly. For Unix "| tee" is used. The compiler output is shown
on the screen and saved in a file the same time. Depending on the shell used
@@ -1248,7 +1246,7 @@ not "b:current_compiler". What the command actually does is the following:
- Delete the "current_compiler" and "b:current_compiler" variables.
- Define the "CompilerSet" user command. With "!" it does ":set", without "!"
it does ":setlocal".
-- Execute ":runtime! compiler/{name}.(vim|lua)". The plugins are expected to
+- Execute ":runtime! compiler/{name}.{vim,lua}". The plugins are expected to
set options with "CompilerSet" and set the "current_compiler" variable to the
name of the compiler.
- Delete the "CompilerSet" user command.
@@ -1319,8 +1317,8 @@ TEX COMPILER *compiler-tex*
Included in the distribution compiler for TeX ($VIMRUNTIME/compiler/tex.vim)
uses make command if possible. If the compiler finds a file named "Makefile"
or "makefile" in the current directory, it supposes that you want to process
-your *TeX files with make, and the makefile does the right work. In this case
-compiler sets 'errorformat' for *TeX output and leaves 'makeprg' untouched. If
+your `*TeX` files with make, and the makefile does the right work. In this case
+compiler sets 'errorformat' for `*TeX` output and leaves 'makeprg' untouched. If
neither "Makefile" nor "makefile" is found, the compiler will not use make.
You can force the compiler to ignore makefiles by defining
b:tex_ignore_makefile or g:tex_ignore_makefile variable (they are checked for
@@ -1383,6 +1381,7 @@ rest is ignored. Items can only be 1023 bytes long.
Basic items
%f file name (finds a string)
+ %b buffer number (finds a number)
%o module name (finds a string)
%l line number (finds a number)
%e end line number (finds a number)
@@ -1422,6 +1421,11 @@ On Windows a leading "C:" will be included in "%f", even when using "%f:".
This means that a file name which is a single alphabetical letter will not be
detected.
+The "%b" conversion is used to parse a buffer number. This is useful for
+referring to lines in a scratch buffer or a buffer with no name. If a buffer
+with the matching number doesn't exist, then that line is used as a non-error
+line.
+
The "%p" conversion is normally followed by a "^". It's used for compilers
that output a line like: >
^
@@ -1617,7 +1621,7 @@ be escaped), meta symbols have to be written with leading '%':
%\ The single '\' character. Note that this has to be
escaped ("%\\") in ":set errorformat=" definitions.
%. The single '.' character.
- %# The single '*'(!) character.
+ %# The single "*"(!) character.
%^ The single '^' character. Note that this is not
useful, the pattern already matches start of line.
%$ The single '$' character. Note that this is not
@@ -1935,7 +1939,7 @@ You can customize the given setting to suit your own purposes, for example,
all the annoying "Overfull ..." warnings could be excluded from being
recognized as an error.
Alternatively to filtering the LaTeX compiler output, it is also possible
-to directly read the *.log file that is produced by the [La]TeX compiler.
+to directly read the `*.log` file that is produced by the [La]TeX compiler.
This contains even more useful information about possible error causes.
However, to properly parse such a complex file, an external filter should
be used. See the description further above how to make such a filter known
diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt
index d17df3cd61..f976eb7464 100644
--- a/runtime/doc/quickref.txt
+++ b/runtime/doc/quickref.txt
@@ -489,8 +489,8 @@ In Insert or Command-line mode:
|v_ip| N ip Select "inner paragraph"
|v_ab| N ab Select "a block" (from "[(" to "])")
|v_ib| N ib Select "inner block" (from "[(" to "])")
-|v_aB| N aB Select "a Block" (from "[{" to "]}")
-|v_iB| N iB Select "inner Block" (from "[{" to "]}")
+|v_aB| N aB Select "a Block" (from `[{` to `]}`)
+|v_iB| N iB Select "inner Block" (from `[{` to `]}`)
|v_a>| N a> Select "a <> block"
|v_i>| N i> Select "inner <> block"
|v_at| N at Select "a tag block" (from <aaa> to </aaa>)
@@ -812,14 +812,11 @@ Short explanation of each option: *option-list*
'operatorfunc' 'opfunc' function to be called for |g@| operator
'packpath' 'pp' list of directories used for packages
'paragraphs' 'para' nroff macros that separate paragraphs
-'paste' allow pasting text
-'pastetoggle' 'pt' key code that causes 'paste' to toggle
'patchexpr' 'pex' expression used to patch a file
'patchmode' 'pm' keep the oldest version of a file
'path' 'pa' list of directories searched with "gf" et.al.
'preserveindent' 'pi' preserve the indent structure when reindenting
'previewheight' 'pvh' height of the preview window
-'previewpopup' 'pvp' use popup window for preview
'previewwindow' 'pvw' identifies the preview window
'pumheight' 'ph' maximum number of items to show in the popup menu
'pumwidth' 'pw' minimum width of the popup menu
@@ -872,6 +869,7 @@ Short explanation of each option: *option-list*
'smartcase' 'scs' no ignore case when pattern has uppercase
'smartindent' 'si' smart autoindenting for C programs
'smarttab' 'sta' use 'shiftwidth' when inserting <Tab>
+'smoothscroll' 'sms' scroll by screen lines when 'wrap' is set
'softtabstop' 'sts' number of spaces that <Tab> uses while editing
'spell' enable spell checking
'spellcapcheck' 'spc' pattern to locate end of a sentence
diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt
index d9aaa757ad..e6b5b06744 100644
--- a/runtime/doc/recover.txt
+++ b/runtime/doc/recover.txt
@@ -83,6 +83,15 @@ Detecting an existing swap file ~
You can find this in the user manual, section |11.3|.
+ *W325*
+The default |SwapExists| handler (|default-autocmds|) skips the |E325| prompt
+(selects "(E)dit") if the swapfile owner process (1) is still running and (2)
+was started by the current user. This presumes that you normally don't want
+to be bothered with the |ATTENTION| message just because you happen to edit
+the same file from multiple Nvim instances. In the worst case (a system
+crash) there will be more than one swapfile for the file; use |:recover| to
+inspect all of its swapfiles.
+
Updating the swapfile ~
@@ -154,7 +163,7 @@ the recover command:
:rec[over]! [file] Like ":recover", but any changes in the current
buffer are lost.
- *E312* *E309* *E310*
+ *E312* *E309* *E310* *E1364*
Vim has some intelligence about what to do if the swap file is corrupt in
some way. If Vim has doubt about what it found, it will give an error
message and insert lines with "???" in the text. If you see an error message
diff --git a/runtime/doc/remote.txt b/runtime/doc/remote.txt
index 4610088ab0..804645f774 100644
--- a/runtime/doc/remote.txt
+++ b/runtime/doc/remote.txt
@@ -46,7 +46,7 @@ The following command line arguments are available:
new tabpage.
*--remote-send*
--remote-send {keys} Send {keys} to server and exit. The {keys}
- are not mapped. Special key names are
+ are not mapped. Special key names are
recognized, e.g., "<CR>" results in a CR
character.
*--remote-expr*
diff --git a/runtime/doc/remote_plugin.txt b/runtime/doc/remote_plugin.txt
index 4cdcbed250..cfe4b08000 100644
--- a/runtime/doc/remote_plugin.txt
+++ b/runtime/doc/remote_plugin.txt
@@ -4,7 +4,7 @@
NVIM REFERENCE MANUAL by Thiago de Arruda
-Nvim support for remote plugins *remote-plugin*
+Nvim support for remote plugins *remote-plugin*
Type |gO| to see the table of contents.
@@ -40,8 +40,8 @@ fast as possible, even if many plugins/hosts are installed.
The best way to learn about remote plugins is with an example, so let's see
what a Python plugin looks like. This plugin exports a command, a function, and
an autocmd. The plugin is called 'Limit', and all it does is limit the number
-of requests made to it. Here's the plugin source code:
->
+of requests made to it. Here's the plugin source code: >python
+
import pynvim
@pynvim.plugin
@@ -119,7 +119,7 @@ would also be spawned, which could take seconds!
With the manifest, each host will only be loaded when required. Continuing with
the example, say the Java plugin is a semantic completion engine for Java code.
-If it defines the autocommand "BufEnter *.java", then the Java host is spawned
+If it defines the autocommand `BufEnter *.java`, then the Java host is spawned
only when Nvim loads a buffer matching "*.java".
If the explicit call to |:UpdateRemotePlugins| seems inconvenient, try to see
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index bf77aacdc0..53f6904170 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -56,7 +56,7 @@ Using the underscore after `:d` avoids clobbering registers or the clipboard.
This also makes it faster.
Instead of the '/' which surrounds the {pattern}, you can use any other
-single byte character, but not an alphabetic character, '\', '"' or '|'.
+single byte character, but not an alphabetic character, '\', '"', '|' or '!'.
This is useful if you want to include a '/' in the search pattern or
replacement string.
@@ -183,7 +183,10 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
*:so* *:source* *load-vim-script*
:[range]so[urce] [file] Runs |Ex| commands or Lua code (".lua" files) from
- [file], or current buffer if no [file].
+ [file].
+ If no [file], the current buffer is used, and it is
+ treated as Lua code if its 'filetype' is "lua" or its
+ file name ends with ".lua".
Triggers the |SourcePre| autocommand.
*:source!*
:[range]so[urce]! {file}
@@ -214,7 +217,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
When [where] is omitted only 'runtimepath' is used.
Other values:
START search only under "start" in 'packpath'
- OPT search only under "opt" in 'packpath'
+ OPT search only under "opt" in 'packpath'
PACK search under "start" and "opt" in
'packpath'
ALL first use 'runtimepath', then search
@@ -222,12 +225,16 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
When {file} contains wildcards it is expanded to all
matching files. Example: >
- :runtime! plugin/**/*.vim
-< This is what Vim uses to load the plugin files when
+ :runtime! plugin/**/*.{vim,lua}
+< This is what Nvim uses to load the plugin files when
starting up. This similar command: >
- :runtime plugin/**/*.vim
+ :runtime plugin/**/*.{vim,lua}
< would source the first file only.
+ For each {file} pattern, if two `.vim` and `.lua` file
+ names match and differ only in extension, the `.vim`
+ file is sourced first.
+
When 'verbose' is one or higher, there is a message
when no file could be found.
When 'verbose' is two or higher, there is a message
@@ -257,8 +264,8 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
'runtimepath'.
If the filetype detection was already enabled (this
- is usually done with a "syntax enable" or "filetype
- on" command in your |init.vim|, or automatically during
+ is usually done with a `syntax enable` or `filetype on`
+ command in your |vimrc|, or automatically during
|initialization|), and the package was found in
"pack/*/opt/{name}", this command will also look
for "{name}/ftdetect/*.vim" files.
@@ -336,6 +343,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|.
:scr[iptnames] List all sourced script names, in the order they were
first sourced. The number is used for the script ID
|<SID>|.
+ Also see `getscriptinfo()`.
:scr[iptnames][!] {scriptId} *:script*
Edit script {scriptId}. Although ":scriptnames name"
@@ -641,15 +649,15 @@ up-to-date easily, but it requires a program like "git" to be available.
You can do both, github can automatically create an archive for a release.
Your directory layout would be like this:
- start/foobar/plugin/foo.vim " always loaded, defines commands
- start/foobar/plugin/bar.vim " always loaded, defines commands
- start/foobar/autoload/foo.vim " loaded when foo command used
- start/foobar/doc/foo.txt " help for foo.vim
- start/foobar/doc/tags " help tags
- opt/fooextra/plugin/extra.vim " optional plugin, defines commands
- opt/fooextra/autoload/extra.vim " loaded when extra command used
- opt/fooextra/doc/extra.txt " help for extra.vim
- opt/fooextra/doc/tags " help tags
+ start/foobar/plugin/foo.vim " always loaded, defines commands
+ start/foobar/plugin/bar.vim " always loaded, defines commands
+ start/foobar/autoload/foo.vim " loaded when foo command used
+ start/foobar/doc/foo.txt " help for foo.vim
+ start/foobar/doc/tags " help tags
+ opt/fooextra/plugin/extra.vim " optional plugin, defines commands
+ opt/fooextra/autoload/extra.vim " loaded when extra command used
+ opt/fooextra/doc/extra.txt " help for extra.vim
+ opt/fooextra/doc/tags " help tags
This allows for the user to do: >
mkdir ~/.local/share/nvim/site/pack
diff --git a/runtime/doc/russian.txt b/runtime/doc/russian.txt
index 8d3ed360c8..89be1a3c5a 100644
--- a/runtime/doc/russian.txt
+++ b/runtime/doc/russian.txt
@@ -34,7 +34,6 @@ enter Normal mode command, you can also set 'langmap' option:
:set langmap=ФИСВУАПРШОЛДЬТЩЗЙКЫЕГМЦЧНЯ;ABCDEFGHIJKLMNOPQRSTUVWXYZ,
фисвуапршолдьтщзйкыегмцчня;abcdefghijklmnopqrstuvwxyz
-This is in utf-8, you cannot read this if your 'encoding' is not utf-8.
You have to type this command in one line, it is wrapped for the sake of
readability.
diff --git a/runtime/doc/scroll.txt b/runtime/doc/scroll.txt
index 170c87a1a4..29d6177213 100644
--- a/runtime/doc/scroll.txt
+++ b/runtime/doc/scroll.txt
@@ -40,9 +40,6 @@ CTRL-D Scroll window Downwards in the buffer. The number of
difference). When the cursor is on the last line of
the buffer nothing happens and a beep is produced.
See also 'startofline' option.
- {difference from vi: Vim scrolls 'scroll' screen
- lines, instead of file lines; makes a difference when
- lines wrap}
<S-Down> or *<S-Down>* *<kPageDown>*
<PageDown> or *<PageDown>* *CTRL-F*
@@ -189,16 +186,16 @@ windows can be given this behavior by setting the (window-specific)
other 'scrollbind' windows are scrolled the same amount, if possible. The
behavior of 'scrollbind' can be modified by the 'scrollopt' option.
-When using the scrollbars, the binding only happens when scrolling the window
-with focus (where the cursor is). You can use this to avoid scroll-binding
-for a moment without resetting options.
+When using the scrollbars or the mouse wheel, the binding only happens when
+scrolling the window with focus (where the cursor is). You can use this to
+avoid scroll-binding for a moment without resetting options.
When a window also has the 'diff' option set, the scroll-binding uses the
differences between the two buffers to synchronize the position precisely.
Otherwise the following method is used.
*scrollbind-relative*
-Each 'scrollbind' window keeps track of its "relative offset," which can be
+Each 'scrollbind' window keeps track of its "relative offset", which can be
thought of as the difference between the current window's vertical scroll
position and the other window's vertical scroll position. When one of the
'scrollbind' windows is asked to vertically scroll past the beginning or end
@@ -222,9 +219,10 @@ option.
*scrollbind-quickadj*
The 'scrollbind' flag is meaningful when using keyboard commands to vertically
-scroll a window, and also meaningful when using the vertical scrollbar of the
-window which has the cursor focus. However, when using the vertical scrollbar
-of a window which doesn't have the cursor focus, 'scrollbind' is ignored.
+scroll a window, and is also meaningful when using the vertical scrollbar or
+the mouse wheel in the window which has the cursor focus. However, when using
+the vertical scrollbar or the mouse wheel in a window which doesn't have the
+cursor focus, 'scrollbind' is ignored.
This allows quick adjustment of the relative offset of 'scrollbind' windows.
==============================================================================
diff --git a/runtime/doc/sign.txt b/runtime/doc/sign.txt
index d09d0f226f..0360ce67f6 100644
--- a/runtime/doc/sign.txt
+++ b/runtime/doc/sign.txt
@@ -53,11 +53,10 @@ If 'cursorline' is enabled, then the CursorLineSign highlight group is used
Each placed sign is identified by a number called the sign identifier. This
identifier is used to jump to the sign or to remove the sign. The identifier
is assigned when placing the sign using the |:sign-place| command or the
-|sign_place()| function. Each sign identifier should be a unique number. If
-multiple placed signs use the same identifier, then jumping to or removing a
-sign becomes unpredictable. To avoid overlapping identifiers, sign groups can
-be used. The |sign_place()| function can be called with a zero sign identifier
-to allocate the next available identifier.
+|sign_place()| function. Each sign identifier should be a unique number (per
+buffer). Placing the same identifier twice will move the previously placed
+sign. The |sign_place()| function can be called with a zero sign identifier to
+allocate the next available identifier.
*sign-group*
Each placed sign can be assigned to either the global group or a named group.
@@ -77,9 +76,8 @@ When two signs with the same priority are present, and one has an icon or text
in the signcolumn while the other has line highlighting, then both are
displayed.
-When the line on which the sign is placed is deleted, the sign is moved to the
-next line (or the last line of the buffer, if there is no next line). When
-the delete is undone the sign does not move back.
+When the line on which the sign is placed is deleted, the sign is removed along
+with it.
==============================================================================
2. Commands *sign-commands* *:sig* *:sign*
@@ -177,11 +175,8 @@ See |sign_place()| for the equivalent Vim script function.
space is ignored.
The sign is remembered under {id}, this can be used for
- further manipulation. {id} must be a number.
- It's up to the user to make sure the {id} is used only once in
- each file (if it's used several times unplacing will also have
- to be done several times and making changes may not work as
- expected).
+ further manipulation. {id} must be a number. Placing the
+ same {id} multiple times will move the sign.
The following optional sign attributes can be specified before
"file=":
@@ -369,389 +364,15 @@ See |sign_jump()| for the equivalent Vim script function.
==============================================================================
3. Functions *sign-functions-details*
-sign_define({name} [, {dict}]) *sign_define()*
-sign_define({list})
- Define a new sign named {name} or modify the attributes of an
- existing sign. This is similar to the |:sign-define| command.
-
- Prefix {name} with a unique text to avoid name collisions.
- There is no {group} like with placing signs.
-
- The {name} can be a String or a Number. The optional {dict}
- argument specifies the sign attributes. The following values
- are supported:
- icon full path to the bitmap file for the sign.
- linehl highlight group used for the whole line the
- sign is placed in.
- numhl highlight group used for the line number where
- the sign is placed.
- text text that is displayed when there is no icon
- or the GUI is not being used.
- texthl highlight group used for the text item
- culhl highlight group used for the text item when
- the cursor is on the same line as the sign and
- 'cursorline' is enabled.
-
- If the sign named {name} already exists, then the attributes
- of the sign are updated.
-
- The one argument {list} can be used to define a list of signs.
- Each list item is a dictionary with the above items in {dict}
- and a "name" item for the sign name.
-
- Returns 0 on success and -1 on failure. When the one argument
- {list} is used, then returns a List of values one for each
- defined sign.
-
- Examples: >
- call sign_define("mySign", {
- \ "text" : "=>",
- \ "texthl" : "Error",
- \ "linehl" : "Search"})
- call sign_define([
- \ {'name' : 'sign1',
- \ 'text' : '=>'},
- \ {'name' : 'sign2',
- \ 'text' : '!!'}
- \ ])
-<
- Can also be used as a |method|: >
- GetSignList()->sign_define()
-
-sign_getdefined([{name}]) *sign_getdefined()*
- Get a list of defined signs and their attributes.
- This is similar to the |:sign-list| command.
-
- If the {name} is not supplied, then a list of all the defined
- signs is returned. Otherwise the attribute of the specified
- sign is returned.
-
- Each list item in the returned value is a dictionary with the
- following entries:
- icon full path to the bitmap file of the sign
- linehl highlight group used for the whole line the
- sign is placed in; not present if not set.
- name name of the sign
- numhl highlight group used for the line number where
- the sign is placed; not present if not set.
- text text that is displayed when there is no icon
- or the GUI is not being used.
- texthl highlight group used for the text item; not
- present if not set.
- culhl highlight group used for the text item when
- the cursor is on the same line as the sign and
- 'cursorline' is enabled; not present if not
- set.
-
- Returns an empty List if there are no signs and when {name} is
- not found.
-
- Examples: >
- " Get a list of all the defined signs
- echo sign_getdefined()
-
- " Get the attribute of the sign named mySign
- echo sign_getdefined("mySign")
-<
- Can also be used as a |method|: >
- GetSignList()->sign_getdefined()
-
-sign_getplaced([{buf} [, {dict}]]) *sign_getplaced()*
- Return a list of signs placed in a buffer or all the buffers.
- This is similar to the |:sign-place-list| command.
-
- If the optional buffer name {buf} is specified, then only the
- list of signs placed in that buffer is returned. For the use
- of {buf}, see |bufname()|. The optional {dict} can contain
- the following entries:
- group select only signs in this group
- id select sign with this identifier
- lnum select signs placed in this line. For the use
- of {lnum}, see |line()|.
- If {group} is '*', then signs in all the groups including the
- global group are returned. If {group} is not supplied or is an
- empty string, then only signs in the global group are
- returned. If no arguments are supplied, then signs in the
- global group placed in all the buffers are returned.
- See |sign-group|.
-
- Each list item in the returned value is a dictionary with the
- following entries:
- bufnr number of the buffer with the sign
- signs list of signs placed in {bufnr}. Each list
- item is a dictionary with the below listed
- entries
-
- The dictionary for each sign contains the following entries:
- group sign group. Set to '' for the global group.
- id identifier of the sign
- lnum line number where the sign is placed
- name name of the defined sign
- priority sign priority
-
- The returned signs in a buffer are ordered by their line
- number and priority.
-
- Returns an empty list on failure or if there are no placed
- signs.
-
- Examples: >
- " Get a List of signs placed in eval.c in the
- " global group
- echo sign_getplaced("eval.c")
-
- " Get a List of signs in group 'g1' placed in eval.c
- echo sign_getplaced("eval.c", {'group' : 'g1'})
-
- " Get a List of signs placed at line 10 in eval.c
- echo sign_getplaced("eval.c", {'lnum' : 10})
-
- " Get sign with identifier 10 placed in a.py
- echo sign_getplaced("a.py", {'id' : 10})
-
- " Get sign with id 20 in group 'g1' placed in a.py
- echo sign_getplaced("a.py", {'group' : 'g1',
- \ 'id' : 20})
-
- " Get a List of all the placed signs
- echo sign_getplaced()
-<
- Can also be used as a |method|: >
- GetBufname()->sign_getplaced()
-<
- *sign_jump()*
-sign_jump({id}, {group}, {buf})
- Open the buffer {buf} or jump to the window that contains
- {buf} and position the cursor at sign {id} in group {group}.
- This is similar to the |:sign-jump| command.
-
- For the use of {buf}, see |bufname()|.
-
- Returns the line number of the sign. Returns -1 if the
- arguments are invalid.
-
- Example: >
- " Jump to sign 10 in the current buffer
- call sign_jump(10, '', '')
-<
- Can also be used as a |method|: >
- GetSignid()->sign_jump()
-<
- *sign_place()*
-sign_place({id}, {group}, {name}, {buf} [, {dict}])
- Place the sign defined as {name} at line {lnum} in file or
- buffer {buf} and assign {id} and {group} to sign. This is
- similar to the |:sign-place| command.
-
- If the sign identifier {id} is zero, then a new identifier is
- allocated. Otherwise the specified number is used. {group} is
- the sign group name. To use the global sign group, use an
- empty string. {group} functions as a namespace for {id}, thus
- two groups can use the same IDs. Refer to |sign-identifier|
- and |sign-group| for more information.
-
- {name} refers to a defined sign.
- {buf} refers to a buffer name or number. For the accepted
- values, see |bufname()|.
-
- The optional {dict} argument supports the following entries:
- lnum line number in the file or buffer
- {buf} where the sign is to be placed.
- For the accepted values, see |line()|.
- priority priority of the sign. See
- |sign-priority| for more information.
-
- If the optional {dict} is not specified, then it modifies the
- placed sign {id} in group {group} to use the defined sign
- {name}.
-
- Returns the sign identifier on success and -1 on failure.
-
- Examples: >
- " Place a sign named sign1 with id 5 at line 20 in
- " buffer json.c
- call sign_place(5, '', 'sign1', 'json.c',
- \ {'lnum' : 20})
-
- " Updates sign 5 in buffer json.c to use sign2
- call sign_place(5, '', 'sign2', 'json.c')
-
- " Place a sign named sign3 at line 30 in
- " buffer json.c with a new identifier
- let id = sign_place(0, '', 'sign3', 'json.c',
- \ {'lnum' : 30})
-
- " Place a sign named sign4 with id 10 in group 'g3'
- " at line 40 in buffer json.c with priority 90
- call sign_place(10, 'g3', 'sign4', 'json.c',
- \ {'lnum' : 40, 'priority' : 90})
-<
- Can also be used as a |method|: >
- GetSignid()->sign_place(group, name, expr)
-<
- *sign_placelist()*
-sign_placelist({list})
- Place one or more signs. This is similar to the
- |sign_place()| function. The {list} argument specifies the
- List of signs to place. Each list item is a dict with the
- following sign attributes:
- buffer buffer name or number. For the accepted
- values, see |bufname()|.
- group sign group. {group} functions as a namespace
- for {id}, thus two groups can use the same
- IDs. If not specified or set to an empty
- string, then the global group is used. See
- |sign-group| for more information.
- id sign identifier. If not specified or zero,
- then a new unique identifier is allocated.
- Otherwise the specified number is used. See
- |sign-identifier| for more information.
- lnum line number in the buffer where the sign is to
- be placed. For the accepted values, see
- |line()|.
- name name of the sign to place. See |sign_define()|
- for more information.
- priority priority of the sign. When multiple signs are
- placed on a line, the sign with the highest
- priority is used. If not specified, the
- default value of 10 is used. See
- |sign-priority| for more information.
-
- If {id} refers to an existing sign, then the existing sign is
- modified to use the specified {name} and/or {priority}.
-
- Returns a List of sign identifiers. If failed to place a
- sign, the corresponding list item is set to -1.
-
- Examples: >
- " Place sign s1 with id 5 at line 20 and id 10 at line
- " 30 in buffer a.c
- let [n1, n2] = sign_placelist([
- \ {'id' : 5,
- \ 'name' : 's1',
- \ 'buffer' : 'a.c',
- \ 'lnum' : 20},
- \ {'id' : 10,
- \ 'name' : 's1',
- \ 'buffer' : 'a.c',
- \ 'lnum' : 30}
- \ ])
-
- " Place sign s1 in buffer a.c at line 40 and 50
- " with auto-generated identifiers
- let [n1, n2] = sign_placelist([
- \ {'name' : 's1',
- \ 'buffer' : 'a.c',
- \ 'lnum' : 40},
- \ {'name' : 's1',
- \ 'buffer' : 'a.c',
- \ 'lnum' : 50}
- \ ])
-<
- Can also be used as a |method|: >
- GetSignlist()->sign_placelist()
-
-sign_undefine([{name}]) *sign_undefine()*
-sign_undefine({list})
- Deletes a previously defined sign {name}. This is similar to
- the |:sign-undefine| command. If {name} is not supplied, then
- deletes all the defined signs.
-
- The one argument {list} can be used to undefine a list of
- signs. Each list item is the name of a sign.
-
- Returns 0 on success and -1 on failure. For the one argument
- {list} call, returns a list of values one for each undefined
- sign.
-
- Examples: >
- " Delete a sign named mySign
- call sign_undefine("mySign")
-
- " Delete signs 'sign1' and 'sign2'
- call sign_undefine(["sign1", "sign2"])
-
- " Delete all the signs
- call sign_undefine()
-<
- Can also be used as a |method|: >
- GetSignlist()->sign_undefine()
-
-sign_unplace({group} [, {dict}]) *sign_unplace()*
- Remove a previously placed sign in one or more buffers. This
- is similar to the |:sign-unplace| command.
-
- {group} is the sign group name. To use the global sign group,
- use an empty string. If {group} is set to '*', then all the
- groups including the global group are used.
- The signs in {group} are selected based on the entries in
- {dict}. The following optional entries in {dict} are
- supported:
- buffer buffer name or number. See |bufname()|.
- id sign identifier
- If {dict} is not supplied, then all the signs in {group} are
- removed.
-
- Returns 0 on success and -1 on failure.
-
- Examples: >
- " Remove sign 10 from buffer a.vim
- call sign_unplace('', {'buffer' : "a.vim", 'id' : 10})
-
- " Remove sign 20 in group 'g1' from buffer 3
- call sign_unplace('g1', {'buffer' : 3, 'id' : 20})
-
- " Remove all the signs in group 'g2' from buffer 10
- call sign_unplace('g2', {'buffer' : 10})
-
- " Remove sign 30 in group 'g3' from all the buffers
- call sign_unplace('g3', {'id' : 30})
-
- " Remove all the signs placed in buffer 5
- call sign_unplace('*', {'buffer' : 5})
-
- " Remove the signs in group 'g4' from all the buffers
- call sign_unplace('g4')
-
- " Remove sign 40 from all the buffers
- call sign_unplace('*', {'id' : 40})
-
- " Remove all the placed signs from all the buffers
- call sign_unplace('*')
-
-< Can also be used as a |method|: >
- GetSigngroup()->sign_unplace()
-<
-sign_unplacelist({list}) *sign_unplacelist()*
- Remove previously placed signs from one or more buffers. This
- is similar to the |sign_unplace()| function.
-
- The {list} argument specifies the List of signs to remove.
- Each list item is a dict with the following sign attributes:
- buffer buffer name or number. For the accepted
- values, see |bufname()|. If not specified,
- then the specified sign is removed from all
- the buffers.
- group sign group name. If not specified or set to an
- empty string, then the global sign group is
- used. If set to '*', then all the groups
- including the global group are used.
- id sign identifier. If not specified, then all
- the signs in the specified group are removed.
-
- Returns a List where an entry is set to 0 if the corresponding
- sign was successfully removed or -1 on failure.
-
- Example: >
- " Remove sign with id 10 from buffer a.vim and sign
- " with id 20 from buffer b.vim
- call sign_unplacelist([
- \ {'id' : 10, 'buffer' : "a.vim"},
- \ {'id' : 20, 'buffer' : 'b.vim'},
- \ ])
-<
- Can also be used as a |method|: >
- GetSignlist()->sign_unplacelist()
-<
+See:
+ - |sign_define()|
+ - |sign_getdefined()|
+ - |sign_getplaced()|
+ - |sign_jump()|
+ - |sign_place()|
+ - |sign_placelist()|
+ - |sign_undefine()|
+ - |sign_unplace()|
+ - |sign_unplacelist()|
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt
index 98a6af1b8b..29e4a7b0aa 100644
--- a/runtime/doc/spell.txt
+++ b/runtime/doc/spell.txt
@@ -111,7 +111,7 @@ zuG Undo |zW| and |zG|, remove the word from the internal
list, like with |zW|.
*:spellra* *:spellrare*
-:[count]spellr[are] {word}
+:[count]spellra[re] {word}
Add {word} as a rare word to 'spellfile', similar to
|zw|. Without count the first name is used, with
a count of two the second entry, etc.
@@ -124,7 +124,7 @@ zuG Undo |zW| and |zG|, remove the word from the internal
nnoremap z/ :exe ':spellrare! ' .. expand('<cWORD>')<CR>
< |:spellundo|, |zuw|, or |zuW| can be used to undo this.
-:spellr[rare]! {word} Add {word} as a rare word to the internal word
+:spellra[re]! {word} Add {word} as a rare word to the internal word
list, similar to |zW|.
:[count]spellu[ndo] {word} *:spellu* *:spellundo*
@@ -205,7 +205,8 @@ line may be postponed. Use |CTRL-L| when needed. Also see |set-spc-auto| for
how it can be set automatically when 'spelllang' is set.
The 'spelloptions' option has a few more flags that influence the way spell
-checking works.
+checking works. For example, "camel" splits CamelCased words so that each
+part of the word is spell-checked separately.
Vim counts the number of times a good word is encountered. This is used to
sort the suggestions: words that have been seen before get a small bonus,
@@ -408,7 +409,7 @@ of the previous line "al." will be flagged as an error. And when you type
Use |CTRL-L| to redraw right away. "[s" will also stop at a word combination
with a line break.
-When encountering a line break Vim skips characters such as '*', '>' and '"',
+When encountering a line break Vim skips characters such as "*", '>' and '"',
so that comments in C, shell and Vim code can be spell checked.
@@ -440,9 +441,9 @@ find these functions useful:
SETTING 'spellcapcheck' AUTOMATICALLY *set-spc-auto*
After the 'spelllang' option has been set successfully, Vim will source the
-files "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang'
-up to the first comma, dot or underscore. This can be used to set options
-specifically for the language, especially 'spellcapcheck'.
+files "spell/LANG.vim" and "spell/LANG.lua" in 'runtimepath'. "LANG" is the
+value of 'spelllang' up to the first comma, dot or underscore. This can be
+used to set options specifically for the language, especially 'spellcapcheck'.
The distribution includes a few of these files. Use this command to see what
they do: >
diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt
index 5e0718c3bb..a6619bc381 100644
--- a/runtime/doc/starting.txt
+++ b/runtime/doc/starting.txt
@@ -224,9 +224,11 @@ argument.
All [args] are treated as {script} arguments and stored in the
Lua `_G.arg` global table, thus "-l" ends processing of Nvim
arguments. The {script} name is stored at `_G.arg[0]`.
-
+
Sets 'verbose' to 1 (like "-V1"), so Lua `print()` writes to
output.
+ If {script} prints messages and doesn't cause Nvim to exit,
+ Nvim ensures output ends with a newline.
Arguments before "-l" are processed before executing {script}.
This example quits before executing "foo.lua": >
@@ -241,8 +243,8 @@ argument.
*-ll*
-ll {script} [args]
- Execute a lua script, similarly to |-l|, but the editor is not
- initialized. This gives a lua enviroment similar to a worker
+ Execute a Lua script, similarly to |-l|, but the editor is not
+ initialized. This gives a Lua environment similar to a worker
thread. See |lua-loop-threading|.
Unlike `-l` no prior arguments are allowed.
@@ -258,7 +260,7 @@ argument.
-A Arabic mode. Sets the 'arabic' option on.
*-H*
--H Hebrew mode. Sets the 'hkmap' and 'rightleft' options on.
+-H Hebrew mode. Sets the 'rightleft' option on and 'keymap' to "hebrew"
*-V* *verbose*
-V[N] Verbose. Sets the 'verbose' option to [N] (default: 10).
@@ -368,7 +370,7 @@ argument.
-W {scriptout} Like -w, but do not append, overwrite an existing file.
*--api-info*
---api-info Print msgpack-encoded |api-metadata| and exit.
+--api-info Print msgpack-encoded |api-metadata| and exit.
*--embed*
--embed Use stdin/stdout as a msgpack-RPC channel, so applications can
@@ -388,11 +390,15 @@ argument.
< Then startup will continue without waiting for `nvim_ui_attach`.
This is equivalent to: >
nvim --headless --cmd "call stdioopen({'rpc': v:true})"
+<
+ Embedders that use the UI protocol on a socket connection must
+ pass |--listen| as well as |--embed|: >
+ nvim --embed --listen addr
< See also: |ui-startup| |channel-stdio|
*--headless*
---headless Start without UI, and do not wait for `nvim_ui_attach`. The
+--headless Start without UI, and do not wait for `nvim_ui_attach`. The
builtin TUI is not used, so stdio works as an arbitrary
communication channel. |channel-stdio|
@@ -426,9 +432,11 @@ accordingly, proceeding as follows:
2. Process the arguments
The options and file names from the command that start Vim are
- inspected. Buffers are created for all files (but not loaded yet).
+ inspected.
The |-V| argument can be used to display or log what happens next,
useful for debugging the initializations.
+ The |--cmd| arguments are executed.
+ Buffers are created for all files (but not loaded yet).
3. Start a server (unless |--listen| was given) and set |v:servername|.
@@ -458,13 +466,13 @@ accordingly, proceeding as follows:
|$XDG_CONFIG_HOME| $XDG_CONFIG_HOME/nvim/init.vim (or init.lua)
If Nvim was started with "-u {file}" then {file} is used as the config
- and all initializations until 5. are skipped. $MYVIMRC is not set.
+ and all initializations until 8. are skipped. $MYVIMRC is not set.
"nvim -u NORC" can be used to skip these initializations without
reading a file. "nvim -u NONE" also skips plugins and syntax
highlighting. |-u|
- If Nvim was started with |-es| all initializations until 5. are
- skipped.
+ If Nvim was started with |-es| or |-Es| or |-l| all initializations until 8.
+ are skipped.
*system-vimrc* *sysinit.vim*
a. The system vimrc file is read for initializations. If
nvim/sysinit.vim file exists in one of $XDG_CONFIG_DIRS, it will be
@@ -502,12 +510,12 @@ accordingly, proceeding as follows:
10. Load the plugin scripts. *load-plugins*
This does the same as the command: >
- :runtime! plugin/**/*.vim
- :runtime! plugin/**/*.lua
+ :runtime! plugin/**/*.{vim,lua}
< The result is that all directories in 'runtimepath' will be searched
for the "plugin" sub-directory and all files ending in ".vim" or
".lua" will be sourced (in alphabetical order per directory),
- also in subdirectories. First "*.vim" are sourced, then "*.lua" files.
+ also in subdirectories. First "*.vim" are sourced, then "*.lua" files,
+ per directory.
However, directories in 'runtimepath' ending in "after" are skipped
here and only loaded after packages, see below.
@@ -822,7 +830,7 @@ resulting file, when executed with a ":source" command:
9. Restores the Views for all the windows, as with |:mkview|. But
'sessionoptions' is used instead of 'viewoptions'.
10. If a file exists with the same name as the Session file, but ending in
- "x.vim" (for eXtra), executes that as well. You can use *x.vim files to
+ "x.vim" (for eXtra), executes that as well. You can use `*x.vim` files to
specify additional settings and actions associated with a given Session,
such as creating menu items in the GUI version.
@@ -906,7 +914,7 @@ found.
You might want to clean up your 'viewdir' directory now and then.
-To automatically save and restore views for *.c files: >
+To automatically save and restore views for `*.c` files: >
au BufWinLeave *.c mkview
au BufWinEnter *.c silent! loadview
@@ -916,7 +924,7 @@ Shada ("shared data") file *shada* *shada-file*
If you exit Vim and later start it again, you would normally lose a lot of
information. The ShaDa file can be used to remember that information, which
enables you to continue where you left off. Its name is the abbreviation of
-SHAred DAta because it is used for sharing data between Neovim sessions.
+SHAred DAta because it is used for sharing data between Nvim sessions.
This is introduced in section |21.3| of the user manual.
@@ -1008,16 +1016,16 @@ MERGING *shada-merging*
When writing ShaDa files with |:wshada| without bang or at regular exit
information in the existing ShaDa file is merged with information from current
-Neovim instance. For this purpose ShaDa files store timestamps associated
+Nvim instance. For this purpose ShaDa files store timestamps associated
with ShaDa entries. Specifically the following is being done:
1. History lines are merged, ordered by timestamp. Maximum amount of items in
ShaDa file is defined by 'shada' option (|shada-/|, |shada-:|, |shada-@|,
etc: one suboption for each character that represents history name
(|:history|)).
-2. Local marks and changes for files that were not opened by Neovim are copied
- to new ShaDa file. Marks for files that were opened by Neovim are merged,
- changes to files opened by Neovim are ignored. |shada-'|
+2. Local marks and changes for files that were not opened by Nvim are copied
+ to new ShaDa file. Marks for files that were opened by Nvim are merged,
+ changes to files opened by Nvim are ignored. |shada-'|
3. Jump list is merged: jumps are ordered by timestamp, identical jumps
(identical position AND timestamp) are squashed.
4. Search patterns and substitute strings are not merged: search pattern or
@@ -1025,14 +1033,14 @@ with ShaDa entries. Specifically the following is being done:
to ShaDa file.
5. For each register entity with greatest timestamp is the only saved.
|shada-<|
-6. All saved variables are saved from current Neovim instance. Additionally
+6. All saved variables are saved from current Nvim instance. Additionally
existing variable values are copied, meaning that the only way to remove
variable from a ShaDa file is either removing it by hand or disabling
writing variables completely. |shada-!|
7. For each global mark entity with greatest timestamp is the only saved.
8. Buffer list and header are the only entries which are not merged in any
fashion: the only header and buffer list present are the ones from the
- Neovim instance which was last writing the file. |shada-%|
+ Nvim instance which was last writing the file. |shada-%|
COMPATIBILITY *shada-compatibility*
@@ -1057,13 +1065,13 @@ ShaDa files are forward and backward compatible. This means that
history types. |history|
6. Unknown keys found in register, local mark, global mark, change, jump and
search pattern entries are saved internally and dumped when writing.
- Entries created during Neovim session never have such additions.
+ Entries created during Nvim session never have such additions.
7. Additional elements found in replacement string and history entries are
- saved internally and dumped. Entries created during Neovim session never
+ saved internally and dumped. Entries created during Nvim session never
have such additions.
8. Additional elements found in variable entries are simply ignored when
reading. When writing new variables they will be preserved during merging,
- but that's all. Variable values dumped from current Neovim session never
+ but that's all. Variable values dumped from current Nvim session never
have additional elements, even if variables themselves were obtained by
reading ShaDa files.
@@ -1100,7 +1108,7 @@ start with an existing one to get the format right. You need to understand
MessagePack (or, more likely, find software that is able to use it) format to
do this. This can be useful in order to create a second file, say
"~/.my.shada" which could contain certain settings that you always want when
-you first start Neovim. For example, you can preload registers with
+you first start Nvim. For example, you can preload registers with
particular data, or put certain commands in the command line history. A line
in your |config| file like >
:rshada! ~/.my.shada
@@ -1110,12 +1118,13 @@ file name, using the ":autocmd" command (see |:autocmd|). More information on
ShaDa file format is contained in |shada-format| section.
*E136* *E929* *shada-error-handling*
-Some errors make Neovim leave temporary file named `{basename}.tmp.X` (X is
+Some errors make Nvim leave temporary file named `{basename}.tmp.X` (X is
any free letter from `a` to `z`) while normally it will create this file,
write to it and then rename `{basename}.tmp.X` to `{basename}`. Such errors
include:
-- Errors which make Neovim think that read file is not a ShaDa file at all:
+- Errors which make Nvim think that the file being read is not a ShaDa
+ file at all:
non-ShaDa files are not overwritten for safety reasons to avoid accidentally
destroying an unrelated file. This could happen e.g. when typing "nvim -i
file" in place of "nvim -R file" (yes, somebody did that at least with Vim).
@@ -1123,26 +1132,26 @@ include:
- If writing to the temporary file failed: e.g. because of the insufficient
space left.
- If renaming file failed: e.g. because of insufficient permissions.
-- If target ShaDa file has different from the Neovim instance's owners (user
+- If target ShaDa file has different from the Nvim instance's owners (user
and group) and changing them failed. Unix-specific, applies only when
- Neovim was launched from root.
+ Nvim was launched from root.
Do not forget to remove the temporary file or replace the target file with
temporary one after getting one of the above errors or all attempts to create
a ShaDa file may fail with |E929|. If you got one of them when using
-|:wshada| (and not when exiting Neovim: i.e. when you have Neovim session
+|:wshada| (and not when exiting Nvim: i.e. when you have Nvim session
running) you have additional options:
- First thing which you should consider if you got any error, except failure
to write to the temporary file: remove existing file and replace it with the
- temporary file. Do it even if you have running Neovim instance.
+ temporary file. Do it even if you have running Nvim instance.
- Fix the permissions and/or file ownership, free some space and attempt to
write again. Do not remove the existing file.
- Use |:wshada| with bang. Does not help in case of permission error. If
target file was actually the ShaDa file some information may be lost in this
case. To make the matters slightly better use |:rshada| prior to writing,
but this still will loose buffer-local marks and change list entries for any
- file which is not opened in the current Neovim instance.
+ file which is not opened in the current Nvim instance.
- Remove the target file from shell and use |:wshada|. Consequences are not
different from using |:wshada| with bang, but "rm -f" works in some cases
when you don't have write permissions.
@@ -1209,7 +1218,7 @@ exactly four MessagePack objects:
Key Data ~
generator Binary, software used to generate ShaDa
file. Is equal to "nvim" when ShaDa file was
- written by Neovim.
+ written by Nvim.
version Binary, generator version.
encoding Binary, effective 'encoding' value.
max_kbyte Integer, effective |shada-s| limit value.
@@ -1302,7 +1311,7 @@ exactly four MessagePack objects:
10 (LocalMark)
11 (Change) Map containing some position description:
Entry Position ~
- GlobaMark Global mark position. |'A|
+ GlobalMark Global mark position. |'A|
LocalMark Local mark position. |'a|
Jump One position from the |jumplist|.
Change One position from the |changelist|.
@@ -1333,10 +1342,13 @@ exactly four MessagePack objects:
reasons, see |shada-compatibility|.
*E575* *E576*
-Errors in ShaDa file may have two types: E575 used for all “logical” errors
-and E576 used for all “critical” errors. Critical errors trigger behaviour
-described in |shada-error-handling| when writing and skipping the rest of the
-file when reading and include:
+Errors in ShaDa file may have two types:
+1. E575 for “logical” errors.
+2. E576 for “critical” errors.
+When writing, critical errors trigger behaviour described in
+|shada-error-handling|.
+When reading, critical errors cause the rest of the file to be skipped.
+Critical errors include:
*shada-critical-contents-errors*
- Any of first three MessagePack objects being not an unsigned integer.
- Third object requesting amount of bytes greater then bytes left in the ShaDa
@@ -1358,9 +1370,12 @@ paths.
*base-directories* *xdg*
The "base" (root) directories conform to the XDG Base Directory Specification.
https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
-The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, and $XDG_STATE_HOME
-environment variables are used if defined, else default values (listed below)
-are used.
+The $XDG_CONFIG_HOME, $XDG_DATA_HOME, $XDG_RUNTIME_DIR, $XDG_STATE_HOME,
+$XDG_CACHE_HOME, $XDG_CONFIG_DIRS and $XDG_DATA_DIRS environment variables
+are used if defined, else default values (listed below) are used.
+
+Throughout the help pages these defaults are used as placeholders, e.g.
+"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config".
CONFIG DIRECTORY (DEFAULT) ~
*$XDG_CONFIG_HOME* Nvim: stdpath("config")
@@ -1382,15 +1397,51 @@ STATE DIRECTORY (DEFAULT) ~
Unix: ~/.local/state ~/.local/state/nvim
Windows: ~/AppData/Local ~/AppData/Local/nvim-data
-Note: Throughout the user manual these defaults are used as placeholders, e.g.
-"~/.config" is understood to mean "$XDG_CONFIG_HOME or ~/.config".
-
-LOG FILE *$NVIM_LOG_FILE* *E5430*
+CACHE DIRECTORY (DEFAULT) ~
+ *$XDG_CACHE_HOME* Nvim: stdpath("cache")
+ Unix: ~/.cache ~/.cache/nvim
+ Windows: ~/AppData/Local/Temp ~/AppData/Local/Temp/nvim-data
+
+LOG FILE (DEFAULT) ~
+ `$NVIM_LOG_FILE` Nvim: stdpath("log")/log
+ Unix: ~/.local/state/nvim ~/.local/state/nvim/log
+ Windows: ~/AppData/Local/nvim-data ~/AppData/Local/nvim-data/log
+
+Note that stdpath("log") is currently an alias for stdpath("state").
+
+ADDITIONAL CONFIGS DIRECTORY (DEFAULT) ~
+ *$XDG_CONFIG_DIRS* Nvim: stdpath("config_dirs")
+ Unix: /etc/xdg/ /etc/xdg/nvim
+ Windows: Not applicable Not applicable
+
+ADDITIONAL DATA DIRECTORY (DEFAULT) ~
+ *$XDG_DATA_DIRS* Nvim: stdpath("data_dirs")
+ Unix: /usr/local/share /usr/local/share/nvim
+ /usr/share /usr/share/nvim
+ Windows: Not applicable Not applicable
+
+NVIM_APPNAME *$NVIM_APPNAME*
+The standard directories can be further configured by the `$NVIM_APPNAME`
+environment variable. This variable controls the sub-directory that Nvim will
+read from (and auto-create) in each of the base directories. For example,
+setting `$NVIM_APPNAME` to "foo" before starting will cause Nvim to look for
+configuration files in `$XDG_CONFIG_HOME/foo` instead of
+`$XDG_CONFIG_HOME/nvim`. `$NVIM_APPNAME` must be a name, such as "foo", or a
+relative path, such as "foo/bar".
+
+One use-case for $NVIM_APPNAME is to "isolate" Nvim applications.
+Alternatively, for true isolation, on Linux you can use cgroups namespaces: >
+ systemd-run --user -qt -p PrivateUsers=yes -p BindPaths=/home/user/profile_xy:/home/user/.config/nvim nvim
+
+Note: Throughout the help pages, wherever `$XDG_CONFIG_…/nvim` is mentioned it
+is understood to mean `$XDG_CONFIG_…/$NVIM_APPNAME`.
+
+LOG FILE *log* *$NVIM_LOG_FILE* *E5430*
Besides 'debug' and 'verbose', Nvim keeps a general log file for internal
debugging, plugins and RPC clients. >
:echo $NVIM_LOG_FILE
-By default, the file is located at stdpath("log")/log unless that path
-is inaccessible or if $NVIM_LOG_FILE was set before |startup|.
+By default, the file is located at stdpath("log")/log ($XDG_STATE_HOME/nvim/log)
+unless that path is inaccessible or if $NVIM_LOG_FILE was set before |startup|.
vim:noet:tw=78:ts=8:ft=help:norl:
diff --git a/runtime/doc/support.txt b/runtime/doc/support.txt
index 481959d8f1..80a035068a 100644
--- a/runtime/doc/support.txt
+++ b/runtime/doc/support.txt
@@ -14,12 +14,16 @@ Supported platforms *supported-platforms*
`System` `Tier` `Versions` `Tested versions`
Linux 1 >= 2.6.32, glibc >= 2.12 Ubuntu 22.04
macOS (Intel) 1 >= 10.15 macOS 12
-Windows 64-bit 1 >= 8 Windows Server 2019
+Windows 64-bit 1 >= 8 (see note below) Windows Server 2022
FreeBSD 1 >= 10 FreeBSD 13
macOS (M1) 2 >= 10.15
OpenBSD 2 >= 7
MinGW 2 MinGW-w64
+Note: Windows 10 "Version 1809" or later is required for |:terminal|. To check
+your Windows version, run the "winver" command and look for "Version xxxx"
+(NOT "OS Build").
+
Support types ~
* Tier 1: Officially supported and tested with CI. Any contributed patch
diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt
index bd5a4f1926..e1053b54f1 100644
--- a/runtime/doc/syntax.txt
+++ b/runtime/doc/syntax.txt
@@ -188,28 +188,27 @@ thing. These are then linked to a highlight group that specifies the color.
A syntax group name doesn't specify any color or attributes itself.
The name for a highlight or syntax group must consist of ASCII letters,
-digits, underscores, periods and `@` characters. As a regexp it is
-`[a-zA-Z0-9_.@]*`. The maximum length of a group name is about 200 bytes.
-*E1249*
+digits, underscores, dots, hyphens, or `@`. As a regexp: `[a-zA-Z0-9_.@-]*`.
+The maximum length of a group name is about 200 bytes. *E1249*
To be able to allow each user to pick their favorite set of colors, there must
be preferred names for highlight groups that are common for many languages.
These are the suggested group names (if syntax highlighting works properly
you can see the actual color, except for "Ignore"):
- *Comment any comment
+ Comment any comment
- *Constant any constant
+ Constant any constant
String a string constant: "this is a string"
Character a character constant: 'c', '\n'
Number a number constant: 234, 0xff
Boolean a boolean constant: TRUE, false
Float a floating point constant: 2.3e10
- *Identifier any variable name
+ Identifier any variable name
Function function name (also: methods for classes)
- *Statement any statement
+ Statement any statement
Conditional if, then, else, endif, switch, etc.
Repeat for, do, while, etc.
Label case, default, etc.
@@ -217,31 +216,31 @@ you can see the actual color, except for "Ignore"):
Keyword any other keyword
Exception try, catch, throw
- *PreProc generic Preprocessor
+ PreProc generic Preprocessor
Include preprocessor #include
Define preprocessor #define
Macro same as Define
PreCondit preprocessor #if, #else, #endif, etc.
- *Type int, long, char, etc.
+ Type int, long, char, etc.
StorageClass static, register, volatile, etc.
Structure struct, union, enum, etc.
Typedef A typedef
- *Special any special symbol
+ Special any special symbol
SpecialChar special character in a constant
Tag you can use CTRL-] on this
Delimiter character that needs attention
SpecialComment special things inside a comment
Debug debugging statements
- *Underlined text that stands out, HTML links
+ Underlined text that stands out, HTML links
- *Ignore left blank, hidden |hl-Ignore|
+ Ignore left blank, hidden |hl-Ignore|
- *Error any erroneous construct
+ Error any erroneous construct
- *Todo anything that needs extra attention; mostly the
+ Todo anything that needs extra attention; mostly the
keywords TODO FIXME and XXX
The names marked with * are the preferred groups; the others are minor groups.
@@ -560,7 +559,7 @@ The method used to prevent copying in the generated page depends on the value
of |g:html_use_input_for_pc|.
*g:html_use_input_for_pc*
-Default: "fallback"
+Default: "none"
If |g:html_prevent_copy| is non-empty, then:
When "all", read-only <input> elements are used in place of normal text for
@@ -893,7 +892,7 @@ nasm_no_warn potentially risky syntax not as ToDo
ASPPERL and ASPVBS *ft-aspperl-syntax* *ft-aspvbs-syntax*
-*.asp and *.asa files could be either Perl or Visual Basic script. Since it's
+`*.asp` and `*.asa` files could be either Perl or Visual Basic script. Since it's
hard to detect this you can set two global variables to tell Vim what you are
using. For Perl script use: >
:let g:filetype_asa = "aspperl"
@@ -979,7 +978,7 @@ Variable Highlight ~
*c_ansi_typedefs* ... but do standard ANSI types
*c_ansi_constants* ... but do standard ANSI constants
*c_no_utf* don't highlight \u and \U in strings
-*c_syntax_for_h* for *.h files use C syntax instead of C++ and use objc
+*c_syntax_for_h* for `*.h` files use C syntax instead of C++ and use objc
syntax instead of objcpp
*c_no_if0* don't highlight "#if 0" blocks as comments
*c_no_cformat* don't highlight %-formats in strings
@@ -987,7 +986,7 @@ Variable Highlight ~
*c_no_c11* don't highlight C11 standard items
*c_no_bsd* don't highlight BSD specific types
-When 'foldmethod' is set to "syntax" then /* */ comments and { } blocks will
+When 'foldmethod' is set to "syntax" then `/* */` comments and { } blocks will
become a fold. If you don't want comments to become a fold use: >
:let c_no_comment_fold = 1
"#if 0" blocks are also folded, unless: >
@@ -1034,7 +1033,7 @@ CH *ch.vim* *ft-ch-syntax*
C/C++ interpreter. Ch has similar syntax highlighting to C and builds upon
the C syntax file. See |c.vim| for all the settings that are available for C.
-By setting a variable you can tell Vim to use Ch syntax for *.h files, instead
+By setting a variable you can tell Vim to use Ch syntax for `*.h` files, instead
of C or C++: >
:let ch_syntax_for_h = 1
@@ -1271,18 +1270,32 @@ When not set 4 is used.
DOSBATCH *dosbatch.vim* *ft-dosbatch-syntax*
-There is one option with highlighting DOS batch files. This covers new
-extensions to the Command Interpreter introduced with Windows 2000 and
-is controlled by the variable dosbatch_cmdextversion. For Windows NT
-this should have the value 1, and for Windows 2000 it should be 2.
+Select the set of Windows Command interpreter extensions that should be
+supported with the variable dosbatch_cmdextversion. For versions of Windows
+NT (before Windows 2000) this should have the value of 1. For Windows 2000
+and later it should be 2.
Select the version you want with the following line: >
:let dosbatch_cmdextversion = 1
If this variable is not defined it defaults to a value of 2 to support
-Windows 2000.
+Windows 2000 and later.
-A second option covers whether *.btm files should be detected as type
+The original MS-DOS supports an idiom of using a double colon (::) as an
+alternative way to enter a comment line. This idiom can be used with the
+current Windows Command Interpreter, but it can lead to problems when used
+inside ( ... ) command blocks. You can find a discussion about this on
+Stack Overflow -
+
+https://stackoverflow.com/questions/12407800/which-comment-style-should-i-use-in-batch-files
+
+To allow the use of the :: idiom for comments in the Windows Command
+Interpreter or working with MS-DOS bat files, set the
+dosbatch_colons_comment variable to anything: >
+
+ :let dosbatch_colons_comment = 1
+
+There is an option that covers whether `*.btm` files should be detected as type
"dosbatch" (MS-DOS batch files) or type "btm" (4DOS batch files). The latter
is used by default. You may select the former with the following line: >
@@ -1413,13 +1426,13 @@ Euphoria version 3.1.1 (https://www.rapideuphoria.com/) is still necessary
for developing applications for the DOS platform, which Euphoria version 4
(https://www.openeuphoria.org/) does not support.
-The following file extensions are auto-detected as Euphoria file type:
+The following file extensions are auto-detected as Euphoria file type: >
*.e, *.eu, *.ew, *.ex, *.exu, *.exw
*.E, *.EU, *.EW, *.EX, *.EXU, *.EXW
To select syntax highlighting file for Euphoria, as well as for
-auto-detecting the *.e and *.E file extensions as Euphoria file type,
+auto-detecting the `*.e` and `*.E` file extensions as Euphoria file type,
add the following line to your startup file: >
:let g:filetype_euphoria = "euphoria3"
@@ -1428,7 +1441,7 @@ add the following line to your startup file: >
:let g:filetype_euphoria = "euphoria4"
-Elixir and Euphoria share the *.ex file extension. If the filetype is
+Elixir and Euphoria share the `*.ex` file extension. If the filetype is
specifically set as Euphoria with the g:filetype_euphoria variable, or the
file is determined to be Euphoria based on keywords in the file, then the
filetype will be set as Euphoria. Otherwise, the filetype will default to
@@ -1455,11 +1468,11 @@ ELIXIR *elixir.vim* *ft-elixir-syntax*
Elixir is a dynamic, functional language for building scalable and
maintainable applications.
-The following file extensions are auto-detected as Elixir file types:
+The following file extensions are auto-detected as Elixir file types: >
*.ex, *.exs, *.eex, *.leex, *.lock
-Elixir and Euphoria share the *.ex file extension. If the filetype is
+Elixir and Euphoria share the `*.ex` file extension. If the filetype is
specifically set as Euphoria with the g:filetype_euphoria variable, or the
file is determined to be Euphoria based on keywords in the file, then the
filetype will be set as Euphoria. Otherwise, the filetype will default to
@@ -1529,9 +1542,10 @@ example, FORM files, use this in your startup vimrc: >
FORTH *forth.vim* *ft-forth-syntax*
-Files matching "*.fs" could be F# or Forth. If the automatic detection
-doesn't work for you, or you don't edit F# at all, use this in your
-startup vimrc: >
+Files matching "*.f" could be Fortran or Forth and those matching "*.fs" could
+be F# or Forth. If the automatic detection doesn't work for you, or you don't
+edit F# or Fortran at all, use this in your startup vimrc: >
+ :let filetype_f = "forth"
:let filetype_fs = "forth"
@@ -1891,7 +1905,7 @@ IA64 *ia64.vim* *intel-itanium* *ft-ia64-syntax*
Highlighting for the Intel Itanium 64 assembly language. See |asm.vim| for
how to recognize this filetype.
-To have *.inc files be recognized as IA64, add this to your vimrc file: >
+To have `*.inc` files be recognized as IA64, add this to your vimrc file: >
:let g:filetype_inc = "ia64"
@@ -2104,18 +2118,18 @@ set "lite_minlines" to the value you desire. Example: >
LPC *lpc.vim* *ft-lpc-syntax*
LPC stands for a simple, memory-efficient language: Lars Pensjö C. The
-file name of LPC is usually *.c. Recognizing these files as LPC would bother
+file name of LPC is usually `*.c`. Recognizing these files as LPC would bother
users writing only C programs. If you want to use LPC syntax in Vim, you
should set a variable in your vimrc file: >
:let lpc_syntax_for_c = 1
If it doesn't work properly for some particular C or LPC files, use a
-modeline. For a LPC file:
+modeline. For a LPC file: >
// vim:set ft=lpc:
-For a C file that is recognized as LPC:
+For a C file that is recognized as LPC: >
// vim:set ft=c:
@@ -2139,7 +2153,7 @@ For LPC4 series of LPC: >
For uLPC series of LPC:
uLPC has been developed to Pike, so you should use Pike syntax
-instead, and the name of your source file should be *.pike
+instead, and the name of your source file should be `*.pike`
LUA *lua.vim* *ft-lua-syntax*
@@ -2147,7 +2161,7 @@ LUA *lua.vim* *ft-lua-syntax*
The Lua syntax file can be used for versions 4.0, 5.0, 5.1 and 5.2 (5.2 is
the default). You can select one of these versions using the global variables
lua_version and lua_subversion. For example, to activate Lua
-5.1 syntax highlighting, set the variables like this:
+5.1 syntax highlighting, set the variables like this: >
:let lua_version = 5
:let lua_subversion = 1
@@ -2216,7 +2230,7 @@ the start of a region, for example 500 lines: >
MATHEMATICA *mma.vim* *ft-mma-syntax* *ft-mathematica-syntax*
-Empty *.m files will automatically be presumed to be Matlab files unless you
+Empty `*.m` files will automatically be presumed to be Matlab files unless you
have the following in your vimrc: >
let filetype_m = "mma"
@@ -2423,7 +2437,7 @@ keywords, etc): >
The option pascal_symbol_operator controls whether symbol operators such as +,
-*, .., etc. are displayed using the Operator color or not. To colorize symbol
+`*`, .., etc. are displayed using the Operator color or not. To colorize symbol
operators, add the following line to your startup file: >
:let pascal_symbol_operator=1
@@ -2586,7 +2600,7 @@ x = 0 to sync from start.
PLAINTEX *plaintex.vim* *ft-plaintex-syntax*
TeX is a typesetting language, and plaintex is the file type for the "plain"
-variant of TeX. If you never want your *.tex files recognized as plain TeX,
+variant of TeX. If you never want your `*.tex` files recognized as plain TeX,
see |ft-tex-plugin|.
This syntax file has the option >
@@ -2742,17 +2756,25 @@ For highlighted doctests and code inside: >
:let python_no_doctest_highlight = 1
or >
:let python_no_doctest_code_highlight = 1
-(first option implies second one).
+The first option implies the second one.
For highlighted trailing whitespace and mix of spaces and tabs: >
:let python_space_error_highlight = 1
-If you want all possible Python highlighting (the same as setting the
-preceding last option and unsetting all other ones): >
+If you want all possible Python highlighting: >
:let python_highlight_all = 1
+This has the same effect as setting python_space_error_highlight and
+unsetting all the other ones.
+
+If you use Python 2 or straddling code (Python 2 and 3 compatible),
+you can enforce the use of an older syntax file with support for
+Python 2 and up to Python 3.5. >
+ :let python_use_python2_syntax = 1
+This option will exclude all modern Python 3.6 or higher features.
+
+Note: Only existence of these options matters, not their value.
+ You can replace 1 above with anything.
-Note: Only existence of these options matter, not their value. You can replace
- 1 above with anything.
QUAKE *quake.vim* *ft-quake-syntax*
@@ -3127,7 +3149,7 @@ The syntax/sh.vim file provides several levels of syntax-based folding: >
let g:sh_fold_enabled= 1 (enable function folding)
let g:sh_fold_enabled= 2 (enable heredoc folding)
let g:sh_fold_enabled= 4 (enable if/do/for folding)
->
+
then various syntax items (ie. HereDocuments and function bodies) become
syntax-foldable (see |:syn-fold|). You also may add these together
to get multiple types of folding: >
@@ -3248,7 +3270,7 @@ This covers the shell named "tcsh". It is a superset of csh. See |csh.vim|
for how the filetype is detected.
Tcsh does not allow \" in strings unless the "backslash_quote" shell variable
-is set. If you want VIM to assume that no backslash quote constructs exist
+is set. If you want VIM to assume that no backslash quote constructs exist
add this line to your vimrc: >
:let tcsh_backslash_quote = 0
@@ -3424,8 +3446,8 @@ has a starred form (ie. eqnarray*).
*tex-style* *b:tex_stylish*
Tex: Starting a New Style? ~
-One may use "\makeatletter" in *.tex files, thereby making the use of "@" in
-commands available. However, since the *.tex file doesn't have one of the
+One may use "\makeatletter" in `*.tex` files, thereby making the use of "@" in
+commands available. However, since the `*.tex` file doesn't have one of the
following suffices: sty cls clo dtx ltx, the syntax highlighting will flag
such use of @ as an error. To solve this: >
@@ -3469,7 +3491,7 @@ substitution will not be made.
Tex: Controlling iskeyword~
Normally, LaTeX keywords support 0-9, a-z, A-z, and 192-255 only. Latex
-keywords don't support the underscore - except when in *.sty files. The
+keywords don't support the underscore - except when in `*.sty` files. The
syntax highlighting script handles this with the following logic:
* If g:tex_stylish exists and is 1
@@ -3676,11 +3698,12 @@ The syntax script for zsh allows for syntax-based folding: >
Vim understands three types of syntax items:
1. Keyword
- It can only contain keyword characters, according to the 'iskeyword'
- option. It cannot contain other syntax items. It will only match with a
- complete word (there are no keyword characters before or after the match).
- The keyword "if" would match in "if(a=b)", but not in "ifdef x", because
- "(" is not a keyword character and "d" is.
+ It can only contain keyword characters, according to the characters
+ specified with |:syn-iskeyword| or the 'iskeyword' option. It cannot
+ contain other syntax items. It will only match with a complete word (there
+ are no keyword characters before or after the match). The keyword "if"
+ would match in "if(a=b)", but not in "ifdef x", because "(" is not a
+ keyword character and "d" is.
2. Match
This is a match with a single regexp pattern.
@@ -3692,7 +3715,7 @@ Vim understands three types of syntax items:
Several syntax ITEMs can be put into one syntax GROUP. For a syntax group
you can give highlighting attributes. For example, you could have an item
-to define a "/* .. */" comment and another one that defines a "// .." comment,
+to define a `/* .. */` comment and another one that defines a "// .." comment,
and put them both in the "Comment" group. You can then specify that a
"Comment" will be in bold font and have a blue color. You are free to make
one highlight group for one syntax item, or put all items into one group.
@@ -3784,7 +3807,7 @@ SYNTAX ISKEYWORD SETTING *:syn-iskeyword*
clear: Syntax specific iskeyword setting is disabled and the
buffer-local 'iskeyword' setting is used.
- {option} Set the syntax 'iskeyword' option to a new value.
+ {option} Set the syntax 'iskeyword' option to a new value.
Example: >
:syntax iskeyword @,48-57,192-255,$,_
@@ -4450,19 +4473,19 @@ Notes:
matched. This doesn't work: "a\nb"ms=e. You can make the highlighting
start in another line, this does work: "a\nb"hs=e.
-Example (match a comment but don't highlight the /* and */): >
+Example (match a comment but don't highlight the `/* and */`): >vim
:syntax region Comment start="/\*"hs=e+1 end="\*/"he=s-1
-<
+< >
/* this is a comment */
^^^^^^^^^^^^^^^^^^^ highlighted
-
-A more complicated Example: >
- :syn region Exa matchgroup=Foo start="foo"hs=s+2,rs=e+2 matchgroup=Bar end="bar"me=e-1,he=e-1,re=s-1
<
+A more complicated Example: >vim
+ :syn region Exa matchgroup=Foo start="foo"hs=s+2,rs=e+2 matchgroup=Bar end="bar"me=e-1,he=e-1,re=s-1
+< >
abcfoostringbarabc
mmmmmmmmmmm match
sssrrreee highlight start/region/end ("Foo", "Exa" and "Bar")
-
+<
Leading context *:syn-lc* *:syn-leading* *:syn-context*
Note: This is an obsolete feature, only included for backwards compatibility
@@ -4762,7 +4785,7 @@ matches, nextgroup, etc. But there are a few differences:
- When a match with a sync pattern is found, the rest of the line (or group of
continued lines) is searched for another match. The last match is used.
This is used when a line can contain both the start end the end of a region
- (e.g., in a C-comment like /* this */, the last "*/" is used).
+ (e.g., in a C-comment like `/* this */`, the last "*/" is used).
There are two ways how a match with a sync pattern can be used:
1. Parsing for highlighting starts where redrawing starts (and where the
@@ -4872,8 +4895,8 @@ in their own color.
output "default".
:colo[rscheme] {name} Load color scheme {name}. This searches 'runtimepath'
- for the file "colors/{name}.(vim|lua)". The first one that
- is found is loaded.
+ for the file "colors/{name}.{vim,lua}". The first one
+ that is found is loaded.
Note: "colors/{name}.vim" is tried first.
Also searches all plugins in 'packpath', first below
"start" and then under "opt".
@@ -4997,7 +5020,7 @@ stop={term-list} *term-list* *highlight-stop*
highlighted area. This should undo the "start" argument.
Otherwise the screen will look messed up.
- {term-list} is a a string with escape sequences. This is any string of
+ {term-list} is a string with escape sequences. This is any string of
characters, except that it can't start with "t_" and blanks are not
allowed. The <> notation is recognized here, so you can use things
like "<Esc>" and "<Space>". Example:
@@ -5035,16 +5058,16 @@ ctermbg={color-nr} *ctermbg*
The number under "NR-16" is used for 16-color terminals ('t_Co'
greater than or equal to 16). The number under "NR-8" is used for
- 8-color terminals ('t_Co' less than 16). The '*' indicates that the
+ 8-color terminals ('t_Co' less than 16). The "*" indicates that the
bold attribute is set for ctermfg. In many 8-color terminals (e.g.,
"linux"), this causes the bright colors to appear. This doesn't work
- for background colors! Without the '*' the bold attribute is removed.
+ for background colors! Without the "*" the bold attribute is removed.
If you want to set the bold attribute in a different way, put a
"cterm=" argument AFTER the "ctermfg=" or "ctermbg=" argument. Or use
a number instead of a color name.
Note that for 16 color ansi style terminals (including xterms), the
- numbers in the NR-8 column is used. Here "*" means "add 8" so that
+ numbers in the NR-8 column is used. Here "*" means "add 8" so that
Blue is 12, DarkGray is 8 etc.
Note that for some color terminals these names may result in the wrong
@@ -5165,7 +5188,7 @@ Conceal Placeholder characters substituted for concealed
*hl-CurSearch*
CurSearch Used for highlighting a search pattern under the cursor
(see 'hlsearch').
- *hl-Cursor*
+ *hl-Cursor* *hl-lCursor*
Cursor Character under the cursor.
lCursor Character under the cursor when |language-mapping|
is used (see 'guicursor').
@@ -5246,12 +5269,26 @@ NonText '@' at the end of the window, characters from 'showbreak'
Normal Normal text.
*hl-NormalFloat*
NormalFloat Normal text in floating windows.
+ *hl-FloatBorder*
+FloatBorder Border of floating windows.
+ *hl-FloatTitle*
+FloatTitle Title of floating windows.
+ *hl-FloatFooter*
+FloatFooter Footer of floating windows.
*hl-NormalNC*
NormalNC Normal text in non-current windows.
*hl-Pmenu*
Pmenu Popup menu: Normal item.
*hl-PmenuSel*
PmenuSel Popup menu: Selected item.
+ *hl-PmenuKind*
+PmenuKind Popup menu: Normal item "kind".
+ *hl-PmenuKindSel*
+PmenuKindSel Popup menu: Selected item "kind".
+ *hl-PmenuExtra*
+PmenuExtra Popup menu: Normal item "extra text".
+ *hl-PmenuExtraSel*
+PmenuExtraSel Popup menu: Selected item "extra text".
*hl-PmenuSbar*
PmenuSbar Popup menu: Scrollbar.
*hl-PmenuThumb*
@@ -5285,8 +5322,6 @@ SpellRare Word that is recognized by the spellchecker as one that is
StatusLine Status line of current window.
*hl-StatusLineNC*
StatusLineNC Status lines of not-current windows.
- Note: If this is equal to "StatusLine", Vim will use "^^^" in
- the status line of the current window.
*hl-TabLine*
TabLine Tab pages line, not active tab page label.
*hl-TabLineFill*
@@ -5446,14 +5481,14 @@ memory Vim will consume.
Only highlighting typedefs, unions and structs can be done too. For this you
must use Universal Ctags (https://ctags.io) or Exuberant ctags.
-Put these lines in your Makefile:
+Put these lines in your Makefile: >
-# Make a highlight file for types. Requires Universal/Exuberant ctags and awk
-types: types.vim
-types.vim: *.[ch]
- ctags --c-kinds=gstu -o- *.[ch] |\
- awk 'BEGIN{printf("syntax keyword Type\t")}\
- {printf("%s ", $$1)}END{print ""}' > $@
+ # Make a highlight file for types. Requires Universal/Exuberant ctags and awk
+ types: types.vim
+ types.vim: *.[ch]
+ ctags --c-kinds=gstu -o- *.[ch] |\
+ awk 'BEGIN{printf("syntax keyword Type\t")}\
+ {printf("%s ", $$1)}END{print ""}' > $@
And put these lines in your vimrc: >
diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt
index 0f785dd1eb..2b5b253a09 100644
--- a/runtime/doc/tagsrch.txt
+++ b/runtime/doc/tagsrch.txt
@@ -92,7 +92,7 @@ The ignore-case matches are found when:
- when 'tagcase' is "followscs" and 'ignorecase' is on or the 'smartcase'
option is on and the pattern does not contain an upper case character
- when 'tagcase' is "ignore"
-- when 'tagcase' is "smart" and the patter does not contain an upper case
+- when 'tagcase' is "smart" and the pattern does not contain an upper case
character
Note that using ignore-case tag searching disables binary searching in the
@@ -695,7 +695,7 @@ do not have an absolute path.
The 'comments' option is used for the commands that display a single line or
jump to a line. It defines patterns that may start a comment. Those lines
are ignored for the search, unless [!] is used. One exception: When the line
-matches the pattern "^# *define" it is not considered to be a comment.
+matches the pattern `"^# *define"` it is not considered to be a comment.
If you want to list matches, and then select one to jump to, you could use a
mapping to do that for you. Here is an example: >
@@ -838,7 +838,7 @@ Common arguments for the commands above:
[!] When included, find matches in lines that are recognized as comments.
When excluded, a match is ignored when the line is recognized as a
comment (according to 'comments'), or the match is in a C comment
- (after "//" or inside /* */). Note that a match may be missed if a
+ (after "//" or inside `/* */`). Note that a match may be missed if a
line is recognized as a comment, but the comment ends halfway the line.
And if the line is a comment, but it is not recognized (according to
'comments') a match may be found in it anyway. Example: >
@@ -907,7 +907,7 @@ The following fields are optional:
If the function returns |v:null| instead of a List, a standard tag lookup will
be performed instead.
-It is not allowed to change the tagstack from inside 'tagfunc'. *E986*
+It is not allowed to change the tagstack from inside 'tagfunc'. *E986*
It is not allowed to close a window or change window from inside 'tagfunc'.
*E1299*
diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt
index 847b4b6112..8ef8675d13 100644
--- a/runtime/doc/term.txt
+++ b/runtime/doc/term.txt
@@ -31,7 +31,7 @@ a non-superuser:
>
curl -LO https://invisible-island.net/datafiles/current/terminfo.src.gz
gunzip terminfo.src.gz
- tic terminfo.src
+ tic -x terminfo.src
<
*$TERM*
The $TERM environment variable must match the terminal you are using!
@@ -108,10 +108,22 @@ and right scroll margins as well. If Nvim detects that the terminal is Xterm,
it will make use of this ability to speed up scrolling that is not the full
width of the terminal.
- *tui-input*
+ *tui-input*
+Historically, terminal emulators could not distinguish between certain control
+key modifiers and other keys. For example, <C-I> and <Tab> are represented in
+the same way, as are <Esc> and <C-[>, <CR> and <C-M>, and <NL> and <C-J>.
+
+Modern terminal emulators are able to distinguish between these pairs of keys
+by encoding control modifiers differently. There are two common but distinct
+ways of doing this, known as "modifyOtherKeys" and "CSI u". Nvim supports both
+encoding methods and at startup will tell the terminal emulator that it
+understands these key encodings. If your terminal emulator supports it then
+this will allow you to map the key pairs listed above separately. |<Tab>|
+
Nvim uses libtermkey to convert terminal escape sequences to key codes.
|terminfo| is used first, and CSI sequences not in |terminfo| (including
-extended keys a.k.a. modifyOtherKeys or "CSI u") can also be parsed.
+extended keys a.k.a. "modifyOtherKeys" or "CSI u") can also be parsed.
+
For example, when running Nvim in tmux, this makes Nvim leave Insert mode and
go to the window below: >
tmux send-keys 'Escape' [ 2 7 u 'C-W' j
@@ -124,42 +136,22 @@ For example, this sequence is recognized by Nvim as <C-kEnter>: >
and can be used differently from <C-CR> in mappings.
*tui-modifyOtherKeys* *tui-csiu*
-Historically, terminal emulators could not distinguish between certain control
-key modifiers and other keys. For example, <C-I> and <Tab> are represented the
-same way, as are <Esc> and <C-[>, <CR> and <C-M>, and <NL> and <C-J>. This
-meant that Nvim also could not map these keys separately.
-
-Modern terminal emulators are able to distinguish between these pairs of keys
-by encoding control modifiers differently. There are two common but distinct
-ways of doing this, known as "modifyOtherKeys" and "CSI u". Nvim supports both
-encoding methods and at startup will tell the terminal emulator that it
-understands these key encodings. If your terminal emulator supports it then
-this will allow you to map the key pairs listed above separately.
-
-At startup Nvim will query your terminal to see if it supports the CSI u
+At startup Nvim will query your terminal to see if it supports the "CSI u"
encoding by writing the sequence >
-
CSI ? u CSI c
-
If your terminal emulator responds with >
-
CSI ? <flags> u
-
-this means your terminal supports the CSI u encoding and Nvim will tell your
+this means your terminal supports the "CSI u" encoding and Nvim will tell your
terminal to enable it by writing the sequence >
-
CSI > 1 u
-
-If your terminal does not support CSI u then Nvim will instead enable the
+If your terminal does not support "CSI u" then Nvim will instead enable the
"modifyOtherKeys" encoding by writing the sequence >
-
CSI > 4 ; 2 m
When Nvim exits cleanly it will send the corresponding sequence to disable the
special key encoding. If Nvim does not exit cleanly then your terminal
emulator could be in a bad state. If this happens, simply run "reset".
-
*tui-colors*
Nvim uses 256 colours by default, ignoring |terminfo| for most terminal types,
including "linux" (whose virtual terminals have had 256-colour support since
diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt
index ef5e179c86..941c440b6c 100644
--- a/runtime/doc/testing.txt
+++ b/runtime/doc/testing.txt
@@ -31,195 +31,24 @@ Find more information in the file src/testdir/README.txt.
==============================================================================
2. Test functions *test-functions-details*
-test_garbagecollect_now() *test_garbagecollect_now()*
- Like garbagecollect(), but executed right away. This must
- only be called directly to avoid any structure to exist
- internally, and |v:testing| must have been set before calling
- any function.
+See |test_garbagecollect_now()|.
==============================================================================
3. Assert functions *assert-functions-details*
-
-assert_beeps({cmd}) *assert_beeps()*
- Run {cmd} and add an error message to |v:errors| if it does
- NOT produce a beep or visual bell.
- Also see |assert_fails()|, |assert_nobeep()| and
- |assert-return|.
-
- Can also be used as a |method|: >
- GetCmd()->assert_beeps()
-<
- *assert_equal()*
-assert_equal({expected}, {actual} [, {msg}])
- When {expected} and {actual} are not equal an error message is
- added to |v:errors| and 1 is returned. Otherwise zero is
- returned |assert-return|.
- There is no automatic conversion, the String "4" is different
- from the Number 4. And the number 4 is different from the
- Float 4.0. The value of 'ignorecase' is not used here, case
- always matters.
- When {msg} is omitted an error in the form "Expected
- {expected} but got {actual}" is produced.
- Example: >
- assert_equal('foo', 'bar')
-< Will result in a string to be added to |v:errors|:
- test.vim line 12: Expected 'foo' but got 'bar' ~
-
- Can also be used as a |method|: >
- mylist->assert_equal([1, 2, 3])
-
-< *assert_equalfile()*
-assert_equalfile({fname-one}, {fname-two})
- When the files {fname-one} and {fname-two} do not contain
- exactly the same text an error message is added to |v:errors|.
- Also see |assert-return|.
- When {fname-one} or {fname-two} does not exist the error will
- mention that.
-
- Can also be used as a |method|: >
- GetLog()->assert_equalfile('expected.log')
-
-assert_exception({error} [, {msg}]) *assert_exception()*
- When v:exception does not contain the string {error} an error
- message is added to |v:errors|. Also see |assert-return|.
- This can be used to assert that a command throws an exception.
- Using the error number, followed by a colon, avoids problems
- with translations: >
- try
- commandthatfails
- call assert_false(1, 'command should have failed')
- catch
- call assert_exception('E492:')
- endtry
-<
- *assert_fails()*
-assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]])
- Run {cmd} and add an error message to |v:errors| if it does
- NOT produce an error or when {error} is not found in the
- error message. Also see |assert-return|.
-
- When {error} is a string it must be found literally in the
- first reported error. Most often this will be the error code,
- including the colon, e.g. "E123:". >
- assert_fails('bad cmd', 'E987:')
-<
- When {error} is a |List| with one or two strings, these are
- used as patterns. The first pattern is matched against the
- first reported error: >
- assert_fails('cmd', ['E987:.*expected bool'])
-< The second pattern, if present, is matched against the last
- reported error. To only match the last error use an empty
- string for the first error: >
- assert_fails('cmd', ['', 'E987:'])
-<
- If {msg} is empty then it is not used. Do this to get the
- default message when passing the {lnum} argument.
-
- When {lnum} is present and not negative, and the {error}
- argument is present and matches, then this is compared with
- the line number at which the error was reported. That can be
- the line number in a function or in a script.
-
- When {context} is present it is used as a pattern and matched
- against the context (script name or function name) where
- {lnum} is located in.
-
- Note that beeping is not considered an error, and some failing
- commands only beep. Use |assert_beeps()| for those.
-
- Can also be used as a |method|: >
- GetCmd()->assert_fails('E99:')
-
-assert_false({actual} [, {msg}]) *assert_false()*
- When {actual} is not false an error message is added to
- |v:errors|, like with |assert_equal()|.
- Also see |assert-return|.
- A value is false when it is zero. When {actual} is not a
- number the assert fails.
- When {msg} is omitted an error in the form
- "Expected False but got {actual}" is produced.
-
- Can also be used as a |method|: >
- GetResult()->assert_false()
-
-assert_inrange({lower}, {upper}, {actual} [, {msg}]) *assert_inrange()*
- This asserts number and |Float| values. When {actual} is lower
- than {lower} or higher than {upper} an error message is added
- to |v:errors|. Also see |assert-return|.
- When {msg} is omitted an error in the form
- "Expected range {lower} - {upper}, but got {actual}" is
- produced.
-
- *assert_match()*
-assert_match({pattern}, {actual} [, {msg}])
- When {pattern} does not match {actual} an error message is
- added to |v:errors|. Also see |assert-return|.
-
- {pattern} is used as with |expr-=~|: The matching is always done
- like 'magic' was set and 'cpoptions' is empty, no matter what
- the actual value of 'magic' or 'cpoptions' is.
-
- {actual} is used as a string, automatic conversion applies.
- Use "^" and "$" to match with the start and end of the text.
- Use both to match the whole text.
-
- When {msg} is omitted an error in the form
- "Pattern {pattern} does not match {actual}" is produced.
- Example: >
- assert_match('^f.*o$', 'foobar')
-< Will result in a string to be added to |v:errors|:
- test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
-
- Can also be used as a |method|: >
- getFile()->assert_match('foo.*')
-<
-assert_nobeep({cmd}) *assert_nobeep()*
- Run {cmd} and add an error message to |v:errors| if it
- produces a beep or visual bell.
- Also see |assert_beeps()|.
-
- Can also be used as a |method|: >
- GetCmd()->assert_nobeep()
-<
- *assert_notequal()*
-assert_notequal({expected}, {actual} [, {msg}])
- The opposite of `assert_equal()`: add an error message to
- |v:errors| when {expected} and {actual} are equal.
- Also see |assert-return|.
-
- Can also be used as a |method|: >
- mylist->assert_notequal([1, 2, 3])
-
-< *assert_notmatch()*
-assert_notmatch({pattern}, {actual} [, {msg}])
- The opposite of `assert_match()`: add an error message to
- |v:errors| when {pattern} matches {actual}.
- Also see |assert-return|.
-
- Can also be used as a |method|: >
- getFile()->assert_notmatch('bar.*')
-
-
-assert_report({msg}) *assert_report()*
- Report a test failure directly, using String {msg}.
- Always returns one.
-
- Can also be used as a |method|: >
- GetMessage()->assert_report()
-
-
-assert_true({actual} [, {msg}]) *assert_true()*
- When {actual} is not true an error message is added to
- |v:errors|, like with |assert_equal()|.
- Also see |assert-return|.
- A value is |TRUE| when it is a non-zero number or |v:true|.
- When {actual} is not a number or |v:true| the assert fails.
- When {msg} is omitted an error in the form "Expected True but
- got {actual}" is produced.
-
- Can also be used as a |method|: >
- GetResult()->assert_true()
-<
+See:
+ - |assert_beeps()|
+ - |assert_equal()|
+ - |assert_equalfile()|
+ - |assert_exception()|
+ - |assert_fails()|
+ - |assert_false()|
+ - |assert_inrange()|
+ - |assert_match()|
+ - |assert_nobeep()|
+ - |assert_notequal()|
+ - |assert_notmatch()|
+ - |assert_report()|
+ - |assert_true()|
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/tips.txt b/runtime/doc/tips.txt
index 5d5e89da77..88528b0e4e 100644
--- a/runtime/doc/tips.txt
+++ b/runtime/doc/tips.txt
@@ -47,7 +47,7 @@ is an overview with tags to jump to:
|gf| Go to file name under the cursor.
-|%| Go to matching (), {}, [], /* */, #if, #else, #endif.
+|%| Go to matching (), {}, [], `/* */`, #if, #else, #endif.
|[/| Go to previous start of comment.
|]/| Go to next end of comment.
|[#| Go back to unclosed #if, #ifdef, or #else.
@@ -59,8 +59,8 @@ is an overview with tags to jump to:
|v_ab| Select "a block" from "[(" to "])", including braces
|v_ib| Select "inner block" from "[(" to "])"
-|v_aB| Select "a block" from "[{" to "]}", including brackets
-|v_iB| Select "inner block" from "[{" to "]}"
+|v_aB| Select "a block" from `[{` to `]}`, including brackets
+|v_iB| Select "inner block" from `[{` to `]}`
==============================================================================
Finding where identifiers are used *ident-search*
@@ -196,7 +196,7 @@ charset.c
digraph.c
...
-and I want to rename *.c *.bla. I'd do it like this: >
+and I want to rename `*.c` `*.bla`. I'd do it like this: >
$ vim
:r !ls *.c
@@ -346,14 +346,26 @@ comma-separated list of extension(s) you find yourself wanting to edit: >
" vim -b : edit binary using xxd-format!
augroup Binary
- au!
- au BufReadPre *.bin let &bin=1
- au BufReadPost *.bin if &bin | %!xxd
- au BufReadPost *.bin set ft=xxd | endif
- au BufWritePre *.bin if &bin | %!xxd -r
- au BufWritePre *.bin endif
- au BufWritePost *.bin if &bin | %!xxd
- au BufWritePost *.bin set nomod | endif
+ autocmd!
+ autocmd BufReadPre *.bin set binary
+ autocmd BufReadPost *.bin
+ \ if &binary
+ \ | execute "silent %!xxd -c 32"
+ \ | set filetype=xxd
+ \ | redraw
+ \ | endif
+ autocmd BufWritePre *.bin
+ \ if &binary
+ \ | let s:view = winsaveview()
+ \ | execute "silent %!xxd -r -c 32"
+ \ | endif
+ autocmd BufWritePost *.bin
+ \ if &binary
+ \ | execute "silent %!xxd -c 32"
+ \ | set nomodified
+ \ | call winrestview(s:view)
+ \ | redraw
+ \ | endif
augroup END
==============================================================================
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 9bfdc0b94e..9bdc6b8d24 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -18,150 +18,129 @@ changes. This documentation may also not fully reflect the latest changes.
PARSER FILES *treesitter-parsers*
Parsers are the heart of tree-sitter. They are libraries that tree-sitter will
-search for in the `parser` runtime directory. By default, Nvim bundles only
-parsers for C, Lua, and Vimscript, but parsers can be installed manually or
-via a plugin like https://github.com/nvim-treesitter/nvim-treesitter.
-Parsers are searched for as `parser/{lang}.*` in any 'runtimepath' directory.
-If multiple parsers for the same language are found, the first one is used.
-(This typically implies the priority "user config > plugins > bundled".
+search for in the `parser` runtime directory. By default, Nvim bundles parsers
+for C, Lua, Vimscript, Vimdoc and Treesitter query files, but parsers can be
+installed manually or via a plugin like
+https://github.com/nvim-treesitter/nvim-treesitter. Parsers are searched for
+as `parser/{lang}.*` in any 'runtimepath' directory. If multiple parsers for
+the same language are found, the first one is used. (This typically implies
+the priority "user config > plugins > bundled".
A parser can also be loaded manually using a full path: >lua
- vim.treesitter.require_language("python", "/path/to/python.so")
+ vim.treesitter.language.add('python', { path = "/path/to/python.so" })
<
==============================================================================
-LANGUAGE TREES *treesitter-languagetree*
- *LanguageTree*
-
-As buffers can contain multiple languages (e.g., Vimscript commands in a Lua
-file), multiple parsers may be needed to parse the full buffer. These are
-combined in a |LanguageTree| object.
-
-To create a LanguageTree (parser object) for a buffer and a given language,
-use >lua
-
- tsparser = vim.treesitter.get_parser(bufnr, lang)
-<
-`bufnr=0` can be used for current buffer. `lang` will default to 'filetype'.
-Currently, the parser will be retained for the lifetime of a buffer but this
-is subject to change. A plugin should keep a reference to the parser object as
-long as it wants incremental updates.
-
-Whenever you need to access the current syntax tree, parse the buffer: >lua
-
- tstree = tsparser:parse()
-<
-This will return a table of immutable |treesitter-tree|s that represent the
-current state of the buffer. When the plugin wants to access the state after a
-(possible) edit it should call `parse()` again. If the buffer wasn't edited,
-the same tree will be returned again without extra work. If the buffer was
-parsed before, incremental parsing will be done of the changed parts.
-
-Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback,
-you must call |vim.treesitter.get_parser()| before you register your callback.
-But preferably parsing shouldn't be done directly in the change callback
-anyway as they will be very frequent. Rather a plugin that does any kind of
-analysis on a tree should use a timer to throttle too frequent updates.
-
-See |lua-treesitter-languagetree| for the list of available methods.
-
-==============================================================================
TREESITTER TREES *treesitter-tree*
- *tstree*
+ *TSTree*
A "treesitter tree" represents the parsed contents of a buffer, which can be
-used to perform further analysis. It is a |luaref-userdata| reference to an
-object held by the tree-sitter library.
+used to perform further analysis. It is a |userdata| reference to an object
+held by the tree-sitter library.
-An instance `tstree` of a treesitter tree supports the following methods.
+An instance `TSTree` of a treesitter tree supports the following methods.
-tstree:root() *tstree:root()*
+TSTree:root() *TSTree:root()*
Return the root node of this tree.
-tstree:copy() *tstree:copy()*
- Returns a copy of the `tstree`.
+TSTree:copy() *TSTree:copy()*
+ Returns a copy of the `TSTree`.
==============================================================================
TREESITTER NODES *treesitter-node*
- *tsnode*
+ *TSNode*
A "treesitter node" represents one specific element of the parsed contents of
-a buffer, which can be captured by a |Query| for, e.g., highlighting. It is a
-|luaref-userdata| reference to an object held by the tree-sitter library.
+a buffer, which can be captured by a |Query| for, e.g., highlighting. It is
+a |userdata| reference to an object held by the tree-sitter library.
-An instance `tsnode` of a treesitter node supports the following methods.
+An instance `TSNode` of a treesitter node supports the following methods.
-tsnode:parent() *tsnode:parent()*
+TSNode:parent() *TSNode:parent()*
Get the node's immediate parent.
-tsnode:next_sibling() *tsnode:next_sibling()*
+TSNode:next_sibling() *TSNode:next_sibling()*
Get the node's next sibling.
-tsnode:prev_sibling() *tsnode:prev_sibling()*
+TSNode:prev_sibling() *TSNode:prev_sibling()*
Get the node's previous sibling.
-tsnode:next_named_sibling() *tsnode:next_named_sibling()*
+TSNode:next_named_sibling() *TSNode:next_named_sibling()*
Get the node's next named sibling.
-tsnode:prev_named_sibling() *tsnode:prev_named_sibling()*
+TSNode:prev_named_sibling() *TSNode:prev_named_sibling()*
Get the node's previous named sibling.
-tsnode:iter_children() *tsnode:iter_children()*
- Iterates over all the direct children of {tsnode}, regardless of whether
+TSNode:iter_children() *TSNode:iter_children()*
+ Iterates over all the direct children of {TSNode}, regardless of whether
they are named or not.
Returns the child node plus the eventual field name corresponding to this
child node.
-tsnode:field({name}) *tsnode:field()*
+TSNode:field({name}) *TSNode:field()*
Returns a table of the nodes corresponding to the {name} field.
-tsnode:child_count() *tsnode:child_count()*
+TSNode:child_count() *TSNode:child_count()*
Get the node's number of children.
-tsnode:child({index}) *tsnode:child()*
+TSNode:child({index}) *TSNode:child()*
Get the node's child at the given {index}, where zero represents the first
child.
-tsnode:named_child_count() *tsnode:named_child_count()*
+TSNode:named_child_count() *TSNode:named_child_count()*
Get the node's number of named children.
-tsnode:named_child({index}) *tsnode:named_child()*
+TSNode:named_child({index}) *TSNode:named_child()*
Get the node's named child at the given {index}, where zero represents the
first named child.
-tsnode:start() *tsnode:start()*
+TSNode:start() *TSNode:start()*
Get the node's start position. Return three values: the row, column and
total byte count (all zero-based).
-tsnode:end_() *tsnode:end_()*
+TSNode:end_() *TSNode:end_()*
Get the node's end position. Return three values: the row, column and
total byte count (all zero-based).
-tsnode:range() *tsnode:range()*
- Get the range of the node. Return four values: the row, column of the
- start position, then the row, column of the end position.
+TSNode:range({include_bytes}) *TSNode:range()*
+ Get the range of the node.
+
+ Return four or six values:
+ - start row
+ - start column
+ - start byte (if {include_bytes} is `true`)
+ - end row
+ - end column
+ - end byte (if {include_bytes} is `true`)
-tsnode:type() *tsnode:type()*
+TSNode:type() *TSNode:type()*
Get the node's type as a string.
-tsnode:symbol() *tsnode:symbol()*
+TSNode:symbol() *TSNode:symbol()*
Get the node's type as a numerical id.
-tsnode:named() *tsnode:named()*
+TSNode:named() *TSNode:named()*
Check if the node is named. Named nodes correspond to named rules in the
grammar, whereas anonymous nodes correspond to string literals in the
grammar.
-tsnode:missing() *tsnode:missing()*
+TSNode:missing() *TSNode:missing()*
Check if the node is missing. Missing nodes are inserted by the parser in
order to recover from certain kinds of syntax errors.
-tsnode:has_error() *tsnode:has_error()*
+TSNode:extra() *TSNode:extra()*
+ Check if the node is extra. Extra nodes represent things like comments,
+ which are not required by the grammar but can appear anywhere.
+
+TSNode:has_changes() *TSNode:has_changes()*
+ Check if a syntax node has been edited.
+
+TSNode:has_error() *TSNode:has_error()*
Check if the node is a syntax error or contains any syntax errors.
-tsnode:sexpr() *tsnode:sexpr()*
+TSNode:sexpr() *TSNode:sexpr()*
Get an S-expression representing the node as a string.
-tsnode:id() *tsnode:id()*
+TSNode:id() *TSNode:id()*
Get an unique identifier for the node inside its own tree.
No guarantees are made about this identifier's internal representation,
@@ -171,20 +150,29 @@ tsnode:id() *tsnode:id()*
Note: The `id` is not guaranteed to be unique for nodes from different
trees.
- *tsnode:descendant_for_range()*
-tsnode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
+TSNode:tree() *TSNode:tree()*
+ Get the |TSTree| of the node.
+ *TSNode:descendant_for_range()*
+TSNode:descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
Get the smallest node within this node that spans the given range of (row,
column) positions
- *tsnode:named_descendant_for_range()*
-tsnode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
+ *TSNode:named_descendant_for_range()*
+TSNode:named_descendant_for_range({start_row}, {start_col}, {end_row}, {end_col})
Get the smallest named node within this node that spans the given range of
(row, column) positions
+ *TSNode:equal()*
+TSNode:equal({node})
+ Check if {node} refers to the same node within the same tree.
+
+ *TSNode:byte_length()*
+TSNode:byte_length()
+ Return the number of bytes spanned by this node.
==============================================================================
TREESITTER QUERIES *treesitter-query*
-Treesitter queries are a way to extract information about a parsed |tstree|,
+Treesitter queries are a way to extract information about a parsed |TSTree|,
e.g., for the purpose of highlighting. Briefly, a `query` consists of one or
more patterns. A `pattern` is defined over node types in the syntax tree. A
`match` corresponds to specific elements of the syntax tree which match a
@@ -202,7 +190,7 @@ Nvim looks for queries as `*.scm` files in a `queries` directory under
purpose, e.g., `queries/lua/highlights.scm` for highlighting Lua files.
By default, the first query on `runtimepath` is used (which usually implies
that user config takes precedence over plugins, which take precedence over
-queries bundled with Neovim). If a query should extend other queries instead
+queries bundled with Nvim). If a query should extend other queries instead
of replacing them, use |treesitter-query-modeline-extends|.
See |lua-treesitter-query| for the list of available methods for working with
@@ -212,7 +200,7 @@ treesitter queries from Lua.
TREESITTER QUERY PREDICATES *treesitter-predicates*
Predicates are special scheme nodes that are evaluated to conditionally capture
-nodes. For example, the `eq?` predicate can be used as follows: >
+nodes. For example, the `eq?` predicate can be used as follows: >query
((identifier) @foo (#eq? @foo "foo"))
<
@@ -221,13 +209,13 @@ to only match identifier corresponding to the `"foo"` text.
The following predicates are built in:
`eq?` *treesitter-predicate-eq?*
- Match a string against the text corresponding to a node: >
+ Match a string against the text corresponding to a node: >query
((identifier) @foo (#eq? @foo "foo"))
((node1) @left (node2) @right (#eq? @left @right))
<
`match?` *treesitter-predicate-match?*
`vim-match?` *treesitter-predicate-vim-match?*
- Match a |regexp| against the text corresponding to a node: >
+ Match a |regexp| against the text corresponding to a node: >query
((identifier) @constant (#match? @constant "^[A-Z_]+$"))
< Note: The `^` and `$` anchors will match the start and end of the
node's text.
@@ -237,31 +225,43 @@ The following predicates are built in:
similar to `match?`
`contains?` *treesitter-predicate-contains?*
- Match a string against parts of the text corresponding to a node: >
+ Match a string against parts of the text corresponding to a node: >query
((identifier) @foo (#contains? @foo "foo"))
((identifier) @foo-bar (#contains? @foo-bar "foo" "bar"))
<
`any-of?` *treesitter-predicate-any-of?*
Match any of the given strings against the text corresponding to
- a node: >
+ a node: >query
((identifier) @foo (#any-of? @foo "foo" "bar"))
<
This is the recommended way to check if the node matches one of many
keywords, as it has been optimized for this.
+ `has-ancestor?` *treesitter-predicate-has-ancestor?*
+ Match any of the given node types against all ancestors of a node: >query
+ ((identifier) @variable.builtin
+ (#any-of? @variable.builtin "begin" "end")
+ (#has-ancestor? @variable.builtin range_expression))
+<
+ `has-parent?` *treesitter-predicate-has-parent?*
+ Match any of the given node types against the direct ancestor of a
+ node: >query
+ (((field_expression
+ (field_identifier) @method)) @_parent
+ (#has-parent? @_parent template_method function_declarator))
+<
*lua-treesitter-not-predicate*
Each predicate has a `not-` prefixed predicate that is just the negation of
the predicate.
Further predicates can be added via |vim.treesitter.query.add_predicate()|.
-Use |vim.treesitter.query.list_predicates()| to list all available
-predicates.
+Use |vim.treesitter.query.list_predicates()| to list all available predicates.
TREESITTER QUERY DIRECTIVES *treesitter-directives*
Treesitter directives store metadata for a node or match and perform side
-effects. For example, the `set!` directive sets metadata on the match or node: >
+effects. For example, the `set!` directive sets metadata on the match or node: >query
((identifier) @foo (#set! "type" "parameter"))
<
@@ -277,7 +277,7 @@ The following directives are built in:
{key}
{value}
- Examples: >
+ Examples: >query
((identifier) @foo (#set! @foo "kind" "parameter"))
((node1) @left (node2) @right (#set! "type" "pair"))
<
@@ -293,18 +293,37 @@ The following directives are built in:
{end_row}
{end_col}
- Example: >
+ Example: >query
((identifier) @constant (#offset! @constant 0 1 0 -1))
<
+ `gsub!` *treesitter-directive-gsub!*
+ Transforms the content of the node using a Lua pattern. This will set
+ a new `metadata[capture_id].text`.
+
+ Parameters: ~
+ {capture_id}
+ {pattern}
+
+ Example: >query
+ (#gsub! @_node ".*%.(.*)" "%1")
+<
+ `trim!` *treesitter-directive-trim!*
+ Trim blank lines from the end of the node. This will set a new
+ `metadata[capture_id].range`.
+
+ Parameters: ~
+ {capture_id}
+ Example: >query
+ (#trim! @fold)
+<
Further directives can be added via |vim.treesitter.query.add_directive()|.
-Use |vim.treesitter.query.list_directives()| to list all available
-directives.
+Use |vim.treesitter.query.list_directives()| to list all available directives.
TREESITTER QUERY MODELINES *treesitter-query-modeline*
-Neovim supports to customize the behavior of the queries using a set of
+Nvim supports to customize the behavior of the queries using a set of
"modelines", that is comments in the queries starting with `;`. Here are the
currently supported modeline alternatives:
@@ -323,11 +342,12 @@ currently supported modeline alternatives:
a base depends on your 'runtimepath' value.
Note: These modeline comments must be at the top of the query, but can be
-repeated, for example, the following two modeline blocks are both valid: >
-
+repeated, for example, the following two modeline blocks are both valid:
+>query
;; inherits: foo,bar
;; extends
-
+<
+>query
;; extends
;;
;; inherits: baz
@@ -336,14 +356,14 @@ repeated, for example, the following two modeline blocks are both valid: >
TREESITTER SYNTAX HIGHLIGHTING *treesitter-highlight*
Syntax highlighting is specified through queries named `highlights.scm`,
-which match a |tsnode| in the parsed |tstree| to a `capture` that can be
-assigned a highlight group. For example, the query >
+which match a |TSNode| in the parsed |TSTree| to a `capture` that can be
+assigned a highlight group. For example, the query >query
(parameters (identifier) @parameter)
<
matches any `identifier` node inside a function `parameter` node (e.g., the
`bar` in `foo(bar)`) to the capture named `@parameter`. It is also possible to
-match literal expressions (provided the parser returns them): >
+match literal expressions (provided the parser returns them): >query
"return" @keyword.return
<
@@ -428,7 +448,7 @@ The following captures are linked by default to standard |group-name|s:
*treesitter-highlight-spell*
The special `@spell` capture can be used to indicate that a node should be
spell checked by Nvim's builtin |spell| checker. For example, the following
-capture marks comments as to be checked: >
+capture marks comments as to be checked: >query
(comment) @spell
<
@@ -439,14 +459,14 @@ There is also `@nospell` which disables spellchecking regions with `@spell`.
Treesitter highlighting supports |conceal| via the `conceal` metadata. By
convention, nodes to be concealed are captured as `@conceal`, but any capture
can be used. For example, the following query can be used to hide code block
-delimiters in Markdown: >
+delimiters in Markdown: >query
- (fenced_code_block_delimiter) @conceal (#set! conceal "")
+ (fenced_code_block_delimiter @conceal (#set! conceal ""))
<
It is also possible to replace a node with a single character, which (unlike
legacy syntax) can be given a custom highlight. For example, the following
(ill-advised) query replaces the `!=` operator by a Unicode glyph, which is
-still highlighted the same as other operators: >
+still highlighted the same as other operators: >query
"!=" @operator (#set! conceal "≠")
<
@@ -457,10 +477,61 @@ Treesitter uses |nvim_buf_set_extmark()| to set highlights with a default
priority of 100. This enables plugins to set a highlighting priority lower or
higher than tree-sitter. It is also possible to change the priority of an
individual query pattern manually by setting its `"priority"` metadata
-attribute: >
+attribute: >query
+
+ ((super_important_node) @superimportant (#set! "priority" 105))
+<
- (super_important_node) @ImportantHighlight (#set! "priority" 105)
+==============================================================================
+TREESITTER LANGUAGE INJECTIONS *treesitter-language-injections*
<
+
+Note the following information is adapted from:
+ https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
+
+Some source files contain code written in multiple different languages.
+Examples include:
+
+ • HTML files, which can contain JavaScript inside of `<script>` tags and
+ CSS inside of `<style>` tags
+ • ERB files, which contain Ruby inside of `<%` `%>` tags, and HTML outside of
+ those tags
+ • PHP files, which can contain HTML between the `<php` tags
+ • JavaScript files, which contain regular expression syntax within regex
+ literals
+ • Ruby, which can contain snippets of code inside of heredoc literals,
+ where the heredoc delimiter often indicates the language
+ • Lua, which can contain snippets of Vimscript inside |vim.cmd()| calls.
+ • Vimscript, which can contain snippets of Lua inside |:lua-heredoc|
+ blocks.
+
+All of these examples can be modeled in terms of a parent syntax tree and one
+or more injected syntax trees, which reside inside of certain nodes in the
+parent tree. The language injection query allows you to specify these
+“injections” using the following captures:
+
+ • `@injection.content` - indicates that the captured node should have its
+ contents re-parsed using another language.
+ • `@injection.language` - indicates that the captured node’s text may
+ contain the name of a language that should be used to re-parse the
+ `@injection.content`.
+
+The language injection behavior can also be configured by some properties
+associated with patterns:
+
+ • `injection.language` - can be used to hard-code the name of a specific
+ language.
+ • `injection.combined` - indicates that all of the matching nodes in the
+ tree should have their content parsed as one nested document.
+ • `injection.include-children` - indicates that the `@injection.content`
+ node's entire text should be re-parsed, including the text of its child
+ nodes. By default, child nodes' text will be excluded from the injected
+ document.
+ • `injection.self` - indicates that the node's text should be parsed with
+ the same language as the node's LanguageTree.
+ • `injection.parent` - indicates that the captured node’s text should
+ be parsed with the same language as the node's parent LanguageTree.
+
==============================================================================
VIM.TREESITTER *lua-treesitter*
@@ -481,12 +552,34 @@ library.
==============================================================================
Lua module: vim.treesitter *lua-treesitter-core*
+foldexpr({lnum}) *vim.treesitter.foldexpr()*
+ Returns the fold level for {lnum} in the current buffer. Can be set
+ directly to 'foldexpr': >lua
+ vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
+<
+
+ Parameters: ~
+ • {lnum} (integer|nil) Line number to calculate fold level for
+
+ Return: ~
+ (string)
+
+foldtext() *vim.treesitter.foldtext()*
+ Returns the highlighted content of the first line of the fold or falls
+ back to |foldtext()| if no treesitter parser is found. Can be set directly
+ to 'foldtext': >lua
+ vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()'
+<
+
+ Return: ~
+ `{ [1]: string, [2]: string[] }[]` | string
+
*vim.treesitter.get_captures_at_cursor()*
get_captures_at_cursor({winnr})
Returns a list of highlight capture names under the cursor
Parameters: ~
- • {winnr} (number|nil) Window handle or 0 for current window (default)
+ • {winnr} (integer|nil) Window handle or 0 for current window (default)
Return: ~
string[] List of capture names
@@ -500,63 +593,90 @@ get_captures_at_pos({bufnr}, {row}, {col})
if none are defined).
Parameters: ~
- • {bufnr} (number) Buffer number (0 for current buffer)
- • {row} (number) Position row
- • {col} (number) Position column
+ • {bufnr} (integer) Buffer number (0 for current buffer)
+ • {row} (integer) Position row
+ • {col} (integer) Position column
Return: ~
- table[] List of captures `{ capture = "capture name", metadata = { ...
- } }`
+ table[] List of captures `{ capture = "name", metadata = { ... } }`
+
+get_node({opts}) *vim.treesitter.get_node()*
+ Returns the smallest named node at the given position
-get_node_at_cursor({winnr}) *vim.treesitter.get_node_at_cursor()*
- Returns the smallest named node under the cursor
+ NOTE: Calling this on an unparsed tree can yield an invalid node. If the
+ tree is not known to be parsed by, e.g., an active highlighter, parse the
+ tree first via >lua
+ vim.treesitter.get_parser(bufnr):parse(range)
+<
Parameters: ~
- • {winnr} (number|nil) Window handle or 0 for current window (default)
+ • {opts} (table|nil) Optional keyword arguments:
+ • bufnr integer|nil Buffer number (nil or 0 for current
+ buffer)
+ • pos table|nil 0-indexed (row, col) tuple. Defaults to cursor
+ position in the current window. Required if {bufnr} is not
+ the current buffer
+ • ignore_injections boolean Ignore injected languages (default
+ true)
Return: ~
- (string) Name of node under the cursor
+ |TSNode| | nil Node at the given position
- *vim.treesitter.get_node_at_pos()*
-get_node_at_pos({bufnr}, {row}, {col}, {opts})
- Returns the smallest named node at the given position
+get_node_range({node_or_range}) *vim.treesitter.get_node_range()*
+ Returns the node's range or an unpacked range table
Parameters: ~
- • {bufnr} (number) Buffer number (0 for current buffer)
- • {row} (number) Position row
- • {col} (number) Position column
- • {opts} (table) Optional keyword arguments:
- • lang string|nil Parser language
- • ignore_injections boolean Ignore injected languages
- (default true)
+ • {node_or_range} (|TSNode| | table) Node or table of positions
- Return: ~
- userdata|nil |tsnode| under the cursor
+ Return (multiple): ~
+ (integer) start_row
+ (integer) start_col
+ (integer) end_row
+ (integer) end_col
-get_node_range({node_or_range}) *vim.treesitter.get_node_range()*
- Returns the node's range or an unpacked range table
+ *vim.treesitter.get_node_text()*
+get_node_text({node}, {source}, {opts})
+ Gets the text corresponding to a given node
Parameters: ~
- • {node_or_range} (userdata|table) |tsnode| or table of positions
+ • {node} |TSNode|
+ • {source} (integer|string) Buffer or string from which the {node} is
+ extracted
+ • {opts} (table|nil) Optional parameters.
+ • metadata (table) Metadata of a specific capture. This
+ would be set to `metadata[capture_id]` when using
+ |vim.treesitter.query.add_directive()|.
Return: ~
- (table) `{ start_row, start_col, end_row, end_col }`
+ (string)
get_parser({bufnr}, {lang}, {opts}) *vim.treesitter.get_parser()*
- Returns the parser for a specific buffer and filetype and attaches it to
- the buffer
+ Returns the parser for a specific buffer and attaches it to the buffer
If needed, this will create the parser.
Parameters: ~
- • {bufnr} (number|nil) Buffer the parser should be tied to (default:
+ • {bufnr} (integer|nil) Buffer the parser should be tied to (default:
current buffer)
• {lang} (string|nil) Filetype of this parser (default: buffer
filetype)
• {opts} (table|nil) Options to pass to the created language tree
Return: ~
- LanguageTree |LanguageTree| object to use for parsing
+ |LanguageTree| object to use for parsing
+
+get_range({node}, {source}, {metadata}) *vim.treesitter.get_range()*
+ Get the range of a |TSNode|. Can also supply {source} and {metadata} to
+ get the range with directives applied.
+
+ Parameters: ~
+ • {node} |TSNode|
+ • {source} integer|string|nil Buffer or string from which the {node}
+ is extracted
+ • {metadata} TSMetadata|nil
+
+ Return: ~
+ (table)
*vim.treesitter.get_string_parser()*
get_string_parser({str}, {lang}, {opts})
@@ -568,14 +688,42 @@ get_string_parser({str}, {lang}, {opts})
• {opts} (table|nil) Options to pass to the created language tree
Return: ~
- LanguageTree |LanguageTree| object to use for parsing
+ |LanguageTree| object to use for parsing
+
+inspect_tree({opts}) *vim.treesitter.inspect_tree()*
+ Open a window that displays a textual representation of the nodes in the
+ language tree.
+
+ While in the window, press "a" to toggle display of anonymous nodes, "I"
+ to toggle the display of the source language of each node, "o" to toggle
+ the query editor, and press <Enter> to jump to the node under the cursor
+ in the source buffer.
+
+ Can also be shown with `:InspectTree`. *:InspectTree*
+
+ Parameters: ~
+ • {opts} (table|nil) Optional options table with the following possible
+ keys:
+ • lang (string|nil): The language of the source buffer. If
+ omitted, the filetype of the source buffer is used.
+ • bufnr (integer|nil): Buffer to draw the tree into. If
+ omitted, a new buffer is created.
+ • winid (integer|nil): Window id to display the tree buffer
+ in. If omitted, a new window is created with {command}.
+ • command (string|nil): Vimscript command to create the
+ window. Default value is "60vnew". Only used when {winid} is
+ nil.
+ • title (string|fun(bufnr:integer):string|nil): Title of the
+ window. If a function, it accepts the buffer number of the
+ source buffer as its only argument and should return a
+ string.
is_ancestor({dest}, {source}) *vim.treesitter.is_ancestor()*
Determines whether a node is the ancestor of another
Parameters: ~
- • {dest} userdata Possible ancestor |tsnode|
- • {source} userdata Possible descendant |tsnode|
+ • {dest} |TSNode| Possible ancestor
+ • {source} |TSNode| Possible descendant
Return: ~
(boolean) True if {dest} is an ancestor of {source}
@@ -585,9 +733,9 @@ is_in_node_range({node}, {line}, {col})
Determines whether (line, col) position is in node range
Parameters: ~
- • {node} userdata |tsnode| defining the range
- • {line} (number) Line (0-based)
- • {col} (number) Column (0-based)
+ • {node} |TSNode| defining the range
+ • {line} (integer) Line (0-based)
+ • {col} (integer) Column (0-based)
Return: ~
(boolean) True if the position is in node range
@@ -596,37 +744,12 @@ node_contains({node}, {range}) *vim.treesitter.node_contains()*
Determines if a node contains a range
Parameters: ~
- • {node} userdata |tsnode|
+ • {node} |TSNode|
• {range} (table)
Return: ~
(boolean) True if the {node} contains the {range}
-show_tree({opts}) *vim.treesitter.show_tree()*
- Open a window that displays a textual representation of the nodes in the
- language tree.
-
- While in the window, press "a" to toggle display of anonymous nodes, "I"
- to toggle the display of the source language of each node, and press
- <Enter> to jump to the node under the cursor in the source buffer.
-
- Parameters: ~
- • {opts} (table|nil) Optional options table with the following possible
- keys:
- • lang (string|nil): The language of the source buffer. If
- omitted, the filetype of the source buffer is used.
- • bufnr (number|nil): Buffer to draw the tree into. If
- omitted, a new buffer is created.
- • winid (number|nil): Window id to display the tree buffer in.
- If omitted, a new window is created with {command}.
- • command (string|nil): Vimscript command to create the
- window. Default value is "topleft 60vnew". Only used when
- {winid} is nil.
- • title (string|fun(bufnr:number):string|nil): Title of the
- window. If a function, it accepts the buffer number of the
- source buffer as its only argument and should return a
- string.
-
start({bufnr}, {lang}) *vim.treesitter.start()*
Starts treesitter highlighting for a buffer
@@ -637,17 +760,16 @@ start({bufnr}, {lang}) *vim.treesitter.start()*
the call to `start`.
Example: >lua
-
- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
- callback = function(args)
- vim.treesitter.start(args.buf, 'latex')
- vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed
- end
- })
+ vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
+ callback = function(args)
+ vim.treesitter.start(args.buf, 'latex')
+ vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed
+ end
+ })
<
Parameters: ~
- • {bufnr} (number|nil) Buffer to be highlighted (default: current
+ • {bufnr} (integer|nil) Buffer to be highlighted (default: current
buffer)
• {lang} (string|nil) Language of the parser (default: buffer
filetype)
@@ -656,14 +778,45 @@ stop({bufnr}) *vim.treesitter.stop()*
Stops treesitter highlighting for a buffer
Parameters: ~
- • {bufnr} (number|nil) Buffer to stop highlighting (default: current
+ • {bufnr} (integer|nil) Buffer to stop highlighting (default: current
buffer)
==============================================================================
Lua module: vim.treesitter.language *lua-treesitter-language*
-inspect_language({lang}) *vim.treesitter.language.inspect_language()*
+add({lang}, {opts}) *vim.treesitter.language.add()*
+ Load parser with name {lang}
+
+ Parsers are searched in the `parser` runtime directory, or the provided
+ {path}
+
+ Parameters: ~
+ • {lang} (string) Name of the parser (alphanumerical and `_` only)
+ • {opts} (table|nil) Options:
+ • filetype (string|string[]) Default filetype the parser
+ should be associated with. Defaults to {lang}.
+ • path (string|nil) Optional path the parser is located at
+ • symbol_name (string|nil) Internal symbol name for the
+ language to load
+
+get_filetypes({lang}) *vim.treesitter.language.get_filetypes()*
+ Get the filetypes associated with the parser named {lang}.
+
+ Parameters: ~
+ • {lang} (string) Name of parser
+
+ Return: ~
+ string[] filetypes
+
+get_lang({filetype}) *vim.treesitter.language.get_lang()*
+ Parameters: ~
+ • {filetype} (string)
+
+ Return: ~
+ (string|nil)
+
+inspect({lang}) *vim.treesitter.language.inspect()*
Inspects the provided language.
Inspecting provides some useful information on the language like node
@@ -675,24 +828,12 @@ inspect_language({lang}) *vim.treesitter.language.inspect_language()*
Return: ~
(table)
- *vim.treesitter.language.require_language()*
-require_language({lang}, {path}, {silent}, {symbol_name})
- Asserts that a parser for the language {lang} is installed.
-
- Parsers are searched in the `parser` runtime directory, or the provided
- {path}
+register({lang}, {filetype}) *vim.treesitter.language.register()*
+ Register a parser named {lang} to be used for {filetype}(s).
Parameters: ~
- • {lang} (string) Language the parser should parse
- (alphanumerical and `_` only)
- • {path} (string|nil) Optional path the parser is located at
- • {silent} (boolean|nil) Don't throw an error if language not
- found
- • {symbol_name} (string|nil) Internal symbol name for the language to
- load
-
- Return: ~
- (boolean) If the specified language is installed
+ • {lang} (string) Name of parser
+ • {filetype} string|string[] Filetype(s) to associate with lang
==============================================================================
@@ -709,8 +850,8 @@ add_directive({name}, {handler}, {force})
Parameters: ~
• {name} (string) Name of the directive, without leading #
- • {handler} function(match:table, pattern:string, bufnr:number,
- predicate:string[], metadata:table)
+ • {handler} function(match:table<string,|TSNode|>, pattern:string,
+ bufnr:integer, predicate:string[], metadata:table)
• match: see |treesitter-query|
• node-level data are accessible via `match[capture_id]`
@@ -718,6 +859,7 @@ add_directive({name}, {handler}, {force})
• predicate: list of strings containing the full directive
being called, e.g. `(node (#set! conceal "-"))` would get
the predicate `{ "#set!", "conceal", "-" }`
+ • {force} (boolean|nil)
*vim.treesitter.query.add_predicate()*
add_predicate({name}, {handler}, {force})
@@ -725,27 +867,27 @@ add_predicate({name}, {handler}, {force})
Parameters: ~
• {name} (string) Name of the predicate, without leading #
- • {handler} function(match:table, pattern:string, bufnr:number,
- predicate:string[])
+ • {handler} function(match:table<string,|TSNode|>, pattern:string,
+ bufnr:integer, predicate:string[])
• see |vim.treesitter.query.add_directive()| for argument
meanings
+ • {force} (boolean|nil)
- *vim.treesitter.query.get_node_text()*
-get_node_text({node}, {source}, {opts})
- Gets the text corresponding to a given node
+edit({lang}) *vim.treesitter.query.edit()*
+ Opens a live editor to query the buffer you started from.
- Parameters: ~
- • {node} userdata |tsnode|
- • {source} (number|string) Buffer or string from which the {node} is
- extracted
- • {opts} (table|nil) Optional parameters.
- • concat: (boolean) Concatenate result in a string (default
- true)
+ Can also be shown with *:EditQuery*.
- Return: ~
- (string[]|string)
+ If you move the cursor to a capture name ("@foo"), text matching the
+ capture is highlighted in the source buffer. The query editor is a scratch
+ buffer, use `:write` to save it. You can find example queries at
+ `$VIMRUNTIME/queries/`.
+
+ Parameters: ~
+ • {lang} (string|nil) language to open the query editor for. If
+ omitted, inferred from the current buffer's filetype.
-get_query({lang}, {query_name}) *vim.treesitter.query.get_query()*
+get({lang}, {query_name}) *vim.treesitter.query.get()*
Returns the runtime query {query_name} for {lang}.
Parameters: ~
@@ -753,10 +895,10 @@ get_query({lang}, {query_name}) *vim.treesitter.query.get_query()*
• {query_name} (string) Name of the query (e.g. "highlights")
Return: ~
- Query Parsed query
+ Query|nil Parsed query
- *vim.treesitter.query.get_query_files()*
-get_query_files({lang}, {query_name}, {is_included})
+ *vim.treesitter.query.get_files()*
+get_files({lang}, {query_name}, {is_included})
Gets the list of files used to make up a query
Parameters: ~
@@ -769,6 +911,28 @@ get_query_files({lang}, {query_name}, {is_included})
string[] query_files List of files to load for given query and
language
+lint({buf}, {opts}) *vim.treesitter.query.lint()*
+ Lint treesitter queries using installed parser, or clear lint errors.
+
+ Use |treesitter-parsers| in runtimepath to check the query file in {buf}
+ for errors:
+
+ • verify that used nodes are valid identifiers in the grammar.
+ • verify that predicates and directives are valid.
+ • verify that top-level s-expressions are valid.
+
+ The found diagnostics are reported using |diagnostic-api|. By default, the
+ parser used for verification is determined by the containing folder of the
+ query file, e.g., if the path ends in `/lua/highlights.scm` , the parser for the `lua` language will be used.
+
+ Parameters: ~
+ • {buf} (integer) Buffer handle
+ • {opts} (QueryLinterOpts|nil) Optional keyword arguments:
+ • langs (string|string[]|nil) Language(s) to use for checking
+ the query. If multiple languages are specified, queries are
+ validated for all of them
+ • clear (boolean) if `true`, just clear current lint errors
+
list_directives() *vim.treesitter.query.list_directives()*
Lists the currently available directives to use in queries.
@@ -781,7 +945,14 @@ list_predicates() *vim.treesitter.query.list_predicates()*
Return: ~
string[] List of supported predicates.
-parse_query({lang}, {query}) *vim.treesitter.query.parse_query()*
+omnifunc({findstart}, {base}) *vim.treesitter.query.omnifunc()*
+ Omnifunc for completing node names and predicates in treesitter queries.
+
+ Use via >lua
+ vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+<
+
+parse({lang}, {query}) *vim.treesitter.query.parse()*
Parse {query} as a string. (If the query is in a file, the caller should
read the contents into a string before calling).
@@ -801,44 +972,41 @@ parse_query({lang}, {query}) *vim.treesitter.query.parse_query()*
Query Parsed query
*Query:iter_captures()*
-Query:iter_captures({self}, {node}, {source}, {start}, {stop})
+Query:iter_captures({node}, {source}, {start}, {stop})
Iterate over all captures from all matches inside {node}
{source} is needed if the query contains predicates; then the caller must
ensure to use a freshly parsed tree consistent with the current text of
- the buffer (if relevant). {start_row} and {end_row} can be used to limit
- matches inside a row range (this is typically used with root node as the
- {node}, i.e., to get syntax highlight matches in the current viewport).
- When omitted, the {start} and {end} row values are used from the given
- node.
+ the buffer (if relevant). {start} and {stop} can be used to limit matches
+ inside a row range (this is typically used with root node as the {node},
+ i.e., to get syntax highlight matches in the current viewport). When
+ omitted, the {start} and {stop} row values are used from the given node.
The iterator returns three values: a numeric id identifying the capture,
the captured node, and metadata from any directives processing the match.
The following example shows how to get captures by name: >lua
-
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
- local name = query.captures[id] -- name of the capture in the query
- -- typically useful info about the node:
- local type = node:type() -- type of the captured node
- local row1, col1, row2, col2 = node:range() -- range of the capture
- ... use the info here ...
- end
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
+ local name = query.captures[id] -- name of the capture in the query
+ -- typically useful info about the node:
+ local type = node:type() -- type of the captured node
+ local row1, col1, row2, col2 = node:range() -- range of the capture
+ -- ... use the info here ...
+ end
<
Parameters: ~
- • {node} userdata |tsnode| under which the search will occur
- • {source} (number|string) Source buffer or string to extract text from
- • {start} (number) Starting line for the search
- • {stop} (number) Stopping line for the search (end-exclusive)
- • {self}
+ • {node} |TSNode| under which the search will occur
+ • {source} (integer|string) Source buffer or string to extract text
+ from
+ • {start} (integer) Starting line for the search
+ • {stop} (integer) Stopping line for the search (end-exclusive)
Return: ~
- (number) capture Matching capture id
- (table) capture_node Capture for {node}
- (table) metadata for the {capture}
+ (fun(end_line: integer|nil): integer, TSNode, TSMetadata): capture id,
+ capture node, metadata
*Query:iter_matches()*
-Query:iter_matches({self}, {node}, {source}, {start}, {stop})
+Query:iter_matches({node}, {source}, {start}, {stop}, {opts})
Iterates the matches of self on a given range.
Iterate over all matches within a {node}. The arguments are the same as
@@ -846,34 +1014,36 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop})
(1-based) index of the pattern in the query, a table mapping capture
indices to nodes, and metadata from any directives processing the match.
If the query has more than one pattern, the capture table might be sparse
- and e.g. `pairs()` method should be used over `ipairs` . Here is an example iterating over all captures in every match: >lua
-
- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
- for id, node in pairs(match) do
- local name = query.captures[id]
- -- `node` was captured by the `name` capture in the match
-
- local node_data = metadata[id] -- Node level metadata
-
- ... use the info here ...
- end
- end
+ and e.g. `pairs()` method should be used over `ipairs`. Here is an example
+ iterating over all captures in every match: >lua
+ for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
+ for id, node in pairs(match) do
+ local name = query.captures[id]
+ -- `node` was captured by the `name` capture in the match
+
+ local node_data = metadata[id] -- Node level metadata
+
+ -- ... use the info here ...
+ end
+ end
<
Parameters: ~
- • {node} userdata |tsnode| under which the search will occur
- • {source} (number|string) Source buffer or string to search
- • {start} (number) Starting line for the search
- • {stop} (number) Stopping line for the search (end-exclusive)
- • {self}
+ • {node} |TSNode| under which the search will occur
+ • {source} (integer|string) Source buffer or string to search
+ • {start} (integer) Starting line for the search
+ • {stop} (integer) Stopping line for the search (end-exclusive)
+ • {opts} (table|nil) Options:
+ • max_start_depth (integer) if non-zero, sets the maximum
+ start depth for each match. This is used to prevent
+ traversing too deep into a tree. Requires treesitter >=
+ 0.20.9.
Return: ~
- (number) pattern id
- (table) match
- (table) metadata
+ (fun(): integer, table<integer,TSNode>, table): pattern id, match,
+ metadata
- *vim.treesitter.query.set_query()*
-set_query({lang}, {query_name}, {text})
+set({lang}, {query_name}, {text}) *vim.treesitter.query.set()*
Sets the runtime query named {query_name} for {lang}
This allows users to override any runtime files and/or configuration set
@@ -886,111 +1056,112 @@ set_query({lang}, {query_name}, {text})
==============================================================================
-Lua module: vim.treesitter.highlighter *lua-treesitter-highlighter*
+Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree*
-new({tree}, {opts}) *vim.treesitter.highlighter.new()*
- Creates a new highlighter using
- Parameters: ~
- • {tree} LanguageTree |LanguageTree| parser object to use for highlighting
- • {opts} (table|nil) Configuration of the highlighter:
- • queries table overwrite queries used by the highlighter
+A *LanguageTree* contains a tree of parsers: the root treesitter parser
+for {lang} and any "injected" language parsers, which themselves may
+inject other languages, recursively. For example a Lua buffer containing
+some Vimscript commands needs multiple parsers to fully understand its
+contents.
- Return: ~
- TSHighlighter Created highlighter object
+To create a LanguageTree (parser object) for a given buffer and language, use: >lua
+ local parser = vim.treesitter.get_parser(bufnr, lang)
-TSHighlighter:destroy({self}) *TSHighlighter:destroy()*
- Removes all internal references to the highlighter
+<
- Parameters: ~
- • {self}
+(where `bufnr=0` means current buffer). `lang` defaults to 'filetype'.
+Note: currently the parser is retained for the lifetime of a buffer but
+this may change; a plugin should keep a reference to the parser object if
+it wants incremental updates.
+Whenever you need to access the current syntax tree, parse the buffer: >lua
+ local tree = parser:parse({ start_row, end_row })
-==============================================================================
-Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree*
+<
-LanguageTree:children({self}) *LanguageTree:children()*
+This returns a table of immutable |treesitter-tree| objects representing
+the current state of the buffer. When the plugin wants to access the state
+after a (possible) edit it must call `parse()` again. If the buffer wasn't
+edited, the same tree will be returned again without extra work. If the
+buffer was parsed before, incremental parsing will be done of the changed
+parts.
+
+Note: To use the parser directly inside a |nvim_buf_attach()| Lua
+callback, you must call |vim.treesitter.get_parser()| before you register
+your callback. But preferably parsing shouldn't be done directly in the
+change callback anyway as they will be very frequent. Rather a plugin that
+does any kind of analysis on a tree should use a timer to throttle too
+frequent updates.
+
+LanguageTree:children() *LanguageTree:children()*
Returns a map of language to child tree.
- Parameters: ~
- • {self}
-
-LanguageTree:contains({self}, {range}) *LanguageTree:contains()*
+LanguageTree:contains({range}) *LanguageTree:contains()*
Determines whether {range} is contained in the |LanguageTree|.
Parameters: ~
• {range} (table) `{ start_line, start_col, end_line, end_col }`
- • {self}
Return: ~
(boolean)
-LanguageTree:destroy({self}) *LanguageTree:destroy()*
+LanguageTree:destroy() *LanguageTree:destroy()*
Destroys this |LanguageTree| and all its children.
Any cleanup logic should be performed here.
- Note: This DOES NOT remove this tree from a parent. Instead, `remove_child` must be called on the parent to remove it.
-
- Parameters: ~
- • {self}
-
- *LanguageTree:for_each_child()*
-LanguageTree:for_each_child({self}, {fn}, {include_self})
- Invokes the callback for each |LanguageTree| and its children recursively
-
- Parameters: ~
- • {fn} function(tree: LanguageTree, lang: string)
- • {include_self} (boolean) Whether to include the invoking tree in the
- results
- • {self}
+ Note: This DOES NOT remove this tree from a parent. Instead,
+ `remove_child` must be called on the parent to remove it.
-LanguageTree:for_each_tree({self}, {fn}) *LanguageTree:for_each_tree()*
+LanguageTree:for_each_tree({fn}) *LanguageTree:for_each_tree()*
Invokes the callback for each |LanguageTree| recursively.
Note: This includes the invoking tree's child trees as well.
Parameters: ~
- • {fn} function(tree: TSTree, languageTree: LanguageTree)
- • {self}
+ • {fn} fun(tree: TSTree, ltree: LanguageTree)
-LanguageTree:included_regions({self}) *LanguageTree:included_regions()*
- Gets the set of included regions
+LanguageTree:included_regions() *LanguageTree:included_regions()*
+ Gets the set of included regions managed by this LanguageTree . This can be different from the regions set by injection query, because a
+ partial |LanguageTree:parse()| drops the regions outside the requested
+ range.
- Parameters: ~
- • {self}
+ Return: ~
+ table<integer, Range6[]>
-LanguageTree:invalidate({self}, {reload}) *LanguageTree:invalidate()*
+LanguageTree:invalidate({reload}) *LanguageTree:invalidate()*
Invalidates this parser and all its children
Parameters: ~
- • {self}
+ • {reload} (boolean|nil)
-LanguageTree:is_valid({self}) *LanguageTree:is_valid()*
- Determines whether this tree is valid. If the tree is invalid, call `parse()` . This will return the updated tree.
+LanguageTree:is_valid({exclude_children}) *LanguageTree:is_valid()*
+ Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest state of the
+ source. If invalid, user should call |LanguageTree:parse()|.
Parameters: ~
- • {self}
+ • {exclude_children} (boolean|nil) whether to ignore the validity of
+ children (default `false`)
-LanguageTree:lang({self}) *LanguageTree:lang()*
- Gets the language of this tree node.
+ Return: ~
+ (boolean)
- Parameters: ~
- • {self}
+LanguageTree:lang() *LanguageTree:lang()*
+ Gets the language of this tree node.
*LanguageTree:language_for_range()*
-LanguageTree:language_for_range({self}, {range})
+LanguageTree:language_for_range({range})
Gets the appropriate language that contains {range}.
Parameters: ~
• {range} (table) `{ start_line, start_col, end_line, end_col }`
- • {self}
Return: ~
- LanguageTree Managing {range}
+ |LanguageTree| Managing {range}
*LanguageTree:named_node_for_range()*
-LanguageTree:named_node_for_range({self}, {range}, {opts})
+LanguageTree:named_node_for_range({range}, {opts})
Gets the smallest named node that contains {range}.
Parameters: ~
@@ -998,48 +1169,58 @@ LanguageTree:named_node_for_range({self}, {range}, {opts})
• {opts} (table|nil) Optional keyword arguments:
• ignore_injections boolean Ignore injected languages
(default true)
- • {self}
Return: ~
- userdata|nil Found |tsnode|
+ |TSNode| | nil Found node
+
+LanguageTree:parse({range}) *LanguageTree:parse()*
+ Recursively parse all regions in the language tree using
+ |treesitter-parsers| for the corresponding languages and run injection
+ queries on the parsed trees to determine whether child trees should be
+ created and parsed.
-LanguageTree:parse({self}) *LanguageTree:parse()*
- Parses all defined regions using a treesitter parser for the language this
- tree represents. This will run the injection query for this language to
- determine if any child languages should be created.
+ Any region with empty range (`{}`, typically only the root tree) is always
+ parsed; otherwise (typically injections) only if it intersects {range} (or
+ if {range} is `true`).
Parameters: ~
- • {self}
+ • {range} boolean|Range|nil: Parse this range in the parser's source.
+ Set to `true` to run a complete parse of the source (Note:
+ Can be slow!) Set to `false|nil` to only parse regions with
+ empty ranges (typically only the root tree without
+ injections).
Return: ~
- userdata[] Table of parsed |tstree|
- (table) Change list
+ table<integer, TSTree>
-LanguageTree:register_cbs({self}, {cbs}) *LanguageTree:register_cbs()*
+ *LanguageTree:register_cbs()*
+LanguageTree:register_cbs({cbs}, {recursive})
Registers callbacks for the |LanguageTree|.
Parameters: ~
- • {cbs} (table) An |nvim_buf_attach()|-like table argument with the
- following handlers:
- • `on_bytes` : see |nvim_buf_attach()|, but this will be called after the parsers callback.
- • `on_changedtree` : a callback that will be called every time
- the tree has syntactical changes. It will only be passed one
- argument, which is a table of the ranges (as node ranges)
- that changed.
- • `on_child_added` : emitted when a child is added to the
- tree.
- • `on_child_removed` : emitted when a child is removed from
- the tree.
- • {self}
-
-LanguageTree:source({self}) *LanguageTree:source()*
+ • {cbs} (table) An |nvim_buf_attach()|-like table argument with
+ the following handlers:
+ • `on_bytes` : see |nvim_buf_attach()|, but this will be called after the parsers callback.
+ • `on_changedtree` : a callback that will be called every
+ time the tree has syntactical changes. It will be
+ passed two arguments: a table of the ranges (as node
+ ranges) that changed and the changed tree.
+ • `on_child_added` : emitted when a child is added to the
+ tree.
+ • `on_child_removed` : emitted when a child is removed
+ from the tree.
+ • `on_detach` : emitted when the buffer is detached, see
+ |nvim_buf_detach_event|. Takes one argument, the number
+ of the buffer.
+ • {recursive} (boolean|nil) Apply callbacks recursively for all
+ children. Any new children will also inherit the
+ callbacks.
+
+LanguageTree:source() *LanguageTree:source()*
Returns the source content of the language tree (bufnr or string).
- Parameters: ~
- • {self}
-
*LanguageTree:tree_for_range()*
-LanguageTree:tree_for_range({self}, {range}, {opts})
+LanguageTree:tree_for_range({range}, {opts})
Gets the tree that contains {range}.
Parameters: ~
@@ -1047,33 +1228,17 @@ LanguageTree:tree_for_range({self}, {range}, {opts})
• {opts} (table|nil) Optional keyword arguments:
• ignore_injections boolean Ignore injected languages
(default true)
- • {self}
Return: ~
- userdata|nil Contained |tstree|
+ TSTree|nil
-LanguageTree:trees({self}) *LanguageTree:trees()*
- Returns all trees this language tree contains. Does not include child
- languages.
-
- Parameters: ~
- • {self}
-
-new({source}, {lang}, {opts}) *vim.treesitter.languagetree.new()*
- A |LanguageTree| holds the treesitter parser for a given language {lang}
- used to parse a buffer. As the buffer may contain injected languages, the LanguageTree needs to store parsers for these child languages as well (which in turn
- may contain child languages themselves, hence the name).
-
- Parameters: ~
- • {source} (number|string) Buffer or a string of text to parse
- • {lang} (string) Root language this tree represents
- • {opts} (table|nil) Optional keyword arguments:
- • injections table Mapping language to injection query
- strings. This is useful for overriding the built-in
- runtime file searching for the injection language query
- per language.
+LanguageTree:trees() *LanguageTree:trees()*
+ Returns all trees of the regions parsed by this parser. Does not include
+ child languages. The result is list-like if
+ • this LanguageTree is the root, in which case the result is empty or a singleton list; or
+ • the root LanguageTree is fully parsed.
Return: ~
- LanguageTree |LanguageTree| parser object
+ table<integer, TSTree>
vim:tw=78:ts=8:sw=4:sts=4:et:ft=help:norl:
diff --git a/runtime/doc/uganda.txt b/runtime/doc/uganda.txt
index d8fc26ad17..42e275c230 100644
--- a/runtime/doc/uganda.txt
+++ b/runtime/doc/uganda.txt
@@ -46,8 +46,8 @@ II) It is allowed to distribute a modified (or extended) version of Vim,
maintainer will do with your changes and under what license they
will be distributed is negotiable. If there has been no negotiation
then this license, or a later version, also applies to your changes.
- The current maintainer is Bram Moolenaar <Bram@vim.org>. If this
- changes it will be announced in appropriate places (most likely
+ The current maintainers are listed here: https://github.com/orgs/vim/people.
+ If this changes it will be announced in appropriate places (most likely
vim.sf.net, www.vim.org and/or comp.editors). When it is completely
impossible to contact the maintainer, the obligation to send him
your changes ceases. Once the maintainer has confirmed that he has
@@ -246,7 +246,7 @@ Credit Card: You can use PayPal to send money with a Credit card. This is
Bram@iccf-holland.org
Others: Transfer to this account if possible:
- ING bank: IBAN: NL95 INGB 0004 5487 74
+ ING bank: IBAN: NL95 INGB 0004 5487 74
Swift code: INGBNL2A
under the name "stichting ICCF Holland", Amersfoort
Checks are not accepted.
diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt
index 3110d0817c..ab99b0446f 100644
--- a/runtime/doc/ui.txt
+++ b/runtime/doc/ui.txt
@@ -52,12 +52,10 @@ with these (optional) keys:
- `ext_termcolors` Use external default colors.
- `term_name` Sets the name of the terminal 'term'.
- `term_colors` Sets the number of supported colors 't_Co'.
-- `term_background` Sets the default value of 'background'.
-- `stdin_fd` Read buffer from `fd` as if it was a stdin pipe
- This option can only used by |--embed| ui,
- see |ui-startup-stdin|.
- `stdin_tty` Tells if `stdin` is a `tty` or not.
- `stdout_tty` Tells if `stdout` is a `tty` or not.
+- `stdin_fd` Read buffer 1 from this fd as if it were stdin |--|.
+ Only from |--embed| UI on startup. |ui-startup-stdin|
+- `stdin_tty` Tells if `stdin` is a `tty` or not.
+- `stdout_tty` Tells if `stdout` is a `tty` or not.
Specifying an unknown option is an error; UIs can check the |api-metadata|
`ui_options` key for supported options.
@@ -79,14 +77,14 @@ Example of a typical "redraw" batch in a single RPC notification: >
[
['grid_resize', [2, 77, 36]],
['grid_line',
- [2, 0, 0, [[' ' , 0, 77]]],
- [2, 1, 0, [['~', 7], [' ', 7, 76]]],
- [2, 9, 0, [['~', 7], [' ', 7, 76]]],
+ [2, 0, 0, [[' ' , 0, 77]], false],
+ [2, 1, 0, [['~', 7], [' ', 7, 76]], false],
+ [2, 9, 0, [['~', 7], [' ', 7, 76]], false],
...
- [2, 35, 0, [['~', 7], [' ', 7, 76]]],
- [1, 36, 0, [['[', 9], ['N'], ['o'], [' '], ['N'], ['a'], ['m'], ['e'], [']']]],
- [1, 36, 9, [[' ', 9, 50]]],
- [1, 36, 59, [['0', 9], [','], ['0'], ['-' ], ['1'], [' ', 9, 10], ['A'], ['l', 9, 2]]]
+ [2, 35, 0, [['~', 7], [' ', 7, 76]], false],
+ [1, 36, 0, [['[', 9], ['N'], ['o'], [' '], ['N'], ['a'], ['m'], ['e'], [']']], false],
+ [1, 36, 9, [[' ', 9, 50]], false],
+ [1, 36, 59, [['0', 9], [','], ['0'], ['-' ], ['1'], [' ', 9, 10], ['A'], ['l', 9, 2]], false]
],
['msg_showmode', [[]]],
['win_pos', [2, 1000, 0, 0, 77, 36]],
@@ -154,11 +152,11 @@ procedure:
An UI can support the native read from stdin feature as invoked with
`command | nvim -` for the builtin TUI. |--|
The embedding process can detect that its stdin is open to a file which
-not is a terminal, just like nvim does. It then needs to forward this fd
+not is a terminal, just like Nvim does. It then needs to forward this fd
to Nvim. As fd=0 is already is used to send rpc data from the embedder to
Nvim, it needs to use some other file descriptor, like fd=3 or higher.
-Then, `stdin_fd` option should be passed to `nvim_ui_attach` and nvim will
+Then, `stdin_fd` option should be passed to `nvim_ui_attach` and Nvim will
implicitly read it as a buffer. This option can only be used when Nvim is
launched with `--embed` option, as described above.
@@ -200,7 +198,7 @@ the editor.
The following keys are deprecated:
`hl_id`: Use `attr_id` instead.
- `hl_lm`: Use `attr_id_lm` instead.
+ `id_lm`: Use `attr_id_lm` instead.
["option_set", name, value] ~
UI-related option changed, where `name` is one of:
@@ -212,6 +210,7 @@ the editor.
- 'guifontwide'
- 'linespace'
- 'mousefocus'
+ - 'mousehide'
- 'mousemoveevent'
- 'pumblend'
- 'showtabline'
@@ -337,7 +336,7 @@ numerical highlight ids to the actual attributes.
Highlights are always transmitted both for both the RGB format and as
terminal 256-color codes, as the `rgb_attr` and `cterm_attr` parameters
- respectively. The |ui-rgb| option has no effect effect anymore.
+ respectively. The |ui-rgb| option has no effect anymore.
Most external UIs will only need to store and use the `rgb_attr`
attributes.
@@ -353,15 +352,15 @@ numerical highlight ids to the actual attributes.
|ui-hlstate| extension explained below.
["hl_group_set", name, hl_id] ~
- The bulitin highlight group `name` was set to use the attributes `hl_id`
+ The built-in highlight group `name` was set to use the attributes `hl_id`
defined by a previous `hl_attr_define` call. This event is not needed
to render the grids which use attribute ids directly, but is useful
- for an UI who want to render its own elements with consistent
- highlighting. For instance an UI using |ui-popupmenu| events, might
+ for a UI who want to render its own elements with consistent
+ highlighting. For instance a UI using |ui-popupmenu| events, might
use the |hl-Pmenu| family of builtin highlights.
*ui-event-grid_line*
-["grid_line", grid, row, col_start, cells] ~
+["grid_line", grid, row, col_start, cells, wrap] ~
Redraw a continuous part of a `row` on a `grid`, starting at the column
`col_start`. `cells` is an array of arrays each with 1 to 3 items:
`[text(, hl_id, repeat)]` . `text` is the UTF-8 text that should be put in
@@ -380,6 +379,12 @@ numerical highlight ids to the actual attributes.
enough to cover the remaining line, will be sent when the rest of the
line should be cleared.
+ `wrap` is a boolean indicating that this line wraps to the next row.
+ When redrawing a line which wraps to the next row, Nvim will emit a
+ `grid_line` event covering the last column of the line with `wrap` set
+ to true, followed immediately by a `grid_line` event starting at the
+ first column of the next row.
+
["grid_clear", grid] ~
Clear a `grid`.
@@ -429,7 +434,7 @@ numerical highlight ids to the actual attributes.
+-------------------------+
<
`cols` is always zero in this version of Nvim, and reserved for future
- use.
+ use.
Note when updating code from |ui-grid-old| events: ranges are
end-exclusive, which is consistent with API conventions, but different
@@ -532,7 +537,7 @@ is not active. New UIs should implement |ui-linegrid| instead.
+-------------------------+
<
==============================================================================
-Detailed highlight state Extension *ui-hlstate*
+Detailed highlight state Extension *ui-hlstate*
Activated by the `ext_hlstate` |ui-option|.
Activates |ui-linegrid| implicitly.
@@ -566,7 +571,7 @@ highlight group is cleared, so `ui_name` can always be used to reliably identify
screen elements, even if no attributes have been applied.
==============================================================================
-Multigrid Events *ui-multigrid*
+Multigrid Events *ui-multigrid*
Activated by the `ext_multigrid` |ui-option|.
Activates |ui-linegrid| implicitly.
@@ -627,15 +632,27 @@ tabs.
When |ui-messages| is active, no message grid is used, and this event
will not be sent.
-["win_viewport", grid, win, topline, botline, curline, curcol] ~
+["win_viewport", grid, win, topline, botline, curline, curcol, line_count, scroll_delta] ~
Indicates the range of buffer text displayed in the window, as well
as the cursor position in the buffer. All positions are zero-based.
`botline` is set to one more than the line count of the buffer, if
- there are filler lines past the end.
+ there are filler lines past the end. `scroll_delta` contains how much
+ the top line of a window moved since `win_viewport` was last emitted.
+ It is intended to be used to implement smooth scrolling. For this
+ purpose it only counts "virtual" or "displayed" lines, so folds
+ only count as one line. When scrolling more than a full screen it is
+ an approximate value.
+
+ All updates, such as `grid_line`, in a batch affects the new viewport,
+ despite the fact that `win_viewport` is received after the updates.
+ Applications implementing, for example, smooth scrolling should take
+ this into account and keep the grid separated from what's displayed on
+ the screen and copy it to the viewport destination once `win_viewport`
+ is received.
["win_extmark", grid, win, ns_id, mark_id, row, col] ~
Updates the position of an extmark which is currently visible in a
- window. Only emitted if the mark has the `ui_watched` attribute.
+ window. Only emitted if the mark has the `ui_watched` attribute.
==============================================================================
Popupmenu Events *ui-popupmenu*
@@ -707,7 +724,7 @@ For command-line 'wildmenu' UI events, activate |ui-popupmenu|.
to distinguish different command lines active at the same time. The
first invoked command line has level 1, the next recursively-invoked
prompt has level 2. A command line invoked from the |cmdline-window|
- has a higher level than than the edited command line.
+ has a higher level than the edited command line.
["cmdline_pos", pos, level] ~
Change the cursor position in the cmdline.
@@ -809,5 +826,8 @@ events, which the UI must handle.
Sent when |:messages| command is invoked. History is sent as a list of
entries, where each entry is a `[kind, content]` tuple.
+["msg_history_clear"] ~
+ Clear the |:messages| history.
+
==============================================================================
vim:tw=78:ts=8:noet:ft=help:norl:
diff --git a/runtime/doc/undo.txt b/runtime/doc/undo.txt
index 98ab60c7e7..3fcc196250 100644
--- a/runtime/doc/undo.txt
+++ b/runtime/doc/undo.txt
@@ -114,7 +114,17 @@ use CTRL-G u. This is useful if you want an insert command to be undoable in
parts. E.g., for each sentence. |i_CTRL-G_u|
Setting the value of 'undolevels' also closes the undo block. Even when the
-new value is equal to the old value: >
+new value is equal to the old value. Use `g:undolevels` to explicitly read
+and write only the global value of 'undolevels'. >
+ let &g:undolevels = &g:undolevels
+
+Note that the similar-looking assignment `let &undolevels=&undolevels` does not
+preserve the global option value of 'undolevels' in the event that the local
+option has been set to a different value. For example: >
+ " Start with different global and local values for 'undolevels'.
+ let &g:undolevels = 1000
+ let &l:undolevels = 2000
+ " This assignment changes the global option to 2000:
let &undolevels = &undolevels
==============================================================================
@@ -255,7 +265,7 @@ ignored if its owner differs from the owner of the edited file, except when
the owner of the undo file is the current user. Set 'verbose' to get a
message about that when opening a file.
-Location of the undo files is controlled by the 'undodir' option, by default
+Location of the undo files is controlled by the 'undodir' option, by default
they are saved to the dedicated directory in the application data folder.
You can also save and restore undo histories by using ":wundo" and ":rundo"
@@ -351,12 +361,20 @@ undo is possible. Use this if you are running out of memory.
When you set 'undolevels' to -1 the undo information is not immediately
cleared, this happens at the next change. To force clearing the undo
information you can use these commands: >
- :let old_undolevels = &undolevels
- :set undolevels=-1
+ :let old_undolevels = &l:undolevels
+ :setlocal undolevels=-1
:exe "normal a \<BS>\<Esc>"
- :let &undolevels = old_undolevels
+ :let &l:undolevels = old_undolevels
:unlet old_undolevels
+Note use of `&l:undolevels` to explicitly read the local value of 'undolevels'
+and the use of `:setlocal` to change only the local option (which takes
+precedence over the corresponding global option value). Saving the option value
+via the use of `&undolevels` is unpredictable; it reads either the local value
+(if one has been set) or the global value (otherwise). Also, if a local value
+has been set, changing the option via `:set undolevels` will change both the
+global and local values, requiring extra work to save and restore both values.
+
Marks for the buffer ('a to 'z) are also saved and restored, together with the
text.
diff --git a/runtime/doc/userfunc.txt b/runtime/doc/userfunc.txt
index 9c428175bb..b0384df454 100644
--- a/runtime/doc/userfunc.txt
+++ b/runtime/doc/userfunc.txt
@@ -44,6 +44,13 @@ functions.
unless "!" is given.
{name} may be a |Dictionary| |Funcref| entry: >
:function dict.init
+< Note that {name} is not an expression, you cannot use
+ a variable that is a function reference. You can use
+ this dirty trick to list the function referred to with
+ variable "Funcref": >
+ let g:MyFuncref = Funcref
+ func g:MyFuncref
+ unlet g:MyFuncref
:fu[nction] /{pattern} List functions with a name matching {pattern}.
Example that lists all functions ending with "File": >
@@ -72,8 +79,7 @@ See |:verbose-cmd| for more information.
name has a colon in the name, e.g. for "foo:bar()".
Before that patch no error was given).
- {name} can also be a |Dictionary| entry that is a
- |Funcref|: >
+ {name} may be a |Dictionary| |Funcref| entry: >
:function dict.init(arg)
< "dict" must be an existing dictionary. The entry
"init" is added if it didn't exist yet. Otherwise [!]
@@ -234,9 +240,10 @@ Example: >
call Something('key', 20) "key: 20"
The argument default expressions are evaluated at the time of the function
-call, not definition. Thus it is possible to use an expression which is
-invalid the moment the function is defined. The expressions are also only
-evaluated when arguments are not specified during a call.
+call, not when the function is defined. Thus it is possible to use an
+expression which is invalid the moment the function is defined. The
+expressions are also only evaluated when arguments are not specified during a
+call.
*E989*
Optional arguments with default expressions must occur after any mandatory
@@ -349,10 +356,69 @@ A function can also be called as part of evaluating an expression or when it
is used as a method: >
let x = GetList()
let y = GetList()->Filter()
+<
+==============================================================================
+3. Cleaning up in a function ~
+ *:defer*
+:defer {func}({args}) Call {func} when the current function is done.
+ {args} are evaluated here.
+
+Quite often a command in a function has a global effect, which must be undone
+when the function finishes. Handling this in all kinds of situations can be a
+hassle. Especially when an unexpected error is encountered. This can be done
+with `try` / `finally` blocks, but this gets complicated when there is more
+than one.
+
+A much simpler solution is using `defer`. It schedules a function call when
+the function is returning, no matter if there is an error. Example: >
+ func Filter(text) abort
+ call writefile(a:text, 'Tempfile')
+ call system('filter < Tempfile > Outfile')
+ call Handle('Outfile')
+ call delete('Tempfile')
+ call delete('Outfile')
+ endfunc
+
+Here 'Tempfile' and 'Outfile' will not be deleted if something causes the
+function to abort. `:defer` can be used to avoid that: >
+ func Filter(text) abort
+ call writefile(a:text, 'Tempfile')
+ defer delete('Tempfile')
+ defer delete('Outfile')
+ call system('filter < Tempfile > Outfile')
+ call Handle('Outfile')
+ endfunc
+
+Note that deleting "Outfile" is scheduled before calling `system()`, since it
+can be created even when `system()` fails.
+
+The deferred functions are called in reverse order, the last one added is
+executed first. A useless example: >
+ func Useless() abort
+ for s in range(3)
+ defer execute('echomsg "number ' .. s .. '"')
+ endfor
+ endfunc
+
+Now `:messages` shows:
+ number 2
+ number 1
+ number 0
+
+Any return value of the deferred function is discarded. The function cannot
+be followed by anything, such as "->func" or ".member". Currently `:defer
+GetArg()->TheFunc()` does not work, it may work in a later version.
+
+Errors are reported but do not cause aborting execution of deferred functions
+or altering execution outside of deferred functions.
+
+No range is accepted. The function can be a partial with extra arguments, but
+not with a dictionary. *E1300*
==============================================================================
-3. Automatically loading functions ~
+
+4. Automatically loading functions ~
*autoload-functions*
When using many or large functions, it's possible to automatically define them
only when they are used. There are two methods: with an autocommand and with
diff --git a/runtime/doc/usr_01.txt b/runtime/doc/usr_01.txt
index f0e2462fae..6b94806941 100644
--- a/runtime/doc/usr_01.txt
+++ b/runtime/doc/usr_01.txt
@@ -81,7 +81,7 @@ from within nvim. The tutorial will lead you from that point. Have fun!
==============================================================================
*01.4* Copyright *manual-copyright*
-The Vim user manual and reference manual are Copyright (c) 1988-2003 by Bram
+The Vim user manual and reference manual are Copyright (c) 1988 by Bram
Moolenaar. This material may be distributed only subject to the terms and
conditions set forth in the Open Publication License, v1.0 or later. The
latest version is presently available at:
diff --git a/runtime/doc/usr_02.txt b/runtime/doc/usr_02.txt
index 11afe39742..1fc612de26 100644
--- a/runtime/doc/usr_02.txt
+++ b/runtime/doc/usr_02.txt
@@ -40,7 +40,7 @@ blank window. This is what your screen will look like:
|~ |
|~ |
|~ |
- |"file.txt" [New file] |
+ |"file.txt" [New] |
+---------------------------------------+
('#' is the cursor position.)
<
@@ -495,7 +495,7 @@ You can use the error ID at the start to find help about it: >
:help E37
-Summary: *help-summary* >
+Summary: *help-summary* >
1) Use Ctrl-D after typing a topic and let Vim show all available topics.
Or press Tab to complete: >
diff --git a/runtime/doc/usr_03.txt b/runtime/doc/usr_03.txt
index 2b0d40ba32..b76ca18d20 100644
--- a/runtime/doc/usr_03.txt
+++ b/runtime/doc/usr_03.txt
@@ -173,6 +173,8 @@ one. Thus if the cursor is at the start of the line of the previous example,
---+---------------->
%
+Other ways to move around code can be found in |usr_29.txt|.
+
==============================================================================
*03.5* Moving to a specific line
diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt
index 24d6185eae..076a50c582 100644
--- a/runtime/doc/usr_05.txt
+++ b/runtime/doc/usr_05.txt
@@ -10,7 +10,7 @@ make Vim start with options set to different values. Add plugins to extend
Vim's capabilities. Or define your own macros.
|05.1| The vimrc file
-|05.2| The example vimrc file explained
+|05.2| Example vimrc contents
|05.3| Simple mappings
|05.4| Adding a package
|05.5| Adding a plugin
@@ -27,10 +27,10 @@ Table of contents: |usr_toc.txt|
You probably got tired of typing commands that you use very often. To start
Vim with all your favorite option settings and mappings, you write them in
-what is called the init.vim file. Vim executes the commands in this file when
+what is called the init.vim file. Vim executes the commands in this file when
it starts up.
-If you already have a init.vim file (e.g., when your sysadmin has one setup
+If you already have a init.vim file (e.g., when your sysadmin has one setup
for you), you can edit it this way: >
:edit $MYVIMRC
@@ -56,80 +56,32 @@ This chapter only explains the most basic items. For more information on how
to write a Vim script file: |usr_41.txt|.
==============================================================================
-*05.2* The example vimrc file explained *vimrc_example.vim*
+*05.2* Example vimrc contents *vimrc_example.vim*
In the first chapter was explained how to create a vimrc file. >
:exe 'edit' stdpath('config').'/init.vim'
-In this section we will explain the various commands used in this file. This
-will give you hints about how to set up your own preferences. Not everything
-will be explained though. Use the ":help" command to find out more.
-
->
- set backspace=indent,eol,start
-
-This specifies where in Insert mode the <BS> is allowed to delete the
-character in front of the cursor. The three items, separated by commas, tell
-Vim to delete the white space at the start of the line, a line break and the
-character before where Insert mode started.
->
-
- set autoindent
-
-This makes Vim use the indent of the previous line for a newly created line.
-Thus there is the same amount of white space before the new line. For example
-when pressing <Enter> in Insert mode, and when using the "o" command to open a
-new line.
+In this section we will explain the various commands that can be specified in
+this file. This will give you hints about how to set up your own preferences.
+Not everything will be explained though. Use the ":help" command to find out
+more.
>
-
set backup
This tells Vim to keep a backup copy of a file when overwriting it. The backup
file will have the same name as the original file with "~" added. See |07.4|
>
-
set history=50
-
+<
Keep 50 commands and 50 search patterns in the history. Use another number if
you want to remember fewer or more lines.
>
-
- set ruler
-
-Always display the current cursor position in the lower right corner of the
-Vim window.
-
->
- set showcmd
-
-Display an incomplete command in the lower right corner of the Vim window,
-left of the ruler. For example, when you type "2f", Vim is waiting for you to
-type the character to find and "2f" is displayed. When you press "w" next,
-the "2fw" command is executed and the displayed "2f" is removed.
->
- +-------------------------------------------------+
- |text in the Vim window |
- |~ |
- |~ |
- |-- VISUAL -- 2f 43,8 17% |
- +-------------------------------------------------+
- ^^^^^^^^^^^ ^^^^^^^^ ^^^^^^^^^^
- 'showmode' 'showcmd' 'ruler'
-
->
- set incsearch
-<
-Display matches for a search pattern while you type.
-
->
map Q gq
This defines a key mapping. More about that in the next section. This
-defines the "Q" command to do formatting with the "gq" operator. This is how
-it worked before Vim 5.0. Otherwise the "Q" command repeats the last recorded
-register.
-
+defines the "Q" command to do formatting with the "gq" operator. Otherwise the
+"Q" command repeats the last recorded register.
>
vnoremap _g y:exe "grep /" .. escape(@", '\\/') .. "/ *.c *.h"<CR>
@@ -138,14 +90,8 @@ This is a complicated mapping. You can see that mappings can be used to do
quite complicated things. Still, it is just a sequence of commands that are
executed like you typed them.
+ *vimrc-filetype*
>
- set hlsearch
-
-This option tells Vim to highlight matches with the last used search pattern.
-The "if" command is very useful to set options only when some condition is
-met. More about that in |usr_41.txt|.
-
- *vimrc-filetype* >
filetype plugin indent on
This switches on three very clever mechanisms:
@@ -172,15 +118,27 @@ This switches on three very clever mechanisms:
*restore-cursor* *last-position-jump* >
- autocmd BufRead * autocmd FileType <buffer> ++once
- \ if &ft !~# 'commit\|rebase' && line("'\"") > 1 && line("'\"") <= line("$") | exe 'normal! g`"' | endif
+ augroup RestoreCursor
+ autocmd!
+ autocmd BufRead * autocmd FileType <buffer> ++once
+ \ let s:line = line("'\"")
+ \ | if s:line >= 1 && s:line <= line("$") && &filetype !~# 'commit'
+ \ && index(['xxd', 'gitrebase'], &filetype) == -1
+ \ | execute "normal! g`\""
+ \ | endif
+ augroup END
Another autocommand. This time it is used after reading any file. The
complicated stuff after it checks if the '" mark is defined, and jumps to it
-if so. The backslash at the start of a line is used to continue the command
-from the previous line. That avoids a line getting very long.
-See |line-continuation|. This only works in a Vim script file, not when
-typing commands at the command-line.
+if so. It doesn't do that for a commit or rebase message, which are likely
+a different one than last time, and when using xxd(1) to filter and edit
+binary files, which transforms input files back and forth, causing them to
+have dual nature, so to speak. See also |using-xxd|.
+
+The backslash at the start of a line is used to continue the command from the
+previous line. That avoids a line getting very long. See |line-continuation|.
+This only works in a Vim script file, not when typing commands at the
+command line.
>
command DiffOrig vert new | set bt=nofile | r ++edit # | 0d_ | diffthis
@@ -244,26 +202,21 @@ The ":map" command (with no arguments) lists your current mappings. At
least the ones for Normal mode. More about mappings in section |40.1|.
==============================================================================
-*05.4* Adding a package *add-package* *vimball-install*
-
-A package is a set of files that you can add to Vim. There are two kinds of
-packages: optional and automatically loaded on startup.
+*05.4* Adding a package *add-package*
-The Vim distribution comes with a few packages that you can optionally use.
-For example, the vimball plugin. This plugin supports creating and using
-vimballs (self-installing Vim plugin archives).
+You may use |:packadd| to enable packages on demand. This is useful for plugins
+you want to enable only sometimes. To enable `example_package`, use the
+following command: >
+ packadd example_package
-To start using the vimball plugin, add one line to your vimrc file: >
- packadd vimball
-
-That's all! You can also type the command to try it out. Now you can find
-help about this plugin: >
- :help vimball
+That's all! Now you can find help about this plugin: >
+ :help example_package
This works, because when `:packadd` loaded the plugin it also added the
-package directory in 'runtimepath', so that the help file can be found. The
-tags for vimball's help are already created. If you need to generate the help
-tags for a package, see the `:helptags` command.
+package directory in 'runtimepath', so that the help file can be found.
+
+A package is a set of files that you can add to Vim. There are two kinds of
+packages: optional and automatically loaded on startup.
You can find packages on the Internet in various places. It usually comes as
an archive or as a repository. For an archive you can follow these steps:
@@ -273,7 +226,7 @@ an archive or as a repository. For an archive you can follow these steps:
package.
2. unpack the archive in that directory. This assumes the top
directory in the archive is "start": >
- cd ~/.local/share/nvim/site/pack/fancy
+ cd ~/.local/share/nvim/site/pack/fancy
unzip /tmp/fancy.zip
< If the archive layout is different make sure that you end up with a
path like this:
@@ -342,7 +295,7 @@ That's all! Now you can use the commands defined in this plugin.
Instead of putting plugins directly into the plugin/ directory, you may
better organize them by putting them into subdirectories under plugin/.
-As an example, consider using "~/.local/share/nvim/site/plugin/perl/*.vim" for
+As an example, consider using "~/.local/share/nvim/site/plugin/perl/*.vim" for
all your Perl plugins.
diff --git a/runtime/doc/usr_09.txt b/runtime/doc/usr_09.txt
index 8084d13b5d..da9a404420 100644
--- a/runtime/doc/usr_09.txt
+++ b/runtime/doc/usr_09.txt
@@ -63,10 +63,10 @@ Vim will set the title to show the name of the current file. First comes the
name of the file. Then some special characters and the directory of the file
in parens. These special characters can be present:
- - The file cannot be modified (e.g., a help file)
- + The file contains changes
- = The file is read-only
- =+ The file is read-only, contains changes anyway
+ • - The file cannot be modified (e.g., a help file)
+ • + The file contains changes
+ • = The file is read-only
+ • =+ The file is read-only, contains changes anyway
If nothing is shown you have an ordinary, unchanged file.
@@ -124,41 +124,13 @@ This adds the 'l' flag to 'guioptions'.
Standards are wonderful. In Microsoft Windows, you can use the mouse to
select text in a standard manner. The X Window system also has a standard
system for using the mouse. Unfortunately, these two standards are not the
-same.
- Fortunately, you can customize Vim. You can make the behavior of the mouse
-work like an X Window system mouse or a Microsoft Windows mouse. The following
-command makes the mouse behave like an X Window mouse: >
+same. Fortunately, you can customize Vim.
- :behave xterm
-
-The following command makes the mouse work like a Microsoft Windows mouse: >
-
- :behave mswin
-
-The default behavior of the mouse on Unix systems is xterm. The default
-behavior on Windows systems is selected during the installation process. For
-details about what the two behaviors are, see |:behave|. Here follows a
-summary.
-
-
-XTERM MOUSE BEHAVIOR
-
-Left mouse click position the cursor
-Left mouse drag select text in Visual mode
-Middle mouse click paste text from the clipboard
-Right mouse click extend the selected text until the mouse
- pointer
-
-
-MSWIN MOUSE BEHAVIOR
-
-Left mouse click position the cursor
-Left mouse drag select text in Select mode (see |09.4|)
-Left mouse click, with Shift extend the selected text until the mouse
- pointer
-Middle mouse click paste text from the clipboard
-Right mouse click display a pop-up menu
+The following commands makes the mouse work more like a Microsoft Windows mouse: >
+ set selection=exclusive
+ set selectmode=mouse,key
+ set keymodel=startsel,stopsel
The mouse can be further tuned. Check out these options if you want to change
the way how the mouse works:
@@ -251,7 +223,7 @@ Remember, "y" is yank, which is Vim's copy command.
"+P
It's the same as for the current selection, but uses the plus (+) register
-instead of the star (*) register.
+instead of the star "*" register.
==============================================================================
*09.4* Select mode
diff --git a/runtime/doc/usr_11.txt b/runtime/doc/usr_11.txt
index 361fe51caa..1fa7fb6f77 100644
--- a/runtime/doc/usr_11.txt
+++ b/runtime/doc/usr_11.txt
@@ -82,9 +82,8 @@ You must be in the right directory, otherwise Vim can't find the swap file.
==============================================================================
*11.2* Where is the swap file?
-Vim can store the swap file in several places. Normally it is in the same
-directory as the original file. To find it, change to the directory of the
-file, and use: >
+Vim can store the swap file in several places. To find it, change to the
+directory of the file, and use: >
vim -r
diff --git a/runtime/doc/usr_12.txt b/runtime/doc/usr_12.txt
index 51a25b1593..dd1d62bb52 100644
--- a/runtime/doc/usr_12.txt
+++ b/runtime/doc/usr_12.txt
@@ -59,7 +59,7 @@ playback.
Let's assume you have a directory with C++ files, all ending in ".cpp".
There is a function called "GetResp" that you want to rename to "GetAnswer".
- vim *.cpp Start Vim, defining the argument list to
+ vim `*.cpp` Start Vim, defining the argument list to
contain all the C++ files. You are now in the
first file.
qq Start recording into the q register
@@ -331,7 +331,7 @@ program files, for example, enter the following command: >
:grep error_string *.c
This causes Vim to search for the string "error_string" in all the specified
-files (*.c). The editor will now open the first file where a match is found
+files (`*.c`). The editor will now open the first file where a match is found
and position the cursor on the first matching line. To go to the next
matching line (no matter in what file it is), use the ":cnext" command. To go
to the previous match, use the ":cprev" command. Use ":clist" to see all the
diff --git a/runtime/doc/usr_21.txt b/runtime/doc/usr_21.txt
index 191d333f3d..4ae72bbe84 100644
--- a/runtime/doc/usr_21.txt
+++ b/runtime/doc/usr_21.txt
@@ -82,7 +82,7 @@ After editing for a while you will have text in registers, marks in various
files, a command line history filled with carefully crafted commands. When
you exit Vim all of this is lost. But you can get it back!
-The ShaDa (abbreviation of SHAred DAta) file is designed to store status
+The ShaDa (abbreviation of SHAred DAta) file is designed to store status
information:
Command-line and Search pattern history
@@ -218,8 +218,8 @@ Obviously, the "w" stands for "write" and the "r" for "read".
The ! character is used by ":wshada" to forcefully overwrite an existing
file. When it is omitted, and the file exists, the information is merged into
the file.
- The ! character used for ":rshada" means that all the information in ShaDa
-file has priority over existing information, this may overwrite it. Without
+ The ! character used for ":rshada" means that all the information in ShaDa
+file has priority over existing information, this may overwrite it. Without
the ! only information that wasn't set is used.
These commands can also be used to store info and use it again later. You
could make a directory full of ShaDa files, each containing info for a
@@ -277,8 +277,8 @@ example, use: >
SESSION HERE, SESSION THERE
The obvious way to use sessions is when working on different projects.
-Suppose you store your session files in the directory "~/.config/nvim". You
-are currently working on the "secret" project and have to switch to the
+Suppose you store your session files in the directory "~/.config/nvim". You
+are currently working on the "secret" project and have to switch to the
"boring" project: >
:wall
@@ -426,7 +426,7 @@ a line of text that tells Vim the values of options, to be used in this file
only.
A typical example is a C program where you make indents by a multiple of 4
spaces. This requires setting the 'shiftwidth' option to 4. This modeline
-will do that:
+will do that: >
/* vim:set shiftwidth=4: */ ~
diff --git a/runtime/doc/usr_22.txt b/runtime/doc/usr_22.txt
index 539bda3980..ae569dc6a0 100644
--- a/runtime/doc/usr_22.txt
+++ b/runtime/doc/usr_22.txt
@@ -50,8 +50,8 @@ You can see these items:
1. The name of the browsing tool and its version number
2. The name of the browsing directory
3. The method of sorting (may be by name, time, or size)
-4. How names are to be sorted (directories first, then *.h files,
- *.c files, etc)
+4. How names are to be sorted (directories first, then `*.h` files,
+ `*.c` files, etc)
5. How to get help (use the <F1> key), and an abbreviated listing
of available commands
6. A listing of files, including "../", which allows one to list
@@ -77,25 +77,25 @@ browser. This is what you get: >
9. Directory Browsing netrw-browse netrw-dir netrw-list netrw-help
MAPS netrw-maps
- <F1>.............Help.......................................|netrw-help|
- <cr>.............Browsing...................................|netrw-cr|
- <del>............Deleting Files or Directories..............|netrw-delete|
- -................Going Up...................................|netrw--|
- a................Hiding Files or Directories................|netrw-a|
- mb...............Bookmarking a Directory....................|netrw-mb|
- gb...............Changing to a Bookmarked Directory.........|netrw-gb|
- cd...............Make Browsing Directory The Current Dir....|netrw-c|
- d................Make A New Directory.......................|netrw-d|
- D................Deleting Files or Directories..............|netrw-D|
- <c-h>............Edit File/Directory Hiding List............|netrw-ctrl-h|
- i................Change Listing Style.......................|netrw-i|
- <c-l>............Refreshing the Listing.....................|netrw-ctrl-l|
- o................Browsing with a Horizontal Split...........|netrw-o|
- p................Use Preview Window.........................|netrw-p|
- P................Edit in Previous Window....................|netrw-p|
- q................Listing Bookmarks and History..............|netrw-qb|
- r................Reversing Sorting Order....................|netrw-r|
-< (etc)
+ <F1>.............Help.......................................|netrw-help|
+ <cr>.............Browsing...................................|netrw-cr|
+ <del>............Deleting Files or Directories..............|netrw-delete|
+ -................Going Up...................................|netrw--|
+ a................Hiding Files or Directories................|netrw-a|
+ mb...............Bookmarking a Directory....................|netrw-mb|
+ gb...............Changing to a Bookmarked Directory.........|netrw-gb|
+ cd...............Make Browsing Directory The Current Dir....|netrw-c|
+ d................Make A New Directory.......................|netrw-d|
+ D................Deleting Files or Directories..............|netrw-D|
+ <c-h>............Edit File/Directory Hiding List............|netrw-ctrl-h|
+ i................Change Listing Style.......................|netrw-i|
+ <c-l>............Refreshing the Listing.....................|netrw-ctrl-l|
+ o................Browsing with a Horizontal Split...........|netrw-o|
+ p................Use Preview Window.........................|netrw-p|
+ P................Edit in Previous Window....................|netrw-p|
+ q................Listing Bookmarks and History..............|netrw-qb|
+ r................Reversing Sorting Order....................|netrw-r|
+< (etc)
The <F1> key thus brings you to a netrw directory browsing contents help page.
It's a regular help page; use the usual |CTRL-]| to jump to tagged help items
@@ -106,7 +106,7 @@ To select files for display and editing: (with the cursor is atop a filename)
<enter> Open the file in the current window. |netrw-cr|
o Horizontally split window and display file |netrw-o|
v Vertically split window and display file |netrw-v|
- p Use the |preview-window| |netrw-p|
+ p Use the |preview-window| |netrw-p|
P Edit in the previous window |netrw-P|
t Open file in a new tab |netrw-t|
diff --git a/runtime/doc/usr_24.txt b/runtime/doc/usr_24.txt
index efda2bc33d..db6c8e45d0 100644
--- a/runtime/doc/usr_24.txt
+++ b/runtime/doc/usr_24.txt
@@ -241,11 +241,11 @@ some other editors it's called intellisense, but that is a trademark.
The key to Omni completion is CTRL-X CTRL-O. Obviously the O stands for Omni
here, so that you can remember it easier. Let's use an example for editing C
-source:
+source: >
- { ~
- struct foo *p; ~
- p-> ~
+ {
+ struct foo *p;
+ p->
The cursor is after "p->". Now type CTRL-X CTRL-O. Vim will offer you a list
of alternatives, which are the items that "struct foo" contains. That is
@@ -270,13 +270,13 @@ work.
If you press CTRL-A, the editor inserts the text you typed the last time you
were in Insert mode.
- Assume, for example, that you have a file that begins with the following:
+ Assume, for example, that you have a file that begins with the following: >
"file.h" ~
/* Main program begins */ ~
You edit this file by inserting "#include " at the beginning of the first
-line:
+line: >
#include "file.h" ~
/* Main program begins */ ~
@@ -286,13 +286,13 @@ now start to insert a new "#include" line. So you type: >
i CTRL-A
-The result is as follows:
+The result is as follows: >
#include "file.h" ~
#include /* Main program begins */ ~
The "#include " was inserted because CTRL-A inserts the text of the previous
-insert. Now you type "main.h"<Enter> to finish the line:
+insert. Now you type "main.h"<Enter> to finish the line: >
#include "file.h" ~
@@ -429,7 +429,7 @@ mistake.
LISTING ABBREVIATIONS
-The ":abbreviate" command lists the abbreviations:
+The ":abbreviate" command lists the abbreviations: >
:abbreviate
i #e ****************************************/
diff --git a/runtime/doc/usr_28.txt b/runtime/doc/usr_28.txt
index 86aa20597e..96f635a307 100644
--- a/runtime/doc/usr_28.txt
+++ b/runtime/doc/usr_28.txt
@@ -277,7 +277,7 @@ Try it: >
:set foldmethod=marker
-Example text, as it could appear in a C program:
+Example text, as it could appear in a C program: >
/* foobar () {{{ */
int foobar()
@@ -292,7 +292,7 @@ Notice that the folded line will display the text before the marker. This is
very useful to tell what the fold contains.
It's quite annoying when the markers don't pair up correctly after moving some
-lines around. This can be avoided by using numbered markers. Example:
+lines around. This can be avoided by using numbered markers. Example: >
/* global variables {{{1 */
int varA, varB;
diff --git a/runtime/doc/usr_29.txt b/runtime/doc/usr_29.txt
index 751cb9a902..dd8598a3a0 100644
--- a/runtime/doc/usr_29.txt
+++ b/runtime/doc/usr_29.txt
@@ -307,9 +307,9 @@ tags file. Example: >
:psearch popen
This will show the "stdio.h" file in the preview window, with the function
-prototype for popen():
+prototype for popen(): >c
- FILE *popen __P((const char *, const char *)); ~
+ FILE *popen __P((const char *, const char *));
You can specify the height of the preview window, when it is opened, with the
'previewheight' option.
@@ -319,13 +319,13 @@ You can specify the height of the preview window, when it is opened, with the
Since a program is structured, Vim can recognize items in it. Specific
commands can be used to move around.
- C programs often contain constructs like this:
+ C programs often contain constructs like this: >c
- #ifdef USE_POPEN ~
- fd = popen("ls", "r") ~
- #else ~
- fd = fopen("tmp", "w") ~
- #endif ~
+ #ifdef USE_POPEN
+ fd = popen("ls", "r")
+ #else
+ fd = fopen("tmp", "w")
+ #endif
But then much longer, and possibly nested. Position the cursor on the
"#ifdef" and press %. Vim will jump to the "#else". Pressing % again takes
@@ -361,7 +361,7 @@ MOVING IN CODE BLOCKS
In C code blocks are enclosed in {}. These can get pretty long. To move to
the start of the outer block use the "[[" command. Use "][" to find the end.
This assumes that the "{" and "}" are in the first column.
- The "[{" command moves to the start of the current block. It skips over
+ The [{ command moves to the start of the current block. It skips over
pairs of {} at the same level. "]}" jumps to the end.
An overview:
@@ -410,7 +410,7 @@ That also works when they are many lines apart.
MOVING IN BRACES
-The "[(" and "])" commands work similar to "[{" and "]}", except that they
+The [( and ]) commands work similar to [{ and ]}, except that they
work on () pairs instead of {} pairs.
>
[(
@@ -424,7 +424,7 @@ work on () pairs instead of {} pairs.
MOVING IN COMMENTS
To move back to the start of a comment use "[/". Move forward to the end of a
-comment with "]/". This only works for /* - */ comments.
+comment with "]/". This only works for `/* - */` comments.
>
+-> +-> /*
| [/ | * A comment about --+
@@ -446,10 +446,10 @@ You are editing a C program and wonder if a variable is declared as "int" or
Vim will list the matching lines it can find. Not only in the current file,
but also in all included files (and files included in them, etc.). The result
-looks like this:
+looks like this: >
- structs.h ~
- 1: 29 unsigned column; /* column number */ ~
+ structs.h
+ 1: 29 unsigned column; /* column number */
The advantage over using tags or the preview window is that included files are
searched. In most cases this results in the right declaration to be found.
diff --git a/runtime/doc/usr_30.txt b/runtime/doc/usr_30.txt
index 7e7b3b21f4..a0d22482c3 100644
--- a/runtime/doc/usr_30.txt
+++ b/runtime/doc/usr_30.txt
@@ -517,12 +517,12 @@ The other way around works just as well: >
One of the great things about Vim is that it understands comments. You can
ask Vim to format a comment and it will do the right thing.
- Suppose, for example, that you have the following comment:
+ Suppose, for example, that you have the following comment: >c
- /* ~
- * This is a test ~
- * of the text formatting. ~
- */ ~
+ /*
+ * This is a test
+ * of the text formatting.
+ */
You then ask Vim to format it by positioning the cursor at the start of the
comment and type: >
@@ -530,33 +530,33 @@ comment and type: >
gq]/
"gq" is the operator to format text. "]/" is the motion that takes you to the
-end of a comment. The result is:
+end of a comment. The result is: >c
- /* ~
- * This is a test of the text formatting. ~
- */ ~
+ /*
+ * This is a test of the text formatting.
+ */
Notice that Vim properly handled the beginning of each line.
An alternative is to select the text that is to be formatted in Visual mode
and type "gq".
To add a new line to the comment, position the cursor on the middle line and
-press "o". The result looks like this:
+press "o". The result looks like this: >c
- /* ~
- * This is a test of the text formatting. ~
- * ~
- */ ~
+ /*
+ * This is a test of the text formatting.
+ *
+ */
Vim has automatically inserted a star and a space for you. Now you can type
the comment text. When it gets longer than 'textwidth', Vim will break the
-line. Again, the star is inserted automatically:
+line. Again, the star is inserted automatically: >c
- /* ~
- * This is a test of the text formatting. ~
- * Typing a lot of text here will make Vim ~
- * break ~
- */ ~
+ /*
+ * This is a test of the text formatting.
+ * Typing a lot of text here will make Vim
+ * break
+ */
For this to work some flags must be present in 'formatoptions':
diff --git a/runtime/doc/usr_40.txt b/runtime/doc/usr_40.txt
index 8befb15528..b0d53e0d8c 100644
--- a/runtime/doc/usr_40.txt
+++ b/runtime/doc/usr_40.txt
@@ -159,7 +159,7 @@ RECURSIVE MAPPING
When a mapping triggers itself, it will run forever. This can be used to
repeat an action an unlimited number of times.
For example, you have a list of files that contain a version number in the
-first line. You edit these files with "vim *.txt". You are now editing the
+first line. You edit these files with `vim *.txt`. You are now editing the
first file. Define this mapping: >
:map ,, :s/5.1/5.2/<CR>:wnext<CR>,,
@@ -501,7 +501,7 @@ See |autocmd-events| for a complete list of events.
PATTERNS
The {file-pattern} argument can actually be a comma-separated list of file
-patterns. For example: "*.c,*.h" matches files ending in ".c" and ".h".
+patterns. For example: `*.c,*.h` matches files ending in ".c" and ".h".
The usual file wildcards can be used. Here is a summary of the most often
used ones:
@@ -622,7 +622,7 @@ Example: >
:autocmd BufReadPost *.log normal G
-This will make the cursor jump to the last line of *.log files when you start
+This will make the cursor jump to the last line of `*.log` files when you start
to edit it.
Using the ":normal" command is a bit tricky. First of all, make sure its
argument is a complete command, including all the arguments. When you use "i"
diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt
index 910aebae70..e206a804f4 100644
--- a/runtime/doc/usr_41.txt
+++ b/runtime/doc/usr_41.txt
@@ -588,7 +588,7 @@ after the substitute() call.
FUNCTIONS *function-list*
There are many functions. We will mention them here, grouped by what they are
-used for. You can find an alphabetical list here: |builtin-function-list|.
+used for. You can find an alphabetical list here: |builtin-function-details|.
Use CTRL-] on the function name to jump to detailed help on it.
String manipulation: *string-functions*
@@ -621,14 +621,18 @@ String manipulation: *string-functions*
strlen() length of a string in bytes
strcharlen() length of a string in characters
strchars() number of characters in a string
+ strutf16len() number of UTF-16 code units in a string
strwidth() size of string when displayed
strdisplaywidth() size of string when displayed, deals with tabs
setcellwidths() set character cell width overrides
getcellwidths() get character cell width overrides
+ reverse() reverse the order of characters in a string
substitute() substitute a pattern match with a string
submatch() get a specific match in ":s" and substitute()
strpart() get part of a string using byte index
strcharpart() get part of a string using char index
+ slice() take a slice of a string, using char index in
+ Vim9 script
strgetchar() get character from a string using char index
expand() expand special keywords
expandcmd() expand a command like done for `:edit`
@@ -636,6 +640,7 @@ String manipulation: *string-functions*
byteidx() byte index of a character in a string
byteidxcomp() like byteidx() but count composing characters
charidx() character index of a byte in a string
+ utf16idx() UTF-16 index of a byte in a string
repeat() repeat a string multiple times
eval() evaluate a string expression
execute() execute an Ex command and get the output
@@ -650,26 +655,32 @@ List manipulation: *list-functions*
insert() insert an item somewhere in a List
add() append an item to a List
extend() append a List to a List
+ extendnew() make a new List and append items
remove() remove one or more items from a List
copy() make a shallow copy of a List
deepcopy() make a full copy of a List
filter() remove selected items from a List
map() change each List item
+ mapnew() make a new List with changed items
reduce() reduce a List to a value
+ slice() take a slice of a List
sort() sort a List
- reverse() reverse the order of a List
+ reverse() reverse the order of items in a List
uniq() remove copies of repeated adjacent items
split() split a String into a List
join() join List items into a String
range() return a List with a sequence of numbers
string() String representation of a List
call() call a function with List as arguments
- index() index of a value in a List
+ index() index of a value in a List or Blob
+ indexof() index in a List or Blob where an expression
+ evaluates to true
max() maximum value in a List
min() minimum value in a List
count() count number of times a value appears in a List
repeat() repeat a List multiple times
flatten() flatten a List
+ flattennew() flatten a copy of a List
Dictionary manipulation: *dict-functions*
get() get an entry without an error for a wrong key
@@ -678,8 +689,10 @@ Dictionary manipulation: *dict-functions*
empty() check if Dictionary is empty
remove() remove an entry from a Dictionary
extend() add entries from one Dictionary to another
+ extendnew() make a new Dictionary and append items
filter() remove selected entries from a Dictionary
map() change each Dictionary entry
+ mapnew() make a new Dictionary with changed items
keys() get List of Dictionary keys
values() get List of Dictionary values
items() get List of Dictionary key-value pairs
@@ -716,6 +729,11 @@ Floating point computation: *float-functions*
isinf() check for infinity
isnan() check for not a number
+Blob manipulation: *blob-functions*
+ blob2list() get a list of numbers from a blob
+ list2blob() get a blob from a list of numbers
+ reverse() reverse the order of numbers in a blob
+
Other computation: *bitwise-function*
and() bitwise AND
invert() bitwise invert
@@ -874,6 +892,7 @@ Buffers, windows and the argument list:
getwininfo() get a list with window information
getchangelist() get a list of change list entries
getjumplist() get a list of jump list entries
+ swapfilelist() list of existing swap files in 'directory'
swapinfo() information about a swap file
swapname() get the swap file path of a buffer
@@ -996,6 +1015,7 @@ Mappings and Menus: *mapping-functions*
hasmapto() check if a mapping exists
mapcheck() check if a matching mapping exists
maparg() get rhs of a mapping
+ maplist() get list of all mappings
mapset() restore a mapping
menu_info() get information about a menu item
wildmenumode() check if the wildmode is active
@@ -1047,6 +1067,14 @@ Prompt Buffer: *promptbuffer-functions*
prompt_setinterrupt() set interrupt callback for a buffer
prompt_setprompt() set the prompt text for a buffer
+Registers: *register-functions*
+ getreg() get contents of a register
+ getreginfo() get information about a register
+ getregtype() get type of a register
+ setreg() set contents and type of a register
+ reg_executing() return the name of the register being executed
+ reg_recording() return the name of the register being recorded
+
Context Stack: *ctx-functions*
ctxget() return context at given index from top
ctxpop() pop and restore top context
@@ -1063,19 +1091,13 @@ Various: *various-functions*
did_filetype() check if a FileType autocommand was used
eventhandler() check if invoked by an event handler
getpid() get process ID of Vim
+ getscriptinfo() get list of sourced vim scripts
libcall() call a function in an external library
libcallnr() idem, returning a number
undofile() get the name of the undo file
- undotree() return the state of the undo tree
-
- getreg() get contents of a register
- getreginfo() get information about a register
- getregtype() get type of a register
- setreg() set contents and type of a register
- reg_executing() return the name of the register being executed
- reg_recording() return the name of the register being recorded
+ undotree() return the state of the undo tree for a buffer
shiftwidth() effective value of 'shiftwidth'
@@ -1316,6 +1338,8 @@ is a List with arguments.
Function references are most useful in combination with a Dictionary, as is
explained in the next section.
+More information about defining your own functions here: |user-function|.
+
==============================================================================
*41.8* Lists and Dictionaries
@@ -1763,7 +1787,7 @@ PITFALLS
Even bigger problem arises in the following example: >
:map ,ab o#include
- :unmap ,ab
+ :unmap ,ab
Here the unmap command will not work, because it tries to unmap ",ab ". This
does not exist as a mapped sequence. An error will be issued, which is very
@@ -2225,7 +2249,7 @@ Example: >
Write this single-line file as "ftdetect/foofoo.vim" in the first directory
that appears in 'runtimepath'. For Unix that would be
-"~/.config/nvim/ftdetect/foofoo.vim". The convention is to use the name of
+"~/.config/nvim/ftdetect/foofoo.vim". The convention is to use the name of
the filetype for the script name.
You can make more complicated checks if you like, for example to inspect the
@@ -2296,7 +2320,7 @@ you can write the different setting in a script: >
Now write this in the "after" directory, so that it gets sourced after the
distributed "vim.vim" ftplugin |after-directory|. For Unix this would be
-"~/.config/nvim/after/ftplugin/vim.vim". Note that the default plugin will
+"~/.config/nvim/after/ftplugin/vim.vim". Note that the default plugin will
have set "b:did_ftplugin", but it is ignored here.
@@ -2466,7 +2490,7 @@ a user to overrule or add to the default file. The default files start with: >
:let current_compiler = "mine"
When you write a compiler file and put it in your personal runtime directory
-(e.g., ~/.config/nvim/compiler for Unix), you set the "current_compiler"
+(e.g., ~/.config/nvim/compiler for Unix), you set the "current_compiler"
variable to make the default file skip the settings.
*:CompilerSet*
The second mechanism is to use ":set" for ":compiler!" and ":setlocal" for
diff --git a/runtime/doc/usr_43.txt b/runtime/doc/usr_43.txt
index 15c94cd15e..0de972b8cc 100644
--- a/runtime/doc/usr_43.txt
+++ b/runtime/doc/usr_43.txt
@@ -27,7 +27,7 @@ want to set the 'softtabstop' option to 4 and define a mapping to insert a
three-line comment. You do this with only two steps:
*your-runtime-dir*
-1. Create your own runtime directory. On Unix this usually is
+1. Create your own runtime directory. On Unix this usually is
"~/.config/nvim". In this directory create the "ftplugin" directory: >
mkdir -p ~/.config/nvim/ftplugin
@@ -123,12 +123,12 @@ That file is found in 'runtimepath' first. Then use this in
What will happen now is that Vim searches for "filetype.vim" files in each
directory in 'runtimepath'. First ~/.config/nvim/filetype.vim is found. The
-autocommand to catch *.txt files is defined there. Then Vim finds the
+autocommand to catch `*.txt` files is defined there. Then Vim finds the
filetype.vim file in $VIMRUNTIME, which is halfway 'runtimepath'. Finally
-~/.config/nvim/after/filetype.vim is found and the autocommand for detecting
+~/.config/nvim/after/filetype.vim is found and the autocommand for detecting
ruby files in /usr/share/scripts is added.
When you now edit /usr/share/scripts/README.txt, the autocommands are
-checked in the order in which they were defined. The *.txt pattern matches,
+checked in the order in which they were defined. The `*.txt` pattern matches,
thus "setf text" is executed to set the filetype to "text". The pattern for
ruby matches too, and the "setf ruby" is executed. But since 'filetype' was
already set to "text", nothing happens here.
diff --git a/runtime/doc/usr_45.txt b/runtime/doc/usr_45.txt
index 95a2bc8f79..85710b15d5 100644
--- a/runtime/doc/usr_45.txt
+++ b/runtime/doc/usr_45.txt
@@ -190,8 +190,7 @@ font. Example: >
xterm -u8 -fn -misc-fixed-medium-r-normal--18-120-100-100-c-90-iso10646-1
-Now you can run Vim inside this terminal. Set 'encoding' to "utf-8" as
-before. That's all.
+Now you can run Vim inside this terminal.
USING UNICODE IN AN ORDINARY TERMINAL
diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt
index e13d892fd6..33f57580c7 100644
--- a/runtime/doc/various.txt
+++ b/runtime/doc/various.txt
@@ -32,7 +32,8 @@ CTRL-L Clears and redraws the screen. The redraw may happen
*:redraws* *:redrawstatus*
:redraws[tatus][!] Redraws the status line and window bar of the current
window, or all status lines and window bars if "!" is
- included. Useful if 'statusline' or 'winbar' includes
+ included. Redraws the commandline instead if it contains
+ the 'ruler'. Useful if 'statusline' or 'winbar' includes
an item that doesn't cause automatic updating.
*:redrawt* *:redrawtabline*
@@ -87,18 +88,24 @@ g8 Print the hex values of the bytes used in the
*8g8*
8g8 Find an illegal UTF-8 byte sequence at or after the
- cursor. This works in two situations:
- 1. when 'encoding' is any 8-bit encoding
- 2. when 'encoding' is "utf-8" and 'fileencoding' is
- any 8-bit encoding
- Thus it can be used when editing a file that was
- supposed to be UTF-8 but was read as if it is an 8-bit
- encoding because it contains illegal bytes.
+ cursor.
+ Can be used when editing a file that was supposed to
+ be UTF-8 but was read as if it is an 8-bit encoding
+ because it contains illegal bytes.
Does not wrap around the end of the file.
Note that when the cursor is on an illegal byte or the
cursor is halfway through a multibyte character the
command won't move the cursor.
+ *gx*
+gx Opens the current filepath or URL (decided by
+ |<cfile>|, 'isfname') at cursor using the system
+ default handler, by calling |vim.ui.open()|.
+
+ *v_gx*
+{Visual}gx Opens the selected text using the system default
+ handler, by calling |vim.ui.open()|.
+
*:p* *:pr* *:print* *E749*
:[range]p[rint] [flags]
Print [range] lines (default current line).
@@ -173,13 +180,12 @@ g8 Print the hex values of the bytes used in the
Like ":z" or ":z!", but number the lines.
*:=*
-:= [flags] Print the last line number.
- See |ex-flags| for [flags].
+:= [args] Without [args]: prints the last line number.
+ With [args]: equivalent to `:lua ={expr}`. see |:lua|
-:{range}= [flags] Prints the last line number in {range}. For example,
+:{range}= Prints the last line number in {range}. For example,
this prints the current line number: >
:.=
-< See |ex-flags| for [flags].
:norm[al][!] {commands} *:norm* *:normal*
Execute Normal mode commands {commands}. This makes
@@ -243,6 +249,10 @@ g8 Print the hex values of the bytes used in the
Fails if changes have been made to the current buffer,
unless 'hidden' is set.
+ If {cmd} is omitted, and the 'shell' job exits with no
+ error, the buffer is closed automatically
+ |default-autocmds|.
+
To enter |Terminal-mode| automatically: >
autocmd TermOpen * startinsert
<
@@ -473,7 +483,7 @@ will be no "Last set" message. When it was defined while executing a function,
user command or autocommand, the script in which it was defined is reported.
*K*
-[count]K Runs the program given by 'keywordprg' to lookup the
+[count]K Runs the program given by 'keywordprg' to lookup the
|word| (defined by 'iskeyword') under or right of the
cursor. Default is "man". Works like this: >
:tabnew | terminal {program} {keyword}
@@ -489,6 +499,9 @@ user command or autocommand, the script in which it was defined is reported.
< - When 'keywordprg' is equal to "man -s", a [count]
before "K" is inserted after the "-s". If there is
no count, the "-s" is removed.
+ *K-lsp-default*
+ - The Nvim |LSP| client sets K to show LSP "hover"
+ feature. |lsp-defaults|
*v_K*
{Visual}K Like "K", but use the visually highlighted text for
diff --git a/runtime/doc/vi_diff.txt b/runtime/doc/vi_diff.txt
index afabddb7f9..0a0cbc8ec6 100644
--- a/runtime/doc/vi_diff.txt
+++ b/runtime/doc/vi_diff.txt
@@ -11,9 +11,9 @@ Differences between Vim and Vi *vi-differences*
==============================================================================
1. Limits *limits*
-Vim has only a few limits for the files that can be edited {Vi: can not handle
+Vim has only a few limits for the files that can be edited. Vi cannot handle
<Nul> characters and characters above 128, has limited line length, many other
-limits}.
+limits.
Maximum line length 2147483647 characters
Maximum number of lines 2147483647 lines
@@ -44,7 +44,7 @@ kept in memory: Command-line history, error messages for Quickfix mode, etc.
Support for different systems.
Vim can be used on:
- - Modern Unix systems (*BSD, Linux, etc.)
+ - Modern Unix systems (BSD, Linux, etc.)
- Windows (XP SP 2 or greater)
- OS X
@@ -180,12 +180,12 @@ Command-line editing and history. |cmdline-editing|
forward/backward one character. The shifted right/left cursor keys
can be used to move forward/backward one word. CTRL-B/CTRL-E can be
used to go to the begin/end of the command-line.
- {Vi: can only alter the last character in the line}
- {Vi: when hitting <Esc> the command-line is executed. This is
+ (Vi: can only alter the last character in the line)
+ (Vi: when hitting <Esc> the command-line is executed. This is
unexpected for most people; therefore it was changed in Vim. But when
the <Esc> is part of a mapping, the command-line is executed. If you
want the Vi behaviour also when typing <Esc>, use ":cmap ^V<Esc>
- ^V^M"}
+ ^V^M")
|cmdline-history|
The command-lines are remembered. The up/down cursor keys can be used
to recall previous command-lines. The 'history' option can be set to
diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt
index bb3b670b24..cf9b3cf0e5 100644
--- a/runtime/doc/vim_diff.txt
+++ b/runtime/doc/vim_diff.txt
@@ -13,7 +13,10 @@ centralized reference of the differences.
Type |gO| to see the table of contents.
==============================================================================
-1. Configuration *nvim-config*
+Configuration *nvim-config*
+
+User configuration and data files are found in standard |base-directories|
+(see also |$NVIM_APPNAME|). Note in particular:
- Use `$XDG_CONFIG_HOME/nvim/init.vim` instead of `.vimrc` for your |config|.
- Use `$XDG_CONFIG_HOME/nvim` instead of `.vim` to store configuration files.
@@ -21,7 +24,7 @@ centralized reference of the differences.
session information. |shada|
==============================================================================
-2. Defaults *nvim-defaults*
+Defaults *nvim-defaults*
- Filetype detection is enabled by default. This can be disabled by adding
":filetype off" to |init.vim|.
@@ -29,23 +32,28 @@ centralized reference of the differences.
":syntax off" to |init.vim|.
- 'autoindent' is enabled
-- 'autoread' is enabled
+- 'autoread' is enabled (works in all UIs, including terminal)
- 'background' defaults to "dark" (unless set automatically by the terminal/UI)
- 'backspace' defaults to "indent,eol,start"
- 'backupdir' defaults to .,~/.local/state/nvim/backup// (|xdg|), auto-created
- 'belloff' defaults to "all"
+- 'comments' includes "fb:•"
+- 'commentstring' defaults to ""
- 'compatible' is always disabled
- 'complete' excludes "i"
+- 'define' defaults to "". The C ftplugin sets it to "^\\s*#\\s*define"
- 'directory' defaults to ~/.local/state/nvim/swap// (|xdg|), auto-created
- 'display' defaults to "lastline"
- 'encoding' is UTF-8 (cf. 'fileencoding' for file-content encoding)
-- 'fillchars' defaults (in effect) to "vert:│,fold:·,sep:│"
+- 'fillchars' defaults (in effect) to "vert:│,fold:·,foldsep:│"
- 'formatoptions' defaults to "tcqj"
-- 'fsync' is disabled
- 'hidden' is enabled
- 'history' defaults to 10000 (the maximum)
- 'hlsearch' is enabled
+- 'include' defaults to "". The C ftplugin sets it to "^\\s*#\\s*include"
- 'incsearch' is enabled
+- 'isfname' does not include ":" (on Windows). Drive letters are handled
+ correctly without it. (Use |gF| for filepaths suffixed with ":line:col").
- 'joinspaces' is disabled
- 'langnoremap' is enabled
- 'langremap' is disabled
@@ -54,9 +62,10 @@ centralized reference of the differences.
- 'mouse' defaults to "nvi"
- 'mousemodel' defaults to "popup_setpos"
- 'nrformats' defaults to "bin,hex"
+- 'path' defaults to ".,,". The C ftplugin adds "/usr/include" if it exists.
- 'ruler' is enabled
- 'sessionoptions' includes "unix,slash", excludes "options"
-- 'shortmess' includes "F", excludes "S"
+- 'shortmess' includes "CF", excludes "S"
- 'showcmd' is enabled
- 'sidescroll' defaults to 1
- 'smarttab' is enabled
@@ -72,13 +81,14 @@ centralized reference of the differences.
- 'wildmenu' is enabled
- 'wildoptions' defaults to "pum,tagfile"
+- |editorconfig| plugin is enabled, .editorconfig settings are applied.
- |man.lua| plugin is enabled, so |:Man| is available by default.
- |matchit| plugin is enabled. To disable it in your config: >vim
:let loaded_matchit = 1
- |g:vimsyn_embed| defaults to "l" to enable Lua highlighting
-Default Mouse ~
+DEFAULT MOUSE
*default-mouse* *disable-mouse*
By default the mouse is enabled, and <RightMouse> opens a |popup-menu| with
standard actions ("Cut", "Copy", "Paste", …). Mouse is NOT enabled in
@@ -102,60 +112,70 @@ To remove the "How-to disable mouse" menu item and the separator above it: >vim
aunmenu PopUp.How-to\ disable\ mouse
aunmenu PopUp.-1-
<
-Default Mappings ~
+DEFAULT MAPPINGS
*default-mappings*
Nvim creates the following default mappings at |startup|. You can disable any
of these in your config by simply removing the mapping, e.g. ":unmap Y".
->vim
- nnoremap Y y$
- nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>
- inoremap <C-U> <C-G>u<C-U>
- inoremap <C-W> <C-G>u<C-W>
- xnoremap * y/\V<C-R>"<CR>
- xnoremap # y?\V<C-R>"<CR>
- nnoremap & :&&<CR>
-<
-Default Autocommands ~
+
+- Y |Y-default|
+- <C-U> |i_CTRL-U-default|
+- <C-W> |i_CTRL-W-default|
+- <C-L> |CTRL-L-default|
+- & |&-default|
+- # |v_#-default|
+- * |v_star-default|
+- Nvim LSP client defaults |lsp-defaults|
+ - K |K-lsp-default|
+
+DEFAULT AUTOCOMMANDS
*default-autocmds*
Default autocommands exist in the following groups. Use ":autocmd! {group}" to
remove them and ":autocmd {group}" to see how they're defined.
nvim_terminal:
- BufReadCmd: Treats "term://" buffers as |terminal| buffers. |terminal-start|
+- TermClose: A |terminal| buffer started with no arguments (which thus uses
+ 'shell') and which exits with no error is closed automatically.
nvim_cmdwin:
- CmdwinEnter: Limits syntax sync to maxlines=1 in the |cmdwin|.
+nvim_swapfile:
+- SwapExists: Skips the swapfile prompt (sets |v:swapchoice| to "e") when the
+ swapfile is owned by a running Nvim process. Shows |W325| "Ignoring
+ swapfile…" message.
+
==============================================================================
-3. New Features *nvim-features*
+New Features *nvim-features*
MAJOR COMPONENTS
-API |API|
-Job control |job-control|
-LSP framework |lsp|
-Lua scripting |lua|
-Parsing engine |treesitter|
-Providers
- Clipboard |provider-clipboard|
- Node.js plugins |provider-nodejs|
- Python plugins |provider-python|
- Ruby plugins |provider-ruby|
-Remote plugins |remote-plugin|
-Shared data |shada|
-Terminal emulator |terminal|
-Vimscript parser |nvim_parse_expression()|
-XDG base directories |xdg|
+- API |API|
+- Job control |job-control|
+- LSP framework |lsp|
+- Lua scripting |lua|
+- Parsing engine |treesitter|
+- Providers
+ - Clipboard |provider-clipboard|
+ - Node.js plugins |provider-nodejs|
+ - Python plugins |provider-python|
+ - Ruby plugins |provider-ruby|
+- Remote plugins |remote-plugin|
+- Shared data |shada|
+- Terminal emulator |terminal|
+- UI |ui| |--listen| |--server|
+- Vimscript parser |nvim_parse_expression()|
+- XDG base directories |xdg|
USER EXPERIENCE
Working intuitively and consistently is a major goal of Nvim.
*feature-compile*
-- Nvim always includes ALL features, in contrast to Vim (which ships with
- various combinations of 100+ optional features). Think of it as a leaner
- version of Vim's "HUGE" build. This reduces surface area for bugs, and
- removes a common source of confusion and friction for users.
+- Nvim always includes ALL features, in contrast to Vim (which ships various
+ combinations of 100+ optional features). |feature-compile| Think of it as
+ a leaner version of Vim's "HUGE" build. This reduces surface area for bugs,
+ and removes a common source of confusion and friction for users.
- Nvim avoids features that cannot be provided on all platforms; instead that
is delegated to external plugins/extensions. E.g. the `-X` platform-specific
@@ -173,17 +193,23 @@ backwards-compatibility cost. Some examples:
- Directories for 'directory' and 'undodir' are auto-created.
- Terminal features such as 'guicursor' are enabled where possible.
+- Various "nvim" |cli-arguments| were redesigned.
Some features are built in that otherwise required external plugins:
-- Highlighting the yanked region, see |lua-highlight|.
+- Highlighting the yanked region, see |vim.highlight|.
ARCHITECTURE
+The Nvim UI is "decoupled" from the core editor: all UIs, including the
+builtin |TUI| are just plugins that connect to a Nvim server (via |--server|
+or |--embed|). Multiple Nvim UI clients can connect to the same Nvim editor
+server.
+
External plugins run in separate processes. |remote-plugin| This improves
stability and allows those plugins to work without blocking the editor. Even
"legacy" Python and Ruby plugins which use the old Vim interfaces (|if_pyth|,
-|if_ruby|) run out-of-process.
+|if_ruby|) run out-of-process, so they cannot crash Nvim.
Platform and I/O facilities are built upon libuv. Nvim benefits from libuv
features and bug fixes, and other projects benefit from improvements to libuv
@@ -191,7 +217,7 @@ by Nvim developers.
FEATURES
-Command-line highlighting:
+Command-line:
The expression prompt (|@=|, |c_CTRL-R_=|, |i_CTRL-R_=|) is highlighted
using a built-in Vimscript expression parser. |expr-highlight|
*E5408* *E5409*
@@ -229,12 +255,16 @@ Functions:
|stdpath()|
|system()|, |systemlist()| can run {cmd} directly (without 'shell')
|matchadd()| can be called before highlight group is defined
+ |tempname()| tries to recover if the Nvim |tempdir| disappears.
|writefile()| with "p" flag creates parent directories.
Highlight groups:
|highlight-blend| controls blend level for a highlight group
|expr-highlight| highlight groups (prefixed with "Nvim")
|hl-NormalFloat| highlights floating window
+ |hl-FloatBorder| highlights border of a floating window
+ |hl-FloatTitle| highlights title of a floating window
+ |hl-FloatFooter| highlights footer of a floating window
|hl-NormalNC| highlights non-current windows
|hl-MsgArea| highlights messages/cmdline area
|hl-MsgSeparator| highlights separator for scrolled messages
@@ -243,6 +273,8 @@ Highlight groups:
|hl-TermCursorNC|
|hl-WinSeparator| highlights window separators
|hl-Whitespace| highlights 'listchars' whitespace
+ |hl-WinBar| highlights 'winbar'
+ |hl-WinBarNC| highlights non-current window 'winbar'
Input/Mappings:
ALT (|META|) chords always work (even in the |TUI|). Map |<M-| with any key:
@@ -253,62 +285,135 @@ Input/Mappings:
Normal commands:
|gO| shows a filetype-defined "outline" of the current buffer.
+ |Q| replays the last recorded macro instead of switching to Ex mode (|gQ|).
Options:
+ Local values for global-local number/boolean options are unset when the
+ option is set without a scope (e.g. by using |:set|), similarly to how
+ global-local string options work.
+
+ 'autoread' works in the terminal (if it supports "focus" events)
'cpoptions' flags: |cpo-_|
- 'guicursor' works in the terminal
- 'fillchars' flags: "msgsep", "horiz", "horizup",
- "horizdown", "vertleft", "vertright", "verthoriz"
+ 'diffopt' "linematch" feature
+ 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The
+ user is prompted whether to trust the file.
+ 'fillchars' flags: "msgsep", "horiz", "horizup", "horizdown",
+ "vertleft", "vertright", "verthoriz"
'foldcolumn' supports up to 9 dynamic/fixed columns
+ 'guicursor' works in the terminal (TUI)
'inccommand' shows interactive results for |:substitute|-like commands
and |:command-preview| commands
+ 'jumpoptions' "view" tries to restore the |mark-view| when moving through
+ the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|.
'laststatus' global statusline support
'mousescroll' amount to scroll by when scrolling with a mouse
'pumblend' pseudo-transparent popupmenu
'scrollback'
+ 'shortmess' "F" flag does not affect output from autocommands
'signcolumn' supports up to 9 dynamic/fixed columns
- 'statusline' supports unlimited alignment sections
+ 'statuscolumn' full control of columns using 'statusline' format
'tabline' %@Func@foo%X can call any function on mouse-click
+ 'termpastefilter'
+ 'ttimeout', 'ttimeoutlen' behavior was simplified
'winblend' pseudo-transparency in floating windows |api-floatwin|
'winhighlight' window-local highlights
- 'diffopt' has the option `linematch`.
+
+Providers:
+ If a Python interpreter is available on your `$PATH`, |:python| and
+ |:python3| are always available. See |provider-python|.
+
+Shell:
+ Shell output (|:!|, |:make|, …) is always routed through the UI, so it
+ cannot "mess up" the screen. (You can still use "chansend(v:stderr,…)" if
+ you want to mess up the screen :)
+
+ Nvim throttles (skips) messages from shell commands (|:!|, |:grep|, |:make|)
+ if there is too much output. No data is lost, this only affects display and
+ improves performance. |:terminal| output is never throttled.
+
+ |:!| does not support "interactive" commands. Use |:terminal| instead.
+ (GUI Vim has a similar limitation, see ":help gui-pty" in Vim.)
+
+ :!start is not special-cased on Windows.
+
+ |system()| does not support writing/reading "backgrounded" commands. |E5677|
Signs:
Signs are removed if the associated line is deleted.
+ Signs placed twice with the same identifier in the same group are moved.
+
+Startup:
+ |-e| and |-es| invoke the same "improved Ex mode" as -E and -Es.
+ |-E| and |-Es| read stdin as text (into buffer 1).
+ |-es| and |-Es| have improved behavior:
+ - Quits automatically, don't need "-c qa!".
+ - Skips swap-file dialog.
+ |-s| reads Normal commands from stdin if the script name is "-".
+ Reading text (instead of commands) from stdin |--|:
+ - works by default: "-" file is optional
+ - works in more cases: |-Es|, file args
+
+TUI:
+ *:set-termcap*
+ Start Nvim with 'verbose' level 3 to show terminal capabilities: >
+ nvim -V3
+<
+ *'term'* *E529* *E530* *E531*
+ 'term' reflects the terminal type derived from |$TERM| and other environment
+ checks. For debugging only; not reliable during startup. >vim
+ :echo &term
+< "builtin_x" means one of the |builtin-terms| was chosen, because the expected
+ terminfo file was not found on the system.
+
+ Nvim will use 256-colour capability on Linux virtual terminals. Vim uses
+ only 8 colours plus bright foreground on Linux VTs.
+
+ Vim combines what is in its |builtin-terms| with what it reads from terminfo,
+ and has a 'ttybuiltin' setting to control how that combination works. Nvim
+ uses one or the other, it does not attempt to merge the two.
+
+UI/Display:
+ |Visual| selection highlights the character at cursor. |visual-use|
+
+ messages: When showing messages longer than 'cmdheight', only
+ scroll the message lines, not the entire screen. The
+ separator line is decorated by |hl-MsgSeparator| and
+ the "msgsep" flag of 'fillchars'. *msgsep*
Variables:
|v:progpath| is always absolute ("full")
|v:windowid| is always available (for use by external UIs)
+ |OptionSet| autocommand args |v:option_new|, |v:option_old|,
+ |v:option_oldlocal|, |v:option_oldglobal| have the type of the option
+ instead of always being strings. |v:option_old| is now the old global value
+ for all global-local options, instead of just string global-local options.
+
+Vimscript:
+ |:redir| nested in |execute()| works.
==============================================================================
-4. Upstreamed features *nvim-upstreamed*
+Upstreamed features *nvim-upstreamed*
These Nvim features were later integrated into Vim.
- 'fillchars' flags: "eob"
+- 'jumpoptions' "stack" behavior
- 'wildoptions' flags: "pum" enables popupmenu for wildmode completion
- |<Cmd>|
- |WinClosed|
- |WinScrolled|
- |:sign-define| "numhl" argument
- |:source| works with anonymous (no file) scripts
+- 'statusline' supports unlimited alignment sections
==============================================================================
-5. Changed features *nvim-features-changed*
-
-Nvim always builds with all features, in contrast to Vim which may have
-certain features removed/added at compile-time. |feature-compile|
-
-Some Vim features were changed in Nvim, and vice versa.
+Other changes *nvim-changed*
-If a Python interpreter is available on your `$PATH`, |:python| and |:python3|
-are always available and may be used simultaneously. See |provider-python|.
-
-|:redir| nested in |execute()| works.
+This section documents various low-level behavior changes.
|mkdir()| behaviour changed:
1. Assuming /tmp/foo does not exist and /tmp can be written to
- mkdir('/tmp/foo/bar', 'p', 0700) will create both /tmp/foo and /tmp/foo/bar
+ mkdir('/tmp/foo/bar', 'p', 0700) will create both /tmp/foo and /tmp/foo/bar
with 0700 permissions. Vim mkdir will create /tmp/foo with 0755.
2. If you try to create an existing directory with `'p'` (e.g. mkdir('/',
'p')) mkdir() will silently exit. In Vim this was an error.
@@ -319,33 +424,33 @@ are always available and may be used simultaneously. See |provider-python|.
structures.
2. |string()| fails immediately on nested containers, not when recursion limit
was exceeded.
-2. When |:echo| encounters duplicate containers like >vim
+3. When |:echo| encounters duplicate containers like >vim
let l = []
echo [l, l]
<
it does not use "[...]" (was: "[[], [...]]", now: "[[], []]"). "..." is
only used for recursive containers.
-3. |:echo| printing nested containers adds "@level" after "..." designating
+4. |:echo| printing nested containers adds "@level" after "..." designating
the level at which recursive container was printed: |:echo-self-refer|.
Same thing applies to |string()| (though it uses construct like
"{E724@level}"), but this is not reliable because |string()| continues to
error out.
-4. Stringifyed infinite and NaN values now use |str2float()| and can be evaled
+5. Stringifyed infinite and NaN values now use |str2float()| and can be evaled
back.
-5. (internal) Trying to print or stringify VAR_UNKNOWN in Vim results in
+6. (internal) Trying to print or stringify VAR_UNKNOWN in Vim results in
nothing, E908, in Nvim it is internal error.
|json_decode()| behaviour changed:
1. It may output |msgpack-special-dict|.
-2. |msgpack-special-dict| is emitted also in case of duplicate keys, while in
+2. |msgpack-special-dict| is emitted also in case of duplicate keys, while in
Vim it errors out.
3. It accepts only valid JSON. Trailing commas are not accepted.
-|json_encode()| behaviour slightly changed: now |msgpack-special-dict| values
+|json_encode()| behaviour slightly changed: now |msgpack-special-dict| values
are accepted, but |v:none| is not.
-Viminfo text files were replaced with binary (messagepack) ShaDa files.
+Viminfo text files were replaced with binary (messagepack) |shada| files.
Additional differences:
- |shada-c| has no effect.
@@ -361,33 +466,35 @@ Additional differences:
|shada-error-handling|
- ShaDa file keeps search direction (|v:searchforward|), viminfo does not.
-|printf()| returns something meaningful when used with `%p` argument: in Vim
-it used to return useless address of the string (strings are copied to the
-newly allocated memory all over the place) and fail on types which cannot be
-coerced to strings. See |id()| for more details, currently it uses
+|printf()| returns something meaningful when used with `%p` argument: in Vim
+it used to return useless address of the string (strings are copied to the
+newly allocated memory all over the place) and fail on types which cannot be
+coerced to strings. See |id()| for more details, currently it uses
`printf("%p", {expr})` internally.
|c_CTRL-R| pasting a non-special register into |cmdline| omits the last <CR>.
-|CursorMoved| always triggers when moving between windows.
+|CursorMoved| triggers when moving between windows.
Lua interface (|lua.txt|):
- `:lua print("a\0b")` will print `a^@b`, like with `:echomsg "a\nb"` . In Vim
that prints `a` and `b` on separate lines, exactly like
`:lua print("a\nb")` .
-- `:lua error('TEST')` emits the error “E5105: Error while calling lua chunk:
- [string "<VimL compiled string>"]:1: TEST”, whereas Vim emits only “TEST”.
+- `:lua error('TEST')` emits the error: >
+ E5108: Error executing lua: [string "<Vimscript compiled string>"]:1: TEST
+< whereas Vim emits only "TEST".
- Lua has direct access to Nvim |API| via `vim.api`.
- Lua package.path and package.cpath are automatically updated according to
- 'runtimepath': |lua-require|.
+ 'runtimepath'. |lua-module-load|
Commands:
|:doautocmd| does not warn about "No matching autocommands".
|:wincmd| accepts a count.
`:write!` does not show a prompt if the file was updated externally.
+ |:=| does not accept |ex-flags|. With an arg it is equivalent to |:lua=|
-Command line completion:
+Command-line:
The meanings of arrow keys do not change depending on 'wildoptions'.
Functions:
@@ -404,9 +511,9 @@ Highlight groups:
using |n| or |N|
|hl-CursorLine| is low-priority unless foreground color is set
|hl-VertSplit| superseded by |hl-WinSeparator|
- Highlight groups names are allowed to contain the characters `.` and `@`.
+ Highlight groups names are allowed to contain `@` characters.
It is an error to define a highlight group with a name that doesn't match
- the regexp `[a-zA-Z0-9_.@]*` (see |group-name|).
+ the regexp `[a-zA-Z0-9_.@-]*` (see |group-name|).
Macro/|recording| behavior
Replay of a macro recorded during :lmap produces the same actions as when it
@@ -418,51 +525,16 @@ Macro/|recording| behavior
the results of keys from 'keymap'.
Mappings:
- Creating a mapping for a simplifiable key (e.g. <C-I>) doesn't replace an
+- Creating a mapping for a simplifiable key (e.g. <C-I>) doesn't replace an
existing mapping for its simplified form (e.g. <Tab>).
+- "#" followed by a digit doesn't stand for a function key at the start of the
+ lhs of a mapping.
Motion:
The |jumplist| avoids useless/phantom jumps.
-Normal commands:
- |Q| replays the last recorded macro instead of switching to Ex mode.
- Instead |gQ| can be used to enter Ex mode.
-
-Options:
- 'ttimeout', 'ttimeoutlen' behavior was simplified
- 'jumpoptions' "stack" behavior
- 'jumpoptions' "view" tries to restore the |mark-view| when moving through
- the |jumplist|, |changelist|, |alternate-file| or using |mark-motions|.
- 'shortmess' the "F" flag does not affect output from autocommands
- 'exrc' searches for ".nvim.lua", ".nvimrc", or ".exrc" files. The user is
- prompted whether to trust the file.
-
-Shell:
- Shell output (|:!|, |:make|, …) is always routed through the UI, so it
- cannot "mess up" the screen. (You can still use "chansend(v:stderr,…)" if
- you want to mess up the screen :)
-
- Nvim throttles (skips) messages from shell commands (|:!|, |:grep|, |:make|)
- if there is too much output. No data is lost, this only affects display and
- improves performance. |:terminal| output is never throttled.
-
- |:!| does not support "interactive" commands. Use |:terminal| instead.
- (GUI Vim has a similar limitation, see ":help gui-pty" in Vim.)
-
- :!start is not special-cased on Windows.
-
- |system()| does not support writing/reading "backgrounded" commands. |E5677|
-
-Startup:
- |-e| and |-es| invoke the same "improved Ex mode" as -E and -Es.
- |-E| and |-Es| read stdin as text (into buffer 1).
- |-es| and |-Es| have improved behavior:
- - Quits automatically, don't need "-c qa!".
- - Skips swap-file dialog.
- |-s| reads Normal commands from stdin if the script name is "-".
- Reading text (instead of commands) from stdin |--|:
- - works by default: "-" file is optional
- - works in more cases: |-Es|, file args
+Performance:
+ Folds are not updated during insert-mode.
Syntax highlighting:
syncolor.vim has been removed. Nvim now sets up default highlighting groups
@@ -472,40 +544,13 @@ Syntax highlighting:
after/syntax/syncolor.vim file should transition that file into a
colorscheme. |:colorscheme|
-TUI:
- *:set-termcap*
- Start Nvim with 'verbose' level 3 to show terminal capabilities: >
- nvim -V3
-<
- *'term'* *E529* *E530* *E531*
- 'term' reflects the terminal type derived from |$TERM| and other environment
- checks. For debugging only; not reliable during startup. >vim
- :echo &term
-< "builtin_x" means one of the |builtin-terms| was chosen, because the expected
- terminfo file was not found on the system.
-
- Nvim will use 256-colour capability on Linux virtual terminals. Vim uses
- only 8 colours plus bright foreground on Linux VTs.
-
- Vim combines what is in its |builtin-terms| with what it reads from terminfo,
- and has a 'ttybuiltin' setting to control how that combination works. Nvim
- uses one or the other, it does not attempt to merge the two.
-
-UI/Display:
- |Visual| selection highlights the character at cursor. |visual-use|
-
- messages: When showing messages longer than 'cmdheight', only
- scroll the message lines, not the entire screen. The
- separator line is decorated by |hl-MsgSeparator| and
- the "msgsep" flag of 'fillchars'. *msgsep*
-
Vimscript compatibility:
`count` does not alias to |v:count|
`errmsg` does not alias to |v:errmsg|
`shell_error` does not alias to |v:shell_error|
`this_session` does not alias to |v:this_session|
-Working directory (Vim implemented some of these later than Nvim):
+Working directory (Vim implemented some of these after Nvim):
- |DirChanged| and |DirChangedPre| can be triggered when switching to another
window or tab.
- |getcwd()| and |haslocaldir()| may throw errors if the tab page or window
@@ -515,21 +560,24 @@ Working directory (Vim implemented some of these later than Nvim):
- `getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global
working directory. Use `getcwd(-1, -1)` to get the global working directory.
-==============================================================================
-6. Missing legacy features *nvim-features-missing*
+Autocommands:
+- Fixed inconsistent behavior in execution of nested autocommands:
+ https://github.com/neovim/neovim/issues/23368
+- |TermResponse| is fired for any OSC sequence received from the terminal,
+ instead of the Primary Device Attributes response. |v:termresponse|
-Some legacy Vim features are not yet implemented:
+==============================================================================
+Missing features *nvim-missing*
-- *if_lua* : Nvim |Lua| API is not compatible with Vim's "if_lua"
-- *if_mzscheme*
-- |if_pyth|: *python-bindeval* *python-Function* are not supported
-- *if_tcl*
+These legacy Vim features are not yet implemented:
-*:gui*
-*:gvim*
+- *:gui*
+- *:gvim*
+- *'completepopup'*
+- *'previewpopup'*
==============================================================================
-7. Removed features *nvim-features-removed*
+Removed legacy features *nvim-removed*
These Vim features were intentionally removed from Nvim.
@@ -548,8 +596,10 @@ Aliases:
vimdiff (alias for "nvim -d" |diff-mode|)
Commands:
+ :behave
:fixdel
- :hardcopy
+ *hardcopy* `:hardcopy` was removed. Instead, use `:TOhtml` and print the
+ resulting HTML using a web browser or other HTML viewer.
:helpfind
:mode (no longer accepts an argument)
:open
@@ -565,14 +615,21 @@ Commands:
:cscope
:lcscope
:scscope
+ :Vimuntar
Compile-time features:
Emacs tags support
X11 integration (see |x11-selection|)
+Cscope:
+ *cscope*
+ Cscope support was removed in favour of plugin-based solutions such as:
+ https://github.com/dhananjaylatkar/cscope_maps.nvim
+
Eval:
Vim9script
*cscope_connection()*
+ *err_teapot()*
*js_encode()*
*js_decode()*
*v:none* (used by Vim to represent JavaScript "undefined"); use |v:null| instead.
@@ -581,6 +638,7 @@ Eval:
*v:sizeofpointer*
Events:
+ *SafeStateAgain*
*SigUSR1* Use |Signal| to detect `SIGUSR1` signal instead.
Highlight groups:
@@ -595,7 +653,13 @@ Highlight groups:
<
Options:
+ *'aleph'* *'al'*
antialias
+ 'backspace' no longer supports number values. Instead:
+ - for `backspace=0` set `backspace=` (empty)
+ - for `backspace=1` set `backspace=indent,eol`
+ - for `backspace=2` set `backspace=indent,eol,start` (default behavior in Nvim)
+ - for `backspace=3` set `backspace=indent,eol,nostop`
*'balloondelay'* *'bdlay'*
*'ballooneval'* *'beval'* *'noballooneval'* *'nobeval'*
*'balloonexpr'* *'bexpr'*
@@ -618,6 +682,14 @@ Options:
*'guifontset'* *'gfs'* (Use 'guifont' instead.)
*'guipty'* (Nvim uses pipes and PTYs consistently on all platforms.)
'highlight' (Names of builtin |highlight-groups| cannot be changed.)
+ *'hkmap'* *'hk'* use `set keymap=hebrew` instead.
+ *'hkmapp'* *'hkp'* use `set keymap=hebrewp` instead.
+ keyprotocol
+
+ *'pastetoggle'* *'pt'* Just Paste It.™ |paste| is handled automatically when
+ you paste text using your terminal's or GUI's paste feature (CTRL-SHIFT-v,
+ CMD-v (macOS), middle-click, …).
+
*'imactivatefunc'* *'imaf'*
*'imactivatekey'* *'imak'*
*'imstatusfunc'* *'imsf'*
@@ -651,11 +723,19 @@ Options:
<
*'macatsui'*
*'maxcombine'* *'mco'*
- Nvim always displays up to 6 combining characters. You can still edit
- text with more than 6 combining characters, you just can't see them.
- Use |g8| or |ga|. See |mbyte-combining|.
+ Nvim counts maximum character sizes in bytes, not codepoints. This is
+ guaranteed to be big enough to always fit all chars properly displayed
+ in vim with 'maxcombine' set to 6.
+
+ You can still edit text with larger characters than fits in the screen buffer,
+ you just can't see them. Use |g8| or |ga|. See |mbyte-combining|.
+
+ NOTE: the rexexp engine still has a hard-coded limit of considering
+ 6 composing chars only.
+
*'maxmem'* Nvim delegates memory-management to the OS.
*'maxmemtot'* Nvim delegates memory-management to the OS.
+ printoptions
*'printdevice'*
*'printencoding'*
*'printexpr'*
@@ -669,6 +749,7 @@ Options:
Everything is allowed in 'exrc' files since they must be explicitly marked
trusted.
*'shelltype'*
+ 'shortmess' flags: *shm-f* *shm-n* *shm-x* *shm-i* (behave like always on)
*'shortname'* *'sn'* *'noshortname'* *'nosn'*
*'swapsync'* *'sws'*
*'termencoding'* *'tenc'* (Vim 7.4.852 also removed this for Windows)
@@ -684,8 +765,18 @@ Options:
*'ttytype'* *'tty'*
weirdinvert
-Performance:
- Folds are not updated during insert-mode.
+Plugins:
+
+- logiPat
+- rrhelper
+- *vimball*
+
+Providers:
+
+- *if_lua* : Nvim |Lua| API is not compatible with Vim's "if_lua".
+- *if_mzscheme*
+- |if_pyth|: *python-bindeval* *python-Function* are not supported.
+- *if_tcl*
Startup:
--literal (file args are always literal; to expand wildcards on Windows, use
@@ -735,14 +826,5 @@ TUI:
at how the terminal is sending CSI. Nvim does not issue such a sequence and
always uses 7-bit control sequences.
-Cscope:
- *cscope*
- Cscope support has been removed in favour of LSP based solutions.
-
-Hardcopy:
- *hardcopy*
- `:hardcopy` was removed. Instead, use `:TOhtml` and print the resulting HTML
- using a web browser or some other HTML viewer.
-
==============================================================================
vim:tw=78:ts=8:sw=2:et:ft=help:norl:
diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt
index 0c6bd4f3a1..0d1ea937c0 100644
--- a/runtime/doc/visual.txt
+++ b/runtime/doc/visual.txt
@@ -169,6 +169,7 @@ If you want to highlight exactly the same area as the last time, you can use
CTRL-C In Visual mode: Stop Visual mode. When insert mode is
pending (the mode message shows
"-- (insert) VISUAL --"), it is also stopped.
+ On MS-Windows, you may need to press CTRL-Break.
==============================================================================
3. Changing the Visual area *visual-change*
@@ -398,7 +399,7 @@ selected text: >
(In the <> notation |<>|, when typing it you should type it literally; you
need to remove the 'B' flag from 'cpoptions')
-Note that special characters (like '.' and '*') will cause problems.
+Note that special characters (like '.' and "*") will cause problems.
Visual-block Examples *blockwise-examples*
With the following text, I will indicate the commands to produce the block and
diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt
index 61f5013f47..d6fce89f23 100644
--- a/runtime/doc/windows.txt
+++ b/runtime/doc/windows.txt
@@ -169,7 +169,7 @@ CTRL-W v *CTRL-W_v*
it doesn't!
CTRL-W n *CTRL-W_n*
-CTRL-W CTRL_N *CTRL-W_CTRL-N*
+CTRL-W CTRL-N *CTRL-W_CTRL-N*
:[N]new [++opt] [+cmd] *:new*
Create a new window and start editing an empty file in it.
Make new window N high (default is to use half the existing
@@ -223,7 +223,7 @@ CTRL-W ^ Split the current window in two and edit the alternate file.
CTRL-W ge *CTRL-W_ge*
Detach the current window as an external window.
- Only available when using an UI with |ui-multigrid| support.
+ Only available when using a UI with |ui-multigrid| support.
Note that the 'splitbelow' and 'splitright' options influence where a new
window will appear.
@@ -243,7 +243,8 @@ and 'winminwidth' are relevant.
:hor[izontal] {cmd}
Execute {cmd}. Currently only makes a difference for
`horizontal wincmd =`, which will equalize windows only
- horizontally.
+ horizontally, and |:terminal|, which will open a |terminal|
+ buffer in a split window.
:lefta[bove] {cmd} *:lefta* *:leftabove*
:abo[veleft] {cmd} *:abo* *:aboveleft*
@@ -391,6 +392,11 @@ CTRL-W CTRL-O *CTRL-W_CTRL-O* *:on* *:only*
given, then they become hidden. But modified buffers are
never abandoned, so changes cannot get lost.
+ *:fc* *:fclose*
+:[count]fc[lose][!]
+ Close [count] floating windows with the highest zindex values.
+ '!' to close all floating windows.
+
==============================================================================
4. Moving cursor to other windows *window-move-cursor*
@@ -631,7 +637,7 @@ times than |WinResized|, it may slow down editing a bit.
The information provided by |WinScrolled| is a dictionary for each window that
has changes, using the window ID as the key, and a total count of the changes
-with the key "all". Example value for |v:event|:
+with the key "all". Example value for |v:event|: >
{
all: {width: 0, height: 2, leftcol: 0, skipcol: 0, topline: 1, topfill: 0},
1003: {width: 0, height: -1, leftcol: 0, skipcol: 0, topline: 0, topfill: 0},
@@ -1090,7 +1096,7 @@ list of buffers. |unlisted-buffer|
a an active buffer: it is loaded and visible
h a hidden buffer: It is loaded, but currently not
displayed in a window |hidden-buffer|
- - a buffer with 'modifiable' off
+ `-` a buffer with 'modifiable' off
= a readonly buffer
R a terminal buffer with a running job
F a terminal buffer with a finished job
@@ -1101,7 +1107,7 @@ list of buffers. |unlisted-buffer|
[flags] can be a combination of the following characters,
which restrict the buffers to be listed:
+ modified buffers
- - buffers with 'modifiable' off
+ `-` buffers with 'modifiable' off
= readonly buffers
a active buffers
u unlisted buffers (overrides the "!")
diff --git a/runtime/filetype.lua b/runtime/filetype.lua
index f772785d21..3f2a7c2960 100644
--- a/runtime/filetype.lua
+++ b/runtime/filetype.lua
@@ -8,6 +8,9 @@ vim.api.nvim_create_augroup('filetypedetect', { clear = false })
vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'StdinReadPost' }, {
group = 'filetypedetect',
callback = function(args)
+ if not vim.api.nvim_buf_is_valid(args.buf) then
+ return
+ end
local ft, on_detect = vim.filetype.match({ filename = args.match, buf = args.buf })
if not ft then
-- Generic configuration file used as fallback
@@ -18,12 +21,15 @@ vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'StdinReadPost' }, {
end)
end
else
- vim.api.nvim_buf_call(args.buf, function()
- vim.api.nvim_cmd({ cmd = 'setf', args = { ft } }, {})
- end)
+ -- on_detect is called before setting the filetype so that it can set any buffer local
+ -- variables that may be used the filetype's ftplugin
if on_detect then
on_detect(args.buf)
end
+
+ vim.api.nvim_buf_call(args.buf, function()
+ vim.api.nvim_cmd({ cmd = 'setf', args = { ft } }, {})
+ end)
end
end,
})
@@ -32,8 +38,7 @@ vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'StdinReadPost' }, {
if not vim.g.did_load_ftdetect then
vim.cmd([[
augroup filetypedetect
- runtime! ftdetect/*.vim
- runtime! ftdetect/*.lua
+ runtime! ftdetect/*.{vim,lua}
augroup END
]])
end
diff --git a/runtime/ftoff.vim b/runtime/ftoff.vim
index 377c6ef829..a33b0962d0 100644
--- a/runtime/ftoff.vim
+++ b/runtime/ftoff.vim
@@ -1,7 +1,8 @@
" Vim support file to switch off detection of file types
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last change: 2001 Jun 11
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("did_load_filetypes")
unlet did_load_filetypes
diff --git a/runtime/ftplugin.vim b/runtime/ftplugin.vim
index feef949dba..2c56241398 100644
--- a/runtime/ftplugin.vim
+++ b/runtime/ftplugin.vim
@@ -1,7 +1,8 @@
" Vim support file to switch on loading plugins for file types
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last change: 2006 Apr 30
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("did_load_ftplugin")
finish
@@ -28,9 +29,10 @@ augroup filetypeplugin
" When there is a dot it is used to separate filetype names. Thus for
" "aaa.bbb" load "aaa" and then "bbb".
for name in split(s, '\.')
- exe 'runtime! ftplugin/' . name . '.vim ftplugin/' . name . '_*.vim ftplugin/' . name . '/*.vim'
- " Load lua ftplugins
- exe printf('runtime! ftplugin/%s.lua ftplugin/%s_*.lua ftplugin/%s/*.lua', name, name, name)
+ " Load Lua ftplugins after Vim ftplugins _per directory_
+ " TODO(clason): use nvim__get_runtime when supports globs and modeline
+ " XXX: "[.]" in the first pattern makes it a wildcard on Windows
+ exe $'runtime! ftplugin/{name}[.]{{vim,lua}} ftplugin/{name}_*.{{vim,lua}} ftplugin/{name}/*.{{vim,lua}}'
endfor
endif
endfunc
diff --git a/runtime/ftplugin/aap.vim b/runtime/ftplugin/aap.vim
index b5065e5157..df839c99ae 100644
--- a/runtime/ftplugin/aap.vim
+++ b/runtime/ftplugin/aap.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Aap recipe
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2021 Nov 14
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/abap.vim b/runtime/ftplugin/abap.vim
index 956b002ecd..61db8093fb 100644
--- a/runtime/ftplugin/abap.vim
+++ b/runtime/ftplugin/abap.vim
@@ -3,6 +3,7 @@
" Author: Steven Oliver <oliver.steven@gmail.com>
" Copyright: Copyright (c) 2013 Steven Oliver
" License: You may redistribute this under the same terms as Vim itself
+" Last Change: 2023 Aug 28 by Vim Project (undo_ftplugin)
" --------------------------------------------------------------------------
" Only do this when not done yet for this buffer
@@ -17,10 +18,13 @@ set cpo&vim
setlocal softtabstop=2 shiftwidth=2
setlocal suffixesadd=.abap
+let b:undo_ftplugin = "setl sts< sua< sw<"
+
" Windows allows you to filter the open file dialog
if has("gui_win32") && !exists("b:browsefilter")
let b:browsefilter = "ABAP Source Files (*.abap)\t*.abap\n" .
\ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin .= " | unlet! b:browsefilter"
endif
let &cpo = s:cpo_save
diff --git a/runtime/ftplugin/art.vim b/runtime/ftplugin/art.vim
index c501a992d8..41b02a94e5 100644
--- a/runtime/ftplugin/art.vim
+++ b/runtime/ftplugin/art.vim
@@ -3,6 +3,7 @@
" Maintainer: Dorai Sitaram <ds26@gte.com>
" URL: http://www.ccs.neu.edu/~dorai/vimplugins/vimplugins.html
" Last Change: Apr 2, 2003
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
@@ -13,3 +14,5 @@ run ftplugin/lisp.vim
setl lw-=if
setl lw+=def-art-fun,deffacts,defglobal,defrule,defschema,for,schema,while
+
+let b:undo_ftplugin ..= " | setl lw<"
diff --git a/runtime/ftplugin/asm.vim b/runtime/ftplugin/asm.vim
index 0914bf634a..f6a92d57d7 100644
--- a/runtime/ftplugin/asm.vim
+++ b/runtime/ftplugin/asm.vim
@@ -1,11 +1,13 @@
" Vim filetype plugin file
" Language: asm
" Maintainer: Colin Caine <cmcaine at the common googlemail domain>
-" Last Changed: 23 May 2020
+" Last Change: 23 May 2020
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
if exists("b:did_ftplugin") | finish | endif
+let b:did_ftplugin = 1
setl comments=:;,s1:/*,mb:*,ex:*/,://
setl commentstring=;%s
-let b:did_ftplugin = 1
+let b:undo_ftplugin = "setl commentstring< comments<"
diff --git a/runtime/ftplugin/awk.vim b/runtime/ftplugin/awk.vim
index 1bca3ad3cf..40fe304cf4 100644
--- a/runtime/ftplugin/awk.vim
+++ b/runtime/ftplugin/awk.vim
@@ -37,11 +37,14 @@ if exists("g:awk_is_gawk")
let b:undo_ftplugin .= " | setl fp<"
endif
- let path = system("gawk 'BEGIN { printf ENVIRON[\"AWKPATH\"] }'")
- let path = substitute(path, '^\.\=:\|:\.\=$\|:\.\=:', ',,', 'g') " POSIX cwd
- let path = substitute(path, ':', ',', 'g')
+ " Disabled by default for security reasons.
+ if dist#vim#IsSafeExecutable('awk', 'gawk')
+ let path = system("gawk 'BEGIN { printf ENVIRON[\"AWKPATH\"] }'")
+ let path = substitute(path, '^\.\=:\|:\.\=$\|:\.\=:', ',,', 'g') " POSIX cwd
+ let path = substitute(path, ':', ',', 'g')
- let &l:path = path
+ let &l:path = path
+ endif
let b:undo_ftplugin .= " | setl inc< path<"
endif
diff --git a/runtime/ftplugin/bash.vim b/runtime/ftplugin/bash.vim
index 7bd9787b6c..7be1eca2a9 100644
--- a/runtime/ftplugin/bash.vim
+++ b/runtime/ftplugin/bash.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: bash
-" Maintainer: Bram Moolenaar
-" Last Changed: 2019 Jan 12
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Changed: 2023 Aug 13
"
" This is not a real filetype plugin. It allows for someone to set 'filetype'
" to "bash" in the modeline, and gets the effect of filetype "sh" with
diff --git a/runtime/ftplugin/btm.vim b/runtime/ftplugin/btm.vim
index d3dc5b75f5..1c2c68599d 100644
--- a/runtime/ftplugin/btm.vim
+++ b/runtime/ftplugin/btm.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: BTM
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2004 Jul 06
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/bzl.vim b/runtime/ftplugin/bzl.vim
index 8ab876e9d6..716b3890b1 100644
--- a/runtime/ftplugin/bzl.vim
+++ b/runtime/ftplugin/bzl.vim
@@ -2,6 +2,7 @@
" Language: Bazel (http://bazel.io)
" Maintainer: David Barnett (https://github.com/google/vim-ft-bzl)
" Last Change: 2021 Jan 19
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
""
" @section Introduction, intro
@@ -41,6 +42,9 @@ let &l:tabstop = s:save_tabstop
setlocal formatoptions-=t
+" Initially defined in the python ftplugin sourced above
+let b:undo_ftplugin .= " | setlocal fo<"
+
" Make gf work with imports in BUILD files.
setlocal includeexpr=substitute(v:fname,'//','','')
@@ -48,6 +52,7 @@ setlocal includeexpr=substitute(v:fname,'//','','')
if get(g:, 'ft_bzl_fold', 0)
setlocal foldmethod=syntax
setlocal foldtext=BzlFoldText()
+ let b:undo_ftplugin .= " | setlocal fdm< fdt<"
endif
if exists('*BzlFoldText')
diff --git a/runtime/ftplugin/c.lua b/runtime/ftplugin/c.lua
new file mode 100644
index 0000000000..0ddbf09470
--- /dev/null
+++ b/runtime/ftplugin/c.lua
@@ -0,0 +1,14 @@
+-- These are the default option values in Vim, but not in Nvim, so must be set explicitly.
+vim.bo.commentstring = '/*%s*/'
+vim.bo.define = '^\\s*#\\s*define'
+vim.bo.include = '^\\s*#\\s*include'
+
+if vim.fn.isdirectory('/usr/include') == 1 then
+ vim.cmd([[
+ setlocal path^=/usr/include
+ setlocal path-=.
+ setlocal path^=.
+ ]])
+end
+
+vim.b.undo_ftplugin = vim.b.undo_ftplugin .. '|setl path<'
diff --git a/runtime/ftplugin/c.vim b/runtime/ftplugin/c.vim
index cfaf26f66c..83fb9ead68 100644
--- a/runtime/ftplugin/c.vim
+++ b/runtime/ftplugin/c.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: C
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Apr 08
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/calender.lua b/runtime/ftplugin/calender.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/calender.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/changelog.vim b/runtime/ftplugin/changelog.vim
index e9df63f8c9..ab73949be5 100644
--- a/runtime/ftplugin/changelog.vim
+++ b/runtime/ftplugin/changelog.vim
@@ -55,13 +55,19 @@ if &filetype == 'changelog'
elseif $EMAIL_ADDRESS != ""
return $EMAIL_ADDRESS
endif
+ let s:default_login = 'unknown'
- let login = s:login()
+ " Disabled by default for security reasons.
+ if dist#vim#IsSafeExecutable('changelog', 'whoami')
+ let login = s:login()
+ else
+ let login = s:default_login
+ endif
return printf('%s <%s@%s>', s:name(login), login, s:hostname())
endfunction
function! s:login()
- return s:trimmed_system_with_default('whoami', 'unknown')
+ return s:trimmed_system_with_default('whoami', s:default_login)
endfunction
function! s:trimmed_system_with_default(command, default)
@@ -71,7 +77,7 @@ if &filetype == 'changelog'
function! s:system_with_default(command, default)
let output = system(a:command)
if v:shell_error
- return default
+ return a:default
endif
return output
endfunction
diff --git a/runtime/ftplugin/checkhealth.vim b/runtime/ftplugin/checkhealth.vim
index 62a1970b4a..4b530e6f7c 100644
--- a/runtime/ftplugin/checkhealth.vim
+++ b/runtime/ftplugin/checkhealth.vim
@@ -9,6 +9,9 @@ endif
runtime! ftplugin/help.vim
setlocal wrap breakindent linebreak
+setlocal foldexpr=getline(v:lnum-1)=~'^=\\{78}$'?'>1':(getline(v:lnum)=~'^=\\{78}'?0:'=')
+setlocal foldmethod=expr
+setlocal foldtext=v:lua.require('vim.health').foldtext()
let &l:iskeyword='!-~,^*,^|,^",192-255'
if exists("b:undo_ftplugin")
diff --git a/runtime/ftplugin/corn.vim b/runtime/ftplugin/corn.vim
new file mode 100644
index 0000000000..2259442229
--- /dev/null
+++ b/runtime/ftplugin/corn.vim
@@ -0,0 +1,18 @@
+" Vim filetype plugin
+" Language: Corn
+" Original Author: Jake Stanger (mail@jstanger.dev)
+" License: MIT
+" Last Change: 2023 May 28
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+setlocal formatoptions-=t
+
+" Set comment (formatting) related options.
+setlocal commentstring=//\ %s comments=://
+
+" Let Vim know how to disable the plug-in.
+let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions<'
diff --git a/runtime/ftplugin/cpp.vim b/runtime/ftplugin/cpp.vim
index 58c4e4b24a..cb425aa8e7 100644
--- a/runtime/ftplugin/cpp.vim
+++ b/runtime/ftplugin/cpp.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: C++
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2020 Jul 26
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/cs.lua b/runtime/ftplugin/cs.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/cs.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/csh.vim b/runtime/ftplugin/csh.vim
index ca5da5a8b9..2feec57bb2 100644
--- a/runtime/ftplugin/csh.vim
+++ b/runtime/ftplugin/csh.vim
@@ -3,9 +3,11 @@
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainer: Dan Sharp
" Contributor: Johannes Zellner <johannes@zellner.org>
-" Last Change: 2021 Oct 15
+" Last Change: 2023 Oct 09
-if exists("b:did_ftplugin") | finish | endif
+if exists("b:did_ftplugin")
+ finish
+endif
let b:did_ftplugin = 1
let s:save_cpo = &cpo
@@ -18,7 +20,7 @@ setlocal formatoptions+=crql
let b:undo_ftplugin = "setlocal com< cms< fo<"
-" Csh: thanks to Johannes Zellner
+" Csh: thanks to Johannes Zellner
" - Both foreach and end must appear alone on separate lines.
" - The words else and endif must appear at the beginning of input lines;
" the if must appear alone on its input line or after an else.
@@ -38,13 +40,14 @@ if exists("loaded_matchit") && !exists("b:match_words")
\ s:line_start .. 'case\s\+:' .. s:line_start .. 'default\>:\<breaksw\>:' ..
\ s:line_start .. 'endsw\>'
unlet s:line_start
- let b:undo_ftplugin ..= " | unlet b:match_words"
+ let b:undo_ftplugin ..= " | unlet! b:match_words"
endif
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter="csh Scripts (*.csh)\t*.csh\n" ..
- \ "All Files (*.*)\t*.*\n"
- let b:undo_ftplugin ..= " | unlet b:browsefilter"
+ let b:browsefilter = "csh Scripts (*.csh)\t*.csh\n" ..
+ \ "All Files (*.*)\t*.*\n"
+ let b:csh_set_browsefilter = 1
+ let b:undo_ftplugin ..= " | unlet! b:browsefilter b:csh_set_browsefilter"
endif
let &cpo = s:save_cpo
diff --git a/runtime/ftplugin/css.lua b/runtime/ftplugin/css.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/css.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/d.lua b/runtime/ftplugin/d.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/d.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/debchangelog.vim b/runtime/ftplugin/debchangelog.vim
index cf8dd17c44..aa657a9b97 100644
--- a/runtime/ftplugin/debchangelog.vim
+++ b/runtime/ftplugin/debchangelog.vim
@@ -3,9 +3,9 @@
" Maintainer: Debian Vim Maintainers <team+vim@tracker.debian.org>
" Former Maintainers: Michael Piefel <piefel@informatik.hu-berlin.de>
" Stefano Zacchiroli <zack@debian.org>
-" Last Change: 2022 Jul 25
+" Last Change: 2023 Aug 18
" License: Vim License
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/ftplugin/debchangelog.vim
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/ftplugin/debchangelog.vim
" Bug completion requires apt-listbugs installed for Debian packages or
" python-launchpadlib installed for Ubuntu packages
@@ -35,14 +35,14 @@ if exists('g:did_changelog_ftplugin')
finish
endif
+" Don't load another plugin (this is global)
+let g:did_changelog_ftplugin = 1
+
" Make sure the '<' and 'C' flags are not included in 'cpoptions', otherwise
" <CR> would not be recognized. See ":help 'cpoptions'".
let s:cpo_save = &cpo
set cpo&vim
-" Don't load another plugin (this is global)
-let g:did_changelog_ftplugin = 1
-
" {{{1 GUI menu
" Helper functions returning various data.
@@ -122,7 +122,7 @@ function NewVersion()
normal! 1G0
call search(')')
normal! h
- " ':normal' doens't support key annotation (<c-a>) directly.
+ " ':normal' doesn't support key annotation (<c-a>) directly.
" Vim's manual recommends using ':exe' to use key annotation indirectly (backslash-escaping needed though).
exe "normal! \<c-a>"
call setline(1, substitute(getline(1), '-\$\$', '-', ''))
diff --git a/runtime/ftplugin/debcontrol.vim b/runtime/ftplugin/debcontrol.vim
index 3a6e39a682..bb710e597c 100644
--- a/runtime/ftplugin/debcontrol.vim
+++ b/runtime/ftplugin/debcontrol.vim
@@ -2,8 +2,8 @@
" Language: Debian control files
" Maintainer: Debian Vim Maintainers
" Former Maintainer: Pierre Habouzit <madcoder@debian.org>
-" Last Change: 2018-01-28
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/ftplugin/debcontrol.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/ftplugin/debcontrol.vim
" Do these settings once per buffer
if exists('b:did_ftplugin')
diff --git a/runtime/ftplugin/debsources.vim b/runtime/ftplugin/debsources.vim
new file mode 100644
index 0000000000..cbb4fafd22
--- /dev/null
+++ b/runtime/ftplugin/debsources.vim
@@ -0,0 +1,16 @@
+" Language: Debian sources.list
+" Maintainer: Debian Vim Maintainers <team+vim@tracker.debian.org>
+" Last Change: 2023 Aug 30
+" License: Vim License
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/ftplugin/debsources.vim
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin=1
+
+setlocal comments=:#
+setlocal commentstring=#%s
+setlocal formatoptions-=t
+
+let b:undo_ftplugin = 'setlocal comments< commentstring< formatoptions<'
diff --git a/runtime/ftplugin/diff.vim b/runtime/ftplugin/diff.vim
index bf37d464c0..f2a0820be9 100644
--- a/runtime/ftplugin/diff.vim
+++ b/runtime/ftplugin/diff.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Diff
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2021 Nov 14
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/dosbatch.vim b/runtime/ftplugin/dosbatch.vim
index 0c5cde2503..f02f26b1fd 100644
--- a/runtime/ftplugin/dosbatch.vim
+++ b/runtime/ftplugin/dosbatch.vim
@@ -1,7 +1,10 @@
" Vim filetype plugin file
-" Language: MS-DOS .bat files
-" Maintainer: Mike Williams <mrw@eandem.co.uk>
-" Last Change: 7th May 2020
+" Language: MS-DOS/Windows .bat files
+" Maintainer: Mike Williams <mrmrdubya@gmail.com>
+" Last Change: 12th February 2023
+"
+" Options Flags:
+" dosbatch_colons_comment - any value to treat :: as comment line
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
@@ -15,8 +18,13 @@ let s:cpo_save = &cpo
set cpo&vim
" BAT comment formatting
-setlocal comments=b:rem,b:@rem,b:REM,b:@REM,:::
-setlocal commentstring=::\ %s
+setlocal comments=b:rem,b:@rem,b:REM,b:@REM
+if exists("dosbatch_colons_comment")
+ setlocal comments+=:::
+ setlocal commentstring=::\ %s
+else
+ setlocal commentstring=REM\ %s
+endif
setlocal formatoptions-=t formatoptions+=rol
" Lookup DOS keywords using Windows command help.
diff --git a/runtime/ftplugin/dune.vim b/runtime/ftplugin/dune.vim
index 86c99c097f..6e20a8fabb 100644
--- a/runtime/ftplugin/dune.vim
+++ b/runtime/ftplugin/dune.vim
@@ -3,8 +3,9 @@
" Anton Kochkov <anton.kochkov@gmail.com>
" URL: https://github.com/ocaml/vim-ocaml
" Last Change:
-" 2018 Nov 3 - Added commentstring (Markus Mottl)
-" 2017 Sep 6 - Initial version (Etienne Millon)
+" 2023 Aug 28 - Added undo_ftplugin (Vim Project)
+" 2018 Nov 03 - Added commentstring (Markus Mottl)
+" 2017 Sep 06 - Initial version (Etienne Millon)
if exists("b:did_ftplugin")
finish
@@ -18,3 +19,5 @@ setl commentstring=;\ %s
setl comments=:;
setl iskeyword+=#,?,.,/
+
+let b:undo_ftplugin = "setl lisp< cms< com< isk<"
diff --git a/runtime/ftplugin/eruby.vim b/runtime/ftplugin/eruby.vim
index e67b00b278..893fa58d32 100644
--- a/runtime/ftplugin/eruby.vim
+++ b/runtime/ftplugin/eruby.vim
@@ -3,7 +3,7 @@
" Maintainer: Tim Pope <vimNOSPAM@tpope.org>
" URL: https://github.com/vim-ruby/vim-ruby
" Release Coordinator: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2020 Jun 28
+" Last Change: 2022 May 15
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
@@ -86,8 +86,12 @@ runtime! ftplugin/ruby.vim ftplugin/ruby_*.vim ftplugin/ruby/*.vim
let b:did_ftplugin = 1
" Combine the new set of values with those previously included.
-if exists("b:undo_ftplugin")
- let s:undo_ftplugin = b:undo_ftplugin . " | " . s:undo_ftplugin
+if !exists('b:undo_ftplugin')
+ " No-op
+ let b:undo_ftplugin = 'exe'
+endif
+if !empty(s:undo_ftplugin)
+ let b:undo_ftplugin .= '|' . s:undo_ftplugin
endif
if exists ("b:browsefilter")
let s:browsefilter = substitute(b:browsefilter,'\cAll Files (\*\.\*)\t\*\.\*\n','','') . s:browsefilter
@@ -119,7 +123,7 @@ endif
setlocal commentstring=<%#%s%>
let b:undo_ftplugin = "setl cms< " .
- \ " | unlet! b:browsefilter b:match_words | " . s:undo_ftplugin
+ \ " | unlet! b:browsefilter b:match_words | " . b:undo_ftplugin
let &cpo = s:save_cpo
unlet s:save_cpo
diff --git a/runtime/ftplugin/fennel.vim b/runtime/ftplugin/fennel.vim
index 2e502699c5..93cf366726 100644
--- a/runtime/ftplugin/fennel.vim
+++ b/runtime/ftplugin/fennel.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: Fennel
" Maintainer: Gregory Anders <greg[NOSPAM]@gpanders.com>
-" Last Update: 2022 Apr 20
+" Last Update: 2023 Jun 9
if exists('b:did_ftplugin')
finish
@@ -13,6 +13,6 @@ setlocal comments=:;;,:;
setlocal formatoptions-=t
setlocal suffixesadd=.fnl
setlocal lisp
-setlocal lispwords=accumulate,collect,do,doto,each,eval-compiler,fn,for,icollect,lambda,let,macro,macros,match,match-try,when,while,with-open
+setlocal lispwords=accumulate,case,case-try,collect,do,doto,each,eval-compiler,faccumulate,fcollect,fn,for,icollect,lambda,let,macro,macros,match,match-try,when,while,with-open
let b:undo_ftplugin = 'setlocal commentstring< comments< formatoptions< suffixesadd< lisp< lispwords<'
diff --git a/runtime/ftplugin/fish.vim b/runtime/ftplugin/fish.vim
new file mode 100644
index 0000000000..f06ad3a0bf
--- /dev/null
+++ b/runtime/ftplugin/fish.vim
@@ -0,0 +1,18 @@
+" Vim filetype plugin file
+" Language: fish
+" Maintainer: Nicholas Boyle (github.com/nickeb96)
+" Repository: https://github.com/nickeb96/fish.vim
+" Last Change: February 1, 2023
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+setlocal iskeyword=@,48-57,_,192-255,-,.
+setlocal comments=:#
+setlocal commentstring=#%s
+setlocal formatoptions+=crjq
+
+let b:undo_ftplugin = "setl cms< com< fo< isk<"
diff --git a/runtime/ftplugin/forth.vim b/runtime/ftplugin/forth.vim
new file mode 100644
index 0000000000..d28c8484e1
--- /dev/null
+++ b/runtime/ftplugin/forth.vim
@@ -0,0 +1,72 @@
+" Vim filetype plugin
+" Language: Forth
+" Maintainer: Johan Kotlinski <kotlinski@gmail.com>
+" Last Change: 2023 Sep 15
+" URL: https://github.com/jkotlinski/forth.vim
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+setlocal commentstring=\\\ %s
+setlocal comments=s:(,mb:\ ,e:),b:\\
+setlocal iskeyword=33-126,128-255
+
+let s:include_patterns =<< trim EOL
+
+ \<\%(INCLUDE\|REQUIRE\)\>\s\+\zs\k\+\ze
+ \<S"\s\+\zs[^"]*\ze"\s\+\%(INCLUDED\|REQUIRED\)\>
+EOL
+let &l:include = $'\c{ s:include_patterns[1:]->join('\|') }'
+
+let s:define_patterns =<< trim EOL
+ :
+ [2F]\=CONSTANT
+ [2F]\=VALUE
+ [2F]\=VARIABLE
+ BEGIN-STRUCTURE
+ BUFFER:
+ CODE
+ CREATE
+ MARKER
+ SYNONYM
+EOL
+let &l:define = $'\c\<\%({ s:define_patterns->join('\|') }\)'
+
+" assume consistent intra-project file extensions
+let &l:suffixesadd = "." .. expand("%:e")
+
+let b:undo_ftplugin = "setl cms< com< def< inc< isk< sua<"
+
+if exists("loaded_matchit") && !exists("b:match_words")
+ let s:matchit_patterns =<< trim EOL
+
+ \<\:\%(NONAME\)\=\>:\<EXIT\>:\<;\>
+ \<IF\>:\<ELSE\>:\<THEN\>
+ \<\[IF]\>:\<\[ELSE]\>:\<\[THEN]\>
+ \<?\=DO\>:\<LEAVE\>:\<+\=LOOP\>
+ \<CASE\>:\<ENDCASE\>
+ \<OF\>:\<ENDOF\>
+ \<BEGIN\>:\<WHILE\>:\<\%(AGAIN\|REPEAT\|UNTIL\)\>
+ \<CODE\>:\<END-CODE\>
+ \<BEGIN-STRUCTURE\>:\<END-STRUCTURE\>
+ EOL
+ let b:match_ignorecase = 1
+ let b:match_words = s:matchit_patterns[1:]->join(',')
+ let b:undo_ftplugin ..= "| unlet! b:match_ignorecase b:match_words"
+ unlet s:matchit_patterns
+endif
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+ let b:browsefilter = "Forth Source Files (*.f *.fs *.ft *.fth *.4th)\t*.f;*.fs;*.ft;*.fth;*.4th\n" ..
+ \ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin ..= " | unlet! b:browsefilter"
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+unlet s:define_patterns s:include_patterns
diff --git a/runtime/ftplugin/gpg.vim b/runtime/ftplugin/gpg.vim
index 3f890e58f6..7fb4f47ed8 100644
--- a/runtime/ftplugin/gpg.vim
+++ b/runtime/ftplugin/gpg.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: gpg(1) configuration file
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2008-07-09
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -15,5 +15,22 @@ let b:undo_ftplugin = "setl com< cms< fo<"
setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 GpgKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s+--' . <q-args> . '\b'' --hilite-search" man ' . 'gpg' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 GpgKeywordPrg
+ \ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+--' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'gpg'
+ endif
+ if exists(':GpgKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:GpgKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer GpgKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
+
diff --git a/runtime/ftplugin/hare.vim b/runtime/ftplugin/hare.vim
index bb10daf38c..0200ba5913 100644
--- a/runtime/ftplugin/hare.vim
+++ b/runtime/ftplugin/hare.vim
@@ -2,26 +2,34 @@
" Language: Hare
" Maintainer: Amelia Clarke <me@rsaihe.dev>
" Previous Maintainer: Drew DeVault <sir@cmpwn.com>
-" Last Updated: 2022-09-21
+" Last Updated: 2022-09-28
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
-" Only do this when not done yet for this buffer
if exists('b:did_ftplugin')
finish
endif
-
-" Don't load another plugin for this buffer
let b:did_ftplugin = 1
-setlocal noexpandtab
-setlocal tabstop=8
-setlocal shiftwidth=0
-setlocal softtabstop=0
-setlocal textwidth=80
+" Formatting settings.
+setlocal formatoptions-=t formatoptions+=croql/
+
+" Miscellaneous.
+setlocal comments=://
setlocal commentstring=//\ %s
+setlocal suffixesadd=.ha
-" Set 'formatoptions' to break comment lines but not other lines,
-" and insert the comment leader when hitting <CR> or using "o".
-setlocal fo-=t fo+=croql
+let b:undo_ftplugin = "setl cms< com< fo< sua<"
+
+" Hare recommended style.
+if get(g:, "hare_recommended_style", 1)
+ setlocal noexpandtab
+ setlocal shiftwidth=8
+ setlocal softtabstop=0
+ setlocal tabstop=8
+ setlocal textwidth=80
+ let b:undo_ftplugin ..= " | setl et< sts< sw< ts< tw<"
+endif
compiler hare
-" vim: tabstop=2 shiftwidth=2 expandtab
+
+" vim: et sw=2 sts=2 ts=8
diff --git a/runtime/ftplugin/haskell.vim b/runtime/ftplugin/haskell.vim
index 84f4d0563b..2a864bf916 100644
--- a/runtime/ftplugin/haskell.vim
+++ b/runtime/ftplugin/haskell.vim
@@ -17,6 +17,7 @@ let b:undo_ftplugin = "setl com< cms< fo<"
setlocal comments=s1fl:{-,mb:-,ex:-},:-- commentstring=--\ %s
setlocal formatoptions-=t formatoptions+=croql
setlocal omnifunc=haskellcomplete#Complete
+setlocal iskeyword+='
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/heex.vim b/runtime/ftplugin/heex.vim
index 5274d59fbf..becc071c37 100644
--- a/runtime/ftplugin/heex.vim
+++ b/runtime/ftplugin/heex.vim
@@ -14,3 +14,14 @@ setlocal comments=:<%!--
setlocal commentstring=<%!--\ %s\ --%>
let b:undo_ftplugin = 'set sw< sts< et< com< cms<'
+
+" HTML: thanks to Johannes Zellner and Benji Fisher.
+if exists("loaded_matchit") && !exists("b:match_words")
+ let b:match_ignorecase = 1
+ let b:match_words = '<%\{-}!--:--%\{-}>,' ..
+ \ '<:>,' ..
+ \ '<\@<=[ou]l\>[^>]*\%(>\|$\):<\@<=li\>:<\@<=/[ou]l>,' ..
+ \ '<\@<=dl\>[^>]*\%(>\|$\):<\@<=d[td]\>:<\@<=/dl>,' ..
+ \ '<\@<=\([^/!][^ \t>]*\)[^>]*\%(>\|$\):<\@<=/\1>'
+ let b:undo_ftplugin ..= " | unlet! b:match_ignorecase b:match_words"
+endif
diff --git a/runtime/ftplugin/help.lua b/runtime/ftplugin/help.lua
new file mode 100644
index 0000000000..4cc3386167
--- /dev/null
+++ b/runtime/ftplugin/help.lua
@@ -0,0 +1,3 @@
+if vim.endswith(vim.fs.normalize(vim.api.nvim_buf_get_name(0)), '/doc/syntax.txt') then
+ require('vim.vimhelp').highlight_groups()
+end
diff --git a/runtime/ftplugin/indent.lua b/runtime/ftplugin/indent.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/indent.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/ishd.vim b/runtime/ftplugin/ishd.vim
index 33ef1510b5..b160349033 100644
--- a/runtime/ftplugin/ishd.vim
+++ b/runtime/ftplugin/ishd.vim
@@ -1,32 +1,37 @@
" Vim filetype plugin file
-" Language: InstallShield (ft=ishd)
-" Maintainer: Johannes Zellner <johannes@zellner.org>
-" Last Change: Sat, 24 May 2003 11:55:36 CEST
+" Language: InstallShield (ft=ishd)
+" Maintainer: Doug Kearns <dougkearns@gmail.com>
+" Previous Maintainer: Johannes Zellner <johannes@zellner.org>
+" Last Change: 2023 Aug 28
if exists("b:did_ftplugin") | finish | endif
let b:did_ftplugin = 1
-setlocal foldmethod=syntax
-
" Using line continuation here.
let s:cpo_save = &cpo
set cpo-=C
+setlocal foldmethod=syntax
+
+let b:undo_ftplugin = "setl fdm<"
+
" matchit support
if exists("loaded_matchit")
- let b:match_ignorecase=0
- let b:match_words=
+ let b:match_ignorecase = 0
+ let b:match_words =
\ '\%(^\s*\)\@<=\<function\>\s\+[^()]\+\s*(:\%(^\s*\)\@<=\<begin\>\s*$:\%(^\s*\)\@<=\<return\>:\%(^\s*\)\@<=\<end\>\s*;\s*$,' .
\ '\%(^\s*\)\@<=\<repeat\>\s*$:\%(^\s*\)\@<=\<until\>\s\+.\{-}\s*;\s*$,' .
\ '\%(^\s*\)\@<=\<switch\>\s*(.\{-}):\%(^\s*\)\@<=\<\%(case\|default\)\>:\%(^\s*\)\@<=\<endswitch\>\s*;\s*$,' .
\ '\%(^\s*\)\@<=\<while\>\s*(.\{-}):\%(^\s*\)\@<=\<endwhile\>\s*;\s*$,' .
\ '\%(^\s*\)\@<=\<for\>.\{-}\<\%(to\|downto\)\>:\%(^\s*\)\@<=\<endfor\>\s*;\s*$,' .
\ '\%(^\s*\)\@<=\<if\>\s*(.\{-})\s*then:\%(^\s*\)\@<=\<else\s*if\>\s*([^)]*)\s*then:\%(^\s*\)\@<=\<else\>:\%(^\s*\)\@<=\<endif\>\s*;\s*$'
+ let b:undo_ftplugin .= " | unlet! b:match_ignorecase b:match_words"
endif
-if has("gui_win32") && !exists("b:browsefilter")
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
let b:browsefilter = "InstallShield Files (*.rul)\t*.rul\n" .
- \ "All Files (*.*)\t*.*\n"
+ \ "All Files (*.*)\t*\n"
+ let b:undo_ftplugin .= " | unlet! b:browsefilter"
endif
let &cpo = s:cpo_save
diff --git a/runtime/ftplugin/json5.vim b/runtime/ftplugin/json5.vim
new file mode 100644
index 0000000000..2560857a33
--- /dev/null
+++ b/runtime/ftplugin/json5.vim
@@ -0,0 +1,28 @@
+" Vim filetype plugin file
+" Language: JSON5
+" Maintainer: Doug Kearns <dougkearns@gmail.com>
+" Last Change: 2023 Oct 19
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,://
+setlocal commentstring=//\ %s
+setlocal formatoptions-=t formatoptions+=croql
+
+let b:undo_ftplugin = "setl fo< com< cms<"
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+ let b:browsefilter = "JSON5 Files (*.json5)\t*.json5\n" ..
+ \ "JSON Files (*.json)\t*.json\n" ..
+ \ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin ..= " | unlet! b:browsefilter"
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/ftplugin/kotlin.vim b/runtime/ftplugin/kotlin.vim
new file mode 100644
index 0000000000..b21de603ea
--- /dev/null
+++ b/runtime/ftplugin/kotlin.vim
@@ -0,0 +1,33 @@
+" Vim filetype plugin file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 7 November 2021
+" 2023 Sep 17 by Vim Project (browsefilter)
+
+if exists('b:did_ftplugin') | finish | endif
+let b:did_ftplugin = 1
+
+let s:save_cpo = &cpo
+set cpo&vim
+
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,://
+setlocal commentstring=//\ %s
+
+setlocal formatoptions-=t formatoptions+=croqnl
+silent! setlocal formatoptions+=j
+
+setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+setlocal suffixesadd=.kt
+
+let b:undo_ftplugin = "setlocal comments< commentstring< ".
+ \ "formatoptions< includeexpr< suffixesadd<"
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+ let b:browsefilter = "Kotlin Source Files (*.kt, *kts)\t*.kt;*.kts\n" .
+ \ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin .= " | unlet! b:browsefilter"
+endif
+
+let &cpo = s:save_cpo
+unlet s:save_cpo
diff --git a/runtime/ftplugin/livebook.vim b/runtime/ftplugin/livebook.vim
new file mode 100644
index 0000000000..c97d210412
--- /dev/null
+++ b/runtime/ftplugin/livebook.vim
@@ -0,0 +1,9 @@
+" Placeholder livebook filetype plugin file.
+" This simply uses the markdown filetype plugin.
+
+" Only load this plugin when no other was loaded.
+if exists("b:did_ftplugin")
+ finish
+endif
+
+runtime! ftplugin/markdown.vim ftplugin/markdown_*.vim ftplugin/markdown/*.vim
diff --git a/runtime/ftplugin/logcheck.vim b/runtime/ftplugin/logcheck.vim
index 9d664b2d09..9c1be1108e 100644
--- a/runtime/ftplugin/logcheck.vim
+++ b/runtime/ftplugin/logcheck.vim
@@ -1,9 +1,9 @@
" Vim filetype plugin file
" Language: Logcheck
" Maintainer: Debian Vim Maintainers
-" Last Change: 2018 Dec 27
+" Last Change: 2023 Jan 16
" License: Vim License
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/ftplugin/logcheck.vim
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/ftplugin/logcheck.vim
if exists('b:did_ftplugin')
finish
diff --git a/runtime/ftplugin/lprolog.vim b/runtime/ftplugin/lprolog.vim
index a8a3c612c1..1075a9c813 100644
--- a/runtime/ftplugin/lprolog.vim
+++ b/runtime/ftplugin/lprolog.vim
@@ -2,7 +2,8 @@
" Language: LambdaProlog (Teyjus)
" Maintainer: Markus Mottl <markus.mottl@gmail.com>
" URL: http://www.ocaml.info/vim/ftplugin/lprolog.vim
-" Last Change: 2006 Feb 05
+" Last Change: 2023 Aug 28 - added undo_ftplugin (Vim Project)
+" 2006 Feb 05
" 2001 Sep 16 - fixed 'no_mail_maps'-bug (MM)
" 2001 Sep 02 - initial release (MM)
@@ -15,11 +16,13 @@ endif
let b:did_ftplugin = 1
" Error format
-setlocal efm=%+A./%f:%l.%c:\ %m formatprg=fmt\ -w75\ -p\\%
+setlocal efm=%+A./%f:%l.%c:\ %m
" Formatting of comments
setlocal formatprg=fmt\ -w75\ -p\\%
+let b:undo_ftplugin = "setlocal efm< fp<"
+
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_lprolog_maps")
" Uncommenting
@@ -28,6 +31,11 @@ if !exists("no_plugin_maps") && !exists("no_lprolog_maps")
vmap <buffer> <LocalLeader>c <Plug>BUncomOn
nmap <buffer> <LocalLeader>C <Plug>LUncomOff
vmap <buffer> <LocalLeader>C <Plug>BUncomOff
+ let b:undo_ftplugin ..=
+ \ " | silent! execute 'nunmap <buffer> <LocalLeader>c'" ..
+ \ " | silent! execute 'vunmap <buffer> <LocalLeader>c'" ..
+ \ " | silent! execute 'nunmap <buffer> <LocalLeader>C'" ..
+ \ " | silent! execute 'vunmap <buffer> <LocalLeader>C'"
endif
nnoremap <buffer> <Plug>LUncomOn mz0i/* <ESC>$A */<ESC>`z
diff --git a/runtime/ftplugin/lua.lua b/runtime/ftplugin/lua.lua
deleted file mode 100644
index 415cf28f9a..0000000000
--- a/runtime/ftplugin/lua.lua
+++ /dev/null
@@ -1,3 +0,0 @@
-if vim.g.ts_highlight_lua then
- vim.treesitter.start()
-end
diff --git a/runtime/ftplugin/lua.vim b/runtime/ftplugin/lua.vim
index 88b1fc9d44..3529e1e3fd 100644
--- a/runtime/ftplugin/lua.vim
+++ b/runtime/ftplugin/lua.vim
@@ -4,7 +4,8 @@
" Previous Maintainer: Max Ischenko <mfi@ukr.net>
" Contributor: Dorai Sitaram <ds26@gte.com>
" C.D. MacEachern <craig.daniel.maceachern@gmail.com>
-" Last Change: 2022 Nov 19
+" Tyler Miller <tmillr@proton.me>
+" Last Change: 2023 Mar 24
if exists("b:did_ftplugin")
finish
@@ -14,7 +15,7 @@ let b:did_ftplugin = 1
let s:cpo_save = &cpo
set cpo&vim
-setlocal comments=:--
+setlocal comments=:---,:--
setlocal commentstring=--\ %s
setlocal formatoptions-=t formatoptions+=croql
diff --git a/runtime/ftplugin/luau.vim b/runtime/ftplugin/luau.vim
new file mode 100644
index 0000000000..458d0b05a9
--- /dev/null
+++ b/runtime/ftplugin/luau.vim
@@ -0,0 +1,14 @@
+" Vim filetype plugin file
+" Language: Luau
+" Maintainer: None yet
+" Last Change: 2023 Apr 30
+
+if exists("b:did_ftplugin")
+ finish
+endif
+
+" Luau is a superset of Lua
+runtime! ftplugin/lua.vim
+
+
+" vim: nowrap sw=2 sts=2 ts=8
diff --git a/runtime/ftplugin/lynx.vim b/runtime/ftplugin/lynx.vim
index b76c69f0ae..bf8410d6a0 100644
--- a/runtime/ftplugin/lynx.vim
+++ b/runtime/ftplugin/lynx.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: Lynx Web Browser Configuration
" Maintainer: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2022 Sep 09
+" Last Change: 2023 Nov 09
if exists("b:did_ftplugin")
finish
@@ -18,7 +18,7 @@ setlocal formatoptions-=t formatoptions+=croql
let b:undo_ftplugin = "setl cms< com< fo<"
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "Lynx Configuration Files (lynx.cfg .lynxrc)\tlynx.cfg;.lynxrc\n" ..
+ let b:browsefilter = "Lynx Configuration Files (lynx.cfg, .lynxrc)\tlynx.cfg;.lynxrc\n" ..
\ "All Files (*.*)\t*.*\n"
let b:undo_ftplugin ..= " | unlet! b:browsefilter"
endif
diff --git a/runtime/ftplugin/mail.vim b/runtime/ftplugin/mail.vim
index f12022305f..3cef84f528 100644
--- a/runtime/ftplugin/mail.vim
+++ b/runtime/ftplugin/mail.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Mail
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2021 Oct 23
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/make.vim b/runtime/ftplugin/make.vim
index 168bc38eb3..7227bb3739 100644
--- a/runtime/ftplugin/make.vim
+++ b/runtime/ftplugin/make.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Make
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2020 Oct 16
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/modconf.vim b/runtime/ftplugin/modconf.vim
index c8e76b538b..22d18a9aad 100644
--- a/runtime/ftplugin/modconf.vim
+++ b/runtime/ftplugin/modconf.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: modules.conf(5) configuration file
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2008-07-09
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -16,5 +16,21 @@ let b:undo_ftplugin = "setl com< cms< inc< fo<"
setlocal comments=:# commentstring=#\ %s include=^\\s*include
setlocal formatoptions-=t formatoptions+=croql
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 ModconfKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s{,8}' . <q-args> . '\b'' --hilite-search" man ' . 'modprobe.d' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 ModconfKeywordPrg
+ \ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'modprobe.d'
+ endif
+ if exists(':ModconfKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:ModconfKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer ModconfKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/muttrc.vim b/runtime/ftplugin/muttrc.vim
index c8ad0f2ec5..c9f6df31d0 100644
--- a/runtime/ftplugin/muttrc.vim
+++ b/runtime/ftplugin/muttrc.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: mutt RC File
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2006-04-19
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -18,5 +18,21 @@ setlocal formatoptions-=t formatoptions+=croql
let &l:include = '^\s*source\>'
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 MuttrcKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s+' . <q-args> . '\b'' --hilite-search" man ' . 'muttrc' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 MuttrcKeywordPrg
+ \ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'muttrc'
+ endif
+ if exists(':MuttrcKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:MuttrcKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer MuttrcKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/netrc.vim b/runtime/ftplugin/netrc.vim
index 02ee327295..5f40bec96e 100644
--- a/runtime/ftplugin/netrc.vim
+++ b/runtime/ftplugin/netrc.vim
@@ -2,6 +2,7 @@
" Language: netrc(5) configuration file
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2008-07-09
+" Last Change: 2023 Feb 27 by Keith Smiley
if exists("b:did_ftplugin")
finish
@@ -13,7 +14,7 @@ set cpo&vim
let b:undo_ftplugin = "setl com< cms< fo<"
-setlocal comments= commentstring= formatoptions-=tcroq formatoptions+=l
+setlocal comments=b:# commentstring=#\ %s formatoptions-=tcroq formatoptions+=l
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/nginx.vim b/runtime/ftplugin/nginx.vim
index e808db1277..525d0fdccf 100644
--- a/runtime/ftplugin/nginx.vim
+++ b/runtime/ftplugin/nginx.vim
@@ -2,5 +2,8 @@
" Language: nginx.conf
" Maintainer: Chris Aumann <me@chr4.org>
" Last Change: Apr 15, 2017
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
setlocal commentstring=#\ %s
+
+let b:undo_ftplugin = "setlocal commentstring<"
diff --git a/runtime/ftplugin/nix.vim b/runtime/ftplugin/nix.vim
new file mode 100644
index 0000000000..d417cc7805
--- /dev/null
+++ b/runtime/ftplugin/nix.vim
@@ -0,0 +1,17 @@
+" Vim filetype plugin
+" Language: nix
+" Maintainer: Keith Smiley <keithbsmiley@gmail.com>
+" Last Change: 2023 Jul 22
+
+" Only do this when not done yet for this buffer
+if exists("b:did_ftplugin")
+ finish
+endif
+
+" Don't load another plugin for this buffer
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setl commentstring< comments<"
+
+setlocal comments=:#
+setlocal commentstring=#\ %s
diff --git a/runtime/ftplugin/objc.vim b/runtime/ftplugin/objc.vim
index e41beb5dad..d129b33c1c 100644
--- a/runtime/ftplugin/objc.vim
+++ b/runtime/ftplugin/objc.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Objective C
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2003 Jan 15
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/objdump.vim b/runtime/ftplugin/objdump.vim
new file mode 100644
index 0000000000..7517a342a3
--- /dev/null
+++ b/runtime/ftplugin/objdump.vim
@@ -0,0 +1,14 @@
+" Vim filetype plugin file
+" Language: Objdump
+" Maintainer: Colin Kennedy <colinvfx@gmail.com>
+" Last Change: 2023 October 25
+
+if exists("b:did_ftplugin")
+ finish
+endif
+
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setlocal cms<"
+
+setlocal commentstring=#\ %s
diff --git a/runtime/ftplugin/pbtxt.vim b/runtime/ftplugin/pbtxt.vim
index e3c1bf7650..56c2553ca8 100644
--- a/runtime/ftplugin/pbtxt.vim
+++ b/runtime/ftplugin/pbtxt.vim
@@ -2,20 +2,16 @@
" Language: Protobuf Text Format
" Maintainer: Lakshay Garg <lakshayg@outlook.in>
" Last Change: 2020 Nov 17
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
" Homepage: https://github.com/lakshayg/vim-pbtxt
if exists("b:did_ftplugin")
finish
endif
-
let b:did_ftplugin = 1
-let s:cpo_save = &cpo
-set cpo&vim
-
setlocal commentstring=#\ %s
-let &cpo = s:cpo_save
-unlet s:cpo_save
+let b:undo_ftplugin = "setlocal commentstring<"
" vim: nowrap sw=2 sts=2 ts=8 noet
diff --git a/runtime/ftplugin/perl.vim b/runtime/ftplugin/perl.vim
index d0bdbc0cfb..c63bd3f9c7 100644
--- a/runtime/ftplugin/perl.vim
+++ b/runtime/ftplugin/perl.vim
@@ -5,6 +5,8 @@
" Bugs/requests: https://github.com/vim-perl/vim-perl/issues
" License: Vim License (see :help license)
" Last Change: 2021 Nov 10
+" 2023 Sep 07 by Vim Project (safety check: don't execute perl
+" from current directory)
if exists("b:did_ftplugin") | finish | endif
let b:did_ftplugin = 1
@@ -54,7 +56,8 @@ endif
" Set this once, globally.
if !exists("perlpath")
- if executable("perl")
+ " safety check: don't execute perl binary by default
+ if dist#vim#IsSafeExecutable('perl', 'perl')
try
if &shellxquote != '"'
let perlpath = system('perl -e "print join(q/,/,@INC)"')
diff --git a/runtime/ftplugin/pod.vim b/runtime/ftplugin/pod.vim
index 2a905ab354..61a4aa094a 100644
--- a/runtime/ftplugin/pod.vim
+++ b/runtime/ftplugin/pod.vim
@@ -5,11 +5,12 @@
" Homepage: https://github.com/vim-perl/vim-perl
" Bugs/requests: https://github.com/vim-perl/vim-perl/issues
" License: Vim License (see :help license)
-" Last Change: 2021 Oct 19
+" Last Change: 2023 Jul 05
if exists("b:did_ftplugin")
- finish
+ finish
endif
+let b:did_ftplugin = 1
let s:save_cpo = &cpo
set cpo-=C
@@ -37,12 +38,12 @@ if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
let b:undo_ftplugin .= " | unlet! b:browsefilter"
endif
-function! s:jumpToSection(backwards)
- let flags = a:backwards ? 'bsWz' : 'sWz'
- if has('syntax_items')
+function s:jumpToSection(direction)
+ let flags = a:direction == "backward" ? "bsWz" : "sWz"
+ if has("syntax_items")
let skip = "synIDattr(synID(line('.'), col('.'), 1), 'name') !~# '\\<podCommand\\>'"
else
- let skip = ''
+ let skip = ""
endif
for i in range(v:count1)
call search('^=\a', flags, 0, 0, skip)
@@ -50,19 +51,17 @@ function! s:jumpToSection(backwards)
endfunction
if !exists("no_plugin_maps") && !exists("no_pod_maps")
- nnoremap <silent> <buffer> ]] <Cmd>call <SID>jumpToSection()<CR>
- vnoremap <silent> <buffer> ]] <Cmd>call <SID>jumpToSection()<CR>
- nnoremap <silent> <buffer> ][ <Cmd>call <SID>jumpToSection()<CR>
- vnoremap <silent> <buffer> ][ <Cmd>call <SID>jumpToSection()<CR>
- nnoremap <silent> <buffer> [[ <Cmd>call <SID>jumpToSection(1)<CR>
- vnoremap <silent> <buffer> [[ <Cmd>call <SID>jumpToSection(1)<CR>
- nnoremap <silent> <buffer> [] <Cmd>call <SID>jumpToSection(1)<CR>
- vnoremap <silent> <buffer> [] <Cmd>call <SID>jumpToSection(1)<CR>
- let b:undo_ftplugin .=
- \ " | silent! exe 'nunmap <buffer> ]]' | silent! exe 'vunmap <buffer> ]]'" .
- \ " | silent! exe 'nunmap <buffer> ][' | silent! exe 'vunmap <buffer> ]['" .
- \ " | silent! exe 'nunmap <buffer> ]]' | silent! exe 'vunmap <buffer> ]]'" .
- \ " | silent! exe 'nunmap <buffer> []' | silent! exe 'vunmap <buffer> []'"
+ for s:mode in ["n", "o", "x"]
+ for s:lhs in ["]]", "]["]
+ execute s:mode . "noremap <silent> <buffer> " . s:lhs . " <Cmd>call <SID>jumpToSection('forward')<CR>"
+ let b:undo_ftplugin .= " | silent! execute '" . s:mode . "unmap <buffer> " . s:lhs . "'"
+ endfor
+ for s:lhs in ["[[", "[]"]
+ execute s:mode . "noremap <silent> <buffer> " . s:lhs . " <Cmd>call <SID>jumpToSection('backward')<CR>"
+ let b:undo_ftplugin .= " | silent! execute '" . s:mode . "unmap <buffer> " . s:lhs . "'"
+ endfor
+ endfor
+ unlet s:mode s:lhs
endif
let &cpo = s:save_cpo
diff --git a/runtime/ftplugin/pymanifest.vim b/runtime/ftplugin/pymanifest.vim
new file mode 100644
index 0000000000..a77e956ae5
--- /dev/null
+++ b/runtime/ftplugin/pymanifest.vim
@@ -0,0 +1,13 @@
+" Vim filetype plugin
+" Language: PyPA manifest
+" Maintainer: ObserverOfTime <chronobserver@disroot.org>
+" Last Change: 2023 Aug 08
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+setl comments=:# commentstring=#\ %s
+
+let b:undo_ftplugin = 'setl com< cms<'
diff --git a/runtime/ftplugin/qml.vim b/runtime/ftplugin/qml.vim
new file mode 100644
index 0000000000..fd5ddbb4bc
--- /dev/null
+++ b/runtime/ftplugin/qml.vim
@@ -0,0 +1,31 @@
+" Vim filetype plugin file
+" Language: QML
+" Maintainer: Chase Knowlden <haroldknowlden@gmail.com>
+" Last Change: 2023 Aug 16
+
+if exists( 'b:did_ftplugin' )
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpoptions_save = &cpoptions
+set cpoptions&vim
+
+" command for undo
+let b:undo_ftplugin = "setlocal formatoptions< comments< commentstring<"
+
+if (has("gui_win32") || has("gui_gtk")) && !exists( 'b:browsefilter' )
+ let b:browsefilter =
+ \ 'QML Files (*.qml,*.qbs)\t*.qml;*.qbs\n' .
+ \ 'All Files\t*\n'
+endif
+
+" Set 'comments' to format dashed lists in comments.
+setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,://
+setlocal commentstring=//%s
+
+setlocal formatoptions-=t
+setlocal formatoptions+=croql
+
+let &cpoptions = s:cpoptions_save
+unlet s:cpoptions_save
diff --git a/runtime/ftplugin/quarto.vim b/runtime/ftplugin/quarto.vim
new file mode 100644
index 0000000000..a76bcc2c7e
--- /dev/null
+++ b/runtime/ftplugin/quarto.vim
@@ -0,0 +1 @@
+runtime ftplugin/rmd.vim
diff --git a/runtime/ftplugin/query.lua b/runtime/ftplugin/query.lua
index 3b99d67247..964c221ad4 100644
--- a/runtime/ftplugin/query.lua
+++ b/runtime/ftplugin/query.lua
@@ -1,6 +1,35 @@
-- Neovim filetype plugin file
-- Language: Tree-sitter query
--- Last Change: 2022 Mar 29
+-- Last Change: 2023 Aug 23
+
+if vim.b.did_ftplugin == 1 then
+ return
+end
+
+-- Do not set vim.b.did_ftplugin = 1 to allow loading of ftplugin/lisp.vim
+
+-- use treesitter over syntax
+vim.treesitter.start()
+
+-- set omnifunc
+vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+
+vim.opt_local.iskeyword:append('.')
+
+-- query linter
+local buf = vim.api.nvim_get_current_buf()
+local query_lint_on = vim.g.query_lint_on or { 'BufEnter', 'BufWrite' }
+
+if not vim.b.disable_query_linter and #query_lint_on > 0 then
+ vim.api.nvim_create_autocmd(query_lint_on, {
+ group = vim.api.nvim_create_augroup('querylint', { clear = false }),
+ buffer = buf,
+ callback = function()
+ vim.treesitter.query.lint(buf)
+ end,
+ desc = 'Query linter',
+ })
+end
-- it's a lisp!
vim.cmd([[ runtime! ftplugin/lisp.vim ]])
diff --git a/runtime/ftplugin/r.vim b/runtime/ftplugin/r.vim
index a78afa2e7e..28966368cb 100644
--- a/runtime/ftplugin/r.vim
+++ b/runtime/ftplugin/r.vim
@@ -2,7 +2,7 @@
" Language: R
" Maintainer: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sat Aug 15, 2020 11:37AM
+" Last Change: Sun Apr 24, 2022 09:14AM
" Only do this when not yet done for this buffer
if exists("b:did_ftplugin")
@@ -22,7 +22,7 @@ setlocal comments=:#',:###,:##,:#
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
let b:browsefilter = "R Source Files (*.R)\t*.R\n" .
- \ "Files that include R (*.Rnw *.Rd *.Rmd *.Rrst)\t*.Rnw;*.Rd;*.Rmd;*.Rrst\n" .
+ \ "Files that include R (*.Rnw *.Rd *.Rmd *.Rrst *.qmd)\t*.Rnw;*.Rd;*.Rmd;*.Rrst;*.qmd\n" .
\ "All Files (*.*)\t*.*\n"
endif
diff --git a/runtime/ftplugin/readline.vim b/runtime/ftplugin/readline.vim
index eba7122347..181d8ac661 100644
--- a/runtime/ftplugin/readline.vim
+++ b/runtime/ftplugin/readline.vim
@@ -25,11 +25,27 @@ if exists("loaded_matchit") && !exists("b:match_words")
endif
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "Readline Intialization Files (inputrc .inputrc)\tinputrc;*.inputrc\n" ..
+ let b:browsefilter = "Readline Initialization Files (inputrc .inputrc)\tinputrc;*.inputrc\n" ..
\ "All Files (*.*)\t*.*\n"
let b:undo_ftplugin ..= " | unlet! b:browsefilter"
endif
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 ReadlineKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s+' . <q-args> . '\b'' --hilite-search" man ' . '3 readline' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 ReadlineKeywordPrg
+ \ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . '3 readline'
+ endif
+ if exists(':ReadlineKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:ReadlineKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer ReadlineKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/rhelp.vim b/runtime/ftplugin/rhelp.vim
index d0b546d62d..2fde4875c6 100644
--- a/runtime/ftplugin/rhelp.vim
+++ b/runtime/ftplugin/rhelp.vim
@@ -2,7 +2,7 @@
" Language: R help file
" Maintainer: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sat Aug 15, 2020 12:01PM
+" Last Change: Sun Apr 24, 2022 09:12AM
" Only do this when not yet done for this buffer
if exists("b:did_ftplugin")
@@ -18,7 +18,7 @@ set cpo&vim
setlocal iskeyword=@,48-57,_,.
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst\n" .
+ let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst *.qmd)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst;*.qmd\n" .
\ "All Files (*.*)\t*.*\n"
endif
diff --git a/runtime/ftplugin/rmd.vim b/runtime/ftplugin/rmd.vim
index 2ee72ffc6c..a407c236dd 100644
--- a/runtime/ftplugin/rmd.vim
+++ b/runtime/ftplugin/rmd.vim
@@ -2,7 +2,7 @@
" Language: R Markdown file
" Maintainer: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sat Aug 15, 2020 12:03PM
+" Last Change: Mon May 29, 2023 06:31AM
" Original work by Alex Zvoleff (adjusted from R help for rmd by Michel Kuhlmann)
" Only do this when not yet done for this buffer
@@ -23,7 +23,7 @@ setlocal iskeyword=@,48-57,_,.
let s:cpo_save = &cpo
set cpo&vim
-function! FormatRmd()
+function FormatRmd()
if search("^[ \t]*```[ ]*{r", "bncW") > search("^[ \t]*```$", "bncW")
setlocal comments=:#',:###,:##,:#
else
@@ -32,13 +32,30 @@ function! FormatRmd()
return 1
endfunction
-" If you do not want 'comments' dynamically defined, put in your vimrc:
-" let g:rmd_dynamic_comments = 0
+let s:last_line = 0
+function SetRmdCommentStr()
+ if line('.') == s:last_line
+ return
+ endif
+ let s:last_line = line('.')
+
+ if (search("^[ \t]*```[ ]*{r", "bncW") > search("^[ \t]*```$", "bncW")) || ((search('^---$', 'Wn') || search('^\.\.\.$', 'Wn')) && search('^---$', 'bnW'))
+ set commentstring=#\ %s
+ else
+ set commentstring=<!--\ %s\ -->
+ endif
+endfunction
+
+" If you do not want both 'comments' and 'commentstring' dynamically defined,
+" put in your vimrc: let g:rmd_dynamic_comments = 0
if !exists("g:rmd_dynamic_comments") || (exists("g:rmd_dynamic_comments") && g:rmd_dynamic_comments == 1)
setlocal formatexpr=FormatRmd()
+ augroup RmdCStr
+ autocmd!
+ autocmd CursorMoved <buffer> call SetRmdCommentStr()
+ augroup END
endif
-
" Enables pandoc if it is installed
unlet! b:did_ftplugin
runtime ftplugin/pandoc.vim
@@ -47,7 +64,7 @@ runtime ftplugin/pandoc.vim
let b:did_ftplugin = 1
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst\n" .
+ let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst *.qmd)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst;*.qmd\n" .
\ "All Files (*.*)\t*.*\n"
endif
diff --git a/runtime/ftplugin/rnoweb.vim b/runtime/ftplugin/rnoweb.vim
index dc5f1b5e06..26c1ab4e3f 100644
--- a/runtime/ftplugin/rnoweb.vim
+++ b/runtime/ftplugin/rnoweb.vim
@@ -2,7 +2,7 @@
" Language: Rnoweb
" Maintainer: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sat Aug 15, 2020 12:02PM
+" Last Change: Mon Feb 27, 2023 07:16PM
" Only do this when not yet done for this buffer
if exists("b:did_ftplugin")
@@ -25,14 +25,31 @@ setlocal suffixesadd=.bib,.tex
setlocal comments=b:%,b:#,b:##,b:###,b:#'
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst\n" .
+ let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst *.qmd)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst;*.qmd\n" .
\ "All Files (*.*)\t*.*\n"
endif
+function SetRnwCommentStr()
+ if (search("^\s*<<.*>>=", "bncW") > search("^@", "bncW"))
+ set commentstring=#\ %s
+ else
+ set commentstring=%\ %s
+ endif
+endfunction
+
+" If you do not want both 'comments' and 'commentstring' dynamically defined,
+" put in your vimrc: let g:rnw_dynamic_comments = 0
+if !exists("g:rnw_dynamic_comments") || (exists("g:rnw_dynamic_comments") && g:rnw_dynamic_comments == 1)
+ augroup RnwCStr
+ autocmd!
+ autocmd CursorMoved <buffer> call SetRnwCommentStr()
+ augroup END
+endif
+
if exists('b:undo_ftplugin')
- let b:undo_ftplugin .= " | setl isk< sua< com< | unlet! b:browsefilter"
+ let b:undo_ftplugin .= " | setl isk< sua< com< cms< | unlet! b:browsefilter"
else
- let b:undo_ftplugin = "setl isk< sua< com< | unlet! b:browsefilter"
+ let b:undo_ftplugin = "setl isk< sua< com< cms< | unlet! b:browsefilter"
endif
let &cpo = s:cpo_save
diff --git a/runtime/ftplugin/rrst.vim b/runtime/ftplugin/rrst.vim
index a56fd6478e..d9df5e587f 100644
--- a/runtime/ftplugin/rrst.vim
+++ b/runtime/ftplugin/rrst.vim
@@ -2,7 +2,7 @@
" Language: reStructuredText documentation format with R code
" Maintainer: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sat Aug 15, 2020 12:02PM
+" Last Change: Mon Feb 27, 2023 07:16PM
" Original work by Alex Zvoleff
" Only do this when not yet done for this buffer
@@ -22,7 +22,7 @@ setlocal formatoptions+=tcqln
setlocal formatlistpat=^\\s*\\d\\+\\.\\s\\+\\\|^\\s*[-*+]\\s\\+
setlocal iskeyword=@,48-57,_,.
-function! FormatRrst()
+function FormatRrst()
if search('^\.\. {r', "bncW") > search('^\.\. \.\.$', "bncW")
setlocal comments=:#',:###,:##,:#
else
@@ -38,7 +38,7 @@ if !exists("g:rrst_dynamic_comments") || (exists("g:rrst_dynamic_comments") && g
endif
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst\n" .
+ let b:browsefilter = "R Source Files (*.R *.Rnw *.Rd *.Rmd *.Rrst *.qmd)\t*.R;*.Rnw;*.Rd;*.Rmd;*.Rrst;*.qmd\n" .
\ "All Files (*.*)\t*.*\n"
endif
diff --git a/runtime/ftplugin/rst.vim b/runtime/ftplugin/rst.vim
index ff7a402d10..c88e8f2580 100644
--- a/runtime/ftplugin/rst.vim
+++ b/runtime/ftplugin/rst.vim
@@ -28,7 +28,7 @@ setlocal formatoptions+=tcroql
" directives (..) and ordered lists (1.), although it can cause problems for
" many other cases.
"
-" More sophisticated indentation rules should be revisted in the future.
+" More sophisticated indentation rules should be revisited in the future.
if exists("g:rst_style") && g:rst_style != 0
setlocal expandtab shiftwidth=3 softtabstop=3 tabstop=8
diff --git a/runtime/ftplugin/ruby.vim b/runtime/ftplugin/ruby.vim
index 8c1f47731c..b61c1765d9 100644
--- a/runtime/ftplugin/ruby.vim
+++ b/runtime/ftplugin/ruby.vim
@@ -3,7 +3,7 @@
" Maintainer: Tim Pope <vimNOSPAM@tpope.org>
" URL: https://github.com/vim-ruby/vim-ruby
" Release Coordinator: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2022 Mar 21
+" Last Change: 2023 Sep 1st
if (exists("b:did_ftplugin"))
finish
@@ -61,6 +61,10 @@ if !exists('g:ruby_version_paths')
endif
function! s:query_path(root) abort
+ " Disabled by default for security reasons.
+ if !get(g:, 'ruby_exec', get(g:, 'plugin_exec', 0))
+ return []
+ endif
let code = "print $:.join %q{,}"
if &shell =~# 'sh' && empty(&shellxquote)
let prefix = 'env PATH='.shellescape($PATH).' '
@@ -77,7 +81,14 @@ function! s:query_path(root) abort
let cwd = fnameescape(getcwd())
try
exe cd fnameescape(a:root)
- let path = split(system(path_check),',')
+ let s:tmp_cwd = getcwd()
+ if (fnamemodify(exepath('ruby'), ':p:h') ==# cwd
+ \ && (index(split($PATH,has("win32")? ';' : ':'), s:tmp_cwd) == -1 || s:tmp_cwd == '.'))
+ let path = []
+ else
+ let path = split(system(path_check),',')
+ endif
+ unlet! s:tmp_cwd
exe cd cwd
return path
finally
diff --git a/runtime/ftplugin/rust.vim b/runtime/ftplugin/rust.vim
index 7efca5985b..7f1a86ea95 100644
--- a/runtime/ftplugin/rust.vim
+++ b/runtime/ftplugin/rust.vim
@@ -1,20 +1,26 @@
" Language: Rust
" Description: Vim ftplugin for Rust
" Maintainer: Chris Morgan <me@chrismorgan.info>
-" Maintainer: Kevin Ballard <kevin@sb.org>
-" Last Change: June 08, 2016
-" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
+" Last Change: 2023-09-11
+" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
if exists("b:did_ftplugin")
- finish
+ finish
endif
let b:did_ftplugin = 1
+" vint: -ProhibitAbbreviationOption
let s:save_cpo = &cpo
set cpo&vim
-
-augroup rust.vim
-autocmd!
+" vint: +ProhibitAbbreviationOption
+
+if get(b:, 'current_compiler', '') ==# ''
+ if strlen(findfile('Cargo.toml', '.;')) > 0
+ compiler cargo
+ else
+ compiler rustc
+ endif
+endif
" Variables {{{1
@@ -22,13 +28,13 @@ autocmd!
" comments, so we'll use that as our default, but make it easy to switch.
" This does not affect indentation at all (I tested it with and without
" leader), merely whether a leader is inserted by default or not.
-if exists("g:rust_bang_comment_leader") && g:rust_bang_comment_leader != 0
- " Why is the `,s0:/*,mb:\ ,ex:*/` there, you ask? I don't understand why,
- " but without it, */ gets indented one space even if there were no
- " leaders. I'm fairly sure that's a Vim bug.
- setlocal comments=s1:/*,mb:*,ex:*/,s0:/*,mb:\ ,ex:*/,:///,://!,://
+if get(g:, 'rust_bang_comment_leader', 0)
+ " Why is the `,s0:/*,mb:\ ,ex:*/` there, you ask? I don't understand why,
+ " but without it, */ gets indented one space even if there were no
+ " leaders. I'm fairly sure that's a Vim bug.
+ setlocal comments=s1:/*,mb:*,ex:*/,s0:/*,mb:\ ,ex:*/,:///,://!,://
else
- setlocal comments=s0:/*!,m:\ ,ex:*/,s1:/*,mb:*,ex:*/,:///,://!,://
+ setlocal comments=s0:/*!,ex:*/,s1:/*,mb:*,ex:*/,:///,://!,://
endif
setlocal commentstring=//%s
setlocal formatoptions-=t formatoptions+=croqnl
@@ -39,13 +45,14 @@ silent! setlocal formatoptions+=j
" otherwise it's better than nothing.
setlocal smartindent nocindent
-if !exists("g:rust_recommended_style") || g:rust_recommended_style != 0
- setlocal tabstop=4 shiftwidth=4 softtabstop=4 expandtab
- setlocal textwidth=99
+if get(g:, 'rust_recommended_style', 1)
+ let b:rust_set_style = 1
+ setlocal shiftwidth=4 softtabstop=4 expandtab
+ setlocal textwidth=99
endif
-" This includeexpr isn't perfect, but it's a good start
-setlocal includeexpr=substitute(v:fname,'::','/','g')
+setlocal include=\\v^\\s*(pub\\s+)?use\\s+\\zs(\\f\|:)+
+setlocal includeexpr=rust#IncludeExpr(v:fname)
setlocal suffixesadd=.rs
@@ -54,51 +61,36 @@ if exists("g:ftplugin_rust_source_path")
endif
if exists("g:loaded_delimitMate")
- if exists("b:delimitMate_excluded_regions")
- let b:rust_original_delimitMate_excluded_regions = b:delimitMate_excluded_regions
- endif
-
- let s:delimitMate_extra_excluded_regions = ',rustLifetimeCandidate,rustGenericLifetimeCandidate'
-
- " For this buffer, when delimitMate issues the `User delimitMate_map`
- " event in the autocommand system, add the above-defined extra excluded
- " regions to delimitMate's state, if they have not already been added.
- autocmd User <buffer>
- \ if expand('<afile>') ==# 'delimitMate_map' && match(
- \ delimitMate#Get("excluded_regions"),
- \ s:delimitMate_extra_excluded_regions) == -1
- \| let b:delimitMate_excluded_regions =
- \ delimitMate#Get("excluded_regions")
- \ . s:delimitMate_extra_excluded_regions
- \|endif
-
- " For this buffer, when delimitMate issues the `User delimitMate_unmap`
- " event in the autocommand system, delete the above-defined extra excluded
- " regions from delimitMate's state (the deletion being idempotent and
- " having no effect if the extra excluded regions are not present in the
- " targeted part of delimitMate's state).
- autocmd User <buffer>
- \ if expand('<afile>') ==# 'delimitMate_unmap'
- \| let b:delimitMate_excluded_regions = substitute(
- \ delimitMate#Get("excluded_regions"),
- \ '\C\V' . s:delimitMate_extra_excluded_regions,
- \ '', 'g')
- \|endif
+ if exists("b:delimitMate_excluded_regions")
+ let b:rust_original_delimitMate_excluded_regions = b:delimitMate_excluded_regions
+ endif
+
+ augroup rust.vim.DelimitMate
+ autocmd!
+
+ autocmd User delimitMate_map :call rust#delimitmate#onMap()
+ autocmd User delimitMate_unmap :call rust#delimitmate#onUnmap()
+ augroup END
+endif
+
+" Integration with auto-pairs (https://github.com/jiangmiao/auto-pairs)
+if exists("g:AutoPairsLoaded") && !get(g:, 'rust_keep_autopairs_default', 0)
+ let b:AutoPairs = {'(':')', '[':']', '{':'}','"':'"', '`':'`'}
endif
-if has("folding") && exists('g:rust_fold') && g:rust_fold != 0
- let b:rust_set_foldmethod=1
- setlocal foldmethod=syntax
- if g:rust_fold == 2
- setlocal foldlevel<
- else
- setlocal foldlevel=99
- endif
+if has("folding") && get(g:, 'rust_fold', 0)
+ let b:rust_set_foldmethod=1
+ setlocal foldmethod=syntax
+ if g:rust_fold == 2
+ setlocal foldlevel<
+ else
+ setlocal foldlevel=99
+ endif
endif
-if has('conceal') && exists('g:rust_conceal') && g:rust_conceal != 0
- let b:rust_set_conceallevel=1
- setlocal conceallevel=2
+if has('conceal') && get(g:, 'rust_conceal', 0)
+ let b:rust_set_conceallevel=1
+ setlocal conceallevel=2
endif
" Motion Commands {{{1
@@ -126,72 +118,122 @@ command! -nargs=* -buffer RustEmitIr call rust#Emit("llvm-ir", <q-args>)
command! -nargs=* -buffer RustEmitAsm call rust#Emit("asm", <q-args>)
" See |:RustPlay| for docs
-command! -range=% RustPlay :call rust#Play(<count>, <line1>, <line2>, <f-args>)
+command! -range=% -buffer RustPlay :call rust#Play(<count>, <line1>, <line2>, <f-args>)
" See |:RustFmt| for docs
-command! -buffer RustFmt call rustfmt#Format()
+command! -bar -buffer RustFmt call rustfmt#Format()
" See |:RustFmtRange| for docs
command! -range -buffer RustFmtRange call rustfmt#FormatRange(<line1>, <line2>)
-" Mappings {{{1
+" See |:RustInfo| for docs
+command! -bar -buffer RustInfo call rust#debugging#Info()
+
+" See |:RustInfoToClipboard| for docs
+command! -bar -buffer RustInfoToClipboard call rust#debugging#InfoToClipboard()
+
+" See |:RustInfoToFile| for docs
+command! -bar -nargs=1 -buffer RustInfoToFile call rust#debugging#InfoToFile(<f-args>)
-" Bind ⌘R in MacVim to :RustRun
-nnoremap <silent> <buffer> <D-r> :RustRun<CR>
-" Bind ⌘⇧R in MacVim to :RustRun! pre-filled with the last args
-nnoremap <buffer> <D-R> :RustRun! <C-r>=join(b:rust_last_rustc_args)<CR><C-\>erust#AppendCmdLine(' -- ' . join(b:rust_last_args))<CR>
+" See |:RustTest| for docs
+command! -buffer -nargs=* -count -bang RustTest call rust#Test(<q-mods>, <count>, <bang>0, <q-args>)
if !exists("b:rust_last_rustc_args") || !exists("b:rust_last_args")
- let b:rust_last_rustc_args = []
- let b:rust_last_args = []
+ let b:rust_last_rustc_args = []
+ let b:rust_last_args = []
endif
" Cleanup {{{1
let b:undo_ftplugin = "
- \ setlocal formatoptions< comments< commentstring< includeexpr< suffixesadd<
- \|setlocal tabstop< shiftwidth< softtabstop< expandtab< textwidth<
- \|if exists('b:rust_original_delimitMate_excluded_regions')
- \|let b:delimitMate_excluded_regions = b:rust_original_delimitMate_excluded_regions
- \|unlet b:rust_original_delimitMate_excluded_regions
- \|else
- \|unlet! b:delimitMate_excluded_regions
- \|endif
- \|if exists('b:rust_set_foldmethod')
- \|setlocal foldmethod< foldlevel<
- \|unlet b:rust_set_foldmethod
- \|endif
- \|if exists('b:rust_set_conceallevel')
- \|setlocal conceallevel<
- \|unlet b:rust_set_conceallevel
- \|endif
- \|unlet! b:rust_last_rustc_args b:rust_last_args
- \|delcommand RustRun
- \|delcommand RustExpand
- \|delcommand RustEmitIr
- \|delcommand RustEmitAsm
- \|delcommand RustPlay
- \|nunmap <buffer> <D-r>
- \|nunmap <buffer> <D-R>
- \|nunmap <buffer> [[
- \|nunmap <buffer> ]]
- \|xunmap <buffer> [[
- \|xunmap <buffer> ]]
- \|ounmap <buffer> [[
- \|ounmap <buffer> ]]
- \|set matchpairs-=<:>
- \"
+ \ setlocal formatoptions< comments< commentstring< include< includeexpr< suffixesadd<
+ \|if exists('b:rust_set_style')
+ \|setlocal tabstop< shiftwidth< softtabstop< expandtab< textwidth<
+ \|endif
+ \|if exists('b:rust_original_delimitMate_excluded_regions')
+ \|let b:delimitMate_excluded_regions = b:rust_original_delimitMate_excluded_regions
+ \|unlet b:rust_original_delimitMate_excluded_regions
+ \|else
+ \|unlet! b:delimitMate_excluded_regions
+ \|endif
+ \|if exists('b:rust_set_foldmethod')
+ \|setlocal foldmethod< foldlevel<
+ \|unlet b:rust_set_foldmethod
+ \|endif
+ \|if exists('b:rust_set_conceallevel')
+ \|setlocal conceallevel<
+ \|unlet b:rust_set_conceallevel
+ \|endif
+ \|unlet! b:rust_last_rustc_args b:rust_last_args
+ \|delcommand -buffer RustRun
+ \|delcommand -buffer RustExpand
+ \|delcommand -buffer RustEmitIr
+ \|delcommand -buffer RustEmitAsm
+ \|delcommand -buffer RustPlay
+ \|delcommand -buffer RustFmt
+ \|delcommand -buffer RustFmtRange
+ \|delcommand -buffer RustInfo
+ \|delcommand -buffer RustInfoToClipboard
+ \|delcommand -buffer RustInfoToFile
+ \|delcommand -buffer RustTest
+ \|nunmap <buffer> [[
+ \|nunmap <buffer> ]]
+ \|xunmap <buffer> [[
+ \|xunmap <buffer> ]]
+ \|ounmap <buffer> [[
+ \|ounmap <buffer> ]]
+ \|setlocal matchpairs-=<:>
+ \|unlet b:match_skip
+ \"
" }}}1
" Code formatting on save
-if get(g:, "rustfmt_autosave", 0)
- autocmd BufWritePre *.rs silent! call rustfmt#Format()
-endif
-
+augroup rust.vim.PreWrite
+ autocmd!
+ autocmd BufWritePre *.rs silent! call rustfmt#PreWrite()
augroup END
+setlocal matchpairs+=<:>
+" For matchit.vim (rustArrow stops `Fn() -> X` messing things up)
+let b:match_skip = 's:comment\|string\|rustCharacter\|rustArrow'
+
+command! -buffer -nargs=+ Cargo call cargo#cmd(<q-args>)
+command! -buffer -nargs=* Cbuild call cargo#build(<q-args>)
+command! -buffer -nargs=* Ccheck call cargo#check(<q-args>)
+command! -buffer -nargs=* Cclean call cargo#clean(<q-args>)
+command! -buffer -nargs=* Cdoc call cargo#doc(<q-args>)
+command! -buffer -nargs=+ Cnew call cargo#new(<q-args>)
+command! -buffer -nargs=* Cinit call cargo#init(<q-args>)
+command! -buffer -nargs=* Crun call cargo#run(<q-args>)
+command! -buffer -nargs=* Ctest call cargo#test(<q-args>)
+command! -buffer -nargs=* Cbench call cargo#bench(<q-args>)
+command! -buffer -nargs=* Cupdate call cargo#update(<q-args>)
+command! -buffer -nargs=* Csearch call cargo#search(<q-args>)
+command! -buffer -nargs=* Cpublish call cargo#publish(<q-args>)
+command! -buffer -nargs=* Cinstall call cargo#install(<q-args>)
+command! -buffer -nargs=* Cruntarget call cargo#runtarget(<q-args>)
+
+let b:undo_ftplugin .= '
+ \|delcommand -buffer Cargo
+ \|delcommand -buffer Cbuild
+ \|delcommand -buffer Ccheck
+ \|delcommand -buffer Cclean
+ \|delcommand -buffer Cdoc
+ \|delcommand -buffer Cnew
+ \|delcommand -buffer Cinit
+ \|delcommand -buffer Crun
+ \|delcommand -buffer Ctest
+ \|delcommand -buffer Cbench
+ \|delcommand -buffer Cupdate
+ \|delcommand -buffer Csearch
+ \|delcommand -buffer Cpublish
+ \|delcommand -buffer Cinstall
+ \|delcommand -buffer Cruntarget'
+
+" vint: -ProhibitAbbreviationOption
let &cpo = s:save_cpo
unlet s:save_cpo
+" vint: +ProhibitAbbreviationOption
-" vim: set noet sw=8 ts=8:
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/ftplugin/scala.vim b/runtime/ftplugin/scala.vim
index b484df99f3..769499cfc1 100644
--- a/runtime/ftplugin/scala.vim
+++ b/runtime/ftplugin/scala.vim
@@ -4,6 +4,7 @@
" URL: https://github.com/derekwyatt/vim-scala
" License: Same as Vim
" Last Change: 11 August 2021
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
" ----------------------------------------------------------------------------
if exists('b:did_ftplugin') || &cp
@@ -32,4 +33,6 @@ setlocal includeexpr=substitute(v:fname,'\\.','/','g')
setlocal path+=src/main/scala,src/test/scala
setlocal suffixesadd=.scala
+let b:undo_ftplugin = "setlocal cms< com< et< fo< inc< inex< pa< sts< sua< sw<"
+
" vim:set sw=2 sts=2 ts=8 et:
diff --git a/runtime/ftplugin/sed.vim b/runtime/ftplugin/sed.vim
new file mode 100644
index 0000000000..0073872877
--- /dev/null
+++ b/runtime/ftplugin/sed.vim
@@ -0,0 +1,29 @@
+" Vim filetype plugin file
+" Language: sed
+" Maintainer: Doug Kearns <dougkearns@gmail.com>
+" Last Change: 2020 Apr 1
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+setlocal comments=:#
+setlocal commentstring=#\ %s
+setlocal formatoptions-=t formatoptions+=croql
+
+let b:undo_ftplugin = "setl com< cms< fo<"
+
+if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
+ let b:browsefilter = "sed Script Files (*.sed)\t*.sed\n" ..
+ \ "All Files (*.*)\t*.*\n"
+ let b:undo_ftplugin ..= " | unlet! b:browsefilter"
+endif
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim: nowrap sw=2 sts=2 ts=8
diff --git a/runtime/ftplugin/sexplib.vim b/runtime/ftplugin/sexplib.vim
index 27e1b28370..b0767ef559 100644
--- a/runtime/ftplugin/sexplib.vim
+++ b/runtime/ftplugin/sexplib.vim
@@ -3,6 +3,7 @@
" Maintainer: Markus Mottl <markus.mottl@gmail.com>
" URL: https://github.com/ocaml/vim-ocaml
" Last Change:
+" 2023 Aug 28 - Added undo_ftplugin (Vim Project)
" 2017 Apr 12 - First version (MM)
if exists("b:did_ftplugin")
@@ -13,3 +14,5 @@ let b:did_ftplugin=1
" Comment string
setl commentstring=;\ %s
setl comments=:;
+
+let b:undo_ftplugin = "setl cms< com<"
diff --git a/runtime/ftplugin/sh.vim b/runtime/ftplugin/sh.vim
index b6fdb8f3e2..c1a6bc5ade 100644
--- a/runtime/ftplugin/sh.vim
+++ b/runtime/ftplugin/sh.vim
@@ -2,15 +2,15 @@
" Language: sh
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainer: Dan Sharp
-" Last Change: 2022 Sep 07
+" Contributor: Enno Nagel <ennonagel+vim@gmail.com>
+" Eisuke Kawashima
+" Last Change: 2023 Sep 28
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
-" Make sure the continuation lines below do not cause problems in
-" compatibility mode.
let s:save_cpo = &cpo
set cpo-=C
@@ -32,16 +32,26 @@ if exists("loaded_matchit") && !exists("b:match_words")
let b:undo_ftplugin ..= " | unlet! b:match_ignorecase b:match_words"
endif
-" Change the :browse e filter to primarily show shell-related files.
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "Bourne Shell Scripts (*.sh)\t*.sh\n" ..
- \ "Korn Shell Scripts (*.ksh)\t*.ksh\n" ..
- \ "Bash Shell Scripts (*.bash)\t*.bash\n" ..
- \ "All Files (*.*)\t*.*\n"
+ let b:browsefilter = "Bourne Shell Scripts (*.sh)\t*.sh\n" ..
+ \ "Korn Shell Scripts (*.ksh)\t*.ksh\n" ..
+ \ "Bash Shell Scripts (*.bash)\t*.bash\n" ..
+ \ "All Files (*.*)\t*.*\n"
let b:undo_ftplugin ..= " | unlet! b:browsefilter"
endif
-" Restore the saved compatibility options.
+if get(b:, "is_bash", 0)
+ if !has("gui_running") && executable("less")
+ command! -buffer -nargs=1 ShKeywordPrg silent exe '!bash -c "{ help "<args>" 2>/dev/null || man "<args>"; } | LESS= less"' | redraw!
+ elseif has("terminal")
+ command! -buffer -nargs=1 ShKeywordPrg silent exe ':term bash -c "help "<args>" 2>/dev/null || man "<args>""'
+ else
+ command! -buffer -nargs=1 ShKeywordPrg echo system('bash -c "help <args>" 2>/dev/null || man "<args>"')
+ endif
+ setlocal keywordprg=:ShKeywordPrg
+ let b:undo_ftplugin ..= " | setl kp< | sil! delc -buffer ShKeywordPrg"
+endif
+
let &cpo = s:save_cpo
unlet s:save_cpo
diff --git a/runtime/ftplugin/solidity.vim b/runtime/ftplugin/solidity.vim
new file mode 100644
index 0000000000..abe9f2ff26
--- /dev/null
+++ b/runtime/ftplugin/solidity.vim
@@ -0,0 +1,15 @@
+" Vim filetype plugin file
+" Language: Solidity
+" Maintainer: Cothi (jiungdev@gmail.com)
+" Original Author: tomlion (https://github.com/tomlion/vim-solidity)
+" Last Change: 2022 Sep 27
+" 2023 Aug 22 Vim Project (did_ftplugin, undo_ftplugin)
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+setlocal commentstring=//\ %s
+
+let b:undo_ftplugin = "setlocal commentstring<"
diff --git a/runtime/ftplugin/sql.vim b/runtime/ftplugin/sql.vim
index 1c02a98c7c..7a29d39f8e 100644
--- a/runtime/ftplugin/sql.vim
+++ b/runtime/ftplugin/sql.vim
@@ -140,7 +140,7 @@ if !exists("*SQL_SetType")
\ )
" Remove duplicates, since sqlanywhere.vim can exist in the
- " sytax, indent and ftplugin directory, yet we only want
+ " syntax, indent and ftplugin directory, yet we only want
" to display the option once
let index = match(sqls, '.\{-}\ze\n')
while index > -1
@@ -204,7 +204,7 @@ if !exists("*SQL_SetType")
endif
let b:sql_type_override = new_sql_type
- " Remove any cached SQL since a new sytax will have different
+ " Remove any cached SQL since a new syntax will have different
" items and groups
if !exists('g:loaded_sql_completion') || g:loaded_sql_completion >= 100
call sqlcomplete#ResetCacheSyntax()
diff --git a/runtime/ftplugin/sshconfig.vim b/runtime/ftplugin/sshconfig.vim
index d933ce0527..4a054da52f 100644
--- a/runtime/ftplugin/sshconfig.vim
+++ b/runtime/ftplugin/sshconfig.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
-" Language: OpenSSH client configuration file
-" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2008-07-09
+" Language: OpenSSH client configuration file
+" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -11,9 +11,24 @@ let b:did_ftplugin = 1
let s:cpo_save = &cpo
set cpo&vim
-let b:undo_ftplugin = "setl com< cms< fo<"
-
setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
+let b:undo_ftplugin = 'setlocal com< cms< fo<'
+
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 SshconfigKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s+' . <q-args> . '$'' --hilite-search" man ' . 'ssh_config' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 SshconfigKeywordPrg
+ \ silent exe 'term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s+' . <q-args> . '$', '\') . ''' --hilite-search" man ' . 'ssh_config'
+ endif
+ if exists(':SshconfigKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:SshconfigKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer SshconfigKeywordPrg'
+ endif
+endif
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/sudoers.vim b/runtime/ftplugin/sudoers.vim
index 38dbf559ee..81ce7906a9 100644
--- a/runtime/ftplugin/sudoers.vim
+++ b/runtime/ftplugin/sudoers.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: sudoers(5) configuration files
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2008-07-09
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -15,5 +15,21 @@ let b:undo_ftplugin = "setl com< cms< fo<"
setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 SudoersKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''\b' . <q-args> . '\b'' --hilite-search" man ' . 'sudoers' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 SudoersKeywordPrg
+ \ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('\b' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'sudoers'
+ endif
+ if exists(':SudoersKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:SudoersKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer SudoersKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/swig.vim b/runtime/ftplugin/swig.vim
new file mode 100644
index 0000000000..506c929a43
--- /dev/null
+++ b/runtime/ftplugin/swig.vim
@@ -0,0 +1,13 @@
+" Vim filetype plugin file
+" Language: SWIG
+" Maintainer: Julien Marrec <julien.marrec 'at' gmail com>
+" Last Change: 2023 November 23
+
+" Only do this when not done yet for this buffer
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setlocal iskeyword<"
+setlocal iskeyword+=%
diff --git a/runtime/ftplugin/systemd.vim b/runtime/ftplugin/systemd.vim
index 4c5c9a1dc1..8bcacdd381 100644
--- a/runtime/ftplugin/systemd.vim
+++ b/runtime/ftplugin/systemd.vim
@@ -1,41 +1,37 @@
" Vim filetype plugin file
" Language: systemd.unit(5)
" Keyword Lookup Support: Enno Nagel <enno.nagel+vim@gmail.com>
+" Latest Revision: 2023-10-07
if !exists('b:did_ftplugin')
" Looks a lot like dosini files.
runtime! ftplugin/dosini.vim
endif
-if !has('unix')
- finish
-endif
-
-if !has('gui_running')
- command! -buffer -nargs=1 Sman silent exe '!' . KeywordLookup_systemd(<q-args>) | redraw!
-elseif has('terminal')
- command! -buffer -nargs=1 Sman silent exe 'term ' . KeywordLookup_systemd(<q-args>)
-else
- finish
-endif
-
-if !exists('*KeywordLookup_systemd')
- function KeywordLookup_systemd(keyword) abort
- let matches = matchlist(getline(search('\v^\s*\[\s*.+\s*\]\s*$', 'nbWz')), '\v^\s*\[\s*(\k+).*\]\s*$')
- if len(matches) > 1
- let section = matches[1]
- return 'LESS= MANPAGER="less --pattern=''(^|,)\s+' . a:keyword . '=$'' --hilite-search" man ' . 'systemd.' . section
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 SystemdKeywordPrg silent exe '!' . KeywordLookup_systemd(<q-args>) | redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 SystemdKeywordPrg silent exe 'term ' . KeywordLookup_systemd(<q-args>)
+ endif
+ if exists(':SystemdKeywordPrg') == 2
+ if !exists('*KeywordLookup_systemd')
+ function KeywordLookup_systemd(keyword) abort
+ let matches = matchlist(getline(search('\v^\s*\[\s*.+\s*\]\s*$', 'nbWz')), '\v^\s*\[\s*(\k+).*\]\s*$')
+ if len(matches) > 1
+ let section = matches[1]
+ return 'LESS= MANPAGER="less --pattern=''(^|,)\s+' . a:keyword . '=$'' --hilite-search" man ' . 'systemd.' . section
+ else
+ return 'LESS= MANPAGER="less --pattern=''(^|,)\s+' . a:keyword . '=$'' --hilite-search" man ' . 'systemd'
+ endif
+ endfunction
+ endif
+ setlocal iskeyword+=-
+ setlocal keywordprg=:SystemdKeywordPrg
+ if !exists('b:undo_ftplugin') || empty(b:undo_ftplugin)
+ let b:undo_ftplugin = 'setlocal keywordprg< iskeyword<'
else
- return 'LESS= MANPAGER="less --pattern=''(^|,)\s+' . a:keyword . '=$'' --hilite-search" man ' . 'systemd'
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer SystemdKeywordPrg'
endif
- endfunction
-endif
-
-setlocal iskeyword+=-
-setlocal keywordprg=:Sman
-
-if !exists('b:undo_ftplugin') || empty(b:undo_ftplugin)
- let b:undo_ftplugin = 'setlocal keywordprg< iskeyword<'
-else
- let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword<'
+ endif
endif
diff --git a/runtime/ftplugin/tcsh.vim b/runtime/ftplugin/tcsh.vim
index 85d3873b33..b2467b43a2 100644
--- a/runtime/ftplugin/tcsh.vim
+++ b/runtime/ftplugin/tcsh.vim
@@ -2,9 +2,11 @@
" Language: tcsh
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainer: Dan Sharp
-" Last Change: 2021 Oct 15
+" Last Change: 2023 Oct 09
-if exists("b:did_ftplugin") | finish | endif
+if exists("b:did_ftplugin")
+ finish
+endif
let s:save_cpo = &cpo
set cpo-=C
@@ -12,24 +14,26 @@ set cpo-=C
" Define some defaults in case the included ftplugins don't set them.
let s:undo_ftplugin = ""
let s:browsefilter = "csh Files (*.csh)\t*.csh\n" ..
- \ "All Files (*.*)\t*.*\n"
+ \ "All Files (*.*)\t*.*\n"
runtime! ftplugin/csh.vim ftplugin/csh_*.vim ftplugin/csh/*.vim
let b:did_ftplugin = 1
" Override our defaults if these were set by an included ftplugin.
if exists("b:undo_ftplugin")
- let s:undo_ftplugin = b:undo_ftplugin
+ let s:undo_ftplugin = b:undo_ftplugin
endif
if exists("b:browsefilter")
- let s:browsefilter = b:browsefilter
+ let s:browsefilter = b:browsefilter
endif
-if (has("gui_win32") || has("gui_gtk"))
- let b:browsefilter="tcsh Scripts (*.tcsh)\t*.tcsh\n" .. s:browsefilter
+if (has("gui_win32") || has("gui_gtk")) &&
+ \ (!exists("b:browsefilter") || exists("b:csh_set_browsefilter"))
+ let b:browsefilter = "tcsh Scripts (*.tcsh)\t*.tcsh\n" .. s:browsefilter
+ let s:undo_ftplugin = "unlet! b:browsefilter | " .. s:undo_ftplugin
endif
-let b:undo_ftplugin = "unlet! b:browsefilter | " .. s:undo_ftplugin
+let b:undo_ftplugin = s:undo_ftplugin
let &cpo = s:save_cpo
unlet s:save_cpo
diff --git a/runtime/ftplugin/tidy.vim b/runtime/ftplugin/tidy.vim
index 470548d83a..b81b66db4a 100644
--- a/runtime/ftplugin/tidy.vim
+++ b/runtime/ftplugin/tidy.vim
@@ -1,5 +1,5 @@
" Vim filetype plugin file
-" Language: HMTL Tidy Configuration
+" Language: HTML Tidy Configuration
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Last Change: 2020 Sep 4
diff --git a/runtime/ftplugin/toml.vim b/runtime/ftplugin/toml.vim
index 1ef09a16e3..6bd79b1c0a 100644
--- a/runtime/ftplugin/toml.vim
+++ b/runtime/ftplugin/toml.vim
@@ -2,7 +2,7 @@
" Language: TOML
" Homepage: https://github.com/cespare/vim-toml
" Maintainer: Aman Verma
-" Author: Kevin Ballard <kevin@sb.org>
+" Author: Lily Ballard <lily@ballards.net>
" Last Change: Sep 21, 2021
if exists('b:did_ftplugin')
diff --git a/runtime/ftplugin/udevrules.vim b/runtime/ftplugin/udevrules.vim
index 6404f6c85e..ec365f04c2 100644
--- a/runtime/ftplugin/udevrules.vim
+++ b/runtime/ftplugin/udevrules.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: udev(8) rules file
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2008-07-09
+" Latest Revision: 2023-10-07
if exists("b:did_ftplugin")
finish
@@ -15,5 +15,21 @@ let b:undo_ftplugin = "setl com< cms< fo<"
setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql
+if has('unix') && executable('less')
+ if !has('gui_running')
+ command -buffer -nargs=1 UdevrulesKeywordPrg
+ \ silent exe '!' . 'LESS= MANPAGER="less --pattern=''^\s{,8}' . <q-args> . '\b'' --hilite-search" man ' . 'udev' |
+ \ redraw!
+ elseif has('terminal')
+ command -buffer -nargs=1 UdevrulesKeywordPrg
+ \ silent exe ':term ' . 'env LESS= MANPAGER="less --pattern=''' . escape('^\s{,8}' . <q-args> . '\b', '\') . ''' --hilite-search" man ' . 'udev'
+ endif
+ if exists(':UdevrulesKeywordPrg') == 2
+ setlocal iskeyword+=-
+ setlocal keywordprg=:UdevrulesKeywordPrg
+ let b:undo_ftplugin .= '| setlocal keywordprg< iskeyword< | sil! delc -buffer UdevrulesKeywordPrg'
+ endif
+endif
+
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/unison.vim b/runtime/ftplugin/unison.vim
new file mode 100644
index 0000000000..76dbaef6aa
--- /dev/null
+++ b/runtime/ftplugin/unison.vim
@@ -0,0 +1,14 @@
+" Vim filetype plugin file
+" Language: unison
+" Maintainer: Anton Parkhomenko <anton@chuwy.me>
+" Latest Revision: 2023-08-07
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let b:undo_ftplugin = "setl cms< isk<"
+
+setlocal commentstring=--\ %s
+setlocal iskeyword+=!,'
diff --git a/runtime/ftplugin/urlshortcut.vim b/runtime/ftplugin/urlshortcut.vim
new file mode 100644
index 0000000000..ebe08ac1d8
--- /dev/null
+++ b/runtime/ftplugin/urlshortcut.vim
@@ -0,0 +1,20 @@
+" Vim filetype plugin file
+" Language: MS Windows URL shortcut file
+" Maintainer: ObserverOfTime <chronobserver@disroot.org>
+" Latest Revision: 2023-06-04
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+let s:cpo_save = &cpoptions
+set cpoptions&vim
+
+let b:undo_ftplugin = "setl com< cms< fo<"
+
+setlocal comments=:; commentstring=;\ %s
+setlocal formatoptions-=t formatoptions+=croql
+
+let &cpoptions = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/ftplugin/usd.vim b/runtime/ftplugin/usd.vim
new file mode 100644
index 0000000000..cd5013f960
--- /dev/null
+++ b/runtime/ftplugin/usd.vim
@@ -0,0 +1,14 @@
+" Vim filetype plugin file
+" Language: Pixar Animation's Universal Scene Description format
+" Maintainer: Colin Kennedy <colinvfx@gmail.com>
+" Last Change: 2023 May 9
+" 2023 Aug 28 by Vim Project (undo_ftplugin)
+
+if exists("b:did_ftplugin")
+ finish
+endif
+let b:did_ftplugin = 1
+
+setlocal commentstring=#\ %s
+
+let b:undo_ftplugin = "setlocal commentstring<"
diff --git a/runtime/ftplugin/vhdl.vim b/runtime/ftplugin/vhdl.vim
index 0249b542be..ff56166ebe 100644
--- a/runtime/ftplugin/vhdl.vim
+++ b/runtime/ftplugin/vhdl.vim
@@ -3,6 +3,7 @@
" Maintainer: R.Shankar <shankar.pec?gmail.com>
" Modified By: Gerald Lai <laigera+vim?gmail.com>
" Last Change: 2011 Dec 11
+" 2023 Aug 28 by Vim Project (undo_ftplugin, commentstring)
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
@@ -22,13 +23,20 @@ set cpo&vim
" Set 'comments' to format dashed lists in comments.
"setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,://
+setlocal commentstring=--\ %s
+
" Format comments to be up to 78 characters long
"setlocal tw=75
+" let b:undo_ftplugin = "setl cms< com< fo< tw<"
+
+let b:undo_ftplugin = "setl cms< "
+
" Win32 can filter files in the browse dialog
"if has("gui_win32") && !exists("b:browsefilter")
" let b:browsefilter = "Verilog Source Files (*.v)\t*.v\n" .
" \ "All Files (*.*)\t*.*\n"
+" let b:undo_ftplugin .= " | unlet! b:browsefilter"
"endif
" Let the matchit plugin know what items can be matched.
@@ -52,37 +60,49 @@ if ! exists("b:match_words") && exists("loaded_matchit")
\ s:notend.'\<package\>:\<end\s\+package\>,'.
\ s:notend.'\<procedure\>:\<end\s\+procedure\>,'.
\ s:notend.'\<configuration\>:\<end\s\+configuration\>'
+ let b:undo_ftplugin .= " | unlet! b:match_ignorecase b:match_words"
endif
-" count repeat
-function! <SID>CountWrapper(cmd)
- let i = v:count1
- if a:cmd[0] == ":"
- while i > 0
- execute a:cmd
- let i = i - 1
- endwhile
- else
- execute "normal! gv\<Esc>"
- execute "normal ".i.a:cmd
- let curcol = col(".")
- let curline = line(".")
- normal! gv
- call cursor(curline, curcol)
- endif
-endfunction
+if !exists("no_plugin_maps") && !exists("no_vhdl_maps")
+ " count repeat
+ function! <SID>CountWrapper(cmd)
+ let i = v:count1
+ if a:cmd[0] == ":"
+ while i > 0
+ execute a:cmd
+ let i = i - 1
+ endwhile
+ else
+ execute "normal! gv\<Esc>"
+ execute "normal ".i.a:cmd
+ let curcol = col(".")
+ let curline = line(".")
+ normal! gv
+ call cursor(curline, curcol)
+ endif
+ endfunction
-" explore motion
-" keywords: "architecture", "block", "configuration", "component", "entity", "function", "package", "procedure", "process", "record", "units"
-let b:vhdl_explore = '\%(architecture\|block\|configuration\|component\|entity\|function\|package\|procedure\|process\|record\|units\)'
-noremap <buffer><silent>[[ :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\%(\\<end\\s\\+\\)\\@<!\\<".b:vhdl_explore."\\>\\c\\<Bar>\\%^","bW")')<CR>
-noremap <buffer><silent>]] :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\%(\\<end\\s\\+\\)\\@<!\\<".b:vhdl_explore."\\>\\c\\<Bar>\\%$","W")')<CR>
-noremap <buffer><silent>[] :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\<end\\s\\+".b:vhdl_explore."\\>\\c\\<Bar>\\%^","bW")')<CR>
-noremap <buffer><silent>][ :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\<end\\s\\+".b:vhdl_explore."\\>\\c\\<Bar>\\%$","W")')<CR>
-vnoremap <buffer><silent>[[ :<C-u>cal <SID>CountWrapper('[[')<CR>
-vnoremap <buffer><silent>]] :<C-u>cal <SID>CountWrapper(']]')<CR>
-vnoremap <buffer><silent>[] :<C-u>cal <SID>CountWrapper('[]')<CR>
-vnoremap <buffer><silent>][ :<C-u>cal <SID>CountWrapper('][')<CR>
+ " explore motion
+ " keywords: "architecture", "block", "configuration", "component", "entity", "function", "package", "procedure", "process", "record", "units"
+ let b:vhdl_explore = '\%(architecture\|block\|configuration\|component\|entity\|function\|package\|procedure\|process\|record\|units\)'
+ noremap <buffer><silent>[[ :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\%(\\<end\\s\\+\\)\\@<!\\<".b:vhdl_explore."\\>\\c\\<Bar>\\%^","bW")')<CR>
+ noremap <buffer><silent>]] :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\%(\\<end\\s\\+\\)\\@<!\\<".b:vhdl_explore."\\>\\c\\<Bar>\\%$","W")')<CR>
+ noremap <buffer><silent>[] :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\<end\\s\\+".b:vhdl_explore."\\>\\c\\<Bar>\\%^","bW")')<CR>
+ noremap <buffer><silent>][ :<C-u>cal <SID>CountWrapper(':cal search("\\%(--.*\\)\\@<!\\<end\\s\\+".b:vhdl_explore."\\>\\c\\<Bar>\\%$","W")')<CR>
+ vnoremap <buffer><silent>[[ :<C-u>cal <SID>CountWrapper('[[')<CR>
+ vnoremap <buffer><silent>]] :<C-u>cal <SID>CountWrapper(']]')<CR>
+ vnoremap <buffer><silent>[] :<C-u>cal <SID>CountWrapper('[]')<CR>
+ vnoremap <buffer><silent>][ :<C-u>cal <SID>CountWrapper('][')<CR>
+ let b:undo_ftplugin .=
+ \ " | silent! execute 'nunmap <buffer> [['" .
+ \ " | silent! execute 'nunmap <buffer> ]]'" .
+ \ " | silent! execute 'nunmap <buffer> []'" .
+ \ " | silent! execute 'nunmap <buffer> ]['" .
+ \ " | silent! execute 'vunmap <buffer> [['" .
+ \ " | silent! execute 'vunmap <buffer> ]]'" .
+ \ " | silent! execute 'vunmap <buffer> []'" .
+ \ " | silent! execute 'vunmap <buffer> ]['"
+endif
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/ftplugin/vim.vim b/runtime/ftplugin/vim.vim
index b64bb55d68..06369e8a82 100644
--- a/runtime/ftplugin/vim.vim
+++ b/runtime/ftplugin/vim.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin
" Language: Vim
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Nov 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
@@ -83,8 +84,8 @@ if !exists("no_plugin_maps") && !exists("no_vim_maps")
vnoremap <silent><buffer> ][ m':<C-U>exe "normal! gv"<Bar>call search('^\s*end\(f\%[unction]\\|def\)\>', "W")<CR>
" Move around comments
- nnoremap <silent><buffer> ]" :call search('^\(\s*".*\n\)\@<!\(\s*"\)', "W")<CR>
- vnoremap <silent><buffer> ]" :<C-U>exe "normal! gv"<Bar>call search('^\(\s*".*\n\)\@<!\(\s*"\)', "W")<CR>
+ nnoremap <silent><buffer> ]" :call search('\%(^\s*".*\n\)\@<!\%(^\s*"\)', "W")<CR>
+ vnoremap <silent><buffer> ]" :<C-U>exe "normal! gv"<Bar>call search('\%(^\s*".*\n\)\@<!\%(^\s*"\)', "W")<CR>
nnoremap <silent><buffer> [" :call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
vnoremap <silent><buffer> [" :<C-U>exe "normal! gv"<Bar>call search('\%(^\s*".*\n\)\%(^\s*"\)\@!', "bW")<CR>
endif
@@ -98,18 +99,23 @@ if exists("loaded_matchit")
" func name
" require a parenthesis following, then there can be an "endfunc".
let b:match_words =
- \ '\<\%(fu\%[nction]\|def\)!\=\s\+\S\+\s*(:\%(\%(^\||\)\s*\)\@<=\<retu\%[rn]\>:\%(\%(^\||\)\s*\)\@<=\<\%(endf\%[unction]\|enddef\)\>,' .
- \ '\<\(wh\%[ile]\|for\)\>:\%(\%(^\||\)\s*\)\@<=\<brea\%[k]\>:\%(\%(^\||\)\s*\)\@<=\<con\%[tinue]\>:\%(\%(^\||\)\s*\)\@<=\<end\(w\%[hile]\|fo\%[r]\)\>,' .
- \ '\<if\>:\%(\%(^\||\)\s*\)\@<=\<el\%[seif]\>:\%(\%(^\||\)\s*\)\@<=\<en\%[dif]\>,' .
- \ '{:},' .
- \ '\<try\>:\%(\%(^\||\)\s*\)\@<=\<cat\%[ch]\>:\%(\%(^\||\)\s*\)\@<=\<fina\%[lly]\>:\%(\%(^\||\)\s*\)\@<=\<endt\%[ry]\>,' .
- \ '\<aug\%[roup]\s\+\%(END\>\)\@!\S:\<aug\%[roup]\s\+END\>,'
+ \ '\<\%(fu\%[nction]\|def\)!\=\s\+\S\+\s*(:\%(\%(^\||\)\s*\)\@<=\<retu\%[rn]\>:\%(\%(^\||\)\s*\)\@<=\<\%(endf\%[unction]\|enddef\)\>,' ..
+ \ '\<\%(wh\%[ile]\|for\)\>:\%(\%(^\||\)\s*\)\@<=\<brea\%[k]\>:\%(\%(^\||\)\s*\)\@<=\<con\%[tinue]\>:\%(\%(^\||\)\s*\)\@<=\<end\%(w\%[hile]\|fo\%[r]\)\>,' ..
+ \ '\<if\>:\%(\%(^\||\)\s*\)\@<=\<el\%[seif]\>:\%(\%(^\||\)\s*\)\@<=\<en\%[dif]\>,' ..
+ \ '{:},' ..
+ \ '\<try\>:\%(\%(^\||\)\s*\)\@<=\<cat\%[ch]\>:\%(\%(^\||\)\s*\)\@<=\<fina\%[lly]\>:\%(\%(^\||\)\s*\)\@<=\<endt\%[ry]\>,' ..
+ \ '\<aug\%[roup]\s\+\%(END\>\)\@!\S:\<aug\%[roup]\s\+END\>,' ..
+ \ '\<class\>:\<endclass\>,' ..
+ \ '\<inte\%[rface]\>:\<endinterface\>,' ..
+ \ '\<enu\%[m]\>:\<endenum\>,'
+
" Ignore syntax region commands and settings, any 'en*' would clobber
" if-endif.
" - set spl=de,en
" - au! FileType javascript syntax region foldBraces start=/{/ end=/}/ …
- let b:match_skip = 'synIDattr(synID(line("."),col("."),1),"name")
- \ =~? "comment\\|string\\|vimLetHereDoc\\|vimSynReg\\|vimSet"'
+ " Also ignore here-doc and dictionary keys (vimVar).
+ let b:match_skip = 'synIDattr(synID(line("."), col("."), 1), "name")
+ \ =~? "comment\\|string\\|vimSynReg\\|vimSet\\|vimLetHereDoc\\|vimVar"'
endif
let &cpo = s:cpo_save
diff --git a/runtime/ftplugin/wast.vim b/runtime/ftplugin/wat.vim
index 0d9e98d37a..35d2d6a322 100644
--- a/runtime/ftplugin/wast.vim
+++ b/runtime/ftplugin/wat.vim
@@ -1,7 +1,7 @@
" Vim filetype plugin file
" Language: WebAssembly
" Maintainer: rhysd <lin90162@yahoo.co.jp>
-" Last Change: Jul 29, 2018
+" Last Change: Nov 14, 2023
" For bugs, patches and license go to https://github.com/rhysd/vim-wasm
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/wget.vim b/runtime/ftplugin/wget.vim
index 7a10221824..db3b62191e 100644
--- a/runtime/ftplugin/wget.vim
+++ b/runtime/ftplugin/wget.vim
@@ -1,5 +1,5 @@
" Vim filetype plugin file
-" Language: Wget configuration file (/etc/wgetrc ~/.wgetrc)
+" Language: Wget configuration file (/etc/wgetrc ~/.wgetrc)
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Last Change: 2022 Apr 28
@@ -18,7 +18,7 @@ setlocal formatoptions-=t formatoptions+=croql
let b:undo_ftplugin = "setl fo< com< cms<"
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "Wget Configuration File (wgetrc, .wgetrc)\twgetrc;.wgetrc\n" .
+ let b:browsefilter = "Wget Configuration File (wgetrc, .wgetrc)\twgetrc;.wgetrc\n" ..
\ "All Files (*.*)\t*.*\n"
let b:undo_ftplugin ..= " | unlet! b:browsefilter"
endif
diff --git a/runtime/ftplugin/wget2.vim b/runtime/ftplugin/wget2.vim
index a6845f6df5..8cfcd514e5 100644
--- a/runtime/ftplugin/wget2.vim
+++ b/runtime/ftplugin/wget2.vim
@@ -1,5 +1,5 @@
" Vim filetype plugin file
-" Language: Wget2 configuration file (/etc/wget2rc ~/.wget2rc)
+" Language: Wget2 configuration file (/etc/wget2rc ~/.wget2rc)
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Last Change: 2022 Apr 28
@@ -18,7 +18,7 @@ setlocal formatoptions-=t formatoptions+=croql
let b:undo_ftplugin = "setl fo< com< cms<"
if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter")
- let b:browsefilter = "Wget2 Configuration File (wget2rc, .wget2rc)\twget2rc;.wget2rc\n" .
+ let b:browsefilter = "Wget2 Configuration File (wget2rc, .wget2rc)\twget2rc;.wget2rc\n" ..
\ "All Files (*.*)\t*.*\n"
let b:undo_ftplugin ..= " | unlet! b:browsefilter"
endif
diff --git a/runtime/ftplugin/xcompose.vim b/runtime/ftplugin/xcompose.vim
new file mode 100644
index 0000000000..7345c27a2e
--- /dev/null
+++ b/runtime/ftplugin/xcompose.vim
@@ -0,0 +1,13 @@
+" Vim filetype plugin file
+" Language: XCompose
+" Maintainer: ObserverOfTime <chronobserver@disroot.org
+" Last Change: 2023 Nov 09
+
+if exists('b:did_ftplugin')
+ finish
+endif
+let b:did_ftplugin = 1
+
+setl comments=:# commentstring=#\ %s
+
+let b:undo_ftplugin = 'setl com< cms<'
diff --git a/runtime/ftplugin/xdefaults.lua b/runtime/ftplugin/xdefaults.lua
new file mode 100644
index 0000000000..b4e68148f5
--- /dev/null
+++ b/runtime/ftplugin/xdefaults.lua
@@ -0,0 +1 @@
+vim.bo.commentstring = '/*%s*/'
diff --git a/runtime/ftplugin/zig.vim b/runtime/ftplugin/zig.vim
index e740a52849..28b8cd5a67 100644
--- a/runtime/ftplugin/zig.vim
+++ b/runtime/ftplugin/zig.vim
@@ -17,7 +17,7 @@ compiler zig_build
" Match Zig builtin fns
setlocal iskeyword+=@-@
-" Recomended code style, no tabs and 4-space indentation
+" Recommended code style, no tabs and 4-space indentation
setlocal expandtab
setlocal tabstop=8
setlocal softtabstop=4
@@ -28,7 +28,7 @@ setlocal formatoptions-=t formatoptions+=croql
setlocal suffixesadd=.zig,.zir
if has('comments')
- setlocal comments=:///,://!,://,:\\\\
+ setlocal comments=:///,://!,://
setlocal commentstring=//\ %s
endif
@@ -39,7 +39,9 @@ endif
let &l:define='\v(<fn>|<const>|<var>|^\s*\#\s*define)'
-if !exists('g:zig_std_dir') && exists('*json_decode') && executable('zig')
+" Safety check: don't execute zig from current directory
+if !exists('g:zig_std_dir') && exists('*json_decode') &&
+ \ executable('zig') && dist#vim#IsSafeExecutable('zig', 'zig')
silent let s:env = system('zig env')
if v:shell_error == 0
let g:zig_std_dir = json_decode(s:env)['std_dir']
@@ -48,7 +50,7 @@ if !exists('g:zig_std_dir') && exists('*json_decode') && executable('zig')
endif
if exists('g:zig_std_dir')
- let &l:path = &l:path . ',' . g:zig_std_dir
+ let &l:path = g:zig_std_dir . ',' . &l:path
endif
let b:undo_ftplugin =
diff --git a/runtime/ftplugin/zimbu.vim b/runtime/ftplugin/zimbu.vim
index cbe2f55572..94d6be758d 100644
--- a/runtime/ftplugin/zimbu.vim
+++ b/runtime/ftplugin/zimbu.vim
@@ -1,7 +1,8 @@
" Vim filetype plugin file
" Language: Zimbu
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Sep 07
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only do this when not done yet for this buffer
if exists("b:did_ftplugin")
diff --git a/runtime/ftplugin/zsh.vim b/runtime/ftplugin/zsh.vim
index 0ca8077305..40986fccbe 100644
--- a/runtime/ftplugin/zsh.vim
+++ b/runtime/ftplugin/zsh.vim
@@ -2,7 +2,7 @@
" Language: Zsh shell script
" Maintainer: Christian Brabandt <cb@256bit.org>
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2021-04-03
+" Latest Revision: 2023-10-07
" License: Vim (see :h license)
" Repository: https://github.com/chrisbra/vim-zsh
@@ -20,17 +20,17 @@ let b:undo_ftplugin = "setl com< cms< fo< "
if executable('zsh') && &shell !~# '/\%(nologin\|false\)$'
if !has('gui_running') && executable('less')
- command! -buffer -nargs=1 RunHelp silent exe '!MANPAGER= zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null | LESS= less"' | redraw!
+ command! -buffer -nargs=1 ZshKeywordPrg silent exe '!MANPAGER= zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null | LESS= less"' | redraw!
elseif has('terminal')
- command! -buffer -nargs=1 RunHelp silent exe ':term zsh -c "autoload -Uz run-help; run-help <args>"'
+ command! -buffer -nargs=1 ZshKeywordPrg silent exe ':term zsh -c "autoload -Uz run-help; run-help <args>"'
else
- command! -buffer -nargs=1 RunHelp echo system('zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null"')
+ command! -buffer -nargs=1 ZshKeywordPrg echo system('zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null"')
endif
if !exists('current_compiler')
compiler zsh
endif
- setlocal keywordprg=:RunHelp
- let b:undo_ftplugin .= 'keywordprg<'
+ setlocal keywordprg=:ZshKeywordPrg
+ let b:undo_ftplugin .= 'keywordprg< | sil! delc -buffer ZshKeywordPrg'
endif
let b:match_words = '\<if\>:\<elif\>:\<else\>:\<fi\>'
diff --git a/runtime/ftplugof.vim b/runtime/ftplugof.vim
index 7828ff2c15..593786a860 100644
--- a/runtime/ftplugof.vim
+++ b/runtime/ftplugof.vim
@@ -1,7 +1,8 @@
" Vim support file to switch off loading plugins for file types
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2011 Oct 20
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("did_load_ftplugin")
unlet did_load_ftplugin
diff --git a/runtime/indent.vim b/runtime/indent.vim
index 2b64dd44b9..3f38ff28b6 100644
--- a/runtime/indent.vim
+++ b/runtime/indent.vim
@@ -1,7 +1,8 @@
" Vim support file to switch on loading indent files for file types
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2008 Feb 22
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("did_indent_on")
finish
@@ -24,8 +25,8 @@ augroup filetypeindent
" When there is a dot it is used to separate filetype names. Thus for
" "aaa.bbb" load "indent/aaa.vim" and then "indent/bbb.vim".
for name in split(s, '\.')
- exe 'runtime! indent/' . name . '.vim'
- exe 'runtime! indent/' . name . '.lua'
+ " XXX: "[.]" in the pattern makes it a wildcard on Windows
+ exe $'runtime! indent/{name}[.]{{vim,lua}}'
endfor
endif
endfunc
diff --git a/runtime/indent/README.txt b/runtime/indent/README.txt
index 649870636e..05ab126c8c 100644
--- a/runtime/indent/README.txt
+++ b/runtime/indent/README.txt
@@ -6,15 +6,16 @@ at ":help indent-expression". Looking at the existing files should give you
inspiration.
If you make a new indent file which would be useful for others, please send it
-to Bram@vim.org. Include instructions for detecting the file type for this
-language, by file name extension or by checking a few lines in the file.
-And please stick to the rules below.
+to the vim-dev mailing list <vim-dev@vim.org>. Include instructions for
+detecting the file type for this language, by file name extension or by
+checking a few lines in the file. And please stick to the rules below.
If you have remarks about an existing file, send them to the maintainer of
-that file. Only when you get no response send a message to Bram@vim.org.
+that file. Only when you get no response send a message to the vim-dev
+mailing list: <vim-dev@vim.org>.
If you are the maintainer of an indent file and make improvements, e-mail the
-new version to Bram@vim.org.
+new version to the vim-dev mailing list: <vim-dev@vim.org>.
Rules for making an indent file:
diff --git a/runtime/indent/aap.vim b/runtime/indent/aap.vim
index 35828b4c1a..23c104985e 100644
--- a/runtime/indent/aap.vim
+++ b/runtime/indent/aap.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: Aap recipe
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2005 Jun 24
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/bash.vim b/runtime/indent/bash.vim
index b91640687c..407081232c 100644
--- a/runtime/indent/bash.vim
+++ b/runtime/indent/bash.vim
@@ -1,7 +1,7 @@
" Vim indent file
" Language: bash
-" Maintainer: Bram
-" Last Change: 2019 Sep 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/c.vim b/runtime/indent/c.vim
index e224382f63..7f285e1b98 100644
--- a/runtime/indent/c.vim
+++ b/runtime/indent/c.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: C
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2005 Mar 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/cdl.vim b/runtime/indent/cdl.vim
index 2c0fc7988e..da675698cf 100644
--- a/runtime/indent/cdl.vim
+++ b/runtime/indent/cdl.vim
@@ -21,7 +21,7 @@ endif
" find out if an "...=..." expression is an assignment (or a conditional)
" it scans 'line' first, and then the previous lines
-fun! CdlAsignment(lnum, line)
+fun! CdlAssignment(lnum, line)
let f = -1
let lnum = a:lnum
let line = a:line
@@ -90,7 +90,7 @@ fun! CdlGetIndent(lnum)
end
end
- " remove members [a] of [b]:[c]... (inicio remainds valid)
+ " remove members [a] of [b]:[c]... (inicio remains valid)
let line = substitute(line, '\c\(\[[^]]*]\(\s*of\s*\|:\)*\)\+', ' ', 'g')
while 1
" search for the next interesting element
@@ -111,7 +111,7 @@ fun! CdlGetIndent(lnum)
else " c == '='
" if it is an assignment increase indent
if f == -1 " we don't know yet, find out
- let f = CdlAsignment(lnum, strpart(line, 0, inicio))
+ let f = CdlAssignment(lnum, strpart(line, 0, inicio))
end
if f == 1 " formula increase it
let ind = ind + shiftwidth()
@@ -125,7 +125,7 @@ fun! CdlGetIndent(lnum)
let ind = ind - shiftwidth()
elseif match(thisline, '^\s*=') >= 0
if f == -1 " we don't know yet if is an assignment, find out
- let f = CdlAsignment(lnum, "")
+ let f = CdlAssignment(lnum, "")
end
if f == 1 " formula increase it
let ind = ind + shiftwidth()
diff --git a/runtime/indent/ch.vim b/runtime/indent/ch.vim
index e1bd8a356c..11b1f67790 100644
--- a/runtime/indent/ch.vim
+++ b/runtime/indent/ch.vim
@@ -3,6 +3,7 @@
" Maintainer: SoftIntegration, Inc. <info@softintegration.com>
" URL: http://www.softintegration.com/download/vim/indent/ch.vim
" Last change: 2006 Apr 30
+" 2023 Aug 28 by Vim Project (undo_indent)
" Created based on cpp.vim
"
" Ch is a C/C++ interpreter with many high level extensions
@@ -16,3 +17,5 @@ let b:did_indent = 1
" Ch indenting is built-in, thus this is very simple
setlocal cindent
+
+let b:undo_indent = "setlocal cindent<"
diff --git a/runtime/indent/cpp.vim b/runtime/indent/cpp.vim
index ffa37d7208..bb4dfd185e 100644
--- a/runtime/indent/cpp.vim
+++ b/runtime/indent/cpp.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: C++
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2008 Nov 29
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/cuda.vim b/runtime/indent/cuda.vim
index 68ee9cec0c..5980ddda44 100644
--- a/runtime/indent/cuda.vim
+++ b/runtime/indent/cuda.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: CUDA
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2008 Nov 29
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/dts.vim b/runtime/indent/dts.vim
new file mode 100644
index 0000000000..e87f8153c7
--- /dev/null
+++ b/runtime/indent/dts.vim
@@ -0,0 +1,63 @@
+" Vim indent file
+" Language: Device Tree
+" Maintainer: Roland Hieber, Pengutronix <rhi@pengutronix.de>
+"
+if exists("b:did_indent")
+ finish
+endif
+let b:did_indent = 1
+
+setlocal autoindent
+setlocal nosmartindent
+setlocal indentkeys=o,O,0},0<>>,!<Ctrl-F>
+setlocal indentexpr=GetDTSIndent()
+setlocal nolisp
+
+let b:undo_indent = 'setl autoindent< smartindent< indentkeys< indentexpr< lisp<'
+
+function GetDTSIndent()
+ let sw = shiftwidth()
+ let lnum = v:lnum
+ let line = getline(lnum)
+ let prevline = getline(prevnonblank(lnum-1))
+ let prevind = indent(prevnonblank(lnum-1))
+
+ if prevnonblank(lnum-1) < 1
+ return 0
+ endif
+
+ " Don't indent header and preprocessor directives
+ if line =~ '^\s*\(/dts-\|#\(include\|define\|undef\|warn\(ing\)\?\|error\|if\(n\?def\)\?\|else\|elif\|endif\)\)'
+ return 0
+
+ " Don't indent /node and &label blocks
+ elseif line =~ '^\s*[/&].\+{\s*$'
+ return 0
+
+ " Indent to matching bracket or remove one shiftwidth if line begins with } or >
+ elseif line =~ '^\s*[}>]'
+ " set cursor to closing bracket on current line
+ let col = matchend(line, '^\s*[>}]')
+ call cursor(lnum, col)
+
+ " determine bracket type, {} or <>
+ let pair = strpart('{}<>', stridx('}>', line[col-1]) * 2, 2)
+
+ " find matching bracket pair
+ let pairline = searchpair(pair[0], '', pair[1], 'bW')
+
+ if pairline > 0
+ return indent(pairline)
+ else
+ return prevind - sw
+ endif
+
+ " else, add one level of indent if line ends in { or < or = or ,
+ elseif prevline =~ '[{<=,]$'
+ return prevind + sw
+
+ else
+ return prevind
+ endif
+
+endfunction
diff --git a/runtime/indent/dune.vim b/runtime/indent/dune.vim
index 0590d66d13..a9349e4e04 100644
--- a/runtime/indent/dune.vim
+++ b/runtime/indent/dune.vim
@@ -3,6 +3,7 @@
" Maintainers: Markus Mottl <markus.mottl@gmail.com>
" URL: https://github.com/ocaml/vim-ocaml
" Last Change: 2021 Jan 01
+" 2023 Aug 28 by Vim Project (undo_indent)
if exists("b:did_indent")
finish
@@ -11,3 +12,5 @@ let b:did_indent = 1
" dune format-dune-file uses 1 space to indent
setlocal softtabstop=1 shiftwidth=1 expandtab
+
+let b:undo_indent = "setl et< sts< sw<"
diff --git a/runtime/indent/erlang.vim b/runtime/indent/erlang.vim
index 7aa38587a6..5682c31ef3 100644
--- a/runtime/indent/erlang.vim
+++ b/runtime/indent/erlang.vim
@@ -1324,7 +1324,7 @@ function! s:ErlangCalcIndent2(lnum, stack)
" maybe A else
" LTI
"
- " Note about Emacs compabitility {{{
+ " Note about Emacs compatibility {{{
"
" It would be fine to indent the examples above the following way:
"
diff --git a/runtime/indent/fish.vim b/runtime/indent/fish.vim
new file mode 100644
index 0000000000..e7678cb7c8
--- /dev/null
+++ b/runtime/indent/fish.vim
@@ -0,0 +1,85 @@
+" Vim indent file
+" Language: fish
+" Maintainer: Nicholas Boyle (github.com/nickeb96)
+" Repository: https://github.com/nickeb96/fish.vim
+" Last Change: February 4, 2023
+" 2023 Aug 28 by Vim Project (undo_indent)
+
+if exists("b:did_indent")
+ finish
+endif
+let b:did_indent = 1
+
+setlocal indentexpr=GetFishIndent(v:lnum)
+setlocal indentkeys+==end,=else,=case
+
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
+function s:PrevCmdStart(linenum)
+ let l:linenum = a:linenum
+ " look for the first line that isn't a line continuation
+ while l:linenum > 1 && getline(l:linenum - 1) =~# '\\$'
+ let l:linenum = l:linenum - 1
+ endwhile
+ return l:linenum
+endfunction
+
+function GetFishIndent(lnum)
+ let l:shiftwidth = shiftwidth()
+
+ let l:prevlnum = prevnonblank(a:lnum - 1)
+ if l:prevlnum ==# 0
+ return 0
+ endif
+
+ " if the previous line ended with a line continuation
+ if getline(a:lnum - 1) =~# '\\$'
+ if a:lnum ==# 0 || getline(a:lnum - 2) !~# '\\$'
+ " this is the first line continuation in a chain, so indent it
+ return indent(a:lnum - 1) + l:shiftwidth
+ else
+ " use the same indentation as the previous continued line
+ return indent(a:lnum - 1)
+ endif
+ endif
+
+ let l:prevlnum = s:PrevCmdStart(l:prevlnum)
+
+ let l:prevline = getline(l:prevlnum)
+ if l:prevline =~# '^\s*\(begin\|if\|else\|while\|for\|function\|case\|switch\)\>'
+ let l:indent = l:shiftwidth
+ else
+ let l:indent = 0
+ endif
+
+ let l:line = getline(a:lnum)
+ if l:line =~# '^\s*end\>'
+ " find end's matching start
+ let l:depth = 1
+ let l:currentlnum = a:lnum
+ while l:depth > 0 && l:currentlnum > 0
+ let l:currentlnum = s:PrevCmdStart(prevnonblank(l:currentlnum - 1))
+ let l:currentline = getline(l:currentlnum)
+ if l:currentline =~# '^\s*end\>'
+ let l:depth = l:depth + 1
+ elseif l:currentline =~# '^\s*\(begin\|if\|while\|for\|function\|switch\)\>'
+ let l:depth = l:depth - 1
+ endif
+ endwhile
+ if l:currentline =~# '^\s*switch\>'
+ return indent(l:currentlnum)
+ else
+ return indent(l:prevlnum) + l:indent - l:shiftwidth
+ endif
+ elseif l:line =~# '^\s*else\>'
+ return indent(l:prevlnum) + l:indent - l:shiftwidth
+ elseif l:line =~# '^\s*case\>'
+ if getline(l:prevlnum) =~# '^\s*switch\>'
+ return indent(l:prevlnum) + l:indent
+ else
+ return indent(l:prevlnum) + l:indent - l:shiftwidth
+ endif
+ else
+ return indent(l:prevlnum) + l:indent
+ endif
+endfunction
diff --git a/runtime/indent/go.vim b/runtime/indent/go.vim
index bf9ff75e6c..a9b1d8d19b 100644
--- a/runtime/indent/go.vim
+++ b/runtime/indent/go.vim
@@ -2,6 +2,7 @@
" Language: Go
" Maintainer: David Barnett (https://github.com/google/vim-ft-go)
" Last Change: 2017 Jun 13
+" 2023 Aug 28 by Vim Project (undo_indent)
"
" TODO:
" - function invocations split across lines
@@ -19,6 +20,8 @@ setlocal autoindent
setlocal indentexpr=GoIndent(v:lnum)
setlocal indentkeys+=<:>,0=},0=)
+let b:undo_indent = "setl ai< inde< indk< lisp<"
+
if exists('*GoIndent')
finish
endif
diff --git a/runtime/indent/hare.vim b/runtime/indent/hare.vim
index bc4fea4e61..0a9d8dafd8 100644
--- a/runtime/indent/hare.vim
+++ b/runtime/indent/hare.vim
@@ -2,6 +2,7 @@
" Language: Hare
" Maintainer: Amelia Clarke <me@rsaihe.dev>
" Last Change: 2022 Sep 22
+" 2023 Aug 28 by Vim Project (undo_indent)
if exists("b:did_indent")
finish
@@ -40,6 +41,8 @@ setlocal cinwords=if,else,for,switch,match
setlocal indentexpr=GetHareIndent()
+let b:undo_indent = "setl cin< cino< cinw< inde< indk<"
+
function! FloorCindent(lnum)
return cindent(a:lnum) / shiftwidth() * shiftwidth()
endfunction
diff --git a/runtime/indent/html.vim b/runtime/indent/html.vim
index 65e0ffc40c..2fa10cc91e 100644
--- a/runtime/indent/html.vim
+++ b/runtime/indent/html.vim
@@ -1,7 +1,7 @@
" Vim indent script for HTML
-" Maintainer: Bram Moolenaar
+" Maintainer: The Vim Project <https://github.com/vim/vim>
" Original Author: Andy Wokula <anwoku@yahoo.de>
-" Last Change: 2022 Jan 31
+" Last Change: 2023 Aug 13
" Version: 1.0 "{{{
" Description: HTML indent script with cached state for faster indenting on a
" range of lines.
diff --git a/runtime/indent/jsonc.vim b/runtime/indent/jsonc.vim
index bf8e501dd5..058634a6d2 100644
--- a/runtime/indent/jsonc.vim
+++ b/runtime/indent/jsonc.vim
@@ -4,6 +4,7 @@
" Acknowledgement: Based off of vim-json maintained by Eli Parra <eli@elzr.com>
" https://github.com/elzr/vim-json
" Last Change: 2021-07-01
+" 2023 Aug 28 by Vim Project (undo_indent)
" 0. Initialization {{{1
" =================
@@ -20,6 +21,8 @@ setlocal nosmartindent
setlocal indentexpr=GetJSONCIndent()
setlocal indentkeys=0{,0},0),0[,0],!^F,o,O,e
+let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<"
+
" Only define the function once.
if exists("*GetJSONCIndent")
finish
diff --git a/runtime/indent/julia.vim b/runtime/indent/julia.vim
index 8e4c60322e..efc98a2851 100644
--- a/runtime/indent/julia.vim
+++ b/runtime/indent/julia.vim
@@ -3,7 +3,8 @@
" Maintainer: Carlo Baldassi <carlobaldassi@gmail.com>
" Homepage: https://github.com/JuliaEditorSupport/julia-vim
" Last Change: 2022 Jun 14
-" Notes: originally based on Bram Molenaar's indent file for vim
+" 2023 Aug 28 by Vim Project (undo_indent)
+" Notes: originally based on Bram Moolenaar's indent file for vim
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
@@ -21,6 +22,8 @@ setlocal indentkeys-=0{
setlocal indentkeys-=0}
setlocal nosmartindent
+let b:undo_indent = "setl ai< inde< indk< si<"
+
" Only define the function once.
if exists("*GetJuliaIndent")
finish
@@ -307,7 +310,7 @@ function IsFunctionArgPar(lnum, c)
endfunction
function JumpToMatch(lnum, last_closed_bracket)
- " we use the % command to skip back (tries to ues matchit if possible,
+ " we use the % command to skip back (tries to use matchit if possible,
" otherwise resorts to vim's default, which is buggy but better than
" nothing)
call cursor(a:lnum, a:last_closed_bracket)
diff --git a/runtime/indent/kotlin.vim b/runtime/indent/kotlin.vim
new file mode 100644
index 0000000000..590a5074d1
--- /dev/null
+++ b/runtime/indent/kotlin.vim
@@ -0,0 +1,60 @@
+" Vim indent file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 7 November 2021
+" 2023 Sep 17 by Vim Project (undo_indent)
+
+if exists('b:did_indent')
+ finish
+endif
+let b:did_indent = 1
+
+setlocal cinoptions& cinoptions+=j1,L0
+setlocal indentexpr=GetKotlinIndent()
+setlocal indentkeys=0},0),!^F,o,O,e,<CR>
+setlocal autoindent " TODO ?
+
+let b:undo_indent = "setlocal autoindent< cinoptions< indentexpr< indentkeys<"
+
+" TODO teach it to count bracket balance, etc.
+function! GetKotlinIndent()
+ if v:lnum == 0
+ return 0
+ endif
+
+ let prev_num = prevnonblank(v:lnum - 1)
+ let prev = getline(prev_num)
+ let prev_indent = indent(prev_num)
+ let cur = getline(v:lnum)
+
+ if cur =~ '^\s*\*'
+ return cindent(v:lnum)
+ endif
+
+ if prev =~ '^\s*\*/'
+ let st = prev
+ while st > 1
+ if getline(st) =~ '^\s*/\*'
+ break
+ endif
+ let st = st - 1
+ endwhile
+ return indent(st)
+ endif
+
+ let prev_open_paren = prev =~ '^.*(\s*$'
+ let cur_close_paren = cur =~ '^\s*).*$'
+ let prev_open_brace = prev =~ '^.*\({\|->\)\s*$'
+ let cur_close_brace = cur =~ '^\s*}.*$'
+
+ if prev_open_paren && !cur_close_paren || prev_open_brace && !cur_close_brace
+ return prev_indent + shiftwidth()
+ endif
+
+ if cur_close_paren && !prev_open_paren || cur_close_brace && !prev_open_brace
+ return prev_indent - shiftwidth()
+ endif
+
+ return prev_indent
+endfunction
diff --git a/runtime/indent/krl.vim b/runtime/indent/krl.vim
index cc3cbd1abb..89f45356ba 100644
--- a/runtime/indent/krl.vim
+++ b/runtime/indent/krl.vim
@@ -41,7 +41,7 @@ function GetKrlIndent() abort
let currentLine = getline(v:lnum)
if currentLine =~? '\v^;(\s*(end)?fold>)@!' && !get(g:, 'krlCommentIndent', 0)
" If current line has a ; in column 1 and is no fold, keep zero indent.
- " This may be usefull if code is commented out at the first column.
+ " This may be useful if code is commented out at the first column.
return 0
endif
@@ -117,7 +117,7 @@ function s:KrlPreNoneBlank(lnum) abort
let nPreNoneBlank = prevnonblank(a:lnum)
while nPreNoneBlank > 0 && getline(nPreNoneBlank) =~? '\v^\s*(\&\w\+|;|continue>)'
- " Previouse none blank line irrelevant. Look further aback.
+ " Previous none blank line irrelevant. Look further aback.
let nPreNoneBlank = prevnonblank(nPreNoneBlank - 1)
endwhile
diff --git a/runtime/indent/livebook.vim b/runtime/indent/livebook.vim
new file mode 100644
index 0000000000..6311050e0e
--- /dev/null
+++ b/runtime/indent/livebook.vim
@@ -0,0 +1,9 @@
+" Placeholder livebook indent file.
+" This simply uses the markdown indenting.
+
+" Only load this indent file when no other was loaded.
+if exists("b:did_indent")
+ finish
+endif
+
+runtime! indent/markdown.vim
diff --git a/runtime/indent/logtalk.vim b/runtime/indent/logtalk.vim
index 8e36f86115..f7a8b0387c 100644
--- a/runtime/indent/logtalk.vim
+++ b/runtime/indent/logtalk.vim
@@ -1,5 +1,6 @@
" Maintainer: Paulo Moura <pmoura@logtalk.org>
" Revised on: 2018.08.04
+" 2023 Aug 28 by Vim Project (undo_indent)
" Language: Logtalk
" This Logtalk indent file is a modified version of the Prolog
@@ -16,6 +17,8 @@ setlocal indentexpr=GetLogtalkIndent()
setlocal indentkeys-=:,0#
setlocal indentkeys+=0%,-,0;,>,0)
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
" Only define the function once.
if exists("*GetLogtalkIndent")
finish
diff --git a/runtime/indent/lua.vim b/runtime/indent/lua.vim
index 0d1f934a03..35b08d4037 100644
--- a/runtime/indent/lua.vim
+++ b/runtime/indent/lua.vim
@@ -27,6 +27,16 @@ if exists("*GetLuaIndent")
endif
function! GetLuaIndent()
+ let ignorecase_save = &ignorecase
+ try
+ let &ignorecase = 0
+ return GetLuaIndentIntern()
+ finally
+ let &ignorecase = ignorecase_save
+ endtry
+endfunction
+
+function! GetLuaIndentIntern()
" Find a non-blank line above the current line.
let prevlnum = prevnonblank(v:lnum - 1)
@@ -41,7 +51,7 @@ function! GetLuaIndent()
let prevline = getline(prevlnum)
let midx = match(prevline, '^\s*\%(if\>\|for\>\|while\>\|repeat\>\|else\>\|elseif\>\|do\>\|then\>\)')
if midx == -1
- let midx = match(prevline, '{\s*$')
+ let midx = match(prevline, '{\s*\%(--\%([^[].*\)\?\)\?$')
if midx == -1
let midx = match(prevline, '\<function\>\s*\%(\k\|[.:]\)\{-}\s*(')
endif
diff --git a/runtime/indent/luau.vim b/runtime/indent/luau.vim
new file mode 100644
index 0000000000..69893f7399
--- /dev/null
+++ b/runtime/indent/luau.vim
@@ -0,0 +1,14 @@
+" Vim filetype indent file
+" Language: Luau
+" Maintainer: None yet
+" Last Change: 2023 Apr 30
+
+" Only load this indent file when no other was loaded.
+if exists("b:did_indent")
+ finish
+endif
+
+" Luau is a superset of Lua
+runtime! indent/lua.vim
+
+
diff --git a/runtime/indent/mail.vim b/runtime/indent/mail.vim
index 22bb0f7e12..eec9b4e43d 100644
--- a/runtime/indent/mail.vim
+++ b/runtime/indent/mail.vim
@@ -1,7 +1,7 @@
" Vim indent file
" Language: Mail
-" Maintainer: Bram Moolenaar
-" Last Change: 2021 Sep 26
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
if exists("b:did_indent")
finish
diff --git a/runtime/indent/nsis.vim b/runtime/indent/nsis.vim
index 5d3decca37..3731781201 100644
--- a/runtime/indent/nsis.vim
+++ b/runtime/indent/nsis.vim
@@ -15,7 +15,7 @@ setlocal nosmartindent
setlocal noautoindent
setlocal indentexpr=GetNsisIndent(v:lnum)
setlocal indentkeys=!^F,o,O
-setlocal indentkeys+==~${Else,=~${EndIf,=~${EndUnless,=~${AndIf,=~${AndUnless,=~${OrIf,=~${OrUnless,=~${Case,=~${Default,=~${EndSelect,=~${EndSwith,=~${Loop,=~${Next,=~${MementoSectionEnd,=~FunctionEnd,=~SectionEnd,=~SectionGroupEnd,=~PageExEnd,0=~!macroend,0=~!if,0=~!else,0=~!endif
+setlocal indentkeys+==~${Else,=~${EndIf,=~${EndUnless,=~${AndIf,=~${AndUnless,=~${OrIf,=~${OrUnless,=~${Case,=~${Default,=~${EndSelect,=~${EndSwitch,=~${Loop,=~${Next,=~${MementoSectionEnd,=~FunctionEnd,=~SectionEnd,=~SectionGroupEnd,=~PageExEnd,0=~!macroend,0=~!if,0=~!else,0=~!endif
let b:undo_indent = "setl ai< inde< indk< si<"
diff --git a/runtime/indent/ocaml.vim b/runtime/indent/ocaml.vim
index 19c81f49c4..c9beb8be0b 100644
--- a/runtime/indent/ocaml.vim
+++ b/runtime/indent/ocaml.vim
@@ -4,7 +4,8 @@
" Mike Leary <leary@nwlink.com>
" Markus Mottl <markus.mottl@gmail.com>
" URL: https://github.com/ocaml/vim-ocaml
-" Last Change: 2017 Jun 13
+" Last Change: 2023 Aug 28 - Add undo_indent (Vim Project)
+" 2017 Jun 13
" 2005 Jun 25 - Fixed multiple bugs due to 'else\nreturn ind' working
" 2005 May 09 - Added an option to not indent OCaml-indents specially (MM)
" 2013 June - commented textwidth (Marc Weber)
@@ -24,6 +25,8 @@ setlocal indentkeys+=0=and,0=class,0=constraint,0=done,0=else,0=end,0=exception,
setlocal nolisp
setlocal nosmartindent
+let b:undo_indent = "setl et< inde< indk< lisp< si<"
+
" At least Marc Weber and Markus Mottl do not like this:
" setlocal textwidth=80
diff --git a/runtime/indent/perl.vim b/runtime/indent/perl.vim
index 4c91fa1b33..a97c34da53 100644
--- a/runtime/indent/perl.vim
+++ b/runtime/indent/perl.vim
@@ -4,7 +4,7 @@
" Homepage: https://github.com/vim-perl/vim-perl
" Bugs/requests: https://github.com/vim-perl/vim-perl/issues
" License: Vim License (see :help license)
-" Last Change: 2021 Sep 24
+" Last Change: 2022 Jun 14
" Suggestions and improvements by :
" Aaron J. Sherman (use syntax for hints)
@@ -133,6 +133,7 @@ function! GetPerlIndent()
\ || synid == "perlHereDoc"
\ || synid == "perlBraces"
\ || synid == "perlStatementIndirObj"
+ \ || synid == "perlSubDeclaration"
\ || synid =~ "^perlFiledescStatement"
\ || synid =~ '^perl\(Sub\|Block\|Package\)Fold'
let brace = strpart(line, bracepos, 1)
diff --git a/runtime/indent/php.vim b/runtime/indent/php.vim
index 0e623689d5..377ffd5afb 100644
--- a/runtime/indent/php.vim
+++ b/runtime/indent/php.vim
@@ -3,8 +3,8 @@
" Author: John Wellesz <John.wellesz (AT) gmail (DOT) com>
" URL: https://www.2072productions.com/vim/indent/php.vim
" Home: https://github.com/2072/PHP-Indenting-for-VIm
-" Last Change: 2020 Mar 05
-" Version: 1.70
+" Last Change: 2023 August 18th
+" Version: 1.75
"
"
" Type :help php-indent for available options
@@ -128,7 +128,7 @@ setlocal nolisp
setlocal indentexpr=GetPhpIndent()
setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,*<Return>,=?>,=<?,=*/
-
+let b:undo_indent = "setl ai< cin< inde< indk< lisp< si<"
let s:searchpairflags = 'bWr'
@@ -142,10 +142,10 @@ if exists("*GetPhpIndent")
endif
-let s:endline = '\s*\%(//.*\|#.*\|/\*.*\*/\s*\)\=$'
+let s:endline = '\s*\%(//.*\|#\[\@!.*\|/\*.*\*/\s*\)\=$'
let s:PHP_validVariable = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'
-let s:notPhpHereDoc = '\%(break\|return\|continue\|exit\|die\|else\|end\%(if\|while\|for\|foreach\|switch\)\)'
-let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|\%(}\s*\)\?else\>\|do\>\|while\>\|switch\>\|case\>\|default\>\|for\%(each\)\=\>\|declare\>\|class\>\|trait\>\|\%()\s*\)\=use\>\|interface\>\|abstract\>\|final\>\|try\>\|\%(}\s*\)\=catch\>\|\%(}\s*\)\=finally\>\)'
+let s:notPhpHereDoc = '\<\%(break\|return\|continue\|exit\|die\|true\|false\|elseif\|else\|end\%(if\|while\|for\|foreach\|match\|switch\)\)\>'
+let s:blockstart = '\%(\%(\%(}\s*\)\=else\%(\s\+\)\=\)\=if\>\|\%(}\s*\)\?else\>\|do\>\|while\>\|match\>\|switch\>\|case\>\|default\>\|for\%(each\)\=\>\|declare\>\|class\>\|trait\>\|\%()\s*\)\=use\>\|interface\>\|abstract\>\|final\>\|try\>\|\%(}\s*\)\=catch\>\|\%(}\s*\)\=finally\>\)'
let s:functionDeclPrefix = '\<function\>\%(\s\+&\='.s:PHP_validVariable.'\)\=\s*('
let s:functionDecl = s:functionDeclPrefix.'.*'
let s:multilineFunctionDecl = s:functionDeclPrefix.s:endline
@@ -156,7 +156,8 @@ let s:unstated = '\%(^\s*'.s:blockstart.'.*)\|\%(//.*\)\@<!\<e'.'lse\>\)'.s:endl
let s:terminated = '\%(\%(;\%(\s*\%(?>\|}\)\)\=\|<<<\s*[''"]\=\a\w*[''"]\=$\|^\s*}\|^\s*'.s:PHP_validVariable.':\)'.s:endline.'\)'
let s:PHP_startindenttag = '<?\%(.*?>\)\@!\|<script[^>]*>\%(.*<\/script>\)\@!'
-let s:structureHead = '^\s*\%(' . s:blockstart . '\)\|'. s:functionDecl . s:endline . '\|\<new\s\+class\>'
+let s:matchStart = 'match\s*(\s*\$\?'.s:PHP_validVariable.'\s*)\s*{'. s:endline
+let s:structureHead = '^\s*\%(' . s:blockstart . '\)\|'. s:functionDecl . s:endline . '\|\<new\s\+class\>\|' . s:matchStart
let s:escapeDebugStops = 0
@@ -223,7 +224,7 @@ function! GetLastRealCodeLNum(startline) " {{{
while getline(lnum) !~? tofind && lnum > 1
let lnum = lnum - 1
endwhile
- elseif lastline =~ '^\s*[''"`][;,]' || (lastline =~ '^[^''"`]*[''"`][;,]'.s:endline && IslinePHP(lnum, "") == "SpecStringEntrails")
+ elseif lastline =~ '^\s*[''"`][;,]'.s:endline || (lastline =~ '^[^''"`]*[''"`][;,]'.s:endline && IslinePHP(lnum, "") == "SpecStringEntrails")
let tofind=substitute( lastline, '^.*\([''"`]\)[;,].*$', '^[^\1]\\+[\1]$\\|^[^\1]\\+[=([]\\s*[\1]', '')
let trylnum = lnum
@@ -266,7 +267,7 @@ function! Skippmatch2()
let line = getline(".")
- if line =~ "\\([\"']\\).*/\\*.*\\1" || line =~ '\%(//\|#\).*/\*'
+ if line =~ "\\([\"']\\).*/\\*.*\\1" || line =~ '\%(//\|#\[\@!\).*/\*'
return 1
else
return 0
@@ -322,18 +323,22 @@ function! BalanceDirection (str)
endfun
function! StripEndlineComments (line)
- return substitute(a:line,"\\(//\\|#\\)\\(\\(\\([^\"']*\\([\"']\\)[^\"']*\\5\\)\\+[^\"']*$\\)\\|\\([^\"']*$\\)\\)",'','')
+
+ let cleaned = substitute(a:line,'\v(//|#\[\@!)((([^"'']*(["''])[^"'']*\5)+[^"'']*$)|([^"'']*$))','','')
+ if cleaned != a:line
+ endif
+ return cleaned
endfun
function! FindArrowIndent (lnum) " {{{
- let parrentArrowPos = -1
+ let parentArrowPos = -1
let cursorPos = -1
let lnum = a:lnum
while lnum > 1
let last_line = getline(lnum)
if last_line =~ '^\s*->'
- let parrentArrowPos = indent(a:lnum)
+ let parentArrowPos = indent(a:lnum)
break
else
@@ -355,28 +360,28 @@ function! FindArrowIndent (lnum) " {{{
else
endif
else
- let parrentArrowPos = -1
+ let parentArrowPos = -1
break
end
endif
if cleanedLnum =~ '->'
call cursor(lnum, cursorPos == -1 ? strwidth(cleanedLnum) : cursorPos)
- let parrentArrowPos = searchpos('->', 'cWb', lnum)[1] - 1
+ let parentArrowPos = searchpos('->', 'cWb', lnum)[1] - 1
break
else
- let parrentArrowPos = -1
+ let parentArrowPos = -1
break
endif
endif
endwhile
- if parrentArrowPos == -1
- let parrentArrowPos = indent(lnum) + shiftwidth()
+ if parentArrowPos == -1
+ let parentArrowPos = indent(lnum) + shiftwidth()
end
- return parrentArrowPos
+ return parentArrowPos
endfun "}}}
function! FindTheIfOfAnElse (lnum, StopAfterFirstPrevElse) " {{{
@@ -490,7 +495,7 @@ function! ResetPhpOptions()
if ! b:optionsset && &filetype =~ "php"
if b:PHP_autoformatcomment
- setlocal comments=s1:/*,mb:*,ex:*/,://,:#
+ setlocal comments=s1:/*,mb:*,ex:*/,://,f:#[,:#
setlocal formatoptions-=t
setlocal formatoptions+=q
@@ -506,7 +511,7 @@ endfunc
call ResetPhpOptions()
function! GetPhpIndentVersion()
- return "1.70-bundle"
+ return "1.75"
endfun
function! GetPhpIndent()
@@ -650,7 +655,7 @@ function! GetPhpIndent()
let b:InPHPcode_and_script = 1
endif
- elseif last_line =~ '^[^''"`]\+[''"`]$' && last_line !~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)' " a string identifier with nothing after it and no other string identifier before
+ elseif last_line =~ '^[^''"`]\+[''"`]$' && last_line !~ '^\s*\%(//\|#\[\@!\|/\*.*\*/\s*$\)' " a string identifier with nothing after it and no other string identifier before
let b:InPHPcode = -1
let b:InPHPcode_tofind = substitute( last_line, '^.*\([''"`]\).*$', '^[^\1]*\1[;,]$', '')
elseif last_line =~? '<<<\s*[''"]\=\a\w*[''"]\=$'
@@ -674,7 +679,7 @@ function! GetPhpIndent()
" Indent successive // or # comment the same way the first is {{{
let addSpecial = 0
- if cline =~ '^\s*\%(//\|#\|/\*.*\*/\s*$\)'
+ if cline =~ '^\s*\%(//\|#\[\@!\|/\*.*\*/\s*$\)'
let addSpecial = b:PHP_outdentSLComments
if b:PHP_LastIndentedWasComment == 1
return indent(real_PHP_lastindented)
@@ -715,7 +720,7 @@ function! GetPhpIndent()
return 0
endif
- if cline =~? '^\s*\a\w*;$\|^\a\w*$\|^\s*[''"`][;,]' && cline !~? s:notPhpHereDoc
+ if (cline =~? '^\s*\a\w*;$\|^\a\w*$' || (cline =~? '^\s*[''"`][;,]' && IslinePHP(v:lnum, '[;,]') !~? '^\(phpString[SD]\|phpBacktick\)') ) && cline !~? s:notPhpHereDoc
return 0
endif " }}}
@@ -738,6 +743,9 @@ function! GetPhpIndent()
if cline =~ '^\s*}\%(}}\)\@!'
let ind = indent(FindOpenBracket(v:lnum, 1))
let b:PHP_CurrentIndentLevel = b:PHP_default_indenting
+ if b:PHP_BracesAtCodeLevel
+ let ind = ind + shiftwidth()
+ endif
return ind
endif
@@ -761,7 +769,7 @@ function! GetPhpIndent()
if last_line =~ '[;}]'.endline && last_line !~ '^[)\]]' && last_line !~# s:defaultORcase && last_line !~ '^\s*[''"`][;,]'
if ind==b:PHP_default_indenting
return b:PHP_default_indenting + addSpecial
- elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)' && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline
+ elseif b:PHP_indentinghuge && ind==b:PHP_CurrentIndentLevel && cline !~# '^\s*\%(else\|\%(case\|default\).*:\|[})];\=\)' && last_line !~# '^\s*\%(\%(}\s*\)\=else\)\|^\(\s*\S\+\s*\)\+}'.endline && getline(GetLastRealCodeLNum(lnum - 1))=~';'.endline
return b:PHP_CurrentIndentLevel + addSpecial
endif
endif
@@ -929,7 +937,7 @@ function! GetPhpIndent()
let ind = ind + shiftwidth()
- elseif AntepenultimateLine =~ '{'.endline && AntepenultimateLine !~? '^\s*use\>' || AntepenultimateLine =~ terminated || AntepenultimateLine =~# s:defaultORcase
+ elseif AntepenultimateLine =~ '{'.endline && AntepenultimateLine !~? '^\s*use\>' && AntepenultimateLine !~? s:matchStart || AntepenultimateLine =~ terminated || AntepenultimateLine =~# s:defaultORcase
let ind = ind + shiftwidth()
endif
diff --git a/runtime/indent/python.vim b/runtime/indent/python.vim
index 8c3d0b0670..42ab4f3778 100644
--- a/runtime/indent/python.vim
+++ b/runtime/indent/python.vim
@@ -1,8 +1,9 @@
" Vim indent file
-" Language: Python
-" Maintainer: Bram Moolenaar <Bram@vim.org>
+" Language: Python
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Original Author: David Bustos <bustos@caltech.edu>
-" Last Change: 2021 Sep 26
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/qml.vim b/runtime/indent/qml.vim
new file mode 100644
index 0000000000..8c9fa91250
--- /dev/null
+++ b/runtime/indent/qml.vim
@@ -0,0 +1,59 @@
+" Vim indent file
+" Language: QML
+" Maintainer: Chase Knowlden <haroldknowlden@gmail.com>
+" Last Change: 2023 Aug 16
+"
+" Improved JavaScript indent script.
+
+" Indent script in place for this already?
+if exists("b:did_indent")
+ finish
+endif
+let b:did_indent = 1
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
+setlocal indentexpr=s:GetQmlIndent()
+setlocal indentkeys=0{,0},0),0],:,!^F,o,O,e,*<Return>,=*/
+
+" Only define functions once per session
+if exists("*s:GetQmlIndent")
+ finish
+endif
+
+" Clean up a line of code by removing trailing '//' and '/* */' comments, and trimming
+" whitespace
+function! s:Trim(line)
+ return substitute(substitute(substitute(a:line, '// .*', '', ''), '/\* .* \*/', '', ''), '^\s*\|\s*$', '', 'g')
+endfunction
+
+function! s:GetQmlIndent()
+ let num = v:lnum
+ let line = s:Trim(getline(num))
+
+ let pnum = prevnonblank(num - 1)
+ if pnum == 0
+ return 0
+ endif
+ let pline = s:Trim(getline(pnum))
+
+ let ind = indent(pnum)
+
+ " bracket/brace/paren blocks
+ if pline =~ '[{[(]$'
+ let ind += &sw
+ endif
+ if line =~ '^[}\])]'
+ let ind -= &sw
+ endif
+
+ " '/*' comments
+ if pline =~ '^/\*.*\*/'
+ " no indent for single-line form
+ elseif pline =~ '^/\*'
+ let ind += 1
+ elseif pline =~ '^\*/'
+ let ind -= 1
+ endif
+
+ return ind
+endfunction
diff --git a/runtime/indent/quarto.vim b/runtime/indent/quarto.vim
new file mode 100644
index 0000000000..586d232d2b
--- /dev/null
+++ b/runtime/indent/quarto.vim
@@ -0,0 +1 @@
+runtime indent/rmd.vim
diff --git a/runtime/indent/r.vim b/runtime/indent/r.vim
index ca85a2e62d..07dfd13881 100644
--- a/runtime/indent/r.vim
+++ b/runtime/indent/r.vim
@@ -2,7 +2,7 @@
" Language: R
" Author: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sun Aug 19, 2018 09:13PM
+" Last Change: Mon Feb 27, 2023 07:16PM
" Only load this indent file when no other was loaded.
@@ -14,6 +14,8 @@ let b:did_indent = 1
setlocal indentkeys=0{,0},:,!^F,o,O,e
setlocal indentexpr=GetRIndent()
+let b:undo_indent = "setl inde< indk<"
+
" Only define the function once.
if exists("*GetRIndent")
finish
@@ -28,7 +30,7 @@ let g:r_indent_ess_comments = get(g:, 'r_indent_ess_comments', 0)
let g:r_indent_comment_column = get(g:, 'r_indent_comment_column', 40)
let g:r_indent_ess_compatible = get(g:, 'r_indent_ess_compatible', 0)
let g:r_indent_op_pattern = get(g:, 'r_indent_op_pattern',
- \ '\(&\||\|+\|-\|\*\|/\|=\|\~\|%\|->\)\s*$')
+ \ '\(&\||\|+\|-\|\*\|/\|=\|\~\|%\|->\||>\)\s*$')
function s:RDelete_quotes(line)
let i = 0
@@ -108,7 +110,7 @@ function s:RDelete_parens(line)
return line1
endfunction
-function! s:Get_paren_balance(line, o, c)
+function s:Get_paren_balance(line, o, c)
let line2 = substitute(a:line, a:o, "", "g")
let openp = strlen(a:line) - strlen(line2)
let line3 = substitute(line2, a:c, "", "g")
@@ -116,7 +118,7 @@ function! s:Get_paren_balance(line, o, c)
return openp - closep
endfunction
-function! s:Get_matching_brace(linenr, o, c, delbrace)
+function s:Get_matching_brace(linenr, o, c, delbrace)
let line = SanitizeRLine(getline(a:linenr))
if a:delbrace == 1
let line = substitute(line, '{$', "", "")
@@ -132,7 +134,7 @@ endfunction
" This function is buggy because there 'if's without 'else'
" It must be rewritten relying more on indentation
-function! s:Get_matching_if(linenr, delif)
+function s:Get_matching_if(linenr, delif)
let line = SanitizeRLine(getline(a:linenr))
if a:delif
let line = substitute(line, "if", "", "g")
@@ -160,7 +162,7 @@ function! s:Get_matching_if(linenr, delif)
endif
endfunction
-function! s:Get_last_paren_idx(line, o, c, pb)
+function s:Get_last_paren_idx(line, o, c, pb)
let blc = a:pb
let line = substitute(a:line, '\t', s:curtabstop, "g")
let theidx = -1
@@ -359,17 +361,19 @@ function GetRIndent()
let olnum = s:Get_prev_line(lnum)
let oline = getline(olnum)
if olnum > 0
- if line =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
- if oline =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
+ if substitute(line, '#.*', '', '') =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
+ if substitute(oline, '#.*', '', '') =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
return indent(lnum)
else
return indent(lnum) + shiftwidth()
endif
else
- if oline =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
+ if substitute(oline, '#.*', '', '') =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
return indent(lnum) - shiftwidth()
endif
endif
+ elseif substitute(line, '#.*', '', '') =~ g:r_indent_op_pattern && s:Get_paren_balance(line, "(", ")") == 0
+ return indent(lnum) + shiftwidth()
endif
let post_fun = 0
diff --git a/runtime/indent/raku.vim b/runtime/indent/raku.vim
index 3f9b49ec77..753a2b04be 100644
--- a/runtime/indent/raku.vim
+++ b/runtime/indent/raku.vim
@@ -4,6 +4,7 @@
" Homepage: https://github.com/vim-perl/vim-perl
" Bugs/requests: https://github.com/vim-perl/vim-perl/issues
" Last Change: 2020 Apr 15
+" 2023 Aug 28 by Vim Project (undo_indent)
" Contributors: Andy Lester <andy@petdance.com>
" Hinrik Örn Sigurðsson <hinrik.sig@gmail.com>
"
@@ -47,6 +48,8 @@ if !b:indent_use_syntax
setlocal indentkeys+=0=EO
endif
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
let s:cpo_save = &cpo
set cpo-=C
diff --git a/runtime/indent/rapid.vim b/runtime/indent/rapid.vim
new file mode 100644
index 0000000000..b1fa00b8cc
--- /dev/null
+++ b/runtime/indent/rapid.vim
@@ -0,0 +1,255 @@
+" ABB Rapid Command indent file for Vim
+" Language: ABB Rapid Command
+" Maintainer: Patrick Meiser-Knosowski <knosowski@graeffrobotics.de>
+" Version: 2.2.7
+" Last Change: 12. May 2023
+" Credits: Based on indent/vim.vim
+"
+" Suggestions of improvement are very welcome. Please email me!
+"
+" Known bugs: ../doc/rapid.txt
+"
+" TODO
+" * indent wrapped lines which do not end with an ; or special key word,
+" maybe this is a better idea, but then () and [] has to be changed as
+" well
+"
+
+if exists("g:rapidNoSpaceIndent")
+ if !exists("g:rapidSpaceIndent")
+ let g:rapidSpaceIndent = !g:rapidNoSpaceIndent
+ endif
+ unlet g:rapidNoSpaceIndent
+endif
+
+" Only load this indent file when no other was loaded.
+if exists("b:did_indent") || get(g:,'rapidNoIndent',0)
+ finish
+endif
+let b:did_indent = 1
+
+setlocal nolisp
+setlocal nosmartindent
+setlocal autoindent
+setlocal indentexpr=GetRapidIndent()
+if get(g:,'rapidNewStyleIndent',0)
+ setlocal indentkeys=!^F,o,O,0=~endmodule,0=~error,0=~undo,0=~backward,0=~endproc,0=~endrecord,0=~endtrap,0=~endfunc,0=~else,0=~endif,0=~endtest,0=~endfor,0=~endwhile,:,<[>,<]>,<(>,<)>
+else
+ setlocal indentkeys=!^F,o,O,0=~endmodule,0=~error,0=~undo,0=~backward,0=~endproc,0=~endrecord,0=~endtrap,0=~endfunc,0=~else,0=~endif,0=~endtest,0=~endfor,0=~endwhile,:
+endif
+let b:undo_indent="setlocal lisp< si< ai< inde< indk<"
+
+if get(g:,'rapidSpaceIndent',1)
+ " Use spaces for indention, 2 is enough.
+ " More or even tabs wastes space on the teach pendant.
+ setlocal softtabstop=2
+ setlocal shiftwidth=2
+ setlocal expandtab
+ setlocal shiftround
+ let b:undo_indent = b:undo_indent." sts< sw< et< sr<"
+endif
+
+" Only define the function once.
+if exists("*GetRapidIndent")
+ finish
+endif
+
+let s:keepcpo= &cpo
+set cpo&vim
+
+function GetRapidIndent()
+ let ignorecase_save = &ignorecase
+ try
+ let &ignorecase = 0
+ return s:GetRapidIndentIntern()
+ finally
+ let &ignorecase = ignorecase_save
+ endtry
+endfunction
+
+function s:GetRapidIndentIntern() abort
+
+ let l:currentLineNum = v:lnum
+ let l:currentLine = getline(l:currentLineNum)
+
+ if l:currentLine =~ '^!' && !get(g:,'rapidCommentIndent',0)
+ " If current line is ! line comment, do not change indent
+ " This may be useful if code is commented out at the first column.
+ return 0
+ endif
+
+ " Find a non-blank line above the current line.
+ let l:preNoneBlankLineNum = s:RapidPreNoneBlank(v:lnum - 1)
+ if l:preNoneBlankLineNum == 0
+ " At the start of the file use zero indent.
+ return 0
+ endif
+
+ let l:preNoneBlankLine = getline(l:preNoneBlankLineNum)
+ let l:ind = indent(l:preNoneBlankLineNum)
+
+ " Define add a 'shiftwidth' pattern
+ let l:addShiftwidthPattern = '\c\v^\s*('
+ let l:addShiftwidthPattern .= '((local|task)\s+)?(module|record|proc|func|trap)\s+\k'
+ let l:addShiftwidthPattern .= '|(backward|error|undo)>'
+ let l:addShiftwidthPattern .= ')'
+ "
+ " Define Subtract 'shiftwidth' pattern
+ let l:subtractShiftwidthPattern = '\c\v^\s*('
+ let l:subtractShiftwidthPattern .= 'end(module|record|proc|func|trap)>'
+ let l:subtractShiftwidthPattern .= '|(backward|error|undo)>'
+ let l:subtractShiftwidthPattern .= ')'
+
+ " Add shiftwidth
+ if l:preNoneBlankLine =~ l:addShiftwidthPattern
+ \|| s:RapidLenTilStr(l:preNoneBlankLineNum, "then", 0)>=0
+ \|| s:RapidLenTilStr(l:preNoneBlankLineNum, "else", 0)>=0
+ \|| s:RapidLenTilStr(l:preNoneBlankLineNum, "do", 0)>=0
+ \|| s:RapidLenTilStr(l:preNoneBlankLineNum, "case", 0)>=0
+ \|| s:RapidLenTilStr(l:preNoneBlankLineNum, "default", 0)>=0
+ let l:ind += &sw
+ endif
+
+ " Subtract shiftwidth
+ if l:currentLine =~ l:subtractShiftwidthPattern
+ \|| s:RapidLenTilStr(l:currentLineNum, "endif", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "endfor", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "endwhile", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "endtest", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "else", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "elseif", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "case", 0)>=0
+ \|| s:RapidLenTilStr(l:currentLineNum, "default", 0)>=0
+ let l:ind = l:ind - &sw
+ endif
+
+ " First case (or default) after a test gets the indent of the test.
+ if (s:RapidLenTilStr(l:currentLineNum, "case", 0)>=0 || s:RapidLenTilStr(l:currentLineNum, "default", 0)>=0) && s:RapidLenTilStr(l:preNoneBlankLineNum, "test", 0)>=0
+ let l:ind += &sw
+ endif
+
+ " continued lines with () or []
+ let l:OpenSum = s:RapidLoneParen(l:preNoneBlankLineNum,"(") + s:RapidLoneParen(l:preNoneBlankLineNum,"[")
+ if get(g:,'rapidNewStyleIndent',0)
+ let l:CloseSum = s:RapidLoneParen(l:preNoneBlankLineNum,")") + s:RapidLoneParen(l:currentLineNum,"]")
+ else
+ let l:CloseSum = s:RapidLoneParen(l:preNoneBlankLineNum,")") + s:RapidLoneParen(l:preNoneBlankLineNum,"]")
+ endif
+ if l:OpenSum > l:CloseSum
+ let l:ind += (l:OpenSum * 4 * &sw)
+ elseif l:OpenSum < l:CloseSum
+ let l:ind -= (l:CloseSum * 4 * &sw)
+ endif
+
+ return l:ind
+endfunction
+
+" Returns the length of the line until a:str occur outside a string or
+" comment. Search starts at string index a:startIdx.
+" If a:str is a word also add word boundaries and case insensitivity.
+" Note: rapidTodoComment and rapidDebugComment are not taken into account.
+function s:RapidLenTilStr(lnum, str, startIdx) abort
+
+ let l:line = getline(a:lnum)
+ let l:len = strlen(l:line)
+ let l:idx = a:startIdx
+ let l:str = a:str
+ if l:str =~ '^\k\+$'
+ let l:str = '\c\<' . l:str . '\>'
+ endif
+
+ while l:len > l:idx
+ let l:idx = match(l:line, l:str, l:idx)
+ if l:idx < 0
+ " a:str not found
+ return -1
+ endif
+ let l:synName = synIDattr(synID(a:lnum,l:idx+1,0),"name")
+ if l:synName != "rapidString"
+ \&& l:synName != "rapidConcealableString"
+ \&& (l:synName != "rapidComment" || l:str =~ '^!')
+ " a:str found outside string or line comment
+ return l:idx
+ endif
+ " a:str is part of string or line comment
+ let l:idx += 1 " continue search for a:str
+ endwhile
+
+ " a:str not found or l:len <= a:startIdx
+ return -1
+endfunction
+
+" a:lchar should be one of (, ), [, ], { or }
+" returns the number of opening/closing parentheses which have no
+" closing/opening match in getline(a:lnum)
+function s:RapidLoneParen(lnum,lchar) abort
+ if a:lchar == "(" || a:lchar == ")"
+ let l:opnParChar = "("
+ let l:clsParChar = ")"
+ elseif a:lchar == "[" || a:lchar == "]"
+ let l:opnParChar = "["
+ let l:clsParChar = "]"
+ elseif a:lchar == "{" || a:lchar == "}"
+ let l:opnParChar = "{"
+ let l:clsParChar = "}"
+ else
+ return 0
+ endif
+
+ let l:line = getline(a:lnum)
+
+ " look for the first ! which is not part of a string
+ let l:len = s:RapidLenTilStr(a:lnum,"!",0)
+ if l:len == 0
+ return 0 " first char is !; ignored
+ endif
+
+ let l:opnParen = 0
+ " count opening brackets
+ let l:i = 0
+ while l:i >= 0
+ let l:i = s:RapidLenTilStr(a:lnum, l:opnParChar, l:i)
+ if l:i >= 0
+ let l:opnParen += 1
+ let l:i += 1
+ endif
+ endwhile
+
+ let l:clsParen = 0
+ " count closing brackets
+ let l:i = 0
+ while l:i >= 0
+ let l:i = s:RapidLenTilStr(a:lnum, l:clsParChar, l:i)
+ if l:i >= 0
+ let l:clsParen += 1
+ let l:i += 1
+ endif
+ endwhile
+
+ if (a:lchar == "(" || a:lchar == "[" || a:lchar == "{") && l:opnParen>l:clsParen
+ return (l:opnParen-l:clsParen)
+ elseif (a:lchar == ")" || a:lchar == "]" || a:lchar == "}") && l:clsParen>l:opnParen
+ return (l:clsParen-l:opnParen)
+ endif
+
+ return 0
+endfunction
+
+" This function works almost like prevnonblank() but handles %%%-headers and
+" comments like blank lines
+function s:RapidPreNoneBlank(lnum) abort
+
+ let nPreNoneBlank = prevnonblank(a:lnum)
+
+ while nPreNoneBlank>0 && getline(nPreNoneBlank) =~ '\v\c^\s*(\%\%\%|!)'
+ " Previous none blank line irrelevant. Look further aback.
+ let nPreNoneBlank = prevnonblank(nPreNoneBlank - 1)
+ endwhile
+
+ return nPreNoneBlank
+endfunction
+
+let &cpo = s:keepcpo
+unlet s:keepcpo
+
+" vim:sw=2 sts=2 et
diff --git a/runtime/indent/rhelp.vim b/runtime/indent/rhelp.vim
index cf69ae3392..334802ab78 100644
--- a/runtime/indent/rhelp.vim
+++ b/runtime/indent/rhelp.vim
@@ -2,7 +2,7 @@
" Language: R Documentation (Help), *.Rd
" Author: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Tue Apr 07, 2015 04:38PM
+" Last Change: Mon Feb 27, 2023 07:01PM
" Only load this indent file when no other was loaded.
@@ -20,6 +20,8 @@ setlocal nolisp
setlocal indentkeys=0{,0},:,!^F,o,O,e
setlocal indentexpr=GetCorrectRHelpIndent()
+let b:undo_indent = "setl ai< cin< inde< indk< lisp< si<"
+
" Only define the functions once.
if exists("*GetRHelpIndent")
finish
diff --git a/runtime/indent/rmd.vim b/runtime/indent/rmd.vim
index 8fd57257fa..a043b0c994 100644
--- a/runtime/indent/rmd.vim
+++ b/runtime/indent/rmd.vim
@@ -2,7 +2,7 @@
" Language: Rmd
" Author: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sun Mar 28, 2021 08:05PM
+" Last Change: Wed Nov 09, 2022 09:44PM
" Only load this indent file when no other was loaded.
@@ -16,6 +16,8 @@ let b:did_indent = 1
setlocal indentkeys=0{,0},<:>,!^F,o,O,e
setlocal indentexpr=GetRmdIndent()
+let b:undo_indent = "setl inde< indk<"
+
if exists("*GetRmdIndent")
finish
endif
@@ -47,6 +49,8 @@ function s:GetMdIndent()
return indent(v:lnum - 1) + 2
elseif pline =~ '^\s*\d\+\.\s\+'
return indent(v:lnum - 1) + 3
+ elseif pline =~ '^\[\^\S\+\]: '
+ return indent(v:lnum - 1) + shiftwidth()
endif
return indent(prevnonblank(v:lnum - 1))
endfunction
diff --git a/runtime/indent/rnoweb.vim b/runtime/indent/rnoweb.vim
index 73966868b8..668cdb7ddd 100644
--- a/runtime/indent/rnoweb.vim
+++ b/runtime/indent/rnoweb.vim
@@ -2,7 +2,7 @@
" Language: Rnoweb
" Author: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Fri Apr 15, 2016 10:58PM
+" Last Change: Mon Feb 27, 2023 07:17PM
" Only load this indent file when no other was loaded.
@@ -11,7 +11,7 @@ if exists("b:did_indent")
endif
runtime indent/tex.vim
-function! s:NoTeXIndent()
+function s:NoTeXIndent()
return indent(line("."))
endfunction
@@ -29,6 +29,8 @@ let b:did_indent = 1
setlocal indentkeys=0{,0},!^F,o,O,e,},=\bibitem,=\item
setlocal indentexpr=GetRnowebIndent()
+let b:undo_indent = "setl inde< indk<"
+
if exists("*GetRnowebIndent")
finish
endif
diff --git a/runtime/indent/rrst.vim b/runtime/indent/rrst.vim
index f3ee53e7fb..585c5e6654 100644
--- a/runtime/indent/rrst.vim
+++ b/runtime/indent/rrst.vim
@@ -2,7 +2,7 @@
" Language: Rrst
" Author: Jakson Alves de Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Tue Apr 07, 2015 04:38PM
+" Last Change: Feb 25, 2023
" Only load this indent file when no other was loaded.
@@ -16,6 +16,8 @@ let b:did_indent = 1
setlocal indentkeys=0{,0},:,!^F,o,O,e
setlocal indentexpr=GetRrstIndent()
+let b:undo_indent = "setl inde< indk<"
+
if exists("*GetRrstIndent")
finish
endif
diff --git a/runtime/indent/rst.vim b/runtime/indent/rst.vim
index a31ad8e080..e3c10865a6 100644
--- a/runtime/indent/rst.vim
+++ b/runtime/indent/rst.vim
@@ -4,6 +4,7 @@
" Maintainer: Marshall Ward <marshall.ward@gmail.com>
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2020-03-31
+" 2023 Aug 28 by Vim Project (undo_indent)
if exists("b:did_indent")
finish
@@ -14,6 +15,8 @@ setlocal indentexpr=GetRSTIndent()
setlocal indentkeys=!^F,o,O
setlocal nosmartindent
+let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<"
+
if exists("*GetRSTIndent")
finish
endif
diff --git a/runtime/indent/ruby.vim b/runtime/indent/ruby.vim
index 6ce8529fd1..ea5a2a7494 100644
--- a/runtime/indent/ruby.vim
+++ b/runtime/indent/ruby.vim
@@ -4,7 +4,7 @@
" Previous Maintainer: Nikolai Weibull <now at bitwi.se>
" URL: https://github.com/vim-ruby/vim-ruby
" Release Coordinator: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2022 Mar 22
+" Last Change: 2022 Jun 30
" 0. Initialization {{{1
" =================
@@ -93,7 +93,7 @@ let s:ruby_indent_keywords =
\ '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>'
" Def without an end clause: def method_call(...) = <expression>
-let s:ruby_endless_def = '\<def\s\+\k\+[!?]\=\%((.*)\|\s\)\s*='
+let s:ruby_endless_def = '\<def\s\+\%(\k\+\.\)\=\k\+[!?]\=\%((.*)\|\s\)\s*='
" Regex used for words that, at the start of a line, remove a level of indent.
let s:ruby_deindent_keywords =
diff --git a/runtime/indent/rust.vim b/runtime/indent/rust.vim
index d30629b64e..7c055ec739 100644
--- a/runtime/indent/rust.vim
+++ b/runtime/indent/rust.vim
@@ -1,213 +1,286 @@
" Vim indent file
" Language: Rust
" Author: Chris Morgan <me@chrismorgan.info>
-" Last Change: 2017 Jun 13
+" Last Change: 2023-09-11
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
- finish
+ finish
endif
let b:did_indent = 1
setlocal cindent
-setlocal cinoptions=L0,(0,Ws,J1,j1
-setlocal cinkeys=0{,0},!^F,o,O,0[,0]
+setlocal cinoptions=L0,(s,Ws,J1,j1,m1
+setlocal cinkeys=0{,0},!^F,o,O,0[,0],0(,0)
" Don't think cinwords will actually do anything at all... never mind
-setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern
+setlocal cinwords=for,if,else,while,loop,impl,mod,unsafe,trait,struct,enum,fn,extern,macro
" Some preliminary settings
setlocal nolisp " Make sure lisp indenting doesn't supersede us
setlocal autoindent " indentexpr isn't much help otherwise
" Also do indentkeys, otherwise # gets shoved to column 0 :-/
-setlocal indentkeys=0{,0},!^F,o,O,0[,0]
+setlocal indentkeys=0{,0},!^F,o,O,0[,0],0(,0)
setlocal indentexpr=GetRustIndent(v:lnum)
+let b:undo_indent = "setlocal cindent< cinoptions< cinkeys< cinwords< lisp< autoindent< indentkeys< indentexpr<"
+
" Only define the function once.
if exists("*GetRustIndent")
- finish
+ finish
endif
+" vint: -ProhibitAbbreviationOption
let s:save_cpo = &cpo
set cpo&vim
+" vint: +ProhibitAbbreviationOption
" Come here when loading the script the first time.
function! s:get_line_trimmed(lnum)
- " Get the line and remove a trailing comment.
- " Use syntax highlighting attributes when possible.
- " NOTE: this is not accurate; /* */ or a line continuation could trick it
- let line = getline(a:lnum)
- let line_len = strlen(line)
- if has('syntax_items')
- " If the last character in the line is a comment, do a binary search for
- " the start of the comment. synID() is slow, a linear search would take
- " too long on a long line.
- if synIDattr(synID(a:lnum, line_len, 1), "name") =~ 'Comment\|Todo'
- let min = 1
- let max = line_len
- while min < max
- let col = (min + max) / 2
- if synIDattr(synID(a:lnum, col, 1), "name") =~ 'Comment\|Todo'
- let max = col
- else
- let min = col + 1
- endif
- endwhile
- let line = strpart(line, 0, min - 1)
- endif
- return substitute(line, "\s*$", "", "")
- else
- " Sorry, this is not complete, nor fully correct (e.g. string "//").
- " Such is life.
- return substitute(line, "\s*//.*$", "", "")
- endif
+ " Get the line and remove a trailing comment.
+ " Use syntax highlighting attributes when possible.
+ " NOTE: this is not accurate; /* */ or a line continuation could trick it
+ let line = getline(a:lnum)
+ let line_len = strlen(line)
+ if has('syntax_items')
+ " If the last character in the line is a comment, do a binary search for
+ " the start of the comment. synID() is slow, a linear search would take
+ " too long on a long line.
+ if synIDattr(synID(a:lnum, line_len, 1), "name") =~? 'Comment\|Todo'
+ let min = 1
+ let max = line_len
+ while min < max
+ let col = (min + max) / 2
+ if synIDattr(synID(a:lnum, col, 1), "name") =~? 'Comment\|Todo'
+ let max = col
+ else
+ let min = col + 1
+ endif
+ endwhile
+ let line = strpart(line, 0, min - 1)
+ endif
+ return substitute(line, "\s*$", "", "")
+ else
+ " Sorry, this is not complete, nor fully correct (e.g. string "//").
+ " Such is life.
+ return substitute(line, "\s*//.*$", "", "")
+ endif
endfunction
function! s:is_string_comment(lnum, col)
- if has('syntax_items')
- for id in synstack(a:lnum, a:col)
- let synname = synIDattr(id, "name")
- if synname == "rustString" || synname =~ "^rustComment"
- return 1
- endif
- endfor
- else
- " without syntax, let's not even try
- return 0
- endif
+ if has('syntax_items')
+ for id in synstack(a:lnum, a:col)
+ let synname = synIDattr(id, "name")
+ if synname ==# "rustString" || synname =~# "^rustComment"
+ return 1
+ endif
+ endfor
+ else
+ " without syntax, let's not even try
+ return 0
+ endif
endfunction
+if exists('*shiftwidth')
+ function! s:shiftwidth()
+ return shiftwidth()
+ endfunc
+else
+ function! s:shiftwidth()
+ return &shiftwidth
+ endfunc
+endif
+
function GetRustIndent(lnum)
+ " Starting assumption: cindent (called at the end) will do it right
+ " normally. We just want to fix up a few cases.
+
+ let line = getline(a:lnum)
+
+ if has('syntax_items')
+ let synname = synIDattr(synID(a:lnum, 1, 1), "name")
+ if synname ==# "rustString"
+ " If the start of the line is in a string, don't change the indent
+ return -1
+ elseif synname =~? '\(Comment\|Todo\)'
+ \ && line !~# '^\s*/\*' " not /* opening line
+ if synname =~? "CommentML" " multi-line
+ if line !~# '^\s*\*' && getline(a:lnum - 1) =~# '^\s*/\*'
+ " This is (hopefully) the line after a /*, and it has no
+ " leader, so the correct indentation is that of the
+ " previous line.
+ return GetRustIndent(a:lnum - 1)
+ endif
+ endif
+ " If it's in a comment, let cindent take care of it now. This is
+ " for cases like "/*" where the next line should start " * ", not
+ " "* " as the code below would otherwise cause for module scope
+ " Fun fact: " /*\n*\n*/" takes two calls to get right!
+ return cindent(a:lnum)
+ endif
+ endif
+
+ " cindent gets second and subsequent match patterns/struct members wrong,
+ " as it treats the comma as indicating an unfinished statement::
+ "
+ " match a {
+ " b => c,
+ " d => e,
+ " f => g,
+ " };
+
+ " Search backwards for the previous non-empty line.
+ let prevlinenum = prevnonblank(a:lnum - 1)
+ let prevline = s:get_line_trimmed(prevlinenum)
+ while prevlinenum > 1 && prevline !~# '[^[:blank:]]'
+ let prevlinenum = prevnonblank(prevlinenum - 1)
+ let prevline = s:get_line_trimmed(prevlinenum)
+ endwhile
+
+ " A standalone '{', '}', or 'where'
+ let l:standalone_open = line =~# '\V\^\s\*{\s\*\$'
+ let l:standalone_close = line =~# '\V\^\s\*}\s\*\$'
+ let l:standalone_where = line =~# '\V\^\s\*where\s\*\$'
+ if l:standalone_open || l:standalone_close || l:standalone_where
+ " ToDo: we can search for more items than 'fn' and 'if'.
+ let [l:found_line, l:col, l:submatch] =
+ \ searchpos('\<\(fn\)\|\(if\)\>', 'bnWp')
+ if l:found_line !=# 0
+ " Now we count the number of '{' and '}' in between the match
+ " locations and the current line (there is probably a better
+ " way to compute this).
+ let l:i = l:found_line
+ let l:search_line = strpart(getline(l:i), l:col - 1)
+ let l:opens = 0
+ let l:closes = 0
+ while l:i < a:lnum
+ let l:search_line2 = substitute(l:search_line, '\V{', '', 'g')
+ let l:opens += strlen(l:search_line) - strlen(l:search_line2)
+ let l:search_line3 = substitute(l:search_line2, '\V}', '', 'g')
+ let l:closes += strlen(l:search_line2) - strlen(l:search_line3)
+ let l:i += 1
+ let l:search_line = getline(l:i)
+ endwhile
+ if l:standalone_open || l:standalone_where
+ if l:opens ==# l:closes
+ return indent(l:found_line)
+ endif
+ else
+ " Expect to find just one more close than an open
+ if l:opens ==# l:closes + 1
+ return indent(l:found_line)
+ endif
+ endif
+ endif
+ endif
+
+ " A standalone 'where' adds a shift.
+ let l:standalone_prevline_where = prevline =~# '\V\^\s\*where\s\*\$'
+ if l:standalone_prevline_where
+ return indent(prevlinenum) + 4
+ endif
- " Starting assumption: cindent (called at the end) will do it right
- " normally. We just want to fix up a few cases.
-
- let line = getline(a:lnum)
-
- if has('syntax_items')
- let synname = synIDattr(synID(a:lnum, 1, 1), "name")
- if synname == "rustString"
- " If the start of the line is in a string, don't change the indent
- return -1
- elseif synname =~ '\(Comment\|Todo\)'
- \ && line !~ '^\s*/\*' " not /* opening line
- if synname =~ "CommentML" " multi-line
- if line !~ '^\s*\*' && getline(a:lnum - 1) =~ '^\s*/\*'
- " This is (hopefully) the line after a /*, and it has no
- " leader, so the correct indentation is that of the
- " previous line.
- return GetRustIndent(a:lnum - 1)
- endif
- endif
- " If it's in a comment, let cindent take care of it now. This is
- " for cases like "/*" where the next line should start " * ", not
- " "* " as the code below would otherwise cause for module scope
- " Fun fact: " /*\n*\n*/" takes two calls to get right!
- return cindent(a:lnum)
- endif
- endif
-
- " cindent gets second and subsequent match patterns/struct members wrong,
- " as it treats the comma as indicating an unfinished statement::
- "
- " match a {
- " b => c,
- " d => e,
- " f => g,
- " };
-
- " Search backwards for the previous non-empty line.
- let prevlinenum = prevnonblank(a:lnum - 1)
- let prevline = s:get_line_trimmed(prevlinenum)
- while prevlinenum > 1 && prevline !~ '[^[:blank:]]'
- let prevlinenum = prevnonblank(prevlinenum - 1)
- let prevline = s:get_line_trimmed(prevlinenum)
- endwhile
-
- " Handle where clauses nicely: subsequent values should line up nicely.
- if prevline[len(prevline) - 1] == ","
- \ && prevline =~# '^\s*where\s'
- return indent(prevlinenum) + 6
- endif
-
- if prevline[len(prevline) - 1] == ","
- \ && s:get_line_trimmed(a:lnum) !~ '^\s*[\[\]{}]'
- \ && prevline !~ '^\s*fn\s'
- \ && prevline !~ '([^()]\+,$'
- \ && s:get_line_trimmed(a:lnum) !~ '^\s*\S\+\s*=>'
- " Oh ho! The previous line ended in a comma! I bet cindent will try to
- " take this too far... For now, let's normally use the previous line's
- " indent.
-
- " One case where this doesn't work out is where *this* line contains
- " square or curly brackets; then we normally *do* want to be indenting
- " further.
- "
- " Another case where we don't want to is one like a function
- " definition with arguments spread over multiple lines:
- "
- " fn foo(baz: Baz,
- " baz: Baz) // <-- cindent gets this right by itself
- "
- " Another case is similar to the previous, except calling a function
- " instead of defining it, or any conditional expression that leaves
- " an open paren:
- "
- " foo(baz,
- " baz);
- "
- " if baz && (foo ||
- " bar) {
- "
- " Another case is when the current line is a new match arm.
- "
- " There are probably other cases where we don't want to do this as
- " well. Add them as needed.
- return indent(prevlinenum)
- endif
-
- if !has("patch-7.4.355")
- " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
- "
- " static FOO : &'static [bool] = [
- " true,
- " false,
- " false,
- " true,
- " ];
- "
- " uh oh, next statement is indented further!
-
- " Note that this does *not* apply the line continuation pattern properly;
- " that's too hard to do correctly for my liking at present, so I'll just
- " start with these two main cases (square brackets and not returning to
- " column zero)
-
- call cursor(a:lnum, 1)
- if searchpair('{\|(', '', '}\|)', 'nbW',
- \ 's:is_string_comment(line("."), col("."))') == 0
- if searchpair('\[', '', '\]', 'nbW',
- \ 's:is_string_comment(line("."), col("."))') == 0
- " Global scope, should be zero
- return 0
- else
- " At the module scope, inside square brackets only
- "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
- if line =~ "^\\s*]"
- " It's the closing line, dedent it
- return 0
- else
- return shiftwidth()
- endif
- endif
- endif
- endif
-
- " Fall back on cindent, which does it mostly right
- return cindent(a:lnum)
+ " Handle where clauses nicely: subsequent values should line up nicely.
+ if prevline[len(prevline) - 1] ==# ","
+ \ && prevline =~# '^\s*where\s'
+ return indent(prevlinenum) + 6
+ endif
+
+ let l:last_prevline_character = prevline[len(prevline) - 1]
+
+ " A line that ends with '.<expr>;' is probably an end of a long list
+ " of method operations.
+ if prevline =~# '\V\^\s\*.' && l:last_prevline_character ==# ';'
+ call cursor(a:lnum - 1, 1)
+ let l:scope_start = searchpair('{\|(', '', '}\|)', 'nbW',
+ \ 's:is_string_comment(line("."), col("."))')
+ if l:scope_start != 0 && l:scope_start < a:lnum
+ return indent(l:scope_start) + 4
+ endif
+ endif
+
+ if l:last_prevline_character ==# ","
+ \ && s:get_line_trimmed(a:lnum) !~# '^\s*[\[\]{})]'
+ \ && prevline !~# '^\s*fn\s'
+ \ && prevline !~# '([^()]\+,$'
+ \ && s:get_line_trimmed(a:lnum) !~# '^\s*\S\+\s*=>'
+ " Oh ho! The previous line ended in a comma! I bet cindent will try to
+ " take this too far... For now, let's normally use the previous line's
+ " indent.
+
+ " One case where this doesn't work out is where *this* line contains
+ " square or curly brackets; then we normally *do* want to be indenting
+ " further.
+ "
+ " Another case where we don't want to is one like a function
+ " definition with arguments spread over multiple lines:
+ "
+ " fn foo(baz: Baz,
+ " baz: Baz) // <-- cindent gets this right by itself
+ "
+ " Another case is similar to the previous, except calling a function
+ " instead of defining it, or any conditional expression that leaves
+ " an open paren:
+ "
+ " foo(baz,
+ " baz);
+ "
+ " if baz && (foo ||
+ " bar) {
+ "
+ " Another case is when the current line is a new match arm.
+ "
+ " There are probably other cases where we don't want to do this as
+ " well. Add them as needed.
+ return indent(prevlinenum)
+ endif
+
+ if !has("patch-7.4.355")
+ " cindent before 7.4.355 doesn't do the module scope well at all; e.g.::
+ "
+ " static FOO : &'static [bool] = [
+ " true,
+ " false,
+ " false,
+ " true,
+ " ];
+ "
+ " uh oh, next statement is indented further!
+
+ " Note that this does *not* apply the line continuation pattern properly;
+ " that's too hard to do correctly for my liking at present, so I'll just
+ " start with these two main cases (square brackets and not returning to
+ " column zero)
+
+ call cursor(a:lnum, 1)
+ if searchpair('{\|(', '', '}\|)', 'nbW',
+ \ 's:is_string_comment(line("."), col("."))') == 0
+ if searchpair('\[', '', '\]', 'nbW',
+ \ 's:is_string_comment(line("."), col("."))') == 0
+ " Global scope, should be zero
+ return 0
+ else
+ " At the module scope, inside square brackets only
+ "if getline(a:lnum)[0] == ']' || search('\[', '', '\]', 'nW') == a:lnum
+ if line =~# "^\\s*]"
+ " It's the closing line, dedent it
+ return 0
+ else
+ return &shiftwidth
+ endif
+ endif
+ endif
+ endif
+
+ " Fall back on cindent, which does it mostly right
+ return cindent(a:lnum)
endfunction
+" vint: -ProhibitAbbreviationOption
let &cpo = s:save_cpo
unlet s:save_cpo
+" vint: +ProhibitAbbreviationOption
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/indent/scala.vim b/runtime/indent/scala.vim
index b5eba29543..c6aba4e388 100644
--- a/runtime/indent/scala.vim
+++ b/runtime/indent/scala.vim
@@ -4,6 +4,7 @@
" Modifications By: Derek Wyatt
" URL: https://github.com/derekwyatt/vim-scala
" Last Change: 2016 Aug 26
+" 2023 Aug 28 by Vim Project (undo_indent)
if exists("b:did_indent")
finish
@@ -14,6 +15,8 @@ setlocal autoindent
setlocal indentexpr=GetScalaIndent()
setlocal indentkeys=0{,0},0),!^F,<>>,o,O,e,=case,<CR>
+let b:undo_indent = "setl ai< inde< indk<"
+
if exists("*GetScalaIndent")
finish
endif
diff --git a/runtime/indent/solidity.vim b/runtime/indent/solidity.vim
index caed726c0a..55a07c015a 100644
--- a/runtime/indent/solidity.vim
+++ b/runtime/indent/solidity.vim
@@ -1,9 +1,11 @@
" Vim indent file
-" Language: Solidity
-" Acknowledgement: Based off of vim-javascript
-" Maintainer: Cothi (jiungdev@gmail.com)
-" Original Author: tomlion (https://github.com/tomlion/vim-solidity)
-" Last Changed: 2022 Sep 27
+" Language: Solidity
+" Maintainer: Cothi (jiungdev@gmail.com)
+" Original Author: tomlion (https://github.com/tomlion/vim-solidity)
+" Last Change: 2022 Sep 27
+" 2023 Aug 22 Vim Project (undo_indent)
+"
+" Acknowledgement: Based off of vim-javascript
"
" 0. Initialization {{{1
" =================
@@ -20,6 +22,8 @@ setlocal nosmartindent
setlocal indentexpr=GetSolidityIndent()
setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
+let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<"
+
" Only define the function once.
if exists("*GetSolidityIndent")
finish
diff --git a/runtime/indent/systemverilog.vim b/runtime/indent/systemverilog.vim
index a5f4d5b90d..42a05a03aa 100644
--- a/runtime/indent/systemverilog.vim
+++ b/runtime/indent/systemverilog.vim
@@ -78,10 +78,10 @@ function SystemVerilogIndent()
" Multiple-line comment count
if curr_line =~ '^\s*/\*' && curr_line !~ '/\*.\{-}\*/'
let s:multiple_comment += 1
- if vverb | echom vverb_str "Start of multiple-line commnt" | endif
+ if vverb | echom vverb_str "Start of multiple-line comment" | endif
elseif curr_line =~ '\*/\s*$' && curr_line !~ '/\*.\{-}\*/'
let s:multiple_comment -= 1
- if vverb | echom vverb_str "End of multiple-line commnt" | endif
+ if vverb | echom vverb_str "End of multiple-line comment" | endif
return ind
endif
" Maintain indentation during commenting.
diff --git a/runtime/indent/testdir/dts.in b/runtime/indent/testdir/dts.in
new file mode 100644
index 0000000000..64e56e9017
--- /dev/null
+++ b/runtime/indent/testdir/dts.in
@@ -0,0 +1,46 @@
+/* vim: set ft=dts noet sw=8 : */
+
+/* START_INDENT */
+/dts-v1/;
+#include <dt-bindings/pinctrl/pinctrl-imx6q.h>
+ #include "imx6qdl.dtsi"
+#include "imx6qdl-someboard.dtsi"
+
+ /delete-node/ &{/memory@10000000};
+
+ / {
+compatible = "some,board";
+/delete-node/ memory;
+
+ chosen {
+environment = &{usdhc4/partitions/partition@0};
+};
+}
+
+ &iomuxc {
+pinctrl-names = "default";
+pinctrl-0 = <&pinctrl_hog>;
+
+pinctrl_gpiohog: gpiohoggrp {
+fsl,pins = <
+MX6QDL_PAD_GPIO_9__GPIO1_IO09 0x130b0
+MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x130b0
+>;
+};
+}
+
+&usdhc4 {
+partitions {
+compatible = "fixed-partitions";
+
+partition@0 {
+label = "environment";
+reg = <0x0 0xe0000>;
+};
+};
+};
+
+&{/aliases} {
+usb0 = &usb;
+};
+/* END_INDENT */
diff --git a/runtime/indent/testdir/dts.ok b/runtime/indent/testdir/dts.ok
new file mode 100644
index 0000000000..d249766fd4
--- /dev/null
+++ b/runtime/indent/testdir/dts.ok
@@ -0,0 +1,46 @@
+/* vim: set ft=dts noet sw=8 : */
+
+/* START_INDENT */
+/dts-v1/;
+#include <dt-bindings/pinctrl/pinctrl-imx6q.h>
+#include "imx6qdl.dtsi"
+#include "imx6qdl-someboard.dtsi"
+
+/delete-node/ &{/memory@10000000};
+
+/ {
+ compatible = "some,board";
+ /delete-node/ memory;
+
+ chosen {
+ environment = &{usdhc4/partitions/partition@0};
+ };
+}
+
+&iomuxc {
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_hog>;
+
+ pinctrl_gpiohog: gpiohoggrp {
+ fsl,pins = <
+ MX6QDL_PAD_GPIO_9__GPIO1_IO09 0x130b0
+ MX6QDL_PAD_GPIO_17__GPIO7_IO12 0x130b0
+ >;
+ };
+}
+
+&usdhc4 {
+ partitions {
+ compatible = "fixed-partitions";
+
+ partition@0 {
+ label = "environment";
+ reg = <0x0 0xe0000>;
+ };
+ };
+};
+
+&{/aliases} {
+ usb0 = &usb;
+};
+/* END_INDENT */
diff --git a/runtime/indent/testdir/rapid.in b/runtime/indent/testdir/rapid.in
new file mode 100644
index 0000000000..515912ed89
--- /dev/null
+++ b/runtime/indent/testdir/rapid.in
@@ -0,0 +1,266 @@
+! vim: set ft=rapid :
+
+! START_INDENT
+
+%%%
+ VERSION:1
+ LANGUAGE:ENGLISH
+%%%
+
+module LowerCaseModule
+
+task pers num n1 := 0;
+local pers num n2 := 1;
+var bool b1 := false;
+var intnum i1;
+
+! put some stuff in those strings that may confuse indentation
+const string st1 := "endmodule (";
+pers string st_Appl_Info{3,3}:=[
+[
+"["
+,
+"default"
+,
+"case"
+],
+[
+"else"
+,
+"then"
+,
+"endif"
+],
+[
+"do"
+,
+"}"
+,
+")"
+],
+];
+
+pers tooldata tTool1:=[TRUE,
+[
+[97.4, 0, 223.1],
+[0.924, 0, 0.383 ,0]
+],
+[5,
+[23, 0, 75],
+[1, 0, 0, 0], 0, 0, 0
+]
+];
+const robtarget p1:=[
+[600, 500, 225.3],
+[1, 0, 0, 0],
+[1, 1, 0, 0],
+[ 11, 12.3, 9E9, 9E9, 9E9, 9E9]
+];
+
+record myRec
+num nRecNum1
+bool bRecBool1
+endrecord
+
+proc proc1(num n1,
+num n2)
+var string st1;
+n1 := n1+1;
+MoveJSync p1, vmax, z30, tool1, "proc2";
+backward
+MoveJSync p1, v100, fine, tool1, "proc2";
+undo
+n1 := n1-1;
+error
+trynext;
+endproc
+
+func num nFunc1(
+switch s1
+|switch s2
+,num n1
+,bool b1)
+var num nVar;
+if not Present(s1) return;
+if Present(s1) then
+Incr n1;'
+elseif Present(s2) then
+b1:=false;
+else
+while n1>0 do
+Decr n1;
+test n1
+
+case 1:
+test1;
+case 2:
+test2;
+default:
+WaitUntil false;
+endtest
+endwhile
+endif
+for i from 1 to 10 step 2 do
+for j from 1 to 10 do
+st_Appl_Info{i,j} := "";
+endfor
+endfor
+! return 1;
+return 0;
+error
+return -1;
+endfunc
+
+trap Trap1
+Reset do1;
+endtrap
+
+endmodule
+
+MODULE UpperCaseModule(SYSMODULE,NOSTEPIN)
+TASK pers num n1 := 0;
+LOCAL pers num n2 := 1;
+VAR bool b1 := false;
+VAR intnum i1;
+
+LOCAL FUNC num nFunc1(
+switch s1
+|switch s2
+,num n1
+,bool b1)
+VAR num nVar;
+IF NOT PRESENT(s1) RETURN;
+IF PRESENT(s1) THEN
+INCR n1;'
+ELSEIF PRESENT(s2) THEN
+b1:=FALSE;
+ELSE
+WHILE n1>0 DO
+DECR n1;
+TEST n1
+
+CASE 1:
+test1;
+CASE 2:
+test2;
+DEFAULT:
+WAITUNTIL FALSE;
+ENDTEST
+ENDWHILE
+ENDIF
+FOR i FROM 1 TO 10 STEP 2 DO
+FOR j FROM 1 TO 10 DO
+st_Appl_Info{i,j} := "";
+ENDFOR
+ENDFOR
+! RETURN 1;
+RETURN 0;
+ERROR
+RETURN -1;
+ENDFUNC
+
+TRAP Trap1
+Reset do1;
+ENDTRAP
+
+ENDMODULE
+
+Module MixedCaseModule(SysModule)
+Task pers num n1 := 0;
+Local pers num n2 := 1;
+Var bool b1 := false;
+Var intnum i1;
+
+Task Func num nFunc1(
+switch s1
+|switch s2
+,num n1
+,bool b1)
+Var num nVar;
+If Not Present(s1) Return;
+If Present(s1) Then
+Incr n1;'
+ElseIf Present(s2) Then
+b1:=false;
+Else
+While n1>0 Do
+Decr n1;
+Test n1
+
+Case 1:
+test1;
+Case 2:
+test2;
+Default:
+WaitUntil false;
+EndTest
+EndWhile
+EndIf
+For i From 1 To 10 Step 2 Do
+For j From 1 To 10 Do
+st_Appl_Info{i,j} := "";
+EndFor
+EndFor
+! Return 1;
+Return 0;
+Error
+Return -1;
+EndFunc
+
+Trap Trap1
+Reset do1;
+EndTrap
+
+EndModule
+
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidSpaceIndent = 0
+! INDENT_EXE set shiftwidth=4
+
+proc bla()
+var num i;
+Incr i;
+endproc
+
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidCommentIndent = 1
+!
+proc bla()
+! indent this first column comment because of g:rapidCommentIndent=1
+endproc
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidNewStyleIndent = 1
+pers string st_Appl_Info{3,3}:=
+[
+[
+"["
+,
+"default"
+,
+"case"
+]
+,
+[
+"else"
+,
+"then"
+,
+"endif"
+]
+,
+[
+"do"
+,
+"}"
+,
+")"
+]
+,
+];
+! END_INDENT
diff --git a/runtime/indent/testdir/rapid.ok b/runtime/indent/testdir/rapid.ok
new file mode 100644
index 0000000000..ce336821c4
--- /dev/null
+++ b/runtime/indent/testdir/rapid.ok
@@ -0,0 +1,266 @@
+! vim: set ft=rapid :
+
+! START_INDENT
+
+%%%
+VERSION:1
+LANGUAGE:ENGLISH
+%%%
+
+module LowerCaseModule
+
+ task pers num n1 := 0;
+ local pers num n2 := 1;
+ var bool b1 := false;
+ var intnum i1;
+
+! put some stuff in those strings that may confuse indentation
+ const string st1 := "endmodule (";
+ pers string st_Appl_Info{3,3}:=[
+ [
+ "["
+ ,
+ "default"
+ ,
+ "case"
+ ],
+ [
+ "else"
+ ,
+ "then"
+ ,
+ "endif"
+ ],
+ [
+ "do"
+ ,
+ "}"
+ ,
+ ")"
+ ],
+ ];
+
+ pers tooldata tTool1:=[TRUE,
+ [
+ [97.4, 0, 223.1],
+ [0.924, 0, 0.383 ,0]
+ ],
+ [5,
+ [23, 0, 75],
+ [1, 0, 0, 0], 0, 0, 0
+ ]
+ ];
+ const robtarget p1:=[
+ [600, 500, 225.3],
+ [1, 0, 0, 0],
+ [1, 1, 0, 0],
+ [ 11, 12.3, 9E9, 9E9, 9E9, 9E9]
+ ];
+
+ record myRec
+ num nRecNum1
+ bool bRecBool1
+ endrecord
+
+ proc proc1(num n1,
+ num n2)
+ var string st1;
+ n1 := n1+1;
+ MoveJSync p1, vmax, z30, tool1, "proc2";
+ backward
+ MoveJSync p1, v100, fine, tool1, "proc2";
+ undo
+ n1 := n1-1;
+ error
+ trynext;
+ endproc
+
+ func num nFunc1(
+ switch s1
+ |switch s2
+ ,num n1
+ ,bool b1)
+ var num nVar;
+ if not Present(s1) return;
+ if Present(s1) then
+ Incr n1;'
+ elseif Present(s2) then
+ b1:=false;
+ else
+ while n1>0 do
+ Decr n1;
+ test n1
+
+ case 1:
+ test1;
+ case 2:
+ test2;
+ default:
+ WaitUntil false;
+ endtest
+ endwhile
+ endif
+ for i from 1 to 10 step 2 do
+ for j from 1 to 10 do
+ st_Appl_Info{i,j} := "";
+ endfor
+ endfor
+! return 1;
+ return 0;
+ error
+ return -1;
+ endfunc
+
+ trap Trap1
+ Reset do1;
+ endtrap
+
+endmodule
+
+MODULE UpperCaseModule(SYSMODULE,NOSTEPIN)
+ TASK pers num n1 := 0;
+ LOCAL pers num n2 := 1;
+ VAR bool b1 := false;
+ VAR intnum i1;
+
+ LOCAL FUNC num nFunc1(
+ switch s1
+ |switch s2
+ ,num n1
+ ,bool b1)
+ VAR num nVar;
+ IF NOT PRESENT(s1) RETURN;
+ IF PRESENT(s1) THEN
+ INCR n1;'
+ ELSEIF PRESENT(s2) THEN
+ b1:=FALSE;
+ ELSE
+ WHILE n1>0 DO
+ DECR n1;
+ TEST n1
+
+ CASE 1:
+ test1;
+ CASE 2:
+ test2;
+ DEFAULT:
+ WAITUNTIL FALSE;
+ ENDTEST
+ ENDWHILE
+ ENDIF
+ FOR i FROM 1 TO 10 STEP 2 DO
+ FOR j FROM 1 TO 10 DO
+ st_Appl_Info{i,j} := "";
+ ENDFOR
+ ENDFOR
+! RETURN 1;
+ RETURN 0;
+ ERROR
+ RETURN -1;
+ ENDFUNC
+
+ TRAP Trap1
+ Reset do1;
+ ENDTRAP
+
+ENDMODULE
+
+Module MixedCaseModule(SysModule)
+ Task pers num n1 := 0;
+ Local pers num n2 := 1;
+ Var bool b1 := false;
+ Var intnum i1;
+
+ Task Func num nFunc1(
+ switch s1
+ |switch s2
+ ,num n1
+ ,bool b1)
+ Var num nVar;
+ If Not Present(s1) Return;
+ If Present(s1) Then
+ Incr n1;'
+ ElseIf Present(s2) Then
+ b1:=false;
+ Else
+ While n1>0 Do
+ Decr n1;
+ Test n1
+
+ Case 1:
+ test1;
+ Case 2:
+ test2;
+ Default:
+ WaitUntil false;
+ EndTest
+ EndWhile
+ EndIf
+ For i From 1 To 10 Step 2 Do
+ For j From 1 To 10 Do
+ st_Appl_Info{i,j} := "";
+ EndFor
+ EndFor
+! Return 1;
+ Return 0;
+ Error
+ Return -1;
+ EndFunc
+
+ Trap Trap1
+ Reset do1;
+ EndTrap
+
+EndModule
+
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidSpaceIndent = 0
+! INDENT_EXE set shiftwidth=4
+
+proc bla()
+ var num i;
+ Incr i;
+endproc
+
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidCommentIndent = 1
+!
+proc bla()
+ ! indent this first column comment because of g:rapidCommentIndent=1
+endproc
+! END_INDENT
+
+! START_INDENT
+! INDENT_EXE let g:rapidNewStyleIndent = 1
+pers string st_Appl_Info{3,3}:=
+[
+ [
+ "["
+ ,
+ "default"
+ ,
+ "case"
+ ]
+ ,
+ [
+ "else"
+ ,
+ "then"
+ ,
+ "endif"
+ ]
+ ,
+ [
+ "do"
+ ,
+ "}"
+ ,
+ ")"
+ ]
+ ,
+];
+! END_INDENT
diff --git a/runtime/indent/testdir/runtest.vim b/runtime/indent/testdir/runtest.vim
index fa4e16e381..882a140a8d 100644
--- a/runtime/indent/testdir/runtest.vim
+++ b/runtime/indent/testdir/runtest.vim
@@ -12,6 +12,7 @@ set nowrapscan
set report=9999
set modeline
set debug=throw
+set nomore
au! SwapExists * call HandleSwapExists()
func HandleSwapExists()
diff --git a/runtime/indent/tex.vim b/runtime/indent/tex.vim
index d356ba905b..68d13fb116 100644
--- a/runtime/indent/tex.vim
+++ b/runtime/indent/tex.vim
@@ -67,7 +67,8 @@
" 2020/04/26 by Yichao Zhou <broken.zhou AT gmail.com>
" (*) Fix a bug related to \[ & \]. Thanks Manuel Boni for
" reporting.
-"
+" 2023/08/28 by Vim Project
+" (*) Set b:undo_indent.
" }}}
" Document: {{{
@@ -167,6 +168,7 @@ setlocal indentexpr=GetTeXIndent()
setlocal indentkeys&
exec 'setlocal indentkeys+=[,(,{,),},],\&' . substitute(g:tex_items, '^\|\(\\|\)', ',=', 'g')
let g:tex_items = '^\s*' . substitute(g:tex_items, '^\(\^\\s\*\)*', '', '')
+let b:undo_indent = "setlocal autoindent< indentexpr< indentkeys< smartindent<"
" }}}
function! GetTeXIndent() " {{{
diff --git a/runtime/indent/typescript.vim b/runtime/indent/typescript.vim
index e899f83d0f..e26750b8aa 100644
--- a/runtime/indent/typescript.vim
+++ b/runtime/indent/typescript.vim
@@ -2,6 +2,7 @@
" Language: TypeScript
" Maintainer: See https://github.com/HerringtonDarkholme/yats.vim
" Last Change: 2019 Oct 18
+" 2023 Aug 28 by Vim Project (undo_indent)
" Acknowledgement: Based off of vim-ruby maintained by Nikolai Weibull http://vim-ruby.rubyforge.org
" 0. Initialization {{{1
@@ -20,6 +21,8 @@ setlocal indentexpr=GetTypescriptIndent()
setlocal formatexpr=Fixedgq(v:lnum,v:count)
setlocal indentkeys=0{,0},0),0],0\,,!^F,o,O,e
+let b:undo_indent = "setlocal formatexpr< indentexpr< indentkeys< smartindent<"
+
" Only define the function once.
if exists("*GetTypescriptIndent")
finish
diff --git a/runtime/indent/typescriptreact.vim b/runtime/indent/typescriptreact.vim
new file mode 100644
index 0000000000..052bddddbd
--- /dev/null
+++ b/runtime/indent/typescriptreact.vim
@@ -0,0 +1,2 @@
+" Placeholder for backwards compatilibity: .tsx used to stand for TypeScript.
+runtime! indent/typescript.vim
diff --git a/runtime/indent/verilog.vim b/runtime/indent/verilog.vim
index e81197c3b4..377615c348 100644
--- a/runtime/indent/verilog.vim
+++ b/runtime/indent/verilog.vim
@@ -1,6 +1,7 @@
" Language: Verilog HDL
" Maintainer: Chih-Tsun Huang <cthuang@cs.nthu.edu.tw>
" Last Change: 2017 Aug 25 by Chih-Tsun Huang
+" 2023 Aug 28 by Vim Project (undo_indent)
" URL: http://www.cs.nthu.edu.tw/~cthuang/vim/indent/verilog.vim
"
" Credits:
@@ -28,6 +29,8 @@ setlocal indentkeys+==endmodule,=endfunction,=endtask,=endspecify
setlocal indentkeys+==endconfig,=endgenerate,=endprimitive,=endtable
setlocal indentkeys+==`else,=`elsif,=`endif
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
" Only define the function once.
if exists("*GetVerilogIndent")
finish
diff --git a/runtime/indent/vhdl.vim b/runtime/indent/vhdl.vim
index ad318066f4..b01d1156e9 100644
--- a/runtime/indent/vhdl.vim
+++ b/runtime/indent/vhdl.vim
@@ -3,6 +3,7 @@
" Maintainer: Gerald Lai <laigera+vim?gmail.com>
" Version: 1.62
" Last Change: 2017 Oct 17
+" 2023 Aug 28 by Vim Project (undo_indent)
" URL: http://www.vim.org/scripts/script.php?script_id=1450
" only load this indent file when no other was loaded
@@ -19,6 +20,8 @@ setlocal indentkeys+==~if,=~then,=~elsif,=~else
setlocal indentkeys+==~case,=~loop,=~for,=~generate,=~record,=~units,=~process,=~block,=~function,=~component,=~procedure
setlocal indentkeys+==~architecture,=~configuration,=~entity,=~package
+let b:undo_indent = "setlocal indentexpr< indentkeys<"
+
" constants
" not a comment
let s:NC = '\%(--.*\)\@<!'
diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim
index 3beb70d255..b2fb57f51f 100644
--- a/runtime/indent/vim.vim
+++ b/runtime/indent/vim.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: Vim script
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Jun 24
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/wast.vim b/runtime/indent/wat.vim
index 1be234b6e9..08997f1a98 100644
--- a/runtime/indent/wast.vim
+++ b/runtime/indent/wat.vim
@@ -1,7 +1,7 @@
" Vim indent file
" Language: WebAssembly
" Maintainer: rhysd <lin90162@yahoo.co.jp>
-" Last Change: Jul 29, 2018
+" Last Change: Nov 14, 2023
" For bugs, patches and license go to https://github.com/rhysd/vim-wasm
if exists("b:did_indent")
diff --git a/runtime/indent/xhtml.vim b/runtime/indent/xhtml.vim
index 2197b7901c..e5c9cc307f 100644
--- a/runtime/indent/xhtml.vim
+++ b/runtime/indent/xhtml.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: XHTML
-" Maintainer: Bram Moolenaar <Bram@vim.org> (for now)
-" Last Change: 2005 Jun 24
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indent/zimbu.vim b/runtime/indent/zimbu.vim
index 0e6e2ab1d1..08369e42e5 100644
--- a/runtime/indent/zimbu.vim
+++ b/runtime/indent/zimbu.vim
@@ -1,7 +1,8 @@
" Vim indent file
" Language: Zimbu
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2021 Sep 26
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
diff --git a/runtime/indoff.vim b/runtime/indoff.vim
index 0d930352bb..57d5e5400e 100644
--- a/runtime/indoff.vim
+++ b/runtime/indoff.vim
@@ -1,7 +1,8 @@
" Vim support file to switch off loading indent files for file types
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2001 Jun 11
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
if exists("did_indent_on")
unlet did_indent_on
diff --git a/runtime/keymap/accents.vim b/runtime/keymap/accents.vim
index fed94c5656..a97a280881 100644
--- a/runtime/keymap/accents.vim
+++ b/runtime/keymap/accents.vim
@@ -1,6 +1,6 @@
" Vim Keymap file for latin1 accents through dead characters
-" Maintainer: Bram Moolenaar
-" Last Change: 2006 Mar 29
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
" All characters are given literally, conversion to another encoding (e.g.,
" UTF-8) should work.
diff --git a/runtime/keymap/arabic_utf-8.vim b/runtime/keymap/arabic_utf-8.vim
index aeb4a4f398..60fd05fc81 100644
--- a/runtime/keymap/arabic_utf-8.vim
+++ b/runtime/keymap/arabic_utf-8.vim
@@ -1,7 +1,7 @@
" Vim Keymap file for Arabic
" Maintainer : Arabic Support group <support-at-arabeyes.org>
" Created by : Nadim Shaikli <nadim-at-arabeyes.org>
-" Last Updated : 2003 Apr 26
+" Last Updated : 2023-10-27
" This is for a standard Microsoft Arabic keyboard layout.
" Use this short name in the status line.
@@ -41,16 +41,6 @@ m <char-0x0629> " (1577) - TEH MARBUTA
, <char-0x0648> " (1608) - WAW
. <char-0x0632> " (1586) - ZAIN
/ <char-0x0638> " (1592) - ZAH
-0 <char-0x0660> " (1632) - Arabic 0
-1 <char-0x0661> " (1633) - Arabic 1
-2 <char-0x0662> " (1634) - Arabic 2
-3 <char-0x0663> " (1635) - Arabic 3
-4 <char-0x0664> " (1636) - Arabic 4
-5 <char-0x0665> " (1637) - Arabic 5
-6 <char-0x0666> " (1638) - Arabic 6
-7 <char-0x0667> " (1639) - Arabic 7
-8 <char-0x0668> " (1640) - Arabic 8
-9 <char-0x0669> " (1641) - Arabic 9
` <char-0x0630> " (1584) - THAL
~ <char-0x0651> " (1617) - Tanween -- SHADDA
Q <char-0x064e> " (1614) - Tanween -- FATHA
diff --git a/runtime/keymap/greek_utf-8.vim b/runtime/keymap/greek_utf-8.vim
index 17564542d9..c6cc32563e 100644
--- a/runtime/keymap/greek_utf-8.vim
+++ b/runtime/keymap/greek_utf-8.vim
@@ -34,7 +34,7 @@
" without having to combine them with letters (usufull for grammarians
" in particular) (especially for dasia and psiln we use ' for psili
" (that is apostrophe) and ;' for dasia. This is done in order to
-" preserve the posibility to write a plain < or >.
+" preserve the possibility to write a plain < or >.
" Ypogegrammeni is | following the character (the originally proposed
" i after the character is problematic: can't write easily ai or vi) :
diff --git a/runtime/keymap/russian-typograph.vim b/runtime/keymap/russian-typograph.vim
index e0fbf22884..7c5aed6089 100644
--- a/runtime/keymap/russian-typograph.vim
+++ b/runtime/keymap/russian-typograph.vim
@@ -1,54 +1,140 @@
" Vim Keymap file for Russian characters
-" layout English-US standard 104 key 'QWERTY', 'JCUKEN'
+" layout English-US 104 key 'QWERTY'
"
-" Maintainer: Restorer <restorers@users.sourceforge.net>
-" Last Changed: 20 Jan 2019
-" Description: Раскладка сделана на основе раскладки «русская машинопись»
-" (KBDRU1.DLL), поставляемой в составе ОС MS Windows. Эта раскладка позволяет
-" печать практически все знаки препинания используя цифровой ряд и не требуя при
-" этом нажатия дополнительных клавиш, ну и также удобное расположение буквы «Ё».
-" Однако были внесены некоторые дополнения (улучшения?), в частности:
-" ‐ раздельные символы круглых скобок (), расположены на тех же позициях, что и
-" в US-International;
-" ‐ раздельные символы типографских кавычек «», расположены на клавишах «3» и
-" «4» соответственно;
-" ‐ на этих же клавишах находятся внутренние кавычки “лапки”, набираемые при
-" нажатой клавише «ALT»;
-" ‐ возможность набирать символы, отсутствующие в русской раскладке клавиатуры,
-" а именно @#$^&*{}[]"'`~<>, которые расположены на тех же местах, что и раньше.
-" Для этого не требуется переключаться в латинскую раскладку клавиатуры, а
-" оставаясь в русской, использовать для этого дополнительные клавиши «SHIFT» и
-" «ALT»;
-" ‐ и ещё несколько удобств, которые позволяют быстро и с минимальными усилиями
-" набирать текст.
-
-scriptencoding utf-8
-
-" Переключение языка ввода со стандартного сочетания <CTRL+^> на указанные ниже
-" Для режимов вставки и замены
-""или SHIFT+SPACE
-" inoremap <S-Space> <C-^>
-""или CTRL+SPACE"
-" inoremap <C-Space> <C-^>
-" Для режима командной строки
-""или SHIFT+SPACE
-" cnoremap <S-Space> <C-^>
-""или CTRL+SPACE"
-" cnoremap <C-Space> <C-^>
-" Одной командой для режимов вставки, замены и командной строки
-" noremap! <S-Space> <C-^>
-" noremap! <C-Space> <C-^>
+" Maintainer: Restorer <restorer@mail2k.ru>
+" Last Changed: 25 Apr 2023
+" Version: 3.3
+" Description: описание дано после изображений клавиатуры
+
+" Расположение символов для русского языка при подключенном файле с раскладкой
+" клавиатуры «русская типографская» (russian-typograph.vim). Версия 3.3
+
+
+" Ни одна из клавиш модификаторов не нажата
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | % | ! | — | « | » | : | , | . | ? | ( | ) | ‐ | ; | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | й | ц | у | к | е | н | г | ш | щ | з | х | ъ | / |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | ф | ы | в | а | п | р | о | л | д | ж | э | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | Shift | я | ч | с | м | и | т | ь | б | ю | ё | Shift |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | Ctrl | | Alt | | Alt | | Ctrl |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажата клавиша SHIFT
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | = | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | + | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | Й | Ц | У | К | Е | Н | Г | Ш | Щ | З | Х | Ъ | § |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | Ф | Ы | В | А | П | Р | О | Л | Д | Ж | Э | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | SHIFT | Я | Ч | С | М | И | Т | Ь | Б | Ю | Ё | SHIFT |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | Ctrl | | Alt | | Alt | | Ctrl |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажата клавиша ALT
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | ` | № | – | „ | “ | | | … | | | | ‑ | ± | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | | | | | | | | | | | [ | ] | \ |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | | | | | | ₽ | | | | | ' | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | Shift | | | | | | | | | | | Shift |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | Ctrl | | ALT | NNBSP | ALT | | Ctrl |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажаты клавиши SHIFT и ALT
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | ~ | | @ | # | $ | | ^ | & | * | | | _ | | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | | | | | | | | | | | { | } | | |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | | | | | | | | | | | " | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | SHIFT | | | | | | | | < | > | | SHIFT |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | Ctrl | | ALT | | ALT | | Ctrl |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажаты клавиши SHIFT и CTRL
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | | | | | | | | | | | | | | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | | | | | | | | | | | | | |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | | | | | | | | | | | | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | SHIFT | | | | | | | | | | | SHIFT |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | CTRL | | Alt | NBSP | Alt | | CTRL |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажаты клавиши ALT и CTRL
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | | | | | | | | | | | | | | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | | | у́ | | е́ | | | | | | | | |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | | ы́ | | а́ | | | о́ | | | | э́ | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | Shift | я́ | | | | и́ | | | | ю́ | | Shift |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | CTRL | | ALT | | ALT | | CTRL |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Нажаты клавиши SHIFT, ALT и CTRL
+
+" ,---,---,---,---,---,---,---,---,---,---,---,---,---,-------,
+" | | | | | | | | | | | | | | <--- |
+" |---'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-----|
+" | ->| | | | У́ | | Е́ | | | | | | | | |
+" |-----',--',--',--',--',--',--',--',--',--',--',--',--'-----|
+" | Caps | | Ы́ | | А́ | | | О́ | | | | Э́ | Enter |
+" |------'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'-,-'--------|
+" | SHIFT | Я́ | | | | И́ | | | | Ю́ | | SHIFT |
+" |------,-',--'--,'---'---'---'---'---'---'-,-'---',--,------|
+" | CTRL | | ALT | | ALT | | CTRL |
+" '------' '-----'--------------------------'------' '------'
+
+
+" Раскладка сделана на основе раскладки «русская машинопись» (KBDRU1.DLL),
+" поставляемой в составе ОС MS Windows.
"
-" Стандартное переключение по CTRL+^ после этих переназначений также сохраняется
+" Как видите, принцип достаточно простой, — при наборе русского текста все
+" буквы, знаки пунктуации и большинство специальных символов находятся под
+" пальцами и их набор не требует нажатия каких-то дополнительных клавиш. При
+" наборе символов, которые отсутствуют в русской раскладке, но есть в раскладке
+" US-International, требуется нажать или клавишу <ALT>, если в английской
+" раскладке они набираются без модификаторов, или нажать <ALT>+<SHIFT>, если они
+" набираются с модификатором <SHIFT>.
+
+" scriptencoding utf-8
let b:keymap_name ="RUS"
loadkeymap
-" DIGITAL ROW
+" DIGITAL ROW
-" The Shift key is not pressed
-"
+" None of the modifier keys are pressed
<char-0x0060> <char-0x0025> " PERCENT SIGN
<char-0x0031> <char-0x0021> " EXCLAMATION MARK
<char-0x0032> <char-0x2014> " EM DASH
@@ -63,8 +149,7 @@ loadkeymap
<char-0x002d> <char-0x2010> " HYPHEN
<char-0x003d> <char-0x003b> " SEMICOLON
-" The Shift key is pressed
-
+" The SHIFT key is pressed
<char-0x007e> <char-0x003d> " EQUALS SIGN
<char-0x0021> <char-0x0031> " DIGIT ONE
<char-0x0040> <char-0x0032> " DIGIT TWO
@@ -79,112 +164,188 @@ loadkeymap
<char-0x005f> <char-0x002d> " HYPHEN-MINUS
<char-0x002b> <char-0x002b> " PLUS SIGN
-" ALPHABETICAL 1st ROW
+" The ALT key pressed
+<A-char-0x0060> <char-0x0060> " GRAVE ACCENT
+<A-char-0x0031> <char-0x2116> " NUMERO SIGN
+<A-char-0x0032> <char-0x2013> " EN DASH
+<A-char-0x0033> <char-0x201e> " DOUBLE LOW-9 QUOTATION MARK
+<A-char-0x0034> <char-0x201c> " LEFT DOUBLE QUOTATION MARK
+<A-char-0x0037> <char-0x2026> " HORIZONTAL ELLIPSIS
+<A-char-0x002d> <char-0x2011> " NON-BREAKING HYPHEN
+<A-char-0x003d> <char-0x00b1> " PLUS-MINUS SIGN
+
+" The SHIFT and ALT keys pressed
+<A-char-0x007e> <char-0x007e> " TILDE
+<A-char-0x0040> <char-0x0040> " COMMERCIAL AT
+<A-char-0x0023> <char-0x0023> " NUMBER SIGN
+<A-char-0x0024> <char-0x0024> " DOLLAR SIGN
+<A-char-0x005e> <char-0x005e> " CIRCUMFLEX ACCENT
+<A-char-0x0026> <char-0x0026> " AMPERSAND
+<A-char-0x002a> <char-0x002a> " ASTERISK
+<A-char-0x005f> <char-0x005f> " LOW LINE
+
+" ALPHABETICAL 1st ROW
+
+" None of the modifier keys are pressed
<char-0x0071> <char-0x0439> " CYRILLIC SMALL LETTER SHORT I
-<char-0x0051> <char-0x0419> " CYRILLIC CAPITAL LETTER SHORT I
<char-0x0077> <char-0x0446> " CYRILLIC SMALL LETTER TSE
-<char-0x0057> <char-0x0426> " CYRILLIC CAPITAL LETTER TSE
<char-0x0065> <char-0x0443> " CYRILLIC SMALL LETTER U
-<char-0x0045> <char-0x0423> " CYRILLIC CAPITAL LETTER U
<char-0x0072> <char-0x043a> " CYRILLIC SMALL LETTER KA
-<char-0x0052> <char-0x041a> " CYRILLIC CAPITAL LETTER KA
<char-0x0074> <char-0x0435> " CYRILLIC SMALL LETTER IE
-<char-0x0054> <char-0x0415> " CYRILLIC CAPITAL LETTER IE
<char-0x0079> <char-0x043d> " CYRILLIC SMALL LETTER EN
-<char-0x0059> <char-0x041d> " CYRILLIC CAPITAL LETTER EN
<char-0x0075> <char-0x0433> " CYRILLIC SMALL LETTER GHE
-<char-0x0055> <char-0x0413> " CYRILLIC CAPITAL LETTER GHE
<char-0x0069> <char-0x0448> " CYRILLIC SMALL LETTER SHA
-<char-0x0049> <char-0x0428> " CYRILLIC CAPITAL LETTER SHA
<char-0x006f> <char-0x0449> " CYRILLIC SMALL LETTER SHCHA
-<char-0x004f> <char-0x0429> " CYRILLIC CAPITAL LETTER SHCHA
<char-0x0070> <char-0x0437> " CYRILLIC SMALL LETTER ZE
-<char-0x0050> <char-0x0417> " CYRILLIC CAPITAL LETTER ZE
<char-0x005b> <char-0x0445> " CYRILLIC SMALL LETTER HA
-<char-0x007b> <char-0x0425> " CYRILLIC CAPITAL LETTER HA
<char-0x005d> <char-0x044a> " CYRILLIC SMALL LETTER HARD SIGN
+
+" The SHIFT key is pressed
+<char-0x0051> <char-0x0419> " CYRILLIC CAPITAL LETTER SHORT I
+<char-0x0057> <char-0x0426> " CYRILLIC CAPITAL LETTER TSE
+<char-0x0045> <char-0x0423> " CYRILLIC CAPITAL LETTER U
+<char-0x0052> <char-0x041a> " CYRILLIC CAPITAL LETTER KA
+<char-0x0054> <char-0x0415> " CYRILLIC CAPITAL LETTER IE
+<char-0x0059> <char-0x041d> " CYRILLIC CAPITAL LETTER EN
+<char-0x0055> <char-0x0413> " CYRILLIC CAPITAL LETTER GHE
+<char-0x0049> <char-0x0428> " CYRILLIC CAPITAL LETTER SHA
+<char-0x004f> <char-0x0429> " CYRILLIC CAPITAL LETTER SHCHA
+<char-0x0050> <char-0x0417> " CYRILLIC CAPITAL LETTER ZE
+<char-0x007b> <char-0x0425> " CYRILLIC CAPITAL LETTER HA
<char-0x007d> <char-0x042a> " CYRILLIC CAPITAL LETTER HARD SIGN
-" ALPHABETIC 2nd ROW
+" The ALT key pressed
+<A-char-0x005b> <char-0x005b> " LEFT SQUARE BRACKET
+<A-char-0x005d> <char-0x005d> " RIGHT SQUARE BRACKET
+
+" The SHIFT and ALT keys pressed
+<A-char-0x007b> <char-0x007b> " LEFT CURLY BRACKET
+<A-char-0x007d> <char-0x007d> " RIGHT CURLY BRACKET
+" The ALT and CTRL key pressed
+<A-C-char-0x0065> <char-0x0443><char-0x0301> " CYRILLIC SMALL LETTER U with COMBINING ACUTE ACCENT
+<A-C-char-0x0074> <char-0x0435><char-0x0301> " CYRILLIC SMALL LETTER IE with COMBINING ACUTE ACCENT
+
+" The SHIFT and ALT and CTRL keys pressed
+<S-A-C-char-0x0045> <char-0x0423><char-0x0301> " CYRILLIC CAPITAL LETTER U with COMBINING ACUTE ACCENT
+<S-A-C-char-0x0054> <char-0x0415><char-0x0301> " CYRILLIC CAPITAL LETTER IE with COMBINING ACUTE ACCENT
+
+
+" ALPHABETIC 2st ROW
+
+" None of the modifier keys are pressed
<char-0x0061> <char-0x0444> " CYRILLIC SMALL LETTER EF
-<char-0x0041> <char-0x0424> " CYRILLIC CAPITAL LETTER EF
<char-0x0073> <char-0x044b> " CYRILLIC SMALL LETTER YERU
-<char-0x0053> <char-0x042b> " CYRILLIC CAPITAL LETTER YERU
<char-0x0064> <char-0x0432> " CYRILLIC SMALL LETTER VE
-<char-0x0044> <char-0x0412> " CYRILLIC CAPITAL LETTER VE
<char-0x0066> <char-0x0430> " CYRILLIC SMALL LETTER A
-<char-0x0046> <char-0x0410> " CYRILLIC CAPITAL LETTER A
<char-0x0067> <char-0x043f> " CYRILLIC SMALL LETTER PE
-<char-0x0047> <char-0x041f> " CYRILLIC CAPITAL LETTER PE
<char-0x0068> <char-0x0440> " CYRILLIC SMALL LETTER ER
-<char-0x0048> <char-0x0420> " CYRILLIC CAPITAL LETTER ER
<char-0x006a> <char-0x043e> " CYRILLIC SMALL LETTER O
-<char-0x004a> <char-0x041e> " CYRILLIC CAPITAL LETTER O
<char-0x006b> <char-0x043b> " CYRILLIC SMALL LETTER EL
-<char-0x004b> <char-0x041b> " CYRILLIC CAPITAL LETTER EL
<char-0x006c> <char-0x0434> " CYRILLIC SMALL LETTER DE
-<char-0x004c> <char-0x0414> " CYRILLIC CAPITAL LETTER DE
<char-0x003b> <char-0x0436> " CYRILLIC SMALL LETTER ZHE
-<char-0x003a> <char-0x0416> " CYRILLIC CAPITAL LETTER ZHE
<char-0x0027> <char-0x044d> " CYRILLIC SMALL LETTER E
+
+" The SHIFT key is pressed
+<char-0x0041> <char-0x0424> " CYRILLIC CAPITAL LETTER EF
+<char-0x0053> <char-0x042b> " CYRILLIC CAPITAL LETTER YERU
+<char-0x0044> <char-0x0412> " CYRILLIC CAPITAL LETTER VE
+<char-0x0046> <char-0x0410> " CYRILLIC CAPITAL LETTER A
+<char-0x0047> <char-0x041f> " CYRILLIC CAPITAL LETTER PE
+<char-0x0048> <char-0x0420> " CYRILLIC CAPITAL LETTER ER
+<char-0x004a> <char-0x041e> " CYRILLIC CAPITAL LETTER O
+<char-0x004b> <char-0x041b> " CYRILLIC CAPITAL LETTER EL
+<char-0x004c> <char-0x0414> " CYRILLIC CAPITAL LETTER DE
+<char-0x003a> <char-0x0416> " CYRILLIC CAPITAL LETTER ZHE
<char-0x0022> <char-0x042d> " CYRILLIC CAPITAL LETTER E
-" ALPHABETIC 3rd ROW
+" The ALT key pressed
+<A-char-0x0027> <char-0x0027> " APOSTROPHE
+<A-char-0x0068> <char-0x20bd> " RUBLE SIGN
+
+" The SHIFT and ALT keys pressed
+<A-char-0x0022> <char-0x0022> " QUOTATION MARK
+
+" The ALT and CTRL key pressed
+<A-C-char-0x0073> <char-0x044b><char-0x0301> " CYRILLIC SMALL LETTER YERU with COMBINING ACUTE ACCENT
+<A-C-char-0x0066> <char-0x0430><char-0x0301> " CYRILLIC SMALL LETTER A with COMBINING ACUTE ACCENT
+<A-C-char-0x006a> <char-0x043e><char-0x0301> " CYRILLIC SMALL LETTER O with COMBINING ACUTE ACCENT
+<A-C-char-0x0027> <char-0x044d><char-0x0301> " CYRILLIC SMALL LETTER E with COMBINING ACUTE ACCENT
+
+" The SHIFT and ALT and CTRL keys pressed
+<S-A-C-char-0x0053> <char-0x042b><char-0x0301> " CYRILLIC CAPITAL LETTER YERU with COMBINING ACUTE ACCENT
+<S-A-C-char-0x0046> <char-0x0410><char-0x0301> " CYRILLIC CAPITAL LETTER A with COMBINING ACUTE ACCENT
+<S-A-C-char-0x004a> <char-0x041e><char-0x0301> " CYRILLIC CAPITAL LETTER O with COMBINING ACUTE ACCENT
+<S-A-C-char-0x0022> <char-0x042d><char-0x0301> " CYRILLIC CAPITAL LETTER E with COMBINING ACUTE ACCENT
+
+" ALPHABETIC 3st ROW
+
+" None of the modifier keys are pressed
<char-0x007a> <char-0x044f> " CYRILLIC SMALL LETTER YA
-<char-0x005a> <char-0x042f> " CYRILLIC CAPITAL LETTER YA
<char-0x0078> <char-0x0447> " CYRILLIC SMALL LETTER CHE
-<char-0x0058> <char-0x0427> " CYRILLIC CAPITAL LETTER CHE
<char-0x0063> <char-0x0441> " CYRILLIC SMALL LETTER ES
-<char-0x0043> <char-0x0421> " CYRILLIC CAPITAL LETTER ES
<char-0x0076> <char-0x043c> " CYRILLIC SMALL LETTER EM
-<char-0x0056> <char-0x041c> " CYRILLIC CAPITAL LETTER EM
<char-0x0062> <char-0x0438> " CYRILLIC SMALL LETTER I
-<char-0x0042> <char-0x0418> " CYRILLIC CAPITAL LETTER I
<char-0x006e> <char-0x0442> " CYRILLIC SMALL LETTER TE
-<char-0x004e> <char-0x0422> " CYRILLIC CAPITAL LETTER TE
<char-0x006d> <char-0x044c> " CYRILLIC SMALL LETTER SOFT SIGN
-<char-0x004d> <char-0x042c> " CYRILLIC CAPITAL LETTER SOFT SIGN
<char-0x002c> <char-0x0431> " CYRILLIC SMALL LETTER BE
-<char-0x003c> <char-0x0411> " CYRILLIC CAPITAL LETTER BE
<char-0x002e> <char-0x044e> " CYRILLIC SMALL LETTER YU
-<char-0x003e> <char-0x042e> " CYRILLIC CAPITAL LETTER YU
<char-0x002f> <char-0x0451> " CYRILLIC SMALL LETTER IO
+
+" The SHIFT key is pressed
+<char-0x005a> <char-0x042f> " CYRILLIC CAPITAL LETTER YA
+<char-0x0058> <char-0x0427> " CYRILLIC CAPITAL LETTER CHE
+<char-0x0043> <char-0x0421> " CYRILLIC CAPITAL LETTER ES
+<char-0x0056> <char-0x041c> " CYRILLIC CAPITAL LETTER EM
+<char-0x0042> <char-0x0418> " CYRILLIC CAPITAL LETTER I
+<char-0x004e> <char-0x0422> " CYRILLIC CAPITAL LETTER TE
+<char-0x004d> <char-0x042c> " CYRILLIC CAPITAL LETTER SOFT SIGN
+<char-0x003c> <char-0x0411> " CYRILLIC CAPITAL LETTER BE
+<char-0x003e> <char-0x042e> " CYRILLIC CAPITAL LETTER YU
<char-0x003f> <char-0x0401> " CYRILLIC CAPITAL LETTER IO
-" VK_OEM_5 key (scan code 2b)
+" The ALT key pressed
-" The Shift key is not pressed
-<char-0x005c> <char-0x002f> " SOLIDUS
-" The Shift key is pressed
-<char-0x007c> <char-0x005c> " REVERSE SOLIDUS
-" Alt key pressed
+" The SHIFT and ALT keys pressed
+<A-char-0x003c> <char-0x003c> " LESS-THAN SIGN
+<A-char-0x003e> <char-0x003e> " GREATER-THAN SIGN
-<A-char-0x0060> <char-0x0060> " GRAVE ACCENT
-<A-char-0x0031> <char-0x2116> " NUMERO SIGN
-<A-char-0x0033> <char-0x201c> " LEFT DOUBLE QUOTATION MARK
-<A-char-0x0034> <char-0x201d> " RIGHT DOUBLE QUOTATION MARK
-<A-char-0x005b> <char-0x005b> " LEFT SQUARE BRACKET
-<A-char-0x005d> <char-0x005d> " RIGHT SQUARE BRACKET
-<A-char-0x0027> <char-0x0027> " APOSTROPHE
+" The ALT and CTRL key pressed
+<A-C-char-0x007a> <char-0x044f><char-0x0301> " CYRILLIC SMALL LETTER YA with COMBINING ACUTE ACCENT
+<A-C-char-0x0062> <char-0x0438><char-0x0301> " CYRILLIC SMALL LETTER I with COMBINING ACUTE ACCENT
+<A-C-char-0x002e> <char-0x044e><char-0x0301> " CYRILLIC SMALL LETTER YU with COMBINING ACUTE ACCENT
-" Alt and Shift keys pressed
+" The SHIFT and ALT and CTRL keys pressed
+<S-A-C-char-0x005a> <char-0x042f><char-0x0301> " CYRILLIC CAPITAL LETTER YA with COMBINING ACUTE ACCENT
+<S-A-C-char-0x0042> <char-0x0418><char-0x0301> " CYRILLIC CAPITAL LETTER I with COMBINING ACUTE ACCENT
+<S-A-C-char-0x003e> <char-0x042e><char-0x0301> " CYRILLIC CAPITAL LETTER Y with COMBINING ACUTE ACCENT
-<A-char-0x007e> <char-0x007e> " TILDE
-<A-char-0x0040> <char-0x0040> " COMMERCIAL AT
-<A-char-0x0023> <char-0x0023> " NUMBER SIGN
-<A-char-0x0024> <char-0x0024> " DOLLAR SIGN
-<A-char-0x005e> <char-0x005e> " CIRCUMFLEX ACCENT
-<A-char-0x0026> <char-0x0026> " AMPERSAND
-<A-char-0x002a> <char-0x002a> " ASTERISK
-<A-char-0x005f> <char-0x005f> " LOW LINE
-<A-char-0x007b> <char-0x007b> " LEFT CURLY BRACKET
-<A-char-0x007d> <char-0x007d> " RIGHT CURLY BRACKET
-<A-char-0x0022> <char-0x0022> " QUOTATION MARK
-<A-char-0x003c> <char-0x003c> " LESS-THAN SIGN
-<A-char-0x003e> <char-0x003e> " GREATER-THAN SIGN
+
+" VK_OEM_5 key (scan code 2b)
+
+" None of the modifier keys are pressed
+<char-0x005c> <char-0x002f> " SOLIDUS
+
+" The SHIFT key is pressed
+<char-0x007c> <char-0x00a7> " SECTION SIGN
+
+" The ALT key is pressed
+<A-char-0x005c> <char-0x005c> " REVERSE SOLIDUS
+
+" The SHIFT and ALT keys pressed
<A-char-0x007c> <char-0x007c> " VERTICAL LINE
+
+" SPACE BAR key
+
+" The ALT key pressed
+<A-char-0x0020> <char-0x202f> " NARROW NO-BREAK SPACE
+
+" The SHIFT and CTRL keys pressed
+<S-C-char-0x0020> <char-0x00A0> " NO-BREAK SPACE
+
+" \///\\
+
diff --git a/runtime/lua/_vim9script.lua b/runtime/lua/_vim9script.lua
index 363d061451..ca0e913d51 100644
--- a/runtime/lua/_vim9script.lua
+++ b/runtime/lua/_vim9script.lua
@@ -162,7 +162,7 @@ local vim9 = (function()
end
end
- vim.api.nvim_exec(table.concat(file, '\n'), false)
+ vim.api.nvim_exec2(table.concat(file, '\n'), { output = false })
end,
})
end
@@ -312,7 +312,7 @@ vim9['fn'] = (function()
-- We do have vim9script ;) that's this plugin
['vim9script'] = true,
- -- Include some vim patches that are sometimes required by variuos vim9script plugins
+ -- Include some vim patches that are sometimes required by various vim9script plugins
-- that we implement via vim9jit
[ [[patch-8.2.2261]] ] = true,
[ [[patch-8.2.4257]] ] = true,
@@ -513,7 +513,7 @@ vim9['import'] = (function()
imported.absolute = setmetatable({}, {
__index = function(self, name)
- if vim.loop.fs_stat(name) then
+ if vim.uv.fs_stat(name) then
local result = loadfile(name)()
rawset(self, name, result)
diff --git a/runtime/lua/coxpcall.lua b/runtime/lua/coxpcall.lua
new file mode 100644
index 0000000000..6b179f1ef0
--- /dev/null
+++ b/runtime/lua/coxpcall.lua
@@ -0,0 +1,108 @@
+-------------------------------------------------------------------------------
+-- Coroutine safe xpcall and pcall versions
+--
+-- Encapsulates the protected calls with a coroutine based loop, so errors can
+-- be dealed without the usual Lua 5.x pcall/xpcall issues with coroutines
+-- yielding inside the call to pcall or xpcall.
+--
+-- Authors: Roberto Ierusalimschy and Andre Carregal
+-- Contributors: Thomas Harning Jr., Ignacio Burgueño, Fabio Mascarenhas
+--
+-- Copyright 2005 - Kepler Project
+--
+-- $Id: coxpcall.lua,v 1.13 2008/05/19 19:20:02 mascarenhas Exp $
+-------------------------------------------------------------------------------
+
+-------------------------------------------------------------------------------
+-- Checks if (x)pcall function is coroutine safe
+-------------------------------------------------------------------------------
+local function isCoroutineSafe(func)
+ local co = coroutine.create(function()
+ return func(coroutine.yield, function() end)
+ end)
+
+ coroutine.resume(co)
+ return coroutine.resume(co)
+end
+
+-- No need to do anything if pcall and xpcall are already safe.
+if isCoroutineSafe(pcall) and isCoroutineSafe(xpcall) then
+ copcall = pcall
+ coxpcall = xpcall
+ return { pcall = pcall, xpcall = xpcall, running = coroutine.running }
+end
+
+-------------------------------------------------------------------------------
+-- Implements xpcall with coroutines
+-------------------------------------------------------------------------------
+local performResume, handleReturnValue
+local oldpcall, oldxpcall = pcall, xpcall
+local pack = table.pack or function(...) return {n = select("#", ...), ...} end
+local unpack = table.unpack or unpack
+local running = coroutine.running
+local coromap = setmetatable({}, { __mode = "k" })
+
+function handleReturnValue(err, co, status, ...)
+ if not status then
+ return false, err(debug.traceback(co, (...)), ...)
+ end
+ if coroutine.status(co) == 'suspended' then
+ return performResume(err, co, coroutine.yield(...))
+ else
+ return true, ...
+ end
+end
+
+function performResume(err, co, ...)
+ return handleReturnValue(err, co, coroutine.resume(co, ...))
+end
+
+local function id(trace, ...)
+ return trace
+end
+
+function coxpcall(f, err, ...)
+ local current = running()
+ if not current then
+ if err == id then
+ return oldpcall(f, ...)
+ else
+ if select("#", ...) > 0 then
+ local oldf, params = f, pack(...)
+ f = function() return oldf(unpack(params, 1, params.n)) end
+ end
+ return oldxpcall(f, err)
+ end
+ else
+ local res, co = oldpcall(coroutine.create, f)
+ if not res then
+ local newf = function(...) return f(...) end
+ co = coroutine.create(newf)
+ end
+ coromap[co] = current
+ return performResume(err, co, ...)
+ end
+end
+
+local function corunning(coro)
+ if coro ~= nil then
+ assert(type(coro)=="thread", "Bad argument; expected thread, got: "..type(coro))
+ else
+ coro = running()
+ end
+ while coromap[coro] do
+ coro = coromap[coro]
+ end
+ if coro == "mainthread" then return nil end
+ return coro
+end
+
+-------------------------------------------------------------------------------
+-- Implements pcall with coroutines
+-------------------------------------------------------------------------------
+
+function copcall(f, ...)
+ return coxpcall(f, id, ...)
+end
+
+return { pcall = copcall, xpcall = coxpcall, running = corunning }
diff --git a/runtime/lua/editorconfig.lua b/runtime/lua/editorconfig.lua
index 2079006234..49d63807a6 100644
--- a/runtime/lua/editorconfig.lua
+++ b/runtime/lua/editorconfig.lua
@@ -1,5 +1,6 @@
local M = {}
+--- @type table<string,fun(bufnr: integer, val: string, opts?: table)>
M.properties = {}
--- Modified version of the builtin assert that does not include error position information
@@ -19,14 +20,14 @@ end
---
---@private
local function warn(msg, ...)
- vim.notify(string.format(msg, ...), vim.log.levels.WARN, {
+ vim.notify_once(string.format(msg, ...), vim.log.levels.WARN, {
title = 'editorconfig',
})
end
function M.properties.charset(bufnr, val)
assert(
- vim.tbl_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
+ vim.list_contains({ 'utf-8', 'utf-8-bom', 'latin1', 'utf-16be', 'utf-16le' }, val),
'charset must be one of "utf-8", "utf-8-bom", "latin1", "utf-16be", or "utf-16le"'
)
if val == 'utf-8' or val == 'utf-8-bom' then
@@ -111,7 +112,20 @@ end
function M.properties.insert_final_newline(bufnr, val)
assert(val == 'true' or val == 'false', 'insert_final_newline must be either "true" or "false"')
vim.bo[bufnr].fixendofline = val == 'true'
- vim.bo[bufnr].endofline = val == 'true'
+
+ -- 'endofline' can be read to detect if the file contains a final newline,
+ -- so only change 'endofline' right before writing the file
+ local endofline = val == 'true'
+ if vim.bo[bufnr].endofline ~= endofline then
+ vim.api.nvim_create_autocmd('BufWritePre', {
+ group = 'editorconfig',
+ buffer = bufnr,
+ once = true,
+ callback = function()
+ vim.bo[bufnr].endofline = endofline
+ end,
+ })
+ end
end
--- Modified version of |glob2regpat()| that does not match path separators on *.
@@ -168,12 +182,12 @@ end
---
---@param filepath string File path of the file to apply EditorConfig settings to
---@param dir string Current directory
----@return table Table of options to apply to the given file
+---@return table<string,string|boolean> Table of options to apply to the given file
---
---@private
local function parse(filepath, dir)
- local pat = nil
- local opts = {}
+ local pat --- @type vim.regex?
+ local opts = {} --- @type table<string,string|boolean>
local f = io.open(dir .. '/.editorconfig')
if f then
for line in f:lines() do
@@ -189,6 +203,7 @@ local function parse(filepath, dir)
end
elseif key ~= nil and val ~= nil then
if key == 'root' then
+ assert(val == 'true' or val == 'false', 'root must be either "true" or "false"')
opts.root = val == 'true'
elseif pat and pat:match_str(filepath) then
opts[key] = val
@@ -202,17 +217,21 @@ end
--- Configure the given buffer with options from an .editorconfig file
---
----@param bufnr number Buffer number to configure
+---@param bufnr integer Buffer number to configure
---
---@private
function M.config(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
+ if not vim.api.nvim_buf_is_valid(bufnr) then
+ return
+ end
+
local path = vim.fs.normalize(vim.api.nvim_buf_get_name(bufnr))
if vim.bo[bufnr].buftype ~= '' or not vim.bo[bufnr].modifiable or path == '' then
return
end
- local opts = {}
+ local opts = {} --- @type table<string,string|boolean>
for parent in vim.fs.parents(path) do
for k, v in pairs(parse(path, parent)) do
if opts[k] == nil then
@@ -225,7 +244,7 @@ function M.config(bufnr)
end
end
- local applied = {}
+ local applied = {} --- @type table<string,string|boolean>
for opt, val in pairs(opts) do
if val ~= 'unset' then
local func = M.properties[opt]
diff --git a/runtime/lua/health.lua b/runtime/lua/health.lua
deleted file mode 100644
index 40e2b3c3e7..0000000000
--- a/runtime/lua/health.lua
+++ /dev/null
@@ -1,6 +0,0 @@
-return setmetatable({}, {
- __index = function(_, k)
- vim.deprecate("require('health')", 'vim.health', '0.9', false)
- return vim.health[k]
- end,
-})
diff --git a/runtime/lua/man.lua b/runtime/lua/man.lua
index 0956022ac6..dcdfc2b87f 100644
--- a/runtime/lua/man.lua
+++ b/runtime/lua/man.lua
@@ -2,6 +2,8 @@ local api, fn = vim.api, vim.fn
local FIND_ARG = '-w'
local localfile_arg = true -- Always use -l if possible. #6683
+
+---@type table[]
local buf_hls = {}
local M = {}
@@ -12,83 +14,32 @@ local function man_error(msg)
end
-- Run a system command and timeout after 30 seconds.
-local function system(cmd_, silent, env)
- local stdout_data = {}
- local stderr_data = {}
- local stdout = vim.loop.new_pipe(false)
- local stderr = vim.loop.new_pipe(false)
-
- local done = false
- local exit_code
-
- -- We use the `env` command here rather than the env option to vim.loop.spawn since spawn will
- -- completely overwrite the environment when we just want to modify the existing one.
- --
- -- Overwriting mainly causes problems NixOS which relies heavily on a non-standard environment.
- local cmd
- if env then
- cmd = { 'env' }
- vim.list_extend(cmd, env)
- vim.list_extend(cmd, cmd_)
- else
- cmd = cmd_
- end
-
- local handle
- handle = vim.loop.spawn(cmd[1], {
- args = vim.list_slice(cmd, 2),
- stdio = { nil, stdout, stderr },
- }, function(code)
- exit_code = code
- stdout:close()
- stderr:close()
- handle:close()
- done = true
- end)
-
- if handle then
- stdout:read_start(function(_, data)
- stdout_data[#stdout_data + 1] = data
- end)
- stderr:read_start(function(_, data)
- stderr_data[#stderr_data + 1] = data
- end)
- else
- stdout:close()
- stderr:close()
- if not silent then
- local cmd_str = table.concat(cmd, ' ')
- man_error(string.format('command error: %s', cmd_str))
- end
- end
-
- vim.wait(30000, function()
- return done
- end)
-
- if not done then
- if handle then
- handle:close()
- stdout:close()
- stderr:close()
- end
- local cmd_str = table.concat(cmd, ' ')
- man_error(string.format('command timed out: %s', cmd_str))
- end
-
- if exit_code ~= 0 and not silent then
+---@param cmd string[]
+---@param silent boolean?
+---@param env? table<string,string|number>
+---@return string
+local function system(cmd, silent, env)
+ local r = vim.system(cmd, { env = env, timeout = 10000 }):wait()
+
+ if r.code ~= 0 and not silent then
local cmd_str = table.concat(cmd, ' ')
- man_error(string.format("command error '%s': %s", cmd_str, table.concat(stderr_data)))
+ man_error(string.format("command error '%s': %s", cmd_str, r.stderr))
end
- return table.concat(stdout_data)
+ return assert(r.stdout)
end
+---@param line string
+---@param linenr integer
local function highlight_line(line, linenr)
+ ---@type string[]
local chars = {}
local prev_char = ''
local overstrike, escape = false, false
+
+ ---@type table<integer,{attr:integer,start:integer,final:integer}>
local hls = {} -- Store highlight groups as { attr, start, final }
+
local NONE, BOLD, UNDERLINE, ITALIC = 0, 1, 2, 3
local hl_groups = { [BOLD] = 'manBold', [UNDERLINE] = 'manUnderline', [ITALIC] = 'manItalic' }
local attr = NONE
@@ -194,11 +145,12 @@ local function highlight_line(line, linenr)
-- We only want to match against SGR sequences, which consist of ESC
-- followed by '[', then a series of parameter and intermediate bytes in
-- the range 0x20 - 0x3f, then 'm'. (See ECMA-48, sections 5.4 & 8.3.117)
+ ---@type string?
local sgr = prev_char:match('^%[([\032-\063]*)m$')
-- Ignore escape sequences with : characters, as specified by ITU's T.416
-- Open Document Architecture and interchange format.
if sgr and not string.find(sgr, ':') then
- local match
+ local match ---@type string?
while sgr and #sgr > 0 do
-- Match against SGR parameters, which may be separated by ';'
match, sgr = sgr:match('^(%d*);?(.*)')
@@ -261,11 +213,16 @@ end
-- intended for PostgreSQL, which has man pages like 'CREATE_TABLE(7)';
-- while editing SQL source code, it's nice to visually select 'CREATE TABLE'
-- and hit 'K', which requires this transformation
+---@param str string
+---@return string
local function spaces_to_underscores(str)
local res = str:gsub('%s', '_')
return res
end
+---@param sect string|nil
+---@param name string|nil
+---@param silent boolean
local function get_path(sect, name, silent)
name = name or ''
sect = sect or ''
@@ -287,7 +244,7 @@ local function get_path(sect, name, silent)
--
-- Finally, we can avoid relying on -S or -s here since they are very
-- inconsistently supported. Instead, call -w with a section and a name.
- local cmd
+ local cmd ---@type string[]
if sect == '' then
cmd = { 'man', FIND_ARG, name }
else
@@ -295,7 +252,7 @@ local function get_path(sect, name, silent)
end
local lines = system(cmd, silent)
- local results = vim.split(lines or {}, '\n', { trimempty = true })
+ local results = vim.split(lines, '\n', { trimempty = true })
if #results == 0 then
return
@@ -310,12 +267,15 @@ local function get_path(sect, name, silent)
end
-- find any that match the specified name
+ ---@param v string
local namematches = vim.tbl_filter(function(v)
- return fn.fnamemodify(v, ':t'):match(name)
+ local tail = fn.fnamemodify(v, ':t')
+ return string.find(tail, name, 1, true)
end, results) or {}
local sectmatches = {}
if #namematches > 0 and sect ~= '' then
+ ---@param v string
sectmatches = vim.tbl_filter(function(v)
return fn.fnamemodify(v, ':e') == sect
end, namematches)
@@ -324,9 +284,12 @@ local function get_path(sect, name, silent)
return fn.substitute(sectmatches[1] or namematches[1] or results[1], [[\n\+$]], '', '')
end
+---@param text string
+---@param pat_or_re string
local function matchstr(text, pat_or_re)
local re = type(pat_or_re) == 'string' and vim.regex(pat_or_re) or pat_or_re
+ ---@type integer, integer
local s, e = re:match_str(text)
if s == nil then
@@ -338,6 +301,8 @@ end
-- attempt to extract the name and sect out of 'name(sect)'
-- otherwise just return the largest string of valid characters in ref
+---@param ref string
+---@return string, string
local function extract_sect_and_name_ref(ref)
ref = ref or ''
if ref:sub(1, 1) == '-' then -- try ':Man -pandoc' with this disabled.
@@ -349,18 +314,18 @@ local function extract_sect_and_name_ref(ref)
if not name then
man_error('manpage reference cannot contain only parentheses: ' .. ref)
end
- return '', spaces_to_underscores(name)
+ return '', name
end
local parts = vim.split(ref1, '(', { plain = true })
-- see ':Man 3X curses' on why tolower.
-- TODO(nhooyr) Not sure if this is portable across OSs
-- but I have not seen a single uppercase section.
local sect = vim.split(parts[2] or '', ')', { plain = true })[1]:lower()
- local name = spaces_to_underscores(parts[1])
+ local name = parts[1]
return sect, name
end
--- verify_exists attempts to find the path to a manpage
+-- find_path attempts to find the path to a manpage
-- based on the passed section and name.
--
-- 1. If manpage could not be found with the given sect and name,
@@ -368,7 +333,10 @@ end
-- 2. If it still could not be found, then we try again without a section.
-- 3. If still not found but $MANSECT is set, then we try again with $MANSECT
-- unset.
-local function verify_exists(sect, name, silent)
+-- 4. If a path still wasn't found, return nil.
+---@param sect string?
+---@param name string
+function M.find_path(sect, name)
if sect and sect ~= '' then
local ret = get_path(sect, name, true)
if ret then
@@ -404,10 +372,8 @@ local function verify_exists(sect, name, silent)
end
end
- if not silent then
- -- finally, if that didn't work, there is no hope
- man_error('no manual entry for ' .. name)
- end
+ -- finally, if that didn't work, there is no hope
+ return nil
end
local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]])
@@ -416,6 +382,8 @@ local EXT_RE = vim.regex([[\.\%([glx]z\|bz2\|lzma\|Z\)$]])
-- more specific than what we provided to `man` (try `:Man 3 App::CLI`).
-- Also on linux, name seems to be case-insensitive. So for `:Man PRIntf`, we
-- still want the name of the buffer to be 'printf'.
+---@param path string
+---@return string, string
local function extract_sect_and_name_path(path)
local tail = fn.fnamemodify(path, ':t')
if EXT_RE:match_str(path) then -- valid extensions
@@ -425,6 +393,7 @@ local function extract_sect_and_name_path(path)
return sect, name
end
+---@return boolean
local function find_man()
if vim.bo.filetype == 'man' then
return true
@@ -442,10 +411,11 @@ local function find_man()
return false
end
+---@param pager boolean
local function set_options(pager)
vim.bo.swapfile = false
vim.bo.buftype = 'nofile'
- vim.bo.bufhidden = 'hide'
+ vim.bo.bufhidden = 'unload'
vim.bo.modified = false
vim.bo.readonly = true
vim.bo.modifiable = false
@@ -453,17 +423,20 @@ local function set_options(pager)
vim.bo.filetype = 'man'
end
+---@param path string
+---@param silent boolean?
+---@return string
local function get_page(path, silent)
-- Disable hard-wrap by using a big $MANWIDTH (max 1000 on some systems #9065).
-- Soft-wrap: ftplugin/man.lua sets wrap/breakindent/….
-- Hard-wrap: driven by `man`.
- local manwidth
+ local manwidth ---@type integer|string
if (vim.g.man_hardwrap or 1) ~= 1 then
manwidth = 999
elseif vim.env.MANWIDTH then
manwidth = vim.env.MANWIDTH
else
- manwidth = api.nvim_win_get_width(0)
+ manwidth = api.nvim_win_get_width(0) - vim.o.wrapmargin
end
local cmd = localfile_arg and { 'man', '-l', path } or { 'man', path }
@@ -472,12 +445,20 @@ local function get_page(path, silent)
-- http://comments.gmane.org/gmane.editors.vim.devel/29085
-- Set MAN_KEEP_FORMATTING so Debian man doesn't discard backspaces.
return system(cmd, silent, {
- 'MANPAGER=cat',
- 'MANWIDTH=' .. manwidth,
- 'MAN_KEEP_FORMATTING=1',
+ MANPAGER = 'cat',
+ MANWIDTH = manwidth,
+ MAN_KEEP_FORMATTING = 1,
})
end
+---@param lnum integer
+---@return string
+local function getline(lnum)
+ ---@diagnostic disable-next-line
+ return fn.getline(lnum)
+end
+
+---@param page string
local function put_page(page)
vim.bo.modifiable = true
vim.bo.readonly = false
@@ -485,7 +466,7 @@ local function put_page(page)
api.nvim_buf_set_lines(0, 0, -1, false, vim.split(page, '\n'))
- while fn.getline(1):match('^%s*$') do
+ while getline(1):match('^%s*$') do
api.nvim_buf_set_lines(0, 0, 1, false, {})
end
-- XXX: nroff justifies text by filling it with whitespace. That interacts
@@ -512,13 +493,21 @@ local function format_candidate(path, psect)
return ''
end
+---@generic T
+---@param list T[]
+---@param elem T
+---@return T[]
local function move_elem_to_head(list, elem)
+ ---@diagnostic disable-next-line:no-unknown
local list1 = vim.tbl_filter(function(v)
return v ~= elem
end, list)
return { elem, unpack(list1) }
end
+---@param sect string
+---@param name string
+---@return string[]
local function get_paths(sect, name)
-- Try several sources for getting the list man directories:
-- 1. `man -w` (works on most systems)
@@ -533,10 +522,11 @@ local function get_paths(sect, name)
end
local mandirs = table.concat(vim.split(mandirs_raw, '[:\n]', { trimempty = true }), ',')
- local paths = fn.globpath(mandirs, 'man?/' .. name .. '*.' .. sect .. '*', false, true)
+ ---@type string[]
+ local paths = fn.globpath(mandirs, 'man[^\\/]*/' .. name .. '*.' .. sect .. '*', false, true)
- -- Prioritize the result from verify_exists as it obeys b:man_default_sects.
- local first = verify_exists(sect, name, true)
+ -- Prioritize the result from find_path as it obeys b:man_default_sects.
+ local first = M.find_path(sect, name)
if first then
paths = move_elem_to_head(paths, first)
end
@@ -544,6 +534,10 @@ local function get_paths(sect, name)
return paths
end
+---@param sect string
+---@param psect string
+---@param name string
+---@return string[]
local function complete(sect, psect, name)
local pages = get_paths(sect, name)
-- We remove duplicates in case the same manpage in different languages was found.
@@ -553,6 +547,8 @@ local function complete(sect, psect, name)
end
-- see extract_sect_and_name_ref on why tolower(sect)
+---@param arg_lead string
+---@param cmd_line string
function M.man_complete(arg_lead, cmd_line, _)
local args = vim.split(cmd_line, '%s+', { trimempty = true })
local cmd_offset = fn.index(args, 'Man')
@@ -589,6 +585,7 @@ function M.man_complete(arg_lead, cmd_line, _)
end
if #args == 2 then
+ ---@type string, string
local name, sect
if arg_lead == '' then
-- cursor (|) is at ':Man 1 |'
@@ -618,10 +615,13 @@ function M.man_complete(arg_lead, cmd_line, _)
return complete(sect, sect, name)
end
+---@param pattern string
+---@return {name:string,filename:string,cmd:string}[]
function M.goto_tag(pattern, _, _)
local sect, name = extract_sect_and_name_ref(pattern)
local paths = get_paths(sect, name)
+ ---@type {name:string,title:string}[]
local structured = {}
for _, path in ipairs(paths) do
@@ -634,6 +634,7 @@ function M.goto_tag(pattern, _, _)
end
end
+ ---@param entry {name:string,title:string}
return vim.tbl_map(function(entry)
return {
name = entry.name,
@@ -645,7 +646,7 @@ end
-- Called when Nvim is invoked as $MANPAGER.
function M.init_pager()
- if fn.getline(1):match('^%s*$') then
+ if getline(1):match('^%s*$') then
api.nvim_buf_set_lines(0, 0, 1, false, {})
else
vim.cmd('keepjumps 1')
@@ -653,7 +654,7 @@ function M.init_pager()
highlight_man_page()
-- Guess the ref from the heading (which is usually uppercase, so we cannot
-- know the correct casing, cf. `man glDrawArraysInstanced`).
- local ref = fn.substitute(matchstr(fn.getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g')
+ local ref = fn.substitute(matchstr(getline(1), [[^[^)]\+)]]) or '', ' ', '_', 'g')
local ok, res = pcall(extract_sect_and_name_ref, ref)
vim.b.man_sect = ok and res or ''
@@ -664,12 +665,10 @@ function M.init_pager()
set_options(true)
end
+---@param count integer
+---@param args string[]
function M.open_page(count, smods, args)
- if #args > 2 then
- man_error('too many arguments')
- end
-
- local ref
+ local ref ---@type string
if #args == 0 then
ref = vim.bo.filetype == 'man' and fn.expand('<cWORD>') or fn.expand('<cword>')
if ref == '' then
@@ -680,9 +679,19 @@ function M.open_page(count, smods, args)
else
-- Combine the name and sect into a manpage reference so that all
-- verification/extraction can be kept in a single function.
- -- If args[2] is a reference as well, that is fine because it is the only
- -- reference that will match.
- ref = ('%s(%s)'):format(args[2], args[1])
+ if args[1]:match('^%d$') or args[1]:match('^%d%a') or args[1]:match('^%a$') then
+ -- NB: Valid sections are not only digits, but also:
+ -- - <digit><word> (see POSIX mans),
+ -- - and even <letter> and <word> (see, for example, by tcl/tk)
+ -- NB2: don't optimize to :match("^%d"), as it will match manpages like
+ -- 441toppm and others whose name starts with digit
+ local sect = args[1]
+ table.remove(args, 1)
+ local name = table.concat(args, ' ')
+ ref = ('%s(%s)'):format(name, sect)
+ else
+ ref = table.concat(args, ' ')
+ end
end
local sect, name = extract_sect_and_name_ref(ref)
@@ -690,9 +699,16 @@ function M.open_page(count, smods, args)
sect = tostring(count)
end
- local path = verify_exists(sect, name)
- sect, name = extract_sect_and_name_path(path)
+ -- Try both spaces and underscores, use the first that exists.
+ local path = M.find_path(sect, name)
+ if path == nil then
+ path = M.find_path(sect, spaces_to_underscores(name))
+ if path == nil then
+ man_error('no manual entry for ' .. name)
+ end
+ end
+ sect, name = extract_sect_and_name_path(path)
local buf = fn.bufnr()
local save_tfu = vim.bo[buf].tagfunc
vim.bo[buf].tagfunc = "v:lua.require'man'.goto_tag"
@@ -723,7 +739,10 @@ end
-- Called when a man:// buffer is opened.
function M.read_page(ref)
local sect, name = extract_sect_and_name_ref(ref)
- local path = verify_exists(sect, name)
+ local path = M.find_path(sect, name)
+ if path == nil then
+ man_error('no manual entry for ' .. name)
+ end
sect = extract_sect_and_name_path(path)
local page = get_page(path)
vim.b.man_sect = sect
@@ -731,24 +750,27 @@ function M.read_page(ref)
end
function M.show_toc()
- local bufname = fn.bufname('%')
+ local bufnr = api.nvim_get_current_buf()
+ local bufname = api.nvim_buf_get_name(bufnr)
local info = fn.getloclist(0, { winid = 1 })
if info ~= '' and vim.w[info.winid].qf_toc == bufname then
vim.cmd.lopen()
return
end
+ ---@type {bufnr:integer, lnum:integer, text:string}[]
local toc = {}
+
local lnum = 2
local last_line = fn.line('$') - 1
local section_title_re = vim.regex([[^\%( \{3\}\)\=\S.*$]])
local flag_title_re = vim.regex([[^\s\+\%(+\|-\)\S\+]])
while lnum and lnum < last_line do
- local text = fn.getline(lnum)
+ local text = getline(lnum)
if section_title_re:match_str(text) then
-- if text is a section title
toc[#toc + 1] = {
- bufnr = fn.bufnr('%'),
+ bufnr = bufnr,
lnum = lnum,
text = text,
}
@@ -756,7 +778,7 @@ function M.show_toc()
-- if text is a flag title. we strip whitespaces and prepend two
-- spaces to have a consistent format in the loclist.
toc[#toc + 1] = {
- bufnr = fn.bufnr('%'),
+ bufnr = bufnr,
lnum = lnum,
text = ' ' .. fn.substitute(text, [[^\s*\(.\{-}\)\s*$]], [[\1]], ''),
}
@@ -772,7 +794,7 @@ end
local function init()
local path = get_path('', 'man', true)
- local page
+ local page ---@type string?
if path ~= nil then
-- Check for -l support.
page = get_page(path, true)
diff --git a/runtime/lua/nvim/health.lua b/runtime/lua/nvim/health.lua
index f11899434e..6b6370fa19 100644
--- a/runtime/lua/nvim/health.lua
+++ b/runtime/lua/nvim/health.lua
@@ -20,7 +20,7 @@ end
local suggest_faq = 'https://github.com/neovim/neovim/wiki/Building-Neovim#optimized-builds'
local function check_runtime()
- health.report_start('Runtime')
+ health.start('Runtime')
-- Files from an old installation.
local bad_files = {
['plugin/man.vim'] = false,
@@ -30,17 +30,17 @@ local function check_runtime()
local bad_files_msg = ''
for k, _ in pairs(bad_files) do
local path = ('%s/%s'):format(vim.env.VIMRUNTIME, k)
- if vim.loop.fs_stat(path) then
+ if vim.uv.fs_stat(path) then
bad_files[k] = true
bad_files_msg = ('%s%s\n'):format(bad_files_msg, path)
end
end
local ok = (bad_files_msg == '')
- local info = ok and health.report_ok or health.report_info
+ local info = ok and health.ok or health.info
info(string.format('$VIMRUNTIME: %s', vim.env.VIMRUNTIME))
if not ok then
- health.report_error(
+ health.error(
string.format(
'$VIMRUNTIME has files from an old installation (this can cause weird behavior):\n%s',
bad_files_msg
@@ -51,30 +51,34 @@ local function check_runtime()
end
local function check_config()
- health.report_start('Configuration')
+ health.start('Configuration')
local ok = true
- local vimrc = (
- empty(vim.env.MYVIMRC) and vim.fn.stdpath('config') .. '/init.vim' or vim.env.MYVIMRC
- )
- if not filereadable(vimrc) then
+ local init_lua = vim.fn.stdpath('config') .. '/init.lua'
+ local init_vim = vim.fn.stdpath('config') .. '/init.vim'
+ local vimrc = empty(vim.env.MYVIMRC) and init_lua or vim.env.MYVIMRC
+
+ if not filereadable(vimrc) and not filereadable(init_vim) then
ok = false
local has_vim = filereadable(vim.fn.expand('~/.vimrc'))
- health.report_warn(
- (-1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable') .. ' user config file: ' .. vimrc,
- { has_vim and ':help nvim-from-vim' or ':help init.vim' }
+ health.warn(
+ ('%s user config file: %s'):format(
+ -1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable',
+ vimrc
+ ),
+ { has_vim and ':help nvim-from-vim' or ':help config' }
)
end
-- If $VIM is empty we don't care. Else make sure it is valid.
if not empty(vim.env.VIM) and not filereadable(vim.env.VIM .. '/runtime/doc/nvim.txt') then
ok = false
- health.report_error('$VIM is invalid: ' .. vim.env.VIM)
+ health.error('$VIM is invalid: ' .. vim.env.VIM)
end
if vim.env.NVIM_TUI_ENABLE_CURSOR_SHAPE then
ok = false
- health.report_warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', {
+ health.warn('$NVIM_TUI_ENABLE_CURSOR_SHAPE is ignored in Nvim 0.2+', {
"Use the 'guicursor' option to configure cursor shape. :help 'guicursor'",
'https://github.com/neovim/neovim/wiki/Following-HEAD#20170402',
})
@@ -82,7 +86,7 @@ local function check_config()
if vim.v.ctype == 'C' then
ok = false
- health.report_error(
+ health.error(
'Locale does not support UTF-8. Unicode characters may not display correctly.'
.. ('\n$LANG=%s $LC_ALL=%s $LC_CTYPE=%s'):format(
vim.env.LANG,
@@ -99,7 +103,7 @@ local function check_config()
if vim.o.paste == 1 then
ok = false
- health.report_error(
+ health.error(
"'paste' is enabled. This option is only for pasting text.\nIt should not be set in your config.",
{
'Remove `set paste` from your init.vim, if applicable.',
@@ -132,7 +136,7 @@ local function check_config()
or (not empty(shadafile) and (not filereadable(shadafile) or not filewritable(shadafile)))
then
ok = false
- health.report_error(
+ health.error(
'shada file is not '
.. ((not writeable or filereadable(shadafile)) and 'writeable' or 'readable')
.. ':\n'
@@ -141,22 +145,22 @@ local function check_config()
end
if ok then
- health.report_ok('no issues found')
+ health.ok('no issues found')
end
end
local function check_performance()
- health.report_start('Performance')
+ health.start('Performance')
-- Check buildtype
local buildtype = vim.fn.matchstr(vim.fn.execute('version'), [[\v\cbuild type:?\s*[^\n\r\t ]+]])
if empty(buildtype) then
- health.report_error('failed to get build type from :version')
+ health.error('failed to get build type from :version')
elseif vim.regex([[\v(MinSizeRel|Release|RelWithDebInfo)]]):match_str(buildtype) then
- health.report_ok(buildtype)
+ health.ok(buildtype)
else
- health.report_info(buildtype)
- health.report_warn('Non-optimized debug build. Nvim will be slower.', {
+ health.info(buildtype)
+ health.warn('Non-optimized debug build. Nvim will be slower.', {
'Install a different Nvim package, or rebuild with `CMAKE_BUILD_TYPE=RelWithDebInfo`.',
suggest_faq,
})
@@ -168,7 +172,7 @@ local function check_performance()
vim.fn.system('echo')
local elapsed_time = vim.fn.reltimefloat(vim.fn.reltime(start_time))
if elapsed_time > slow_cmd_time then
- health.report_warn(
+ health.warn(
'Slow shell invocation (took ' .. vim.fn.printf('%.2f', elapsed_time) .. ' seconds).'
)
end
@@ -176,14 +180,14 @@ end
-- Load the remote plugin manifest file and check for unregistered plugins
local function check_rplugin_manifest()
- health.report_start('Remote Plugins')
+ health.start('Remote Plugins')
local existing_rplugins = {}
for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python')) do
existing_rplugins[item.path] = 'python'
end
- for item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do
+ for _, item in ipairs(vim.fn['remote#host#PluginsForHost']('python3')) do
existing_rplugins[item.path] = 'python3'
end
@@ -200,7 +204,7 @@ local function check_rplugin_manifest()
local scripts = vim.fn.glob(python_dir .. '/*.py', true, true)
vim.list_extend(scripts, vim.fn.glob(python_dir .. '/*/__init__.py', true, true))
- for script in ipairs(scripts) do
+ for _, script in ipairs(scripts) do
local contents = vim.fn.join(vim.fn.readfile(script))
if vim.regex([[\<\%(from\|import\)\s\+neovim\>]]):match_str(contents) then
if vim.regex([[[\/]__init__\.py$]]):match_str(script) then
@@ -218,7 +222,7 @@ local function check_rplugin_manifest()
require_update = true
end
- health.report_warn(msg)
+ health.warn(msg)
end
break
@@ -231,9 +235,9 @@ local function check_rplugin_manifest()
end
if require_update then
- health.report_warn('Out of date', { 'Run `:UpdateRemotePlugins`' })
+ health.warn('Out of date', { 'Run `:UpdateRemotePlugins`' })
else
- health.report_ok('Up to date')
+ health.ok('Up to date')
end
end
@@ -247,21 +251,21 @@ local function check_tmux()
local out = vim.fn.system(vim.fn.split(cmd))
local val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
if shell_error() then
- health.report_error('command failed: ' .. cmd .. '\n' .. out)
+ health.error('command failed: ' .. cmd .. '\n' .. out)
return 'error'
elseif empty(val) then
cmd = 'tmux show-option -qvgs ' .. option -- try session scope
out = vim.fn.system(vim.fn.split(cmd))
val = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
if shell_error() then
- health.report_error('command failed: ' .. cmd .. '\n' .. out)
+ health.error('command failed: ' .. cmd .. '\n' .. out)
return 'error'
end
end
return val
end
- health.report_start('tmux')
+ health.start('tmux')
-- check escape-time
local suggestions =
@@ -269,14 +273,11 @@ local function check_tmux()
local tmux_esc_time = get_tmux_option('escape-time')
if tmux_esc_time ~= 'error' then
if empty(tmux_esc_time) then
- health.report_error('`escape-time` is not set', suggestions)
+ health.error('`escape-time` is not set', suggestions)
elseif tonumber(tmux_esc_time) > 300 then
- health.report_error(
- '`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms',
- suggestions
- )
+ health.error('`escape-time` (' .. tmux_esc_time .. ') is higher than 300ms', suggestions)
else
- health.report_ok('escape-time: ' .. tmux_esc_time)
+ health.ok('escape-time: ' .. tmux_esc_time)
end
end
@@ -284,17 +285,17 @@ local function check_tmux()
local tmux_focus_events = get_tmux_option('focus-events')
if tmux_focus_events ~= 'error' then
if empty(tmux_focus_events) or tmux_focus_events ~= 'on' then
- health.report_warn(
+ health.warn(
"`focus-events` is not enabled. |'autoread'| may not work.",
{ '(tmux 1.9+ only) Set `focus-events` in ~/.tmux.conf:\nset-option -g focus-events on' }
)
else
- health.report_ok('focus-events: ' .. tmux_focus_events)
+ health.ok('focus-events: ' .. tmux_focus_events)
end
end
-- check default-terminal and $TERM
- health.report_info('$TERM: ' .. vim.env.TERM)
+ health.info('$TERM: ' .. vim.env.TERM)
local cmd = 'tmux show-option -qvg default-terminal'
local out = vim.fn.system(vim.fn.split(cmd))
local tmux_default_term = vim.fn.substitute(out, [[\v(\s|\r|\n)]], '', 'g')
@@ -305,15 +306,15 @@ local function check_tmux()
end
if shell_error() then
- health.report_error('command failed: ' .. cmd .. '\n' .. out)
+ health.error('command failed: ' .. cmd .. '\n' .. out)
elseif tmux_default_term ~= vim.env.TERM then
- health.report_info('default-terminal: ' .. tmux_default_term)
- health.report_error(
+ health.info('default-terminal: ' .. tmux_default_term)
+ health.error(
'$TERM differs from the tmux `default-terminal` setting. Colors might look wrong.',
{ '$TERM may have been set by some rc (.bashrc, .zshrc, ...).' }
)
elseif not vim.regex([[\v(tmux-256color|screen-256color)]]):match_str(vim.env.TERM) then
- health.report_error(
+ health.error(
'$TERM should be "screen-256color" or "tmux-256color" in tmux. Colors might look wrong.',
{
'Set default-terminal in ~/.tmux.conf:\nset-option -g default-terminal "screen-256color"',
@@ -325,7 +326,7 @@ local function check_tmux()
-- check for RGB capabilities
local info = vim.fn.system({ 'tmux', 'display-message', '-p', '#{client_termfeatures}' })
info = vim.split(vim.trim(info), ',', { trimempty = true })
- if not vim.tbl_contains(info, 'RGB') then
+ if not vim.list_contains(info, 'RGB') then
local has_rgb = false
if #info == 0 then
-- client_termfeatures may not be supported; fallback to checking show-messages
@@ -333,7 +334,7 @@ local function check_tmux()
has_rgb = info:find(' Tc: (flag) true', 1, true) or info:find(' RGB: (flag) true', 1, true)
end
if not has_rgb then
- health.report_warn(
+ health.warn(
"Neither Tc nor RGB capability set. True colors are disabled. |'termguicolors'| won't work properly.",
{
"Put this in your ~/.tmux.conf and replace XXX by your $TERM outside of tmux:\nset-option -sa terminal-features ',XXX:RGB'",
@@ -349,7 +350,7 @@ local function check_terminal()
return
end
- health.report_start('terminal')
+ health.start('terminal')
local cmd = 'infocmp -L'
local out = vim.fn.system(vim.fn.split(cmd))
local kbs_entry = vim.fn.matchstr(out, 'key_backspace=[^,[:space:]]*')
@@ -367,16 +368,16 @@ local function check_terminal()
)
)
then
- health.report_error('command failed: ' .. cmd .. '\n' .. out)
+ health.error('command failed: ' .. cmd .. '\n' .. out)
else
- health.report_info(
+ health.info(
vim.fn.printf(
'key_backspace (kbs) terminfo entry: `%s`',
(empty(kbs_entry) and '? (not found)' or kbs_entry)
)
)
- health.report_info(
+ health.info(
vim.fn.printf(
'key_dc (kdch1) terminfo entry: `%s`',
(empty(kbs_entry) and '? (not found)' or kdch1_entry)
@@ -384,9 +385,15 @@ local function check_terminal()
)
end
- for env_var in ipairs({ 'XTERM_VERSION', 'VTE_VERSION', 'TERM_PROGRAM', 'COLORTERM', 'SSH_TTY' }) do
+ for _, env_var in ipairs({
+ 'XTERM_VERSION',
+ 'VTE_VERSION',
+ 'TERM_PROGRAM',
+ 'COLORTERM',
+ 'SSH_TTY',
+ }) do
if vim.env[env_var] then
- health.report_info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var]))
+ health.info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var]))
end
end
end
diff --git a/runtime/lua/provider/health.lua b/runtime/lua/provider/health.lua
new file mode 100644
index 0000000000..d33f74f6e9
--- /dev/null
+++ b/runtime/lua/provider/health.lua
@@ -0,0 +1,916 @@
+local M = {}
+
+local start = vim.health.start
+local ok = vim.health.ok
+local info = vim.health.info
+local warn = vim.health.warn
+local error = vim.health.error
+local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
+
+local shell_error_code = 0
+local function shell_error()
+ return shell_error_code ~= 0
+end
+
+-- Returns true if `cmd` exits with success, else false.
+local function cmd_ok(cmd)
+ vim.fn.system(cmd)
+ return vim.v.shell_error == 0
+end
+
+local function executable(exe)
+ return vim.fn.executable(exe) == 1
+end
+
+local function is_blank(s)
+ return s:find('^%s*$') ~= nil
+end
+
+local function isdir(path)
+ if not path then
+ return false
+ end
+ local stat = vim.uv.fs_stat(path)
+ if not stat then
+ return false
+ end
+ return stat.type == 'directory'
+end
+
+local function isfile(path)
+ if not path then
+ return false
+ end
+ local stat = vim.uv.fs_stat(path)
+ if not stat then
+ return false
+ end
+ return stat.type == 'file'
+end
+
+-- Handler for s:system() function.
+local function system_handler(self, _, data, event)
+ if event == 'stderr' then
+ if self.add_stderr_to_output then
+ self.output = self.output .. vim.fn.join(data, '')
+ else
+ self.stderr = self.stderr .. vim.fn.join(data, '')
+ end
+ elseif event == 'stdout' then
+ self.output = self.output .. vim.fn.join(data, '')
+ elseif event == 'exit' then
+ shell_error_code = data
+ end
+end
+
+-- Attempts to construct a shell command from an args list.
+-- Only for display, to help users debug a failed command.
+local function shellify(cmd)
+ if type(cmd) ~= 'table' then
+ return cmd
+ end
+ return vim.fn.join(
+ vim.fn.map(vim.fn.copy(cmd), [[v:val =~# ''\m[^\-.a-zA-Z_/]'' ? shellescape(v:val) : v:val]]),
+ ' '
+ )
+end
+
+-- Run a system command and timeout after 30 seconds.
+local function system(cmd, ...)
+ local args = { ... }
+ local args_count = vim.tbl_count(args)
+
+ local stdin = (args_count > 0 and args[1] or '')
+ local stderr = (args_count > 1 and args[2] or false)
+ local ignore_error = (args_count > 2 and args[3] or false)
+
+ local opts = {
+ add_stderr_to_output = stderr,
+ output = '',
+ stderr = '',
+ on_stdout = system_handler,
+ on_stderr = system_handler,
+ on_exit = system_handler,
+ }
+ local jobid = vim.fn.jobstart(cmd, opts)
+
+ if jobid < 1 then
+ local message = 'Command error (job='
+ .. jobid
+ .. '): `'
+ .. shellify(cmd)
+ .. '` (in '
+ .. vim.fn.string(vim.fn.getcwd())
+ .. ')'
+
+ error(message)
+ shell_error_code = 1
+ return opts.output
+ end
+
+ if not is_blank(stdin) then
+ vim.api.nvim_chan_send(jobid, stdin)
+ end
+
+ local res = vim.fn.jobwait({ jobid }, 30000)
+ if res[1] == -1 then
+ error('Command timed out: ' .. shellify(cmd))
+ vim.fn.jobstop(jobid)
+ elseif shell_error() and not ignore_error then
+ local emsg = 'Command error (job='
+ .. jobid
+ .. ', exit code '
+ .. shell_error_code
+ .. '): `'
+ .. shellify(cmd)
+ .. '` (in '
+ .. vim.fn.string(vim.fn.getcwd())
+ .. ')'
+ if not is_blank(opts.output) then
+ emsg = emsg .. '\noutput: ' .. opts.output
+ end
+ if not is_blank(opts.stderr) then
+ emsg = emsg .. '\nstderr: ' .. opts.stderr
+ end
+ error(emsg)
+ end
+
+ -- return opts.output
+ local _ = ...
+ return vim.trim(vim.fn.system(cmd))
+end
+
+local function clipboard()
+ start('Clipboard (optional)')
+
+ if
+ os.getenv('TMUX')
+ and executable('tmux')
+ and executable('pbpaste')
+ and not cmd_ok('pbpaste')
+ then
+ local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+')
+ local advice = {
+ 'Install tmux 2.6+. https://superuser.com/q/231130',
+ 'or use tmux with reattach-to-user-namespace. https://superuser.com/a/413233',
+ }
+ error('pbcopy does not work with tmux version: ' .. tmux_version, advice)
+ end
+
+ local clipboard_tool = vim.fn['provider#clipboard#Executable']()
+ if vim.g.clipboard and is_blank(clipboard_tool) then
+ local error_message = vim.fn['provider#clipboard#Error']()
+ error(
+ error_message,
+ "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all."
+ )
+ elseif is_blank(clipboard_tool) then
+ warn(
+ 'No clipboard tool found. Clipboard registers (`"+` and `"*`) will not work.',
+ ':help clipboard'
+ )
+ else
+ ok('Clipboard tool found: ' .. clipboard_tool)
+ end
+end
+
+local function disabled_via_loaded_var(provider)
+ local loaded_var = 'loaded_' .. provider .. '_provider'
+ local v = vim.g[loaded_var]
+ if v == 0 then
+ info('Disabled (' .. loaded_var .. '=' .. v .. ').')
+ return true
+ end
+ return false
+end
+
+-- Check if pyenv is available and a valid pyenv root can be found, then return
+-- their respective paths. If either of those is invalid, return two empty
+-- strings, effectively ignoring pyenv.
+local function check_for_pyenv()
+ local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv'))
+
+ if is_blank(pyenv_path) then
+ return { '', '' }
+ end
+
+ info('pyenv: Path: ' .. pyenv_path)
+
+ local pyenv_root = os.getenv('PYENV_ROOT') and vim.fn.resolve(os.getenv('PYENV_ROOT')) or ''
+
+ if is_blank(pyenv_root) then
+ pyenv_root = vim.trim(system({ pyenv_path, 'root' }))
+ info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.')
+ end
+
+ if not isdir(pyenv_root) then
+ local message = 'pyenv: Root does not exist: '
+ .. pyenv_root
+ .. '. Ignoring pyenv for all following checks.'
+ warn(message)
+ return { '', '' }
+ end
+
+ info('pyenv: Root: ' .. pyenv_root)
+
+ return { pyenv_path, pyenv_root }
+end
+
+-- Check the Python interpreter's usability.
+local function check_bin(bin)
+ if not isfile(bin) and (not iswin or not isfile(bin .. '.exe')) then
+ error('"' .. bin .. '" was not found.')
+ return false
+ elseif not executable(bin) then
+ error('"' .. bin .. '" is not executable.')
+ return false
+ end
+ return true
+end
+
+-- Fetch the contents of a URL.
+local function download(url)
+ local has_curl = executable('curl')
+ if has_curl and vim.fn.system({ 'curl', '-V' }):find('Protocols:.*https') then
+ local rv = system({ 'curl', '-sL', url }, '', 1, 1)
+ if shell_error() then
+ return 'curl error with ' .. url .. ': ' .. shell_error_code
+ else
+ return rv
+ end
+ elseif executable('python') then
+ local script = "try:\n\
+ from urllib.request import urlopen\n\
+ except ImportError:\n\
+ from urllib2 import urlopen\n\
+ response = urlopen('" .. url .. "')\n\
+ print(response.read().decode('utf8'))\n"
+ local rv = system({ 'python', '-c', script })
+ if is_blank(rv) and shell_error() then
+ return 'python urllib.request error: ' .. shell_error_code
+ else
+ return rv
+ end
+ end
+
+ local message = 'missing `curl` '
+
+ if has_curl then
+ message = message .. '(with HTTPS support) '
+ end
+ message = message .. 'and `python`, cannot make web request'
+
+ return message
+end
+
+-- Get the latest Nvim Python client (pynvim) version from PyPI.
+local function latest_pypi_version()
+ local pypi_version = 'unable to get pypi response'
+ local pypi_response = download('https://pypi.python.org/pypi/pynvim/json')
+ if not is_blank(pypi_response) then
+ local pcall_ok, output = pcall(vim.fn.json_decode, pypi_response)
+ local pypi_data
+ if pcall_ok then
+ pypi_data = output
+ else
+ return 'error: ' .. pypi_response
+ end
+
+ local pypi_element = pypi_data['info'] or {}
+ pypi_version = pypi_element['version'] or 'unable to parse'
+ end
+ return pypi_version
+end
+
+local function is_bad_response(s)
+ local lower = s:lower()
+ return vim.startswith(lower, 'unable')
+ or vim.startswith(lower, 'error')
+ or vim.startswith(lower, 'outdated')
+end
+
+-- Get version information using the specified interpreter. The interpreter is
+-- used directly in case breaking changes were introduced since the last time
+-- Nvim's Python client was updated.
+--
+-- Returns: {
+-- {python executable version},
+-- {current nvim version},
+-- {current pypi nvim status},
+-- {installed version status}
+-- }
+local function version_info(python)
+ local pypi_version = latest_pypi_version()
+
+ local python_version = vim.trim(system({
+ python,
+ '-c',
+ 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))',
+ }))
+
+ if is_blank(python_version) then
+ python_version = 'unable to parse ' .. python .. ' response'
+ end
+
+ local nvim_path = vim.trim(system({
+ python,
+ '-c',
+ 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)',
+ }))
+ if shell_error() or is_blank(nvim_path) then
+ return { python_version, 'unable to load neovim Python module', pypi_version, nvim_path }
+ end
+
+ -- Assuming that multiple versions of a package are installed, sort them
+ -- numerically in descending order.
+ local function compare(metapath1, metapath2)
+ local a = vim.fn.matchstr(vim.fn.fnamemodify(metapath1, ':p:h:t'), [[[0-9.]\+]])
+ local b = vim.fn.matchstr(vim.fn.fnamemodify(metapath2, ':p:h:t'), [[[0-9.]\+]])
+ if a == b then
+ return 0
+ elseif a > b then
+ return 1
+ else
+ return -1
+ end
+ end
+
+ -- Try to get neovim.VERSION (added in 0.1.11dev).
+ local nvim_version = system({
+ python,
+ '-c',
+ 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))',
+ }, '', 1, 1)
+ if is_blank(nvim_version) then
+ nvim_version = 'unable to find pynvim module version'
+ local base = vim.fs.basename(nvim_path, ':h')
+ local metas = vim.fn.glob(base .. '-*/METADATA', 1, 1)
+ vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', 1, 1))
+ vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', 1, 1))
+ metas = table.sort(metas, compare)
+
+ if metas and next(metas) ~= nil then
+ for _, meta_line in ipairs(vim.fn.readfile(metas[1])) do
+ if vim.startswith(meta_line, 'Version:') then
+ nvim_version = vim.fn.matchstr(meta_line, [[^Version: \zs\S\+]])
+ break
+ end
+ end
+ end
+ end
+
+ local nvim_path_base = vim.fn.fnamemodify(nvim_path, [[:~:h]])
+ local version_status = 'unknown; ' .. nvim_path_base
+ if is_bad_response(nvim_version) and is_bad_response(pypi_version) then
+ if vim.version.lt(nvim_version, pypi_version) then
+ version_status = 'outdated; from ' .. nvim_path_base
+ else
+ version_status = 'up to date'
+ end
+ end
+
+ return { python_version, nvim_version, pypi_version, version_status }
+end
+
+-- Resolves Python executable path by invoking and checking `sys.executable`.
+local function python_exepath(invocation)
+ return vim.fs.normalize(
+ system(vim.fn.fnameescape(invocation) .. ' -c "import sys; sys.stdout.write(sys.executable)"')
+ )
+end
+
+local function python()
+ start('Python 3 provider (optional)')
+
+ local pyname = 'python3'
+ local python_exe = ''
+ local virtual_env = os.getenv('VIRTUAL_ENV')
+ local venv = virtual_env and vim.fn.resolve(virtual_env) or ''
+ local host_prog_var = pyname .. '_host_prog'
+ local python_multiple = {}
+
+ if disabled_via_loaded_var(pyname) then
+ return
+ end
+
+ local pyenv_table = check_for_pyenv()
+ local pyenv = pyenv_table[1]
+ local pyenv_root = pyenv_table[2]
+
+ if vim.g[host_prog_var] then
+ local message = 'Using: g:' .. host_prog_var .. ' = "' .. vim.g[host_prog_var] .. '"'
+ info(message)
+ end
+
+ local python_table = vim.fn['provider#pythonx#Detect'](3)
+ pyname = python_table[1]
+ local pythonx_warnings = python_table[2]
+
+ if is_blank(pyname) then
+ warn(
+ 'No Python executable found that can `import neovim`. '
+ .. 'Using the first available executable for diagnostics.'
+ )
+ elseif vim.g[host_prog_var] then
+ python_exe = pyname
+ end
+
+ -- No Python executable could `import neovim`, or host_prog_var was used.
+ if not is_blank(pythonx_warnings) then
+ warn(pythonx_warnings, {
+ 'See :help provider-python for more information.',
+ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim',
+ })
+ elseif not is_blank(pyname) and is_blank(python_exe) then
+ if not vim.g[host_prog_var] then
+ local message = '`g:'
+ .. host_prog_var
+ .. '` is not set. Searching for '
+ .. pyname
+ .. ' in the environment.'
+ info(message)
+ end
+
+ if not is_blank(pyenv) then
+ python_exe = vim.trim(system({ pyenv, 'which', pyname }, '', 1))
+ if is_blank(python_exe) then
+ warn('pyenv could not find ' .. pyname .. '.')
+ end
+ end
+
+ if is_blank(python_exe) then
+ python_exe = vim.fn.exepath(pyname)
+
+ if os.getenv('PATH') then
+ local path_sep = iswin and ';' or ':'
+ local paths = vim.split(os.getenv('PATH') or '', path_sep)
+
+ for _, path in ipairs(paths) do
+ local path_bin = vim.fs.normalize(path .. '/' .. pyname)
+ if
+ path_bin ~= vim.fs.normalize(python_exe)
+ and vim.list_contains(python_multiple, path_bin)
+ and executable(path_bin)
+ then
+ python_multiple[#python_multiple + 1] = path_bin
+ end
+ end
+
+ if vim.tbl_count(python_multiple) > 0 then
+ -- This is worth noting since the user may install something
+ -- that changes $PATH, like homebrew.
+ local message = 'Multiple '
+ .. pyname
+ .. ' executables found. '
+ .. 'Set `g:'
+ .. host_prog_var
+ .. '` to avoid surprises.'
+ info(message)
+ end
+
+ if python_exe:find('shims') then
+ local message = '`' .. python_exe .. '` appears to be a pyenv shim.'
+ local advice = '`pyenv` is not in $PATH, your pyenv installation is broken. Set `g:'
+ .. host_prog_var
+ .. '` to avoid surprises.'
+
+ warn(message, advice)
+ end
+ end
+ end
+ end
+
+ if not is_blank(python_exe) and not vim.g[host_prog_var] then
+ if
+ is_blank(venv)
+ and not is_blank(pyenv)
+ and not is_blank(pyenv_root)
+ and vim.startswith(vim.fn.resolve(python_exe), pyenv_root .. '/')
+ then
+ local advice = 'Create a virtualenv specifically for Nvim using pyenv, and set `g:'
+ .. host_prog_var
+ .. '`. This will avoid the need to install the pynvim module in each version/virtualenv.'
+ warn('pyenv is not set up optimally.', advice)
+ elseif not is_blank(venv) then
+ local venv_root
+ if not is_blank(pyenv_root) then
+ venv_root = pyenv_root
+ else
+ venv_root = vim.fs.dirname(venv)
+ end
+
+ if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then
+ local advice = 'Create a virtualenv specifically for Nvim and use `g:'
+ .. host_prog_var
+ .. '`. This will avoid the need to install the pynvim module in each virtualenv.'
+ warn('Your virtualenv is not set up optimally.', advice)
+ end
+ end
+ end
+
+ if is_blank(python_exe) and not is_blank(pyname) then
+ -- An error message should have already printed.
+ error('`' .. pyname .. '` was not found.')
+ elseif not is_blank(python_exe) and not check_bin(python_exe) then
+ python_exe = ''
+ end
+
+ -- Diagnostic output
+ info('Executable: ' .. (is_blank(python_exe) and 'Not found' or python_exe))
+ if vim.tbl_count(python_multiple) > 0 then
+ for _, path_bin in ipairs(python_multiple) do
+ info('Other python executable: ' .. path_bin)
+ end
+ end
+
+ if is_blank(python_exe) then
+ -- No Python executable can import 'neovim'. Check if any Python executable
+ -- can import 'pynvim'. If so, that Python failed to import 'neovim' as
+ -- well, which is most probably due to a failed pip upgrade:
+ -- https://github.com/neovim/neovim/wiki/Following-HEAD#20181118
+ local pynvim_table = vim.fn['provider#pythonx#DetectByModule']('pynvim', 3)
+ local pynvim_exe = pynvim_table[1]
+ if not is_blank(pynvim_exe) then
+ local message = 'Detected pip upgrade failure: Python executable can import "pynvim" but not "neovim": '
+ .. pynvim_exe
+ local advice = {
+ 'Use that Python version to reinstall "pynvim" and optionally "neovim".',
+ pynvim_exe .. ' -m pip uninstall pynvim neovim',
+ pynvim_exe .. ' -m pip install pynvim',
+ pynvim_exe .. ' -m pip install neovim # only if needed by third-party software',
+ }
+ error(message, advice)
+ end
+ else
+ local version_info_table = version_info(python_exe)
+ local majorpyversion = version_info_table[1]
+ local current = version_info_table[2]
+ local latest = version_info_table[3]
+ local status = version_info_table[4]
+
+ if vim.fn.str2nr(majorpyversion) ~= 3 then
+ warn('Unexpected Python version. This could lead to confusing error messages.')
+ end
+
+ info('Python version: ' .. majorpyversion)
+
+ if is_bad_response(status) then
+ info('pynvim version: ' .. current .. ' (' .. status .. ')')
+ else
+ info('pynvim version: ' .. current)
+ end
+
+ if is_bad_response(current) then
+ error(
+ 'pynvim is not installed.\nError: ' .. current,
+ 'Run in shell: ' .. python_exe .. ' -m pip install pynvim'
+ )
+ end
+
+ if is_bad_response(latest) then
+ warn('Could not contact PyPI to get latest version.')
+ error('HTTP request failed: ' .. latest)
+ elseif is_bad_response(status) then
+ warn('Latest pynvim is NOT installed: ' .. latest)
+ elseif not is_bad_response(current) then
+ ok('Latest pynvim is installed.')
+ end
+ end
+
+ start('Python virtualenv')
+ if not virtual_env then
+ ok('no $VIRTUAL_ENV')
+ return
+ end
+ local errors = {}
+ -- Keep hints as dict keys in order to discard duplicates.
+ local hints = {}
+ -- The virtualenv should contain some Python executables, and those
+ -- executables should be first both on Nvim's $PATH and the $PATH of
+ -- subshells launched from Nvim.
+ local bin_dir = iswin and 'Scripts' or 'bin'
+ local venv_bins = vim.tbl_filter(function(v)
+ -- XXX: Remove irrelevant executables found in bin/.
+ return not v:match('python%-config')
+ end, vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true))
+ if vim.tbl_count(venv_bins) > 0 then
+ for _, venv_bin in pairs(venv_bins) do
+ venv_bin = vim.fs.normalize(venv_bin)
+ local py_bin_basename = vim.fs.basename(venv_bin)
+ local nvim_py_bin = python_exepath(vim.fn.exepath(py_bin_basename))
+ local subshell_py_bin = python_exepath(py_bin_basename)
+ if venv_bin ~= nvim_py_bin then
+ errors[#errors + 1] = '$PATH yields this '
+ .. py_bin_basename
+ .. ' executable: '
+ .. nvim_py_bin
+ local hint = '$PATH ambiguities arise if the virtualenv is not '
+ .. 'properly activated prior to launching Nvim. Close Nvim, activate the virtualenv, '
+ .. 'check that invoking Python from the command line launches the correct one, '
+ .. 'then relaunch Nvim.'
+ hints[hint] = true
+ end
+ if venv_bin ~= subshell_py_bin then
+ errors[#errors + 1] = '$PATH in subshells yields this '
+ .. py_bin_basename
+ .. ' executable: '
+ .. subshell_py_bin
+ local hint = '$PATH ambiguities in subshells typically are '
+ .. 'caused by your shell config overriding the $PATH previously set by the '
+ .. 'virtualenv. Either prevent them from doing so, or use this workaround: '
+ .. 'https://vi.stackexchange.com/a/34996'
+ hints[hint] = true
+ end
+ end
+ else
+ errors[#errors + 1] = 'no Python executables found in the virtualenv '
+ .. bin_dir
+ .. ' directory.'
+ end
+
+ local msg = '$VIRTUAL_ENV is set to: ' .. virtual_env
+ if vim.tbl_count(errors) > 0 then
+ if vim.tbl_count(venv_bins) > 0 then
+ msg = msg
+ .. '\nAnd its '
+ .. bin_dir
+ .. ' directory contains: '
+ .. vim.fn.join(vim.fn.map(venv_bins, [[fnamemodify(v:val, ':t')]]), ', ')
+ end
+ local conj = '\nBut '
+ for _, err in ipairs(errors) do
+ msg = msg .. conj .. err
+ conj = '\nAnd '
+ end
+ msg = msg .. '\nSo invoking Python may lead to unexpected results.'
+ warn(msg, vim.fn.keys(hints))
+ else
+ info(msg)
+ info(
+ 'Python version: '
+ .. system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"')
+ )
+ ok('$VIRTUAL_ENV provides :!python.')
+ end
+end
+
+local function ruby()
+ start('Ruby provider (optional)')
+
+ if disabled_via_loaded_var('ruby') then
+ return
+ end
+
+ if not executable('ruby') or not executable('gem') then
+ warn(
+ '`ruby` and `gem` must be in $PATH.',
+ 'Install Ruby and verify that `ruby` and `gem` commands work.'
+ )
+ return
+ end
+ info('Ruby: ' .. system({ 'ruby', '-v' }))
+
+ local ruby_detect_table = vim.fn['provider#ruby#Detect']()
+ local host = ruby_detect_table[1]
+ if is_blank(host) then
+ warn('`neovim-ruby-host` not found.', {
+ 'Run `gem install neovim` to ensure the neovim RubyGem is installed.',
+ 'Run `gem environment` to ensure the gem bin directory is in $PATH.',
+ 'If you are using rvm/rbenv/chruby, try "rehashing".',
+ 'See :help g:ruby_host_prog for non-standard gem installations.',
+ 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim',
+ })
+ return
+ end
+ info('Host: ' .. host)
+
+ local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$')
+ local latest_gem = system(vim.fn.split(latest_gem_cmd))
+ if shell_error() or is_blank(latest_gem) then
+ error(
+ 'Failed to run: ' .. latest_gem_cmd,
+ { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+ )
+ return
+ end
+ local gem_split = vim.split(latest_gem, [[neovim (\|, \|)$]])
+ latest_gem = gem_split[1] or 'not found'
+
+ local current_gem_cmd = { host, '--version' }
+ local current_gem = system(current_gem_cmd)
+ if shell_error() then
+ error(
+ 'Failed to run: ' .. table.concat(current_gem_cmd, ' '),
+ { 'Report this issue with the output of: ', table.concat(current_gem_cmd, ' ') }
+ )
+ return
+ end
+
+ if vim.version.lt(current_gem, latest_gem) then
+ local message = 'Gem "neovim" is out-of-date. Installed: '
+ .. current_gem
+ .. ', latest: '
+ .. latest_gem
+ warn(message, 'Run in shell: gem update neovim')
+ else
+ ok('Latest "neovim" gem is installed: ' .. current_gem)
+ end
+end
+
+local function node()
+ start('Node.js provider (optional)')
+
+ if disabled_via_loaded_var('node') then
+ return
+ end
+
+ if
+ not executable('node')
+ or (not executable('npm') and not executable('yarn') and not executable('pnpm'))
+ then
+ warn(
+ '`node` and `npm` (or `yarn`, `pnpm`) must be in $PATH.',
+ 'Install Node.js and verify that `node` and `npm` (or `yarn`, `pnpm`) commands work.'
+ )
+ return
+ end
+
+ -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or ''
+ local node_v = system({ 'node', '-v' })
+ info('Node.js: ' .. node_v)
+ if shell_error() or vim.version.lt(node_v, '6.0.0') then
+ warn('Nvim node.js host does not support Node ' .. node_v)
+ -- Skip further checks, they are nonsense if nodejs is too old.
+ return
+ end
+ if vim.fn['provider#node#can_inspect']() == 0 then
+ warn(
+ 'node.js on this system does not support --inspect-brk so $NVIM_NODE_HOST_DEBUG is ignored.'
+ )
+ end
+
+ local node_detect_table = vim.fn['provider#node#Detect']()
+ local host = node_detect_table[1]
+ if is_blank(host) then
+ warn('Missing "neovim" npm (or yarn, pnpm) package.', {
+ 'Run in shell: npm install -g neovim',
+ 'Run in shell (if you use yarn): yarn global add neovim',
+ 'Run in shell (if you use pnpm): pnpm install -g neovim',
+ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim',
+ })
+ return
+ end
+ info('Nvim node.js host: ' .. host)
+
+ local manager = 'npm'
+ if executable('yarn') then
+ manager = 'yarn'
+ elseif executable('pnpm') then
+ manager = 'pnpm'
+ end
+
+ local latest_npm_cmd = (
+ iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json'
+ )
+ local latest_npm = system(vim.fn.split(latest_npm_cmd))
+ if shell_error() or is_blank(latest_npm) then
+ error(
+ 'Failed to run: ' .. latest_npm_cmd,
+ { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+ )
+ return
+ end
+
+ local pcall_ok, output = pcall(vim.fn.json_decode, latest_npm)
+ local pkg_data
+ if pcall_ok then
+ pkg_data = output
+ else
+ return 'error: ' .. latest_npm
+ end
+ local latest_npm_subtable = pkg_data['dist-tags'] or {}
+ latest_npm = latest_npm_subtable['latest'] or 'unable to parse'
+
+ local current_npm_cmd = { 'node', host, '--version' }
+ local current_npm = system(current_npm_cmd)
+ if shell_error() then
+ error(
+ 'Failed to run: ' .. table.concat(current_npm_cmd, ' '),
+ { 'Report this issue with the output of: ', table.concat(current_npm_cmd, ' ') }
+ )
+ return
+ end
+
+ if latest_npm ~= 'unable to parse' and vim.version.lt(current_npm, latest_npm) then
+ local message = 'Package "neovim" is out-of-date. Installed: '
+ .. current_npm
+ .. ' latest: '
+ .. latest_npm
+ warn(message, {
+ 'Run in shell: npm install -g neovim',
+ 'Run in shell (if you use yarn): yarn global add neovim',
+ 'Run in shell (if you use pnpm): pnpm install -g neovim',
+ })
+ else
+ ok('Latest "neovim" npm/yarn/pnpm package is installed: ' .. current_npm)
+ end
+end
+
+local function perl()
+ start('Perl provider (optional)')
+
+ if disabled_via_loaded_var('perl') then
+ return
+ end
+
+ local perl_detect_table = vim.fn['provider#perl#Detect']()
+ local perl_exec = perl_detect_table[1]
+ local perl_warnings = perl_detect_table[2]
+
+ if is_blank(perl_exec) then
+ if not is_blank(perl_warnings) then
+ warn(perl_warnings, {
+ 'See :help provider-perl for more information.',
+ 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim',
+ })
+ else
+ warn('No usable perl executable found')
+ end
+ return
+ end
+
+ info('perl executable: ' .. perl_exec)
+
+ -- we cannot use cpanm that is on the path, as it may not be for the perl
+ -- set with g:perl_host_prog
+ system({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' })
+ if shell_error() then
+ return { perl_exec, '"App::cpanminus" module is not installed' }
+ end
+
+ local latest_cpan_cmd = {
+ perl_exec,
+ '-MApp::cpanminus::script',
+ '-e',
+ 'my $app = App::cpanminus::script->new; $app->parse_options ("--info", "-q", "Neovim::Ext"); exit $app->doit',
+ }
+
+ local latest_cpan = system(latest_cpan_cmd)
+ if shell_error() or is_blank(latest_cpan) then
+ error(
+ 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '),
+ { "Make sure you're connected to the internet.", 'Are you behind a firewall or proxy?' }
+ )
+ return
+ elseif latest_cpan[1] == '!' then
+ local cpanm_errs = vim.split(latest_cpan, '!')
+ if cpanm_errs[1]:find("Can't write to ") then
+ local advice = {}
+ for i = 2, #cpanm_errs do
+ advice[#advice + 1] = cpanm_errs[i]
+ end
+
+ warn(cpanm_errs[1], advice)
+ -- Last line is the package info
+ latest_cpan = cpanm_errs[#cpanm_errs]
+ else
+ error('Unknown warning from command: ' .. latest_cpan_cmd, cpanm_errs)
+ return
+ end
+ end
+ latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]])
+ if is_blank(latest_cpan) then
+ error('Cannot parse version number from cpanm output: ' .. latest_cpan)
+ return
+ end
+
+ local current_cpan_cmd = { perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION' }
+ local current_cpan = system(current_cpan_cmd)
+ if shell_error() then
+ error(
+ 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '),
+ { 'Report this issue with the output of: ', table.concat(current_cpan_cmd, ' ') }
+ )
+ return
+ end
+
+ if vim.version.lt(current_cpan, latest_cpan) then
+ local message = 'Module "Neovim::Ext" is out-of-date. Installed: '
+ .. current_cpan
+ .. ', latest: '
+ .. latest_cpan
+ warn(message, 'Run in shell: cpanm -n Neovim::Ext')
+ else
+ ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan)
+ end
+end
+
+function M.check()
+ clipboard()
+ python()
+ ruby()
+ node()
+ perl()
+end
+
+return M
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
index 3e370c0a84..5ed60ca8ab 100644
--- a/runtime/lua/vim/F.lua
+++ b/runtime/lua/vim/F.lua
@@ -1,18 +1,30 @@
local F = {}
---- Returns {a} if it is not nil, otherwise returns {b}.
+--- Returns the first argument which is not nil.
---
----@generic A
----@generic B
+--- If all arguments are nil, returns nil.
---
----@param a A
----@param b B
----@return A | B
-function F.if_nil(a, b)
- if a == nil then
- return b
+--- Examples:
+---
+--- ```lua
+--- local a = nil
+--- local b = nil
+--- local c = 42
+--- local d = true
+--- assert(vim.F.if_nil(a, b, c, d) == 42)
+--- ```
+---
+---@param ... any
+---@return any
+function F.if_nil(...)
+ local nargs = select('#', ...)
+ for i = 1, nargs do
+ local v = select(i, ...)
+ if v ~= nil then
+ return v
+ end
end
- return a
+ return nil
end
-- Use in combination with pcall
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
new file mode 100644
index 0000000000..cc872dea83
--- /dev/null
+++ b/runtime/lua/vim/_defaults.lua
@@ -0,0 +1,314 @@
+--- Default mappings
+do
+ --- Default maps for * and # in visual mode.
+ ---
+ --- See |v_star-default| and |v_#-default|
+ do
+ local function region_chunks(region)
+ local chunks = {}
+ local maxcol = vim.v.maxcol
+ for line, cols in vim.spairs(region) do
+ local endcol = cols[2] == maxcol and -1 or cols[2]
+ local chunk = vim.api.nvim_buf_get_text(0, line, cols[1], line, endcol, {})[1]
+ table.insert(chunks, chunk)
+ end
+ return chunks
+ end
+
+ local function _visual_search(cmd)
+ assert(cmd == '/' or cmd == '?')
+ local region = vim.region(
+ 0,
+ '.',
+ 'v',
+ vim.api.nvim_get_mode().mode:sub(1, 1),
+ vim.o.selection == 'inclusive'
+ )
+ local chunks = region_chunks(region)
+ local esc_chunks = vim
+ .iter(chunks)
+ :map(function(v)
+ return vim.fn.escape(v, cmd == '/' and [[/\]] or [[?\]])
+ end)
+ :totable()
+ local esc_pat = table.concat(esc_chunks, [[\n]])
+ local search_cmd = ([[%s\V%s%s]]):format(cmd, esc_pat, '\n')
+ return '\27' .. search_cmd
+ end
+
+ vim.keymap.set('x', '*', function()
+ return _visual_search('/')
+ end, { desc = ':help v_star-default', expr = true, silent = true })
+ vim.keymap.set('x', '#', function()
+ return _visual_search('?')
+ end, { desc = ':help v_#-default', expr = true, silent = true })
+ end
+
+ --- Map Y to y$. This mimics the behavior of D and C. See |Y-default|
+ vim.keymap.set('n', 'Y', 'y$', { desc = ':help Y-default' })
+
+ --- Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>. #17473
+ ---
+ --- See |CTRL-L-default|
+ vim.keymap.set('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>', {
+ desc = ':help CTRL-L-default',
+ })
+
+ --- Set undo points when deleting text in insert mode.
+ ---
+ --- See |i_CTRL-U-default| and |i_CTRL-W-default|
+ vim.keymap.set('i', '<C-U>', '<C-G>u<C-U>', { desc = ':help i_CTRL-U-default' })
+ vim.keymap.set('i', '<C-W>', '<C-G>u<C-W>', { desc = ':help i_CTRL-W-default' })
+
+ --- Use the same flags as the previous substitution with &.
+ ---
+ --- Use : instead of <Cmd> so that ranges are supported. #19365
+ ---
+ --- See |&-default|
+ vim.keymap.set('n', '&', ':&&<CR>', { desc = ':help &-default' })
+
+ --- Map |gx| to call |vim.ui.open| on the identifier under the cursor
+ do
+ -- TODO: use vim.region() when it lands... #13896 #16843
+ local function get_visual_selection()
+ local save_a = vim.fn.getreginfo('a')
+ vim.cmd([[norm! "ay]])
+ local selection = vim.fn.getreg('a', 1)
+ vim.fn.setreg('a', save_a)
+ return selection
+ end
+
+ local function do_open(uri)
+ local _, err = vim.ui.open(uri)
+ if err then
+ vim.notify(err, vim.log.levels.ERROR)
+ end
+ end
+
+ local gx_desc =
+ 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)'
+ vim.keymap.set({ 'n' }, 'gx', function()
+ do_open(vim.fn.expand('<cfile>'))
+ end, { desc = gx_desc })
+ vim.keymap.set({ 'x' }, 'gx', function()
+ do_open(get_visual_selection())
+ end, { desc = gx_desc })
+ end
+end
+
+--- Default menus
+do
+ --- Right click popup menu
+ -- TODO VimScript, no l10n
+ vim.cmd([[
+ aunmenu *
+ vnoremenu PopUp.Cut "+x
+ vnoremenu PopUp.Copy "+y
+ anoremenu PopUp.Paste "+gP
+ vnoremenu PopUp.Paste "+P
+ vnoremenu PopUp.Delete "_x
+ nnoremenu PopUp.Select\ All ggVG
+ vnoremenu PopUp.Select\ All gg0oG$
+ inoremenu PopUp.Select\ All <C-Home><C-O>VG
+ anoremenu PopUp.-1- <Nop>
+ anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
+ ]])
+end
+
+--- Default autocommands. See |default-autocmds|
+do
+ local nvim_terminal_augroup = vim.api.nvim_create_augroup('nvim_terminal', {})
+ vim.api.nvim_create_autocmd('BufReadCmd', {
+ pattern = 'term://*',
+ group = nvim_terminal_augroup,
+ desc = 'Treat term:// buffers as terminal buffers',
+ nested = true,
+ command = "if !exists('b:term_title')|call termopen(matchstr(expand(\"<amatch>\"), '\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), {'cwd': expand(get(matchlist(expand(\"<amatch>\"), '\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))})",
+ })
+
+ vim.api.nvim_create_autocmd({ 'TermClose' }, {
+ group = nvim_terminal_augroup,
+ desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error',
+ callback = function(args)
+ if vim.v.event.status == 0 then
+ local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel)
+ local argv = info.argv or {}
+ if #argv == 1 and argv[1] == vim.o.shell then
+ vim.cmd({ cmd = 'bdelete', args = { args.buf }, bang = true })
+ end
+ end
+ end,
+ })
+
+ vim.api.nvim_create_autocmd('CmdwinEnter', {
+ pattern = '[:>]',
+ desc = 'Limit syntax sync to maxlines=1 in the command window',
+ group = vim.api.nvim_create_augroup('nvim_cmdwin', {}),
+ command = 'syntax sync minlines=1 maxlines=1',
+ })
+
+ vim.api.nvim_create_autocmd('SwapExists', {
+ pattern = '*',
+ desc = 'Skip the swapfile prompt when the swapfile is owned by a running Nvim process',
+ group = vim.api.nvim_create_augroup('nvim_swapfile', {}),
+ callback = function()
+ local info = vim.fn.swapinfo(vim.v.swapname)
+ local user = vim.uv.os_get_passwd().username
+ local iswin = 1 == vim.fn.has('win32')
+ if info.error or info.pid <= 0 or (not iswin and info.user ~= user) then
+ vim.v.swapchoice = '' -- Show the prompt.
+ return
+ end
+ vim.v.swapchoice = 'e' -- Choose "(E)dit".
+ vim.notify(('W325: Ignoring swapfile from Nvim process %d'):format(info.pid))
+ end,
+ })
+end
+
+--- Guess value of 'background' based on terminal color.
+---
+--- We write Operating System Command (OSC) 11 to the terminal to request the
+--- terminal's background color. We then wait for a response. If the response
+--- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
+--- compute the luminance[1] of the RGB color and classify it as light/dark
+--- accordingly. Note that the color components may have anywhere from one to
+--- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
+--- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
+--- ignored in the calculations.
+---
+--- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
+do
+ --- Parse a string of hex characters as a color.
+ ---
+ --- The string can contain 1 to 4 hex characters. The returned value is
+ --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
+ ---
+ --- For instance, if only a single hex char "a" is used, then this function
+ --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
+ --- 256).
+ ---
+ --- @param c string Color as a string of hex chars
+ --- @return number? Intensity of the color
+ local function parsecolor(c)
+ if #c == 0 or #c > 4 then
+ return nil
+ end
+
+ local val = tonumber(c, 16)
+ if not val then
+ return nil
+ end
+
+ local max = tonumber(string.rep('f', #c), 16)
+ return val / max
+ end
+
+ --- Parse an OSC 11 response
+ ---
+ --- Either of the two formats below are accepted:
+ ---
+ --- OSC 11 ; rgb:<red>/<green>/<blue>
+ ---
+ --- or
+ ---
+ --- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
+ ---
+ --- where
+ ---
+ --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
+ ---
+ --- The alpha component is ignored, if present.
+ ---
+ --- @param resp string OSC 11 response
+ --- @return string? Red component
+ --- @return string? Green component
+ --- @return string? Blue component
+ local function parseosc11(resp)
+ local r, g, b
+ r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
+ if not r and not g and not b then
+ local a
+ r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
+ if not a or #a > 4 then
+ return nil, nil, nil
+ end
+ end
+
+ if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
+ return r, g, b
+ end
+
+ return nil, nil, nil
+ end
+
+ local tty = false
+ for _, ui in ipairs(vim.api.nvim_list_uis()) do
+ if ui.chan == 1 and ui.stdout_tty then
+ tty = true
+ break
+ end
+ end
+
+ if tty then
+ local timer = assert(vim.uv.new_timer())
+
+ ---@param bg string New value of the 'background' option
+ local function setbg(bg)
+ if vim.api.nvim_get_option_info2('background', {}).was_set then
+ -- Don't do anything if 'background' is already set
+ return
+ end
+
+ -- Wait until Nvim is finished starting to set 'background' to ensure the
+ -- OptionSet event fires.
+ if vim.v.vim_did_enter == 1 then
+ if vim.o.background ~= bg then
+ vim.o.background = bg
+ end
+ else
+ vim.api.nvim_create_autocmd('VimEnter', {
+ once = true,
+ nested = true,
+ callback = function()
+ setbg(bg)
+ end,
+ })
+ end
+ end
+
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ nested = true,
+ callback = function(args)
+ local resp = args.data ---@type string
+ local r, g, b = parseosc11(resp)
+ if r and g and b then
+ local rr = parsecolor(r)
+ local gg = parsecolor(g)
+ local bb = parsecolor(b)
+
+ if rr and gg and bb then
+ local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
+ local bg = luminance < 0.5 and 'dark' or 'light'
+ setbg(bg)
+ end
+
+ return true
+ end
+ end,
+ })
+
+ io.stdout:write('\027]11;?\027\\')
+
+ timer:start(1000, 0, function()
+ -- No response received. Delete the autocommand
+ vim.schedule(function()
+ -- Suppress error if autocommand has already been deleted
+ pcall(vim.api.nvim_del_autocmd, id)
+ end)
+
+ if not timer:is_closing() then
+ timer:close()
+ end
+ end)
+ end
+end
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index da8764fbd4..6cccbe8313 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -3,7 +3,7 @@
-- Lua code lives in one of three places:
-- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the
-- `inspect` and `lpeg` modules.
--- 2. runtime/lua/vim/shared.lua: pure lua functions which always
+-- 2. runtime/lua/vim/shared.lua: pure Lua functions which always
-- are available. Used in the test runner, as well as worker threads
-- and processes launched from Nvim.
-- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with
@@ -28,6 +28,8 @@
for k, v in pairs({
treesitter = true,
filetype = true,
+ loader = true,
+ func = true,
F = true,
lsp = true,
highlight = true,
@@ -35,12 +37,26 @@ for k, v in pairs({
keymap = true,
ui = true,
health = true,
- fs = true,
secure = true,
+ snippet = true,
+ _watch = true,
}) do
vim._submodules[k] = v
end
+-- There are things which have special rules in vim._init_packages
+-- for legacy reasons (uri) or for performance (_inspector).
+-- most new things should go into a submodule namespace ( vim.foobar.do_thing() )
+vim._extra = {
+ uri_from_fname = true,
+ uri_from_bufnr = true,
+ uri_to_fname = true,
+ uri_to_bufnr = true,
+ show_pos = true,
+ inspect_pos = true,
+}
+
+--- @private
vim.log = {
levels = {
TRACE = 0,
@@ -52,13 +68,78 @@ vim.log = {
},
}
--- Internal-only until comments in #8107 are addressed.
--- Returns:
--- {errcode}, {output}
-function vim._system(cmd)
- local out = vim.fn.system(cmd)
- local err = vim.v.shell_error
- return err, out
+-- TODO(lewis6991): document that the signature is system({cmd}, [{opts},] {on_exit})
+--- Runs a system command or throws an error if {cmd} cannot be run.
+---
+--- Examples:
+---
+--- ```lua
+---
+--- local on_exit = function(obj)
+--- print(obj.code)
+--- print(obj.signal)
+--- print(obj.stdout)
+--- print(obj.stderr)
+--- end
+---
+--- -- Runs asynchronously:
+--- vim.system({'echo', 'hello'}, { text = true }, on_exit)
+---
+--- -- Runs synchronously:
+--- local obj = vim.system({'echo', 'hello'}, { text = true }):wait()
+--- -- { code = 0, signal = 0, stdout = 'hello', stderr = '' }
+---
+--- ```
+---
+--- See |uv.spawn()| for more details. Note: unlike |uv.spawn()|, vim.system
+--- throws an error if {cmd} cannot be run.
+---
+--- @param cmd (string[]) Command to execute
+--- @param opts (SystemOpts|nil) Options:
+--- - cwd: (string) Set the current working directory for the sub-process.
+--- - env: table<string,string> Set environment variables for the new process. Inherits the
+--- current environment with `NVIM` set to |v:servername|.
+--- - clear_env: (boolean) `env` defines the job environment exactly, instead of merging current
+--- environment.
+--- - stdin: (string|string[]|boolean) If `true`, then a pipe to stdin is opened and can be written
+--- to via the `write()` method to SystemObj. If string or string[] then will be written to stdin
+--- and closed. Defaults to `false`.
+--- - stdout: (boolean|function)
+--- Handle output from stdout. When passed as a function must have the signature `fun(err: string, data: string)`.
+--- Defaults to `true`
+--- - stderr: (boolean|function)
+--- Handle output from stderr. When passed as a function must have the signature `fun(err: string, data: string)`.
+--- Defaults to `true`.
+--- - text: (boolean) Handle stdout and stderr as text. Replaces `\r\n` with `\n`.
+--- - timeout: (integer) Run the command with a time limit. Upon timeout the process is sent the
+--- TERM signal (15) and the exit code is set to 124.
+--- - detach: (boolean) If true, spawn the child process in a detached state - this will make it
+--- a process group leader, and will effectively enable the child to keep running after the
+--- parent exits. Note that the child process will still keep the parent's event loop alive
+--- unless the parent process calls |uv.unref()| on the child's process handle.
+---
+--- @param on_exit (function|nil) Called when subprocess exits. When provided, the command runs
+--- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait().
+---
+--- @return vim.SystemObj Object with the fields:
+--- - pid (integer) Process ID
+--- - wait (fun(timeout: integer|nil): SystemCompleted) Wait for the process to complete. Upon
+--- timeout the process is sent the KILL signal (9) and the exit code is set to 124. Cannot
+--- be called in |api-fast|.
+--- - SystemCompleted is an object with the fields:
+--- - code: (integer)
+--- - signal: (integer)
+--- - stdout: (string), nil if stdout argument is passed
+--- - stderr: (string), nil if stderr argument is passed
+--- - kill (fun(signal: integer|string))
+--- - write (fun(data: string|nil)) Requires `stdin=true`. Pass `nil` to close the stream.
+--- - is_closing (fun(): boolean)
+function vim.system(cmd, opts, on_exit)
+ if type(opts) == 'function' then
+ on_exit = opts
+ opts = nil
+ end
+ return require('vim._system').run(cmd, opts, on_exit)
end
-- Gets process info from the `ps` command.
@@ -68,13 +149,14 @@ function vim._os_proc_info(pid)
error('invalid pid')
end
local cmd = { 'ps', '-p', pid, '-o', 'comm=' }
- local err, name = vim._system(cmd)
- if 1 == err and vim.trim(name) == '' then
+ local r = vim.system(cmd):wait()
+ local name = assert(r.stdout)
+ if r.code == 1 and vim.trim(name) == '' then
return {} -- Process not found.
- elseif 0 ~= err then
+ elseif r.code ~= 0 then
error('command failed: ' .. vim.fn.string(cmd))
end
- local _, ppid = vim._system({ 'ps', '-p', pid, '-o', 'ppid=' })
+ local ppid = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout)
-- Remove trailing whitespace.
name = vim.trim(name):gsub('^.*/', '')
ppid = tonumber(ppid) or -1
@@ -92,14 +174,14 @@ function vim._os_proc_children(ppid)
error('invalid ppid')
end
local cmd = { 'pgrep', '-P', ppid }
- local err, rv = vim._system(cmd)
- if 1 == err and vim.trim(rv) == '' then
+ local r = vim.system(cmd):wait()
+ if r.code == 1 and vim.trim(r.stdout) == '' then
return {} -- Process not found.
- elseif 0 ~= err then
+ elseif r.code ~= 0 then
error('command failed: ' .. vim.fn.string(cmd))
end
local children = {}
- for s in rv:gmatch('%S+') do
+ for s in r.stdout:gmatch('%S+') do
local i = tonumber(s)
if i ~= nil then
table.insert(children, i)
@@ -110,11 +192,11 @@ end
--- Gets a human-readable representation of the given object.
---
+---@see |vim.print()|
---@see https://github.com/kikito/inspect.lua
---@see https://github.com/mpeterv/vinspect
-local function inspect(object, options) -- luacheck: no unused
- error(object, options) -- Stub for gen_vimdoc.py
-end
+---@return string
+vim.inspect = vim.inspect
do
local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false
@@ -123,7 +205,8 @@ do
--- (such as the |TUI|) pastes text into the editor.
---
--- Example: To remove ANSI color codes when pasting:
- --- <pre>lua
+ ---
+ --- ```lua
--- vim.paste = (function(overridden)
--- return function(lines, phase)
--- for i,line in ipairs(lines) do
@@ -133,7 +216,7 @@ do
--- overridden(lines, phase)
--- end
--- end)(vim.paste)
- --- </pre>
+ --- ```
---
---@see |paste|
---@alias paste_phase -1 | 1 | 2 | 3
@@ -144,9 +227,9 @@ do
--- - 1: starts the paste (exactly once)
--- - 2: continues the paste (zero or more times)
--- - 3: ends the paste (exactly once)
- ---@returns boolean # false if client should cancel the paste.
+ ---@return boolean result false if client should cancel the paste.
function vim.paste(lines, phase)
- local now = vim.loop.now()
+ local now = vim.uv.now()
local is_first_chunk = phase < 2
local is_last_chunk = phase == -1 or phase == 3
if is_first_chunk then -- Reset flags.
@@ -236,23 +319,35 @@ do
end
end
---- Defers callback `cb` until the Nvim API is safe to call.
+--- Returns a function which calls {fn} via |vim.schedule()|.
+---
+--- The returned function passes all arguments to {fn}.
+---
+--- Example:
+---
+--- ```lua
+--- function notify_readable(_err, readable)
+--- vim.notify("readable? " .. tostring(readable))
+--- end
+--- vim.uv.fs_access(vim.fn.stdpath("config"), "R", vim.schedule_wrap(notify_readable))
+--- ```
---
---@see |lua-loop-callbacks|
---@see |vim.schedule()|
---@see |vim.in_fast_event()|
----@param cb function
+---@param fn function
---@return function
-function vim.schedule_wrap(cb)
+function vim.schedule_wrap(fn)
return function(...)
local args = vim.F.pack_len(...)
vim.schedule(function()
- cb(vim.F.unpack_len(args))
+ fn(vim.F.unpack_len(args))
end)
end
end
-- vim.fn.{func}(...)
+---@private
vim.fn = setmetatable({}, {
__index = function(t, key)
local _fn
@@ -270,62 +365,61 @@ vim.fn = setmetatable({}, {
end,
})
+--- @private
vim.funcref = function(viml_func_name)
return vim.fn[viml_func_name]
end
---- Execute Vim script commands.
+local VIM_CMD_ARG_MAX = 20
+
+--- Executes Vim script commands.
---
--- Note that `vim.cmd` can be indexed with a command name to return a callable function to the
--- command.
---
--- Example:
---- <pre>lua
---- vim.cmd('echo 42')
---- vim.cmd([[
---- augroup My_group
---- autocmd!
---- autocmd FileType c setlocal cindent
---- augroup END
---- ]])
----
---- -- Ex command :echo "foo"
---- -- Note string literals need to be double quoted.
---- vim.cmd('echo "foo"')
---- vim.cmd { cmd = 'echo', args = { '"foo"' } }
---- vim.cmd.echo({ args = { '"foo"' } })
---- vim.cmd.echo('"foo"')
----
---- -- Ex command :write! myfile.txt
---- vim.cmd('write! myfile.txt')
---- vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true }
---- vim.cmd.write { args = { "myfile.txt" }, bang = true }
---- vim.cmd.write { "myfile.txt", bang = true }
----
---- -- Ex command :colorscheme blue
---- vim.cmd('colorscheme blue')
---- vim.cmd.colorscheme('blue')
---- </pre>
+---
+--- ```lua
+--- vim.cmd('echo 42')
+--- vim.cmd([[
+--- augroup My_group
+--- autocmd!
+--- autocmd FileType c setlocal cindent
+--- augroup END
+--- ]])
+---
+--- -- Ex command :echo "foo"
+--- -- Note string literals need to be double quoted.
+--- vim.cmd('echo "foo"')
+--- vim.cmd { cmd = 'echo', args = { '"foo"' } }
+--- vim.cmd.echo({ args = { '"foo"' } })
+--- vim.cmd.echo('"foo"')
+---
+--- -- Ex command :write! myfile.txt
+--- vim.cmd('write! myfile.txt')
+--- vim.cmd { cmd = 'write', args = { "myfile.txt" }, bang = true }
+--- vim.cmd.write { args = { "myfile.txt" }, bang = true }
+--- vim.cmd.write { "myfile.txt", bang = true }
+---
+--- -- Ex command :colorscheme blue
+--- vim.cmd('colorscheme blue')
+--- vim.cmd.colorscheme('blue')
+--- ```
---
---@param command string|table Command(s) to execute.
--- If a string, executes multiple lines of Vim script at once. In this
---- case, it is an alias to |nvim_exec()|, where `output` is set to
---- false. Thus it works identical to |:source|.
+--- case, it is an alias to |nvim_exec2()|, where `opts.output` is set
+--- to false. Thus it works identical to |:source|.
--- If a table, executes a single command. In this case, it is an alias
--- to |nvim_cmd()| where `opts` is empty.
---@see |ex-cmd-index|
-function vim.cmd(command) -- luacheck: no unused
- error(command) -- Stub for gen_vimdoc.py
-end
-
-local VIM_CMD_ARG_MAX = 20
-
vim.cmd = setmetatable({}, {
__call = function(_, command)
if type(command) == 'table' then
return vim.api.nvim_cmd(command, {})
else
- return vim.api.nvim_exec(command, false)
+ vim.api.nvim_exec2(command, {})
+ return ''
end
end,
__index = function(t, command)
@@ -355,11 +449,17 @@ vim.cmd = setmetatable({}, {
end,
})
+--- @class vim.var_accessor
+--- @field [string] any
+--- @field [integer] vim.var_accessor
+
-- These are the vim.env/v/g/o/bo/wo variable magic accessors.
do
local validate = vim.validate
- --@private
+ --- @param scope string
+ --- @param handle? false|integer
+ --- @return vim.var_accessor
local function make_dict_accessor(scope, handle)
validate({
scope = { scope, 's' },
@@ -384,19 +484,41 @@ do
vim.t = make_dict_accessor('t')
end
---- Get a table of lines with start, end columns for a region marked by two points
+--- Gets a dict of line segment ("chunk") positions for the region from `pos1` to `pos2`.
+---
+--- Input and output positions are byte positions, (0,0)-indexed. "End of line" column
+--- position (for example, |linewise| visual selection) is returned as |v:maxcol| (big number).
---
----@param bufnr number of buffer
----@param pos1 integer[] (line, column) tuple marking beginning of region
----@param pos2 integer[] (line, column) tuple marking end of region
----@param regtype string type of selection, see |setreg()|
----@param inclusive boolean indicating whether the selection is end-inclusive
----@return table region Table of the form `{linenr = {startcol,endcol}}`
+---@param bufnr integer Buffer number, or 0 for current buffer
+---@param pos1 integer[]|string Start of region as a (line, column) tuple or |getpos()|-compatible string
+---@param pos2 integer[]|string End of region as a (line, column) tuple or |getpos()|-compatible string
+---@param regtype string \|setreg()|-style selection type
+---@param inclusive boolean Controls whether the ending column is inclusive (see also 'selection').
+---@return table region Dict of the form `{linenr = {startcol,endcol}}`. `endcol` is exclusive, and
+---whole lines are returned as `{startcol,endcol} = {0,-1}`.
function vim.region(bufnr, pos1, pos2, regtype, inclusive)
if not vim.api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
+ if type(pos1) == 'string' then
+ local pos = vim.fn.getpos(pos1)
+ pos1 = { pos[2] - 1, pos[3] - 1 }
+ end
+ if type(pos2) == 'string' then
+ local pos = vim.fn.getpos(pos2)
+ pos2 = { pos[2] - 1, pos[3] - 1 }
+ end
+
+ if pos1[1] > pos2[1] or (pos1[1] == pos2[1] and pos1[2] > pos2[2]) then
+ pos1, pos2 = pos2, pos1
+ end
+
+ -- getpos() may return {0,0,0,0}
+ if pos1[1] < 0 or pos1[2] < 0 then
+ return {}
+ end
+
-- check that region falls within current buffer
local buf_line_count = vim.api.nvim_buf_line_count(bufnr)
pos1[1] = math.min(pos1[1], buf_line_count - 1)
@@ -404,9 +526,8 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
-- in case of block selection, columns need to be adjusted for non-ASCII characters
-- TODO: handle double-width characters
- local bufline
if regtype:byte() == 22 then
- bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
+ local bufline = vim.api.nvim_buf_get_lines(bufnr, pos1[1], pos1[1] + 1, true)[1]
pos1[2] = vim.str_utfindex(bufline, pos1[2])
end
@@ -417,7 +538,7 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
c1 = pos1[2]
c2 = c1 + regtype:sub(2)
-- and adjust for non-ASCII characters
- bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
+ local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
local utflen = vim.str_utfindex(bufline, #bufline)
if c1 <= utflen then
c1 = vim.str_byteindex(bufline, c1)
@@ -429,18 +550,25 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
else
c2 = #bufline + 1
end
+ elseif regtype == 'V' then -- linewise selection, always return whole line
+ c1 = 0
+ c2 = -1
else
c1 = (l == pos1[1]) and pos1[2] or 0
- c2 = (l == pos2[1]) and (pos2[2] + (inclusive and 1 or 0)) or -1
+ if inclusive and l == pos2[1] then
+ local bufline = vim.api.nvim_buf_get_lines(bufnr, pos2[1], pos2[1] + 1, true)[1]
+ pos2[2] = vim.fn.byteidx(bufline, vim.fn.charidx(bufline, pos2[2]) + 1)
+ end
+ c2 = (l == pos2[1]) and pos2[2] or -1
end
table.insert(region, l, { c1, c2 })
end
return region
end
---- Defers calling `fn` until `timeout` ms passes.
+--- Defers calling {fn} until {timeout} ms passes.
---
---- Use to do a one-shot timer that calls `fn`
+--- Use to do a one-shot timer that calls {fn}
--- Note: The {fn} is |vim.schedule_wrap()|ped automatically, so API functions are
--- safe to call.
---@param fn function Callback to call once `timeout` expires
@@ -448,7 +576,7 @@ end
---@return table timer luv timer object
function vim.defer_fn(fn, timeout)
vim.validate({ fn = { fn, 'c', true } })
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(
timeout,
0,
@@ -464,14 +592,14 @@ function vim.defer_fn(fn, timeout)
return timer
end
---- Display a notification to the user.
+--- Displays a notification to the user.
---
--- This function can be overridden by plugins to display notifications using a
--- custom provider (such as the system notification provider). By default,
--- writes to |:messages|.
---
---@param msg string Content of the notification to show to the user.
----@param level number|nil One of the values from |vim.log.levels|.
+---@param level integer|nil One of the values from |vim.log.levels|.
---@param opts table|nil Optional parameters. Unused by default.
function vim.notify(msg, level, opts) -- luacheck: no unused args
if level == vim.log.levels.ERROR then
@@ -486,13 +614,13 @@ end
do
local notified = {}
- --- Display a notification only one time.
+ --- Displays a notification only one time.
---
--- Like |vim.notify()|, but subsequent calls with the same message will not
--- display a notification.
---
---@param msg string Content of the notification to show to the user.
- ---@param level number|nil One of the values from |vim.log.levels|.
+ ---@param level integer|nil One of the values from |vim.log.levels|.
---@param opts table|nil Optional parameters. Unused by default.
---@return boolean true if message was displayed, else false
function vim.notify_once(msg, level, opts)
@@ -505,11 +633,6 @@ do
end
end
----@private
-function vim.register_keystroke_callback()
- error('vim.register_keystroke_callback is deprecated, instead use: vim.on_key')
-end
-
local on_key_cbs = {}
--- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
@@ -518,21 +641,20 @@ local on_key_cbs = {}
--- The Nvim command-line option |-w| is related but does not support callbacks
--- and cannot be toggled dynamically.
---
----@param fn function: Callback function. It should take one string argument.
---- On each key press, Nvim passes the key char to fn(). |i_CTRL-V|
---- If {fn} is nil, it removes the callback for the associated {ns_id}
----@param ns_id number? Namespace ID. If nil or 0, generates and returns a new
---- |nvim_create_namespace()| id.
----
----@return number Namespace id associated with {fn}. Or count of all callbacks
----if on_key() is called without arguments.
----
----@note {fn} will be removed if an error occurs while calling.
+---@note {fn} will be removed on error.
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
---@note {fn} will receive the keys after mappings have been evaluated
+---
+---@param fn fun(key: string) Function invoked on every key press. |i_CTRL-V|
+--- Returning nil removes the callback associated with namespace {ns_id}.
+---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a
+--- new |nvim_create_namespace()| id.
+---
+---@return integer Namespace id associated with {fn}. Or count of all callbacks
+---if on_key() is called without arguments.
function vim.on_key(fn, ns_id)
if fn == nil and ns_id == nil then
- return #on_key_cbs
+ return vim.tbl_count(on_key_cbs)
end
vim.validate({
@@ -573,16 +695,14 @@ function vim._on_key(char)
end
end
---- Generate a list of possible completions for the string.
---- String starts with ^ and then has the pattern.
+--- Generates a list of possible completions for the string.
+--- String has the pattern.
---
--- 1. Can we get it to just return things in the global namespace with that name prefix
--- 2. Can we get it to return things from global namespace even with `print(` in front.
function vim._expand_pat(pat, env)
env = env or _G
- pat = string.sub(pat, 2, #pat)
-
if pat == '' then
local result = vim.tbl_keys(env)
table.sort(result)
@@ -643,7 +763,7 @@ function vim._expand_pat(pat, env)
local mt = getmetatable(final_env)
if mt and type(mt.__index) == 'table' then
field = rawget(mt.__index, key)
- elseif final_env == vim and vim._submodules[key] then
+ elseif final_env == vim and (vim._submodules[key] or vim._extra[key]) then
field = vim[key]
end
end
@@ -655,7 +775,6 @@ function vim._expand_pat(pat, env)
end
local keys = {}
- ---@private
local function insert_keys(obj)
for k, _ in pairs(obj) do
if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then
@@ -673,6 +792,7 @@ function vim._expand_pat(pat, env)
end
if final_env == vim then
insert_keys(vim._submodules)
+ insert_keys(vim._extra)
end
keys = vim.tbl_keys(keys)
@@ -744,25 +864,80 @@ vim._expand_pat_get_parts = function(lua_string)
return parts, search_index
end
----Prints given arguments in human-readable format.
----Example:
----<pre>lua
---- -- Print highlight group Normal and store it's contents in a variable.
---- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true))
----</pre>
----@see |vim.inspect()|
----@return any # given arguments.
+do
+ -- Ideally we should just call complete() inside omnifunc, though there are
+ -- some bugs, so fake the two-step dance for now.
+ local matches
+
+ --- Omnifunc for completing Lua values from the runtime Lua interpreter,
+ --- similar to the builtin completion for the `:lua` command.
+ ---
+ --- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a Lua buffer.
+ function vim.lua_omnifunc(find_start, _)
+ if find_start == 1 then
+ local line = vim.api.nvim_get_current_line()
+ local prefix = string.sub(line, 1, vim.api.nvim_win_get_cursor(0)[2])
+ local pos
+ matches, pos = vim._expand_pat(prefix)
+ return (#matches > 0 and pos) or -1
+ else
+ return matches
+ end
+ end
+end
+
+---@private
function vim.pretty_print(...)
- local objects = {}
+ vim.deprecate('vim.pretty_print', 'vim.print', '0.10')
+ return vim.print(...)
+end
+
+--- "Pretty prints" the given arguments and returns them unmodified.
+---
+--- Example:
+---
+--- ```lua
+--- local hl_normal = vim.print(vim.api.nvim_get_hl(0, { name = 'Normal' }))
+--- ```
+---
+--- @see |vim.inspect()|
+--- @see |:=|
+--- @return any # given arguments.
+function vim.print(...)
+ if vim.in_fast_event() then
+ print(...)
+ return ...
+ end
+
for i = 1, select('#', ...) do
- local v = select(i, ...)
- table.insert(objects, vim.inspect(v))
+ local o = select(i, ...)
+ if type(o) == 'string' then
+ vim.api.nvim_out_write(o)
+ else
+ vim.api.nvim_out_write(vim.inspect(o, { newline = '\n', indent = ' ' }))
+ end
+ vim.api.nvim_out_write('\n')
end
- print(table.concat(objects, ' '))
return ...
end
+--- Translates keycodes.
+---
+--- Example:
+---
+--- ```lua
+--- local k = vim.keycode
+--- vim.g.mapleader = k'<bs>'
+--- ```
+---
+--- @param str string String to be converted.
+--- @return string
+--- @see |nvim_replace_termcodes()|
+function vim.keycode(str)
+ return vim.api.nvim_replace_termcodes(str, true, true, true)
+end
+
function vim._cs_remote(rcid, server_addr, connect_error, args)
local function connection_failure_errmsg(consequence)
local explanation
@@ -791,7 +966,7 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
or subcmd == 'tab-wait'
or subcmd == 'tab-wait-silent'
then
- return { errmsg = 'E5600: Wait commands not yet implemented in nvim' }
+ return { errmsg = 'E5600: Wait commands not yet implemented in Nvim' }
elseif subcmd == 'tab-silent' then
f_tab = true
f_silent = true
@@ -799,14 +974,14 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
if rcid == 0 then
return { errmsg = connection_failure_errmsg('Send failed.') }
end
- vim.fn.rpcrequest(rcid, 'nvim_input', args[2])
+ vim.rpcrequest(rcid, 'nvim_input', args[2])
return { should_exit = true, tabbed = false }
elseif subcmd == 'expr' then
if rcid == 0 then
return { errmsg = connection_failure_errmsg('Send expression failed.') }
end
- print(vim.fn.rpcrequest(rcid, 'nvim_eval', args[2]))
- return { should_exit = true, tabbed = false }
+ local res = tostring(vim.rpcrequest(rcid, 'nvim_eval', args[2]))
+ return { result = res, should_exit = true, tabbed = false }
elseif subcmd ~= '' then
return { errmsg = 'Unknown option argument: ' .. args[1] }
end
@@ -833,67 +1008,37 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
}
end
---- Display a deprecation notification to the user.
+--- Shows a deprecation message to the user.
---
----@param name string Deprecated function.
----@param alternative string|nil Preferred alternative function.
----@param version string Version in which the deprecated function will
---- be removed.
----@param plugin string|nil Plugin name that the function will be removed
---- from. Defaults to "Nvim".
+---@param name string Deprecated feature (function, API, etc.).
+---@param alternative string|nil Suggested alternative feature.
+---@param version string Version when the deprecated function will be removed.
+---@param plugin string|nil Name of the plugin that owns the deprecated feature.
+--- Defaults to "Nvim".
---@param backtrace boolean|nil Prints backtrace. Defaults to true.
+---
+---@return string|nil # Deprecated message, or nil if no message was shown.
function vim.deprecate(name, alternative, version, plugin, backtrace)
- local message = name .. ' is deprecated'
+ local msg = ('%s is deprecated'):format(name)
plugin = plugin or 'Nvim'
- message = alternative and (message .. ', use ' .. alternative .. ' instead.') or message
- message = message
- .. ' See :h deprecated\nThis function will be removed in '
- .. plugin
- .. ' version '
- .. version
- if vim.notify_once(message, vim.log.levels.WARN) and backtrace ~= false then
+ msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or msg
+ msg = ('%s%s\nThis feature will be removed in %s version %s'):format(
+ msg,
+ (plugin == 'Nvim' and ' :help deprecated' or ''),
+ plugin,
+ version
+ )
+ local displayed = vim.notify_once(msg, vim.log.levels.WARN)
+ if displayed and backtrace ~= false then
vim.notify(debug.traceback('', 2):sub(2), vim.log.levels.WARN)
end
+ return displayed and msg or nil
end
---- Create builtin mappings (incl. menus).
---- Called once on startup.
-function vim._init_default_mappings()
- -- mappings
-
- --@private
- local function map(mode, lhs, rhs)
- vim.api.nvim_set_keymap(mode, lhs, rhs, { noremap = true, desc = 'Nvim builtin' })
- end
-
- map('n', 'Y', 'y$')
- -- Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O>. #17473
- map('n', '<C-L>', '<Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>')
- map('i', '<C-U>', '<C-G>u<C-U>')
- map('i', '<C-W>', '<C-G>u<C-W>')
- map('x', '*', 'y/\\V<C-R>"<CR>')
- map('x', '#', 'y?\\V<C-R>"<CR>')
- -- Use : instead of <Cmd> so that ranges are supported. #19365
- map('n', '&', ':&&<CR>')
-
- -- menus
-
- -- TODO VimScript, no l10n
- vim.cmd([[
- aunmenu *
- vnoremenu PopUp.Cut "+x
- vnoremenu PopUp.Copy "+y
- anoremenu PopUp.Paste "+gP
- vnoremenu PopUp.Paste "+P
- vnoremenu PopUp.Delete "_x
- nnoremenu PopUp.Select\ All ggVG
- vnoremenu PopUp.Select\ All gg0oG$
- inoremenu PopUp.Select\ All <C-Home><C-O>VG
- anoremenu PopUp.-1- <Nop>
- anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR>
- ]])
-end
+require('vim._options')
-require('vim._meta')
+-- Remove at Nvim 1.0
+---@deprecated
+vim.loop = vim.uv
return vim
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
index e3a442af5e..4a961970cc 100644
--- a/runtime/lua/vim/_init_packages.lua
+++ b/runtime/lua/vim/_init_packages.lua
@@ -51,7 +51,14 @@ end
-- builtin functions which always should be available
require('vim.shared')
-vim._submodules = { inspect = true }
+vim._submodules = {
+ inspect = true,
+ version = true,
+ fs = true,
+ iter = true,
+ re = true,
+ text = true,
+}
-- These are for loading runtime modules in the vim namespace lazily.
setmetatable(vim, {
diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index f46a525910..3f7b9d2c23 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -2,7 +2,7 @@
---@field syntax boolean include syntax based highlight groups (defaults to true)
---@field treesitter boolean include treesitter based highlight groups (defaults to true)
---@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
----@field semantic_tokens boolean include semantic tokens (defaults to true)
+---@field semantic_tokens boolean include semantic token highlights (defaults to true)
local defaults = {
syntax = true,
treesitter = true,
@@ -14,15 +14,15 @@ local defaults = {
---
---Can also be pretty-printed with `:Inspect!`. *:Inspect!*
---
----@param bufnr? number defaults to the current buffer
----@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
----@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
+---@param bufnr? integer defaults to the current buffer
+---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
+---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
---@param filter? InspectorFilter (table|nil) a table with key-value pairs to filter the items
--- - syntax (boolean): include syntax based highlight groups (defaults to true)
--- - treesitter (boolean): include treesitter based highlight groups (defaults to true)
--- - extmarks (boolean|"all"): include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
--- - semantic_tokens (boolean): include semantic tokens (defaults to true)
----@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:number,col:number,row:number} (table) a table with the following key-value pairs. Items are in "traversal order":
+---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:integer,col:integer,row:integer} (table) a table with the following key-value pairs. Items are in "traversal order":
--- - treesitter: a list of treesitter captures
--- - syntax: a list of syntax groups
--- - semantic_tokens: a list of semantic tokens
@@ -56,7 +56,6 @@ function vim.inspect_pos(bufnr, row, col, filter)
}
-- resolve hl links
- ---@private
local function resolve_hl(data)
if data.hl_group then
local hlid = vim.api.nvim_get_hl_id_by_name(data.hl_group)
@@ -69,59 +68,67 @@ function vim.inspect_pos(bufnr, row, col, filter)
-- treesitter
if filter.treesitter then
for _, capture in pairs(vim.treesitter.get_captures_at_pos(bufnr, row, col)) do
- capture.hl_group = '@' .. capture.capture
- table.insert(results.treesitter, resolve_hl(capture))
+ capture.hl_group = '@' .. capture.capture .. '.' .. capture.lang
+ results.treesitter[#results.treesitter + 1] = resolve_hl(capture)
end
end
-- syntax
- if filter.syntax then
- for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
- table.insert(results.syntax, resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') }))
- end
+ if filter.syntax and vim.api.nvim_buf_is_valid(bufnr) then
+ vim.api.nvim_buf_call(bufnr, function()
+ for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do
+ results.syntax[#results.syntax + 1] =
+ resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') })
+ end
+ end)
end
- -- semantic tokens
+ -- namespace id -> name map
+ local nsmap = {}
+ for name, id in pairs(vim.api.nvim_get_namespaces()) do
+ nsmap[id] = name
+ end
+
+ --- Convert an extmark tuple into a table
+ local function to_map(extmark)
+ extmark = {
+ id = extmark[1],
+ row = extmark[2],
+ col = extmark[3],
+ opts = resolve_hl(extmark[4]),
+ }
+ extmark.ns_id = extmark.opts.ns_id
+ extmark.ns = nsmap[extmark.ns_id] or ''
+ extmark.end_row = extmark.opts.end_row or extmark.row -- inclusive
+ extmark.end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive
+ return extmark
+ end
+
+ --- Check if an extmark overlaps this position
+ local function is_here(extmark)
+ return (row >= extmark.row and row <= extmark.end_row) -- within the rows of the extmark
+ and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col
+ and (row < extmark.end_row or col < extmark.end_col) -- either not in the last row or in range of the col
+ end
+
+ -- all extmarks at this position
+ local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, 0, -1, { details = true })
+ extmarks = vim.tbl_map(to_map, extmarks)
+ extmarks = vim.tbl_filter(is_here, extmarks)
+
if filter.semantic_tokens then
- for _, token in ipairs(vim.lsp.semantic_tokens.get_at_pos(bufnr, row, col) or {}) do
- token.hl_groups = {
- type = resolve_hl({ hl_group = '@' .. token.type }),
- modifiers = vim.tbl_map(function(modifier)
- return resolve_hl({ hl_group = '@' .. modifier })
- end, token.modifiers or {}),
- }
- table.insert(results.semantic_tokens, token)
- end
+ results.semantic_tokens = vim.tbl_filter(function(extmark)
+ return extmark.ns:find('vim_lsp_semantic_tokens') == 1
+ end, extmarks)
end
- -- extmarks
if filter.extmarks then
- for ns, nsid in pairs(vim.api.nvim_get_namespaces()) do
- if ns:find('vim_lsp_semantic_tokens') ~= 1 then
- local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, nsid, 0, -1, { details = true })
- for _, extmark in ipairs(extmarks) do
- extmark = {
- ns_id = nsid,
- ns = ns,
- id = extmark[1],
- row = extmark[2],
- col = extmark[3],
- opts = resolve_hl(extmark[4]),
- }
- local end_row = extmark.opts.end_row or extmark.row -- inclusive
- local end_col = extmark.opts.end_col or (extmark.col + 1) -- exclusive
- if
- (filter.extmarks == 'all' or extmark.opts.hl_group) -- filter hl_group
- and (row >= extmark.row and row <= end_row) -- within the rows of the extmark
- and (row > extmark.row or col >= extmark.col) -- either not the first row, or in range of the col
- and (row < end_row or col < end_col) -- either not in the last row or in range of the col
- then
- table.insert(results.extmarks, extmark)
- end
- end
- end
- end
+ results.extmarks = vim.tbl_filter(function(extmark)
+ return extmark.ns:find('vim_lsp_semantic_tokens') ~= 1
+ and (filter.extmarks == 'all' or extmark.opts.hl_group)
+ end, extmarks)
end
+
return results
end
@@ -129,26 +136,23 @@ end
---
---Can also be shown with `:Inspect`. *:Inspect*
---
----@param bufnr? number defaults to the current buffer
----@param row? number row to inspect, 0-based. Defaults to the row of the current cursor
----@param col? number col to inspect, 0-based. Defaults to the col of the current cursor
+---@param bufnr? integer defaults to the current buffer
+---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
+---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
---@param filter? InspectorFilter (table|nil) see |vim.inspect_pos()|
function vim.show_pos(bufnr, row, col, filter)
local items = vim.inspect_pos(bufnr, row, col, filter)
local lines = { {} }
- ---@private
local function append(str, hl)
table.insert(lines[#lines], { str, hl })
end
- ---@private
local function nl()
table.insert(lines, {})
end
- ---@private
local function item(data, comment)
append(' - ')
append(data.hl_group, data.hl_group)
@@ -174,16 +178,17 @@ function vim.show_pos(bufnr, row, col, filter)
nl()
end
+ -- semantic tokens
if #items.semantic_tokens > 0 then
append('Semantic Tokens', 'Title')
nl()
- for _, token in ipairs(items.semantic_tokens) do
- local client = vim.lsp.get_client_by_id(token.client_id)
- client = client and (' (' .. client.name .. ')') or ''
- item(token.hl_groups.type, 'type' .. client)
- for _, modifier in ipairs(token.hl_groups.modifiers) do
- item(modifier, 'modifier' .. client)
- end
+ local sorted_marks = vim.fn.sort(items.semantic_tokens, function(left, right)
+ local left_first = left.opts.priority < right.opts.priority
+ or left.opts.priority == right.opts.priority and left.opts.hl_group < right.opts.hl_group
+ return left_first and -1 or 1
+ end)
+ for _, extmark in ipairs(sorted_marks) do
+ item(extmark.opts, 'priority: ' .. extmark.opts.priority)
end
nl()
end
@@ -197,6 +202,7 @@ function vim.show_pos(bufnr, row, col, filter)
end
nl()
end
+
-- extmarks
if #items.extmarks > 0 then
append('Extmarks', 'Title')
diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua
index 104f29c4c0..e3b99f6b3d 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -1,540 +1,32 @@
-local a = vim.api
-
--- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
--- Can be done in a separate PR.
-local key_value_options = {
- fillchars = true,
- fcs = true,
- listchars = true,
- lcs = true,
- winhighlight = true,
- winhl = true,
-}
-
---- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
----@return string
-local function get_option_metatype(name, info)
- if info.type == 'string' then
- if info.flaglist then
- return 'set'
- elseif info.commalist then
- if key_value_options[name] then
- return 'map'
- end
- return 'array'
- end
- return 'string'
- end
- return info.type
-end
-
-local options_info = setmetatable({}, {
- __index = function(t, k)
- local info = a.nvim_get_option_info(k)
- info.metatype = get_option_metatype(k, info)
- rawset(t, k, info)
- return rawget(t, k)
- end,
-})
-
-vim.env = setmetatable({}, {
- __index = function(_, k)
- local v = vim.fn.getenv(k)
- if v == vim.NIL then
- return nil
- end
- return v
- end,
-
- __newindex = function(_, k, v)
- vim.fn.setenv(k, v)
- end,
-})
-
-local function opt_validate(option_name, target_scope)
- local scope = options_info[option_name].scope
- if scope ~= target_scope then
- local scope_to_string = { buf = 'buffer', win = 'window' }
- error(
- string.format(
- [['%s' is a %s option, not a %s option. See ":help %s"]],
- option_name,
- scope_to_string[scope] or scope,
- scope_to_string[target_scope] or target_scope,
- option_name
- )
- )
- end
-end
-
-local function new_opt_accessor(handle, scope)
- return setmetatable({}, {
- __index = function(_, k)
- if handle == nil and type(k) == 'number' then
- return new_opt_accessor(k, scope)
- end
- opt_validate(k, scope)
- return a.nvim_get_option_value(k, { [scope] = handle or 0 })
- end,
-
- __newindex = function(_, k, v)
- opt_validate(k, scope)
- return a.nvim_set_option_value(k, v, { [scope] = handle or 0 })
- end,
- })
-end
-
-vim.bo = new_opt_accessor(nil, 'buf')
-vim.wo = new_opt_accessor(nil, 'win')
-
--- vim global option
--- this ONLY sets the global option. like `setglobal`
-vim.go = setmetatable({}, {
- __index = function(_, k)
- return a.nvim_get_option_value(k, { scope = 'global' })
- end,
- __newindex = function(_, k, v)
- return a.nvim_set_option_value(k, v, { scope = 'global' })
- end,
-})
-
--- vim `set` style options.
--- it has no additional metamethod magic.
-vim.o = setmetatable({}, {
- __index = function(_, k)
- return a.nvim_get_option_value(k, {})
- end,
- __newindex = function(_, k, v)
- return a.nvim_set_option_value(k, v, {})
- end,
-})
-
----@brief [[
---- vim.opt, vim.opt_local and vim.opt_global implementation
----
---- To be used as helpers for working with options within neovim.
---- For information on how to use, see :help vim.opt
----
----@brief ]]
-
---- Preserves the order and does not mutate the original list
-local function remove_duplicate_values(t)
- local result, seen = {}, {}
- for _, v in ipairs(t) do
- if not seen[v] then
- table.insert(result, v)
- end
-
- seen[v] = true
- end
-
- return result
-end
-
--- Check whether the OptionTypes is allowed for vim.opt
--- If it does not match, throw an error which indicates which option causes the error.
-local function assert_valid_value(name, value, types)
- local type_of_value = type(value)
- for _, valid_type in ipairs(types) do
- if valid_type == type_of_value then
- return
- end
- end
-
- error(
- string.format(
- "Invalid option type '%s' for '%s', should be %s",
- type_of_value,
- name,
- table.concat(types, ' or ')
- )
- )
-end
-
-local function passthrough(_, x)
- return x
-end
-
-local function tbl_merge(left, right)
- return vim.tbl_extend('force', left, right)
-end
-
-local function tbl_remove(t, value)
- if type(value) == 'string' then
- t[value] = nil
- else
- for _, v in ipairs(value) do
- t[v] = nil
- end
- end
-
- return t
-end
-
-local valid_types = {
- boolean = { 'boolean' },
- number = { 'number' },
- string = { 'string' },
- set = { 'string', 'table' },
- array = { 'string', 'table' },
- map = { 'string', 'table' },
-}
-
--- Map of functions to take a Lua style value and convert to vimoption_T style value.
--- Each function takes (info, lua_value) -> vim_value
-local to_vim_value = {
- boolean = passthrough,
- number = passthrough,
- string = passthrough,
-
- set = function(info, value)
- if type(value) == 'string' then
- return value
- end
-
- if info.flaglist and info.commalist then
- local keys = {}
- for k, v in pairs(value) do
- if v then
- table.insert(keys, k)
- end
- end
-
- table.sort(keys)
- return table.concat(keys, ',')
- else
- local result = ''
- for k, v in pairs(value) do
- if v then
- result = result .. k
- end
- end
-
- return result
- end
- end,
-
- array = function(info, value)
- if type(value) == 'string' then
- return value
- end
- if not info.allows_duplicates then
- value = remove_duplicate_values(value)
- end
- return table.concat(value, ',')
- end,
-
- map = function(_, value)
- if type(value) == 'string' then
- return value
- end
-
- local result = {}
- for opt_key, opt_value in pairs(value) do
- table.insert(result, string.format('%s:%s', opt_key, opt_value))
- end
-
- table.sort(result)
- return table.concat(result, ',')
- end,
-}
-
---- Convert a lua value to a vimoption_T value
-local function convert_value_to_vim(name, info, value)
- if value == nil then
- return vim.NIL
- end
-
- assert_valid_value(name, value, valid_types[info.metatype])
-
- return to_vim_value[info.metatype](info, value)
-end
-
--- Map of OptionType to functions that take vimoption_T values and convert to lua values.
--- Each function takes (info, vim_value) -> lua_value
-local to_lua_value = {
- boolean = passthrough,
- number = passthrough,
- string = passthrough,
-
- array = function(info, value)
- if type(value) == 'table' then
- if not info.allows_duplicates then
- value = remove_duplicate_values(value)
- end
-
- return value
- end
-
- -- Empty strings mean that there is nothing there,
- -- so empty table should be returned.
- if value == '' then
- return {}
- end
-
- -- Handles unescaped commas in a list.
- if string.find(value, ',,,') then
- local left, right = unpack(vim.split(value, ',,,'))
-
- local result = {}
- vim.list_extend(result, vim.split(left, ','))
- table.insert(result, ',')
- vim.list_extend(result, vim.split(right, ','))
-
- table.sort(result)
-
- return result
- end
-
- if string.find(value, ',^,,', 1, true) then
- local left, right = unpack(vim.split(value, ',^,,', true))
-
- local result = {}
- vim.list_extend(result, vim.split(left, ','))
- table.insert(result, '^,')
- vim.list_extend(result, vim.split(right, ','))
-
- table.sort(result)
-
- return result
- end
-
- return vim.split(value, ',')
- end,
-
- set = function(info, value)
- if type(value) == 'table' then
- return value
- end
-
- -- Empty strings mean that there is nothing there,
- -- so empty table should be returned.
- if value == '' then
- return {}
- end
-
- assert(info.flaglist, 'That is the only one I know how to handle')
-
- if info.flaglist and info.commalist then
- local split_value = vim.split(value, ',')
- local result = {}
- for _, v in ipairs(split_value) do
- result[v] = true
- end
-
- return result
- else
- local result = {}
- for i = 1, #value do
- result[value:sub(i, i)] = true
- end
-
- return result
- end
- end,
-
- map = function(info, raw_value)
- if type(raw_value) == 'table' then
- return raw_value
- end
-
- assert(info.commalist, 'Only commas are supported currently')
-
- local result = {}
-
- local comma_split = vim.split(raw_value, ',')
- for _, key_value_str in ipairs(comma_split) do
- local key, value = unpack(vim.split(key_value_str, ':'))
- key = vim.trim(key)
-
- result[key] = value
- end
-
- return result
- end,
-}
-
---- Converts a vimoption_T style value to a Lua value
-local function convert_value_to_lua(info, option_value)
- return to_lua_value[info.metatype](info, option_value)
-end
-
-local prepend_methods = {
- number = function()
- error("The '^' operator is not currently supported for")
- end,
-
- string = function(left, right)
- return right .. left
- end,
-
- array = function(left, right)
- for i = #right, 1, -1 do
- table.insert(left, 1, right[i])
- end
-
- return left
- end,
-
- map = tbl_merge,
- set = tbl_merge,
-}
-
---- Handles the '^' operator
-local function prepend_value(info, current, new)
- return prepend_methods[info.metatype](
- convert_value_to_lua(info, current),
- convert_value_to_lua(info, new)
- )
-end
-
-local add_methods = {
- number = function(left, right)
- return left + right
- end,
-
- string = function(left, right)
- return left .. right
- end,
-
- array = function(left, right)
- for _, v in ipairs(right) do
- table.insert(left, v)
- end
-
- return left
- end,
-
- map = tbl_merge,
- set = tbl_merge,
-}
-
---- Handles the '+' operator
-local function add_value(info, current, new)
- return add_methods[info.metatype](
- convert_value_to_lua(info, current),
- convert_value_to_lua(info, new)
- )
-end
-
-local function remove_one_item(t, val)
- if vim.tbl_islist(t) then
- local remove_index = nil
- for i, v in ipairs(t) do
- if v == val then
- remove_index = i
- end
- end
-
- if remove_index then
- table.remove(t, remove_index)
- end
- else
- t[val] = nil
- end
-end
-
-local remove_methods = {
- number = function(left, right)
- return left - right
- end,
-
- string = function()
- error('Subtraction not supported for strings.')
- end,
-
- array = function(left, right)
- if type(right) == 'string' then
- remove_one_item(left, right)
- else
- for _, v in ipairs(right) do
- remove_one_item(left, v)
- end
- end
-
- return left
- end,
-
- map = tbl_remove,
- set = tbl_remove,
-}
-
---- Handles the '-' operator
-local function remove_value(info, current, new)
- return remove_methods[info.metatype](convert_value_to_lua(info, current), new)
-end
-
-local function create_option_accessor(scope)
- local option_mt
-
- local function make_option(name, value)
- local info = assert(options_info[name], 'Not a valid option name: ' .. name)
-
- if type(value) == 'table' and getmetatable(value) == option_mt then
- assert(name == value._name, "must be the same value, otherwise that's weird.")
-
- value = value._value
- end
-
- return setmetatable({
- _name = name,
- _value = value,
- _info = info,
- }, option_mt)
- end
-
- option_mt = {
- -- To set a value, instead use:
- -- opt[my_option] = value
- _set = function(self)
- local value = convert_value_to_vim(self._name, self._info, self._value)
- a.nvim_set_option_value(self._name, value, { scope = scope })
- end,
-
- get = function(self)
- return convert_value_to_lua(self._info, self._value)
- end,
-
- append = function(self, right)
- self._value = add_value(self._info, self._value, right)
- self:_set()
- end,
-
- __add = function(self, right)
- return make_option(self._name, add_value(self._info, self._value, right))
- end,
-
- prepend = function(self, right)
- self._value = prepend_value(self._info, self._value, right)
- self:_set()
- end,
-
- __pow = function(self, right)
- return make_option(self._name, prepend_value(self._info, self._value, right))
- end,
-
- remove = function(self, right)
- self._value = remove_value(self._info, self._value, right)
- self:_set()
- end,
-
- __sub = function(self, right)
- return make_option(self._name, remove_value(self._info, self._value, right))
- end,
- }
- option_mt.__index = option_mt
-
- return setmetatable({}, {
- __index = function(_, k)
- return make_option(k, a.nvim_get_option_value(k, {}))
- end,
-
- __newindex = function(_, k, v)
- make_option(k, v):_set()
- end,
- })
-end
-
-vim.opt = create_option_accessor()
-vim.opt_local = create_option_accessor('local')
-vim.opt_global = create_option_accessor('global')
+--- @meta
+
+---@type uv
+vim.uv = ...
+
+--- The following modules are loaded specially in _init_packages.lua
+
+vim.F = require('vim.F')
+vim._watch = require('vim._watch')
+vim.diagnostic = require('vim.diagnostic')
+vim.filetype = require('vim.filetype')
+vim.fs = require('vim.fs')
+vim.func = require('vim.func')
+vim.health = require('vim.health')
+vim.highlight = require('vim.highlight')
+vim.iter = require('vim.iter')
+vim.keymap = require('vim.keymap')
+vim.loader = require('vim.loader')
+vim.lsp = require('vim.lsp')
+vim.re = require('vim.re')
+vim.secure = require('vim.secure')
+vim.snippet = require('vim.snippet')
+vim.treesitter = require('vim.treesitter')
+vim.ui = require('vim.ui')
+vim.version = require('vim.version')
+
+local uri = require('vim.uri')
+
+vim.uri_from_fname = uri.uri_from_fname
+vim.uri_from_bufnr = uri.uri_from_bufnr
+vim.uri_to_fname = uri.uri_to_fname
+vim.uri_to_bufnr = uri.uri_to_bufnr
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
new file mode 100644
index 0000000000..49269ba631
--- /dev/null
+++ b/runtime/lua/vim/_meta/api.lua
@@ -0,0 +1,2102 @@
+--- @meta _
+-- THIS FILE IS GENERATED
+-- DO NOT EDIT
+error('Cannot require a meta file')
+
+vim.api = {}
+
+--- @private
+--- @param buffer integer
+--- @param keys boolean
+--- @param dot boolean
+--- @return string
+function vim.api.nvim__buf_debug_extmarks(buffer, keys, dot) end
+
+--- @private
+--- @param buffer integer
+--- @param first integer
+--- @param last integer
+function vim.api.nvim__buf_redraw_range(buffer, first, last) end
+
+--- @private
+--- @param buffer integer
+--- @return table<string,any>
+function vim.api.nvim__buf_stats(buffer) end
+
+--- @private
+--- @return string
+function vim.api.nvim__get_lib_dir() end
+
+--- @private
+--- Find files in runtime directories
+---
+--- @param pat any[] pattern of files to search for
+--- @param all boolean whether to return all matches or only the first
+--- @param opts vim.api.keyset.runtime is_lua: only search Lua subdirs
+--- @return string[]
+function vim.api.nvim__get_runtime(pat, all, opts) end
+
+--- @private
+--- Returns object given as argument.
+--- This API function is used for testing. One should not rely on its presence
+--- in plugins.
+---
+--- @param obj any Object to return.
+--- @return any
+function vim.api.nvim__id(obj) end
+
+--- @private
+--- Returns array given as argument.
+--- This API function is used for testing. One should not rely on its presence
+--- in plugins.
+---
+--- @param arr any[] Array to return.
+--- @return any[]
+function vim.api.nvim__id_array(arr) end
+
+--- @private
+--- Returns dictionary given as argument.
+--- This API function is used for testing. One should not rely on its presence
+--- in plugins.
+---
+--- @param dct table<string,any> Dictionary to return.
+--- @return table<string,any>
+function vim.api.nvim__id_dictionary(dct) end
+
+--- @private
+--- Returns floating-point value given as argument.
+--- This API function is used for testing. One should not rely on its presence
+--- in plugins.
+---
+--- @param flt number Value to return.
+--- @return number
+function vim.api.nvim__id_float(flt) end
+
+--- @private
+--- @param grid integer
+--- @param row integer
+--- @param col integer
+--- @return any[]
+function vim.api.nvim__inspect_cell(grid, row, col) end
+
+--- @private
+--- For testing. The condition in schar_cache_clear_if_full is hard to reach,
+--- so this function can be used to force a cache clear in a test.
+---
+function vim.api.nvim__invalidate_glyph_cache() end
+
+--- @private
+--- @return any[]
+function vim.api.nvim__runtime_inspect() end
+
+--- @private
+--- @param path string
+function vim.api.nvim__screenshot(path) end
+
+--- @private
+--- Gets internal stats.
+---
+--- @return table<string,any>
+function vim.api.nvim__stats() end
+
+--- @private
+--- @param str string
+--- @return any
+function vim.api.nvim__unpack(str) end
+
+--- Adds a highlight to buffer.
+--- Useful for plugins that dynamically generate highlights to a buffer (like
+--- a semantic highlighter or linter). The function adds a single highlight to
+--- a buffer. Unlike `matchaddpos()` highlights follow changes to line
+--- numbering (as lines are inserted/removed above the highlighted line), like
+--- signs and marks do.
+--- Namespaces are used for batch deletion/updating of a set of highlights. To
+--- create a namespace, use `nvim_create_namespace()` which returns a
+--- namespace id. Pass it in to this function as `ns_id` to add highlights to
+--- the namespace. All highlights in the same namespace can then be cleared
+--- with single call to `nvim_buf_clear_namespace()`. If the highlight never
+--- will be deleted by an API call, pass `ns_id = -1`.
+--- As a shorthand, `ns_id = 0` can be used to create a new namespace for the
+--- highlight, the allocated id is then returned. If `hl_group` is the empty
+--- string no highlight is added, but a new `ns_id` is still returned. This is
+--- supported for backwards compatibility, new code should use
+--- `nvim_create_namespace()` to create a new empty namespace.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer namespace to use or -1 for ungrouped highlight
+--- @param hl_group string Name of the highlight group to use
+--- @param line integer Line to highlight (zero-indexed)
+--- @param col_start integer Start of (byte-indexed) column range to highlight
+--- @param col_end integer End of (byte-indexed) column range to highlight, or -1 to
+--- highlight to end of line
+--- @return integer
+function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) end
+
+--- Activates buffer-update events on a channel, or as Lua callbacks.
+--- Example (Lua): capture buffer updates in a global `events` variable (use
+--- "vim.print(events)" to see its contents):
+---
+--- ```lua
+--- events = {}
+--- vim.api.nvim_buf_attach(0, false, {
+--- on_lines = function(...)
+--- table.insert(events, {...})
+--- end,
+--- })
+--- ```
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param send_buffer boolean True if the initial notification should contain the
+--- whole buffer: first notification will be
+--- `nvim_buf_lines_event`. Else the first notification
+--- will be `nvim_buf_changedtick_event`. Not for Lua
+--- callbacks.
+--- @param opts table<string,function> Optional parameters.
+--- • on_lines: Lua callback invoked on change. Return `true` to detach. Args:
+--- • the string "lines"
+--- • buffer handle
+--- • b:changedtick
+--- • first line that changed (zero-indexed)
+--- • last line that was changed
+--- • last line in the updated range
+--- • byte count of previous contents
+--- • deleted_codepoints (if `utf_sizes` is true)
+--- • deleted_codeunits (if `utf_sizes` is true)
+---
+--- • on_bytes: Lua callback invoked on change. This
+--- callback receives more granular information about the
+--- change compared to on_lines. Return `true` to detach. Args:
+--- • the string "bytes"
+--- • buffer handle
+--- • b:changedtick
+--- • start row of the changed text (zero-indexed)
+--- • start column of the changed text
+--- • byte offset of the changed text (from the start of
+--- the buffer)
+--- • old end row of the changed text
+--- • old end column of the changed text
+--- • old end byte length of the changed text
+--- • new end row of the changed text
+--- • new end column of the changed text
+--- • new end byte length of the changed text
+---
+--- • on_changedtick: Lua callback invoked on changedtick
+--- increment without text change. Args:
+--- • the string "changedtick"
+--- • buffer handle
+--- • b:changedtick
+---
+--- • on_detach: Lua callback invoked on detach. Args:
+--- • the string "detach"
+--- • buffer handle
+---
+--- • on_reload: Lua callback invoked on reload. The entire
+--- buffer content should be considered changed. Args:
+--- • the string "reload"
+--- • buffer handle
+---
+--- • utf_sizes: include UTF-32 and UTF-16 size of the
+--- replaced region, as args to `on_lines`.
+--- • preview: also attach to command preview (i.e.
+--- 'inccommand') events.
+--- @return boolean
+function vim.api.nvim_buf_attach(buffer, send_buffer, opts) end
+
+--- call a function with buffer as temporary current buffer
+--- This temporarily switches current buffer to "buffer". If the current
+--- window already shows "buffer", the window is not switched If a window
+--- inside the current tabpage (including a float) already shows the buffer
+--- One of these windows will be set as current window temporarily. Otherwise
+--- a temporary scratch window (called the "autocmd window" for historical
+--- reasons) will be used.
+--- This is useful e.g. to call Vimscript functions that only work with the
+--- current buffer/window currently, like `termopen()`.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param fun function Function to call inside the buffer (currently Lua callable
+--- only)
+--- @return any
+function vim.api.nvim_buf_call(buffer, fun) end
+
+--- @deprecated
+--- @param buffer integer
+--- @param ns_id integer
+--- @param line_start integer
+--- @param line_end integer
+function vim.api.nvim_buf_clear_highlight(buffer, ns_id, line_start, line_end) end
+
+--- Clears `namespace`d objects (highlights, `extmarks`, virtual text) from a
+--- region.
+--- Lines are 0-indexed. `api-indexing` To clear the namespace in the entire
+--- buffer, specify line_start=0 and line_end=-1.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer Namespace to clear, or -1 to clear all namespaces.
+--- @param line_start integer Start of range of lines to clear
+--- @param line_end integer End of range of lines to clear (exclusive) or -1 to
+--- clear to end of buffer.
+function vim.api.nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) end
+
+--- Creates a buffer-local command `user-commands`.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer.
+--- @param name string
+--- @param command any
+--- @param opts vim.api.keyset.user_command
+function vim.api.nvim_buf_create_user_command(buffer, name, command, opts) end
+
+--- Removes an `extmark`.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer Namespace id from `nvim_create_namespace()`
+--- @param id integer Extmark id
+--- @return boolean
+function vim.api.nvim_buf_del_extmark(buffer, ns_id, id) end
+
+--- Unmaps a buffer-local `mapping` for the given mode.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param mode string
+--- @param lhs string
+function vim.api.nvim_buf_del_keymap(buffer, mode, lhs) end
+
+--- Deletes a named mark in the buffer. See `mark-motions`.
+---
+--- @param buffer integer Buffer to set the mark on
+--- @param name string Mark name
+--- @return boolean
+function vim.api.nvim_buf_del_mark(buffer, name) end
+
+--- Delete a buffer-local user-defined command.
+--- Only commands created with `:command-buffer` or
+--- `nvim_buf_create_user_command()` can be deleted with this function.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer.
+--- @param name string Name of the command to delete.
+function vim.api.nvim_buf_del_user_command(buffer, name) end
+
+--- Removes a buffer-scoped (b:) variable
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param name string Variable name
+function vim.api.nvim_buf_del_var(buffer, name) end
+
+--- Deletes the buffer. See `:bwipeout`
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param opts table<string,any> Optional parameters. Keys:
+--- • force: Force deletion and ignore unsaved changes.
+--- • unload: Unloaded only, do not delete. See `:bunload`
+function vim.api.nvim_buf_delete(buffer, opts) end
+
+--- Gets a changed tick of a buffer
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @return integer
+function vim.api.nvim_buf_get_changedtick(buffer) end
+
+--- Gets a map of buffer-local `user-commands`.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param opts vim.api.keyset.get_commands Optional parameters. Currently not used.
+--- @return table<string,any>
+function vim.api.nvim_buf_get_commands(buffer, opts) end
+
+--- Gets the position (0-indexed) of an `extmark`.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer Namespace id from `nvim_create_namespace()`
+--- @param id integer Extmark id
+--- @param opts table<string,any> Optional parameters. Keys:
+--- • details: Whether to include the details dict
+--- • hl_name: Whether to include highlight group name instead
+--- of id, true if omitted
+--- @return integer[]
+function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
+
+--- Gets `extmarks` (including `signs`) in "traversal order" from a `charwise`
+--- region defined by buffer positions (inclusive, 0-indexed `api-indexing`).
+--- Region can be given as (row,col) tuples, or valid extmark ids (whose
+--- positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
+--- respectively, thus the following are equivalent:
+---
+--- ```lua
+--- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+--- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
+--- ```
+---
+--- If `end` is less than `start`, traversal works backwards. (Useful with
+--- `limit`, to get the first marks prior to a given position.)
+--- Note: when using extmark ranges (marks with a end_row/end_col position)
+--- the `overlap` option might be useful. Otherwise only the start position of
+--- an extmark will be considered.
+--- Example:
+---
+--- ```lua
+--- local api = vim.api
+--- local pos = api.nvim_win_get_cursor(0)
+--- local ns = api.nvim_create_namespace('my-plugin')
+--- -- Create new extmark at line 1, column 1.
+--- local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {})
+--- -- Create new extmark at line 3, column 1.
+--- local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {})
+--- -- Get extmarks only from line 3.
+--- local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+--- -- Get all marks in this buffer + namespace.
+--- local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+--- vim.print(ms)
+--- ```
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer Namespace id from `nvim_create_namespace()` or -1 for all
+--- namespaces
+--- @param start any Start of range: a 0-indexed (row, col) or valid extmark id
+--- (whose position defines the bound). `api-indexing`
+--- @param end_ any End of range (inclusive): a 0-indexed (row, col) or valid
+--- extmark id (whose position defines the bound).
+--- `api-indexing`
+--- @param opts vim.api.keyset.get_extmarks Optional parameters. Keys:
+--- • limit: Maximum number of marks to return
+--- • details: Whether to include the details dict
+--- • hl_name: Whether to include highlight group name instead
+--- of id, true if omitted
+--- • overlap: Also include marks which overlap the range, even
+--- if their start position is less than `start`
+--- • type: Filter marks by type: "highlight", "sign",
+--- "virt_text" and "virt_lines"
+--- @return any[]
+function vim.api.nvim_buf_get_extmarks(buffer, ns_id, start, end_, opts) end
+
+--- Gets a list of buffer-local `mapping` definitions.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param mode string Mode short-name ("n", "i", "v", ...)
+--- @return table<string,any>[]
+function vim.api.nvim_buf_get_keymap(buffer, mode) end
+
+--- Gets a line-range from the buffer.
+--- Indexing is zero-based, end-exclusive. Negative indices are interpreted as
+--- length+1+index: -1 refers to the index past the end. So to get the last
+--- element use start=-2 and end=-1.
+--- Out-of-bounds indices are clamped to the nearest valid value, unless
+--- `strict_indexing` is set.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param start integer First line index
+--- @param end_ integer Last line index, exclusive
+--- @param strict_indexing boolean Whether out-of-bounds should be an error.
+--- @return string[]
+function vim.api.nvim_buf_get_lines(buffer, start, end_, strict_indexing) end
+
+--- Returns a `(row,col)` tuple representing the position of the named mark.
+--- "End of line" column position is returned as `v:maxcol` (big number). See
+--- `mark-motions`.
+--- Marks are (1,0)-indexed. `api-indexing`
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param name string Mark name
+--- @return integer[]
+function vim.api.nvim_buf_get_mark(buffer, name) end
+
+--- Gets the full file name for the buffer
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @return string
+function vim.api.nvim_buf_get_name(buffer) end
+
+--- @deprecated
+--- @param buffer integer
+--- @return integer
+function vim.api.nvim_buf_get_number(buffer) end
+
+--- Returns the byte offset of a line (0-indexed). `api-indexing`
+--- Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
+--- 'fileformat' and 'fileencoding' are ignored. The line index just after the
+--- last line gives the total byte-count of the buffer. A final EOL byte is
+--- counted if it would be written, see 'eol'.
+--- Unlike `line2byte()`, throws error for out-of-bounds indexing. Returns -1
+--- for unloaded buffer.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param index integer Line index
+--- @return integer
+function vim.api.nvim_buf_get_offset(buffer, index) end
+
+--- @deprecated
+--- @param buffer integer
+--- @param name string
+--- @return any
+function vim.api.nvim_buf_get_option(buffer, name) end
+
+--- Gets a range from the buffer.
+--- This differs from `nvim_buf_get_lines()` in that it allows retrieving only
+--- portions of a line.
+--- Indexing is zero-based. Row indices are end-inclusive, and column indices
+--- are end-exclusive.
+--- Prefer `nvim_buf_get_lines()` when retrieving entire lines.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param start_row integer First line index
+--- @param start_col integer Starting column (byte offset) on first line
+--- @param end_row integer Last line index, inclusive
+--- @param end_col integer Ending column (byte offset) on last line, exclusive
+--- @param opts table<string,any> Optional parameters. Currently unused.
+--- @return string[]
+function vim.api.nvim_buf_get_text(buffer, start_row, start_col, end_row, end_col, opts) end
+
+--- Gets a buffer-scoped (b:) variable.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param name string Variable name
+--- @return any
+function vim.api.nvim_buf_get_var(buffer, name) end
+
+--- Checks if a buffer is valid and loaded. See `api-buffer` for more info
+--- about unloaded buffers.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @return boolean
+function vim.api.nvim_buf_is_loaded(buffer) end
+
+--- Checks if a buffer is valid.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @return boolean
+function vim.api.nvim_buf_is_valid(buffer) end
+
+--- Returns the number of lines in the given buffer.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @return integer
+function vim.api.nvim_buf_line_count(buffer) end
+
+--- Creates or updates an `extmark`.
+--- By default a new extmark is created when no id is passed in, but it is
+--- also possible to create a new mark by passing in a previously unused id or
+--- move an existing mark by passing in its id. The caller must then keep
+--- track of existing and unused ids itself. (Useful over RPC, to avoid
+--- waiting for the return value.)
+--- Using the optional arguments, it is possible to use this to highlight a
+--- range of text, and also to associate virtual text to the mark.
+--- If present, the position defined by `end_col` and `end_row` should be
+--- after the start position in order for the extmark to cover a range. An
+--- earlier end position is not an error, but then it behaves like an empty
+--- range (no highlighting).
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param ns_id integer Namespace id from `nvim_create_namespace()`
+--- @param line integer Line where to place the mark, 0-based. `api-indexing`
+--- @param col integer Column where to place the mark, 0-based. `api-indexing`
+--- @param opts vim.api.keyset.set_extmark Optional parameters.
+--- • id : id of the extmark to edit.
+--- • end_row : ending line of the mark, 0-based inclusive.
+--- • end_col : ending col of the mark, 0-based exclusive.
+--- • hl_group : name of the highlight group used to highlight
+--- this mark.
+--- • hl_eol : when true, for a multiline highlight covering the
+--- EOL of a line, continue the highlight for the rest of the
+--- screen line (just like for diff and cursorline highlight).
+--- • virt_text : virtual text to link to this mark. A list of
+--- [text, highlight] tuples, each representing a text chunk
+--- with specified highlight. `highlight` element can either
+--- be a single highlight group, or an array of multiple
+--- highlight groups that will be stacked (highest priority
+--- last). A highlight group can be supplied either as a
+--- string or as an integer, the latter which can be obtained
+--- using `nvim_get_hl_id_by_name()`.
+--- • virt_text_pos : position of virtual text. Possible values:
+--- • "eol": right after eol character (default).
+--- • "overlay": display over the specified column, without
+--- shifting the underlying text.
+--- • "right_align": display right aligned in the window.
+--- • "inline": display at the specified column, and shift the
+--- buffer text to the right as needed.
+---
+--- • virt_text_win_col : position the virtual text at a fixed
+--- window column (starting from the first text column of the
+--- screen line) instead of "virt_text_pos".
+--- • virt_text_hide : hide the virtual text when the background
+--- text is selected or hidden because of scrolling with
+--- 'nowrap' or 'smoothscroll'. Currently only affects
+--- "overlay" virt_text.
+--- • hl_mode : control how highlights are combined with the
+--- highlights of the text. Currently only affects virt_text
+--- highlights, but might affect `hl_group` in later versions.
+--- • "replace": only show the virt_text color. This is the
+--- default.
+--- • "combine": combine with background text color.
+--- • "blend": blend with background text color. Not supported
+--- for "inline" virt_text.
+---
+--- • virt_lines : virtual lines to add next to this mark This
+--- should be an array over lines, where each line in turn is
+--- an array over [text, highlight] tuples. In general, buffer
+--- and window options do not affect the display of the text.
+--- In particular 'wrap' and 'linebreak' options do not take
+--- effect, so the number of extra screen lines will always
+--- match the size of the array. However the 'tabstop' buffer
+--- option is still used for hard tabs. By default lines are
+--- placed below the buffer line containing the mark.
+--- • virt_lines_above: place virtual lines above instead.
+--- • virt_lines_leftcol: Place extmarks in the leftmost column
+--- of the window, bypassing sign and number columns.
+--- • ephemeral : for use with `nvim_set_decoration_provider()`
+--- callbacks. The mark will only be used for the current
+--- redraw cycle, and not be permantently stored in the
+--- buffer.
+--- • right_gravity : boolean that indicates the direction the
+--- extmark will be shifted in when new text is inserted (true
+--- for right, false for left). Defaults to true.
+--- • end_right_gravity : boolean that indicates the direction
+--- the extmark end position (if it exists) will be shifted in
+--- when new text is inserted (true for right, false for
+--- left). Defaults to false.
+--- • undo_restore : Restore the exact position of the mark if
+--- text around the mark was deleted and then restored by
+--- undo. Defaults to true.
+--- • invalidate : boolean that indicates whether to hide the
+--- extmark if the entirety of its range is deleted. If
+--- "undo_restore" is false, the extmark is deleted instead.
+--- • priority: a priority value for the highlight group or sign
+--- attribute. For example treesitter highlighting uses a
+--- value of 100.
+--- • strict: boolean that indicates extmark should not be
+--- placed if the line or column value is past the end of the
+--- buffer or end of the line respectively. Defaults to true.
+--- • sign_text: string of length 1-2 used to display in the
+--- sign column. Note: ranges are unsupported and decorations
+--- are only applied to start_row
+--- • sign_hl_group: name of the highlight group used to
+--- highlight the sign column text. Note: ranges are
+--- unsupported and decorations are only applied to start_row
+--- • number_hl_group: name of the highlight group used to
+--- highlight the number column. Note: ranges are unsupported
+--- and decorations are only applied to start_row
+--- • line_hl_group: name of the highlight group used to
+--- highlight the whole line. Note: ranges are unsupported and
+--- decorations are only applied to start_row
+--- • cursorline_hl_group: name of the highlight group used to
+--- highlight the line when the cursor is on the same line as
+--- the mark and 'cursorline' is enabled. Note: ranges are
+--- unsupported and decorations are only applied to start_row
+--- • conceal: string which should be either empty or a single
+--- character. Enable concealing similar to `:syn-conceal`.
+--- When a character is supplied it is used as `:syn-cchar`.
+--- "hl_group" is used as highlight for the cchar if provided,
+--- otherwise it defaults to `hl-Conceal`.
+--- • spell: boolean indicating that spell checking should be
+--- performed within this extmark
+--- • ui_watched: boolean that indicates the mark should be
+--- drawn by a UI. When set, the UI will receive win_extmark
+--- events. Note: the mark is positioned by virt_text
+--- attributes. Can be used together with virt_text.
+--- @return integer
+function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end
+
+--- Sets a buffer-local `mapping` for the given mode.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param mode string
+--- @param lhs string
+--- @param rhs string
+--- @param opts vim.api.keyset.keymap
+function vim.api.nvim_buf_set_keymap(buffer, mode, lhs, rhs, opts) end
+
+--- Sets (replaces) a line-range in the buffer.
+--- Indexing is zero-based, end-exclusive. Negative indices are interpreted as
+--- length+1+index: -1 refers to the index past the end. So to change or
+--- delete the last element use start=-2 and end=-1.
+--- To insert lines at a given index, set `start` and `end` to the same index.
+--- To delete a range of lines, set `replacement` to an empty array.
+--- Out-of-bounds indices are clamped to the nearest valid value, unless
+--- `strict_indexing` is set.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param start integer First line index
+--- @param end_ integer Last line index, exclusive
+--- @param strict_indexing boolean Whether out-of-bounds should be an error.
+--- @param replacement string[] Array of lines to use as replacement
+function vim.api.nvim_buf_set_lines(buffer, start, end_, strict_indexing, replacement) end
+
+--- Sets a named mark in the given buffer, all marks are allowed
+--- file/uppercase, visual, last change, etc. See `mark-motions`.
+--- Marks are (1,0)-indexed. `api-indexing`
+---
+--- @param buffer integer Buffer to set the mark on
+--- @param name string Mark name
+--- @param line integer Line number
+--- @param col integer Column/row number
+--- @param opts table<string,any> Optional parameters. Reserved for future use.
+--- @return boolean
+function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end
+
+--- Sets the full file name for a buffer
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param name string Buffer name
+function vim.api.nvim_buf_set_name(buffer, name) end
+
+--- @deprecated
+--- @param buffer integer
+--- @param name string
+--- @param value any
+function vim.api.nvim_buf_set_option(buffer, name, value) end
+
+--- Sets (replaces) a range in the buffer
+--- This is recommended over `nvim_buf_set_lines()` when only modifying parts
+--- of a line, as extmarks will be preserved on non-modified parts of the
+--- touched lines.
+--- Indexing is zero-based. Row indices are end-inclusive, and column indices
+--- are end-exclusive.
+--- To insert text at a given `(row, column)` location, use `start_row =
+--- end_row = row` and `start_col = end_col = col`. To delete the text in a
+--- range, use `replacement = {}`.
+--- Prefer `nvim_buf_set_lines()` if you are only adding or deleting entire
+--- lines.
+--- Prefer `nvim_put()` if you want to insert text at the cursor position.
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param start_row integer First line index
+--- @param start_col integer Starting column (byte offset) on first line
+--- @param end_row integer Last line index, inclusive
+--- @param end_col integer Ending column (byte offset) on last line, exclusive
+--- @param replacement string[] Array of lines to use as replacement
+function vim.api.nvim_buf_set_text(buffer, start_row, start_col, end_row, end_col, replacement) end
+
+--- Sets a buffer-scoped (b:) variable
+---
+--- @param buffer integer Buffer handle, or 0 for current buffer
+--- @param name string Variable name
+--- @param value any Variable value
+function vim.api.nvim_buf_set_var(buffer, name, value) end
+
+--- @deprecated
+--- @param buffer integer
+--- @param src_id integer
+--- @param line integer
+--- @param chunks any[]
+--- @param opts table<string,any>
+--- @return integer
+function vim.api.nvim_buf_set_virtual_text(buffer, src_id, line, chunks, opts) end
+
+--- Calls a Vimscript `Dictionary-function` with the given arguments.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
+--- @param dict any Dictionary, or String evaluating to a Vimscript `self` dict
+--- @param fn string Name of the function defined on the Vimscript dict
+--- @param args any[] Function arguments packed in an Array
+--- @return any
+function vim.api.nvim_call_dict_function(dict, fn, args) end
+
+--- Calls a Vimscript function with the given arguments.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
+--- @param fn string Function to call
+--- @param args any[] Function arguments packed in an Array
+--- @return any
+function vim.api.nvim_call_function(fn, args) end
+
+--- Send data to channel `id`. For a job, it writes it to the stdin of the
+--- process. For the stdio channel `channel-stdio`, it writes to Nvim's
+--- stdout. For an internal terminal instance (`nvim_open_term()`) it writes
+--- directly to terminal output. See `channel-bytes` for more information.
+--- This function writes raw data, not RPC messages. If the channel was
+--- created with `rpc=true` then the channel expects RPC messages, use
+--- `vim.rpcnotify()` and `vim.rpcrequest()` instead.
+---
+--- @param chan integer id of the channel
+--- @param data string data to write. 8-bit clean: can contain NUL bytes.
+function vim.api.nvim_chan_send(chan, data) end
+
+--- Clears all autocommands selected by {opts}. To delete autocmds see
+--- `nvim_del_autocmd()`.
+---
+--- @param opts vim.api.keyset.clear_autocmds Parameters
+--- • event: (string|table) Examples:
+--- • event: "pat1"
+--- • event: { "pat1" }
+--- • event: { "pat1", "pat2", "pat3" }
+---
+--- • pattern: (string|table)
+--- • pattern or patterns to match exactly.
+--- • For example, if you have `*.py` as that pattern for the
+--- autocmd, you must pass `*.py` exactly to clear it.
+--- `test.py` will not match the pattern.
+---
+--- • defaults to clearing all patterns.
+--- • NOTE: Cannot be used with {buffer}
+---
+--- • buffer: (bufnr)
+--- • clear only `autocmd-buflocal` autocommands.
+--- • NOTE: Cannot be used with {pattern}
+---
+--- • group: (string|int) The augroup name or id.
+--- • NOTE: If not passed, will only delete autocmds not in any group.
+function vim.api.nvim_clear_autocmds(opts) end
+
+--- Executes an Ex command.
+--- Unlike `nvim_command()` this command takes a structured Dictionary instead
+--- of a String. This allows for easier construction and manipulation of an Ex
+--- command. This also allows for things such as having spaces inside a
+--- command argument, expanding filenames in a command that otherwise doesn't
+--- expand filenames, etc. Command arguments may also be Number, Boolean or
+--- String.
+--- The first argument may also be used instead of count for commands that
+--- support it in order to make their usage simpler with `vim.cmd()`. For
+--- example, instead of `vim.cmd.bdelete{ count = 2 }`, you may do
+--- `vim.cmd.bdelete(2)`.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
+--- @param cmd vim.api.keyset.cmd Command to execute. Must be a Dictionary that can contain the
+--- same values as the return value of `nvim_parse_cmd()` except
+--- "addr", "nargs" and "nextcmd" which are ignored if provided.
+--- All values except for "cmd" are optional.
+--- @param opts vim.api.keyset.cmd_opts Optional parameters.
+--- • output: (boolean, default false) Whether to return command
+--- output.
+--- @return string
+function vim.api.nvim_cmd(cmd, opts) end
+
+--- Executes an Ex command.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+--- Prefer using `nvim_cmd()` or `nvim_exec2()` over this. To evaluate
+--- multiple lines of Vim script or an Ex command directly, use
+--- `nvim_exec2()`. To construct an Ex command using a structured format and
+--- then execute it, use `nvim_cmd()`. To modify an Ex command before
+--- evaluating it, use `nvim_parse_cmd()` in conjunction with `nvim_cmd()`.
+---
+--- @param command string Ex command string
+function vim.api.nvim_command(command) end
+
+--- @deprecated
+--- @param command string
+--- @return string
+function vim.api.nvim_command_output(command) end
+
+--- Create or get an autocommand group `autocmd-groups`.
+--- To get an existing group id, do:
+---
+--- ```lua
+--- local id = vim.api.nvim_create_augroup("MyGroup", {
+--- clear = false
+--- })
+--- ```
+---
+--- @param name string String: The name of the group
+--- @param opts vim.api.keyset.create_augroup Dictionary Parameters
+--- • clear (bool) optional: defaults to true. Clear existing
+--- commands if the group already exists `autocmd-groups`.
+--- @return integer
+function vim.api.nvim_create_augroup(name, opts) end
+
+--- Creates an `autocommand` event handler, defined by `callback` (Lua function or Vimscript function name string) or `command` (Ex command string).
+--- Example using Lua callback:
+---
+--- ```lua
+--- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+--- pattern = {"*.c", "*.h"},
+--- callback = function(ev)
+--- print(string.format('event fired: %s', vim.inspect(ev)))
+--- end
+--- })
+--- ```
+---
+--- Example using an Ex command as the handler:
+---
+--- ```lua
+--- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+--- pattern = {"*.c", "*.h"},
+--- command = "echo 'Entering a C or C++ file'",
+--- })
+--- ```
+---
+--- Note: `pattern` is NOT automatically expanded (unlike with `:autocmd`),
+--- thus names like "$HOME" and "~" must be expanded explicitly:
+---
+--- ```lua
+--- pattern = vim.fn.expand("~") .. "/some/path/*.py"
+--- ```
+---
+--- @param event any (string|array) Event(s) that will trigger the handler
+--- (`callback` or `command`).
+--- @param opts vim.api.keyset.create_autocmd Options dict:
+--- • group (string|integer) optional: autocommand group name or
+--- id to match against.
+--- • pattern (string|array) optional: pattern(s) to match
+--- literally `autocmd-pattern`.
+--- • buffer (integer) optional: buffer number for buffer-local
+--- autocommands `autocmd-buflocal`. Cannot be used with
+--- {pattern}.
+--- • desc (string) optional: description (for documentation and
+--- troubleshooting).
+--- • callback (function|string) optional: Lua function (or
+--- Vimscript function name, if string) called when the
+--- event(s) is triggered. Lua callback can return true to
+--- delete the autocommand, and receives a table argument with
+--- these keys:
+--- • id: (number) autocommand id
+--- • event: (string) name of the triggered event
+--- `autocmd-events`
+--- • group: (number|nil) autocommand group id, if any
+--- • match: (string) expanded value of `<amatch>`
+--- • buf: (number) expanded value of `<abuf>`
+--- • file: (string) expanded value of `<afile>`
+--- • data: (any) arbitrary data passed from
+--- `nvim_exec_autocmds()`
+---
+--- • command (string) optional: Vim command to execute on event.
+--- Cannot be used with {callback}
+--- • once (boolean) optional: defaults to false. Run the
+--- autocommand only once `autocmd-once`.
+--- • nested (boolean) optional: defaults to false. Run nested
+--- autocommands `autocmd-nested`.
+--- @return integer
+function vim.api.nvim_create_autocmd(event, opts) end
+
+--- Creates a new, empty, unnamed buffer.
+---
+--- @param listed boolean Sets 'buflisted'
+--- @param scratch boolean Creates a "throwaway" `scratch-buffer` for temporary work
+--- (always 'nomodified'). Also sets 'nomodeline' on the
+--- buffer.
+--- @return integer
+function vim.api.nvim_create_buf(listed, scratch) end
+
+--- Creates a new namespace or gets an existing one. *namespace*
+--- Namespaces are used for buffer highlights and virtual text, see
+--- `nvim_buf_add_highlight()` and `nvim_buf_set_extmark()`.
+--- Namespaces can be named or anonymous. If `name` matches an existing
+--- namespace, the associated id is returned. If `name` is an empty string a
+--- new, anonymous namespace is created.
+---
+--- @param name string Namespace name or empty string
+--- @return integer
+function vim.api.nvim_create_namespace(name) end
+
+--- Creates a global `user-commands` command.
+--- For Lua usage see `lua-guide-commands-create`.
+--- Example:
+---
+--- ```vim
+--- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+--- :SayHello
+--- Hello world!
+--- ```
+---
+--- @param name string Name of the new user command. Must begin with an uppercase
+--- letter.
+--- @param command any Replacement command to execute when this user command is
+--- executed. When called from Lua, the command can also be a
+--- Lua function. The function is called with a single table
+--- argument that contains the following keys:
+--- • name: (string) Command name
+--- • args: (string) The args passed to the command, if any
+--- `<args>`
+--- • fargs: (table) The args split by unescaped whitespace
+--- (when more than one argument is allowed), if any
+--- `<f-args>`
+--- • nargs: (string) Number of arguments `:command-nargs`
+--- • bang: (boolean) "true" if the command was executed with a
+--- ! modifier `<bang>`
+--- • line1: (number) The starting line of the command range
+--- `<line1>`
+--- • line2: (number) The final line of the command range
+--- `<line2>`
+--- • range: (number) The number of items in the command range:
+--- 0, 1, or 2 `<range>`
+--- • count: (number) Any count supplied `<count>`
+--- • reg: (string) The optional register, if specified `<reg>`
+--- • mods: (string) Command modifiers, if any `<mods>`
+--- • smods: (table) Command modifiers in a structured format.
+--- Has the same structure as the "mods" key of
+--- `nvim_parse_cmd()`.
+--- @param opts vim.api.keyset.user_command Optional `command-attributes`.
+--- • Set boolean attributes such as `:command-bang` or
+--- `:command-bar` to true (but not `:command-buffer`, use
+--- `nvim_buf_create_user_command()` instead).
+--- • "complete" `:command-complete` also accepts a Lua
+--- function which works like
+--- `:command-completion-customlist`.
+--- • Other parameters:
+--- • desc: (string) Used for listing the command when a Lua
+--- function is used for {command}.
+--- • force: (boolean, default true) Override any previous
+--- definition.
+--- • preview: (function) Preview callback for 'inccommand'
+--- `:command-preview`
+function vim.api.nvim_create_user_command(name, command, opts) end
+
+--- Delete an autocommand group by id.
+--- To get a group id one can use `nvim_get_autocmds()`.
+--- NOTE: behavior differs from `:augroup-delete`. When deleting a group,
+--- autocommands contained in this group will also be deleted and cleared.
+--- This group will no longer exist.
+---
+--- @param id integer Integer The id of the group.
+function vim.api.nvim_del_augroup_by_id(id) end
+
+--- Delete an autocommand group by name.
+--- NOTE: behavior differs from `:augroup-delete`. When deleting a group,
+--- autocommands contained in this group will also be deleted and cleared.
+--- This group will no longer exist.
+---
+--- @param name string String The name of the group.
+function vim.api.nvim_del_augroup_by_name(name) end
+
+--- Deletes an autocommand by id.
+---
+--- @param id integer Integer Autocommand id returned by `nvim_create_autocmd()`
+function vim.api.nvim_del_autocmd(id) end
+
+--- Deletes the current line.
+---
+function vim.api.nvim_del_current_line() end
+
+--- Unmaps a global `mapping` for the given mode.
+--- To unmap a buffer-local mapping, use `nvim_buf_del_keymap()`.
+---
+--- @param mode string
+--- @param lhs string
+function vim.api.nvim_del_keymap(mode, lhs) end
+
+--- Deletes an uppercase/file named mark. See `mark-motions`.
+---
+--- @param name string Mark name
+--- @return boolean
+function vim.api.nvim_del_mark(name) end
+
+--- Delete a user-defined command.
+---
+--- @param name string Name of the command to delete.
+function vim.api.nvim_del_user_command(name) end
+
+--- Removes a global (g:) variable.
+---
+--- @param name string Variable name
+function vim.api.nvim_del_var(name) end
+
+--- Echo a message.
+---
+--- @param chunks any[] A list of [text, hl_group] arrays, each representing a text
+--- chunk with specified highlight. `hl_group` element can be
+--- omitted for no highlight.
+--- @param history boolean if true, add to `message-history`.
+--- @param opts vim.api.keyset.echo_opts Optional parameters.
+--- • verbose: Message was printed as a result of 'verbose'
+--- option if Nvim was invoked with -V3log_file, the message
+--- will be redirected to the log_file and suppressed from
+--- direct output.
+function vim.api.nvim_echo(chunks, history, opts) end
+
+--- Writes a message to the Vim error buffer. Does not append "\n", the
+--- message is buffered (won't display) until a linefeed is written.
+---
+--- @param str string Message
+function vim.api.nvim_err_write(str) end
+
+--- Writes a message to the Vim error buffer. Appends "\n", so the buffer is
+--- flushed (and displayed).
+---
+--- @param str string Message
+function vim.api.nvim_err_writeln(str) end
+
+--- Evaluates a Vimscript `expression`. Dictionaries and Lists are recursively
+--- expanded.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
+--- @param expr string Vimscript expression string
+--- @return any
+function vim.api.nvim_eval(expr) end
+
+--- Evaluates statusline string.
+---
+--- @param str string Statusline string (see 'statusline').
+--- @param opts vim.api.keyset.eval_statusline Optional parameters.
+--- • winid: (number) `window-ID` of the window to use as context
+--- for statusline.
+--- • maxwidth: (number) Maximum width of statusline.
+--- • fillchar: (string) Character to fill blank spaces in the
+--- statusline (see 'fillchars'). Treated as single-width even
+--- if it isn't.
+--- • highlights: (boolean) Return highlight information.
+--- • use_winbar: (boolean) Evaluate winbar instead of statusline.
+--- • use_tabline: (boolean) Evaluate tabline instead of
+--- statusline. When true, {winid} is ignored. Mutually
+--- exclusive with {use_winbar}.
+--- • use_statuscol_lnum: (number) Evaluate statuscolumn for this
+--- line number instead of statusline.
+--- @return table<string,any>
+function vim.api.nvim_eval_statusline(str, opts) end
+
+--- @deprecated
+--- @param src string
+--- @param output boolean
+--- @return string
+function vim.api.nvim_exec(src, output) end
+
+--- Executes Vimscript (multiline block of Ex commands), like anonymous
+--- `:source`.
+--- Unlike `nvim_command()` this function supports heredocs, script-scope
+--- (s:), etc.
+--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
+--- @param src string Vimscript code
+--- @param opts vim.api.keyset.exec_opts Optional parameters.
+--- • output: (boolean, default false) Whether to capture and
+--- return all (non-error, non-shell `:!`) output.
+--- @return table<string,any>
+function vim.api.nvim_exec2(src, opts) end
+
+--- Execute all autocommands for {event} that match the corresponding {opts}
+--- `autocmd-execute`.
+---
+--- @param event any (String|Array) The event or events to execute
+--- @param opts vim.api.keyset.exec_autocmds Dictionary of autocommand options:
+--- • group (string|integer) optional: the autocommand group name
+--- or id to match against. `autocmd-groups`.
+--- • pattern (string|array) optional: defaults to "*"
+--- `autocmd-pattern`. Cannot be used with {buffer}.
+--- • buffer (integer) optional: buffer number
+--- `autocmd-buflocal`. Cannot be used with {pattern}.
+--- • modeline (bool) optional: defaults to true. Process the
+--- modeline after the autocommands `<nomodeline>`.
+--- • data (any): arbitrary data to send to the autocommand
+--- callback. See `nvim_create_autocmd()` for details.
+function vim.api.nvim_exec_autocmds(event, opts) end
+
+--- Sends input-keys to Nvim, subject to various quirks controlled by `mode`
+--- flags. This is a blocking call, unlike `nvim_input()`.
+--- On execution error: does not fail, but updates v:errmsg.
+--- To input sequences like <C-o> use `nvim_replace_termcodes()` (typically
+--- with escape_ks=false) to replace `keycodes`, then pass the result to
+--- nvim_feedkeys().
+--- Example:
+---
+--- ```vim
+--- :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+--- :call nvim_feedkeys(key, 'n', v:false)
+--- ```
+---
+--- @param keys string to be typed
+--- @param mode string behavior flags, see `feedkeys()`
+--- @param escape_ks boolean If true, escape K_SPECIAL bytes in `keys`. This should be
+--- false if you already used `nvim_replace_termcodes()`, and
+--- true otherwise.
+function vim.api.nvim_feedkeys(keys, mode, escape_ks) end
+
+--- Gets the option information for all options.
+--- The dictionary has the full option names as keys and option metadata
+--- dictionaries as detailed at `nvim_get_option_info2()`.
+---
+--- @return table<string,any>
+function vim.api.nvim_get_all_options_info() end
+
+--- Get all autocommands that match the corresponding {opts}.
+--- These examples will get autocommands matching ALL the given criteria:
+---
+--- ```lua
+--- -- Matches all criteria
+--- autocommands = vim.api.nvim_get_autocmds({
+--- group = "MyGroup",
+--- event = {"BufEnter", "BufWinEnter"},
+--- pattern = {"*.c", "*.h"}
+--- })
+---
+--- -- All commands from one group
+--- autocommands = vim.api.nvim_get_autocmds({
+--- group = "MyGroup",
+--- })
+--- ```
+---
+--- NOTE: When multiple patterns or events are provided, it will find all the
+--- autocommands that match any combination of them.
+---
+--- @param opts vim.api.keyset.get_autocmds Dictionary with at least one of the following:
+--- • group (string|integer): the autocommand group name or id to
+--- match against.
+--- • event (string|array): event or events to match against
+--- `autocmd-events`.
+--- • pattern (string|array): pattern or patterns to match against
+--- `autocmd-pattern`. Cannot be used with {buffer}
+--- • buffer: Buffer number or list of buffer numbers for buffer
+--- local autocommands `autocmd-buflocal`. Cannot be used with
+--- {pattern}
+--- @return any[]
+function vim.api.nvim_get_autocmds(opts) end
+
+--- Gets information about a channel.
+---
+--- @param chan integer
+--- @return table<string,any>
+function vim.api.nvim_get_chan_info(chan) end
+
+--- Returns the 24-bit RGB value of a `nvim_get_color_map()` color name or
+--- "#rrggbb" hexadecimal string.
+--- Example:
+---
+--- ```vim
+--- :echo nvim_get_color_by_name("Pink")
+--- :echo nvim_get_color_by_name("#cbcbcb")
+--- ```
+---
+--- @param name string Color name or "#rrggbb" string
+--- @return integer
+function vim.api.nvim_get_color_by_name(name) end
+
+--- Returns a map of color names and RGB values.
+--- Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values
+--- (e.g. 65535).
+---
+--- @return table<string,any>
+function vim.api.nvim_get_color_map() end
+
+--- Gets a map of global (non-buffer-local) Ex commands.
+--- Currently only `user-commands` are supported, not builtin Ex commands.
+---
+--- @param opts vim.api.keyset.get_commands Optional parameters. Currently only supports {"builtin":false}
+--- @return table<string,any>
+function vim.api.nvim_get_commands(opts) end
+
+--- Gets a map of the current editor state.
+---
+--- @param opts vim.api.keyset.context Optional parameters.
+--- • types: List of `context-types` ("regs", "jumps", "bufs",
+--- "gvars", …) to gather, or empty for "all".
+--- @return table<string,any>
+function vim.api.nvim_get_context(opts) end
+
+--- Gets the current buffer.
+---
+--- @return integer
+function vim.api.nvim_get_current_buf() end
+
+--- Gets the current line.
+---
+--- @return string
+function vim.api.nvim_get_current_line() end
+
+--- Gets the current tabpage.
+---
+--- @return integer
+function vim.api.nvim_get_current_tabpage() end
+
+--- Gets the current window.
+---
+--- @return integer
+function vim.api.nvim_get_current_win() end
+
+--- Gets all or specific highlight groups in a namespace.
+---
+--- @param ns_id integer Get highlight groups for namespace ns_id
+--- `nvim_get_namespaces()`. Use 0 to get global highlight groups
+--- `:highlight`.
+--- @param opts vim.api.keyset.get_highlight Options dict:
+--- • name: (string) Get a highlight definition by name.
+--- • id: (integer) Get a highlight definition by id.
+--- • link: (boolean, default true) Show linked group name
+--- instead of effective definition `:hi-link`.
+--- • create: (boolean, default true) When highlight group
+--- doesn't exist create it.
+--- @return table<string,any>
+function vim.api.nvim_get_hl(ns_id, opts) end
+
+--- @deprecated
+--- @param hl_id integer
+--- @param rgb boolean
+--- @return table<string,any>
+function vim.api.nvim_get_hl_by_id(hl_id, rgb) end
+
+--- @deprecated
+--- @param name string
+--- @param rgb boolean
+--- @return table<string,any>
+function vim.api.nvim_get_hl_by_name(name, rgb) end
+
+--- Gets a highlight group by name
+--- similar to `hlID()`, but allocates a new ID if not present.
+---
+--- @param name string
+--- @return integer
+function vim.api.nvim_get_hl_id_by_name(name) end
+
+--- Gets the active highlight namespace.
+---
+--- @param opts vim.api.keyset.get_ns Optional parameters
+--- • winid: (number) `window-ID` for retrieving a window's
+--- highlight namespace. A value of -1 is returned when
+--- `nvim_win_set_hl_ns()` has not been called for the window
+--- (or was called with a namespace of -1).
+--- @return integer
+function vim.api.nvim_get_hl_ns(opts) end
+
+--- Gets a list of global (non-buffer-local) `mapping` definitions.
+---
+--- @param mode string Mode short-name ("n", "i", "v", ...)
+--- @return table<string,any>[]
+function vim.api.nvim_get_keymap(mode) end
+
+--- Returns a `(row, col, buffer, buffername)` tuple representing the position
+--- of the uppercase/file named mark. "End of line" column position is
+--- returned as `v:maxcol` (big number). See `mark-motions`.
+--- Marks are (1,0)-indexed. `api-indexing`
+---
+--- @param name string Mark name
+--- @param opts table<string,any> Optional parameters. Reserved for future use.
+--- @return any[]
+function vim.api.nvim_get_mark(name, opts) end
+
+--- Gets the current mode. `mode()` "blocking" is true if Nvim is waiting for
+--- input.
+---
+--- @return table<string,any>
+function vim.api.nvim_get_mode() end
+
+--- Gets existing, non-anonymous `namespace`s.
+---
+--- @return table<string,any>
+function vim.api.nvim_get_namespaces() end
+
+--- @deprecated
+--- @param name string
+--- @return any
+function vim.api.nvim_get_option(name) end
+
+--- @deprecated
+--- @param name string
+--- @return table<string,any>
+function vim.api.nvim_get_option_info(name) end
+
+--- Gets the option information for one option from arbitrary buffer or window
+--- Resulting dictionary has keys:
+--- • name: Name of the option (like 'filetype')
+--- • shortname: Shortened name of the option (like 'ft')
+--- • type: type of option ("string", "number" or "boolean")
+--- • default: The default value for the option
+--- • was_set: Whether the option was set.
+--- • last_set_sid: Last set script id (if any)
+--- • last_set_linenr: line number where option was set
+--- • last_set_chan: Channel where option was set (0 for local)
+--- • scope: one of "global", "win", or "buf"
+--- • global_local: whether win or buf option has a global value
+--- • commalist: List of comma separated values
+--- • flaglist: List of single char flags
+---
+--- When {scope} is not provided, the last set information applies to the
+--- local value in the current buffer or window if it is available, otherwise
+--- the global value information is returned. This behavior can be disabled by
+--- explicitly specifying {scope} in the {opts} table.
+---
+--- @param name string Option name
+--- @param opts vim.api.keyset.option Optional parameters
+--- • scope: One of "global" or "local". Analogous to `:setglobal`
+--- and `:setlocal`, respectively.
+--- • win: `window-ID`. Used for getting window local options.
+--- • buf: Buffer number. Used for getting buffer local options.
+--- Implies {scope} is "local".
+--- @return table<string,any>
+function vim.api.nvim_get_option_info2(name, opts) end
+
+--- Gets the value of an option. The behavior of this function matches that of
+--- `:set`: the local value of an option is returned if it exists; otherwise,
+--- the global value is returned. Local values always correspond to the
+--- current buffer or window, unless "buf" or "win" is set in {opts}.
+---
+--- @param name string Option name
+--- @param opts vim.api.keyset.option Optional parameters
+--- • scope: One of "global" or "local". Analogous to `:setglobal`
+--- and `:setlocal`, respectively.
+--- • win: `window-ID`. Used for getting window local options.
+--- • buf: Buffer number. Used for getting buffer local options.
+--- Implies {scope} is "local".
+--- • filetype: `filetype`. Used to get the default option for a
+--- specific filetype. Cannot be used with any other option.
+--- Note: this will trigger `ftplugin` and all `FileType`
+--- autocommands for the corresponding filetype.
+--- @return any
+function vim.api.nvim_get_option_value(name, opts) end
+
+--- Gets info describing process `pid`.
+---
+--- @param pid integer
+--- @return any
+function vim.api.nvim_get_proc(pid) end
+
+--- Gets the immediate children of process `pid`.
+---
+--- @param pid integer
+--- @return any[]
+function vim.api.nvim_get_proc_children(pid) end
+
+--- Find files in runtime directories
+--- "name" can contain wildcards. For example
+--- nvim_get_runtime_file("colors/*.vim", true) will return all color scheme
+--- files. Always use forward slashes (/) in the search pattern for
+--- subdirectories regardless of platform.
+--- It is not an error to not find any files. An empty array is returned then.
+---
+--- @param name string pattern of files to search for
+--- @param all boolean whether to return all matches or only the first
+--- @return string[]
+function vim.api.nvim_get_runtime_file(name, all) end
+
+--- Gets a global (g:) variable.
+---
+--- @param name string Variable name
+--- @return any
+function vim.api.nvim_get_var(name) end
+
+--- Gets a v: variable.
+---
+--- @param name string Variable name
+--- @return any
+function vim.api.nvim_get_vvar(name) end
+
+--- Queues raw user-input. Unlike `nvim_feedkeys()`, this uses a low-level
+--- input buffer and the call is non-blocking (input is processed
+--- asynchronously by the eventloop).
+--- On execution error: does not fail, but updates v:errmsg.
+---
+--- @param keys string to be typed
+--- @return integer
+function vim.api.nvim_input(keys) end
+
+--- Send mouse event from GUI.
+--- Non-blocking: does not wait on any result, but queues the event to be
+--- processed soon by the event loop.
+---
+--- @param button string Mouse button: one of "left", "right", "middle", "wheel",
+--- "move".
+--- @param action string For ordinary buttons, one of "press", "drag", "release".
+--- For the wheel, one of "up", "down", "left", "right".
+--- Ignored for "move".
+--- @param modifier string String of modifiers each represented by a single char. The
+--- same specifiers are used as for a key press, except that
+--- the "-" separator is optional, so "C-A-", "c-a" and "CA"
+--- can all be used to specify Ctrl+Alt+click.
+--- @param grid integer Grid number if the client uses `ui-multigrid`, else 0.
+--- @param row integer Mouse row-position (zero-based, like redraw events)
+--- @param col integer Mouse column-position (zero-based, like redraw events)
+function vim.api.nvim_input_mouse(button, action, modifier, grid, row, col) end
+
+--- Gets the current list of buffer handles
+--- Includes unlisted (unloaded/deleted) buffers, like `:ls!`. Use
+--- `nvim_buf_is_loaded()` to check if a buffer is loaded.
+---
+--- @return integer[]
+function vim.api.nvim_list_bufs() end
+
+--- Get information about all open channels.
+---
+--- @return any[]
+function vim.api.nvim_list_chans() end
+
+--- Gets the paths contained in `runtime-search-path`.
+---
+--- @return string[]
+function vim.api.nvim_list_runtime_paths() end
+
+--- Gets the current list of tabpage handles.
+---
+--- @return integer[]
+function vim.api.nvim_list_tabpages() end
+
+--- Gets a list of dictionaries representing attached UIs.
+---
+--- @return any[]
+function vim.api.nvim_list_uis() end
+
+--- Gets the current list of window handles.
+---
+--- @return integer[]
+function vim.api.nvim_list_wins() end
+
+--- Sets the current editor state from the given `context` map.
+---
+--- @param dict table<string,any> `Context` map.
+--- @return any
+function vim.api.nvim_load_context(dict) end
+
+--- Notify the user with a message
+--- Relays the call to vim.notify . By default forwards your message in the
+--- echo area but can be overridden to trigger desktop notifications.
+---
+--- @param msg string Message to display to the user
+--- @param log_level integer The log level
+--- @param opts table<string,any> Reserved for future use.
+--- @return any
+function vim.api.nvim_notify(msg, log_level, opts) end
+
+--- Open a terminal instance in a buffer
+--- By default (and currently the only option) the terminal will not be
+--- connected to an external process. Instead, input send on the channel will
+--- be echoed directly by the terminal. This is useful to display ANSI
+--- terminal sequences returned as part of a rpc message, or similar.
+--- Note: to directly initiate the terminal using the right size, display the
+--- buffer in a configured window before calling this. For instance, for a
+--- floating display, first create an empty buffer using `nvim_create_buf()`,
+--- then display it using `nvim_open_win()`, and then call this function. Then
+--- `nvim_chan_send()` can be called immediately to process sequences in a
+--- virtual terminal having the intended size.
+---
+--- @param buffer integer the buffer to use (expected to be empty)
+--- @param opts table<string,function> Optional parameters.
+--- • on_input: Lua callback for input sent, i e keypresses in
+--- terminal mode. Note: keypresses are sent raw as they would
+--- be to the pty master end. For instance, a carriage return
+--- is sent as a "\r", not as a "\n". `textlock` applies. It
+--- is possible to call `nvim_chan_send()` directly in the
+--- callback however. ["input", term, bufnr, data]
+--- @return integer
+function vim.api.nvim_open_term(buffer, opts) end
+
+--- Open a new window.
+--- Currently this is used to open floating and external windows. Floats are
+--- windows that are drawn above the split layout, at some anchor position in
+--- some other window. Floats can be drawn internally or by external GUI with
+--- the `ui-multigrid` extension. External windows are only supported with
+--- multigrid GUIs, and are displayed as separate top-level windows.
+--- For a general overview of floats, see `api-floatwin`.
+--- Exactly one of `external` and `relative` must be specified. The `width`
+--- and `height` of the new window must be specified.
+--- With relative=editor (row=0,col=0) refers to the top-left corner of the
+--- screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
+--- corner. Fractional values are allowed, but the builtin implementation
+--- (used by non-multigrid UIs) will always round down to nearest integer.
+--- Out-of-bounds values, and configurations that make the float not fit
+--- inside the main editor, are allowed. The builtin implementation truncates
+--- values so floats are fully within the main screen grid. External GUIs
+--- could let floats hover outside of the main window like a tooltip, but this
+--- should not be used to specify arbitrary WM screen positions.
+--- Example (Lua): window-relative float
+---
+--- ```lua
+--- vim.api.nvim_open_win(0, false,
+--- {relative='win', row=3, col=3, width=12, height=3})
+--- ```
+---
+--- Example (Lua): buffer-relative float (travels as buffer is scrolled)
+---
+--- ```lua
+--- vim.api.nvim_open_win(0, false,
+--- {relative='win', width=12, height=3, bufpos={100,10}})
+--- })
+--- ```
+---
+--- @param buffer integer Buffer to display, or 0 for current buffer
+--- @param enter boolean Enter the window (make it the current window)
+--- @param config vim.api.keyset.float_config Map defining the window configuration. Keys:
+--- • relative: Sets the window layout to "floating", placed at
+--- (row,col) coordinates relative to:
+--- • "editor" The global editor grid
+--- • "win" Window given by the `win` field, or current
+--- window.
+--- • "cursor" Cursor position in current window.
+--- • "mouse" Mouse position
+---
+--- • win: `window-ID` for relative="win".
+--- • anchor: Decides which corner of the float to place at
+--- (row,col):
+--- • "NW" northwest (default)
+--- • "NE" northeast
+--- • "SW" southwest
+--- • "SE" southeast
+---
+--- • width: Window width (in character cells). Minimum of 1.
+--- • height: Window height (in character cells). Minimum of 1.
+--- • bufpos: Places float relative to buffer text (only when
+--- relative="win"). Takes a tuple of zero-indexed [line,
+--- column]. `row` and `col` if given are applied relative to this position, else they
+--- default to:
+--- • `row=1` and `col=0` if `anchor` is "NW" or "NE"
+--- • `row=0` and `col=0` if `anchor` is "SW" or "SE" (thus
+--- like a tooltip near the buffer text).
+---
+--- • row: Row position in units of "screen cell height", may be
+--- fractional.
+--- • col: Column position in units of "screen cell width", may
+--- be fractional.
+--- • focusable: Enable focus by user actions (wincmds, mouse
+--- events). Defaults to true. Non-focusable windows can be
+--- entered by `nvim_set_current_win()`.
+--- • external: GUI should display the window as an external
+--- top-level window. Currently accepts no other positioning
+--- configuration together with this.
+--- • zindex: Stacking order. floats with higher `zindex` go on top on floats with lower indices. Must be larger
+--- than zero. The following screen elements have hard-coded
+--- z-indices:
+--- • 100: insert completion popupmenu
+--- • 200: message scrollback
+--- • 250: cmdline completion popupmenu (when
+--- wildoptions+=pum) The default value for floats are 50.
+--- In general, values below 100 are recommended, unless
+--- there is a good reason to overshadow builtin elements.
+---
+--- • style: (optional) Configure the appearance of the window.
+--- Currently only supports one value:
+--- • "minimal" Nvim will display the window with many UI
+--- options disabled. This is useful when displaying a
+--- temporary float where the text should not be edited.
+--- Disables 'number', 'relativenumber', 'cursorline',
+--- 'cursorcolumn', 'foldcolumn', 'spell' and 'list'
+--- options. 'signcolumn' is changed to `auto` and
+--- 'colorcolumn' is cleared. 'statuscolumn' is changed to
+--- empty. The end-of-buffer region is hidden by setting
+--- `eob` flag of 'fillchars' to a space char, and clearing
+--- the `hl-EndOfBuffer` region in 'winhighlight'.
+---
+--- • border: Style of (optional) window border. This can either
+--- be a string or an array. The string values are
+--- • "none": No border (default).
+--- • "single": A single line box.
+--- • "double": A double line box.
+--- • "rounded": Like "single", but with rounded corners ("╭"
+--- etc.).
+--- • "solid": Adds padding by a single whitespace cell.
+--- • "shadow": A drop shadow effect by blending with the
+--- background.
+--- • If it is an array, it should have a length of eight or
+--- any divisor of eight. The array will specify the eight
+--- chars building up the border in a clockwise fashion
+--- starting with the top-left corner. As an example, the
+--- double box style could be specified as [ "╔", "═" ,"╗",
+--- "║", "╝", "═", "╚", "║" ]. If the number of chars are
+--- less than eight, they will be repeated. Thus an ASCII
+--- border could be specified as [ "/", "-", "\\", "|" ], or
+--- all chars the same as [ "x" ]. An empty string can be
+--- used to turn off a specific border, for instance, [ "",
+--- "", "", ">", "", "", "", "<" ] will only make vertical
+--- borders but not horizontal ones. By default,
+--- `FloatBorder` highlight is used, which links to
+--- `WinSeparator` when not defined. It could also be
+--- specified by character: [ ["+", "MyCorner"], ["x",
+--- "MyBorder"] ].
+---
+--- • title: Title (optional) in window border, string or list.
+--- List should consist of `[text, highlight]` tuples. If
+--- string, the default highlight group is `FloatTitle`.
+--- • title_pos: Title position. Must be set with `title`
+--- option. Value can be one of "left", "center", or "right".
+--- Default is `"left"`.
+--- • footer: Footer (optional) in window border, string or
+--- list. List should consist of `[text, highlight]` tuples.
+--- If string, the default highlight group is `FloatFooter`.
+--- • footer_pos: Footer position. Must be set with `footer`
+--- option. Value can be one of "left", "center", or "right".
+--- Default is `"left"`.
+--- • noautocmd: If true then no buffer-related autocommand
+--- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may
+--- fire from calling this function.
+--- • fixed: If true when anchor is NW or SW, the float window
+--- would be kept fixed even if the window would be truncated.
+--- • hide: If true the floating window will be hidden.
+--- @return integer
+function vim.api.nvim_open_win(buffer, enter, config) end
+
+--- Writes a message to the Vim output buffer. Does not append "\n", the
+--- message is buffered (won't display) until a linefeed is written.
+---
+--- @param str string Message
+function vim.api.nvim_out_write(str) end
+
+--- Parse command line.
+--- Doesn't check the validity of command arguments.
+---
+--- @param str string Command line string to parse. Cannot contain "\n".
+--- @param opts table<string,any> Optional parameters. Reserved for future use.
+--- @return table<string,any>
+function vim.api.nvim_parse_cmd(str, opts) end
+
+--- Parse a Vimscript expression.
+---
+--- @param expr string Expression to parse. Always treated as a single line.
+--- @param flags string Flags:
+--- • "m" if multiple expressions in a row are allowed (only
+--- the first one will be parsed),
+--- • "E" if EOC tokens are not allowed (determines whether
+--- they will stop parsing process or be recognized as an
+--- operator/space, though also yielding an error).
+--- • "l" when needing to start parsing with lvalues for
+--- ":let" or ":for". Common flag sets:
+--- • "m" to parse like for ":echo".
+--- • "E" to parse like for "<C-r>=".
+--- • empty string for ":call".
+--- • "lm" to parse for ":let".
+--- @param highlight boolean If true, return value will also include "highlight" key
+--- containing array of 4-tuples (arrays) (Integer, Integer,
+--- Integer, String), where first three numbers define the
+--- highlighted region and represent line, starting column
+--- and ending column (latter exclusive: one should highlight
+--- region [start_col, end_col)).
+--- @return table<string,any>
+function vim.api.nvim_parse_expression(expr, flags, highlight) end
+
+--- Pastes at cursor, in any mode.
+--- Invokes the `vim.paste` handler, which handles each mode appropriately.
+--- Sets redo/undo. Faster than `nvim_input()`. Lines break at LF ("\n").
+--- Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err`
+--- but do not affect the return value (which is strictly decided by
+--- `vim.paste()`). On error, subsequent calls are ignored ("drained") until
+--- the next paste is initiated (phase 1 or -1).
+---
+--- @param data string Multiline input. May be binary (containing NUL bytes).
+--- @param crlf boolean Also break lines at CR and CRLF.
+--- @param phase integer -1: paste in a single call (i.e. without streaming). To
+--- "stream" a paste, call `nvim_paste` sequentially with these `phase` values:
+--- • 1: starts the paste (exactly once)
+--- • 2: continues the paste (zero or more times)
+--- • 3: ends the paste (exactly once)
+--- @return boolean
+function vim.api.nvim_paste(data, crlf, phase) end
+
+--- Puts text at cursor, in any mode.
+--- Compare `:put` and `p` which are always linewise.
+---
+--- @param lines string[] `readfile()`-style list of lines. `channel-lines`
+--- @param type string Edit behavior: any `getregtype()` result, or:
+--- • "b" `blockwise-visual` mode (may include width, e.g. "b3")
+--- • "c" `charwise` mode
+--- • "l" `linewise` mode
+--- • "" guess by contents, see `setreg()`
+--- @param after boolean If true insert after cursor (like `p`), or before (like
+--- `P`).
+--- @param follow boolean If true place cursor at end of inserted text.
+function vim.api.nvim_put(lines, type, after, follow) end
+
+--- Replaces terminal codes and `keycodes` (<CR>, <Esc>, ...) in a string with
+--- the internal representation.
+---
+--- @param str string String to be converted.
+--- @param from_part boolean Legacy Vim parameter. Usually true.
+--- @param do_lt boolean Also translate <lt>. Ignored if `special` is false.
+--- @param special boolean Replace `keycodes`, e.g. <CR> becomes a "\r" char.
+--- @return string
+function vim.api.nvim_replace_termcodes(str, from_part, do_lt, special) end
+
+--- Selects an item in the completion popup menu.
+--- If neither `ins-completion` nor `cmdline-completion` popup menu is active
+--- this API call is silently ignored. Useful for an external UI using
+--- `ui-popupmenu` to control the popup menu with the mouse. Can also be used
+--- in a mapping; use <Cmd> `:map-cmd` or a Lua mapping to ensure the mapping
+--- doesn't end completion mode.
+---
+--- @param item integer Index (zero-based) of the item to select. Value of -1
+--- selects nothing and restores the original text.
+--- @param insert boolean For `ins-completion`, whether the selection should be
+--- inserted in the buffer. Ignored for `cmdline-completion`.
+--- @param finish boolean Finish the completion and dismiss the popup menu. Implies
+--- {insert}.
+--- @param opts table<string,any> Optional parameters. Reserved for future use.
+function vim.api.nvim_select_popupmenu_item(item, insert, finish, opts) end
+
+--- Sets the current buffer.
+---
+--- @param buffer integer Buffer handle
+function vim.api.nvim_set_current_buf(buffer) end
+
+--- Changes the global working directory.
+---
+--- @param dir string Directory path
+function vim.api.nvim_set_current_dir(dir) end
+
+--- Sets the current line.
+---
+--- @param line string Line contents
+function vim.api.nvim_set_current_line(line) end
+
+--- Sets the current tabpage.
+---
+--- @param tabpage integer Tabpage handle
+function vim.api.nvim_set_current_tabpage(tabpage) end
+
+--- Sets the current window.
+---
+--- @param window integer Window handle
+function vim.api.nvim_set_current_win(window) end
+
+--- Set or change decoration provider for a `namespace`
+--- This is a very general purpose interface for having Lua callbacks being
+--- triggered during the redraw code.
+--- The expected usage is to set `extmarks` for the currently redrawn buffer.
+--- `nvim_buf_set_extmark()` can be called to add marks on a per-window or
+--- per-lines basis. Use the `ephemeral` key to only use the mark for the
+--- current screen redraw (the callback will be called again for the next
+--- redraw).
+--- Note: this function should not be called often. Rather, the callbacks
+--- themselves can be used to throttle unneeded callbacks. the `on_start`
+--- callback can return `false` to disable the provider until the next redraw.
+--- Similarly, return `false` in `on_win` will skip the `on_lines` calls for
+--- that window (but any extmarks set in `on_win` will still be used). A
+--- plugin managing multiple sources of decoration should ideally only set one
+--- provider, and merge the sources internally. You can use multiple `ns_id`
+--- for the extmarks set/modified inside the callback anyway.
+--- Note: doing anything other than setting extmarks is considered
+--- experimental. Doing things like changing options are not explicitly
+--- forbidden, but is likely to have unexpected consequences (such as 100% CPU
+--- consumption). doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is
+--- quite dubious for the moment.
+--- Note: It is not allowed to remove or update extmarks in 'on_line'
+--- callbacks.
+---
+--- @param ns_id integer Namespace id from `nvim_create_namespace()`
+--- @param opts vim.api.keyset.set_decoration_provider Table of callbacks:
+--- • on_start: called first on each screen redraw ["start",
+--- tick]
+--- • on_buf: called for each buffer being redrawn (before window
+--- callbacks) ["buf", bufnr, tick]
+--- • on_win: called when starting to redraw a specific window.
+--- botline_guess is an approximation that does not exceed the
+--- last line number. ["win", winid, bufnr, topline,
+--- botline_guess]
+--- • on_line: called for each buffer line being redrawn. (The
+--- interaction with fold lines is subject to change) ["win",
+--- winid, bufnr, row]
+--- • on_end: called at the end of a redraw cycle ["end", tick]
+function vim.api.nvim_set_decoration_provider(ns_id, opts) end
+
+--- Sets a highlight group.
+---
+--- @param ns_id integer Namespace id for this highlight `nvim_create_namespace()`.
+--- Use 0 to set a highlight group globally `:highlight`.
+--- Highlights from non-global namespaces are not active by
+--- default, use `nvim_set_hl_ns()` or `nvim_win_set_hl_ns()` to
+--- activate them.
+--- @param name string Highlight group name, e.g. "ErrorMsg"
+--- @param val vim.api.keyset.highlight Highlight definition map, accepts the following keys:
+--- • fg (or foreground): color name or "#RRGGBB", see note.
+--- • bg (or background): color name or "#RRGGBB", see note.
+--- • sp (or special): color name or "#RRGGBB"
+--- • blend: integer between 0 and 100
+--- • bold: boolean
+--- • standout: boolean
+--- • underline: boolean
+--- • undercurl: boolean
+--- • underdouble: boolean
+--- • underdotted: boolean
+--- • underdashed: boolean
+--- • strikethrough: boolean
+--- • italic: boolean
+--- • reverse: boolean
+--- • nocombine: boolean
+--- • link: name of another highlight group to link to, see
+--- `:hi-link`.
+--- • default: Don't override existing definition `:hi-default`
+--- • ctermfg: Sets foreground of cterm color `ctermfg`
+--- • ctermbg: Sets background of cterm color `ctermbg`
+--- • cterm: cterm attribute map, like `highlight-args`. If not
+--- set, cterm attributes will match those from the attribute
+--- map documented above.
+--- • force: if true force update the highlight group when it
+--- exists.
+function vim.api.nvim_set_hl(ns_id, name, val) end
+
+--- Set active namespace for highlights defined with `nvim_set_hl()`. This can
+--- be set for a single window, see `nvim_win_set_hl_ns()`.
+---
+--- @param ns_id integer the namespace to use
+function vim.api.nvim_set_hl_ns(ns_id) end
+
+--- Set active namespace for highlights defined with `nvim_set_hl()` while
+--- redrawing.
+--- This function meant to be called while redrawing, primarily from
+--- `nvim_set_decoration_provider()` on_win and on_line callbacks, which are
+--- allowed to change the namespace during a redraw cycle.
+---
+--- @param ns_id integer the namespace to activate
+function vim.api.nvim_set_hl_ns_fast(ns_id) end
+
+--- Sets a global `mapping` for the given mode.
+--- To set a buffer-local mapping, use `nvim_buf_set_keymap()`.
+--- Unlike `:map`, leading/trailing whitespace is accepted as part of the
+--- {lhs} or {rhs}. Empty {rhs} is `<Nop>`. `keycodes` are replaced as usual.
+--- Example:
+---
+--- ```vim
+--- call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+--- ```
+---
+--- is equivalent to:
+---
+--- ```vim
+--- nmap <nowait> <Space><NL> <Nop>
+--- ```
+---
+--- @param mode string Mode short-name (map command prefix: "n", "i", "v", "x", …) or
+--- "!" for `:map!`, or empty string for `:map`. "ia", "ca" or
+--- "!a" for abbreviation in Insert mode, Cmdline mode, or both,
+--- respectively
+--- @param lhs string Left-hand-side `{lhs}` of the mapping.
+--- @param rhs string Right-hand-side `{rhs}` of the mapping.
+--- @param opts vim.api.keyset.keymap Optional parameters map: Accepts all `:map-arguments` as keys
+--- except `<buffer>`, values are booleans (default false). Also:
+--- • "noremap" disables `recursive_mapping`, like `:noremap`
+--- • "desc" human-readable description.
+--- • "callback" Lua function called in place of {rhs}.
+--- • "replace_keycodes" (boolean) When "expr" is true, replace
+--- keycodes in the resulting string (see
+--- `nvim_replace_termcodes()`). Returning nil from the Lua
+--- "callback" is equivalent to returning an empty string.
+function vim.api.nvim_set_keymap(mode, lhs, rhs, opts) end
+
+--- @deprecated
+--- @param name string
+--- @param value any
+function vim.api.nvim_set_option(name, value) end
+
+--- Sets the value of an option. The behavior of this function matches that of
+--- `:set`: for global-local options, both the global and local value are set
+--- unless otherwise specified with {scope}.
+--- Note the options {win} and {buf} cannot be used together.
+---
+--- @param name string Option name
+--- @param value any New option value
+--- @param opts vim.api.keyset.option Optional parameters
+--- • scope: One of "global" or "local". Analogous to
+--- `:setglobal` and `:setlocal`, respectively.
+--- • win: `window-ID`. Used for setting window local option.
+--- • buf: Buffer number. Used for setting buffer local option.
+function vim.api.nvim_set_option_value(name, value, opts) end
+
+--- Sets a global (g:) variable.
+---
+--- @param name string Variable name
+--- @param value any Variable value
+function vim.api.nvim_set_var(name, value) end
+
+--- Sets a v: variable, if it is not readonly.
+---
+--- @param name string Variable name
+--- @param value any Variable value
+function vim.api.nvim_set_vvar(name, value) end
+
+--- Calculates the number of display cells occupied by `text`. Control
+--- characters including <Tab> count as one cell.
+---
+--- @param text string Some text
+--- @return integer
+function vim.api.nvim_strwidth(text) end
+
+--- Removes a tab-scoped (t:) variable
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @param name string Variable name
+function vim.api.nvim_tabpage_del_var(tabpage, name) end
+
+--- Gets the tabpage number
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @return integer
+function vim.api.nvim_tabpage_get_number(tabpage) end
+
+--- Gets a tab-scoped (t:) variable
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @param name string Variable name
+--- @return any
+function vim.api.nvim_tabpage_get_var(tabpage, name) end
+
+--- Gets the current window in a tabpage
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @return integer
+function vim.api.nvim_tabpage_get_win(tabpage) end
+
+--- Checks if a tabpage is valid
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @return boolean
+function vim.api.nvim_tabpage_is_valid(tabpage) end
+
+--- Gets the windows in a tabpage
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @return integer[]
+function vim.api.nvim_tabpage_list_wins(tabpage) end
+
+--- Sets a tab-scoped (t:) variable
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @param name string Variable name
+--- @param value any Variable value
+function vim.api.nvim_tabpage_set_var(tabpage, name, value) end
+
+--- Calls a function with window as temporary current window.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param fun function Function to call inside the window (currently Lua callable
+--- only)
+--- @return any
+function vim.api.nvim_win_call(window, fun) end
+
+--- Closes the window (like `:close` with a `window-ID`).
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param force boolean Behave like `:close!` The last window of a buffer with
+--- unwritten changes can be closed. The buffer will become
+--- hidden, even if 'hidden' is not set.
+function vim.api.nvim_win_close(window, force) end
+
+--- Removes a window-scoped (w:) variable
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param name string Variable name
+function vim.api.nvim_win_del_var(window, name) end
+
+--- Gets the current buffer in a window
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer
+function vim.api.nvim_win_get_buf(window) end
+
+--- Gets window configuration.
+--- The returned value may be given to `nvim_open_win()`.
+--- `relative` is empty for normal windows.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return table<string,any>
+function vim.api.nvim_win_get_config(window) end
+
+--- Gets the (1,0)-indexed, buffer-relative cursor position for a given window
+--- (different windows showing the same buffer have independent cursor
+--- positions). `api-indexing`
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer[]
+function vim.api.nvim_win_get_cursor(window) end
+
+--- Gets the window height
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer
+function vim.api.nvim_win_get_height(window) end
+
+--- Gets the window number
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer
+function vim.api.nvim_win_get_number(window) end
+
+--- @deprecated
+--- @param window integer
+--- @param name string
+--- @return any
+function vim.api.nvim_win_get_option(window, name) end
+
+--- Gets the window position in display cells. First position is zero.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer[]
+function vim.api.nvim_win_get_position(window) end
+
+--- Gets the window tabpage
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer
+function vim.api.nvim_win_get_tabpage(window) end
+
+--- Gets a window-scoped (w:) variable
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param name string Variable name
+--- @return any
+function vim.api.nvim_win_get_var(window, name) end
+
+--- Gets the window width
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer
+function vim.api.nvim_win_get_width(window) end
+
+--- Closes the window and hide the buffer it contains (like `:hide` with a
+--- `window-ID`).
+--- Like `:hide` the buffer becomes hidden unless another window is editing
+--- it, or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to `:close`
+--- or `nvim_win_close()`, which will close the buffer.
+---
+--- @param window integer Window handle, or 0 for current window
+function vim.api.nvim_win_hide(window) end
+
+--- Checks if a window is valid
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return boolean
+function vim.api.nvim_win_is_valid(window) end
+
+--- Sets the current buffer in a window, without side effects
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param buffer integer Buffer handle
+function vim.api.nvim_win_set_buf(window, buffer) end
+
+--- Configures window layout. Currently only for floating and external windows
+--- (including changing a split window to those layouts).
+--- When reconfiguring a floating window, absent option keys will not be
+--- changed. `row`/`col` and `relative` must be reconfigured together.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param config vim.api.keyset.float_config Map defining the window configuration, see `nvim_open_win()`
+function vim.api.nvim_win_set_config(window, config) end
+
+--- Sets the (1,0)-indexed cursor position in the window. `api-indexing` This
+--- scrolls the window even if it is not the current one.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param pos integer[] (row, col) tuple representing the new position
+function vim.api.nvim_win_set_cursor(window, pos) end
+
+--- Sets the window height.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param height integer Height as a count of rows
+function vim.api.nvim_win_set_height(window, height) end
+
+--- Set highlight namespace for a window. This will use highlights defined
+--- with `nvim_set_hl()` for this namespace, but fall back to global
+--- highlights (ns=0) when missing.
+--- This takes precedence over the 'winhighlight' option.
+---
+--- @param window integer
+--- @param ns_id integer the namespace to use
+function vim.api.nvim_win_set_hl_ns(window, ns_id) end
+
+--- @deprecated
+--- @param window integer
+--- @param name string
+--- @param value any
+function vim.api.nvim_win_set_option(window, name, value) end
+
+--- Sets a window-scoped (w:) variable
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param name string Variable name
+--- @param value any Variable value
+function vim.api.nvim_win_set_var(window, name, value) end
+
+--- Sets the window width. This will only succeed if the screen is split
+--- vertically.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param width integer Width as a count of columns
+function vim.api.nvim_win_set_width(window, width) end
+
+--- Computes the number of screen lines occupied by a range of text in a given
+--- window. Works for off-screen text and takes folds into account.
+--- Diff filler or virtual lines above a line are counted as a part of that
+--- line, unless the line is on "start_row" and "start_vcol" is specified.
+--- Diff filler or virtual lines below the last buffer line are counted in the
+--- result when "end_row" is omitted.
+--- Line indexing is similar to `nvim_buf_get_text()`.
+---
+--- @param window integer Window handle, or 0 for current window.
+--- @param opts vim.api.keyset.win_text_height Optional parameters:
+--- • start_row: Starting line index, 0-based inclusive. When
+--- omitted start at the very top.
+--- • end_row: Ending line index, 0-based inclusive. When
+--- omitted end at the very bottom.
+--- • start_vcol: Starting virtual column index on "start_row",
+--- 0-based inclusive, rounded down to full screen lines. When
+--- omitted include the whole line.
+--- • end_vcol: Ending virtual column index on "end_row",
+--- 0-based exclusive, rounded up to full screen lines. When
+--- omitted include the whole line.
+--- @return table<string,any>
+function vim.api.nvim_win_text_height(window, opts) end
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
new file mode 100644
index 0000000000..f69e5a92c7
--- /dev/null
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -0,0 +1,267 @@
+--- @meta _
+-- THIS FILE IS GENERATED
+-- DO NOT EDIT
+error('Cannot require a meta file')
+
+--- @class vim.api.keyset.clear_autocmds
+--- @field buffer? integer
+--- @field event? any
+--- @field group? any
+--- @field pattern? any
+
+--- @class vim.api.keyset.cmd
+--- @field cmd? string
+--- @field range? any[]
+--- @field count? integer
+--- @field reg? string
+--- @field bang? boolean
+--- @field args? any[]
+--- @field magic? table<string,any>
+--- @field mods? table<string,any>
+--- @field nargs? any
+--- @field addr? any
+--- @field nextcmd? any
+
+--- @class vim.api.keyset.cmd_magic
+--- @field file? boolean
+--- @field bar? boolean
+
+--- @class vim.api.keyset.cmd_mods
+--- @field silent? boolean
+--- @field emsg_silent? boolean
+--- @field unsilent? boolean
+--- @field filter? table<string,any>
+--- @field sandbox? boolean
+--- @field noautocmd? boolean
+--- @field browse? boolean
+--- @field confirm? boolean
+--- @field hide? boolean
+--- @field horizontal? boolean
+--- @field keepalt? boolean
+--- @field keepjumps? boolean
+--- @field keepmarks? boolean
+--- @field keeppatterns? boolean
+--- @field lockmarks? boolean
+--- @field noswapfile? boolean
+--- @field tab? integer
+--- @field verbose? integer
+--- @field vertical? boolean
+--- @field split? string
+
+--- @class vim.api.keyset.cmd_mods_filter
+--- @field pattern? string
+--- @field force? boolean
+
+--- @class vim.api.keyset.cmd_opts
+--- @field output? boolean
+
+--- @class vim.api.keyset.context
+--- @field types? any[]
+
+--- @class vim.api.keyset.create_augroup
+--- @field clear? any
+
+--- @class vim.api.keyset.create_autocmd
+--- @field buffer? integer
+--- @field callback? any
+--- @field command? string
+--- @field desc? string
+--- @field group? any
+--- @field nested? boolean
+--- @field once? boolean
+--- @field pattern? any
+
+--- @class vim.api.keyset.echo_opts
+--- @field verbose? boolean
+
+--- @class vim.api.keyset.eval_statusline
+--- @field winid? integer
+--- @field maxwidth? integer
+--- @field fillchar? string
+--- @field highlights? boolean
+--- @field use_winbar? boolean
+--- @field use_tabline? boolean
+--- @field use_statuscol_lnum? integer
+
+--- @class vim.api.keyset.exec_autocmds
+--- @field buffer? integer
+--- @field group? any
+--- @field modeline? boolean
+--- @field pattern? any
+--- @field data? any
+
+--- @class vim.api.keyset.exec_opts
+--- @field output? boolean
+
+--- @class vim.api.keyset.float_config
+--- @field row? number
+--- @field col? number
+--- @field width? integer
+--- @field height? integer
+--- @field anchor? string
+--- @field relative? string
+--- @field win? integer
+--- @field bufpos? any[]
+--- @field external? boolean
+--- @field focusable? boolean
+--- @field zindex? integer
+--- @field border? any
+--- @field title? any
+--- @field title_pos? string
+--- @field footer? any
+--- @field footer_pos? string
+--- @field style? string
+--- @field noautocmd? boolean
+--- @field fixed? boolean
+--- @field hide? boolean
+
+--- @class vim.api.keyset.get_autocmds
+--- @field event? any
+--- @field group? any
+--- @field pattern? any
+--- @field buffer? any
+
+--- @class vim.api.keyset.get_commands
+--- @field builtin? boolean
+
+--- @class vim.api.keyset.get_extmarks
+--- @field limit? integer
+--- @field details? boolean
+--- @field hl_name? boolean
+--- @field overlap? boolean
+--- @field type? string
+
+--- @class vim.api.keyset.get_highlight
+--- @field id? integer
+--- @field name? string
+--- @field link? boolean
+--- @field create? boolean
+
+--- @class vim.api.keyset.get_ns
+--- @field winid? integer
+
+--- @class vim.api.keyset.highlight
+--- @field bold? boolean
+--- @field standout? boolean
+--- @field strikethrough? boolean
+--- @field underline? boolean
+--- @field undercurl? boolean
+--- @field underdouble? boolean
+--- @field underdotted? boolean
+--- @field underdashed? boolean
+--- @field italic? boolean
+--- @field reverse? boolean
+--- @field altfont? boolean
+--- @field nocombine? boolean
+--- @field default? boolean
+--- @field cterm? any
+--- @field foreground? any
+--- @field fg? any
+--- @field background? any
+--- @field bg? any
+--- @field ctermfg? any
+--- @field ctermbg? any
+--- @field special? any
+--- @field sp? any
+--- @field link? any
+--- @field global_link? any
+--- @field fallback? boolean
+--- @field blend? integer
+--- @field fg_indexed? boolean
+--- @field bg_indexed? boolean
+--- @field force? boolean
+
+--- @class vim.api.keyset.highlight_cterm
+--- @field bold? boolean
+--- @field standout? boolean
+--- @field strikethrough? boolean
+--- @field underline? boolean
+--- @field undercurl? boolean
+--- @field underdouble? boolean
+--- @field underdotted? boolean
+--- @field underdashed? boolean
+--- @field italic? boolean
+--- @field reverse? boolean
+--- @field altfont? boolean
+--- @field nocombine? boolean
+
+--- @class vim.api.keyset.keymap
+--- @field noremap? boolean
+--- @field nowait? boolean
+--- @field silent? boolean
+--- @field script? boolean
+--- @field expr? boolean
+--- @field unique? boolean
+--- @field callback? function
+--- @field desc? string
+--- @field replace_keycodes? boolean
+
+--- @class vim.api.keyset.option
+--- @field scope? string
+--- @field win? integer
+--- @field buf? integer
+--- @field filetype? string
+
+--- @class vim.api.keyset.runtime
+--- @field is_lua? boolean
+--- @field do_source? boolean
+
+--- @class vim.api.keyset.set_decoration_provider
+--- @field on_start? function
+--- @field on_buf? function
+--- @field on_win? function
+--- @field on_line? function
+--- @field on_end? function
+--- @field _on_hl_def? function
+--- @field _on_spell_nav? function
+
+--- @class vim.api.keyset.set_extmark
+--- @field id? integer
+--- @field end_line? integer
+--- @field end_row? integer
+--- @field end_col? integer
+--- @field hl_group? any
+--- @field virt_text? any[]
+--- @field virt_text_pos? string
+--- @field virt_text_win_col? integer
+--- @field virt_text_hide? boolean
+--- @field hl_eol? boolean
+--- @field hl_mode? string
+--- @field invalidate? boolean
+--- @field ephemeral? boolean
+--- @field priority? integer
+--- @field right_gravity? boolean
+--- @field end_right_gravity? boolean
+--- @field virt_lines? any[]
+--- @field virt_lines_above? boolean
+--- @field virt_lines_leftcol? boolean
+--- @field strict? boolean
+--- @field sign_text? string
+--- @field sign_hl_group? any
+--- @field number_hl_group? any
+--- @field line_hl_group? any
+--- @field cursorline_hl_group? any
+--- @field conceal? string
+--- @field spell? boolean
+--- @field ui_watched? boolean
+--- @field undo_restore? boolean
+
+--- @class vim.api.keyset.user_command
+--- @field addr? any
+--- @field bang? boolean
+--- @field bar? boolean
+--- @field complete? any
+--- @field count? any
+--- @field desc? any
+--- @field force? boolean
+--- @field keepscript? boolean
+--- @field nargs? any
+--- @field preview? any
+--- @field range? any
+--- @field register? boolean
+
+--- @class vim.api.keyset.win_text_height
+--- @field start_row? integer
+--- @field end_row? integer
+--- @field start_vcol? integer
+--- @field end_vcol? integer
diff --git a/runtime/lua/vim/_meta/base64.lua b/runtime/lua/vim/_meta/base64.lua
new file mode 100644
index 0000000000..f25b4af234
--- /dev/null
+++ b/runtime/lua/vim/_meta/base64.lua
@@ -0,0 +1,13 @@
+--- @meta
+
+--- Encode {str} using Base64.
+---
+--- @param str string String to encode
+--- @return string Encoded string
+function vim.base64.encode(str) end
+
+--- Decode a Base64 encoded string.
+---
+--- @param str string Base64 encoded string
+--- @return string Decoded string
+function vim.base64.decode(str) end
diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
new file mode 100644
index 0000000000..eeba356672
--- /dev/null
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -0,0 +1,289 @@
+---@meta
+
+-- luacheck: no unused args
+
+---@defgroup vim.builtin
+---
+---@brief <pre>help
+---vim.api.{func}({...}) *vim.api*
+--- Invokes Nvim |API| function {func} with arguments {...}.
+--- Example: call the "nvim_get_current_line()" API function: >lua
+--- print(tostring(vim.api.nvim_get_current_line()))
+---
+---vim.NIL *vim.NIL*
+--- Special value representing NIL in |RPC| and |v:null| in Vimscript
+--- conversion, and similar cases. Lua `nil` cannot be used as part of a Lua
+--- table representing a Dictionary or Array, because it is treated as
+--- missing: `{"foo", nil}` is the same as `{"foo"}`.
+---
+---vim.type_idx *vim.type_idx*
+--- Type index for use in |lua-special-tbl|. Specifying one of the values from
+--- |vim.types| allows typing the empty table (it is unclear whether empty Lua
+--- table represents empty list or empty array) and forcing integral numbers
+--- to be |Float|. See |lua-special-tbl| for more details.
+---
+---vim.val_idx *vim.val_idx*
+--- Value index for tables representing |Float|s. A table representing
+--- floating-point value 1.0 looks like this: >lua
+--- {
+--- [vim.type_idx] = vim.types.float,
+--- [vim.val_idx] = 1.0,
+--- }
+---< See also |vim.type_idx| and |lua-special-tbl|.
+---
+---vim.types *vim.types*
+--- Table with possible values for |vim.type_idx|. Contains two sets of
+--- key-value pairs: first maps possible values for |vim.type_idx| to
+--- human-readable strings, second maps human-readable type names to values
+--- for |vim.type_idx|. Currently contains pairs for `float`, `array` and
+--- `dictionary` types.
+---
+--- Note: One must expect that values corresponding to `vim.types.float`,
+--- `vim.types.array` and `vim.types.dictionary` fall under only two following
+--- assumptions:
+--- 1. Value may serve both as a key and as a value in a table. Given the
+--- properties of Lua tables this basically means “value is not `nil`”.
+--- 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
+--- same as `value`.
+--- No other restrictions are put on types, and it is not guaranteed that
+--- values corresponding to `vim.types.float`, `vim.types.array` and
+--- `vim.types.dictionary` will not change or that `vim.types` table will only
+--- contain values for these three types.
+---
+--- *log_levels* *vim.log.levels*
+---Log levels are one of the values defined in `vim.log.levels`:
+---
+--- vim.log.levels.DEBUG
+--- vim.log.levels.ERROR
+--- vim.log.levels.INFO
+--- vim.log.levels.TRACE
+--- vim.log.levels.WARN
+--- vim.log.levels.OFF
+---
+---</pre>
+
+--- Returns true if the code is executing as part of a "fast" event handler,
+--- where most of the API is disabled. These are low-level events (e.g.
+--- |lua-loop-callbacks|) which can be invoked whenever Nvim polls for input.
+--- When this is `false` most API functions are callable (but may be subject
+--- to other restrictions such as |textlock|).
+function vim.in_fast_event() end
+
+--- Creates a special empty table (marked with a metatable), which Nvim
+--- converts to an empty dictionary when translating Lua values to Vimscript
+--- or API types. Nvim by default converts an empty table `{}` without this
+--- metatable to an list/array.
+---
+--- Note: If numeric keys are present in the table, Nvim ignores the metatable
+--- marker and converts the dict to a list/array anyway.
+function vim.empty_dict() end
+
+--- Sends {event} to {channel} via |RPC| and returns immediately. If {channel}
+--- is 0, the event is broadcast to all channels.
+---
+--- This function also works in a fast callback |lua-loop-callbacks|.
+--- @param channel integer
+--- @param method string
+--- @param args? any[]
+--- @param ...? any
+function vim.rpcnotify(channel, method, args, ...) end
+
+--- Sends a request to {channel} to invoke {method} via |RPC| and blocks until
+--- a response is received.
+---
+--- Note: NIL values as part of the return value is represented as |vim.NIL|
+--- special value
+--- @param channel integer
+--- @param method string
+--- @param args? any[]
+--- @param ...? any
+function vim.rpcrequest(channel, method, args, ...) end
+
+--- Compares strings case-insensitively.
+--- @param a string
+--- @param b string
+--- @return 0|1|-1
+--- if strings are
+--- equal, {a} is greater than {b} or {a} is lesser than {b}, respectively.
+function vim.stricmp(a, b) end
+
+--- Convert UTF-32 or UTF-16 {index} to byte index. If {use_utf16} is not
+--- supplied, it defaults to false (use UTF-32). Returns the byte index.
+---
+--- Invalid UTF-8 and NUL is treated like by |vim.str_byteindex()|.
+--- An {index} in the middle of a UTF-16 sequence is rounded upwards to
+--- the end of that sequence.
+--- @param str string
+--- @param index number
+--- @param use_utf16? any
+function vim.str_byteindex(str, index, use_utf16) end
+
+--- Gets a list of the starting byte positions of each UTF-8 codepoint in the given string.
+---
+--- Embedded NUL bytes are treated as terminating the string.
+--- @param str string
+--- @return table
+function vim.str_utf_pos(str) end
+
+--- Gets the distance (in bytes) from the starting byte of the codepoint (character) that {index}
+--- points to.
+---
+--- The result can be added to {index} to get the starting byte of a character.
+---
+--- Examples:
+---
+--- ```lua
+--- -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8)
+---
+--- -- Returns 0 because the index is pointing at the first byte of a character
+--- vim.str_utf_start('æ', 1)
+---
+--- -- Returns -1 because the index is pointing at the second byte of a character
+--- vim.str_utf_start('æ', 2)
+--- ```
+---
+--- @param str string
+--- @param index number
+--- @return number
+function vim.str_utf_start(str, index) end
+
+--- Gets the distance (in bytes) from the last byte of the codepoint (character) that {index} points
+--- to.
+---
+--- Examples:
+---
+--- ```lua
+--- -- The character 'æ' is stored as the bytes '\xc3\xa6' (using UTF-8)
+---
+--- -- Returns 0 because the index is pointing at the last byte of a character
+--- vim.str_utf_end('æ', 2)
+---
+--- -- Returns 1 because the index is pointing at the penultimate byte of a character
+--- vim.str_utf_end('æ', 1)
+--- ```
+---
+--- @param str string
+--- @param index number
+--- @return number
+function vim.str_utf_end(str, index) end
+
+--- Convert byte index to UTF-32 and UTF-16 indices. If {index} is not
+--- supplied, the length of the string is used. All indices are zero-based.
+---
+--- Embedded NUL bytes are treated as terminating the string. Invalid UTF-8
+--- bytes, and embedded surrogates are counted as one code point each. An
+--- {index} in the middle of a UTF-8 sequence is rounded upwards to the end of
+--- that sequence.
+--- @param str string
+--- @param index? number
+--- @return integer UTF-32 index
+--- @return integer UTF-16 index
+function vim.str_utfindex(str, index) end
+
+--- The result is a String, which is the text {str} converted from
+--- encoding {from} to encoding {to}. When the conversion fails `nil` is
+--- returned. When some characters could not be converted they
+--- are replaced with "?".
+--- The encoding names are whatever the iconv() library function
+--- can accept, see ":Man 3 iconv".
+---
+--- @param str string Text to convert
+--- @param from number Encoding of {str}
+--- @param to number Target encoding
+--- @param opts? table<string,any>
+--- @return string|nil Converted string if conversion succeeds, `nil` otherwise.
+function vim.iconv(str, from, to, opts) end
+
+--- Schedules {fn} to be invoked soon by the main event-loop. Useful
+--- to avoid |textlock| or other temporary restrictions.
+--- @param fn function
+function vim.schedule(fn) end
+
+--- Wait for {time} in milliseconds until {callback} returns `true`.
+---
+--- Executes {callback} immediately and at approximately {interval}
+--- milliseconds (default 200). Nvim still processes other events during
+--- this time.
+---
+--- Cannot be called while in an |api-fast| event.
+---
+--- Examples:
+---
+--- ```lua
+---
+--- ---
+--- -- Wait for 100 ms, allowing other events to process
+--- vim.wait(100, function() end)
+---
+--- ---
+--- -- Wait for 100 ms or until global variable set.
+--- vim.wait(100, function() return vim.g.waiting_for_var end)
+---
+--- ---
+--- -- Wait for 1 second or until global variable set, checking every ~500 ms
+--- vim.wait(1000, function() return vim.g.waiting_for_var end, 500)
+---
+--- ---
+--- -- Schedule a function to set a value in 100ms
+--- vim.defer_fn(function() vim.g.timer_result = true end, 100)
+---
+--- -- Would wait ten seconds if results blocked. Actually only waits 100 ms
+--- if vim.wait(10000, function() return vim.g.timer_result end) then
+--- print('Only waiting a little bit of time!')
+--- end
+--- ```
+---
+--- @param time integer Number of milliseconds to wait
+--- @param callback? fun(): boolean Optional callback. Waits until {callback} returns true
+--- @param interval? integer (Approximate) number of milliseconds to wait between polls
+--- @param fast_only? boolean If true, only |api-fast| events will be processed.
+--- @return boolean, nil|-1|-2
+--- - If {callback} returns `true` during the {time}: `true, nil`
+--- - If {callback} never returns `true` during the {time}: `false, -1`
+--- - If {callback} is interrupted during the {time}: `false, -2`
+--- - If {callback} errors, the error is raised.
+function vim.wait(time, callback, interval, fast_only) end
+
+--- Attach to ui events, similar to |nvim_ui_attach()| but receive events
+--- as Lua callback. Can be used to implement screen elements like
+--- popupmenu or message handling in Lua.
+---
+--- {options} should be a dictionary-like table, where `ext_...` options should
+--- be set to true to receive events for the respective external element.
+---
+--- {callback} receives event name plus additional parameters. See |ui-popupmenu|
+--- and the sections below for event format for respective events.
+---
+--- WARNING: This api is considered experimental. Usability will vary for
+--- different screen elements. In particular `ext_messages` behavior is subject
+--- to further changes and usability improvements. This is expected to be
+--- used to handle messages when setting 'cmdheight' to zero (which is
+--- likewise experimental).
+---
+--- Example (stub for a |ui-popupmenu| implementation):
+---
+--- ```lua
+--- ns = vim.api.nvim_create_namespace('my_fancy_pum')
+---
+--- vim.ui_attach(ns, {ext_popupmenu=true}, function(event, ...)
+--- if event == "popupmenu_show" then
+--- local items, selected, row, col, grid = ...
+--- print("display pum ", #items)
+--- elseif event == "popupmenu_select" then
+--- local selected = ...
+--- print("selected", selected)
+--- elseif event == "popupmenu_hide" then
+--- print("FIN")
+--- end
+--- end)
+--- ```
+---
+--- @param ns integer
+--- @param options table<string, any>
+--- @param callback fun()
+function vim.ui_attach(ns, options, callback) end
+
+--- Detach a callback previously attached with |vim.ui_attach()| for the
+--- given namespace {ns}.
+--- @param ns integer
+function vim.ui_detach(ns) end
diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua
new file mode 100644
index 0000000000..ef0452c649
--- /dev/null
+++ b/runtime/lua/vim/_meta/builtin_types.lua
@@ -0,0 +1,129 @@
+--- @class vim.fn.sign
+--- @field group string
+--- @field id integer
+--- @field lnum integer
+--- @field name string
+--- @field priority integer
+
+--- @class vim.fn.getbufinfo.dict
+--- @field buflisted? 0|1
+--- @field bufloaded? 0|1
+--- @field bufmodified? 0|1
+
+--- @class vim.fn.getbufinfo.ret.item
+--- @field bufnr integer
+--- @field changed 0|1
+--- @field changedtick integer
+--- @field hidden 0|1
+--- @field lastused integer
+--- @field linecount integer
+--- @field listed 0|1
+--- @field lnum integer
+--- @field loaded 0|1
+--- @field name string
+--- @field signs vim.fn.sign[]
+--- @field variables table<string,any>
+--- @field windows integer[]
+
+--- @alias vim.fn.getjumplist.ret {[1]: vim.fn.getjumplist.ret.item[], [2]: integer}
+
+--- @class vim.fn.getjumplist.ret.item
+--- @field bufnr integer
+--- @field col integer
+--- @field coladd integer
+--- @field filename? string
+--- @field lnum integer
+
+--- @class vim.fn.getmousepos.ret
+--- @field screenrow integer
+--- @field screencol integer
+--- @field winid integer
+--- @field winrow integer
+--- @field wincol integer
+--- @field line integer
+--- @field column integer
+
+--- @class vim.fn.getwininfo.ret.item
+--- @field botline integer
+--- @field bufnr integer
+--- @field height integer
+--- @field loclist integer
+--- @field quickfix integer
+--- @field tabnr integer
+--- @field terminal integer
+--- @field textoff integer
+--- @field topline integer
+--- @field variables table<string,any>
+--- @field width integer
+--- @field winbar integer
+--- @field wincol integer
+--- @field winid integer
+--- @field winnr integer
+--- @field winrow integer
+
+--- @class vim.fn.sign_define.dict
+--- @field text string
+--- @field icon? string
+--- @field linehl? string
+--- @field numhl? string
+--- @field texthl? string
+--- @field culhl? string
+
+--- @class vim.fn.sign_getdefined.ret.item
+--- @field name string
+--- @field text string
+--- @field icon? string
+--- @field texthl? string
+--- @field culhl? string
+--- @field numhl? string
+--- @field linehl? string
+
+--- @class vim.fn.sign_getplaced.dict
+--- @field group? string
+--- @field id? integer
+--- @field lnum? string
+
+--- @class vim.fn.sign_getplaced.ret.item
+--- @field buf integer
+--- @field signs vim.fn.sign[]
+
+--- @class vim.fn.sign_place.dict
+--- @field lnum? integer
+--- @field priority? integer
+
+--- @class vim.fn.sign_placelist.list.item
+--- @field buffer integer|string
+--- @field group? string
+--- @field id? integer
+--- @field lnum integer
+--- @field name string
+--- @field priority? integer
+
+--- @class vim.fn.sign_unplace.dict
+--- @field buffer? integer|string
+--- @field id? integer
+
+--- @class vim.fn.sign_unplacelist.list.item
+--- @field buffer? integer|string
+--- @field group? string
+--- @field id? integer
+
+--- @class vim.fn.winrestview.dict
+--- @field col? integer
+--- @field coladd? integer
+--- @field curswant? integer
+--- @field leftcol? integer
+--- @field lnum? integer
+--- @field skipcol? integer
+--- @field topfill? integer
+--- @field topline? integer
+
+--- @class vim.fn.winsaveview.ret
+--- @field col integer
+--- @field coladd integer
+--- @field curswant integer
+--- @field leftcol integer
+--- @field lnum integer
+--- @field skipcol integer
+--- @field topfill integer
+--- @field topline integer
diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua
new file mode 100644
index 0000000000..f265139448
--- /dev/null
+++ b/runtime/lua/vim/_meta/diff.lua
@@ -0,0 +1,70 @@
+---@meta
+
+-- luacheck: no unused args
+
+--- Run diff on strings {a} and {b}. Any indices returned by this function,
+--- either directly or via callback arguments, are 1-based.
+---
+--- Examples:
+---
+--- ```lua
+--- vim.diff('a\n', 'b\nc\n')
+--- -- =>
+--- -- @@ -1 +1,2 @@
+--- -- -a
+--- -- +b
+--- -- +c
+---
+--- vim.diff('a\n', 'b\nc\n', {result_type = 'indices'})
+--- -- =>
+--- -- {
+--- -- {1, 1, 1, 2}
+--- -- }
+--- ```
+---
+---@param a string First string to compare
+---@param b string Second string to compare
+---@param opts table<string,any> Optional parameters:
+--- - `on_hunk` (callback):
+--- Invoked for each hunk in the diff. Return a negative number
+--- to cancel the callback for any remaining hunks.
+--- Args:
+--- - `start_a` (integer): Start line of hunk in {a}.
+--- - `count_a` (integer): Hunk size in {a}.
+--- - `start_b` (integer): Start line of hunk in {b}.
+--- - `count_b` (integer): Hunk size in {b}.
+--- - `result_type` (string): Form of the returned diff:
+--- - "unified": (default) String in unified format.
+--- - "indices": Array of hunk locations.
+--- Note: This option is ignored if `on_hunk` is used.
+--- - `linematch` (boolean|integer): Run linematch on the resulting hunks
+--- from xdiff. When integer, only hunks upto this size in
+--- lines are run through linematch. Requires `result_type = indices`,
+--- ignored otherwise.
+--- - `algorithm` (string):
+--- Diff algorithm to use. Values:
+--- - "myers" the default algorithm
+--- - "minimal" spend extra time to generate the
+--- smallest possible diff
+--- - "patience" patience diff algorithm
+--- - "histogram" histogram diff algorithm
+--- - `ctxlen` (integer): Context length
+--- - `interhunkctxlen` (integer):
+--- Inter hunk context length
+--- - `ignore_whitespace` (boolean):
+--- Ignore whitespace
+--- - `ignore_whitespace_change` (boolean):
+--- Ignore whitespace change
+--- - `ignore_whitespace_change_at_eol` (boolean)
+--- Ignore whitespace change at end-of-line.
+--- - `ignore_cr_at_eol` (boolean)
+--- Ignore carriage return at end-of-line
+--- - `ignore_blank_lines` (boolean)
+--- Ignore blank lines
+--- - `indent_heuristic` (boolean):
+--- Use the indent heuristic for the internal
+--- diff library.
+---
+---@return string|table|nil
+--- See {opts.result_type}. `nil` if {opts.on_hunk} is given.
+function vim.diff(a, b, opts) end
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
new file mode 100644
index 0000000000..e010086615
--- /dev/null
+++ b/runtime/lua/vim/_meta/json.lua
@@ -0,0 +1,39 @@
+---@meta
+
+---@nodoc
+vim.json = {}
+
+-- luacheck: no unused args
+
+---@defgroup vim.json
+---
+--- This module provides encoding and decoding of Lua objects to and
+--- from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
+
+--- Decodes (or "unpacks") the JSON-encoded {str} to a Lua object.
+---
+--- - Decodes JSON "null" as |vim.NIL| (controllable by {opts}, see below).
+--- - Decodes empty object as |vim.empty_dict()|.
+--- - Decodes empty array as `{}` (empty Lua table).
+---
+--- Example:
+---
+--- ```lua
+--- vim.print(vim.json.decode('{"bar":[],"foo":{},"zub":null}'))
+--- -- { bar = {}, foo = vim.empty_dict(), zub = vim.NIL }
+--- ```
+---
+---@param str string Stringified JSON data.
+---@param opts? table<string,any> Options table with keys:
+--- - luanil: (table) Table with keys:
+--- * object: (boolean) When true, converts `null` in JSON objects
+--- to Lua `nil` instead of |vim.NIL|.
+--- * array: (boolean) When true, converts `null` in JSON arrays
+--- to Lua `nil` instead of |vim.NIL|.
+---@return any
+function vim.json.decode(str, opts) end
+
+--- Encodes (or "packs") Lua object {obj} as JSON in a Lua string.
+---@param obj any
+---@return string
+function vim.json.encode(obj) end
diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua
new file mode 100644
index 0000000000..42c9a6449e
--- /dev/null
+++ b/runtime/lua/vim/_meta/lpeg.lua
@@ -0,0 +1,323 @@
+--- @meta
+
+-- These types were taken from https://github.com/LuaCATS/lpeg, with types being renamed to include
+-- the vim namespace and with some descriptions made less verbose.
+
+--- *LPeg* is a new pattern-matching library for Lua, based on [Parsing Expression Grammars](https://bford.info/packrat/) (PEGs).
+vim.lpeg = {}
+
+--- @class vim.lpeg.Pattern
+--- @operator unm: vim.lpeg.Pattern
+--- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern
+--- @operator sub(vim.lpeg.Pattern): vim.lpeg.Pattern
+--- @operator mul(vim.lpeg.Pattern): vim.lpeg.Pattern
+--- @operator mul(vim.lpeg.Capture): vim.lpeg.Pattern
+--- @operator div(string): vim.lpeg.Capture
+--- @operator div(number): vim.lpeg.Capture
+--- @operator div(table): vim.lpeg.Capture
+--- @operator div(function): vim.lpeg.Capture
+--- @operator pow(number): vim.lpeg.Pattern
+--- @operator mod(function): nil
+local Pattern = {}
+
+--- @alias vim.lpeg.Capture vim.lpeg.Pattern
+
+--- Matches the given `pattern` against the `subject` string. If the match succeeds, returns the index in the
+--- subject of the first character after the match, or the captured values (if the pattern captured any value).
+--- An optional numeric argument `init` makes the match start at that position in the subject string. As usual
+--- in Lua libraries, a negative value counts from the end. Unlike typical pattern-matching functions, `match`
+--- works only in anchored mode; that is, it tries to match the pattern with a prefix of the given subject
+--- string (at position `init`), not with an arbitrary substring of the subject. So, if we want to find a
+--- pattern anywhere in a string, we must either write a loop in Lua or write a pattern that
+--- matches anywhere.
+---
+--- Example:
+--- ```lua
+--- local pattern = lpeg.R("az") ^ 1 * -1
+--- assert(pattern:match("hello") == 6)
+--- assert(lpeg.match(pattern, "hello") == 6)
+--- assert(pattern:match("1 hello") == nil)
+--- ```
+---
+--- @param pattern vim.lpeg.Pattern
+--- @param subject string
+--- @param init? integer
+--- @return integer|vim.lpeg.Capture|nil
+function vim.lpeg.match(pattern, subject, init) end
+
+--- Matches the given `pattern` against the `subject` string. If the match succeeds, returns the
+--- index in the subject of the first character after the match, or the captured values (if the
+--- pattern captured any value). An optional numeric argument `init` makes the match start at
+--- that position in the subject string. As usual in Lua libraries, a negative value counts from the end.
+--- Unlike typical pattern-matching functions, `match` works only in anchored mode; that is, it tries
+--- to match the pattern with a prefix of the given subject string (at position `init`), not with
+--- an arbitrary substring of the subject. So, if we want to find a pattern anywhere in a string,
+--- we must either write a loop in Lua or write a pattern that matches anywhere.
+---
+--- Example:
+--- ```lua
+--- local pattern = lpeg.R("az") ^ 1 * -1
+--- assert(pattern:match("hello") == 6)
+--- assert(lpeg.match(pattern, "hello") == 6)
+--- assert(pattern:match("1 hello") == nil)
+--- ```
+---
+--- @param subject string
+--- @param init? integer
+--- @return integer|vim.lpeg.Capture|nil
+function Pattern:match(subject, init) end
+
+--- Returns the string `"pattern"` if the given value is a pattern, otherwise `nil`.
+---
+--- @return 'pattern'|nil
+function vim.lpeg.type(value) end
+
+--- Returns a string with the running version of LPeg.
+--- @return string
+function vim.lpeg.version() end
+
+--- Sets a limit for the size of the backtrack stack used by LPeg to track calls and choices.
+--- The default limit is `400`. Most well-written patterns need little backtrack levels and
+--- therefore you seldom need to change this limit; before changing it you should try to rewrite
+--- your pattern to avoid the need for extra space. Nevertheless, a few useful patterns may overflow.
+--- Also, with recursive grammars, subjects with deep recursion may also need larger limits.
+---
+--- @param max integer
+function vim.lpeg.setmaxstack(max) end
+
+--- Converts the given value into a proper pattern. This following rules are applied:
+--- * If the argument is a pattern, it is returned unmodified.
+--- * If the argument is a string, it is translated to a pattern that matches the string literally.
+--- * If the argument is a non-negative number `n`, the result is a pattern that matches exactly `n` characters.
+--- * If the argument is a negative number `-n`, the result is a pattern that succeeds only if
+--- the input string has less than `n` characters left: `lpeg.P(-n)` is equivalent to `-lpeg.P(n)`
+--- (see the unary minus operation).
+--- * If the argument is a boolean, the result is a pattern that always succeeds or always fails
+--- (according to the boolean value), without consuming any input.
+--- * If the argument is a table, it is interpreted as a grammar (see Grammars).
+--- * If the argument is a function, returns a pattern equivalent to a match-time captureover the empty string.
+---
+--- @param value vim.lpeg.Pattern|string|integer|boolean|table|function
+--- @return vim.lpeg.Pattern
+function vim.lpeg.P(value) end
+
+--- Returns a pattern that matches only if the input string at the current position is preceded by `patt`.
+--- Pattern `patt` must match only strings with some fixed length, and it cannot contain captures.
+--- Like the and predicate, this pattern never consumes any input, independently of success or failure.
+---
+--- @param pattern vim.lpeg.Pattern
+--- @return vim.lpeg.Pattern
+function vim.lpeg.B(pattern) end
+
+--- Returns a pattern that matches any single character belonging to one of the given ranges.
+--- Each `range` is a string `xy` of length 2, representing all characters with code between the codes of
+--- `x` and `y` (both inclusive). As an example, the pattern `lpeg.R("09")` matches any digit, and
+--- `lpeg.R("az", "AZ")` matches any ASCII letter.
+---
+--- Example:
+--- ```lua
+--- local pattern = lpeg.R("az") ^ 1 * -1
+--- assert(pattern:match("hello") == 6)
+--- ```
+---
+--- @param ... string
+--- @return vim.lpeg.Pattern
+function vim.lpeg.R(...) end
+
+--- Returns a pattern that matches any single character that appears in the given string (the `S` stands for Set).
+--- As an example, the pattern `lpeg.S("+-*/")` matches any arithmetic operator. Note that, if `s` is a character
+--- (that is, a string of length 1), then `lpeg.P(s)` is equivalent to `lpeg.S(s)` which is equivalent to
+--- `lpeg.R(s..s)`. Note also that both `lpeg.S("")` and `lpeg.R()` are patterns that always fail.
+---
+--- @param string string
+--- @return vim.lpeg.Pattern
+function vim.lpeg.S(string) end
+
+--- Creates a non-terminal (a variable) for a grammar. This operation creates a non-terminal (a variable)
+--- for a grammar. The created non-terminal refers to the rule indexed by `v` in the enclosing grammar.
+---
+--- Example:
+--- ```lua
+--- local b = lpeg.P({"(" * ((1 - lpeg.S "()") + lpeg.V(1)) ^ 0 * ")"})
+--- assert(b:match('((string))') == 11)
+--- assert(b:match('(') == nil)
+--- ```
+---
+--- @param v string|integer
+--- @return vim.lpeg.Pattern
+function vim.lpeg.V(v) end
+
+--- @class vim.lpeg.Locale
+--- @field alnum userdata
+--- @field alpha userdata
+--- @field cntrl userdata
+--- @field digit userdata
+--- @field graph userdata
+--- @field lower userdata
+--- @field print userdata
+--- @field punct userdata
+--- @field space userdata
+--- @field upper userdata
+--- @field xdigit userdata
+
+--- Returns a table with patterns for matching some character classes according to the current locale.
+--- The table has fields named `alnum`, `alpha`, `cntrl`, `digit`, `graph`, `lower`, `print`, `punct`,
+--- `space`, `upper`, and `xdigit`, each one containing a correspondent pattern. Each pattern matches
+--- any single character that belongs to its class.
+--- If called with an argument `table`, then it creates those fields inside the given table and returns
+--- that table.
+---
+--- Example:
+--- ```lua
+--- lpeg.locale(lpeg)
+--- local space = lpeg.space^0
+--- local name = lpeg.C(lpeg.alpha^1) * space
+--- local sep = lpeg.S(",;") * space
+--- local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
+--- local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
+--- local t = list:match("a=b, c = hi; next = pi")
+--- assert(t.a == 'b')
+--- assert(t.c == 'hi')
+--- assert(t.next == 'pi')
+--- local locale = lpeg.locale()
+--- assert(type(locale.digit) == 'userdata')
+--- ```
+---
+--- @param tab? table
+--- @return vim.lpeg.Locale
+function vim.lpeg.locale(tab) end
+
+--- Creates a simple capture, which captures the substring of the subject that matches `patt`.
+--- The captured value is a string. If `patt` has other captures, their values are returned after this one.
+---
+--- Example:
+--- ```lua
+--- local function split (s, sep)
+--- sep = lpeg.P(sep)
+--- local elem = lpeg.C((1 - sep)^0)
+--- local p = elem * (sep * elem)^0
+--- return lpeg.match(p, s)
+--- end
+--- local a, b, c = split('a,b,c', ',')
+--- assert(a == 'a')
+--- assert(b == 'b')
+--- assert(c == 'c')
+--- ```
+---
+--- @param patt vim.lpeg.Pattern
+--- @return vim.lpeg.Capture
+function vim.lpeg.C(patt) end
+
+--- Creates an argument capture. This pattern matches the empty string and produces the value given as the
+--- nth extra argument given in the call to `lpeg.match`.
+--- @param n integer
+--- @return vim.lpeg.Capture
+function vim.lpeg.Carg(n) end
+
+--- Creates a back capture. This pattern matches the empty string and produces the values produced by the most recent
+--- group capture named `name` (where `name` can be any Lua value). Most recent means the last complete outermost
+--- group capture with the given name. A Complete capture means that the entire pattern corresponding to the capture
+--- has matched. An Outermost capture means that the capture is not inside another complete capture.
+--- In the same way that LPeg does not specify when it evaluates captures, it does not specify whether it reuses
+--- values previously produced by the group or re-evaluates them.
+---
+--- @param name any
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cb(name) end
+
+--- Creates a constant capture. This pattern matches the empty string and produces all given values as its captured values.
+---
+--- @param ... any
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cc(...) end
+
+--- Creates a fold capture. If `patt` produces a list of captures C1 C2 ... Cn, this capture will produce the value
+--- `func(...func(func(C1, C2), C3)...,Cn)`, that is, it will fold (or accumulate, or reduce) the captures from
+--- `patt` using function `func`. This capture assumes that `patt` should produce at least one capture with at
+--- least one value (of any type), which becomes the initial value of an accumulator. (If you need a specific
+--- initial value, you may prefix a constant captureto `patt`.) For each subsequent capture, LPeg calls `func`
+--- with this accumulator as the first argument and all values produced by the capture as extra arguments;
+--- the first result from this call becomes the new value for the accumulator. The final value of the accumulator
+--- becomes the captured value.
+---
+--- Example:
+--- ```lua
+--- local number = lpeg.R("09") ^ 1 / tonumber
+--- local list = number * ("," * number) ^ 0
+--- local function add(acc, newvalue) return acc + newvalue end
+--- local sum = lpeg.Cf(list, add)
+--- assert(sum:match("10,30,43") == 83)
+--- ```
+---
+--- @param patt vim.lpeg.Pattern
+--- @param func fun(acc, newvalue)
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cf(patt, func) end
+
+--- Creates a group capture. It groups all values returned by `patt` into a single capture.
+--- The group may be anonymous (if no name is given) or named with the given name (which
+--- can be any non-nil Lua value).
+---
+--- @param patt vim.lpeg.Pattern
+--- @param name? string
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cg(patt, name) end
+
+--- Creates a position capture. It matches the empty string and captures the position in the
+--- subject where the match occurs. The captured value is a number.
+---
+--- Example:
+--- ```lua
+--- local I = lpeg.Cp()
+--- local function anywhere(p) return lpeg.P({I * p * I + 1 * lpeg.V(1)}) end
+--- local match_start, match_end = anywhere("world"):match("hello world!")
+--- assert(match_start == 7)
+--- assert(match_end == 12)
+--- ```
+---
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cp() end
+
+--- Creates a substitution capture. This function creates a substitution capture, which
+--- captures the substring of the subject that matches `patt`, with substitutions.
+--- For any capture inside `patt` with a value, the substring that matched the capture
+--- is replaced by the capture value (which should be a string). The final captured
+--- value is the string resulting from all replacements.
+---
+--- Example:
+--- ```lua
+--- local function gsub (s, patt, repl)
+--- patt = lpeg.P(patt)
+--- patt = lpeg.Cs((patt / repl + 1)^0)
+--- return lpeg.match(patt, s)
+--- end
+--- assert(gsub('Hello, xxx!', 'xxx', 'World') == 'Hello, World!')
+--- ```
+---
+--- @param patt vim.lpeg.Pattern
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cs(patt) end
+
+--- Creates a table capture. This capture returns a table with all values from all anonymous captures
+--- made by `patt` inside this table in successive integer keys, starting at 1.
+--- Moreover, for each named capture group created by `patt`, the first value of the group is put into
+--- the table with the group name as its key. The captured value is only the table.
+---
+--- @param patt vim.lpeg.Pattern|''
+--- @return vim.lpeg.Capture
+function vim.lpeg.Ct(patt) end
+
+--- Creates a match-time capture. Unlike all other captures, this one is evaluated immediately when a match occurs
+--- (even if it is part of a larger pattern that fails later). It forces the immediate evaluation of all its nested captures
+--- and then calls `function`. The given function gets as arguments the entire subject, the current position
+--- (after the match of `patt`), plus any capture values produced by `patt`. The first value returned by `function`
+--- defines how the match happens. If the call returns a number, the match succeeds and the returned number
+--- becomes the new current position. (Assuming a subject sand current position i, the returned number must be
+--- in the range [i, len(s) + 1].) If the call returns true, the match succeeds without consuming any input
+--- (so, to return true is equivalent to return i). If the call returns false, nil, or no value, the match fails.
+--- Any extra values returned by the function become the values produced by the capture.
+---
+--- @param patt vim.lpeg.Pattern
+--- @param fn function
+--- @return vim.lpeg.Capture
+function vim.lpeg.Cmt(patt, fn) end
diff --git a/runtime/lua/vim/_meta/misc.lua b/runtime/lua/vim/_meta/misc.lua
new file mode 100644
index 0000000000..0d70e16314
--- /dev/null
+++ b/runtime/lua/vim/_meta/misc.lua
@@ -0,0 +1,15 @@
+---@meta
+
+-- luacheck: no unused args
+
+--- Invokes |vim-function| or |user-function| {func} with arguments {...}.
+--- See also |vim.fn|.
+--- Equivalent to:
+---
+--- ```lua
+--- vim.fn[func]({...})
+--- ```
+---
+--- @param func fun()
+--- @param ... any
+function vim.call(func, ...) end
diff --git a/runtime/lua/vim/_meta/mpack.lua b/runtime/lua/vim/_meta/mpack.lua
new file mode 100644
index 0000000000..54e097ad97
--- /dev/null
+++ b/runtime/lua/vim/_meta/mpack.lua
@@ -0,0 +1,15 @@
+--- @meta
+
+-- luacheck: no unused args
+
+--- @defgroup vim.mpack
+---
+--- This module provides encoding and decoding of Lua objects to and
+--- from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
+
+--- Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object.
+--- @param str string
+function vim.mpack.decode(str) end
+
+--- Encodes (or "packs") Lua object {obj} as msgpack in a Lua string.
+function vim.mpack.encode(obj) end
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
new file mode 100644
index 0000000000..d2bdab4d28
--- /dev/null
+++ b/runtime/lua/vim/_meta/options.lua
@@ -0,0 +1,7958 @@
+--- @meta _
+-- THIS FILE IS GENERATED
+-- DO NOT EDIT
+error('Cannot require a meta file')
+
+---@class vim.bo
+---@field [integer] vim.bo
+vim.bo = vim.bo
+
+---@class vim.wo
+---@field [integer] vim.wo
+vim.wo = vim.wo
+
+--- Allow CTRL-_ in Insert and Command-line mode. This is default off, to
+--- avoid that users that accidentally type CTRL-_ instead of SHIFT-_ get
+--- into reverse Insert mode, and don't know how to get out. See
+--- 'revins'.
+---
+--- @type boolean
+vim.o.allowrevins = false
+vim.o.ari = vim.o.allowrevins
+vim.go.allowrevins = vim.o.allowrevins
+vim.go.ari = vim.go.allowrevins
+
+--- Tells Vim what to do with characters with East Asian Width Class
+--- Ambiguous (such as Euro, Registered Sign, Copyright Sign, Greek
+--- letters, Cyrillic letters).
+---
+--- There are currently two possible values:
+--- "single": Use the same width as characters in US-ASCII. This is
+--- expected by most users.
+--- "double": Use twice the width of ASCII characters.
+--- *E834* *E835*
+--- The value "double" cannot be used if 'listchars' or 'fillchars'
+--- contains a character that would be double width. These errors may
+--- also be given when calling setcellwidths().
+---
+--- The values are overruled for characters specified with
+--- `setcellwidths()`.
+---
+--- There are a number of CJK fonts for which the width of glyphs for
+--- those characters are solely based on how many octets they take in
+--- legacy/traditional CJK encodings. In those encodings, Euro,
+--- Registered sign, Greek/Cyrillic letters are represented by two octets,
+--- therefore those fonts have "wide" glyphs for them. This is also
+--- true of some line drawing characters used to make tables in text
+--- file. Therefore, when a CJK font is used for GUI Vim or
+--- Vim is running inside a terminal (emulators) that uses a CJK font
+--- (or Vim is run inside an xterm invoked with "-cjkwidth" option.),
+--- this option should be set to "double" to match the width perceived
+--- by Vim with the width of glyphs in the font. Perhaps it also has
+--- to be set to "double" under CJK MS-Windows when the system locale is
+--- set to one of CJK locales. See Unicode Standard Annex #11
+--- (https://www.unicode.org/reports/tr11).
+---
+--- @type string
+vim.o.ambiwidth = "single"
+vim.o.ambw = vim.o.ambiwidth
+vim.go.ambiwidth = vim.o.ambiwidth
+vim.go.ambw = vim.go.ambiwidth
+
+--- This option can be set to start editing Arabic text.
+--- Setting this option will:
+--- - Set the 'rightleft' option, unless 'termbidi' is set.
+--- - Set the 'arabicshape' option, unless 'termbidi' is set.
+--- - Set the 'keymap' option to "arabic"; in Insert mode CTRL-^ toggles
+--- between typing English and Arabic key mapping.
+--- - Set the 'delcombine' option
+---
+--- Resetting this option will:
+--- - Reset the 'rightleft' option.
+--- - Disable the use of 'keymap' (without changing its value).
+--- Note that 'arabicshape' and 'delcombine' are not reset (it is a global
+--- option).
+--- Also see `arabic.txt`.
+---
+--- @type boolean
+vim.o.arabic = false
+vim.o.arab = vim.o.arabic
+vim.wo.arabic = vim.o.arabic
+vim.wo.arab = vim.wo.arabic
+
+--- When on and 'termbidi' is off, the required visual character
+--- corrections that need to take place for displaying the Arabic language
+--- take effect. Shaping, in essence, gets enabled; the term is a broad
+--- one which encompasses:
+--- a) the changing/morphing of characters based on their location
+--- within a word (initial, medial, final and stand-alone).
+--- b) the enabling of the ability to compose characters
+--- c) the enabling of the required combining of some characters
+--- When disabled the display shows each character's true stand-alone
+--- form.
+--- Arabic is a complex language which requires other settings, for
+--- further details see `arabic.txt`.
+---
+--- @type boolean
+vim.o.arabicshape = true
+vim.o.arshape = vim.o.arabicshape
+vim.go.arabicshape = vim.o.arabicshape
+vim.go.arshape = vim.go.arabicshape
+
+--- When on, Vim will change the current working directory whenever you
+--- open a file, switch buffers, delete a buffer or open/close a window.
+--- It will change to the directory containing the file which was opened
+--- or selected. When a buffer has no name it also has no directory, thus
+--- the current directory won't change when navigating to it.
+--- Note: When this option is on some plugins may not work.
+---
+--- @type boolean
+vim.o.autochdir = false
+vim.o.acd = vim.o.autochdir
+vim.go.autochdir = vim.o.autochdir
+vim.go.acd = vim.go.autochdir
+
+--- Copy indent from current line when starting a new line (typing <CR>
+--- in Insert mode or when using the "o" or "O" command). If you do not
+--- type anything on the new line except <BS> or CTRL-D and then type
+--- <Esc>, CTRL-O or <CR>, the indent is deleted again. Moving the cursor
+--- to another line has the same effect, unless the 'I' flag is included
+--- in 'cpoptions'.
+--- When autoindent is on, formatting (with the "gq" command or when you
+--- reach 'textwidth' in Insert mode) uses the indentation of the first
+--- line.
+--- When 'smartindent' or 'cindent' is on the indent is changed in
+--- a different way.
+---
+--- @type boolean
+vim.o.autoindent = true
+vim.o.ai = vim.o.autoindent
+vim.bo.autoindent = vim.o.autoindent
+vim.bo.ai = vim.bo.autoindent
+
+--- When a file has been detected to have been changed outside of Vim and
+--- it has not been changed inside of Vim, automatically read it again.
+--- When the file has been deleted this is not done, so you have the text
+--- from before it was deleted. When it appears again then it is read.
+--- `timestamp`
+--- If this option has a local value, use this command to switch back to
+--- using the global value:
+--- ```
+--- :set autoread<
+--- ```
+---
+---
+--- @type boolean
+vim.o.autoread = true
+vim.o.ar = vim.o.autoread
+vim.bo.autoread = vim.o.autoread
+vim.bo.ar = vim.bo.autoread
+vim.go.autoread = vim.o.autoread
+vim.go.ar = vim.go.autoread
+
+--- Write the contents of the file, if it has been modified, on each
+--- `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`,
+--- `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when
+--- a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one
+--- to another file.
+--- A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is
+--- set to "hide" and `:next` is used.
+--- Note that for some commands the 'autowrite' option is not used, see
+--- 'autowriteall' for that.
+--- Some buffers will not be written, specifically when 'buftype' is
+--- "nowrite", "nofile", "terminal" or "prompt".
+--- USE WITH CARE: If you make temporary changes to a buffer that you
+--- don't want to be saved this option may cause it to be saved anyway.
+--- Renaming the buffer with ":file {name}" may help avoid this.
+---
+--- @type boolean
+vim.o.autowrite = false
+vim.o.aw = vim.o.autowrite
+vim.go.autowrite = vim.o.autowrite
+vim.go.aw = vim.go.autowrite
+
+--- Like 'autowrite', but also used for commands ":edit", ":enew", ":quit",
+--- ":qall", ":exit", ":xit", ":recover" and closing the Vim window.
+--- Setting this option also implies that Vim behaves like 'autowrite' has
+--- been set.
+---
+--- @type boolean
+vim.o.autowriteall = false
+vim.o.awa = vim.o.autowriteall
+vim.go.autowriteall = vim.o.autowriteall
+vim.go.awa = vim.go.autowriteall
+
+--- When set to "dark" or "light", adjusts the default color groups for
+--- that background type. The `TUI` or other UI sets this on startup
+--- (triggering `OptionSet`) if it can detect the background color.
+---
+--- This option does NOT change the background color, it tells Nvim what
+--- the "inherited" (terminal/GUI) background looks like.
+--- See `:hi-normal` if you want to set the background color explicitly.
+--- *g:colors_name*
+--- When a color scheme is loaded (the "g:colors_name" variable is set)
+--- setting 'background' will cause the color scheme to be reloaded. If
+--- the color scheme adjusts to the value of 'background' this will work.
+--- However, if the color scheme sets 'background' itself the effect may
+--- be undone. First delete the "g:colors_name" variable when needed.
+---
+--- Normally this option would be set in the vimrc file. Possibly
+--- depending on the terminal name. Example:
+--- ```
+--- :if $TERM ==# "xterm"
+--- : set background=dark
+--- :endif
+--- ```
+--- When this option is set, the default settings for the highlight groups
+--- will change. To use other settings, place ":highlight" commands AFTER
+--- the setting of the 'background' option.
+--- This option is also used in the "$VIMRUNTIME/syntax/syntax.vim" file
+--- to select the colors for syntax highlighting. After changing this
+--- option, you must load syntax.vim again to see the result. This can be
+--- done with ":syntax on".
+---
+--- @type string
+vim.o.background = "dark"
+vim.o.bg = vim.o.background
+vim.go.background = vim.o.background
+vim.go.bg = vim.go.background
+
+--- Influences the working of <BS>, <Del>, CTRL-W and CTRL-U in Insert
+--- mode. This is a list of items, separated by commas. Each item allows
+--- a way to backspace over something:
+--- value effect ~
+--- indent allow backspacing over autoindent
+--- eol allow backspacing over line breaks (join lines)
+--- start allow backspacing over the start of insert; CTRL-W and CTRL-U
+--- stop once at the start of insert.
+--- nostop like start, except CTRL-W and CTRL-U do not stop at the start of
+--- insert.
+---
+--- When the value is empty, Vi compatible backspacing is used, none of
+--- the ways mentioned for the items above are possible.
+---
+--- @type string
+vim.o.backspace = "indent,eol,start"
+vim.o.bs = vim.o.backspace
+vim.go.backspace = vim.o.backspace
+vim.go.bs = vim.go.backspace
+
+--- Make a backup before overwriting a file. Leave it around after the
+--- file has been successfully written. If you do not want to keep the
+--- backup file, but you do want a backup while the file is being
+--- written, reset this option and set the 'writebackup' option (this is
+--- the default). If you do not want a backup file at all reset both
+--- options (use this if your file system is almost full). See the
+--- `backup-table` for more explanations.
+--- When the 'backupskip' pattern matches, a backup is not made anyway.
+--- When 'patchmode' is set, the backup may be renamed to become the
+--- oldest version of a file.
+---
+--- @type boolean
+vim.o.backup = false
+vim.o.bk = vim.o.backup
+vim.go.backup = vim.o.backup
+vim.go.bk = vim.go.backup
+
+--- When writing a file and a backup is made, this option tells how it's
+--- done. This is a comma-separated list of words.
+---
+--- The main values are:
+--- "yes" make a copy of the file and overwrite the original one
+--- "no" rename the file and write a new one
+--- "auto" one of the previous, what works best
+---
+--- Extra values that can be combined with the ones above are:
+--- "breaksymlink" always break symlinks when writing
+--- "breakhardlink" always break hardlinks when writing
+---
+--- Making a copy and overwriting the original file:
+--- - Takes extra time to copy the file.
+--- + When the file has special attributes, is a (hard/symbolic) link or
+--- has a resource fork, all this is preserved.
+--- - When the file is a link the backup will have the name of the link,
+--- not of the real file.
+---
+--- Renaming the file and writing a new one:
+--- + It's fast.
+--- - Sometimes not all attributes of the file can be copied to the new
+--- file.
+--- - When the file is a link the new file will not be a link.
+---
+--- The "auto" value is the middle way: When Vim sees that renaming the
+--- file is possible without side effects (the attributes can be passed on
+--- and the file is not a link) that is used. When problems are expected,
+--- a copy will be made.
+---
+--- The "breaksymlink" and "breakhardlink" values can be used in
+--- combination with any of "yes", "no" and "auto". When included, they
+--- force Vim to always break either symbolic or hard links by doing
+--- exactly what the "no" option does, renaming the original file to
+--- become the backup and writing a new file in its place. This can be
+--- useful for example in source trees where all the files are symbolic or
+--- hard links and any changes should stay in the local source tree, not
+--- be propagated back to the original source.
+--- *crontab*
+--- One situation where "no" and "auto" will cause problems: A program
+--- that opens a file, invokes Vim to edit that file, and then tests if
+--- the open file was changed (through the file descriptor) will check the
+--- backup file instead of the newly created file. "crontab -e" is an
+--- example.
+---
+--- When a copy is made, the original file is truncated and then filled
+--- with the new text. This means that protection bits, owner and
+--- symbolic links of the original file are unmodified. The backup file,
+--- however, is a new file, owned by the user who edited the file. The
+--- group of the backup is set to the group of the original file. If this
+--- fails, the protection bits for the group are made the same as for
+--- others.
+---
+--- When the file is renamed, this is the other way around: The backup has
+--- the same attributes of the original file, and the newly written file
+--- is owned by the current user. When the file was a (hard/symbolic)
+--- link, the new file will not! That's why the "auto" value doesn't
+--- rename when the file is a link. The owner and group of the newly
+--- written file will be set to the same ones as the original file, but
+--- the system may refuse to do this. In that case the "auto" value will
+--- again not rename the file.
+---
+--- @type string
+vim.o.backupcopy = "auto"
+vim.o.bkc = vim.o.backupcopy
+vim.bo.backupcopy = vim.o.backupcopy
+vim.bo.bkc = vim.bo.backupcopy
+vim.go.backupcopy = vim.o.backupcopy
+vim.go.bkc = vim.go.backupcopy
+
+--- List of directories for the backup file, separated with commas.
+--- - The backup file will be created in the first directory in the list
+--- where this is possible. If none of the directories exist Nvim will
+--- attempt to create the last directory in the list.
+--- - Empty means that no backup file will be created ('patchmode' is
+--- impossible!). Writing may fail because of this.
+--- - A directory "." means to put the backup file in the same directory
+--- as the edited file.
+--- - A directory starting with "./" (or ".\" for MS-Windows) means to put
+--- the backup file relative to where the edited file is. The leading
+--- "." is replaced with the path name of the edited file.
+--- ("." inside a directory name has no special meaning).
+--- - Spaces after the comma are ignored, other spaces are considered part
+--- of the directory name. To have a space at the start of a directory
+--- name, precede it with a backslash.
+--- - To include a comma in a directory name precede it with a backslash.
+--- - A directory name may end in an '/'.
+--- - For Unix and Win32, if a directory ends in two path separators "//",
+--- the swap file name will be built from the complete path to the file
+--- with all path separators changed to percent '%' signs. This will
+--- ensure file name uniqueness in the backup directory.
+--- On Win32, it is also possible to end with "\\". However, When a
+--- separating comma is following, you must use "//", since "\\" will
+--- include the comma in the file name. Therefore it is recommended to
+--- use '//', instead of '\\'.
+--- - Environment variables are expanded `:set_env`.
+--- - Careful with '\' characters, type one before a space, type two to
+--- get one in the option (see `option-backslash`), for example:
+--- ```
+--- :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+--- ```
+---
+--- See also 'backup' and 'writebackup' options.
+--- If you want to hide your backup files on Unix, consider this value:
+--- ```
+--- :set backupdir=./.backup,~/.backup,.,/tmp
+--- ```
+--- You must create a ".backup" directory in each directory and in your
+--- home directory for this to work properly.
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- directories from the list. This avoids problems when a future version
+--- uses another default.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.backupdir = ".,$XDG_STATE_HOME/nvim/backup//"
+vim.o.bdir = vim.o.backupdir
+vim.go.backupdir = vim.o.backupdir
+vim.go.bdir = vim.go.backupdir
+
+--- String which is appended to a file name to make the name of the
+--- backup file. The default is quite unusual, because this avoids
+--- accidentally overwriting existing files with a backup file. You might
+--- prefer using ".bak", but make sure that you don't have files with
+--- ".bak" that you want to keep.
+--- Only normal file name characters can be used; `/\*?[|<>` are illegal.
+---
+--- If you like to keep a lot of backups, you could use a BufWritePre
+--- autocommand to change 'backupext' just before writing the file to
+--- include a timestamp.
+--- ```
+--- :au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~'
+--- ```
+--- Use 'backupdir' to put the backup in a different directory.
+---
+--- @type string
+vim.o.backupext = "~"
+vim.o.bex = vim.o.backupext
+vim.go.backupext = vim.o.backupext
+vim.go.bex = vim.go.backupext
+
+--- A list of file patterns. When one of the patterns matches with the
+--- name of the file which is written, no backup file is created. Both
+--- the specified file name and the full path name of the file are used.
+--- The pattern is used like with `:autocmd`, see `autocmd-pattern`.
+--- Watch out for special characters, see `option-backslash`.
+--- When $TMPDIR, $TMP or $TEMP is not defined, it is not used for the
+--- default value. "/tmp/*" is only used for Unix.
+---
+--- WARNING: Not having a backup file means that when Vim fails to write
+--- your buffer correctly and then, for whatever reason, Vim exits, you
+--- lose both the original file and what you were writing. Only disable
+--- backups if you don't care about losing the file.
+---
+--- Note that environment variables are not expanded. If you want to use
+--- $HOME you must expand it explicitly, e.g.:
+---
+--- ```vim
+--- :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
+--- ```
+--- Note that the default also makes sure that "crontab -e" works (when a
+--- backup would be made by renaming the original file crontab won't see
+--- the newly created file). Also see 'backupcopy' and `crontab`.
+---
+--- @type string
+vim.o.backupskip = "/tmp/*"
+vim.o.bsk = vim.o.backupskip
+vim.go.backupskip = vim.o.backupskip
+vim.go.bsk = vim.go.backupskip
+
+--- Specifies for which events the bell will not be rung. It is a comma-
+--- separated list of items. For each item that is present, the bell
+--- will be silenced. This is most useful to specify specific events in
+--- insert mode to be silenced.
+---
+--- item meaning when present ~
+--- all All events.
+--- backspace When hitting <BS> or <Del> and deleting results in an
+--- error.
+--- cursor Fail to move around using the cursor keys or
+--- <PageUp>/<PageDown> in `Insert-mode`.
+--- complete Error occurred when using `i_CTRL-X_CTRL-K` or
+--- `i_CTRL-X_CTRL-T`.
+--- copy Cannot copy char from insert mode using `i_CTRL-Y` or
+--- `i_CTRL-E`.
+--- ctrlg Unknown Char after <C-G> in Insert mode.
+--- error Other Error occurred (e.g. try to join last line)
+--- (mostly used in `Normal-mode` or `Cmdline-mode`).
+--- esc hitting <Esc> in `Normal-mode`.
+--- hangul Ignored.
+--- lang Calling the beep module for Lua/Mzscheme/TCL.
+--- mess No output available for `g<`.
+--- showmatch Error occurred for 'showmatch' function.
+--- operator Empty region error `cpo-E`.
+--- register Unknown register after <C-R> in `Insert-mode`.
+--- shell Bell from shell output `:!`.
+--- spell Error happened on spell suggest.
+--- wildmode More matches in `cmdline-completion` available
+--- (depends on the 'wildmode' setting).
+---
+--- This is most useful to fine tune when in Insert mode the bell should
+--- be rung. For Normal mode and Ex commands, the bell is often rung to
+--- indicate that an error occurred. It can be silenced by adding the
+--- "error" keyword.
+---
+--- @type string
+vim.o.belloff = "all"
+vim.o.bo = vim.o.belloff
+vim.go.belloff = vim.o.belloff
+vim.go.bo = vim.go.belloff
+
+--- This option should be set before editing a binary file. You can also
+--- use the `-b` Vim argument. When this option is switched on a few
+--- options will be changed (also when it already was on):
+--- 'textwidth' will be set to 0
+--- 'wrapmargin' will be set to 0
+--- 'modeline' will be off
+--- 'expandtab' will be off
+--- Also, 'fileformat' and 'fileformats' options will not be used, the
+--- file is read and written like 'fileformat' was "unix" (a single <NL>
+--- separates lines).
+--- The 'fileencoding' and 'fileencodings' options will not be used, the
+--- file is read without conversion.
+--- NOTE: When you start editing a(nother) file while the 'bin' option is
+--- on, settings from autocommands may change the settings again (e.g.,
+--- 'textwidth'), causing trouble when editing. You might want to set
+--- 'bin' again when the file has been loaded.
+--- The previous values of these options are remembered and restored when
+--- 'bin' is switched from on to off. Each buffer has its own set of
+--- saved option values.
+--- To edit a file with 'binary' set you can use the `++bin` argument.
+--- This avoids you have to do ":set bin", which would have effect for all
+--- files you edit.
+--- When writing a file the <EOL> for the last line is only written if
+--- there was one in the original file (normally Vim appends an <EOL> to
+--- the last line if there is none; this would make the file longer). See
+--- the 'endofline' option.
+---
+--- @type boolean
+vim.o.binary = false
+vim.o.bin = vim.o.binary
+vim.bo.binary = vim.o.binary
+vim.bo.bin = vim.bo.binary
+
+--- When writing a file and the following conditions are met, a BOM (Byte
+--- Order Mark) is prepended to the file:
+--- - this option is on
+--- - the 'binary' option is off
+--- - 'fileencoding' is "utf-8", "ucs-2", "ucs-4" or one of the little/big
+--- endian variants.
+--- Some applications use the BOM to recognize the encoding of the file.
+--- Often used for UCS-2 files on MS-Windows. For other applications it
+--- causes trouble, for example: "cat file1 file2" makes the BOM of file2
+--- appear halfway through the resulting file. Gcc doesn't accept a BOM.
+--- When Vim reads a file and 'fileencodings' starts with "ucs-bom", a
+--- check for the presence of the BOM is done and 'bomb' set accordingly.
+--- Unless 'binary' is set, it is removed from the first line, so that you
+--- don't see it when editing. When you don't change the options, the BOM
+--- will be restored when writing the file.
+---
+--- @type boolean
+vim.o.bomb = false
+vim.bo.bomb = vim.o.bomb
+
+--- This option lets you choose which characters might cause a line
+--- break if 'linebreak' is on. Only works for ASCII characters.
+---
+--- @type string
+vim.o.breakat = " \t!@*-+;:,./?"
+vim.o.brk = vim.o.breakat
+vim.go.breakat = vim.o.breakat
+vim.go.brk = vim.go.breakat
+
+--- Every wrapped line will continue visually indented (same amount of
+--- space as the beginning of that line), thus preserving horizontal blocks
+--- of text.
+---
+--- @type boolean
+vim.o.breakindent = false
+vim.o.bri = vim.o.breakindent
+vim.wo.breakindent = vim.o.breakindent
+vim.wo.bri = vim.wo.breakindent
+
+--- Settings for 'breakindent'. It can consist of the following optional
+--- items and must be separated by a comma:
+--- min:{n} Minimum text width that will be kept after
+--- applying 'breakindent', even if the resulting
+--- text should normally be narrower. This prevents
+--- text indented almost to the right window border
+--- occupying lot of vertical space when broken.
+--- (default: 20)
+--- shift:{n} After applying 'breakindent', the wrapped line's
+--- beginning will be shifted by the given number of
+--- characters. It permits dynamic French paragraph
+--- indentation (negative) or emphasizing the line
+--- continuation (positive).
+--- (default: 0)
+--- sbr Display the 'showbreak' value before applying the
+--- additional indent.
+--- (default: off)
+--- list:{n} Adds an additional indent for lines that match a
+--- numbered or bulleted list (using the
+--- 'formatlistpat' setting).
+--- list:-1 Uses the length of a match with 'formatlistpat'
+--- for indentation.
+--- (default: 0)
+--- column:{n} Indent at column {n}. Will overrule the other
+--- sub-options. Note: an additional indent may be
+--- added for the 'showbreak' setting.
+--- (default: off)
+---
+--- @type string
+vim.o.breakindentopt = ""
+vim.o.briopt = vim.o.breakindentopt
+vim.wo.breakindentopt = vim.o.breakindentopt
+vim.wo.briopt = vim.wo.breakindentopt
+
+--- Which directory to use for the file browser:
+--- last Use same directory as with last file browser, where a
+--- file was opened or saved.
+--- buffer Use the directory of the related buffer.
+--- current Use the current directory.
+--- {path} Use the specified directory
+---
+--- @type string
+vim.o.browsedir = ""
+vim.o.bsdir = vim.o.browsedir
+vim.go.browsedir = vim.o.browsedir
+vim.go.bsdir = vim.go.browsedir
+
+--- This option specifies what happens when a buffer is no longer
+--- displayed in a window:
+--- <empty> follow the global 'hidden' option
+--- hide hide the buffer (don't unload it), even if 'hidden' is
+--- not set
+--- unload unload the buffer, even if 'hidden' is set; the
+--- `:hide` command will also unload the buffer
+--- delete delete the buffer from the buffer list, even if
+--- 'hidden' is set; the `:hide` command will also delete
+--- the buffer, making it behave like `:bdelete`
+--- wipe wipe the buffer from the buffer list, even if
+--- 'hidden' is set; the `:hide` command will also wipe
+--- out the buffer, making it behave like `:bwipeout`
+---
+--- CAREFUL: when "unload", "delete" or "wipe" is used changes in a buffer
+--- are lost without a warning. Also, these values may break autocommands
+--- that switch between buffers temporarily.
+--- This option is used together with 'buftype' and 'swapfile' to specify
+--- special kinds of buffers. See `special-buffers`.
+---
+--- @type string
+vim.o.bufhidden = ""
+vim.o.bh = vim.o.bufhidden
+vim.bo.bufhidden = vim.o.bufhidden
+vim.bo.bh = vim.bo.bufhidden
+
+--- When this option is set, the buffer shows up in the buffer list. If
+--- it is reset it is not used for ":bnext", "ls", the Buffers menu, etc.
+--- This option is reset by Vim for buffers that are only used to remember
+--- a file name or marks. Vim sets it when starting to edit a buffer.
+--- But not when moving to a buffer with ":buffer".
+---
+--- @type boolean
+vim.o.buflisted = true
+vim.o.bl = vim.o.buflisted
+vim.bo.buflisted = vim.o.buflisted
+vim.bo.bl = vim.bo.buflisted
+
+--- The value of this option specifies the type of a buffer:
+--- <empty> normal buffer
+--- acwrite buffer will always be written with `BufWriteCmd`s
+--- help help buffer (do not set this manually)
+--- nofile buffer is not related to a file, will not be written
+--- nowrite buffer will not be written
+--- quickfix list of errors `:cwindow` or locations `:lwindow`
+--- terminal `terminal-emulator` buffer
+--- prompt buffer where only the last line can be edited, meant
+--- to be used by a plugin, see `prompt-buffer`
+---
+--- This option is used together with 'bufhidden' and 'swapfile' to
+--- specify special kinds of buffers. See `special-buffers`.
+--- Also see `win_gettype()`, which returns the type of the window.
+---
+--- Be careful with changing this option, it can have many side effects!
+--- One such effect is that Vim will not check the timestamp of the file,
+--- if the file is changed by another program this will not be noticed.
+---
+--- A "quickfix" buffer is only used for the error list and the location
+--- list. This value is set by the `:cwindow` and `:lwindow` commands and
+--- you are not supposed to change it.
+---
+--- "nofile" and "nowrite" buffers are similar:
+--- both: The buffer is not to be written to disk, ":w" doesn't
+--- work (":w filename" does work though).
+--- both: The buffer is never considered to be `'modified'`.
+--- There is no warning when the changes will be lost, for
+--- example when you quit Vim.
+--- both: A swap file is only created when using too much memory
+--- (when 'swapfile' has been reset there is never a swap
+--- file).
+--- nofile only: The buffer name is fixed, it is not handled like a
+--- file name. It is not modified in response to a `:cd`
+--- command.
+--- both: When using ":e bufname" and already editing "bufname"
+--- the buffer is made empty and autocommands are
+--- triggered as usual for `:edit`.
+--- *E676*
+--- "acwrite" implies that the buffer name is not related to a file, like
+--- "nofile", but it will be written. Thus, in contrast to "nofile" and
+--- "nowrite", ":w" does work and a modified buffer can't be abandoned
+--- without saving. For writing there must be matching `BufWriteCmd|,
+--- |FileWriteCmd` or `FileAppendCmd` autocommands.
+---
+--- @type string
+vim.o.buftype = ""
+vim.o.bt = vim.o.buftype
+vim.bo.buftype = vim.o.buftype
+vim.bo.bt = vim.bo.buftype
+
+--- Specifies details about changing the case of letters. It may contain
+--- these words, separated by a comma:
+--- internal Use internal case mapping functions, the current
+--- locale does not change the case mapping. When
+--- "internal" is omitted, the towupper() and towlower()
+--- system library functions are used when available.
+--- keepascii For the ASCII characters (0x00 to 0x7f) use the US
+--- case mapping, the current locale is not effective.
+--- This probably only matters for Turkish.
+---
+--- @type string
+vim.o.casemap = "internal,keepascii"
+vim.o.cmp = vim.o.casemap
+vim.go.casemap = vim.o.casemap
+vim.go.cmp = vim.go.casemap
+
+--- When on, `:cd`, `:tcd` and `:lcd` without an argument changes the
+--- current working directory to the `$HOME` directory like in Unix.
+--- When off, those commands just print the current directory name.
+--- On Unix this option has no effect.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type boolean
+vim.o.cdhome = false
+vim.o.cdh = vim.o.cdhome
+vim.go.cdhome = vim.o.cdhome
+vim.go.cdh = vim.go.cdhome
+
+--- This is a list of directories which will be searched when using the
+--- `:cd`, `:tcd` and `:lcd` commands, provided that the directory being
+--- searched for has a relative path, not an absolute part starting with
+--- "/", "./" or "../", the 'cdpath' option is not used then.
+--- The 'cdpath' option's value has the same form and semantics as
+--- `'path'`. Also see `file-searching`.
+--- The default value is taken from $CDPATH, with a "," prepended to look
+--- in the current directory first.
+--- If the default value taken from $CDPATH is not what you want, include
+--- a modified version of the following command in your vimrc file to
+--- override it:
+--- ```
+--- :let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g')
+--- ```
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+--- (parts of 'cdpath' can be passed to the shell to expand file names).
+---
+--- @type string
+vim.o.cdpath = ",,"
+vim.o.cd = vim.o.cdpath
+vim.go.cdpath = vim.o.cdpath
+vim.go.cd = vim.go.cdpath
+
+--- The key used in Command-line Mode to open the command-line window.
+--- Only non-printable keys are allowed.
+--- The key can be specified as a single character, but it is difficult to
+--- type. The preferred way is to use the <> notation. Examples:
+--- ```
+--- :exe "set cedit=\\<C-Y>"
+--- :exe "set cedit=\\<Esc>"
+--- ```
+--- `Nvi` also has this option, but it only uses the first character.
+--- See `cmdwin`.
+---
+--- @type string
+vim.o.cedit = "\6"
+vim.go.cedit = vim.o.cedit
+
+--- `channel` connected to the buffer, or 0 if no channel is connected.
+--- In a `:terminal` buffer this is the terminal channel.
+--- Read-only.
+---
+--- @type integer
+vim.o.channel = 0
+vim.bo.channel = vim.o.channel
+
+--- An expression that is used for character encoding conversion. It is
+--- evaluated when a file that is to be read or has been written has a
+--- different encoding from what is desired.
+--- 'charconvert' is not used when the internal iconv() function is
+--- supported and is able to do the conversion. Using iconv() is
+--- 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, 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.
+--- Conversion between "latin1", "unicode", "ucs-2", "ucs-4" and "utf-8"
+--- is done internally by Vim, 'charconvert' is not used for this.
+--- Also used for Unicode conversion.
+--- Example:
+--- ```
+--- set charconvert=CharConvert()
+--- fun CharConvert()
+--- system("recode "
+--- \ .. v:charconvert_from .. ".." .. v:charconvert_to
+--- \ .. " <" .. v:fname_in .. " >" .. v:fname_out)
+--- return v:shell_error
+--- endfun
+--- ```
+--- The related Vim variables are:
+--- v:charconvert_from name of the current encoding
+--- v:charconvert_to name of the desired encoding
+--- v:fname_in name of the input file
+--- v:fname_out name of the output file
+--- Note that v:fname_in and v:fname_out will never be the same.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.charconvert = ""
+vim.o.ccv = vim.o.charconvert
+vim.go.charconvert = vim.o.charconvert
+vim.go.ccv = vim.go.charconvert
+
+--- Enables automatic C program indenting. See 'cinkeys' to set the keys
+--- that trigger reindenting in insert mode and 'cinoptions' to set your
+--- preferred indent style.
+--- If 'indentexpr' is not empty, it overrules 'cindent'.
+--- If 'lisp' is not on and both 'indentexpr' and 'equalprg' are empty,
+--- the "=" operator indents using this algorithm rather than calling an
+--- external program.
+--- See `C-indenting`.
+--- When you don't like the way 'cindent' works, try the 'smartindent'
+--- option or 'indentexpr'.
+---
+--- @type boolean
+vim.o.cindent = false
+vim.o.cin = vim.o.cindent
+vim.bo.cindent = vim.o.cindent
+vim.bo.cin = vim.bo.cindent
+
+--- A list of keys that, when typed in Insert mode, cause reindenting of
+--- the current line. Only used if 'cindent' is on and 'indentexpr' is
+--- empty.
+--- For the format of this option see `cinkeys-format`.
+--- See `C-indenting`.
+---
+--- @type string
+vim.o.cinkeys = "0{,0},0),0],:,0#,!^F,o,O,e"
+vim.o.cink = vim.o.cinkeys
+vim.bo.cinkeys = vim.o.cinkeys
+vim.bo.cink = vim.bo.cinkeys
+
+--- The 'cinoptions' affect the way 'cindent' reindents lines in a C
+--- program. See `cinoptions-values` for the values of this option, and
+--- `C-indenting` for info on C indenting in general.
+---
+--- @type string
+vim.o.cinoptions = ""
+vim.o.cino = vim.o.cinoptions
+vim.bo.cinoptions = vim.o.cinoptions
+vim.bo.cino = vim.bo.cinoptions
+
+--- Keywords that are interpreted as a C++ scope declaration by `cino-g`.
+--- Useful e.g. for working with the Qt framework that defines additional
+--- scope declarations "signals", "public slots" and "private slots":
+--- ```
+--- set cinscopedecls+=signals,public\ slots,private\ slots
+--- ```
+---
+---
+--- @type string
+vim.o.cinscopedecls = "public,protected,private"
+vim.o.cinsd = vim.o.cinscopedecls
+vim.bo.cinscopedecls = vim.o.cinscopedecls
+vim.bo.cinsd = vim.bo.cinscopedecls
+
+--- These keywords start an extra indent in the next line when
+--- 'smartindent' or 'cindent' is set. For 'cindent' this is only done at
+--- an appropriate place (inside {}).
+--- Note that 'ignorecase' isn't used for 'cinwords'. If case doesn't
+--- matter, include the keyword both the uppercase and lowercase:
+--- "if,If,IF".
+---
+--- @type string
+vim.o.cinwords = "if,else,while,do,for,switch"
+vim.o.cinw = vim.o.cinwords
+vim.bo.cinwords = vim.o.cinwords
+vim.bo.cinw = vim.bo.cinwords
+
+--- This option is a list of comma-separated names.
+--- These names are recognized:
+---
+--- *clipboard-unnamed*
+--- unnamed When included, Vim will use the clipboard register "*"
+--- for all yank, delete, change and put operations which
+--- would normally go to the unnamed register. When a
+--- register is explicitly specified, it will always be
+--- used regardless of whether "unnamed" is in 'clipboard'
+--- or not. The clipboard register can always be
+--- explicitly accessed using the "* notation. Also see
+--- `clipboard`.
+---
+--- *clipboard-unnamedplus*
+--- unnamedplus A variant of the "unnamed" flag which uses the
+--- clipboard register "+" (`quoteplus`) instead of
+--- register "*" for all yank, delete, change and put
+--- operations which would normally go to the unnamed
+--- register. When "unnamed" is also included to the
+--- option, yank and delete operations (but not put)
+--- will additionally copy the text into register
+--- "*". See `clipboard`.
+---
+--- @type string
+vim.o.clipboard = ""
+vim.o.cb = vim.o.clipboard
+vim.go.clipboard = vim.o.clipboard
+vim.go.cb = vim.go.clipboard
+
+--- Number of screen lines to use for the command-line. Helps avoiding
+--- `hit-enter` prompts.
+--- The value of this option is stored with the tab page, so that each tab
+--- page can have a different value.
+---
+--- When 'cmdheight' is zero, there is no command-line unless it is being
+--- used. The command-line will cover the last line of the screen when
+--- shown.
+---
+--- WARNING: `cmdheight=0` is considered experimental. Expect some
+--- unwanted behaviour. Some 'shortmess' flags and similar
+--- mechanism might fail to take effect, causing unwanted hit-enter
+--- prompts. Some informative messages, both from Nvim itself and
+--- plugins, will not be displayed.
+---
+--- @type integer
+vim.o.cmdheight = 1
+vim.o.ch = vim.o.cmdheight
+vim.go.cmdheight = vim.o.cmdheight
+vim.go.ch = vim.go.cmdheight
+
+--- Number of screen lines to use for the command-line window. `cmdwin`
+---
+--- @type integer
+vim.o.cmdwinheight = 7
+vim.o.cwh = vim.o.cmdwinheight
+vim.go.cmdwinheight = vim.o.cmdwinheight
+vim.go.cwh = vim.go.cmdwinheight
+
+--- 'colorcolumn' is a comma-separated list of screen columns that are
+--- highlighted with ColorColumn `hl-ColorColumn`. Useful to align
+--- text. Will make screen redrawing slower.
+--- The screen column can be an absolute number, or a number preceded with
+--- '+' or '-', which is added to or subtracted from 'textwidth'.
+--- ```
+--- :set cc=+1 " highlight column after 'textwidth'
+--- :set cc=+1,+2,+3 " highlight three columns after 'textwidth'
+--- :hi ColorColumn ctermbg=lightgrey guibg=lightgrey
+--- ```
+---
+--- When 'textwidth' is zero then the items with '-' and '+' are not used.
+--- A maximum of 256 columns are highlighted.
+---
+--- @type string
+vim.o.colorcolumn = ""
+vim.o.cc = vim.o.colorcolumn
+vim.wo.colorcolumn = vim.o.colorcolumn
+vim.wo.cc = vim.wo.colorcolumn
+
+--- Number of columns of the screen. Normally this is set by the terminal
+--- initialization and does not have to be set by hand.
+--- When Vim is running in the GUI or in a resizable window, setting this
+--- option will cause the window size to be changed. When you only want
+--- to use the size for the GUI, put the command in your `ginit.vim` file.
+--- When you set this option and Vim is unable to change the physical
+--- number of columns of the display, the display may be messed up. For
+--- the GUI it is always possible and Vim limits the number of columns to
+--- what fits on the screen. You can use this command to get the widest
+--- window possible:
+--- ```
+--- :set columns=9999
+--- ```
+--- Minimum value is 12, maximum value is 10000.
+---
+--- @type integer
+vim.o.columns = 80
+vim.o.co = vim.o.columns
+vim.go.columns = vim.o.columns
+vim.go.co = vim.go.columns
+
+--- A comma-separated list of strings that can start a comment line. See
+--- `format-comments`. See `option-backslash` about using backslashes to
+--- insert a space.
+---
+--- @type string
+vim.o.comments = "s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•"
+vim.o.com = vim.o.comments
+vim.bo.comments = vim.o.comments
+vim.bo.com = vim.bo.comments
+
+--- A template for a comment. The "%s" in the value is replaced with the
+--- comment text. For example, C uses "/*%s*/". Currently only used to
+--- add markers for folding, see `fold-marker`.
+---
+--- @type string
+vim.o.commentstring = ""
+vim.o.cms = vim.o.commentstring
+vim.bo.commentstring = vim.o.commentstring
+vim.bo.cms = vim.bo.commentstring
+
+--- This option specifies how keyword completion `ins-completion` works
+--- when CTRL-P or CTRL-N are used. It is also used for whole-line
+--- completion `i_CTRL-X_CTRL-L`. It indicates the type of completion
+--- and the places to scan. It is a comma-separated list of flags:
+--- . scan the current buffer ('wrapscan' is ignored)
+--- w scan buffers from other windows
+--- b scan other loaded buffers that are in the buffer list
+--- u scan the unloaded buffers that are in the buffer list
+--- U scan the buffers that are not in the buffer list
+--- k scan the files given with the 'dictionary' option
+--- kspell use the currently active spell checking `spell`
+--- k{dict} scan the file {dict}. Several "k" flags can be given,
+--- patterns are valid too. For example:
+--- ```
+--- :set cpt=k/usr/dict/*,k~/spanish
+--- ```
+--- s scan the files given with the 'thesaurus' option
+--- s{tsr} scan the file {tsr}. Several "s" flags can be given, patterns
+--- are valid too.
+--- i scan current and included files
+--- d scan current and included files for defined name or macro
+--- `i_CTRL-X_CTRL-D`
+--- ] tag completion
+--- t same as "]"
+--- f scan the buffer names (as opposed to buffer contents)
+---
+--- Unloaded buffers are not loaded, thus their autocmds `:autocmd` are
+--- not executed, this may lead to unexpected completions from some files
+--- (gzipped files for example). Unloaded buffers are not scanned for
+--- whole-line completion.
+---
+--- As you can see, CTRL-N and CTRL-P can be used to do any 'iskeyword'-
+--- based expansion (e.g., dictionary `i_CTRL-X_CTRL-K`, included patterns
+--- `i_CTRL-X_CTRL-I`, tags `i_CTRL-X_CTRL-]` and normal expansions).
+---
+--- @type string
+vim.o.complete = ".,w,b,u,t"
+vim.o.cpt = vim.o.complete
+vim.bo.complete = vim.o.complete
+vim.bo.cpt = vim.bo.complete
+
+--- This option specifies a function to be used for Insert mode completion
+--- with CTRL-X CTRL-U. `i_CTRL-X_CTRL-U`
+--- See `complete-functions` for an explanation of how the function is
+--- invoked and what it should return. The value can be the name of a
+--- function, a `lambda` or a `Funcref`. See `option-value-function` for
+--- more information.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.completefunc = ""
+vim.o.cfu = vim.o.completefunc
+vim.bo.completefunc = vim.o.completefunc
+vim.bo.cfu = vim.bo.completefunc
+
+--- A comma-separated list of options for Insert mode completion
+--- `ins-completion`. The supported values are:
+---
+--- menu Use a popup menu to show the possible completions. The
+--- menu is only shown when there is more than one match and
+--- sufficient colors are available. `ins-completion-menu`
+---
+--- menuone Use the popup menu also when there is only one match.
+--- Useful when there is additional information about the
+--- match, e.g., what file it comes from.
+---
+--- longest Only insert the longest common text of the matches. If
+--- the menu is displayed you can use CTRL-L to add more
+--- characters. Whether case is ignored depends on the kind
+--- of completion. For buffer text the 'ignorecase' option is
+--- used.
+---
+--- preview Show extra information about the currently selected
+--- completion in the preview window. Only works in
+--- combination with "menu" or "menuone".
+---
+--- noinsert Do not insert any text for a match until the user selects
+--- a match from the menu. Only works in combination with
+--- "menu" or "menuone". No effect if "longest" is present.
+---
+--- noselect Do not select a match in the menu, force the user to
+--- select one from the menu. Only works in combination with
+--- "menu" or "menuone".
+---
+--- @type string
+vim.o.completeopt = "menu,preview"
+vim.o.cot = vim.o.completeopt
+vim.go.completeopt = vim.o.completeopt
+vim.go.cot = vim.go.completeopt
+
+--- only for MS-Windows
+--- When this option is set it overrules 'shellslash' for completion:
+--- - When this option is set to "slash", a forward slash is used for path
+--- completion in insert mode. This is useful when editing HTML tag, or
+--- Makefile with 'noshellslash' on MS-Windows.
+--- - When this option is set to "backslash", backslash is used. This is
+--- useful when editing a batch file with 'shellslash' set on MS-Windows.
+--- - When this option is empty, same character is used as for
+--- 'shellslash'.
+--- For Insert mode completion the buffer-local value is used. For
+--- command line completion the global value is used.
+---
+--- @type string
+vim.o.completeslash = ""
+vim.o.csl = vim.o.completeslash
+vim.bo.completeslash = vim.o.completeslash
+vim.bo.csl = vim.bo.completeslash
+
+--- Sets the modes in which text in the cursor line can also be concealed.
+--- When the current mode is listed then concealing happens just like in
+--- other lines.
+--- n Normal mode
+--- v Visual mode
+--- i Insert mode
+--- c Command line editing, for 'incsearch'
+---
+--- 'v' applies to all lines in the Visual area, not only the cursor.
+--- A useful value is "nc". This is used in help files. So long as you
+--- are moving around text is concealed, but when starting to insert text
+--- or selecting a Visual area the concealed text is displayed, so that
+--- you can see what you are doing.
+--- Keep in mind that the cursor position is not always where it's
+--- displayed. E.g., when moving vertically it may change column.
+---
+--- @type string
+vim.o.concealcursor = ""
+vim.o.cocu = vim.o.concealcursor
+vim.wo.concealcursor = vim.o.concealcursor
+vim.wo.cocu = vim.wo.concealcursor
+
+--- Determine how text with the "conceal" syntax attribute `:syn-conceal`
+--- is shown:
+---
+--- Value Effect ~
+--- 0 Text is shown normally
+--- 1 Each block of concealed text is replaced with one
+--- character. If the syntax item does not have a custom
+--- replacement character defined (see `:syn-cchar`) the
+--- character defined in 'listchars' is used.
+--- It is highlighted with the "Conceal" highlight group.
+--- 2 Concealed text is completely hidden unless it has a
+--- custom replacement character defined (see
+--- `:syn-cchar`).
+--- 3 Concealed text is completely hidden.
+---
+--- Note: in the cursor line concealed text is not hidden, so that you can
+--- edit and copy the text. This can be changed with the 'concealcursor'
+--- option.
+---
+--- @type integer
+vim.o.conceallevel = 0
+vim.o.cole = vim.o.conceallevel
+vim.wo.conceallevel = vim.o.conceallevel
+vim.wo.cole = vim.wo.conceallevel
+
+--- When 'confirm' is on, certain operations that would normally
+--- fail because of unsaved changes to a buffer, e.g. ":q" and ":e",
+--- instead raise a dialog asking if you wish to save the current
+--- file(s). You can still use a ! to unconditionally `abandon` a buffer.
+--- If 'confirm' is off you can still activate confirmation for one
+--- command only (this is most useful in mappings) with the `:confirm`
+--- command.
+--- Also see the `confirm()` function and the 'v' flag in 'guioptions'.
+---
+--- @type boolean
+vim.o.confirm = false
+vim.o.cf = vim.o.confirm
+vim.go.confirm = vim.o.confirm
+vim.go.cf = vim.go.confirm
+
+--- Copy the structure of the existing lines indent when autoindenting a
+--- new line. Normally the new indent is reconstructed by a series of
+--- tabs followed by spaces as required (unless `'expandtab'` is enabled,
+--- in which case only spaces are used). Enabling this option makes the
+--- new line copy whatever characters were used for indenting on the
+--- existing line. 'expandtab' has no effect on these characters, a Tab
+--- remains a Tab. If the new indent is greater than on the existing
+--- line, the remaining space is filled in the normal manner.
+--- See 'preserveindent'.
+---
+--- @type boolean
+vim.o.copyindent = false
+vim.o.ci = vim.o.copyindent
+vim.bo.copyindent = vim.o.copyindent
+vim.bo.ci = vim.bo.copyindent
+
+--- A sequence of single character flags. When a character is present
+--- this indicates Vi-compatible behavior. This is used for things where
+--- not being Vi-compatible is mostly or sometimes preferred.
+--- 'cpoptions' stands for "compatible-options".
+--- Commas can be added for readability.
+--- To avoid problems with flags that are added in the future, use the
+--- "+=" and "-=" feature of ":set" `add-option-flags`.
+---
+--- contains behavior ~
+--- *cpo-a*
+--- a When included, a ":read" command with a file name
+--- argument will set the alternate file name for the
+--- current window.
+--- *cpo-A*
+--- A When included, a ":write" command with a file name
+--- argument will set the alternate file name for the
+--- current window.
+--- *cpo-b*
+--- b "\|" in a ":map" command is recognized as the end of
+--- the map command. The '\' is included in the mapping,
+--- the text after the '|' is interpreted as the next
+--- command. Use a CTRL-V instead of a backslash to
+--- include the '|' in the mapping. Applies to all
+--- mapping, abbreviation, menu and autocmd commands.
+--- See also `map_bar`.
+--- *cpo-B*
+--- B A backslash has no special meaning in mappings,
+--- abbreviations, user commands and the "to" part of the
+--- menu commands. Remove this flag to be able to use a
+--- backslash like a CTRL-V. For example, the command
+--- ":map X \\<Esc>" results in X being mapped to:
+--- 'B' included: "\^[" (^[ is a real <Esc>)
+--- 'B' excluded: "<Esc>" (5 characters)
+--- *cpo-c*
+--- c Searching continues at the end of any match at the
+--- cursor position, but not further than the start of the
+--- next line. When not present searching continues
+--- one character from the cursor position. With 'c'
+--- "abababababab" only gets three matches when repeating
+--- "/abab", without 'c' there are five matches.
+--- *cpo-C*
+--- C Do not concatenate sourced lines that start with a
+--- backslash. See `line-continuation`.
+--- *cpo-d*
+--- d Using "./" in the 'tags' option doesn't mean to use
+--- the tags file relative to the current file, but the
+--- tags file in the current directory.
+--- *cpo-D*
+--- D Can't use CTRL-K to enter a digraph after Normal mode
+--- commands with a character argument, like `r`, `f` and
+--- `t`.
+--- *cpo-e*
+--- e When executing a register with ":@r", always add a
+--- <CR> to the last line, also when the register is not
+--- linewise. If this flag is not present, the register
+--- is not linewise and the last line does not end in a
+--- <CR>, then the last line is put on the command-line
+--- and can be edited before hitting <CR>.
+--- *cpo-E*
+--- E It is an error when using "y", "d", "c", "g~", "gu" or
+--- "gU" on an Empty region. The operators only work when
+--- at least one character is to be operated on. Example:
+--- This makes "y0" fail in the first column.
+--- *cpo-f*
+--- f When included, a ":read" command with a file name
+--- argument will set the file name for the current buffer,
+--- if the current buffer doesn't have a file name yet.
+--- *cpo-F*
+--- F When included, a ":write" command with a file name
+--- argument will set the file name for the current
+--- buffer, if the current buffer doesn't have a file name
+--- yet. Also see `cpo-P`.
+--- *cpo-i*
+--- i When included, interrupting the reading of a file will
+--- leave it modified.
+--- *cpo-I*
+--- I When moving the cursor up or down just after inserting
+--- indent for 'autoindent', do not delete the indent.
+--- *cpo-J*
+--- J A `sentence` has to be followed by two spaces after
+--- the '.', '!' or '?'. A <Tab> is not recognized as
+--- white space.
+--- *cpo-K*
+--- K Don't wait for a key code to complete when it is
+--- halfway through a mapping. This breaks mapping
+--- <F1><F1> when only part of the second <F1> has been
+--- read. It enables cancelling the mapping by typing
+--- <F1><Esc>.
+--- *cpo-l*
+--- l Backslash in a [] range in a search pattern is taken
+--- literally, only "\]", "\^", "\-" and "\\" are special.
+--- See `/[]`
+--- 'l' included: "/[ \t]" finds <Space>, '\' and 't'
+--- 'l' excluded: "/[ \t]" finds <Space> and <Tab>
+--- *cpo-L*
+--- L When the 'list' option is set, 'wrapmargin',
+--- 'textwidth', 'softtabstop' and Virtual Replace mode
+--- (see `gR`) count a <Tab> as two characters, instead of
+--- the normal behavior of a <Tab>.
+--- *cpo-m*
+--- m When included, a showmatch will always wait half a
+--- second. When not included, a showmatch will wait half
+--- a second or until a character is typed. `'showmatch'`
+--- *cpo-M*
+--- M When excluded, "%" matching will take backslashes into
+--- account. Thus in "( \( )" and "\( ( \)" the outer
+--- parenthesis match. When included "%" ignores
+--- backslashes, which is Vi compatible.
+--- *cpo-n*
+--- n When included, the column used for 'number' and
+--- 'relativenumber' will also be used for text of wrapped
+--- lines.
+--- *cpo-o*
+--- o Line offset to search command is not remembered for
+--- next search.
+--- *cpo-O*
+--- O Don't complain if a file is being overwritten, even
+--- when it didn't exist when editing it. This is a
+--- protection against a file unexpectedly created by
+--- someone else. Vi didn't complain about this.
+--- *cpo-p*
+--- p Vi compatible Lisp indenting. When not present, a
+--- slightly better algorithm is used.
+--- *cpo-P*
+--- P When included, a ":write" command that appends to a
+--- file will set the file name for the current buffer, if
+--- the current buffer doesn't have a file name yet and
+--- the 'F' flag is also included `cpo-F`.
+--- *cpo-q*
+--- q When joining multiple lines leave the cursor at the
+--- position where it would be when joining two lines.
+--- *cpo-r*
+--- r Redo ("." command) uses "/" to repeat a search
+--- command, instead of the actually used search string.
+--- *cpo-R*
+--- R Remove marks from filtered lines. Without this flag
+--- marks are kept like `:keepmarks` was used.
+--- *cpo-s*
+--- s Set buffer options when entering the buffer for the
+--- first time. This is like it is in Vim version 3.0.
+--- And it is the default. If not present the options are
+--- set when the buffer is created.
+--- *cpo-S*
+--- S Set buffer options always when entering a buffer
+--- (except 'readonly', 'fileformat', 'filetype' and
+--- 'syntax'). This is the (most) Vi compatible setting.
+--- The options are set to the values in the current
+--- buffer. When you change an option and go to another
+--- buffer, the value is copied. Effectively makes the
+--- buffer options global to all buffers.
+---
+--- 's' 'S' copy buffer options
+--- no no when buffer created
+--- yes no when buffer first entered (default)
+--- X yes each time when buffer entered (vi comp.)
+--- *cpo-t*
+--- t Search pattern for the tag command is remembered for
+--- "n" command. Otherwise Vim only puts the pattern in
+--- the history for search pattern, but doesn't change the
+--- last used search pattern.
+--- *cpo-u*
+--- u Undo is Vi compatible. See `undo-two-ways`.
+--- *cpo-v*
+--- v Backspaced characters remain visible on the screen in
+--- Insert mode. Without this flag the characters are
+--- erased from the screen right away. With this flag the
+--- screen newly typed text overwrites backspaced
+--- characters.
+--- *cpo-W*
+--- W Don't overwrite a readonly file. When omitted, ":w!"
+--- overwrites a readonly file, if possible.
+--- *cpo-x*
+--- x <Esc> on the command-line executes the command-line.
+--- The default in Vim is to abandon the command-line,
+--- because <Esc> normally aborts a command. `c_<Esc>`
+--- *cpo-X*
+--- X When using a count with "R" the replaced text is
+--- deleted only once. Also when repeating "R" with "."
+--- and a count.
+--- *cpo-y*
+--- y A yank command can be redone with ".". Think twice if
+--- you really want to use this, it may break some
+--- plugins, since most people expect "." to only repeat a
+--- change.
+--- *cpo-Z*
+--- Z When using "w!" while the 'readonly' option is set,
+--- don't reset 'readonly'.
+--- *cpo-!*
+--- ! When redoing a filter command, use the last used
+--- external command, whatever it was. Otherwise the last
+--- used -filter- command is used.
+--- *cpo-$*
+--- $ When making a change to one line, don't redisplay the
+--- line, but put a '$' at the end of the changed text.
+--- The changed text will be overwritten when you type the
+--- new text. The line is redisplayed if you type any
+--- command that moves the cursor from the insertion
+--- point.
+--- *cpo-%*
+--- % Vi-compatible matching is done for the "%" command.
+--- Does not recognize "#if", "#endif", etc.
+--- Does not recognize "/*" and "*/".
+--- Parens inside single and double quotes are also
+--- counted, causing a string that contains a paren to
+--- disturb the matching. For example, in a line like
+--- "if (strcmp("foo(", s))" the first paren does not
+--- match the last one. When this flag is not included,
+--- parens inside single and double quotes are treated
+--- specially. When matching a paren outside of quotes,
+--- everything inside quotes is ignored. When matching a
+--- paren inside quotes, it will find the matching one (if
+--- there is one). This works very well for C programs.
+--- This flag is also used for other features, such as
+--- C-indenting.
+--- *cpo-+*
+--- + When included, a ":write file" command will reset the
+--- 'modified' flag of the buffer, even though the buffer
+--- itself may still be different from its file.
+--- *cpo->*
+--- > When appending to a register, put a line break before
+--- the appended text.
+--- *cpo-;*
+--- ; When using `,` or `;` to repeat the last `t` search
+--- and the cursor is right in front of the searched
+--- character, the cursor won't move. When not included,
+--- the cursor would skip over it and jump to the
+--- following occurrence.
+--- *cpo-_*
+--- _ When using `cw` on a word, do not include the
+--- whitespace following the word in the motion.
+---
+--- @type string
+vim.o.cpoptions = "aABceFs_"
+vim.o.cpo = vim.o.cpoptions
+vim.go.cpoptions = vim.o.cpoptions
+vim.go.cpo = vim.go.cpoptions
+
+--- When this option is set, as the cursor in the current
+--- window moves other cursorbound windows (windows that also have
+--- this option set) move their cursors to the corresponding line and
+--- column. This option is useful for viewing the
+--- differences between two versions of a file (see 'diff'); in diff mode,
+--- inserted and deleted lines (though not characters within a line) are
+--- taken into account.
+---
+--- @type boolean
+vim.o.cursorbind = false
+vim.o.crb = vim.o.cursorbind
+vim.wo.cursorbind = vim.o.cursorbind
+vim.wo.crb = vim.wo.cursorbind
+
+--- Highlight the screen column of the cursor with CursorColumn
+--- `hl-CursorColumn`. Useful to align text. Will make screen redrawing
+--- slower.
+--- If you only want the highlighting in the current window you can use
+--- these autocommands:
+--- ```
+--- au WinLeave * set nocursorline nocursorcolumn
+--- au WinEnter * set cursorline cursorcolumn
+--- ```
+---
+---
+--- @type boolean
+vim.o.cursorcolumn = false
+vim.o.cuc = vim.o.cursorcolumn
+vim.wo.cursorcolumn = vim.o.cursorcolumn
+vim.wo.cuc = vim.wo.cursorcolumn
+
+--- Highlight the text line of the cursor with CursorLine `hl-CursorLine`.
+--- Useful to easily spot the cursor. Will make screen redrawing slower.
+--- When Visual mode is active the highlighting isn't used to make it
+--- easier to see the selected text.
+---
+--- @type boolean
+vim.o.cursorline = false
+vim.o.cul = vim.o.cursorline
+vim.wo.cursorline = vim.o.cursorline
+vim.wo.cul = vim.wo.cursorline
+
+--- Comma-separated list of settings for how 'cursorline' is displayed.
+--- Valid values:
+--- "line" Highlight the text line of the cursor with
+--- CursorLine `hl-CursorLine`.
+--- "screenline" Highlight only the screen line of the cursor with
+--- CursorLine `hl-CursorLine`.
+--- "number" Highlight the line number of the cursor with
+--- CursorLineNr `hl-CursorLineNr`.
+---
+--- Special value:
+--- "both" Alias for the values "line,number".
+---
+--- "line" and "screenline" cannot be used together.
+---
+--- @type string
+vim.o.cursorlineopt = "both"
+vim.o.culopt = vim.o.cursorlineopt
+vim.wo.cursorlineopt = vim.o.cursorlineopt
+vim.wo.culopt = vim.wo.cursorlineopt
+
+--- These values can be used:
+--- msg Error messages that would otherwise be omitted will be given
+--- anyway.
+--- throw Error messages that would otherwise be omitted will be given
+--- anyway and also throw an exception and set `v:errmsg`.
+--- beep A message will be given when otherwise only a beep would be
+--- produced.
+--- The values can be combined, separated by a comma.
+--- "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or
+--- 'indentexpr'.
+---
+--- @type string
+vim.o.debug = ""
+vim.go.debug = vim.o.debug
+
+--- Pattern to be used to find a macro definition. It is a search
+--- pattern, just like for the "/" command. This option is used for the
+--- commands like "[i" and "[d" `include-search`. The 'isident' option is
+--- used to recognize the defined name after the match:
+--- ```
+--- {match with 'define'}{non-ID chars}{defined name}{non-ID char}
+--- ```
+--- See `option-backslash` about inserting backslashes to include a space
+--- or backslash.
+--- For C++ this value would be useful, to include const type declarations:
+--- ```
+--- ^\(#\s*define\|[a-z]*\s*const\s*[a-z]*\)
+--- ```
+--- You can also use "\ze" just before the name and continue the pattern
+--- to check what is following. E.g. for Javascript, if a function is
+--- defined with `func_name = function(args)`:
+--- ```
+--- ^\s*\ze\i\+\s*=\s*function(
+--- ```
+--- If the function is defined with `func_name : function() {...`:
+--- ```
+--- ^\s*\ze\i\+\s*[:]\s*(*function\s*(
+--- ```
+--- When using the ":set" command, you need to double the backslashes!
+--- To avoid that use `:let` with a single quote string:
+--- ```
+--- let &l:define = '^\s*\ze\k\+\s*=\s*function('
+--- ```
+---
+---
+--- @type string
+vim.o.define = ""
+vim.o.def = vim.o.define
+vim.bo.define = vim.o.define
+vim.bo.def = vim.bo.define
+vim.go.define = vim.o.define
+vim.go.def = vim.go.define
+
+--- If editing Unicode and this option is set, backspace and Normal mode
+--- "x" delete each combining character on its own. When it is off (the
+--- default) the character along with its combining characters are
+--- deleted.
+--- Note: When 'delcombine' is set "xx" may work differently from "2x"!
+---
+--- This is useful for Arabic, Hebrew and many other languages where one
+--- may have combining characters overtop of base characters, and want
+--- to remove only the combining ones.
+---
+--- @type boolean
+vim.o.delcombine = false
+vim.o.deco = vim.o.delcombine
+vim.go.delcombine = vim.o.delcombine
+vim.go.deco = vim.go.delcombine
+
+--- List of file names, separated by commas, that are used to lookup words
+--- for keyword completion commands `i_CTRL-X_CTRL-K`. Each file should
+--- contain a list of words. This can be one word per line, or several
+--- words per line, separated by non-keyword characters (white space is
+--- preferred). Maximum line length is 510 bytes.
+---
+--- When this option is empty or an entry "spell" is present, and spell
+--- checking is enabled, words in the word lists for the currently active
+--- 'spelllang' are used. See `spell`.
+---
+--- To include a comma in a file name precede it with a backslash. Spaces
+--- after a comma are ignored, otherwise spaces are included in the file
+--- name. See `option-backslash` about using backslashes.
+--- This has nothing to do with the `Dictionary` variable type.
+--- Where to find a list of words?
+--- - BSD/macOS include the "/usr/share/dict/words" file.
+--- - Try "apt install spell" to get the "/usr/share/dict/words" file on
+--- apt-managed systems (Debian/Ubuntu).
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- directories from the list. This avoids problems when a future version
+--- uses another default.
+--- Backticks cannot be used in this option for security reasons.
+---
+--- @type string
+vim.o.dictionary = ""
+vim.o.dict = vim.o.dictionary
+vim.bo.dictionary = vim.o.dictionary
+vim.bo.dict = vim.bo.dictionary
+vim.go.dictionary = vim.o.dictionary
+vim.go.dict = vim.go.dictionary
+
+--- Join the current window in the group of windows that shows differences
+--- between files. See `diff-mode`.
+---
+--- @type boolean
+vim.o.diff = false
+vim.wo.diff = vim.o.diff
+
+--- Expression which is evaluated to obtain a diff file (either ed-style
+--- or unified-style) from two versions of a file. See `diff-diffexpr`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.diffexpr = ""
+vim.o.dex = vim.o.diffexpr
+vim.go.diffexpr = vim.o.diffexpr
+vim.go.dex = vim.go.diffexpr
+
+--- Option settings for diff mode. It can consist of the following items.
+--- All are optional. Items must be separated by a comma.
+---
+--- filler Show filler lines, to keep the text
+--- synchronized with a window that has inserted
+--- lines at the same position. Mostly useful
+--- when windows are side-by-side and 'scrollbind'
+--- is set.
+---
+--- context:{n} Use a context of {n} lines between a change
+--- and a fold that contains unchanged lines.
+--- When omitted a context of six lines is used.
+--- When using zero the context is actually one,
+--- since folds require a line in between, also
+--- for a deleted line. Set it to a very large
+--- value (999999) to disable folding completely.
+--- See `fold-diff`.
+---
+--- iblank Ignore changes where lines are all blank. Adds
+--- the "-B" flag to the "diff" command if
+--- 'diffexpr' is empty. Check the documentation
+--- of the "diff" command for what this does
+--- exactly.
+--- NOTE: the diff windows will get out of sync,
+--- because no differences between blank lines are
+--- taken into account.
+---
+--- icase Ignore changes in case of text. "a" and "A"
+--- are considered the same. Adds the "-i" flag
+--- to the "diff" command if 'diffexpr' is empty.
+---
+--- iwhite Ignore changes in amount of white space. Adds
+--- the "-b" flag to the "diff" command if
+--- 'diffexpr' is empty. Check the documentation
+--- of the "diff" command for what this does
+--- exactly. It should ignore adding trailing
+--- white space, but not leading white space.
+---
+--- iwhiteall Ignore all white space changes. Adds
+--- the "-w" flag to the "diff" command if
+--- 'diffexpr' is empty. Check the documentation
+--- of the "diff" command for what this does
+--- exactly.
+---
+--- iwhiteeol Ignore white space changes at end of line.
+--- Adds the "-Z" flag to the "diff" command if
+--- 'diffexpr' is empty. Check the documentation
+--- of the "diff" command for what this does
+--- exactly.
+---
+--- horizontal Start diff mode with horizontal splits (unless
+--- explicitly specified otherwise).
+---
+--- vertical Start diff mode with vertical splits (unless
+--- explicitly specified otherwise).
+---
+--- closeoff When a window is closed where 'diff' is set
+--- and there is only one window remaining in the
+--- same tab page with 'diff' set, execute
+--- `:diffoff` in that window. This undoes a
+--- `:diffsplit` command.
+---
+--- hiddenoff Do not use diff mode for a buffer when it
+--- becomes hidden.
+---
+--- foldcolumn:{n} Set the 'foldcolumn' option to {n} when
+--- starting diff mode. Without this 2 is used.
+---
+--- followwrap Follow the 'wrap' option and leave as it is.
+---
+--- internal Use the internal diff library. This is
+--- ignored when 'diffexpr' is set. *E960*
+--- When running out of memory when writing a
+--- buffer this item will be ignored for diffs
+--- involving that buffer. Set the 'verbose'
+--- option to see when this happens.
+---
+--- indent-heuristic
+--- Use the indent heuristic for the internal
+--- diff library.
+---
+--- linematch:{n} Enable a second stage diff on each generated
+--- hunk in order to align lines. When the total
+--- number of lines in a hunk exceeds {n}, the
+--- second stage diff will not be performed as
+--- very large hunks can cause noticeable lag. A
+--- recommended setting is "linematch:60", as this
+--- will enable alignment for a 2 buffer diff with
+--- hunks of up to 30 lines each, or a 3 buffer
+--- diff with hunks of up to 20 lines each.
+---
+--- algorithm:{text} Use the specified diff algorithm with the
+--- internal diff engine. Currently supported
+--- algorithms are:
+--- myers the default algorithm
+--- minimal spend extra time to generate the
+--- smallest possible diff
+--- patience patience diff algorithm
+--- histogram histogram diff algorithm
+---
+--- Examples:
+--- ```
+--- :set diffopt=internal,filler,context:4
+--- :set diffopt=
+--- :set diffopt=internal,filler,foldcolumn:3
+--- :set diffopt-=internal " do NOT use the internal diff parser
+--- ```
+---
+---
+--- @type string
+vim.o.diffopt = "internal,filler,closeoff"
+vim.o.dip = vim.o.diffopt
+vim.go.diffopt = vim.o.diffopt
+vim.go.dip = vim.go.diffopt
+
+--- Enable the entering of digraphs in Insert mode with {char1} <BS>
+--- {char2}. See `digraphs`.
+---
+--- @type boolean
+vim.o.digraph = false
+vim.o.dg = vim.o.digraph
+vim.go.digraph = vim.o.digraph
+vim.go.dg = vim.go.digraph
+
+--- List of directory names for the swap file, separated with commas.
+---
+--- Possible items:
+--- - The swap file will be created in the first directory where this is
+--- possible. If it is not possible in any directory, but last
+--- directory listed in the option does not exist, it is created.
+--- - Empty means that no swap file will be used (recovery is
+--- impossible!) and no `E303` error will be given.
+--- - A directory "." means to put the swap file in the same directory as
+--- the edited file. On Unix, a dot is prepended to the file name, so
+--- it doesn't show in a directory listing. On MS-Windows the "hidden"
+--- attribute is set and a dot prepended if possible.
+--- - A directory starting with "./" (or ".\" for MS-Windows) means to put
+--- the swap file relative to where the edited file is. The leading "."
+--- is replaced with the path name of the edited file.
+--- - For Unix and Win32, if a directory ends in two path separators "//",
+--- the swap file name will be built from the complete path to the file
+--- with all path separators replaced by percent '%' signs (including
+--- the colon following the drive letter on Win32). This will ensure
+--- file name uniqueness in the preserve directory.
+--- On Win32, it is also possible to end with "\\". However, When a
+--- separating comma is following, you must use "//", since "\\" will
+--- include the comma in the file name. Therefore it is recommended to
+--- use '//', instead of '\\'.
+--- - Spaces after the comma are ignored, other spaces are considered part
+--- of the directory name. To have a space at the start of a directory
+--- name, precede it with a backslash.
+--- - To include a comma in a directory name precede it with a backslash.
+--- - A directory name may end in an ':' or '/'.
+--- - Environment variables are expanded `:set_env`.
+--- - Careful with '\' characters, type one before a space, type two to
+--- get one in the option (see `option-backslash`), for example:
+--- ```
+--- :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+--- ```
+---
+--- Editing the same file twice will result in a warning. Using "/tmp" on
+--- is discouraged: if the system crashes you lose the swap file. And
+--- others on the computer may be able to see the files.
+--- Use `:set+=` and `:set-=` when adding or removing directories from the
+--- list, this avoids problems if the Nvim default is changed.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.directory = "$XDG_STATE_HOME/nvim/swap//"
+vim.o.dir = vim.o.directory
+vim.go.directory = vim.o.directory
+vim.go.dir = vim.go.directory
+
+--- Change the way text is displayed. This is a comma-separated list of
+--- flags:
+--- lastline When included, as much as possible of the last line
+--- in a window will be displayed. "@@@" is put in the
+--- last columns of the last screen line to indicate the
+--- rest of the line is not displayed.
+--- truncate Like "lastline", but "@@@" is displayed in the first
+--- column of the last screen line. Overrules "lastline".
+--- uhex Show unprintable characters hexadecimal as <xx>
+--- instead of using ^C and ~C.
+--- msgsep Obsolete flag. Allowed but takes no effect. `msgsep`
+---
+--- When neither "lastline" nor "truncate" is included, a last line that
+--- doesn't fit is replaced with "@" lines.
+---
+--- The "@" character can be changed by setting the "lastline" item in
+--- 'fillchars'. The character is highlighted with `hl-NonText`.
+---
+--- @type string
+vim.o.display = "lastline"
+vim.o.dy = vim.o.display
+vim.go.display = vim.o.display
+vim.go.dy = vim.go.display
+
+--- Tells when the 'equalalways' option applies:
+--- ver vertically, width of windows is not affected
+--- hor horizontally, height of windows is not affected
+--- both width and height of windows is affected
+---
+--- @type string
+vim.o.eadirection = "both"
+vim.o.ead = vim.o.eadirection
+vim.go.eadirection = vim.o.eadirection
+vim.go.ead = vim.go.eadirection
+
+--- When on all Unicode emoji characters are considered to be full width.
+--- This excludes "text emoji" characters, which are normally displayed as
+--- single width. Unfortunately there is no good specification for this
+--- and it has been determined on trial-and-error basis. Use the
+--- `setcellwidths()` function to change the behavior.
+---
+--- @type boolean
+vim.o.emoji = true
+vim.o.emo = vim.o.emoji
+vim.go.emoji = vim.o.emoji
+vim.go.emo = vim.go.emoji
+
+--- String-encoding used internally and for `RPC` communication.
+--- Always UTF-8.
+---
+--- See 'fileencoding' to control file-content encoding.
+---
+--- @type string
+vim.o.encoding = "utf-8"
+vim.o.enc = vim.o.encoding
+vim.go.encoding = vim.o.encoding
+vim.go.enc = vim.go.encoding
+
+--- Indicates that a CTRL-Z character was found at the end of the file
+--- when reading it. Normally only happens when 'fileformat' is "dos".
+--- When writing a file and this option is off and the 'binary' option
+--- is on, or 'fixeol' option is off, no CTRL-Z will be written at the
+--- end of the file.
+--- See `eol-and-eof` for example settings.
+---
+--- @type boolean
+vim.o.endoffile = false
+vim.o.eof = vim.o.endoffile
+vim.bo.endoffile = vim.o.endoffile
+vim.bo.eof = vim.bo.endoffile
+
+--- When writing a file and this option is off and the 'binary' option
+--- is on, or 'fixeol' option is off, no <EOL> will be written for the
+--- last line in the file. This option is automatically set or reset when
+--- starting to edit a new file, depending on whether file has an <EOL>
+--- for the last line in the file. Normally you don't have to set or
+--- reset this option.
+--- When 'binary' is off and 'fixeol' is on the value is not used when
+--- writing the file. When 'binary' is on or 'fixeol' is off it is used
+--- to remember the presence of a <EOL> for the last line in the file, so
+--- that when you write the file the situation from the original file can
+--- be kept. But you can change it if you want to.
+--- See `eol-and-eof` for example settings.
+---
+--- @type boolean
+vim.o.endofline = true
+vim.o.eol = vim.o.endofline
+vim.bo.endofline = vim.o.endofline
+vim.bo.eol = vim.bo.endofline
+
+--- When on, all the windows are automatically made the same size after
+--- splitting or closing a window. This also happens the moment the
+--- option is switched on. When off, splitting a window will reduce the
+--- size of the current window and leave the other windows the same. When
+--- closing a window the extra lines are given to the window next to it
+--- (depending on 'splitbelow' and 'splitright').
+--- When mixing vertically and horizontally split windows, a minimal size
+--- is computed and some windows may be larger if there is room. The
+--- 'eadirection' option tells in which direction the size is affected.
+--- Changing the height and width of a window can be avoided by setting
+--- 'winfixheight' and 'winfixwidth', respectively.
+--- If a window size is specified when creating a new window sizes are
+--- currently not equalized (it's complicated, but may be implemented in
+--- the future).
+---
+--- @type boolean
+vim.o.equalalways = true
+vim.o.ea = vim.o.equalalways
+vim.go.equalalways = vim.o.equalalways
+vim.go.ea = vim.go.equalalways
+
+--- External program to use for "=" command. When this option is empty
+--- the internal formatting functions are used; either 'lisp', 'cindent'
+--- or 'indentexpr'.
+--- Environment variables are expanded `:set_env`. See `option-backslash`
+--- about including spaces and backslashes.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.equalprg = ""
+vim.o.ep = vim.o.equalprg
+vim.bo.equalprg = vim.o.equalprg
+vim.bo.ep = vim.bo.equalprg
+vim.go.equalprg = vim.o.equalprg
+vim.go.ep = vim.go.equalprg
+
+--- Ring the bell (beep or screen flash) for error messages. This only
+--- makes a difference for error messages, the bell will be used always
+--- for a lot of errors without a message (e.g., hitting <Esc> in Normal
+--- mode). See 'visualbell' to make the bell behave like a screen flash
+--- or do nothing. See 'belloff' to finetune when to ring the bell.
+---
+--- @type boolean
+vim.o.errorbells = false
+vim.o.eb = vim.o.errorbells
+vim.go.errorbells = vim.o.errorbells
+vim.go.eb = vim.go.errorbells
+
+--- Name of the errorfile for the QuickFix mode (see `:cf`).
+--- When the "-q" command-line argument is used, 'errorfile' is set to the
+--- following argument. See `-q`.
+--- NOT used for the ":make" command. See 'makeef' for that.
+--- Environment variables are expanded `:set_env`.
+--- See `option-backslash` about including spaces and backslashes.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.errorfile = "errors.err"
+vim.o.ef = vim.o.errorfile
+vim.go.errorfile = vim.o.errorfile
+vim.go.ef = vim.go.errorfile
+
+--- Scanf-like description of the format for the lines in the error file
+--- (see `errorformat`).
+---
+--- @type string
+vim.o.errorformat = "%*[^\"]\"%f\"%*\\D%l: %m,\"%f\"%*\\D%l: %m,%-Gg%\\?make[%*\\d]: *** [%f:%l:%m,%-Gg%\\?make: *** [%f:%l:%m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,\"%f\"\\, line %l%*\\D%c%*[^ ] %m,%D%*\\a[%*\\d]: Entering directory %*[`']%f',%X%*\\a[%*\\d]: Leaving directory %*[`']%f',%D%*\\a: Entering directory %*[`']%f',%X%*\\a: Leaving directory %*[`']%f',%DMaking %*\\a in %f,%f|%l| %m"
+vim.o.efm = vim.o.errorformat
+vim.bo.errorformat = vim.o.errorformat
+vim.bo.efm = vim.bo.errorformat
+vim.go.errorformat = vim.o.errorformat
+vim.go.efm = vim.go.errorformat
+
+--- A list of autocommand event names, which are to be ignored.
+--- When set to "all" or when "all" is one of the items, all autocommand
+--- events are ignored, autocommands will not be executed.
+--- Otherwise this is a comma-separated list of event names. Example:
+--- ```
+--- :set ei=WinEnter,WinLeave
+--- ```
+---
+---
+--- @type string
+vim.o.eventignore = ""
+vim.o.ei = vim.o.eventignore
+vim.go.eventignore = vim.o.eventignore
+vim.go.ei = vim.go.eventignore
+
+--- In Insert mode: Use the appropriate number of spaces to insert a
+--- <Tab>. Spaces are used in indents with the '>' and '<' commands and
+--- when 'autoindent' is on. To insert a real tab when 'expandtab' is
+--- on, use CTRL-V<Tab>. See also `:retab` and `ins-expandtab`.
+---
+--- @type boolean
+vim.o.expandtab = false
+vim.o.et = vim.o.expandtab
+vim.bo.expandtab = vim.o.expandtab
+vim.bo.et = vim.bo.expandtab
+
+--- Automatically execute .nvim.lua, .nvimrc, and .exrc files in the
+--- current directory, if the file is in the `trust` list. Use `:trust` to
+--- manage trusted files. See also `vim.secure.read()`.
+---
+--- Compare 'exrc' to `editorconfig`:
+--- - 'exrc' can execute any code; editorconfig only specifies settings.
+--- - 'exrc' is Nvim-specific; editorconfig works in other editors.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type boolean
+vim.o.exrc = false
+vim.o.ex = vim.o.exrc
+vim.go.exrc = vim.o.exrc
+vim.go.ex = vim.go.exrc
+
+--- File-content encoding for the current buffer. Conversion is done with
+--- iconv() or as specified with 'charconvert'.
+---
+--- When 'fileencoding' is not UTF-8, conversion will be done when
+--- writing the file. For reading see below.
+--- When 'fileencoding' is empty, the file will be saved with UTF-8
+--- encoding (no conversion when reading or writing a file).
+---
+--- WARNING: Conversion to a non-Unicode encoding can cause loss of
+--- information!
+---
+--- See `encoding-names` for the possible values. Additionally, values may be
+--- specified that can be handled by the converter, see
+--- `mbyte-conversion`.
+---
+--- When reading a file 'fileencoding' will be set from 'fileencodings'.
+--- To read a file in a certain encoding it won't work by setting
+--- 'fileencoding', use the `++enc` argument. One exception: when
+--- 'fileencodings' is empty the value of 'fileencoding' is used.
+--- For a new file the global value of 'fileencoding' is used.
+---
+--- Prepending "8bit-" and "2byte-" has no meaning here, they are ignored.
+--- When the option is set, the value is converted to lowercase. Thus
+--- you can set it with uppercase values too. '_' characters are
+--- replaced with '-'. If a name is recognized from the list at
+--- `encoding-names`, it is replaced by the standard name. For example
+--- "ISO8859-2" becomes "iso-8859-2".
+---
+--- When this option is set, after starting to edit a file, the 'modified'
+--- option is set, because the file would be different when written.
+---
+--- Keep in mind that changing 'fenc' from a modeline happens
+--- AFTER the text has been read, thus it applies to when the file will be
+--- written. If you do set 'fenc' in a modeline, you might want to set
+--- 'nomodified' to avoid not being able to ":q".
+---
+--- This option cannot be changed when 'modifiable' is off.
+---
+--- @type string
+vim.o.fileencoding = ""
+vim.o.fenc = vim.o.fileencoding
+vim.bo.fileencoding = vim.o.fileencoding
+vim.bo.fenc = vim.bo.fileencoding
+
+--- This is a list of character encodings considered when starting to edit
+--- an existing file. When a file is read, Vim tries to use the first
+--- mentioned character encoding. If an error is detected, the next one
+--- in the list is tried. When an encoding is found that works,
+--- 'fileencoding' is set to it. If all fail, 'fileencoding' is set to
+--- an empty string, which means that UTF-8 is used.
+--- WARNING: Conversion can cause loss of information! You can use
+--- the `++bad` argument to specify what is done with characters
+--- that can't be converted.
+--- For an empty file or a file with only ASCII characters most encodings
+--- will work and the first entry of 'fileencodings' will be used (except
+--- "ucs-bom", which requires the BOM to be present). If you prefer
+--- another encoding use an BufReadPost autocommand event to test if your
+--- preferred encoding is to be used. Example:
+--- ```
+--- au BufReadPost * if search('\S', 'w') == 0 |
+--- \ set fenc=iso-2022-jp | endif
+--- ```
+--- This sets 'fileencoding' to "iso-2022-jp" if the file does not contain
+--- non-blank characters.
+--- When the `++enc` argument is used then the value of 'fileencodings' is
+--- not used.
+--- Note that 'fileencodings' is not used for a new file, the global value
+--- of 'fileencoding' is used instead. You can set it with:
+--- ```
+--- :setglobal fenc=iso-8859-2
+--- ```
+--- This means that a non-existing file may get a different encoding than
+--- an empty file.
+--- The special value "ucs-bom" can be used to check for a Unicode BOM
+--- (Byte Order Mark) at the start of the file. It must not be preceded
+--- by "utf-8" or another Unicode encoding for this to work properly.
+--- An entry for an 8-bit encoding (e.g., "latin1") should be the last,
+--- because Vim cannot detect an error, thus the encoding is always
+--- accepted.
+--- The special value "default" can be used for the encoding from the
+--- environment. It is useful when your environment uses a non-latin1
+--- encoding, such as Russian.
+--- When a file contains an illegal UTF-8 byte sequence it won't be
+--- recognized as "utf-8". You can use the `8g8` command to find the
+--- illegal byte sequence.
+--- WRONG VALUES: WHAT'S WRONG:
+--- latin1,utf-8 "latin1" will always be used
+--- utf-8,ucs-bom,latin1 BOM won't be recognized in an utf-8
+--- file
+--- cp1250,latin1 "cp1250" will always be used
+--- If 'fileencodings' is empty, 'fileencoding' is not modified.
+--- See 'fileencoding' for the possible values.
+--- Setting this option does not have an effect until the next time a file
+--- is read.
+---
+--- @type string
+vim.o.fileencodings = "ucs-bom,utf-8,default,latin1"
+vim.o.fencs = vim.o.fileencodings
+vim.go.fileencodings = vim.o.fileencodings
+vim.go.fencs = vim.go.fileencodings
+
+--- This gives the <EOL> of the current buffer, which is used for
+--- reading/writing the buffer from/to a file:
+--- dos <CR><NL>
+--- unix <NL>
+--- mac <CR>
+--- When "dos" is used, CTRL-Z at the end of a file is ignored.
+--- See `file-formats` and `file-read`.
+--- For the character encoding of the file see 'fileencoding'.
+--- When 'binary' is set, the value of 'fileformat' is ignored, file I/O
+--- works like it was set to "unix".
+--- This option is set automatically when starting to edit a file and
+--- 'fileformats' is not empty and 'binary' is off.
+--- When this option is set, after starting to edit a file, the 'modified'
+--- option is set, because the file would be different when written.
+--- This option cannot be changed when 'modifiable' is off.
+---
+--- @type string
+vim.o.fileformat = "unix"
+vim.o.ff = vim.o.fileformat
+vim.bo.fileformat = vim.o.fileformat
+vim.bo.ff = vim.bo.fileformat
+
+--- This gives the end-of-line (<EOL>) formats that will be tried when
+--- starting to edit a new buffer and when reading a file into an existing
+--- buffer:
+--- - When empty, the format defined with 'fileformat' will be used
+--- always. It is not set automatically.
+--- - When set to one name, that format will be used whenever a new buffer
+--- is opened. 'fileformat' is set accordingly for that buffer. The
+--- 'fileformats' name will be used when a file is read into an existing
+--- buffer, no matter what 'fileformat' for that buffer is set to.
+--- - When more than one name is present, separated by commas, automatic
+--- <EOL> detection will be done when reading a file. When starting to
+--- edit a file, a check is done for the <EOL>:
+--- 1. If all lines end in <CR><NL>, and 'fileformats' includes "dos",
+--- 'fileformat' is set to "dos".
+--- 2. If a <NL> is found and 'fileformats' includes "unix", 'fileformat'
+--- is set to "unix". Note that when a <NL> is found without a
+--- preceding <CR>, "unix" is preferred over "dos".
+--- 3. If 'fileformat' has not yet been set, and if a <CR> is found, and
+--- if 'fileformats' includes "mac", 'fileformat' is set to "mac".
+--- This means that "mac" is only chosen when:
+--- "unix" is not present or no <NL> is found in the file, and
+--- "dos" is not present or no <CR><NL> is found in the file.
+--- Except: if "unix" was chosen, but there is a <CR> before
+--- the first <NL>, and there appear to be more <CR>s than <NL>s in
+--- the first few lines, "mac" is used.
+--- 4. If 'fileformat' is still not set, the first name from
+--- 'fileformats' is used.
+--- When reading a file into an existing buffer, the same is done, but
+--- this happens like 'fileformat' has been set appropriately for that
+--- file only, the option is not changed.
+--- When 'binary' is set, the value of 'fileformats' is not used.
+---
+--- When Vim starts up with an empty buffer the first item is used. You
+--- can overrule this by setting 'fileformat' in your .vimrc.
+---
+--- For systems with a Dos-like <EOL> (<CR><NL>), when reading files that
+--- are ":source"ed and for vimrc files, automatic <EOL> detection may be
+--- done:
+--- - When 'fileformats' is empty, there is no automatic detection. Dos
+--- format will be used.
+--- - When 'fileformats' is set to one or more names, automatic detection
+--- is done. This is based on the first <NL> in the file: If there is a
+--- <CR> in front of it, Dos format is used, otherwise Unix format is
+--- used.
+--- Also see `file-formats`.
+---
+--- @type string
+vim.o.fileformats = "unix,dos"
+vim.o.ffs = vim.o.fileformats
+vim.go.fileformats = vim.o.fileformats
+vim.go.ffs = vim.go.fileformats
+
+--- When set case is ignored when using file names and directories.
+--- See 'wildignorecase' for only ignoring case when doing completion.
+---
+--- @type boolean
+vim.o.fileignorecase = false
+vim.o.fic = vim.o.fileignorecase
+vim.go.fileignorecase = vim.o.fileignorecase
+vim.go.fic = vim.go.fileignorecase
+
+--- When this option is set, the FileType autocommand event is triggered.
+--- All autocommands that match with the value of this option will be
+--- executed. Thus the value of 'filetype' is used in place of the file
+--- name.
+--- Otherwise this option does not always reflect the current file type.
+--- This option is normally set when the file type is detected. To enable
+--- this use the ":filetype on" command. `:filetype`
+--- Setting this option to a different value is most useful in a modeline,
+--- for a file for which the file type is not automatically recognized.
+--- Example, for in an IDL file:
+--- ```
+--- /* vim: set filetype=idl : */
+--- ```
+--- `FileType` `filetypes`
+--- When a dot appears in the value then this separates two filetype
+--- names. Example:
+--- ```
+--- /* vim: set filetype=c.doxygen : */
+--- ```
+--- This will use the "c" filetype first, then the "doxygen" filetype.
+--- This works both for filetype plugins and for syntax files. More than
+--- one dot may appear.
+--- This option is not copied to another buffer, independent of the 's' or
+--- 'S' flag in 'cpoptions'.
+--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+---
+--- @type string
+vim.o.filetype = ""
+vim.o.ft = vim.o.filetype
+vim.bo.filetype = vim.o.filetype
+vim.bo.ft = vim.bo.filetype
+
+--- Characters to fill the statuslines, vertical separators and special
+--- lines in the window.
+--- It is a comma-separated list of items. Each item has a name, a colon
+--- and the value of that item:
+---
+--- item default Used for ~
+--- stl ' ' statusline of the current window
+--- stlnc ' ' statusline of the non-current windows
+--- wbr ' ' window bar
+--- horiz '─' or '-' horizontal separators `:split`
+--- horizup '┴' or '-' upwards facing horizontal separator
+--- horizdown '┬' or '-' downwards facing horizontal separator
+--- vert '│' or '|' vertical separators `:vsplit`
+--- vertleft '┤' or '|' left facing vertical separator
+--- vertright '├' or '|' right facing vertical separator
+--- verthoriz '┼' or '+' overlapping vertical and horizontal
+--- separator
+--- fold '·' or '-' filling 'foldtext'
+--- foldopen '-' mark the beginning of a fold
+--- foldclose '+' show a closed fold
+--- foldsep '│' or '|' open fold middle marker
+--- diff '-' deleted lines of the 'diff' option
+--- msgsep ' ' message separator 'display'
+--- eob '~' empty lines at the end of a buffer
+--- lastline '@' 'display' contains lastline/truncate
+---
+--- Any one that is omitted will fall back to the default.
+---
+--- Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and
+--- "verthoriz" are only used when 'laststatus' is 3, since only vertical
+--- window separators are used otherwise.
+---
+--- If 'ambiwidth' is "double" then "horiz", "horizup", "horizdown",
+--- "vert", "vertleft", "vertright", "verthoriz", "foldsep" and "fold"
+--- default to single-byte alternatives.
+---
+--- Example:
+--- ```
+--- :set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
+--- ```
+---
+--- For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
+--- single-byte and multibyte characters are supported. But double-width
+--- characters are not supported.
+---
+--- The highlighting used for these items:
+--- item highlight group ~
+--- stl StatusLine `hl-StatusLine`
+--- stlnc StatusLineNC `hl-StatusLineNC`
+--- wbr WinBar `hl-WinBar` or `hl-WinBarNC`
+--- horiz WinSeparator `hl-WinSeparator`
+--- horizup WinSeparator `hl-WinSeparator`
+--- horizdown WinSeparator `hl-WinSeparator`
+--- vert WinSeparator `hl-WinSeparator`
+--- vertleft WinSeparator `hl-WinSeparator`
+--- vertright WinSeparator `hl-WinSeparator`
+--- verthoriz WinSeparator `hl-WinSeparator`
+--- fold Folded `hl-Folded`
+--- diff DiffDelete `hl-DiffDelete`
+--- eob EndOfBuffer `hl-EndOfBuffer`
+--- lastline NonText `hl-NonText`
+---
+--- @type string
+vim.o.fillchars = ""
+vim.o.fcs = vim.o.fillchars
+vim.wo.fillchars = vim.o.fillchars
+vim.wo.fcs = vim.wo.fillchars
+vim.go.fillchars = vim.o.fillchars
+vim.go.fcs = vim.go.fillchars
+
+--- When writing a file and this option is on, <EOL> at the end of file
+--- will be restored if missing. Turn this option off if you want to
+--- preserve the situation from the original file.
+--- When the 'binary' option is set the value of this option doesn't
+--- matter.
+--- See the 'endofline' option.
+--- See `eol-and-eof` for example settings.
+---
+--- @type boolean
+vim.o.fixendofline = true
+vim.o.fixeol = vim.o.fixendofline
+vim.bo.fixendofline = vim.o.fixendofline
+vim.bo.fixeol = vim.bo.fixendofline
+
+--- When set to "all", a fold is closed when the cursor isn't in it and
+--- its level is higher than 'foldlevel'. Useful if you want folds to
+--- automatically close when moving out of them.
+---
+--- @type string
+vim.o.foldclose = ""
+vim.o.fcl = vim.o.foldclose
+vim.go.foldclose = vim.o.foldclose
+vim.go.fcl = vim.go.foldclose
+
+--- When and how to draw the foldcolumn. Valid values are:
+--- "auto": resize to the minimum amount of folds to display.
+--- "auto:[1-9]": resize to accommodate multiple folds up to the
+--- selected level
+--- "0": to disable foldcolumn
+--- "[1-9]": to display a fixed number of columns
+--- See `folding`.
+---
+--- @type string
+vim.o.foldcolumn = "0"
+vim.o.fdc = vim.o.foldcolumn
+vim.wo.foldcolumn = vim.o.foldcolumn
+vim.wo.fdc = vim.wo.foldcolumn
+
+--- When off, all folds are open. This option can be used to quickly
+--- switch between showing all text unfolded and viewing the text with
+--- folds (including manually opened or closed folds). It can be toggled
+--- with the `zi` command. The 'foldcolumn' will remain blank when
+--- 'foldenable' is off.
+--- This option is set by commands that create a new fold or close a fold.
+--- See `folding`.
+---
+--- @type boolean
+vim.o.foldenable = true
+vim.o.fen = vim.o.foldenable
+vim.wo.foldenable = vim.o.foldenable
+vim.wo.fen = vim.wo.foldenable
+
+--- The expression used for when 'foldmethod' is "expr". It is evaluated
+--- for each line to obtain its fold level. The context is set to the
+--- script where 'foldexpr' was set, script-local items can be accessed.
+--- See `fold-expr` for the usage.
+---
+--- The expression will be evaluated in the `sandbox` if set from a
+--- modeline, see `sandbox-option`.
+--- This option can't be set from a `modeline` when the 'diff' option is
+--- on or the 'modelineexpr' option is off.
+---
+--- It is not allowed to change text or jump to another window while
+--- evaluating 'foldexpr' `textlock`.
+---
+--- @type string
+vim.o.foldexpr = "0"
+vim.o.fde = vim.o.foldexpr
+vim.wo.foldexpr = vim.o.foldexpr
+vim.wo.fde = vim.wo.foldexpr
+
+--- Used only when 'foldmethod' is "indent". Lines starting with
+--- characters in 'foldignore' will get their fold level from surrounding
+--- lines. White space is skipped before checking for this character.
+--- The default "#" works well for C programs. See `fold-indent`.
+---
+--- @type string
+vim.o.foldignore = "#"
+vim.o.fdi = vim.o.foldignore
+vim.wo.foldignore = vim.o.foldignore
+vim.wo.fdi = vim.wo.foldignore
+
+--- Sets the fold level: Folds with a higher level will be closed.
+--- Setting this option to zero will close all folds. Higher numbers will
+--- close fewer folds.
+--- This option is set by commands like `zm`, `zM` and `zR`.
+--- See `fold-foldlevel`.
+---
+--- @type integer
+vim.o.foldlevel = 0
+vim.o.fdl = vim.o.foldlevel
+vim.wo.foldlevel = vim.o.foldlevel
+vim.wo.fdl = vim.wo.foldlevel
+
+--- Sets 'foldlevel' when starting to edit another buffer in a window.
+--- Useful to always start editing with all folds closed (value zero),
+--- some folds closed (one) or no folds closed (99).
+--- This is done before reading any modeline, thus a setting in a modeline
+--- overrules this option. Starting to edit a file for `diff-mode` also
+--- ignores this option and closes all folds.
+--- It is also done before BufReadPre autocommands, to allow an autocmd to
+--- overrule the 'foldlevel' value for specific files.
+--- When the value is negative, it is not used.
+---
+--- @type integer
+vim.o.foldlevelstart = -1
+vim.o.fdls = vim.o.foldlevelstart
+vim.go.foldlevelstart = vim.o.foldlevelstart
+vim.go.fdls = vim.go.foldlevelstart
+
+--- The start and end marker used when 'foldmethod' is "marker". There
+--- must be one comma, which separates the start and end marker. The
+--- marker is a literal string (a regular expression would be too slow).
+--- See `fold-marker`.
+---
+--- @type string
+vim.o.foldmarker = "{{{,}}}"
+vim.o.fmr = vim.o.foldmarker
+vim.wo.foldmarker = vim.o.foldmarker
+vim.wo.fmr = vim.wo.foldmarker
+
+--- The kind of folding used for the current window. Possible values:
+--- `fold-manual` manual Folds are created manually.
+--- `fold-indent` indent Lines with equal indent form a fold.
+--- `fold-expr` expr 'foldexpr' gives the fold level of a line.
+--- `fold-marker` marker Markers are used to specify folds.
+--- `fold-syntax` syntax Syntax highlighting items specify folds.
+--- `fold-diff` diff Fold text that is not changed.
+---
+--- @type string
+vim.o.foldmethod = "manual"
+vim.o.fdm = vim.o.foldmethod
+vim.wo.foldmethod = vim.o.foldmethod
+vim.wo.fdm = vim.wo.foldmethod
+
+--- Sets the number of screen lines above which a fold can be displayed
+--- closed. Also for manually closed folds. With the default value of
+--- one a fold can only be closed if it takes up two or more screen lines.
+--- Set to zero to be able to close folds of just one screen line.
+--- Note that this only has an effect on what is displayed. After using
+--- "zc" to close a fold, which is displayed open because it's smaller
+--- than 'foldminlines', a following "zc" may close a containing fold.
+---
+--- @type integer
+vim.o.foldminlines = 1
+vim.o.fml = vim.o.foldminlines
+vim.wo.foldminlines = vim.o.foldminlines
+vim.wo.fml = vim.wo.foldminlines
+
+--- Sets the maximum nesting of folds for the "indent" and "syntax"
+--- methods. This avoids that too many folds will be created. Using more
+--- than 20 doesn't work, because the internal limit is 20.
+---
+--- @type integer
+vim.o.foldnestmax = 20
+vim.o.fdn = vim.o.foldnestmax
+vim.wo.foldnestmax = vim.o.foldnestmax
+vim.wo.fdn = vim.wo.foldnestmax
+
+--- Specifies for which type of commands folds will be opened, if the
+--- command moves the cursor into a closed fold. It is a comma-separated
+--- list of items.
+--- NOTE: When the command is part of a mapping this option is not used.
+--- Add the `zv` command to the mapping to get the same effect.
+--- (rationale: the mapping may want to control opening folds itself)
+---
+--- item commands ~
+--- all any
+--- block (, {, [[, [{, etc.
+--- hor horizontal movements: "l", "w", "fx", etc.
+--- insert any command in Insert mode
+--- jump far jumps: "G", "gg", etc.
+--- mark jumping to a mark: "'m", CTRL-O, etc.
+--- percent "%"
+--- quickfix ":cn", ":crew", ":make", etc.
+--- search search for a pattern: "/", "n", "*", "gd", etc.
+--- (not for a search pattern in a ":" command)
+--- Also for `[s` and `]s`.
+--- tag jumping to a tag: ":ta", CTRL-T, etc.
+--- undo undo or redo: "u" and CTRL-R
+--- When a movement command is used for an operator (e.g., "dl" or "y%")
+--- this option is not used. This means the operator will include the
+--- whole closed fold.
+--- Note that vertical movements are not here, because it would make it
+--- very difficult to move onto a closed fold.
+--- In insert mode the folds containing the cursor will always be open
+--- when text is inserted.
+--- To close folds you can re-apply 'foldlevel' with the `zx` command or
+--- set the 'foldclose' option to "all".
+---
+--- @type string
+vim.o.foldopen = "block,hor,mark,percent,quickfix,search,tag,undo"
+vim.o.fdo = vim.o.foldopen
+vim.go.foldopen = vim.o.foldopen
+vim.go.fdo = vim.go.foldopen
+
+--- An expression which is used to specify the text displayed for a closed
+--- fold. The context is set to the script where 'foldexpr' was set,
+--- script-local items can be accessed. See `fold-foldtext` for the
+--- usage.
+---
+--- The expression will be evaluated in the `sandbox` if set from a
+--- modeline, see `sandbox-option`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- It is not allowed to change text or jump to another window while
+--- evaluating 'foldtext' `textlock`.
+---
+--- @type string
+vim.o.foldtext = "foldtext()"
+vim.o.fdt = vim.o.foldtext
+vim.wo.foldtext = vim.o.foldtext
+vim.wo.fdt = vim.wo.foldtext
+
+--- Expression which is evaluated to format a range of lines for the `gq`
+--- operator or automatic formatting (see 'formatoptions'). When this
+--- option is empty 'formatprg' is used.
+---
+--- The `v:lnum` variable holds the first line to be formatted.
+--- The `v:count` variable holds the number of lines to be formatted.
+--- The `v:char` variable holds the character that is going to be
+--- inserted if the expression is being evaluated due to
+--- automatic formatting. This can be empty. Don't insert
+--- it yet!
+---
+--- Example:
+--- ```
+--- :set formatexpr=mylang#Format()
+--- ```
+--- This will invoke the mylang#Format() function in the
+--- autoload/mylang.vim file in 'runtimepath'. `autoload`
+---
+--- The expression is also evaluated when 'textwidth' is set and adding
+--- text beyond that limit. This happens under the same conditions as
+--- when internal formatting is used. Make sure the cursor is kept in the
+--- same spot relative to the text then! The `mode()` function will
+--- return "i" or "R" in this situation.
+---
+--- When the expression evaluates to non-zero Vim will fall back to using
+--- the internal format mechanism.
+---
+--- If the expression starts with s: or `<SID>`, then it is replaced with
+--- the script ID (`local-function`). Example:
+--- ```
+--- set formatexpr=s:MyFormatExpr()
+--- set formatexpr=<SID>SomeFormatExpr()
+--- ```
+--- Otherwise, the expression is evaluated in the context of the script
+--- where the option was set, thus script-local items are available.
+---
+--- The expression will be evaluated in the `sandbox` when set from a
+--- modeline, see `sandbox-option`. That stops the option from working,
+--- since changing the buffer text is not allowed.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+--- NOTE: This option is set to "" when 'compatible' is set.
+---
+--- @type string
+vim.o.formatexpr = ""
+vim.o.fex = vim.o.formatexpr
+vim.bo.formatexpr = vim.o.formatexpr
+vim.bo.fex = vim.bo.formatexpr
+
+--- A pattern that is used to recognize a list header. This is used for
+--- the "n" flag in 'formatoptions'.
+--- The pattern must match exactly the text that will be the indent for
+--- the line below it. You can use `/\ze` to mark the end of the match
+--- while still checking more characters. There must be a character
+--- following the pattern, when it matches the whole line it is handled
+--- like there is no match.
+--- The default recognizes a number, followed by an optional punctuation
+--- character and white space.
+---
+--- @type string
+vim.o.formatlistpat = "^\\s*\\d\\+[\\]:.)}\\t ]\\s*"
+vim.o.flp = vim.o.formatlistpat
+vim.bo.formatlistpat = vim.o.formatlistpat
+vim.bo.flp = vim.bo.formatlistpat
+
+--- This is a sequence of letters which describes how automatic
+--- formatting is to be done.
+--- See `fo-table` for possible values and `gq` for how to format text.
+--- Commas can be inserted for readability.
+--- To avoid problems with flags that are added in the future, use the
+--- "+=" and "-=" feature of ":set" `add-option-flags`.
+---
+--- @type string
+vim.o.formatoptions = "tcqj"
+vim.o.fo = vim.o.formatoptions
+vim.bo.formatoptions = vim.o.formatoptions
+vim.bo.fo = vim.bo.formatoptions
+
+--- The name of an external program that will be used to format the lines
+--- selected with the `gq` operator. The program must take the input on
+--- stdin and produce the output on stdout. The Unix program "fmt" is
+--- such a program.
+--- If the 'formatexpr' option is not empty it will be used instead.
+--- Otherwise, if 'formatprg' option is an empty string, the internal
+--- format function will be used `C-indenting`.
+--- Environment variables are expanded `:set_env`. See `option-backslash`
+--- about including spaces and backslashes.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.formatprg = ""
+vim.o.fp = vim.o.formatprg
+vim.bo.formatprg = vim.o.formatprg
+vim.bo.fp = vim.bo.formatprg
+vim.go.formatprg = vim.o.formatprg
+vim.go.fp = vim.go.formatprg
+
+--- When on, the OS function fsync() will be called after saving a file
+--- (`:write`, `writefile()`, …), `swap-file`, `undo-persistence` and `shada-file`.
+--- This flushes the file to disk, ensuring that it is safely written.
+--- Slow on some systems: writing buffers, quitting Nvim, and other
+--- operations may sometimes take a few seconds.
+---
+--- Files are ALWAYS flushed ('fsync' is ignored) when:
+--- - `CursorHold` event is triggered
+--- - `:preserve` is called
+--- - system signals low battery life
+--- - Nvim exits abnormally
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type boolean
+vim.o.fsync = true
+vim.o.fs = vim.o.fsync
+vim.go.fsync = vim.o.fsync
+vim.go.fs = vim.go.fsync
+
+--- When on, the ":substitute" flag 'g' is default on. This means that
+--- all matches in a line are substituted instead of one. When a 'g' flag
+--- is given to a ":substitute" command, this will toggle the substitution
+--- of all or one match. See `complex-change`.
+---
+--- command 'gdefault' on 'gdefault' off ~
+--- :s/// subst. all subst. one
+--- :s///g subst. one subst. all
+--- :s///gg subst. all subst. one
+---
+--- DEPRECATED: Setting this option may break plugins that are not aware
+--- of this option. Also, many users get confused that adding the /g flag
+--- has the opposite effect of that it normally does.
+---
+--- @type boolean
+vim.o.gdefault = false
+vim.o.gd = vim.o.gdefault
+vim.go.gdefault = vim.o.gdefault
+vim.go.gd = vim.go.gdefault
+
+--- Format to recognize for the ":grep" command output.
+--- This is a scanf-like string that uses the same format as the
+--- 'errorformat' option: see `errorformat`.
+---
+--- @type string
+vim.o.grepformat = "%f:%l:%m,%f:%l%m,%f %l%m"
+vim.o.gfm = vim.o.grepformat
+vim.go.grepformat = vim.o.grepformat
+vim.go.gfm = vim.go.grepformat
+
+--- Program to use for the `:grep` command. This option may contain '%'
+--- and '#' characters, which are expanded like when used in a command-
+--- line. The placeholder "$*" is allowed to specify where the arguments
+--- will be included. Environment variables are expanded `:set_env`. See
+--- `option-backslash` about including spaces and backslashes.
+--- When your "grep" accepts the "-H" argument, use this to make ":grep"
+--- also work well with a single file:
+--- ```
+--- :set grepprg=grep\ -nH
+--- ```
+--- Special value: When 'grepprg' is set to "internal" the `:grep` command
+--- works like `:vimgrep`, `:lgrep` like `:lvimgrep`, `:grepadd` like
+--- `:vimgrepadd` and `:lgrepadd` like `:lvimgrepadd`.
+--- See also the section `:make_makeprg`, since most of the comments there
+--- apply equally to 'grepprg'.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.grepprg = "grep -n $* /dev/null"
+vim.o.gp = vim.o.grepprg
+vim.bo.grepprg = vim.o.grepprg
+vim.bo.gp = vim.bo.grepprg
+vim.go.grepprg = vim.o.grepprg
+vim.go.gp = vim.go.grepprg
+
+--- Configures the cursor style for each mode. Works in the GUI and many
+--- terminals. See `tui-cursor-shape`.
+---
+--- To disable cursor-styling, reset the option:
+--- ```
+--- :set guicursor=
+--- ```
+--- To enable mode shapes, "Cursor" highlight, and blinking:
+--- ```
+--- :set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
+--- \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor
+--- \,sm:block-blinkwait175-blinkoff150-blinkon175
+--- ```
+--- The option is a comma-separated list of parts. Each part consists of a
+--- mode-list and an argument-list:
+--- mode-list:argument-list,mode-list:argument-list,..
+--- The mode-list is a dash separated list of these modes:
+--- n Normal mode
+--- v Visual mode
+--- ve Visual mode with 'selection' "exclusive" (same as 'v',
+--- if not specified)
+--- o Operator-pending mode
+--- i Insert mode
+--- r Replace mode
+--- c Command-line Normal (append) mode
+--- ci Command-line Insert mode
+--- cr Command-line Replace mode
+--- sm showmatch in Insert mode
+--- a all modes
+--- The argument-list is a dash separated list of these arguments:
+--- hor{N} horizontal bar, {N} percent of the character height
+--- ver{N} vertical bar, {N} percent of the character width
+--- block block cursor, fills the whole character
+--- - Only one of the above three should be present.
+--- - Default is "block" for each mode.
+--- blinkwait{N} *cursor-blinking*
+--- blinkon{N}
+--- blinkoff{N}
+--- blink times for cursor: blinkwait is the delay before
+--- the cursor starts blinking, blinkon is the time that
+--- the cursor is shown and blinkoff is the time that the
+--- cursor is not shown. Times are in msec. When one of
+--- the numbers is zero, there is no blinking. E.g.:
+--- ```
+--- :set guicursor=n:blinkon0
+--- ```
+--- - Default is "blinkon0" for each mode.
+--- {group-name}
+--- Highlight group that decides the color and font of the
+--- cursor.
+--- In the `TUI`:
+--- - `inverse`/reverse and no group-name are interpreted
+--- as "host-terminal default cursor colors" which
+--- typically means "inverted bg and fg colors".
+--- - `ctermfg` and `guifg` are ignored.
+--- {group-name}/{group-name}
+--- Two highlight group names, the first is used when
+--- no language mappings are used, the other when they
+--- are. `language-mapping`
+---
+--- Examples of parts:
+--- n-c-v:block-nCursor In Normal, Command-line and Visual mode, use a
+--- block cursor with colors from the "nCursor"
+--- highlight group
+--- n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20
+--- In Normal et al. modes, use a block cursor
+--- with the default colors defined by the host
+--- terminal. In Insert-like modes, use
+--- a vertical bar cursor with colors from
+--- "Cursor" highlight group. In Replace-like
+--- modes, use an underline cursor with
+--- default colors.
+--- i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150
+--- In Insert and Command-line Insert mode, use a
+--- 30% vertical bar cursor with colors from the
+--- "iCursor" highlight group. Blink a bit
+--- faster.
+---
+--- The 'a' mode is different. It will set the given argument-list for
+--- all modes. It does not reset anything to defaults. This can be used
+--- to do a common setting for all modes. For example, to switch off
+--- blinking: "a:blinkon0"
+---
+--- Examples of cursor highlighting:
+--- ```
+--- :highlight Cursor gui=reverse guifg=NONE guibg=NONE
+--- :highlight Cursor gui=NONE guifg=bg guibg=fg
+--- ```
+---
+---
+--- @type string
+vim.o.guicursor = "n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20"
+vim.o.gcr = vim.o.guicursor
+vim.go.guicursor = vim.o.guicursor
+vim.go.gcr = vim.go.guicursor
+
+--- This is a list of fonts which will be used for the GUI version of Vim.
+--- In its simplest form the value is just one font name. When
+--- the font cannot be found you will get an error message. To try other
+--- font names a list can be specified, font names separated with commas.
+--- The first valid font is used.
+---
+--- Spaces after a comma are ignored. To include a comma in a font name
+--- precede it with a backslash. Setting an option requires an extra
+--- backslash before a space and a backslash. See also
+--- `option-backslash`. For example:
+--- ```
+--- :set guifont=Screen15,\ 7x13,font\\,with\\,commas
+--- ```
+--- will make Vim try to use the font "Screen15" first, and if it fails it
+--- will try to use "7x13" and then "font,with,commas" instead.
+---
+--- If none of the fonts can be loaded, Vim will keep the current setting.
+--- If an empty font list is given, Vim will try using other resource
+--- settings (for X, it will use the Vim.font resource), and finally it
+--- will try some builtin default which should always be there ("7x13" in
+--- the case of X). The font names given should be "normal" fonts. Vim
+--- will try to find the related bold and italic fonts.
+---
+--- For Win32 and Mac OS:
+--- ```
+--- :set guifont=*
+--- ```
+--- will bring up a font requester, where you can pick the font you want.
+---
+--- The font name depends on the GUI used.
+---
+--- For Mac OSX you can use something like this:
+--- ```
+--- :set guifont=Monaco:h10
+--- ```
+--- *E236*
+--- Note that the fonts must be mono-spaced (all characters have the same
+--- width).
+---
+--- To preview a font on X11, you might be able to use the "xfontsel"
+--- program. The "xlsfonts" program gives a list of all available fonts.
+---
+--- For the Win32 GUI *E244* *E245*
+--- - takes these options in the font name:
+--- hXX - height is XX (points, can be floating-point)
+--- wXX - width is XX (points, can be floating-point)
+--- b - bold
+--- i - italic
+--- u - underline
+--- s - strikeout
+--- cXX - character set XX. Valid charsets are: ANSI, ARABIC,
+--- BALTIC, CHINESEBIG5, DEFAULT, EASTEUROPE, GB2312, GREEK,
+--- HANGEUL, HEBREW, JOHAB, MAC, OEM, RUSSIAN, SHIFTJIS,
+--- SYMBOL, THAI, TURKISH, VIETNAMESE ANSI and BALTIC.
+--- Normally you would use "cDEFAULT".
+---
+--- Use a ':' to separate the options.
+--- - A '_' can be used in the place of a space, so you don't need to use
+--- backslashes to escape the spaces.
+--- - Examples:
+--- ```
+--- :set guifont=courier_new:h12:w5:b:cRUSSIAN
+--- :set guifont=Andale_Mono:h7.5:w4.5
+--- ```
+---
+---
+--- @type string
+vim.o.guifont = ""
+vim.o.gfn = vim.o.guifont
+vim.go.guifont = vim.o.guifont
+vim.go.gfn = vim.go.guifont
+
+--- Comma-separated list of fonts to be used for double-width characters.
+--- The first font that can be loaded is used.
+--- Note: The size of these fonts must be exactly twice as wide as the one
+--- specified with 'guifont' and the same height.
+---
+--- When 'guifont' has a valid font and 'guifontwide' is empty Vim will
+--- attempt to set 'guifontwide' to a matching double-width font.
+---
+--- @type string
+vim.o.guifontwide = ""
+vim.o.gfw = vim.o.guifontwide
+vim.go.guifontwide = vim.o.guifontwide
+vim.go.gfw = vim.go.guifontwide
+
+--- This option only has an effect in the GUI version of Vim. It is a
+--- sequence of letters which describes what components and options of the
+--- GUI should be used.
+--- To avoid problems with flags that are added in the future, use the
+--- "+=" and "-=" feature of ":set" `add-option-flags`.
+---
+--- Valid letters are as follows:
+--- *guioptions_a* *'go-a'*
+--- 'a' Autoselect: If present, then whenever VISUAL mode is started,
+--- or the Visual area extended, Vim tries to become the owner of
+--- the windowing system's global selection. This means that the
+--- Visually highlighted text is available for pasting into other
+--- applications as well as into Vim itself. When the Visual mode
+--- ends, possibly due to an operation on the text, or when an
+--- application wants to paste the selection, the highlighted text
+--- is automatically yanked into the "* selection register.
+--- Thus the selection is still available for pasting into other
+--- applications after the VISUAL mode has ended.
+--- If not present, then Vim won't become the owner of the
+--- windowing system's global selection unless explicitly told to
+--- by a yank or delete operation for the "* register.
+--- The same applies to the modeless selection.
+--- *'go-P'*
+--- 'P' Like autoselect but using the "+ register instead of the "*
+--- register.
+--- *'go-A'*
+--- 'A' Autoselect for the modeless selection. Like 'a', but only
+--- applies to the modeless selection.
+---
+--- 'guioptions' autoselect Visual autoselect modeless ~
+--- "" - -
+--- "a" yes yes
+--- "A" - yes
+--- "aA" yes yes
+---
+--- *'go-c'*
+--- 'c' Use console dialogs instead of popup dialogs for simple
+--- choices.
+--- *'go-d'*
+--- 'd' Use dark theme variant if available.
+--- *'go-e'*
+--- 'e' Add tab pages when indicated with 'showtabline'.
+--- 'guitablabel' can be used to change the text in the labels.
+--- When 'e' is missing a non-GUI tab pages line may be used.
+--- The GUI tabs are only supported on some systems, currently
+--- Mac OS/X and MS-Windows.
+--- *'go-i'*
+--- 'i' Use a Vim icon.
+--- *'go-m'*
+--- 'm' Menu bar is present.
+--- *'go-M'*
+--- 'M' The system menu "$VIMRUNTIME/menu.vim" is not sourced. Note
+--- that this flag must be added in the vimrc file, before
+--- switching on syntax or filetype recognition (when the `gvimrc`
+--- file is sourced the system menu has already been loaded; the
+--- `:syntax on` and `:filetype on` commands load the menu too).
+--- *'go-g'*
+--- 'g' Grey menu items: Make menu items that are not active grey. If
+--- 'g' is not included inactive menu items are not shown at all.
+--- *'go-T'*
+--- 'T' Include Toolbar. Currently only in Win32 GUI.
+--- *'go-r'*
+--- 'r' Right-hand scrollbar is always present.
+--- *'go-R'*
+--- 'R' Right-hand scrollbar is present when there is a vertically
+--- split window.
+--- *'go-l'*
+--- 'l' Left-hand scrollbar is always present.
+--- *'go-L'*
+--- 'L' Left-hand scrollbar is present when there is a vertically
+--- split window.
+--- *'go-b'*
+--- 'b' Bottom (horizontal) scrollbar is present. Its size depends on
+--- the longest visible line, or on the cursor line if the 'h'
+--- flag is included. `gui-horiz-scroll`
+--- *'go-h'*
+--- 'h' Limit horizontal scrollbar size to the length of the cursor
+--- line. Reduces computations. `gui-horiz-scroll`
+---
+--- And yes, you may even have scrollbars on the left AND the right if
+--- you really want to :-). See `gui-scrollbars` for more information.
+---
+--- *'go-v'*
+--- 'v' Use a vertical button layout for dialogs. When not included,
+--- a horizontal layout is preferred, but when it doesn't fit a
+--- vertical layout is used anyway. Not supported in GTK 3.
+--- *'go-p'*
+--- 'p' Use Pointer callbacks for X11 GUI. This is required for some
+--- window managers. If the cursor is not blinking or hollow at
+--- the right moment, try adding this flag. This must be done
+--- before starting the GUI. Set it in your `gvimrc`. Adding or
+--- removing it after the GUI has started has no effect.
+--- *'go-k'*
+--- 'k' Keep the GUI window size when adding/removing a scrollbar, or
+--- toolbar, tabline, etc. Instead, the behavior is similar to
+--- when the window is maximized and will adjust 'lines' and
+--- 'columns' to fit to the window. Without the 'k' flag Vim will
+--- try to keep 'lines' and 'columns' the same when adding and
+--- removing GUI components.
+---
+--- @type string
+vim.o.guioptions = ""
+vim.o.go = vim.o.guioptions
+vim.go.guioptions = vim.o.guioptions
+vim.go.go = vim.go.guioptions
+
+--- When non-empty describes the text to use in a label of the GUI tab
+--- pages line. When empty and when the result is empty Vim will use a
+--- default label. See `setting-guitablabel` for more info.
+---
+--- The format of this option is like that of 'statusline'.
+--- 'guitabtooltip' is used for the tooltip, see below.
+--- The expression will be evaluated in the `sandbox` when set from a
+--- modeline, see `sandbox-option`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- Only used when the GUI tab pages line is displayed. 'e' must be
+--- present in 'guioptions'. For the non-GUI tab pages line 'tabline' is
+--- used.
+---
+--- @type string
+vim.o.guitablabel = ""
+vim.o.gtl = vim.o.guitablabel
+vim.go.guitablabel = vim.o.guitablabel
+vim.go.gtl = vim.go.guitablabel
+
+--- When non-empty describes the text to use in a tooltip for the GUI tab
+--- pages line. When empty Vim will use a default tooltip.
+--- This option is otherwise just like 'guitablabel' above.
+--- You can include a line break. Simplest method is to use `:let`:
+--- ```
+--- :let &guitabtooltip = "line one\nline two"
+--- ```
+---
+---
+--- @type string
+vim.o.guitabtooltip = ""
+vim.o.gtt = vim.o.guitabtooltip
+vim.go.guitabtooltip = vim.o.guitabtooltip
+vim.go.gtt = vim.go.guitabtooltip
+
+--- Name of the main help file. All distributed help files should be
+--- placed together in one directory. Additionally, all "doc" directories
+--- in 'runtimepath' will be used.
+--- Environment variables are expanded `:set_env`. For example:
+--- "$VIMRUNTIME/doc/help.txt". If $VIMRUNTIME is not set, $VIM is also
+--- tried. Also see `$VIMRUNTIME` and `option-backslash` about including
+--- spaces and backslashes.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.helpfile = "$VIMRUNTIME/doc/help.txt"
+vim.o.hf = vim.o.helpfile
+vim.go.helpfile = vim.o.helpfile
+vim.go.hf = vim.go.helpfile
+
+--- Minimal initial height of the help window when it is opened with the
+--- ":help" command. The initial height of the help window is half of the
+--- current window, or (when the 'ea' option is on) the same as other
+--- windows. When the height is less than 'helpheight', the height is
+--- set to 'helpheight'. Set to zero to disable.
+---
+--- @type integer
+vim.o.helpheight = 20
+vim.o.hh = vim.o.helpheight
+vim.go.helpheight = vim.o.helpheight
+vim.go.hh = vim.go.helpheight
+
+--- Comma-separated list of languages. Vim will use the first language
+--- for which the desired help can be found. The English help will always
+--- be used as a last resort. You can add "en" to prefer English over
+--- another language, but that will only find tags that exist in that
+--- language and not in the English help.
+--- Example:
+--- ```
+--- :set helplang=de,it
+--- ```
+--- This will first search German, then Italian and finally English help
+--- files.
+--- When using `CTRL-]` and ":help!" in a non-English help file Vim will
+--- try to find the tag in the current language before using this option.
+--- See `help-translated`.
+---
+--- @type string
+vim.o.helplang = ""
+vim.o.hlg = vim.o.helplang
+vim.go.helplang = vim.o.helplang
+vim.go.hlg = vim.go.helplang
+
+--- When off a buffer is unloaded (including loss of undo information)
+--- when it is `abandon`ed. When on a buffer becomes hidden when it is
+--- `abandon`ed. A buffer displayed in another window does not become
+--- hidden, of course.
+---
+--- Commands that move through the buffer list sometimes hide a buffer
+--- although the 'hidden' option is off when these three are true:
+--- - the buffer is modified
+--- - 'autowrite' is off or writing is not possible
+--- - the '!' flag was used
+--- Also see `windows`.
+---
+--- To hide a specific buffer use the 'bufhidden' option.
+--- 'hidden' is set for one command with ":hide {command}" `:hide`.
+---
+--- @type boolean
+vim.o.hidden = true
+vim.o.hid = vim.o.hidden
+vim.go.hidden = vim.o.hidden
+vim.go.hid = vim.go.hidden
+
+--- A history of ":" commands, and a history of previous search patterns
+--- is remembered. This option decides how many entries may be stored in
+--- each of these histories (see `cmdline-editing`).
+--- The maximum value is 10000.
+---
+--- @type integer
+vim.o.history = 10000
+vim.o.hi = vim.o.history
+vim.go.history = vim.o.history
+vim.go.hi = vim.go.history
+
+--- When there is a previous search pattern, highlight all its matches.
+--- The `hl-Search` highlight group determines the highlighting for all
+--- matches not under the cursor while the `hl-CurSearch` highlight group
+--- (if defined) determines the highlighting for the match under the
+--- cursor. If `hl-CurSearch` is not defined, then `hl-Search` is used for
+--- both. Note that only the matching text is highlighted, any offsets
+--- are not applied.
+--- See also: 'incsearch' and `:match`.
+--- When you get bored looking at the highlighted matches, you can turn it
+--- off with `:nohlsearch`. This does not change the option value, as
+--- soon as you use a search command, the highlighting comes back.
+--- 'redrawtime' specifies the maximum time spent on finding matches.
+--- When the search pattern can match an end-of-line, Vim will try to
+--- highlight all of the matched text. However, this depends on where the
+--- search starts. This will be the first line in the window or the first
+--- line below a closed fold. A match in a previous line which is not
+--- drawn may not continue in a newly drawn line.
+--- You can specify whether the highlight status is restored on startup
+--- with the 'h' flag in 'shada' `shada-h`.
+---
+--- @type boolean
+vim.o.hlsearch = true
+vim.o.hls = vim.o.hlsearch
+vim.go.hlsearch = vim.o.hlsearch
+vim.go.hls = vim.go.hlsearch
+
+--- When on, the icon text of the window will be set to the value of
+--- 'iconstring' (if it is not empty), or to the name of the file
+--- currently being edited. Only the last part of the name is used.
+--- Overridden by the 'iconstring' option.
+--- Only works if the terminal supports setting window icons.
+---
+--- @type boolean
+vim.o.icon = false
+vim.go.icon = vim.o.icon
+
+--- When this option is not empty, it will be used for the icon text of
+--- the window. This happens only when the 'icon' option is on.
+--- Only works if the terminal supports setting window icon text
+--- When this option contains printf-style '%' items, they will be
+--- expanded according to the rules used for 'statusline'. See
+--- 'titlestring' for example settings.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- @type string
+vim.o.iconstring = ""
+vim.go.iconstring = vim.o.iconstring
+
+--- Ignore case in search patterns, `cmdline-completion`, when
+--- searching in the tags file, and `expr-==`.
+--- Also see 'smartcase' and 'tagcase'.
+--- Can be overruled by using "\c" or "\C" in the pattern, see
+--- `/ignorecase`.
+---
+--- @type boolean
+vim.o.ignorecase = false
+vim.o.ic = vim.o.ignorecase
+vim.go.ignorecase = vim.o.ignorecase
+vim.go.ic = vim.go.ignorecase
+
+--- When set the Input Method is always on when starting to edit a command
+--- line, unless entering a search pattern (see 'imsearch' for that).
+--- Setting this option is useful when your input method allows entering
+--- English characters directly, e.g., when it's used to type accented
+--- characters with dead keys.
+---
+--- @type boolean
+vim.o.imcmdline = false
+vim.o.imc = vim.o.imcmdline
+vim.go.imcmdline = vim.o.imcmdline
+vim.go.imc = vim.go.imcmdline
+
+--- When set the Input Method is never used. This is useful to disable
+--- the IM when it doesn't work properly.
+--- Currently this option is on by default for SGI/IRIX machines. This
+--- may change in later releases.
+---
+--- @type boolean
+vim.o.imdisable = false
+vim.o.imd = vim.o.imdisable
+vim.go.imdisable = vim.o.imdisable
+vim.go.imd = vim.go.imdisable
+
+--- Specifies whether :lmap or an Input Method (IM) is to be used in
+--- Insert mode. Valid values:
+--- 0 :lmap is off and IM is off
+--- 1 :lmap is ON and IM is off
+--- 2 :lmap is off and IM is ON
+--- To always reset the option to zero when leaving Insert mode with <Esc>
+--- this can be used:
+--- ```
+--- :inoremap <ESC> <ESC>:set iminsert=0<CR>
+--- ```
+--- This makes :lmap and IM turn off automatically when leaving Insert
+--- mode.
+--- Note that this option changes when using CTRL-^ in Insert mode
+--- `i_CTRL-^`.
+--- The value is set to 1 when setting 'keymap' to a valid keymap name.
+--- It is also used for the argument of commands like "r" and "f".
+---
+--- @type integer
+vim.o.iminsert = 0
+vim.o.imi = vim.o.iminsert
+vim.bo.iminsert = vim.o.iminsert
+vim.bo.imi = vim.bo.iminsert
+
+--- Specifies whether :lmap or an Input Method (IM) is to be used when
+--- entering a search pattern. Valid values:
+--- -1 the value of 'iminsert' is used, makes it look like
+--- 'iminsert' is also used when typing a search pattern
+--- 0 :lmap is off and IM is off
+--- 1 :lmap is ON and IM is off
+--- 2 :lmap is off and IM is ON
+--- Note that this option changes when using CTRL-^ in Command-line mode
+--- `c_CTRL-^`.
+--- The value is set to 1 when it is not -1 and setting the 'keymap'
+--- option to a valid keymap name.
+---
+--- @type integer
+vim.o.imsearch = -1
+vim.o.ims = vim.o.imsearch
+vim.bo.imsearch = vim.o.imsearch
+vim.bo.ims = vim.bo.imsearch
+
+--- When nonempty, shows the effects of `:substitute`, `:smagic|,
+--- |:snomagic` and user commands with the `:command-preview` flag as you
+--- type.
+---
+--- Possible values:
+--- nosplit Shows the effects of a command incrementally in the
+--- buffer.
+--- split Like "nosplit", but also shows partial off-screen
+--- results in a preview window.
+---
+--- If the preview for built-in commands is too slow (exceeds
+--- 'redrawtime') then 'inccommand' is automatically disabled until
+--- `Command-line-mode` is done.
+---
+--- @type string
+vim.o.inccommand = "nosplit"
+vim.o.icm = vim.o.inccommand
+vim.go.inccommand = vim.o.inccommand
+vim.go.icm = vim.go.inccommand
+
+--- Pattern to be used to find an include command. It is a search
+--- pattern, just like for the "/" command (See `pattern`). This option
+--- is used for the commands "[i", "]I", "[d", etc.
+--- Normally the 'isfname' option is used to recognize the file name that
+--- comes after the matched pattern. But if "\zs" appears in the pattern
+--- then the text matched from "\zs" to the end, or until "\ze" if it
+--- appears, is used as the file name. Use this to include characters
+--- that are not in 'isfname', such as a space. You can then use
+--- 'includeexpr' to process the matched text.
+--- See `option-backslash` about including spaces and backslashes.
+---
+--- @type string
+vim.o.include = ""
+vim.o.inc = vim.o.include
+vim.bo.include = vim.o.include
+vim.bo.inc = vim.bo.include
+vim.go.include = vim.o.include
+vim.go.inc = vim.go.include
+
+--- Expression to be used to transform the string found with the 'include'
+--- option to a file name. Mostly useful to change "." to "/" for Java:
+--- ```
+--- :setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+--- ```
+--- The "v:fname" variable will be set to the file name that was detected.
+--- Note the double backslash: the `:set` command first halves them, then
+--- one remains in the value, where "\." matches a dot literally. For
+--- simple character replacements `tr()` avoids the need for escaping:
+--- ```
+--- :setlocal includeexpr=tr(v:fname,'.','/')
+--- ```
+---
+--- Also used for the `gf` command if an unmodified file name can't be
+--- found. Allows doing "gf" on the name after an 'include' statement.
+--- Also used for `<cfile>`.
+---
+--- If the expression starts with s: or `<SID>`, then it is replaced with
+--- the script ID (`local-function`). Example:
+--- ```
+--- setlocal includeexpr=s:MyIncludeExpr(v:fname)
+--- setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
+--- ```
+--- Otherwise, the expression is evaluated in the context of the script
+--- where the option was set, thus script-local items are available.
+---
+--- The expression will be evaluated in the `sandbox` when set from a
+--- modeline, see `sandbox-option`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- It is not allowed to change text or jump to another window while
+--- evaluating 'includeexpr' `textlock`.
+---
+--- @type string
+vim.o.includeexpr = ""
+vim.o.inex = vim.o.includeexpr
+vim.bo.includeexpr = vim.o.includeexpr
+vim.bo.inex = vim.bo.includeexpr
+
+--- While typing a search command, show where the pattern, as it was typed
+--- so far, matches. The matched string is highlighted. If the pattern
+--- is invalid or not found, nothing is shown. The screen will be updated
+--- often, this is only useful on fast terminals.
+--- Note that the match will be shown, but the cursor will return to its
+--- original position when no match is found and when pressing <Esc>. You
+--- still need to finish the search command with <Enter> to move the
+--- cursor to the match.
+--- You can use the CTRL-G and CTRL-T keys to move to the next and
+--- previous match. `c_CTRL-G` `c_CTRL-T`
+--- Vim only searches for about half a second. With a complicated
+--- pattern and/or a lot of text the match may not be found. This is to
+--- avoid that Vim hangs while you are typing the pattern.
+--- The `hl-IncSearch` highlight group determines the highlighting.
+--- When 'hlsearch' is on, all matched strings are highlighted too while
+--- typing a search command. See also: 'hlsearch'.
+--- If you don't want to turn 'hlsearch' on, but want to highlight all
+--- matches while searching, you can turn on and off 'hlsearch' with
+--- autocmd. Example:
+--- ```
+--- augroup vimrc-incsearch-highlight
+--- autocmd!
+--- autocmd CmdlineEnter /,\? :set hlsearch
+--- autocmd CmdlineLeave /,\? :set nohlsearch
+--- augroup END
+--- ```
+---
+--- CTRL-L can be used to add one character from after the current match
+--- to the command line. If 'ignorecase' and 'smartcase' are set and the
+--- command line has no uppercase characters, the added character is
+--- converted to lowercase.
+--- CTRL-R CTRL-W can be used to add the word at the end of the current
+--- match, excluding the characters that were already typed.
+---
+--- @type boolean
+vim.o.incsearch = true
+vim.o.is = vim.o.incsearch
+vim.go.incsearch = vim.o.incsearch
+vim.go.is = vim.go.incsearch
+
+--- Expression which is evaluated to obtain the proper indent for a line.
+--- It is used when a new line is created, for the `=` operator and
+--- in Insert mode as specified with the 'indentkeys' option.
+--- When this option is not empty, it overrules the 'cindent' and
+--- 'smartindent' indenting. When 'lisp' is set, this option is
+--- is only used when 'lispoptions' contains "expr:1".
+--- The expression is evaluated with `v:lnum` set to the line number for
+--- which the indent is to be computed. The cursor is also in this line
+--- when the expression is evaluated (but it may be moved around).
+---
+--- If the expression starts with s: or `<SID>`, then it is replaced with
+--- the script ID (`local-function`). Example:
+--- ```
+--- set indentexpr=s:MyIndentExpr()
+--- set indentexpr=<SID>SomeIndentExpr()
+--- ```
+--- Otherwise, the expression is evaluated in the context of the script
+--- where the option was set, thus script-local items are available.
+---
+--- The expression must return the number of spaces worth of indent. It
+--- can return "-1" to keep the current indent (this means 'autoindent' is
+--- used for the indent).
+--- Functions useful for computing the indent are `indent()`, `cindent()`
+--- and `lispindent()`.
+--- The evaluation of the expression must not have side effects! It must
+--- not change the text, jump to another window, etc. Afterwards the
+--- cursor position is always restored, thus the cursor may be moved.
+--- Normally this option would be set to call a function:
+--- ```
+--- :set indentexpr=GetMyIndent()
+--- ```
+--- Error messages will be suppressed, unless the 'debug' option contains
+--- "msg".
+--- See `indent-expression`.
+---
+--- The expression will be evaluated in the `sandbox` when set from a
+--- modeline, see `sandbox-option`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- It is not allowed to change text or jump to another window while
+--- evaluating 'indentexpr' `textlock`.
+---
+--- @type string
+vim.o.indentexpr = ""
+vim.o.inde = vim.o.indentexpr
+vim.bo.indentexpr = vim.o.indentexpr
+vim.bo.inde = vim.bo.indentexpr
+
+--- A list of keys that, when typed in Insert mode, cause reindenting of
+--- the current line. Only happens if 'indentexpr' isn't empty.
+--- The format is identical to 'cinkeys', see `indentkeys-format`.
+--- See `C-indenting` and `indent-expression`.
+---
+--- @type string
+vim.o.indentkeys = "0{,0},0),0],:,0#,!^F,o,O,e"
+vim.o.indk = vim.o.indentkeys
+vim.bo.indentkeys = vim.o.indentkeys
+vim.bo.indk = vim.bo.indentkeys
+
+--- When doing keyword completion in insert mode `ins-completion`, and
+--- 'ignorecase' is also on, the case of the match is adjusted depending
+--- on the typed text. If the typed text contains a lowercase letter
+--- where the match has an upper case letter, the completed part is made
+--- lowercase. If the typed text has no lowercase letters and the match
+--- has a lowercase letter where the typed text has an uppercase letter,
+--- and there is a letter before it, the completed part is made uppercase.
+--- With 'noinfercase' the match is used as-is.
+---
+--- @type boolean
+vim.o.infercase = false
+vim.o.inf = vim.o.infercase
+vim.bo.infercase = vim.o.infercase
+vim.bo.inf = vim.bo.infercase
+
+--- The characters specified by this option are included in file names and
+--- path names. Filenames are used for commands like "gf", "[i" and in
+--- the tags file. It is also used for "\f" in a `pattern`.
+--- Multi-byte characters 256 and above are always included, only the
+--- characters up to 255 are specified with this option.
+--- For UTF-8 the characters 0xa0 to 0xff are included as well.
+--- Think twice before adding white space to this option. Although a
+--- space may appear inside a file name, the effect will be that Vim
+--- doesn't know where a file name starts or ends when doing completion.
+--- It most likely works better without a space in 'isfname'.
+---
+--- Note that on systems using a backslash as path separator, Vim tries to
+--- do its best to make it work as you would expect. That is a bit
+--- tricky, since Vi originally used the backslash to escape special
+--- characters. Vim will not remove a backslash in front of a normal file
+--- name character on these systems, but it will on Unix and alikes. The
+--- '&' and '^' are not included by default, because these are special for
+--- cmd.exe.
+---
+--- The format of this option is a list of parts, separated with commas.
+--- Each part can be a single character number or a range. A range is two
+--- character numbers with '-' in between. A character number can be a
+--- decimal number between 0 and 255 or the ASCII character itself (does
+--- not work for digits). Example:
+--- "_,-,128-140,#-43" (include '_' and '-' and the range
+--- 128 to 140 and '#' to 43)
+--- If a part starts with '^', the following character number or range
+--- will be excluded from the option. The option is interpreted from left
+--- to right. Put the excluded character after the range where it is
+--- included. To include '^' itself use it as the last character of the
+--- option or the end of a range. Example:
+--- "^a-z,#,^" (exclude 'a' to 'z', include '#' and '^')
+--- If the character is '@', all characters where isalpha() returns TRUE
+--- are included. Normally these are the characters a to z and A to Z,
+--- plus accented characters. To include '@' itself use "@-@". Examples:
+--- "@,^a-z" All alphabetic characters, excluding lower
+--- case ASCII letters.
+--- "a-z,A-Z,@-@" All letters plus the '@' character.
+--- A comma can be included by using it where a character number is
+--- expected. Example:
+--- "48-57,,,_" Digits, comma and underscore.
+--- A comma can be excluded by prepending a '^'. Example:
+--- " -~,^,,9" All characters from space to '~', excluding
+--- comma, plus <Tab>.
+--- See `option-backslash` about including spaces and backslashes.
+---
+--- @type string
+vim.o.isfname = "@,48-57,/,.,-,_,+,,,#,$,%,~,="
+vim.o.isf = vim.o.isfname
+vim.go.isfname = vim.o.isfname
+vim.go.isf = vim.go.isfname
+
+--- The characters given by this option are included in identifiers.
+--- Identifiers are used in recognizing environment variables and after a
+--- match of the 'define' option. It is also used for "\i" in a
+--- `pattern`. See 'isfname' for a description of the format of this
+--- option. For '@' only characters up to 255 are used.
+--- Careful: If you change this option, it might break expanding
+--- environment variables. E.g., when '/' is included and Vim tries to
+--- expand "$HOME/.local/state/nvim/shada/main.shada". Maybe you should
+--- change 'iskeyword' instead.
+---
+--- @type string
+vim.o.isident = "@,48-57,_,192-255"
+vim.o.isi = vim.o.isident
+vim.go.isident = vim.o.isident
+vim.go.isi = vim.go.isident
+
+--- Keywords are used in searching and recognizing with many commands:
+--- "w", "*", "[i", etc. It is also used for "\k" in a `pattern`. See
+--- 'isfname' for a description of the format of this option. For '@'
+--- characters above 255 check the "word" character class (any character
+--- that is not white space or punctuation).
+--- For C programs you could use "a-z,A-Z,48-57,_,.,-,>".
+--- For a help file it is set to all non-blank printable characters except
+--- "*", '"' and '|' (so that CTRL-] on a command finds the help for that
+--- command).
+--- When the 'lisp' option is on the '-' character is always included.
+--- This option also influences syntax highlighting, unless the syntax
+--- uses `:syn-iskeyword`.
+---
+--- @type string
+vim.o.iskeyword = "@,48-57,_,192-255"
+vim.o.isk = vim.o.iskeyword
+vim.bo.iskeyword = vim.o.iskeyword
+vim.bo.isk = vim.bo.iskeyword
+
+--- The characters given by this option are displayed directly on the
+--- screen. It is also used for "\p" in a `pattern`. The characters from
+--- space (ASCII 32) to '~' (ASCII 126) are always displayed directly,
+--- even when they are not included in 'isprint' or excluded. See
+--- 'isfname' for a description of the format of this option.
+---
+--- Non-printable characters are displayed with two characters:
+--- 0 - 31 "^@" - "^_"
+--- 32 - 126 always single characters
+--- 127 "^?"
+--- 128 - 159 "~@" - "~_"
+--- 160 - 254 "| " - "|~"
+--- 255 "~?"
+--- Illegal bytes from 128 to 255 (invalid UTF-8) are
+--- displayed as <xx>, with the hexadecimal value of the byte.
+--- When 'display' contains "uhex" all unprintable characters are
+--- displayed as <xx>.
+--- The SpecialKey highlighting will be used for unprintable characters.
+--- `hl-SpecialKey`
+---
+--- Multi-byte characters 256 and above are always included, only the
+--- characters up to 255 are specified with this option. When a character
+--- is printable but it is not available in the current font, a
+--- replacement character will be shown.
+--- Unprintable and zero-width Unicode characters are displayed as <xxxx>.
+--- There is no option to specify these characters.
+---
+--- @type string
+vim.o.isprint = "@,161-255"
+vim.o.isp = vim.o.isprint
+vim.go.isprint = vim.o.isprint
+vim.go.isp = vim.go.isprint
+
+--- Insert two spaces after a '.', '?' and '!' with a join command.
+--- Otherwise only one space is inserted.
+---
+--- @type boolean
+vim.o.joinspaces = false
+vim.o.js = vim.o.joinspaces
+vim.go.joinspaces = vim.o.joinspaces
+vim.go.js = vim.go.joinspaces
+
+--- List of words that change the behavior of the `jumplist`.
+--- stack Make the jumplist behave like the tagstack.
+--- Relative location of entries in the jumplist is
+--- preserved at the cost of discarding subsequent entries
+--- when navigating backwards in the jumplist and then
+--- jumping to a location. `jumplist-stack`
+---
+--- view When moving through the jumplist, `changelist|,
+--- |alternate-file` or using `mark-motions` try to
+--- restore the `mark-view` in which the action occurred.
+---
+--- @type string
+vim.o.jumpoptions = ""
+vim.o.jop = vim.o.jumpoptions
+vim.go.jumpoptions = vim.o.jumpoptions
+vim.go.jop = vim.go.jumpoptions
+
+--- Name of a keyboard mapping. See `mbyte-keymap`.
+--- Setting this option to a valid keymap name has the side effect of
+--- setting 'iminsert' to one, so that the keymap becomes effective.
+--- 'imsearch' is also set to one, unless it was -1
+--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+---
+--- @type string
+vim.o.keymap = ""
+vim.o.kmp = vim.o.keymap
+vim.bo.keymap = vim.o.keymap
+vim.bo.kmp = vim.bo.keymap
+
+--- List of comma-separated words, which enable special things that keys
+--- can do. These values can be used:
+--- startsel Using a shifted special key starts selection (either
+--- Select mode or Visual mode, depending on "key" being
+--- present in 'selectmode').
+--- stopsel Using a not-shifted special key stops selection.
+--- Special keys in this context are the cursor keys, <End>, <Home>,
+--- <PageUp> and <PageDown>.
+---
+--- @type string
+vim.o.keymodel = ""
+vim.o.km = vim.o.keymodel
+vim.go.keymodel = vim.o.keymodel
+vim.go.km = vim.go.keymodel
+
+--- Program to use for the `K` command. Environment variables are
+--- expanded `:set_env`. ":help" may be used to access the Vim internal
+--- help. (Note that previously setting the global option to the empty
+--- value did this, which is now deprecated.)
+--- When the first character is ":", the command is invoked as a Vim
+--- Ex command prefixed with [count].
+--- When "man" or "man -s" is used, Vim will automatically translate
+--- a [count] for the "K" command to a section number.
+--- See `option-backslash` about including spaces and backslashes.
+--- Example:
+--- ```
+--- :set keywordprg=man\ -s
+--- :set keywordprg=:Man
+--- ```
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.keywordprg = ":Man"
+vim.o.kp = vim.o.keywordprg
+vim.bo.keywordprg = vim.o.keywordprg
+vim.bo.kp = vim.bo.keywordprg
+vim.go.keywordprg = vim.o.keywordprg
+vim.go.kp = vim.go.keywordprg
+
+--- This option allows switching your keyboard into a special language
+--- mode. When you are typing text in Insert mode the characters are
+--- inserted directly. When in Normal mode the 'langmap' option takes
+--- care of translating these special characters to the original meaning
+--- of the key. This means you don't have to change the keyboard mode to
+--- be able to execute Normal mode commands.
+--- This is the opposite of the 'keymap' option, where characters are
+--- mapped in Insert mode.
+--- Also consider setting 'langremap' to off, to prevent 'langmap' from
+--- applying to characters resulting from a mapping.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- Example (for Greek, in UTF-8): *greek*
+--- ```
+--- :set langmap=ΑA,ΒB,ΨC,ΔD,ΕE,ΦF,ΓG,ΗH,ΙI,ΞJ,ΚK,ΛL,ΜM,ΝN,ΟO,ΠP,QQ,ΡR,ΣS,ΤT,ΘU,ΩV,WW,ΧX,ΥY,ΖZ,αa,βb,ψc,δd,εe,φf,γg,ηh,ιi,ξj,κk,λl,μm,νn,οo,πp,qq,ρr,σs,τt,θu,ωv,ςw,χx,υy,ζz
+--- ```
+--- Example (exchanges meaning of z and y for commands):
+--- ```
+--- :set langmap=zy,yz,ZY,YZ
+--- ```
+---
+--- The 'langmap' option is a list of parts, separated with commas. Each
+--- part can be in one of two forms:
+--- 1. A list of pairs. Each pair is a "from" character immediately
+--- followed by the "to" character. Examples: "aA", "aAbBcC".
+--- 2. A list of "from" characters, a semi-colon and a list of "to"
+--- characters. Example: "abc;ABC"
+--- Example: "aA,fgh;FGH,cCdDeE"
+--- Special characters need to be preceded with a backslash. These are
+--- ";", ',', '"', '|' and backslash itself.
+---
+--- This will allow you to activate vim actions without having to switch
+--- back and forth between the languages. Your language characters will
+--- be understood as normal vim English characters (according to the
+--- langmap mappings) in the following cases:
+--- o Normal/Visual mode (commands, buffer/register names, user mappings)
+--- o Insert/Replace Mode: Register names after CTRL-R
+--- o Insert/Replace Mode: Mappings
+--- Characters entered in Command-line mode will NOT be affected by
+--- this option. Note that this option can be changed at any time
+--- allowing to switch between mappings for different languages/encodings.
+--- Use a mapping to avoid having to type it each time!
+---
+--- @type string
+vim.o.langmap = ""
+vim.o.lmap = vim.o.langmap
+vim.go.langmap = vim.o.langmap
+vim.go.lmap = vim.go.langmap
+
+--- Language to use for menu translation. Tells which file is loaded
+--- from the "lang" directory in 'runtimepath':
+--- ```
+--- "lang/menu_" .. &langmenu .. ".vim"
+--- ```
+--- (without the spaces). For example, to always use the Dutch menus, no
+--- matter what $LANG is set to:
+--- ```
+--- :set langmenu=nl_NL.ISO_8859-1
+--- ```
+--- When 'langmenu' is empty, `v:lang` is used.
+--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+--- If your $LANG is set to a non-English language but you do want to use
+--- the English menus:
+--- ```
+--- :set langmenu=none
+--- ```
+--- This option must be set before loading menus, switching on filetype
+--- detection or syntax highlighting. Once the menus are defined setting
+--- this option has no effect. But you could do this:
+--- ```
+--- :source $VIMRUNTIME/delmenu.vim
+--- :set langmenu=de_DE.ISO_8859-1
+--- :source $VIMRUNTIME/menu.vim
+--- ```
+--- Warning: This deletes all menus that you defined yourself!
+---
+--- @type string
+vim.o.langmenu = ""
+vim.o.lm = vim.o.langmenu
+vim.go.langmenu = vim.o.langmenu
+vim.go.lm = vim.go.langmenu
+
+--- When off, setting 'langmap' does not apply to characters resulting from
+--- a mapping. If setting 'langmap' disables some of your mappings, make
+--- sure this option is off.
+---
+--- @type boolean
+vim.o.langremap = false
+vim.o.lrm = vim.o.langremap
+vim.go.langremap = vim.o.langremap
+vim.go.lrm = vim.go.langremap
+
+--- The value of this option influences when the last window will have a
+--- status line:
+--- 0: never
+--- 1: only if there are at least two windows
+--- 2: always
+--- 3: always and ONLY the last window
+--- The screen looks nicer with a status line if you have several
+--- windows, but it takes another screen line. `status-line`
+---
+--- @type integer
+vim.o.laststatus = 2
+vim.o.ls = vim.o.laststatus
+vim.go.laststatus = vim.o.laststatus
+vim.go.ls = vim.go.laststatus
+
+--- When this option is set, the screen will not be redrawn while
+--- executing macros, registers and other commands that have not been
+--- typed. Also, updating the window title is postponed. To force an
+--- update use `:redraw`.
+--- This may occasionally cause display errors. It is only meant to be set
+--- temporarily when performing an operation where redrawing may cause
+--- flickering or cause a slow down.
+---
+--- @type boolean
+vim.o.lazyredraw = false
+vim.o.lz = vim.o.lazyredraw
+vim.go.lazyredraw = vim.o.lazyredraw
+vim.go.lz = vim.go.lazyredraw
+
+--- If on, Vim will wrap long lines at a character in 'breakat' rather
+--- than at the last character that fits on the screen. Unlike
+--- 'wrapmargin' and 'textwidth', this does not insert <EOL>s in the file,
+--- it only affects the way the file is displayed, not its contents.
+--- If 'breakindent' is set, line is visually indented. Then, the value
+--- of 'showbreak' is used to put in front of wrapped lines. This option
+--- is not used when the 'wrap' option is off.
+--- Note that <Tab> characters after an <EOL> are mostly not displayed
+--- with the right amount of white space.
+---
+--- @type boolean
+vim.o.linebreak = false
+vim.o.lbr = vim.o.linebreak
+vim.wo.linebreak = vim.o.linebreak
+vim.wo.lbr = vim.wo.linebreak
+
+--- Number of lines of the Vim window.
+--- Normally you don't need to set this. It is done automatically by the
+--- terminal initialization code.
+--- When Vim is running in the GUI or in a resizable window, setting this
+--- option will cause the window size to be changed. When you only want
+--- to use the size for the GUI, put the command in your `gvimrc` file.
+--- Vim limits the number of lines to what fits on the screen. You can
+--- use this command to get the tallest window possible:
+--- ```
+--- :set lines=999
+--- ```
+--- Minimum value is 2, maximum value is 1000.
+---
+--- @type integer
+vim.o.lines = 24
+vim.go.lines = vim.o.lines
+
+--- only in the GUI
+--- Number of pixel lines inserted between characters. Useful if the font
+--- uses the full character cell height, making lines touch each other.
+--- When non-zero there is room for underlining.
+--- With some fonts there can be too much room between lines (to have
+--- space for ascents and descents). Then it makes sense to set
+--- 'linespace' to a negative value. This may cause display problems
+--- though!
+---
+--- @type integer
+vim.o.linespace = 0
+vim.o.lsp = vim.o.linespace
+vim.go.linespace = vim.o.linespace
+vim.go.lsp = vim.go.linespace
+
+--- Lisp mode: When <Enter> is typed in insert mode set the indent for
+--- the next line to Lisp standards (well, sort of). Also happens with
+--- "cc" or "S". 'autoindent' must also be on for this to work. The 'p'
+--- flag in 'cpoptions' changes the method of indenting: Vi compatible or
+--- better. Also see 'lispwords'.
+--- The '-' character is included in keyword characters. Redefines the
+--- "=" operator to use this same indentation algorithm rather than
+--- calling an external program if 'equalprg' is empty.
+---
+--- @type boolean
+vim.o.lisp = false
+vim.bo.lisp = vim.o.lisp
+
+--- Comma-separated list of items that influence the Lisp indenting when
+--- enabled with the `'lisp'` option. Currently only one item is
+--- supported:
+--- expr:1 use 'indentexpr' for Lisp indenting when it is set
+--- expr:0 do not use 'indentexpr' for Lisp indenting (default)
+--- Note that when using 'indentexpr' the `=` operator indents all the
+--- lines, otherwise the first line is not indented (Vi-compatible).
+---
+--- @type string
+vim.o.lispoptions = ""
+vim.o.lop = vim.o.lispoptions
+vim.bo.lispoptions = vim.o.lispoptions
+vim.bo.lop = vim.bo.lispoptions
+
+--- Comma-separated list of words that influence the Lisp indenting when
+--- enabled with the `'lisp'` option.
+---
+--- @type string
+vim.o.lispwords = "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object"
+vim.o.lw = vim.o.lispwords
+vim.bo.lispwords = vim.o.lispwords
+vim.bo.lw = vim.bo.lispwords
+vim.go.lispwords = vim.o.lispwords
+vim.go.lw = vim.go.lispwords
+
+--- List mode: By default, show tabs as ">", trailing spaces as "-", and
+--- non-breakable space characters as "+". Useful to see the difference
+--- between tabs and spaces and for trailing blanks. Further changed by
+--- the 'listchars' option.
+---
+--- The cursor is displayed at the start of the space a Tab character
+--- occupies, not at the end as usual in Normal mode. To get this cursor
+--- position while displaying Tabs with spaces, use:
+--- ```
+--- :set list lcs=tab:\ \
+--- ```
+---
+--- Note that list mode will also affect formatting (set with 'textwidth'
+--- or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
+--- changing the way tabs are displayed.
+---
+--- @type boolean
+vim.o.list = false
+vim.wo.list = vim.o.list
+
+--- Strings to use in 'list' mode and for the `:list` command. It is a
+--- comma-separated list of string settings.
+---
+--- *lcs-eol*
+--- eol:c Character to show at the end of each line. When
+--- omitted, there is no extra character at the end of the
+--- line.
+--- *lcs-tab*
+--- tab:xy[z] Two or three characters to be used to show a tab.
+--- The third character is optional.
+---
+--- tab:xy The 'x' is always used, then 'y' as many times as will
+--- fit. Thus "tab:>-" displays:
+--- ```
+---
+--- ```
+--- >-
+--- >--
+--- etc.
+--- ```
+---
+--- tab:xyz The 'z' is always used, then 'x' is prepended, and
+--- then 'y' is used as many times as will fit. Thus
+--- "tab:<->" displays:
+--- ```
+---
+--- ```
+--- <>
+--- <->
+--- <-->
+--- etc.
+--- ```
+---
+--- When "tab:" is omitted, a tab is shown as ^I.
+--- *lcs-space*
+--- space:c Character to show for a space. When omitted, spaces
+--- are left blank.
+--- *lcs-multispace*
+--- multispace:c...
+--- One or more characters to use cyclically to show for
+--- multiple consecutive spaces. Overrides the "space"
+--- setting, except for single spaces. When omitted, the
+--- "space" setting is used. For example,
+--- `:set listchars=multispace:---+` shows ten consecutive
+--- spaces as:
+--- ```
+--- ---+---+--
+--- ```
+---
+--- *lcs-lead*
+--- lead:c Character to show for leading spaces. When omitted,
+--- leading spaces are blank. Overrides the "space" and
+--- "multispace" settings for leading spaces. You can
+--- combine it with "tab:", for example:
+--- ```
+--- :set listchars+=tab:>-,lead:.
+--- ```
+---
+--- *lcs-leadmultispace*
+--- leadmultispace:c...
+--- Like the `lcs-multispace` value, but for leading
+--- spaces only. Also overrides `lcs-lead` for leading
+--- multiple spaces.
+--- `:set listchars=leadmultispace:---+` shows ten
+--- consecutive leading spaces as:
+--- ```
+--- ---+---+--XXX
+--- ```
+---
+--- Where "XXX" denotes the first non-blank characters in
+--- the line.
+--- *lcs-trail*
+--- trail:c Character to show for trailing spaces. When omitted,
+--- trailing spaces are blank. Overrides the "space" and
+--- "multispace" settings for trailing spaces.
+--- *lcs-extends*
+--- extends:c Character to show in the last column, when 'wrap' is
+--- off and the line continues beyond the right of the
+--- screen.
+--- *lcs-precedes*
+--- precedes:c Character to show in the first visible column of the
+--- physical line, when there is text preceding the
+--- character visible in the first column.
+--- *lcs-conceal*
+--- conceal:c Character to show in place of concealed text, when
+--- 'conceallevel' is set to 1. A space when omitted.
+--- *lcs-nbsp*
+--- nbsp:c Character to show for a non-breakable space character
+--- (0xA0 (160 decimal) and U+202F). Left blank when
+--- omitted.
+---
+--- The characters ':' and ',' should not be used. UTF-8 characters can
+--- be used. All characters must be single width.
+---
+--- Each character can be specified as hex:
+--- ```
+--- set listchars=eol:\\x24
+--- set listchars=eol:\\u21b5
+--- set listchars=eol:\\U000021b5
+--- ```
+--- Note that a double backslash is used. The number of hex characters
+--- must be exactly 2 for \\x, 4 for \\u and 8 for \\U.
+---
+--- Examples:
+--- ```
+--- :set lcs=tab:>-,trail:-
+--- :set lcs=tab:>-,eol:<,nbsp:%
+--- :set lcs=extends:>,precedes:<
+--- ```
+--- `hl-NonText` highlighting will be used for "eol", "extends" and
+--- "precedes". `hl-Whitespace` for "nbsp", "space", "tab", "multispace",
+--- "lead" and "trail".
+---
+--- @type string
+vim.o.listchars = "tab:> ,trail:-,nbsp:+"
+vim.o.lcs = vim.o.listchars
+vim.wo.listchars = vim.o.listchars
+vim.wo.lcs = vim.wo.listchars
+vim.go.listchars = vim.o.listchars
+vim.go.lcs = vim.go.listchars
+
+--- When on the plugin scripts are loaded when starting up `load-plugins`.
+--- This option can be reset in your `vimrc` file to disable the loading
+--- of plugins.
+--- Note that using the "-u NONE" and "--noplugin" command line arguments
+--- reset this option. `-u` `--noplugin`
+---
+--- @type boolean
+vim.o.loadplugins = true
+vim.o.lpl = vim.o.loadplugins
+vim.go.loadplugins = vim.o.loadplugins
+vim.go.lpl = vim.go.loadplugins
+
+--- Changes the special characters that can be used in search patterns.
+--- See `pattern`.
+--- WARNING: Switching this option off most likely breaks plugins! That
+--- is because many patterns assume it's on and will fail when it's off.
+--- Only switch it off when working with old Vi scripts. In any other
+--- situation write patterns that work when 'magic' is on. Include "\M"
+--- when you want to `/\M`.
+---
+--- @type boolean
+vim.o.magic = true
+vim.go.magic = vim.o.magic
+
+--- Name of the errorfile for the `:make` command (see `:make_makeprg`)
+--- and the `:grep` command.
+--- When it is empty, an internally generated temp file will be used.
+--- When "##" is included, it is replaced by a number to make the name
+--- unique. This makes sure that the ":make" command doesn't overwrite an
+--- existing file.
+--- NOT used for the ":cf" command. See 'errorfile' for that.
+--- Environment variables are expanded `:set_env`.
+--- See `option-backslash` about including spaces and backslashes.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.makeef = ""
+vim.o.mef = vim.o.makeef
+vim.go.makeef = vim.o.makeef
+vim.go.mef = vim.go.makeef
+
+--- Encoding used for reading the output of external commands. When empty,
+--- encoding is not converted.
+--- This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`,
+--- `:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`,
+--- and `:laddfile`.
+---
+--- This would be mostly useful when you use MS-Windows. If iconv is
+--- enabled, setting 'makeencoding' to "char" has the same effect as
+--- setting to the system locale encoding. Example:
+--- ```
+--- :set makeencoding=char " system locale is used
+--- ```
+---
+---
+--- @type string
+vim.o.makeencoding = ""
+vim.o.menc = vim.o.makeencoding
+vim.bo.makeencoding = vim.o.makeencoding
+vim.bo.menc = vim.bo.makeencoding
+vim.go.makeencoding = vim.o.makeencoding
+vim.go.menc = vim.go.makeencoding
+
+--- Program to use for the ":make" command. See `:make_makeprg`.
+--- This option may contain '%' and '#' characters (see `:_%` and `:_#`),
+--- which are expanded to the current and alternate file name. Use `::S`
+--- to escape file names in case they contain special characters.
+--- Environment variables are expanded `:set_env`. See `option-backslash`
+--- about including spaces and backslashes.
+--- Note that a '|' must be escaped twice: once for ":set" and once for
+--- the interpretation of a command. When you use a filter called
+--- "myfilter" do it like this:
+--- ```
+--- :set makeprg=gmake\ \\\|\ myfilter
+--- ```
+--- The placeholder "$*" can be given (even multiple times) to specify
+--- where the arguments will be included, for example:
+--- ```
+--- :set makeprg=latex\ \\\\nonstopmode\ \\\\input\\{$*}
+--- ```
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.makeprg = "make"
+vim.o.mp = vim.o.makeprg
+vim.bo.makeprg = vim.o.makeprg
+vim.bo.mp = vim.bo.makeprg
+vim.go.makeprg = vim.o.makeprg
+vim.go.mp = vim.go.makeprg
+
+--- Characters that form pairs. The `%` command jumps from one to the
+--- other.
+--- Only character pairs are allowed that are different, thus you cannot
+--- jump between two double quotes.
+--- The characters must be separated by a colon.
+--- The pairs must be separated by a comma. Example for including '<' and
+--- '>' (for HTML):
+--- ```
+--- :set mps+=<:>
+--- ```
+--- A more exotic example, to jump between the '=' and ';' in an
+--- assignment, useful for languages like C and Java:
+--- ```
+--- :au FileType c,cpp,java set mps+==:;
+--- ```
+--- For a more advanced way of using "%", see the matchit.vim plugin in
+--- the $VIMRUNTIME/plugin directory. `add-local-help`
+---
+--- @type string
+vim.o.matchpairs = "(:),{:},[:]"
+vim.o.mps = vim.o.matchpairs
+vim.bo.matchpairs = vim.o.matchpairs
+vim.bo.mps = vim.bo.matchpairs
+
+--- Tenths of a second to show the matching paren, when 'showmatch' is
+--- set. Note that this is not in milliseconds, like other options that
+--- set a time. This is to be compatible with Nvi.
+---
+--- @type integer
+vim.o.matchtime = 5
+vim.o.mat = vim.o.matchtime
+vim.go.matchtime = vim.o.matchtime
+vim.go.mat = vim.go.matchtime
+
+--- Maximum depth of function calls for user functions. This normally
+--- catches endless recursion. When using a recursive function with
+--- more depth, set 'maxfuncdepth' to a bigger number. But this will use
+--- more memory, there is the danger of failing when memory is exhausted.
+--- Increasing this limit above 200 also changes the maximum for Ex
+--- command recursion, see `E169`.
+--- See also `:function`.
+---
+--- @type integer
+vim.o.maxfuncdepth = 100
+vim.o.mfd = vim.o.maxfuncdepth
+vim.go.maxfuncdepth = vim.o.maxfuncdepth
+vim.go.mfd = vim.go.maxfuncdepth
+
+--- Maximum number of times a mapping is done without resulting in a
+--- character to be used. This normally catches endless mappings, like
+--- ":map x y" with ":map y x". It still does not catch ":map g wg",
+--- because the 'w' is used before the next mapping is done. See also
+--- `key-mapping`.
+---
+--- @type integer
+vim.o.maxmapdepth = 1000
+vim.o.mmd = vim.o.maxmapdepth
+vim.go.maxmapdepth = vim.o.maxmapdepth
+vim.go.mmd = vim.go.maxmapdepth
+
+--- Maximum amount of memory (in Kbyte) to use for pattern matching.
+--- The maximum value is about 2000000. Use this to work without a limit.
+--- *E363*
+--- When Vim runs into the limit it gives an error message and mostly
+--- behaves like CTRL-C was typed.
+--- Running into the limit often means that the pattern is very
+--- inefficient or too complex. This may already happen with the pattern
+--- "\(.\)*" on a very long line. ".*" works much better.
+--- Might also happen on redraw, when syntax rules try to match a complex
+--- text structure.
+--- Vim may run out of memory before hitting the 'maxmempattern' limit, in
+--- which case you get an "Out of memory" error instead.
+---
+--- @type integer
+vim.o.maxmempattern = 1000
+vim.o.mmp = vim.o.maxmempattern
+vim.go.maxmempattern = vim.o.maxmempattern
+vim.go.mmp = vim.go.maxmempattern
+
+--- Maximum number of items to use in a menu. Used for menus that are
+--- generated from a list of items, e.g., the Buffers menu. Changing this
+--- option has no direct effect, the menu must be refreshed first.
+---
+--- @type integer
+vim.o.menuitems = 25
+vim.o.mis = vim.o.menuitems
+vim.go.menuitems = vim.o.menuitems
+vim.go.mis = vim.go.menuitems
+
+--- Parameters for `:mkspell`. This tunes when to start compressing the
+--- word tree. Compression can be slow when there are many words, but
+--- it's needed to avoid running out of memory. The amount of memory used
+--- per word depends very much on how similar the words are, that's why
+--- this tuning is complicated.
+---
+--- There are three numbers, separated by commas:
+--- ```
+--- {start},{inc},{added}
+--- ```
+---
+--- For most languages the uncompressed word tree fits in memory. {start}
+--- gives the amount of memory in Kbyte that can be used before any
+--- compression is done. It should be a bit smaller than the amount of
+--- memory that is available to Vim.
+---
+--- When going over the {start} limit the {inc} number specifies the
+--- amount of memory in Kbyte that can be allocated before another
+--- compression is done. A low number means compression is done after
+--- less words are added, which is slow. A high number means more memory
+--- will be allocated.
+---
+--- After doing compression, {added} times 1024 words can be added before
+--- the {inc} limit is ignored and compression is done when any extra
+--- amount of memory is needed. A low number means there is a smaller
+--- chance of hitting the {inc} limit, less memory is used but it's
+--- slower.
+---
+--- The languages for which these numbers are important are Italian and
+--- Hungarian. The default works for when you have about 512 Mbyte. If
+--- you have 1 Gbyte you could use:
+--- ```
+--- :set mkspellmem=900000,3000,800
+--- ```
+--- If you have less than 512 Mbyte `:mkspell` may fail for some
+--- languages, no matter what you set 'mkspellmem' to.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.mkspellmem = "460000,2000,500"
+vim.o.msm = vim.o.mkspellmem
+vim.go.mkspellmem = vim.o.mkspellmem
+vim.go.msm = vim.go.mkspellmem
+
+--- If 'modeline' is on 'modelines' gives the number of lines that is
+--- checked for set commands. If 'modeline' is off or 'modelines' is zero
+--- no lines are checked. See `modeline`.
+---
+--- @type boolean
+vim.o.modeline = true
+vim.o.ml = vim.o.modeline
+vim.bo.modeline = vim.o.modeline
+vim.bo.ml = vim.bo.modeline
+
+--- When on allow some options that are an expression to be set in the
+--- modeline. Check the option for whether it is affected by
+--- 'modelineexpr'. Also see `modeline`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type boolean
+vim.o.modelineexpr = false
+vim.o.mle = vim.o.modelineexpr
+vim.go.modelineexpr = vim.o.modelineexpr
+vim.go.mle = vim.go.modelineexpr
+
+--- If 'modeline' is on 'modelines' gives the number of lines that is
+--- checked for set commands. If 'modeline' is off or 'modelines' is zero
+--- no lines are checked. See `modeline`.
+---
+---
+--- @type integer
+vim.o.modelines = 5
+vim.o.mls = vim.o.modelines
+vim.go.modelines = vim.o.modelines
+vim.go.mls = vim.go.modelines
+
+--- When off the buffer contents cannot be changed. The 'fileformat' and
+--- 'fileencoding' options also can't be changed.
+--- Can be reset on startup with the `-M` command line argument.
+---
+--- @type boolean
+vim.o.modifiable = true
+vim.o.ma = vim.o.modifiable
+vim.bo.modifiable = vim.o.modifiable
+vim.bo.ma = vim.bo.modifiable
+
+--- When on, the buffer is considered to be modified. This option is set
+--- when:
+--- 1. A change was made to the text since it was last written. Using the
+--- `undo` command to go back to the original text will reset the
+--- option. But undoing changes that were made before writing the
+--- buffer will set the option again, since the text is different from
+--- when it was written.
+--- 2. 'fileformat' or 'fileencoding' is different from its original
+--- value. The original value is set when the buffer is read or
+--- written. A ":set nomodified" command also resets the original
+--- values to the current values and the 'modified' option will be
+--- reset.
+--- Similarly for 'eol' and 'bomb'.
+--- This option is not set when a change is made to the buffer as the
+--- result of a BufNewFile, BufRead/BufReadPost, BufWritePost,
+--- FileAppendPost or VimLeave autocommand event. See `gzip-example` for
+--- an explanation.
+--- When 'buftype' is "nowrite" or "nofile" this option may be set, but
+--- will be ignored.
+--- Note that the text may actually be the same, e.g. 'modified' is set
+--- when using "rA" on an "A".
+---
+--- @type boolean
+vim.o.modified = false
+vim.o.mod = vim.o.modified
+vim.bo.modified = vim.o.modified
+vim.bo.mod = vim.bo.modified
+
+--- When on, listings pause when the whole screen is filled. You will get
+--- the `more-prompt`. When this option is off there are no pauses, the
+--- listing continues until finished.
+---
+--- @type boolean
+vim.o.more = true
+vim.go.more = vim.o.more
+
+--- Enables mouse support. For example, to enable the mouse in Normal mode
+--- and Visual mode:
+--- ```
+--- :set mouse=nv
+--- ```
+---
+--- To temporarily disable mouse support, hold the shift key while using
+--- the mouse.
+---
+--- Mouse support can be enabled for different modes:
+--- n Normal mode
+--- v Visual mode
+--- i Insert mode
+--- c Command-line mode
+--- h all previous modes when editing a help file
+--- a all previous modes
+--- r for `hit-enter` and `more-prompt` prompt
+---
+--- Left-click anywhere in a text buffer to place the cursor there. This
+--- works with operators too, e.g. type `d` then left-click to delete text
+--- from the current cursor position to the position where you clicked.
+---
+--- Drag the `status-line` or vertical separator of a window to resize it.
+---
+--- If enabled for "v" (Visual mode) then double-click selects word-wise,
+--- triple-click makes it line-wise, and quadruple-click makes it
+--- rectangular block-wise.
+---
+--- For scrolling with a mouse wheel see `scroll-mouse-wheel`.
+---
+--- Note: When enabling the mouse in a terminal, copy/paste will use the
+--- "* register if possible. See also 'clipboard'.
+---
+--- Related options:
+--- 'mousefocus' window focus follows mouse pointer
+--- 'mousemodel' what mouse button does which action
+--- 'mousehide' hide mouse pointer while typing text
+--- 'selectmode' whether to start Select mode or Visual mode
+---
+--- @type string
+vim.o.mouse = "nvi"
+vim.go.mouse = vim.o.mouse
+
+--- The window that the mouse pointer is on is automatically activated.
+--- When changing the window layout or window focus in another way, the
+--- mouse pointer is moved to the window with keyboard focus. Off is the
+--- default because it makes using the pull down menus a little goofy, as
+--- a pointer transit may activate a window unintentionally.
+---
+--- @type boolean
+vim.o.mousefocus = false
+vim.o.mousef = vim.o.mousefocus
+vim.go.mousefocus = vim.o.mousefocus
+vim.go.mousef = vim.go.mousefocus
+
+--- only in the GUI
+--- When on, the mouse pointer is hidden when characters are typed.
+--- The mouse pointer is restored when the mouse is moved.
+---
+--- @type boolean
+vim.o.mousehide = true
+vim.o.mh = vim.o.mousehide
+vim.go.mousehide = vim.o.mousehide
+vim.go.mh = vim.go.mousehide
+
+--- Sets the model to use for the mouse. The name mostly specifies what
+--- the right mouse button is used for:
+--- extend Right mouse button extends a selection. This works
+--- like in an xterm.
+--- popup Right mouse button pops up a menu. The shifted left
+--- mouse button extends a selection. This works like
+--- with Microsoft Windows.
+--- popup_setpos Like "popup", but the cursor will be moved to the
+--- position where the mouse was clicked, and thus the
+--- selected operation will act upon the clicked object.
+--- If clicking inside a selection, that selection will
+--- be acted upon, i.e. no cursor move. This implies of
+--- course, that right clicking outside a selection will
+--- end Visual mode.
+--- Overview of what button does what for each model:
+--- mouse extend popup(_setpos) ~
+--- left click place cursor place cursor
+--- left drag start selection start selection
+--- shift-left search word extend selection
+--- right click extend selection popup menu (place cursor)
+--- right drag extend selection -
+--- middle click paste paste
+---
+--- In the "popup" model the right mouse button produces a pop-up menu.
+--- Nvim creates a default `popup-menu` but you can redefine it.
+---
+--- Note that you can further refine the meaning of buttons with mappings.
+--- See `mouse-overview`. But mappings are NOT used for modeless selection.
+---
+--- Example:
+--- ```
+--- :map <S-LeftMouse> <RightMouse>
+--- :map <S-LeftDrag> <RightDrag>
+--- :map <S-LeftRelease> <RightRelease>
+--- :map <2-S-LeftMouse> <2-RightMouse>
+--- :map <2-S-LeftDrag> <2-RightDrag>
+--- :map <2-S-LeftRelease> <2-RightRelease>
+--- :map <3-S-LeftMouse> <3-RightMouse>
+--- :map <3-S-LeftDrag> <3-RightDrag>
+--- :map <3-S-LeftRelease> <3-RightRelease>
+--- :map <4-S-LeftMouse> <4-RightMouse>
+--- :map <4-S-LeftDrag> <4-RightDrag>
+--- :map <4-S-LeftRelease> <4-RightRelease>
+--- ```
+---
+--- Mouse commands requiring the CTRL modifier can be simulated by typing
+--- the "g" key before using the mouse:
+--- "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click)
+--- "g<RightMouse>" is "<C-RightMouse> ("CTRL-T")
+---
+--- @type string
+vim.o.mousemodel = "popup_setpos"
+vim.o.mousem = vim.o.mousemodel
+vim.go.mousemodel = vim.o.mousemodel
+vim.go.mousem = vim.go.mousemodel
+
+--- When on, mouse move events are delivered to the input queue and are
+--- available for mapping. The default, off, avoids the mouse movement
+--- overhead except when needed.
+--- Warning: Setting this option can make pending mappings to be aborted
+--- when the mouse is moved.
+---
+--- @type boolean
+vim.o.mousemoveevent = false
+vim.o.mousemev = vim.o.mousemoveevent
+vim.go.mousemoveevent = vim.o.mousemoveevent
+vim.go.mousemev = vim.go.mousemoveevent
+
+--- This option controls the number of lines / columns to scroll by when
+--- scrolling with a mouse wheel (`scroll-mouse-wheel`). The option is
+--- a comma-separated list. Each part consists of a direction and a count
+--- as follows:
+--- direction:count,direction:count
+--- Direction is one of either "hor" or "ver". "hor" controls horizontal
+--- scrolling and "ver" controls vertical scrolling. Count sets the amount
+--- to scroll by for the given direction, it should be a non negative
+--- integer. Each direction should be set at most once. If a direction
+--- is omitted, a default value is used (6 for horizontal scrolling and 3
+--- for vertical scrolling). You can disable mouse scrolling by using
+--- a count of 0.
+---
+--- Example:
+--- ```
+--- :set mousescroll=ver:5,hor:2
+--- ```
+--- Will make Nvim scroll 5 lines at a time when scrolling vertically, and
+--- scroll 2 columns at a time when scrolling horizontally.
+---
+--- @type string
+vim.o.mousescroll = "ver:3,hor:6"
+vim.go.mousescroll = vim.o.mousescroll
+
+--- This option tells Vim what the mouse pointer should look like in
+--- different modes. The option is a comma-separated list of parts, much
+--- like used for 'guicursor'. Each part consist of a mode/location-list
+--- and an argument-list:
+--- mode-list:shape,mode-list:shape,..
+--- The mode-list is a dash separated list of these modes/locations:
+--- In a normal window: ~
+--- n Normal mode
+--- v Visual mode
+--- ve Visual mode with 'selection' "exclusive" (same as 'v',
+--- if not specified)
+--- o Operator-pending mode
+--- i Insert mode
+--- r Replace mode
+---
+--- Others: ~
+--- c appending to the command-line
+--- ci inserting in the command-line
+--- cr replacing in the command-line
+--- m at the 'Hit ENTER' or 'More' prompts
+--- ml idem, but cursor in the last line
+--- e any mode, pointer below last window
+--- s any mode, pointer on a status line
+--- sd any mode, while dragging a status line
+--- vs any mode, pointer on a vertical separator line
+--- vd any mode, while dragging a vertical separator line
+--- a everywhere
+---
+--- The shape is one of the following:
+--- avail name looks like ~
+--- w x arrow Normal mouse pointer
+--- w x blank no pointer at all (use with care!)
+--- w x beam I-beam
+--- w x updown up-down sizing arrows
+--- w x leftright left-right sizing arrows
+--- w x busy The system's usual busy pointer
+--- w x no The system's usual "no input" pointer
+--- x udsizing indicates up-down resizing
+--- x lrsizing indicates left-right resizing
+--- x crosshair like a big thin +
+--- x hand1 black hand
+--- x hand2 white hand
+--- x pencil what you write with
+--- x question big ?
+--- x rightup-arrow arrow pointing right-up
+--- w x up-arrow arrow pointing up
+--- x <number> any X11 pointer number (see X11/cursorfont.h)
+---
+--- The "avail" column contains a 'w' if the shape is available for Win32,
+--- x for X11.
+--- Any modes not specified or shapes not available use the normal mouse
+--- pointer.
+---
+--- Example:
+--- ```
+--- :set mouseshape=s:udsizing,m:no
+--- ```
+--- will make the mouse turn to a sizing arrow over the status lines and
+--- indicate no input when the hit-enter prompt is displayed (since
+--- clicking the mouse has no effect in this state.)
+---
+--- @type string
+vim.o.mouseshape = ""
+vim.o.mouses = vim.o.mouseshape
+vim.go.mouseshape = vim.o.mouseshape
+vim.go.mouses = vim.go.mouseshape
+
+--- Defines the maximum time in msec between two mouse clicks for the
+--- second click to be recognized as a multi click.
+---
+--- @type integer
+vim.o.mousetime = 500
+vim.o.mouset = vim.o.mousetime
+vim.go.mousetime = vim.o.mousetime
+vim.go.mouset = vim.go.mousetime
+
+--- This defines what bases Vim will consider for numbers when using the
+--- CTRL-A and CTRL-X commands for adding to and subtracting from a number
+--- respectively; see `CTRL-A` for more info on these commands.
+--- alpha If included, single alphabetical characters will be
+--- incremented or decremented. This is useful for a list with a
+--- letter index a), b), etc. *octal-nrformats*
+--- octal If included, numbers that start with a zero will be considered
+--- to be octal. Example: Using CTRL-A on "007" results in "010".
+--- hex If included, numbers starting with "0x" or "0X" will be
+--- considered to be hexadecimal. Example: Using CTRL-X on
+--- "0x100" results in "0x0ff".
+--- bin If included, numbers starting with "0b" or "0B" will be
+--- considered to be binary. Example: Using CTRL-X on
+--- "0b1000" subtracts one, resulting in "0b0111".
+--- unsigned If included, numbers are recognized as unsigned. Thus a
+--- leading dash or negative sign won't be considered as part of
+--- the number. Examples:
+--- Using CTRL-X on "2020" in "9-2020" results in "9-2019"
+--- (without "unsigned" it would become "9-2021").
+--- Using CTRL-A on "2020" in "9-2020" results in "9-2021"
+--- (without "unsigned" it would become "9-2019").
+--- Using CTRL-X on "0" or CTRL-A on "18446744073709551615"
+--- (2^64 - 1) has no effect, overflow is prevented.
+--- Numbers which simply begin with a digit in the range 1-9 are always
+--- considered decimal. This also happens for numbers that are not
+--- recognized as octal or hex.
+---
+--- @type string
+vim.o.nrformats = "bin,hex"
+vim.o.nf = vim.o.nrformats
+vim.bo.nrformats = vim.o.nrformats
+vim.bo.nf = vim.bo.nrformats
+
+--- Print the line number in front of each line. When the 'n' option is
+--- excluded from 'cpoptions' a wrapped line will not use the column of
+--- line numbers.
+--- Use the 'numberwidth' option to adjust the room for the line number.
+--- When a long, wrapped line doesn't start with the first character, '-'
+--- characters are put before the number.
+--- For highlighting see `hl-LineNr`, `hl-CursorLineNr`, and the
+--- `:sign-define` "numhl" argument.
+--- *number_relativenumber*
+--- The 'relativenumber' option changes the displayed number to be
+--- relative to the cursor. Together with 'number' there are these
+--- four combinations (cursor in line 3):
+---
+--- 'nonu' 'nu' 'nonu' 'nu'
+--- 'nornu' 'nornu' 'rnu' 'rnu'
+--- ```
+--- |apple | 1 apple | 2 apple | 2 apple
+--- |pear | 2 pear | 1 pear | 1 pear
+--- |nobody | 3 nobody | 0 nobody |3 nobody
+--- |there | 4 there | 1 there | 1 there
+--- ```
+---
+---
+--- @type boolean
+vim.o.number = false
+vim.o.nu = vim.o.number
+vim.wo.number = vim.o.number
+vim.wo.nu = vim.wo.number
+
+--- Minimal number of columns to use for the line number. Only relevant
+--- when the 'number' or 'relativenumber' option is set or printing lines
+--- with a line number. Since one space is always between the number and
+--- the text, there is one less character for the number itself.
+--- The value is the minimum width. A bigger width is used when needed to
+--- fit the highest line number in the buffer respectively the number of
+--- rows in the window, depending on whether 'number' or 'relativenumber'
+--- is set. Thus with the Vim default of 4 there is room for a line number
+--- up to 999. When the buffer has 1000 lines five columns will be used.
+--- The minimum value is 1, the maximum value is 20.
+---
+--- @type integer
+vim.o.numberwidth = 4
+vim.o.nuw = vim.o.numberwidth
+vim.wo.numberwidth = vim.o.numberwidth
+vim.wo.nuw = vim.wo.numberwidth
+
+--- This option specifies a function to be used for Insert mode omni
+--- completion with CTRL-X CTRL-O. `i_CTRL-X_CTRL-O`
+--- See `complete-functions` for an explanation of how the function is
+--- invoked and what it should return. The value can be the name of a
+--- function, a `lambda` or a `Funcref`. See `option-value-function` for
+--- more information.
+--- This option is usually set by a filetype plugin:
+--- `:filetype-plugin-on`
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.omnifunc = ""
+vim.o.ofu = vim.o.omnifunc
+vim.bo.omnifunc = vim.o.omnifunc
+vim.bo.ofu = vim.bo.omnifunc
+
+--- only for Windows
+--- Enable reading and writing from devices. This may get Vim stuck on a
+--- device that can be opened but doesn't actually do the I/O. Therefore
+--- it is off by default.
+--- Note that on Windows editing "aux.h", "lpt1.txt" and the like also
+--- result in editing a device.
+---
+--- @type boolean
+vim.o.opendevice = false
+vim.o.odev = vim.o.opendevice
+vim.go.opendevice = vim.o.opendevice
+vim.go.odev = vim.go.opendevice
+
+--- This option specifies a function to be called by the `g@` operator.
+--- See `:map-operator` for more info and an example. The value can be
+--- the name of a function, a `lambda` or a `Funcref`. See
+--- `option-value-function` for more information.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.operatorfunc = ""
+vim.o.opfunc = vim.o.operatorfunc
+vim.go.operatorfunc = vim.o.operatorfunc
+vim.go.opfunc = vim.go.operatorfunc
+
+--- Directories used to find packages.
+--- See `packages` and `packages-runtimepath`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.packpath = "..."
+vim.o.pp = vim.o.packpath
+vim.go.packpath = vim.o.packpath
+vim.go.pp = vim.go.packpath
+
+--- Specifies the nroff macros that separate paragraphs. These are pairs
+--- of two letters (see `object-motions`).
+---
+--- @type string
+vim.o.paragraphs = "IPLPPPQPP TPHPLIPpLpItpplpipbp"
+vim.o.para = vim.o.paragraphs
+vim.go.paragraphs = vim.o.paragraphs
+vim.go.para = vim.go.paragraphs
+
+--- Expression which is evaluated to apply a patch to a file and generate
+--- the resulting new version of the file. See `diff-patchexpr`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.patchexpr = ""
+vim.o.pex = vim.o.patchexpr
+vim.go.patchexpr = vim.o.patchexpr
+vim.go.pex = vim.go.patchexpr
+
+--- When non-empty the oldest version of a file is kept. This can be used
+--- to keep the original version of a file if you are changing files in a
+--- source distribution. Only the first time that a file is written a
+--- copy of the original file will be kept. The name of the copy is the
+--- name of the original file with the string in the 'patchmode' option
+--- appended. This option should start with a dot. Use a string like
+--- ".orig" or ".org". 'backupdir' must not be empty for this to work
+--- (Detail: The backup file is renamed to the patchmode file after the
+--- new file has been successfully written, that's why it must be possible
+--- to write a backup file). If there was no file to be backed up, an
+--- empty file is created.
+--- When the 'backupskip' pattern matches, a patchmode file is not made.
+--- Using 'patchmode' for compressed files appends the extension at the
+--- end (e.g., "file.gz.orig"), thus the resulting name isn't always
+--- recognized as a compressed file.
+--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+---
+--- @type string
+vim.o.patchmode = ""
+vim.o.pm = vim.o.patchmode
+vim.go.patchmode = vim.o.patchmode
+vim.go.pm = vim.go.patchmode
+
+--- This is a list of directories which will be searched when using the
+--- `gf`, [f, ]f, ^Wf, `:find`, `:sfind`, `:tabfind` and other commands,
+--- provided that the file being searched for has a relative path (not
+--- starting with "/", "./" or "../"). The directories in the 'path'
+--- option may be relative or absolute.
+--- - Use commas to separate directory names:
+--- ```
+--- :set path=.,/usr/local/include,/usr/include
+--- ```
+--- - Spaces can also be used to separate directory names. To have a
+--- space in a directory name, precede it with an extra backslash, and
+--- escape the space:
+--- ```
+--- :set path=.,/dir/with\\\ space
+--- ```
+--- - To include a comma in a directory name precede it with an extra
+--- backslash:
+--- ```
+--- :set path=.,/dir/with\\,comma
+--- ```
+--- - To search relative to the directory of the current file, use:
+--- ```
+--- :set path=.
+--- ```
+--- - To search in the current directory use an empty string between two
+--- commas:
+--- ```
+--- :set path=,,
+--- ```
+--- - A directory name may end in a ':' or '/'.
+--- - Environment variables are expanded `:set_env`.
+--- - When using `netrw.vim` URLs can be used. For example, adding
+--- "https://www.vim.org" will make ":find index.html" work.
+--- - Search upwards and downwards in a directory tree using "*", "**" and
+--- ";". See `file-searching` for info and syntax.
+--- - Careful with '\' characters, type two to get one in the option:
+--- ```
+--- :set path=.,c:\\include
+--- ```
+--- Or just use '/' instead:
+--- ```
+--- :set path=.,c:/include
+--- ```
+--- Don't forget "." or files won't even be found in the same directory as
+--- the file!
+--- The maximum length is limited. How much depends on the system, mostly
+--- it is something like 256 or 1024 characters.
+--- You can check if all the include files are found, using the value of
+--- 'path', see `:checkpath`.
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- directories from the list. This avoids problems when a future version
+--- uses another default. To remove the current directory use:
+--- ```
+--- :set path-=
+--- ```
+--- To add the current directory use:
+--- ```
+--- :set path+=
+--- ```
+--- To use an environment variable, you probably need to replace the
+--- separator. Here is an example to append $INCL, in which directory
+--- names are separated with a semi-colon:
+--- ```
+--- :let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
+--- ```
+--- Replace the ';' with a ':' or whatever separator is used. Note that
+--- this doesn't work when $INCL contains a comma or white space.
+---
+--- @type string
+vim.o.path = ".,,"
+vim.o.pa = vim.o.path
+vim.bo.path = vim.o.path
+vim.bo.pa = vim.bo.path
+vim.go.path = vim.o.path
+vim.go.pa = vim.go.path
+
+--- When changing the indent of the current line, preserve as much of the
+--- indent structure as possible. Normally the indent is replaced by a
+--- series of tabs followed by spaces as required (unless `'expandtab'` is
+--- enabled, in which case only spaces are used). Enabling this option
+--- means the indent will preserve as many existing characters as possible
+--- for indenting, and only add additional tabs or spaces as required.
+--- 'expandtab' does not apply to the preserved white space, a Tab remains
+--- a Tab.
+--- NOTE: When using ">>" multiple times the resulting indent is a mix of
+--- tabs and spaces. You might not like this.
+--- Also see 'copyindent'.
+--- Use `:retab` to clean up white space.
+---
+--- @type boolean
+vim.o.preserveindent = false
+vim.o.pi = vim.o.preserveindent
+vim.bo.preserveindent = vim.o.preserveindent
+vim.bo.pi = vim.bo.preserveindent
+
+--- Default height for a preview window. Used for `:ptag` and associated
+--- commands. Used for `CTRL-W_}` when no count is given.
+---
+--- @type integer
+vim.o.previewheight = 12
+vim.o.pvh = vim.o.previewheight
+vim.go.previewheight = vim.o.previewheight
+vim.go.pvh = vim.go.previewheight
+
+--- Identifies the preview window. Only one window can have this option
+--- set. It's normally not set directly, but by using one of the commands
+--- `:ptag`, `:pedit`, etc.
+---
+--- @type boolean
+vim.o.previewwindow = false
+vim.o.pvw = vim.o.previewwindow
+vim.wo.previewwindow = vim.o.previewwindow
+vim.wo.pvw = vim.wo.previewwindow
+
+--- Enables pseudo-transparency for the `popup-menu`. Valid values are in
+--- the range of 0 for fully opaque popupmenu (disabled) to 100 for fully
+--- transparent background. Values between 0-30 are typically most useful.
+---
+--- It is possible to override the level for individual highlights within
+--- the popupmenu using `highlight-blend`. For instance, to enable
+--- transparency but force the current selected element to be fully opaque:
+--- ```
+--- :set pumblend=15
+--- :hi PmenuSel blend=0
+--- ```
+---
+--- UI-dependent. Works best with RGB colors. 'termguicolors'
+---
+--- @type integer
+vim.o.pumblend = 0
+vim.o.pb = vim.o.pumblend
+vim.go.pumblend = vim.o.pumblend
+vim.go.pb = vim.go.pumblend
+
+--- Maximum number of items to show in the popup menu
+--- (`ins-completion-menu`). Zero means "use available screen space".
+---
+--- @type integer
+vim.o.pumheight = 0
+vim.o.ph = vim.o.pumheight
+vim.go.pumheight = vim.o.pumheight
+vim.go.ph = vim.go.pumheight
+
+--- Minimum width for the popup menu (`ins-completion-menu`). If the
+--- cursor column + 'pumwidth' exceeds screen width, the popup menu is
+--- nudged to fit on the screen.
+---
+--- @type integer
+vim.o.pumwidth = 15
+vim.o.pw = vim.o.pumwidth
+vim.go.pumwidth = vim.o.pumwidth
+vim.go.pw = vim.go.pumwidth
+
+--- Specifies the python version used for pyx* functions and commands
+--- `python_x`. As only Python 3 is supported, this always has the value
+--- `3`. Setting any other value is an error.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type integer
+vim.o.pyxversion = 3
+vim.o.pyx = vim.o.pyxversion
+vim.go.pyxversion = vim.o.pyxversion
+vim.go.pyx = vim.go.pyxversion
+
+--- This option specifies a function to be used to get the text to display
+--- in the quickfix and location list windows. This can be used to
+--- customize the information displayed in the quickfix or location window
+--- for each entry in the corresponding quickfix or location list. See
+--- `quickfix-window-function` for an explanation of how to write the
+--- function and an example. The value can be the name of a function, a
+--- `lambda` or a `Funcref`. See `option-value-function` for more
+--- information.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.quickfixtextfunc = ""
+vim.o.qftf = vim.o.quickfixtextfunc
+vim.go.quickfixtextfunc = vim.o.quickfixtextfunc
+vim.go.qftf = vim.go.quickfixtextfunc
+
+--- The characters that are used to escape quotes in a string. Used for
+--- objects like a', a" and a` `a'`.
+--- When one of the characters in this option is found inside a string,
+--- the following character will be skipped. The default value makes the
+--- text "foo\"bar\\" considered to be one string.
+---
+--- @type string
+vim.o.quoteescape = "\\"
+vim.o.qe = vim.o.quoteescape
+vim.bo.quoteescape = vim.o.quoteescape
+vim.bo.qe = vim.bo.quoteescape
+
+--- If on, writes fail unless you use a '!'. Protects you from
+--- accidentally overwriting a file. Default on when Vim is started
+--- in read-only mode ("vim -R") or when the executable is called "view".
+--- When using ":w!" the 'readonly' option is reset for the current
+--- buffer, unless the 'Z' flag is in 'cpoptions'.
+--- When using the ":view" command the 'readonly' option is set for the
+--- newly edited buffer.
+--- See 'modifiable' for disallowing changes to the buffer.
+---
+--- @type boolean
+vim.o.readonly = false
+vim.o.ro = vim.o.readonly
+vim.bo.readonly = vim.o.readonly
+vim.bo.ro = vim.bo.readonly
+
+--- Flags to change the way redrawing works, for debugging purposes.
+--- Most useful with 'writedelay' set to some reasonable value.
+--- Supports the following flags:
+--- compositor Indicate each redraw event handled by the compositor
+--- by briefly flashing the redrawn regions in colors
+--- indicating the redraw type. These are the highlight
+--- groups used (and their default colors):
+--- RedrawDebugNormal gui=reverse normal redraw passed through
+--- RedrawDebugClear guibg=Yellow clear event passed through
+--- RedrawDebugComposed guibg=Green redraw event modified by the
+--- compositor (due to
+--- overlapping grids, etc)
+--- RedrawDebugRecompose guibg=Red redraw generated by the
+--- compositor itself, due to a
+--- grid being moved or deleted.
+--- line introduce a delay after each line drawn on the screen.
+--- When using the TUI or another single-grid UI, "compositor"
+--- gives more information and should be preferred (every
+--- line is processed as a separate event by the compositor)
+--- flush introduce a delay after each "flush" event.
+--- nothrottle Turn off throttling of the message grid. This is an
+--- optimization that joins many small scrolls to one
+--- larger scroll when drawing the message area (with
+--- 'display' msgsep flag active).
+--- invalid Enable stricter checking (abort) of inconsistencies
+--- of the internal screen state. This is mostly
+--- useful when running nvim inside a debugger (and
+--- the test suite).
+--- nodelta Send all internally redrawn cells to the UI, even if
+--- they are unchanged from the already displayed state.
+---
+--- @type string
+vim.o.redrawdebug = ""
+vim.o.rdb = vim.o.redrawdebug
+vim.go.redrawdebug = vim.o.redrawdebug
+vim.go.rdb = vim.go.redrawdebug
+
+--- Time in milliseconds for redrawing the display. Applies to
+--- 'hlsearch', 'inccommand', `:match` highlighting and syntax
+--- highlighting.
+--- When redrawing takes more than this many milliseconds no further
+--- matches will be highlighted.
+--- For syntax highlighting the time applies per window. When over the
+--- limit syntax highlighting is disabled until `CTRL-L` is used.
+--- This is used to avoid that Vim hangs when using a very complicated
+--- pattern.
+---
+--- @type integer
+vim.o.redrawtime = 2000
+vim.o.rdt = vim.o.redrawtime
+vim.go.redrawtime = vim.o.redrawtime
+vim.go.rdt = vim.go.redrawtime
+
+--- This selects the default regexp engine. `two-engines`
+--- The possible values are:
+--- 0 automatic selection
+--- 1 old engine
+--- 2 NFA engine
+--- Note that when using the NFA engine and the pattern contains something
+--- that is not supported the pattern will not match. This is only useful
+--- for debugging the regexp engine.
+--- Using automatic selection enables Vim to switch the engine, if the
+--- default engine becomes too costly. E.g., when the NFA engine uses too
+--- many states. This should prevent Vim from hanging on a combination of
+--- a complex pattern with long text.
+---
+--- @type integer
+vim.o.regexpengine = 0
+vim.o.re = vim.o.regexpengine
+vim.go.regexpengine = vim.o.regexpengine
+vim.go.re = vim.go.regexpengine
+
+--- Show the line number relative to the line with the cursor in front of
+--- each line. Relative line numbers help you use the `count` you can
+--- precede some vertical motion commands (e.g. j k + -) with, without
+--- having to calculate it yourself. Especially useful in combination with
+--- other commands (e.g. y d c < > gq gw =).
+--- When the 'n' option is excluded from 'cpoptions' a wrapped
+--- line will not use the column of line numbers.
+--- The 'numberwidth' option can be used to set the room used for the line
+--- number.
+--- When a long, wrapped line doesn't start with the first character, '-'
+--- characters are put before the number.
+--- See `hl-LineNr` and `hl-CursorLineNr` for the highlighting used for
+--- the number.
+---
+--- The number in front of the cursor line also depends on the value of
+--- 'number', see `number_relativenumber` for all combinations of the two
+--- options.
+---
+--- @type boolean
+vim.o.relativenumber = false
+vim.o.rnu = vim.o.relativenumber
+vim.wo.relativenumber = vim.o.relativenumber
+vim.wo.rnu = vim.wo.relativenumber
+
+--- Threshold for reporting number of lines changed. When the number of
+--- changed lines is more than 'report' a message will be given for most
+--- ":" commands. If you want it always, set 'report' to 0.
+--- For the ":substitute" command the number of substitutions is used
+--- instead of the number of lines.
+---
+--- @type integer
+vim.o.report = 2
+vim.go.report = vim.o.report
+
+--- Inserting characters in Insert mode will work backwards. See "typing
+--- backwards" `ins-reverse`. This option can be toggled with the CTRL-_
+--- command in Insert mode, when 'allowrevins' is set.
+---
+--- @type boolean
+vim.o.revins = false
+vim.o.ri = vim.o.revins
+vim.go.revins = vim.o.revins
+vim.go.ri = vim.go.revins
+
+--- When on, display orientation becomes right-to-left, i.e., characters
+--- that are stored in the file appear from the right to the left.
+--- Using this option, it is possible to edit files for languages that
+--- are written from the right to the left such as Hebrew and Arabic.
+--- This option is per window, so it is possible to edit mixed files
+--- simultaneously, or to view the same file in both ways (this is
+--- useful whenever you have a mixed text file with both right-to-left
+--- and left-to-right strings so that both sets are displayed properly
+--- in different windows). Also see `rileft.txt`.
+---
+--- @type boolean
+vim.o.rightleft = false
+vim.o.rl = vim.o.rightleft
+vim.wo.rightleft = vim.o.rightleft
+vim.wo.rl = vim.wo.rightleft
+
+--- Each word in this option enables the command line editing to work in
+--- right-to-left mode for a group of commands:
+---
+--- search "/" and "?" commands
+---
+--- This is useful for languages such as Hebrew, Arabic and Farsi.
+--- The 'rightleft' option must be set for 'rightleftcmd' to take effect.
+---
+--- @type string
+vim.o.rightleftcmd = "search"
+vim.o.rlc = vim.o.rightleftcmd
+vim.wo.rightleftcmd = vim.o.rightleftcmd
+vim.wo.rlc = vim.wo.rightleftcmd
+
+--- Show the line and column number of the cursor position, separated by a
+--- comma. When there is room, the relative position of the displayed
+--- text in the file is shown on the far right:
+--- Top first line is visible
+--- Bot last line is visible
+--- All first and last line are visible
+--- 45% relative position in the file
+--- If 'rulerformat' is set, it will determine the contents of the ruler.
+--- Each window has its own ruler. If a window has a status line, the
+--- ruler is shown there. If a window doesn't have a status line and
+--- 'cmdheight' is zero, the ruler is not shown. Otherwise it is shown in
+--- the last line of the screen. If the statusline is given by
+--- 'statusline' (i.e. not empty), this option takes precedence over
+--- 'ruler' and 'rulerformat'.
+--- If the number of characters displayed is different from the number of
+--- bytes in the text (e.g., for a TAB or a multibyte character), both
+--- the text column (byte number) and the screen column are shown,
+--- separated with a dash.
+--- For an empty line "0-1" is shown.
+--- For an empty buffer the line number will also be zero: "0,0-1".
+--- If you don't want to see the ruler all the time but want to know where
+--- you are, use "g CTRL-G" `g_CTRL-G`.
+---
+--- @type boolean
+vim.o.ruler = true
+vim.o.ru = vim.o.ruler
+vim.go.ruler = vim.o.ruler
+vim.go.ru = vim.go.ruler
+
+--- When this option is not empty, it determines the content of the ruler
+--- string, as displayed for the 'ruler' option.
+--- The format of this option is like that of 'statusline'.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- The default ruler width is 17 characters. To make the ruler 15
+--- characters wide, put "%15(" at the start and "%)" at the end.
+--- Example:
+--- ```
+--- :set rulerformat=%15(%c%V\ %p%%%)
+--- ```
+---
+---
+--- @type string
+vim.o.rulerformat = ""
+vim.o.ruf = vim.o.rulerformat
+vim.go.rulerformat = vim.o.rulerformat
+vim.go.ruf = vim.go.rulerformat
+
+--- List of directories to be searched for these runtime files:
+--- filetype.lua filetypes `new-filetype`
+--- autoload/ automatically loaded scripts `autoload-functions`
+--- colors/ color scheme files `:colorscheme`
+--- compiler/ compiler files `:compiler`
+--- doc/ documentation `write-local-help`
+--- ftplugin/ filetype plugins `write-filetype-plugin`
+--- indent/ indent scripts `indent-expression`
+--- keymap/ key mapping files `mbyte-keymap`
+--- lang/ menu translations `:menutrans`
+--- lua/ `Lua` plugins
+--- menu.vim GUI menus `menu.vim`
+--- pack/ packages `:packadd`
+--- parser/ `treesitter` syntax parsers
+--- plugin/ plugin scripts `write-plugin`
+--- queries/ `treesitter` queries
+--- rplugin/ `remote-plugin` scripts
+--- spell/ spell checking files `spell`
+--- syntax/ syntax files `mysyntaxfile`
+--- tutor/ tutorial files `:Tutor`
+---
+--- And any other file searched for with the `:runtime` command.
+---
+--- Defaults are setup to search these locations:
+--- 1. Your home directory, for personal preferences.
+--- Given by `stdpath("config")`. `$XDG_CONFIG_HOME`
+--- 2. Directories which must contain configuration files according to
+--- `xdg` ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
+--- preferences from system administrator.
+--- 3. Data home directory, for plugins installed by user.
+--- Given by `stdpath("data")/site`. `$XDG_DATA_HOME`
+--- 4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
+--- This is for plugins which were installed by system administrator,
+--- but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
+--- to /usr/local/share/:/usr/share/, so system administrators are
+--- expected to install site plugins to /usr/share/nvim/site.
+--- 5. Session state directory, for state data such as swap, backupdir,
+--- viewdir, undodir, etc.
+--- Given by `stdpath("state")`. `$XDG_STATE_HOME`
+--- 6. $VIMRUNTIME, for files distributed with Nvim.
+--- *after-directory*
+--- 7, 8, 9, 10. In after/ subdirectories of 1, 2, 3 and 4, with reverse
+--- ordering. This is for preferences to overrule or add to the
+--- distributed defaults or system-wide settings (rarely needed).
+---
+--- *packages-runtimepath*
+--- "start" packages will also be searched (`runtime-search-path`) for
+--- runtime files after these, though such packages are not explicitly
+--- reported in &runtimepath. But "opt" packages are explicitly added to
+--- &runtimepath by `:packadd`.
+---
+--- Note that, unlike 'path', no wildcards like "**" are allowed. Normal
+--- wildcards are allowed, but can significantly slow down searching for
+--- runtime files. For speed, use as few items as possible and avoid
+--- wildcards.
+--- See `:runtime`.
+--- Example:
+--- ```
+--- :set runtimepath=~/vimruntime,/mygroup/vim,$VIMRUNTIME
+--- ```
+--- This will use the directory "~/vimruntime" first (containing your
+--- personal Nvim runtime files), then "/mygroup/vim", and finally
+--- "$VIMRUNTIME" (the default runtime files).
+--- You can put a directory before $VIMRUNTIME to find files which replace
+--- distributed runtime files. You can put a directory after $VIMRUNTIME
+--- to find files which add to distributed runtime files.
+---
+--- With `--clean` the home directory entries are not included.
+---
+--- @type string
+vim.o.runtimepath = "..."
+vim.o.rtp = vim.o.runtimepath
+vim.go.runtimepath = vim.o.runtimepath
+vim.go.rtp = vim.go.runtimepath
+
+--- Number of lines to scroll with CTRL-U and CTRL-D commands. Will be
+--- set to half the number of lines in the window when the window size
+--- changes. This may happen when enabling the `status-line` or
+--- 'tabline' option after setting the 'scroll' option.
+--- If you give a count to the CTRL-U or CTRL-D command it will
+--- be used as the new value for 'scroll'. Reset to half the window
+--- height with ":set scroll=0".
+---
+--- @type integer
+vim.o.scroll = 0
+vim.o.scr = vim.o.scroll
+vim.wo.scroll = vim.o.scroll
+vim.wo.scr = vim.wo.scroll
+
+--- Maximum number of lines kept beyond the visible screen. Lines at the
+--- top are deleted if new lines exceed this limit.
+--- Minimum is 1, maximum is 100000.
+--- Only in `terminal` buffers.
+---
+--- Note: Lines that are not visible and kept in scrollback are not
+--- reflown when the terminal buffer is resized horizontally.
+---
+--- @type integer
+vim.o.scrollback = -1
+vim.o.scbk = vim.o.scrollback
+vim.bo.scrollback = vim.o.scrollback
+vim.bo.scbk = vim.bo.scrollback
+
+--- See also `scroll-binding`. When this option is set, scrolling the
+--- current window also scrolls other scrollbind windows (windows that
+--- also have this option set). This option is useful for viewing the
+--- differences between two versions of a file, see 'diff'.
+--- See `'scrollopt'` for options that determine how this option should be
+--- interpreted.
+--- This option is mostly reset when splitting a window to edit another
+--- file. This means that ":split | edit file" results in two windows
+--- with scroll-binding, but ":split file" does not.
+---
+--- @type boolean
+vim.o.scrollbind = false
+vim.o.scb = vim.o.scrollbind
+vim.wo.scrollbind = vim.o.scrollbind
+vim.wo.scb = vim.wo.scrollbind
+
+--- Minimal number of lines to scroll when the cursor gets off the
+--- screen (e.g., with "j"). Not used for scroll commands (e.g., CTRL-E,
+--- CTRL-D). Useful if your terminal scrolls very slowly.
+--- When set to a negative number from -1 to -100 this is used as the
+--- percentage of the window height. Thus -50 scrolls half the window
+--- height.
+---
+--- @type integer
+vim.o.scrolljump = 1
+vim.o.sj = vim.o.scrolljump
+vim.go.scrolljump = vim.o.scrolljump
+vim.go.sj = vim.go.scrolljump
+
+--- Minimal number of screen lines to keep above and below the cursor.
+--- This will make some context visible around where you are working. If
+--- you set it to a very large value (999) the cursor line will always be
+--- in the middle of the window (except at the start or end of the file or
+--- when long lines wrap).
+--- After using the local value, go back the global value with one of
+--- these two:
+--- ```
+--- setlocal scrolloff<
+--- setlocal scrolloff=-1
+--- ```
+--- For scrolling horizontally see 'sidescrolloff'.
+---
+--- @type integer
+vim.o.scrolloff = 0
+vim.o.so = vim.o.scrolloff
+vim.wo.scrolloff = vim.o.scrolloff
+vim.wo.so = vim.wo.scrolloff
+vim.go.scrolloff = vim.o.scrolloff
+vim.go.so = vim.go.scrolloff
+
+--- This is a comma-separated list of words that specifies how
+--- 'scrollbind' windows should behave. 'sbo' stands for ScrollBind
+--- Options.
+--- The following words are available:
+--- ver Bind vertical scrolling for 'scrollbind' windows
+--- hor Bind horizontal scrolling for 'scrollbind' windows
+--- jump Applies to the offset between two windows for vertical
+--- scrolling. This offset is the difference in the first
+--- displayed line of the bound windows. When moving
+--- around in a window, another 'scrollbind' window may
+--- reach a position before the start or after the end of
+--- the buffer. The offset is not changed though, when
+--- moving back the 'scrollbind' window will try to scroll
+--- to the desired position when possible.
+--- When now making that window the current one, two
+--- things can be done with the relative offset:
+--- 1. When "jump" is not included, the relative offset is
+--- adjusted for the scroll position in the new current
+--- window. When going back to the other window, the
+--- new relative offset will be used.
+--- 2. When "jump" is included, the other windows are
+--- scrolled to keep the same relative offset. When
+--- going back to the other window, it still uses the
+--- same relative offset.
+--- Also see `scroll-binding`.
+--- When 'diff' mode is active there always is vertical scroll binding,
+--- even when "ver" isn't there.
+---
+--- @type string
+vim.o.scrollopt = "ver,jump"
+vim.o.sbo = vim.o.scrollopt
+vim.go.scrollopt = vim.o.scrollopt
+vim.go.sbo = vim.go.scrollopt
+
+--- Specifies the nroff macros that separate sections. These are pairs of
+--- two letters (See `object-motions`). The default makes a section start
+--- at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh".
+---
+--- @type string
+vim.o.sections = "SHNHH HUnhsh"
+vim.o.sect = vim.o.sections
+vim.go.sections = vim.o.sections
+vim.go.sect = vim.go.sections
+
+--- This option defines the behavior of the selection. It is only used
+--- in Visual and Select mode.
+--- Possible values:
+--- value past line inclusive ~
+--- old no yes
+--- inclusive yes yes
+--- exclusive yes no
+--- "past line" means that the cursor is allowed to be positioned one
+--- character past the line.
+--- "inclusive" means that the last character of the selection is included
+--- in an operation. For example, when "x" is used to delete the
+--- selection.
+--- When "old" is used and 'virtualedit' allows the cursor to move past
+--- the end of line the line break still isn't included.
+--- Note that when "exclusive" is used and selecting from the end
+--- backwards, you cannot include the last character of a line, when
+--- starting in Normal mode and 'virtualedit' empty.
+---
+--- @type string
+vim.o.selection = "inclusive"
+vim.o.sel = vim.o.selection
+vim.go.selection = vim.o.selection
+vim.go.sel = vim.go.selection
+
+--- This is a comma-separated list of words, which specifies when to start
+--- Select mode instead of Visual mode, when a selection is started.
+--- Possible values:
+--- mouse when using the mouse
+--- key when using shifted special keys
+--- cmd when using "v", "V" or CTRL-V
+--- See `Select-mode`.
+---
+--- @type string
+vim.o.selectmode = ""
+vim.o.slm = vim.o.selectmode
+vim.go.selectmode = vim.o.selectmode
+vim.go.slm = vim.go.selectmode
+
+--- Changes the effect of the `:mksession` command. It is a comma-
+--- separated list of words. Each word enables saving and restoring
+--- something:
+--- word save and restore ~
+--- blank empty windows
+--- buffers hidden and unloaded buffers, not just those in windows
+--- curdir the current directory
+--- folds manually created folds, opened/closed folds and local
+--- fold options
+--- globals global variables that start with an uppercase letter
+--- and contain at least one lowercase letter. Only
+--- String and Number types are stored.
+--- help the help window
+--- localoptions options and mappings local to a window or buffer (not
+--- global values for local options)
+--- options all options and mappings (also global values for local
+--- options)
+--- skiprtp exclude 'runtimepath' and 'packpath' from the options
+--- resize size of the Vim window: 'lines' and 'columns'
+--- sesdir the directory in which the session file is located
+--- will become the current directory (useful with
+--- projects accessed over a network from different
+--- systems)
+--- tabpages all tab pages; without this only the current tab page
+--- is restored, so that you can make a session for each
+--- tab page separately
+--- terminal include terminal windows where the command can be
+--- restored
+--- winpos position of the whole Vim window
+--- winsize window sizes
+--- slash `deprecated` Always enabled. Uses "/" in filenames.
+--- unix `deprecated` Always enabled. Uses "\n" line endings.
+---
+--- Don't include both "curdir" and "sesdir". When neither is included
+--- filenames are stored as absolute paths.
+--- If you leave out "options" many things won't work well after restoring
+--- the session.
+---
+--- @type string
+vim.o.sessionoptions = "blank,buffers,curdir,folds,help,tabpages,winsize,terminal"
+vim.o.ssop = vim.o.sessionoptions
+vim.go.sessionoptions = vim.o.sessionoptions
+vim.go.ssop = vim.go.sessionoptions
+
+--- When non-empty, the shada file is read upon startup and written
+--- when exiting Vim (see `shada-file`). The string should be a comma-
+--- separated list of parameters, each consisting of a single character
+--- identifying the particular parameter, followed by a number or string
+--- which specifies the value of that parameter. If a particular
+--- character is left out, then the default value is used for that
+--- parameter. The following is a list of the identifying characters and
+--- the effect of their value.
+--- CHAR VALUE ~
+--- *shada-!*
+--- ! When included, save and restore global variables that start
+--- with an uppercase letter, and don't contain a lowercase
+--- letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis"
+--- and "_K_L_M" are not. Nested List and Dict items may not be
+--- read back correctly, you end up with an empty item.
+--- *shada-quote*
+--- " Maximum number of lines saved for each register. Old name of
+--- the '<' item, with the disadvantage that you need to put a
+--- backslash before the ", otherwise it will be recognized as the
+--- start of a comment!
+--- *shada-%*
+--- % When included, save and restore the buffer list. If Vim is
+--- started with a file name argument, the buffer list is not
+--- restored. If Vim is started without a file name argument, the
+--- buffer list is restored from the shada file. Quickfix
+--- ('buftype'), unlisted ('buflisted'), unnamed and buffers on
+--- removable media (`shada-r`) are not saved.
+--- When followed by a number, the number specifies the maximum
+--- number of buffers that are stored. Without a number all
+--- buffers are stored.
+--- *shada-'*
+--- ' Maximum number of previously edited files for which the marks
+--- are remembered. This parameter must always be included when
+--- 'shada' is non-empty.
+--- Including this item also means that the `jumplist` and the
+--- `changelist` are stored in the shada file.
+--- *shada-/*
+--- / Maximum number of items in the search pattern history to be
+--- saved. If non-zero, then the previous search and substitute
+--- patterns are also saved. When not included, the value of
+--- 'history' is used.
+--- *shada-:*
+--- : Maximum number of items in the command-line history to be
+--- saved. When not included, the value of 'history' is used.
+--- *shada-<*
+--- \< Maximum number of lines saved for each register. If zero then
+--- registers are not saved. When not included, all lines are
+--- saved. '"' is the old name for this item.
+--- Also see the 's' item below: limit specified in KiB.
+--- *shada-@*
+--- @ Maximum number of items in the input-line history to be
+--- saved. When not included, the value of 'history' is used.
+--- *shada-c*
+--- c Dummy option, kept for compatibility reasons. Has no actual
+--- effect: ShaDa always uses UTF-8 and 'encoding' value is fixed
+--- to UTF-8 as well.
+--- *shada-f*
+--- f Whether file marks need to be stored. If zero, file marks ('0
+--- to '9, 'A to 'Z) are not stored. When not present or when
+--- non-zero, they are all stored. '0 is used for the current
+--- cursor position (when exiting or when doing `:wshada`).
+--- *shada-h*
+--- h Disable the effect of 'hlsearch' when loading the shada
+--- file. When not included, it depends on whether ":nohlsearch"
+--- has been used since the last search command.
+--- *shada-n*
+--- n Name of the shada file. The name must immediately follow
+--- the 'n'. Must be at the end of the option! If the
+--- 'shadafile' option is set, that file name overrides the one
+--- given here with 'shada'. Environment variables are
+--- expanded when opening the file, not when setting the option.
+--- *shada-r*
+--- r Removable media. The argument is a string (up to the next
+--- ','). This parameter can be given several times. Each
+--- specifies the start of a path for which no marks will be
+--- stored. This is to avoid removable media. For Windows you
+--- could use "ra:,rb:". You can also use it for temp files,
+--- e.g., for Unix: "r/tmp". Case is ignored.
+--- *shada-s*
+--- s Maximum size of an item contents in KiB. If zero then nothing
+--- is saved. Unlike Vim this applies to all items, except for
+--- the buffer list and header. Full item size is off by three
+--- unsigned integers: with `s10` maximum item size may be 1 byte
+--- (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
+--- integer) + 3 bytes (item size: up to 16-bit integer because
+--- 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
+--- contents size) = 10253 bytes.
+---
+--- Example:
+--- ```
+--- :set shada='50,<1000,s100,:0,n~/nvim/shada
+--- ```
+---
+--- '50 Marks will be remembered for the last 50 files you
+--- edited.
+--- <1000 Contents of registers (up to 1000 lines each) will be
+--- remembered.
+--- s100 Items with contents occupying more then 100 KiB are
+--- skipped.
+--- :0 Command-line history will not be saved.
+--- n~/nvim/shada The name of the file to use is "~/nvim/shada".
+--- no / Since '/' is not specified, the default will be used,
+--- that is, save all of the search history, and also the
+--- previous search and substitute patterns.
+--- no % The buffer list will not be saved nor read back.
+--- no h 'hlsearch' highlighting will be restored.
+---
+--- When setting 'shada' from an empty value you can use `:rshada` to
+--- load the contents of the file, this is not done automatically.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shada = "!,'100,<50,s10,h"
+vim.o.sd = vim.o.shada
+vim.go.shada = vim.o.shada
+vim.go.sd = vim.go.shada
+
+--- When non-empty, overrides the file name used for `shada` (viminfo).
+--- When equal to "NONE" no shada file will be read or written.
+--- This option can be set with the `-i` command line flag. The `--clean`
+--- command line flag sets it to "NONE".
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shadafile = ""
+vim.o.sdf = vim.o.shadafile
+vim.go.shadafile = vim.o.shadafile
+vim.go.sdf = vim.go.shadafile
+
+--- Name of the shell to use for ! and :! commands. When changing the
+--- value also check these options: 'shellpipe', 'shellslash'
+--- 'shellredir', 'shellquote', 'shellxquote' and 'shellcmdflag'.
+--- It is allowed to give an argument to the command, e.g. "csh -f".
+--- See `option-backslash` about including spaces and backslashes.
+--- Environment variables are expanded `:set_env`.
+---
+--- If the name of the shell contains a space, you need to enclose it in
+--- quotes. Example with quotes:
+--- ```
+--- :set shell=\"c:\program\ files\unix\sh.exe\"\ -f
+--- ```
+--- Note the backslash before each quote (to avoid starting a comment) and
+--- each space (to avoid ending the option value), so better use `:let-&`
+--- like this:
+--- ```
+--- :let &shell='"C:\Program Files\unix\sh.exe" -f'
+--- ```
+--- Also note that the "-f" is not inside the quotes, because it is not
+--- part of the command name.
+--- *shell-unquoting*
+--- Rules regarding quotes:
+--- 1. Option is split on space and tab characters that are not inside
+--- quotes: "abc def" runs shell named "abc" with additional argument
+--- "def", '"abc def"' runs shell named "abc def" with no additional
+--- arguments (here and below: additional means “additional to
+--- 'shellcmdflag'”).
+--- 2. Quotes in option may be present in any position and any number:
+--- '"abc"', '"a"bc', 'a"b"c', 'ab"c"' and '"a"b"c"' are all equivalent
+--- to just "abc".
+--- 3. Inside quotes backslash preceding backslash means one backslash.
+--- Backslash preceding quote means one quote. Backslash preceding
+--- anything else means backslash and next character literally:
+--- '"a\\b"' is the same as "a\b", '"a\\"b"' runs shell named literally
+--- 'a"b', '"a\b"' is the same as "a\b" again.
+--- 4. Outside of quotes backslash always means itself, it cannot be used
+--- to escape quote: 'a\"b"' is the same as "a\b".
+--- Note that such processing is done after `:set` did its own round of
+--- unescaping, so to keep yourself sane use `:let-&` like shown above.
+--- *shell-powershell*
+--- To use PowerShell:
+--- ```
+--- let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
+--- let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
+--- let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
+--- let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
+--- set shellquote= shellxquote=
+--- ```
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shell = "sh"
+vim.o.sh = vim.o.shell
+vim.go.shell = vim.o.shell
+vim.go.sh = vim.go.shell
+
+--- Flag passed to the shell to execute "!" and ":!" commands; e.g.,
+--- `bash.exe -c ls` or `cmd.exe /s /c "dir"`. For MS-Windows, the
+--- default is set according to the value of 'shell', to reduce the need
+--- to set this option by the user.
+--- On Unix it can have more than one flag. Each white space separated
+--- part is passed as an argument to the shell command.
+--- See `option-backslash` about including spaces and backslashes.
+--- See `shell-unquoting` which talks about separating this option into
+--- multiple arguments.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellcmdflag = "-c"
+vim.o.shcf = vim.o.shellcmdflag
+vim.go.shellcmdflag = vim.o.shellcmdflag
+vim.go.shcf = vim.go.shellcmdflag
+
+--- String to be used to put the output of the ":make" command in the
+--- error file. See also `:make_makeprg`. See `option-backslash` about
+--- including spaces and backslashes.
+--- The name of the temporary file can be represented by "%s" if necessary
+--- (the file name is appended automatically if no %s appears in the value
+--- of this option).
+--- For MS-Windows the default is "2>&1| tee". The stdout and stderr are
+--- saved in a file and echoed to the screen.
+--- For Unix the default is "| tee". The stdout of the compiler is saved
+--- in a file and echoed to the screen. If the 'shell' option is "csh" or
+--- "tcsh" after initializations, the default becomes "|& tee". If the
+--- 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+--- "bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This
+--- means that stderr is also included. Before using the 'shell' option a
+--- path is removed, thus "/bin/sh" uses "sh".
+--- The initialization of this option is done after reading the vimrc
+--- and the other initializations, so that when the 'shell' option is set
+--- there, the 'shellpipe' option changes automatically, unless it was
+--- explicitly set before.
+--- When 'shellpipe' is set to an empty string, no redirection of the
+--- ":make" output will be done. This is useful if you use a 'makeprg'
+--- that writes to 'makeef' by itself. If you want no piping, but do
+--- want to include the 'makeef', set 'shellpipe' to a single space.
+--- Don't forget to precede the space with a backslash: ":set sp=\ ".
+--- In the future pipes may be used for filtering and this option will
+--- become obsolete (at least for Unix).
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellpipe = "| tee"
+vim.o.sp = vim.o.shellpipe
+vim.go.shellpipe = vim.o.shellpipe
+vim.go.sp = vim.go.shellpipe
+
+--- Quoting character(s), put around the command passed to the shell, for
+--- the "!" and ":!" commands. The redirection is kept outside of the
+--- quoting. See 'shellxquote' to include the redirection. It's
+--- probably not useful to set both options.
+--- This is an empty string by default. Only known to be useful for
+--- third-party shells on Windows systems, such as the MKS Korn Shell
+--- or bash, where it should be "\"". The default is adjusted according
+--- the value of 'shell', to reduce the need to set this option by the
+--- user.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellquote = ""
+vim.o.shq = vim.o.shellquote
+vim.go.shellquote = vim.o.shellquote
+vim.go.shq = vim.go.shellquote
+
+--- String to be used to put the output of a filter command in a temporary
+--- file. See also `:!`. See `option-backslash` about including spaces
+--- and backslashes.
+--- The name of the temporary file can be represented by "%s" if necessary
+--- (the file name is appended automatically if no %s appears in the value
+--- of this option).
+--- The default is ">". For Unix, if the 'shell' option is "csh" or
+--- "tcsh" during initializations, the default becomes ">&". If the
+--- 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+--- "bash" or "fish", the default becomes ">%s 2>&1". This means that
+--- stderr is also included. For Win32, the Unix checks are done and
+--- additionally "cmd" is checked for, which makes the default ">%s 2>&1".
+--- Also, the same names with ".exe" appended are checked for.
+--- The initialization of this option is done after reading the vimrc
+--- and the other initializations, so that when the 'shell' option is set
+--- there, the 'shellredir' option changes automatically unless it was
+--- explicitly set before.
+--- In the future pipes may be used for filtering and this option will
+--- become obsolete (at least for Unix).
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellredir = ">"
+vim.o.srr = vim.o.shellredir
+vim.go.shellredir = vim.o.shellredir
+vim.go.srr = vim.go.shellredir
+
+--- only for MS-Windows
+--- When set, a forward slash is used when expanding file names. This is
+--- useful when a Unix-like shell is used instead of cmd.exe. Backward
+--- slashes can still be typed, but they are changed to forward slashes by
+--- Vim.
+--- Note that setting or resetting this option has no effect for some
+--- existing file names, thus this option needs to be set before opening
+--- any file for best results. This might change in the future.
+--- 'shellslash' only works when a backslash can be used as a path
+--- separator. To test if this is so use:
+--- ```
+--- if exists('+shellslash')
+--- ```
+--- Also see 'completeslash'.
+---
+--- @type boolean
+vim.o.shellslash = false
+vim.o.ssl = vim.o.shellslash
+vim.go.shellslash = vim.o.shellslash
+vim.go.ssl = vim.go.shellslash
+
+--- When on, use temp files for shell commands. When off use a pipe.
+--- When using a pipe is not possible temp files are used anyway.
+--- The advantage of using a pipe is that nobody can read the temp file
+--- and the 'shell' command does not need to support redirection.
+--- The advantage of using a temp file is that the file type and encoding
+--- can be detected.
+--- The `FilterReadPre`, `FilterReadPost` and `FilterWritePre|,
+--- |FilterWritePost` autocommands event are not triggered when
+--- 'shelltemp' is off.
+--- `system()` does not respect this option, it always uses pipes.
+---
+--- @type boolean
+vim.o.shelltemp = true
+vim.o.stmp = vim.o.shelltemp
+vim.go.shelltemp = vim.o.shelltemp
+vim.go.stmp = vim.go.shelltemp
+
+--- When 'shellxquote' is set to "(" then the characters listed in this
+--- option will be escaped with a '^' character. This makes it possible
+--- to execute most external commands with cmd.exe.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellxescape = ""
+vim.o.sxe = vim.o.shellxescape
+vim.go.shellxescape = vim.o.shellxescape
+vim.go.sxe = vim.go.shellxescape
+
+--- Quoting character(s), put around the command passed to the shell, for
+--- the "!" and ":!" commands. Includes the redirection. See
+--- 'shellquote' to exclude the redirection. It's probably not useful
+--- to set both options.
+--- When the value is '(' then ')' is appended. When the value is '"('
+--- then ')"' is appended.
+--- When the value is '(' then also see 'shellxescape'.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.shellxquote = ""
+vim.o.sxq = vim.o.shellxquote
+vim.go.shellxquote = vim.o.shellxquote
+vim.go.sxq = vim.go.shellxquote
+
+--- Round indent to multiple of 'shiftwidth'. Applies to > and <
+--- commands. CTRL-T and CTRL-D in Insert mode always round the indent to
+--- a multiple of 'shiftwidth' (this is Vi compatible).
+---
+--- @type boolean
+vim.o.shiftround = false
+vim.o.sr = vim.o.shiftround
+vim.go.shiftround = vim.o.shiftround
+vim.go.sr = vim.go.shiftround
+
+--- Number of spaces to use for each step of (auto)indent. Used for
+--- `'cindent'`, `>>`, `<<`, etc.
+--- When zero the 'tabstop' value will be used. Use the `shiftwidth()`
+--- function to get the effective shiftwidth value.
+---
+--- @type integer
+vim.o.shiftwidth = 8
+vim.o.sw = vim.o.shiftwidth
+vim.bo.shiftwidth = vim.o.shiftwidth
+vim.bo.sw = vim.bo.shiftwidth
+
+--- This option helps to avoid all the `hit-enter` prompts caused by file
+--- messages, for example with CTRL-G, and to avoid some other messages.
+--- It is a list of flags:
+--- flag meaning when present ~
+--- l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l*
+--- m use "[+]" instead of "[Modified]" *shm-m*
+--- r use "[RO]" instead of "[readonly]" *shm-r*
+--- w use "[w]" instead of "written" for file write message *shm-w*
+--- and "[a]" instead of "appended" for ':w >> file' command
+--- a all of the above abbreviations *shm-a*
+---
+--- o overwrite message for writing a file with subsequent *shm-o*
+--- message for reading a file (useful for ":wn" or when
+--- 'autowrite' on)
+--- O message for reading a file overwrites any previous *shm-O*
+--- message; also for quickfix message (e.g., ":cn")
+--- s don't give "search hit BOTTOM, continuing at TOP" or *shm-s*
+--- "search hit TOP, continuing at BOTTOM" messages; when using
+--- the search count do not show "W" after the count message (see
+--- S below)
+--- t truncate file message at the start if it is too long *shm-t*
+--- to fit on the command-line, "<" will appear in the left most
+--- column; ignored in Ex mode
+--- T truncate other messages in the middle if they are too *shm-T*
+--- long to fit on the command line; "..." will appear in the
+--- middle; ignored in Ex mode
+--- W don't give "written" or "[w]" when writing a file *shm-W*
+--- A don't give the "ATTENTION" message when an existing *shm-A*
+--- swap file is found
+--- I don't give the intro message when starting Vim, *shm-I*
+--- see `:intro`
+--- c don't give `ins-completion-menu` messages; for *shm-c*
+--- example, "-- XXX completion (YYY)", "match 1 of 2", "The only
+--- match", "Pattern not found", "Back at original", etc.
+--- C don't give messages while scanning for ins-completion *shm-C*
+--- items, for instance "scanning tags"
+--- q use "recording" instead of "recording @a" *shm-q*
+--- F don't give the file info when editing a file, like *shm-F*
+--- `:silent` was used for the command
+--- S do not show search count message when searching, e.g. *shm-S*
+--- "[1/5]"
+---
+--- This gives you the opportunity to avoid that a change between buffers
+--- requires you to hit <Enter>, but still gives as useful a message as
+--- possible for the space available. To get the whole message that you
+--- would have got with 'shm' empty, use ":file!"
+--- Useful values:
+--- shm= No abbreviation of message.
+--- shm=a Abbreviation, but no loss of information.
+--- shm=at Abbreviation, and truncate message when necessary.
+---
+--- @type string
+vim.o.shortmess = "ltToOCF"
+vim.o.shm = vim.o.shortmess
+vim.go.shortmess = vim.o.shortmess
+vim.go.shm = vim.go.shortmess
+
+--- String to put at the start of lines that have been wrapped. Useful
+--- values are "> " or "+++ ":
+--- ```
+--- :let &showbreak = "> "
+--- :let &showbreak = '+++ '
+--- ```
+--- Only printable single-cell characters are allowed, excluding <Tab> and
+--- comma (in a future version the comma might be used to separate the
+--- part that is shown at the end and at the start of a line).
+--- The `hl-NonText` highlight group determines the highlighting.
+--- Note that tabs after the showbreak will be displayed differently.
+--- If you want the 'showbreak' to appear in between line numbers, add the
+--- "n" flag to 'cpoptions'.
+--- A window-local value overrules a global value. If the global value is
+--- set and you want no value in the current window use NONE:
+--- ```
+--- :setlocal showbreak=NONE
+--- ```
+---
+---
+--- @type string
+vim.o.showbreak = ""
+vim.o.sbr = vim.o.showbreak
+vim.wo.showbreak = vim.o.showbreak
+vim.wo.sbr = vim.wo.showbreak
+vim.go.showbreak = vim.o.showbreak
+vim.go.sbr = vim.go.showbreak
+
+--- Show (partial) command in the last line of the screen. Set this
+--- option off if your terminal is slow.
+--- In Visual mode the size of the selected area is shown:
+--- - When selecting characters within a line, the number of characters.
+--- If the number of bytes is different it is also displayed: "2-6"
+--- means two characters and six bytes.
+--- - When selecting more than one line, the number of lines.
+--- - When selecting a block, the size in screen characters:
+--- {lines}x{columns}.
+--- This information can be displayed in an alternative location using the
+--- 'showcmdloc' option, useful when 'cmdheight' is 0.
+---
+--- @type boolean
+vim.o.showcmd = true
+vim.o.sc = vim.o.showcmd
+vim.go.showcmd = vim.o.showcmd
+vim.go.sc = vim.go.showcmd
+
+--- This option can be used to display the (partially) entered command in
+--- another location. Possible values are:
+--- last Last line of the screen (default).
+--- statusline Status line of the current window.
+--- tabline First line of the screen if 'showtabline' is enabled.
+--- Setting this option to "statusline" or "tabline" means that these will
+--- be redrawn whenever the command changes, which can be on every key
+--- pressed.
+--- The %S 'statusline' item can be used in 'statusline' or 'tabline' to
+--- place the text. Without a custom 'statusline' or 'tabline' it will be
+--- displayed in a convenient location.
+---
+--- @type string
+vim.o.showcmdloc = "last"
+vim.o.sloc = vim.o.showcmdloc
+vim.go.showcmdloc = vim.o.showcmdloc
+vim.go.sloc = vim.go.showcmdloc
+
+--- When completing a word in insert mode (see `ins-completion`) from the
+--- tags file, show both the tag name and a tidied-up form of the search
+--- pattern (if there is one) as possible matches. Thus, if you have
+--- matched a C function, you can see a template for what arguments are
+--- required (coding style permitting).
+--- Note that this doesn't work well together with having "longest" in
+--- 'completeopt', because the completion from the search pattern may not
+--- match the typed text.
+---
+--- @type boolean
+vim.o.showfulltag = false
+vim.o.sft = vim.o.showfulltag
+vim.go.showfulltag = vim.o.showfulltag
+vim.go.sft = vim.go.showfulltag
+
+--- When a bracket is inserted, briefly jump to the matching one. The
+--- jump is only done if the match can be seen on the screen. The time to
+--- show the match can be set with 'matchtime'.
+--- A Beep is given if there is no match (no matter if the match can be
+--- seen or not).
+--- When the 'm' flag is not included in 'cpoptions', typing a character
+--- will immediately move the cursor back to where it belongs.
+--- See the "sm" field in 'guicursor' for setting the cursor shape and
+--- blinking when showing the match.
+--- The 'matchpairs' option can be used to specify the characters to show
+--- matches for. 'rightleft' and 'revins' are used to look for opposite
+--- matches.
+--- Also see the matchparen plugin for highlighting the match when moving
+--- around `pi_paren.txt`.
+--- Note: Use of the short form is rated PG.
+---
+--- @type boolean
+vim.o.showmatch = false
+vim.o.sm = vim.o.showmatch
+vim.go.showmatch = vim.o.showmatch
+vim.go.sm = vim.go.showmatch
+
+--- If in Insert, Replace or Visual mode put a message on the last line.
+--- The `hl-ModeMsg` highlight group determines the highlighting.
+--- The option has no effect when 'cmdheight' is zero.
+---
+--- @type boolean
+vim.o.showmode = true
+vim.o.smd = vim.o.showmode
+vim.go.showmode = vim.o.showmode
+vim.go.smd = vim.go.showmode
+
+--- The value of this option specifies when the line with tab page labels
+--- will be displayed:
+--- 0: never
+--- 1: only if there are at least two tab pages
+--- 2: always
+--- This is both for the GUI and non-GUI implementation of the tab pages
+--- line.
+--- See `tab-page` for more information about tab pages.
+---
+--- @type integer
+vim.o.showtabline = 1
+vim.o.stal = vim.o.showtabline
+vim.go.showtabline = vim.o.showtabline
+vim.go.stal = vim.go.showtabline
+
+--- The minimal number of columns to scroll horizontally. Used only when
+--- the 'wrap' option is off and the cursor is moved off of the screen.
+--- When it is zero the cursor will be put in the middle of the screen.
+--- When using a slow terminal set it to a large number or 0. Not used
+--- for "zh" and "zl" commands.
+---
+--- @type integer
+vim.o.sidescroll = 1
+vim.o.ss = vim.o.sidescroll
+vim.go.sidescroll = vim.o.sidescroll
+vim.go.ss = vim.go.sidescroll
+
+--- The minimal number of screen columns to keep to the left and to the
+--- right of the cursor if 'nowrap' is set. Setting this option to a
+--- value greater than 0 while having `'sidescroll'` also at a non-zero
+--- value makes some context visible in the line you are scrolling in
+--- horizontally (except at beginning of the line). Setting this option
+--- to a large value (like 999) has the effect of keeping the cursor
+--- horizontally centered in the window, as long as one does not come too
+--- close to the beginning of the line.
+--- After using the local value, go back the global value with one of
+--- these two:
+--- ```
+--- setlocal sidescrolloff<
+--- setlocal sidescrolloff=-1
+--- ```
+---
+--- Example: Try this together with 'sidescroll' and 'listchars' as
+--- in the following example to never allow the cursor to move
+--- onto the "extends" character:
+--- ```
+--- :set nowrap sidescroll=1 listchars=extends:>,precedes:<
+--- :set sidescrolloff=1
+--- ```
+---
+---
+--- @type integer
+vim.o.sidescrolloff = 0
+vim.o.siso = vim.o.sidescrolloff
+vim.wo.sidescrolloff = vim.o.sidescrolloff
+vim.wo.siso = vim.wo.sidescrolloff
+vim.go.sidescrolloff = vim.o.sidescrolloff
+vim.go.siso = vim.go.sidescrolloff
+
+--- When and how to draw the signcolumn. Valid values are:
+--- "auto" only when there is a sign to display
+--- "auto:[1-9]" resize to accommodate multiple signs up to the
+--- given number (maximum 9), e.g. "auto:4"
+--- "auto:[1-8]-[2-9]"
+--- resize to accommodate multiple signs up to the
+--- given maximum number (maximum 9) while keeping
+--- at least the given minimum (maximum 8) fixed
+--- space. The minimum number should always be less
+--- than the maximum number, e.g. "auto:2-5"
+--- "no" never
+--- "yes" always
+--- "yes:[1-9]" always, with fixed space for signs up to the given
+--- number (maximum 9), e.g. "yes:3"
+--- "number" display signs in the 'number' column. If the number
+--- column is not present, then behaves like "auto".
+---
+--- @type string
+vim.o.signcolumn = "auto"
+vim.o.scl = vim.o.signcolumn
+vim.wo.signcolumn = vim.o.signcolumn
+vim.wo.scl = vim.wo.signcolumn
+
+--- Override the 'ignorecase' option if the search pattern contains upper
+--- case characters. Only used when the search pattern is typed and
+--- 'ignorecase' option is on. Used for the commands "/", "?", "n", "N",
+--- ":g" and ":s". Not used for "*", "#", "gd", tag search, etc. After
+--- "*" and "#" you can make 'smartcase' used by doing a "/" command,
+--- recalling the search pattern from history and hitting <Enter>.
+---
+--- @type boolean
+vim.o.smartcase = false
+vim.o.scs = vim.o.smartcase
+vim.go.smartcase = vim.o.smartcase
+vim.go.scs = vim.go.smartcase
+
+--- Do smart autoindenting when starting a new line. Works for C-like
+--- programs, but can also be used for other languages. 'cindent' does
+--- something like this, works better in most cases, but is more strict,
+--- see `C-indenting`. When 'cindent' is on or 'indentexpr' is set,
+--- setting 'si' has no effect. 'indentexpr' is a more advanced
+--- alternative.
+--- Normally 'autoindent' should also be on when using 'smartindent'.
+--- An indent is automatically inserted:
+--- - After a line ending in "{".
+--- - After a line starting with a keyword from 'cinwords'.
+--- - Before a line starting with "}" (only with the "O" command).
+--- When typing '}' as the first character in a new line, that line is
+--- given the same indent as the matching "{".
+--- When typing '#' as the first character in a new line, the indent for
+--- that line is removed, the '#' is put in the first column. The indent
+--- is restored for the next line. If you don't want this, use this
+--- mapping: ":inoremap # X^H#", where ^H is entered with CTRL-V CTRL-H.
+--- When using the ">>" command, lines starting with '#' are not shifted
+--- right.
+---
+--- @type boolean
+vim.o.smartindent = false
+vim.o.si = vim.o.smartindent
+vim.bo.smartindent = vim.o.smartindent
+vim.bo.si = vim.bo.smartindent
+
+--- When on, a <Tab> in front of a line inserts blanks according to
+--- 'shiftwidth'. 'tabstop' or 'softtabstop' is used in other places. A
+--- <BS> will delete a 'shiftwidth' worth of space at the start of the
+--- line.
+--- When off, a <Tab> always inserts blanks according to 'tabstop' or
+--- 'softtabstop'. 'shiftwidth' is only used for shifting text left or
+--- right `shift-left-right`.
+--- What gets inserted (a <Tab> or spaces) depends on the 'expandtab'
+--- option. Also see `ins-expandtab`. When 'expandtab' is not set, the
+--- number of spaces is minimized by using <Tab>s.
+---
+--- @type boolean
+vim.o.smarttab = true
+vim.o.sta = vim.o.smarttab
+vim.go.smarttab = vim.o.smarttab
+vim.go.sta = vim.go.smarttab
+
+--- Scrolling works with screen lines. When 'wrap' is set and the first
+--- line in the window wraps part of it may not be visible, as if it is
+--- above the window. "<<<" is displayed at the start of the first line,
+--- highlighted with `hl-NonText`.
+--- You may also want to add "lastline" to the 'display' option to show as
+--- much of the last line as possible.
+--- NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
+--- and scrolling with the mouse.
+---
+--- @type boolean
+vim.o.smoothscroll = false
+vim.o.sms = vim.o.smoothscroll
+vim.wo.smoothscroll = vim.o.smoothscroll
+vim.wo.sms = vim.wo.smoothscroll
+
+--- Number of spaces that a <Tab> counts for while performing editing
+--- operations, like inserting a <Tab> or using <BS>. It "feels" like
+--- <Tab>s are being inserted, while in fact a mix of spaces and <Tab>s is
+--- used. This is useful to keep the 'ts' setting at its standard value
+--- of 8, while being able to edit like it is set to 'sts'. However,
+--- commands like "x" still work on the actual characters.
+--- When 'sts' is zero, this feature is off.
+--- When 'sts' is negative, the value of 'shiftwidth' is used.
+--- See also `ins-expandtab`. When 'expandtab' is not set, the number of
+--- spaces is minimized by using <Tab>s.
+--- The 'L' flag in 'cpoptions' changes how tabs are used when 'list' is
+--- set.
+---
+--- The value of 'softtabstop' will be ignored if `'varsofttabstop'` is set
+--- to anything other than an empty string.
+---
+--- @type integer
+vim.o.softtabstop = 0
+vim.o.sts = vim.o.softtabstop
+vim.bo.softtabstop = vim.o.softtabstop
+vim.bo.sts = vim.bo.softtabstop
+
+--- When on spell checking will be done. See `spell`.
+--- The languages are specified with 'spelllang'.
+---
+--- @type boolean
+vim.o.spell = false
+vim.wo.spell = vim.o.spell
+
+--- Pattern to locate the end of a sentence. The following word will be
+--- checked to start with a capital letter. If not then it is highlighted
+--- with SpellCap `hl-SpellCap` (unless the word is also badly spelled).
+--- When this check is not wanted make this option empty.
+--- Only used when 'spell' is set.
+--- Be careful with special characters, see `option-backslash` about
+--- including spaces and backslashes.
+--- To set this option automatically depending on the language, see
+--- `set-spc-auto`.
+---
+--- @type string
+vim.o.spellcapcheck = "[.?!]\\_[\\])'\"\\t ]\\+"
+vim.o.spc = vim.o.spellcapcheck
+vim.bo.spellcapcheck = vim.o.spellcapcheck
+vim.bo.spc = vim.bo.spellcapcheck
+
+--- Name of the word list file where words are added for the `zg` and `zw`
+--- commands. It must end in ".{encoding}.add". You need to include the
+--- path, otherwise the file is placed in the current directory.
+--- The path may include characters from 'isfname', space, comma and '@'.
+--- *E765*
+--- It may also be a comma-separated list of names. A count before the
+--- `zg` and `zw` commands can be used to access each. This allows using
+--- a personal word list file and a project word list file.
+--- When a word is added while this option is empty Vim will set it for
+--- you: Using the first directory in 'runtimepath' that is writable. If
+--- there is no "spell" directory yet it will be created. For the file
+--- name the first language name that appears in 'spelllang' is used,
+--- ignoring the region.
+--- The resulting ".spl" file will be used for spell checking, it does not
+--- have to appear in 'spelllang'.
+--- Normally one file is used for all regions, but you can add the region
+--- name if you want to. However, it will then only be used when
+--- 'spellfile' is set to it, for entries in 'spelllang' only files
+--- without region name will be found.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.spellfile = ""
+vim.o.spf = vim.o.spellfile
+vim.bo.spellfile = vim.o.spellfile
+vim.bo.spf = vim.bo.spellfile
+
+--- A comma-separated list of word list names. When the 'spell' option is
+--- on spellchecking will be done for these languages. Example:
+--- ```
+--- set spelllang=en_us,nl,medical
+--- ```
+--- This means US English, Dutch and medical words are recognized. Words
+--- that are not recognized will be highlighted.
+--- The word list name must consist of alphanumeric characters, a dash or
+--- an underscore. It should not include a comma or dot. Using a dash is
+--- recommended to separate the two letter language name from a
+--- specification. Thus "en-rare" is used for rare English words.
+--- A region name must come last and have the form "_xx", where "xx" is
+--- the two-letter, lower case region name. You can use more than one
+--- region by listing them: "en_us,en_ca" supports both US and Canadian
+--- English, but not words specific for Australia, New Zealand or Great
+--- Britain. (Note: currently en_au and en_nz dictionaries are older than
+--- en_ca, en_gb and en_us).
+--- If the name "cjk" is included East Asian characters are excluded from
+--- spell checking. This is useful when editing text that also has Asian
+--- words.
+--- Note that the "medical" dictionary does not exist, it is just an
+--- example of a longer name.
+--- *E757*
+--- As a special case the name of a .spl file can be given as-is. The
+--- first "_xx" in the name is removed and used as the region name
+--- (_xx is an underscore, two letters and followed by a non-letter).
+--- This is mainly for testing purposes. You must make sure the correct
+--- encoding is used, Vim doesn't check it.
+--- How the related spell files are found is explained here: `spell-load`.
+---
+--- If the `spellfile.vim` plugin is active and you use a language name
+--- for which Vim cannot find the .spl file in 'runtimepath' the plugin
+--- will ask you if you want to download the file.
+---
+--- After this option has been set successfully, Vim will source the files
+--- "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang'
+--- up to the first character that is not an ASCII letter or number and
+--- not a dash. Also see `set-spc-auto`.
+---
+--- @type string
+vim.o.spelllang = "en"
+vim.o.spl = vim.o.spelllang
+vim.bo.spelllang = vim.o.spelllang
+vim.bo.spl = vim.bo.spelllang
+
+--- A comma-separated list of options for spell checking:
+--- camel When a word is CamelCased, assume "Cased" is a
+--- separate word: every upper-case character in a word
+--- that comes after a lower case character indicates the
+--- start of a new word.
+--- noplainbuffer Only spellcheck a buffer when 'syntax' is enabled,
+--- or when extmarks are set within the buffer. Only
+--- designated regions of the buffer are spellchecked in
+--- this case.
+---
+--- @type string
+vim.o.spelloptions = ""
+vim.o.spo = vim.o.spelloptions
+vim.bo.spelloptions = vim.o.spelloptions
+vim.bo.spo = vim.bo.spelloptions
+
+--- Methods used for spelling suggestions. Both for the `z=` command and
+--- the `spellsuggest()` function. This is a comma-separated list of
+--- items:
+---
+--- best Internal method that works best for English. Finds
+--- changes like "fast" and uses a bit of sound-a-like
+--- scoring to improve the ordering.
+---
+--- double Internal method that uses two methods and mixes the
+--- results. The first method is "fast", the other method
+--- computes how much the suggestion sounds like the bad
+--- word. That only works when the language specifies
+--- sound folding. Can be slow and doesn't always give
+--- better results.
+---
+--- fast Internal method that only checks for simple changes:
+--- character inserts/deletes/swaps. Works well for
+--- simple typing mistakes.
+---
+--- {number} The maximum number of suggestions listed for `z=`.
+--- Not used for `spellsuggest()`. The number of
+--- suggestions is never more than the value of 'lines'
+--- minus two.
+---
+--- timeout:{millisec} Limit the time searching for suggestions to
+--- {millisec} milli seconds. Applies to the following
+--- methods. When omitted the limit is 5000. When
+--- negative there is no limit.
+---
+--- file:{filename} Read file {filename}, which must have two columns,
+--- separated by a slash. The first column contains the
+--- bad word, the second column the suggested good word.
+--- Example:
+--- theribal/terrible ~
+--- Use this for common mistakes that do not appear at the
+--- top of the suggestion list with the internal methods.
+--- Lines without a slash are ignored, use this for
+--- comments.
+--- The word in the second column must be correct,
+--- otherwise it will not be used. Add the word to an
+--- ".add" file if it is currently flagged as a spelling
+--- mistake.
+--- The file is used for all languages.
+---
+--- expr:{expr} Evaluate expression {expr}. Use a function to avoid
+--- trouble with spaces. `v:val` holds the badly spelled
+--- word. The expression must evaluate to a List of
+--- Lists, each with a suggestion and a score.
+--- Example:
+--- [['the', 33], ['that', 44]] ~
+--- Set 'verbose' and use `z=` to see the scores that the
+--- internal methods use. A lower score is better.
+--- This may invoke `spellsuggest()` if you temporarily
+--- set 'spellsuggest' to exclude the "expr:" part.
+--- Errors are silently ignored, unless you set the
+--- 'verbose' option to a non-zero value.
+---
+--- Only one of "best", "double" or "fast" may be used. The others may
+--- appear several times in any order. Example:
+--- ```
+--- :set sps=file:~/.config/nvim/sugg,best,expr:MySuggest()
+--- ```
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.spellsuggest = "best"
+vim.o.sps = vim.o.spellsuggest
+vim.go.spellsuggest = vim.o.spellsuggest
+vim.go.sps = vim.go.spellsuggest
+
+--- When on, splitting a window will put the new window below the current
+--- one. `:split`
+---
+--- @type boolean
+vim.o.splitbelow = false
+vim.o.sb = vim.o.splitbelow
+vim.go.splitbelow = vim.o.splitbelow
+vim.go.sb = vim.go.splitbelow
+
+--- The value of this option determines the scroll behavior when opening,
+--- closing or resizing horizontal splits.
+---
+--- Possible values are:
+--- cursor Keep the same relative cursor position.
+--- screen Keep the text on the same screen line.
+--- topline Keep the topline the same.
+---
+--- For the "screen" and "topline" values, the cursor position will be
+--- changed when necessary. In this case, the jumplist will be populated
+--- with the previous cursor position. For "screen", the text cannot always
+--- be kept on the same screen line when 'wrap' is enabled.
+---
+--- @type string
+vim.o.splitkeep = "cursor"
+vim.o.spk = vim.o.splitkeep
+vim.go.splitkeep = vim.o.splitkeep
+vim.go.spk = vim.go.splitkeep
+
+--- When on, splitting a window will put the new window right of the
+--- current one. `:vsplit`
+---
+--- @type boolean
+vim.o.splitright = false
+vim.o.spr = vim.o.splitright
+vim.go.splitright = vim.o.splitright
+vim.go.spr = vim.go.splitright
+
+--- When "on" the commands listed below move the cursor to the first
+--- non-blank of the line. When off the cursor is kept in the same column
+--- (if possible). This applies to the commands:
+--- - CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg"
+--- - "d", "<<" and ">>" with a linewise operator
+--- - "%" with a count
+--- - buffer changing commands (CTRL-^, :bnext, :bNext, etc.)
+--- - Ex commands that only have a line number, e.g., ":25" or ":+".
+--- In case of buffer changing commands the cursor is placed at the column
+--- where it was the last time the buffer was edited.
+---
+--- @type boolean
+vim.o.startofline = false
+vim.o.sol = vim.o.startofline
+vim.go.startofline = vim.o.startofline
+vim.go.sol = vim.go.startofline
+
+--- EXPERIMENTAL
+--- When non-empty, this option determines the content of the area to the
+--- side of a window, normally containing the fold, sign and number columns.
+--- The format of this option is like that of 'statusline'.
+---
+--- Some of the items from the 'statusline' format are different for
+--- 'statuscolumn':
+---
+--- %l line number of currently drawn line
+--- %r relative line number of currently drawn line
+--- %s sign column for currently drawn line
+--- %C fold column for currently drawn line
+---
+--- NOTE: To draw the sign and fold columns, their items must be included in
+--- 'statuscolumn'. Even when they are not included, the status column width
+--- will adapt to the 'signcolumn' and 'foldcolumn' width.
+---
+--- The `v:lnum` variable holds the line number to be drawn.
+--- The `v:relnum` variable holds the relative line number to be drawn.
+--- The `v:virtnum` variable is negative when drawing virtual lines, zero
+--- when drawing the actual buffer line, and positive when
+--- drawing the wrapped part of a buffer line.
+---
+--- NOTE: The %@ click execute function item is supported as well but the
+--- specified function will be the same for each row in the same column.
+--- It cannot be switched out through a dynamic 'statuscolumn' format, the
+--- handler should be written with this in mind.
+---
+--- Examples:
+---
+--- ```vim
+--- " Relative number with bar separator and click handlers:
+--- :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
+---
+--- " Right aligned relative cursor line number:
+--- :let &stc='%=%{v:relnum?v:relnum:v:lnum} '
+---
+--- " Line numbers in hexadecimal for non wrapped part of lines:
+--- :let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
+---
+--- " Human readable line numbers with thousands separator:
+--- :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\'
+--- . '%(\\d\\d\\d\\)\\+$",",","g")}'
+---
+--- " Both relative and absolute line numbers with different
+--- " highlighting for odd and even relative numbers:
+--- :let &stc='%#NonText#%{&nu?v:lnum:""}' .
+--- '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' .
+--- '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}'
+--- ```
+--- WARNING: this expression is evaluated for each screen line so defining
+--- an expensive expression can negatively affect render performance.
+---
+--- @type string
+vim.o.statuscolumn = ""
+vim.o.stc = vim.o.statuscolumn
+vim.wo.statuscolumn = vim.o.statuscolumn
+vim.wo.stc = vim.wo.statuscolumn
+
+--- When non-empty, this option determines the content of the status line.
+--- Also see `status-line`.
+---
+--- The option consists of printf style '%' items interspersed with
+--- normal text. Each status line item is of the form:
+--- %-0{minwid}.{maxwid}{item}
+--- All fields except the {item} are optional. A single percent sign can
+--- be given as "%%".
+---
+--- When the option starts with "%!" then it is used as an expression,
+--- evaluated and the result is used as the option value. Example:
+--- ```
+--- :set statusline=%!MyStatusLine()
+--- ```
+--- The *g:statusline_winid* variable will be set to the `window-ID` of the
+--- window that the status line belongs to.
+--- The result can contain %{} items that will be evaluated too.
+--- Note that the "%!" expression is evaluated in the context of the
+--- current window and buffer, while %{} items are evaluated in the
+--- context of the window that the statusline belongs to.
+---
+--- When there is error while evaluating the option then it will be made
+--- empty to avoid further errors. Otherwise screen updating would loop.
+--- When the result contains unprintable characters the result is
+--- unpredictable.
+---
+--- Note that the only effect of 'ruler' when this option is set (and
+--- 'laststatus' is 2 or 3) is controlling the output of `CTRL-G`.
+---
+--- field meaning ~
+--- - Left justify the item. The default is right justified
+--- when minwid is larger than the length of the item.
+--- 0 Leading zeroes in numeric items. Overridden by "-".
+--- minwid Minimum width of the item, padding as set by "-" & "0".
+--- Value must be 50 or less.
+--- maxwid Maximum width of the item. Truncation occurs with a "<"
+--- on the left for text items. Numeric items will be
+--- shifted down to maxwid-2 digits followed by ">"number
+--- where number is the amount of missing digits, much like
+--- an exponential notation.
+--- item A one letter code as described below.
+---
+--- Following is a description of the possible statusline items. The
+--- second character in "item" is the type:
+--- N for number
+--- S for string
+--- F for flags as described below
+--- - not applicable
+---
+--- item meaning ~
+--- f S Path to the file in the buffer, as typed or relative to current
+--- directory.
+--- F S Full path to the file in the buffer.
+--- t S File name (tail) of file in the buffer.
+--- m F Modified flag, text is "[+]"; "[-]" if 'modifiable' is off.
+--- M F Modified flag, text is ",+" or ",-".
+--- r F Readonly flag, text is "[RO]".
+--- R F Readonly flag, text is ",RO".
+--- h F Help buffer flag, text is "[help]".
+--- H F Help buffer flag, text is ",HLP".
+--- w F Preview window flag, text is "[Preview]".
+--- W F Preview window flag, text is ",PRV".
+--- y F Type of file in the buffer, e.g., "[vim]". See 'filetype'.
+--- Y F Type of file in the buffer, e.g., ",VIM". See 'filetype'.
+--- q S "[Quickfix List]", "[Location List]" or empty.
+--- k S Value of "b:keymap_name" or 'keymap' when `:lmap` mappings are
+--- being used: "<keymap>"
+--- n N Buffer number.
+--- b N Value of character under cursor.
+--- B N As above, in hexadecimal.
+--- o N Byte number in file of byte under cursor, first byte is 1.
+--- Mnemonic: Offset from start of file (with one added)
+--- O N As above, in hexadecimal.
+--- l N Line number.
+--- L N Number of lines in buffer.
+--- c N Column number (byte index).
+--- v N Virtual column number (screen column).
+--- V N Virtual column number as -{num}. Not displayed if equal to 'c'.
+--- p N Percentage through file in lines as in `CTRL-G`.
+--- P S Percentage through file of displayed window. This is like the
+--- percentage described for 'ruler'. Always 3 in length, unless
+--- translated.
+--- S S 'showcmd' content, see 'showcmdloc'.
+--- a S Argument list status as in default title. ({current} of {max})
+--- Empty if the argument file count is zero or one.
+--- { NF Evaluate expression between "%{" and "}" and substitute result.
+--- Note that there is no "%" before the closing "}". The
+--- expression cannot contain a "}" character, call a function to
+--- work around that. See `stl-%{` below.
+--- `{%` - This is almost same as "{" except the result of the expression is
+--- re-evaluated as a statusline format string. Thus if the
+--- return value of expr contains "%" items they will get expanded.
+--- The expression can contain the "}" character, the end of
+--- expression is denoted by "%}".
+--- For example:
+--- ```
+--- func! Stl_filename() abort
+--- return "%t"
+--- endfunc
+--- ```
+--- `stl=%{Stl_filename()}` results in `"%t"`
+--- `stl=%{%Stl_filename()%}` results in `"Name of current file"`
+--- %} - End of "{%" expression
+--- ( - Start of item group. Can be used for setting the width and
+--- alignment of a section. Must be followed by %) somewhere.
+--- ) - End of item group. No width fields allowed.
+--- T N For 'tabline': start of tab page N label. Use %T or %X to end
+--- the label. Clicking this label with left mouse button switches
+--- to the specified tab page.
+--- X N For 'tabline': start of close tab N label. Use %X or %T to end
+--- the label, e.g.: %3Xclose%X. Use %999X for a "close current
+--- tab" label. Clicking this label with left mouse button closes
+--- specified tab page.
+--- @ N Start of execute function label. Use %X or %T to
+--- end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
+--- label runs specified function: in the example when clicking once
+--- using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
+--- ' ')" expression will be run. Function receives the
+--- following arguments in order:
+--- 1. minwid field value or zero if no N was specified
+--- 2. number of mouse clicks to detect multiple clicks
+--- 3. mouse button used: "l", "r" or "m" for left, right or middle
+--- button respectively; one should not rely on third argument
+--- being only "l", "r" or "m": any other non-empty string value
+--- that contains only ASCII lower case letters may be expected
+--- for other mouse buttons
+--- 4. modifiers pressed: string which contains "s" if shift
+--- modifier was pressed, "c" for control, "a" for alt and "m"
+--- for meta; currently if modifier is not pressed string
+--- contains space instead, but one should not rely on presence
+--- of spaces or specific order of modifiers: use `stridx()` to
+--- test whether some modifier is present; string is guaranteed
+--- to contain only ASCII letters and spaces, one letter per
+--- modifier; "?" modifier may also be present, but its presence
+--- is a bug that denotes that new mouse button recognition was
+--- added without modifying code that reacts on mouse clicks on
+--- this label.
+--- Use `getmousepos()`.winid in the specified function to get the
+--- corresponding window id of the clicked item.
+--- \< - Where to truncate line if too long. Default is at the start.
+--- No width fields allowed.
+--- = - Separation point between alignment sections. Each section will
+--- be separated by an equal number of spaces. With one %= what
+--- comes after it will be right-aligned. With two %= there is a
+--- middle part, with white space left and right of it.
+--- No width fields allowed.
+--- # - Set highlight group. The name must follow and then a # again.
+--- Thus use %#HLname# for highlight group HLname. The same
+--- highlighting is used, also for the statusline of non-current
+--- windows.
+--- * - Set highlight group to User{N}, where {N} is taken from the
+--- minwid field, e.g. %1*. Restore normal highlight with %* or %0*.
+--- The difference between User{N} and StatusLine will be applied to
+--- StatusLineNC for the statusline of non-current windows.
+--- The number N must be between 1 and 9. See `hl-User1..9`
+---
+--- When displaying a flag, Vim removes the leading comma, if any, when
+--- that flag comes right after plaintext. This will make a nice display
+--- when flags are used like in the examples below.
+---
+--- When all items in a group becomes an empty string (i.e. flags that are
+--- not set) and a minwid is not set for the group, the whole group will
+--- become empty. This will make a group like the following disappear
+--- completely from the statusline when none of the flags are set.
+--- ```
+--- :set statusline=...%(\ [%M%R%H]%)...
+--- ```
+--- Beware that an expression is evaluated each and every time the status
+--- line is displayed.
+--- *stl-%{* *g:actual_curbuf* *g:actual_curwin*
+--- While evaluating %{} the current buffer and current window will be set
+--- temporarily to that of the window (and buffer) whose statusline is
+--- currently being drawn. The expression will evaluate in this context.
+--- The variable "g:actual_curbuf" is set to the `bufnr()` number of the
+--- real current buffer and "g:actual_curwin" to the `window-ID` of the
+--- real current window. These values are strings.
+---
+--- The 'statusline' option will be evaluated in the `sandbox` if set from
+--- a modeline, see `sandbox-option`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- It is not allowed to change text or jump to another window while
+--- evaluating 'statusline' `textlock`.
+---
+--- If the statusline is not updated when you want it (e.g., after setting
+--- a variable that's used in an expression), you can force an update by
+--- using `:redrawstatus`.
+---
+--- A result of all digits is regarded a number for display purposes.
+--- Otherwise the result is taken as flag text and applied to the rules
+--- described above.
+---
+--- Watch out for errors in expressions. They may render Vim unusable!
+--- If you are stuck, hold down ':' or 'Q' to get a prompt, then quit and
+--- edit your vimrc or whatever with "vim --clean" to get it right.
+---
+--- Examples:
+--- Emulate standard status line with 'ruler' set
+--- ```
+--- :set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+--- ```
+--- Similar, but add ASCII value of char under the cursor (like "ga")
+--- ```
+--- :set statusline=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
+--- ```
+--- Display byte count and byte value, modified flag in red.
+--- ```
+--- :set statusline=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
+--- :hi User1 term=inverse,bold cterm=inverse,bold ctermfg=red
+--- ```
+--- Display a ,GZ flag if a compressed file is loaded
+--- ```
+--- :set statusline=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
+--- ```
+--- In the `:autocmd`'s:
+--- ```
+--- :let b:gzflag = 1
+--- ```
+--- And:
+--- ```
+--- :unlet b:gzflag
+--- ```
+--- And define this function:
+--- ```
+--- :function VarExists(var, val)
+--- : if exists(a:var) | return a:val | else | return '' | endif
+--- :endfunction
+--- ```
+---
+---
+--- @type string
+vim.o.statusline = ""
+vim.o.stl = vim.o.statusline
+vim.wo.statusline = vim.o.statusline
+vim.wo.stl = vim.wo.statusline
+vim.go.statusline = vim.o.statusline
+vim.go.stl = vim.go.statusline
+
+--- Files with these suffixes get a lower priority when multiple files
+--- match a wildcard. See `suffixes`. Commas can be used to separate the
+--- suffixes. Spaces after the comma are ignored. A dot is also seen as
+--- the start of a suffix. To avoid a dot or comma being recognized as a
+--- separator, precede it with a backslash (see `option-backslash` about
+--- including spaces and backslashes).
+--- See 'wildignore' for completely ignoring files.
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- suffixes from the list. This avoids problems when a future version
+--- uses another default.
+---
+--- @type string
+vim.o.suffixes = ".bak,~,.o,.h,.info,.swp,.obj"
+vim.o.su = vim.o.suffixes
+vim.go.suffixes = vim.o.suffixes
+vim.go.su = vim.go.suffixes
+
+--- Comma-separated list of suffixes, which are used when searching for a
+--- file for the "gf", "[I", etc. commands. Example:
+--- ```
+--- :set suffixesadd=.java
+--- ```
+---
+---
+--- @type string
+vim.o.suffixesadd = ""
+vim.o.sua = vim.o.suffixesadd
+vim.bo.suffixesadd = vim.o.suffixesadd
+vim.bo.sua = vim.bo.suffixesadd
+
+--- Use a swapfile for the buffer. This option can be reset when a
+--- swapfile is not wanted for a specific buffer. For example, with
+--- confidential information that even root must not be able to access.
+--- Careful: All text will be in memory:
+--- - Don't use this for big files.
+--- - Recovery will be impossible!
+--- A swapfile will only be present when `'updatecount'` is non-zero and
+--- 'swapfile' is set.
+--- When 'swapfile' is reset, the swap file for the current buffer is
+--- immediately deleted. When 'swapfile' is set, and 'updatecount' is
+--- non-zero, a swap file is immediately created.
+--- Also see `swap-file`.
+--- If you want to open a new buffer without creating a swap file for it,
+--- use the `:noswapfile` modifier.
+--- See 'directory' for where the swap file is created.
+---
+--- This option is used together with 'bufhidden' and 'buftype' to
+--- specify special kinds of buffers. See `special-buffers`.
+---
+--- @type boolean
+vim.o.swapfile = true
+vim.o.swf = vim.o.swapfile
+vim.bo.swapfile = vim.o.swapfile
+vim.bo.swf = vim.bo.swapfile
+
+--- This option controls the behavior when switching between buffers.
+--- This option is checked, when
+--- - jumping to errors with the `quickfix` commands (`:cc`, `:cn`, `:cp`,
+--- etc.).
+--- - jumping to a tag using the `:stag` command.
+--- - opening a file using the `CTRL-W_f` or `CTRL-W_F` command.
+--- - jumping to a buffer using a buffer split command (e.g. `:sbuffer`,
+--- `:sbnext`, or `:sbrewind`).
+--- Possible values (comma-separated list):
+--- useopen If included, jump to the first open window in the
+--- current tab page that contains the specified buffer
+--- (if there is one). Otherwise: Do not examine other
+--- windows.
+--- usetab Like "useopen", but also consider windows in other tab
+--- pages.
+--- split If included, split the current window before loading
+--- a buffer for a `quickfix` command that display errors.
+--- Otherwise: do not split, use current window (when used
+--- in the quickfix window: the previously used window or
+--- split if there is no other window).
+--- vsplit Just like "split" but split vertically.
+--- newtab Like "split", but open a new tab page. Overrules
+--- "split" when both are present.
+--- uselast If included, jump to the previously used window when
+--- jumping to errors with `quickfix` commands.
+---
+--- @type string
+vim.o.switchbuf = "uselast"
+vim.o.swb = vim.o.switchbuf
+vim.go.switchbuf = vim.o.switchbuf
+vim.go.swb = vim.go.switchbuf
+
+--- Maximum column in which to search for syntax items. In long lines the
+--- text after this column is not highlighted and following lines may not
+--- be highlighted correctly, because the syntax state is cleared.
+--- This helps to avoid very slow redrawing for an XML file that is one
+--- long line.
+--- Set to zero to remove the limit.
+---
+--- @type integer
+vim.o.synmaxcol = 3000
+vim.o.smc = vim.o.synmaxcol
+vim.bo.synmaxcol = vim.o.synmaxcol
+vim.bo.smc = vim.bo.synmaxcol
+
+--- When this option is set, the syntax with this name is loaded, unless
+--- syntax highlighting has been switched off with ":syntax off".
+--- Otherwise this option does not always reflect the current syntax (the
+--- b:current_syntax variable does).
+--- This option is most useful in a modeline, for a file which syntax is
+--- not automatically recognized. Example, in an IDL file:
+--- ```
+--- /* vim: set syntax=idl : */
+--- ```
+--- When a dot appears in the value then this separates two filetype
+--- names. Example:
+--- ```
+--- /* vim: set syntax=c.doxygen : */
+--- ```
+--- This will use the "c" syntax first, then the "doxygen" syntax.
+--- Note that the second one must be prepared to be loaded as an addition,
+--- otherwise it will be skipped. More than one dot may appear.
+--- To switch off syntax highlighting for the current file, use:
+--- ```
+--- :set syntax=OFF
+--- ```
+--- To switch syntax highlighting on according to the current value of the
+--- 'filetype' option:
+--- ```
+--- :set syntax=ON
+--- ```
+--- What actually happens when setting the 'syntax' option is that the
+--- Syntax autocommand event is triggered with the value as argument.
+--- This option is not copied to another buffer, independent of the 's' or
+--- 'S' flag in 'cpoptions'.
+--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+---
+--- @type string
+vim.o.syntax = ""
+vim.o.syn = vim.o.syntax
+vim.bo.syntax = vim.o.syntax
+vim.bo.syn = vim.bo.syntax
+
+--- When non-empty, this option determines the content of the tab pages
+--- line at the top of the Vim window. When empty Vim will use a default
+--- tab pages line. See `setting-tabline` for more info.
+---
+--- The tab pages line only appears as specified with the 'showtabline'
+--- option and only when there is no GUI tab line. When 'e' is in
+--- 'guioptions' and the GUI supports a tab line 'guitablabel' is used
+--- instead. Note that the two tab pages lines are very different.
+---
+--- The value is evaluated like with 'statusline'. You can use
+--- `tabpagenr()`, `tabpagewinnr()` and `tabpagebuflist()` to figure out
+--- the text to be displayed. Use "%1T" for the first label, "%2T" for
+--- the second one, etc. Use "%X" items for closing labels.
+---
+--- When changing something that is used in 'tabline' that does not
+--- trigger it to be updated, use `:redrawtabline`.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- Keep in mind that only one of the tab pages is the current one, others
+--- are invisible and you can't jump to their windows.
+---
+--- @type string
+vim.o.tabline = ""
+vim.o.tal = vim.o.tabline
+vim.go.tabline = vim.o.tabline
+vim.go.tal = vim.go.tabline
+
+--- Maximum number of tab pages to be opened by the `-p` command line
+--- argument or the ":tab all" command. `tabpage`
+---
+--- @type integer
+vim.o.tabpagemax = 50
+vim.o.tpm = vim.o.tabpagemax
+vim.go.tabpagemax = vim.o.tabpagemax
+vim.go.tpm = vim.go.tabpagemax
+
+--- Number of spaces that a <Tab> in the file counts for. Also see
+--- 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.
+--- 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
+--- (or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim
+--- will use a mix of tabs and spaces, but typing <Tab> and <BS> will
+--- behave like a tab appears every 4 (or 3) characters.
+--- This is the recommended way, the file will look the same with other
+--- tools and when listing it in a terminal.
+--- 2. Set 'softtabstop' and 'shiftwidth' to whatever you prefer and use
+--- 'expandtab'. This way you will always insert spaces. The
+--- formatting will never be messed up when 'tabstop' is changed (leave
+--- it at 8 just in case). The file will be a bit larger.
+--- You do need to check if no Tabs exist in the file. You can get rid
+--- of them by first setting 'expandtab' and using `%retab!`, making
+--- sure the value of 'tabstop' is set correctly.
+--- 3. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use
+--- 'expandtab'. This way you will always insert spaces. The
+--- formatting will never be messed up when 'tabstop' is changed.
+--- You do need to check if no Tabs exist in the file, just like in the
+--- item just above.
+--- 4. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use a
+--- `modeline` to set these values when editing the file again. Only
+--- works when using Vim to edit the file, other tools assume a tabstop
+--- is worth 8 spaces.
+--- 5. Always set 'tabstop' and 'shiftwidth' to the same value, and
+--- 'noexpandtab'. This should then work (for initial indents only)
+--- for any tabstop setting that people use. It might be nice to have
+--- tabs after the first non-blank inserted as spaces if you do this
+--- though. Otherwise aligned comments will be wrong when 'tabstop' is
+--- changed.
+---
+--- The value of 'tabstop' will be ignored if `'vartabstop'` is set to
+--- anything other than an empty string.
+---
+--- @type integer
+vim.o.tabstop = 8
+vim.o.ts = vim.o.tabstop
+vim.bo.tabstop = vim.o.tabstop
+vim.bo.ts = vim.bo.tabstop
+
+--- When searching for a tag (e.g., for the `:ta` command), Vim can either
+--- use a binary search or a linear search in a tags file. Binary
+--- searching makes searching for a tag a LOT faster, but a linear search
+--- will find more tags if the tags file wasn't properly sorted.
+--- Vim normally assumes that your tags files are sorted, or indicate that
+--- they are not sorted. Only when this is not the case does the
+--- 'tagbsearch' option need to be switched off.
+---
+--- When 'tagbsearch' is on, binary searching is first used in the tags
+--- files. In certain situations, Vim will do a linear search instead for
+--- certain files, or retry all files with a linear search. When
+--- 'tagbsearch' is off, only a linear search is done.
+---
+--- Linear searching is done anyway, for one file, when Vim finds a line
+--- at the start of the file indicating that it's not sorted:
+--- ```
+--- !_TAG_FILE_SORTED 0 /some comment/
+--- ```
+--- [The whitespace before and after the '0' must be a single <Tab>]
+---
+--- When a binary search was done and no match was found in any of the
+--- files listed in 'tags', and case is ignored or a pattern is used
+--- instead of a normal tag name, a retry is done with a linear search.
+--- Tags in unsorted tags files, and matches with different case will only
+--- be found in the retry.
+---
+--- If a tag file indicates that it is case-fold sorted, the second,
+--- linear search can be avoided when case is ignored. Use a value of '2'
+--- in the "!_TAG_FILE_SORTED" line for this. A tag file can be case-fold
+--- sorted with the -f switch to "sort" in most unices, as in the command:
+--- "sort -f -o tags tags". For Universal ctags and Exuberant ctags
+--- version 5.x or higher (at least 5.5) the --sort=foldcase switch can be
+--- used for this as well. Note that case must be folded to uppercase for
+--- this to work.
+---
+--- By default, tag searches are case-sensitive. Case is ignored when
+--- 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is
+--- "ignore".
+--- Also when 'tagcase' is "followscs" and 'smartcase' is set, or
+--- 'tagcase' is "smart", and the pattern contains only lowercase
+--- characters.
+---
+--- When 'tagbsearch' is off, tags searching is slower when a full match
+--- exists, but faster when no full match exists. Tags in unsorted tags
+--- files may only be found with 'tagbsearch' off.
+--- When the tags file is not sorted, or sorted in a wrong way (not on
+--- ASCII byte value), 'tagbsearch' should be off, or the line given above
+--- must be included in the tags file.
+--- This option doesn't affect commands that find all matching tags (e.g.,
+--- command-line completion and ":help").
+---
+--- @type boolean
+vim.o.tagbsearch = true
+vim.o.tbs = vim.o.tagbsearch
+vim.go.tagbsearch = vim.o.tagbsearch
+vim.go.tbs = vim.go.tagbsearch
+
+--- This option specifies how case is handled when searching the tags
+--- file:
+--- followic Follow the 'ignorecase' option
+--- followscs Follow the 'smartcase' and 'ignorecase' options
+--- ignore Ignore case
+--- match Match case
+--- smart Ignore case unless an upper case letter is used
+---
+--- @type string
+vim.o.tagcase = "followic"
+vim.o.tc = vim.o.tagcase
+vim.bo.tagcase = vim.o.tagcase
+vim.bo.tc = vim.bo.tagcase
+vim.go.tagcase = vim.o.tagcase
+vim.go.tc = vim.go.tagcase
+
+--- This option specifies a function to be used to perform tag searches.
+--- The function gets the tag pattern and should return a List of matching
+--- tags. See `tag-function` for an explanation of how to write the
+--- function and an example. The value can be the name of a function, a
+--- `lambda` or a `Funcref`. See `option-value-function` for more
+--- information.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.tagfunc = ""
+vim.o.tfu = vim.o.tagfunc
+vim.bo.tagfunc = vim.o.tagfunc
+vim.bo.tfu = vim.bo.tagfunc
+
+--- If non-zero, tags are significant up to this number of characters.
+---
+--- @type integer
+vim.o.taglength = 0
+vim.o.tl = vim.o.taglength
+vim.go.taglength = vim.o.taglength
+vim.go.tl = vim.go.taglength
+
+--- If on and using a tags file in another directory, file names in that
+--- tags file are relative to the directory where the tags file is.
+---
+--- @type boolean
+vim.o.tagrelative = true
+vim.o.tr = vim.o.tagrelative
+vim.go.tagrelative = vim.o.tagrelative
+vim.go.tr = vim.go.tagrelative
+
+--- Filenames for the tag command, separated by spaces or commas. To
+--- include a space or comma in a file name, precede it with backslashes
+--- (see `option-backslash` about including spaces/commas and backslashes).
+--- When a file name starts with "./", the '.' is replaced with the path
+--- of the current file. But only when the 'd' flag is not included in
+--- 'cpoptions'. Environment variables are expanded `:set_env`. Also see
+--- `tags-option`.
+--- "*", "**" and other wildcards can be used to search for tags files in
+--- a directory tree. See `file-searching`. E.g., "/lib/**/tags" will
+--- find all files named "tags" below "/lib". The filename itself cannot
+--- contain wildcards, it is used as-is. E.g., "/lib/**/tags?" will find
+--- files called "tags?".
+--- The `tagfiles()` function can be used to get a list of the file names
+--- actually used.
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- file names from the list. This avoids problems when a future version
+--- uses another default.
+---
+--- @type string
+vim.o.tags = "./tags;,tags"
+vim.o.tag = vim.o.tags
+vim.bo.tags = vim.o.tags
+vim.bo.tag = vim.bo.tags
+vim.go.tags = vim.o.tags
+vim.go.tag = vim.go.tags
+
+--- When on, the `tagstack` is used normally. When off, a ":tag" or
+--- ":tselect" command with an argument will not push the tag onto the
+--- tagstack. A following ":tag" without an argument, a ":pop" command or
+--- any other command that uses the tagstack will use the unmodified
+--- tagstack, but does change the pointer to the active entry.
+--- Resetting this option is useful when using a ":tag" command in a
+--- mapping which should not change the tagstack.
+---
+--- @type boolean
+vim.o.tagstack = true
+vim.o.tgst = vim.o.tagstack
+vim.go.tagstack = vim.o.tagstack
+vim.go.tgst = vim.go.tagstack
+
+--- The terminal is in charge of Bi-directionality of text (as specified
+--- by Unicode). The terminal is also expected to do the required shaping
+--- that some languages (such as Arabic) require.
+--- Setting this option implies that 'rightleft' will not be set when
+--- 'arabic' is set and the value of 'arabicshape' will be ignored.
+--- Note that setting 'termbidi' has the immediate effect that
+--- 'arabicshape' is ignored, but 'rightleft' isn't changed automatically.
+--- For further details see `arabic.txt`.
+---
+--- @type boolean
+vim.o.termbidi = false
+vim.o.tbidi = vim.o.termbidi
+vim.go.termbidi = vim.o.termbidi
+vim.go.tbidi = vim.go.termbidi
+
+--- Enables 24-bit RGB color in the `TUI`. Uses "gui" `:highlight`
+--- attributes instead of "cterm" attributes. `guifg`
+--- Requires an ISO-8613-3 compatible terminal.
+---
+--- @type boolean
+vim.o.termguicolors = false
+vim.o.tgc = vim.o.termguicolors
+vim.go.termguicolors = vim.o.termguicolors
+vim.go.tgc = vim.go.termguicolors
+
+--- A comma-separated list of options for specifying control characters
+--- to be removed from the text pasted into the terminal window. The
+--- supported values are:
+---
+--- BS Backspace
+---
+--- HT TAB
+---
+--- FF Form feed
+---
+--- ESC Escape
+---
+--- DEL DEL
+---
+--- C0 Other control characters, excluding Line feed and
+--- Carriage return < ' '
+---
+--- C1 Control characters 0x80...0x9F
+---
+--- @type string
+vim.o.termpastefilter = "BS,HT,ESC,DEL"
+vim.o.tpf = vim.o.termpastefilter
+vim.go.termpastefilter = vim.o.termpastefilter
+vim.go.tpf = vim.go.termpastefilter
+
+--- If the host terminal supports it, buffer all screen updates
+--- made during a redraw cycle so that each screen is displayed in
+--- the terminal all at once. This can prevent tearing or flickering
+--- when the terminal updates faster than Nvim can redraw.
+---
+--- @type boolean
+vim.o.termsync = true
+vim.go.termsync = vim.o.termsync
+
+--- Maximum width of text that is being inserted. A longer line will be
+--- broken after white space to get this width. A zero value disables
+--- this.
+--- When 'textwidth' is zero, 'wrapmargin' may be used. See also
+--- 'formatoptions' and `ins-textwidth`.
+--- When 'formatexpr' is set it will be used to break the line.
+---
+--- @type integer
+vim.o.textwidth = 0
+vim.o.tw = vim.o.textwidth
+vim.bo.textwidth = vim.o.textwidth
+vim.bo.tw = vim.bo.textwidth
+
+--- List of file names, separated by commas, that are used to lookup words
+--- for thesaurus completion commands `i_CTRL-X_CTRL-T`. See
+--- `compl-thesaurus`.
+---
+--- This option is not used if 'thesaurusfunc' is set, either for the
+--- buffer or globally.
+---
+--- To include a comma in a file name precede it with a backslash. Spaces
+--- after a comma are ignored, otherwise spaces are included in the file
+--- name. See `option-backslash` about using backslashes. The use of
+--- `:set+=` and `:set-=` is preferred when adding or removing directories
+--- from the list. This avoids problems when a future version uses
+--- another default. Backticks cannot be used in this option for security
+--- reasons.
+---
+--- @type string
+vim.o.thesaurus = ""
+vim.o.tsr = vim.o.thesaurus
+vim.bo.thesaurus = vim.o.thesaurus
+vim.bo.tsr = vim.bo.thesaurus
+vim.go.thesaurus = vim.o.thesaurus
+vim.go.tsr = vim.go.thesaurus
+
+--- This option specifies a function to be used for thesaurus completion
+--- with CTRL-X CTRL-T. `i_CTRL-X_CTRL-T` See `compl-thesaurusfunc`.
+--- The value can be the name of a function, a `lambda` or a `Funcref`.
+--- See `option-value-function` for more information.
+---
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.thesaurusfunc = ""
+vim.o.tsrfu = vim.o.thesaurusfunc
+vim.bo.thesaurusfunc = vim.o.thesaurusfunc
+vim.bo.tsrfu = vim.bo.thesaurusfunc
+vim.go.thesaurusfunc = vim.o.thesaurusfunc
+vim.go.tsrfu = vim.go.thesaurusfunc
+
+--- When on: The tilde command "~" behaves like an operator.
+---
+--- @type boolean
+vim.o.tildeop = false
+vim.o.top = vim.o.tildeop
+vim.go.tildeop = vim.o.tildeop
+vim.go.top = vim.go.tildeop
+
+--- This option and 'timeoutlen' determine the behavior when part of a
+--- mapped key sequence has been received. For example, if <c-f> is
+--- pressed and 'timeout' is set, Nvim will wait 'timeoutlen' milliseconds
+--- for any key that can follow <c-f> in a mapping.
+---
+--- @type boolean
+vim.o.timeout = true
+vim.o.to = vim.o.timeout
+vim.go.timeout = vim.o.timeout
+vim.go.to = vim.go.timeout
+
+--- Time in milliseconds to wait for a mapped sequence to complete.
+---
+--- @type integer
+vim.o.timeoutlen = 1000
+vim.o.tm = vim.o.timeoutlen
+vim.go.timeoutlen = vim.o.timeoutlen
+vim.go.tm = vim.go.timeoutlen
+
+--- When on, the title of the window will be set to the value of
+--- 'titlestring' (if it is not empty), or to:
+--- filename [+=-] (path) - NVIM
+--- Where:
+--- filename the name of the file being edited
+--- - indicates the file cannot be modified, 'ma' off
+--- + indicates the file was modified
+--- = indicates the file is read-only
+--- =+ indicates the file is read-only and modified
+--- (path) is the path of the file being edited
+--- - NVIM the server name `v:servername` or "NVIM"
+---
+--- @type boolean
+vim.o.title = false
+vim.go.title = vim.o.title
+
+--- Gives the percentage of 'columns' to use for the length of the window
+--- title. When the title is longer, only the end of the path name is
+--- shown. A '<' character before the path name is used to indicate this.
+--- Using a percentage makes this adapt to the width of the window. But
+--- it won't work perfectly, because the actual number of characters
+--- available also depends on the font used and other things in the title
+--- bar. When 'titlelen' is zero the full path is used. Otherwise,
+--- values from 1 to 30000 percent can be used.
+--- 'titlelen' is also used for the 'titlestring' option.
+---
+--- @type integer
+vim.o.titlelen = 85
+vim.go.titlelen = vim.o.titlelen
+
+--- If not empty, this option will be used to set the window title when
+--- exiting. Only if 'title' is enabled.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.titleold = ""
+vim.go.titleold = vim.o.titleold
+
+--- When this option is not empty, it will be used for the title of the
+--- window. This happens only when the 'title' option is on.
+---
+--- When this option contains printf-style '%' items, they will be
+--- expanded according to the rules used for 'statusline'.
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- Example:
+--- ```
+--- :auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p")
+--- :set title titlestring=%<%F%=%l/%L-%P titlelen=70
+--- ```
+--- The value of 'titlelen' is used to align items in the middle or right
+--- of the available space.
+--- Some people prefer to have the file name first:
+--- ```
+--- :set titlestring=%t%(\ %M%)%(\ (%{expand(\"%:~:.:h\")})%)%(\ %a%)
+--- ```
+--- Note the use of "%{ }" and an expression to get the path of the file,
+--- without the file name. The "%( %)" constructs are used to add a
+--- separating space only when needed.
+--- NOTE: Use of special characters in 'titlestring' may cause the display
+--- to be garbled (e.g., when it contains a CR or NL character).
+---
+--- @type string
+vim.o.titlestring = ""
+vim.go.titlestring = vim.o.titlestring
+
+--- This option and 'ttimeoutlen' determine the behavior when part of a
+--- key code sequence has been received by the `TUI`.
+---
+--- For example if <Esc> (the \x1b byte) is received and 'ttimeout' is
+--- set, Nvim waits 'ttimeoutlen' milliseconds for the terminal to
+--- complete a key code sequence. If no input arrives before the timeout,
+--- a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
+---
+--- On very slow systems this may fail, causing cursor keys not to work
+--- sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
+--- Nvim will wait for the next character to arrive after an <Esc>.
+---
+--- @type boolean
+vim.o.ttimeout = true
+vim.go.ttimeout = vim.o.ttimeout
+
+--- Time in milliseconds to wait for a key code sequence to complete. Also
+--- used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has
+--- been typed.
+---
+--- @type integer
+vim.o.ttimeoutlen = 50
+vim.o.ttm = vim.o.ttimeoutlen
+vim.go.ttimeoutlen = vim.o.ttimeoutlen
+vim.go.ttm = vim.go.ttimeoutlen
+
+--- List of directory names for undo files, separated with commas.
+--- See 'backupdir' for details of the format.
+--- "." means using the directory of the file. The undo file name for
+--- "file.txt" is ".file.txt.un~".
+--- For other directories the file name is the full path of the edited
+--- file, with path separators replaced with "%".
+--- When writing: The first directory that exists is used. "." always
+--- works, no directories after "." will be used for writing. If none of
+--- the directories exist Nvim will attempt to create the last directory in
+--- the list.
+--- When reading all entries are tried to find an undo file. The first
+--- undo file that exists is used. When it cannot be read an error is
+--- given, no further entry is used.
+--- See `undo-persistence`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- Note that unlike 'directory' and 'backupdir', 'undodir' always acts as
+--- though the trailing slashes are present (see 'backupdir' for what this
+--- means).
+---
+--- @type string
+vim.o.undodir = "$XDG_STATE_HOME/nvim/undo//"
+vim.o.udir = vim.o.undodir
+vim.go.undodir = vim.o.undodir
+vim.go.udir = vim.go.undodir
+
+--- When on, Vim automatically saves undo history to an undo file when
+--- writing a buffer to a file, and restores undo history from the same
+--- file on buffer read.
+--- The directory where the undo file is stored is specified by 'undodir'.
+--- For more information about this feature see `undo-persistence`.
+--- The undo file is not read when 'undoreload' causes the buffer from
+--- before a reload to be saved for undo.
+--- When 'undofile' is turned off the undo file is NOT deleted.
+---
+--- @type boolean
+vim.o.undofile = false
+vim.o.udf = vim.o.undofile
+vim.bo.undofile = vim.o.undofile
+vim.bo.udf = vim.bo.undofile
+
+--- Maximum number of changes that can be undone. Since undo information
+--- is kept in memory, higher numbers will cause more memory to be used.
+--- Nevertheless, a single change can already use a large amount of memory.
+--- Set to 0 for Vi compatibility: One level of undo and "u" undoes
+--- itself:
+--- ```
+--- set ul=0
+--- ```
+--- But you can also get Vi compatibility by including the 'u' flag in
+--- 'cpoptions', and still be able to use CTRL-R to repeat undo.
+--- Also see `undo-two-ways`.
+--- Set to -1 for no undo at all. You might want to do this only for the
+--- current buffer:
+--- ```
+--- setlocal ul=-1
+--- ```
+--- This helps when you run out of memory for a single change.
+---
+--- The local value is set to -123456 when the global value is to be used.
+---
+--- Also see `clear-undo`.
+---
+--- @type integer
+vim.o.undolevels = 1000
+vim.o.ul = vim.o.undolevels
+vim.bo.undolevels = vim.o.undolevels
+vim.bo.ul = vim.bo.undolevels
+vim.go.undolevels = vim.o.undolevels
+vim.go.ul = vim.go.undolevels
+
+--- Save the whole buffer for undo when reloading it. This applies to the
+--- ":e!" command and reloading for when the buffer changed outside of
+--- Vim. `FileChangedShell`
+--- The save only happens when this option is negative or when the number
+--- of lines is smaller than the value of this option.
+--- Set this option to zero to disable undo for a reload.
+---
+--- When saving undo for a reload, any undo file is not read.
+---
+--- Note that this causes the whole buffer to be stored in memory. Set
+--- this option to a lower value if you run out of memory.
+---
+--- @type integer
+vim.o.undoreload = 10000
+vim.o.ur = vim.o.undoreload
+vim.go.undoreload = vim.o.undoreload
+vim.go.ur = vim.go.undoreload
+
+--- After typing this many characters the swap file will be written to
+--- disk. When zero, no swap file will be created at all (see chapter on
+--- recovery `crash-recovery`). 'updatecount' is set to zero by starting
+--- Vim with the "-n" option, see `startup`. When editing in readonly
+--- mode this option will be initialized to 10000.
+--- The swapfile can be disabled per buffer with `'swapfile'`.
+--- When 'updatecount' is set from zero to non-zero, swap files are
+--- created for all buffers that have 'swapfile' set. When 'updatecount'
+--- is set to zero, existing swap files are not deleted.
+--- This option has no meaning in buffers where `'buftype'` is "nofile"
+--- or "nowrite".
+---
+--- @type integer
+vim.o.updatecount = 200
+vim.o.uc = vim.o.updatecount
+vim.go.updatecount = vim.o.updatecount
+vim.go.uc = vim.go.updatecount
+
+--- If this many milliseconds nothing is typed the swap file will be
+--- written to disk (see `crash-recovery`). Also used for the
+--- `CursorHold` autocommand event.
+---
+--- @type integer
+vim.o.updatetime = 4000
+vim.o.ut = vim.o.updatetime
+vim.go.updatetime = vim.o.updatetime
+vim.go.ut = vim.go.updatetime
+
+--- A list of the number of spaces that a <Tab> counts for while editing,
+--- such as inserting a <Tab> or using <BS>. It "feels" like variable-
+--- width <Tab>s are being inserted, while in fact a mixture of spaces
+--- and <Tab>s is used. Tab widths are separated with commas, with the
+--- final value applying to all subsequent tabs.
+---
+--- For example, when editing assembly language files where statements
+--- start in the 9th column and comments in the 41st, it may be useful
+--- to use the following:
+--- ```
+--- :set varsofttabstop=8,32,8
+--- ```
+--- This will set soft tabstops with 8 and 8 + 32 spaces, and 8 more
+--- for every column thereafter.
+---
+--- Note that the value of `'softtabstop'` will be ignored while
+--- 'varsofttabstop' is set.
+---
+--- @type string
+vim.o.varsofttabstop = ""
+vim.o.vsts = vim.o.varsofttabstop
+vim.bo.varsofttabstop = vim.o.varsofttabstop
+vim.bo.vsts = vim.bo.varsofttabstop
+
+--- A list of the number of spaces that a <Tab> in the file counts for,
+--- separated by commas. Each value corresponds to one tab, with the
+--- final value applying to all subsequent tabs. For example:
+--- ```
+--- :set vartabstop=4,20,10,8
+--- ```
+--- This will make the first tab 4 spaces wide, the second 20 spaces,
+--- the third 10 spaces, and all following tabs 8 spaces.
+---
+--- Note that the value of `'tabstop'` will be ignored while 'vartabstop'
+--- is set.
+---
+--- @type string
+vim.o.vartabstop = ""
+vim.o.vts = vim.o.vartabstop
+vim.bo.vartabstop = vim.o.vartabstop
+vim.bo.vts = vim.bo.vartabstop
+
+--- Sets the verbosity level. Also set by `-V` and `:verbose`.
+---
+--- Tracing of options in Lua scripts is activated at level 1; Lua scripts
+--- are not traced with verbose=0, for performance.
+---
+--- If greater than or equal to a given level, Nvim produces the following
+--- messages:
+---
+--- Level Messages ~
+--- ----------------------------------------------------------------------
+--- 1 Lua assignments to options, mappings, etc.
+--- 2 When a file is ":source"'ed, or `shada` file is read or written.
+--- 3 UI info, terminal capabilities.
+--- 4 Shell commands.
+--- 5 Every searched tags file and include file.
+--- 8 Files for which a group of autocommands is executed.
+--- 9 Executed autocommands.
+--- 11 Finding items in a path.
+--- 12 Vimscript function calls.
+--- 13 When an exception is thrown, caught, finished, or discarded.
+--- 14 Anything pending in a ":finally" clause.
+--- 15 Ex commands from a script (truncated at 200 characters).
+--- 16 Ex commands.
+---
+--- If 'verbosefile' is set then the verbose messages are not displayed.
+---
+--- @type integer
+vim.o.verbose = 0
+vim.o.vbs = vim.o.verbose
+vim.go.verbose = vim.o.verbose
+vim.go.vbs = vim.go.verbose
+
+--- When not empty all messages are written in a file with this name.
+--- When the file exists messages are appended.
+--- Writing to the file ends when Vim exits or when 'verbosefile' is made
+--- empty. Writes are buffered, thus may not show up for some time.
+--- Setting 'verbosefile' to a new value is like making it empty first.
+--- The difference with `:redir` is that verbose messages are not
+--- displayed when 'verbosefile' is set.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.verbosefile = ""
+vim.o.vfile = vim.o.verbosefile
+vim.go.verbosefile = vim.o.verbosefile
+vim.go.vfile = vim.go.verbosefile
+
+--- Name of the directory where to store files for `:mkview`.
+--- This option cannot be set from a `modeline` or in the `sandbox`, for
+--- security reasons.
+---
+--- @type string
+vim.o.viewdir = "$XDG_STATE_HOME/nvim/view//"
+vim.o.vdir = vim.o.viewdir
+vim.go.viewdir = vim.o.viewdir
+vim.go.vdir = vim.go.viewdir
+
+--- Changes the effect of the `:mkview` command. It is a comma-separated
+--- list of words. Each word enables saving and restoring something:
+--- word save and restore ~
+--- cursor cursor position in file and in window
+--- curdir local current directory, if set with `:lcd`
+--- folds manually created folds, opened/closed folds and local
+--- fold options
+--- options options and mappings local to a window or buffer (not
+--- global values for local options)
+--- localoptions same as "options"
+--- slash `deprecated` Always enabled. Uses "/" in filenames.
+--- unix `deprecated` Always enabled. Uses "\n" line endings.
+---
+--- @type string
+vim.o.viewoptions = "folds,cursor,curdir"
+vim.o.vop = vim.o.viewoptions
+vim.go.viewoptions = vim.o.viewoptions
+vim.go.vop = vim.go.viewoptions
+
+--- A comma-separated list of these words:
+--- block Allow virtual editing in Visual block mode.
+--- insert Allow virtual editing in Insert mode.
+--- all Allow virtual editing in all modes.
+--- onemore Allow the cursor to move just past the end of the line
+--- none When used as the local value, do not allow virtual
+--- editing even when the global value is set. When used
+--- as the global value, "none" is the same as "".
+--- NONE Alternative spelling of "none".
+---
+--- Virtual editing means that the cursor can be positioned where there is
+--- no actual character. This can be halfway into a tab or beyond the end
+--- of the line. Useful for selecting a rectangle in Visual mode and
+--- editing a table.
+--- "onemore" is not the same, it will only allow moving the cursor just
+--- after the last character of the line. This makes some commands more
+--- consistent. Previously the cursor was always past the end of the line
+--- if the line was empty. But it is far from Vi compatible. It may also
+--- break some plugins or Vim scripts. For example because `l` can move
+--- the cursor after the last character. Use with care!
+--- Using the `$` command will move to the last character in the line, not
+--- past it. This may actually move the cursor to the left!
+--- The `g$` command will move to the end of the screen line.
+--- It doesn't make sense to combine "all" with "onemore", but you will
+--- not get a warning for it.
+--- When combined with other words, "none" is ignored.
+---
+--- @type string
+vim.o.virtualedit = ""
+vim.o.ve = vim.o.virtualedit
+vim.wo.virtualedit = vim.o.virtualedit
+vim.wo.ve = vim.wo.virtualedit
+vim.go.virtualedit = vim.o.virtualedit
+vim.go.ve = vim.go.virtualedit
+
+--- Use visual bell instead of beeping. Also see 'errorbells'.
+---
+--- @type boolean
+vim.o.visualbell = false
+vim.o.vb = vim.o.visualbell
+vim.go.visualbell = vim.o.visualbell
+vim.go.vb = vim.go.visualbell
+
+--- Give a warning message when a shell command is used while the buffer
+--- has been changed.
+---
+--- @type boolean
+vim.o.warn = true
+vim.go.warn = vim.o.warn
+
+--- Allow specified keys that move the cursor left/right to move to the
+--- previous/next line when the cursor is on the first/last character in
+--- the line. Concatenate characters to allow this for these keys:
+--- char key mode ~
+--- b <BS> Normal and Visual
+--- s <Space> Normal and Visual
+--- h "h" Normal and Visual (not recommended)
+--- l "l" Normal and Visual (not recommended)
+--- < <Left> Normal and Visual
+--- > <Right> Normal and Visual
+--- ~ "~" Normal
+--- [ <Left> Insert and Replace
+--- ] <Right> Insert and Replace
+--- For example:
+--- ```
+--- :set ww=<,>,[,]
+--- ```
+--- allows wrap only when cursor keys are used.
+--- When the movement keys are used in combination with a delete or change
+--- operator, the <EOL> also counts for a character. This makes "3h"
+--- different from "3dh" when the cursor crosses the end of a line. This
+--- is also true for "x" and "X", because they do the same as "dl" and
+--- "dh". If you use this, you may also want to use the mapping
+--- ":map <BS> X" to make backspace delete the character in front of the
+--- cursor.
+--- When 'l' is included and it is used after an operator at the end of a
+--- line (not an empty line) then it will not move to the next line. This
+--- makes "dl", "cl", "yl" etc. work normally.
+---
+--- @type string
+vim.o.whichwrap = "b,s"
+vim.o.ww = vim.o.whichwrap
+vim.go.whichwrap = vim.o.whichwrap
+vim.go.ww = vim.go.whichwrap
+
+--- Character you have to type to start wildcard expansion in the
+--- command-line, as specified with 'wildmode'.
+--- More info here: `cmdline-completion`.
+--- The character is not recognized when used inside a macro. See
+--- 'wildcharm' for that.
+--- Some keys will not work, such as CTRL-C, <CR> and Enter.
+--- <Esc> can be used, but hitting it twice in a row will still exit
+--- command-line as a failsafe measure.
+--- Although 'wc' is a number option, you can set it to a special key:
+--- ```
+--- :set wc=<Tab>
+--- ```
+---
+---
+--- @type integer
+vim.o.wildchar = 9
+vim.o.wc = vim.o.wildchar
+vim.go.wildchar = vim.o.wildchar
+vim.go.wc = vim.go.wildchar
+
+--- 'wildcharm' works exactly like 'wildchar', except that it is
+--- recognized when used inside a macro. You can find "spare" command-line
+--- keys suitable for this option by looking at `ex-edit-index`. Normally
+--- you'll never actually type 'wildcharm', just use it in mappings that
+--- automatically invoke completion mode, e.g.:
+--- ```
+--- :set wcm=<C-Z>
+--- :cnoremap ss so $vim/sessions/*.vim<C-Z>
+--- ```
+--- Then after typing :ss you can use CTRL-P & CTRL-N.
+---
+--- @type integer
+vim.o.wildcharm = 0
+vim.o.wcm = vim.o.wildcharm
+vim.go.wildcharm = vim.o.wildcharm
+vim.go.wcm = vim.go.wildcharm
+
+--- A list of file patterns. A file that matches with one of these
+--- patterns is ignored when expanding `wildcards`, completing file or
+--- directory names, and influences the result of `expand()`, `glob()` and
+--- `globpath()` unless a flag is passed to disable this.
+--- The pattern is used like with `:autocmd`, see `autocmd-pattern`.
+--- Also see 'suffixes'.
+--- Example:
+--- ```
+--- :set wildignore=*.o,*.obj
+--- ```
+--- The use of `:set+=` and `:set-=` is preferred when adding or removing
+--- a pattern from the list. This avoids problems when a future version
+--- uses another default.
+---
+--- @type string
+vim.o.wildignore = ""
+vim.o.wig = vim.o.wildignore
+vim.go.wildignore = vim.o.wildignore
+vim.go.wig = vim.go.wildignore
+
+--- When set case is ignored when completing file names and directories.
+--- Has no effect when 'fileignorecase' is set.
+--- Does not apply when the shell is used to expand wildcards, which
+--- happens when there are special characters.
+---
+--- @type boolean
+vim.o.wildignorecase = false
+vim.o.wic = vim.o.wildignorecase
+vim.go.wildignorecase = vim.o.wildignorecase
+vim.go.wic = vim.go.wildignorecase
+
+--- When 'wildmenu' is on, command-line completion operates in an enhanced
+--- mode. On pressing 'wildchar' (usually <Tab>) to invoke completion,
+--- the possible matches are shown.
+--- When 'wildoptions' contains "pum", then the completion matches are
+--- shown in a popup menu. Otherwise they are displayed just above the
+--- command line, with the first match highlighted (overwriting the status
+--- line, if there is one).
+--- Keys that show the previous/next match, such as <Tab> or
+--- CTRL-P/CTRL-N, cause the highlight to move to the appropriate match.
+--- 'wildmode' must specify "full": "longest" and "list" do not start
+--- 'wildmenu' mode. You can check the current mode with `wildmenumode()`.
+--- The menu is cancelled when a key is hit that is not used for selecting
+--- a completion.
+---
+--- While the menu is active these keys have special meanings:
+--- CTRL-P - go to the previous entry
+--- CTRL-N - go to the next entry
+--- <Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
+--- <PageUp> - select a match several entries back
+--- <PageDown> - select a match several entries further
+--- <Up> - in filename/menu name completion: move up into
+--- parent directory or parent menu.
+--- <Down> - in filename/menu name completion: move into a
+--- subdirectory or submenu.
+--- <CR> - in menu completion, when the cursor is just after a
+--- dot: move into a submenu.
+--- CTRL-E - end completion, go back to what was there before
+--- selecting a match.
+--- CTRL-Y - accept the currently selected match and stop
+--- completion.
+---
+--- If you want <Left> and <Right> to move the cursor instead of selecting
+--- a different match, use this:
+--- ```
+--- :cnoremap <Left> <Space><BS><Left>
+--- :cnoremap <Right> <Space><BS><Right>
+--- ```
+---
+--- `hl-WildMenu` highlights the current match.
+---
+--- @type boolean
+vim.o.wildmenu = true
+vim.o.wmnu = vim.o.wildmenu
+vim.go.wildmenu = vim.o.wildmenu
+vim.go.wmnu = vim.go.wildmenu
+
+--- Completion mode that is used for the character specified with
+--- 'wildchar'. It is a comma-separated list of up to four parts. Each
+--- part specifies what to do for each consecutive use of 'wildchar'. The
+--- first part specifies the behavior for the first use of 'wildchar',
+--- The second part for the second use, etc.
+---
+--- Each part consists of a colon separated list consisting of the
+--- following possible values:
+--- "" Complete only the first match.
+--- "full" Complete the next full match. After the last match,
+--- the original string is used and then the first match
+--- again. Will also start 'wildmenu' if it is enabled.
+--- "longest" Complete till longest common string. If this doesn't
+--- result in a longer string, use the next part.
+--- "list" When more than one match, list all matches.
+--- "lastused" When completing buffer names and more than one buffer
+--- matches, sort buffers by time last used (other than
+--- the current buffer).
+--- When there is only a single match, it is fully completed in all cases.
+---
+--- Examples of useful colon-separated values:
+--- "longest:full" Like "longest", but also start 'wildmenu' if it is
+--- enabled. Will not complete to the next full match.
+--- "list:full" When more than one match, list all matches and
+--- complete first match.
+--- "list:longest" When more than one match, list all matches and
+--- complete till longest common string.
+--- "list:lastused" When more than one buffer matches, list all matches
+--- and sort buffers by time last used (other than the
+--- current buffer).
+---
+--- Examples:
+--- ```
+--- :set wildmode=full
+--- ```
+--- Complete first full match, next match, etc. (the default)
+--- ```
+--- :set wildmode=longest,full
+--- ```
+--- Complete longest common string, then each full match
+--- ```
+--- :set wildmode=list:full
+--- ```
+--- List all matches and complete each full match
+--- ```
+--- :set wildmode=list,full
+--- ```
+--- List all matches without completing, then each full match
+--- ```
+--- :set wildmode=longest,list
+--- ```
+--- Complete longest common string, then list alternatives.
+--- More info here: `cmdline-completion`.
+---
+--- @type string
+vim.o.wildmode = "full"
+vim.o.wim = vim.o.wildmode
+vim.go.wildmode = vim.o.wildmode
+vim.go.wim = vim.go.wildmode
+
+--- A list of words that change how `cmdline-completion` is done.
+--- The following values are supported:
+--- fuzzy Use `fuzzy-matching` to find completion matches. When
+--- this value is specified, wildcard expansion will not
+--- be used for completion. The matches will be sorted by
+--- the "best match" rather than alphabetically sorted.
+--- This will find more matches than the wildcard
+--- expansion. Currently fuzzy matching based completion
+--- is not supported for file and directory names and
+--- instead wildcard expansion is used.
+--- pum Display the completion matches using the popup menu
+--- in the same style as the `ins-completion-menu`.
+--- tagfile When using CTRL-D to list matching tags, the kind of
+--- tag and the file of the tag is listed. Only one match
+--- is displayed per line. Often used tag kinds are:
+--- d #define
+--- f function
+---
+--- @type string
+vim.o.wildoptions = "pum,tagfile"
+vim.o.wop = vim.o.wildoptions
+vim.go.wildoptions = vim.o.wildoptions
+vim.go.wop = vim.go.wildoptions
+
+--- only used in Win32
+--- Some GUI versions allow the access to menu entries by using the ALT
+--- key in combination with a character that appears underlined in the
+--- menu. This conflicts with the use of the ALT key for mappings and
+--- entering special characters. This option tells what to do:
+--- no Don't use ALT keys for menus. ALT key combinations can be
+--- mapped, but there is no automatic handling.
+--- yes ALT key handling is done by the windowing system. ALT key
+--- combinations cannot be mapped.
+--- menu Using ALT in combination with a character that is a menu
+--- shortcut key, will be handled by the windowing system. Other
+--- keys can be mapped.
+--- If the menu is disabled by excluding 'm' from 'guioptions', the ALT
+--- key is never used for the menu.
+--- This option is not used for <F10>; on Win32.
+---
+--- @type string
+vim.o.winaltkeys = "menu"
+vim.o.wak = vim.o.winaltkeys
+vim.go.winaltkeys = vim.o.winaltkeys
+vim.go.wak = vim.go.winaltkeys
+
+--- When non-empty, this option enables the window bar and determines its
+--- contents. The window bar is a bar that's shown at the top of every
+--- window with it enabled. The value of 'winbar' is evaluated like with
+--- 'statusline'.
+---
+--- When changing something that is used in 'winbar' that does not trigger
+--- it to be updated, use `:redrawstatus`.
+---
+--- Floating windows do not use the global value of 'winbar'. The
+--- window-local value of 'winbar' must be set for a floating window to
+--- have a window bar.
+---
+--- This option cannot be set in a modeline when 'modelineexpr' is off.
+---
+--- @type string
+vim.o.winbar = ""
+vim.o.wbr = vim.o.winbar
+vim.wo.winbar = vim.o.winbar
+vim.wo.wbr = vim.wo.winbar
+vim.go.winbar = vim.o.winbar
+vim.go.wbr = vim.go.winbar
+
+--- Enables pseudo-transparency for a floating window. Valid values are in
+--- the range of 0 for fully opaque window (disabled) to 100 for fully
+--- transparent background. Values between 0-30 are typically most useful.
+---
+--- UI-dependent. Works best with RGB colors. 'termguicolors'
+---
+--- @type integer
+vim.o.winblend = 0
+vim.o.winbl = vim.o.winblend
+vim.wo.winblend = vim.o.winblend
+vim.wo.winbl = vim.wo.winblend
+
+--- Window height used for `CTRL-F` and `CTRL-B` when there is only one
+--- window and the value is smaller than 'lines' minus one. The screen
+--- will scroll 'window' minus two lines, with a minimum of one.
+--- When 'window' is equal to 'lines' minus one CTRL-F and CTRL-B scroll
+--- in a much smarter way, taking care of wrapping lines.
+--- When resizing the Vim window, the value is smaller than 1 or more than
+--- or equal to 'lines' it will be set to 'lines' minus 1.
+--- Note: Do not confuse this with the height of the Vim window, use
+--- 'lines' for that.
+---
+--- @type integer
+vim.o.window = 0
+vim.o.wi = vim.o.window
+vim.go.window = vim.o.window
+vim.go.wi = vim.go.window
+
+--- Keep the window height when windows are opened or closed and
+--- 'equalalways' is set. Also for `CTRL-W_=`. Set by default for the
+--- `preview-window` and `quickfix-window`.
+--- The height may be changed anyway when running out of room.
+---
+--- @type boolean
+vim.o.winfixheight = false
+vim.o.wfh = vim.o.winfixheight
+vim.wo.winfixheight = vim.o.winfixheight
+vim.wo.wfh = vim.wo.winfixheight
+
+--- Keep the window width when windows are opened or closed and
+--- 'equalalways' is set. Also for `CTRL-W_=`.
+--- The width may be changed anyway when running out of room.
+---
+--- @type boolean
+vim.o.winfixwidth = false
+vim.o.wfw = vim.o.winfixwidth
+vim.wo.winfixwidth = vim.o.winfixwidth
+vim.wo.wfw = vim.wo.winfixwidth
+
+--- Minimal number of lines for the current window. This is not a hard
+--- minimum, Vim will use fewer lines if there is not enough room. If the
+--- focus goes to a window that is smaller, its size is increased, at the
+--- cost of the height of other windows.
+--- Set 'winheight' to a small number for normal editing.
+--- Set it to 999 to make the current window fill most of the screen.
+--- Other windows will be only 'winminheight' high. This has the drawback
+--- that ":all" will create only two windows. To avoid "vim -o 1 2 3 4"
+--- to create only two windows, set the option after startup is done,
+--- using the `VimEnter` event:
+--- ```
+--- au VimEnter * set winheight=999
+--- ```
+--- Minimum value is 1.
+--- The height is not adjusted after one of the commands that change the
+--- height of the current window.
+--- 'winheight' applies to the current window. Use 'winminheight' to set
+--- the minimal height for other windows.
+---
+--- @type integer
+vim.o.winheight = 1
+vim.o.wh = vim.o.winheight
+vim.go.winheight = vim.o.winheight
+vim.go.wh = vim.go.winheight
+
+--- Window-local highlights. Comma-delimited list of highlight
+--- `group-name` pairs "{hl-from}:{hl-to},..." where each {hl-from} is
+--- a `highlight-groups` item to be overridden by {hl-to} group in
+--- the window.
+---
+--- Note: highlight namespaces take precedence over 'winhighlight'.
+--- See `nvim_win_set_hl_ns()` and `nvim_set_hl()`.
+---
+--- Highlights of vertical separators are determined by the window to the
+--- left of the separator. The 'tabline' highlight of a tabpage is
+--- decided by the last-focused window of the tabpage. Highlights of
+--- the popupmenu are determined by the current window. Highlights in the
+--- message area cannot be overridden.
+---
+--- Example: show a different color for non-current windows:
+--- ```
+--- set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
+--- ```
+---
+---
+--- @type string
+vim.o.winhighlight = ""
+vim.o.winhl = vim.o.winhighlight
+vim.wo.winhighlight = vim.o.winhighlight
+vim.wo.winhl = vim.wo.winhighlight
+
+--- The minimal height of a window, when it's not the current window.
+--- This is a hard minimum, windows will never become smaller.
+--- When set to zero, windows may be "squashed" to zero lines (i.e. just a
+--- status bar) if necessary. They will return to at least one line when
+--- they become active (since the cursor has to have somewhere to go.)
+--- Use 'winheight' to set the minimal height of the current window.
+--- This option is only checked when making a window smaller. Don't use a
+--- large number, it will cause errors when opening more than a few
+--- windows. A value of 0 to 3 is reasonable.
+---
+--- @type integer
+vim.o.winminheight = 1
+vim.o.wmh = vim.o.winminheight
+vim.go.winminheight = vim.o.winminheight
+vim.go.wmh = vim.go.winminheight
+
+--- The minimal width of a window, when it's not the current window.
+--- This is a hard minimum, windows will never become smaller.
+--- When set to zero, windows may be "squashed" to zero columns (i.e. just
+--- a vertical separator) if necessary. They will return to at least one
+--- line when they become active (since the cursor has to have somewhere
+--- to go.)
+--- Use 'winwidth' to set the minimal width of the current window.
+--- This option is only checked when making a window smaller. Don't use a
+--- large number, it will cause errors when opening more than a few
+--- windows. A value of 0 to 12 is reasonable.
+---
+--- @type integer
+vim.o.winminwidth = 1
+vim.o.wmw = vim.o.winminwidth
+vim.go.winminwidth = vim.o.winminwidth
+vim.go.wmw = vim.go.winminwidth
+
+--- Minimal number of columns for the current window. This is not a hard
+--- minimum, Vim will use fewer columns if there is not enough room. If
+--- the current window is smaller, its size is increased, at the cost of
+--- the width of other windows. Set it to 999 to make the current window
+--- always fill the screen. Set it to a small number for normal editing.
+--- The width is not adjusted after one of the commands to change the
+--- width of the current window.
+--- 'winwidth' applies to the current window. Use 'winminwidth' to set
+--- the minimal width for other windows.
+---
+--- @type integer
+vim.o.winwidth = 20
+vim.o.wiw = vim.o.winwidth
+vim.go.winwidth = vim.o.winwidth
+vim.go.wiw = vim.go.winwidth
+
+--- This option changes how text is displayed. It doesn't change the text
+--- in the buffer, see 'textwidth' for that.
+--- When on, lines longer than the width of the window will wrap and
+--- displaying continues on the next line. When off lines will not wrap
+--- and only part of long lines will be displayed. When the cursor is
+--- moved to a part that is not shown, the screen will scroll
+--- horizontally.
+--- The line will be broken in the middle of a word if necessary. See
+--- 'linebreak' to get the break at a word boundary.
+--- To make scrolling horizontally a bit more useful, try this:
+--- ```
+--- :set sidescroll=5
+--- :set listchars+=precedes:<,extends:>
+--- ```
+--- See 'sidescroll', 'listchars' and `wrap-off`.
+--- This option can't be set from a `modeline` when the 'diff' option is
+--- on.
+---
+--- @type boolean
+vim.o.wrap = true
+vim.wo.wrap = vim.o.wrap
+
+--- Number of characters from the right window border where wrapping
+--- starts. When typing text beyond this limit, an <EOL> will be inserted
+--- and inserting continues on the next line.
+--- Options that add a margin, such as 'number' and 'foldcolumn', cause
+--- the text width to be further reduced.
+--- When 'textwidth' is non-zero, this option is not used.
+--- See also 'formatoptions' and `ins-textwidth`.
+---
+--- @type integer
+vim.o.wrapmargin = 0
+vim.o.wm = vim.o.wrapmargin
+vim.bo.wrapmargin = vim.o.wrapmargin
+vim.bo.wm = vim.bo.wrapmargin
+
+--- Searches wrap around the end of the file. Also applies to `]s` and
+--- `[s`, searching for spelling mistakes.
+---
+--- @type boolean
+vim.o.wrapscan = true
+vim.o.ws = vim.o.wrapscan
+vim.go.wrapscan = vim.o.wrapscan
+vim.go.ws = vim.go.wrapscan
+
+--- Allows writing files. When not set, writing a file is not allowed.
+--- Can be used for a view-only mode, where modifications to the text are
+--- still allowed. Can be reset with the `-m` or `-M` command line
+--- argument. Filtering text is still possible, even though this requires
+--- writing a temporary file.
+---
+--- @type boolean
+vim.o.write = true
+vim.go.write = vim.o.write
+
+--- Allows writing to any file with no need for "!" override.
+---
+--- @type boolean
+vim.o.writeany = false
+vim.o.wa = vim.o.writeany
+vim.go.writeany = vim.o.writeany
+vim.go.wa = vim.go.writeany
+
+--- Make a backup before overwriting a file. The backup is removed after
+--- the file was successfully written, unless the 'backup' option is
+--- also on.
+--- WARNING: Switching this option off means that when Vim fails to write
+--- your buffer correctly and then, for whatever reason, Vim exits, you
+--- lose both the original file and what you were writing. Only reset
+--- this option if your file system is almost full and it makes the write
+--- fail (and make sure not to exit Vim until the write was successful).
+--- See `backup-table` for another explanation.
+--- When the 'backupskip' pattern matches, a backup is not made anyway.
+--- Depending on 'backupcopy' the backup is a new file or the original
+--- file renamed (and a new file is written).
+---
+--- @type boolean
+vim.o.writebackup = true
+vim.o.wb = vim.o.writebackup
+vim.go.writebackup = vim.o.writebackup
+vim.go.wb = vim.go.writebackup
+
+--- Only takes effect together with 'redrawdebug'.
+--- The number of milliseconds to wait after each line or each flush
+---
+--- @type integer
+vim.o.writedelay = 0
+vim.o.wd = vim.o.writedelay
+vim.go.writedelay = vim.o.writedelay
+vim.go.wd = vim.go.writedelay
diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua
new file mode 100644
index 0000000000..58aa2be8c2
--- /dev/null
+++ b/runtime/lua/vim/_meta/regex.lua
@@ -0,0 +1,36 @@
+--- @meta
+
+-- luacheck: no unused args
+
+--- @defgroup vim.regex
+---
+--- @brief Vim regexes can be used directly from Lua. Currently they only allow
+--- matching within a single line.
+
+--- Parse the Vim regex {re} and return a regex object. Regexes are "magic"
+--- and case-sensitive by default, regardless of 'magic' and 'ignorecase'.
+--- They can be controlled with flags, see |/magic| and |/ignorecase|.
+--- @param re string
+--- @return vim.regex
+function vim.regex(re) end
+
+--- @class vim.regex
+local regex = {} -- luacheck: no unused
+
+--- Match the string against the regex. If the string should match the regex
+--- precisely, surround the regex with `^` and `$`. If there was a match, the
+--- byte indices for the beginning and end of the match are returned. When
+--- there is no match, `nil` is returned. Because any integer is "truthy",
+--- `regex:match_str()` can be directly used as a condition in an if-statement.
+--- @param str string
+function regex:match_str(str) end
+
+--- Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end}
+--- are supplied, match only this byte index range. Otherwise see
+--- |regex:match_str()|. If {start} is used, then the returned byte indices
+--- will be relative {start}.
+--- @param bufnr integer
+--- @param line_idx integer
+--- @param start? integer
+--- @param end_? integer
+function regex:match_line(bufnr, line_idx, start, end_) end
diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua
new file mode 100644
index 0000000000..57f2180895
--- /dev/null
+++ b/runtime/lua/vim/_meta/spell.lua
@@ -0,0 +1,32 @@
+--- @meta
+
+-- luacheck: no unused args
+
+--- Check {str} for spelling errors. Similar to the Vimscript function
+--- |spellbadword()|.
+---
+--- Note: The behaviour of this function is dependent on: 'spelllang',
+--- 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be local to
+--- the buffer. Consider calling this with |nvim_buf_call()|.
+---
+--- Example:
+---
+--- ```lua
+--- vim.spell.check("the quik brown fox")
+--- -- =>
+--- -- {
+--- -- {'quik', 'bad', 5}
+--- -- }
+--- ```
+---
+--- @param str string
+--- @return {[1]: string, [2]: string, [3]: string}[]
+--- List of tuples with three items:
+--- - The badly spelled word.
+--- - The type of the spelling error:
+--- "bad" spelling mistake
+--- "rare" rare word
+--- "local" word only valid in another region
+--- "caps" word should start with Capital
+--- - The position in {str} where the word begins.
+function vim.spell.check(str) end
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
new file mode 100644
index 0000000000..05e5b2b871
--- /dev/null
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -0,0 +1,10689 @@
+--- @meta _
+-- THIS FILE IS GENERATED
+-- DO NOT EDIT
+error('Cannot require a meta file')
+
+--- Return the absolute value of {expr}. When {expr} evaluates to
+--- a |Float| abs() returns a |Float|. When {expr} can be
+--- converted to a |Number| abs() returns a |Number|. Otherwise
+--- abs() gives an error message and returns -1.
+--- Examples: >vim
+--- echo abs(1.456)
+--- < 1.456 >vim
+--- echo abs(-5.456)
+--- < 5.456 >vim
+--- echo abs(-4)
+--- < 4
+---
+--- @param expr any
+--- @return number
+function vim.fn.abs(expr) end
+
+--- Return the arc cosine of {expr} measured in radians, as a
+--- |Float| in the range of [0, pi].
+--- {expr} must evaluate to a |Float| or a |Number| in the range
+--- [-1, 1].
+--- Returns NaN if {expr} is outside the range [-1, 1]. Returns
+--- 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo acos(0)
+--- < 1.570796 >vim
+--- echo acos(-0.5)
+--- < 2.094395
+---
+--- @param expr any
+--- @return number
+function vim.fn.acos(expr) end
+
+--- Append the item {expr} to |List| or |Blob| {object}. Returns
+--- the resulting |List| or |Blob|. Examples: >vim
+--- let alist = add([1, 2, 3], item)
+--- call add(mylist, "woodstock")
+--- <Note that when {expr} is a |List| it is appended as a single
+--- item. Use |extend()| to concatenate |Lists|.
+--- When {object} is a |Blob| then {expr} must be a number.
+--- Use |insert()| to add an item at another position.
+--- Returns 1 if {object} is not a |List| or a |Blob|.
+---
+--- @param object any
+--- @param expr any
+--- @return any
+function vim.fn.add(object, expr) end
+
+--- Bitwise AND on the two arguments. The arguments are converted
+--- to a number. A List, Dict or Float argument causes an error.
+--- Also see `or()` and `xor()`.
+--- Example: >vim
+--- let flag = and(bits, 0x80)
+--- <
+---
+--- @param expr any
+--- @param expr1 any
+--- @return integer
+vim.fn['and'] = function(expr, expr1) end
+
+--- Returns Dictionary of |api-metadata|.
+---
+--- View it in a nice human-readable format: >vim
+--- lua vim.print(vim.fn.api_info())
+--- <
+---
+--- @return table
+function vim.fn.api_info() end
+
+--- When {text} is a |List|: Append each item of the |List| as a
+--- text line below line {lnum} in the current buffer.
+--- Otherwise append {text} as one text line below line {lnum} in
+--- the current buffer.
+--- Any type of item is accepted and converted to a String.
+--- {lnum} can be zero to insert a line before the first one.
+--- {lnum} is used like with |getline()|.
+--- Returns 1 for failure ({lnum} out of range or out of memory),
+--- 0 for success. When {text} is an empty list zero is returned,
+--- no matter the value of {lnum}. Example: >vim
+--- let failed = append(line('$'), "# THE END")
+--- let failed = append(0, ["Chapter 1", "the beginning"])
+--- <
+---
+--- @param lnum integer
+--- @param text any
+--- @return 0|1
+function vim.fn.append(lnum, text) end
+
+--- Like |append()| but append the text in buffer {expr}.
+---
+--- This function works only for loaded buffers. First call
+--- |bufload()| if needed.
+---
+--- For the use of {buf}, see |bufname()|.
+---
+--- {lnum} is the line number to append below. Note that using
+--- |line()| would use the current buffer, not the one appending
+--- to. Use "$" to append at the end of the buffer. Other string
+--- values are not supported.
+---
+--- On success 0 is returned, on failure 1 is returned.
+---
+--- If {buf} is not a valid buffer or {lnum} is not valid, an
+--- error message is given. Example: >vim
+--- let failed = appendbufline(13, 0, "# THE START")
+--- <However, when {text} is an empty list then no error is given
+--- for an invalid {lnum}, since {lnum} isn't actually used.
+---
+--- @param buf any
+--- @param lnum integer
+--- @param text string
+--- @return 0|1
+function vim.fn.appendbufline(buf, lnum, text) end
+
+--- The result is the number of files in the argument list. See
+--- |arglist|.
+--- If {winid} is not supplied, the argument list of the current
+--- window is used.
+--- If {winid} is -1, the global argument list is used.
+--- Otherwise {winid} specifies the window of which the argument
+--- list is used: either the window number or the window ID.
+--- Returns -1 if the {winid} argument is invalid.
+---
+--- @param winid? integer
+--- @return integer
+function vim.fn.argc(winid) end
+
+--- The result is the current index in the argument list. 0 is
+--- the first file. argc() - 1 is the last one. See |arglist|.
+---
+--- @return integer
+function vim.fn.argidx() end
+
+--- Return the argument list ID. This is a number which
+--- identifies the argument list being used. Zero is used for the
+--- global argument list. See |arglist|.
+--- Returns -1 if the arguments are invalid.
+---
+--- Without arguments use the current window.
+--- With {winnr} only use this window in the current tab page.
+--- With {winnr} and {tabnr} use the window in the specified tab
+--- page.
+--- {winnr} can be the window number or the |window-ID|.
+---
+--- @param winnr? integer
+--- @param tabnr? integer
+--- @return integer
+function vim.fn.arglistid(winnr, tabnr) end
+
+--- The result is the {nr}th file in the argument list. See
+--- |arglist|. "argv(0)" is the first one. Example: >vim
+--- let i = 0
+--- while i < argc()
+--- let f = escape(fnameescape(argv(i)), '.')
+--- exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>'
+--- let i = i + 1
+--- endwhile
+--- <Without the {nr} argument, or when {nr} is -1, a |List| with
+--- the whole |arglist| is returned.
+---
+--- The {winid} argument specifies the window ID, see |argc()|.
+--- For the Vim command line arguments see |v:argv|.
+---
+--- Returns an empty string if {nr}th argument is not present in
+--- the argument list. Returns an empty List if the {winid}
+--- argument is invalid.
+---
+--- @param nr? integer
+--- @param winid? integer
+--- @return string|string[]
+function vim.fn.argv(nr, winid) end
+
+--- Return the arc sine of {expr} measured in radians, as a |Float|
+--- in the range of [-pi/2, pi/2].
+--- {expr} must evaluate to a |Float| or a |Number| in the range
+--- [-1, 1].
+--- Returns NaN if {expr} is outside the range [-1, 1]. Returns
+--- 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo asin(0.8)
+--- < 0.927295 >vim
+--- echo asin(-0.5)
+--- < -0.523599
+---
+--- @param expr any
+--- @return number
+function vim.fn.asin(expr) end
+
+--- Run {cmd} and add an error message to |v:errors| if it does
+--- NOT produce a beep or visual bell.
+--- Also see |assert_fails()|, |assert_nobeep()| and
+--- |assert-return|.
+---
+--- @param cmd any
+--- @return 0|1
+function vim.fn.assert_beeps(cmd) end
+
+--- When {expected} and {actual} are not equal an error message is
+--- added to |v:errors| and 1 is returned. Otherwise zero is
+--- returned. |assert-return|
+--- The error is in the form "Expected {expected} but got
+--- {actual}". When {msg} is present it is prefixed to that.
+---
+--- There is no automatic conversion, the String "4" is different
+--- from the Number 4. And the number 4 is different from the
+--- Float 4.0. The value of 'ignorecase' is not used here, case
+--- always matters.
+--- Example: >vim
+--- assert_equal('foo', 'bar')
+--- <Will result in a string to be added to |v:errors|:
+--- test.vim line 12: Expected 'foo' but got 'bar' ~
+---
+--- @param expected any
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_equal(expected, actual, msg) end
+
+--- When the files {fname-one} and {fname-two} do not contain
+--- exactly the same text an error message is added to |v:errors|.
+--- Also see |assert-return|.
+--- When {fname-one} or {fname-two} does not exist the error will
+--- mention that.
+---
+--- @return 0|1
+function vim.fn.assert_equalfile() end
+
+--- When v:exception does not contain the string {error} an error
+--- message is added to |v:errors|. Also see |assert-return|.
+--- This can be used to assert that a command throws an exception.
+--- Using the error number, followed by a colon, avoids problems
+--- with translations: >vim
+--- try
+--- commandthatfails
+--- call assert_false(1, 'command should have failed')
+--- catch
+--- call assert_exception('E492:')
+--- endtry
+--- <
+---
+--- @param error any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_exception(error, msg) end
+
+--- Run {cmd} and add an error message to |v:errors| if it does
+--- NOT produce an error or when {error} is not found in the
+--- error message. Also see |assert-return|.
+---
+--- When {error} is a string it must be found literally in the
+--- first reported error. Most often this will be the error code,
+--- including the colon, e.g. "E123:". >vim
+--- assert_fails('bad cmd', 'E987:')
+--- <
+--- When {error} is a |List| with one or two strings, these are
+--- used as patterns. The first pattern is matched against the
+--- first reported error: >vim
+--- assert_fails('cmd', ['E987:.*expected bool'])
+--- <The second pattern, if present, is matched against the last
+--- reported error. To only match the last error use an empty
+--- string for the first error: >vim
+--- assert_fails('cmd', ['', 'E987:'])
+--- <
+--- If {msg} is empty then it is not used. Do this to get the
+--- default message when passing the {lnum} argument.
+---
+--- When {lnum} is present and not negative, and the {error}
+--- argument is present and matches, then this is compared with
+--- the line number at which the error was reported. That can be
+--- the line number in a function or in a script.
+---
+--- When {context} is present it is used as a pattern and matched
+--- against the context (script name or function name) where
+--- {lnum} is located in.
+---
+--- Note that beeping is not considered an error, and some failing
+--- commands only beep. Use |assert_beeps()| for those.
+---
+--- @param cmd any
+--- @param error? any
+--- @param msg? any
+--- @param lnum? integer
+--- @param context? any
+--- @return 0|1
+function vim.fn.assert_fails(cmd, error, msg, lnum, context) end
+
+--- When {actual} is not false an error message is added to
+--- |v:errors|, like with |assert_equal()|.
+--- The error is in the form "Expected False but got {actual}".
+--- When {msg} is present it is prepended to that.
+--- Also see |assert-return|.
+---
+--- A value is false when it is zero. When {actual} is not a
+--- number the assert fails.
+---
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_false(actual, msg) end
+
+--- This asserts number and |Float| values. When {actual} is lower
+--- than {lower} or higher than {upper} an error message is added
+--- to |v:errors|. Also see |assert-return|.
+--- The error is in the form "Expected range {lower} - {upper},
+--- but got {actual}". When {msg} is present it is prefixed to
+--- that.
+---
+--- @param lower any
+--- @param upper any
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_inrange(lower, upper, actual, msg) end
+
+--- When {pattern} does not match {actual} an error message is
+--- added to |v:errors|. Also see |assert-return|.
+--- The error is in the form "Pattern {pattern} does not match
+--- {actual}". When {msg} is present it is prefixed to that.
+---
+--- {pattern} is used as with |expr-=~|: The matching is always done
+--- like 'magic' was set and 'cpoptions' is empty, no matter what
+--- the actual value of 'magic' or 'cpoptions' is.
+---
+--- {actual} is used as a string, automatic conversion applies.
+--- Use "^" and "$" to match with the start and end of the text.
+--- Use both to match the whole text.
+---
+--- Example: >vim
+--- assert_match('^f.*o$', 'foobar')
+--- <Will result in a string to be added to |v:errors|:
+--- test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
+---
+--- @param pattern any
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_match(pattern, actual, msg) end
+
+--- Run {cmd} and add an error message to |v:errors| if it
+--- produces a beep or visual bell.
+--- Also see |assert_beeps()|.
+---
+--- @param cmd any
+--- @return 0|1
+function vim.fn.assert_nobeep(cmd) end
+
+--- The opposite of `assert_equal()`: add an error message to
+--- |v:errors| when {expected} and {actual} are equal.
+--- Also see |assert-return|.
+---
+--- @param expected any
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_notequal(expected, actual, msg) end
+
+--- The opposite of `assert_match()`: add an error message to
+--- |v:errors| when {pattern} matches {actual}.
+--- Also see |assert-return|.
+---
+--- @param pattern any
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_notmatch(pattern, actual, msg) end
+
+--- Report a test failure directly, using String {msg}.
+--- Always returns one.
+---
+--- @param msg any
+--- @return 0|1
+function vim.fn.assert_report(msg) end
+
+--- When {actual} is not true an error message is added to
+--- |v:errors|, like with |assert_equal()|.
+--- Also see |assert-return|.
+--- A value is |TRUE| when it is a non-zero number or |v:true|.
+--- When {actual} is not a number or |v:true| the assert fails.
+--- When {msg} is given it precedes the default message.
+---
+--- @param actual any
+--- @param msg? any
+--- @return 0|1
+function vim.fn.assert_true(actual, msg) end
+
+--- Return the principal value of the arc tangent of {expr}, in
+--- the range [-pi/2, +pi/2] radians, as a |Float|.
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo atan(100)
+--- < 1.560797 >vim
+--- echo atan(-4.01)
+--- < -1.326405
+---
+--- @param expr any
+--- @return number
+function vim.fn.atan(expr) end
+
+--- Return the arc tangent of {expr1} / {expr2}, measured in
+--- radians, as a |Float| in the range [-pi, pi].
+--- {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+--- |Number|.
+--- Examples: >vim
+--- echo atan2(-1, 1)
+--- < -0.785398 >vim
+--- echo atan2(1, -1)
+--- < 2.356194
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return number
+function vim.fn.atan2(expr1, expr2) end
+
+--- Return a List containing the number value of each byte in Blob
+--- {blob}. Examples: >vim
+--- blob2list(0z0102.0304) " returns [1, 2, 3, 4]
+--- blob2list(0z) " returns []
+--- <Returns an empty List on error. |list2blob()| does the
+--- opposite.
+---
+--- @param blob any
+--- @return any[]
+function vim.fn.blob2list(blob) end
+
+--- Put up a file requester. This only works when "has("browse")"
+--- returns |TRUE| (only in some GUI versions).
+--- The input fields are:
+--- {save} when |TRUE|, select file to write
+--- {title} title for the requester
+--- {initdir} directory to start browsing in
+--- {default} default file name
+--- An empty string is returned when the "Cancel" button is hit,
+--- something went wrong, or browsing is not possible.
+---
+--- @param save any
+--- @param title any
+--- @param initdir any
+--- @param default any
+--- @return 0|1
+function vim.fn.browse(save, title, initdir, default) end
+
+--- Put up a directory requester. This only works when
+--- "has("browse")" returns |TRUE| (only in some GUI versions).
+--- On systems where a directory browser is not supported a file
+--- browser is used. In that case: select a file in the directory
+--- to be used.
+--- The input fields are:
+--- {title} title for the requester
+--- {initdir} directory to start browsing in
+--- When the "Cancel" button is hit, something went wrong, or
+--- browsing is not possible, an empty string is returned.
+---
+--- @param title any
+--- @param initdir any
+--- @return 0|1
+function vim.fn.browsedir(title, initdir) end
+
+--- Add a buffer to the buffer list with name {name} (must be a
+--- String).
+--- If a buffer for file {name} already exists, return that buffer
+--- number. Otherwise return the buffer number of the newly
+--- created buffer. When {name} is an empty string then a new
+--- buffer is always created.
+--- The buffer will not have 'buflisted' set and not be loaded
+--- yet. To add some text to the buffer use this: >vim
+--- let bufnr = bufadd('someName')
+--- call bufload(bufnr)
+--- call setbufline(bufnr, 1, ['some', 'text'])
+--- <Returns 0 on error.
+---
+--- @param name string
+--- @return integer
+function vim.fn.bufadd(name) end
+
+--- The result is a Number, which is |TRUE| if a buffer called
+--- {buf} exists.
+--- If the {buf} argument is a number, buffer numbers are used.
+--- Number zero is the alternate buffer for the current window.
+---
+--- If the {buf} argument is a string it must match a buffer name
+--- exactly. The name can be:
+--- - Relative to the current directory.
+--- - A full path.
+--- - The name of a buffer with 'buftype' set to "nofile".
+--- - A URL name.
+--- Unlisted buffers will be found.
+--- Note that help files are listed by their short name in the
+--- output of |:buffers|, but bufexists() requires using their
+--- long name to be able to find them.
+--- bufexists() may report a buffer exists, but to use the name
+--- with a |:buffer| command you may need to use |expand()|. Esp
+--- for MS-Windows 8.3 names in the form "c:\DOCUME~1"
+--- Use "bufexists(0)" to test for the existence of an alternate
+--- file name.
+---
+--- @param buf any
+--- @return 0|1
+function vim.fn.bufexists(buf) end
+
+--- @deprecated
+--- Obsolete name for |bufexists()|.
+---
+--- @param ... any
+--- @return 0|1
+function vim.fn.buffer_exists(...) end
+
+--- @deprecated
+--- Obsolete name for |bufname()|.
+---
+--- @param ... any
+--- @return string
+function vim.fn.buffer_name(...) end
+
+--- @deprecated
+--- Obsolete name for |bufnr()|.
+---
+--- @param ... any
+--- @return integer
+function vim.fn.buffer_number(...) end
+
+--- The result is a Number, which is |TRUE| if a buffer called
+--- {buf} exists and is listed (has the 'buflisted' option set).
+--- The {buf} argument is used like with |bufexists()|.
+---
+--- @param buf any
+--- @return 0|1
+function vim.fn.buflisted(buf) end
+
+--- Ensure the buffer {buf} is loaded. When the buffer name
+--- refers to an existing file then the file is read. Otherwise
+--- the buffer will be empty. If the buffer was already loaded
+--- then there is no change. If the buffer is not related to a
+--- file then no file is read (e.g., when 'buftype' is "nofile").
+--- If there is an existing swap file for the file of the buffer,
+--- there will be no dialog, the buffer will be loaded anyway.
+--- The {buf} argument is used like with |bufexists()|.
+---
+--- @param buf any
+function vim.fn.bufload(buf) end
+
+--- The result is a Number, which is |TRUE| if a buffer called
+--- {buf} exists and is loaded (shown in a window or hidden).
+--- The {buf} argument is used like with |bufexists()|.
+---
+--- @param buf any
+--- @return 0|1
+function vim.fn.bufloaded(buf) end
+
+--- The result is the name of a buffer. Mostly as it is displayed
+--- by the `:ls` command, but not using special names such as
+--- "[No Name]".
+--- If {buf} is omitted the current buffer is used.
+--- If {buf} is a Number, that buffer number's name is given.
+--- Number zero is the alternate buffer for the current window.
+--- If {buf} is a String, it is used as a |file-pattern| to match
+--- with the buffer names. This is always done like 'magic' is
+--- set and 'cpoptions' is empty. When there is more than one
+--- match an empty string is returned.
+--- "" or "%" can be used for the current buffer, "#" for the
+--- alternate buffer.
+--- A full match is preferred, otherwise a match at the start, end
+--- or middle of the buffer name is accepted. If you only want a
+--- full match then put "^" at the start and "$" at the end of the
+--- pattern.
+--- Listed buffers are found first. If there is a single match
+--- with a listed buffer, that one is returned. Next unlisted
+--- buffers are searched for.
+--- If the {buf} is a String, but you want to use it as a buffer
+--- number, force it to be a Number by adding zero to it: >vim
+--- echo bufname("3" + 0)
+--- <If the buffer doesn't exist, or doesn't have a name, an empty
+--- string is returned. >vim
+--- echo bufname("#") " alternate buffer name
+--- echo bufname(3) " name of buffer 3
+--- echo bufname("%") " name of current buffer
+--- echo bufname("file2") " name of buffer where "file2" matches.
+--- <
+---
+--- @param buf? any
+--- @return string
+function vim.fn.bufname(buf) end
+
+--- The result is the number of a buffer, as it is displayed by
+--- the `:ls` command. For the use of {buf}, see |bufname()|
+--- above.
+--- If the buffer doesn't exist, -1 is returned. Or, if the
+--- {create} argument is present and TRUE, a new, unlisted,
+--- buffer is created and its number is returned.
+--- bufnr("$") is the last buffer: >vim
+--- let last_buffer = bufnr("$")
+--- <The result is a Number, which is the highest buffer number
+--- of existing buffers. Note that not all buffers with a smaller
+--- number necessarily exist, because ":bwipeout" may have removed
+--- them. Use bufexists() to test for the existence of a buffer.
+---
+--- @param buf? any
+--- @param create? any
+--- @return integer
+function vim.fn.bufnr(buf, create) end
+
+--- The result is a Number, which is the |window-ID| of the first
+--- window associated with buffer {buf}. For the use of {buf},
+--- see |bufname()| above. If buffer {buf} doesn't exist or
+--- there is no such window, -1 is returned. Example: >vim
+---
+--- echo "A window containing buffer 1 is " .. (bufwinid(1))
+--- <
+--- Only deals with the current tab page. See |win_findbuf()| for
+--- finding more.
+---
+--- @param buf any
+--- @return integer
+function vim.fn.bufwinid(buf) end
+
+--- Like |bufwinid()| but return the window number instead of the
+--- |window-ID|.
+--- If buffer {buf} doesn't exist or there is no such window, -1
+--- is returned. Example: >vim
+---
+--- echo "A window containing buffer 1 is " .. (bufwinnr(1))
+---
+--- <The number can be used with |CTRL-W_w| and ":wincmd w"
+--- |:wincmd|.
+---
+--- @param buf any
+--- @return integer
+function vim.fn.bufwinnr(buf) end
+
+--- Return the line number that contains the character at byte
+--- count {byte} in the current buffer. This includes the
+--- end-of-line character, depending on the 'fileformat' option
+--- for the current buffer. The first character has byte count
+--- one.
+--- Also see |line2byte()|, |go| and |:goto|.
+---
+--- Returns -1 if the {byte} value is invalid.
+---
+--- @param byte any
+--- @return integer
+function vim.fn.byte2line(byte) end
+
+--- Return byte index of the {nr}th character in the String
+--- {expr}. Use zero for the first character, it then returns
+--- zero.
+--- If there are no multibyte characters the returned value is
+--- equal to {nr}.
+--- Composing characters are not counted separately, their byte
+--- length is added to the preceding base character. See
+--- |byteidxcomp()| below for counting composing characters
+--- separately.
+--- When {utf16} is present and TRUE, {nr} is used as the UTF-16
+--- index in the String {expr} instead of as the character index.
+--- The UTF-16 index is the index in the string when it is encoded
+--- with 16-bit words. If the specified UTF-16 index is in the
+--- middle of a character (e.g. in a 4-byte character), then the
+--- byte index of the first byte in the character is returned.
+--- Refer to |string-offset-encoding| for more information.
+--- Example : >vim
+--- echo matchstr(str, ".", byteidx(str, 3))
+--- <will display the fourth character. Another way to do the
+--- same: >vim
+--- let s = strpart(str, byteidx(str, 3))
+--- echo strpart(s, 0, byteidx(s, 1))
+--- <Also see |strgetchar()| and |strcharpart()|.
+---
+--- If there are less than {nr} characters -1 is returned.
+--- If there are exactly {nr} characters the length of the string
+--- in bytes is returned.
+--- See |charidx()| and |utf16idx()| for getting the character and
+--- UTF-16 index respectively from the byte index.
+--- Examples: >vim
+--- echo byteidx('a😊😊', 2) " returns 5
+--- echo byteidx('a😊😊', 2, 1) " returns 1
+--- echo byteidx('a😊😊', 3, 1) " returns 5
+--- <
+---
+--- @param expr any
+--- @param nr integer
+--- @param utf16? any
+--- @return integer
+function vim.fn.byteidx(expr, nr, utf16) end
+
+--- Like byteidx(), except that a composing character is counted
+--- as a separate character. Example: >vim
+--- let s = 'e' .. nr2char(0x301)
+--- echo byteidx(s, 1)
+--- echo byteidxcomp(s, 1)
+--- echo byteidxcomp(s, 2)
+--- <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).
+---
+--- @param expr any
+--- @param nr integer
+--- @param utf16? any
+--- @return integer
+function vim.fn.byteidxcomp(expr, nr, utf16) end
+
+--- Call function {func} with the items in |List| {arglist} as
+--- arguments.
+--- {func} can either be a |Funcref| or the name of a function.
+--- a:firstline and a:lastline are set to the cursor line.
+--- Returns the return value of the called function.
+--- {dict} is for functions with the "dict" attribute. It will be
+--- used to set the local variable "self". |Dictionary-function|
+---
+--- @param func any
+--- @param arglist any
+--- @param dict? any
+--- @return any
+function vim.fn.call(func, arglist, dict) end
+
+--- Return the smallest integral value greater than or equal to
+--- {expr} as a |Float| (round up).
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Examples: >vim
+--- echo ceil(1.456)
+--- < 2.0 >vim
+--- echo ceil(-5.456)
+--- < -5.0 >vim
+--- echo ceil(4.0)
+--- < 4.0
+---
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+---
+--- @param expr any
+--- @return number
+function vim.fn.ceil(expr) end
+
+--- 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
+--- with `"rpc":v:true`) If {stream} is omitted, all streams
+--- are closed. If the channel is a pty, this will then close the
+--- pty master, sending SIGHUP to the job process.
+--- For a socket, there is only one stream, and {stream} should be
+--- omitted.
+---
+--- @param id any
+--- @param stream? any
+--- @return 0|1
+function vim.fn.chanclose(id, stream) end
+
+--- Return the number of the most recent change. This is the same
+--- number as what is displayed with |:undolist| and can be used
+--- with the |:undo| command.
+--- When a change was made it is the number of that change. After
+--- redo it is the number of the redone change. After undo it is
+--- one less than the number of the undone change.
+--- Returns 0 if the undo list is empty.
+---
+--- @return integer
+function vim.fn.changenr() end
+
+--- Send data to channel {id}. For a job, it writes it to the
+--- stdin of the process. For the stdio channel |channel-stdio|,
+--- it writes to Nvim's stdout. Returns the number of bytes
+--- written if the write succeeded, 0 otherwise.
+--- See |channel-bytes| for more information.
+---
+--- {data} may be a string, string convertible, |Blob|, or a list.
+--- If {data} is a list, the items will be joined by newlines; any
+--- newlines in an item will be sent as NUL. To send a final
+--- newline, include a final empty string. Example: >vim
+--- call chansend(id, ["abc", "123\n456", ""])
+--- <will send "abc<NL>123<NUL>456<NL>".
+---
+--- chansend() writes raw data, not RPC messages. If the channel
+--- was created with `"rpc":v:true` then the channel expects RPC
+--- messages, use |rpcnotify()| and |rpcrequest()| instead.
+---
+--- @param id any
+--- @param data any
+--- @return 0|1
+function vim.fn.chansend(id, data) end
+
+--- Return Number value of the first char in {string}.
+--- Examples: >vim
+--- echo char2nr(" ") " returns 32
+--- echo char2nr("ABC") " returns 65
+--- echo char2nr("á") " returns 225
+--- echo char2nr("á"[0]) " returns 195
+--- echo char2nr("\<M-x>") " returns 128
+--- <Non-ASCII characters are always treated as UTF-8 characters.
+--- {utf8} is ignored, it exists only for backwards-compatibility.
+--- A combining character is a separate character.
+--- |nr2char()| does the opposite.
+---
+--- Returns 0 if {string} is not a |String|.
+---
+--- @param string string
+--- @param utf8? any
+--- @return 0|1
+function vim.fn.char2nr(string, utf8) end
+
+--- Return the character class of the first character in {string}.
+--- The character class is one of:
+--- 0 blank
+--- 1 punctuation
+--- 2 word character
+--- 3 emoji
+--- other specific Unicode class
+--- The class is used in patterns and word motions.
+--- Returns 0 if {string} is not a |String|.
+---
+--- @param string string
+--- @return 0|1|2|3|'other'
+function vim.fn.charclass(string) end
+
+--- 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 "여보세요": >vim
+--- echo charcol('.') " returns 3
+--- echo col('.') " returns 7
+---
+--- @param expr any
+--- @param winid? integer
+--- @return integer
+function vim.fn.charcol(expr, winid) end
+
+--- Return the character index of the byte at {idx} in {string}.
+--- The index of the first character is zero.
+--- If there are no multibyte characters the returned value is
+--- equal to {idx}.
+---
+--- When {countcc} is omitted or |FALSE|, then composing characters
+--- are not counted separately, their byte length is added to the
+--- preceding base character.
+--- When {countcc} is |TRUE|, then composing characters are
+--- counted as separate characters.
+---
+--- When {utf16} is present and TRUE, {idx} is used as the UTF-16
+--- index in the String {expr} instead of as the byte index.
+---
+--- Returns -1 if the arguments are invalid or if there are less
+--- than {idx} bytes. If there are exactly {idx} bytes the length
+--- of the string in characters is returned.
+---
+--- An error is given and -1 is returned if the first argument is
+--- not a string, the second argument is not a number or when the
+--- third argument is present and is not zero or one.
+---
+--- See |byteidx()| and |byteidxcomp()| for getting the byte index
+--- from the character index and |utf16idx()| for getting the
+--- UTF-16 index from the character index.
+--- Refer to |string-offset-encoding| for more information.
+--- Examples: >vim
+--- echo charidx('áb́ć', 3) " returns 1
+--- echo charidx('áb́ć', 6, 1) " returns 4
+--- echo charidx('áb́ć', 16) " returns -1
+--- echo charidx('a😊😊', 4, 0, 1) " returns 2
+--- <
+---
+--- @param string string
+--- @param idx integer
+--- @param countcc? any
+--- @param utf16? any
+--- @return integer
+function vim.fn.charidx(string, idx, countcc, utf16) end
+
+--- Change the current working directory to {dir}. The scope of
+--- the directory change depends on the directory of the current
+--- window:
+--- - If the current window has a window-local directory
+--- (|:lcd|), then changes the window local directory.
+--- - Otherwise, if the current tabpage has a local
+--- directory (|:tcd|) then changes the tabpage local
+--- directory.
+--- - Otherwise, changes the global directory.
+--- {dir} must be a String.
+--- If successful, returns the previous working directory. Pass
+--- this to another chdir() to restore the directory.
+--- On failure, returns an empty string.
+---
+--- Example: >vim
+--- let save_dir = chdir(newdir)
+--- if save_dir != ""
+--- " ... do some work
+--- call chdir(save_dir)
+--- endif
+---
+--- @param dir string
+--- @return string
+function vim.fn.chdir(dir) end
+
+--- Get the amount of indent for line {lnum} according the C
+--- indenting rules, as with 'cindent'.
+--- The indent is counted in spaces, the value of 'tabstop' is
+--- relevant. {lnum} is used just like in |getline()|.
+--- When {lnum} is invalid -1 is returned.
+--- See |C-indenting|.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.cindent(lnum) end
+
+--- Clears all matches previously defined for the current window
+--- by |matchadd()| and the |:match| commands.
+--- If {win} is specified, use the window with this number or
+--- window ID instead of the current window.
+---
+--- @param win? any
+function vim.fn.clearmatches(win) end
+
+--- The result is a Number, which is the byte index of the column
+--- position given with {expr}. The accepted positions are:
+--- . the cursor position
+--- $ the end of the cursor line (the result is the
+--- number of bytes in the cursor line plus one)
+--- 'x position of mark x (if the mark is not set, 0 is
+--- returned)
+--- v In Visual mode: the start of the Visual area (the
+--- cursor is the end). When not in Visual mode
+--- returns the cursor position. Differs from |'<| in
+--- that it's updated right away.
+--- Additionally {expr} can be [lnum, col]: a |List| with the line
+--- and column number. Most useful when the column is "$", to get
+--- the last column of a specific line. When "lnum" or "col" is
+--- out of range then col() returns zero.
+--- With the optional {winid} argument the values are obtained for
+--- that window instead of the current window.
+--- To get the line number use |line()|. To get both use
+--- |getpos()|.
+--- 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: >vim
+--- echo col(".") " column of cursor
+--- echo col("$") " length of cursor line plus one
+--- echo col("'t") " column of mark t
+--- echo col("'" .. markname) " column of mark markname
+--- <The first column is 1. Returns 0 if {expr} is invalid or when
+--- the window with ID {winid} is not found.
+--- For an uppercase mark the column may actually be in another
+--- buffer.
+--- For the cursor position, when 'virtualedit' is active, the
+--- column is one higher if the cursor is after the end of the
+--- line. Also, when using a <Cmd> mapping the cursor isn't
+--- moved, this can be used to obtain the column in Insert mode: >vim
+--- imap <F2> <Cmd>echo col(".").."\n"<CR>
+---
+--- @param expr any
+--- @param winid? integer
+--- @return integer
+function vim.fn.col(expr, winid) end
+
+--- Set the matches for Insert mode completion.
+--- Can only be used in Insert mode. You need to use a mapping
+--- with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O
+--- or with an expression mapping.
+--- {startcol} is the byte offset in the line where the completed
+--- text start. The text up to the cursor is the original text
+--- that will be replaced by the matches. Use col('.') for an
+--- empty string. "col('.') - 1" will replace one character by a
+--- match.
+--- {matches} must be a |List|. Each |List| item is one match.
+--- See |complete-items| for the kind of items that are possible.
+--- "longest" in 'completeopt' is ignored.
+--- Note that the after calling this function you need to avoid
+--- inserting anything that would cause completion to stop.
+--- The match can be selected with CTRL-N and CTRL-P as usual with
+--- Insert mode completion. The popup menu will appear if
+--- specified, see |ins-completion-menu|.
+--- Example: >vim
+--- inoremap <F5> <C-R>=ListMonths()<CR>
+---
+--- func ListMonths()
+--- call complete(col('.'), ['January', 'February', 'March',
+--- \ 'April', 'May', 'June', 'July', 'August', 'September',
+--- \ 'October', 'November', 'December'])
+--- return ''
+--- endfunc
+--- <This isn't very useful, but it shows how it works. Note that
+--- an empty string is returned to avoid a zero being inserted.
+---
+--- @param startcol any
+--- @param matches any
+function vim.fn.complete(startcol, matches) end
+
+--- Add {expr} to the list of matches. Only to be used by the
+--- function specified with the 'completefunc' option.
+--- Returns 0 for failure (empty string or out of memory),
+--- 1 when the match was added, 2 when the match was already in
+--- the list.
+--- See |complete-functions| for an explanation of {expr}. It is
+--- the same as one item in the list that 'omnifunc' would return.
+---
+--- @param expr any
+--- @return 0|1|2
+function vim.fn.complete_add(expr) end
+
+--- Check for a key typed while looking for completion matches.
+--- This is to be used when looking for matches takes some time.
+--- Returns |TRUE| when searching for matches is to be aborted,
+--- zero otherwise.
+--- Only to be used by the function specified with the
+--- 'completefunc' option.
+---
+--- @return 0|1
+function vim.fn.complete_check() end
+
+--- Returns a |Dictionary| with information about Insert mode
+--- completion. See |ins-completion|.
+--- The items are:
+--- mode Current completion mode name string.
+--- See |complete_info_mode| for the values.
+--- pum_visible |TRUE| if popup menu is visible.
+--- See |pumvisible()|.
+--- items List of completion matches. Each item is a
+--- dictionary containing the entries "word",
+--- "abbr", "menu", "kind", "info" and "user_data".
+--- See |complete-items|.
+--- selected Selected item index. First index is zero.
+--- Index is -1 if no item is selected (showing
+--- typed text only, or the last completion after
+--- no item is selected when using the <Up> or
+--- <Down> keys)
+--- inserted Inserted string. [NOT IMPLEMENTED YET]
+---
+--- *complete_info_mode*
+--- mode values are:
+--- "" Not in completion mode
+--- "keyword" Keyword completion |i_CTRL-X_CTRL-N|
+--- "ctrl_x" Just pressed CTRL-X |i_CTRL-X|
+--- "scroll" Scrolling with |i_CTRL-X_CTRL-E| or
+--- |i_CTRL-X_CTRL-Y|
+--- "whole_line" Whole lines |i_CTRL-X_CTRL-L|
+--- "files" File names |i_CTRL-X_CTRL-F|
+--- "tags" Tags |i_CTRL-X_CTRL-]|
+--- "path_defines" Definition completion |i_CTRL-X_CTRL-D|
+--- "path_patterns" Include completion |i_CTRL-X_CTRL-I|
+--- "dictionary" Dictionary |i_CTRL-X_CTRL-K|
+--- "thesaurus" Thesaurus |i_CTRL-X_CTRL-T|
+--- "cmdline" Vim Command line |i_CTRL-X_CTRL-V|
+--- "function" User defined completion |i_CTRL-X_CTRL-U|
+--- "omni" Omni completion |i_CTRL-X_CTRL-O|
+--- "spell" Spelling suggestions |i_CTRL-X_s|
+--- "eval" |complete()| completion
+--- "unknown" Other internal modes
+---
+--- If the optional {what} list argument is supplied, then only
+--- the items listed in {what} are returned. Unsupported items in
+--- {what} are silently ignored.
+---
+--- To get the position and size of the popup menu, see
+--- |pum_getpos()|. It's also available in |v:event| during the
+--- |CompleteChanged| event.
+---
+--- Returns an empty |Dictionary| on error.
+---
+--- Examples: >vim
+--- " Get all items
+--- call complete_info()
+--- " Get only 'mode'
+--- call complete_info(['mode'])
+--- " Get only 'mode' and 'pum_visible'
+--- call complete_info(['mode', 'pum_visible'])
+---
+--- @param what? any
+--- @return table
+function vim.fn.complete_info(what) end
+
+--- confirm() offers the user a dialog, from which a choice can be
+--- made. It returns the number of the choice. For the first
+--- choice this is 1.
+---
+--- {msg} is displayed in a dialog with {choices} as the
+--- alternatives. When {choices} is missing or empty, "&OK" is
+--- used (and translated).
+--- {msg} is a String, use '\n' to include a newline. Only on
+--- some systems the string is wrapped when it doesn't fit.
+---
+--- {choices} is a String, with the individual choices separated
+--- by '\n', e.g. >vim
+--- confirm("Save changes?", "&Yes\n&No\n&Cancel")
+--- <The letter after the '&' is the shortcut key for that choice.
+--- Thus you can type 'c' to select "Cancel". The shortcut does
+--- not need to be the first letter: >vim
+--- confirm("file has been modified", "&Save\nSave &All")
+--- <For the console, the first letter of each choice is used as
+--- the default shortcut key. Case is ignored.
+---
+--- The optional {type} String argument gives the type of dialog.
+--- It can be one of these values: "Error", "Question", "Info",
+--- "Warning" or "Generic". Only the first character is relevant.
+--- When {type} is omitted, "Generic" is used.
+---
+--- The optional {type} argument gives the type of dialog. This
+--- is only used for the icon of the Win32 GUI. It can be one of
+--- these values: "Error", "Question", "Info", "Warning" or
+--- "Generic". Only the first character is relevant.
+--- When {type} is omitted, "Generic" is used.
+---
+--- If the user aborts the dialog by pressing <Esc>, CTRL-C,
+--- or another valid interrupt key, confirm() returns 0.
+---
+--- An example: >vim
+--- let choice = confirm("What do you want?",
+--- \ "&Apples\n&Oranges\n&Bananas", 2)
+--- if choice == 0
+--- echo "make up your mind!"
+--- elseif choice == 3
+--- echo "tasteful"
+--- else
+--- echo "I prefer bananas myself."
+--- endif
+--- <In a GUI dialog, buttons are used. The layout of the buttons
+--- depends on the 'v' flag in 'guioptions'. If it is included,
+--- the buttons are always put vertically. Otherwise, confirm()
+--- tries to put the buttons in one horizontal line. If they
+--- don't fit, a vertical layout is used anyway. For some systems
+--- the horizontal layout is always used.
+---
+--- @param msg any
+--- @param choices? any
+--- @param default? any
+--- @param type? any
+--- @return integer
+function vim.fn.confirm(msg, choices, default, type) end
+
+--- Make a copy of {expr}. For Numbers and Strings this isn't
+--- different from using {expr} directly.
+--- When {expr} is a |List| a shallow copy is created. This means
+--- that the original |List| can be changed without changing the
+--- copy, and vice versa. But the items are identical, thus
+--- changing an item changes the contents of both |Lists|.
+--- A |Dictionary| is copied in a similar way as a |List|.
+--- Also see |deepcopy()|.
+---
+--- @param expr any
+--- @return any
+function vim.fn.copy(expr) end
+
+--- Return the cosine of {expr}, measured in radians, as a |Float|.
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo cos(100)
+--- < 0.862319 >vim
+--- echo cos(-4.01)
+--- < -0.646043
+---
+--- @param expr any
+--- @return number
+function vim.fn.cos(expr) end
+
+--- Return the hyperbolic cosine of {expr} as a |Float| in the range
+--- [1, inf].
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo cosh(0.5)
+--- < 1.127626 >vim
+--- echo cosh(-0.5)
+--- < -1.127626
+---
+--- @param expr any
+--- @return number
+function vim.fn.cosh(expr) end
+
+--- Return the number of times an item with value {expr} appears
+--- in |String|, |List| or |Dictionary| {comp}.
+---
+--- If {start} is given then start with the item with this index.
+--- {start} can only be used with a |List|.
+---
+--- When {ic} is given and it's |TRUE| then case is ignored.
+---
+--- When {comp} is a string then the number of not overlapping
+--- occurrences of {expr} is returned. Zero is returned when
+--- {expr} is an empty string.
+---
+--- @param comp any
+--- @param expr any
+--- @param ic? any
+--- @param start? any
+--- @return integer
+function vim.fn.count(comp, expr, ic, start) end
+
+--- Returns a |Dictionary| representing the |context| at {index}
+--- from the top of the |context-stack| (see |context-dict|).
+--- If {index} is not given, it is assumed to be 0 (i.e.: top).
+---
+--- @param index? any
+--- @return table
+function vim.fn.ctxget(index) end
+
+--- Pops and restores the |context| at the top of the
+--- |context-stack|.
+---
+--- @return any
+function vim.fn.ctxpop() end
+
+--- Pushes the current editor state (|context|) on the
+--- |context-stack|.
+--- If {types} is given and is a |List| of |String|s, it specifies
+--- which |context-types| to include in the pushed context.
+--- Otherwise, all context types are included.
+---
+--- @param types? any
+--- @return any
+function vim.fn.ctxpush(types) end
+
+--- 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|).
+--- If {index} is not given, it is assumed to be 0 (i.e.: top).
+---
+--- @param context any
+--- @param index? any
+--- @return any
+function vim.fn.ctxset(context, index) end
+
+--- Returns the size of the |context-stack|.
+---
+--- @return any
+function vim.fn.ctxsize() end
+
+--- @param lnum integer
+--- @param col? integer
+--- @param off? any
+--- @return any
+function vim.fn.cursor(lnum, col, off) end
+
+--- Positions the cursor at the column (byte count) {col} in the
+--- line {lnum}. The first column is one.
+---
+--- When there is one argument {list} this is used as a |List|
+--- with two, three or four item:
+--- [{lnum}, {col}]
+--- [{lnum}, {col}, {off}]
+--- [{lnum}, {col}, {off}, {curswant}]
+--- This is like the return value of |getpos()| or |getcurpos()|,
+--- but without the first item.
+---
+--- To position the cursor using {col} as the character count, use
+--- |setcursorcharpos()|.
+---
+--- Does not change the jumplist.
+--- {lnum} is used like with |getline()|, except that if {lnum} is
+--- zero, the cursor will stay in the current line.
+--- If {lnum} is greater than the number of lines in the buffer,
+--- the cursor will be positioned at the last line in the buffer.
+--- If {col} is greater than the number of bytes in the line,
+--- the cursor will be positioned at the last character in the
+--- line.
+--- If {col} is zero, the cursor will stay in the current column.
+--- If {curswant} is given it is used to set the preferred column
+--- for vertical movement. Otherwise {col} is used.
+---
+--- When 'virtualedit' is used {off} specifies the offset in
+--- screen columns from the start of the character. E.g., a
+--- position within a <Tab> or after the last character.
+--- Returns 0 when the position could be set, -1 otherwise.
+---
+--- @param list any
+--- @return any
+function vim.fn.cursor(list) end
+
+--- Specifically used to interrupt a program being debugged. It
+--- will cause process {pid} to get a SIGTRAP. Behavior for other
+--- processes is undefined. See |terminal-debug|.
+--- (Sends a SIGINT to a process {pid} other than MS-Windows)
+---
+--- Returns |TRUE| if successfully interrupted the program.
+--- Otherwise returns |FALSE|.
+---
+--- @param pid any
+--- @return any
+function vim.fn.debugbreak(pid) end
+
+--- 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
+--- that the original |List| can be changed without changing the
+--- copy, and vice versa. When an item is a |List|, a copy for it
+--- is made, recursively. Thus changing an item in the copy does
+--- not change the contents of the original |List|.
+---
+--- When {noref} is omitted or zero a contained |List| or
+--- |Dictionary| is only copied once. All references point to
+--- this single copy. With {noref} set to 1 every occurrence of a
+--- |List| or |Dictionary| results in a new copy. This also means
+--- that a cyclic reference causes deepcopy() to fail.
+--- *E724*
+--- Nesting is possible up to 100 levels. When there is an item
+--- that refers back to a higher level making a deep copy with
+--- {noref} set to 1 will fail.
+--- Also see |copy()|.
+---
+--- @param expr any
+--- @param noref? any
+--- @return any
+function vim.fn.deepcopy(expr, noref) end
+
+--- Without {flags} or with {flags} empty: Deletes the file by the
+--- name {fname}.
+---
+--- This also works when {fname} is a symbolic link. The symbolic
+--- link itself is deleted, not what it points to.
+---
+--- When {flags} is "d": Deletes the directory by the name
+--- {fname}. This fails when directory {fname} is not empty.
+---
+--- When {flags} is "rf": Deletes the directory by the name
+--- {fname} and everything in it, recursively. BE CAREFUL!
+--- Note: on MS-Windows it is not possible to delete a directory
+--- that is being used.
+---
+--- The result is a Number, which is 0/false if the delete
+--- operation was successful and -1/true when the deletion failed
+--- or partly failed.
+---
+--- @param fname string
+--- @param flags? string
+--- @return integer
+function vim.fn.delete(fname, flags) end
+
+--- 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.
+---
+--- This function works only for loaded buffers. First call
+--- |bufload()| if needed.
+---
+--- For the use of {buf}, see |bufname()| above.
+---
+--- {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}.
+---
+--- @param buf any
+--- @param first any
+--- @param last? any
+--- @return any
+function vim.fn.deletebufline(buf, first, last) end
+
+--- Adds a watcher to a dictionary. A dictionary watcher is
+--- identified by three components:
+---
+--- - A dictionary({dict});
+--- - A key pattern({pattern}).
+--- - A function({callback}).
+---
+--- After this is called, every change on {dict} and on keys
+--- matching {pattern} will result in {callback} being invoked.
+---
+--- For example, to watch all global variables: >vim
+--- silent! call dictwatcherdel(g:, '*', 'OnDictChanged')
+--- function! OnDictChanged(d,k,z)
+--- echomsg string(a:k) string(a:z)
+--- endfunction
+--- call dictwatcheradd(g:, '*', 'OnDictChanged')
+--- <
+--- For now {pattern} only accepts very simple patterns that can
+--- contain a "*" at the end of the string, in which case it will
+--- match every key that begins with the substring before the "*".
+--- That means if "*" is not the last character of {pattern}, only
+--- keys that are exactly equal as {pattern} will be matched.
+---
+--- The {callback} receives three arguments:
+---
+--- - The dictionary being watched.
+--- - The key which changed.
+--- - A dictionary containing the new and old values for the key.
+---
+--- The type of change can be determined by examining the keys
+--- present on the third argument:
+---
+--- - If contains both `old` and `new`, the key was updated.
+--- - If it contains only `new`, the key was added.
+--- - If it contains only `old`, the key was deleted.
+---
+--- This function can be used by plugins to implement options with
+--- validation and parsing logic.
+---
+--- @param dict any
+--- @param pattern any
+--- @param callback any
+--- @return any
+function vim.fn.dictwatcheradd(dict, pattern, callback) end
+
+--- Removes a watcher added with |dictwatcheradd()|. All three
+--- arguments must match the ones passed to |dictwatcheradd()| in
+--- order for the watcher to be successfully deleted.
+---
+--- @param dict any
+--- @param pattern any
+--- @param callback any
+--- @return any
+function vim.fn.dictwatcherdel(dict, pattern, callback) end
+
+--- Returns |TRUE| when autocommands are being executed and the
+--- FileType event has been triggered at least once. Can be used
+--- to avoid triggering the FileType event again in the scripts
+--- that detect the file type. |FileType|
+--- Returns |FALSE| when `:setf FALLBACK` was used.
+--- When editing another file, the counter is reset, thus this
+--- really checks if the FileType event has been triggered for the
+--- current buffer. This allows an autocommand that starts
+--- editing another buffer to set 'filetype' and load a syntax
+--- file.
+---
+--- @return any
+function vim.fn.did_filetype() end
+
+--- Returns the number of filler lines above line {lnum}.
+--- These are the lines that were inserted at this point in
+--- another diff'ed window. These filler lines are shown in the
+--- display but don't exist in the buffer.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+--- Returns 0 if the current window is not in diff mode.
+---
+--- @param lnum integer
+--- @return any
+function vim.fn.diff_filler(lnum) end
+
+--- Returns the highlight ID for diff mode at line {lnum} column
+--- {col} (byte index). When the current line does not have a
+--- diff change zero is returned.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+--- {col} is 1 for the leftmost column, {lnum} is 1 for the first
+--- line.
+--- The highlight ID can be used with |synIDattr()| to obtain
+--- syntax information about the highlighting.
+---
+--- @param lnum integer
+--- @param col integer
+--- @return any
+function vim.fn.diff_hlID(lnum, col) end
+
+--- Return the digraph of {chars}. This should be a string with
+--- exactly two characters. If {chars} are not just two
+--- characters, or the digraph of {chars} does not exist, an error
+--- is given and an empty string is returned.
+---
+--- Also see |digraph_getlist()|.
+---
+--- Examples: >vim
+--- " Get a built-in digraph
+--- echo digraph_get('00') " Returns '∞'
+---
+--- " Get a user-defined digraph
+--- call digraph_set('aa', 'あ')
+--- echo digraph_get('aa') " Returns 'あ'
+--- <
+---
+--- @param chars any
+--- @return any
+function vim.fn.digraph_get(chars) end
+
+--- Return a list of digraphs. If the {listall} argument is given
+--- and it is TRUE, return all digraphs, including the default
+--- digraphs. Otherwise, return only user-defined digraphs.
+---
+--- Also see |digraph_get()|.
+---
+--- Examples: >vim
+--- " Get user-defined digraphs
+--- echo digraph_getlist()
+---
+--- " Get all the digraphs, including default digraphs
+--- echo digraph_getlist(1)
+--- <
+---
+--- @param listall? any
+--- @return any
+function vim.fn.digraph_getlist(listall) end
+
+--- Add digraph {chars} to the list. {chars} must be a string
+--- with two characters. {digraph} is a string with one UTF-8
+--- encoded character. *E1215*
+--- Be careful, composing characters are NOT ignored. This
+--- function is similar to |:digraphs| command, but useful to add
+--- digraphs start with a white space.
+---
+--- The function result is v:true if |digraph| is registered. If
+--- this fails an error message is given and v:false is returned.
+---
+--- If you want to define multiple digraphs at once, you can use
+--- |digraph_setlist()|.
+---
+--- Example: >vim
+--- call digraph_set(' ', 'あ')
+--- <
+--- Can be used as a |method|: >vim
+--- GetString()->digraph_set('あ')
+--- <
+---
+--- @param chars any
+--- @param digraph any
+--- @return any
+function vim.fn.digraph_set(chars, digraph) end
+
+--- Similar to |digraph_set()| but this function can add multiple
+--- digraphs at once. {digraphlist} is a list composed of lists,
+--- where each list contains two strings with {chars} and
+--- {digraph} as in |digraph_set()|. *E1216*
+--- Example: >vim
+--- call digraph_setlist([['aa', 'あ'], ['ii', 'い']])
+--- <
+--- It is similar to the following: >vim
+--- for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']]
+--- call digraph_set(chars, digraph)
+--- endfor
+--- <Except that the function returns after the first error,
+--- following digraphs will not be added.
+---
+--- Can be used as a |method|: >vim
+--- GetList()->digraph_setlist()
+--- <
+---
+--- @param digraphlist any
+--- @return any
+function vim.fn.digraph_setlist(digraphlist) end
+
+--- Return the Number 1 if {expr} is empty, zero otherwise.
+--- - A |List| or |Dictionary| is empty when it does not have any
+--- items.
+--- - A |String| is empty when its length is zero.
+--- - A |Number| and |Float| are empty when their value is zero.
+--- - |v:false| and |v:null| are empty, |v:true| is not.
+--- - A |Blob| is empty when its length is zero.
+---
+--- @param expr any
+--- @return any
+function vim.fn.empty(expr) end
+
+--- Return all of environment variables as dictionary. You can
+--- check if an environment variable exists like this: >vim
+--- echo has_key(environ(), 'HOME')
+--- <Note that the variable name may be CamelCase; to ignore case
+--- use this: >vim
+--- echo index(keys(environ()), 'HOME', 0, 1) != -1
+--- <
+---
+--- @return any
+function vim.fn.environ() end
+
+--- Escape the characters in {chars} that occur in {string} with a
+--- backslash. Example: >vim
+--- echo escape('c:\program files\vim', ' \')
+--- <results in: >
+--- c:\\program\ files\\vim
+--- <Also see |shellescape()| and |fnameescape()|.
+---
+--- @param string string
+--- @param chars any
+--- @return any
+function vim.fn.escape(string, chars) end
+
+--- Evaluate {string} and return the result. Especially useful to
+--- turn the result of |string()| back into the original value.
+--- This works for Numbers, Floats, Strings, Blobs and composites
+--- of them. Also works for |Funcref|s that refer to existing
+--- functions.
+---
+--- @param string string
+--- @return any
+function vim.fn.eval(string) end
+
+--- Returns 1 when inside an event handler. That is that Vim got
+--- interrupted while waiting for the user to type a character,
+--- e.g., when dropping a file on Vim. This means interactive
+--- commands cannot be used. Otherwise zero is returned.
+---
+--- @return any
+function vim.fn.eventhandler() end
+
+--- This function checks if an executable with the name {expr}
+--- exists. {expr} must be the name of the program without any
+--- arguments.
+--- executable() uses the value of $PATH and/or the normal
+--- searchpath for programs. *PATHEXT*
+--- On MS-Windows the ".exe", ".bat", etc. can optionally be
+--- included. Then the extensions in $PATHEXT are tried. Thus if
+--- "foo.exe" does not exist, "foo.exe.bat" can be found. If
+--- $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot
+--- by itself can be used in $PATHEXT to try using the name
+--- without an extension. When 'shell' looks like a Unix shell,
+--- then the name is also tried without adding an extension.
+--- On MS-Windows it only checks if the file exists and is not a
+--- directory, not if it's really executable.
+--- On Windows an executable in the same directory as Vim is
+--- always found (it is added to $PATH at |startup|).
+--- The result is a Number:
+--- 1 exists
+--- 0 does not exist
+--- -1 not implemented on this system
+--- |exepath()| can be used to get the full path of an executable.
+---
+--- @param expr any
+--- @return 0|1|-1
+function vim.fn.executable(expr) end
+
+--- Execute {command} and capture its output.
+--- If {command} is a |String|, returns {command} output.
+--- If {command} is a |List|, returns concatenated outputs.
+--- Line continuations in {command} are not recognized.
+--- Examples: >vim
+--- echo execute('echon "foo"')
+--- < foo >vim
+--- echo execute(['echon "foo"', 'echon "bar"'])
+--- < foobar
+---
+--- The optional {silent} argument can have these values:
+--- "" no `:silent` used
+--- "silent" `:silent` used
+--- "silent!" `:silent!` used
+--- The default is "silent". Note that with "silent!", unlike
+--- `:redir`, error messages are dropped.
+---
+--- To get a list of lines use `split()` on the result: >vim
+--- execute('args')->split("\n")
+---
+--- <This function is not available in the |sandbox|.
+--- Note: If nested, an outer execute() will not observe output of
+--- the inner calls.
+--- Note: Text attributes (highlights) are not captured.
+--- To execute a command in another window than the current one
+--- use `win_execute()`.
+---
+--- @param command string|string[]
+--- @param silent? ''|'silent'|'silent!'
+--- @return string
+function vim.fn.execute(command, silent) end
+
+--- Returns the full path of {expr} if it is an executable and
+--- given as a (partial or full) path or is found in $PATH.
+--- Returns empty string otherwise.
+--- If {expr} starts with "./" the |current-directory| is used.
+---
+--- @param expr any
+--- @return any
+function vim.fn.exepath(expr) end
+
+--- The result is a Number, which is |TRUE| if {expr} is
+--- defined, zero otherwise.
+---
+--- For checking for a supported feature use |has()|.
+--- 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.: >vim
+--- let l = [1, 2, 3]
+--- echo exists("l[5]")
+--- < 0 >vim
+--- 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.
+--- $ENVNAME environment variable (could also be
+--- done by comparing with an empty
+--- string)
+--- `*funcname` built-in function (see |functions|)
+--- or user defined function (see
+--- |user-function|). Also works for a
+--- variable that is a Funcref.
+--- :cmdname Ex command: built-in command, user
+--- command or command modifier |:command|.
+--- Returns:
+--- 1 for match with start of a command
+--- 2 full match with a command
+--- 3 matches several user commands
+--- To check for a supported command
+--- always check the return value to be 2.
+--- :2match The |:2match| command.
+--- :3match The |:3match| command (but you
+--- probably should not use it, it is
+--- reserved for internal usage)
+--- #event autocommand defined for this event
+--- #event#pattern autocommand defined for this event and
+--- pattern (the pattern is taken
+--- literally and compared to the
+--- autocommand patterns character by
+--- character)
+--- #group autocommand group exists
+--- #group#event autocommand defined for this group and
+--- event.
+--- #group#event#pattern
+--- autocommand defined for this group,
+--- event and pattern.
+--- ##event autocommand for this event is
+--- supported.
+---
+--- Examples: >vim
+--- echo exists("&mouse")
+--- echo exists("$HOSTNAME")
+--- echo exists("*strftime")
+--- echo exists("*s:MyFunc")
+--- echo exists("*MyFunc")
+--- echo exists("bufcount")
+--- echo exists(":Make")
+--- echo exists("#CursorHold")
+--- echo exists("#BufReadPre#*.gz")
+--- echo exists("#filetypeindent")
+--- echo exists("#filetypeindent#FileType")
+--- echo exists("#filetypeindent#FileType#*")
+--- echo exists("##ColorScheme")
+--- <There must be no space between the symbol (&/$/*/#) and the
+--- name.
+--- There must be no extra characters after the name, although in
+--- a few cases this is ignored. That may become stricter in the
+--- future, thus don't count on it!
+--- Working example: >vim
+--- echo exists(":make")
+--- <NOT working example: >vim
+--- echo exists(":make install")
+---
+--- <Note that the argument must be a string, not the name of the
+--- variable itself. For example: >vim
+--- echo exists(bufcount)
+--- <This doesn't check for existence of the "bufcount" variable,
+--- but gets the value of "bufcount", and checks if that exists.
+---
+--- @param expr any
+--- @return 0|1
+function vim.fn.exists(expr) end
+
+--- Return the exponential of {expr} as a |Float| in the range
+--- [0, inf].
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo exp(2)
+--- < 7.389056 >vim
+--- echo exp(-1)
+--- < 0.367879
+---
+--- @param expr any
+--- @return any
+function vim.fn.exp(expr) end
+
+--- Expand wildcards and the following special keywords in
+--- {string}. 'wildignorecase' applies.
+---
+--- If {list} is given and it is |TRUE|, a List will be returned.
+--- Otherwise the result is a String and when there are several
+--- matches, they are separated by <NL> characters.
+---
+--- If the expansion fails, the result is an empty string. A name
+--- for a non-existing file is not included, unless {string} does
+--- not start with '%', '#' or '<', see below.
+---
+--- When {string} starts with '%', '#' or '<', the expansion is
+--- done like for the |cmdline-special| variables with their
+--- associated modifiers. Here is a short overview:
+---
+--- % current file name
+--- # alternate file name
+--- #n alternate file name n
+--- <cfile> file name under the cursor
+--- <afile> autocmd file name
+--- <abuf> autocmd buffer number (as a String!)
+--- <amatch> autocmd matched name
+--- <cexpr> C expression under the cursor
+--- <sfile> sourced script file or function name
+--- <slnum> sourced script line number or function
+--- line number
+--- <sflnum> script file line number, also when in
+--- a function
+--- <SID> "<SNR>123_" where "123" is the
+--- current script ID |<SID>|
+--- <script> sourced script file, or script file
+--- where the current function was defined
+--- <stack> call stack
+--- <cword> word under the cursor
+--- <cWORD> WORD under the cursor
+--- <client> the {clientid} of the last received
+--- message
+--- Modifiers:
+--- :p expand to full path
+--- :h head (last path component removed)
+--- :t tail (last path component only)
+--- :r root (one extension removed)
+--- :e extension only
+---
+--- Example: >vim
+--- let &tags = expand("%:p:h") .. "/tags"
+--- <Note that when expanding a string that starts with '%', '#' or
+--- '<', any following text is ignored. This does NOT work: >vim
+--- let doesntwork = expand("%:h.bak")
+--- <Use this: >vim
+--- let doeswork = expand("%:h") .. ".bak"
+--- <Also note that expanding "<cfile>" and others only returns the
+--- referenced file name without further expansion. If "<cfile>"
+--- is "~/.cshrc", you need to do another expand() to have the
+--- "~/" expanded into the path of the home directory: >vim
+--- echo expand(expand("<cfile>"))
+--- <
+--- There cannot be white space between the variables and the
+--- following modifier. The |fnamemodify()| function can be used
+--- to modify normal file names.
+---
+--- When using '%' or '#', and the current or alternate file name
+--- is not defined, an empty string is used. Using "%:p" in a
+--- buffer with no name, results in the current directory, with a
+--- '/' added.
+--- When 'verbose' is set then expanding '%', '#' and <> items
+--- will result in an error message if the argument cannot be
+--- expanded.
+---
+--- When {string} does not start with '%', '#' or '<', it is
+--- expanded like a file name is expanded on the command line.
+--- 'suffixes' and 'wildignore' are used, unless the optional
+--- {nosuf} argument is given and it is |TRUE|.
+--- Names for non-existing files are included. The "**" item can
+--- be used to search in a directory tree. For example, to find
+--- all "README" files in the current directory and below: >vim
+--- echo expand("**/README")
+--- <
+--- expand() can also be used to expand variables and environment
+--- variables that are only known in a shell. But this can be
+--- slow, because a shell may be used to do the expansion. See
+--- |expr-env-expand|.
+--- The expanded variable is still handled like a list of file
+--- names. When an environment variable cannot be expanded, it is
+--- left unchanged. Thus ":echo expand('$FOOBAR')" results in
+--- "$FOOBAR".
+---
+--- See |glob()| for finding existing files. See |system()| for
+--- getting the raw output of an external command.
+---
+--- @param string string
+--- @param nosuf? boolean
+--- @param list? any
+--- @return string|string[]
+function vim.fn.expand(string, nosuf, list) end
+
+--- Expand special items in String {string} like what is done for
+--- an Ex command such as `:edit`. This expands special keywords,
+--- like with |expand()|, and environment variables, anywhere in
+--- {string}. "~user" and "~/path" are only expanded at the
+--- start.
+---
+--- The following items are supported in the {options} Dict
+--- argument:
+--- errmsg If set to TRUE, error messages are displayed
+--- if an error is encountered during expansion.
+--- By default, error messages are not displayed.
+---
+--- Returns the expanded string. If an error is encountered
+--- during expansion, the unmodified {string} is returned.
+---
+--- Example: >vim
+--- echo expandcmd('make %<.o')
+--- < >
+--- make /path/runtime/doc/builtin.o
+--- < >vim
+--- echo expandcmd('make %<.o', {'errmsg': v:true})
+--- <
+---
+--- @param string string
+--- @param options? table
+--- @return any
+function vim.fn.expandcmd(string, options) end
+
+--- {expr1} and {expr2} must be both |Lists| or both
+--- |Dictionaries|.
+---
+--- If they are |Lists|: Append {expr2} to {expr1}.
+--- If {expr3} is given insert the items of {expr2} before the
+--- item with index {expr3} in {expr1}. When {expr3} is zero
+--- insert before the first item. When {expr3} is equal to
+--- len({expr1}) then {expr2} is appended.
+--- Examples: >vim
+--- echo sort(extend(mylist, [7, 5]))
+--- call extend(mylist, [2, 3], 1)
+--- <When {expr1} is the same List as {expr2} then the number of
+--- items copied is equal to the original length of the List.
+--- E.g., when {expr3} is 1 you get N new copies of the first item
+--- (where N is the original length of the List).
+--- Use |add()| to concatenate one item to a list. To concatenate
+--- two lists into a new list use the + operator: >vim
+--- let newlist = [1, 2, 3] + [4, 5]
+--- <
+--- If they are |Dictionaries|:
+--- Add all entries from {expr2} to {expr1}.
+--- If a key exists in both {expr1} and {expr2} then {expr3} is
+--- used to decide what to do:
+--- {expr3} = "keep": keep the value of {expr1}
+--- {expr3} = "force": use the value of {expr2}
+--- {expr3} = "error": give an error message *E737*
+--- When {expr3} is omitted then "force" is assumed.
+---
+--- {expr1} is changed when {expr2} is not empty. If necessary
+--- make a copy of {expr1} first.
+--- {expr2} remains unchanged.
+--- When {expr1} is locked and {expr2} is not empty the operation
+--- fails.
+--- Returns {expr1}. Returns 0 on error.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @param expr3? any
+--- @return any
+function vim.fn.extend(expr1, expr2, expr3) end
+
+--- Like |extend()| but instead of adding items to {expr1} a new
+--- List or Dictionary is created and returned. {expr1} remains
+--- unchanged.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @param expr3? any
+--- @return any
+function vim.fn.extendnew(expr1, expr2, expr3) end
+
+--- Characters in {string} are queued for processing as if they
+--- come from a mapping or were typed by the user.
+---
+--- By default the string is added to the end of the typeahead
+--- buffer, thus if a mapping is still being executed the
+--- characters come after them. Use the 'i' flag to insert before
+--- other characters, they will be executed next, before any
+--- characters from a mapping.
+---
+--- The function does not wait for processing of keys contained in
+--- {string}.
+---
+--- To include special keys into {string}, use double-quotes
+--- and "\..." notation |expr-quote|. For example,
+--- feedkeys("\<CR>") simulates pressing of the <Enter> key. But
+--- feedkeys('\<CR>') pushes 5 characters.
+--- The |<Ignore>| keycode may be used to exit the
+--- wait-for-character without doing anything.
+---
+--- {mode} is a String, which can contain these character flags:
+--- 'm' Remap keys. This is default. If {mode} is absent,
+--- keys are remapped.
+--- 'n' Do not remap keys.
+--- 't' Handle keys as if typed; otherwise they are handled as
+--- if coming from a mapping. This matters for undo,
+--- opening folds, etc.
+--- 'i' Insert the string instead of appending (see above).
+--- 'x' Execute commands until typeahead is empty. This is
+--- similar to using ":normal!". You can call feedkeys()
+--- several times without 'x' and then one time with 'x'
+--- (possibly with an empty {string}) to execute all the
+--- typeahead. Note that when Vim ends in Insert mode it
+--- will behave as if <Esc> is typed, to avoid getting
+--- stuck, waiting for a character to be typed before the
+--- script continues.
+--- Note that if you manage to call feedkeys() while
+--- executing commands, thus calling it recursively, then
+--- all typeahead will be consumed by the last call.
+--- '!' When used with 'x' will not end Insert mode. Can be
+--- used in a test when a timer is set to exit Insert mode
+--- a little later. Useful for testing CursorHoldI.
+---
+--- Return value is always 0.
+---
+--- @param string string
+--- @param mode? string
+--- @return any
+function vim.fn.feedkeys(string, mode) end
+
+--- @deprecated
+--- Obsolete name for |filereadable()|.
+---
+--- @param file string
+--- @return any
+function vim.fn.file_readable(file) end
+
+--- The result is a Number, which is |TRUE| when a file with the
+--- name {file} exists, and can be read. If {file} doesn't exist,
+--- or is a directory, the result is |FALSE|. {file} is any
+--- expression, which is used as a String.
+--- If you don't care about the file being readable you can use
+--- |glob()|.
+--- {file} is used as-is, you may want to expand wildcards first: >vim
+--- echo filereadable('~/.vimrc')
+--- < >
+--- 0
+--- < >vim
+--- echo filereadable(expand('~/.vimrc'))
+--- < >
+--- 1
+--- <
+---
+--- @param file string
+--- @return 0|1
+function vim.fn.filereadable(file) end
+
+--- The result is a Number, which is 1 when a file with the
+--- name {file} exists, and can be written. If {file} doesn't
+--- exist, or is not writable, the result is 0. If {file} is a
+--- directory, and we can write to it, the result is 2.
+---
+--- @param file string
+--- @return 0|1
+function vim.fn.filewritable(file) end
+
+--- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+--- For each item in {expr1} evaluate {expr2} and when the result
+--- is zero or false remove the item from the |List| or
+--- |Dictionary|. Similarly for each byte in a |Blob| and each
+--- character in a |String|.
+---
+--- {expr2} must be a |string| or |Funcref|.
+---
+--- If {expr2} is a |string|, inside {expr2} |v:val| has the value
+--- of the current item. For a |Dictionary| |v:key| has the key
+--- of the current item and for a |List| |v:key| has the index of
+--- the current item. For a |Blob| |v:key| has the index of the
+--- current byte. For a |String| |v:key| has the index of the
+--- current character.
+--- Examples: >vim
+--- call filter(mylist, 'v:val !~ "OLD"')
+--- <Removes the items where "OLD" appears. >vim
+--- call filter(mydict, 'v:key >= 8')
+--- <Removes the items with a key below 8. >vim
+--- call filter(var, 0)
+--- <Removes all the items, thus clears the |List| or |Dictionary|.
+---
+--- Note that {expr2} is the result of expression and is then
+--- used as an expression again. Often it is good to use a
+--- |literal-string| to avoid having to double backslashes.
+---
+--- If {expr2} is a |Funcref| it must take two arguments:
+--- 1. the key or the index of the current item.
+--- 2. the value of the current item.
+--- The function must return |TRUE| if the item should be kept.
+--- Example that keeps the odd items of a list: >vim
+--- func Odd(idx, val)
+--- return a:idx % 2 == 1
+--- endfunc
+--- call filter(mylist, function('Odd'))
+--- <It is shorter when using a |lambda|: >vim
+--- call filter(myList, {idx, val -> idx * val <= 42})
+--- <If you do not use "val" you can leave it out: >vim
+--- call filter(myList, {idx -> idx % 2 == 1})
+--- <
+--- For a |List| and a |Dictionary| the operation is done
+--- in-place. If you want it to remain unmodified make a copy
+--- first: >vim
+--- let l = filter(copy(mylist), 'v:val =~ "KEEP"')
+---
+--- <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+--- or a new |Blob| or |String|.
+--- When an error is encountered while evaluating {expr2} no
+--- further items in {expr1} are processed.
+--- When {expr2} is a Funcref errors inside a function are ignored,
+--- unless it was defined with the "abort" flag.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.filter(expr1, expr2) end
+
+--- Find directory {name} in {path}. Supports both downwards and
+--- upwards recursive directory searches. See |file-searching|
+--- for the syntax of {path}.
+---
+--- Returns the path of the first found match. When the found
+--- directory is below the current directory a relative path is
+--- returned. Otherwise a full path is returned.
+--- If {path} is omitted or empty then 'path' is used.
+---
+--- If the optional {count} is given, find {count}'s occurrence of
+--- {name} in {path} instead of the first one.
+--- When {count} is negative return all the matches in a |List|.
+---
+--- Returns an empty string if the directory is not found.
+---
+--- This is quite similar to the ex-command `:find`.
+---
+--- @param name string
+--- @param path? string
+--- @param count? any
+--- @return any
+function vim.fn.finddir(name, path, count) end
+
+--- Just like |finddir()|, but find a file instead of a directory.
+--- Uses 'suffixesadd'.
+--- Example: >vim
+--- echo findfile("tags.vim", ".;")
+--- <Searches from the directory of the current file upwards until
+--- it finds the file "tags.vim".
+---
+--- @param name string
+--- @param path? string
+--- @param count? any
+--- @return any
+function vim.fn.findfile(name, path, count) end
+
+--- Flatten {list} up to {maxdepth} levels. Without {maxdepth}
+--- the result is a |List| without nesting, as if {maxdepth} is
+--- a very large number.
+--- The {list} is changed in place, use |flattennew()| if you do
+--- not want that.
+--- *E900*
+--- {maxdepth} means how deep in nested lists changes are made.
+--- {list} is not modified when {maxdepth} is 0.
+--- {maxdepth} must be positive number.
+---
+--- If there is an error the number zero is returned.
+---
+--- Example: >vim
+--- echo flatten([1, [2, [3, 4]], 5])
+--- < [1, 2, 3, 4, 5] >vim
+--- echo flatten([1, [2, [3, 4]], 5], 1)
+--- < [1, 2, [3, 4], 5]
+---
+--- @param list any
+--- @param maxdepth? any
+--- @return any[]|0
+function vim.fn.flatten(list, maxdepth) end
+
+--- Like |flatten()| but first make a copy of {list}.
+---
+--- @param list any
+--- @param maxdepth? any
+--- @return any[]|0
+function vim.fn.flattennew(list, maxdepth) end
+
+--- Convert {expr} to a Number by omitting the part after the
+--- decimal point.
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0 if {expr} is not a |Float| or a |Number|.
+--- When the value of {expr} is out of range for a |Number| the
+--- result is truncated to 0x7fffffff or -0x7fffffff (or when
+--- 64-bit Number support is enabled, 0x7fffffffffffffff or
+--- -0x7fffffffffffffff). NaN results in -0x80000000 (or when
+--- 64-bit Number support is enabled, -0x8000000000000000).
+--- Examples: >vim
+--- echo float2nr(3.95)
+--- < 3 >vim
+--- echo float2nr(-23.45)
+--- < -23 >vim
+--- echo float2nr(1.0e100)
+--- < 2147483647 (or 9223372036854775807) >vim
+--- echo float2nr(-1.0e150)
+--- < -2147483647 (or -9223372036854775807) >vim
+--- echo float2nr(1.0e-100)
+--- < 0
+---
+--- @param expr any
+--- @return any
+function vim.fn.float2nr(expr) end
+
+--- Return the largest integral value less than or equal to
+--- {expr} as a |Float| (round down).
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo floor(1.856)
+--- < 1.0 >vim
+--- echo floor(-5.456)
+--- < -6.0 >vim
+--- echo floor(4.0)
+--- < 4.0
+---
+--- @param expr any
+--- @return any
+function vim.fn.floor(expr) end
+
+--- Return the remainder of {expr1} / {expr2}, even if the
+--- division is not representable. Returns {expr1} - i * {expr2}
+--- for some integer i such that if {expr2} is non-zero, the
+--- result has the same sign as {expr1} and magnitude less than
+--- the magnitude of {expr2}. If {expr2} is zero, the value
+--- returned is zero. The value returned is a |Float|.
+--- {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+--- |Number|.
+--- Examples: >vim
+--- echo fmod(12.33, 1.22)
+--- < 0.13 >vim
+--- echo fmod(-12.33, 1.22)
+--- < -0.13
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.fmod(expr1, expr2) end
+
+--- Escape {string} for use as file name command argument. All
+--- characters that have a special meaning, such as `'%'` and `'|'`
+--- are escaped with a backslash.
+--- For most systems the characters escaped are
+--- " \t\n*?[{`$\\%#'\"|!<". For systems where a backslash
+--- appears in a filename, it depends on the value of 'isfname'.
+--- A leading '+' and '>' is also escaped (special after |:edit|
+--- and |:write|). And a "-" by itself (special after |:cd|).
+--- Returns an empty string on error.
+--- Example: >vim
+--- let fname = '+some str%nge|name'
+--- exe "edit " .. fnameescape(fname)
+--- <results in executing: >vim
+--- edit \+some\ str\%nge\|name
+--- <
+---
+--- @param string string
+--- @return string
+function vim.fn.fnameescape(string) end
+
+--- Modify file name {fname} according to {mods}. {mods} is a
+--- string of characters like it is used for file names on the
+--- command line. See |filename-modifiers|.
+--- Example: >vim
+--- echo fnamemodify("main.c", ":p:h")
+--- <results in: >
+--- /home/user/vim/vim/src
+--- <If {mods} is empty or an unsupported modifier is used then
+--- {fname} is returned.
+--- When {fname} is empty then with {mods} ":h" returns ".", so
+--- that `:cd` can be used with it. This is different from
+--- expand('%:h') without a buffer name, which returns an empty
+--- string.
+--- Note: Environment variables don't work in {fname}, use
+--- |expand()| first then.
+---
+--- @param fname string
+--- @param mods string
+--- @return string
+function vim.fn.fnamemodify(fname, mods) end
+
+--- The result is a Number. If the line {lnum} is in a closed
+--- fold, the result is the number of the first line in that fold.
+--- If the line {lnum} is not in a closed fold, -1 is returned.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.foldclosed(lnum) end
+
+--- The result is a Number. If the line {lnum} is in a closed
+--- fold, the result is the number of the last line in that fold.
+--- If the line {lnum} is not in a closed fold, -1 is returned.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.foldclosedend(lnum) end
+
+--- The result is a Number, which is the foldlevel of line {lnum}
+--- in the current buffer. For nested folds the deepest level is
+--- returned. If there is no fold at line {lnum}, zero is
+--- returned. It doesn't matter if the folds are open or closed.
+--- When used while updating folds (from 'foldexpr') -1 is
+--- returned for lines where folds are still to be updated and the
+--- foldlevel is unknown. As a special case the level of the
+--- previous line is usually available.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.foldlevel(lnum) end
+
+--- Returns a String, to be displayed for a closed fold. This is
+--- the default function used for the 'foldtext' option and should
+--- only be called from evaluating 'foldtext'. It uses the
+--- |v:foldstart|, |v:foldend| and |v:folddashes| variables.
+--- The returned string looks like this: >
+--- +-- 45 lines: abcdef
+--- <The number of leading dashes depends on the foldlevel. The
+--- "45" is the number of lines in the fold. "abcdef" is the text
+--- in the first non-blank line of the fold. Leading white space,
+--- "//" or "/*" and the text from the 'foldmarker' and
+--- 'commentstring' options is removed.
+--- When used to draw the actual foldtext, the rest of the line
+--- will be filled with the fold char from the 'fillchars'
+--- setting.
+--- Returns an empty string when there is no fold.
+---
+--- @return string
+function vim.fn.foldtext() end
+
+--- Returns the text that is displayed for the closed fold at line
+--- {lnum}. Evaluates 'foldtext' in the appropriate context.
+--- When there is no closed fold at {lnum} an empty string is
+--- returned.
+--- {lnum} is used like with |getline()|. Thus "." is the current
+--- line, "'m" mark m, etc.
+--- Useful when exporting folded text, e.g., to HTML.
+---
+--- @param lnum integer
+--- @return string
+function vim.fn.foldtextresult(lnum) end
+
+--- Get the full command name from a short abbreviated command
+--- name; see |20.2| for details on command abbreviations.
+---
+--- The string argument {name} may start with a `:` and can
+--- include a [range], these are skipped and not returned.
+--- Returns an empty string if a command doesn't exist or if it's
+--- ambiguous (for user-defined commands).
+---
+--- For example `fullcommand('s')`, `fullcommand('sub')`,
+--- `fullcommand(':%substitute')` all return "substitute".
+---
+--- @param name string
+--- @return string
+function vim.fn.fullcommand(name) end
+
+--- Just like |function()|, but the returned Funcref will lookup
+--- the function by reference, not by name. This matters when the
+--- function {name} is redefined later.
+---
+--- Unlike |function()|, {name} must be an existing user function.
+--- It only works for an autoloaded function if it has already
+--- been loaded (to avoid mistakenly loading the autoload script
+--- when only intending to use the function name, use |function()|
+--- instead). {name} cannot be a builtin function.
+--- Returns 0 on error.
+---
+--- @param name string
+--- @param arglist? any
+--- @param dict? any
+--- @return any
+function vim.fn.funcref(name, arglist, dict) end
+
+--- Return a |Funcref| variable that refers to function {name}.
+--- {name} can be the name of a user defined function or an
+--- internal function.
+---
+--- {name} can also be a Funcref or a partial. When it is a
+--- partial the dict stored in it will be used and the {dict}
+--- argument is not allowed. E.g.: >vim
+--- let FuncWithArg = function(dict.Func, [arg])
+--- let Broken = function(dict.Func, [arg], dict)
+--- <
+--- When using the Funcref the function will be found by {name},
+--- also when it was redefined later. Use |funcref()| to keep the
+--- same function.
+---
+--- When {arglist} or {dict} is present this creates a partial.
+--- That means the argument list and/or the dictionary is stored in
+--- the Funcref and will be used when the Funcref is called.
+---
+--- The arguments are passed to the function in front of other
+--- arguments, but after any argument from |method|. Example: >vim
+--- func Callback(arg1, arg2, name)
+--- "...
+--- endfunc
+--- let Partial = function('Callback', ['one', 'two'])
+--- "...
+--- call Partial('name')
+--- <Invokes the function as with: >vim
+--- call Callback('one', 'two', 'name')
+---
+--- <With a |method|: >vim
+--- func Callback(one, two, three)
+--- "...
+--- endfunc
+--- let Partial = function('Callback', ['two'])
+--- "...
+--- eval 'one'->Partial('three')
+--- <Invokes the function as with: >vim
+--- call Callback('one', 'two', 'three')
+---
+--- <The function() call can be nested to add more arguments to the
+--- Funcref. The extra arguments are appended to the list of
+--- arguments. Example: >vim
+--- func Callback(arg1, arg2, name)
+--- "...
+--- endfunc
+--- let Func = function('Callback', ['one'])
+--- let Func2 = function(Func, ['two'])
+--- "...
+--- call Func2('name')
+--- <Invokes the function as with: >vim
+--- call Callback('one', 'two', 'name')
+---
+--- <The Dictionary is only useful when calling a "dict" function.
+--- In that case the {dict} is passed in as "self". Example: >vim
+--- function Callback() dict
+--- echo "called for " .. self.name
+--- endfunction
+--- "...
+--- let context = {"name": "example"}
+--- let Func = function('Callback', context)
+--- "...
+--- call Func() " will echo: called for example
+--- <The use of function() is not needed when there are no extra
+--- arguments, these two are equivalent, if Callback() is defined
+--- as context.Callback(): >vim
+--- let Func = function('Callback', context)
+--- let Func = context.Callback
+---
+--- <The argument list and the Dictionary can be combined: >vim
+--- function Callback(arg1, count) dict
+--- "...
+--- endfunction
+--- let context = {"name": "example"}
+--- let Func = function('Callback', ['one'], context)
+--- "...
+--- call Func(500)
+--- <Invokes the function as with: >vim
+--- call context.Callback('one', 500)
+--- <
+--- Returns 0 on error.
+---
+--- @param name string
+--- @param arglist? any
+--- @param dict? any
+--- @return any
+vim.fn['function'] = function(name, arglist, dict) end
+
+--- Cleanup unused |Lists| and |Dictionaries| that have circular
+--- references.
+---
+--- There is hardly ever a need to invoke this function, as it is
+--- automatically done when Vim runs out of memory or is waiting
+--- for the user to press a key after 'updatetime'. Items without
+--- circular references are always freed when they become unused.
+--- This is useful if you have deleted a very big |List| and/or
+--- |Dictionary| with circular references in a script that runs
+--- for a long time.
+---
+--- When the optional {atexit} argument is one, garbage
+--- collection will also be done when exiting Vim, if it wasn't
+--- done before. This is useful when checking for memory leaks.
+---
+--- The garbage collection is not done immediately but only when
+--- it's safe to perform. This is when waiting for the user to
+--- type a character.
+---
+--- @param atexit? any
+--- @return any
+function vim.fn.garbagecollect(atexit) end
+
+--- Get item {idx} from |List| {list}. When this item is not
+--- available return {default}. Return zero when {default} is
+--- omitted.
+---
+--- @param list any[]
+--- @param idx integer
+--- @param default? any
+--- @return any
+function vim.fn.get(list, idx, default) end
+
+--- Get byte {idx} from |Blob| {blob}. When this byte is not
+--- available return {default}. Return -1 when {default} is
+--- omitted.
+---
+--- @param blob string
+--- @param idx integer
+--- @param default? any
+--- @return any
+function vim.fn.get(blob, idx, default) end
+
+--- Get item with key {key} from |Dictionary| {dict}. When this
+--- item is not available return {default}. Return zero when
+--- {default} is omitted. Useful example: >vim
+--- let val = get(g:, 'var_name', 'default')
+--- <This gets the value of g:var_name if it exists, and uses
+--- "default" when it does not exist.
+---
+--- @param dict table<string,any>
+--- @param key string
+--- @param default? any
+--- @return any
+function vim.fn.get(dict, key, default) end
+
+--- Get item {what} from Funcref {func}. Possible values for
+--- {what} are:
+--- "name" The function name
+--- "func" The function
+--- "dict" The dictionary
+--- "args" The list with arguments
+--- Returns zero on error.
+---
+--- @param func function
+--- @param what string
+--- @return any
+function vim.fn.get(func, what) end
+
+--- @param buf? integer|string
+--- @return vim.fn.getbufinfo.ret.item[]
+function vim.fn.getbufinfo(buf) end
+
+--- Get information about buffers as a List of Dictionaries.
+---
+--- Without an argument information about all the buffers is
+--- returned.
+---
+--- When the argument is a |Dictionary| only the buffers matching
+--- the specified criteria are returned. The following keys can
+--- be specified in {dict}:
+--- buflisted include only listed buffers.
+--- bufloaded include only loaded buffers.
+--- bufmodified include only modified buffers.
+---
+--- Otherwise, {buf} specifies a particular buffer to return
+--- information for. For the use of {buf}, see |bufname()|
+--- above. If the buffer is found the returned List has one item.
+--- Otherwise the result is an empty list.
+---
+--- Each returned List item is a dictionary with the following
+--- entries:
+--- bufnr Buffer number.
+--- changed TRUE if the buffer is modified.
+--- changedtick Number of changes made to the buffer.
+--- hidden TRUE if the buffer is hidden.
+--- lastused Timestamp in seconds, like
+--- |localtime()|, when the buffer was
+--- last used.
+--- listed TRUE if the buffer is listed.
+--- lnum Line number used for the buffer when
+--- opened in the current window.
+--- Only valid if the buffer has been
+--- displayed in the window in the past.
+--- If you want the line number of the
+--- last known cursor position in a given
+--- window, use |line()|: >vim
+--- echo line('.', {winid})
+--- <
+--- linecount Number of lines in the buffer (only
+--- valid when loaded)
+--- loaded TRUE if the buffer is loaded.
+--- name Full path to the file in the buffer.
+--- signs List of signs placed in the buffer.
+--- Each list item is a dictionary with
+--- the following fields:
+--- id sign identifier
+--- lnum line number
+--- name sign name
+--- variables A reference to the dictionary with
+--- buffer-local variables.
+--- windows List of |window-ID|s that display this
+--- buffer
+---
+--- Examples: >vim
+--- for buf in getbufinfo()
+--- echo buf.name
+--- endfor
+--- for buf in getbufinfo({'buflisted':1})
+--- if buf.changed
+--- " ....
+--- endif
+--- endfor
+--- <
+--- To get buffer-local options use: >vim
+--- getbufvar({bufnr}, '&option_name')
+--- <
+---
+--- @param dict? vim.fn.getbufinfo.dict
+--- @return vim.fn.getbufinfo.ret.item[]
+function vim.fn.getbufinfo(dict) end
+
+--- Return a |List| with the lines starting from {lnum} to {end}
+--- (inclusive) in the buffer {buf}. If {end} is omitted, a
+--- |List| with only the line {lnum} is returned. See
+--- `getbufoneline()` for only getting the line.
+---
+--- For the use of {buf}, see |bufname()| above.
+---
+--- For {lnum} and {end} "$" can be used for the last line of the
+--- buffer. Otherwise a number must be used.
+---
+--- When {lnum} is smaller than 1 or bigger than the number of
+--- lines in the buffer, an empty |List| is returned.
+---
+--- When {end} is greater than the number of lines in the buffer,
+--- it is treated as {end} is set to the number of lines in the
+--- buffer. When {end} is before {lnum} an empty |List| is
+--- returned.
+---
+--- This function works only for loaded buffers. For unloaded and
+--- non-existing buffers, an empty |List| is returned.
+---
+--- Example: >vim
+--- let lines = getbufline(bufnr("myfile"), 1, "$")
+---
+--- @param buf any
+--- @param lnum integer
+--- @param end_? integer
+--- @return any
+function vim.fn.getbufline(buf, lnum, end_) end
+
+--- Just like `getbufline()` but only get one line and return it
+--- as a string.
+---
+--- @param buf integer|string
+--- @param lnum integer
+--- @return string
+function vim.fn.getbufoneline(buf, lnum) end
+
+--- The result is the value of option or local buffer variable
+--- {varname} in buffer {buf}. Note that the name without "b:"
+--- must be used.
+--- The {varname} argument is a string.
+--- When {varname} is empty returns a |Dictionary| with all the
+--- buffer-local variables.
+--- When {varname} is equal to "&" returns a |Dictionary| with all
+--- the buffer-local options.
+--- Otherwise, when {varname} starts with "&" returns the value of
+--- a buffer-local option.
+--- This also works for a global or buffer-local option, but it
+--- doesn't work for a global variable, window-local variable or
+--- window-local option.
+--- For the use of {buf}, see |bufname()| above.
+--- When the buffer or variable doesn't exist {def} or an empty
+--- string is returned, there is no error message.
+--- Examples: >vim
+--- let bufmodified = getbufvar(1, "&mod")
+--- echo "todo myvar = " .. getbufvar("todo", "myvar")
+---
+--- @param buf any
+--- @param varname string
+--- @param def? any
+--- @return any
+function vim.fn.getbufvar(buf, varname, def) end
+
+--- Returns a |List| of cell widths of character ranges overridden
+--- by |setcellwidths()|. The format is equal to the argument of
+--- |setcellwidths()|. If no character ranges have their cell
+--- widths overridden, an empty List is returned.
+---
+--- @return any
+function vim.fn.getcellwidths() end
+
+--- Returns the |changelist| for the buffer {buf}. For the use
+--- of {buf}, see |bufname()| above. If buffer {buf} doesn't
+--- exist, an empty list is returned.
+---
+--- The returned list contains two entries: a list with the change
+--- locations and the current position in the list. Each
+--- entry in the change list is a dictionary with the following
+--- entries:
+--- col column number
+--- coladd column offset for 'virtualedit'
+--- lnum line number
+--- If buffer {buf} is the current buffer, then the current
+--- position refers to the position in the list. For other
+--- buffers, it is set to the length of the list.
+---
+--- @param buf? integer|string
+--- @return table[]
+function vim.fn.getchangelist(buf) end
+
+--- Get a single character from the user or input stream.
+--- If [expr] is omitted, wait until a character is available.
+--- If [expr] is 0, only get a character when one is available.
+--- Return zero otherwise.
+--- If [expr] is 1, only check if a character is available, it is
+--- not consumed. Return zero if no character available.
+--- If you prefer always getting a string use |getcharstr()|.
+---
+--- Without [expr] and when [expr] is 0 a whole character or
+--- special key is returned. If it is a single character, the
+--- result is a Number. Use |nr2char()| to convert it to a String.
+--- Otherwise a String is returned with the encoded character.
+--- For a special key it's a String with a sequence of bytes
+--- starting with 0x80 (decimal: 128). This is the same value as
+--- the String "\<Key>", e.g., "\<Left>". The returned value is
+--- also a String when a modifier (shift, control, alt) was used
+--- that is not included in the character.
+---
+--- When [expr] is 0 and Esc is typed, there will be a short delay
+--- while Vim waits to see if this is the start of an escape
+--- sequence.
+---
+--- When [expr] is 1 only the first byte is returned. For a
+--- one-byte character it is the character itself as a number.
+--- Use nr2char() to convert it to a String.
+---
+--- Use getcharmod() to obtain any additional modifiers.
+---
+--- When the user clicks a mouse button, the mouse event will be
+--- returned. The position can then be found in |v:mouse_col|,
+--- |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|.
+--- |getmousepos()| can also be used. Mouse move events will be
+--- ignored.
+--- This example positions the mouse as it would normally happen: >vim
+--- let c = getchar()
+--- if c == "\<LeftMouse>" && v:mouse_win > 0
+--- exe v:mouse_win .. "wincmd w"
+--- exe v:mouse_lnum
+--- exe "normal " .. v:mouse_col .. "|"
+--- endif
+--- <
+--- There is no prompt, you will somehow have to make clear to the
+--- user that a character has to be typed. The screen is not
+--- redrawn, e.g. when resizing the window.
+---
+--- There is no mapping for the character.
+--- Key codes are replaced, thus when the user presses the <Del>
+--- key you get the code for the <Del> key, not the raw character
+--- sequence. Examples: >vim
+--- getchar() == "\<Del>"
+--- getchar() == "\<S-Left>"
+--- <This example redefines "f" to ignore case: >vim
+--- nmap f :call FindChar()<CR>
+--- function FindChar()
+--- let c = nr2char(getchar())
+--- while col('.') < col('$') - 1
+--- normal l
+--- if getline('.')[col('.') - 1] ==? c
+--- break
+--- endif
+--- endwhile
+--- endfunction
+--- <
+---
+--- @return integer
+function vim.fn.getchar() end
+
+--- The result is a Number which is the state of the modifiers for
+--- the last obtained character with getchar() or in another way.
+--- These values are added together:
+--- 2 shift
+--- 4 control
+--- 8 alt (meta)
+--- 16 meta (when it's different from ALT)
+--- 32 mouse double click
+--- 64 mouse triple click
+--- 96 mouse quadruple click (== 32 + 64)
+--- 128 command (Macintosh only)
+--- Only the modifiers that have not been included in the
+--- character itself are obtained. Thus Shift-a results in "A"
+--- without a modifier. Returns 0 if no modifiers are used.
+---
+--- @return integer
+function vim.fn.getcharmod() end
+
+--- 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.
+--- If |getpos()| returns a very large column number, equal to
+--- |v:maxcol|, then getcharpos() will return the character index
+--- of the last character.
+---
+--- Example:
+--- With the cursor on '세' in line 5 with text "여보세요": >vim
+--- getcharpos('.') returns [0, 5, 3, 0]
+--- getpos('.') returns [0, 5, 7, 0]
+--- <
+---
+--- @param expr any
+--- @return integer[]
+function vim.fn.getcharpos(expr) end
+
+--- Return the current character search information as a {dict}
+--- with the following entries:
+---
+--- char character previously used for a character
+--- search (|t|, |f|, |T|, or |F|); empty string
+--- if no character search has been performed
+--- forward direction of character search; 1 for forward,
+--- 0 for backward
+--- until type of character search; 1 for a |t| or |T|
+--- character search, 0 for an |f| or |F|
+--- character search
+---
+--- This can be useful to always have |;| and |,| search
+--- forward/backward regardless of the direction of the previous
+--- character search: >vim
+--- nnoremap <expr> ; getcharsearch().forward ? ';' : ','
+--- nnoremap <expr> , getcharsearch().forward ? ',' : ';'
+--- <Also see |setcharsearch()|.
+---
+--- @return table[]
+function vim.fn.getcharsearch() end
+
+--- Get a single character from the user or input stream as a
+--- string.
+--- If [expr] is omitted, wait until a character is available.
+--- If [expr] is 0 or false, only get a character when one is
+--- available. Return an empty string otherwise.
+--- If [expr] is 1 or true, only check if a character is
+--- available, it is not consumed. Return an empty string
+--- if no character is available.
+--- Otherwise this works like |getchar()|, except that a number
+--- result is converted to a string.
+---
+--- @return string
+function vim.fn.getcharstr() end
+
+--- Return the type of the current command-line completion.
+--- Only works when the command line is being edited, thus
+--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
+--- See |:command-completion| for the return string.
+--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+--- |setcmdline()|.
+--- Returns an empty string when completion is not defined.
+---
+--- @return string
+function vim.fn.getcmdcompltype() end
+
+--- Return the current command-line. Only works when the command
+--- line is being edited, thus requires use of |c_CTRL-\_e| or
+--- |c_CTRL-R_=|.
+--- Example: >vim
+--- cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
+--- <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and
+--- |setcmdline()|.
+--- Returns an empty string when entering a password or using
+--- |inputsecret()|.
+---
+--- @return string
+function vim.fn.getcmdline() end
+
+--- Return the position of the cursor in the command line as a
+--- byte count. The first column is 1.
+--- Only works when editing the command line, thus requires use of
+--- |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+--- Returns 0 otherwise.
+--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+--- |setcmdline()|.
+---
+--- @return integer
+function vim.fn.getcmdpos() end
+
+--- Return the screen position of the cursor in the command line
+--- as a byte count. The first column is 1.
+--- Instead of |getcmdpos()|, it adds the prompt position.
+--- Only works when editing the command line, thus requires use of
+--- |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+--- Returns 0 otherwise.
+--- Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and
+--- |setcmdline()|.
+---
+--- @return any
+function vim.fn.getcmdscreenpos() end
+
+--- Return the current command-line type. Possible return values
+--- are:
+--- : normal Ex command
+--- > debug mode command |debug-mode|
+--- / forward search command
+--- ? backward search command
+--- \@ |input()| command
+--- `-` |:insert| or |:append| command
+--- = |i_CTRL-R_=|
+--- Only works when editing the command line, thus requires use of
+--- |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+--- Returns an empty string otherwise.
+--- Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|.
+---
+--- @return ':'|'>'|'/'|'?'|'@'|'-'|'='
+function vim.fn.getcmdtype() end
+
+--- Return the current |command-line-window| type. Possible return
+--- values are the same as |getcmdtype()|. Returns an empty string
+--- when not in the command-line window.
+---
+--- @return ':'|'>'|'/'|'?'|'@'|'-'|'='
+function vim.fn.getcmdwintype() end
+
+--- Return a list of command-line completion matches. The String
+--- {type} argument specifies what for. The following completion
+--- types are supported:
+---
+--- arglist file names in argument list
+--- augroup autocmd groups
+--- buffer buffer names
+--- breakpoint |:breakadd| and |:breakdel| suboptions
+--- cmdline |cmdline-completion| result
+--- color color schemes
+--- command Ex command
+--- compiler compilers
+--- custom,{func} custom completion, defined via {func}
+--- customlist,{func} custom completion, defined via {func}
+--- diff_buffer |:diffget| and |:diffput| completion
+--- dir directory names
+--- environment environment variable names
+--- event autocommand events
+--- expression Vim expression
+--- file file and directory names
+--- file_in_path file and directory names in |'path'|
+--- filetype filetype names |'filetype'|
+--- function function name
+--- help help subjects
+--- highlight highlight groups
+--- history |:history| suboptions
+--- locale locale names (as output of locale -a)
+--- mapclear buffer argument
+--- mapping mapping name
+--- menu menus
+--- messages |:messages| suboptions
+--- option options
+--- packadd optional package |pack-add| names
+--- runtime |:runtime| completion
+--- scriptnames sourced script names |:scriptnames|
+--- shellcmd Shell command
+--- sign |:sign| suboptions
+--- syntax syntax file names |'syntax'|
+--- syntime |:syntime| suboptions
+--- tag tags
+--- tag_listfiles tags, file names
+--- user user names
+--- var user variables
+---
+--- If {pat} is an empty string, then all the matches are
+--- returned. Otherwise only items matching {pat} are returned.
+--- See |wildcards| for the use of special characters in {pat}.
+---
+--- If the optional {filtered} flag is set to 1, then 'wildignore'
+--- is applied to filter the results. Otherwise all the matches
+--- are returned. The 'wildignorecase' option always applies.
+---
+--- If the 'wildoptions' option contains "fuzzy", then fuzzy
+--- matching is used to get the completion matches. Otherwise
+--- regular expression matching is used. Thus this function
+--- follows the user preference, what happens on the command line.
+--- If you do not want this you can make 'wildoptions' empty
+--- before calling getcompletion() and restore it afterwards.
+---
+--- If {type} is "cmdline", then the |cmdline-completion| result is
+--- returned. For example, to complete the possible values after
+--- a ":call" command: >vim
+--- echo getcompletion('call ', 'cmdline')
+--- <
+--- If there are no matches, an empty list is returned. An
+--- invalid value for {type} produces an error.
+---
+--- @param pat any
+--- @param type any
+--- @param filtered? any
+--- @return string[]
+function vim.fn.getcompletion(pat, type, filtered) end
+
+--- Get the position of the cursor. This is like getpos('.'), but
+--- includes an extra "curswant" item in the list:
+--- [0, lnum, col, off, curswant] ~
+--- The "curswant" number is the preferred column when moving the
+--- cursor vertically. After |$| command it will be a very large
+--- number equal to |v:maxcol|. 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: >vim
+--- let save_cursor = getcurpos()
+--- MoveTheCursorAround
+--- call setpos('.', save_cursor)
+--- <Note that this only works within the window. See
+--- |winrestview()| for restoring more state.
+---
+--- @param winid? integer
+--- @return any
+function vim.fn.getcurpos(winid) end
+
+--- 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 "여보세요": >vim
+--- getcursorcharpos() " returns [0, 3, 2, 0, 3]
+--- getcurpos() " returns [0, 3, 4, 0, 3]
+--- <
+---
+--- @param winid? integer
+--- @return any
+function vim.fn.getcursorcharpos(winid) end
+
+--- 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
+--- ignored.
+--- Tabs and windows are identified by their respective numbers,
+--- 0 means current tab or window. Missing tab number implies 0.
+--- Thus the following are equivalent: >vim
+--- getcwd(0)
+--- getcwd(0, 0)
+--- <If {winnr} is -1 it is ignored, only the tab is resolved.
+--- {winnr} can be the window number or the |window-ID|.
+--- If both {winnr} and {tabnr} are -1 the global working
+--- directory is returned.
+--- Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+---
+--- @param winnr? integer
+--- @param tabnr? integer
+--- @return string
+function vim.fn.getcwd(winnr, tabnr) end
+
+--- Return the value of environment variable {name}. The {name}
+--- argument is a string, without a leading '$'. Example: >vim
+--- myHome = getenv('HOME')
+---
+--- <When the variable does not exist |v:null| is returned. That
+--- is different from a variable set to an empty string.
+--- See also |expr-env|.
+---
+--- @param name string
+--- @return string
+function vim.fn.getenv(name) end
+
+--- Without an argument returns the name of the normal font being
+--- used. Like what is used for the Normal highlight group
+--- |hl-Normal|.
+--- With an argument a check is done whether String {name} is a
+--- valid font name. If not then an empty string is returned.
+--- Otherwise the actual font name is returned, or {name} if the
+--- GUI does not support obtaining the real name.
+--- Only works when the GUI is running, thus not in your vimrc or
+--- gvimrc file. Use the |GUIEnter| autocommand to use this
+--- function just after the GUI has started.
+---
+--- @param name? string
+--- @return string
+function vim.fn.getfontname(name) end
+
+--- The result is a String, which is the read, write, and execute
+--- permissions of the given file {fname}.
+--- If {fname} does not exist or its directory cannot be read, an
+--- empty string is returned.
+--- The result is of the form "rwxrwxrwx", where each group of
+--- "rwx" flags represent, in turn, the permissions of the owner
+--- of the file, the group the file belongs to, and other users.
+--- If a user does not have a given permission the flag for this
+--- is replaced with the string "-". Examples: >vim
+--- echo getfperm("/etc/passwd")
+--- echo getfperm(expand("~/.config/nvim/init.vim"))
+--- <This will hopefully (from a security point of view) display
+--- the string "rw-r--r--" or even "rw-------".
+---
+--- For setting permissions use |setfperm()|.
+---
+--- @param fname string
+--- @return string
+function vim.fn.getfperm(fname) end
+
+--- The result is a Number, which is the size in bytes of the
+--- given file {fname}.
+--- If {fname} is a directory, 0 is returned.
+--- If the file {fname} can't be found, -1 is returned.
+--- If the size of {fname} is too big to fit in a Number then -2
+--- is returned.
+---
+--- @param fname string
+--- @return integer
+function vim.fn.getfsize(fname) end
+
+--- The result is a Number, which is the last modification time of
+--- the given file {fname}. The value is measured as seconds
+--- since 1st Jan 1970, and may be passed to strftime(). See also
+--- |localtime()| and |strftime()|.
+--- If the file {fname} can't be found -1 is returned.
+---
+--- @param fname string
+--- @return integer
+function vim.fn.getftime(fname) end
+
+--- The result is a String, which is a description of the kind of
+--- file of the given file {fname}.
+--- If {fname} does not exist an empty string is returned.
+--- Here is a table over different kinds of files and their
+--- results:
+--- Normal file "file"
+--- Directory "dir"
+--- Symbolic link "link"
+--- Block device "bdev"
+--- Character device "cdev"
+--- Socket "socket"
+--- FIFO "fifo"
+--- All other "other"
+--- Example: >vim
+--- getftype("/home")
+--- <Note that a type such as "link" will only be returned on
+--- systems that support it. On some systems only "dir" and
+--- "file" are returned.
+---
+--- @param fname string
+--- @return 'file'|'dir'|'link'|'bdev'|'cdev'|'socket'|'fifo'|'other'
+function vim.fn.getftype(fname) end
+
+--- Returns the |jumplist| for the specified window.
+---
+--- Without arguments use the current window.
+--- With {winnr} only use this window in the current tab page.
+--- {winnr} can also be a |window-ID|.
+--- With {winnr} and {tabnr} use the window in the specified tab
+--- page. If {winnr} or {tabnr} is invalid, an empty list is
+--- returned.
+---
+--- The returned list contains two entries: a list with the jump
+--- locations and the last used jump position number in the list.
+--- Each entry in the jump location list is a dictionary with
+--- the following entries:
+--- bufnr buffer number
+--- col column number
+--- coladd column offset for 'virtualedit'
+--- filename filename if available
+--- lnum line number
+---
+--- @param winnr? integer
+--- @param tabnr? integer
+--- @return vim.fn.getjumplist.ret
+function vim.fn.getjumplist(winnr, tabnr) end
+
+--- Without {end} the result is a String, which is line {lnum}
+--- from the current buffer. Example: >vim
+--- getline(1)
+--- <When {lnum} is a String that doesn't start with a
+--- digit, |line()| is called to translate the String into a Number.
+--- To get the line under the cursor: >vim
+--- getline(".")
+--- <When {lnum} is a number smaller than 1 or bigger than the
+--- number of lines in the buffer, an empty string is returned.
+---
+--- When {end} is given the result is a |List| where each item is
+--- a line from the current buffer in the range {lnum} to {end},
+--- including line {end}.
+--- {end} is used in the same way as {lnum}.
+--- Non-existing lines are silently omitted.
+--- When {end} is before {lnum} an empty |List| is returned.
+--- Example: >vim
+--- let start = line('.')
+--- let end = search("^$") - 1
+--- let lines = getline(start, end)
+---
+--- <To get lines from another buffer see |getbufline()| and
+--- |getbufoneline()|
+---
+--- @param lnum integer
+--- @param end_? any
+--- @return string|string[]
+function vim.fn.getline(lnum, end_) end
+
+--- 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.
+---
+--- For a location list window, the displayed location list is
+--- returned. For an invalid window number {nr}, an empty list is
+--- returned. Otherwise, same as |getqflist()|.
+---
+--- If the optional {what} dictionary argument is supplied, then
+--- returns the items listed in {what} as a dictionary. Refer to
+--- |getqflist()| for the supported items in {what}.
+---
+--- In addition to the items supported by |getqflist()| in {what},
+--- the following item is supported by |getloclist()|:
+---
+--- filewinid id of the window used to display files
+--- from the location list. This field is
+--- applicable only when called from a
+--- location list window. See
+--- |location-list-file-window| for more
+--- details.
+---
+--- Returns a |Dictionary| with default values if there is no
+--- location list for the window {nr}.
+--- Returns an empty Dictionary if window {nr} does not exist.
+---
+--- Examples (See also |getqflist-examples|): >vim
+--- echo getloclist(3, {'all': 0})
+--- echo getloclist(5, {'filewinid': 0})
+--- <
+---
+--- @param nr integer
+--- @param what? any
+--- @return any
+function vim.fn.getloclist(nr, what) end
+
+--- Without the {buf} argument returns a |List| with information
+--- about all the global marks. |mark|
+---
+--- If the optional {buf} argument is specified, returns the
+--- local marks defined in buffer {buf}. For the use of {buf},
+--- see |bufname()|. If {buf} is invalid, an empty list is
+--- returned.
+---
+--- Each item in the returned List is a |Dict| with the following:
+--- mark name of the mark prefixed by "'"
+--- pos a |List| with the position of the mark:
+--- [bufnum, lnum, col, off]
+--- Refer to |getpos()| for more information.
+--- file file name
+---
+--- Refer to |getpos()| for getting information about a specific
+--- mark.
+---
+--- @param buf? any
+--- @return any
+function vim.fn.getmarklist(buf) end
+
+--- Returns a |List| with all matches previously defined for the
+--- current window by |matchadd()| and the |:match| commands.
+--- |getmatches()| is useful in combination with |setmatches()|,
+--- as |setmatches()| can restore a list of matches saved by
+--- |getmatches()|.
+--- If {win} is specified, use the window with this number or
+--- window ID instead of the current window. If {win} is invalid,
+--- an empty list is returned.
+--- Example: >vim
+--- echo getmatches()
+--- < >
+--- [{"group": "MyGroup1", "pattern": "TODO",
+--- "priority": 10, "id": 1}, {"group": "MyGroup2",
+--- "pattern": "FIXME", "priority": 10, "id": 2}]
+--- < >vim
+--- let m = getmatches()
+--- call clearmatches()
+--- echo getmatches()
+--- < >
+--- []
+--- < >vim
+--- call setmatches(m)
+--- echo getmatches()
+--- < >
+--- [{"group": "MyGroup1", "pattern": "TODO",
+--- "priority": 10, "id": 1}, {"group": "MyGroup2",
+--- "pattern": "FIXME", "priority": 10, "id": 2}]
+--- < >vim
+--- unlet m
+--- <
+---
+--- @param win? any
+--- @return any
+function vim.fn.getmatches(win) end
+
+--- Returns a |Dictionary| with the last known position of the
+--- mouse. This can be used in a mapping for a mouse click. The
+--- items are:
+--- screenrow screen row
+--- screencol screen column
+--- winid Window ID of the click
+--- winrow row inside "winid"
+--- wincol column inside "winid"
+--- line text line inside "winid"
+--- column text column inside "winid"
+--- coladd offset (in screen columns) from the
+--- start of the clicked char
+--- All numbers are 1-based.
+---
+--- If not over a window, e.g. when in the command line, then only
+--- "screenrow" and "screencol" are valid, the others are zero.
+---
+--- When on the status line below a window or the vertical
+--- separator right of a window, the "line" and "column" values
+--- are zero.
+---
+--- When the position is after the text then "column" is the
+--- length of the text in bytes plus one.
+---
+--- If the mouse is over a focusable floating window then that
+--- window is used.
+---
+--- When using |getchar()| the Vim variables |v:mouse_lnum|,
+--- |v:mouse_col| and |v:mouse_winid| also provide these values.
+---
+--- @return vim.fn.getmousepos.ret
+function vim.fn.getmousepos() end
+
+--- Return a Number which is the process ID of the Vim process.
+--- This is a unique number, until Vim exits.
+---
+--- @return integer
+function vim.fn.getpid() end
+
+--- Get the position for String {expr}. For possible values of
+--- {expr} see |line()|. For getting the cursor position see
+--- |getcurpos()|.
+--- The result is a |List| with four numbers:
+--- [bufnum, lnum, col, off]
+--- "bufnum" is zero, unless a mark like '0 or 'A is used, then it
+--- is the buffer number of the mark.
+--- "lnum" and "col" are the position in the buffer. The first
+--- column is 1.
+--- The "off" number is zero, unless 'virtualedit' is used. Then
+--- it is the offset in screen columns from the start of the
+--- character. E.g., a position within a <Tab> or after the last
+--- character.
+--- Note that for '< and '> Visual mode matters: when it is "V"
+--- (visual line mode) the column of '< is zero and the column of
+--- '> is a large number equal to |v:maxcol|.
+--- The column number in the returned List is the byte position
+--- within the line. To get the character position in the line,
+--- use |getcharpos()|.
+--- A very large column number equal to |v:maxcol| can be returned,
+--- in which case it means "after the end of the line".
+--- If {expr} is invalid, returns a list with all zeros.
+--- This can be used to save and restore the position of a mark: >vim
+--- let save_a_mark = getpos("'a")
+--- " ...
+--- call setpos("'a", save_a_mark)
+--- <Also see |getcharpos()|, |getcurpos()| and |setpos()|.
+---
+--- @param expr string
+--- @return integer[]
+function vim.fn.getpos(expr) end
+
+--- Returns a |List| with all the current quickfix errors. Each
+--- list item is a dictionary with these entries:
+--- bufnr number of buffer that has the file name, use
+--- bufname() to get the name
+--- module module name
+--- lnum line number in the buffer (first line is 1)
+--- end_lnum
+--- end of line number if the item is multiline
+--- col column number (first column is 1)
+--- end_col end of column number if the item has range
+--- vcol |TRUE|: "col" is visual column
+--- |FALSE|: "col" is byte index
+--- nr error number
+--- pattern search pattern used to locate the error
+--- text description of the error
+--- type type of the error, 'E', '1', etc.
+--- valid |TRUE|: recognized error message
+--- user_data
+--- custom data associated with the item, can be
+--- any type.
+---
+--- When there is no error list or it's empty, an empty list is
+--- returned. Quickfix list entries with a non-existing buffer
+--- number are returned with "bufnr" set to zero (Note: some
+--- functions accept buffer number zero for the alternate buffer,
+--- you may need to explicitly check for zero).
+---
+--- Useful application: Find pattern matches in multiple files and
+--- do something with them: >vim
+--- vimgrep /theword/jg *.c
+--- for d in getqflist()
+--- echo bufname(d.bufnr) ':' d.lnum '=' d.text
+--- endfor
+--- <
+--- If the optional {what} dictionary argument is supplied, then
+--- returns only the items listed in {what} as a dictionary. The
+--- following string items are supported in {what}:
+--- changedtick get the total number of changes made
+--- to the list |quickfix-changedtick|
+--- context get the |quickfix-context|
+--- efm errorformat to use when parsing "lines". If
+--- not present, then the 'errorformat' option
+--- value is used.
+--- id get information for the quickfix list with
+--- |quickfix-ID|; zero means the id for the
+--- current list or the list specified by "nr"
+--- idx get information for the quickfix entry at this
+--- index in the list specified by "id" or "nr".
+--- If set to zero, then uses the current entry.
+--- See |quickfix-index|
+--- items quickfix list entries
+--- lines parse a list of lines using 'efm' and return
+--- the resulting entries. Only a |List| type is
+--- accepted. The current quickfix list is not
+--- modified. See |quickfix-parse|.
+--- nr get information for this quickfix list; zero
+--- means the current quickfix list and "$" means
+--- the last quickfix list
+--- qfbufnr number of the buffer displayed in the quickfix
+--- window. Returns 0 if the quickfix buffer is
+--- not present. See |quickfix-buffer|.
+--- size number of entries in the quickfix list
+--- title get the list title |quickfix-title|
+--- winid get the quickfix |window-ID|
+--- all all of the above quickfix properties
+--- Non-string items in {what} are ignored. To get the value of a
+--- particular item, set it to zero.
+--- If "nr" is not present then the current quickfix list is used.
+--- If both "nr" and a non-zero "id" are specified, then the list
+--- specified by "id" is used.
+--- To get the number of lists in the quickfix stack, set "nr" to
+--- "$" in {what}. The "nr" value in the returned dictionary
+--- contains the quickfix stack size.
+--- When "lines" is specified, all the other items except "efm"
+--- are ignored. The returned dictionary contains the entry
+--- "items" with the list of entries.
+---
+--- The returned dictionary contains the following entries:
+--- changedtick total number of changes made to the
+--- list |quickfix-changedtick|
+--- context quickfix list context. See |quickfix-context|
+--- If not present, set to "".
+--- id quickfix list ID |quickfix-ID|. If not
+--- present, set to 0.
+--- idx index of the quickfix entry in the list. If not
+--- present, set to 0.
+--- items quickfix list entries. If not present, set to
+--- an empty list.
+--- nr quickfix list number. If not present, set to 0
+--- qfbufnr number of the buffer displayed in the quickfix
+--- window. If not present, set to 0.
+--- size number of entries in the quickfix list. If not
+--- present, set to 0.
+--- title quickfix list title text. If not present, set
+--- to "".
+--- winid quickfix |window-ID|. If not present, set to 0
+---
+--- Examples (See also |getqflist-examples|): >vim
+--- echo getqflist({'all': 1})
+--- echo getqflist({'nr': 2, 'title': 1})
+--- echo getqflist({'lines' : ["F1:10:L10"]})
+--- <
+---
+--- @param what? any
+--- @return any
+function vim.fn.getqflist(what) end
+
+--- The result is a String, which is the contents of register
+--- {regname}. Example: >vim
+--- let cliptext = getreg('*')
+--- <When register {regname} was not set the result is an empty
+--- string.
+--- The {regname} argument must be a string.
+---
+--- getreg('=') returns the last evaluated value of the expression
+--- register. (For use in maps.)
+--- getreg('=', 1) returns the expression itself, so that it can
+--- be restored with |setreg()|. For other registers the extra
+--- argument is ignored, thus you can always give it.
+---
+--- If {list} is present and |TRUE|, the result type is changed
+--- to |List|. Each list item is one text line. Use it if you care
+--- about zero bytes possibly present inside register: without
+--- third argument both NLs and zero bytes are represented as NLs
+--- (see |NL-used-for-Nul|).
+--- When the register was not set an empty list is returned.
+---
+--- If {regname} is not specified, |v:register| is used.
+---
+--- @param regname? string
+--- @param list? any
+--- @return string|string[]
+function vim.fn.getreg(regname, list) end
+
+--- Returns detailed information about register {regname} as a
+--- Dictionary with the following entries:
+--- regcontents List of lines contained in register
+--- {regname}, like
+--- getreg({regname}, 1, 1).
+--- regtype the type of register {regname}, as in
+--- |getregtype()|.
+--- isunnamed Boolean flag, v:true if this register
+--- is currently pointed to by the unnamed
+--- register.
+--- points_to for the unnamed register, gives the
+--- single letter name of the register
+--- currently pointed to (see |quotequote|).
+--- For example, after deleting a line
+--- with `dd`, this field will be "1",
+--- which is the register that got the
+--- deleted text.
+---
+--- The {regname} argument is a string. If {regname} is invalid
+--- or not set, an empty Dictionary will be returned.
+--- If {regname} is not specified, |v:register| is used.
+--- The returned Dictionary can be passed to |setreg()|.
+---
+--- @param regname? string
+--- @return table
+function vim.fn.getreginfo(regname) end
+
+--- The result is a String, which is type of register {regname}.
+--- The value will be one of:
+--- "v" for |charwise| text
+--- "V" for |linewise| text
+--- "<CTRL-V>{width}" for |blockwise-visual| text
+--- "" for an empty or unknown register
+--- <CTRL-V> is one character with value 0x16.
+--- The {regname} argument is a string. If {regname} is not
+--- specified, |v:register| is used.
+---
+--- @param regname? string
+--- @return string
+function vim.fn.getregtype(regname) end
+
+--- Returns a |List| with information about all the sourced Vim
+--- scripts in the order they were sourced, like what
+--- `:scriptnames` shows.
+---
+--- The optional Dict argument {opts} supports the following
+--- optional items:
+--- name Script name match pattern. If specified,
+--- and "sid" is not specified, information about
+--- scripts with a name that match the pattern
+--- "name" are returned.
+--- sid Script ID |<SID>|. If specified, only
+--- information about the script with ID "sid" is
+--- returned and "name" is ignored.
+---
+--- Each item in the returned List is a |Dict| with the following
+--- items:
+--- autoload Always set to FALSE.
+--- functions List of script-local function names defined in
+--- the script. Present only when a particular
+--- script is specified using the "sid" item in
+--- {opts}.
+--- name Vim script file name.
+--- sid Script ID |<SID>|.
+--- variables A dictionary with the script-local variables.
+--- Present only when a particular script is
+--- specified using the "sid" item in {opts}.
+--- Note that this is a copy, the value of
+--- script-local variables cannot be changed using
+--- this dictionary.
+--- version Vim script version, always 1
+---
+--- Examples: >vim
+--- echo getscriptinfo({'name': 'myscript'})
+--- echo getscriptinfo({'sid': 15}).variables
+--- <
+---
+--- @param opts? table
+--- @return any
+function vim.fn.getscriptinfo(opts) end
+
+--- If {tabnr} is not specified, then information about all the
+--- tab pages is returned as a |List|. Each List item is a
+--- |Dictionary|. Otherwise, {tabnr} specifies the tab page
+--- number and information about that one is returned. If the tab
+--- page does not exist an empty List is returned.
+---
+--- Each List item is a |Dictionary| with the following entries:
+--- tabnr tab page number.
+--- variables a reference to the dictionary with
+--- tabpage-local variables
+--- windows List of |window-ID|s in the tab page.
+---
+--- @param tabnr? integer
+--- @return any
+function vim.fn.gettabinfo(tabnr) end
+
+--- Get the value of a tab-local variable {varname} in tab page
+--- {tabnr}. |t:var|
+--- Tabs are numbered starting with one.
+--- The {varname} argument is a string. When {varname} is empty a
+--- dictionary with all tab-local variables is returned.
+--- Note that the name without "t:" must be used.
+--- When the tab or variable doesn't exist {def} or an empty
+--- string is returned, there is no error message.
+---
+--- @param tabnr integer
+--- @param varname string
+--- @param def? any
+--- @return any
+function vim.fn.gettabvar(tabnr, varname, def) end
+
+--- Get the value of window-local variable {varname} in window
+--- {winnr} in tab page {tabnr}.
+--- The {varname} argument is a string. When {varname} is empty a
+--- dictionary with all window-local variables is returned.
+--- When {varname} is equal to "&" get the values of all
+--- window-local options in a |Dictionary|.
+--- Otherwise, when {varname} starts with "&" get the value of a
+--- window-local option.
+--- Note that {varname} must be the name without "w:".
+--- Tabs are numbered starting with one. For the current tabpage
+--- use |getwinvar()|.
+--- {winnr} can be the window number or the |window-ID|.
+--- When {winnr} is zero the current window is used.
+--- This also works for a global option, buffer-local option and
+--- window-local option, but it doesn't work for a global variable
+--- or buffer-local variable.
+--- When the tab, window or variable doesn't exist {def} or an
+--- empty string is returned, there is no error message.
+--- Examples: >vim
+--- let list_is_on = gettabwinvar(1, 2, '&list')
+--- echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
+--- <
+--- To obtain all window-local variables use: >vim
+--- gettabwinvar({tabnr}, {winnr}, '&')
+--- <
+---
+--- @param tabnr integer
+--- @param winnr integer
+--- @param varname string
+--- @param def? any
+--- @return any
+function vim.fn.gettabwinvar(tabnr, winnr, varname, def) end
+
+--- The result is a Dict, which is the tag stack of window {winnr}.
+--- {winnr} can be the window number or the |window-ID|.
+--- When {winnr} is not specified, the current window is used.
+--- When window {winnr} doesn't exist, an empty Dict is returned.
+---
+--- The returned dictionary contains the following entries:
+--- curidx Current index in the stack. When at
+--- top of the stack, set to (length + 1).
+--- Index of bottom of the stack is 1.
+--- items List of items in the stack. Each item
+--- is a dictionary containing the
+--- entries described below.
+--- length Number of entries in the stack.
+---
+--- Each item in the stack is a dictionary with the following
+--- entries:
+--- bufnr buffer number of the current jump
+--- from cursor position before the tag jump.
+--- See |getpos()| for the format of the
+--- returned list.
+--- matchnr current matching tag number. Used when
+--- multiple matching tags are found for a
+--- name.
+--- tagname name of the tag
+---
+--- See |tagstack| for more information about the tag stack.
+---
+--- @param winnr? integer
+--- @return any
+function vim.fn.gettagstack(winnr) end
+
+--- Translate String {text} if possible.
+--- This is mainly for use in the distributed Vim scripts. When
+--- generating message translations the {text} is extracted by
+--- xgettext, the translator can add the translated message in the
+--- .po file and Vim will lookup the translation when gettext() is
+--- called.
+--- For {text} double quoted strings are preferred, because
+--- xgettext does not understand escaping in single quoted
+--- strings.
+---
+--- @param text any
+--- @return any
+function vim.fn.gettext(text) end
+
+--- Returns information about windows as a |List| with Dictionaries.
+---
+--- If {winid} is given Information about the window with that ID
+--- is returned, as a |List| with one item. If the window does not
+--- exist the result is an empty list.
+---
+--- Without {winid} information about all the windows in all the
+--- tab pages is returned.
+---
+--- Each List item is a |Dictionary| with the following entries:
+--- botline last complete displayed buffer line
+--- bufnr number of buffer in the window
+--- height window height (excluding winbar)
+--- loclist 1 if showing a location list
+--- quickfix 1 if quickfix or location list window
+--- terminal 1 if a terminal window
+--- tabnr tab page number
+--- topline first displayed buffer line
+--- variables a reference to the dictionary with
+--- window-local variables
+--- width window width
+--- winbar 1 if the window has a toolbar, 0
+--- otherwise
+--- wincol leftmost screen column of the window;
+--- "col" from |win_screenpos()|
+--- textoff number of columns occupied by any
+--- 'foldcolumn', 'signcolumn' and line
+--- number in front of the text
+--- winid |window-ID|
+--- winnr window number
+--- winrow topmost screen line of the window;
+--- "row" from |win_screenpos()|
+---
+--- @param winid? integer
+--- @return vim.fn.getwininfo.ret.item[]
+function vim.fn.getwininfo(winid) end
+
+--- The result is a |List| with two numbers, the result of
+--- |getwinposx()| and |getwinposy()| combined:
+--- [x-pos, y-pos]
+--- {timeout} can be used to specify how long to wait in msec for
+--- a response from the terminal. When omitted 100 msec is used.
+---
+--- Use a longer time for a remote terminal.
+--- When using a value less than 10 and no response is received
+--- within that time, a previously reported position is returned,
+--- if available. This can be used to poll for the position and
+--- do some work in the meantime: >vim
+--- while 1
+--- let res = getwinpos(1)
+--- if res[0] >= 0
+--- break
+--- endif
+--- " Do some work here
+--- endwhile
+--- <
+---
+--- @param timeout? integer
+--- @return any
+function vim.fn.getwinpos(timeout) end
+
+--- The result is a Number, which is the X coordinate in pixels of
+--- the left hand side of the GUI Vim window. The result will be
+--- -1 if the information is not available.
+--- The value can be used with `:winpos`.
+---
+--- @return integer
+function vim.fn.getwinposx() end
+
+--- The result is a Number, which is the Y coordinate in pixels of
+--- the top of the GUI Vim window. The result will be -1 if the
+--- information is not available.
+--- The value can be used with `:winpos`.
+---
+--- @return integer
+function vim.fn.getwinposy() end
+
+--- Like |gettabwinvar()| for the current tabpage.
+--- Examples: >vim
+--- let list_is_on = getwinvar(2, '&list')
+--- echo "myvar = " .. getwinvar(1, 'myvar')
+---
+--- @param winnr integer
+--- @param varname string
+--- @param def? any
+--- @return any
+function vim.fn.getwinvar(winnr, varname, def) end
+
+--- Expand the file wildcards in {expr}. See |wildcards| for the
+--- use of special characters.
+---
+--- Unless the optional {nosuf} argument is given and is |TRUE|,
+--- the 'suffixes' and 'wildignore' options apply: Names matching
+--- one of the patterns in 'wildignore' will be skipped and
+--- 'suffixes' affect the ordering of matches.
+--- 'wildignorecase' always applies.
+---
+--- When {list} is present and it is |TRUE| the result is a |List|
+--- with all matching files. The advantage of using a List is,
+--- you also get filenames containing newlines correctly.
+--- Otherwise the result is a String and when there are several
+--- matches, they are separated by <NL> characters.
+---
+--- If the expansion fails, the result is an empty String or List.
+---
+--- You can also use |readdir()| if you need to do complicated
+--- things, such as limiting the number of matches.
+---
+--- A name for a non-existing file is not included. A symbolic
+--- link is only included if it points to an existing file.
+--- However, when the {alllinks} argument is present and it is
+--- |TRUE| then all symbolic links are included.
+---
+--- For most systems backticks can be used to get files names from
+--- any external command. Example: >vim
+--- let tagfiles = glob("`find . -name tags -print`")
+--- let &tags = substitute(tagfiles, "\n", ",", "g")
+--- <The result of the program inside the backticks should be one
+--- item per line. Spaces inside an item are allowed.
+---
+--- See |expand()| for expanding special Vim variables. See
+--- |system()| for getting the raw output of an external command.
+---
+--- @param expr any
+--- @param nosuf? boolean
+--- @param list? any
+--- @param alllinks? any
+--- @return any
+function vim.fn.glob(expr, nosuf, list, alllinks) end
+
+--- Convert a file pattern, as used by glob(), into a search
+--- pattern. The result can be used to match with a string that
+--- is a file name. E.g. >vim
+--- if filename =~ glob2regpat('Make*.mak')
+--- " ...
+--- endif
+--- <This is equivalent to: >vim
+--- if filename =~ '^Make.*\.mak$'
+--- " ...
+--- endif
+--- <When {string} is an empty string the result is "^$", match an
+--- empty string.
+--- Note that the result depends on the system. On MS-Windows
+--- a backslash usually means a path separator.
+---
+--- @param string string
+--- @return any
+function vim.fn.glob2regpat(string) end
+
+--- Perform glob() for String {expr} on all directories in {path}
+--- and concatenate the results. Example: >vim
+--- echo globpath(&rtp, "syntax/c.vim")
+--- <
+--- {path} is a comma-separated list of directory names. Each
+--- directory name is prepended to {expr} and expanded like with
+--- |glob()|. A path separator is inserted when needed.
+--- To add a comma inside a directory name escape it with a
+--- backslash. Note that on MS-Windows a directory may have a
+--- trailing backslash, remove it if you put a comma after it.
+--- If the expansion fails for one of the directories, there is no
+--- error message.
+---
+--- Unless the optional {nosuf} argument is given and is |TRUE|,
+--- the 'suffixes' and 'wildignore' options apply: Names matching
+--- one of the patterns in 'wildignore' will be skipped and
+--- 'suffixes' affect the ordering of matches.
+---
+--- When {list} is present and it is |TRUE| the result is a |List|
+--- with all matching files. The advantage of using a List is, you
+--- also get filenames containing newlines correctly. Otherwise
+--- the result is a String and when there are several matches,
+--- they are separated by <NL> characters. Example: >vim
+--- echo globpath(&rtp, "syntax/c.vim", 0, 1)
+--- <
+--- {allinks} is used as with |glob()|.
+---
+--- The "**" item can be used to search in a directory tree.
+--- For example, to find all "README.txt" files in the directories
+--- in 'runtimepath' and below: >vim
+--- echo globpath(&rtp, "**/README.txt")
+--- <Upwards search and limiting the depth of "**" is not
+--- supported, thus using 'path' will not always work properly.
+---
+--- @param path string
+--- @param expr any
+--- @param nosuf? boolean
+--- @param list? any
+--- @param allinks? any
+--- @return any
+function vim.fn.globpath(path, expr, nosuf, list, allinks) end
+
+--- Returns 1 if {feature} is supported, 0 otherwise. The
+--- {feature} argument is a feature name like "nvim-0.2.1" or
+--- "win32", see below. See also |exists()|.
+---
+--- To get the system name use |vim.uv|.os_uname() in Lua: >lua
+--- print(vim.uv.os_uname().sysname)
+---
+--- <If the code has a syntax error then Vimscript may skip the
+--- rest of the line. Put |:if| and |:endif| on separate lines to
+--- avoid the syntax error: >vim
+--- if has('feature')
+--- let x = this_breaks_without_the_feature()
+--- endif
+--- <
+--- Vim's compile-time feature-names (prefixed with "+") are not
+--- recognized because Nvim is always compiled with all possible
+--- features. |feature-compile|
+---
+--- Feature names can be:
+--- 1. Nvim version. For example the "nvim-0.2.1" feature means
+--- that Nvim is version 0.2.1 or later: >vim
+--- if has("nvim-0.2.1")
+--- " ...
+--- endif
+---
+--- <2. Runtime condition or other pseudo-feature. For example the
+--- "win32" feature checks if the current system is Windows: >vim
+--- if has("win32")
+--- " ...
+--- endif
+--- < *feature-list*
+--- List of supported pseudo-feature names:
+--- acl |ACL| support.
+--- bsd BSD system (not macOS, use "mac" for that).
+--- clipboard |clipboard| provider is available.
+--- fname_case Case in file names matters (for Darwin and MS-Windows
+--- this is not present).
+--- gui_running Nvim has a GUI.
+--- 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|
+--- 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.
+---
+--- *has-patch*
+--- 3. Vim patch. For example the "patch123" feature means that
+--- Vim patch 123 at the current |v:version| was included: >vim
+--- if v:version > 602 || v:version == 602 && has("patch148")
+--- " ...
+--- endif
+---
+--- <4. Vim version. For example the "patch-7.4.237" feature means
+--- that Nvim is Vim-compatible to version 7.4.237 or later. >vim
+--- if has("patch-7.4.237")
+--- " ...
+--- endif
+--- <
+---
+--- @param feature any
+--- @return 0|1
+function vim.fn.has(feature) end
+
+--- The result is a Number, which is TRUE if |Dictionary| {dict}
+--- has an entry with key {key}. FALSE otherwise. The {key}
+--- argument is a string.
+---
+--- @param dict any
+--- @param key any
+--- @return 0|1
+function vim.fn.has_key(dict, key) end
+
+--- 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.
+---
+--- Tabs and windows are identified by their respective numbers,
+--- 0 means current tab or window. Missing argument implies 0.
+--- Thus the following are equivalent: >vim
+--- echo haslocaldir()
+--- echo haslocaldir(0)
+--- echo haslocaldir(0, 0)
+--- <With {winnr} use that window in the current tabpage.
+--- With {winnr} and {tabnr} use the window in that tabpage.
+--- {winnr} can be the window number or the |window-ID|.
+--- If {winnr} is -1 it is ignored, only the tab is resolved.
+--- Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+---
+--- @param winnr? integer
+--- @param tabnr? integer
+--- @return 0|1
+function vim.fn.haslocaldir(winnr, tabnr) end
+
+--- The result is a Number, which is TRUE if there is a mapping
+--- that contains {what} in somewhere in the rhs (what it is
+--- mapped to) and this mapping exists in one of the modes
+--- indicated by {mode}.
+--- The arguments {what} and {mode} are strings.
+--- When {abbr} is there and it is |TRUE| use abbreviations
+--- instead of mappings. Don't forget to specify Insert and/or
+--- Command-line mode.
+--- Both the global mappings and the mappings local to the current
+--- buffer are checked for a match.
+--- If no matching mapping is found FALSE is returned.
+--- The following characters are recognized in {mode}:
+--- n Normal mode
+--- v Visual and Select mode
+--- x Visual mode
+--- s Select mode
+--- o Operator-pending mode
+--- i Insert mode
+--- l Language-Argument ("r", "f", "t", etc.)
+--- c Command-line mode
+--- When {mode} is omitted, "nvo" is used.
+---
+--- This function is useful to check if a mapping already exists
+--- to a function in a Vim script. Example: >vim
+--- if !hasmapto('\ABCdoit')
+--- map <Leader>d \ABCdoit
+--- endif
+--- <This installs the mapping to "\ABCdoit" only if there isn't
+--- already a mapping to "\ABCdoit".
+---
+--- @param what any
+--- @param mode? string
+--- @param abbr? any
+--- @return 0|1
+function vim.fn.hasmapto(what, mode, abbr) end
+
+--- @deprecated
+--- Obsolete name for |hlID()|.
+---
+--- @param name string
+--- @return any
+function vim.fn.highlightID(name) end
+
+--- @deprecated
+--- Obsolete name for |hlexists()|.
+---
+--- @param name string
+--- @return any
+function vim.fn.highlight_exists(name) end
+
+--- Add the String {item} to the history {history} which can be
+--- one of: *hist-names*
+--- "cmd" or ":" command line history
+--- "search" or "/" search pattern history
+--- "expr" or "=" typed expression history
+--- "input" or "\@" input line history
+--- "debug" or ">" debug command history
+--- empty the current or last used history
+--- The {history} string does not need to be the whole name, one
+--- character is sufficient.
+--- If {item} does already exist in the history, it will be
+--- shifted to become the newest entry.
+--- The result is a Number: TRUE if the operation was successful,
+--- otherwise FALSE is returned.
+---
+--- Example: >vim
+--- call histadd("input", strftime("%Y %b %d"))
+--- let date=input("Enter date: ")
+--- <This function is not available in the |sandbox|.
+---
+--- @param history any
+--- @param item any
+--- @return 0|1
+function vim.fn.histadd(history, item) end
+
+--- Clear {history}, i.e. delete all its entries. See |hist-names|
+--- for the possible values of {history}.
+---
+--- If the parameter {item} evaluates to a String, it is used as a
+--- regular expression. All entries matching that expression will
+--- be removed from the history (if there are any).
+--- Upper/lowercase must match, unless "\c" is used |/\c|.
+--- If {item} evaluates to a Number, it will be interpreted as
+--- an index, see |:history-indexing|. The respective entry will
+--- be removed if it exists.
+---
+--- The result is TRUE for a successful operation, otherwise FALSE
+--- is returned.
+---
+--- Examples:
+--- Clear expression register history: >vim
+--- call histdel("expr")
+--- <
+--- Remove all entries starting with "*" from the search history: >vim
+--- call histdel("/", '^\*')
+--- <
+--- The following three are equivalent: >vim
+--- call histdel("search", histnr("search"))
+--- call histdel("search", -1)
+--- call histdel("search", '^' .. histget("search", -1) .. '$')
+--- <
+--- To delete the last search pattern and use the last-but-one for
+--- the "n" command and 'hlsearch': >vim
+--- call histdel("search", -1)
+--- let \@/ = histget("search", -1)
+--- <
+---
+--- @param history any
+--- @param item? any
+--- @return 0|1
+function vim.fn.histdel(history, item) end
+
+--- The result is a String, the entry with Number {index} from
+--- {history}. See |hist-names| for the possible values of
+--- {history}, and |:history-indexing| for {index}. If there is
+--- no such entry, an empty String is returned. When {index} is
+--- omitted, the most recent item from the history is used.
+---
+--- Examples:
+--- Redo the second last search from history. >vim
+--- execute '/' .. histget("search", -2)
+---
+--- <Define an Ex command ":H {num}" that supports re-execution of
+--- the {num}th entry from the output of |:history|. >vim
+--- command -nargs=1 H execute histget("cmd", 0+<args>)
+--- <
+---
+--- @param history any
+--- @param index? any
+--- @return string
+function vim.fn.histget(history, index) end
+
+--- The result is the Number of the current entry in {history}.
+--- See |hist-names| for the possible values of {history}.
+--- If an error occurred, -1 is returned.
+---
+--- Example: >vim
+--- let inp_index = histnr("expr")
+---
+--- @param history any
+--- @return integer
+function vim.fn.histnr(history) end
+
+--- The result is a Number, which is the ID of the highlight group
+--- with name {name}. When the highlight group doesn't exist,
+--- zero is returned.
+--- This can be used to retrieve information about the highlight
+--- group. For example, to get the background color of the
+--- "Comment" group: >vim
+--- echo synIDattr(synIDtrans(hlID("Comment")), "bg")
+--- <
+---
+--- @param name string
+--- @return integer
+function vim.fn.hlID(name) end
+
+--- The result is a Number, which is TRUE if a highlight group
+--- called {name} exists. This is when the group has been
+--- defined in some way. Not necessarily when highlighting has
+--- been defined for it, it may also have been used for a syntax
+--- item.
+---
+--- @param name string
+--- @return 0|1
+function vim.fn.hlexists(name) end
+
+--- The result is a String, which is the name of the machine on
+--- which Vim is currently running. Machine names greater than
+--- 256 characters long are truncated.
+---
+--- @return string
+function vim.fn.hostname() end
+
+--- The result is a String, which is the text {string} converted
+--- from encoding {from} to encoding {to}.
+--- When the conversion completely fails an empty string is
+--- returned. When some characters could not be converted they
+--- are replaced with "?".
+--- The encoding names are whatever the iconv() library function
+--- can accept, see ":!man 3 iconv".
+--- Note that Vim uses UTF-8 for all Unicode encodings, conversion
+--- from/to UCS-2 is automatically changed to use UTF-8. You
+--- cannot use UCS-2 in a string anyway, because of the NUL bytes.
+---
+--- @param string string
+--- @param from any
+--- @param to any
+--- @return any
+function vim.fn.iconv(string, from, to) end
+
+--- Returns a |String| which is a unique identifier of the
+--- container type (|List|, |Dict|, |Blob| and |Partial|). It is
+--- guaranteed that for the mentioned types `id(v1) ==# id(v2)`
+--- returns true iff `type(v1) == type(v2) && v1 is v2`.
+--- Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and
+--- `v:_null_blob` have the same `id()` with different types
+--- because they are internally represented as NULL pointers.
+--- `id()` returns a hexadecimal representanion of the pointers to
+--- the containers (i.e. like `0x994a40`), same as `printf("%p",
+--- {expr})`, but it is advised against counting on the exact
+--- format of the return value.
+---
+--- It is not guaranteed that `id(no_longer_existing_container)`
+--- will not be equal to some other `id()`: new containers may
+--- reuse identifiers of the garbage-collected ones.
+---
+--- @param expr any
+--- @return any
+function vim.fn.id(expr) end
+
+--- The result is a Number, which is indent of line {lnum} in the
+--- current buffer. The indent is counted in spaces, the value
+--- of 'tabstop' is relevant. {lnum} is used just like in
+--- |getline()|.
+--- When {lnum} is invalid -1 is returned.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.indent(lnum) end
+
+--- Find {expr} in {object} and return its index. See
+--- |indexof()| for using a lambda to select the item.
+---
+--- If {object} is a |List| return the lowest index where the item
+--- has a value equal to {expr}. There is no automatic
+--- conversion, so the String "4" is different from the Number 4.
+--- And the Number 4 is different from the Float 4.0. The value
+--- of 'ignorecase' is not used here, case matters as indicated by
+--- the {ic} argument.
+---
+--- If {object} is a |Blob| return the lowest index where the byte
+--- value is equal to {expr}.
+---
+--- If {start} is given then start looking at the item with index
+--- {start} (may be negative for an item relative to the end).
+---
+--- When {ic} is given and it is |TRUE|, ignore case. Otherwise
+--- case must match.
+---
+--- -1 is returned when {expr} is not found in {object}.
+--- Example: >vim
+--- let idx = index(words, "the")
+--- if index(numbers, 123) >= 0
+--- " ...
+--- endif
+---
+--- @param object any
+--- @param expr any
+--- @param start? any
+--- @param ic? any
+--- @return any
+function vim.fn.index(object, expr, start, ic) end
+
+--- Returns the index of an item in {object} where {expr} is
+--- v:true. {object} must be a |List| or a |Blob|.
+---
+--- If {object} is a |List|, evaluate {expr} for each item in the
+--- List until the expression is v:true and return the index of
+--- this item.
+---
+--- If {object} is a |Blob| evaluate {expr} for each byte in the
+--- Blob until the expression is v:true and return the index of
+--- this byte.
+---
+--- {expr} must be a |string| or |Funcref|.
+---
+--- If {expr} is a |string|: If {object} is a |List|, inside
+--- {expr} |v:key| has the index of the current List item and
+--- |v:val| has the value of the item. If {object} is a |Blob|,
+--- inside {expr} |v:key| has the index of the current byte and
+--- |v:val| has the byte value.
+---
+--- If {expr} is a |Funcref| it must take two arguments:
+--- 1. the key or the index of the current item.
+--- 2. the value of the current item.
+--- The function must return |TRUE| if the item is found and the
+--- search should stop.
+---
+--- The optional argument {opts} is a Dict and supports the
+--- following items:
+--- startidx start evaluating {expr} at the item with this
+--- index; may be negative for an item relative to
+--- the end
+--- Returns -1 when {expr} evaluates to v:false for all the items.
+--- Example: >vim
+--- let l = [#{n: 10}, #{n: 20}, #{n: 30}]
+--- echo indexof(l, "v:val.n == 20")
+--- echo indexof(l, {i, v -> v.n == 30})
+--- echo indexof(l, "v:val.n == 20", #{startidx: 1})
+---
+--- @param object any
+--- @param expr any
+--- @param opts? table
+--- @return any
+function vim.fn.indexof(object, expr, opts) end
+
+---
+--- @param prompt any
+--- @param text? any
+--- @param completion? any
+--- @return any
+function vim.fn.input(prompt, text, completion) end
+
+--- The result is a String, which is whatever the user typed on
+--- the command-line. The {prompt} argument is either a prompt
+--- string, or a blank string (for no prompt). A '\n' can be used
+--- in the prompt to start a new line.
+---
+--- In the second form it accepts a single dictionary with the
+--- following keys, any of which may be omitted:
+---
+--- Key Default Description ~
+--- prompt "" Same as {prompt} in the first form.
+--- default "" Same as {text} in the first form.
+--- completion nothing Same as {completion} in the first form.
+--- cancelreturn "" The value returned when the dialog is
+--- cancelled.
+--- highlight nothing Highlight handler: |Funcref|.
+---
+--- The highlighting set with |:echohl| is used for the prompt.
+--- The input is entered just like a command-line, with the same
+--- editing commands and mappings. There is a separate history
+--- for lines typed for input().
+--- Example: >vim
+--- if input("Coffee or beer? ") == "beer"
+--- echo "Cheers!"
+--- endif
+--- <
+--- If the optional {text} argument is present and not empty, this
+--- is used for the default reply, as if the user typed this.
+--- Example: >vim
+--- let color = input("Color? ", "white")
+---
+--- <The optional {completion} argument specifies the type of
+--- completion supported for the input. Without it completion is
+--- not performed. The supported completion types are the same as
+--- that can be supplied to a user-defined command using the
+--- "-complete=" argument. Refer to |:command-completion| for
+--- more information. Example: >vim
+--- let fname = input("File: ", "", "file")
+---
+--- < *input()-highlight* *E5400* *E5402*
+--- The optional `highlight` key allows specifying function which
+--- will be used for highlighting user input. This function
+--- receives user input as its only argument and must return
+--- a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group]
+--- where
+--- hl_start_col is the first highlighted column,
+--- hl_end_col is the last highlighted column (+ 1!),
+--- hl_group is |:hi| group used for highlighting.
+--- *E5403* *E5404* *E5405* *E5406*
+--- Both hl_start_col and hl_end_col + 1 must point to the start
+--- of the multibyte character (highlighting must not break
+--- multibyte characters), hl_end_col + 1 may be equal to the
+--- input length. Start column must be in range [0, len(input)),
+--- end column must be in range (hl_start_col, len(input)],
+--- sections must be ordered so that next hl_start_col is greater
+--- then or equal to previous hl_end_col.
+---
+--- Example (try some input with parentheses): >vim
+--- highlight RBP1 guibg=Red ctermbg=red
+--- highlight RBP2 guibg=Yellow ctermbg=yellow
+--- highlight RBP3 guibg=Green ctermbg=green
+--- highlight RBP4 guibg=Blue ctermbg=blue
+--- let g:rainbow_levels = 4
+--- function! RainbowParens(cmdline)
+--- let ret = []
+--- let i = 0
+--- let lvl = 0
+--- while i < len(a:cmdline)
+--- if a:cmdline[i] is# '('
+--- call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+--- let lvl += 1
+--- elseif a:cmdline[i] is# ')'
+--- let lvl -= 1
+--- call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+--- endif
+--- let i += 1
+--- endwhile
+--- return ret
+--- endfunction
+--- call input({'prompt':'>','highlight':'RainbowParens'})
+--- <
+--- Highlight function is called at least once for each new
+--- displayed input string, before command-line is redrawn. It is
+--- expected that function is pure for the duration of one input()
+--- call, i.e. it produces the same output for the same input, so
+--- output may be memoized. Function is run like under |:silent|
+--- modifier. If the function causes any errors, it will be
+--- skipped for the duration of the current input() call.
+---
+--- Highlighting is disabled if command-line contains arabic
+--- characters.
+---
+--- NOTE: This function must not be used in a startup file, for
+--- the versions that only run in GUI mode (e.g., the Win32 GUI).
+--- Note: When input() is called from within a mapping it will
+--- consume remaining characters from that mapping, because a
+--- mapping is handled like the characters were typed.
+--- Use |inputsave()| before input() and |inputrestore()|
+--- after input() to avoid that. Another solution is to avoid
+--- that further characters follow in the mapping, e.g., by using
+--- |:execute| or |:normal|.
+---
+--- Example with a mapping: >vim
+--- nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR>
+--- function GetFoo()
+--- call inputsave()
+--- let g:Foo = input("enter search pattern: ")
+--- call inputrestore()
+--- endfunction
+---
+--- @param opts table
+--- @return any
+function vim.fn.input(opts) end
+
+--- @deprecated
+--- Use |input()| instead.
+---
+--- @param ... any
+--- @return any
+function vim.fn.inputdialog(...) end
+
+--- {textlist} must be a |List| of strings. This |List| is
+--- displayed, one string per line. The user will be prompted to
+--- enter a number, which is returned.
+--- The user can also select an item by clicking on it with the
+--- mouse, if the mouse is enabled in the command line ('mouse' is
+--- "a" or includes "c"). For the first string 0 is returned.
+--- When clicking above the first item a negative number is
+--- returned. When clicking on the prompt one more than the
+--- length of {textlist} is returned.
+--- Make sure {textlist} has less than 'lines' entries, otherwise
+--- it won't work. It's a good idea to put the entry number at
+--- the start of the string. And put a prompt in the first item.
+--- Example: >vim
+--- let color = inputlist(['Select color:', '1. red',
+--- \ '2. green', '3. blue'])
+---
+--- @param textlist any
+--- @return any
+function vim.fn.inputlist(textlist) end
+
+--- Restore typeahead that was saved with a previous |inputsave()|.
+--- Should be called the same number of times inputsave() is
+--- called. Calling it more often is harmless though.
+--- Returns TRUE when there is nothing to restore, FALSE otherwise.
+---
+--- @return any
+function vim.fn.inputrestore() end
+
+--- Preserve typeahead (also from mappings) and clear it, so that
+--- a following prompt gets input from the user. Should be
+--- followed by a matching inputrestore() after the prompt. Can
+--- be used several times, in which case there must be just as
+--- many inputrestore() calls.
+--- Returns TRUE when out of memory, FALSE otherwise.
+---
+--- @return any
+function vim.fn.inputsave() end
+
+--- This function acts much like the |input()| function with but
+--- two exceptions:
+--- a) the user's response will be displayed as a sequence of
+--- asterisks ("*") thereby keeping the entry secret, and
+--- b) the user's response will not be recorded on the input
+--- |history| stack.
+--- The result is a String, which is whatever the user actually
+--- typed on the command-line in response to the issued prompt.
+--- NOTE: Command-line completion is not supported.
+---
+--- @param prompt any
+--- @param text? any
+--- @return any
+function vim.fn.inputsecret(prompt, text) end
+
+--- When {object} is a |List| or a |Blob| insert {item} at the start
+--- of it.
+---
+--- If {idx} is specified insert {item} before the item with index
+--- {idx}. If {idx} is zero it goes before the first item, just
+--- like omitting {idx}. A negative {idx} is also possible, see
+--- |list-index|. -1 inserts just before the last item.
+---
+--- Returns the resulting |List| or |Blob|. Examples: >vim
+--- let mylist = insert([2, 3, 5], 1)
+--- call insert(mylist, 4, -1)
+--- call insert(mylist, 6, len(mylist))
+--- <The last example can be done simpler with |add()|.
+--- Note that when {item} is a |List| it is inserted as a single
+--- item. Use |extend()| to concatenate |Lists|.
+---
+--- @param object any
+--- @param item any
+--- @param idx? integer
+--- @return any
+function vim.fn.insert(object, item, idx) end
+
+--- Interrupt script execution. It works more or less like the
+--- user typing CTRL-C, most commands won't execute and control
+--- returns to the user. This is useful to abort execution
+--- from lower down, e.g. in an autocommand. Example: >vim
+--- function s:check_typoname(file)
+--- if fnamemodify(a:file, ':t') == '['
+--- echomsg 'Maybe typo'
+--- call interrupt()
+--- endif
+--- endfunction
+--- au BufWritePre * call s:check_typoname(expand('<amatch>'))
+--- <
+---
+--- @return any
+function vim.fn.interrupt() end
+
+--- Bitwise invert. The argument is converted to a number. A
+--- List, Dict or Float argument causes an error. Example: >vim
+--- let bits = invert(bits)
+--- <
+---
+--- @param expr any
+--- @return any
+function vim.fn.invert(expr) end
+
+--- The result is a Number, which is |TRUE| when a directory
+--- with the name {directory} exists. If {directory} doesn't
+--- exist, or isn't a directory, the result is |FALSE|. {directory}
+--- is any expression, which is used as a String.
+---
+--- @param directory any
+--- @return 0|1
+function vim.fn.isdirectory(directory) end
+
+--- Return 1 if {expr} is a positive infinity, or -1 a negative
+--- infinity, otherwise 0. >vim
+--- echo isinf(1.0 / 0.0)
+--- < 1 >vim
+--- echo isinf(-1.0 / 0.0)
+--- < -1
+---
+--- @param expr any
+--- @return 1|0|-1
+function vim.fn.isinf(expr) end
+
+--- The result is a Number, which is |TRUE| when {expr} is the
+--- name of a locked variable.
+--- The string argument {expr} must be the name of a variable,
+--- |List| item or |Dictionary| entry, not the variable itself!
+--- Example: >vim
+--- let alist = [0, ['a', 'b'], 2, 3]
+--- lockvar 1 alist
+--- echo islocked('alist') " 1
+--- echo islocked('alist[1]') " 0
+---
+--- <When {expr} is a variable that does not exist you get an error
+--- message. Use |exists()| to check for existence.
+---
+--- @param expr any
+--- @return 0|1
+function vim.fn.islocked(expr) end
+
+--- Return |TRUE| if {expr} is a float with value NaN. >vim
+--- echo isnan(0.0 / 0.0)
+--- < 1
+---
+--- @param expr any
+--- @return 0|1
+function vim.fn.isnan(expr) end
+
+--- Return a |List| with all the key-value pairs of {dict}. Each
+--- |List| item is a list with two items: the key of a {dict}
+--- entry and the value of this entry. The |List| is in arbitrary
+--- order. Also see |keys()| and |values()|.
+--- Example: >vim
+--- for [key, value] in items(mydict)
+--- echo key .. ': ' .. value
+--- endfor
+---
+--- @param dict any
+--- @return any
+function vim.fn.items(dict) end
+
+--- @deprecated
+--- Obsolete name for |chanclose()|
+---
+--- @param ... any
+--- @return any
+function vim.fn.jobclose(...) end
+
+--- Return the PID (process id) of |job-id| {job}.
+---
+--- @param job any
+--- @return integer
+function vim.fn.jobpid(job) end
+
+--- Resize the pseudo terminal window of |job-id| {job} to {width}
+--- columns and {height} rows.
+--- Fails if the job was not started with `"pty":v:true`.
+---
+--- @param job any
+--- @param width integer
+--- @param height integer
+--- @return any
+function vim.fn.jobresize(job, width, height) end
+
+--- @deprecated
+--- Obsolete name for |chansend()|
+---
+--- @param ... any
+--- @return any
+function vim.fn.jobsend(...) end
+
+--- Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+---
+--- 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: >vim
+--- call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+--- <(See |shell-unquoting| for details.)
+---
+--- Example: >vim
+--- call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+--- <
+--- Returns |job-id| on success, 0 on invalid arguments (or job
+--- table is full), -1 if {cmd}[0] or 'shell' is not executable.
+--- The returned job-id is a valid |channel-id| representing the
+--- job's stdio streams. Use |chansend()| (or |rpcnotify()| and
+--- |rpcrequest()| if "rpc" was enabled) to send data to stdin and
+--- |chanclose()| to close the streams without stopping the job.
+---
+--- See |job-control| and |RPC|.
+---
+--- NOTE: on Windows if {cmd} is a List:
+--- - cmd[0] must be an executable (not a "built-in"). If it is
+--- in $PATH it can be called by name, without an extension: >vim
+--- call jobstart(['ping', 'neovim.io'])
+--- < If it is a full or partial path, extension is required: >vim
+--- call jobstart(['System32\ping.exe', 'neovim.io'])
+--- < - {cmd} is collapsed to a string of quoted args as expected
+--- by CommandLineToArgvW https://msdn.microsoft.com/bb776391
+--- unless cmd[0] is some form of "cmd.exe".
+---
+--- *jobstart-env*
+--- The job environment is initialized as follows:
+--- $NVIM is set to |v:servername| of the parent Nvim
+--- $NVIM_LISTEN_ADDRESS is unset
+--- $NVIM_LOG_FILE is unset
+--- $VIM is unset
+--- $VIMRUNTIME is unset
+--- You can set these with the `env` option.
+---
+--- *jobstart-options*
+--- {opts} is a dictionary with these keys:
+--- clear_env: (boolean) `env` defines the job environment
+--- exactly, instead of merging current environment.
+--- cwd: (string, default=|current-directory|) Working
+--- directory of the job.
+--- detach: (boolean) Detach the job process: it will not be
+--- killed when Nvim exits. If the process exits
+--- before Nvim, `on_exit` will be invoked.
+--- env: (dict) Map of environment variable name:value
+--- pairs extending (or replace with "clear_env")
+--- the current environment. |jobstart-env|
+--- height: (number) Height of the `pty` terminal.
+--- |on_exit|: (function) Callback invoked when the job exits.
+--- |on_stdout|: (function) Callback invoked when the job emits
+--- stdout data.
+--- |on_stderr|: (function) Callback invoked when the job emits
+--- stderr data.
+--- overlapped: (boolean) Sets FILE_FLAG_OVERLAPPED for the
+--- stdio passed to the child process. Only on
+--- MS-Windows; ignored on other platforms.
+--- pty: (boolean) Connect the job to a new pseudo
+--- terminal, and its streams to the master file
+--- descriptor. `on_stdout` receives all output,
+--- `on_stderr` is ignored. |terminal-start|
+--- rpc: (boolean) Use |msgpack-rpc| to communicate with
+--- the job over stdio. Then `on_stdout` is ignored,
+--- but `on_stderr` can still be used.
+--- stderr_buffered: (boolean) Collect data until EOF (stream closed)
+--- before invoking `on_stderr`. |channel-buffered|
+--- stdout_buffered: (boolean) Collect data until EOF (stream
+--- closed) before invoking `on_stdout`. |channel-buffered|
+--- stdin: (string) Either "pipe" (default) to connect the
+--- job's stdin to a channel or "null" to disconnect
+--- stdin.
+--- width: (number) Width of the `pty` terminal.
+---
+--- {opts} is passed as |self| dictionary to the callback; the
+--- caller may set other keys to pass application-specific data.
+---
+--- Returns:
+--- - |channel-id| on success
+--- - 0 on invalid arguments
+--- - -1 if {cmd}[0] is not executable.
+--- See also |job-control|, |channel|, |msgpack-rpc|.
+---
+--- @param cmd any
+--- @param opts? table
+--- @return any
+function vim.fn.jobstart(cmd, opts) end
+
+--- Stop |job-id| {id} by sending SIGTERM to the job process. If
+--- the process does not terminate after a timeout then SIGKILL
+--- will be sent. When the job terminates its |on_exit| handler
+--- (if any) will be invoked.
+--- See |job-control|.
+---
+--- Returns 1 for valid job id, 0 for invalid id, including jobs have
+--- exited or stopped.
+---
+--- @param id any
+--- @return any
+function vim.fn.jobstop(id) end
+
+--- Waits for jobs and their |on_exit| handlers to complete.
+---
+--- {jobs} is a List of |job-id|s to wait for.
+--- {timeout} is the maximum waiting time in milliseconds. If
+--- omitted or -1, wait forever.
+---
+--- Timeout of 0 can be used to check the status of a job: >vim
+--- let running = jobwait([{job-id}], 0)[0] == -1
+--- <
+--- During jobwait() callbacks for jobs not in the {jobs} list may
+--- be invoked. The screen will not redraw unless |:redraw| is
+--- invoked by a callback.
+---
+--- Returns a list of len({jobs}) integers, where each integer is
+--- the status of the corresponding job:
+--- Exit-code, if the job exited
+--- -1 if the timeout was exceeded
+--- -2 if the job was interrupted (by |CTRL-C|)
+--- -3 if the job-id is invalid
+---
+--- @param jobs any
+--- @param timeout? integer
+--- @return any
+function vim.fn.jobwait(jobs, timeout) end
+
+--- Join the items in {list} together into one String.
+--- When {sep} is specified it is put in between the items. If
+--- {sep} is omitted a single space is used.
+--- Note that {sep} is not added at the end. You might want to
+--- add it there too: >vim
+--- let lines = join(mylist, "\n") .. "\n"
+--- <String items are used as-is. |Lists| and |Dictionaries| are
+--- converted into a string like with |string()|.
+--- The opposite function is |split()|.
+---
+--- @param list any
+--- @param sep? any
+--- @return any
+function vim.fn.join(list, sep) end
+
+--- Convert {expr} from JSON object. Accepts |readfile()|-style
+--- list as the input, as well as regular string. May output any
+--- Vim value. In the following cases it will output
+--- |msgpack-special-dict|:
+--- 1. Dictionary contains duplicate key.
+--- 2. Dictionary contains empty key.
+--- 3. String contains NUL byte. Two special dictionaries: for
+--- dictionary and for string will be emitted in case string
+--- with NUL byte was a dictionary key.
+---
+--- Note: function treats its input as UTF-8 always. The JSON
+--- standard allows only a few encodings, of which UTF-8 is
+--- recommended and the only one required to be supported.
+--- Non-UTF-8 characters are an error.
+---
+--- @param expr any
+--- @return any
+function vim.fn.json_decode(expr) end
+
+--- Convert {expr} into a JSON string. Accepts
+--- |msgpack-special-dict| as the input. Will not convert
+--- |Funcref|s, mappings with non-string keys (can be created as
+--- |msgpack-special-dict|), values with self-referencing
+--- containers, strings which contain non-UTF-8 characters,
+--- pseudo-UTF-8 strings which contain codepoints reserved for
+--- surrogate pairs (such strings are not valid UTF-8 strings).
+--- Non-printable characters are converted into "\u1234" escapes
+--- or special escapes like "\t", other are dumped as-is.
+--- |Blob|s are converted to arrays of the individual bytes.
+---
+--- @param expr any
+--- @return any
+function vim.fn.json_encode(expr) end
+
+--- Return a |List| with all the keys of {dict}. The |List| is in
+--- arbitrary order. Also see |items()| and |values()|.
+---
+--- @param dict any
+--- @return any
+function vim.fn.keys(dict) end
+
+--- Turn the internal byte representation of keys into a form that
+--- can be used for |:map|. E.g. >vim
+--- let xx = "\<C-Home>"
+--- echo keytrans(xx)
+--- < <C-Home>
+---
+--- @param string string
+--- @return any
+function vim.fn.keytrans(string) end
+
+--- @deprecated
+--- Obsolete name for bufnr("$").
+---
+--- @return any
+function vim.fn.last_buffer_nr() end
+
+--- The result is a Number, which is the length of the argument.
+--- When {expr} is a String or a Number the length in bytes is
+--- used, as with |strlen()|.
+--- When {expr} is a |List| the number of items in the |List| is
+--- returned.
+--- When {expr} is a |Blob| the number of bytes is returned.
+--- When {expr} is a |Dictionary| the number of entries in the
+--- |Dictionary| is returned.
+--- Otherwise an error is given and returns zero.
+---
+--- @param expr any
+--- @return any
+function vim.fn.len(expr) end
+
+--- Call function {funcname} in the run-time library {libname}
+--- with single argument {argument}.
+--- This is useful to call functions in a library that you
+--- especially made to be used with Vim. Since only one argument
+--- is possible, calling standard library functions is rather
+--- limited.
+--- The result is the String returned by the function. If the
+--- function returns NULL, this will appear as an empty string ""
+--- to Vim.
+--- If the function returns a number, use libcallnr()!
+--- If {argument} is a number, it is passed to the function as an
+--- int; if {argument} is a string, it is passed as a
+--- null-terminated string.
+---
+--- libcall() allows you to write your own 'plug-in' extensions to
+--- Vim without having to recompile the program. It is NOT a
+--- means to call system functions! If you try to do so Vim will
+--- very probably crash.
+---
+--- For Win32, the functions you write must be placed in a DLL
+--- and use the normal C calling convention (NOT Pascal which is
+--- used in Windows System DLLs). The function must take exactly
+--- one parameter, either a character pointer or a long integer,
+--- and must return a character pointer or NULL. The character
+--- pointer returned must point to memory that will remain valid
+--- after the function has returned (e.g. in static data in the
+--- DLL). If it points to allocated memory, that memory will
+--- leak away. Using a static buffer in the function should work,
+--- it's then freed when the DLL is unloaded.
+---
+--- WARNING: If the function returns a non-valid pointer, Vim may
+--- crash! This also happens if the function returns a number,
+--- because Vim thinks it's a pointer.
+--- For Win32 systems, {libname} should be the filename of the DLL
+--- without the ".DLL" suffix. A full path is only required if
+--- the DLL is not in the usual places.
+--- For Unix: When compiling your own plugins, remember that the
+--- object code must be compiled as position-independent ('PIC').
+--- Examples: >vim
+--- echo libcall("libc.so", "getenv", "HOME")
+---
+--- @param libname string
+--- @param funcname string
+--- @param argument any
+--- @return any
+function vim.fn.libcall(libname, funcname, argument) end
+
+--- Just like |libcall()|, but used for a function that returns an
+--- int instead of a string.
+--- Examples: >vim
+--- echo libcallnr("/usr/lib/libc.so", "getpid", "")
+--- call libcallnr("libc.so", "printf", "Hello World!\n")
+--- call libcallnr("libc.so", "sleep", 10)
+--- <
+---
+--- @param libname string
+--- @param funcname string
+--- @param argument any
+--- @return any
+function vim.fn.libcallnr(libname, funcname, argument) end
+
+--- The result is a Number, which is the line number of the file
+--- position given with {expr}. The {expr} argument is a string.
+--- The accepted positions are:
+--- . the cursor position
+--- $ the last line in the current buffer
+--- 'x position of mark x (if the mark is not set, 0 is
+--- returned)
+--- w0 first line visible in current window (one if the
+--- display isn't updated, e.g. in silent Ex mode)
+--- w$ last line visible in current window (this is one
+--- less than "w0" if no lines are visible)
+--- v In Visual mode: the start of the Visual area (the
+--- cursor is the end). When not in Visual mode
+--- returns the cursor position. Differs from |'<| in
+--- that it's updated right away.
+--- Note that a mark in another file can be used. The line number
+--- then applies to another buffer.
+--- To get the column number use |col()|. To get both use
+--- |getpos()|.
+--- With the optional {winid} argument the values are obtained for
+--- that window instead of the current window.
+--- Returns 0 for invalid values of {expr} and {winid}.
+--- Examples: >vim
+--- echo line(".") " line number of the cursor
+--- echo line(".", winid) " idem, in window "winid"
+--- echo line("'t") " line number of mark t
+--- echo line("'" .. marker) " line number of mark marker
+--- <
+--- To jump to the last known position when opening a file see
+--- |last-position-jump|.
+---
+--- @param expr any
+--- @param winid? integer
+--- @return integer
+function vim.fn.line(expr, winid) end
+
+--- Return the byte count from the start of the buffer for line
+--- {lnum}. This includes the end-of-line character, depending on
+--- the 'fileformat' option for the current buffer. The first
+--- line returns 1. UTF-8 encoding is used, 'fileencoding' is
+--- ignored. This can also be used to get the byte count for the
+--- line just below the last line: >vim
+--- echo line2byte(line("$") + 1)
+--- <This is the buffer size plus one. If 'fileencoding' is empty
+--- it is the file size plus one. {lnum} is used like with
+--- |getline()|. When {lnum} is invalid -1 is returned.
+--- Also see |byte2line()|, |go| and |:goto|.
+---
+--- @param lnum integer
+--- @return integer
+function vim.fn.line2byte(lnum) end
+
+--- Get the amount of indent for line {lnum} according the lisp
+--- indenting rules, as with 'lisp'.
+--- The indent is counted in spaces, the value of 'tabstop' is
+--- relevant. {lnum} is used just like in |getline()|.
+--- When {lnum} is invalid, -1 is returned.
+---
+--- @param lnum integer
+--- @return any
+function vim.fn.lispindent(lnum) end
+
+--- Return a Blob concatenating all the number values in {list}.
+--- Examples: >vim
+--- echo list2blob([1, 2, 3, 4]) " returns 0z01020304
+--- echo list2blob([]) " returns 0z
+--- <Returns an empty Blob on error. If one of the numbers is
+--- negative or more than 255 error *E1239* is given.
+---
+--- |blob2list()| does the opposite.
+---
+--- @param list any
+--- @return any
+function vim.fn.list2blob(list) end
+
+--- Convert each number in {list} to a character string can
+--- concatenate them all. Examples: >vim
+--- echo list2str([32]) " returns " "
+--- echo list2str([65, 66, 67]) " returns "ABC"
+--- <The same can be done (slowly) with: >vim
+--- echo join(map(list, {nr, val -> nr2char(val)}), '')
+--- <|str2list()| does the opposite.
+---
+--- UTF-8 encoding is always used, {utf8} option has no effect,
+--- and exists only for backwards-compatibility.
+--- With UTF-8 composing characters work as expected: >vim
+--- echo list2str([97, 769]) " returns "á"
+--- <
+--- Returns an empty string on error.
+---
+--- @param list any
+--- @param utf8? any
+--- @return any
+function vim.fn.list2str(list, utf8) end
+
+--- Return the current time, measured as seconds since 1st Jan
+--- 1970. See also |strftime()|, |strptime()| and |getftime()|.
+---
+--- @return any
+function vim.fn.localtime() end
+
+--- Return the natural logarithm (base e) of {expr} as a |Float|.
+--- {expr} must evaluate to a |Float| or a |Number| in the range
+--- (0, inf].
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo log(10)
+--- < 2.302585 >vim
+--- echo log(exp(5))
+--- < 5.0
+---
+--- @param expr any
+--- @return any
+function vim.fn.log(expr) end
+
+--- Return the logarithm of Float {expr} to base 10 as a |Float|.
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo log10(1000)
+--- < 3.0 >vim
+--- echo log10(0.01)
+--- < -2.0
+---
+--- @param expr any
+--- @return any
+function vim.fn.log10(expr) end
+
+--- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+--- When {expr1} is a |List|| or |Dictionary|, replace each
+--- item in {expr1} with the result of evaluating {expr2}.
+--- For a |Blob| each byte is replaced.
+--- For a |String|, each character, including composing
+--- characters, is replaced.
+--- If the item type changes you may want to use |mapnew()| to
+--- create a new List or Dictionary.
+---
+--- {expr2} must be a |String| or |Funcref|.
+---
+--- If {expr2} is a |String|, inside {expr2} |v:val| has the value
+--- of the current item. For a |Dictionary| |v:key| has the key
+--- of the current item and for a |List| |v:key| has the index of
+--- the current item. For a |Blob| |v:key| has the index of the
+--- current byte. For a |String| |v:key| has the index of the
+--- current character.
+--- Example: >vim
+--- call map(mylist, '"> " .. v:val .. " <"')
+--- <This puts "> " before and " <" after each item in "mylist".
+---
+--- Note that {expr2} is the result of an expression and is then
+--- used as an expression again. Often it is good to use a
+--- |literal-string| to avoid having to double backslashes. You
+--- still have to double ' quotes
+---
+--- If {expr2} is a |Funcref| it is called with two arguments:
+--- 1. The key or the index of the current item.
+--- 2. the value of the current item.
+--- The function must return the new value of the item. Example
+--- that changes each value by "key-value": >vim
+--- func KeyValue(key, val)
+--- return a:key .. '-' .. a:val
+--- endfunc
+--- call map(myDict, function('KeyValue'))
+--- <It is shorter when using a |lambda|: >vim
+--- call map(myDict, {key, val -> key .. '-' .. val})
+--- <If you do not use "val" you can leave it out: >vim
+--- call map(myDict, {key -> 'item: ' .. key})
+--- <If you do not use "key" you can use a short name: >vim
+--- call map(myDict, {_, val -> 'item: ' .. val})
+--- <
+--- The operation is done in-place for a |List| and |Dictionary|.
+--- If you want it to remain unmodified make a copy first: >vim
+--- let tlist = map(copy(mylist), ' v:val .. "\t"')
+---
+--- <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+--- or a new |Blob| or |String|.
+--- When an error is encountered while evaluating {expr2} no
+--- further items in {expr1} are processed.
+--- When {expr2} is a Funcref errors inside a function are ignored,
+--- unless it was defined with the "abort" flag.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.map(expr1, expr2) end
+
+--- When {dict} is omitted or zero: Return the rhs of mapping
+--- {name} in mode {mode}. The returned String has special
+--- characters translated like in the output of the ":map" command
+--- listing. When {dict} is TRUE a dictionary is returned, see
+--- below. To get a list of all mappings see |maplist()|.
+---
+--- When there is no mapping for {name}, an empty String is
+--- returned if {dict} is FALSE, otherwise returns an empty Dict.
+--- When the mapping for {name} is empty, then "<Nop>" is
+--- returned.
+---
+--- The {name} can have special key names, like in the ":map"
+--- command.
+---
+--- {mode} can be one of these strings:
+--- "n" Normal
+--- "v" Visual (including Select)
+--- "o" Operator-pending
+--- "i" Insert
+--- "c" Cmd-line
+--- "s" Select
+--- "x" Visual
+--- "l" langmap |language-mapping|
+--- "t" Terminal
+--- "" Normal, Visual and Operator-pending
+--- When {mode} is omitted, the modes for "" are used.
+---
+--- When {abbr} is there and it is |TRUE| use abbreviations
+--- instead of mappings.
+---
+--- When {dict} is there and it is |TRUE| return a dictionary
+--- containing all the information of the mapping with the
+--- following items: *mapping-dict*
+--- "lhs" The {lhs} of the mapping as it would be typed
+--- "lhsraw" The {lhs} of the mapping as raw bytes
+--- "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate
+--- form, only present when it differs from "lhsraw"
+--- "rhs" The {rhs} of the mapping as typed.
+--- "silent" 1 for a |:map-silent| mapping, else 0.
+--- "noremap" 1 if the {rhs} of the mapping is not remappable.
+--- "script" 1 if mapping was defined with <script>.
+--- "expr" 1 for an expression mapping (|:map-<expr>|).
+--- "buffer" 1 for a buffer local mapping (|:map-local|).
+--- "mode" Modes for which the mapping is defined. In
+--- addition to the modes mentioned above, these
+--- characters will be used:
+--- " " Normal, Visual and Operator-pending
+--- "!" Insert and Commandline mode
+--- (|mapmode-ic|)
+--- "sid" The script local ID, used for <sid> mappings
+--- (|<SID>|). Negative for special contexts.
+--- "scriptversion" The version of the script, always 1.
+--- "lnum" The line number in "sid", zero if unknown.
+--- "nowait" Do not wait for other, longer mappings.
+--- (|:map-<nowait>|).
+--- "abbr" True if this is an |abbreviation|.
+--- "mode_bits" Nvim's internal binary representation of "mode".
+--- |mapset()| ignores this; only "mode" is used.
+--- See |maplist()| for usage examples. The values
+--- are from src/nvim/state_defs.h and may change in
+--- the future.
+---
+--- The dictionary can be used to restore a mapping with
+--- |mapset()|.
+---
+--- The mappings local to the current buffer are checked first,
+--- then the global mappings.
+--- This function can be used to map a key even when it's already
+--- mapped, and have it do the original mapping too. Sketch: >vim
+--- exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n')
+---
+--- @param name string
+--- @param mode? string
+--- @param abbr? boolean
+--- @param dict? boolean
+--- @return string|table<string,any>
+function vim.fn.maparg(name, mode, abbr, dict) end
+
+--- Check if there is a mapping that matches with {name} in mode
+--- {mode}. See |maparg()| for {mode} and special names in
+--- {name}.
+--- When {abbr} is there and it is non-zero use abbreviations
+--- instead of mappings.
+--- A match happens with a mapping that starts with {name} and
+--- with a mapping which is equal to the start of {name}.
+---
+--- matches mapping "a" "ab" "abc" ~
+--- mapcheck("a") yes yes yes
+--- mapcheck("abc") yes yes yes
+--- mapcheck("ax") yes no no
+--- mapcheck("b") no no no
+---
+--- The difference with maparg() is that mapcheck() finds a
+--- mapping that matches with {name}, while maparg() only finds a
+--- mapping for {name} exactly.
+--- When there is no mapping that starts with {name}, an empty
+--- String is returned. If there is one, the RHS of that mapping
+--- is returned. If there are several mappings that start with
+--- {name}, the RHS of one of them is returned. This will be
+--- "<Nop>" if the RHS is empty.
+--- The mappings local to the current buffer are checked first,
+--- then the global mappings.
+--- This function can be used to check if a mapping can be added
+--- without being ambiguous. Example: >vim
+--- if mapcheck("_vv") == ""
+--- map _vv :set guifont=7x13<CR>
+--- endif
+--- <This avoids adding the "_vv" mapping when there already is a
+--- mapping for "_v" or for "_vvv".
+---
+--- @param name string
+--- @param mode? string
+--- @param abbr? any
+--- @return any
+function vim.fn.mapcheck(name, mode, abbr) end
+
+--- Returns a |List| of all mappings. Each List item is a |Dict|,
+--- the same as what is returned by |maparg()|, see
+--- |mapping-dict|. When {abbr} is there and it is |TRUE| use
+--- abbreviations instead of mappings.
+---
+--- Example to show all mappings with "MultiMatch" in rhs: >vim
+--- echo maplist()->filter({_, m ->
+--- \ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
+--- \ })
+--- <It can be tricky to find mappings for particular |:map-modes|.
+--- |mapping-dict|'s "mode_bits" can simplify this. For example,
+--- the mode_bits for Normal, Insert or Command-line modes are
+--- 0x19. To find all the mappings available in those modes you
+--- can do: >vim
+--- let saved_maps = []
+--- for m in maplist()
+--- if and(m.mode_bits, 0x19) != 0
+--- eval saved_maps->add(m)
+--- endif
+--- endfor
+--- echo saved_maps->mapnew({_, m -> m.lhs})
+--- <The values of the mode_bits are defined in Nvim's
+--- src/nvim/state_defs.h file and they can be discovered at
+--- runtime using |:map-commands| and "maplist()". Example: >vim
+--- omap xyzzy <Nop>
+--- let op_bit = maplist()->filter(
+--- \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+--- ounmap xyzzy
+--- echo printf("Operator-pending mode bit: 0x%x", op_bit)
+---
+--- @return any
+function vim.fn.maplist() end
+
+--- Like |map()| but instead of replacing items in {expr1} a new
+--- List or Dictionary is created and returned. {expr1} remains
+--- unchanged. Items can still be changed by {expr2}, if you
+--- don't want that use |deepcopy()| first.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.mapnew(expr1, expr2) end
+
+--- Restore a mapping from a dictionary, possibly returned by
+--- |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
+--- is true, is set on the current buffer; it is up to the caller
+--- to ensure that the intended buffer is the current buffer. This
+--- feature allows copying mappings from one buffer to another.
+--- The dict.mode value may restore a single mapping that covers
+--- more than one mode, like with mode values of '!', ' ', "nox",
+--- or 'v'. *E1276*
+---
+--- In the first form, {mode} and {abbr} should be the same as
+--- for the call to |maparg()|. *E460*
+--- {mode} is used to define the mode in which the mapping is set,
+--- not the "mode" entry in {dict}.
+--- Example for saving and restoring a mapping: >vim
+--- let save_map = maparg('K', 'n', 0, 1)
+--- nnoremap K somethingelse
+--- " ...
+--- call mapset('n', 0, save_map)
+--- <Note that if you are going to replace a map in several modes,
+--- e.g. with `:map!`, you need to save/restore the mapping for
+--- all of them, when they might differ.
+---
+--- In the second form, with {dict} as the only argument, mode
+--- and abbr are taken from the dict.
+--- Example: >vim
+--- let save_maps = maplist()->filter(
+--- \ {_, m -> m.lhs == 'K'})
+--- nnoremap K somethingelse
+--- cnoremap K somethingelse2
+--- " ...
+--- unmap K
+--- for d in save_maps
+--- call mapset(d)
+--- endfor
+---
+--- @param mode string
+--- @param abbr? any
+--- @param dict? any
+--- @return any
+function vim.fn.mapset(mode, abbr, dict) end
+
+--- When {expr} is a |List| then this returns the index of the
+--- first item where {pat} matches. Each item is used as a
+--- String, |Lists| and |Dictionaries| are used as echoed.
+---
+--- Otherwise, {expr} is used as a String. The result is a
+--- Number, which gives the index (byte offset) in {expr} where
+--- {pat} matches.
+---
+--- A match at the first character or |List| item returns zero.
+--- If there is no match -1 is returned.
+---
+--- For getting submatches see |matchlist()|.
+--- Example: >vim
+--- echo match("testing", "ing") " results in 4
+--- echo match([1, 'x'], '\a') " results in 1
+--- <See |string-match| for how {pat} is used.
+--- *strpbrk()*
+--- Vim doesn't have a strpbrk() function. But you can do: >vim
+--- let sepidx = match(line, '[.,;: \t]')
+--- < *strcasestr()*
+--- Vim doesn't have a strcasestr() function. But you can add
+--- "\c" to the pattern to ignore case: >vim
+--- let idx = match(haystack, '\cneedle')
+--- <
+--- If {start} is given, the search starts from byte index
+--- {start} in a String or item {start} in a |List|.
+--- The result, however, is still the index counted from the
+--- first character/item. Example: >vim
+--- echo match("testing", "ing", 2)
+--- <result is again "4". >vim
+--- echo match("testing", "ing", 4)
+--- <result is again "4". >vim
+--- echo match("testing", "t", 2)
+--- <result is "3".
+--- For a String, if {start} > 0 then it is like the string starts
+--- {start} bytes later, thus "^" will match at {start}. Except
+--- when {count} is given, then it's like matches before the
+--- {start} byte are ignored (this is a bit complicated to keep it
+--- backwards compatible).
+--- For a String, if {start} < 0, it will be set to 0. For a list
+--- the index is counted from the end.
+--- If {start} is out of range ({start} > strlen({expr}) for a
+--- String or {start} > len({expr}) for a |List|) -1 is returned.
+---
+--- When {count} is given use the {count}th match. When a match
+--- is found in a String the search for the next one starts one
+--- character further. Thus this example results in 1: >vim
+--- echo match("testing", "..", 0, 2)
+--- <In a |List| the search continues in the next item.
+--- Note that when {count} is added the way {start} works changes,
+--- see above.
+---
+--- See |pattern| for the patterns that are accepted.
+--- The 'ignorecase' option is used to set the ignore-caseness of
+--- the pattern. 'smartcase' is NOT used. The matching is always
+--- done like 'magic' is set and 'cpoptions' is empty.
+--- Note that a match at the start is preferred, thus when the
+--- pattern is using "*" (any number of matches) it tends to find
+--- zero matches at the start instead of a number of matches
+--- further down in the text.
+---
+--- @param expr any
+--- @param pat any
+--- @param start? any
+--- @param count? any
+--- @return any
+function vim.fn.match(expr, pat, start, count) end
+
+--- 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
+--- match using |matchdelete()|. The ID is bound to the window.
+--- Matching is case sensitive and magic, unless case sensitivity
+--- or magicness are explicitly overridden in {pattern}. The
+--- 'magic', 'smartcase' and 'ignorecase' options are not used.
+--- The "Conceal" value is special, it causes the match to be
+--- concealed.
+---
+--- The optional {priority} argument assigns a priority to the
+--- match. A match with a high priority will have its
+--- highlighting overrule that of a match with a lower priority.
+--- A priority is specified as an integer (negative numbers are no
+--- exception). If the {priority} argument is not specified, the
+--- default priority is 10. The priority of 'hlsearch' is zero,
+--- hence all matches with a priority greater than zero will
+--- overrule it. Syntax highlighting (see 'syntax') is a separate
+--- mechanism, and regardless of the chosen priority a match will
+--- always overrule syntax highlighting.
+---
+--- The optional {id} argument allows the request for a specific
+--- match ID. If a specified ID is already taken, an error
+--- message will appear and the match will not be added. An ID
+--- is specified as a positive integer (zero excluded). IDs 1, 2
+--- and 3 are reserved for |:match|, |:2match| and |:3match|,
+--- respectively. 3 is reserved for use by the |matchparen|
+--- plugin.
+--- If the {id} argument is not specified or -1, |matchadd()|
+--- automatically chooses a free ID, which is at least 1000.
+---
+--- The optional {dict} argument allows for further custom
+--- values. Currently this is used to specify a match specific
+--- conceal character that will be shown for |hl-Conceal|
+--- highlighted matches. The dict can have the following members:
+---
+--- conceal Special character to show instead of the
+--- match (only for |hl-Conceal| highlighted
+--- matches, see |:syn-cchar|)
+--- window Instead of the current window use the
+--- window with this number or window ID.
+---
+--- The number of matches is not limited, as it is the case with
+--- the |:match| commands.
+---
+--- Returns -1 on error.
+---
+--- Example: >vim
+--- highlight MyGroup ctermbg=green guibg=green
+--- let m = matchadd("MyGroup", "TODO")
+--- <Deletion of the pattern: >vim
+--- call matchdelete(m)
+---
+--- <A list of matches defined by |matchadd()| and |:match| are
+--- available from |getmatches()|. All matches can be deleted in
+--- one operation by |clearmatches()|.
+---
+--- @param group any
+--- @param pattern any
+--- @param priority? any
+--- @param id? any
+--- @param dict? any
+--- @return any
+function vim.fn.matchadd(group, pattern, priority, id, dict) end
+
+--- Same as |matchadd()|, but requires a list of positions {pos}
+--- instead of a pattern. This command is faster than |matchadd()|
+--- because it does not require to handle regular expressions and
+--- sets buffer line boundaries to redraw screen. It is supposed
+--- to be used when fast match additions and deletions are
+--- required, for example to highlight matching parentheses.
+--- *E5030* *E5031*
+--- {pos} is a list of positions. Each position can be one of
+--- these:
+--- - A number. This whole line will be highlighted. The first
+--- line has number 1.
+--- - A list with one number, e.g., [23]. The whole line with this
+--- number will be highlighted.
+--- - A list with two numbers, e.g., [23, 11]. The first number is
+--- the line number, the second one is the column number (first
+--- column is 1, the value must correspond to the byte index as
+--- |col()| would return). The character at this position will
+--- be highlighted.
+--- - A list with three numbers, e.g., [23, 11, 3]. As above, but
+--- the third number gives the length of the highlight in bytes.
+---
+--- Entries with zero and negative line numbers are silently
+--- ignored, as well as entries with negative column numbers and
+--- lengths.
+---
+--- Returns -1 on error.
+---
+--- Example: >vim
+--- highlight MyGroup ctermbg=green guibg=green
+--- let m = matchaddpos("MyGroup", [[23, 24], 34])
+--- <Deletion of the pattern: >vim
+--- call matchdelete(m)
+---
+--- <Matches added by |matchaddpos()| are returned by
+--- |getmatches()|.
+---
+--- @param group any
+--- @param pos any
+--- @param priority? any
+--- @param id? any
+--- @param dict? any
+--- @return any
+function vim.fn.matchaddpos(group, pos, priority, id, dict) end
+
+--- Selects the {nr} match item, as set with a |:match|,
+--- |:2match| or |:3match| command.
+--- Return a |List| with two elements:
+--- The name of the highlight group used
+--- The pattern used.
+--- When {nr} is not 1, 2 or 3 returns an empty |List|.
+--- When there is no match item set returns ['', ''].
+--- This is useful to save and restore a |:match|.
+--- Highlighting matches using the |:match| commands are limited
+--- to three matches. |matchadd()| does not have this limitation.
+---
+--- @param nr integer
+--- @return any
+function vim.fn.matcharg(nr) end
+
+--- Deletes a match with ID {id} previously defined by |matchadd()|
+--- or one of the |:match| commands. Returns 0 if successful,
+--- otherwise -1. See example for |matchadd()|. All matches can
+--- be deleted in one operation by |clearmatches()|.
+--- If {win} is specified, use the window with this number or
+--- window ID instead of the current window.
+---
+--- @param id any
+--- @param win? any
+--- @return any
+function vim.fn.matchdelete(id, win) end
+
+--- Same as |match()|, but return the index of first character
+--- after the match. Example: >vim
+--- echo matchend("testing", "ing")
+--- <results in "7".
+--- *strspn()* *strcspn()*
+--- Vim doesn't have a strspn() or strcspn() function, but you can
+--- do it with matchend(): >vim
+--- let span = matchend(line, '[a-zA-Z]')
+--- let span = matchend(line, '[^a-zA-Z]')
+--- <Except that -1 is returned when there are no matches.
+---
+--- The {start}, if given, has the same meaning as for |match()|. >vim
+--- echo matchend("testing", "ing", 2)
+--- <results in "7". >vim
+--- echo matchend("testing", "ing", 5)
+--- <result is "-1".
+--- When {expr} is a |List| the result is equal to |match()|.
+---
+--- @param expr any
+--- @param pat any
+--- @param start? any
+--- @param count? any
+--- @return any
+function vim.fn.matchend(expr, pat, start, count) end
+
+--- 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 return only matches
+--- that contain the characters in {str} in the
+--- given sequence.
+--- limit Maximum number of matches in {list} to be
+--- returned. Zero means no limit.
+---
+--- 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.
+---
+--- When {limit} is given, matchfuzzy() will find up to this
+--- number of matches in {list} and return them in sorted order.
+---
+--- Refer to |fuzzy-matching| for more information about fuzzy
+--- matching strings.
+---
+--- Example: >vim
+--- echo matchfuzzy(["clay", "crow"], "cay")
+--- <results in ["clay"]. >vim
+--- echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
+--- <results in a list of buffer names fuzzy matching "ndl". >vim
+--- echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'})
+--- <results in a list of buffer information dicts with buffer
+--- names fuzzy matching "ndl". >vim
+--- echo getbufinfo()->matchfuzzy("spl",
+--- \ {'text_cb' : {v -> v.name}})
+--- <results in a list of buffer information dicts with buffer
+--- names fuzzy matching "spl". >vim
+--- echo v:oldfiles->matchfuzzy("test")
+--- <results in a list of file names fuzzy matching "test". >vim
+--- let l = readfile("buffer.c")->matchfuzzy("str")
+--- <results in a list of lines in "buffer.c" fuzzy matching "str". >vim
+--- echo ['one two', 'two one']->matchfuzzy('two one')
+--- <results in `['two one', 'one two']` . >vim
+--- echo ['one two', 'two one']->matchfuzzy('two one',
+--- \ {'matchseq': 1})
+--- <results in `['two one']`.
+---
+--- @param list any
+--- @param str any
+--- @param dict? any
+--- @return any
+function vim.fn.matchfuzzy(list, str, dict) end
+
+--- 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: >vim
+--- echo matchfuzzypos(['testing'], 'tsg')
+--- <results in [["testing"], [[0, 2, 6]], [99]] >vim
+--- echo matchfuzzypos(['clay', 'lacy'], 'la')
+--- <results in [["lacy", "clay"], [[0, 1], [1, 2]], [153, 133]] >vim
+--- echo [{'text': 'hello', 'id' : 10}]
+--- \ ->matchfuzzypos('ll', {'key' : 'text'})
+--- <results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]`
+---
+--- @param list any
+--- @param str any
+--- @param dict? any
+--- @return any
+function vim.fn.matchfuzzypos(list, str, dict) end
+
+--- Same as |match()|, but return a |List|. The first item in the
+--- list is the matched string, same as what matchstr() would
+--- return. Following items are submatches, like "\1", "\2", etc.
+--- in |:substitute|. When an optional submatch didn't match an
+--- empty string is used. Example: >vim
+--- echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)')
+--- <Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', '']
+--- When there is no match an empty list is returned.
+---
+--- You can pass in a List, but that is not very useful.
+---
+--- @param expr any
+--- @param pat any
+--- @param start? any
+--- @param count? any
+--- @return any
+function vim.fn.matchlist(expr, pat, start, count) end
+
+--- Same as |match()|, but return the matched string. Example: >vim
+--- echo matchstr("testing", "ing")
+--- <results in "ing".
+--- When there is no match "" is returned.
+--- The {start}, if given, has the same meaning as for |match()|. >vim
+--- echo matchstr("testing", "ing", 2)
+--- <results in "ing". >vim
+--- echo matchstr("testing", "ing", 5)
+--- <result is "".
+--- When {expr} is a |List| then the matching item is returned.
+--- The type isn't changed, it's not necessarily a String.
+---
+--- @param expr any
+--- @param pat any
+--- @param start? any
+--- @param count? any
+--- @return any
+function vim.fn.matchstr(expr, pat, start, count) end
+
+--- Same as |matchstr()|, but return the matched string, the start
+--- position and the end position of the match. Example: >vim
+--- echo matchstrpos("testing", "ing")
+--- <results in ["ing", 4, 7].
+--- When there is no match ["", -1, -1] is returned.
+--- The {start}, if given, has the same meaning as for |match()|. >vim
+--- echo matchstrpos("testing", "ing", 2)
+--- <results in ["ing", 4, 7]. >vim
+--- echo matchstrpos("testing", "ing", 5)
+--- <result is ["", -1, -1].
+--- When {expr} is a |List| then the matching item, the index
+--- of first item where {pat} matches, the start position and the
+--- end position of the match are returned. >vim
+--- echo matchstrpos([1, '__x'], '\a')
+--- <result is ["x", 1, 2, 3].
+--- The type isn't changed, it's not necessarily a String.
+---
+--- @param expr any
+--- @param pat any
+--- @param start? any
+--- @param count? any
+--- @return any
+function vim.fn.matchstrpos(expr, pat, start, count) end
+
+--- Return the maximum value of all items in {expr}. Example: >vim
+--- 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
+--- an error. An empty |List| or |Dictionary| results in zero.
+---
+--- @param expr any
+--- @return any
+function vim.fn.max(expr) end
+
+--- Returns a |List| of |Dictionaries| describing |menus| (defined
+--- by |:menu|, |:amenu|, …), including |hidden-menus|.
+---
+--- {path} matches a menu by name, or all menus if {path} is an
+--- empty string. Example: >vim
+--- echo menu_get('File','')
+--- echo menu_get('')
+--- <
+--- {modes} is a string of zero or more modes (see |maparg()| or
+--- |creating-menus| for the list of modes). "a" means "all".
+---
+--- Example: >vim
+--- nnoremenu &Test.Test inormal
+--- inoremenu Test.Test insert
+--- vnoremenu Test.Test x
+--- echo menu_get("")
+---
+--- <returns something like this: >
+---
+--- [ {
+--- "hidden": 0,
+--- "name": "Test",
+--- "priority": 500,
+--- "shortcut": 84,
+--- "submenus": [ {
+--- "hidden": 0,
+--- "mappings": {
+--- i": {
+--- "enabled": 1,
+--- "noremap": 1,
+--- "rhs": "insert",
+--- "sid": 1,
+--- "silent": 0
+--- },
+--- n": { ... },
+--- s": { ... },
+--- v": { ... }
+--- },
+--- "name": "Test",
+--- "priority": 500,
+--- "shortcut": 0
+--- } ]
+--- } ]
+--- <
+---
+--- @param path string
+--- @param modes? any
+--- @return any
+function vim.fn.menu_get(path, modes) end
+
+--- Return information about the specified menu {name} in
+--- mode {mode}. The menu name should be specified without the
+--- shortcut character ('&'). If {name} is "", then the top-level
+--- menu names are returned.
+---
+--- {mode} can be one of these strings:
+--- "n" Normal
+--- "v" Visual (including Select)
+--- "o" Operator-pending
+--- "i" Insert
+--- "c" Cmd-line
+--- "s" Select
+--- "x" Visual
+--- "t" Terminal-Job
+--- "" Normal, Visual and Operator-pending
+--- "!" Insert and Cmd-line
+--- When {mode} is omitted, the modes for "" are used.
+---
+--- Returns a |Dictionary| containing the following items:
+--- accel menu item accelerator text |menu-text|
+--- display display name (name without '&')
+--- enabled v:true if this menu item is enabled
+--- Refer to |:menu-enable|
+--- icon name of the icon file (for toolbar)
+--- |toolbar-icon|
+--- iconidx index of a built-in icon
+--- modes modes for which the menu is defined. In
+--- addition to the modes mentioned above, these
+--- characters will be used:
+--- " " Normal, Visual and Operator-pending
+--- name menu item name.
+--- noremenu v:true if the {rhs} of the menu item is not
+--- remappable else v:false.
+--- priority menu order priority |menu-priority|
+--- rhs right-hand-side of the menu item. The returned
+--- string has special characters translated like
+--- in the output of the ":menu" command listing.
+--- When the {rhs} of a menu item is empty, then
+--- "<Nop>" is returned.
+--- script v:true if script-local remapping of {rhs} is
+--- allowed else v:false. See |:menu-script|.
+--- shortcut shortcut key (character after '&' in
+--- the menu name) |menu-shortcut|
+--- silent v:true if the menu item is created
+--- with <silent> argument |:menu-silent|
+--- submenus |List| containing the names of
+--- all the submenus. Present only if the menu
+--- item has submenus.
+---
+--- Returns an empty dictionary if the menu item is not found.
+---
+--- Examples: >vim
+--- echo menu_info('Edit.Cut')
+--- echo menu_info('File.Save', 'n')
+---
+--- " Display the entire menu hierarchy in a buffer
+--- func ShowMenu(name, pfx)
+--- let m = menu_info(a:name)
+--- call append(line('$'), a:pfx .. m.display)
+--- for child in m->get('submenus', [])
+--- call ShowMenu(a:name .. '.' .. escape(child, '.'),
+--- \ a:pfx .. ' ')
+--- endfor
+--- endfunc
+--- new
+--- for topmenu in menu_info('').submenus
+--- call ShowMenu(topmenu, '')
+--- endfor
+--- <
+---
+--- @param name string
+--- @param mode? string
+--- @return any
+function vim.fn.menu_info(name, mode) end
+
+--- Return the minimum value of all items in {expr}. Example: >vim
+--- 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
+--- an error. An empty |List| or |Dictionary| results in zero.
+---
+--- @param expr any
+--- @return any
+function vim.fn.min(expr) end
+
+--- Create directory {name}.
+---
+--- When {flags} is present it must be a string. An empty string
+--- has no effect.
+---
+--- If {flags} contains "p" then intermediate directories are
+--- created as necessary.
+---
+--- If {flags} contains "D" then {name} is deleted at the end of
+--- the current function, as with: >vim
+--- defer delete({name}, 'd')
+--- <
+--- If {flags} contains "R" then {name} is deleted recursively at
+--- the end of the current function, as with: >vim
+--- defer delete({name}, 'rf')
+--- <Note that when {name} has more than one part and "p" is used
+--- some directories may already exist. Only the first one that
+--- is created and what it contains is scheduled to be deleted.
+--- E.g. when using: >vim
+--- call mkdir('subdir/tmp/autoload', 'pR')
+--- <and "subdir" already exists then "subdir/tmp" will be
+--- scheduled for deletion, like with: >vim
+--- defer delete('subdir/tmp', 'rf')
+--- <
+--- 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
+--- unreadable for others.
+---
+--- {prot} is applied for all parts of {name}. Thus if you create
+--- /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: >vim
+--- 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 {flags} set to
+--- "p" mkdir() will silently exit.
+---
+--- The function result is a Number, which is TRUE if the call was
+--- successful or FALSE if the directory creation failed or partly
+--- failed.
+---
+--- @param name string
+--- @param flags? string
+--- @param prot? any
+--- @return any
+function vim.fn.mkdir(name, flags, prot) end
+
+--- Return a string that indicates the current mode.
+--- If [expr] is supplied and it evaluates to a non-zero Number or
+--- a non-empty String (|non-zero-arg|), then the full mode is
+--- returned, otherwise only the first letter is returned.
+--- Also see |state()|.
+---
+--- n Normal
+--- no Operator-pending
+--- nov Operator-pending (forced charwise |o_v|)
+--- noV Operator-pending (forced linewise |o_V|)
+--- noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|)
+--- CTRL-V is one character
+--- niI Normal using |i_CTRL-O| in |Insert-mode|
+--- niR Normal using |i_CTRL-O| in |Replace-mode|
+--- niV Normal using |i_CTRL-O| in |Virtual-Replace-mode|
+--- nt Normal in |terminal-emulator| (insert goes to
+--- Terminal mode)
+--- ntT Normal using |t_CTRL-\_CTRL-O| in |Terminal-mode|
+--- v Visual by character
+--- vs Visual by character using |v_CTRL-O| in Select mode
+--- V Visual by line
+--- Vs Visual by line using |v_CTRL-O| in Select mode
+--- CTRL-V Visual blockwise
+--- CTRL-Vs Visual blockwise using |v_CTRL-O| in Select mode
+--- s Select by character
+--- S Select by line
+--- CTRL-S Select blockwise
+--- i Insert
+--- ic Insert mode completion |compl-generic|
+--- ix Insert mode |i_CTRL-X| completion
+--- R Replace |R|
+--- Rc Replace mode completion |compl-generic|
+--- Rx Replace mode |i_CTRL-X| completion
+--- Rv Virtual Replace |gR|
+--- Rvc Virtual Replace mode completion |compl-generic|
+--- Rvx Virtual Replace mode |i_CTRL-X| completion
+--- c Command-line editing
+--- cr Command-line editing overstrike mode |c_<Insert>|
+--- cv Vim Ex mode |gQ|
+--- cvr Vim Ex mode while in overstrike mode |c_<Insert>|
+--- r Hit-enter prompt
+--- rm The -- more -- prompt
+--- r? A |:confirm| query of some sort
+--- ! Shell or external command is executing
+--- t Terminal mode: keys go to the job
+---
+--- This is useful in the 'statusline' option or RPC calls. In
+--- most other places it always returns "c" or "n".
+--- Note that in the future more modes and more specific modes may
+--- be added. It's better not to compare the whole string but only
+--- the leading character(s).
+--- Also see |visualmode()|.
+---
+--- @return any
+function vim.fn.mode() end
+
+--- Convert a list of Vimscript objects to msgpack. Returned value is a
+--- |readfile()|-style list. When {type} contains "B", a |Blob| is
+--- returned instead. Example: >vim
+--- call writefile(msgpackdump([{}]), 'fname.mpack', 'b')
+--- <or, using a |Blob|: >vim
+--- call writefile(msgpackdump([{}], 'B'), 'fname.mpack')
+--- <
+--- This will write the single 0x80 byte to a `fname.mpack` file
+--- (dictionary with zero items is represented by 0x80 byte in
+--- messagepack).
+---
+--- Limitations: *E5004* *E5005*
+--- 1. |Funcref|s cannot be dumped.
+--- 2. Containers that reference themselves cannot be dumped.
+--- 3. Dictionary keys are always dumped as STR strings.
+--- 4. Other strings and |Blob|s are always dumped as BIN strings.
+--- 5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
+---
+--- @param list any
+--- @param type? any
+--- @return any
+function vim.fn.msgpackdump(list, type) end
+
+--- Convert a |readfile()|-style list or a |Blob| to a list of
+--- Vimscript objects.
+--- Example: >vim
+--- let fname = expand('~/.config/nvim/shada/main.shada')
+--- let mpack = readfile(fname, 'b')
+--- let shada_objects = msgpackparse(mpack)
+--- <This will read ~/.config/nvim/shada/main.shada file to
+--- `shada_objects` list.
+---
+--- Limitations:
+--- 1. Mapping ordering is not preserved unless messagepack
+--- mapping is dumped using generic mapping
+--- (|msgpack-special-map|).
+--- 2. Since the parser aims to preserve all data untouched
+--- (except for 1.) some strings are parsed to
+--- |msgpack-special-dict| format which is not convenient to
+--- use.
+--- *msgpack-special-dict*
+--- Some messagepack strings may be parsed to special
+--- dictionaries. Special dictionaries are dictionaries which
+---
+--- 1. Contain exactly two keys: `_TYPE` and `_VAL`.
+--- 2. `_TYPE` key is one of the types found in |v:msgpack_types|
+--- variable.
+--- 3. Value for `_VAL` has the following format (Key column
+--- contains name of the key from |v:msgpack_types|):
+---
+--- Key Value ~
+--- nil Zero, ignored when dumping. Not returned by
+--- |msgpackparse()| since |v:null| was introduced.
+--- boolean One or zero. When dumping it is only checked that
+--- value is a |Number|. Not returned by |msgpackparse()|
+--- since |v:true| and |v:false| were introduced.
+--- integer |List| with four numbers: sign (-1 or 1), highest two
+--- bits, number with bits from 62nd to 31st, lowest 31
+--- bits. I.e. to get actual number one will need to use
+--- code like >
+--- _VAL[0] * ((_VAL[1] << 62)
+--- & (_VAL[2] << 31)
+--- & _VAL[3])
+--- < Special dictionary with this type will appear in
+--- |msgpackparse()| output under one of the following
+--- circumstances:
+--- 1. |Number| is 32-bit and value is either above
+--- INT32_MAX or below INT32_MIN.
+--- 2. |Number| is 64-bit and value is above INT64_MAX. It
+--- cannot possibly be below INT64_MIN because msgpack
+--- C parser does not support such values.
+--- float |Float|. This value cannot possibly appear in
+--- |msgpackparse()| output.
+--- string |readfile()|-style list of strings. This value will
+--- appear in |msgpackparse()| output if string contains
+--- zero byte or if string is a mapping key and mapping is
+--- being represented as special dictionary for other
+--- reasons.
+--- binary |String|, or |Blob| if binary string contains zero
+--- byte. This value cannot appear in |msgpackparse()|
+--- output since blobs were introduced.
+--- array |List|. This value cannot appear in |msgpackparse()|
+--- output.
+--- *msgpack-special-map*
+--- map |List| of |List|s with two items (key and value) each.
+--- This value will appear in |msgpackparse()| output if
+--- parsed mapping contains one of the following keys:
+--- 1. Any key that is not a string (including keys which
+--- are binary strings).
+--- 2. String with NUL byte inside.
+--- 3. Duplicate key.
+--- 4. Empty key.
+--- ext |List| with two values: first is a signed integer
+--- representing extension type. Second is
+--- |readfile()|-style list of strings.
+---
+--- @param data any
+--- @return any
+function vim.fn.msgpackparse(data) end
+
+--- Return the line number of the first line at or below {lnum}
+--- that is not blank. Example: >vim
+--- if getline(nextnonblank(1)) =~ "Java" | endif
+--- <When {lnum} is invalid or there is no non-blank line at or
+--- below it, zero is returned.
+--- {lnum} is used like with |getline()|.
+--- See also |prevnonblank()|.
+---
+--- @param lnum integer
+--- @return any
+function vim.fn.nextnonblank(lnum) end
+
+--- Return a string with a single character, which has the number
+--- value {expr}. Examples: >vim
+--- echo nr2char(64) " returns '\@'
+--- echo nr2char(32) " returns ' '
+--- <Example for "utf-8": >vim
+--- echo nr2char(300) " returns I with bow character
+--- <
+--- UTF-8 encoding is always used, {utf8} option has no effect,
+--- and exists only for backwards-compatibility.
+--- Note that a NUL character in the file is specified with
+--- nr2char(10), because NULs are represented with newline
+--- characters. nr2char(0) is a real NUL and terminates the
+--- string, thus results in an empty string.
+---
+--- @param expr any
+--- @param utf8? any
+--- @return any
+function vim.fn.nr2char(expr, utf8) end
+
+--- Bitwise OR on the two arguments. The arguments are converted
+--- to a number. A List, Dict or Float argument causes an error.
+--- Also see `and()` and `xor()`.
+--- Example: >vim
+--- let bits = or(bits, 0x80)
+---
+--- <Rationale: The reason this is a function and not using the "|"
+--- character like many languages, is that Vi has always used "|"
+--- to separate commands. In many places it would not be clear if
+--- "|" is an operator or a command separator.
+---
+--- @param expr any
+--- @param expr1 any
+--- @return any
+vim.fn['or'] = function(expr, expr1) end
+
+--- 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 {len} letters in length.
+--- If {len} is omitted or smaller than 1 then 1 is used (single
+--- letters). Leading '~' and '.' characters are kept. Examples: >vim
+--- echo pathshorten('~/.config/nvim/autoload/file1.vim')
+--- < ~/.c/n/a/file1.vim ~
+--- >vim
+--- echo pathshorten('~/.config/nvim/autoload/file2.vim', 2)
+--- < ~/.co/nv/au/file2.vim ~
+--- It doesn't matter if the path exists or not.
+--- Returns an empty string on error.
+---
+--- @param path string
+--- @param len? any
+--- @return any
+function vim.fn.pathshorten(path, len) end
+
+--- Evaluate |perl| expression {expr} and return its result
+--- converted to Vim data structures.
+--- Numbers and strings are returned as they are (strings are
+--- copied though).
+--- Lists are represented as Vim |List| type.
+--- Dictionaries are represented as Vim |Dictionary| type,
+--- non-string keys result in error.
+---
+--- Note: If you want an array or hash, {expr} must return a
+--- reference to it.
+--- Example: >vim
+--- echo perleval('[1 .. 4]')
+--- < [1, 2, 3, 4]
+---
+--- @param expr any
+--- @return any
+function vim.fn.perleval(expr) end
+
+--- Return the power of {x} to the exponent {y} as a |Float|.
+--- {x} and {y} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {x} or {y} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo pow(3, 3)
+--- < 27.0 >vim
+--- echo pow(2, 16)
+--- < 65536.0 >vim
+--- echo pow(32, 0.20)
+--- < 2.0
+---
+--- @param x any
+--- @param y any
+--- @return any
+function vim.fn.pow(x, y) end
+
+--- Return the line number of the first line at or above {lnum}
+--- that is not blank. Example: >vim
+--- let ind = indent(prevnonblank(v:lnum - 1))
+--- <When {lnum} is invalid or there is no non-blank line at or
+--- above it, zero is returned.
+--- {lnum} is used like with |getline()|.
+--- Also see |nextnonblank()|.
+---
+--- @param lnum integer
+--- @return any
+function vim.fn.prevnonblank(lnum) end
+
+--- Return a String with {fmt}, where "%" items are replaced by
+--- the formatted form of their respective arguments. Example: >vim
+--- echo printf("%4d: E%d %.30s", lnum, errno, msg)
+--- <May result in:
+--- " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~
+---
+--- When used as a |method| the base is passed as the second
+--- argument: >vim
+--- Compute()->printf("result: %d")
+--- <
+--- You can use `call()` to pass the items as a list.
+---
+--- Often used items are:
+--- %s string
+--- %6S string right-aligned in 6 display cells
+--- %6s string right-aligned in 6 bytes
+--- %.9s string truncated to 9 bytes
+--- %c single byte
+--- %d decimal number
+--- %5d decimal number padded with spaces to 5 characters
+--- %b binary number
+--- %08b binary number padded with zeros to at least 8 characters
+--- %B binary number using upper case letters
+--- %x hex number
+--- %04x hex number padded with zeros to at least 4 characters
+--- %X hex number using upper case letters
+--- %o octal number
+--- %f floating point number as 12.23, inf, -inf or nan
+--- %F floating point number as 12.23, INF, -INF or NAN
+--- %e floating point number as 1.23e3, inf, -inf or nan
+--- %E floating point number as 1.23E3, INF, -INF or NAN
+--- %g floating point number, as %f or %e depending on value
+--- %G floating point number, as %F or %E depending on value
+--- %% the % character itself
+--- %p representation of the pointer to the container
+---
+--- Conversion specifications start with '%' and end with the
+--- conversion type. All other characters are copied unchanged to
+--- the result.
+---
+--- The "%" starts a conversion specification. The following
+--- arguments appear in sequence:
+---
+--- % [pos-argument] [flags] [field-width] [.precision] type
+---
+--- pos-argument
+--- At most one positional argument specifier. These
+--- take the form {n$}, where n is >= 1.
+---
+--- flags
+--- Zero or more of the following flags:
+---
+--- # The value should be converted to an "alternate
+--- form". For c, d, and s conversions, this option
+--- has no effect. For o conversions, the precision
+--- of the number is increased to force the first
+--- character of the output string to a zero (except
+--- if a zero value is printed with an explicit
+--- precision of zero).
+--- For x and X conversions, a non-zero result has
+--- the string "0x" (or "0X" for X conversions)
+--- prepended to it.
+---
+--- 0 (zero) Zero padding. For all conversions the converted
+--- value is padded on the left with zeros rather
+--- than blanks. If a precision is given with a
+--- numeric conversion (d, o, x, and X), the 0 flag
+--- is ignored.
+---
+--- - A negative field width flag; the converted value
+--- is to be left adjusted on the field boundary.
+--- The converted value is padded on the right with
+--- blanks, rather than on the left with blanks or
+--- zeros. A - overrides a 0 if both are given.
+---
+--- ' ' (space) A blank should be left before a positive
+--- number produced by a signed conversion (d).
+---
+--- + A sign must always be placed before a number
+--- produced by a signed conversion. A + overrides
+--- a space if both are used.
+---
+--- field-width
+--- An optional decimal digit string specifying a minimum
+--- field width. If the converted value has fewer bytes
+--- than the field width, it will be padded with spaces on
+--- the left (or right, if the left-adjustment flag has
+--- been given) to fill out the field width. For the S
+--- conversion the count is in cells.
+---
+--- .precision
+--- An optional precision, in the form of a period '.'
+--- followed by an optional digit string. If the digit
+--- string is omitted, the precision is taken as zero.
+--- This gives the minimum number of digits to appear for
+--- d, o, x, and X conversions, the maximum number of
+--- bytes to be printed from a string for s conversions,
+--- or the maximum number of cells to be printed from a
+--- string for S conversions.
+--- For floating point it is the number of digits after
+--- the decimal point.
+---
+--- type
+--- A character that specifies the type of conversion to
+--- be applied, see below.
+---
+--- A field width or precision, or both, may be indicated by an
+--- asterisk "*" instead of a digit string. In this case, a
+--- Number argument supplies the field width or precision. A
+--- negative field width is treated as a left adjustment flag
+--- followed by a positive field width; a negative precision is
+--- treated as though it were missing. Example: >vim
+--- echo printf("%d: %.*s", nr, width, line)
+--- <This limits the length of the text used from "line" to
+--- "width" bytes.
+---
+--- If the argument to be formatted is specified using a posional
+--- argument specifier, and a '*' is used to indicate that a
+--- number argument is to be used to specify the width or
+--- precision, the argument(s) to be used must also be specified
+--- using a {n$} positional argument specifier. See |printf-$|.
+---
+--- The conversion specifiers and their meanings are:
+---
+--- *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X*
+--- dbBoxX The Number argument is converted to signed decimal (d),
+--- unsigned binary (b and B), unsigned octal (o), or
+--- unsigned hexadecimal (x and X) notation. The letters
+--- "abcdef" are used for x conversions; the letters
+--- "ABCDEF" are used for X conversions. The precision, if
+--- any, gives the minimum number of digits that must
+--- appear; if the converted value requires fewer digits, it
+--- is padded on the left with zeros. In no case does a
+--- non-existent or small field width cause truncation of a
+--- numeric field; if the result of a conversion is wider
+--- than the field width, the field is expanded to contain
+--- the conversion result.
+--- The 'h' modifier indicates the argument is 16 bits.
+--- The 'l' modifier indicates the argument is a long
+--- integer. The size will be 32 bits or 64 bits
+--- depending on your platform.
+--- The "ll" modifier indicates the argument is 64 bits.
+--- The b and B conversion specifiers never take a width
+--- modifier and always assume their argument is a 64 bit
+--- integer.
+--- Generally, these modifiers are not useful. They are
+--- ignored when type is known from the argument.
+---
+--- i alias for d
+--- D alias for ld
+--- U alias for lu
+--- O alias for lo
+---
+--- *printf-c*
+--- c The Number argument is converted to a byte, and the
+--- resulting character is written.
+---
+--- *printf-s*
+--- s The text of the String argument is used. If a
+--- precision is specified, no more bytes than the number
+--- specified are used.
+--- If the argument is not a String type, it is
+--- automatically converted to text with the same format
+--- as ":echo".
+--- *printf-S*
+--- S The text of the String argument is used. If a
+--- precision is specified, no more display cells than the
+--- number specified are used.
+---
+--- *printf-f* *E807*
+--- f F The Float argument is converted into a string of the
+--- form 123.456. The precision specifies the number of
+--- digits after the decimal point. When the precision is
+--- zero the decimal point is omitted. When the precision
+--- is not specified 6 is used. A really big number
+--- (out of range or dividing by zero) results in "inf"
+--- or "-inf" with %f (INF or -INF with %F).
+--- "0.0 / 0.0" results in "nan" with %f (NAN with %F).
+--- Example: >vim
+--- echo printf("%.2f", 12.115)
+--- < 12.12
+--- Note that roundoff depends on the system libraries.
+--- Use |round()| when in doubt.
+---
+--- *printf-e* *printf-E*
+--- e E The Float argument is converted into a string of the
+--- form 1.234e+03 or 1.234E+03 when using 'E'. The
+--- precision specifies the number of digits after the
+--- decimal point, like with 'f'.
+---
+--- *printf-g* *printf-G*
+--- g G The Float argument is converted like with 'f' if the
+--- value is between 0.001 (inclusive) and 10000000.0
+--- (exclusive). Otherwise 'e' is used for 'g' and 'E'
+--- for 'G'. When no precision is specified superfluous
+--- zeroes and '+' signs are removed, except for the zero
+--- immediately after the decimal point. Thus 10000000.0
+--- results in 1.0e7.
+---
+--- *printf-%*
+--- % A '%' is written. No argument is converted. The
+--- complete conversion specification is "%%".
+---
+--- When a Number argument is expected a String argument is also
+--- accepted and automatically converted.
+--- When a Float or String argument is expected a Number argument
+--- is also accepted and automatically converted.
+--- Any other argument type results in an error message.
+---
+--- *E766* *E767*
+--- The number of {exprN} arguments must exactly match the number
+--- of "%" items. If there are not sufficient or too many
+--- arguments an error is given. Up to 18 arguments can be used.
+---
+--- *printf-$*
+--- In certain languages, error and informative messages are
+--- more readable when the order of words is different from the
+--- corresponding message in English. To accommodate translations
+--- having a different word order, positional arguments may be
+--- used to indicate this. For instance: >vim
+---
+--- #, c-format
+--- msgid "%s returning %s"
+--- msgstr "waarde %2$s komt terug van %1$s"
+--- <
+--- In this example, the sentence has its 2 string arguments
+--- reversed in the output. >vim
+---
+--- echo printf(
+--- "In The Netherlands, vim's creator's name is: %1$s %2$s",
+--- "Bram", "Moolenaar")
+--- < In The Netherlands, vim's creator's name is: Bram Moolenaar >vim
+---
+--- echo printf(
+--- "In Belgium, vim's creator's name is: %2$s %1$s",
+--- "Bram", "Moolenaar")
+--- < In Belgium, vim's creator's name is: Moolenaar Bram
+---
+--- Width (and precision) can be specified using the '*' specifier.
+--- In this case, you must specify the field width position in the
+--- argument list. >vim
+---
+--- echo printf("%1$*2$.*3$d", 1, 2, 3)
+--- < 001 >vim
+--- echo printf("%2$*3$.*1$d", 1, 2, 3)
+--- < 2 >vim
+--- echo printf("%3$*1$.*2$d", 1, 2, 3)
+--- < 03 >vim
+--- echo printf("%1$*2$.*3$g", 1.4142, 2, 3)
+--- < 1.414
+---
+--- You can mix specifying the width and/or precision directly
+--- and via positional arguments: >vim
+---
+--- echo printf("%1$4.*2$f", 1.4142135, 6)
+--- < 1.414214 >vim
+--- echo printf("%1$*2$.4f", 1.4142135, 6)
+--- < 1.4142 >vim
+--- echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
+--- < 1.41
+---
+--- *E1500*
+--- You cannot mix positional and non-positional arguments: >vim
+--- echo printf("%s%1$s", "One", "Two")
+--- < E1500: Cannot mix positional and non-positional arguments:
+--- %s%1$s
+---
+--- *E1501*
+--- You cannot skip a positional argument in a format string: >vim
+--- echo printf("%3$s%1$s", "One", "Two", "Three")
+--- < E1501: format argument 2 unused in $-style format:
+--- %3$s%1$s
+---
+--- *E1502*
+--- You can re-use a [field-width] (or [precision]) argument: >vim
+--- echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+--- < 1 at width 2 is: 01
+---
+--- However, you can't use it as a different type: >vim
+--- echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+--- < E1502: Positional argument 2 used as field width reused as
+--- different type: long int/int
+---
+--- *E1503*
+--- When a positional argument is used, but not the correct number
+--- or arguments is given, an error is raised: >vim
+--- echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+--- < E1503: Positional argument 3 out of bounds: %1$d at width
+--- %2$d is: %01$*2$.*3$d
+---
+--- Only the first error is reported: >vim
+--- echo printf("%01$*2$.*3$d %4$d", 1, 2)
+--- < E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+--- %4$d
+---
+--- *E1504*
+--- A positional argument can be used more than once: >vim
+--- echo printf("%1$s %2$s %1$s", "One", "Two")
+--- < One Two One
+---
+--- However, you can't use a different type the second time: >vim
+--- echo printf("%1$s %2$s %1$d", "One", "Two")
+--- < E1504: Positional argument 1 type used inconsistently:
+--- int/string
+---
+--- *E1505*
+--- Various other errors that lead to a format string being
+--- wrongly formatted lead to: >vim
+--- echo printf("%1$d at width %2$d is: %01$*2$.3$d", 1, 2)
+--- < E1505: Invalid format specifier: %1$d at width %2$d is:
+--- %01$*2$.3$d
+---
+--- *E1507*
+--- This internal error indicates that the logic to parse a
+--- positional format argument ran into a problem that couldn't be
+--- otherwise reported. Please file a bug against Vim if you run
+--- into this, copying the exact format string and parameters that
+--- were used.
+---
+--- @param fmt any
+--- @param expr1? any
+--- @return any
+function vim.fn.printf(fmt, expr1) end
+
+--- Returns the effective prompt text for buffer {buf}. {buf} can
+--- be a buffer name or number. See |prompt-buffer|.
+---
+--- If the buffer doesn't exist or isn't a prompt buffer, an empty
+--- string is returned.
+---
+--- @param buf any
+--- @return any
+function vim.fn.prompt_getprompt(buf) end
+
+--- Set prompt callback for buffer {buf} to {expr}. When {expr}
+--- is an empty string the callback is removed. This has only
+--- effect if {buf} has 'buftype' set to "prompt".
+---
+--- The callback is invoked when pressing Enter. The current
+--- buffer will always be the prompt buffer. A new line for a
+--- prompt is added before invoking the callback, thus the prompt
+--- for which the callback was invoked will be in the last but one
+--- line.
+--- If the callback wants to add text to the buffer, it must
+--- insert it above the last line, since that is where the current
+--- prompt is. This can also be done asynchronously.
+--- The callback is invoked with one argument, which is the text
+--- that was entered at the prompt. This can be an empty string
+--- if the user only typed Enter.
+--- Example: >vim
+--- func s:TextEntered(text)
+--- if a:text == 'exit' || a:text == 'quit'
+--- stopinsert
+--- " Reset 'modified' to allow the buffer to be closed.
+--- " We assume there is nothing useful to be saved.
+--- set nomodified
+--- close
+--- else
+--- " Do something useful with "a:text". In this example
+--- " we just repeat it.
+--- call append(line('$') - 1, 'Entered: "' .. a:text .. '"')
+--- endif
+--- endfunc
+--- call prompt_setcallback(bufnr(), function('s:TextEntered'))
+---
+--- @param buf any
+--- @param expr any
+--- @return any
+function vim.fn.prompt_setcallback(buf, expr) end
+
+--- Set a callback for buffer {buf} to {expr}. When {expr} is an
+--- empty string the callback is removed. This has only effect if
+--- {buf} has 'buftype' set to "prompt".
+---
+--- This callback will be invoked when pressing CTRL-C in Insert
+--- mode. Without setting a callback Vim will exit Insert mode,
+--- as in any buffer.
+---
+--- @param buf any
+--- @param expr any
+--- @return any
+function vim.fn.prompt_setinterrupt(buf, expr) end
+
+--- Set prompt for buffer {buf} to {text}. You most likely want
+--- {text} to end in a space.
+--- The result is only visible if {buf} has 'buftype' set to
+--- "prompt". Example: >vim
+--- call prompt_setprompt(bufnr(''), 'command: ')
+--- <
+---
+--- @param buf any
+--- @param text any
+--- @return any
+function vim.fn.prompt_setprompt(buf, text) end
+
+--- If the popup menu (see |ins-completion-menu|) is not visible,
+--- returns an empty |Dictionary|, otherwise, returns a
+--- |Dictionary| with the following keys:
+--- height nr of items visible
+--- width screen cells
+--- row top screen row (0 first row)
+--- col leftmost screen column (0 first col)
+--- size total nr of items
+--- scrollbar |TRUE| if scrollbar is visible
+---
+--- The values are the same as in |v:event| during |CompleteChanged|.
+---
+--- @return any
+function vim.fn.pum_getpos() end
+
+--- Returns non-zero when the popup menu is visible, zero
+--- otherwise. See |ins-completion-menu|.
+--- This can be used to avoid some things that would remove the
+--- popup menu.
+---
+--- @return any
+function vim.fn.pumvisible() end
+
+--- Evaluate Python expression {expr} and return its result
+--- converted to Vim data structures.
+--- Numbers and strings are returned as they are (strings are
+--- copied though, Unicode strings are additionally converted to
+--- UTF-8).
+--- Lists are represented as Vim |List| type.
+--- Dictionaries are represented as Vim |Dictionary| type with
+--- keys converted to strings.
+---
+--- @param expr any
+--- @return any
+function vim.fn.py3eval(expr) end
+
+--- Evaluate Python expression {expr} and return its result
+--- converted to Vim data structures.
+--- Numbers and strings are returned as they are (strings are
+--- copied though).
+--- Lists are represented as Vim |List| type.
+--- Dictionaries are represented as Vim |Dictionary| type,
+--- non-string keys result in error.
+---
+--- @param expr any
+--- @return any
+function vim.fn.pyeval(expr) end
+
+--- Evaluate Python expression {expr} and return its result
+--- converted to Vim data structures.
+--- Uses Python 2 or 3, see |python_x| and 'pyxversion'.
+--- See also: |pyeval()|, |py3eval()|
+---
+--- @param expr any
+--- @return any
+function vim.fn.pyxeval(expr) end
+
+--- 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.
+--- Returns -1 if {expr} is invalid.
+---
+--- Examples: >vim
+--- echo rand()
+--- let seed = srand()
+--- echo rand(seed)
+--- echo rand(seed) % 16 " random number 0 - 15
+--- <
+---
+--- @param expr? any
+--- @return any
+function vim.fn.rand(expr) end
+
+--- Returns a |List| with Numbers:
+--- - If only {expr} is specified: [0, 1, ..., {expr} - 1]
+--- - If {max} is specified: [{expr}, {expr} + 1, ..., {max}]
+--- - If {stride} is specified: [{expr}, {expr} + {stride}, ...,
+--- {max}] (increasing {expr} with {stride} each time, not
+--- producing a value past {max}).
+--- When the maximum is one before the start the result is an
+--- empty list. When the maximum is more than one before the
+--- start this is an error.
+--- Examples: >vim
+--- echo range(4) " [0, 1, 2, 3]
+--- echo range(2, 4) " [2, 3, 4]
+--- echo range(2, 9, 3) " [2, 5, 8]
+--- echo range(2, -2, -1) " [2, 1, 0, -1, -2]
+--- echo range(0) " []
+--- echo range(2, 0) " error!
+--- <
+---
+--- @param expr any
+--- @param max? any
+--- @param stride? any
+--- @return any
+function vim.fn.range(expr, max, stride) end
+
+--- Read file {fname} in binary mode and return a |Blob|.
+--- If {offset} is specified, read the file from the specified
+--- offset. If it is a negative value, it is used as an offset
+--- from the end of the file. E.g., to read the last 12 bytes: >vim
+--- echo readblob('file.bin', -12)
+--- <If {size} is specified, only the specified size will be read.
+--- E.g. to read the first 100 bytes of a file: >vim
+--- echo readblob('file.bin', 0, 100)
+--- <If {size} is -1 or omitted, the whole data starting from
+--- {offset} will be read.
+--- This can be also used to read the data from a character device
+--- on Unix when {size} is explicitly set. Only if the device
+--- supports seeking {offset} can be used. Otherwise it should be
+--- zero. E.g. to read 10 bytes from a serial console: >vim
+--- echo readblob('/dev/ttyS0', 0, 10)
+--- <When the file can't be opened an error message is given and
+--- the result is an empty |Blob|.
+--- When the offset is beyond the end of the file the result is an
+--- empty blob.
+--- When trying to read more bytes than are available the result
+--- is truncated.
+--- Also see |readfile()| and |writefile()|.
+---
+--- @param fname string
+--- @param offset? any
+--- @param size? any
+--- @return any
+function vim.fn.readblob(fname, offset, size) end
+
+--- Return a list with file and directory names in {directory}.
+--- You can also use |glob()| if you don't need to do complicated
+--- things, such as limiting the number of matches.
+---
+--- When {expr} is omitted all entries are included.
+--- When {expr} is given, it is evaluated to check what to do:
+--- If {expr} results in -1 then no further entries will
+--- be handled.
+--- If {expr} results in 0 then this entry will not be
+--- added to the list.
+--- If {expr} results in 1 then this entry will be added
+--- to the list.
+--- Each time {expr} is evaluated |v:val| is set to the entry name.
+--- When {expr} is a function the name is passed as the argument.
+--- For example, to get a list of files ending in ".txt": >vim
+--- echo readdir(dirname, {n -> n =~ '.txt$'})
+--- <To skip hidden and backup files: >vim
+--- echo readdir(dirname, {n -> n !~ '^\.\|\~$'})
+---
+--- <If you want to get a directory tree: >vim
+--- function! s:tree(dir)
+--- return {a:dir : map(readdir(a:dir),
+--- \ {_, x -> isdirectory(x) ?
+--- \ {x : s:tree(a:dir .. '/' .. x)} : x})}
+--- endfunction
+--- echo s:tree(".")
+--- <
+--- Returns an empty List on error.
+---
+--- @param directory any
+--- @param expr? any
+--- @return any
+function vim.fn.readdir(directory, expr) end
+
+--- Read file {fname} and return a |List|, each line of the file
+--- as an item. Lines are broken at NL characters. Macintosh
+--- files separated with CR will result in a single long line
+--- (unless a NL appears somewhere).
+--- All NUL characters are replaced with a NL character.
+--- When {type} contains "b" binary mode is used:
+--- - When the last line ends in a NL an extra empty list item is
+--- added.
+--- - No CR characters are removed.
+--- Otherwise:
+--- - CR characters that appear before a NL are removed.
+--- - Whether the last line ends in a NL or not does not matter.
+--- - Any UTF-8 byte order mark is removed from the text.
+--- When {max} is given this specifies the maximum number of lines
+--- to be read. Useful if you only want to check the first ten
+--- lines of a file: >vim
+--- for line in readfile(fname, '', 10)
+--- if line =~ 'Date' | echo line | endif
+--- endfor
+--- <When {max} is negative -{max} lines from the end of the file
+--- are returned, or as many as there are.
+--- When {max} is zero the result is an empty list.
+--- Note that without {max} the whole file is read into memory.
+--- Also note that there is no recognition of encoding. Read a
+--- file into a buffer if you need to.
+--- Deprecated (use |readblob()| instead): When {type} contains
+--- "B" a |Blob| is returned with the binary data of the file
+--- unmodified.
+--- When the file can't be opened an error message is given and
+--- the result is an empty list.
+--- Also see |writefile()|.
+---
+--- @param fname string
+--- @param type? any
+--- @param max? any
+--- @return any
+function vim.fn.readfile(fname, type, max) end
+
+--- {func} is called for every item in {object}, which can be a
+--- |String|, |List| or a |Blob|. {func} is called with two
+--- arguments: the result so far and current item. After
+--- processing all items the result is returned.
+---
+--- {initial} is the initial result. When omitted, the first item
+--- in {object} is used and {func} is first called for the second
+--- item. If {initial} is not given and {object} is empty no
+--- result can be computed, an E998 error is given.
+---
+--- Examples: >vim
+--- echo reduce([1, 3, 5], { acc, val -> acc + val })
+--- echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+--- echo reduce(0z1122, { acc, val -> 2 * acc + val })
+--- echo reduce('xyz', { acc, val -> acc .. ',' .. val })
+--- <
+---
+--- @param object any
+--- @param func any
+--- @param initial? any
+--- @return any
+function vim.fn.reduce(object, func, initial) end
+
+--- Returns the single letter name of the register being executed.
+--- Returns an empty string when no register is being executed.
+--- See |\@|.
+---
+--- @return any
+function vim.fn.reg_executing() end
+
+--- Returns the single letter name of the last recorded register.
+--- Returns an empty string when nothing was recorded yet.
+--- See |q| and |Q|.
+---
+--- @return any
+function vim.fn.reg_recorded() end
+
+--- Returns the single letter name of the register being recorded.
+--- Returns an empty string when not recording. See |q|.
+---
+--- @return any
+function vim.fn.reg_recording() end
+
+--- @return any
+function vim.fn.reltime() end
+
+--- @param start? any
+--- @return any
+function vim.fn.reltime(start) end
+
+--- Return an item that represents a time value. The item is a
+--- list with items that depend on the system.
+--- The item can be passed to |reltimestr()| to convert it to a
+--- string or |reltimefloat()| to convert to a Float.
+---
+--- Without an argument it returns the current "relative time", an
+--- implementation-defined value meaningful only when used as an
+--- argument to |reltime()|, |reltimestr()| and |reltimefloat()|.
+---
+--- With one argument it returns the time passed since the time
+--- specified in the argument.
+--- With two arguments it returns the time passed between {start}
+--- and {end}.
+---
+--- The {start} and {end} arguments must be values returned by
+--- reltime(). Returns zero on error.
+---
+--- Note: |localtime()| returns the current (non-relative) time.
+---
+--- @param start? any
+--- @param end_? any
+--- @return any
+function vim.fn.reltime(start, end_) end
+
+--- Return a Float that represents the time value of {time}.
+--- Unit of time is seconds.
+--- Example:
+--- let start = reltime()
+--- call MyFunction()
+--- let seconds = reltimefloat(reltime(start))
+--- See the note of reltimestr() about overhead.
+--- Also see |profiling|.
+--- If there is an error an empty string is returned
+---
+--- @param time any
+--- @return any
+function vim.fn.reltimefloat(time) end
+
+--- Return a String that represents the time value of {time}.
+--- This is the number of seconds, a dot and the number of
+--- microseconds. Example: >vim
+--- let start = reltime()
+--- call MyFunction()
+--- echo reltimestr(reltime(start))
+--- <Note that overhead for the commands will be added to the time.
+--- Leading spaces are used to make the string align nicely. You
+--- can use split() to remove it. >vim
+--- echo split(reltimestr(reltime(start)))[0]
+--- <Also see |profiling|.
+--- If there is an error an empty string is returned
+---
+--- @param time any
+--- @return any
+function vim.fn.reltimestr(time) end
+
+--- @param list any
+--- @param idx integer
+--- @return any
+function vim.fn.remove(list, idx) end
+
+--- Without {end}: Remove the item at {idx} from |List| {list} and
+--- return the item.
+--- With {end}: Remove items from {idx} to {end} (inclusive) and
+--- return a |List| with these items. When {idx} points to the same
+--- item as {end} a list with one item is returned. When {end}
+--- points to an item before {idx} this is an error.
+--- See |list-index| for possible values of {idx} and {end}.
+--- Returns zero on error.
+--- Example: >vim
+--- echo "last item: " .. remove(mylist, -1)
+--- call remove(mylist, 0, 9)
+--- <
+--- Use |delete()| to remove a file.
+---
+--- @param list any
+--- @param idx integer
+--- @param end_? any
+--- @return any
+function vim.fn.remove(list, idx, end_) end
+
+--- @param blob any
+--- @param idx integer
+--- @return any
+function vim.fn.remove(blob, idx) end
+
+--- Without {end}: Remove the byte at {idx} from |Blob| {blob} and
+--- return the byte.
+--- With {end}: Remove bytes from {idx} to {end} (inclusive) and
+--- return a |Blob| with these bytes. When {idx} points to the same
+--- byte as {end} a |Blob| with one byte is returned. When {end}
+--- points to a byte before {idx} this is an error.
+--- Returns zero on error.
+--- Example: >vim
+--- echo "last byte: " .. remove(myblob, -1)
+--- call remove(mylist, 0, 9)
+--- <
+---
+--- @param blob any
+--- @param idx integer
+--- @param end_? any
+--- @return any
+function vim.fn.remove(blob, idx, end_) end
+
+--- Remove the entry from {dict} with key {key} and return it.
+--- Example: >vim
+--- echo "removed " .. remove(dict, "one")
+--- <If there is no {key} in {dict} this is an error.
+--- Returns zero on error.
+---
+--- @param dict any
+--- @param key any
+--- @return any
+function vim.fn.remove(dict, key) end
+
+--- Rename the file by the name {from} to the name {to}. This
+--- should also work to move files across file systems. The
+--- result is a Number, which is 0 if the file was renamed
+--- successfully, and non-zero when the renaming failed.
+--- NOTE: If {to} exists it is overwritten without warning.
+--- This function is not available in the |sandbox|.
+---
+--- @param from any
+--- @param to any
+--- @return any
+function vim.fn.rename(from, to) end
+
+--- Repeat {expr} {count} times and return the concatenated
+--- result. Example: >vim
+--- let separator = repeat('-', 80)
+--- <When {count} is zero or negative the result is empty.
+--- When {expr} is a |List| or a |Blob| the result is {expr}
+--- concatenated {count} times. Example: >vim
+--- let longlist = repeat(['a', 'b'], 3)
+--- <Results in ['a', 'b', 'a', 'b', 'a', 'b'].
+---
+--- @param expr any
+--- @param count any
+--- @return any
+vim.fn['repeat'] = function(expr, count) end
+
+--- On MS-Windows, when {filename} is a shortcut (a .lnk file),
+--- returns the path the shortcut points to in a simplified form.
+--- On Unix, repeat resolving symbolic links in all path
+--- components of {filename} and return the simplified result.
+--- To cope with link cycles, resolving of symbolic links is
+--- stopped after 100 iterations.
+--- On other systems, return the simplified {filename}.
+--- The simplification step is done as by |simplify()|.
+--- resolve() keeps a leading path component specifying the
+--- current directory (provided the result is still a relative
+--- path name) and also keeps a trailing path separator.
+---
+--- @param filename any
+--- @return any
+function vim.fn.resolve(filename) end
+
+--- Reverse the order of items in {object}. {object} can be a
+--- |List|, a |Blob| or a |String|. For a List and a Blob the
+--- items are reversed in-place and {object} is returned.
+--- For a String a new String is returned.
+--- Returns zero if {object} is not a List, Blob or a String.
+--- If you want a List or Blob to remain unmodified make a copy
+--- first: >vim
+--- let revlist = reverse(copy(mylist))
+--- <
+---
+--- @param object any
+--- @return any
+function vim.fn.reverse(object) end
+
+--- Round off {expr} to the nearest integral value and return it
+--- as a |Float|. If {expr} lies halfway between two integral
+--- values, then use the larger one (away from zero).
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo round(0.456)
+--- < 0.0 >vim
+--- echo round(4.5)
+--- < 5.0 >vim
+--- echo round(-4.5)
+--- < -5.0
+---
+--- @param expr any
+--- @return any
+function vim.fn.round(expr) end
+
+--- Sends {event} to {channel} via |RPC| and returns immediately.
+--- If {channel} is 0, the event is broadcast to all channels.
+--- Example: >vim
+--- au VimLeave call rpcnotify(0, "leaving")
+--- <
+---
+--- @param channel any
+--- @param event any
+--- @param args? any
+--- @return any
+function vim.fn.rpcnotify(channel, event, args) end
+
+--- Sends a request to {channel} to invoke {method} via
+--- |RPC| and blocks until a response is received.
+--- Example: >vim
+--- let result = rpcrequest(rpc_chan, "func", 1, 2, 3)
+--- <
+---
+--- @param channel any
+--- @param method any
+--- @param args? any
+--- @return any
+function vim.fn.rpcrequest(channel, method, args) end
+
+--- Deprecated. Replace >vim
+--- let id = rpcstart('prog', ['arg1', 'arg2'])
+--- <with >vim
+--- let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true})
+--- <
+---
+--- @param prog any
+--- @param argv? any
+--- @return any
+function vim.fn.rpcstart(prog, argv) end
+
+--- @deprecated
+--- Use |jobstop()| instead to stop any job, or
+--- `chanclose(id, "rpc")` to close RPC communication
+--- without stopping the job. Use chanclose(id) to close
+--- any socket.
+---
+--- @param ... any
+--- @return any
+function vim.fn.rpcstop(...) end
+
+--- Evaluate Ruby expression {expr} and return its result
+--- converted to Vim data structures.
+--- Numbers, floats and strings are returned as they are (strings
+--- are copied though).
+--- Arrays are represented as Vim |List| type.
+--- Hashes are represented as Vim |Dictionary| type.
+--- Other objects are represented as strings resulted from their
+--- "Object#to_s" method.
+---
+--- @param expr any
+--- @return any
+function vim.fn.rubyeval(expr) end
+
+--- Like |screenchar()|, but return the attribute. This is a rather
+--- arbitrary number that can only be used to compare to the
+--- attribute at other positions.
+--- Returns -1 when row or col is out of range.
+---
+--- @param row any
+--- @param col integer
+--- @return any
+function vim.fn.screenattr(row, col) end
+
+--- The result is a Number, which is the character at position
+--- [row, col] on the screen. This works for every possible
+--- screen position, also status lines, window separators and the
+--- command line. The top left position is row one, column one
+--- The character excludes composing characters. For double-byte
+--- encodings it may only be the first byte.
+--- This is mainly to be used for testing.
+--- Returns -1 when row or col is out of range.
+---
+--- @param row any
+--- @param col integer
+--- @return any
+function vim.fn.screenchar(row, col) end
+
+--- The result is a |List| of Numbers. The first number is the same
+--- as what |screenchar()| returns. Further numbers are
+--- composing characters on top of the base character.
+--- This is mainly to be used for testing.
+--- Returns an empty List when row or col is out of range.
+---
+--- @param row any
+--- @param col integer
+--- @return any
+function vim.fn.screenchars(row, col) end
+
+--- The result is a Number, which is the current screen column of
+--- the cursor. The leftmost column has number 1.
+--- This function is mainly used for testing.
+---
+--- Note: Always returns the current screen column, thus if used
+--- in a command (e.g. ":echo screencol()") it will return the
+--- column inside the command line, which is 1 when the command is
+--- executed. To get the cursor position in the file use one of
+--- the following mappings: >vim
+--- nnoremap <expr> GG ":echom " .. screencol() .. "\n"
+--- nnoremap <silent> GG :echom screencol()<CR>
+--- noremap GG <Cmd>echom screencol()<Cr>
+--- <
+---
+--- @return any
+function vim.fn.screencol() end
+
+--- The result is a Dict with the screen position of the text
+--- character in window {winid} at buffer line {lnum} and column
+--- {col}. {col} is a one-based byte index.
+--- The Dict has these members:
+--- row screen row
+--- col first screen column
+--- endcol last screen column
+--- curscol cursor screen column
+--- If the specified position is not visible, all values are zero.
+--- The "endcol" value differs from "col" when the character
+--- occupies more than one screen cell. E.g. for a Tab "col" can
+--- be 1 and "endcol" can be 8.
+--- The "curscol" value is where the cursor would be placed. For
+--- a Tab it would be the same as "endcol", while for a double
+--- width character it would be the same as "col".
+--- The |conceal| feature is ignored here, the column numbers are
+--- as if 'conceallevel' is zero. You can set the cursor to the
+--- right position and use |screencol()| to get the value with
+--- |conceal| taken into account.
+--- If the position is in a closed fold the screen position of the
+--- first character is returned, {col} is not used.
+--- Returns an empty Dict if {winid} is invalid.
+---
+--- @param winid integer
+--- @param lnum integer
+--- @param col integer
+--- @return any
+function vim.fn.screenpos(winid, lnum, col) end
+
+--- The result is a Number, which is the current screen row of the
+--- cursor. The top line has number one.
+--- This function is mainly used for testing.
+--- Alternatively you can use |winline()|.
+---
+--- Note: Same restrictions as with |screencol()|.
+---
+--- @return any
+function vim.fn.screenrow() end
+
+--- The result is a String that contains the base character and
+--- any composing characters at position [row, col] on the screen.
+--- This is like |screenchars()| but returning a String with the
+--- characters.
+--- This is mainly to be used for testing.
+--- Returns an empty String when row or col is out of range.
+---
+--- @param row any
+--- @param col integer
+--- @return any
+function vim.fn.screenstring(row, col) end
+
+--- Search for regexp pattern {pattern}. The search starts at the
+--- cursor position (you can use |cursor()| to set it).
+---
+--- When a match has been found its line number is returned.
+--- If there is no match a 0 is returned and the cursor doesn't
+--- move. No error message is given.
+---
+--- {flags} is a String, which can contain these character flags:
+--- 'b' search Backward instead of forward
+--- 'c' accept a match at the Cursor position
+--- 'e' move to the End of the match
+--- 'n' do Not move the cursor
+--- 'p' return number of matching sub-Pattern (see below)
+--- 's' Set the ' mark at the previous location of the cursor
+--- 'w' Wrap around the end of the file
+--- 'W' don't Wrap around the end of the file
+--- 'z' start searching at the cursor column instead of Zero
+--- If neither 'w' or 'W' is given, the 'wrapscan' option applies.
+---
+--- If the 's' flag is supplied, the ' mark is set, only if the
+--- cursor is moved. The 's' flag cannot be combined with the 'n'
+--- flag.
+---
+--- 'ignorecase', 'smartcase' and 'magic' are used.
+---
+--- When the 'z' flag is not given, forward searching always
+--- starts in column zero and then matches before the cursor are
+--- skipped. When the 'c' flag is present in 'cpo' the next
+--- search starts after the match. Without the 'c' flag the next
+--- search starts one column after the start of the match. This
+--- matters for overlapping matches. See |cpo-c|. You can also
+--- insert "\ze" to change where the match ends, see |/\ze|.
+---
+--- When searching backwards and the 'z' flag is given then the
+--- search starts in column zero, thus no match in the current
+--- line will be found (unless wrapping around the end of the
+--- file).
+---
+--- When the {stopline} argument is given then the search stops
+--- after searching this line. This is useful to restrict the
+--- search to a range of lines. Examples: >vim
+--- let match = search('(', 'b', line("w0"))
+--- let end = search('END', '', line("w$"))
+--- <When {stopline} is used and it is not zero this also implies
+--- that the search does not wrap around the end of the file.
+--- A zero value is equal to not giving the argument.
+---
+--- When the {timeout} argument is given the search stops when
+--- more than this many milliseconds have passed. Thus when
+--- {timeout} is 500 the search stops after half a second.
+--- The value must not be negative. A zero value is like not
+--- giving the argument.
+---
+--- If the {skip} expression is given it is evaluated with the
+--- cursor positioned on the start of a match. If it evaluates to
+--- non-zero this match is skipped. This can be used, for
+--- example, to skip a match in a comment or a string.
+--- {skip} can be a string, which is evaluated as an expression, a
+--- function reference or a lambda.
+--- When {skip} is omitted or empty, every match is accepted.
+--- When evaluating {skip} causes an error the search is aborted
+--- and -1 returned.
+--- *search()-sub-match*
+--- With the 'p' flag the returned value is one more than the
+--- first sub-match in \(\). One if none of them matched but the
+--- whole pattern did match.
+--- To get the column number too use |searchpos()|.
+---
+--- The cursor will be positioned at the match, unless the 'n'
+--- flag is used.
+---
+--- Example (goes over all files in the argument list): >vim
+--- let n = 1
+--- while n <= argc() " loop over all files in arglist
+--- exe "argument " .. n
+--- " start at the last char in the file and wrap for the
+--- " first search to find match at start of file
+--- normal G$
+--- let flags = "w"
+--- while search("foo", flags) > 0
+--- s/foo/bar/g
+--- let flags = "W"
+--- endwhile
+--- update " write the file if modified
+--- let n = n + 1
+--- endwhile
+--- <
+--- Example for using some flags: >vim
+--- echo search('\<if\|\(else\)\|\(endif\)', 'ncpe')
+--- <This will search for the keywords "if", "else", and "endif"
+--- under or after the cursor. Because of the 'p' flag, it
+--- returns 1, 2, or 3 depending on which keyword is found, or 0
+--- if the search fails. With the cursor on the first word of the
+--- line:
+--- if (foo == 0) | let foo = foo + 1 | endif ~
+--- the function returns 1. Without the 'c' flag, the function
+--- finds the "endif" and returns 3. The same thing happens
+--- without the 'e' flag if the cursor is on the "f" of "if".
+--- The 'n' flag tells the function not to move the cursor.
+---
+--- @param pattern any
+--- @param flags? string
+--- @param stopline? any
+--- @param timeout? integer
+--- @param skip? any
+--- @return any
+function vim.fn.search(pattern, flags, stopline, timeout, skip) end
+
+--- Get or update the last search count, like what is displayed
+--- without the "S" flag in 'shortmess'. This works even if
+--- 'shortmess' does contain the "S" flag.
+---
+--- This returns a |Dictionary|. The dictionary is empty if the
+--- previous pattern was not set and "pattern" was not specified.
+---
+--- key type meaning ~
+--- current |Number| current position of match;
+--- 0 if the cursor position is
+--- before the first match
+--- exact_match |Boolean| 1 if "current" is matched on
+--- "pos", otherwise 0
+--- total |Number| total count of matches found
+--- incomplete |Number| 0: search was fully completed
+--- 1: recomputing was timed out
+--- 2: max count exceeded
+---
+--- For {options} see further down.
+---
+--- To get the last search count when |n| or |N| was pressed, call
+--- this function with `recompute: 0` . This sometimes returns
+--- wrong information because |n| and |N|'s maximum count is 99.
+--- If it exceeded 99 the result must be max count + 1 (100). If
+--- you want to get correct information, specify `recompute: 1`: >vim
+---
+--- " result == maxcount + 1 (100) when many matches
+--- let result = searchcount(#{recompute: 0})
+---
+--- " Below returns correct result (recompute defaults
+--- " to 1)
+--- let result = searchcount()
+--- <
+--- The function is useful to add the count to 'statusline': >vim
+--- function! LastSearchCount() abort
+--- let result = searchcount(#{recompute: 0})
+--- if empty(result)
+--- return ''
+--- endif
+--- if result.incomplete ==# 1 " timed out
+--- return printf(' /%s [?/??]', \@/)
+--- elseif result.incomplete ==# 2 " max count exceeded
+--- if result.total > result.maxcount &&
+--- \ result.current > result.maxcount
+--- return printf(' /%s [>%d/>%d]', \@/,
+--- \ result.current, result.total)
+--- elseif result.total > result.maxcount
+--- return printf(' /%s [%d/>%d]', \@/,
+--- \ result.current, result.total)
+--- endif
+--- endif
+--- return printf(' /%s [%d/%d]', \@/,
+--- \ result.current, result.total)
+--- endfunction
+--- let &statusline ..= '%{LastSearchCount()}'
+---
+--- " Or if you want to show the count only when
+--- " 'hlsearch' was on
+--- " let &statusline ..=
+--- " \ '%{v:hlsearch ? LastSearchCount() : ""}'
+--- <
+--- You can also update the search count, which can be useful in a
+--- |CursorMoved| or |CursorMovedI| autocommand: >vim
+---
+--- autocmd CursorMoved,CursorMovedI *
+--- \ let s:searchcount_timer = timer_start(
+--- \ 200, function('s:update_searchcount'))
+--- function! s:update_searchcount(timer) abort
+--- if a:timer ==# s:searchcount_timer
+--- call searchcount(#{
+--- \ recompute: 1, maxcount: 0, timeout: 100})
+--- redrawstatus
+--- endif
+--- endfunction
+--- <
+--- This can also be used to count matched texts with specified
+--- pattern in the current buffer using "pattern": >vim
+---
+--- " Count '\<foo\>' in this buffer
+--- " (Note that it also updates search count)
+--- let result = searchcount(#{pattern: '\<foo\>'})
+---
+--- " To restore old search count by old pattern,
+--- " search again
+--- call searchcount()
+--- <
+--- {options} must be a |Dictionary|. It can contain:
+--- key type meaning ~
+--- recompute |Boolean| if |TRUE|, recompute the count
+--- like |n| or |N| was executed.
+--- otherwise returns the last
+--- computed result (when |n| or
+--- |N| was used when "S" is not
+--- in 'shortmess', or this
+--- function was called).
+--- (default: |TRUE|)
+--- pattern |String| recompute if this was given
+--- and different with |\@/|.
+--- this works as same as the
+--- below command is executed
+--- before calling this function >vim
+--- let \@/ = pattern
+--- < (default: |\@/|)
+--- timeout |Number| 0 or negative number is no
+--- timeout. timeout milliseconds
+--- for recomputing the result
+--- (default: 0)
+--- maxcount |Number| 0 or negative number is no
+--- limit. max count of matched
+--- text while recomputing the
+--- result. if search exceeded
+--- total count, "total" value
+--- becomes `maxcount + 1`
+--- (default: 0)
+--- pos |List| `[lnum, col, off]` value
+--- when recomputing the result.
+--- this changes "current" result
+--- value. see |cursor()|, |getpos()|
+--- (default: cursor's position)
+---
+--- @param options? table
+--- @return any
+function vim.fn.searchcount(options) end
+
+--- Search for the declaration of {name}.
+---
+--- With a non-zero {global} argument it works like |gD|, find
+--- first match in the file. Otherwise it works like |gd|, find
+--- first match in the function.
+---
+--- With a non-zero {thisblock} argument matches in a {} block
+--- that ends before the cursor position are ignored. Avoids
+--- finding variable declarations only valid in another scope.
+---
+--- Moves the cursor to the found match.
+--- Returns zero for success, non-zero for failure.
+--- Example: >vim
+--- if searchdecl('myvar') == 0
+--- echo getline('.')
+--- endif
+--- <
+---
+--- @param name string
+--- @param global? any
+--- @param thisblock? any
+--- @return any
+function vim.fn.searchdecl(name, global, thisblock) end
+
+--- Search for the match of a nested start-end pair. This can be
+--- used to find the "endif" that matches an "if", while other
+--- if/endif pairs in between are ignored.
+--- The search starts at the cursor. The default is to search
+--- forward, include 'b' in {flags} to search backward.
+--- If a match is found, the cursor is positioned at it and the
+--- line number is returned. If no match is found 0 or -1 is
+--- returned and the cursor doesn't move. No error message is
+--- given.
+---
+--- {start}, {middle} and {end} are patterns, see |pattern|. They
+--- must not contain \( \) pairs. Use of \%( \) is allowed. When
+--- {middle} is not empty, it is found when searching from either
+--- direction, but only when not in a nested start-end pair. A
+--- typical use is: >vim
+--- echo searchpair('\<if\>', '\<else\>', '\<endif\>')
+--- <By leaving {middle} empty the "else" is skipped.
+---
+--- {flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with
+--- |search()|. Additionally:
+--- 'r' Repeat until no more matches found; will find the
+--- outer pair. Implies the 'W' flag.
+--- 'm' Return number of matches instead of line number with
+--- the match; will be > 1 when 'r' is used.
+--- Note: it's nearly always a good idea to use the 'W' flag, to
+--- avoid wrapping around the end of the file.
+---
+--- When a match for {start}, {middle} or {end} is found, the
+--- {skip} expression is evaluated with the cursor positioned on
+--- the start of the match. It should return non-zero if this
+--- match is to be skipped. E.g., because it is inside a comment
+--- or a string.
+--- When {skip} is omitted or empty, every match is accepted.
+--- When evaluating {skip} causes an error the search is aborted
+--- and -1 returned.
+--- {skip} can be a string, a lambda, a funcref or a partial.
+--- Anything else makes the function fail.
+---
+--- For {stopline} and {timeout} see |search()|.
+---
+--- The value of 'ignorecase' is used. 'magic' is ignored, the
+--- patterns are used like it's on.
+---
+--- The search starts exactly at the cursor. A match with
+--- {start}, {middle} or {end} at the next character, in the
+--- direction of searching, is the first one found. Example: >vim
+--- if 1
+--- if 2
+--- endif 2
+--- endif 1
+--- <When starting at the "if 2", with the cursor on the "i", and
+--- searching forwards, the "endif 2" is found. When starting on
+--- the character just before the "if 2", the "endif 1" will be
+--- found. That's because the "if 2" will be found first, and
+--- then this is considered to be a nested if/endif from "if 2" to
+--- "endif 2".
+--- When searching backwards and {end} is more than one character,
+--- it may be useful to put "\zs" at the end of the pattern, so
+--- that when the cursor is inside a match with the end it finds
+--- the matching start.
+---
+--- Example, to find the "endif" command in a Vim script: >vim
+---
+--- echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W',
+--- \ 'getline(".") =~ "^\\s*\""')
+---
+--- <The cursor must be at or after the "if" for which a match is
+--- to be found. Note that single-quote strings are used to avoid
+--- having to double the backslashes. The skip expression only
+--- catches comments at the start of a line, not after a command.
+--- Also, a word "en" or "if" halfway through a line is considered
+--- a match.
+--- Another example, to search for the matching "{" of a "}": >vim
+---
+--- echo searchpair('{', '', '}', 'bW')
+---
+--- <This works when the cursor is at or before the "}" for which a
+--- match is to be found. To reject matches that syntax
+--- highlighting recognized as strings: >vim
+---
+--- echo searchpair('{', '', '}', 'bW',
+--- \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')
+--- <
+---
+--- @return any
+function vim.fn.searchpair() end
+
+--- Same as |searchpair()|, but returns a |List| with the line and
+--- column position of the match. The first element of the |List|
+--- is the line number and the second element is the byte index of
+--- the column position of the match. If no match is found,
+--- returns [0, 0]. >vim
+---
+--- let [lnum,col] = searchpairpos('{', '', '}', 'n')
+--- <
+--- See |match-parens| for a bigger and more useful example.
+---
+--- @return any
+function vim.fn.searchpairpos() end
+
+--- Same as |search()|, but returns a |List| with the line and
+--- column position of the match. The first element of the |List|
+--- is the line number and the second element is the byte index of
+--- the column position of the match. If no match is found,
+--- returns [0, 0].
+--- Example: >vim
+--- let [lnum, col] = searchpos('mypattern', 'n')
+---
+--- <When the 'p' flag is given then there is an extra item with
+--- the sub-pattern match number |search()-sub-match|. Example: >vim
+--- let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np')
+--- <In this example "submatch" is 2 when a lowercase letter is
+--- found |/\l|, 3 when an uppercase letter is found |/\u|.
+---
+--- @param pattern any
+--- @param flags? string
+--- @param stopline? any
+--- @param timeout? integer
+--- @param skip? any
+--- @return any
+function vim.fn.searchpos(pattern, flags, stopline, timeout, skip) end
+
+--- Returns a list of server addresses, or empty if all servers
+--- were stopped. |serverstart()| |serverstop()|
+--- Example: >vim
+--- echo serverlist()
+--- <
+---
+--- @return any
+function vim.fn.serverlist() end
+
+--- Opens a socket or named pipe at {address} and listens for
+--- |RPC| messages. Clients can send |API| commands to the
+--- returned address to control Nvim.
+---
+--- Returns the address string (which may differ from the
+--- {address} argument, see below).
+---
+--- - If {address} has a colon (":") it is a TCP/IPv4/IPv6 address
+--- where the last ":" separates host and port (empty or zero
+--- assigns a random port).
+--- - Else {address} is the path to a named pipe (except on Windows).
+--- - If {address} has no slashes ("/") it is treated as the
+--- "name" part of a generated path in this format: >vim
+--- stdpath("run").."/{name}.{pid}.{counter}"
+--- < - If {address} is omitted the name is "nvim". >vim
+--- echo serverstart()
+--- < >
+--- => /tmp/nvim.bram/oknANW/nvim.15430.5
+--- <
+--- Example bash command to list all Nvim servers: >bash
+--- ls ${XDG_RUNTIME_DIR:-${TMPDIR}nvim.${USER}}/*/nvim.*.0
+---
+--- <Example named pipe: >vim
+--- if has('win32')
+--- echo serverstart('\\.\pipe\nvim-pipe-1234')
+--- else
+--- echo serverstart('nvim.sock')
+--- endif
+--- <
+--- Example TCP/IP address: >vim
+--- echo serverstart('::1:12345')
+--- <
+---
+--- @param address? any
+--- @return any
+function vim.fn.serverstart(address) end
+
+--- Closes the pipe or socket at {address}.
+--- Returns TRUE if {address} is valid, else FALSE.
+--- If |v:servername| is stopped it is set to the next available
+--- address in |serverlist()|.
+---
+--- @param address any
+--- @return any
+function vim.fn.serverstop(address) end
+
+--- Set line {lnum} to {text} in buffer {buf}. This works like
+--- |setline()| for the specified buffer.
+---
+--- This function works only for loaded buffers. First call
+--- |bufload()| if needed.
+---
+--- To insert lines use |appendbufline()|.
+---
+--- {text} can be a string to set one line, or a List of strings
+--- to set multiple lines. If the List extends below the last
+--- line then those lines are added. If the List is empty then
+--- nothing is changed and zero is returned.
+---
+--- For the use of {buf}, see |bufname()| above.
+---
+--- {lnum} is used like with |setline()|.
+--- Use "$" to refer to the last line in buffer {buf}.
+--- When {lnum} is just below the last line the {text} will be
+--- added below the last line.
+--- On success 0 is returned, on failure 1 is returned.
+---
+--- If {buf} is not a valid buffer or {lnum} is not valid, an
+--- error message is given.
+---
+--- @param buf any
+--- @param lnum integer
+--- @param text any
+--- @return any
+function vim.fn.setbufline(buf, lnum, text) end
+
+--- Set option or local variable {varname} in buffer {buf} to
+--- {val}.
+--- This also works for a global or local window option, but it
+--- doesn't work for a global or local window variable.
+--- For a local window option the global value is unchanged.
+--- For the use of {buf}, see |bufname()| above.
+--- The {varname} argument is a string.
+--- Note that the variable name without "b:" must be used.
+--- Examples: >vim
+--- call setbufvar(1, "&mod", 1)
+--- call setbufvar("todo", "myvar", "foobar")
+--- <This function is not available in the |sandbox|.
+---
+--- @param buf any
+--- @param varname string
+--- @param val any
+--- @return any
+function vim.fn.setbufvar(buf, varname, val) end
+
+--- Specify overrides for cell widths of character ranges. This
+--- tells Vim how wide characters are when displayed in the
+--- terminal, counted in screen cells. The values override
+--- 'ambiwidth'. Example: >vim
+--- call setcellwidths([
+--- \ [0x111, 0x111, 1],
+--- \ [0x2194, 0x2199, 2],
+--- \ ])
+---
+--- <The {list} argument is a List of Lists with each three
+--- numbers: [{low}, {high}, {width}]. *E1109* *E1110*
+--- {low} and {high} can be the same, in which case this refers to
+--- one character. Otherwise it is the range of characters from
+--- {low} to {high} (inclusive). *E1111* *E1114*
+--- Only characters with value 0x80 and higher can be used.
+---
+--- {width} must be either 1 or 2, indicating the character width
+--- in screen cells. *E1112*
+--- An error is given if the argument is invalid, also when a
+--- range overlaps with another. *E1113*
+---
+--- If the new value causes 'fillchars' or 'listchars' to become
+--- invalid it is rejected and an error is given.
+---
+--- To clear the overrides pass an empty {list}: >vim
+--- call setcellwidths([])
+---
+--- <You can use the script $VIMRUNTIME/tools/emoji_list.vim to see
+--- the effect for known emoji characters. Move the cursor
+--- through the text to check if the cell widths of your terminal
+--- match with what Vim knows about each emoji. If it doesn't
+--- look right you need to adjust the {list} argument.
+---
+--- @param list any
+--- @return any
+function vim.fn.setcellwidths(list) end
+
+--- 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: >vim
+--- call setcharpos('.', [0, 8, 4, 0])
+--- <positions the cursor on the fourth character '요'. >vim
+--- call setpos('.', [0, 8, 4, 0])
+--- <positions the cursor on the second character '보'.
+---
+--- @param expr any
+--- @param list any
+--- @return any
+function vim.fn.setcharpos(expr, list) end
+
+--- Set the current character search information to {dict},
+--- which contains one or more of the following entries:
+---
+--- char character which will be used for a subsequent
+--- |,| or |;| command; an empty string clears the
+--- character search
+--- forward direction of character search; 1 for forward,
+--- 0 for backward
+--- until type of character search; 1 for a |t| or |T|
+--- character search, 0 for an |f| or |F|
+--- character search
+---
+--- This can be useful to save/restore a user's character search
+--- from a script: >vim
+--- let prevsearch = getcharsearch()
+--- " Perform a command which clobbers user's search
+--- call setcharsearch(prevsearch)
+--- <Also see |getcharsearch()|.
+---
+--- @param dict any
+--- @return any
+function vim.fn.setcharsearch(dict) end
+
+--- Set the command line to {str} and set the cursor position to
+--- {pos}.
+--- If {pos} is omitted, the cursor is positioned after the text.
+--- Returns 0 when successful, 1 when not editing the command
+--- line.
+---
+--- @param str any
+--- @param pos? any
+--- @return any
+function vim.fn.setcmdline(str, pos) end
+
+--- Set the cursor position in the command line to byte position
+--- {pos}. The first position is 1.
+--- Use |getcmdpos()| to obtain the current position.
+--- Only works while editing the command line, thus you must use
+--- |c_CTRL-\_e|, |c_CTRL-R_=| or |c_CTRL-R_CTRL-R| with '='. For
+--- |c_CTRL-\_e| and |c_CTRL-R_CTRL-R| with '=' the position is
+--- set after the command line is set to the expression. For
+--- |c_CTRL-R_=| it is set after evaluating the expression but
+--- before inserting the resulting text.
+--- When the number is too big the cursor is put at the end of the
+--- line. A number smaller than one has undefined results.
+--- Returns 0 when successful, 1 when not editing the command
+--- line.
+---
+--- @param pos any
+--- @return any
+function vim.fn.setcmdpos(pos) end
+
+--- @param lnum integer
+--- @param col? integer
+--- @param off? any
+--- @return any
+function vim.fn.setcursorcharpos(lnum, col, off) end
+
+--- 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: >vim
+--- call setcursorcharpos(4, 3)
+--- <positions the cursor on the third character '세'. >vim
+--- call cursor(4, 3)
+--- <positions the cursor on the first character '여'.
+---
+--- @param list any
+--- @return any
+function vim.fn.setcursorcharpos(list) end
+
+--- Set environment variable {name} to {val}. Example: >vim
+--- call setenv('HOME', '/home/myhome')
+---
+--- <When {val} is |v:null| the environment variable is deleted.
+--- See also |expr-env|.
+---
+--- @param name string
+--- @param val any
+--- @return any
+function vim.fn.setenv(name, val) end
+
+--- Set the file permissions for {fname} to {mode}.
+--- {mode} must be a string with 9 characters. It is of the form
+--- "rwxrwxrwx", where each group of "rwx" flags represent, in
+--- turn, the permissions of the owner of the file, the group the
+--- file belongs to, and other users. A '-' character means the
+--- permission is off, any other character means on. Multi-byte
+--- characters are not supported.
+---
+--- For example "rw-r-----" means read-write for the user,
+--- readable by the group, not accessible by others. "xx-x-----"
+--- would do the same thing.
+---
+--- Returns non-zero for success, zero for failure.
+---
+--- To read permissions see |getfperm()|.
+---
+--- @param fname string
+--- @param mode string
+--- @return any
+function vim.fn.setfperm(fname, mode) end
+
+--- Set line {lnum} of the current buffer to {text}. To insert
+--- lines use |append()|. To set lines in another buffer use
+--- |setbufline()|.
+---
+--- {lnum} is used like with |getline()|.
+--- When {lnum} is just below the last line the {text} will be
+--- added below the last line.
+--- {text} can be any type or a List of any type, each item is
+--- converted to a String. When {text} is an empty List then
+--- nothing is changed and FALSE is returned.
+---
+--- If this succeeds, FALSE is returned. If this fails (most likely
+--- because {lnum} is invalid) TRUE is returned.
+---
+--- Example: >vim
+--- call setline(5, strftime("%c"))
+---
+--- <When {text} is a |List| then line {lnum} and following lines
+--- will be set to the items in the list. Example: >vim
+--- call setline(5, ['aaa', 'bbb', 'ccc'])
+--- <This is equivalent to: >vim
+--- for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']]
+--- call setline(n, l)
+--- endfor
+---
+--- <Note: The '[ and '] marks are not set.
+---
+--- @param lnum integer
+--- @param text any
+--- @return any
+function vim.fn.setline(lnum, text) end
+
+--- Create or replace or add to 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.
+---
+--- For a location list window, the displayed location list is
+--- modified. For an invalid window number {nr}, -1 is returned.
+--- Otherwise, same as |setqflist()|.
+--- Also see |location-list|.
+---
+--- For {action} see |setqflist-action|.
+---
+--- If the optional {what} dictionary argument is supplied, then
+--- only the items listed in {what} are set. Refer to |setqflist()|
+--- for the list of supported keys in {what}.
+---
+--- @param nr integer
+--- @param list any
+--- @param action? any
+--- @param what? any
+--- @return any
+function vim.fn.setloclist(nr, list, action, what) end
+
+--- Restores a list of matches saved by |getmatches()| for the
+--- current window. Returns 0 if successful, otherwise -1. All
+--- current matches are cleared before the list is restored. See
+--- example for |getmatches()|.
+--- If {win} is specified, use the window with this number or
+--- window ID instead of the current window.
+---
+--- @param list any
+--- @param win? any
+--- @return any
+function vim.fn.setmatches(list, win) end
+
+--- Set the position for String {expr}. Possible values:
+--- . the cursor
+--- 'x mark x
+---
+--- {list} must be a |List| with four or five numbers:
+--- [bufnum, lnum, col, off]
+--- [bufnum, lnum, col, off, curswant]
+---
+--- "bufnum" is the buffer number. Zero can be used for the
+--- current buffer. When setting an uppercase mark "bufnum" is
+--- used for the mark position. For other marks it specifies the
+--- buffer to set the mark in. You can use the |bufnr()| function
+--- to turn a file name into a buffer number.
+--- For setting the cursor and the ' mark "bufnum" is ignored,
+--- since these are associated with a window, not a buffer.
+--- Does not change the jumplist.
+---
+--- "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. 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
+--- character. E.g., a position within a <Tab> or after the last
+--- character.
+---
+--- The "curswant" number is only used when setting the cursor
+--- position. It sets the preferred column for when moving the
+--- cursor vertically. When the "curswant" number is missing the
+--- preferred column is not set. When it is present and setting a
+--- mark position it is not used.
+---
+--- Note that for '< and '> changing the line number may result in
+--- the marks to be effectively be swapped, so that '< is always
+--- before '>.
+---
+--- Returns 0 when the position could be set, -1 otherwise.
+--- An error message is given if {expr} is invalid.
+---
+--- 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
+--- |k| motions will jump to previous columns! Use |cursor()| to
+--- also set the preferred column. Also see the "curswant" key in
+--- |winrestview()|.
+---
+--- @param expr any
+--- @param list any
+--- @return any
+function vim.fn.setpos(expr, list) end
+
+--- Create or replace or add to the quickfix list.
+---
+--- If the optional {what} dictionary argument is supplied, then
+--- only the items listed in {what} are set. The first {list}
+--- argument is ignored. See below for the supported items in
+--- {what}.
+--- *setqflist-what*
+--- When {what} is not present, the items in {list} are used. Each
+--- item must be a dictionary. Non-dictionary items in {list} are
+--- ignored. Each dictionary item can contain the following
+--- entries:
+---
+--- bufnr buffer number; must be the number of a valid
+--- buffer
+--- filename name of a file; only used when "bufnr" is not
+--- present or it is invalid.
+--- module name of a module; if given it will be used in
+--- quickfix error window instead of the filename.
+--- lnum line number in the file
+--- end_lnum end of lines, if the item spans multiple lines
+--- pattern search pattern used to locate the error
+--- col column number
+--- vcol when non-zero: "col" is visual column
+--- when zero: "col" is byte index
+--- end_col end column, if the item spans multiple columns
+--- nr error number
+--- text description of the error
+--- type single-character error type, 'E', 'W', etc.
+--- valid recognized error message
+--- user_data
+--- custom data associated with the item, can be
+--- any type.
+---
+--- The "col", "vcol", "nr", "type" and "text" entries are
+--- optional. Either "lnum" or "pattern" entry can be used to
+--- locate a matching error line.
+--- If the "filename" and "bufnr" entries are not present or
+--- neither the "lnum" or "pattern" entries are present, then the
+--- item will not be handled as an error line.
+--- If both "pattern" and "lnum" are present then "pattern" will
+--- be used.
+--- If the "valid" entry is not supplied, then the valid flag is
+--- set when "bufnr" is a valid buffer or "filename" exists.
+--- If you supply an empty {list}, the quickfix list will be
+--- cleared.
+--- Note that the list is not exactly the same as what
+--- |getqflist()| returns.
+---
+--- {action} values: *setqflist-action* *E927*
+--- 'a' The items from {list} are added to the existing
+--- quickfix list. If there is no existing list, then a
+--- new list is created.
+---
+--- 'r' The items from the current quickfix list are replaced
+--- with the items from {list}. This can also be used to
+--- clear the list: >vim
+--- call setqflist([], 'r')
+--- <
+--- 'f' All the quickfix lists in the quickfix stack are
+--- freed.
+---
+--- If {action} is not present or is set to ' ', then a new list
+--- is created. The new quickfix list is added after the current
+--- quickfix list in the stack and all the following lists are
+--- freed. To add a new quickfix list at the end of the stack,
+--- set "nr" in {what} to "$".
+---
+--- The following items can be specified in dictionary {what}:
+--- context quickfix list context. See |quickfix-context|
+--- efm errorformat to use when parsing text from
+--- "lines". If this is not present, then the
+--- 'errorformat' option value is used.
+--- See |quickfix-parse|
+--- id quickfix list identifier |quickfix-ID|
+--- idx index of the current entry in the quickfix
+--- list specified by "id" or "nr". If set to '$',
+--- then the last entry in the list is set as the
+--- current entry. See |quickfix-index|
+--- items list of quickfix entries. Same as the {list}
+--- argument.
+--- lines use 'errorformat' to parse a list of lines and
+--- add the resulting entries to the quickfix list
+--- {nr} or {id}. Only a |List| value is supported.
+--- See |quickfix-parse|
+--- nr list number in the quickfix stack; zero
+--- means the current quickfix list and "$" means
+--- the last quickfix list.
+--- quickfixtextfunc
+--- function to get the text to display in the
+--- quickfix window. The value can be the name of
+--- a function or a funcref or a lambda. Refer to
+--- |quickfix-window-function| for an explanation
+--- of how to write the function and an example.
+--- title quickfix list title text. See |quickfix-title|
+--- Unsupported keys in {what} are ignored.
+--- If the "nr" item is not present, then the current quickfix list
+--- is modified. When creating a new quickfix list, "nr" can be
+--- set to a value one greater than the quickfix stack size.
+--- When modifying a quickfix list, to guarantee that the correct
+--- list is modified, "id" should be used instead of "nr" to
+--- specify the list.
+---
+--- Examples (See also |setqflist-examples|): >vim
+--- call setqflist([], 'r', {'title': 'My search'})
+--- call setqflist([], 'r', {'nr': 2, 'title': 'Errors'})
+--- call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]})
+--- <
+--- Returns zero for success, -1 for failure.
+---
+--- This function can be used to create a quickfix list
+--- independent of the 'errorformat' setting. Use a command like
+--- `:cc 1` to jump to the first position.
+---
+--- @param list any
+--- @param action? any
+--- @param what? any
+--- @return any
+function vim.fn.setqflist(list, action, what) end
+
+--- Set the register {regname} to {value}.
+--- If {regname} is "" or "\@", the unnamed register '"' is used.
+--- The {regname} argument is a string.
+---
+--- {value} may be any value returned by |getreg()| or
+--- |getreginfo()|, including a |List| or |Dict|.
+--- If {options} contains "a" or {regname} is upper case,
+--- then the value is appended.
+---
+--- {options} can also contain a register type specification:
+--- "c" or "v" |charwise| mode
+--- "l" or "V" |linewise| mode
+--- "b" or "<CTRL-V>" |blockwise-visual| mode
+--- If a number immediately follows "b" or "<CTRL-V>" then this is
+--- used as the width of the selection - if it is not specified
+--- then the width of the block is set to the number of characters
+--- in the longest line (counting a <Tab> as 1 character).
+--- If {options} contains "u" or '"', then the unnamed register is
+--- set to point to register {regname}.
+---
+--- If {options} contains no register settings, then the default
+--- is to use character mode unless {value} ends in a <NL> for
+--- string {value} and linewise mode for list {value}. Blockwise
+--- mode is never selected automatically.
+--- Returns zero for success, non-zero for failure.
+---
+--- *E883*
+--- Note: you may not use |List| containing more than one item to
+--- set search and expression registers. Lists containing no
+--- items act like empty strings.
+---
+--- Examples: >vim
+--- call setreg(v:register, \@*)
+--- call setreg('*', \@%, 'ac')
+--- call setreg('a', "1\n2\n3", 'b5')
+--- call setreg('"', { 'points_to': 'a'})
+---
+--- <This example shows using the functions to save and restore a
+--- register: >vim
+--- let var_a = getreginfo()
+--- call setreg('a', var_a)
+--- <or: >vim
+--- let var_a = getreg('a', 1, 1)
+--- let var_amode = getregtype('a')
+--- " ....
+--- call setreg('a', var_a, var_amode)
+--- <Note: you may not reliably restore register value
+--- without using the third argument to |getreg()| as without it
+--- newlines are represented as newlines AND Nul bytes are
+--- represented as newlines as well, see |NL-used-for-Nul|.
+---
+--- You can also change the type of a register by appending
+--- nothing: >vim
+--- call setreg('a', '', 'al')
+---
+--- @param regname string
+--- @param value any
+--- @param options? table
+--- @return any
+function vim.fn.setreg(regname, value, options) end
+
+--- Set tab-local variable {varname} to {val} in tab page {tabnr}.
+--- |t:var|
+--- The {varname} argument is a string.
+--- Note that the variable name without "t:" must be used.
+--- Tabs are numbered starting with one.
+--- This function is not available in the |sandbox|.
+---
+--- @param tabnr integer
+--- @param varname string
+--- @param val any
+--- @return any
+function vim.fn.settabvar(tabnr, varname, val) end
+
+--- Set option or local variable {varname} in window {winnr} to
+--- {val}.
+--- Tabs are numbered starting with one. For the current tabpage
+--- use |setwinvar()|.
+--- {winnr} can be the window number or the |window-ID|.
+--- When {winnr} is zero the current window is used.
+--- This also works for a global or local buffer option, but it
+--- doesn't work for a global or local buffer variable.
+--- For a local buffer option the global value is unchanged.
+--- Note that the variable name without "w:" must be used.
+--- Examples: >vim
+--- call settabwinvar(1, 1, "&list", 0)
+--- call settabwinvar(3, 2, "myvar", "foobar")
+--- <This function is not available in the |sandbox|.
+---
+--- @param tabnr integer
+--- @param winnr integer
+--- @param varname string
+--- @param val any
+--- @return any
+function vim.fn.settabwinvar(tabnr, winnr, varname, val) end
+
+--- Modify the tag stack of the window {nr} using {dict}.
+--- {nr} can be the window number or the |window-ID|.
+---
+--- For a list of supported items in {dict}, refer to
+--- |gettagstack()|. "curidx" takes effect before changing the tag
+--- stack.
+--- *E962*
+--- How the tag stack is modified depends on the {action}
+--- argument:
+--- - If {action} is not present or is set to 'r', then the tag
+--- stack is replaced.
+--- - If {action} is set to 'a', then new entries from {dict} are
+--- pushed (added) onto the tag stack.
+--- - If {action} is set to 't', then all the entries from the
+--- current entry in the tag stack or "curidx" in {dict} are
+--- removed and then new entries are pushed to the stack.
+---
+--- The current index is set to one after the length of the tag
+--- stack after the modification.
+---
+--- Returns zero for success, -1 for failure.
+---
+--- Examples (for more examples see |tagstack-examples|):
+--- Empty the tag stack of window 3: >vim
+--- call settagstack(3, {'items' : []})
+---
+--- < Save and restore the tag stack: >vim
+--- let stack = gettagstack(1003)
+--- " do something else
+--- call settagstack(1003, stack)
+--- unlet stack
+--- <
+---
+--- @param nr integer
+--- @param dict any
+--- @param action? any
+--- @return any
+function vim.fn.settagstack(nr, dict, action) end
+
+--- Like |settabwinvar()| for the current tab page.
+--- Examples: >vim
+--- call setwinvar(1, "&list", 0)
+--- call setwinvar(2, "myvar", "foobar")
+---
+--- @param nr integer
+--- @param varname string
+--- @param val any
+--- @return any
+function vim.fn.setwinvar(nr, varname, val) end
+
+--- Returns a String with 64 hex characters, which is the SHA256
+--- checksum of {string}.
+---
+--- @param string string
+--- @return any
+function vim.fn.sha256(string) end
+
+--- Escape {string} for use as a shell command argument.
+---
+--- On Windows when 'shellslash' is not set, encloses {string} in
+--- double-quotes and doubles all double-quotes within {string}.
+--- Otherwise encloses {string} in single-quotes and replaces all
+--- "'" with "'\''".
+---
+--- If {special} is a |non-zero-arg|:
+--- - Special items such as "!", "%", "#" and "<cword>" will be
+--- preceded by a backslash. The backslash will be removed again
+--- by the |:!| command.
+--- - The <NL> character is escaped.
+---
+--- If 'shell' contains "csh" in the tail:
+--- - The "!" character will be escaped. This is because csh and
+--- tcsh use "!" for history replacement even in single-quotes.
+--- - The <NL> character is escaped (twice if {special} is
+--- a |non-zero-arg|).
+---
+--- If 'shell' contains "fish" in the tail, the "\" character will
+--- be escaped because in fish it is used as an escape character
+--- inside single quotes.
+---
+--- Example of use with a |:!| command: >vim
+--- exe '!dir ' .. shellescape(expand('<cfile>'), 1)
+--- <This results in a directory listing for the file under the
+--- cursor. Example of use with |system()|: >vim
+--- call system("chmod +w -- " .. shellescape(expand("%")))
+--- <See also |::S|.
+---
+--- @param string string
+--- @param special? any
+--- @return any
+function vim.fn.shellescape(string, special) end
+
+--- Returns the effective value of 'shiftwidth'. This is the
+--- 'shiftwidth' value unless it is zero, in which case it is the
+--- 'tabstop' value. To be backwards compatible in indent
+--- plugins, use this: >vim
+--- if exists('*shiftwidth')
+--- func s:sw()
+--- return shiftwidth()
+--- endfunc
+--- else
+--- func s:sw()
+--- return &sw
+--- endfunc
+--- endif
+--- <And then use s:sw() instead of &sw.
+---
+--- When there is one argument {col} this is used as column number
+--- for which to return the 'shiftwidth' value. This matters for the
+--- 'vartabstop' feature. If no {col} argument is given, column 1
+--- will be assumed.
+---
+--- @param col? integer
+--- @return integer
+function vim.fn.shiftwidth(col) end
+
+--- @param name string
+--- @param dict? vim.fn.sign_define.dict
+--- @return 0|-1
+function vim.fn.sign_define(name, dict) end
+
+--- Define a new sign named {name} or modify the attributes of an
+--- existing sign. This is similar to the |:sign-define| command.
+---
+--- Prefix {name} with a unique text to avoid name collisions.
+--- There is no {group} like with placing signs.
+---
+--- The {name} can be a String or a Number. The optional {dict}
+--- argument specifies the sign attributes. The following values
+--- are supported:
+--- icon full path to the bitmap file for the sign.
+--- linehl highlight group used for the whole line the
+--- sign is placed in.
+--- numhl highlight group used for the line number where
+--- the sign is placed.
+--- text text that is displayed when there is no icon
+--- or the GUI is not being used.
+--- texthl highlight group used for the text item
+--- culhl highlight group used for the text item when
+--- the cursor is on the same line as the sign and
+--- 'cursorline' is enabled.
+---
+--- If the sign named {name} already exists, then the attributes
+--- of the sign are updated.
+---
+--- The one argument {list} can be used to define a list of signs.
+--- Each list item is a dictionary with the above items in {dict}
+--- and a "name" item for the sign name.
+---
+--- Returns 0 on success and -1 on failure. When the one argument
+--- {list} is used, then returns a List of values one for each
+--- defined sign.
+---
+--- Examples: >vim
+--- call sign_define("mySign", {
+--- \ "text" : "=>",
+--- \ "texthl" : "Error",
+--- \ "linehl" : "Search"})
+--- call sign_define([
+--- \ {'name' : 'sign1',
+--- \ 'text' : '=>'},
+--- \ {'name' : 'sign2',
+--- \ 'text' : '!!'}
+--- \ ])
+--- <
+---
+--- @param list vim.fn.sign_define.dict[]
+--- @return (0|-1)[]
+function vim.fn.sign_define(list) end
+
+--- Get a list of defined signs and their attributes.
+--- This is similar to the |:sign-list| command.
+---
+--- If the {name} is not supplied, then a list of all the defined
+--- signs is returned. Otherwise the attribute of the specified
+--- sign is returned.
+---
+--- Each list item in the returned value is a dictionary with the
+--- following entries:
+--- icon full path to the bitmap file of the sign
+--- linehl highlight group used for the whole line the
+--- sign is placed in; not present if not set.
+--- name name of the sign
+--- numhl highlight group used for the line number where
+--- the sign is placed; not present if not set.
+--- text text that is displayed when there is no icon
+--- or the GUI is not being used.
+--- texthl highlight group used for the text item; not
+--- present if not set.
+--- culhl highlight group used for the text item when
+--- the cursor is on the same line as the sign and
+--- 'cursorline' is enabled; not present if not
+--- set.
+---
+--- Returns an empty List if there are no signs and when {name} is
+--- not found.
+---
+--- Examples: >vim
+--- " Get a list of all the defined signs
+--- echo sign_getdefined()
+---
+--- " Get the attribute of the sign named mySign
+--- echo sign_getdefined("mySign")
+--- <
+---
+--- @param name? string
+--- @return vim.fn.sign_getdefined.ret.item[]
+function vim.fn.sign_getdefined(name) end
+
+--- Return a list of signs placed in a buffer or all the buffers.
+--- This is similar to the |:sign-place-list| command.
+---
+--- If the optional buffer name {buf} is specified, then only the
+--- list of signs placed in that buffer is returned. For the use
+--- of {buf}, see |bufname()|. The optional {dict} can contain
+--- the following entries:
+--- group select only signs in this group
+--- id select sign with this identifier
+--- lnum select signs placed in this line. For the use
+--- of {lnum}, see |line()|.
+--- If {group} is "*", then signs in all the groups including the
+--- global group are returned. If {group} is not supplied or is an
+--- empty string, then only signs in the global group are
+--- returned. If no arguments are supplied, then signs in the
+--- global group placed in all the buffers are returned.
+--- See |sign-group|.
+---
+--- Each list item in the returned value is a dictionary with the
+--- following entries:
+--- bufnr number of the buffer with the sign
+--- signs list of signs placed in {bufnr}. Each list
+--- item is a dictionary with the below listed
+--- entries
+---
+--- The dictionary for each sign contains the following entries:
+--- group sign group. Set to '' for the global group.
+--- id identifier of the sign
+--- lnum line number where the sign is placed
+--- name name of the defined sign
+--- priority sign priority
+---
+--- The returned signs in a buffer are ordered by their line
+--- number and priority.
+---
+--- Returns an empty list on failure or if there are no placed
+--- signs.
+---
+--- Examples: >vim
+--- " Get a List of signs placed in eval.c in the
+--- " global group
+--- echo sign_getplaced("eval.c")
+---
+--- " Get a List of signs in group 'g1' placed in eval.c
+--- echo sign_getplaced("eval.c", {'group' : 'g1'})
+---
+--- " Get a List of signs placed at line 10 in eval.c
+--- echo sign_getplaced("eval.c", {'lnum' : 10})
+---
+--- " Get sign with identifier 10 placed in a.py
+--- echo sign_getplaced("a.py", {'id' : 10})
+---
+--- " Get sign with id 20 in group 'g1' placed in a.py
+--- echo sign_getplaced("a.py", {'group' : 'g1',
+--- \ 'id' : 20})
+---
+--- " Get a List of all the placed signs
+--- echo sign_getplaced()
+--- <
+---
+--- @param buf? any
+--- @param dict? vim.fn.sign_getplaced.dict
+--- @return vim.fn.sign_getplaced.ret.item[]
+function vim.fn.sign_getplaced(buf, dict) end
+
+--- Open the buffer {buf} or jump to the window that contains
+--- {buf} and position the cursor at sign {id} in group {group}.
+--- This is similar to the |:sign-jump| command.
+---
+--- If {group} is an empty string, then the global group is used.
+--- For the use of {buf}, see |bufname()|.
+---
+--- Returns the line number of the sign. Returns -1 if the
+--- arguments are invalid.
+---
+--- Example: >vim
+--- " Jump to sign 10 in the current buffer
+--- call sign_jump(10, '', '')
+--- <
+---
+--- @param id integer
+--- @param group string
+--- @param buf integer|string
+--- @return integer
+function vim.fn.sign_jump(id, group, buf) end
+
+--- Place the sign defined as {name} at line {lnum} in file or
+--- buffer {buf} and assign {id} and {group} to sign. This is
+--- similar to the |:sign-place| command.
+---
+--- If the sign identifier {id} is zero, then a new identifier is
+--- allocated. Otherwise the specified number is used. {group} is
+--- the sign group name. To use the global sign group, use an
+--- empty string. {group} functions as a namespace for {id}, thus
+--- two groups can use the same IDs. Refer to |sign-identifier|
+--- and |sign-group| for more information.
+---
+--- {name} refers to a defined sign.
+--- {buf} refers to a buffer name or number. For the accepted
+--- values, see |bufname()|.
+---
+--- The optional {dict} argument supports the following entries:
+--- lnum line number in the file or buffer
+--- {buf} where the sign is to be placed.
+--- For the accepted values, see |line()|.
+--- priority priority of the sign. See
+--- |sign-priority| for more information.
+---
+--- If the optional {dict} is not specified, then it modifies the
+--- placed sign {id} in group {group} to use the defined sign
+--- {name}.
+---
+--- Returns the sign identifier on success and -1 on failure.
+---
+--- Examples: >vim
+--- " Place a sign named sign1 with id 5 at line 20 in
+--- " buffer json.c
+--- call sign_place(5, '', 'sign1', 'json.c',
+--- \ {'lnum' : 20})
+---
+--- " Updates sign 5 in buffer json.c to use sign2
+--- call sign_place(5, '', 'sign2', 'json.c')
+---
+--- " Place a sign named sign3 at line 30 in
+--- " buffer json.c with a new identifier
+--- let id = sign_place(0, '', 'sign3', 'json.c',
+--- \ {'lnum' : 30})
+---
+--- " Place a sign named sign4 with id 10 in group 'g3'
+--- " at line 40 in buffer json.c with priority 90
+--- call sign_place(10, 'g3', 'sign4', 'json.c',
+--- \ {'lnum' : 40, 'priority' : 90})
+--- <
+---
+--- @param id any
+--- @param group any
+--- @param name string
+--- @param buf any
+--- @param dict? vim.fn.sign_place.dict
+--- @return integer
+function vim.fn.sign_place(id, group, name, buf, dict) end
+
+--- Place one or more signs. This is similar to the
+--- |sign_place()| function. The {list} argument specifies the
+--- List of signs to place. Each list item is a dict with the
+--- following sign attributes:
+--- buffer Buffer name or number. For the accepted
+--- values, see |bufname()|.
+--- group Sign group. {group} functions as a namespace
+--- for {id}, thus two groups can use the same
+--- IDs. If not specified or set to an empty
+--- string, then the global group is used. See
+--- |sign-group| for more information.
+--- id Sign identifier. If not specified or zero,
+--- then a new unique identifier is allocated.
+--- Otherwise the specified number is used. See
+--- |sign-identifier| for more information.
+--- lnum Line number in the buffer where the sign is to
+--- be placed. For the accepted values, see
+--- |line()|.
+--- name Name of the sign to place. See |sign_define()|
+--- for more information.
+--- priority Priority of the sign. When multiple signs are
+--- placed on a line, the sign with the highest
+--- priority is used. If not specified, the
+--- default value of 10 is used. See
+--- |sign-priority| for more information.
+---
+--- If {id} refers to an existing sign, then the existing sign is
+--- modified to use the specified {name} and/or {priority}.
+---
+--- Returns a List of sign identifiers. If failed to place a
+--- sign, the corresponding list item is set to -1.
+---
+--- Examples: >vim
+--- " Place sign s1 with id 5 at line 20 and id 10 at line
+--- " 30 in buffer a.c
+--- let [n1, n2] = sign_placelist([
+--- \ {'id' : 5,
+--- \ 'name' : 's1',
+--- \ 'buffer' : 'a.c',
+--- \ 'lnum' : 20},
+--- \ {'id' : 10,
+--- \ 'name' : 's1',
+--- \ 'buffer' : 'a.c',
+--- \ 'lnum' : 30}
+--- \ ])
+---
+--- " Place sign s1 in buffer a.c at line 40 and 50
+--- " with auto-generated identifiers
+--- let [n1, n2] = sign_placelist([
+--- \ {'name' : 's1',
+--- \ 'buffer' : 'a.c',
+--- \ 'lnum' : 40},
+--- \ {'name' : 's1',
+--- \ 'buffer' : 'a.c',
+--- \ 'lnum' : 50}
+--- \ ])
+--- <
+---
+--- @param list vim.fn.sign_placelist.list.item[]
+--- @return integer[]
+function vim.fn.sign_placelist(list) end
+
+--- @param name? string
+--- @return 0|-1
+function vim.fn.sign_undefine(name) end
+
+--- Deletes a previously defined sign {name}. This is similar to
+--- the |:sign-undefine| command. If {name} is not supplied, then
+--- deletes all the defined signs.
+---
+--- The one argument {list} can be used to undefine a list of
+--- signs. Each list item is the name of a sign.
+---
+--- Returns 0 on success and -1 on failure. For the one argument
+--- {list} call, returns a list of values one for each undefined
+--- sign.
+---
+--- Examples: >vim
+--- " Delete a sign named mySign
+--- call sign_undefine("mySign")
+---
+--- " Delete signs 'sign1' and 'sign2'
+--- call sign_undefine(["sign1", "sign2"])
+---
+--- " Delete all the signs
+--- call sign_undefine()
+--- <
+---
+--- @param list? string[]
+--- @return integer[]
+function vim.fn.sign_undefine(list) end
+
+--- Remove a previously placed sign in one or more buffers. This
+--- is similar to the |:sign-unplace| command.
+---
+--- {group} is the sign group name. To use the global sign group,
+--- use an empty string. If {group} is set to "*", then all the
+--- groups including the global group are used.
+--- The signs in {group} are selected based on the entries in
+--- {dict}. The following optional entries in {dict} are
+--- supported:
+--- buffer buffer name or number. See |bufname()|.
+--- id sign identifier
+--- If {dict} is not supplied, then all the signs in {group} are
+--- removed.
+---
+--- Returns 0 on success and -1 on failure.
+---
+--- Examples: >vim
+--- " Remove sign 10 from buffer a.vim
+--- call sign_unplace('', {'buffer' : "a.vim", 'id' : 10})
+---
+--- " Remove sign 20 in group 'g1' from buffer 3
+--- call sign_unplace('g1', {'buffer' : 3, 'id' : 20})
+---
+--- " Remove all the signs in group 'g2' from buffer 10
+--- call sign_unplace('g2', {'buffer' : 10})
+---
+--- " Remove sign 30 in group 'g3' from all the buffers
+--- call sign_unplace('g3', {'id' : 30})
+---
+--- " Remove all the signs placed in buffer 5
+--- call sign_unplace('*', {'buffer' : 5})
+---
+--- " Remove the signs in group 'g4' from all the buffers
+--- call sign_unplace('g4')
+---
+--- " Remove sign 40 from all the buffers
+--- call sign_unplace('*', {'id' : 40})
+---
+--- " Remove all the placed signs from all the buffers
+--- call sign_unplace('*')
+---
+--- @param group string
+--- @param dict? vim.fn.sign_unplace.dict
+--- @return 0|-1
+function vim.fn.sign_unplace(group, dict) end
+
+--- Remove previously placed signs from one or more buffers. This
+--- is similar to the |sign_unplace()| function.
+---
+--- The {list} argument specifies the List of signs to remove.
+--- Each list item is a dict with the following sign attributes:
+--- buffer buffer name or number. For the accepted
+--- values, see |bufname()|. If not specified,
+--- then the specified sign is removed from all
+--- the buffers.
+--- group sign group name. If not specified or set to an
+--- empty string, then the global sign group is
+--- used. If set to "*", then all the groups
+--- including the global group are used.
+--- id sign identifier. If not specified, then all
+--- the signs in the specified group are removed.
+---
+--- Returns a List where an entry is set to 0 if the corresponding
+--- sign was successfully removed or -1 on failure.
+---
+--- Example: >vim
+--- " Remove sign with id 10 from buffer a.vim and sign
+--- " with id 20 from buffer b.vim
+--- call sign_unplacelist([
+--- \ {'id' : 10, 'buffer' : "a.vim"},
+--- \ {'id' : 20, 'buffer' : 'b.vim'},
+--- \ ])
+--- <
+---
+--- @param list vim.fn.sign_unplacelist.list.item
+--- @return (0|-1)[]
+function vim.fn.sign_unplacelist(list) end
+
+--- Simplify the file name as much as possible without changing
+--- the meaning. Shortcuts (on MS-Windows) or symbolic links (on
+--- Unix) are not resolved. If the first path component in
+--- {filename} designates the current directory, this will be
+--- valid for the result as well. A trailing path separator is
+--- not removed either. On Unix "//path" is unchanged, but
+--- "///path" is simplified to "/path" (this follows the Posix
+--- standard).
+--- Example: >vim
+--- simplify("./dir/.././/file/") == "./file/"
+--- <Note: The combination "dir/.." is only removed if "dir" is
+--- a searchable directory or does not exist. On Unix, it is also
+--- removed when "dir" is a symbolic link within the same
+--- directory. In order to resolve all the involved symbolic
+--- links before simplifying the path name, use |resolve()|.
+---
+--- @param filename any
+--- @return any
+function vim.fn.simplify(filename) end
+
+--- Return the sine of {expr}, measured in radians, as a |Float|.
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo sin(100)
+--- < -0.506366 >vim
+--- echo sin(-4.01)
+--- < 0.763301
+---
+--- @param expr any
+--- @return any
+function vim.fn.sin(expr) end
+
+--- Return the hyperbolic sine of {expr} as a |Float| in the range
+--- [-inf, inf].
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo sinh(0.5)
+--- < 0.521095 >vim
+--- echo sinh(-0.9)
+--- < -1.026517
+---
+--- @param expr any
+--- @return any
+function vim.fn.sinh(expr) end
+
+--- Similar to using a |slice| "expr[start : end]", but "end" is
+--- used exclusive. And for a string the indexes are used as
+--- character indexes instead of byte indexes.
+--- Also, composing characters are not counted.
+--- When {end} is omitted the slice continues to the last item.
+--- When {end} is -1 the last item is omitted.
+--- Returns an empty value if {start} or {end} are invalid.
+---
+--- @param expr any
+--- @param start any
+--- @param end_? any
+--- @return any
+function vim.fn.slice(expr, start, end_) end
+
+--- Connect a socket to an address. If {mode} is "pipe" then
+--- {address} should be the path of a local domain socket (on
+--- unix) or named pipe (on Windows). If {mode} is "tcp" then
+--- {address} should be of the form "host:port" where the host
+--- should be an ip address or host name, and port the port
+--- number.
+---
+--- For "pipe" mode, see |luv-pipe-handle|. For "tcp" mode, see
+--- |luv-tcp-handle|.
+---
+--- Returns a |channel| ID. Close the socket with |chanclose()|.
+--- Use |chansend()| to send data over a bytes socket, and
+--- |rpcrequest()| and |rpcnotify()| to communicate with a RPC
+--- socket.
+---
+--- {opts} is an optional dictionary with these keys:
+--- |on_data| : callback invoked when data was read from socket
+--- data_buffered : read socket data in |channel-buffered| mode.
+--- rpc : If set, |msgpack-rpc| will be used to communicate
+--- over the socket.
+--- Returns:
+--- - The channel ID on success (greater than zero)
+--- - 0 on invalid arguments or connection failure.
+---
+--- @param mode string
+--- @param address any
+--- @param opts? table
+--- @return any
+function vim.fn.sockconnect(mode, address, opts) end
+
+--- Sort the items in {list} in-place. Returns {list}.
+---
+--- If you want a list to remain unmodified make a copy first: >vim
+--- let sortedlist = sort(copy(mylist))
+---
+--- <When {how} is omitted or is a string, then sort() uses the
+--- string representation of each item to sort on. Numbers sort
+--- after Strings, |Lists| after Numbers. For sorting text in the
+--- current buffer use |:sort|.
+---
+--- When {how} is given and it is 'i' then case is ignored.
+--- For backwards compatibility, the value one can be used to
+--- ignore case. Zero means to not ignore case.
+---
+--- When {how} is given and it is 'l' then the current collation
+--- locale is used for ordering. Implementation details: strcoll()
+--- is used to compare strings. See |:language| check or set the
+--- collation locale. |v:collate| can also be used to check the
+--- current locale. Sorting using the locale typically ignores
+--- case. Example: >vim
+--- " ö is sorted similarly to o with English locale.
+--- language collate en_US.UTF8
+--- echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+--- < ['n', 'o', 'O', 'ö', 'p', 'z'] ~
+--- >vim
+--- " ö is sorted after z with Swedish locale.
+--- language collate sv_SE.UTF8
+--- echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+--- < ['n', 'o', 'O', 'p', 'z', 'ö'] ~
+--- This does not work properly on Mac.
+---
+--- When {how} is given and it is 'n' then all items will be
+--- sorted numerical (Implementation detail: this uses the
+--- strtod() function to parse numbers, Strings, Lists, Dicts and
+--- Funcrefs will be considered as being 0).
+---
+--- When {how} is given and it is 'N' then all items will be
+--- sorted numerical. This is like 'n' but a string containing
+--- digits will be used as the number they represent.
+---
+--- When {how} is given and it is 'f' then all items will be
+--- sorted numerical. All values must be a Number or a Float.
+---
+--- When {how} is a |Funcref| or a function name, this function
+--- is called to compare items. The function is invoked with two
+--- items as argument and must return zero if they are equal, 1 or
+--- bigger if the first one sorts after the second one, -1 or
+--- smaller if the first one sorts before the second one.
+---
+--- {dict} is for functions with the "dict" attribute. It will be
+--- used to set the local variable "self". |Dictionary-function|
+---
+--- The sort is stable, items which compare equal (as number or as
+--- string) will keep their relative position. E.g., when sorting
+--- on numbers, text strings will sort next to each other, in the
+--- same order as they were originally.
+---
+---
+--- Example: >vim
+--- func MyCompare(i1, i2)
+--- return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
+--- endfunc
+--- eval mylist->sort("MyCompare")
+--- <A shorter compare version for this specific simple case, which
+--- ignores overflow: >vim
+--- func MyCompare(i1, i2)
+--- return a:i1 - a:i2
+--- endfunc
+--- <For a simple expression you can use a lambda: >vim
+--- eval mylist->sort({i1, i2 -> i1 - i2})
+--- <
+---
+--- @param list any
+--- @param how? any
+--- @param dict? any
+--- @return any
+function vim.fn.sort(list, how, dict) end
+
+--- Return the sound-folded equivalent of {word}. Uses the first
+--- language in 'spelllang' for the current window that supports
+--- soundfolding. 'spell' must be set. When no sound folding is
+--- possible the {word} is returned unmodified.
+--- This can be used for making spelling suggestions. Note that
+--- the method can be quite slow.
+---
+--- @param word any
+--- @return any
+function vim.fn.soundfold(word) end
+
+--- Without argument: The result is the badly spelled word under
+--- or after the cursor. The cursor is moved to the start of the
+--- bad word. When no bad word is found in the cursor line the
+--- result is an empty string and the cursor doesn't move.
+---
+--- With argument: The result is the first word in {sentence} that
+--- is badly spelled. If there are no spelling mistakes the
+--- result is an empty string.
+---
+--- The return value is a list with two items:
+--- - The badly spelled word or an empty string.
+--- - The type of the spelling error:
+--- "bad" spelling mistake
+--- "rare" rare word
+--- "local" word only valid in another region
+--- "caps" word should start with Capital
+--- Example: >vim
+--- echo spellbadword("the quik brown fox")
+--- < ['quik', 'bad'] ~
+---
+--- The spelling information for the current window and the value
+--- of 'spelllang' are used.
+---
+--- @param sentence? any
+--- @return any
+function vim.fn.spellbadword(sentence) end
+
+--- Return a |List| with spelling suggestions to replace {word}.
+--- When {max} is given up to this number of suggestions are
+--- returned. Otherwise up to 25 suggestions are returned.
+---
+--- When the {capital} argument is given and it's non-zero only
+--- suggestions with a leading capital will be given. Use this
+--- after a match with 'spellcapcheck'.
+---
+--- {word} can be a badly spelled word followed by other text.
+--- This allows for joining two words that were split. The
+--- suggestions also include the following text, thus you can
+--- replace a line.
+---
+--- {word} may also be a good word. Similar words will then be
+--- returned. {word} itself is not included in the suggestions,
+--- although it may appear capitalized.
+---
+--- The spelling information for the current window is used. The
+--- values of 'spelllang' and 'spellsuggest' are used.
+---
+--- @param word any
+--- @param max? any
+--- @param capital? any
+--- @return any
+function vim.fn.spellsuggest(word, max, capital) end
+
+--- Make a |List| out of {string}. When {pattern} is omitted or
+--- empty each white-separated sequence of characters becomes an
+--- item.
+--- Otherwise the string is split where {pattern} matches,
+--- removing the matched characters. 'ignorecase' is not used
+--- here, add \c to ignore case. |/\c|
+--- When the first or last item is empty it is omitted, unless the
+--- {keepempty} argument is given and it's non-zero.
+--- Other empty items are kept when {pattern} matches at least one
+--- character or when {keepempty} is non-zero.
+--- Example: >vim
+--- let words = split(getline('.'), '\W\+')
+--- <To split a string in individual characters: >vim
+--- for c in split(mystring, '\zs') | endfor
+--- <If you want to keep the separator you can also use '\zs' at
+--- the end of the pattern: >vim
+--- echo split('abc:def:ghi', ':\zs')
+--- < >
+--- ['abc:', 'def:', 'ghi']
+--- <
+--- Splitting a table where the first element can be empty: >vim
+--- let items = split(line, ':', 1)
+--- <The opposite function is |join()|.
+---
+--- @param string string
+--- @param pattern? any
+--- @param keepempty? any
+--- @return any
+function vim.fn.split(string, pattern, keepempty) end
+
+--- Return the non-negative square root of Float {expr} as a
+--- |Float|.
+--- {expr} must evaluate to a |Float| or a |Number|. When {expr}
+--- is negative the result is NaN (Not a Number). Returns 0.0 if
+--- {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo sqrt(100)
+--- < 10.0 >vim
+--- echo sqrt(-4.01)
+--- < str2float("nan")
+--- NaN may be different, it depends on system libraries.
+---
+--- @param expr any
+--- @return any
+function vim.fn.sqrt(expr) end
+
+--- 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: >vim
+--- let seed = srand()
+--- let seed = srand(userinput)
+--- echo rand(seed)
+--- <
+---
+--- @param expr? any
+--- @return any
+function vim.fn.srand(expr) end
+
+--- Return a string which contains characters indicating the
+--- current state. Mostly useful in callbacks that want to do
+--- work that may not always be safe. Roughly this works like:
+--- - callback uses state() to check if work is safe to do.
+--- Yes: then do it right away.
+--- No: add to work queue and add a |SafeState| autocommand.
+--- - When SafeState is triggered and executes your autocommand,
+--- check with `state()` if the work can be done now, and if yes
+--- remove it from the queue and execute.
+--- Remove the autocommand if the queue is now empty.
+--- Also see |mode()|.
+---
+--- When {what} is given only characters in this string will be
+--- added. E.g, this checks if the screen has scrolled: >vim
+--- if state('s') == ''
+--- " screen has not scrolled
+--- <
+--- These characters indicate the state, generally indicating that
+--- something is busy:
+--- m halfway a mapping, :normal command, feedkeys() or
+--- stuffed command
+--- o operator pending, e.g. after |d|
+--- a Insert mode autocomplete active
+--- x executing an autocommand
+--- S not triggering SafeState, e.g. after |f| or a count
+--- c callback invoked, including timer (repeats for
+--- recursiveness up to "ccc")
+--- s screen has scrolled for messages
+---
+--- @param what? string
+--- @return any
+function vim.fn.state(what) end
+
+--- With |--headless| this opens stdin and stdout as a |channel|.
+--- May be called only once. See |channel-stdio|. stderr is not
+--- handled by this function, see |v:stderr|.
+---
+--- Close the stdio handles with |chanclose()|. Use |chansend()|
+--- to send data to stdout, and |rpcrequest()| and |rpcnotify()|
+--- to communicate over RPC.
+---
+--- {opts} is a dictionary with these keys:
+--- |on_stdin| : callback invoked when stdin is written to.
+--- on_print : callback invoked when Nvim needs to print a
+--- message, with the message (whose type is string)
+--- as sole argument.
+--- stdin_buffered : read stdin in |channel-buffered| mode.
+--- rpc : If set, |msgpack-rpc| will be used to communicate
+--- over stdio
+--- Returns:
+--- - |channel-id| on success (value is always 1)
+--- - 0 on invalid arguments
+---
+--- @param opts table
+--- @return any
+function vim.fn.stdioopen(opts) end
+
+--- Returns |standard-path| locations of various default files and
+--- directories.
+---
+--- {what} Type Description ~
+--- cache String Cache directory: arbitrary temporary
+--- storage for plugins, etc.
+--- config String User configuration directory. |init.vim|
+--- is stored here.
+--- config_dirs List Other configuration directories.
+--- data String User data directory.
+--- data_dirs List Other data directories.
+--- log String Logs directory (for use by plugins too).
+--- run String Run directory: temporary, local storage
+--- for sockets, named pipes, etc.
+--- state String Session state directory: storage for file
+--- drafts, swap, undo, |shada|.
+---
+--- Example: >vim
+--- echo stdpath("config")
+--- <
+---
+--- @param what 'cache'|'config'|'config_dirs'|'data'|'data_dirs'|'log'|'run'|'state'
+--- @return string|string[]
+function vim.fn.stdpath(what) end
+
+--- Convert String {string} to a Float. This mostly works the
+--- same as when using a floating point number in an expression,
+--- see |floating-point-format|. But it's a bit more permissive.
+--- E.g., "1e40" is accepted, while in an expression you need to
+--- write "1.0e40". The hexadecimal form "0x123" is also
+--- accepted, but not others, like binary or octal.
+--- When {quoted} is present and non-zero then embedded single
+--- quotes before the dot are ignored, thus "1'000.0" is a
+--- thousand.
+--- Text after the number is silently ignored.
+--- The decimal point is always '.', no matter what the locale is
+--- set to. A comma ends the number: "12,345.67" is converted to
+--- 12.0. You can strip out thousands separators with
+--- |substitute()|: >vim
+--- let f = str2float(substitute(text, ',', '', 'g'))
+--- <
+--- Returns 0.0 if the conversion fails.
+---
+--- @param string string
+--- @param quoted? any
+--- @return any
+function vim.fn.str2float(string, quoted) end
+
+--- Return a list containing the number values which represent
+--- each character in String {string}. Examples: >vim
+--- echo str2list(" ") " returns [32]
+--- echo str2list("ABC") " returns [65, 66, 67]
+--- <|list2str()| does the opposite.
+---
+--- UTF-8 encoding is always used, {utf8} option has no effect,
+--- and exists only for backwards-compatibility.
+--- With UTF-8 composing characters are handled properly: >vim
+--- echo str2list("á") " returns [97, 769]
+---
+--- @param string string
+--- @param utf8? any
+--- @return any
+function vim.fn.str2list(string, utf8) end
+
+--- Convert string {string} to a number.
+--- {base} is the conversion base, it can be 2, 8, 10 or 16.
+--- When {quoted} is present and non-zero then embedded single
+--- quotes are ignored, thus "1'000'000" is a million.
+---
+--- When {base} is omitted base 10 is used. This also means that
+--- a leading zero doesn't cause octal conversion to be used, as
+--- with the default String to Number conversion. Example: >vim
+--- let nr = str2nr('0123')
+--- <
+--- When {base} is 16 a leading "0x" or "0X" is ignored. With a
+--- different base the result will be zero. Similarly, when
+--- {base} is 8 a leading "0", "0o" or "0O" is ignored, and when
+--- {base} is 2 a leading "0b" or "0B" is ignored.
+--- Text after the number is silently ignored.
+---
+--- Returns 0 if {string} is empty or on error.
+---
+--- @param string string
+--- @param base? any
+--- @return any
+function vim.fn.str2nr(string, base) end
+
+--- The result is a Number, which is the number of characters
+--- in String {string}. Composing characters are ignored.
+--- |strchars()| can count the number of characters, counting
+--- composing characters separately.
+---
+--- Returns 0 if {string} is empty or on error.
+---
+--- Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+---
+--- @param string string
+--- @return any
+function vim.fn.strcharlen(string) end
+
+--- Like |strpart()| but using character index and length instead
+--- of byte index and length.
+--- When {skipcc} is omitted or zero, composing characters are
+--- counted separately.
+--- When {skipcc} set to 1, Composing characters are ignored,
+--- similar to |slice()|.
+--- When a character index is used where a character does not
+--- exist it is omitted and counted as one character. For
+--- example: >vim
+--- echo strcharpart('abc', -1, 2)
+--- <results in 'a'.
+---
+--- Returns an empty string on error.
+---
+--- @param src any
+--- @param start any
+--- @param len? any
+--- @param skipcc? any
+--- @return any
+function vim.fn.strcharpart(src, start, len, skipcc) end
+
+--- The result is a Number, which is the number of characters
+--- in String {string}.
+--- When {skipcc} is omitted or zero, composing characters are
+--- counted separately.
+--- When {skipcc} set to 1, Composing characters are ignored.
+--- |strcharlen()| always does this.
+---
+--- Returns zero on error.
+---
+--- Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+---
+--- {skipcc} is only available after 7.4.755. For backward
+--- compatibility, you can define a wrapper function: >vim
+--- if has("patch-7.4.755")
+--- function s:strchars(str, skipcc)
+--- return strchars(a:str, a:skipcc)
+--- endfunction
+--- else
+--- function s:strchars(str, skipcc)
+--- if a:skipcc
+--- return strlen(substitute(a:str, ".", "x", "g"))
+--- else
+--- return strchars(a:str)
+--- endif
+--- endfunction
+--- endif
+--- <
+---
+--- @param string string
+--- @param skipcc? any
+--- @return integer
+function vim.fn.strchars(string, skipcc) end
+
+--- The result is a Number, which is the number of display cells
+--- String {string} occupies on the screen when it starts at {col}
+--- (first column is zero). When {col} is omitted zero is used.
+--- Otherwise it is the screen column where to start. This
+--- matters for Tab characters.
+--- The option settings of the current window are used. This
+--- matters for anything that's displayed differently, such as
+--- 'tabstop' and 'display'.
+--- When {string} contains characters with East Asian Width Class
+--- Ambiguous, this function's return value depends on 'ambiwidth'.
+--- Returns zero on error.
+--- Also see |strlen()|, |strwidth()| and |strchars()|.
+---
+--- @param string string
+--- @param col? integer
+--- @return integer
+function vim.fn.strdisplaywidth(string, col) end
+
+--- The result is a String, which is a formatted date and time, as
+--- specified by the {format} string. The given {time} is used,
+--- or the current time if no time is given. The accepted
+--- {format} depends on your system, thus this is not portable!
+--- See the manual page of the C function strftime() for the
+--- format. The maximum length of the result is 80 characters.
+--- See also |localtime()|, |getftime()| and |strptime()|.
+--- The language can be changed with the |:language| command.
+--- Examples: >vim
+--- echo strftime("%c") " Sun Apr 27 11:49:23 1997
+--- echo strftime("%Y %b %d %X") " 1997 Apr 27 11:53:25
+--- echo strftime("%y%m%d %T") " 970427 11:53:55
+--- echo strftime("%H:%M") " 11:55
+--- echo strftime("%c", getftime("file.c"))
+--- " Show mod time of file.c.
+---
+--- @param format any
+--- @param time? any
+--- @return string
+function vim.fn.strftime(format, time) end
+
+--- Get a Number corresponding to the character at {index} in
+--- {str}. This uses a zero-based character index, not a byte
+--- index. Composing characters are considered separate
+--- characters here. Use |nr2char()| to convert the Number to a
+--- String.
+--- Returns -1 if {index} is invalid.
+--- Also see |strcharpart()| and |strchars()|.
+---
+--- @param str string
+--- @param index integer
+--- @return integer
+function vim.fn.strgetchar(str, index) end
+
+--- The result is a Number, which gives the byte index in
+--- {haystack} of the first occurrence of the String {needle}.
+--- If {start} is specified, the search starts at index {start}.
+--- This can be used to find a second match: >vim
+--- let colon1 = stridx(line, ":")
+--- let colon2 = stridx(line, ":", colon1 + 1)
+--- <The search is done case-sensitive.
+--- For pattern searches use |match()|.
+--- -1 is returned if the {needle} does not occur in {haystack}.
+--- See also |strridx()|.
+--- Examples: >vim
+--- echo stridx("An Example", "Example") " 3
+--- echo stridx("Starting point", "Start") " 0
+--- echo stridx("Starting point", "start") " -1
+--- < *strstr()* *strchr()*
+--- stridx() works similar to the C function strstr(). When used
+--- with a single character it works similar to strchr().
+---
+--- @param haystack string
+--- @param needle string
+--- @param start? integer
+--- @return integer
+function vim.fn.stridx(haystack, needle, start) end
+
+--- Return {expr} converted to a String. If {expr} is a Number,
+--- Float, String, Blob or a composition of them, then the result
+--- can be parsed back with |eval()|.
+--- {expr} type result ~
+--- String 'string'
+--- Number 123
+--- Float 123.123456 or 1.123456e8 or
+--- `str2float('inf')`
+--- Funcref `function('name')`
+--- Blob 0z00112233.44556677.8899
+--- List [item, item]
+--- Dictionary `{key: value, key: value}`
+--- Note that in String values the ' character is doubled.
+--- Also see |strtrans()|.
+--- Note 2: Output format is mostly compatible with YAML, except
+--- for infinite and NaN floating-point values representations
+--- which use |str2float()|. Strings are also dumped literally,
+--- only single quote is escaped, which does not allow using YAML
+--- for parsing back binary strings. |eval()| should always work for
+--- strings and floats though and this is the only official
+--- method, use |msgpackdump()| or |json_encode()| if you need to
+--- share data with other application.
+---
+--- @param expr any
+--- @return string
+function vim.fn.string(expr) end
+
+--- The result is a Number, which is the length of the String
+--- {string} in bytes.
+--- If the argument is a Number it is first converted to a String.
+--- For other types an error is given and zero is returned.
+--- If you want to count the number of multibyte characters use
+--- |strchars()|.
+--- Also see |len()|, |strdisplaywidth()| and |strwidth()|.
+---
+--- @param string string
+--- @return integer
+function vim.fn.strlen(string) end
+
+--- The result is a String, which is part of {src}, starting from
+--- byte {start}, with the byte length {len}.
+--- When {chars} is present and TRUE then {len} is the number of
+--- characters positions (composing characters are not counted
+--- separately, thus "1" means one base character and any
+--- following composing characters).
+--- To count {start} as characters instead of bytes use
+--- |strcharpart()|.
+---
+--- When bytes are selected which do not exist, this doesn't
+--- result in an error, the bytes are simply omitted.
+--- If {len} is missing, the copy continues from {start} till the
+--- end of the {src}. >vim
+--- echo strpart("abcdefg", 3, 2) " returns 'de'
+--- echo strpart("abcdefg", -2, 4) " returns 'ab'
+--- echo strpart("abcdefg", 5, 4) " returns 'fg'
+--- echo strpart("abcdefg", 3) " returns 'defg'
+---
+--- <Note: To get the first character, {start} must be 0. For
+--- example, to get the character under the cursor: >vim
+--- strpart(getline("."), col(".") - 1, 1, v:true)
+--- <
+--- Returns an empty string on error.
+---
+--- @param src string
+--- @param start integer
+--- @param len? integer
+--- @param chars? 0|1
+--- @return string
+function vim.fn.strpart(src, start, len, chars) end
+
+--- The result is a Number, which is a unix timestamp representing
+--- the date and time in {timestring}, which is expected to match
+--- the format specified in {format}.
+---
+--- The accepted {format} depends on your system, thus this is not
+--- portable! See the manual page of the C function strptime()
+--- for the format. Especially avoid "%c". The value of $TZ also
+--- matters.
+---
+--- If the {timestring} cannot be parsed with {format} zero is
+--- returned. If you do not know the format of {timestring} you
+--- can try different {format} values until you get a non-zero
+--- result.
+---
+--- See also |strftime()|.
+--- Examples: >vim
+--- echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23")
+--- < 862156163 >vim
+--- echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55"))
+--- < Sun Apr 27 11:53:55 1997 >vim
+--- echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600)
+--- < Sun Apr 27 12:53:55 1997
+---
+--- @param format string
+--- @param timestring string
+--- @return integer
+function vim.fn.strptime(format, timestring) end
+
+--- The result is a Number, which gives the byte index in
+--- {haystack} of the last occurrence of the String {needle}.
+--- When {start} is specified, matches beyond this index are
+--- ignored. This can be used to find a match before a previous
+--- match: >vim
+--- let lastcomma = strridx(line, ",")
+--- let comma2 = strridx(line, ",", lastcomma - 1)
+--- <The search is done case-sensitive.
+--- For pattern searches use |match()|.
+--- -1 is returned if the {needle} does not occur in {haystack}.
+--- If the {needle} is empty the length of {haystack} is returned.
+--- See also |stridx()|. Examples: >vim
+--- echo strridx("an angry armadillo", "an") 3
+--- < *strrchr()*
+--- When used with a single character it works similar to the C
+--- function strrchr().
+---
+--- @param haystack string
+--- @param needle string
+--- @param start? integer
+--- @return integer
+function vim.fn.strridx(haystack, needle, start) end
+
+--- The result is a String, which is {string} with all unprintable
+--- characters translated into printable characters |'isprint'|.
+--- Like they are shown in a window. Example: >vim
+--- echo strtrans(\@a)
+--- <This displays a newline in register a as "^\@" instead of
+--- starting a new line.
+---
+--- Returns an empty string on error.
+---
+--- @param string string
+--- @return string
+function vim.fn.strtrans(string) end
+
+--- The result is a Number, which is the number of UTF-16 code
+--- units in String {string} (after converting it to UTF-16).
+---
+--- When {countcc} is TRUE, composing characters are counted
+--- separately.
+--- When {countcc} is omitted or FALSE, composing characters are
+--- ignored.
+---
+--- Returns zero on error.
+---
+--- Also see |strlen()| and |strcharlen()|.
+--- Examples: >vim
+--- echo strutf16len('a') " returns 1
+--- echo strutf16len('©') " returns 1
+--- echo strutf16len('😊') " returns 2
+--- echo strutf16len('ą́') " returns 1
+--- echo strutf16len('ą́', v:true) " returns 3
+--- <
+---
+--- @param string string
+--- @param countcc? 0|1
+--- @return integer
+function vim.fn.strutf16len(string, countcc) end
+
+--- The result is a Number, which is the number of display cells
+--- String {string} occupies. A Tab character is counted as one
+--- cell, alternatively use |strdisplaywidth()|.
+--- When {string} contains characters with East Asian Width Class
+--- Ambiguous, this function's return value depends on 'ambiwidth'.
+--- Returns zero on error.
+--- Also see |strlen()|, |strdisplaywidth()| and |strchars()|.
+---
+--- @param string string
+--- @return integer
+function vim.fn.strwidth(string) end
+
+--- Only for an expression in a |:substitute| command or
+--- substitute() function.
+--- Returns the {nr}th submatch of the matched text. When {nr}
+--- is 0 the whole matched text is returned.
+--- Note that a NL in the string can stand for a line break of a
+--- multi-line match or a NUL character in the text.
+--- Also see |sub-replace-expression|.
+---
+--- If {list} is present and non-zero then submatch() returns
+--- a list of strings, similar to |getline()| with two arguments.
+--- NL characters in the text represent NUL characters in the
+--- text.
+--- Only returns more than one item for |:substitute|, inside
+--- |substitute()| this list will always contain one or zero
+--- items, since there are no real line breaks.
+---
+--- When substitute() is used recursively only the submatches in
+--- the current (deepest) call can be obtained.
+---
+--- Returns an empty string or list on error.
+---
+--- Examples: >vim
+--- s/\d\+/\=submatch(0) + 1/
+--- echo substitute(text, '\d\+', '\=submatch(0) + 1', '')
+--- <This finds the first number in the line and adds one to it.
+--- A line break is included as a newline character.
+---
+--- @param nr integer
+--- @param list? integer
+--- @return string|string[]
+function vim.fn.submatch(nr, list) end
+
+--- The result is a String, which is a copy of {string}, in which
+--- the first match of {pat} is replaced with {sub}.
+--- When {flags} is "g", all matches of {pat} in {string} are
+--- replaced. Otherwise {flags} should be "".
+---
+--- This works like the ":substitute" command (without any flags).
+--- But the matching with {pat} is always done like the 'magic'
+--- option is set and 'cpoptions' is empty (to make scripts
+--- portable). 'ignorecase' is still relevant, use |/\c| or |/\C|
+--- if you want to ignore or match case and ignore 'ignorecase'.
+--- 'smartcase' is not used. See |string-match| for how {pat} is
+--- used.
+---
+--- A "~" in {sub} is not replaced with the previous {sub}.
+--- Note that some codes in {sub} have a special meaning
+--- |sub-replace-special|. For example, to replace something with
+--- "\n" (two characters), use "\\\\n" or '\\n'.
+---
+--- When {pat} does not match in {string}, {string} is returned
+--- unmodified.
+---
+--- Example: >vim
+--- let &path = substitute(&path, ",\\=[^,]*$", "", "")
+--- <This removes the last component of the 'path' option. >vim
+--- echo substitute("testing", ".*", "\\U\\0", "")
+--- <results in "TESTING".
+---
+--- When {sub} starts with "\=", the remainder is interpreted as
+--- an expression. See |sub-replace-expression|. Example: >vim
+--- echo substitute(s, '%\(\x\x\)',
+--- \ '\=nr2char("0x" .. submatch(1))', 'g')
+---
+--- <When {sub} is a Funcref that function is called, with one
+--- optional argument. Example: >vim
+--- echo substitute(s, '%\(\x\x\)', SubNr, 'g')
+--- <The optional argument is a list which contains the whole
+--- matched string and up to nine submatches, like what
+--- |submatch()| returns. Example: >vim
+--- echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g')
+---
+--- <Returns an empty string on error.
+---
+--- @param string string
+--- @param pat string
+--- @param sub string
+--- @param flags string
+--- @return string
+function vim.fn.substitute(string, pat, sub, flags) end
+
+--- Returns a list of swap file names, like what "vim -r" shows.
+--- See the |-r| command argument. The 'directory' option is used
+--- for the directories to inspect. If you only want to get a
+--- list of swap files in the current directory then temporarily
+--- set 'directory' to a dot: >vim
+--- let save_dir = &directory
+--- let &directory = '.'
+--- let swapfiles = swapfilelist()
+--- let &directory = save_dir
+---
+--- @return string[]
+function vim.fn.swapfilelist() end
+
+--- The result is a dictionary, which holds information about the
+--- swapfile {fname}. The available fields are:
+--- version Vim version
+--- user user name
+--- host host name
+--- fname original file name
+--- pid PID of the Nvim process that created the swap
+--- file, or zero if not running.
+--- mtime last modification time in seconds
+--- inode Optional: INODE number of the file
+--- dirty 1 if file was modified, 0 if not
+--- In case of failure an "error" item is added with the reason:
+--- Cannot open file: file not found or in accessible
+--- Cannot read file: cannot read first block
+--- Not a swap file: does not contain correct block ID
+--- Magic number mismatch: Info in first block is invalid
+---
+--- @param fname string
+--- @return any
+function vim.fn.swapinfo(fname) end
+
+--- The result is the swap file path of the buffer {buf}.
+--- For the use of {buf}, see |bufname()| above.
+--- If buffer {buf} is the current buffer, the result is equal to
+--- |:swapname| (unless there is no swap file).
+--- If buffer {buf} has no swap file, returns an empty string.
+---
+--- @param buf integer|string
+--- @return string
+function vim.fn.swapname(buf) end
+
+--- The result is a Number, which is the syntax ID at the position
+--- {lnum} and {col} in the current window.
+--- The syntax ID can be used with |synIDattr()| and
+--- |synIDtrans()| to obtain syntax information about text.
+---
+--- {col} is 1 for the leftmost column, {lnum} is 1 for the first
+--- line. 'synmaxcol' applies, in a longer line zero is returned.
+--- Note that when the position is after the last character,
+--- that's where the cursor can be in Insert mode, synID() returns
+--- zero. {lnum} is used like with |getline()|.
+---
+--- When {trans} is |TRUE|, transparent items are reduced to the
+--- item that they reveal. This is useful when wanting to know
+--- the effective color. When {trans} is |FALSE|, the transparent
+--- item is returned. This is useful when wanting to know which
+--- syntax item is effective (e.g. inside parens).
+--- Warning: This function can be very slow. Best speed is
+--- obtained by going through the file in forward direction.
+---
+--- Returns zero on error.
+---
+--- Example (echoes the name of the syntax item under the cursor): >vim
+--- echo synIDattr(synID(line("."), col("."), 1), "name")
+--- <
+---
+--- @param lnum integer
+--- @param col integer
+--- @param trans 0|1
+--- @return integer
+function vim.fn.synID(lnum, col, trans) end
+
+--- The result is a String, which is the {what} attribute of
+--- syntax ID {synID}. This can be used to obtain information
+--- about a syntax item.
+--- {mode} can be "gui" or "cterm", to get the attributes
+--- for that mode. When {mode} is omitted, or an invalid value is
+--- used, the attributes for the currently active highlighting are
+--- used (GUI or cterm).
+--- Use synIDtrans() to follow linked highlight groups.
+--- {what} result
+--- "name" the name of the syntax item
+--- "fg" foreground color (GUI: color name used to set
+--- the color, cterm: color number as a string,
+--- term: empty string)
+--- "bg" background color (as with "fg")
+--- "font" font name (only available in the GUI)
+--- |highlight-font|
+--- "sp" special color (as with "fg") |guisp|
+--- "fg#" like "fg", but for the GUI and the GUI is
+--- running the name in "#RRGGBB" form
+--- "bg#" like "fg#" for "bg"
+--- "sp#" like "fg#" for "sp"
+--- "bold" "1" if bold
+--- "italic" "1" if italic
+--- "reverse" "1" if reverse
+--- "inverse" "1" if inverse (= reverse)
+--- "standout" "1" if standout
+--- "underline" "1" if underlined
+--- "undercurl" "1" if undercurled
+--- "underdouble" "1" if double underlined
+--- "underdotted" "1" if dotted underlined
+--- "underdashed" "1" if dashed underlined
+--- "strikethrough" "1" if struckthrough
+--- "altfont" "1" if alternative font
+--- "nocombine" "1" if nocombine
+---
+--- Returns an empty string on error.
+---
+--- Example (echoes the color of the syntax item under the
+--- cursor): >vim
+--- echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg")
+--- <
+--- Can also be used as a |method|: >vim
+--- echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
+--- <
+---
+--- @param synID integer
+--- @param what string
+--- @param mode? string
+--- @return string
+function vim.fn.synIDattr(synID, what, mode) end
+
+--- The result is a Number, which is the translated syntax ID of
+--- {synID}. This is the syntax group ID of what is being used to
+--- highlight the character. Highlight links given with
+--- ":highlight link" are followed.
+---
+--- Returns zero on error.
+---
+--- @param synID integer
+--- @return integer
+function vim.fn.synIDtrans(synID) end
+
+--- The result is a |List| with currently three items:
+--- 1. The first item in the list is 0 if the character at the
+--- position {lnum} and {col} is not part of a concealable
+--- region, 1 if it is. {lnum} is used like with |getline()|.
+--- 2. The second item in the list is a string. If the first item
+--- is 1, the second item contains the text which will be
+--- displayed in place of the concealed text, depending on the
+--- current setting of 'conceallevel' and 'listchars'.
+--- 3. The third and final item in the list is a number
+--- representing the specific syntax region matched in the
+--- line. When the character is not concealed the value is
+--- zero. This allows detection of the beginning of a new
+--- concealable region if there are two consecutive regions
+--- with the same replacement character. For an example, if
+--- the text is "123456" and both "23" and "45" are concealed
+--- and replaced by the character "X", then:
+--- call returns ~
+--- synconcealed(lnum, 1) [0, '', 0]
+--- synconcealed(lnum, 2) [1, 'X', 1]
+--- synconcealed(lnum, 3) [1, 'X', 1]
+--- synconcealed(lnum, 4) [1, 'X', 2]
+--- synconcealed(lnum, 5) [1, 'X', 2]
+--- synconcealed(lnum, 6) [0, '', 0]
+---
+--- @param lnum integer
+--- @param col integer
+--- @return {[1]: integer, [2]: string, [3]: integer}[]
+function vim.fn.synconcealed(lnum, col) end
+
+--- Return a |List|, which is the stack of syntax items at the
+--- position {lnum} and {col} in the current window. {lnum} is
+--- used like with |getline()|. Each item in the List is an ID
+--- like what |synID()| returns.
+--- The first item in the List is the outer region, following are
+--- items contained in that one. The last one is what |synID()|
+--- returns, unless not the whole item is highlighted or it is a
+--- transparent item.
+--- This function is useful for debugging a syntax file.
+--- Example that shows the syntax stack under the cursor: >vim
+--- for id in synstack(line("."), col("."))
+--- echo synIDattr(id, "name")
+--- endfor
+--- <When the position specified with {lnum} and {col} is invalid
+--- an empty list is returned. The position just after the last
+--- character in a line and the first column in an empty line are
+--- valid positions.
+---
+--- @param lnum integer
+--- @param col integer
+--- @return integer[]
+function vim.fn.synstack(lnum, col) end
+
+--- Note: Prefer |vim.system()| in Lua.
+---
+--- Gets the output of {cmd} as a |string| (|systemlist()| returns
+--- a |List|) and sets |v:shell_error| to the error code.
+--- {cmd} is treated as in |jobstart()|:
+--- If {cmd} is a List it runs directly (no 'shell').
+--- If {cmd} is a String it runs in the 'shell', like this: >vim
+--- call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+---
+--- <Not to be used for interactive commands.
+---
+--- Result is a String, filtered to avoid platform-specific quirks:
+--- - <CR><NL> is replaced with <NL>
+--- - NUL characters are replaced with SOH (0x01)
+---
+--- Example: >vim
+--- echo system(['ls', expand('%:h')])
+---
+--- <If {input} is a string it is written to a pipe and passed as
+--- stdin to the command. The string is written as-is, line
+--- separators are not changed.
+--- If {input} is a |List| it is written to the pipe as
+--- |writefile()| does with {binary} set to "b" (i.e. with
+--- a newline between each list item, and newlines inside list
+--- items converted to NULs).
+--- When {input} is given and is a valid buffer id, the content of
+--- the buffer is written to the file line by line, each line
+--- terminated by NL (and NUL where the text has NL).
+--- *E5677*
+--- Note: system() cannot write to or read from backgrounded ("&")
+--- shell commands, e.g.: >vim
+--- echo system("cat - &", "foo")
+--- <which is equivalent to: >
+--- $ echo foo | bash -c 'cat - &'
+--- <The pipes are disconnected (unless overridden by shell
+--- redirection syntax) before input can reach it. Use
+--- |jobstart()| instead.
+---
+--- Note: Use |shellescape()| or |::S| with |expand()| or
+--- |fnamemodify()| to escape special characters in a command
+--- argument. 'shellquote' and 'shellxquote' must be properly
+--- configured. Example: >vim
+--- echo system('ls '..shellescape(expand('%:h')))
+--- echo system('ls '..expand('%:h:S'))
+---
+--- <Unlike ":!cmd" there is no automatic check for changed files.
+--- Use |:checktime| to force a check.
+---
+--- @param cmd string|string[]
+--- @param input? string|string[]|integer
+--- @return string
+function vim.fn.system(cmd, input) end
+
+--- Same as |system()|, but returns a |List| with lines (parts of
+--- output separated by NL) with NULs transformed into NLs. Output
+--- is the same as |readfile()| will output with {binary} argument
+--- set to "b", except that a final newline is not preserved,
+--- unless {keepempty} is non-zero.
+--- Note that on MS-Windows you may get trailing CR characters.
+---
+--- To see the difference between "echo hello" and "echo -n hello"
+--- use |system()| and |split()|: >vim
+--- echo split(system('echo hello'), '\n', 1)
+--- <
+--- Returns an empty string on error.
+---
+--- @param cmd string|string[]
+--- @param input? string|string[]|integer
+--- @param keepempty? integer
+--- @return string[]
+function vim.fn.systemlist(cmd, input, keepempty) end
+
+--- The result is a |List|, where each item is the number of the
+--- buffer associated with each window in the current tab page.
+--- {arg} specifies the number of the tab page to be used. When
+--- omitted the current tab page is used.
+--- When {arg} is invalid the number zero is returned.
+--- To get a list of all buffers in all tabs use this: >vim
+--- let buflist = []
+--- for i in range(tabpagenr('$'))
+--- call extend(buflist, tabpagebuflist(i + 1))
+--- endfor
+--- <Note that a buffer may appear in more than one window.
+---
+--- @param arg? any
+--- @return any
+function vim.fn.tabpagebuflist(arg) end
+
+--- The result is a Number, which is the number of the current
+--- tab page. The first tab page has number 1.
+---
+--- The optional argument {arg} supports the following values:
+--- $ the number of the last tab page (the tab page
+--- count).
+--- # the number of the last accessed tab page
+--- (where |g<Tab>| goes to). If there is no
+--- previous tab page, 0 is returned.
+--- The number can be used with the |:tab| command.
+---
+--- Returns zero on error.
+---
+--- @param arg? '$'|'#'
+--- @return integer
+function vim.fn.tabpagenr(arg) end
+
+--- Like |winnr()| but for tab page {tabarg}.
+--- {tabarg} specifies the number of tab page to be used.
+--- {arg} is used like with |winnr()|:
+--- - When omitted the current window number is returned. This is
+--- the window which will be used when going to this tab page.
+--- - When "$" the number of windows is returned.
+--- - When "#" the previous window nr is returned.
+--- Useful examples: >vim
+--- tabpagewinnr(1) " current window of tab page 1
+--- tabpagewinnr(4, '$') " number of windows in tab page 4
+--- <When {tabarg} is invalid zero is returned.
+---
+--- @param tabarg integer
+--- @param arg? '$'|'#'
+--- @return integer
+function vim.fn.tabpagewinnr(tabarg, arg) end
+
+--- Returns a |List| with the file names used to search for tags
+--- for the current buffer. This is the 'tags' option expanded.
+---
+--- @return string[]
+function vim.fn.tagfiles() end
+
+--- Returns a |List| of tags matching the regular expression {expr}.
+---
+--- If {filename} is passed it is used to prioritize the results
+--- in the same way that |:tselect| does. See |tag-priority|.
+--- {filename} should be the full path of the file.
+---
+--- Each list item is a dictionary with at least the following
+--- entries:
+--- name Name of the tag.
+--- filename Name of the file where the tag is
+--- defined. It is either relative to the
+--- current directory or a full path.
+--- cmd Ex command used to locate the tag in
+--- the file.
+--- kind Type of the tag. The value for this
+--- entry depends on the language specific
+--- kind values. Only available when
+--- using a tags file generated by
+--- Universal/Exuberant ctags or hdrtag.
+--- static A file specific tag. Refer to
+--- |static-tag| for more information.
+--- More entries may be present, depending on the content of the
+--- tags file: access, implementation, inherits and signature.
+--- Refer to the ctags documentation for information about these
+--- fields. For C code the fields "struct", "class" and "enum"
+--- may appear, they give the name of the entity the tag is
+--- contained in.
+---
+--- The ex-command "cmd" can be either an ex search pattern, a
+--- line number or a line number followed by a byte number.
+---
+--- If there are no matching tags, then an empty list is returned.
+---
+--- To get an exact tag match, the anchors '^' and '$' should be
+--- used in {expr}. This also make the function work faster.
+--- Refer to |tag-regexp| for more information about the tag
+--- search regular expression pattern.
+---
+--- Refer to |'tags'| for information about how the tags file is
+--- located by Vim. Refer to |tags-file-format| for the format of
+--- the tags file generated by the different ctags tools.
+---
+--- @param expr any
+--- @param filename? string
+--- @return any
+function vim.fn.taglist(expr, filename) end
+
+--- Return the tangent of {expr}, measured in radians, as a |Float|
+--- in the range [-inf, inf].
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo tan(10)
+--- < 0.648361 >vim
+--- echo tan(-4.01)
+--- < -1.181502
+---
+--- @param expr number
+--- @return number
+function vim.fn.tan(expr) end
+
+--- Return the hyperbolic tangent of {expr} as a |Float| in the
+--- range [-1, 1].
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo tanh(0.5)
+--- < 0.462117 >vim
+--- echo tanh(-1)
+--- < -0.761594
+---
+--- @param expr number
+--- @return number
+function vim.fn.tanh(expr) end
+
+--- Generates a (non-existent) filename located in the Nvim root
+--- |tempdir|. Scripts can use the filename as a temporary file.
+--- Example: >vim
+--- let tmpfile = tempname()
+--- exe "redir > " .. tmpfile
+--- <
+---
+--- @return string
+function vim.fn.tempname() end
+
+--- Spawns {cmd} in a new pseudo-terminal session connected
+--- to the current (unmodified) buffer. Parameters and behavior
+--- are the same as |jobstart()| except "pty", "width", "height",
+--- and "TERM" are ignored: "height" and "width" are taken from
+--- the current window. Note that termopen() implies a "pty" arg
+--- to jobstart(), and thus has the implications documented at
+--- |jobstart()|.
+---
+--- Returns the same values as jobstart().
+---
+--- Terminal environment is initialized as in |jobstart-env|,
+--- except $TERM is set to "xterm-256color". Full behavior is
+--- described in |terminal|.
+---
+--- @param cmd any
+--- @param opts? table
+--- @return any
+function vim.fn.termopen(cmd, opts) end
+
+--- Return a list with information about timers.
+--- When {id} is given only information about this timer is
+--- returned. When timer {id} does not exist an empty list is
+--- returned.
+--- When {id} is omitted information about all timers is returned.
+---
+--- For each timer the information is stored in a |Dictionary| with
+--- these items:
+--- "id" the timer ID
+--- "time" time the timer was started with
+--- "repeat" number of times the timer will still fire;
+--- -1 means forever
+--- "callback" the callback
+---
+--- @param id? any
+--- @return any
+function vim.fn.timer_info(id) end
+
+--- Pause or unpause a timer. A paused timer does not invoke its
+--- callback when its time expires. Unpausing a timer may cause
+--- the callback to be invoked almost immediately if enough time
+--- has passed.
+---
+--- Pausing a timer is useful to avoid the callback to be called
+--- for a short time.
+---
+--- If {paused} evaluates to a non-zero Number or a non-empty
+--- String, then the timer is paused, otherwise it is unpaused.
+--- See |non-zero-arg|.
+---
+--- @param timer any
+--- @param paused any
+--- @return any
+function vim.fn.timer_pause(timer, paused) end
+
+--- Create a timer and return the timer ID.
+---
+--- {time} is the waiting time in milliseconds. This is the
+--- minimum time before invoking the callback. When the system is
+--- busy or Vim is not waiting for input the time will be longer.
+--- Zero can be used to execute the callback when Vim is back in
+--- the main loop.
+---
+--- {callback} is the function to call. It can be the name of a
+--- function or a |Funcref|. It is called with one argument, which
+--- is the timer ID. The callback is only invoked when Vim is
+--- waiting for input.
+---
+--- {options} is a dictionary. Supported entries:
+--- "repeat" Number of times to repeat the callback.
+--- -1 means forever. Default is 1.
+--- If the timer causes an error three times in a
+--- row the repeat is cancelled.
+---
+--- Returns -1 on error.
+---
+--- Example: >vim
+--- func MyHandler(timer)
+--- echo 'Handler called'
+--- endfunc
+--- let timer = timer_start(500, 'MyHandler',
+--- \ {'repeat': 3})
+--- <This invokes MyHandler() three times at 500 msec intervals.
+---
+--- @param time any
+--- @param callback any
+--- @param options? table
+--- @return any
+function vim.fn.timer_start(time, callback, options) end
+
+--- Stop a timer. The timer callback will no longer be invoked.
+--- {timer} is an ID returned by timer_start(), thus it must be a
+--- Number. If {timer} does not exist there is no error.
+---
+--- @param timer any
+--- @return any
+function vim.fn.timer_stop(timer) end
+
+--- Stop all timers. The timer callbacks will no longer be
+--- invoked. Useful if some timers is misbehaving. If there are
+--- no timers there is no error.
+---
+--- @return any
+function vim.fn.timer_stopall() end
+
+--- The result is a copy of the String given, with all uppercase
+--- characters turned into lowercase (just like applying |gu| to
+--- the string). Returns an empty string on error.
+---
+--- @param expr any
+--- @return string
+function vim.fn.tolower(expr) end
+
+--- The result is a copy of the String given, with all lowercase
+--- characters turned into uppercase (just like applying |gU| to
+--- the string). Returns an empty string on error.
+---
+--- @param expr any
+--- @return string
+function vim.fn.toupper(expr) end
+
+--- The result is a copy of the {src} string with all characters
+--- which appear in {fromstr} replaced by the character in that
+--- position in the {tostr} string. Thus the first character in
+--- {fromstr} is translated into the first character in {tostr}
+--- and so on. Exactly like the unix "tr" command.
+--- This code also deals with multibyte characters properly.
+---
+--- Returns an empty string on error.
+---
+--- Examples: >vim
+--- echo tr("hello there", "ht", "HT")
+--- <returns "Hello THere" >vim
+--- echo tr("<blob>", "<>", "{}")
+--- <returns "{blob}"
+---
+--- @param src string
+--- @param fromstr string
+--- @param tostr string
+--- @return string
+function vim.fn.tr(src, fromstr, tostr) end
+
+--- Return {text} as a String where any character in {mask} is
+--- removed from the beginning and/or end of {text}.
+---
+--- If {mask} is not given, or is an empty string, {mask} is all
+--- characters up to 0x20, which includes Tab, space, NL and CR,
+--- plus the non-breaking space character 0xa0.
+---
+--- The optional {dir} argument specifies where to remove the
+--- characters:
+--- 0 remove from the beginning and end of {text}
+--- 1 remove only at the beginning of {text}
+--- 2 remove only at the end of {text}
+--- When omitted both ends are trimmed.
+---
+--- This function deals with multibyte characters properly.
+--- Returns an empty string on error.
+---
+--- Examples: >vim
+--- echo trim(" some text ")
+--- <returns "some text" >vim
+--- echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") .. "_TAIL"
+--- <returns "RESERVE_TAIL" >vim
+--- echo trim("rm<Xrm<>X>rrm", "rm<>")
+--- <returns "Xrm<>X" (characters in the middle are not removed) >vim
+--- echo trim(" vim ", " ", 2)
+--- <returns " vim"
+---
+--- @param text any
+--- @param mask? string
+--- @param dir? 0|1|2
+--- @return string
+function vim.fn.trim(text, mask, dir) end
+
+--- Return the largest integral value with magnitude less than or
+--- equal to {expr} as a |Float| (truncate towards zero).
+--- {expr} must evaluate to a |Float| or a |Number|.
+--- Returns 0.0 if {expr} is not a |Float| or a |Number|.
+--- Examples: >vim
+--- echo trunc(1.456)
+--- < 1.0 >vim
+--- echo trunc(-5.456)
+--- < -5.0 >vim
+--- echo trunc(4.0)
+--- < 4.0
+---
+--- @param expr any
+--- @return integer
+function vim.fn.trunc(expr) end
+
+--- The result is a Number representing the type of {expr}.
+--- Instead of using the number directly, it is better to use the
+--- v:t_ variable that has the value:
+--- Number: 0 |v:t_number|
+--- String: 1 |v:t_string|
+--- Funcref: 2 |v:t_func|
+--- List: 3 |v:t_list|
+--- Dictionary: 4 |v:t_dict|
+--- Float: 5 |v:t_float|
+--- Boolean: 6 |v:t_bool| (|v:false| and |v:true|)
+--- Null: 7 (|v:null|)
+--- Blob: 10 |v:t_blob|
+--- For backward compatibility, this method can be used: >vim
+--- if type(myvar) == type(0) | endif
+--- if type(myvar) == type("") | endif
+--- if type(myvar) == type(function("tr")) | endif
+--- if type(myvar) == type([]) | endif
+--- if type(myvar) == type({}) | endif
+--- if type(myvar) == type(0.0) | endif
+--- if type(myvar) == type(v:true) | endif
+--- <In place of checking for |v:null| type it is better to check
+--- for |v:null| directly as it is the only value of this type: >vim
+--- if myvar is v:null | endif
+--- <To check if the v:t_ variables exist use this: >vim
+--- if exists('v:t_number') | endif
+---
+--- @param expr any
+--- @return integer
+function vim.fn.type(expr) end
+
+--- Return the name of the undo file that would be used for a file
+--- with name {name} when writing. This uses the 'undodir'
+--- option, finding directories that exist. It does not check if
+--- the undo file exists.
+--- {name} is always expanded to the full path, since that is what
+--- is used internally.
+--- If {name} is empty undofile() returns an empty string, since a
+--- buffer without a file name will not write an undo file.
+--- Useful in combination with |:wundo| and |:rundo|.
+---
+--- @param name string
+--- @return string
+function vim.fn.undofile(name) end
+
+--- Return the current state of the undo tree for the current
+--- buffer, or for a specific buffer if {buf} is given. The
+--- result is a dictionary with the following items:
+--- "seq_last" The highest undo sequence number used.
+--- "seq_cur" The sequence number of the current position in
+--- the undo tree. This differs from "seq_last"
+--- when some changes were undone.
+--- "time_cur" Time last used for |:earlier| and related
+--- commands. Use |strftime()| to convert to
+--- something readable.
+--- "save_last" Number of the last file write. Zero when no
+--- write yet.
+--- "save_cur" Number of the current position in the undo
+--- tree.
+--- "synced" Non-zero when the last undo block was synced.
+--- This happens when waiting from input from the
+--- user. See |undo-blocks|.
+--- "entries" A list of dictionaries with information about
+--- undo blocks.
+---
+--- The first item in the "entries" list is the oldest undo item.
+--- Each List item is a |Dictionary| with these items:
+--- "seq" Undo sequence number. Same as what appears in
+--- |:undolist|.
+--- "time" Timestamp when the change happened. Use
+--- |strftime()| to convert to something readable.
+--- "newhead" Only appears in the item that is the last one
+--- that was added. This marks the last change
+--- and where further changes will be added.
+--- "curhead" Only appears in the item that is the last one
+--- that was undone. This marks the current
+--- position in the undo tree, the block that will
+--- be used by a redo command. When nothing was
+--- undone after the last change this item will
+--- not appear anywhere.
+--- "save" Only appears on the last block before a file
+--- write. The number is the write count. The
+--- first write has number 1, the last one the
+--- "save_last" mentioned above.
+--- "alt" Alternate entry. This is again a List of undo
+--- blocks. Each item may again have an "alt"
+--- item.
+---
+--- @param buf? integer|string
+--- @return any
+function vim.fn.undotree(buf) end
+
+--- Remove second and succeeding copies of repeated adjacent
+--- {list} items in-place. Returns {list}. If you want a list
+--- to remain unmodified make a copy first: >vim
+--- let newlist = uniq(copy(mylist))
+--- <The default compare function uses the string representation of
+--- each item. For the use of {func} and {dict} see |sort()|.
+---
+--- Returns zero if {list} is not a |List|.
+---
+--- @param list any
+--- @param func? any
+--- @param dict? any
+--- @return any[]|0
+function vim.fn.uniq(list, func, dict) end
+
+--- Same as |charidx()| but returns the UTF-16 code unit index of
+--- the byte at {idx} in {string} (after converting it to UTF-16).
+---
+--- When {charidx} is present and TRUE, {idx} is used as the
+--- character index in the String {string} instead of as the byte
+--- index.
+--- An {idx} in the middle of a UTF-8 sequence is rounded
+--- downwards to the beginning of that sequence.
+---
+--- Returns -1 if the arguments are invalid or if there are less
+--- than {idx} bytes in {string}. If there are exactly {idx} bytes
+--- the length of the string in UTF-16 code units is returned.
+---
+--- See |byteidx()| and |byteidxcomp()| for getting the byte index
+--- from the UTF-16 index and |charidx()| for getting the
+--- character index from the UTF-16 index.
+--- Refer to |string-offset-encoding| for more information.
+--- Examples: >vim
+--- echo utf16idx('a😊😊', 3) " returns 2
+--- echo utf16idx('a😊😊', 7) " returns 4
+--- echo utf16idx('a😊😊', 1, 0, 1) " returns 2
+--- echo utf16idx('a😊😊', 2, 0, 1) " returns 4
+--- echo utf16idx('aą́c', 6) " returns 2
+--- echo utf16idx('aą́c', 6, 1) " returns 4
+--- echo utf16idx('a😊😊', 9) " returns -1
+--- <
+---
+--- @param string string
+--- @param idx integer
+--- @param countcc? any
+--- @param charidx? any
+--- @return integer
+function vim.fn.utf16idx(string, idx, countcc, charidx) end
+
+--- Return a |List| with all the values of {dict}. The |List| is
+--- in arbitrary order. Also see |items()| and |keys()|.
+--- Returns zero if {dict} is not a |Dict|.
+---
+--- @param dict any
+--- @return any
+function vim.fn.values(dict) end
+
+--- The result is a Number, which is the screen column of the file
+--- position given with {expr}. That is, the last screen position
+--- occupied by the character at that position, when the screen
+--- would be of unlimited width. When there is a <Tab> at the
+--- position, the returned Number will be the column at the end of
+--- the <Tab>. For example, for a <Tab> in column 1, with 'ts'
+--- set to 8, it returns 8. |conceal| is ignored.
+--- For the byte position use |col()|.
+---
+--- For the use of {expr} see |col()|.
+---
+--- When 'virtualedit' is used {expr} can be [lnum, col, off],
+--- where "off" is the offset in screen columns from the start of
+--- the character. E.g., a position within a <Tab> or after the
+--- last character. When "off" is omitted zero is used. When
+--- Virtual editing is active in the current mode, a position
+--- beyond the end of the line can be returned. Also see
+--- |'virtualedit'|
+---
+--- The accepted positions are:
+--- . the cursor position
+--- $ the end of the cursor line (the result is the
+--- number of displayed characters in the cursor line
+--- plus one)
+--- 'x position of mark x (if the mark is not set, 0 is
+--- returned)
+--- v In Visual mode: the start of the Visual area (the
+--- cursor is the end). When not in Visual mode
+--- returns the cursor position. Differs from |'<| in
+--- that it's updated right away.
+---
+--- If {list} is present and non-zero then virtcol() returns a
+--- List with the first and last screen position occupied by the
+--- character.
+---
+--- With the optional {winid} argument the values are obtained for
+--- that window instead of the current window.
+---
+--- Note that only marks in the current file can be used.
+--- Examples: >vim
+--- " With text "foo^Lbar" and cursor on the "^L":
+---
+--- echo virtcol(".") " returns 5
+--- echo virtcol(".", 1) " returns [4, 5]
+--- echo virtcol("$") " returns 9
+---
+--- " With text " there", with 't at 'h':
+---
+--- echo virtcol("'t") " returns 6
+--- <The first column is 1. 0 or [0, 0] is returned for an error.
+--- A more advanced example that echoes the maximum length of
+--- all lines: >vim
+--- echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
+---
+--- @param expr any
+--- @param list? any
+--- @param winid? integer
+--- @return any
+function vim.fn.virtcol(expr, list, winid) end
+
+--- The result is a Number, which is the byte index of the
+--- character in window {winid} at buffer line {lnum} and virtual
+--- column {col}.
+---
+--- If buffer line {lnum} is an empty line, 0 is returned.
+---
+--- If {col} is greater than the last virtual column in line
+--- {lnum}, then the byte index of the character at the last
+--- virtual column is returned.
+---
+--- For a multi-byte character, the column number of the first
+--- byte in the character is returned.
+---
+--- The {winid} argument can be the window number or the
+--- |window-ID|. If this is zero, then the current window is used.
+---
+--- Returns -1 if the window {winid} doesn't exist or the buffer
+--- line {lnum} or virtual column {col} is invalid.
+---
+--- See also |screenpos()|, |virtcol()| and |col()|.
+---
+--- @param winid integer
+--- @param lnum integer
+--- @param col integer
+--- @return any
+function vim.fn.virtcol2col(winid, lnum, col) end
+
+--- The result is a String, which describes the last Visual mode
+--- used in the current buffer. Initially it returns an empty
+--- string, but once Visual mode has been used, it returns "v",
+--- "V", or "<CTRL-V>" (a single CTRL-V character) for
+--- character-wise, line-wise, or block-wise Visual mode
+--- respectively.
+--- Example: >vim
+--- exe "normal " .. visualmode()
+--- <This enters the same Visual mode as before. It is also useful
+--- in scripts if you wish to act differently depending on the
+--- Visual mode that was used.
+--- If Visual mode is active, use |mode()| to get the Visual mode
+--- (e.g., in a |:vmap|).
+--- If {expr} is supplied and it evaluates to a non-zero Number or
+--- a non-empty String, then the Visual mode will be cleared and
+--- the old value is returned. See |non-zero-arg|.
+---
+--- @param expr? any
+--- @return any
+function vim.fn.visualmode(expr) end
+
+--- Waits until {condition} evaluates to |TRUE|, where {condition}
+--- is a |Funcref| or |string| containing an expression.
+---
+--- {timeout} is the maximum waiting time in milliseconds, -1
+--- means forever.
+---
+--- Condition is evaluated on user events, internal events, and
+--- every {interval} milliseconds (default: 200).
+---
+--- Returns a status integer:
+--- 0 if the condition was satisfied before timeout
+--- -1 if the timeout was exceeded
+--- -2 if the function was interrupted (by |CTRL-C|)
+--- -3 if an error occurred
+---
+--- @param timeout integer
+--- @param condition any
+--- @param interval? any
+--- @return any
+function vim.fn.wait(timeout, condition, interval) end
+
+--- Returns |TRUE| when the wildmenu is active and |FALSE|
+--- otherwise. See 'wildmenu' and 'wildmode'.
+--- This can be used in mappings to handle the 'wildcharm' option
+--- gracefully. (Makes only sense with |mapmode-c| mappings).
+---
+--- For example to make <c-j> work like <down> in wildmode, use: >vim
+--- cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
+--- <
+--- (Note, this needs the 'wildcharm' option set appropriately).
+---
+--- @return any
+function vim.fn.wildmenumode() end
+
+--- Like `execute()` but in the context of window {id}.
+--- The window will temporarily be made the current window,
+--- without triggering autocommands or changing directory. When
+--- executing {command} autocommands will be triggered, this may
+--- have unexpected side effects. Use `:noautocmd` if needed.
+--- Example: >vim
+--- call win_execute(winid, 'syntax enable')
+--- <Doing the same with `setwinvar()` would not trigger
+--- autocommands and not actually show syntax highlighting.
+---
+--- When window {id} does not exist then no error is given and
+--- an empty string is returned.
+---
+--- @param id any
+--- @param command any
+--- @param silent? boolean
+--- @return any
+function vim.fn.win_execute(id, command, silent) end
+
+--- Returns a |List| with |window-ID|s for windows that contain
+--- buffer {bufnr}. When there is none the list is empty.
+---
+--- @param bufnr any
+--- @return integer[]
+function vim.fn.win_findbuf(bufnr) end
+
+--- Get the |window-ID| for the specified window.
+--- When {win} is missing use the current window.
+--- With {win} this is the window number. The top window has
+--- number 1.
+--- Without {tab} use the current tab, otherwise the tab with
+--- number {tab}. The first tab has number one.
+--- Return zero if the window cannot be found.
+---
+--- @param win? any
+--- @param tab? any
+--- @return integer
+function vim.fn.win_getid(win, tab) end
+
+--- Return the type of the window:
+--- "autocmd" autocommand window. Temporary window
+--- used to execute autocommands.
+--- "command" command-line window |cmdwin|
+--- (empty) normal window
+--- "loclist" |location-list-window|
+--- "popup" floating window |api-floatwin|
+--- "preview" preview window |preview-window|
+--- "quickfix" |quickfix-window|
+--- "unknown" window {nr} not found
+---
+--- When {nr} is omitted return the type of the current window.
+--- When {nr} is given return the type of this window by number or
+--- |window-ID|.
+---
+--- Also see the 'buftype' option.
+---
+--- @param nr? integer
+--- @return 'autocmd'|'command'|''|'loclist'|'popup'|'preview'|'quickfix'|'unknown'
+function vim.fn.win_gettype(nr) end
+
+--- Go to window with ID {expr}. This may also change the current
+--- tabpage.
+--- Return TRUE if successful, FALSE if the window cannot be found.
+---
+--- @param expr any
+--- @return 0|1
+function vim.fn.win_gotoid(expr) end
+
+--- Return a list with the tab number and window number of window
+--- with ID {expr}: [tabnr, winnr].
+--- Return [0, 0] if the window cannot be found.
+---
+--- @param expr any
+--- @return any
+function vim.fn.win_id2tabwin(expr) end
+
+--- Return the window number of window with ID {expr}.
+--- Return 0 if the window cannot be found in the current tabpage.
+---
+--- @param expr any
+--- @return any
+function vim.fn.win_id2win(expr) end
+
+--- Move window {nr}'s vertical separator (i.e., the right border)
+--- by {offset} columns, as if being dragged by the mouse. {nr}
+--- can be a window number or |window-ID|. A positive {offset}
+--- moves right and a negative {offset} moves left. Moving a
+--- window's vertical separator will change the width of the
+--- window and the width of other windows adjacent to the vertical
+--- separator. The magnitude of movement may be smaller than
+--- specified (e.g., as a consequence of maintaining
+--- 'winminwidth'). Returns TRUE if the window can be found and
+--- FALSE otherwise.
+--- This will fail for the rightmost window and a full-width
+--- window, since it has no separator on the right.
+--- Only works for the current tab page. *E1308*
+---
+--- @param nr integer
+--- @param offset any
+--- @return any
+function vim.fn.win_move_separator(nr, offset) end
+
+--- Move window {nr}'s status line (i.e., the bottom border) by
+--- {offset} rows, as if being dragged by the mouse. {nr} can be a
+--- window number or |window-ID|. A positive {offset} moves down
+--- and a negative {offset} moves up. Moving a window's status
+--- line will change the height of the window and the height of
+--- other windows adjacent to the status line. The magnitude of
+--- movement may be smaller than specified (e.g., as a consequence
+--- of maintaining 'winminheight'). Returns TRUE if the window can
+--- be found and FALSE otherwise.
+--- Only works for the current tab page.
+---
+--- @param nr integer
+--- @param offset any
+--- @return any
+function vim.fn.win_move_statusline(nr, offset) end
+
+--- Return the screen position of window {nr} as a list with two
+--- numbers: [row, col]. The first window always has position
+--- [1, 1], unless there is a tabline, then it is [2, 1].
+--- {nr} can be the window number or the |window-ID|. Use zero
+--- for the current window.
+--- Returns [0, 0] if the window cannot be found in the current
+--- tabpage.
+---
+--- @param nr integer
+--- @return any
+function vim.fn.win_screenpos(nr) end
+
+--- Move the window {nr} to a new split of the window {target}.
+--- This is similar to moving to {target}, creating a new window
+--- using |:split| but having the same contents as window {nr}, and
+--- then closing {nr}.
+---
+--- Both {nr} and {target} can be window numbers or |window-ID|s.
+--- Both must be in the current tab page.
+---
+--- Returns zero for success, non-zero for failure.
+---
+--- {options} is a |Dictionary| with the following optional entries:
+--- "vertical" When TRUE, the split is created vertically,
+--- like with |:vsplit|.
+--- "rightbelow" When TRUE, the split is made below or to the
+--- right (if vertical). When FALSE, it is done
+--- above or to the left (if vertical). When not
+--- present, the values of 'splitbelow' and
+--- 'splitright' are used.
+---
+--- @param nr integer
+--- @param target any
+--- @param options? table
+--- @return any
+function vim.fn.win_splitmove(nr, target, options) end
+
+--- The result is a Number, which is the number of the buffer
+--- associated with window {nr}. {nr} can be the window number or
+--- the |window-ID|.
+--- When {nr} is zero, the number of the buffer in the current
+--- window is returned.
+--- When window {nr} doesn't exist, -1 is returned.
+--- Example: >vim
+--- echo "The file in the current window is " .. bufname(winbufnr(0))
+--- <
+---
+--- @param nr integer
+--- @return integer
+function vim.fn.winbufnr(nr) end
+
+--- The result is a Number, which is the virtual column of the
+--- cursor in the window. This is counting screen cells from the
+--- left side of the window. The leftmost column is one.
+---
+--- @return integer
+function vim.fn.wincol() end
+
+--- The result is a String. For MS-Windows it indicates the OS
+--- version. E.g, Windows 10 is "10.0", Windows 8 is "6.2",
+--- Windows XP is "5.1". For non-MS-Windows systems the result is
+--- an empty string.
+---
+--- @return string
+function vim.fn.windowsversion() end
+
+--- The result is a Number, which is the height of window {nr}.
+--- {nr} can be the window number or the |window-ID|.
+--- When {nr} is zero, the height of the current window is
+--- returned. When window {nr} doesn't exist, -1 is returned.
+--- An existing window always has a height of zero or more.
+--- This excludes any window toolbar line.
+--- Examples: >vim
+--- echo "The current window has " .. winheight(0) .. " lines."
+---
+--- @param nr integer
+--- @return integer
+function vim.fn.winheight(nr) end
+
+--- The result is a nested List containing the layout of windows
+--- in a tabpage.
+---
+--- Without {tabnr} use the current tabpage, otherwise the tabpage
+--- with number {tabnr}. If the tabpage {tabnr} is not found,
+--- returns an empty list.
+---
+--- For a leaf window, it returns: >
+--- ["leaf", {winid}]
+--- <
+--- For horizontally split windows, which form a column, it
+--- returns: >
+--- ["col", [{nested list of windows}]]
+--- <For vertically split windows, which form a row, it returns: >
+--- ["row", [{nested list of windows}]]
+--- <
+--- Example: >vim
+--- " Only one window in the tab page
+--- echo winlayout()
+--- < >
+--- ['leaf', 1000]
+--- < >vim
+--- " Two horizontally split windows
+--- echo winlayout()
+--- < >
+--- ['col', [['leaf', 1000], ['leaf', 1001]]]
+--- < >vim
+--- " The second tab page, with three horizontally split
+--- " windows, with two vertically split windows in the
+--- " middle window
+--- echo winlayout(2)
+--- < >
+--- ['col', [['leaf', 1002], ['row', [['leaf', 1003],
+--- ['leaf', 1001]]], ['leaf', 1000]]]
+--- <
+---
+--- @param tabnr? integer
+--- @return any
+function vim.fn.winlayout(tabnr) end
+
+--- The result is a Number, which is the screen line of the cursor
+--- in the window. This is counting screen lines from the top of
+--- the window. The first line is one.
+--- If the cursor was moved the view on the file will be updated
+--- first, this may cause a scroll.
+---
+--- @return integer
+function vim.fn.winline() end
+
+--- The result is a Number, which is the number of the current
+--- window. The top window has number 1.
+--- Returns zero for a popup window.
+---
+--- The optional argument {arg} supports the following values:
+--- $ the number of the last window (the window
+--- count).
+--- # the number of the last accessed window (where
+--- |CTRL-W_p| goes to). If there is no previous
+--- window or it is in another tab page 0 is
+--- returned.
+--- {N}j the number of the Nth window below the
+--- current window (where |CTRL-W_j| goes to).
+--- {N}k the number of the Nth window above the current
+--- window (where |CTRL-W_k| goes to).
+--- {N}h the number of the Nth window left of the
+--- current window (where |CTRL-W_h| goes to).
+--- {N}l the number of the Nth window right of the
+--- current window (where |CTRL-W_l| goes to).
+--- The number can be used with |CTRL-W_w| and ":wincmd w"
+--- |:wincmd|.
+--- When {arg} is invalid an error is given and zero is returned.
+--- Also see |tabpagewinnr()| and |win_getid()|.
+--- Examples: >vim
+--- let window_count = winnr('$')
+--- let prev_window = winnr('#')
+--- let wnum = winnr('3k')
+---
+--- @param arg? any
+--- @return any
+function vim.fn.winnr(arg) end
+
+--- Returns a sequence of |:resize| commands that should restore
+--- the current window sizes. Only works properly when no windows
+--- are opened or closed and the current window and tab page is
+--- unchanged.
+--- Example: >vim
+--- let cmd = winrestcmd()
+--- call MessWithWindowSizes()
+--- exe cmd
+--- <
+---
+--- @return any
+function vim.fn.winrestcmd() end
+
+--- Uses the |Dictionary| returned by |winsaveview()| to restore
+--- the view of the current window.
+--- Note: The {dict} does not have to contain all values, that are
+--- returned by |winsaveview()|. If values are missing, those
+--- settings won't be restored. So you can use: >vim
+--- call winrestview({'curswant': 4})
+--- <
+--- This will only set the curswant value (the column the cursor
+--- wants to move on vertical movements) of the cursor to column 5
+--- (yes, that is 5), while all other settings will remain the
+--- same. This is useful, if you set the cursor position manually.
+---
+--- If you have changed the values the result is unpredictable.
+--- If the window size changed the result won't be the same.
+---
+--- @param dict vim.fn.winrestview.dict
+--- @return any
+function vim.fn.winrestview(dict) end
+
+--- Returns a |Dictionary| that contains information to restore
+--- the view of the current window. Use |winrestview()| to
+--- restore the view.
+--- This is useful if you have a mapping that jumps around in the
+--- buffer and you want to go back to the original view.
+--- This does not save fold information. Use the 'foldenable'
+--- option to temporarily switch off folding, so that folds are
+--- not opened when moving around. This may have side effects.
+--- The return value includes:
+--- lnum cursor line number
+--- col cursor column (Note: the first column
+--- zero, as opposed to what |getcurpos()|
+--- returns)
+--- coladd cursor column offset for 'virtualedit'
+--- curswant column for vertical movement (Note:
+--- the first column is zero, as opposed
+--- to what |getcurpos()| returns). After
+--- |$| command it will be a very large
+--- number equal to |v:maxcol|.
+--- topline first line in the window
+--- topfill filler lines, only in diff mode
+--- leftcol first column displayed; only used when
+--- 'wrap' is off
+--- skipcol columns skipped
+--- Note that no option values are saved.
+---
+--- @return vim.fn.winsaveview.ret
+function vim.fn.winsaveview() end
+
+--- The result is a Number, which is the width of window {nr}.
+--- {nr} can be the window number or the |window-ID|.
+--- When {nr} is zero, the width of the current window is
+--- returned. When window {nr} doesn't exist, -1 is returned.
+--- An existing window always has a width of zero or more.
+--- Examples: >vim
+--- echo "The current window has " .. winwidth(0) .. " columns."
+--- if winwidth(0) <= 50
+--- 50 wincmd |
+--- endif
+--- <For getting the terminal or screen size, see the 'columns'
+--- option.
+---
+--- @param nr integer
+--- @return any
+function vim.fn.winwidth(nr) end
+
+--- The result is a dictionary of byte/chars/word statistics for
+--- the current buffer. This is the same info as provided by
+--- |g_CTRL-G|
+--- The return value includes:
+--- bytes Number of bytes in the buffer
+--- chars Number of chars in the buffer
+--- words Number of words in the buffer
+--- cursor_bytes Number of bytes before cursor position
+--- (not in Visual mode)
+--- cursor_chars Number of chars before cursor position
+--- (not in Visual mode)
+--- cursor_words Number of words before cursor position
+--- (not in Visual mode)
+--- visual_bytes Number of bytes visually selected
+--- (only in Visual mode)
+--- visual_chars Number of chars visually selected
+--- (only in Visual mode)
+--- visual_words Number of words visually selected
+--- (only in Visual mode)
+---
+--- @return any
+function vim.fn.wordcount() end
+
+--- When {object} is a |List| write it to file {fname}. Each list
+--- item is separated with a NL. Each list item must be a String
+--- or Number.
+--- All NL characters are replaced with a NUL character.
+--- Inserting CR characters needs to be done before passing {list}
+--- to writefile().
+---
+--- When {object} is a |Blob| write the bytes to file {fname}
+--- unmodified, also when binary mode is not specified.
+---
+--- {flags} must be a String. These characters are recognized:
+---
+--- 'b' Binary mode is used: There will not be a NL after the
+--- last list item. An empty item at the end does cause the
+--- last line in the file to end in a NL.
+---
+--- 'a' Append mode is used, lines are appended to the file: >vim
+--- call writefile(["foo"], "event.log", "a")
+--- call writefile(["bar"], "event.log", "a")
+--- <
+--- 'D' Delete the file when the current function ends. This
+--- works like: >vim
+--- defer delete({fname})
+--- < Fails when not in a function. Also see |:defer|.
+---
+--- 's' fsync() is called after writing the file. This flushes
+--- the file to disk, if possible. This takes more time but
+--- avoids losing the file if the system crashes.
+---
+--- 'S' fsync() is not called, even when 'fsync' is set.
+---
+--- When {flags} does not contain "S" or "s" then fsync() is
+--- called if the 'fsync' option is set.
+---
+--- An existing file is overwritten, if possible.
+---
+--- When the write fails -1 is returned, otherwise 0. There is an
+--- error message if the file can't be created or when writing
+--- fails.
+---
+--- Also see |readfile()|.
+--- To copy a file byte for byte: >vim
+--- let fl = readfile("foo", "b")
+--- call writefile(fl, "foocopy", "b")
+---
+--- @param object any
+--- @param fname string
+--- @param flags? string
+--- @return any
+function vim.fn.writefile(object, fname, flags) end
+
+--- Bitwise XOR on the two arguments. The arguments are converted
+--- to a number. A List, Dict or Float argument causes an error.
+--- Also see `and()` and `or()`.
+--- Example: >vim
+--- let bits = xor(bits, 0x80)
+--- <
+---
+--- @param expr any
+--- @param expr1 any
+--- @return any
+function vim.fn.xor(expr, expr1) end
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
new file mode 100644
index 0000000000..b83a8dd4b1
--- /dev/null
+++ b/runtime/lua/vim/_options.lua
@@ -0,0 +1,910 @@
+---@defgroup lua-vimscript
+---
+---@brief Nvim Lua provides an interface or "bridge" to Vimscript variables and
+---functions, and editor commands and options.
+---
+---Objects passed over this bridge are COPIED (marshalled): there are no
+---"references". |lua-guide-variables| For example, using \`vim.fn.remove()\` on
+---a Lua list copies the list object to Vimscript and does NOT modify the Lua
+---list:
+---
+--- ```lua
+--- local list = { 1, 2, 3 }
+--- vim.fn.remove(list, 0)
+--- vim.print(list) --> "{ 1, 2, 3 }"
+--- ```
+
+---@addtogroup lua-vimscript
+---@brief <pre>help
+---vim.call({func}, {...}) *vim.call()*
+--- Invokes |vim-function| or |user-function| {func} with arguments {...}.
+--- See also |vim.fn|.
+--- Equivalent to: >lua
+--- vim.fn[func]({...})
+---<
+---vim.cmd({command})
+--- See |vim.cmd()|.
+---
+---vim.fn.{func}({...}) *vim.fn*
+--- Invokes |vim-function| or |user-function| {func} with arguments {...}.
+--- To call autoload functions, use the syntax: >lua
+--- vim.fn['some\#function']({...})
+---<
+--- Unlike vim.api.|nvim_call_function()| this converts directly between Vim
+--- objects and Lua objects. If the Vim function returns a float, it will be
+--- represented directly as a Lua number. Empty lists and dictionaries both
+--- are represented by an empty table.
+---
+--- Note: |v:null| values as part of the return value is represented as
+--- |vim.NIL| special value
+---
+--- Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
+--- enumerates functions that were called at least once.
+---
+--- Note: The majority of functions cannot run in |api-fast| callbacks with some
+--- undocumented exceptions which are allowed.
+---
+--- *lua-vim-variables*
+---The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed
+---from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables
+---described below. In this way you can easily read and modify global Vimscript
+---variables from Lua.
+---
+---Example: >lua
+---
+--- vim.g.foo = 5 -- Set the g:foo Vimscript variable.
+--- print(vim.g.foo) -- Get and print the g:foo Vimscript variable.
+--- vim.g.foo = nil -- Delete (:unlet) the Vimscript variable.
+--- vim.b[2].foo = 6 -- Set b:foo for buffer 2
+---<
+---
+---Note that setting dictionary fields directly will not write them back into
+---Nvim. This is because the index into the namespace simply returns a copy.
+---Instead the whole dictionary must be written as one. This can be achieved by
+---creating a short-lived temporary.
+---
+---Example: >lua
+---
+--- vim.g.my_dict.field1 = 'value' -- Does not work
+---
+--- local my_dict = vim.g.my_dict --
+--- my_dict.field1 = 'value' -- Instead do
+--- vim.g.my_dict = my_dict --
+---
+---vim.g *vim.g*
+--- Global (|g:|) editor variables.
+--- Key with no value returns `nil`.
+---
+---vim.b *vim.b*
+--- Buffer-scoped (|b:|) variables for the current buffer.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific buffer.
+---
+---vim.w *vim.w*
+--- Window-scoped (|w:|) variables for the current window.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific window.
+---
+---vim.t *vim.t*
+--- Tabpage-scoped (|t:|) variables for the current tabpage.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific tabpage.
+---
+---vim.v *vim.v*
+--- |v:| variables.
+--- Invalid or unset key returns `nil`.
+---</pre>
+
+local api = vim.api
+
+-- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded.
+-- Can be done in a separate PR.
+local key_value_options = {
+ fillchars = true,
+ fcs = true,
+ listchars = true,
+ lcs = true,
+ winhighlight = true,
+ winhl = true,
+}
+
+--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
+---@return string
+local function get_option_metatype(name, info)
+ if info.type == 'string' then
+ if info.flaglist then
+ return 'set'
+ elseif info.commalist then
+ if key_value_options[name] then
+ return 'map'
+ end
+ return 'array'
+ end
+ return 'string'
+ end
+ return info.type
+end
+
+--- @param name string
+local function get_options_info(name)
+ local info = api.nvim_get_option_info2(name, {})
+ info.metatype = get_option_metatype(name, info)
+ return info
+end
+
+--- Environment variables defined in the editor session.
+--- See |expand-env| and |:let-environment| for the Vimscript behavior.
+--- Invalid or unset key returns `nil`.
+---
+--- Example:
+---
+--- ```lua
+--- vim.env.FOO = 'bar'
+--- print(vim.env.TERM)
+--- ```
+---
+---@param var string
+vim.env = setmetatable({}, {
+ __index = function(_, k)
+ local v = vim.fn.getenv(k)
+ if v == vim.NIL then
+ return nil
+ end
+ return v
+ end,
+
+ __newindex = function(_, k, v)
+ vim.fn.setenv(k, v)
+ end,
+})
+
+local function new_buf_opt_accessor(bufnr)
+ return setmetatable({}, {
+ __index = function(_, k)
+ if bufnr == nil and type(k) == 'number' then
+ return new_buf_opt_accessor(k)
+ end
+ return api.nvim_get_option_value(k, { buf = bufnr or 0 })
+ end,
+
+ __newindex = function(_, k, v)
+ return api.nvim_set_option_value(k, v, { buf = bufnr or 0 })
+ end,
+ })
+end
+
+local function new_win_opt_accessor(winid, bufnr)
+ return setmetatable({}, {
+ __index = function(_, k)
+ if bufnr == nil and type(k) == 'number' then
+ if winid == nil then
+ return new_win_opt_accessor(k)
+ else
+ return new_win_opt_accessor(winid, k)
+ end
+ end
+
+ if bufnr ~= nil and bufnr ~= 0 then
+ error('only bufnr=0 is supported')
+ end
+
+ -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value
+ return api.nvim_get_option_value(k, {
+ scope = bufnr and 'local' or nil,
+ win = winid or 0,
+ })
+ end,
+
+ __newindex = function(_, k, v)
+ -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value
+ return api.nvim_set_option_value(k, v, {
+ scope = bufnr and 'local' or nil,
+ win = winid or 0,
+ })
+ end,
+ })
+end
+
+---@addtogroup lua-vimscript
+---@brief <pre>help
+---` ` *lua-options*
+--- *lua-vim-options*
+--- *lua-vim-set*
+--- *lua-vim-setlocal*
+---
+---Vim options can be accessed through |vim.o|, which behaves like Vimscript
+---|:set|.
+---
+--- Examples: ~
+---
+--- To set a boolean toggle:
+--- Vimscript: `set number`
+--- Lua: `vim.o.number = true`
+---
+--- To set a string value:
+--- Vimscript: `set wildignore=*.o,*.a,__pycache__`
+--- Lua: `vim.o.wildignore = '*.o,*.a,__pycache__'`
+---
+---Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
+---window-scoped options. Note that this must NOT be confused with
+---|local-options| and |:setlocal|. There is also |vim.go| that only accesses the
+---global value of a |global-local| option, see |:setglobal|.
+---</pre>
+
+--- Get or set |options|. Like `:set`. Invalid key is an error.
+---
+--- Note: this works on both buffer-scoped and window-scoped options using the
+--- current buffer and window.
+---
+--- Example:
+---
+--- ```lua
+--- vim.o.cmdheight = 4
+--- print(vim.o.columns)
+--- print(vim.o.foo) -- error: invalid key
+--- ```
+vim.o = setmetatable({}, {
+ __index = function(_, k)
+ return api.nvim_get_option_value(k, {})
+ end,
+ __newindex = function(_, k, v)
+ return api.nvim_set_option_value(k, v, {})
+ end,
+})
+
+--- Get or set global |options|. Like `:setglobal`. Invalid key is
+--- an error.
+---
+--- Note: this is different from |vim.o| because this accesses the global
+--- option value and thus is mostly useful for use with |global-local|
+--- options.
+---
+--- Example:
+---
+--- ```lua
+--- vim.go.cmdheight = 4
+--- print(vim.go.columns)
+--- print(vim.go.bar) -- error: invalid key
+--- ```
+vim.go = setmetatable({}, {
+ __index = function(_, k)
+ return api.nvim_get_option_value(k, { scope = 'global' })
+ end,
+ __newindex = function(_, k, v)
+ return api.nvim_set_option_value(k, v, { scope = 'global' })
+ end,
+})
+
+--- Get or set buffer-scoped |options| for the buffer with number {bufnr}.
+--- Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
+--- buffer is used. Invalid {bufnr} or key is an error.
+---
+--- Note: this is equivalent to both `:set` and `:setlocal`.
+---
+--- Example:
+---
+--- ```lua
+--- local bufnr = vim.api.nvim_get_current_buf()
+--- vim.bo[bufnr].buflisted = true -- same as vim.bo.buflisted = true
+--- print(vim.bo.comments)
+--- print(vim.bo.baz) -- error: invalid key
+--- ```
+vim.bo = new_buf_opt_accessor()
+
+--- Get or set window-scoped |options| for the window with handle {winid} and
+--- buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
+--- `:set` otherwise. If [{winid}] is omitted then the current window is
+--- used. Invalid {winid}, {bufnr} or key is an error.
+---
+--- Note: only {bufnr} with value `0` (the current buffer in the window) is
+--- supported.
+---
+--- Example:
+---
+--- ```lua
+--- local winid = vim.api.nvim_get_current_win()
+--- vim.wo[winid].number = true -- same as vim.wo.number = true
+--- print(vim.wo.foldmarker)
+--- print(vim.wo.quux) -- error: invalid key
+--- vim.wo[winid][0].spell = false -- like ':setlocal nospell'
+--- ```
+vim.wo = new_win_opt_accessor()
+
+---@brief [[
+--- vim.opt, vim.opt_local and vim.opt_global implementation
+---
+--- To be used as helpers for working with options within neovim.
+--- For information on how to use, see :help vim.opt
+---
+---@brief ]]
+
+--- Preserves the order and does not mutate the original list
+local function remove_duplicate_values(t)
+ local result, seen = {}, {}
+ for _, v in ipairs(t) do
+ if not seen[v] then
+ table.insert(result, v)
+ end
+
+ seen[v] = true
+ end
+
+ return result
+end
+
+-- Check whether the OptionTypes is allowed for vim.opt
+-- If it does not match, throw an error which indicates which option causes the error.
+local function assert_valid_value(name, value, types)
+ local type_of_value = type(value)
+ for _, valid_type in ipairs(types) do
+ if valid_type == type_of_value then
+ return
+ end
+ end
+
+ error(
+ string.format(
+ "Invalid option type '%s' for '%s', should be %s",
+ type_of_value,
+ name,
+ table.concat(types, ' or ')
+ )
+ )
+end
+
+local function passthrough(_, x)
+ return x
+end
+
+local function tbl_merge(left, right)
+ return vim.tbl_extend('force', left, right)
+end
+
+local function tbl_remove(t, value)
+ if type(value) == 'string' then
+ t[value] = nil
+ else
+ for _, v in ipairs(value) do
+ t[v] = nil
+ end
+ end
+
+ return t
+end
+
+local valid_types = {
+ boolean = { 'boolean' },
+ number = { 'number' },
+ string = { 'string' },
+ set = { 'string', 'table' },
+ array = { 'string', 'table' },
+ map = { 'string', 'table' },
+}
+
+-- Map of functions to take a Lua style value and convert to vimoption_T style value.
+-- Each function takes (info, lua_value) -> vim_value
+local to_vim_value = {
+ boolean = passthrough,
+ number = passthrough,
+ string = passthrough,
+
+ set = function(info, value)
+ if type(value) == 'string' then
+ return value
+ end
+
+ if info.flaglist and info.commalist then
+ local keys = {}
+ for k, v in pairs(value) do
+ if v then
+ table.insert(keys, k)
+ end
+ end
+
+ table.sort(keys)
+ return table.concat(keys, ',')
+ else
+ local result = ''
+ for k, v in pairs(value) do
+ if v then
+ result = result .. k
+ end
+ end
+
+ return result
+ end
+ end,
+
+ array = function(info, value)
+ if type(value) == 'string' then
+ return value
+ end
+ if not info.allows_duplicates then
+ value = remove_duplicate_values(value)
+ end
+ return table.concat(value, ',')
+ end,
+
+ map = function(_, value)
+ if type(value) == 'string' then
+ return value
+ end
+
+ local result = {}
+ for opt_key, opt_value in pairs(value) do
+ table.insert(result, string.format('%s:%s', opt_key, opt_value))
+ end
+
+ table.sort(result)
+ return table.concat(result, ',')
+ end,
+}
+
+--- Convert a Lua value to a vimoption_T value
+local function convert_value_to_vim(name, info, value)
+ if value == nil then
+ return vim.NIL
+ end
+
+ assert_valid_value(name, value, valid_types[info.metatype])
+
+ return to_vim_value[info.metatype](info, value)
+end
+
+-- Map of OptionType to functions that take vimoption_T values and convert to Lua values.
+-- Each function takes (info, vim_value) -> lua_value
+local to_lua_value = {
+ boolean = passthrough,
+ number = passthrough,
+ string = passthrough,
+
+ array = function(info, value)
+ if type(value) == 'table' then
+ if not info.allows_duplicates then
+ value = remove_duplicate_values(value)
+ end
+
+ return value
+ end
+
+ -- Empty strings mean that there is nothing there,
+ -- so empty table should be returned.
+ if value == '' then
+ return {}
+ end
+
+ -- Handles unescaped commas in a list.
+ if string.find(value, ',,,') then
+ local left, right = unpack(vim.split(value, ',,,'))
+
+ local result = {}
+ vim.list_extend(result, vim.split(left, ','))
+ table.insert(result, ',')
+ vim.list_extend(result, vim.split(right, ','))
+
+ table.sort(result)
+
+ return result
+ end
+
+ if string.find(value, ',^,,', 1, true) then
+ local left, right = unpack(vim.split(value, ',^,,', true))
+
+ local result = {}
+ vim.list_extend(result, vim.split(left, ','))
+ table.insert(result, '^,')
+ vim.list_extend(result, vim.split(right, ','))
+
+ table.sort(result)
+
+ return result
+ end
+
+ return vim.split(value, ',')
+ end,
+
+ set = function(info, value)
+ if type(value) == 'table' then
+ return value
+ end
+
+ -- Empty strings mean that there is nothing there,
+ -- so empty table should be returned.
+ if value == '' then
+ return {}
+ end
+
+ assert(info.flaglist, 'That is the only one I know how to handle')
+
+ if info.flaglist and info.commalist then
+ local split_value = vim.split(value, ',')
+ local result = {}
+ for _, v in ipairs(split_value) do
+ result[v] = true
+ end
+
+ return result
+ else
+ local result = {}
+ for i = 1, #value do
+ result[value:sub(i, i)] = true
+ end
+
+ return result
+ end
+ end,
+
+ map = function(info, raw_value)
+ if type(raw_value) == 'table' then
+ return raw_value
+ end
+
+ assert(info.commalist, 'Only commas are supported currently')
+
+ local result = {}
+
+ local comma_split = vim.split(raw_value, ',')
+ for _, key_value_str in ipairs(comma_split) do
+ local key, value = unpack(vim.split(key_value_str, ':'))
+ key = vim.trim(key)
+
+ result[key] = value
+ end
+
+ return result
+ end,
+}
+
+--- Converts a vimoption_T style value to a Lua value
+local function convert_value_to_lua(info, option_value)
+ return to_lua_value[info.metatype](info, option_value)
+end
+
+local prepend_methods = {
+ number = function()
+ error("The '^' operator is not currently supported for")
+ end,
+
+ string = function(left, right)
+ return right .. left
+ end,
+
+ array = function(left, right)
+ for i = #right, 1, -1 do
+ table.insert(left, 1, right[i])
+ end
+
+ return left
+ end,
+
+ map = tbl_merge,
+ set = tbl_merge,
+}
+
+--- Handles the '^' operator
+local function prepend_value(info, current, new)
+ return prepend_methods[info.metatype](
+ convert_value_to_lua(info, current),
+ convert_value_to_lua(info, new)
+ )
+end
+
+local add_methods = {
+ number = function(left, right)
+ return left + right
+ end,
+
+ string = function(left, right)
+ return left .. right
+ end,
+
+ array = function(left, right)
+ for _, v in ipairs(right) do
+ table.insert(left, v)
+ end
+
+ return left
+ end,
+
+ map = tbl_merge,
+ set = tbl_merge,
+}
+
+--- Handles the '+' operator
+local function add_value(info, current, new)
+ return add_methods[info.metatype](
+ convert_value_to_lua(info, current),
+ convert_value_to_lua(info, new)
+ )
+end
+
+local function remove_one_item(t, val)
+ if vim.tbl_islist(t) then
+ local remove_index = nil
+ for i, v in ipairs(t) do
+ if v == val then
+ remove_index = i
+ end
+ end
+
+ if remove_index then
+ table.remove(t, remove_index)
+ end
+ else
+ t[val] = nil
+ end
+end
+
+local remove_methods = {
+ number = function(left, right)
+ return left - right
+ end,
+
+ string = function()
+ error('Subtraction not supported for strings.')
+ end,
+
+ array = function(left, right)
+ if type(right) == 'string' then
+ remove_one_item(left, right)
+ else
+ for _, v in ipairs(right) do
+ remove_one_item(left, v)
+ end
+ end
+
+ return left
+ end,
+
+ map = tbl_remove,
+ set = tbl_remove,
+}
+
+--- Handles the '-' operator
+local function remove_value(info, current, new)
+ return remove_methods[info.metatype](convert_value_to_lua(info, current), new)
+end
+
+local function create_option_accessor(scope)
+ local option_mt
+
+ local function make_option(name, value)
+ local info = assert(get_options_info(name), 'Not a valid option name: ' .. name)
+
+ if type(value) == 'table' and getmetatable(value) == option_mt then
+ assert(name == value._name, "must be the same value, otherwise that's weird.")
+
+ value = value._value
+ end
+
+ return setmetatable({
+ _name = name,
+ _value = value,
+ _info = info,
+ }, option_mt)
+ end
+
+ option_mt = {
+ -- To set a value, instead use:
+ -- opt[my_option] = value
+ _set = function(self)
+ local value = convert_value_to_vim(self._name, self._info, self._value)
+ api.nvim_set_option_value(self._name, value, { scope = scope })
+ end,
+
+ get = function(self)
+ return convert_value_to_lua(self._info, self._value)
+ end,
+
+ append = function(self, right)
+ self._value = add_value(self._info, self._value, right)
+ self:_set()
+ end,
+
+ __add = function(self, right)
+ return make_option(self._name, add_value(self._info, self._value, right))
+ end,
+
+ prepend = function(self, right)
+ self._value = prepend_value(self._info, self._value, right)
+ self:_set()
+ end,
+
+ __pow = function(self, right)
+ return make_option(self._name, prepend_value(self._info, self._value, right))
+ end,
+
+ remove = function(self, right)
+ self._value = remove_value(self._info, self._value, right)
+ self:_set()
+ end,
+
+ __sub = function(self, right)
+ return make_option(self._name, remove_value(self._info, self._value, right))
+ end,
+ }
+ option_mt.__index = option_mt
+
+ return setmetatable({}, {
+ __index = function(_, k)
+ -- vim.opt_global must get global value only
+ -- vim.opt_local may fall back to global value like vim.opt
+ local opts = { scope = scope == 'global' and 'global' or nil }
+ return make_option(k, api.nvim_get_option_value(k, opts))
+ end,
+
+ __newindex = function(_, k, v)
+ make_option(k, v):_set()
+ end,
+ })
+end
+
+---@addtogroup lua-vimscript
+---@brief <pre>help
+---` ` *vim.opt_local*
+--- *vim.opt_global*
+--- *vim.opt*
+---
+---
+---A special interface |vim.opt| exists for conveniently interacting with list-
+---and map-style option from Lua: It allows accessing them as Lua tables and
+---offers object-oriented method for adding and removing entries.
+---
+--- Examples: ~
+---
+--- The following methods of setting a list-style option are equivalent:
+--- In Vimscript: >vim
+--- set wildignore=*.o,*.a,__pycache__
+---<
+--- In Lua using `vim.o`: >lua
+--- vim.o.wildignore = '*.o,*.a,__pycache__'
+---<
+--- In Lua using `vim.opt`: >lua
+--- vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
+---<
+--- To replicate the behavior of |:set+=|, use: >lua
+---
+--- vim.opt.wildignore:append { "*.pyc", "node_modules" }
+---<
+--- To replicate the behavior of |:set^=|, use: >lua
+---
+--- vim.opt.wildignore:prepend { "new_first_value" }
+---<
+--- To replicate the behavior of |:set-=|, use: >lua
+---
+--- vim.opt.wildignore:remove { "node_modules" }
+---<
+--- The following methods of setting a map-style option are equivalent:
+--- In Vimscript: >vim
+--- set listchars=space:_,tab:>~
+---<
+--- In Lua using `vim.o`: >lua
+--- vim.o.listchars = 'space:_,tab:>~'
+---<
+--- In Lua using `vim.opt`: >lua
+--- vim.opt.listchars = { space = '_', tab = '>~' }
+---<
+---
+---Note that |vim.opt| returns an `Option` object, not the value of the option,
+---which is accessed through |vim.opt:get()|:
+---
+--- Examples: ~
+---
+--- The following methods of getting a list-style option are equivalent:
+--- In Vimscript: >vim
+--- echo wildignore
+---<
+--- In Lua using `vim.o`: >lua
+--- print(vim.o.wildignore)
+---<
+--- In Lua using `vim.opt`: >lua
+--- vim.print(vim.opt.wildignore:get())
+---<
+---
+---In any of the above examples, to replicate the behavior |:setlocal|, use
+---`vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use
+---`vim.opt_global`.
+---</pre>
+
+--- @diagnostic disable-next-line:unused-local used for gen_vimdoc
+local Option = {} -- luacheck: no unused
+
+--- Returns a Lua-representation of the option. Boolean, number and string
+--- values will be returned in exactly the same fashion.
+---
+--- For values that are comma-separated lists, an array will be returned with
+--- the values as entries in the array:
+---
+--- ```lua
+--- vim.cmd [[set wildignore=*.pyc,*.o]]
+---
+--- vim.print(vim.opt.wildignore:get())
+--- -- { "*.pyc", "*.o", }
+---
+--- for _, ignore_pattern in ipairs(vim.opt.wildignore:get()) do
+--- print("Will ignore:", ignore_pattern)
+--- end
+--- -- Will ignore: *.pyc
+--- -- Will ignore: *.o
+--- ```
+---
+--- For values that are comma-separated maps, a table will be returned with
+--- the names as keys and the values as entries:
+---
+--- ```lua
+--- vim.cmd [[set listchars=space:_,tab:>~]]
+---
+--- vim.print(vim.opt.listchars:get())
+--- -- { space = "_", tab = ">~", }
+---
+--- for char, representation in pairs(vim.opt.listchars:get()) do
+--- print(char, "=>", representation)
+--- end
+--- ```
+---
+--- For values that are lists of flags, a set will be returned with the flags
+--- as keys and `true` as entries.
+---
+--- ```lua
+--- vim.cmd [[set formatoptions=njtcroql]]
+---
+--- vim.print(vim.opt.formatoptions:get())
+--- -- { n = true, j = true, c = true, ... }
+---
+--- local format_opts = vim.opt.formatoptions:get()
+--- if format_opts.j then
+--- print("J is enabled!")
+--- end
+--- ```
+---
+---@return string|integer|boolean|nil value of option
+---@diagnostic disable-next-line:unused-local used for gen_vimdoc
+function Option:get() end
+
+--- Append a value to string-style options. See |:set+=|
+---
+--- These are equivalent:
+---
+--- ```lua
+--- vim.opt.formatoptions:append('j')
+--- vim.opt.formatoptions = vim.opt.formatoptions + 'j'
+--- ```
+---
+---@param value string Value to append
+---@diagnostic disable-next-line:unused-local used for gen_vimdoc
+function Option:append(value) end -- luacheck: no unused
+
+--- Prepend a value to string-style options. See |:set^=|
+---
+--- These are equivalent:
+---
+--- ```lua
+--- vim.opt.wildignore:prepend('*.o')
+--- vim.opt.wildignore = vim.opt.wildignore ^ '*.o'
+--- ```
+---
+---@param value string Value to prepend
+---@diagnostic disable-next-line:unused-local used for gen_vimdoc
+function Option:prepend(value) end -- luacheck: no unused
+
+--- Remove a value from string-style options. See |:set-=|
+---
+--- These are equivalent:
+---
+--- ```lua
+--- vim.opt.wildignore:remove('*.pyc')
+--- vim.opt.wildignore = vim.opt.wildignore - '*.pyc'
+--- ```
+---
+---@param value string Value to remove
+---@diagnostic disable-next-line:unused-local used for gen_vimdoc
+function Option:remove(value) end -- luacheck: no unused
+
+---@private
+vim.opt = create_option_accessor()
+
+---@private
+vim.opt_local = create_option_accessor('local')
+
+---@private
+vim.opt_global = create_option_accessor('global')
diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
new file mode 100644
index 0000000000..9279febddf
--- /dev/null
+++ b/runtime/lua/vim/_system.lua
@@ -0,0 +1,374 @@
+local uv = vim.uv
+
+--- @class SystemOpts
+--- @field stdin? string|string[]|true
+--- @field stdout? fun(err:string?, data: string?)|false
+--- @field stderr? fun(err:string?, data: string?)|false
+--- @field cwd? string
+--- @field env? table<string,string|number>
+--- @field clear_env? boolean
+--- @field text? boolean
+--- @field timeout? integer Timeout in ms
+--- @field detach? boolean
+
+--- @class vim.SystemCompleted
+--- @field code integer
+--- @field signal integer
+--- @field stdout? string
+--- @field stderr? string
+
+--- @class vim.SystemState
+--- @field handle? uv.uv_process_t
+--- @field timer? uv.uv_timer_t
+--- @field pid? integer
+--- @field timeout? integer
+--- @field done? boolean|'timeout'
+--- @field stdin? uv.uv_stream_t
+--- @field stdout? uv.uv_stream_t
+--- @field stderr? uv.uv_stream_t
+--- @field stdout_data? string[]
+--- @field stderr_data? string[]
+--- @field result? vim.SystemCompleted
+
+--- @enum vim.SystemSig
+local SIG = {
+ HUP = 1, -- Hangup
+ INT = 2, -- Interrupt from keyboard
+ KILL = 9, -- Kill signal
+ TERM = 15, -- Termination signal
+ -- STOP = 17,19,23 -- Stop the process
+}
+
+---@param handle uv.uv_handle_t?
+local function close_handle(handle)
+ if handle and not handle:is_closing() then
+ handle:close()
+ end
+end
+
+---@param state vim.SystemState
+local function close_handles(state)
+ close_handle(state.handle)
+ close_handle(state.stdin)
+ close_handle(state.stdout)
+ close_handle(state.stderr)
+ close_handle(state.timer)
+end
+
+--- @class vim.SystemObj
+--- @field pid integer
+--- @field private _state vim.SystemState
+--- @field wait fun(self: vim.SystemObj, timeout?: integer): vim.SystemCompleted
+--- @field kill fun(self: vim.SystemObj, signal: integer|string)
+--- @field write fun(self: vim.SystemObj, data?: string|string[])
+--- @field is_closing fun(self: vim.SystemObj): boolean?
+local SystemObj = {}
+
+--- @param state vim.SystemState
+--- @return vim.SystemObj
+local function new_systemobj(state)
+ return setmetatable({
+ pid = state.pid,
+ _state = state,
+ }, { __index = SystemObj })
+end
+
+--- @param signal integer|string
+function SystemObj:kill(signal)
+ self._state.handle:kill(signal)
+end
+
+--- @package
+--- @param signal? vim.SystemSig
+function SystemObj:_timeout(signal)
+ self._state.done = 'timeout'
+ self:kill(signal or SIG.TERM)
+end
+
+local MAX_TIMEOUT = 2 ^ 31
+
+--- @param timeout? integer
+--- @return vim.SystemCompleted
+function SystemObj:wait(timeout)
+ local state = self._state
+
+ local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
+ return state.result ~= nil
+ end)
+
+ if not done then
+ -- Send sigkill since this cannot be caught
+ self:_timeout(SIG.KILL)
+ vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
+ return state.result ~= nil
+ end)
+ end
+
+ return state.result
+end
+
+--- @param data string[]|string|nil
+function SystemObj:write(data)
+ local stdin = self._state.stdin
+
+ if not stdin then
+ error('stdin has not been opened on this object')
+ end
+
+ if type(data) == 'table' then
+ for _, v in ipairs(data) do
+ stdin:write(v)
+ stdin:write('\n')
+ end
+ elseif type(data) == 'string' then
+ stdin:write(data)
+ elseif data == nil then
+ -- Shutdown the write side of the duplex stream and then close the pipe.
+ -- Note shutdown will wait for all the pending write requests to complete
+ -- TODO(lewis6991): apparently shutdown doesn't behave this way.
+ -- (https://github.com/neovim/neovim/pull/17620#discussion_r820775616)
+ stdin:write('', function()
+ stdin:shutdown(function()
+ if stdin then
+ stdin:close()
+ end
+ end)
+ end)
+ end
+end
+
+--- @return boolean
+function SystemObj:is_closing()
+ local handle = self._state.handle
+ return handle == nil or handle:is_closing()
+end
+
+---@param output fun(err:string?, data: string?)|false
+---@return uv.uv_stream_t?
+---@return fun(err:string?, data: string?)? Handler
+local function setup_output(output)
+ if output == nil then
+ return assert(uv.new_pipe(false)), nil
+ end
+
+ if type(output) == 'function' then
+ return assert(uv.new_pipe(false)), output
+ end
+
+ assert(output == false)
+ return nil, nil
+end
+
+---@param input string|string[]|true|nil
+---@return uv.uv_stream_t?
+---@return string|string[]?
+local function setup_input(input)
+ if not input then
+ return
+ end
+
+ local towrite --- @type string|string[]?
+ if type(input) == 'string' or type(input) == 'table' then
+ towrite = input
+ end
+
+ return assert(uv.new_pipe(false)), towrite
+end
+
+--- @return table<string,string>
+local function base_env()
+ local env = vim.fn.environ() --- @type table<string,string>
+ env['NVIM'] = vim.v.servername
+ env['NVIM_LISTEN_ADDRESS'] = nil
+ return env
+end
+
+--- uv.spawn will completely overwrite the environment
+--- when we just want to modify the existing one, so
+--- make sure to prepopulate it with the current env.
+--- @param env? table<string,string|number>
+--- @param clear_env? boolean
+--- @return string[]?
+local function setup_env(env, clear_env)
+ if clear_env then
+ return env
+ end
+
+ --- @type table<string,string|number>
+ env = vim.tbl_extend('force', base_env(), env or {})
+
+ local renv = {} --- @type string[]
+ for k, v in pairs(env) do
+ renv[#renv + 1] = string.format('%s=%s', k, tostring(v))
+ end
+
+ return renv
+end
+
+--- @param stream uv.uv_stream_t
+--- @param text? boolean
+--- @param bucket string[]
+--- @return fun(err: string?, data: string?)
+local function default_handler(stream, text, bucket)
+ return function(err, data)
+ if err then
+ error(err)
+ end
+ if data ~= nil then
+ if text then
+ bucket[#bucket + 1] = data:gsub('\r\n', '\n')
+ else
+ bucket[#bucket + 1] = data
+ end
+ else
+ stream:read_stop()
+ stream:close()
+ end
+ end
+end
+
+local M = {}
+
+--- @param cmd string
+--- @param opts uv.spawn.options
+--- @param on_exit fun(code: integer, signal: integer)
+--- @param on_error fun()
+--- @return uv.uv_process_t, integer
+local function spawn(cmd, opts, on_exit, on_error)
+ local handle, pid_or_err = uv.spawn(cmd, opts, on_exit)
+ if not handle then
+ on_error()
+ error(pid_or_err)
+ end
+ return handle, pid_or_err --[[@as integer]]
+end
+
+---@param timeout integer
+---@param cb fun()
+---@return uv.uv_timer_t
+local function timer_oneshot(timeout, cb)
+ local timer = assert(uv.new_timer())
+ timer:start(timeout, 0, function()
+ timer:stop()
+ timer:close()
+ cb()
+ end)
+ return timer
+end
+
+--- @param state vim.SystemState
+--- @param code integer
+--- @param signal integer
+--- @param on_exit fun(result: vim.SystemCompleted)?
+local function _on_exit(state, code, signal, on_exit)
+ close_handles(state)
+
+ local check = assert(uv.new_check())
+ check:start(function()
+ for _, pipe in pairs({ state.stdin, state.stdout, state.stderr }) do
+ if not pipe:is_closing() then
+ return
+ end
+ end
+ check:stop()
+ check:close()
+
+ if state.done == nil then
+ state.done = true
+ end
+
+ if (code == 0 or code == 1) and state.done == 'timeout' then
+ -- Unix: code == 0
+ -- Windows: code == 1
+ code = 124
+ end
+
+ local stdout_data = state.stdout_data
+ local stderr_data = state.stderr_data
+
+ state.result = {
+ code = code,
+ signal = signal,
+ stdout = stdout_data and table.concat(stdout_data) or nil,
+ stderr = stderr_data and table.concat(stderr_data) or nil,
+ }
+
+ if on_exit then
+ on_exit(state.result)
+ end
+ end)
+end
+
+--- Run a system command
+---
+--- @param cmd string[]
+--- @param opts? SystemOpts
+--- @param on_exit? fun(out: vim.SystemCompleted)
+--- @return vim.SystemObj
+function M.run(cmd, opts, on_exit)
+ vim.validate({
+ cmd = { cmd, 'table' },
+ opts = { opts, 'table', true },
+ on_exit = { on_exit, 'function', true },
+ })
+
+ opts = opts or {}
+
+ local stdout, stdout_handler = setup_output(opts.stdout)
+ local stderr, stderr_handler = setup_output(opts.stderr)
+ local stdin, towrite = setup_input(opts.stdin)
+
+ --- @type vim.SystemState
+ local state = {
+ done = false,
+ cmd = cmd,
+ timeout = opts.timeout,
+ stdin = stdin,
+ stdout = stdout,
+ stderr = stderr,
+ }
+
+ --- @diagnostic disable-next-line:missing-fields
+ state.handle, state.pid = spawn(cmd[1], {
+ args = vim.list_slice(cmd, 2),
+ stdio = { stdin, stdout, stderr },
+ cwd = opts.cwd,
+ --- @diagnostic disable-next-line:assign-type-mismatch
+ env = setup_env(opts.env, opts.clear_env),
+ detached = opts.detach,
+ hide = true,
+ }, function(code, signal)
+ _on_exit(state, code, signal, on_exit)
+ end, function()
+ close_handles(state)
+ end)
+
+ if stdout then
+ state.stdout_data = {}
+ stdout:read_start(stdout_handler or default_handler(stdout, opts.text, state.stdout_data))
+ end
+
+ if stderr then
+ state.stderr_data = {}
+ stderr:read_start(stderr_handler or default_handler(stderr, opts.text, state.stderr_data))
+ end
+
+ local obj = new_systemobj(state)
+
+ if towrite then
+ obj:write(towrite)
+ obj:write(nil) -- close the stream
+ end
+
+ if opts.timeout then
+ state.timer = timer_oneshot(opts.timeout, function()
+ if state.handle and state.handle:is_active() then
+ obj:_timeout()
+ end
+ end)
+ end
+
+ return obj
+end
+
+return M
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
new file mode 100644
index 0000000000..43fce3bf7f
--- /dev/null
+++ b/runtime/lua/vim/_watch.lua
@@ -0,0 +1,223 @@
+local M = {}
+local uv = vim.uv
+
+---@enum vim._watch.FileChangeType
+local FileChangeType = {
+ Created = 1,
+ Changed = 2,
+ Deleted = 3,
+}
+
+--- Enumeration describing the types of events watchers will emit.
+M.FileChangeType = vim.tbl_add_reverse_lookup(FileChangeType)
+
+--- Joins filepath elements by static '/' separator
+---
+---@param ... (string) The path elements.
+---@return string
+local function filepath_join(...)
+ return table.concat({ ... }, '/')
+end
+
+--- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle
+---
+---@param handle (uv.uv_fs_event_t|uv.uv_fs_poll_t) The handle to stop
+local function stop(handle)
+ local _, stop_err = handle:stop()
+ assert(not stop_err, stop_err)
+ local is_closing, close_err = handle:is_closing()
+ assert(not close_err, close_err)
+ if not is_closing then
+ handle:close()
+ end
+end
+
+--- Initializes and starts a |uv_fs_event_t|
+---
+---@param path (string) The path to watch
+---@param opts (table|nil) Additional options
+--- - uvflags (table|nil)
+--- Same flags as accepted by |uv.fs_event_start()|
+---@param callback (function) The function called when new events
+---@return (function) Stops the watcher
+function M.watch(path, opts, callback)
+ vim.validate({
+ path = { path, 'string', false },
+ opts = { opts, 'table', true },
+ callback = { callback, 'function', false },
+ })
+
+ path = vim.fs.normalize(path)
+ local uvflags = opts and opts.uvflags or {}
+ local handle, new_err = vim.uv.new_fs_event()
+ assert(not new_err, new_err)
+ local _, start_err = handle:start(path, uvflags, function(err, filename, events)
+ assert(not err, err)
+ local fullpath = path
+ if filename then
+ filename = filename:gsub('\\', '/')
+ fullpath = filepath_join(fullpath, filename)
+ end
+ local change_type = events.change and M.FileChangeType.Changed or 0
+ if events.rename then
+ local _, staterr, staterrname = vim.uv.fs_stat(fullpath)
+ if staterrname == 'ENOENT' then
+ change_type = M.FileChangeType.Deleted
+ else
+ assert(not staterr, staterr)
+ change_type = M.FileChangeType.Created
+ end
+ end
+ callback(fullpath, change_type)
+ end)
+ assert(not start_err, start_err)
+ return function()
+ stop(handle)
+ end
+end
+
+--- @class watch.PollOpts
+--- @field debounce? integer
+--- @field include_pattern? vim.lpeg.Pattern
+--- @field exclude_pattern? vim.lpeg.Pattern
+
+---@param path string
+---@param opts watch.PollOpts
+---@param callback function Called on new events
+---@return function cancel stops the watcher
+local function recurse_watch(path, opts, callback)
+ opts = opts or {}
+ local debounce = opts.debounce or 500
+ local uvflags = {}
+ ---@type table<string, uv.uv_fs_event_t> handle by fullpath
+ local handles = {}
+
+ local timer = assert(uv.new_timer())
+
+ ---@type table[]
+ local changesets = {}
+
+ local function is_included(filepath)
+ return opts.include_pattern and opts.include_pattern:match(filepath)
+ end
+ local function is_excluded(filepath)
+ return opts.exclude_pattern and opts.exclude_pattern:match(filepath)
+ end
+
+ local process_changes = function()
+ assert(false, "Replaced later. I'm only here as forward reference")
+ end
+
+ local function create_on_change(filepath)
+ return function(err, filename, events)
+ assert(not err, err)
+ local fullpath = vim.fs.joinpath(filepath, filename)
+ if is_included(fullpath) and not is_excluded(filepath) then
+ table.insert(changesets, {
+ fullpath = fullpath,
+ events = events,
+ })
+ timer:start(debounce, 0, process_changes)
+ end
+ end
+ end
+
+ process_changes = function()
+ ---@type table<string, table[]>
+ local filechanges = vim.defaulttable()
+ for i, change in ipairs(changesets) do
+ changesets[i] = nil
+ if is_included(change.fullpath) and not is_excluded(change.fullpath) then
+ table.insert(filechanges[change.fullpath], change.events)
+ end
+ end
+ for fullpath, events_list in pairs(filechanges) do
+ uv.fs_stat(fullpath, function(_, stat)
+ ---@type vim._watch.FileChangeType
+ local change_type
+ if stat then
+ change_type = FileChangeType.Created
+ for _, event in ipairs(events_list) do
+ if event.change then
+ change_type = FileChangeType.Changed
+ end
+ end
+ if stat.type == 'directory' then
+ local handle = handles[fullpath]
+ if not handle then
+ handle = assert(uv.new_fs_event())
+ handles[fullpath] = handle
+ handle:start(fullpath, uvflags, create_on_change(fullpath))
+ end
+ end
+ else
+ local handle = handles[fullpath]
+ if handle then
+ if not handle:is_closing() then
+ handle:close()
+ end
+ handles[fullpath] = nil
+ end
+ change_type = FileChangeType.Deleted
+ end
+ callback(fullpath, change_type)
+ end)
+ end
+ end
+ local root_handle = assert(uv.new_fs_event())
+ handles[path] = root_handle
+ root_handle:start(path, uvflags, create_on_change(path))
+
+ --- "640K ought to be enough for anyone"
+ --- Who has folders this deep?
+ local max_depth = 100
+
+ for name, type in vim.fs.dir(path, { depth = max_depth }) do
+ local filepath = vim.fs.joinpath(path, name)
+ if type == 'directory' and not is_excluded(filepath) then
+ local handle = assert(uv.new_fs_event())
+ handles[filepath] = handle
+ handle:start(filepath, uvflags, create_on_change(filepath))
+ end
+ end
+ local function cancel()
+ for fullpath, handle in pairs(handles) do
+ if not handle:is_closing() then
+ handle:close()
+ end
+ handles[fullpath] = nil
+ end
+ timer:stop()
+ timer:close()
+ end
+ return cancel
+end
+
+--- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the
+--- directory at path.
+---
+---@param path (string) The path to watch. Must refer to a directory.
+---@param opts (table|nil) Additional options
+--- - debounce (number|nil)
+--- Time events are debounced in ms. Defaults to 500
+--- - include_pattern (LPeg pattern|nil)
+--- An |lpeg| pattern. Only changes to files whose full paths match the pattern
+--- will be reported. Only matches against non-directoriess, all directories will
+--- be watched for new potentially-matching files. exclude_pattern can be used to
+--- filter out directories. When nil, matches any file name.
+--- - exclude_pattern (LPeg pattern|nil)
+--- An |lpeg| pattern. Only changes to files and directories whose full path does
+--- not match the pattern will be reported. Matches against both files and
+--- directories. When nil, matches nothing.
+---@param callback (function) The function called when new events
+---@return function Stops the watcher
+function M.poll(path, opts, callback)
+ vim.validate({
+ path = { path, 'string', false },
+ opts = { opts, 'table', true },
+ callback = { callback, 'function', false },
+ })
+ return recurse_watch(path, opts, callback)
+end
+
+return M
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 6fd000a029..99448982b4 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -72,7 +72,6 @@ local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
local all_namespaces = {}
----@private
local function to_severity(severity)
if type(severity) == 'string' then
return assert(
@@ -83,7 +82,6 @@ local function to_severity(severity)
return severity
end
----@private
local function filter_by_severity(severity, diagnostics)
if not severity then
return diagnostics
@@ -96,15 +94,25 @@ local function filter_by_severity(severity, diagnostics)
end, diagnostics)
end
- local min_severity = to_severity(severity.min) or M.severity.HINT
- local max_severity = to_severity(severity.max) or M.severity.ERROR
+ if severity.min or severity.max then
+ local min_severity = to_severity(severity.min) or M.severity.HINT
+ local max_severity = to_severity(severity.max) or M.severity.ERROR
+
+ return vim.tbl_filter(function(t)
+ return t.severity <= min_severity and t.severity >= max_severity
+ end, diagnostics)
+ end
+
+ local severities = {}
+ for _, s in ipairs(severity) do
+ severities[to_severity(s)] = true
+ end
return vim.tbl_filter(function(t)
- return t.severity <= min_severity and t.severity >= max_severity
+ return severities[t.severity]
end, diagnostics)
end
----@private
local function count_sources(bufnr)
local seen = {}
local count = 0
@@ -119,7 +127,6 @@ local function count_sources(bufnr)
return count
end
----@private
local function prefix_source(diagnostics)
return vim.tbl_map(function(d)
if not d.source then
@@ -132,7 +139,6 @@ local function prefix_source(diagnostics)
end, diagnostics)
end
----@private
local function reformat_diagnostics(format, diagnostics)
vim.validate({
format = { format, 'f' },
@@ -146,7 +152,6 @@ local function reformat_diagnostics(format, diagnostics)
return formatted
end
----@private
local function enabled_value(option, namespace)
local ns = namespace and M.get_namespace(namespace) or {}
if ns.opts and type(ns.opts[option]) == 'table' then
@@ -160,7 +165,6 @@ local function enabled_value(option, namespace)
return {}
end
----@private
local function resolve_optional_value(option, value, namespace, bufnr)
if not value then
return false
@@ -180,7 +184,6 @@ local function resolve_optional_value(option, value, namespace, bufnr)
end
end
----@private
local function get_resolved_options(opts, namespace, bufnr)
local ns = namespace and M.get_namespace(namespace) or {}
-- Do not use tbl_deep_extend so that an empty table can be used to reset to default values
@@ -202,7 +205,6 @@ local diagnostic_severities = {
}
-- Make a map from DiagnosticSeverity -> Highlight Name
----@private
local function make_highlight_map(base_name)
local result = {}
for k in pairs(diagnostic_severities) do
@@ -243,7 +245,6 @@ local define_default_signs = (function()
end
end)()
----@private
local function get_bufnr(bufnr)
if not bufnr or bufnr == 0 then
return api.nvim_get_current_buf()
@@ -251,7 +252,6 @@ local function get_bufnr(bufnr)
return bufnr
end
----@private
local function diagnostic_lines(diagnostics)
if not diagnostics then
return {}
@@ -269,7 +269,6 @@ local function diagnostic_lines(diagnostics)
return diagnostics_by_line
end
----@private
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
assert(diagnostic.lnum, 'Diagnostic line number is required')
@@ -284,7 +283,6 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics)
diagnostic_cache[bufnr][namespace] = diagnostics
end
----@private
local function restore_extmarks(bufnr, last)
for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
@@ -306,7 +304,6 @@ local function restore_extmarks(bufnr, last)
end
end
----@private
local function save_extmarks(namespace, bufnr)
bufnr = get_bufnr(bufnr)
if not diagnostic_attached_buffers[bufnr] then
@@ -326,13 +323,11 @@ end
local registered_autocmds = {}
----@private
local function make_augroup_key(namespace, bufnr)
local ns = M.get_namespace(namespace)
return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name)
end
----@private
local function execute_scheduled_display(namespace, bufnr)
local args = bufs_waiting_to_update[bufnr][namespace]
if not args then
@@ -348,7 +343,6 @@ end
--- Table of autocmd events to fire the update for displaying new diagnostic information
local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
----@private
local function schedule_display(namespace, bufnr, args)
bufs_waiting_to_update[bufnr][namespace] = args
@@ -367,7 +361,6 @@ local function schedule_display(namespace, bufnr, args)
end
end
----@private
local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr)
@@ -377,7 +370,6 @@ local function clear_scheduled_display(namespace, bufnr)
end
end
----@private
local function get_diagnostics(bufnr, opts, clamp)
opts = opts or {}
@@ -392,9 +384,9 @@ local function get_diagnostics(bufnr, opts, clamp)
end,
})
- ---@private
local function add(b, d)
if not opts.lnum or d.lnum == opts.lnum then
+ d = vim.deepcopy(d)
if clamp and api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1
if
@@ -405,7 +397,6 @@ local function get_diagnostics(bufnr, opts, clamp)
or d.col < 0
or d.end_col < 0
then
- d = vim.deepcopy(d)
d.lnum = math.max(math.min(d.lnum, line_count), 0)
d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
d.col = math.max(d.col, 0)
@@ -416,7 +407,6 @@ local function get_diagnostics(bufnr, opts, clamp)
end
end
- ---@private
local function add_all_diags(buf, diags)
for _, diagnostic in pairs(diags) do
add(buf, diagnostic)
@@ -450,7 +440,6 @@ local function get_diagnostics(bufnr, opts, clamp)
return diagnostics
end
----@private
local function set_list(loclist, opts)
opts = opts or {}
local open = vim.F.if_nil(opts.open, true)
@@ -474,7 +463,6 @@ local function set_list(loclist, opts)
end
end
----@private
local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
position[1] = position[1] - 1
bufnr = get_bufnr(bufnr)
@@ -483,6 +471,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
local diagnostics =
get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true)
local line_diagnostics = diagnostic_lines(diagnostics)
+
for i = 0, line_count do
local offset = i * (search_forward and 1 or -1)
local lnum = position[1] + offset
@@ -524,7 +513,6 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
end
end
----@private
local function diagnostic_move_pos(opts, pos)
opts = opts or {}
@@ -565,14 +553,16 @@ end
--- followed by namespace configuration, and finally global configuration.
---
--- For example, if a user enables virtual text globally with
---- <pre>lua
---- vim.diagnostic.config({ virtual_text = true })
---- </pre>
+---
+--- ```lua
+--- vim.diagnostic.config({ virtual_text = true })
+--- ```
---
--- and a diagnostic producer sets diagnostics with
---- <pre>lua
---- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
---- </pre>
+---
+--- ```lua
+--- vim.diagnostic.set(ns, 0, diagnostics, { virtual_text = false })
+--- ```
---
--- then virtual text will not be enabled for those diagnostics.
---
@@ -585,11 +575,13 @@ end
---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a
--- configuration table with the following keys:
--- - underline: (default true) Use underline for diagnostics. Options:
---- * severity: Only underline diagnostics matching the given severity
---- |diagnostic-severity|
+--- * severity: Only underline diagnostics matching the given
+--- severity |diagnostic-severity|
--- - virtual_text: (default true) Use virtual text for diagnostics. If multiple diagnostics
--- are set for a namespace, one prefix per diagnostic + the last diagnostic
---- message are shown.
+--- message are shown. In addition to the options listed below, the
+--- "virt_text" options of |nvim_buf_set_extmark()| may also be used here
+--- (e.g. "virt_text_pos" and "hl_mode").
--- Options:
--- * severity: Only show virtual text for diagnostics matching the given
--- severity |diagnostic-severity|
@@ -599,7 +591,12 @@ end
--- means to always show the diagnostic source.
--- * spacing: (number) Amount of empty spaces inserted at the beginning
--- of the virtual text.
---- * prefix: (string) Prepend diagnostic message with prefix.
+--- * prefix: (string or function) prepend diagnostic message with prefix.
+--- If a function, it must have the signature (diagnostic, i, total)
+--- -> string, where {diagnostic} is of type |diagnostic-structure|,
+--- {i} is the index of the diagnostic being evaluated, and {total}
+--- is the total number of diagnostics for the line. This can be
+--- used to render diagnostic symbols or error codes.
--- * suffix: (string or function) Append diagnostic message with suffix.
--- If a function, it must have the signature (diagnostic) ->
--- string, where {diagnostic} is of type |diagnostic-structure|.
@@ -616,8 +613,8 @@ end
--- end
--- </pre>
--- - signs: (default true) Use signs for diagnostics. Options:
---- * severity: Only show signs for diagnostics matching the given severity
---- |diagnostic-severity|
+--- * severity: Only show signs for diagnostics matching the given
+--- severity |diagnostic-severity|
--- * priority: (number, default 10) Base priority to use for signs. When
--- {severity_sort} is used, the priority of a sign is adjusted based on
--- its severity. Otherwise, all signs use the same priority.
@@ -630,7 +627,7 @@ end
--- Options:
--- * reverse: (boolean) Reverse sort order
---
----@param namespace number|nil Update the options for the given namespace. When omitted, update the
+---@param namespace integer|nil Update the options for the given namespace. When omitted, update the
--- global diagnostic options.
function M.config(opts, namespace)
vim.validate({
@@ -657,16 +654,14 @@ function M.config(opts, namespace)
if namespace then
for bufnr, v in pairs(diagnostic_cache) do
- if api.nvim_buf_is_loaded(bufnr) and v[namespace] then
+ if v[namespace] then
M.show(namespace, bufnr)
end
end
else
for bufnr, v in pairs(diagnostic_cache) do
- if api.nvim_buf_is_loaded(bufnr) then
- for ns in pairs(v) do
- M.show(ns, bufnr)
- end
+ for ns in pairs(v) do
+ M.show(ns, bufnr)
end
end
end
@@ -674,8 +669,8 @@ end
--- Set diagnostics for the given namespace and buffer.
---
----@param namespace number The diagnostic namespace
----@param bufnr number Buffer number
+---@param namespace integer The diagnostic namespace
+---@param bufnr integer Buffer number
---@param diagnostics table A list of diagnostic items |diagnostic-structure|
---@param opts table|nil Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
@@ -698,9 +693,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
set_diagnostic_cache(namespace, bufnr, diagnostics)
end
- if api.nvim_buf_is_loaded(bufnr) then
- M.show(namespace, bufnr, nil, opts)
- end
+ M.show(namespace, bufnr, nil, opts)
api.nvim_exec_autocmds('DiagnosticChanged', {
modeline = false,
@@ -711,7 +704,7 @@ end
--- Get namespace metadata.
---
----@param namespace number Diagnostic namespace
+---@param namespace integer Diagnostic namespace
---@return table Namespace metadata
function M.get_namespace(namespace)
vim.validate({ namespace = { namespace, 'n' } })
@@ -743,26 +736,29 @@ function M.get_namespaces()
end
---@class Diagnostic
----@field buffer number
----@field lnum number 0-indexed
----@field end_lnum nil|number 0-indexed
----@field col number 0-indexed
----@field end_col nil|number 0-indexed
----@field severity DiagnosticSeverity
+---@field bufnr? integer
+---@field lnum integer 0-indexed
+---@field end_lnum? integer 0-indexed
+---@field col integer 0-indexed
+---@field end_col? integer 0-indexed
+---@field severity? DiagnosticSeverity
---@field message string
----@field source nil|string
----@field code nil|string
----@field user_data nil|any arbitrary data plugins can add
+---@field source? string
+---@field code? string
+---@field _tags? { deprecated: boolean, unnecessary: boolean}
+---@field user_data? any arbitrary data plugins can add
--- Get current diagnostics.
---
----@param bufnr number|nil Buffer number to get diagnostics from. Use 0 for
+--- Modifying diagnostics in the returned table has no effect. To set diagnostics in a buffer, use |vim.diagnostic.set()|.
+---
+---@param bufnr integer|nil Buffer number to get diagnostics from. Use 0 for
--- current buffer or nil for all buffers.
---@param opts table|nil A table with the following keys:
--- - namespace: (number) Limit diagnostics to the given namespace.
--- - lnum: (number) Limit diagnostics to the given line number.
--- - severity: See |diagnostic-severity|.
----@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|.
+---@return Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present.
function M.get(bufnr, opts)
vim.validate({
bufnr = { bufnr, 'n', true },
@@ -838,13 +834,13 @@ end
---
---@param opts table|nil Configuration table with the following keys:
--- - namespace: (number) Only consider diagnostics from the given namespace.
---- - cursor_position: (cursor position) Cursor position as a (row, col) tuple. See
---- |nvim_win_get_cursor()|. Defaults to the current cursor position.
+--- - cursor_position: (cursor position) Cursor position as a (row, col) tuple.
+--- See |nvim_win_get_cursor()|. Defaults to the current cursor position.
--- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'.
--- - severity: See |diagnostic-severity|.
--- - float: (boolean or table, default true) If "true", call |vim.diagnostic.open_float()|
---- after moving. If a table, pass the table as the {opts} parameter to
---- |vim.diagnostic.open_float()|. Unless overridden, the float will show
+--- after moving. If a table, pass the table as the {opts} parameter
+--- to |vim.diagnostic.open_float()|. Unless overridden, the float will show
--- diagnostics at the new cursor position (as if "cursor" were passed to
--- the "scope" option).
--- - win_id: (number, default 0) Window ID
@@ -930,6 +926,10 @@ M.handlers.underline = {
bufnr = get_bufnr(bufnr)
opts = opts or {}
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
if opts.underline and opts.underline.severity then
diagnostics = filter_by_severity(opts.underline.severity, diagnostics)
end
@@ -948,6 +948,16 @@ M.handlers.underline = {
higroup = underline_highlight_map.Error
end
+ if diagnostic._tags then
+ -- TODO(lewis6991): we should be able to stack these.
+ if diagnostic._tags.unnecessary then
+ higroup = 'DiagnosticUnnecessary'
+ end
+ if diagnostic._tags.deprecated then
+ higroup = 'DiagnosticDeprecated'
+ end
+ end
+
vim.highlight.range(
bufnr,
underline_ns,
@@ -986,6 +996,10 @@ M.handlers.virtual_text = {
bufnr = get_bufnr(bufnr)
opts = opts or {}
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
local severity
if opts.virtual_text then
if opts.virtual_text.format then
@@ -1017,8 +1031,11 @@ M.handlers.virtual_text = {
if virt_texts then
api.nvim_buf_set_extmark(bufnr, virt_text_ns, line, 0, {
- hl_mode = 'combine',
+ hl_mode = opts.virtual_text.hl_mode or 'combine',
virt_text = virt_texts,
+ virt_text_pos = opts.virtual_text.virt_text_pos,
+ virt_text_hide = opts.virtual_text.virt_text_hide,
+ virt_text_win_col = opts.virtual_text.virt_text_win_col,
})
end
end
@@ -1054,8 +1071,15 @@ function M._get_virt_text_chunks(line_diags, opts)
-- Create a little more space between virtual text and contents
local virt_texts = { { string.rep(' ', spacing) } }
- for i = 1, #line_diags - 1 do
- table.insert(virt_texts, { prefix, virtual_text_highlight_map[line_diags[i].severity] })
+ for i = 1, #line_diags do
+ local resolved_prefix = prefix
+ if type(prefix) == 'function' then
+ resolved_prefix = prefix(line_diags[i], i, #line_diags) or ''
+ end
+ table.insert(
+ virt_texts,
+ { resolved_prefix, virtual_text_highlight_map[line_diags[i].severity] }
+ )
end
local last = line_diags[#line_diags]
@@ -1066,7 +1090,7 @@ function M._get_virt_text_chunks(line_diags, opts)
suffix = suffix(last) or ''
end
table.insert(virt_texts, {
- string.format('%s %s%s', prefix, last.message:gsub('\r', ''):gsub('\n', ' '), suffix),
+ string.format(' %s%s', last.message:gsub('\r', ''):gsub('\n', ' '), suffix),
virtual_text_highlight_map[last.severity],
})
@@ -1083,9 +1107,9 @@ end
--- To hide diagnostics and prevent them from re-displaying, use
--- |vim.diagnostic.disable()|.
---
----@param namespace number|nil Diagnostic namespace. When omitted, hide
+---@param namespace integer|nil Diagnostic namespace. When omitted, hide
--- diagnostics from all namespaces.
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
--- omitted, hide diagnostics in all buffers.
function M.hide(namespace, bufnr)
vim.validate({
@@ -1108,8 +1132,8 @@ end
--- Check whether diagnostics are disabled in a given buffer.
---
----@param bufnr number|nil Buffer number, or 0 for current buffer.
----@param namespace number|nil Diagnostic namespace. When omitted, checks if
+---@param bufnr integer|nil Buffer number, or 0 for current buffer.
+---@param namespace integer|nil Diagnostic namespace. When omitted, checks if
--- all diagnostics are disabled in {bufnr}.
--- Otherwise, only checks if diagnostics from
--- {namespace} are disabled.
@@ -1129,9 +1153,9 @@ end
--- Display diagnostics for the given namespace and buffer.
---
----@param namespace number|nil Diagnostic namespace. When omitted, show
+---@param namespace integer|nil Diagnostic namespace. When omitted, show
--- diagnostics from all namespaces.
----@param bufnr number|nil Buffer number, or 0 for current buffer. When omitted, show
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When omitted, show
--- diagnostics in all buffers.
---@param diagnostics table|nil The diagnostics to display. When omitted, use the
--- saved diagnostics for the given namespace and
@@ -1215,8 +1239,8 @@ end
--- Show diagnostics in a floating window.
---
----@param opts table|nil Configuration table with the same keys as
---- |vim.lsp.util.open_floating_preview()| in addition to the following:
+---@param opts table|nil Configuration table with the same keys
+--- as |vim.lsp.util.open_floating_preview()| in addition to the following:
--- - bufnr: (number) Buffer number to show diagnostics from.
--- Defaults to the current buffer.
--- - namespace: (number) Limit diagnostics to the given namespace
@@ -1229,16 +1253,15 @@ end
--- otherwise, a (row, col) tuple.
--- - severity_sort: (default false) Sort diagnostics by severity. Overrides the setting
--- from |vim.diagnostic.config()|.
---- - severity: See |diagnostic-severity|. Overrides the setting from
---- |vim.diagnostic.config()|.
+--- - severity: See |diagnostic-severity|. Overrides the setting
+--- from |vim.diagnostic.config()|.
--- - header: (string or table) String to use as the header for the floating window. If a
--- table, it is interpreted as a [text, hl_group] tuple. Overrides the setting
--- from |vim.diagnostic.config()|.
--- - source: (boolean or string) Include the diagnostic source in the message.
--- Use "if_many" to only show sources if there is more than one source of
--- diagnostics in the buffer. Otherwise, any truthy value means to always show
---- the diagnostic source. Overrides the setting from
---- |vim.diagnostic.config()|.
+--- the diagnostic source. Overrides the setting from |vim.diagnostic.config()|.
--- - format: (function) A function that takes a diagnostic as input and returns a
--- string. The return value is the text used to display the diagnostic.
--- Overrides the setting from |vim.diagnostic.config()|.
@@ -1256,7 +1279,7 @@ end
--- Overrides the setting from |vim.diagnostic.config()|.
--- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of
--- prepending it. Overrides the setting from |vim.diagnostic.config()|.
----@return number|nil, number|nil: ({float_bufnr}, {win_id})
+---@return integer|nil, integer|nil: ({float_bufnr}, {win_id})
function M.open_float(opts, ...)
-- Support old (bufnr, opts) signature
local bufnr
@@ -1463,9 +1486,9 @@ end
--- simply remove diagnostic decorations in a way that they can be
--- re-displayed, use |vim.diagnostic.hide()|.
---
----@param namespace number|nil Diagnostic namespace. When omitted, remove
+---@param namespace integer|nil Diagnostic namespace. When omitted, remove
--- diagnostics from all namespaces.
----@param bufnr number|nil Remove diagnostics for the given buffer. When omitted,
+---@param bufnr integer|nil Remove diagnostics for the given buffer. When omitted,
--- diagnostics are removed for all buffers.
function M.reset(namespace, bufnr)
vim.validate({
@@ -1518,9 +1541,9 @@ end
--- Disable diagnostics in the given buffer.
---
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
--- omitted, disable diagnostics in all buffers.
----@param namespace number|nil Only disable diagnostics for the given namespace.
+---@param namespace integer|nil Only disable diagnostics for the given namespace.
function M.disable(bufnr, namespace)
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
@@ -1555,9 +1578,9 @@ end
--- Enable diagnostics in the given buffer.
---
----@param bufnr number|nil Buffer number, or 0 for current buffer. When
+---@param bufnr integer|nil Buffer number, or 0 for current buffer. When
--- omitted, enable diagnostics in all buffers.
----@param namespace number|nil Only enable diagnostics for the given namespace.
+---@param namespace integer|nil Only enable diagnostics for the given namespace.
function M.enable(bufnr, namespace)
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
@@ -1586,18 +1609,20 @@ end
--- Parse a diagnostic from a string.
---
--- For example, consider a line of output from a linter:
---- <pre>
+---
+--- ```
--- WARNING filename:27:3: Variable 'foo' does not exist
---- </pre>
+--- ```
---
--- This can be parsed into a diagnostic |diagnostic-structure|
--- with:
---- <pre>lua
---- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
---- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
---- local groups = { "severity", "lnum", "col", "message" }
---- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
---- </pre>
+---
+--- ```lua
+--- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
+--- local pattern = "^(%w+) %w+:(%d+):(%d+): (.+)$"
+--- local groups = { "severity", "lnum", "col", "message" }
+--- vim.diagnostic.match(s, pattern, groups, { WARNING = vim.diagnostic.WARN })
+--- ```
---
---@param str string String to parse diagnostics from.
---@param pat string Lua pattern with capture groups.
@@ -1694,8 +1719,7 @@ end
--- Convert a list of quickfix items to a list of diagnostics.
---
----@param list table A list of quickfix items from |getqflist()| or
---- |getloclist()|.
+---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|.
---@return Diagnostic[] array of |diagnostic-structure|
function M.fromqflist(list)
vim.validate({
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 8144731b09..c6200f16bb 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -1,8 +1,16 @@
local api = vim.api
+local fn = vim.fn
local M = {}
----@private
+--- @alias vim.filetype.mapfn fun(path:string,bufnr:integer, ...):string?, fun(b:integer)?
+--- @alias vim.filetype.maptbl {[1]:string|vim.filetype.mapfn, [2]:{priority:integer}}
+--- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl
+--- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value>
+
+--- @param ft string|vim.filetype.mapfn
+--- @param opts? {priority:integer}
+--- @return vim.filetype.maptbl
local function starsetf(ft, opts)
return {
function(path, bufnr)
@@ -24,36 +32,38 @@ local function starsetf(ft, opts)
end
---@private
---- Get a single line or line range from the buffer.
---- If only start_lnum is specified, return a single line as a string.
---- If both start_lnum and end_lnum are omitted, return all lines from the buffer.
----
----@param bufnr number|nil The buffer to get the lines from
----@param start_lnum number|nil The line number of the first line (inclusive, 1-based)
----@param end_lnum number|nil The line number of the last line (inclusive, 1-based)
----@return table<string>|string Array of lines, or string when end_lnum is omitted
-function M.getlines(bufnr, start_lnum, end_lnum)
- if end_lnum then
- -- Return a line range
- return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false)
- end
+--- Get a line range from the buffer.
+---@param bufnr integer The buffer to get the lines from
+---@param start_lnum integer|nil The line number of the first line (inclusive, 1-based)
+---@param end_lnum integer|nil The line number of the last line (inclusive, 1-based)
+---@return string[] # Array of lines
+function M._getlines(bufnr, start_lnum, end_lnum)
if start_lnum then
- -- Return a single line
- return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
- else
- -- Return all lines
- return api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum or start_lnum, false)
end
+
+ -- Return all lines
+ return api.nvim_buf_get_lines(bufnr, 0, -1, false)
+end
+
+---@private
+--- Get a single line from the buffer.
+---@param bufnr integer The buffer to get the lines from
+---@param start_lnum integer The line number of the first line (inclusive, 1-based)
+---@return string
+function M._getline(bufnr, start_lnum)
+ -- Return a single line
+ return api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] or ''
end
---@private
--- Check whether a string matches any of the given Lua patterns.
---
----@param s string The string to check
----@param patterns table<string> A list of Lua patterns
+---@param s string? The string to check
+---@param patterns string[] A list of Lua patterns
---@return boolean `true` if s matched a pattern, else `false`
-function M.findany(s, patterns)
- if s == nil then
+function M._findany(s, patterns)
+ if not s then
return false
end
for _, v in ipairs(patterns) do
@@ -67,11 +77,11 @@ end
---@private
--- Get the next non-whitespace line in the buffer.
---
----@param bufnr number The buffer to get the line from
----@param start_lnum number The line number of the first line to start from (inclusive, 1-based)
+---@param bufnr integer The buffer to get the line from
+---@param start_lnum integer The line number of the first line to start from (inclusive, 1-based)
---@return string|nil The first non-blank line if found or `nil` otherwise
-function M.nextnonblank(bufnr, start_lnum)
- for _, line in ipairs(M.getlines(bufnr, start_lnum, -1)) do
+function M._nextnonblank(bufnr, start_lnum)
+ for _, line in ipairs(M._getlines(bufnr, start_lnum, -1)) do
if not line:find('^%s*$') then
return line
end
@@ -79,30 +89,93 @@ function M.nextnonblank(bufnr, start_lnum)
return nil
end
----@private
---- Check whether the given string matches the Vim regex pattern.
-M.matchregex = (function()
- local cache = {}
- return function(s, pattern)
- if s == nil then
- return nil
+do
+ --- @type table<string,vim.regex>
+ local regex_cache = {}
+
+ ---@private
+ --- Check whether the given string matches the Vim regex pattern.
+ --- @param s string?
+ --- @param pattern string
+ --- @return boolean
+ function M._matchregex(s, pattern)
+ if not s then
+ return false
end
- if not cache[pattern] then
- cache[pattern] = vim.regex(pattern)
+ if not regex_cache[pattern] then
+ regex_cache[pattern] = vim.regex(pattern)
end
- return cache[pattern]:match_str(s)
+ return regex_cache[pattern]:match_str(s) ~= nil
end
-end)()
+end
+
+--- @module 'vim.filetype.detect'
+local detect = setmetatable({}, {
+ --- @param k string
+ --- @param t table<string,function>
+ --- @return function
+ __index = function(t, k)
+ t[k] = function(...)
+ return require('vim.filetype.detect')[k](...)
+ end
+ return t[k]
+ end,
+})
+
+--- @param ... string|vim.filetype.mapfn
+--- @return vim.filetype.mapfn
+local function detect_seq(...)
+ local candidates = { ... }
+ return function(...)
+ for _, c in ipairs(candidates) do
+ if type(c) == 'string' then
+ return c
+ end
+ if type(c) == 'function' then
+ local r = c(...)
+ if r then
+ return r
+ end
+ end
+ end
+ end
+end
+
+local function detect_noext(path, bufnr)
+ local root = fn.fnamemodify(path, ':r')
+ return M.match({ buf = bufnr, filename = root })
+end
+
+--- @param pat string
+--- @param a string?
+--- @param b string?
+--- @return vim.filetype.mapfn
+local function detect_line1(pat, a, b)
+ return function(_path, bufnr)
+ if M._getline(bufnr, 1):find(pat) then
+ return a
+ end
+ return b
+ end
+end
+
+--- @type vim.filetype.mapfn
+local detect_rc = function(path, _bufnr)
+ if not path:find('/etc/Muttrc%.d/') then
+ return 'rc'
+ end
+end
-- luacheck: push no unused args
-- luacheck: push ignore 122
-- Filetypes based on file extension
---@diagnostic disable: unused-local
+--- @type vim.filetype.mapping
local extension = {
-- BEGIN EXTENSION
['8th'] = '8th',
- ['a65'] = 'a65',
+ a65 = 'a65',
aap = 'aap',
abap = 'abap',
abc = 'abc',
@@ -128,51 +201,36 @@ local extension = {
end
return 'aspvbs'
end,
- asm = function(path, bufnr)
- return require('vim.filetype.detect').asm(bufnr)
- end,
- lst = function(path, bufnr)
- return require('vim.filetype.detect').asm(bufnr)
- end,
- mac = function(path, bufnr)
- return require('vim.filetype.detect').asm(bufnr)
- end,
- ['asn1'] = 'asn',
+ asm = detect.asm,
+ lst = detect.asm,
+ mac = detect.asm,
+ asn1 = 'asn',
asn = 'asn',
- asp = function(path, bufnr)
- return require('vim.filetype.detect').asp(bufnr)
- end,
+ asp = detect.asp,
astro = 'astro',
atl = 'atlas',
as = 'atlas',
+ zed = 'authzed',
ahk = 'autohotkey',
- ['au3'] = 'autoit',
+ au3 = 'autoit',
ave = 'ave',
gawk = 'awk',
awk = 'awk',
ref = 'b',
imp = 'b',
mch = 'b',
- bas = function(path, bufnr)
- return require('vim.filetype.detect').bas(bufnr)
- end,
- bi = function(path, bufnr)
- return require('vim.filetype.detect').bas(bufnr)
- end,
- bm = function(path, bufnr)
- return require('vim.filetype.detect').bas(bufnr)
- end,
+ bas = detect.bas,
+ bass = 'bass',
+ bi = detect.bas,
+ bm = detect.bas,
bc = 'bc',
bdf = 'bdf',
beancount = 'beancount',
bib = 'bib',
- com = function(path, bufnr)
- return require('vim.filetype.detect').bindzone(bufnr, 'dcl')
- end,
- db = function(path, bufnr)
- return require('vim.filetype.detect').bindzone(bufnr)
- end,
+ com = detect_seq(detect.bindzone, 'dcl'),
+ db = detect.bindzone,
bicep = 'bicep',
+ bicepparam = 'bicep',
bb = 'bitbake',
bbappend = 'bitbake',
bbclass = 'bitbake',
@@ -190,7 +248,9 @@ local extension = {
BUILD = 'bzl',
qc = 'c',
cabal = 'cabal',
+ cairo = 'cairo',
capnp = 'capnp',
+ cdc = 'cdc',
cdl = 'cdl',
toc = 'cdrtoc',
cfc = 'cf',
@@ -199,9 +259,7 @@ local extension = {
hgrc = 'cfg',
chf = 'ch',
chai = 'chaiscript',
- ch = function(path, bufnr)
- return require('vim.filetype.detect').change(bufnr)
- end,
+ ch = detect.change,
chs = 'chaskell',
chatito = 'chatito',
chopro = 'chordpro',
@@ -224,7 +282,7 @@ local extension = {
atg = 'coco',
recipe = 'conaryrecipe',
hook = function(path, bufnr)
- return M.getlines(bufnr, 1) == '[Trigger]' and 'conf'
+ return M._getline(bufnr, 1) == '[Trigger]' and 'confini' or nil
end,
nmconnection = 'confini',
mklx = 'context',
@@ -232,15 +290,11 @@ local extension = {
mkii = 'context',
mkxl = 'context',
mkvi = 'context',
- control = function(path, bufnr)
- return require('vim.filetype.detect').control(bufnr)
- end,
- copyright = function(path, bufnr)
- return require('vim.filetype.detect').copyright(bufnr)
- end,
- csh = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
+ control = detect.control,
+ copyright = detect.copyright,
+ corn = 'corn',
+ csh = detect.csh,
+ cpon = 'cpon',
moc = 'cpp',
hh = 'cpp',
tlh = 'cpp',
@@ -253,14 +307,15 @@ local extension = {
tcc = 'cpp',
hxx = 'cpp',
hpp = 'cpp',
- cpp = function(path, bufnr)
- return vim.g.cynlib_syntax_for_cpp and 'cynlib' or 'cpp'
- end,
- cc = function(path, bufnr)
- return vim.g.cynlib_syntax_for_cc and 'cynlib' or 'cpp'
- end,
+ ccm = 'cpp',
+ cppm = 'cpp',
+ cxxm = 'cpp',
+ ['c++m'] = 'cpp',
+ cpp = detect.cpp,
+ cc = detect.cpp,
cql = 'cqlang',
crm = 'crm',
+ cr = 'crystal',
csx = 'cs',
cs = 'cs',
csc = 'csc',
@@ -274,26 +329,23 @@ local extension = {
feature = 'cucumber',
cuh = 'cuda',
cu = 'cuda',
+ cue = 'cue',
pld = 'cupl',
si = 'cuplsim',
cyn = 'cynpp',
+ cypher = 'cypher',
dart = 'dart',
drt = 'dart',
ds = 'datascript',
dcd = 'dcd',
- decl = function(path, bufnr)
- return require('vim.filetype.detect').decl(bufnr)
- end,
- dec = function(path, bufnr)
- return require('vim.filetype.detect').decl(bufnr)
- end,
- dcl = function(path, bufnr)
- return require('vim.filetype.detect').decl(bufnr) or 'clean'
- end,
+ decl = detect.decl,
+ dec = detect.decl,
+ dcl = detect_seq(detect.decl, 'clean'),
def = 'def',
desc = 'desc',
directory = 'desktop',
desktop = 'desktop',
+ dhall = 'dhall',
diff = 'diff',
rej = 'diff',
Dockerfile = 'dockerfile',
@@ -301,32 +353,26 @@ local extension = {
bat = 'dosbatch',
wrap = 'dosini',
ini = 'dosini',
+ INI = 'dosini',
+ vbp = 'dosini',
dot = 'dot',
gv = 'dot',
drac = 'dracula',
drc = 'dracula',
dtd = 'dtd',
- d = function(path, bufnr)
- return require('vim.filetype.detect').dtrace(bufnr)
- end,
+ d = detect.dtrace,
dts = 'dts',
dtsi = 'dts',
dylan = 'dylan',
intr = 'dylanintr',
lid = 'dylanlid',
- e = function(path, bufnr)
- return require('vim.filetype.detect').e(bufnr)
- end,
- E = function(path, bufnr)
- return require('vim.filetype.detect').e(bufnr)
- end,
+ e = detect.e,
+ E = detect.e,
ecd = 'ecd',
edf = 'edif',
edif = 'edif',
edo = 'edif',
- edn = function(path, bufnr)
- return require('vim.filetype.detect').edn(bufnr)
- end,
+ edn = detect.edn,
eex = 'eelixir',
leex = 'eelixir',
am = 'elf',
@@ -334,94 +380,71 @@ local extension = {
elm = 'elm',
lc = 'elsa',
elv = 'elvish',
- ent = function(path, bufnr)
- return require('vim.filetype.detect').ent(bufnr)
- end,
+ ent = detect.ent,
epp = 'epuppet',
erl = 'erlang',
hrl = 'erlang',
yaws = 'erlang',
erb = 'eruby',
rhtml = 'eruby',
+ esdl = 'esdl',
ec = 'esqlc',
EC = 'esqlc',
strl = 'esterel',
- eu = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- EU = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- ew = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- EW = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- EX = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- exu = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- EXU = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- exw = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- EXW = function(path, bufnr)
- return vim.g.filetype_euphoria or 'euphoria3'
- end,
- ex = function(path, bufnr)
- return require('vim.filetype.detect').ex(bufnr)
- end,
+ eu = detect.euphoria,
+ EU = detect.euphoria,
+ ew = detect.euphoria,
+ EW = detect.euphoria,
+ EX = detect.euphoria,
+ exu = detect.euphoria,
+ EXU = detect.euphoria,
+ exw = detect.euphoria,
+ EXW = detect.euphoria,
+ ex = detect.ex,
exp = 'expect',
+ f = detect.f,
factor = 'factor',
fal = 'falcon',
fan = 'fan',
fwt = 'fan',
fnl = 'fennel',
- ['m4gl'] = 'fgl',
+ m4gl = 'fgl',
['4gl'] = 'fgl',
['4gh'] = 'fgl',
fir = 'firrtl',
fish = 'fish',
focexec = 'focexec',
fex = 'focexec',
- fth = 'forth',
ft = 'forth',
+ fth = 'forth',
+ ['4th'] = 'forth',
FOR = 'fortran',
- ['f77'] = 'fortran',
- ['f03'] = 'fortran',
+ f77 = 'fortran',
+ f03 = 'fortran',
fortran = 'fortran',
- ['F95'] = 'fortran',
- ['f90'] = 'fortran',
- ['F03'] = 'fortran',
+ F95 = 'fortran',
+ f90 = 'fortran',
+ F03 = 'fortran',
fpp = 'fortran',
FTN = 'fortran',
ftn = 'fortran',
['for'] = 'fortran',
- ['F90'] = 'fortran',
- ['F77'] = 'fortran',
- ['f95'] = 'fortran',
+ F90 = 'fortran',
+ F77 = 'fortran',
+ f95 = 'fortran',
FPP = 'fortran',
- f = 'fortran',
F = 'fortran',
- ['F08'] = 'fortran',
- ['f08'] = 'fortran',
+ F08 = 'fortran',
+ f08 = 'fortran',
fpc = 'fpcmake',
fsl = 'framescript',
- frm = function(path, bufnr)
- return require('vim.filetype.detect').frm(bufnr)
- end,
+ frm = detect.frm,
fb = 'freebasic',
- fs = function(path, bufnr)
- return require('vim.filetype.detect').fs(bufnr)
- end,
+ fs = detect.fs,
fsh = 'fsh',
fsi = 'fsharp',
fsx = 'fsharp',
+ fc = 'func',
fusion = 'fusion',
gdb = 'gdb',
gdmo = 'gdmo',
@@ -437,6 +460,8 @@ local extension = {
gift = 'gift',
gleam = 'gleam',
glsl = 'glsl',
+ gn = 'gn',
+ gni = 'gn',
gpi = 'gnuplot',
go = 'go',
gp = 'gp',
@@ -462,17 +487,16 @@ local extension = {
hsig = 'haskell',
hsc = 'haskell',
hs = 'haskell',
+ persistentmodels = 'haskellpersistent',
ht = 'haste',
htpp = 'hastepreproc',
+ hcl = 'hcl',
hb = 'hb',
- h = function(path, bufnr)
- return require('vim.filetype.detect').header(bufnr)
- end,
+ h = detect.header,
sum = 'hercules',
errsum = 'hercules',
ev = 'hercules',
vc = 'hercules',
- hcl = 'hcl',
heex = 'heex',
hex = 'hex',
['h32'] = 'hex',
@@ -482,56 +506,29 @@ local extension = {
hog = 'hog',
hws = 'hollywood',
hoon = 'hoon',
- cpt = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- dtml = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- htm = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- html = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- pt = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- shtml = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
- stm = function(path, bufnr)
- return require('vim.filetype.detect').html(bufnr)
- end,
+ cpt = detect.html,
+ dtml = detect.html,
+ htm = detect.html,
+ html = detect.html,
+ pt = detect.html,
+ shtml = detect.html,
+ stm = detect.html,
htt = 'httest',
htb = 'httest',
- hw = function(path, bufnr)
- return require('vim.filetype.detect').hw(bufnr)
- end,
- module = function(path, bufnr)
- return require('vim.filetype.detect').hw(bufnr)
- end,
- pkg = function(path, bufnr)
- return require('vim.filetype.detect').hw(bufnr)
- end,
+ hurl = 'hurl',
+ hw = detect.hw,
+ module = detect.hw,
+ pkg = detect.hw,
iba = 'ibasic',
ibi = 'ibasic',
icn = 'icon',
- idl = function(path, bufnr)
- return require('vim.filetype.detect').idl(bufnr)
- end,
- inc = function(path, bufnr)
- return require('vim.filetype.detect').inc(bufnr)
- end,
+ idl = detect.idl,
+ inc = detect.inc,
inf = 'inform',
INF = 'inform',
ii = 'initng',
- inp = function(path, bufnr)
- return require('vim.filetype.detect').inp(bufnr)
- end,
- ms = function(path, bufnr)
- return require('vim.filetype.detect').nroff(bufnr) or 'xmath'
- end,
+ inp = detect.inp,
+ ms = detect_seq(detect.nroff, 'xmath'),
iss = 'iss',
mst = 'ist',
ist = 'ist',
@@ -540,6 +537,7 @@ local extension = {
jal = 'jal',
jpr = 'jam',
jpl = 'jam',
+ janet = 'janet',
jav = 'java',
java = 'java',
jj = 'javacc',
@@ -553,7 +551,7 @@ local extension = {
jsx = 'javascriptreact',
clp = 'jess',
jgr = 'jgraph',
- ['j73'] = 'jovial',
+ j73 = 'jovial',
jov = 'jovial',
jovial = 'jovial',
properties = 'jproperties',
@@ -561,15 +559,18 @@ local extension = {
slnf = 'json',
json = 'json',
jsonp = 'json',
+ geojson = 'json',
webmanifest = 'json',
ipynb = 'json',
['json-patch'] = 'json',
json5 = 'json5',
jsonc = 'jsonc',
+ jsonl = 'jsonl',
jsonnet = 'jsonnet',
libsonnet = 'jsonnet',
jsp = 'jsp',
jl = 'julia',
+ just = 'just',
kdl = 'kdl',
kv = 'kivy',
kix = 'kix',
@@ -584,6 +585,7 @@ local extension = {
lte = 'latte',
ld = 'ld',
ldif = 'ldif',
+ lean = 'lean',
journal = 'ledger',
ldg = 'ledger',
ledger = 'ledger',
@@ -597,6 +599,7 @@ local extension = {
ly = 'lilypond',
ily = 'lilypond',
liquid = 'liquid',
+ liq = 'liquidsoap',
cl = 'lisp',
L = 'lisp',
lisp = 'lisp',
@@ -605,6 +608,7 @@ local extension = {
asd = 'lisp',
lt = 'lite',
lite = 'lite',
+ livemd = 'livebook',
lgt = 'logtalk',
lotos = 'lotos',
lot = 'lotos',
@@ -612,28 +616,21 @@ local extension = {
lou = 'lout',
ulpc = 'lpc',
lpc = 'lpc',
- c = function(path, bufnr)
- return require('vim.filetype.detect').lpc(bufnr)
- end,
- lsl = function(path, bufnr)
- return require('vim.filetype.detect').lsl(bufnr)
- end,
+ c = detect.lpc,
+ lsl = detect.lsl,
lss = 'lss',
nse = 'lua',
rockspec = 'lua',
lua = 'lua',
+ luau = 'luau',
lrc = 'lyrics',
- m = function(path, bufnr)
- return require('vim.filetype.detect').m(bufnr)
- end,
+ m = detect.m,
at = 'm4',
- mc = function(path, bufnr)
- return require('vim.filetype.detect').mc(bufnr)
- end,
+ mc = detect.mc,
quake = 'm3quake',
- ['m4'] = function(path, bufnr)
+ m4 = function(path, bufnr)
path = path:lower()
- return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4'
+ return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4' or nil
end,
eml = 'mail',
mk = 'make',
@@ -672,47 +669,34 @@ local extension = {
mib = 'mib',
mix = 'mix',
mixal = 'mix',
- mm = function(path, bufnr)
- return require('vim.filetype.detect').mm(bufnr)
- end,
+ mm = detect.mm,
nb = 'mma',
mmp = 'mmp',
- mms = function(path, bufnr)
- return require('vim.filetype.detect').mms(bufnr)
- end,
+ mms = detect.mms,
DEF = 'modula2',
- ['m2'] = 'modula2',
+ m2 = 'modula2',
mi = 'modula2',
lm3 = 'modula3',
+ mojo = 'mojo',
+ ['🔥'] = 'mojo', -- 🙄
ssc = 'monk',
monk = 'monk',
tsc = 'monk',
isc = 'monk',
moo = 'moo',
moon = 'moonscript',
+ move = 'move',
mp = 'mp',
- mpiv = function(path, bufnr)
- return 'mp', function(b)
- vim.b[b].mp_metafun = 1
- end
- end,
- mpvi = function(path, bufnr)
- return 'mp', function(b)
- vim.b[b].mp_metafun = 1
- end
- end,
- mpxl = function(path, bufnr)
- return 'mp', function(b)
- vim.b[b].mp_metafun = 1
- end
- end,
+ mpiv = detect.mp,
+ mpvi = detect.mp,
+ mpxl = detect.mp,
mof = 'msidl',
odl = 'msidl',
msql = 'msql',
mu = 'mupad',
mush = 'mush',
mysql = 'mysql',
- ['n1ql'] = 'n1ql',
+ n1ql = 'n1ql',
nql = 'n1ql',
nanorc = 'nanorc',
ncf = 'ncf',
@@ -722,6 +706,7 @@ local extension = {
nimble = 'nim',
ninja = 'ninja',
nix = 'nix',
+ norg = 'norg',
nqc = 'nqc',
roff = 'nroff',
tmac = 'nroff',
@@ -731,7 +716,10 @@ local extension = {
tr = 'nroff',
nsi = 'nsis',
nsh = 'nsis',
+ nu = 'nu',
obj = 'obj',
+ objdump = 'objdump',
+ cppobjdump = 'objdump',
obl = 'obse',
obse = 'obse',
oblivion = 'obse',
@@ -744,6 +732,7 @@ local extension = {
mli = 'ocaml',
ml = 'ocaml',
occ = 'occam',
+ odin = 'odin',
xom = 'omnimark',
xin = 'omnimark',
opam = 'opam',
@@ -763,6 +752,10 @@ local extension = {
g = 'pccts',
pcmk = 'pcmk',
pdf = 'pdf',
+ pem = 'pem',
+ cer = 'pem',
+ crt = 'pem',
+ csr = 'pem',
plx = 'perl',
prisma = 'prisma',
psgi = 'perl',
@@ -775,12 +768,10 @@ local extension = {
pike = 'pike',
pmod = 'pike',
rcp = 'pilrc',
- PL = function(path, bufnr)
- return require('vim.filetype.detect').pl(bufnr)
- end,
+ PL = detect.pl,
pli = 'pli',
- ['pl1'] = 'pli',
- ['p36'] = 'plm',
+ pl1 = 'pli',
+ p36 = 'plm',
plm = 'plm',
pac = 'plm',
plp = 'plp',
@@ -791,6 +782,7 @@ local extension = {
pod = 'pod',
filter = 'poefilter',
pk = 'poke',
+ pony = 'pony',
ps = 'postscr',
epsi = 'postscr',
afm = 'postscr',
@@ -807,11 +799,12 @@ local extension = {
pdb = 'prolog',
pml = 'promela',
proto = 'proto',
- ['psd1'] = 'ps1',
- ['psm1'] = 'ps1',
- ['ps1'] = 'ps1',
+ prql = 'prql',
+ psd1 = 'ps1',
+ psm1 = 'ps1',
+ ps1 = 'ps1',
pssc = 'ps1',
- ['ps1xml'] = 'ps1xml',
+ ps1xml = 'ps1xml',
psf = 'psf',
psl = 'psl',
pug = 'pug',
@@ -824,25 +817,29 @@ local extension = {
ptl = 'python',
ql = 'ql',
qll = 'ql',
+ qml = 'qml',
+ qbs = 'qml',
qmd = 'quarto',
- R = function(path, bufnr)
- return require('vim.filetype.detect').r(bufnr)
- end,
+ R = detect.r,
+ rkt = 'racket',
+ rktd = 'racket',
+ rktl = 'racket',
rad = 'radiance',
mat = 'radiance',
- ['pod6'] = 'raku',
+ pod6 = 'raku',
rakudoc = 'raku',
rakutest = 'raku',
rakumod = 'raku',
- ['pm6'] = 'raku',
+ pm6 = 'raku',
raku = 'raku',
- ['t6'] = 'raku',
- ['p6'] = 'raku',
+ t6 = 'raku',
+ p6 = 'raku',
raml = 'raml',
rbs = 'rbs',
rego = 'rego',
rem = 'remind',
remind = 'remind',
+ pip = 'requirements',
res = 'rescript',
resi = 'rescript',
frt = 'reva',
@@ -870,8 +867,11 @@ local extension = {
Snw = 'rnoweb',
robot = 'robot',
resource = 'robot',
+ ron = 'ron',
rsc = 'routeros',
x = 'rpcgen',
+ rpgle = 'rpgle',
+ rpgleinc = 'rpgle',
rpl = 'rpl',
Srst = 'rrst',
srst = 'rrst',
@@ -889,6 +889,7 @@ local extension = {
builder = 'ruby',
rake = 'ruby',
rs = 'rust',
+ sage = 'sage',
sas = 'sas',
sass = 'sass',
sa = 'sather',
@@ -897,9 +898,6 @@ local extension = {
ss = 'scheme',
scm = 'scheme',
sld = 'scheme',
- rkt = 'scheme',
- rktd = 'scheme',
- rktl = 'scheme',
sce = 'scilab',
sci = 'scilab',
scss = 'scss',
@@ -909,34 +907,18 @@ local extension = {
sdl = 'sdl',
sed = 'sed',
sexp = 'sexplib',
- bash = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ebuild = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- eclass = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- env = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- ksh = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
- end,
- sh = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
+ bash = detect.bash,
+ ebuild = detect.bash,
+ eclass = detect.bash,
+ env = detect.sh,
+ ksh = detect.ksh,
+ sh = detect.sh,
sieve = 'sieve',
siv = 'sieve',
- sig = function(path, bufnr)
- return require('vim.filetype.detect').sig(bufnr)
- end,
- sil = function(path, bufnr)
- return require('vim.filetype.detect').sil(bufnr)
- end,
+ sig = detect.sig,
+ sil = detect.sil,
sim = 'simula',
- ['s85'] = 'sinda',
+ s85 = 'sinda',
sin = 'sinda',
ssm = 'sisu',
sst = 'sisu',
@@ -973,7 +955,7 @@ local extension = {
spi = 'spyce',
spy = 'spyce',
tyc = 'sql',
- typ = 'sql',
+ typ = detect.typ,
pkb = 'sql',
tyb = 'sql',
pks = 'sql',
@@ -981,15 +963,18 @@ local extension = {
sqi = 'sqr',
sqr = 'sqr',
nut = 'squirrel',
- ['s28'] = 'srec',
- ['s37'] = 'srec',
+ s28 = 'srec',
+ s37 = 'srec',
srec = 'srec',
mot = 'srec',
- ['s19'] = 'srec',
+ s19 = 'srec',
srt = 'srt',
ssa = 'ssa',
ass = 'ssa',
st = 'st',
+ ipd = 'starlark',
+ star = 'starlark',
+ starlark = 'starlark',
imata = 'stata',
['do'] = 'stata',
mata = 'stata',
@@ -1000,9 +985,15 @@ local extension = {
svelte = 'svelte',
svg = 'svg',
swift = 'swift',
+ swig = 'swig',
+ swg = 'swig',
svh = 'systemverilog',
sv = 'systemverilog',
+ cmm = 'trace32',
+ t32 = 'trace32',
+ td = 'tablegen',
tak = 'tak',
+ tal = 'tal',
task = 'taskedit',
tm = 'tcl',
tcl = 'tcl',
@@ -1018,9 +1009,7 @@ local extension = {
bbl = 'tex',
latex = 'tex',
sty = 'tex',
- cls = function(path, bufnr)
- return require('vim.filetype.detect').cls(bufnr)
- end,
+ cls = detect.cls,
texi = 'texinfo',
txi = 'texinfo',
texinfo = 'texinfo',
@@ -1039,20 +1028,31 @@ local extension = {
tsv = 'tsv',
tutor = 'tutor',
twig = 'twig',
- ts = function(path, bufnr)
- return M.getlines(bufnr, 1):find('<%?xml') and 'xml' or 'typescript'
- end,
+ ts = detect_line1('<%?xml', 'xml', 'typescript'),
mts = 'typescript',
cts = 'typescript',
tsx = 'typescriptreact',
uc = 'uc',
uit = 'uil',
uil = 'uil',
+ ungram = 'ungrammar',
+ u = 'unison',
+ uu = 'unison',
+ url = 'urlshortcut',
+ usd = 'usd',
+ usda = 'usd',
+ v = detect.v,
+ vsh = 'v',
+ vv = 'v',
+ ctl = 'vb',
+ dob = 'vb',
+ dsm = 'vb',
+ dsr = 'vb',
+ pag = 'vb',
sba = 'vb',
vb = 'vb',
- dsm = 'vb',
- ctl = 'vb',
vbs = 'vb',
+ vba = detect.vba,
vdf = 'vdf',
vdmpp = 'vdmpp',
vpp = 'vdmpp',
@@ -1062,7 +1062,6 @@ local extension = {
vr = 'vera',
vri = 'vera',
vrh = 'vera',
- v = 'verilog',
va = 'verilogams',
vams = 'verilogams',
vhdl = 'vhdl',
@@ -1073,17 +1072,18 @@ local extension = {
vbe = 'vhdl',
tape = 'vhs',
vim = 'vim',
- vba = 'vim',
mar = 'vmasm',
cm = 'voscm',
wrl = 'vrml',
vroom = 'vroom',
vue = 'vue',
- wat = 'wast',
- wast = 'wast',
+ wast = 'wat',
+ wat = 'wat',
wdl = 'wdl',
wm = 'webmacro',
+ wgsl = 'wgsl',
wbt = 'winbatch',
+ wit = 'wit',
wml = 'wml',
wsml = 'wsml',
ad = 'xdefaults',
@@ -1091,7 +1091,7 @@ local extension = {
xht = 'xhtml',
msc = 'xmath',
msf = 'xmath',
- ['psc1'] = 'xml',
+ psc1 = 'xml',
tpm = 'xml',
xliff = 'xml',
atom = 'xml',
@@ -1107,10 +1107,8 @@ local extension = {
csproj = 'xml',
wpl = 'xml',
xmi = 'xml',
- xpm = function(path, bufnr)
- return M.getlines(bufnr, 1):find('XPM2') and 'xpm2' or 'xpm'
- end,
- ['xpm2'] = 'xpm2',
+ xpm = detect_line1('XPM2', 'xpm2', 'xpm'),
+ xpm2 = 'xpm2',
xqy = 'xquery',
xqm = 'xquery',
xquery = 'xquery',
@@ -1125,191 +1123,80 @@ local extension = {
yxx = 'yacc',
yml = 'yaml',
yaml = 'yaml',
+ eyaml = 'yaml',
yang = 'yang',
- ['z8a'] = 'z8a',
+ yuck = 'yuck',
+ z8a = 'z8a',
zig = 'zig',
- zir = 'zir',
+ zon = 'zig',
zu = 'zimbu',
zut = 'zimbutempl',
+ zs = 'zserio',
zsh = 'zsh',
vala = 'vala',
- web = function(path, bufnr)
- return require('vim.filetype.detect').web(bufnr)
- end,
- pl = function(path, bufnr)
- return require('vim.filetype.detect').pl(bufnr)
- end,
- pp = function(path, bufnr)
- return require('vim.filetype.detect').pp(bufnr)
- end,
- i = function(path, bufnr)
- return require('vim.filetype.detect').progress_asm(bufnr)
- end,
- w = function(path, bufnr)
- return require('vim.filetype.detect').progress_cweb(bufnr)
- end,
- p = function(path, bufnr)
- return require('vim.filetype.detect').progress_pascal(bufnr)
- end,
- pro = function(path, bufnr)
- return require('vim.filetype.detect').proto(bufnr, 'idlang')
- end,
- patch = function(path, bufnr)
- return require('vim.filetype.detect').patch(bufnr)
- end,
- r = function(path, bufnr)
- return require('vim.filetype.detect').r(bufnr)
- end,
- rdf = function(path, bufnr)
- return require('vim.filetype.detect').redif(bufnr)
- end,
- rules = function(path, bufnr)
- return require('vim.filetype.detect').rules(path)
- end,
- sc = function(path, bufnr)
- return require('vim.filetype.detect').sc(bufnr)
- end,
- scd = function(path, bufnr)
- return require('vim.filetype.detect').scd(bufnr)
- end,
+ web = detect.web,
+ pl = detect.pl,
+ pp = detect.pp,
+ i = detect.i,
+ w = detect.progress_cweb,
+ p = detect.progress_pascal,
+ pro = detect_seq(detect.proto, 'idlang'),
+ patch = detect.patch,
+ r = detect.r,
+ rdf = detect.redif,
+ rules = detect.rules,
+ sc = detect.sc,
+ scd = detect.scd,
tcsh = function(path, bufnr)
- return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
- end,
- sql = function(path, bufnr)
- return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
- end,
- zsql = function(path, bufnr)
- return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
- end,
- tex = function(path, bufnr)
- return require('vim.filetype.detect').tex(path, bufnr)
- end,
- tf = function(path, bufnr)
- return require('vim.filetype.detect').tf(bufnr)
- end,
- txt = function(path, bufnr)
- return require('vim.filetype.detect').txt(bufnr)
- end,
- xml = function(path, bufnr)
- return require('vim.filetype.detect').xml(bufnr)
- end,
- y = function(path, bufnr)
- return require('vim.filetype.detect').y(bufnr)
- end,
- cmd = function(path, bufnr)
- return M.getlines(bufnr, 1):find('^/%*') and 'rexx' or 'dosbatch'
- end,
- rul = function(path, bufnr)
- return require('vim.filetype.detect').rul(bufnr)
- end,
- cpy = function(path, bufnr)
- return M.getlines(bufnr, 1):find('^##') and 'python' or 'cobol'
- end,
- dsl = function(path, bufnr)
- return M.getlines(bufnr, 1):find('^%s*<!') and 'dsl' or 'structurizr'
- end,
- smil = function(path, bufnr)
- return M.getlines(bufnr, 1):find('<%?%s*xml.*%?>') and 'xml' or 'smil'
- end,
- smi = function(path, bufnr)
- return require('vim.filetype.detect').smi(bufnr)
- end,
- install = function(path, bufnr)
- return require('vim.filetype.detect').install(path, bufnr)
- end,
- pm = function(path, bufnr)
- return require('vim.filetype.detect').pm(bufnr)
- end,
- me = function(path, bufnr)
- return require('vim.filetype.detect').me(path)
- end,
- reg = function(path, bufnr)
- return require('vim.filetype.detect').reg(bufnr)
- end,
- ttl = function(path, bufnr)
- return require('vim.filetype.detect').ttl(bufnr)
- end,
- rc = function(path, bufnr)
- if not path:find('/etc/Muttrc%.d/') then
- return 'rc'
- end
- end,
- rch = function(path, bufnr)
- if not path:find('/etc/Muttrc%.d/') then
- return 'rc'
- end
- end,
- class = function(path, bufnr)
- require('vim.filetype.detect').class(bufnr)
- end,
- sgml = function(path, bufnr)
- return require('vim.filetype.detect').sgml(bufnr)
- end,
- sgm = function(path, bufnr)
- return require('vim.filetype.detect').sgml(bufnr)
- end,
- t = function(path, bufnr)
- local nroff = require('vim.filetype.detect').nroff(bufnr)
- return nroff or require('vim.filetype.detect').perl(path, bufnr) or 'tads'
- end,
+ return require('vim.filetype.detect').shell(path, M._getlines(bufnr), 'tcsh')
+ end,
+ sql = detect.sql,
+ zsql = detect.sql,
+ tex = detect.tex,
+ tf = detect.tf,
+ txt = detect.txt,
+ xml = detect.xml,
+ y = detect.y,
+ cmd = detect_line1('^/%*', 'rexx', 'dosbatch'),
+ rul = detect.rul,
+ cpy = detect_line1('^##', 'python', 'cobol'),
+ dsl = detect_line1('^%s*<!', 'dsl', 'structurizr'),
+ smil = detect_line1('<%?%s*xml.*%?>', 'xml', 'smil'),
+ smi = detect.smi,
+ install = detect.install,
+ pm = detect.pm,
+ me = detect.me,
+ reg = detect.reg,
+ ttl = detect.ttl,
+ rc = detect_rc,
+ rch = detect_rc,
+ class = detect.class,
+ sgml = detect.sgml,
+ sgm = detect.sgml,
+ t = detect_seq(detect.nroff, detect.perl, 'tads'),
-- Ignored extensions
- bak = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- ['dpkg-bak'] = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- ['dpkg-dist'] = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- ['dpkg-old'] = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- ['dpkg-new'] = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
+ bak = detect_noext,
+ ['dpkg-bak'] = detect_noext,
+ ['dpkg-dist'] = detect_noext,
+ ['dpkg-old'] = detect_noext,
+ ['dpkg-new'] = detect_noext,
['in'] = function(path, bufnr)
if vim.fs.basename(path) ~= 'configure.in' then
- local root = vim.fn.fnamemodify(path, ':r')
+ local root = fn.fnamemodify(path, ':r')
return M.match({ buf = bufnr, filename = root })
end
end,
- new = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- old = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- orig = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- pacsave = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- pacnew = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- rpmsave = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
- rmpnew = function(path, bufnr)
- local root = vim.fn.fnamemodify(path, ':r')
- return M.match({ buf = bufnr, filename = root })
- end,
+ new = detect_noext,
+ old = detect_noext,
+ orig = detect_noext,
+ pacsave = detect_noext,
+ pacnew = detect_noext,
+ rpmsave = detect_noext,
+ rmpnew = detect_noext,
-- END EXTENSION
}
+--- @type vim.filetype.mapping
local filename = {
-- BEGIN FILENAME
['a2psrc'] = 'a2ps',
@@ -1328,6 +1215,7 @@ local filename = {
['named.root'] = 'bindzone',
WORKSPACE = 'bzl',
['WORKSPACE.bzlmod'] = 'bzl',
+ BUCK = 'bzl',
BUILD = 'bzl',
['cabal.project'] = 'cabalproject',
['cabal.config'] = 'cabalconfig',
@@ -1339,24 +1227,12 @@ local filename = {
['/etc/defaults/cdrdao'] = 'cdrdaoconf',
['cfengine.conf'] = 'cfengine',
['CMakeLists.txt'] = 'cmake',
- ['.alias'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['.cshrc'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['.login'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['csh.cshrc'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['csh.login'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['csh.logout'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
+ ['.alias'] = detect.csh,
+ ['.cshrc'] = detect.csh,
+ ['.login'] = detect.csh,
+ ['csh.cshrc'] = detect.csh,
+ ['csh.login'] = detect.csh,
+ ['csh.logout'] = detect.csh,
['auto.master'] = 'conf',
['configure.in'] = 'config',
['configure.ac'] = 'config',
@@ -1396,18 +1272,10 @@ local filename = {
['exim.conf'] = 'exim',
exports = 'exports',
['.fetchmailrc'] = 'fetchmail',
- fvSchemes = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- fvSolution = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- fvConstraints = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- fvModels = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
+ fvSchemes = detect.foam,
+ fvSolution = detect.foam,
+ fvConstraints = detect.foam,
+ fvModels = detect.foam,
fstab = 'fstab',
mtab = 'fstab',
['.gdbinit'] = 'gdb',
@@ -1415,11 +1283,11 @@ local filename = {
['.gdbearlyinit'] = 'gdb',
gdbearlyinit = 'gdb',
['lltxxxxx.txt'] = 'gedcom',
- ['TAG_EDITMSG'] = 'gitcommit',
- ['MERGE_MSG'] = 'gitcommit',
- ['COMMIT_EDITMSG'] = 'gitcommit',
- ['NOTES_EDITMSG'] = 'gitcommit',
- ['EDIT_DESCRIPTION'] = 'gitcommit',
+ TAG_EDITMSG = 'gitcommit',
+ MERGE_MSG = 'gitcommit',
+ COMMIT_EDITMSG = 'gitcommit',
+ NOTES_EDITMSG = 'gitcommit',
+ EDIT_DESCRIPTION = 'gitcommit',
['.gitconfig'] = 'gitconfig',
['.gitmodules'] = 'gitconfig',
['.gitattributes'] = 'gitattributes',
@@ -1433,10 +1301,12 @@ local filename = {
gnashrc = 'gnash',
['.gnuplot'] = 'gnuplot',
['go.sum'] = 'gosum',
+ ['go.work.sum'] = 'gosum',
['go.work'] = 'gowork',
['.gprc'] = 'gp',
['/.gnupg/gpg.conf'] = 'gpg',
['/.gnupg/options'] = 'gpg',
+ Jenkinsfile = 'groovy',
['/var/backups/gshadow.bak'] = 'group',
['/etc/gshadow'] = 'group',
['/etc/group-'] = 'group',
@@ -1472,6 +1342,7 @@ local filename = {
['.jsfmtrc'] = 'jsonc',
['.jshintrc'] = 'jsonc',
['.swrc'] = 'jsonc',
+ ['.justfile'] = 'just',
Kconfig = 'kconfig',
['Kconfig.debug'] = 'kconfig',
['lftp.conf'] = 'lftp',
@@ -1486,9 +1357,8 @@ local filename = {
['.sawfishrc'] = 'lisp',
['/etc/login.access'] = 'loginaccess',
['/etc/login.defs'] = 'logindefs',
- ['.lsl'] = function(path, bufnr)
- return require('vim.filetype.detect').lsl(bufnr)
- end,
+ ['.lsl'] = detect.lsl,
+ ['.busted'] = 'lua',
['.luacheckrc'] = 'lua',
['lynx.cfg'] = 'lynx',
['m3overrides'] = 'm3build',
@@ -1505,6 +1375,7 @@ local filename = {
['man.config'] = 'manconf',
['maxima-init.mac'] = 'maxima',
['meson.build'] = 'meson',
+ ['meson.options'] = 'meson',
['meson_options.txt'] = 'meson',
['/etc/conf.modules'] = 'modconf',
['/etc/modules'] = 'modconf',
@@ -1516,9 +1387,7 @@ local filename = {
['/etc/nanorc'] = 'nanorc',
Neomuttrc = 'neomuttrc',
['.netrc'] = 'netrc',
- NEWS = function(path, bufnr)
- return require('vim.filetype.detect').news(bufnr)
- end,
+ NEWS = detect.news,
['.ocamlinit'] = 'ocaml',
['.octaverc'] = 'octave',
octaverc = 'octave',
@@ -1547,39 +1416,36 @@ local filename = {
['/etc/pinforc'] = 'pinfo',
['/.pinforc'] = 'pinfo',
['.povrayrc'] = 'povini',
- ['printcap'] = function(path, bufnr)
+ printcap = function(path, bufnr)
return 'ptcap', function(b)
vim.b[b].ptcap_type = 'print'
end
end,
- ['termcap'] = function(path, bufnr)
+ termcap = function(path, bufnr)
return 'ptcap', function(b)
vim.b[b].ptcap_type = 'term'
end
end,
['.procmailrc'] = 'procmail',
['.procmail'] = 'procmail',
- ['indent.pro'] = function(path, bufnr)
- return require('vim.filetype.detect').proto(bufnr, 'indent')
- end,
+ ['indent.pro'] = detect_seq(detect.proto, 'indent'),
['/etc/protocols'] = 'protocols',
- ['INDEX'] = function(path, bufnr)
- return require('vim.filetype.detect').psf(bufnr)
- end,
- ['INFO'] = function(path, bufnr)
- return require('vim.filetype.detect').psf(bufnr)
- end,
+ INDEX = detect.psf,
+ INFO = detect.psf,
+ ['MANIFEST.in'] = 'pymanifest',
['.pythonstartup'] = 'python',
['.pythonrc'] = 'python',
SConstruct = 'python',
+ qmldir = 'qmldir',
['.Rprofile'] = 'r',
- ['Rprofile'] = 'r',
+ Rprofile = 'r',
['Rprofile.site'] = 'r',
ratpoisonrc = 'ratpoison',
['.ratpoisonrc'] = 'ratpoison',
inputrc = 'readline',
['.inputrc'] = 'readline',
['.reminders'] = 'remind',
+ ['requirements.txt'] = 'requirements',
['resolv.conf'] = 'resolv',
['robots.txt'] = 'robots',
Gemfile = 'ruby',
@@ -1595,42 +1461,18 @@ local filename = {
['/etc/services'] = 'services',
['/etc/serial.conf'] = 'setserial',
['/etc/udev/cdsymlinks.conf'] = 'sh',
- ['bash.bashrc'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- bashrc = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['.bashrc'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['.env'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- ['.kshrc'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
- end,
- ['.profile'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- ['/etc/profile'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- APKBUILD = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- PKGBUILD = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['.tcshrc'] = function(path, bufnr)
- return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
- end,
- ['tcsh.login'] = function(path, bufnr)
- return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
- end,
- ['tcsh.tcshrc'] = function(path, bufnr)
- return require('vim.filetype.detect').shell(path, M.getlines(bufnr), 'tcsh')
- end,
+ ['bash.bashrc'] = detect.bash,
+ bashrc = detect.bash,
+ ['.bashrc'] = detect.bash,
+ ['.env'] = detect.sh,
+ ['.kshrc'] = detect.ksh,
+ ['.profile'] = detect.sh,
+ ['/etc/profile'] = detect.sh,
+ APKBUILD = detect.bash,
+ PKGBUILD = detect.bash,
+ ['.tcshrc'] = detect.tcsh,
+ ['tcsh.login'] = detect.tcsh,
+ ['tcsh.tcshrc'] = detect.tcsh,
['/etc/slp.conf'] = 'slpconf',
['/etc/slp.reg'] = 'slpreg',
['/etc/slp.spi'] = 'slpspi',
@@ -1680,23 +1522,15 @@ local filename = {
wget2rc = 'wget2',
['.wvdialrc'] = 'wvdial',
['wvdial.conf'] = 'wvdial',
+ ['.XCompose'] = 'xcompose',
+ ['Compose'] = 'xcompose',
['.Xresources'] = 'xdefaults',
['.Xpdefaults'] = 'xdefaults',
['xdm-config'] = 'xdefaults',
['.Xdefaults'] = 'xdefaults',
- ['xorg.conf'] = function(path, bufnr)
- return 'xf86conf', function(b)
- vim.b[b].xf86conf_xfree86_version = 4
- end
- end,
- ['xorg.conf-4'] = function(path, bufnr)
- return 'xf86conf', function(b)
- vim.b[b].xf86conf_xfree86_version = 4
- end
- end,
- ['XF86Config'] = function(path, bufnr)
- return require('vim.filetype.detect').xfree86()
- end,
+ ['xorg.conf'] = detect.xfree86_v4,
+ ['xorg.conf-4'] = detect.xfree86_v4,
+ ['XF86Config'] = detect.xfree86_v3,
['/etc/xinetd.conf'] = 'xinetd',
fglrxrc = 'xml',
['/etc/blkid.tab'] = 'xml',
@@ -1715,6 +1549,12 @@ local filename = {
-- END FILENAME
}
+-- Re-use closures as much as possible
+local detect_apache = starsetf('apache')
+local detect_muttrc = starsetf('muttrc')
+local detect_neomuttrc = starsetf('neomuttrc')
+
+--- @type vim.filetype.mapping
local pattern = {
-- BEGIN PATTERN
['.*/etc/a2ps/.*%.cfg'] = 'a2ps',
@@ -1723,40 +1563,39 @@ local pattern = {
['.*/etc/asound%.conf'] = 'alsaconf',
['.*/etc/apache2/sites%-.*/.*%.com'] = 'apache',
['.*/etc/httpd/.*%.conf'] = 'apache',
- ['.*/etc/apache2/.*%.conf.*'] = starsetf('apache'),
- ['.*/etc/apache2/conf%..*/.*'] = starsetf('apache'),
- ['.*/etc/apache2/mods%-.*/.*'] = starsetf('apache'),
- ['.*/etc/apache2/sites%-.*/.*'] = starsetf('apache'),
- ['access%.conf.*'] = starsetf('apache'),
- ['apache%.conf.*'] = starsetf('apache'),
- ['apache2%.conf.*'] = starsetf('apache'),
- ['httpd%.conf.*'] = starsetf('apache'),
- ['srm%.conf.*'] = starsetf('apache'),
- ['.*/etc/httpd/conf%..*/.*'] = starsetf('apache'),
- ['.*/etc/httpd/conf%.d/.*%.conf.*'] = starsetf('apache'),
- ['.*/etc/httpd/mods%-.*/.*'] = starsetf('apache'),
- ['.*/etc/httpd/sites%-.*/.*'] = starsetf('apache'),
+ ['.*/etc/apache2/.*%.conf.*'] = detect_apache,
+ ['.*/etc/apache2/conf%..*/.*'] = detect_apache,
+ ['.*/etc/apache2/mods%-.*/.*'] = detect_apache,
+ ['.*/etc/apache2/sites%-.*/.*'] = detect_apache,
+ ['access%.conf.*'] = detect_apache,
+ ['apache%.conf.*'] = detect_apache,
+ ['apache2%.conf.*'] = detect_apache,
+ ['httpd%.conf.*'] = detect_apache,
+ ['srm%.conf.*'] = detect_apache,
+ ['.*/etc/httpd/conf%..*/.*'] = detect_apache,
+ ['.*/etc/httpd/conf%.d/.*%.conf.*'] = detect_apache,
+ ['.*/etc/httpd/mods%-.*/.*'] = detect_apache,
+ ['.*/etc/httpd/sites%-.*/.*'] = detect_apache,
['.*/etc/proftpd/.*%.conf.*'] = starsetf('apachestyle'),
['.*/etc/proftpd/conf%..*/.*'] = starsetf('apachestyle'),
['proftpd%.conf.*'] = starsetf('apachestyle'),
['.*asterisk/.*%.conf.*'] = starsetf('asterisk'),
['.*asterisk.*/.*voicemail%.conf.*'] = starsetf('asteriskvm'),
['.*/%.aptitude/config'] = 'aptconf',
- ['.*%.[aA]'] = function(path, bufnr)
- return require('vim.filetype.detect').asm(bufnr)
- end,
- ['.*%.[sS]'] = function(path, bufnr)
- return require('vim.filetype.detect').asm(bufnr)
- end,
+ ['.*%.[aA]'] = detect.asm,
+ ['.*%.[sS]'] = detect.asm,
['[mM]akefile%.am'] = 'automake',
['.*/bind/db%..*'] = starsetf('bindzone'),
['.*/named/db%..*'] = starsetf('bindzone'),
['.*/build/conf/.*%.conf'] = 'bitbake',
['.*/meta/conf/.*%.conf'] = 'bitbake',
['.*/meta%-.*/conf/.*%.conf'] = 'bitbake',
+ ['.*%.blade%.php'] = 'blade',
['bzr_log%..*'] = 'bzr',
['.*enlightenment/.*%.cfg'] = 'c',
['${HOME}/cabal%.config'] = 'cabalconfig',
+ ['${HOME}/%.config/cabal/config'] = 'cabalconfig',
+ ['${XDG_CONFIG_HOME}/cabal/config'] = 'cabalconfig',
['cabal%.project%..*'] = starsetf('cabalproject'),
['.*/%.calendar/.*'] = starsetf('calendar'),
['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'),
@@ -1767,16 +1606,12 @@ local pattern = {
['.*/etc/default/cdrdao'] = 'cdrdaoconf',
['.*hgrc'] = 'cfg',
['.*%.[Cc][Ff][Gg]'] = {
- function(path, bufnr)
- return require('vim.filetype.detect').cfg(bufnr)
- end,
+ detect.cfg,
-- Decrease priority to avoid conflicts with more specific patterns
-- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc.
{ priority = -1 },
},
- ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr)
- return require('vim.filetype.detect').changelog(bufnr)
- end),
+ ['[cC]hange[lL]og.*'] = starsetf(detect.changelog),
['.*%.%.ch'] = 'chill',
['.*%.cmake%.in'] = 'cmake',
-- */cmus/rc and */.cmus/rc
@@ -1788,19 +1623,11 @@ local pattern = {
['.*/etc/hostname%..*'] = starsetf('config'),
['crontab%..*'] = starsetf('crontab'),
['.*/etc/cron%.d/.*'] = starsetf('crontab'),
- ['%.cshrc.*'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
- ['%.login.*'] = function(path, bufnr)
- return require('vim.filetype.detect').csh(path, bufnr)
- end,
+ ['%.cshrc.*'] = detect.csh,
+ ['%.login.*'] = detect.csh,
['cvs%d+'] = 'cvs',
- ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr)
- return require('vim.filetype.detect').dat(path, bufnr)
- end,
- ['.*/debian/patches/.*'] = function(path, bufnr)
- return require('vim.filetype.detect').dep3patch(path, bufnr)
- end,
+ ['.*%.[Dd][Aa][Tt]'] = detect.dat,
+ ['.*/debian/patches/.*'] = detect.dep3patch,
['.*/etc/dnsmasq%.d/.*'] = starsetf('dnsmasq'),
['Containerfile%..*'] = starsetf('dockerfile'),
['Dockerfile%..*'] = starsetf('dockerfile'),
@@ -1811,6 +1638,7 @@ local pattern = {
['.*/debian/copyright'] = 'debcopyright',
['.*/etc/apt/sources%.list%.d/.*%.list'] = 'debsources',
['.*/etc/apt/sources%.list'] = 'debsources',
+ ['.*/etc/apt/sources%.list%.d/.*%.sources'] = 'deb822sources',
['.*%.directory'] = 'desktop',
['.*%.desktop'] = 'desktop',
['dictd.*%.conf'] = 'dictdconf',
@@ -1826,53 +1654,25 @@ local pattern = {
['.*/dtrace/.*%.d'] = 'dtrace',
['.*esmtprc'] = 'esmtprc',
['.*Eterm/.*%.cfg'] = 'eterm',
- ['[a-zA-Z0-9].*Dict'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['[a-zA-Z0-9].*Dict%..*'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['[a-zA-Z].*Properties'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['[a-zA-Z].*Properties%..*'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['.*Transport%..*'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['.*/constant/g'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['.*/0/.*'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
- ['.*/0%.orig/.*'] = function(path, bufnr)
- return require('vim.filetype.detect').foam(bufnr)
- end,
+ ['[a-zA-Z0-9].*Dict'] = detect.foam,
+ ['[a-zA-Z0-9].*Dict%..*'] = detect.foam,
+ ['[a-zA-Z].*Properties'] = detect.foam,
+ ['[a-zA-Z].*Properties%..*'] = detect.foam,
+ ['.*Transport%..*'] = detect.foam,
+ ['.*/constant/g'] = detect.foam,
+ ['.*/0/.*'] = detect.foam,
+ ['.*/0%.orig/.*'] = detect.foam,
['.*/%.fvwm/.*'] = starsetf('fvwm'),
- ['.*fvwmrc.*'] = starsetf(function(path, bufnr)
- return 'fvwm', function(b)
- vim.b[b].fvwm_version = 1
- end
- end),
- ['.*fvwm95.*%.hook'] = starsetf(function(path, bufnr)
- return 'fvwm', function(b)
- vim.b[b].fvwm_version = 1
- end
- end),
- ['.*fvwm2rc.*'] = starsetf(function(path, bufnr)
- return require('vim.filetype.detect').fvwm(path)
- end),
+ ['.*fvwmrc.*'] = starsetf(detect.fvwm_v1),
+ ['.*fvwm95.*%.hook'] = starsetf(detect.fvwm_v1),
+ ['.*fvwm2rc.*'] = starsetf(detect.fvwm_v2),
['.*/tmp/lltmp.*'] = starsetf('gedcom'),
['.*/etc/gitconfig%.d/.*'] = starsetf('gitconfig'),
['.*/gitolite%-admin/conf/.*'] = starsetf('gitolite'),
['tmac%..*'] = starsetf('nroff'),
['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'),
['.*%.git/.*'] = {
- function(path, bufnr)
- return require('vim.filetype.detect').git(bufnr)
- end,
+ detect.git,
-- Decrease priority to run after simple pattern checks
{ priority = -1 },
},
@@ -1928,6 +1728,7 @@ local pattern = {
['org%.eclipse%..*%.prefs'] = 'jproperties',
['.*%.properties_.._.._.*'] = starsetf('jproperties'),
['[jt]sconfig.*%.json'] = 'jsonc',
+ ['[jJ]ustfile'] = 'just',
['Kconfig%..*'] = starsetf('kconfig'),
['.*%.[Ss][Uu][Bb]'] = 'krl',
['lilo%.conf.*'] = starsetf('lilo'),
@@ -2035,15 +1836,13 @@ local pattern = {
['.*/log/news/news%.notice'] = 'messages',
['.*/log/syslog%.notice'] = 'messages',
['.*/log/user%.notice'] = 'messages',
- ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr)
- return require('vim.filetype.detect').mod(path, bufnr)
- end,
+ ['.*%.[Mm][Oo][Dd]'] = detect.mod,
['.*/etc/modules%.conf'] = 'modconf',
['.*/etc/conf%.modules'] = 'modconf',
['.*/etc/modules'] = 'modconf',
['.*/etc/modprobe%..*'] = starsetf('modconf'),
['.*/etc/modutils/.*'] = starsetf(function(path, bufnr)
- if vim.fn.executable(vim.fn.expand(path)) ~= 1 then
+ if fn.executable(fn.expand(path)) ~= 1 then
return 'modconf'
end
end),
@@ -2052,32 +1851,30 @@ local pattern = {
['Muttngrc'] = 'muttrc',
['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'),
['.*/%.mplayer/config'] = 'mplayerconf',
- ['Muttrc.*'] = starsetf('muttrc'),
- ['Muttngrc.*'] = starsetf('muttrc'),
+ ['Muttrc.*'] = detect_muttrc,
+ ['Muttngrc.*'] = detect_muttrc,
-- muttrc* and .muttrc*
- ['%.?muttrc.*'] = starsetf('muttrc'),
+ ['%.?muttrc.*'] = detect_muttrc,
-- muttngrc* and .muttngrc*
- ['%.?muttngrc.*'] = starsetf('muttrc'),
- ['.*/%.mutt/muttrc.*'] = starsetf('muttrc'),
- ['.*/%.muttng/muttrc.*'] = starsetf('muttrc'),
- ['.*/%.muttng/muttngrc.*'] = starsetf('muttrc'),
+ ['%.?muttngrc.*'] = detect_muttrc,
+ ['.*/%.mutt/muttrc.*'] = detect_muttrc,
+ ['.*/%.muttng/muttrc.*'] = detect_muttrc,
+ ['.*/%.muttng/muttngrc.*'] = detect_muttrc,
['rndc.*%.conf'] = 'named',
['rndc.*%.key'] = 'named',
['named.*%.conf'] = 'named',
['.*/etc/nanorc'] = 'nanorc',
['.*%.NS[ACGLMNPS]'] = 'natural',
- ['Neomuttrc.*'] = starsetf('neomuttrc'),
+ ['Neomuttrc.*'] = detect_neomuttrc,
-- neomuttrc* and .neomuttrc*
- ['%.?neomuttrc.*'] = starsetf('neomuttrc'),
- ['.*/%.neomutt/neomuttrc.*'] = starsetf('neomuttrc'),
+ ['%.?neomuttrc.*'] = detect_neomuttrc,
+ ['.*/%.neomutt/neomuttrc.*'] = detect_neomuttrc,
['nginx.*%.conf'] = 'nginx',
['.*/etc/nginx/.*'] = 'nginx',
['.*nginx%.conf'] = 'nginx',
['.*/nginx/.*%.conf'] = 'nginx',
['.*/usr/local/nginx/conf/.*'] = 'nginx',
- ['.*%.[1-9]'] = function(path, bufnr)
- return require('vim.filetype.detect').nroff(bufnr)
- end,
+ ['.*%.[1-9]'] = detect.nroff,
['.*%.ml%.cppo'] = 'ocaml',
['.*%.mli%.cppo'] = 'ocaml',
['.*%.opam%.template'] = 'opam',
@@ -2098,9 +1895,7 @@ local pattern = {
['.*%.php%d'] = 'php',
['.*/%.pinforc'] = 'pinfo',
['.*/etc/pinforc'] = 'pinfo',
- ['.*%.[Pp][Rr][Gg]'] = function(path, bufnr)
- return require('vim.filetype.detect').prg(bufnr)
- end,
+ ['.*%.[Pp][Rr][Gg]'] = detect.prg,
['.*/etc/protocols'] = 'protocols',
['.*printcap.*'] = starsetf(function(path, bufnr)
return require('vim.filetype.detect').printcap('print')
@@ -2120,30 +1915,14 @@ local pattern = {
['.*/etc/services'] = 'services',
['.*/etc/serial%.conf'] = 'setserial',
['.*/etc/udev/cdsymlinks%.conf'] = 'sh',
- ['%.bash[_%-]aliases'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['%.bash[_%-]logout'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['%.bash[_%-]profile'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['%.kshrc.*'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'ksh')
- end,
- ['%.profile.*'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- ['.*/etc/profile'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr))
- end,
- ['bash%-fc[%-%.]'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'bash')
- end,
- ['%.tcshrc.*'] = function(path, bufnr)
- return require('vim.filetype.detect').sh(path, M.getlines(bufnr), 'tcsh')
- end,
+ ['%.bash[_%-]aliases'] = detect.bash,
+ ['%.bash[_%-]logout'] = detect.bash,
+ ['%.bash[_%-]profile'] = detect.bash,
+ ['%.kshrc.*'] = detect.ksh,
+ ['%.profile.*'] = detect.sh,
+ ['.*/etc/profile'] = detect.sh,
+ ['bash%-fc[%-%.].*'] = detect.bash,
+ ['%.tcshrc.*'] = detect.tcsh,
['.*/etc/sudoers%.d/.*'] = starsetf('sudoers'),
['.*%._sst%.meta'] = 'sisu',
['.*%.%-sst%.meta'] = 'sisu',
@@ -2155,17 +1934,13 @@ local pattern = {
['.*/%.ssh/config'] = 'sshconfig',
['.*/%.ssh/.*%.conf'] = 'sshconfig',
['.*/etc/ssh/sshd_config%.d/.*%.conf'] = 'sshdconfig',
- ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr)
- return require('vim.filetype.detect').src(bufnr)
- end,
+ ['.*%.[Ss][Rr][Cc]'] = detect.src,
['.*/etc/sudoers'] = 'sudoers',
['svn%-commit.*%.tmp'] = 'svn',
['.*/sway/config'] = 'swayconfig',
['.*/%.sway/config'] = 'swayconfig',
['.*%.swift%.gyb'] = 'swiftgyb',
- ['.*%.[Ss][Yy][Ss]'] = function(path, bufnr)
- return require('vim.filetype.detect').sys(bufnr)
- end,
+ ['.*%.[Ss][Yy][Ss]'] = detect.sys,
['.*/etc/sysctl%.conf'] = 'sysctl',
['.*/etc/sysctl%.d/.*%.conf'] = 'sysctl',
['.*/systemd/.*%.automount'] = 'systemd',
@@ -2208,14 +1983,17 @@ local pattern = {
['.*/%.config/upstart/.*%.conf'] = 'upstart',
['.*/%.init/.*%.conf'] = 'upstart',
['.*/usr/share/upstart/.*%.override'] = 'upstart',
- ['.*%.[Ll][Oo][Gg]'] = function(path, bufnr)
- return require('vim.filetype.detect').log(path)
- end,
+ ['.*%.[Ll][Oo][Gg]'] = detect.log,
['.*%.vhdl_[0-9].*'] = starsetf('vhdl'),
['.*%.ws[fc]'] = 'wsh',
['.*/Xresources/.*'] = starsetf('xdefaults'),
['.*/app%-defaults/.*'] = starsetf('xdefaults'),
['.*/etc/xinetd%.conf'] = 'xinetd',
+ ['.*/usr/share/X11/xkb/compat/.*'] = starsetf('xkb'),
+ ['.*/usr/share/X11/xkb/geometry/.*'] = starsetf('xkb'),
+ ['.*/usr/share/X11/xkb/keycodes/.*'] = starsetf('xkb'),
+ ['.*/usr/share/X11/xkb/symbols/.*'] = starsetf('xkb'),
+ ['.*/usr/share/X11/xkb/types/.*'] = starsetf('xkb'),
['.*/etc/blkid%.tab'] = 'xml',
['.*/etc/blkid%.tab%.old'] = 'xml',
['.*%.vbproj%.user'] = 'xml',
@@ -2228,20 +2006,10 @@ local pattern = {
['Xresources.*'] = starsetf('xdefaults'),
['.*/etc/xinetd%.d/.*'] = starsetf('xinetd'),
['.*xmodmap.*'] = starsetf('xmodmap'),
- ['.*/xorg%.conf%.d/.*%.conf'] = function(path, bufnr)
- return 'xf86config', function(b)
- vim.b[b].xf86conf_xfree86_version = 4
- end
- end,
+ ['.*/xorg%.conf%.d/.*%.conf'] = detect.xfree86_v4,
-- Increase priority to run before the pattern below
- ['XF86Config%-4.*'] = starsetf(function(path, bufnr)
- return 'xf86conf', function(b)
- vim.b[b].xf86conf_xfree86_version = 4
- end
- end, { priority = -math.huge + 1 }),
- ['XF86Config.*'] = starsetf(function(path, bufnr)
- return require('vim.filetype.detect').xfree86()
- end),
+ ['XF86Config%-4.*'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }),
+ ['XF86Config.*'] = starsetf(detect.xfree86_v3),
['%.zcompdump.*'] = starsetf('zsh'),
-- .zlog* and zlog*
['%.?zlog.*'] = starsetf('zsh'),
@@ -2251,7 +2019,7 @@ local pattern = {
['.*~'] = function(path, bufnr)
local short = path:gsub('~$', '', 1)
if path ~= short and short ~= '' then
- return M.match({ buf = bufnr, filename = vim.fn.fnameescape(short) })
+ return M.match({ buf = bufnr, filename = fn.fnameescape(short) })
end
end,
-- END PATTERN
@@ -2259,9 +2027,10 @@ local pattern = {
-- luacheck: pop
-- luacheck: pop
----@private
+--- @param t vim.filetype.mapping
+--- @return vim.filetype.mapping[]
local function sort_by_priority(t)
- local sorted = {}
+ local sorted = {} --- @type vim.filetype.mapping[]
for k, v in pairs(t) do
local ft = type(v) == 'table' and v[1] or v
assert(
@@ -2283,7 +2052,9 @@ end
local pattern_sorted = sort_by_priority(pattern)
----@private
+--- @param path string
+--- @param as_pattern? true
+--- @return string
local function normalize_path(path, as_pattern)
local normal = path:gsub('\\', '/')
if normal:find('^~') then
@@ -2292,12 +2063,17 @@ local function normalize_path(path, as_pattern)
-- The rest of path should already be properly escaped.
normal = vim.pesc(vim.env.HOME) .. normal:sub(2)
else
- normal = vim.env.HOME .. normal:sub(2)
+ normal = vim.env.HOME .. normal:sub(2) --- @type string
end
end
return normal
end
+--- @class vim.filetype.add.filetypes
+--- @field pattern? vim.filetype.mapping
+--- @field extension? vim.filetype.mapping
+--- @field filename? vim.filetype.mapping
+
--- Add new filetype mappings.
---
--- Filetype mappings can be added either by extension or by filename (either
@@ -2313,7 +2089,8 @@ end
--- pattern, if any) and should return a string that will be used as the
--- buffer's filetype. Optionally, the function can return a second function
--- value which, when called, modifies the state of the buffer. This can be used
---- to, for example, set filetype-specific buffer variables.
+--- to, for example, set filetype-specific buffer variables. This function will
+--- be called by Nvim before setting the buffer's filetype.
---
--- Filename patterns can specify an optional priority to resolve cases when a
--- file path matches multiple patterns. Higher priorities are matched first.
@@ -2325,61 +2102,63 @@ end
--- See $VIMRUNTIME/lua/vim/filetype.lua for more examples.
---
--- Example:
---- <pre>lua
---- vim.filetype.add({
---- extension = {
---- foo = 'fooscript',
---- bar = function(path, bufnr)
---- if some_condition() then
---- return 'barscript', function(bufnr)
---- -- Set a buffer variable
---- vim.b[bufnr].barscript_version = 2
---- end
---- end
---- return 'bar'
---- end,
---- },
---- filename = {
---- ['.foorc'] = 'toml',
---- ['/etc/foo/config'] = 'toml',
---- },
---- pattern = {
---- ['.*/etc/foo/.*'] = 'fooscript',
---- -- Using an optional priority
---- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } },
---- -- A pattern containing an environment variable
---- ['${XDG_CONFIG_HOME}/foo/git'] = 'git',
---- ['README.(%a+)$'] = function(path, bufnr, ext)
---- if ext == 'md' then
---- return 'markdown'
---- elseif ext == 'rst' then
---- return 'rst'
---- end
---- end,
---- },
---- })
---- </pre>
+---
+--- ```lua
+--- vim.filetype.add({
+--- extension = {
+--- foo = 'fooscript',
+--- bar = function(path, bufnr)
+--- if some_condition() then
+--- return 'barscript', function(bufnr)
+--- -- Set a buffer variable
+--- vim.b[bufnr].barscript_version = 2
+--- end
+--- end
+--- return 'bar'
+--- end,
+--- },
+--- filename = {
+--- ['.foorc'] = 'toml',
+--- ['/etc/foo/config'] = 'toml',
+--- },
+--- pattern = {
+--- ['.*/etc/foo/.*'] = 'fooscript',
+--- -- Using an optional priority
+--- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } },
+--- -- A pattern containing an environment variable
+--- ['${XDG_CONFIG_HOME}/foo/git'] = 'git',
+--- ['README.(%a+)$'] = function(path, bufnr, ext)
+--- if ext == 'md' then
+--- return 'markdown'
+--- elseif ext == 'rst' then
+--- return 'rst'
+--- end
+--- end,
+--- },
+--- })
+--- ```
---
--- To add a fallback match on contents, use
---- <pre>lua
+---
+--- ```lua
--- vim.filetype.add {
--- pattern = {
--- ['.*'] = {
--- priority = -math.huge,
--- function(path, bufnr)
---- local content = vim.filetype.getlines(bufnr, 1)
---- if vim.filetype.matchregex(content, [[^#!.*\\<mine\\>]]) then
+--- local content = vim.api.nvim_buf_get_lines(bufnr, 0, 1, false)[1] or ''
+--- if vim.regex([[^#!.*\\<mine\\>]]):match_str(content) ~= nil then
--- return 'mine'
---- elseif vim.filetype.matchregex(content, [[\\<drawing\\>]]) then
+--- elseif vim.regex([[\\<drawing\\>]]):match_str(content) ~= nil then
--- return 'drawing'
--- end
--- end,
--- },
--- },
--- }
---- </pre>
+--- ```
---
----@param filetypes table A table containing new filetype maps (see example).
+---@param filetypes vim.filetype.add.filetypes A table containing new filetype maps (see example).
function M.add(filetypes)
for k, v in pairs(filetypes.extension or {}) do
extension[k] = v
@@ -2398,66 +2177,93 @@ function M.add(filetypes)
end
end
----@private
+--- @param ft vim.filetype.mapping.value
+--- @param path? string
+--- @param bufnr? integer
+--- @param ... any
+--- @return string?
+--- @return fun(b: integer)?
local function dispatch(ft, path, bufnr, ...)
- local on_detect
- if type(ft) == 'function' then
- if bufnr then
- ft, on_detect = ft(path, bufnr, ...)
- else
- -- If bufnr is nil (meaning we are matching only against the filename), set it to an invalid
- -- value (-1) and catch any errors from the filetype detection function. If the function tries
- -- to use the buffer then it will fail, but this enables functions which do not need a buffer
- -- to still work.
- local ok
- ok, ft, on_detect = pcall(ft, path, -1, ...)
- if not ok then
- return
- end
+ if type(ft) == 'string' then
+ return ft
+ end
+
+ if type(ft) ~= 'function' then
+ return
+ end
+
+ assert(path)
+
+ ---@type string|false?, fun(b: integer)?
+ local ft0, on_detect
+ if bufnr then
+ ft0, on_detect = ft(path, bufnr, ...)
+ else
+ -- If bufnr is nil (meaning we are matching only against the filename), set it to an invalid
+ -- value (-1) and catch any errors from the filetype detection function. If the function tries
+ -- to use the buffer then it will fail, but this enables functions which do not need a buffer
+ -- to still work.
+ local ok
+ ok, ft0, on_detect = pcall(ft, path, -1, ...)
+ if not ok then
+ return
end
end
- if type(ft) == 'string' then
- return ft, on_detect
+ if not ft0 then
+ return
end
+
+ return ft0, on_detect
end
--- Lookup table/cache for patterns that contain an environment variable pattern, e.g. ${SOME_VAR}.
+--- Lookup table/cache for patterns that contain an environment variable pattern, e.g. ${SOME_VAR}.
+--- @type table<string,boolean>
local expand_env_lookup = {}
----@private
+--- @param name string
+--- @param path string
+--- @param tail string
+--- @param pat string
+--- @return string|false?
local function match_pattern(name, path, tail, pat)
if expand_env_lookup[pat] == nil then
expand_env_lookup[pat] = pat:find('%${') ~= nil
end
if expand_env_lookup[pat] then
- local return_early
+ local return_early --- @type true?
+ --- @type string
pat = pat:gsub('%${(%S-)}', function(env)
-- If an environment variable is present in the pattern but not set, there is no match
if not vim.env[env] then
return_early = true
return nil
end
- return vim.env[env]
+ return vim.pesc(vim.env[env])
end)
if return_early then
return false
end
end
+
-- If the pattern contains a / match against the full path, otherwise just the tail
local fullpat = '^' .. pat .. '$'
- local matches
+
if pat:find('/') then
-- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against
-- both the short file name (as typed) and the full file name (after expanding to full path
-- and resolving symlinks)
- matches = name:match(fullpat) or path:match(fullpat)
- else
- matches = tail:match(fullpat)
+ return (name:match(fullpat) or path:match(fullpat))
end
- return matches
+
+ return (tail:match(fullpat))
end
+--- @class vim.filetype.match.args
+--- @field buf? integer
+--- @field filename? string
+--- @field contents? string[]
+
--- Perform filetype detection.
---
--- The filetype can be detected using one of three methods:
@@ -2473,21 +2279,22 @@ end
--- Each of the three options is specified using a key to the single argument of this function.
--- Example:
---
---- <pre>lua
---- -- Using a buffer number
---- vim.filetype.match({ buf = 42 })
+--- ```lua
+--- -- Using a buffer number
+--- vim.filetype.match({ buf = 42 })
---
---- -- Override the filename of the given buffer
---- vim.filetype.match({ buf = 42, filename = 'foo.c' })
+--- -- Override the filename of the given buffer
+--- vim.filetype.match({ buf = 42, filename = 'foo.c' })
---
---- -- Using a filename without a buffer
---- vim.filetype.match({ filename = 'main.lua' })
+--- -- Using a filename without a buffer
+--- vim.filetype.match({ filename = 'main.lua' })
---
---- -- Using file contents
---- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
---- </pre>
+--- -- Using file contents
+--- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
+--- ```
---
----@param args table Table specifying which matching strategy to use. Accepted keys are:
+---@param args vim.filetype.match.args Table specifying which matching strategy to use.
+--- Accepted keys are:
--- * buf (number): Buffer number to use for matching. Mutually exclusive with
--- {contents}
--- * filename (string): Filename to use for matching. When {buf} is given,
@@ -2500,8 +2307,8 @@ end
--- * contents (table): An array of lines representing file contents to use for
--- matching. Can be used with {filename}. Mutually exclusive
--- with {buf}.
----@return string|nil If a match was found, the matched filetype.
----@return function|nil A function that modifies buffer state when called (for example, to set some
+---@return string|nil # If a match was found, the matched filetype.
+---@return function|nil # A function that modifies buffer state when called (for example, to set some
--- filetype specific buffer variables). The function accepts a buffer number as
--- its only argument.
function M.match(args)
@@ -2521,64 +2328,65 @@ function M.match(args)
name = api.nvim_buf_get_name(bufnr)
end
- if name then
- name = normalize_path(name)
- end
-
+ --- @type string?, fun(b: integer)?
local ft, on_detect
- -- First check for the simple case where the full path exists as a key
- local path = vim.fn.fnamemodify(name, ':p')
- ft, on_detect = dispatch(filename[path], path, bufnr)
- if ft then
- return ft, on_detect
- end
+ if name then
+ name = normalize_path(name)
- -- Next check against just the file name
- local tail = vim.fn.fnamemodify(name, ':t')
- ft, on_detect = dispatch(filename[tail], path, bufnr)
- if ft then
- return ft, on_detect
- end
+ -- First check for the simple case where the full path exists as a key
+ local path = fn.fnamemodify(name, ':p')
+ ft, on_detect = dispatch(filename[path], path, bufnr)
+ if ft then
+ return ft, on_detect
+ end
- -- Next, check the file path against available patterns with non-negative priority
- local j = 1
- for i, v in ipairs(pattern_sorted) do
- local k = next(v)
- local opts = v[k][2]
- if opts.priority < 0 then
- j = i
- break
+ -- Next check against just the file name
+ local tail = fn.fnamemodify(name, ':t')
+ ft, on_detect = dispatch(filename[tail], path, bufnr)
+ if ft then
+ return ft, on_detect
end
- local filetype = v[k][1]
- local matches = match_pattern(name, path, tail, k)
- if matches then
- ft, on_detect = dispatch(filetype, path, bufnr, matches)
- if ft then
- return ft, on_detect
+ -- Next, check the file path against available patterns with non-negative priority
+ local j = 1
+ for i, v in ipairs(pattern_sorted) do
+ local k = next(v)
+ local opts = v[k][2]
+ if opts.priority < 0 then
+ j = i
+ break
+ end
+
+ local filetype = v[k][1]
+ local matches = match_pattern(name, path, tail, k)
+ if matches then
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
+ end
end
end
- end
- -- Next, check file extension
- local ext = vim.fn.fnamemodify(name, ':e')
- ft, on_detect = dispatch(extension[ext], path, bufnr)
- if ft then
- return ft, on_detect
- end
+ -- Next, check file extension
+ local ext = fn.fnamemodify(name, ':e')
+ ft, on_detect = dispatch(extension[ext], path, bufnr)
+ if ft then
+ return ft, on_detect
+ end
- -- Next, check patterns with negative priority
- for i = j, #pattern_sorted do
- local v = pattern_sorted[i]
- local k = next(v)
+ -- Next, check patterns with negative priority
+ for i = j, #pattern_sorted do
+ local v = pattern_sorted[i]
+ local k = next(v)
- local filetype = v[k][1]
- local matches = match_pattern(name, path, tail, k)
- if matches then
- ft, on_detect = dispatch(filetype, path, bufnr, matches)
- if ft then
- return ft, on_detect
+ local filetype = v[k][1]
+ local matches = match_pattern(name, path, tail, k)
+ if matches then
+ ft, on_detect = dispatch(filetype, path, bufnr, matches)
+ if ft then
+ return ft, on_detect
+ end
end
end
end
@@ -2586,23 +2394,52 @@ function M.match(args)
-- Finally, check file contents
if contents or bufnr then
if contents == nil then
+ assert(bufnr)
if api.nvim_buf_line_count(bufnr) > 101 then
-- only need first 100 and last line for current checks
- contents = M.getlines(bufnr, 1, 100)
- contents[#contents + 1] = M.getlines(bufnr, -1)
+ contents = M._getlines(bufnr, 1, 100)
+ contents[#contents + 1] = M._getline(bufnr, -1)
else
- contents = M.getlines(bufnr)
+ contents = M._getlines(bufnr)
end
end
-- If name is nil, catch any errors from the contents filetype detection function.
-- If the function tries to use the filename that is nil then it will fail,
-- but this enables checks which do not need a filename to still work.
local ok
- ok, ft = pcall(require('vim.filetype.detect').match_contents, contents, name)
- if ok and ft then
- return ft
+ ok, ft, on_detect = pcall(
+ require('vim.filetype.detect').match_contents,
+ contents,
+ name,
+ function(ext)
+ return dispatch(extension[ext], name, bufnr)
+ end
+ )
+ if ok then
+ return ft, on_detect
end
end
end
+--- Get the default option value for a {filetype}.
+---
+--- The returned value is what would be set in a new buffer after 'filetype'
+--- is set, meaning it should respect all FileType autocmds and ftplugin files.
+---
+--- Example:
+---
+--- ```lua
+--- vim.filetype.get_option('vim', 'commentstring')
+--- ```
+---
+--- Note: this uses |nvim_get_option_value()| but caches the result.
+--- This means |ftplugin| and |FileType| autocommands are only
+--- triggered once and may not reflect later changes.
+--- @param filetype string Filetype
+--- @param option string Option name
+--- @return string|boolean|integer: Option value
+function M.get_option(filetype, option)
+ return require('vim.filetype.options').get_option(filetype, option)
+end
+
return M
diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index edffdde9c7..9d0f1bd3ab 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -17,22 +17,26 @@
-- Example:
-- `if line =~ '^\s*unwind_protect\>'` => `if matchregex(line, [[\c^\s*unwind_protect\>]])`
+local fn = vim.fn
+
local M = {}
-local getlines = vim.filetype.getlines
-local findany = vim.filetype.findany
-local nextnonblank = vim.filetype.nextnonblank
-local matchregex = vim.filetype.matchregex
+local getlines = vim.filetype._getlines
+local getline = vim.filetype._getline
+local findany = vim.filetype._findany
+local nextnonblank = vim.filetype._nextnonblank
+local matchregex = vim.filetype._matchregex
-- luacheck: push no unused args
-- luacheck: push ignore 122
-- This function checks for the kind of assembly that is wanted by the user, or
-- can be detected from the first five lines of the file.
-function M.asm(bufnr)
+--- @type vim.filetype.mapfn
+function M.asm(path, bufnr)
local syntax = vim.b[bufnr].asmsyntax
if not syntax or syntax == '' then
- syntax = M.asm_syntax(bufnr)
+ syntax = M.asm_syntax(path, bufnr)
end
-- If b:asmsyntax still isn't set, default to asmsyntax or GNU
@@ -48,20 +52,21 @@ function M.asm(bufnr)
end
end
--- Active Server Pages (with Perl or Visual Basic Script)
-function M.asp(bufnr)
+--- Active Server Pages (with Perl or Visual Basic Script)
+--- @type vim.filetype.mapfn
+function M.asp(_, bufnr)
if vim.g.filetype_asp then
return vim.g.filetype_asp
elseif table.concat(getlines(bufnr, 1, 3)):lower():find('perlscript') then
return 'aspperl'
- else
- return 'aspvbs'
end
+ return 'aspvbs'
end
-- Checks the first 5 lines for a asmsyntax=foo override.
-- Only whitespace characters can be present immediately before or after this statement.
-function M.asm_syntax(bufnr)
+--- @type vim.filetype.mapfn
+function M.asm_syntax(_, bufnr)
local lines = ' ' .. table.concat(getlines(bufnr, 1, 5), ' '):lower() .. ' '
local match = lines:match('%sasmsyntax=([a-zA-Z0-9]+)%s')
if match then
@@ -72,10 +77,11 @@ function M.asm_syntax(bufnr)
end
local visual_basic_content =
- { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform', 'begin vb%.usercontrol' }
+ [[\c^\s*\%(Attribute\s\+VB_Name\|Begin\s\+\%(VB\.\|{\%(\x\+-\)\+\x\+}\)\)]]
-- See frm() for Visual Basic form file detection
-function M.bas(bufnr)
+--- @type vim.filetype.mapfn
+function M.bas(_, bufnr)
if vim.g.filetype_bas then
return vim.g.filetype_bas
end
@@ -91,7 +97,7 @@ function M.bas(bufnr)
local qb64_preproc = [[\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)]]
for _, line in ipairs(getlines(bufnr, 1, 100)) do
- if findany(line:lower(), visual_basic_content) then
+ if matchregex(line, visual_basic_content) then
return 'vb'
elseif
line:find(fb_comment)
@@ -106,18 +112,21 @@ function M.bas(bufnr)
return 'basic'
end
-function M.bindzone(bufnr, default)
+--- @type vim.filetype.mapfn
+function M.bindzone(_, bufnr)
local lines = table.concat(getlines(bufnr, 1, 4))
if findany(lines, { '^; <<>> DiG [0-9%.]+.* <<>>', '%$ORIGIN', '%$TTL', 'IN%s+SOA' }) then
return 'bindzone'
end
- return default
end
-- Returns true if file content looks like RAPID
+--- @param bufnr integer
+--- @param extension? string
+--- @return string|boolean?
local function is_rapid(bufnr, extension)
if extension == 'cfg' then
- local line = getlines(bufnr, 1):lower()
+ local line = getline(bufnr, 1):lower()
return findany(line, { 'eio:cfg', 'mmc:cfg', 'moc:cfg', 'proc:cfg', 'sio:cfg', 'sys:cfg' })
end
local line = nextnonblank(bufnr, 1)
@@ -128,23 +137,24 @@ local function is_rapid(bufnr, extension)
return false
end
-function M.cfg(bufnr)
+--- @type vim.filetype.mapfn
+function M.cfg(_, bufnr)
if vim.g.filetype_cfg then
- return vim.g.filetype_cfg
+ return vim.g.filetype_cfg --[[@as string]]
elseif is_rapid(bufnr, 'cfg') then
return 'rapid'
- else
- return 'cfg'
end
+ return 'cfg'
end
--- This function checks if one of the first ten lines start with a '@'. In
--- that case it is probably a change file.
--- If the first line starts with # or ! it's probably a ch file.
--- If a line has "main", "include", "//" or "/*" it's probably ch.
--- Otherwise CHILL is assumed.
-function M.change(bufnr)
- local first_line = getlines(bufnr, 1)
+--- This function checks if one of the first ten lines start with a '@'. In
+--- that case it is probably a change file.
+--- If the first line starts with # or ! it's probably a ch file.
+--- If a line has "main", "include", "//" or "/*" it's probably ch.
+--- Otherwise CHILL is assumed.
+--- @type vim.filetype.mapfn
+function M.change(_, bufnr)
+ local first_line = getline(bufnr, 1)
if findany(first_line, { '^#', '^!' }) then
return 'ch'
end
@@ -161,41 +171,52 @@ function M.change(bufnr)
return 'chill'
end
-function M.changelog(bufnr)
- local line = getlines(bufnr, 1):lower()
+--- @type vim.filetype.mapfn
+function M.changelog(_, bufnr)
+ local line = getline(bufnr, 1):lower()
if line:find('; urgency=') then
return 'debchangelog'
end
return 'changelog'
end
-function M.class(bufnr)
+--- @type vim.filetype.mapfn
+function M.class(_, bufnr)
-- Check if not a Java class (starts with '\xca\xfe\xba\xbe')
- if not getlines(bufnr, 1):find('^\202\254\186\190') then
+ if not getline(bufnr, 1):find('^\202\254\186\190') then
return 'stata'
end
end
-function M.cls(bufnr)
+--- @type vim.filetype.mapfn
+function M.cls(_, bufnr)
if vim.g.filetype_cls then
return vim.g.filetype_cls
end
- local line = getlines(bufnr, 1)
- if line:find('^[%%\\]') then
- return 'tex'
- elseif line:find('^#') and line:lower():find('rexx') then
+ local line1 = getline(bufnr, 1)
+ if matchregex(line1, [[^#!.*\<\%(rexx\|regina\)\>]]) then
return 'rexx'
- elseif line == 'VERSION 1.0 CLASS' then
+ elseif line1 == 'VERSION 1.0 CLASS' then
return 'vb'
- else
- return 'st'
end
+
+ local nonblank1 = nextnonblank(bufnr, 1)
+ if nonblank1 and nonblank1:find('^[%%\\]') then
+ return 'tex'
+ elseif nonblank1 and findany(nonblank1, { '^%s*/%*', '^%s*::%w' }) then
+ return 'rexx'
+ end
+ return 'st'
end
+--- @type vim.filetype.mapfn
function M.conf(path, bufnr)
- if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
+ if fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then
return
end
+ if path:find('%.conf$') then
+ return 'conf'
+ end
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^#') then
return 'conf'
@@ -203,22 +224,30 @@ function M.conf(path, bufnr)
end
end
--- Debian Control
-function M.control(bufnr)
- if getlines(bufnr, 1):find('^Source:') then
+--- Debian Control
+--- @type vim.filetype.mapfn
+function M.control(_, bufnr)
+ if getline(bufnr, 1):find('^Source:') then
return 'debcontrol'
end
end
--- Debian Copyright
-function M.copyright(bufnr)
- if getlines(bufnr, 1):find('^Format:') then
+--- Debian Copyright
+--- @type vim.filetype.mapfn
+function M.copyright(_, bufnr)
+ if getline(bufnr, 1):find('^Format:') then
return 'debcopyright'
end
end
+--- @type vim.filetype.mapfn
+function M.cpp(_, _)
+ return vim.g.cynlib_syntax_for_cpp and 'cynlib' or 'cpp'
+end
+
+--- @type vim.filetype.mapfn
function M.csh(path, bufnr)
- if vim.fn.did_filetype() ~= 0 then
+ if fn.did_filetype() ~= 0 then
-- Filetype was already detected
return
end
@@ -232,6 +261,9 @@ function M.csh(path, bufnr)
end
end
+--- @param path string
+--- @param contents string[]
+--- @return string?
local function cvs_diff(path, contents)
for _, line in ipairs(contents) do
if not line:find('^%? ') then
@@ -277,8 +309,9 @@ local function cvs_diff(path, contents)
end
end
+--- @type vim.filetype.mapfn
function M.dat(path, bufnr)
- local file_name = vim.fn.fnamemodify(path, ':t'):lower()
+ local file_name = fn.fnamemodify(path, ':t'):lower()
-- Innovation data processing
if findany(file_name, { '^upstream%.dat$', '^upstream%..*%.dat$', '^.*%.upstream%.dat$' }) then
return 'upstreamdat'
@@ -293,7 +326,8 @@ function M.dat(path, bufnr)
end
end
-function M.decl(bufnr)
+--- @type vim.filetype.mapfn
+function M.decl(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 3)) do
if line:lower():find('^<!sgml') then
return 'sgmldecl'
@@ -303,8 +337,9 @@ end
-- This function is called for all files under */debian/patches/*, make sure not
-- to non-dep3patch files, such as README and other text files.
+--- @type vim.filetype.mapfn
function M.dep3patch(path, bufnr)
- local file_name = vim.fn.fnamemodify(path, ':t')
+ local file_name = fn.fnamemodify(path, ':t')
if file_name == 'series' then
return
end
@@ -349,7 +384,7 @@ local function diff(contents)
end
end
-function M.dns_zone(contents)
+local function dns_zone(contents)
if
findany(
contents[1] .. contents[2] .. contents[3] .. contents[4],
@@ -367,8 +402,9 @@ function M.dns_zone(contents)
end
end
-function M.dtrace(bufnr)
- if vim.fn.did_filetype() ~= 0 then
+--- @type vim.filetype.mapfn
+function M.dtrace(_, bufnr)
+ if fn.did_filetype() ~= 0 then
-- Filetype was already detected
return
end
@@ -383,7 +419,8 @@ function M.dtrace(bufnr)
return 'd'
end
-function M.e(bufnr)
+--- @type vim.filetype.mapfn
+function M.e(_, bufnr)
if vim.g.filetype_euphoria then
return vim.g.filetype_euphoria
end
@@ -395,8 +432,9 @@ function M.e(bufnr)
return 'eiffel'
end
-function M.edn(bufnr)
- local line = getlines(bufnr, 1)
+--- @type vim.filetype.mapfn
+function M.edn(_, bufnr)
+ local line = getline(bufnr, 1)
if matchregex(line, [[\c^\s*(\s*edif\>]]) then
return 'edif'
else
@@ -407,7 +445,8 @@ end
-- This function checks for valid cl syntax in the first five lines.
-- Look for either an opening comment, '#', or a block start, '{'.
-- If not found, assume SGML.
-function M.ent(bufnr)
+--- @type vim.filetype.mapfn
+function M.ent(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^%s*[#{]') then
return 'cl'
@@ -420,7 +459,13 @@ function M.ent(bufnr)
return 'dtd'
end
-function M.ex(bufnr)
+--- @type vim.filetype.mapfn
+function M.euphoria(_, _)
+ return vim.g.filetype_euphoria or 'euphoria3'
+end
+
+--- @type vim.filetype.mapfn
+function M.ex(_, bufnr)
if vim.g.filetype_euphoria then
return vim.g.filetype_euphoria
else
@@ -433,10 +478,43 @@ function M.ex(bufnr)
end
end
+--- @param bufnr integer
+--- @return boolean
+local function is_forth(bufnr)
+ local first_line = nextnonblank(bufnr, 1)
+
+ -- SwiftForth block comment (line is usually filled with '-' or '=') or
+ -- OPTIONAL (sometimes precedes the header comment)
+ if first_line and findany(first_line:lower(), { '^%{%s', '^%{$', '^optional%s' }) then
+ return true
+ end
+
+ for _, line in ipairs(getlines(bufnr, 1, 100)) do
+ -- Forth comments and colon definitions
+ if line:find('^[:(\\] ') then
+ return true
+ end
+ end
+ return false
+end
+
+-- Distinguish between Forth and Fortran
+--- @type vim.filetype.mapfn
+function M.f(_, bufnr)
+ if vim.g.filetype_f then
+ return vim.g.filetype_f
+ end
+ if is_forth(bufnr) then
+ return 'forth'
+ end
+ return 'fortran'
+end
+
-- This function checks the first 15 lines for appearance of 'FoamFile'
-- and then 'object' in a following line.
-- In that case, it's probably an OpenFOAM file
-function M.foam(bufnr)
+--- @type vim.filetype.mapfn
+function M.foam(_, bufnr)
local foam_file = false
for _, line in ipairs(getlines(bufnr, 1, 15)) do
if line:find('^FoamFile') then
@@ -447,20 +525,32 @@ function M.foam(bufnr)
end
end
-function M.frm(bufnr)
+--- @type vim.filetype.mapfn
+function M.frm(_, bufnr)
if vim.g.filetype_frm then
return vim.g.filetype_frm
end
- local lines = table.concat(getlines(bufnr, 1, 5)):lower()
- if findany(lines, visual_basic_content) then
+ if getline(bufnr, 1) == 'VERSION 5.00' then
return 'vb'
- else
- return 'form'
end
+ for _, line in ipairs(getlines(bufnr, 1, 5)) do
+ if matchregex(line, visual_basic_content) then
+ return 'vb'
+ end
+ end
+ return 'form'
end
-function M.fvwm(path)
- if vim.fn.fnamemodify(path, ':e') == 'm4' then
+--- @type vim.filetype.mapfn
+function M.fvwm_1(_, _)
+ return 'fvwm', function(bufnr)
+ vim.b[bufnr].fvwm_version = 1
+ end
+end
+
+--- @type vim.filetype.mapfn
+function M.fvwm_v2(path, _)
+ if fn.fnamemodify(path, ':e') == 'm4' then
return 'fvwm2m4'
end
return 'fvwm', function(bufnr)
@@ -468,27 +558,28 @@ function M.fvwm(path)
end
end
--- Distinguish between Forth and F#.
-function M.fs(bufnr)
+-- Distinguish between Forth and F#
+--- @type vim.filetype.mapfn
+function M.fs(_, bufnr)
if vim.g.filetype_fs then
return vim.g.filetype_fs
end
- local line = nextnonblank(bufnr, 1)
- if findany(line, { '^%s*%.?%( ', '^%s*\\G? ', '^\\$', '^%s*: %S' }) then
+ if is_forth(bufnr) then
return 'forth'
- else
- return 'fsharp'
end
+ return 'fsharp'
end
-function M.git(bufnr)
- local line = getlines(bufnr, 1)
+--- @type vim.filetype.mapfn
+function M.git(_, bufnr)
+ local line = getline(bufnr, 1)
if matchregex(line, [[^\x\{40,\}\>\|^ref: ]]) then
return 'git'
end
end
-function M.header(bufnr)
+--- @type vim.filetype.mapfn
+function M.header(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 200)) do
if findany(line:lower(), { '^@interface', '^@end', '^@class' }) then
if vim.g.c_syntax_for_h then
@@ -507,7 +598,8 @@ function M.header(bufnr)
end
end
-function M.html(bufnr)
+--- @type vim.filetype.mapfn
+function M.html(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 10)) do
if matchregex(line, [[\<DTD\s\+XHTML\s]]) then
return 'xhtml'
@@ -519,14 +611,41 @@ function M.html(bufnr)
end
-- Virata Config Script File or Drupal module
-function M.hw(bufnr)
- if getlines(bufnr, 1):lower():find('<%?php') then
+--- @type vim.filetype.mapfn
+function M.hw(_, bufnr)
+ if getline(bufnr, 1):lower():find('<%?php') then
return 'php'
end
return 'virata'
end
-function M.idl(bufnr)
+-- This function checks for an assembly comment or a SWIG keyword or verbatim
+-- block in the first 50 lines.
+-- If not found, assume Progress.
+--- @type vim.filetype.mapfn
+function M.i(path, bufnr)
+ if vim.g.filetype_i then
+ return vim.g.filetype_i
+ end
+
+ -- These include the leading '%' sign
+ local ft_swig_keywords =
+ [[^\s*%\%(addmethods\|apply\|beginfile\|clear\|constant\|define\|echo\|enddef\|endoffile\|extend\|feature\|fragment\|ignore\|import\|importfile\|include\|includefile\|inline\|insert\|keyword\|module\|name\|namewarn\|native\|newobject\|parms\|pragma\|rename\|template\|typedef\|typemap\|types\|varargs\|warn\)]]
+ -- This is the start/end of a block that is copied literally to the processor file (C/C++)
+ local ft_swig_verbatim_block_start = '^%s*%%{'
+
+ for _, line in ipairs(getlines(bufnr, 1, 50)) do
+ if line:find('^%s*;') or line:find('^%*') then
+ return M.asm(path, bufnr)
+ elseif matchregex(line, ft_swig_keywords) or line:find(ft_swig_verbatim_block_start) then
+ return 'swig'
+ end
+ end
+ return 'progress'
+end
+
+--- @type vim.filetype.mapfn
+function M.idl(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 50)) do
if findany(line:lower(), { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then
return 'msidl'
@@ -539,7 +658,8 @@ local pascal_comments = { '^%s*{', '^%s*%(%*', '^%s*//' }
local pascal_keywords =
[[\c^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>]]
-function M.inc(bufnr)
+--- @type vim.filetype.mapfn
+function M.inc(path, bufnr)
if vim.g.filetype_inc then
return vim.g.filetype_inc
end
@@ -557,7 +677,7 @@ function M.inc(bufnr)
elseif findany(lines, { '^%s*inherit ', '^%s*require ', '^%s*%u[%w_:${}]*%s+%??[?:+]?= ' }) then
return 'bitbake'
else
- local syntax = M.asm_syntax(bufnr)
+ local syntax = M.asm_syntax(path, bufnr)
if not syntax or syntax == '' then
return 'pov'
end
@@ -567,8 +687,9 @@ function M.inc(bufnr)
end
end
-function M.inp(bufnr)
- if getlines(bufnr, 1):find('^%*') then
+--- @type vim.filetype.mapfn
+function M.inp(_, bufnr)
+ if getline(bufnr, 1):find('^%*') then
return 'abaqus'
else
for _, line in ipairs(getlines(bufnr, 1, 500)) do
@@ -579,16 +700,18 @@ function M.inp(bufnr)
end
end
+--- @type vim.filetype.mapfn
function M.install(path, bufnr)
- if getlines(bufnr, 1):lower():find('<%?php') then
+ if getline(bufnr, 1):lower():find('<%?php') then
return 'php'
end
- return M.sh(path, getlines(bufnr), 'bash')
+ return M.bash(path, bufnr)
end
--- Innovation Data Processing
--- (refactor of filetype.vim since the patterns are case-insensitive)
-function M.log(path)
+--- Innovation Data Processing
+--- (refactor of filetype.vim since the patterns are case-insensitive)
+--- @type vim.filetype.mapfn
+function M.log(path, _)
path = path:lower()
if
findany(
@@ -611,7 +734,8 @@ function M.log(path)
end
end
-function M.lpc(bufnr)
+--- @type vim.filetype.mapfn
+function M.lpc(_, bufnr)
if vim.g.lpc_syntax_for_c then
for _, line in ipairs(getlines(bufnr, 1, 12)) do
if
@@ -634,7 +758,8 @@ function M.lpc(bufnr)
return 'c'
end
-function M.lsl(bufnr)
+--- @type vim.filetype.mapfn
+function M.lsl(_, bufnr)
if vim.g.filetype_lsl then
return vim.g.filetype_lsl
end
@@ -647,7 +772,8 @@ function M.lsl(bufnr)
end
end
-function M.m(bufnr)
+--- @type vim.filetype.mapfn
+function M.m(_, bufnr)
if vim.g.filetype_m then
return vim.g.filetype_m
end
@@ -700,6 +826,8 @@ function M.m(bufnr)
end
end
+--- @param contents string[]
+--- @return string?
local function m4(contents)
for _, line in ipairs(contents) do
if matchregex(line, [[^\s*dnl\>]]) then
@@ -712,9 +840,10 @@ local function m4(contents)
end
end
--- Rely on the file to start with a comment.
--- MS message text files use ';', Sendmail files use '#' or 'dnl'
-function M.mc(bufnr)
+--- Rely on the file to start with a comment.
+--- MS message text files use ';', Sendmail files use '#' or 'dnl'
+--- @type vim.filetype.mapfn
+function M.mc(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do
if findany(line:lower(), { '^%s*#', '^%s*dnl' }) then
-- Sendmail .mc file
@@ -727,14 +856,17 @@ function M.mc(bufnr)
return 'm4'
end
+--- @param path string
+--- @return string?
function M.me(path)
- local filename = vim.fn.fnamemodify(path, ':t'):lower()
+ local filename = fn.fnamemodify(path, ':t'):lower()
if filename ~= 'read.me' and filename ~= 'click.me' then
return 'nroff'
end
end
-function M.mm(bufnr)
+--- @type vim.filetype.mapfn
+function M.mm(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do
if matchregex(line, [[\c^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)]]) then
return 'objcpp'
@@ -743,7 +875,8 @@ function M.mm(bufnr)
return 'nroff'
end
-function M.mms(bufnr)
+--- @type vim.filetype.mapfn
+function M.mms(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 20)) do
if findany(line, { '^%s*%%', '^%s*//', '^%*' }) then
return 'mmix'
@@ -754,7 +887,9 @@ function M.mms(bufnr)
return 'mmix'
end
--- Returns true if file content looks like LambdaProlog
+--- Returns true if file content looks like LambdaProlog
+--- @param bufnr integer
+--- @return boolean
local function is_lprolog(bufnr)
-- Skip apparent comments and blank lines, what looks like
-- LambdaProlog comment may be RAPID header
@@ -762,38 +897,49 @@ local function is_lprolog(bufnr)
-- The second pattern matches a LambdaProlog comment
if not findany(line, { '^%s*$', '^%s*%%' }) then
-- The pattern must not catch a go.mod file
- return matchregex(line, [[\c\<module\s\+\w\+\s*\.\s*\(%\|$\)]]) ~= nil
+ return matchregex(line, [[\c\<module\s\+\w\+\s*\.\s*\(%\|$\)]])
end
end
+ return false
end
--- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+--- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+--- @type vim.filetype.mapfn
function M.mod(path, bufnr)
if vim.g.filetype_mod then
return vim.g.filetype_mod
+ elseif matchregex(path, [[\c\<go\.mod$]]) then
+ return 'gomod'
elseif is_lprolog(bufnr) then
return 'lprolog'
elseif matchregex(nextnonblank(bufnr, 1), [[\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)]]) then
return 'modula2'
elseif is_rapid(bufnr) then
return 'rapid'
- elseif matchregex(path, [[\c\<go\.mod$]]) then
- return 'gomod'
- else
- -- Nothing recognized, assume modsim3
- return 'modsim3'
end
+ -- Nothing recognized, assume modsim3
+ return 'modsim3'
end
-function M.news(bufnr)
- if getlines(bufnr, 1):lower():find('; urgency=') then
+--- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
+--- @type vim.filetype.mapfn
+function M.mp(_, _)
+ return 'mp', function(b)
+ vim.b[b].mp_metafun = 1
+ end
+end
+
+--- @type vim.filetype.mapfn
+function M.news(_, bufnr)
+ if getline(bufnr, 1):lower():find('; urgency=') then
return 'debchangelog'
end
end
--- This function checks if one of the first five lines start with a dot. In
--- that case it is probably an nroff file.
-function M.nroff(bufnr)
+--- This function checks if one of the first five lines start with a dot. In
+--- that case it is probably an nroff file.
+--- @type vim.filetype.mapfn
+function M.nroff(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^%.') then
return 'nroff'
@@ -801,25 +947,26 @@ function M.nroff(bufnr)
end
end
-function M.patch(bufnr)
- local firstline = getlines(bufnr, 1)
+--- @type vim.filetype.mapfn
+function M.patch(_, bufnr)
+ local firstline = getline(bufnr, 1)
if string.find(firstline, '^From ' .. string.rep('%x', 40) .. '+ Mon Sep 17 00:00:00 2001$') then
return 'gitsendemail'
- else
- return 'diff'
end
+ return 'diff'
end
--- If the file has an extension of 't' and is in a directory 't' or 'xt' then
--- it is almost certainly a Perl test file.
--- If the first line starts with '#' and contains 'perl' it's probably a Perl file.
--- (Slow test) If a file contains a 'use' statement then it is almost certainly a Perl file.
+--- If the file has an extension of 't' and is in a directory 't' or 'xt' then
+--- it is almost certainly a Perl test file.
+--- If the first line starts with '#' and contains 'perl' it's probably a Perl file.
+--- (Slow test) If a file contains a 'use' statement then it is almost certainly a Perl file.
+--- @type vim.filetype.mapfn
function M.perl(path, bufnr)
local dir_name = vim.fs.dirname(path)
- if vim.fn.expand(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then
+ if fn.expand(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then
return 'perl'
end
- local first_line = getlines(bufnr, 1)
+ local first_line = getline(bufnr, 1)
if first_line:find('^#') and first_line:lower():find('perl') then
return 'perl'
end
@@ -830,7 +977,8 @@ function M.perl(path, bufnr)
end
end
-function M.pl(bufnr)
+--- @type vim.filetype.mapfn
+function M.pl(_, bufnr)
if vim.g.filetype_pl then
return vim.g.filetype_pl
end
@@ -848,8 +996,9 @@ function M.pl(bufnr)
end
end
-function M.pm(bufnr)
- local line = getlines(bufnr, 1)
+--- @type vim.filetype.mapfn
+function M.pm(_, bufnr)
+ local line = getline(bufnr, 1)
if line:find('XPM2') then
return 'xpm2'
elseif line:find('XPM') then
@@ -859,7 +1008,8 @@ function M.pm(bufnr)
end
end
-function M.pp(bufnr)
+--- @type vim.filetype.mapfn
+function M.pp(_, bufnr)
if vim.g.filetype_pp then
return vim.g.filetype_pp
end
@@ -871,7 +1021,8 @@ function M.pp(bufnr)
end
end
-function M.prg(bufnr)
+--- @type vim.filetype.mapfn
+function M.prg(_, bufnr)
if vim.g.filetype_prg then
return vim.g.filetype_prg
elseif is_rapid(bufnr) then
@@ -883,39 +1034,21 @@ function M.prg(bufnr)
end
function M.printcap(ptcap_type)
- if vim.fn.did_filetype() == 0 then
+ if fn.did_filetype() == 0 then
return 'ptcap', function(bufnr)
vim.b[bufnr].ptcap_type = ptcap_type
end
end
end
--- This function checks for an assembly comment in the first ten lines.
--- If not found, assume Progress.
-function M.progress_asm(bufnr)
- if vim.g.filetype_i then
- return vim.g.filetype_i
- end
-
- for _, line in ipairs(getlines(bufnr, 1, 10)) do
- if line:find('^%s*;') or line:find('^/%*') then
- return M.asm(bufnr)
- elseif not line:find('^%s*$') or line:find('^/%*') then
- -- Not an empty line: doesn't look like valid assembly code
- -- or it looks like a Progress /* comment.
- break
- end
- end
- return 'progress'
-end
-
-function M.progress_cweb(bufnr)
+--- @type vim.filetype.mapfn
+function M.progress_cweb(_, bufnr)
if vim.g.filetype_w then
return vim.g.filetype_w
else
if
- getlines(bufnr, 1):lower():find('^&analyze')
- or getlines(bufnr, 3):lower():find('^&global%-define')
+ getline(bufnr, 1):lower():find('^&analyze')
+ or getline(bufnr, 3):lower():find('^&global%-define')
then
return 'progress'
else
@@ -927,7 +1060,8 @@ end
-- This function checks for valid Pascal syntax in the first 10 lines.
-- Look for either an opening comment or a program start.
-- If not found, assume Progress.
-function M.progress_pascal(bufnr)
+--- @type vim.filetype.mapfn
+function M.progress_pascal(_, bufnr)
if vim.g.filetype_p then
return vim.g.filetype_p
end
@@ -943,34 +1077,33 @@ function M.progress_pascal(bufnr)
return 'progress'
end
--- Distinguish between "default", Prolog and Cproto prototype file.
-function M.proto(bufnr, default)
+--- Distinguish between "default", Prolog and Cproto prototype file.
+--- @type vim.filetype.mapfn
+function M.proto(_, bufnr)
-- Cproto files have a comment in the first line and a function prototype in
-- the second line, it always ends in ";". Indent files may also have
-- comments, thus we can't match comments to see the difference.
-- IDL files can have a single ';' in the second line, require at least one
-- character before the ';'.
- if getlines(bufnr, 2):find('.;$') then
+ if getline(bufnr, 2):find('.;$') then
return 'cpp'
- else
- -- Recognize Prolog by specific text in the first non-empty line;
- -- require a blank after the '%' because Perl uses "%list" and "%translate"
- local line = nextnonblank(bufnr, 1)
- if
- line and line:find(':%-')
- or matchregex(line, [[\c\<prolog\>]])
- or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' })
- then
- return 'prolog'
- else
- return default
- end
+ end
+ -- Recognize Prolog by specific text in the first non-empty line;
+ -- require a blank after the '%' because Perl uses "%list" and "%translate"
+ local line = nextnonblank(bufnr, 1)
+ if
+ line and line:find(':%-')
+ or matchregex(line, [[\c\<prolog\>]])
+ or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' })
+ then
+ return 'prolog'
end
end
-- Software Distributor Product Specification File (POSIX 1387.2-1995)
-function M.psf(bufnr)
- local line = getlines(bufnr, 1):lower()
+--- @type vim.filetype.mapfn
+function M.psf(_, bufnr)
+ local line = getline(bufnr, 1):lower()
if
findany(line, {
'^%s*distribution%s*$',
@@ -984,7 +1117,8 @@ function M.psf(bufnr)
end
end
-function M.r(bufnr)
+--- @type vim.filetype.mapfn
+function M.r(_, bufnr)
local lines = getlines(bufnr, 1, 50)
-- Rebol is easy to recognize, check for that first
if matchregex(table.concat(lines), [[\c\<rebol\>]]) then
@@ -1005,13 +1139,13 @@ function M.r(bufnr)
-- Nothing recognized, use user default or assume R
if vim.g.filetype_r then
return vim.g.filetype_r
- else
- -- Rexx used to be the default, but R appears to be much more popular.
- return 'r'
end
+ -- Rexx used to be the default, but R appears to be much more popular.
+ return 'r'
end
-function M.redif(bufnr)
+--- @type vim.filetype.mapfn
+function M.redif(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:lower():find('^template%-type:') then
return 'redif'
@@ -1019,8 +1153,9 @@ function M.redif(bufnr)
end
end
-function M.reg(bufnr)
- local line = getlines(bufnr, 1):lower()
+--- @type vim.filetype.mapfn
+function M.reg(_, bufnr)
+ local line = getline(bufnr, 1):lower()
if
line:find('^regedit[0-9]*%s*$') or line:find('^windows registry editor version %d*%.%d*%s*$')
then
@@ -1029,7 +1164,8 @@ function M.reg(bufnr)
end
-- Diva (with Skill) or InstallShield
-function M.rul(bufnr)
+--- @type vim.filetype.mapfn
+function M.rul(_, bufnr)
if table.concat(getlines(bufnr, 1, 6)):lower():find('installshield') then
return 'ishd'
end
@@ -1037,6 +1173,7 @@ function M.rul(bufnr)
end
local udev_rules_pattern = '^%s*udev_rules%s*=%s*"([%^"]+)/*".*'
+--- @type vim.filetype.mapfn
function M.rules(path)
path = path:lower()
if
@@ -1056,11 +1193,12 @@ function M.rules(path)
elseif findany(path, { '^/etc/polkit%-1/rules%.d', '/usr/share/polkit%-1/rules%.d' }) then
return 'javascript'
else
- local ok, config_lines = pcall(vim.fn.readfile, '/etc/udev/udev.conf')
+ local ok, config_lines = pcall(fn.readfile, '/etc/udev/udev.conf')
+ --- @cast config_lines +string[]
if not ok then
return 'hog'
end
- local dir = vim.fn.expand(path, ':h')
+ local dir = fn.expand(path, ':h')
for _, line in ipairs(config_lines) do
local match = line:match(udev_rules_pattern)
if match then
@@ -1075,7 +1213,8 @@ function M.rules(path)
end
-- LambdaProlog and Standard ML signature files
-function M.sig(bufnr)
+--- @type vim.filetype.mapfn
+function M.sig(_, bufnr)
if vim.g.filetype_sig then
return vim.g.filetype_sig
end
@@ -1093,7 +1232,8 @@ end
-- This function checks the first 25 lines of file extension "sc" to resolve
-- detection between scala and SuperCollider
-function M.sc(bufnr)
+--- @type vim.filetype.mapfn
+function M.sc(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 25)) do
if
findany(line, {
@@ -1113,18 +1253,19 @@ end
-- This function checks the first line of file extension "scd" to resolve
-- detection between scdoc and SuperCollider
-function M.scd(bufnr)
+--- @type vim.filetype.mapfn
+function M.scd(_, bufnr)
local first = '^%S+%(%d[0-9A-Za-z]*%)'
local opt = [[%s+"[^"]*"]]
- local line = getlines(bufnr, 1)
+ local line = getline(bufnr, 1)
if findany(line, { first .. '$', first .. opt .. '$', first .. opt .. opt .. '$' }) then
return 'scdoc'
- else
- return 'supercollider'
end
+ return 'supercollider'
end
-function M.sgml(bufnr)
+--- @type vim.filetype.mapfn
+function M.sgml(_, bufnr)
local lines = table.concat(getlines(bufnr, 1, 5))
if lines:find('linuxdoc') then
return 'smgllnx'
@@ -1139,15 +1280,17 @@ function M.sgml(bufnr)
end
end
-function M.sh(path, contents, name)
+--- @param path string
+--- @param contents string[]
+--- @param name? string
+--- @return string?, fun(b: integer)?
+local function sh(path, contents, name)
-- Path may be nil, do not fail in that case
- if vim.fn.did_filetype() ~= 0 or (path or ''):find(vim.g.ft_ignore_pat) then
+ if fn.did_filetype() ~= 0 or (path or ''):find(vim.g.ft_ignore_pat) then
-- Filetype was already detected or detection should be skipped
return
end
- local on_detect
-
-- Get the name from the first line if not specified
name = name or contents[1]
if matchregex(name, [[\<csh\>]]) then
@@ -1159,7 +1302,11 @@ function M.sh(path, contents, name)
-- Some .sh scripts contain #!/bin/zsh.
elseif matchregex(name, [[\<zsh\>]]) then
return M.shell(path, contents, 'zsh')
- elseif matchregex(name, [[\<ksh\>]]) then
+ end
+
+ local on_detect --- @type fun(b: integer)?
+
+ if matchregex(name, [[\<ksh\>]]) then
on_detect = function(b)
vim.b[b].is_kornshell = 1
vim.b[b].is_bash = nil
@@ -1182,9 +1329,26 @@ function M.sh(path, contents, name)
return M.shell(path, contents, 'sh'), on_detect
end
--- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
+--- @param name? string
+--- @return vim.filetype.mapfn
+local function sh_with(name)
+ return function(path, bufnr)
+ return sh(path, getlines(bufnr), name)
+ end
+end
+
+M.sh = sh_with()
+M.bash = sh_with('bash')
+M.ksh = sh_with('ksh')
+M.tcsh = sh_with('tcsh')
+
+--- For shell-like file types, check for an "exec" command hidden in a comment, as used for Tcl.
+--- @param path string
+--- @param contents string[]
+--- @param name? string
+--- @return string?
function M.shell(path, contents, name)
- if vim.fn.did_filetype() ~= 0 or matchregex(path, vim.g.ft_ignore_pat) then
+ if fn.did_filetype() ~= 0 or matchregex(path, vim.g.ft_ignore_pat) then
-- Filetype was already detected or detection should be skipped
return
end
@@ -1193,6 +1357,7 @@ function M.shell(path, contents, name)
for line_nr, line in ipairs(contents) do
-- Skip the first line
if line_nr ~= 1 then
+ --- @type string
line = line:lower()
if line:find('%s*exec%s') and not prev_line:find('^%s*#.*\\$') then
-- Found an "exec" line after a comment with continuation
@@ -1208,7 +1373,8 @@ function M.shell(path, contents, name)
end
-- Swift Intermediate Language or SILE
-function M.sil(bufnr)
+--- @type vim.filetype.mapfn
+function M.sil(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do
if line:find('^%s*[\\%%]') then
return 'sile'
@@ -1221,8 +1387,9 @@ function M.sil(bufnr)
end
-- SMIL or SNMP MIB file
-function M.smi(bufnr)
- local line = getlines(bufnr, 1)
+--- @type vim.filetype.mapfn
+function M.smi(_, bufnr)
+ local line = getline(bufnr, 1)
if matchregex(line, [[\c\<smil\>]]) then
return 'smil'
else
@@ -1230,8 +1397,14 @@ function M.smi(bufnr)
end
end
+--- @type vim.filetype.mapfn
+function M.sql(_, _)
+ return vim.g.filetype_sql and vim.g.filetype_sql or 'sql'
+end
+
-- Determine if a *.src file is Kuka Robot Language
-function M.src(bufnr)
+--- @type vim.filetype.mapfn
+function M.src(_, bufnr)
if vim.g.filetype_src then
return vim.g.filetype_src
end
@@ -1241,23 +1414,25 @@ function M.src(bufnr)
end
end
-function M.sys(bufnr)
+--- @type vim.filetype.mapfn
+function M.sys(_, bufnr)
if vim.g.filetype_sys then
return vim.g.filetype_sys
elseif is_rapid(bufnr) then
return 'rapid'
- else
- return 'bat'
end
+ return 'bat'
end
-- Choose context, plaintex, or tex (LaTeX) based on these rules:
-- 1. Check the first line of the file for "%&<format>".
-- 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords.
-- 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc.
+--- @type vim.filetype.mapfn
function M.tex(path, bufnr)
- local matched, _, format = getlines(bufnr, 1):find('^%%&%s*(%a+)')
+ local matched, _, format = getline(bufnr, 1):find('^%%&%s*(%a+)')
if matched then
+ --- @type string
format = format:lower():gsub('pdf', '', 1)
elseif path:lower():find('tex/context/.*/.*%.tex') then
return 'context'
@@ -1296,7 +1471,8 @@ function M.tex(path, bufnr)
end
-- Determine if a *.tf file is TF mud client or terraform
-function M.tf(bufnr)
+--- @type vim.filetype.mapfn
+function M.tf(_, bufnr)
for _, line in ipairs(getlines(bufnr)) do
-- Assume terraform file on a non-empty line (not whitespace-only)
-- and when the first non-whitespace character is not a ; or /
@@ -1307,24 +1483,77 @@ function M.tf(bufnr)
return 'tf'
end
-function M.ttl(bufnr)
- local line = getlines(bufnr, 1):lower()
+--- @type vim.filetype.mapfn
+function M.ttl(_, bufnr)
+ local line = getline(bufnr, 1):lower()
if line:find('^@?prefix') or line:find('^@?base') then
return 'turtle'
end
return 'teraterm'
end
-function M.txt(bufnr)
+--- @type vim.filetype.mapfn
+function M.txt(_, bufnr)
-- helpfiles match *.txt, but should have a modeline as last line
- if not getlines(bufnr, -1):find('vim:.*ft=help') then
+ if not getline(bufnr, -1):find('vim:.*ft=help') then
return 'text'
end
end
+--- @type vim.filetype.mapfn
+function M.typ(_, bufnr)
+ if vim.g.filetype_typ then
+ return vim.g.filetype_typ
+ end
+
+ for _, line in ipairs(getlines(bufnr, 1, 200)) do
+ if
+ findany(line, {
+ '^CASE[%s]?=[%s]?SAME$',
+ '^CASE[%s]?=[%s]?LOWER$',
+ '^CASE[%s]?=[%s]?UPPER$',
+ '^CASE[%s]?=[%s]?OPPOSITE$',
+ '^TYPE%s',
+ })
+ then
+ return 'sql'
+ end
+ end
+
+ return 'typst'
+end
+
+-- Determine if a .v file is Verilog, V, or Coq
+--- @type vim.filetype.mapfn
+function M.v(_, bufnr)
+ if fn.did_filetype() ~= 0 then
+ -- Filetype was already detected
+ return
+ end
+ for _, line in ipairs(getlines(bufnr, 1, 200)) do
+ if not line:find('^%s*/') then
+ if findany(line, { ';%s*$', ';%s*/' }) then
+ return 'verilog'
+ elseif findany(line, { '%.%s*$', '%.%s*%(%*' }) then
+ return 'coq'
+ end
+ end
+ end
+ return 'v'
+end
+
+--- @type vim.filetype.mapfn
+function M.vba(_, bufnr)
+ if getline(bufnr, 1):find('^["#] Vimball Archiver') then
+ return 'vim'
+ end
+ return 'vb'
+end
+
-- WEB (*.web is also used for Winbatch: Guess, based on expecting "%" comment
-- lines in a WEB file).
-function M.web(bufnr)
+--- @type vim.filetype.mapfn
+function M.web(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 5)) do
if line:find('^%%') then
return 'web'
@@ -1334,17 +1563,27 @@ function M.web(bufnr)
end
-- XFree86 config
-function M.xfree86()
+--- @type vim.filetype.mapfn
+function M.xfree86_v3(_, _)
return 'xf86conf',
function(bufnr)
- local line = getlines(bufnr, 1)
+ local line = getline(bufnr, 1)
if matchregex(line, [[\<XConfigurator\>]]) then
vim.b[bufnr].xf86conf_xfree86_version = 3
end
end
end
-function M.xml(bufnr)
+-- XFree86 config
+--- @type vim.filetype.mapfn
+function M.xfree86_v4(_, _)
+ return 'xf86conf', function(b)
+ vim.b[b].xf86conf_xfree86_version = 4
+ end
+end
+
+--- @type vim.filetype.mapfn
+function M.xml(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do
local is_docbook4 = line:find('<!DOCTYPE.*DocBook')
line = line:lower()
@@ -1363,7 +1602,8 @@ function M.xml(bufnr)
return 'xml'
end
-function M.y(bufnr)
+--- @type vim.filetype.mapfn
+function M.y(_, bufnr)
for _, line in ipairs(getlines(bufnr, 1, 100)) do
if line:find('^%s*%%') then
return 'yacc'
@@ -1416,11 +1656,21 @@ local patterns_hashbang = {
['gforth\\>'] = { 'forth', { vim_regex = true } },
['icon\\>'] = { 'icon', { vim_regex = true } },
guile = 'scheme',
+ ['nix%-shell'] = 'nix',
+ ['^crystal\\>'] = { 'crystal', { vim_regex = true } },
+ ['^\\%(rexx\\|regina\\)\\>'] = { 'rexx', { vim_regex = true } },
+ ['^janet\\>'] = { 'janet', { vim_regex = true } },
+ ['^dart\\>'] = { 'dart', { vim_regex = true } },
}
---@private
--- File starts with "#!".
-local function match_from_hashbang(contents, path)
+--- File starts with "#!".
+--- @param contents string[]
+--- @param path string
+--- @param dispatch_extension fun(name: string): string?, fun(b: integer)?
+--- @return string?
+--- @return fun(b: integer)?
+local function match_from_hashbang(contents, path, dispatch_extension)
local first_line = contents[1]
-- Check for a line like "#!/usr/bin/env {options} bash". Turn it into
-- "#!/usr/bin/bash" to make matching easier.
@@ -1431,7 +1681,7 @@ local function match_from_hashbang(contents, path)
:gsub('%-%-ignore%-environment', '', 1)
:gsub('%-%-split%-string', '', 1)
:gsub('%-[iS]', '', 1)
- first_line = vim.fn.substitute(first_line, [[\<env\s\+]], '', '')
+ first_line = fn.substitute(first_line, [[\<env\s\+]], '', '')
end
-- Get the program name.
@@ -1440,15 +1690,15 @@ local function match_from_hashbang(contents, path)
-- "#!/usr/bin/env perl [path/args]"
-- If there is no path use the first word: "#!perl [path/args]".
-- Otherwise get the last word after a slash: "#!/usr/bin/perl [path/args]".
- local name
+ local name --- @type string
if first_line:find('^#!%s*%a:[/\\]') then
- name = vim.fn.substitute(first_line, [[^#!.*[/\\]\(\i\+\).*]], '\\1', '')
+ name = fn.substitute(first_line, [[^#!.*[/\\]\(\i\+\).*]], '\\1', '')
elseif matchregex(first_line, [[^#!.*\<env\>]]) then
- name = vim.fn.substitute(first_line, [[^#!.*\<env\>\s\+\(\i\+\).*]], '\\1', '')
+ name = fn.substitute(first_line, [[^#!.*\<env\>\s\+\(\i\+\).*]], '\\1', '')
elseif matchregex(first_line, [[^#!\s*[^/\\ ]*\>\([^/\\]\|$\)]]) then
- name = vim.fn.substitute(first_line, [[^#!\s*\([^/\\ ]*\>\).*]], '\\1', '')
+ name = fn.substitute(first_line, [[^#!\s*\([^/\\ ]*\>\).*]], '\\1', '')
else
- name = vim.fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\i\+\).*]], '\\1', '')
+ name = fn.substitute(first_line, [[^#!\s*\S*[/\\]\(\f\+\).*]], '\\1', '')
end
-- tcl scripts may have #!/bin/sh in the first line and "exec wish" in the
@@ -1459,11 +1709,11 @@ local function match_from_hashbang(contents, path)
if matchregex(name, [[^\(bash\d*\|dash\|ksh\d*\|sh\)\>]]) then
-- Bourne-like shell scripts: bash bash2 dash ksh ksh93 sh
- return require('vim.filetype.detect').sh(path, contents, first_line)
+ return sh(path, contents, first_line)
elseif matchregex(name, [[^csh\>]]) then
- return require('vim.filetype.detect').shell(path, contents, vim.g.filetype_csh or 'csh')
+ return M.shell(path, contents, vim.g.filetype_csh or 'csh')
elseif matchregex(name, [[^tcsh\>]]) then
- return require('vim.filetype.detect').shell(path, contents, 'tcsh')
+ return M.shell(path, contents, 'tcsh')
end
for k, v in pairs(patterns_hashbang) do
@@ -1473,6 +1723,11 @@ local function match_from_hashbang(contents, path)
return ft
end
end
+
+ -- If nothing matched, check the extension table. For a hashbang like
+ -- '#!/bin/env foo', this will set the filetype to 'fooscript' assuming
+ -- the filetype for the 'foo' extension is 'fooscript' in the extension table.
+ return dispatch_extension(name)
end
local patterns_text = {
@@ -1545,8 +1800,15 @@ local patterns_text = {
['^SNNS pattern definition file'] = 'snnspat',
['^SNNS result file'] = 'snnsres',
['^%%.-[Vv]irata'] = { 'virata', { start_lnum = 1, end_lnum = 5 } },
- ['[0-9:%.]* *execve%('] = 'strace',
- ['^__libc_start_main'] = 'strace',
+ function(lines)
+ if
+ -- inaccurate fast match first, then use accurate slow match
+ (lines[1]:find('execve%(') and lines[1]:find('^[0-9:%. ]*execve%('))
+ or lines[1]:find('^__libc_start_main')
+ then
+ return 'strace'
+ end
+ end,
-- VSE JCL
['^\\* $$ JOB\\>'] = { 'vsejcl', { vim_regex = true } },
['^// *JOB\\>'] = { 'vsejcl', { vim_regex = true } },
@@ -1556,9 +1818,7 @@ local patterns_text = {
['S Y S T E M S I M P R O V E D '] = { 'syndaout', { start_lnum = 3 } },
['Run Date: '] = { 'takcmp', { start_lnum = 6 } },
['Node File 1'] = { 'sindacmp', { start_lnum = 9 } },
- function(contents)
- require('vim.filetype.detect').dns_zone(contents)
- end,
+ dns_zone,
-- Valgrind
['^==%d+== valgrind'] = 'valgrind',
['^==%d+== Using valgrind'] = { 'valgrind', { start_lnum = 3 } },
@@ -1597,11 +1857,15 @@ local patterns_text = {
}
---@private
--- File does not start with "#!".
+--- File does not start with "#!".
+--- @param contents string[]
+--- @param path string
+--- @return string?
+--- @return fun(b: integer)?
local function match_from_text(contents, path)
if contents[1]:find('^:$') then
-- Bourne-like shell scripts: sh ksh bash bash2
- return M.sh(path, contents)
+ return sh(path, contents)
elseif
matchregex(
'\n' .. table.concat(contents, '\n'),
@@ -1652,10 +1916,15 @@ local function match_from_text(contents, path)
return cvs_diff(path, contents)
end
-M.match_contents = function(contents, path)
+--- @param contents string[]
+--- @param path string
+--- @param dispatch_extension fun(name: string): string?, fun(b: integer)?
+--- @return string?
+--- @return fun(b: integer)?
+function M.match_contents(contents, path, dispatch_extension)
local first_line = contents[1]
if first_line:find('^#!') then
- return match_from_hashbang(contents, path)
+ return match_from_hashbang(contents, path, dispatch_extension)
else
return match_from_text(contents, path)
end
diff --git a/runtime/lua/vim/filetype/options.lua b/runtime/lua/vim/filetype/options.lua
new file mode 100644
index 0000000000..2a28b5a8e3
--- /dev/null
+++ b/runtime/lua/vim/filetype/options.lua
@@ -0,0 +1,88 @@
+local api = vim.api
+
+local M = {}
+
+local function get_ftplugin_runtime(filetype)
+ local files = api.nvim__get_runtime({
+ string.format('ftplugin/%s.vim', filetype),
+ string.format('ftplugin/%s_*.vim', filetype),
+ string.format('ftplugin/%s/*.vim', filetype),
+ string.format('ftplugin/%s.lua', filetype),
+ string.format('ftplugin/%s_*.lua', filetype),
+ string.format('ftplugin/%s/*.lua', filetype),
+ }, true, {}) --[[@as string[] ]]
+
+ local r = {} ---@type string[]
+ for _, f in ipairs(files) do
+ -- VIMRUNTIME should be static so shouldn't need to worry about it changing
+ if not vim.startswith(f, vim.env.VIMRUNTIME) then
+ r[#r + 1] = f
+ end
+ end
+ return r
+end
+
+-- Keep track of ftplugin files
+local ftplugin_cache = {} ---@type table<string,table<string,integer>>
+
+-- Keep track of total number of FileType autocmds
+local ft_autocmd_num ---@type integer?
+
+-- Keep track of filetype options
+local ft_option_cache = {} ---@type table<string,table<string,any>>
+
+--- @param path string
+--- @return integer
+local function hash(path)
+ local mtime0 = vim.uv.fs_stat(path).mtime
+ return mtime0.sec * 1000000000 + mtime0.nsec
+end
+
+--- Only update the cache on changes to the number of FileType autocmds
+--- and changes to any ftplugin/ file. This isn't guaranteed to catch everything
+--- but should be good enough.
+--- @param filetype string
+local function update_ft_option_cache(filetype)
+ local new_ftautos = #api.nvim_get_autocmds({ event = 'FileType' })
+ if new_ftautos ~= ft_autocmd_num then
+ -- invalidate
+ ft_option_cache[filetype] = nil
+ ft_autocmd_num = new_ftautos
+ end
+
+ local files = get_ftplugin_runtime(filetype)
+
+ ftplugin_cache[filetype] = ftplugin_cache[filetype] or {}
+
+ if #files ~= #vim.tbl_keys(ftplugin_cache[filetype]) then
+ -- invalidate
+ ft_option_cache[filetype] = nil
+ ftplugin_cache[filetype] = {}
+ end
+
+ for _, f in ipairs(files) do
+ local mtime = hash(f)
+ if ftplugin_cache[filetype][f] ~= mtime then
+ -- invalidate
+ ft_option_cache[filetype] = nil
+ ftplugin_cache[filetype][f] = mtime
+ end
+ end
+end
+
+--- @private
+--- @param filetype string Filetype
+--- @param option string Option name
+--- @return string|integer|boolean
+function M.get_option(filetype, option)
+ update_ft_option_cache(filetype)
+
+ if not ft_option_cache[filetype] or not ft_option_cache[filetype][option] then
+ ft_option_cache[filetype] = ft_option_cache[filetype] or {}
+ ft_option_cache[filetype][option] = api.nvim_get_option_value(option, { filetype = filetype })
+ end
+
+ return ft_option_cache[filetype][option]
+end
+
+return M
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index a0d2c4c339..22612a7255 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -1,11 +1,12 @@
local M = {}
-local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
+local iswin = vim.uv.os_uname().sysname == 'Windows_NT'
---- Iterate over all the parents of the given file or directory.
+--- Iterate over all the parents of the given path.
---
--- Example:
---- <pre>lua
+---
+--- ```lua
--- local root_dir
--- for dir in vim.fs.parents(vim.api.nvim_buf_get_name(0)) do
--- if vim.fn.isdirectory(dir .. "/.git") == 1 then
@@ -17,10 +18,12 @@ local iswin = vim.loop.os_uname().sysname == 'Windows_NT'
--- if root_dir then
--- print("Found git repository at", root_dir)
--- end
---- </pre>
+--- ```
---
----@param start (string) Initial file or directory.
----@return (function) Iterator
+---@param start (string) Initial path.
+---@return fun(_, dir: string): string? # Iterator
+---@return nil
+---@return string|nil
function M.parents(start)
return function(_, dir)
local parent = M.dirname(dir)
@@ -34,10 +37,10 @@ function M.parents(start)
start
end
---- Return the parent directory of the given file or directory
+--- Return the parent directory of the given path
---
----@param file (string) File or directory
----@return (string) Parent directory of {file}
+---@param file (string) Path
+---@return string|nil Parent directory of {file}
function M.dirname(file)
if file == nil then
return nil
@@ -57,10 +60,10 @@ function M.dirname(file)
return (dir:gsub('\\', '/'))
end
---- Return the basename of the given file or directory
+--- Return the basename of the given path
---
----@param file (string) File or directory
----@return (string) Basename of {file}
+---@param file string Path
+---@return string|nil Basename of {file}
function M.basename(file)
if file == nil then
return nil
@@ -72,12 +75,18 @@ function M.basename(file)
return file:match('[/\\]$') and '' or (file:match('[^\\/]*$'):gsub('\\', '/'))
end
----@private
-local function join_paths(...)
+--- Concatenate directories and/or file paths into a single path with normalization
+--- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`)
+---
+---@param ... string
+---@return string
+function M.joinpath(...)
return (table.concat({ ... }, '/'):gsub('//+', '/'))
end
---- Return an iterator over the files and directories located in {path}
+---@alias Iterator fun(): string?, string?
+
+--- Return an iterator over the items located in {path}
---
---@param path (string) An absolute or relative path to the directory to iterate
--- over. The path is first normalized |vim.fs.normalize()|.
@@ -87,9 +96,10 @@ end
--- to control traversal. Return false to stop searching the current directory.
--- Only useful when depth > 1
---
----@return Iterator over files and directories in {path}. Each iteration yields
---- two values: name and type. Each "name" is the basename of the file or
---- directory relative to {path}. Type is one of "file" or "directory".
+---@return Iterator over items in {path}. Each iteration yields two values: "name" and "type".
+--- "name" is the basename of the item relative to {path}.
+--- "type" is one of the following:
+--- "file", "directory", "link", "fifo", "socket", "char", "block", "unknown".
function M.dir(path, opts)
opts = opts or {}
@@ -100,25 +110,29 @@ function M.dir(path, opts)
})
if not opts.depth or opts.depth == 1 then
- return function(fs)
- return vim.loop.fs_scandir_next(fs)
- end,
- vim.loop.fs_scandir(M.normalize(path))
+ local fs = vim.uv.fs_scandir(M.normalize(path))
+ return function()
+ if not fs then
+ return
+ end
+ return vim.uv.fs_scandir_next(fs)
+ end
end
--- @async
return coroutine.wrap(function()
local dirs = { { path, 1 } }
while #dirs > 0 do
+ --- @type string, integer
local dir0, level = unpack(table.remove(dirs, 1))
- local dir = level == 1 and dir0 or join_paths(path, dir0)
- local fs = vim.loop.fs_scandir(M.normalize(dir))
+ local dir = level == 1 and dir0 or M.joinpath(path, dir0)
+ local fs = vim.uv.fs_scandir(M.normalize(dir))
while fs do
- local name, t = vim.loop.fs_scandir_next(fs)
+ local name, t = vim.uv.fs_scandir_next(fs)
if not name then
break
end
- local f = level == 1 and name or join_paths(dir0, name)
+ local f = level == 1 and name or M.joinpath(dir0, name)
coroutine.yield(f, t)
if
opts.depth
@@ -133,22 +147,52 @@ function M.dir(path, opts)
end)
end
---- Find files or directories in the given path.
+--- @class vim.fs.find.opts
+--- @field path string
+--- @field upward boolean
+--- @field stop string
+--- @field type string
+--- @field limit number
+
+--- Find files or directories (or other items as specified by `opts.type`) in the given path.
+---
+--- Finds items given in {names} starting from {path}. If {upward} is "true"
+--- then the search traverses upward through parent directories; otherwise,
+--- the search traverses downward. Note that downward searches are recursive
+--- and may search through many directories! If {stop} is non-nil, then the
+--- search stops when the directory given in {stop} is reached. The search
+--- terminates when {limit} (default 1) matches are found. You can set {type}
+--- to "file", "directory", "link", "socket", "char", "block", or "fifo"
+--- to narrow the search to find only that type.
---
---- Finds any files or directories given in {names} starting from {path}. If
---- {upward} is "true" then the search traverses upward through parent
---- directories; otherwise, the search traverses downward. Note that downward
---- searches are recursive and may search through many directories! If {stop}
---- is non-nil, then the search stops when the directory given in {stop} is
---- reached. The search terminates when {limit} (default 1) matches are found.
---- The search can be narrowed to find only files or only directories by
---- specifying {type} to be "file" or "directory", respectively.
+--- Examples:
+---
+--- ```lua
+--- -- location of Cargo.toml from the current buffer's path
+--- local cargo = vim.fs.find('Cargo.toml', {
+--- upward = true,
+--- stop = vim.uv.os_homedir(),
+--- path = vim.fs.dirname(vim.api.nvim_buf_get_name(0)),
+--- })
+---
+--- -- list all test directories under the runtime directory
+--- local test_dirs = vim.fs.find(
+--- {'test', 'tst', 'testdir'},
+--- {limit = math.huge, type = 'directory', path = './runtime/'}
+--- )
+---
+--- -- get all files ending with .cpp or .hpp inside lib/
+--- local cpp_hpp = vim.fs.find(function(name, path)
+--- return name:match('.*%.[ch]pp$') and path:match('[/\\\\]lib$')
+--- end, {limit = math.huge, type = 'file'})
+--- ```
---
----@param names (string|table|fun(name: string): boolean) Names of the files
---- and directories to find.
---- Must be base names, paths and globs are not supported.
---- The function is called per file and directory within the
---- traversed directories to test if they match {names}.
+---@param names (string|string[]|fun(name: string, path: string): boolean) Names of the items to find.
+--- Must be base names, paths and globs are not supported when {names} is a string or a table.
+--- If {names} is a function, it is called for each traversed item with args:
+--- - name: base name of the current item
+--- - path: full path of the current item
+--- The function should return `true` if the given item is considered a match.
---
---@param opts (table) Optional keyword arguments:
--- - path (string): Path to begin searching from. If
@@ -159,16 +203,14 @@ end
--- (recursively).
--- - stop (string): Stop searching when this directory is
--- reached. The directory itself is not searched.
---- - type (string): Find only files ("file") or
---- directories ("directory"). If omitted, both
---- files and directories that match {names} are
---- included.
+--- - type (string): Find only items of the given type.
+--- If omitted, all items that match {names} are included.
--- - limit (number, default 1): Stop the search after
--- finding this many matches. Use `math.huge` to
--- place no limit on the number of matches.
----@return (table) Normalized paths |vim.fs.normalize()| of all matching files or directories
+---@return (string[]) # Normalized paths |vim.fs.normalize()| of all matching items
function M.find(names, opts)
- opts = opts or {}
+ opts = opts or {} --[[@as vim.fs.find.opts]]
vim.validate({
names = { names, { 's', 't', 'f' } },
path = { opts.path, 's', true },
@@ -178,41 +220,42 @@ function M.find(names, opts)
limit = { opts.limit, 'n', true },
})
- names = type(names) == 'string' and { names } or names
+ if type(names) == 'string' then
+ names = { names }
+ end
- local path = opts.path or vim.loop.cwd()
+ local path = opts.path or vim.uv.cwd()
local stop = opts.stop
local limit = opts.limit or 1
- local matches = {}
+ local matches = {} --- @type string[]
- ---@private
local function add(match)
- matches[#matches + 1] = match
+ matches[#matches + 1] = M.normalize(match)
if #matches == limit then
return true
end
end
if opts.upward then
- local test
+ local test --- @type fun(p: string): string[]
if type(names) == 'function' then
test = function(p)
local t = {}
for name, type in M.dir(p) do
- if names(name) and (not opts.type or opts.type == type) then
- table.insert(t, join_paths(p, name))
+ if (not opts.type or opts.type == type) and names(name, p) then
+ table.insert(t, M.joinpath(p, name))
end
end
return t
end
else
test = function(p)
- local t = {}
+ local t = {} --- @type string[]
for _, name in ipairs(names) do
- local f = join_paths(p, name)
- local stat = vim.loop.fs_stat(f)
+ local f = M.joinpath(p, name)
+ local stat = vim.uv.fs_stat(f)
if stat and (not opts.type or opts.type == stat.type) then
t[#t + 1] = f
end
@@ -248,9 +291,9 @@ function M.find(names, opts)
end
for other, type_ in M.dir(dir) do
- local f = join_paths(dir, other)
+ local f = M.joinpath(dir, other)
if type(names) == 'function' then
- if names(other) and (not opts.type or opts.type == type_) then
+ if (not opts.type or opts.type == type_) and names(other, dir) then
if add(f) then
return matches
end
@@ -281,28 +324,47 @@ end
--- variables are also expanded.
---
--- Examples:
---- <pre>lua
---- vim.fs.normalize('C:\\\\Users\\\\jdoe')
---- --> 'C:/Users/jdoe'
---
---- vim.fs.normalize('~/src/neovim')
---- --> '/home/jdoe/src/neovim'
+--- ```lua
+--- vim.fs.normalize('C:\\\\Users\\\\jdoe')
+--- -- 'C:/Users/jdoe'
---
---- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
---- --> '/Users/jdoe/.config/nvim/init.vim'
---- </pre>
+--- vim.fs.normalize('~/src/neovim')
+--- -- '/home/jdoe/src/neovim'
+---
+--- vim.fs.normalize('$XDG_CONFIG_HOME/nvim/init.vim')
+--- -- '/Users/jdoe/.config/nvim/init.vim'
+--- ```
---
---@param path (string) Path to normalize
+---@param opts table|nil Options:
+--- - expand_env: boolean Expand environment variables (default: true)
---@return (string) Normalized path
-function M.normalize(path)
- vim.validate({ path = { path, 's' } })
- return (
- path
- :gsub('^~$', vim.loop.os_homedir())
- :gsub('^~/', vim.loop.os_homedir() .. '/')
- :gsub('%$([%w_]+)', vim.loop.os_getenv)
- :gsub('\\', '/')
- )
+function M.normalize(path, opts)
+ opts = opts or {}
+
+ vim.validate({
+ path = { path, { 'string' } },
+ expand_env = { opts.expand_env, { 'boolean' }, true },
+ })
+
+ if path:sub(1, 1) == '~' then
+ local home = vim.uv.os_homedir() or '~'
+ if home:sub(-1) == '\\' or home:sub(-1) == '/' then
+ home = home:sub(1, -2)
+ end
+ path = home .. path:sub(2)
+ end
+
+ if opts.expand_env == nil or opts.expand_env then
+ path = path:gsub('%$([%w_]+)', vim.uv.os_getenv)
+ end
+
+ path = path:gsub('\\', '/'):gsub('/+', '/')
+ if iswin and path:match('^%w:/$') then
+ return path
+ end
+ return (path:gsub('(.)/$', '%1'))
end
return M
diff --git a/runtime/lua/vim/func.lua b/runtime/lua/vim/func.lua
new file mode 100644
index 0000000000..206d1bae95
--- /dev/null
+++ b/runtime/lua/vim/func.lua
@@ -0,0 +1,41 @@
+local M = {}
+
+-- TODO(lewis6991): Private for now until:
+-- - There are other places in the codebase that could benefit from this
+-- (e.g. LSP), but might require other changes to accommodate.
+-- - Invalidation of the cache needs to be controllable. Using weak tables
+-- is an acceptable invalidation policy, but it shouldn't be the only
+-- one.
+-- - I don't think the story around `hash` is completely thought out. We
+-- may be able to have a good default hash by hashing each argument,
+-- so basically a better 'concat'.
+-- - Need to support multi level caches. Can be done by allow `hash` to
+-- return multiple values.
+--
+--- Memoizes a function {fn} using {hash} to hash the arguments.
+---
+--- Internally uses a |lua-weaktable| to cache the results of {fn} meaning the
+--- cache will be invalidated whenever Lua does garbage collection.
+---
+--- The memoized function returns shared references so be wary about
+--- mutating return values.
+---
+--- @generic F: function
+--- @param hash integer|string|function Hash function to create a hash to use as a key to
+--- store results. Possible values:
+--- - When integer, refers to the index of an argument of {fn} to hash.
+--- This argument can have any type.
+--- - When function, is evaluated using the same arguments passed to {fn}.
+--- - When `concat`, the hash is determined by string concatenating all the
+--- arguments passed to {fn}.
+--- - When `concat-n`, the hash is determined by string concatenating the
+--- first n arguments passed to {fn}.
+---
+--- @param fn F Function to memoize.
+--- @return F # Memoized version of {fn}
+--- @nodoc
+function M._memoize(hash, fn)
+ return require('vim.func._memoize')(hash, fn)
+end
+
+return M
diff --git a/runtime/lua/vim/func/_memoize.lua b/runtime/lua/vim/func/_memoize.lua
new file mode 100644
index 0000000000..835bf64c93
--- /dev/null
+++ b/runtime/lua/vim/func/_memoize.lua
@@ -0,0 +1,59 @@
+--- Module for private utility functions
+
+--- @param argc integer?
+--- @return fun(...): any
+local function concat_hash(argc)
+ return function(...)
+ return table.concat({ ... }, '%%', 1, argc)
+ end
+end
+
+--- @param idx integer
+--- @return fun(...): any
+local function idx_hash(idx)
+ return function(...)
+ return select(idx, ...)
+ end
+end
+
+--- @param hash integer|string|fun(...): any
+--- @return fun(...): any
+local function resolve_hash(hash)
+ if type(hash) == 'number' then
+ hash = idx_hash(hash)
+ elseif type(hash) == 'string' then
+ local c = hash == 'concat' or hash:match('^concat%-(%d+)')
+ if c then
+ hash = concat_hash(tonumber(c))
+ else
+ error('invalid value for hash: ' .. hash)
+ end
+ end
+ --- @cast hash -integer
+ return hash
+end
+
+--- @generic F: function
+--- @param hash integer|string|fun(...): any
+--- @param fn F
+--- @return F
+return function(hash, fn)
+ vim.validate({
+ hash = { hash, { 'number', 'string', 'function' } },
+ fn = { fn, 'function' },
+ })
+
+ ---@type table<any,table<any,any>>
+ local cache = setmetatable({}, { __mode = 'kv' })
+
+ hash = resolve_hash(hash)
+
+ return function(...)
+ local key = hash(...)
+ if cache[key] == nil then
+ cache[key] = vim.F.pack_len(fn(...))
+ end
+
+ return vim.F.unpack_len(cache[key])
+ end
+end
diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index 044880e076..6e47a22d03 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -1,23 +1,230 @@
local M = {}
-function M.report_start(msg)
- vim.fn['health#report_start'](msg)
+local s_output = {}
+
+-- Returns the fold text of the current healthcheck section
+function M.foldtext()
+ local foldtext = vim.fn.foldtext()
+
+ if vim.bo.filetype ~= 'checkhealth' then
+ return foldtext
+ end
+
+ if vim.b.failedchecks == nil then
+ vim.b.failedchecks = vim.empty_dict()
+ end
+
+ if vim.b.failedchecks[foldtext] == nil then
+ local warning = '- WARNING '
+ local warninglen = string.len(warning)
+ local err = '- ERROR '
+ local errlen = string.len(err)
+ local failedchecks = vim.b.failedchecks
+ failedchecks[foldtext] = false
+
+ local foldcontent = vim.api.nvim_buf_get_lines(0, vim.v.foldstart - 1, vim.v.foldend, false)
+ for _, line in ipairs(foldcontent) do
+ if string.sub(line, 1, warninglen) == warning or string.sub(line, 1, errlen) == err then
+ failedchecks[foldtext] = true
+ break
+ end
+ end
+
+ vim.b.failedchecks = failedchecks
+ end
+
+ return vim.b.failedchecks[foldtext] and '+WE' .. foldtext:sub(4) or foldtext
end
-function M.report_info(msg)
- vim.fn['health#report_info'](msg)
+-- From a path return a list [{name}, {func}, {type}] representing a healthcheck
+local function filepath_to_healthcheck(path)
+ path = vim.fs.normalize(path)
+ local name
+ local func
+ local filetype
+ if path:find('vim$') then
+ name = vim.fs.basename(path):gsub('%.vim$', '')
+ func = 'health#' .. name .. '#check'
+ filetype = 'v'
+ else
+ local subpath = path:gsub('.*lua/', '')
+ if vim.fs.basename(subpath) == 'health.lua' then
+ -- */health.lua
+ name = vim.fs.dirname(subpath)
+ else
+ -- */health/init.lua
+ name = vim.fs.dirname(vim.fs.dirname(subpath))
+ end
+ name = name:gsub('/', '.')
+
+ func = 'require("' .. name .. '.health").check()'
+ filetype = 'l'
+ end
+ return { name, func, filetype }
end
-function M.report_ok(msg)
- vim.fn['health#report_ok'](msg)
+-- Returns { {name, func, type}, ... } representing healthchecks
+local function get_healthcheck_list(plugin_names)
+ local healthchecks = {}
+ plugin_names = vim.split(plugin_names, ' ')
+ for _, p in pairs(plugin_names) do
+ -- support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
+
+ p = p:gsub('%.', '/')
+ p = p:gsub('*', '**')
+
+ local paths = vim.api.nvim_get_runtime_file('autoload/health/' .. p .. '.vim', true)
+ vim.list_extend(
+ paths,
+ vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health/init.lua', true)
+ )
+ vim.list_extend(paths, vim.api.nvim_get_runtime_file('lua/**/' .. p .. '/health.lua', true))
+
+ if vim.tbl_count(paths) == 0 then
+ healthchecks[#healthchecks + 1] = { p, '', '' } -- healthcheck not found
+ else
+ local unique_paths = {}
+ for _, v in pairs(paths) do
+ unique_paths[v] = true
+ end
+ paths = {}
+ for k, _ in pairs(unique_paths) do
+ paths[#paths + 1] = k
+ end
+
+ for _, v in ipairs(paths) do
+ healthchecks[#healthchecks + 1] = filepath_to_healthcheck(v)
+ end
+ end
+ end
+ return healthchecks
end
-function M.report_warn(msg, ...)
- vim.fn['health#report_warn'](msg, ...)
+-- Returns {name: [func, type], ..} representing healthchecks
+local function get_healthcheck(plugin_names)
+ local health_list = get_healthcheck_list(plugin_names)
+ local healthchecks = {}
+ for _, c in pairs(health_list) do
+ if c[1] ~= 'vim' then
+ healthchecks[c[1]] = { c[2], c[3] }
+ end
+ end
+
+ return healthchecks
+end
+
+-- Indents lines *except* line 1 of a string if it contains newlines.
+local function indent_after_line1(s, columns)
+ local lines = vim.split(s, '\n')
+ local indent = string.rep(' ', columns)
+ for i = 2, #lines do
+ lines[i] = indent .. lines[i]
+ end
+ return table.concat(lines, '\n')
+end
+
+-- Changes ':h clipboard' to ':help |clipboard|'.
+local function help_to_link(s)
+ return vim.fn.substitute(s, [[\v:h%[elp] ([^|][^"\r\n ]+)]], [[:help |\1|]], [[g]])
end
+-- Format a message for a specific report item.
+-- Variable args: Optional advice (string or list)
+local function format_report_message(status, msg, ...)
+ local output = '- ' .. status
+ if status ~= '' then
+ output = output .. ' '
+ end
+
+ output = output .. indent_after_line1(msg, 2)
+
+ local varargs = ...
+
+ -- Optional parameters
+ if varargs then
+ if type(varargs) == 'string' then
+ varargs = { varargs }
+ end
+
+ output = output .. '\n - ADVICE:'
+
+ -- Report each suggestion
+ for _, v in ipairs(varargs) do
+ if v then
+ output = output .. '\n - ' .. indent_after_line1(v, 6)
+ end
+ end
+ end
+
+ return help_to_link(output)
+end
+
+local function collect_output(output)
+ vim.list_extend(s_output, vim.split(output, '\n'))
+end
+
+-- Starts a new report.
+function M.start(name)
+ local input = string.format('\n%s ~', name)
+ collect_output(input)
+end
+
+-- Reports a message in the current section.
+function M.info(msg)
+ local input = format_report_message('', msg)
+ collect_output(input)
+end
+
+-- Reports a successful healthcheck.
+function M.ok(msg)
+ local input = format_report_message('OK', msg)
+ collect_output(input)
+end
+
+-- Reports a health warning.
+-- ...: Optional advice (string or table)
+function M.warn(msg, ...)
+ local input = format_report_message('WARNING', msg, ...)
+ collect_output(input)
+end
+
+-- Reports a failed healthcheck.
+-- ...: Optional advice (string or table)
+function M.error(msg, ...)
+ local input = format_report_message('ERROR', msg, ...)
+ collect_output(input)
+end
+
+local function deprecate(type)
+ local before = string.format('vim.health.report_%s()', type)
+ local after = string.format('vim.health.%s()', type)
+ local message = vim.deprecate(before, after, '0.11')
+ if message then
+ M.warn(message)
+ end
+ vim.cmd.redraw()
+ vim.print('Running healthchecks...')
+end
+
+function M.report_start(name)
+ deprecate('start')
+ M.start(name)
+end
+function M.report_info(msg)
+ deprecate('info')
+ M.info(msg)
+end
+function M.report_ok(msg)
+ deprecate('ok')
+ M.ok(msg)
+end
+function M.report_warn(msg, ...)
+ deprecate('warn')
+ M.warn(msg, ...)
+end
function M.report_error(msg, ...)
- vim.fn['health#report_error'](msg, ...)
+ deprecate('error')
+ M.error(msg, ...)
end
local path2name = function(path)
@@ -59,4 +266,77 @@ M._complete = function()
return vim.tbl_keys(unique)
end
+-- Runs the specified healthchecks.
+-- Runs all discovered healthchecks if plugin_names is empty.
+function M._check(plugin_names)
+ local healthchecks = plugin_names == '' and get_healthcheck('*') or get_healthcheck(plugin_names)
+
+ -- Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
+ local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
+ local mod = emptybuf and 'buffer' or 'tab sbuffer'
+ local bufnr = vim.api.nvim_create_buf(true, true)
+ vim.cmd(mod .. ' ' .. bufnr)
+
+ if vim.fn.bufexists('health://') == 1 then
+ vim.cmd.bwipe('health://')
+ end
+ vim.cmd.file('health://')
+ vim.cmd.setfiletype('checkhealth')
+
+ if healthchecks == nil or next(healthchecks) == nil then
+ vim.fn.setline(1, 'ERROR: No healthchecks found.')
+ return
+ end
+ vim.cmd.redraw()
+ vim.print('Running healthchecks...')
+
+ for name, value in vim.spairs(healthchecks) do
+ local func = value[1]
+ local type = value[2]
+ s_output = {}
+
+ if func == '' then
+ s_output = {}
+ M.error('No healthcheck found for "' .. name .. '" plugin.')
+ end
+ if type == 'v' then
+ vim.fn.call(func, {})
+ else
+ local f = assert(loadstring(func))
+ local ok, output = pcall(f)
+ if not ok then
+ M.error(
+ string.format('Failed to run healthcheck for "%s" plugin. Exception:\n%s\n', name, output)
+ )
+ end
+ end
+ -- in the event the healthcheck doesn't return anything
+ -- (the plugin author should avoid this possibility)
+ if next(s_output) == nil then
+ s_output = {}
+ M.error('The healthcheck report for "' .. name .. '" plugin is empty.')
+ end
+ local header = { string.rep('=', 78), name .. ': ' .. func, '' }
+ -- remove empty line after header from report_start
+ if s_output[1] == '' then
+ local tmp = {}
+ for i = 2, #s_output do
+ tmp[#tmp + 1] = s_output[i]
+ end
+ s_output = {}
+ for _, v in ipairs(tmp) do
+ s_output[#s_output + 1] = v
+ end
+ end
+ s_output[#s_output + 1] = ''
+ s_output = vim.list_extend(header, s_output)
+ vim.fn.append('$', s_output)
+ vim.cmd.redraw()
+ end
+
+ -- Clear the 'Running healthchecks...' message.
+ vim.cmd.redraw()
+ vim.print('')
+end
+
return M
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index 20ad48dd27..fc2fd43c97 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,7 +1,36 @@
+---@defgroup vim.highlight
+---
+--- Nvim includes a function for highlighting a selection on yank.
+---
+--- To enable it, add the following to your `init.vim`:
+---
+--- ```vim
+--- au TextYankPost * silent! lua vim.highlight.on_yank()
+--- ```
+---
+--- You can customize the highlight group and the duration of the highlight via:
+---
+--- ```vim
+--- au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
+--- ```
+---
+--- If you want to exclude visual selections from highlighting on yank, use:
+---
+--- ```vim
+--- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false}
+--- ```
+
local api = vim.api
local M = {}
+--- Table with default priorities used for highlighting:
+--- - `syntax`: `50`, used for standard syntax highlighting
+--- - `treesitter`: `100`, used for tree-sitter-based highlighting
+--- - `semantic_tokens`: `125`, used for LSP semantic token highlighting
+--- - `diagnostics`: `150`, used for code analysis such as diagnostics
+--- - `user`: `200`, used for user-triggered highlights such as LSP document
+--- symbols or `on_yank` autocommands
M.priorities = {
syntax = 50,
treesitter = 100,
@@ -10,52 +39,26 @@ M.priorities = {
user = 200,
}
----@private
-function M.create(higroup, hi_info, default)
- vim.deprecate('vim.highlight.create', 'vim.api.nvim_set_hl', '0.9')
- local options = {}
- -- TODO: Add validation
- for k, v in pairs(hi_info) do
- table.insert(options, string.format('%s=%s', k, v))
- end
- vim.cmd(
- string.format(
- [[highlight %s %s %s]],
- default and 'default' or '',
- higroup,
- table.concat(options, ' ')
- )
- )
-end
-
----@private
-function M.link(higroup, link_to, force)
- vim.deprecate('vim.highlight.link', 'vim.api.nvim_set_hl', '0.9')
- vim.cmd(string.format([[highlight%s link %s %s]], force and '!' or ' default', higroup, link_to))
-end
-
---- Highlight range between two positions
+--- Apply highlight group to range of text.
---
----@param bufnr number of buffer to apply highlighting to
----@param ns namespace to add highlight to
----@param higroup highlight group to use for highlighting
----@param start first position (tuple {line,col})
----@param finish second position (tuple {line,col})
----@param opts table with options:
--- - regtype type of range (see |setreg()|, default charwise)
--- - inclusive boolean indicating whether the range is end-inclusive (default false)
--- - priority number indicating priority of highlight (default priorities.user)
+---@param bufnr integer Buffer number to apply highlighting to
+---@param ns integer Namespace to add highlight to
+---@param higroup string Highlight group to use for highlighting
+---@param start integer[]|string Start of region as a (line, column) tuple or string accepted by |getpos()|
+---@param finish integer[]|string End of region as a (line, column) tuple or string accepted by |getpos()|
+---@param opts table|nil Optional parameters
+--- - regtype type of range (see |setreg()|, default charwise)
+--- - inclusive boolean indicating whether the range is end-inclusive (default false)
+--- - priority number indicating priority of highlight (default priorities.user)
function M.range(bufnr, ns, higroup, start, finish, opts)
opts = opts or {}
local regtype = opts.regtype or 'v'
local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user
- -- sanity check
- if start[2] < 0 or finish[1] < start[1] then
- return
- end
-
+ -- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
+ -- bounds (row, col, end_row, end_col) as multiline regions are natively
+ -- supported now
local region = vim.region(bufnr, start, finish, regtype, inclusive)
for linenr, cols in pairs(region) do
local end_row
@@ -75,21 +78,16 @@ end
local yank_ns = api.nvim_create_namespace('hlyank')
local yank_timer
---- Highlight the yanked region
----
---- use from init.vim via
---- au TextYankPost * lua vim.highlight.on_yank()
---- customize highlight group and timeout via
---- au TextYankPost * lua vim.highlight.on_yank {higroup="IncSearch", timeout=150}
---- customize conditions (here: do not highlight a visual selection) via
---- au TextYankPost * lua vim.highlight.on_yank {on_visual=false}
+
+--- Highlight the yanked text
---
--- @param opts table with options controlling the highlight:
--- - higroup highlight group for yanked region (default "IncSearch")
--- - timeout time in ms before highlight is cleared (default 150)
--- - on_macro highlight when executing macro (default false)
--- - on_visual highlight when yanking visual selection (default true)
--- - event event structure (default vim.v.event)
+--- @param opts table|nil Optional parameters
+--- - higroup highlight group for yanked region (default "IncSearch")
+--- - timeout time in ms before highlight is cleared (default 150)
+--- - on_macro highlight when executing macro (default false)
+--- - on_visual highlight when yanking visual selection (default true)
+--- - event event structure (default vim.v.event)
+--- - priority integer priority (default |vim.highlight.priorities|`.user`)
function M.on_yank(opts)
vim.validate({
opts = {
@@ -128,20 +126,11 @@ function M.on_yank(opts)
yank_timer:close()
end
- local pos1 = vim.fn.getpos("'[")
- local pos2 = vim.fn.getpos("']")
-
- pos1 = { pos1[2] - 1, pos1[3] - 1 + pos1[4] }
- pos2 = { pos2[2] - 1, pos2[3] - 1 + pos2[4] }
-
- M.range(
- bufnr,
- yank_ns,
- higroup,
- pos1,
- pos2,
- { regtype = event.regtype, inclusive = event.inclusive, priority = M.priorities.user }
- )
+ M.range(bufnr, yank_ns, higroup, "'[", "']", {
+ regtype = event.regtype,
+ inclusive = event.inclusive,
+ priority = opts.priority or M.priorities.user,
+ })
yank_timer = vim.defer_fn(function()
yank_timer = nil
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
new file mode 100644
index 0000000000..874bdfb437
--- /dev/null
+++ b/runtime/lua/vim/iter.lua
@@ -0,0 +1,1013 @@
+---@defgroup vim.iter
+---
+--- \*vim.iter()\* is an interface for |iterable|s: it wraps a table or function argument into an
+--- \*Iter\* object with methods (such as |Iter:filter()| and |Iter:map()|) that transform the
+--- underlying source data. These methods can be chained to create iterator "pipelines": the output
+--- of each pipeline stage is input to the next stage. The first stage depends on the type passed to
+--- `vim.iter()`:
+---
+--- - List tables (arrays, |lua-list|) yield only the value of each element.
+--- - Use |Iter:enumerate()| to also pass the index to the next stage.
+--- - Or initialize with ipairs(): `vim.iter(ipairs(…))`.
+--- - Non-list tables (|lua-dict|) yield both the key and value of each element.
+--- - Function |iterator|s yield all values returned by the underlying function.
+--- - Tables with a |__call()| metamethod are treated as function iterators.
+---
+--- The iterator pipeline terminates when the underlying |iterable| is exhausted (for function
+--- iterators this means it returned nil).
+---
+--- Note: `vim.iter()` scans table input to decide if it is a list or a dict; to avoid this cost you
+--- can wrap the table with an iterator e.g. `vim.iter(ipairs({…}))`, but that precludes the use of
+--- |list-iterator| operations such as |Iter:rev()|).
+---
+--- Examples:
+---
+--- ```lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 })
+--- it:map(function(v)
+--- return v * 3
+--- end)
+--- it:rev()
+--- it:skip(2)
+--- it:totable()
+--- -- { 9, 6, 3 }
+---
+--- -- ipairs() is a function iterator which returns both the index (i) and the value (v)
+--- vim.iter(ipairs({ 1, 2, 3, 4, 5 })):map(function(i, v)
+--- if i > 2 then return v end
+--- end):totable()
+--- -- { 3, 4, 5 }
+---
+--- local it = vim.iter(vim.gsplit('1,2,3,4,5', ','))
+--- it:map(function(s) return tonumber(s) end)
+--- for i, d in it:enumerate() do
+--- print(string.format("Column %d is %d", i, d))
+--- end
+--- -- Column 1 is 1
+--- -- Column 2 is 2
+--- -- Column 3 is 3
+--- -- Column 4 is 4
+--- -- Column 5 is 5
+---
+--- vim.iter({ a = 1, b = 2, c = 3, z = 26 }):any(function(k, v)
+--- return k == 'z'
+--- end)
+--- -- true
+---
+--- local rb = vim.ringbuf(3)
+--- rb:push("a")
+--- rb:push("b")
+--- vim.iter(rb):totable()
+--- -- { "a", "b" }
+--- ```
+---
+--- In addition to the |vim.iter()| function, the |vim.iter| module provides
+--- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
+
+---@class IterMod
+---@operator call:Iter
+local M = {}
+
+---@class Iter
+local Iter = {}
+Iter.__index = Iter
+Iter.__call = function(self)
+ return self:next()
+end
+
+--- Special case implementations for iterators on list tables.
+---@class ListIter : Iter
+---@field _table table Underlying table data
+---@field _head number Index to the front of a table iterator
+---@field _tail number Index to the end of a table iterator (exclusive)
+local ListIter = {}
+ListIter.__index = setmetatable(ListIter, Iter)
+ListIter.__call = function(self)
+ return self:next()
+end
+
+--- Packed tables use this as their metatable
+local packedmt = {}
+
+local function unpack(t)
+ if type(t) == 'table' and getmetatable(t) == packedmt then
+ return _G.unpack(t, 1, t.n)
+ end
+ return t
+end
+
+local function pack(...)
+ local n = select('#', ...)
+ if n > 1 then
+ return setmetatable({ n = n, ... }, packedmt)
+ end
+ return ...
+end
+
+local function sanitize(t)
+ if type(t) == 'table' and getmetatable(t) == packedmt then
+ -- Remove length tag
+ t.n = nil
+ end
+ return t
+end
+
+--- Determine if the current iterator stage should continue.
+---
+--- If any arguments are passed to this function, then return those arguments
+--- and stop the current iterator stage. Otherwise, return true to signal that
+--- the current stage should continue.
+---
+---@param ... any Function arguments.
+---@return boolean True if the iterator stage should continue, false otherwise
+---@return any Function arguments.
+local function continue(...)
+ if select(1, ...) ~= nil then
+ return false, ...
+ end
+ return true
+end
+
+--- If no input arguments are given return false, indicating the current
+--- iterator stage should stop. Otherwise, apply the arguments to the function
+--- f. If that function returns no values, the current iterator stage continues.
+--- Otherwise, those values are returned.
+---
+---@param f function Function to call with the given arguments
+---@param ... any Arguments to apply to f
+---@return boolean True if the iterator pipeline should continue, false otherwise
+---@return any Return values of f
+local function apply(f, ...)
+ if select(1, ...) ~= nil then
+ return continue(f(...))
+ end
+ return false
+end
+
+--- Filters an iterator pipeline.
+---
+--- Example:
+---
+--- ```lua
+--- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
+--- ```
+---
+---@param f function(...):bool Takes all values returned from the previous stage
+--- in the pipeline and returns false or nil if the
+--- current iterator element should be removed.
+---@return Iter
+function Iter.filter(self, f)
+ return self:map(function(...)
+ if f(...) then
+ return ...
+ end
+ end)
+end
+
+---@private
+function ListIter.filter(self, f)
+ local inc = self._head < self._tail and 1 or -1
+ local n = self._head
+ for i = self._head, self._tail - inc, inc do
+ local v = self._table[i]
+ if f(unpack(v)) then
+ self._table[n] = v
+ n = n + inc
+ end
+ end
+ self._tail = n
+ return self
+end
+
+--- Maps the items of an iterator pipeline to the values returned by `f`.
+---
+--- If the map function returns nil, the value is filtered from the iterator.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({ 1, 2, 3, 4 }):map(function(v)
+--- if v % 2 == 0 then
+--- return v * 3
+--- end
+--- end)
+--- it:totable()
+--- -- { 6, 12 }
+--- ```
+---
+---@param f function(...):any Mapping function. Takes all values returned from
+--- the previous stage in the pipeline as arguments
+--- and returns one or more new values, which are used
+--- in the next pipeline stage. Nil return values
+--- are filtered from the output.
+---@return Iter
+function Iter.map(self, f)
+ -- Implementation note: the reader may be forgiven for observing that this
+ -- function appears excessively convoluted. The problem to solve is that each
+ -- stage of the iterator pipeline can return any number of values, and the
+ -- number of values could even change per iteration. And the return values
+ -- must be checked to determine if the pipeline has ended, so we cannot
+ -- naively forward them along to the next stage.
+ --
+ -- A simple approach is to pack all of the return values into a table, check
+ -- for nil, then unpack the table for the next stage. However, packing and
+ -- unpacking tables is quite slow. There is no other way in Lua to handle an
+ -- unknown number of function return values than to simply forward those
+ -- values along to another function. Hence the intricate function passing you
+ -- see here.
+
+ local next = self.next
+
+ --- Drain values from the upstream iterator source until a value can be
+ --- returned.
+ ---
+ --- This is a recursive function. The base case is when the first argument is
+ --- false, which indicates that the rest of the arguments should be returned
+ --- as the values for the current iteration stage.
+ ---
+ ---@param cont boolean If true, the current iterator stage should continue to
+ --- pull values from its upstream pipeline stage.
+ --- Otherwise, this stage is complete and returns the
+ --- values passed.
+ ---@param ... any Values to return if cont is false.
+ ---@return any
+ local function fn(cont, ...)
+ if cont then
+ return fn(apply(f, next(self)))
+ end
+ return ...
+ end
+
+ self.next = function()
+ return fn(apply(f, next(self)))
+ end
+ return self
+end
+
+---@private
+function ListIter.map(self, f)
+ local inc = self._head < self._tail and 1 or -1
+ local n = self._head
+ for i = self._head, self._tail - inc, inc do
+ local v = pack(f(unpack(self._table[i])))
+ if v ~= nil then
+ self._table[n] = v
+ n = n + inc
+ end
+ end
+ self._tail = n
+ return self
+end
+
+--- Calls a function once for each item in the pipeline, draining the iterator.
+---
+--- For functions with side effects. To modify the values in the iterator, use |Iter:map()|.
+---
+---@param f function(...) Function to execute for each item in the pipeline.
+--- Takes all of the values returned by the previous stage
+--- in the pipeline as arguments.
+function Iter.each(self, f)
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ f(...)
+ return true
+ end
+ end
+ while fn(self:next()) do
+ end
+end
+
+---@private
+function ListIter.each(self, f)
+ local inc = self._head < self._tail and 1 or -1
+ for i = self._head, self._tail - inc, inc do
+ f(unpack(self._table[i]))
+ end
+ self._head = self._tail
+end
+
+--- Collect the iterator into a table.
+---
+--- The resulting table depends on the initial source in the iterator pipeline.
+--- List-like tables and function iterators will be collected into a list-like
+--- table. If multiple values are returned from the final stage in the iterator
+--- pipeline, each value will be included in a table.
+---
+--- Examples:
+---
+--- ```lua
+--- vim.iter(string.gmatch('100 20 50', '%d+')):map(tonumber):totable()
+--- -- { 100, 20, 50 }
+---
+--- vim.iter({ 1, 2, 3 }):map(function(v) return v, 2 * v end):totable()
+--- -- { { 1, 2 }, { 2, 4 }, { 3, 6 } }
+---
+--- vim.iter({ a = 1, b = 2, c = 3 }):filter(function(k, v) return v % 2 ~= 0 end):totable()
+--- -- { { 'a', 1 }, { 'c', 3 } }
+--- ```
+---
+--- The generated table is a list-like table with consecutive, numeric indices.
+--- To create a map-like table with arbitrary keys, use |Iter:fold()|.
+---
+---
+---@return table
+function Iter.totable(self)
+ local t = {}
+
+ while true do
+ local args = pack(self:next())
+ if args == nil then
+ break
+ end
+
+ t[#t + 1] = sanitize(args)
+ end
+ return t
+end
+
+---@private
+function ListIter.totable(self)
+ if self.next ~= ListIter.next or self._head >= self._tail then
+ return Iter.totable(self)
+ end
+
+ local needs_sanitize = getmetatable(self._table[1]) == packedmt
+
+ -- Reindex and sanitize.
+ local len = self._tail - self._head
+
+ if needs_sanitize then
+ for i = 1, len do
+ self._table[i] = sanitize(self._table[self._head - 1 + i])
+ end
+ else
+ for i = 1, len do
+ self._table[i] = self._table[self._head - 1 + i]
+ end
+ end
+
+ for i = len + 1, table.maxn(self._table) do
+ self._table[i] = nil
+ end
+
+ self._head = 1
+ self._tail = len + 1
+
+ return self._table
+end
+
+--- Folds ("reduces") an iterator into a single value.
+---
+--- Examples:
+---
+--- ```lua
+--- -- Create a new table with only even values
+--- local t = { a = 1, b = 2, c = 3, d = 4 }
+--- local it = vim.iter(t)
+--- it:filter(function(k, v) return v % 2 == 0 end)
+--- it:fold({}, function(t, k, v)
+--- t[k] = v
+--- return t
+--- end)
+--- -- { b = 2, d = 4 }
+--- ```
+---
+---@generic A
+---
+---@param init A Initial value of the accumulator.
+---@param f function(acc:A, ...):A Accumulation function.
+---@return A
+function Iter.fold(self, init, f)
+ local acc = init
+
+ --- Use a closure to handle var args returned from iterator
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ acc = f(acc, ...)
+ return true
+ end
+ end
+
+ while fn(self:next()) do
+ end
+ return acc
+end
+
+---@private
+function ListIter.fold(self, init, f)
+ local acc = init
+ local inc = self._head < self._tail and 1 or -1
+ for i = self._head, self._tail - inc, inc do
+ acc = f(acc, unpack(self._table[i]))
+ end
+ return acc
+end
+
+--- Gets the next value from the iterator.
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter(string.gmatch('1 2 3', '%d+')):map(tonumber)
+--- it:next()
+--- -- 1
+--- it:next()
+--- -- 2
+--- it:next()
+--- -- 3
+---
+--- ```
+---
+---@return any
+function Iter.next(self) -- luacheck: no unused args
+ -- This function is provided by the source iterator in Iter.new. This definition exists only for
+ -- the docstring
+end
+
+---@private
+function ListIter.next(self)
+ if self._head ~= self._tail then
+ local v = self._table[self._head]
+ local inc = self._head < self._tail and 1 or -1
+ self._head = self._head + inc
+ return unpack(v)
+ end
+end
+
+--- Reverses a |list-iterator| pipeline.
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 }):rev()
+--- it:totable()
+--- -- { 12, 9, 6, 3 }
+---
+--- ```
+---
+---@return Iter
+function Iter.rev(self)
+ error('rev() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.rev(self)
+ local inc = self._head < self._tail and 1 or -1
+ self._head, self._tail = self._tail - inc, self._head - inc
+ return self
+end
+
+--- Gets the next value in a |list-iterator| without consuming it.
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:peek()
+--- -- 3
+--- it:peek()
+--- -- 3
+--- it:next()
+--- -- 3
+---
+--- ```
+---
+---@return any
+function Iter.peek(self) -- luacheck: no unused args
+ error('peek() requires a list-like table')
+end
+
+---@private
+function ListIter.peek(self)
+ if self._head ~= self._tail then
+ return self._table[self._head]
+ end
+end
+
+--- Find the first value in the iterator that satisfies the given predicate.
+---
+--- Advances the iterator. Returns nil and drains the iterator if no value is found.
+---
+--- Examples:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(12)
+--- -- 12
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(20)
+--- -- nil
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:find(function(v) return v % 4 == 0 end)
+--- -- 12
+---
+--- ```
+---
+---@return any
+function Iter.find(self, f)
+ if type(f) ~= 'function' then
+ local val = f
+ f = function(v)
+ return v == val
+ end
+ end
+
+ local result = nil
+
+ --- Use a closure to handle var args returned from iterator
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ if f(...) then
+ result = pack(...)
+ else
+ return true
+ end
+ end
+ end
+
+ while fn(self:next()) do
+ end
+ return unpack(result)
+end
+
+--- Gets the first value in a |list-iterator| that satisfies a predicate, starting from the end.
+---
+--- Advances the iterator. Returns nil and drains the iterator if no value is found.
+---
+--- Examples:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 1, 2, 3, 2, 1 }):enumerate()
+--- it:rfind(1)
+--- -- 5 1
+--- it:rfind(1)
+--- -- 1 1
+---
+--- ```
+---
+---@see Iter.find
+---
+---@return any
+function Iter.rfind(self, f) -- luacheck: no unused args
+ error('rfind() requires a list-like table')
+end
+
+---@private
+function ListIter.rfind(self, f) -- luacheck: no unused args
+ if type(f) ~= 'function' then
+ local val = f
+ f = function(v)
+ return v == val
+ end
+ end
+
+ local inc = self._head < self._tail and 1 or -1
+ for i = self._tail - inc, self._head, -inc do
+ local v = self._table[i]
+ if f(unpack(v)) then
+ self._tail = i
+ return unpack(v)
+ end
+ end
+ self._head = self._tail
+end
+
+--- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail).
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({1, 2, 3, 4})
+--- it:nextback()
+--- -- 4
+--- it:nextback()
+--- -- 3
+--- ```
+---
+---@return any
+function Iter.nextback(self) -- luacheck: no unused args
+ error('nextback() requires a list-like table')
+end
+
+function ListIter.nextback(self)
+ if self._head ~= self._tail then
+ local inc = self._head < self._tail and 1 or -1
+ self._tail = self._tail - inc
+ return self._table[self._tail]
+ end
+end
+
+--- Gets the last value of a |list-iterator| without consuming it.
+---
+--- See also |Iter:last()|.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({1, 2, 3, 4})
+--- it:peekback()
+--- -- 4
+--- it:peekback()
+--- -- 4
+--- it:nextback()
+--- -- 4
+--- ```
+---
+---@return any
+function Iter.peekback(self) -- luacheck: no unused args
+ error('peekback() requires a list-like table')
+end
+
+function ListIter.peekback(self)
+ if self._head ~= self._tail then
+ local inc = self._head < self._tail and 1 or -1
+ return self._table[self._tail - inc]
+ end
+end
+
+--- Skips `n` values of an iterator pipeline.
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 }):skip(2)
+--- it:next()
+--- -- 9
+---
+--- ```
+---
+---@param n number Number of values to skip.
+---@return Iter
+function Iter.skip(self, n)
+ for _ = 1, n do
+ local _ = self:next()
+ end
+ return self
+end
+
+---@private
+function ListIter.skip(self, n)
+ local inc = self._head < self._tail and n or -n
+ self._head = self._head + inc
+ if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
+ self._head = self._tail
+ end
+ return self
+end
+
+--- Skips `n` values backwards from the end of a |list-iterator| pipeline.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({ 1, 2, 3, 4, 5 }):skipback(2)
+--- it:next()
+--- -- 1
+--- it:nextback()
+--- -- 3
+--- ```
+---
+---@param n number Number of values to skip.
+---@return Iter
+function Iter.skipback(self, n) -- luacheck: no unused args
+ error('skipback() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.skipback(self, n)
+ local inc = self._head < self._tail and n or -n
+ self._tail = self._tail - inc
+ if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
+ self._head = self._tail
+ end
+ return self
+end
+
+--- Gets the nth value of an iterator (and advances to it).
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:nth(2)
+--- -- 6
+--- it:nth(2)
+--- -- 12
+---
+--- ```
+---
+---@param n number The index of the value to return.
+---@return any
+function Iter.nth(self, n)
+ if n > 0 then
+ return self:skip(n - 1):next()
+ end
+end
+
+--- Gets the nth value from the end of a |list-iterator| (and advances to it).
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter({ 3, 6, 9, 12 })
+--- it:nthback(2)
+--- -- 9
+--- it:nthback(2)
+--- -- 3
+---
+--- ```
+---
+---@param n number The index of the value to return.
+---@return any
+function Iter.nthback(self, n)
+ if n > 0 then
+ return self:skipback(n - 1):nextback()
+ end
+end
+
+--- Sets the start and end of a |list-iterator| pipeline.
+---
+--- Equivalent to `:skip(first - 1):skipback(len - last + 1)`.
+---
+---@param first number
+---@param last number
+---@return Iter
+function Iter.slice(self, first, last) -- luacheck: no unused args
+ error('slice() requires a list-like table')
+ return self
+end
+
+---@private
+function ListIter.slice(self, first, last)
+ return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1))
+end
+
+--- Returns true if any of the items in the iterator match the given predicate.
+---
+---@param pred function(...):bool Predicate function. Takes all values returned from the previous
+--- stage in the pipeline as arguments and returns true if the
+--- predicate matches.
+function Iter.any(self, pred)
+ local any = false
+
+ --- Use a closure to handle var args returned from iterator
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ if pred(...) then
+ any = true
+ else
+ return true
+ end
+ end
+ end
+
+ while fn(self:next()) do
+ end
+ return any
+end
+
+--- Returns true if all items in the iterator match the given predicate.
+---
+---@param pred function(...):bool Predicate function. Takes all values returned from the previous
+--- stage in the pipeline as arguments and returns true if the
+--- predicate matches.
+function Iter.all(self, pred)
+ local all = true
+
+ local function fn(...)
+ if select(1, ...) ~= nil then
+ if not pred(...) then
+ all = false
+ else
+ return true
+ end
+ end
+ end
+
+ while fn(self:next()) do
+ end
+ return all
+end
+
+--- Drains the iterator and returns the last item.
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter(vim.gsplit('abcdefg', ''))
+--- it:last()
+--- -- 'g'
+---
+--- local it = vim.iter({ 3, 6, 9, 12, 15 })
+--- it:last()
+--- -- 15
+---
+--- ```
+---
+---@return any
+function Iter.last(self)
+ local last = self:next()
+ local cur = self:next()
+ while cur do
+ last = cur
+ cur = self:next()
+ end
+ return last
+end
+
+---@private
+function ListIter.last(self)
+ local inc = self._head < self._tail and 1 or -1
+ local v = self._table[self._tail - inc]
+ self._head = self._tail
+ return v
+end
+
+--- Yields the item index (count) and value for each item of an iterator pipeline.
+---
+--- For list tables, this is more efficient:
+---
+--- ```lua
+--- vim.iter(ipairs(t))
+--- ```
+---
+--- instead of:
+---
+--- ```lua
+--- vim.iter(t):enumerate()
+--- ```
+---
+--- Example:
+---
+--- ```lua
+---
+--- local it = vim.iter(vim.gsplit('abc', '')):enumerate()
+--- it:next()
+--- -- 1 'a'
+--- it:next()
+--- -- 2 'b'
+--- it:next()
+--- -- 3 'c'
+---
+--- ```
+---
+---@return Iter
+function Iter.enumerate(self)
+ local i = 0
+ return self:map(function(...)
+ i = i + 1
+ return i, ...
+ end)
+end
+
+---@private
+function ListIter.enumerate(self)
+ local inc = self._head < self._tail and 1 or -1
+ for i = self._head, self._tail - inc, inc do
+ local v = self._table[i]
+ self._table[i] = pack(i, v)
+ end
+ return self
+end
+
+--- Creates a new Iter object from a table or other |iterable|.
+---
+---@param src table|function Table or iterator to drain values from
+---@return Iter
+---@private
+function Iter.new(src, ...)
+ local it = {}
+ if type(src) == 'table' then
+ local mt = getmetatable(src)
+ if mt and type(mt.__call) == 'function' then
+ ---@private
+ function it.next()
+ return src()
+ end
+
+ setmetatable(it, Iter)
+ return it
+ end
+
+ local t = {}
+
+ -- O(n): scan the source table to decide if it is a list (consecutive integer indices 1…n).
+ local count = 0
+ for _ in pairs(src) do
+ count = count + 1
+ local v = src[count]
+ if v == nil then
+ return Iter.new(pairs(src))
+ end
+ t[count] = v
+ end
+ return ListIter.new(t)
+ end
+
+ if type(src) == 'function' then
+ local s, var = ...
+
+ --- Use a closure to handle var args returned from iterator
+ local function fn(...)
+ -- Per the Lua 5.1 reference manual, an iterator is complete when the first returned value is
+ -- nil (even if there are other, non-nil return values). See |for-in|.
+ if select(1, ...) ~= nil then
+ var = select(1, ...)
+ return ...
+ end
+ end
+
+ ---@private
+ function it.next()
+ return fn(src(s, var))
+ end
+
+ setmetatable(it, Iter)
+ else
+ error('src must be a table or function')
+ end
+ return it
+end
+
+--- Create a new ListIter
+---
+---@param t table List-like table. Caller guarantees that this table is a valid list.
+---@return Iter
+---@private
+function ListIter.new(t)
+ local it = {}
+ it._table = t
+ it._head = 1
+ it._tail = #t + 1
+ setmetatable(it, ListIter)
+ return it
+end
+
+--- Collects an |iterable| into a table.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(f):totable()
+--- ```
+---
+---@param f function Iterator function
+---@return table
+function M.totable(f, ...)
+ return Iter.new(f, ...):totable()
+end
+
+--- Filters a table or other |iterable|.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(src):filter(f):totable()
+--- ```
+---
+---@see |Iter:filter()|
+---
+---@param f function(...):bool Filter function. Accepts the current iterator or table values as
+--- arguments and returns true if those values should be kept in the
+--- final table
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.filter(f, src, ...)
+ return Iter.new(src, ...):filter(f):totable()
+end
+
+--- Maps a table or other |iterable|.
+---
+--- ```lua
+--- -- Equivalent to:
+--- vim.iter(src):map(f):totable()
+--- ```
+---
+---@see |Iter:map()|
+---
+---@param f function(...):?any Map function. Accepts the current iterator or table values as
+--- arguments and returns one or more new values. Nil values are removed
+--- from the final table.
+---@param src table|function Table or iterator function to filter
+---@return table
+function M.map(f, src, ...)
+ return Iter.new(src, ...):map(f):totable()
+end
+
+---@type IterMod
+return setmetatable(M, {
+ __call = function(_, ...)
+ return Iter.new(...)
+ end,
+})
diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua
index ef1c66ea20..bdea95f9ab 100644
--- a/runtime/lua/vim/keymap.lua
+++ b/runtime/lua/vim/keymap.lua
@@ -1,53 +1,41 @@
local keymap = {}
---- Add a new |mapping|.
+--- Adds a new |mapping|.
--- Examples:
---- <pre>lua
---- -- Can add mapping to Lua functions
---- vim.keymap.set('n', 'lhs', function() print("real lua function") end)
---
---- -- Can use it to map multiple modes
---- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true })
+--- ```lua
+--- -- Map to a Lua function:
+--- vim.keymap.set('n', 'lhs', function() print("real lua function") end)
+--- -- Map to multiple modes:
+--- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true })
+--- -- Buffer-local mapping:
+--- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
+--- -- Expr mapping:
+--- vim.keymap.set('i', '<Tab>', function()
+--- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
+--- end, { expr = true })
+--- -- <Plug> mapping:
+--- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)')
+--- ```
---
---- -- Can add mapping for specific buffer
---- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 })
----
---- -- Expr mappings
---- vim.keymap.set('i', '<Tab>', function()
---- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
---- end, { expr = true })
---- -- <Plug> mappings
---- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)')
---- </pre>
----
---- Note that in a mapping like:
---- <pre>lua
---- vim.keymap.set('n', 'asdf', require('jkl').my_fun)
---- </pre>
----
---- the ``require('jkl')`` gets evaluated during this call in order to access the function.
---- If you want to avoid this cost at startup you can wrap it in a function, for example:
---- <pre>lua
---- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end)
---- </pre>
----
----@param mode string|table Same mode short names as |nvim_set_keymap()|.
+---@param mode string|table Mode short-name, see |nvim_set_keymap()|.
--- Can also be list of modes to create mapping on multiple modes.
---@param lhs string Left-hand side |{lhs}| of the mapping.
----@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function.
---
----@param opts table|nil A table of |:map-arguments|.
---- + Accepts options accepted by the {opts} parameter in |nvim_set_keymap()|,
---- with the following notable differences:
---- - replace_keycodes: Defaults to `true` if "expr" is `true`.
---- - noremap: Always overridden with the inverse of "remap" (see below).
---- + In addition to those options, the table accepts the following keys:
---- - buffer: (number or boolean) Add a mapping to the given buffer.
---- When `0` or `true`, use the current buffer.
---- - remap: (boolean) Make the mapping recursive.
---- This is the inverse of the "noremap" option from |nvim_set_keymap()|.
+---@param rhs string|function Right-hand side |{rhs}| of the mapping, can be a Lua function.
+---
+---@param opts table|nil Table of |:map-arguments|.
+--- - Same as |nvim_set_keymap()| {opts}, except:
+--- - "replace_keycodes" defaults to `true` if "expr" is `true`.
+--- - "noremap": inverse of "remap" (see below).
+--- - Also accepts:
+--- - "buffer": (integer|boolean) Creates buffer-local mapping, `0` or `true`
+--- for current buffer.
+--- - "remap": (boolean) Make the mapping recursive. Inverse of "noremap".
--- Defaults to `false`.
---@see |nvim_set_keymap()|
+---@see |maparg()|
+---@see |mapcheck()|
+---@see |mapset()|
function keymap.set(mode, lhs, rhs, opts)
vim.validate({
mode = { mode, { 's', 't' } },
@@ -56,7 +44,9 @@ function keymap.set(mode, lhs, rhs, opts)
opts = { opts, 't', true },
})
- opts = vim.deepcopy(opts) or {}
+ opts = vim.deepcopy(opts or {})
+
+ ---@cast mode string[]
mode = type(mode) == 'string' and { mode } or mode
if opts.expr and opts.replace_keycodes ~= false then
@@ -69,7 +59,7 @@ function keymap.set(mode, lhs, rhs, opts)
else
-- remaps behavior is opposite of noremap option.
opts.noremap = not opts.remap
- opts.remap = nil
+ opts.remap = nil ---@type boolean?
end
if type(rhs) == 'function' then
@@ -78,8 +68,8 @@ function keymap.set(mode, lhs, rhs, opts)
end
if opts.buffer then
- local bufnr = opts.buffer == true and 0 or opts.buffer
- opts.buffer = nil
+ local bufnr = opts.buffer == true and 0 or opts.buffer --[[@as integer]]
+ opts.buffer = nil ---@type integer?
for _, m in ipairs(mode) do
vim.api.nvim_buf_set_keymap(bufnr, m, lhs, rhs, opts)
end
@@ -93,14 +83,16 @@ end
--- Remove an existing mapping.
--- Examples:
---- <pre>lua
---- vim.keymap.del('n', 'lhs')
---
---- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
---- </pre>
+--- ```lua
+--- vim.keymap.del('n', 'lhs')
+---
+--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
+--- ```
+---
---@param opts table|nil A table of optional arguments:
---- - buffer: (number or boolean) Remove a mapping from the given buffer.
---- When "true" or 0, use the current buffer.
+--- - "buffer": (integer|boolean) Remove a mapping from the given buffer.
+--- When `0` or `true`, use the current buffer.
---@see |vim.keymap.set()|
---
function keymap.del(modes, lhs, opts)
@@ -113,9 +105,9 @@ function keymap.del(modes, lhs, opts)
opts = opts or {}
modes = type(modes) == 'string' and { modes } or modes
- local buffer = false
+ local buffer = false ---@type false|integer
if opts.buffer ~= nil then
- buffer = opts.buffer == true and 0 or opts.buffer
+ buffer = opts.buffer == true and 0 or opts.buffer --[[@as integer]]
end
if buffer == false then
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
new file mode 100644
index 0000000000..ee01111337
--- /dev/null
+++ b/runtime/lua/vim/loader.lua
@@ -0,0 +1,536 @@
+local uv = vim.uv
+local uri_encode = vim.uri_encode
+
+--- @type (fun(modename: string): fun()|string)[]
+local loaders = package.loaders
+
+local M = {}
+
+---@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: uv.aliases.fs_stat_types}
+---@alias CacheEntry {hash:CacheHash, chunk:string}
+
+---@class ModuleFindOpts
+---@field all? boolean Search for all matches (defaults to `false`)
+---@field rtp? boolean Search for modname in the runtime path (defaults to `true`)
+---@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`)
+---@field paths? string[] Extra paths to search for modname
+
+---@class ModuleInfo
+---@field modpath string Path of the module
+---@field modname string Name of the module
+---@field stat? uv.uv_fs_t File stat of the module path
+
+---@alias LoaderStats table<string, {total:number, time:number, [string]:number?}?>
+
+---@nodoc
+M.path = vim.fn.stdpath('cache') .. '/luac'
+
+---@nodoc
+M.enabled = false
+
+---@class Loader
+---@field _rtp string[]
+---@field _rtp_pure string[]
+---@field _rtp_key string
+---@field _hashes? table<string, CacheHash>
+local Loader = {
+ VERSION = 4,
+ ---@type table<string, table<string,ModuleInfo>>
+ _indexed = {},
+ ---@type table<string, string[]>
+ _topmods = {},
+ _loadfile = loadfile,
+ ---@type LoaderStats
+ _stats = {
+ find = { total = 0, time = 0, not_found = 0 },
+ },
+}
+
+--- @param path string
+--- @return CacheHash
+--- @private
+function Loader.get_hash(path)
+ if not Loader._hashes then
+ return uv.fs_stat(path) --[[@as CacheHash]]
+ end
+
+ if not Loader._hashes[path] then
+ -- Note we must never save a stat for a non-existent path.
+ -- For non-existent paths fs_stat() will return nil.
+ Loader._hashes[path] = uv.fs_stat(path)
+ end
+ return Loader._hashes[path]
+end
+
+local function normalize(path)
+ return vim.fs.normalize(path, { expand_env = false })
+end
+
+--- Gets the rtp excluding after directories.
+--- The result is cached, and will be updated if the runtime path changes.
+--- When called from a fast event, the cached value will be returned.
+--- @return string[] rtp, boolean updated
+---@private
+function Loader.get_rtp()
+ if vim.in_fast_event() then
+ return (Loader._rtp or {}), false
+ end
+ local updated = false
+ local key = vim.go.rtp
+ if key ~= Loader._rtp_key then
+ Loader._rtp = {}
+ for _, path in ipairs(vim.api.nvim_get_runtime_file('', true)) do
+ path = normalize(path)
+ -- skip after directories
+ if
+ path:sub(-6, -1) ~= '/after'
+ and not (Loader._indexed[path] and vim.tbl_isempty(Loader._indexed[path]))
+ then
+ Loader._rtp[#Loader._rtp + 1] = path
+ end
+ end
+ updated = true
+ Loader._rtp_key = key
+ end
+ return Loader._rtp, updated
+end
+
+--- Returns the cache file name
+---@param name string can be a module name, or a file name
+---@return string file_name
+---@private
+function Loader.cache_file(name)
+ local ret = ('%s/%s'):format(M.path, uri_encode(name, 'rfc2396'))
+ return ret:sub(-4) == '.lua' and (ret .. 'c') or (ret .. '.luac')
+end
+
+--- Saves the cache entry for a given module or file
+---@param name string module name or filename
+---@param entry CacheEntry
+---@private
+function Loader.write(name, entry)
+ local cname = Loader.cache_file(name)
+ local f = assert(uv.fs_open(cname, 'w', 438))
+ local header = {
+ Loader.VERSION,
+ entry.hash.size,
+ entry.hash.mtime.sec,
+ entry.hash.mtime.nsec,
+ }
+ uv.fs_write(f, table.concat(header, ',') .. '\0')
+ uv.fs_write(f, entry.chunk)
+ uv.fs_close(f)
+end
+
+--- @param path string
+--- @param mode integer
+--- @return string? data
+local function readfile(path, mode)
+ local f = uv.fs_open(path, 'r', mode)
+ if f then
+ local hash = assert(uv.fs_fstat(f))
+ local data = uv.fs_read(f, hash.size, 0) --[[@as string?]]
+ uv.fs_close(f)
+ return data
+ end
+end
+
+--- Loads the cache entry for a given module or file
+---@param name string module name or filename
+---@return CacheEntry?
+---@private
+function Loader.read(name)
+ local cname = Loader.cache_file(name)
+ local data = readfile(cname, 438)
+ if data then
+ local zero = data:find('\0', 1, true)
+ if not zero then
+ return
+ end
+
+ ---@type integer[]|{[0]:integer}
+ local header = vim.split(data:sub(1, zero - 1), ',')
+ if tonumber(header[1]) ~= Loader.VERSION then
+ return
+ end
+ return {
+ hash = {
+ size = tonumber(header[2]),
+ mtime = { sec = tonumber(header[3]), nsec = tonumber(header[4]) },
+ },
+ chunk = data:sub(zero + 1),
+ }
+ end
+end
+
+--- The `package.loaders` loader for Lua files using the cache.
+---@param modname string module name
+---@return string|function
+---@private
+function Loader.loader(modname)
+ Loader._hashes = {}
+ local ret = M.find(modname)[1]
+ if ret then
+ -- Make sure to call the global loadfile so we respect any augmentations done elsewhere.
+ -- E.g. profiling
+ local chunk, err = loadfile(ret.modpath)
+ Loader._hashes = nil
+ return chunk or error(err)
+ end
+ Loader._hashes = nil
+ return '\ncache_loader: module ' .. modname .. ' not found'
+end
+
+--- The `package.loaders` loader for libs
+---@param modname string module name
+---@return string|function
+---@private
+function Loader.loader_lib(modname)
+ local sysname = uv.os_uname().sysname:lower() or ''
+ local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true)
+ local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1]
+ ---@type function?, string?
+ if ret then
+ -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
+ -- a) strip prefix up to and including the first dash, if any
+ -- b) replace all dots by underscores
+ -- c) prepend "luaopen_"
+ -- So "foo-bar.baz" should result in "luaopen_bar_baz"
+ local dash = modname:find('-', 1, true)
+ local funcname = dash and modname:sub(dash + 1) or modname
+ local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_'))
+ return chunk or error(err)
+ end
+ return '\ncache_loader_lib: module ' .. modname .. ' not found'
+end
+
+--- `loadfile` using the cache
+--- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible.
+---@param filename? string
+---@param mode? "b"|"t"|"bt"
+---@param env? table
+---@return function?, string? error_message
+---@private
+-- luacheck: ignore 312
+function Loader.loadfile(filename, mode, env)
+ -- ignore mode, since we byte-compile the Lua source files
+ mode = nil
+ return Loader.load(normalize(filename), { mode = mode, env = env })
+end
+
+--- Checks whether two cache hashes are the same based on:
+--- * file size
+--- * mtime in seconds
+--- * mtime in nanoseconds
+---@param h1 CacheHash
+---@param h2 CacheHash
+---@private
+function Loader.eq(h1, h2)
+ return h1
+ and h2
+ and h1.size == h2.size
+ and h1.mtime.sec == h2.mtime.sec
+ and h1.mtime.nsec == h2.mtime.nsec
+end
+
+--- Loads the given module path using the cache
+---@param modpath string
+---@param opts? {mode?: "b"|"t"|"bt", env?:table} (table|nil) Options for loading the module:
+--- - mode: (string) the mode to load the module with. "b"|"t"|"bt" (defaults to `nil`)
+--- - env: (table) the environment to load the module in. (defaults to `nil`)
+---@see |luaL_loadfile()|
+---@return function?, string? error_message
+---@private
+function Loader.load(modpath, opts)
+ opts = opts or {}
+ local hash = Loader.get_hash(modpath)
+ ---@type function?, string?
+ local chunk, err
+
+ if not hash then
+ -- trigger correct error
+ return Loader._loadfile(modpath, opts.mode, opts.env)
+ end
+
+ local entry = Loader.read(modpath)
+ if entry and Loader.eq(entry.hash, hash) then
+ -- found in cache and up to date
+ chunk, err = load(entry.chunk --[[@as string]], '@' .. modpath, opts.mode, opts.env)
+ if not (err and err:find('cannot load incompatible bytecode', 1, true)) then
+ return chunk, err
+ end
+ end
+ entry = { hash = hash, modpath = modpath }
+
+ chunk, err = Loader._loadfile(modpath, opts.mode, opts.env)
+ if chunk then
+ entry.chunk = string.dump(chunk)
+ Loader.write(modpath, entry)
+ end
+ return chunk, err
+end
+
+--- Finds Lua modules for the given module name.
+---@param modname string Module name, or `"*"` to find the top-level modules instead
+---@param opts? ModuleFindOpts (table|nil) Options for finding a module:
+--- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`)
+--- - paths: (string[]) Extra paths to search for modname (defaults to `{}`)
+--- - patterns: (string[]) List of patterns to use when searching for modules.
+--- A pattern is a string added to the basename of the Lua module being searched.
+--- (defaults to `{"/init.lua", ".lua"}`)
+--- - all: (boolean) Return all matches instead of just the first one (defaults to `false`)
+---@return ModuleInfo[] (list) A list of results with the following properties:
+--- - modpath: (string) the path to the module
+--- - modname: (string) the name of the module
+--- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"`
+function M.find(modname, opts)
+ opts = opts or {}
+
+ modname = modname:gsub('/', '.')
+ local basename = modname:gsub('%.', '/')
+ local idx = modname:find('.', 1, true)
+
+ -- HACK: fix incorrect require statements. Really not a fan of keeping this,
+ -- but apparently the regular Lua loader also allows this
+ if idx == 1 then
+ modname = modname:gsub('^%.+', '')
+ basename = modname:gsub('%.', '/')
+ idx = modname:find('.', 1, true)
+ end
+
+ -- get the top-level module name
+ local topmod = idx and modname:sub(1, idx - 1) or modname
+
+ -- OPTIM: search for a directory first when topmod == modname
+ local patterns = opts.patterns
+ or (topmod == modname and { '/init.lua', '.lua' } or { '.lua', '/init.lua' })
+ for p, pattern in ipairs(patterns) do
+ patterns[p] = '/lua/' .. basename .. pattern
+ end
+
+ ---@type ModuleInfo[]
+ local results = {}
+
+ -- Only continue if we haven't found anything yet or we want to find all
+ local function continue()
+ return #results == 0 or opts.all
+ end
+
+ -- Checks if the given paths contain the top-level module.
+ -- If so, it tries to find the module path for the given module name.
+ ---@param paths string[]
+ local function _find(paths)
+ for _, path in ipairs(paths) do
+ if topmod == '*' then
+ for _, r in pairs(Loader.lsmod(path)) do
+ results[#results + 1] = r
+ if not continue() then
+ return
+ end
+ end
+ elseif Loader.lsmod(path)[topmod] then
+ for _, pattern in ipairs(patterns) do
+ local modpath = path .. pattern
+ Loader._stats.find.stat = (Loader._stats.find.stat or 0) + 1
+ local hash = Loader.get_hash(modpath)
+ if hash then
+ results[#results + 1] = { modpath = modpath, stat = hash, modname = modname }
+ if not continue() then
+ return
+ end
+ end
+ end
+ end
+ end
+ end
+
+ -- always check the rtp first
+ if opts.rtp ~= false then
+ _find(Loader._rtp or {})
+ if continue() then
+ local rtp, updated = Loader.get_rtp()
+ if updated then
+ _find(rtp)
+ end
+ end
+ end
+
+ -- check any additional paths
+ if continue() and opts.paths then
+ _find(opts.paths)
+ end
+
+ if #results == 0 then
+ -- module not found
+ Loader._stats.find.not_found = Loader._stats.find.not_found + 1
+ end
+
+ return results
+end
+
+--- Resets the cache for the path, or all the paths
+--- if path is nil.
+---@param path string? path to reset
+function M.reset(path)
+ if path then
+ Loader._indexed[normalize(path)] = nil
+ else
+ Loader._indexed = {}
+ end
+
+ -- Path could be a directory so just clear all the hashes.
+ if Loader._hashes then
+ Loader._hashes = {}
+ end
+end
+
+--- Enables the experimental Lua module loader:
+--- * overrides loadfile
+--- * adds the Lua loader using the byte-compilation cache
+--- * adds the libs loader
+--- * removes the default Nvim loader
+function M.enable()
+ if M.enabled then
+ return
+ end
+ M.enabled = true
+ vim.fn.mkdir(vim.fn.fnamemodify(M.path, ':p'), 'p')
+ _G.loadfile = Loader.loadfile
+ -- add Lua loader
+ table.insert(loaders, 2, Loader.loader)
+ -- add libs loader
+ table.insert(loaders, 3, Loader.loader_lib)
+ -- remove Nvim loader
+ for l, loader in ipairs(loaders) do
+ if loader == vim._load_package then
+ table.remove(loaders, l)
+ break
+ end
+ end
+end
+
+--- Disables the experimental Lua module loader:
+--- * removes the loaders
+--- * adds the default Nvim loader
+function M.disable()
+ if not M.enabled then
+ return
+ end
+ M.enabled = false
+ _G.loadfile = Loader._loadfile
+ for l, loader in ipairs(loaders) do
+ if loader == Loader.loader or loader == Loader.loader_lib then
+ table.remove(loaders, l)
+ end
+ end
+ table.insert(loaders, 2, vim._load_package)
+end
+
+--- Return the top-level \`/lua/*` modules for this path
+---@param path string path to check for top-level Lua modules
+---@private
+function Loader.lsmod(path)
+ if not Loader._indexed[path] then
+ Loader._indexed[path] = {}
+ for name, t in vim.fs.dir(path .. '/lua') do
+ local modpath = path .. '/lua/' .. name
+ -- HACK: type is not always returned due to a bug in luv
+ t = t or Loader.get_hash(modpath).type
+ ---@type string
+ local topname
+ local ext = name:sub(-4)
+ if ext == '.lua' or ext == '.dll' then
+ topname = name:sub(1, -5)
+ elseif name:sub(-3) == '.so' then
+ topname = name:sub(1, -4)
+ elseif t == 'link' or t == 'directory' then
+ topname = name
+ end
+ if topname then
+ Loader._indexed[path][topname] = { modpath = modpath, modname = topname }
+ Loader._topmods[topname] = Loader._topmods[topname] or {}
+ if not vim.list_contains(Loader._topmods[topname], path) then
+ table.insert(Loader._topmods[topname], path)
+ end
+ end
+ end
+ end
+ return Loader._indexed[path]
+end
+
+--- Tracks the time spent in a function
+--- @generic F: function
+--- @param f F
+--- @return F
+--- @private
+function Loader.track(stat, f)
+ return function(...)
+ local start = vim.uv.hrtime()
+ local r = { f(...) }
+ Loader._stats[stat] = Loader._stats[stat] or { total = 0, time = 0 }
+ Loader._stats[stat].total = Loader._stats[stat].total + 1
+ Loader._stats[stat].time = Loader._stats[stat].time + uv.hrtime() - start
+ return unpack(r, 1, table.maxn(r))
+ end
+end
+
+---@class ProfileOpts
+---@field loaders? boolean Add profiling to the loaders
+
+--- Debug function that wraps all loaders and tracks stats
+---@private
+---@param opts ProfileOpts?
+function M._profile(opts)
+ Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp)
+ Loader.read = Loader.track('read', Loader.read)
+ Loader.loader = Loader.track('loader', Loader.loader)
+ Loader.loader_lib = Loader.track('loader_lib', Loader.loader_lib)
+ Loader.loadfile = Loader.track('loadfile', Loader.loadfile)
+ Loader.load = Loader.track('load', Loader.load)
+ M.find = Loader.track('find', M.find)
+ Loader.lsmod = Loader.track('lsmod', Loader.lsmod)
+
+ if opts and opts.loaders then
+ for l, loader in pairs(loaders) do
+ local loc = debug.getinfo(loader, 'Sn').source:sub(2)
+ loaders[l] = Loader.track('loader ' .. l .. ': ' .. loc, loader)
+ end
+ end
+end
+
+--- Prints all cache stats
+---@param opts? {print?:boolean}
+---@return LoaderStats
+---@private
+function M._inspect(opts)
+ if opts and opts.print then
+ local function ms(nsec)
+ return math.floor(nsec / 1e6 * 1000 + 0.5) / 1000 .. 'ms'
+ end
+ local chunks = {} ---@type string[][]
+ ---@type string[]
+ local stats = vim.tbl_keys(Loader._stats)
+ table.sort(stats)
+ for _, stat in ipairs(stats) do
+ vim.list_extend(chunks, {
+ { '\n' .. stat .. '\n', 'Title' },
+ { '* total: ' },
+ { tostring(Loader._stats[stat].total) .. '\n', 'Number' },
+ { '* time: ' },
+ { ms(Loader._stats[stat].time) .. '\n', 'Bold' },
+ { '* avg time: ' },
+ { ms(Loader._stats[stat].time / Loader._stats[stat].total) .. '\n', 'Bold' },
+ })
+ for k, v in pairs(Loader._stats[stat]) do
+ if not vim.list_contains({ 'time', 'total' }, k) then
+ chunks[#chunks + 1] = { '* ' .. k .. ':' .. string.rep(' ', 9 - #k) }
+ chunks[#chunks + 1] = { tostring(v) .. '\n', 'Number' }
+ end
+ end
+ end
+ vim.api.nvim_echo(chunks, true, {})
+ end
+ return Loader._stats
+end
+
+return M
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index c5392ac154..261a3aa5de 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,19 +1,17 @@
+---@diagnostic disable: invisible
local default_handlers = require('vim.lsp.handlers')
local log = require('vim.lsp.log')
local lsp_rpc = require('vim.lsp.rpc')
local protocol = require('vim.lsp.protocol')
+local ms = protocol.Methods
local util = require('vim.lsp.util')
local sync = require('vim.lsp.sync')
local semantic_tokens = require('vim.lsp.semantic_tokens')
local api = vim.api
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_buf_get_option, nvim_exec_autocmds =
- api.nvim_err_writeln,
- api.nvim_buf_get_lines,
- api.nvim_command,
- api.nvim_buf_get_option,
- api.nvim_exec_autocmds
-local uv = vim.loop
+local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
+ api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
+local uv = vim.uv
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
local if_nil = vim.F.if_nil
@@ -26,6 +24,7 @@ local lsp = {
buf = require('vim.lsp.buf'),
diagnostic = require('vim.lsp.diagnostic'),
codelens = require('vim.lsp.codelens'),
+ inlay_hint = require('vim.lsp.inlay_hint'),
semantic_tokens = semantic_tokens,
util = util,
@@ -38,47 +37,50 @@ local lsp = {
-- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = {
- ['textDocument/hover'] = { 'hoverProvider' },
- ['textDocument/signatureHelp'] = { 'signatureHelpProvider' },
- ['textDocument/definition'] = { 'definitionProvider' },
- ['textDocument/implementation'] = { 'implementationProvider' },
- ['textDocument/declaration'] = { 'declarationProvider' },
- ['textDocument/typeDefinition'] = { 'typeDefinitionProvider' },
- ['textDocument/documentSymbol'] = { 'documentSymbolProvider' },
- ['textDocument/prepareCallHierarchy'] = { 'callHierarchyProvider' },
- ['textDocument/rename'] = { 'renameProvider' },
- ['textDocument/prepareRename'] = { 'renameProvider', 'prepareProvider' },
- ['textDocument/codeAction'] = { 'codeActionProvider' },
- ['textDocument/codeLens'] = { 'codeLensProvider' },
- ['codeLens/resolve'] = { 'codeLensProvider', 'resolveProvider' },
- ['workspace/executeCommand'] = { 'executeCommandProvider' },
- ['workspace/symbol'] = { 'workspaceSymbolProvider' },
- ['textDocument/references'] = { 'referencesProvider' },
- ['textDocument/rangeFormatting'] = { 'documentRangeFormattingProvider' },
- ['textDocument/formatting'] = { 'documentFormattingProvider' },
- ['textDocument/completion'] = { 'completionProvider' },
- ['textDocument/documentHighlight'] = { 'documentHighlightProvider' },
- ['textDocument/semanticTokens/full'] = { 'semanticTokensProvider' },
- ['textDocument/semanticTokens/full/delta'] = { 'semanticTokensProvider' },
+ [ms.textDocument_hover] = { 'hoverProvider' },
+ [ms.textDocument_signatureHelp] = { 'signatureHelpProvider' },
+ [ms.textDocument_definition] = { 'definitionProvider' },
+ [ms.textDocument_implementation] = { 'implementationProvider' },
+ [ms.textDocument_declaration] = { 'declarationProvider' },
+ [ms.textDocument_typeDefinition] = { 'typeDefinitionProvider' },
+ [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' },
+ [ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' },
+ [ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' },
+ [ms.callHierarchy_outgoingCalls] = { 'callHierarchyProvider' },
+ [ms.textDocument_rename] = { 'renameProvider' },
+ [ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' },
+ [ms.textDocument_codeAction] = { 'codeActionProvider' },
+ [ms.textDocument_codeLens] = { 'codeLensProvider' },
+ [ms.codeLens_resolve] = { 'codeLensProvider', 'resolveProvider' },
+ [ms.codeAction_resolve] = { 'codeActionProvider', 'resolveProvider' },
+ [ms.workspace_executeCommand] = { 'executeCommandProvider' },
+ [ms.workspace_symbol] = { 'workspaceSymbolProvider' },
+ [ms.textDocument_references] = { 'referencesProvider' },
+ [ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' },
+ [ms.textDocument_formatting] = { 'documentFormattingProvider' },
+ [ms.textDocument_completion] = { 'completionProvider' },
+ [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' },
+ [ms.textDocument_semanticTokens_full] = { 'semanticTokensProvider' },
+ [ms.textDocument_semanticTokens_full_delta] = { 'semanticTokensProvider' },
+ [ms.textDocument_inlayHint] = { 'inlayHintProvider' },
+ [ms.textDocument_diagnostic] = { 'diagnosticProvider' },
+ [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' },
}
-- TODO improve handling of scratch buffers with LSP attached.
----@private
--- Concatenates and writes a list of strings to the Vim error buffer.
---
----@param {...} table[] List to write to the buffer
+---@param ... string List to write to the buffer
local function err_message(...)
nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
nvim_command('redraw')
end
----@private
--- Returns the buffer number for the given {bufnr}.
---
----@param bufnr (number|nil) Buffer number to resolve. Defaults to the current
----buffer if not given.
----@returns bufnr (number) Number of requested buffer
+---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
+---@return integer bufnr
local function resolve_bufnr(bufnr)
validate({ bufnr = { bufnr, 'n', true } })
if bufnr == nil or bufnr == 0 then
@@ -100,11 +102,10 @@ function lsp._unsupported_method(method)
return msg
end
----@private
--- Checks whether a given path is a directory.
---
---@param filename (string) path to check
----@returns true if {filename} exists and is a directory, false otherwise
+---@return boolean # true if {filename} exists and is a directory, false otherwise
local function is_dir(filename)
validate({ filename = { filename, 's' } })
local stat = uv.fs_stat(filename)
@@ -131,28 +132,27 @@ local format_line_ending = {
['mac'] = '\r',
}
----@private
---@param bufnr (number)
----@returns (string)
+---@return string
local function buf_get_line_ending(bufnr)
- return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n'
+ return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
end
local client_index = 0
----@private
--- Returns a new, unused client id.
---
----@returns (number) client id
+---@return integer client_id
local function next_client_id()
client_index = client_index + 1
return client_index
end
-- Tracks all clients created via lsp.start_client
-local active_clients = {}
-local all_buffer_active_clients = {}
-local uninitialized_clients = {}
+local active_clients = {} --- @type table<integer,lsp.Client>
+local all_buffer_active_clients = {} --- @type table<integer,table<integer,true>>
+local uninitialized_clients = {} --- @type table<integer,lsp.Client>
----@private
+---@param bufnr? integer
+---@param fn fun(client: lsp.Client, client_id: integer, bufnr: integer)
local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
validate({
fn = { fn, 'f' },
@@ -165,9 +165,9 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
end
if restrict_client_ids and #restrict_client_ids > 0 then
- local filtered_client_ids = {}
+ local filtered_client_ids = {} --- @type table<integer,true>
for client_id in pairs(client_ids) do
- if vim.tbl_contains(restrict_client_ids, client_id) then
+ if vim.list_contains(restrict_client_ids, client_id) then
filtered_client_ids[client_id] = true
end
end
@@ -182,22 +182,24 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
end
end
--- Error codes to be used with `on_error` from |vim.lsp.start_client|.
--- Can be used to look up the string from a the number or the number
--- from the string.
+--- Error codes to be used with `on_error` from |vim.lsp.start_client|.
+--- Can be used to look up the string from a the number or the number
+--- from the string.
+--- @nodoc
lsp.client_errors = tbl_extend(
'error',
lsp_rpc.client_errors,
vim.tbl_add_reverse_lookup({
- ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
+ BEFORE_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
+ ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 2,
+ ON_ATTACH_ERROR = table.maxn(lsp_rpc.client_errors) + 3,
})
)
----@private
--- Normalizes {encoding} to valid LSP encoding names.
---
---@param encoding (string) Encoding to normalize
----@returns (string) normalized encoding name
+---@return string # normalized encoding name
local function validate_encoding(encoding)
validate({
encoding = { encoding, 's' },
@@ -215,9 +217,8 @@ end
--- Parses a command invocation into the command itself and its args. If there
--- are no arguments, an empty table is returned as the second argument.
---
----@param input (List)
----@returns (string) the command
----@returns (list of strings) its arguments
+---@param input string[]
+---@return string command, string[] args #the command and arguments
function lsp._cmd_parts(input)
validate({
cmd = {
@@ -241,12 +242,11 @@ function lsp._cmd_parts(input)
return cmd, cmd_args
end
----@private
--- Augments a validator function with support for optional (nil) values.
---
----@param fn (fun(v)) The original validator function; should return a
+---@param fn (fun(v): boolean) The original validator function; should return a
---bool.
----@returns (fun(v)) The augmented function. Also returns true if {v} is
+---@return fun(v): boolean # The augmented function. Also returns true if {v} is
---`nil`.
local function optional_validator(fn)
return function(v)
@@ -254,14 +254,12 @@ local function optional_validator(fn)
end
end
----@private
--- Validates a client configuration as given to |vim.lsp.start_client()|.
---
----@param config (table)
----@returns (table) "Cleaned" config, containing only the command, its
----arguments, and a valid encoding.
----
----@see |vim.lsp.start_client()|
+---@param config (lsp.ClientConfig)
+---@return (string|fun(dispatchers:table):table) Command
+---@return string[] Arguments
+---@return string Encoding.
local function validate_client_config(config)
validate({
config = { config, 't' },
@@ -292,48 +290,44 @@ local function validate_client_config(config)
'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
)
- local cmd, cmd_args
- if type(config.cmd) == 'function' then
- cmd = config.cmd
+ local cmd, cmd_args --- @type (string|fun(dispatchers:table):table), string[]
+ local config_cmd = config.cmd
+ if type(config_cmd) == 'function' then
+ cmd = config_cmd
else
- cmd, cmd_args = lsp._cmd_parts(config.cmd)
+ cmd, cmd_args = lsp._cmd_parts(config_cmd)
end
local offset_encoding = valid_encodings.UTF16
if config.offset_encoding then
offset_encoding = validate_encoding(config.offset_encoding)
end
- return {
- cmd = cmd,
- cmd_args = cmd_args,
- offset_encoding = offset_encoding,
- }
+ return cmd, cmd_args, offset_encoding
end
----@private
--- Returns full text of buffer {bufnr} as a string.
---
---@param bufnr (number) Buffer handle, or 0 for current.
----@returns Buffer text as string.
+---@return string # Buffer text as string.
local function buf_get_full_text(bufnr)
local line_ending = buf_get_line_ending(bufnr)
local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
- if nvim_buf_get_option(bufnr, 'eol') then
+ if vim.bo[bufnr].eol then
text = text .. line_ending
end
return text
end
----@private
--- Memoizes a function. On first run, the function return value is saved and
--- immediately returned on subsequent runs. If the function returns a multival,
--- only the first returned value will be memoized and returned. The function will only be run once,
--- even if it has side effects.
---
----@param fn (function) Function to run
----@returns (function) Memoized function
+---@generic T: function
+---@param fn (T) Function to run
+---@return T
local function once(fn)
- local value
+ local value --- @type any
local ran = false
return function(...)
if not ran then
@@ -365,7 +359,7 @@ do
--- smallest debounce interval is used and we don't group clients by different intervals.
---
--- @class CTGroup
- --- @field sync_kind number TextDocumentSyncKind, considers config.flags.allow_incremental_sync
+ --- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync
--- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
---
--- @class CTBufferState
@@ -373,17 +367,16 @@ do
--- @field lines string[] snapshot of buffer lines from last didChange
--- @field lines_tmp string[]
--- @field pending_changes table[] List of debounced changes in incremental sync mode
- --- @field timer nil|userdata uv_timer
+ --- @field timer nil|uv.uv_timer_t uv_timer
--- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
--- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
- --- @field refs number how many clients are using this group
+ --- @field refs integer how many clients are using this group
---
--- @class CTGroupState
- --- @field buffers table<number, CTBufferState>
- --- @field debounce number debounce duration in ms
- --- @field clients table<number, table> clients using this state. {client_id, client}
+ --- @field buffers table<integer, CTBufferState>
+ --- @field debounce integer debounce duration in ms
+ --- @field clients table<integer, table> clients using this state. {client_id, client}
- ---@private
---@param group CTGroup
---@return string
local function group_key(group)
@@ -404,12 +397,10 @@ do
end,
})
- ---@private
---@return CTGroup
local function get_group(client)
local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true)
- local change_capability =
- vim.tbl_get(client.server_capabilities or {}, 'textDocumentSync', 'change')
+ local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
sync_kind = protocol.TextDocumentSyncKind.Full
@@ -420,7 +411,6 @@ do
}
end
- ---@private
---@param state CTBufferState
local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline)
local prev_lines = state.lines
@@ -544,15 +534,13 @@ do
end
end
- ---@private
- --
-- Adjust debounce time by taking time of last didChange notification into
-- consideration. If the last didChange happened more than `debounce` time ago,
-- debounce can be skipped and otherwise maybe reduced.
--
-- This turns the debounce into a kind of client rate limiting
--
- ---@param debounce number
+ ---@param debounce integer
---@param buf_state CTBufferState
---@return number
local function next_debounce(debounce, buf_state)
@@ -568,9 +556,8 @@ do
return math.max(debounce - ms_since_last_flush, 0)
end
- ---@private
- ---@param bufnr number
- ---@param sync_kind number protocol.TextDocumentSyncKind
+ ---@param bufnr integer
+ ---@param sync_kind integer protocol.TextDocumentSyncKind
---@param state CTGroupState
---@param buf_state CTBufferState
local function send_changes(bufnr, sync_kind, state, buf_state)
@@ -599,7 +586,7 @@ do
local uri = vim.uri_from_bufnr(bufnr)
for _, client in pairs(state.clients) do
if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then
- client.notify('textDocument/didChange', {
+ client.notify(ms.textDocument_didChange, {
textDocument = {
uri = uri,
version = util.buf_versions[bufnr],
@@ -612,8 +599,8 @@ do
---@private
function changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
- local groups = {}
- for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do
+ local groups = {} ---@type table<string,CTGroup>
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
local group = get_group(client)
groups[group_key(group)] = group
end
@@ -648,7 +635,7 @@ do
if debounce == 0 then
send_changes(bufnr, group.sync_kind, state, buf_state)
else
- local timer = uv.new_timer()
+ local timer = assert(uv.new_timer(), 'Must be able to create timer')
buf_state.timer = timer
timer:start(
debounce,
@@ -695,10 +682,9 @@ do
end
end
----@private
--- Default handler for the 'textDocument/didOpen' LSP notification.
---
----@param bufnr number Number of the buffer, or 0 for current
+---@param bufnr integer Number of the buffer, or 0 for current
---@param client table Client object
local function text_document_did_open_handler(bufnr, client)
changetracking.init(client, bufnr)
@@ -708,7 +694,7 @@ local function text_document_did_open_handler(bufnr, client)
if not api.nvim_buf_is_loaded(bufnr) then
return
end
- local filetype = nvim_buf_get_option(bufnr, 'filetype')
+ local filetype = vim.bo[bufnr].filetype
local params = {
textDocument = {
@@ -718,7 +704,7 @@ local function text_document_did_open_handler(bufnr, client)
text = buf_get_full_text(bufnr),
},
}
- client.notify('textDocument/didOpen', params)
+ client.notify(ms.textDocument_didOpen, params)
util.buf_versions[bufnr] = params.textDocument.version
-- Next chance we get, we should re-do the diagnostics
@@ -735,7 +721,7 @@ end
-- FIXME: DOC: Shouldn't need to use a dummy function
--
--- LSP client object. You can get an active client object via
---- |vim.lsp.get_client_by_id()| or |vim.lsp.get_active_clients()|.
+--- |vim.lsp.get_client_by_id()| or |vim.lsp.get_clients()|.
---
--- - Methods:
---
@@ -801,29 +787,39 @@ end
--- to the server. Entries are key-value pairs with the key
--- being the request ID while the value is a table with `type`,
--- `bufnr`, and `method` key-value pairs. `type` is either "pending"
---- for an active request, or "cancel" for a cancel request.
+--- for an active request, or "cancel" for a cancel request. It will
+--- be "complete" ephemerally while executing |LspRequest| autocmds
+--- when replies are received from the server.
---
--- - {config} (table): copy of the table that was passed by the user
--- to |vim.lsp.start_client()|.
---
--- - {server_capabilities} (table): Response from the server sent on
--- `initialize` describing the server's capabilities.
+---
+--- - {progress} A ring buffer (|vim.ringbuf()|) containing progress messages
+--- sent by the server.
function lsp.client()
error()
end
+--- @class lsp.StartOpts
+--- @field reuse_client fun(client: lsp.Client, config: table): boolean
+--- @field bufnr integer
+
--- Create a new LSP client and start a language server or reuses an already
--- running client if one is found matching `name` and `root_dir`.
--- Attaches the current buffer to the client.
---
--- Example:
---- <pre>lua
+---
+--- ```lua
--- vim.lsp.start({
--- name = 'my-server-name',
--- cmd = {'name-of-language-server-executable'},
--- root_dir = vim.fs.dirname(vim.fs.find({'pyproject.toml', 'setup.py'}, { upward = true })[1]),
--- })
---- </pre>
+--- ```
---
--- See |vim.lsp.start_client()| for all available options. The most important are:
---
@@ -849,7 +845,7 @@ end
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
---@param config table Same configuration as documented in |vim.lsp.start_client()|
----@param opts nil|table Optional keyword arguments:
+---@param opts (nil|lsp.StartOpts) Optional keyword arguments:
--- - reuse_client (fun(client: client, config: table): boolean)
--- Predicate used to decide if a client should be re-used.
--- Used on all running clients.
@@ -858,14 +854,13 @@ end
--- - bufnr (number)
--- Buffer handle to attach to if starting or re-using a
--- client (0 for current).
----@return number|nil client_id
+---@return integer|nil client_id
function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client
or function(client, conf)
return client.config.root_dir == conf.root_dir and client.name == conf.name
end
- config.name = config.name
if not config.name and type(config.cmd) == 'table' then
config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil
end
@@ -873,7 +868,7 @@ function lsp.start(config, opts)
if bufnr == nil or bufnr == 0 then
bufnr = api.nvim_get_current_buf()
end
- for _, clients in ipairs({ uninitialized_clients, lsp.get_active_clients() }) do
+ for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do
for _, client in pairs(clients) do
if reuse_client(client, config) then
lsp.buf_attach_client(bufnr, client.id)
@@ -889,6 +884,109 @@ function lsp.start(config, opts)
return client_id
end
+--- Consumes the latest progress messages from all clients and formats them as a string.
+--- Empty if there are no clients or if no new messages
+---
+---@return string
+function lsp.status()
+ local percentage = nil
+ local messages = {}
+ for _, client in ipairs(vim.lsp.get_clients()) do
+ for progress in client.progress do
+ local value = progress.value
+ if type(value) == 'table' and value.kind then
+ local message = value.message and (value.title .. ': ' .. value.message) or value.title
+ messages[#messages + 1] = message
+ if value.percentage then
+ percentage = math.max(percentage or 0, value.percentage)
+ end
+ end
+ -- else: Doesn't look like work done progress and can be in any format
+ -- Just ignore it as there is no sensible way to display it
+ end
+ end
+ local message = table.concat(messages, ', ')
+ if percentage then
+ return string.format('%3d%%: %s', percentage, message)
+ end
+ return message
+end
+
+-- Determines whether the given option can be set by `set_defaults`.
+local function is_empty_or_default(bufnr, option)
+ if vim.bo[bufnr][option] == '' then
+ return true
+ end
+
+ local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
+ local scriptinfo = vim.tbl_filter(function(e)
+ return e.sid == info.last_set_sid
+ end, vim.fn.getscriptinfo())
+
+ if #scriptinfo ~= 1 then
+ return false
+ end
+
+ return vim.startswith(scriptinfo[1].name, vim.fn.expand('$VIMRUNTIME'))
+end
+
+---@private
+---@param client lsp.Client
+function lsp._set_defaults(client, bufnr)
+ if
+ client.supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc')
+ then
+ vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
+ end
+ if
+ client.supports_method(ms.textDocument_completion) and is_empty_or_default(bufnr, 'omnifunc')
+ then
+ vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
+ end
+ if
+ client.supports_method(ms.textDocument_rangeFormatting)
+ and is_empty_or_default(bufnr, 'formatprg')
+ and is_empty_or_default(bufnr, 'formatexpr')
+ then
+ vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
+ end
+ api.nvim_buf_call(bufnr, function()
+ if
+ client.supports_method(ms.textDocument_hover)
+ and is_empty_or_default(bufnr, 'keywordprg')
+ and vim.fn.maparg('K', 'n', false, false) == ''
+ then
+ vim.keymap.set('n', 'K', vim.lsp.buf.hover, { buffer = bufnr })
+ end
+ end)
+ if client.supports_method(ms.textDocument_diagnostic) then
+ lsp.diagnostic._enable(bufnr)
+ end
+end
+
+--- @class lsp.ClientConfig
+--- @field cmd (string[]|fun(dispatchers: table):table)
+--- @field cmd_cwd string
+--- @field cmd_env (table)
+--- @field detached boolean
+--- @field workspace_folders (table)
+--- @field capabilities lsp.ClientCapabilities
+--- @field handlers table<string,function>
+--- @field settings table
+--- @field commands table
+--- @field init_options table
+--- @field name string
+--- @field get_language_id fun(bufnr: integer, filetype: string): string
+--- @field offset_encoding string
+--- @field on_error fun(code: integer)
+--- @field before_init function
+--- @field on_init function
+--- @field on_exit fun(code: integer, signal: integer, client_id: integer)
+--- @field on_attach fun(client: lsp.Client, bufnr: integer)
+--- @field trace 'off'|'messages'|'verbose'|nil
+--- @field flags table
+--- @field root_dir string
+
-- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
-- documented twice: Here, and on the methods themselves (e.g.
-- `client.request()`). This is a workaround for the vimdoc generator script
@@ -899,9 +997,9 @@ end
---
--- Field `cmd` in {config} is required.
---
----@param config (table) Configuration for the server:
---- - cmd: (table|string|fun(dispatchers: table):table) command string or
---- list treated like |jobstart()|. The command must launch the language server
+---@param config (lsp.ClientConfig) Configuration for the server:
+--- - cmd: (string[]|fun(dispatchers: table):table) command a list of
+--- strings treated like |jobstart()|. The command must launch the language server
--- process. `cmd` can also be a function that creates an RPC client.
--- The function receives a dispatchers table and must return a table with the
--- functions `request`, `notify`, `is_closing` and `terminate`
@@ -912,11 +1010,11 @@ end
--- the `cmd` process. Not related to `root_dir`.
---
--- - cmd_env: (table) Environment flags to pass to the LSP on
---- spawn. Can be specified using keys like a map or as a list with `k=v`
---- pairs or both. Non-string values are coerced to string.
+--- spawn. Must be specified using a table.
+--- Non-string values are coerced to string.
--- Example:
--- <pre>
---- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; }
+--- { PORT = 8080; HOST = "0.0.0.0"; }
--- </pre>
---
--- - detached: (boolean, default true) Daemonize the server process so that it runs in a
@@ -929,11 +1027,10 @@ end
--- the LSP spec.
---
--- - capabilities: Map overriding the default capabilities defined by
---- |vim.lsp.protocol.make_client_capabilities()|, passed to the language
+--- \|vim.lsp.protocol.make_client_capabilities()|, passed to the language
--- server on initialization. Hint: use make_client_capabilities() and modify
--- its result.
---- - Note: To send an empty dictionary use
---- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
+--- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
--- array.
---
--- - handlers: Map of language server method names to |lsp-handler|
@@ -977,7 +1074,7 @@ end
--- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was
--- sent to it. You can only modify the `client.offset_encoding` here before
--- any notifications are sent. Most language servers expect to be sent client specified settings after
---- initialization. Neovim does not make this assumption. A
+--- initialization. Nvim does not make this assumption. A
--- `workspace/didChangeConfiguration` notification should be sent
--- to the server during on_init.
---
@@ -1006,13 +1103,11 @@ end
--- server will base its workspaceFolders, rootUri, and rootPath
--- on initialization.
---
----@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be
+---@return integer|nil client_id. |vim.lsp.get_client_by_id()| Note: client may not be
--- fully initialized. Use `on_init` to do any actions once
--- the client has been initialized.
function lsp.start_client(config)
- local cleaned_config = validate_client_config(config)
- local cmd, cmd_args, offset_encoding =
- cleaned_config.cmd, cleaned_config.cmd_args, cleaned_config.offset_encoding
+ local cmd, cmd_args, offset_encoding = validate_client_config(config)
config.flags = config.flags or {}
config.settings = config.settings or {}
@@ -1032,12 +1127,11 @@ function lsp.start_client(config)
local dispatch = {}
- ---@private
--- Returns the handler associated with an LSP method.
--- Returns the default handler if the user hasn't set a custom one.
---
---@param method (string) LSP method name
- ---@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers|
+ ---@return lsp-handler|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers|
local function resolve_handler(method)
return handlers[method] or default_handlers[method]
end
@@ -1049,7 +1143,9 @@ function lsp.start_client(config)
---@param method (string) LSP method name
---@param params (table) The parameters for that method.
function dispatch.notification(method, params)
- local _ = log.trace() and log.trace('notification', method, params)
+ if log.trace() then
+ log.trace('notification', method, params)
+ end
local handler = resolve_handler(method)
if handler then
-- Method name is provided here for convenience.
@@ -1063,27 +1159,41 @@ function lsp.start_client(config)
---@param method (string) LSP method name
---@param params (table) The parameters for that method
function dispatch.server_request(method, params)
- local _ = log.trace() and log.trace('server_request', method, params)
+ if log.trace() then
+ log.trace('server_request', method, params)
+ end
local handler = resolve_handler(method)
if handler then
- local _ = log.trace() and log.trace('server_request: found handler for', method)
+ if log.trace() then
+ log.trace('server_request: found handler for', method)
+ end
return handler(nil, params, { method = method, client_id = client_id })
end
- local _ = log.warn() and log.warn('server_request: no handler found for', method)
+ if log.warn() then
+ log.warn('server_request: no handler found for', method)
+ end
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
+ --- Logs the given error to the LSP log and to the error buffer.
+ --- @param code integer Error code
+ --- @param err any Error arguments
+ local function write_error(code, err)
+ if log.error() then
+ log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
+ end
+ err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
+ end
+
---@private
--- Invoked when the client operation throws an error.
---
- ---@param code (number) Error code
+ ---@param code (integer) Error code
---@param err (...) Other arguments may be passed depending on the error kind
- ---@see `vim.lsp.rpc.client_errors` for possible errors. Use
+ ---@see vim.lsp.rpc.client_errors for possible errors. Use
---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name.
function dispatch.on_error(code, err)
- local _ = log.error()
- and log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
- err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
+ write_error(code, err)
if config.on_error then
local status, usererr = pcall(config.on_error, code, err)
if not status then
@@ -1093,28 +1203,9 @@ function lsp.start_client(config)
end
end
- ---@private
- local function set_defaults(client, bufnr)
- local capabilities = client.server_capabilities
- if capabilities.definitionProvider and vim.bo[bufnr].tagfunc == '' then
- vim.bo[bufnr].tagfunc = 'v:lua.vim.lsp.tagfunc'
- end
- if capabilities.completionProvider and vim.bo[bufnr].omnifunc == '' then
- vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc'
- end
- if
- capabilities.documentRangeFormattingProvider
- and vim.bo[bufnr].formatprg == ''
- and vim.bo[bufnr].formatexpr == ''
- then
- vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()'
- end
- end
-
- ---@private
--- Reset defaults set by `set_defaults`.
--- Must only be called if the last client attached to a buffer exits.
- local function unset_defaults(bufnr)
+ local function reset_defaults(bufnr)
if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
vim.bo[bufnr].tagfunc = nil
end
@@ -1124,33 +1215,44 @@ function lsp.start_client(config)
if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
vim.bo[bufnr].formatexpr = nil
end
+ api.nvim_buf_call(bufnr, function()
+ local keymap = vim.fn.maparg('K', 'n', false, true)
+ if keymap and keymap.callback == vim.lsp.buf.hover then
+ vim.keymap.del('n', 'K', { buffer = bufnr })
+ end
+ end)
end
---@private
--- Invoked on client exit.
---
- ---@param code (number) exit code of the process
- ---@param signal (number) the signal used to terminate (if any)
+ ---@param code (integer) exit code of the process
+ ---@param signal (integer) the signal used to terminate (if any)
function dispatch.on_exit(code, signal)
if config.on_exit then
pcall(config.on_exit, code, signal, client_id)
end
+ local client = active_clients[client_id] and active_clients[client_id]
+ or uninitialized_clients[client_id]
+
for bufnr, client_ids in pairs(all_buffer_active_clients) do
if client_ids[client_id] then
vim.schedule(function()
- nvim_exec_autocmds('LspDetach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id },
- })
+ if client and client.attached_buffers[bufnr] then
+ nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
+ end
local namespace = vim.lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
client_ids[client_id] = nil
if vim.tbl_isempty(client_ids) then
- unset_defaults(bufnr)
+ reset_defaults(bufnr)
end
end)
end
@@ -1159,8 +1261,6 @@ function lsp.start_client(config)
-- Schedule the deletion of the client object so that it exists in the execution of LspDetach
-- autocommands
vim.schedule(function()
- local client = active_clients[client_id] and active_clients[client_id]
- or uninitialized_clients[client_id]
active_clients[client_id] = nil
uninitialized_clients[client_id] = nil
@@ -1170,8 +1270,13 @@ function lsp.start_client(config)
changetracking.reset(client)
end
if code ~= 0 or (signal ~= 0 and signal ~= 15) then
- local msg =
- string.format('Client %s quit with exit code %s and signal %s', client_id, code, signal)
+ local msg = string.format(
+ 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
+ name,
+ code,
+ signal,
+ lsp.get_log_path()
+ )
vim.notify(msg, vim.log.levels.WARN)
end
end)
@@ -1194,6 +1299,7 @@ function lsp.start_client(config)
return
end
+ ---@class lsp.Client
local client = {
id = client_id,
name = name,
@@ -1205,26 +1311,44 @@ function lsp.start_client(config)
handlers = handlers,
commands = config.commands or {},
+ --- @type table<integer,{ type: string, bufnr: integer, method: string}>
requests = {},
- -- for $/progress report
+
+ --- Contains $/progress report messages.
+ --- They have the format {token: integer|string, value: any}
+ --- For "work done progress", value will be one of:
+ --- - lsp.WorkDoneProgressBegin,
+ --- - lsp.WorkDoneProgressReport (extended with title from Begin)
+ --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
+ progress = vim.ringbuf(50),
+
+ --- @type lsp.ServerCapabilities
+ server_capabilities = {},
+
+ ---@deprecated use client.progress instead
messages = { name = name, messages = {}, progress = {}, status = {} },
+ dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
}
+ ---@type table<string|integer, string> title of unfinished progress sequences by token
+ client.progress.pending = {}
+
+ --- @type lsp.ClientCapabilities
+ client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
+
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
uninitialized_clients[client_id] = client
- ---@private
local function initialize()
local valid_traces = {
off = 'off',
messages = 'messages',
verbose = 'verbose',
}
- local version = vim.version()
- local workspace_folders
- local root_uri
- local root_path
+ local workspace_folders --- @type table[]?
+ local root_uri --- @type string?
+ local root_path --- @type string?
if config.workspace_folders or config.root_dir then
if config.root_dir and not config.workspace_folders then
workspace_folders = {
@@ -1249,12 +1373,12 @@ function lsp.start_client(config)
-- the process has not been started by another process. If the parent
-- process is not alive then the server should exit (see exit notification)
-- its process.
- processId = uv.getpid(),
+ processId = uv.os_getpid(),
-- Information about the client
-- since 3.15.0
clientInfo = {
name = 'Neovim',
- version = string.format('%s.%s.%s', version.major, version.minor, version.patch),
+ version = tostring(vim.version()),
},
-- The rootPath of the workspace. Is null if no folder is open.
--
@@ -1271,15 +1395,37 @@ function lsp.start_client(config)
-- User provided initialization options.
initializationOptions = config.init_options,
-- The capabilities provided by the client (editor or tool)
- capabilities = config.capabilities or protocol.make_client_capabilities(),
+ capabilities = config.capabilities,
-- The initial trace setting. If omitted trace is disabled ("off").
-- trace = "off" | "messages" | "verbose";
trace = valid_traces[config.trace] or 'off',
}
if config.before_init then
- -- TODO(ashkan) handle errors here.
- pcall(config.before_init, initialize_params, config)
+ local status, err = pcall(config.before_init, initialize_params, config)
+ if not status then
+ write_error(lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR, err)
+ end
end
+
+ --- @param method string
+ --- @param opts? {bufnr?: number}
+ client.supports_method = function(method, opts)
+ opts = opts or {}
+ local required_capability = lsp._request_name_to_capability[method]
+ -- if we don't know about the method, assume that the client supports it.
+ if not required_capability then
+ return true
+ end
+ if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
+ return true
+ else
+ if client.dynamic_capabilities:supports_registration(method) then
+ return client.dynamic_capabilities:supports(method, opts)
+ end
+ return false
+ end
+ end
+
local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
rpc.request('initialize', initialize_params, function(init_err, result)
assert(not init_err, tostring(init_err))
@@ -1295,46 +1441,18 @@ function lsp.start_client(config)
assert(result.capabilities, "initialize result doesn't contain capabilities")
client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
- -- Deprecation wrapper: this will be removed in 0.8
- local mt = {}
- mt.__index = function(table, key)
- if key == 'resolved_capabilities' then
- vim.notify_once(
- '[LSP] Accessing client.resolved_capabilities is deprecated, '
- .. 'update your plugins or configuration to access client.server_capabilities instead.'
- .. 'The new key/value pairs in server_capabilities directly match those '
- .. 'defined in the language server protocol',
- vim.log.levels.WARN
- )
- rawset(table, key, protocol._resolve_capabilities_compat(client.server_capabilities))
- return rawget(table, key)
- else
- return rawget(table, key)
- end
- end
- setmetatable(client, mt)
-
- client.supports_method = function(method)
- local required_capability = lsp._request_name_to_capability[method]
- -- if we don't know about the method, assume that the client supports it.
- if not required_capability then
- return true
- end
- if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
- return true
- else
- return false
- end
+ if client.server_capabilities.positionEncoding then
+ client.offset_encoding = client.server_capabilities.positionEncoding
end
if next(config.settings) then
- client.notify('workspace/didChangeConfiguration', { settings = config.settings })
+ client.notify(ms.workspace_didChangeConfiguration, { settings = config.settings })
end
if config.on_init then
local status, err = pcall(config.on_init, client, result)
if not status then
- pcall(handlers.on_error, lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
+ write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
end
end
local _ = log.info()
@@ -1357,23 +1475,23 @@ function lsp.start_client(config)
end)
end
- ---@private
+ ---@nodoc
--- Sends a request to the server.
---
--- This is a thin wrapper around {client.rpc.request} with some additional
--- checks for capabilities and handler availability.
---
- ---@param method (string) LSP method name.
- ---@param params (table) LSP request params.
- ---@param handler (function|nil) Response |lsp-handler| for this method.
- ---@param bufnr (number) Buffer handle (0 for current).
- ---@returns ({status}, [request_id]): {status} is a bool indicating
+ ---@param method string LSP method name.
+ ---@param params table|nil LSP request params.
+ ---@param handler lsp-handler|nil Response |lsp-handler| for this method.
+ ---@param bufnr integer Buffer handle (0 for current).
+ ---@return boolean status, integer|nil request_id {status} is a bool indicating
---whether the request was successful. If it is `false`, then it will
---always be `false` (the client has shutdown). If it was
---successful, then it will return {request_id} as the
---second result. You can use this with `client.cancel_request(request_id)`
---to cancel the-request.
- ---@see |vim.lsp.buf_request()|
+ ---@see |vim.lsp.buf_request_all()|
function client.request(method, params, handler, bufnr)
if not handler then
handler = assert(
@@ -1383,23 +1501,39 @@ function lsp.start_client(config)
end
-- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
changetracking.flush(client, bufnr)
+ local version = util.buf_versions[bufnr]
bufnr = resolve_bufnr(bufnr)
- local _ = log.debug()
- and log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
+ if log.debug() then
+ log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
+ end
local success, request_id = rpc.request(method, params, function(err, result)
- handler(
- err,
- result,
- { method = method, client_id = client_id, bufnr = bufnr, params = params }
- )
+ local context = {
+ method = method,
+ client_id = client_id,
+ bufnr = bufnr,
+ params = params,
+ version = version,
+ }
+ handler(err, result, context)
end, function(request_id)
+ local request = client.requests[request_id]
+ request.type = 'complete'
+ nvim_exec_autocmds('LspRequest', {
+ buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
+ modeline = false,
+ data = { client_id = client_id, request_id = request_id, request = request },
+ })
client.requests[request_id] = nil
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
end)
if success and request_id then
- client.requests[request_id] = { type = 'pending', bufnr = bufnr, method = method }
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+ local request = { type = 'pending', bufnr = bufnr, method = method }
+ client.requests[request_id] = request
+ nvim_exec_autocmds('LspRequest', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id, request_id = request_id, request = request },
+ })
end
return success, request_id
@@ -1412,13 +1546,14 @@ function lsp.start_client(config)
---
---@param method (string) LSP method name.
---@param params (table) LSP request params.
- ---@param timeout_ms (number|nil) Maximum time in milliseconds to wait for
+ ---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
--- a result. Defaults to 1000
- ---@param bufnr (number) Buffer handle (0 for current).
- ---@returns { err=err, result=result }, a dictionary, where `err` and `result` come from the |lsp-handler|.
- ---On timeout, cancel or error, returns `(nil, err)` where `err` is a
- ---string describing the failure reason. If the request was unsuccessful
- ---returns `nil`.
+ ---@param bufnr (integer) Buffer handle (0 for current).
+ ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
+ --- `err` and `result` come from the |lsp-handler|.
+ --- On timeout, cancel or error, returns `(nil, err)` where `err` is a
+ --- string describing the failure reason. If the request was unsuccessful
+ --- returns `nil`.
---@see |vim.lsp.buf_request_sync()|
function client.request_sync(method, params, timeout_ms, bufnr)
local request_result = nil
@@ -1444,41 +1579,62 @@ function lsp.start_client(config)
return request_result
end
- ---@private
+ ---@nodoc
--- Sends a notification to an LSP server.
---
---@param method string LSP method name.
---@param params table|nil LSP request params.
- ---@returns {status} (bool) true if the notification was successful.
+ ---@return boolean status true if the notification was successful.
---If it is false, then it will always be false
---(the client has shutdown).
function client.notify(method, params)
- if method ~= 'textDocument/didChange' then
+ if method ~= ms.textDocument_didChange then
changetracking.flush(client)
end
- return rpc.notify(method, params)
+
+ local client_active = rpc.notify(method, params)
+
+ if client_active then
+ vim.schedule(function()
+ nvim_exec_autocmds('LspNotify', {
+ modeline = false,
+ data = {
+ client_id = client.id,
+ method = method,
+ params = params,
+ },
+ })
+ end)
+ end
+
+ return client_active
end
- ---@private
+ ---@nodoc
--- Cancels a request with a given request id.
---
- ---@param id (number) id of request to cancel
- ---@returns true if any client returns true; false otherwise
+ ---@param id (integer) id of request to cancel
+ ---@return boolean status true if notification was successful. false otherwise
---@see |vim.lsp.client.notify()|
function client.cancel_request(id)
validate({ id = { id, 'n' } })
local request = client.requests[id]
if request and request.type == 'pending' then
request.type = 'cancel'
- nvim_exec_autocmds('User', { pattern = 'LspRequest', modeline = false })
+ nvim_exec_autocmds('LspRequest', {
+ buffer = request.bufnr,
+ modeline = false,
+ data = { client_id = client_id, request_id = id, request = request },
+ })
end
- return rpc.notify('$/cancelRequest', { id = id })
+ return rpc.notify(ms.dollar_cancelRequest, { id = id })
end
-- Track this so that we can escalate automatically if we've already tried a
-- graceful shutdown
local graceful_shutdown_failed = false
- ---@private
+
+ ---@nodoc
--- Stops a client, optionally with force.
---
---By default, it will just ask the - server to shutdown without force. If
@@ -1495,9 +1651,9 @@ function lsp.start_client(config)
return
end
-- Sending a signal after a process has exited is acceptable.
- rpc.request('shutdown', nil, function(err, _)
+ rpc.request(ms.shutdown, nil, function(err, _)
if err == nil then
- rpc.notify('exit')
+ rpc.notify(ms.exit)
else
-- If there was an error in the shutdown request, then term to be safe.
rpc.terminate()
@@ -1509,19 +1665,59 @@ function lsp.start_client(config)
---@private
--- Checks whether a client is stopped.
---
- ---@returns (bool) true if client is stopped or in the process of being
+ ---@return boolean # true if client is stopped or in the process of being
---stopped; false otherwise
function client.is_stopped()
return rpc.is_closing()
end
---@private
+ --- Execute a lsp command, either via client command function (if available)
+ --- or via workspace/executeCommand (if supported by the server)
+ ---
+ ---@param command lsp.Command
+ ---@param context? {bufnr: integer}
+ ---@param handler? lsp-handler only called if a server command
+ function client._exec_cmd(command, context, handler)
+ context = vim.deepcopy(context or {})
+ context.bufnr = context.bufnr or api.nvim_get_current_buf()
+ context.client_id = client.id
+ local cmdname = command.command
+ local fn = client.commands[cmdname] or lsp.commands[cmdname]
+ if fn then
+ fn(command, context)
+ return
+ end
+
+ local command_provider = client.server_capabilities.executeCommandProvider
+ local commands = type(command_provider) == 'table' and command_provider.commands or {}
+ if not vim.list_contains(commands, cmdname) then
+ vim.notify_once(
+ string.format(
+ 'Language server `%s` does not support command `%s`. This command may require a client extension.',
+ client.name,
+ cmdname
+ ),
+ vim.log.levels.WARN
+ )
+ return
+ end
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ }
+ client.request(ms.workspace_executeCommand, params, handler, context.bufnr)
+ end
+
+ ---@private
--- Runs the on_attach function from the client's config if it was defined.
- ---@param bufnr (number) Buffer number
+ ---@param bufnr integer Buffer number
function client._on_attach(bufnr)
text_document_did_open_handler(bufnr, client)
- set_defaults(client, bufnr)
+ lsp._set_defaults(client, bufnr)
nvim_exec_autocmds('LspAttach', {
buffer = bufnr,
@@ -1530,8 +1726,10 @@ function lsp.start_client(config)
})
if config.on_attach then
- -- TODO(ashkan) handle errors.
- pcall(config.on_attach, client, bufnr)
+ local status, err = pcall(config.on_attach, client, bufnr)
+ if not status then
+ write_error(lsp.client_errors.ON_ATTACH_ERROR, err)
+ end
end
-- schedule the initialization of semantic tokens to give the above
@@ -1573,17 +1771,21 @@ do
end
end
----@private
---Buffer lifecycle handler for textDocument/didSave
local function text_document_did_save_handler(bufnr)
bufnr = resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr)
local text = once(buf_get_full_text)
- for_each_buffer_client(bufnr, function(client)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
local name = api.nvim_buf_get_name(bufnr)
local old_name = changetracking._get_and_set_name(client, bufnr, name)
if old_name and name ~= old_name then
- client.notify('textDocument/didOpen', {
+ client.notify(ms.textDocument_didClose, {
+ textDocument = {
+ uri = vim.uri_from_fname(old_name),
+ },
+ })
+ client.notify(ms.textDocument_didOpen, {
textDocument = {
version = 0,
uri = uri,
@@ -1599,14 +1801,14 @@ local function text_document_did_save_handler(bufnr)
if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
- client.notify('textDocument/didSave', {
+ client.notify(ms.textDocument_didSave, {
textDocument = {
uri = uri,
},
text = included_text,
})
end
- end)
+ end
end
--- Implements the `textDocument/did…` notifications required to track a buffer
@@ -1614,8 +1816,9 @@ end
---
--- Without calling this, the server won't be notified of changes to a buffer.
---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) Client id
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param client_id (integer) Client id
+---@return boolean success `true` if client was attached successfully; `false` otherwise
function lsp.buf_attach_client(bufnr, client_id)
validate({
bufnr = { bufnr, 'n', true },
@@ -1641,7 +1844,7 @@ function lsp.buf_attach_client(bufnr, client_id)
buffer = bufnr,
desc = 'vim.lsp: textDocument/willSave',
callback = function(ctx)
- for_each_buffer_client(ctx.buf, function(client)
+ for _, client in ipairs(lsp.get_clients({ bufnr = ctx.buf })) do
local params = {
textDocument = {
uri = uri,
@@ -1649,18 +1852,18 @@ function lsp.buf_attach_client(bufnr, client_id)
reason = protocol.TextDocumentSaveReason.Manual,
}
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSave') then
- client.notify('textDocument/willSave', params)
+ client.notify(ms.textDocument_willSave, params)
end
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'willSaveWaitUntil') then
local result, err =
- client.request_sync('textDocument/willSaveWaitUntil', params, 1000, ctx.buf)
+ client.request_sync(ms.textDocument_willSaveWaitUntil, params, 1000, ctx.buf)
if result and result.result then
util.apply_text_edits(result.result, ctx.buf, client.offset_encoding)
elseif err then
log.error(vim.inspect(err))
end
end
- end)
+ end
end,
})
api.nvim_create_autocmd('BufWritePost', {
@@ -1676,23 +1879,23 @@ function lsp.buf_attach_client(bufnr, client_id)
on_lines = text_document_did_change_handler,
on_reload = function()
local params = { textDocument = { uri = uri } }
- for_each_buffer_client(bufnr, function(client, _)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
text_document_did_open_handler(bufnr, client)
- end)
+ end
end,
on_detach = function()
local params = { textDocument = { uri = uri } }
- for_each_buffer_client(bufnr, function(client, _)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
changetracking.reset_buf(client, bufnr)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
client.attached_buffers[bufnr] = nil
- end)
+ end
util.buf_versions[bufnr] = nil
all_buffer_active_clients[bufnr] = nil
end,
@@ -1704,7 +1907,7 @@ function lsp.buf_attach_client(bufnr, client_id)
end
if buffer_client_ids[client_id] then
- return
+ return true
end
-- This is our first time attaching this client to this buffer.
buffer_client_ids[client_id] = true
@@ -1722,8 +1925,8 @@ end
--- Note: While the server is notified that the text document (buffer)
--- was closed, it is still able to send notifications should it ignore this notification.
---
----@param bufnr number Buffer handle, or 0 for current
----@param client_id number Client id
+---@param bufnr integer Buffer handle, or 0 for current
+---@param client_id integer Client id
function lsp.buf_detach_client(bufnr, client_id)
validate({
bufnr = { bufnr, 'n', true },
@@ -1736,8 +1939,8 @@ function lsp.buf_detach_client(bufnr, client_id)
vim.notify(
string.format(
'Buffer (id: %d) is not attached to client (id: %d). Cannot detach.',
- client_id,
- bufnr
+ bufnr,
+ client_id
)
)
return
@@ -1754,7 +1957,7 @@ function lsp.buf_detach_client(bufnr, client_id)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
local uri = vim.uri_from_bufnr(bufnr)
local params = { textDocument = { uri = uri } }
- client.notify('textDocument/didClose', params)
+ client.notify(ms.textDocument_didClose, params)
end
client.attached_buffers[bufnr] = nil
@@ -1765,16 +1968,14 @@ function lsp.buf_detach_client(bufnr, client_id)
all_buffer_active_clients[bufnr] = nil
end
- local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ local namespace = lsp.diagnostic.get_namespace(client_id)
vim.diagnostic.reset(namespace, bufnr)
-
- vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id))
end
--- Checks if a buffer is attached for a particular client.
---
----@param bufnr (number) Buffer handle, or 0 for current
----@param client_id (number) the client id
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param client_id (integer) the client id
function lsp.buf_is_attached(bufnr, client_id)
return (all_buffer_active_clients[resolve_bufnr(bufnr)] or {})[client_id] == true
end
@@ -1782,17 +1983,17 @@ end
--- Gets a client by id, or nil if the id is invalid.
--- The returned client may not yet be fully initialized.
---
----@param client_id number client id
+---@param client_id integer client id
---
----@returns |vim.lsp.client| object, or nil
+---@return (nil|lsp.Client) client rpc object
function lsp.get_client_by_id(client_id)
return active_clients[client_id] or uninitialized_clients[client_id]
end
--- Returns list of buffers attached to client_id.
---
----@param client_id number client id
----@returns list of buffer ids
+---@param client_id integer client id
+---@return integer[] buffers list of buffer ids
function lsp.get_buffers_by_client_id(client_id)
local client = lsp.get_client_by_id(client_id)
return client and vim.tbl_keys(client.attached_buffers) or {}
@@ -1802,14 +2003,15 @@ end
---
--- You can also use the `stop()` function on a |vim.lsp.client| object.
--- To stop all clients:
---- <pre>lua
---- vim.lsp.stop_client(vim.lsp.get_active_clients())
---- </pre>
+---
+--- ```lua
+--- vim.lsp.stop_client(vim.lsp.get_clients())
+--- ```
---
--- By default asks the server to shutdown, unless stop was requested
--- already for this client, then force-shutdown is attempted.
---
----@param client_id number|table id or |vim.lsp.client| object, or list thereof
+---@param client_id integer|table id or |vim.lsp.client| object, or list thereof
---@param force boolean|nil shutdown forcefully
function lsp.stop_client(client_id, force)
local ids = type(client_id) == 'table' and client_id or { client_id }
@@ -1824,26 +2026,28 @@ function lsp.stop_client(client_id, force)
end
end
----@class vim.lsp.get_active_clients.filter
----@field id number|nil Match clients by id
----@field bufnr number|nil match clients attached to the given buffer
+---@class vim.lsp.get_clients.filter
+---@field id integer|nil Match clients by id
+---@field bufnr integer|nil match clients attached to the given buffer
---@field name string|nil match clients by name
+---@field method string|nil match client by supported method name
--- Get active clients.
---
----@param filter vim.lsp.get_active_clients.filter|nil (table|nil) A table with
+---@param filter vim.lsp.get_clients.filter|nil (table|nil) A table with
--- key-value pairs used to filter the returned clients.
--- The available keys are:
--- - id (number): Only return clients with the given id
--- - bufnr (number): Only return clients attached to this buffer
--- - name (string): Only return clients with the given name
----@returns (table) List of |vim.lsp.client| objects
-function lsp.get_active_clients(filter)
+--- - method (string): Only return clients supporting the given method
+---@return lsp.Client[]: List of |vim.lsp.client| objects
+function lsp.get_clients(filter)
validate({ filter = { filter, 't', true } })
filter = filter or {}
- local clients = {}
+ local clients = {} --- @type lsp.Client[]
local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {})
or active_clients
@@ -1853,6 +2057,7 @@ function lsp.get_active_clients(filter)
client
and (filter.id == nil or client.id == filter.id)
and (filter.name == nil or client.name == filter.name)
+ and (filter.method == nil or client.supports_method(filter.method, { bufnr = filter.bufnr }))
then
clients[#clients + 1] = client
end
@@ -1860,6 +2065,13 @@ function lsp.get_active_clients(filter)
return clients
end
+---@private
+---@deprecated
+function lsp.get_active_clients(filter)
+ -- TODO: add vim.deprecate call after 0.10 is out for removal in 0.12
+ return lsp.get_clients(filter)
+end
+
api.nvim_create_autocmd('VimLeavePre', {
desc = 'vim.lsp: exit handler',
callback = function()
@@ -1890,7 +2102,6 @@ api.nvim_create_autocmd('VimLeavePre', {
local poll_time = 50
- ---@private
local function check_clients_closed()
for client_id, timeout in pairs(timeouts) do
timeouts[client_id] = timeout - poll_time
@@ -1920,16 +2131,17 @@ api.nvim_create_autocmd('VimLeavePre', {
--- Sends an async request for all active clients attached to the
--- buffer.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@param params table|nil Parameters to send to the server
----@param handler function|nil See |lsp-handler|
+---@param handler? lsp-handler See |lsp-handler|
--- If nil, follows resolution strategy defined in |lsp-handler-configuration|
---
----@returns 2-tuple:
---- - Map of client-id:request-id pairs for all successful requests.
---- - Function which can be used to cancel all the requests. You could instead
---- iterate all clients and call their `cancel_request()` methods.
+---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
+---for all successful requests.
+---@return function _cancel_all_requests Function which can be used to
+---cancel all the requests. You could instead
+---iterate all clients and call their `cancel_request()` methods.
function lsp.buf_request(bufnr, method, params, handler)
validate({
bufnr = { bufnr, 'n', true },
@@ -1937,34 +2149,30 @@ function lsp.buf_request(bufnr, method, params, handler)
handler = { handler, 'f', true },
})
- local supported_clients = {}
+ bufnr = resolve_bufnr(bufnr)
local method_supported = false
- for_each_buffer_client(bufnr, function(client, client_id)
- if client.supports_method(method) then
+ local clients = lsp.get_clients({ bufnr = bufnr })
+ local client_request_ids = {}
+ for _, client in ipairs(clients) do
+ if client.supports_method(method, { bufnr = bufnr }) then
method_supported = true
- table.insert(supported_clients, client_id)
+
+ local request_success, request_id = client.request(method, params, handler, bufnr)
+ -- This could only fail if the client shut down in the time since we looked
+ -- it up and we did the request, which should be rare.
+ if request_success then
+ client_request_ids[client.id] = request_id
+ end
end
- end)
+ end
-- if has client but no clients support the given method, notify the user
- if
- not tbl_isempty(all_buffer_active_clients[resolve_bufnr(bufnr)] or {}) and not method_supported
- then
+ if next(clients) and not method_supported then
vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
nvim_command('redraw')
return {}, function() end
end
- local client_request_ids = {}
- for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
- local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
- -- This could only fail if the client shut down in the time since we looked
- -- it up and we did the request, which should be rare.
- if request_success then
- client_request_ids[client_id] = request_id
- end
- end, supported_clients)
-
local function _cancel_all_requests()
for client_id, request_id in pairs(client_request_ids) do
local client = active_clients[client_id]
@@ -1975,39 +2183,36 @@ function lsp.buf_request(bufnr, method, params, handler)
return client_request_ids, _cancel_all_requests
end
----Sends an async request for all active clients attached to the buffer.
----Executes the callback on the combined result.
----Parameters are the same as |vim.lsp.buf_request()| but the return result and callback are
----different.
+--- Sends an async request for all active clients attached to the buffer and executes the `handler`
+--- callback with the combined result.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@param params (table|nil) Parameters to send to the server
----@param callback (function) The callback to call when all requests are finished.
--- Unlike `buf_request`, this will collect all the responses from each server instead of handling them.
--- A map of client_id:request_result will be provided to the callback
---
----@returns (function) A function that will cancel all requests which is the same as the one returned from `buf_request`.
-function lsp.buf_request_all(bufnr, method, params, callback)
- local request_results = {}
+---@param handler fun(results: table<integer, {error: lsp.ResponseError, result: any}>) (function)
+--- Handler called after all requests are completed. Server results are passed as
+--- a `client_id:result` map.
+---@return function cancel Function that cancels all requests.
+function lsp.buf_request_all(bufnr, method, params, handler)
+ local results = {}
local result_count = 0
local expected_result_count = 0
local set_expected_result_count = once(function()
- for_each_buffer_client(bufnr, function(client)
- if client.supports_method(method) then
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
+ if client.supports_method(method, { bufnr = bufnr }) then
expected_result_count = expected_result_count + 1
end
- end)
+ end
end)
local function _sync_handler(err, result, ctx)
- request_results[ctx.client_id] = { error = err, result = result }
+ results[ctx.client_id] = { error = err, result = result }
result_count = result_count + 1
set_expected_result_count()
if result_count >= expected_result_count then
- callback(request_results)
+ handler(results)
end
end
@@ -2019,18 +2224,17 @@ end
--- Sends a request to all server and waits for the response of all of them.
---
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
---- Parameters are the same as |vim.lsp.buf_request()| but the return result is
---- different. Wait maximum of {timeout_ms} (default 1000) ms.
+--- Parameters are the same as |vim.lsp.buf_request_all()| but the result is
+--- different. Waits a maximum of {timeout_ms} (default 1000) ms.
---
----@param bufnr (number) Buffer handle, or 0 for current.
+---@param bufnr (integer) Buffer handle, or 0 for current.
---@param method (string) LSP method name
---@param params (table|nil) Parameters to send to the server
----@param timeout_ms (number|nil) Maximum time in milliseconds to wait for a
+---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for a
--- result. Defaults to 1000
---
----@returns Map of client_id:request_result. On timeout, cancel or error,
---- returns `(nil, err)` where `err` is a string describing the failure
---- reason.
+---@return table<integer, {err: lsp.ResponseError, result: any}>|nil (table) result Map of client_id:request_result.
+---@return string|nil err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results
@@ -2051,41 +2255,23 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
end
--- Send a notification to a server
----@param bufnr (number|nil) The number of the buffer
+---@param bufnr (integer|nil) The number of the buffer
---@param method (string) Name of the request method
---@param params (any) Arguments to send to the server
---
----@returns true if any client returns true; false otherwise
+---@return boolean success true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
validate({
bufnr = { bufnr, 'n', true },
method = { method, 's' },
})
local resp = false
- for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
+ for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
if client.rpc.notify(method, params) then
resp = true
end
- end)
- return resp
-end
-
----@private
-local function adjust_start_col(lnum, line, items, encoding)
- local min_start_char = nil
- for _, item in pairs(items) do
- if item.filterText == nil and item.textEdit and item.textEdit.range.start.line == lnum - 1 then
- if min_start_char and min_start_char ~= item.textEdit.range.start.character then
- return nil
- end
- min_start_char = item.textEdit.range.start.character
- end
- end
- if min_start_char then
- return util._str_byteindex_enc(line, min_start_char, encoding)
- else
- return nil
end
+ return resp
end
--- Implements 'omnifunc' compatible LSP completion.
@@ -2094,80 +2280,24 @@ end
---@see |complete-items|
---@see |CompleteDone|
---
----@param findstart number 0 or 1, decides behavior
----@param base number findstart=0, text to match against
+---@param findstart integer 0 or 1, decides behavior
+---@param base integer findstart=0, text to match against
---
----@returns (number) Decided by {findstart}:
+---@return integer|table Decided by {findstart}:
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
- local _ = log.debug() and log.debug('omnifunc.findstart', { findstart = findstart, base = base })
-
- local bufnr = resolve_bufnr()
- local has_buffer_clients = not tbl_isempty(all_buffer_active_clients[bufnr] or {})
- if not has_buffer_clients then
- if findstart == 1 then
- return -1
- else
- return {}
- end
+ if log.debug() then
+ log.debug('omnifunc.findstart', { findstart = findstart, base = base })
end
-
- -- Then, perform standard completion request
- local _ = log.info() and log.info('base ', base)
-
- local pos = api.nvim_win_get_cursor(0)
- local line = api.nvim_get_current_line()
- local line_to_cursor = line:sub(1, pos[2])
- local _ = log.trace() and log.trace('omnifunc.line', pos, line)
-
- -- Get the start position of the current keyword
- local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
-
- local params = util.make_position_params()
-
- local items = {}
- lsp.buf_request(bufnr, 'textDocument/completion', params, function(err, result, ctx)
- if err or not result or vim.fn.mode() ~= 'i' then
- return
- end
-
- -- Completion response items may be relative to a position different than `textMatch`.
- -- Concrete example, with sumneko/lua-language-server:
- --
- -- require('plenary.asy|
- -- ▲ ▲ ▲
- -- │ │ └── cursor_pos: 20
- -- │ └────── textMatch: 17
- -- └────────────── textEdit.range.start.character: 9
- -- .newText = 'plenary.async'
- -- ^^^
- -- prefix (We'd remove everything not starting with `asy`,
- -- so we'd eliminate the `plenary.async` result
- --
- -- `adjust_start_col` is used to prefer the language server boundary.
- --
- local client = lsp.get_client_by_id(ctx.client_id)
- local encoding = client and client.offset_encoding or 'utf-16'
- local candidates = util.extract_completion_items(result)
- local startbyte = adjust_start_col(pos[1], line, candidates, encoding) or textMatch
- local prefix = line:sub(startbyte + 1, pos[2])
- local matches = util.text_document_completion_list_to_complete_items(result, prefix)
- -- TODO(ashkan): is this the best way to do this?
- vim.list_extend(items, matches)
- vim.fn.complete(startbyte + 1, items)
- end)
-
- -- Return -2 to signal that we should continue completion so that we can
- -- async complete.
- return -2
+ return require('vim.lsp._completion').omnifunc(findstart, base)
end
--- Provides an interface between the built-in client and a `formatexpr` function.
---
--- Currently only supports a single client. This can be set via
--- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach`
---- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``.
+--- via ``vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'``.
---
---@param opts table options for customizing the formatting expression which takes the
--- following optional keys:
@@ -2176,7 +2306,7 @@ function lsp.formatexpr(opts)
opts = opts or {}
local timeout_ms = opts.timeout_ms or 500
- if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
+ if vim.list_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
-- fall back to internal formatting
return 1
@@ -2189,10 +2319,10 @@ function lsp.formatexpr(opts)
return 0
end
local bufnr = api.nvim_get_current_buf()
- for _, client in pairs(lsp.get_active_clients({ bufnr = bufnr })) do
- if client.supports_method('textDocument/rangeFormatting') then
+ for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
+ if client.supports_method(ms.textDocument_rangeFormatting) then
local params = util.make_formatting_params()
- local end_line = vim.fn.getline(end_lnum)
+ local end_line = vim.fn.getline(end_lnum) --[[@as string]]
local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding)
params.range = {
start = {
@@ -2205,9 +2335,9 @@ function lsp.formatexpr(opts)
},
}
local response =
- client.request_sync('textDocument/rangeFormatting', params, timeout_ms, bufnr)
- if response.result then
- vim.lsp.util.apply_text_edits(response.result, 0, client.offset_encoding)
+ client.request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr)
+ if response and response.result then
+ lsp.util.apply_text_edits(response.result, 0, client.offset_encoding)
return 0
end
end
@@ -2227,39 +2357,41 @@ end
---@param pattern string Pattern used to find a workspace symbol
---@param flags string See |tag-function|
---
----@returns A list of matching tags
-function lsp.tagfunc(...)
- return require('vim.lsp.tagfunc')(...)
+---@return table[] tags A list of matching tags
+function lsp.tagfunc(pattern, flags)
+ return require('vim.lsp.tagfunc')(pattern, flags)
end
---Checks whether a client is stopped.
---
----@param client_id (number)
----@returns true if client is stopped, false otherwise.
+---@param client_id (integer)
+---@return boolean stopped true if client is stopped, false otherwise.
function lsp.client_is_stopped(client_id)
- return active_clients[client_id] == nil
+ assert(client_id, 'missing client_id param')
+ return active_clients[client_id] == nil and not uninitialized_clients[client_id]
end
--- Gets a map of client_id:client pairs for the given buffer, where each value
--- is a |vim.lsp.client| object.
---
----@param bufnr (number|nil): Buffer handle, or 0 for current
----@returns (table) Table of (client_id, client) pairs
----@deprecated Use |vim.lsp.get_active_clients()| instead.
+---@param bufnr (integer|nil): Buffer handle, or 0 for current
+---@return table result is table of (client_id, client) pairs
+---@deprecated Use |vim.lsp.get_clients()| instead.
function lsp.buf_get_clients(bufnr)
local result = {}
- for _, client in ipairs(lsp.get_active_clients({ bufnr = resolve_bufnr(bufnr) })) do
+ for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do
result[client.id] = client
end
return result
end
--- Log level dictionary with reverse lookup as well.
---
--- Can be used to lookup the number from the name or the
--- name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
--- Level numbers begin with "TRACE" at 0
+--- Log level dictionary with reverse lookup as well.
+---
+--- Can be used to lookup the number from the name or the
+--- name from the number.
+--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
+--- Level numbers begin with "TRACE" at 0
+--- @nodoc
lsp.log_levels = log.levels
--- Sets the global log level for LSP logging.
@@ -2272,7 +2404,7 @@ lsp.log_levels = log.levels
---
---@see |vim.lsp.log_levels|
---
----@param level (number|string) the case insensitive level name or number
+---@param level (integer|string) the case insensitive level name or number
function lsp.set_log_level(level)
if type(level) == 'string' or type(level) == 'number' then
log.set_level(level)
@@ -2282,22 +2414,19 @@ function lsp.set_log_level(level)
end
--- Gets the path of the logfile used by the LSP client.
----@returns (String) Path to logfile.
+---@return string path to log file
function lsp.get_log_path()
return log.get_filename()
end
+---@private
--- Invokes a function for each LSP client attached to a buffer.
---
----@param bufnr number Buffer number
+---@param bufnr integer Buffer number
---@param fn function Function to run on each client attached to buffer
--- {bufnr}. The function takes the client, client ID, and
---- buffer number as arguments. Example:
---- <pre>lua
---- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr)
---- print(vim.inspect(client))
---- end)
---- </pre>
+--- buffer number as arguments.
+---@deprecated use lsp.get_clients({ bufnr = bufnr }) with regular loop
function lsp.for_each_buffer_client(bufnr, fn)
return for_each_buffer_client(bufnr, fn)
end
@@ -2316,10 +2445,13 @@ end
--- are valid keys and make sense to include for this handler.
---
--- Will error on invalid keys (i.e. keys that do not exist in the options)
+--- @param name string
+--- @param options table<string,any>
+--- @param user_config table<string,any>
function lsp._with_extend(name, options, user_config)
user_config = user_config or {}
- local resulting_config = {}
+ local resulting_config = {} --- @type table<string,any>
for k, v in pairs(user_config) do
if options[k] == nil then
error(
@@ -2374,4 +2506,3 @@ lsp.commands = setmetatable({}, {
})
return lsp
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/_completion.lua b/runtime/lua/vim/lsp/_completion.lua
new file mode 100644
index 0000000000..7a607d6c13
--- /dev/null
+++ b/runtime/lua/vim/lsp/_completion.lua
@@ -0,0 +1,236 @@
+local M = {}
+local api = vim.api
+local lsp = vim.lsp
+local protocol = lsp.protocol
+local ms = protocol.Methods
+
+---@param input string unparsed snippet
+---@return string parsed snippet
+local function parse_snippet(input)
+ local ok, parsed = pcall(function()
+ return require('vim.lsp._snippet_grammar').parse(input)
+ end)
+ return ok and tostring(parsed) or input
+end
+
+--- Returns text that should be inserted when selecting completion item. The
+--- precedence is as follows: textEdit.newText > insertText > label
+---
+--- See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+---
+---@param item lsp.CompletionItem
+---@return string
+local function get_completion_word(item)
+ if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= '' then
+ if item.insertTextFormat == protocol.InsertTextFormat.PlainText then
+ return item.textEdit.newText
+ else
+ return parse_snippet(item.textEdit.newText)
+ end
+ elseif item.insertText ~= nil and item.insertText ~= '' then
+ if item.insertTextFormat == protocol.InsertTextFormat.PlainText then
+ return item.insertText
+ else
+ return parse_snippet(item.insertText)
+ end
+ end
+ return item.label
+end
+
+---@param result lsp.CompletionList|lsp.CompletionItem[]
+---@return lsp.CompletionItem[]
+local function get_items(result)
+ if result.items then
+ return result.items
+ end
+ return result
+end
+
+--- Turns the result of a `textDocument/completion` request into vim-compatible
+--- |complete-items|.
+---
+---@param result lsp.CompletionList|lsp.CompletionItem[] Result of `textDocument/completion`
+---@param prefix string prefix to filter the completion items
+---@return table[]
+---@see complete-items
+function M._lsp_to_complete_items(result, prefix)
+ local items = get_items(result)
+ if vim.tbl_isempty(items) then
+ return {}
+ end
+
+ local function matches_prefix(item)
+ return vim.startswith(get_completion_word(item), prefix)
+ end
+
+ items = vim.tbl_filter(matches_prefix, items) --[[@as lsp.CompletionItem[]|]]
+ table.sort(items, function(a, b)
+ return (a.sortText or a.label) < (b.sortText or b.label)
+ end)
+
+ local matches = {}
+ for _, item in ipairs(items) do
+ local info = ''
+ local documentation = item.documentation
+ if documentation then
+ if type(documentation) == 'string' and documentation ~= '' then
+ info = documentation
+ elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
+ info = documentation.value
+ else
+ vim.notify(
+ ('invalid documentation value %s'):format(vim.inspect(documentation)),
+ vim.log.levels.WARN
+ )
+ end
+ end
+ local word = get_completion_word(item)
+ table.insert(matches, {
+ word = word,
+ abbr = item.label,
+ kind = protocol.CompletionItemKind[item.kind] or 'Unknown',
+ menu = item.detail or '',
+ info = #info > 0 and info or nil,
+ icase = 1,
+ dup = 1,
+ empty = 1,
+ user_data = {
+ nvim = {
+ lsp = {
+ completion_item = item,
+ },
+ },
+ },
+ })
+ end
+ return matches
+end
+
+---@param lnum integer 0-indexed
+---@param items lsp.CompletionItem[]
+local function adjust_start_col(lnum, line, items, encoding)
+ local min_start_char = nil
+ for _, item in pairs(items) do
+ if item.textEdit and item.textEdit.range.start.line == lnum then
+ if min_start_char and min_start_char ~= item.textEdit.range.start.character then
+ return nil
+ end
+ min_start_char = item.textEdit.range.start.character
+ end
+ end
+ if min_start_char then
+ return vim.lsp.util._str_byteindex_enc(line, min_start_char, encoding)
+ else
+ return nil
+ end
+end
+
+---@private
+---@param line string line content
+---@param lnum integer 0-indexed line number
+---@param client_start_boundary integer 0-indexed word boundary
+---@param server_start_boundary? integer 0-indexed word boundary, based on textEdit.range.start.character
+---@param result lsp.CompletionList|lsp.CompletionItem[]
+---@param encoding string
+---@return table[] matches
+---@return integer? server_start_boundary
+function M._convert_results(
+ line,
+ lnum,
+ cursor_col,
+ client_start_boundary,
+ server_start_boundary,
+ result,
+ encoding
+)
+ -- Completion response items may be relative to a position different than `client_start_boundary`.
+ -- Concrete example, with lua-language-server:
+ --
+ -- require('plenary.asy|
+ -- ▲ ▲ ▲
+ -- │ │ └── cursor_pos: 20
+ -- │ └────── client_start_boundary: 17
+ -- └────────────── textEdit.range.start.character: 9
+ -- .newText = 'plenary.async'
+ -- ^^^
+ -- prefix (We'd remove everything not starting with `asy`,
+ -- so we'd eliminate the `plenary.async` result
+ --
+ -- `adjust_start_col` is used to prefer the language server boundary.
+ --
+ local candidates = get_items(result)
+ local curstartbyte = adjust_start_col(lnum, line, candidates, encoding)
+ if server_start_boundary == nil then
+ server_start_boundary = curstartbyte
+ elseif curstartbyte ~= nil and curstartbyte ~= server_start_boundary then
+ server_start_boundary = client_start_boundary
+ end
+ local prefix = line:sub((server_start_boundary or client_start_boundary) + 1, cursor_col)
+ local matches = M._lsp_to_complete_items(result, prefix)
+ return matches, server_start_boundary
+end
+
+---@param findstart integer 0 or 1, decides behavior
+---@param base integer findstart=0, text to match against
+---@return integer|table Decided by {findstart}:
+--- - findstart=0: column where the completion starts, or -2 or -3
+--- - findstart=1: list of matches (actually just calls |complete()|)
+function M.omnifunc(findstart, base)
+ assert(base) -- silence luals
+ local bufnr = api.nvim_get_current_buf()
+ local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_completion })
+ local remaining = #clients
+ if remaining == 0 then
+ return findstart == 1 and -1 or {}
+ end
+
+ local win = api.nvim_get_current_win()
+ local cursor = api.nvim_win_get_cursor(win)
+ local lnum = cursor[1] - 1
+ local cursor_col = cursor[2]
+ local line = api.nvim_get_current_line()
+ local line_to_cursor = line:sub(1, cursor_col)
+ local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$') --[[@as integer]]
+ local server_start_boundary = nil
+ local items = {}
+
+ local function on_done()
+ local mode = api.nvim_get_mode()['mode']
+ if mode == 'i' or mode == 'ic' then
+ vim.fn.complete((server_start_boundary or client_start_boundary) + 1, items)
+ end
+ end
+
+ local util = vim.lsp.util
+ for _, client in ipairs(clients) do
+ local params = util.make_position_params(win, client.offset_encoding)
+ client.request(ms.textDocument_completion, params, function(err, result)
+ if err then
+ require('vim.lsp.log').warn(err.message)
+ end
+ if result and vim.fn.mode() == 'i' then
+ local matches
+ matches, server_start_boundary = M._convert_results(
+ line,
+ lnum,
+ cursor_col,
+ client_start_boundary,
+ server_start_boundary,
+ result,
+ client.offset_encoding
+ )
+ vim.list_extend(items, matches)
+ end
+ remaining = remaining - 1
+ if remaining == 0 then
+ vim.schedule(on_done)
+ end
+ end, bufnr)
+ end
+
+ -- Return -2 to signal that we should continue completion so that we can
+ -- async complete.
+ return -2
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
new file mode 100644
index 0000000000..04040e8e28
--- /dev/null
+++ b/runtime/lua/vim/lsp/_dynamic.lua
@@ -0,0 +1,109 @@
+local wf = require('vim.lsp._watchfiles')
+
+--- @class lsp.DynamicCapabilities
+--- @field capabilities table<string, lsp.Registration[]>
+--- @field client_id number
+local M = {}
+
+--- @param client_id number
+function M.new(client_id)
+ return setmetatable({
+ capabilities = {},
+ client_id = client_id,
+ }, { __index = M })
+end
+
+function M:supports_registration(method)
+ local client = vim.lsp.get_client_by_id(self.client_id)
+ if not client then
+ return false
+ end
+ local capability = vim.tbl_get(client.config.capabilities, unpack(vim.split(method, '/')))
+ return type(capability) == 'table' and capability.dynamicRegistration
+end
+
+--- @param registrations lsp.Registration[]
+--- @private
+function M:register(registrations)
+ -- remove duplicates
+ self:unregister(registrations)
+ for _, reg in ipairs(registrations) do
+ local method = reg.method
+ if not self.capabilities[method] then
+ self.capabilities[method] = {}
+ end
+ table.insert(self.capabilities[method], reg)
+ end
+end
+
+--- @param unregisterations lsp.Unregistration[]
+--- @private
+function M:unregister(unregisterations)
+ for _, unreg in ipairs(unregisterations) do
+ local method = unreg.method
+ if not self.capabilities[method] then
+ return
+ end
+ local id = unreg.id
+ for i, reg in ipairs(self.capabilities[method]) do
+ if reg.id == id then
+ table.remove(self.capabilities[method], i)
+ break
+ end
+ end
+ end
+end
+
+--- @param method string
+--- @param opts? {bufnr?: number}
+--- @return lsp.Registration? (table|nil) the registration if found
+--- @private
+function M:get(method, opts)
+ opts = opts or {}
+ opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
+ for _, reg in ipairs(self.capabilities[method] or {}) do
+ if not reg.registerOptions then
+ return reg
+ end
+ local documentSelector = reg.registerOptions.documentSelector
+ if not documentSelector then
+ return reg
+ end
+ if M.match(opts.bufnr, documentSelector) then
+ return reg
+ end
+ end
+end
+
+--- @param method string
+--- @param opts? {bufnr?: number}
+--- @private
+function M:supports(method, opts)
+ return self:get(method, opts) ~= nil
+end
+
+--- @param bufnr number
+--- @param documentSelector lsp.DocumentSelector
+--- @private
+function M.match(bufnr, documentSelector)
+ local ft = vim.bo[bufnr].filetype
+ local uri = vim.uri_from_bufnr(bufnr)
+ local fname = vim.uri_to_fname(uri)
+ for _, filter in ipairs(documentSelector) do
+ local matches = true
+ if filter.language and ft ~= filter.language then
+ matches = false
+ end
+ if matches and filter.scheme and not vim.startswith(uri, filter.scheme .. ':') then
+ matches = false
+ end
+ if matches and filter.pattern and not wf._match(filter.pattern, fname) then
+ matches = false
+ end
+ if matches then
+ return true
+ end
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/_meta.lua b/runtime/lua/vim/lsp/_meta.lua
new file mode 100644
index 0000000000..acf799264e
--- /dev/null
+++ b/runtime/lua/vim/lsp/_meta.lua
@@ -0,0 +1,22 @@
+---@meta
+error('Cannot require a meta file')
+
+---@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil): any?
+
+---@class lsp.HandlerContext
+---@field method string
+---@field client_id integer
+---@field bufnr? integer
+---@field params? any
+
+---@class lsp.ResponseError
+---@field code integer
+---@field message string
+---@field data string|number|boolean|table[]|table|nil
+
+--- @class lsp.DocumentFilter
+--- @field language? string
+--- @field scheme? string
+--- @field pattern? string
+
+--- @alias lsp.RegisterOptions any | lsp.StaticRegistrationOptions | lsp.TextDocumentRegistrationOptions
diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua
new file mode 100644
index 0000000000..72b0f00f65
--- /dev/null
+++ b/runtime/lua/vim/lsp/_meta/protocol.lua
@@ -0,0 +1,4396 @@
+--[[
+This file is autogenerated from scripts/gen_lsp.lua
+Regenerate:
+nvim -l scripts/gen_lsp.lua gen --version 3.18 --runtime/lua/vim/lsp/_meta/protocol.lua
+--]]
+
+---@meta
+error('Cannot require a meta file')
+
+---@alias lsp.null nil
+---@alias uinteger integer
+---@alias lsp.decimal number
+---@alias lsp.DocumentUri string
+---@alias lsp.URI string
+---@alias lsp.LSPObject table<string, lsp.LSPAny>
+---@alias lsp.LSPArray lsp.LSPAny[]
+---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil
+
+---@class lsp.ImplementationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---Represents a location inside a resource, such as a line
+---inside a text file.
+---@class lsp.Location
+---@field uri lsp.DocumentUri
+---@field range lsp.Range
+
+---@class lsp.ImplementationRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---@class lsp.TypeDefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---@class lsp.TypeDefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---A workspace folder inside a client.
+---@class lsp.WorkspaceFolder
+---The associated URI for this workspace folder.
+---@field uri lsp.URI
+---The name of the workspace folder. Used to refer to this
+---workspace folder in the user interface.
+---@field name string
+
+---The parameters of a `workspace/didChangeWorkspaceFolders` notification.
+---@class lsp.DidChangeWorkspaceFoldersParams
+---The actual workspace folder change event.
+---@field event lsp.WorkspaceFoldersChangeEvent
+
+---The parameters of a configuration request.
+---@class lsp.ConfigurationParams
+---@field items lsp.ConfigurationItem[]
+
+---Parameters for a {@link DocumentColorRequest}.
+---@class lsp.DocumentColorParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---Represents a color range from a document.
+---@class lsp.ColorInformation
+---The range in the document where this color appears.
+---@field range lsp.Range
+---The actual color value for this color range.
+---@field color lsp.Color
+
+---@class lsp.DocumentColorRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---Parameters for a {@link ColorPresentationRequest}.
+---@class lsp.ColorPresentationParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The color to request presentations for.
+---@field color lsp.Color
+---The range where the color would be inserted. Serves as a context.
+---@field range lsp.Range
+
+---@class lsp.ColorPresentation
+---The label of this color presentation. It will be shown on the color
+---picker header. By default this is also the text that is inserted when selecting
+---this color presentation.
+---@field label string
+---An {@link TextEdit edit} which is applied to a document when selecting
+---this presentation for the color. When `falsy` the {@link ColorPresentation.label label}
+---is used.
+---@field textEdit? lsp.TextEdit
+---An optional array of additional {@link TextEdit text edits} that are applied when
+---selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves.
+---@field additionalTextEdits? lsp.TextEdit[]
+
+---@class lsp.WorkDoneProgressOptions
+---@field workDoneProgress? boolean
+
+---General text document registration options.
+---@class lsp.TextDocumentRegistrationOptions
+---A document selector to identify the scope of the registration. If set to null
+---the document selector provided on the client side will be used.
+---@field documentSelector lsp.DocumentSelector|lsp.null
+
+---Parameters for a {@link FoldingRangeRequest}.
+---@class lsp.FoldingRangeParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---Represents a folding range. To be valid, start and end line must be bigger than zero and smaller
+---than the number of lines in the document. Clients are free to ignore invalid ranges.
+---@class lsp.FoldingRange
+---The zero-based start line of the range to fold. The folded area starts after the line's last character.
+---To be valid, the end must be zero or larger and smaller than the number of lines in the document.
+---@field startLine uinteger
+---The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
+---@field startCharacter? uinteger
+---The zero-based end line of the range to fold. The folded area ends with the line's last character.
+---To be valid, the end must be zero or larger and smaller than the number of lines in the document.
+---@field endLine uinteger
+---The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
+---@field endCharacter? uinteger
+---Describes the kind of the folding range such as `comment' or 'region'. The kind
+---is used to categorize folding ranges and used by commands like 'Fold all comments'.
+---See {@link FoldingRangeKind} for an enumeration of standardized kinds.
+---@field kind? lsp.FoldingRangeKind
+---The text that the client should show when the specified range is
+---collapsed. If not defined or not supported by the client, a default
+---will be chosen by the client.
+---
+---@since 3.17.0
+---@field collapsedText? string
+
+---@class lsp.FoldingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---@class lsp.DeclarationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---@class lsp.DeclarationRegistrationOptions: lsp.DeclarationOptions, lsp.StaticRegistrationOptions
+
+---A parameter literal used in selection range requests.
+---@class lsp.SelectionRangeParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The positions inside the text document.
+---@field positions lsp.Position[]
+
+---A selection range represents a part of a selection hierarchy. A selection range
+---may have a parent selection range that contains it.
+---@class lsp.SelectionRange
+---The {@link Range range} of this selection range.
+---@field range lsp.Range
+---The parent selection range containing this range. Therefore `parent.range` must contain `this.range`.
+---@field parent? lsp.SelectionRange
+
+---@class lsp.SelectionRangeRegistrationOptions: lsp.SelectionRangeOptions, lsp.StaticRegistrationOptions
+
+---@class lsp.WorkDoneProgressCreateParams
+---The token to be used to report progress.
+---@field token lsp.ProgressToken
+
+---@class lsp.WorkDoneProgressCancelParams
+---The token to be used to report progress.
+---@field token lsp.ProgressToken
+
+---The parameter of a `textDocument/prepareCallHierarchy` request.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyPrepareParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+
+---Represents programming constructs like functions or constructors in the context
+---of call hierarchy.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyItem
+---The name of this item.
+---@field name string
+---The kind of this item.
+---@field kind lsp.SymbolKind
+---Tags for this item.
+---@field tags? lsp.SymbolTag[]
+---More detail for this item, e.g. the signature of a function.
+---@field detail? string
+---The resource identifier of this item.
+---@field uri lsp.DocumentUri
+---The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
+---@field range lsp.Range
+---The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function.
+---Must be contained by the {@link CallHierarchyItem.range `range`}.
+---@field selectionRange lsp.Range
+---A data entry field that is preserved between a call hierarchy prepare and
+---incoming calls or outgoing calls requests.
+---@field data? lsp.LSPAny
+
+---Call hierarchy options used during static or dynamic registration.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---The parameter of a `callHierarchy/incomingCalls` request.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyIncomingCallsParams
+---@field item lsp.CallHierarchyItem
+
+---Represents an incoming call, e.g. a caller of a method or constructor.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyIncomingCall
+---The item that makes the call.
+---@field from lsp.CallHierarchyItem
+---The ranges at which the calls appear. This is relative to the caller
+---denoted by {@link CallHierarchyIncomingCall.from `this.from`}.
+---@field fromRanges lsp.Range[]
+
+---The parameter of a `callHierarchy/outgoingCalls` request.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyOutgoingCallsParams
+---@field item lsp.CallHierarchyItem
+
+---Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyOutgoingCall
+---The item that is called.
+---@field to lsp.CallHierarchyItem
+---The range at which this item is called. This is the range relative to the caller, e.g the item
+---passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`}
+---and not {@link CallHierarchyOutgoingCall.to `this.to`}.
+---@field fromRanges lsp.Range[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---@since 3.16.0
+---@class lsp.SemanticTokens
+---An optional result id. If provided and clients support delta updating
+---the client will include the result id in the next semantic token request.
+---A server can then instead of computing all semantic tokens again simply
+---send a delta.
+---@field resultId? string
+---The actual tokens.
+---@field data uinteger[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensPartialResult
+---@field data uinteger[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---@since 3.16.0
+---@class lsp.SemanticTokensDeltaParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The result id of a previous response. The result Id can either point to a full response
+---or a delta response depending on what was received last.
+---@field previousResultId string
+
+---@since 3.16.0
+---@class lsp.SemanticTokensDelta
+---@field resultId? string
+---The semantic token edits to transform a previous result into a new result.
+---@field edits lsp.SemanticTokensEdit[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensDeltaPartialResult
+---@field edits lsp.SemanticTokensEdit[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensRangeParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The range the semantic tokens are requested for.
+---@field range lsp.Range
+
+---Params to show a resource in the UI.
+---
+---@since 3.16.0
+---@class lsp.ShowDocumentParams
+---The uri to show.
+---@field uri lsp.URI
+---Indicates to show the resource in an external program.
+---To show, for example, `https://code.visualstudio.com/`
+---in the default WEB browser set `external` to `true`.
+---@field external? boolean
+---An optional property to indicate whether the editor
+---showing the document should take focus or not.
+---Clients might ignore this property if an external
+---program is started.
+---@field takeFocus? boolean
+---An optional selection range if the document is a text
+---document. Clients might ignore the property if an
+---external program is started or the file is not a text
+---file.
+---@field selection? lsp.Range
+
+---The result of a showDocument request.
+---
+---@since 3.16.0
+---@class lsp.ShowDocumentResult
+---A boolean indicating if the show was successful.
+---@field success boolean
+
+---@class lsp.LinkedEditingRangeParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+
+---The result of a linked editing range request.
+---
+---@since 3.16.0
+---@class lsp.LinkedEditingRanges
+---A list of ranges that can be edited together. The ranges must have
+---identical length and contain identical text content. The ranges cannot overlap.
+---@field ranges lsp.Range[]
+---An optional word pattern (regular expression) that describes valid contents for
+---the given ranges. If no pattern is provided, the client configuration's word
+---pattern will be used.
+---@field wordPattern? string
+
+---@class lsp.LinkedEditingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---The parameters sent in notifications/requests for user-initiated creation of
+---files.
+---
+---@since 3.16.0
+---@class lsp.CreateFilesParams
+---An array of all files/folders created in this operation.
+---@field files lsp.FileCreate[]
+
+---A workspace edit represents changes to many resources managed in the workspace. The edit
+---should either provide `changes` or `documentChanges`. If documentChanges are present
+---they are preferred over `changes` if the client can handle versioned document edits.
+---
+---Since version 3.13.0 a workspace edit can contain resource operations as well. If resource
+---operations are present clients need to execute the operations in the order in which they
+---are provided. So a workspace edit for example can consist of the following two changes:
+---(1) a create file a.txt and (2) a text document edit which insert text into file a.txt.
+---
+---An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will
+---cause failure of the operation. How the client recovers from the failure is described by
+---the client capability: `workspace.workspaceEdit.failureHandling`
+---@class lsp.WorkspaceEdit
+---Holds changes to existing resources.
+---@field changes? table<lsp.DocumentUri, lsp.TextEdit[]>
+---Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
+---are either an array of `TextDocumentEdit`s to express changes to n different text documents
+---where each text document edit addresses a specific version of a text document. Or it can contain
+---above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations.
+---
+---Whether a client supports versioned document edits is expressed via
+---`workspace.workspaceEdit.documentChanges` client capability.
+---
+---If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
+---only plain `TextEdit`s using the `changes` property are supported.
+---@field documentChanges? lsp.TextDocumentEdit|lsp.CreateFile|lsp.RenameFile|lsp.DeleteFile[]
+---A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
+---delete file / folder operations.
+---
+---Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`.
+---
+---@since 3.16.0
+---@field changeAnnotations? table<lsp.ChangeAnnotationIdentifier, lsp.ChangeAnnotation>
+
+---The options to register for file operations.
+---
+---@since 3.16.0
+---@class lsp.FileOperationRegistrationOptions
+---The actual filters.
+---@field filters lsp.FileOperationFilter[]
+
+---The parameters sent in notifications/requests for user-initiated renames of
+---files.
+---
+---@since 3.16.0
+---@class lsp.RenameFilesParams
+---An array of all files/folders renamed in this operation. When a folder is renamed, only
+---the folder will be included, and not its children.
+---@field files lsp.FileRename[]
+
+---The parameters sent in notifications/requests for user-initiated deletes of
+---files.
+---
+---@since 3.16.0
+---@class lsp.DeleteFilesParams
+---An array of all files/folders deleted in this operation.
+---@field files lsp.FileDelete[]
+
+---@class lsp.MonikerParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---Moniker definition to match LSIF 0.5 moniker definition.
+---
+---@since 3.16.0
+---@class lsp.Moniker
+---The scheme of the moniker. For example tsc or .Net
+---@field scheme string
+---The identifier of the moniker. The value is opaque in LSIF however
+---schema owners are allowed to define the structure if they want.
+---@field identifier string
+---The scope in which the moniker is unique
+---@field unique lsp.UniquenessLevel
+---The moniker kind if known.
+---@field kind? lsp.MonikerKind
+
+---@class lsp.MonikerRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameter of a `textDocument/prepareTypeHierarchy` request.
+---
+---@since 3.17.0
+---@class lsp.TypeHierarchyPrepareParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+
+---@since 3.17.0
+---@class lsp.TypeHierarchyItem
+---The name of this item.
+---@field name string
+---The kind of this item.
+---@field kind lsp.SymbolKind
+---Tags for this item.
+---@field tags? lsp.SymbolTag[]
+---More detail for this item, e.g. the signature of a function.
+---@field detail? string
+---The resource identifier of this item.
+---@field uri lsp.DocumentUri
+---The range enclosing this symbol not including leading/trailing whitespace
+---but everything else, e.g. comments and code.
+---@field range lsp.Range
+---The range that should be selected and revealed when this symbol is being
+---picked, e.g. the name of a function. Must be contained by the
+---{@link TypeHierarchyItem.range `range`}.
+---@field selectionRange lsp.Range
+---A data entry field that is preserved between a type hierarchy prepare and
+---supertypes or subtypes requests. It could also be used to identify the
+---type hierarchy in the server, helping improve the performance on
+---resolving supertypes and subtypes.
+---@field data? lsp.LSPAny
+
+---Type hierarchy options used during static or dynamic registration.
+---
+---@since 3.17.0
+---@class lsp.TypeHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---The parameter of a `typeHierarchy/supertypes` request.
+---
+---@since 3.17.0
+---@class lsp.TypeHierarchySupertypesParams
+---@field item lsp.TypeHierarchyItem
+
+---The parameter of a `typeHierarchy/subtypes` request.
+---
+---@since 3.17.0
+---@class lsp.TypeHierarchySubtypesParams
+---@field item lsp.TypeHierarchyItem
+
+---A parameter literal used in inline value requests.
+---
+---@since 3.17.0
+---@class lsp.InlineValueParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The document range for which inline values should be computed.
+---@field range lsp.Range
+---Additional information about the context in which inline values were
+---requested.
+---@field context lsp.InlineValueContext
+
+---Inline value options used during static or dynamic registration.
+---
+---@since 3.17.0
+---@class lsp.InlineValueRegistrationOptions: lsp.InlineValueOptions, lsp.StaticRegistrationOptions
+
+---A parameter literal used in inlay hint requests.
+---
+---@since 3.17.0
+---@class lsp.InlayHintParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The document range for which inlay hints should be computed.
+---@field range lsp.Range
+
+---Inlay hint information.
+---
+---@since 3.17.0
+---@class lsp.InlayHint
+---The position of this hint.
+---@field position lsp.Position
+---The label of this hint. A human readable string or an array of
+---InlayHintLabelPart label parts.
+---
+---*Note* that neither the string nor the label part can be empty.
+---@field label string|lsp.InlayHintLabelPart[]
+---The kind of this hint. Can be omitted in which case the client
+---should fall back to a reasonable default.
+---@field kind? lsp.InlayHintKind
+---Optional text edits that are performed when accepting this inlay hint.
+---
+---*Note* that edits are expected to change the document so that the inlay
+---hint (or its nearest variant) is now part of the document and the inlay
+---hint itself is now obsolete.
+---@field textEdits? lsp.TextEdit[]
+---The tooltip text when you hover over this item.
+---@field tooltip? string|lsp.MarkupContent
+---Render padding before the hint.
+---
+---Note: Padding should use the editor's background color, not the
+---background color of the hint itself. That means padding can be used
+---to visually align/separate an inlay hint.
+---@field paddingLeft? boolean
+---Render padding after the hint.
+---
+---Note: Padding should use the editor's background color, not the
+---background color of the hint itself. That means padding can be used
+---to visually align/separate an inlay hint.
+---@field paddingRight? boolean
+---A data entry field that is preserved on an inlay hint between
+---a `textDocument/inlayHint` and a `inlayHint/resolve` request.
+---@field data? lsp.LSPAny
+
+---Inlay hint options used during static or dynamic registration.
+---
+---@since 3.17.0
+---@class lsp.InlayHintRegistrationOptions: lsp.InlayHintOptions, lsp.StaticRegistrationOptions
+
+---Parameters of the document diagnostic request.
+---
+---@since 3.17.0
+---@class lsp.DocumentDiagnosticParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The additional identifier provided during registration.
+---@field identifier? string
+---The result id of a previous response if provided.
+---@field previousResultId? string
+
+---A partial result for a document diagnostic report.
+---
+---@since 3.17.0
+---@class lsp.DocumentDiagnosticReportPartialResult
+---@field relatedDocuments table<lsp.DocumentUri, lsp.FullDocumentDiagnosticReport|lsp.UnchangedDocumentDiagnosticReport>
+
+---Cancellation data returned from a diagnostic request.
+---
+---@since 3.17.0
+---@class lsp.DiagnosticServerCancellationData
+---@field retriggerRequest boolean
+
+---Diagnostic registration options.
+---
+---@since 3.17.0
+---@class lsp.DiagnosticRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+
+---Parameters of the workspace diagnostic request.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceDiagnosticParams
+---The additional identifier provided during registration.
+---@field identifier? string
+---The currently known diagnostic reports with their
+---previous result ids.
+---@field previousResultIds lsp.PreviousResultId[]
+
+---A workspace diagnostic report.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceDiagnosticReport
+---@field items lsp.WorkspaceDocumentDiagnosticReport[]
+
+---A partial result for a workspace diagnostic report.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceDiagnosticReportPartialResult
+---@field items lsp.WorkspaceDocumentDiagnosticReport[]
+
+---The params sent in an open notebook document notification.
+---
+---@since 3.17.0
+---@class lsp.DidOpenNotebookDocumentParams
+---The notebook document that got opened.
+---@field notebookDocument lsp.NotebookDocument
+---The text documents that represent the content
+---of a notebook cell.
+---@field cellTextDocuments lsp.TextDocumentItem[]
+
+---The params sent in a change notebook document notification.
+---
+---@since 3.17.0
+---@class lsp.DidChangeNotebookDocumentParams
+---The notebook document that did change. The version number points
+---to the version after all provided changes have been applied. If
+---only the text document content of a cell changes the notebook version
+---doesn't necessarily have to change.
+---@field notebookDocument lsp.VersionedNotebookDocumentIdentifier
+---The actual changes to the notebook document.
+---
+---The changes describe single state changes to the notebook document.
+---So if there are two changes c1 (at array index 0) and c2 (at array
+---index 1) for a notebook in state S then c1 moves the notebook from
+---S to S' and c2 from S' to S''. So c1 is computed on the state S and
+---c2 is computed on the state S'.
+---
+---To mirror the content of a notebook using change events use the following approach:
+---- start with the same initial content
+---- apply the 'notebookDocument/didChange' notifications in the order you receive them.
+---- apply the `NotebookChangeEvent`s in a single notification in the order
+--- you receive them.
+---@field change lsp.NotebookDocumentChangeEvent
+
+---The params sent in a save notebook document notification.
+---
+---@since 3.17.0
+---@class lsp.DidSaveNotebookDocumentParams
+---The notebook document that got saved.
+---@field notebookDocument lsp.NotebookDocumentIdentifier
+
+---The params sent in a close notebook document notification.
+---
+---@since 3.17.0
+---@class lsp.DidCloseNotebookDocumentParams
+---The notebook document that got closed.
+---@field notebookDocument lsp.NotebookDocumentIdentifier
+---The text documents that represent the content
+---of a notebook cell that got closed.
+---@field cellTextDocuments lsp.TextDocumentIdentifier[]
+
+---A parameter literal used in inline completion requests.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+---Additional information about the context in which inline completions were
+---requested.
+---@field context lsp.InlineCompletionContext
+
+---Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor.
+---@class lsp.InlineCompletionList
+---The inline completion items
+---@field items lsp.InlineCompletionItem[]
+
+---An inline completion item represents a text snippet that is proposed inline to complete text that is being typed.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionItem
+---The text to replace the range with. Must be set.
+---@field insertText string
+---The format of the insert text. The format applies to the `insertText`. If omitted defaults to `InsertTextFormat.PlainText`.
+---@field insertTextFormat? lsp.InsertTextFormat
+---A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used.
+---@field filterText? string
+---The range to replace. Must begin and end on the same line.
+---@field range? lsp.Range
+---An optional {@link Command} that is executed *after* inserting this completion.
+---@field command? lsp.Command
+
+---Inline completion options used during static or dynamic registration.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionRegistrationOptions: lsp.InlineCompletionOptions, lsp.StaticRegistrationOptions
+
+---@class lsp.RegistrationParams
+---@field registrations lsp.Registration[]
+
+---@class lsp.UnregistrationParams
+---@field unregisterations lsp.Unregistration[]
+
+---@class lsp.InitializeParams: lsp._InitializeParams
+
+---The result returned from an initialize request.
+---@class lsp.InitializeResult
+---The capabilities the language server provides.
+---@field capabilities lsp.ServerCapabilities
+---Information about the server.
+---
+---@since 3.15.0
+---@field serverInfo? anonym1
+
+---The data type of the ResponseError if the
+---initialize request fails.
+---@class lsp.InitializeError
+---Indicates whether the client execute the following retry logic:
+---(1) show the message provided by the ResponseError to the user
+---(2) user selects retry or cancel
+---(3) if user selected retry the initialize method is sent again.
+---@field retry boolean
+
+---@class lsp.InitializedParams
+
+---The parameters of a change configuration notification.
+---@class lsp.DidChangeConfigurationParams
+---The actual changed settings
+---@field settings lsp.LSPAny
+
+---@class lsp.DidChangeConfigurationRegistrationOptions
+---@field section? string|string[]
+
+---The parameters of a notification message.
+---@class lsp.ShowMessageParams
+---The message type. See {@link MessageType}
+---@field type lsp.MessageType
+---The actual message.
+---@field message string
+
+---@class lsp.ShowMessageRequestParams
+---The message type. See {@link MessageType}
+---@field type lsp.MessageType
+---The actual message.
+---@field message string
+---The message action items to present.
+---@field actions? lsp.MessageActionItem[]
+
+---@class lsp.MessageActionItem
+---A short title like 'Retry', 'Open Log' etc.
+---@field title string
+
+---The log message parameters.
+---@class lsp.LogMessageParams
+---The message type. See {@link MessageType}
+---@field type lsp.MessageType
+---The actual message.
+---@field message string
+
+---The parameters sent in an open text document notification
+---@class lsp.DidOpenTextDocumentParams
+---The document that was opened.
+---@field textDocument lsp.TextDocumentItem
+
+---The change text document notification's parameters.
+---@class lsp.DidChangeTextDocumentParams
+---The document that did change. The version number points
+---to the version after all provided content changes have
+---been applied.
+---@field textDocument lsp.VersionedTextDocumentIdentifier
+---The actual content changes. The content changes describe single state changes
+---to the document. So if there are two content changes c1 (at array index 0) and
+---c2 (at array index 1) for a document in state S then c1 moves the document from
+---S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed
+---on the state S'.
+---
+---To mirror the content of a document using change events use the following approach:
+---- start with the same initial content
+---- apply the 'textDocument/didChange' notifications in the order you receive them.
+---- apply the `TextDocumentContentChangeEvent`s in a single notification in the order
+--- you receive them.
+---@field contentChanges lsp.TextDocumentContentChangeEvent[]
+
+---Describe options to be used when registered for text document change events.
+---@class lsp.TextDocumentChangeRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---How documents are synced to the server.
+---@field syncKind lsp.TextDocumentSyncKind
+
+---The parameters sent in a close text document notification
+---@class lsp.DidCloseTextDocumentParams
+---The document that was closed.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---The parameters sent in a save text document notification
+---@class lsp.DidSaveTextDocumentParams
+---The document that was saved.
+---@field textDocument lsp.TextDocumentIdentifier
+---Optional the content when saved. Depends on the includeText value
+---when the save notification was requested.
+---@field text? string
+
+---Save registration options.
+---@class lsp.TextDocumentSaveRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters sent in a will save text document notification.
+---@class lsp.WillSaveTextDocumentParams
+---The document that will be saved.
+---@field textDocument lsp.TextDocumentIdentifier
+---The 'TextDocumentSaveReason'.
+---@field reason lsp.TextDocumentSaveReason
+
+---A text edit applicable to a text document.
+---@class lsp.TextEdit
+---The range of the text document to be manipulated. To insert
+---text into a document create a range where start === end.
+---@field range lsp.Range
+---The string to be inserted. For delete operations use an
+---empty string.
+---@field newText string
+
+---The watched files change notification's parameters.
+---@class lsp.DidChangeWatchedFilesParams
+---The actual file events.
+---@field changes lsp.FileEvent[]
+
+---Describe options to be used when registered for text document change events.
+---@class lsp.DidChangeWatchedFilesRegistrationOptions
+---The watchers to register.
+---@field watchers lsp.FileSystemWatcher[]
+
+---The publish diagnostic notification's parameters.
+---@class lsp.PublishDiagnosticsParams
+---The URI for which diagnostic information is reported.
+---@field uri lsp.DocumentUri
+---Optional the version number of the document the diagnostics are published for.
+---
+---@since 3.15.0
+---@field version? integer
+---An array of diagnostic information items.
+---@field diagnostics lsp.Diagnostic[]
+
+---Completion parameters
+---@class lsp.CompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---The completion context. This is only available it the client specifies
+---to send this using the client capability `textDocument.completion.contextSupport === true`
+---@field context? lsp.CompletionContext
+
+---A completion item represents a text snippet that is
+---proposed to complete text that is being typed.
+---@class lsp.CompletionItem
+---The label of this completion item.
+---
+---The label property is also by default the text that
+---is inserted when selecting this completion.
+---
+---If label details are provided the label itself should
+---be an unqualified name of the completion item.
+---@field label string
+---Additional details for the label
+---
+---@since 3.17.0
+---@field labelDetails? lsp.CompletionItemLabelDetails
+---The kind of this completion item. Based of the kind
+---an icon is chosen by the editor.
+---@field kind? lsp.CompletionItemKind
+---Tags for this completion item.
+---
+---@since 3.15.0
+---@field tags? lsp.CompletionItemTag[]
+---A human-readable string with additional information
+---about this item, like type or symbol information.
+---@field detail? string
+---A human-readable string that represents a doc-comment.
+---@field documentation? string|lsp.MarkupContent
+---Indicates if this item is deprecated.
+---@deprecated Use `tags` instead.
+---@field deprecated? boolean
+---Select this item when showing.
+---
+---*Note* that only one completion item can be selected and that the
+---tool / client decides which item that is. The rule is that the *first*
+---item of those that match best is selected.
+---@field preselect? boolean
+---A string that should be used when comparing this item
+---with other items. When `falsy` the {@link CompletionItem.label label}
+---is used.
+---@field sortText? string
+---A string that should be used when filtering a set of
+---completion items. When `falsy` the {@link CompletionItem.label label}
+---is used.
+---@field filterText? string
+---A string that should be inserted into a document when selecting
+---this completion. When `falsy` the {@link CompletionItem.label label}
+---is used.
+---
+---The `insertText` is subject to interpretation by the client side.
+---Some tools might not take the string literally. For example
+---VS Code when code complete is requested in this example
+---`con<cursor position>` and a completion item with an `insertText` of
+---`console` is provided it will only insert `sole`. Therefore it is
+---recommended to use `textEdit` instead since it avoids additional client
+---side interpretation.
+---@field insertText? string
+---The format of the insert text. The format applies to both the
+---`insertText` property and the `newText` property of a provided
+---`textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
+---
+---Please note that the insertTextFormat doesn't apply to
+---`additionalTextEdits`.
+---@field insertTextFormat? lsp.InsertTextFormat
+---How whitespace and indentation is handled during completion
+---item insertion. If not provided the clients default value depends on
+---the `textDocument.completion.insertTextMode` client capability.
+---
+---@since 3.16.0
+---@field insertTextMode? lsp.InsertTextMode
+---An {@link TextEdit edit} which is applied to a document when selecting
+---this completion. When an edit is provided the value of
+---{@link CompletionItem.insertText insertText} is ignored.
+---
+---Most editors support two different operations when accepting a completion
+---item. One is to insert a completion text and the other is to replace an
+---existing text with a completion text. Since this can usually not be
+---predetermined by a server it can report both ranges. Clients need to
+---signal support for `InsertReplaceEdits` via the
+---`textDocument.completion.insertReplaceSupport` client capability
+---property.
+---
+---*Note 1:* The text edit's range as well as both ranges from an insert
+---replace edit must be a [single line] and they must contain the position
+---at which completion has been requested.
+---*Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range
+---must be a prefix of the edit's replace range, that means it must be
+---contained and starting at the same position.
+---
+---@since 3.16.0 additional type `InsertReplaceEdit`
+---@field textEdit? lsp.TextEdit|lsp.InsertReplaceEdit
+---The edit text used if the completion item is part of a CompletionList and
+---CompletionList defines an item default for the text edit range.
+---
+---Clients will only honor this property if they opt into completion list
+---item defaults using the capability `completionList.itemDefaults`.
+---
+---If not provided and a list's default range is provided the label
+---property is used as a text.
+---
+---@since 3.17.0
+---@field textEditText? string
+---An optional array of additional {@link TextEdit text edits} that are applied when
+---selecting this completion. Edits must not overlap (including the same insert position)
+---with the main {@link CompletionItem.textEdit edit} nor with themselves.
+---
+---Additional text edits should be used to change text unrelated to the current cursor position
+---(for example adding an import statement at the top of the file if the completion item will
+---insert an unqualified type).
+---@field additionalTextEdits? lsp.TextEdit[]
+---An optional set of characters that when pressed while this completion is active will accept it first and
+---then type that character. *Note* that all commit characters should have `length=1` and that superfluous
+---characters will be ignored.
+---@field commitCharacters? string[]
+---An optional {@link Command command} that is executed *after* inserting this completion. *Note* that
+---additional modifications to the current document should be described with the
+---{@link CompletionItem.additionalTextEdits additionalTextEdits}-property.
+---@field command? lsp.Command
+---A data entry field that is preserved on a completion item between a
+---{@link CompletionRequest} and a {@link CompletionResolveRequest}.
+---@field data? lsp.LSPAny
+
+---Represents a collection of {@link CompletionItem completion items} to be presented
+---in the editor.
+---@class lsp.CompletionList
+---This list it not complete. Further typing results in recomputing this list.
+---
+---Recomputed lists have all their items replaced (not appended) in the
+---incomplete completion sessions.
+---@field isIncomplete boolean
+---In many cases the items of an actual completion result share the same
+---value for properties like `commitCharacters` or the range of a text
+---edit. A completion list can therefore define item defaults which will
+---be used if a completion item itself doesn't specify the value.
+---
+---If a completion list specifies a default value and a completion item
+---also specifies a corresponding value the one from the item is used.
+---
+---Servers are only allowed to return default values if the client
+---signals support for this via the `completionList.itemDefaults`
+---capability.
+---
+---@since 3.17.0
+---@field itemDefaults? anonym3
+---The completion items.
+---@field items lsp.CompletionItem[]
+
+---Registration options for a {@link CompletionRequest}.
+---@class lsp.CompletionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link HoverRequest}.
+---@class lsp.HoverParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+
+---The result of a hover request.
+---@class lsp.Hover
+---The hover's content
+---@field contents lsp.MarkupContent|lsp.MarkedString|lsp.MarkedString[]
+---An optional range inside the text document that is used to
+---visualize the hover, e.g. by changing the background color.
+---@field range? lsp.Range
+
+---Registration options for a {@link HoverRequest}.
+---@class lsp.HoverRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link SignatureHelpRequest}.
+---@class lsp.SignatureHelpParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+---The signature help context. This is only available if the client specifies
+---to send this using the client capability `textDocument.signatureHelp.contextSupport === true`
+---
+---@since 3.15.0
+---@field context? lsp.SignatureHelpContext
+
+---Signature help represents the signature of something
+---callable. There can be multiple signature but only one
+---active and only one active parameter.
+---@class lsp.SignatureHelp
+---One or more signatures.
+---@field signatures lsp.SignatureInformation[]
+---The active signature. If omitted or the value lies outside the
+---range of `signatures` the value defaults to zero or is ignored if
+---the `SignatureHelp` has no signatures.
+---
+---Whenever possible implementors should make an active decision about
+---the active signature and shouldn't rely on a default value.
+---
+---In future version of the protocol this property might become
+---mandatory to better express this.
+---@field activeSignature? uinteger
+---The active parameter of the active signature. If omitted or the value
+---lies outside the range of `signatures[activeSignature].parameters`
+---defaults to 0 if the active signature has parameters. If
+---the active signature has no parameters it is ignored.
+---In future version of the protocol this property might become
+---mandatory to better express the active parameter if the
+---active signature does have any.
+---@field activeParameter? uinteger
+
+---Registration options for a {@link SignatureHelpRequest}.
+---@class lsp.SignatureHelpRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link DefinitionRequest}.
+---@class lsp.DefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---Registration options for a {@link DefinitionRequest}.
+---@class lsp.DefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link ReferencesRequest}.
+---@class lsp.ReferenceParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---@field context lsp.ReferenceContext
+
+---Registration options for a {@link ReferencesRequest}.
+---@class lsp.ReferenceRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link DocumentHighlightRequest}.
+---@class lsp.DocumentHighlightParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+
+---A document highlight is a range inside a text document which deserves
+---special attention. Usually a document highlight is visualized by changing
+---the background color of its range.
+---@class lsp.DocumentHighlight
+---The range this highlight applies to.
+---@field range lsp.Range
+---The highlight kind, default is {@link DocumentHighlightKind.Text text}.
+---@field kind? lsp.DocumentHighlightKind
+
+---Registration options for a {@link DocumentHighlightRequest}.
+---@class lsp.DocumentHighlightRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---Parameters for a {@link DocumentSymbolRequest}.
+---@class lsp.DocumentSymbolParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---Represents information about programming constructs like variables, classes,
+---interfaces etc.
+---@class lsp.SymbolInformation: lsp.BaseSymbolInformation
+---Indicates if this symbol is deprecated.
+---
+---@deprecated Use tags instead
+---@field deprecated? boolean
+---The location of this symbol. The location's range is used by a tool
+---to reveal the location in the editor. If the symbol is selected in the
+---tool the range's start information is used to position the cursor. So
+---the range usually spans more than the actual symbol's name and does
+---normally include things like visibility modifiers.
+---
+---The range doesn't have to denote a node range in the sense of an abstract
+---syntax tree. It can therefore not be used to re-construct a hierarchy of
+---the symbols.
+---@field location lsp.Location
+
+---Represents programming constructs like variables, classes, interfaces etc.
+---that appear in a document. Document symbols can be hierarchical and they
+---have two ranges: one that encloses its definition and one that points to
+---its most interesting range, e.g. the range of an identifier.
+---@class lsp.DocumentSymbol
+---The name of this symbol. Will be displayed in the user interface and therefore must not be
+---an empty string or a string only consisting of white spaces.
+---@field name string
+---More detail for this symbol, e.g the signature of a function.
+---@field detail? string
+---The kind of this symbol.
+---@field kind lsp.SymbolKind
+---Tags for this document symbol.
+---
+---@since 3.16.0
+---@field tags? lsp.SymbolTag[]
+---Indicates if this symbol is deprecated.
+---
+---@deprecated Use tags instead
+---@field deprecated? boolean
+---The range enclosing this symbol not including leading/trailing whitespace but everything else
+---like comments. This information is typically used to determine if the clients cursor is
+---inside the symbol to reveal in the symbol in the UI.
+---@field range lsp.Range
+---The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
+---Must be contained by the `range`.
+---@field selectionRange lsp.Range
+---Children of this symbol, e.g. properties of a class.
+---@field children? lsp.DocumentSymbol[]
+
+---Registration options for a {@link DocumentSymbolRequest}.
+---@class lsp.DocumentSymbolRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link CodeActionRequest}.
+---@class lsp.CodeActionParams
+---The document in which the command was invoked.
+---@field textDocument lsp.TextDocumentIdentifier
+---The range for which the command was invoked.
+---@field range lsp.Range
+---Context carrying additional information.
+---@field context lsp.CodeActionContext
+
+---Represents a reference to a command. Provides a title which
+---will be used to represent a command in the UI and, optionally,
+---an array of arguments which will be passed to the command handler
+---function when invoked.
+---@class lsp.Command
+---Title of the command, like `save`.
+---@field title string
+---The identifier of the actual command handler.
+---@field command string
+---Arguments that the command handler should be
+---invoked with.
+---@field arguments? lsp.LSPAny[]
+
+---A code action represents a change that can be performed in code, e.g. to fix a problem or
+---to refactor code.
+---
+---A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed.
+---@class lsp.CodeAction
+---A short, human-readable, title for this code action.
+---@field title string
+---The kind of the code action.
+---
+---Used to filter code actions.
+---@field kind? lsp.CodeActionKind
+---The diagnostics that this code action resolves.
+---@field diagnostics? lsp.Diagnostic[]
+---Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted
+---by keybindings.
+---
+---A quick fix should be marked preferred if it properly addresses the underlying error.
+---A refactoring should be marked preferred if it is the most reasonable choice of actions to take.
+---
+---@since 3.15.0
+---@field isPreferred? boolean
+---Marks that the code action cannot currently be applied.
+---
+---Clients should follow the following guidelines regarding disabled code actions:
+---
+--- - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action)
+--- code action menus.
+---
+--- - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type
+--- of code action, such as refactorings.
+---
+--- - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions)
+--- that auto applies a code action and only disabled code actions are returned, the client should show the user an
+--- error message with `reason` in the editor.
+---
+---@since 3.16.0
+---@field disabled? anonym4
+---The workspace edit this code action performs.
+---@field edit? lsp.WorkspaceEdit
+---A command this code action executes. If a code action
+---provides an edit and a command, first the edit is
+---executed and then the command.
+---@field command? lsp.Command
+---A data entry field that is preserved on a code action between
+---a `textDocument/codeAction` and a `codeAction/resolve` request.
+---
+---@since 3.16.0
+---@field data? lsp.LSPAny
+
+---Registration options for a {@link CodeActionRequest}.
+---@class lsp.CodeActionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link WorkspaceSymbolRequest}.
+---@class lsp.WorkspaceSymbolParams
+---A query string to filter symbols by. Clients may send an empty
+---string here to request all symbols.
+---@field query string
+
+---A special workspace symbol that supports locations without a range.
+---
+---See also SymbolInformation.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceSymbol: lsp.BaseSymbolInformation
+---The location of the symbol. Whether a server is allowed to
+---return a location without a range depends on the client
+---capability `workspace.symbol.resolveSupport`.
+---
+---See SymbolInformation#location for more details.
+---@field location lsp.Location|anonym5
+---A data entry field that is preserved on a workspace symbol between a
+---workspace symbol request and a workspace symbol resolve request.
+---@field data? lsp.LSPAny
+
+---Registration options for a {@link WorkspaceSymbolRequest}.
+---@class lsp.WorkspaceSymbolRegistrationOptions: lsp.WorkspaceSymbolOptions
+
+---The parameters of a {@link CodeLensRequest}.
+---@class lsp.CodeLensParams
+---The document to request code lens for.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---A code lens represents a {@link Command command} that should be shown along with
+---source text, like the number of references, a way to run tests, etc.
+---
+---A code lens is _unresolved_ when no command is associated to it. For performance
+---reasons the creation of a code lens and resolving should be done in two stages.
+---@class lsp.CodeLens
+---The range in which this code lens is valid. Should only span a single line.
+---@field range lsp.Range
+---The command this code lens represents.
+---@field command? lsp.Command
+---A data entry field that is preserved on a code lens item between
+---a {@link CodeLensRequest} and a [CodeLensResolveRequest]
+---(#CodeLensResolveRequest)
+---@field data? lsp.LSPAny
+
+---Registration options for a {@link CodeLensRequest}.
+---@class lsp.CodeLensRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link DocumentLinkRequest}.
+---@class lsp.DocumentLinkParams
+---The document to provide document links for.
+---@field textDocument lsp.TextDocumentIdentifier
+
+---A document link is a range in a text document that links to an internal or external resource, like another
+---text document or a web site.
+---@class lsp.DocumentLink
+---The range this link applies to.
+---@field range lsp.Range
+---The uri this link points to. If missing a resolve request is sent later.
+---@field target? lsp.URI
+---The tooltip text when you hover over this link.
+---
+---If a tooltip is provided, is will be displayed in a string that includes instructions on how to
+---trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS,
+---user settings, and localization.
+---
+---@since 3.15.0
+---@field tooltip? string
+---A data entry field that is preserved on a document link between a
+---DocumentLinkRequest and a DocumentLinkResolveRequest.
+---@field data? lsp.LSPAny
+
+---Registration options for a {@link DocumentLinkRequest}.
+---@class lsp.DocumentLinkRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link DocumentFormattingRequest}.
+---@class lsp.DocumentFormattingParams
+---The document to format.
+---@field textDocument lsp.TextDocumentIdentifier
+---The format options.
+---@field options lsp.FormattingOptions
+
+---Registration options for a {@link DocumentFormattingRequest}.
+---@class lsp.DocumentFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link DocumentRangeFormattingRequest}.
+---@class lsp.DocumentRangeFormattingParams
+---The document to format.
+---@field textDocument lsp.TextDocumentIdentifier
+---The range to format
+---@field range lsp.Range
+---The format options
+---@field options lsp.FormattingOptions
+
+---Registration options for a {@link DocumentRangeFormattingRequest}.
+---@class lsp.DocumentRangeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link DocumentRangesFormattingRequest}.
+---
+---@since 3.18.0
+---@proposed
+---@class lsp.DocumentRangesFormattingParams
+---The document to format.
+---@field textDocument lsp.TextDocumentIdentifier
+---The ranges to format
+---@field ranges lsp.Range[]
+---The format options
+---@field options lsp.FormattingOptions
+
+---The parameters of a {@link DocumentOnTypeFormattingRequest}.
+---@class lsp.DocumentOnTypeFormattingParams
+---The document to format.
+---@field textDocument lsp.TextDocumentIdentifier
+---The position around which the on type formatting should happen.
+---This is not necessarily the exact position where the character denoted
+---by the property `ch` got typed.
+---@field position lsp.Position
+---The character that has been typed that triggered the formatting
+---on type request. That is not necessarily the last character that
+---got inserted into the document since the client could auto insert
+---characters as well (e.g. like automatic brace completion).
+---@field ch string
+---The formatting options.
+---@field options lsp.FormattingOptions
+
+---Registration options for a {@link DocumentOnTypeFormattingRequest}.
+---@class lsp.DocumentOnTypeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---The parameters of a {@link RenameRequest}.
+---@class lsp.RenameParams
+---The document to rename.
+---@field textDocument lsp.TextDocumentIdentifier
+---The position at which this request was sent.
+---@field position lsp.Position
+---The new name of the symbol. If the given name is not valid the
+---request must return a {@link ResponseError} with an
+---appropriate message set.
+---@field newName string
+
+---Registration options for a {@link RenameRequest}.
+---@class lsp.RenameRegistrationOptions: lsp.TextDocumentRegistrationOptions
+
+---@class lsp.PrepareRenameParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+
+---The parameters of a {@link ExecuteCommandRequest}.
+---@class lsp.ExecuteCommandParams
+---The identifier of the actual command handler.
+---@field command string
+---Arguments that the command should be invoked with.
+---@field arguments? lsp.LSPAny[]
+
+---Registration options for a {@link ExecuteCommandRequest}.
+---@class lsp.ExecuteCommandRegistrationOptions: lsp.ExecuteCommandOptions
+
+---The parameters passed via an apply workspace edit request.
+---@class lsp.ApplyWorkspaceEditParams
+---An optional label of the workspace edit. This label is
+---presented in the user interface for example on an undo
+---stack to undo the workspace edit.
+---@field label? string
+---The edits to apply.
+---@field edit lsp.WorkspaceEdit
+
+---The result returned from the apply workspace edit request.
+---
+---@since 3.17 renamed from ApplyWorkspaceEditResponse
+---@class lsp.ApplyWorkspaceEditResult
+---Indicates whether the edit was applied or not.
+---@field applied boolean
+---An optional textual description for why the edit was not applied.
+---This may be used by the server for diagnostic logging or to provide
+---a suitable error for a request that triggered the edit.
+---@field failureReason? string
+---Depending on the client's failure handling strategy `failedChange` might
+---contain the index of the change that failed. This property is only available
+---if the client signals a `failureHandlingStrategy` in its client capabilities.
+---@field failedChange? uinteger
+
+---@class lsp.WorkDoneProgressBegin
+---@field kind "begin"
+---Mandatory title of the progress operation. Used to briefly inform about
+---the kind of operation being performed.
+---
+---Examples: "Indexing" or "Linking dependencies".
+---@field title string
+---Controls if a cancel button should show to allow the user to cancel the
+---long running operation. Clients that don't support cancellation are allowed
+---to ignore the setting.
+---@field cancellable? boolean
+---Optional, more detailed associated progress message. Contains
+---complementary information to the `title`.
+---
+---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
+---If unset, the previous progress message (if any) is still valid.
+---@field message? string
+---Optional progress percentage to display (value 100 is considered 100%).
+---If not provided infinite progress is assumed and clients are allowed
+---to ignore the `percentage` value in subsequent in report notifications.
+---
+---The value should be steadily rising. Clients are free to ignore values
+---that are not following this rule. The value range is [0, 100].
+---@field percentage? uinteger
+
+---@class lsp.WorkDoneProgressReport
+---@field kind "report"
+---Controls enablement state of a cancel button.
+---
+---Clients that don't support cancellation or don't support controlling the button's
+---enablement state are allowed to ignore the property.
+---@field cancellable? boolean
+---Optional, more detailed associated progress message. Contains
+---complementary information to the `title`.
+---
+---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
+---If unset, the previous progress message (if any) is still valid.
+---@field message? string
+---Optional progress percentage to display (value 100 is considered 100%).
+---If not provided infinite progress is assumed and clients are allowed
+---to ignore the `percentage` value in subsequent in report notifications.
+---
+---The value should be steadily rising. Clients are free to ignore values
+---that are not following this rule. The value range is [0, 100]
+---@field percentage? uinteger
+
+---@class lsp.WorkDoneProgressEnd
+---@field kind "end"
+---Optional, a final message indicating to for example indicate the outcome
+---of the operation.
+---@field message? string
+
+---@class lsp.SetTraceParams
+---@field value lsp.TraceValues
+
+---@class lsp.LogTraceParams
+---@field message string
+---@field verbose? string
+
+---@class lsp.CancelParams
+---The request id to cancel.
+---@field id integer|string
+
+---@class lsp.ProgressParams
+---The progress token provided by the client or server.
+---@field token lsp.ProgressToken
+---The progress data.
+---@field value lsp.LSPAny
+
+---A parameter literal used in requests to pass a text document and a position inside that
+---document.
+---@class lsp.TextDocumentPositionParams
+---The text document.
+---@field textDocument lsp.TextDocumentIdentifier
+---The position inside the text document.
+---@field position lsp.Position
+
+---@class lsp.WorkDoneProgressParams
+---An optional token that a server can use to report work done progress.
+---@field workDoneToken? lsp.ProgressToken
+
+---@class lsp.PartialResultParams
+---An optional token that a server can use to report partial results (e.g. streaming) to
+---the client.
+---@field partialResultToken? lsp.ProgressToken
+
+---Represents the connection of two locations. Provides additional metadata over normal {@link Location locations},
+---including an origin range.
+---@class lsp.LocationLink
+---Span of the origin of this link.
+---
+---Used as the underlined span for mouse interaction. Defaults to the word range at
+---the definition position.
+---@field originSelectionRange? lsp.Range
+---The target resource identifier of this link.
+---@field targetUri lsp.DocumentUri
+---The full target range of this link. If the target for example is a symbol then target range is the
+---range enclosing this symbol not including leading/trailing whitespace but everything else
+---like comments. This information is typically used to highlight the range in the editor.
+---@field targetRange lsp.Range
+---The range that should be selected and revealed when this link is being followed, e.g the name of a function.
+---Must be contained by the `targetRange`. See also `DocumentSymbol#range`
+---@field targetSelectionRange lsp.Range
+
+---A range in a text document expressed as (zero-based) start and end positions.
+---
+---If you want to specify a range that contains a line including the line ending
+---character(s) then use an end position denoting the start of the next line.
+---For example:
+---```ts
+---{
+--- start: { line: 5, character: 23 }
+--- end : { line 6, character : 0 }
+---}
+---```
+---@class lsp.Range
+---The range's start position.
+---@field start lsp.Position
+---The range's end position.
+---@field end lsp.Position
+
+---@class lsp.ImplementationOptions
+
+---Static registration options to be returned in the initialize
+---request.
+---@class lsp.StaticRegistrationOptions
+---The id used to register the request. The id can be used to deregister
+---the request again. See also Registration#id.
+---@field id? string
+
+---@class lsp.TypeDefinitionOptions
+
+---The workspace folder change event.
+---@class lsp.WorkspaceFoldersChangeEvent
+---The array of added workspace folders
+---@field added lsp.WorkspaceFolder[]
+---The array of the removed workspace folders
+---@field removed lsp.WorkspaceFolder[]
+
+---@class lsp.ConfigurationItem
+---The scope to get the configuration section for.
+---@field scopeUri? string
+---The configuration section asked for.
+---@field section? string
+
+---A literal to identify a text document in the client.
+---@class lsp.TextDocumentIdentifier
+---The text document's uri.
+---@field uri lsp.DocumentUri
+
+---Represents a color in RGBA space.
+---@class lsp.Color
+---The red component of this color in the range [0-1].
+---@field red decimal
+---The green component of this color in the range [0-1].
+---@field green decimal
+---The blue component of this color in the range [0-1].
+---@field blue decimal
+---The alpha component of this color in the range [0-1].
+---@field alpha decimal
+
+---@class lsp.DocumentColorOptions
+
+---@class lsp.FoldingRangeOptions
+
+---@class lsp.DeclarationOptions
+
+---Position in a text document expressed as zero-based line and character
+---offset. Prior to 3.17 the offsets were always based on a UTF-16 string
+---representation. So a string of the form `a𐐀b` the character offset of the
+---character `a` is 0, the character offset of `𐐀` is 1 and the character
+---offset of b is 3 since `𐐀` is represented using two code units in UTF-16.
+---Since 3.17 clients and servers can agree on a different string encoding
+---representation (e.g. UTF-8). The client announces it's supported encoding
+---via the client capability [`general.positionEncodings`](#clientCapabilities).
+---The value is an array of position encodings the client supports, with
+---decreasing preference (e.g. the encoding at index `0` is the most preferred
+---one). To stay backwards compatible the only mandatory encoding is UTF-16
+---represented via the string `utf-16`. The server can pick one of the
+---encodings offered by the client and signals that encoding back to the
+---client via the initialize result's property
+---[`capabilities.positionEncoding`](#serverCapabilities). If the string value
+---`utf-16` is missing from the client's capability `general.positionEncodings`
+---servers can safely assume that the client supports UTF-16. If the server
+---omits the position encoding in its initialize result the encoding defaults
+---to the string value `utf-16`. Implementation considerations: since the
+---conversion from one encoding into another requires the content of the
+---file / line the conversion is best done where the file is read which is
+---usually on the server side.
+---
+---Positions are line end character agnostic. So you can not specify a position
+---that denotes `\r|\n` or `\n|` where `|` represents the character offset.
+---
+---@since 3.17.0 - support for negotiated position encoding.
+---@class lsp.Position
+---Line position in a document (zero-based).
+---
+---If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.
+---If a line number is negative, it defaults to 0.
+---@field line uinteger
+---Character offset on a line in a document (zero-based).
+---
+---The meaning of this offset is determined by the negotiated
+---`PositionEncodingKind`.
+---
+---If the character value is greater than the line length it defaults back to the
+---line length.
+---@field character uinteger
+
+---@class lsp.SelectionRangeOptions
+
+---Call hierarchy options used during static registration.
+---
+---@since 3.16.0
+---@class lsp.CallHierarchyOptions
+
+---@since 3.16.0
+---@class lsp.SemanticTokensOptions
+---The legend used by the server
+---@field legend lsp.SemanticTokensLegend
+---Server supports providing semantic tokens for a specific range
+---of a document.
+---@field range? boolean|anonym6
+---Server supports providing semantic tokens for a full document.
+---@field full? boolean|anonym7
+
+---@since 3.16.0
+---@class lsp.SemanticTokensEdit
+---The start offset of the edit.
+---@field start uinteger
+---The count of elements to remove.
+---@field deleteCount uinteger
+---The elements to insert.
+---@field data? uinteger[]
+
+---@class lsp.LinkedEditingRangeOptions
+
+---Represents information on a file/folder create.
+---
+---@since 3.16.0
+---@class lsp.FileCreate
+---A file:// URI for the location of the file/folder being created.
+---@field uri string
+
+---Describes textual changes on a text document. A TextDocumentEdit describes all changes
+---on a document version Si and after they are applied move the document to version Si+1.
+---So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any
+---kind of ordering. However the edits must be non overlapping.
+---@class lsp.TextDocumentEdit
+---The text document to change.
+---@field textDocument lsp.OptionalVersionedTextDocumentIdentifier
+---The edits to be applied.
+---
+---@since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a
+---client capability.
+---@field edits lsp.TextEdit|lsp.AnnotatedTextEdit[]
+
+---Create file operation.
+---@class lsp.CreateFile: lsp.ResourceOperation
+---A create
+---@field kind "create"
+---The resource to create.
+---@field uri lsp.DocumentUri
+---Additional options
+---@field options? lsp.CreateFileOptions
+
+---Rename file operation
+---@class lsp.RenameFile: lsp.ResourceOperation
+---A rename
+---@field kind "rename"
+---The old (existing) location.
+---@field oldUri lsp.DocumentUri
+---The new location.
+---@field newUri lsp.DocumentUri
+---Rename options.
+---@field options? lsp.RenameFileOptions
+
+---Delete file operation
+---@class lsp.DeleteFile: lsp.ResourceOperation
+---A delete
+---@field kind "delete"
+---The file to delete.
+---@field uri lsp.DocumentUri
+---Delete options.
+---@field options? lsp.DeleteFileOptions
+
+---Additional information that describes document changes.
+---
+---@since 3.16.0
+---@class lsp.ChangeAnnotation
+---A human-readable string describing the actual change. The string
+---is rendered prominent in the user interface.
+---@field label string
+---A flag which indicates that user confirmation is needed
+---before applying the change.
+---@field needsConfirmation? boolean
+---A human-readable string which is rendered less prominent in
+---the user interface.
+---@field description? string
+
+---A filter to describe in which file operation requests or notifications
+---the server is interested in receiving.
+---
+---@since 3.16.0
+---@class lsp.FileOperationFilter
+---A Uri scheme like `file` or `untitled`.
+---@field scheme? string
+---The actual file operation pattern.
+---@field pattern lsp.FileOperationPattern
+
+---Represents information on a file/folder rename.
+---
+---@since 3.16.0
+---@class lsp.FileRename
+---A file:// URI for the original location of the file/folder being renamed.
+---@field oldUri string
+---A file:// URI for the new location of the file/folder being renamed.
+---@field newUri string
+
+---Represents information on a file/folder delete.
+---
+---@since 3.16.0
+---@class lsp.FileDelete
+---A file:// URI for the location of the file/folder being deleted.
+---@field uri string
+
+---@class lsp.MonikerOptions
+
+---Type hierarchy options used during static registration.
+---
+---@since 3.17.0
+---@class lsp.TypeHierarchyOptions
+
+---@since 3.17.0
+---@class lsp.InlineValueContext
+---The stack frame (as a DAP Id) where the execution has stopped.
+---@field frameId integer
+---The document range where execution has stopped.
+---Typically the end position of the range denotes the line where the inline values are shown.
+---@field stoppedLocation lsp.Range
+
+---Provide inline value as text.
+---
+---@since 3.17.0
+---@class lsp.InlineValueText
+---The document range for which the inline value applies.
+---@field range lsp.Range
+---The text of the inline value.
+---@field text string
+
+---Provide inline value through a variable lookup.
+---If only a range is specified, the variable name will be extracted from the underlying document.
+---An optional variable name can be used to override the extracted name.
+---
+---@since 3.17.0
+---@class lsp.InlineValueVariableLookup
+---The document range for which the inline value applies.
+---The range is used to extract the variable name from the underlying document.
+---@field range lsp.Range
+---If specified the name of the variable to look up.
+---@field variableName? string
+---How to perform the lookup.
+---@field caseSensitiveLookup boolean
+
+---Provide an inline value through an expression evaluation.
+---If only a range is specified, the expression will be extracted from the underlying document.
+---An optional expression can be used to override the extracted expression.
+---
+---@since 3.17.0
+---@class lsp.InlineValueEvaluatableExpression
+---The document range for which the inline value applies.
+---The range is used to extract the evaluatable expression from the underlying document.
+---@field range lsp.Range
+---If specified the expression overrides the extracted expression.
+---@field expression? string
+
+---Inline value options used during static registration.
+---
+---@since 3.17.0
+---@class lsp.InlineValueOptions
+
+---An inlay hint label part allows for interactive and composite labels
+---of inlay hints.
+---
+---@since 3.17.0
+---@class lsp.InlayHintLabelPart
+---The value of this label part.
+---@field value string
+---The tooltip text when you hover over this label part. Depending on
+---the client capability `inlayHint.resolveSupport` clients might resolve
+---this property late using the resolve request.
+---@field tooltip? string|lsp.MarkupContent
+---An optional source code location that represents this
+---label part.
+---
+---The editor will use this location for the hover and for code navigation
+---features: This part will become a clickable link that resolves to the
+---definition of the symbol at the given location (not necessarily the
+---location itself), it shows the hover that shows at the given location,
+---and it shows a context menu with further code navigation commands.
+---
+---Depending on the client capability `inlayHint.resolveSupport` clients
+---might resolve this property late using the resolve request.
+---@field location? lsp.Location
+---An optional command for this label part.
+---
+---Depending on the client capability `inlayHint.resolveSupport` clients
+---might resolve this property late using the resolve request.
+---@field command? lsp.Command
+
+---A `MarkupContent` literal represents a string value which content is interpreted base on its
+---kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds.
+---
+---If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues.
+---See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
+---
+---Here is an example how such a string can be constructed using JavaScript / TypeScript:
+---```ts
+---let markdown: MarkdownContent = {
+--- kind: MarkupKind.Markdown,
+--- value: [
+--- '# Header',
+--- 'Some text',
+--- '```typescript',
+--- 'someCode();',
+--- '```'
+--- ].join('\n')
+---};
+---```
+---
+---*Please Note* that clients might sanitize the return markdown. A client could decide to
+---remove HTML from the markdown to avoid script execution.
+---@class lsp.MarkupContent
+---The type of the Markup
+---@field kind lsp.MarkupKind
+---The content itself
+---@field value string
+
+---Inlay hint options used during static registration.
+---
+---@since 3.17.0
+---@class lsp.InlayHintOptions
+---The server provides support to resolve additional
+---information for an inlay hint item.
+---@field resolveProvider? boolean
+
+---A full diagnostic report with a set of related documents.
+---
+---@since 3.17.0
+---@class lsp.RelatedFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport
+---Diagnostics of related documents. This information is useful
+---in programming languages where code in a file A can generate
+---diagnostics in a file B which A depends on. An example of
+---such a language is C/C++ where marco definitions in a file
+---a.cpp and result in errors in a header file b.hpp.
+---
+---@since 3.17.0
+---@field relatedDocuments? table<lsp.DocumentUri, lsp.FullDocumentDiagnosticReport|lsp.UnchangedDocumentDiagnosticReport>
+
+---An unchanged diagnostic report with a set of related documents.
+---
+---@since 3.17.0
+---@class lsp.RelatedUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport
+---Diagnostics of related documents. This information is useful
+---in programming languages where code in a file A can generate
+---diagnostics in a file B which A depends on. An example of
+---such a language is C/C++ where marco definitions in a file
+---a.cpp and result in errors in a header file b.hpp.
+---
+---@since 3.17.0
+---@field relatedDocuments? table<lsp.DocumentUri, lsp.FullDocumentDiagnosticReport|lsp.UnchangedDocumentDiagnosticReport>
+
+---A diagnostic report with a full set of problems.
+---
+---@since 3.17.0
+---@class lsp.FullDocumentDiagnosticReport
+---A full document diagnostic report.
+---@field kind "full"
+---An optional result id. If provided it will
+---be sent on the next diagnostic request for the
+---same document.
+---@field resultId? string
+---The actual items.
+---@field items lsp.Diagnostic[]
+
+---A diagnostic report indicating that the last returned
+---report is still accurate.
+---
+---@since 3.17.0
+---@class lsp.UnchangedDocumentDiagnosticReport
+---A document diagnostic report indicating
+---no changes to the last result. A server can
+---only return `unchanged` if result ids are
+---provided.
+---@field kind "unchanged"
+---A result id which will be sent on the next
+---diagnostic request for the same document.
+---@field resultId string
+
+---Diagnostic options.
+---
+---@since 3.17.0
+---@class lsp.DiagnosticOptions
+---An optional identifier under which the diagnostics are
+---managed by the client.
+---@field identifier? string
+---Whether the language has inter file dependencies meaning that
+---editing code in one file can result in a different diagnostic
+---set in another file. Inter file dependencies are common for
+---most programming languages and typically uncommon for linters.
+---@field interFileDependencies boolean
+---The server provides support for workspace diagnostics as well.
+---@field workspaceDiagnostics boolean
+
+---A previous result id in a workspace pull request.
+---
+---@since 3.17.0
+---@class lsp.PreviousResultId
+---The URI for which the client knowns a
+---result id.
+---@field uri lsp.DocumentUri
+---The value of the previous result id.
+---@field value string
+
+---A notebook document.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocument
+---The notebook document's uri.
+---@field uri lsp.URI
+---The type of the notebook.
+---@field notebookType string
+---The version number of this document (it will increase after each
+---change, including undo/redo).
+---@field version integer
+---Additional metadata stored with the notebook
+---document.
+---
+---Note: should always be an object literal (e.g. LSPObject)
+---@field metadata? lsp.LSPObject
+---The cells of a notebook.
+---@field cells lsp.NotebookCell[]
+
+---An item to transfer a text document from the client to the
+---server.
+---@class lsp.TextDocumentItem
+---The text document's uri.
+---@field uri lsp.DocumentUri
+---The text document's language identifier.
+---@field languageId string
+---The version number of this document (it will increase after each
+---change, including undo/redo).
+---@field version integer
+---The content of the opened text document.
+---@field text string
+
+---A versioned notebook document identifier.
+---
+---@since 3.17.0
+---@class lsp.VersionedNotebookDocumentIdentifier
+---The version number of this notebook document.
+---@field version integer
+---The notebook document's uri.
+---@field uri lsp.URI
+
+---A change event for a notebook document.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentChangeEvent
+---The changed meta data if any.
+---
+---Note: should always be an object literal (e.g. LSPObject)
+---@field metadata? lsp.LSPObject
+---Changes to cells
+---@field cells? anonym10
+
+---A literal to identify a notebook document in the client.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentIdentifier
+---The notebook document's uri.
+---@field uri lsp.URI
+
+---Provides information about the context in which an inline completion was requested.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionContext
+---Describes how the inline completion was triggered.
+---@field triggerKind lsp.InlineCompletionTriggerKind
+---Provides information about the currently selected item in the autocomplete widget if it is visible.
+---@field selectedCompletionInfo? lsp.SelectedCompletionInfo
+
+---Inline completion options used during static registration.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionOptions
+
+---General parameters to to register for an notification or to register a provider.
+---@class lsp.Registration
+---The id used to register the request. The id can be used to deregister
+---the request again.
+---@field id string
+---The method / capability to register for.
+---@field method string
+---Options necessary for the registration.
+---@field registerOptions? lsp.LSPAny
+
+---General parameters to unregister a request or notification.
+---@class lsp.Unregistration
+---The id used to unregister the request or notification. Usually an id
+---provided during the register request.
+---@field id string
+---The method to unregister for.
+---@field method string
+
+---The initialize parameters
+---@class lsp._InitializeParams
+---The process Id of the parent process that started
+---the server.
+---
+---Is `null` if the process has not been started by another process.
+---If the parent process is not alive then the server should exit.
+---@field processId integer|lsp.null
+---Information about the client
+---
+---@since 3.15.0
+---@field clientInfo? anonym11
+---The locale the client is currently showing the user interface
+---in. This must not necessarily be the locale of the operating
+---system.
+---
+---Uses IETF language tags as the value's syntax
+---(See https://en.wikipedia.org/wiki/IETF_language_tag)
+---
+---@since 3.16.0
+---@field locale? string
+---The rootPath of the workspace. Is null
+---if no folder is open.
+---
+---@deprecated in favour of rootUri.
+---@field rootPath? string|lsp.null
+---The rootUri of the workspace. Is null if no
+---folder is open. If both `rootPath` and `rootUri` are set
+---`rootUri` wins.
+---
+---@deprecated in favour of workspaceFolders.
+---@field rootUri lsp.DocumentUri|lsp.null
+---The capabilities provided by the client (editor or tool)
+---@field capabilities lsp.ClientCapabilities
+---User provided initialization options.
+---@field initializationOptions? lsp.LSPAny
+---The initial trace setting. If omitted trace is disabled ('off').
+---@field trace? lsp.TraceValues
+
+---@class lsp.WorkspaceFoldersInitializeParams
+---The workspace folders configured in the client when the server starts.
+---
+---This property is only available if the client supports workspace folders.
+---It can be `null` if the client supports workspace folders but none are
+---configured.
+---
+---@since 3.6.0
+---@field workspaceFolders? lsp.WorkspaceFolder[]|lsp.null
+
+---Defines the capabilities provided by a language
+---server.
+---@class lsp.ServerCapabilities
+---The position encoding the server picked from the encodings offered
+---by the client via the client capability `general.positionEncodings`.
+---
+---If the client didn't provide any position encodings the only valid
+---value that a server can return is 'utf-16'.
+---
+---If omitted it defaults to 'utf-16'.
+---
+---@since 3.17.0
+---@field positionEncoding? lsp.PositionEncodingKind
+---Defines how text documents are synced. Is either a detailed structure
+---defining each notification or for backwards compatibility the
+---TextDocumentSyncKind number.
+---@field textDocumentSync? lsp.TextDocumentSyncOptions|lsp.TextDocumentSyncKind
+---Defines how notebook documents are synced.
+---
+---@since 3.17.0
+---@field notebookDocumentSync? lsp.NotebookDocumentSyncOptions|lsp.NotebookDocumentSyncRegistrationOptions
+---The server provides completion support.
+---@field completionProvider? lsp.CompletionOptions
+---The server provides hover support.
+---@field hoverProvider? boolean|lsp.HoverOptions
+---The server provides signature help support.
+---@field signatureHelpProvider? lsp.SignatureHelpOptions
+---The server provides Goto Declaration support.
+---@field declarationProvider? boolean|lsp.DeclarationOptions|lsp.DeclarationRegistrationOptions
+---The server provides goto definition support.
+---@field definitionProvider? boolean|lsp.DefinitionOptions
+---The server provides Goto Type Definition support.
+---@field typeDefinitionProvider? boolean|lsp.TypeDefinitionOptions|lsp.TypeDefinitionRegistrationOptions
+---The server provides Goto Implementation support.
+---@field implementationProvider? boolean|lsp.ImplementationOptions|lsp.ImplementationRegistrationOptions
+---The server provides find references support.
+---@field referencesProvider? boolean|lsp.ReferenceOptions
+---The server provides document highlight support.
+---@field documentHighlightProvider? boolean|lsp.DocumentHighlightOptions
+---The server provides document symbol support.
+---@field documentSymbolProvider? boolean|lsp.DocumentSymbolOptions
+---The server provides code actions. CodeActionOptions may only be
+---specified if the client states that it supports
+---`codeActionLiteralSupport` in its initial `initialize` request.
+---@field codeActionProvider? boolean|lsp.CodeActionOptions
+---The server provides code lens.
+---@field codeLensProvider? lsp.CodeLensOptions
+---The server provides document link support.
+---@field documentLinkProvider? lsp.DocumentLinkOptions
+---The server provides color provider support.
+---@field colorProvider? boolean|lsp.DocumentColorOptions|lsp.DocumentColorRegistrationOptions
+---The server provides workspace symbol support.
+---@field workspaceSymbolProvider? boolean|lsp.WorkspaceSymbolOptions
+---The server provides document formatting.
+---@field documentFormattingProvider? boolean|lsp.DocumentFormattingOptions
+---The server provides document range formatting.
+---@field documentRangeFormattingProvider? boolean|lsp.DocumentRangeFormattingOptions
+---The server provides document formatting on typing.
+---@field documentOnTypeFormattingProvider? lsp.DocumentOnTypeFormattingOptions
+---The server provides rename support. RenameOptions may only be
+---specified if the client states that it supports
+---`prepareSupport` in its initial `initialize` request.
+---@field renameProvider? boolean|lsp.RenameOptions
+---The server provides folding provider support.
+---@field foldingRangeProvider? boolean|lsp.FoldingRangeOptions|lsp.FoldingRangeRegistrationOptions
+---The server provides selection range support.
+---@field selectionRangeProvider? boolean|lsp.SelectionRangeOptions|lsp.SelectionRangeRegistrationOptions
+---The server provides execute command support.
+---@field executeCommandProvider? lsp.ExecuteCommandOptions
+---The server provides call hierarchy support.
+---
+---@since 3.16.0
+---@field callHierarchyProvider? boolean|lsp.CallHierarchyOptions|lsp.CallHierarchyRegistrationOptions
+---The server provides linked editing range support.
+---
+---@since 3.16.0
+---@field linkedEditingRangeProvider? boolean|lsp.LinkedEditingRangeOptions|lsp.LinkedEditingRangeRegistrationOptions
+---The server provides semantic tokens support.
+---
+---@since 3.16.0
+---@field semanticTokensProvider? lsp.SemanticTokensOptions|lsp.SemanticTokensRegistrationOptions
+---The server provides moniker support.
+---
+---@since 3.16.0
+---@field monikerProvider? boolean|lsp.MonikerOptions|lsp.MonikerRegistrationOptions
+---The server provides type hierarchy support.
+---
+---@since 3.17.0
+---@field typeHierarchyProvider? boolean|lsp.TypeHierarchyOptions|lsp.TypeHierarchyRegistrationOptions
+---The server provides inline values.
+---
+---@since 3.17.0
+---@field inlineValueProvider? boolean|lsp.InlineValueOptions|lsp.InlineValueRegistrationOptions
+---The server provides inlay hints.
+---
+---@since 3.17.0
+---@field inlayHintProvider? boolean|lsp.InlayHintOptions|lsp.InlayHintRegistrationOptions
+---The server has support for pull model diagnostics.
+---
+---@since 3.17.0
+---@field diagnosticProvider? lsp.DiagnosticOptions|lsp.DiagnosticRegistrationOptions
+---Inline completion options used during static registration.
+---
+---@since 3.18.0
+---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions
+---Workspace specific server capabilities.
+---@field workspace? anonym12
+---Experimental server capabilities.
+---@field experimental? lsp.LSPAny
+
+---A text document identifier to denote a specific version of a text document.
+---@class lsp.VersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier
+---The version number of this document.
+---@field version integer
+
+---Save options.
+---@class lsp.SaveOptions
+---The client is supposed to include the content on save.
+---@field includeText? boolean
+
+---An event describing a file change.
+---@class lsp.FileEvent
+---The file's uri.
+---@field uri lsp.DocumentUri
+---The change type.
+---@field type lsp.FileChangeType
+
+---@class lsp.FileSystemWatcher
+---The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail.
+---
+---@since 3.17.0 support for relative patterns.
+---@field globPattern lsp.GlobPattern
+---The kind of events of interest. If omitted it defaults
+---to WatchKind.Create | WatchKind.Change | WatchKind.Delete
+---which is 7.
+---@field kind? lsp.WatchKind
+
+---Represents a diagnostic, such as a compiler error or warning. Diagnostic objects
+---are only valid in the scope of a resource.
+---@class lsp.Diagnostic
+---The range at which the message applies
+---@field range lsp.Range
+---The diagnostic's severity. Can be omitted. If omitted it is up to the
+---client to interpret diagnostics as error, warning, info or hint.
+---@field severity? lsp.DiagnosticSeverity
+---The diagnostic's code, which usually appear in the user interface.
+---@field code? integer|string
+---An optional property to describe the error code.
+---Requires the code field (above) to be present/not null.
+---
+---@since 3.16.0
+---@field codeDescription? lsp.CodeDescription
+---A human-readable string describing the source of this
+---diagnostic, e.g. 'typescript' or 'super lint'. It usually
+---appears in the user interface.
+---@field source? string
+---The diagnostic's message. It usually appears in the user interface
+---@field message string
+---Additional metadata about the diagnostic.
+---
+---@since 3.15.0
+---@field tags? lsp.DiagnosticTag[]
+---An array of related diagnostic information, e.g. when symbol-names within
+---a scope collide all definitions can be marked via this property.
+---@field relatedInformation? lsp.DiagnosticRelatedInformation[]
+---A data entry field that is preserved between a `textDocument/publishDiagnostics`
+---notification and `textDocument/codeAction` request.
+---
+---@since 3.16.0
+---@field data? lsp.LSPAny
+
+---Contains additional information about the context in which a completion request is triggered.
+---@class lsp.CompletionContext
+---How the completion was triggered.
+---@field triggerKind lsp.CompletionTriggerKind
+---The trigger character (a single character) that has trigger code complete.
+---Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
+---@field triggerCharacter? string
+
+---Additional details for a completion item label.
+---
+---@since 3.17.0
+---@class lsp.CompletionItemLabelDetails
+---An optional string which is rendered less prominently directly after {@link CompletionItem.label label},
+---without any spacing. Should be used for function signatures and type annotations.
+---@field detail? string
+---An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used
+---for fully qualified names and file paths.
+---@field description? string
+
+---A special text edit to provide an insert and a replace operation.
+---
+---@since 3.16.0
+---@class lsp.InsertReplaceEdit
+---The string to be inserted.
+---@field newText string
+---The range if the insert is requested
+---@field insert lsp.Range
+---The range if the replace is requested.
+---@field replace lsp.Range
+
+---Completion options.
+---@class lsp.CompletionOptions
+---Most tools trigger completion request automatically without explicitly requesting
+---it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user
+---starts to type an identifier. For example if the user types `c` in a JavaScript file
+---code complete will automatically pop up present `console` besides others as a
+---completion item. Characters that make up identifiers don't need to be listed here.
+---
+---If code complete should automatically be trigger on characters not being valid inside
+---an identifier (for example `.` in JavaScript) list them in `triggerCharacters`.
+---@field triggerCharacters? string[]
+---The list of all possible characters that commit a completion. This field can be used
+---if clients don't support individual commit characters per completion item. See
+---`ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport`
+---
+---If a server provides both `allCommitCharacters` and commit characters on an individual
+---completion item the ones on the completion item win.
+---
+---@since 3.2.0
+---@field allCommitCharacters? string[]
+---The server provides support to resolve additional
+---information for a completion item.
+---@field resolveProvider? boolean
+---The server supports the following `CompletionItem` specific
+---capabilities.
+---
+---@since 3.17.0
+---@field completionItem? anonym13
+
+---Hover options.
+---@class lsp.HoverOptions
+
+---Additional information about the context in which a signature help request was triggered.
+---
+---@since 3.15.0
+---@class lsp.SignatureHelpContext
+---Action that caused signature help to be triggered.
+---@field triggerKind lsp.SignatureHelpTriggerKind
+---Character that caused signature help to be triggered.
+---
+---This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter`
+---@field triggerCharacter? string
+---`true` if signature help was already showing when it was triggered.
+---
+---Retriggers occurs when the signature help is already active and can be caused by actions such as
+---typing a trigger character, a cursor move, or document content changes.
+---@field isRetrigger boolean
+---The currently active `SignatureHelp`.
+---
+---The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on
+---the user navigating through available signatures.
+---@field activeSignatureHelp? lsp.SignatureHelp
+
+---Represents the signature of something callable. A signature
+---can have a label, like a function-name, a doc-comment, and
+---a set of parameters.
+---@class lsp.SignatureInformation
+---The label of this signature. Will be shown in
+---the UI.
+---@field label string
+---The human-readable doc-comment of this signature. Will be shown
+---in the UI but can be omitted.
+---@field documentation? string|lsp.MarkupContent
+---The parameters of this signature.
+---@field parameters? lsp.ParameterInformation[]
+---The index of the active parameter.
+---
+---If provided, this is used in place of `SignatureHelp.activeParameter`.
+---
+---@since 3.16.0
+---@field activeParameter? uinteger
+
+---Server Capabilities for a {@link SignatureHelpRequest}.
+---@class lsp.SignatureHelpOptions
+---List of characters that trigger signature help automatically.
+---@field triggerCharacters? string[]
+---List of characters that re-trigger signature help.
+---
+---These trigger characters are only active when signature help is already showing. All trigger characters
+---are also counted as re-trigger characters.
+---
+---@since 3.15.0
+---@field retriggerCharacters? string[]
+
+---Server Capabilities for a {@link DefinitionRequest}.
+---@class lsp.DefinitionOptions
+
+---Value-object that contains additional information when
+---requesting references.
+---@class lsp.ReferenceContext
+---Include the declaration of the current symbol.
+---@field includeDeclaration boolean
+
+---Reference options.
+---@class lsp.ReferenceOptions
+
+---Provider options for a {@link DocumentHighlightRequest}.
+---@class lsp.DocumentHighlightOptions
+
+---A base for all symbol information.
+---@class lsp.BaseSymbolInformation
+---The name of this symbol.
+---@field name string
+---The kind of this symbol.
+---@field kind lsp.SymbolKind
+---Tags for this symbol.
+---
+---@since 3.16.0
+---@field tags? lsp.SymbolTag[]
+---The name of the symbol containing this symbol. This information is for
+---user interface purposes (e.g. to render a qualifier in the user interface
+---if necessary). It can't be used to re-infer a hierarchy for the document
+---symbols.
+---@field containerName? string
+
+---Provider options for a {@link DocumentSymbolRequest}.
+---@class lsp.DocumentSymbolOptions
+---A human-readable string that is shown when multiple outlines trees
+---are shown for the same document.
+---
+---@since 3.16.0
+---@field label? string
+
+---Contains additional diagnostic information about the context in which
+---a {@link CodeActionProvider.provideCodeActions code action} is run.
+---@class lsp.CodeActionContext
+---An array of diagnostics known on the client side overlapping the range provided to the
+---`textDocument/codeAction` request. They are provided so that the server knows which
+---errors are currently presented to the user for the given range. There is no guarantee
+---that these accurately reflect the error state of the resource. The primary parameter
+---to compute code actions is the provided range.
+---@field diagnostics lsp.Diagnostic[]
+---Requested kind of actions to return.
+---
+---Actions not of this kind are filtered out by the client before being shown. So servers
+---can omit computing them.
+---@field only? lsp.CodeActionKind[]
+---The reason why code actions were requested.
+---
+---@since 3.17.0
+---@field triggerKind? lsp.CodeActionTriggerKind
+
+---Provider options for a {@link CodeActionRequest}.
+---@class lsp.CodeActionOptions
+---CodeActionKinds that this server may return.
+---
+---The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server
+---may list out every specific kind they provide.
+---@field codeActionKinds? lsp.CodeActionKind[]
+---The server provides support to resolve additional
+---information for a code action.
+---
+---@since 3.16.0
+---@field resolveProvider? boolean
+
+---Server capabilities for a {@link WorkspaceSymbolRequest}.
+---@class lsp.WorkspaceSymbolOptions
+---The server provides support to resolve additional
+---information for a workspace symbol.
+---
+---@since 3.17.0
+---@field resolveProvider? boolean
+
+---Code Lens provider options of a {@link CodeLensRequest}.
+---@class lsp.CodeLensOptions
+---Code lens has a resolve provider as well.
+---@field resolveProvider? boolean
+
+---Provider options for a {@link DocumentLinkRequest}.
+---@class lsp.DocumentLinkOptions
+---Document links have a resolve provider as well.
+---@field resolveProvider? boolean
+
+---Value-object describing what options formatting should use.
+---@class lsp.FormattingOptions
+---Size of a tab in spaces.
+---@field tabSize uinteger
+---Prefer spaces over tabs.
+---@field insertSpaces boolean
+---Trim trailing whitespace on a line.
+---
+---@since 3.15.0
+---@field trimTrailingWhitespace? boolean
+---Insert a newline character at the end of the file if one does not exist.
+---
+---@since 3.15.0
+---@field insertFinalNewline? boolean
+---Trim all newlines after the final newline at the end of the file.
+---
+---@since 3.15.0
+---@field trimFinalNewlines? boolean
+
+---Provider options for a {@link DocumentFormattingRequest}.
+---@class lsp.DocumentFormattingOptions
+
+---Provider options for a {@link DocumentRangeFormattingRequest}.
+---@class lsp.DocumentRangeFormattingOptions
+---Whether the server supports formatting multiple ranges at once.
+---
+---@since 3.18.0
+---@proposed
+---@field rangesSupport? boolean
+
+---Provider options for a {@link DocumentOnTypeFormattingRequest}.
+---@class lsp.DocumentOnTypeFormattingOptions
+---A character on which formatting should be triggered, like `{`.
+---@field firstTriggerCharacter string
+---More trigger characters.
+---@field moreTriggerCharacter? string[]
+
+---Provider options for a {@link RenameRequest}.
+---@class lsp.RenameOptions
+---Renames should be checked and tested before being executed.
+---
+---@since version 3.12.0
+---@field prepareProvider? boolean
+
+---The server capabilities of a {@link ExecuteCommandRequest}.
+---@class lsp.ExecuteCommandOptions
+---The commands to be executed on the server
+---@field commands string[]
+
+---@since 3.16.0
+---@class lsp.SemanticTokensLegend
+---The token types a server uses.
+---@field tokenTypes string[]
+---The token modifiers a server uses.
+---@field tokenModifiers string[]
+
+---A text document identifier to optionally denote a specific version of a text document.
+---@class lsp.OptionalVersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier
+---The version number of this document. If a versioned text document identifier
+---is sent from the server to the client and the file is not open in the editor
+---(the server has not received an open notification before) the server can send
+---`null` to indicate that the version is unknown and the content on disk is the
+---truth (as specified with document content ownership).
+---@field version integer|lsp.null
+
+---A special text edit with an additional change annotation.
+---
+---@since 3.16.0.
+---@class lsp.AnnotatedTextEdit: lsp.TextEdit
+---The actual identifier of the change annotation
+---@field annotationId lsp.ChangeAnnotationIdentifier
+
+---A generic resource operation.
+---@class lsp.ResourceOperation
+---The resource operation kind.
+---@field kind string
+---An optional annotation identifier describing the operation.
+---
+---@since 3.16.0
+---@field annotationId? lsp.ChangeAnnotationIdentifier
+
+---Options to create a file.
+---@class lsp.CreateFileOptions
+---Overwrite existing file. Overwrite wins over `ignoreIfExists`
+---@field overwrite? boolean
+---Ignore if exists.
+---@field ignoreIfExists? boolean
+
+---Rename file options
+---@class lsp.RenameFileOptions
+---Overwrite target if existing. Overwrite wins over `ignoreIfExists`
+---@field overwrite? boolean
+---Ignores if target exists.
+---@field ignoreIfExists? boolean
+
+---Delete file options
+---@class lsp.DeleteFileOptions
+---Delete the content recursively if a folder is denoted.
+---@field recursive? boolean
+---Ignore the operation if the file doesn't exist.
+---@field ignoreIfNotExists? boolean
+
+---A pattern to describe in which file operation requests or notifications
+---the server is interested in receiving.
+---
+---@since 3.16.0
+---@class lsp.FileOperationPattern
+---The glob pattern to match. Glob patterns can have the following syntax:
+---- `*` to match one or more characters in a path segment
+---- `?` to match on one character in a path segment
+---- `**` to match any number of path segments, including none
+---- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
+---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
+---@field glob string
+---Whether to match files or folders with this pattern.
+---
+---Matches both if undefined.
+---@field matches? lsp.FileOperationPatternKind
+---Additional options used during matching.
+---@field options? lsp.FileOperationPatternOptions
+
+---A full document diagnostic report for a workspace diagnostic result.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport
+---The URI for which diagnostic information is reported.
+---@field uri lsp.DocumentUri
+---The version number for which the diagnostics are reported.
+---If the document is not marked as open `null` can be provided.
+---@field version integer|lsp.null
+
+---An unchanged document diagnostic report for a workspace diagnostic result.
+---
+---@since 3.17.0
+---@class lsp.WorkspaceUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport
+---The URI for which diagnostic information is reported.
+---@field uri lsp.DocumentUri
+---The version number for which the diagnostics are reported.
+---If the document is not marked as open `null` can be provided.
+---@field version integer|lsp.null
+
+---A notebook cell.
+---
+---A cell's document URI must be unique across ALL notebook
+---cells and can therefore be used to uniquely identify a
+---notebook cell or the cell's text document.
+---
+---@since 3.17.0
+---@class lsp.NotebookCell
+---The cell's kind
+---@field kind lsp.NotebookCellKind
+---The URI of the cell's text document
+---content.
+---@field document lsp.DocumentUri
+---Additional metadata stored with the cell.
+---
+---Note: should always be an object literal (e.g. LSPObject)
+---@field metadata? lsp.LSPObject
+---Additional execution summary information
+---if supported by the client.
+---@field executionSummary? lsp.ExecutionSummary
+
+---A change describing how to move a `NotebookCell`
+---array from state S to S'.
+---
+---@since 3.17.0
+---@class lsp.NotebookCellArrayChange
+---The start oftest of the cell that changed.
+---@field start uinteger
+---The deleted cells
+---@field deleteCount uinteger
+---The new cells, if any
+---@field cells? lsp.NotebookCell[]
+
+---Describes the currently selected completion item.
+---
+---@since 3.18.0
+---@class lsp.SelectedCompletionInfo
+---The range that will be replaced if this completion item is accepted.
+---@field range lsp.Range
+---The text the range will be replaced with if this completion is accepted.
+---@field text string
+
+---Defines the capabilities provided by the client.
+---@class lsp.ClientCapabilities
+---Workspace specific client capabilities.
+---@field workspace? lsp.WorkspaceClientCapabilities
+---Text document specific client capabilities.
+---@field textDocument? lsp.TextDocumentClientCapabilities
+---Capabilities specific to the notebook document support.
+---
+---@since 3.17.0
+---@field notebookDocument? lsp.NotebookDocumentClientCapabilities
+---Window specific client capabilities.
+---@field window? lsp.WindowClientCapabilities
+---General client capabilities.
+---
+---@since 3.16.0
+---@field general? lsp.GeneralClientCapabilities
+---Experimental client capabilities.
+---@field experimental? lsp.LSPAny
+
+---@class lsp.TextDocumentSyncOptions
+---Open and close notifications are sent to the server. If omitted open close notification should not
+---be sent.
+---@field openClose? boolean
+---Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
+---and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.
+---@field change? lsp.TextDocumentSyncKind
+---If present will save notifications are sent to the server. If omitted the notification should not be
+---sent.
+---@field willSave? boolean
+---If present will save wait until requests are sent to the server. If omitted the request should not be
+---sent.
+---@field willSaveWaitUntil? boolean
+---If present save notifications are sent to the server. If omitted the notification should not be
+---sent.
+---@field save? boolean|lsp.SaveOptions
+
+---Options specific to a notebook plus its cells
+---to be synced to the server.
+---
+---If a selector provides a notebook document
+---filter but no cell selector all cells of a
+---matching notebook document will be synced.
+---
+---If a selector provides no notebook document
+---filter but only a cell selector all notebook
+---document that contain at least one matching
+---cell will be synced.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentSyncOptions
+---The notebooks to be synced
+---@field notebookSelector anonym15|anonym17[]
+---Whether save notification should be forwarded to
+---the server. Will only be honored if mode === `notebook`.
+---@field save? boolean
+
+---Registration options specific to a notebook.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentSyncRegistrationOptions: lsp.NotebookDocumentSyncOptions, lsp.StaticRegistrationOptions
+
+---@class lsp.WorkspaceFoldersServerCapabilities
+---The server has support for workspace folders
+---@field supported? boolean
+---Whether the server wants to receive workspace folder
+---change notifications.
+---
+---If a string is provided the string is treated as an ID
+---under which the notification is registered on the client
+---side. The ID can be used to unregister for these events
+---using the `client/unregisterCapability` request.
+---@field changeNotifications? string|boolean
+
+---Options for notifications/requests for user operations on files.
+---
+---@since 3.16.0
+---@class lsp.FileOperationOptions
+---The server is interested in receiving didCreateFiles notifications.
+---@field didCreate? lsp.FileOperationRegistrationOptions
+---The server is interested in receiving willCreateFiles requests.
+---@field willCreate? lsp.FileOperationRegistrationOptions
+---The server is interested in receiving didRenameFiles notifications.
+---@field didRename? lsp.FileOperationRegistrationOptions
+---The server is interested in receiving willRenameFiles requests.
+---@field willRename? lsp.FileOperationRegistrationOptions
+---The server is interested in receiving didDeleteFiles file notifications.
+---@field didDelete? lsp.FileOperationRegistrationOptions
+---The server is interested in receiving willDeleteFiles file requests.
+---@field willDelete? lsp.FileOperationRegistrationOptions
+
+---Structure to capture a description for an error code.
+---
+---@since 3.16.0
+---@class lsp.CodeDescription
+---An URI to open with more information about the diagnostic error.
+---@field href lsp.URI
+
+---Represents a related message and source code location for a diagnostic. This should be
+---used to point to code locations that cause or related to a diagnostics, e.g when duplicating
+---a symbol in a scope.
+---@class lsp.DiagnosticRelatedInformation
+---The location of this related diagnostic information.
+---@field location lsp.Location
+---The message of this related diagnostic information.
+---@field message string
+
+---Represents a parameter of a callable-signature. A parameter can
+---have a label and a doc-comment.
+---@class lsp.ParameterInformation
+---The label of this parameter information.
+---
+---Either a string or an inclusive start and exclusive end offsets within its containing
+---signature label. (see SignatureInformation.label). The offsets are based on a UTF-16
+---string representation as `Position` and `Range` does.
+---
+---*Note*: a label of type string should be a substring of its containing signature label.
+---Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
+---@field label string|{ [1]: uinteger, [2]: uinteger }
+---The human-readable doc-comment of this parameter. Will be shown
+---in the UI but can be omitted.
+---@field documentation? string|lsp.MarkupContent
+
+---A notebook cell text document filter denotes a cell text
+---document by different properties.
+---
+---@since 3.17.0
+---@class lsp.NotebookCellTextDocumentFilter
+---A filter that matches against the notebook
+---containing the notebook cell. If a string
+---value is provided it matches against the
+---notebook type. '*' matches every notebook.
+---@field notebook string|lsp.NotebookDocumentFilter
+---A language id like `python`.
+---
+---Will be matched against the language id of the
+---notebook cell document. '*' matches every language.
+---@field language? string
+
+---Matching options for the file operation pattern.
+---
+---@since 3.16.0
+---@class lsp.FileOperationPatternOptions
+---The pattern should be matched ignoring casing.
+---@field ignoreCase? boolean
+
+---@class lsp.ExecutionSummary
+---A strict monotonically increasing value
+---indicating the execution order of a cell
+---inside a notebook.
+---@field executionOrder uinteger
+---Whether the execution was successful or
+---not if known by the client.
+---@field success? boolean
+
+---Workspace specific client capabilities.
+---@class lsp.WorkspaceClientCapabilities
+---The client supports applying batch edits
+---to the workspace by supporting the request
+---'workspace/applyEdit'
+---@field applyEdit? boolean
+---Capabilities specific to `WorkspaceEdit`s.
+---@field workspaceEdit? lsp.WorkspaceEditClientCapabilities
+---Capabilities specific to the `workspace/didChangeConfiguration` notification.
+---@field didChangeConfiguration? lsp.DidChangeConfigurationClientCapabilities
+---Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
+---@field didChangeWatchedFiles? lsp.DidChangeWatchedFilesClientCapabilities
+---Capabilities specific to the `workspace/symbol` request.
+---@field symbol? lsp.WorkspaceSymbolClientCapabilities
+---Capabilities specific to the `workspace/executeCommand` request.
+---@field executeCommand? lsp.ExecuteCommandClientCapabilities
+---The client has support for workspace folders.
+---
+---@since 3.6.0
+---@field workspaceFolders? boolean
+---The client supports `workspace/configuration` requests.
+---
+---@since 3.6.0
+---@field configuration? boolean
+---Capabilities specific to the semantic token requests scoped to the
+---workspace.
+---
+---@since 3.16.0.
+---@field semanticTokens? lsp.SemanticTokensWorkspaceClientCapabilities
+---Capabilities specific to the code lens requests scoped to the
+---workspace.
+---
+---@since 3.16.0.
+---@field codeLens? lsp.CodeLensWorkspaceClientCapabilities
+---The client has support for file notifications/requests for user operations on files.
+---
+---Since 3.16.0
+---@field fileOperations? lsp.FileOperationClientCapabilities
+---Capabilities specific to the inline values requests scoped to the
+---workspace.
+---
+---@since 3.17.0.
+---@field inlineValue? lsp.InlineValueWorkspaceClientCapabilities
+---Capabilities specific to the inlay hint requests scoped to the
+---workspace.
+---
+---@since 3.17.0.
+---@field inlayHint? lsp.InlayHintWorkspaceClientCapabilities
+---Capabilities specific to the diagnostic requests scoped to the
+---workspace.
+---
+---@since 3.17.0.
+---@field diagnostics? lsp.DiagnosticWorkspaceClientCapabilities
+
+---Text document specific client capabilities.
+---@class lsp.TextDocumentClientCapabilities
+---Defines which synchronization capabilities the client supports.
+---@field synchronization? lsp.TextDocumentSyncClientCapabilities
+---Capabilities specific to the `textDocument/completion` request.
+---@field completion? lsp.CompletionClientCapabilities
+---Capabilities specific to the `textDocument/hover` request.
+---@field hover? lsp.HoverClientCapabilities
+---Capabilities specific to the `textDocument/signatureHelp` request.
+---@field signatureHelp? lsp.SignatureHelpClientCapabilities
+---Capabilities specific to the `textDocument/declaration` request.
+---
+---@since 3.14.0
+---@field declaration? lsp.DeclarationClientCapabilities
+---Capabilities specific to the `textDocument/definition` request.
+---@field definition? lsp.DefinitionClientCapabilities
+---Capabilities specific to the `textDocument/typeDefinition` request.
+---
+---@since 3.6.0
+---@field typeDefinition? lsp.TypeDefinitionClientCapabilities
+---Capabilities specific to the `textDocument/implementation` request.
+---
+---@since 3.6.0
+---@field implementation? lsp.ImplementationClientCapabilities
+---Capabilities specific to the `textDocument/references` request.
+---@field references? lsp.ReferenceClientCapabilities
+---Capabilities specific to the `textDocument/documentHighlight` request.
+---@field documentHighlight? lsp.DocumentHighlightClientCapabilities
+---Capabilities specific to the `textDocument/documentSymbol` request.
+---@field documentSymbol? lsp.DocumentSymbolClientCapabilities
+---Capabilities specific to the `textDocument/codeAction` request.
+---@field codeAction? lsp.CodeActionClientCapabilities
+---Capabilities specific to the `textDocument/codeLens` request.
+---@field codeLens? lsp.CodeLensClientCapabilities
+---Capabilities specific to the `textDocument/documentLink` request.
+---@field documentLink? lsp.DocumentLinkClientCapabilities
+---Capabilities specific to the `textDocument/documentColor` and the
+---`textDocument/colorPresentation` request.
+---
+---@since 3.6.0
+---@field colorProvider? lsp.DocumentColorClientCapabilities
+---Capabilities specific to the `textDocument/formatting` request.
+---@field formatting? lsp.DocumentFormattingClientCapabilities
+---Capabilities specific to the `textDocument/rangeFormatting` request.
+---@field rangeFormatting? lsp.DocumentRangeFormattingClientCapabilities
+---Capabilities specific to the `textDocument/onTypeFormatting` request.
+---@field onTypeFormatting? lsp.DocumentOnTypeFormattingClientCapabilities
+---Capabilities specific to the `textDocument/rename` request.
+---@field rename? lsp.RenameClientCapabilities
+---Capabilities specific to the `textDocument/foldingRange` request.
+---
+---@since 3.10.0
+---@field foldingRange? lsp.FoldingRangeClientCapabilities
+---Capabilities specific to the `textDocument/selectionRange` request.
+---
+---@since 3.15.0
+---@field selectionRange? lsp.SelectionRangeClientCapabilities
+---Capabilities specific to the `textDocument/publishDiagnostics` notification.
+---@field publishDiagnostics? lsp.PublishDiagnosticsClientCapabilities
+---Capabilities specific to the various call hierarchy requests.
+---
+---@since 3.16.0
+---@field callHierarchy? lsp.CallHierarchyClientCapabilities
+---Capabilities specific to the various semantic token request.
+---
+---@since 3.16.0
+---@field semanticTokens? lsp.SemanticTokensClientCapabilities
+---Capabilities specific to the `textDocument/linkedEditingRange` request.
+---
+---@since 3.16.0
+---@field linkedEditingRange? lsp.LinkedEditingRangeClientCapabilities
+---Client capabilities specific to the `textDocument/moniker` request.
+---
+---@since 3.16.0
+---@field moniker? lsp.MonikerClientCapabilities
+---Capabilities specific to the various type hierarchy requests.
+---
+---@since 3.17.0
+---@field typeHierarchy? lsp.TypeHierarchyClientCapabilities
+---Capabilities specific to the `textDocument/inlineValue` request.
+---
+---@since 3.17.0
+---@field inlineValue? lsp.InlineValueClientCapabilities
+---Capabilities specific to the `textDocument/inlayHint` request.
+---
+---@since 3.17.0
+---@field inlayHint? lsp.InlayHintClientCapabilities
+---Capabilities specific to the diagnostic pull model.
+---
+---@since 3.17.0
+---@field diagnostic? lsp.DiagnosticClientCapabilities
+---Client capabilities specific to inline completions.
+---
+---@since 3.18.0
+---@field inlineCompletion? lsp.InlineCompletionClientCapabilities
+
+---Capabilities specific to the notebook document support.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentClientCapabilities
+---Capabilities specific to notebook document synchronization
+---
+---@since 3.17.0
+---@field synchronization lsp.NotebookDocumentSyncClientCapabilities
+
+---@class lsp.WindowClientCapabilities
+---It indicates whether the client supports server initiated
+---progress using the `window/workDoneProgress/create` request.
+---
+---The capability also controls Whether client supports handling
+---of progress notifications. If set servers are allowed to report a
+---`workDoneProgress` property in the request specific server
+---capabilities.
+---
+---@since 3.15.0
+---@field workDoneProgress? boolean
+---Capabilities specific to the showMessage request.
+---
+---@since 3.16.0
+---@field showMessage? lsp.ShowMessageRequestClientCapabilities
+---Capabilities specific to the showDocument request.
+---
+---@since 3.16.0
+---@field showDocument? lsp.ShowDocumentClientCapabilities
+
+---General client capabilities.
+---
+---@since 3.16.0
+---@class lsp.GeneralClientCapabilities
+---Client capability that signals how the client
+---handles stale requests (e.g. a request
+---for which the client will not process the response
+---anymore since the information is outdated).
+---
+---@since 3.17.0
+---@field staleRequestSupport? anonym18
+---Client capabilities specific to regular expressions.
+---
+---@since 3.16.0
+---@field regularExpressions? lsp.RegularExpressionsClientCapabilities
+---Client capabilities specific to the client's markdown parser.
+---
+---@since 3.16.0
+---@field markdown? lsp.MarkdownClientCapabilities
+---The position encodings supported by the client. Client and server
+---have to agree on the same position encoding to ensure that offsets
+---(e.g. character position in a line) are interpreted the same on both
+---sides.
+---
+---To keep the protocol backwards compatible the following applies: if
+---the value 'utf-16' is missing from the array of position encodings
+---servers can assume that the client supports UTF-16. UTF-16 is
+---therefore a mandatory encoding.
+---
+---If omitted it defaults to ['utf-16'].
+---
+---Implementation considerations: since the conversion from one encoding
+---into another requires the content of the file / line the conversion
+---is best done where the file is read which is usually on the server
+---side.
+---
+---@since 3.17.0
+---@field positionEncodings? lsp.PositionEncodingKind[]
+
+---A relative pattern is a helper to construct glob patterns that are matched
+---relatively to a base URI. The common value for a `baseUri` is a workspace
+---folder root, but it can be another absolute URI as well.
+---
+---@since 3.17.0
+---@class lsp.RelativePattern
+---A workspace folder or a base URI to which this pattern will be matched
+---against relatively.
+---@field baseUri lsp.WorkspaceFolder|lsp.URI
+---The actual glob pattern;
+---@field pattern lsp.Pattern
+
+---@class lsp.WorkspaceEditClientCapabilities
+---The client supports versioned document changes in `WorkspaceEdit`s
+---@field documentChanges? boolean
+---The resource operations the client supports. Clients should at least
+---support 'create', 'rename' and 'delete' files and folders.
+---
+---@since 3.13.0
+---@field resourceOperations? lsp.ResourceOperationKind[]
+---The failure handling strategy of a client if applying the workspace edit
+---fails.
+---
+---@since 3.13.0
+---@field failureHandling? lsp.FailureHandlingKind
+---Whether the client normalizes line endings to the client specific
+---setting.
+---If set to `true` the client will normalize line ending characters
+---in a workspace edit to the client-specified new line
+---character.
+---
+---@since 3.16.0
+---@field normalizesLineEndings? boolean
+---Whether the client in general supports change annotations on text edits,
+---create file, rename file and delete file changes.
+---
+---@since 3.16.0
+---@field changeAnnotationSupport? anonym19
+
+---@class lsp.DidChangeConfigurationClientCapabilities
+---Did change configuration notification supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---@class lsp.DidChangeWatchedFilesClientCapabilities
+---Did change watched files notification supports dynamic registration. Please note
+---that the current protocol doesn't support static configuration for file changes
+---from the server side.
+---@field dynamicRegistration? boolean
+---Whether the client has support for {@link RelativePattern relative pattern}
+---or not.
+---
+---@since 3.17.0
+---@field relativePatternSupport? boolean
+
+---Client capabilities for a {@link WorkspaceSymbolRequest}.
+---@class lsp.WorkspaceSymbolClientCapabilities
+---Symbol request supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
+---@field symbolKind? anonym20
+---The client supports tags on `SymbolInformation`.
+---Clients supporting tags have to handle unknown tags gracefully.
+---
+---@since 3.16.0
+---@field tagSupport? anonym21
+---The client support partial workspace symbols. The client will send the
+---request `workspaceSymbol/resolve` to the server to resolve additional
+---properties.
+---
+---@since 3.17.0
+---@field resolveSupport? anonym22
+
+---The client capabilities of a {@link ExecuteCommandRequest}.
+---@class lsp.ExecuteCommandClientCapabilities
+---Execute command supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---@since 3.16.0
+---@class lsp.SemanticTokensWorkspaceClientCapabilities
+---Whether the client implementation supports a refresh request sent from
+---the server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---semantic tokens currently shown. It should be used with absolute care
+---and is useful for situation where a server for example detects a project
+---wide change that requires such a calculation.
+---@field refreshSupport? boolean
+
+---@since 3.16.0
+---@class lsp.CodeLensWorkspaceClientCapabilities
+---Whether the client implementation supports a refresh request sent from the
+---server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---code lenses currently shown. It should be used with absolute care and is
+---useful for situation where a server for example detect a project wide
+---change that requires such a calculation.
+---@field refreshSupport? boolean
+
+---Capabilities relating to events from file operations by the user in the client.
+---
+---These events do not come from the file system, they come from user operations
+---like renaming a file in the UI.
+---
+---@since 3.16.0
+---@class lsp.FileOperationClientCapabilities
+---Whether the client supports dynamic registration for file requests/notifications.
+---@field dynamicRegistration? boolean
+---The client has support for sending didCreateFiles notifications.
+---@field didCreate? boolean
+---The client has support for sending willCreateFiles requests.
+---@field willCreate? boolean
+---The client has support for sending didRenameFiles notifications.
+---@field didRename? boolean
+---The client has support for sending willRenameFiles requests.
+---@field willRename? boolean
+---The client has support for sending didDeleteFiles notifications.
+---@field didDelete? boolean
+---The client has support for sending willDeleteFiles requests.
+---@field willDelete? boolean
+
+---Client workspace capabilities specific to inline values.
+---
+---@since 3.17.0
+---@class lsp.InlineValueWorkspaceClientCapabilities
+---Whether the client implementation supports a refresh request sent from the
+---server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---inline values currently shown. It should be used with absolute care and is
+---useful for situation where a server for example detects a project wide
+---change that requires such a calculation.
+---@field refreshSupport? boolean
+
+---Client workspace capabilities specific to inlay hints.
+---
+---@since 3.17.0
+---@class lsp.InlayHintWorkspaceClientCapabilities
+---Whether the client implementation supports a refresh request sent from
+---the server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---inlay hints currently shown. It should be used with absolute care and
+---is useful for situation where a server for example detects a project wide
+---change that requires such a calculation.
+---@field refreshSupport? boolean
+
+---Workspace client capabilities specific to diagnostic pull requests.
+---
+---@since 3.17.0
+---@class lsp.DiagnosticWorkspaceClientCapabilities
+---Whether the client implementation supports a refresh request sent from
+---the server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---pulled diagnostics currently shown. It should be used with absolute care and
+---is useful for situation where a server for example detects a project wide
+---change that requires such a calculation.
+---@field refreshSupport? boolean
+
+---@class lsp.TextDocumentSyncClientCapabilities
+---Whether text document synchronization supports dynamic registration.
+---@field dynamicRegistration? boolean
+---The client supports sending will save notifications.
+---@field willSave? boolean
+---The client supports sending a will save request and
+---waits for a response providing text edits which will
+---be applied to the document before it is saved.
+---@field willSaveWaitUntil? boolean
+---The client supports did save notifications.
+---@field didSave? boolean
+
+---Completion client capabilities
+---@class lsp.CompletionClientCapabilities
+---Whether completion supports dynamic registration.
+---@field dynamicRegistration? boolean
+---The client supports the following `CompletionItem` specific
+---capabilities.
+---@field completionItem? anonym26
+---@field completionItemKind? anonym27
+---Defines how the client handles whitespace and indentation
+---when accepting a completion item that uses multi line
+---text in either `insertText` or `textEdit`.
+---
+---@since 3.17.0
+---@field insertTextMode? lsp.InsertTextMode
+---The client supports to send additional context information for a
+---`textDocument/completion` request.
+---@field contextSupport? boolean
+---The client supports the following `CompletionList` specific
+---capabilities.
+---
+---@since 3.17.0
+---@field completionList? anonym28
+
+---@class lsp.HoverClientCapabilities
+---Whether hover supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Client supports the following content formats for the content
+---property. The order describes the preferred format of the client.
+---@field contentFormat? lsp.MarkupKind[]
+
+---Client Capabilities for a {@link SignatureHelpRequest}.
+---@class lsp.SignatureHelpClientCapabilities
+---Whether signature help supports dynamic registration.
+---@field dynamicRegistration? boolean
+---The client supports the following `SignatureInformation`
+---specific properties.
+---@field signatureInformation? anonym30
+---The client supports to send additional context information for a
+---`textDocument/signatureHelp` request. A client that opts into
+---contextSupport will also support the `retriggerCharacters` on
+---`SignatureHelpOptions`.
+---
+---@since 3.15.0
+---@field contextSupport? boolean
+
+---@since 3.14.0
+---@class lsp.DeclarationClientCapabilities
+---Whether declaration supports dynamic registration. If this is set to `true`
+---the client supports the new `DeclarationRegistrationOptions` return value
+---for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---The client supports additional metadata in the form of declaration links.
+---@field linkSupport? boolean
+
+---Client Capabilities for a {@link DefinitionRequest}.
+---@class lsp.DefinitionClientCapabilities
+---Whether definition supports dynamic registration.
+---@field dynamicRegistration? boolean
+---The client supports additional metadata in the form of definition links.
+---
+---@since 3.14.0
+---@field linkSupport? boolean
+
+---Since 3.6.0
+---@class lsp.TypeDefinitionClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `TypeDefinitionRegistrationOptions` return value
+---for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---The client supports additional metadata in the form of definition links.
+---
+---Since 3.14.0
+---@field linkSupport? boolean
+
+---@since 3.6.0
+---@class lsp.ImplementationClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `ImplementationRegistrationOptions` return value
+---for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---The client supports additional metadata in the form of definition links.
+---
+---@since 3.14.0
+---@field linkSupport? boolean
+
+---Client Capabilities for a {@link ReferencesRequest}.
+---@class lsp.ReferenceClientCapabilities
+---Whether references supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---Client Capabilities for a {@link DocumentHighlightRequest}.
+---@class lsp.DocumentHighlightClientCapabilities
+---Whether document highlight supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---Client Capabilities for a {@link DocumentSymbolRequest}.
+---@class lsp.DocumentSymbolClientCapabilities
+---Whether document symbol supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Specific capabilities for the `SymbolKind` in the
+---`textDocument/documentSymbol` request.
+---@field symbolKind? anonym31
+---The client supports hierarchical document symbols.
+---@field hierarchicalDocumentSymbolSupport? boolean
+---The client supports tags on `SymbolInformation`. Tags are supported on
+---`DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
+---Clients supporting tags have to handle unknown tags gracefully.
+---
+---@since 3.16.0
+---@field tagSupport? anonym32
+---The client supports an additional label presented in the UI when
+---registering a document symbol provider.
+---
+---@since 3.16.0
+---@field labelSupport? boolean
+
+---The Client Capabilities of a {@link CodeActionRequest}.
+---@class lsp.CodeActionClientCapabilities
+---Whether code action supports dynamic registration.
+---@field dynamicRegistration? boolean
+---The client support code action literals of type `CodeAction` as a valid
+---response of the `textDocument/codeAction` request. If the property is not
+---set the request can only return `Command` literals.
+---
+---@since 3.8.0
+---@field codeActionLiteralSupport? anonym34
+---Whether code action supports the `isPreferred` property.
+---
+---@since 3.15.0
+---@field isPreferredSupport? boolean
+---Whether code action supports the `disabled` property.
+---
+---@since 3.16.0
+---@field disabledSupport? boolean
+---Whether code action supports the `data` property which is
+---preserved between a `textDocument/codeAction` and a
+---`codeAction/resolve` request.
+---
+---@since 3.16.0
+---@field dataSupport? boolean
+---Whether the client supports resolving additional code action
+---properties via a separate `codeAction/resolve` request.
+---
+---@since 3.16.0
+---@field resolveSupport? anonym35
+---Whether the client honors the change annotations in
+---text edits and resource operations returned via the
+---`CodeAction#edit` property by for example presenting
+---the workspace edit in the user interface and asking
+---for confirmation.
+---
+---@since 3.16.0
+---@field honorsChangeAnnotations? boolean
+
+---The client capabilities of a {@link CodeLensRequest}.
+---@class lsp.CodeLensClientCapabilities
+---Whether code lens supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---The client capabilities of a {@link DocumentLinkRequest}.
+---@class lsp.DocumentLinkClientCapabilities
+---Whether document link supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Whether the client supports the `tooltip` property on `DocumentLink`.
+---
+---@since 3.15.0
+---@field tooltipSupport? boolean
+
+---@class lsp.DocumentColorClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `DocumentColorRegistrationOptions` return value
+---for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+
+---Client capabilities of a {@link DocumentFormattingRequest}.
+---@class lsp.DocumentFormattingClientCapabilities
+---Whether formatting supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---Client capabilities of a {@link DocumentRangeFormattingRequest}.
+---@class lsp.DocumentRangeFormattingClientCapabilities
+---Whether range formatting supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Whether the client supports formatting multiple ranges at once.
+---
+---@since 3.18.0
+---@proposed
+---@field rangesSupport? boolean
+
+---Client capabilities of a {@link DocumentOnTypeFormattingRequest}.
+---@class lsp.DocumentOnTypeFormattingClientCapabilities
+---Whether on type formatting supports dynamic registration.
+---@field dynamicRegistration? boolean
+
+---@class lsp.RenameClientCapabilities
+---Whether rename supports dynamic registration.
+---@field dynamicRegistration? boolean
+---Client supports testing for validity of rename operations
+---before execution.
+---
+---@since 3.12.0
+---@field prepareSupport? boolean
+---Client supports the default behavior result.
+---
+---The value indicates the default behavior used by the
+---client.
+---
+---@since 3.16.0
+---@field prepareSupportDefaultBehavior? lsp.PrepareSupportDefaultBehavior
+---Whether the client honors the change annotations in
+---text edits and resource operations returned via the
+---rename request's workspace edit by for example presenting
+---the workspace edit in the user interface and asking
+---for confirmation.
+---
+---@since 3.16.0
+---@field honorsChangeAnnotations? boolean
+
+---@class lsp.FoldingRangeClientCapabilities
+---Whether implementation supports dynamic registration for folding range
+---providers. If this is set to `true` the client supports the new
+---`FoldingRangeRegistrationOptions` return value for the corresponding
+---server capability as well.
+---@field dynamicRegistration? boolean
+---The maximum number of folding ranges that the client prefers to receive
+---per document. The value serves as a hint, servers are free to follow the
+---limit.
+---@field rangeLimit? uinteger
+---If set, the client signals that it only supports folding complete lines.
+---If set, client will ignore specified `startCharacter` and `endCharacter`
+---properties in a FoldingRange.
+---@field lineFoldingOnly? boolean
+---Specific options for the folding range kind.
+---
+---@since 3.17.0
+---@field foldingRangeKind? anonym36
+---Specific options for the folding range.
+---
+---@since 3.17.0
+---@field foldingRange? anonym37
+
+---@class lsp.SelectionRangeClientCapabilities
+---Whether implementation supports dynamic registration for selection range providers. If this is set to `true`
+---the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server
+---capability as well.
+---@field dynamicRegistration? boolean
+
+---The publish diagnostic client capabilities.
+---@class lsp.PublishDiagnosticsClientCapabilities
+---Whether the clients accepts diagnostics with related information.
+---@field relatedInformation? boolean
+---Client supports the tag property to provide meta data about a diagnostic.
+---Clients supporting tags have to handle unknown tags gracefully.
+---
+---@since 3.15.0
+---@field tagSupport? anonym38
+---Whether the client interprets the version property of the
+---`textDocument/publishDiagnostics` notification's parameter.
+---
+---@since 3.15.0
+---@field versionSupport? boolean
+---Client supports a codeDescription property
+---
+---@since 3.16.0
+---@field codeDescriptionSupport? boolean
+---Whether code action supports the `data` property which is
+---preserved between a `textDocument/publishDiagnostics` and
+---`textDocument/codeAction` request.
+---
+---@since 3.16.0
+---@field dataSupport? boolean
+
+---@since 3.16.0
+---@class lsp.CallHierarchyClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+
+---@since 3.16.0
+---@class lsp.SemanticTokensClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---Which requests the client supports and might send to the server
+---depending on the server's capability. Please note that clients might not
+---show semantic tokens or degrade some of the user experience if a range
+---or full request is advertised by the client but not provided by the
+---server. If for example the client capability `requests.full` and
+---`request.range` are both set to true but the server only provides a
+---range provider the client might not render a minimap correctly or might
+---even decide to not show any semantic tokens at all.
+---@field requests anonym41
+---The token types that the client supports.
+---@field tokenTypes string[]
+---The token modifiers that the client supports.
+---@field tokenModifiers string[]
+---The token formats the clients supports.
+---@field formats lsp.TokenFormat[]
+---Whether the client supports tokens that can overlap each other.
+---@field overlappingTokenSupport? boolean
+---Whether the client supports tokens that can span multiple lines.
+---@field multilineTokenSupport? boolean
+---Whether the client allows the server to actively cancel a
+---semantic token request, e.g. supports returning
+---LSPErrorCodes.ServerCancelled. If a server does the client
+---needs to retrigger the request.
+---
+---@since 3.17.0
+---@field serverCancelSupport? boolean
+---Whether the client uses semantic tokens to augment existing
+---syntax tokens. If set to `true` client side created syntax
+---tokens and semantic tokens are both used for colorization. If
+---set to `false` the client only uses the returned semantic tokens
+---for colorization.
+---
+---If the value is `undefined` then the client behavior is not
+---specified.
+---
+---@since 3.17.0
+---@field augmentsSyntaxTokens? boolean
+
+---Client capabilities for the linked editing range request.
+---
+---@since 3.16.0
+---@class lsp.LinkedEditingRangeClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+
+---Client capabilities specific to the moniker request.
+---
+---@since 3.16.0
+---@class lsp.MonikerClientCapabilities
+---Whether moniker supports dynamic registration. If this is set to `true`
+---the client supports the new `MonikerRegistrationOptions` return value
+---for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+
+---@since 3.17.0
+---@class lsp.TypeHierarchyClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+
+---Client capabilities specific to inline values.
+---
+---@since 3.17.0
+---@class lsp.InlineValueClientCapabilities
+---Whether implementation supports dynamic registration for inline value providers.
+---@field dynamicRegistration? boolean
+
+---Inlay hint client capabilities.
+---
+---@since 3.17.0
+---@class lsp.InlayHintClientCapabilities
+---Whether inlay hints support dynamic registration.
+---@field dynamicRegistration? boolean
+---Indicates which properties a client can resolve lazily on an inlay
+---hint.
+---@field resolveSupport? anonym42
+
+---Client capabilities specific to diagnostic pull requests.
+---
+---@since 3.17.0
+---@class lsp.DiagnosticClientCapabilities
+---Whether implementation supports dynamic registration. If this is set to `true`
+---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---Whether the clients supports related documents for document diagnostic pulls.
+---@field relatedDocumentSupport? boolean
+
+---Client capabilities specific to inline completions.
+---
+---@since 3.18.0
+---@class lsp.InlineCompletionClientCapabilities
+---Whether implementation supports dynamic registration for inline completion providers.
+---@field dynamicRegistration? boolean
+
+---Notebook specific client capabilities.
+---
+---@since 3.17.0
+---@class lsp.NotebookDocumentSyncClientCapabilities
+---Whether implementation supports dynamic registration. If this is
+---set to `true` the client supports the new
+---`(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
+---return value for the corresponding server capability as well.
+---@field dynamicRegistration? boolean
+---The client supports sending execution summary data per cell.
+---@field executionSummarySupport? boolean
+
+---Show message request client capabilities
+---@class lsp.ShowMessageRequestClientCapabilities
+---Capabilities specific to the `MessageActionItem` type.
+---@field messageActionItem? anonym43
+
+---Client capabilities for the showDocument request.
+---
+---@since 3.16.0
+---@class lsp.ShowDocumentClientCapabilities
+---The client has support for the showDocument
+---request.
+---@field support boolean
+
+---Client capabilities specific to regular expressions.
+---
+---@since 3.16.0
+---@class lsp.RegularExpressionsClientCapabilities
+---The engine's name.
+---@field engine string
+---The engine's version.
+---@field version? string
+
+---Client capabilities specific to the used markdown parser.
+---
+---@since 3.16.0
+---@class lsp.MarkdownClientCapabilities
+---The name of the parser.
+---@field parser string
+---The version of the parser.
+---@field version? string
+---A list of HTML tags that the client allows / supports in
+---Markdown.
+---
+---@since 3.17.0
+---@field allowedTags? string[]
+
+---A set of predefined token types. This set is not fixed
+---an clients can specify additional token types via the
+---corresponding client capabilities.
+---
+---@since 3.16.0
+---@alias lsp.SemanticTokenTypes
+---| "namespace" # namespace
+---| "type" # type
+---| "class" # class
+---| "enum" # enum
+---| "interface" # interface
+---| "struct" # struct
+---| "typeParameter" # typeParameter
+---| "parameter" # parameter
+---| "variable" # variable
+---| "property" # property
+---| "enumMember" # enumMember
+---| "event" # event
+---| "function" # function
+---| "method" # method
+---| "macro" # macro
+---| "keyword" # keyword
+---| "modifier" # modifier
+---| "comment" # comment
+---| "string" # string
+---| "number" # number
+---| "regexp" # regexp
+---| "operator" # operator
+---| "decorator" # decorator
+
+---A set of predefined token modifiers. This set is not fixed
+---an clients can specify additional token types via the
+---corresponding client capabilities.
+---
+---@since 3.16.0
+---@alias lsp.SemanticTokenModifiers
+---| "declaration" # declaration
+---| "definition" # definition
+---| "readonly" # readonly
+---| "static" # static
+---| "deprecated" # deprecated
+---| "abstract" # abstract
+---| "async" # async
+---| "modification" # modification
+---| "documentation" # documentation
+---| "defaultLibrary" # defaultLibrary
+
+---The document diagnostic report kinds.
+---
+---@since 3.17.0
+---@alias lsp.DocumentDiagnosticReportKind
+---| "full" # Full
+---| "unchanged" # Unchanged
+
+---Predefined error codes.
+---@alias lsp.ErrorCodes
+---| -32700 # ParseError
+---| -32600 # InvalidRequest
+---| -32601 # MethodNotFound
+---| -32602 # InvalidParams
+---| -32603 # InternalError
+---| -32002 # ServerNotInitialized
+---| -32001 # UnknownErrorCode
+
+---@alias lsp.LSPErrorCodes
+---| -32803 # RequestFailed
+---| -32802 # ServerCancelled
+---| -32801 # ContentModified
+---| -32800 # RequestCancelled
+
+---A set of predefined range kinds.
+---@alias lsp.FoldingRangeKind
+---| "comment" # Comment
+---| "imports" # Imports
+---| "region" # Region
+
+---A symbol kind.
+---@alias lsp.SymbolKind
+---| 1 # File
+---| 2 # Module
+---| 3 # Namespace
+---| 4 # Package
+---| 5 # Class
+---| 6 # Method
+---| 7 # Property
+---| 8 # Field
+---| 9 # Constructor
+---| 10 # Enum
+---| 11 # Interface
+---| 12 # Function
+---| 13 # Variable
+---| 14 # Constant
+---| 15 # String
+---| 16 # Number
+---| 17 # Boolean
+---| 18 # Array
+---| 19 # Object
+---| 20 # Key
+---| 21 # Null
+---| 22 # EnumMember
+---| 23 # Struct
+---| 24 # Event
+---| 25 # Operator
+---| 26 # TypeParameter
+
+---Symbol tags are extra annotations that tweak the rendering of a symbol.
+---
+---@since 3.16
+---@alias lsp.SymbolTag
+---| 1 # Deprecated
+
+---Moniker uniqueness level to define scope of the moniker.
+---
+---@since 3.16.0
+---@alias lsp.UniquenessLevel
+---| "document" # document
+---| "project" # project
+---| "group" # group
+---| "scheme" # scheme
+---| "global" # global
+
+---The moniker kind.
+---
+---@since 3.16.0
+---@alias lsp.MonikerKind
+---| "import" # import
+---| "export" # export
+---| "local" # local
+
+---Inlay hint kinds.
+---
+---@since 3.17.0
+---@alias lsp.InlayHintKind
+---| 1 # Type
+---| 2 # Parameter
+
+---Defines whether the insert text in a completion item should be interpreted as
+---plain text or a snippet.
+---@alias lsp.InsertTextFormat
+---| 1 # PlainText
+---| 2 # Snippet
+
+---The message type
+---@alias lsp.MessageType
+---| 1 # Error
+---| 2 # Warning
+---| 3 # Info
+---| 4 # Log
+
+---Defines how the host (editor) should sync
+---document changes to the language server.
+---@alias lsp.TextDocumentSyncKind
+---| 0 # None
+---| 1 # Full
+---| 2 # Incremental
+
+---Represents reasons why a text document is saved.
+---@alias lsp.TextDocumentSaveReason
+---| 1 # Manual
+---| 2 # AfterDelay
+---| 3 # FocusOut
+
+---The kind of a completion entry.
+---@alias lsp.CompletionItemKind
+---| 1 # Text
+---| 2 # Method
+---| 3 # Function
+---| 4 # Constructor
+---| 5 # Field
+---| 6 # Variable
+---| 7 # Class
+---| 8 # Interface
+---| 9 # Module
+---| 10 # Property
+---| 11 # Unit
+---| 12 # Value
+---| 13 # Enum
+---| 14 # Keyword
+---| 15 # Snippet
+---| 16 # Color
+---| 17 # File
+---| 18 # Reference
+---| 19 # Folder
+---| 20 # EnumMember
+---| 21 # Constant
+---| 22 # Struct
+---| 23 # Event
+---| 24 # Operator
+---| 25 # TypeParameter
+
+---Completion item tags are extra annotations that tweak the rendering of a completion
+---item.
+---
+---@since 3.15.0
+---@alias lsp.CompletionItemTag
+---| 1 # Deprecated
+
+---How whitespace and indentation is handled during completion
+---item insertion.
+---
+---@since 3.16.0
+---@alias lsp.InsertTextMode
+---| 1 # asIs
+---| 2 # adjustIndentation
+
+---A document highlight kind.
+---@alias lsp.DocumentHighlightKind
+---| 1 # Text
+---| 2 # Read
+---| 3 # Write
+
+---A set of predefined code action kinds
+---@alias lsp.CodeActionKind
+---| "" # Empty
+---| "quickfix" # QuickFix
+---| "refactor" # Refactor
+---| "refactor.extract" # RefactorExtract
+---| "refactor.inline" # RefactorInline
+---| "refactor.rewrite" # RefactorRewrite
+---| "source" # Source
+---| "source.organizeImports" # SourceOrganizeImports
+---| "source.fixAll" # SourceFixAll
+
+---@alias lsp.TraceValues
+---| "off" # Off
+---| "messages" # Messages
+---| "verbose" # Verbose
+
+---Describes the content type that a client supports in various
+---result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
+---
+---Please note that `MarkupKinds` must not start with a `$`. This kinds
+---are reserved for internal usage.
+---@alias lsp.MarkupKind
+---| "plaintext" # PlainText
+---| "markdown" # Markdown
+
+---Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered.
+---
+---@since 3.18.0
+---@alias lsp.InlineCompletionTriggerKind
+---| 0 # Invoked
+---| 1 # Automatic
+
+---A set of predefined position encoding kinds.
+---
+---@since 3.17.0
+---@alias lsp.PositionEncodingKind
+---| "utf-8" # UTF8
+---| "utf-16" # UTF16
+---| "utf-32" # UTF32
+
+---The file event type
+---@alias lsp.FileChangeType
+---| 1 # Created
+---| 2 # Changed
+---| 3 # Deleted
+
+---@alias lsp.WatchKind
+---| 1 # Create
+---| 2 # Change
+---| 4 # Delete
+
+---The diagnostic's severity.
+---@alias lsp.DiagnosticSeverity
+---| 1 # Error
+---| 2 # Warning
+---| 3 # Information
+---| 4 # Hint
+
+---The diagnostic tags.
+---
+---@since 3.15.0
+---@alias lsp.DiagnosticTag
+---| 1 # Unnecessary
+---| 2 # Deprecated
+
+---How a completion was triggered
+---@alias lsp.CompletionTriggerKind
+---| 1 # Invoked
+---| 2 # TriggerCharacter
+---| 3 # TriggerForIncompleteCompletions
+
+---How a signature help was triggered.
+---
+---@since 3.15.0
+---@alias lsp.SignatureHelpTriggerKind
+---| 1 # Invoked
+---| 2 # TriggerCharacter
+---| 3 # ContentChange
+
+---The reason why code actions were requested.
+---
+---@since 3.17.0
+---@alias lsp.CodeActionTriggerKind
+---| 1 # Invoked
+---| 2 # Automatic
+
+---A pattern kind describing if a glob pattern matches a file a folder or
+---both.
+---
+---@since 3.16.0
+---@alias lsp.FileOperationPatternKind
+---| "file" # file
+---| "folder" # folder
+
+---A notebook cell kind.
+---
+---@since 3.17.0
+---@alias lsp.NotebookCellKind
+---| 1 # Markup
+---| 2 # Code
+
+---@alias lsp.ResourceOperationKind
+---| "create" # Create
+---| "rename" # Rename
+---| "delete" # Delete
+
+---@alias lsp.FailureHandlingKind
+---| "abort" # Abort
+---| "transactional" # Transactional
+---| "textOnlyTransactional" # TextOnlyTransactional
+---| "undo" # Undo
+
+---@alias lsp.PrepareSupportDefaultBehavior
+---| 1 # Identifier
+
+---@alias lsp.TokenFormat
+---| "relative" # Relative
+
+---The definition of a symbol represented as one or many {@link Location locations}.
+---For most programming languages there is only one location at which a symbol is
+---defined.
+---
+---Servers should prefer returning `DefinitionLink` over `Definition` if supported
+---by the client.
+---@alias lsp.Definition lsp.Location|lsp.Location[]
+
+---Information about where a symbol is defined.
+---
+---Provides additional metadata over normal {@link Location location} definitions, including the range of
+---the defining symbol
+---@alias lsp.DefinitionLink lsp.LocationLink
+
+---LSP arrays.
+---@since 3.17.0
+---@alias lsp.LSPArray lsp.LSPAny[]
+
+---The LSP any type.
+---Please note that strictly speaking a property with the value `undefined`
+---can't be converted into JSON preserving the property name. However for
+---convenience it is allowed and assumed that all these properties are
+---optional as well.
+---@since 3.17.0
+---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|integer|uinteger|decimal|boolean|lsp.null
+
+---The declaration of a symbol representation as one or many {@link Location locations}.
+---@alias lsp.Declaration lsp.Location|lsp.Location[]
+
+---Information about where a symbol is declared.
+---
+---Provides additional metadata over normal {@link Location location} declarations, including the range of
+---the declaring symbol.
+---
+---Servers should prefer returning `DeclarationLink` over `Declaration` if supported
+---by the client.
+---@alias lsp.DeclarationLink lsp.LocationLink
+
+---Inline value information can be provided by different means:
+---- directly as a text value (class InlineValueText).
+---- as a name to use for a variable lookup (class InlineValueVariableLookup)
+---- as an evaluatable expression (class InlineValueEvaluatableExpression)
+---The InlineValue types combines all inline value types into one type.
+---
+---@since 3.17.0
+---@alias lsp.InlineValue lsp.InlineValueText|lsp.InlineValueVariableLookup|lsp.InlineValueEvaluatableExpression
+
+---The result of a document diagnostic pull request. A report can
+---either be a full report containing all diagnostics for the
+---requested document or an unchanged report indicating that nothing
+---has changed in terms of diagnostics in comparison to the last
+---pull request.
+---
+---@since 3.17.0
+---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport
+
+---@alias lsp.PrepareRenameResult lsp.Range|anonym44|anonym45
+
+---A document selector is the combination of one or many document filters.
+---
+---@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`;
+---
+---The use of a string as a document filter is deprecated @since 3.16.0.
+---@alias lsp.DocumentSelector lsp.DocumentFilter[]
+
+---@alias lsp.ProgressToken integer|string
+
+---An identifier to refer to a change annotation stored with a workspace edit.
+---@alias lsp.ChangeAnnotationIdentifier string
+
+---A workspace diagnostic document report.
+---
+---@since 3.17.0
+---@alias lsp.WorkspaceDocumentDiagnosticReport lsp.WorkspaceFullDocumentDiagnosticReport|lsp.WorkspaceUnchangedDocumentDiagnosticReport
+
+---An event describing a change to a text document. If only a text is provided
+---it is considered to be the full content of the document.
+---@alias lsp.TextDocumentContentChangeEvent anonym46|anonym47
+
+---MarkedString can be used to render human readable text. It is either a markdown string
+---or a code-block that provides a language and a code snippet. The language identifier
+---is semantically equal to the optional language identifier in fenced code blocks in GitHub
+---issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting
+---
+---The pair of a language and a value is an equivalent to markdown:
+---```${language}
+---${value}
+---```
+---
+---Note that markdown strings will be sanitized - that means html will be escaped.
+---@deprecated use MarkupContent instead.
+---@alias lsp.MarkedString string|anonym48
+
+---A document filter describes a top level text document or
+---a notebook cell document.
+---
+---@since 3.17.0 - proposed support for NotebookCellTextDocumentFilter.
+---@alias lsp.DocumentFilter lsp.TextDocumentFilter|lsp.NotebookCellTextDocumentFilter
+
+---LSP object definition.
+---@since 3.17.0
+---@alias lsp.LSPObject table<string, lsp.LSPAny>
+
+---The glob pattern. Either a string pattern or a relative pattern.
+---
+---@since 3.17.0
+---@alias lsp.GlobPattern lsp.Pattern|lsp.RelativePattern
+
+---A document filter denotes a document by different properties like
+---the {@link TextDocument.languageId language}, the {@link Uri.scheme scheme} of
+---its resource, or a glob-pattern that is applied to the {@link TextDocument.fileName path}.
+---
+---Glob patterns can have the following syntax:
+---- `*` to match one or more characters in a path segment
+---- `?` to match on one character in a path segment
+---- `**` to match any number of path segments, including none
+---- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
+---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
+---
+---@sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }`
+---@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }`
+---
+---@since 3.17.0
+---@alias lsp.TextDocumentFilter anonym49|anonym50|anonym51
+
+---A notebook document filter denotes a notebook document by
+---different properties. The properties will be match
+---against the notebook's URI (same as with documents)
+---
+---@since 3.17.0
+---@alias lsp.NotebookDocumentFilter anonym52|anonym53|anonym54
+
+---The glob pattern to watch relative to the base path. Glob patterns can have the following syntax:
+---- `*` to match one or more characters in a path segment
+---- `?` to match on one character in a path segment
+---- `**` to match any number of path segments, including none
+---- `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
+---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
+---
+---@since 3.17.0
+---@alias lsp.Pattern string
+
+---@class anonym1
+---The name of the server as defined by the server.
+---@field name string
+---The server's version as defined by the server.
+---@field version? string
+
+---@class anonym3
+---@field insert lsp.Range
+---@field replace lsp.Range
+
+---@class anonym2
+---A default commit character set.
+---
+---@since 3.17.0
+---@field commitCharacters? string[]
+---A default edit range.
+---
+---@since 3.17.0
+---@field editRange? lsp.Range|anonym3
+---A default insert text format.
+---
+---@since 3.17.0
+---@field insertTextFormat? lsp.InsertTextFormat
+---A default insert text mode.
+---
+---@since 3.17.0
+---@field insertTextMode? lsp.InsertTextMode
+---A default data value.
+---
+---@since 3.17.0
+---@field data? lsp.LSPAny
+
+---@class anonym4
+---Human readable description of why the code action is currently disabled.
+---
+---This is displayed in the code actions UI.
+---@field reason string
+
+---@class anonym5
+---@field uri lsp.DocumentUri
+
+---@class anonym6
+
+---@class anonym7
+---The server supports deltas for full documents.
+---@field delta? boolean
+
+---@class anonym9
+---The change to the cell array.
+---@field array lsp.NotebookCellArrayChange
+---Additional opened cell text documents.
+---@field didOpen? lsp.TextDocumentItem[]
+---Additional closed cell text documents.
+---@field didClose? lsp.TextDocumentIdentifier[]
+
+---@class anonym10
+---@field document lsp.VersionedTextDocumentIdentifier
+---@field changes lsp.TextDocumentContentChangeEvent[]
+
+---@class anonym8
+---Changes to the cell structure to add or
+---remove cells.
+---@field structure? anonym9
+---Changes to notebook cells properties like its
+---kind, execution summary or metadata.
+---@field data? lsp.NotebookCell[]
+---Changes to the text content of notebook cells.
+---@field textContent? anonym10[]
+
+---@class anonym11
+---The name of the client as defined by the client.
+---@field name string
+---The client's version as defined by the client.
+---@field version? string
+
+---@class anonym12
+---The server supports workspace folder.
+---
+---@since 3.6.0
+---@field workspaceFolders? lsp.WorkspaceFoldersServerCapabilities
+---The server is interested in notifications/requests for operations on files.
+---
+---@since 3.16.0
+---@field fileOperations? lsp.FileOperationOptions
+
+---@class anonym13
+---The server has support for completion item label
+---details (see also `CompletionItemLabelDetails`) when
+---receiving a completion item in a resolve call.
+---
+---@since 3.17.0
+---@field labelDetailsSupport? boolean
+
+---@class anonym15
+---@field language string
+
+---@class anonym14
+---The notebook to be synced If a string
+---value is provided it matches against the
+---notebook type. '*' matches every notebook.
+---@field notebook string|lsp.NotebookDocumentFilter
+---The cells of the matching notebook to be synced.
+---@field cells? anonym15[]
+
+---@class anonym17
+---@field language string
+
+---@class anonym16
+---The notebook to be synced If a string
+---value is provided it matches against the
+---notebook type. '*' matches every notebook.
+---@field notebook? string|lsp.NotebookDocumentFilter
+---The cells of the matching notebook to be synced.
+---@field cells anonym17[]
+
+---@class anonym18
+---The client will actively cancel the request.
+---@field cancel boolean
+---The list of requests for which the client
+---will retry the request if it receives a
+---response with error code `ContentModified`
+---@field retryOnContentModified string[]
+
+---@class anonym19
+---Whether the client groups edits with equal labels into tree nodes,
+---for instance all edits labelled with "Changes in Strings" would
+---be a tree node.
+---@field groupsOnLabel? boolean
+
+---@class anonym20
+---The symbol kind values the client supports. When this
+---property exists the client also guarantees that it will
+---handle values outside its set gracefully and falls back
+---to a default value when unknown.
+---
+---If this property is not present the client only supports
+---the symbol kinds from `File` to `Array` as defined in
+---the initial version of the protocol.
+---@field valueSet? lsp.SymbolKind[]
+
+---@class anonym21
+---The tags supported by the client.
+---@field valueSet lsp.SymbolTag[]
+
+---@class anonym22
+---The properties that a client can resolve lazily. Usually
+---`location.range`
+---@field properties string[]
+
+---@class anonym24
+---The tags supported by the client.
+---@field valueSet lsp.CompletionItemTag[]
+
+---@class anonym25
+---The properties that a client can resolve lazily.
+---@field properties string[]
+
+---@class anonym26
+---@field valueSet lsp.InsertTextMode[]
+
+---@class anonym23
+---Client supports snippets as insert text.
+---
+---A snippet can define tab stops and placeholders with `$1`, `$2`
+---and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+---the end of the snippet. Placeholders with equal identifiers are linked,
+---that is typing in one will update others too.
+---@field snippetSupport? boolean
+---Client supports commit characters on a completion item.
+---@field commitCharactersSupport? boolean
+---Client supports the following content formats for the documentation
+---property. The order describes the preferred format of the client.
+---@field documentationFormat? lsp.MarkupKind[]
+---Client supports the deprecated property on a completion item.
+---@field deprecatedSupport? boolean
+---Client supports the preselect property on a completion item.
+---@field preselectSupport? boolean
+---Client supports the tag property on a completion item. Clients supporting
+---tags have to handle unknown tags gracefully. Clients especially need to
+---preserve unknown tags when sending a completion item back to the server in
+---a resolve call.
+---
+---@since 3.15.0
+---@field tagSupport? anonym24
+---Client support insert replace edit to control different behavior if a
+---completion item is inserted in the text or should replace text.
+---
+---@since 3.16.0
+---@field insertReplaceSupport? boolean
+---Indicates which properties a client can resolve lazily on a completion
+---item. Before version 3.16.0 only the predefined properties `documentation`
+---and `details` could be resolved lazily.
+---
+---@since 3.16.0
+---@field resolveSupport? anonym25
+---The client supports the `insertTextMode` property on
+---a completion item to override the whitespace handling mode
+---as defined by the client (see `insertTextMode`).
+---
+---@since 3.16.0
+---@field insertTextModeSupport? anonym26
+---The client has support for completion item label
+---details (see also `CompletionItemLabelDetails`).
+---
+---@since 3.17.0
+---@field labelDetailsSupport? boolean
+
+---@class anonym27
+---The completion item kind values the client supports. When this
+---property exists the client also guarantees that it will
+---handle values outside its set gracefully and falls back
+---to a default value when unknown.
+---
+---If this property is not present the client only supports
+---the completion items kinds from `Text` to `Reference` as defined in
+---the initial version of the protocol.
+---@field valueSet? lsp.CompletionItemKind[]
+
+---@class anonym28
+---The client supports the following itemDefaults on
+---a completion list.
+---
+---The value lists the supported property names of the
+---`CompletionList.itemDefaults` object. If omitted
+---no properties are supported.
+---
+---@since 3.17.0
+---@field itemDefaults? string[]
+
+---@class anonym30
+---The client supports processing label offsets instead of a
+---simple label string.
+---
+---@since 3.14.0
+---@field labelOffsetSupport? boolean
+
+---@class anonym29
+---Client supports the following content formats for the documentation
+---property. The order describes the preferred format of the client.
+---@field documentationFormat? lsp.MarkupKind[]
+---Client capabilities specific to parameter information.
+---@field parameterInformation? anonym30
+---The client supports the `activeParameter` property on `SignatureInformation`
+---literal.
+---
+---@since 3.16.0
+---@field activeParameterSupport? boolean
+
+---@class anonym31
+---The symbol kind values the client supports. When this
+---property exists the client also guarantees that it will
+---handle values outside its set gracefully and falls back
+---to a default value when unknown.
+---
+---If this property is not present the client only supports
+---the symbol kinds from `File` to `Array` as defined in
+---the initial version of the protocol.
+---@field valueSet? lsp.SymbolKind[]
+
+---@class anonym32
+---The tags supported by the client.
+---@field valueSet lsp.SymbolTag[]
+
+---@class anonym34
+---The code action kind values the client supports. When this
+---property exists the client also guarantees that it will
+---handle values outside its set gracefully and falls back
+---to a default value when unknown.
+---@field valueSet lsp.CodeActionKind[]
+
+---@class anonym33
+---The code action kind is support with the following value
+---set.
+---@field codeActionKind anonym34
+
+---@class anonym35
+---The properties that a client can resolve lazily.
+---@field properties string[]
+
+---@class anonym36
+---The folding range kind values the client supports. When this
+---property exists the client also guarantees that it will
+---handle values outside its set gracefully and falls back
+---to a default value when unknown.
+---@field valueSet? lsp.FoldingRangeKind[]
+
+---@class anonym37
+---If set, the client signals that it supports setting collapsedText on
+---folding ranges to display custom labels instead of the default text.
+---
+---@since 3.17.0
+---@field collapsedText? boolean
+
+---@class anonym38
+---The tags supported by the client.
+---@field valueSet lsp.DiagnosticTag[]
+
+---@class anonym40
+
+---@class anonym41
+---The client will send the `textDocument/semanticTokens/full/delta` request if
+---the server provides a corresponding handler.
+---@field delta? boolean
+
+---@class anonym39
+---The client will send the `textDocument/semanticTokens/range` request if
+---the server provides a corresponding handler.
+---@field range? boolean|anonym40
+---The client will send the `textDocument/semanticTokens/full` request if
+---the server provides a corresponding handler.
+---@field full? boolean|anonym41
+
+---@class anonym42
+---The properties that a client can resolve lazily.
+---@field properties string[]
+
+---@class anonym43
+---Whether the client supports additional attributes which
+---are preserved and send back to the server in the
+---request's response.
+---@field additionalPropertiesSupport? boolean
+
+---@class anonym44
+---@field range lsp.Range
+---@field placeholder string
+
+---@class anonym45
+---@field defaultBehavior boolean
+
+---@class anonym46
+---The range of the document that changed.
+---@field range lsp.Range
+---The optional length of the range that got replaced.
+---
+---@deprecated use range instead.
+---@field rangeLength? uinteger
+---The new text for the provided range.
+---@field text string
+
+---@class anonym47
+---The new text of the whole document.
+---@field text string
+
+---@class anonym48
+---@field language string
+---@field value string
+
+---@class anonym49
+---A language id, like `typescript`.
+---@field language string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme? string
+---A glob pattern, like `*.{ts,js}`.
+---@field pattern? string
+
+---@class anonym50
+---A language id, like `typescript`.
+---@field language? string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme string
+---A glob pattern, like `*.{ts,js}`.
+---@field pattern? string
+
+---@class anonym51
+---A language id, like `typescript`.
+---@field language? string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme? string
+---A glob pattern, like `*.{ts,js}`.
+---@field pattern string
+
+---@class anonym52
+---The type of the enclosing notebook.
+---@field notebookType string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme? string
+---A glob pattern.
+---@field pattern? string
+
+---@class anonym53
+---The type of the enclosing notebook.
+---@field notebookType? string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme string
+---A glob pattern.
+---@field pattern? string
+
+---@class anonym54
+---The type of the enclosing notebook.
+---@field notebookType? string
+---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
+---@field scheme? string
+---A glob pattern.
+---@field pattern string
diff --git a/runtime/lua/vim/lsp/_snippet.lua b/runtime/lua/vim/lsp/_snippet.lua
deleted file mode 100644
index 3488639fb4..0000000000
--- a/runtime/lua/vim/lsp/_snippet.lua
+++ /dev/null
@@ -1,500 +0,0 @@
-local P = {}
-
----Take characters until the target characters (The escape sequence is '\' + char)
----@param targets string[] The character list for stop consuming text.
----@param specials string[] If the character isn't contained in targets/specials, '\' will be left.
-P.take_until = function(targets, specials)
- targets = targets or {}
- specials = specials or {}
-
- return function(input, pos)
- local new_pos = pos
- local raw = {}
- local esc = {}
- while new_pos <= #input do
- local c = string.sub(input, new_pos, new_pos)
- if c == '\\' then
- table.insert(raw, '\\')
- new_pos = new_pos + 1
- c = string.sub(input, new_pos, new_pos)
- if not vim.tbl_contains(targets, c) and not vim.tbl_contains(specials, c) then
- table.insert(esc, '\\')
- end
- table.insert(raw, c)
- table.insert(esc, c)
- new_pos = new_pos + 1
- else
- if vim.tbl_contains(targets, c) then
- break
- end
- table.insert(raw, c)
- table.insert(esc, c)
- new_pos = new_pos + 1
- end
- end
-
- if new_pos == pos then
- return P.unmatch(pos)
- end
-
- return {
- parsed = true,
- value = {
- raw = table.concat(raw, ''),
- esc = table.concat(esc, ''),
- },
- pos = new_pos,
- }
- end
-end
-
-P.unmatch = function(pos)
- return {
- parsed = false,
- value = nil,
- pos = pos,
- }
-end
-
-P.map = function(parser, map)
- return function(input, pos)
- local result = parser(input, pos)
- if result.parsed then
- return {
- parsed = true,
- value = map(result.value),
- pos = result.pos,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.lazy = function(factory)
- return function(input, pos)
- return factory()(input, pos)
- end
-end
-
-P.token = function(token)
- return function(input, pos)
- local maybe_token = string.sub(input, pos, pos + #token - 1)
- if token == maybe_token then
- return {
- parsed = true,
- value = maybe_token,
- pos = pos + #token,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.pattern = function(p)
- return function(input, pos)
- local maybe_match = string.match(string.sub(input, pos), '^' .. p)
- if maybe_match then
- return {
- parsed = true,
- value = maybe_match,
- pos = pos + #maybe_match,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.many = function(parser)
- return function(input, pos)
- local values = {}
- local new_pos = pos
- while new_pos <= #input do
- local result = parser(input, new_pos)
- if not result.parsed then
- break
- end
- table.insert(values, result.value)
- new_pos = result.pos
- end
- if #values > 0 then
- return {
- parsed = true,
- value = values,
- pos = new_pos,
- }
- end
- return P.unmatch(pos)
- end
-end
-
-P.any = function(...)
- local parsers = { ... }
- return function(input, pos)
- for _, parser in ipairs(parsers) do
- local result = parser(input, pos)
- if result.parsed then
- return result
- end
- end
- return P.unmatch(pos)
- end
-end
-
-P.opt = function(parser)
- return function(input, pos)
- local result = parser(input, pos)
- return {
- parsed = true,
- value = result.value,
- pos = result.pos,
- }
- end
-end
-
-P.seq = function(...)
- local parsers = { ... }
- return function(input, pos)
- local values = {}
- local new_pos = pos
- for i, parser in ipairs(parsers) do
- local result = parser(input, new_pos)
- if result.parsed then
- values[i] = result.value
- new_pos = result.pos
- else
- return P.unmatch(pos)
- end
- end
- return {
- parsed = true,
- value = values,
- pos = new_pos,
- }
- end
-end
-
-local Node = {}
-
-Node.Type = {
- SNIPPET = 0,
- TABSTOP = 1,
- PLACEHOLDER = 2,
- VARIABLE = 3,
- CHOICE = 4,
- TRANSFORM = 5,
- FORMAT = 6,
- TEXT = 7,
-}
-
-function Node:__tostring()
- local insert_text = {}
- if self.type == Node.Type.SNIPPET then
- for _, c in ipairs(self.children) do
- table.insert(insert_text, tostring(c))
- end
- elseif self.type == Node.Type.CHOICE then
- table.insert(insert_text, self.items[1])
- elseif self.type == Node.Type.PLACEHOLDER then
- for _, c in ipairs(self.children or {}) do
- table.insert(insert_text, tostring(c))
- end
- elseif self.type == Node.Type.TEXT then
- table.insert(insert_text, self.esc)
- end
- return table.concat(insert_text, '')
-end
-
---@see https://code.visualstudio.com/docs/editor/userdefinedsnippets#_grammar
-
-local S = {}
-S.dollar = P.token('$')
-S.open = P.token('{')
-S.close = P.token('}')
-S.colon = P.token(':')
-S.slash = P.token('/')
-S.comma = P.token(',')
-S.pipe = P.token('|')
-S.plus = P.token('+')
-S.minus = P.token('-')
-S.question = P.token('?')
-S.int = P.map(P.pattern('[0-9]+'), function(value)
- return tonumber(value, 10)
-end)
-S.var = P.pattern('[%a_][%w_]+')
-S.text = function(targets, specials)
- return P.map(P.take_until(targets, specials), function(value)
- return setmetatable({
- type = Node.Type.TEXT,
- raw = value.raw,
- esc = value.esc,
- }, Node)
- end)
-end
-
-S.toplevel = P.lazy(function()
- return P.any(S.placeholder, S.tabstop, S.variable, S.choice)
-end)
-
-S.format = P.any(
- P.map(P.seq(S.dollar, S.int), function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- }, Node)
- end),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- S.slash,
- P.any(
- P.token('upcase'),
- P.token('downcase'),
- P.token('capitalize'),
- P.token('camelcase'),
- P.token('pascalcase')
- ),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- modifier = values[6],
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.seq(
- S.question,
- P.opt(P.take_until({ ':' }, { '\\' })),
- S.colon,
- P.opt(P.take_until({ '}' }, { '\\' }))
- ),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = values[5][2] and values[5][2].esc or '',
- else_text = values[5][4] and values[5][4].esc or '',
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.seq(S.plus, P.opt(P.take_until({ '}' }, { '\\' }))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = values[5][2] and values[5][2].esc or '',
- else_text = '',
- }, Node)
- end
- ),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- S.minus,
- P.opt(P.take_until({ '}' }, { '\\' })),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = '',
- else_text = values[6] and values[6].esc or '',
- }, Node)
- end
- ),
- P.map(
- P.seq(S.dollar, S.open, S.int, S.colon, P.opt(P.take_until({ '}' }, { '\\' })), S.close),
- function(values)
- return setmetatable({
- type = Node.Type.FORMAT,
- capture_index = values[3],
- if_text = '',
- else_text = values[5] and values[5].esc or '',
- }, Node)
- end
- )
-)
-
-S.transform = P.map(
- P.seq(
- S.slash,
- P.take_until({ '/' }, { '\\' }),
- S.slash,
- P.many(P.any(S.format, S.text({ '$', '/' }, { '\\' }))),
- S.slash,
- P.opt(P.pattern('[ig]+'))
- ),
- function(values)
- return setmetatable({
- type = Node.Type.TRANSFORM,
- pattern = values[2].raw,
- format = values[4],
- option = values[6],
- }, Node)
- end
-)
-
-S.tabstop = P.any(
- P.map(P.seq(S.dollar, S.int), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.close), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[3],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.int, S.transform, S.close), function(values)
- return setmetatable({
- type = Node.Type.TABSTOP,
- tabstop = values[3],
- transform = values[4],
- }, Node)
- end)
-)
-
-S.placeholder = P.any(
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.colon,
- P.opt(P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' })))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.PLACEHOLDER,
- tabstop = values[3],
- -- insert empty text if opt did not match.
- children = values[5] or {
- setmetatable({
- type = Node.Type.TEXT,
- raw = '',
- esc = '',
- }, Node),
- },
- }, Node)
- end
- )
-)
-
-S.choice = P.map(
- P.seq(
- S.dollar,
- S.open,
- S.int,
- S.pipe,
- P.many(P.map(P.seq(S.text({ ',', '|' }), P.opt(S.comma)), function(values)
- return values[1].esc
- end)),
- S.pipe,
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.CHOICE,
- tabstop = values[3],
- items = values[5],
- }, Node)
- end
-)
-
-S.variable = P.any(
- P.map(P.seq(S.dollar, S.var), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[2],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.var, S.close), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- }, Node)
- end),
- P.map(P.seq(S.dollar, S.open, S.var, S.transform, S.close), function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- transform = values[4],
- }, Node)
- end),
- P.map(
- P.seq(
- S.dollar,
- S.open,
- S.var,
- S.colon,
- P.many(P.any(S.toplevel, S.text({ '$', '}' }, { '\\' }))),
- S.close
- ),
- function(values)
- return setmetatable({
- type = Node.Type.VARIABLE,
- name = values[3],
- children = values[5],
- }, Node)
- end
- )
-)
-
-S.snippet = P.map(P.many(P.any(S.toplevel, S.text({ '$' }, { '}', '\\' }))), function(values)
- return setmetatable({
- type = Node.Type.SNIPPET,
- children = values,
- }, Node)
-end)
-
-local M = {}
-
----The snippet node type enum
----@types table<string, number>
-M.NodeType = Node.Type
-
----Parse snippet string and returns the AST
----@param input string
----@return table
-function M.parse(input)
- local result = S.snippet(input, 1)
- if not result.parsed then
- error('snippet parsing failed.')
- end
- return result.value
-end
-
-return M
diff --git a/runtime/lua/vim/lsp/_snippet_grammar.lua b/runtime/lua/vim/lsp/_snippet_grammar.lua
new file mode 100644
index 0000000000..9318fefcbc
--- /dev/null
+++ b/runtime/lua/vim/lsp/_snippet_grammar.lua
@@ -0,0 +1,181 @@
+--- Grammar for LSP snippets, based on https://microsoft.github.io/language-server-protocol/specification/#snippet_syntax
+
+local lpeg = vim.lpeg
+local P, S, R, V = lpeg.P, lpeg.S, lpeg.R, lpeg.V
+local C, Cg, Ct = lpeg.C, lpeg.Cg, lpeg.Ct
+
+local M = {}
+
+local alpha = R('az', 'AZ')
+local backslash = P('\\')
+local colon = P(':')
+local dollar = P('$')
+local int = R('09') ^ 1
+local l_brace, r_brace = P('{'), P('}')
+local pipe = P('|')
+local slash = P('/')
+local underscore = P('_')
+local var = Cg((underscore + alpha) * ((underscore + alpha + int) ^ 0), 'name')
+local format_capture = Cg(int / tonumber, 'capture')
+local format_modifier = Cg(P('upcase') + P('downcase') + P('capitalize'), 'modifier')
+local tabstop = Cg(int / tonumber, 'tabstop')
+
+-- These characters are always escapable in text nodes no matter the context.
+local escapable = '$}\\'
+
+--- Returns a function that unescapes occurrences of "special" characters.
+---
+--- @param special? string
+--- @return fun(match: string): string
+local function escape_text(special)
+ special = special or escapable
+ return function(match)
+ local escaped = match:gsub('\\(.)', function(c)
+ return special:find(c) and c or '\\' .. c
+ end)
+ return escaped
+ end
+end
+
+--- Returns a pattern for text nodes. Will match characters in `escape` when preceded by a backslash,
+--- and will stop with characters in `stop_with`.
+---
+--- @param escape string
+--- @param stop_with? string
+--- @return vim.lpeg.Pattern
+local function text(escape, stop_with)
+ stop_with = stop_with or escape
+ return (backslash * S(escape)) + (P(1) - S(stop_with))
+end
+
+-- For text nodes inside curly braces. It stops parsing when reaching an escapable character.
+local braced_text = (text(escapable) ^ 0) / escape_text()
+
+-- Within choice nodes, \ also escapes comma and pipe characters.
+local choice_text = C(text(escapable .. ',|') ^ 1) / escape_text(escapable .. ',|')
+
+-- Within format nodes, make sure we stop at /
+local format_text = C(text(escapable, escapable .. '/') ^ 1) / escape_text()
+
+local if_text, else_text = Cg(braced_text, 'if_text'), Cg(braced_text, 'else_text')
+
+-- Within ternary condition format nodes, make sure we stop at :
+local if_till_colon_text = Cg(C(text(escapable, escapable .. ':') ^ 1) / escape_text(), 'if_text')
+
+-- Matches the string inside //, allowing escaping of the closing slash.
+local regex = Cg(text('/') ^ 1, 'regex')
+
+-- Regex constructor flags (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp#parameters).
+local options = Cg(S('dgimsuvy') ^ 0, 'options')
+
+--- @enum vim.snippet.Type
+local Type = {
+ Tabstop = 1,
+ Placeholder = 2,
+ Choice = 3,
+ Variable = 4,
+ Format = 5,
+ Text = 6,
+ Snippet = 7,
+}
+M.NodeType = Type
+
+--- @class vim.snippet.Node<T>: { type: vim.snippet.Type, data: T }
+--- @class vim.snippet.TabstopData: { tabstop: integer }
+--- @class vim.snippet.TextData: { text: string }
+--- @class vim.snippet.PlaceholderData: { tabstop: integer, value: vim.snippet.Node<any> }
+--- @class vim.snippet.ChoiceData: { tabstop: integer, values: string[] }
+--- @class vim.snippet.VariableData: { name: string, default?: vim.snippet.Node<any>, regex?: string, format?: vim.snippet.Node<vim.snippet.FormatData|vim.snippet.TextData>[], options?: string }
+--- @class vim.snippet.FormatData: { capture: number, modifier?: string, if_text?: string, else_text?: string }
+--- @class vim.snippet.SnippetData: { children: vim.snippet.Node<any>[] }
+
+--- @type vim.snippet.Node<any>
+local Node = {}
+
+--- @return string
+--- @diagnostic disable-next-line: inject-field
+function Node:__tostring()
+ local node_text = {}
+ local type, data = self.type, self.data
+ if type == Type.Snippet then
+ --- @cast data vim.snippet.SnippetData
+ for _, child in ipairs(data.children) do
+ table.insert(node_text, tostring(child))
+ end
+ elseif type == Type.Choice then
+ --- @cast data vim.snippet.ChoiceData
+ table.insert(node_text, data.values[1])
+ elseif type == Type.Placeholder then
+ --- @cast data vim.snippet.PlaceholderData
+ table.insert(node_text, tostring(data.value))
+ elseif type == Type.Text then
+ --- @cast data vim.snippet.TextData
+ table.insert(node_text, data.text)
+ end
+ return table.concat(node_text)
+end
+
+--- Returns a function that constructs a snippet node of the given type.
+---
+--- @generic T
+--- @param type vim.snippet.Type
+--- @return fun(data: T): vim.snippet.Node<T>
+local function node(type)
+ return function(data)
+ return setmetatable({ type = type, data = data }, Node)
+ end
+end
+
+-- stylua: ignore
+local G = P({
+ 'snippet';
+ snippet = Ct(Cg(
+ Ct((
+ V('any') +
+ (Ct(Cg((text(escapable, '$') ^ 1) / escape_text(), 'text')) / node(Type.Text))
+ ) ^ 1), 'children'
+ ) * -P(1)) / node(Type.Snippet),
+ any = V('placeholder') + V('tabstop') + V('choice') + V('variable'),
+ any_or_text = V('any') + (Ct(Cg(braced_text, 'text')) / node(Type.Text)),
+ tabstop = Ct(dollar * (tabstop + (l_brace * tabstop * r_brace))) / node(Type.Tabstop),
+ placeholder = Ct(dollar * l_brace * tabstop * colon * Cg(V('any_or_text'), 'value') * r_brace) / node(Type.Placeholder),
+ choice = Ct(dollar *
+ l_brace *
+ tabstop *
+ pipe *
+ Cg(Ct(choice_text * (P(',') * choice_text) ^ 0), 'values') *
+ pipe *
+ r_brace) / node(Type.Choice),
+ variable = Ct(dollar * (
+ var + (
+ l_brace * var * (
+ r_brace +
+ (colon * Cg(V('any_or_text'), 'default') * r_brace) +
+ (slash * regex * slash * Cg(Ct((V('format') + (C(format_text) / node(Type.Text))) ^ 1), 'format') * slash * options * r_brace)
+ ))
+ )) / node(Type.Variable),
+ format = Ct(dollar * (
+ format_capture + (
+ l_brace * format_capture * (
+ r_brace +
+ (colon * (
+ (slash * format_modifier * r_brace) +
+ (P('+') * if_text * r_brace) +
+ (P('?') * if_till_colon_text * colon * else_text * r_brace) +
+ (P('-') * else_text * r_brace) +
+ (else_text * r_brace)
+ ))
+ ))
+ )) / node(Type.Format),
+})
+
+--- Parses the given input into a snippet tree.
+--- @param input string
+--- @return vim.snippet.Node<vim.snippet.SnippetData>
+function M.parse(input)
+ local snippet = G:match(input)
+ assert(snippet, 'snippet parsing failed')
+ return snippet --- @type vim.snippet.Node<vim.snippet.SnippetData>
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
new file mode 100644
index 0000000000..1fd112631d
--- /dev/null
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -0,0 +1,251 @@
+local bit = require('bit')
+local watch = require('vim._watch')
+local protocol = require('vim.lsp.protocol')
+local ms = protocol.Methods
+local lpeg = vim.lpeg
+
+local M = {}
+
+--- Parses the raw pattern into an |lpeg| pattern. LPeg patterns natively support the "this" or "that"
+--- alternative constructions described in the LSP spec that cannot be expressed in a standard Lua pattern.
+---
+---@param pattern string The raw glob pattern
+---@return vim.lpeg.Pattern? pattern An |lpeg| representation of the pattern, or nil if the pattern is invalid.
+local function parse(pattern)
+ local l = lpeg
+
+ local P, S, V = lpeg.P, lpeg.S, lpeg.V
+ local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf
+
+ local pathsep = '/'
+
+ local function class(inv, ranges)
+ for i, r in ipairs(ranges) do
+ ranges[i] = r[1] .. r[2]
+ end
+ local patt = l.R(unpack(ranges))
+ if inv == '!' then
+ patt = P(1) - patt
+ end
+ return patt
+ end
+
+ local function add(acc, a)
+ return acc + a
+ end
+
+ local function mul(acc, m)
+ return acc * m
+ end
+
+ local function star(stars, after)
+ return (-after * (l.P(1) - pathsep)) ^ #stars * after
+ end
+
+ local function dstar(after)
+ return (-after * l.P(1)) ^ 0 * after
+ end
+
+ local p = P({
+ 'Pattern',
+ Pattern = V('Elem') ^ -1 * V('End'),
+ Elem = Cf(
+ (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal'))
+ * (V('Elem') + V('End')),
+ mul
+ ),
+ DStar = P('**') * (P(pathsep) * (V('Elem') + V('End')) + V('End')) / dstar,
+ Star = C(P('*') ^ 1) * (V('Elem') + V('End')) / star,
+ Ques = P('?') * Cc(l.P(1) - pathsep),
+ Class = P('[') * C(P('!') ^ -1) * Ct(Ct(C(1) * '-' * C(P(1) - ']')) ^ 1 * ']') / class,
+ CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * '}',
+ -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same
+ -- wildcard semantics it usually has.
+ -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the
+ -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {}
+ -- condition means "everything after the {}" where several other options separated by ',' may
+ -- exist in between that should not be matched by '*'.
+ Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul)
+ + Cc(l.P(0)),
+ Literal = P(1) / l.P,
+ End = P(-1) * Cc(l.P(-1)),
+ })
+
+ return p:match(pattern) --[[@as vim.lpeg.Pattern?]]
+end
+
+---@private
+--- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern
+---
+---@param pattern string|vim.lpeg.Pattern The glob pattern (raw or parsed) to match.
+---@param s string The string to match against pattern.
+---@return boolean Whether or not pattern matches s.
+function M._match(pattern, s)
+ if type(pattern) == 'string' then
+ local p = assert(parse(pattern))
+ return p:match(s) ~= nil
+ end
+ return pattern:match(s) ~= nil
+end
+
+M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
+
+---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function
+local cancels = vim.defaulttable()
+
+local queue_timeout_ms = 100
+---@type table<integer, uv.uv_timer_t> client id -> libuv timer which will send queued changes at its timeout
+local queue_timers = {}
+---@type table<integer, lsp.FileEvent[]> client id -> set of queued changes to send in a single LSP notification
+local change_queues = {}
+---@type table<integer, table<string, lsp.FileChangeType>> client id -> URI -> last type of change processed
+--- Used to prune consecutive events of the same type for the same file
+local change_cache = vim.defaulttable()
+
+---@type table<vim._watch.FileChangeType, lsp.FileChangeType>
+local to_lsp_change_type = {
+ [watch.FileChangeType.Created] = protocol.FileChangeType.Created,
+ [watch.FileChangeType.Changed] = protocol.FileChangeType.Changed,
+ [watch.FileChangeType.Deleted] = protocol.FileChangeType.Deleted,
+}
+
+--- Default excludes the same as VSCode's `files.watcherExclude` setting.
+--- https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/workbench/contrib/files/browser/files.contribution.ts#L261
+---@type vim.lpeg.Pattern parsed Lpeg pattern
+M._poll_exclude_pattern = parse('**/.git/{objects,subtree-cache}/**')
+ + parse('**/node_modules/*/**')
+ + parse('**/.hg/store/**')
+
+--- Registers the workspace/didChangeWatchedFiles capability dynamically.
+---
+---@param reg lsp.Registration LSP Registration object.
+---@param ctx lsp.HandlerContext Context from the |lsp-handler|.
+function M.register(reg, ctx)
+ local client_id = ctx.client_id
+ local client = assert(vim.lsp.get_client_by_id(client_id), 'Client must be running')
+ -- Ill-behaved servers may not honor the client capability and try to register
+ -- anyway, so ignore requests when the user has opted out of the feature.
+ local has_capability = vim.tbl_get(
+ client.config.capabilities or {},
+ 'workspace',
+ 'didChangeWatchedFiles',
+ 'dynamicRegistration'
+ )
+ if not has_capability or not client.workspace_folders then
+ return
+ end
+ local register_options = reg.registerOptions --[[@as lsp.DidChangeWatchedFilesRegistrationOptions]]
+ ---@type table<string, {pattern: vim.lpeg.Pattern, kind: lsp.WatchKind}[]> by base_dir
+ local watch_regs = vim.defaulttable()
+ for _, w in ipairs(register_options.watchers) do
+ local kind = w.kind
+ or (protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete)
+ local glob_pattern = w.globPattern
+
+ if type(glob_pattern) == 'string' then
+ local pattern = parse(glob_pattern)
+ if not pattern then
+ error('Cannot parse pattern: ' .. glob_pattern)
+ end
+ for _, folder in ipairs(client.workspace_folders) do
+ local base_dir = vim.uri_to_fname(folder.uri)
+ table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind })
+ end
+ else
+ local base_uri = glob_pattern.baseUri
+ local uri = type(base_uri) == 'string' and base_uri or base_uri.uri
+ local base_dir = vim.uri_to_fname(uri)
+ local pattern = parse(glob_pattern.pattern)
+ if not pattern then
+ error('Cannot parse pattern: ' .. glob_pattern.pattern)
+ end
+ pattern = lpeg.P(base_dir .. '/') * pattern
+ table.insert(watch_regs[base_dir], { pattern = pattern, kind = kind })
+ end
+ end
+
+ ---@param base_dir string
+ local callback = function(base_dir)
+ return function(fullpath, change_type)
+ local registrations = watch_regs[base_dir]
+ for _, w in ipairs(registrations) do
+ local lsp_change_type = assert(
+ to_lsp_change_type[change_type],
+ 'Must receive change type Created, Changed or Deleted'
+ )
+ -- e.g. match kind with Delete bit (0b0100) to Delete change_type (3)
+ local kind_mask = bit.lshift(1, lsp_change_type - 1)
+ local change_type_match = bit.band(w.kind, kind_mask) == kind_mask
+ if w.pattern:match(fullpath) ~= nil and change_type_match then
+ ---@type lsp.FileEvent
+ local change = {
+ uri = vim.uri_from_fname(fullpath),
+ type = lsp_change_type,
+ }
+
+ local last_type = change_cache[client_id][change.uri]
+ if last_type ~= change.type then
+ change_queues[client_id] = change_queues[client_id] or {}
+ table.insert(change_queues[client_id], change)
+ change_cache[client_id][change.uri] = change.type
+ end
+
+ if not queue_timers[client_id] then
+ queue_timers[client_id] = vim.defer_fn(function()
+ ---@type lsp.DidChangeWatchedFilesParams
+ local params = {
+ changes = change_queues[client_id],
+ }
+ client.notify(ms.workspace_didChangeWatchedFiles, params)
+ queue_timers[client_id] = nil
+ change_queues[client_id] = nil
+ change_cache[client_id] = nil
+ end, queue_timeout_ms)
+ end
+
+ break -- if an event matches multiple watchers, only send one notification
+ end
+ end
+ end
+ end
+
+ for base_dir, watches in pairs(watch_regs) do
+ local include_pattern = vim.iter(watches):fold(lpeg.P(false), function(acc, w)
+ return acc + w.pattern
+ end)
+
+ table.insert(
+ cancels[client_id][reg.id],
+ M._watchfunc(base_dir, {
+ uvflags = {
+ recursive = true,
+ },
+ -- include_pattern will ensure the pattern from *any* watcher definition for the
+ -- base_dir matches. This first pass prevents polling for changes to files that
+ -- will never be sent to the LSP server. A second pass in the callback is still necessary to
+ -- match a *particular* pattern+kind pair.
+ include_pattern = include_pattern,
+ exclude_pattern = M._poll_exclude_pattern,
+ }, callback(base_dir))
+ )
+ end
+end
+
+--- Unregisters the workspace/didChangeWatchedFiles capability dynamically.
+---
+---@param unreg lsp.Unregistration LSP Unregistration object.
+---@param ctx lsp.HandlerContext Context from the |lsp-handler|.
+function M.unregister(unreg, ctx)
+ local client_id = ctx.client_id
+ local client_cancels = cancels[client_id]
+ local reg_cancels = client_cancels[unreg.id]
+ while #reg_cancels > 0 do
+ table.remove(reg_cancels)()
+ end
+ client_cancels[unreg.id] = nil
+ if not next(cancels[client_id]) then
+ cancels[client_id] = nil
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 6ac885c78f..cf9acc0808 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -2,10 +2,10 @@ local api = vim.api
local validate = vim.validate
local util = require('vim.lsp.util')
local npcall = vim.F.npcall
+local ms = require('vim.lsp.protocol').Methods
local M = {}
----@private
--- Sends an async request to all active clients attached to the current
--- buffer.
---
@@ -13,10 +13,11 @@ local M = {}
---@param params (table|nil) Parameters to send to the server
---@param handler (function|nil) See |lsp-handler|. Follows |lsp-handler-resolution|
--
----@returns 2-tuple:
---- - Map of client-id:request-id pairs for all successful requests.
---- - Function which can be used to cancel all the requests. You could instead
---- iterate all clients and call their `cancel_request()` methods.
+---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
+---for all successful requests.
+---@return function _cancel_all_requests Function which can be used to
+---cancel all the requests. You could instead
+---iterate all clients and call their `cancel_request()` methods.
---
---@see |vim.lsp.buf_request()|
local function request(method, params, handler)
@@ -30,8 +31,10 @@ end
--- Checks whether the language servers attached to the current buffer are
--- ready.
---
----@returns `true` if server responds.
+---@return boolean if server responds.
+---@deprecated
function M.server_ready()
+ vim.deprecate('vim.lsp.buf.server_ready', nil, '0.10.0')
return not not vim.lsp.buf_notify(0, 'window/progress', {})
end
@@ -39,10 +42,9 @@ end
--- window. Calling the function twice will jump into the floating window.
function M.hover()
local params = util.make_position_params()
- request('textDocument/hover', params)
+ request(ms.textDocument_hover, params)
end
----@private
local function request_with_options(name, params, options)
local req_handler
if options then
@@ -60,66 +62,71 @@ end
---
---@param options table|nil additional options
--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
+--- Called for any non-empty result.
function M.declaration(options)
local params = util.make_position_params()
- request_with_options('textDocument/declaration', params, options)
+ request_with_options(ms.textDocument_declaration, params, options)
end
--- Jumps to the definition of the symbol under the cursor.
---
---@param options table|nil additional options
--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
+--- Called for any non-empty result.
function M.definition(options)
local params = util.make_position_params()
- request_with_options('textDocument/definition', params, options)
+ request_with_options(ms.textDocument_definition, params, options)
end
--- Jumps to the definition of the type of the symbol under the cursor.
---
---@param options table|nil additional options
--- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
+--- Called for any non-empty result.
function M.type_definition(options)
local params = util.make_position_params()
- request_with_options('textDocument/typeDefinition', params, options)
+ request_with_options(ms.textDocument_typeDefinition, params, options)
end
--- Lists all the implementations for the symbol under the cursor in the
--- quickfix window.
---
---@param options table|nil additional options
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
+--- Called for any non-empty result.
function M.implementation(options)
local params = util.make_position_params()
- request_with_options('textDocument/implementation', params, options)
+ request_with_options(ms.textDocument_implementation, params, options)
end
--- Displays signature information about the symbol under the cursor in a
--- floating window.
function M.signature_help()
local params = util.make_position_params()
- request('textDocument/signatureHelp', params)
+ request(ms.textDocument_signatureHelp, params)
end
--- Retrieves the completion items at the current cursor position. Can only be
--- called in Insert mode.
---
----@param context (context support not yet implemented) Additional information
+---@param context table (context support not yet implemented) Additional information
--- about the context in which a completion was triggered (how it was triggered,
--- and by which trigger character, if applicable)
---
----@see vim.lsp.protocol.constants.CompletionTriggerKind
+---@see vim.lsp.protocol.CompletionTriggerKind
function M.completion(context)
local params = util.make_position_params()
params.context = context
- return request('textDocument/completion', params)
+ return request(ms.textDocument_completion, params)
end
----@private
----@return table {start={row, col}, end={row, col}} using (1, 0) indexing
-local function range_from_selection()
+---@param bufnr integer
+---@param mode "v"|"V"
+---@return table {start={row,col}, end={row,col}} using (1, 0) indexing
+local function range_from_selection(bufnr, mode)
-- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896
-- [bufnum, lnum, col, off]; both row and column 1-indexed
@@ -138,6 +145,11 @@ local function range_from_selection()
start_row, end_row = end_row, start_row
start_col, end_col = end_col, start_col
end
+ if mode == 'V' then
+ start_col = 1
+ local lines = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)
+ end_col = #lines[1]
+ end
return {
['start'] = { start_row, start_col - 1 },
['end'] = { end_row, end_col - 1 },
@@ -150,8 +162,8 @@ end
--- @param options table|nil Optional table which holds the following optional fields:
--- - formatting_options (table|nil):
--- Can be used to specify FormattingOptions. Some unspecified options will be
---- automatically derived from the current Neovim options.
---- See https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#formattingOptions
+--- automatically derived from the current Nvim options.
+--- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
--- - timeout_ms (integer|nil, default 1000):
--- Time in milliseconds to block for formatting requests. No effect if async=true
--- - bufnr (number|nil):
@@ -160,13 +172,11 @@ end
---
--- - filter (function|nil):
--- Predicate used to filter clients. Receives a client as argument and must return a
---- boolean. Clients matching the predicate are included. Example:
----
---- <pre>lua
---- -- Never request typescript-language-server for formatting
---- vim.lsp.buf.format {
---- filter = function(client) return client.name ~= "tsserver" end
---- }
+--- boolean. Clients matching the predicate are included. Example: <pre>lua
+--- -- Never request typescript-language-server for formatting
+--- vim.lsp.buf.format {
+--- filter = function(client) return client.name ~= "tsserver" end
+--- }
--- </pre>
---
--- - async boolean|nil
@@ -180,39 +190,34 @@ end
--- Restrict formatting to the client with name (client.name) matching this field.
---
--- - range (table|nil) Range to format.
---- Table must contain `start` and `end` keys with {row, col} tuples using
+--- Table must contain `start` and `end` keys with {row,col} tuples using
--- (1,0) indexing.
--- Defaults to current selection in visual mode
--- Defaults to `nil` in other modes, formatting the full buffer
function M.format(options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
- local clients = vim.lsp.get_active_clients({
+ local mode = api.nvim_get_mode().mode
+ local range = options.range
+ if not range and mode == 'v' or mode == 'V' then
+ range = range_from_selection(bufnr, mode)
+ end
+ local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting
+
+ local clients = vim.lsp.get_clients({
id = options.id,
bufnr = bufnr,
name = options.name,
+ method = method,
})
-
if options.filter then
clients = vim.tbl_filter(options.filter, clients)
end
- local mode = api.nvim_get_mode().mode
- local range = options.range
- if not range and mode == 'v' or mode == 'V' then
- range = range_from_selection()
- end
- local method = range and 'textDocument/rangeFormatting' or 'textDocument/formatting'
-
- clients = vim.tbl_filter(function(client)
- return client.supports_method(method)
- end, clients)
-
if #clients == 0 then
vim.notify('[LSP] Format request failed, no matching language servers.')
end
- ---@private
local function set_range(client, params)
if range then
local range_params =
@@ -264,19 +269,16 @@ end
function M.rename(new_name, options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
- local clients = vim.lsp.get_active_clients({
+ local clients = vim.lsp.get_clients({
bufnr = bufnr,
name = options.name,
+ -- Clients must at least support rename, prepareRename is optional
+ method = ms.textDocument_rename,
})
if options.filter then
clients = vim.tbl_filter(options.filter, clients)
end
- -- Clients must at least support rename, prepareRename is optional
- clients = vim.tbl_filter(function(client)
- return client.supports_method('textDocument/rename')
- end, clients)
-
if #clients == 0 then
vim.notify('[LSP] Rename, no matching language servers with rename capability.')
end
@@ -286,7 +288,6 @@ function M.rename(new_name, options)
-- Compute early to account for cursor movements after going async
local cword = vim.fn.expand('<cword>')
- ---@private
local function get_text_at_range(range, offset_encoding)
return api.nvim_buf_get_text(
bufnr,
@@ -304,21 +305,20 @@ function M.rename(new_name, options)
return
end
- ---@private
local function rename(name)
local params = util.make_position_params(win, client.offset_encoding)
params.newName = name
- local handler = client.handlers['textDocument/rename']
- or vim.lsp.handlers['textDocument/rename']
- client.request('textDocument/rename', params, function(...)
+ local handler = client.handlers[ms.textDocument_rename]
+ or vim.lsp.handlers[ms.textDocument_rename]
+ client.request(ms.textDocument_rename, params, function(...)
handler(...)
try_use_client(next(clients, idx))
end, bufnr)
end
- if client.supports_method('textDocument/prepareRename') then
+ if client.supports_method(ms.textDocument_prepareRename) then
local params = util.make_position_params(win, client.offset_encoding)
- client.request('textDocument/prepareRename', params, function(err, result)
+ client.request(ms.textDocument_prepareRename, params, function(err, result)
if err or result == nil then
if next(clients, idx) then
try_use_client(next(clients, idx))
@@ -357,7 +357,7 @@ function M.rename(new_name, options)
end, bufnr)
else
assert(
- client.supports_method('textDocument/rename'),
+ client.supports_method(ms.textDocument_rename),
'Client must support textDocument/rename'
)
if new_name then
@@ -393,7 +393,7 @@ function M.references(context, options)
params.context = context or {
includeDeclaration = true,
}
- request_with_options('textDocument/references', params, options)
+ request_with_options(ms.textDocument_references, params, options)
end
--- Lists all symbols in the current buffer in the quickfix window.
@@ -402,10 +402,9 @@ end
--- - on_list: (function) handler for list results. See |lsp-on-list-handler|
function M.document_symbol(options)
local params = { textDocument = util.make_text_document_params() }
- request_with_options('textDocument/documentSymbol', params, options)
+ request_with_options(ms.textDocument_documentSymbol, params, options)
end
----@private
local function pick_call_hierarchy_item(call_hierarchy_items)
if not call_hierarchy_items then
return
@@ -425,10 +424,9 @@ local function pick_call_hierarchy_item(call_hierarchy_items)
return choice
end
----@private
local function call_hierarchy(method)
local params = util.make_position_params()
- request('textDocument/prepareCallHierarchy', params, function(err, result, ctx)
+ request(ms.textDocument_prepareCallHierarchy, params, function(err, result, ctx)
if err then
vim.notify(err.message, vim.log.levels.WARN)
return
@@ -450,21 +448,21 @@ end
--- |quickfix| window. If the symbol can resolve to multiple
--- items, the user can pick one in the |inputlist()|.
function M.incoming_calls()
- call_hierarchy('callHierarchy/incomingCalls')
+ call_hierarchy(ms.callHierarchy_incomingCalls)
end
--- Lists all the items that are called by the symbol under the
--- cursor in the |quickfix| window. If the symbol can resolve to
--- multiple items, the user can pick one in the |inputlist()|.
function M.outgoing_calls()
- call_hierarchy('callHierarchy/outgoingCalls')
+ call_hierarchy(ms.callHierarchy_outgoingCalls)
end
--- List workspace folders.
---
function M.list_workspace_folders()
local workspace_folders = {}
- for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
+ for _, client in pairs(vim.lsp.get_clients({ bufnr = 0 })) do
for _, folder in pairs(client.workspace_folders or {}) do
table.insert(workspace_folders, folder.name)
end
@@ -485,11 +483,13 @@ function M.add_workspace_folder(workspace_folder)
print(workspace_folder, ' is not a valid directory')
return
end
- local params = util.make_workspace_params(
- { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } },
- {}
- )
- for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
+ local new_workspace = {
+ uri = vim.uri_from_fname(workspace_folder),
+ name = workspace_folder,
+ }
+ local params = { event = { added = { new_workspace }, removed = {} } }
+ local bufnr = vim.api.nvim_get_current_buf()
+ for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
local found = false
for _, folder in pairs(client.workspace_folders or {}) do
if folder.name == workspace_folder then
@@ -499,11 +499,11 @@ function M.add_workspace_folder(workspace_folder)
end
end
if not found then
- vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params)
+ client.notify(ms.workspace_didChangeWorkspaceFolders, params)
if not client.workspace_folders then
client.workspace_folders = {}
end
- table.insert(client.workspace_folders, params.event.added[1])
+ table.insert(client.workspace_folders, new_workspace)
end
end
end
@@ -518,14 +518,16 @@ function M.remove_workspace_folder(workspace_folder)
if not (workspace_folder and #workspace_folder > 0) then
return
end
- local params = util.make_workspace_params(
- { {} },
- { { uri = vim.uri_from_fname(workspace_folder), name = workspace_folder } }
- )
- for _, client in pairs(vim.lsp.get_active_clients({ bufnr = 0 })) do
+ local workspace = {
+ uri = vim.uri_from_fname(workspace_folder),
+ name = workspace_folder,
+ }
+ local params = { event = { added = {}, removed = { workspace } } }
+ local bufnr = vim.api.nvim_get_current_buf()
+ for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
for idx, folder in pairs(client.workspace_folders) do
if folder.name == workspace_folder then
- vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params)
+ client.notify(ms.workspace_didChangeWorkspaceFolders, params)
client.workspace_folders[idx] = nil
return
end
@@ -540,7 +542,7 @@ end
--- call, the user is prompted to enter a string on the command line. An empty
--- string means no filtering is done.
---
----@param query (string, optional)
+---@param query string|nil optional
---@param options table|nil additional options
--- - on_list: (function) handler for list results. See |lsp-on-list-handler|
function M.workspace_symbol(query, options)
@@ -549,17 +551,18 @@ function M.workspace_symbol(query, options)
return
end
local params = { query = query }
- request_with_options('workspace/symbol', params, options)
+ request_with_options(ms.workspace_symbol, params, options)
end
--- Send request to the server to resolve document highlights for the current
--- text document position. This request can be triggered by a key mapping or
--- by events such as `CursorHold`, e.g.:
---- <pre>vim
---- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
---- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
---- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
---- </pre>
+---
+--- ```vim
+--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
+--- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()
+--- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
+--- ```
---
--- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups
--- to be defined or you won't be able to see the actual highlights.
@@ -568,17 +571,25 @@ end
--- |hl-LspReferenceWrite|
function M.document_highlight()
local params = util.make_position_params()
- request('textDocument/documentHighlight', params)
+ request(ms.textDocument_documentHighlight, params)
end
--- Removes document highlights from current buffer.
----
function M.clear_references()
util.buf_clear_references()
end
----@private
---
+---@class vim.lsp.CodeActionResultEntry
+---@field error? lsp.ResponseError
+---@field result? (lsp.Command|lsp.CodeAction)[]
+---@field ctx lsp.HandlerContext
+
+---@class vim.lsp.buf.code_action.opts
+---@field context? lsp.CodeActionContext
+---@field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
+---@field apply? boolean
+---@field range? {start: integer[], end: integer[]}
+
--- This is not public because the main extension point is
--- vim.ui.select which can be overridden independently.
---
@@ -587,21 +598,21 @@ end
--- from multiple clients to have 1 single UI prompt for the user, yet we still
--- need to be able to link a `CodeAction|Command` to the right client for
--- `codeAction/resolve`
-local function on_code_action_results(results, ctx, options)
- local action_tuples = {}
-
- ---@private
+---@param results table<integer, vim.lsp.CodeActionResultEntry>
+---@param opts? vim.lsp.buf.code_action.opts
+local function on_code_action_results(results, opts)
+ ---@param a lsp.Command|lsp.CodeAction
local function action_filter(a)
-- filter by specified action kind
- if options and options.context and options.context.only then
+ if opts and opts.context and opts.context.only then
if not a.kind then
return false
end
local found = false
- for _, o in ipairs(options.context.only) do
- -- action kinds are hierarchical with . as a separator: when requesting only
- -- 'quickfix' this filter allows both 'quickfix' and 'quickfix.foo', for example
- if a.kind:find('^' .. o .. '$') or a.kind:find('^' .. o .. '%.') then
+ for _, o in ipairs(opts.context.only) do
+ -- action kinds are hierarchical with . as a separator: when requesting only 'type-annotate'
+ -- this filter allows both 'type-annotate' and 'type-annotate.foo', for example
+ if a.kind == o or vim.startswith(a.kind, o .. '.') then
found = true
break
end
@@ -611,53 +622,43 @@ local function on_code_action_results(results, ctx, options)
end
end
-- filter by user function
- if options and options.filter and not options.filter(a) then
+ if opts and opts.filter and not opts.filter(a) then
return false
end
-- no filter removed this action
return true
end
- for client_id, result in pairs(results) do
+ ---@type {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}[]
+ local actions = {}
+ for _, result in pairs(results) do
for _, action in pairs(result.result or {}) do
if action_filter(action) then
- table.insert(action_tuples, { client_id, action })
+ table.insert(actions, { action = action, ctx = result.ctx })
end
end
end
- if #action_tuples == 0 then
+ if #actions == 0 then
vim.notify('No code actions available', vim.log.levels.INFO)
return
end
- ---@private
- local function apply_action(action, client)
+ ---@param action lsp.Command|lsp.CodeAction
+ ---@param client lsp.Client
+ ---@param ctx lsp.HandlerContext
+ local function apply_action(action, client, ctx)
if action.edit then
util.apply_workspace_edit(action.edit, client.offset_encoding)
end
if action.command then
local command = type(action.command) == 'table' and action.command or action
- local fn = client.commands[command.command] or vim.lsp.commands[command.command]
- if fn then
- local enriched_ctx = vim.deepcopy(ctx)
- enriched_ctx.client_id = client.id
- fn(command, enriched_ctx)
- else
- -- Not using command directly to exclude extra properties,
- -- see https://github.com/python-lsp/python-lsp-server/issues/146
- local params = {
- command = command.command,
- arguments = command.arguments,
- workDoneToken = command.workDoneToken,
- }
- client.request('workspace/executeCommand', params, nil, ctx.bufnr)
- end
+ client._exec_cmd(command, ctx)
end
end
- ---@private
- local function on_user_choice(action_tuple)
- if not action_tuple then
+ ---@param choice {action: lsp.Command|lsp.CodeAction, ctx: lsp.HandlerContext}
+ local function on_user_choice(choice)
+ if not choice then
return
end
-- textDocument/codeAction can return either Command[] or CodeAction[]
@@ -672,52 +673,51 @@ local function on_code_action_results(results, ctx, options)
-- command: string
-- arguments?: any[]
--
- local client = vim.lsp.get_client_by_id(action_tuple[1])
- local action = action_tuple[2]
- if
- not action.edit
- and client
- and vim.tbl_get(client.server_capabilities, 'codeActionProvider', 'resolveProvider')
- then
- client.request('codeAction/resolve', action, function(err, resolved_action)
+ ---@type lsp.Client
+ local client = assert(vim.lsp.get_client_by_id(choice.ctx.client_id))
+ local action = choice.action
+ local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
+
+ local reg = client.dynamic_capabilities:get(ms.textDocument_codeAction, { bufnr = bufnr })
+
+ local supports_resolve = vim.tbl_get(reg or {}, 'registerOptions', 'resolveProvider')
+ or client.supports_method(ms.codeAction_resolve)
+
+ if not action.edit and client and supports_resolve then
+ client.request(ms.codeAction_resolve, action, function(err, resolved_action)
if err then
- vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
- return
+ if action.command then
+ apply_action(action, client, choice.ctx)
+ else
+ vim.notify(err.code .. ': ' .. err.message, vim.log.levels.ERROR)
+ end
+ else
+ apply_action(resolved_action, client, choice.ctx)
end
- apply_action(resolved_action, client)
- end)
+ end, bufnr)
else
- apply_action(action, client)
+ apply_action(action, client, choice.ctx)
end
end
-- If options.apply is given, and there are just one remaining code action,
-- apply it directly without querying the user.
- if options and options.apply and #action_tuples == 1 then
- on_user_choice(action_tuples[1])
+ if opts and opts.apply and #actions == 1 then
+ on_user_choice(actions[1])
return
end
- vim.ui.select(action_tuples, {
+ ---@param item {action: lsp.Command|lsp.CodeAction}
+ local function format_item(item)
+ local title = item.action.title:gsub('\r\n', '\\r\\n')
+ return title:gsub('\n', '\\n')
+ end
+ local select_opts = {
prompt = 'Code actions:',
kind = 'codeaction',
- format_item = function(action_tuple)
- local title = action_tuple[2].title:gsub('\r\n', '\\r\\n')
- return title:gsub('\n', '\\n')
- end,
- }, on_user_choice)
-end
-
---- Requests code actions from all clients and calls the handler exactly once
---- with all aggregated results
----@private
-local function code_action_request(params, options)
- local bufnr = api.nvim_get_current_buf()
- local method = 'textDocument/codeAction'
- vim.lsp.buf_request_all(bufnr, method, params, function(results)
- local ctx = { bufnr = bufnr, method = method, params = params }
- on_code_action_results(results, ctx, options)
- end)
+ format_item = format_item,
+ }
+ vim.ui.select(actions, select_opts, on_user_choice)
end
--- Selects a code action available at the current
@@ -743,11 +743,11 @@ end
--- - range: (table|nil)
--- Range for which code actions should be requested.
--- If in visual mode this defaults to the active selection.
---- Table must contain `start` and `end` keys with {row, col} tuples
+--- Table must contain `start` and `end` keys with {row,col} tuples
--- using mark-like indexing. See |api-indexing|
---
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
----@see vim.lsp.protocol.constants.CodeActionTriggerKind
+---@see vim.lsp.protocol.CodeActionTriggerKind
function M.code_action(options)
validate({ options = { options, 't', true } })
options = options or {}
@@ -764,21 +764,50 @@ function M.code_action(options)
local bufnr = api.nvim_get_current_buf()
context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr)
end
- local params
local mode = api.nvim_get_mode().mode
- if options.range then
- assert(type(options.range) == 'table', 'code_action range must be a table')
- local start = assert(options.range.start, 'range must have a `start` property')
- local end_ = assert(options.range['end'], 'range must have a `end` property')
- params = util.make_given_range_params(start, end_)
- elseif mode == 'v' or mode == 'V' then
- local range = range_from_selection()
- params = util.make_given_range_params(range.start, range['end'])
- else
- params = util.make_range_params()
+ local bufnr = api.nvim_get_current_buf()
+ local win = api.nvim_get_current_win()
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_codeAction })
+ local remaining = #clients
+ if remaining == 0 then
+ if next(vim.lsp.get_clients({ bufnr = bufnr })) then
+ vim.notify(vim.lsp._unsupported_method(ms.textDocument_codeAction), vim.log.levels.WARN)
+ end
+ return
+ end
+
+ ---@type table<integer, vim.lsp.CodeActionResultEntry>
+ local results = {}
+
+ ---@param err? lsp.ResponseError
+ ---@param result? (lsp.Command|lsp.CodeAction)[]
+ ---@param ctx lsp.HandlerContext
+ local function on_result(err, result, ctx)
+ results[ctx.client_id] = { error = err, result = result, ctx = ctx }
+ remaining = remaining - 1
+ if remaining == 0 then
+ on_code_action_results(results, options)
+ end
+ end
+
+ for _, client in ipairs(clients) do
+ ---@type lsp.CodeActionParams
+ local params
+ if options.range then
+ assert(type(options.range) == 'table', 'code_action range must be a table')
+ local start = assert(options.range.start, 'range must have a `start` property')
+ local end_ = assert(options.range['end'], 'range must have a `end` property')
+ params = util.make_given_range_params(start, end_, bufnr, client.offset_encoding)
+ elseif mode == 'v' or mode == 'V' then
+ local range = range_from_selection(bufnr, mode)
+ params =
+ util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding)
+ else
+ params = util.make_range_params(win, client.offset_encoding)
+ end
+ params.context = context
+ client.request(ms.textDocument_codeAction, params, on_result, bufnr)
end
- params.context = context
- code_action_request(params, options)
end
--- Executes an LSP server command.
@@ -795,8 +824,7 @@ function M.execute_command(command_params)
arguments = command_params.arguments,
workDoneToken = command_params.workDoneToken,
}
- request('workspace/executeCommand', command_params)
+ request(ms.workspace_executeCommand, command_params)
end
return M
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 17489ed84d..9cccaa1d66 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -1,5 +1,6 @@
local util = require('vim.lsp.util')
local log = require('vim.lsp.log')
+local ms = require('vim.lsp.protocol').Methods
local api = vim.api
local M = {}
@@ -7,6 +8,7 @@ local M = {}
--- to throttle refreshes to at most one at a time
local active_refreshes = {}
+---@type table<integer, table<integer, lsp.CodeLens[]>>
--- bufnr -> client_id -> lenses
local lens_cache_by_buf = setmetatable({}, {
__index = function(t, b)
@@ -15,6 +17,8 @@ local lens_cache_by_buf = setmetatable({}, {
end,
})
+---@type table<integer, integer>
+---client_id -> namespace
local namespaces = setmetatable({}, {
__index = function(t, key)
local value = api.nvim_create_namespace('vim_lsp_codelens:' .. key)
@@ -26,43 +30,34 @@ local namespaces = setmetatable({}, {
---@private
M.__namespaces = namespaces
----@private
+local augroup = api.nvim_create_augroup('vim_lsp_codelens', {})
+
+api.nvim_create_autocmd('LspDetach', {
+ group = augroup,
+ callback = function(ev)
+ M.clear(ev.data.client_id, ev.buf)
+ end,
+})
+
+---@param lens lsp.CodeLens
+---@param bufnr integer
+---@param client_id integer
local function execute_lens(lens, bufnr, client_id)
local line = lens.range.start.line
api.nvim_buf_clear_namespace(bufnr, namespaces[client_id], line, line + 1)
local client = vim.lsp.get_client_by_id(client_id)
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
- local command = lens.command
- local fn = client.commands[command.command] or vim.lsp.commands[command.command]
- if fn then
- fn(command, { bufnr = bufnr, client_id = client_id })
- return
- end
- -- Need to use the client that returned the lens → must not use buf_request
- local command_provider = client.server_capabilities.executeCommandProvider
- local commands = type(command_provider) == 'table' and command_provider.commands or {}
- if not vim.tbl_contains(commands, command.command) then
- vim.notify(
- string.format(
- 'Language server does not support command `%s`. This command may require a client extension.',
- command.command
- ),
- vim.log.levels.WARN
- )
- return
- end
- client.request('workspace/executeCommand', command, function(...)
- local result = vim.lsp.handlers['workspace/executeCommand'](...)
+ client._exec_cmd(lens.command, { bufnr = bufnr }, function(...)
+ vim.lsp.handlers[ms.workspace_executeCommand](...)
M.refresh()
- return result
- end, bufnr)
+ end)
end
--- Return all lenses for the given buffer
---
----@param bufnr number Buffer number. 0 can be used for the current buffer.
----@return table (`CodeLens[]`)
+---@param bufnr integer Buffer number. 0 can be used for the current buffer.
+---@return lsp.CodeLens[]
function M.get(bufnr)
local lenses_by_client = lens_cache_by_buf[bufnr or 0]
if not lenses_by_client then
@@ -97,6 +92,7 @@ function M.run()
else
vim.ui.select(options, {
prompt = 'Code lenses:',
+ kind = 'codelens',
format_item = function(option)
return option.lens.command.title
end,
@@ -108,22 +104,26 @@ function M.run()
end
end
----@private
local function resolve_bufnr(bufnr)
return bufnr == 0 and api.nvim_get_current_buf() or bufnr
end
--- Clear the lenses
---
----@param client_id number|nil filter by client_id. All clients if nil
----@param bufnr number|nil filter by buffer. All buffers if nil
+---@param client_id integer|nil filter by client_id. All clients if nil
+---@param bufnr integer|nil filter by buffer. All buffers if nil
function M.clear(client_id, bufnr)
- local buffers = bufnr and { resolve_bufnr(bufnr) } or vim.tbl_keys(lens_cache_by_buf)
+ bufnr = bufnr and resolve_bufnr(bufnr)
+ local buffers = bufnr and { bufnr }
+ or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
for _, iter_bufnr in pairs(buffers) do
local client_ids = client_id and { client_id } or vim.tbl_keys(namespaces)
for _, iter_client_id in pairs(client_ids) do
local ns = namespaces[iter_client_id]
- lens_cache_by_buf[iter_bufnr][iter_client_id] = {}
+ -- there can be display()ed lenses, which are not stored in cache
+ if lens_cache_by_buf[iter_bufnr] then
+ lens_cache_by_buf[iter_bufnr][iter_client_id] = {}
+ end
api.nvim_buf_clear_namespace(iter_bufnr, ns, 0, -1)
end
end
@@ -131,16 +131,21 @@ end
--- Display the lenses using virtual text
---
----@param lenses table of lenses to display (`CodeLens[] | null`)
----@param bufnr number
----@param client_id number
+---@param lenses? lsp.CodeLens[] lenses to display
+---@param bufnr integer
+---@param client_id integer
function M.display(lenses, bufnr, client_id)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
local ns = namespaces[client_id]
if not lenses or not next(lenses) then
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
return
end
- local lenses_by_lnum = {}
+
+ local lenses_by_lnum = {} ---@type table<integer, lsp.CodeLens[]>
for _, lens in pairs(lenses) do
local line_lenses = lenses_by_lnum[lens.range.start.line]
if not line_lenses then
@@ -176,17 +181,21 @@ end
--- Store lenses for a specific buffer and client
---
----@param lenses table of lenses to store (`CodeLens[] | null`)
----@param bufnr number
----@param client_id number
+---@param lenses? lsp.CodeLens[] lenses to store
+---@param bufnr integer
+---@param client_id integer
function M.save(lenses, bufnr, client_id)
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+
local lenses_by_client = lens_cache_by_buf[bufnr]
if not lenses_by_client then
lenses_by_client = {}
lens_cache_by_buf[bufnr] = lenses_by_client
local ns = namespaces[client_id]
api.nvim_buf_attach(bufnr, false, {
- on_detach = function(b)
+ on_detach = function(_, b)
lens_cache_by_buf[b] = nil
end,
on_lines = function(_, b, _, first_lnum, last_lnum)
@@ -197,7 +206,10 @@ function M.save(lenses, bufnr, client_id)
lenses_by_client[client_id] = lenses
end
----@private
+---@param lenses? lsp.CodeLens[]
+---@param bufnr integer
+---@param client_id integer
+---@param callback fun()
local function resolve_lenses(lenses, bufnr, client_id, callback)
lenses = lenses or {}
local num_lens = vim.tbl_count(lenses)
@@ -206,7 +218,6 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
return
end
- ---@private
local function countdown()
num_lens = num_lens - 1
if num_lens == 0 then
@@ -220,19 +231,24 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
countdown()
else
client.request('codeLens/resolve', lens, function(_, result)
- if result and result.command then
+ if api.nvim_buf_is_loaded(bufnr) and result and result.command then
lens.command = result.command
-- Eager display to have some sort of incremental feedback
-- Once all lenses got resolved there will be a full redraw for all lenses
-- So that multiple lens per line are properly displayed
- api.nvim_buf_set_extmark(
- bufnr,
- ns,
- lens.range.start.line,
- 0,
- { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
- )
+
+ local num_lines = api.nvim_buf_line_count(bufnr)
+ if lens.range.start.line <= num_lines then
+ api.nvim_buf_set_extmark(
+ bufnr,
+ ns,
+ lens.range.start.line,
+ 0,
+ { virt_text = { { lens.command.title, 'LspCodeLens' } }, hl_mode = 'combine' }
+ )
+ end
end
+
countdown()
end, bufnr)
end
@@ -264,10 +280,10 @@ end
--- It is recommended to trigger this using an autocmd or via keymap.
---
--- Example:
---- <pre>vim
---- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
---- </pre>
---
+--- ```vim
+--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+--- ```
function M.refresh()
local params = {
textDocument = util.make_text_document_params(),
@@ -277,7 +293,7 @@ function M.refresh()
return
end
active_refreshes[bufnr] = true
- vim.lsp.buf_request(0, 'textDocument/codeLens', params, M.on_codelens)
+ vim.lsp.buf_request(0, ms.textDocument_codeLens, params, M.on_codelens)
end
return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 5e2bf75f1b..b6f0cfa0b3 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1,18 +1,18 @@
---@brief lsp-diagnostic
----
----@class Diagnostic
----@field range Range
----@field message string
----@field severity DiagnosticSeverity|nil
----@field code number | string
----@field source string
----@field tags DiagnosticTag[]
----@field relatedInformation DiagnosticRelatedInformation[]
+
+local util = require('vim.lsp.util')
+local protocol = require('vim.lsp.protocol')
+local log = require('vim.lsp.log')
+local ms = protocol.Methods
+
+local api = vim.api
local M = {}
+local augroup = api.nvim_create_augroup('vim_lsp_diagnostic', {})
+
local DEFAULT_CLIENT_ID = -1
----@private
+
local function get_client_id(client_id)
if client_id == nil then
client_id = DEFAULT_CLIENT_ID
@@ -21,15 +21,15 @@ local function get_client_id(client_id)
return client_id
end
----@private
+---@param severity lsp.DiagnosticSeverity
local function severity_lsp_to_vim(severity)
if type(severity) == 'string' then
- severity = vim.lsp.protocol.DiagnosticSeverity[severity]
+ severity = protocol.DiagnosticSeverity[severity]
end
return severity
end
----@private
+---@return lsp.DiagnosticSeverity
local function severity_vim_to_lsp(severity)
if type(severity) == 'string' then
severity = vim.diagnostic.severity[severity]
@@ -37,7 +37,7 @@ local function severity_vim_to_lsp(severity)
return severity
end
----@private
+---@return integer
local function line_byte_from_position(lines, lnum, col, offset_encoding)
if not lines or offset_encoding == 'utf-8' then
return col
@@ -52,7 +52,6 @@ local function line_byte_from_position(lines, lnum, col, offset_encoding)
return col
end
----@private
local function get_buf_lines(bufnr)
if vim.api.nvim_buf_is_loaded(bufnr) then
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
@@ -77,12 +76,36 @@ local function get_buf_lines(bufnr)
return lines
end
----@private
+--- @param diagnostic lsp.Diagnostic
+--- @param client_id integer
+--- @return table?
+local function tags_lsp_to_vim(diagnostic, client_id)
+ local tags ---@type table?
+ for _, tag in ipairs(diagnostic.tags or {}) do
+ if tag == protocol.DiagnosticTag.Unnecessary then
+ tags = tags or {}
+ tags.unnecessary = true
+ elseif tag == protocol.DiagnosticTag.Deprecated then
+ tags = tags or {}
+ tags.deprecated = true
+ else
+ log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id))
+ end
+ end
+ return tags
+end
+
+---@param diagnostics lsp.Diagnostic[]
+---@param bufnr integer
+---@param client_id integer
+---@return Diagnostic[]
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id)
local offset_encoding = client and client.offset_encoding or 'utf-16'
+ ---@diagnostic disable-next-line:no-unknown
return vim.tbl_map(function(diagnostic)
+ ---@cast diagnostic lsp.Diagnostic
local start = diagnostic.range.start
local _end = diagnostic.range['end']
return {
@@ -94,12 +117,12 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
message = diagnostic.message,
source = diagnostic.source,
code = diagnostic.code,
+ _tags = tags_lsp_to_vim(diagnostic, client_id),
user_data = {
lsp = {
-- usage of user_data.lsp.code is deprecated in favor of the top-level code field
code = diagnostic.code,
codeDescription = diagnostic.codeDescription,
- tags = diagnostic.tags,
relatedInformation = diagnostic.relatedInformation,
data = diagnostic.data,
},
@@ -108,9 +131,12 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
end, diagnostics)
end
----@private
+--- @param diagnostics Diagnostic[]
+--- @return lsp.Diagnostic[]
local function diagnostic_vim_to_lsp(diagnostics)
+ ---@diagnostic disable-next-line:no-unknown
return vim.tbl_map(function(diagnostic)
+ ---@cast diagnostic Diagnostic
return vim.tbl_extend('keep', {
-- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = {
@@ -131,26 +157,53 @@ local function diagnostic_vim_to_lsp(diagnostics)
end, diagnostics)
end
-local _client_namespaces = {}
+---@type table<integer, integer>
+local _client_push_namespaces = {}
+
+---@type table<string, integer>
+local _client_pull_namespaces = {}
---- Get the diagnostic namespace associated with an LSP client |vim.diagnostic|.
+--- Get the diagnostic namespace associated with an LSP client |vim.diagnostic| for diagnostics
---
----@param client_id number The id of the LSP client
-function M.get_namespace(client_id)
+---@param client_id integer The id of the LSP client
+---@param is_pull boolean? Whether the namespace is for a pull or push client. Defaults to push
+function M.get_namespace(client_id, is_pull)
vim.validate({ client_id = { client_id, 'n' } })
- if not _client_namespaces[client_id] then
- local client = vim.lsp.get_client_by_id(client_id)
+
+ local client = vim.lsp.get_client_by_id(client_id)
+ if is_pull then
+ local server_id =
+ vim.tbl_get((client or {}).server_capabilities, 'diagnosticProvider', 'identifier')
+ local key = string.format('%d:%s', client_id, server_id or 'nil')
+ local name = string.format(
+ 'vim.lsp.%s.%d.%s',
+ client and client.name or 'unknown',
+ client_id,
+ server_id or 'nil'
+ )
+ local ns = _client_pull_namespaces[key]
+ if not ns then
+ ns = api.nvim_create_namespace(name)
+ _client_pull_namespaces[key] = ns
+ end
+ return ns
+ else
local name = string.format('vim.lsp.%s.%d', client and client.name or 'unknown', client_id)
- _client_namespaces[client_id] = vim.api.nvim_create_namespace(name)
+ local ns = _client_push_namespaces[client_id]
+ if not ns then
+ ns = api.nvim_create_namespace(name)
+ _client_push_namespaces[client_id] = ns
+ end
+ return ns
end
- return _client_namespaces[client_id]
end
--- |lsp-handler| for the method "textDocument/publishDiagnostics"
---
--- See |vim.diagnostic.config()| for configuration options. Handler-specific
--- configuration can be set using |vim.lsp.with()|:
---- <pre>lua
+---
+--- ```lua
--- vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
--- vim.lsp.diagnostic.on_publish_diagnostics, {
--- -- Enable underline, use default values
@@ -168,7 +221,7 @@ end
--- update_in_insert = false,
--- }
--- )
---- </pre>
+--- ```
---
---@param config table Configuration table (see |vim.diagnostic.config()|).
function M.on_publish_diagnostics(_, result, ctx, config)
@@ -186,7 +239,7 @@ function M.on_publish_diagnostics(_, result, ctx, config)
end
client_id = get_client_id(client_id)
- local namespace = M.get_namespace(client_id)
+ local namespace = M.get_namespace(client_id, false)
if config then
for _, opt in pairs(config) do
@@ -206,13 +259,82 @@ function M.on_publish_diagnostics(_, result, ctx, config)
vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
end
---- Clear diagnostics and diagnostic cache.
+--- |lsp-handler| for the method "textDocument/diagnostic"
+---
+--- See |vim.diagnostic.config()| for configuration options. Handler-specific
+--- configuration can be set using |vim.lsp.with()|:
+---
+--- ```lua
+--- vim.lsp.handlers["textDocument/diagnostic"] = vim.lsp.with(
+--- vim.lsp.diagnostic.on_diagnostic, {
+--- -- Enable underline, use default values
+--- underline = true,
+--- -- Enable virtual text, override spacing to 4
+--- virtual_text = {
+--- spacing = 4,
+--- },
+--- -- Use a function to dynamically turn signs off
+--- -- and on, using buffer local variables
+--- signs = function(namespace, bufnr)
+--- return vim.b[bufnr].show_signs == true
+--- end,
+--- -- Disable a feature
+--- update_in_insert = false,
+--- }
+--- )
+--- ```
+---
+---@param config table Configuration table (see |vim.diagnostic.config()|).
+function M.on_diagnostic(_, result, ctx, config)
+ local client_id = ctx.client_id
+ local uri = ctx.params.textDocument.uri
+ local fname = vim.uri_to_fname(uri)
+
+ if result == nil then
+ return
+ end
+
+ if result.kind == 'unchanged' then
+ return
+ end
+
+ local diagnostics = result.items
+ if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ return
+ end
+ local bufnr = vim.fn.bufadd(fname)
+
+ if not bufnr then
+ return
+ end
+
+ client_id = get_client_id(client_id)
+
+ local namespace = M.get_namespace(client_id, true)
+
+ if config then
+ for _, opt in pairs(config) do
+ if type(opt) == 'table' and not opt.severity and opt.severity_limit then
+ opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
+ end
+ end
+
+ -- Persist configuration to ensure buffer reloads use the same
+ -- configuration. To make lsp.with configuration work (See :help
+ -- lsp-handler-configuration)
+ vim.diagnostic.config(config, namespace)
+ end
+
+ vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+end
+
+--- Clear push diagnostics and diagnostic cache.
---
--- Diagnostic producers should prefer |vim.diagnostic.reset()|. However,
--- this method signature is still used internally in some parts of the LSP
--- implementation so it's simply marked @private rather than @deprecated.
---
----@param client_id number
+---@param client_id integer
---@param buffer_client_map table map of buffers to active clients
---@private
function M.reset(client_id, buffer_client_map)
@@ -220,7 +342,7 @@ function M.reset(client_id, buffer_client_map)
vim.schedule(function()
for bufnr, client_ids in pairs(buffer_client_map) do
if client_ids[client_id] then
- local namespace = M.get_namespace(client_id)
+ local namespace = M.get_namespace(client_id, false)
vim.diagnostic.reset(namespace, bufnr)
end
end
@@ -232,14 +354,14 @@ end
--- Marked private as this is used internally by the LSP subsystem, but
--- most users should instead prefer |vim.diagnostic.get()|.
---
----@param bufnr number|nil The buffer number
----@param line_nr number|nil The line number
+---@param bufnr integer|nil The buffer number
+---@param line_nr integer|nil The line number
---@param opts table|nil Configuration keys
--- - severity: (DiagnosticSeverity, default nil)
--- - Only return diagnostics with this severity. Overrides severity_limit
--- - severity_limit: (DiagnosticSeverity, default nil)
--- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
----@param client_id|nil number the client id
+---@param client_id integer|nil the client id
---@return table Table with map of line number to list of diagnostics.
--- Structured: { [1] = {...}, [5] = {.... } }
---@private
@@ -252,7 +374,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
end
if client_id then
- opts.namespace = M.get_namespace(client_id)
+ opts.namespace = M.get_namespace(client_id, false)
end
if not line_nr then
@@ -264,4 +386,95 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts))
end
+--- Clear diagnostics from pull based clients
+--- @private
+local function clear(bufnr)
+ for _, namespace in pairs(_client_pull_namespaces) do
+ vim.diagnostic.reset(namespace, bufnr)
+ end
+end
+
+---@class lsp.diagnostic.bufstate
+---@field enabled boolean Whether inlay hints are enabled for this buffer
+---@type table<integer, lsp.diagnostic.bufstate>
+local bufstates = {}
+
+--- Disable pull diagnostics for a buffer
+--- @private
+local function disable(bufnr)
+ local bufstate = bufstates[bufnr]
+ if bufstate then
+ bufstate.enabled = false
+ end
+ clear(bufnr)
+end
+
+--- Refresh diagnostics, only if we have attached clients that support it
+---@param bufnr (integer) buffer number
+---@param opts? table Additional options to pass to util._refresh
+---@private
+local function _refresh(bufnr, opts)
+ opts = opts or {}
+ opts['bufnr'] = bufnr
+ util._refresh(ms.textDocument_diagnostic, opts)
+end
+
+--- Enable pull diagnostics for a buffer
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@private
+function M._enable(bufnr)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
+ if not bufstates[bufnr] then
+ bufstates[bufnr] = { enabled = true }
+
+ api.nvim_create_autocmd('LspNotify', {
+ buffer = bufnr,
+ callback = function(opts)
+ if
+ opts.data.method ~= ms.textDocument_didChange
+ and opts.data.method ~= ms.textDocument_didOpen
+ then
+ return
+ end
+ if bufstates[bufnr] and bufstates[bufnr].enabled then
+ _refresh(bufnr, { only_visible = true, client_id = opts.data.client_id })
+ end
+ end,
+ group = augroup,
+ })
+
+ api.nvim_buf_attach(bufnr, false, {
+ on_reload = function()
+ if bufstates[bufnr] and bufstates[bufnr].enabled then
+ _refresh(bufnr)
+ end
+ end,
+ on_detach = function()
+ disable(bufnr)
+ end,
+ })
+
+ api.nvim_create_autocmd('LspDetach', {
+ buffer = bufnr,
+ callback = function(args)
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_diagnostic })
+
+ if
+ not vim.iter(clients):any(function(c)
+ return c.id ~= args.data.client_id
+ end)
+ then
+ disable(bufnr)
+ end
+ end,
+ group = augroup,
+ })
+ else
+ bufstates[bufnr].enabled = true
+ end
+end
+
return M
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 5096100a60..6fde55cf04 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -1,5 +1,6 @@
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
+local ms = protocol.Methods
local util = require('vim.lsp.util')
local api = vim.api
@@ -7,82 +8,70 @@ local M = {}
-- FIXME: DOC: Expose in vimdocs
----@private
--- Writes to error buffer.
----@param ... (table of strings) Will be concatenated before being written
+---@param ... string Will be concatenated before being written
local function err_message(...)
vim.notify(table.concat(vim.tbl_flatten({ ... })), vim.log.levels.ERROR)
api.nvim_command('redraw')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
-M['workspace/executeCommand'] = function(_, _, _, _)
+M[ms.workspace_executeCommand] = function(_, _, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
----@private
-local function progress_handler(_, result, ctx, _)
- local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format('id=%d', client_id)
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
+---@param result lsp.ProgressParams
+---@param ctx lsp.HandlerContext
+M[ms.dollar_progress] = function(_, result, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down during progress update')
+ err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- local val = result.value -- unspecified yet
- local token = result.token -- string or number
+ local kind = nil
+ local value = result.value
- if type(val) ~= 'table' then
- val = { content = val }
- end
- if val.kind then
- if val.kind == 'begin' then
- client.messages.progress[token] = {
- title = val.title,
- cancellable = val.cancellable,
- message = val.message,
- percentage = val.percentage,
- }
- elseif val.kind == 'report' then
- client.messages.progress[token].cancellable = val.cancellable
- client.messages.progress[token].message = val.message
- client.messages.progress[token].percentage = val.percentage
- elseif val.kind == 'end' then
- if client.messages.progress[token] == nil then
- err_message('LSP[', client_name, '] received `end` message with no corresponding `begin`')
- else
- client.messages.progress[token].message = val.message
- client.messages.progress[token].done = true
+ if type(value) == 'table' then
+ kind = value.kind
+ -- Carry over title of `begin` messages to `report` and `end` messages
+ -- So that consumers always have it available, even if they consume a
+ -- subset of the full sequence
+ if kind == 'begin' then
+ client.progress.pending[result.token] = value.title
+ else
+ value.title = client.progress.pending[result.token]
+ if kind == 'end' then
+ client.progress.pending[result.token] = nil
end
end
- else
- client.messages.progress[token] = val
- client.messages.progress[token].done = true
end
- api.nvim_exec_autocmds('User', { pattern = 'LspProgressUpdate', modeline = false })
-end
+ client.progress:push(result)
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
-M['$/progress'] = progress_handler
+ api.nvim_exec_autocmds('LspProgress', {
+ pattern = kind,
+ modeline = false,
+ data = { client_id = ctx.client_id, result = result },
+ })
+end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
-M['window/workDoneProgress/create'] = function(_, result, ctx)
- local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
- local token = result.token -- string or number
- local client_name = client and client.name or string.format('id=%d', client_id)
+---@param result lsp.WorkDoneProgressCreateParams
+---@param ctx lsp.HandlerContext
+M[ms.window_workDoneProgress_create] = function(_, result, ctx)
+ local client = vim.lsp.get_client_by_id(ctx.client_id)
if not client then
- err_message('LSP[', client_name, '] client has shut down while creating progress report')
+ err_message('LSP[id=', tostring(ctx.client_id), '] client has shut down during progress update')
return vim.NIL
end
- client.messages.progress[token] = {}
+ client.progress:push(result)
return vim.NIL
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
---@param result lsp.ShowMessageRequestParams
-M['window/showMessageRequest'] = function(_, result)
+M[ms.window_showMessageRequest] = function(_, result)
local actions = result.actions or {}
local co, is_main = coroutine.running()
if co and not is_main then
@@ -117,20 +106,52 @@ M['window/showMessageRequest'] = function(_, result)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
-M['client/registerCapability'] = function(_, _, ctx)
+M[ms.client_registerCapability] = function(_, result, ctx)
local client_id = ctx.client_id
- local warning_tpl = 'The language server %s triggers a registerCapability '
- .. 'handler despite dynamicRegistration set to false. '
- .. 'Report upstream, this warning is harmless'
+ ---@type lsp.Client
local client = vim.lsp.get_client_by_id(client_id)
- local client_name = client and client.name or string.format('id=%d', client_id)
- local warning = string.format(warning_tpl, client_name)
- log.warn(warning)
+
+ client.dynamic_capabilities:register(result.registrations)
+ for bufnr, _ in pairs(client.attached_buffers) do
+ vim.lsp._set_defaults(client, bufnr)
+ end
+
+ ---@type string[]
+ local unsupported = {}
+ for _, reg in ipairs(result.registrations) do
+ if reg.method == ms.workspace_didChangeWatchedFiles then
+ require('vim.lsp._watchfiles').register(reg, ctx)
+ elseif not client.dynamic_capabilities:supports_registration(reg.method) then
+ unsupported[#unsupported + 1] = reg.method
+ end
+ end
+ if #unsupported > 0 then
+ local warning_tpl = 'The language server %s triggers a registerCapability '
+ .. 'handler for %s despite dynamicRegistration set to false. '
+ .. 'Report upstream, this warning is harmless'
+ local client_name = client and client.name or string.format('id=%d', client_id)
+ local warning = string.format(warning_tpl, client_name, table.concat(unsupported, ', '))
+ log.warn(warning)
+ end
+ return vim.NIL
+end
+
+--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
+M[ms.client_unregisterCapability] = function(_, result, ctx)
+ local client_id = ctx.client_id
+ local client = vim.lsp.get_client_by_id(client_id)
+ client.dynamic_capabilities:unregister(result.unregisterations)
+
+ for _, unreg in ipairs(result.unregisterations) do
+ if unreg.method == ms.workspace_didChangeWatchedFiles then
+ require('vim.lsp._watchfiles').unregister(unreg, ctx)
+ end
+ end
return vim.NIL
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
-M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
+M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
assert(
workspace_edit,
'workspace/applyEdit must be called with `ApplyWorkspaceEditParams`. Server is violating the specification'
@@ -150,7 +171,7 @@ M['workspace/applyEdit'] = function(_, workspace_edit, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
-M['workspace/configuration'] = function(_, result, ctx)
+M[ms.workspace_configuration] = function(_, result, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then
@@ -180,7 +201,7 @@ M['workspace/configuration'] = function(_, result, ctx)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
-M['workspace/workspaceFolders'] = function(_, _, ctx)
+M[ms.workspace_workspaceFolders] = function(_, _, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
if not client then
@@ -190,16 +211,24 @@ M['workspace/workspaceFolders'] = function(_, _, ctx)
return client.workspace_folders or vim.NIL
end
-M['textDocument/publishDiagnostics'] = function(...)
+M[ms.textDocument_publishDiagnostics] = function(...)
return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
end
-M['textDocument/codeLens'] = function(...)
+M[ms.textDocument_diagnostic] = function(...)
+ return require('vim.lsp.diagnostic').on_diagnostic(...)
+end
+
+M[ms.textDocument_codeLens] = function(...)
return require('vim.lsp.codelens').on_codelens(...)
end
+M[ms.textDocument_inlayHint] = function(...)
+ return require('vim.lsp.inlay_hint').on_inlayhint(...)
+end
+
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
-M['textDocument/references'] = function(_, result, ctx, config)
+M[ms.textDocument_references] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No references found')
else
@@ -221,7 +250,6 @@ M['textDocument/references'] = function(_, result, ctx, config)
end
end
----@private
--- Return a function that converts LSP responses to list items and opens the list
---
--- The returned function has an optional {config} parameter that accepts a table
@@ -256,7 +284,7 @@ local function response_to_list(map_result, entity, title_fn)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
-M['textDocument/documentSymbol'] = response_to_list(
+M[ms.textDocument_documentSymbol] = response_to_list(
util.symbols_to_items,
'document symbols',
function(ctx)
@@ -266,12 +294,12 @@ M['textDocument/documentSymbol'] = response_to_list(
)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
-M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
+M[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
return string.format("Symbols matching '%s'", ctx.params.query)
end)
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
-M['textDocument/rename'] = function(_, result, ctx, _)
+M[ms.textDocument_rename] = function(_, result, ctx, _)
if not result then
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
return
@@ -281,7 +309,7 @@ M['textDocument/rename'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
-M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
+M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
if not result then
return
end
@@ -290,7 +318,7 @@ M['textDocument/rangeFormatting'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
-M['textDocument/formatting'] = function(_, result, ctx, _)
+M[ms.textDocument_formatting] = function(_, result, ctx, _)
if not result then
return
end
@@ -299,7 +327,7 @@ M['textDocument/formatting'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-M['textDocument/completion'] = function(_, result, _, _)
+M[ms.textDocument_completion] = function(_, result, _, _)
if vim.tbl_isempty(result or {}) then
return
end
@@ -314,20 +342,22 @@ M['textDocument/completion'] = function(_, result, _, _)
end
--- |lsp-handler| for the method "textDocument/hover"
---- <pre>lua
---- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
---- vim.lsp.handlers.hover, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single",
---- -- add the title in hover float window
---- title = "hover"
---- }
---- )
---- </pre>
+---
+--- ```lua
+--- vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
+--- vim.lsp.handlers.hover, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single",
+--- -- add the title in hover float window
+--- title = "hover"
+--- }
+--- )
+--- ```
+---
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |nvim_open_win()|
+--- - See |vim.lsp.util.open_floating_preview()| for more options.
function M.hover(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -341,21 +371,28 @@ function M.hover(_, result, ctx, config)
end
return
end
- local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
- markdown_lines = util.trim_empty_lines(markdown_lines)
- if vim.tbl_isempty(markdown_lines) then
- vim.notify('No information available')
+ local format = 'markdown'
+ local contents ---@type string[]
+ if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
+ format = 'plaintext'
+ contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
+ else
+ contents = util.convert_input_to_markdown_lines(result.contents)
+ end
+ if vim.tbl_isempty(contents) then
+ if config.silent ~= true then
+ vim.notify('No information available')
+ end
return
end
- return util.open_floating_preview(markdown_lines, 'markdown', config)
+ return util.open_floating_preview(contents, format, config)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
-M['textDocument/hover'] = M.hover
+M[ms.textDocument_hover] = M.hover
----@private
--- Jumps to a location. Used as a handler for multiple LSP methods.
----@param _ (not used)
+---@param _ nil not used
---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (table) table containing the context of the request, including the method
---(`textDocument/definition` can return `Location` or `Location[]`
@@ -370,50 +407,54 @@ local function location_handler(_, result, ctx, config)
-- textDocument/definition can return Location or Location[]
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+ if not vim.tbl_islist(result) then
+ result = { result }
+ end
- if vim.tbl_islist(result) then
- local title = 'LSP locations'
- local items = util.locations_to_items(result, client.offset_encoding)
+ local title = 'LSP locations'
+ local items = util.locations_to_items(result, client.offset_encoding)
- if config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items })
- else
- if #result == 1 then
- util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
- return
- end
- vim.fn.setqflist({}, ' ', { title = title, items = items })
- api.nvim_command('botright copen')
- end
- else
- util.jump_to_location(result, client.offset_encoding, config.reuse_win)
+ if config.on_list then
+ assert(type(config.on_list) == 'function', 'on_list is not a function')
+ config.on_list({ title = title, items = items })
+ return
+ end
+ if #result == 1 then
+ util.jump_to_location(result[1], client.offset_encoding, config.reuse_win)
+ return
end
+ vim.fn.setqflist({}, ' ', { title = title, items = items })
+ api.nvim_command('botright copen')
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
-M['textDocument/declaration'] = location_handler
+M[ms.textDocument_declaration] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
-M['textDocument/definition'] = location_handler
+M[ms.textDocument_definition] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
-M['textDocument/typeDefinition'] = location_handler
+M[ms.textDocument_typeDefinition] = location_handler
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
-M['textDocument/implementation'] = location_handler
+M[ms.textDocument_implementation] = location_handler
--- |lsp-handler| for the method "textDocument/signatureHelp".
+---
--- The active parameter is highlighted with |hl-LspSignatureActiveParameter|.
---- <pre>lua
---- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
---- vim.lsp.handlers.signature_help, {
---- -- Use a sharp border with `FloatBorder` highlights
---- border = "single"
---- }
---- )
---- </pre>
+---
+--- ```lua
+--- vim.lsp.handlers["textDocument/signatureHelp"] = vim.lsp.with(
+--- vim.lsp.handlers.signature_help, {
+--- -- Use a sharp border with `FloatBorder` highlights
+--- border = "single"
+--- }
+--- )
+--- ```
+---
+---@param result table Response from the language server
+---@param ctx table Client context
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
---- - See |nvim_open_win()|
+--- - See |vim.lsp.util.open_floating_preview()| for more options
function M.signature_help(_, result, ctx, config)
config = config or {}
config.focus_id = ctx.method
@@ -432,10 +473,9 @@ function M.signature_help(_, result, ctx, config)
local client = vim.lsp.get_client_by_id(ctx.client_id)
local triggers =
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
- local ft = api.nvim_buf_get_option(ctx.bufnr, 'filetype')
+ local ft = vim.bo[ctx.bufnr].filetype
local lines, hl = util.convert_signature_help_to_markdown_lines(result, ft, triggers)
- lines = util.trim_empty_lines(lines)
- if vim.tbl_isempty(lines) then
+ if not lines or vim.tbl_isempty(lines) then
if config.silent ~= true then
print('No signature help available')
end
@@ -443,16 +483,18 @@ function M.signature_help(_, result, ctx, config)
end
local fbuf, fwin = util.open_floating_preview(lines, 'markdown', config)
if hl then
- api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', 0, unpack(hl))
+ -- Highlight the second line if the signature is wrapped in a Markdown code block.
+ local line = vim.startswith(lines[1], '```') and 1 or 0
+ api.nvim_buf_add_highlight(fbuf, -1, 'LspSignatureActiveParameter', line, unpack(hl))
end
return fbuf, fwin
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
-M['textDocument/signatureHelp'] = M.signature_help
+M[ms.textDocument_signatureHelp] = M.signature_help
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
-M['textDocument/documentHighlight'] = function(_, result, ctx, _)
+M[ms.textDocument_documentHighlight] = function(_, result, ctx, _)
if not result then
return
end
@@ -469,8 +511,9 @@ end
--- Displays call hierarchy in the quickfix window.
---
---@param direction `"from"` for incoming calls and `"to"` for outgoing calls
----@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
----@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
+---@return function
+--- `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
+--- `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
local make_call_hierarchy_handler = function(direction)
return function(_, result)
if not result then
@@ -494,13 +537,13 @@ local make_call_hierarchy_handler = function(direction)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
-M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from')
+M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
-M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to')
+M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
-M['window/logMessage'] = function(_, result, ctx, _)
+M[ms.window_logMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
local client_id = ctx.client_id
@@ -522,7 +565,7 @@ M['window/logMessage'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
-M['window/showMessage'] = function(_, result, ctx, _)
+M[ms.window_showMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
local client_id = ctx.client_id
@@ -541,27 +584,19 @@ M['window/showMessage'] = function(_, result, ctx, _)
end
--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
-M['window/showDocument'] = function(_, result, ctx, _)
+M[ms.window_showDocument] = function(_, result, ctx, _)
local uri = result.uri
if result.external then
-- TODO(lvimuser): ask the user for confirmation
- local cmd
- if vim.fn.has('win32') == 1 then
- cmd = { 'cmd.exe', '/c', 'start', '""', uri }
- elseif vim.fn.has('macunix') == 1 then
- cmd = { 'open', uri }
- else
- cmd = { 'xdg-open', uri }
- end
+ local ret, err = vim.ui.open(uri)
- local ret = vim.fn.system(cmd)
- if vim.v.shell_error ~= 0 then
+ if ret == nil or ret.code ~= 0 then
return {
success = false,
error = {
code = protocol.ErrorCodes.UnknownErrorCode,
- message = ret,
+ message = ret and ret.stderr or err,
},
}
end
@@ -573,7 +608,7 @@ M['window/showDocument'] = function(_, result, ctx, _)
local client = vim.lsp.get_client_by_id(client_id)
local client_name = client and client.name or string.format('id=%d', client_id)
if not client then
- err_message({ 'LSP[', client_name, '] client has shut down after sending ', ctx.method })
+ err_message('LSP[', client_name, '] client has shut down after sending ', ctx.method)
return vim.NIL
end
@@ -589,6 +624,11 @@ M['window/showDocument'] = function(_, result, ctx, _)
return { success = success or false }
end
+---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
+M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config)
+ return require('vim.lsp.inlay_hint').on_refresh(err, result, ctx, config)
+end
+
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
@@ -622,4 +662,3 @@ for k, fn in pairs(M) do
end
return M
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 987707e661..fe06006108 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -2,8 +2,8 @@ local M = {}
--- Performs a healthcheck for LSP
function M.check()
- local report_info = vim.health.report_info
- local report_warn = vim.health.report_warn
+ local report_info = vim.health.info
+ local report_warn = vim.health.warn
local log = require('vim.lsp.log')
local current_log_level = log.get_level()
@@ -22,18 +22,25 @@ function M.check()
local log_path = vim.lsp.get_log_path()
report_info(string.format('Log path: %s', log_path))
- local log_file = vim.loop.fs_stat(log_path)
+ local log_file = vim.uv.fs_stat(log_path)
local log_size = log_file and log_file.size or 0
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
report_fn(string.format('Log size: %d KB', log_size / 1000))
- local clients = vim.lsp.get_active_clients()
- vim.health.report_start('vim.lsp: Active Clients')
+ local clients = vim.lsp.get_clients()
+ vim.health.start('vim.lsp: Active Clients')
if next(clients) then
for _, client in pairs(clients) do
+ local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',')
report_info(
- string.format('%s (id=%s, root_dir=%s)', client.name, client.id, client.config.root_dir)
+ string.format(
+ '%s (id=%s, root_dir=%s, attached_to=[%s])',
+ client.name,
+ client.id,
+ vim.fn.fnamemodify(client.config.root_dir, ':~'),
+ attached_to
+ )
)
end
else
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
new file mode 100644
index 0000000000..4f7a3b0076
--- /dev/null
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -0,0 +1,377 @@
+local util = require('vim.lsp.util')
+local log = require('vim.lsp.log')
+local ms = require('vim.lsp.protocol').Methods
+local api = vim.api
+local M = {}
+
+---@class lsp.inlay_hint.bufstate
+---@field version? integer
+---@field client_hint? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints)
+---@field applied table<integer, integer> Last version of hints applied to this line
+---@field enabled boolean Whether inlay hints are enabled for this buffer
+---@type table<integer, lsp.inlay_hint.bufstate>
+local bufstates = {}
+
+local namespace = api.nvim_create_namespace('vim_lsp_inlayhint')
+local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
+
+--- |lsp-handler| for the method `textDocument/inlayHint`
+--- Store hints for a specific buffer and client
+---@private
+function M.on_inlayhint(err, result, ctx, _)
+ if err then
+ local _ = log.error() and log.error('inlayhint', err)
+ return
+ end
+ local bufnr = ctx.bufnr
+ if util.buf_versions[bufnr] ~= ctx.version then
+ return
+ end
+ local client_id = ctx.client_id
+ if not result then
+ return
+ end
+ local bufstate = bufstates[bufnr]
+ if not bufstate or not bufstate.enabled then
+ return
+ end
+ if not (bufstate.client_hint and bufstate.version) then
+ bufstate.client_hint = vim.defaulttable()
+ bufstate.version = ctx.version
+ end
+ local hints_by_client = bufstate.client_hint
+ local client = vim.lsp.get_client_by_id(client_id)
+
+ local new_hints_by_lnum = vim.defaulttable()
+ local num_unprocessed = #result
+ if num_unprocessed == 0 then
+ hints_by_client[client_id] = {}
+ bufstate.version = ctx.version
+ api.nvim__buf_redraw_range(bufnr, 0, -1)
+ return
+ end
+
+ local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local function pos_to_byte(position)
+ local col = position.character
+ if col > 0 then
+ local line = lines[position.line + 1] or ''
+ local ok, convert_result
+ ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding)
+ if ok then
+ return convert_result
+ end
+ return math.min(#line, col)
+ end
+ return col
+ end
+
+ for _, hint in ipairs(result) do
+ local lnum = hint.position.line
+ hint.position.character = pos_to_byte(hint.position)
+ table.insert(new_hints_by_lnum[lnum], hint)
+ end
+
+ hints_by_client[client_id] = new_hints_by_lnum
+ bufstate.version = ctx.version
+ api.nvim__buf_redraw_range(bufnr, 0, -1)
+end
+
+--- |lsp-handler| for the method `textDocument/inlayHint/refresh`
+---@private
+function M.on_refresh(err, _, ctx, _)
+ if err then
+ return vim.NIL
+ end
+ for _, bufnr in ipairs(vim.lsp.get_buffers_by_client_id(ctx.client_id)) do
+ for _, winid in ipairs(api.nvim_list_wins()) do
+ if api.nvim_win_get_buf(winid) == bufnr then
+ local bufstate = bufstates[bufnr]
+ if bufstate then
+ util._refresh(ms.textDocument_inlayHint, { bufnr = bufnr })
+ break
+ end
+ end
+ end
+ end
+
+ return vim.NIL
+end
+
+--- @class vim.lsp.inlay_hint.get.filter
+--- @field bufnr integer?
+--- @field range lsp.Range?
+---
+--- @class vim.lsp.inlay_hint.get.ret
+--- @field bufnr integer
+--- @field client_id integer
+--- @field inlay_hint lsp.InlayHint
+
+--- Get the list of inlay hints, (optionally) restricted by buffer or range.
+---
+--- Example usage:
+---
+--- ```lua
+--- local hint = vim.lsp.inlay_hint.get({ bufnr = 0 })[1] -- 0 for current buffer
+---
+--- local client = vim.lsp.get_client_by_id(hint.client_id)
+--- resolved_hint = client.request_sync('inlayHint/resolve', hint.inlay_hint, 100, 0).result
+--- vim.lsp.util.apply_text_edits(resolved_hint.textEdits, 0, client.encoding)
+---
+--- location = resolved_hint.label[1].location
+--- client.request('textDocument/hover', {
+--- textDocument = { uri = location.uri },
+--- position = location.range.start,
+--- })
+--- ```
+---
+--- @param filter vim.lsp.inlay_hint.get.filter?
+--- Optional filters |kwargs|:
+--- - bufnr (integer?): 0 for current buffer
+--- - range (lsp.Range?)
+---
+--- @return vim.lsp.inlay_hint.get.ret[]
+--- Each list item is a table with the following fields:
+--- - bufnr (integer)
+--- - client_id (integer)
+--- - inlay_hint (lsp.InlayHint)
+---
+--- @since 12
+function M.get(filter)
+ vim.validate({ filter = { filter, 'table', true } })
+ filter = filter or {}
+
+ local bufnr = filter.bufnr
+ if not bufnr then
+ --- @type vim.lsp.inlay_hint.get.ret[]
+ local hints = {}
+ --- @param buf integer
+ vim.tbl_map(function(buf)
+ vim.list_extend(hints, M.get(vim.tbl_extend('keep', { bufnr = buf }, filter)))
+ end, vim.api.nvim_list_bufs())
+ return hints
+ elseif bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
+ local bufstate = bufstates[bufnr]
+ if not (bufstate and bufstate.client_hint) then
+ return {}
+ end
+
+ local clients = vim.lsp.get_clients({
+ bufnr = bufnr,
+ method = ms.textDocument_inlayHint,
+ })
+ if #clients == 0 then
+ return {}
+ end
+
+ local range = filter.range
+ if not range then
+ range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = api.nvim_buf_line_count(bufnr), character = 0 },
+ }
+ end
+
+ --- @type vim.lsp.inlay_hint.get.ret[]
+ local hints = {}
+ for _, client in pairs(clients) do
+ local hints_by_lnum = bufstate.client_hint[client.id]
+ if hints_by_lnum then
+ for lnum = range.start.line, range['end'].line do
+ local line_hints = hints_by_lnum[lnum] or {}
+ for _, hint in pairs(line_hints) do
+ local line, char = hint.position.line, hint.position.character
+ if
+ (line > range.start.line or char >= range.start.character)
+ and (line < range['end'].line or char <= range['end'].character)
+ then
+ table.insert(hints, {
+ bufnr = bufnr,
+ client_id = client.id,
+ inlay_hint = hint,
+ })
+ end
+ end
+ end
+ end
+ end
+ return hints
+end
+
+--- Clear inlay hints
+---@param bufnr (integer) Buffer handle, or 0 for current
+local function clear(bufnr)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+ if not bufstates[bufnr] then
+ return
+ end
+ local bufstate = bufstates[bufnr]
+ local client_lens = (bufstate or {}).client_hint or {}
+ local client_ids = vim.tbl_keys(client_lens)
+ for _, iter_client_id in ipairs(client_ids) do
+ if bufstate then
+ bufstate.client_hint[iter_client_id] = {}
+ end
+ end
+ api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
+ api.nvim__buf_redraw_range(bufnr, 0, -1)
+end
+
+--- Disable inlay hints for a buffer
+---@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+local function _disable(bufnr)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+ clear(bufnr)
+ if bufstates[bufnr] then
+ bufstates[bufnr] = { enabled = false, applied = {} }
+ end
+end
+
+--- Refresh inlay hints, only if we have attached clients that support it
+---@param bufnr (integer) Buffer handle, or 0 for current
+---@param opts? table Additional options to pass to util._refresh
+---@private
+local function _refresh(bufnr, opts)
+ opts = opts or {}
+ opts['bufnr'] = bufnr
+ util._refresh(ms.textDocument_inlayHint, opts)
+end
+
+--- Enable inlay hints for a buffer
+---@param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+local function _enable(bufnr)
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+ local bufstate = bufstates[bufnr]
+ if not bufstate then
+ bufstates[bufnr] = { applied = {}, enabled = true }
+ api.nvim_create_autocmd('LspNotify', {
+ buffer = bufnr,
+ callback = function(opts)
+ if
+ opts.data.method ~= ms.textDocument_didChange
+ and opts.data.method ~= ms.textDocument_didOpen
+ then
+ return
+ end
+ if bufstates[bufnr] and bufstates[bufnr].enabled then
+ _refresh(bufnr, { client_id = opts.data.client_id })
+ end
+ end,
+ group = augroup,
+ })
+ _refresh(bufnr)
+ api.nvim_buf_attach(bufnr, false, {
+ on_reload = function(_, cb_bufnr)
+ clear(cb_bufnr)
+ if bufstates[cb_bufnr] and bufstates[cb_bufnr].enabled then
+ bufstates[cb_bufnr].applied = {}
+ _refresh(cb_bufnr)
+ end
+ end,
+ on_detach = function(_, cb_bufnr)
+ _disable(cb_bufnr)
+ end,
+ })
+ api.nvim_create_autocmd('LspDetach', {
+ buffer = bufnr,
+ callback = function(args)
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_inlayHint })
+
+ if
+ not vim.iter(clients):any(function(c)
+ return c.id ~= args.data.client_id
+ end)
+ then
+ _disable(bufnr)
+ end
+ end,
+ group = augroup,
+ })
+ else
+ bufstate.enabled = true
+ _refresh(bufnr)
+ end
+end
+
+api.nvim_set_decoration_provider(namespace, {
+ on_win = function(_, _, bufnr, topline, botline)
+ local bufstate = bufstates[bufnr]
+ if not bufstate then
+ return
+ end
+
+ if bufstate.version ~= util.buf_versions[bufnr] then
+ return
+ end
+ local hints_by_client = bufstate.client_hint
+
+ for lnum = topline, botline do
+ if bufstate.applied[lnum] ~= bufstate.version then
+ api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1)
+ for _, hints_by_lnum in pairs(hints_by_client) do
+ local line_hints = hints_by_lnum[lnum] or {}
+ for _, hint in pairs(line_hints) do
+ local text = ''
+ if type(hint.label) == 'string' then
+ text = hint.label
+ else
+ for _, part in ipairs(hint.label) do
+ text = text .. part.value
+ end
+ end
+ local vt = {}
+ if hint.paddingLeft then
+ vt[#vt + 1] = { ' ' }
+ end
+ vt[#vt + 1] = { text, 'LspInlayHint' }
+ if hint.paddingRight then
+ vt[#vt + 1] = { ' ' }
+ end
+ api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, {
+ virt_text_pos = 'inline',
+ ephemeral = false,
+ virt_text = vt,
+ })
+ end
+ end
+ bufstate.applied[lnum] = bufstate.version
+ end
+ end
+ end,
+})
+
+--- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+--- @return boolean
+--- @since 12
+function M.is_enabled(bufnr)
+ vim.validate({ bufnr = { bufnr, 'number', true } })
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+ return bufstates[bufnr] and bufstates[bufnr].enabled or false
+end
+
+--- Enable/disable/toggle inlay hints for a buffer
+---
+--- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
+--- @param enable (boolean|nil) true/nil to enable, false to disable
+--- @since 12
+function M.enable(bufnr, enable)
+ vim.validate({ enable = { enable, 'boolean', true }, bufnr = { bufnr, 'number', true } })
+ if enable == false then
+ _disable(bufnr)
+ else
+ _enable(bufnr)
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index d1a78572aa..6d2e0bc292 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -2,14 +2,12 @@
local log = {}
--- FIXME: DOC
--- Should be exposed in the vim docs.
---
--- Log level dictionary with reverse lookup as well.
---
--- Can be used to lookup the number from the name or the name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
--- Level numbers begin with "TRACE" at 0
+--- Log level dictionary with reverse lookup as well.
+---
+--- Can be used to lookup the number from the name or the name from the number.
+--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
+--- Level numbers begin with "TRACE" at 0
+--- @nodoc
log.levels = vim.deepcopy(vim.log.levels)
-- Default log level is warn.
@@ -20,7 +18,6 @@ local format_func = function(arg)
end
do
- ---@private
local function notify(msg, level)
if vim.in_fast_event() then
vim.schedule(function()
@@ -31,8 +28,7 @@ do
end
end
- local path_sep = vim.loop.os_uname().version:match('Windows') and '\\' or '/'
- ---@private
+ local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/'
local function path_join(...)
return table.concat(vim.tbl_flatten({ ... }), path_sep)
end
@@ -44,13 +40,12 @@ do
vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
--- Returns the log filename.
- ---@returns (string) log filename
+ ---@return string log filename
function log.get_filename()
return logfilename
end
local logfile, openerr
- ---@private
--- Opens log file. Returns true if file is open, false on error
local function open_logfile()
-- Try to open file only once
@@ -68,7 +63,7 @@ do
return false
end
- local log_info = vim.loop.fs_stat(logfilename)
+ local log_info = vim.uv.fs_stat(logfilename)
if log_info and log_info.size > 1e9 then
local warn_msg = string.format(
'LSP client log is large (%d MB): %s',
@@ -141,7 +136,7 @@ end
vim.tbl_add_reverse_lookup(log.levels)
--- Sets the current log level.
----@param level (string|number) One of `vim.lsp.log.levels`
+---@param level (string|integer) One of `vim.lsp.log.levels`
function log.set_level(level)
if type(level) == 'string' then
current_log_level =
@@ -154,7 +149,7 @@ function log.set_level(level)
end
--- Gets the current log level.
----@return string current log level
+---@return integer current log level
function log.get_level()
return current_log_level
end
@@ -167,11 +162,10 @@ function log.set_format_func(handle)
end
--- Checks whether the level is sufficient for logging.
----@param level number log level
+---@param level integer log level
---@returns (bool) true if would log, false if not
function log.should_log(level)
return level >= current_log_level
end
return log
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index 12345b6c8c..a7c3914834 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -20,15 +20,8 @@ function transform_schema_to_table()
end
--]=]
----@class lsp.ShowMessageRequestParams
----@field type lsp.MessageType
----@field message string
----@field actions nil|lsp.MessageActionItem[]
-
----@class lsp.MessageActionItem
----@field title string
-
local constants = {
+ --- @enum lsp.DiagnosticSeverity
DiagnosticSeverity = {
-- Reports an error.
Error = 1,
@@ -40,6 +33,7 @@ local constants = {
Hint = 4,
},
+ --- @enum lsp.DiagnosticTag
DiagnosticTag = {
-- Unused or unnecessary code
Unnecessary = 1,
@@ -60,6 +54,7 @@ local constants = {
},
-- The file event type.
+ ---@enum lsp.FileChangeType
FileChangeType = {
-- The file got created.
Created = 1,
@@ -247,6 +242,7 @@ local constants = {
-- Defines whether the insert text in a completion item should be interpreted as
-- plain text or a snippet.
+ --- @enum lsp.InsertTextFormat
InsertTextFormat = {
-- The primary text to be inserted is treated as a plain string.
PlainText = 1,
@@ -637,9 +633,29 @@ export interface WorkspaceClientCapabilities {
--- Gets a new ClientCapabilities object describing the LSP client
--- capabilities.
+--- @return lsp.ClientCapabilities
function protocol.make_client_capabilities()
return {
+ general = {
+ positionEncodings = {
+ 'utf-16',
+ },
+ },
textDocument = {
+ diagnostic = {
+ dynamicRegistration = false,
+ },
+ inlayHint = {
+ dynamicRegistration = true,
+ resolveSupport = {
+ properties = {
+ 'textEdits',
+ 'tooltip',
+ 'location',
+ 'command',
+ },
+ },
+ },
semanticTokens = {
dynamicRegistration = false,
tokenTypes = {
@@ -702,7 +718,7 @@ function protocol.make_client_capabilities()
didSave = true,
},
codeAction = {
- dynamicRegistration = false,
+ dynamicRegistration = true,
codeActionLiteralSupport = {
codeActionKind = {
@@ -719,6 +735,12 @@ function protocol.make_client_capabilities()
properties = { 'edit' },
},
},
+ formatting = {
+ dynamicRegistration = true,
+ },
+ rangeFormatting = {
+ dynamicRegistration = true,
+ },
completion = {
dynamicRegistration = false,
completionItem = {
@@ -726,7 +748,6 @@ function protocol.make_client_capabilities()
-- this should be disabled out of the box.
-- However, users can turn this back on if they have a snippet plugin.
snippetSupport = false,
-
commitCharactersSupport = false,
preselectSupport = false,
deprecatedSupport = false,
@@ -752,6 +773,7 @@ function protocol.make_client_capabilities()
},
definition = {
linkSupport = true,
+ dynamicRegistration = true,
},
implementation = {
linkSupport = true,
@@ -760,7 +782,7 @@ function protocol.make_client_capabilities()
linkSupport = true,
},
hover = {
- dynamicRegistration = false,
+ dynamicRegistration = true,
contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
},
signatureHelp = {
@@ -795,7 +817,7 @@ function protocol.make_client_capabilities()
hierarchicalDocumentSymbolSupport = true,
},
rename = {
- dynamicRegistration = false,
+ dynamicRegistration = true,
prepareSupport = true,
},
publishDiagnostics = {
@@ -811,6 +833,7 @@ function protocol.make_client_capabilities()
return res
end)(),
},
+ dataSupport = true,
},
callHierarchy = {
dynamicRegistration = false,
@@ -830,9 +853,11 @@ function protocol.make_client_capabilities()
return res
end)(),
},
- hierarchicalWorkspaceSymbolSupport = true,
},
configuration = true,
+ didChangeConfiguration = {
+ dynamicRegistration = false,
+ },
workspaceFolders = true,
applyEdit = true,
workspaceEdit = {
@@ -841,6 +866,13 @@ function protocol.make_client_capabilities()
semanticTokens = {
refreshSupport = true,
},
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ relativePatternSupport = true,
+ },
+ inlayHint = {
+ refreshSupport = true,
+ },
},
experimental = nil,
window = {
@@ -857,10 +889,9 @@ function protocol.make_client_capabilities()
}
end
-local if_nil = vim.F.if_nil
--- Creates a normalized object describing LSP server capabilities.
---@param server_capabilities table Table of capabilities supported by the server
----@return table Normalized table of capabilities
+---@return table|nil Normalized table of capabilities
function protocol.resolve_capabilities(server_capabilities)
local TextDocumentSyncKind = protocol.TextDocumentSyncKind
local textDocumentSync = server_capabilities.textDocumentSync
@@ -878,7 +909,8 @@ function protocol.resolve_capabilities(server_capabilities)
elseif type(textDocumentSync) == 'number' then
-- Backwards compatibility
if not TextDocumentSyncKind[textDocumentSync] then
- return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
+ vim.notify('Invalid server TextDocumentSyncKind for textDocumentSync', vim.log.levels.ERROR)
+ return nil
end
server_capabilities.textDocumentSync = {
openClose = true,
@@ -890,183 +922,367 @@ function protocol.resolve_capabilities(server_capabilities)
},
}
elseif type(textDocumentSync) ~= 'table' then
- return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
+ vim.notify(
+ string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync)),
+ vim.log.levels.ERROR
+ )
+ return nil
end
return server_capabilities
end
----@private
---- Creates a normalized object describing LSP server capabilities.
--- @deprecated access resolved_capabilities instead
----@param server_capabilities table Table of capabilities supported by the server
----@return table Normalized table of capabilities
-function protocol._resolve_capabilities_compat(server_capabilities)
- local general_properties = {}
- local text_document_sync_properties
- do
- local TextDocumentSyncKind = protocol.TextDocumentSyncKind
- local textDocumentSync = server_capabilities.textDocumentSync
- if textDocumentSync == nil then
- -- Defaults if omitted.
- text_document_sync_properties = {
- text_document_open_close = false,
- text_document_did_change = TextDocumentSyncKind.None,
- -- text_document_did_change = false;
- text_document_will_save = false,
- text_document_will_save_wait_until = false,
- text_document_save = false,
- text_document_save_include_text = false,
- }
- elseif type(textDocumentSync) == 'number' then
- -- Backwards compatibility
- if not TextDocumentSyncKind[textDocumentSync] then
- return nil, 'Invalid server TextDocumentSyncKind for textDocumentSync'
- end
- text_document_sync_properties = {
- text_document_open_close = true,
- text_document_did_change = textDocumentSync,
- text_document_will_save = false,
- text_document_will_save_wait_until = false,
- text_document_save = true,
- text_document_save_include_text = false,
- }
- elseif type(textDocumentSync) == 'table' then
- text_document_sync_properties = {
- text_document_open_close = if_nil(textDocumentSync.openClose, false),
- text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None),
- text_document_will_save = if_nil(textDocumentSync.willSave, true),
- text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, true),
- text_document_save = if_nil(textDocumentSync.save, false),
- text_document_save_include_text = if_nil(
- type(textDocumentSync.save) == 'table' and textDocumentSync.save.includeText,
- false
- ),
- }
- else
- return nil, string.format('Invalid type for textDocumentSync: %q', type(textDocumentSync))
- end
- end
- general_properties.completion = server_capabilities.completionProvider ~= nil
- general_properties.hover = server_capabilities.hoverProvider or false
- general_properties.goto_definition = server_capabilities.definitionProvider or false
- general_properties.find_references = server_capabilities.referencesProvider or false
- general_properties.document_highlight = server_capabilities.documentHighlightProvider or false
- general_properties.document_symbol = server_capabilities.documentSymbolProvider or false
- general_properties.workspace_symbol = server_capabilities.workspaceSymbolProvider or false
- general_properties.document_formatting = server_capabilities.documentFormattingProvider or false
- general_properties.document_range_formatting = server_capabilities.documentRangeFormattingProvider
- or false
- general_properties.call_hierarchy = server_capabilities.callHierarchyProvider or false
- general_properties.execute_command = server_capabilities.executeCommandProvider ~= nil
-
- if server_capabilities.renameProvider == nil then
- general_properties.rename = false
- elseif type(server_capabilities.renameProvider) == 'boolean' then
- general_properties.rename = server_capabilities.renameProvider
- else
- general_properties.rename = true
- end
-
- if server_capabilities.codeLensProvider == nil then
- general_properties.code_lens = false
- general_properties.code_lens_resolve = false
- elseif type(server_capabilities.codeLensProvider) == 'table' then
- general_properties.code_lens = true
- general_properties.code_lens_resolve = server_capabilities.codeLensProvider.resolveProvider
- or false
- else
- error('The server sent invalid codeLensProvider')
- end
-
- if server_capabilities.codeActionProvider == nil then
- general_properties.code_action = false
- elseif
- type(server_capabilities.codeActionProvider) == 'boolean'
- or type(server_capabilities.codeActionProvider) == 'table'
- then
- general_properties.code_action = server_capabilities.codeActionProvider
- else
- error('The server sent invalid codeActionProvider')
- end
-
- if server_capabilities.declarationProvider == nil then
- general_properties.declaration = false
- elseif type(server_capabilities.declarationProvider) == 'boolean' then
- general_properties.declaration = server_capabilities.declarationProvider
- elseif type(server_capabilities.declarationProvider) == 'table' then
- general_properties.declaration = server_capabilities.declarationProvider
- else
- error('The server sent invalid declarationProvider')
- end
-
- if server_capabilities.typeDefinitionProvider == nil then
- general_properties.type_definition = false
- elseif type(server_capabilities.typeDefinitionProvider) == 'boolean' then
- general_properties.type_definition = server_capabilities.typeDefinitionProvider
- elseif type(server_capabilities.typeDefinitionProvider) == 'table' then
- general_properties.type_definition = server_capabilities.typeDefinitionProvider
- else
- error('The server sent invalid typeDefinitionProvider')
- end
-
- if server_capabilities.implementationProvider == nil then
- general_properties.implementation = false
- elseif type(server_capabilities.implementationProvider) == 'boolean' then
- general_properties.implementation = server_capabilities.implementationProvider
- elseif type(server_capabilities.implementationProvider) == 'table' then
- general_properties.implementation = server_capabilities.implementationProvider
- else
- error('The server sent invalid implementationProvider')
- end
-
- local workspace = server_capabilities.workspace
- local workspace_properties = {}
- if workspace == nil or workspace.workspaceFolders == nil then
- -- Defaults if omitted.
- workspace_properties = {
- workspace_folder_properties = {
- supported = false,
- changeNotifications = false,
- },
- }
- elseif type(workspace.workspaceFolders) == 'table' then
- workspace_properties = {
- workspace_folder_properties = {
- supported = if_nil(workspace.workspaceFolders.supported, false),
- changeNotifications = if_nil(workspace.workspaceFolders.changeNotifications, false),
- },
- }
- else
- error('The server sent invalid workspace')
- end
-
- local signature_help_properties
- if server_capabilities.signatureHelpProvider == nil then
- signature_help_properties = {
- signature_help = false,
- signature_help_trigger_characters = {},
- }
- elseif type(server_capabilities.signatureHelpProvider) == 'table' then
- signature_help_properties = {
- signature_help = true,
- -- The characters that trigger signature help automatically.
- signature_help_trigger_characters = server_capabilities.signatureHelpProvider.triggerCharacters
- or {},
- }
- else
- error('The server sent invalid signatureHelpProvider')
- end
-
- local capabilities = vim.tbl_extend(
- 'error',
- text_document_sync_properties,
- signature_help_properties,
- workspace_properties,
- general_properties
- )
-
- return capabilities
+-- Generated by gen_lsp.lua, keep at end of file.
+--- LSP method names.
+---
+---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#metaModel
+protocol.Methods = {
+ --- A request to resolve the incoming calls for a given `CallHierarchyItem`.
+ --- @since 3.16.0
+ callHierarchy_incomingCalls = 'callHierarchy/incomingCalls',
+ --- A request to resolve the outgoing calls for a given `CallHierarchyItem`.
+ --- @since 3.16.0
+ callHierarchy_outgoingCalls = 'callHierarchy/outgoingCalls',
+ --- The `client/registerCapability` request is sent from the server to the client to register a new capability
+ --- handler on the client side.
+ client_registerCapability = 'client/registerCapability',
+ --- The `client/unregisterCapability` request is sent from the server to the client to unregister a previously registered capability
+ --- handler on the client side.
+ client_unregisterCapability = 'client/unregisterCapability',
+ --- Request to resolve additional information for a given code action.The request's
+ --- parameter is of type {@link CodeAction} the response
+ --- is of type {@link CodeAction} or a Thenable that resolves to such.
+ codeAction_resolve = 'codeAction/resolve',
+ --- A request to resolve a command for a given code lens.
+ codeLens_resolve = 'codeLens/resolve',
+ --- Request to resolve additional information for a given completion item.The request's
+ --- parameter is of type {@link CompletionItem} the response
+ --- is of type {@link CompletionItem} or a Thenable that resolves to such.
+ completionItem_resolve = 'completionItem/resolve',
+ --- Request to resolve additional information for a given document link. The request's
+ --- parameter is of type {@link DocumentLink} the response
+ --- is of type {@link DocumentLink} or a Thenable that resolves to such.
+ documentLink_resolve = 'documentLink/resolve',
+ dollar_cancelRequest = '$/cancelRequest',
+ dollar_logTrace = '$/logTrace',
+ dollar_progress = '$/progress',
+ dollar_setTrace = '$/setTrace',
+ --- The exit event is sent from the client to the server to
+ --- ask the server to exit its process.
+ exit = 'exit',
+ --- The initialize request is sent from the client to the server.
+ --- It is sent once as the request after starting up the server.
+ --- The requests parameter is of type {@link InitializeParams}
+ --- the response if of type {@link InitializeResult} of a Thenable that
+ --- resolves to such.
+ initialize = 'initialize',
+ --- The initialized notification is sent from the client to the
+ --- server after the client is fully initialized and the server
+ --- is allowed to send requests from the server to the client.
+ initialized = 'initialized',
+ --- A request to resolve additional properties for an inlay hint.
+ --- The request's parameter is of type {@link InlayHint}, the response is
+ --- of type {@link InlayHint} or a Thenable that resolves to such.
+ --- @since 3.17.0
+ inlayHint_resolve = 'inlayHint/resolve',
+ notebookDocument_didChange = 'notebookDocument/didChange',
+ --- A notification sent when a notebook closes.
+ --- @since 3.17.0
+ notebookDocument_didClose = 'notebookDocument/didClose',
+ --- A notification sent when a notebook opens.
+ --- @since 3.17.0
+ notebookDocument_didOpen = 'notebookDocument/didOpen',
+ --- A notification sent when a notebook document is saved.
+ --- @since 3.17.0
+ notebookDocument_didSave = 'notebookDocument/didSave',
+ --- A shutdown request is sent from the client to the server.
+ --- It is sent once when the client decides to shutdown the
+ --- server. The only notification that is sent after a shutdown request
+ --- is the exit event.
+ shutdown = 'shutdown',
+ --- The telemetry event notification is sent from the server to the client to ask
+ --- the client to log telemetry data.
+ telemetry_event = 'telemetry/event',
+ --- A request to provide commands for the given text document and range.
+ textDocument_codeAction = 'textDocument/codeAction',
+ --- A request to provide code lens for the given text document.
+ textDocument_codeLens = 'textDocument/codeLens',
+ --- A request to list all presentation for a color. The request's
+ --- parameter is of type {@link ColorPresentationParams} the
+ --- response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ --- that resolves to such.
+ textDocument_colorPresentation = 'textDocument/colorPresentation',
+ --- Request to request completion at a given text document position. The request's
+ --- parameter is of type {@link TextDocumentPosition} the response
+ --- is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
+ --- or a Thenable that resolves to such.
+ --- The request can delay the computation of the {@link CompletionItem.detail `detail`}
+ --- and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
+ --- request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
+ --- `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
+ textDocument_completion = 'textDocument/completion',
+ --- A request to resolve the type definition locations of a symbol at a given text
+ --- document position. The request's parameter is of type [TextDocumentPositionParams]
+ --- (#TextDocumentPositionParams) the response is of type {@link Declaration}
+ --- or a typed array of {@link DeclarationLink} or a Thenable that resolves
+ --- to such.
+ textDocument_declaration = 'textDocument/declaration',
+ --- A request to resolve the definition location of a symbol at a given text
+ --- document position. The request's parameter is of type [TextDocumentPosition]
+ --- (#TextDocumentPosition) the response is of either type {@link Definition}
+ --- or a typed array of {@link DefinitionLink} or a Thenable that resolves
+ --- to such.
+ textDocument_definition = 'textDocument/definition',
+ --- The document diagnostic request definition.
+ --- @since 3.17.0
+ textDocument_diagnostic = 'textDocument/diagnostic',
+ --- The document change notification is sent from the client to the server to signal
+ --- changes to a text document.
+ textDocument_didChange = 'textDocument/didChange',
+ --- The document close notification is sent from the client to the server when
+ --- the document got closed in the client. The document's truth now exists where
+ --- the document's uri points to (e.g. if the document's uri is a file uri the
+ --- truth now exists on disk). As with the open notification the close notification
+ --- is about managing the document's content. Receiving a close notification
+ --- doesn't mean that the document was open in an editor before. A close
+ --- notification requires a previous open notification to be sent.
+ textDocument_didClose = 'textDocument/didClose',
+ --- The document open notification is sent from the client to the server to signal
+ --- newly opened text documents. The document's truth is now managed by the client
+ --- and the server must not try to read the document's truth using the document's
+ --- uri. Open in this sense means it is managed by the client. It doesn't necessarily
+ --- mean that its content is presented in an editor. An open notification must not
+ --- be sent more than once without a corresponding close notification send before.
+ --- This means open and close notification must be balanced and the max open count
+ --- is one.
+ textDocument_didOpen = 'textDocument/didOpen',
+ --- The document save notification is sent from the client to the server when
+ --- the document got saved in the client.
+ textDocument_didSave = 'textDocument/didSave',
+ --- A request to list all color symbols found in a given text document. The request's
+ --- parameter is of type {@link DocumentColorParams} the
+ --- response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+ --- that resolves to such.
+ textDocument_documentColor = 'textDocument/documentColor',
+ --- Request to resolve a {@link DocumentHighlight} for a given
+ --- text document position. The request's parameter is of type [TextDocumentPosition]
+ --- (#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
+ --- (#DocumentHighlight) or a Thenable that resolves to such.
+ textDocument_documentHighlight = 'textDocument/documentHighlight',
+ --- A request to provide document links
+ textDocument_documentLink = 'textDocument/documentLink',
+ --- A request to list all symbols found in a given text document. The request's
+ --- parameter is of type {@link TextDocumentIdentifier} the
+ --- response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
+ --- that resolves to such.
+ textDocument_documentSymbol = 'textDocument/documentSymbol',
+ --- A request to provide folding ranges in a document. The request's
+ --- parameter is of type {@link FoldingRangeParams}, the
+ --- response is of type {@link FoldingRangeList} or a Thenable
+ --- that resolves to such.
+ textDocument_foldingRange = 'textDocument/foldingRange',
+ --- A request to to format a whole document.
+ textDocument_formatting = 'textDocument/formatting',
+ --- Request to request hover information at a given text document position. The request's
+ --- parameter is of type {@link TextDocumentPosition} the response is of
+ --- type {@link Hover} or a Thenable that resolves to such.
+ textDocument_hover = 'textDocument/hover',
+ --- A request to resolve the implementation locations of a symbol at a given text
+ --- document position. The request's parameter is of type [TextDocumentPositionParams]
+ --- (#TextDocumentPositionParams) the response is of type {@link Definition} or a
+ --- Thenable that resolves to such.
+ textDocument_implementation = 'textDocument/implementation',
+ --- A request to provide inlay hints in a document. The request's parameter is of
+ --- type {@link InlayHintsParams}, the response is of type
+ --- {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
+ --- @since 3.17.0
+ textDocument_inlayHint = 'textDocument/inlayHint',
+ --- A request to provide inline completions in a document. The request's parameter is of
+ --- type {@link InlineCompletionParams}, the response is of type
+ --- {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
+ --- @since 3.18.0
+ textDocument_inlineCompletion = 'textDocument/inlineCompletion',
+ --- A request to provide inline values in a document. The request's parameter is of
+ --- type {@link InlineValueParams}, the response is of type
+ --- {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
+ --- @since 3.17.0
+ textDocument_inlineValue = 'textDocument/inlineValue',
+ --- A request to provide ranges that can be edited together.
+ --- @since 3.16.0
+ textDocument_linkedEditingRange = 'textDocument/linkedEditingRange',
+ --- A request to get the moniker of a symbol at a given text document position.
+ --- The request parameter is of type {@link TextDocumentPositionParams}.
+ --- The response is of type {@link Moniker Moniker[]} or `null`.
+ textDocument_moniker = 'textDocument/moniker',
+ --- A request to format a document on type.
+ textDocument_onTypeFormatting = 'textDocument/onTypeFormatting',
+ --- A request to result a `CallHierarchyItem` in a document at a given position.
+ --- Can be used as an input to an incoming or outgoing call hierarchy.
+ --- @since 3.16.0
+ textDocument_prepareCallHierarchy = 'textDocument/prepareCallHierarchy',
+ --- A request to test and perform the setup necessary for a rename.
+ --- @since 3.16 - support for default behavior
+ textDocument_prepareRename = 'textDocument/prepareRename',
+ --- A request to result a `TypeHierarchyItem` in a document at a given position.
+ --- Can be used as an input to a subtypes or supertypes type hierarchy.
+ --- @since 3.17.0
+ textDocument_prepareTypeHierarchy = 'textDocument/prepareTypeHierarchy',
+ --- Diagnostics notification are sent from the server to the client to signal
+ --- results of validation runs.
+ textDocument_publishDiagnostics = 'textDocument/publishDiagnostics',
+ --- A request to format a range in a document.
+ textDocument_rangeFormatting = 'textDocument/rangeFormatting',
+ --- A request to format ranges in a document.
+ --- @since 3.18.0
+ --- @proposed
+ textDocument_rangesFormatting = 'textDocument/rangesFormatting',
+ --- A request to resolve project-wide references for the symbol denoted
+ --- by the given text document position. The request's parameter is of
+ --- type {@link ReferenceParams} the response is of type
+ --- {@link Location Location[]} or a Thenable that resolves to such.
+ textDocument_references = 'textDocument/references',
+ --- A request to rename a symbol.
+ textDocument_rename = 'textDocument/rename',
+ --- A request to provide selection ranges in a document. The request's
+ --- parameter is of type {@link SelectionRangeParams}, the
+ --- response is of type {@link SelectionRange SelectionRange[]} or a Thenable
+ --- that resolves to such.
+ textDocument_selectionRange = 'textDocument/selectionRange',
+ --- @since 3.16.0
+ textDocument_semanticTokens_full = 'textDocument/semanticTokens/full',
+ --- @since 3.16.0
+ textDocument_semanticTokens_full_delta = 'textDocument/semanticTokens/full/delta',
+ --- @since 3.16.0
+ textDocument_semanticTokens_range = 'textDocument/semanticTokens/range',
+ textDocument_signatureHelp = 'textDocument/signatureHelp',
+ --- A request to resolve the type definition locations of a symbol at a given text
+ --- document position. The request's parameter is of type [TextDocumentPositionParams]
+ --- (#TextDocumentPositionParams) the response is of type {@link Definition} or a
+ --- Thenable that resolves to such.
+ textDocument_typeDefinition = 'textDocument/typeDefinition',
+ --- A document will save notification is sent from the client to the server before
+ --- the document is actually saved.
+ textDocument_willSave = 'textDocument/willSave',
+ --- A document will save request is sent from the client to the server before
+ --- the document is actually saved. The request can return an array of TextEdits
+ --- which will be applied to the text document before it is saved. Please note that
+ --- clients might drop results if computing the text edits took too long or if a
+ --- server constantly fails on this request. This is done to keep the save fast and
+ --- reliable.
+ textDocument_willSaveWaitUntil = 'textDocument/willSaveWaitUntil',
+ --- A request to resolve the subtypes for a given `TypeHierarchyItem`.
+ --- @since 3.17.0
+ typeHierarchy_subtypes = 'typeHierarchy/subtypes',
+ --- A request to resolve the supertypes for a given `TypeHierarchyItem`.
+ --- @since 3.17.0
+ typeHierarchy_supertypes = 'typeHierarchy/supertypes',
+ --- The log message notification is sent from the server to the client to ask
+ --- the client to log a particular message.
+ window_logMessage = 'window/logMessage',
+ --- A request to show a document. This request might open an
+ --- external program depending on the value of the URI to open.
+ --- For example a request to open `https://code.visualstudio.com/`
+ --- will very likely open the URI in a WEB browser.
+ --- @since 3.16.0
+ window_showDocument = 'window/showDocument',
+ --- The show message notification is sent from a server to a client to ask
+ --- the client to display a particular message in the user interface.
+ window_showMessage = 'window/showMessage',
+ --- The show message request is sent from the server to the client to show a message
+ --- and a set of options actions to the user.
+ window_showMessageRequest = 'window/showMessageRequest',
+ --- The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress
+ --- initiated on the server side.
+ window_workDoneProgress_cancel = 'window/workDoneProgress/cancel',
+ --- The `window/workDoneProgress/create` request is sent from the server to the client to initiate progress
+ --- reporting from the server.
+ window_workDoneProgress_create = 'window/workDoneProgress/create',
+ --- A request to resolve the range inside the workspace
+ --- symbol's location.
+ --- @since 3.17.0
+ workspaceSymbol_resolve = 'workspaceSymbol/resolve',
+ --- A request sent from the server to the client to modified certain resources.
+ workspace_applyEdit = 'workspace/applyEdit',
+ --- A request to refresh all code actions
+ --- @since 3.16.0
+ workspace_codeLens_refresh = 'workspace/codeLens/refresh',
+ --- The 'workspace/configuration' request is sent from the server to the client to fetch a certain
+ --- configuration setting.
+ --- This pull model replaces the old push model were the client signaled configuration change via an
+ --- event. If the server still needs to react to configuration changes (since the server caches the
+ --- result of `workspace/configuration` requests) the server should register for an empty configuration
+ --- change event and empty the cache if such an event is received.
+ workspace_configuration = 'workspace/configuration',
+ --- The workspace diagnostic request definition.
+ --- @since 3.17.0
+ workspace_diagnostic = 'workspace/diagnostic',
+ --- The diagnostic refresh request definition.
+ --- @since 3.17.0
+ workspace_diagnostic_refresh = 'workspace/diagnostic/refresh',
+ --- The configuration change notification is sent from the client to the server
+ --- when the client's configuration has changed. The notification contains
+ --- the changed configuration as defined by the language client.
+ workspace_didChangeConfiguration = 'workspace/didChangeConfiguration',
+ --- The watched files notification is sent from the client to the server when
+ --- the client detects changes to file watched by the language client.
+ workspace_didChangeWatchedFiles = 'workspace/didChangeWatchedFiles',
+ --- The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace
+ --- folder configuration changes.
+ workspace_didChangeWorkspaceFolders = 'workspace/didChangeWorkspaceFolders',
+ --- The did create files notification is sent from the client to the server when
+ --- files were created from within the client.
+ --- @since 3.16.0
+ workspace_didCreateFiles = 'workspace/didCreateFiles',
+ --- The will delete files request is sent from the client to the server before files are actually
+ --- deleted as long as the deletion is triggered from within the client.
+ --- @since 3.16.0
+ workspace_didDeleteFiles = 'workspace/didDeleteFiles',
+ --- The did rename files notification is sent from the client to the server when
+ --- files were renamed from within the client.
+ --- @since 3.16.0
+ workspace_didRenameFiles = 'workspace/didRenameFiles',
+ --- A request send from the client to the server to execute a command. The request might return
+ --- a workspace edit which the client will apply to the workspace.
+ workspace_executeCommand = 'workspace/executeCommand',
+ --- @since 3.17.0
+ workspace_inlayHint_refresh = 'workspace/inlayHint/refresh',
+ --- @since 3.17.0
+ workspace_inlineValue_refresh = 'workspace/inlineValue/refresh',
+ --- @since 3.16.0
+ workspace_semanticTokens_refresh = 'workspace/semanticTokens/refresh',
+ --- A request to list project-wide symbols matching the query string given
+ --- by the {@link WorkspaceSymbolParams}. The response is
+ --- of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
+ --- resolves to such.
+ --- @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
+ --- need to advertise support for WorkspaceSymbols via the client capability
+ --- `workspace.symbol.resolveSupport`.
+ workspace_symbol = 'workspace/symbol',
+ --- The will create files request is sent from the client to the server before files are actually
+ --- created as long as the creation is triggered from within the client.
+ --- The request can return a `WorkspaceEdit` which will be applied to workspace before the
+ --- files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file
+ --- to be created.
+ --- @since 3.16.0
+ workspace_willCreateFiles = 'workspace/willCreateFiles',
+ --- The did delete files notification is sent from the client to the server when
+ --- files were deleted from within the client.
+ --- @since 3.16.0
+ workspace_willDeleteFiles = 'workspace/willDeleteFiles',
+ --- The will rename files request is sent from the client to the server before files are actually
+ --- renamed as long as the rename is triggered from within the client.
+ --- @since 3.16.0
+ workspace_willRenameFiles = 'workspace/willRenameFiles',
+ --- The `workspace/workspaceFolders` is sent from the server to the client to fetch the open workspace folders.
+ workspace_workspaceFolders = 'workspace/workspaceFolders',
+}
+local function freeze(t)
+ return setmetatable({}, {
+ __index = t,
+ __newindex = function()
+ error('cannot modify immutable table')
+ end,
+ })
end
+protocol.Methods = freeze(protocol.Methods)
return protocol
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index f1492601ff..6ab5708721 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -1,50 +1,22 @@
-local uv = vim.loop
+local uv = vim.uv
local log = require('vim.lsp.log')
local protocol = require('vim.lsp.protocol')
local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap
local is_win = uv.os_uname().version:find('Windows')
----@private
--- Checks whether a given path exists and is a directory.
---@param filename (string) path to check
----@returns (bool)
+---@return boolean
local function is_dir(filename)
local stat = uv.fs_stat(filename)
return stat and stat.type == 'directory' or false
end
----@private
---- Merges current process env with the given env and returns the result as
---- a list of "k=v" strings.
----
---- <pre>
---- Example:
----
---- in: { PRODUCTION="false", PATH="/usr/bin/", PORT=123, HOST="0.0.0.0", }
---- out: { "PRODUCTION=false", "PATH=/usr/bin/", "PORT=123", "HOST=0.0.0.0", }
---- </pre>
----@param env (table) table of environment variable assignments
----@returns (table) list of `"k=v"` strings
-local function env_merge(env)
- if env == nil then
- return env
- end
- -- Merge.
- env = vim.tbl_extend('force', vim.fn.environ(), env)
- local final_env = {}
- for k, v in pairs(env) do
- assert(type(k) == 'string', 'env must be a dict')
- table.insert(final_env, k .. '=' .. tostring(v))
- end
- return final_env
-end
-
----@private
--- Embeds the given string into a table and correctly computes `Content-Length`.
---
---@param encoded_message (string)
----@returns (table) table containing encoded message and `Content-Length` attribute
+---@return string containing encoded message and `Content-Length` attribute
local function format_message_with_content_length(encoded_message)
return table.concat({
'Content-Length: ',
@@ -54,15 +26,14 @@ local function format_message_with_content_length(encoded_message)
})
end
----@private
--- Parses an LSP Message's header
---
---@param header string: The header to parse.
----@return table parsed headers
+---@return table # parsed headers
local function parse_headers(header)
assert(type(header) == 'string', 'header must be a string')
local headers = {}
- for line in vim.gsplit(header, '\r\n', true) do
+ for line in vim.gsplit(header, '\r\n', { plain = true }) do
if line == '' then
break
end
@@ -86,7 +57,6 @@ local header_start_pattern = ('content'):gsub('%w', function(c)
return '[' .. c .. c:upper() .. ']'
end)
----@private
--- The actual workhorse.
local function request_parser_loop()
local buffer = '' -- only for header part
@@ -141,8 +111,11 @@ local function request_parser_loop()
end
end
+local M = {}
+
--- Mapping of error codes used by the client
-local client_errors = {
+--- @nodoc
+M.client_errors = {
INVALID_SERVER_MESSAGE = 1,
INVALID_SERVER_JSON = 2,
NO_RESULT_CALLBACK_FOUND = 3,
@@ -152,13 +125,13 @@ local client_errors = {
SERVER_RESULT_CALLBACK_ERROR = 7,
}
-client_errors = vim.tbl_add_reverse_lookup(client_errors)
+M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors)
--- Constructs an error message from an LSP error object.
---
---@param err (table) The error object
---@returns (string) The formatted error message
-local function format_rpc_error(err)
+function M.format_rpc_error(err)
validate({
err = { err, 't' },
})
@@ -186,10 +159,10 @@ end
--- Creates an RPC response object/table.
---
----@param code number RPC error code defined in `vim.lsp.protocol.ErrorCodes`
+---@param code integer RPC error code defined in `vim.lsp.protocol.ErrorCodes`
---@param message string|nil arbitrary message to send to server
---@param data any|nil arbitrary data to send to server
-local function rpc_response_error(code, message, data)
+function M.rpc_response_error(code, message, data)
-- TODO should this error or just pick a sane error (like InternalError)?
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({
@@ -197,7 +170,7 @@ local function rpc_response_error(code, message, data)
message = message or code_name,
data = data,
}, {
- __tostring = format_rpc_error,
+ __tostring = M.format_rpc_error,
})
end
@@ -211,37 +184,41 @@ local default_dispatchers = {}
function default_dispatchers.notification(method, params)
local _ = log.debug() and log.debug('notification', method, params)
end
+
---@private
--- Default dispatcher for requests sent to an LSP server.
---
---@param method (string) The invoked LSP method
---@param params (table): Parameters for the invoked LSP method
----@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
+---@return nil
+---@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound`
function default_dispatchers.server_request(method, params)
local _ = log.debug() and log.debug('server_request', method, params)
- return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
+ return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
end
+
---@private
--- Default dispatcher for when a client exits.
---
----@param code (number): Exit code
----@param signal (number): Number describing the signal used to terminate (if
+---@param code (integer): Exit code
+---@param signal (integer): Number describing the signal used to terminate (if
---any)
function default_dispatchers.on_exit(code, signal)
local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
end
+
---@private
--- Default dispatcher for client errors.
---
----@param code (number): Error code
+---@param code (integer): Error code
---@param err (any): Details about the error
---any)
function default_dispatchers.on_error(code, err)
- local _ = log.error() and log.error('client_error:', client_errors[code], err)
+ local _ = log.error() and log.error('client_error:', M.client_errors[code], err)
end
---@private
-local function create_read_loop(handle_body, on_no_chunk, on_error)
+function M.create_read_loop(handle_body, on_no_chunk, on_error)
local parse_chunk = coroutine.wrap(request_parser_loop)
parse_chunk()
return function(err, chunk)
@@ -270,7 +247,7 @@ local function create_read_loop(handle_body, on_no_chunk, on_error)
end
---@class RpcClient
----@field message_index number
+---@field message_index integer
---@field message_callbacks table
---@field notify_reply_callbacks table
---@field transport table
@@ -294,7 +271,7 @@ end
--- Sends a notification to the LSP server.
---@param method (string) The invoked LSP method
---@param params (any): Parameters for the invoked LSP method
----@returns (bool) `true` if notification could be sent, `false` if not
+---@return boolean `true` if notification could be sent, `false` if not
function Client:notify(method, params)
return self:encode_and_send({
jsonrpc = '2.0',
@@ -319,9 +296,9 @@ end
---
---@param method (string) The invoked LSP method
---@param params (table|nil) Parameters for the invoked LSP method
----@param callback (function) Callback to invoke
+---@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke
---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
----@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
+---@return boolean success, integer|nil request_id true, request_id if request could be sent, `false` if not
function Client:request(method, params, callback, notify_reply_callback)
validate({
callback = { callback, 'f' },
@@ -354,7 +331,7 @@ end
---@private
function Client:on_error(errkind, ...)
- assert(client_errors[errkind])
+ assert(M.client_errors[errkind])
-- TODO what to do if this fails?
pcall(self.dispatchers.on_error, errkind, ...)
end
@@ -381,7 +358,7 @@ end
function Client:handle_body(body)
local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } })
if not ok then
- self:on_error(client_errors.INVALID_SERVER_JSON, decoded)
+ self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded)
return
end
local _ = log.debug() and log.debug('rpc.receive', decoded)
@@ -394,7 +371,7 @@ function Client:handle_body(body)
coroutine.wrap(function()
local status, result
status, result, err = self:try_call(
- client_errors.SERVER_REQUEST_HANDLER_ERROR,
+ M.client_errors.SERVER_REQUEST_HANDLER_ERROR,
self.dispatchers.server_request,
decoded.method,
decoded.params
@@ -426,7 +403,7 @@ function Client:handle_body(body)
end
else
-- On an exception, result will contain the error message.
- err = rpc_response_error(protocol.ErrorCodes.InternalError, result)
+ err = M.rpc_response_error(protocol.ErrorCodes.InternalError, result)
result = nil
end
self:send_response(decoded.id, err, result)
@@ -479,34 +456,33 @@ function Client:handle_body(body)
})
if decoded.error then
decoded.error = setmetatable(decoded.error, {
- __tostring = format_rpc_error,
+ __tostring = M.format_rpc_error,
})
end
self:try_call(
- client_errors.SERVER_RESULT_CALLBACK_ERROR,
+ M.client_errors.SERVER_RESULT_CALLBACK_ERROR,
callback,
decoded.error,
decoded.result
)
else
- self:on_error(client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
+ self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
local _ = log.error() and log.error('No callback found for server response id ' .. result_id)
end
elseif type(decoded.method) == 'string' then
-- Notification
self:try_call(
- client_errors.NOTIFICATION_HANDLER_ERROR,
+ M.client_errors.NOTIFICATION_HANDLER_ERROR,
self.dispatchers.notification,
decoded.method,
decoded.params
)
else
-- Invalid server message
- self:on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
+ self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded)
end
end
----@private
---@return RpcClient
local function new_client(dispatchers, transport)
local state = {
@@ -519,7 +495,6 @@ local function new_client(dispatchers, transport)
return setmetatable(state, { __index = Client })
end
----@private
---@param client RpcClient
local function public_client(client)
local result = {}
@@ -538,9 +513,9 @@ local function public_client(client)
---
---@param method (string) The invoked LSP method
---@param params (table|nil) Parameters for the invoked LSP method
- ---@param callback (function) Callback to invoke
+ ---@param callback fun(err: lsp.ResponseError | nil, result: any) Callback to invoke
---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
- ---@returns (bool, number) `(true, message_id)` if request could be sent, `false` if not
+ ---@return boolean success, integer|nil request_id true, message_id if request could be sent, `false` if not
function result.request(method, params, callback, notify_reply_callback)
return client:request(method, params, callback, notify_reply_callback)
end
@@ -548,7 +523,7 @@ local function public_client(client)
--- Sends a notification to the LSP server.
---@param method (string) The invoked LSP method
---@param params (table|nil): Parameters for the invoked LSP method
- ---@returns (bool) `true` if notification could be sent, `false` if not
+ ---@return boolean `true` if notification could be sent, `false` if not
function result.notify(method, params)
return client:notify(method, params)
end
@@ -556,7 +531,6 @@ local function public_client(client)
return result
end
----@private
local function merge_dispatchers(dispatchers)
if dispatchers then
local user_dispatchers = dispatchers
@@ -588,9 +562,9 @@ end
--- and port
---
---@param host string
----@param port number
+---@param port integer
---@return function
-local function connect(host, port)
+function M.connect(host, port)
return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers)
local tcp = uv.new_tcp()
@@ -625,8 +599,8 @@ local function connect(host, port)
local handle_body = function(body)
client:handle_body(body)
end
- tcp:read_start(create_read_loop(handle_body, transport.terminate, function(read_err)
- client:on_error(client_errors.READ_ERROR, read_err)
+ tcp:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
+ client:on_error(M.client_errors.READ_ERROR, read_err)
end))
end)
@@ -650,107 +624,93 @@ end
--- server process. May contain:
--- - {cwd} (string) Working directory for the LSP server process
--- - {env} (table) Additional environment variables for LSP server process
----@returns Client RPC object.
----
----@returns Methods:
+---@return table|nil Client RPC object, with these methods:
--- - `notify()` |vim.lsp.rpc.notify()|
--- - `request()` |vim.lsp.rpc.request()|
--- - `is_closing()` returns a boolean indicating if the RPC is closing.
--- - `terminate()` terminates the RPC client.
-local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
- local _ = log.info()
- and log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
+function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
+ if log.info() then
+ log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
+ end
+
validate({
cmd = { cmd, 's' },
cmd_args = { cmd_args, 't' },
dispatchers = { dispatchers, 't', true },
})
- if extra_spawn_params and extra_spawn_params.cwd then
+ extra_spawn_params = extra_spawn_params or {}
+
+ if extra_spawn_params.cwd then
assert(is_dir(extra_spawn_params.cwd), 'cwd must be a directory')
end
dispatchers = merge_dispatchers(dispatchers)
- local stdin = uv.new_pipe(false)
- local stdout = uv.new_pipe(false)
- local stderr = uv.new_pipe(false)
- local handle, pid
+
+ local sysobj ---@type vim.SystemObj
local client = new_client(dispatchers, {
write = function(msg)
- stdin:write(msg)
+ sysobj:write(msg)
end,
is_closing = function()
- return handle == nil or handle:is_closing()
+ return sysobj == nil or sysobj:is_closing()
end,
terminate = function()
- if handle then
- handle:kill(15)
- end
+ sysobj:kill(15)
end,
})
- ---@private
- --- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher.
- ---@param code (number) Exit code
- ---@param signal (number) Signal that was used to terminate (if any)
- local function onexit(code, signal)
- stdin:close()
- stdout:close()
- stderr:close()
- handle:close()
- dispatchers.on_exit(code, signal)
+ local handle_body = function(body)
+ client:handle_body(body)
end
- local spawn_params = {
- args = cmd_args,
- stdio = { stdin, stdout, stderr },
- detached = not is_win,
- }
- if extra_spawn_params then
- spawn_params.cwd = extra_spawn_params.cwd
- spawn_params.env = env_merge(extra_spawn_params.env)
- if extra_spawn_params.detached ~= nil then
- spawn_params.detached = extra_spawn_params.detached
+
+ local stdout_handler = M.create_read_loop(handle_body, nil, function(err)
+ client:on_error(M.client_errors.READ_ERROR, err)
+ end)
+
+ local stderr_handler = function(_, chunk)
+ if chunk and log.error() then
+ log.error('rpc', cmd, 'stderr', chunk)
end
end
- handle, pid = uv.spawn(cmd, spawn_params, onexit)
- if handle == nil then
- stdin:close()
- stdout:close()
- stderr:close()
+
+ local detached = not is_win
+ if extra_spawn_params.detached ~= nil then
+ detached = extra_spawn_params.detached
+ end
+
+ local cmd1 = { cmd }
+ vim.list_extend(cmd1, cmd_args)
+
+ local ok, sysobj_or_err = pcall(vim.system, cmd1, {
+ stdin = true,
+ stdout = stdout_handler,
+ stderr = stderr_handler,
+ cwd = extra_spawn_params.cwd,
+ env = extra_spawn_params.env,
+ detach = detached,
+ }, function(obj)
+ dispatchers.on_exit(obj.code, obj.signal)
+ end)
+
+ if not ok then
+ local err = sysobj_or_err --[[@as string]]
local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
- if string.match(pid, 'ENOENT') then
+ if string.match(err, 'ENOENT') then
msg = msg
.. '. The language server is either not installed, missing from PATH, or not executable.'
else
- msg = msg .. string.format(' with error message: %s', pid)
+ msg = msg .. string.format(' with error message: %s', err)
end
vim.notify(msg, vim.log.levels.WARN)
return
end
- stderr:read_start(function(_, chunk)
- if chunk then
- local _ = log.error() and log.error('rpc', cmd, 'stderr', chunk)
- end
- end)
-
- local handle_body = function(body)
- client:handle_body(body)
- end
- stdout:read_start(create_read_loop(handle_body, nil, function(err)
- client:on_error(client_errors.READ_ERROR, err)
- end))
+ sysobj = sysobj_or_err --[[@as vim.SystemObj]]
return public_client(client)
end
-return {
- start = start,
- connect = connect,
- rpc_response_error = rpc_response_error,
- format_rpc_error = format_rpc_error,
- client_errors = client_errors,
- create_read_loop = create_read_loop,
-}
--- vim:sw=2 ts=2 et
+return M
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index b1bc48dac6..a5831c0beb 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -1,46 +1,50 @@
local api = vim.api
+local bit = require('bit')
local handlers = require('vim.lsp.handlers')
+local ms = require('vim.lsp.protocol').Methods
local util = require('vim.lsp.util')
+local uv = vim.uv
--- @class STTokenRange
---- @field line number line number 0-based
---- @field start_col number start column 0-based
---- @field end_col number end column 0-based
+--- @field line integer line number 0-based
+--- @field start_col integer start column 0-based
+--- @field end_col integer end column 0-based
--- @field type string token type as string
---- @field modifiers string[] token modifiers as strings
---- @field extmark_added boolean whether this extmark has been added to the buffer yet
+--- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true }
+--- @field marked boolean whether this token has had extmarks applied
---
--- @class STCurrentResult
---- @field version number document version associated with this result
---- @field result_id string resultId from the server; used with delta requests
---- @field highlights STTokenRange[] cache of highlight ranges for this document version
---- @field tokens number[] raw token array as received by the server. used for calculating delta responses
---- @field namespace_cleared boolean whether the namespace was cleared for this result yet
+--- @field version? integer document version associated with this result
+--- @field result_id? string resultId from the server; used with delta requests
+--- @field highlights? STTokenRange[] cache of highlight ranges for this document version
+--- @field tokens? integer[] raw token array as received by the server. used for calculating delta responses
+--- @field namespace_cleared? boolean whether the namespace was cleared for this result yet
---
--- @class STActiveRequest
---- @field request_id number the LSP request ID of the most recent request sent to the server
---- @field version number the document version associated with the most recent request
+--- @field request_id integer the LSP request ID of the most recent request sent to the server
+--- @field version integer the document version associated with the most recent request
---
--- @class STClientState
---- @field namespace number
+--- @field namespace integer
--- @field active_request STActiveRequest
--- @field current_result STCurrentResult
---@class STHighlighter
----@field active table<number, STHighlighter>
----@field bufnr number
----@field augroup number augroup for buffer events
----@field debounce number milliseconds to debounce requests for new tokens
+---@field active table<integer, STHighlighter>
+---@field bufnr integer
+---@field augroup integer augroup for buffer events
+---@field debounce integer milliseconds to debounce requests for new tokens
---@field timer table uv_timer for debouncing requests for new tokens
----@field client_state table<number, STClientState>
+---@field client_state table<integer, STClientState>
local STHighlighter = { active = {} }
----@private
-local function binary_search(tokens, line)
- local lo = 1
- local hi = #tokens
+--- Do a binary search of the tokens in the half-open range [lo, hi).
+---
+--- Return the index i in range such that tokens[j].line < line for all j < i, and
+--- tokens[j].line >= line for all j >= i, or return hi if no such index is found.
+local function lower_bound(tokens, line, lo, hi)
while lo < hi do
- local mid = math.floor((lo + hi) / 2)
+ local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2).
if tokens[mid].line < line then
lo = mid + 1
else
@@ -50,26 +54,33 @@ local function binary_search(tokens, line)
return lo
end
+--- Do a binary search of the tokens in the half-open range [lo, hi).
+---
+--- Return the index i in range such that tokens[j].line <= line for all j < i, and
+--- tokens[j].line > line for all j >= i, or return hi if no such index is found.
+local function upper_bound(tokens, line, lo, hi)
+ while lo < hi do
+ local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2).
+ if line < tokens[mid].line then
+ hi = mid
+ else
+ lo = mid + 1
+ end
+ end
+ return lo
+end
+
--- Extracts modifier strings from the encoded number in the token array
---
----@private
----@return string[]
+---@return table<string, boolean>
local function modifiers_from_number(x, modifiers_table)
local modifiers = {}
local idx = 1
while x > 0 do
- if _G.bit then
- if _G.bit.band(x, 1) == 1 then
- modifiers[#modifiers + 1] = modifiers_table[idx]
- end
- x = _G.bit.rshift(x, 1)
- else
- --TODO(jdrouhard): remove this branch once `bit` module is available for non-LuaJIT (#21222)
- if x % 2 == 1 then
- modifiers[#modifiers + 1] = modifiers_table[idx]
- end
- x = math.floor(x / 2)
+ if bit.band(x, 1) == 1 then
+ modifiers[modifiers_table[idx]] = true
end
+ x = bit.rshift(x, 1)
idx = idx + 1
end
@@ -78,17 +89,40 @@ end
--- Converts a raw token list to a list of highlight ranges used by the on_win callback
---
----@private
---@return STTokenRange[]
-local function tokens_to_ranges(data, bufnr, client)
+local function tokens_to_ranges(data, bufnr, client, request)
local legend = client.server_capabilities.semanticTokensProvider.legend
local token_types = legend.tokenTypes
local token_modifiers = legend.tokenModifiers
+ local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
local ranges = {}
+ local start = uv.hrtime()
+ local ms_to_ns = 1000 * 1000
+ local yield_interval_ns = 5 * ms_to_ns
+ local co, is_main = coroutine.running()
+
local line
local start_char = 0
for i = 1, #data, 5 do
+ -- if this function is called from the main coroutine, let it run to completion with no yield
+ if not is_main then
+ local elapsed_ns = uv.hrtime() - start
+
+ if elapsed_ns > yield_interval_ns then
+ vim.schedule(function()
+ coroutine.resume(co, util.buf_versions[bufnr])
+ end)
+ if request.version ~= coroutine.yield() then
+ -- request became stale since the last time the coroutine ran.
+ -- abandon it by yielding without a way to resume
+ coroutine.yield()
+ end
+
+ start = uv.hrtime()
+ end
+ end
+
local delta_line = data[i]
line = line and line + delta_line or delta_line
local delta_start = data[i + 1]
@@ -98,12 +132,17 @@ local function tokens_to_ranges(data, bufnr, client)
local token_type = token_types[data[i + 3] + 1]
local modifiers = modifiers_from_number(data[i + 4], token_modifiers)
- ---@private
- local function _get_byte_pos(char_pos)
- return util._get_line_byte_from_position(bufnr, {
- line = line,
- character = char_pos,
- }, client.offset_encoding)
+ local function _get_byte_pos(col)
+ if col > 0 then
+ local buf_line = lines[line + 1] or ''
+ local ok, result
+ ok, result = pcall(util._str_byteindex_enc, buf_line, col, client.offset_encoding)
+ if ok then
+ return result
+ end
+ return math.min(#buf_line, col)
+ end
+ return col
end
local start_col = _get_byte_pos(start_char)
@@ -116,7 +155,7 @@ local function tokens_to_ranges(data, bufnr, client)
end_col = end_col,
type = token_type,
modifiers = modifiers,
- extmark_added = false,
+ marked = false,
}
end
end
@@ -127,7 +166,7 @@ end
--- Construct a new STHighlighter for the buffer
---
---@private
----@param bufnr number
+---@param bufnr integer
function STHighlighter.new(bufnr)
local self = setmetatable({}, { __index = STHighlighter })
@@ -254,7 +293,7 @@ function STHighlighter:send_request()
local hasEditProvider = type(spec) == 'table' and spec.delta
local params = { textDocument = util.make_text_document_params(self.bufnr) }
- local method = 'textDocument/semanticTokens/full'
+ local method = ms.textDocument_semanticTokens_full
if hasEditProvider and current_result.result_id then
method = method .. '/delta'
@@ -266,7 +305,7 @@ function STHighlighter:send_request()
local c = vim.lsp.get_client_by_id(ctx.client_id)
local highlighter = STHighlighter.active[ctx.bufnr]
if not err and c and highlighter then
- highlighter:process_response(response, c, version)
+ coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version)
end
end, self.bufnr)
@@ -301,11 +340,9 @@ function STHighlighter:process_response(response, client, version)
return
end
- -- reset active request
- state.active_request = {}
-
-- skip nil responses
if response == nil then
+ state.active_request = {}
return
end
@@ -333,15 +370,23 @@ function STHighlighter:process_response(response, client, version)
tokens = response.data
end
- -- Update the state with the new results
+ -- convert token list to highlight ranges
+ -- this could yield and run over multiple event loop iterations
+ local highlights = tokens_to_ranges(tokens, self.bufnr, client, state.active_request)
+
+ -- reset active request
+ state.active_request = {}
+
+ -- update the state with the new results
local current_result = state.current_result
current_result.version = version
current_result.result_id = response.resultId
current_result.tokens = tokens
- current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client)
+ current_result.highlights = highlights
current_result.namespace_cleared = false
- api.nvim_command('redraw!')
+ -- redraw all windows displaying buffer
+ api.nvim__buf_redraw_range(self.bufnr, 0, -1)
end
--- on_win handler for the decoration provider (see |nvim_set_decoration_provider|)
@@ -361,7 +406,7 @@ end
---
---@private
function STHighlighter:on_win(topline, botline)
- for _, state in pairs(self.client_state) do
+ for client_id, state in pairs(self.client_state) do
local current_result = state.current_result
if current_result.version and current_result.version == util.buf_versions[self.bufnr] then
if not current_result.namespace_cleared then
@@ -378,52 +423,55 @@ function STHighlighter:on_win(topline, botline)
--
-- Instead, we have to use normal extmarks that can attach to locations
-- in the buffer and are persisted between redraws.
+ --
+ -- `strict = false` is necessary here for the 1% of cases where the
+ -- current result doesn't actually match the buffer contents. Some
+ -- LSP servers can respond with stale tokens on requests if they are
+ -- still processing changes from a didChange notification.
+ --
+ -- LSP servers that do this _should_ follow up known stale responses
+ -- with a refresh notification once they've finished processing the
+ -- didChange notification, which would re-synchronize the tokens from
+ -- our end.
+ --
+ -- The server I know of that does this is clangd when the preamble of
+ -- a file changes and the token request is processed with a stale
+ -- preamble while the new one is still being built. Once the preamble
+ -- finishes, clangd sends a refresh request which lets the client
+ -- re-synchronize the tokens.
+
+ local set_mark = function(token, hl_group, delta)
+ vim.api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, {
+ hl_group = hl_group,
+ end_col = token.end_col,
+ priority = vim.highlight.priorities.semantic_tokens + delta,
+ strict = false,
+ })
+ end
+
+ local ft = vim.bo[self.bufnr].filetype
local highlights = current_result.highlights
- local idx = binary_search(highlights, topline)
+ local first = lower_bound(highlights, topline, 1, #highlights + 1)
+ local last = upper_bound(highlights, botline, first, #highlights + 1) - 1
- for i = idx, #highlights do
+ for i = first, last do
local token = highlights[i]
-
- if token.line > botline then
- break
- end
-
- if not token.extmark_added then
- -- `strict = false` is necessary here for the 1% of cases where the
- -- current result doesn't actually match the buffer contents. Some
- -- LSP servers can respond with stale tokens on requests if they are
- -- still processing changes from a didChange notification.
- --
- -- LSP servers that do this _should_ follow up known stale responses
- -- with a refresh notification once they've finished processing the
- -- didChange notification, which would re-synchronize the tokens from
- -- our end.
- --
- -- The server I know of that does this is clangd when the preamble of
- -- a file changes and the token request is processed with a stale
- -- preamble while the new one is still being built. Once the preamble
- -- finishes, clangd sends a refresh request which lets the client
- -- re-synchronize the tokens.
- api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, {
- hl_group = '@' .. token.type,
- end_col = token.end_col,
- priority = vim.highlight.priorities.semantic_tokens,
- strict = false,
- })
-
- -- TODO(bfredl) use single extmark when hl_group supports table
- if #token.modifiers > 0 then
- for _, modifier in pairs(token.modifiers) do
- api.nvim_buf_set_extmark(self.bufnr, state.namespace, token.line, token.start_col, {
- hl_group = '@' .. modifier,
- end_col = token.end_col,
- priority = vim.highlight.priorities.semantic_tokens + 1,
- strict = false,
- })
- end
+ if not token.marked then
+ set_mark(token, string.format('@lsp.type.%s.%s', token.type, ft), 0)
+ for modifier, _ in pairs(token.modifiers) do
+ set_mark(token, string.format('@lsp.mod.%s.%s', modifier, ft), 1)
+ set_mark(token, string.format('@lsp.typemod.%s.%s.%s', token.type, modifier, ft), 2)
end
-
- token.extmark_added = true
+ token.marked = true
+
+ api.nvim_exec_autocmds('LspTokenUpdate', {
+ buffer = self.bufnr,
+ modeline = false,
+ data = {
+ token = token,
+ client_id = client_id,
+ },
+ })
end
end
end
@@ -452,7 +500,7 @@ end
--- in case the server supports delta requests.
---
---@private
----@param client_id number
+---@param client_id integer
function STHighlighter:mark_dirty(client_id)
local state = self.client_state[client_id]
assert(state)
@@ -507,14 +555,15 @@ local M = {}
--- delete the semanticTokensProvider table from the {server_capabilities} of
--- your client in your |LspAttach| callback or your configuration's
--- `on_attach` callback:
---- <pre>lua
---- client.server_capabilities.semanticTokensProvider = nil
---- </pre>
---
----@param bufnr number
----@param client_id number
+--- ```lua
+--- client.server_capabilities.semanticTokensProvider = nil
+--- ```
+---
+---@param bufnr integer
+---@param client_id integer
---@param opts (nil|table) Optional keyword arguments
---- - debounce (number, default: 200): Debounce token requests
+--- - debounce (integer, default: 200): Debounce token requests
--- to the server by the given number in milliseconds
function M.start(bufnr, client_id, opts)
vim.validate({
@@ -567,8 +616,8 @@ end
--- of `start()`, so you should only need this function to manually disengage the semantic
--- token engine without fully detaching the LSP client from the buffer.
---
----@param bufnr number
----@param client_id number
+---@param bufnr integer
+---@param client_id integer
function M.stop(bufnr, client_id)
vim.validate({
bufnr = { bufnr, 'n', false },
@@ -590,11 +639,17 @@ end
--- Return the semantic token(s) at the given position.
--- If called without arguments, returns the token under the cursor.
---
----@param bufnr number|nil Buffer number (0 for current buffer, default)
----@param row number|nil Position row (default cursor position)
----@param col number|nil Position column (default cursor position)
+---@param bufnr integer|nil Buffer number (0 for current buffer, default)
+---@param row integer|nil Position row (default cursor position)
+---@param col integer|nil Position column (default cursor position)
---
----@return table|nil (table|nil) List of tokens at position
+---@return table|nil (table|nil) List of tokens at position. Each token has
+--- the following fields:
+--- - line (integer) line number, 0-based
+--- - start_col (integer) start column, 0-based
+--- - end_col (integer) end column, 0-based
+--- - type (string) token type as string, e.g. "variable"
+--- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true }
function M.get_at_pos(bufnr, row, col)
if bufnr == nil or bufnr == 0 then
bufnr = api.nvim_get_current_buf()
@@ -614,7 +669,7 @@ function M.get_at_pos(bufnr, row, col)
for client_id, client in pairs(highlighter.client_state) do
local highlights = client.current_result.highlights
if highlights then
- local idx = binary_search(highlights, row)
+ local idx = lower_bound(highlights, row, 1, #highlights + 1)
for i = idx, #highlights do
local token = highlights[i]
@@ -637,23 +692,59 @@ end
--- Only has an effect if the buffer is currently active for semantic token
--- highlighting (|vim.lsp.semantic_tokens.start()| has been called for it)
---
----@param bufnr (nil|number) default: current buffer
+---@param bufnr (integer|nil) filter by buffer. All buffers if nil, current
+--- buffer if 0
function M.force_refresh(bufnr)
vim.validate({
bufnr = { bufnr, 'n', true },
})
- if bufnr == nil or bufnr == 0 then
- bufnr = api.nvim_get_current_buf()
+ local buffers = bufnr == nil and vim.tbl_keys(STHighlighter.active)
+ or bufnr == 0 and { api.nvim_get_current_buf() }
+ or { bufnr }
+
+ for _, buffer in ipairs(buffers) do
+ local highlighter = STHighlighter.active[buffer]
+ if highlighter then
+ highlighter:reset()
+ highlighter:send_request()
+ end
end
+end
+--- Highlight a semantic token.
+---
+--- Apply an extmark with a given highlight group for a semantic token. The
+--- mark will be deleted by the semantic token engine when appropriate; for
+--- example, when the LSP sends updated tokens. This function is intended for
+--- use inside |LspTokenUpdate| callbacks.
+---@param token (table) a semantic token, found as `args.data.token` in |LspTokenUpdate|.
+---@param bufnr (integer) the buffer to highlight
+---@param client_id (integer) The ID of the |vim.lsp.client|
+---@param hl_group (string) Highlight group name
+---@param opts (table|nil) Optional parameters.
+--- - priority: (integer|nil) Priority for the applied extmark. Defaults
+--- to `vim.highlight.priorities.semantic_tokens + 3`
+function M.highlight_token(token, bufnr, client_id, hl_group, opts)
local highlighter = STHighlighter.active[bufnr]
if not highlighter then
return
end
- highlighter:reset()
- highlighter:send_request()
+ local state = highlighter.client_state[client_id]
+ if not state then
+ return
+ end
+
+ opts = opts or {}
+ local priority = opts.priority or vim.highlight.priorities.semantic_tokens + 3
+
+ vim.api.nvim_buf_set_extmark(bufnr, state.namespace, token.line, token.start_col, {
+ hl_group = hl_group,
+ end_col = token.end_col,
+ priority = priority,
+ strict = false,
+ })
end
--- |lsp-handler| for the method `workspace/semanticTokens/refresh`
@@ -665,7 +756,7 @@ end
--- the BufWinEnter event should take care of it next time it's displayed.
---
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
-handlers['workspace/semanticTokens/refresh'] = function(err, _, ctx)
+handlers[ms.workspace_semanticTokens_refresh] = function(err, _, ctx)
if err then
return vim.NIL
end
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index 826352f036..ca01cdc08b 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -48,7 +48,6 @@ local str_utfindex = vim.str_utfindex
local str_utf_start = vim.str_utf_start
local str_utf_end = vim.str_utf_end
----@private
-- Given a line, byte idx, and offset_encoding convert to the
-- utf-8, utf-16, or utf-32 index.
---@param line string the line to index into
@@ -74,7 +73,6 @@ local function byte_to_utf(line, byte, offset_encoding)
return utf_idx + 1
end
----@private
local function compute_line_length(line, offset_encoding)
local length
local _
@@ -88,13 +86,13 @@ local function compute_line_length(line, offset_encoding)
return length
end
----@private
-- Given a line, byte idx, alignment, and offset_encoding convert to the aligned
-- utf-8 index and either the utf-16, or utf-32 index.
---@param line string the line to index into
---@param byte integer the byte idx
---@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8)
----@returns table<string, int> byte_idx and char_idx of first change position
+---@return integer byte_idx of first change position
+---@return integer char_idx of first change position
local function align_end_position(line, byte, offset_encoding)
local char
-- If on the first byte, or an empty string: the trivial case
@@ -121,7 +119,6 @@ local function align_end_position(line, byte, offset_encoding)
return byte, char
end
----@private
--- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint.
---@param prev_lines table list of lines from previous buffer
---@param curr_lines table list of lines from current buffer
@@ -129,7 +126,7 @@ end
---@param lastline integer lastline from on_lines, adjusted to 1-index
---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index
---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8)
----@returns table<int, int> line_idx, byte_idx, and char_idx of first change position
+---@return table result table include line_idx, byte_idx, and char_idx of first change position
local function compute_start_range(
prev_lines,
curr_lines,
@@ -197,7 +194,6 @@ local function compute_start_range(
return { line_idx = firstline, byte_idx = byte_idx, char_idx = char_idx }
end
----@private
--- Finds the last line and byte index of the differences between prev and current buffer.
--- Normalized to the next codepoint.
--- prev_end_range is the text range sent to the server representing the changed region.
@@ -209,7 +205,8 @@ end
---@param lastline integer
---@param new_lastline integer
---@param offset_encoding string
----@returns (int, int) end_line_idx and end_col_idx of range
+---@return integer|table end_line_idx and end_col_idx of range
+---@return table|nil end_col_idx of range
local function compute_end_range(
prev_lines,
curr_lines,
@@ -305,12 +302,11 @@ local function compute_end_range(
return prev_end_range, curr_end_range
end
----@private
--- Get the text of the range defined by start and end line/column
---@param lines table list of lines
---@param start_range table table returned by first_difference
---@param end_range table new_end_range returned by last_difference
----@returns string text extracted from defined region
+---@return string text extracted from defined region
local function extract_text(lines, start_range, end_range, line_ending)
if not lines[start_range.line_idx] then
return ''
@@ -341,7 +337,6 @@ local function extract_text(lines, start_range, end_range, line_ending)
end
end
----@private
-- rangelength depends on the offset encoding
-- bytes for utf-8 (clangd with extension)
-- codepoints for utf-16
@@ -388,11 +383,11 @@ end
--- Returns the range table for the difference between prev and curr lines
---@param prev_lines table list of lines
---@param curr_lines table list of lines
----@param firstline number line to begin search for first difference
----@param lastline number line to begin search in old_lines for last difference
----@param new_lastline number line to begin search in new_lines for last difference
+---@param firstline integer line to begin search for first difference
+---@param lastline integer line to begin search in old_lines for last difference
+---@param new_lastline integer line to begin search in new_lines for last difference
---@param offset_encoding string encoding requested by language server
----@returns table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent
+---@return table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
function M.compute_diff(
prev_lines,
curr_lines,
diff --git a/runtime/lua/vim/lsp/tagfunc.lua b/runtime/lua/vim/lsp/tagfunc.lua
index 49029f8599..4ad50e4a58 100644
--- a/runtime/lua/vim/lsp/tagfunc.lua
+++ b/runtime/lua/vim/lsp/tagfunc.lua
@@ -1,7 +1,12 @@
local lsp = vim.lsp
local util = lsp.util
+local ms = lsp.protocol.Methods
----@private
+---@param name string
+---@param range lsp.Range
+---@param uri string
+---@param offset_encoding string
+---@return {name: string, filename: string, cmd: string, kind?: string}
local function mk_tag_item(name, range, uri, offset_encoding)
local bufnr = vim.uri_to_bufnr(uri)
-- This is get_line_byte_from_position is 0-indexed, call cursor expects a 1-indexed position
@@ -9,14 +14,15 @@ local function mk_tag_item(name, range, uri, offset_encoding)
return {
name = name,
filename = vim.uri_to_fname(uri),
- cmd = string.format('call cursor(%d, %d)|', range.start.line + 1, byte),
+ cmd = string.format([[/\%%%dl\%%%dc/]], range.start.line + 1, byte),
}
end
----@private
+---@param pattern string
+---@return table[]
local function query_definition(pattern)
local params = util.make_position_params()
- local results_by_client, err = lsp.buf_request_sync(0, 'textDocument/definition', params, 1000)
+ local results_by_client, err = lsp.buf_request_sync(0, ms.textDocument_definition, params, 1000)
if err then
return {}
end
@@ -24,17 +30,19 @@ local function query_definition(pattern)
local add = function(range, uri, offset_encoding)
table.insert(results, mk_tag_item(pattern, range, uri, offset_encoding))
end
- for client_id, lsp_results in pairs(results_by_client) do
+ for client_id, lsp_results in pairs(assert(results_by_client)) do
local client = lsp.get_client_by_id(client_id)
+ local offset_encoding = client and client.offset_encoding or 'utf-16'
local result = lsp_results.result or {}
if result.range then -- Location
add(result.range, result.uri)
- else -- Location[] or LocationLink[]
+ else
+ result = result --[[@as (lsp.Location[]|lsp.LocationLink[])]]
for _, item in pairs(result) do
if item.range then -- Location
- add(item.range, item.uri, client.offset_encoding)
+ add(item.range, item.uri, offset_encoding)
else -- LocationLink
- add(item.targetSelectionRange, item.targetUri, client.offset_encoding)
+ add(item.targetSelectionRange, item.targetUri, offset_encoding)
end
end
end
@@ -42,19 +50,22 @@ local function query_definition(pattern)
return results
end
----@private
+---@param pattern string
+---@return table[]
local function query_workspace_symbols(pattern)
local results_by_client, err =
- lsp.buf_request_sync(0, 'workspace/symbol', { query = pattern }, 1000)
+ lsp.buf_request_sync(0, ms.workspace_symbol, { query = pattern }, 1000)
if err then
return {}
end
local results = {}
- for client_id, symbols in pairs(results_by_client) do
+ for client_id, responses in pairs(assert(results_by_client)) do
local client = lsp.get_client_by_id(client_id)
- for _, symbol in pairs(symbols.result or {}) do
+ local offset_encoding = client and client.offset_encoding or 'utf-16'
+ local symbols = responses.result --[[@as lsp.SymbolInformation[]|nil]]
+ for _, symbol in pairs(symbols or {}) do
local loc = symbol.location
- local item = mk_tag_item(symbol.name, loc.range, loc.uri, client.offset_encoding)
+ local item = mk_tag_item(symbol.name, loc.range, loc.uri, offset_encoding)
item.kind = lsp.protocol.SymbolKind[symbol.kind] or 'Unknown'
table.insert(results, item)
end
@@ -62,14 +73,9 @@ local function query_workspace_symbols(pattern)
return results
end
----@private
local function tagfunc(pattern, flags)
- local matches
- if string.match(flags, 'c') then
- matches = query_definition(pattern)
- else
- matches = query_workspace_symbols(pattern)
- end
+ local matches = string.match(flags, 'c') and query_definition(pattern)
+ or query_workspace_symbols(pattern)
-- fall back to tags if no matches
return #matches > 0 and matches or vim.NIL
end
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 38051e6410..32b220746f 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -1,10 +1,10 @@
local protocol = require('vim.lsp.protocol')
-local snippet = require('vim.lsp._snippet')
+local snippet = require('vim.lsp._snippet_grammar')
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
local highlight = require('vim.highlight')
-local uv = vim.loop
+local uv = vim.uv
local npcall = vim.F.npcall
local split = vim.split
@@ -22,12 +22,11 @@ local default_border = {
{ ' ', 'NormalFloat' },
}
----@private
--- Check the border given by opts or the default border for the additional
--- size it adds to a float.
----@param opts (table, optional) options for the floating window
+---@param opts table optional options for the floating window
--- - border (string or table) the border
----@returns (table) size of border in the form of { height = height, width = width }
+---@return table size of border in the form of { height = height, width = width }
local function get_border_size(opts)
local border = opts and opts.border or default_border
local height = 0
@@ -60,7 +59,6 @@ local function get_border_size(opts)
)
)
end
- ---@private
local function border_width(id)
id = (id - 1) % #border + 1
if type(border[id]) == 'table' then
@@ -77,7 +75,6 @@ local function get_border_size(opts)
)
)
end
- ---@private
local function border_height(id)
id = (id - 1) % #border + 1
if type(border[id]) == 'table' then
@@ -103,13 +100,11 @@ local function get_border_size(opts)
return { height = height, width = width }
end
----@private
local function split_lines(value)
value = string.gsub(value, '\r\n?', '\n')
- return split(value, '\n', true)
+ return split(value, '\n', { plain = true, trimempty = true })
end
----@private
local function create_window_without_focus()
local prev = vim.api.nvim_get_current_win()
vim.cmd.new()
@@ -121,9 +116,9 @@ end
--- Convert byte index to `encoding` index.
--- Convenience wrapper around vim.str_utfindex
---@param line string line to be indexed
----@param index number|nil byte index (utf-8), or `nil` for length
----@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
----@return number `encoding` index of `index` in `line`
+---@param index integer|nil byte index (utf-8), or `nil` for length
+---@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16
+---@return integer `encoding` index of `index` in `line`
function M._str_utfindex_enc(line, index, encoding)
if not encoding then
encoding = 'utf-16'
@@ -149,9 +144,9 @@ end
--- Convenience wrapper around vim.str_byteindex
---Alternative to vim.str_byteindex that takes an encoding.
---@param line string line to be indexed
----@param index number UTF index
----@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16
----@return number byte (utf-8) index of `encoding` index `index` in `line`
+---@param index integer UTF index
+---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16
+---@return integer byte (utf-8) index of `encoding` index `index` in `line`
function M._str_byteindex_enc(line, index, encoding)
if not encoding then
encoding = 'utf-16'
@@ -173,15 +168,17 @@ end
local _str_utfindex_enc = M._str_utfindex_enc
local _str_byteindex_enc = M._str_byteindex_enc
+
--- Replaces text in a range with new text.
---
--- CAUTION: Changes in-place!
---
+---@deprecated
---@param lines (table) Original list of strings
----@param A (table) Start position; a 2-tuple of {line, col} numbers
----@param B (table) End position; a 2-tuple of {line, col} numbers
----@param new_lines A list of strings to replace the original
----@returns (table) The modified {lines} object
+---@param A (table) Start position; a 2-tuple of {line,col} numbers
+---@param B (table) End position; a 2-tuple of {line,col} numbers
+---@param new_lines (table) list of strings to replace the original
+---@return table The modified {lines} object
function M.set_lines(lines, A, B, new_lines)
-- 0-indexing to 1-indexing
local i_0 = A[1] + 1
@@ -219,7 +216,6 @@ function M.set_lines(lines, A, B, new_lines)
return lines
end
----@private
local function sort_by_key(fn)
return function(a, b)
local ka, kb = fn(a), fn(b)
@@ -234,14 +230,13 @@ local function sort_by_key(fn)
end
end
----@private
--- Gets the zero-indexed lines from the given buffer.
--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events.
--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI.
---
----@param bufnr number bufnr to get the lines from
----@param rows number[] zero-indexed line numbers
----@return table<number string> a table mapping rows to lines
+---@param bufnr integer bufnr to get the lines from
+---@param rows integer[] zero-indexed line numbers
+---@return table<integer, string>|string a table mapping rows to lines
local function get_lines(bufnr, rows)
rows = type(rows) == 'table' and rows or { rows }
@@ -250,15 +245,19 @@ local function get_lines(bufnr, rows)
bufnr = api.nvim_get_current_buf()
end
- ---@private
local function buf_lines()
local lines = {}
- for _, row in pairs(rows) do
+ for _, row in ipairs(rows) do
lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1]
end
return lines
end
+ -- use loaded buffers if available
+ if vim.fn.bufloaded(bufnr) == 1 then
+ return buf_lines()
+ end
+
local uri = vim.uri_from_bufnr(bufnr)
-- load the buffer if this is not a file uri
@@ -268,11 +267,6 @@ local function get_lines(bufnr, rows)
return buf_lines()
end
- -- use loaded buffers if available
- if vim.fn.bufloaded(bufnr) == 1 then
- return buf_lines()
- end
-
local filename = api.nvim_buf_get_name(bufnr)
-- get the data from the file
@@ -316,23 +310,20 @@ local function get_lines(bufnr, rows)
return lines
end
----@private
--- Gets the zero-indexed line from the given buffer.
--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events.
--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI.
---
----@param bufnr number
----@param row number zero-indexed line number
+---@param bufnr integer
+---@param row integer zero-indexed line number
---@return string the line at row in filename
local function get_line(bufnr, row)
return get_lines(bufnr, { row })[row]
end
----@private
--- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position
---- Returns a zero-indexed column, since set_lines() does the conversion to
----@param offset_encoding string utf-8|utf-16|utf-32
---- 1-indexed
+---@param offset_encoding string|nil utf-8|utf-16|utf-32
+---@return integer
local function get_line_byte_from_position(bufnr, position, offset_encoding)
-- LSP's line and characters are 0-indexed
-- Vim's line and columns are 1-indexed
@@ -353,11 +344,40 @@ end
--- Process and return progress reports from lsp server
---@private
+---@deprecated Use vim.lsp.status() or access client.progress directly
function M.get_progress_messages()
+ vim.deprecate('vim.lsp.util.get_progress_messages', 'vim.lsp.status', '0.11.0')
local new_messages = {}
local progress_remove = {}
- for _, client in ipairs(vim.lsp.get_active_clients()) do
+ for _, client in ipairs(vim.lsp.get_clients()) do
+ local groups = {}
+ for progress in client.progress do
+ local value = progress.value
+ if type(value) == 'table' and value.kind then
+ local group = groups[progress.token]
+ if not group then
+ group = {
+ done = false,
+ progress = true,
+ title = 'empty title',
+ }
+ groups[progress.token] = group
+ end
+ group.title = value.title or group.title
+ group.cancellable = value.cancellable or group.cancellable
+ if value.kind == 'end' then
+ group.done = true
+ end
+ group.message = value.message or group.message
+ group.percentage = value.percentage or group.percentage
+ end
+ end
+
+ for _, group in pairs(groups) do
+ table.insert(new_messages, group)
+ end
+
local messages = client.messages
local data = messages
for token, ctx in pairs(data.progress) do
@@ -386,7 +406,7 @@ end
--- Applies a list of text edits to a buffer.
---@param text_edits table list of `TextEdit` objects
----@param bufnr number Buffer id
+---@param bufnr integer Buffer id
---@param offset_encoding string utf-8|utf-16|utf-32
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit
function M.apply_text_edits(text_edits, bufnr, offset_encoding)
@@ -401,7 +421,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
- api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ vim.bo[bufnr].buflisted = true
-- Fix reversed range and indexing each text_edits
local index = 0
@@ -434,25 +454,15 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
end
end)
- -- Some LSP servers are depending on the VSCode behavior.
- -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it.
- local is_current_buf = api.nvim_get_current_buf() == bufnr
- local cursor = (function()
- if not is_current_buf then
- return {
- row = -1,
- col = -1,
- }
+ -- save and restore local marks since they get deleted by nvim_buf_set_lines
+ local marks = {}
+ for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+ if m.mark:match("^'[a-z]$") then
+ marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed
end
- local cursor = api.nvim_win_get_cursor(0)
- return {
- row = cursor[1] - 1,
- col = cursor[2],
- }
- end)()
+ end
-- Apply text edits.
- local is_cursor_fixed = false
local has_eol_text_edit = false
for _, text_edit in ipairs(text_edits) do
-- Normalize line ending
@@ -464,7 +474,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding),
end_row = text_edit.range['end'].line,
end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding),
- text = split(text_edit.newText, '\n', true),
+ text = split(text_edit.newText, '\n', { plain = true }),
}
local max = api.nvim_buf_line_count(bufnr)
@@ -499,42 +509,28 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
e.end_col = math.min(last_line_len, e.end_col)
api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text)
-
- -- Fix cursor position.
- local row_count = (e.end_row - e.start_row) + 1
- if e.end_row < cursor.row then
- cursor.row = cursor.row + (#e.text - row_count)
- is_cursor_fixed = true
- elseif e.end_row == cursor.row and e.end_col <= cursor.col then
- cursor.row = cursor.row + (#e.text - row_count)
- cursor.col = #e.text[#e.text] + (cursor.col - e.end_col)
- if #e.text == 1 then
- cursor.col = cursor.col + e.start_col
- end
- is_cursor_fixed = true
- end
end
end
local max = api.nvim_buf_line_count(bufnr)
- -- Apply fixed cursor position.
- if is_cursor_fixed then
- local is_valid_cursor = true
- is_valid_cursor = is_valid_cursor and cursor.row < max
- is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '')
- if is_valid_cursor then
- api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col })
+ -- no need to restore marks that still exist
+ for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+ marks[m.mark:sub(2, 2)] = nil
+ end
+ -- restore marks
+ for mark, pos in pairs(marks) do
+ if pos then
+ -- make sure we don't go out of bounds
+ pos[1] = math.min(pos[1], max)
+ pos[2] = math.min(pos[2], #(get_line(bufnr, pos[1] - 1) or ''))
+ vim.api.nvim_buf_set_mark(bufnr or 0, mark, pos[1], pos[2], {})
end
end
-- Remove final line if needed
local fix_eol = has_eol_text_edit
- fix_eol = fix_eol
- and (
- api.nvim_buf_get_option(bufnr, 'eol')
- or (api.nvim_buf_get_option(bufnr, 'fixeol') and not api.nvim_buf_get_option(bufnr, 'binary'))
- )
+ fix_eol = fix_eol and (vim.bo[bufnr].eol or (vim.bo[bufnr].fixeol and not vim.bo[bufnr].binary))
fix_eol = fix_eol and get_line(bufnr, max - 1) == ''
if fix_eol then
api.nvim_buf_set_lines(bufnr, -2, -1, false, {})
@@ -551,10 +547,12 @@ end
--- Can be used to extract the completion items from a
--- `textDocument/completion` request, which may return one of
--- `CompletionItem[]`, `CompletionList` or null.
----@param result (table) The result of a `textDocument/completion` request
----@returns (table) List of completion items
+---@deprecated
+---@param result table The result of a `textDocument/completion` request
+---@return lsp.CompletionItem[] List of completion items
---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
function M.extract_completion_items(result)
+ vim.deprecate('vim.lsp.util.extract_completion_items', nil, '0.11')
if type(result) == 'table' and result.items then
-- result is a `CompletionList`
return result.items
@@ -571,7 +569,7 @@ end
--- document.
---
---@param text_document_edit table: a `TextDocumentEdit` object
----@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list)
+---@param index integer: Optional index of the edit, if from a list of edits (or nil, if not from a list)
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
local text_document = text_document_edit.textDocument
@@ -610,130 +608,36 @@ end
--- Parses snippets in a completion entry.
---
+---@deprecated
---@param input string unparsed snippet
----@returns string parsed snippet
+---@return string parsed snippet
function M.parse_snippet(input)
+ vim.deprecate('vim.lsp.util.parse_snippet', nil, '0.11')
local ok, parsed = pcall(function()
- return tostring(snippet.parse(input))
+ return snippet.parse(input)
end)
if not ok then
return input
end
- return parsed
-end
-
----@private
---- Sorts by CompletionItem.sortText.
----
---see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-local function sort_completion_items(items)
- table.sort(items, function(a, b)
- return (a.sortText or a.label) < (b.sortText or b.label)
- end)
-end
-
----@private
---- Returns text that should be inserted when selecting completion item. The
---- precedence is as follows: textEdit.newText > insertText > label
---see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-local function get_completion_word(item)
- if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= '' then
- local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
- if insert_text_format == 'PlainText' or insert_text_format == nil then
- return item.textEdit.newText
- else
- return M.parse_snippet(item.textEdit.newText)
- end
- elseif item.insertText ~= nil and item.insertText ~= '' then
- local insert_text_format = protocol.InsertTextFormat[item.insertTextFormat]
- if insert_text_format == 'PlainText' or insert_text_format == nil then
- return item.insertText
- else
- return M.parse_snippet(item.insertText)
- end
- end
- return item.label
-end
----@private
---- Some language servers return complementary candidates whose prefixes do not
---- match are also returned. So we exclude completion candidates whose prefix
---- does not match.
-local function remove_unmatch_completion_items(items, prefix)
- return vim.tbl_filter(function(item)
- local word = get_completion_word(item)
- return vim.startswith(word, prefix)
- end, items)
-end
-
---- According to LSP spec, if the client set `completionItemKind.valueSet`,
---- the client must handle it properly even if it receives a value outside the
---- specification.
----
----@param completion_item_kind (`vim.lsp.protocol.completionItemKind`)
----@returns (`vim.lsp.protocol.completionItemKind`)
----@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
-function M._get_completion_item_kind_name(completion_item_kind)
- return protocol.CompletionItemKind[completion_item_kind] or 'Unknown'
+ return tostring(parsed)
end
--- Turns the result of a `textDocument/completion` request into vim-compatible
--- |complete-items|.
---
----@param result The result of a `textDocument/completion` call, e.g. from
----|vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`,
+---@deprecated
+---@param result table The result of a `textDocument/completion` call, e.g.
+--- from |vim.lsp.buf.completion()|, which may be one of `CompletionItem[]`,
--- `CompletionList` or `null`
---@param prefix (string) the prefix to filter the completion items
----@returns { matches = complete-items table, incomplete = bool }
----@see |complete-items|
+---@return table[] items
+---@see complete-items
function M.text_document_completion_list_to_complete_items(result, prefix)
- local items = M.extract_completion_items(result)
- if vim.tbl_isempty(items) then
- return {}
- end
-
- items = remove_unmatch_completion_items(items, prefix)
- sort_completion_items(items)
-
- local matches = {}
-
- for _, completion_item in ipairs(items) do
- local info = ' '
- local documentation = completion_item.documentation
- if documentation then
- if type(documentation) == 'string' and documentation ~= '' then
- info = documentation
- elseif type(documentation) == 'table' and type(documentation.value) == 'string' then
- info = documentation.value
- -- else
- -- TODO(ashkan) Validation handling here?
- end
- end
-
- local word = get_completion_word(completion_item)
- table.insert(matches, {
- word = word,
- abbr = completion_item.label,
- kind = M._get_completion_item_kind_name(completion_item.kind),
- menu = completion_item.detail or '',
- info = info,
- icase = 1,
- dup = 1,
- empty = 1,
- user_data = {
- nvim = {
- lsp = {
- completion_item = completion_item,
- },
- },
- },
- })
- end
-
- return matches
+ vim.deprecate('vim.lsp.util.text_document_completion_list_to_complete_items', nil, '0.11')
+ return require('vim.lsp._completion')._lsp_to_complete_items(result, prefix)
end
----@private
--- Like vim.fn.bufwinid except it works across tabpages.
local function bufwinid(bufnr)
for _, win in ipairs(api.nvim_list_wins()) do
@@ -743,6 +647,19 @@ local function bufwinid(bufnr)
end
end
+--- Get list of buffers for a directory
+local function get_dir_bufs(path)
+ path = path:gsub('([^%w])', '%%%1')
+ local buffers = {}
+ for _, v in ipairs(vim.api.nvim_list_bufs()) do
+ local bufname = vim.api.nvim_buf_get_name(v):gsub('buffer://', '')
+ if bufname:find(path) then
+ table.insert(buffers, v)
+ end
+ end
+ return buffers
+end
+
--- Rename old_fname to new_fname
---
---@param opts (table)
@@ -755,26 +672,41 @@ function M.rename(old_fname, new_fname, opts)
vim.notify('Rename target already exists. Skipping rename.')
return
end
- local oldbuf = vim.fn.bufadd(old_fname)
- vim.fn.bufload(oldbuf)
- -- The there may be pending changes in the buffer
- api.nvim_buf_call(oldbuf, function()
- vim.cmd('w!')
- end)
+ local oldbufs = {}
+ local win = nil
+
+ if vim.fn.isdirectory(old_fname) == 1 then
+ oldbufs = get_dir_bufs(old_fname)
+ else
+ local oldbuf = vim.fn.bufadd(old_fname)
+ table.insert(oldbufs, oldbuf)
+ win = bufwinid(oldbuf)
+ end
+
+ for _, b in ipairs(oldbufs) do
+ vim.fn.bufload(b)
+ -- The there may be pending changes in the buffer
+ api.nvim_buf_call(b, function()
+ vim.cmd('w!')
+ end)
+ end
local ok, err = os.rename(old_fname, new_fname)
assert(ok, err)
- local newbuf = vim.fn.bufadd(new_fname)
- local win = bufwinid(oldbuf)
- if win then
- api.nvim_win_set_buf(win, newbuf)
+ if vim.fn.isdirectory(new_fname) == 0 then
+ local newbuf = vim.fn.bufadd(new_fname)
+ if win then
+ api.nvim_win_set_buf(win, newbuf)
+ end
+ end
+
+ for _, b in ipairs(oldbufs) do
+ api.nvim_buf_delete(b, {})
end
- api.nvim_buf_delete(oldbuf, { force = true })
end
----@private
local function create_file(change)
local opts = change.options or {}
-- from spec: Overwrite wins over `ignoreIfExists`
@@ -789,7 +721,6 @@ local function create_file(change)
vim.fn.bufadd(fname)
end
----@private
local function delete_file(change)
local opts = change.options or {}
local fname = vim.uri_to_fname(change.uri)
@@ -855,9 +786,12 @@ end
--- window for `textDocument/hover`, for parsing the result of
--- `textDocument/signatureHelp`, and potentially others.
---
+--- Note that if the input is of type `MarkupContent` and its kind is `plaintext`,
+--- then the corresponding value is returned without further modifications.
+---
---@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`)
---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}.
----@returns {contents}, extended with lines of converted markdown.
+---@return string[] extended with lines of converted markdown.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
function M.convert_input_to_markdown_lines(input, contents)
contents = contents or {}
@@ -865,27 +799,13 @@ function M.convert_input_to_markdown_lines(input, contents)
if type(input) == 'string' then
list_extend(contents, split_lines(input))
else
- assert(type(input) == 'table', 'Expected a table for Hover.contents')
+ assert(type(input) == 'table', 'Expected a table for LSP input')
-- MarkupContent
if input.kind then
- -- The kind can be either plaintext or markdown.
- -- If it's plaintext, then wrap it in a <text></text> block
-
- -- Some servers send input.value as empty, so let's ignore this :(
local value = input.value or ''
-
- if input.kind == 'plaintext' then
- -- wrap this in a <text></text> block so that stylize_markdown
- -- can properly process it as plaintext
- value = string.format('<text>\n%s\n</text>', value)
- end
-
- -- assert(type(value) == 'string')
list_extend(contents, split_lines(value))
-- MarkupString variation 2
elseif input.language then
- -- Some servers send input.value as empty, so let's ignore this :(
- -- assert(type(input.value) == 'string')
table.insert(contents, '```' .. input.language)
list_extend(contents, split_lines(input.value or ''))
table.insert(contents, '```')
@@ -903,12 +823,13 @@ function M.convert_input_to_markdown_lines(input, contents)
return contents
end
---- Converts `textDocument/SignatureHelp` response to markdown lines.
+--- Converts `textDocument/signatureHelp` response to markdown lines.
---
----@param signature_help Response of `textDocument/SignatureHelp`
----@param ft optional filetype that will be use as the `lang` for the label markdown code block
----@param triggers optional list of trigger characters from the lsp server. used to better determine parameter offsets
----@returns list of lines of converted markdown.
+---@param signature_help table Response of `textDocument/SignatureHelp`
+---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block
+---@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets
+---@return table|nil table list of lines of converted markdown.
+---@return table|nil table of active hl
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers)
if not signature_help.signatures then
@@ -932,11 +853,17 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers
end
local label = signature.label
if ft then
- -- wrap inside a code block so stylize_markdown can render it properly
+ -- wrap inside a code block for proper rendering
label = ('```%s\n%s\n```'):format(ft, label)
end
- list_extend(contents, split(label, '\n', true))
+ list_extend(contents, split(label, '\n', { plain = true, trimempty = true }))
if signature.documentation then
+ -- if LSP returns plain string, we treat it as plaintext. This avoids
+ -- special characters like underscore or similar from being interpreted
+ -- as markdown font modifiers
+ if type(signature.documentation) == 'string' then
+ signature.documentation = { kind = 'plaintext', value = signature.documentation }
+ end
M.convert_input_to_markdown_lines(signature.documentation, contents)
end
if signature.parameters and #signature.parameters > 0 then
@@ -1007,16 +934,22 @@ end
--- Creates a table with sensible default options for a floating window. The
--- table can be passed to |nvim_open_win()|.
---
----@param width (number) window width (in character cells)
----@param height (number) window height (in character cells)
----@param opts (table, optional)
---- - offset_x (number) offset to add to `col`
---- - offset_y (number) offset to add to `row`
+---@param width integer window width (in character cells)
+---@param height integer window height (in character cells)
+---@param opts table optional
+--- - offset_x (integer) offset to add to `col`
+--- - offset_y (integer) offset to add to `row`
--- - border (string or table) override `border`
--- - focusable (string or table) override `focusable`
--- - zindex (string or table) override `zindex`, defaults to 50
--- - relative ("mouse"|"cursor") defaults to "cursor"
----@returns (table) Options
+--- - anchor_bias ("auto"|"above"|"below") defaults to "auto"
+--- - "auto": place window based on which side of the cursor has more lines
+--- - "above": place the window above the cursor unless there are not enough lines
+--- to display the full window height.
+--- - "below": place the window below the cursor unless there are not enough lines
+--- to display the full window height.
+---@return table Options
function M.make_floating_popup_options(width, height, opts)
validate({
opts = { opts, 't', true },
@@ -1034,19 +967,33 @@ function M.make_floating_popup_options(width, height, opts)
or vim.fn.winline() - 1
local lines_below = vim.fn.winheight(0) - lines_above
- if lines_above < lines_below then
+ local anchor_bias = opts.anchor_bias or 'auto'
+
+ local anchor_below
+
+ if anchor_bias == 'below' then
+ anchor_below = (lines_below > lines_above) or (height <= lines_below)
+ elseif anchor_bias == 'above' then
+ local anchor_above = (lines_above > lines_below) or (height <= lines_above)
+ anchor_below = not anchor_above
+ else
+ anchor_below = lines_below > lines_above
+ end
+
+ local border_height = get_border_size(opts).height
+ if anchor_below then
anchor = anchor .. 'N'
- height = math.min(lines_below, height)
+ height = math.max(math.min(lines_below - border_height, height), 0)
row = 1
else
anchor = anchor .. 'S'
- height = math.min(lines_above, height)
+ height = math.max(math.min(lines_above - border_height, height), 0)
row = 0
end
local wincol = opts.relative == 'mouse' and vim.fn.getmousepos().column or vim.fn.wincol()
- if wincol + width + (opts.offset_x or 0) <= api.nvim_get_option('columns') then
+ if wincol + width + (opts.offset_x or 0) <= vim.o.columns then
anchor = anchor .. 'W'
col = 0
else
@@ -1080,7 +1027,7 @@ end
--- Shows document and optionally jumps to the location.
---
---@param location table (`Location`|`LocationLink`)
----@param offset_encoding "utf-8" | "utf-16" | "utf-32"
+---@param offset_encoding string|nil utf-8|utf-16|utf-32
---@param opts table|nil options
--- - reuse_win (boolean) Jump to existing window if buffer is already open.
--- - focus (boolean) Whether to focus/jump to location if possible. Defaults to true.
@@ -1112,7 +1059,7 @@ function M.show_document(location, offset_encoding, opts)
or focus and api.nvim_get_current_win()
or create_window_without_focus()
- api.nvim_buf_set_option(bufnr, 'buflisted', true)
+ vim.bo[bufnr].buflisted = true
api.nvim_win_set_buf(win, bufnr)
if focus then
api.nvim_set_current_win(win)
@@ -1137,7 +1084,7 @@ end
--- Jumps to a location.
---
---@param location table (`Location`|`LocationLink`)
----@param offset_encoding "utf-8" | "utf-16" | "utf-32"
+---@param offset_encoding string|nil utf-8|utf-16|utf-32
---@param reuse_win boolean|nil Jump to existing window if buffer is already open.
---@return boolean `true` if the jump succeeded
function M.jump_to_location(location, offset_encoding, reuse_win)
@@ -1157,8 +1104,9 @@ end
--- - for Location, range is shown (e.g., function definition)
--- - for LocationLink, targetRange is shown (e.g., body of function definition)
---
----@param location a single `Location` or `LocationLink`
----@returns (bufnr,winnr) buffer and window number of floating window or nil
+---@param location table a single `Location` or `LocationLink`
+---@return integer|nil buffer id of float window
+---@return integer|nil window id of float window
function M.preview_location(location, opts)
-- location may be LocationLink or Location (more useful for the former)
local uri = location.targetUri or location.uri
@@ -1171,19 +1119,18 @@ function M.preview_location(location, opts)
end
local range = location.targetRange or location.range
local contents = api.nvim_buf_get_lines(bufnr, range.start.line, range['end'].line + 1, false)
- local syntax = api.nvim_buf_get_option(bufnr, 'syntax')
+ local syntax = vim.bo[bufnr].syntax
if syntax == '' then
-- When no syntax is set, we use filetype as fallback. This might not result
- -- in a valid syntax definition. See also ft detection in stylize_markdown.
+ -- in a valid syntax definition.
-- An empty syntax is more common now with TreeSitter, since TS disables syntax.
- syntax = api.nvim_buf_get_option(bufnr, 'filetype')
+ syntax = vim.bo[bufnr].filetype
end
opts = opts or {}
opts.focus_id = 'location'
return M.open_floating_preview(contents, syntax, opts)
end
----@private
local function find_window_by_var(name, value)
for _, win in ipairs(api.nvim_list_wins()) do
if npcall(api.nvim_win_get_var, win, name) == value then
@@ -1192,37 +1139,65 @@ local function find_window_by_var(name, value)
end
end
---- Trims empty lines from input and pad top and bottom with empty lines
----
----@param contents table of lines to trim and pad
----@param opts dictionary with optional fields
---- - pad_top number of lines to pad contents at top (default 0)
---- - pad_bottom number of lines to pad contents at bottom (default 0)
----@return contents table of trimmed and padded lines
-function M._trim(contents, opts)
- validate({
- contents = { contents, 't' },
- opts = { opts, 't', true },
- })
- opts = opts or {}
- contents = M.trim_empty_lines(contents)
- if opts.pad_top then
- for _ = 1, opts.pad_top do
- table.insert(contents, 1, '')
+---Returns true if the line is empty or only contains whitespace.
+---@param line string
+---@return boolean
+local function is_blank_line(line)
+ return line and line:match('^%s*$')
+end
+
+---Returns true if the line corresponds to a Markdown thematic break.
+---@param line string
+---@return boolean
+local function is_separator_line(line)
+ return line and line:match('^ ? ? ?%-%-%-+%s*$')
+end
+
+---Replaces separator lines by the given divider and removing surrounding blank lines.
+---@param contents string[]
+---@param divider string
+---@return string[]
+local function replace_separators(contents, divider)
+ local trimmed = {}
+ local l = 1
+ while l <= #contents do
+ local line = contents[l]
+ if is_separator_line(line) then
+ if l > 1 and is_blank_line(contents[l - 1]) then
+ table.remove(trimmed)
+ end
+ table.insert(trimmed, divider)
+ if is_blank_line(contents[l + 1]) then
+ l = l + 1
+ end
+ else
+ table.insert(trimmed, line)
end
+ l = l + 1
end
- if opts.pad_bottom then
- for _ = 1, opts.pad_bottom do
- table.insert(contents, '')
+
+ return trimmed
+end
+
+---Collapses successive blank lines in the input table into a single one.
+---@param contents string[]
+---@return string[]
+local function collapse_blank_lines(contents)
+ local collapsed = {}
+ local l = 1
+ while l <= #contents do
+ local line = contents[l]
+ if is_blank_line(line) then
+ while is_blank_line(contents[l + 1]) do
+ l = l + 1
+ end
end
+ table.insert(collapsed, line)
+ l = l + 1
end
- return contents
+ return collapsed
end
---- Generates a table mapping markdown code block lang to vim syntax,
---- based on g:markdown_fenced_languages
----@return a table of lang -> syntax mappings
----@private
local function get_markdown_fences()
local fences = {}
for _, fence in pairs(vim.g.markdown_fenced_languages or {}) do
@@ -1244,16 +1219,14 @@ end
--- If you want to open a popup with fancy markdown, use `open_floating_preview` instead
---
---@param contents table of lines to show in window
----@param opts dictionary with optional fields
+---@param opts table with optional fields
--- - height of floating window
--- - width of floating window
--- - wrap_at character to wrap at for computing height
--- - max_width maximal width of floating window
--- - max_height maximal height of floating window
---- - pad_top number of lines to pad contents at top
---- - pad_bottom number of lines to pad contents at bottom
--- - separator insert separator after code block
----@returns width,height size of float
+---@return table stripped content
function M.stylize_markdown(bufnr, contents, opts)
validate({
contents = { contents, 't' },
@@ -1264,7 +1237,7 @@ function M.stylize_markdown(bufnr, contents, opts)
-- table of fence types to {ft, begin, end}
-- when ft is nil, we get the ft from the regex match
local matchers = {
- block = { nil, '```+([a-zA-Z0-9_]*)', '```+' },
+ block = { nil, '```+%s*([a-zA-Z0-9_]*)', '```+' },
pre = { nil, '<pre>([a-z0-9]*)', '</pre>' },
code = { '', '<code>', '</code>' },
text = { 'text', '<text>', '</text>' },
@@ -1288,7 +1261,7 @@ function M.stylize_markdown(bufnr, contents, opts)
end
-- Clean up
- contents = M._trim(contents, opts)
+ contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
local stripped = {}
local highlights = {}
@@ -1348,6 +1321,20 @@ function M.stylize_markdown(bufnr, contents, opts)
end
end
+ -- Handle some common html escape sequences
+ stripped = vim.tbl_map(function(line)
+ local escapes = {
+ ['&gt;'] = '>',
+ ['&lt;'] = '<',
+ ['&quot;'] = '"',
+ ['&apos;'] = "'",
+ ['&ensp;'] = ' ',
+ ['&emsp;'] = ' ',
+ ['&amp;'] = '&',
+ }
+ return (string.gsub(line, '&[^ ;]+;', escapes))
+ end, stripped)
+
-- Compute size of float needed to show (wrapped) lines
opts.wrap_at = opts.wrap_at or (vim.wo['wrap'] and api.nvim_win_get_width(0))
local width = M._make_floating_popup_size(stripped, opts)
@@ -1363,7 +1350,6 @@ function M.stylize_markdown(bufnr, contents, opts)
api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
local idx = 1
- ---@private
-- keep track of syntaxes we already included.
-- no need to include the same syntax more than once
local langs = {}
@@ -1386,10 +1372,10 @@ function M.stylize_markdown(bufnr, contents, opts)
if not langs[lang] then
-- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
pcall(api.nvim_buf_del_var, bufnr, 'current_syntax')
- -- TODO(ashkan): better validation before this.
- if not pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft)) then
+ if #api.nvim_get_runtime_file(('syntax/%s.vim'):format(ft), true) == 0 then
return
end
+ pcall(vim.cmd, string.format('syntax include %s syntax/%s.vim', lang, ft))
langs[lang] = true
end
vim.cmd(
@@ -1424,15 +1410,53 @@ function M.stylize_markdown(bufnr, contents, opts)
return stripped
end
+--- @class lsp.util.NormalizeMarkdownOptions
+--- @field width integer Thematic breaks are expanded to this size. Defaults to 80.
+
+--- Normalizes Markdown input to a canonical form.
+---
+--- The returned Markdown adheres to the GitHub Flavored Markdown (GFM)
+--- specification.
+---
+--- The following transformations are made:
+---
+--- 1. Carriage returns ('\r') and empty lines at the beginning and end are removed
+--- 2. Successive empty lines are collapsed into a single empty line
+--- 3. Thematic breaks are expanded to the given width
+---
---@private
+---@param contents string[]
+---@param opts? lsp.util.NormalizeMarkdownOptions
+---@return string[] table of lines containing normalized Markdown
+---@see https://github.github.com/gfm
+function M._normalize_markdown(contents, opts)
+ validate({
+ contents = { contents, 't' },
+ opts = { opts, 't', true },
+ })
+ opts = opts or {}
+
+ -- 1. Carriage returns are removed
+ contents = vim.split(table.concat(contents, '\n'):gsub('\r', ''), '\n', { trimempty = true })
+
+ -- 2. Successive empty lines are collapsed into a single empty line
+ contents = collapse_blank_lines(contents)
+
+ -- 3. Thematic breaks are expanded to the given width
+ local divider = string.rep('─', opts.width or 80)
+ contents = replace_separators(contents, divider)
+
+ return contents
+end
+
--- Closes the preview window
---
----@param winnr number window id of preview window
+---@param winnr integer window id of preview window
---@param bufnrs table|nil optional list of ignored buffers
local function close_preview_window(winnr, bufnrs)
vim.schedule(function()
-- exit if we are in one of ignored buffers
- if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then
+ if bufnrs and vim.list_contains(bufnrs, api.nvim_get_current_buf()) then
return
end
@@ -1442,13 +1466,12 @@ local function close_preview_window(winnr, bufnrs)
end)
end
----@private
--- Creates autocommands to close a preview window when events happen.
---
---@param events table list of events
----@param winnr number window id of preview window
+---@param winnr integer window id of preview window
---@param bufnrs table list of buffers where the preview window will remain visible
----@see |autocmd-events|
+---@see autocmd-events
local function close_preview_autocmd(events, winnr, bufnrs)
local augroup = api.nvim_create_augroup('preview_window_' .. winnr, {
clear = true,
@@ -1478,13 +1501,14 @@ end
--- Computes size of float needed to show contents (with optional wrapping)
---
---@param contents table of lines to show in window
----@param opts dictionary with optional fields
+---@param opts table with optional fields
--- - height of floating window
--- - width of floating window
--- - wrap_at character to wrap at for computing height
--- - max_width maximal width of floating window
--- - max_height maximal height of floating window
----@returns width,height size of float
+---@return integer width size of float
+---@return integer height size of float
function M._make_floating_popup_size(contents, opts)
validate({
contents = { contents, 't' },
@@ -1503,7 +1527,7 @@ function M._make_floating_popup_size(contents, opts)
width = 0
for i, line in ipairs(contents) do
-- TODO(ashkan) use nvim_strdisplaywidth if/when that is introduced.
- line_widths[i] = vim.fn.strdisplaywidth(line)
+ line_widths[i] = vim.fn.strdisplaywidth(line:gsub('%z', '\n'))
width = math.max(line_widths[i], width)
end
end
@@ -1532,7 +1556,7 @@ function M._make_floating_popup_size(contents, opts)
height = 0
if vim.tbl_isempty(line_widths) then
for _, line in ipairs(contents) do
- local line_width = vim.fn.strdisplaywidth(line)
+ local line_width = vim.fn.strdisplaywidth(line:gsub('%z', '\n'))
height = height + math.ceil(line_width / wrap_at)
end
else
@@ -1553,23 +1577,22 @@ end
---
---@param contents table of lines to show in window
---@param syntax string of syntax to set for opened buffer
----@param opts table with optional fields (additional keys are passed on to |nvim_open_win()|)
---- - height: (number) height of floating window
---- - width: (number) width of floating window
+---@param opts table with optional fields (additional keys are filtered with |vim.lsp.util.make_floating_popup_options()|
+--- before they are passed on to |nvim_open_win()|)
+--- - height: (integer) height of floating window
+--- - width: (integer) width of floating window
--- - wrap: (boolean, default true) wrap long lines
---- - wrap_at: (number) character to wrap at for computing height when wrap is enabled
---- - max_width: (number) maximal width of floating window
---- - max_height: (number) maximal height of floating window
---- - pad_top: (number) number of lines to pad contents at top
---- - pad_bottom: (number) number of lines to pad contents at bottom
+--- - wrap_at: (integer) character to wrap at for computing height when wrap is enabled
+--- - max_width: (integer) maximal width of floating window
+--- - max_height: (integer) maximal height of floating window
--- - focus_id: (string) if a popup with this id is opened, then focus it
--- - close_events: (table) list of events that closes the floating window
--- - focusable: (boolean, default true) Make float focusable
--- - focus: (boolean, default true) If `true`, and if {focusable}
--- is also `true`, focus an existing floating window with the same
--- {focus_id}
----@returns bufnr,winnr buffer and window number of the newly created floating
----preview window
+---@return integer bufnr of newly created float window
+---@return integer winid of newly created float window preview window
function M.open_floating_preview(contents, syntax, opts)
validate({
contents = { contents, 't' },
@@ -1578,7 +1601,6 @@ function M.open_floating_preview(contents, syntax, opts)
})
opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
- opts.stylize_markdown = opts.stylize_markdown ~= false and vim.g.syntax_on ~= nil
opts.focus = opts.focus ~= false
opts.close_events = opts.close_events or { 'CursorMoved', 'CursorMovedI', 'InsertCharPre' }
@@ -1610,18 +1632,23 @@ function M.open_floating_preview(contents, syntax, opts)
api.nvim_win_close(existing_float, true)
end
+ -- Create the buffer
local floating_bufnr = api.nvim_create_buf(false, true)
- local do_stylize = syntax == 'markdown' and opts.stylize_markdown
-
- -- Clean up input: trim empty lines from the end, pad
- contents = M._trim(contents, opts)
+ -- Set up the contents, using treesitter for markdown
+ local do_stylize = syntax == 'markdown' and vim.g.syntax_on ~= nil
if do_stylize then
- -- applies the syntax and sets the lines to the buffer
- contents = M.stylize_markdown(floating_bufnr, contents, opts)
+ local width = M._make_floating_popup_size(contents, opts)
+ contents = M._normalize_markdown(contents, { width = width })
+ vim.bo[floating_bufnr].filetype = 'markdown'
+ vim.treesitter.start(floating_bufnr)
+ api.nvim_buf_set_lines(floating_bufnr, 0, -1, false, contents)
else
+ -- Clean up input: trim empty lines
+ contents = vim.split(table.concat(contents, '\n'), '\n', { trimempty = true })
+
if syntax then
- api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
+ vim.bo[floating_bufnr].syntax = syntax
end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
end
@@ -1636,17 +1663,18 @@ function M.open_floating_preview(contents, syntax, opts)
local float_option = M.make_floating_popup_options(width, height, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
+
if do_stylize then
- api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
- api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n')
+ vim.wo[floating_winnr].conceallevel = 2
end
-- disable folding
- api.nvim_win_set_option(floating_winnr, 'foldenable', false)
+ vim.wo[floating_winnr].foldenable = false
-- soft wrapping
- api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap)
+ vim.wo[floating_winnr].wrap = opts.wrap
+
+ vim.bo[floating_bufnr].modifiable = false
+ vim.bo[floating_bufnr].bufhidden = 'wipe'
- api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
- api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
api.nvim_buf_set_keymap(
floating_bufnr,
'n',
@@ -1670,18 +1698,18 @@ do --[[ References ]]
--- Removes document highlights from a buffer.
---
- ---@param bufnr number Buffer id
+ ---@param bufnr integer|nil Buffer id
function M.buf_clear_references(bufnr)
- validate({ bufnr = { bufnr, 'n', true } })
+ validate({ bufnr = { bufnr, { 'n' }, true } })
api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1)
end
--- Shows a list of document highlights for a certain buffer.
---
- ---@param bufnr number Buffer id
+ ---@param bufnr integer Buffer id
---@param references table List of `DocumentHighlight` objects to highlight
---@param offset_encoding string One of "utf-8", "utf-16", "utf-32".
- ---@see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent
+ ---@see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
function M.buf_highlight_references(bufnr, references, offset_encoding)
validate({
bufnr = { bufnr, 'n', true },
@@ -1729,18 +1757,23 @@ end)
--- Returns the items with the byte position calculated correctly and in sorted
--- order, for display in quickfix and location lists.
---
+--- The `user_data` field of each resulting item will contain the original
+--- `Location` or `LocationLink` it was computed from.
+---
--- The result can be passed to the {list} argument of |setqflist()| or
--- |setloclist()|.
---
---@param locations table list of `Location`s or `LocationLink`s
---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32
----@returns (table) list of items
+--- default to first client of buffer
+---@return table list of items
function M.locations_to_items(locations, offset_encoding)
if offset_encoding == nil then
vim.notify_once(
'locations_to_items must be called with valid offset encoding',
vim.log.levels.WARN
)
+ offset_encoding = vim.lsp.get_clients({ bufnr = 0 })[1].offset_encoding
end
local items = {}
@@ -1755,7 +1788,7 @@ function M.locations_to_items(locations, offset_encoding)
-- locations may be Location or LocationLink
local uri = d.uri or d.targetUri
local range = d.range or d.targetSelectionRange
- table.insert(grouped[uri], { start = range.start })
+ table.insert(grouped[uri], { start = range.start, location = d })
end
local keys = vim.tbl_keys(grouped)
@@ -1787,6 +1820,7 @@ function M.locations_to_items(locations, offset_encoding)
lnum = row + 1,
col = col + 1,
text = line,
+ user_data = temp.location,
})
end
end
@@ -1802,9 +1836,8 @@ end
--- Converts symbols to quickfix list items.
---
----@param symbols DocumentSymbol[] or SymbolInformation[]
+---@param symbols table DocumentSymbol[] or SymbolInformation[]
function M.symbols_to_items(symbols, bufnr)
- ---@private
local function _symbols_to_items(_symbols, _items, _bufnr)
for _, symbol in ipairs(_symbols) do
if symbol.location then -- SymbolInformation type
@@ -1842,8 +1875,9 @@ function M.symbols_to_items(symbols, bufnr)
end
--- Removes empty lines from the beginning and end.
----@param lines (table) list of lines to trim
----@returns (table) trimmed list of lines
+---@deprecated use `vim.split()` with `trimempty` instead
+---@param lines table list of lines to trim
+---@return table trimmed list of lines
function M.trim_empty_lines(lines)
local start = 1
for i = 1, #lines do
@@ -1867,8 +1901,9 @@ end
---
--- CAUTION: Modifies the input in-place!
---
----@param lines (table) list of lines
----@returns (string) filetype or "markdown" if it was unchanged.
+---@deprecated
+---@param lines table list of lines
+---@return string filetype or "markdown" if it was unchanged.
function M.try_trim_markdown_code_blocks(lines)
local language_id = lines[1]:match('^```(.*)')
if language_id then
@@ -1890,8 +1925,7 @@ function M.try_trim_markdown_code_blocks(lines)
return 'markdown'
end
----@private
----@param window number|nil: window handle or 0 for current, defaults to current
+---@param window integer|nil: window handle or 0 for current, defaults to current
---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window`
local function make_position_param(window, offset_encoding)
window = window or 0
@@ -1911,9 +1945,9 @@ end
--- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position.
---
----@param window number|nil: window handle or 0 for current, defaults to current
+---@param window integer|nil: window handle or 0 for current, defaults to current
---@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window`
----@returns `TextDocumentPositionParams` object
+---@return table `TextDocumentPositionParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams
function M.make_position_params(window, offset_encoding)
window = window or 0
@@ -1926,8 +1960,8 @@ function M.make_position_params(window, offset_encoding)
end
--- Utility function for getting the encoding of the first LSP client on the given buffer.
----@param bufnr (number) buffer handle or 0 for current, defaults to current
----@returns (string) encoding first client if there is one, nil otherwise
+---@param bufnr (integer) buffer handle or 0 for current, defaults to current
+---@return string encoding first client if there is one, nil otherwise
function M._get_offset_encoding(bufnr)
validate({
bufnr = { bufnr, 'n', true },
@@ -1935,7 +1969,7 @@ function M._get_offset_encoding(bufnr)
local offset_encoding
- for _, client in pairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do
+ for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
if client.offset_encoding == nil then
vim.notify_once(
string.format(
@@ -1949,7 +1983,7 @@ function M._get_offset_encoding(bufnr)
if not offset_encoding then
offset_encoding = this_offset_encoding
elseif offset_encoding ~= this_offset_encoding then
- vim.notify(
+ vim.notify_once(
'warning: multiple different client offset_encodings detected for buffer, this is not supported yet',
vim.log.levels.WARN
)
@@ -1964,9 +1998,9 @@ end
--- `textDocument/codeAction`, `textDocument/colorPresentation`,
--- `textDocument/rangeFormatting`.
---
----@param window number|nil: window handle or 0 for current, defaults to current
+---@param window integer|nil: window handle or 0 for current, defaults to current
---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of buffer of `window`
----@returns { textDocument = { uri = `current_file_uri` }, range = { start =
+---@return table { textDocument = { uri = `current_file_uri` }, range = { start =
---`current_position`, end = `current_position` } }
function M.make_range_params(window, offset_encoding)
local buf = api.nvim_win_get_buf(window or 0)
@@ -1981,13 +2015,13 @@ end
--- Using the given range in the current buffer, creates an object that
--- is similar to |vim.lsp.util.make_range_params()|.
---
----@param start_pos number[]|nil {row, col} mark-indexed position.
+---@param start_pos integer[]|nil {row,col} mark-indexed position.
--- Defaults to the start of the last visual selection.
----@param end_pos number[]|nil {row, col} mark-indexed position.
+---@param end_pos integer[]|nil {row,col} mark-indexed position.
--- Defaults to the end of the last visual selection.
----@param bufnr number|nil buffer handle or 0 for current, defaults to current
+---@param bufnr integer|nil buffer handle or 0 for current, defaults to current
---@param offset_encoding "utf-8"|"utf-16"|"utf-32"|nil defaults to `offset_encoding` of first client of `bufnr`
----@returns { textDocument = { uri = `current_file_uri` }, range = { start =
+---@return table { textDocument = { uri = `current_file_uri` }, range = { start =
---`start_position`, end = `end_position` } }
function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding)
validate({
@@ -2026,24 +2060,25 @@ end
--- Creates a `TextDocumentIdentifier` object for the current buffer.
---
----@param bufnr number|nil: Buffer handle, defaults to current
----@returns `TextDocumentIdentifier`
+---@param bufnr integer|nil: Buffer handle, defaults to current
+---@return table `TextDocumentIdentifier`
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier
function M.make_text_document_params(bufnr)
return { uri = vim.uri_from_bufnr(bufnr or 0) }
end
--- Create the workspace params
----@param added
----@param removed
+---@param added table
+---@param removed table
function M.make_workspace_params(added, removed)
return { event = { added = added, removed = removed } }
end
+
--- Returns indentation size.
---
---@see 'shiftwidth'
----@param bufnr (number|nil): Buffer handle, defaults to current
----@returns (number) indentation size
+---@param bufnr (integer|nil): Buffer handle, defaults to current
+---@return (integer) indentation size
function M.get_effective_tabstop(bufnr)
validate({ bufnr = { bufnr, 'n', true } })
local bo = bufnr and vim.bo[bufnr] or vim.bo
@@ -2054,7 +2089,7 @@ end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
---
---@param options table|nil with valid `FormattingOptions` entries
----@returns `DocumentFormattingParams` object
+---@return `DocumentFormattingParams` object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options)
validate({ options = { options, 't', true } })
@@ -2070,11 +2105,11 @@ end
--- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer.
---
----@param buf number buffer number (0 for current)
+---@param buf integer buffer number (0 for current)
---@param row 0-indexed line
---@param col 0-indexed byte offset in line
----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `buf`
----@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf}
+---@param offset_encoding string utf-8|utf-16|utf-32 defaults to `offset_encoding` of first client of `buf`
+---@return integer `offset_encoding` index of the character in line {row} column {col} in buffer {buf}
function M.character_offset(buf, row, col, offset_encoding)
local line = get_line(buf, row)
if offset_encoding == nil then
@@ -2082,6 +2117,7 @@ function M.character_offset(buf, row, col, offset_encoding)
'character_offset must be called with valid offset encoding',
vim.log.levels.WARN
)
+ offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding
end
-- If the col is past the EOL, use the line length.
if col > #line then
@@ -2092,11 +2128,11 @@ end
--- Helper function to return nested values in language server settings
---
----@param settings a table of language server settings
----@param section a string indicating the field of the settings table
----@returns (table or string) The value of settings accessed via section
+---@param settings table language server settings
+---@param section string indicating the field of the settings table
+---@return table|string The value of settings accessed via section
function M.lookup_section(settings, section)
- for part in vim.gsplit(section, '.', true) do
+ for part in vim.gsplit(section, '.', { plain = true }) do
settings = settings[part]
if settings == nil then
return vim.NIL
@@ -2105,9 +2141,93 @@ function M.lookup_section(settings, section)
return settings
end
+--- Converts line range (0-based, end-inclusive) to lsp range,
+--- handles absence of a trailing newline
+---
+---@param bufnr integer
+---@param start_line integer
+---@param end_line integer
+---@param offset_encoding lsp.PositionEncodingKind
+---@return lsp.Range
+local function make_line_range_params(bufnr, start_line, end_line, offset_encoding)
+ local last_line = api.nvim_buf_line_count(bufnr) - 1
+
+ ---@type lsp.Position
+ local end_pos
+
+ if end_line == last_line and not vim.api.nvim_get_option_value('endofline', { buf = bufnr }) then
+ end_pos = {
+ line = end_line,
+ character = M.character_offset(bufnr, end_line, #get_line(bufnr, end_line), offset_encoding),
+ }
+ else
+ end_pos = { line = end_line + 1, character = 0 }
+ end
+
+ return {
+ start = { line = start_line, character = 0 },
+ ['end'] = end_pos,
+ }
+end
+
+---@private
+--- Request updated LSP information for a buffer.
+---
+---@class lsp.util.RefreshOptions
+---@field bufnr integer? Buffer to refresh (default: 0)
+---@field only_visible? boolean Whether to only refresh for the visible regions of the buffer (default: false)
+---@field client_id? integer Client ID to refresh (default: all clients)
+--
+---@param method string LSP method to call
+---@param opts? lsp.util.RefreshOptions Options table
+function M._refresh(method, opts)
+ opts = opts or {}
+ local bufnr = opts.bufnr
+ if bufnr == nil or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
+ local clients = vim.lsp.get_clients({ bufnr = bufnr, method = method, id = opts.client_id })
+
+ if #clients == 0 then
+ return
+ end
+
+ local textDocument = M.make_text_document_params(bufnr)
+
+ local only_visible = opts.only_visible or false
+
+ if only_visible then
+ for _, window in ipairs(api.nvim_list_wins()) do
+ if api.nvim_win_get_buf(window) == bufnr then
+ local first = vim.fn.line('w0', window)
+ local last = vim.fn.line('w$', window)
+ for _, client in ipairs(clients) do
+ client.request(method, {
+ textDocument = textDocument,
+ range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding),
+ }, nil, bufnr)
+ end
+ end
+ end
+ else
+ for _, client in ipairs(clients) do
+ client.request(method, {
+ textDocument = textDocument,
+ range = make_line_range_params(
+ bufnr,
+ 0,
+ api.nvim_buf_line_count(bufnr) - 1,
+ client.offset_encoding
+ ),
+ }, nil, bufnr)
+ end
+ end
+end
+
M._get_line_byte_from_position = get_line_byte_from_position
+---@nodoc
M.buf_versions = {}
return M
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/re.lua b/runtime/lua/vim/re.lua
new file mode 100644
index 0000000000..007eb27ed8
--- /dev/null
+++ b/runtime/lua/vim/re.lua
@@ -0,0 +1,271 @@
+--
+-- Copyright 2007-2023, Lua.org & PUC-Rio (see 'lpeg.html' for license)
+-- written by Roberto Ierusalimschy
+--
+--- vendored from lpeg-1.1.0
+
+-- imported functions and modules
+local tonumber, type, print, error = tonumber, type, print, error
+local setmetatable = setmetatable
+local m = require"lpeg"
+
+-- 'm' will be used to parse expressions, and 'mm' will be used to
+-- create expressions; that is, 're' runs on 'm', creating patterns
+-- on 'mm'
+local mm = m
+
+-- patterns' metatable
+local mt = getmetatable(mm.P(0))
+
+
+local version = _VERSION
+
+-- No more global accesses after this point
+_ENV = nil -- does no harm in Lua 5.1
+
+
+local any = m.P(1)
+
+
+-- Pre-defined names
+local Predef = { nl = m.P"\n" }
+
+
+local mem
+local fmem
+local gmem
+
+
+local function updatelocale ()
+ mm.locale(Predef)
+ Predef.a = Predef.alpha
+ Predef.c = Predef.cntrl
+ Predef.d = Predef.digit
+ Predef.g = Predef.graph
+ Predef.l = Predef.lower
+ Predef.p = Predef.punct
+ Predef.s = Predef.space
+ Predef.u = Predef.upper
+ Predef.w = Predef.alnum
+ Predef.x = Predef.xdigit
+ Predef.A = any - Predef.a
+ Predef.C = any - Predef.c
+ Predef.D = any - Predef.d
+ Predef.G = any - Predef.g
+ Predef.L = any - Predef.l
+ Predef.P = any - Predef.p
+ Predef.S = any - Predef.s
+ Predef.U = any - Predef.u
+ Predef.W = any - Predef.w
+ Predef.X = any - Predef.x
+ mem = {} -- restart memoization
+ fmem = {}
+ gmem = {}
+ local mt = {__mode = "v"}
+ setmetatable(mem, mt)
+ setmetatable(fmem, mt)
+ setmetatable(gmem, mt)
+end
+
+
+updatelocale()
+
+
+
+local I = m.P(function (s,i) print(i, s:sub(1, i-1)); return i end)
+
+
+local function patt_error (s, i)
+ local msg = (#s < i + 20) and s:sub(i)
+ or s:sub(i,i+20) .. "..."
+ msg = ("pattern error near '%s'"):format(msg)
+ error(msg, 2)
+end
+
+local function mult (p, n)
+ local np = mm.P(true)
+ while n >= 1 do
+ if n%2 >= 1 then np = np * p end
+ p = p * p
+ n = n/2
+ end
+ return np
+end
+
+local function equalcap (s, i, c)
+ if type(c) ~= "string" then return nil end
+ local e = #c + i
+ if s:sub(i, e - 1) == c then return e else return nil end
+end
+
+
+local S = (Predef.space + "--" * (any - Predef.nl)^0)^0
+
+local name = m.R("AZ", "az", "__") * m.R("AZ", "az", "__", "09")^0
+
+local arrow = S * "<-"
+
+local seq_follow = m.P"/" + ")" + "}" + ":}" + "~}" + "|}" + (name * arrow) + -1
+
+name = m.C(name)
+
+
+-- a defined name only have meaning in a given environment
+local Def = name * m.Carg(1)
+
+
+local function getdef (id, defs)
+ local c = defs and defs[id]
+ if not c then error("undefined name: " .. id) end
+ return c
+end
+
+-- match a name and return a group of its corresponding definition
+-- and 'f' (to be folded in 'Suffix')
+local function defwithfunc (f)
+ return m.Cg(Def / getdef * m.Cc(f))
+end
+
+
+local num = m.C(m.R"09"^1) * S / tonumber
+
+local String = "'" * m.C((any - "'")^0) * "'" +
+ '"' * m.C((any - '"')^0) * '"'
+
+
+local defined = "%" * Def / function (c,Defs)
+ local cat = Defs and Defs[c] or Predef[c]
+ if not cat then error ("name '" .. c .. "' undefined") end
+ return cat
+end
+
+local Range = m.Cs(any * (m.P"-"/"") * (any - "]")) / mm.R
+
+local item = (defined + Range + m.C(any)) / m.P
+
+local Class =
+ "["
+ * (m.C(m.P"^"^-1)) -- optional complement symbol
+ * (item * ((item % mt.__add) - "]")^0) /
+ function (c, p) return c == "^" and any - p or p end
+ * "]"
+
+local function adddef (t, k, exp)
+ if t[k] then
+ error("'"..k.."' already defined as a rule")
+ else
+ t[k] = exp
+ end
+ return t
+end
+
+local function firstdef (n, r) return adddef({n}, n, r) end
+
+
+local function NT (n, b)
+ if not b then
+ error("rule '"..n.."' used outside a grammar")
+ else return mm.V(n)
+ end
+end
+
+
+local exp = m.P{ "Exp",
+ Exp = S * ( m.V"Grammar"
+ + m.V"Seq" * ("/" * S * m.V"Seq" % mt.__add)^0 );
+ Seq = (m.Cc(m.P"") * (m.V"Prefix" % mt.__mul)^0)
+ * (#seq_follow + patt_error);
+ Prefix = "&" * S * m.V"Prefix" / mt.__len
+ + "!" * S * m.V"Prefix" / mt.__unm
+ + m.V"Suffix";
+ Suffix = m.V"Primary" * S *
+ ( ( m.P"+" * m.Cc(1, mt.__pow)
+ + m.P"*" * m.Cc(0, mt.__pow)
+ + m.P"?" * m.Cc(-1, mt.__pow)
+ + "^" * ( m.Cg(num * m.Cc(mult))
+ + m.Cg(m.C(m.S"+-" * m.R"09"^1) * m.Cc(mt.__pow))
+ )
+ + "->" * S * ( m.Cg((String + num) * m.Cc(mt.__div))
+ + m.P"{}" * m.Cc(nil, m.Ct)
+ + defwithfunc(mt.__div)
+ )
+ + "=>" * S * defwithfunc(mm.Cmt)
+ + ">>" * S * defwithfunc(mt.__mod)
+ + "~>" * S * defwithfunc(mm.Cf)
+ ) % function (a,b,f) return f(a,b) end * S
+ )^0;
+ Primary = "(" * m.V"Exp" * ")"
+ + String / mm.P
+ + Class
+ + defined
+ + "{:" * (name * ":" + m.Cc(nil)) * m.V"Exp" * ":}" /
+ function (n, p) return mm.Cg(p, n) end
+ + "=" * name / function (n) return mm.Cmt(mm.Cb(n), equalcap) end
+ + m.P"{}" / mm.Cp
+ + "{~" * m.V"Exp" * "~}" / mm.Cs
+ + "{|" * m.V"Exp" * "|}" / mm.Ct
+ + "{" * m.V"Exp" * "}" / mm.C
+ + m.P"." * m.Cc(any)
+ + (name * -arrow + "<" * name * ">") * m.Cb("G") / NT;
+ Definition = name * arrow * m.V"Exp";
+ Grammar = m.Cg(m.Cc(true), "G") *
+ ((m.V"Definition" / firstdef) * (m.V"Definition" % adddef)^0) / mm.P
+}
+
+local pattern = S * m.Cg(m.Cc(false), "G") * exp / mm.P * (-any + patt_error)
+
+
+local function compile (p, defs)
+ if mm.type(p) == "pattern" then return p end -- already compiled
+ local cp = pattern:match(p, 1, defs)
+ if not cp then error("incorrect pattern", 3) end
+ return cp
+end
+
+local function match (s, p, i)
+ local cp = mem[p]
+ if not cp then
+ cp = compile(p)
+ mem[p] = cp
+ end
+ return cp:match(s, i or 1)
+end
+
+local function find (s, p, i)
+ local cp = fmem[p]
+ if not cp then
+ cp = compile(p) / 0
+ cp = mm.P{ mm.Cp() * cp * mm.Cp() + 1 * mm.V(1) }
+ fmem[p] = cp
+ end
+ local i, e = cp:match(s, i or 1)
+ if i then return i, e - 1
+ else return i
+ end
+end
+
+local function gsub (s, p, rep)
+ local g = gmem[p] or {} -- ensure gmem[p] is not collected while here
+ gmem[p] = g
+ local cp = g[rep]
+ if not cp then
+ cp = compile(p)
+ cp = mm.Cs((cp / rep + 1)^0)
+ g[rep] = cp
+ end
+ return cp:match(s)
+end
+
+
+-- exported names
+local re = {
+ compile = compile,
+ match = match,
+ find = find,
+ gsub = gsub,
+ updatelocale = updatelocale,
+}
+
+if version == "Lua 5.1" then _G.re = re end
+
+return re
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index 443b152273..d29c356af3 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -1,11 +1,10 @@
local M = {}
----@private
--- Reads trust database from $XDG_STATE_HOME/nvim/trust.
---
----@return (table) Contents of trust database, if it exists. Empty table otherwise.
+---@return table<string, string> Contents of trust database, if it exists. Empty table otherwise.
local function read_trust()
- local trust = {}
+ local trust = {} ---@type table<string, string>
local f = io.open(vim.fn.stdpath('state') .. '/trust', 'r')
if f then
local contents = f:read('*a')
@@ -22,16 +21,15 @@ local function read_trust()
return trust
end
----@private
--- Writes provided {trust} table to trust database at
--- $XDG_STATE_HOME/nvim/trust.
---
----@param trust (table) Trust table to write
+---@param trust table<string, string> Trust table to write
local function write_trust(trust)
vim.validate({ trust = { trust, 't' } })
local f = assert(io.open(vim.fn.stdpath('state') .. '/trust', 'w'))
- local t = {}
+ local t = {} ---@type string[]
for p, h in pairs(trust) do
t[#t + 1] = string.format('%s %s\n', h, p)
end
@@ -51,7 +49,7 @@ end
--- trusted, or nil otherwise.
function M.read(path)
vim.validate({ path = { path, 's' } })
- local fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
+ local fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
if not fullpath then
return nil
end
@@ -63,7 +61,7 @@ function M.read(path)
return nil
end
- local contents
+ local contents ---@type string?
do
local f = io.open(fullpath, 'r')
if not f then
@@ -110,6 +108,11 @@ function M.read(path)
return contents
end
+---@class vim.trust.opts
+---@field action string
+---@field path? string
+---@field bufnr? integer
+
--- Manage the trust database.
---
--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
@@ -121,9 +124,8 @@ end
--- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}.
--- Cannot be used when {action} is "allow".
--- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}.
----@return (boolean, string) success, msg:
---- - true and full path of target file if operation was successful
---- - false and error message on failure
+---@return boolean success true if operation was successful
+---@return string msg full path if operation was successful, else error message
function M.trust(opts)
vim.validate({
path = { opts.path, 's', true },
@@ -137,6 +139,7 @@ function M.trust(opts)
},
})
+ ---@cast opts vim.trust.opts
local path = opts.path
local bufnr = opts.bufnr
local action = opts.action
@@ -147,15 +150,15 @@ function M.trust(opts)
assert(not path, '"path" is not valid when action is "allow"')
end
- local fullpath
+ local fullpath ---@type string?
if path then
- fullpath = vim.loop.fs_realpath(vim.fs.normalize(path))
+ fullpath = vim.uv.fs_realpath(vim.fs.normalize(path))
elseif bufnr then
local bufname = vim.api.nvim_buf_get_name(bufnr)
if bufname == '' then
return false, 'buffer is not associated with a file'
end
- fullpath = vim.loop.fs_realpath(vim.fs.normalize(bufname))
+ fullpath = vim.uv.fs_realpath(vim.fs.normalize(bufname))
else
error('one of "path" or "bufnr" is required')
end
@@ -168,7 +171,8 @@ function M.trust(opts)
if action == 'allow' then
local newline = vim.bo[bufnr].fileformat == 'unix' and '\n' or '\r\n'
- local contents = table.concat(vim.api.nvim_buf_get_lines(bufnr, 0, -1, false), newline)
+ local contents =
+ table.concat(vim.api.nvim_buf_get_lines(bufnr --[[@as integer]], 0, -1, false), newline)
if vim.bo[bufnr].endofline then
contents = contents .. newline
end
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index cc48e3f193..9542d93789 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -6,8 +6,48 @@
-- or the test suite. (Eventually the test suite will be run in a worker process,
-- so this wouldn't be a separate case to consider)
+---@diagnostic disable-next-line: lowercase-global
vim = vim or {}
+local function _id(v)
+ return v
+end
+
+local deepcopy
+
+local deepcopy_funcs = {
+ table = function(orig, cache)
+ if cache[orig] then
+ return cache[orig]
+ end
+ local copy = {}
+
+ cache[orig] = copy
+ local mt = getmetatable(orig)
+ for k, v in pairs(orig) do
+ copy[deepcopy(k, cache)] = deepcopy(v, cache)
+ end
+ return setmetatable(copy, mt)
+ end,
+ number = _id,
+ string = _id,
+ ['nil'] = _id,
+ boolean = _id,
+ ['function'] = _id,
+}
+
+deepcopy = function(orig, _cache)
+ local f = deepcopy_funcs[type(orig)]
+ if f then
+ return f(orig, _cache or {})
+ else
+ if type(orig) == 'userdata' and orig == vim.NIL then
+ return vim.NIL
+ end
+ error('Cannot deepcopy object of type ' .. type(orig))
+ end
+end
+
--- Returns a deep copy of the given object. Non-table objects are copied as
--- in a typical Lua assignment, whereas table objects are copied recursively.
--- Functions are naively copied, so functions in the copied table point to the
@@ -17,63 +57,60 @@ vim = vim or {}
---@generic T: table
---@param orig T Table to copy
---@return T Table of copied keys and (nested) values.
-function vim.deepcopy(orig) end -- luacheck: no unused
-vim.deepcopy = (function()
- local function _id(v)
- return v
- end
-
- local deepcopy_funcs = {
- table = function(orig, cache)
- if cache[orig] then
- return cache[orig]
- end
- local copy = {}
-
- cache[orig] = copy
- local mt = getmetatable(orig)
- for k, v in pairs(orig) do
- copy[vim.deepcopy(k, cache)] = vim.deepcopy(v, cache)
- end
- return setmetatable(copy, mt)
- end,
- number = _id,
- string = _id,
- ['nil'] = _id,
- boolean = _id,
- ['function'] = _id,
- }
+function vim.deepcopy(orig)
+ return deepcopy(orig)
+end
- return function(orig, cache)
- local f = deepcopy_funcs[type(orig)]
- if f then
- return f(orig, cache or {})
- else
- if type(orig) == 'userdata' and orig == vim.NIL then
- return vim.NIL
- end
- error('Cannot deepcopy object of type ' .. type(orig))
- end
+--- Gets an |iterator| that splits a string at each instance of a separator, in "lazy" fashion
+--- (as opposed to |vim.split()| which is "eager").
+---
+--- Example:
+---
+--- ```lua
+--- for s in vim.gsplit(':aa::b:', ':', {plain=true}) do
+--- print(s)
+--- end
+--- ```
+---
+--- If you want to also inspect the separator itself (instead of discarding it), use
+--- |string.gmatch()|. Example:
+---
+--- ```lua
+--- for word, num in ('foo111bar222'):gmatch('([^0-9]*)(%d*)') do
+--- print(('word: %s num: %s'):format(word, num))
+--- end
+--- ```
+---
+--- @see |string.gmatch()|
+--- @see |vim.split()|
+--- @see |lua-patterns|
+--- @see https://www.lua.org/pil/20.2.html
+--- @see http://lua-users.org/wiki/StringLibraryTutorial
+---
+--- @param s string String to split
+--- @param sep string Separator or pattern
+--- @param opts (table|nil) Keyword arguments |kwargs|:
+--- - plain: (boolean) Use `sep` literally (as in string.find).
+--- - trimempty: (boolean) Discard empty segments at start and end of the sequence.
+---@return fun():string|nil (function) Iterator over the split components
+function vim.gsplit(s, sep, opts)
+ local plain
+ local trimempty = false
+ if type(opts) == 'boolean' then
+ plain = opts -- For backwards compatibility.
+ else
+ vim.validate({ s = { s, 's' }, sep = { sep, 's' }, opts = { opts, 't', true } })
+ opts = opts or {}
+ plain, trimempty = opts.plain, opts.trimempty
end
-end)()
-
---- Splits a string at each instance of a separator.
----
----@see |vim.split()|
----@see |luaref-patterns|
----@see https://www.lua.org/pil/20.2.html
----@see http://lua-users.org/wiki/StringLibraryTutorial
----
----@param s string String to split
----@param sep string Separator or pattern
----@param plain (boolean|nil) If `true` use `sep` literally (passed to string.find)
----@return fun():string (function) Iterator over the split components
-function vim.gsplit(s, sep, plain)
- vim.validate({ s = { s, 's' }, sep = { sep, 's' }, plain = { plain, 'b', true } })
local start = 1
local done = false
+ -- For `trimempty`: queue of collected segments, to be emitted at next pass.
+ local segs = {}
+ local empty_start = true -- Only empty segments seen so far.
+
local function _pass(i, j, ...)
if i then
assert(j + 1 > start, 'Infinite loop detected')
@@ -87,72 +124,69 @@ function vim.gsplit(s, sep, plain)
end
return function()
- if done or (s == '' and sep == '') then
- return
- end
- if sep == '' then
+ if trimempty and #segs > 0 then
+ -- trimempty: Pop the collected segments.
+ return table.remove(segs)
+ elseif done or (s == '' and sep == '') then
+ return nil
+ elseif sep == '' then
if start == #s then
done = true
end
return _pass(start + 1, start)
end
- return _pass(s:find(sep, start, plain))
+
+ local seg = _pass(s:find(sep, start, plain))
+
+ -- Trim empty segments from start/end.
+ if trimempty and seg ~= '' then
+ empty_start = false
+ elseif trimempty and seg == '' then
+ while not done and seg == '' do
+ table.insert(segs, 1, '')
+ seg = _pass(s:find(sep, start, plain))
+ end
+ if done and seg == '' then
+ return nil
+ elseif empty_start then
+ empty_start = false
+ segs = {}
+ return seg
+ end
+ if seg ~= '' then
+ table.insert(segs, 1, seg)
+ end
+ return table.remove(segs)
+ end
+
+ return seg
end
end
---- Splits a string at each instance of a separator.
+--- Splits a string at each instance of a separator and returns the result as a table (unlike
+--- |vim.gsplit()|).
---
--- Examples:
---- <pre>lua
---- split(":aa::b:", ":") --> {'','aa','','b',''}
---- split("axaby", "ab?") --> {'','x','y'}
---- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'}
---- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
---- </pre>
+---
+--- ```lua
+--- split(":aa::b:", ":") --> {'','aa','','b',''}
+--- split("axaby", "ab?") --> {'','x','y'}
+--- split("x*yz*o", "*", {plain=true}) --> {'x','yz','o'}
+--- split("|x|y|z|", "|", {trimempty=true}) --> {'x', 'y', 'z'}
+--- ```
---
---@see |vim.gsplit()|
+---@see |string.gmatch()|
---
---@param s string String to split
---@param sep string Separator or pattern
----@param kwargs (table|nil) Keyword arguments:
---- - plain: (boolean) If `true` use `sep` literally (passed to string.find)
---- - trimempty: (boolean) If `true` remove empty items from the front
---- and back of the list
+---@param opts (table|nil) Keyword arguments |kwargs| accepted by |vim.gsplit()|
---@return string[] List of split components
-function vim.split(s, sep, kwargs)
- local plain
- local trimempty = false
- if type(kwargs) == 'boolean' then
- -- Support old signature for backward compatibility
- plain = kwargs
- else
- vim.validate({ kwargs = { kwargs, 't', true } })
- kwargs = kwargs or {}
- plain = kwargs.plain
- trimempty = kwargs.trimempty
- end
-
+function vim.split(s, sep, opts)
local t = {}
- local skip = trimempty
- for c in vim.gsplit(s, sep, plain) do
- if c ~= '' then
- skip = false
- end
-
- if not skip then
- table.insert(t, c)
- end
- end
-
- if trimempty then
- for i = #t, 1, -1 do
- if t[i] ~= '' then
- break
- end
- table.remove(t, i)
- end
+ for c in vim.gsplit(s, sep, opts) do
+ table.insert(t, c)
end
-
return t
end
@@ -224,12 +258,54 @@ function vim.tbl_filter(func, t)
return rettab
end
---- Checks if a list-like (vector) table contains `value`.
+--- Checks if a table contains a given value, specified either directly or via
+--- a predicate that is checked for each value.
+---
+--- Example:
+---
+--- ```lua
+--- vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
+--- return vim.deep_equal(v, { 'b', 'c' })
+--- end, { predicate = true })
+--- -- true
+--- ```
+---
+---@see |vim.list_contains()| for checking values in list-like tables
---
---@param t table Table to check
+---@param value any Value to compare or predicate function reference
+---@param opts (table|nil) Keyword arguments |kwargs|:
+--- - predicate: (boolean) `value` is a function reference to be checked (default false)
+---@return boolean `true` if `t` contains `value`
+function vim.tbl_contains(t, value, opts)
+ vim.validate({ t = { t, 't' }, opts = { opts, 't', true } })
+
+ local pred
+ if opts and opts.predicate then
+ vim.validate({ value = { value, 'c' } })
+ pred = value
+ else
+ pred = function(v)
+ return v == value
+ end
+ end
+
+ for _, v in pairs(t) do
+ if pred(v) then
+ return true
+ end
+ end
+ return false
+end
+
+--- Checks if a list-like table (integer keys without gaps) contains `value`.
+---
+---@see |vim.tbl_contains()| for checking values in general tables
+---
+---@param t table Table to check (must be list-like, not validated)
---@param value any Value to compare
---@return boolean `true` if `t` contains `value`
-function vim.tbl_contains(t, value)
+function vim.list_contains(t, value)
vim.validate({ t = { t, 't' } })
for _, v in ipairs(t) do
@@ -251,10 +327,9 @@ function vim.tbl_isempty(t)
return next(t) == nil
end
---- We only merge empty tables or tables that are not a list
----@private
+--- We only merge empty tables or tables that are not an array (indexed by integers)
local function can_merge(v)
- return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_islist(v))
+ return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.tbl_isarray(v))
end
local function tbl_extend(behavior, deep_extend, ...)
@@ -295,7 +370,7 @@ local function tbl_extend(behavior, deep_extend, ...)
return ret
end
---- Merges two or more map-like tables.
+--- Merges two or more tables.
---
---@see |extend()|
---
@@ -303,13 +378,13 @@ end
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
----@param ... table Two or more map-like tables
+---@param ... table Two or more tables
---@return table Merged table
function vim.tbl_extend(behavior, ...)
return tbl_extend(behavior, false, ...)
end
---- Merges recursively two or more map-like tables.
+--- Merges recursively two or more tables.
---
---@see |vim.tbl_extend()|
---
@@ -319,7 +394,7 @@ end
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
----@param ... T2 Two or more map-like tables
+---@param ... T2 Two or more tables
---@return T1|T2 (table) Merged table
function vim.tbl_deep_extend(behavior, ...)
return tbl_extend(behavior, true, ...)
@@ -384,13 +459,14 @@ end
--- Return `nil` if the key does not exist.
---
--- Examples:
---- <pre>lua
---- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true
---- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil
---- </pre>
+---
+--- ```lua
+--- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true
+--- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil
+--- ```
---
---@param o table Table to index
----@param ... string Optional strings (0 or more, variadic) via which to index the table
+---@param ... any Optional keys (0 or more, variadic) via which to index the table
---
---@return any Nested value indexed by key (if it exists), else nil
function vim.tbl_get(o, ...)
@@ -418,8 +494,8 @@ end
---@generic T: table
---@param dst T List which will be modified and appended to
---@param src table List from which values will be inserted
----@param start (number|nil) Start index on src. Defaults to 1
----@param finish (number|nil) Final index on src. Defaults to `#src`
+---@param start (integer|nil) Start index on src. Defaults to 1
+---@param finish (integer|nil) Final index on src. Defaults to `#src`
---@return T dst
function vim.list_extend(dst, src, start, finish)
vim.validate({
@@ -458,12 +534,12 @@ function vim.tbl_flatten(t)
return result
end
---- Enumerate a table sorted by its keys.
+--- Enumerates key-value pairs of a table, ordered by key.
---
---@see Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@param t table List-like table
----@return iterator over sorted keys and their values
+---@param t table Dict-like table
+---@return function # |for-in| iterator over sorted keys and their values
function vim.spairs(t)
assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
@@ -475,7 +551,6 @@ function vim.spairs(t)
table.sort(keys)
-- Return the iterator function.
- -- TODO(justinmk): Return "iterator function, table {t}, and nil", like pairs()?
local i = 0
return function()
i = i + 1
@@ -485,15 +560,18 @@ function vim.spairs(t)
end
end
---- Tests if a Lua table can be treated as an array.
+--- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous).
---
---- Empty table `{}` is assumed to be an array, unless it was created by
---- |vim.empty_dict()| or returned as a dict-like |API| or Vimscript result,
---- for example from |rpcrequest()| or |vim.fn|.
+--- If the indexes start from 1 and are contiguous then the array is also a list. |vim.tbl_islist()|
---
----@param t table Table
----@return boolean `true` if array-like table, else `false`
-function vim.tbl_islist(t)
+--- Empty table `{}` is an array, unless it was created by |vim.empty_dict()| or returned as
+--- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|.
+---
+---@see https://github.com/openresty/luajit2#tableisarray
+---
+---@param t table
+---@return boolean `true` if array-like table, else `false`.
+function vim.tbl_isarray(t)
if type(t) ~= 'table' then
return false
end
@@ -501,7 +579,8 @@ function vim.tbl_islist(t)
local count = 0
for k, _ in pairs(t) do
- if type(k) == 'number' then
+ --- Check if the number k is an integer
+ if type(k) == 'number' and k == math.floor(k) then
count = count + 1
else
return false
@@ -520,16 +599,50 @@ function vim.tbl_islist(t)
end
end
+--- Tests if `t` is a "list": a table indexed _only_ by contiguous integers starting from 1 (what
+--- |lua-length| calls a "regular array").
+---
+--- Empty table `{}` is a list, unless it was created by |vim.empty_dict()| or returned as
+--- a dict-like |API| or Vimscript result, for example from |rpcrequest()| or |vim.fn|.
+---
+---@see |vim.tbl_isarray()|
+---
+---@param t table
+---@return boolean `true` if list-like table, else `false`.
+function vim.tbl_islist(t)
+ if type(t) ~= 'table' then
+ return false
+ end
+
+ local num_elem = vim.tbl_count(t)
+
+ if num_elem == 0 then
+ -- TODO(bfredl): in the future, we will always be inside nvim
+ -- then this check can be deleted.
+ if vim._empty_dict_mt == nil then
+ return nil
+ end
+ return getmetatable(t) ~= vim._empty_dict_mt
+ else
+ for i = 1, num_elem do
+ if t[i] == nil then
+ return false
+ end
+ end
+ return true
+ end
+end
+
--- Counts the number of non-nil values in table `t`.
---
---- <pre>lua
+--- ```lua
--- vim.tbl_count({ a=1, b=2 }) --> 2
--- vim.tbl_count({ 1, 2 }) --> 2
---- </pre>
+--- ```
---
---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
---@param t table Table
----@return number Number of non-nil values in table
+---@return integer Number of non-nil values in table
function vim.tbl_count(t)
vim.validate({ t = { t, 't' } })
@@ -544,8 +657,8 @@ end
---
---@generic T
---@param list T[] (list) Table
----@param start number|nil Start range of slice
----@param finish number|nil End range of slice
+---@param start integer|nil Start range of slice
+---@param finish integer|nil End range of slice
---@return T[] (list) Copy of table sliced from start to finish (inclusive)
function vim.list_slice(list, start, finish)
local new_list = {}
@@ -557,7 +670,7 @@ end
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
----@see |luaref-patterns|
+---@see |lua-patterns|
---@see https://www.lua.org/pil/20.2.html
---@param s string String to trim
---@return string String with whitespace removed from its beginning and end
@@ -596,58 +709,6 @@ function vim.endswith(s, suffix)
return #suffix == 0 or s:sub(-#suffix) == suffix
end
---- Validates a parameter specification (types and values).
----
---- Usage example:
---- <pre>lua
---- function user.new(name, age, hobbies)
---- vim.validate{
---- name={name, 'string'},
---- age={age, 'number'},
---- hobbies={hobbies, 'table'},
---- }
---- ...
---- end
---- </pre>
----
---- Examples with explicit argument values (can be run directly):
---- <pre>lua
---- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
---- --> NOP (success)
----
---- vim.validate{arg1={1, 'table'}}
---- --> error('arg1: expected table, got number')
----
---- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
---- --> error('arg1: expected even number, got 3')
---- </pre>
----
---- If multiple types are valid they can be given as a list.
---- <pre>lua
---- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
---- --> NOP (success)
----
---- vim.validate{arg1={1, {'string', table'}}}
---- --> error('arg1: expected string|table, got number')
----
---- </pre>
----
----@param opt table Names of parameters to validate. Each key is a parameter
---- name; each value is a tuple in one of these forms:
---- 1. (arg_value, type_name, optional)
---- - arg_value: argument value
---- - type_name: string|table type name, one of: ("table", "t", "string",
---- "s", "number", "n", "boolean", "b", "function", "f", "nil",
---- "thread", "userdata") or list of them.
---- - optional: (optional) boolean, if true, `nil` is valid
---- 2. (arg_value, fn, msg)
---- - arg_value: argument value
---- - fn: any function accepting one argument, returns true if and
---- only if the argument is valid. Can optionally return an additional
---- informative error message as the second returned value.
---- - msg: (optional) error string if validation fails
-function vim.validate(opt) end -- luacheck: no unused
-
do
local type_names = {
['table'] = 'table',
@@ -671,7 +732,6 @@ do
return type(val) == t or (t == 'callable' and vim.is_callable(val))
end
- ---@private
local function is_valid(opt)
if type(opt) ~= 'table' then
return false, string.format('opt: expected table, got %s', type(opt))
@@ -733,6 +793,59 @@ do
return true, nil
end
+ --- Validates a parameter specification (types and values).
+ ---
+ --- Usage example:
+ ---
+ --- ```lua
+ --- function user.new(name, age, hobbies)
+ --- vim.validate{
+ --- name={name, 'string'},
+ --- age={age, 'number'},
+ --- hobbies={hobbies, 'table'},
+ --- }
+ --- ...
+ --- end
+ --- ```
+ ---
+ --- Examples with explicit argument values (can be run directly):
+ ---
+ --- ```lua
+ --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
+ --- --> NOP (success)
+ ---
+ --- vim.validate{arg1={1, 'table'}}
+ --- --> error('arg1: expected table, got number')
+ ---
+ --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
+ --- --> error('arg1: expected even number, got 3')
+ --- ```
+ ---
+ --- If multiple types are valid they can be given as a list.
+ ---
+ --- ```lua
+ --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
+ --- -- NOP (success)
+ ---
+ --- vim.validate{arg1={1, {'string', 'table'}}}
+ --- -- error('arg1: expected string|table, got number')
+ ---
+ --- ```
+ ---
+ ---@param opt table Names of parameters to validate. Each key is a parameter
+ --- name; each value is a tuple in one of these forms:
+ --- 1. (arg_value, type_name, optional)
+ --- - arg_value: argument value
+ --- - type_name: string|table type name, one of: ("table", "t", "string",
+ --- "s", "number", "n", "boolean", "b", "function", "f", "nil",
+ --- "thread", "userdata") or list of them.
+ --- - optional: (optional) boolean, if true, `nil` is valid
+ --- 2. (arg_value, fn, msg)
+ --- - arg_value: argument value
+ --- - fn: any function accepting one argument, returns true if and
+ --- only if the argument is valid. Can optionally return an additional
+ --- informative error message as the second returned value.
+ --- - msg: (optional) error string if validation fails
function vim.validate(opt)
local ok, err_msg = is_valid(opt)
if not ok then
@@ -755,30 +868,122 @@ function vim.is_callable(f)
return type(m.__call) == 'function'
end
---- Creates a table whose members are automatically created when accessed, if they don't already
---- exist.
----
---- They mimic defaultdict in python.
+--- Creates a table whose missing keys are provided by {createfn} (like Python's "defaultdict").
---
---- If {create} is `nil`, this will create a defaulttable whose constructor function is
---- this function, effectively allowing to create nested tables on the fly:
+--- If {createfn} is `nil` it defaults to defaulttable() itself, so accessing nested keys creates
+--- nested tables:
---
---- <pre>lua
+--- ```lua
--- local a = vim.defaulttable()
--- a.b.c = 1
---- </pre>
+--- ```
---
----@param create function|nil The function called to create a missing value.
----@return table Empty table with metamethod
-function vim.defaulttable(create)
- create = create or vim.defaulttable
+---@param createfn function?(key:any):any Provides the value for a missing `key`.
+---@return table # Empty table with `__index` metamethod.
+function vim.defaulttable(createfn)
+ createfn = createfn or function(_)
+ return vim.defaulttable()
+ end
return setmetatable({}, {
__index = function(tbl, key)
- rawset(tbl, key, create())
+ rawset(tbl, key, createfn(key))
return rawget(tbl, key)
end,
})
end
+do
+ ---@class vim.Ringbuf<T>
+ ---@field private _items table[]
+ ---@field private _idx_read integer
+ ---@field private _idx_write integer
+ ---@field private _size integer
+ local Ringbuf = {}
+
+ --- Clear all items
+ function Ringbuf.clear(self)
+ self._items = {}
+ self._idx_read = 0
+ self._idx_write = 0
+ end
+
+ --- Adds an item, overriding the oldest item if the buffer is full.
+ ---@generic T
+ ---@param item T
+ function Ringbuf.push(self, item)
+ self._items[self._idx_write] = item
+ self._idx_write = (self._idx_write + 1) % self._size
+ if self._idx_write == self._idx_read then
+ self._idx_read = (self._idx_read + 1) % self._size
+ end
+ end
+
+ --- Removes and returns the first unread item
+ ---@generic T
+ ---@return T?
+ function Ringbuf.pop(self)
+ local idx_read = self._idx_read
+ if idx_read == self._idx_write then
+ return nil
+ end
+ local item = self._items[idx_read]
+ self._items[idx_read] = nil
+ self._idx_read = (idx_read + 1) % self._size
+ return item
+ end
+
+ --- Returns the first unread item without removing it
+ ---@generic T
+ ---@return T?
+ function Ringbuf.peek(self)
+ if self._idx_read == self._idx_write then
+ return nil
+ end
+ return self._items[self._idx_read]
+ end
+
+ --- Create a ring buffer limited to a maximal number of items.
+ --- Once the buffer is full, adding a new entry overrides the oldest entry.
+ ---
+ --- ```lua
+ --- local ringbuf = vim.ringbuf(4)
+ --- ringbuf:push("a")
+ --- ringbuf:push("b")
+ --- ringbuf:push("c")
+ --- ringbuf:push("d")
+ --- ringbuf:push("e") -- overrides "a"
+ --- print(ringbuf:pop()) -- returns "b"
+ --- print(ringbuf:pop()) -- returns "c"
+ ---
+ --- -- Can be used as iterator. Pops remaining items:
+ --- for val in ringbuf do
+ --- print(val)
+ --- end
+ --- ```
+ ---
+ --- Returns a Ringbuf instance with the following methods:
+ ---
+ --- - |Ringbuf:push()|
+ --- - |Ringbuf:pop()|
+ --- - |Ringbuf:peek()|
+ --- - |Ringbuf:clear()|
+ ---
+ ---@param size integer
+ ---@return vim.Ringbuf ringbuf (table)
+ function vim.ringbuf(size)
+ local ringbuf = {
+ _items = {},
+ _size = size + 1,
+ _idx_read = 0,
+ _idx_write = 0,
+ }
+ return setmetatable(ringbuf, {
+ __index = Ringbuf,
+ __call = function(self)
+ return self:pop()
+ end,
+ })
+ end
+end
+
return vim
--- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
new file mode 100644
index 0000000000..32a8ea0b0d
--- /dev/null
+++ b/runtime/lua/vim/snippet.lua
@@ -0,0 +1,591 @@
+local G = require('vim.lsp._snippet_grammar')
+local snippet_group = vim.api.nvim_create_augroup('vim/snippet', {})
+local snippet_ns = vim.api.nvim_create_namespace('vim/snippet')
+
+--- Returns the 0-based cursor position.
+---
+--- @return integer, integer
+local function cursor_pos()
+ local cursor = vim.api.nvim_win_get_cursor(0)
+ return cursor[1] - 1, cursor[2]
+end
+
+--- Resolves variables (like `$name` or `${name:default}`) as follows:
+--- - When a variable is unknown (i.e.: its name is not recognized in any of the cases below), return `nil`.
+--- - When a variable isn't set, return its default (if any) or an empty string.
+---
+--- Note that in some cases, the default is ignored since it's not clear how to distinguish an empty
+--- value from an unset value (e.g.: `TM_CURRENT_LINE`).
+---
+--- @param var string
+--- @param default string
+--- @return string?
+local function resolve_variable(var, default)
+ --- @param str string
+ --- @return string
+ local function expand_or_default(str)
+ local expansion = vim.fn.expand(str) --[[@as string]]
+ return expansion == '' and default or expansion
+ end
+
+ if var == 'TM_SELECTED_TEXT' then
+ -- Snippets are expanded in insert mode only, so there's no selection.
+ return default
+ elseif var == 'TM_CURRENT_LINE' then
+ return vim.api.nvim_get_current_line()
+ elseif var == 'TM_CURRENT_WORD' then
+ return expand_or_default('<cword>')
+ elseif var == 'TM_LINE_INDEX' then
+ return tostring(vim.fn.line('.') - 1)
+ elseif var == 'TM_LINE_NUMBER' then
+ return tostring(vim.fn.line('.'))
+ elseif var == 'TM_FILENAME' then
+ return expand_or_default('%:t')
+ elseif var == 'TM_FILENAME_BASE' then
+ -- Not using '%:t:r' since we want to remove all extensions.
+ local filename_base = expand_or_default('%:t'):gsub('%.[^%.]*$', '')
+ return filename_base
+ elseif var == 'TM_DIRECTORY' then
+ return expand_or_default('%:p:h:t')
+ elseif var == 'TM_FILEPATH' then
+ return expand_or_default('%:p')
+ end
+
+ -- Unknown variable.
+ return nil
+end
+
+--- Transforms the given text into an array of lines (so no line contains `\n`).
+---
+--- @param text string|string[]
+--- @return string[]
+local function text_to_lines(text)
+ text = type(text) == 'string' and { text } or text
+ --- @cast text string[]
+ return vim.split(table.concat(text), '\n', { plain = true })
+end
+
+--- Computes the 0-based position of a tabstop located at the end of `snippet` and spanning
+--- `placeholder` (if given).
+---
+--- @param snippet string[]
+--- @param placeholder string?
+--- @return Range4
+local function compute_tabstop_range(snippet, placeholder)
+ local cursor_row, cursor_col = cursor_pos()
+ local snippet_text = text_to_lines(snippet)
+ local placeholder_text = text_to_lines(placeholder or '')
+ local start_row = cursor_row + #snippet_text - 1
+ local start_col = #(snippet_text[#snippet_text] or '')
+
+ -- Add the cursor's column offset to the first line.
+ if start_row == cursor_row then
+ start_col = start_col + cursor_col
+ end
+
+ local end_row = start_row + #placeholder_text - 1
+ local end_col = (start_row == end_row and start_col or 0)
+ + #(placeholder_text[#placeholder_text] or '')
+
+ return { start_row, start_col, end_row, end_col }
+end
+
+--- Returns the range spanned by the respective extmark.
+---
+--- @param bufnr integer
+--- @param extmark_id integer
+--- @return Range4
+local function get_extmark_range(bufnr, extmark_id)
+ local mark = vim.api.nvim_buf_get_extmark_by_id(bufnr, snippet_ns, extmark_id, { details = true })
+
+ --- @diagnostic disable-next-line: undefined-field
+ return { mark[1], mark[2], mark[3].end_row, mark[3].end_col }
+end
+
+--- @class vim.snippet.Tabstop
+--- @field extmark_id integer
+--- @field bufnr integer
+--- @field index integer
+--- @field choices? string[]
+local Tabstop = {}
+
+--- Creates a new tabstop.
+---
+--- @package
+--- @param index integer
+--- @param bufnr integer
+--- @param range Range4
+--- @param choices? string[]
+--- @return vim.snippet.Tabstop
+function Tabstop.new(index, bufnr, range, choices)
+ local extmark_id = vim.api.nvim_buf_set_extmark(bufnr, snippet_ns, range[1], range[2], {
+ right_gravity = false,
+ end_right_gravity = true,
+ end_line = range[3],
+ end_col = range[4],
+ hl_group = 'SnippetTabstop',
+ })
+
+ local self = setmetatable(
+ { extmark_id = extmark_id, bufnr = bufnr, index = index, choices = choices },
+ { __index = Tabstop }
+ )
+
+ return self
+end
+
+--- Returns the tabstop's range.
+---
+--- @package
+--- @return Range4
+function Tabstop:get_range()
+ return get_extmark_range(self.bufnr, self.extmark_id)
+end
+
+--- Returns the text spanned by the tabstop.
+---
+--- @package
+--- @return string
+function Tabstop:get_text()
+ local range = self:get_range()
+ return table.concat(
+ vim.api.nvim_buf_get_text(self.bufnr, range[1], range[2], range[3], range[4], {}),
+ '\n'
+ )
+end
+
+--- Sets the tabstop's text.
+---
+--- @package
+--- @param text string
+function Tabstop:set_text(text)
+ local range = self:get_range()
+ vim.api.nvim_buf_set_text(self.bufnr, range[1], range[2], range[3], range[4], text_to_lines(text))
+end
+
+--- @class vim.snippet.Session
+--- @field bufnr integer
+--- @field extmark_id integer
+--- @field tabstops table<integer, vim.snippet.Tabstop[]>
+--- @field current_tabstop vim.snippet.Tabstop
+local Session = {}
+
+--- Creates a new snippet session in the current buffer.
+---
+--- @package
+--- @param bufnr integer
+--- @param snippet_extmark integer
+--- @param tabstop_data table<integer, { range: Range4, choices?: string[] }[]>
+--- @return vim.snippet.Session
+function Session.new(bufnr, snippet_extmark, tabstop_data)
+ local self = setmetatable({
+ bufnr = bufnr,
+ extmark_id = snippet_extmark,
+ tabstops = {},
+ current_tabstop = Tabstop.new(0, bufnr, { 0, 0, 0, 0 }),
+ }, { __index = Session })
+
+ -- Create the tabstops.
+ for index, ranges in pairs(tabstop_data) do
+ for _, data in ipairs(ranges) do
+ self.tabstops[index] = self.tabstops[index] or {}
+ table.insert(self.tabstops[index], Tabstop.new(index, self.bufnr, data.range, data.choices))
+ end
+ end
+
+ return self
+end
+
+--- Returns the destination tabstop index when jumping in the given direction.
+---
+--- @package
+--- @param direction vim.snippet.Direction
+--- @return integer?
+function Session:get_dest_index(direction)
+ local tabstop_indexes = vim.tbl_keys(self.tabstops) --- @type integer[]
+ table.sort(tabstop_indexes)
+ for i, index in ipairs(tabstop_indexes) do
+ if index == self.current_tabstop.index then
+ local dest_index = tabstop_indexes[i + direction] --- @type integer?
+ -- When jumping forwards, $0 is the last tabstop.
+ if not dest_index and direction == 1 then
+ dest_index = 0
+ end
+ -- When jumping backwards, make sure we don't think that $0 is the first tabstop.
+ if dest_index == 0 and direction == -1 then
+ dest_index = nil
+ end
+ return dest_index
+ end
+ end
+end
+
+--- @class vim.snippet.Snippet
+--- @field private _session? vim.snippet.Session
+local M = { session = nil }
+
+--- Displays the choices for the given tabstop as completion items.
+---
+--- @param tabstop vim.snippet.Tabstop
+local function display_choices(tabstop)
+ assert(tabstop.choices, 'Tabstop has no choices')
+
+ local start_col = tabstop:get_range()[2] + 1
+ local matches = vim.iter.map(function(choice)
+ return { word = choice }
+ end, tabstop.choices)
+
+ vim.defer_fn(function()
+ vim.fn.complete(start_col, matches)
+ end, 100)
+end
+
+--- Select the given tabstop range.
+---
+--- @param tabstop vim.snippet.Tabstop
+local function select_tabstop(tabstop)
+ --- @param keys string
+ local function feedkeys(keys)
+ keys = vim.api.nvim_replace_termcodes(keys, true, false, true)
+ vim.api.nvim_feedkeys(keys, 'n', true)
+ end
+
+ --- NOTE: We don't use `vim.api.nvim_win_set_cursor` here because it causes the cursor to end
+ --- at the end of the selection instead of the start.
+ ---
+ --- @param row integer
+ --- @param col integer
+ local function move_cursor_to(row, col)
+ local line = vim.fn.getline(row) --[[ @as string ]]
+ col = math.max(vim.fn.strchars(line:sub(1, col)) - 1, 0)
+ feedkeys(string.format('%sG0%s', row, string.rep('<Right>', col)))
+ end
+
+ local range = tabstop:get_range()
+ local mode = vim.fn.mode()
+
+ if vim.fn.pumvisible() ~= 0 then
+ -- Close the choice completion menu if open.
+ vim.fn.complete(vim.fn.col('.'), {})
+ end
+
+ -- Move the cursor to the start of the tabstop.
+ vim.api.nvim_win_set_cursor(0, { range[1] + 1, range[2] })
+
+ -- For empty, choice and the final tabstops, start insert mode at the end of the range.
+ if tabstop.choices or tabstop.index == 0 or (range[1] == range[3] and range[2] == range[4]) then
+ if mode ~= 'i' then
+ if mode == 's' then
+ feedkeys('<Esc>')
+ end
+ vim.cmd.startinsert({ bang = range[4] >= #vim.api.nvim_get_current_line() })
+ end
+ if tabstop.choices then
+ display_choices(tabstop)
+ end
+ else
+ -- Else, select the tabstop's text.
+ if mode ~= 'n' then
+ feedkeys('<Esc>')
+ end
+ move_cursor_to(range[1] + 1, range[2] + 1)
+ feedkeys('v')
+ move_cursor_to(range[3] + 1, range[4])
+ feedkeys('o<c-g>')
+ end
+end
+
+--- Sets up the necessary autocommands for snippet expansion.
+---
+--- @param bufnr integer
+local function setup_autocmds(bufnr)
+ vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
+ group = snippet_group,
+ desc = 'Update snippet state when the cursor moves',
+ buffer = bufnr,
+ callback = function()
+ -- Just update the tabstop in insert and select modes.
+ if not vim.fn.mode():match('^[isS]') then
+ return
+ end
+
+ local cursor_row, cursor_col = cursor_pos()
+
+ -- The cursor left the snippet region.
+ local snippet_range = get_extmark_range(bufnr, M._session.extmark_id)
+ if
+ cursor_row < snippet_range[1]
+ or (cursor_row == snippet_range[1] and cursor_col < snippet_range[2])
+ or cursor_row > snippet_range[3]
+ or (cursor_row == snippet_range[3] and cursor_col > snippet_range[4])
+ then
+ M.exit()
+ return true
+ end
+
+ for tabstop_index, tabstops in pairs(M._session.tabstops) do
+ for _, tabstop in ipairs(tabstops) do
+ local range = tabstop:get_range()
+ if
+ (cursor_row > range[1] or (cursor_row == range[1] and cursor_col >= range[2]))
+ and (cursor_row < range[3] or (cursor_row == range[3] and cursor_col <= range[4]))
+ then
+ if tabstop_index ~= 0 then
+ return
+ end
+ end
+ end
+ end
+
+ -- The cursor is either not on a tabstop or we reached the end, so exit the session.
+ M.exit()
+ return true
+ end,
+ })
+
+ vim.api.nvim_create_autocmd({ 'TextChanged', 'TextChangedI' }, {
+ group = snippet_group,
+ desc = 'Update active tabstops when buffer text changes',
+ buffer = bufnr,
+ callback = function()
+ -- Check that the snippet hasn't been deleted.
+ local snippet_range = get_extmark_range(M._session.bufnr, M._session.extmark_id)
+ if
+ (snippet_range[1] == snippet_range[3] and snippet_range[2] == snippet_range[4])
+ or snippet_range[3] + 1 > vim.fn.line('$')
+ then
+ M.exit()
+ end
+
+ if not M.active() then
+ return true
+ end
+
+ -- Sync the tabstops in the current group.
+ local current_tabstop = M._session.current_tabstop
+ local current_text = current_tabstop:get_text()
+ for _, tabstop in ipairs(M._session.tabstops[current_tabstop.index]) do
+ if tabstop.extmark_id ~= current_tabstop.extmark_id then
+ tabstop:set_text(current_text)
+ end
+ end
+ end,
+ })
+end
+
+--- Expands the given snippet text.
+--- Refer to https://microsoft.github.io/language-server-protocol/specification/#snippet_syntax
+--- for the specification of valid input.
+---
+--- Tabstops are highlighted with hl-SnippetTabstop.
+---
+--- @param input string
+function M.expand(input)
+ local snippet = G.parse(input)
+ local snippet_text = {}
+ local base_indent = vim.api.nvim_get_current_line():match('^%s*') or ''
+
+ -- Get the placeholders we should use for each tabstop index.
+ --- @type table<integer, string>
+ local placeholders = {}
+ for _, child in ipairs(snippet.data.children) do
+ local type, data = child.type, child.data
+ if type == G.NodeType.Placeholder then
+ --- @cast data vim.snippet.PlaceholderData
+ local tabstop, value = data.tabstop, tostring(data.value)
+ if placeholders[tabstop] and placeholders[tabstop] ~= value then
+ error('Snippet has multiple placeholders for tabstop $' .. tabstop)
+ end
+ placeholders[tabstop] = value
+ end
+ end
+
+ -- Keep track of tabstop nodes during expansion.
+ --- @type table<integer, { range: Range4, choices?: string[] }[]>
+ local tabstop_data = {}
+
+ --- @param index integer
+ --- @param placeholder? string
+ --- @param choices? string[]
+ local function add_tabstop(index, placeholder, choices)
+ tabstop_data[index] = tabstop_data[index] or {}
+ local range = compute_tabstop_range(snippet_text, placeholder)
+ table.insert(tabstop_data[index], { range = range, choices = choices })
+ end
+
+ --- Appends the given text to the snippet, taking care of indentation.
+ ---
+ --- @param text string|string[]
+ local function append_to_snippet(text)
+ local snippet_lines = text_to_lines(snippet_text)
+ -- Get the base indentation based on the current line and the last line of the snippet.
+ if #snippet_lines > 0 then
+ base_indent = base_indent .. (snippet_lines[#snippet_lines]:match('(^%s*)%S') or '') --- @type string
+ end
+
+ local lines = vim.iter.map(function(i, line)
+ -- Replace tabs by spaces.
+ if vim.o.expandtab then
+ line = line:gsub('\t', (' '):rep(vim.fn.shiftwidth())) --- @type string
+ end
+ -- Add the base indentation.
+ if i > 1 then
+ line = base_indent .. line
+ end
+ return line
+ end, ipairs(text_to_lines(text)))
+
+ table.insert(snippet_text, table.concat(lines, '\n'))
+ end
+
+ for _, child in ipairs(snippet.data.children) do
+ local type, data = child.type, child.data
+ if type == G.NodeType.Tabstop then
+ --- @cast data vim.snippet.TabstopData
+ local placeholder = placeholders[data.tabstop]
+ add_tabstop(data.tabstop, placeholder)
+ if placeholder then
+ append_to_snippet(placeholder)
+ end
+ elseif type == G.NodeType.Placeholder then
+ --- @cast data vim.snippet.PlaceholderData
+ local value = placeholders[data.tabstop]
+ add_tabstop(data.tabstop, value)
+ append_to_snippet(value)
+ elseif type == G.NodeType.Choice then
+ --- @cast data vim.snippet.ChoiceData
+ add_tabstop(data.tabstop, nil, data.values)
+ elseif type == G.NodeType.Variable then
+ --- @cast data vim.snippet.VariableData
+ -- Try to get the variable's value.
+ local value = resolve_variable(data.name, data.default and tostring(data.default) or '')
+ if not value then
+ -- Unknown variable, make this a tabstop and use the variable name as a placeholder.
+ value = data.name
+ local tabstop_indexes = vim.tbl_keys(tabstop_data)
+ local index = math.max(unpack((#tabstop_indexes == 0 and { 0 }) or tabstop_indexes)) + 1
+ add_tabstop(index, value)
+ end
+ append_to_snippet(value)
+ elseif type == G.NodeType.Text then
+ --- @cast data vim.snippet.TextData
+ append_to_snippet(data.text)
+ end
+ end
+
+ -- $0, which defaults to the end of the snippet, defines the final cursor position.
+ -- Make sure the snippet has exactly one of these.
+ if vim.tbl_contains(vim.tbl_keys(tabstop_data), 0) then
+ assert(#tabstop_data[0] == 1, 'Snippet has multiple $0 tabstops')
+ else
+ add_tabstop(0)
+ end
+
+ snippet_text = text_to_lines(snippet_text)
+
+ -- Insert the snippet text.
+ local bufnr = vim.api.nvim_get_current_buf()
+ local cursor_row, cursor_col = cursor_pos()
+ vim.api.nvim_buf_set_text(bufnr, cursor_row, cursor_col, cursor_row, cursor_col, snippet_text)
+
+ -- Create the session.
+ local snippet_extmark = vim.api.nvim_buf_set_extmark(bufnr, snippet_ns, cursor_row, cursor_col, {
+ end_line = cursor_row + #snippet_text - 1,
+ end_col = #snippet_text > 1 and #snippet_text[#snippet_text] or cursor_col + #snippet_text[1],
+ right_gravity = false,
+ end_right_gravity = true,
+ })
+ M._session = Session.new(bufnr, snippet_extmark, tabstop_data)
+
+ -- Jump to the first tabstop.
+ M.jump(1)
+end
+
+--- @alias vim.snippet.Direction -1 | 1
+
+--- Returns `true` if there is an active snippet which can be jumped in the given direction.
+--- You can use this function to navigate a snippet as follows:
+---
+--- ```lua
+--- vim.keymap.set({ 'i', 's' }, '<Tab>', function()
+--- if vim.snippet.jumpable(1) then
+--- return '<cmd>lua vim.snippet.jump(1)<cr>'
+--- else
+--- return '<Tab>'
+--- end
+--- end, { expr = true })
+--- ```
+---
+--- @param direction (vim.snippet.Direction) Navigation direction. -1 for previous, 1 for next.
+--- @return boolean
+function M.jumpable(direction)
+ if not M.active() then
+ return false
+ end
+
+ return M._session:get_dest_index(direction) ~= nil
+end
+
+--- Jumps within the active snippet in the given direction.
+--- If the jump isn't possible, the function call does nothing.
+---
+--- You can use this function to navigate a snippet as follows:
+---
+--- ```lua
+--- vim.keymap.set({ 'i', 's' }, '<Tab>', function()
+--- if vim.snippet.jumpable(1) then
+--- return '<cmd>lua vim.snippet.jump(1)<cr>'
+--- else
+--- return '<Tab>'
+--- end
+--- end, { expr = true })
+--- ```
+---
+--- @param direction (vim.snippet.Direction) Navigation direction. -1 for previous, 1 for next.
+function M.jump(direction)
+ -- Get the tabstop index to jump to.
+ local dest_index = M._session and M._session:get_dest_index(direction)
+ if not dest_index then
+ return
+ end
+
+ -- Find the tabstop with the lowest range.
+ local tabstops = M._session.tabstops[dest_index]
+ local dest = tabstops[1]
+ for _, tabstop in ipairs(tabstops) do
+ local dest_range, range = dest:get_range(), tabstop:get_range()
+ if (range[1] < dest_range[1]) or (range[1] == dest_range[1] and range[2] < dest_range[2]) then
+ dest = tabstop
+ end
+ end
+
+ -- Clear the autocommands so that we can move the cursor freely while selecting the tabstop.
+ vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
+
+ M._session.current_tabstop = dest
+ select_tabstop(dest)
+
+ -- Restore the autocommands.
+ setup_autocmds(M._session.bufnr)
+end
+
+--- Returns `true` if there's an active snippet in the current buffer.
+---
+--- @return boolean
+function M.active()
+ return M._session ~= nil and M._session.bufnr == vim.api.nvim_get_current_buf()
+end
+
+--- Exits the current snippet.
+function M.exit()
+ if not M.active() then
+ return
+ end
+
+ vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
+ vim.api.nvim_buf_clear_namespace(M._session.bufnr, snippet_ns, 0, -1)
+
+ M._session = nil
+end
+
+return M
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
new file mode 100644
index 0000000000..862cc52149
--- /dev/null
+++ b/runtime/lua/vim/termcap.lua
@@ -0,0 +1,62 @@
+local M = {}
+
+--- Query the host terminal emulator for terminfo capabilities.
+---
+--- This function sends the XTGETTCAP DCS sequence to the host terminal emulator asking the terminal
+--- to send us its terminal capabilities. These are strings that are normally taken from a terminfo
+--- file, however an up to date terminfo database is not always available (particularly on remote
+--- machines), and many terminals continue to misidentify themselves or do not provide their own
+--- terminfo file, making the terminfo database unreliable.
+---
+--- Querying the terminal guarantees that we get a truthful answer, but only if the host terminal
+--- emulator supports the XTGETTCAP sequence.
+---
+--- @param caps string|table A terminal capability or list of capabilities to query
+--- @param cb function(cap:string, seq:string) Function to call when a response is received
+function M.query(caps, cb)
+ vim.validate({
+ caps = { caps, { 'string', 'table' } },
+ cb = { cb, 'f' },
+ })
+
+ if type(caps) ~= 'table' then
+ caps = { caps }
+ end
+
+ local count = #caps
+
+ vim.api.nvim_create_autocmd('TermResponse', {
+ callback = function(args)
+ local resp = args.data ---@type string
+ local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$')
+ if k and v then
+ local cap = vim.text.hexdecode(k)
+ local seq =
+ vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char)
+
+ cb(cap, seq)
+
+ count = count - 1
+ if count == 0 then
+ return true
+ end
+ end
+ end,
+ })
+
+ local encoded = {} ---@type string[]
+ for i = 1, #caps do
+ encoded[i] = vim.text.hexencode(caps[i])
+ end
+
+ local query = string.format('\027P+q%s\027\\', table.concat(encoded, ';'))
+
+ -- If running in tmux, wrap with the passthrough sequence
+ if os.getenv('TMUX') then
+ query = string.format('\027Ptmux;%s\027\\', query:gsub('\027', '\027\027'))
+ end
+
+ io.stdout:write(query)
+end
+
+return M
diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua
new file mode 100644
index 0000000000..cfb0f9b821
--- /dev/null
+++ b/runtime/lua/vim/text.lua
@@ -0,0 +1,32 @@
+--- Text processing functions.
+
+local M = {}
+
+--- Hex encode a string.
+---
+--- @param str string String to encode
+--- @return string Hex encoded string
+function M.hexencode(str)
+ local bytes = { str:byte(1, #str) }
+ local enc = {} ---@type string[]
+ for i = 1, #bytes do
+ enc[i] = string.format('%02X', bytes[i])
+ end
+ return table.concat(enc)
+end
+
+--- Hex decode a string.
+---
+--- @param enc string String to decode
+--- @return string Decoded string
+function M.hexdecode(enc)
+ assert(#enc % 2 == 0, 'string must have an even number of hex characters')
+ local str = {} ---@type string[]
+ for i = 1, #enc, 2 do
+ local n = assert(tonumber(enc:sub(i, i + 1), 16))
+ str[#str + 1] = string.char(n)
+ end
+ return table.concat(str)
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 582922ecb6..e7a66c00b2 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -1,17 +1,17 @@
-local a = vim.api
-local query = require('vim.treesitter.query')
-local language = require('vim.treesitter.language')
+local api = vim.api
local LanguageTree = require('vim.treesitter.languagetree')
+local Range = require('vim.treesitter._range')
+---@type table<integer,LanguageTree>
local parsers = setmetatable({}, { __mode = 'v' })
-local M = vim.tbl_extend('error', query, language)
-
-M.language_version = vim._ts_get_language_version()
-M.minimum_language_version = vim._ts_get_minimum_language_version()
-
-setmetatable(M, {
+---@class TreesitterModule
+---@field highlighter TSHighlighter
+---@field query TSQueryModule
+---@field language TSLanguageModule
+local M = setmetatable({}, {
__index = function(t, k)
+ ---@diagnostic disable:no-unknown
if k == 'highlighter' then
t[k] = require('vim.treesitter.highlighter')
return t[k]
@@ -22,34 +22,51 @@ setmetatable(M, {
t[k] = require('vim.treesitter.query')
return t[k]
end
+
+ local query = require('vim.treesitter.query')
+ if query[k] then
+ vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.query.' .. k .. '()', '0.10')
+ t[k] = query[k]
+ return t[k]
+ end
+
+ local language = require('vim.treesitter.language')
+ if language[k] then
+ vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.language.' .. k .. '()', '0.10')
+ t[k] = language[k]
+ return t[k]
+ end
end,
})
+--- @nodoc
+M.language_version = vim._ts_get_language_version()
+
+--- @nodoc
+M.minimum_language_version = vim._ts_get_minimum_language_version()
+
--- Creates a new parser
---
--- It is not recommended to use this; use |get_parser()| instead.
---
----@param bufnr string Buffer the parser will be tied to (0 for current buffer)
+---@param bufnr integer Buffer the parser will be tied to (0 for current buffer)
---@param lang string Language of the parser
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree |LanguageTree| object to use for parsing
+---@return LanguageTree object to use for parsing
function M._create_parser(bufnr, lang, opts)
- language.require_language(lang)
if bufnr == 0 then
- bufnr = a.nvim_get_current_buf()
+ bufnr = vim.api.nvim_get_current_buf()
end
vim.fn.bufload(bufnr)
local self = LanguageTree.new(bufnr, lang, opts)
- ---@private
local function bytes_cb(_, ...)
self:_on_bytes(...)
end
- ---@private
local function detach_cb(_, ...)
if parsers[bufnr] == self then
parsers[bufnr] = nil
@@ -57,13 +74,14 @@ function M._create_parser(bufnr, lang, opts)
self:_on_detach(...)
end
- ---@private
- local function reload_cb(_, ...)
- self:_on_reload(...)
+ local function reload_cb(_)
+ self:_on_reload()
end
- a.nvim_buf_attach(
- self:source(),
+ local source = self:source() --[[@as integer]]
+
+ api.nvim_buf_attach(
+ source,
false,
{ on_bytes = bytes_cb, on_detach = detach_cb, on_reload = reload_cb, preview = true }
)
@@ -73,26 +91,43 @@ function M._create_parser(bufnr, lang, opts)
return self
end
---- Returns the parser for a specific buffer and filetype and attaches it to the buffer
+local function valid_lang(lang)
+ return lang and lang ~= ''
+end
+
+--- Returns the parser for a specific buffer and attaches it to the buffer
---
--- If needed, this will create the parser.
---
----@param bufnr (number|nil) Buffer the parser should be tied to (default: current buffer)
+---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
---@param lang (string|nil) Filetype of this parser (default: buffer filetype)
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree |LanguageTree| object to use for parsing
+---@return LanguageTree object to use for parsing
function M.get_parser(bufnr, lang, opts)
opts = opts or {}
if bufnr == nil or bufnr == 0 then
- bufnr = a.nvim_get_current_buf()
+ bufnr = api.nvim_get_current_buf()
end
- if lang == nil then
- lang = a.nvim_buf_get_option(bufnr, 'filetype')
+
+ if not valid_lang(lang) then
+ lang = M.language.get_lang(vim.bo[bufnr].filetype) or vim.bo[bufnr].filetype
end
- if parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
+ if not valid_lang(lang) then
+ if not parsers[bufnr] then
+ error(
+ string.format(
+ 'There is no parser available for buffer %d and one could not be'
+ .. ' created because lang could not be determined. Either pass lang'
+ .. ' or set the buffer filetype',
+ bufnr
+ )
+ )
+ end
+ elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then
+ assert(lang, 'lang should be valid')
parsers[bufnr] = M._create_parser(bufnr, lang, opts)
end
@@ -107,21 +142,20 @@ end
---@param lang string Language of this string
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree |LanguageTree| object to use for parsing
+---@return LanguageTree object to use for parsing
function M.get_string_parser(str, lang, opts)
vim.validate({
str = { str, 'string' },
lang = { lang, 'string' },
})
- language.require_language(lang)
return LanguageTree.new(str, lang, opts)
end
--- Determines whether a node is the ancestor of another
---
----@param dest userdata Possible ancestor |tsnode|
----@param source userdata Possible descendant |tsnode|
+---@param dest TSNode Possible ancestor
+---@param source TSNode Possible descendant
---
---@return boolean True if {dest} is an ancestor of {source}
function M.is_ancestor(dest, source)
@@ -129,7 +163,7 @@ function M.is_ancestor(dest, source)
return false
end
- local current = source
+ local current = source ---@type TSNode?
while current ~= nil do
if current == dest then
return true
@@ -143,9 +177,12 @@ end
--- Returns the node's range or an unpacked range table
---
----@param node_or_range (userdata|table) |tsnode| or table of positions
+---@param node_or_range (TSNode | table) Node or table of positions
---
----@return table `{ start_row, start_col, end_row, end_col }`
+---@return integer start_row
+---@return integer start_col
+---@return integer end_row
+---@return integer end_col
function M.get_node_range(node_or_range)
if type(node_or_range) == 'table' then
return unpack(node_or_range)
@@ -154,42 +191,84 @@ function M.get_node_range(node_or_range)
end
end
+---Get the range of a |TSNode|. Can also supply {source} and {metadata}
+---to get the range with directives applied.
+---@param node TSNode
+---@param source integer|string|nil Buffer or string from which the {node} is extracted
+---@param metadata TSMetadata|nil
+---@return Range6
+function M.get_range(node, source, metadata)
+ if metadata and metadata.range then
+ assert(source)
+ return Range.add_bytes(source, metadata.range)
+ end
+ return { node:range(true) }
+end
+
+---@param buf integer
+---@param range Range
+---@returns string
+local function buf_range_get_text(buf, range)
+ local start_row, start_col, end_row, end_col = Range.unpack4(range)
+ if end_col == 0 then
+ if start_row == end_row then
+ start_col = -1
+ start_row = start_row - 1
+ end
+ end_col = -1
+ end_row = end_row - 1
+ end
+ local lines = api.nvim_buf_get_text(buf, start_row, start_col, end_row, end_col, {})
+ return table.concat(lines, '\n')
+end
+
+--- Gets the text corresponding to a given node
+---
+---@param node TSNode
+---@param source (integer|string) Buffer or string from which the {node} is extracted
+---@param opts (table|nil) Optional parameters.
+--- - metadata (table) Metadata of a specific capture. This would be
+--- set to `metadata[capture_id]` when using |vim.treesitter.query.add_directive()|.
+---@return string
+function M.get_node_text(node, source, opts)
+ opts = opts or {}
+ local metadata = opts.metadata or {}
+
+ if metadata.text then
+ return metadata.text
+ elseif type(source) == 'number' then
+ local range = vim.treesitter.get_range(node, source, metadata)
+ return buf_range_get_text(source, range)
+ end
+
+ ---@cast source string
+ return source:sub(select(3, node:start()) + 1, select(3, node:end_()))
+end
+
--- Determines whether (line, col) position is in node range
---
----@param node userdata |tsnode| defining the range
----@param line number Line (0-based)
----@param col number Column (0-based)
+---@param node TSNode defining the range
+---@param line integer Line (0-based)
+---@param col integer Column (0-based)
---
---@return boolean True if the position is in node range
function M.is_in_node_range(node, line, col)
- local start_line, start_col, end_line, end_col = M.get_node_range(node)
- if line >= start_line and line <= end_line then
- if line == start_line and line == end_line then
- return col >= start_col and col < end_col
- elseif line == start_line then
- return col >= start_col
- elseif line == end_line then
- return col < end_col
- else
- return true
- end
- else
- return false
- end
+ return M.node_contains(node, { line, col, line, col + 1 })
end
--- Determines if a node contains a range
---
----@param node userdata |tsnode|
+---@param node TSNode
---@param range table
---
---@return boolean True if the {node} contains the {range}
function M.node_contains(node, range)
- local start_row, start_col, end_row, end_col = node:range()
- local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
- local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
-
- return start_fits and end_fits
+ vim.validate({
+ -- allow a table so nodes can be mocked
+ node = { node, { 'userdata', 'table' } },
+ range = { range, Range.validate, 'integer list with 4 or 6 elements' },
+ })
+ return Range.contains({ node:range() }, range)
end
--- Returns a list of highlight captures at the given position
@@ -197,14 +276,14 @@ end
--- Each capture is represented by a table containing the capture name as a string as
--- well as a table of metadata (`priority`, `conceal`, ...; empty if none are defined).
---
----@param bufnr number Buffer number (0 for current buffer)
----@param row number Position row
----@param col number Position column
+---@param bufnr integer Buffer number (0 for current buffer)
+---@param row integer Position row
+---@param col integer Position column
---
----@return table[] List of captures `{ capture = "capture name", metadata = { ... } }`
+---@return table[] List of captures `{ capture = "name", metadata = { ... } }`
function M.get_captures_at_pos(bufnr, row, col)
if bufnr == 0 then
- bufnr = a.nvim_get_current_buf()
+ bufnr = api.nvim_get_current_buf()
end
local buf_highlighter = M.highlighter.active[bufnr]
@@ -244,19 +323,19 @@ function M.get_captures_at_pos(bufnr, row, col)
end
end
end
- end, true)
+ end)
return matches
end
--- Returns a list of highlight capture names under the cursor
---
----@param winnr (number|nil) Window handle or 0 for current window (default)
+---@param winnr (integer|nil) Window handle or 0 for current window (default)
---
---@return string[] List of capture names
function M.get_captures_at_cursor(winnr)
winnr = winnr or 0
- local bufnr = a.nvim_win_get_buf(winnr)
- local cursor = a.nvim_win_get_cursor(winnr)
+ local bufnr = api.nvim_win_get_buf(winnr)
+ local cursor = api.nvim_win_get_cursor(winnr)
local data = M.get_captures_at_pos(bufnr, cursor[1] - 1, cursor[2])
@@ -271,20 +350,76 @@ end
--- Returns the smallest named node at the given position
---
----@param bufnr number Buffer number (0 for current buffer)
----@param row number Position row
----@param col number Position column
+--- NOTE: Calling this on an unparsed tree can yield an invalid node.
+--- If the tree is not known to be parsed by, e.g., an active highlighter,
+--- parse the tree first via
+---
+--- ```lua
+--- vim.treesitter.get_parser(bufnr):parse(range)
+--- ```
+---
+---@param opts table|nil Optional keyword arguments:
+--- - bufnr integer|nil Buffer number (nil or 0 for current buffer)
+--- - pos table|nil 0-indexed (row, col) tuple. Defaults to cursor position in the
+--- current window. Required if {bufnr} is not the current buffer
+--- - ignore_injections boolean Ignore injected languages (default true)
+---
+---@return TSNode | nil Node at the given position
+function M.get_node(opts)
+ opts = opts or {}
+
+ local bufnr = opts.bufnr
+
+ if not bufnr or bufnr == 0 then
+ bufnr = api.nvim_get_current_buf()
+ end
+
+ local row, col
+ if opts.pos then
+ assert(#opts.pos == 2, 'Position must be a (row, col) tuple')
+ row, col = opts.pos[1], opts.pos[2]
+ else
+ assert(
+ bufnr == api.nvim_get_current_buf(),
+ 'Position must be explicitly provided when not using the current buffer'
+ )
+ local pos = api.nvim_win_get_cursor(0)
+ -- Subtract one to account for 1-based row indexing in nvim_win_get_cursor
+ row, col = pos[1] - 1, pos[2]
+ end
+
+ assert(row >= 0 and col >= 0, 'Invalid position: row and col must be non-negative')
+
+ local ts_range = { row, col, row, col }
+
+ local root_lang_tree = M.get_parser(bufnr)
+ if not root_lang_tree then
+ return
+ end
+
+ return root_lang_tree:named_node_for_range(ts_range, opts)
+end
+
+--- Returns the smallest named node at the given position
+---
+---@param bufnr integer Buffer number (0 for current buffer)
+---@param row integer Position row
+---@param col integer Position column
---@param opts table Optional keyword arguments:
--- - lang string|nil Parser language
--- - ignore_injections boolean Ignore injected languages (default true)
---
----@return userdata|nil |tsnode| under the cursor
+---@return TSNode | nil Node at the given position
+---@deprecated
function M.get_node_at_pos(bufnr, row, col, opts)
+ vim.deprecate('vim.treesitter.get_node_at_pos()', 'vim.treesitter.get_node()', '0.10')
if bufnr == 0 then
- bufnr = a.nvim_get_current_buf()
+ bufnr = api.nvim_get_current_buf()
end
local ts_range = { row, col, row, col }
+ opts = opts or {}
+
local root_lang_tree = M.get_parser(bufnr, opts.lang)
if not root_lang_tree then
return
@@ -295,15 +430,16 @@ end
--- Returns the smallest named node under the cursor
---
----@param winnr (number|nil) Window handle or 0 for current window (default)
+---@param winnr (integer|nil) Window handle or 0 for current window (default)
---
---@return string Name of node under the cursor
+---@deprecated
function M.get_node_at_cursor(winnr)
+ vim.deprecate('vim.treesitter.get_node_at_cursor()', 'vim.treesitter.get_node():type()', '0.10')
winnr = winnr or 0
- local bufnr = a.nvim_win_get_buf(winnr)
- local cursor = a.nvim_win_get_cursor(winnr)
+ local bufnr = api.nvim_win_get_buf(winnr)
- return M.get_node_at_pos(bufnr, cursor[1] - 1, cursor[2], { ignore_injections = false }):type()
+ return M.get_node({ bufnr = bufnr, ignore_injections = false }):type()
end
--- Starts treesitter highlighting for a buffer
@@ -314,28 +450,29 @@ end
--- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`.
---
--- Example:
---- <pre>lua
+---
+--- ```lua
--- vim.api.nvim_create_autocmd( 'FileType', { pattern = 'tex',
--- callback = function(args)
--- vim.treesitter.start(args.buf, 'latex')
--- vim.bo[args.buf].syntax = 'on' -- only if additional legacy syntax is needed
--- end
--- })
---- </pre>
+--- ```
---
----@param bufnr (number|nil) Buffer to be highlighted (default: current buffer)
+---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer)
---@param lang (string|nil) Language of the parser (default: buffer filetype)
function M.start(bufnr, lang)
- bufnr = bufnr or a.nvim_get_current_buf()
+ bufnr = bufnr or api.nvim_get_current_buf()
local parser = M.get_parser(bufnr, lang)
M.highlighter.new(parser)
end
--- Stops treesitter highlighting for a buffer
---
----@param bufnr (number|nil) Buffer to stop highlighting (default: current buffer)
+---@param bufnr (integer|nil) Buffer to stop highlighting (default: current buffer)
function M.stop(bufnr)
- bufnr = bufnr or a.nvim_get_current_buf()
+ bufnr = (bufnr and bufnr ~= 0) and bufnr or api.nvim_get_current_buf()
if M.highlighter.active[bufnr] then
M.highlighter.active[bufnr]:destroy()
@@ -345,198 +482,50 @@ end
--- Open a window that displays a textual representation of the nodes in the language tree.
---
--- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the
---- display of the source language of each node, and press <Enter> to jump to the node under the
---- cursor in the source buffer.
+--- display of the source language of each node, "o" to toggle the query editor, and press
+--- <Enter> to jump to the node under the cursor in the source buffer.
+---
+--- Can also be shown with `:InspectTree`. *:InspectTree*
---
---@param opts table|nil Optional options table with the following possible keys:
--- - lang (string|nil): The language of the source buffer. If omitted, the
--- filetype of the source buffer is used.
---- - bufnr (number|nil): Buffer to draw the tree into. If omitted, a new
+--- - bufnr (integer|nil): Buffer to draw the tree into. If omitted, a new
--- buffer is created.
---- - winid (number|nil): Window id to display the tree buffer in. If omitted,
+--- - winid (integer|nil): Window id to display the tree buffer in. If omitted,
--- a new window is created with {command}.
--- - command (string|nil): Vimscript command to create the window. Default
---- value is "topleft 60vnew". Only used when {winid} is nil.
---- - title (string|fun(bufnr:number):string|nil): Title of the window. If a
+--- value is "60vnew". Only used when {winid} is nil.
+--- - title (string|fun(bufnr:integer):string|nil): Title of the window. If a
--- function, it accepts the buffer number of the source buffer as its only
--- argument and should return a string.
-function M.show_tree(opts)
- vim.validate({
- opts = { opts, 't', true },
- })
-
- opts = opts or {}
-
- local Playground = require('vim.treesitter.playground')
- local buf = a.nvim_get_current_buf()
- local win = a.nvim_get_current_win()
- local pg = assert(Playground:new(buf, opts.lang))
-
- -- Close any existing playground window
- if vim.b[buf].playground then
- local w = vim.b[buf].playground
- if a.nvim_win_is_valid(w) then
- a.nvim_win_close(w, true)
- end
- end
-
- local w = opts.winid
- if not w then
- vim.cmd(opts.command or 'topleft 60vnew')
- w = a.nvim_get_current_win()
- end
-
- local b = opts.bufnr
- if b then
- a.nvim_win_set_buf(w, b)
- else
- b = a.nvim_win_get_buf(w)
- end
-
- vim.b[buf].playground = w
-
- vim.wo[w].scrolloff = 5
- vim.wo[w].wrap = false
- vim.bo[b].buflisted = false
- vim.bo[b].buftype = 'nofile'
- vim.bo[b].bufhidden = 'wipe'
-
- local title = opts.title
- if not title then
- local bufname = a.nvim_buf_get_name(buf)
- title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.'))
- elseif type(title) == 'function' then
- title = title(buf)
- end
-
- assert(type(title) == 'string', 'Window title must be a string')
- a.nvim_buf_set_name(b, title)
-
- pg:draw(b)
-
- vim.fn.matchadd('Comment', '\\[[0-9:-]\\+\\]')
- vim.fn.matchadd('String', '".*"')
-
- a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- a.nvim_buf_set_keymap(b, 'n', '<CR>', '', {
- desc = 'Jump to the node under the cursor in the source buffer',
- callback = function()
- local row = a.nvim_win_get_cursor(w)[1]
- local pos = pg:get(row)
- a.nvim_set_current_win(win)
- a.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col })
- end,
- })
- a.nvim_buf_set_keymap(b, 'n', 'a', '', {
- desc = 'Toggle anonymous nodes',
- callback = function()
- pg.opts.anon = not pg.opts.anon
- pg:draw(b)
- end,
- })
- a.nvim_buf_set_keymap(b, 'n', 'I', '', {
- desc = 'Toggle language display',
- callback = function()
- pg.opts.lang = not pg.opts.lang
- pg:draw(b)
- end,
- })
-
- local group = a.nvim_create_augroup('treesitter/playground', {})
-
- a.nvim_create_autocmd('CursorMoved', {
- group = group,
- buffer = b,
- callback = function()
- a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- local row = a.nvim_win_get_cursor(w)[1]
- local pos = pg:get(row)
- a.nvim_buf_set_extmark(buf, pg.ns, pos.lnum, pos.col, {
- end_row = pos.end_lnum,
- end_col = math.max(0, pos.end_col),
- hl_group = 'Visual',
- })
- end,
- })
-
- a.nvim_create_autocmd('CursorMoved', {
- group = group,
- buffer = buf,
- callback = function()
- if not a.nvim_buf_is_loaded(b) then
- return true
- end
-
- a.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
-
- local cursor = a.nvim_win_get_cursor(win)
- local cursor_node = M.get_node_at_pos(buf, cursor[1] - 1, cursor[2], {
- lang = opts.lang,
- ignore_injections = false,
- })
- if not cursor_node then
- return
- end
-
- local cursor_node_id = cursor_node:id()
- for i, v in pg:iter() do
- if v.id == cursor_node_id then
- local start = v.depth
- local end_col = start + #v.text
- a.nvim_buf_set_extmark(b, pg.ns, i - 1, start, {
- end_col = end_col,
- hl_group = 'Visual',
- })
- a.nvim_win_set_cursor(w, { i, 0 })
- break
- end
- end
- end,
- })
-
- a.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
- group = group,
- buffer = buf,
- callback = function()
- if not a.nvim_buf_is_loaded(b) then
- return true
- end
-
- pg = assert(Playground:new(buf, opts.lang))
- pg:draw(b)
- end,
- })
-
- a.nvim_create_autocmd('BufLeave', {
- group = group,
- buffer = b,
- callback = function()
- a.nvim_buf_clear_namespace(buf, pg.ns, 0, -1)
- end,
- })
-
- a.nvim_create_autocmd('BufLeave', {
- group = group,
- buffer = buf,
- callback = function()
- if not a.nvim_buf_is_loaded(b) then
- return true
- end
+function M.inspect_tree(opts)
+ ---@diagnostic disable-next-line: invisible
+ require('vim.treesitter.dev').inspect_tree(opts)
+end
- a.nvim_buf_clear_namespace(b, pg.ns, 0, -1)
- end,
- })
+--- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr':
+---
+--- ```lua
+--- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()'
+--- ```
+---
+---@param lnum integer|nil Line number to calculate fold level for
+---@return string
+function M.foldexpr(lnum)
+ return require('vim.treesitter._fold').foldexpr(lnum)
+end
- a.nvim_create_autocmd('BufHidden', {
- group = group,
- buffer = buf,
- once = true,
- callback = function()
- if a.nvim_win_is_valid(w) then
- a.nvim_win_close(w, true)
- end
- end,
- })
+--- Returns the highlighted content of the first line of the fold or falls back to |foldtext()|
+--- if no treesitter parser is found. Can be set directly to 'foldtext':
+---
+--- ```lua
+--- vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()'
+--- ```
+---
+---@return { [1]: string, [2]: string[] }[] | string
+function M.foldtext()
+ return require('vim.treesitter._fold').foldtext()
end
return M
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
new file mode 100644
index 0000000000..5c1cc06908
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -0,0 +1,456 @@
+local ts = vim.treesitter
+
+local Range = require('vim.treesitter._range')
+
+local api = vim.api
+
+---@class TS.FoldInfo
+---@field levels table<integer,string>
+---@field levels0 table<integer,integer>
+---@field private start_counts table<integer,integer>
+---@field private stop_counts table<integer,integer>
+local FoldInfo = {}
+FoldInfo.__index = FoldInfo
+
+---@private
+function FoldInfo.new()
+ return setmetatable({
+ start_counts = {},
+ stop_counts = {},
+ levels0 = {},
+ levels = {},
+ }, FoldInfo)
+end
+
+---@package
+---@param srow integer
+---@param erow integer
+function FoldInfo:invalidate_range(srow, erow)
+ for i = srow, erow do
+ self.start_counts[i + 1] = nil
+ self.stop_counts[i + 1] = nil
+ self.levels0[i + 1] = nil
+ self.levels[i + 1] = nil
+ end
+end
+
+--- Efficiently remove items from middle of a list a list.
+---
+--- Calling table.remove() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+local function list_remove(t, first, last)
+ local n = #t
+ for i = 0, n - first do
+ t[first + i] = t[last + 1 + i]
+ t[last + 1 + i] = nil
+ end
+end
+
+---@package
+---@param srow integer
+---@param erow integer
+function FoldInfo:remove_range(srow, erow)
+ list_remove(self.levels, srow + 1, erow)
+ list_remove(self.levels0, srow + 1, erow)
+ list_remove(self.start_counts, srow + 1, erow)
+ list_remove(self.stop_counts, srow + 1, erow)
+end
+
+--- Efficiently insert items into the middle of a list.
+---
+--- Calling table.insert() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+---@param v any
+local function list_insert(t, first, last, v)
+ local n = #t
+
+ -- Shift table forward
+ for i = n - first, 0, -1 do
+ t[last + 1 + i] = t[first + i]
+ end
+
+ -- Fill in new values
+ for i = first, last do
+ t[i] = v
+ end
+end
+
+---@package
+---@param srow integer
+---@param erow integer
+function FoldInfo:add_range(srow, erow)
+ list_insert(self.levels, srow + 1, erow, '-1')
+ list_insert(self.levels0, srow + 1, erow, -1)
+ list_insert(self.start_counts, srow + 1, erow, nil)
+ list_insert(self.stop_counts, srow + 1, erow, nil)
+end
+
+---@package
+---@param lnum integer
+function FoldInfo:add_start(lnum)
+ self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1
+end
+
+---@package
+---@param lnum integer
+function FoldInfo:add_stop(lnum)
+ self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1
+end
+
+---@package
+---@param lnum integer
+---@return integer
+function FoldInfo:get_start(lnum)
+ return self.start_counts[lnum] or 0
+end
+
+---@package
+---@param lnum integer
+---@return integer
+function FoldInfo:get_stop(lnum)
+ return self.stop_counts[lnum] or 0
+end
+
+local function trim_level(level)
+ local max_fold_level = vim.wo.foldnestmax
+ if level > max_fold_level then
+ return max_fold_level
+ end
+ return level
+end
+
+--- If a parser doesn't have any ranges explicitly set, treesitter will
+--- return a range with end_row and end_bytes with a value of UINT32_MAX,
+--- so clip end_row to the max buffer line.
+---
+--- TODO(lewis6991): Handle this generally
+---
+--- @param bufnr integer
+--- @param erow integer?
+--- @return integer
+local function normalise_erow(bufnr, erow)
+ local max_erow = api.nvim_buf_line_count(bufnr) - 1
+ return math.min(erow or max_erow, max_erow)
+end
+
+-- TODO(lewis6991): Setup a decor provider so injections folds can be parsed
+-- as the window is redrawn
+---@param bufnr integer
+---@param info TS.FoldInfo
+---@param srow integer?
+---@param erow integer?
+---@param parse_injections? boolean
+local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
+ srow = srow or 0
+ erow = normalise_erow(bufnr, erow)
+
+ info:invalidate_range(srow, erow)
+
+ local prev_start = -1
+ local prev_stop = -1
+
+ local parser = ts.get_parser(bufnr)
+
+ parser:parse(parse_injections and { srow, erow } or nil)
+
+ parser:for_each_tree(function(tree, ltree)
+ local query = ts.query.get(ltree:lang(), 'folds')
+ if not query then
+ return
+ end
+
+ -- erow in query is end-exclusive
+ local q_erow = erow and erow + 1 or -1
+
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do
+ if query.captures[id] == 'fold' then
+ local range = ts.get_range(node, bufnr, metadata[id])
+ local start, _, stop, stop_col = Range.unpack4(range)
+
+ if stop_col == 0 then
+ stop = stop - 1
+ end
+
+ local fold_length = stop - start + 1
+
+ -- Fold only multiline nodes that are not exactly the same as previously met folds
+ -- Checking against just the previously found fold is sufficient if nodes
+ -- are returned in preorder or postorder when traversing tree
+ if
+ fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop)
+ then
+ info:add_start(start + 1)
+ info:add_stop(stop + 1)
+ prev_start = start
+ prev_stop = stop
+ end
+ end
+ end
+ end)
+
+ local current_level = info.levels0[srow] or 0
+
+ -- We now have the list of fold opening and closing, fill the gaps and mark where fold start
+ for lnum = srow + 1, erow + 1 do
+ local last_trimmed_level = trim_level(current_level)
+ current_level = current_level + info:get_start(lnum)
+ info.levels0[lnum] = current_level
+
+ local trimmed_level = trim_level(current_level)
+ current_level = current_level - info:get_stop(lnum)
+
+ -- Determine if it's the start/end of a fold
+ -- NB: vim's fold-expr interface does not have a mechanism to indicate that
+ -- two (or more) folds start at this line, so it cannot distinguish between
+ -- ( \n ( \n )) \n (( \n ) \n )
+ -- versus
+ -- ( \n ( \n ) \n ( \n ) \n )
+ -- If it did have such a mechanism, (trimmed_level - last_trimmed_level)
+ -- would be the correct number of starts to pass on.
+ local prefix = ''
+ if trimmed_level - last_trimmed_level > 0 then
+ prefix = '>'
+ end
+
+ info.levels[lnum] = prefix .. tostring(trimmed_level)
+ end
+end
+
+local M = {}
+
+---@type table<integer,TS.FoldInfo>
+local foldinfos = {}
+
+local group = api.nvim_create_augroup('treesitter/fold', {})
+
+--- Update the folds in the windows that contain the buffer and use expr foldmethod (assuming that
+--- the user doesn't use different foldexpr for the same buffer).
+---
+--- Nvim usually automatically updates folds when text changes, but it doesn't work here because
+--- FoldInfo update is scheduled. So we do it manually.
+local function foldupdate(bufnr)
+ local function do_update()
+ for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do
+ api.nvim_win_call(win, function()
+ if vim.wo.foldmethod == 'expr' then
+ vim._foldupdate()
+ end
+ end)
+ end
+ end
+
+ if api.nvim_get_mode().mode == 'i' then
+ -- foldUpdate() is guarded in insert mode. So update folds on InsertLeave
+ if #(api.nvim_get_autocmds({
+ group = group,
+ buffer = bufnr,
+ })) > 0 then
+ return
+ end
+ api.nvim_create_autocmd('InsertLeave', {
+ group = group,
+ buffer = bufnr,
+ once = true,
+ callback = do_update,
+ })
+ return
+ end
+
+ do_update()
+end
+
+--- Schedule a function only if bufnr is loaded.
+--- We schedule fold level computation for the following reasons:
+--- * queries seem to use the old buffer state in on_bytes for some unknown reason;
+--- * to avoid textlock;
+--- * to avoid infinite recursion:
+--- get_folds_levels → parse → _do_callback → on_changedtree → get_folds_levels.
+---@param bufnr integer
+---@param fn function
+local function schedule_if_loaded(bufnr, fn)
+ vim.schedule(function()
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ fn()
+ end)
+end
+
+---@param bufnr integer
+---@param foldinfo TS.FoldInfo
+---@param tree_changes Range4[]
+local function on_changedtree(bufnr, foldinfo, tree_changes)
+ schedule_if_loaded(bufnr, function()
+ for _, change in ipairs(tree_changes) do
+ local srow, _, erow = Range.unpack4(change)
+ get_folds_levels(bufnr, foldinfo, srow, erow)
+ end
+ if #tree_changes > 0 then
+ foldupdate(bufnr)
+ end
+ end)
+end
+
+---@param bufnr integer
+---@param foldinfo TS.FoldInfo
+---@param start_row integer
+---@param old_row integer
+---@param new_row integer
+local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row)
+ local end_row_old = start_row + old_row
+ local end_row_new = start_row + new_row
+
+ if new_row ~= old_row then
+ if new_row < old_row then
+ foldinfo:remove_range(end_row_new, end_row_old)
+ else
+ foldinfo:add_range(start_row, end_row_new)
+ end
+ schedule_if_loaded(bufnr, function()
+ get_folds_levels(bufnr, foldinfo, start_row, end_row_new)
+ foldupdate(bufnr)
+ end)
+ end
+end
+
+---@package
+---@param lnum integer|nil
+---@return string
+function M.foldexpr(lnum)
+ lnum = lnum or vim.v.lnum
+ local bufnr = api.nvim_get_current_buf()
+
+ local parser = vim.F.npcall(ts.get_parser, bufnr)
+ if not parser then
+ return '0'
+ end
+
+ if not foldinfos[bufnr] then
+ foldinfos[bufnr] = FoldInfo.new()
+ get_folds_levels(bufnr, foldinfos[bufnr])
+
+ parser:register_cbs({
+ on_changedtree = function(tree_changes)
+ on_changedtree(bufnr, foldinfos[bufnr], tree_changes)
+ end,
+
+ on_bytes = function(_, _, start_row, _, _, old_row, _, _, new_row, _, _)
+ on_bytes(bufnr, foldinfos[bufnr], start_row, old_row, new_row)
+ end,
+
+ on_detach = function()
+ foldinfos[bufnr] = nil
+ end,
+ })
+ end
+
+ return foldinfos[bufnr].levels[lnum] or '0'
+end
+
+---@package
+---@return { [1]: string, [2]: string[] }[]|string
+function M.foldtext()
+ local foldstart = vim.v.foldstart
+ local bufnr = api.nvim_get_current_buf()
+
+ ---@type boolean, LanguageTree
+ local ok, parser = pcall(ts.get_parser, bufnr)
+ if not ok then
+ return vim.fn.foldtext()
+ end
+
+ local query = ts.query.get(parser:lang(), 'highlights')
+ if not query then
+ return vim.fn.foldtext()
+ end
+
+ local tree = parser:parse({ foldstart - 1, foldstart })[1]
+
+ local line = api.nvim_buf_get_lines(bufnr, foldstart - 1, foldstart, false)[1]
+ if not line then
+ return vim.fn.foldtext()
+ end
+
+ ---@type { [1]: string, [2]: string[], range: { [1]: integer, [2]: integer } }[] | { [1]: string, [2]: string[] }[]
+ local result = {}
+
+ local line_pos = 0
+
+ for id, node, metadata in query:iter_captures(tree:root(), 0, foldstart - 1, foldstart) do
+ local name = query.captures[id]
+ local start_row, start_col, end_row, end_col = node:range()
+
+ local priority = tonumber(metadata.priority or vim.highlight.priorities.treesitter)
+
+ if start_row == foldstart - 1 and end_row == foldstart - 1 then
+ -- check for characters ignored by treesitter
+ if start_col > line_pos then
+ table.insert(result, {
+ line:sub(line_pos + 1, start_col),
+ {},
+ range = { line_pos, start_col },
+ })
+ end
+ line_pos = end_col
+
+ local text = line:sub(start_col + 1, end_col)
+ table.insert(result, { text, { { '@' .. name, priority } }, range = { start_col, end_col } })
+ end
+ end
+
+ local i = 1
+ while i <= #result do
+ -- find first capture that is not in current range and apply highlights on the way
+ local j = i + 1
+ while
+ j <= #result
+ and result[j].range[1] >= result[i].range[1]
+ and result[j].range[2] <= result[i].range[2]
+ do
+ for k, v in ipairs(result[i][2]) do
+ if not vim.tbl_contains(result[j][2], v) then
+ table.insert(result[j][2], k, v)
+ end
+ end
+ j = j + 1
+ end
+
+ -- remove the parent capture if it is split into children
+ if j > i + 1 then
+ table.remove(result, i)
+ else
+ -- highlights need to be sorted by priority, on equal prio, the deeper nested capture (earlier
+ -- in list) should be considered higher prio
+ if #result[i][2] > 1 then
+ table.sort(result[i][2], function(a, b)
+ return a[2] < b[2]
+ end)
+ end
+
+ result[i][2] = vim.tbl_map(function(tbl)
+ return tbl[1]
+ end, result[i][2])
+ result[i] = { result[i][1], result[i][2] }
+
+ i = i + 1
+ end
+ end
+
+ return result
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
new file mode 100644
index 0000000000..80c998b555
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -0,0 +1,80 @@
+---@meta
+
+---@class TSNode: userdata
+---@field id fun(self: TSNode): string
+---@field tree fun(self: TSNode): TSTree
+---@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer
+---@field range fun(self: TSNode, include_bytes: true): integer, integer, integer, integer, integer, integer
+---@field start fun(self: TSNode): integer, integer, integer
+---@field end_ fun(self: TSNode): integer, integer, integer
+---@field type fun(self: TSNode): string
+---@field symbol fun(self: TSNode): integer
+---@field named fun(self: TSNode): boolean
+---@field missing fun(self: TSNode): boolean
+---@field extra fun(self: TSNode): boolean
+---@field child_count fun(self: TSNode): integer
+---@field named_child_count fun(self: TSNode): integer
+---@field child fun(self: TSNode, index: integer): TSNode?
+---@field named_child fun(self: TSNode, index: integer): TSNode?
+---@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
+---@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode?
+---@field parent fun(self: TSNode): TSNode?
+---@field next_sibling fun(self: TSNode): TSNode?
+---@field prev_sibling fun(self: TSNode): TSNode?
+---@field next_named_sibling fun(self: TSNode): TSNode?
+---@field prev_named_sibling fun(self: TSNode): TSNode?
+---@field named_children fun(self: TSNode): TSNode[]
+---@field has_changes fun(self: TSNode): boolean
+---@field has_error fun(self: TSNode): boolean
+---@field sexpr fun(self: TSNode): string
+---@field equal fun(self: TSNode, other: TSNode): boolean
+---@field iter_children fun(self: TSNode): fun(): TSNode, string
+---@field field fun(self: TSNode, name: string): TSNode[]
+---@field byte_length fun(self: TSNode): integer
+local TSNode = {}
+
+---@param query userdata
+---@param captures true
+---@param start? integer
+---@param end_? integer
+---@param opts? table
+---@return fun(): integer, TSNode, any
+function TSNode:_rawquery(query, captures, start, end_, opts) end
+
+---@param query userdata
+---@param captures false
+---@param start? integer
+---@param end_? integer
+---@param opts? table
+---@return fun(): string, any
+function TSNode:_rawquery(query, captures, start, end_, opts) end
+
+---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
+
+---@class TSParser
+---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: true): TSTree, Range6[]
+---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: false|nil): TSTree, Range4[]
+---@field reset fun(self: TSParser)
+---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
+---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
+---@field set_timeout fun(self: TSParser, timeout: integer)
+---@field timeout fun(self: TSParser): integer
+---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
+---@field _logger fun(self: TSParser): TSLoggerCallback
+
+---@class TSTree
+---@field root fun(self: TSTree): TSNode
+---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
+---@field copy fun(self: TSTree): TSTree
+---@field included_ranges fun(self: TSTree, include_bytes: true): Range6[]
+---@field included_ranges fun(self: TSTree, include_bytes: false): Range4[]
+
+---@return integer
+vim._ts_get_language_version = function() end
+
+---@return integer
+vim._ts_get_minimum_language_version = function() end
+
+---@param lang string
+---@return TSParser
+vim._create_ts_parser = function(lang) end
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
new file mode 100644
index 0000000000..87d74789a3
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_query_linter.lua
@@ -0,0 +1,249 @@
+local api = vim.api
+
+local namespace = api.nvim_create_namespace('vim.treesitter.query_linter')
+
+local M = {}
+
+--- @class QueryLinterNormalizedOpts
+--- @field langs string[]
+--- @field clear boolean
+
+--- @alias vim.treesitter.ParseError {msg: string, range: Range4}
+
+--- Contains language dependent context for the query linter
+--- @class QueryLinterLanguageContext
+--- @field lang string? Current `lang` of the targeted parser
+--- @field parser_info table? Parser info returned by vim.treesitter.language.inspect
+--- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs`
+
+--- Adds a diagnostic for node in the query buffer
+--- @param diagnostics Diagnostic[]
+--- @param range Range4
+--- @param lint string
+--- @param lang string?
+local function add_lint_for_node(diagnostics, range, lint, lang)
+ local message = lint:gsub('\n', ' ')
+ diagnostics[#diagnostics + 1] = {
+ lnum = range[1],
+ end_lnum = range[3],
+ col = range[2],
+ end_col = range[4],
+ severity = vim.diagnostic.ERROR,
+ message = message,
+ source = lang,
+ }
+end
+
+--- Determines the target language of a query file by its path: <lang>/<query_type>.scm
+--- @param buf integer
+--- @return string?
+local function guess_query_lang(buf)
+ local filename = api.nvim_buf_get_name(buf)
+ if filename ~= '' then
+ return vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t')
+ end
+end
+
+--- @param buf integer
+--- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil
+--- @return QueryLinterNormalizedOpts
+local function normalize_opts(buf, opts)
+ opts = opts or {}
+ if not opts.langs then
+ opts.langs = guess_query_lang(buf)
+ end
+
+ if type(opts.langs) ~= 'table' then
+ --- @diagnostic disable-next-line:assign-type-mismatch
+ opts.langs = { opts.langs }
+ end
+
+ --- @cast opts QueryLinterNormalizedOpts
+ opts.langs = opts.langs or {}
+ return opts
+end
+
+local lint_query = [[;; query
+ (program [(named_node) (list) (grouping)] @toplevel)
+ (named_node
+ name: _ @node.named)
+ (anonymous_node
+ name: _ @node.anonymous)
+ (field_definition
+ name: (identifier) @field)
+ (predicate
+ name: (identifier) @predicate.name
+ type: (predicate_type) @predicate.type)
+ (ERROR) @error
+]]
+
+--- @param err string
+--- @param node TSNode
+--- @return vim.treesitter.ParseError
+local function get_error_entry(err, node)
+ local start_line, start_col = node:range()
+ local line_offset, col_offset, msg = err:gmatch('.-:%d+: Query error at (%d+):(%d+)%. ([^:]+)')() ---@type string, string, string
+ start_line, start_col =
+ start_line + tonumber(line_offset) - 1, start_col + tonumber(col_offset) - 1
+ local end_line, end_col = start_line, start_col
+ if msg:match('^Invalid syntax') or msg:match('^Impossible') then
+ -- Use the length of the underlined node
+ local underlined = vim.split(err, '\n')[2]
+ end_col = end_col + #underlined
+ elseif msg:match('^Invalid') then
+ -- Use the length of the problematic type/capture/field
+ end_col = end_col + #msg:match('"([^"]+)"')
+ end
+
+ return {
+ msg = msg,
+ range = { start_line, start_col, end_line, end_col },
+ }
+end
+
+--- @param node TSNode
+--- @param buf integer
+--- @param lang string
+local function hash_parse(node, buf, lang)
+ return tostring(node:id()) .. tostring(buf) .. tostring(vim.b[buf].changedtick) .. lang
+end
+
+--- @param node TSNode
+--- @param buf integer
+--- @param lang string
+--- @return vim.treesitter.ParseError?
+local parse = vim.func._memoize(hash_parse, function(node, buf, lang)
+ local query_text = vim.treesitter.get_node_text(node, buf)
+ local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|Query
+
+ if not ok and type(err) == 'string' then
+ return get_error_entry(err, node)
+ end
+end)
+
+--- @param buf integer
+--- @param match table<integer,TSNode>
+--- @param query Query
+--- @param lang_context QueryLinterLanguageContext
+--- @param diagnostics Diagnostic[]
+local function lint_match(buf, match, query, lang_context, diagnostics)
+ local lang = lang_context.lang
+ local parser_info = lang_context.parser_info
+
+ for id, node in pairs(match) do
+ local cap_id = query.captures[id]
+
+ -- perform language-independent checks only for first lang
+ if lang_context.is_first_lang and cap_id == 'error' then
+ local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ')
+ add_lint_for_node(diagnostics, { node:range() }, 'Syntax error: ' .. node_text)
+ end
+
+ -- other checks rely on Neovim parser introspection
+ if lang and parser_info and cap_id == 'toplevel' then
+ local err = parse(node, buf, lang)
+ if err then
+ add_lint_for_node(diagnostics, err.range, err.msg, lang)
+ end
+ end
+ end
+end
+
+--- @private
+--- @param buf integer Buffer to lint
+--- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil Options for linting
+function M.lint(buf, opts)
+ if buf == 0 then
+ buf = api.nvim_get_current_buf()
+ end
+
+ local diagnostics = {}
+ local query = vim.treesitter.query.parse('query', lint_query)
+
+ opts = normalize_opts(buf, opts)
+
+ -- perform at least one iteration even with no langs to perform language independent checks
+ for i = 1, math.max(1, #opts.langs) do
+ local lang = opts.langs[i]
+
+ --- @type (table|nil)
+ local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang)
+
+ local parser = vim.treesitter.get_parser(buf)
+ parser:parse()
+ parser:for_each_tree(function(tree, ltree)
+ if ltree:lang() == 'query' then
+ for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do
+ local lang_context = {
+ lang = lang,
+ parser_info = parser_info,
+ is_first_lang = i == 1,
+ }
+ lint_match(buf, match, query, lang_context, diagnostics)
+ end
+ end
+ end)
+ end
+
+ vim.diagnostic.set(namespace, buf, diagnostics)
+end
+
+--- @private
+--- @param buf integer
+function M.clear(buf)
+ vim.diagnostic.reset(namespace, buf)
+end
+
+--- @private
+--- @param findstart integer
+--- @param base string
+function M.omnifunc(findstart, base)
+ if findstart == 1 then
+ local result =
+ api.nvim_get_current_line():sub(1, api.nvim_win_get_cursor(0)[2]):find('["#%-%w]*$')
+ return result - 1
+ end
+
+ local buf = api.nvim_get_current_buf()
+ local query_lang = guess_query_lang(buf)
+
+ local ok, parser_info = pcall(vim.treesitter.language.inspect, query_lang)
+ if not ok then
+ return -2
+ end
+
+ local items = {}
+ for _, f in pairs(parser_info.fields) do
+ if f:find(base, 1, true) then
+ table.insert(items, f .. ':')
+ end
+ end
+ for _, p in pairs(vim.treesitter.query.list_predicates()) do
+ local text = '#' .. p
+ local found = text:find(base, 1, true)
+ if found and found <= 2 then -- with or without '#'
+ table.insert(items, text)
+ end
+ text = '#not-' .. p
+ found = text:find(base, 1, true)
+ if found and found <= 2 then -- with or without '#'
+ table.insert(items, text)
+ end
+ end
+ for _, p in pairs(vim.treesitter.query.list_directives()) do
+ local text = '#' .. p
+ local found = text:find(base, 1, true)
+ if found and found <= 2 then -- with or without '#'
+ table.insert(items, text)
+ end
+ end
+ for _, s in pairs(parser_info.symbols) do
+ local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' ---@type string
+ if text:find(base, 1, true) then
+ table.insert(items, text)
+ end
+ end
+ return { words = items, refresh = 'always' }
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua
new file mode 100644
index 0000000000..8d727c3c52
--- /dev/null
+++ b/runtime/lua/vim/treesitter/_range.lua
@@ -0,0 +1,193 @@
+local api = vim.api
+
+local M = {}
+
+---@class Range2
+---@field [1] integer start row
+---@field [2] integer end row
+
+---@class Range4
+---@field [1] integer start row
+---@field [2] integer start column
+---@field [3] integer end row
+---@field [4] integer end column
+
+---@class Range6
+---@field [1] integer start row
+---@field [2] integer start column
+---@field [3] integer start bytes
+---@field [4] integer end row
+---@field [5] integer end column
+---@field [6] integer end bytes
+
+---@alias Range Range2|Range4|Range6
+
+---@private
+---@param a_row integer
+---@param a_col integer
+---@param b_row integer
+---@param b_col integer
+---@return integer
+--- 1: a > b
+--- 0: a == b
+--- -1: a < b
+local function cmp_pos(a_row, a_col, b_row, b_col)
+ if a_row == b_row then
+ if a_col > b_col then
+ return 1
+ elseif a_col < b_col then
+ return -1
+ else
+ return 0
+ end
+ elseif a_row > b_row then
+ return 1
+ end
+
+ return -1
+end
+
+M.cmp_pos = {
+ lt = function(...)
+ return cmp_pos(...) == -1
+ end,
+ le = function(...)
+ return cmp_pos(...) ~= 1
+ end,
+ gt = function(...)
+ return cmp_pos(...) == 1
+ end,
+ ge = function(...)
+ return cmp_pos(...) ~= -1
+ end,
+ eq = function(...)
+ return cmp_pos(...) == 0
+ end,
+ ne = function(...)
+ return cmp_pos(...) ~= 0
+ end,
+}
+
+setmetatable(M.cmp_pos, { __call = cmp_pos })
+
+---@private
+---Check if a variable is a valid range object
+---@param r any
+---@return boolean
+function M.validate(r)
+ if type(r) ~= 'table' or #r ~= 6 and #r ~= 4 then
+ return false
+ end
+
+ for _, e in
+ ipairs(r --[[@as any[] ]])
+ do
+ if type(e) ~= 'number' then
+ return false
+ end
+ end
+
+ return true
+end
+
+---@private
+---@param r1 Range
+---@param r2 Range
+---@return boolean
+function M.intercepts(r1, r2)
+ local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
+ local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2)
+
+ -- r1 is above r2
+ if M.cmp_pos.le(erow_1, ecol_1, srow_2, scol_2) then
+ return false
+ end
+
+ -- r1 is below r2
+ if M.cmp_pos.ge(srow_1, scol_1, erow_2, ecol_2) then
+ return false
+ end
+
+ return true
+end
+
+---@private
+---@param r Range
+---@return integer, integer, integer, integer
+function M.unpack4(r)
+ if #r == 2 then
+ return r[1], 0, r[2], 0
+ end
+ local off_1 = #r == 6 and 1 or 0
+ return r[1], r[2], r[3 + off_1], r[4 + off_1]
+end
+
+---@private
+---@param r Range6
+---@return integer, integer, integer, integer, integer, integer
+function M.unpack6(r)
+ return r[1], r[2], r[3], r[4], r[5], r[6]
+end
+
+---@private
+---@param r1 Range
+---@param r2 Range
+---@return boolean whether r1 contains r2
+function M.contains(r1, r2)
+ local srow_1, scol_1, erow_1, ecol_1 = M.unpack4(r1)
+ local srow_2, scol_2, erow_2, ecol_2 = M.unpack4(r2)
+
+ -- start doesn't fit
+ if M.cmp_pos.gt(srow_1, scol_1, srow_2, scol_2) then
+ return false
+ end
+
+ -- end doesn't fit
+ if M.cmp_pos.lt(erow_1, ecol_1, erow_2, ecol_2) then
+ return false
+ end
+
+ return true
+end
+
+--- @param source integer|string
+--- @param index integer
+--- @return integer
+local function get_offset(source, index)
+ if index == 0 then
+ return 0
+ end
+
+ if type(source) == 'number' then
+ return api.nvim_buf_get_offset(source, index)
+ end
+
+ local byte = 0
+ local next_offset = source:gmatch('()\n')
+ local line = 1
+ while line <= index do
+ byte = next_offset() --[[@as integer]]
+ line = line + 1
+ end
+
+ return byte
+end
+
+---@private
+---@param source integer|string
+---@param range Range
+---@return Range6
+function M.add_bytes(source, range)
+ if type(range) == 'table' and #range == 6 then
+ return range --[[@as Range6]]
+ end
+
+ local start_row, start_col, end_row, end_col = M.unpack4(range)
+ -- TODO(vigoux): proper byte computation here, and account for EOL ?
+ local start_byte = get_offset(source, start_row) + start_col
+ local end_byte = get_offset(source, end_row) + end_col
+
+ return { start_row, start_col, start_byte, end_row, end_col, end_byte }
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
new file mode 100644
index 0000000000..69ddc9b558
--- /dev/null
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -0,0 +1,645 @@
+local api = vim.api
+
+---@class TSDevModule
+local M = {}
+
+---@class TSTreeView
+---@field ns integer API namespace
+---@field opts table Options table with the following keys:
+--- - anon (boolean): If true, display anonymous nodes
+--- - lang (boolean): If true, display the language alongside each node
+--- - indent (number): Number of spaces to indent nested lines. Default is 2.
+---@field nodes TSP.Node[]
+---@field named TSP.Node[]
+local TSTreeView = {}
+
+---@class TSP.Node
+---@field id integer Node id
+---@field text string Node text
+---@field named boolean True if this is a named (non-anonymous) node
+---@field depth integer Depth of the node within the tree
+---@field lnum integer Beginning line number of this node in the source buffer
+---@field col integer Beginning column number of this node in the source buffer
+---@field end_lnum integer Final line number of this node in the source buffer
+---@field end_col integer Final column number of this node in the source buffer
+---@field lang string Source language of this node
+---@field root TSNode
+
+---@class TSP.Injection
+---@field lang string Source language of this injection
+---@field root TSNode Root node of the injection
+
+--- Traverse all child nodes starting at {node}.
+---
+--- This is a recursive function. The {depth} parameter indicates the current recursion level.
+--- {lang} is a string indicating the language of the tree currently being traversed. Each traversed
+--- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order
+--- they were visited.
+---
+--- {injections} is a table mapping node ids from the primary tree to language tree injections. Each
+--- injected language has a series of trees nested within the primary language's tree, and the root
+--- node of each of these trees is contained within a node in the primary tree. The {injections}
+--- table maps nodes in the primary tree to root nodes of injected trees.
+---
+---@param node TSNode Starting node to begin traversal |tsnode|
+---@param depth integer Current recursion depth
+---@param lang string Language of the tree currently being traversed
+---@param injections table<string, TSP.Injection> Mapping of node ids to root nodes
+--- of injected language trees (see explanation above)
+---@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree
+local function traverse(node, depth, lang, injections, tree)
+ local injection = injections[node:id()]
+ if injection then
+ traverse(injection.root, depth, injection.lang, injections, tree)
+ end
+
+ for child, field in node:iter_children() do
+ local type = child:type()
+ local lnum, col, end_lnum, end_col = child:range()
+ local named = child:named()
+ local text ---@type string
+ if named then
+ if field then
+ text = string.format('%s: (%s', field, type)
+ else
+ text = string.format('(%s', type)
+ end
+ else
+ text = string.format('"%s"', type:gsub('\n', '\\n'):gsub('"', '\\"'))
+ end
+
+ table.insert(tree, {
+ id = child:id(),
+ text = text,
+ named = named,
+ depth = depth,
+ lnum = lnum,
+ col = col,
+ end_lnum = end_lnum,
+ end_col = end_col,
+ lang = lang,
+ })
+
+ traverse(child, depth + 1, lang, injections, tree)
+
+ if named then
+ tree[#tree].text = string.format('%s)', tree[#tree].text)
+ end
+ end
+
+ return tree
+end
+
+--- Create a new treesitter view.
+---
+---@param bufnr integer Source buffer number
+---@param lang string|nil Language of source buffer
+---
+---@return TSTreeView|nil
+---@return string|nil Error message, if any
+---
+---@package
+function TSTreeView:new(bufnr, lang)
+ local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
+ if not ok then
+ return nil, 'No parser available for the given buffer'
+ end
+
+ -- For each child tree (injected language), find the root of the tree and locate the node within
+ -- the primary tree that contains that root. Add a mapping from the node in the primary tree to
+ -- the root in the child tree to the {injections} table.
+ local root = parser:parse(true)[1]:root()
+ local injections = {} ---@type table<string, TSP.Injection>
+
+ parser:for_each_tree(function(parent_tree, parent_ltree)
+ local parent = parent_tree:root()
+ for _, child in pairs(parent_ltree:children()) do
+ child:for_each_tree(function(tree, ltree)
+ local r = tree:root()
+ local node = assert(parent:named_descendant_for_range(r:range()))
+ local id = node:id()
+ if not injections[id] or r:byte_length() > injections[id].root:byte_length() then
+ injections[id] = {
+ lang = ltree:lang(),
+ root = r,
+ }
+ end
+ end)
+ end
+ end)
+
+ local nodes = traverse(root, 0, parser:lang(), injections, {})
+
+ local named = {} ---@type TSP.Node[]
+ for _, v in ipairs(nodes) do
+ if v.named then
+ named[#named + 1] = v
+ end
+ end
+
+ local t = {
+ ns = api.nvim_create_namespace('treesitter/dev-inspect'),
+ nodes = nodes,
+ named = named,
+ opts = {
+ anon = false,
+ lang = false,
+ indent = 2,
+ },
+ }
+
+ setmetatable(t, self)
+ self.__index = self
+ return t
+end
+
+local decor_ns = api.nvim_create_namespace('ts.dev')
+
+---@param lnum integer
+---@param col integer
+---@param end_lnum integer
+---@param end_col integer
+---@return string
+local function get_range_str(lnum, col, end_lnum, end_col)
+ if lnum == end_lnum then
+ return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col)
+ end
+ return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
+end
+
+---@param w integer
+---@return boolean closed Whether the window was closed.
+local function close_win(w)
+ if api.nvim_win_is_valid(w) then
+ api.nvim_win_close(w, true)
+ return true
+ end
+
+ return false
+end
+
+---@param w integer
+---@param b integer
+local function set_dev_properties(w, b)
+ vim.wo[w].scrolloff = 5
+ vim.wo[w].wrap = false
+ vim.wo[w].foldmethod = 'manual' -- disable folding
+ vim.bo[b].buflisted = false
+ vim.bo[b].buftype = 'nofile'
+ vim.bo[b].bufhidden = 'wipe'
+ vim.bo[b].filetype = 'query'
+end
+
+--- Updates the cursor position in the inspector to match the node under the cursor.
+---
+--- @param treeview TSTreeView
+--- @param lang string
+--- @param source_buf integer
+--- @param inspect_buf integer
+--- @param inspect_win integer
+--- @param pos? { [1]: integer, [2]: integer }
+local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos)
+ api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1)
+
+ local cursor_node = vim.treesitter.get_node({
+ bufnr = source_buf,
+ lang = lang,
+ pos = pos,
+ ignore_injections = false,
+ })
+ if not cursor_node then
+ return
+ end
+
+ local cursor_node_id = cursor_node:id()
+ for i, v in treeview:iter() do
+ if v.id == cursor_node_id then
+ local start = v.depth * treeview.opts.indent ---@type integer
+ local end_col = start + #v.text
+ api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, {
+ end_col = end_col,
+ hl_group = 'Visual',
+ })
+ api.nvim_win_set_cursor(inspect_win, { i, 0 })
+ break
+ end
+ end
+end
+
+--- Write the contents of this View into {bufnr}.
+---
+---@param bufnr integer Buffer number to write into.
+---@package
+function TSTreeView:draw(bufnr)
+ vim.bo[bufnr].modifiable = true
+ local lines = {} ---@type string[]
+ local lang_hl_marks = {} ---@type table[]
+
+ for _, item in self:iter() do
+ local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col)
+ local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
+ local line = string.format(
+ '%s%s ; %s%s',
+ string.rep(' ', item.depth * self.opts.indent),
+ item.text,
+ range_str,
+ lang_str
+ )
+
+ if self.opts.lang then
+ lang_hl_marks[#lang_hl_marks + 1] = {
+ col = #line - #lang_str,
+ end_col = #line,
+ }
+ end
+
+ lines[#lines + 1] = line
+ end
+
+ api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
+
+ api.nvim_buf_clear_namespace(bufnr, decor_ns, 0, -1)
+
+ for i, m in ipairs(lang_hl_marks) do
+ api.nvim_buf_set_extmark(bufnr, decor_ns, i - 1, m.col, {
+ hl_group = 'Title',
+ end_col = m.end_col,
+ })
+ end
+
+ vim.bo[bufnr].modifiable = false
+end
+
+--- Get node {i} from this View.
+---
+--- The node number is dependent on whether or not anonymous nodes are displayed.
+---
+---@param i integer Node number to get
+---@return TSP.Node
+---@package
+function TSTreeView:get(i)
+ local t = self.opts.anon and self.nodes or self.named
+ return t[i]
+end
+
+--- Iterate over all of the nodes in this View.
+---
+---@return (fun(): integer, TSP.Node) Iterator over all nodes in this View
+---@return table
+---@return integer
+---@package
+function TSTreeView:iter()
+ return ipairs(self.opts.anon and self.nodes or self.named)
+end
+
+--- @class InspectTreeOpts
+--- @field lang string? The language of the source buffer. If omitted, the
+--- filetype of the source buffer is used.
+--- @field bufnr integer? Buffer to draw the tree into. If omitted, a new
+--- buffer is created.
+--- @field winid integer? Window id to display the tree buffer in. If omitted,
+--- a new window is created with {command}.
+--- @field command string? Vimscript command to create the window. Default
+--- value is "60vnew". Only used when {winid} is nil.
+--- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a
+--- function, it accepts the buffer number of the source
+--- buffer as its only argument and should return a string.
+
+--- @private
+---
+--- @param opts InspectTreeOpts?
+function M.inspect_tree(opts)
+ vim.validate({
+ opts = { opts, 't', true },
+ })
+
+ opts = opts or {}
+
+ local buf = api.nvim_get_current_buf()
+ local win = api.nvim_get_current_win()
+ local treeview = assert(TSTreeView:new(buf, opts.lang))
+
+ -- Close any existing inspector window
+ if vim.b[buf].dev_inspect then
+ close_win(vim.b[buf].dev_inspect)
+ end
+
+ local w = opts.winid
+ if not w then
+ vim.cmd(opts.command or '60vnew')
+ w = api.nvim_get_current_win()
+ end
+
+ local b = opts.bufnr
+ if b then
+ api.nvim_win_set_buf(w, b)
+ else
+ b = api.nvim_win_get_buf(w)
+ end
+
+ vim.b[buf].dev_inspect = w
+ vim.b[b].dev_base = win -- base window handle
+ vim.b[b].disable_query_linter = true
+ set_dev_properties(w, b)
+
+ local title --- @type string?
+ local opts_title = opts.title
+ if not opts_title then
+ local bufname = api.nvim_buf_get_name(buf)
+ title = string.format('Syntax tree for %s', vim.fn.fnamemodify(bufname, ':.'))
+ elseif type(opts_title) == 'function' then
+ title = opts_title(buf)
+ end
+
+ assert(type(title) == 'string', 'Window title must be a string')
+ api.nvim_buf_set_name(b, title)
+
+ treeview:draw(b)
+
+ local cursor = api.nvim_win_get_cursor(win)
+ set_inspector_cursor(treeview, opts.lang, buf, b, w, { cursor[1] - 1, cursor[2] })
+
+ api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
+ api.nvim_buf_set_keymap(b, 'n', '<CR>', '', {
+ desc = 'Jump to the node under the cursor in the source buffer',
+ callback = function()
+ local row = api.nvim_win_get_cursor(w)[1]
+ local pos = treeview:get(row)
+ api.nvim_set_current_win(win)
+ api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col })
+ end,
+ })
+ api.nvim_buf_set_keymap(b, 'n', 'a', '', {
+ desc = 'Toggle anonymous nodes',
+ callback = function()
+ local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer
+ local curnode = treeview:get(row)
+ while curnode and not curnode.named do
+ row = row - 1
+ curnode = treeview:get(row)
+ end
+
+ treeview.opts.anon = not treeview.opts.anon
+ treeview:draw(b)
+
+ if not curnode then
+ return
+ end
+
+ local id = curnode.id
+ for i, node in treeview:iter() do
+ if node.id == id then
+ api.nvim_win_set_cursor(w, { i, col })
+ break
+ end
+ end
+ end,
+ })
+ api.nvim_buf_set_keymap(b, 'n', 'I', '', {
+ desc = 'Toggle language display',
+ callback = function()
+ treeview.opts.lang = not treeview.opts.lang
+ treeview:draw(b)
+ end,
+ })
+ api.nvim_buf_set_keymap(b, 'n', 'o', '', {
+ desc = 'Toggle query editor',
+ callback = function()
+ local edit_w = vim.b[buf].dev_edit
+ if not edit_w or not close_win(edit_w) then
+ M.edit_query()
+ end
+ end,
+ })
+
+ local group = api.nvim_create_augroup('treesitter/dev', {})
+
+ api.nvim_create_autocmd('CursorMoved', {
+ group = group,
+ buffer = b,
+ callback = function()
+ if not api.nvim_buf_is_loaded(buf) then
+ return true
+ end
+
+ api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
+ local row = api.nvim_win_get_cursor(w)[1]
+ local pos = treeview:get(row)
+ api.nvim_buf_set_extmark(buf, treeview.ns, pos.lnum, pos.col, {
+ end_row = pos.end_lnum,
+ end_col = math.max(0, pos.end_col),
+ hl_group = 'Visual',
+ })
+
+ local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
+
+ -- Move the cursor if highlighted range is completely out of view
+ if pos.lnum < topline and pos.end_lnum < topline then
+ api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 })
+ elseif pos.lnum > botline and pos.end_lnum > botline then
+ api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 })
+ end
+ end,
+ })
+
+ api.nvim_create_autocmd('CursorMoved', {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not api.nvim_buf_is_loaded(b) then
+ return true
+ end
+
+ set_inspector_cursor(treeview, opts.lang, buf, b, w)
+ end,
+ })
+
+ api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not api.nvim_buf_is_loaded(b) then
+ return true
+ end
+
+ treeview = assert(TSTreeView:new(buf, opts.lang))
+ treeview:draw(b)
+ end,
+ })
+
+ api.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = b,
+ callback = function()
+ if not api.nvim_buf_is_loaded(buf) then
+ return true
+ end
+ api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
+ end,
+ })
+
+ api.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = buf,
+ callback = function()
+ if not api.nvim_buf_is_loaded(b) then
+ return true
+ end
+ api.nvim_buf_clear_namespace(b, treeview.ns, 0, -1)
+ end,
+ })
+
+ api.nvim_create_autocmd('BufHidden', {
+ group = group,
+ buffer = buf,
+ once = true,
+ callback = function()
+ close_win(w)
+ end,
+ })
+end
+
+local edit_ns = api.nvim_create_namespace('treesitter/dev-edit')
+
+---@param query_win integer
+---@param base_win integer
+---@param lang string
+local function update_editor_highlights(query_win, base_win, lang)
+ local base_buf = api.nvim_win_get_buf(base_win)
+ local query_buf = api.nvim_win_get_buf(query_win)
+ local parser = vim.treesitter.get_parser(base_buf, lang)
+ api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1)
+ local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n')
+
+ local ok_query, query = pcall(vim.treesitter.query.parse, lang, query_content)
+ if not ok_query then
+ return
+ end
+
+ local cursor_word = vim.fn.expand('<cword>') --[[@as string]]
+ -- Only highlight captures if the cursor is on a capture name
+ if cursor_word:find('^@') == nil then
+ return
+ end
+ -- Remove the '@' from the cursor word
+ cursor_word = cursor_word:sub(2)
+ local topline, botline = vim.fn.line('w0', base_win), vim.fn.line('w$', base_win)
+ for id, node in query:iter_captures(parser:trees()[1]:root(), base_buf, topline - 1, botline) do
+ local capture_name = query.captures[id]
+ if capture_name == cursor_word then
+ local lnum, col, end_lnum, end_col = node:range()
+ api.nvim_buf_set_extmark(base_buf, edit_ns, lnum, col, {
+ end_row = end_lnum,
+ end_col = end_col,
+ hl_group = 'Visual',
+ virt_text = {
+ { capture_name, 'Title' },
+ },
+ })
+ end
+ end
+end
+
+--- @private
+--- @param lang? string language to open the query editor for.
+function M.edit_query(lang)
+ local buf = api.nvim_get_current_buf()
+ local win = api.nvim_get_current_win()
+
+ -- Close any existing editor window
+ if vim.b[buf].dev_edit then
+ close_win(vim.b[buf].dev_edit)
+ end
+
+ local cmd = '60vnew'
+ -- If the inspector is open, place the editor above it.
+ local base_win = vim.b[buf].dev_base ---@type integer?
+ local base_buf = base_win and api.nvim_win_get_buf(base_win)
+ local inspect_win = base_buf and vim.b[base_buf].dev_inspect
+ if base_win and base_buf and api.nvim_win_is_valid(inspect_win) then
+ vim.api.nvim_set_current_win(inspect_win)
+ buf = base_buf
+ win = base_win
+ cmd = 'new'
+ end
+ vim.cmd(cmd)
+
+ local ok, parser = pcall(vim.treesitter.get_parser, buf, lang)
+ if not ok then
+ return nil, 'No parser available for the given buffer'
+ end
+ lang = parser:lang()
+
+ local query_win = api.nvim_get_current_win()
+ local query_buf = api.nvim_win_get_buf(query_win)
+
+ vim.b[buf].dev_edit = query_win
+ vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+ set_dev_properties(query_win, query_buf)
+
+ -- Note that omnifunc guesses the language based on the containing folder,
+ -- so we add the parser's language to the buffer's name so that omnifunc
+ -- can infer the language later.
+ api.nvim_buf_set_name(query_buf, string.format('%s/query_editor.scm', lang))
+
+ local group = api.nvim_create_augroup('treesitter/dev-edit', {})
+ api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave' }, {
+ group = group,
+ buffer = query_buf,
+ desc = 'Update query editor diagnostics when the query changes',
+ callback = function()
+ vim.treesitter.query.lint(query_buf, { langs = lang, clear = false })
+ end,
+ })
+ api.nvim_create_autocmd({ 'TextChanged', 'InsertLeave', 'CursorMoved', 'BufEnter' }, {
+ group = group,
+ buffer = query_buf,
+ desc = 'Update query editor highlights when the cursor moves',
+ callback = function()
+ if api.nvim_win_is_valid(win) then
+ update_editor_highlights(query_win, win, lang)
+ end
+ end,
+ })
+ api.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = query_buf,
+ desc = 'Clear highlights when leaving the query editor',
+ callback = function()
+ api.nvim_buf_clear_namespace(buf, edit_ns, 0, -1)
+ end,
+ })
+ api.nvim_create_autocmd('BufLeave', {
+ group = group,
+ buffer = buf,
+ desc = 'Clear the query editor highlights when leaving the source buffer',
+ callback = function()
+ if not api.nvim_buf_is_loaded(query_buf) then
+ return true
+ end
+
+ api.nvim_buf_clear_namespace(query_buf, edit_ns, 0, -1)
+ end,
+ })
+ api.nvim_create_autocmd('BufHidden', {
+ group = group,
+ buffer = buf,
+ desc = 'Close the editor window when the source buffer is hidden',
+ once = true,
+ callback = function()
+ close_win(query_win)
+ end,
+ })
+
+ api.nvim_buf_set_lines(query_buf, 0, -1, false, {
+ ';; Write queries here (see $VIMRUNTIME/queries/ for examples).',
+ ';; Move cursor to a capture ("@foo") to highlight matches in the source buffer.',
+ ';; Completion for grammar nodes is available (:help compl-omni)',
+ '',
+ '',
+ })
+ vim.cmd('normal! G')
+ vim.cmd.startinsert()
+end
+
+return M
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index c0a1eca0ce..ed1161e97f 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -2,30 +2,28 @@ local M = {}
local ts = vim.treesitter
local health = require('vim.health')
---- Lists the parsers currently installed
----
----@return string[] list of parser files
-function M.list_parsers()
- return vim.api.nvim_get_runtime_file('parser/*', true)
-end
-
--- Performs a healthcheck for treesitter integration
function M.check()
- local parsers = M.list_parsers()
+ local parsers = vim.api.nvim_get_runtime_file('parser/*', true)
- health.report_info(string.format('Nvim runtime ABI version: %d', ts.language_version))
+ health.info(string.format('Nvim runtime ABI version: %d', ts.language_version))
for _, parser in pairs(parsers) do
local parsername = vim.fn.fnamemodify(parser, ':t:r')
- local is_loadable, ret = pcall(ts.language.require_language, parsername)
+ local is_loadable, err_or_nil = pcall(ts.language.add, parsername)
- if not is_loadable or not ret then
- health.report_error(
- string.format('Parser "%s" failed to load (path: %s): %s', parsername, parser, ret or '?')
+ if not is_loadable then
+ health.error(
+ string.format(
+ 'Parser "%s" failed to load (path: %s): %s',
+ parsername,
+ parser,
+ err_or_nil or '?'
+ )
)
- elseif ret then
- local lang = ts.language.inspect_language(parsername)
- health.report_ok(
+ else
+ local lang = ts.language.inspect(parsername)
+ health.ok(
string.format('Parser: %-10s ABI: %d, path: %s', parsername, lang._abi_version, parser)
)
end
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index d77a0d0d03..496193c6ed 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -1,17 +1,34 @@
-local a = vim.api
-local query = require('vim.treesitter.query')
+local api = vim.api
+local query = vim.treesitter.query
+local Range = require('vim.treesitter._range')
+
+---@alias TSHlIter fun(end_line: integer|nil): integer, TSNode, TSMetadata
+
+---@class TSHighlightState
+---@field next_row integer
+---@field iter TSHlIter|nil
--- support reload for quick experimentation
---@class TSHighlighter
+---@field active table<integer,TSHighlighter>
+---@field bufnr integer
+---@field orig_spelloptions string
+---@field _highlight_states table<TSTree,TSHighlightState>
+---@field _queries table<string,TSHighlighterQuery>
+---@field tree LanguageTree
+---@field redraw_count integer
local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
TSHighlighter.__index = TSHighlighter
+--- @nodoc
TSHighlighter.active = TSHighlighter.active or {}
+---@class TSHighlighterQuery
+---@field _query Query|nil
+---@field hl_cache table<integer,integer>
local TSHighlighterQuery = {}
TSHighlighterQuery.__index = TSHighlighterQuery
-local ns = a.nvim_create_namespace('treesitter/highlighter')
+local ns = api.nvim_create_namespace('treesitter/highlighter')
---@private
function TSHighlighterQuery.new(lang, query_string)
@@ -22,7 +39,7 @@ function TSHighlighterQuery.new(lang, query_string)
local name = self._query.captures[capture]
local id = 0
if not vim.startswith(name, '_') then
- id = a.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang)
+ id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang)
end
rawset(table, capture, id)
@@ -31,22 +48,24 @@ function TSHighlighterQuery.new(lang, query_string)
})
if query_string then
- self._query = query.parse_query(lang, query_string)
+ self._query = query.parse(lang, query_string)
else
- self._query = query.get_query(lang, 'highlights')
+ self._query = query.get(lang, 'highlights')
end
return self
end
----@private
+---@package
function TSHighlighterQuery:query()
return self._query
end
---- Creates a new highlighter using @param tree
+---@package
+---
+--- Creates a highlighter for `tree`.
---
----@param tree LanguageTree |LanguageTree| parser object to use for highlighting
+---@param tree LanguageTree parser object to use for highlighting
---@param opts (table|nil) Configuration of the highlighter:
--- - queries table overwrite queries used by the highlighter
---@return TSHighlighter Created highlighter object
@@ -57,27 +76,37 @@ function TSHighlighter.new(tree, opts)
error('TSHighlighter can not be used with a string parser source.')
end
- opts = opts or {}
+ opts = opts or {} ---@type { queries: table<string,string> }
self.tree = tree
tree:register_cbs({
- on_changedtree = function(...)
- self:on_changedtree(...)
- end,
on_bytes = function(...)
self:on_bytes(...)
end,
- on_detach = function(...)
- self:on_detach(...)
+ on_detach = function()
+ self:on_detach()
end,
})
- self.bufnr = tree:source()
+ tree:register_cbs({
+ on_changedtree = function(...)
+ self:on_changedtree(...)
+ end,
+ on_child_removed = function(child)
+ child:for_each_tree(function(t)
+ self:on_changedtree(t:included_ranges(true))
+ end)
+ end,
+ }, true)
+
+ self.bufnr = tree:source() --[[@as integer]]
self.edit_count = 0
self.redraw_count = 0
self.line_count = {}
-- A map of highlight states.
-- This state is kept during rendering across each line update.
self._highlight_states = {}
+
+ ---@type table<string,TSHighlighterQuery>
self._queries = {}
-- Queries for a specific language can be overridden by a custom
@@ -103,7 +132,7 @@ function TSHighlighter.new(tree, opts)
vim.cmd.runtime({ 'syntax/synload.vim', bang = true })
end
- a.nvim_buf_call(self.bufnr, function()
+ api.nvim_buf_call(self.bufnr, function()
vim.opt_local.spelloptions:append('noplainbuffer')
end)
@@ -112,6 +141,7 @@ function TSHighlighter.new(tree, opts)
return self
end
+--- @nodoc
--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
if TSHighlighter.active[self.bufnr] then
@@ -122,12 +152,14 @@ function TSHighlighter:destroy()
vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
vim.b[self.bufnr].ts_highlight = nil
if vim.g.syntax_on == 1 then
- a.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr })
+ api.nvim_exec_autocmds('FileType', { group = 'syntaxset', buffer = self.bufnr })
end
end
end
----@private
+---@package
+---@param tstree TSTree
+---@return TSHighlightState
function TSHighlighter:get_highlight_state(tstree)
if not self._highlight_states[tstree] then
self._highlight_states[tstree] = {
@@ -144,28 +176,31 @@ function TSHighlighter:reset_highlight_state()
self._highlight_states = {}
end
----@private
+---@package
+---@param start_row integer
+---@param new_end integer
function TSHighlighter:on_bytes(_, _, start_row, _, _, _, _, _, new_end)
- a.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
+ api.nvim__buf_redraw_range(self.bufnr, start_row, start_row + new_end + 1)
end
----@private
+---@package
function TSHighlighter:on_detach()
self:destroy()
end
----@private
+---@package
+---@param changes Range6[]
function TSHighlighter:on_changedtree(changes)
- for _, ch in ipairs(changes or {}) do
- a.nvim__buf_redraw_range(self.bufnr, ch[1], ch[3] + 1)
+ for _, ch in ipairs(changes) do
+ api.nvim__buf_redraw_range(self.bufnr, ch[1], ch[4] + 1)
end
end
--- Gets the query used for @param lang
--
----@private
+---@package
---@param lang string Language used by the highlighter.
----@return Query
+---@return TSHighlighterQuery
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -174,7 +209,10 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
----@private
+---@param self TSHighlighter
+---@param buf integer
+---@param line integer
+---@param is_spell_nav boolean
local function on_line_impl(self, buf, line, is_spell_nav)
self.tree:for_each_tree(function(tstree, tree)
if not tstree then
@@ -203,45 +241,54 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end
while line >= state.next_row do
- local capture, node, metadata = state.iter()
+ local capture, node, metadata = state.iter(line)
- if capture == nil then
- break
+ local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
+ if node then
+ range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
end
-
- local start_row, start_col, end_row, end_col = node:range()
- local hl = highlighter_query.hl_cache[capture]
-
- local capture_name = highlighter_query:query().captures[capture]
- local spell = nil
- if capture_name == 'spell' then
- spell = true
- elseif capture_name == 'nospell' then
- spell = false
+ local start_row, start_col, end_row, end_col = Range.unpack4(range)
+
+ if capture then
+ local hl = highlighter_query.hl_cache[capture]
+
+ local capture_name = highlighter_query:query().captures[capture]
+ local spell = nil ---@type boolean?
+ if capture_name == 'spell' then
+ spell = true
+ elseif capture_name == 'nospell' then
+ spell = false
+ end
+
+ -- Give nospell a higher priority so it always overrides spell captures.
+ local spell_pri_offset = capture_name == 'nospell' and 1 or 0
+
+ if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
+ local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter)
+ + spell_pri_offset
+ api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
+ end_line = end_row,
+ end_col = end_col,
+ hl_group = hl,
+ ephemeral = true,
+ priority = priority,
+ conceal = metadata.conceal,
+ spell = spell,
+ })
+ end
end
- -- Give nospell a higher priority so it always overrides spell captures.
- local spell_pri_offset = capture_name == 'nospell' and 1 or 0
-
- if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
- a.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
- end_line = end_row,
- end_col = end_col,
- hl_group = hl,
- ephemeral = true,
- priority = (tonumber(metadata.priority) or 100) + spell_pri_offset, -- Low but leaves room below
- conceal = metadata.conceal,
- spell = spell,
- })
- end
if start_row > line then
state.next_row = start_row
end
end
- end, true)
+ end)
end
---@private
+---@param _win integer
+---@param buf integer
+---@param line integer
function TSHighlighter._on_line(_, _win, buf, line, _)
local self = TSHighlighter.active[buf]
if not self then
@@ -252,6 +299,9 @@ function TSHighlighter._on_line(_, _win, buf, line, _)
end
---@private
+---@param buf integer
+---@param srow integer
+---@param erow integer
function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
local self = TSHighlighter.active[buf]
if not self then
@@ -266,27 +316,22 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
end
---@private
-function TSHighlighter._on_buf(_, buf)
- local self = TSHighlighter.active[buf]
- if self then
- self.tree:parse()
- end
-end
-
----@private
-function TSHighlighter._on_win(_, _win, buf, _topline)
+---@param _win integer
+---@param buf integer
+---@param topline integer
+---@param botline integer
+function TSHighlighter._on_win(_, _win, buf, topline, botline)
local self = TSHighlighter.active[buf]
if not self then
return false
end
-
+ self.tree:parse({ topline, botline + 1 })
self:reset_highlight_state()
self.redraw_count = self.redraw_count + 1
return true
end
-a.nvim_set_decoration_provider(ns, {
- on_buf = TSHighlighter._on_buf,
+api.nvim_set_decoration_provider(ns, {
on_win = TSHighlighter._on_win,
on_line = TSHighlighter._on_line,
_on_spell_nav = TSHighlighter._on_spell_nav,
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 8634e53b7b..15bf666a1e 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -1,48 +1,132 @@
-local a = vim.api
+local api = vim.api
+---@class TSLanguageModule
local M = {}
---- Asserts that a parser for the language {lang} is installed.
+---@type table<string,string>
+local ft_to_lang = {
+ help = 'vimdoc',
+}
+
+--- Get the filetypes associated with the parser named {lang}.
+--- @param lang string Name of parser
+--- @return string[] filetypes
+function M.get_filetypes(lang)
+ local r = {} ---@type string[]
+ for ft, p in pairs(ft_to_lang) do
+ if p == lang then
+ r[#r + 1] = ft
+ end
+ end
+ return r
+end
+
+--- @param filetype string
+--- @return string|nil
+function M.get_lang(filetype)
+ if filetype == '' then
+ return
+ end
+ if ft_to_lang[filetype] then
+ return ft_to_lang[filetype]
+ end
+ -- support subfiletypes like html.glimmer
+ filetype = vim.split(filetype, '.', { plain = true })[1]
+ return ft_to_lang[filetype]
+end
+
+---@deprecated
+function M.require_language(lang, path, silent, symbol_name)
+ local opts = {
+ silent = silent,
+ path = path,
+ symbol_name = symbol_name,
+ }
+
+ if silent then
+ local installed = pcall(M.add, lang, opts)
+ return installed
+ end
+
+ M.add(lang, opts)
+ return true
+end
+
+---@class treesitter.RequireLangOpts
+---@field path? string
+---@field silent? boolean
+---@field filetype? string|string[]
+---@field symbol_name? string
+
+--- Load parser with name {lang}
---
--- Parsers are searched in the `parser` runtime directory, or the provided {path}
---
----@param lang string Language the parser should parse (alphanumerical and `_` only)
----@param path (string|nil) Optional path the parser is located at
----@param silent (boolean|nil) Don't throw an error if language not found
----@param symbol_name (string|nil) Internal symbol name for the language to load
----@return boolean If the specified language is installed
-function M.require_language(lang, path, silent, symbol_name)
+---@param lang string Name of the parser (alphanumerical and `_` only)
+---@param opts (table|nil) Options:
+--- - filetype (string|string[]) Default filetype the parser should be associated with.
+--- Defaults to {lang}.
+--- - path (string|nil) Optional path the parser is located at
+--- - symbol_name (string|nil) Internal symbol name for the language to load
+function M.add(lang, opts)
+ ---@cast opts treesitter.RequireLangOpts
+ opts = opts or {}
+ local path = opts.path
+ local filetype = opts.filetype or lang
+ local symbol_name = opts.symbol_name
+
+ vim.validate({
+ lang = { lang, 'string' },
+ path = { path, 'string', true },
+ symbol_name = { symbol_name, 'string', true },
+ filetype = { filetype, { 'string', 'table' }, true },
+ })
+
if vim._ts_has_language(lang) then
- return true
+ M.register(lang, filetype)
+ return
end
+
if path == nil then
if not (lang and lang:match('[%w_]+') == lang) then
- if silent then
- return false
- end
error("'" .. lang .. "' is not a valid language name")
end
local fname = 'parser/' .. lang .. '.*'
- local paths = a.nvim_get_runtime_file(fname, false)
+ local paths = api.nvim_get_runtime_file(fname, false)
if #paths == 0 then
- if silent then
- return false
- end
error("no parser for '" .. lang .. "' language, see :help treesitter-parsers")
end
path = paths[1]
end
- if silent then
- return pcall(function()
- vim._ts_add_language(path, lang, symbol_name)
- end)
- else
- vim._ts_add_language(path, lang, symbol_name)
+ vim._ts_add_language(path, lang, symbol_name)
+ M.register(lang, filetype)
+end
+
+--- @param x string|string[]
+--- @return string[]
+local function ensure_list(x)
+ if type(x) == 'table' then
+ return x
end
+ return { x }
+end
- return true
+--- Register a parser named {lang} to be used for {filetype}(s).
+--- @param lang string Name of parser
+--- @param filetype string|string[] Filetype(s) to associate with lang
+function M.register(lang, filetype)
+ vim.validate({
+ lang = { lang, 'string' },
+ filetype = { filetype, { 'string', 'table' } },
+ })
+
+ for _, f in ipairs(ensure_list(filetype)) do
+ if f ~= '' then
+ ft_to_lang[f] = lang
+ end
+ end
end
--- Inspects the provided language.
@@ -51,9 +135,19 @@ end
---
---@param lang string Language
---@return table
-function M.inspect_language(lang)
- M.require_language(lang)
+function M.inspect(lang)
+ M.add(lang)
return vim._ts_inspect_language(lang)
end
+---@deprecated
+function M.inspect_language(...)
+ vim.deprecate(
+ 'vim.treesitter.language.inspect_language()',
+ 'vim.treesitter.language.inspect()',
+ '0.10'
+ )
+ return M.inspect(...)
+end
+
return M
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index a1e96f8ef2..0171b416cd 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1,85 +1,257 @@
-local a = vim.api
+--- @defgroup lua-treesitter-languagetree
+---
+--- @brief A \*LanguageTree\* contains a tree of parsers: the root treesitter parser for {lang} and
+--- any "injected" language parsers, which themselves may inject other languages, recursively.
+--- For example a Lua buffer containing some Vimscript commands needs multiple parsers to fully
+--- understand its contents.
+---
+--- To create a LanguageTree (parser object) for a given buffer and language, use:
+---
+--- ```lua
+--- local parser = vim.treesitter.get_parser(bufnr, lang)
+--- ```
+---
+--- (where `bufnr=0` means current buffer). `lang` defaults to 'filetype'.
+--- Note: currently the parser is retained for the lifetime of a buffer but this may change;
+--- a plugin should keep a reference to the parser object if it wants incremental updates.
+---
+--- Whenever you need to access the current syntax tree, parse the buffer:
+---
+--- ```lua
+--- local tree = parser:parse({ start_row, end_row })
+--- ```
+---
+--- This returns a table of immutable |treesitter-tree| objects representing the current state of
+--- the buffer. When the plugin wants to access the state after a (possible) edit it must call
+--- `parse()` again. If the buffer wasn't edited, the same tree will be returned again without extra
+--- work. If the buffer was parsed before, incremental parsing will be done of the changed parts.
+---
+--- Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback, you must call
+--- |vim.treesitter.get_parser()| before you register your callback. But preferably parsing
+--- shouldn't be done directly in the change callback anyway as they will be very frequent. Rather
+--- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent
+--- updates.
+---
+
+-- Debugging:
+--
+-- vim.g.__ts_debug levels:
+-- - 1. Messages from languagetree.lua
+-- - 2. Parse messages from treesitter
+-- - 2. Lex messages from treesitter
+--
+-- Log file can be found in stdpath('log')/treesitter.log
+
local query = require('vim.treesitter.query')
local language = require('vim.treesitter.language')
+local Range = require('vim.treesitter._range')
+
+---@alias TSCallbackName
+---| 'changedtree'
+---| 'bytes'
+---| 'detach'
+---| 'child_added'
+---| 'child_removed'
+
+---@alias TSCallbackNameOn
+---| 'on_changedtree'
+---| 'on_bytes'
+---| 'on_detach'
+---| 'on_child_added'
+---| 'on_child_removed'
+
+--- @type table<TSCallbackNameOn,TSCallbackName>
+local TSCallbackNames = {
+ on_changedtree = 'changedtree',
+ on_bytes = 'bytes',
+ on_detach = 'detach',
+ on_child_added = 'child_added',
+ on_child_removed = 'child_removed',
+}
---@class LanguageTree
----@field _callbacks function[] Callback handlers
----@field _children LanguageTree[] Injected languages
----@field _injection_query table Queries defining injected languages
----@field _opts table Options
----@field _parser userdata Parser for language
----@field _regions table List of regions this tree should manage and parse
----@field _lang string Language name
----@field _regions table
----@field _source (number|string) Buffer or string to parse
----@field _trees userdata[] Reference to parsed |tstree| (one for each language)
----@field _valid boolean If the parsed tree is valid
-
+---@field private _callbacks table<TSCallbackName,function[]> Callback handlers
+---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive)
+---@field private _children table<string,LanguageTree> Injected languages
+---@field private _injection_query Query Queries defining injected languages
+---@field private _injections_processed boolean
+---@field private _opts table Options
+---@field private _parser TSParser Parser for language
+---@field private _has_regions boolean
+---@field private _regions table<integer, Range6[]>?
+---List of regions this tree should manage and parse. If nil then regions are
+---taken from _trees. This is mostly a short-lived cache for included_regions()
+---@field private _lang string Language name
+---@field private _parent_lang? string Parent language name
+---@field private _source (integer|string) Buffer or string to parse
+---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language).
+---Each key is the index of region, which is synced with _regions and _valid.
+---@field private _valid boolean|table<integer,boolean> If the parsed tree is valid
+---@field private _logger? fun(logtype: string, msg: string)
+---@field private _logfile? file*
local LanguageTree = {}
+
+---@class LanguageTreeOpts
+---@field queries table<string,string> -- Deprecated
+---@field injections table<string,string>
+
LanguageTree.__index = LanguageTree
---- A |LanguageTree| holds the treesitter parser for a given language {lang} used
---- to parse a buffer. As the buffer may contain injected languages, the LanguageTree
---- needs to store parsers for these child languages as well (which in turn may contain
---- child languages themselves, hence the name).
----
----@param source (number|string) Buffer or a string of text to parse
----@param lang string Root language this tree represents
----@param opts (table|nil) Optional keyword arguments:
---- - injections table Mapping language to injection query strings.
---- This is useful for overriding the built-in
---- runtime file searching for the injection language
---- query per language.
----@return LanguageTree |LanguageTree| parser object
-function LanguageTree.new(source, lang, opts)
- language.require_language(lang)
+--- @package
+---
+--- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any
+--- "injected" language parsers, which themselves may inject other languages, recursively.
+---
+---@param source (integer|string) Buffer or text string to parse
+---@param lang string Root language of this tree
+---@param opts (table|nil) Optional arguments:
+--- - injections table Map of language to injection query strings. Overrides the
+--- built-in runtime file searching for language injections.
+---@param parent_lang? string Parent language name of this tree
+---@return LanguageTree parser object
+function LanguageTree.new(source, lang, opts, parent_lang)
+ language.add(lang)
+ ---@type LanguageTreeOpts
opts = opts or {}
- if opts.queries then
- a.nvim_err_writeln("'queries' is no longer supported. Use 'injections' now")
- opts.injections = opts.queries
+ if source == 0 then
+ source = vim.api.nvim_get_current_buf()
end
local injections = opts.injections or {}
- local self = setmetatable({
+
+ --- @type LanguageTree
+ local self = {
_source = source,
_lang = lang,
+ _parent_lang = parent_lang,
_children = {},
- _regions = {},
_trees = {},
_opts = opts,
- _injection_query = injections[lang] and query.parse_query(lang, injections[lang])
- or query.get_query(lang, 'injections'),
+ _injection_query = injections[lang] and query.parse(lang, injections[lang])
+ or query.get(lang, 'injections'),
+ _has_regions = false,
+ _injections_processed = false,
_valid = false,
_parser = vim._create_ts_parser(lang),
- _callbacks = {
- changedtree = {},
- bytes = {},
- detach = {},
- child_added = {},
- child_removed = {},
- },
- }, LanguageTree)
+ _callbacks = {},
+ _callbacks_rec = {},
+ }
+
+ setmetatable(self, LanguageTree)
+
+ if vim.g.__ts_debug and type(vim.g.__ts_debug) == 'number' then
+ self:_set_logger()
+ self:_log('START')
+ end
+
+ for _, name in pairs(TSCallbackNames) do
+ self._callbacks[name] = {}
+ self._callbacks_rec[name] = {}
+ end
return self
end
+--- @private
+function LanguageTree:_set_logger()
+ local source = self:source()
+ source = type(source) == 'string' and 'text' or tostring(source)
+
+ local lang = self:lang()
+
+ vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+ local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'treesitter.log')
+
+ local logfile, openerr = io.open(logfilename, 'a+')
+
+ if not logfile or openerr then
+ error(string.format('Could not open file (%s) for logging: %s', logfilename, openerr))
+ return
+ end
+
+ self._logfile = logfile
+
+ self._logger = function(logtype, msg)
+ self._logfile:write(string.format('%s:%s:(%s) %s\n', source, lang, logtype, msg))
+ self._logfile:flush()
+ end
+
+ local log_lex = vim.g.__ts_debug >= 3
+ local log_parse = vim.g.__ts_debug >= 2
+ self._parser:_set_logger(log_lex, log_parse, self._logger)
+end
+
+---Measure execution time of a function
+---@generic R1, R2, R3
+---@param f fun(): R1, R2, R2
+---@return number, R1, R2, R3
+local function tcall(f, ...)
+ local start = vim.uv.hrtime()
+ ---@diagnostic disable-next-line
+ local r = { f(...) }
+ --- @type number
+ local duration = (vim.uv.hrtime() - start) / 1000000
+ return duration, unpack(r)
+end
+
+---@private
+---@vararg any
+function LanguageTree:_log(...)
+ if not self._logger then
+ return
+ end
+
+ if not vim.g.__ts_debug or vim.g.__ts_debug < 1 then
+ return
+ end
+
+ local args = { ... }
+ if type(args[1]) == 'function' then
+ args = { args[1]() }
+ end
+
+ local info = debug.getinfo(2, 'nl')
+ local nregions = vim.tbl_count(self:included_regions())
+ local prefix =
+ string.format('%s:%d: (#regions=%d) ', info.name or '???', info.currentline or 0, nregions)
+
+ local msg = { prefix }
+ for _, x in ipairs(args) do
+ if type(x) == 'string' then
+ msg[#msg + 1] = x
+ else
+ msg[#msg + 1] = vim.inspect(x, { newline = ' ', indent = '' })
+ end
+ end
+ self._logger('nvim', table.concat(msg, ' '))
+end
+
--- Invalidates this parser and all its children
+---@param reload boolean|nil
function LanguageTree:invalidate(reload)
self._valid = false
-- buffer was reloaded, reparse all trees
if reload then
+ for _, t in pairs(self._trees) do
+ self:_do_callback('changedtree', t:included_ranges(true), t)
+ end
self._trees = {}
end
- for _, child in ipairs(self._children) do
+ for _, child in pairs(self._children) do
child:invalidate(reload)
end
end
---- Returns all trees this language tree contains.
+--- Returns all trees of the regions parsed by this parser.
--- Does not include child languages.
+--- The result is list-like if
+--- * this LanguageTree is the root, in which case the result is empty or a singleton list; or
+--- * the root LanguageTree is fully parsed.
+---
+---@return table<integer, TSTree>
function LanguageTree:trees()
return self._trees
end
@@ -89,11 +261,39 @@ function LanguageTree:lang()
return self._lang
end
---- Determines whether this tree is valid.
---- If the tree is invalid, call `parse()`.
---- This will return the updated tree.
-function LanguageTree:is_valid()
- return self._valid
+--- Returns whether this LanguageTree is valid, i.e., |LanguageTree:trees()| reflects the latest
+--- state of the source. If invalid, user should call |LanguageTree:parse()|.
+---@param exclude_children boolean|nil whether to ignore the validity of children (default `false`)
+---@return boolean
+function LanguageTree:is_valid(exclude_children)
+ local valid = self._valid
+
+ if type(valid) == 'table' then
+ for i, _ in pairs(self:included_regions()) do
+ if not valid[i] then
+ return false
+ end
+ end
+ end
+
+ if not exclude_children then
+ if not self._injections_processed then
+ return false
+ end
+
+ for _, child in pairs(self._children) do
+ if not child:is_valid(exclude_children) then
+ return false
+ end
+ end
+ end
+
+ if type(valid) == 'boolean' then
+ return valid
+ end
+
+ self._valid = true
+ return true
end
--- Returns a map of language to child tree.
@@ -106,50 +306,77 @@ function LanguageTree:source()
return self._source
end
---- Parses all defined regions using a treesitter parser
---- for the language this tree represents.
---- This will run the injection query for this language to
---- determine if any child languages should be created.
----
----@return userdata[] Table of parsed |tstree|
----@return table Change list
-function LanguageTree:parse()
- if self._valid then
- return self._trees
+--- @param region Range6[]
+--- @param range? boolean|Range
+--- @return boolean
+local function intercepts_region(region, range)
+ if #region == 0 then
+ return true
+ end
+
+ if range == nil then
+ return false
+ end
+
+ if type(range) == 'boolean' then
+ return range
end
- local parser = self._parser
+ for _, r in ipairs(region) do
+ if Range.intercepts(r, range) then
+ return true
+ end
+ end
+
+ return false
+end
+
+--- @private
+--- @param range boolean|Range?
+--- @return Range6[] changes
+--- @return integer no_regions_parsed
+--- @return number total_parse_time
+function LanguageTree:_parse_regions(range)
local changes = {}
+ local no_regions_parsed = 0
+ local total_parse_time = 0
- local old_trees = self._trees
- self._trees = {}
+ if type(self._valid) ~= 'table' then
+ self._valid = {}
+ end
-- If there are no ranges, set to an empty list
-- so the included ranges in the parser are cleared.
- if self._regions and #self._regions > 0 then
- for i, ranges in ipairs(self._regions) do
- local old_tree = old_trees[i]
- parser:set_included_ranges(ranges)
+ for i, ranges in pairs(self:included_regions()) do
+ if not self._valid[i] and intercepts_region(ranges, range) then
+ self._parser:set_included_ranges(ranges)
+ local parse_time, tree, tree_changes =
+ tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
- local tree, tree_changes = parser:parse(old_tree, self._source)
- self:_do_callback('changedtree', tree_changes, tree)
+ -- Pass ranges if this is an initial parse
+ local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true)
- table.insert(self._trees, tree)
+ self:_do_callback('changedtree', cb_changes, tree)
+ self._trees[i] = tree
vim.list_extend(changes, tree_changes)
- end
- else
- local tree, tree_changes = parser:parse(old_trees[1], self._source)
- self:_do_callback('changedtree', tree_changes, tree)
- table.insert(self._trees, tree)
- vim.list_extend(changes, tree_changes)
+ total_parse_time = total_parse_time + parse_time
+ no_regions_parsed = no_regions_parsed + 1
+ self._valid[i] = true
+ end
end
- local injections_by_lang = self:_get_injections()
- local seen_langs = {}
+ return changes, no_regions_parsed, total_parse_time
+end
+
+--- @private
+--- @return number
+function LanguageTree:_add_injections()
+ local seen_langs = {} ---@type table<string,boolean>
- for lang, injection_ranges in pairs(injections_by_lang) do
- local has_lang = language.require_language(lang, nil, true)
+ local query_time, injections_by_lang = tcall(self._get_injections, self)
+ for lang, injection_regions in pairs(injections_by_lang) do
+ local has_lang = pcall(language.add, lang)
-- Child language trees should just be ignored if not found, since
-- they can depend on the text of a node. Intermediate strings
@@ -161,16 +388,7 @@ function LanguageTree:parse()
child = self:add_child(lang)
end
- child:set_included_regions(injection_ranges)
-
- local _, child_changes = child:parse()
-
- -- Propagate any child changes so they are included in the
- -- the change list for the callback.
- if child_changes then
- vim.list_extend(changes, child_changes)
- end
-
+ child:set_included_regions(injection_regions)
seen_langs[lang] = true
end
end
@@ -181,16 +399,71 @@ function LanguageTree:parse()
end
end
- self._valid = true
+ return query_time
+end
+
+--- Recursively parse all regions in the language tree using |treesitter-parsers|
+--- for the corresponding languages and run injection queries on the parsed trees
+--- to determine whether child trees should be created and parsed.
+---
+--- Any region with empty range (`{}`, typically only the root tree) is always parsed;
+--- otherwise (typically injections) only if it intersects {range} (or if {range} is `true`).
+---
+--- @param range boolean|Range|nil: Parse this range in the parser's source.
+--- Set to `true` to run a complete parse of the source (Note: Can be slow!)
+--- Set to `false|nil` to only parse regions with empty ranges (typically
+--- only the root tree without injections).
+--- @return table<integer, TSTree>
+function LanguageTree:parse(range)
+ if self:is_valid() then
+ self:_log('valid')
+ return self._trees
+ end
- return self._trees, changes
+ local changes --- @type Range6[]?
+
+ -- Collect some stats
+ local no_regions_parsed = 0
+ local query_time = 0
+ local total_parse_time = 0
+
+ --- At least 1 region is invalid
+ if not self:is_valid(true) then
+ changes, no_regions_parsed, total_parse_time = self:_parse_regions(range)
+ -- Need to run injections when we parsed something
+ if no_regions_parsed > 0 then
+ self._injections_processed = false
+ end
+ end
+
+ if not self._injections_processed and range ~= false and range ~= nil then
+ query_time = self:_add_injections()
+ self._injections_processed = true
+ end
+
+ self:_log({
+ changes = changes and #changes > 0 and changes or nil,
+ regions_parsed = no_regions_parsed,
+ parse_time = total_parse_time,
+ query_time = query_time,
+ range = range,
+ })
+
+ for _, child in pairs(self._children) do
+ child:parse(range)
+ end
+
+ return self._trees
end
+---@deprecated Misleading name. Use `LanguageTree:children()` (non-recursive) instead,
+--- add recursion yourself if needed.
--- Invokes the callback for each |LanguageTree| and its children recursively
---
----@param fn function(tree: LanguageTree, lang: string)
----@param include_self boolean Whether to include the invoking tree in the results
+---@param fn fun(tree: LanguageTree, lang: string)
+---@param include_self boolean|nil Whether to include the invoking tree in the results
function LanguageTree:for_each_child(fn, include_self)
+ vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11')
if include_self then
fn(self, self._lang)
end
@@ -204,9 +477,9 @@ end
---
--- Note: This includes the invoking tree's child trees as well.
---
----@param fn function(tree: TSTree, languageTree: LanguageTree)
+---@param fn fun(tree: TSTree, ltree: LanguageTree)
function LanguageTree:for_each_tree(fn)
- for _, tree in ipairs(self._trees) do
+ for _, tree in pairs(self._trees) do
fn(tree, self)
end
@@ -221,15 +494,20 @@ end
---
---@private
---@param lang string Language to add.
----@return LanguageTree Injected |LanguageTree|
+---@return LanguageTree injected
function LanguageTree:add_child(lang)
if self._children[lang] then
self:remove_child(lang)
end
- self._children[lang] = LanguageTree.new(self._source, lang, self._opts)
+ local child = LanguageTree.new(self._source, lang, self._opts, self:lang())
- self:invalidate()
+ -- Inherit recursive callbacks
+ for nm, cb in pairs(self._callbacks_rec) do
+ vim.list_extend(child._callbacks_rec[nm], cb)
+ end
+
+ self._children[lang] = child
self:_do_callback('child_added', self._children[lang])
return self._children[lang]
@@ -245,7 +523,6 @@ function LanguageTree:remove_child(lang)
if child then
self._children[lang] = nil
child:destroy()
- self:invalidate()
self:_do_callback('child_removed', child)
end
end
@@ -258,11 +535,60 @@ end
--- `remove_child` must be called on the parent to remove it.
function LanguageTree:destroy()
-- Cleanup here
- for _, child in ipairs(self._children) do
+ for _, child in pairs(self._children) do
child:destroy()
end
end
+---@param region Range6[]
+local function region_tostr(region)
+ if #region == 0 then
+ return '[]'
+ end
+ local srow, scol = region[1][1], region[1][2]
+ local erow, ecol = region[#region][4], region[#region][5]
+ return string.format('[%d:%d-%d:%d]', srow, scol, erow, ecol)
+end
+
+---@private
+---Iterate through all the regions. fn returns a boolean to indicate if the
+---region is valid or not.
+---@param fn fun(index: integer, region: Range6[]): boolean
+function LanguageTree:_iter_regions(fn)
+ if not self._valid then
+ return
+ end
+
+ local was_valid = type(self._valid) ~= 'table'
+
+ if was_valid then
+ self:_log('was valid', self._valid)
+ self._valid = {}
+ end
+
+ local all_valid = true
+
+ for i, region in pairs(self:included_regions()) do
+ if was_valid or self._valid[i] then
+ self._valid[i] = fn(i, region)
+ if not self._valid[i] then
+ self:_log(function()
+ return 'invalidating region', i, region_tostr(region)
+ end)
+ end
+ end
+
+ if not self._valid[i] then
+ all_valid = false
+ end
+ end
+
+ -- Compress the valid value to 'true' if there are no invalid regions
+ if all_valid then
+ self._valid = all_valid
+ end
+end
+
--- Sets the included regions that should be parsed by this |LanguageTree|.
--- A region is a set of nodes and/or ranges that will be parsed in the same context.
---
@@ -277,151 +603,253 @@ end
--- This allows for embedded languages to be parsed together across different
--- nodes, which is useful for templating languages like ERB and EJS.
---
---- Note: This call invalidates the tree and requires it to be parsed again.
----
---@private
----@param regions table List of regions this tree should manage and parse.
-function LanguageTree:set_included_regions(regions)
+---@param new_regions (Range4|Range6|TSNode)[][] List of regions this tree should manage and parse.
+function LanguageTree:set_included_regions(new_regions)
+ self._has_regions = true
+
-- Transform the tables from 4 element long to 6 element long (with byte offset)
- for _, region in ipairs(regions) do
+ for _, region in ipairs(new_regions) do
for i, range in ipairs(region) do
if type(range) == 'table' and #range == 4 then
- local start_row, start_col, end_row, end_col = unpack(range)
- local start_byte = 0
- local end_byte = 0
- -- TODO(vigoux): proper byte computation here, and account for EOL ?
- if type(self._source) == 'number' then
- -- Easy case, this is a buffer parser
- start_byte = a.nvim_buf_get_offset(self._source, start_row) + start_col
- end_byte = a.nvim_buf_get_offset(self._source, end_row) + end_col
- elseif type(self._source) == 'string' then
- -- string parser, single `\n` delimited string
- start_byte = vim.fn.byteidx(self._source, start_col)
- end_byte = vim.fn.byteidx(self._source, end_col)
- end
-
- region[i] = { start_row, start_col, start_byte, end_row, end_col, end_byte }
+ region[i] = Range.add_bytes(self._source, range --[[@as Range4]])
+ elseif type(range) == 'userdata' then
+ region[i] = { range:range(true) }
end
end
end
- self._regions = regions
- -- Trees are no longer valid now that we have changed regions.
- -- TODO(vigoux,steelsojka): Look into doing this smarter so we can use some of the
- -- old trees for incremental parsing. Currently, this only
- -- affects injected languages.
- self._trees = {}
- self:invalidate()
+ -- included_regions is not guaranteed to be list-like, but this is still sound, i.e. if
+ -- new_regions is different from included_regions, then outdated regions in included_regions are
+ -- invalidated. For example, if included_regions = new_regions ++ hole ++ outdated_regions, then
+ -- outdated_regions is invalidated by _iter_regions in else branch.
+ if #self:included_regions() ~= #new_regions then
+ -- TODO(lewis6991): inefficient; invalidate trees incrementally
+ for _, t in pairs(self._trees) do
+ self:_do_callback('changedtree', t:included_ranges(true), t)
+ end
+ self._trees = {}
+ self:invalidate()
+ else
+ self:_iter_regions(function(i, region)
+ return vim.deep_equal(new_regions[i], region)
+ end)
+ end
+
+ self._regions = new_regions
end
---- Gets the set of included regions
+---Gets the set of included regions managed by this LanguageTree. This can be different from the
+---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions
+---outside the requested range.
+---@return table<integer, Range6[]>
function LanguageTree:included_regions()
- return self._regions
+ if self._regions then
+ return self._regions
+ end
+
+ if not self._has_regions then
+ -- treesitter.c will default empty ranges to { -1, -1, -1, -1, -1, -1} (the full range)
+ return { {} }
+ end
+
+ local regions = {} ---@type Range6[][]
+ for i, _ in pairs(self._trees) do
+ regions[i] = self._trees[i]:included_ranges(true)
+ end
+
+ self._regions = regions
+ return regions
+end
+
+---@param node TSNode
+---@param source string|integer
+---@param metadata TSMetadata
+---@param include_children boolean
+---@return Range6[]
+local function get_node_ranges(node, source, metadata, include_children)
+ local range = vim.treesitter.get_range(node, source, metadata)
+ local child_count = node:named_child_count()
+
+ if include_children or child_count == 0 then
+ return { range }
+ end
+
+ local ranges = {} ---@type Range6[]
+
+ local srow, scol, sbyte, erow, ecol, ebyte = Range.unpack6(range)
+
+ -- We are excluding children so we need to mask out their ranges
+ for i = 0, child_count - 1 do
+ local child = assert(node:named_child(i))
+ local c_srow, c_scol, c_sbyte, c_erow, c_ecol, c_ebyte = child:range(true)
+ if c_srow > srow or c_scol > scol then
+ ranges[#ranges + 1] = { srow, scol, sbyte, c_srow, c_scol, c_sbyte }
+ end
+ srow = c_erow
+ scol = c_ecol
+ sbyte = c_ebyte
+ end
+
+ if erow > srow or ecol > scol then
+ ranges[#ranges + 1] = Range.add_bytes(source, { srow, scol, sbyte, erow, ecol, ebyte })
+ end
+
+ return ranges
+end
+
+---@class TSInjectionElem
+---@field combined boolean
+---@field regions Range6[][]
+
+---@alias TSInjection table<string,table<integer,TSInjectionElem>>
+
+---@param t table<integer,TSInjection>
+---@param tree_index integer
+---@param pattern integer
+---@param lang string
+---@param combined boolean
+---@param ranges Range6[]
+local function add_injection(t, tree_index, pattern, lang, combined, ranges)
+ if #ranges == 0 then
+ -- Make sure not to add an empty range set as this is interpreted to mean the whole buffer.
+ return
+ end
+
+ -- Each tree index should be isolated from the other nodes.
+ if not t[tree_index] then
+ t[tree_index] = {}
+ end
+
+ if not t[tree_index][lang] then
+ t[tree_index][lang] = {}
+ end
+
+ -- Key this by pattern. If combined is set to true all captures of this pattern
+ -- will be parsed by treesitter as the same "source".
+ -- If combined is false, each "region" will be parsed as a single source.
+ if not t[tree_index][lang][pattern] then
+ t[tree_index][lang][pattern] = { combined = combined, regions = {} }
+ end
+
+ table.insert(t[tree_index][lang][pattern].regions, ranges)
+end
+
+-- TODO(clason): replace by refactored `ts.has_parser` API (without registering)
+--- The result of this function is cached to prevent nvim_get_runtime_file from being
+--- called too often
+--- @param lang string parser name
+--- @return boolean # true if parser for {lang} exists on rtp
+local has_parser = vim.func._memoize(1, function(lang)
+ return vim._ts_has_language(lang)
+ or #vim.api.nvim_get_runtime_file('parser/' .. lang .. '.*', false) > 0
+end)
+
+--- Return parser name for language (if exists) or filetype (if registered and exists).
+--- Also attempts with the input lower-cased.
+---
+---@param alias string language or filetype name
+---@return string? # resolved parser name
+local function resolve_lang(alias)
+ if has_parser(alias) then
+ return alias
+ end
+
+ if has_parser(alias:lower()) then
+ return alias:lower()
+ end
+
+ local lang = vim.treesitter.language.get_lang(alias)
+ if lang and has_parser(lang) then
+ return lang
+ end
+
+ lang = vim.treesitter.language.get_lang(alias:lower())
+ if lang and has_parser(lang) then
+ return lang
+ end
end
---@private
-local function get_range_from_metadata(node, id, metadata)
- if metadata[id] and metadata[id].range then
- return metadata[id].range
+--- Extract injections according to:
+--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
+---@param match table<integer,TSNode>
+---@param metadata TSMetadata
+---@return string?, boolean, Range6[]
+function LanguageTree:_get_injection(match, metadata)
+ local ranges = {} ---@type Range6[]
+ local combined = metadata['injection.combined'] ~= nil
+ local injection_lang = metadata['injection.language'] --[[@as string?]]
+ local lang = metadata['injection.self'] ~= nil and self:lang()
+ or metadata['injection.parent'] ~= nil and self._parent_lang
+ or (injection_lang and resolve_lang(injection_lang))
+ local include_children = metadata['injection.include-children'] ~= nil
+
+ for id, node in pairs(match) do
+ local name = self._injection_query.captures[id]
+ -- Lang should override any other language tag
+ if name == 'injection.language' then
+ local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
+ lang = resolve_lang(text)
+ elseif name == 'injection.content' then
+ ranges = get_node_ranges(node, self._source, metadata[id], include_children)
+ end
end
- return { node:range() }
+
+ return lang, combined, ranges
end
---- Gets language injection points by language.
+--- Can't use vim.tbl_flatten since a range is just a table.
+---@param regions Range6[][]
+---@return Range6[]
+local function combine_regions(regions)
+ local result = {} ---@type Range6[]
+ for _, region in ipairs(regions) do
+ for _, range in ipairs(region) do
+ result[#result + 1] = range
+ end
+ end
+ return result
+end
+
+--- Gets language injection regions by language.
---
--- This is where most of the injection processing occurs.
---
--- TODO: Allow for an offset predicate to tailor the injection range
--- instead of using the entire nodes range.
----@private
+--- @private
+--- @return table<string, Range6[][]>
function LanguageTree:_get_injections()
if not self._injection_query then
return {}
end
+ ---@type table<integer,TSInjection>
local injections = {}
- for tree_index, tree in ipairs(self._trees) do
+ for index, tree in pairs(self._trees) do
local root_node = tree:root()
local start_line, _, end_line, _ = root_node:range()
for pattern, match, metadata in
self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
do
- local lang = nil
- local ranges = {}
- local combined = metadata.combined
-
- -- Directives can configure how injections are captured as well as actual node captures.
- -- This allows more advanced processing for determining ranges and language resolution.
- if metadata.content then
- local content = metadata.content
-
- -- Allow for captured nodes to be used
- if type(content) == 'number' then
- content = { match[content]:range() }
- end
-
- if type(content) == 'table' and #content >= 4 then
- vim.list_extend(ranges, content)
- end
+ local lang, combined, ranges = self:_get_injection(match, metadata)
+ if lang then
+ add_injection(injections, index, pattern, lang, combined, ranges)
+ else
+ self:_log('match from injection query failed for pattern', pattern)
end
-
- if metadata.language then
- lang = metadata.language
- end
-
- -- You can specify the content and language together
- -- using a tag with the language, for example
- -- @javascript
- for id, node in pairs(match) do
- local name = self._injection_query.captures[id]
-
- -- Lang should override any other language tag
- if name == 'language' and not lang then
- lang = query.get_node_text(node, self._source)
- elseif name == 'combined' then
- combined = true
- elseif name == 'content' and #ranges == 0 then
- table.insert(ranges, get_range_from_metadata(node, id, metadata))
- -- Ignore any tags that start with "_"
- -- Allows for other tags to be used in matches
- elseif string.sub(name, 1, 1) ~= '_' then
- if not lang then
- lang = name
- end
-
- if #ranges == 0 then
- table.insert(ranges, get_range_from_metadata(node, id, metadata))
- end
- end
- end
-
- -- Each tree index should be isolated from the other nodes.
- if not injections[tree_index] then
- injections[tree_index] = {}
- end
-
- if not injections[tree_index][lang] then
- injections[tree_index][lang] = {}
- end
-
- -- Key this by pattern. If combined is set to true all captures of this pattern
- -- will be parsed by treesitter as the same "source".
- -- If combined is false, each "region" will be parsed as a single source.
- if not injections[tree_index][lang][pattern] then
- injections[tree_index][lang][pattern] = { combined = combined, regions = {} }
- end
-
- table.insert(injections[tree_index][lang][pattern].regions, ranges)
end
end
+ ---@type table<string,Range6[][]>
local result = {}
-- Generate a map by lang of node lists.
-- Each list is a set of ranges that should be parsed together.
- for _, lang_map in ipairs(injections) do
+ for _, lang_map in pairs(injections) do
for lang, patterns in pairs(lang_map) do
if not result[lang] then
result[lang] = {}
@@ -429,12 +857,9 @@ function LanguageTree:_get_injections()
for _, entry in pairs(patterns) do
if entry.combined then
- local regions = vim.tbl_map(function(e)
- return vim.tbl_flatten(e)
- end, entry.regions)
- table.insert(result[lang], regions)
+ table.insert(result[lang], combine_regions(entry.regions))
else
- for _, ranges in ipairs(entry.regions) do
+ for _, ranges in pairs(entry.regions) do
table.insert(result[lang], ranges)
end
end
@@ -446,13 +871,94 @@ function LanguageTree:_get_injections()
end
---@private
+---@param cb_name TSCallbackName
function LanguageTree:_do_callback(cb_name, ...)
for _, cb in ipairs(self._callbacks[cb_name]) do
cb(...)
end
+ for _, cb in ipairs(self._callbacks_rec[cb_name]) do
+ cb(...)
+ end
end
----@private
+---@package
+function LanguageTree:_edit(
+ start_byte,
+ end_byte_old,
+ end_byte_new,
+ start_row,
+ start_col,
+ end_row_old,
+ end_col_old,
+ end_row_new,
+ end_col_new
+)
+ for _, tree in pairs(self._trees) do
+ tree:edit(
+ start_byte,
+ end_byte_old,
+ end_byte_new,
+ start_row,
+ start_col,
+ end_row_old,
+ end_col_old,
+ end_row_new,
+ end_col_new
+ )
+ end
+
+ self._regions = nil
+
+ local changed_range = {
+ start_row,
+ start_col,
+ start_byte,
+ end_row_old,
+ end_col_old,
+ end_byte_old,
+ }
+
+ -- Validate regions after editing the tree
+ self:_iter_regions(function(_, region)
+ if #region == 0 then
+ -- empty region, use the full source
+ return false
+ end
+ for _, r in ipairs(region) do
+ if Range.intercepts(r, changed_range) then
+ return false
+ end
+ end
+ return true
+ end)
+
+ for _, child in pairs(self._children) do
+ child:_edit(
+ start_byte,
+ end_byte_old,
+ end_byte_new,
+ start_row,
+ start_col,
+ end_row_old,
+ end_col_old,
+ end_row_new,
+ end_col_new
+ )
+ end
+end
+
+---@package
+---@param bufnr integer
+---@param changed_tick integer
+---@param start_row integer
+---@param start_col integer
+---@param start_byte integer
+---@param old_row integer
+---@param old_col integer
+---@param old_byte integer
+---@param new_row integer
+---@param new_col integer
+---@param new_byte integer
function LanguageTree:_on_bytes(
bufnr,
changed_tick,
@@ -466,26 +972,36 @@ function LanguageTree:_on_bytes(
new_col,
new_byte
)
- self:invalidate()
-
local old_end_col = old_col + ((old_row == 0) and start_col or 0)
local new_end_col = new_col + ((new_row == 0) and start_col or 0)
- -- Edit all trees recursively, together BEFORE emitting a bytes callback.
- -- In most cases this callback should only be called from the root tree.
- self:for_each_tree(function(tree)
- tree:edit(
- start_byte,
- start_byte + old_byte,
- start_byte + new_byte,
- start_row,
- start_col,
- start_row + old_row,
- old_end_col,
- start_row + new_row,
- new_end_col
- )
- end)
+ self:_log(
+ 'on_bytes',
+ bufnr,
+ changed_tick,
+ start_row,
+ start_col,
+ start_byte,
+ old_row,
+ old_col,
+ old_byte,
+ new_row,
+ new_col,
+ new_byte
+ )
+
+ -- Edit trees together BEFORE emitting a bytes callback.
+ self:_edit(
+ start_byte,
+ start_byte + old_byte,
+ start_byte + new_byte,
+ start_row,
+ start_col,
+ start_row + old_row,
+ old_end_col,
+ start_row + new_row,
+ new_end_col
+ )
self:_do_callback(
'bytes',
@@ -503,63 +1019,65 @@ function LanguageTree:_on_bytes(
)
end
----@private
+---@package
function LanguageTree:_on_reload()
self:invalidate(true)
end
----@private
+---@package
function LanguageTree:_on_detach(...)
self:invalidate(true)
self:_do_callback('detach', ...)
+ if self._logfile then
+ self._logger('nvim', 'detaching')
+ self._logger = nil
+ self._logfile:close()
+ end
end
--- Registers callbacks for the |LanguageTree|.
---@param cbs table An |nvim_buf_attach()|-like table argument with the following handlers:
--- - `on_bytes` : see |nvim_buf_attach()|, but this will be called _after_ the parsers callback.
--- - `on_changedtree` : a callback that will be called every time the tree has syntactical changes.
---- It will only be passed one argument, which is a table of the ranges (as node ranges) that
---- changed.
+--- It will be passed two arguments: a table of the ranges (as node ranges) that
+--- changed and the changed tree.
--- - `on_child_added` : emitted when a child is added to the tree.
--- - `on_child_removed` : emitted when a child is removed from the tree.
-function LanguageTree:register_cbs(cbs)
+--- - `on_detach` : emitted when the buffer is detached, see |nvim_buf_detach_event|.
+--- Takes one argument, the number of the buffer.
+--- @param recursive? boolean Apply callbacks recursively for all children. Any new children will
+--- also inherit the callbacks.
+function LanguageTree:register_cbs(cbs, recursive)
+ ---@cast cbs table<TSCallbackNameOn,function>
if not cbs then
return
end
- if cbs.on_changedtree then
- table.insert(self._callbacks.changedtree, cbs.on_changedtree)
- end
-
- if cbs.on_bytes then
- table.insert(self._callbacks.bytes, cbs.on_bytes)
- end
+ local callbacks = recursive and self._callbacks_rec or self._callbacks
- if cbs.on_detach then
- table.insert(self._callbacks.detach, cbs.on_detach)
- end
-
- if cbs.on_child_added then
- table.insert(self._callbacks.child_added, cbs.on_child_added)
+ for name, cbname in pairs(TSCallbackNames) do
+ if cbs[name] then
+ table.insert(callbacks[cbname], cbs[name])
+ end
end
- if cbs.on_child_removed then
- table.insert(self._callbacks.child_removed, cbs.on_child_removed)
+ if recursive then
+ for _, child in pairs(self._children) do
+ child:register_cbs(cbs, true)
+ end
end
end
----@private
+---@param tree TSTree
+---@param range Range
+---@return boolean
local function tree_contains(tree, range)
- local start_row, start_col, end_row, end_col = tree:root():range()
- local start_fits = start_row < range[1] or (start_row == range[1] and start_col <= range[2])
- local end_fits = end_row > range[3] or (end_row == range[3] and end_col >= range[4])
-
- return start_fits and end_fits
+ return Range.contains({ tree:root():range() }, range)
end
--- Determines whether {range} is contained in the |LanguageTree|.
---
----@param range table `{ start_line, start_col, end_line, end_col }`
+---@param range Range4 `{ start_line, start_col, end_line, end_col }`
---@return boolean
function LanguageTree:contains(range)
for _, tree in pairs(self._trees) do
@@ -573,20 +1091,19 @@ end
--- Gets the tree that contains {range}.
---
----@param range table `{ start_line, start_col, end_line, end_col }`
+---@param range Range4 `{ start_line, start_col, end_line, end_col }`
---@param opts table|nil Optional keyword arguments:
--- - ignore_injections boolean Ignore injected languages (default true)
----@return userdata|nil Contained |tstree|
+---@return TSTree|nil
function LanguageTree:tree_for_range(range, opts)
opts = opts or {}
local ignore = vim.F.if_nil(opts.ignore_injections, true)
if not ignore then
for _, child in pairs(self._children) do
- for _, tree in pairs(child:trees()) do
- if tree_contains(tree, range) then
- return tree
- end
+ local tree = child:tree_for_range(range, opts)
+ if tree then
+ return tree
end
end
end
@@ -602,10 +1119,10 @@ end
--- Gets the smallest named node that contains {range}.
---
----@param range table `{ start_line, start_col, end_line, end_col }`
+---@param range Range4 `{ start_line, start_col, end_line, end_col }`
---@param opts table|nil Optional keyword arguments:
--- - ignore_injections boolean Ignore injected languages (default true)
----@return userdata|nil Found |tsnode|
+---@return TSNode | nil Found node
function LanguageTree:named_node_for_range(range, opts)
local tree = self:tree_for_range(range, opts)
if tree then
@@ -615,7 +1132,7 @@ end
--- Gets the appropriate language that contains {range}.
---
----@param range table `{ start_line, start_col, end_line, end_col }`
+---@param range Range4 `{ start_line, start_col, end_line, end_col }`
---@return LanguageTree Managing {range}
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
diff --git a/runtime/lua/vim/treesitter/playground.lua b/runtime/lua/vim/treesitter/playground.lua
deleted file mode 100644
index bb073290c6..0000000000
--- a/runtime/lua/vim/treesitter/playground.lua
+++ /dev/null
@@ -1,186 +0,0 @@
-local api = vim.api
-
-local M = {}
-
----@class Playground
----@field ns number API namespace
----@field opts table Options table with the following keys:
---- - anon (boolean): If true, display anonymous nodes
---- - lang (boolean): If true, display the language alongside each node
----
----@class Node
----@field id number Node id
----@field text string Node text
----@field named boolean True if this is a named (non-anonymous) node
----@field depth number Depth of the node within the tree
----@field lnum number Beginning line number of this node in the source buffer
----@field col number Beginning column number of this node in the source buffer
----@field end_lnum number Final line number of this node in the source buffer
----@field end_col number Final column number of this node in the source buffer
----@field lang string Source language of this node
-
---- Traverse all child nodes starting at {node}.
----
---- This is a recursive function. The {depth} parameter indicates the current recursion level.
---- {lang} is a string indicating the language of the tree currently being traversed. Each traversed
---- node is added to {tree}. When recursion completes, {tree} is an array of all nodes in the order
---- they were visited.
----
---- {injections} is a table mapping node ids from the primary tree to language tree injections. Each
---- injected language has a series of trees nested within the primary language's tree, and the root
---- node of each of these trees is contained within a node in the primary tree. The {injections}
---- table maps nodes in the primary tree to root nodes of injected trees.
----
----@param node userdata Starting node to begin traversal |tsnode|
----@param depth number Current recursion depth
----@param lang string Language of the tree currently being traversed
----@param injections table Mapping of node ids to root nodes of injected language trees (see
---- explanation above)
----@param tree Node[] Output table containing a list of tables each representing a node in the tree
----@private
-local function traverse(node, depth, lang, injections, tree)
- local injection = injections[node:id()]
- if injection then
- traverse(injection.root, depth, injection.lang, injections, tree)
- end
-
- for child, field in node:iter_children() do
- local type = child:type()
- local lnum, col, end_lnum, end_col = child:range()
- local named = child:named()
- local text
- if named then
- if field then
- text = string.format('%s: (%s)', field, type)
- else
- text = string.format('(%s)', type)
- end
- else
- text = string.format('"%s"', type:gsub('\n', '\\n'))
- end
-
- table.insert(tree, {
- id = child:id(),
- text = text,
- named = named,
- depth = depth,
- lnum = lnum,
- col = col,
- end_lnum = end_lnum,
- end_col = end_col,
- lang = lang,
- })
-
- traverse(child, depth + 1, lang, injections, tree)
- end
-
- return tree
-end
-
---- Create a new Playground object.
----
----@param bufnr number Source buffer number
----@param lang string|nil Language of source buffer
----
----@return Playground|nil
----@return string|nil Error message, if any
----
----@private
-function M.new(self, bufnr, lang)
- local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
- if not ok then
- return nil, 'No parser available for the given buffer'
- end
-
- -- For each child tree (injected language), find the root of the tree and locate the node within
- -- the primary tree that contains that root. Add a mapping from the node in the primary tree to
- -- the root in the child tree to the {injections} table.
- local root = parser:parse()[1]:root()
- local injections = {}
- parser:for_each_child(function(child, lang_)
- child:for_each_tree(function(tree)
- local r = tree:root()
- local node = root:named_descendant_for_range(r:range())
- if node then
- injections[node:id()] = {
- lang = lang_,
- root = r,
- }
- end
- end)
- end)
-
- local nodes = traverse(root, 0, parser:lang(), injections, {})
-
- local named = {}
- for _, v in ipairs(nodes) do
- if v.named then
- named[#named + 1] = v
- end
- end
-
- local t = {
- ns = api.nvim_create_namespace(''),
- nodes = nodes,
- named = named,
- opts = {
- anon = false,
- lang = false,
- },
- }
-
- setmetatable(t, self)
- self.__index = self
- return t
-end
-
---- Write the contents of this Playground into {bufnr}.
----
----@param bufnr number Buffer number to write into.
----@private
-function M.draw(self, bufnr)
- vim.bo[bufnr].modifiable = true
- local lines = {}
- for _, item in self:iter() do
- lines[#lines + 1] = table.concat({
- string.rep(' ', item.depth),
- item.text,
- item.lnum == item.end_lnum
- and string.format(' [%d:%d-%d]', item.lnum + 1, item.col + 1, item.end_col)
- or string.format(
- ' [%d:%d-%d:%d]',
- item.lnum + 1,
- item.col + 1,
- item.end_lnum + 1,
- item.end_col
- ),
- self.opts.lang and string.format(' %s', item.lang) or '',
- })
- end
- api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
- vim.bo[bufnr].modifiable = false
-end
-
---- Get node {i} from this Playground object.
----
---- The node number is dependent on whether or not anonymous nodes are displayed.
----
----@param i number Node number to get
----@return Node
----@private
-function M.get(self, i)
- local t = self.opts.anon and self.nodes or self.named
- return t[i]
-end
-
---- Iterate over all of the nodes in this Playground object.
----
----@return function Iterator over all nodes in this Playground
----@return table
----@return number
----@private
-function M.iter(self)
- return ipairs(self.opts.anon and self.nodes or self.named)
-end
-
-return M
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index dbf134573d..8cbbffcd60 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -1,21 +1,25 @@
-local a = vim.api
+local api = vim.api
local language = require('vim.treesitter.language')
--- query: pattern matching on trees
--- predicate matching is implemented in lua
---
---@class Query
---@field captures string[] List of captures used in query
----@field info table Contains used queries, predicates, directives
+---@field info TSQueryInfo Contains used queries, predicates, directives
---@field query userdata Parsed query
local Query = {}
Query.__index = Query
+---@class TSQueryInfo
+---@field captures table
+---@field patterns table<string,any[][]>
+
+---@class TSQueryModule
local M = {}
----@private
+---@param files string[]
+---@return string[]
local function dedupe_files(files)
local result = {}
+ ---@type table<string,boolean>
local seen = {}
for _, path in ipairs(files) do
@@ -28,7 +32,6 @@ local function dedupe_files(files)
return result
end
----@private
local function safe_read(filename, read_quantifier)
local file, err = io.open(filename, 'r')
if not file then
@@ -39,7 +42,6 @@ local function safe_read(filename, read_quantifier)
return content
end
----@private
--- Adds {ilang} to {base_langs}, only if {ilang} is different than {lang}
---
---@return boolean true If lang == ilang
@@ -51,24 +53,34 @@ local function add_included_lang(base_langs, lang, ilang)
return false
end
+---@deprecated
+function M.get_query_files(...)
+ vim.deprecate(
+ 'vim.treesitter.query.get_query_files()',
+ 'vim.treesitter.query.get_files()',
+ '0.10'
+ )
+ return M.get_files(...)
+end
+
--- Gets the list of files used to make up a query
---
---@param lang string Language to get query for
---@param query_name string Name of the query to load (e.g., "highlights")
---@param is_included (boolean|nil) Internal parameter, most of the time left as `nil`
---@return string[] query_files List of files to load for given query and language
-function M.get_query_files(lang, query_name, is_included)
+function M.get_files(lang, query_name, is_included)
local query_path = string.format('queries/%s/%s.scm', lang, query_name)
- local lang_files = dedupe_files(a.nvim_get_runtime_file(query_path, true))
+ local lang_files = dedupe_files(api.nvim_get_runtime_file(query_path, true))
if #lang_files == 0 then
return {}
end
- local base_query = nil
+ local base_query = nil ---@type string?
local extensions = {}
- local base_langs = {}
+ local base_langs = {} ---@type string[]
-- Now get the base languages by looking at the first line of every file
-- The syntax is the following :
@@ -87,6 +99,7 @@ function M.get_query_files(lang, query_name, is_included)
local extension = false
for modeline in
+ ---@return string
function()
return file:read('*l')
end
@@ -97,6 +110,7 @@ function M.get_query_files(lang, query_name, is_included)
local langlist = modeline:match(MODELINE_FORMAT)
if langlist then
+ ---@diagnostic disable-next-line:param-type-mismatch
for _, incllang in ipairs(vim.split(langlist, ',', true)) do
local is_optional = incllang:match('%(.*%)')
@@ -127,7 +141,7 @@ function M.get_query_files(lang, query_name, is_included)
local query_files = {}
for _, base_lang in ipairs(base_langs) do
- local base_files = M.get_query_files(base_lang, query_name, true)
+ local base_files = M.get_files(base_lang, query_name, true)
vim.list_extend(query_files, base_files)
end
vim.list_extend(query_files, { base_query })
@@ -136,7 +150,8 @@ function M.get_query_files(lang, query_name, is_included)
return query_files
end
----@private
+---@param filenames string[]
+---@return string
local function read_query_files(filenames)
local contents = {}
@@ -147,7 +162,8 @@ local function read_query_files(filenames)
return table.concat(contents, '')
end
---- The explicitly set queries from |vim.treesitter.query.set_query()|
+-- The explicitly set queries from |vim.treesitter.query.set()|
+---@type table<string,table<string,Query>>
local explicit_queries = setmetatable({}, {
__index = function(t, k)
local lang_queries = {}
@@ -157,6 +173,12 @@ local explicit_queries = setmetatable({}, {
end,
})
+---@deprecated
+function M.set_query(...)
+ vim.deprecate('vim.treesitter.query.set_query()', 'vim.treesitter.query.set()', '0.10')
+ M.set(...)
+end
+
--- Sets the runtime query named {query_name} for {lang}
---
--- This allows users to override any runtime files and/or configuration
@@ -165,8 +187,14 @@ local explicit_queries = setmetatable({}, {
---@param lang string Language to use for the query
---@param query_name string Name of the query (e.g., "highlights")
---@param text string Query text (unparsed).
-function M.set_query(lang, query_name, text)
- explicit_queries[lang][query_name] = M.parse_query(lang, text)
+function M.set(lang, query_name, text)
+ explicit_queries[lang][query_name] = M.parse(lang, text)
+end
+
+---@deprecated
+function M.get_query(...)
+ vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10')
+ return M.get(...)
end
--- Returns the runtime query {query_name} for {lang}.
@@ -174,24 +202,28 @@ end
---@param lang string Language to use for the query
---@param query_name string Name of the query (e.g. "highlights")
---
----@return Query Parsed query
-function M.get_query(lang, query_name)
+---@return Query|nil Parsed query
+M.get = vim.func._memoize('concat-2', function(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
end
- local query_files = M.get_query_files(lang, query_name)
+ local query_files = M.get_files(lang, query_name)
local query_string = read_query_files(query_files)
- if #query_string > 0 then
- return M.parse_query(lang, query_string)
+ if #query_string == 0 then
+ return nil
end
-end
-local query_cache = vim.defaulttable(function()
- return setmetatable({}, { __mode = 'v' })
+ return M.parse(lang, query_string)
end)
+---@deprecated
+function M.parse_query(...)
+ vim.deprecate('vim.treesitter.query.parse_query()', 'vim.treesitter.query.parse()', '0.10')
+ return M.parse(...)
+end
+
--- Parse {query} as a string. (If the query is in a file, the caller
--- should read the contents into a string before calling).
---
@@ -209,81 +241,50 @@ end)
---@param query string Query in s-expr syntax
---
---@return Query Parsed query
-function M.parse_query(lang, query)
- language.require_language(lang)
- local cached = query_cache[lang][query]
- if cached then
- return cached
- else
- local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, query)
- self.info = self.query:inspect()
- self.captures = self.info.captures
- query_cache[lang][query] = self
- return self
- end
-end
+M.parse = vim.func._memoize('concat-2', function(lang, query)
+ language.add(lang)
+
+ local self = setmetatable({}, Query)
+ self.query = vim._ts_parse_query(lang, query)
+ self.info = self.query:inspect()
+ self.captures = self.info.captures
+ return self
+end)
---- Gets the text corresponding to a given node
----
----@param node userdata |tsnode|
----@param source (number|string) Buffer or string from which the {node} is extracted
----@param opts (table|nil) Optional parameters.
---- - concat: (boolean) Concatenate result in a string (default true)
----@return (string[]|string)
-function M.get_node_text(node, source, opts)
- opts = opts or {}
- local concat = vim.F.if_nil(opts.concat, true)
-
- local start_row, start_col, start_byte = node:start()
- local end_row, end_col, end_byte = node:end_()
-
- if type(source) == 'number' then
- local lines
- local eof_row = a.nvim_buf_line_count(source)
- if start_row >= eof_row then
- return nil
- end
+---@deprecated
+function M.get_range(...)
+ vim.deprecate('vim.treesitter.query.get_range()', 'vim.treesitter.get_range()', '0.10')
+ return vim.treesitter.get_range(...)
+end
- if end_col == 0 then
- lines = a.nvim_buf_get_lines(source, start_row, end_row, true)
- end_col = -1
- else
- lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true)
- end
+---@deprecated
+function M.get_node_text(...)
+ vim.deprecate('vim.treesitter.query.get_node_text()', 'vim.treesitter.get_node_text()', '0.10')
+ return vim.treesitter.get_node_text(...)
+end
- if #lines > 0 then
- if #lines == 1 then
- lines[1] = string.sub(lines[1], start_col + 1, end_col)
- else
- lines[1] = string.sub(lines[1], start_col + 1)
- lines[#lines] = string.sub(lines[#lines], 1, end_col)
- end
- end
+---@alias TSMatch table<integer,TSNode>
- return concat and table.concat(lines, '\n') or lines
- elseif type(source) == 'string' then
- return source:sub(start_byte + 1, end_byte)
- end
-end
+---@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean
-- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate)
+---@type table<string,TSPredicate>
local predicate_handlers = {
['eq?'] = function(match, _, source, predicate)
local node = match[predicate[2]]
if not node then
return true
end
- local node_text = M.get_node_text(node, source)
+ local node_text = vim.treesitter.get_node_text(node, source)
- local str
+ local str ---@type string
if type(predicate[3]) == 'string' then
-- (#eq? @aa "foo")
str = predicate[3]
else
-- (#eq? @aa @bb)
- str = M.get_node_text(match[predicate[3]], source)
+ str = vim.treesitter.get_node_text(match[predicate[3]], source)
end
if node_text ~= str or str == nil then
@@ -299,12 +300,11 @@ local predicate_handlers = {
return true
end
local regex = predicate[3]
- return string.find(M.get_node_text(node, source), regex)
+ return string.find(vim.treesitter.get_node_text(node, source), regex) ~= nil
end,
['match?'] = (function()
local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true }
- ---@private
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str, 1, 2)] then
return str
@@ -321,12 +321,14 @@ local predicate_handlers = {
})
return function(match, _, source, pred)
+ ---@cast match TSMatch
local node = match[pred[2]]
if not node then
return true
end
+ ---@diagnostic disable-next-line no-unknown
local regex = compiled_vim_regexes[pred[3]]
- return regex:match_str(M.get_node_text(node, source))
+ return regex:match_str(vim.treesitter.get_node_text(node, source))
end
end)(),
@@ -335,7 +337,7 @@ local predicate_handlers = {
if not node then
return true
end
- local node_text = M.get_node_text(node, source)
+ local node_text = vim.treesitter.get_node_text(node, source)
for i = 3, #predicate do
if string.find(node_text, predicate[i], 1, true) then
@@ -351,7 +353,7 @@ local predicate_handlers = {
if not node then
return true
end
- local node_text = M.get_node_text(node, source)
+ local node_text = vim.treesitter.get_node_text(node, source)
-- Since 'predicate' will not be used by callers of this function, use it
-- to store a string set built from the list of words to check against.
@@ -359,6 +361,7 @@ local predicate_handlers = {
if not string_set then
string_set = {}
for i = 3, #predicate do
+ ---@diagnostic disable-next-line:no-unknown
string_set[predicate[i]] = true
end
predicate['string_set'] = string_set
@@ -366,36 +369,85 @@ local predicate_handlers = {
return string_set[node_text]
end,
+
+ ['has-ancestor?'] = function(match, _, _, predicate)
+ local node = match[predicate[2]]
+ if not node then
+ return true
+ end
+
+ local ancestor_types = {}
+ for _, type in ipairs({ unpack(predicate, 3) }) do
+ ancestor_types[type] = true
+ end
+
+ node = node:parent()
+ while node do
+ if ancestor_types[node:type()] then
+ return true
+ end
+ node = node:parent()
+ end
+ return false
+ end,
+
+ ['has-parent?'] = function(match, _, _, predicate)
+ local node = match[predicate[2]]
+ if not node then
+ return true
+ end
+
+ if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then
+ return true
+ end
+ return false
+ end,
}
-- As we provide lua-match? also expose vim-match?
predicate_handlers['vim-match?'] = predicate_handlers['match?']
+---@class TSMetadata
+---@field range? Range
+---@field conceal? string
+---@field [integer] TSMetadata
+---@field [string] integer|string
+
+---@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata)
+
+-- Predicate handler receive the following arguments
+-- (match, pattern, bufnr, predicate)
+
-- Directives store metadata or perform side effects against a match.
-- Directives should always end with a `!`.
-- Directive handler receive the following arguments
-- (match, pattern, bufnr, predicate, metadata)
+---@type table<string,TSDirective>
local directive_handlers = {
['set!'] = function(_, _, _, pred, metadata)
- if #pred == 4 then
- -- (#set! @capture "key" "value")
- local _, capture_id, key, value = unpack(pred)
+ if #pred >= 3 and type(pred[2]) == 'number' then
+ -- (#set! @capture key value)
+ local capture_id, key, value = pred[2], pred[3], pred[4]
if not metadata[capture_id] then
metadata[capture_id] = {}
end
metadata[capture_id][key] = value
else
- local _, key, value = unpack(pred)
- -- (#set! "key" "value")
- metadata[key] = value
+ -- (#set! key value)
+ local key, value = pred[2], pred[3]
+ metadata[key] = value or true
end
end,
-- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1)
['offset!'] = function(match, _, _, pred, metadata)
+ ---@cast pred integer[]
local capture_id = pred[2]
- local offset_node = match[capture_id]
- local range = { offset_node:range() }
+ if not metadata[capture_id] then
+ metadata[capture_id] = {}
+ end
+
+ local range = metadata[capture_id].range or { match[capture_id]:range() }
local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0
@@ -408,19 +460,74 @@ local directive_handlers = {
-- If this produces an invalid range, we just skip it.
if range[1] < range[3] or (range[1] == range[3] and range[2] <= range[4]) then
- if not metadata[capture_id] then
- metadata[capture_id] = {}
- end
metadata[capture_id].range = range
end
end,
+ -- Transform the content of the node
+ -- Example: (#gsub! @_node ".*%.(.*)" "%1")
+ ['gsub!'] = function(match, _, bufnr, pred, metadata)
+ assert(#pred == 4)
+
+ local id = pred[2]
+ assert(type(id) == 'number')
+
+ local node = match[id]
+ local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or ''
+
+ if not metadata[id] then
+ metadata[id] = {}
+ end
+
+ local pattern, replacement = pred[3], pred[4]
+ assert(type(pattern) == 'string')
+ assert(type(replacement) == 'string')
+
+ metadata[id].text = text:gsub(pattern, replacement)
+ end,
+ -- Trim blank lines from end of the node
+ -- Example: (#trim! @fold)
+ -- TODO(clason): generalize to arbitrary whitespace removal
+ ['trim!'] = function(match, _, bufnr, pred, metadata)
+ local capture_id = pred[2]
+ assert(type(capture_id) == 'number')
+
+ local node = match[capture_id]
+ if not node then
+ return
+ end
+
+ local start_row, start_col, end_row, end_col = node:range()
+
+ -- Don't trim if region ends in middle of a line
+ if end_col ~= 0 then
+ return
+ end
+
+ while end_row >= start_row do
+ -- As we only care when end_col == 0, always inspect one line above end_row.
+ local end_line = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1]
+
+ if end_line ~= '' then
+ break
+ end
+
+ end_row = end_row - 1
+ end
+
+ -- If this produces an invalid range, we just skip it.
+ if start_row < end_row or (start_row == end_row and start_col <= end_col) then
+ metadata[capture_id] = metadata[capture_id] or {}
+ metadata[capture_id].range = { start_row, start_col, end_row, end_col }
+ end
+ end,
}
--- Adds a new predicate to be used in queries
---
---@param name string Name of the predicate, without leading #
----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[])
+---@param handler function(match:table<string,TSNode>, pattern:string, bufnr:integer, predicate:string[])
--- - see |vim.treesitter.query.add_directive()| for argument meanings
+---@param force boolean|nil
function M.add_predicate(name, handler, force)
if predicate_handlers[name] and not force then
error(string.format('Overriding %s', name))
@@ -437,12 +544,13 @@ end
--- metadata table `metadata[capture_id].key = value`
---
---@param name string Name of the directive, without leading #
----@param handler function(match:table, pattern:string, bufnr:number, predicate:string[], metadata:table)
+---@param handler function(match:table<string,TSNode>, pattern:string, bufnr:integer, predicate:string[], metadata:table)
--- - match: see |treesitter-query|
--- - node-level data are accessible via `match[capture_id]`
--- - pattern: see |treesitter-query|
--- - predicate: list of strings containing the full directive being called, e.g.
--- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }`
+---@param force boolean|nil
function M.add_directive(name, handler, force)
if directive_handlers[name] and not force then
error(string.format('Overriding %s', name))
@@ -463,17 +571,18 @@ function M.list_predicates()
return vim.tbl_keys(predicate_handlers)
end
----@private
local function xor(x, y)
return (x or y) and not (x and y)
end
----@private
local function is_directive(name)
return string.sub(name, -1) == '!'
end
---@private
+---@param match TSMatch
+---@param pattern string
+---@param source integer|string
function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern]
@@ -482,8 +591,9 @@ function Query:match_preds(match, pattern, source)
-- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173).
-- Also, tree-sitter strips the leading # from predicates for us.
- local pred_name
- local is_not
+ local pred_name ---@type string
+
+ local is_not ---@type boolean
-- Skip over directives... they will get processed after all the predicates.
if not is_directive(pred[1]) then
@@ -513,6 +623,8 @@ function Query:match_preds(match, pattern, source)
end
---@private
+---@param match TSMatch
+---@param metadata TSMetadata
function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern]
@@ -533,7 +645,10 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
----@private
+---@param start integer
+---@param stop integer
+---@param node TSNode
+---@return integer, integer
local function value_or_node_range(start, stop, node)
if start == nil and stop == nil then
local node_start, _, node_stop, _ = node:range()
@@ -547,42 +662,41 @@ end
---
--- {source} is needed if the query contains predicates; then the caller
--- must ensure to use a freshly parsed tree consistent with the current
---- text of the buffer (if relevant). {start_row} and {end_row} can be used to limit
+--- text of the buffer (if relevant). {start} and {stop} can be used to limit
--- matches inside a row range (this is typically used with root node
--- as the {node}, i.e., to get syntax highlight matches in the current
---- viewport). When omitted, the {start} and {end} row values are used from the given node.
+--- viewport). When omitted, the {start} and {stop} row values are used from the given node.
---
--- The iterator returns three values: a numeric id identifying the capture,
--- the captured node, and metadata from any directives processing the match.
--- The following example shows how to get captures by name:
---- <pre>lua
+---
+--- ```lua
--- for id, node, metadata in query:iter_captures(tree:root(), bufnr, first, last) do
--- local name = query.captures[id] -- name of the capture in the query
--- -- typically useful info about the node:
--- local type = node:type() -- type of the captured node
--- local row1, col1, row2, col2 = node:range() -- range of the capture
---- ... use the info here ...
+--- -- ... use the info here ...
--- end
---- </pre>
+--- ```
---
----@param node userdata |tsnode| under which the search will occur
----@param source (number|string) Source buffer or string to extract text from
----@param start number Starting line for the search
----@param stop number Stopping line for the search (end-exclusive)
+---@param node TSNode under which the search will occur
+---@param source (integer|string) Source buffer or string to extract text from
+---@param start integer Starting line for the search
+---@param stop integer Stopping line for the search (end-exclusive)
---
----@return number capture Matching capture id
----@return table capture_node Capture for {node}
----@return table metadata for the {capture}
+---@return (fun(end_line: integer|nil): integer, TSNode, TSMetadata):
+--- capture id, capture node, metadata
function Query:iter_captures(node, source, start, stop)
if type(source) == 'number' and source == 0 then
- source = vim.api.nvim_get_current_buf()
+ source = api.nvim_get_current_buf()
end
start, stop = value_or_node_range(start, stop, node)
local raw_iter = node:_rawquery(self.query, true, start, stop)
- ---@private
- local function iter()
+ local function iter(end_line)
local capture, captured_node, match = raw_iter()
local metadata = {}
@@ -590,7 +704,10 @@ function Query:iter_captures(node, source, start, stop)
local active = self:match_preds(match, match.pattern, source)
match.active = active
if not active then
- return iter() -- tail call: try next match
+ if end_line and captured_node:range() > end_line then
+ return nil, captured_node, nil
+ end
+ return iter(end_line) -- tail call: try next match
end
self:apply_directives(match, match.pattern, source, metadata)
@@ -609,7 +726,8 @@ end
--- If the query has more than one pattern, the capture table might be sparse
--- and e.g. `pairs()` method should be used over `ipairs`.
--- Here is an example iterating over all captures in every match:
---- <pre>lua
+---
+--- ```lua
--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
--- for id, node in pairs(match) do
--- local name = query.captures[id]
@@ -617,27 +735,30 @@ end
---
--- local node_data = metadata[id] -- Node level metadata
---
---- ... use the info here ...
+--- -- ... use the info here ...
--- end
--- end
---- </pre>
+--- ```
---
----@param node userdata |tsnode| under which the search will occur
----@param source (number|string) Source buffer or string to search
----@param start number Starting line for the search
----@param stop number Stopping line for the search (end-exclusive)
+---@param node TSNode under which the search will occur
+---@param source (integer|string) Source buffer or string to search
+---@param start integer Starting line for the search
+---@param stop integer Stopping line for the search (end-exclusive)
+---@param opts table|nil Options:
+--- - max_start_depth (integer) if non-zero, sets the maximum start depth
+--- for each match. This is used to prevent traversing too deep into a tree.
+--- Requires treesitter >= 0.20.9.
---
----@return number pattern id
----@return table match
----@return table metadata
-function Query:iter_matches(node, source, start, stop)
+---@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata
+function Query:iter_matches(node, source, start, stop, opts)
if type(source) == 'number' and source == 0 then
- source = vim.api.nvim_get_current_buf()
+ source = api.nvim_get_current_buf()
end
start, stop = value_or_node_range(start, stop, node)
- local raw_iter = node:_rawquery(self.query, false, start, stop)
+ local raw_iter = node:_rawquery(self.query, false, start, stop, opts)
+ ---@cast raw_iter fun(): string, any
local function iter()
local pattern, match = raw_iter()
local metadata = {}
@@ -655,4 +776,58 @@ function Query:iter_matches(node, source, start, stop)
return iter
end
+---@class QueryLinterOpts
+---@field langs (string|string[]|nil)
+---@field clear (boolean)
+
+--- Lint treesitter queries using installed parser, or clear lint errors.
+---
+--- Use |treesitter-parsers| in runtimepath to check the query file in {buf} for errors:
+---
+--- - verify that used nodes are valid identifiers in the grammar.
+--- - verify that predicates and directives are valid.
+--- - verify that top-level s-expressions are valid.
+---
+--- The found diagnostics are reported using |diagnostic-api|.
+--- By default, the parser used for verification is determined by the containing folder
+--- of the query file, e.g., if the path ends in `/lua/highlights.scm`, the parser for the
+--- `lua` language will be used.
+---@param buf (integer) Buffer handle
+---@param opts (QueryLinterOpts|nil) Optional keyword arguments:
+--- - langs (string|string[]|nil) Language(s) to use for checking the query.
+--- If multiple languages are specified, queries are validated for all of them
+--- - clear (boolean) if `true`, just clear current lint errors
+function M.lint(buf, opts)
+ if opts and opts.clear then
+ require('vim.treesitter._query_linter').clear(buf)
+ else
+ require('vim.treesitter._query_linter').lint(buf, opts)
+ end
+end
+
+--- Omnifunc for completing node names and predicates in treesitter queries.
+---
+--- Use via
+---
+--- ```lua
+--- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
+--- ```
+---
+function M.omnifunc(findstart, base)
+ return require('vim.treesitter._query_linter').omnifunc(findstart, base)
+end
+
+--- Opens a live editor to query the buffer you started from.
+---
+--- Can also be shown with *:EditQuery*.
+---
+--- If you move the cursor to a capture name ("@foo"), text matching the capture is highlighted in
+--- the source buffer. The query editor is a scratch buffer, use `:write` to save it. You can find
+--- example queries at `$VIMRUNTIME/queries/`.
+---
+--- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype.
+function M.edit(lang)
+ require('vim.treesitter.dev').edit_query(lang)
+end
+
return M
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index 8f5be15221..b6ddf337ce 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -1,6 +1,24 @@
local M = {}
---- Prompts the user to pick a single item from a collection of entries
+--- Prompts the user to pick from a list of items, allowing arbitrary (potentially asynchronous)
+--- work until `on_choice`.
+---
+--- Example:
+---
+--- ```lua
+--- vim.ui.select({ 'tabs', 'spaces' }, {
+--- prompt = 'Select tabs or spaces:',
+--- format_item = function(item)
+--- return "I'd like to choose " .. item
+--- end,
+--- }, function(choice)
+--- if choice == 'spaces' then
+--- vim.o.expandtab = true
+--- else
+--- vim.o.expandtab = false
+--- end
+--- end)
+--- ```
---
---@param items table Arbitrary items
---@param opts table Additional options
@@ -18,24 +36,6 @@ local M = {}
--- Called once the user made a choice.
--- `idx` is the 1-based index of `item` within `items`.
--- `nil` if the user aborted the dialog.
----
----
---- Example:
---- <pre>lua
---- vim.ui.select({ 'tabs', 'spaces' }, {
---- prompt = 'Select tabs or spaces:',
---- format_item = function(item)
---- return "I'd like to choose " .. item
---- end,
---- }, function(choice)
---- if choice == 'spaces' then
---- vim.o.expandtab = true
---- else
---- vim.o.expandtab = false
---- end
---- end)
---- </pre>
-
function M.select(items, opts, on_choice)
vim.validate({
items = { items, 'table', false },
@@ -55,7 +55,16 @@ function M.select(items, opts, on_choice)
end
end
---- Prompts the user for input
+--- Prompts the user for input, allowing arbitrary (potentially asynchronous) work until
+--- `on_confirm`.
+---
+--- Example:
+---
+--- ```lua
+--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
+--- vim.o.shiftwidth = tonumber(input)
+--- end)
+--- ```
---
---@param opts table Additional options. See |input()|
--- - prompt (string|nil)
@@ -76,13 +85,6 @@ end
--- `input` is what the user typed (it might be
--- an empty string if nothing was entered), or
--- `nil` if the user aborted the dialog.
----
---- Example:
---- <pre>lua
---- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input)
---- vim.o.shiftwidth = tonumber(input)
---- end)
---- </pre>
function M.input(opts, on_confirm)
vim.validate({
on_confirm = { on_confirm, 'function', false },
@@ -103,4 +105,59 @@ function M.input(opts, on_confirm)
end
end
+--- Opens `path` with the system default handler (macOS `open`, Windows `explorer.exe`, Linux
+--- `xdg-open`, …), or returns (but does not show) an error message on failure.
+---
+--- Expands "~/" and environment variables in filesystem paths.
+---
+--- Examples:
+---
+--- ```lua
+--- vim.ui.open("https://neovim.io/")
+--- vim.ui.open("~/path/to/file")
+--- vim.ui.open("$VIMRUNTIME")
+--- ```
+---
+---@param path string Path or URL to open
+---
+---@return vim.SystemCompleted|nil # Command result, or nil if not found.
+---@return string|nil # Error message on failure
+---
+---@see |vim.system()|
+function M.open(path)
+ vim.validate({
+ path = { path, 'string' },
+ })
+ local is_uri = path:match('%w+:')
+ if not is_uri then
+ path = vim.fn.expand(path)
+ end
+
+ local cmd
+
+ if vim.fn.has('mac') == 1 then
+ cmd = { 'open', path }
+ elseif vim.fn.has('win32') == 1 then
+ if vim.fn.executable('rundll32') == 1 then
+ cmd = { 'rundll32', 'url.dll,FileProtocolHandler', path }
+ else
+ return nil, 'vim.ui.open: rundll32 not found'
+ end
+ elseif vim.fn.executable('wslview') == 1 then
+ cmd = { 'wslview', path }
+ elseif vim.fn.executable('xdg-open') == 1 then
+ cmd = { 'xdg-open', path }
+ else
+ return nil, 'vim.ui.open: no handler found (tried: wslview, xdg-open)'
+ end
+
+ local rv = vim.system(cmd, { text = true, detach = true }):wait()
+ if rv.code ~= 0 then
+ local msg = ('vim.ui.open: command failed (%d): %s'):format(rv.code, vim.inspect(cmd))
+ return rv, msg
+ end
+
+ return rv, nil
+end
+
return M
diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua
new file mode 100644
index 0000000000..6483f0387d
--- /dev/null
+++ b/runtime/lua/vim/ui/clipboard/osc52.lua
@@ -0,0 +1,75 @@
+local M = {}
+
+--- Return the OSC 52 escape sequence
+---
+--- @param clipboard string The clipboard to read from or write to
+--- @param contents string The Base64 encoded contents to write to the clipboard, or '?' to read
+--- from the clipboard
+local function osc52(clipboard, contents)
+ return string.format('\027]52;%s;%s\027\\', clipboard, contents)
+end
+
+function M.copy(reg)
+ local clipboard = reg == '+' and 'c' or 'p'
+ return function(lines)
+ local s = table.concat(lines, '\n')
+ io.stdout:write(osc52(clipboard, vim.base64.encode(s)))
+ end
+end
+
+function M.paste(reg)
+ local clipboard = reg == '+' and 'c' or 'p'
+ return function()
+ local contents = nil
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ callback = function(args)
+ local resp = args.data ---@type string
+ local encoded = resp:match('\027%]52;%w?;([A-Za-z0-9+/=]*)')
+ if encoded then
+ contents = vim.base64.decode(encoded)
+ return true
+ end
+ end,
+ })
+
+ io.stdout:write(osc52(clipboard, '?'))
+
+ local ok, res
+
+ -- Wait 1s first for terminals that respond quickly
+ ok, res = vim.wait(1000, function()
+ return contents ~= nil
+ end)
+
+ if res == -1 then
+ -- If no response was received after 1s, print a message and keep waiting
+ vim.api.nvim_echo(
+ { { 'Waiting for OSC 52 response from the terminal. Press Ctrl-C to interrupt...' } },
+ false,
+ {}
+ )
+ ok, res = vim.wait(9000, function()
+ return contents ~= nil
+ end)
+ end
+
+ if not ok then
+ vim.api.nvim_del_autocmd(id)
+ if res == -1 then
+ vim.notify(
+ 'Timed out waiting for a clipboard response from the terminal',
+ vim.log.levels.WARN
+ )
+ elseif res == -2 then
+ -- Clear message area
+ vim.api.nvim_echo({ { '' } }, false, {})
+ end
+ return 0
+ end
+
+ -- If we get here, contents should be non-nil
+ return vim.split(assert(contents), '\n')
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index d6b0b7410e..2dc817c5c1 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -1,72 +1,71 @@
---- TODO: This is implemented only for files now.
+---TODO: This is implemented only for files currently.
-- https://tools.ietf.org/html/rfc3986
-- https://tools.ietf.org/html/rfc2732
-- https://tools.ietf.org/html/rfc2396
-local uri_decode
-do
- local schar = string.char
+local M = {}
+local sbyte = string.byte
+local schar = string.char
+local tohex = require('bit').tohex
+local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
+local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
+local PATTERNS = {
+ ---RFC 2396
+ ---https://tools.ietf.org/html/rfc2396#section-2.2
+ rfc2396 = "^A-Za-z0-9%-_.!~*'()",
+ ---RFC 2732
+ ---https://tools.ietf.org/html/rfc2732
+ rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
+ ---RFC 3986
+ ---https://tools.ietf.org/html/rfc3986#section-2.2
+ rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
+}
- --- Convert hex to char
- ---@private
- local function hex_to_char(hex)
- return schar(tonumber(hex, 16))
- end
- uri_decode = function(str)
- return str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)
- end
+---Converts hex to char
+---@param hex string
+---@return string
+local function hex_to_char(hex)
+ return schar(tonumber(hex, 16))
end
-local uri_encode
-do
- local PATTERNS = {
- --- RFC 2396
- -- https://tools.ietf.org/html/rfc2396#section-2.2
- rfc2396 = "^A-Za-z0-9%-_.!~*'()",
- --- RFC 2732
- -- https://tools.ietf.org/html/rfc2732
- rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
- --- RFC 3986
- -- https://tools.ietf.org/html/rfc3986#section-2.2
- rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
- }
- local sbyte, tohex = string.byte
- if jit then
- tohex = require('bit').tohex
- else
- tohex = function(b)
- return string.format('%02x', b)
- end
- end
-
- ---@private
- local function percent_encode_char(char)
- return '%' .. tohex(sbyte(char), 2)
- end
- uri_encode = function(text, rfc)
- if not text then
- return
- end
- local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
- return text:gsub('([' .. pattern .. '])', percent_encode_char)
- end
+---@param char string
+---@return string
+local function percent_encode_char(char)
+ return '%' .. tohex(sbyte(char), 2)
end
----@private
+---@param uri string
+---@return boolean
local function is_windows_file_uri(uri)
return uri:match('^file:/+[a-zA-Z]:') ~= nil
end
---- Get a URI from a file path.
+---URI-encodes a string using percent escapes.
+---@param str string string to encode
+---@param rfc "rfc2396" | "rfc2732" | "rfc3986" | nil
+---@return string encoded string
+function M.uri_encode(str, rfc)
+ local pattern = PATTERNS[rfc] or PATTERNS.rfc3986
+ return (str:gsub('([' .. pattern .. '])', percent_encode_char)) -- clamped to 1 retval with ()
+end
+
+---URI-decodes a string containing percent escapes.
+---@param str string string to decode
+---@return string decoded string
+function M.uri_decode(str)
+ return (str:gsub('%%([a-fA-F0-9][a-fA-F0-9])', hex_to_char)) -- clamped to 1 retval with ()
+end
+
+---Gets a URI from a file path.
---@param path string Path to file
---@return string URI
-local function uri_from_fname(path)
- local volume_path, fname = path:match('^([a-zA-Z]:)(.*)')
+function M.uri_from_fname(path)
+ local volume_path, fname = path:match('^([a-zA-Z]:)(.*)') ---@type string?
local is_windows = volume_path ~= nil
if is_windows then
- path = volume_path .. uri_encode(fname:gsub('\\', '/'))
+ path = volume_path .. M.uri_encode(fname:gsub('\\', '/'))
else
- path = uri_encode(path)
+ path = M.uri_encode(path)
end
local uri_parts = { 'file://' }
if is_windows then
@@ -76,17 +75,14 @@ local function uri_from_fname(path)
return table.concat(uri_parts)
end
-local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
-local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
-
---- Get a URI from a bufnr
----@param bufnr number
+---Gets a URI from a bufnr.
+---@param bufnr integer
---@return string URI
-local function uri_from_bufnr(bufnr)
+function M.uri_from_bufnr(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
local volume_path = fname:match('^([a-zA-Z]:).*')
local is_windows = volume_path ~= nil
- local scheme
+ local scheme ---@type string?
if is_windows then
fname = fname:gsub('\\', '/')
scheme = fname:match(WINDOWS_URI_SCHEME_PATTERN)
@@ -96,42 +92,35 @@ local function uri_from_bufnr(bufnr)
if scheme then
return fname
else
- return uri_from_fname(fname)
+ return M.uri_from_fname(fname)
end
end
---- Get a filename from a URI
+---Gets a filename from a URI.
---@param uri string
---@return string filename or unchanged URI for non-file URIs
-local function uri_to_fname(uri)
+function M.uri_to_fname(uri)
local scheme = assert(uri:match(URI_SCHEME_PATTERN), 'URI must contain a scheme: ' .. uri)
if scheme ~= 'file' then
return uri
end
- uri = uri_decode(uri)
- -- TODO improve this.
+ uri = M.uri_decode(uri)
+ --TODO improve this.
if is_windows_file_uri(uri) then
- uri = uri:gsub('^file:/+', '')
- uri = uri:gsub('/', '\\')
+ uri = uri:gsub('^file:/+', ''):gsub('/', '\\')
else
- uri = uri:gsub('^file:/+', '/')
+ uri = uri:gsub('^file:/+', '/') ---@type string
end
return uri
end
---- Get the buffer for a uri.
---- Creates a new unloaded buffer if no buffer for the uri already exists.
+---Gets the buffer for a uri.
+---Creates a new unloaded buffer if no buffer for the uri already exists.
--
---@param uri string
----@return number bufnr
-local function uri_to_bufnr(uri)
- return vim.fn.bufadd(uri_to_fname(uri))
+---@return integer bufnr
+function M.uri_to_bufnr(uri)
+ return vim.fn.bufadd(M.uri_to_fname(uri))
end
-return {
- uri_from_fname = uri_from_fname,
- uri_from_bufnr = uri_from_bufnr,
- uri_to_fname = uri_to_fname,
- uri_to_bufnr = uri_to_bufnr,
-}
--- vim:sw=2 ts=2 et
+return M
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
new file mode 100644
index 0000000000..306eef90d3
--- /dev/null
+++ b/runtime/lua/vim/version.lua
@@ -0,0 +1,437 @@
+--- @defgroup vim.version
+---
+--- @brief The \`vim.version\` module provides functions for comparing versions and ranges
+--- conforming to the https://semver.org spec. Plugins, and plugin managers, can use this to check
+--- available tools and dependencies on the current system.
+---
+--- Example:
+---
+--- ```lua
+--- local v = vim.version.parse(vim.fn.system({'tmux', '-V'}), {strict=false})
+--- if vim.version.gt(v, {3, 2, 0}) then
+--- -- ...
+--- end
+--- ```
+---
+--- \*vim.version()\* returns the version of the current Nvim process.
+---
+--- VERSION RANGE SPEC \*version-range\*
+---
+--- A version "range spec" defines a semantic version range which can be tested against a version,
+--- using |vim.version.range()|.
+---
+--- Supported range specs are shown in the following table.
+--- Note: suffixed versions (1.2.3-rc1) are not matched.
+---
+--- ```
+--- 1.2.3 is 1.2.3
+--- =1.2.3 is 1.2.3
+--- >1.2.3 greater than 1.2.3
+--- <1.2.3 before 1.2.3
+--- >=1.2.3 at least 1.2.3
+--- ~1.2.3 is >=1.2.3 <1.3.0 "reasonably close to 1.2.3"
+--- ^1.2.3 is >=1.2.3 <2.0.0 "compatible with 1.2.3"
+--- ^0.2.3 is >=0.2.3 <0.3.0 (0.x.x is special)
+--- ^0.0.1 is =0.0.1 (0.0.x is special)
+--- ^1.2 is >=1.2.0 <2.0.0 (like ^1.2.0)
+--- ~1.2 is >=1.2.0 <1.3.0 (like ~1.2.0)
+--- ^1 is >=1.0.0 <2.0.0 "compatible with 1"
+--- ~1 same "reasonably close to 1"
+--- 1.x same
+--- 1.* same
+--- 1 same
+--- * any version
+--- x same
+---
+--- 1.2.3 - 2.3.4 is >=1.2.3 <=2.3.4
+---
+--- Partial right: missing pieces treated as x (2.3 => 2.3.x).
+--- 1.2.3 - 2.3 is >=1.2.3 <2.4.0
+--- 1.2.3 - 2 is >=1.2.3 <3.0.0
+---
+--- Partial left: missing pieces treated as 0 (1.2 => 1.2.0).
+--- 1.2 - 2.3.0 is 1.2.0 - 2.3.0
+--- ```
+
+local M = {}
+
+---@class Version
+---@field [1] number
+---@field [2] number
+---@field [3] number
+---@field major number
+---@field minor number
+---@field patch number
+---@field prerelease? string
+---@field build? string
+local Version = {}
+Version.__index = Version
+
+--- Compares prerelease strings: per semver, number parts must be must be treated as numbers:
+--- "pre1.10" is greater than "pre1.2". https://semver.org/#spec-item-11
+local function cmp_prerel(prerel1, prerel2)
+ if not prerel1 or not prerel2 then
+ return prerel1 and -1 or (prerel2 and 1 or 0)
+ end
+ -- TODO(justinmk): not fully spec-compliant; this treats non-dot-delimited digit sequences as
+ -- numbers. Maybe better: "(.-)(%.%d*)".
+ local iter1 = prerel1:gmatch('([^0-9]*)(%d*)')
+ local iter2 = prerel2:gmatch('([^0-9]*)(%d*)')
+ while true do
+ local word1, n1 = iter1()
+ local word2, n2 = iter2()
+ if word1 == nil and word2 == nil then -- Done iterating.
+ return 0
+ end
+ word1, n1, word2, n2 =
+ word1 or '', n1 and tonumber(n1) or 0, word2 or '', n2 and tonumber(n2) or 0
+ if word1 ~= word2 then
+ return word1 < word2 and -1 or 1
+ end
+ if n1 ~= n2 then
+ return n1 < n2 and -1 or 1
+ end
+ end
+end
+
+function Version:__index(key)
+ return type(key) == 'number' and ({ self.major, self.minor, self.patch })[key] or Version[key]
+end
+
+function Version:__newindex(key, value)
+ if key == 1 then
+ self.major = value
+ elseif key == 2 then
+ self.minor = value
+ elseif key == 3 then
+ self.patch = value
+ else
+ rawset(self, key, value)
+ end
+end
+
+---@param other Version
+function Version:__eq(other)
+ for i = 1, 3 do
+ if self[i] ~= other[i] then
+ return false
+ end
+ end
+ return 0 == cmp_prerel(self.prerelease, other.prerelease)
+end
+
+function Version:__tostring()
+ local ret = table.concat({ self.major, self.minor, self.patch }, '.')
+ if self.prerelease then
+ ret = ret .. '-' .. self.prerelease
+ end
+ if self.build and self.build ~= vim.NIL then
+ ret = ret .. '+' .. self.build
+ end
+ return ret
+end
+
+---@param other Version
+function Version:__lt(other)
+ for i = 1, 3 do
+ if self[i] > other[i] then
+ return false
+ elseif self[i] < other[i] then
+ return true
+ end
+ end
+ return -1 == cmp_prerel(self.prerelease, other.prerelease)
+end
+
+---@param other Version
+function Version:__le(other)
+ return self < other or self == other
+end
+
+--- @private
+---
+--- Creates a new Version object, or returns `nil` if `version` is invalid.
+---
+--- @param version string|number[]|Version
+--- @param strict? boolean Reject "1.0", "0-x", "3.2a" or other non-conforming version strings
+--- @return Version?
+function M._version(version, strict) -- Adapted from https://github.com/folke/lazy.nvim
+ if type(version) == 'table' then
+ if version.major then
+ return setmetatable(vim.deepcopy(version), Version)
+ end
+ return setmetatable({
+ major = version[1] or 0,
+ minor = version[2] or 0,
+ patch = version[3] or 0,
+ }, Version)
+ end
+
+ if not strict then -- TODO: add more "scrubbing".
+ version = version:match('%d[^ ]*')
+ end
+
+ local prerel = version:match('%-([^+]*)')
+ local prerel_strict = version:match('%-([0-9A-Za-z-]*)')
+ if
+ strict
+ and prerel
+ and (prerel_strict == nil or prerel_strict == '' or not vim.startswith(prerel, prerel_strict))
+ then
+ return nil -- Invalid prerelease.
+ end
+ local build = prerel and version:match('%-[^+]*%+(.*)$') or version:match('%+(.*)$')
+ local major, minor, patch =
+ version:match('^v?(%d+)%.?(%d*)%.?(%d*)' .. (strict and (prerel and '%-' or '$') or ''))
+
+ if
+ (not strict and major)
+ or (major and minor and patch and major ~= '' and minor ~= '' and patch ~= '')
+ then
+ return setmetatable({
+ major = tonumber(major),
+ minor = minor == '' and 0 or tonumber(minor),
+ patch = patch == '' and 0 or tonumber(patch),
+ prerelease = prerel ~= '' and prerel or nil,
+ build = build ~= '' and build or nil,
+ }, Version)
+ end
+ return nil -- Invalid version string.
+end
+
+---TODO: generalize this, move to func.lua
+---
+---@generic T: Version
+---@param versions T[]
+---@return T?
+function M.last(versions)
+ local last = versions[1]
+ for i = 2, #versions do
+ if versions[i] > last then
+ last = versions[i]
+ end
+ end
+ return last
+end
+
+---@class VersionRange
+---@field from Version
+---@field to? Version
+local VersionRange = {}
+
+--- @private
+---
+---@param version string|Version
+function VersionRange:has(version)
+ if type(version) == 'string' then
+ ---@diagnostic disable-next-line: cast-local-type
+ version = M.parse(version)
+ elseif getmetatable(version) ~= Version then
+ -- Need metatable to compare versions.
+ version = setmetatable(vim.deepcopy(version), Version)
+ end
+ if version then
+ if version.prerelease ~= self.from.prerelease then
+ return false
+ end
+ return version >= self.from and (self.to == nil or version < self.to)
+ end
+end
+
+--- Parses a semver |version-range| "spec" and returns a range object:
+---
+--- ```
+--- {
+--- from: Version
+--- to: Version
+--- has(v: string|Version)
+--- }
+--- ```
+---
+--- `:has()` checks if a version is in the range (inclusive `from`, exclusive `to`).
+---
+--- Example:
+---
+--- ```lua
+--- local r = vim.version.range('1.0.0 - 2.0.0')
+--- print(r:has('1.9.9')) -- true
+--- print(r:has('2.0.0')) -- false
+--- print(r:has(vim.version())) -- check against current Nvim version
+--- ```
+---
+--- Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly:
+---
+--- ```lua
+--- local r = vim.version.range('1.0.0 - 2.0.0')
+--- print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to))
+--- ```
+---
+--- @see # https://github.com/npm/node-semver#ranges
+---
+--- @param spec string Version range "spec"
+function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
+ if spec == '*' or spec == '' then
+ return setmetatable({ from = M.parse('0.0.0') }, { __index = VersionRange })
+ end
+
+ ---@type number?
+ local hyphen = spec:find(' - ', 1, true)
+ if hyphen then
+ local a = spec:sub(1, hyphen - 1)
+ local b = spec:sub(hyphen + 3)
+ local parts = vim.split(b, '.', { plain = true })
+ local ra = M.range(a)
+ local rb = M.range(b)
+ return setmetatable({
+ from = ra and ra.from,
+ to = rb and (#parts == 3 and rb.from or rb.to),
+ }, { __index = VersionRange })
+ end
+ ---@type string, string
+ local mods, version = spec:lower():match('^([%^=<>~]*)(.*)$')
+ version = version:gsub('%.[%*x]', '')
+ local parts = vim.split(version:gsub('%-.*', ''), '.', { plain = true })
+ if #parts < 3 and mods == '' then
+ mods = '~'
+ end
+
+ local semver = M.parse(version)
+ if semver then
+ local from = semver
+ local to = vim.deepcopy(semver)
+ if mods == '' or mods == '=' then
+ to.patch = to.patch + 1
+ elseif mods == '<' then
+ from = M._version({})
+ elseif mods == '<=' then
+ from = M._version({})
+ to.patch = to.patch + 1
+ elseif mods == '>' then
+ from.patch = from.patch + 1
+ to = nil ---@diagnostic disable-line: cast-local-type
+ elseif mods == '>=' then
+ to = nil ---@diagnostic disable-line: cast-local-type
+ elseif mods == '~' then
+ if #parts >= 2 then
+ to[2] = to[2] + 1
+ to[3] = 0
+ else
+ to[1] = to[1] + 1
+ to[2] = 0
+ to[3] = 0
+ end
+ elseif mods == '^' then
+ for i = 1, 3 do
+ if to[i] ~= 0 then
+ to[i] = to[i] + 1
+ for j = i + 1, 3 do
+ to[j] = 0
+ end
+ break
+ end
+ end
+ end
+ return setmetatable({ from = from, to = to }, { __index = VersionRange })
+ end
+end
+
+---@param v string|Version
+---@return string
+local function create_err_msg(v)
+ if type(v) == 'string' then
+ return string.format('invalid version: "%s"', tostring(v))
+ elseif type(v) == 'table' and v.major then
+ return string.format('invalid version: %s', vim.inspect(v))
+ end
+ return string.format('invalid version: %s (%s)', tostring(v), type(v))
+end
+
+--- Parses and compares two version objects (the result of |vim.version.parse()|, or
+--- specified literally as a `{major, minor, patch}` tuple, e.g. `{1, 0, 3}`).
+---
+--- Example:
+---
+--- ```lua
+--- if vim.version.cmp({1,0,3}, {0,2,1}) == 0 then
+--- -- ...
+--- end
+--- local v1 = vim.version.parse('1.0.3-pre')
+--- local v2 = vim.version.parse('0.2.1')
+--- if vim.version.cmp(v1, v2) == 0 then
+--- -- ...
+--- end
+--- ```
+---
+--- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions.
+---
+---@param v1 Version|number[] Version object.
+---@param v2 Version|number[] Version to compare with `v1`.
+---@return integer -1 if `v1 < v2`, 0 if `v1 == v2`, 1 if `v1 > v2`.
+function M.cmp(v1, v2)
+ local v1_parsed = assert(M._version(v1), create_err_msg(v1))
+ local v2_parsed = assert(M._version(v2), create_err_msg(v1))
+ if v1_parsed == v2_parsed then
+ return 0
+ end
+ if v1_parsed > v2_parsed then
+ return 1
+ end
+ return -1
+end
+
+---Returns `true` if the given versions are equal. See |vim.version.cmp()| for usage.
+---@param v1 Version|number[]
+---@param v2 Version|number[]
+---@return boolean
+function M.eq(v1, v2)
+ return M.cmp(v1, v2) == 0
+end
+
+---Returns `true` if `v1 < v2`. See |vim.version.cmp()| for usage.
+---@param v1 Version|number[]
+---@param v2 Version|number[]
+---@return boolean
+function M.lt(v1, v2)
+ return M.cmp(v1, v2) == -1
+end
+
+---Returns `true` if `v1 > v2`. See |vim.version.cmp()| for usage.
+---@param v1 Version|number[]
+---@param v2 Version|number[]
+---@return boolean
+function M.gt(v1, v2)
+ return M.cmp(v1, v2) == 1
+end
+
+--- Parses a semantic version string and returns a version object which can be used with other
+--- `vim.version` functions. For example "1.0.1-rc1+build.2" returns:
+---
+--- ```
+--- { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" }
+--- ```
+---
+--- @see # https://semver.org/spec/v2.0.0.html
+---
+---@param version string Version string to parse.
+---@param opts table|nil Optional keyword arguments:
+--- - strict (boolean): Default false. If `true`, no coercion is attempted on
+--- input not conforming to semver v2.0.0. If `false`, `parse()` attempts to
+--- coerce input such as "1.0", "0-x", "tmux 3.2a" into valid versions.
+---@return table|nil parsed_version Version object or `nil` if input is invalid.
+function M.parse(version, opts)
+ assert(type(version) == 'string', create_err_msg(version))
+ opts = opts or { strict = false }
+ return M._version(version, opts.strict)
+end
+
+setmetatable(M, {
+ --- Returns the current Nvim version.
+ __call = function()
+ local version = vim.fn.api_info().version
+ -- Workaround: vim.fn.api_info().version reports "prerelease" as a boolean.
+ version.prerelease = version.prerelease and 'dev' or nil
+ return setmetatable(version, Version)
+ end,
+})
+
+return M
diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua
new file mode 100644
index 0000000000..a4d6a50b12
--- /dev/null
+++ b/runtime/lua/vim/vimhelp.lua
@@ -0,0 +1,31 @@
+-- Extra functionality for displaying Vim help.
+
+local M = {}
+
+-- Called when editing the doc/syntax.txt file
+function M.highlight_groups()
+ local save_cursor = vim.fn.getcurpos()
+
+ local start_lnum = vim.fn.search([[\*highlight-groups\*]], 'c')
+ if start_lnum == 0 then
+ return
+ end
+ local end_lnum = vim.fn.search('^======')
+ if end_lnum == 0 then
+ return
+ end
+
+ local ns = vim.api.nvim_create_namespace('vimhelp')
+ vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
+
+ for lnum = start_lnum, end_lnum do
+ local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match('^(%w+)\t')
+ if vim.fn.hlexists(word) ~= 0 then
+ vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word })
+ end
+ end
+
+ vim.fn.setpos('.', save_cursor)
+end
+
+return M
diff --git a/runtime/macros/editexisting.vim b/runtime/macros/editexisting.vim
index 3530e29dc4..b21777d861 100644
--- a/runtime/macros/editexisting.vim
+++ b/runtime/macros/editexisting.vim
@@ -1,6 +1,6 @@
" Vim Plugin: Edit the file with an existing Vim if possible
-" Maintainer: Bram Moolenaar
-" Last Change: 2014 Dec 06
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
" This is a plugin, drop it in your (Unix) ~/.vim/plugin or (Win32)
" $VIM/vimfiles/plugin directory. Or make a symbolic link, so that you
diff --git a/runtime/macros/less.vim b/runtime/macros/less.vim
index a4f96f340e..c99c636e59 100644
--- a/runtime/macros/less.vim
+++ b/runtime/macros/less.vim
@@ -1,6 +1,7 @@
" Vim script to work like "less"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2020 Dec 17
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Avoid loading this file twice, allow the user to define his own script.
if exists("loaded_less")
diff --git a/runtime/makemenu.vim b/runtime/makemenu.vim
index 6173d8b691..5d288e2f40 100644
--- a/runtime/makemenu.vim
+++ b/runtime/makemenu.vim
@@ -1,6 +1,7 @@
" Script to define the syntax menu in synmenu.vim
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 Dec 07
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This is used by "make menu" in the src directory.
edit <sfile>:p:h/synmenu.vim
@@ -86,7 +87,7 @@ SynMenu AB.Assembly.PIC:pic
SynMenu AB.Assembly.Turbo:tasm
SynMenu AB.Assembly.VAX\ Macro\ Assembly:vmasm
SynMenu AB.Assembly.Z-80:z8a
-SynMenu AB.Assembly.xa\ 6502\ cross\ assember:a65
+SynMenu AB.Assembly.xa\ 6502\ cross\ assembler:a65
SynMenu AB.ASN\.1:asn
SynMenu AB.Asterisk\ config:asterisk
SynMenu AB.Asterisk\ voicemail\ config:asteriskvm
@@ -324,7 +325,7 @@ SynMenu HIJK.Kivy:kivy
SynMenu HIJK.KixTart:kix
SynMenu L.Lace:lace
-SynMenu L.LamdaProlog:lprolog
+SynMenu L.LambdaProlog:lprolog
SynMenu L.Latte:latte
SynMenu L.Ld\ script:ld
SynMenu L.LDAP.LDIF:ldif
@@ -648,7 +649,7 @@ SynMenu UV.VSE\ JCL:vsejcl
SynMenu WXYZ.WEB.CWEB:cweb
SynMenu WXYZ.WEB.WEB:web
SynMenu WXYZ.WEB.WEB\ Changes:change
-SynMenu WXYZ.WebAssembly:wast
+SynMenu WXYZ.WebAssembly:wat
SynMenu WXYZ.Webmacro:webmacro
SynMenu WXYZ.Website\ MetaLanguage:wml
SynMenu WXYZ.wDiff:wdiff
@@ -675,6 +676,7 @@ SynMenu WXYZ.XFree86\ Config:xf86conf
SynMenu WXYZ.YAML:yaml
SynMenu WXYZ.Yacc:yacc
SynMenu WXYZ.Zimbu:zimbu
+SynMenu WXYZ.Zserio:zserio
call append(s:lnum, "")
diff --git a/runtime/menu.vim b/runtime/menu.vim
index 2671bb51cb..4576ca0ab7 100644
--- a/runtime/menu.vim
+++ b/runtime/menu.vim
@@ -1,8 +1,9 @@
" Vim support file to define the default menus
" You can also use this as a start for your own set of menus.
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Nov 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Note that ":an" (short for ":anoremenu") is often used to make a menu work
" in all modes and avoid side effects from mappings defined by the user.
@@ -121,7 +122,7 @@ endfun
" File menu
an 10.310 &File.&Open\.\.\.<Tab>:e :browse confirm e<CR>
an 10.320 &File.Sp&lit-Open\.\.\.<Tab>:sp :browse sp<CR>
-an 10.320 &File.Open\ Tab\.\.\.<Tab>:tabnew :browse tabnew<CR>
+an 10.320 &File.Open\ &Tab\.\.\.<Tab>:tabnew :browse tabnew<CR>
an 10.325 &File.&New<Tab>:enew :confirm enew<CR>
an <silent> 10.330 &File.&Close<Tab>:close
\ :if winheight(2) < 0 && tabpagewinnr(2) == 0 <Bar>
@@ -599,7 +600,9 @@ func s:XxdBack()
exe ':%!' . g:xxdprogram . ' -r'
endif
set ft=
- doautocmd filetypedetect BufReadPost
+ if exists('#filetypedetect') && exists('#BufReadPost')
+ doautocmd filetypedetect BufReadPost
+ endif
let &mod = mod
endfun
diff --git a/runtime/mswin.vim b/runtime/mswin.vim
index 2b04c1aea3..815667ead9 100644
--- a/runtime/mswin.vim
+++ b/runtime/mswin.vim
@@ -1,7 +1,8 @@
" Set options and add mapping such that Vim behaves a lot like MS-Windows
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2018 Dec 07
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Bail out if this isn't wanted.
if exists("g:skip_loading_mswin") && g:skip_loading_mswin
@@ -15,7 +16,10 @@ endif
set cpo&vim
" set 'selection', 'selectmode', 'mousemodel' and 'keymodel' for MS-Windows
-behave mswin
+set selection=exclusive
+set selectmode=mouse,key
+set mousemodel=popup
+set keymodel=startsel,stopsel
" backspace and cursor keys wrap to previous/next line
set backspace=indent,eol,start whichwrap+=<,>,[,]
diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml
index 7d2ea49df4..29db9d5ee4 100644
--- a/runtime/nvim.appdata.xml
+++ b/runtime/nvim.appdata.xml
@@ -7,7 +7,7 @@
https://github.com/flathub/io.neovim.nvim
-->
<component type="desktop-application">
- <id>nvim</id>
+ <id>io.neovim.nvim</id>
<translation type="gettext">nvim</translation>
<project_license>Apache-2.0</project_license>
<metadata_license>CC0-1.0</metadata_license>
@@ -26,6 +26,7 @@
</screenshots>
<releases>
+ <release date="2023-04-07" version="0.9.0"/>
<release date="2023-02-02" version="0.8.3"/>
<release date="2022-12-29" version="0.8.2"/>
<release date="2022-11-14" version="0.8.1"/>
@@ -61,5 +62,6 @@
<url type="translate">https://github.com/neovim/neovim/tree/master/src/nvim/po</url>
<provides>
<binary>nvim</binary>
+ <id>nvim</id>
</provides>
</component>
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index 200254321e..fc60f70335 100644
--- a/runtime/optwin.vim
+++ b/runtime/optwin.vim
@@ -1,7 +1,8 @@
" These commands create the option window.
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Dec 16
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 31
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" If there already is an option window, jump to that one.
let buf = bufnr('option-window')
@@ -106,11 +107,7 @@ func <SID>Update(lnum, line, local, thiswin)
else
let name = substitute(a:line, '^ \tset \(no\)\=\([a-z]*\).*', '\2', "")
endif
- if name == "pt" && &pt =~ "\x80"
- let val = <SID>PTvalue()
- else
- let val = escape(eval('&' . name), " \t\\\"|")
- endif
+ let val = escape(eval('&' . name), " \t\\\"|")
if a:local
exe a:thiswin . "wincmd w"
endif
@@ -211,14 +208,6 @@ func <SID>Header(text)
let s:lnum = s:lnum + 1
endfunc
-" Get the value of 'pastetoggle'. It could be a special key.
-func <SID>PTvalue()
- redir @a
- silent set pt
- redir END
- return substitute(@a, '[^=]*=\(.*\)', '\1', "")
-endfunc
-
" Restore the previous value of 'cpoptions' here, it's used below.
let &cpo = s:cpo_save
@@ -232,12 +221,6 @@ call <SID>AddOption("cpoptions", gettext("list of flags to specify Vi compatibil
call <SID>OptionG("cpo", &cpo)
call <SID>AddOption("paste", gettext("paste mode, insert typed text literally"))
call <SID>BinOptionG("paste", &paste)
-call <SID>AddOption("pastetoggle", gettext("key sequence to toggle paste mode"))
-if &pt =~ "\x80"
- call append("$", " \tset pt=" . <SID>PTvalue())
-else
- call <SID>OptionG("pt", &pt)
-endif
call <SID>AddOption("runtimepath", gettext("list of directories used for runtime files and plugins"))
call <SID>OptionG("rtp", &rtp)
call <SID>AddOption("packpath", gettext("list of directories used for plugin packages"))
@@ -323,6 +306,9 @@ call <SID>Header(gettext("displaying text"))
call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D"))
call append("$", "\t" .. s:local_to_window)
call <SID>OptionL("scr")
+call <SID>AddOption("smoothscroll", gettext("scroll by screen line"))
+call append("$", "\t" .. s:local_to_window)
+call <SID>BinOptionL("sms")
call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor"))
call append("$", " \tset so=" . &so)
call <SID>AddOption("wrap", gettext("long lines wrap"))
@@ -494,7 +480,7 @@ call <SID>OptionG("swb", &swb)
call <SID>AddOption("splitbelow", gettext("a new window is put below the current one"))
call <SID>BinOptionG("sb", &sb)
call <SID>AddOption("splitkeep", gettext("determines scroll behavior for split windows"))
-call <SID>BinOptionG("spk", &spk)
+call <SID>OptionG("spk", &spk)
call <SID>AddOption("splitright", gettext("a new window is put right of the current one"))
call <SID>BinOptionG("spr", &spr)
call <SID>AddOption("scrollbind", gettext("this window scrolls together with other bound windows"))
diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim
index eafb7c0551..dc2aba696d 100644
--- a/runtime/pack/dist/opt/matchit/autoload/matchit.vim
+++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim
@@ -1,6 +1,6 @@
" matchit.vim: (global plugin) Extended "%" matching
" autload script of matchit plugin, see ../plugin/matchit.vim
-" Last Change: Jun 10, 2021
+" Last Change: Jan 24, 2022
" Neovim does not support scriptversion
if has("vimscript-4")
@@ -42,6 +42,10 @@ function s:RestoreOptions()
let restore_options = " ve=" .. &ve .. restore_options
set ve=
endif
+ if &smartcase
+ let restore_options = " smartcase " .. restore_options
+ set nosmartcase
+ endif
return restore_options
endfunction
@@ -134,9 +138,6 @@ function matchit#Match_wrapper(word, forward, mode) range
let curcol = match(matchline, regexp)
" If there is no match, give up.
if curcol == -1
- " Make sure macros abort properly
- "exe "norm! \<esc>"
- call feedkeys("\e", 'tni')
return s:CleanUp(restore_options, a:mode, startpos)
endif
let endcol = matchend(matchline, regexp)
@@ -756,15 +757,15 @@ endfun
fun! s:ParseSkip(str)
let skip = a:str
if skip[1] == ":"
- if skip[0] == "s"
+ if skip[0] ==# "s"
let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" ..
\ strpart(skip,2) .. "'"
- elseif skip[0] == "S"
+ elseif skip[0] ==# "S"
let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" ..
\ strpart(skip,2) .. "'"
- elseif skip[0] == "r"
+ elseif skip[0] ==# "r"
let skip = "strpart(getline('.'),0,col('.'))=~'" .. strpart(skip,2) .. "'"
- elseif skip[0] == "R"
+ elseif skip[0] ==# "R"
let skip = "strpart(getline('.'),0,col('.'))!~'" .. strpart(skip,2) .. "'"
endif
endif
diff --git a/runtime/pack/dist/opt/matchit/doc/matchit.txt b/runtime/pack/dist/opt/matchit/doc/matchit.txt
index 45033ce3f1..88d0c38e4d 100644
--- a/runtime/pack/dist/opt/matchit/doc/matchit.txt
+++ b/runtime/pack/dist/opt/matchit/doc/matchit.txt
@@ -1,10 +1,10 @@
-*matchit.txt* Extended "%" matching
+*matchit.txt* Extended |%| matching
For instructions on installing this file, type
`:help matchit-install`
inside Vim.
-For Vim version 8.2. Last change: 2021 Dec 24
+For Vim version 9.0. Last change: 2023 June 28
VIM REFERENCE MANUAL by Benji Fisher et al
@@ -150,8 +150,7 @@ To use the matchit plugin add this line to your |vimrc|: >
The script should start working the next time you start Vim.
-To use the matchit plugin after startup, you can use this command (note the
-omitted '!'): >
+To use the matchit plugin after Vim has started, execute this command: >
packadd matchit
(Earlier versions of the script did nothing unless a |buffer-variable| named
@@ -175,6 +174,22 @@ fail to skip matching groups in comments and strings. If the |filetype|
mechanism is turned off, the |b:match_words| variable will probably not be
defined automatically.
+2.1 Temporarily disable the matchit plugin *matchit-disable* *:MatchDisable*
+
+To temporarily reset the plugins, that are setup you can run the following
+command: >
+ :MatchDisable
+
+This will delete all the defined key mappings to the Vim default.
+Now the "%" command will work like before loading the plugin |%|
+
+2.2 Re-enable the matchit plugin *:MatchEnable*
+
+To re-enable the plugin, after it was disabled, use the following command: >
+ :MatchEnable
+
+This will resetup the key mappings.
+
==============================================================================
3. Configuration *matchit-configure*
@@ -243,6 +258,9 @@ Examples:
comment character) you can >
:let b:match_skip = 'r:\(^\|[^\\]\)\(\\\\\)*%'
<
+ See the $VIMRUNTIME/ftplugin/vim.vim for an example that uses both
+ syntax and a regular expression.
+
==============================================================================
4. Supporting a New Language *matchit-newlang*
*b:match_words*
diff --git a/runtime/pack/dist/opt/matchit/plugin/matchit.vim b/runtime/pack/dist/opt/matchit/plugin/matchit.vim
index 51ba3a7f51..d6c735d7b4 100644
--- a/runtime/pack/dist/opt/matchit/plugin/matchit.vim
+++ b/runtime/pack/dist/opt/matchit/plugin/matchit.vim
@@ -1,7 +1,7 @@
" matchit.vim: (global plugin) Extended "%" matching
" Maintainer: Christian Brabandt
-" Version: 1.18
-" Last Change: 2020 Dec 23
+" Version: 1.19
+" Last Change: 2023, June 28th
" Repository: https://github.com/chrisbra/matchit
" Previous URL:http://www.vim.org/script.php?script_id=39
" Previous Maintainer: Benji Fisher PhD <benji@member.AMS.org>
@@ -46,44 +46,65 @@ let g:loaded_matchit = 1
let s:save_cpo = &cpo
set cpo&vim
-nnoremap <silent> <Plug>(MatchitNormalForward) :<C-U>call matchit#Match_wrapper('',1,'n')<CR>
-nnoremap <silent> <Plug>(MatchitNormalBackward) :<C-U>call matchit#Match_wrapper('',0,'n')<CR>
-xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR>
- \:if col("''") != col("$") \| exe ":normal! m'" \| endif<cr>gv``
-xnoremap <silent> <Plug>(MatchitVisualBackward) :<C-U>call matchit#Match_wrapper('',0,'v')<CR>m'gv``
-onoremap <silent> <Plug>(MatchitOperationForward) :<C-U>call matchit#Match_wrapper('',1,'o')<CR>
-onoremap <silent> <Plug>(MatchitOperationBackward) :<C-U>call matchit#Match_wrapper('',0,'o')<CR>
-
-" Analogues of [{ and ]} using matching patterns:
-nnoremap <silent> <Plug>(MatchitNormalMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>
-nnoremap <silent> <Plug>(MatchitNormalMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>
-xnoremap <silent> <Plug>(MatchitVisualMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>m'gv``
-xnoremap <silent> <Plug>(MatchitVisualMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>m'gv``
-onoremap <silent> <Plug>(MatchitOperationMultiBackward) :<C-U>call matchit#MultiMatch("bW", "o")<CR>
-onoremap <silent> <Plug>(MatchitOperationMultiForward) :<C-U>call matchit#MultiMatch("W", "o")<CR>
-
-" text object:
-xmap <silent> <Plug>(MatchitVisualTextObject) <Plug>(MatchitVisualMultiBackward)o<Plug>(MatchitVisualMultiForward)
-
-if !exists("g:no_plugin_maps")
- nmap <silent> % <Plug>(MatchitNormalForward)
- nmap <silent> g% <Plug>(MatchitNormalBackward)
- xmap <silent> % <Plug>(MatchitVisualForward)
- xmap <silent> g% <Plug>(MatchitVisualBackward)
- omap <silent> % <Plug>(MatchitOperationForward)
- omap <silent> g% <Plug>(MatchitOperationBackward)
+fun MatchEnable()
+ nnoremap <silent> <Plug>(MatchitNormalForward) :<C-U>call matchit#Match_wrapper('',1,'n')<CR>
+ nnoremap <silent> <Plug>(MatchitNormalBackward) :<C-U>call matchit#Match_wrapper('',0,'n')<CR>
+ xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR>
+ \:if col("''") != col("$") \| exe ":normal! m'" \| endif<cr>gv``
+ xnoremap <silent> <Plug>(MatchitVisualBackward) :<C-U>call matchit#Match_wrapper('',0,'v')<CR>m'gv``
+ onoremap <silent> <Plug>(MatchitOperationForward) :<C-U>call matchit#Match_wrapper('',1,'o')<CR>
+ onoremap <silent> <Plug>(MatchitOperationBackward) :<C-U>call matchit#Match_wrapper('',0,'o')<CR>
" Analogues of [{ and ]} using matching patterns:
- nmap <silent> [% <Plug>(MatchitNormalMultiBackward)
- nmap <silent> ]% <Plug>(MatchitNormalMultiForward)
- xmap <silent> [% <Plug>(MatchitVisualMultiBackward)
- xmap <silent> ]% <Plug>(MatchitVisualMultiForward)
- omap <silent> [% <Plug>(MatchitOperationMultiBackward)
- omap <silent> ]% <Plug>(MatchitOperationMultiForward)
-
- " Text object
- xmap a% <Plug>(MatchitVisualTextObject)
-endif
+ nnoremap <silent> <Plug>(MatchitNormalMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>
+ nnoremap <silent> <Plug>(MatchitNormalMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>
+ xnoremap <silent> <Plug>(MatchitVisualMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR>m'gv``
+ xnoremap <silent> <Plug>(MatchitVisualMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR>m'gv``
+ onoremap <silent> <Plug>(MatchitOperationMultiBackward) :<C-U>call matchit#MultiMatch("bW", "o")<CR>
+ onoremap <silent> <Plug>(MatchitOperationMultiForward) :<C-U>call matchit#MultiMatch("W", "o")<CR>
+
+ " text object:
+ xmap <silent> <Plug>(MatchitVisualTextObject) <Plug>(MatchitVisualMultiBackward)o<Plug>(MatchitVisualMultiForward)
+
+ if !exists("g:no_plugin_maps")
+ nmap <silent> % <Plug>(MatchitNormalForward)
+ nmap <silent> g% <Plug>(MatchitNormalBackward)
+ xmap <silent> % <Plug>(MatchitVisualForward)
+ xmap <silent> g% <Plug>(MatchitVisualBackward)
+ omap <silent> % <Plug>(MatchitOperationForward)
+ omap <silent> g% <Plug>(MatchitOperationBackward)
+
+ " Analogues of [{ and ]} using matching patterns:
+ nmap <silent> [% <Plug>(MatchitNormalMultiBackward)
+ nmap <silent> ]% <Plug>(MatchitNormalMultiForward)
+ xmap <silent> [% <Plug>(MatchitVisualMultiBackward)
+ xmap <silent> ]% <Plug>(MatchitVisualMultiForward)
+ omap <silent> [% <Plug>(MatchitOperationMultiBackward)
+ omap <silent> ]% <Plug>(MatchitOperationMultiForward)
+
+ " Text object
+ xmap a% <Plug>(MatchitVisualTextObject)
+ endif
+endfun
+
+fun MatchDisable()
+ " remove all the setup keymappings
+ nunmap %
+ nunmap g%
+ xunmap %
+ xunmap g%
+ ounmap %
+ ounmap g%
+
+ nunmap [%
+ nunmap ]%
+ xunmap [%
+ xunmap ]%
+ ounmap [%
+ ounmap ]%
+
+ xunmap a%
+endfun
" Call this function to turn on debugging information. Every time the main
" script is run, buffer variables will be saved. These can be used directly
@@ -91,6 +112,14 @@ endif
if !exists(":MatchDebug")
command! -nargs=0 MatchDebug call matchit#Match_debug()
endif
+if !exists(":MatchDisable")
+ command! -nargs=0 MatchDisable :call MatchDisable()
+endif
+if !exists(":MatchEnable")
+ command! -nargs=0 MatchEnable :call MatchEnable()
+endif
+
+call MatchEnable()
let &cpo = s:save_cpo
unlet s:save_cpo
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 99fd7dba42..1b5baa9a8b 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
-" Last Change: 2022 Nov 10
+" Last Change: 2023 Nov 02
"
" WORK IN PROGRESS - The basics works stable, more to come
" Note: In general you need at least GDB 7.12 because this provides the
@@ -75,6 +75,7 @@ let s:pc_id = 12
let s:asm_id = 13
let s:break_id = 14 " breakpoint number is added to this
let s:stopped = 1
+let s:running = 0
let s:parsing_disasm_msg = 0
let s:asm_lines = []
@@ -87,6 +88,8 @@ func s:Breakpoint2SignNumber(id, subid)
return s:break_id + a:id * 1000 + a:subid
endfunction
+" Define or adjust the default highlighting, using background "new".
+" When the 'background' option is set then "old" has the old value.
func s:Highlight(init, old, new)
let default = a:init ? 'default ' : ''
if a:new ==# 'light' && a:old !=# 'light'
@@ -96,9 +99,21 @@ func s:Highlight(init, old, new)
endif
endfunc
-call s:Highlight(1, '', &background)
-hi default debugBreakpoint term=reverse ctermbg=red guibg=red
-hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray
+" Define the default highlighting, using the current 'background' value.
+func s:InitHighlight()
+ call s:Highlight(1, '', &background)
+ hi default debugBreakpoint term=reverse ctermbg=red guibg=red
+ hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray
+endfunc
+
+" Setup an autocommand to redefine the default highlight when the colorscheme
+" is changed.
+func s:InitAutocmd()
+ augroup TermDebug
+ autocmd!
+ autocmd ColorScheme * call s:InitHighlight()
+ augroup END
+endfunc
" Get the command to execute the debugger as a list, defaults to ["gdb"].
func s:GetCommand()
@@ -113,6 +128,10 @@ func s:GetCommand()
return type(cmd) == v:t_list ? copy(cmd) : [cmd]
endfunc
+func s:Echoerr(msg)
+ echohl ErrorMsg | echom '[termdebug] ' .. a:msg | echohl None
+endfunc
+
func s:StartDebug(bang, ...)
" First argument is the command to debug, second core file or process ID.
call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang})
@@ -125,18 +144,21 @@ endfunc
func s:StartDebug_internal(dict)
if exists('s:gdbwin')
- echoerr 'Terminal debugger already running, cannot run two'
+ call s:Echoerr('Terminal debugger already running, cannot run two')
return
endif
let gdbcmd = s:GetCommand()
if !executable(gdbcmd[0])
- echoerr 'Cannot execute debugger program "' .. gdbcmd[0] .. '"'
+ call s:Echoerr('Cannot execute debugger program "' .. gdbcmd[0] .. '"')
return
endif
let s:ptywin = 0
let s:pid = 0
let s:asmwin = 0
+ let s:asmbuf = 0
+ let s:varwin = 0
+ let s:varbuf = 0
if exists('#User#TermdebugStartPre')
doauto <nomodeline> User TermdebugStartPre
@@ -145,7 +167,7 @@ func s:StartDebug_internal(dict)
" Uncomment this line to write logging in "debuglog".
" call ch_logfile('debuglog', 'w')
- let s:sourcewin = win_getid(winnr())
+ let s:sourcewin = win_getid()
" Remember the old value of 'signcolumn' for each buffer that it's set in, so
" that we can restore the value for all buffers.
@@ -182,9 +204,9 @@ func s:StartDebug_internal(dict)
endif
if !has('win32') && !use_prompt
let s:way = 'terminal'
- else
+ else
let s:way = 'prompt'
- endif
+ endif
if s:way == 'prompt'
call s:StartDebug_prompt(a:dict)
@@ -193,11 +215,17 @@ func s:StartDebug_internal(dict)
endif
if s:GetDisasmWindow()
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
call s:GotoAsmwinOrCreateIt()
call win_gotoid(curwinid)
endif
+ if s:GetVariablesWindow()
+ let curwinid = win_getid()
+ call s:GotoVariableswinOrCreateIt()
+ call win_gotoid(curwinid)
+ endif
+
if exists('#User#TermdebugStartPost')
doauto <nomodeline> User TermdebugStartPost
endif
@@ -206,14 +234,21 @@ endfunc
" Use when debugger didn't start or ended.
func s:CloseBuffers()
exe 'bwipe! ' . s:ptybuf
+ if s:asmbuf > 0 && bufexists(s:asmbuf)
+ exe 'bwipe! ' . s:asmbuf
+ endif
+ if s:varbuf > 0 && bufexists(s:varbuf)
+ exe 'bwipe! ' . s:varbuf
+ endif
+ let s:running = 0
unlet! s:gdbwin
endfunc
func s:CheckGdbRunning()
- if !s:running
- echoerr string(s:GetCommand()[0]) . ' exited unexpectedly'
- call s:CloseBuffers()
- return ''
+ if !s:gdb_running
+ call s:Echoerr(string(s:GetCommand()[0]) . ' exited unexpectedly')
+ call s:CloseBuffers()
+ return ''
endif
return 'ok'
endfunc
@@ -223,16 +258,16 @@ func s:StartDebug_term(dict)
execute s:vertical ? 'vnew' : 'new'
let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
if s:pty_job_id == 0
- echoerr 'invalid argument (or job table is full) while opening terminal window'
+ call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
return
elseif s:pty_job_id == -1
- echoerr 'Failed to open the program terminal window'
+ call s:Echoerr('Failed to open the program terminal window')
return
endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty']
- let s:ptywin = win_getid(winnr())
+ let s:ptywin = win_getid()
if s:vertical
" Assuming the source code window will get a signcolumn, use two more
" columns for that, thus one less for the terminal window.
@@ -245,16 +280,16 @@ func s:StartDebug_term(dict)
" Create a hidden terminal window to communicate with gdb
let s:comm_job_id = jobstart('tail -f /dev/null;#gdb communication', {
- \ 'on_stdout': function('s:CommOutput'),
+ \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:CommOutput')}),
\ 'pty': v:true,
\ })
" hide terminal buffer
if s:comm_job_id == 0
- echoerr 'invalid argument (or job table is full) while opening communication terminal window'
+ call s:Echoerr('Invalid argument (or job table is full) while opening communication terminal window')
exe 'bwipe! ' . s:ptybuf
return
elseif s:comm_job_id == -1
- echoerr 'Failed to open the communication terminal window'
+ call s:Echoerr('Failed to open the communication terminal window')
exe 'bwipe! ' . s:ptybuf
return
endif
@@ -295,19 +330,19 @@ func s:StartDebug_term(dict)
" call ch_log('executing "' . join(gdb_cmd) . '"')
let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')})
if s:gdb_job_id == 0
- echoerr 'invalid argument (or job table is full) while opening gdb terminal window'
+ call s:Echoerr('Invalid argument (or job table is full) while opening gdb terminal window')
exe 'bwipe! ' . s:ptybuf
return
elseif s:gdb_job_id == -1
- echoerr 'Failed to open the gdb terminal window'
+ call s:Echoerr('Failed to open the gdb terminal window')
call s:CloseBuffers()
return
endif
- let s:running = v:true
+ let s:gdb_running = v:true
let s:starting = v:true
let gdb_job_info = nvim_get_chan_info(s:gdb_job_id)
let s:gdbbuf = gdb_job_info['buffer']
- let s:gdbwin = win_getid(winnr())
+ let s:gdbwin = win_getid()
" Wait for the "startupdone" message before sending any commands.
let try_count = 0
@@ -355,7 +390,7 @@ func s:StartDebug_term(dict)
" response can be in the same line or the next line
let response = line1 . line2
if response =~ 'Undefined command'
- echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
+ call s:Echoerr('Sorry, your gdb is too old, gdb 7.12 is required')
" CHECKME: possibly send a "server show version" here
call s:CloseBuffers()
return
@@ -374,7 +409,7 @@ func s:StartDebug_term(dict)
endif
let try_count += 1
if try_count > 100
- echoerr 'Cannot check if your gdb works, continuing anyway'
+ call s:Echoerr('Cannot check if your gdb works, continuing anyway')
break
endif
sleep 10m
@@ -395,7 +430,7 @@ func s:StartDebug_prompt(dict)
else
new
endif
- let s:gdbwin = win_getid(winnr())
+ let s:gdbwin = win_getid()
let s:promptbuf = bufnr('')
call prompt_setprompt(s:promptbuf, 'gdb> ')
set buftype=prompt
@@ -429,18 +464,19 @@ func s:StartDebug_prompt(dict)
" call ch_log('executing "' . join(gdb_cmd) . '"')
let s:gdbjob = jobstart(gdb_cmd, {
- \ 'on_exit': function('s:EndPromptDebug'),
- \ 'on_stdout': function('s:GdbOutCallback'),
- \ })
+ \ 'on_exit': function('s:EndPromptDebug'),
+ \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:GdbOutCallback')}),
+ \ })
if s:gdbjob == 0
- echoerr 'invalid argument (or job table is full) while starting gdb job'
+ call s:Echoerr('Invalid argument (or job table is full) while starting gdb job')
exe 'bwipe! ' . s:ptybuf
return
elseif s:gdbjob == -1
- echoerr 'Failed to start the gdb job'
+ call s:Echoerr('Failed to start the gdb job')
call s:CloseBuffers()
return
endif
+ exe $'au BufUnload <buffer={s:promptbuf}> ++once call jobstop(s:gdbjob)'
let s:ptybuf = 0
if has('win32')
@@ -449,20 +485,19 @@ func s:StartDebug_prompt(dict)
else
" Unix: Run the debugged program in a terminal window. Open it below the
" gdb window.
- execute 'new'
- wincmd x | wincmd j
- belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
+ belowright new
+ let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
if s:pty_job_id == 0
- echoerr 'invalid argument (or job table is full) while opening terminal window'
+ call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
return
elseif s:pty_job_id == -1
- echoerr 'Failed to open the program terminal window'
+ call s:Echoerr('Failed to open the program terminal window')
return
endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty']
- let s:ptywin = win_getid(winnr())
+ let s:ptywin = win_getid()
call s:SendCommand('tty ' . pty)
" Since GDB runs in a prompt window, the environment has not been set to
@@ -585,46 +620,69 @@ func s:PromptInterrupt()
" Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
" the debugger program so that gdb responds again.
if s:pid == 0
- echoerr 'Cannot interrupt gdb, did not find a process ID'
+ call s:Echoerr('Cannot interrupt gdb, did not find a process ID')
else
call debugbreak(s:pid)
endif
else
- call jobstop(s:gdbjob)
+ call v:lua.vim.uv.kill(jobpid(s:gdbjob), 'sigint')
endif
endfunc
+" Wrapper around job callback that handles partial lines (:h channel-lines).
+" It should be called from a Dictionary with the following keys:
+" - last_line: the last (partial) line received
+" - real_cb: a callback that assumes full lines
+func s:JobOutCallback(jobid, data, event) dict
+ let eof = (a:data == [''])
+ let msgs = a:data
+ let msgs[0] = self.last_line .. msgs[0]
+ if eof
+ let self.last_line = ''
+ else
+ let self.last_line = msgs[-1]
+ unlet msgs[-1]
+ endif
+ call self.real_cb(a:jobid, msgs, a:event)
+endfunc
+
" Function called when gdb outputs text.
func s:GdbOutCallback(job_id, msgs, event)
"call ch_log('received from gdb: ' . a:text)
- " Drop the gdb prompt, we have our own.
- " Drop status and echo'd commands.
- call filter(a:msgs, { index, val ->
- \ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'})
-
+ let comm_msgs = []
let lines = []
- let index = 0
for msg in a:msgs
+ " Disassembly messages need to be forwarded as-is.
+ if s:parsing_disasm_msg || msg =~ '^&"disassemble'
+ call s:CommOutput(a:job_id, [msg], a:event)
+ continue
+ endif
+
+ " Drop the gdb prompt, we have our own.
+ " Drop status and echo'd commands.
+ if msg == '(gdb) ' || msg == '^done' || msg[0] == '&'
+ continue
+ endif
+
if msg =~ '^\^error,msg='
if exists('s:evalexpr')
- \ && s:DecodeMessage(msg[11:])
+ \ && s:DecodeMessage(msg[11:], v:false)
\ =~ 'A syntax error in expression, near\|No symbol .* in current context'
" Silently drop evaluation errors.
- call remove(a:msgs, index)
unlet s:evalexpr
continue
endif
elseif msg[0] == '~'
- call add(lines, s:DecodeMessage(msg[1:]))
- call remove(a:msgs, index)
+ call add(lines, s:DecodeMessage(msg[1:], v:false))
continue
endif
- let index += 1
+
+ call add(comm_msgs, msg)
endfor
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
call win_gotoid(s:gdbwin)
" Add the output above the current prompt.
@@ -636,35 +694,43 @@ func s:GdbOutCallback(job_id, msgs, event)
endif
call win_gotoid(curwinid)
- call s:CommOutput(a:job_id, a:msgs, a:event)
+ call s:CommOutput(a:job_id, comm_msgs, a:event)
endfunc
-" Decode a message from gdb. quotedText starts with a ", return the text up
-" to the next ", unescaping characters:
-" - remove line breaks
-" - change \\t to \t
+" Decode a message from gdb. "quotedText" starts with a ", return the text up
+" to the next unescaped ", unescaping characters:
+" - remove line breaks (unless "literal" is v:true)
+" - change \" to "
+" - change \\t to \t (unless "literal" is v:true)
" - change \0xhh to \xhh (disabled for now)
" - change \ooo to octal
" - change \\ to \
-func s:DecodeMessage(quotedText)
+func s:DecodeMessage(quotedText, literal)
if a:quotedText[0] != '"'
- echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
+ call s:Echoerr('DecodeMessage(): missing quote in ' . a:quotedText)
return
endif
- return a:quotedText
- \ ->substitute('^"\|".*\|\\n', '', 'g')
- \ ->substitute('\\t', "\t", 'g')
- " multi-byte characters arrive in octal form
- " NULL-values must be kept encoded as those break the string otherwise
+ let msg = a:quotedText
+ \ ->substitute('^"\|[^\\]\zs".*', '', 'g')
+ \ ->substitute('\\"', '"', 'g')
+ "\ multi-byte characters arrive in octal form
+ "\ NULL-values must be kept encoded as those break the string otherwise
\ ->substitute('\\000', s:NullRepl, 'g')
\ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g')
- " Note: GDB docs also mention hex encodings - the translations below work
- " but we keep them out for performance-reasons until we actually see
- " those in mi-returns
- " \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g')
- " \ ->substitute('\\0x00', s:NullRepl, 'g')
+ "\ Note: GDB docs also mention hex encodings - the translations below work
+ "\ but we keep them out for performance-reasons until we actually see
+ "\ those in mi-returns
+ "\ \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g')
+ "\ \ ->substitute('\\0x00', s:NullRepl, 'g')
\ ->substitute('\\\\', '\', 'g')
\ ->substitute(s:NullRepl, '\\000', 'g')
+ if !a:literal
+ return msg
+ \ ->substitute('\\t', "\t", 'g')
+ \ ->substitute('\\n', '', 'g')
+ else
+ return msg
+ endif
endfunc
const s:NullRepl = 'XXXNULLXXX'
@@ -673,7 +739,7 @@ func s:GetFullname(msg)
if a:msg !~ 'fullname'
return ''
endif
- let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
+ let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''), v:true)
if has('win32') && name =~ ':\\\\'
" sometimes the name arrives double-escaped
let name = substitute(name, '\\\\', '\\', 'g')
@@ -686,12 +752,12 @@ func s:GetAsmAddr(msg)
if a:msg !~ 'addr='
return ''
endif
- let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', ''))
+ let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', ''), v:false)
return addr
endfunc
-function s:EndTermDebug(job_id, exit_code, event)
- let s:running = v:false
+func s:EndTermDebug(job_id, exit_code, event)
+ let s:gdb_running = v:false
if s:starting
return
endif
@@ -701,16 +767,22 @@ function s:EndTermDebug(job_id, exit_code, event)
endif
unlet s:gdbwin
-
call s:EndDebugCommon()
endfunc
func s:EndDebugCommon()
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if exists('s:ptybuf') && s:ptybuf
exe 'bwipe! ' . s:ptybuf
endif
+ if s:asmbuf > 0 && bufexists(s:asmbuf)
+ exe 'bwipe! ' . s:asmbuf
+ endif
+ if s:varbuf > 0 && bufexists(s:varbuf)
+ exe 'bwipe! ' . s:varbuf
+ endif
+ let s:running = 0
" Restore 'signcolumn' in all buffers for which it was set.
call win_gotoid(s:sourcewin)
@@ -748,11 +820,8 @@ func s:EndPromptDebug(job_id, exit_code, event)
doauto <nomodeline> User TermdebugStopPre
endif
- let curwinid = win_getid(winnr())
- call win_gotoid(s:gdbwin)
- close
- if curwinid != s:gdbwin
- call win_gotoid(curwinid)
+ if bufexists(s:promptbuf)
+ exe 'bwipe! ' . s:promptbuf
endif
call s:EndDebugCommon()
@@ -760,7 +829,6 @@ func s:EndPromptDebug(job_id, exit_code, event)
"call ch_log("Returning from EndPromptDebug()")
endfunc
-" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n"
" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n"
" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n"
@@ -770,15 +838,14 @@ endfunc
" - CommOutput: ~"End of assembler dump.\n"
" - CommOutput: ^done
-" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n"
" - CommOutput: &"No function contains specified address.\n"
" - CommOutput: ^error,msg="No function contains specified address."
func s:HandleDisasmMsg(msg)
if a:msg =~ '^\^done'
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if win_gotoid(s:asmwin)
- silent normal! gg0"_dG
+ silent! %delete _
call setline(1, s:asm_lines)
set nomodified
set filetype=asm
@@ -802,15 +869,17 @@ func s:HandleDisasmMsg(msg)
call s:SendCommand('disassemble $pc,+100')
endif
let s:parsing_disasm_msg = 0
- elseif a:msg =~ '\&\"disassemble \$pc'
+ elseif a:msg =~ '^&"disassemble \$pc'
if a:msg =~ '+100'
" This is our second disasm attempt
let s:parsing_disasm_msg = 2
endif
- else
+ elseif a:msg !~ '^&"disassemble'
let value = substitute(a:msg, '^\~\"[ ]*', '', '')
let value = substitute(value, '^=>[ ]*', '', '')
- let value = substitute(value, '\\n\"\r$', '', '')
+ " Nvim already trims the final "\r" in s:CommOutput()
+ " let value = substitute(value, '\\n\"\r$', '', '')
+ let value = substitute(value, '\\n\"$', '', '')
let value = substitute(value, '\r', '', '')
let value = substitute(value, '\\t', ' ', 'g')
@@ -820,12 +889,56 @@ func s:HandleDisasmMsg(msg)
endif
endfunc
-func s:CommOutput(job_id, msgs, event)
+func s:ParseVarinfo(varinfo)
+ let dict = {}
+ let nameIdx = matchstrpos(a:varinfo, '{name="\([^"]*\)"')
+ let dict['name'] = a:varinfo[nameIdx[1] + 7 : nameIdx[2] - 2]
+ let typeIdx = matchstrpos(a:varinfo, ',type="\([^"]*\)"')
+ " 'type' maybe is a url-like string,
+ " try to shorten it and show only the /tail
+ let dict['type'] = (a:varinfo[typeIdx[1] + 7 : typeIdx[2] - 2])->fnamemodify(':t')
+ let valueIdx = matchstrpos(a:varinfo, ',value="\(.*\)"}')
+ if valueIdx[1] == -1
+ let dict['value'] = 'Complex value'
+ else
+ let dict['value'] = a:varinfo[valueIdx[1] + 8 : valueIdx[2] - 3]
+ endif
+ return dict
+endfunc
+func s:HandleVariablesMsg(msg)
+ let curwinid = win_getid()
+ if win_gotoid(s:varwin)
+
+ silent! %delete _
+ let spaceBuffer = 20
+ call setline(1, 'Type' .
+ \ repeat(' ', 16) .
+ \ 'Name' .
+ \ repeat(' ', 16) .
+ \ 'Value')
+ let cnt = 1
+ let capture = '{name=".\{-}",\%(arg=".\{-}",\)\{0,1\}type=".\{-}"\%(,value=".\{-}"\)\{0,1\}}'
+ let varinfo = matchstr(a:msg, capture, 0, cnt)
+ while varinfo != ''
+ let vardict = s:ParseVarinfo(varinfo)
+ call setline(cnt + 1, vardict['type'] .
+ \ repeat(' ', max([20 - len(vardict['type']), 1])) .
+ \ vardict['name'] .
+ \ repeat(' ', max([20 - len(vardict['name']), 1])) .
+ \ vardict['value'])
+ let cnt += 1
+ let varinfo = matchstr(a:msg, capture, 0, cnt)
+ endwhile
+ endif
+ call win_gotoid(curwinid)
+endfunc
+
+func s:CommOutput(job_id, msgs, event)
for msg in a:msgs
- " remove prefixed NL
- if msg[0] == "\n"
- let msg = msg[1:]
+ " Nvim job lines are split on "\n", so trim a suffixed CR.
+ if msg[-1:] == "\r"
+ let msg = msg[:-2]
endif
if s:parsing_disasm_msg
@@ -845,9 +958,12 @@ func s:CommOutput(job_id, msgs, event)
call s:HandleEvaluate(msg)
elseif msg =~ '^\^error,msg='
call s:HandleError(msg)
- elseif msg =~ '^disassemble'
+ elseif msg =~ '^&"disassemble'
let s:parsing_disasm_msg = 1
let s:asm_lines = []
+ call s:HandleDisasmMsg(msg)
+ elseif msg =~ '^\^done,variables='
+ call s:HandleVariablesMsg(msg)
endif
endif
endfor
@@ -888,12 +1004,17 @@ func s:InstallCommands()
command Continue call chansend(s:gdb_job_id, "continue\r")
endif
+ command -nargs=* Frame call s:Frame(<q-args>)
+ command -count=1 Up call s:Up(<count>)
+ command -count=1 Down call s:Down(<count>)
+
command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
command Gdb call win_gotoid(s:gdbwin)
command Program call s:GotoProgram()
command Source call s:GotoSourcewinOrCreateIt()
command Asm call s:GotoAsmwinOrCreateIt()
- command Winbar call s:InstallWinbar()
+ command Var call s:GotoVariableswinOrCreateIt()
+ command Winbar call s:InstallWinbar(1)
let map = 1
if exists('g:termdebug_config')
@@ -902,26 +1023,37 @@ func s:InstallCommands()
let map = g:termdebug_map_K
endif
if map
- " let s:k_map_saved = maparg('K', 'n', 0, 1)
- let s:k_map_saved = {}
- for map in nvim_get_keymap('n')
- if map.lhs ==# 'K'
- let s:k_map_saved = map
- break
- endif
- endfor
- nnoremap K :Evaluate<CR>
+ let s:k_map_saved = maparg('K', 'n', 0, 1)
+ if !empty(s:k_map_saved) && !s:k_map_saved.buffer || empty(s:k_map_saved)
+ nnoremap K :Evaluate<CR>
+ endif
endif
- if has('menu') && &mouse != ''
- " install the window toolbar by default, can be disabled in the config
- let winbar = 1
- if exists('g:termdebug_config')
- let winbar = get(g:termdebug_config, 'winbar', 1)
+ let map = 1
+ if exists('g:termdebug_config')
+ let map = get(g:termdebug_config, 'map_plus', 1)
+ endif
+ if map
+ let s:plus_map_saved = maparg('+', 'n', 0, 1)
+ if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer || empty(s:plus_map_saved)
+ nnoremap <expr> + $'<Cmd>{v:count1}Up<CR>'
endif
- if winbar
- call s:InstallWinbar()
+ endif
+
+ let map = 1
+ if exists('g:termdebug_config')
+ let map = get(g:termdebug_config, 'map_minus', 1)
+ endif
+ if map
+ let s:minus_map_saved = maparg('-', 'n', 0, 1)
+ if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer || empty(s:minus_map_saved)
+ nnoremap <expr> - $'<Cmd>{v:count1}Down<CR>'
endif
+ endif
+
+
+ if has('menu') && &mouse != ''
+ call s:InstallWinbar(0)
let popup = 1
if exists('g:termdebug_config')
@@ -932,11 +1064,11 @@ func s:InstallCommands()
if popup
let s:saved_mousemodel = &mousemodel
let &mousemodel = 'popup_setpos'
- an 1.200 PopUp.-SEP3- <Nop>
- an 1.210 PopUp.Set\ breakpoint :Break<CR>
- an 1.220 PopUp.Clear\ breakpoint :Clear<CR>
- an 1.230 PopUp.Run\ until :Until<CR>
- an 1.240 PopUp.Evaluate :Evaluate<CR>
+ an 1.200 PopUp.-SEP3- <Nop>
+ an 1.210 PopUp.Set\ breakpoint :Break<CR>
+ an 1.220 PopUp.Clear\ breakpoint :Clear<CR>
+ an 1.230 PopUp.Run\ until :Until<CR>
+ an 1.240 PopUp.Evaluate :Evaluate<CR>
endif
endif
@@ -946,7 +1078,7 @@ endfunc
" let s:winbar_winids = []
" Install the window toolbar in the current window.
-func s:InstallWinbar()
+func s:InstallWinbar(force)
" if has('menu') && &mouse != ''
" nnoremenu WinBar.Step :Step<CR>
" nnoremenu WinBar.Next :Over<CR>
@@ -954,7 +1086,7 @@ func s:InstallWinbar()
" nnoremenu WinBar.Cont :Continue<CR>
" nnoremenu WinBar.Stop :Stop<CR>
" nnoremenu WinBar.Eval :Evaluate<CR>
- " call add(s:winbar_winids, win_getid(winnr()))
+ " call add(s:winbar_winids, win_getid())
" endif
endfunc
@@ -970,26 +1102,48 @@ func s:DeleteCommands()
delcommand Arguments
delcommand Stop
delcommand Continue
+ delcommand Frame
+ delcommand Up
+ delcommand Down
delcommand Evaluate
delcommand Gdb
delcommand Program
delcommand Source
delcommand Asm
+ delcommand Var
delcommand Winbar
if exists('s:k_map_saved')
- if empty(s:k_map_saved)
+ if !empty(s:k_map_saved) && !s:k_map_saved.buffer
+ nunmap K
+ call mapset(s:k_map_saved)
+ elseif empty(s:k_map_saved)
nunmap K
- else
- " call mapset(s:k_map_saved)
- call mapset('n', 0, s:k_map_saved)
endif
unlet s:k_map_saved
endif
+ if exists('s:plus_map_saved')
+ if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer
+ nunmap +
+ call mapset(s:plus_map_saved)
+ elseif empty(s:plus_map_saved)
+ nunmap +
+ endif
+ unlet s:plus_map_saved
+ endif
+ if exists('s:minus_map_saved')
+ if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer
+ nunmap -
+ call mapset(s:minus_map_saved)
+ elseif empty(s:minus_map_saved)
+ nunmap -
+ endif
+ unlet s:minus_map_saved
+ endif
if has('menu')
" Remove the WinBar entries from all windows where it was added.
- " let curwinid = win_getid(winnr())
+ " let curwinid = win_getid()
" for winid in s:winbar_winids
" if win_gotoid(winid)
" aunmenu WinBar.Step
@@ -1051,7 +1205,7 @@ func s:SetBreakpoint(at)
" Use the fname:lnum format, older gdb can't handle --source.
let at = empty(a:at) ?
- \ fnameescape(expand('%:p')) . ':' . line('.') : a:at
+ \ fnameescape(expand('%:p')) . ':' . line('.') : a:at
call s:SendCommand('-break-insert ' . at)
if do_continue
Continue
@@ -1088,7 +1242,7 @@ func s:ClearBreakpoint()
endif
echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.'
else
- echoerr 'Internal error trying to remove breakpoint at line ' . lnum . '!'
+ call s:Echoerr('Internal error trying to remove breakpoint at line ' . lnum . '!')
endif
else
echomsg 'No breakpoint to remove at line ' . lnum . '.'
@@ -1102,6 +1256,37 @@ func s:Run(args)
call s:SendResumingCommand('-exec-run')
endfunc
+" :Frame - go to a specific frame in the stack
+func s:Frame(arg)
+ " Note: we explicit do not use mi's command
+ " call s:SendCommand('-stack-select-frame "' . a:arg .'"')
+ " as we only get a "done" mi response and would have to open the file
+ " 'manually' - using cli command "frame" provides us with the mi response
+ " already parsed and allows for more formats
+ if a:arg =~ '^\d\+$' || a:arg == ''
+ " specify frame by number
+ call s:SendCommand('-interpreter-exec mi "frame ' . a:arg .'"')
+ elseif a:arg =~ '^0x[0-9a-fA-F]\+$'
+ " specify frame by stack address
+ call s:SendCommand('-interpreter-exec mi "frame address ' . a:arg .'"')
+ else
+ " specify frame by function name
+ call s:SendCommand('-interpreter-exec mi "frame function ' . a:arg .'"')
+ endif
+endfunc
+
+" :Up - go a:count frames in the stack "higher"
+func s:Up(count)
+ " the 'correct' one would be -stack-select-frame N, but we don't know N
+ call s:SendCommand($'-interpreter-exec console "up {a:count}"')
+endfunc
+
+" :Down - go a:count frames in the stack "below"
+func s:Down(count)
+ " the 'correct' one would be -stack-select-frame N, but we don't know N
+ call s:SendCommand($'-interpreter-exec console "down {a:count}"')
+endfunc
+
func s:SendEval(expr)
" check for "likely" boolean expressions, in which case we take it as lhs
if a:expr =~ "[=!<>]="
@@ -1240,101 +1425,101 @@ function! s:CloseFloatingHoverOnCursorMove(win_id, opened) abort
endfunction
function! s:CloseFloatingHoverOnBufEnter(win_id, bufnr) abort
- let winnr = win_id2win(a:win_id)
- if winnr == 0
- " Float window was already closed
- autocmd! nvim_termdebug_close_hover
- return
- endif
- if winnr == winnr()
- " Cursor is moving into floating window. Do not close it
- return
- endif
- if bufnr('%') == a:bufnr
- " When current buffer opened hover window, it's not another buffer. Skipped
- return
- endif
+ let winnr = win_id2win(a:win_id)
+ if winnr == 0
+ " Float window was already closed
autocmd! nvim_termdebug_close_hover
- call nvim_win_close(a:win_id, v:true)
- endfunction
+ return
+ endif
+ if winnr == winnr()
+ " Cursor is moving into floating window. Do not close it
+ return
+ endif
+ if bufnr('%') == a:bufnr
+ " When current buffer opened hover window, it's not another buffer. Skipped
+ return
+ endif
+ autocmd! nvim_termdebug_close_hover
+ call nvim_win_close(a:win_id, v:true)
+endfunction
" Open preview window. Window is open in:
" - Floating window on Neovim (0.4.0 or later)
" - Preview window on Neovim (0.3.0 or earlier) or Vim
function! s:OpenHoverPreview(lines, filetype) abort
- " Use local variable since parameter is not modifiable
- let lines = a:lines
- let bufnr = bufnr('%')
-
- let use_float_win = s:ShouldUseFloatWindow()
- if use_float_win
- let pos = getpos('.')
-
- " Calculate width and height
- let width = 0
- for index in range(len(lines))
- let line = lines[index]
- let lw = strdisplaywidth(line)
- if lw > width
- let width = lw
- endif
- let lines[index] = line
- endfor
-
- let height = len(lines)
-
- " Calculate anchor
- " Prefer North, but if there is no space, fallback into South
- let bottom_line = line('w0') + winheight(0) - 1
- if pos[1] + height <= bottom_line
- let vert = 'N'
- let row = 1
- else
- let vert = 'S'
- let row = 0
+ " Use local variable since parameter is not modifiable
+ let lines = a:lines
+ let bufnr = bufnr('%')
+
+ let use_float_win = s:ShouldUseFloatWindow()
+ if use_float_win
+ let pos = getpos('.')
+
+ " Calculate width and height
+ let width = 0
+ for index in range(len(lines))
+ let line = lines[index]
+ let lw = strdisplaywidth(line)
+ if lw > width
+ let width = lw
endif
+ let lines[index] = line
+ endfor
- " Prefer West, but if there is no space, fallback into East
- if pos[2] + width <= &columns
- let hor = 'W'
- let col = 0
- else
- let hor = 'E'
- let col = 1
- endif
+ let height = len(lines)
- let buf = nvim_create_buf(v:false, v:true)
- call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
- " using v:true for second argument of nvim_open_win make the floating
- " window disappear
- let float_win_id = nvim_open_win(buf, v:false, {
- \ 'relative': 'cursor',
- \ 'anchor': vert . hor,
- \ 'row': row,
- \ 'col': col,
- \ 'width': width,
- \ 'height': height,
- \ 'style': 'minimal',
- \ })
-
- if a:filetype isnot v:null
- call nvim_win_set_option(float_win_id, 'filetype', a:filetype)
- endif
+ " Calculate anchor
+ " Prefer North, but if there is no space, fallback into South
+ let bottom_line = line('w0') + winheight(0) - 1
+ if pos[1] + height <= bottom_line
+ let vert = 'N'
+ let row = 1
+ else
+ let vert = 'S'
+ let row = 0
+ endif
- call nvim_buf_set_option(buf, 'modified', v:false)
- call nvim_buf_set_option(buf, 'modifiable', v:false)
-
- " Unlike preview window, :pclose does not close window. Instead, close
- " hover window automatically when cursor is moved.
- let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
- let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
- augroup nvim_termdebug_close_hover
- execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
- execute 'autocmd BufEnter * call ' . call_on_bufenter
- augroup END
+ " Prefer West, but if there is no space, fallback into East
+ if pos[2] + width <= &columns
+ let hor = 'W'
+ let col = 0
else
- echomsg a:lines[0]
+ let hor = 'E'
+ let col = 1
endif
+
+ let buf = nvim_create_buf(v:false, v:true)
+ call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
+ " using v:true for second argument of nvim_open_win make the floating
+ " window disappear
+ let float_win_id = nvim_open_win(buf, v:false, {
+ \ 'relative': 'cursor',
+ \ 'anchor': vert . hor,
+ \ 'row': row,
+ \ 'col': col,
+ \ 'width': width,
+ \ 'height': height,
+ \ 'style': 'minimal',
+ \ })
+
+ if a:filetype isnot v:null
+ call nvim_set_option_value('filetype', a:filetype, { 'win' : float_win_id })
+ endif
+
+ call nvim_set_option_value('modified', v:false, { 'buf' : buf })
+ call nvim_set_option_value('modifiable', v:false, { 'buf' : buf })
+
+ " Unlike preview window, :pclose does not close window. Instead, close
+ " hover window automatically when cursor is moved.
+ let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
+ let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
+ augroup nvim_termdebug_close_hover
+ execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
+ execute 'autocmd BufEnter * call ' . call_on_bufenter
+ augroup END
+ else
+ echomsg a:lines[0]
+ endif
endfunction
" Handle an error.
@@ -1346,14 +1531,14 @@ func s:HandleError(msg)
return
endif
let msgVal = substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
- echoerr substitute(msgVal, '\\"', '"', 'g')
+ call s:Echoerr(substitute(msgVal, '\\"', '"', 'g'))
endfunc
func s:GotoSourcewinOrCreateIt()
if !win_gotoid(s:sourcewin)
new
- let s:sourcewin = win_getid(winnr())
- call s:InstallWinbar()
+ let s:sourcewin = win_getid()
+ call s:InstallWinbar(0)
endif
endfunc
@@ -1379,28 +1564,37 @@ endfunc
func s:GotoAsmwinOrCreateIt()
if !win_gotoid(s:asmwin)
+ let mdf = ''
if win_gotoid(s:sourcewin)
- exe 'rightbelow new'
+ " 60 is approx spaceBuffer * 3
+ if winwidth(0) > (78 + 60)
+ let mdf = 'vert'
+ exe mdf .. ' ' .. 60 .. 'new'
+ else
+ exe 'rightbelow new'
+ endif
else
exe 'new'
endif
- let s:asmwin = win_getid(winnr())
+ let s:asmwin = win_getid()
setlocal nowrap
setlocal number
setlocal noswapfile
setlocal buftype=nofile
+ setlocal bufhidden=wipe
+ setlocal signcolumn=no
setlocal modifiable
- let asmbuf = bufnr('Termdebug-asm-listing')
- if asmbuf > 0
- exe 'buffer' . asmbuf
+ if s:asmbuf > 0 && bufexists(s:asmbuf)
+ exe 'buffer' . s:asmbuf
else
- exe 'file Termdebug-asm-listing'
+ silent file Termdebug-asm-listing
+ let s:asmbuf = bufnr('Termdebug-asm-listing')
endif
- if s:GetDisasmWindowHeight() > 0
+ if mdf != 'vert' && s:GetDisasmWindowHeight() > 0
exe 'resize ' .. s:GetDisasmWindowHeight()
endif
endif
@@ -1418,17 +1612,82 @@ func s:GotoAsmwinOrCreateIt()
endif
endfunc
+func s:GetVariablesWindow()
+ if exists('g:termdebug_config')
+ return get(g:termdebug_config, 'variables_window', 0)
+ endif
+ if exists('g:termdebug_disasm_window')
+ return g:termdebug_variables_window
+ endif
+ return 0
+endfunc
+
+func s:GetVariablesWindowHeight()
+ if exists('g:termdebug_config')
+ return get(g:termdebug_config, 'variables_window_height', 0)
+ endif
+ if exists('g:termdebug_variables_window') && g:termdebug_variables_window > 1
+ return g:termdebug_variables_window
+ endif
+ return 0
+endfunc
+
+func s:GotoVariableswinOrCreateIt()
+ if !win_gotoid(s:varwin)
+ let mdf = ''
+ if win_gotoid(s:sourcewin)
+ " 60 is approx spaceBuffer * 3
+ if winwidth(0) > (78 + 60)
+ let mdf = 'vert'
+ exe mdf .. ' ' .. 60 .. 'new'
+ else
+ exe 'rightbelow new'
+ endif
+ else
+ exe 'new'
+ endif
+
+ let s:varwin = win_getid()
+
+ setlocal nowrap
+ setlocal noswapfile
+ setlocal buftype=nofile
+ setlocal bufhidden=wipe
+ setlocal signcolumn=no
+ setlocal modifiable
+
+ if s:varbuf > 0 && bufexists(s:varbuf)
+ exe 'buffer' . s:varbuf
+ else
+ silent file Termdebug-variables-listing
+ let s:varbuf = bufnr('Termdebug-variables-listing')
+ endif
+
+ if mdf != 'vert' && s:GetVariablesWindowHeight() > 0
+ exe 'resize ' .. s:GetVariablesWindowHeight()
+ endif
+ endif
+
+ if s:running
+ call s:SendCommand('-stack-list-variables 2')
+ endif
+endfunc
+
" Handle stopping and running message from gdb.
" Will update the sign that shows the current position.
func s:HandleCursor(msg)
- let wid = win_getid(winnr())
+ let wid = win_getid()
if a:msg =~ '^\*stopped'
"call ch_log('program stopped')
let s:stopped = 1
+ if a:msg =~ '^\*stopped,reason="exited-normally"'
+ let s:running = 0
+ endif
elseif a:msg =~ '^\*running'
"call ch_log('program running')
let s:stopped = 0
+ let s:running = 1
endif
if a:msg =~ 'fullname='
@@ -1442,21 +1701,25 @@ func s:HandleCursor(msg)
if asm_addr != ''
let s:asm_addr = asm_addr
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if win_gotoid(s:asmwin)
- let lnum = search('^' . s:asm_addr)
- if lnum == 0
- call s:SendCommand('disassemble $pc')
- else
- call sign_unplace('TermDebug', #{id: s:asm_id})
- call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum})
- endif
+ let lnum = search('^' . s:asm_addr)
+ if lnum == 0
+ call s:SendCommand('disassemble $pc')
+ else
+ call sign_unplace('TermDebug', #{id: s:asm_id})
+ call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum})
+ endif
- call win_gotoid(curwinid)
+ call win_gotoid(curwinid)
endif
endif
endif
+ if s:running && s:stopped && bufwinnr('Termdebug-variables-listing') != -1
+ call s:SendCommand('-stack-list-variables 2')
+ endif
+
if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
if lnum =~ '^[0-9]*$'
@@ -1468,15 +1731,15 @@ func s:HandleCursor(msg)
" prompt, since it is unlikely we want to edit the file.
" The file may be changed but not saved, warn for that.
au SwapExists * echohl WarningMsg
- \ | echo 'Warning: file is being edited elsewhere'
- \ | echohl None
- \ | let v:swapchoice = 'o'
+ \ | echo 'Warning: file is being edited elsewhere'
+ \ | echohl None
+ \ | let v:swapchoice = 'o'
augroup END
if &modified
" TODO: find existing window
exe 'split ' . fnameescape(fname)
- let s:sourcewin = win_getid(winnr())
- call s:InstallWinbar()
+ let s:sourcewin = win_getid()
+ call s:InstallWinbar(0)
else
exe 'edit ' . fnameescape(fname)
endif
@@ -1513,8 +1776,18 @@ func s:CreateBreakpoint(id, subid, enabled)
else
let hiName = "debugBreakpoint"
endif
+ let label = ''
+ if exists('g:termdebug_config')
+ let label = get(g:termdebug_config, 'sign', '')
+ endif
+ if label == ''
+ let label = printf('%02X', a:id)
+ if a:id > 255
+ let label = 'F+'
+ endif
+ endif
call sign_define('debugBreakpoint' .. nr,
- \ #{text: substitute(nr, '\..*', '', ''),
+ \ #{text: strpart(label, 0, 2),
\ texthl: hiName})
endif
endfunc
@@ -1654,5 +1927,10 @@ func s:BufUnloaded()
endfor
endfunc
+call s:InitHighlight()
+call s:InitAutocmd()
+
let &cpo = s:keepcpo
unlet s:keepcpo
+
+" vim: sw=2 sts=2 et
diff --git a/runtime/pack/dist/opt/vimball/autoload/vimball.vim b/runtime/pack/dist/opt/vimball/autoload/vimball.vim
deleted file mode 100644
index 9c7dcbda0f..0000000000
--- a/runtime/pack/dist/opt/vimball/autoload/vimball.vim
+++ /dev/null
@@ -1,775 +0,0 @@
-" vimball.vim : construct a file containing both paths and files
-" Author: Charles E. Campbell
-" Date: Apr 11, 2016
-" Version: 37
-" GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim
-" Copyright: (c) 2004-2011 by Charles E. Campbell
-" The VIM LICENSE applies to Vimball.vim, and Vimball.txt
-" (see |copyright|) except use "Vimball" instead of "Vim".
-" No warranty, express or implied.
-" *** *** Use At-Your-Own-Risk! *** ***
-
-" ---------------------------------------------------------------------
-" Load Once: {{{1
-if &cp || exists("g:loaded_vimball")
- finish
-endif
-let g:loaded_vimball = "v37"
-if v:version < 702
- echohl WarningMsg
- echo "***warning*** this version of vimball needs vim 7.2"
- echohl Normal
- finish
-endif
-let s:keepcpo= &cpo
-set cpo&vim
-"DechoTabOn
-
-" =====================================================================
-" Constants: {{{1
-if !exists("s:USAGE")
- let s:USAGE = 0
- let s:WARNING = 1
- let s:ERROR = 2
-
- " determine if cygwin is in use or not
- if !exists("g:netrw_cygwin")
- if has("win32") || has("win95") || has("win64") || has("win16")
- if &shell =~ '\%(\<bash\>\|\<zsh\>\)\%(\.exe\)\=$'
- let g:netrw_cygwin= 1
- else
- let g:netrw_cygwin= 0
- endif
- else
- let g:netrw_cygwin= 0
- endif
- endif
-
- " set up g:vimball_mkdir if the mkdir() call isn't defined
- if !exists("*mkdir")
- if exists("g:netrw_local_mkdir")
- let g:vimball_mkdir= g:netrw_local_mkdir
- elseif executable("mkdir")
- let g:vimball_mkdir= "mkdir"
- elseif executable("makedir")
- let g:vimball_mkdir= "makedir"
- endif
- if !exists(g:vimball_mkdir)
- call vimball#ShowMesg(s:WARNING,"(vimball) g:vimball_mkdir undefined")
- endif
- endif
-endif
-
-" =====================================================================
-" Functions: {{{1
-
-" ---------------------------------------------------------------------
-" vimball#MkVimball: creates a vimball given a list of paths to files {{{2
-" Input:
-" line1,line2: a range of lines containing paths to files to be included in the vimball
-" writelevel : if true, force a write to filename.vmb, even if it exists
-" (usually accomplished with :MkVimball! ...
-" filename : base name of file to be created (ie. filename.vmb)
-" Output: a filename.vmb using vimball format:
-" path
-" filesize
-" [file]
-" path
-" filesize
-" [file]
-fun! vimball#MkVimball(line1,line2,writelevel,...) range
-" call Dfunc("MkVimball(line1=".a:line1." line2=".a:line2." writelevel=".a:writelevel." vimballname<".a:1.">) a:0=".a:0)
- if a:1 =~ '\.vim$' || a:1 =~ '\.txt$'
- let vbname= substitute(a:1,'\.\a\{3}$','.vmb','')
- else
- let vbname= a:1
- endif
- if vbname !~ '\.vmb$'
- let vbname= vbname.'.vmb'
- endif
-" call Decho("vbname<".vbname.">")
- if !a:writelevel && a:1 =~ '[\/]'
- call vimball#ShowMesg(s:ERROR,"(MkVimball) vimball name<".a:1."> should not include slashes; use ! to insist")
-" call Dret("MkVimball : vimball name<".a:1."> should not include slashes")
- return
- endif
- if !a:writelevel && filereadable(vbname)
- call vimball#ShowMesg(s:ERROR,"(MkVimball) file<".vbname."> exists; use ! to insist")
-" call Dret("MkVimball : file<".vbname."> already exists; use ! to insist")
- return
- endif
-
- " user option bypass
- call vimball#SaveSettings()
-
- if a:0 >= 2
- " allow user to specify where to get the files
- let home= expand(a:2)
- else
- " use first existing directory from rtp
- let home= vimball#VimballHome()
- endif
-
- " save current directory
- let curdir = getcwd()
- call s:ChgDir(home)
-
- " record current tab, initialize while loop index
- let curtabnr = tabpagenr()
- let linenr = a:line1
-" call Decho("curtabnr=".curtabnr)
-
- while linenr <= a:line2
- let svfile = getline(linenr)
-" call Decho("svfile<".svfile.">")
-
- if !filereadable(svfile)
- call vimball#ShowMesg(s:ERROR,"unable to read file<".svfile.">")
- call s:ChgDir(curdir)
- call vimball#RestoreSettings()
-" call Dret("MkVimball")
- return
- endif
-
- " create/switch to mkvimball tab
- if !exists("vbtabnr")
- tabnew
- sil! file Vimball
- let vbtabnr= tabpagenr()
- else
- exe "tabn ".vbtabnr
- endif
-
- let lastline= line("$") + 1
- if lastline == 2 && getline("$") == ""
- call setline(1,'" Vimball Archiver by Charles E. Campbell')
- call setline(2,'UseVimball')
- call setline(3,'finish')
- let lastline= line("$") + 1
- endif
- call setline(lastline ,substitute(svfile,'$',' [[[1',''))
- call setline(lastline+1,0)
-
- " write the file from the tab
-" call Decho("exe $r ".fnameescape(svfile))
- exe "$r ".fnameescape(svfile)
-
- call setline(lastline+1,line("$") - lastline - 1)
-" call Decho("lastline=".lastline." line$=".line("$"))
-
- " restore to normal tab
- exe "tabn ".curtabnr
- let linenr= linenr + 1
- endwhile
-
- " write the vimball
- exe "tabn ".vbtabnr
- call s:ChgDir(curdir)
- setlocal ff=unix
- if a:writelevel
-" call Decho("exe w! ".fnameescape(vbname))
- exe "w! ".fnameescape(vbname)
- else
-" call Decho("exe w ".fnameescape(vbname))
- exe "w ".fnameescape(vbname)
- endif
-" call Decho("Vimball<".vbname."> created")
- echo "Vimball<".vbname."> created"
-
- " remove the evidence
- setlocal nomod bh=wipe
- exe "tabn ".curtabnr
- exe "tabc! ".vbtabnr
-
- " restore options
- call vimball#RestoreSettings()
-
-" call Dret("MkVimball")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#Vimball: extract and distribute contents from a vimball {{{2
-" (invoked the the UseVimball command embedded in
-" vimballs' prologue)
-fun! vimball#Vimball(really,...)
-" call Dfunc("vimball#Vimball(really=".a:really.") a:0=".a:0)
-
- if v:version < 701 || (v:version == 701 && !exists('*fnameescape'))
- echoerr "your vim is missing the fnameescape() function (pls upgrade to vim 7.2 or later)"
-" call Dret("vimball#Vimball : needs 7.1 with patch 299 or later")
- return
- endif
-
- if getline(1) !~ '^" Vimball Archiver'
- echoerr "(Vimball) The current file does not appear to be a Vimball!"
-" call Dret("vimball#Vimball")
- return
- endif
-
- " set up standard settings
- call vimball#SaveSettings()
- let curtabnr = tabpagenr()
- let vimballfile = expand("%:tr")
-
- " set up vimball tab
-" call Decho("setting up vimball tab")
- tabnew
- sil! file Vimball
- let vbtabnr= tabpagenr()
- let didhelp= ""
-
- " go to vim plugin home
- if a:0 > 0
- " let user specify the directory where the vimball is to be unpacked.
- " If, however, the user did not specify a full path, set the home to be below the current directory
- let home= expand(a:1)
- if has("win32") || has("win95") || has("win64") || has("win16")
- if home !~ '^\a:[/\\]'
- let home= getcwd().'/'.a:1
- endif
- elseif home !~ '^/'
- let home= getcwd().'/'.a:1
- endif
- else
- let home= vimball#VimballHome()
- endif
-" call Decho("home<".home.">")
-
- " save current directory and remove older same-named vimball, if any
- let curdir = getcwd()
-" call Decho("home<".home.">")
-" call Decho("curdir<".curdir.">")
-
- call s:ChgDir(home)
- let s:ok_unablefind= 1
- call vimball#RmVimball(vimballfile)
- unlet s:ok_unablefind
-
- let linenr = 4
- let filecnt = 0
-
- " give title to listing of (extracted) files from Vimball Archive
- if a:really
- echohl Title | echomsg "Vimball Archive" | echohl None
- else
- echohl Title | echomsg "Vimball Archive Listing" | echohl None
- echohl Statement | echomsg "files would be placed under: ".home | echohl None
- endif
-
- " apportion vimball contents to various files
-" call Decho("exe tabn ".curtabnr)
- exe "tabn ".curtabnr
-" call Decho("linenr=".linenr." line$=".line("$"))
- while 1 < linenr && linenr < line("$")
- let fname = substitute(getline(linenr),'\t\[\[\[1$','','')
- let fname = substitute(fname,'\\','/','g')
- let fsize = substitute(getline(linenr+1),'^\(\d\+\).\{-}$','\1','')+0
- let fenc = substitute(getline(linenr+1),'^\d\+\s*\(\S\{-}\)$','\1','')
- let filecnt = filecnt + 1
-" call Decho("fname<".fname."> fsize=".fsize." filecnt=".filecnt. " fenc=".fenc)
-
- if a:really
- echomsg "extracted <".fname.">: ".fsize." lines"
- else
- echomsg "would extract <".fname.">: ".fsize." lines"
- endif
-" call Decho("using L#".linenr.": will extract file<".fname.">")
-" call Decho("using L#".(linenr+1).": fsize=".fsize)
-
- " Allow AsNeeded/ directory to take place of plugin/ directory
- " when AsNeeded/filename is filereadable or was present in VimballRecord
- if fname =~ '\<plugin/'
- let anfname= substitute(fname,'\<plugin/','AsNeeded/','')
- if filereadable(anfname) || (exists("s:VBRstring") && s:VBRstring =~# anfname)
-" call Decho("using anfname<".anfname."> instead of <".fname.">")
- let fname= anfname
- endif
- endif
-
- " make directories if they don't exist yet
- if a:really
-" call Decho("making directories if they don't exist yet (fname<".fname.">)")
- let fnamebuf= substitute(fname,'\\','/','g')
- let dirpath = substitute(home,'\\','/','g')
-" call Decho("init: fnamebuf<".fnamebuf.">")
-" call Decho("init: dirpath <".dirpath.">")
- while fnamebuf =~ '/'
- let dirname = dirpath."/".substitute(fnamebuf,'/.*$','','')
- let dirpath = dirname
- let fnamebuf = substitute(fnamebuf,'^.\{-}/\(.*\)$','\1','')
-" call Decho("dirname<".dirname.">")
-" call Decho("dirpath<".dirpath.">")
- if !isdirectory(dirname)
-" call Decho("making <".dirname.">")
- if exists("g:vimball_mkdir")
- call system(g:vimball_mkdir." ".shellescape(dirname))
- else
- call mkdir(dirname)
- endif
- call s:RecordInVar(home,"rmdir('".dirname."')")
- endif
- endwhile
- endif
- call s:ChgDir(home)
-
- " grab specified qty of lines and place into "a" buffer
- " (skip over path/filename and qty-lines)
- let linenr = linenr + 2
- let lastline = linenr + fsize - 1
-" call Decho("exe ".linenr.",".lastline."yank a")
- " no point in handling a zero-length file
- if lastline >= linenr
- exe "silent ".linenr.",".lastline."yank a"
-
- " copy "a" buffer into tab
-" call Decho('copy "a buffer into tab#'.vbtabnr)
- exe "tabn ".vbtabnr
- setlocal ma
- sil! %d
- silent put a
- 1
- sil! d
-
- " write tab to file
- if a:really
- let fnamepath= home."/".fname
-" call Decho("exe w! ".fnameescape(fnamepath))
- if fenc != ""
- exe "silent w! ++enc=".fnameescape(fenc)." ".fnameescape(fnamepath)
- else
- exe "silent w! ".fnameescape(fnamepath)
- endif
- echo "wrote ".fnameescape(fnamepath)
- call s:RecordInVar(home,"call delete('".fnamepath."')")
- endif
-
- " return to tab with vimball
-" call Decho("exe tabn ".curtabnr)
- exe "tabn ".curtabnr
-
- " set up help if it's a doc/*.txt file
-" call Decho("didhelp<".didhelp."> fname<".fname.">")
- if a:really && didhelp == "" && fname =~ 'doc/[^/]\+\.\(txt\|..x\)$'
- let didhelp= substitute(fname,'^\(.*\<doc\)[/\\][^.]*\.\(txt\|..x\)$','\1','')
-" call Decho("didhelp<".didhelp.">")
- endif
- endif
-
- " update for next file
-" call Decho("update linenr= [linenr=".linenr."] + [fsize=".fsize."] = ".(linenr+fsize))
- let linenr= linenr + fsize
- endwhile
-
- " set up help
-" call Decho("about to set up help: didhelp<".didhelp.">")
- if didhelp != ""
- let htpath= home."/".didhelp
-" call Decho("exe helptags ".htpath)
- exe "helptags ".fnameescape(htpath)
- echo "did helptags"
- endif
-
- " make sure a "Press ENTER..." prompt appears to keep the messages showing!
- while filecnt <= &ch
- echomsg " "
- let filecnt= filecnt + 1
- endwhile
-
- " record actions in <.VimballRecord>
- call s:RecordInFile(home)
-
- " restore events, delete tab and buffer
- exe "sil! tabn ".vbtabnr
- setlocal nomod bh=wipe
- exe "sil! tabn ".curtabnr
- exe "sil! tabc! ".vbtabnr
- call vimball#RestoreSettings()
- call s:ChgDir(curdir)
-
-" call Dret("vimball#Vimball")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#RmVimball: remove any files, remove any directories made by any {{{2
-" previous vimball extraction based on a file of the current
-" name.
-" Usage: RmVimball (assume current file is a vimball; remove)
-" RmVimball vimballname
-fun! vimball#RmVimball(...)
-" call Dfunc("vimball#RmVimball() a:0=".a:0)
- if exists("g:vimball_norecord")
-" call Dret("vimball#RmVimball : (g:vimball_norecord)")
- return
- endif
-
- if a:0 == 0
- let curfile= expand("%:tr")
-" call Decho("case a:0=0: curfile<".curfile."> (used expand(%:tr))")
- else
- if a:1 =~ '[\/]'
- call vimball#ShowMesg(s:USAGE,"RmVimball vimballname [path]")
-" call Dret("vimball#RmVimball : suspect a:1<".a:1.">")
- return
- endif
- let curfile= a:1
-" call Decho("case a:0=".a:0.": curfile<".curfile.">")
- endif
- if curfile =~ '\.vmb$'
- let curfile= substitute(curfile,'\.vmb','','')
- elseif curfile =~ '\.vba$'
- let curfile= substitute(curfile,'\.vba','','')
- endif
- if a:0 >= 2
- let home= expand(a:2)
- else
- let home= vimball#VimballHome()
- endif
- let curdir = getcwd()
-" call Decho("home <".home.">")
-" call Decho("curfile<".curfile.">")
-" call Decho("curdir <".curdir.">")
-
- call s:ChgDir(home)
- if filereadable(".VimballRecord")
-" call Decho(".VimballRecord is readable")
-" call Decho("curfile<".curfile.">")
- keepalt keepjumps 1split
- sil! keepalt keepjumps e .VimballRecord
- let keepsrch= @/
-" call Decho('search for ^\M'.curfile.'.\m: ')
-" call Decho('search for ^\M'.curfile.'.\m{vba|vmb}: ')
-" call Decho('search for ^\M'.curfile.'\m[-0-9.]*\.{vba|vmb}: ')
- if search('^\M'.curfile."\m: ".'cw')
- let foundit= 1
- elseif search('^\M'.curfile.".\mvmb: ",'cw')
- let foundit= 2
- elseif search('^\M'.curfile.'\m[-0-9.]*\.vmb: ','cw')
- let foundit= 2
- elseif search('^\M'.curfile.".\mvba: ",'cw')
- let foundit= 1
- elseif search('^\M'.curfile.'\m[-0-9.]*\.vba: ','cw')
- let foundit= 1
- else
- let foundit = 0
- endif
- if foundit
- if foundit == 1
- let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vba: ','','')
- else
- let exestring = substitute(getline("."),'^\M'.curfile.'\m\S\{-}\.vmb: ','','')
- endif
- let s:VBRstring= substitute(exestring,'call delete(','','g')
- let s:VBRstring= substitute(s:VBRstring,"[')]",'','g')
-" call Decho("exe ".exestring)
- sil! keepalt keepjumps exe exestring
- sil! keepalt keepjumps d
- let exestring= strlen(substitute(exestring,'call delete(.\{-})|\=',"D","g"))
-" call Decho("exestring<".exestring.">")
- echomsg "removed ".exestring." files"
- else
- let s:VBRstring= ''
- let curfile = substitute(curfile,'\.vmb','','')
-" call Decho("unable to find <".curfile."> in .VimballRecord")
- if !exists("s:ok_unablefind")
- call vimball#ShowMesg(s:WARNING,"(RmVimball) unable to find <".curfile."> in .VimballRecord")
- endif
- endif
- sil! keepalt keepjumps g/^\s*$/d
- sil! keepalt keepjumps wq!
- let @/= keepsrch
- endif
- call s:ChgDir(curdir)
-
-" call Dret("vimball#RmVimball")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#Decompress: attempts to automatically decompress vimballs {{{2
-fun! vimball#Decompress(fname,...)
-" call Dfunc("Decompress(fname<".a:fname.">) a:0=".a:0)
-
- " decompression:
- if expand("%") =~ '.*\.gz' && executable("gunzip")
- " handle *.gz with gunzip
- silent exe "!gunzip ".shellescape(a:fname)
- if v:shell_error != 0
- call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) gunzip may have failed with <".a:fname.">")
- endif
- let fname= substitute(a:fname,'\.gz$','','')
- exe "e ".escape(fname,' \')
- if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif
-
- elseif expand("%") =~ '.*\.gz' && executable("gzip")
- " handle *.gz with gzip -d
- silent exe "!gzip -d ".shellescape(a:fname)
- if v:shell_error != 0
- call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "gzip -d" may have failed with <'.a:fname.">")
- endif
- let fname= substitute(a:fname,'\.gz$','','')
- exe "e ".escape(fname,' \')
- if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif
-
- elseif expand("%") =~ '.*\.bz2' && executable("bunzip2")
- " handle *.bz2 with bunzip2
- silent exe "!bunzip2 ".shellescape(a:fname)
- if v:shell_error != 0
- call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) bunzip2 may have failed with <".a:fname.">")
- endif
- let fname= substitute(a:fname,'\.bz2$','','')
- exe "e ".escape(fname,' \')
- if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif
-
- elseif expand("%") =~ '.*\.bz2' && executable("bzip2")
- " handle *.bz2 with bzip2 -d
- silent exe "!bzip2 -d ".shellescape(a:fname)
- if v:shell_error != 0
- call vimball#ShowMesg(s:WARNING,'(vimball#Decompress) "bzip2 -d" may have failed with <'.a:fname.">")
- endif
- let fname= substitute(a:fname,'\.bz2$','','')
- exe "e ".escape(fname,' \')
- if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif
-
- elseif expand("%") =~ '.*\.zip' && executable("unzip")
- " handle *.zip with unzip
- silent exe "!unzip ".shellescape(a:fname)
- if v:shell_error != 0
- call vimball#ShowMesg(s:WARNING,"(vimball#Decompress) unzip may have failed with <".a:fname.">")
- endif
- let fname= substitute(a:fname,'\.zip$','','')
- exe "e ".escape(fname,' \')
- if a:0 == 0| call vimball#ShowMesg(s:USAGE,"Source this file to extract it! (:so %)") | endif
- endif
-
- if a:0 == 0| setlocal noma bt=nofile fmr=[[[,]]] fdm=marker | endif
-
-" call Dret("Decompress")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#ShowMesg: {{{2
-fun! vimball#ShowMesg(level,msg)
-" call Dfunc("vimball#ShowMesg(level=".a:level." msg<".a:msg.">)")
-
- let rulerkeep = &ruler
- let showcmdkeep = &showcmd
- set noruler noshowcmd
- redraw!
-
- if &fo =~# '[ta]'
- echomsg "***vimball*** ".a:msg
- else
- if a:level == s:WARNING || a:level == s:USAGE
- echohl WarningMsg
- elseif a:level == s:ERROR
- echohl Error
- endif
- echomsg "***vimball*** ".a:msg
- echohl None
- endif
-
- if a:level != s:USAGE
- call inputsave()|let ok= input("Press <cr> to continue")|call inputrestore()
- endif
-
- let &ruler = rulerkeep
- let &showcmd = showcmdkeep
-
-" call Dret("vimball#ShowMesg")
-endfun
-" =====================================================================
-" s:ChgDir: change directory (in spite of Windoze) {{{2
-fun! s:ChgDir(newdir)
-" call Dfunc("ChgDir(newdir<".a:newdir.">)")
- if (has("win32") || has("win95") || has("win64") || has("win16"))
- try
- exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g'))
- catch /^Vim\%((\a\+)\)\=:E/
- call mkdir(fnameescape(substitute(a:newdir,'/','\\','g')))
- exe 'silent cd '.fnameescape(substitute(a:newdir,'/','\\','g'))
- endtry
- else
- try
- exe 'silent cd '.fnameescape(a:newdir)
- catch /^Vim\%((\a\+)\)\=:E/
- call mkdir(fnameescape(a:newdir))
- exe 'silent cd '.fnameescape(a:newdir)
- endtry
- endif
-" call Dret("ChgDir : curdir<".getcwd().">")
-endfun
-
-" ---------------------------------------------------------------------
-" s:RecordInVar: record a un-vimball command in the .VimballRecord file {{{2
-fun! s:RecordInVar(home,cmd)
-" call Dfunc("RecordInVar(home<".a:home."> cmd<".a:cmd.">)")
- if a:cmd =~ '^rmdir'
-" if !exists("s:recorddir")
-" let s:recorddir= substitute(a:cmd,'^rmdir',"call s:Rmdir",'')
-" else
-" let s:recorddir= s:recorddir."|".substitute(a:cmd,'^rmdir',"call s:Rmdir",'')
-" endif
- elseif !exists("s:recordfile")
- let s:recordfile= a:cmd
- else
- let s:recordfile= s:recordfile."|".a:cmd
- endif
-" call Dret("RecordInVar : s:recordfile<".(exists("s:recordfile")? s:recordfile : "")."> s:recorddir<".(exists("s:recorddir")? s:recorddir : "").">")
-endfun
-
-" ---------------------------------------------------------------------
-" s:RecordInFile: {{{2
-fun! s:RecordInFile(home)
-" call Dfunc("s:RecordInFile()")
- if exists("g:vimball_norecord")
-" call Dret("s:RecordInFile : g:vimball_norecord")
- return
- endif
-
- if exists("s:recordfile") || exists("s:recorddir")
- let curdir= getcwd()
- call s:ChgDir(a:home)
- keepalt keepjumps 1split
-
- let cmd= expand("%:tr").": "
-" call Decho("cmd<".cmd.">")
-
- sil! keepalt keepjumps e .VimballRecord
- setlocal ma
- $
- if exists("s:recordfile") && exists("s:recorddir")
- let cmd= cmd.s:recordfile."|".s:recorddir
- elseif exists("s:recorddir")
- let cmd= cmd.s:recorddir
- elseif exists("s:recordfile")
- let cmd= cmd.s:recordfile
- else
-" call Dret("s:RecordInFile : neither recordfile nor recorddir exist")
- return
- endif
-" call Decho("cmd<".cmd.">")
-
- " put command into buffer, write .VimballRecord `file
- keepalt keepjumps put=cmd
- sil! keepalt keepjumps g/^\s*$/d
- sil! keepalt keepjumps wq!
- call s:ChgDir(curdir)
-
- if exists("s:recorddir")
-" call Decho("unlet s:recorddir<".s:recorddir.">")
- unlet s:recorddir
- endif
- if exists("s:recordfile")
-" call Decho("unlet s:recordfile<".s:recordfile.">")
- unlet s:recordfile
- endif
- else
-" call Decho("s:record[file|dir] doesn't exist")
- endif
-
-" call Dret("s:RecordInFile")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#VimballHome: determine/get home directory path (usually from rtp) {{{2
-fun! vimball#VimballHome()
-" call Dfunc("vimball#VimballHome()")
- if exists("g:vimball_home")
- let home= g:vimball_home
- else
- " go to vim plugin home
- for home in split(&rtp,',') + ['']
- if isdirectory(home) && filewritable(home) | break | endif
- let basehome= substitute(home,'[/\\]\.vim$','','')
- if isdirectory(basehome) && filewritable(basehome)
- let home= basehome."/.vim"
- break
- endif
- endfor
- if home == ""
- " just pick the first directory
- let home= substitute(&rtp,',.*$','','')
- endif
- if (has("win32") || has("win95") || has("win64") || has("win16"))
- let home= substitute(home,'/','\\','g')
- endif
- endif
- " insure that the home directory exists
-" call Decho("picked home<".home.">")
- if !isdirectory(home)
- if exists("g:vimball_mkdir")
-" call Decho("home<".home."> isn't a directory -- making it now with g:vimball_mkdir<".g:vimball_mkdir.">")
-" call Decho("system(".g:vimball_mkdir." ".shellescape(home).")")
- call system(g:vimball_mkdir." ".shellescape(home))
- else
-" call Decho("home<".home."> isn't a directory -- making it now with mkdir()")
- call mkdir(home)
- endif
- endif
-" call Dret("vimball#VimballHome <".home.">")
- return home
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#SaveSettings: {{{2
-fun! vimball#SaveSettings()
-" call Dfunc("SaveSettings()")
- let s:makeep = getpos("'a")
- let s:regakeep= @a
- if exists("+acd")
- let s:acdkeep = &acd
- endif
- let s:eikeep = &ei
- let s:fenkeep = &l:fen
- let s:hidkeep = &hidden
- let s:ickeep = &ic
- let s:lzkeep = &lz
- let s:pmkeep = &pm
- let s:repkeep = &report
- let s:vekeep = &ve
- let s:ffkeep = &l:ff
- let s:swfkeep = &l:swf
- if exists("+acd")
- setlocal ei=all ve=all noacd nofen noic report=999 nohid bt= ma lz pm= ff=unix noswf
- else
- setlocal ei=all ve=all nofen noic report=999 nohid bt= ma lz pm= ff=unix noswf
- endif
- " vimballs should be in unix format
- setlocal ff=unix
-" call Dret("SaveSettings")
-endfun
-
-" ---------------------------------------------------------------------
-" vimball#RestoreSettings: {{{2
-fun! vimball#RestoreSettings()
-" call Dfunc("RestoreSettings()")
- let @a = s:regakeep
- if exists("+acd")
- let &acd = s:acdkeep
- endif
- let &l:fen = s:fenkeep
- let &hidden = s:hidkeep
- let &ic = s:ickeep
- let &lz = s:lzkeep
- let &pm = s:pmkeep
- let &report = s:repkeep
- let &ve = s:vekeep
- let &ei = s:eikeep
- let &l:ff = s:ffkeep
- if s:makeep[0] != 0
- " restore mark a
-" call Decho("restore mark-a: makeep=".string(makeep))
- call setpos("'a",s:makeep)
- endif
- if exists("+acd")
- unlet s:acdkeep
- endif
- unlet s:regakeep s:eikeep s:fenkeep s:hidkeep s:ickeep s:repkeep s:vekeep s:makeep s:lzkeep s:pmkeep s:ffkeep
-" call Dret("RestoreSettings")
-endfun
-
-let &cpo = s:keepcpo
-unlet s:keepcpo
-
-" ---------------------------------------------------------------------
-" Modelines: {{{1
-" vim: fdm=marker
diff --git a/runtime/pack/dist/opt/vimball/doc/vimball.txt b/runtime/pack/dist/opt/vimball/doc/vimball.txt
deleted file mode 100644
index 602fe85954..0000000000
--- a/runtime/pack/dist/opt/vimball/doc/vimball.txt
+++ /dev/null
@@ -1,273 +0,0 @@
-*vimball.txt* For Vim version 7.4. Last change: 2012 Jan 17
-
- ----------------
- Vimball Archiver
- ----------------
-
-Author: Charles E. Campbell, Jr. <NdrOchip@ScampbellPfamily.AbizM>
- (remove NOSPAM from Campbell's email first)
-Copyright: (c) 2004-2012 by Charles E. Campbell, Jr. *Vimball-copyright*
- The VIM LICENSE (see |copyright|) applies to the files in this
- package, including vimballPlugin.vim, vimball.vim, and pi_vimball.txt.
- except use "vimball" instead of "VIM". Like anything else that's free,
- vimball.vim and its associated files are provided *as is* and comes with
- no warranty of any kind, either expressed or implied. No guarantees
- of merchantability. No guarantees of suitability for any purpose. By
- using this plugin, you agree that in no event will the copyright
- holder be liable for any damages resulting from the use of this
- software. Use at your own risk!
-
-==============================================================================
-1. Contents *vba* *vimball* *vimball-contents*
-
- 1. Contents......................................: |vimball-contents|
- 2. Vimball Introduction..........................: |vimball-intro|
- 3. Vimball Manual................................: |vimball-manual|
- MkVimball.....................................: |:MkVimball|
- UseVimball....................................: |:UseVimball|
- RmVimball.....................................: |:RmVimball|
- 4. Vimball History...............................: |vimball-history|
-
-
-==============================================================================
-2. Vimball Introduction *vimball-intro*
-
- Vimball is intended to make life simpler for users of plugins. All
- a user needs to do with a vimball is: >
- vim someplugin.vba
- :so %
- :q
-< and the plugin and all its components will be installed into their
- appropriate directories. Note that one doesn't need to be in any
- particular directory when one does this. Plus, any help for the
- plugin will also be automatically installed.
-
- If a user has decided to use the AsNeeded plugin, vimball is smart
- enough to put scripts nominally intended for .vim/plugin/ into
- .vim/AsNeeded/ instead.
-
- Removing a plugin that was installed with vimball is really easy: >
- vim
- :RmVimball someplugin
-< This operation is not at all easy for zips and tarballs, for example.
-
- Vimball examines the user's |'runtimepath'| to determine where to put
- the scripts. The first directory mentioned on the runtimepath is
- usually used if possible. Use >
- :echo &rtp
-< to see that directory.
-
-
-==============================================================================
-3. Vimball Manual *vimball-manual*
-
-MAKING A VIMBALL *:MkVimball*
- :[range]MkVimball[!] filename [path]
-
- The range is composed of lines holding paths to files to be included
- in your new vimball, omitting the portion of the paths that is
- normally specified by the runtimepath (|'rtp'|). As an example: >
- plugin/something.vim
- doc/something.txt
-< using >
- :[range]MkVimball filename
-<
- on this range of lines will create a file called "filename.vba" which
- can be used by Vimball.vim to re-create these files. If the
- "filename.vba" file already exists, then MkVimball will issue a
- warning and not create the file. Note that these paths are relative
- to your .vim (vimfiles) directory, and the files should be in that
- directory. The vimball plugin normally uses the first |'runtimepath'|
- directory that exists as a prefix; don't use absolute paths, unless
- the user has specified such a path.
-
- If you use the exclamation point (!), then MkVimball will create the
- "filename.vba" file, overwriting it if it already exists. This
- behavior resembles that for |:w|.
-
- If you wish to force slashes into the filename, that can also be done
- by using the exclamation mark (ie. :MkVimball! path/filename).
-
- The tip at https://vim.wikia.com/wiki/Using_VimBall_with_%27Make%27
- has a good idea on how to automate the production of vimballs using
- make.
-
-
-MAKING DIRECTORIES VIA VIMBALLS *g:vimball_mkdir*
-
- First, the |mkdir()| command is tried (not all systems support it).
-
- If it doesn't exist, then if g:vimball_mkdir doesn't exist, it is set
- as follows: >
- |g:netrw_local_mkdir|, if it exists
- "mkdir" , if it is executable
- "makedir" , if it is executable
- Otherwise , it is undefined.
-< One may explicitly specify the directory making command using
- g:vimball_mkdir. This command is used to make directories that
- are needed as indicated by the vimball.
-
-
-CONTROLLING THE VIMBALL EXTRACTION DIRECTORY *g:vimball_home*
-
- You may override the use of the |'runtimepath'| by specifying a
- variable, g:vimball_home.
-
- *vimball-extract*
- vim filename.vba
-
- Simply editing a Vimball will cause Vimball.vim to tell the user to
- source the file to extract its contents.
-
- Extraction will only proceed if the first line of a putative vimball
- file holds the "Vimball Archiver by Charles E. Campbell, Jr., Ph.D."
- line.
-
-LISTING FILES IN A VIMBALL *:VimballList*
-
- :VimballList
-
- This command will tell Vimball to list the files in the archive, along
- with their lengths in lines.
-
-MANUALLY INVOKING VIMBALL EXTRACTION *:UseVimball*
-
- :UseVimball [path]
-
- This command is contained within the vimball itself; it invokes the
- vimball#Vimball() routine which is responsible for unpacking the
- vimball. One may choose to execute it by hand instead of sourcing
- the vimball; one may also choose to specify a path for the
- installation, thereby overriding the automatic choice of the first
- existing directory on the |'runtimepath'|.
-
-REMOVING A VIMBALL *:RmVimball*
-
- :RmVimball vimballfile [path]
-
- This command removes all files generated by the specified vimball
- (but not any directories it may have made). One may choose a path
- for de-installation, too (see |'runtimepath'|); otherwise, the
- default is the first existing directory on the |'runtimepath'|.
- To implement this, a file (.VimballRecord) is made in that directory
- containing a record of what files need to be removed for all vimballs
- used thus far.
-
-PREVENTING LOADING
-
- If for some reason you don't want to be able to extract plugins
- using vimballs: you may prevent the loading of vimball.vim by
- putting the following two variables in your <.vimrc>: >
-
- let g:loaded_vimballPlugin= 1
- let g:loaded_vimball = 1
-<
-WINDOWS *vimball-windows*
-
- Many vimball files are compressed with gzip. Windows, unfortunately,
- does not come provided with a tool to decompress gzip'ped files.
- Fortunately, there are a number of tools available for Windows users
- to un-gzip files:
->
- Item Tool/Suite Free Website
- ---- ---------- ---- -------
- 7zip tool y https://www.7-zip.org/
- Winzip tool n https://www.winzip.com/downwz.htm
- unxutils suite y https://unxutils.sourceforge.net/
- cygwin suite y https://www.cygwin.com/
- GnuWin32 suite y https://gnuwin32.sourceforge.net/
- MinGW suite y https://www.mingw.org/
-<
-
-==============================================================================
-4. Vimball History *vimball-history* {{{1
-
- 34 : Sep 22, 2011 * "UseVimball path" now supports a non-full path by
- prepending the current directory to it.
- 33 : Apr 02, 2011 * Gave priority to *.vmb over *.vba
- * Changed silent! to sil! (shorter)
- * Safed |'swf'| setting (during vimball extraction,
- its now turned off)
- 32 : May 19, 2010 * (Christian Brabandt) :so someplugin.vba and
- :so someplugin.vba.gz (and the other supported
- compression types) now works
- * (Jan Steffens) added support for xz compression
- * fenc extraction was erroneously picking up the
- end of the line number when no file encoding
- was present. Fixed.
- * By request, beginning the switchover from the vba
- extension to vmb. Currently both are supported;
- MkVimball, however, now will create *.vmb files.
- Feb 11, 2011 * motoyakurotsu reported an error with vimball's
- handling of zero-length files
- 30 : Dec 08, 2008 * fnameescape() inserted to protect error
- messaging using corrupted filenames from
- causing problems
- * RmVimball supports filenames that would
- otherwise be considered to have "magic"
- characters (ie. Abc[1].vba)
- Feb 18, 2009 * s:Escape(), g:vimball_shq, and g:netrw_shq
- removed (shellescape() used directly)
- Oct 05, 2009 * (Nikolai Weibull) suggested that MkVimball
- be allowed to use slashes in the filename.
- 26 : May 27, 2008 * g:vimball_mkdir usage installed. Makes the
- $HOME/.vim (or $HOME\vimfiles) directory if
- necessary.
- May 30, 2008 * (tnx to Bill McCarthy) found and fixed a bug:
- vimball wasn't updating plugins to AsNeeded/
- when it should
- 25 : Mar 24, 2008 * changed vimball#Vimball() to recognize doc/*.??x
- files as help files, too.
- Apr 18, 2008 * RmVimball command is now protected by saving and
- restoring settings -- in particular, acd was
- causing problems as reported by Zhang Shuhan
- 24 : Nov 15, 2007 * g:vimball_path_escape used by s:Path() to
- prevent certain characters from causing trouble
- (defunct: |fnameescape()| and |shellescape()|
- now used instead)
- 22 : Mar 21, 2007 * uses setlocal instead of set during BufEnter
- 21 : Nov 27, 2006 * (tnx to Bill McCarthy) vimball had a header
- handling problem and it now changes \s to /s
- 20 : Nov 20, 2006 * substitute() calls have all had the 'e' flag
- removed.
- 18 : Aug 01, 2006 * vimballs now use folding to easily display their
- contents.
- * if a user has AsNeeded/somefile, then vimball
- will extract plugin/somefile to the AsNeeded/
- directory
- 17 : Jun 28, 2006 * changes all \s to /s internally for Windows
- 16 : Jun 15, 2006 * A. Mechelynck's idea to allow users to specify
- installation root paths implemented for
- UseVimball, MkVimball, and RmVimball.
- * RmVimball implemented
- 15 : Jun 13, 2006 * bugfix
- 14 : May 26, 2006 * bugfixes
- 13 : May 01, 2006 * exists("&acd") used to determine if the acd
- option exists
- 12 : May 01, 2006 * bugfix - the |'acd'| option is not always defined
- 11 : Apr 27, 2006 * VimballList would create missing subdirectories that
- the vimball specified were needed. Fixed.
- 10 : Apr 27, 2006 * moved all setting saving/restoration to a pair of
- functions. Included some more settings in them
- which frequently cause trouble.
- 9 : Apr 26, 2006 * various changes to support Windows' predilection
- for backslashes and spaces in file and directory
- names.
- 7 : Apr 25, 2006 * bypasses foldenable
- * uses more exe and less norm! (:yank :put etc)
- * does better at insuring a "Press ENTER" prompt
- appears to keep its messages visible
- 4 : Mar 31, 2006 * BufReadPost seems to fire twice; BufReadEnter
- only fires once, so the "Source this file..."
- message is now issued only once.
- 3 : Mar 20, 2006 * removed query, now requires sourcing to be
- extracted (:so %). Message to that effect
- included.
- * :VimballList now shows files that would be
- extracted.
- 2 : Mar 20, 2006 * query, :UseVimball included
- 1 : Mar 20, 2006 * initial release
-
-
-==============================================================================
-vim:tw=78:ts=8:ft=help:fdm=marker
diff --git a/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim b/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim
deleted file mode 100644
index d7473a0296..0000000000
--- a/runtime/pack/dist/opt/vimball/plugin/vimballPlugin.vim
+++ /dev/null
@@ -1,43 +0,0 @@
-" vimballPlugin : construct a file containing both paths and files
-" Author: Charles E. Campbell
-" Copyright: (c) 2004-2014 by Charles E. Campbell
-" The VIM LICENSE applies to Vimball.vim, and Vimball.txt
-" (see |copyright|) except use "Vimball" instead of "Vim".
-" No warranty, express or implied.
-" *** *** Use At-Your-Own-Risk! *** ***
-"
-" (Rom 2:1 WEB) Therefore you are without excuse, O man, whoever you are who
-" judge. For in that which you judge another, you condemn yourself. For
-" you who judge practice the same things.
-" GetLatestVimScripts: 1502 1 :AutoInstall: vimball.vim
-
-" ---------------------------------------------------------------------
-" Load Once: {{{1
-if &cp || exists("g:loaded_vimballPlugin")
- finish
-endif
-let g:loaded_vimballPlugin = "v37"
-let s:keepcpo = &cpo
-set cpo&vim
-
-" ------------------------------------------------------------------------------
-" Public Interface: {{{1
-com! -range -complete=file -nargs=+ -bang MkVimball call vimball#MkVimball(<line1>,<line2>,<bang>0,<f-args>)
-com! -nargs=? -complete=dir UseVimball call vimball#Vimball(1,<f-args>)
-com! -nargs=0 VimballList call vimball#Vimball(0)
-com! -nargs=* -complete=dir RmVimball call vimball#SaveSettings()|call vimball#RmVimball(<f-args>)|call vimball#RestoreSettings()
-augroup Vimball
- au!
- au BufEnter *.vba,*.vba.gz,*.vba.bz2,*.vba.zip,*.vba.xz setlocal bt=nofile fmr=[[[,]]] fdm=marker|if &ff != 'unix'|setlocal ma ff=unix noma|endif|if line('$') > 1|call vimball#ShowMesg(0,"Source this file to extract it! (:so %)")|endif
- au SourceCmd *.vba.gz,*.vba.bz2,*.vba.zip,*.vba.xz let s:origfile=expand("%")|if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|endif|call vimball#Decompress(expand("<amatch>"))|so %|if s:origfile!=expand("<afile>")|close|endif
- au SourceCmd *.vba if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|call vimball#Vimball(1)|close|else|call vimball#Vimball(1)|endif
- au BufEnter *.vmb,*.vmb.gz,*.vmb.bz2,*.vmb.zip,*.vmb.xz setlocal bt=nofile fmr=[[[,]]] fdm=marker|if &ff != 'unix'|setlocal ma ff=unix noma|endif|if line('$') > 1|call vimball#ShowMesg(0,"Source this file to extract it! (:so %)")|endif
- au SourceCmd *.vmb.gz,*.vmb.bz2,*.vmb.zip,*.vmb.xz let s:origfile=expand("%")|if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|endif|call vimball#Decompress(expand("<amatch>"))|so %|if s:origfile!=expand("<afile>")|close|endif
- au SourceCmd *.vmb if expand("%")!=expand("<afile>") | exe "1sp" fnameescape(expand("<afile>"))|call vimball#Vimball(1)|close|else|call vimball#Vimball(1)|endif
-augroup END
-
-" =====================================================================
-" Restoration And Modelines: {{{1
-" vim: fdm=marker
-let &cpo= s:keepcpo
-unlet s:keepcpo
diff --git a/runtime/plugin/editorconfig.lua b/runtime/plugin/editorconfig.lua
index 54cd0e828e..a96919e1fe 100644
--- a/runtime/plugin/editorconfig.lua
+++ b/runtime/plugin/editorconfig.lua
@@ -3,7 +3,7 @@ vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead', 'BufFilePost' }, {
group = group,
callback = function(args)
-- Buffer-local enable has higher priority
- local enable = vim.F.if_nil(vim.b.editorconfig, vim.F.if_nil(vim.g.editorconfig, true))
+ local enable = vim.F.if_nil(vim.b.editorconfig, vim.g.editorconfig, true)
if not enable then
return
end
diff --git a/runtime/plugin/gzip.vim b/runtime/plugin/gzip.vim
index 7214488579..c02bd99567 100644
--- a/runtime/plugin/gzip.vim
+++ b/runtime/plugin/gzip.vim
@@ -1,6 +1,7 @@
" Vim plugin for editing compressed files.
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2016 Oct 30
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Exit quickly when:
" - this plugin was already loaded
diff --git a/runtime/plugin/health.vim b/runtime/plugin/health.vim
deleted file mode 100644
index 66ae8fb239..0000000000
--- a/runtime/plugin/health.vim
+++ /dev/null
@@ -1 +0,0 @@
-autocmd CmdUndefined CheckHealth checkhealth
diff --git a/runtime/plugin/man.lua b/runtime/plugin/man.lua
index 4b1528b0cb..512b1f63e8 100644
--- a/runtime/plugin/man.lua
+++ b/runtime/plugin/man.lua
@@ -16,6 +16,7 @@ vim.api.nvim_create_user_command('Man', function(params)
end, {
bang = true,
bar = true,
+ range = true,
addr = 'other',
nargs = '*',
complete = function(...)
@@ -28,6 +29,7 @@ local augroup = vim.api.nvim_create_augroup('man', {})
vim.api.nvim_create_autocmd('BufReadCmd', {
group = augroup,
pattern = 'man://*',
+ nested = true,
callback = function(params)
require('man').read_page(vim.fn.matchstr(params.match, 'man://\\zs.*'))
end,
diff --git a/runtime/plugin/matchparen.vim b/runtime/plugin/matchparen.vim
index 3982489b92..4235a0d39b 100644
--- a/runtime/plugin/matchparen.vim
+++ b/runtime/plugin/matchparen.vim
@@ -1,6 +1,7 @@
" Vim plugin for showing matching parens
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Dec 01
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Oct 20
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Exit quickly when:
" - this plugin was already loaded (or disabled)
@@ -17,12 +18,15 @@ if !exists("g:matchparen_insert_timeout")
let g:matchparen_insert_timeout = 60
endif
+let s:has_matchaddpos = exists('*matchaddpos')
+
augroup matchparen
" Replace all matchparen autocommands
autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair()
autocmd! WinLeave,BufLeave * call s:Remove_Matches()
if exists('##TextChanged')
autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair()
+ autocmd! TextChangedP * call s:Remove_Matches()
endif
augroup END
@@ -37,6 +41,9 @@ set cpo-=C
" The function that is invoked (very often) to define a ":match" highlighting
" for any matching paren.
func s:Highlight_Matching_Pair()
+ if !exists("w:matchparen_ids")
+ let w:matchparen_ids = []
+ endif
" Remove any previous match.
call s:Remove_Matches()
@@ -108,8 +115,9 @@ func s:Highlight_Matching_Pair()
" searchpairpos()'s skip argument.
" We match "escape" for special items, such as lispEscapeSpecial, and
" match "symbol" for lispBarSymbol.
- let s_skip = '!empty(filter(map(synstack(line("."), col(".")), ''synIDattr(v:val, "name")''), ' .
- \ '''v:val =~? "string\\|character\\|singlequote\\|escape\\|symbol\\|comment"''))'
+ let s_skip = 'synstack(".", col("."))'
+ \ . '->indexof({_, id -> synIDattr(id, "name") =~? '
+ \ . '"string\\|character\\|singlequote\\|escape\\|symbol\\|comment"}) >= 0'
" If executing the expression determines that the cursor is currently in
" one of the syntax types, then we want searchpairpos() to find the pair
" within those syntax types (i.e., not skip). Otherwise, the cursor is
@@ -183,11 +191,12 @@ func s:Highlight_Matching_Pair()
" If a match is found setup match highlighting.
if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom
- if exists('*matchaddpos')
- call matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10, 3)
+ if s:has_matchaddpos
+ call add(w:matchparen_ids, matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10))
else
exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) .
\ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/'
+ call add(w:matchparen_ids, 3)
endif
let w:paren_hl_on = 1
endif
@@ -195,12 +204,13 @@ endfunction
func s:Remove_Matches()
if exists('w:paren_hl_on') && w:paren_hl_on
- silent! call matchdelete(3)
+ while !empty(w:matchparen_ids)
+ silent! call remove(w:matchparen_ids, 0)->matchdelete()
+ endwhile
let w:paren_hl_on = 0
endif
endfunc
-
" Define commands that will disable and enable the plugin.
command DoMatchParen call s:DoMatchParen()
command NoMatchParen call s:NoMatchParen()
diff --git a/runtime/plugin/netrwPlugin.vim b/runtime/plugin/netrwPlugin.vim
index d309f81484..a347781e6f 100644
--- a/runtime/plugin/netrwPlugin.vim
+++ b/runtime/plugin/netrwPlugin.vim
@@ -20,7 +20,7 @@
if &cp || exists("g:loaded_netrwPlugin")
finish
endif
-let g:loaded_netrwPlugin = "v171"
+let g:loaded_netrwPlugin = "v173"
let s:keepcpo = &cpo
set cpo&vim
"DechoRemOn
diff --git a/runtime/plugin/nvim.lua b/runtime/plugin/nvim.lua
index 815886f896..743d3044b6 100644
--- a/runtime/plugin/nvim.lua
+++ b/runtime/plugin/nvim.lua
@@ -1,7 +1,24 @@
vim.api.nvim_create_user_command('Inspect', function(cmd)
if cmd.bang then
- vim.pretty_print(vim.inspect_pos())
+ vim.print(vim.inspect_pos())
else
vim.show_pos()
end
end, { desc = 'Inspect highlights and extmarks at the cursor', bang = true })
+
+vim.api.nvim_create_user_command('InspectTree', function(cmd)
+ if cmd.mods ~= '' or cmd.count ~= 0 then
+ local count = cmd.count ~= 0 and cmd.count or ''
+ local new = cmd.mods ~= '' and 'new' or 'vnew'
+
+ vim.treesitter.inspect_tree({
+ command = ('%s %s%s'):format(cmd.mods, count, new),
+ })
+ else
+ vim.treesitter.inspect_tree()
+ end
+end, { desc = 'Inspect treesitter language tree for buffer', count = true })
+
+vim.api.nvim_create_user_command('EditQuery', function(cmd)
+ vim.treesitter.query.edit(cmd.fargs[1])
+end, { desc = 'Edit treesitter query', nargs = '?' })
diff --git a/runtime/plugin/osc52.lua b/runtime/plugin/osc52.lua
new file mode 100644
index 0000000000..374b70066f
--- /dev/null
+++ b/runtime/plugin/osc52.lua
@@ -0,0 +1,36 @@
+local tty = vim.iter(vim.api.nvim_list_uis()):any(function(ui)
+ return ui.chan == 1 and ui.stdout_tty
+end)
+
+if not tty or vim.g.clipboard ~= nil or vim.o.clipboard ~= '' or not os.getenv('SSH_TTY') then
+ return
+end
+
+require('vim.termcap').query('Ms', function(cap, seq)
+ assert(cap == 'Ms')
+
+ -- Check 'clipboard' and g:clipboard again to avoid a race condition
+ if vim.o.clipboard ~= '' or vim.g.clipboard ~= nil then
+ return
+ end
+
+ -- If the terminal reports a sequence other than OSC 52 for the Ms capability
+ -- then ignore it. We only support OSC 52 (for now)
+ if not seq:match('^\027%]52') then
+ return
+ end
+
+ local osc52 = require('vim.ui.clipboard.osc52')
+
+ vim.g.clipboard = {
+ name = 'OSC 52',
+ copy = {
+ ['+'] = osc52.copy('+'),
+ ['*'] = osc52.copy('*'),
+ },
+ paste = {
+ ['+'] = osc52.paste('+'),
+ ['*'] = osc52.paste('*'),
+ },
+ }
+end)
diff --git a/runtime/plugin/rplugin.vim b/runtime/plugin/rplugin.vim
index 52f4bd05bc..bb637fcb77 100644
--- a/runtime/plugin/rplugin.vim
+++ b/runtime/plugin/rplugin.vim
@@ -14,7 +14,9 @@ function! s:GetManifestPath() abort
let dest = stdpath('data')
if !empty(dest)
if !isdirectory(dest)
- call mkdir(dest, 'p', 0700)
+ if getftype(dest) != "link"
+ call mkdir(dest, 'p', 0700)
+ endif
endif
let manifest_base = dest
endif
diff --git a/runtime/plugin/tarPlugin.vim b/runtime/plugin/tarPlugin.vim
index d55492a93e..384a3ed823 100644
--- a/runtime/plugin/tarPlugin.vim
+++ b/runtime/plugin/tarPlugin.vim
@@ -47,7 +47,6 @@ augroup tar
au BufReadCmd *.tar.zst call tar#Browse(expand("<amatch>"))
au BufReadCmd *.tzs call tar#Browse(expand("<amatch>"))
augroup END
-com! -nargs=? -complete=file Vimuntar call tar#Vimuntar(<q-args>)
" ---------------------------------------------------------------------
" Restoration And Modelines: {{{1
diff --git a/runtime/plugin/tohtml.vim b/runtime/plugin/tohtml.vim
index 08df19b4f9..56eb2c15bf 100644
--- a/runtime/plugin/tohtml.vim
+++ b/runtime/plugin/tohtml.vim
@@ -1,6 +1,6 @@
" Vim plugin for converting a syntax highlighted file to HTML.
" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2019 Nov 13
+" Last Change: 2023 Sep 07
"
" The core of the code is in $VIMRUNTIME/autoload/tohtml.vim and
" $VIMRUNTIME/syntax/2html.vim
@@ -8,11 +8,42 @@
if exists('g:loaded_2html_plugin')
finish
endif
-let g:loaded_2html_plugin = 'vim8.1_v2'
+let g:loaded_2html_plugin = 'vim9.0_v2'
"
" Changelog: {{{
-" 8.1_v2 (this version): - Fix Bitbucket issue #19: fix calculation of tab
+" 9.0_v2 (this version): - Warn if using deprecated g:use_xhtml option
+" - Change default g:html_use_input_for_pc to "none"
+" instead of "fallback". All modern browsers support
+" the "user-select: none" and "content:" CSS
+" properties so the older method relying on extra
+" markup and unspecified browser/app clipboard
+" handling is only needed in rare special cases.
+" - Fix SourceForge issue #33: generate diff filler
+" correctly when new lines have been added to or
+" removed from end of buffer.
+" - Fix SourceForge issue #32/Vim Github issue #8547:
+" use translated highlight ID for styling the
+" special-use group names (e.g. LineNr) used
+" directly by name in the 2html processing.
+" - Fix SourceForge issue #26, refactoring to use
+" :let-heredoc style string assignment and
+" additional fixes for ".." vs. "." style string
+" concatenation. Requires Vim v8.1.1354 or higher.
+" 9.0_v1 (Vim 9.0.1275): - Implement g:html_no_doc and g:html_no_modeline
+" for diff mode. Add tests.
+" (Vim 9.0.1122): NOTE: no version string update for this version!
+" - Bugfix for variable name in g:html_no_doc
+" (Vim 9.0.0819): NOTE: no version string update for this version!
+" - Add options g:html_no_doc, g:html_no_lines,
+" and g:html_no_modeline (partially included in Vim
+" runtime prior to version string update).
+" - Updates for new Vim9 string append style (i.e. use
+" ".." instead of "."). Requires Vim version
+" 8.1.1114 or higher.
+"
+" 8.1 updates: {{{
+" 8.1_v2 (Vim 8.1.2312): - Fix SourceForge issue #19: fix calculation of tab
" stop position to use in expanding a tab, when that
" tab occurs after a syntax match which in turn
" comes after previously expanded tabs.
@@ -20,17 +51,17 @@ let g:loaded_2html_plugin = 'vim8.1_v2'
" destination file to ignore FileType events;
" speeds up processing when the destination file
" already exists and HTML highlight takes too long.
-" - Fix Bitbucket issue #20: progress bar could not be
+" - Fix SourceForge issue #20: progress bar could not be
" seen when DiffDelete background color matched
" StatusLine background color. Added TOhtmlProgress
" highlight group for manual user override, but
" calculate it to be visible compared to StatusLine
" by default.
-" - Fix Bitbucket issue #1: Remove workaround for old
+" - Fix SourceForge issue #1: Remove workaround for old
" browsers which don't support 'ch' CSS unit, since
" all modern browsers, including IE>=9, support it.
-" - Fix Bitbucket issue #10: support termguicolors
-" - Fix Bitbucket issue #21: default to using
+" - Fix SourceForge issue #10: support termguicolors
+" - Fix SourceForge issue #21: default to using
" generated content instead of <input> tags for
" uncopyable text, so that text is correctly
" prevented from being copied in chrome. Use
@@ -41,13 +72,14 @@ let g:loaded_2html_plugin = 'vim8.1_v2'
" - Fix fallback sizing of <input> tags for browsers
" without "ch" support.
" - Fix cursor on unselectable diff filler text.
-" 8.1_v1 (Vim 8.1.0528): - Fix Bitbucket issue #6: Don't generate empty
+" 8.1_v1 (Vim 8.1.0528): - Fix SourceForge issue #6: Don't generate empty
" script tag.
-" - Fix Bitbucket issue #5: javascript should
+" - Fix SourceForge issue #5: javascript should
" declare variables with "var".
-" - Fix Bitbucket issue #13: errors thrown sourcing
+" - Fix SourceForge issue #13: errors thrown sourcing
" 2html.vim directly when plugins not loaded.
-" - Fix Bitbucket issue #16: support 'vartabstop'.
+" - Fix SourceForge issue #16: support 'vartabstop'.
+"}}}
"
" 7.4 updates: {{{
" 7.4_v2 (Vim 7.4.0899): Fix error raised when converting a diff containing
@@ -152,7 +184,7 @@ let g:loaded_2html_plugin = 'vim8.1_v2'
" TODO: {{{
" * Check the issue tracker:
-" https://bitbucket.org/fritzophrenic/vim-tohtml/issues?status=new&status=open
+" https://sourceforge.net/p/vim-tohtml/issues/search/?q=%21status%3Aclosed
" * Options for generating the CSS in external style sheets. New :TOcss
" command to convert the current color scheme into a (mostly) generic CSS
" stylesheet which can be re-used. Alternate stylesheet support? Good start
diff --git a/runtime/plugin/zipPlugin.vim b/runtime/plugin/zipPlugin.vim
index edc52713a8..c3118a361d 100644
--- a/runtime/plugin/zipPlugin.vim
+++ b/runtime/plugin/zipPlugin.vim
@@ -1,6 +1,6 @@
" zipPlugin.vim: Handles browsing zipfiles
" PLUGIN PORTION
-" Date: Jan 07, 2020
+" Date: Dec 07, 2021
" Maintainer: Charles E Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
" License: Vim License (see vim's :help license)
" Copyright: Copyright (C) 2005-2016 Charles E. Campbell {{{1
@@ -20,14 +20,14 @@
if &cp || exists("g:loaded_zipPlugin")
finish
endif
-let g:loaded_zipPlugin = "v32"
+let g:loaded_zipPlugin = "v33"
let s:keepcpo = &cpo
set cpo&vim
" ---------------------------------------------------------------------
" Options: {{{1
if !exists("g:zipPlugin_ext")
- let g:zipPlugin_ext='*.aar,*.apk,*.celzip,*.crtx,*.docm,*.docx,*.dotm,*.dotx,*.ear,*.epub,*.gcsx,*.glox,*.gqsx,*.ja,*.jar,*.kmz,*.odb,*.odc,*.odf,*.odg,*.odi,*.odm,*.odp,*.ods,*.odt,*.otc,*.otf,*.otg,*.oth,*.oti,*.otp,*.ots,*.ott,*.oxt,*.potm,*.potx,*.ppam,*.ppsm,*.ppsx,*.pptm,*.pptx,*.sldx,*.thmx,*.vdw,*.war,*.wsz,*.xap,*.xlam,*.xlam,*.xlsb,*.xlsm,*.xlsx,*.xltm,*.xltx,*.xpi,*.zip'
+ let g:zipPlugin_ext='*.aar,*.apk,*.celzip,*.crtx,*.docm,*.docx,*.dotm,*.dotx,*.ear,*.epub,*.gcsx,*.glox,*.gqsx,*.ja,*.jar,*.kmz,*.odb,*.odc,*.odf,*.odg,*.odi,*.odm,*.odp,*.ods,*.odt,*.otc,*.otf,*.otg,*.oth,*.oti,*.otp,*.ots,*.ott,*.oxt,*.potm,*.potx,*.ppam,*.ppsm,*.ppsx,*.pptm,*.pptx,*.sldx,*.thmx,*.vdw,*.war,*.wsz,*.xap,*.xlam,*.xlsb,*.xlsm,*.xlsx,*.xltm,*.xltx,*.xpi,*.zip'
endif
" ---------------------------------------------------------------------
diff --git a/runtime/queries/bash/folds.scm b/runtime/queries/bash/folds.scm
new file mode 100644
index 0000000000..851c67eed4
--- /dev/null
+++ b/runtime/queries/bash/folds.scm
@@ -0,0 +1,8 @@
+[
+ (function_definition)
+ (if_statement)
+ (case_statement)
+ (for_statement)
+ (while_statement)
+ (c_style_for_statement)
+] @fold
diff --git a/runtime/queries/bash/highlights.scm b/runtime/queries/bash/highlights.scm
new file mode 100644
index 0000000000..23bf03e697
--- /dev/null
+++ b/runtime/queries/bash/highlights.scm
@@ -0,0 +1,145 @@
+(simple_expansion) @none
+(expansion
+ "${" @punctuation.special
+ "}" @punctuation.special) @none
+[
+ "("
+ ")"
+ "(("
+ "))"
+ "{"
+ "}"
+ "["
+ "]"
+ "[["
+ "]]"
+ ] @punctuation.bracket
+
+[
+ ";"
+ ";;"
+ (heredoc_start)
+ ] @punctuation.delimiter
+
+[
+ "$"
+] @punctuation.special
+
+[
+ ">"
+ ">>"
+ "<"
+ "<<"
+ "&"
+ "&&"
+ "|"
+ "||"
+ "="
+ "=~"
+ "=="
+ "!="
+ ] @operator
+
+[
+ (string)
+ (raw_string)
+ (ansi_c_string)
+ (heredoc_body)
+] @string @spell
+
+(variable_assignment (word) @string)
+
+[
+ "if"
+ "then"
+ "else"
+ "elif"
+ "fi"
+ "case"
+ "in"
+ "esac"
+ ] @conditional
+
+[
+ "for"
+ "do"
+ "done"
+ "select"
+ "until"
+ "while"
+ ] @repeat
+
+[
+ "declare"
+ "export"
+ "local"
+ "readonly"
+ "unset"
+ ] @keyword
+
+"function" @keyword.function
+
+(special_variable_name) @constant
+
+; trap -l
+((word) @constant.builtin
+ (#match? @constant.builtin "^SIG(HUP|INT|QUIT|ILL|TRAP|ABRT|BUS|FPE|KILL|USR[12]|SEGV|PIPE|ALRM|TERM|STKFLT|CHLD|CONT|STOP|TSTP|TT(IN|OU)|URG|XCPU|XFSZ|VTALRM|PROF|WINCH|IO|PWR|SYS|RTMIN([+]([1-9]|1[0-5]))?|RTMAX(-([1-9]|1[0-4]))?)$"))
+
+((word) @boolean
+ (#any-of? @boolean "true" "false"))
+
+(comment) @comment @spell
+(test_operator) @string
+
+(command_substitution
+ [ "$(" ")" ] @punctuation.bracket)
+
+(process_substitution
+ [ "<(" ")" ] @punctuation.bracket)
+
+
+(function_definition
+ name: (word) @function)
+
+(command_name (word) @function.call)
+
+((command_name (word) @function.builtin)
+ (#any-of? @function.builtin
+ "alias" "bg" "bind" "break" "builtin" "caller" "cd"
+ "command" "compgen" "complete" "compopt" "continue"
+ "coproc" "dirs" "disown" "echo" "enable" "eval"
+ "exec" "exit" "fc" "fg" "getopts" "hash" "help"
+ "history" "jobs" "kill" "let" "logout" "mapfile"
+ "popd" "printf" "pushd" "pwd" "read" "readarray"
+ "return" "set" "shift" "shopt" "source" "suspend"
+ "test" "time" "times" "trap" "type" "typeset"
+ "ulimit" "umask" "unalias" "wait"))
+
+(command
+ argument: [
+ (word) @parameter
+ (concatenation (word) @parameter)
+ ])
+
+((word) @number
+ (#lua-match? @number "^[0-9]+$"))
+
+(file_redirect
+ descriptor: (file_descriptor) @operator
+ destination: (word) @parameter)
+
+(expansion
+ [ "${" "}" ] @punctuation.bracket)
+
+(variable_name) @variable
+
+((variable_name) @constant
+ (#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
+
+(case_item
+ value: (word) @parameter)
+
+(regex) @string.regex
+
+((program . (comment) @preproc)
+ (#lua-match? @preproc "^#!/"))
diff --git a/runtime/queries/c/folds.scm b/runtime/queries/c/folds.scm
new file mode 100644
index 0000000000..5a35334a24
--- /dev/null
+++ b/runtime/queries/c/folds.scm
@@ -0,0 +1,20 @@
+[
+ (for_statement)
+ (if_statement)
+ (while_statement)
+ (switch_statement)
+ (case_statement)
+ (function_definition)
+ (struct_specifier)
+ (enum_specifier)
+ (comment)
+ (preproc_if)
+ (preproc_elif)
+ (preproc_else)
+ (preproc_ifdef)
+ (initializer_list)
+ (gnu_asm_expression)
+] @fold
+
+(compound_statement
+ (compound_statement) @fold)
diff --git a/runtime/queries/c/highlights.scm b/runtime/queries/c/highlights.scm
index 33e6df74ab..29fb5747ca 100644
--- a/runtime/queries/c/highlights.scm
+++ b/runtime/queries/c/highlights.scm
@@ -1,23 +1,27 @@
-(identifier) @variable
+; Lower priority to prefer @parameter when identifier appears in parameter_declaration.
+((identifier) @variable (#set! "priority" 95))
+(preproc_def (preproc_arg) @variable)
[
- "const"
"default"
"enum"
- "extern"
- "inline"
- "return"
- "sizeof"
- "static"
"struct"
"typedef"
"union"
- "volatile"
"goto"
- "register"
+ "asm"
+ "__asm__"
] @keyword
[
+ "sizeof"
+ "offsetof"
+] @keyword.operator
+(alignof_expression . _ @keyword.operator)
+
+"return" @keyword.return
+
+[
"while"
"for"
"do"
@@ -32,7 +36,6 @@
"switch"
] @conditional
-"#define" @constant.macro
[
"#if"
"#ifdef"
@@ -40,11 +43,21 @@
"#else"
"#elif"
"#endif"
+ "#elifdef"
+ "#elifndef"
(preproc_directive)
-] @keyword
+] @preproc
+
+"#define" @define
"#include" @include
+[ ";" ":" "," "::" ] @punctuation.delimiter
+
+"..." @punctuation.special
+
+[ "(" ")" "[" "]" "{" "}"] @punctuation.bracket
+
[
"="
@@ -62,6 +75,7 @@
">>"
"->"
+ "."
"<"
"<="
@@ -88,56 +102,110 @@
"++"
] @operator
+;; Make sure the comma operator is given a highlight group after the comma
+;; punctuator so the operator is highlighted properly.
+(comma_expression [ "," ] @operator)
+
[
- (true)
- (false)
+ (true)
+ (false)
] @boolean
-[ "." ";" ":" "," ] @punctuation.delimiter
-
-(conditional_expression [ "?" ":" ] @conditional)
-
-
-[ "(" ")" "[" "]" "{" "}"] @punctuation.bracket
+(conditional_expression [ "?" ":" ] @conditional.ternary)
(string_literal) @string
-(string_literal) @spell
(system_lib_string) @string
+(escape_sequence) @string.escape
(null) @constant.builtin
(number_literal) @number
-(char_literal) @number
+(char_literal) @character
-(call_expression
- function: (identifier) @function)
-(call_expression
- function: (field_expression
- field: (field_identifier) @function))
-(function_declarator
- declarator: (identifier) @function)
-(preproc_function_def
- name: (identifier) @function.macro)
-[
- (preproc_arg)
- (preproc_defined)
-] @function.macro
+((preproc_arg) @function.macro (#set! "priority" 90))
+(preproc_defined) @function.macro
+
+(((field_expression
+ (field_identifier) @property)) @_parent
+ (#not-has-parent? @_parent template_method function_declarator call_expression))
+
+(field_designator) @property
+(((field_identifier) @property)
+ (#has-ancestor? @property field_declaration)
+ (#not-has-ancestor? @property function_declarator))
-(field_identifier) @property
(statement_identifier) @label
[
-(type_identifier)
-(primitive_type)
-(sized_type_specifier)
-(type_descriptor)
- ] @type
+ (type_identifier)
+ (type_descriptor)
+] @type
+
+(storage_class_specifier) @storageclass
+
+[
+ (type_qualifier)
+ (gnu_asm_qualifier)
+ "__extension__"
+] @type.qualifier
+
+(linkage_specification
+ "extern" @storageclass)
-(declaration (type_qualifier) @type)
-(cast_expression type: (type_descriptor) @type)
-(sizeof_expression value: (parenthesized_expression (identifier) @type))
+(type_definition
+ declarator: (type_identifier) @type.definition)
+
+(primitive_type) @type.builtin
+
+(sized_type_specifier _ @type.builtin type: _?)
((identifier) @constant
- (#match? @constant "^[A-Z][A-Z0-9_]+$"))
+ (#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
+(preproc_def (preproc_arg) @constant
+ (#lua-match? @constant "^[A-Z][A-Z0-9_]+$"))
+(enumerator
+ name: (identifier) @constant)
+(case_statement
+ value: (identifier) @constant)
+
+((identifier) @constant.builtin
+ (#any-of? @constant.builtin
+ "stderr" "stdin" "stdout"
+ "__FILE__" "__LINE__" "__DATE__" "__TIME__"
+ "__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
+ "__cplusplus" "__OBJC__" "__ASSEMBLER__"
+ "__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
+ "__TIMESTAMP__" "__clang__" "__clang_major__"
+ "__clang_minor__" "__clang_patchlevel__"
+ "__clang_version__" "__clang_literal_encoding__"
+ "__clang_wide_literal_encoding__"
+ "__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
+ "__VA_ARGS__" "__VA_OPT__"))
+(preproc_def (preproc_arg) @constant.builtin
+ (#any-of? @constant.builtin
+ "stderr" "stdin" "stdout"
+ "__FILE__" "__LINE__" "__DATE__" "__TIME__"
+ "__STDC__" "__STDC_VERSION__" "__STDC_HOSTED__"
+ "__cplusplus" "__OBJC__" "__ASSEMBLER__"
+ "__BASE_FILE__" "__FILE_NAME__" "__INCLUDE_LEVEL__"
+ "__TIMESTAMP__" "__clang__" "__clang_major__"
+ "__clang_minor__" "__clang_patchlevel__"
+ "__clang_version__" "__clang_literal_encoding__"
+ "__clang_wide_literal_encoding__"
+ "__FUNCTION__" "__func__" "__PRETTY_FUNCTION__"
+ "__VA_ARGS__" "__VA_OPT__"))
+
+(attribute_specifier
+ (argument_list (identifier) @variable.builtin))
+((attribute_specifier
+ (argument_list (call_expression
+ function: (identifier) @variable.builtin))))
+
+((call_expression
+ function: (identifier) @function.builtin)
+ (#lua-match? @function.builtin "^__builtin_"))
+((call_expression
+ function: (identifier) @function.builtin)
+ (#has-ancestor? @function.builtin attribute_specifier))
;; Preproc def / undef
(preproc_def
@@ -147,18 +215,49 @@
argument: (_) @constant
(#eq? @_u "#undef"))
+(call_expression
+ function: (identifier) @function.call)
+(call_expression
+ function: (field_expression
+ field: (field_identifier) @function.call))
+(function_declarator
+ declarator: (identifier) @function)
+(function_declarator
+ declarator: (parenthesized_declarator
+ (pointer_declarator
+ declarator: (field_identifier) @function)))
+(preproc_function_def
+ name: (identifier) @function.macro)
+
+(comment) @comment @spell
-(comment) @comment
-(comment) @spell
+((comment) @comment.documentation
+ (#lua-match? @comment.documentation "^/[*][*][^*].*[*]/$"))
;; Parameters
(parameter_declaration
declarator: (identifier) @parameter)
(parameter_declaration
+ declarator: (array_declarator) @parameter)
+
+(parameter_declaration
declarator: (pointer_declarator) @parameter)
-(preproc_params
- (identifier)) @parameter
+(preproc_params (identifier) @parameter)
+
+[
+ "__attribute__"
+ "__declspec"
+ "__based"
+ "__cdecl"
+ "__clrcall"
+ "__stdcall"
+ "__fastcall"
+ "__thiscall"
+ "__vectorcall"
+ (ms_pointer_modifier)
+ (attribute_declaration)
+] @attribute
(ERROR) @error
diff --git a/runtime/queries/c/injections.scm b/runtime/queries/c/injections.scm
index 7e9e73449d..5a49e20df5 100644
--- a/runtime/queries/c/injections.scm
+++ b/runtime/queries/c/injections.scm
@@ -1,3 +1,21 @@
-(preproc_arg) @c
+((preproc_def
+ (preproc_arg) @injection.content)
+ (#lua-match? @injection.content "\n")
+ (#set! injection.language "c"))
-; (comment) @comment
+(preproc_function_def
+ (preproc_arg) @injection.content
+ (#set! injection.language "c"))
+
+(preproc_call
+ (preproc_arg) @injection.content
+ (#set! injection.language "c"))
+
+; ((comment) @injection.content
+; (#set! injection.language "comment"))
+
+; TODO: add when asm is added
+; (gnu_asm_expression assembly_code: (string_literal) @injection.content
+; (#set! injection.language "asm"))
+; (gnu_asm_expression assembly_code: (concatenated_string (string_literal) @injection.content)
+; (#set! injection.language "asm"))
diff --git a/runtime/queries/help/injections.scm b/runtime/queries/help/injections.scm
deleted file mode 100644
index 09bbe44e84..0000000000
--- a/runtime/queries/help/injections.scm
+++ /dev/null
@@ -1,3 +0,0 @@
-(codeblock
- (language) @language
- (code) @content)
diff --git a/runtime/queries/lua/folds.scm b/runtime/queries/lua/folds.scm
new file mode 100644
index 0000000000..d8f0b42df3
--- /dev/null
+++ b/runtime/queries/lua/folds.scm
@@ -0,0 +1,10 @@
+[
+ (do_statement)
+ (while_statement)
+ (repeat_statement)
+ (if_statement)
+ (for_statement)
+ (function_declaration)
+ (function_definition)
+ (table_constructor)
+] @fold
diff --git a/runtime/queries/lua/highlights.scm b/runtime/queries/lua/highlights.scm
index 2c0dc5447a..96ffeae793 100644
--- a/runtime/queries/lua/highlights.scm
+++ b/runtime/queries/lua/highlights.scm
@@ -8,8 +8,6 @@
"local"
] @keyword
-(label_statement) @label
-
(break_statement) @keyword
(do_statement
@@ -109,6 +107,7 @@
[
";"
":"
+ "::"
","
"."
] @punctuation.delimiter
@@ -128,13 +127,28 @@
(identifier) @variable
+((identifier) @constant.builtin
+ (#eq? @constant.builtin "_VERSION"))
+
((identifier) @variable.builtin
- (#eq? @variable.builtin "self"))
+ (#eq? @variable.builtin "self"))
+
+((identifier) @namespace.builtin
+ (#any-of? @namespace.builtin "_G" "debug" "io" "jit" "math" "os" "package" "string" "table" "utf8"))
+
+((identifier) @keyword.coroutine
+ (#eq? @keyword.coroutine "coroutine"))
(variable_list
- attribute: (attribute
- (["<" ">"] @punctuation.bracket
- (identifier) @attribute)))
+ attribute: (attribute
+ (["<" ">"] @punctuation.bracket
+ (identifier) @attribute)))
+
+;; Labels
+
+(label_statement (identifier) @label)
+
+(goto_statement (identifier) @label)
;; Constants
@@ -166,13 +180,40 @@
(parameters (identifier) @parameter)
-(function_call name: (identifier) @function.call)
-(function_declaration name: (identifier) @function)
+(function_declaration
+ name: [
+ (identifier) @function
+ (dot_index_expression
+ field: (identifier) @function)
+ ])
-(function_call name: (dot_index_expression field: (identifier) @function.call))
-(function_declaration name: (dot_index_expression field: (identifier) @function))
+(function_declaration
+ name: (method_index_expression
+ method: (identifier) @method))
+
+(assignment_statement
+ (variable_list .
+ name: [
+ (identifier) @function
+ (dot_index_expression
+ field: (identifier) @function)
+ ])
+ (expression_list .
+ value: (function_definition)))
+
+(table_constructor
+ (field
+ name: (identifier) @function
+ value: (function_definition)))
-(method_index_expression method: (identifier) @method)
+(function_call
+ name: [
+ (identifier) @function.call
+ (dot_index_expression
+ field: (identifier) @function.call)
+ (method_index_expression
+ method: (identifier) @method.call)
+ ])
(function_call
(identifier) @function.builtin
@@ -180,20 +221,29 @@
;; built-in functions in Lua 5.1
"assert" "collectgarbage" "dofile" "error" "getfenv" "getmetatable" "ipairs"
"load" "loadfile" "loadstring" "module" "next" "pairs" "pcall" "print"
- "rawequal" "rawget" "rawset" "require" "select" "setfenv" "setmetatable"
- "tonumber" "tostring" "type" "unpack" "xpcall"))
+ "rawequal" "rawget" "rawlen" "rawset" "require" "select" "setfenv" "setmetatable"
+ "tonumber" "tostring" "type" "unpack" "xpcall"
+ "__add" "__band" "__bnot" "__bor" "__bxor" "__call" "__concat" "__div" "__eq" "__gc"
+ "__idiv" "__index" "__le" "__len" "__lt" "__metatable" "__mod" "__mul" "__name" "__newindex"
+ "__pairs" "__pow" "__shl" "__shr" "__sub" "__tostring" "__unm"))
;; Others
-(comment) @comment
-(comment) @spell
+(comment) @comment @spell
-(hash_bang_line) @comment
+((comment) @comment.documentation
+ (#lua-match? @comment.documentation "^[-][-][-]"))
+
+((comment) @comment.documentation
+ (#lua-match? @comment.documentation "^[-][-](%s?)@"))
+
+(hash_bang_line) @preproc
(number) @number
-(string) @string
-(string) @spell
+(string) @string @spell
+
+(escape_sequence) @string.escape
;; Error
(ERROR) @error
diff --git a/runtime/queries/lua/injections.scm b/runtime/queries/lua/injections.scm
index 0e67329139..dbfe75ae31 100644
--- a/runtime/queries/lua/injections.scm
+++ b/runtime/queries/lua/injections.scm
@@ -3,20 +3,33 @@
(identifier) @_cdef_identifier
(_ _ (identifier) @_cdef_identifier)
]
- arguments: (arguments (string content: _ @c)))
+ arguments:
+ (arguments
+ (string content: _ @injection.content)))
+ (#set! injection.language "c")
(#eq? @_cdef_identifier "cdef"))
((function_call
name: (_) @_vimcmd_identifier
- arguments: (arguments (string content: _ @vim)))
- (#any-of? @_vimcmd_identifier "vim.cmd" "vim.api.nvim_command" "vim.api.nvim_exec" "vim.api.nvim_cmd"))
+ arguments: (arguments (string content: _ @injection.content)))
+ (#set! injection.language "vim")
+ (#any-of? @_vimcmd_identifier "vim.cmd" "vim.api.nvim_command" "vim.api.nvim_command" "vim.api.nvim_exec2"))
-; ((function_call
-; name: (_) @_vimcmd_identifier
-; arguments: (arguments (string content: _ @query) .))
-; (#eq? @_vimcmd_identifier "vim.treesitter.query.set_query"))
+((function_call
+ name: (_) @_vimcmd_identifier
+ arguments: (arguments (string content: _ @injection.content) .))
+ (#set! injection.language "query")
+ (#any-of? @_vimcmd_identifier "vim.treesitter.query.set" "vim.treesitter.query.parse"))
+
+((function_call
+ name: (_) @_vimcmd_identifier
+ arguments: (arguments . (_) . (string content: _ @_method) . (string content: _ @injection.content)))
+ (#any-of? @_vimcmd_identifier "vim.rpcrequest" "vim.rpcnotify")
+ (#eq? @_method "nvim_exec_lua")
+ (#set! injection.language "lua"))
-; ;; highlight string as query if starts with `;; query`
-; ((string ("string_content") @query) (#lua-match? @query "^%s*;+%s?query"))
+;; highlight string as query if starts with `;; query`
+(string content: _ @injection.content
+ (#lua-match? @injection.content "^%s*;+%s?query")
+ (#set! injection.language "query"))
-; (comment) @comment
diff --git a/runtime/queries/markdown/folds.scm b/runtime/queries/markdown/folds.scm
new file mode 100644
index 0000000000..5900f7ffbe
--- /dev/null
+++ b/runtime/queries/markdown/folds.scm
@@ -0,0 +1,9 @@
+(
+ [
+ (fenced_code_block)
+ (indented_code_block)
+ (list)
+ (section)
+ ] @fold
+ (#trim! @fold)
+)
diff --git a/runtime/queries/markdown/highlights.scm b/runtime/queries/markdown/highlights.scm
new file mode 100644
index 0000000000..2cc5546bac
--- /dev/null
+++ b/runtime/queries/markdown/highlights.scm
@@ -0,0 +1,71 @@
+;From MDeiml/tree-sitter-markdown & Helix
+(setext_heading (paragraph) @text.title.1 (setext_h1_underline) @text.title.1.marker)
+(setext_heading (paragraph) @text.title.2 (setext_h2_underline) @text.title.2.marker)
+
+(atx_heading (atx_h1_marker) @text.title.1.marker (inline) @text.title.1)
+(atx_heading (atx_h2_marker) @text.title.2.marker (inline) @text.title.2)
+(atx_heading (atx_h3_marker) @text.title.3.marker (inline) @text.title.3)
+(atx_heading (atx_h4_marker) @text.title.4.marker (inline) @text.title.4)
+(atx_heading (atx_h5_marker) @text.title.5.marker (inline) @text.title.5)
+(atx_heading (atx_h6_marker) @text.title.6.marker (inline) @text.title.6)
+
+(link_title) @text.literal
+(indented_code_block) @text.literal.block
+((fenced_code_block) @text.literal.block (#set! "priority" 90))
+
+(info_string) @label
+
+(pipe_table_header (pipe_table_cell) @text.title)
+
+(pipe_table_header "|" @punctuation.special)
+(pipe_table_row "|" @punctuation.special)
+(pipe_table_delimiter_row "|" @punctuation.special)
+(pipe_table_delimiter_cell) @punctuation.special
+
+[
+ (fenced_code_block_delimiter)
+] @punctuation.delimiter
+
+(code_fence_content) @none
+
+[
+ (link_destination)
+] @text.uri
+
+[
+ (link_label)
+] @text.reference
+
+[
+ (list_marker_plus)
+ (list_marker_minus)
+ (list_marker_star)
+ (list_marker_dot)
+ (list_marker_parenthesis)
+ (thematic_break)
+] @punctuation.special
+
+
+(task_list_marker_unchecked) @text.todo.unchecked
+(task_list_marker_checked) @text.todo.checked
+
+(block_quote) @text.quote
+
+[
+ (block_continuation)
+ (block_quote_marker)
+] @punctuation.special
+
+[
+ (backslash_escape)
+] @string.escape
+
+(inline) @spell
+
+;; Conceal backticks
+(fenced_code_block
+ (fenced_code_block_delimiter) @conceal
+ (#set! conceal ""))
+(fenced_code_block
+ (info_string (language) @conceal
+ (#set! conceal "")))
diff --git a/runtime/queries/markdown/injections.scm b/runtime/queries/markdown/injections.scm
new file mode 100644
index 0000000000..fda7036830
--- /dev/null
+++ b/runtime/queries/markdown/injections.scm
@@ -0,0 +1,25 @@
+(fenced_code_block
+ (info_string
+ (language) @injection.language)
+ (code_fence_content) @injection.content)
+
+((html_block) @injection.content
+ (#set! injection.language "html")
+ (#set! injection.combined)
+ (#set! injection.include-children))
+
+((minus_metadata) @injection.content
+ (#set! injection.language "yaml")
+ (#offset! @injection.content 1 0 -1 0)
+ (#set! injection.include-children))
+
+((plus_metadata) @injection.content
+ (#set! injection.language "toml")
+ (#offset! @injection.content 1 0 -1 0)
+ (#set! injection.include-children))
+
+([
+ (inline)
+ (pipe_table_cell)
+ ] @injection.content
+ (#set! injection.language "markdown_inline"))
diff --git a/runtime/queries/markdown_inline/highlights.scm b/runtime/queries/markdown_inline/highlights.scm
new file mode 100644
index 0000000000..c75da478af
--- /dev/null
+++ b/runtime/queries/markdown_inline/highlights.scm
@@ -0,0 +1,102 @@
+;; From MDeiml/tree-sitter-markdown
+[
+ (code_span)
+ (link_title)
+] @text.literal @nospell
+
+[
+ (emphasis_delimiter)
+ (code_span_delimiter)
+] @punctuation.delimiter
+
+(emphasis) @text.emphasis
+
+(strong_emphasis) @text.strong
+
+(strikethrough) @text.strike
+
+[
+ (link_destination)
+ (uri_autolink)
+] @text.uri @nospell
+
+(shortcut_link (link_text) @nospell)
+
+[
+ (link_label)
+ (link_text)
+ (image_description)
+] @text.reference
+
+[
+ (backslash_escape)
+ (hard_line_break)
+] @string.escape
+
+(image "!" @punctuation.special)
+(image ["[" "]" "(" ")"] @punctuation.bracket)
+(inline_link ["[" "]" "(" ")"] @punctuation.bracket)
+(shortcut_link ["[" "]"] @punctuation.bracket)
+
+; Conceal codeblock and text style markers
+([
+ (code_span_delimiter)
+ (emphasis_delimiter)
+] @conceal
+(#set! conceal ""))
+
+; Conceal inline links
+(inline_link
+ [
+ "["
+ "]"
+ "("
+ (link_destination)
+ ")"
+ ] @conceal
+ (#set! conceal ""))
+
+; Conceal image links
+(image
+ [
+ "!"
+ "["
+ "]"
+ "("
+ (link_destination)
+ ")"
+ ] @conceal
+ (#set! conceal ""))
+
+; Conceal full reference links
+(full_reference_link
+ [
+ "["
+ "]"
+ (link_label)
+ ] @conceal
+ (#set! conceal ""))
+
+; Conceal collapsed reference links
+(collapsed_reference_link
+ [
+ "["
+ "]"
+ ] @conceal
+ (#set! conceal ""))
+
+; Conceal shortcut links
+(shortcut_link
+ [
+ "["
+ "]"
+ ] @conceal
+ (#set! conceal ""))
+
+;; Replace common HTML entities.
+((entity_reference) @conceal (#eq? @conceal "&nbsp;") (#set! conceal ""))
+((entity_reference) @conceal (#eq? @conceal "&lt;") (#set! conceal "<"))
+((entity_reference) @conceal (#eq? @conceal "&gt;") (#set! conceal ">"))
+((entity_reference) @conceal (#eq? @conceal "&amp;") (#set! conceal "&"))
+((entity_reference) @conceal (#eq? @conceal "&quot;") (#set! conceal "\""))
+((entity_reference) @conceal (#any-of? @conceal "&ensp;" "&emsp;") (#set! conceal " "))
diff --git a/runtime/queries/markdown_inline/injections.scm b/runtime/queries/markdown_inline/injections.scm
new file mode 100644
index 0000000000..f7aa19caff
--- /dev/null
+++ b/runtime/queries/markdown_inline/injections.scm
@@ -0,0 +1,8 @@
+((html_tag) @injection.content
+ (#set! injection.language "html")
+ (#set! injection.combined)
+ (#set! injection.include-children))
+
+((latex_block) @injection.content
+ (#set! injection.language "latex")
+ (#set! injection.include-children))
diff --git a/runtime/queries/python/folds.scm b/runtime/queries/python/folds.scm
new file mode 100644
index 0000000000..78e1e2c00d
--- /dev/null
+++ b/runtime/queries/python/folds.scm
@@ -0,0 +1,28 @@
+[
+ (function_definition)
+ (class_definition)
+
+ (while_statement)
+ (for_statement)
+ (if_statement)
+ (with_statement)
+ (try_statement)
+ (match_statement)
+
+ (import_from_statement)
+ (parameters)
+ (argument_list)
+
+ (parenthesized_expression)
+ (generator_expression)
+ (list_comprehension)
+ (set_comprehension)
+ (dictionary_comprehension)
+
+ (tuple)
+ (list)
+ (set)
+ (dictionary)
+
+ (string)
+] @fold
diff --git a/runtime/queries/python/highlights.scm b/runtime/queries/python/highlights.scm
new file mode 100644
index 0000000000..04398668e9
--- /dev/null
+++ b/runtime/queries/python/highlights.scm
@@ -0,0 +1,351 @@
+;; From tree-sitter-python licensed under MIT License
+; Copyright (c) 2016 Max Brunsfeld
+
+; Variables
+(identifier) @variable
+
+; Reset highlighting in f-string interpolations
+(interpolation) @none
+
+;; Identifier naming conventions
+((identifier) @type
+ (#lua-match? @type "^[A-Z].*[a-z]"))
+((identifier) @constant
+ (#lua-match? @constant "^[A-Z][A-Z_0-9]*$"))
+
+((identifier) @constant.builtin
+ (#lua-match? @constant.builtin "^__[a-zA-Z0-9_]*__$"))
+
+((identifier) @constant.builtin
+ (#any-of? @constant.builtin
+ ;; https://docs.python.org/3/library/constants.html
+ "NotImplemented"
+ "Ellipsis"
+ "quit"
+ "exit"
+ "copyright"
+ "credits"
+ "license"))
+
+"_" @constant.builtin ; match wildcard
+
+((attribute
+ attribute: (identifier) @field)
+ (#lua-match? @field "^[%l_].*$"))
+
+((assignment
+ left: (identifier) @type.definition
+ (type (identifier) @_annotation))
+ (#eq? @_annotation "TypeAlias"))
+
+((assignment
+ left: (identifier) @type.definition
+ right: (call
+ function: (identifier) @_func))
+ (#any-of? @_func "TypeVar" "NewType"))
+
+; Function calls
+
+(call
+ function: (identifier) @function.call)
+
+(call
+ function: (attribute
+ attribute: (identifier) @method.call))
+
+((call
+ function: (identifier) @constructor)
+ (#lua-match? @constructor "^%u"))
+
+((call
+ function: (attribute
+ attribute: (identifier) @constructor))
+ (#lua-match? @constructor "^%u"))
+
+;; Decorators
+
+((decorator "@" @attribute)
+ (#set! "priority" 101))
+
+(decorator
+ (identifier) @attribute)
+(decorator
+ (attribute
+ attribute: (identifier) @attribute))
+(decorator
+ (call (identifier) @attribute))
+(decorator
+ (call (attribute
+ attribute: (identifier) @attribute)))
+
+((decorator
+ (identifier) @attribute.builtin)
+ (#any-of? @attribute.builtin "classmethod" "property"))
+
+;; Builtin functions
+
+((call
+ function: (identifier) @function.builtin)
+ (#any-of? @function.builtin
+ "abs" "all" "any" "ascii" "bin" "bool" "breakpoint" "bytearray" "bytes" "callable" "chr" "classmethod"
+ "compile" "complex" "delattr" "dict" "dir" "divmod" "enumerate" "eval" "exec" "filter" "float" "format"
+ "frozenset" "getattr" "globals" "hasattr" "hash" "help" "hex" "id" "input" "int" "isinstance" "issubclass"
+ "iter" "len" "list" "locals" "map" "max" "memoryview" "min" "next" "object" "oct" "open" "ord" "pow"
+ "print" "property" "range" "repr" "reversed" "round" "set" "setattr" "slice" "sorted" "staticmethod" "str"
+ "sum" "super" "tuple" "type" "vars" "zip" "__import__"))
+
+;; Function definitions
+
+(function_definition
+ name: (identifier) @function)
+
+(type (identifier) @type)
+(type
+ (subscript
+ (identifier) @type)) ; type subscript: Tuple[int]
+
+((call
+ function: (identifier) @_isinstance
+ arguments: (argument_list
+ (_)
+ (identifier) @type))
+ (#eq? @_isinstance "isinstance"))
+
+;; Normal parameters
+(parameters
+ (identifier) @parameter)
+;; Lambda parameters
+(lambda_parameters
+ (identifier) @parameter)
+(lambda_parameters
+ (tuple_pattern
+ (identifier) @parameter))
+; Default parameters
+(keyword_argument
+ name: (identifier) @parameter)
+; Naming parameters on call-site
+(default_parameter
+ name: (identifier) @parameter)
+(typed_parameter
+ (identifier) @parameter)
+(typed_default_parameter
+ (identifier) @parameter)
+; Variadic parameters *args, **kwargs
+(parameters
+ (list_splat_pattern ; *args
+ (identifier) @parameter))
+(parameters
+ (dictionary_splat_pattern ; **kwargs
+ (identifier) @parameter))
+
+
+;; Literals
+
+(none) @constant.builtin
+[(true) (false)] @boolean
+((identifier) @variable.builtin
+ (#eq? @variable.builtin "self"))
+((identifier) @variable.builtin
+ (#eq? @variable.builtin "cls"))
+
+(integer) @number
+(float) @float
+
+(comment) @comment @spell
+
+((module . (comment) @preproc)
+ (#lua-match? @preproc "^#!/"))
+
+(string) @string
+[
+ (escape_sequence)
+ (escape_interpolation)
+] @string.escape
+
+; doc-strings
+
+(module . (expression_statement (string) @string.documentation @spell))
+
+(class_definition
+ body:
+ (block
+ . (expression_statement (string) @string.documentation @spell)))
+
+(function_definition
+ body:
+ (block
+ . (expression_statement (string) @string.documentation @spell)))
+
+; Tokens
+
+[
+ "-"
+ "-="
+ ":="
+ "!="
+ "*"
+ "**"
+ "**="
+ "*="
+ "/"
+ "//"
+ "//="
+ "/="
+ "&"
+ "&="
+ "%"
+ "%="
+ "^"
+ "^="
+ "+"
+ "+="
+ "<"
+ "<<"
+ "<<="
+ "<="
+ "<>"
+ "="
+ "=="
+ ">"
+ ">="
+ ">>"
+ ">>="
+ "@"
+ "@="
+ "|"
+ "|="
+ "~"
+ "->"
+] @operator
+
+; Keywords
+[
+ "and"
+ "in"
+ "is"
+ "not"
+ "or"
+ "is not"
+ "not in"
+
+ "del"
+] @keyword.operator
+
+[
+ "def"
+ "lambda"
+] @keyword.function
+
+[
+ "assert"
+ "class"
+ "exec"
+ "global"
+ "nonlocal"
+ "pass"
+ "print"
+ "with"
+ "as"
+ "type"
+] @keyword
+
+[
+ "async"
+ "await"
+] @keyword.coroutine
+
+[
+ "return"
+ "yield"
+] @keyword.return
+(yield "from" @keyword.return)
+
+(future_import_statement
+ "from" @include
+ "__future__" @constant.builtin)
+(import_from_statement "from" @include)
+"import" @include
+
+(aliased_import "as" @include)
+
+["if" "elif" "else" "match" "case"] @conditional
+
+["for" "while" "break" "continue"] @repeat
+
+[
+ "try"
+ "except"
+ "except*"
+ "raise"
+ "finally"
+] @exception
+
+(raise_statement "from" @exception)
+
+(try_statement
+ (else_clause
+ "else" @exception))
+
+["(" ")" "[" "]" "{" "}"] @punctuation.bracket
+
+(interpolation
+ "{" @punctuation.special
+ "}" @punctuation.special)
+
+(type_conversion) @function.macro
+
+["," "." ":" ";" (ellipsis)] @punctuation.delimiter
+
+;; Class definitions
+
+(class_definition name: (identifier) @type)
+
+(class_definition
+ body: (block
+ (function_definition
+ name: (identifier) @method)))
+
+(class_definition
+ superclasses: (argument_list
+ (identifier) @type))
+
+((class_definition
+ body: (block
+ (expression_statement
+ (assignment
+ left: (identifier) @field))))
+ (#lua-match? @field "^%l.*$"))
+((class_definition
+ body: (block
+ (expression_statement
+ (assignment
+ left: (_
+ (identifier) @field)))))
+ (#lua-match? @field "^%l.*$"))
+
+((class_definition
+ (block
+ (function_definition
+ name: (identifier) @constructor)))
+ (#any-of? @constructor "__new__" "__init__"))
+
+((identifier) @type.builtin
+ (#any-of? @type.builtin
+ ;; https://docs.python.org/3/library/exceptions.html
+ "BaseException" "Exception" "ArithmeticError" "BufferError" "LookupError" "AssertionError" "AttributeError"
+ "EOFError" "FloatingPointError" "GeneratorExit" "ImportError" "ModuleNotFoundError" "IndexError" "KeyError"
+ "KeyboardInterrupt" "MemoryError" "NameError" "NotImplementedError" "OSError" "OverflowError" "RecursionError"
+ "ReferenceError" "RuntimeError" "StopIteration" "StopAsyncIteration" "SyntaxError" "IndentationError" "TabError"
+ "SystemError" "SystemExit" "TypeError" "UnboundLocalError" "UnicodeError" "UnicodeEncodeError" "UnicodeDecodeError"
+ "UnicodeTranslateError" "ValueError" "ZeroDivisionError" "EnvironmentError" "IOError" "WindowsError"
+ "BlockingIOError" "ChildProcessError" "ConnectionError" "BrokenPipeError" "ConnectionAbortedError"
+ "ConnectionRefusedError" "ConnectionResetError" "FileExistsError" "FileNotFoundError" "InterruptedError"
+ "IsADirectoryError" "NotADirectoryError" "PermissionError" "ProcessLookupError" "TimeoutError" "Warning"
+ "UserWarning" "DeprecationWarning" "PendingDeprecationWarning" "SyntaxWarning" "RuntimeWarning"
+ "FutureWarning" "ImportWarning" "UnicodeWarning" "BytesWarning" "ResourceWarning"
+ ;; https://docs.python.org/3/library/stdtypes.html
+ "bool" "int" "float" "complex" "list" "tuple" "range" "str"
+ "bytes" "bytearray" "memoryview" "set" "frozenset" "dict" "type" "object"))
+
+;; Error
+(ERROR) @error
diff --git a/runtime/queries/query/folds.scm b/runtime/queries/query/folds.scm
new file mode 100644
index 0000000000..47dd965126
--- /dev/null
+++ b/runtime/queries/query/folds.scm
@@ -0,0 +1,6 @@
+[
+ (named_node)
+ (predicate)
+ (grouping)
+ (list)
+] @fold
diff --git a/runtime/queries/query/highlights.scm b/runtime/queries/query/highlights.scm
new file mode 100644
index 0000000000..f2d2ef6c7f
--- /dev/null
+++ b/runtime/queries/query/highlights.scm
@@ -0,0 +1,34 @@
+(string) @string
+(escape_sequence) @string.escape
+(capture (identifier) @type)
+(anonymous_node (identifier) @string)
+(predicate name: (identifier) @function)
+(named_node name: (identifier) @variable)
+(field_definition name: (identifier) @property)
+(negated_field "!" @operator (identifier) @property)
+(comment) @comment @spell
+
+(quantifier) @operator
+(predicate_type) @punctuation.special
+
+"." @operator
+
+[
+ "["
+ "]"
+ "("
+ ")"
+] @punctuation.bracket
+
+":" @punctuation.delimiter
+["@" "#"] @punctuation.special
+"_" @constant
+
+((parameters (identifier) @number)
+ (#match? @number "^[-+]?[0-9]+(.[0-9]+)?$"))
+
+((program . (comment)* . (comment) @include)
+ (#lua-match? @include "^;+ *inherits *:"))
+
+((program . (comment)* . (comment) @preproc)
+ (#lua-match? @preproc "^;+ *extends"))
diff --git a/runtime/queries/vim/folds.scm b/runtime/queries/vim/folds.scm
new file mode 100644
index 0000000000..4c99735836
--- /dev/null
+++ b/runtime/queries/vim/folds.scm
@@ -0,0 +1,4 @@
+[
+ (if_statement)
+ (function_definition)
+] @fold
diff --git a/runtime/queries/vim/highlights.scm b/runtime/queries/vim/highlights.scm
index 239b0a0b37..09188ddb68 100644
--- a/runtime/queries/vim/highlights.scm
+++ b/runtime/queries/vim/highlights.scm
@@ -271,11 +271,11 @@
":"
] @punctuation.delimiter
-(ternary_expression ["?" ":"] @conditional)
+(ternary_expression ["?" ":"] @conditional.ternary)
; Options
((set_value) @number
- (#match? @number "^[0-9]+(\.[0-9]+)?$"))
+ (#lua-match? @number "^[%d]+(%.[%d]+)?$"))
(inv_option "!" @operator)
(set_item "?" @operator)
diff --git a/runtime/queries/vim/injections.scm b/runtime/queries/vim/injections.scm
index fdd025bfd9..50f0190112 100644
--- a/runtime/queries/vim/injections.scm
+++ b/runtime/queries/vim/injections.scm
@@ -1,18 +1,33 @@
-(lua_statement (script (body) @lua))
-(lua_statement (chunk) @lua)
-(ruby_statement (script (body) @ruby))
-(ruby_statement (chunk) @ruby)
-(python_statement (script (body) @python))
-(python_statement (chunk) @python)
+((lua_statement (script (body) @injection.content))
+ (#set! injection.language "lua"))
+
+((lua_statement (chunk) @injection.content)
+ (#set! injection.language "lua"))
+
+((ruby_statement (script (body) @injection.content))
+ (#set! injection.language "ruby"))
+
+((ruby_statement (chunk) @injection.content)
+ (#set! injection.language "ruby"))
+
+((python_statement (script (body) @injection.content))
+ (#set! injection.language "python"))
+
+((python_statement (chunk) @injection.content)
+ (#set! injection.language "python"))
+
;; If we support perl at some point...
-;; (perl_statement (script (body) @perl))
-;; (perl_statement (chunk) @perl)
+;; ((perl_statement (script (body) @injection.content))
+;; (#set! injection.language "perl"))
+;; ((perl_statement (chunk) @injection.content)
+;; (#set! injection.language "perl"))
-(autocmd_statement (pattern) @regex)
+((autocmd_statement (pattern) @injection.content)
+ (#set! injection.language "regex"))
((set_item
option: (option_name) @_option
- value: (set_value) @vim)
+ value: (set_value) @injection.content)
(#any-of? @_option
"includeexpr" "inex"
"printexpr" "pexpr"
@@ -22,7 +37,12 @@
"foldexpr" "fde"
"diffexpr" "dex"
"patchexpr" "pex"
- "charconvert" "ccv"))
+ "charconvert" "ccv")
+ (#set! injection.language "vim"))
+
+
+; ((comment) @injection.content
+; (#set! injection.language "comment"))
-(comment) @comment
-(line_continuation_comment) @comment
+; ((line_continuation_comment) @injection.content
+; (#set! injection.language "comment"))
diff --git a/runtime/queries/help/highlights.scm b/runtime/queries/vimdoc/highlights.scm
index c0d88301bc..e0dce49b2a 100644
--- a/runtime/queries/help/highlights.scm
+++ b/runtime/queries/vimdoc/highlights.scm
@@ -1,7 +1,7 @@
-(h1) @text.title
-(h2) @text.title
-(h3) @text.title
-(column_heading) @text.title
+(h1) @text.title.1
+(h2) @text.title.2
+(h3) @text.title.3
+(column_heading) @text.title.4
(column_heading
"~" @conceal (#set! conceal ""))
(tag
diff --git a/runtime/queries/vimdoc/injections.scm b/runtime/queries/vimdoc/injections.scm
new file mode 100644
index 0000000000..260a05d863
--- /dev/null
+++ b/runtime/queries/vimdoc/injections.scm
@@ -0,0 +1,4 @@
+((codeblock
+ (language) @injection.language
+ (code) @injection.content)
+ (#set! injection.include-children))
diff --git a/runtime/synmenu.vim b/runtime/synmenu.vim
index a664e7689d..43aae6f88b 100644
--- a/runtime/synmenu.vim
+++ b/runtime/synmenu.vim
@@ -1,8 +1,9 @@
" Vim support file to define the syntax selection menu
" This file is normally sourced from menu.vim.
"
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Oct 04
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Define the SetSyn function, used for the Syntax menu entries.
" Set 'filetype' and also 'syntax' if it is manually selected.
@@ -77,7 +78,7 @@ an 50.10.440 &Syntax.AB.Assembly.PIC :cal SetSyn("pic")<CR>
an 50.10.450 &Syntax.AB.Assembly.Turbo :cal SetSyn("tasm")<CR>
an 50.10.460 &Syntax.AB.Assembly.VAX\ Macro\ Assembly :cal SetSyn("vmasm")<CR>
an 50.10.470 &Syntax.AB.Assembly.Z-80 :cal SetSyn("z8a")<CR>
-an 50.10.480 &Syntax.AB.Assembly.xa\ 6502\ cross\ assember :cal SetSyn("a65")<CR>
+an 50.10.480 &Syntax.AB.Assembly.xa\ 6502\ cross\ assembler :cal SetSyn("a65")<CR>
an 50.10.490 &Syntax.AB.ASN\.1 :cal SetSyn("asn")<CR>
an 50.10.500 &Syntax.AB.Asterisk\ config :cal SetSyn("asterisk")<CR>
an 50.10.510 &Syntax.AB.Asterisk\ voicemail\ config :cal SetSyn("asteriskvm")<CR>
@@ -307,7 +308,7 @@ an 50.50.710 &Syntax.HIJK.Kimwitu++ :cal SetSyn("kwt")<CR>
an 50.50.720 &Syntax.HIJK.Kivy :cal SetSyn("kivy")<CR>
an 50.50.730 &Syntax.HIJK.KixTart :cal SetSyn("kix")<CR>
an 50.60.100 &Syntax.L.Lace :cal SetSyn("lace")<CR>
-an 50.60.110 &Syntax.L.LamdaProlog :cal SetSyn("lprolog")<CR>
+an 50.60.110 &Syntax.L.LambdaProlog :cal SetSyn("lprolog")<CR>
an 50.60.120 &Syntax.L.Latte :cal SetSyn("latte")<CR>
an 50.60.130 &Syntax.L.Ld\ script :cal SetSyn("ld")<CR>
an 50.60.140 &Syntax.L.LDAP.LDIF :cal SetSyn("ldif")<CR>
@@ -622,7 +623,7 @@ an 50.160.330 &Syntax.UV.VSE\ JCL :cal SetSyn("vsejcl")<CR>
an 50.170.100 &Syntax.WXYZ.WEB.CWEB :cal SetSyn("cweb")<CR>
an 50.170.110 &Syntax.WXYZ.WEB.WEB :cal SetSyn("web")<CR>
an 50.170.120 &Syntax.WXYZ.WEB.WEB\ Changes :cal SetSyn("change")<CR>
-an 50.170.130 &Syntax.WXYZ.WebAssembly :cal SetSyn("wast")<CR>
+an 50.170.130 &Syntax.WXYZ.WebAssembly :cal SetSyn("wat")<CR>
an 50.170.140 &Syntax.WXYZ.Webmacro :cal SetSyn("webmacro")<CR>
an 50.170.150 &Syntax.WXYZ.Website\ MetaLanguage :cal SetSyn("wml")<CR>
an 50.170.170 &Syntax.WXYZ.wDiff :cal SetSyn("wdiff")<CR>
@@ -649,6 +650,7 @@ an 50.170.390 &Syntax.WXYZ.XFree86\ Config :cal SetSyn("xf86conf")<CR>
an 50.170.410 &Syntax.WXYZ.YAML :cal SetSyn("yaml")<CR>
an 50.170.420 &Syntax.WXYZ.Yacc :cal SetSyn("yacc")<CR>
an 50.170.440 &Syntax.WXYZ.Zimbu :cal SetSyn("zimbu")<CR>
+an 50.170.440 &Syntax.WXYZ.Zserio:cal SetSyn("zserio")<CR>
" The End Of The Syntax Menu
diff --git a/runtime/syntax/2html.vim b/runtime/syntax/2html.vim
index ce6797deaa..5fbdad90f3 100644
--- a/runtime/syntax/2html.vim
+++ b/runtime/syntax/2html.vim
@@ -1,6 +1,6 @@
" Vim syntax support file
" Maintainer: Ben Fritz <fritzophrenic@gmail.com>
-" Last Change: 2022 Dec 26
+" Last Change: 2023 Sep 05
"
" Additional contributors:
"
@@ -32,9 +32,9 @@ let s:end=line('$')
" Font
if exists("g:html_font")
if type(g:html_font) == type([])
- let s:htmlfont = "'". join(g:html_font,"','") . "', monospace"
+ let s:htmlfont = "'".. join(g:html_font,"','") .. "', monospace"
else
- let s:htmlfont = "'". g:html_font . "', monospace"
+ let s:htmlfont = "'".. g:html_font .. "', monospace"
endif
else
let s:htmlfont = "monospace"
@@ -221,8 +221,8 @@ else
endif
" Find out the background and foreground color for use later
-let s:fgc = s:HtmlColor(synIDattr(hlID("Normal"), "fg#", s:whatterm))
-let s:bgc = s:HtmlColor(synIDattr(hlID("Normal"), "bg#", s:whatterm))
+let s:fgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "fg#", s:whatterm))
+let s:bgc = s:HtmlColor(synIDattr(hlID("Normal")->synIDtrans(), "bg#", s:whatterm))
if s:fgc == ""
let s:fgc = ( &background == "dark" ? "#ffffff" : "#000000" )
endif
@@ -234,41 +234,43 @@ if !s:settings.use_css
" Return opening HTML tag for given highlight id
function! s:HtmlOpening(id, extra_attrs)
let a = ""
- if synIDattr(a:id, "inverse")
+ let translated_ID = synIDtrans(a:id)
+ if synIDattr(translated_ID, "inverse")
" For inverse, we always must set both colors (and exchange them)
- let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
- let a = a . '<span '.a:extra_attrs.'style="background-color: ' . ( x != "" ? x : s:fgc ) . '">'
- let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
- let a = a . '<font color="' . ( x != "" ? x : s:bgc ) . '">'
+ let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
+ let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. ( x != "" ? x : s:fgc ) .. '">'
+ let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
+ let a = a .. '<font color="' .. ( x != "" ? x : s:bgc ) .. '">'
else
- let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
+ let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
if x != ""
- let a = a . '<span '.a:extra_attrs.'style="background-color: ' . x . '">'
+ let a = a .. '<span '..a:extra_attrs..'style="background-color: ' .. x .. '">'
elseif !empty(a:extra_attrs)
- let a = a . '<span '.a:extra_attrs.'>'
+ let a = a .. '<span '..a:extra_attrs..'>'
endif
- let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
- if x != "" | let a = a . '<font color="' . x . '">' | endif
+ let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
+ if x != "" | let a = a .. '<font color="' .. x .. '">' | endif
endif
- if synIDattr(a:id, "bold") | let a = a . "<b>" | endif
- if synIDattr(a:id, "italic") | let a = a . "<i>" | endif
- if synIDattr(a:id, "underline") | let a = a . "<u>" | endif
+ if synIDattr(translated_ID, "bold") | let a = a .. "<b>" | endif
+ if synIDattr(translated_ID, "italic") | let a = a .. "<i>" | endif
+ if synIDattr(translated_ID, "underline") | let a = a .. "<u>" | endif
return a
endfun
" Return closing HTML tag for given highlight id
function! s:HtmlClosing(id, has_extra_attrs)
let a = ""
- if synIDattr(a:id, "underline") | let a = a . "</u>" | endif
- if synIDattr(a:id, "italic") | let a = a . "</i>" | endif
- if synIDattr(a:id, "bold") | let a = a . "</b>" | endif
- if synIDattr(a:id, "inverse")
- let a = a . '</font></span>'
+ let translated_ID = synIDtrans(a:id)
+ if synIDattr(translated_ID, "underline") | let a = a .. "</u>" | endif
+ if synIDattr(translated_ID, "italic") | let a = a .. "</i>" | endif
+ if synIDattr(translated_ID, "bold") | let a = a .. "</b>" | endif
+ if synIDattr(translated_ID, "inverse")
+ let a = a .. '</font></span>'
else
- let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
- if x != "" | let a = a . '</font>' | endif
- let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
- if x != "" || a:has_extra_attrs | let a = a . '</span>' | endif
+ let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
+ if x != "" | let a = a .. '</font>' | endif
+ let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
+ if x != "" || a:has_extra_attrs | let a = a .. '</span>' | endif
endif
return a
endfun
@@ -286,84 +288,102 @@ if s:settings.use_css
" save CSS to a list of rules to add to the output at the end of processing
" first, get the style names we need
- let wrapperfunc_lines = [
- \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted)',
- \ '',
- \ ' let l:style_name = synIDattr(a:style_id, "name", s:whatterm)'
- \ ]
+ let s:wrapperfunc_lines = []
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, make_unselectable, unformatted)
+
+ let l:style_name = synIDattr(a:style_id, "name", s:whatterm)
+ ENDLET
if &diff
- let wrapperfunc_lines += [
- \ ' let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm)']
-
- " Add normal groups and diff groups to separate lists so we can order them to
- " allow diff highlight to override normal highlight
-
- " if primary style IS a diff style, grab it from the diff cache instead
- " (always succeeds because we pre-populate it)
- let wrapperfunc_lines += [
- \ '',
- \ ' if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID ||'.
- \ ' a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID',
- \ ' let l:saved_style = get(s:diffstylelist,a:style_id)',
- \ ' else'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ let l:diff_style_name = synIDattr(a:diff_style_id, "name", s:whatterm)
+ ENDLET
+
+ " Add normal groups and diff groups to separate lists so we can order them to
+ " allow diff highlight to override normal highlight
+
+ " if primary style IS a diff style, grab it from the diff cache instead
+ " (always succeeds because we pre-populate it)
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+
+ if a:style_id == s:DIFF_D_ID || a:style_id == s:DIFF_A_ID || a:style_id == s:DIFF_C_ID || a:style_id == s:DIFF_T_ID
+ let l:saved_style = get(s:diffstylelist,a:style_id)
+ else
+ ENDLET
endif
" get primary style info from cache or build it on the fly if not found
- let wrapperfunc_lines += [
- \ ' let l:saved_style = get(s:stylelist,a:style_id)',
- \ ' if type(l:saved_style) == type(0)',
- \ ' unlet l:saved_style',
- \ ' let l:saved_style = s:CSS1(a:style_id)',
- \ ' if l:saved_style != ""',
- \ ' let l:saved_style = "." . l:style_name . " { " . l:saved_style . "}"',
- \ ' endif',
- \ ' let s:stylelist[a:style_id]= l:saved_style',
- \ ' endif'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ let l:saved_style = get(s:stylelist,a:style_id)
+ if type(l:saved_style) == type(0)
+ unlet l:saved_style
+ let l:saved_style = s:CSS1(a:style_id)
+ if l:saved_style != ""
+ let l:saved_style = "." .. l:style_name .. " { " .. l:saved_style .. "}"
+ endif
+ let s:stylelist[a:style_id] = l:saved_style
+ endif
+ ENDLET
if &diff
- let wrapperfunc_lines += [ ' endif' ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ endif
+ ENDLET
endif
+" Ignore this comment, just bypassing a highlighting issue: if
" Build the wrapper tags around the text. It turns out that caching these
" gives pretty much zero performance gain and adds a lot of logic.
- let wrapperfunc_lines += [
- \ '',
- \ ' if l:saved_style == "" && empty(a:extra_attrs)'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+
+ if l:saved_style == "" && empty(a:extra_attrs)
+ ENDLET
if &diff
- let wrapperfunc_lines += [
- \ ' if a:diff_style_id <= 0'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ if a:diff_style_id <= 0
+ ENDLET
endif
" no surroundings if neither primary nor diff style has any info
- let wrapperfunc_lines += [
- \ ' return a:text'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ return a:text
+ ENDLET
if &diff
" no primary style, but diff style
- let wrapperfunc_lines += [
- \ ' else',
- \ ' return "<span class=\"" .l:diff_style_name . "\">".a:text."</span>"',
- \ ' endif'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ else
+ return '<span class="' ..l:diff_style_name .. '">'..a:text.."</span>"
+ endif
+ ENDLET
endif
+ " Ignore this comment, just bypassing a highlighting issue: if
+
" open tag for non-empty primary style
- let wrapperfunc_lines += [
- \ ' else']
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ else
+ ENDLET
" non-empty primary style. handle either empty or non-empty diff style.
"
" separate the two classes by a space to apply them both if there is a diff
" style name, unless the primary style is empty, then just use the diff style
" name
- let diffstyle =
- \ (&diff ? '(a:diff_style_id <= 0 ? "" : " ". l:diff_style_name) .'
- \ : "")
+ let s:diffstyle =
+ \ (&diff ? '(a:diff_style_id <= 0 ? "" : " " .. l:diff_style_name)..'
+ \ : '')
if s:settings.prevent_copy == ""
- let wrapperfunc_lines += [
- \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\">".a:text."</span>"'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
+ return "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'">'..a:text.."</span>"
+ ENDLET
else
" New method: use generated content in the CSS. The only thing needed here
@@ -388,59 +408,76 @@ if s:settings.use_css
" Note, if maxlength property needs to be added in the future, it will need
" to use strchars(), because HTML specifies that the maxlength parameter
" uses the number of unique codepoints for its limit.
- let wrapperfunc_lines += [
- \ ' if a:make_unselectable',
- \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'.diffstyle.'"\"'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
+ if a:make_unselectable
+ let return_span = "<span "..a:extra_attrs..'class="' .. l:style_name ..{s:diffstyle}'"'
+ ENDLET
if s:settings.use_input_for_pc !=# 'all'
- let wrapperfunc_lines[-1] .= ' " . "data-" . l:style_name . "-content=\"".a:text."\"'
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ let return_span ..= " data-" .. l:style_name .. '-content="'..a:text..'"'
+ ENDLET
endif
- let wrapperfunc_lines[-1] .= '>'
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ let return_span ..= '>'
+ ENDLET
if s:settings.use_input_for_pc !=# 'none'
- let wrapperfunc_lines[-1] .=
- \ '<input'.s:unselInputType.' class=\"" . l:style_name .'.diffstyle.'"\"'.
- \ ' value=\"".substitute(a:unformatted,''\s\+$'',"","")."\"'.
- \ ' onselect=''this.blur(); return false;'''.
- \ ' onmousedown=''this.blur(); return false;'''.
- \ ' onclick=''this.blur(); return false;'''.
- \ ' readonly=''readonly'''.
- \ ' size=\"".strwidth(a:unformatted)."\"'.
- \ (s:settings.use_xhtml ? '/' : '').'>'
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
+ let return_span ..= '<input'..s:unselInputType..' class="' .. l:style_name ..{s:diffstyle}'"'
+ let return_span ..= ' value="'..substitute(a:unformatted,'\s\+$',"","")..'"'
+ let return_span ..= " onselect='this.blur(); return false;'"
+ let return_span ..= " onmousedown='this.blur(); return false;'"
+ let return_span ..= " onclick='this.blur(); return false;'"
+ let return_span ..= " readonly='readonly'"
+ let return_span ..= ' size="'..strwidth(a:unformatted)..'"'
+ let return_span ..= (s:settings.use_xhtml ? '/>' : '>')
+ ENDLET
endif
- let wrapperfunc_lines[-1] .= '</span>"'
- let wrapperfunc_lines += [
- \ ' else',
- \ ' return "<span ".a:extra_attrs."class=\"" . l:style_name .'. diffstyle .'"\">".a:text."</span>"'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim eval ENDLET
+ return return_span..'</span>'
+ else
+ return "<span "..a:extra_attrs..'class="' .. l:style_name .. {s:diffstyle}'">'..a:text.."</span>"
+ endif
+ ENDLET
endif
- let wrapperfunc_lines += [
- \ ' endif',
- \ 'endfun'
- \ ]
+ call add(s:wrapperfunc_lines, [])
+ let s:wrapperfunc_lines[-1] =<< trim ENDLET
+ endif
+ endfun
+ ENDLET
else
" Non-CSS method just needs the wrapper.
"
" Functions used to get opening/closing automatically return null strings if
" no styles exist.
if &diff
- let wrapperfunc_lines = [
- \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)',
- \ ' return s:HtmlOpening(a:style_id, a:extra_attrs).(a:diff_style_id <= 0 ? "" :'.
- \ 's:HtmlOpening(a:diff_style_id, "")).a:text.'.
- \ '(a:diff_style_id <= 0 ? "" : s:HtmlClosing(a:diff_style_id, 0)).s:HtmlClosing(a:style_id, !empty(a:extra_attrs))',
- \ 'endfun'
- \ ]
+ let s:wrapperfunc_lines =<< trim ENDLET
+ function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)
+ if a:diff_style_id <= 0
+ let l:diff_opening = s:HtmlOpening(a:diff_style_id, "")
+ let l:diff_closing = s:HtmlClosing(a:diff_style_id, 0)
+ else
+ let l:diff_opening = ""
+ let l:diff_closing = ""
+ endif
+ return s:HtmlOpening(a:style_id, a:extra_attrs)..l:diff_opening..a:text..l:diff_closing..s:HtmlClosing(a:style_id, !empty(a:extra_attrs))
+ endfun
+ ENDLET
else
- let wrapperfunc_lines = [
- \ 'function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)',
- \ ' return s:HtmlOpening(a:style_id, a:extra_attrs).a:text.s:HtmlClosing(a:style_id, !empty(a:extra_attrs))',
- \ 'endfun'
- \ ]
+ let s:wrapperfunc_lines =<< trim ENDLET
+ function! s:BuildStyleWrapper(style_id, diff_style_id, extra_attrs, text, unusedarg, unusedarg2)
+ return s:HtmlOpening(a:style_id, a:extra_attrs)..a:text..s:HtmlClosing(a:style_id, !empty(a:extra_attrs))
+ endfun
+ ENDLET
endif
endif
" create the function we built line by line above
-exec join(wrapperfunc_lines, "\n")
+exec join(flatten(s:wrapperfunc_lines), "\n")
let s:diff_mode = &diff
@@ -471,7 +508,7 @@ function! s:HtmlFormat(text, style_id, diff_style_id, extra_attrs, make_unselect
" Replace double spaces, leading spaces, and trailing spaces if needed
if ' ' != s:HtmlSpace
- let formatted = substitute(formatted, ' ', s:HtmlSpace . s:HtmlSpace, 'g')
+ let formatted = substitute(formatted, ' ', s:HtmlSpace .. s:HtmlSpace, 'g')
let formatted = substitute(formatted, '^ ', s:HtmlSpace, 'g')
let formatted = substitute(formatted, ' \+$', s:HtmlSpace, 'g')
endif
@@ -487,7 +524,7 @@ if s:settings.prevent_copy =~# 'n'
if s:settings.line_ids
function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
if a:lnr > 0
- return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 1)
+ return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 1)
else
return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 1)
endif
@@ -503,14 +540,14 @@ if s:settings.prevent_copy =~# 'n'
" always be non-zero, however we don't want to use the <input> because that
" won't work as nice for empty text
function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
- return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 0)
+ return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0)
endfun
endif
else
if s:settings.line_ids
function! s:HtmlFormat_n(text, style_id, diff_style_id, lnr)
if a:lnr > 0
- return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'.(exists('g:html_diff_win_num') ? 'W'.g:html_diff_win_num : "").'L'.a:lnr.s:settings.id_suffix.'" ', 0)
+ return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, 'id="'..(exists('g:html_diff_win_num') ? 'W'..g:html_diff_win_num : "")..'L'..a:lnr..s:settings.id_suffix..'" ', 0)
else
return s:HtmlFormat(a:text, a:style_id, a:diff_style_id, "", 0)
endif
@@ -535,8 +572,8 @@ if s:settings.prevent_copy =~# 'f'
" Simply space-pad to the desired width inside the generated content (note
" that the FoldColumn definition includes a whitespace:pre rule)
function! s:FoldColumn_build(char, len, numfill, char2, class, click)
- return "<a href='#' class='".a:class."' onclick='".a:click."' data-FoldColumn-content='".
- \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill).
+ return "<a href='#' class='"..a:class.."' onclick='"..a:click.."' data-FoldColumn-content='".
+ \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill).
\ "'></a>"
endfun
function! s:FoldColumn_fill()
@@ -554,35 +591,38 @@ if s:settings.prevent_copy =~# 'f'
"
" Note, 'exec' commands do not recognize line continuations, so must
" concatenate lines rather than continue them.
- let build_fun_lines = [
- \ 'function! s:FoldColumn_build(char, len, numfill, char2, class, click)',
- \ ' let l:input_open = "<input readonly=''readonly''".s:unselInputType.'.
- \ ' " onselect=''this.blur(); return false;''".'.
- \ ' " onmousedown=''this.blur(); ".a:click." return false;''".'.
- \ ' " onclick=''return false;'' size=''".'.
- \ ' string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .'.
- \ ' "'' "',
- \ ' let l:common_attrs = "class=''FoldColumn'' value=''"',
- \ ' let l:input_close = (s:settings.use_xhtml ? "'' />" : "''>")'
- \ ]
+ let s:build_fun_lines = []
+ call add(s:build_fun_lines, [])
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ function! s:FoldColumn_build(char, len, numfill, char2, class, click)
+ let l:input_open = "<input readonly='readonly'"..s:unselInputType
+ let l:input_open ..= " onselect='this.blur(); return false;'"
+ let l:input_open ..= " onmousedown='this.blur(); "..a:click.." return false;'"
+ let l:input_open ..= " onclick='return false;' size='"
+ let l:input_open ..= string(a:len + (empty(a:char2) ? 0 : 1) + a:numfill) .. "' "
+ let l:common_attrs = "class='FoldColumn' value='"
+ let l:input_close = (s:settings.use_xhtml ? "' />" : "'>")
+ let l:return_span = "<span class='"..a:class.."'>"
+ let l:return_span ..= l:input_open..l:common_attrs..repeat(a:char, a:len)..(a:char2)
+ let l:return_span ..= l:input_close
+ ENDLET
if s:settings.use_input_for_pc ==# 'fallback'
- let build_fun_lines += [
- \ ' let l:gen_content_link ='.
- \ ' "<a href=''#'' class=''FoldColumn'' onclick=''".a:click."'' data-FoldColumn-content=''".'.
- \ ' repeat(a:char, a:len).a:char2.repeat('' '', a:numfill).'.
- \ ' "''></a>"'
- \ ]
+ call add(s:build_fun_lines, [])
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ let l:return_span ..= "<a href='#' class='FoldColumn' onclick='"..a:click.."'"
+ let l:return_span ..= " data-FoldColumn-content='"
+ let l:return_span ..= repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill)
+ let l:return_span ..= "'></a>"
+ ENDLET
endif
- let build_fun_lines += [
- \ ' return "<span class=''".a:class."''>".'.
- \ ' l:input_open.l:common_attrs.repeat(a:char, a:len).(a:char2).'.
- \ ' l:input_close.'.
- \ (s:settings.use_input_for_pc ==# 'fallback' ? 'l:gen_content_link.' : "").
- \ ' "</span>"',
- \ 'endfun'
- \ ]
+ call add(s:build_fun_lines, [])
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ let l:return_span ..= "</span>"
+ return l:return_span
+ endfun
+ ENDLET
" create the function we built line by line above
- exec join(build_fun_lines, "\n")
+ exec join(flatten(s:build_fun_lines), "\n")
function! s:FoldColumn_fill()
return s:FoldColumn_build(' ', s:foldcolumn, 0, '', 'FoldColumn', '')
@@ -592,8 +632,8 @@ else
" For normal fold columns, simply space-pad to the desired width (note that
" the FoldColumn definition includes a whitespace:pre rule)
function! s:FoldColumn_build(char, len, numfill, char2, class, click)
- return "<a href='#' class='".a:class."' onclick='".a:click."'>".
- \ repeat(a:char, a:len).a:char2.repeat(' ', a:numfill).
+ return "<a href='#' class='"..a:class.."' onclick='"..a:click.."'>".
+ \ repeat(a:char, a:len)..a:char2..repeat(' ', a:numfill).
\ "</a>"
endfun
function! s:FoldColumn_fill()
@@ -625,29 +665,30 @@ endif
" Return CSS style describing given highlight id (can be empty)
function! s:CSS1(id)
let a = ""
- if synIDattr(a:id, "inverse")
+ let translated_ID = synIDtrans(a:id)
+ if synIDattr(translated_ID, "inverse")
" For inverse, we always must set both colors (and exchange them)
- let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
- let a = a . "color: " . ( x != "" ? x : s:bgc ) . "; "
- let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
- let a = a . "background-color: " . ( x != "" ? x : s:fgc ) . "; "
+ let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
+ let a = a .. "color: " .. ( x != "" ? x : s:bgc ) .. "; "
+ let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
+ let a = a .. "background-color: " .. ( x != "" ? x : s:fgc ) .. "; "
else
- let x = s:HtmlColor(synIDattr(a:id, "fg#", s:whatterm))
- if x != "" | let a = a . "color: " . x . "; " | endif
- let x = s:HtmlColor(synIDattr(a:id, "bg#", s:whatterm))
+ let x = s:HtmlColor(synIDattr(translated_ID, "fg#", s:whatterm))
+ if x != "" | let a = a .. "color: " .. x .. "; " | endif
+ let x = s:HtmlColor(synIDattr(translated_ID, "bg#", s:whatterm))
if x != ""
- let a = a . "background-color: " . x . "; "
+ let a = a .. "background-color: " .. x .. "; "
" stupid hack because almost every browser seems to have at least one font
" which shows 1px gaps between lines which have background
- let a = a . "padding-bottom: 1px; "
- elseif (a:id == s:FOLDED_ID || a:id == s:LINENR_ID || a:id == s:FOLD_C_ID) && !empty(s:settings.prevent_copy)
+ let a = a .. "padding-bottom: 1px; "
+ elseif (translated_ID == s:FOLDED_ID || translated_ID == s:LINENR_ID || translated_ID == s:FOLD_C_ID) && !empty(s:settings.prevent_copy)
" input elements default to a different color than the rest of the page
- let a = a . "background-color: " . s:bgc . "; "
+ let a = a .. "background-color: " .. s:bgc .. "; "
endif
endif
- if synIDattr(a:id, "bold") | let a = a . "font-weight: bold; " | endif
- if synIDattr(a:id, "italic") | let a = a . "font-style: italic; " | endif
- if synIDattr(a:id, "underline") | let a = a . "text-decoration: underline; " | endif
+ if synIDattr(translated_ID, "bold") | let a = a .. "font-weight: bold; " | endif
+ if synIDattr(translated_ID, "italic") | let a = a .. "font-style: italic; " | endif
+ if synIDattr(translated_ID, "underline") | let a = a .. "text-decoration: underline; " | endif
return a
endfun
@@ -720,7 +761,7 @@ if exists("g:loaded_2html_plugin")
let s:pluginversion = g:loaded_2html_plugin
else
if !exists("g:unloaded_tohtml_plugin")
- let s:main_plugin_path = expand("<sfile>:p:h:h")."/plugin/tohtml.vim"
+ let s:main_plugin_path = expand("<sfile>:p:h:h").."/plugin/tohtml.vim"
if filereadable(s:main_plugin_path)
let s:lines = readfile(s:main_plugin_path, "", 20)
call filter(s:lines, 'v:val =~ "loaded_2html_plugin = "')
@@ -743,12 +784,12 @@ let s:orgbufnr = winbufnr(0)
let s:origwin_stl = &l:stl
if expand("%") == ""
if exists('g:html_diff_win_num')
- exec 'new Untitled_win'.g:html_diff_win_num.'.'.(s:settings.use_xhtml ? 'x' : '').'html'
+ exec 'new Untitled_win'..g:html_diff_win_num..'.'.(s:settings.use_xhtml ? 'xhtml' : 'html')
else
- exec 'new Untitled.'.(s:settings.use_xhtml ? 'x' : '').'html'
+ exec 'new Untitled.'..(s:settings.use_xhtml ? 'xhtml' : 'html')
endif
else
- exec 'new %.'.(s:settings.use_xhtml ? 'x' : '').'html'
+ exec 'new %.'..(s:settings.use_xhtml ? 'xhtml' : 'html')
endif
" Resize the new window to very small in order to make it draw faster
@@ -795,7 +836,7 @@ let s:lines = []
if s:settings.use_xhtml
if s:settings.encoding != ""
- call add(s:lines, "<?xml version=\"1.0\" encoding=\"" . s:settings.encoding . "\"?>")
+ call add(s:lines, "<?xml version=\"1.0\" encoding=\"" .. s:settings.encoding .. "\"?>")
else
call add(s:lines, "<?xml version=\"1.0\"?>")
endif
@@ -808,9 +849,9 @@ let s:HtmlSpace = ' '
let s:LeadingSpace = ' '
let s:HtmlEndline = ''
if s:settings.no_pre
- let s:HtmlEndline = '<br' . s:tag_close
+ let s:HtmlEndline = '<br' .. s:tag_close
let s:LeadingSpace = s:settings.use_xhtml ? '&#160;' : '&nbsp;'
- let s:HtmlSpace = '\' . s:LeadingSpace
+ let s:HtmlSpace = '\' .. s:LeadingSpace
endif
" HTML header, with the title and generator ;-). Left free space for the CSS,
@@ -823,30 +864,30 @@ if !s:settings.no_doc
" contained in XML information (to avoid haggling over content type)
if s:settings.encoding != "" && !s:settings.use_xhtml
if s:html5
- call add(s:lines, '<meta charset="' . s:settings.encoding . '"' . s:tag_close)
+ call add(s:lines, '<meta charset="' .. s:settings.encoding .. '"' .. s:tag_close)
else
- call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" . s:settings.encoding . '"' . s:tag_close)
+ call add(s:lines, "<meta http-equiv=\"content-type\" content=\"text/html; charset=" .. s:settings.encoding .. '"' .. s:tag_close)
endif
endif
call extend(s:lines, [
- \ ("<title>".expand("%:p:~")."</title>"),
- \ ("<meta name=\"Generator\" content=\"Vim/".v:version/100.".".v:version%100.'"'.s:tag_close),
- \ ("<meta name=\"plugin-version\" content=\"".s:pluginversion.'"'.s:tag_close)
+ \ ("<title>"..expand("%:p:~").."</title>"),
+ \ ("<meta name=\"Generator\" content=\"Vim/"..v:version/100.."."..v:version%100..'"'..s:tag_close),
+ \ ("<meta name=\"plugin-version\" content=\""..s:pluginversion..'"'..s:tag_close)
\ ])
- call add(s:lines, '<meta name="syntax" content="'.s:current_syntax.'"'.s:tag_close)
- call add(s:lines, '<meta name="settings" content="'.
- \ join(filter(keys(s:settings),'s:settings[v:val]'),',').
- \ ',prevent_copy='.s:settings.prevent_copy.
- \ ',use_input_for_pc='.s:settings.use_input_for_pc.
- \ '"'.s:tag_close)
- call add(s:lines, '<meta name="colorscheme" content="'.
+ call add(s:lines, '<meta name="syntax" content="'..s:current_syntax..'"'..s:tag_close)
+ call add(s:lines, '<meta name="settings" content="'..
+ \ join(filter(keys(s:settings),'s:settings[v:val]'),',')..
+ \ ',prevent_copy='..s:settings.prevent_copy..
+ \ ',use_input_for_pc='..s:settings.use_input_for_pc..
+ \ '"'..s:tag_close)
+ call add(s:lines, '<meta name="colorscheme" content="'..
\ (exists('g:colors_name')
\ ? g:colors_name
- \ : 'none'). '"'.s:tag_close)
+ \ : 'none').. '"'..s:tag_close)
if s:settings.use_css
call extend(s:lines, [
- \ "<style" . (s:html5 ? "" : " type=\"text/css\"") . ">",
+ \ "<style" .. (s:html5 ? "" : " type=\"text/css\"") .. ">",
\ s:settings.use_xhtml ? "" : "<!--"])
let s:ieonly = []
if s:settings.dynamic_folds
@@ -921,7 +962,7 @@ if !s:settings.no_doc
if s:uses_script
call extend(s:lines, [
\ "",
- \ "<script" . (s:html5 ? "" : " type='text/javascript'") . ">",
+ \ "<script" .. (s:html5 ? "" : " type='text/javascript'") .. ">",
\ s:settings.use_xhtml ? '//<![CDATA[' : "<!--"])
endif
@@ -968,7 +1009,7 @@ if !s:settings.no_doc
\ "",
\ " /* navigate upwards in the DOM tree to open all folds containing the line */",
\ " var node = lineElem;",
- \ " while (node && node.id != 'vimCodeElement".s:settings.id_suffix."')",
+ \ " while (node && node.id != 'vimCodeElement"..s:settings.id_suffix.."')",
\ " {",
\ " if (node.className == 'closed-fold')",
\ " {",
@@ -1003,7 +1044,7 @@ if !s:settings.no_doc
endif
call extend(s:lines, ["</head>",
- \ "<body".(s:settings.line_ids ? " onload='JumpToLine();'" : "").">"])
+ \ "<body"..(s:settings.line_ids ? " onload='JumpToLine();'" : "")..">"])
endif
if s:settings.no_pre
@@ -1015,20 +1056,20 @@ else
call extend(s:lines, ["<pre id='vimCodeElement" .. s:settings.id_suffix .. "'>"])
endif
-exe s:orgwin . "wincmd w"
+exe s:orgwin .. "wincmd w"
" caches of style data
" initialize to include line numbers if using them
if s:settings.number_lines
- let s:stylelist = { s:LINENR_ID : ".LineNr { " . s:CSS1( s:LINENR_ID ) . "}" }
+ let s:stylelist = { s:LINENR_ID : ".LineNr { " .. s:CSS1( s:LINENR_ID ) .. "}" }
else
let s:stylelist = {}
endif
let s:diffstylelist = {
- \ s:DIFF_A_ID : ".DiffAdd { " . s:CSS1( s:DIFF_A_ID ) . "}",
- \ s:DIFF_C_ID : ".DiffChange { " . s:CSS1( s:DIFF_C_ID ) . "}",
- \ s:DIFF_D_ID : ".DiffDelete { " . s:CSS1( s:DIFF_D_ID ) . "}",
- \ s:DIFF_T_ID : ".DiffText { " . s:CSS1( s:DIFF_T_ID ) . "}"
+ \ s:DIFF_A_ID : ".DiffAdd { " .. s:CSS1( s:DIFF_A_ID ) .. "}",
+ \ s:DIFF_C_ID : ".DiffChange { " .. s:CSS1( s:DIFF_C_ID ) .. "}",
+ \ s:DIFF_D_ID : ".DiffDelete { " .. s:CSS1( s:DIFF_D_ID ) .. "}",
+ \ s:DIFF_T_ID : ".DiffText { " .. s:CSS1( s:DIFF_T_ID ) .. "}"
\ }
" set up progress bar in the status line
@@ -1046,17 +1087,17 @@ if !s:settings.no_progress
\ g:colors_name != s:last_colors_name
let s:last_colors_name = exists("g:colors_name") ? g:colors_name : "none"
- let l:diffatr = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? "fg#" : "bg#"
- let l:stlatr = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? "fg#" : "bg#"
+ let l:diffatr = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#"
+ let l:stlatr = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? "fg#" : "bg#"
- let l:progbar_color = synIDattr(hlID("DiffDelete"), l:diffatr, s:whatterm)
- let l:stl_color = synIDattr(hlID("StatusLine"), l:stlatr, s:whatterm)
+ let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), l:diffatr, s:whatterm)
+ let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), l:stlatr, s:whatterm)
if "" == l:progbar_color
- let l:progbar_color = synIDattr(hlID("DiffDelete"), "reverse", s:whatterm) ? s:fgc : s:bgc
+ let l:progbar_color = synIDattr(hlID("DiffDelete")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc
endif
if "" == l:stl_color
- let l:stl_color = synIDattr(hlID("StatusLine"), "reverse", s:whatterm) ? s:fgc : s:bgc
+ let l:stl_color = synIDattr(hlID("StatusLine")->synIDtrans(), "reverse", s:whatterm) ? s:fgc : s:bgc
endif
if l:progbar_color == l:stl_color
@@ -1086,13 +1127,13 @@ if !s:settings.no_progress
endif
echomsg "diff detected progbar color set to" l:progbar_color
endif
- exe "hi TOhtmlProgress_auto" s:whatterm."bg=".l:progbar_color
+ exe "hi TOhtmlProgress_auto" s:whatterm.."bg="..l:progbar_color
endif
endfun
func! s:ProgressBar(title, max_value, winnr)
let pgb=copy(s:progressbar)
- let pgb.title = a:title.' '
+ let pgb.title = a:title..' '
let pgb.max_value = a:max_value
let pgb.winnr = a:winnr
let pgb.cur_value = 0
@@ -1194,6 +1235,66 @@ if !s:settings.no_progress
call s:SetProgbarColor()
endif
+let s:build_fun_lines = []
+call add(s:build_fun_lines, [])
+let s:build_fun_lines[-1] =<< trim ENDLET
+ func! s:Add_diff_fill(lnum)
+ let l:filler = diff_filler(a:lnum)
+ if l:filler > 0
+ let l:to_insert = l:filler
+ while l:to_insert > 0
+ let l:new = repeat(s:difffillchar, 3)
+
+ if l:to_insert > 2 && l:to_insert < l:filler && !s:settings.whole_filler
+ let l:new = l:new .. " " .. l:filler .. " inserted lines "
+ let l:to_insert = 2
+ endif
+ENDLET
+call add(s:build_fun_lines, [])
+if !s:settings.no_pre
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ " HTML line wrapping is off--go ahead and fill to the margin
+ " TODO: what about when CSS wrapping is turned on?
+ let l:new = l:new .. repeat(s:difffillchar, &columns - strlen(l:new) - s:margin)
+ ENDLET
+else
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ let l:new = l:new .. repeat(s:difffillchar, 3)
+ ENDLET
+endif
+call add(s:build_fun_lines, [])
+let s:build_fun_lines[-1] =<< trim ENDLET
+ let l:new = s:HtmlFormat_d(l:new, s:DIFF_D_ID, 0)
+ENDLET
+if s:settings.number_lines
+ call add(s:build_fun_lines, [])
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ " Indent if line numbering is on. Indent gets style of line number
+ " column.
+ let l:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) .. l:new
+ ENDLET
+endif
+if s:settings.dynamic_folds && !s:settings.no_foldcolumn
+ call add(s:build_fun_lines, [])
+ let s:build_fun_lines[-1] =<< trim ENDLET
+ if s:foldcolumn > 0
+ " Indent for foldcolumn if there is one. Assume it's empty, there should
+ " not be a fold for deleted lines in diff mode.
+ let l:new = s:FoldColumn_fill() .. l:new
+ endif
+ ENDLET
+endif
+" Ignore this comment, just bypassing a highlighting issue: if
+call add(s:build_fun_lines, [])
+let s:build_fun_lines[-1] =<< trim ENDLET
+ call add(s:lines, l:new..s:HtmlEndline)
+ let l:to_insert = l:to_insert - 1
+ endwhile
+ endif
+ endfun
+ENDLET
+exec join(flatten(s:build_fun_lines), "\n")
+
" First do some preprocessing for dynamic folding. Do this for the entire file
" so we don't accidentally start within a closed fold or something.
let s:allfolds = []
@@ -1220,7 +1321,7 @@ if s:settings.dynamic_folds
let s:newfold = {'firstline': s:lnum, 'lastline': foldclosedend(s:lnum), 'level': s:level,'type': "closed-fold"}
call add(s:allfolds, s:newfold)
" open the fold so we can find any contained folds
- execute s:lnum."foldopen"
+ execute s:lnum.."foldopen"
else
if !s:settings.no_progress
call s:pgb.incr()
@@ -1252,7 +1353,7 @@ if s:settings.dynamic_folds
call add(s:allfolds, s:newfold)
endif
" open the fold so we can find any contained folds
- execute s:lnum."foldopen"
+ execute s:lnum.."foldopen"
else
if !s:settings.no_progress
call s:pgb.incr()
@@ -1339,7 +1440,7 @@ if s:settings.dynamic_folds
" Note that only when a start and an end line is specified will a fold
" containing the current range ever be removed.
while leveladjust > 0
- exe g:html_start_line."foldopen"
+ exe g:html_start_line.."foldopen"
let leveladjust -= 1
endwhile
endif
@@ -1399,47 +1500,11 @@ endif
while s:lnum <= s:end
" If there are filler lines for diff mode, show these above the line.
- let s:filler = diff_filler(s:lnum)
- if s:filler > 0
- let s:n = s:filler
- while s:n > 0
- let s:new = repeat(s:difffillchar, 3)
-
- if s:n > 2 && s:n < s:filler && !s:settings.whole_filler
- let s:new = s:new . " " . s:filler . " inserted lines "
- let s:n = 2
- endif
-
- if !s:settings.no_pre
- " HTML line wrapping is off--go ahead and fill to the margin
- " TODO: what about when CSS wrapping is turned on?
- let s:new = s:new . repeat(s:difffillchar, &columns - strlen(s:new) - s:margin)
- else
- let s:new = s:new . repeat(s:difffillchar, 3)
- endif
-
- let s:new = s:HtmlFormat_d(s:new, s:DIFF_D_ID, 0)
- if s:settings.number_lines
- " Indent if line numbering is on. Indent gets style of line number
- " column.
- let s:new = s:HtmlFormat_n(repeat(' ', s:margin), s:LINENR_ID, 0, 0) . s:new
- endif
- if s:settings.dynamic_folds && !s:settings.no_foldcolumn && s:foldcolumn > 0
- " Indent for foldcolumn if there is one. Assume it's empty, there should
- " not be a fold for deleted lines in diff mode.
- let s:new = s:FoldColumn_fill() . s:new
- endif
- call add(s:lines, s:new.s:HtmlEndline)
-
- let s:n = s:n - 1
- endwhile
- unlet s:n
- endif
- unlet s:filler
+ call s:Add_diff_fill(s:lnum)
" Start the line with the line number.
if s:settings.number_lines
- let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) . s:lnum . ' '
+ let s:numcol = repeat(' ', s:margin - 1 - strlen(s:lnum)) .. s:lnum .. ' '
endif
let s:new = ""
@@ -1450,11 +1515,11 @@ while s:lnum <= s:end
let s:new = foldtextresult(s:lnum)
if !s:settings.no_pre
" HTML line wrapping is off--go ahead and fill to the margin
- let s:new = s:new . repeat(s:foldfillchar, &columns - strlen(s:new))
+ let s:new = s:new .. repeat(s:foldfillchar, &columns - strlen(s:new))
endif
" put numcol in a separate group for sake of unselectable text
- let s:new = (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, s:lnum): "") . s:HtmlFormat_t(s:new, s:FOLDED_ID, 0)
+ let s:new = (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, s:lnum): "") .. s:HtmlFormat_t(s:new, s:FOLDED_ID, 0)
" Skip to the end of the fold
let s:new_lnum = foldclosedend(s:lnum)
@@ -1475,7 +1540,7 @@ while s:lnum <= s:end
if s:settings.dynamic_folds
" First insert a closing for any open folds that end on this line
while !empty(s:foldstack) && get(s:foldstack,0).lastline == s:lnum-1
- let s:new = s:new."</span></span>"
+ let s:new = s:new.."</span></span>"
call remove(s:foldstack, 0)
endwhile
@@ -1483,9 +1548,9 @@ while s:lnum <= s:end
let s:firstfold = 1
while !empty(s:allfolds) && get(s:allfolds,0).firstline == s:lnum
let s:foldId = s:foldId + 1
- let s:new .= "<span id='"
- let s:new .= (exists('g:html_diff_win_num') ? "win".g:html_diff_win_num : "")
- let s:new .= "fold".s:foldId.s:settings.id_suffix."' class='".s:allfolds[0].type."'>"
+ let s:new ..= "<span id='"
+ let s:new ..= (exists('g:html_diff_win_num') ? "win"..g:html_diff_win_num : "")
+ let s:new ..= "fold"..s:foldId..s:settings.id_suffix.."' class='"..s:allfolds[0].type.."'>"
" Unless disabled, add a fold column for the opening line of a fold.
@@ -1496,20 +1561,20 @@ while s:lnum <= s:end
if !s:settings.no_foldcolumn
" add fold column that can open the new fold
if s:allfolds[0].level > 1 && s:firstfold
- let s:new = s:new . s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "",
- \ 'toggle-open FoldColumn','javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");')
+ let s:new = s:new .. s:FoldColumn_build('|', s:allfolds[0].level - 1, 0, "",
+ \ 'toggle-open FoldColumn','javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");')
endif
" add the filler spaces separately from the '+' char so that it can be
" shown/hidden separately during a hover unfold
- let s:new = s:new . s:FoldColumn_build("+", 1, 0, "",
- \ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
+ let s:new = s:new .. s:FoldColumn_build("+", 1, 0, "",
+ \ 'toggle-open FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
" If this is not the last fold we're opening on this line, we need
" to keep the filler spaces hidden if the fold is opened by mouse
" hover. If it is the last fold to open in the line, we shouldn't hide
" them, so don't apply the toggle-filler class.
- let s:new = s:new . s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "",
- \ 'toggle-open FoldColumn'. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""),
- \ 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
+ let s:new = s:new .. s:FoldColumn_build(" ", 1, s:foldcolumn - s:allfolds[0].level - 1, "",
+ \ 'toggle-open FoldColumn'.. (get(s:allfolds, 1, {'firstline': 0}).firstline == s:lnum ?" toggle-filler" :""),
+ \ 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
" add fold column that can close the new fold
" only add extra blank space if we aren't opening another fold on the
@@ -1522,12 +1587,12 @@ while s:lnum <= s:end
if s:firstfold
" the first fold in a line has '|' characters from folds opened in
" previous lines, before the '-' for this fold
- let s:new .= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-',
- \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
+ let s:new ..= s:FoldColumn_build('|', s:allfolds[0].level - 1, s:extra_space, '-',
+ \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
else
" any subsequent folds in the line only add a single '-'
- let s:new = s:new . s:FoldColumn_build("-", 1, s:extra_space, "",
- \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'.s:foldId.s:settings.id_suffix.'");')
+ let s:new = s:new .. s:FoldColumn_build("-", 1, s:extra_space, "",
+ \ 'toggle-closed FoldColumn', 'javascript:toggleFold("fold'..s:foldId..s:settings.id_suffix..'");')
endif
let s:firstfold = 0
endif
@@ -1535,12 +1600,12 @@ while s:lnum <= s:end
" Add fold text, moving the span ending to the next line so collapsing
" of folds works correctly.
" Put numcol in a separate group for sake of unselectable text.
- let s:new = s:new . (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, 0) : "") . substitute(s:HtmlFormat_t(foldtextresult(s:lnum), s:FOLDED_ID, 0), '</span>', s:HtmlEndline.'\n\0', '')
- let s:new = s:new . "<span class='fulltext'>"
+ let s:new = s:new .. (s:settings.number_lines ? s:HtmlFormat_n(s:numcol, s:FOLDED_ID, 0, 0) : "") .. substitute(s:HtmlFormat_t(foldtextresult(s:lnum), s:FOLDED_ID, 0), '</span>', s:HtmlEndline..'\n\0', '')
+ let s:new = s:new .. "<span class='fulltext'>"
" open the fold now that we have the fold text to allow retrieval of
" fold text for subsequent folds
- execute s:lnum."foldopen"
+ execute s:lnum.."foldopen"
call insert(s:foldstack, remove(s:allfolds,0))
let s:foldstack[0].id = s:foldId
endwhile
@@ -1555,13 +1620,13 @@ while s:lnum <= s:end
" add the empty foldcolumn for unfolded lines if there is a fold
" column at all
if s:foldcolumn > 0
- let s:new = s:new . s:FoldColumn_fill()
+ let s:new = s:new .. s:FoldColumn_fill()
endif
else
" add the fold column for folds not on the opening line
if get(s:foldstack, 0).firstline < s:lnum
- let s:new = s:new . s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "",
- \ 'FoldColumn', 'javascript:toggleFold("fold'.s:foldstack[0].id.s:settings.id_suffix.'");')
+ let s:new = s:new .. s:FoldColumn_build('|', s:foldstack[0].level, s:foldcolumn - s:foldstack[0].level, "",
+ \ 'FoldColumn', 'javascript:toggleFold("fold'..s:foldstack[0].id..s:settings.id_suffix..'");')
endif
endif
endif
@@ -1569,9 +1634,9 @@ while s:lnum <= s:end
" Now continue with the unfolded line text
if s:settings.number_lines
- let s:new = s:new . s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum)
+ let s:new = s:new .. s:HtmlFormat_n(s:numcol, s:LINENR_ID, 0, s:lnum)
elseif s:settings.line_ids
- let s:new = s:new . s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum)
+ let s:new = s:new .. s:HtmlFormat_n("", s:LINENR_ID, 0, s:lnum)
endif
" Get the diff attribute, if any.
@@ -1611,7 +1676,7 @@ while s:lnum <= s:end
if s:len < &columns && !s:settings.no_pre
" Add spaces at the end of the raw text line to extend the changed
" line to the full width.
- let s:line = s:line . repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin)
+ let s:line = s:line .. repeat(' ', &columns - virtcol([s:lnum, s:len]) - s:margin)
let s:len = &columns
endif
else
@@ -1649,11 +1714,11 @@ while s:lnum <= s:end
" if the found tab is the first character in the text being
" processed, we need to get the character prior to the text,
" given by startcol.
- let s:prevc = matchstr(s:line, '.\%' . (s:startcol + s:offset) . 'c')
+ let s:prevc = matchstr(s:line, '.\%' .. (s:startcol + s:offset) .. 'c')
else
" Otherwise, the byte index of the tab into s:expandedtab is
" given by s:idx.
- let s:prevc = matchstr(s:expandedtab, '.\%' . (s:idx + 1) . 'c')
+ let s:prevc = matchstr(s:expandedtab, '.\%' .. (s:idx + 1) .. 'c')
endif
let s:vcol = virtcol([s:lnum, s:startcol + s:idx + s:offset - len(s:prevc)])
@@ -1689,12 +1754,12 @@ while s:lnum <= s:end
" Output the text with the same synID, with class set to the highlight ID
" name, unless it has been concealed completely.
if strlen(s:expandedtab) > 0
- let s:new = s:new . s:HtmlFormat(s:expandedtab, s:id, s:diff_id, "", 0)
+ let s:new = s:new .. s:HtmlFormat(s:expandedtab, s:id, s:diff_id, "", 0)
endif
endwhile
endif
- call extend(s:lines, split(s:new.s:HtmlEndline, '\n', 1))
+ call extend(s:lines, split(s:new..s:HtmlEndline, '\n', 1))
if !s:settings.no_progress && s:pgb.needs_redraw
redrawstatus
let s:pgb.needs_redraw = 0
@@ -1706,17 +1771,21 @@ while s:lnum <= s:end
endif
endwhile
+" Diff filler is returned based on what needs inserting *before* the given line.
+" So to get diff filler at the end of the buffer, we need to use last line + 1
+call s:Add_diff_fill(s:end+1)
+
if s:settings.dynamic_folds
" finish off any open folds
while !empty(s:foldstack)
- let s:lines[-1].="</span></span>"
+ let s:lines[-1]..="</span></span>"
call remove(s:foldstack, 0)
endwhile
" add fold column to the style list if not already there
let s:id = s:FOLD_C_ID
if !has_key(s:stylelist, s:id)
- let s:stylelist[s:id] = '.FoldColumn { ' . s:CSS1(s:id) . '}'
+ let s:stylelist[s:id] = '.FoldColumn { ' .. s:CSS1(s:id) .. '}'
endif
endif
@@ -1734,7 +1803,7 @@ if !s:settings.no_doc
call extend(s:lines, ["</body>", "</html>"])
endif
-exe s:newwin . "wincmd w"
+exe s:newwin .. "wincmd w"
call setline(1, s:lines)
unlet s:lines
@@ -1757,17 +1826,17 @@ if s:settings.use_css && !s:settings.no_doc
" Normal/global attributes
if s:settings.no_pre
- call append('.', "body { color: " . s:fgc . "; background-color: " . s:bgc . "; font-family: ". s:htmlfont ."; }")
+ call append('.', "body { color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; font-family: ".. s:htmlfont .."; }")
+
else
- call append('.', "pre { " . s:whitespace . "font-family: ". s:htmlfont ."; color: " . s:fgc . "; background-color: " . s:bgc . "; }")
+ call append('.', "pre { " .. s:whitespace .. "font-family: ".. s:htmlfont .."; color: " .. s:fgc .. "; background-color: " .. s:bgc .. "; }")
+
yank
put
execute "normal! ^cwbody\e"
" body should not have the wrap formatting, only the pre section
if s:whitespace != ''
- exec 's#'.s:whitespace
+ exec 's#'..s:whitespace
endif
endif
" fix browser inconsistencies (sometimes within the same browser) of different
@@ -1778,13 +1847,13 @@ if s:settings.use_css && !s:settings.no_doc
" like normal text
if !empty(s:settings.prevent_copy)
if s:settings.use_input_for_pc !=# "none"
- call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '.s:htmlfont.'; }')
+ call append('.', 'input { border: none; margin: 0; padding: 0; font-family: '..s:htmlfont..'; }')
+
" ch units for browsers which support them, em units for a somewhat
" reasonable fallback.
for w in range(1, 20, 1)
call append('.', [
- \ "input[size='".w."'] { width: ".w."em; width: ".w."ch; }"
+ \ "input[size='"..w.."'] { width: "..w.."em; width: "..w.."ch; }"
\ ])
+
endfor
@@ -1828,14 +1897,15 @@ if s:settings.use_css && !s:settings.no_doc
endif
for s:style_name in s:unselectable_styles
call append('.', [
- \ ' .'.s:style_name.' { user-select: none; }',
- \ ' [data-'.s:style_name.'-content]::before { content: attr(data-'.s:style_name.'-content); }',
- \ ' [data-'.s:style_name.'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
- \ ' span[data-'.s:style_name.'-content]::before { cursor: default; }',
+ \ ' .'..s:style_name..' { user-select: none; }',
+ \ ' [data-'..s:style_name..'-content]::before { content: attr(data-'..s:style_name..'-content); }',
+ \ ' [data-'..s:style_name..'-content]::before { padding-bottom: 1px; display: inline-block; /* match the 1-px padding of standard items with background */ }',
+ \ ' span[data-'..s:style_name..'-content]::before { cursor: default; }',
\ ])
+4
endfor
if s:settings.use_input_for_pc !=# 'none'
+ " Note, the extra '}' is to match the "@supports" above
call append('.', [
\ ' input { display: none; }',
\ '}'
@@ -1851,12 +1921,12 @@ if s:settings.use_css && !s:settings.no_doc
" Make the cursor show active fold columns as active areas, and empty fold
" columns as not interactive.
call append('.', ['input.FoldColumn { cursor: pointer; }',
- \ 'input.FoldColumn[value="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }'
+ \ 'input.FoldColumn[value="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }'
\ ])
+2
if s:settings.use_input_for_pc !=# 'all'
call append('.', [
- \ 'a[data-FoldColumn-content="'.repeat(' ', s:foldcolumn).'"] { cursor: default; }'
+ \ 'a[data-FoldColumn-content="'..repeat(' ', s:foldcolumn)..'"] { cursor: default; }'
\ ])
+1
end
@@ -1884,7 +1954,7 @@ endif
if !s:settings.use_css && !s:settings.no_doc
" For Netscape 4, set <body> attributes too, though, strictly speaking, it's
" incorrect.
- execute '%s:<body\([^>]*\):<body bgcolor="' . s:bgc . '" text="' . s:fgc . '"\1>\r<font face="'. s:htmlfont .'"'
+ execute '%s:<body\([^>]*\):<body bgcolor="' .. s:bgc .. '" text="' .. s:fgc .. '"\1>\r<font face="'.. s:htmlfont ..'"'
endif
" Gather attributes for all other classes. Do diff first so that normal
@@ -1935,7 +2005,7 @@ let @/ = s:old_search
let &more = s:old_more
" switch to original window to restore those settings
-exe s:orgwin . "wincmd w"
+exe s:orgwin .. "wincmd w"
if !s:settings.expand_tabs
let &l:isprint = s:old_isprint
@@ -1945,7 +2015,7 @@ let &l:et = s:old_et
let &l:scrollbind = s:old_bind
" and back to the new window again to end there
-exe s:newwin . "wincmd w"
+exe s:newwin .. "wincmd w"
let &l:stl = s:newwin_stl
exec 'resize' s:old_winheight
@@ -1982,10 +2052,13 @@ if !v:profiling
delfunc s:progressbar.incr
unlet s:pgb s:progressbar
endif
+
+ delfunc s:Add_diff_fill
endif
-unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace
+unlet! s:new_lnum s:diffattr s:difffillchar s:foldfillchar s:HtmlSpace s:diffstyle
unlet! s:LeadingSpace s:HtmlEndline s:firstfold s:numcol s:foldcolumn
+unlet! s:wrapperfunc_lines s:build_fun_lines
unlet s:foldstack s:allfolds s:foldId s:settings
let &cpo = s:cpo_sav
diff --git a/runtime/syntax/8th.vim b/runtime/syntax/8th.vim
index ce27d10a44..643c9cb095 100644
--- a/runtime/syntax/8th.vim
+++ b/runtime/syntax/8th.vim
@@ -363,7 +363,7 @@ syn region eighthComment start="\zs\\" end="$" contains=eighthTodo
if !exists("did_eighth_syntax_inits")
let did_eighth_syntax_inits=1
- " The default methods for highlighting. Can be overriden later.
+ " The default methods for highlighting. Can be overridden later.
hi def link eighthTodo Todo
hi def link eighthOperators Operator
hi def link eighthMath Number
diff --git a/runtime/syntax/README.txt b/runtime/syntax/README.txt
index d6a86e5ca9..756ae41587 100644
--- a/runtime/syntax/README.txt
+++ b/runtime/syntax/README.txt
@@ -28,14 +28,16 @@ whitespace.vim View Tabs and Spaces.
If you want to write a syntax file, read the docs at ":help usr_44.txt".
If you make a new syntax file which would be useful for others, please send it
-to Bram@vim.org. Include instructions for detecting the file type for this
-language, by file name extension or by checking a few lines in the file.
-And please write the file in a portable way, see ":help 44.12".
+to the vim-dev mailing list <vim-dev@vim.org>. Include instructions for
+detecting the file type for this language, by file name extension or by
+checking a few lines in the file. And please write the file in a portable way,
+see ":help 44.12".
If you have remarks about an existing file, send them to the maintainer of
-that file. Only when you get no response send a message to Bram@vim.org.
+that file. Only when you get no response send a message to the vim-dev
+mailing list: <vim-dev@vim.org>.
If you are the maintainer of a syntax file and make improvements, send the new
-version to Bram@vim.org.
+version to the vim-dev mailing list: <vim-dev@vim.org>
For further info see ":help syntax" in Vim.
diff --git a/runtime/syntax/a65.vim b/runtime/syntax/a65.vim
index b232e826cd..6445b9438b 100644
--- a/runtime/syntax/a65.vim
+++ b/runtime/syntax/a65.vim
@@ -118,7 +118,7 @@ syn match a65Section "\(^\|\s\)\.)\($\|\s\)"
" Strings
syn match a65String "\".*\""
-" Programm Counter
+" Program Counter
syn region a65PC start="\*=" end="\>" keepend
" HI/LO Byte
diff --git a/runtime/syntax/aap.vim b/runtime/syntax/aap.vim
index 8399a4d224..87cedab30f 100644
--- a/runtime/syntax/aap.vim
+++ b/runtime/syntax/aap.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: A-A-P recipe
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2004 Jun 13
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/abap.vim b/runtime/syntax/abap.vim
index e48dfc3603..627e51504a 100644
--- a/runtime/syntax/abap.vim
+++ b/runtime/syntax/abap.vim
@@ -122,7 +122,7 @@ syn keyword abapStatement TABLES TIMES TRANSFER TRANSLATE TRY TYPE TYPES
syn keyword abapStatement UNASSIGN ULINE UNPACK UPDATE
syn keyword abapStatement WHEN WHILE WINDOW WRITE
-" More statemets
+" More statements
syn keyword abapStatement LINES
syn keyword abapStatement INTO GROUP BY HAVING ORDER BY SINGLE
syn keyword abapStatement APPENDING CORRESPONDING FIELDS OF TABLE
diff --git a/runtime/syntax/asm68k.vim b/runtime/syntax/asm68k.vim
index 1607507909..104887d026 100644
--- a/runtime/syntax/asm68k.vim
+++ b/runtime/syntax/asm68k.vim
@@ -4,7 +4,7 @@
" Last change: 2001 May 01
"
" This is incomplete. In particular, support for 68020 and
-" up and 68851/68881 co-processors is partial or non-existant.
+" up and 68851/68881 co-processors is partial or non-existent.
" Feel free to contribute...
"
diff --git a/runtime/syntax/automake.vim b/runtime/syntax/automake.vim
index 8a7db7c27b..2f1ad982c6 100644
--- a/runtime/syntax/automake.vim
+++ b/runtime/syntax/automake.vim
@@ -2,8 +2,8 @@
" Language: automake Makefile.am
" Maintainer: Debian Vim Maintainers
" Former Maintainer: John Williams <jrw@pobox.com>
-" Last Change: 2018 Dec 27
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/automake.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/automake.vim
"
" XXX This file is in need of a new maintainer, Debian VIM Maintainers maintain
" it only because patches have been submitted for it by Debian users and the
diff --git a/runtime/syntax/bash.vim b/runtime/syntax/bash.vim
index 75ab99938e..1e565c3022 100644
--- a/runtime/syntax/bash.vim
+++ b/runtime/syntax/bash.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: bash
-" Maintainer: Bram
-" Last Change: 2019 Sep 27
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
" quit when a syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/bindzone.vim b/runtime/syntax/bindzone.vim
index bb790bb75c..dce9974903 100644
--- a/runtime/syntax/bindzone.vim
+++ b/runtime/syntax/bindzone.vim
@@ -33,7 +33,7 @@ syn match zoneDomain contained /[^[:space:]!"#$%&'()*+,\/:;<=>?@[\]\
syn match zoneSpecial contained /^[@*.]\s/
syn match zoneTTL contained /\s\@<=\d[0-9WwDdHhMmSs]*\(\s\|$\)\@=/ nextgroup=zoneClass,zoneRRType skipwhite
syn keyword zoneClass contained IN CHAOS CH HS HESIOD nextgroup=zoneRRType,zoneTTL skipwhite
-syn keyword zoneRRType contained A AAAA CERT CNAME DNAME DNSKEY DS HINFO LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM PTR RP RRSIG SSHFP SOA SPF SRV TLSA TXT nextgroup=zoneRData skipwhite
+syn keyword zoneRRType contained A AAAA APL CAA CERT CNAME DNAME DNSKEY DS HINFO LOC MX NAPTR NS NSEC NSEC3 NSEC3PARAM OPENPGPKEY PTR RP RRSIG SMIMEA SOA SPF SRV SSHFP TLSA TXT nextgroup=zoneRData skipwhite
syn match zoneRData contained /[^;]*/ contains=zoneDomain,zoneIPAddr,zoneIP6Addr,zoneText,zoneNumber,zoneParen,zoneUnknown
syn match zoneIPAddr contained /\<[0-9]\{1,3}\(\.[0-9]\{1,3}\)\{,3}\>/
@@ -60,7 +60,7 @@ syn match zoneIP6Addr contained /\<\(\x\{1,4}:\)\{1,7}:\(\s\|;\|$\)\@=
syn match zoneText contained /"\([^"\\]\|\\.\)*"\(\s\|;\|$\)\@=/
syn match zoneNumber contained /\<[0-9]\+\(\s\|;\|$\)\@=/
-syn match zoneSerial contained /\<[0-9]\{9,10}\(\s\|;\|$\)\@=/
+syn match zoneSerial contained /\<[0-9]\{1,10}\(\s\|;\|$\)\@=/
syn match zoneErrParen /)/
syn region zoneParen contained start="(" end=")" contains=zoneSerial,zoneTTL,zoneNumber,zoneComment
diff --git a/runtime/syntax/c.vim b/runtime/syntax/c.vim
index 50878a78ea..5ed8fdc847 100644
--- a/runtime/syntax/c.vim
+++ b/runtime/syntax/c.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: C
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Oct 05
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
@@ -348,7 +349,10 @@ if !exists("c_no_ansi") || exists("c_ansi_constants") || exists("c_gnu")
syn keyword cConstant SIGABRT SIGALRM SIGCHLD SIGCONT SIGFPE SIGHUP SIGILL SIGINT SIGKILL SIGPIPE SIGQUIT SIGSEGV
syn keyword cConstant SIGSTOP SIGTERM SIGTRAP SIGTSTP SIGTTIN SIGTTOU SIGUSR1 SIGUSR2
syn keyword cConstant _IOFBF _IOLBF _IONBF BUFSIZ EOF WEOF FOPEN_MAX FILENAME_MAX L_tmpnam
- syn keyword cConstant SEEK_CUR SEEK_END SEEK_SET TMP_MAX stderr stdin stdout EXIT_FAILURE EXIT_SUCCESS RAND_MAX
+ syn keyword cConstant SEEK_CUR SEEK_END SEEK_SET TMP_MAX EXIT_FAILURE EXIT_SUCCESS RAND_MAX
+ syn keyword cConstant stdin stdout stderr
+ " POSIX 2001, in unistd.h
+ syn keyword cConstant STDIN_FILENO STDOUT_FILENO STDERR_FILENO
" used in assert.h
syn keyword cConstant NDEBUG
" POSIX 2001
diff --git a/runtime/syntax/chaiscript.vim b/runtime/syntax/chaiscript.vim
index 5a64bdb556..9925ba5138 100644
--- a/runtime/syntax/chaiscript.vim
+++ b/runtime/syntax/chaiscript.vim
@@ -61,7 +61,7 @@ syn region chaiscriptFunc matchgroup=chaiscriptFunc start="`" end="`"
" Intentionally leaving out all of the normal, well known operators
syn match chaiscriptOperator "\.\."
-" Guard seperator as an operator
+" Guard separator as an operator
syn match chaiscriptOperator ":"
" Comments
diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim
index 4b0ce75a54..2fd0aed601 100644
--- a/runtime/syntax/checkhealth.vim
+++ b/runtime/syntax/checkhealth.vim
@@ -11,15 +11,10 @@ unlet! b:current_syntax
syn case match
-syn keyword healthError ERROR[:]
-syn keyword healthWarning WARNING[:]
-syn keyword healthSuccess OK[:]
+syn keyword DiagnosticError ERROR[:]
+syn keyword DiagnosticWarn WARNING[:]
+syn keyword DiagnosticOk OK[:]
syn match helpSectionDelim "^======*\n.*$"
syn match healthHeadingChar "=" conceal cchar=─ contained containedin=helpSectionDelim
-hi def link healthError Error
-hi def link healthWarning WarningMsg
-hi def healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232
-hi def link healthHelp Identifier
-
let b:current_syntax = "checkhealth"
diff --git a/runtime/syntax/cmake.vim b/runtime/syntax/cmake.vim
index f7616e4c6d..7340ac238e 100644
--- a/runtime/syntax/cmake.vim
+++ b/runtime/syntax/cmake.vim
@@ -335,7 +335,7 @@ syn keyword cmakeGeneratorExpressions contained
syn case ignore
syn keyword cmakeCommand
- \ add_compile_options add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_host_system_information cmake_minimum_required cmake_parse_arguments cmake_policy configure_file continue create_test_sourcelist ctest_build ctest_configure ctest_coverage ctest_empty_binary_directory ctest_memcheck ctest_read_custom_files ctest_run_script ctest_sleep ctest_start ctest_submit ctest_test ctest_update ctest_upload define_property enable_language enable_testing endfunction endmacro execute_process export file find_file find_library find_package find_path find_program fltk_wrap_ui function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property include include_directories include_external_msproject include_guard include_regular_expression install link_directories list load_cache load_command macro mark_as_advanced math message option project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_compile_definitions target_compile_features target_compile_options target_include_directories target_link_libraries target_sources try_compile try_run unset variable_watch
+ \ add_compile_options add_compile_definitions add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_host_system_information cmake_minimum_required cmake_parse_arguments cmake_policy configure_file continue create_test_sourcelist ctest_build ctest_configure ctest_coverage ctest_empty_binary_directory ctest_memcheck ctest_read_custom_files ctest_run_script ctest_sleep ctest_start ctest_submit ctest_test ctest_update ctest_upload define_property enable_language enable_testing endfunction endmacro execute_process export file find_file find_library find_package find_path find_program fltk_wrap_ui function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property include include_directories include_external_msproject include_guard include_regular_expression install link_directories list load_cache load_command macro mark_as_advanced math message option project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_compile_definitions target_compile_features target_compile_options target_include_directories target_link_libraries target_sources try_compile try_run unset variable_watch
\ nextgroup=cmakeArguments
syn keyword cmakeCommandConditional
diff --git a/runtime/syntax/colortest.vim b/runtime/syntax/colortest.vim
index 58de7aaf13..1dd860c1d3 100644
--- a/runtime/syntax/colortest.vim
+++ b/runtime/syntax/colortest.vim
@@ -1,7 +1,8 @@
" Vim script for testing colors
-" Maintainer: Bram Moolenaar <Bram@vim.org>
+" Maintainer: The Vim Project <https://github.com/vim/vim>
" Contributors: Rafael Garcia-Suarez, Charles Campbell
-" Last Change: 2008 Jun 04
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" edit this file, then do ":source %", and check if the colors match
diff --git a/runtime/syntax/conf.vim b/runtime/syntax/conf.vim
index 6a78ef8c6e..15a1fffab3 100644
--- a/runtime/syntax/conf.vim
+++ b/runtime/syntax/conf.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: generic configure file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2021 May 01
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/crontab.vim b/runtime/syntax/crontab.vim
index 5e38ffaafe..12daa9b7b8 100644
--- a/runtime/syntax/crontab.vim
+++ b/runtime/syntax/crontab.vim
@@ -5,7 +5,7 @@
" License: This file can be redistribued and/or modified under the same terms
" as Vim itself.
" Filenames: /tmp/crontab.* used by "crontab -e"
-" Last Change: 2015-01-20
+" Last Change: 2022-09-22
"
" crontab line format:
" Minutes Hours Days Months Days_of_Week Commands # comments
@@ -15,20 +15,20 @@ if exists("b:current_syntax")
finish
endif
-syntax match crontabNick "^\s*@\(reboot\|yearly\|annually\|monthly\|weekly\|daily\|midnight\|hourly\)\>" nextgroup=crontabCmd skipwhite
+syntax match crontabNick "^\s*@\(reboot\|yearly\|annually\|monthly\|weekly\|daily\|midnight\|hourly\|every_minute\|every_second\)\>" nextgroup=crontabCmd skipwhite
syntax match crontabVar "^\s*\k\w*\s*="me=e-1
syntax case ignore
-syntax match crontabMin "^\s*[-0-9/,.*]\+" nextgroup=crontabHr skipwhite
-syntax match crontabHr "\s[-0-9/,.*]\+" nextgroup=crontabDay skipwhite contained
-syntax match crontabDay "\s[-0-9/,.*]\+" nextgroup=crontabMnth skipwhite contained
+syntax match crontabMin "^\s*[-~0-9/,.*]\+" nextgroup=crontabHr skipwhite
+syntax match crontabHr "\s[-~0-9/,.*]\+" nextgroup=crontabDay skipwhite contained
+syntax match crontabDay "\s[-~0-9/,.*]\+" nextgroup=crontabMnth skipwhite contained
-syntax match crontabMnth "\s[-a-z0-9/,.*]\+" nextgroup=crontabDow skipwhite contained
+syntax match crontabMnth "\s[-~a-z0-9/,.*]\+" nextgroup=crontabDow skipwhite contained
syntax keyword crontabMnth12 contained jan feb mar apr may jun jul aug sep oct nov dec
-syntax match crontabDow "\s[-a-z0-9/,.*]\+" nextgroup=crontabCmd skipwhite contained
+syntax match crontabDow "\s[-~a-z0-9/,.*]\+" nextgroup=crontabCmd skipwhite contained
syntax keyword crontabDow7 contained sun mon tue wed thu fri sat
syntax region crontabCmd start="\S" end="$" skipwhite contained keepend contains=crontabPercent
diff --git a/runtime/syntax/css.vim b/runtime/syntax/css.vim
index 564dc151bc..f8104ea2c5 100644
--- a/runtime/syntax/css.vim
+++ b/runtime/syntax/css.vim
@@ -452,12 +452,12 @@ syn match cssAttrComma ","
" Pseudo class
" https://www.w3.org/TR/selectors-4/
syn match cssPseudoClass ":[A-Za-z0-9_-]*" contains=cssNoise,cssPseudoClassId,cssUnicodeEscape,cssVendor,cssPseudoClassFn
-syn keyword cssPseudoClassId contained link visited active hover before after left right
-syn keyword cssPseudoClassId contained root empty target enabled disabled checked invalid
+syn keyword cssPseudoClassId contained link visited active hover before after left right any-link
+syn keyword cssPseudoClassId contained root empty target enabled disabled checked invalid default defined autofill fullscreen host indeterminate in-range modal optional out-of-range picture-in-picture placeholder-shown paused playing read-only read-write required scope
syn match cssPseudoClassId contained "\<first-\(line\|letter\)\>"
syn match cssPseudoClassId contained "\<\(first\|last\|only\)-\(of-type\|child\)\>"
syn match cssPseudoClassId contained "\<focus\(-within\|-visible\)\=\>"
-syn region cssPseudoClassFn contained matchgroup=cssFunctionName start="\<\(not\|is\|lang\|\(nth\|nth-last\)-\(of-type\|child\)\)(" end=")" contains=cssStringQ,cssStringQQ,cssTagName,cssAttributeSelector,cssClassName,cssIdentifier
+syn region cssPseudoClassFn contained matchgroup=cssFunctionName start="\<\(where\|has\|host\|not\|is\|lang\|\(nth\|nth-last\)-\(of-type\|child\)\)(" end=")" contains=cssStringQ,cssStringQQ,cssTagName,cssAttributeSelector,cssClassName,cssIdentifier
" ------------------------------------
" Vendor specific properties
syn match cssPseudoClassId contained "\<selection\>"
diff --git a/runtime/syntax/ctrlh.vim b/runtime/syntax/ctrlh.vim
index b4bf3477e6..b34f335785 100644
--- a/runtime/syntax/ctrlh.vim
+++ b/runtime/syntax/ctrlh.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: CTRL-H (e.g., ASCII manpages)
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2005 Jun 20
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Existing syntax is kept, this file can be used as an addition
diff --git a/runtime/syntax/deb822sources.vim b/runtime/syntax/deb822sources.vim
new file mode 100644
index 0000000000..81113610e8
--- /dev/null
+++ b/runtime/syntax/deb822sources.vim
@@ -0,0 +1,63 @@
+" Vim syntax file
+" Language: Debian deb822-format source list file
+" Maintainer: Debian Vim Maintainers
+" Last Change: 2023 May 25
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/deb822sources.vim
+
+" Standard syntax initialization
+if exists('b:current_syntax')
+ finish
+endif
+
+" case insensitive
+syn case ignore
+
+" Comments are matched from the first character of a line to the end-of-line
+syn region deb822sourcesComment start="^#" end="$"
+
+" A bunch of useful keywords
+syn match deb822sourcesType /\(deb-src\|deb\)/
+syn match deb822sourcesFreeComponent /\(main\|universe\)/
+syn match deb822sourcesNonFreeComponent /\(contrib\|non-free-firmware\|non-free\|restricted\|multiverse\)/
+
+" Include Debian versioning information
+runtime! syntax/shared/debversions.vim
+
+exe 'syn match deb822sourcesSupportedSuites contained + *\([[:alnum:]_./]*\)\<\('. join(g:debSharedSupportedVersions, '\|'). '\)\>\([-[:alnum:]_./]*\)+'
+exe 'syn match deb822sourcesUnsupportedSuites contained + *\([[:alnum:]_./]*\)\<\('. join(g:debSharedUnsupportedVersions, '\|'). '\)\>\([-[:alnum:]_./]*\)+'
+
+unlet g:debSharedSupportedVersions
+unlet g:debSharedUnsupportedVersions
+
+syn region deb822sourcesSuites start="\(^Suites: *\)\@<=" end="$" contains=deb822sourcesSupportedSuites,deb822sourcesUnsupportedSuites oneline
+
+syn keyword deb822sourcesForce contained force
+syn keyword deb822sourcesYesNo contained yes no
+
+" Match uri's
+syn match deb822sourcesUri '\(https\?://\|ftp://\|[rs]sh://\|debtorrent://\|\(cdrom\|copy\|file\):\)[^' <>"]\+'
+
+syn match deb822sourcesEntryField "^\%(Types\|URIs\|Suites\|Components\): *"
+syn match deb822sourcesOptionField "^\%(Signed-By\|Check-Valid-Until\|Valid-Until-Min\|Valid-Until-Max\|Date-Max-Future\|InRelease-Path\): *"
+syn match deb822sourcesMultiValueOptionField "^\%(Architectures\|Languages\|Targets\)\%(-Add\|-Remove\)\?: *"
+
+syn region deb822sourcesStrictField matchgroup=deb822sourcesBooleanOptionField start="^\%(PDiffs\|Allow-Insecure\|Allow-Weak\|Allow-Downgrade-To-Insecure\|Trusted\|Check-Date\): *" end="$" contains=deb822sourcesYesNo oneline
+syn region deb822sourcesStrictField matchgroup=deb822sourcesForceBooleanOptionField start="^\%(By-Hash\): *" end="$" contains=deb822sourcesForce,deb822sourcesYesNo oneline
+
+hi def link deb822sourcesComment Comment
+hi def link deb822sourcesEntryField Keyword
+hi def link deb822sourcesOptionField Special
+hi def link deb822sourcesMultiValueOptionField Special
+hi def link deb822sourcesBooleanOptionField Special
+hi def link deb822sourcesForceBooleanOptionField Special
+hi def link deb822sourcesStrictField Error
+hi def link deb822sourcesType Identifier
+hi def link deb822sourcesFreeComponent Identifier
+hi def link deb822sourcesNonFreeComponent Identifier
+hi def link deb822sourcesForce Identifier
+hi def link deb822sourcesYesNo Identifier
+hi def link deb822sourcesUri Constant
+hi def link deb822sourcesSupportedSuites Type
+hi def link deb822sourcesUnsupportedSuites WarningMsg
+
+let b:current_syntax = 'deb822sources'
diff --git a/runtime/syntax/debchangelog.vim b/runtime/syntax/debchangelog.vim
index 691c3778c1..da35a6a10b 100644
--- a/runtime/syntax/debchangelog.vim
+++ b/runtime/syntax/debchangelog.vim
@@ -3,8 +3,8 @@
" Maintainer: Debian Vim Maintainers
" Former Maintainers: Gerfried Fuchs <alfie@ist.org>
" Wichert Akkerman <wakkerma@debian.org>
-" Last Change: 2022 Oct 29
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debchangelog.vim
+" Last Change: 2023 Oct 11
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/debchangelog.vim
" Standard syntax initialization
if exists('b:current_syntax')
@@ -17,34 +17,19 @@ syn case ignore
let s:urgency='urgency=\(low\|medium\|high\|emergency\|critical\)\( [^[:space:],][^,]*\)\='
let s:binNMU='binary-only=yes'
-let s:cpo = &cpo
-set cpo-=C
-let s:supported = [
- \ 'oldstable', 'stable', 'testing', 'unstable', 'experimental', 'sid', 'rc-buggy',
- \ 'buster', 'bullseye', 'bookworm', 'trixie', 'forky',
- \
- \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', 'lunar',
- \ 'devel'
- \ ]
-let s:unsupported = [
- \ 'frozen', 'buzz', 'rex', 'bo', 'hamm', 'slink', 'potato',
- \ 'woody', 'sarge', 'etch', 'lenny', 'squeeze', 'wheezy',
- \ 'jessie', 'stretch',
- \
- \ 'warty', 'hoary', 'breezy', 'dapper', 'edgy', 'feisty',
- \ 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid',
- \ 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy',
- \ 'utopic', 'vivid', 'wily', 'yakkety', 'zesty', 'artful', 'cosmic',
- \ 'disco', 'eoan', 'hirsute', 'impish', 'groovy'
- \ ]
-let &cpo=s:cpo
+" Include Debian versioning information
+runtime! syntax/shared/debversions.vim
+
+exe 'syn match debchangelogTarget contained "\%( \%('.join(g:debSharedSupportedVersions, '\|').'\)\>[-[:alnum:]]*\)\+"'
+exe 'syn match debchangelogUnsupportedTarget contained "\%( \%('.join(g:debSharedUnsupportedVersions, '\|').'\)\>[-[:alnum:]]*\)\+"'
+
+unlet g:debSharedSupportedVersions
+unlet g:debSharedUnsupportedVersions
" Define some common expressions we can use later on
syn match debchangelogName contained "^[[:alnum:]][[:alnum:].+-]\+ "
exe 'syn match debchangelogFirstKV contained "; \('.s:urgency.'\|'.s:binNMU.'\)"'
exe 'syn match debchangelogOtherKV contained ", \('.s:urgency.'\|'.s:binNMU.'\)"'
-exe 'syn match debchangelogTarget contained "\%( \%('.join(s:supported, '\|').'\)\>[-[:alnum:]]*\)\+"'
-exe 'syn match debchangelogUnsupportedTarget contained "\%( \%('.join(s:unsupported, '\|').'\)\>[-[:alnum:]]*\)\+"'
syn match debchangelogUnreleased contained / UNRELEASED/
syn match debchangelogVersion contained "(.\{-})"
syn match debchangelogCloses contained "closes:\_s*\(bug\)\=#\=\_s\=\d\+\(,\_s*\(bug\)\=#\=\_s\=\d\+\)*"
@@ -58,19 +43,19 @@ syn region debchangelogFooter start="^ [^ ]" end="$" contains=debchangelogEmail
syn region debchangelogEntry start="^ " end="$" contains=debchangelogCloses,debchangelogLP oneline
" Associate our matches and regions with pretty colours
-hi def link debchangelogHeader Error
-hi def link debchangelogFooter Identifier
-hi def link debchangelogEntry Normal
-hi def link debchangelogCloses Statement
-hi def link debchangelogLP Statement
-hi def link debchangelogFirstKV Identifier
-hi def link debchangelogOtherKV Identifier
-hi def link debchangelogName Comment
-hi def link debchangelogVersion Identifier
-hi def link debchangelogTarget Identifier
-hi def link debchangelogUnsupportedTarget Identifier
+hi def link debchangelogHeader Error
+hi def link debchangelogFooter Identifier
+hi def link debchangelogEntry Normal
+hi def link debchangelogCloses Statement
+hi def link debchangelogLP Statement
+hi def link debchangelogFirstKV Identifier
+hi def link debchangelogOtherKV Identifier
+hi def link debchangelogName Comment
+hi def link debchangelogVersion Identifier
hi def link debchangelogUnreleased WarningMsg
-hi def link debchangelogEmail Special
+hi def link debchangelogEmail Special
+hi def link debchangelogTarget Identifier
+hi def link debchangelogUnsupportedTarget Identifier
let b:current_syntax = 'debchangelog'
diff --git a/runtime/syntax/debcontrol.vim b/runtime/syntax/debcontrol.vim
index 4c7fb5dd63..af78ebc3ae 100644
--- a/runtime/syntax/debcontrol.vim
+++ b/runtime/syntax/debcontrol.vim
@@ -3,8 +3,8 @@
" Maintainer: Debian Vim Maintainers
" Former Maintainers: Gerfried Fuchs <alfie@ist.org>
" Wichert Akkerman <wakkerma@debian.org>
-" Last Change: 2022 May 11
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debcontrol.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/debcontrol.vim
" Standard syntax initialization
if exists('b:current_syntax')
@@ -22,7 +22,7 @@ syn iskeyword @,48-57,-
" Everything that is not explicitly matched by the rules below
syn match debcontrolElse "^.*$"
-" Common seperators
+" Common separators
syn match debControlComma ",[ \t]*"
syn match debControlSpace "[ \t]"
diff --git a/runtime/syntax/debcopyright.vim b/runtime/syntax/debcopyright.vim
index c87b876eea..6f76b5c868 100644
--- a/runtime/syntax/debcopyright.vim
+++ b/runtime/syntax/debcopyright.vim
@@ -1,8 +1,8 @@
" Vim syntax file
" Language: Debian copyright file
" Maintainer: Debian Vim Maintainers
-" Last Change: 2019 Sep 07
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debcopyright.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/debcopyright.vim
" Standard syntax initialization
if exists('b:current_syntax')
diff --git a/runtime/syntax/debsources.vim b/runtime/syntax/debsources.vim
index 9b75797b54..9846cfdef0 100644
--- a/runtime/syntax/debsources.vim
+++ b/runtime/syntax/debsources.vim
@@ -2,8 +2,8 @@
" Language: Debian sources.list
" Maintainer: Debian Vim Maintainers
" Former Maintainer: Matthijs Mohlmann <matthijs@cacholong.nl>
-" Last Change: 2022 Oct 29
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debsources.vim
+" Last Change: 2023 Oct 11
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/debsources.vim
" Standard syntax initialization
if exists('b:current_syntax')
@@ -14,44 +14,34 @@ endif
syn case match
" A bunch of useful keywords
-syn match debsourcesKeyword /\(deb-src\|deb\|main\|contrib\|non-free\|restricted\|universe\|multiverse\)/
+syn match debsourcesType /\(deb-src\|deb\)/
+syn match debsourcesFreeComponent /\(main\|universe\)/
+syn match debsourcesNonFreeComponent /\(contrib\|non-free-firmware\|non-free\|restricted\|multiverse\)/
" Match comments
syn match debsourcesComment /#.*/ contains=@Spell
-let s:cpo = &cpo
-set cpo-=C
-let s:supported = [
- \ 'oldstable', 'stable', 'testing', 'unstable', 'experimental', 'sid', 'rc-buggy',
- \ 'buster', 'bullseye', 'bookworm', 'trixie', 'forky',
- \
- \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'kinetic', 'lunar',
- \ 'devel'
- \ ]
-let s:unsupported = [
- \ 'buzz', 'rex', 'bo', 'hamm', 'slink', 'potato',
- \ 'woody', 'sarge', 'etch', 'lenny', 'squeeze', 'wheezy',
- \ 'jessie', 'stretch',
- \
- \ 'warty', 'hoary', 'breezy', 'dapper', 'edgy', 'feisty',
- \ 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid',
- \ 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy',
- \ 'utopic', 'vivid', 'wily', 'yakkety', 'zesty', 'artful', 'cosmic',
- \ 'disco', 'eoan', 'hirsute', 'impish', 'groovy'
- \ ]
-let &cpo=s:cpo
+" Include Debian versioning information
+runtime! syntax/shared/debversions.vim
+
+exe 'syn match debsourcesDistrKeyword +\([[:alnum:]_./]*\)\<\('. join(g:debSharedSupportedVersions, '\|'). '\)\>\([-[:alnum:]_./]*\)+'
+exe 'syn match debsourcesUnsupportedDistrKeyword +\([[:alnum:]_./]*\)\<\('. join(g:debSharedUnsupportedVersions, '\|') .'\)\>\([-[:alnum:]_./]*\)+'
+
+unlet g:debSharedSupportedVersions
+unlet g:debSharedUnsupportedVersions
" Match uri's
syn match debsourcesUri '\(https\?://\|ftp://\|[rs]sh://\|debtorrent://\|\(cdrom\|copy\|file\):\)[^' <>"]\+'
-exe 'syn match debsourcesDistrKeyword +\([[:alnum:]_./]*\)\<\('. join(s:supported, '\|'). '\)\>\([-[:alnum:]_./]*\)+'
-exe 'syn match debsourcesUnsupportedDistrKeyword +\([[:alnum:]_./]*\)\<\('. join(s:unsupported, '\|') .'\)\>\([-[:alnum:]_./]*\)+'
+syn region debsourcesLine start="^" end="$" contains=debsourcesType,debsourcesFreeComponent,debsourcesNonFreeComponent,debsourcesComment,debsourcesUri,debsourcesDistrKeyword,debsourcesUnsupportedDistrKeyword oneline
+
" Associate our matches and regions with pretty colours
-hi def link debsourcesLine Error
-hi def link debsourcesKeyword Statement
-hi def link debsourcesDistrKeyword Type
-hi def link debsourcesUnsupportedDistrKeyword WarningMsg
+hi def link debsourcesType Statement
+hi def link debsourcesFreeComponent Statement
+hi def link debsourcesNonFreeComponent Statement
hi def link debsourcesComment Comment
hi def link debsourcesUri Constant
+hi def link debsourcesDistrKeyword Type
+hi def link debsourcesUnsupportedDistrKeyword WarningMsg
let b:current_syntax = 'debsources'
diff --git a/runtime/syntax/dep3patch.vim b/runtime/syntax/dep3patch.vim
index cb0eda8931..c00bddfde2 100644
--- a/runtime/syntax/dep3patch.vim
+++ b/runtime/syntax/dep3patch.vim
@@ -1,8 +1,8 @@
" Vim syntax file
" Language: Debian DEP3 Patch headers
" Maintainer: Gabriel Filion <gabster@lelutin.ca>
-" Last Change: 2022 Apr 06
-" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/dep3patch.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/dep3patch.vim
"
" Specification of the DEP3 patch header format is available at:
" https://dep-team.pages.debian.net/deps/dep3/
diff --git a/runtime/syntax/diff.vim b/runtime/syntax/diff.vim
index 408556ac13..4cadf5dae8 100644
--- a/runtime/syntax/diff.vim
+++ b/runtime/syntax/diff.vim
@@ -1,8 +1,9 @@
" Vim syntax file
" Language: Diff (context or unified)
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Translations by Jakson Alves de Aquino.
-" Last Change: 2020 Dec 30
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Translations by Jakson Alves de Aquino.
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/dosbatch.vim b/runtime/syntax/dosbatch.vim
index f003a65909..a75771bd2d 100644
--- a/runtime/syntax/dosbatch.vim
+++ b/runtime/syntax/dosbatch.vim
@@ -1,12 +1,12 @@
" Vim syntax file
-" Language: MS-DOS batch file (with NT command extensions)
-" Maintainer: Mike Williams <mrw@eandem.co.uk>
+" Language: MS-DOS/Windows batch file (with NT command extensions)
+" Maintainer: Mike Williams <mrmrdubya@gmail.com>
" Filenames: *.bat
-" Last Change: 6th September 2009
-" Web Page: http://www.eandem.co.uk/mrw/vim
+" Last Change: 12th February 2023
"
" Options Flags:
" dosbatch_cmdextversion - 1 = Windows NT, 2 = Windows 2000 [default]
+" dosbatch_colons_comment - any value to treat :: as comment line
"
" quit when a syntax file was already loaded
@@ -92,7 +92,11 @@ syn match dosbatchComment "^rem\($\|\s.*$\)"lc=3 contains=dosbatchTodo,dosbatchS
syn match dosbatchComment "^@rem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
syn match dosbatchComment "\srem\($\|\s.*$\)"lc=4 contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
syn match dosbatchComment "\s@rem\($\|\s.*$\)"lc=5 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
-syn match dosbatchComment "\s*:\s*:.*$" contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+if exists("dosbatch_colons_comment")
+ syn match dosbatchComment "\s*:\s*:.*$" contains=dosbatchTodo,dosbatchSpecialChar,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
+else
+ syn match dosbatchError "\s*:\s*:.*$"
+endif
" Comments in ()'s - still to handle spaces before rem
syn match dosbatchComment "(rem\([^)]\|\^\@<=)\)*"lc=4 contains=dosbatchTodo,@dosbatchNumber,dosbatchVariable,dosbatchArgument,@Spell
@@ -110,34 +114,35 @@ syn keyword dosbatchImplicit vol xcopy
" Define the default highlighting.
" Only when an item doesn't have highlighting yet
-hi def link dosbatchTodo Todo
+hi def link dosbatchTodo Todo
+hi def link dosbatchError Error
hi def link dosbatchStatement Statement
hi def link dosbatchCommands dosbatchStatement
-hi def link dosbatchLabel Label
+hi def link dosbatchLabel Label
hi def link dosbatchConditional Conditional
-hi def link dosbatchRepeat Repeat
+hi def link dosbatchRepeat Repeat
-hi def link dosbatchOperator Operator
-hi def link dosbatchEchoOperator dosbatchOperator
-hi def link dosbatchIfOperator dosbatchOperator
+hi def link dosbatchOperator Operator
+hi def link dosbatchEchoOperator dosbatchOperator
+hi def link dosbatchIfOperator dosbatchOperator
hi def link dosbatchArgument Identifier
-hi def link dosbatchIdentifier Identifier
+hi def link dosbatchIdentifier Identifier
hi def link dosbatchVariable dosbatchIdentifier
hi def link dosbatchSpecialChar SpecialChar
-hi def link dosbatchString String
-hi def link dosbatchNumber Number
+hi def link dosbatchString String
+hi def link dosbatchNumber Number
hi def link dosbatchInteger dosbatchNumber
hi def link dosbatchHex dosbatchNumber
-hi def link dosbatchBinary dosbatchNumber
-hi def link dosbatchOctal dosbatchNumber
+hi def link dosbatchBinary dosbatchNumber
+hi def link dosbatchOctal dosbatchNumber
hi def link dosbatchComment Comment
hi def link dosbatchImplicit Function
-hi def link dosbatchSwitch Special
+hi def link dosbatchSwitch Special
hi def link dosbatchCmd PreProc
diff --git a/runtime/syntax/dosini.vim b/runtime/syntax/dosini.vim
index cf42819bcd..66e17ec9af 100644
--- a/runtime/syntax/dosini.vim
+++ b/runtime/syntax/dosini.vim
@@ -1,12 +1,12 @@
" Vim syntax file
" Language: Configuration File (ini file) for MSDOS/MS Windows
-" Version: 2.2
+" Version: 2.3
" Original Author: Sean M. McKee <mckee@misslink.net>
" Previous Maintainer: Nima Talebi <nima@it.net.au>
" Current Maintainer: Hong Xu <hong@topbug.net>
" Homepage: http://www.vim.org/scripts/script.php?script_id=3747
" Repository: https://github.com/xuhdev/syntax-dosini.vim
-" Last Change: 2018 Sep 11
+" Last Change: 2023 Aug 20
" quit when a syntax file was already loaded
@@ -14,6 +14,10 @@ if exists("b:current_syntax")
finish
endif
+" using of line-continuation requires cpo&vim
+let s:cpo_save = &cpo
+set cpo&vim
+
" shut case off
syn case ignore
@@ -24,6 +28,8 @@ syn match dosiniNumber "=\zs\s*\d*\.\d\+\s*$"
syn match dosiniNumber "=\zs\s*\d\+e[+-]\=\d\+\s*$"
syn region dosiniHeader start="^\s*\[" end="\]"
syn match dosiniComment "^[#;].*$"
+syn region dosiniSection start="\s*\[.*\]" end="\ze\s*\[.*\]" fold
+ \ contains=dosiniLabel,dosiniValue,dosiniNumber,dosiniHeader,dosiniComment
" Define the default highlighting.
" Only when an item doesn't have highlighting yet
@@ -37,4 +43,7 @@ hi def link dosiniValue String
let b:current_syntax = "dosini"
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
" vim: sts=2 sw=2 et
diff --git a/runtime/syntax/dts.vim b/runtime/syntax/dts.vim
index be51ab5b10..bb7eff7be1 100644
--- a/runtime/syntax/dts.vim
+++ b/runtime/syntax/dts.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: dts/dtsi (device tree files)
" Maintainer: Daniel Mack <vim@zonque.org>
-" Last Change: 2022 Jun 14
+" Last Change: 2023 Apr 28
if exists("b:current_syntax")
finish
@@ -10,9 +10,10 @@ let b:current_syntax = 'dts'
syntax region dtsComment start="/\*" end="\*/"
syntax match dtsReference "&[[:alpha:][:digit:]_]\+"
+syntax match dtsReference "&{[[:alpha:][:digit:]@_/-]\+}"
syntax region dtsBinaryProperty start="\[" end="\]"
syntax match dtsStringProperty "\".*\""
-syntax match dtsKeyword "/.\{-1,\}/"
+syntax match dtsKeyword "/[[:alpha:][:digit:]-]\+/\([[:space:]]\|;\)"he=e-1
syntax match dtsLabel "^[[:space:]]*[[:alpha:][:digit:]_]\+:"
syntax match dtsNode /[[:alpha:][:digit:]-_]\+\(@[0-9a-fA-F]\+\|\)[[:space:]]*{/he=e-1
syntax region dtsCellProperty start="<" end=">" contains=dtsReference,dtsBinaryProperty,dtsStringProperty,dtsComment
diff --git a/runtime/syntax/editorconfig.vim b/runtime/syntax/editorconfig.vim
index 4392d2b2d5..74a0e56131 100644
--- a/runtime/syntax/editorconfig.vim
+++ b/runtime/syntax/editorconfig.vim
@@ -1,15 +1,18 @@
+" Nvim syntax file
+" Language: EditorConfig
+" Last Change: 2023-07-20
+"
+" This file is intentionally _not_ copied from Vim.
+
runtime! syntax/dosini.vim
unlet! b:current_syntax
-syntax match editorconfigUnknownProperty "^\s*\zs\w\+\ze\s*="
+syntax match editorconfigUnknownProperty "^\s*\zs[a-zA-Z0-9_-]\+\ze\s*="
syntax keyword editorconfigProperty root
lua<<
-local props = {}
-for k in pairs(require('editorconfig').properties) do
- props[#props + 1] = k
-end
-vim.cmd(string.format('syntax keyword editorconfigProperty %s', table.concat(props, ' ')))
+local props = vim.tbl_keys(require('editorconfig').properties)
+vim.cmd.syntax { 'keyword', 'editorconfigProperty', unpack(props) }
.
hi def link editorconfigProperty dosiniLabel
diff --git a/runtime/syntax/euphoria4.vim b/runtime/syntax/euphoria4.vim
index 5e668a7d67..baa0e8e7b9 100644
--- a/runtime/syntax/euphoria4.vim
+++ b/runtime/syntax/euphoria4.vim
@@ -27,7 +27,7 @@ syn keyword euphoria4Debug includes inline warning define
" Keywords for conditional compilation - from $EUDIR/include/euphoria/keywords.e:
syn keyword euphoria4PreProc elsedef elsifdef ifdef
-" Keywords (Statments) - from $EUDIR/include/euphoria/keywords.e:
+" Keywords (Statements) - from $EUDIR/include/euphoria/keywords.e:
syn keyword euphoria4Keyword and as break by case constant continue do else
syn keyword euphoria4Keyword elsif end entry enum exit export
syn keyword euphoria4Keyword fallthru for function global goto if include
diff --git a/runtime/syntax/fish.vim b/runtime/syntax/fish.vim
new file mode 100644
index 0000000000..266878bbdc
--- /dev/null
+++ b/runtime/syntax/fish.vim
@@ -0,0 +1,225 @@
+" Vim syntax file
+" Language: fish
+" Maintainer: Nicholas Boyle (github.com/nickeb96)
+" Repository: https://github.com/nickeb96/fish.vim
+" Last Change: February 1, 2023
+
+if exists("b:current_syntax")
+ finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+
+" Statements
+syn cluster fishStatement contains=fishKeywordAndOr,fishNot,fishSelectStatement,fishKeyword,fishKeywordIf,fishCommand,fishVariable
+
+syn keyword fishKeywordAndOr and or nextgroup=fishNot,fishSelectStatement,fishKeyword,fishKeywordIf,fishCommand
+hi def link fishKeywordAndOr fishOperator
+
+syn keyword fishNot not skipwhite nextgroup=fishSelectStatement,fishKeyword,fishKeywordIf,fishCommand
+syn match fishNot /!/ skipwhite nextgroup=fishSelectStatement,fishKeyword,fishKeywordIf,fishCommand
+hi def link fishNot fishOperator
+
+syn keyword fishSelectStatement command builtin skipwhite nextgroup=fishKeyword,fishKeywordIf,fishCommand,fishOption
+hi def link fishSelectStatement fishKeyword
+
+syn keyword fishKeyword end skipwhite nextgroup=@fishTerminator
+
+syn keyword fishKeywordIf if skipwhite nextgroup=@fishStatement
+syn keyword fishKeyword else skipwhite nextgroup=fishKeywordIf,fishSemicolon
+hi def link fishKeywordIf fishKeyword
+
+syn keyword fishKeyword switch skipwhite nextgroup=@fishArgument
+syn keyword fishKeyword case skipwhite nextgroup=@fishArgument
+
+syn keyword fishKeyword while skipwhite nextgroup=@fishStatement
+
+syn keyword fishKeyword for skipwhite nextgroup=fishForVariable
+syn match fishForVariable /[[:alnum:]_]\+/ contained skipwhite nextgroup=fishKeywordIn
+syn keyword fishKeywordIn in contained skipwhite nextgroup=@fishArgument
+hi def link fishForVariable fishParameter
+hi def link fishKeywordIn fishKeyword
+
+syn keyword fishKeyword _ abbr argparse begin bg bind block break breakpoint cd commandline
+ \ complete continue count disown echo emit eval exec exit false fg function functions
+ \ history jobs math printf pwd random read realpath return set set_color source status
+ \ string test time true type ulimit wait
+ \ skipwhite nextgroup=@fishNext
+syn match fishKeyword /\<contains\>/ skipwhite nextgroup=@fishNext
+
+syn match fishCommand /[[:alnum:]_\/[][[:alnum:]+._-]*/ skipwhite nextgroup=@fishNext
+
+
+" Internally Nested Arguments
+
+syn cluster fishSubscriptArgs contains=fishInnerVariable,fishIndexNum,fishIndexRange,fishInnerCommandSub
+
+syn match fishInnerVariable /\$\+[[:alnum:]_]\+/ contained
+syn match fishInnerVariable /\$\+[[:alnum:]_]\+\[/me=e-1,he=e-1 contained nextgroup=fishInnerSubscript
+hi def link fishInnerVariable fishVariable
+
+syn region fishInnerSubscript matchgroup=fishVariable start=/\[/ end=/]/ contained
+ \ keepend contains=@fishSubscriptArgs
+hi def link fishInnerSubscript fishSubscript
+
+syn match fishIndexNum /[+-]?[[:digit:]]\+/ contained
+hi def link fishIndexNum fishParameter
+
+syn match fishIndexRange /\.\./ contained
+hi def link fishIndexRange fishParameter
+
+syn region fishInnerCommandSub matchgroup=fishOperator start=/(/ start=/\$(/ end=/)/ contained
+ \ contains=@fishStatement
+hi def link fishInnerCommandSub fishCommandSub
+
+syn region fishQuotedCommandSub matchgroup=fishOperator start=/\$(/ end=/)/ contained
+ \ contains=@fishStatement
+hi def link fishQuotedCommandSub fishCommandSub
+
+syn match fishBraceExpansionComma /,/ contained
+hi def link fishBraceExpansionComma fishOperator
+
+syn match fishBracedParameter '[[:alnum:]\u5b\u5d@:=+.%/!_-]\+' contained contains=fishInnerPathGlob
+hi def link fishBracedParameter fishParameter
+
+syn region fishBracedQuote start=/'/ skip=/\\'/ end=/'/ contained
+ \ contains=fishEscapedEscape,fishEscapedSQuote
+syn region fishBracedQuote start=/"/ skip=/\\"/ end=/"/ contained
+ \ contains=fishEscapedEscape,fishEscapedDQuote,fishEscapedDollar,fishInnerVariable,fishInnerCommandSub
+hi def link fishBracedQuote fishQuote
+
+
+" Arguments
+
+syn cluster fishArgument contains=fishParameter,fishOption,fishVariable,fishPathGlob,fishBraceExpansion,fishQuote,fishCharacter,fishCommandSub,fishRedirection,fishSelfPid
+
+syn match fishParameter '[[:alnum:]\u5b\u5d@:=+.,%/!_-]\+' contained skipwhite nextgroup=@fishNext
+
+syn match fishOption /-[[:alnum:]=_-]*/ contained skipwhite nextgroup=@fishNext
+
+syn match fishPathGlob /\(\~\|*\|?\)/ contained skipwhite nextgroup=@fishNext
+
+syn region fishBraceExpansion matchgroup=fishOperator start=/{/ end=/}/ contained
+ \ contains=fishBraceExpansionComma,fishInnerVariable,fishInnerCommandSub,fishBracedParameter,fishBracedQuote
+ \ skipwhite nextgroup=@fishNext
+
+syn match fishVariable /\$\+[[:alnum:]_]\+/ skipwhite nextgroup=@fishNext
+syn match fishVariable /\$\+[[:alnum:]_]\+\[/me=e-1,he=e-1 nextgroup=fishSubscript
+
+syn region fishSubscript matchgroup=fishVariable start=/\[/ end=/]/ contained
+ \ keepend contains=@fishSubscriptArgs
+ \ skipwhite nextgroup=@fishNext
+
+syn region fishCommandSub matchgroup=fishOperator start=/(/ start=/\$(/ end=/)/ contained
+ \ contains=@fishStatement
+ \ skipwhite nextgroup=@fishNext
+
+syn region fishQuote start=/'/ skip=/\\'/ end=/'/ contained
+ \ contains=fishEscapedEscape,fishEscapedSQuote
+ \ skipwhite nextgroup=@fishNext
+syn region fishQuote start=/"/ skip=/\\"/ end=/"/ contained
+ \ contains=fishEscapedEscape,fishEscapedDQuote,fishEscapedDollar,fishInnerVariable,fishQuotedCommandSub
+ \ skipwhite nextgroup=@fishNext
+
+syn match fishEscapedEscape /\\\\/ contained
+syn match fishEscapedSQuote /\\'/ contained
+syn match fishEscapedDQuote /\\"/ contained
+syn match fishEscapedDollar /\\\$/ contained
+hi def link fishEscapedEscape fishCharacter
+hi def link fishEscapedSQuote fishCharacter
+hi def link fishEscapedDQuote fishCharacter
+hi def link fishEscapedDollar fishCharacter
+
+syn match fishCharacter /\\[0-7]\{1,3}/ contained skipwhite nextgroup=@fishNext
+syn match fishCharacter /\\u[0-9a-fA-F]\{4}/ contained skipwhite nextgroup=@fishNext
+syn match fishCharacter /\\U[0-9a-fA-F]\{8}/ contained skipwhite nextgroup=@fishNext
+syn match fishCharacter /\\x[0-7][0-9a-fA-F]\|\\x[0-9a-fA-F]/ contained skipwhite nextgroup=@fishNext
+syn match fishCharacter /\\X[0-9a-fA-F]\{1,2}/ contained skipwhite nextgroup=@fishNext
+syn match fishCharacter /\\[abcefnrtv[\](){}<>\\*?~%#$|&;'" ]/ contained skipwhite nextgroup=@fishNext
+
+syn match fishRedirection /</ contained skipwhite nextgroup=fishRedirectionTarget
+syn match fishRedirection /[0-9&]\?>[>?]\?/ contained skipwhite nextgroup=fishRedirectionTarget
+syn match fishRedirection /[0-9&]\?>&[0-9-]/ contained skipwhite nextgroup=@fishNext
+
+syn match fishRedirectionTarget /[[:alnum:]$~*?{,}"'\/._-]\+/ contained contains=fishInnerVariable skipwhite nextgroup=@fishNext
+hi def link fishRedirectionTarget fishRedirection
+
+syn match fishSelfPid /%self\>/ contained nextgroup=@fishNext
+hi def link fishSelfPid fishOperator
+
+
+" Terminators
+
+syn cluster fishTerminator contains=fishPipe,fishBackgroundJob,fishSemicolon,fishSymbolicAndOr
+
+syn match fishPipe /\(1>\|2>\|&\)\?|/ contained skipwhite nextgroup=@fishStatement
+hi def link fishPipe fishEnd
+
+syn match fishBackgroundJob /&$/ contained skipwhite nextgroup=@fishStatement
+syn match fishBackgroundJob /&[^<>&|]/me=s+1,he=s+1 contained skipwhite nextgroup=@fishStatement
+hi def link fishBackgroundJob fishEnd
+
+syn match fishSemicolon /;/ skipwhite nextgroup=@fishStatement
+hi def link fishSemicolon fishEnd
+
+syn match fishSymbolicAndOr /\(&&\|||\)/ contained skipwhite skipempty nextgroup=@fishStatement
+hi def link fishSymbolicAndOr fishOperator
+
+
+" Other
+
+syn cluster fishNext contains=fishEscapedNl,@fishArgument,@fishTerminator
+
+syn match fishEscapedNl /\\$/ skipnl skipwhite contained nextgroup=@fishNext
+
+syn match fishComment /#.*/ contains=fishTodo,@Spell
+
+syn keyword fishTodo TODO contained
+
+
+
+syn sync minlines=200
+syn sync maxlines=300
+
+
+" Intermediate highlight groups matching $fish_color_* variables
+
+hi def link fishCommand fish_color_command
+hi def link fishComment fish_color_comment
+hi def link fishEnd fish_color_end
+hi def link fishCharacter fish_color_escape
+hi def link fishKeyword fish_color_keyword
+hi def link fishEscapedNl fish_color_normal
+hi def link fishOperator fish_color_operator
+hi def link fishVariable fish_color_operator
+hi def link fishInnerVariable fish_color_operator
+hi def link fishPathGlob fish_color_operator
+hi def link fishOption fish_color_option
+hi def link fishParameter fish_color_param
+hi def link fishQuote fish_color_quote
+hi def link fishRedirection fish_color_redirection
+
+
+" Default highlight groups
+
+hi def link fish_color_param Normal
+hi def link fish_color_normal Normal
+hi def link fish_color_option Normal
+hi def link fish_color_command Function
+hi def link fish_color_keyword Keyword
+hi def link fish_color_end Delimiter
+hi def link fish_color_operator Operator
+hi def link fish_color_redirection Type
+hi def link fish_color_quote String
+hi def link fish_color_escape Character
+hi def link fish_color_comment Comment
+
+hi def link fishTodo Todo
+
+
+let b:current_syntax = 'fish'
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/syntax/flexwiki.vim b/runtime/syntax/flexwiki.vim
index 6b15ab2d90..3b5f7ff573 100644
--- a/runtime/syntax/flexwiki.vim
+++ b/runtime/syntax/flexwiki.vim
@@ -67,10 +67,10 @@ syntax match flexwikiEmoticons /\((.)\|:[()|$@]\|:-[DOPS()\]|$@]\|;)\|:'(\)
" Aggregate all the regular text highlighting into flexwikiText
syntax cluster flexwikiText contains=flexwikiItalic,flexwikiBold,flexwikiCode,flexwikiDeEmphasis,flexwikiDelText,flexwikiInsText,flexwikiSuperScript,flexwikiSubScript,flexwikiCitation,flexwikiLink,flexwikiWord,flexwikiEmoticons
-" single-line WikiPropertys
+" single-line WikiProperties
syntax match flexwikiSingleLineProperty /^:\?[A-Z_][_a-zA-Z0-9]\+:/
-" TODO: multi-line WikiPropertys
+" TODO: multi-line WikiProperties
" Header levels, 1-6
syntax match flexwikiH1 /^!.*$/
diff --git a/runtime/syntax/forth.vim b/runtime/syntax/forth.vim
index 721bceb367..252116a187 100644
--- a/runtime/syntax/forth.vim
+++ b/runtime/syntax/forth.vim
@@ -1,10 +1,20 @@
" Vim syntax file
-" Language: FORTH
-" Current Maintainer: Johan Kotlinski <kotlinski@gmail.com>
-" Previous Maintainer: Christian V. J. Brüssow <cvjb@cvjb.de>
-" Last Change: 2018-03-29
-" Filenames: *.fs,*.ft
-" URL: https://github.com/jkotlinski/forth.vim
+" Language: Forth
+" Maintainer: Johan Kotlinski <kotlinski@gmail.com>
+" Previous Maintainer: Christian V. J. Brüssow <cvjb@cvjb.de>
+" Last Change: 2023 Aug 13
+" Filenames: *.f,*.fs,*.ft,*.fth,*.4th
+" URL: https://github.com/jkotlinski/forth.vim
+
+" Supports the Forth-2012 Standard.
+"
+" Removed words from the earlier Forth-79, Forth-83 and Forth-94 standards are
+" also included.
+"
+" These have been organised according to the version in which they were
+" initially included and the version in which they were removed (obsolescent
+" status is ignored). Words with "experimental" or "uncontrolled" status are
+" not included unless they were later standardised.
" quit when a syntax file was already loaded
if exists("b:current_syntax")
@@ -15,20 +25,15 @@ let s:cpo_save = &cpo
set cpo&vim
" Synchronization method
-syn sync ccomment
-syn sync maxlines=200
+exe "syn sync minlines=" .. get(g:, "forth_minlines", 50)
-" I use gforth, so I set this to case ignore
syn case ignore
-" Some special, non-FORTH keywords
-syn keyword forthTodo contained TODO FIXME XXX
-syn match forthTodo contained 'Copyright\(\s([Cc])\)\=\(\s[0-9]\{2,4}\)\='
-
" Characters allowed in keywords
" I don't know if 128-255 are allowed in ANS-FORTH
-setlocal iskeyword=!,@,33-35,%,$,38-64,A-Z,91-96,a-z,123-126,128-255
+syn iskeyword 33-126,128-255
+" Space errors {{{1
" when wanted, highlight trailing white space
if exists("forth_space_errors")
if !exists("forth_no_trail_space_error")
@@ -39,184 +44,369 @@ if exists("forth_space_errors")
endif
endif
-" Keywords
-
-" basic mathematical and logical operators
-syn keyword forthOperators + - * / MOD /MOD NEGATE ABS MIN MAX
-syn keyword forthOperators AND OR XOR NOT LSHIFT RSHIFT INVERT 2* 2/ 1+
-syn keyword forthOperators 1- 2+ 2- 8* UNDER+
-syn keyword forthOperators M+ */ */MOD M* UM* M*/ UM/MOD FM/MOD SM/REM
-syn keyword forthOperators D+ D- DNEGATE DABS DMIN DMAX D2* D2/
-syn keyword forthOperators F+ F- F* F/ FNEGATE FABS FMAX FMIN FLOOR FROUND
-syn keyword forthOperators F** FSQRT FEXP FEXPM1 FLN FLNP1 FLOG FALOG FSIN
-syn keyword forthOperators FCOS FSINCOS FTAN FASIN FACOS FATAN FATAN2 FSINH
-syn keyword forthOperators FCOSH FTANH FASINH FACOSH FATANH F2* F2/ 1/F
-syn keyword forthOperators F~REL F~ABS F~
-syn keyword forthOperators 0< 0<= 0<> 0= 0> 0>= < <= <> = > >= U< U<=
-syn keyword forthOperators U> U>= D0< D0<= D0<> D0= D0> D0>= D< D<= D<>
-syn keyword forthOperators D= D> D>= DU< DU<= DU> DU>= WITHIN ?NEGATE
-syn keyword forthOperators ?DNEGATE TRUE FALSE
-
-" various words that take an input and do something with it
-syn keyword forthFunction . U. .R U.R
-
-" stack manipulations
-syn keyword forthStack DROP NIP DUP OVER TUCK SWAP ROT -ROT ?DUP PICK ROLL
-syn keyword forthStack 2DROP 2NIP 2DUP 2OVER 2TUCK 2SWAP 2ROT 2-ROT
-syn keyword forthStack 3DUP 4DUP 5DUP 3DROP 4DROP 5DROP 8DROP 4SWAP 4ROT
-syn keyword forthStack 4-ROT 4TUCK 8SWAP 8DUP
-syn keyword forthRStack >R R> R@ RDROP 2>R 2R> 2R@ 2RDROP
-syn keyword forthRstack 4>R 4R> 4R@ 4RDROP
-syn keyword forthFStack FDROP FNIP FDUP FOVER FTUCK FSWAP FROT
-
-" stack pointer manipulations
-syn keyword forthSP SP@ SP! FP@ FP! RP@ RP! LP@ LP! DEPTH
-
-" address operations
-syn keyword forthMemory @ ! +! C@ C! 2@ 2! F@ F! SF@ SF! DF@ DF!
-syn keyword forthAdrArith CHARS CHAR+ CELLS CELL+ CELL ALIGN ALIGNED FLOATS
-syn keyword forthAdrArith FLOAT+ FLOAT FALIGN FALIGNED SFLOATS SFLOAT+
-syn keyword forthAdrArith SFALIGN SFALIGNED DFLOATS DFLOAT+ DFALIGN DFALIGNED
-syn keyword forthAdrArith MAXALIGN MAXALIGNED CFALIGN CFALIGNED
-syn keyword forthAdrArith ADDRESS-UNIT-BITS ALLOT ALLOCATE HERE
-syn keyword forthMemBlks MOVE ERASE CMOVE CMOVE> FILL BLANK UNUSED
-
-" conditionals
-syn keyword forthCond IF ELSE ENDIF THEN CASE OF ENDOF ENDCASE ?DUP-IF
-syn keyword forthCond ?DUP-0=-IF AHEAD CS-PICK CS-ROLL CATCH THROW WITHIN
-
-" iterations
-syn keyword forthLoop BEGIN WHILE REPEAT UNTIL AGAIN
-syn keyword forthLoop ?DO LOOP I J K +DO U+DO -DO U-DO DO +LOOP -LOOP
-syn keyword forthLoop UNLOOP LEAVE ?LEAVE EXIT DONE FOR NEXT RECURSE
-
-" new words
-syn match forthClassDef '\<:class\s*[^ \t]\+\>'
-syn match forthObjectDef '\<:object\s*[^ \t]\+\>'
-syn match forthColonDef '\<:m\?\s*[^ \t]\+\>'
-syn keyword forthEndOfColonDef ; ;M ;m
-syn keyword forthEndOfClassDef ;class
-syn keyword forthEndOfObjectDef ;object
-syn keyword forthDefine CONSTANT 2CONSTANT FCONSTANT VARIABLE 2VARIABLE
-syn keyword forthDefine FVARIABLE CREATE USER VALUE TO DEFER IS DOES> IMMEDIATE
-syn keyword forthDefine COMPILE-ONLY COMPILE RESTRICT INTERPRET POSTPONE EXECUTE
-syn keyword forthDefine LITERAL CREATE-INTERPRET/COMPILE INTERPRETATION>
-syn keyword forthDefine <INTERPRETATION COMPILATION> <COMPILATION ] LASTXT
-syn keyword forthDefine COMP' POSTPONE, FIND-NAME NAME>INT NAME?INT NAME>COMP
-syn keyword forthDefine NAME>STRING STATE C; CVARIABLE BUFFER: MARKER
-syn keyword forthDefine , 2, F, C, COMPILE,
-syn match forthDefine "\[IFDEF]"
-syn match forthDefine "\[IFUNDEF]"
-syn match forthDefine "\[THEN]"
-syn match forthDefine "\[ENDIF]"
-syn match forthDefine "\[ELSE]"
-syn match forthDefine "\[?DO]"
-syn match forthDefine "\[DO]"
-syn match forthDefine "\[LOOP]"
-syn match forthDefine "\[+LOOP]"
-syn match forthDefine "\[NEXT]"
-syn match forthDefine "\[BEGIN]"
-syn match forthDefine "\[UNTIL]"
-syn match forthDefine "\[AGAIN]"
-syn match forthDefine "\[WHILE]"
-syn match forthDefine "\[REPEAT]"
-syn match forthDefine "\[COMP']"
-syn match forthDefine "'"
-syn match forthDefine '\<\[\>'
-syn match forthDefine "\[']"
-syn match forthDefine '\[COMPILE]'
-syn match forthDefine '\[CHAR]'
-
-" debugging
-syn keyword forthDebug PRINTDEBUGDATA PRINTDEBUGLINE
-syn match forthDebug "\<\~\~\>"
-
-" Assembler
-syn keyword forthAssembler ASSEMBLER CODE END-CODE ;CODE FLUSH-ICACHE C,
-
-" basic character operations
-syn keyword forthCharOps (.) CHAR EXPECT FIND WORD TYPE -TRAILING EMIT KEY
-syn keyword forthCharOps KEY? TIB CR BL COUNT SPACE SPACES
-" recognize 'char (' or '[char] (' correctly, so it doesn't
+" Core words {{{1
+
+" basic mathematical and logical operators {{{2
+syn keyword forthOperators * */ */MOD + - / /MOD 0< 0= 1+ 1- 2* 2/ < = > ABS
+syn keyword forthOperators AND FM/MOD INVERT LSHIFT M* MAX MIN MOD NEGATE OR
+syn keyword forthOperators RSHIFT SM/REM U< UM* UM/MOD XOR
+ " extension words
+syn keyword forthOperators 0<> 0> <> U> WITHIN
+ " Forth-79
+syn keyword forthOperators U* U/ U/MOD
+ " Forth-79, Forth-83
+syn keyword forthOperators NOT
+ " Forth-83
+syn keyword forthOperators 2+ 2-
+
+" non-standard basic mathematical and logical operators
+syn keyword forthOperators 0<= 0>= 8* <= >= ?DNEGATE ?NEGATE U<= U>= UNDER+
+
+" various words that take an input and do something with it {{{2
+syn keyword forthFunction . U.
+ " extension words
+syn keyword forthFunction .R U.R
+
+" stack manipulations {{{2
+syn keyword forthStack 2DROP 2DUP 2OVER 2SWAP >R ?DUP DROP DUP OVER R> R@ ROT
+syn keyword forthStack SWAP
+ " extension words
+syn keyword forthStack NIP PICK ROLL TUCK
+syn keyword forthRStack 2>R 2R> 2R@
+
+" non-standard stack manipulations
+syn keyword forthStack -ROT 3DROP 3DUP 4-ROT 4DROP 4DUP 4ROT 4SWAP 4TUCK
+syn keyword forthStack 5DROP 5DUP 8DROP 8DUP 8SWAP
+syn keyword forthRStack 4>R 4R> 4R@ 4RDROP RDROP
+
+" stack pointer manipulations {{{2
+syn keyword forthSP DEPTH
+
+" non-standard stack pointer manipulations
+syn keyword forthSP FP! FP@ LP! LP@ RP! RP@ SP! SP@
+
+" address operations {{{2
+syn keyword forthMemory ! +! 2! 2@ @ C! C@
+syn keyword forthAdrArith ALIGN ALIGNED ALLOT CELL+ CELLS CHAR+ CHARS
+syn keyword forthMemBlks FILL MOVE
+ " extension words
+syn keyword forthMemBlks ERASE UNUSED
+
+" non-standard address operations
+syn keyword forthAdrArith ADDRESS-UNIT-BITS CELL CFALIGN CFALIGNED FLOAT
+syn keyword forthAdrArith MAXALIGN MAXALIGNED
+
+" conditionals {{{2
+syn keyword forthCond ELSE IF THEN
+ " extension words
+syn keyword forthCond CASE ENDCASE ENDOF OF
+
+" non-standard conditionals
+syn keyword forthCond ?DUP-0=-IF ?DUP-IF ENDIF
+
+" iterations {{{2
+syn keyword forthLoop +LOOP BEGIN DO EXIT I J LEAVE LOOP RECURSE REPEAT UNLOOP
+syn keyword forthLoop UNTIL WHILE
+ " extension words
+syn keyword forthLoop ?DO AGAIN
+
+" non-standard iterations
+syn keyword forthLoop +DO -DO -LOOP ?LEAVE DONE FOR K NEXT U+DO U-DO
+
+" new words {{{2
+syn match forthColonDef "\<:\s*[^ \t]\+\>"
+syn keyword forthEndOfColonDef ;
+syn keyword forthDefine ' , C, CONSTANT CREATE DOES> EXECUTE IMMEDIATE LITERAL
+syn keyword forthDefine POSTPONE STATE VARIABLE ]
+syn match forthDefine "\<\[']\>"
+syn match forthDefine "\<\[\>"
+ " extension words
+syn keyword forthColonDef :NONAME
+syn keyword forthDefine BUFFER: COMPILE, DEFER IS MARKER TO VALUE
+syn match forthDefine "\<\[COMPILE]\>"
+ " Forth-79, Forth-83
+syn keyword forthDefine COMPILE
+
+" non-standard new words
+syn match forthClassDef "\<:CLASS\s*[^ \t]\+\>"
+syn keyword forthEndOfClassDef ;CLASS
+syn match forthObjectDef "\<:OBJECT\s*[^ \t]\+\>"
+syn keyword forthEndOfObjectDef ;OBJECT
+syn match forthColonDef "\<:M\s*[^ \t]\+\>"
+syn keyword forthEndOfColonDef ;M
+syn keyword forthDefine 2, <BUILDS <COMPILATION <INTERPRETATION C; COMP'
+syn keyword forthDefine COMPILATION> COMPILE-ONLY CREATE-INTERPRET/COMPILE
+syn keyword forthDefine CVARIABLE F, FIND-NAME INTERPRET INTERPRETATION>
+syn keyword forthDefine LASTXT NAME>COMP NAME>INT NAME?INT POSTPONE, RESTRICT
+syn keyword forthDefine USER
+syn match forthDefine "\<\[COMP']\>"
+
+" basic character operations {{{2
+syn keyword forthCharOps BL COUNT CR EMIT FIND KEY SPACE SPACES TYPE WORD
+" recognize 'char (' or '[CHAR] (' correctly, so it doesn't
" highlight everything after the paren as a comment till a closing ')'
-syn match forthCharOps '\<char\s\S\s'
-syn match forthCharOps '\<\[char\]\s\S\s'
-syn region forthCharOps start=+."\s+ skip=+\\"+ end=+"+
-
-" char-number conversion
-syn keyword forthConversion <<# <# # #> #>> #S (NUMBER) (NUMBER?) CONVERT D>F
-syn keyword forthConversion D>S DIGIT DPL F>D HLD HOLD NUMBER S>D SIGN >NUMBER
-syn keyword forthConversion F>S S>F HOLDS
-
-" interpreter, wordbook, compiler
-syn keyword forthForth (LOCAL) BYE COLD ABORT >BODY >NEXT >LINK CFA >VIEW HERE
-syn keyword forthForth PAD WORDS VIEW VIEW> N>LINK NAME> LINK> L>NAME FORGET
-syn keyword forthForth BODY> ASSERT( ASSERT0( ASSERT1( ASSERT2( ASSERT3( )
-syn keyword forthForth >IN ACCEPT ENVIRONMENT? EVALUATE QUIT SOURCE ACTION-OF
-syn keyword forthForth DEFER! DEFER@ PARSE PARSE-NAME REFILL RESTORE-INPUT
-syn keyword forthForth SAVE-INPUT SOURCE-ID
-syn region forthForth start=+ABORT"\s+ skip=+\\"+ end=+"+
-
-" vocabularies
-syn keyword forthVocs ONLY FORTH ALSO ROOT SEAL VOCS ORDER CONTEXT #VOCS
-syn keyword forthVocs VOCABULARY DEFINITIONS
-
-" File keywords
-syn keyword forthFileMode R/O R/W W/O BIN
-syn keyword forthFileWords OPEN-FILE CREATE-FILE CLOSE-FILE DELETE-FILE
-syn keyword forthFileWords RENAME-FILE READ-FILE READ-LINE KEY-FILE
-syn keyword forthFileWords KEY?-FILE WRITE-FILE WRITE-LINE EMIT-FILE
-syn keyword forthFileWords FLUSH-FILE FILE-STATUS FILE-POSITION
-syn keyword forthFileWords REPOSITION-FILE FILE-SIZE RESIZE-FILE
-syn keyword forthFileWords SLURP-FILE SLURP-FID STDIN STDOUT STDERR
-syn keyword forthFileWords INCLUDE-FILE INCLUDED REQUIRED
-syn keyword forthBlocks OPEN-BLOCKS USE LOAD --> BLOCK-OFFSET
-syn keyword forthBlocks GET-BLOCK-FID BLOCK-POSITION LIST SCR BLOCK
-syn keyword forthBlocks BUFER EMPTY-BUFFERS EMPTY-BUFFER UPDATE UPDATED?
-syn keyword forthBlocks SAVE-BUFFERS SAVE-BUFFER FLUSH THRU +LOAD +THRU
-syn keyword forthBlocks BLOCK-INCLUDED BLK
+syn match forthCharOps '\<CHAR\s\S\s'
+syn match forthCharOps '\<\[CHAR]\s\S\s'
+ " Forth-83, Forth-94
+syn keyword forthCharOps EXPECT #TIB TIB
+
+" non-standard basic character operations
+syn keyword forthCharOps (.)
+
+" char-number conversion {{{2
+syn keyword forthConversion # #> #S <# >NUMBER HOLD S>D SIGN
+ " extension words
+syn keyword forthConversion HOLDS
+ " Forth-79, Forth-83, Forth-93
+syn keyword forthConversion CONVERT
+
+" non-standard char-number conversion
+syn keyword forthConversion #>> (NUMBER) (NUMBER?) <<# DIGIT DPL HLD NUMBER
+
+" interpreter, wordbook, compiler {{{2
+syn keyword forthForth >BODY >IN ACCEPT ENVIRONMENT? EVALUATE HERE QUIT SOURCE
+ " extension words
+syn keyword forthForth ACTION-OF DEFER! DEFER@ PAD PARSE PARSE-NAME REFILL
+syn keyword forthForth RESTORE-INPUT SAVE-INPUT SOURCE-ID
+ " Forth-79
+syn keyword forthForth 79-STANDARD
+ " Forth-83
+syn keyword forthForth <MARK <RESOLVE >MARK >RESOLVE ?BRANCH BRANCH FORTH-83
+ " Forth-79, Forth-83, Forth-94
+syn keyword forthForth QUERY
+ " Forth-83, Forth-94
+syn keyword forthForth SPAN
+
+" non-standard interpreter, wordbook, compiler
+syn keyword forthForth ) >LINK >NEXT >VIEW ASSERT( ASSERT0( ASSERT1( ASSERT2(
+syn keyword forthForth ASSERT3( BODY> CFA COLD L>NAME LINK> N>LINK NAME> VIEW
+syn keyword forthForth VIEW>
+
+" booleans {{{2
+ " extension words
+syn match forthBoolean "\<\%(TRUE\|FALSE\)\>"
+
+" numbers {{{2
+syn keyword forthMath BASE DECIMAL
+ " extension words
+syn keyword forthMath HEX
+syn match forthInteger '\<-\=\d\+\.\=\>'
+syn match forthInteger '\<#-\=\d\+\.\=\>'
+syn match forthInteger '\<\$-\=\x\+\.\=\>'
+syn match forthInteger '\<%-\=[01]\+\.\=\>'
+
+" characters {{{2
+syn match forthCharacter "'\k'"
+
+" strings {{{2
+
+" Words that end with " are assumed to start string parsing.
+" This includes standard words: S" ."
+syn region forthString matchgroup=forthString start=+\<\S\+"\s+ end=+"+ end=+$+ contains=@Spell
+ " extension words
+syn region forthString matchgroup=forthString start=+\<C"\s+ end=+"+ end=+$+ contains=@Spell
+" Matches S\"
+syn region forthString matchgroup=forthString start=+\<S\\"\s+ end=+"+ end=+$+ contains=@Spell,forthEscape
+
+syn match forthEscape +\C\\[abeflmnqrtvz"\\]+ contained
+syn match forthEscape "\C\\x\x\x" contained
+
+" comments {{{2
-" numbers
-syn keyword forthMath DECIMAL HEX BASE
-syn match forthInteger '\<-\=[0-9]\+.\=\>'
-syn match forthInteger '\<&-\=[0-9]\+.\=\>'
-" recognize hex and binary numbers, the '$' and '%' notation is for gforth
-syn match forthInteger '\<\$\x*\x\+\>' " *1* --- don't mess
-syn match forthInteger '\<\x*\d\x*\>' " *2* --- this order!
-syn match forthInteger '\<%[0-1]*[0-1]\+\>'
-syn match forthFloat '\<-\=\d*[.]\=\d\+[DdEe]\d\+\>'
-syn match forthFloat '\<-\=\d*[.]\=\d\+[DdEe][-+]\d\+\>'
-
-" XXX If you find this overkill you can remove it. this has to come after the
-" highlighting for numbers otherwise it has no effect.
-syn region forthComment start='0 \[if\]' end='\[endif\]' end='\[then\]' contains=forthTodo
-
-" Strings
-syn region forthString start=+\.*\"+ end=+"+ end=+$+
-" XXX
-syn region forthString start=+s\"+ end=+"+ end=+$+
-syn region forthString start=+s\\\"+ end=+"+ end=+$+
-syn region forthString start=+c\"+ end=+"+ end=+$+
-
-" Comments
-syn match forthComment '\\\s.*$' contains=forthTodo,forthSpaceError
-syn region forthComment start='\\S\s' end='.*' contains=forthTodo,forthSpaceError
-syn match forthComment '\.(\s[^)]*)' contains=forthTodo,forthSpaceError
-syn region forthComment start='\(^\|\s\)\zs(\s' skip='\\)' end=')' contains=forthTodo,forthSpaceError
-syn region forthComment start='/\*' end='\*/' contains=forthTodo,forthSpaceError
-
-" Include files
-syn match forthInclude '^INCLUDE\s\+\k\+'
-syn match forthInclude '^REQUIRE\s\+\k\+'
+syn keyword forthTodo contained TODO FIXME XXX
+
+" Some special, non-FORTH keywords
+syn match forthTodo contained "\<\%(TODO\|FIXME\|XXX\)\%(\>\|:\@=\)"
+
+" XXX If you find this overkill you can remove it. This has to come after the
+" highlighting for numbers and booleans otherwise it has no effect.
+syn region forthComment start='\<\%(0\|FALSE\)\s\+\[IF]' end='\<\[ENDIF]' end='\<\[THEN]' contains=forthTodo
+
+if get(g:, "forth_no_comment_fold", 0)
+ syn region forthComment start='\<(\>' end=')' contains=@Spell,forthTodo,forthSpaceError
+ " extension words
+ syn match forthComment '\<\\\>.*$' contains=@Spell,forthTodo,forthSpaceError
+else
+ syn region forthComment start='\<(\>' end=')' contains=@Spell,forthTodo,forthSpaceError fold
+ " extension words
+ syn match forthComment '\<\\\>.*$' contains=@Spell,forthTodo,forthSpaceError
+ syn region forthMultilineComment start="^\s*\\\>" end="\n\%(\s*\\\>\)\@!" contains=forthComment transparent fold
+endif
+
+ " extension words
+syn region forthComment start='\<\.(\>' end=')' end='$' contains=@Spell,forthTodo,forthSpaceError
+
+" ABORT {{{2
+syn keyword forthForth ABORT
+syn region forthForth start=+\<ABORT"\s+ end=+"\>+ end=+$+
+
+" The optional Block word set {{{1
+" Handled as Core words - REFILL
+syn keyword forthBlocks BLK BLOCK BUFFER FLUSH LOAD SAVE-BUFFERS UPDATE
+ " extension words
+syn keyword forthBlocks EMPTY-BUFFERS LIST SCR THRU
+
+" Non-standard Block words
+syn keyword forthBlocks +LOAD +THRU --> BLOCK-INCLUDED BLOCK-OFFSET
+syn keyword forthBlocks BLOCK-POSITION EMPTY-BUFFER GET-BLOCK-FID OPEN-BLOCKS
+syn keyword forthBlocks SAVE-BUFFER UPDATED? USE
+
+" The optional Double-Number word set {{{1
+syn keyword forthConversion D>S
+syn keyword forthDefine 2CONSTANT 2LITERAL 2VARIABLE
+syn keyword forthFunction D. D.R
+syn keyword forthOperators D+ D- D0= D2* D2/ D= DABS DMAX DMIN DNEGATE
+syn keyword forthOperators D0< D< M+ M*/
+ " extension words
+syn keyword forthDefine 2VALUE
+syn keyword forthOperators DU<
+syn keyword forthStack 2ROT
+
+" Non-standard Double-Number words
+syn keyword forthOperators D0<= D0<> D0> D0>= D<= D<> D> D>= DU<= DU> DU>=
+syn keyword forthStack 2-ROT 2NIP 2RDROP 2TUCK
+
+" The optional Exception word set {{{1
+" Handled as Core words - ABORT ABORT"
+syn keyword forthCond CATCH THROW
+
+" The optional Facility word set {{{1
+syn keyword forthCharOps AT-XY KEY? PAGE
+ " extension words
+syn keyword forthCharOps EKEY EKEY>CHAR EKEY>FKEY EKEY? EMIT? K-ALT-MASK
+syn keyword forthCharOps K-CTRL-MASK K-DELETE K-DOWN K-END K-F1 K-F10 K-F11
+syn keyword forthCharOps K-F12 K-F2 K-F3 K-F4 K-F5 K-F6 K-F7 K-F8 K-F9 K-HOME
+syn keyword forthCharOps K-INSERT K-LEFT K-NEXT K-PRIOR K-RIGHT K-SHIFT-MASK
+syn keyword forthCharOps K-UP
+syn keyword forthDefine +FIELD BEGIN-STRUCTURE CFIELD: END-STRUCTURE FIELD:
+syn keyword forthForth MS TIME&DATE
+
+" The optional File-Access word set {{{1
+" Handled as Core words - REFILL SOURCE-ID S\" S" (
+syn keyword forthFileMode BIN R/O R/W W/O
+syn keyword forthFileWords CLOSE-FILE CREATE-FILE DELETE-FILE FILE-POSITION
+syn keyword forthFileWords FILE-SIZE INCLUDE-FILE INCLUDED OPEN-FILE READ-FILE
+syn keyword forthFileWords READ-LINE REPOSITION-FILE RESIZE-FILE WRITE-FILE
+syn keyword forthFileWords WRITE-LINE
+ " extension words
+syn keyword forthFileWords FILE-STATUS FLUSH-FILE RENAME-FILE REQUIRED
+syn match forthInclude '\<INCLUDE\s\+\k\+'
+syn match forthInclude '\<REQUIRE\s\+\k\+'
+
+" Non-standard File-Access words
+syn keyword forthFileWords EMIT-FILE KEY-FILE KEY?-FILE SLURP-FID SLURP-FILE
+syn keyword forthFileWords STDERR STDIN STDOUT
syn match forthInclude '^FLOAD\s\+'
syn match forthInclude '^NEEDS\s\+'
-" Locals definitions
-syn region forthLocals start='{\s' start='{$' end='\s}' end='^}'
-syn match forthLocals '{ }' " otherwise, at least two spaces between
-syn region forthDeprecated start='locals|' end='|'
+" The optional Floating-Point word set {{{1
-" Define the default highlighting.
+" numbers
+syn match forthFloat '\<[+-]\=\d\+\.\=\d*[DdEe][+-]\=\d*\>'
+
+syn keyword forthConversion >FLOAT D>F F>D
+syn keyword forthAdrArith FALIGN FALIGNED FLOAT+ FLOATS
+syn keyword forthDefine FCONSTANT FLITERAL FVARIABLE
+syn keyword forthFStack FDROP FDUP FOVER FROT FSWAP
+syn keyword forthFunction REPRESENT
+syn keyword forthMemory F! F@
+syn keyword forthOperators F* F+ F- F/ F0< F0= F< FLOOR FMAX FMIN FNEGATE
+syn keyword forthOperators FROUND
+syn keyword forthSP FDEPTH
+ " extension words
+syn keyword forthConversion F>S S>F
+syn keyword forthAdrArith DFALIGN DFALIGNED DFLOAT+ DFLOATS SFALIGN
+syn keyword forthAdrArith SFALIGNED SFLOAT+ SFLOATS
+syn keyword forthDefine DFFIELD: FFIELD: FVALUE SFFIELD:
+syn keyword forthFunction F. FE. FS. PRECISION SET-PRECISION
+syn keyword forthMemory DF! DF@ SF! SF@
+syn keyword forthOperators F** FABS FACOS FACOSH FALOG FASIN FASINH FATAN
+syn keyword forthOperators FATAN2 FATANH FCOS FCOSH FEXP FEXPM1 FLN FLNP1
+syn keyword forthOperators FLOG FSIN FSINCOS FSINH FSQRT FTAN FTANH FTRUNC F~
+
+" Non-standard Floating-Point words
+syn keyword forthOperators 1/F F2* F2/ F~ABS F~REL
+syn keyword forthFStack FNIP FTUCK
+
+" The optional Locals word set {{{1
+syn keyword forthForth (LOCAL)
+ " extension words
+syn region forthLocals start="\<{:\>" end="\<:}\>"
+syn region forthLocals start="\<LOCALS|\>" end="\<|\>"
+
+" Non-standard Locals words
+syn region forthLocals start="\<{\>" end="\<}\>"
+
+" The optional Memory-Allocation word set {{{1
+syn keyword forthMemory ALLOCATE FREE RESIZE
+
+" The optional Programming-Tools wordset {{{1
+syn keyword forthDebug .S ? DUMP SEE WORDS
+ " extension words
+syn keyword forthAssembler ;CODE ASSEMBLER CODE END-CODE
+syn keyword forthCond AHEAD CS-PICK CS-ROLL
+syn keyword forthDefine NAME>COMPILE NAME>INTERPRET NAME>STRING SYNONYM
+syn keyword forthDefine TRAVERSE-WORDLIST
+syn match forthDefine "\<\[DEFINED]\>"
+syn match forthDefine "\<\[ELSE]\>"
+syn match forthDefine "\<\[IF]\>"
+syn match forthDefine "\<\[THEN]\>"
+syn match forthDefine "\<\[UNDEFINED]\>"
+syn keyword forthForth BYE FORGET
+syn keyword forthStack N>R NR>
+syn keyword forthVocs EDITOR
+
+" Non-standard Programming-Tools words
+syn keyword forthAssembler FLUSH-ICACHE
+syn keyword forthDebug PRINTDEBUGDATA PRINTDEBUGLINE
+syn match forthDebug "\<\~\~\>"
+syn match forthDefine "\<\[+LOOP]\>"
+syn match forthDefine "\<\[?DO]\>"
+syn match forthDefine "\<\[AGAIN]\>"
+syn match forthDefine "\<\[BEGIN]\>"
+syn match forthDefine "\<\[DO]\>"
+syn match forthDefine "\<\[ENDIF]\>"
+syn match forthDefine "\<\[IFDEF]\>"
+syn match forthDefine "\<\[IFUNDEF]\>"
+syn match forthDefine "\<\[LOOP]\>"
+syn match forthDefine "\<\[NEXT]\>"
+syn match forthDefine "\<\[REPEAT]\>"
+syn match forthDefine "\<\[UNTIL]\>"
+syn match forthDefine "\<\[WHILE]\>"
+
+" The optional Search-Order word set {{{1
+" Handled as Core words - FIND
+syn keyword forthVocs DEFINITIONS FORTH-WORDLIST GET-CURRENT GET-ORDER
+syn keyword forthVocs SEARCH-WORDLIST SET-CURRENT SET-ORDER WORDLIST
+ " extension words
+syn keyword forthVocs ALSO FORTH ONLY ORDER PREVIOUS
+ " Forth-79, Forth-83
+syn keyword forthVocs CONTEXT CURRENT VOCABULARY
+
+" Non-standard Search-Order words
+syn keyword forthVocs #VOCS ROOT SEAL VOCS
+
+" The optional String word set {{{1
+syn keyword forthFunction -TRAILING /STRING BLANK CMOVE CMOVE> COMPARE SEARCH
+syn keyword forthFunction SLITERAL
+ " extension words
+syn keyword forthFunction REPLACES SUBSTITUTE UNESCAPE
+
+" The optional Extended-Character word set {{{1
+" Handled as Core words - [CHAR] CHAR and PARSE
+syn keyword forthAdrArith XCHAR+
+syn keyword forthCharOps X-SIZE XC-SIZE XEMIT XKEY XKEY?
+syn keyword forthDefine XC,
+syn keyword forthMemory XC!+ XC!+? XC@+
+ " extension words
+syn keyword forthAdrArith XCHAR- +X/STRING X\\STRING-
+syn keyword forthCharOps EKEY>XCHAR X-WIDTH XC-WIDTH
+syn keyword forthConversion XHOLD
+syn keyword forthString -TRAILING-GARBAGE
+
+" Define the default highlighting {{{1
+hi def link forthBoolean Boolean
+hi def link forthCharacter Character
hi def link forthTodo Todo
hi def link forthOperators Operator
hi def link forthMath Number
@@ -240,6 +430,7 @@ hi def link forthCharOps Character
hi def link forthConversion String
hi def link forthForth Statement
hi def link forthVocs Statement
+hi def link forthEscape Special
hi def link forthString String
hi def link forthComment Comment
hi def link forthClassDef Define
@@ -248,15 +439,17 @@ hi def link forthObjectDef Define
hi def link forthEndOfObjectDef Define
hi def link forthInclude Include
hi def link forthLocals Type " nothing else uses type and locals must stand out
-hi def link forthDeprecated Error " if you must, change to Type
hi def link forthFileMode Function
hi def link forthFunction Function
hi def link forthFileWords Statement
hi def link forthBlocks Statement
hi def link forthSpaceError Error
+"}}}
let b:current_syntax = "forth"
let &cpo = s:cpo_save
unlet s:cpo_save
-" vim:ts=8:sw=4:nocindent:smartindent:
+
+" vim:ts=8:sw=4:nocindent:smartindent:fdm=marker:tw=78
+
diff --git a/runtime/syntax/fortran.vim b/runtime/syntax/fortran.vim
index b5c9b1ef8d..fc6c82b480 100644
--- a/runtime/syntax/fortran.vim
+++ b/runtime/syntax/fortran.vim
@@ -1,6 +1,6 @@
" Vim syntax file
" Language: Fortran 2008 (and older: Fortran 2003, 95, 90, and 77)
-" Version: (v104) 2021 April 06
+" Version: (v105) 2023 August 14
" Maintainer: Ajit J. Thakkar <ajit@unb.ca>; <http://www2.unb.ca/~ajit/>
" Usage: For instructions, do :help fortran-syntax from Vim
" Credits:
@@ -11,7 +11,7 @@
" Walter Dieudonne, Alexander Wagner, Roman Bertle, Charles Rendleman,
" Andrew Griffiths, Joe Krahn, Hendrik Merx, Matt Thompson, Jan Hermann,
" Stefano Zaghi, Vishnu V. Krishnan, Judicael Grasset, Takuma Yoshida,
-" Eisuke Kawashima, Andre Chalella, and Fritz Reese.
+" Eisuke Kawashima, Andre Chalella, Fritz Reese, and Karl D. Hammond.
if exists("b:current_syntax")
finish
@@ -95,16 +95,14 @@ if exists("fortran_more_precise")
syn match fortranConstructName "\(\<end\s*do\s\+\)\@11<=\a\w*"
syn match fortranConstructName "\(\<end\s*if\s\+\)\@11<=\a\w*"
syn match fortranConstructName "\(\<end\s*select\s\+\)\@15<=\a\w*"
+ syn match fortranConstructName "\(\<\%(exit\|cycle\)\s\+\)\@11<=\a\w*"
endif
syn match fortranUnitHeader "\<end\>"
-syn match fortranType "\<character\>"
-syn match fortranType "\<complex\>"
-syn match fortranType "\<integer\>"
-syn match fortranType "\<real\>"
-syn match fortranType "\<logical\>"
+syn match fortranType "\<character\((\s*kind\s*=\w\+)\)\?\>"
+syn match fortranType "\<complex\((\s*kind\s*=\w\+)\)\?\>"
syn keyword fortranType intrinsic
-syn match fortranType "\<implicit\>"
+syn match fortranType "\<implicit\>\s\+\(none\)\?"
syn keyword fortranStructure dimension
syn keyword fortranStorageClass parameter save
syn match fortranUnitHeader "\<subroutine\>"
@@ -131,7 +129,8 @@ syn match fortranTypeOb "\<character\s*\*"
syn match fortranBoolean "\.\s*\(true\|false\)\s*\."
-syn keyword fortranReadWrite backspace close endfile inquire open print read rewind write
+syn keyword fortranReadWrite print
+syn match fortranReadWrite '\<\(backspace\|close\|endfile\|inquire\|open\|read\|rewind\|write\)\ze\s*('
"If tabs are allowed then the left margin checks do not work
if exists("fortran_have_tabs")
@@ -140,7 +139,7 @@ else
syn match fortranTab "\t"
endif
-syn keyword fortranIO access blank direct exist file fmt form formatted iostat name named nextrec number opened rec recl sequential status unformatted unit
+syn match fortranIO '\%(\((\|,\|, *&\n\)\s*\)\@<=\(access\|blank\|direct\|exist\|file\|fmt\|form\|formatted\|iostat\|name\|named\|nextrec\|number\|opened\|rec\|recl\|sequential\|status\|unformatted\|unit\)\ze\s*='
syn keyword fortranIntrinsicR alog alog10 amax0 amax1 amin0 amin1 amod cabs ccos cexp clog csin csqrt dabs dacos dasin datan datan2 dcos dcosh ddim dexp dint dlog dlog10 dmax1 dmin1 dmod dnint dsign dsin dsinh dsqrt dtan dtanh float iabs idim idint idnint ifix isign max0 max1 min0 min1 sngl
@@ -151,8 +150,9 @@ syn keyword fortranIntrinsic abs acos aimag aint anint asin atan atan2 char cmpl
syn match fortranIntrinsic "\<len\s*[(,]"me=s+3
syn match fortranIntrinsic "\<real\s*("me=s+4
syn match fortranIntrinsic "\<logical\s*("me=s+7
-syn match fortranType "\<implicit\s\+real\>"
-syn match fortranType "\<implicit\s\+logical\>"
+syn match fortranType "\<type\>\(\s\+is\>\)\?"
+syn match fortranType "^\s*\(type\s\+\(is\)\? \)\?\s*\(real\|integer\|logical\|complex\|character\)\>"
+syn match fortranType "^\s*\(implicit \)\?\s*\(real\|integer\|logical\|complex\|character\)\>"
"Numbers of various sorts
" Integers
@@ -206,9 +206,6 @@ syn region fortranStringR start=+'+ end=+'+ contains=fortranContinueMark,fortran
syn keyword fortranIntrinsicR dim lge lgt lle llt mod
syn keyword fortranKeywordDel assign pause
-syn match fortranType "\<type\>"
-syn keyword fortranType none
-
syn keyword fortranStructure private public intent optional
syn keyword fortranStructure pointer target allocatable
syn keyword fortranStorageClass in out
@@ -222,7 +219,8 @@ syn keyword fortranUnitHeader result operator assignment
syn match fortranUnitHeader "\<interface\>"
syn keyword fortranKeyword allocate deallocate nullify cycle exit
syn match fortranConditional "\<select\>"
-syn keyword fortranConditional case default where elsewhere
+syn match fortranConditional "\<case\s\+default\>"
+syn keyword fortranConditional where elsewhere
syn match fortranOperator "\(\(>\|<\)=\=\|==\|/=\|=\)"
syn match fortranOperator "=>"
@@ -231,8 +229,7 @@ syn region fortranString start=+"+ end=+"+ contains=fortranLeftMargin,fortranCon
syn keyword fortranIO pad position action delim readwrite
syn keyword fortranIO eor advance nml
-syn keyword fortranIntrinsic adjustl adjustr all allocated any associated bit_size btest ceiling count cshift date_and_time digits dot_product eoshift epsilon exponent floor fraction huge iand ibclr ibits ibset ieor ior ishft ishftc lbound len_trim matmul maxexponent maxloc maxval merge minexponent minloc minval modulo mvbits nearest pack precision present product radix random_number random_seed range repeat reshape rrspacing
-syn keyword fortranIntrinsic scale scan selected_int_kind selected_real_kind set_exponent shape size spacing spread sum system_clock tiny transpose trim ubound unpack verify
+syn match fortranIntrinsic '\<\(adjustl\|adjustr\|all\|allocated\|any\|associated\|bit_size\|btest\|ceiling\|count\|cshift\|date_and_time\|digits\|dot_product\|eoshift\|epsilon\|exponent\|floor\|fraction\|huge\|iand\|ibclr\|ibits\|ibset\|ieor\|ior\|ishft\|ishftc\|lbound\|len_trim\|matmul\|maxexponent\|maxloc\|maxval\|merge\|minexponent\|minloc\|minval\|modulo\|mvbits\|nearest\|pack\|precision\|present\|product\|radix\|random_number\|random_seed\|range\|repeat\|reshape\|rrspacing\|scale\|scan\|selected_int_kind\|selected_real_kind\|set_exponent\|shape\|size\|spacing\|spread\|sum\|system_clock\|tiny\|transpose\|trim\|ubound\|unpack\|verify\)\>\ze\s*('
syn match fortranIntrinsic "\<not\>\(\s*\.\)\@!"me=s+3
syn match fortranIntrinsic "\<kind\>\s*[(,]"me=s+4
@@ -306,9 +303,9 @@ if b:fortran_dialect == "f08"
syn match fortranType "\<end\s*associate"
syn match fortranType "\<enum\s*,\s*bind\s*(\s*c\s*)"
syn match fortranType "\<end\s*enum"
- syn match fortranConditional "\<select\s*type"
- syn match fortranConditional "\<type\s*is\>"
+ syn match fortranConditional "\<select\s*type"
syn match fortranConditional "\<class\s*is\>"
+ syn match fortranConditional "\<class\s*default\>"
syn match fortranUnitHeader "\<abstract\s*interface\>"
syn match fortranOperator "\([\|]\)"
@@ -525,11 +522,6 @@ else
hi! def link fortranConditionalR fortranConditional
endif
-" CUDA
-hi def link fortranIntrinsicCUDA fortranIntrinsic
-hi def link fortranTypeCUDA fortranType
-hi def link fortranStringCUDA fortranString
-
hi def link fortranFormatSpec Identifier
hi def link fortranFloat Float
hi def link fortranPreCondit PreCondit
@@ -543,8 +535,15 @@ hi def link fortranComment Comment
hi def link fortranSerialNumber Todo
hi def link fortranTab Error
-" Uncomment the next line if you use extra intrinsics provided by vendors
-"hi def link fortranExtraIntrinsic Function
+if exists("fortran_CUDA")
+ hi def link fortranIntrinsicCUDA fortranIntrinsic
+ hi def link fortranTypeCUDA fortranType
+ hi def link fortranStringCUDA fortranString
+endif
+
+if exists("fortran_vendor_intrinsics")
+ hi def link fortranExtraIntrinsic Function
+endif
let b:current_syntax = "fortran"
diff --git a/runtime/syntax/freebasic.vim b/runtime/syntax/freebasic.vim
index 7549d02555..5c43289c16 100644
--- a/runtime/syntax/freebasic.vim
+++ b/runtime/syntax/freebasic.vim
@@ -2,7 +2,7 @@
" Language: FreeBASIC
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainer: Mark Manning <markem@sim1.us>
-" Last Change: 2022 Jun 26
+" Last Change: 2023 Aug 14
"
" Description:
"
@@ -338,13 +338,13 @@ syn keyword freebasicPredefined __FB_64BIT__ __FB_ARGC__ __FB_ARG_COUNT__ __FB_
syn keyword freebasicPredefined __FB_ARG_RIGHTOF__ __FB_ARGV__ __FB_ARM__ __FB_ASM__ __FB_BACKEND__
syn keyword freebasicPredefined __FB_BIGENDIAN__ __FB_BUILD_DATE__ __FB_BUILD_DATE_ISO__ __FB_BUILD_SHA1__
syn keyword freebasicPredefined __FB_CYGWIN__ __FB_DARWIN__ __FB_DEBUG__ __FB_DOS__ __FB_ERR__ __FB_EVAL__
-syn keyword freebasicPredefined __FB_FPMODE__ __FB_FPU__ __FB_FREEBSD__ __FB_GCC__ __FB_GUI__ __FB_JOIN__
+syn keyword freebasicPredefined __FB_FPMODE__ __FB_FPU__ __FB_FREEBSD__ __FB_GCC__ __FB_GUI__ __FB_IIF__ __FB_JOIN__
syn keyword freebasicPredefined __FB_LANG__ __FB_LINUX__ __FB_MAIN__ __FB_MIN_VERSION__ __FB_MT__ __FB_NETBSD__
syn keyword freebasicPredefined __FB_OPENBSD__ __FB_OPTIMIZE__ __FB_OPTION_BYVAL__ __FB_OPTION_DYNAMIC__
syn keyword freebasicPredefined __FB_OPTION_ESCAPE__ __FB_OPTION_EXPLICIT__ __FB_OPTION_GOSUB__
syn keyword freebasicPredefined __FB_OPTION_PRIVATE__ __FB_OUT_DLL__ __FB_OUT_EXE__ __FB_OUT_LIB__ __FB_OUT_OBJ__
-syn keyword freebasicPredefined __FB_PCOS__ __FB_PPC__ __FB_QUOTE__ __FB_SIGNATURE__ __FB_SSE__ __FB_UNIQUEID__
-syn keyword freebasicPredefined __FB_UNIQUEID_POP__ __FB_UNIQUEID_PUSH__ __FB_UNIX__ __FB_UNQUOTE__
+syn keyword freebasicPredefined __FB_PCOS__ __FB_PPC__ __FB_QUERY_SYMBOL__ __FB_QUOTE__ __FB_SIGNATURE__ __FB_SSE__
+syn keyword freebasicPredefined __FB_UNIQUEID__ __FB_UNIQUEID_POP__ __FB_UNIQUEID_PUSH__ __FB_UNIX__ __FB_UNQUOTE__
syn keyword freebasicPredefined __FB_VECTORIZE__ __FB_VER_MAJOR__ __FB_VER_MINOR__ __FB_VER_PATCH__ __FB_VERSION__
syn keyword freebasicPredefined __FB_WIN32__ __FB_X86__ __FB_XBOX__
syn keyword freebasicPredefined __FILE__ __FILE_NQ__ __FUNCTION__ __FUNCTION_NQ__
diff --git a/runtime/syntax/fstab.vim b/runtime/syntax/fstab.vim
index 7e18c267f7..91150bc37b 100644
--- a/runtime/syntax/fstab.vim
+++ b/runtime/syntax/fstab.vim
@@ -2,8 +2,8 @@
" Language: fstab file
" Maintainer: Radu Dineiu <radu.dineiu@gmail.com>
" URL: https://raw.github.com/rid9/vim-fstab/master/syntax/fstab.vim
-" Last Change: 2022 Dec 11
-" Version: 1.6.2
+" Last Change: 2023 Feb 19
+" Version: 1.6.3
"
" Credits:
" David Necas (Yeti) <yeti@physics.muni.cz>
@@ -389,7 +389,7 @@ syn match fsFreqPassNumber /\d\+\s\+[012]\s*/ contained
syn match fsDevice /^\s*\zs.\{-1,}\s/me=e-1 nextgroup=fsMountPoint contains=@fsDeviceCluster,@fsGeneralCluster
syn match fsMountPoint /\s\+.\{-}\s/me=e-1 nextgroup=fsType contains=@fsMountPointCluster,@fsGeneralCluster contained
syn match fsType /\s\+.\{-}\s/me=e-1 nextgroup=fsOptions contains=@fsTypeCluster,@fsGeneralCluster contained
-syn match fsOptions /\s\+.\{-}\s/me=e-1 nextgroup=fsFreqPass contains=@fsOptionsCluster,@fsGeneralCluster contained
+syn match fsOptions /\s\+.\{-}\%(\s\|$\)/ nextgroup=fsFreqPass contains=@fsOptionsCluster,@fsGeneralCluster contained
syn match fsFreqPass /\s\+.\{-}$/ contains=@fsFreqPassCluster,@fsGeneralCluster contained
" Whole line comments
@@ -491,4 +491,4 @@ let b:current_syntax = "fstab"
let &cpo = s:cpo_save
unlet s:cpo_save
-" vim: ts=8 ft=vim
+" vim: ts=8 noet ft=vim
diff --git a/runtime/syntax/gdb.vim b/runtime/syntax/gdb.vim
index c820ba40a9..c15b96de6f 100644
--- a/runtime/syntax/gdb.vim
+++ b/runtime/syntax/gdb.vim
@@ -30,7 +30,7 @@ syn keyword gdbStatement contained search section set sharedlibrary shell show s
syn keyword gdbStatement contained stop target tbreak tdump tfind thbreak thread tp trace tstart tstatus tstop
syn keyword gdbStatement contained tty und[isplay] unset until up watch whatis where while ws x
syn match gdbFuncDef "\<define\>.*"
-syn match gdbStatmentContainer "^\s*\S\+" contains=gdbStatement,gdbFuncDef
+syn match gdbStatementContainer "^\s*\S\+" contains=gdbStatement,gdbFuncDef
syn match gdbStatement "^\s*info" nextgroup=gdbInfo skipwhite skipempty
" some commonly used abbreviations
diff --git a/runtime/syntax/go.vim b/runtime/syntax/go.vim
index 904c8ad7f2..4272e807f3 100644
--- a/runtime/syntax/go.vim
+++ b/runtime/syntax/go.vim
@@ -5,7 +5,7 @@
" go.vim: Vim syntax file for Go.
" Language: Go
" Maintainer: Billie Cleek <bhcleek@gmail.com>
-" Latest Revision: 2022-11-17
+" Latest Revision: 2023-08-21
" License: BSD-style. See LICENSE file in source repository.
" Repository: https://github.com/fatih/vim-go
@@ -130,14 +130,17 @@ hi def link goFloats Type
hi def link goComplexes Type
" Predefined functions and values
-syn keyword goBuiltins append cap close complex copy delete imag len
-syn keyword goBuiltins make new panic print println real recover
+syn keyword goBuiltins append cap clear close complex copy delete imag len
+syn keyword goBuiltins make max min new panic print println real recover
syn keyword goBoolean true false
syn keyword goPredefinedIdentifiers nil iota
hi def link goBuiltins Identifier
+hi def link goPredefinedIdentifiers Constant
+" Boolean links to Constant by default by vim: goBoolean and goPredefinedIdentifiers
+" will be highlighted the same, but having the separate groups allows users to
+" have separate highlighting for them if they desire.
hi def link goBoolean Boolean
-hi def link goPredefinedIdentifiers goBoolean
" Comments; their contents
syn keyword goTodo contained TODO FIXME XXX BUG
diff --git a/runtime/syntax/gp.vim b/runtime/syntax/gp.vim
index aecf7df48b..89f2d3f0ff 100644
--- a/runtime/syntax/gp.vim
+++ b/runtime/syntax/gp.vim
@@ -1,7 +1,7 @@
" Vim syntax file
-" Language: gp (version 2.5)
+" Language: gp (version 2.15)
" Maintainer: Karim Belabas <Karim.Belabas@math.u-bordeaux.fr>
-" Last change: 2012 Jan 08
+" Last change: 2023 Aug 22
" URL: http://pari.math.u-bordeaux.fr
" quit when a syntax file was already loaded
@@ -14,23 +14,29 @@ set cpo&vim
" control statements
syntax keyword gpStatement break return next
-syntax keyword gpConditional if
-syntax keyword gpRepeat until while for fordiv forell forprime
-syntax keyword gpRepeat forsubgroup forstep forvec
+syntax keyword gpConditional if iferr
+syntax keyword gpRepeat until while for forcomposite fordiv
+syntax keyword gpRepeat fordivfactored foreach forell forfactored
+syntax keyword gpRepeat forpart forperm forprime forprimestep forqfvec
+syntax keyword gpRepeat forsquarefree forstep forsubgroup forsubset
+syntax keyword gpRepeat forvec
+syntax keyword gpRepeat parfor parforeach parforprime parforprimestep
+syntax keyword gpRepeat parforvec
" storage class
-syntax keyword gpScope my local global
+syntax keyword gpScope my local global export exportall
" defaults
syntax keyword gpInterfaceKey breakloop colors compatible
-syntax keyword gpInterfaceKey datadir debug debugfiles debugmem
-syntax keyword gpInterfaceKey echo factor_add_primes factor_proven format
+syntax keyword gpInterfaceKey datadir debug debugfiles debugmem
+syntax keyword gpInterfaceKey echo factor_add_primes factor_proven format
syntax keyword gpInterfaceKey graphcolormap graphcolors
-syntax keyword gpInterfaceKey help histfile histsize
-syntax keyword gpInterfaceKey lines linewrap log logfile new_galois_format
-syntax keyword gpInterfaceKey output parisize path prettyprinter primelimit
-syntax keyword gpInterfaceKey prompt prompt_cont psfile
-syntax keyword gpInterfaceKey readline realprecision recover
-syntax keyword gpInterfaceKey secure seriesprecision simplify strictmatch
-syntax keyword gpInterfaceKey TeXstyle timer
+syntax keyword gpInterfaceKey help histfile histsize
+syntax keyword gpInterfaceKey lines linewrap log logfile nbthreads
+syntax keyword gpInterfaceKey new_galois_format output parisize parisizemax
+syntax keyword gpInterfaceKey path plothsizes prettyprinter primelimit prompt
+syntax keyword gpInterfaceKey prompt_cont psfile readline realbitprecision
+syntax keyword gpInterfaceKey realprecision recover secure seriesprecision
+syntax keyword gpInterfaceKey simplify sopath strictmatch TeXstyle
+syntax keyword gpInterfaceKey threadsize threadsizemax timer
syntax match gpInterface "^\s*\\[a-z].*"
syntax keyword gpInterface default
@@ -58,24 +64,23 @@ syntax region gpParen transparent start='(' end=')' contains=ALLBUT,gpParenErro
syntax match gpParenError ")"
syntax match gpInParen contained "[{}]"
-
-hi def link gpConditional Conditional
+hi def link gpConditional Conditional
hi def link gpRepeat Repeat
hi def link gpError Error
-hi def link gpParenError gpError
+hi def link gpParenError gpError
hi def link gpInParen gpError
hi def link gpStatement Statement
hi def link gpString String
hi def link gpComment Comment
hi def link gpInterface Type
hi def link gpInput Type
-hi def link gpInterfaceKey Statement
+hi def link gpInterfaceKey Statement
hi def link gpFunction Function
hi def link gpScope Type
" contained ones
hi def link gpSpecial Special
-hi def link gpTodo Todo
-hi def link gpArgs Type
+hi def link gpTodo Todo
+hi def link gpArgs Type
let b:current_syntax = "gp"
let &cpo = s:cpo_save
diff --git a/runtime/syntax/gpg.vim b/runtime/syntax/gpg.vim
index 46e2099994..c7f3584ff0 100644
--- a/runtime/syntax/gpg.vim
+++ b/runtime/syntax/gpg.vim
@@ -1,7 +1,9 @@
" Vim syntax file
-" Language: gpg(1) configuration file
-" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
-" Latest Revision: 2010-10-14
+" Language: gpg(1) configuration file
+" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
+" Latest Revision: 2010-10-14
+" Updated: 2023-01-23 @ObserverOfTime: added a couple of keywords
+" 2023-03-21 Todd Zullinger <tmz@pobox.com>: sync with gnupg-2.4.0
if exists("b:current_syntax")
finish
@@ -12,92 +14,165 @@ set cpo&vim
setlocal iskeyword+=-
-syn keyword gpgTodo contained FIXME TODO XXX NOTE
+syn keyword gpgTodo contained FIXME TODO XXX NOTE
-syn region gpgComment contained display oneline start='#' end='$'
- \ contains=gpgTodo,gpgID,@Spell
+syn region gpgComment contained display oneline start='#' end='$'
+ \ contains=gpgTodo,gpgID,@Spell
-syn match gpgID contained display '\<\(0x\)\=\x\{8,}\>'
+syn match gpgID contained display '\<\(0x\)\=\x\{8,}\>'
-syn match gpgBegin display '^' skipwhite nextgroup=gpgComment,gpgOption,gpgCommand
+syn match gpgBegin display '^' skipwhite nextgroup=gpgComment,gpgOption,gpgCommand
-syn keyword gpgCommand contained skipwhite nextgroup=gpgArg
- \ check-sigs decrypt decrypt-files delete-key
- \ delete-secret-and-public-key delete-secret-key
- \ edit-key encrypt-files export export-all
- \ export-ownertrust export-secret-keys
- \ export-secret-subkeys fast-import fingerprint
- \ gen-prime gen-random import import-ownertrust
- \ list-keys list-public-keys list-secret-keys
- \ list-sigs lsign-key nrsign-key print-md print-mds
- \ recv-keys search-keys send-keys sign-key verify
- \ verify-files
-syn keyword gpgCommand contained skipwhite nextgroup=gpgArgError
- \ check-trustdb clearsign desig-revoke detach-sign
- \ encrypt gen-key gen-revoke help list-packets
- \ rebuild-keydb-caches sign store symmetric
- \ update-trustdb version warranty
+syn keyword gpgCommand contained skipwhite nextgroup=gpgArg
+ \ change-passphrase check-sig check-signatures
+ \ check-sigs delete-keys delete-secret-and-public-keys
+ \ delete-secret-keys desig-revoke export
+ \ export-secret-keys export-secret-ssh-key
+ \ export-secret-subkeys export-ssh-key list-key
+ \ list-keys list-packets list-public-keys
+ \ list-secret-keys list-sig list-signatures list-sigs
+ \ passwd send-keys fetch-keys
+ \ generate-designated-revocation generate-revocation
+ \ gen-prime gen-random gen-revoke locate-external-keys
+ \ locate-keys lsign-key options print-md quick-add-key
+ \ quick-addkey quick-add-uid quick-adduid
+ \ quick-generate-key quick-gen-key quick-lsign-key
+ \ quick-revoke-sig quick-revoke-uid quick-revuid
+ \ quick-set-expire quick-set-primary-uid quick-sign-key
+ \ quick-update-pref receive-keys recv-keys refresh-keys
+ \ search-keys show-key show-keys sign-key tofu-policy
+syn keyword gpgCommand contained skipwhite nextgroup=gpgArgError
+ \ card-edit card-status change-pin check-trustdb
+ \ clear-sign clearsign dearmor dearmour decrypt
+ \ decrypt-files detach-sign encrypt encrypt-files
+ \ edit-card edit-key enarmor enarmour export-ownertrust
+ \ fast-import import import-ownertrust key-edit
+ \ fingerprint fix-trustdb full-generate-key
+ \ full-gen-key generate-key gen-key gpgconf-list
+ \ gpgconf-test list-config list-gcrypt-config
+ \ list-trustdb no-options print-mds
+ \ rebuild-keydb-caches server sign store symmetric
+ \ update-trustdb verify verify-files
-syn keyword gpgOption contained skipwhite nextgroup=gpgArg
- \ attribute-fd cert-digest-algo charset cipher-algo
- \ command-fd comment completes-needed compress
- \ compress-algo debug default-cert-check-level
- \ default-key default-preference-list
- \ default-recipient digest-algo disable-cipher-algo
- \ disable-pubkey-algo encrypt-to exec-path
- \ export-options group homedir import-options
- \ keyring keyserver keyserver-options load-extension
- \ local-user logger-fd marginals-needed max-cert-depth
- \ notation-data options output override-session-key
- \ passphrase-fd personal-cipher-preferences
- \ personal-compress-preferences
- \ personal-digest-preferences photo-viewer
- \ recipient s2k-cipher-algo s2k-digest-algo s2k-mode
- \ secret-keyring set-filename set-policy-url status-fd
- \ trusted-key verify-options keyid-format list-options
-syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
- \ allow-freeform-uid allow-non-selfsigned-uid
- \ allow-secret-key-import always-trust
- \ armor ask-cert-expire ask-sig-expire
- \ auto-check-trustdb batch debug-all default-comment
- \ default-recipient-self dry-run emit-version
- \ emulate-md-encode-bug enable-special-filenames
- \ escape-from-lines expert fast-list-mode
- \ fixed-list-mode for-your-eyes-only
- \ force-mdc force-v3-sigs force-v4-certs
- \ gpg-agent-info ignore-crc-error ignore-mdc-error
- \ ignore-time-conflict ignore-valid-from interactive
- \ list-only lock-multiple lock-never lock-once
- \ merge-only no no-allow-non-selfsigned-uid
- \ no-armor no-ask-cert-expire no-ask-sig-expire
- \ no-auto-check-trustdb no-batch no-comment
- \ no-default-keyring no-default-recipient
- \ no-encrypt-to no-expensive-trust-checks
- \ no-expert no-for-your-eyes-only no-force-v3-sigs
- \ no-force-v4-certs no-greeting no-literal
- \ no-mdc-warning no-options no-permission-warning
- \ no-pgp2 no-pgp6 no-pgp7 no-random-seed-file
- \ no-secmem-warning no-show-notation no-show-photos
- \ no-show-policy-url no-sig-cache no-sig-create-check
- \ no-sk-comments no-tty no-utf8-strings no-verbose
- \ no-version not-dash-escaped openpgp pgp2
- \ pgp6 pgp7 preserve-permissions quiet rfc1991
- \ set-filesize show-keyring show-notation show-photos
- \ show-policy-url show-session-key simple-sk-checksum
- \ sk-comments skip-verify textmode throw-keyid
- \ try-all-secrets use-agent use-embedded-filename
- \ utf8-strings verbose with-colons with-fingerprint
- \ with-key-data yes
+syn keyword gpgOption contained skipwhite nextgroup=gpgArg
+ \ aead-algo agent-program attribute-fd attribute-file
+ \ auto-key-locate bzip2-compress-level cert-digest-algo
+ \ cert-notation cert-policy-url charset chuid
+ \ chunk-size cipher-algo command-fd command-file
+ \ comment compatibility-flags completes-needed
+ \ compliance compress-algo compression-algo
+ \ compress-level ctapi-driver debug
+ \ debug-allow-large-chunks debug-level
+ \ debug-set-iobuf-size default-cert-check-level
+ \ default-cert-expire default-cert-level default-key
+ \ default-keyserver-url default-new-key-algo
+ \ default-preference-list default-recipient
+ \ default-sig-expire digest-algo dirmngr-program
+ \ disable-cipher-algo disable-pubkey-algo display
+ \ display-charset encrypt-to exec-path export-filter
+ \ export-options faked-system-time force-ownertrust
+ \ gpg-agent-info group hidden-encrypt-to
+ \ hidden-recipient hidden-recipient-file homedir
+ \ import-filter import-options input-size-hint
+ \ keyboxd-program keyid-format key-origin keyring
+ \ keyserver keyserver-options known-notation lc-ctype
+ \ lc-messages limit-card-insert-tries list-filter
+ \ list-options local-user log-file logger-fd
+ \ logger-file marginals-needed max-cert-depth
+ \ max-output min-cert-level min-rsa-length output
+ \ override-session-key override-session-key-fd
+ \ passphrase passphrase-fd passphrase-file
+ \ passphrase-repeat pcsc-driver
+ \ personal-aead-preferences personal-cipher-preferences
+ \ personal-cipher-prefs personal-compress-preferences
+ \ personal-compress-prefs personal-digest-preferences
+ \ photo-viewer pinentry-mode primary-keyring
+ \ reader-port recipient recipient-file remote-user
+ \ request-origin s2k-cipher-algo s2k-count
+ \ s2k-digest-algo s2k-mode secret-keyring sender
+ \ set-filename set-filesize set-notation set-policy-url
+ \ sig-keyserver-url sig-notation sign-with
+ \ sig-policy-url status-fd status-file temp-directory
+ \ tofu-db-format tofu-default-policy trustdb-name
+ \ trusted-key trust-model try-secret-key ttyname
+ \ ttytype ungroup user verify-options weak-digest
+ \ xauthority
+syn keyword gpgOption contained skipwhite nextgroup=gpgArgError
+ \ allow-freeform-uid allow-multiple-messages
+ \ allow-multisig-verification allow-non-selfsigned-uid
+ \ allow-old-cipher-algos allow-secret-key-import
+ \ allow-weak-digest-algos allow-weak-key-signatures
+ \ always-trust armor armour ask-cert-expire
+ \ ask-cert-level ask-sig-expire auto-check-trustdb
+ \ auto-key-import auto-key-retrieve batch
+ \ bzip2-decompress-lowmem compress-keys compress-sigs
+ \ debug-all debug-iolbf debug-quick-random
+ \ default-comment default-recipient-self disable-ccid
+ \ disable-dirmngr disable-dsa2 disable-large-rsa
+ \ disable-mdc disable-signer-uid dry-run dump-options
+ \ dump-option-table emit-version enable-dsa2
+ \ enable-large-rsa enable-progress-filter
+ \ enable-special-filenames encrypt-to-default-key
+ \ escape-from-lines exit-on-status-write-error expert
+ \ fast-list-mode file-is-digest fixed-list-mode
+ \ forbid-gen-key force-aead force-mdc force-ocb
+ \ force-sign-key force-v3-sigs force-v4-certs
+ \ for-your-eyes-only full-timestrings gnupg help
+ \ honor-http-proxy ignore-crc-error ignore-mdc-error
+ \ ignore-time-conflict ignore-valid-from
+ \ include-key-block interactive legacy-list-mode
+ \ list-only lock-multiple lock-never lock-once
+ \ mangle-dos-filenames merge-only mimemode multifile no
+ \ no-allow-freeform-uid no-allow-multiple-messages
+ \ no-allow-non-selfsigned-uid no-armor no-armour
+ \ no-ask-cert-expire no-ask-cert-level
+ \ no-ask-sig-expire no-auto-check-trustdb
+ \ no-auto-key-import no-auto-key-locate
+ \ no-auto-key-retrieve no-autostart
+ \ no-auto-trust-new-key no-batch no-comments
+ \ no-default-keyring no-default-recipient
+ \ no-disable-mdc no-emit-version no-encrypt-to
+ \ no-escape-from-lines no-expensive-trust-checks
+ \ no-expert no-force-mdc no-force-v3-sigs
+ \ no-force-v4-certs no-for-your-eyes-only no-greeting
+ \ no-groups no-include-key-block no-keyring no-literal
+ \ no-mangle-dos-filenames no-mdc-warning
+ \ no-permission-warning no-pgp2 no-pgp6 no-pgp7 no-pgp8
+ \ no-random-seed-file no-require-backsigs
+ \ no-require-cross-certification no-require-secmem
+ \ no-rfc2440-text no-secmem-warning no-show-notation
+ \ no-show-photos no-show-policy-url no-sig-cache
+ \ no-sk-comments no-skip-hidden-recipients
+ \ no-symkey-cache not-dash-escaped no-textmode
+ \ no-throw-keyids no-tty no-use-agent
+ \ no-use-embedded-filename no-utf8-strings no-verbose
+ \ no-version only-sign-text-ids openpgp
+ \ override-compliance-check pgp6 pgp7 pgp8
+ \ preserve-permissions print-dane-records quiet
+ \ require-backsigs require-compliance
+ \ require-cross-certification require-secmem rfc2440
+ \ rfc2440-text rfc4880 rfc4880bis show-keyring
+ \ show-notation show-photos show-policy-url
+ \ show-session-key sk-comments skip-hidden-recipients
+ \ skip-verify textmode throw-keyids try-all-secrets
+ \ unwrap use-agent use-embedded-filename use-keyboxd
+ \ use-only-openpgp-card utf8-strings verbose version
+ \ warranty with-colons with-fingerprint
+ \ with-icao-spelling with-key-data with-keygrip
+ \ with-key-origin with-key-screening with-secret
+ \ with-sig-check with-sig-list with-subkey-fingerprint
+ \ with-subkey-fingerprints with-tofu-info with-wkd-hash
+ \ yes
-syn match gpgArg contained display '\S\+\(\s\+\S\+\)*' contains=gpgID
+syn match gpgArg contained display '\S\+\(\s\+\S\+\)*' contains=gpgID
syn match gpgArgError contained display '\S\+\(\s\+\S\+\)*'
-hi def link gpgComment Comment
-hi def link gpgTodo Todo
-hi def link gpgID Number
-hi def link gpgOption Keyword
-hi def link gpgCommand Error
-hi def link gpgArgError Error
+hi def link gpgComment Comment
+hi def link gpgTodo Todo
+hi def link gpgID Number
+hi def link gpgOption Keyword
+hi def link gpgCommand Error
+hi def link gpgArgError Error
let b:current_syntax = "gpg"
diff --git a/runtime/syntax/groovy.vim b/runtime/syntax/groovy.vim
index 41495e6682..e48279bd1a 100644
--- a/runtime/syntax/groovy.vim
+++ b/runtime/syntax/groovy.vim
@@ -362,7 +362,7 @@ exec "syn sync ccomment groovyComment minlines=" . groovy_minlines
" Mark these as operators
-" Hightlight brackets
+" Highlight brackets
" syn match groovyBraces "[{}]"
" syn match groovyBraces "[\[\]]"
" syn match groovyBraces "[\|]"
diff --git a/runtime/syntax/haskell.vim b/runtime/syntax/haskell.vim
index 1b70b9344a..b48b278084 100644
--- a/runtime/syntax/haskell.vim
+++ b/runtime/syntax/haskell.vim
@@ -108,6 +108,8 @@ syn match hsLineComment "---*\([^-!#$%&\*\+./<=>\?@\\^|~].*\)\?$" contain
syn region hsBlockComment start="{-" end="-}" contains=hsBlockComment,@Spell
syn region hsPragma start="{-#" end="#-}"
+syn keyword hsTodo contained FIXME TODO XXX NOTE
+
" C Preprocessor directives. Shamelessly ripped from c.vim and trimmed
" First, see whether to flag directive-like lines or not
if (!exists("hs_allow_hash_operator"))
diff --git a/runtime/syntax/help.vim b/runtime/syntax/help.vim
index 8b469d7242..f1e650b2fb 100644
--- a/runtime/syntax/help.vim
+++ b/runtime/syntax/help.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Vim help file
-" Maintainer: Bram Moolenaar (Bram@vim.org)
-" Last Change: 2022 Nov 13
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/hollywood.vim b/runtime/syntax/hollywood.vim
index fcd03a68f0..7846d5230f 100644
--- a/runtime/syntax/hollywood.vim
+++ b/runtime/syntax/hollywood.vim
@@ -1,8 +1,8 @@
" Vim syntax file
-" Language: Hollywood 9.1
+" Language: Hollywood 10.0
" Maintainer: Ola Sder <rolfkopman@gmail.com>
" First Author: Tom Crecelius <holly@net-eclipse.net>
-" Last Change: 2022 Nov 09
+" Last Change: 2023 Mar 22
" Highlighting Issues:
" Depending on your colour schema, Strings or Comments might be highlighted in
" a way, you don't like. If so, try one of the following settings after
@@ -105,7 +105,7 @@ syn keyword hwIn contained In
syn keyword hwStatement Return Break Continue
syn keyword hwStatement Goto Gosub Dim DimStr Const Local Global
syn match hwLabel "::\I\i*::"
-syn match hwOperator "\%(&\|\*\|+\|-\|\.\||\|//\|/\|:\|<\|=\|>\|<>\|<=\|=>\|\^\|\~\|\\\|\<And\>\|\<Not\>\|\<Or\>\)"
+syn match hwOperator "\%(&\|\*\|+\|-\|\.\||\|//\|/\|:\|<\|=\|>\|<>\|<=\|=>\|\^\|\~\|\\\|\<And\>\|\<Not\>\|\<Or\>\|\<Xor\>\)"
syn keyword hwConstant Nil
syn keyword hwConstant True False
" predefined preprocessing commands
@@ -113,7 +113,7 @@ syn match hwPreProcessor "@\<\%(ANIM\|APPAUTHOR\|APPCOPYRIGHT\|APPDESCRIPTION\|A
" predefined constants
syn match hwConstant "#\<\%(ACTIVEWINDOW\|ADF_ANIM\|ADF_FX\|ADF_MOVEOBJECT\|ALL\|ALPHABETICAL\|ALPHACHANNEL\|ALPHANUMERICAL\|AMIGAICON_DEVICE\|AMIGAICON_DISK\|AMIGAICON_DRAWER\|AMIGAICON_GARBAGE\|AMIGAICON_HIDE\|AMIGAICON_KICKSTART\|AMIGAICON_NONE\|AMIGAICON_PROJECT\|AMIGAICON_SETPOSITION\|AMIGAICON_SETTITLE\|AMIGAICON_SHOW\|AMIGAICON_TOOL\|ANIM\|ANIMSTREAM\|ANIMTYPE_RASTER\|ANIMTYPE_VECTOR\|ANMFMT_GIF\|ANMFMT_IFF\|ANMFMT_MJPEG\|ANTIALIAS\|AQUA\|ARC\|ASYNCDRAW\|ASYNCOBJ\|ATTRACTIVE\|ATTRADAPTER\|ATTRALPHAINTENSITY\|ATTRBGPIC\|ATTRBITRATE\|ATTRBORDERBOTTOM\|ATTRBORDERLEFT\|ATTRBORDERLESS\|ATTRBORDERPEN\|ATTRBORDERRIGHT\|ATTRBORDERTOP\|ATTRBULLETPEN\|ATTRCANSEEK\|ATTRCLIPREGION\|ATTRCOUNT\|ATTRCURFRAME\|ATTRCURSORX\|ATTRCURSORY\|ATTRCURSUBSONG\|ATTRCYCLE\|ATTRDENSITY\|ATTRDEPTH\|ATTRDISPLAY\|ATTRDITHERMODE\|ATTRDOUBLEBUFFER\|ATTRDRIVER\|ATTRDURATION\|ATTRELAPSE\|ATTRENCODING\|ATTRFIXED\|ATTRFONTAA\|ATTRFONTASCENDER\|ATTRFONTCHARMAP\|ATTRFONTDEPTH\|ATTRFONTDESCENDER\|ATTRFONTENGINE\|ATTRFONTNAME\|ATTRFONTPALETTE\|ATTRFONTSCALABLE\|ATTRFONTSIZE\|ATTRFONTTRANSPARENTPEN\|ATTRFONTTYPE\|ATTRFORMAT\|ATTRFRAMEDELAY\|ATTRFUNCTION\|ATTRGROUP\|ATTRHARDWARE\|ATTRHASALPHA\|ATTRHASMASK\|ATTRHEIGHT\|ATTRHOSTDEPTH\|ATTRHOSTHEIGHT\|ATTRHOSTMONITORS\|ATTRHOSTSCALE\|ATTRHOSTSCALEX\|ATTRHOSTSCALEY\|ATTRHOSTTASKBAR\|ATTRHOSTTITLEBARHEIGHT\|ATTRHOSTWIDTH\|ATTRID\|ATTRIMMERSIVEMODE\|ATTRINTERPOLATE\|ATTRKEYBOARD\|ATTRLAYERID\|ATTRLAYERS\|ATTRLAYERSON\|ATTRLOADER\|ATTRMARGINLEFT\|ATTRMARGINRIGHT\|ATTRMASKMODE\|ATTRMAXHEIGHT\|ATTRMAXIMIZED\|ATTRMAXWIDTH\|ATTRMENU\|ATTRMODE\|ATTRMONITOR\|ATTRNOCLOSE\|ATTRNOHIDE\|ATTRNOMODESWITCH\|ATTRNUMENTRIES\|ATTRNUMFRAMES\|ATTRNUMSUBSONGS\|ATTRONSCREEN\|ATTRORIENTATION\|ATTROUTPUTDEVICE\|ATTRPALETTE\|ATTRPALETTEMODE\|ATTRPAUSED\|ATTRPEN\|ATTRPITCH\|ATTRPLAYING\|ATTRPOINTER\|ATTRPOSITION\|ATTRPUBSCREEN\|ATTRRAWHEIGHT\|ATTRRAWWIDTH\|ATTRRAWXPOS\|ATTRRAWYPOS\|ATTRSCALEHEIGHT\|ATTRSCALEMODE\|ATTRSCALESWITCH\|ATTRSCALEWIDTH\|ATTRSHADOWPEN\|ATTRSIZE\|ATTRSIZEABLE\|ATTRSPRITES\|ATTRSTANDARD\|ATTRSTATE\|ATTRSYSTEMBARS\|ATTRTEXT\|ATTRTITLE\|ATTRTRANSPARENTCOLOR\|ATTRTRANSPARENTPEN\|ATTRTYPE\|ATTRUSERDATA\|ATTRVISIBLE\|ATTRWIDTH\|ATTRXDPI\|ATTRXPOS\|ATTRXSERVER\|ATTRYDPI\|ATTRYPOS\|ATTRZPOS\|BARS\|BAUD_115200\|BAUD_1200\|BAUD_19200\|BAUD_2400\|BAUD_300\|BAUD_38400\|BAUD_460800\|BAUD_4800\|BAUD_57600\|BAUD_600\|BAUD_9600\|BEEPERROR\|BEEPINFORMATION\|BEEPQUESTION\|BEEPSYSTEM\|BEEPWARNING\|BGPIC\|BGPICPART\|BIGENDIAN\|BIGSINE\|BITMAP_DEFAULT\|BLACK\|BLEND\|BLUE\|BOLD\|BOOLEAN\|BORDER\|BOTTOM\|BOTTOMOUT\|BOUNCE\|BOX\|BRUSH\|BRUSH_VS_BOX\|BRUSHPART\|BULLET_ARROW\|BULLET_BOX\|BULLET_CHECKMARK\|BULLET_CIRCLE\|BULLET_CROSS\|BULLET_DASH\|BULLET_DIAMOND\|BULLET_LALPHA\|BULLET_LALPHADOUBLE\|BULLET_LALPHASINGLE\|BULLET_LROMAN\|BULLET_LROMANDOUBLE\|BULLET_LROMANSINGLE\|BULLET_NONE\|BULLET_NUMERIC\|BULLET_NUMERICDOUBLE\|BULLET_NUMERICSINGLE\|BULLET_UALPHA\|BULLET_UALPHADOUBLE\|BULLET_UALPHASINGLE\|BULLET_UROMAN\|BULLET_UROMANDOUBLE\|BULLET_UROMANSINGLE\|BYTE\|CAPBUTT\|CAPROUND\|CAPSQUARE\|CARDBOTTOM\|CARDTOP\|CENTER\|CHARMAP_ADOBECUSTOM\|CHARMAP_ADOBEEXPERT\|CHARMAP_ADOBELATIN1\|CHARMAP_ADOBESTANDARD\|CHARMAP_APPLEROMAN\|CHARMAP_BIG5\|CHARMAP_DEFAULT\|CHARMAP_JOHAB\|CHARMAP_MSSYMBOL\|CHARMAP_OLDLATIN2\|CHARMAP_SJIS\|CHARMAP_UNICODE\|CHARMAP_WANSUNG\|CHIPMEMORY\|CIRCLE\|CLIENT\|CLIPBOARD_EMPTY\|CLIPBOARD_IMAGE\|CLIPBOARD_SOUND\|CLIPBOARD_TEXT\|CLIPBOARD_UNKNOWN\|CLIPREGION\|CLOCKWIPE\|CLOSEWINDOW\|CONICAL\|COPYFILE_FAILED\|COPYFILE_OVERWRITE\|COPYFILE_STATUS\|COPYFILE_UNPROTECT\|COUNTBOTH\|COUNTDIRECTORIES\|COUNTFILES\|COUNTRY_AFGHANISTAN\|COUNTRY_ALANDISLANDS\|COUNTRY_ALBANIA\|COUNTRY_ALGERIA\|COUNTRY_AMERICANSAMOA\|COUNTRY_ANDORRA\|COUNTRY_ANGOLA\|COUNTRY_ANGUILLA\|COUNTRY_ANTARCTICA\|COUNTRY_ANTIGUAANDBARBUDA\|COUNTRY_ARGENTINA\|COUNTRY_ARMENIA\|COUNTRY_ARUBA\|COUNTRY_AUSTRALIA\|COUNTRY_AUSTRIA\|COUNTRY_AZERBAIJAN\|COUNTRY_BAHAMAS\|COUNTRY_BAHRAIN\|COUNTRY_BANGLADESH\|COUNTRY_BARBADOS\|COUNTRY_BELARUS\|COUNTRY_BELGIUM\|COUNTRY_BELIZE\|COUNTRY_BENIN\|COUNTRY_BERMUDA\|COUNTRY_BESISLANDS\|COUNTRY_BHUTAN\|COUNTRY_BOLIVIA\|COUNTRY_BOSNIAANDHERZEGOVINA\|COUNTRY_BOTSWANA\|COUNTRY_BOUVETISLAND\|COUNTRY_BRAZIL\|COUNTRY_BRUNEI\|COUNTRY_BULGARIA\|COUNTRY_BURKINAFASO\|COUNTRY_BURUNDI\|COUNTRY_CAMBODIA\|COUNTRY_CAMEROON\|COUNTRY_CANADA\|COUNTRY_CAPEVERDE\|COUNTRY_CAYMANISLANDS\|COUNTRY_CENTRALAFRICANREPUBLIC\|COUNTRY_CHAD\|COUNTRY_CHILE\|COUNTRY_CHINA\|COUNTRY_CHRISTMASISLAND\|COUNTRY_COCOSISLANDS\|COUNTRY_COLOMBIA\|COUNTRY_COMOROS\|COUNTRY_CONGO\|COUNTRY_COOKISLANDS\|COUNTRY_COSTARICA\|COUNTRY_CROATIA\|COUNTRY_CUBA\|COUNTRY_CURACAO\|COUNTRY_CYPRUS\|COUNTRY_CZECHREPUBLIC\|COUNTRY_DENMARK\|COUNTRY_DJIBOUTI\|COUNTRY_DOMINICA\|COUNTRY_DOMINICANREPUBLIC\|COUNTRY_DRCONGO\|COUNTRY_ECUADOR\|COUNTRY_EGYPT\|COUNTRY_ELSALVADOR\|COUNTRY_EQUATORIALGUINEA\|COUNTRY_ERITREA\|COUNTRY_ESTONIA\|COUNTRY_ETHIOPIA\|COUNTRY_FALKLANDISLANDS\|COUNTRY_FAROEISLANDS\|COUNTRY_FIJI\|COUNTRY_FINLAND\|COUNTRY_FRANCE\|COUNTRY_FRENCHGUIANA\|COUNTRY_FRENCHPOLYNESIA\|COUNTRY_GABON\|COUNTRY_GAMBIA\|COUNTRY_GEORGIA\|COUNTRY_GERMANY\|COUNTRY_GHANA\|COUNTRY_GIBRALTAR\|COUNTRY_GREECE\|COUNTRY_GREENLAND\|COUNTRY_GRENADA\|COUNTRY_GUADELOUPE\|COUNTRY_GUAM\|COUNTRY_GUATEMALA\|COUNTRY_GUERNSEY\|COUNTRY_GUINEA\|COUNTRY_GUINEABISSAU\|COUNTRY_GUYANA\|COUNTRY_HAITI\|COUNTRY_HOLYSEE\|COUNTRY_HONDURAS\|COUNTRY_HONGKONG\|COUNTRY_HUNGARY\|COUNTRY_ICELAND\|COUNTRY_INDIA\|COUNTRY_INDONESIA\|COUNTRY_IRAN\|COUNTRY_IRAQ\|COUNTRY_IRELAND\|COUNTRY_ISLEOFMAN\|COUNTRY_ISRAEL\|COUNTRY_ITALY\|COUNTRY_IVORYCOAST\|COUNTRY_JAMAICA\|COUNTRY_JAPAN\|COUNTRY_JERSEY\|COUNTRY_JORDAN\|COUNTRY_KAZAKHSTAN\|COUNTRY_KENYA\|COUNTRY_KIRIBATI\|COUNTRY_KUWAIT\|COUNTRY_KYRGYZSTAN\|COUNTRY_LAOS\|COUNTRY_LATVIA\|COUNTRY_LEBANON\|COUNTRY_LESOTHO\|COUNTRY_LIBERIA\|COUNTRY_LIBYA\|COUNTRY_LIECHTENSTEIN\|COUNTRY_LITHUANIA\|COUNTRY_LUXEMBOURG\|COUNTRY_MACAO\|COUNTRY_MACEDONIA\|COUNTRY_MADAGASCAR\|COUNTRY_MALAWI\|COUNTRY_MALAYSIA\|COUNTRY_MALDIVES\|COUNTRY_MALI\|COUNTRY_MALTA\|COUNTRY_MARSHALLISLANDS\|COUNTRY_MARTINIQUE\|COUNTRY_MAURITANIA\|COUNTRY_MAURITIUS\|COUNTRY_MAYOTTE\|COUNTRY_MEXICO\|COUNTRY_MICRONESIA\|COUNTRY_MOLDOVA\|COUNTRY_MONACO\|COUNTRY_MONGOLIA\|COUNTRY_MONTENEGRO\|COUNTRY_MONTSERRAT\|COUNTRY_MOROCCO\|COUNTRY_MOZAMBIQUE\|COUNTRY_MYANMAR\|COUNTRY_NAMIBIA\|COUNTRY_NAURU\|COUNTRY_NEPAL\|COUNTRY_NETHERLANDS\|COUNTRY_NEWCALEDONIA\|COUNTRY_NEWZEALAND\|COUNTRY_NICARAGUA\|COUNTRY_NIGER\|COUNTRY_NIGERIA\|COUNTRY_NIUE\|COUNTRY_NORFOLKISLAND\|COUNTRY_NORTHKOREA\|COUNTRY_NORWAY\|COUNTRY_OMAN\|COUNTRY_PAKISTAN\|COUNTRY_PALAU\|COUNTRY_PALESTINE\|COUNTRY_PANAMA\|COUNTRY_PAPUANEWGUINEA\|COUNTRY_PARAGUAY\|COUNTRY_PERU\|COUNTRY_PHILIPPINES\|COUNTRY_PITCAIRN\|COUNTRY_POLAND\|COUNTRY_PORTUGAL\|COUNTRY_PUERTORICO\|COUNTRY_QATAR\|COUNTRY_REUNION\|COUNTRY_ROMANIA\|COUNTRY_RUSSIA\|COUNTRY_RWANDA\|COUNTRY_SAINTBARTHELEMY\|COUNTRY_SAINTHELENA\|COUNTRY_SAINTKITTSANDNEVIS\|COUNTRY_SAINTLUCIA\|COUNTRY_SAINTVINCENT\|COUNTRY_SAMOA\|COUNTRY_SANMARINO\|COUNTRY_SAOTOMEANDPRINCIPE\|COUNTRY_SAUDIARABIA\|COUNTRY_SENEGAL\|COUNTRY_SERBIA\|COUNTRY_SEYCHELLES\|COUNTRY_SIERRALEONE\|COUNTRY_SINGAPORE\|COUNTRY_SLOVAKIA\|COUNTRY_SLOVENIA\|COUNTRY_SOLOMONISLANDS\|COUNTRY_SOMALIA\|COUNTRY_SOUTHAFRICA\|COUNTRY_SOUTHKOREA\|COUNTRY_SOUTHSUDAN\|COUNTRY_SPAIN\|COUNTRY_SRILANKA\|COUNTRY_SUDAN\|COUNTRY_SURINAME\|COUNTRY_SWAZILAND\|COUNTRY_SWEDEN\|COUNTRY_SWITZERLAND\|COUNTRY_SYRIA\|COUNTRY_TAIWAN\|COUNTRY_TAJIKISTAN\|COUNTRY_TANZANIA\|COUNTRY_THAILAND\|COUNTRY_TIMOR\|COUNTRY_TOGO\|COUNTRY_TONGA\|COUNTRY_TRINIDADANDTOBAGO\|COUNTRY_TUNISIA\|COUNTRY_TURKEY\|COUNTRY_TURKMENISTAN\|COUNTRY_TUVALU\|COUNTRY_UAE\|COUNTRY_UGANDA\|COUNTRY_UK\|COUNTRY_UKRAINE\|COUNTRY_UNKNOWN\|COUNTRY_URUGUAY\|COUNTRY_USA\|COUNTRY_UZBEKISTAN\|COUNTRY_VANUATU\|COUNTRY_VENEZUELA\|COUNTRY_VIETNAM\|COUNTRY_YEMEN\|COUNTRY_ZAMBIA\|COUNTSEPARATE\|CR_DEAD\|CR_RUNNING\|CR_SUSPENDED\|CROSSFADE\|CRUSHBOTTOM\|CRUSHLEFT\|CRUSHRIGHT\|CRUSHTOP\|DAMPED\|DATA_5\|DATA_6\|DATA_7\|DATA_8\|DATEDAY\|DATELOCAL\|DATELOCALNATIVE\|DATEMONTH\|DATETIME\|DATEUTC\|DATEYEAR\|DEFAULTICON\|DEFAULTSPEED\|DEINTERLACE_DEFAULT\|DEINTERLACE_DOUBLE\|DELETEFILE_FAILED\|DELETEFILE_STATUS\|DELETEFILE_UNPROTECT\|DENSITY_HIGH\|DENSITY_LOW\|DENSITY_MEDIUM\|DENSITY_NONE\|DIAGONAL\|DIRECTORY\|DIRMONITOR_ADD\|DIRMONITOR_CHANGE\|DIRMONITOR_REMOVE\|DISPLAY\|DISPMODE_ASK\|DISPMODE_FAKEFULLSCREEN\|DISPMODE_FULLSCREEN\|DISPMODE_FULLSCREENSCALE\|DISPMODE_MODEREQUESTER\|DISPMODE_MODESWITCH\|DISPMODE_SYSTEMSCALE\|DISPMODE_WINDOWED\|DISPSTATE_CLOSED\|DISPSTATE_MINIMIZED\|DISPSTATE_OPEN\|DISSOLVE\|DITHERMODE_FLOYDSTEINBERG\|DITHERMODE_NONE\|DOSTYPE_DIRECTORY\|DOSTYPE_FILE\|DOUBLE\|DOUBLEBUFFER\|DOWNLOADFILE_STATUS\|DTR_OFF\|DTR_ON\|DURATION_LONG\|DURATION_SHORT\|EDGE\|ELLIPSE\|ENCODING_AMIGA\|ENCODING_ISO8859_1\|ENCODING_RAW\|ENCODING_UTF8\|EOF\|ERR_8OR16BITONLY\|ERR_ACCELERATOR\|ERR_ADDAPPICON\|ERR_ADDAPPWIN\|ERR_ADDSYSEVENT\|ERR_ADDTASK\|ERR_ADFFREEDISP\|ERR_ADFWRONGDISP\|ERR_AFILEPROP\|ERR_AHI\|ERR_ALLOCALPHA\|ERR_ALLOCCHANNEL\|ERR_ALLOCCHUNKY\|ERR_ALLOCMASK\|ERR_ALRDYDECLRD\|ERR_ALREADYASYNC\|ERR_ALSAPCM\|ERR_AMIGAGUIDE\|ERR_ANIMDISK\|ERR_ANIMFRAME\|ERR_ANTIALIAS\|ERR_APPLET\|ERR_APPLETVERSION\|ERR_APPLICATION\|ERR_ARGS\|ERR_ARRAYDECLA\|ERR_ASSERTFAILED\|ERR_ATSUI\|ERR_AUDIOCONVERTER\|ERR_BACKFILL\|ERR_BAD8SVX\|ERR_BADBASE64\|ERR_BADBYTECODE\|ERR_BADCALLBACKRET\|ERR_BADCONSTANT\|ERR_BADDIMENSIONS\|ERR_BADENCODING\|ERR_BADINTEGER\|ERR_BADIP\|ERR_BADLAYERTYPE\|ERR_BADPLATFORM\|ERR_BADSIGNATURE\|ERR_BADUPVALUES\|ERR_BADURL\|ERR_BADWAVE\|ERR_BADYIELD\|ERR_BEGINREFRESH\|ERR_BGPICBUTTON\|ERR_BGPICPALETTE\|ERR_BGPICTYPE\|ERR_BITMAP\|ERR_BLKWOENDBLK\|ERR_BRACECLOSE\|ERR_BRACEOPEN\|ERR_BRACKETCLOSE\|ERR_BRACKETOPEN\|ERR_BRUSHLINK\|ERR_BRUSHSIZE\|ERR_BRUSHTYPE\|ERR_CACHEERROR\|ERR_CASECST\|ERR_CHANGEDIR\|ERR_CHANNELRANGE\|ERR_CHRCSTEMPTY\|ERR_CHRCSTLEN\|ERR_CLIPFORMAT\|ERR_CLIPOPEN\|ERR_CLIPREAD\|ERR_CLIPWRITE\|ERR_CLOSEDDISPLAY\|ERR_CLOSEFILE\|ERR_CMDASVAR\|ERR_CMPUNSUPPORTED\|ERR_COLORSPACE\|ERR_COMMENTSTRUCT\|ERR_COMMODITY\|ERR_COMPLEXEXPR\|ERR_COMPLEXPATTERN\|ERR_COMPLEXWHILE\|ERR_CONCAT\|ERR_CONFIG\|ERR_CONFIG2\|ERR_CONITEMS\|ERR_CONSOLEARG\|ERR_CONTEXTMENU\|ERR_COORDSRANGE\|ERR_COREFOUNDATION\|ERR_CORETEXT\|ERR_CREATEDIR\|ERR_CREATEDOCKY\|ERR_CREATEEVENT\|ERR_CREATEGC\|ERR_CREATEICON\|ERR_CREATEMENU\|ERR_CREATEPORT\|ERR_CREATESHORTCUT\|ERR_CSTDOUBLEDEF\|ERR_CTRLSTRUCT\|ERR_CYIELD\|ERR_DATATYPEALPHA\|ERR_DATATYPESAVE\|ERR_DATATYPESAVE2\|ERR_DBLENCODING\|ERR_DBPALETTE\|ERR_DBTRANSWIN\|ERR_DBVIDEOLAYER\|ERR_DDAUTOSCALE\|ERR_DDMOBILE\|ERR_DDRECVIDEO\|ERR_DEADRESUME\|ERR_DEFFONT\|ERR_DELETEFILE\|ERR_DEMO\|ERR_DEMO2\|ERR_DEMO3\|ERR_DEPTHMISMATCH\|ERR_DEPTHRANGE\|ERR_DESERIALIZE\|ERR_DIFFDEPTH\|ERR_DIFFENCODING\|ERR_DINPUT\|ERR_DIRECTSHOW\|ERR_DIRLOCK\|ERR_DISPLAYADAPTERSUPPORT\|ERR_DISPLAYDESKTOP\|ERR_DISPLAYDESKTOPPAL\|ERR_DISPLAYSIZE\|ERR_DISPMINIMIZED\|ERR_DLOPEN\|ERR_DOUBLEDECLA\|ERR_DOUBLEMENU\|ERR_DRAWPATH\|ERR_DSOUNDNOTIFY\|ERR_DSOUNDNOTIPOS\|ERR_DSOUNDPLAY\|ERR_ELSEIFAFTERELSE\|ERR_ELSETWICE\|ERR_ELSEWOIF\|ERR_EMPTYMENUTREE\|ERR_EMPTYOBJ\|ERR_EMPTYPATH\|ERR_EMPTYSCRIPT\|ERR_EMPTYTABLE\|ERR_ENDBLKWOBLK\|ERR_ENDDOUBLEBUFFER\|ERR_ENDFUNCWOFUNC\|ERR_ENDIFWOIF\|ERR_ENDSWCHWOSWCH\|ERR_ENDWITHWOWITH\|ERR_EQUALEXPECTED\|ERR_ERRORCALLED\|ERR_ESCREPLACE\|ERR_EVNTEXPCTED\|ERR_EXAMINE\|ERR_EXECUTE\|ERR_EXETYPE\|ERR_FGRABVIDSTATE\|ERR_FIELDINIT\|ERR_FILEEXIST\|ERR_FILEFORMAT\|ERR_FILENOTFOUND\|ERR_FILESIZE\|ERR_FINDACTIVITY\|ERR_FINDANIM\|ERR_FINDANIMSTREAM\|ERR_FINDAPPLICATION\|ERR_FINDARRAY\|ERR_FINDASYNCDRAW\|ERR_FINDASYNCOBJ\|ERR_FINDBGPIC\|ERR_FINDBRUSH\|ERR_FINDBUTTON\|ERR_FINDCLIENT\|ERR_FINDCLIPREGION\|ERR_FINDCST\|ERR_FINDDIR\|ERR_FINDDISPLAY\|ERR_FINDFILE\|ERR_FINDFONT\|ERR_FINDFONT2\|ERR_FINDICON\|ERR_FINDINTERVAL\|ERR_FINDLAYER\|ERR_FINDLAYERDATA\|ERR_FINDMEMBLK\|ERR_FINDMENU\|ERR_FINDMENUITEM\|ERR_FINDMONITOR\|ERR_FINDMOVE\|ERR_FINDMUSIC\|ERR_FINDOBJECTDATA\|ERR_FINDPALETTE\|ERR_FINDPATH\|ERR_FINDPLUGIN\|ERR_FINDPOINTER\|ERR_FINDPORT\|ERR_FINDSAMPLE\|ERR_FINDSELECTOR\|ERR_FINDSERIAL\|ERR_FINDSERVER\|ERR_FINDSPRITE\|ERR_FINDTEXTOBJECT\|ERR_FINDTIMEOUT\|ERR_FINDTIMER\|ERR_FINDUDPOBJECT\|ERR_FINDVIDEO\|ERR_FIRSTPREPROC\|ERR_FONTFORMAT\|ERR_FONTPATH\|ERR_FONTPATH2\|ERR_FORBIDMODAL\|ERR_FOREVERWOREPEAT\|ERR_FORWONEXT\|ERR_FRAMEGRABBER\|ERR_FREEABGPIC\|ERR_FREEADISPLAY\|ERR_FREECURPOINTER\|ERR_FT2\|ERR_FTPAUTH\|ERR_FTPERROR\|ERR_FULLSCREEN\|ERR_FUNCARGS\|ERR_FUNCDECLA\|ERR_FUNCEXPECTED\|ERR_FUNCJMP\|ERR_FUNCREMOVED\|ERR_FUNCTABLEARG\|ERR_FUNCWOENDFUNC\|ERR_GETDISKOBJ\|ERR_GETIFADDRS\|ERR_GETMONITORINFO\|ERR_GETSHORTCUT\|ERR_GRABSCREEN\|ERR_GROUPNAMEUSED\|ERR_GTK\|ERR_GUIGFX\|ERR_HEXPOINT\|ERR_HOSTNAME\|ERR_HTTPERROR\|ERR_HTTPTE\|ERR_HWBMCLOSEDISP\|ERR_HWBRUSH\|ERR_HWBRUSHFUNC\|ERR_HWDBFREEDISP\|ERR_ICONDIMS\|ERR_ICONENTRY\|ERR_ICONPARMS\|ERR_ICONSIZE\|ERR_ICONSTANDARD\|ERR_ICONVECTOR\|ERR_IFWOENDIF\|ERR_IMAGEERROR\|ERR_INCOMPATBRUSH\|ERR_INISYNTAX\|ERR_INITSERIAL\|ERR_INTERNAL\|ERR_INTERNAL1\|ERR_INTEXPECTED\|ERR_INVALIDDATE\|ERR_INVALIDUTF8\|ERR_INVALIDUTF8ARG\|ERR_INVCAPIDX\|ERR_INVINSERT\|ERR_INVNEXTKEY\|ERR_INVPATCAP\|ERR_INVREPLACE\|ERR_JAVA\|ERR_JAVAMETHOD\|ERR_JOYSTICK\|ERR_KEYFILE\|ERR_KEYNOTFOUND\|ERR_KEYWORD\|ERR_KICKSTART\|ERR_LABELDECLA\|ERR_LABELDOUBLE\|ERR_LABINFOR\|ERR_LABINFUNC\|ERR_LABINIF\|ERR_LABINWHILE\|ERR_LABMAINBLK\|ERR_LAYERRANGE\|ERR_LAYERSOFF\|ERR_LAYERSON\|ERR_LAYERSUPPORT\|ERR_LAYERSUPPORT2\|ERR_LAYERSWITCH\|ERR_LEGACYPTMOD\|ERR_LFSYNTAX\|ERR_LINKFONT\|ERR_LINKPLUGIN\|ERR_LOADFRAME\|ERR_LOADICON\|ERR_LOADPICTURE\|ERR_LOADPICTURE2\|ERR_LOADPLUGIN\|ERR_LOADSOUND\|ERR_LOADVIDEO\|ERR_LOCK\|ERR_LOCK2\|ERR_LOCKBMAP\|ERR_LOCKEDOBJ\|ERR_LOOPRANGE\|ERR_LOWFREQ\|ERR_MAGICKEY\|ERR_MALFORMPAT1\|ERR_MALFORMPAT2\|ERR_MASKNALPHA\|ERR_MAXLINES\|ERR_MAXLOCALS\|ERR_MAXPARAMS\|ERR_MAXUPVALS\|ERR_MEDIAFOUNDATION\|ERR_MEM\|ERR_MEMCODE\|ERR_MEMCST\|ERR_MEMRANGE\|ERR_MENUATTACHED\|ERR_MENUCOMPLEXITY\|ERR_MISSINGBRACKET\|ERR_MISSINGFIELD\|ERR_MISSINGOPBRACK\|ERR_MISSINGPARAMTR\|ERR_MISSINGSEPARTR\|ERR_MIXMUSMOD\|ERR_MOBILE\|ERR_MODIFYAANIM\|ERR_MODIFYABG\|ERR_MODIFYABGPIC\|ERR_MODIFYABR\|ERR_MODIFYPSMP\|ERR_MODIFYSPRITE\|ERR_MODIFYSPRITE2\|ERR_MONITORDIR\|ERR_MONITORFULLSCREEN\|ERR_MONITORRANGE\|ERR_MOVEFILE\|ERR_MSGPORT\|ERR_MULDISMOBILE\|ERR_MULTIBGPIC\|ERR_MULTIDISPLAYS\|ERR_MUSFMTSUPPORT\|ERR_MUSNOTPAUSED\|ERR_MUSNOTPLYNG\|ERR_MUSNOTPLYNG2\|ERR_MUSPAUSED\|ERR_MUSPLAYING\|ERR_NAMETOOLONG\|ERR_NAMEUSED\|ERR_NEEDAPPLICATION\|ERR_NEEDCOMPOSITE\|ERR_NEEDMORPHOS2\|ERR_NEEDOS41\|ERR_NEEDPALETTEIMAGE\|ERR_NEGCOORDS\|ERR_NEWHWPLUGIN\|ERR_NEXTWOFOR\|ERR_NOABSPATH\|ERR_NOACCESS\|ERR_NOALPHA\|ERR_NOANMLAYER\|ERR_NOAPPLET\|ERR_NOARGBVISUAL\|ERR_NOBLOCKBREAK\|ERR_NOCALLBACK\|ERR_NOCHANNEL\|ERR_NOCHAR\|ERR_NOCLIPREG\|ERR_NOCOLON\|ERR_NOCOMMA\|ERR_NOCOMPRESS\|ERR_NOCONSTANTS\|ERR_NOCONTEXTMENU\|ERR_NOCOORDCST\|ERR_NODIRPATTERN\|ERR_NODISLAYERS\|ERR_NODISPMODES\|ERR_NODOUBLEBUFFER\|ERR_NOFALLTHROUGH\|ERR_NOFILTERNAME\|ERR_NOFMBHANDLER\|ERR_NOFUNCTION\|ERR_NOHWFUNC\|ERR_NOJOYATPORT\|ERR_NOKEYWORDS\|ERR_NOLAYERS\|ERR_NOLOOP\|ERR_NOLOOPCONT\|ERR_NOMASKBRUSH\|ERR_NOMENU\|ERR_NOMIMEVIEWER\|ERR_NOMUSICCB\|ERR_NONE\|ERR_NONSUSPENDEDRESUME\|ERR_NOPALETTE\|ERR_NOPALETTEIMAGE\|ERR_NOPALETTEMODE\|ERR_NORETVAL\|ERR_NOREXX\|ERR_NOSPRITES\|ERR_NOTADIR\|ERR_NOTENOUGHPIXELS\|ERR_NOTIGER\|ERR_NOTPROTRACKER\|ERR_NOTRANSPARENCY\|ERR_NOTXTLAYER\|ERR_NUMBEREXPECTED\|ERR_NUMCALLBACK\|ERR_NUMCONCAT\|ERR_NUMEXPECTED\|ERR_NUMSTRCMP\|ERR_NUMTABLEARG\|ERR_OLDAPPLET\|ERR_OPENANIM\|ERR_OPENANIM2\|ERR_OPENAUDIO\|ERR_OPENFONT\|ERR_OPENLIB\|ERR_OPENSERIAL\|ERR_OPENSOCKET\|ERR_OPENSOUND\|ERR_OPENSOUND2\|ERR_OUTOFRANGE\|ERR_PAKFORMAT\|ERR_PALETTEFILL\|ERR_PALETTEMODE\|ERR_PALSCREEN\|ERR_PEERNAME\|ERR_PENRANGE\|ERR_PERCENTFORMAT\|ERR_PERCENTFORMATSTR\|ERR_PIPE\|ERR_PIXELFORMAT\|ERR_PIXELRANGE\|ERR_PLAYERCOMP\|ERR_PLAYVIDEO\|ERR_PLUGINARCH\|ERR_PLUGINDOUBLET\|ERR_PLUGINSUPPORT\|ERR_PLUGINSYMBOL\|ERR_PLUGINTYPE\|ERR_PLUGINVER\|ERR_POINTERFORMAT\|ERR_POINTERIMG\|ERR_PORTNOTAVAIL\|ERR_PREPROCSYM\|ERR_PROTMETATABLE\|ERR_PUBSCREEN\|ERR_QUICKTIME\|ERR_RADIOTOGGLEMENU\|ERR_RANDOMIZE\|ERR_READ\|ERR_READFILE\|ERR_READFUNC\|ERR_READONLY\|ERR_READRANGE\|ERR_READTABLE\|ERR_READVIDEOPIXELS\|ERR_RECVCLOSED\|ERR_RECVTIMEOUT\|ERR_RECVUNKNOWN\|ERR_REGCLASS\|ERR_REGISTRYREAD\|ERR_REGISTRYWRITE\|ERR_REMADLAYER\|ERR_RENAME\|ERR_RENDER\|ERR_RENDERADLAYER\|ERR_RENDERCALLBACK\|ERR_RENDERER\|ERR_REPEATWOUNTIL\|ERR_REQAUTH\|ERR_REQUIREFIELD\|ERR_REQUIREPLUGIN\|ERR_REQUIRETAGFMT\|ERR_RETWOGOSUB\|ERR_REVDWORD\|ERR_REWINDDIR\|ERR_REXXERR\|ERR_SATELLITE\|ERR_SATFREEDISP\|ERR_SAVEANIM\|ERR_SAVEICON\|ERR_SAVEIMAGE\|ERR_SAVEPNG\|ERR_SAVERALPHA\|ERR_SAVESAMPLE\|ERR_SCALEBGPIC\|ERR_SCREEN\|ERR_SCREENMODE\|ERR_SCREENSIZE\|ERR_SCRPIXFMT\|ERR_SEEK\|ERR_SEEKFILE\|ERR_SEEKFORMAT\|ERR_SEEKRANGE\|ERR_SELECTALPHACHANNEL\|ERR_SELECTANIM\|ERR_SELECTBG\|ERR_SELECTBGPIC\|ERR_SELECTBGPIC2\|ERR_SELECTBRUSH\|ERR_SELECTMASK\|ERR_SEMAPHORE\|ERR_SENDDATA\|ERR_SENDMESSAGE\|ERR_SENDTIMEOUT\|ERR_SENDUNKNOWN\|ERR_SERIALIO\|ERR_SERIALIZE\|ERR_SERIALIZETYPE\|ERR_SETADAPTER\|ERR_SETENV\|ERR_SETFILEATTR\|ERR_SETFILECOMMENT\|ERR_SETFILEDATE\|ERR_SETMENU\|ERR_SHORTIF\|ERR_SIGNAL\|ERR_SMODEALPHA\|ERR_SMPRANGE\|ERR_SOCKET\|ERR_SOCKNAME\|ERR_SOCKOPT\|ERR_SORTFUNC\|ERR_SPRITELINK\|ERR_SPRITEONSCREEN\|ERR_SPRITEONSCREEN2\|ERR_SQBRACKETCLOSE\|ERR_SQBRACKETOPEN\|ERR_STACK\|ERR_STAT\|ERR_STRCALLBACK\|ERR_STREAMASSAMPLE\|ERR_STREXPECTED\|ERR_STRINGCST\|ERR_STRINGEXPECTED\|ERR_STRORNUM\|ERR_STRTABLEARG\|ERR_STRTOOSHORT\|ERR_SURFACE\|ERR_SWCHWOENDSWCH\|ERR_SYNTAXERROR\|ERR_SYNTAXLEVELS\|ERR_SYSBUTTON\|ERR_SYSIMAGE\|ERR_SYSTOOOLD\|ERR_TABCALLBACK\|ERR_TABEXPECTED\|ERR_TABEXPECTED2\|ERR_TABEXPECTED3\|ERR_TABLEDECLA\|ERR_TABLEINDEX\|ERR_TABLEORNIL\|ERR_TABLEOVERFLOW\|ERR_TAGEXPECTED\|ERR_TASKSETUP\|ERR_TEXTARG\|ERR_TEXTCONVERT\|ERR_TEXTCONVERT2\|ERR_TEXTSYNTAX\|ERR_TEXTURE\|ERR_TFIMAGE\|ERR_TFVANIM\|ERR_TFVBGPICBRUSH\|ERR_TFVBRUSH\|ERR_TFVBRUSHBGPIC\|ERR_THREAD\|ERR_THREADEXPECTED\|ERR_TIMER\|ERR_TOKENEXPECTED\|ERR_TOOMANYARGS\|ERR_TOOMANYCAPTURES\|ERR_TOOSMALL2\|ERR_TRANSBGMOBILE\|ERR_TRANSBRUSH\|ERR_TRAYICON\|ERR_TRIALCOMPILE\|ERR_TRIALINCLUDE\|ERR_TRIALLIMIT\|ERR_TRIALSAVEVID\|ERR_UDEXPECTED\|ERR_UNBALANCEDPAT\|ERR_UNEXPECTEDEOF\|ERR_UNEXPECTEDSYM\|ERR_UNFINISHEDCAPTURE\|ERR_UNIMPLCMD\|ERR_UNKNOWN\|ERR_UNKNOWNANMOUT\|ERR_UNKNOWNATTR\|ERR_UNKNOWNCMD\|ERR_UNKNOWNCOND\|ERR_UNKNOWNFILTER\|ERR_UNKNOWNICNOUT\|ERR_UNKNOWNIMGOUT\|ERR_UNKNOWNMIMETYPE\|ERR_UNKNOWNMUSFMT\|ERR_UNKNOWNPALETTE\|ERR_UNKNOWNSEC\|ERR_UNKNOWNSEQ\|ERR_UNKNOWNSMPOUT\|ERR_UNKNOWNTAG\|ERR_UNKNUMFMT\|ERR_UNKPROTOCOL\|ERR_UNKTEXTFMT\|ERR_UNMPARENTHESES\|ERR_UNSETENV\|ERR_UNSUPPORTEDFEAT\|ERR_UNTERMINTDSTR\|ERR_UNTILWOREPEAT\|ERR_UPDATEICON\|ERR_UPLOADFORBIDDEN\|ERR_USERABORT\|ERR_VALUEEXPECTED\|ERR_VAREXPECTED\|ERR_VARLENGTH\|ERR_VARSYNTAX\|ERR_VECGFXPLUGIN\|ERR_VECTORANIM\|ERR_VECTORBRUSH\|ERR_VERSION\|ERR_VFONT\|ERR_VFONTTYPE\|ERR_VIDATTACHED\|ERR_VIDEOFRAME\|ERR_VIDEOINIT\|ERR_VIDEOLAYER\|ERR_VIDEOLAYERDRV\|ERR_VIDEOSTRATEGY\|ERR_VIDEOTRANS\|ERR_VIDLAYERFUNC\|ERR_VIDNOTPAUSED\|ERR_VIDNOTPLAYING\|ERR_VIDPAUSED\|ERR_VIDPLAYING\|ERR_VIDRECMULTI\|ERR_VIDRECTRANS\|ERR_VIDSTOPPED\|ERR_VISUALINFO\|ERR_VMMISMATCH\|ERR_WARPOS\|ERR_WENDWOWHILE\|ERR_WHILEWOWEND\|ERR_WINDOW\|ERR_WITHWOENDWITH\|ERR_WRITE\|ERR_WRITEFILE\|ERR_WRITEJPEG\|ERR_WRITEONLY\|ERR_WRONGCLIPREG\|ERR_WRONGCMDRECVIDEO\|ERR_WRONGDTYPE\|ERR_WRONGFLOAT\|ERR_WRONGHEX\|ERR_WRONGID\|ERR_WRONGOP\|ERR_WRONGOPCST\|ERR_WRONGSPRITESIZE\|ERR_WRONGUSAGE\|ERR_WRONGVSTRATEGY\|ERR_XCURSOR\|ERR_XDISPLAY\|ERR_XF86VIDMODEEXT\|ERR_XFIXES\|ERR_YIELD\|ERR_ZERODIVISION\|ERR_ZLIBDATA\|ERR_ZLIBIO\|ERR_ZLIBSTREAM\|ERR_ZLIBVERSION\|EVENTHANDLER\|FADE\|FASTMEMORY\|FASTSPEED\|FILE\|FILEATTR_ARCHIVE\|FILEATTR_DELETE_USR\|FILEATTR_EXECUTE_GRP\|FILEATTR_EXECUTE_OTH\|FILEATTR_EXECUTE_USR\|FILEATTR_HIDDEN\|FILEATTR_NORMAL\|FILEATTR_PURE\|FILEATTR_READ_GRP\|FILEATTR_READ_OTH\|FILEATTR_READ_USR\|FILEATTR_READONLY\|FILEATTR_SCRIPT\|FILEATTR_SYSTEM\|FILEATTR_WRITE_GRP\|FILEATTR_WRITE_OTH\|FILEATTR_WRITE_USR\|FILETYPE_ANIM\|FILETYPE_ICON\|FILETYPE_IMAGE\|FILETYPE_SOUND\|FILETYPE_VIDEO\|FILETYPEFLAGS_ALPHA\|FILETYPEFLAGS_FPS\|FILETYPEFLAGS_QUALITY\|FILETYPEFLAGS_SAVE\|FILLCOLOR\|FILLGRADIENT\|FILLNONE\|FILLRULEEVENODD\|FILLRULEWINDING\|FILLTEXTURE\|FLOAT\|FLOW_HARDWARE\|FLOW_OFF\|FLOW_XON_XOFF\|FONT\|FONTENGINE_INBUILT\|FONTENGINE_NATIVE\|FONTSLANT_ITALIC\|FONTSLANT_OBLIQUE\|FONTSLANT_ROMAN\|FONTTYPE_BITMAP\|FONTTYPE_COLOR\|FONTTYPE_VECTOR\|FONTWEIGHT_BLACK\|FONTWEIGHT_BOLD\|FONTWEIGHT_BOOK\|FONTWEIGHT_DEMIBOLD\|FONTWEIGHT_EXTRABLACK\|FONTWEIGHT_EXTRABOLD\|FONTWEIGHT_EXTRALIGHT\|FONTWEIGHT_HEAVY\|FONTWEIGHT_LIGHT\|FONTWEIGHT_MEDIUM\|FONTWEIGHT_NORMAL\|FONTWEIGHT_REGULAR\|FONTWEIGHT_SEMIBOLD\|FONTWEIGHT_THIN\|FONTWEIGHT_ULTRABLACK\|FONTWEIGHT_ULTRABOLD\|FONTWEIGHT_ULTRALIGHT\|FRAMEMODE_FULL\|FRAMEMODE_SINGLE\|FREESPACE\|FTPASCII\|FTPBINARY\|FUCHSIA\|FUNCTION\|GRAY\|GREEN\|HBLINDS128\|HBLINDS16\|HBLINDS32\|HBLINDS64\|HBLINDS8\|HCLOSECURTAIN\|HCLOSEGATE\|HEXNUMERICAL\|HFLIPCOIN\|HFLOWBOTTOM\|HFLOWTOP\|HIDEBRUSH\|HIDELAYER\|HKEY_CLASSES_ROOT\|HKEY_CURRENT_CONFIG\|HKEY_CURRENT_USER\|HKEY_LOCAL_MACHINE\|HKEY_USERS\|HLINES\|HLINES2\|HLOWFLIPCOIN\|HOLLYWOOD\|HOPENCURTAIN\|HOPENGATE\|HSPLIT\|HSTRANGEPUSH\|HSTRETCHCENTER\|HSTRIPES\|HSTRIPES16\|HSTRIPES2\|HSTRIPES32\|HSTRIPES4\|HSTRIPES64\|HSTRIPES8\|HW_64BIT\|HW_AMIGA\|HW_AMIGAOS3\|HW_AMIGAOS4\|HW_ANDROID\|HW_AROS\|HW_IOS\|HW_LINUX\|HW_LITTLE_ENDIAN\|HW_MACOS\|HW_MORPHOS\|HW_REVISION\|HW_VERSION\|HW_WARPOS\|HW_WINDOWS\|ICNFMT_HOLLYWOOD\|ICON\|IMAGETYPE_RASTER\|IMAGETYPE_VECTOR\|IMGFMT_BMP\|IMGFMT_GIF\|IMGFMT_ILBM\|IMGFMT_JPEG\|IMGFMT_NATIVE\|IMGFMT_PLUGIN\|IMGFMT_PNG\|IMGFMT_TIFF\|IMGFMT_UNKNOWN\|IMMERSIVE_LEANBACK\|IMMERSIVE_NONE\|IMMERSIVE_NORMAL\|IMMERSIVE_STICKY\|INACTIVEWINDOW\|INF\|INSERTBRUSH\|INTEGER\|INTERVAL\|IO_BUFFERED\|IO_FAKE64\|IO_LITTLEENDIAN\|IO_SIGNED\|IO_UNBUFFERED\|IO_UNSIGNED\|IPAUTO\|IPUNKNOWN\|IPV4\|IPV6\|ITALIC\|JOINBEVEL\|JOINMITER\|JOINROUND\|JOYDOWN\|JOYDOWNLEFT\|JOYDOWNRIGHT\|JOYLEFT\|JOYNODIR\|JOYRIGHT\|JOYUP\|JOYUPLEFT\|JOYUPRIGHT\|JUSTIFIED\|KEEPASPRAT\|KEEPPOSITION\|LANGUAGE_ABKHAZIAN\|LANGUAGE_AFAR\|LANGUAGE_AFRIKAANS\|LANGUAGE_AKAN\|LANGUAGE_ALBANIAN\|LANGUAGE_AMHARIC\|LANGUAGE_ARABIC\|LANGUAGE_ARAGONESE\|LANGUAGE_ARMENIAN\|LANGUAGE_ASSAMESE\|LANGUAGE_AVARIC\|LANGUAGE_AVESTAN\|LANGUAGE_AYMARA\|LANGUAGE_AZERBAIJANI\|LANGUAGE_BAMBARA\|LANGUAGE_BASHKIR\|LANGUAGE_BASQUE\|LANGUAGE_BELARUSIAN\|LANGUAGE_BENGALI\|LANGUAGE_BIHARI\|LANGUAGE_BISLAMA\|LANGUAGE_BOSNIAN\|LANGUAGE_BRETON\|LANGUAGE_BULGARIAN\|LANGUAGE_BURMESE\|LANGUAGE_CATALAN\|LANGUAGE_CENTRALKHMER\|LANGUAGE_CHAMORRO\|LANGUAGE_CHECHEN\|LANGUAGE_CHICHEWA\|LANGUAGE_CHINESE\|LANGUAGE_CHURCHSLAVIC\|LANGUAGE_CHUVASH\|LANGUAGE_CORNISH\|LANGUAGE_CORSICAN\|LANGUAGE_CREE\|LANGUAGE_CROATIAN\|LANGUAGE_CZECH\|LANGUAGE_DANISH\|LANGUAGE_DIVEHI\|LANGUAGE_DUTCH\|LANGUAGE_DZONGKHA\|LANGUAGE_ENGLISH\|LANGUAGE_ESPERANTO\|LANGUAGE_ESTONIAN\|LANGUAGE_EWE\|LANGUAGE_FAROESE\|LANGUAGE_FIJIAN\|LANGUAGE_FINNISH\|LANGUAGE_FRENCH\|LANGUAGE_FULAH\|LANGUAGE_GAELIC\|LANGUAGE_GALICIAN\|LANGUAGE_GANDA\|LANGUAGE_GEORGIAN\|LANGUAGE_GERMAN\|LANGUAGE_GREEK\|LANGUAGE_GREENLANDIC\|LANGUAGE_GUARANI\|LANGUAGE_GUJARATI\|LANGUAGE_HAITIAN\|LANGUAGE_HAUSA\|LANGUAGE_HEBREW\|LANGUAGE_HERERO\|LANGUAGE_HINDI\|LANGUAGE_HIRIMOTU\|LANGUAGE_HUNGARIAN\|LANGUAGE_ICELANDIC\|LANGUAGE_IDO\|LANGUAGE_IGBO\|LANGUAGE_INDONESIAN\|LANGUAGE_INTERLINGUA\|LANGUAGE_INTERLINGUE\|LANGUAGE_INUKTITUT\|LANGUAGE_INUPIAQ\|LANGUAGE_IRISH\|LANGUAGE_ITALIAN\|LANGUAGE_JAPANESE\|LANGUAGE_JAVANESE\|LANGUAGE_KANNADA\|LANGUAGE_KANURI\|LANGUAGE_KASHMIRI\|LANGUAGE_KAZAKH\|LANGUAGE_KIKUYU\|LANGUAGE_KINYARWANDA\|LANGUAGE_KIRGHIZ\|LANGUAGE_KOMI\|LANGUAGE_KONGO\|LANGUAGE_KOREAN\|LANGUAGE_KUANYAMA\|LANGUAGE_KURDISH\|LANGUAGE_LAO\|LANGUAGE_LATIN\|LANGUAGE_LATVIAN\|LANGUAGE_LIMBURGAN\|LANGUAGE_LINGALA\|LANGUAGE_LITHUANIAN\|LANGUAGE_LUBAKATANGA\|LANGUAGE_LUXEMBOURGISH\|LANGUAGE_MACEDONIAN\|LANGUAGE_MALAGASY\|LANGUAGE_MALAY\|LANGUAGE_MALAYALAM\|LANGUAGE_MALTESE\|LANGUAGE_MANX\|LANGUAGE_MAORI\|LANGUAGE_MARATHI\|LANGUAGE_MARSHALLESE\|LANGUAGE_MONGOLIAN\|LANGUAGE_NAURU\|LANGUAGE_NAVAJO\|LANGUAGE_NDONGA\|LANGUAGE_NEPALI\|LANGUAGE_NORTHERNSAMI\|LANGUAGE_NORTHNDEBELE\|LANGUAGE_NORWEGIAN\|LANGUAGE_NORWEGIANBOKMAL\|LANGUAGE_NORWEGIANNYNORSK\|LANGUAGE_OCCITAN\|LANGUAGE_OJIBWA\|LANGUAGE_ORIYA\|LANGUAGE_OROMO\|LANGUAGE_OSSETIAN\|LANGUAGE_PALI\|LANGUAGE_PANJABI\|LANGUAGE_PASHTO\|LANGUAGE_PERSIAN\|LANGUAGE_POLISH\|LANGUAGE_PORTUGUESE\|LANGUAGE_QUECHUA\|LANGUAGE_ROMANIAN\|LANGUAGE_ROMANSH\|LANGUAGE_RUNDI\|LANGUAGE_RUSSIAN\|LANGUAGE_SAMOAN\|LANGUAGE_SANGO\|LANGUAGE_SANSKRIT\|LANGUAGE_SARDINIAN\|LANGUAGE_SERBIAN\|LANGUAGE_SHONA\|LANGUAGE_SICHUANYI\|LANGUAGE_SINDHI\|LANGUAGE_SINHALA\|LANGUAGE_SLOVAK\|LANGUAGE_SLOVENIAN\|LANGUAGE_SOMALI\|LANGUAGE_SOUTHERNSOTHO\|LANGUAGE_SOUTHNDEBELE\|LANGUAGE_SPANISH\|LANGUAGE_SUNDANESE\|LANGUAGE_SWAHILI\|LANGUAGE_SWATI\|LANGUAGE_SWEDISH\|LANGUAGE_TAGALOG\|LANGUAGE_TAHITIAN\|LANGUAGE_TAJIK\|LANGUAGE_TAMIL\|LANGUAGE_TATAR\|LANGUAGE_TELUGU\|LANGUAGE_THAI\|LANGUAGE_TIBETAN\|LANGUAGE_TIGRINYA\|LANGUAGE_TONGA\|LANGUAGE_TSONGA\|LANGUAGE_TSWANA\|LANGUAGE_TURKISH\|LANGUAGE_TURKMEN\|LANGUAGE_TWI\|LANGUAGE_UIGHUR\|LANGUAGE_UKRAINIAN\|LANGUAGE_UNKNOWN\|LANGUAGE_URDU\|LANGUAGE_UZBEK\|LANGUAGE_VENDA\|LANGUAGE_VIETNAMESE\|LANGUAGE_WALLOON\|LANGUAGE_WELSH\|LANGUAGE_WESTERNFRISIAN\|LANGUAGE_WOLOF\|LANGUAGE_XHOSA\|LANGUAGE_YIDDISH\|LANGUAGE_YORUBA\|LANGUAGE_ZHUANG\|LANGUAGE_ZULU\|LAYER\|LAYER_VS_BOX\|LAYERBUTTON\|LEFT\|LEFTOUT\|LIGHTUSERDATA\|LIME\|LINE\|LINEAR\|LITTLEENDIAN\|LONG\|LOWERCURVE\|MAROON\|MASK\|MASKAND\|MASKINVISIBLE\|MASKOR\|MASKVANILLACOPY\|MASKVISIBLE\|MASKXOR\|MEMORY\|MENU\|MENUITEM_DISABLED\|MENUITEM_RADIO\|MENUITEM_SELECTED\|MENUITEM_TOGGLE\|MILLISECONDS\|MODE_READ\|MODE_READWRITE\|MODE_WRITE\|MODLALT\|MODLCOMMAND\|MODLCONTROL\|MODLSHIFT\|MODRALT\|MODRCOMMAND\|MODRCONTROL\|MODRSHIFT\|MONO16\|MONO8\|MONOSPACE\|MOVEFILE_COPY\|MOVEFILE_COPYFAILED\|MOVEFILE_DELETE\|MOVEFILE_DELETEFAILED\|MOVEFILE_UNPROTECT\|MOVELIST\|MOVEWINDOW\|MUSIC\|NAN\|NATIVE\|NATIVEENDIAN\|NAVY\|NETWORKCONNECTION\|NETWORKSERVER\|NETWORKUDP\|NEXTFRAME\|NEXTFRAME2\|NIL\|NOCOLOR\|NONE\|NOPEN\|NORMAL\|NORMALSPEED\|NOTRANSPARENCY\|NUMBER\|NUMERICAL\|OLIVE\|ONBUTTONCLICK\|ONBUTTONCLICKALL\|ONBUTTONOVER\|ONBUTTONOVERALL\|ONBUTTONRIGHTCLICK\|ONBUTTONRIGHTCLICKALL\|ONKEYDOWN\|ONKEYDOWNALL\|ORIENTATION_LANDSCAPE\|ORIENTATION_LANDSCAPEREV\|ORIENTATION_NONE\|ORIENTATION_PORTRAIT\|ORIENTATION_PORTRAITREV\|PALETTE\|PALETTE_AGA\|PALETTE_CGA\|PALETTE_DEFAULT\|PALETTE_EGA\|PALETTE_GRAY128\|PALETTE_GRAY16\|PALETTE_GRAY256\|PALETTE_GRAY32\|PALETTE_GRAY4\|PALETTE_GRAY64\|PALETTE_GRAY8\|PALETTE_MACINTOSH\|PALETTE_MONOCHROME\|PALETTE_OCS\|PALETTE_WINDOWS\|PALETTE_WORKBENCH\|PALETTEMODE_PEN\|PALETTEMODE_REMAP\|PARITY_EVEN\|PARITY_NONE\|PARITY_ODD\|PERMREQ_READEXTERNAL\|PERMREQ_WRITEEXTERNAL\|PI\|PIXELZOOM1\|PIXELZOOM2\|PLOT\|PLUGINCAPS_ANIM\|PLUGINCAPS_AUDIOADAPTER\|PLUGINCAPS_CONVERT\|PLUGINCAPS_DIRADAPTER\|PLUGINCAPS_DISPLAYADAPTER\|PLUGINCAPS_EXTENSION\|PLUGINCAPS_FILEADAPTER\|PLUGINCAPS_ICON\|PLUGINCAPS_IMAGE\|PLUGINCAPS_IPCADAPTER\|PLUGINCAPS_LIBRARY\|PLUGINCAPS_NETWORKADAPTER\|PLUGINCAPS_REQUESTERADAPTER\|PLUGINCAPS_REQUIRE\|PLUGINCAPS_SAVEANIM\|PLUGINCAPS_SAVEICON\|PLUGINCAPS_SAVEIMAGE\|PLUGINCAPS_SAVESAMPLE\|PLUGINCAPS_SERIALIZE\|PLUGINCAPS_SOUND\|PLUGINCAPS_TIMERADAPTER\|PLUGINCAPS_VECTOR\|PLUGINCAPS_VIDEO\|POINTER\|POLYGON\|PRGTYPE_APPLET\|PRGTYPE_PROGRAM\|PRGTYPE_SCRIPT\|PRINT\|PURPLE\|PUSHBOTTOM\|PUSHLEFT\|PUSHRIGHT\|PUSHTOP\|PUZZLE\|QUADRECT\|QUARTERS\|RADIAL\|RANDOMEFFECT\|RANDOMPARAMETER\|RECEIVEALL\|RECEIVEBYTES\|RECEIVEDATA_PACKET\|RECEIVELINE\|RECTBACKCENTER\|RECTBACKEAST\|RECTBACKNORTH\|RECTBACKNORTHEAST\|RECTBACKNORTHWEST\|RECTBACKSOUTH\|RECTBACKSOUTHEAST\|RECTBACKSOUTHWEST\|RECTBACKWEST\|RECTCENTER\|RECTEAST\|RECTNORTH\|RECTNORTHEAST\|RECTNORTHWEST\|RECTSOUTH\|RECTSOUTHEAST\|RECTSOUTHWEST\|RECTWEST\|RED\|REMOVELAYER\|REQ_CAMERA\|REQ_GALLERY\|REQ_HIDEICONS\|REQ_MULTISELECT\|REQ_NORMAL\|REQ_SAVEMODE\|REQICON_ERROR\|REQICON_INFORMATION\|REQICON_NONE\|REQICON_QUESTION\|REQICON_WARNING\|REVEALBOTTOM\|REVEALLEFT\|REVEALRIGHT\|REVEALTOP\|RIGHT\|RIGHTOUT\|ROLLLEFT\|ROLLTOP\|RTS_OFF\|RTS_ON\|SAMPLE\|SANS\|SCALEMODE_AUTO\|SCALEMODE_LAYER\|SCALEMODE_NONE\|SCROLLBOTTOM\|SCROLLEAST\|SCROLLLEFT\|SCROLLNORTH\|SCROLLNORTHEAST\|SCROLLNORTHWEST\|SCROLLRIGHT\|SCROLLSOUTH\|SCROLLSOUTHEAST\|SCROLLSOUTHWEST\|SCROLLTOP\|SCROLLWEST\|SECONDS\|SEEK_BEGINNING\|SEEK_CURRENT\|SEEK_END\|SELMODE_COMBO\|SELMODE_LAYERS\|SELMODE_NORMAL\|SERIAL\|SERIF\|SERVER\|SHADOW\|SHAPE\|SHDWEAST\|SHDWNORTH\|SHDWNORTHEAST\|SHDWNORTHWEST\|SHDWSOUTH\|SHDWSOUTHEAST\|SHDWSOUTHWEST\|SHDWWEST\|SHORT\|SILVER\|SIMPLEBUTTON\|SINE\|SIZEWINDOW\|SLIDEBOTTOM\|SLIDELEFT\|SLIDERIGHT\|SLIDETOP\|SLOWSPEED\|SMOOTHOUT\|SMPFMT_WAVE\|SNAPDESKTOP\|SNAPDISPLAY\|SNAPWINDOW\|SPIRAL\|SPRITE\|SPRITE_VS_BOX\|SPRITE_VS_BRUSH\|STAR\|STDERR\|STDIN\|STDOUT\|STDPTR_BUSY\|STDPTR_CUSTOM\|STDPTR_SYSTEM\|STEREO16\|STEREO8\|STOP_1\|STOP_2\|STRETCHBOTTOM\|STRETCHLEFT\|STRETCHRIGHT\|STRETCHTOP\|STRING\|STRUDEL\|SUN\|SWISS\|TABLE\|TEAL\|TEXTOBJECT\|TEXTOUT\|THREAD\|TICKS\|TIMEOUT\|TIMER\|TOP\|TOPOUT\|TRUETYPE_DEFAULT\|TURNDOWNBOTTOM\|TURNDOWNLEFT\|TURNDOWNRIGHT\|TURNDOWNTOP\|UDPCLIENT\|UDPNONE\|UDPOBJECT\|UDPSERVER\|UNDERLINED\|UNDO\|UPLOADFILE_RESPONSE\|UPLOADFILE_STATUS\|UPNDOWN\|UPPERCURVE\|USEDSPACE\|USELAYERPOSITION\|USERDATA\|VANILLACOPY\|VBLINDS128\|VBLINDS16\|VBLINDS32\|VBLINDS64\|VBLINDS8\|VCLOSECURTAIN\|VCLOSEGATE\|VECTORPATH\|VFLIPCOIN\|VFLOWLEFT\|VFLOWRIGHT\|VIDDRV_HOLLYWOOD\|VIDDRV_OS\|VIDEO\|VIEWMODE_DATE\|VIEWMODE_ICONS\|VIEWMODE_NAME\|VIEWMODE_NONE\|VIEWMODE_SIZE\|VIEWMODE_TYPE\|VLINES\|VLINES2\|VLOWFLIPCOIN\|VOID\|VOPENCURTAIN\|VOPENGATE\|VSPLIT\|VSTRANGEPUSH\|VSTRETCHCENTER\|VSTRIPES\|VSTRIPES16\|VSTRIPES2\|VSTRIPES32\|VSTRIPES4\|VSTRIPES64\|VSTRIPES8\|WALLPAPERLEFT\|WALLPAPERTOP\|WATER1\|WATER2\|WATER3\|WATER4\|WHITE\|WORD\|YELLOW\|ZOOMCENTER\|ZOOMEAST\|ZOOMIN\|ZOOMNORTH\|ZOOMNORTHEAST\|ZOOMNORTHWEST\|ZOOMOUT\|ZOOMSOUTH\|ZOOMSOUTHEAST\|ZOOMSOUTHWEST\|ZOOMWEST\)\>"
" Hollywood Functions
-syn keyword hwFunction Abs ACos ARGB ActivateDisplay Add AddArcToPath AddBoxToPath AddCircleToPath AddEllipseToPath AddFontPath AddIconImage AddMove AddStr AddTab AddTextToPath AllocMem AllocMemFromPointer AllocMemFromVirtualFile AppendPath ApplyPatch Arc ArcDistortBrush ArrayToStr Asc ASin Assert AsyncDrawFrame ATan ATan2 BarrelDistortBrush Base64Str Beep BeginAnimStream BeginDoubleBuffer BeginRefresh BGPicToBrush BinStr BitClear BitComplement BitSet BitTest BitXor Blue BlurBrush Box BreakEventHandler BreakWhileMouseOn BrushToBGPic BrushToGray BrushToMonochrome BrushToPenArray BrushToRGBArray ByteAsc ByteChr ByteLen ByteOffset ByteStrStr ByteVal CRC32 CRC32Str CallJavaMethod CancelAsyncDraw CancelAsyncOperation CanonizePath Cast Ceil ChangeApplicationIcon ChangeBrushTransparency ChangeDirectory ChangeDisplayMode ChangeDisplaySize ChangeInterval CharcoalBrush CharOffset CharWidth CheckEvent CheckEvents Chr Circle ClearClipboard ClearEvents ClearInterval ClearMove ClearObjectData ClearPath ClearScreen ClearSerialQueue ClearTimeout CloseAmigaGuide CloseAnim CloseAudio CloseCatalog CloseConnection CloseDirectory CloseDisplay CloseFile CloseFont CloseMusic ClosePath CloseResourceMonitor CloseSerialPort CloseServer CloseUDPObject CloseVideo Cls CollectGarbage Collision ColorRequest CompareDates CompareStr CompressFile Concat ConsolePrint ConsolePrintNR ConsolePrompt ContinueAsyncOperation ContrastBrush ContrastPalette ConvertStr ConvertToBrush CopyAnim CopyBGPic CopyBrush CopyFile CopyLayer CopyMem CopyObjectData CopyPalette CopyPath CopyPens CopySample CopySprite CopyTable CopyTextObject Cos CountDirectoryEntries CountJoysticks CountStr CreateAnim CreateBGPic CreateBorderBrush CreateBrush CreateButton CreateClipRegion CreateCoroutine CreateDisplay CreateGradientBGPic CreateGradientBrush CreateIcon CreateKeyDown CreateLayer CreateList CreateMenu CreateMusic CreatePalette CreatePointer CreatePort CreateRainbowBGPic CreateRexxPort CreateSample CreateServer CreateShadowBrush CreateShortcut CreateSprite CreateTextObject CreateTexturedBGPic CreateTexturedBrush CreateUDPObject CropBrush CtrlCQuit CurveTo CyclePalette DateToTimestamp DateToUTC DebugOutput DebugPrint DebugPrintNR DebugPrompt DebugStr DebugVal DecompressFile DecreasePointer DefineVirtualFile DefineVirtualFileFromString Deg DeleteAlphaChannel DeleteButton DeleteFile DeleteMask DeletePrefs DeselectMenuItem DeserializeTable DirectoryItems DisableButton DisableEvent DisableEventHandler DisableLayers DisableLineHook DisableMenuItem DisablePlugin DisablePrecalculation DisableVWait DisplayAnimFrame DisplayBGPic DisplayBGPicPart DisplayBGPicPartFX DisplayBrush DisplayBrushFX DisplayBrushPart DisplaySprite DisplayTextObject DisplayTextObjectFX DisplayTransitionFX DisplayVideoFrame Div DoMove DownloadFile DrawPath DumpButtons DumpLayers DumpMem DumpVideo DumpVideoTime EdgeBrush Ellipse EmbossBrush EmptyStr EnableButton EnableEventHandler EnableEvent EnableLayers EnableLineHook EnableMenuItem EnablePlugin EnablePrecalculation EnableVWait End EndDoubleBuffer EndianSwap EndRefresh EndSelect EndsWith Eof Error EscapeQuit Eval Execute Exists ExitOnError Exp ExtractPalette FileAttributes FileLength FileLines FilePart FilePos FileRequest FileSize FileToString FillMem FillMusicBuffer FindStr FinishAnimStream FinishAsyncDraw Flip FlipBrush FlipSprite FloodFill Floor FlushFile FlushMusicBuffer FlushSerialPort FontRequest ForcePathUse ForceSound ForceVideoDriver ForceVideoMode ForEach ForEachI FormatStr Frac FreeAnim FreeBGPic FreeBrush FreeClipRegion FreeDisplay FreeEventCache FreeGlyphCache FreeIcon FreeLayers FreeMem FreeMenu FreeModule FreePalette FreePath FreePointer FreeSample FreeSprite FreeTextObject FrExp FullPath GammaBrush GammaPalette GCInfo GetAnimFrame GetApplicationInfo GetApplicationList GetAsset GetAttribute GetAvailableFonts GetBaudRate GetBestPen GetBrushLink GetBrushPen GetBulletColor GetCatalogString GetChannels GetCharMaps GetClipboard GetCommandLine GetConnectionIP GetConnectionPort GetConnectionProtocol GetConstant GetCoroutineStatus GetCountryInfo GetCurrentDirectory GetCurrentPoint GetDash GetDataBits GetDate GetDateNum GetDefaultEncoding GetDirectoryEntry GetDisplayModes GetDTR GetEnv GetErrorName GetEventCode GetFileArgument GetFileAttributes GetFillRule GetFillStyle GetFlowControl GetFontColor GetFontStyle GetFormStyle GetFPSLimit GetFrontScreen GetHostName GetIconProperties GetItem GetKerningPair GetLanguageInfo GetLastError GetLayerAtPos GetLayerPen GetLayerStyle GetLineCap GetLineJoin GetLineWidth GetLocalInterfaces GetLocalIP GetLocalPort GetLocalProtocol GetMACAddress GetMemoryInfo GetMemPointer GetMemString GetMetaTable GetMiterLimit GetMonitors GetObjectData GetObjects GetObjectType GetPalettePen GetParity GetPathExtents GetPatternPosition GetPen GetPlugins GetProgramDirectory GetProgramInfo GetPubScreens GetRandomColor GetRandomFX GetRealColor GetRTS GetSampleData GetShortcutPath GetSongPosition GetStartDirectory GetStopBits GetSystemCountry GetSystemInfo GetSystemLanguage GetTempFileName GetTime GetTimer GetTimestamp GetTimeZone GetType GetVersion GetVideoFrame GetVolumeInfo GetVolumeName GetWeekday Gosub Goto GrabDesktop Green HaveFreeChannel HaveItem HaveObject HaveObjectData HavePlugin HaveVolume HexStr HideDisplay HideKeyboard HideLayer HideLayerFX HidePointer HideScreen Hypot IgnoreCase IIf ImageRequest IncreasePointer InKeyStr InsertItem InsertLayer InsertSample InsertStr InstallEventHandler Intersection Int InvertAlphaChannel InvertBrush InvertMask InvertPalette IPairs IsAbsolutePath IsAlNum IsAlpha IsAnim IsAnimPlaying IsBrushEmpty IsChannelPlaying IsCntrl IsDigit IsDirectory IsFinite IsGraph IsInf IsKeyDown IsLeftMouse IsLower IsMenuItemDisabled IsMenuItemSelected IsMidMouse IsModule IsMusicPlaying IsMusic IsNan IsNil IsOnline IsPathEmpty IsPicture IsPrint IsPunct IsRightMouse IsSamplePlaying IsSample IsSound IsSpace IsTableEmpty IsUnicode IsUpper IsVideo IsVideoPlaying IsXDigit JoyDir JoyFire Label LayerExists LayerToBack LayerToFront Ld LdExp LeftMouseQuit LeftStr LegacyControl Limit Line LineTo ListItems ListRequest Ln LoadAnim LoadAnimFrame LoadBGPic LoadBrush LoadIcon LoadModule LoadPalette LoadPlugin LoadPrefs LoadSample LoadSprite Locate Log LowerStr MakeButton MakeDate MakeDirectory MakeHostPath MatchPattern Max MD5 MD5Str MemToTable MidStr Min MixBrush MixRGB MixSample ModifyAnimFrames ModifyButton ModifyKeyDown ModifyLayerFrames ModulateBrush Mod ModulatePalette MonitorDirectory MouseX MouseY MoveAnim MoveBrush MoveDisplay MoveFile MoveLayer MovePointer MoveSprite MoveTextObject MoveTo Mul NextDirectoryEntry NextFrame NextItem NormalizePath NPrint OilPaintBrush OpenAmigaGuide OpenAnim OpenAudio OpenCatalog OpenConnection OpenDirectory OpenDisplay OpenFile OpenFont OpenMusic OpenResourceMonitor OpenSerialPort OpenURL OpenVideo Pack PadNum Pairs PaletteToGray ParseDate PathItems PathPart PathRequest PathToBrush PatternFindStr PatternFindStrDirect PatternFindStrShort PatternReplaceStr PauseLayer PauseModule PauseMusic PauseTimer PauseVideo PeekClipboard Peek PenArrayToBrush PerformSelector PermissionRequest PerspectiveDistortBrush Pi PixelateBrush PlayAnim PlayAnimDisk PlayLayer PlayModule PlayMusic PlaySample PlaySubsong PlayVideo Plot Poke PolarDistortBrush PollSerialQueue Polygon Pow Print QuantizeBrush Rad RaiseOnError RasterizeBrush RawDiv RawEqual RawGet RawSet ReadBrushPixel ReadByte ReadBytes ReadChr ReadDirectory ReadFloat ReadFunction ReadInt ReadLine ReadMem ReadPen ReadPixel ReadRegistryKey ReadSerialData ReadShort ReadString ReadTable ReceiveData ReceiveUDPData Red ReduceAlphaChannel RefreshDisplay RelCurveTo RelLineTo RelMoveTo RemapBrush RemoveBrushPalette RemoveButton RemoveIconImage RemoveItem RemoveKeyDown RemoveLayer RemoveLayerFX RemoveLayers RemoveSprite RemoveSprites Rename RepeatStr ReplaceColors ReplaceStr ResetKeyStates ResetTabs ResetTimer ResolveHostName ResumeCoroutine ResumeLayer ResumeModule ResumeMusic ResumeTimer ResumeVideo ReverseFindStr ReverseStr RewindDirectory RGB RGBArrayToBrush RightStr Rnd RndF RndStrong Rol Ror RotateBrush RotateLayer RotateTextObject Round Rt Run RunCallback RunRexxScript Sar SaveAnim SaveBrush SaveIcon SavePalette SavePrefs SaveSample SaveSnapshot ScaleAnim ScaleBGPic ScaleBrush ScaleLayer ScaleSprite ScaleTextObject Seek SeekLayer SeekMusic SeekVideo SelectAlphaChannel SelectAnim SelectBGPic SelectBrush SelectDisplay SelectLayer SelectMask SelectMenuItem SelectPalette SendApplicationMessage SendData SendMessage SendRexxCommand SendUDPData SepiaToneBrush SerializeTable SetAlphaIntensity SetAnimFrameDelay SetBaudRate SetBorderPen SetBrushDepth SetBrushPalette SetBrushPen SetBrushTransparency SetBrushTransparentPen SetBulletColor SetBulletPen SetChannelVolume SetClipboard SetClipRegion SetCycleTable SetDash SetDataBits SetDefaultEncoding SetDepth SetDisplayAttributes SetDitherMode SetDrawPen SetDrawTagsDefault SetDTR SetEnv SetEventTimeout SetFileAttributes SetFileEncoding SetFillRule SetFillStyle SetFlowControl SetFont SetFontColor SetFontStyle SetFormStyle SetFPSLimit SetGradientPalette SetIconProperties SetInterval SetIOMode SetLayerAnchor SetLayerBorder SetLayerDepth SetLayerFilter SetLayerLight SetLayerName SetLayerPalette SetLayerPen SetLayerShadow SetLayerStyle SetLayerTint SetLayerTransparency SetLayerTransparentPen SetLayerVolume SetLayerZPos SetLineCap SetLineJoin SetLineWidth SetListItems SetMargins SetMaskMode SetMasterVolume SetMetaTable SetMiterLimit SetMusicVolume SetNetworkProtocol SetNetworkTimeout SetObjectData SetPalette SetPaletteDepth SetPaletteMode SetPalettePen SetPaletteTransparentPen SetPanning SetParity SetPen SetPitch SetPointer SetRTS SetScreenTitle SetShadowPen SetSpriteZPos SetStandardIconImage SetStandardPalette SetStopBits SetSubtitle SetTimeout SetTimerElapse SetTitle SetTransparentPen SetTransparentThreshold SetTrayIcon SetVarType SetVectorEngine SetVideoPosition SetVideoSize SetVideoVolume SetVolume SetWBIcon Sgn SharpenBrush Shl ShowDisplay ShowKeyboard ShowLayer ShowLayerFX ShowNotification ShowPointer ShowRinghioMessage ShowScreen ShowToast Shr Sin SolarizeBrush SolarizePalette Sort SplitStr Sqrt StartPath StartSubPath StartTimer StartsWith StopAnim StopChannel StopLayer StopModule StopMusic StopSample StopTimer StopVideo StringRequest StringToFile StripStr StrLen StrStr StrToArray Sub SwapLayers SwirlBrush SystemRequest TableItems TableToMem Tan TextExtent TextHeight TextOut TextWidth TimerElapsed TimestampToDate TintBrush TintPalette ToHostName ToIP ToNumber ToString ToUserData TransformBrush TransformLayer TranslateLayer TranslatePath TrimBrush TrimStr UndefineVirtualStringFile Undo UndoFX UnleftStr UnmidStr Unpack UnrightStr UnsetEnv UploadFile UpperStr Usage UseCarriageReturn UseFont UTCToDate Val ValidateDate ValidateStr Vibrate VWait Wait WaitAnimEnd WaitEvent WaitKeyDown WaitLeftMouse WaitMidMouse WaitPatternPosition WaitRightMouse WaitSampleEnd WaitSongPosition WaitTimer WaterRippleBrush WhileKeyDown WhileMouseDown WhileMouseOn WhileRightMouseDown Wrap WriteAnimFrame WriteBrushPixel WriteByte WriteBytes WriteChr WriteFloat WriteFunction WriteInt WriteLine WriteMem WritePen WriteRegistryKey WriteSerialData WriteShort WriteString WriteTable YieldCoroutine
+syn keyword hwFunction Abs ACos ActivateDisplay Add AddArcToPath AddBoxToPath AddCircleToPath AddEllipseToPath AddFontPath AddIconImage AddMove AddStr AddTab AddTextToPath AllocConsoleColor AllocMem AllocMemFromPointer AllocMemFromVirtualFile AppendPath ApplyPatch Arc ArcDistortBrush ARGB ArrayToStr Asc ASin Assert AsyncDrawFrame ATan ATan2 BarrelDistortBrush Base64Str Beep BeepConsole BeginAnimStream BeginDoubleBuffer BeginRefresh BGPicToBrush BinStr BitClear BitComplement BitSet BitTest BitXor Blue BlurBrush Box BreakEventHandler BreakWhileMouseOn BrushToBGPic BrushToGray BrushToMonochrome BrushToPenArray BrushToRGBArray ByteAsc ByteChr ByteLen ByteOffset ByteStrStr ByteVal CallJavaMethod CancelAsyncDraw CancelAsyncOperation CanonizePath Cast Ceil ChangeApplicationIcon ChangeBrushTransparency ChangeDirectory ChangeDisplayMode ChangeDisplaySize ChangeInterval CharcoalBrush CharOffset CharWidth CheckEvent CheckEvents Chr Circle ClearClipboard ClearConsole ClearConsoleStyle ClearEvents ClearInterval ClearMove ClearObjectData ClearPath ClearScreen ClearSerialQueue ClearTimeout CloseAmigaGuide CloseAnim CloseAudio CloseCatalog CloseConnection CloseConsole CloseDirectory CloseDisplay CloseFile CloseFont CloseMusic ClosePath CloseResourceMonitor CloseSerialPort CloseServer CloseUDPObject CloseVideo Cls CollectGarbage Collision ColorRequest CompareDates CompareStr CompressFile Concat ConfigureJoystick ConsolePrint ConsolePrintChr ConsolePrintNR ConsolePrompt ContinueAsyncOperation ContrastBrush ContrastPalette ConvertStr ConvertToBrush CopyAnim CopyBGPic CopyBrush CopyConsoleWindow CopyFile CopyLayer CopyMem CopyObjectData CopyPalette CopyPath CopyPens CopySample CopySprite CopyTable CopyTextObject Cos CountDirectoryEntries CountJoysticks CountStr CRC32 CRC32Str CreateAnim CreateBGPic CreateBorderBrush CreateBrush CreateButton CreateClipRegion CreateConsoleWindow CreateCoroutine CreateDisplay CreateFont CreateGradientBGPic CreateGradientBrush CreateIcon CreateKeyDown CreateLayer CreateList CreateMenu CreateMusic CreatePalette CreatePointer CreatePort CreateRainbowBGPic CreateRexxPort CreateSample CreateServer CreateShadowBrush CreateShortcut CreateSprite CreateTextObject CreateTexturedBGPic CreateTexturedBrush CreateUDPObject CropBrush CtrlCQuit CurveTo CyclePalette DateToTimestamp DateToUTC DebugOutput DebugPrint DebugPrintNR DebugPrompt DebugStr DebugVal DecomposeConsoleChr DecompressFile DecreasePointer DefineVirtualFile DefineVirtualFileFromString Deg DeleteAlphaChannel DeleteButton DeleteConsoleChr DeleteConsoleLine DeleteFile DeleteMask DeletePrefs DeselectMenuItem DeserializeTable DirectoryItems DisableAdvancedConsole DisableButton DisableEvent DisableEventHandler DisableLayers DisableLineHook DisableMenuItem DisablePlugin DisablePrecalculation DisableVWait DisplayAnimFrame DisplayBGPic DisplayBGPicPart DisplayBGPicPartFX DisplayBrush DisplayBrushFX DisplayBrushPart DisplaySprite DisplayTextObject DisplayTextObjectFX DisplayTransitionFX DisplayVideoFrame Div DoMove DownloadFile DrawConsoleBorder DrawConsoleBox DrawConsoleHLine DrawConsoleVLine DrawPath DumpButtons DumpLayers DumpMem DumpVideo DumpVideoTime EdgeBrush Ellipse EmbossBrush EmptyStr EnableAdvancedConsole EnableButton EnableEvent EnableEventHandler EnableLayers EnableLineHook EnableMenuItem EnablePlugin EnablePrecalculation EnableVWait End EndDoubleBuffer EndianSwap EndRefresh EndSelect EndsWith Eof EraseConsole Error EscapeQuit Eval Execute Exists ExitOnError Exp ExtendBrush ExtractPalette FileAttributes FileLength FileLines FilePart FilePos FileRequest FileSize FileToString FillMem FillMusicBuffer FindStr FinishAnimStream FinishAsyncDraw FlashConsole Flip FlipBrush FlipSprite FloodFill Floor FlushFile FlushMusicBuffer FlushSerialPort FontRequest ForcePathUse ForceSound ForceVideoDriver ForceVideoMode ForEach ForEachI FormatConsoleLine FormatDate FormatNumber FormatStr Frac FreeAnim FreeBGPic FreeBrush FreeClipRegion FreeConsoleColor FreeConsoleWindow FreeDisplay FreeEventCache FreeGlyphCache FreeIcon FreeLayers FreeMem FreeMenu FreeModule FreePalette FreePath FreePointer FreeSample FreeSprite FreeTextObject FrExp FullPath GammaBrush GammaPalette GCInfo GetAllocConsoleColor GetAnimFrame GetApplicationInfo GetApplicationList GetAsset GetAttribute GetAvailableFonts GetBaudRate GetBestPen GetBrushLink GetBrushPen GetBulletColor GetCatalogString GetChannels GetCharMaps GetClipboard GetCommandLine GetConnectionIP GetConnectionPort GetConnectionProtocol GetConsoleBackground GetConsoleChr GetConsoleColor GetConsoleControlChr GetConsoleCursor GetConsoleOrigin GetConsoleSize GetConsoleStr GetConsoleStyle GetConsoleWindow GetConstant GetCoroutineStatus GetCountryInfo GetCurrentDirectory GetCurrentPoint GetDash GetDataBits GetDate GetDateNum GetDefaultAdapter GetDefaultEncoding GetDefaultLoader GetDirectoryEntry GetDisplayModes GetDTR GetEnv GetErrorName GetEventCode GetFileArgument GetFileAttributes GetFillRule GetFillStyle GetFlowControl GetFontColor GetFontStyle GetFormStyle GetFPSLimit GetFreePen GetFrontScreen GetHostName GetIconProperties GetItem GetKerningPair GetLanguageInfo GetLastError GetLayerAtPos GetLayerGroupMembers GetLayerGroups GetLayerPen GetLayerStyle GetLineCap GetLineJoin GetLineWidth GetLocaleInfo GetLocalInterfaces GetLocalIP GetLocalPort GetLocalProtocol GetMACAddress GetMemoryInfo GetMemPointer GetMemString GetMetaTable GetMiterLimit GetMonitors GetObjectData GetObjects GetObjectType GetPalettePen GetParity GetPathExtents GetPatternPosition GetPen GetPlugins GetProgramDirectory GetProgramInfo GetPubScreens GetRandomColor GetRandomFX GetRawArguments GetRealColor GetRTS GetSampleData GetSerializeMode GetShortcutPath GetSongPosition GetStartDirectory GetStopBits GetSystemCountry GetSystemInfo GetSystemLanguage GetTempFileName GetTime GetTimer GetTimestamp GetTimeZone GetType GetVersion GetVideoFrame GetVolumeInfo GetVolumeName GetWeekday Gosub Goto GrabDesktop Green GroupLayer HasItem HaveConsole HaveFreeChannel HaveItem HaveObject HaveObjectData HavePlugin HaveVolume HexStr HideConsoleCursor HideDisplay HideKeyboard HideLayer HideLayerFX HidePointer HideScreen Hypot IgnoreCase IIf ImageRequest IncreasePointer InitConsoleColor InKeyStr InsertConsoleChr InsertConsoleLine InsertConsoleStr InsertItem InsertLayer InsertSample InsertStr InstallEventHandler Int Intersection InvertAlphaChannel InvertBrush InvertMask InvertPalette IPairs IsAbsolutePath IsAlNum IsAlpha IsAnim IsAnimPlaying IsBrushEmpty IsChannelPlaying IsCntrl IsDigit IsDirectory IsFinite IsGraph IsInf IsKeyDown IsLeftMouse IsLower IsMenuItemDisabled IsMenuItemSelected IsMidMouse IsModule IsMusic IsMusicPlaying IsNan IsNil IsOnline IsPathEmpty IsPicture IsPrint IsPunct IsRightMouse IsSample IsSamplePlaying IsSound IsSpace IsTableEmpty IsUnicode IsUpper IsVideo IsVideoPlaying IsXDigit JoyAxisX JoyAxisY JoyAxisZ JoyButton JoyDir JoyFire Label JoyHat LayerExists LayerGroupExists LayerToBack LayerToFront Ld LdExp LeftMouseQuit LeftStr LegacyControl Limit Line LineTo ListItems ListRequest Ln LoadAnim LoadAnimFrame LoadBGPic LoadBrush LoadIcon LoadModule LoadPalette LoadPlugin LoadPrefs LoadSample LoadSprite Locate Log LowerStr MakeButton MakeConsoleChr MakeDate MakeDirectory MakeHostPath MatchPattern Matrix2D Max MD5 MD5Str MemToTable MergeLayers MidStr Min MixBrush MixRGB MixSample Mod ModifyAnimFrames ModifyButton ModifyKeyDown ModifyLayerFrames ModulateBrush ModulatePalette MonitorDirectory MouseX MouseY MoveAnim MoveBrush MoveConsoleWindow MoveDisplay MoveFile MoveLayer MovePointer MoveSprite MoveTextObject MoveTo Mul NearlyEqual NextDirectoryEntry NextFrame NextItem NormalizePath NPrint OilPaintBrush OpenAmigaGuide OpenAnim OpenAudio OpenCatalog OpenConnection OpenConsole OpenDirectory OpenDisplay OpenFile OpenFont OpenMusic OpenResourceMonitor OpenSerialPort OpenURL OpenVideo Pack PadNum Pairs PaletteToGray ParseDate PathItems PathPart PathRequest PathToBrush PatternFindStr PatternFindStrDirect PatternFindStrShort PatternReplaceStr PauseLayer PauseModule PauseMusic PauseTimer PauseVideo Peek PeekClipboard PenArrayToBrush PerformSelector PermissionRequest PerspectiveDistortBrush Pi PixelateBrush PlayAnim PlayAnimDisk PlayLayer PlayModule PlayMusic PlaySample PlaySubsong PlayVideo Plot Poke PolarDistortBrush PollSerialQueue Polygon PopupMenu Pow Print QuantizeBrush Rad RaiseOnError RasterizeBrush RawDiv RawEqual RawGet RawSet ReadBrushPixel ReadByte ReadBytes ReadChr ReadConsoleKey ReadConsoleStr ReadDirectory ReadFloat ReadFunction ReadInt ReadLine ReadMem ReadPen ReadPixel ReadRegistryKey ReadSerialData ReadShort ReadString ReadTable ReceiveData ReceiveUDPData Red ReduceAlphaChannel RefreshConsole RefreshDisplay RefreshLayer RelCurveTo RelLineTo RelMoveTo RemapBrush RemoveBrushPalette RemoveButton RemoveIconImage RemoveItem RemoveKeyDown RemoveLayer RemoveLayerFX RemoveLayers RemoveSprite RemoveSprites Rename RenderLayer RepeatStr ReplaceColors ReplaceStr ResetKeyStates ResetTabs ResetTimer ResolveHostName ResumeCoroutine ResumeLayer ResumeModule ResumeMusic ResumeTimer ResumeVideo ReverseFindStr ReverseStr RewindDirectory RGB RGBArrayToBrush RightStr Rnd RndF RndStrong Rol Ror RotateBrush RotateLayer RotateTextObject Round Rt Run RunCallback RunRexxScript Sar SaveAnim SaveBrush SaveIcon SavePalette SavePrefs SaveSample SaveSnapshot ScaleAnim ScaleBGPic ScaleBrush ScaleLayer ScaleSprite ScaleTextObject ScrollConsole Seek SeekLayer SeekMusic SeekVideo SelectAlphaChannel SelectAnim SelectBGPic SelectBrush SelectConsoleWindow SelectDisplay SelectLayer SelectMask SelectMenuItem SelectPalette SendApplicationMessage SendData SendMessage SendRexxCommand SendUDPData SepiaToneBrush SerializeTable SetAllocConsoleColor SetAlphaIntensity SetAnimFrameDelay SetAttribute SetBaudRate SetBorderPen SetBrushDepth SetBrushPalette SetBrushPen SetBrushTransparency SetBrushTransparentPen SetBulletColor SetBulletPen SetChannelVolume SetClipboard SetClipRegion SetConsoleBackground SetConsoleColor SetConsoleCursor SetConsoleOptions SetConsoleStyle SetConsoleTitle SetCycleTable SetDash SetDataBits SetDefaultAdapter SetDefaultEncoding SetDefaultLoader SetDepth SetDisplayAttributes SetDitherMode SetDrawPen SetDrawTagsDefault SetDTR SetEnv SetEventTimeout SetFileAttributes SetFileEncoding SetFillRule SetFillStyle SetFlowControl SetFont SetFontColor SetFontStyle SetFormStyle SetFPSLimit SetGradientPalette SetIconProperties SetInterval SetIOMode SetLayerAnchor SetLayerBorder SetLayerDepth SetLayerFilter SetLayerLight SetLayerName SetLayerPalette SetLayerPen SetLayerShadow SetLayerStyle SetLayerTint SetLayerTransparency SetLayerTransparentPen SetLayerVolume SetLayerZPos SetLineCap SetLineJoin SetLineWidth SetListItems SetMargins SetMaskMode SetMasterVolume SetMetaTable SetMiterLimit SetMusicVolume SetNetworkProtocol SetNetworkTimeout SetObjectData SetPalette SetPaletteDepth SetPaletteMode SetPalettePen SetPaletteTransparentPen SetPanning SetParity SetPen SetPitch SetPointer SetRTS SetScreenTitle SetSerializeMode SetSerializeOptions SetShadowPen SetSpriteZPos SetStandardIconImage SetStandardPalette SetStopBits SetSubtitle SetTimeout SetTimerElapse SetTitle SetTransparentPen SetTransparentThreshold SetTrayIcon SetVarType SetVectorEngine SetVideoPosition SetVideoSize SetVideoVolume SetVolume SetWBIcon Sgn SharpenBrush Shl ShowConsoleCursor ShowDisplay ShowKeyboard ShowLayer ShowLayerFX ShowNotification ShowPointer ShowRinghioMessage ShowScreen ShowToast Shr Sin Sleep SolarizeBrush SolarizePalette Sort SplitStr Sqrt StartConsoleColorMode StartPath StartSubPath StartsWith StartTimer StopAnim StopChannel StopLayer StopModule StopMusic StopSample StopTimer StopVideo StringRequest StringToFile StripStr StrLen StrStr StrToArray Sub SwapLayers SwirlBrush SystemRequest TableItems TableToMem Tan TextExtent TextHeight TextOut TextWidth TimerElapsed TimestampToDate TintBrush TintPalette ToHostName ToIP ToNumber ToString TouchConsoleWindow ToUserData TransformBox TransformBrush TransformLayer TransformPoint TransformTextObject TranslateLayer TranslatePath TrimBrush TrimStr UndefineVirtualStringFile Undo UndoFX UngroupLayer UnleftStr UnmidStr Unpack UnrightStr UnsetEnv UploadFile UpperStr Usage UseCarriageReturn UseFont UTCToDate Val ValidateDate ValidateStr Vibrate VWait Wait WaitAnimEnd WaitEvent WaitKeyDown WaitLeftMouse WaitMidMouse WaitMusicEnd WaitPatternPosition WaitRightMouse WaitSampleEnd WaitSongPosition WaitTimer WaterRippleBrush WhileKeyDown WhileMouseDown WhileMouseOn WhileRightMouseDown Wrap WriteAnimFrame WriteBrushPixel WriteByte WriteBytes WriteChr WriteFloat WriteFunction WriteInt WriteLine WriteMem WritePen WriteRegistryKey WriteSerialData WriteShort WriteString WriteTable YieldCoroutine
" user-defined constants
syn match hwUserConstant "#\<\u\+\>"
diff --git a/runtime/syntax/html.vim b/runtime/syntax/html.vim
index 605db3ae1c..82c829a2e1 100644
--- a/runtime/syntax/html.vim
+++ b/runtime/syntax/html.vim
@@ -3,7 +3,7 @@
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" Previous Maintainers: Jorge Maldonado Ventura <jorgesumle@freakspot.net>
" Claudio Fleiner <claudio@fleiner.com>
-" Last Change: 2022 Nov 18
+" Last Change: 2023 Feb 20
" Please check :help html.vim for some comments and a description of the options
@@ -221,7 +221,7 @@ if main_syntax != 'java' || exists("java_javascript")
" JAVA SCRIPT
syn include @htmlJavaScript syntax/javascript.vim
unlet b:current_syntax
- syn region javaScript start=+<script\_[^>]*>+ keepend end=+</script\_[^>]*>+me=s-1 contains=@htmlJavaScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
+ syn region javaScript start=+<script\>\_[^>]*>+ keepend end=+</script\_[^>]*>+me=s-1 contains=@htmlJavaScript,htmlCssStyleComment,htmlScriptTag,@htmlPreproc
syn region htmlScriptTag contained start=+<script+ end=+>+ fold contains=htmlTagN,htmlString,htmlArg,htmlValue,htmlTagError,htmlEvent
hi def link htmlScriptTag htmlTag
diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim
index caef244ce5..d4512525f9 100644
--- a/runtime/syntax/i3config.vim
+++ b/runtime/syntax/i3config.vim
@@ -1,9 +1,9 @@
" Vim syntax file
" Language: i3 config file
-" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com>
+" Original Author: Josef Litos (JosefLitos/i3config.vim)
" Maintainer: Quentin Hibon (github user hiqua)
-" Version: 0.4
-" Last Change: 2022 Jun 05
+" Version: 1.0.0
+" Last Change: 2023-11-11
" References:
" http://i3wm.org/docs/userguide.html#configuring
@@ -18,247 +18,320 @@ endif
scriptencoding utf-8
" Error
-syn match i3ConfigError /.*/
+syn match i3ConfigError /.\+/
" Todo
syn keyword i3ConfigTodo TODO FIXME XXX contained
-" Comment
-" Comments are started with a # and can only be used at the beginning of a line
+" Helper type definitions
+syn match i3ConfigSeparator /[,;\\]/ contained
+syn match i3ConfigParen /[{}]/ contained
+syn keyword i3ConfigBoolean yes no enabled disabled on off true false contained
+syn region i3ConfigString start=/\W\@<="/ skip=/\\\("\|$\)/ end=/"\|$/ contained contains=i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigVariable,i3ConfigExecAction keepend extend
+syn region i3ConfigString start=/\W\@<='/ skip=/\\$/ end=/'\|$/ contained contains=i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigVariable,i3ConfigExecAction keepend extend
+syn match i3ConfigColor /#[0-9A-Fa-f]\{3,8}/ contained
+syn match i3ConfigNumber /[0-9A-Za-z_$-]\@<!-\?\d\+\w\@!/ contained
+
+" 4.1 Include directive
+syn keyword i3ConfigIncludeKeyword include contained
+syn match i3ConfigIncludeCommand /`[^`]*`/ contained contains=i3ConfigShDelim,i3ConfigShParam,i3ConfigShOper,i3ConfigShCommand,i3ConfigString
+syn match i3ConfigParamLine /^include .*$/ contains=i3ConfigIncludeKeyword,i3ConfigString,i3ConfigVariable,i3ConfigIncludeCommand,i3ConfigShOper
+
+" 4.2 Comments
syn match i3ConfigComment /^\s*#.*$/ contains=i3ConfigTodo
-" Font
-" A FreeType font description is composed by:
-" a font family, a style, a weight, a variant, a stretch and a size.
-syn match i3ConfigFontSeparator /,/ contained
-syn match i3ConfigFontSeparator /:/ contained
+" 4.3 Fonts
syn keyword i3ConfigFontKeyword font contained
-syn match i3ConfigFontNamespace /\w\+:/ contained contains=i3ConfigFontSeparator
-syn match i3ConfigFontContent /-\?\w\+\(-\+\|\s\+\|,\)/ contained contains=i3ConfigFontNamespace,i3ConfigFontSeparator,i3ConfigFontKeyword
-syn match i3ConfigFontSize /\s\=\d\+\(px\)\?\s\?$/ contained
-syn match i3ConfigFont /^\s*font\s\+.*$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace
-syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace
-syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?[^\\]\+$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace
-syn match i3ConfigFont /^\s*font\s\+\(\(.*\\\_.*\)\|\(.*[^\\]\+$\)\)/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace
-
-" variables
-syn match i3ConfigString /\(['"]\)\(.\{-}\)\1/ contained
-syn match i3ConfigColor /#\w\{6}/ contained
-syn match i3ConfigVariableModifier /+/ contained
-syn match i3ConfigVariableAndModifier /+\w\+/ contained contains=i3ConfigVariableModifier
-syn match i3ConfigVariable /\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?/ contains=i3ConfigVariableModifier,i3ConfigVariableAndModifier
-syn keyword i3ConfigInitializeKeyword set contained
-syn match i3ConfigInitialize /^\s*set\s\+.*$/ contains=i3ConfigVariable,i3ConfigInitializeKeyword,i3ConfigColor,i3ConfigString
-
-" Include
-syn keyword i3ConfigIncludeKeyword include contained
-syn match i3ConfigInclude /^\s*include\s\+.*$/ contains=i3ConfigIncludeKeyword,i3ConfigString,i3ConfigVariable
-
-" Gaps
-syn keyword i3ConfigGapStyleKeyword inner outer horizontal vertical top right bottom left current all set plus minus toggle up down contained
-syn match i3ConfigGapStyle /^\s*\(gaps\)\s\+\(inner\|outer\|horizontal\|vertical\|left\|top\|right\|bottom\)\(\s\+\(current\|all\)\)\?\(\s\+\(set\|plus\|minus\|toggle\)\)\?\(\s\+\(-\?\d\+\|\$.*\)\)$/ contains=i3ConfigGapStyleKeyword,i3ConfigNumber,i3ConfigVariable
-syn keyword i3ConfigSmartGapKeyword on inverse_outer off contained
-syn match i3ConfigSmartGap /^\s*smart_gaps\s\+\(on\|inverse_outer\|off\)\s\?$/ contains=i3ConfigSmartGapKeyword
-syn keyword i3ConfigSmartBorderKeyword on no_gaps contained
-syn match i3ConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\)\s\?$/ contains=i3ConfigSmartBorderKeyword
-
-" Keyboard bindings
-syn keyword i3ConfigAction toggle fullscreen restart key import kill shrink grow contained
-syn keyword i3ConfigAction focus move grow height width split layout resize restore reload mute unmute exit mode workspace container to contained
-syn match i3ConfigModifier /\w\++\w\+\(\(+\w\+\)\+\)\?/ contained contains=i3ConfigVariableModifier
-syn match i3ConfigNumber /\s\d\+/ contained
-syn match i3ConfigUnit /\sp\(pt\|x\)/ contained
-syn match i3ConfigUnitOr /\sor/ contained
-syn keyword i3ConfigBindKeyword bindsym bindcode exec gaps border contained
-syn match i3ConfigBindArgument /--\w\+\(\(-\w\+\)\+\)\?\s/ contained
-syn match i3ConfigBind /^\s*\(bindsym\|bindcode\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword
-
-" Floating
+syn match i3ConfigColonOperator /:/ contained
+syn match i3ConfigFontNamespace /\w\+:/ contained contains=i3ConfigColonOperator
+syn match i3ConfigFontSize / \d\+\(px\)\?\s\?$/ contained
+syn region i3ConfigFont start=/^\s*font / skip=/\\$/ end=/$/ contains=i3ConfigFontKeyword,i3ConfigFontNamespace,i3ConfigFontSize,i3ConfigSeparator keepend
+
+" 4.4-4.5 Keyboard/Mouse bindings
+syn keyword i3ConfigBindKeyword bindsym bindcode contained
+syn match i3ConfigBindArgument /--\(release\|border\|whole-window\|exclude-titlebar\)/ contained
+syn match i3ConfigBindModifier /+/ contained
+syn keyword i3ConfigBindModkey Ctrl Shift Mod1 Mod2 Mod3 Mod4 Mod5 contained
+syn match i3ConfigBindCombo /[$0-9A-Za-z_+]\+ / contained contains=i3ConfigBindModifier,i3ConfigVariable,i3ConfigBindModkey
+syn match i3ConfigBindComboLine /bind\(sym\|code\)\( --[a-z-]\+\)* [$0-9A-Za-z_+]\+ / contained contains=i3ConfigBindKeyword,i3ConfigBindArgument,i3ConfigBindCombo
+syn region i3ConfigBind start=/^\s*bind\(sym\|code\) / skip=/\\$/ end=/$/ contains=i3ConfigBindComboLine,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean keepend
+
+" 4.6 Binding modes
+syn region i3ConfigKeyword start=/^mode\( --pango_markup\)\? \([^'" {]\+\|'[^']\+'\|".\+"\)\s\+{$/ end=/^\s*}$/ contains=i3ConfigShParam,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigNumber,i3ConfigParen,i3ConfigVariable fold keepend extend
+
+" 4.7 Floating modifier
+syn match i3ConfigKeyword /^floating_modifier [$0-9A-Za-z]*$/ contains=i3ConfigVariable,i3ConfigBindModkey
+
+" 4.8 Floating window size
syn keyword i3ConfigSizeSpecial x contained
-syn match i3ConfigNegativeSize /-/ contained
-syn match i3ConfigSize /-\?\d\+\s\?x\s\?-\?\d\+/ contained contains=i3ConfigSizeSpecial,i3ConfigNumber,i3ConfigNegativeSize
-syn match i3ConfigFloatingModifier /^\s*floating_modifier\s\+\$\w\+\d\?/ contains=i3ConfigVariable
-syn match i3ConfigFloating /^\s*floating_\(maximum\|minimum\)_size\s\+-\?\d\+\s\?x\s\?-\?\d\+/ contains=i3ConfigSize
-
-" Orientation
-syn keyword i3ConfigOrientationKeyword vertical horizontal auto contained
-syn match i3ConfigOrientation /^\s*default_orientation\s\+\(vertical\|horizontal\|auto\)\s\?$/ contains=i3ConfigOrientationKeyword
-
-" Layout
-syn keyword i3ConfigLayoutKeyword default stacking tabbed contained
-syn match i3ConfigLayout /^\s*workspace_layout\s\+\(default\|stacking\|tabbed\)\s\?$/ contains=i3ConfigLayoutKeyword
-
-" Border style
-syn keyword i3ConfigBorderStyleKeyword none normal pixel contained
-syn match i3ConfigBorderStyle /^\s*\(new_window\|new_float\|default_border\|default_floating_border\)\s\+\(none\|\(normal\|pixel\)\(\s\+\d\+\)\?\(\s\+\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?\)\?\)\s\?$/ contains=i3ConfigBorderStyleKeyword,i3ConfigNumber,i3ConfigVariable
-
-" Hide borders and edges
-syn keyword i3ConfigEdgeKeyword none vertical horizontal both smart smart_no_gaps contained
-syn match i3ConfigEdge /^\s*hide_edge_borders\s\+\(none\|vertical\|horizontal\|both\|smart\|smart_no_gaps\)\s\?$/ contains=i3ConfigEdgeKeyword
-
-" Arbitrary commands for specific windows (for_window)
-syn keyword i3ConfigCommandKeyword for_window contained
-syn region i3ConfigWindowStringSpecial start=+"+ skip=+\\"+ end=+"+ contained contains=i3ConfigString
-syn region i3ConfigWindowCommandSpecial start="\[" end="\]" contained contains=i3ConfigWindowStringSpacial,i3ConfigString
-syn match i3ConfigArbitraryCommand /^\s*for_window\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigCommandKeyword,i3ConfigBorderStyleKeyword,i3ConfigLayoutKeyword,i3ConfigOrientationKeyword,Size,i3ConfigNumber
-
-" Disable focus open opening
-syn keyword i3ConfigNoFocusKeyword no_focus contained
-syn match i3ConfigDisableFocus /^\s*no_focus\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigNoFocusKeyword
-
-" Move client to specific workspace automatically
-syn keyword i3ConfigAssignKeyword assign contained
-syn match i3ConfigAssignSpecial /→/ contained
-syn match i3ConfigAssign /^\s*assign\s\+.*$/ contains=i3ConfigAssignKeyword,i3ConfigWindowCommandSpecial,i3ConfigAssignSpecial
+syn match i3ConfigSize / -\?\d\+ x -\?\d\+/ contained contains=i3ConfigSizeSpecial,i3ConfigNumber
+syn match i3ConfigKeyword /^floating_\(maximum\|minimum\)_size .*$/ contains=i3ConfigSize
-" X resources
-syn keyword i3ConfigResourceKeyword set_from_resource contained
-syn match i3ConfigResource /^\s*set_from_resource\s\+.*$/ contains=i3ConfigResourceKeyword,i3ConfigWindowCommandSpecial,i3ConfigColor,i3ConfigVariable
+" 4.9 Orientation
+syn keyword i3ConfigOrientationOpts vertical horizontal auto contained
+syn match i3ConfigKeyword /^default_orientation \w*$/ contains=i3ConfigOrientationOpts
-" Auto start applications
-syn keyword i3ConfigExecKeyword exec exec_always contained
-syn match i3ConfigNoStartupId /--no-startup-id/ contained " We are not using i3ConfigBindArgument as only no-startup-id is supported here
-syn match i3ConfigExec /^\s*exec\(_always\)\?\s\+.*$/ contains=i3ConfigExecKeyword,i3ConfigNoStartupId,i3ConfigString
+" 4.10 Layout mode
+syn keyword i3ConfigWorkspaceLayoutOpts default stacking tabbed contained
+syn match i3ConfigKeyword /^workspace_layout \w*$/ contains=i3ConfigWorkspaceLayoutOpts
-" Automatically putting workspaces on specific screens
-syn keyword i3ConfigWorkspaceKeyword workspace contained
-syn keyword i3ConfigOutput output contained
-syn match i3ConfigWorkspace /^\s*workspace\s\+.*$/ contains=i3ConfigWorkspaceKeyword,i3ConfigNumber,i3ConfigString,i3ConfigOutput
+" 4.11 Title alignment
+syn keyword i3ConfigTitleAlignOpts left center right contained
+syn match i3ConfigKeyword /^title_align .*$/ contains=i3ConfigTitleAlignOpts
-" Changing colors
-syn keyword i3ConfigClientColorKeyword client focused focused_inactive unfocused urgent placeholder background contained
-syn match i3ConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable
+" 4.12 Border style
+syn keyword i3ConfigBorderOpts none normal pixel contained
+syn match i3ConfigKeyword /^default\(_floating\)\?_border .*$/ contains=i3ConfigBorderOpts,i3ConfigNumber,i3ConfigVariable
-syn keyword i3ConfigTitleAlignKeyword left center right contained
-syn match i3ConfigTitleAlign /^\s*title_align\s\+.*$/ contains=i3ConfigTitleAlignKeyword
+" 4.13 Hide edge borders
+syn keyword i3ConfigEdgeOpts none vertical horizontal both smart smart_no_gaps contained
+syn match i3ConfigKeyword /^hide_edge_borders \w*$/ contains=i3ConfigEdgeOpts
-" Interprocess communication
-syn match i3ConfigInterprocessKeyword /ipc-socket/ contained
-syn match i3ConfigInterprocess /^\s*ipc-socket\s\+.*$/ contains=i3ConfigInterprocessKeyword
+" 4.14 Smart Borders
+syn keyword i3ConfigSmartBorderOpts no_gaps contained
+syn match i3ConfigKeyword /^smart_borders \(on\|off\|no_gaps\)$/ contains=i3ConfigSmartBorderOpts,i3ConfigBoolean
-" Mouse warping
-syn keyword i3ConfigMouseWarpingKeyword mouse_warping contained
-syn keyword i3ConfigMouseWarpingType output none contained
-syn match i3ConfigMouseWarping /^\s*mouse_warping\s\+\(output\|none\)\s\?$/ contains=i3ConfigMouseWarpingKeyword,i3ConfigMouseWarpingType
+" 4.15 Arbitrary commands
+syn region i3ConfigKeyword start=/^for_window / end=/$/ contains=i3ConfigForWindowKeyword,i3ConfigCriteria keepend
-" Focus follows mouse
-syn keyword i3ConfigFocusFollowsMouseKeyword focus_follows_mouse contained
-syn keyword i3ConfigFocusFollowsMouseType yes no contained
-syn match i3ConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,i3ConfigFocusFollowsMouseType
+" 4.16 No opening focus
+syn match i3ConfigKeyword /^no_focus .*$/ contains=i3ConfigCondition
-" Popups during fullscreen mode
-syn keyword i3ConfigPopupOnFullscreenKeyword popup_during_fullscreen contained
-syn keyword i3ConfigPopuponFullscreenType smart ignore leave_fullscreen contained
-syn match i3ConfigPopupOnFullscreen /^\s*popup_during_fullscreen\s\+\w\+\s\?$/ contains=i3ConfigPopupOnFullscreenKeyword,i3ConfigPopupOnFullscreenType
+" 4.17 Variables
+syn match i3ConfigVariable /\$[0-9A-Za-z_:|[\]-]\+/
+syn keyword i3ConfigSetKeyword set contained
+syn match i3ConfigSet /^set \$.*$/ contains=i3ConfigSetKeyword,i3ConfigVariable,i3ConfigColor,i3ConfigString,i3ConfigNumber,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShParam,i3ConfigShOper,i3ConfigBindModkey
-" Focus wrapping
-syn keyword i3ConfigFocusWrappingKeyword force_focus_wrapping focus_wrapping contained
-syn keyword i3ConfigFocusWrappingType yes no contained
-syn match i3ConfigFocusWrapping /^\s*\(force_\)\?focus_wrapping\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigFocusWrappingKeyword
+" 4.18 X resources
+syn keyword i3ConfigResourceKeyword set_from_resource contained
+syn match i3ConfigParamLine /^set_from_resource\s\+.*$/ contains=i3ConfigResourceKeyword,i3ConfigCondition,i3ConfigColor,i3ConfigVariable,i3ConfigString,i3ConfigNumber
-" Forcing Xinerama
-syn keyword i3ConfigForceXineramaKeyword force_xinerama contained
-syn match i3ConfigForceXinerama /^\s*force_xinerama\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigForceXineramaKeyword
+" 4.19 Assign clients to workspaces
+syn keyword i3ConfigAssignKeyword assign contained
+syn match i3ConfigAssignSpecial /→\|number/ contained
+syn match i3ConfigAssign /^assign .*$/ contains=i3ConfigAssignKeyword,i3ConfigAssignSpecial,i3ConfigCondition,i3ConfigVariable,i3ConfigString,i3ConfigNumber
+
+" 4.20 Executing shell commands
+syn keyword i3ConfigExecKeyword exec contained
+syn keyword i3ConfigExecAlwaysKeyword exec_always contained
+syn match i3ConfigShCmdDelim /\$(/ contained
+syn region i3ConfigShCommand start=/\$(/ end=/)/ contained contains=i3ConfigShCmdDelim,i3ConfigExecAction,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigString,i3ConfigNumber,i3ConfigVariable keepend extend
+syn match i3ConfigShDelim /[[\]{}();`]\+/ contained
+syn match i3ConfigShOper /[<>&|+=~^*!.?]\+/ contained
+syn match i3ConfigShParam /\<-[0-9A-Za-z_-]\+\>/ contained containedin=i3ConfigVar
+syn region i3ConfigExec start=/^\s*exec\(_always\)\?\( --no-startup-id\)\? [^{]/ skip=/\\$/ end=/$/ contains=i3ConfigExecKeyword,i3ConfigExecAlwaysKeyword,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigExecAction keepend
+
+" 4.21 Workspaces per output
+syn keyword i3ConfigWorkspaceKeyword workspace contained
+syn keyword i3ConfigWorkspaceOutput output contained
+syn keyword i3ConfigWorkspaceDir prev next back_and_forth number contained
+syn region i3ConfigWorkspaceLine start=/^workspace / skip=/\\$/ end=/$/ contains=i3ConfigWorkspaceKeyword,i3ConfigNumber,i3ConfigString,i3ConfigGaps,i3ConfigWorkspaceOutput,i3ConfigVariable,i3ConfigBoolean,i3ConfigSeparator keepend
-" Automatic back-and-forth when switching to the current workspace
-syn keyword i3ConfigAutomaticSwitchKeyword workspace_auto_back_and_forth contained
-syn match i3ConfigAutomaticSwitch /^\s*workspace_auto_back_and_forth\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigAutomaticSwitchKeyword
+" 4.22 Changing colors
+syn match i3ConfigDotOperator /\./ contained
+syn keyword i3ConfigClientOpts focused focused_inactive unfocused urgent placeholder background contained
+syn match i3ConfigKeyword /^client\..*$/ contains=i3ConfigDotOperator,i3ConfigClientOpts,i3ConfigColor,i3ConfigVariable
-" Delay urgency hint
-syn keyword i3ConfigTimeUnit ms contained
-syn keyword i3ConfigDelayUrgencyKeyword force_display_urgency_hint contained
-syn match i3ConfigDelayUrgency /^\s*force_display_urgency_hint\s\+\d\+\s\+ms\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDelayUrgencyKeyword,i3ConfigNumber,i3ConfigTimeUnit
+" 4.23 Interprocess communication
+syn match i3ConfigIpcKeyword /ipc-socket/ contained
+syn match i3ConfigParamLine /^ipc-socket .*$/ contains=i3ConfigIpcKeyword
+
+" 4.24 Focus follows mouse
+syn keyword i3ConfigFocusFollowsMouseOpts always contained
+syn match i3ConfigKeyword /^focus_follows_mouse \(yes\|no\|always\)$/ contains=i3ConfigBoolean,i3ConfigFocusFollowsMouseOpts
+
+" 4.25 Mouse warping
+syn keyword i3ConfigMouseWarpingOpts output container none contained
+syn match i3ConfigKeyword /^mouse_warping \w*$/ contains=i3ConfigMouseWarpingOpts
-" Focus on window activation
-syn keyword i3ConfigFocusOnActivationKeyword focus_on_window_activation contained
-syn keyword i3ConfigFocusOnActivationType smart urgent focus none contained
-syn match i3ConfigFocusOnActivation /^\s*focus_on_window_activation\s\+\(smart\|urgent\|focus\|none\)\s\?$/ contains=i3ConfigFocusOnActivationKeyword,i3ConfigFocusOnActivationType
+" 4.26 Popups while fullscreen
+syn keyword i3ConfigPopupFullscreenOpts smart ignore leave_fullscreen contained
+syn match i3ConfigKeyword /^popup_during_fullscreen \w*$/ contains=i3ConfigPopupFullscreenOpts
-" Automatic back-and-forth when switching to the current workspace
-syn keyword i3ConfigDrawingMarksKeyword show_marks contained
-syn match i3ConfigDrawingMarks /^\s*show_marks\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDrawingMarksKeyword
+" 4.27 Focus wrapping
+syn keyword i3ConfigFocusWrappingOpts force workspace contained
+syn match i3ConfigKeyword /^focus_wrapping \(yes\|no\|force\|workspace\)$/ contains=i3ConfigBoolean,i3ConfigFocusWrappingOpts
-" Group mode/bar
-syn keyword i3ConfigBlockKeyword mode bar colors i3bar_command status_command position exec mode hidden_state modifier id position output background statusline tray_output tray_padding separator separator_symbol workspace_min_width workspace_buttons strip_workspace_numbers binding_mode_indicator focused_workspace active_workspace inactive_workspace urgent_workspace binding_mode contained
-syn region i3ConfigBlock start=+^\s*[^#]*s\?{$+ end=+^\s*[^#]*}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend
+" 4.28 Forcing Xinerama
+syn match i3ConfigKeyword /^force_xinerama \(yes\|no\)$/ contains=i3ConfigBoolean
-" Line continuation
-syn region i3ConfigLineCont start=/^.*\\$/ end=/^.*$/ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend
+" 4.29 Automatic workspace back-and-forth
+syn match i3ConfigKeyword /^workspace_auto_back_and_forth \(yes\|no\)$/ contains=i3ConfigBoolean
+
+" 4.30 Delay urgency hint
+syn keyword i3ConfigTimeUnit ms contained
+syn match i3ConfigKeyword /^force_display_urgency_hint \d\+\( ms\)\?$/ contains=i3ConfigNumber,i3ConfigTimeUnit
+
+" 4.31 Focus on window activation
+syn keyword i3ConfigFocusOnActivationOpts smart urgent focus none contained
+syn match i3ConfigKeyword /^focus_on_window_activation \w*$/ contains=i3ConfigFocusOnActivationOpts
+
+" 4.32 Show marks in title
+syn match i3ConfigShowMarks /^show_marks \(yes\|no\)$/ contains=i3ConfigBoolean
+
+" 4.34 Tiling drag
+syn keyword i3ConfigTilingDragOpts modifier titlebar contained
+syn match i3ConfigKeyword /^tiling_drag\( off\|\( modifier\| titlebar\)\{1,2\}\)$/ contains=i3ConfigTilingDragOpts,i3ConfigBoolean
+
+" 4.35 Gaps
+syn keyword i3ConfigGapsOpts inner outer horizontal vertical left right top bottom current all set plus minus toggle contained
+syn region i3ConfigGaps start=/gaps/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigGapsOpts,i3ConfigNumber,i3ConfigVariable,i3ConfigSeparator keepend
+syn match i3ConfigGapsLine /^gaps .*$/ contains=i3ConfigGaps
+syn keyword i3ConfigSmartGapOpts inverse_outer contained
+syn match i3ConfigKeyword /^smart_gaps \(on\|off\|inverse_outer\)$/ contains=i3ConfigSmartGapOpts,i3ConfigBoolean
+
+" 5 Configuring bar
+syn match i3ConfigBarModifier /^\s\+modifier \S\+$/ contained contains=i3ConfigBindModifier,i3ConfigVariable,i3ConfigBindModkey,i3ConfigBarOptVals
+syn keyword i3ConfigBarOpts bar i3bar_command status_command workspace_command mode hidden_state id position output tray_output tray_padding separator_symbol workspace_buttons workspace_min_width strip_workspace_numbers strip_workspace_name binding_mode_indicator padding contained
+syn keyword i3ConfigBarOptVals dock hide invisible show none top bottom primary nonprimary contained
+syn region i3ConfigBarBlock start=/^bar {$/ end=/^}$/ contains=i3ConfigBarOpts,i3ConfigBarOptVals,i3ConfigBarModifier,i3ConfigBind,i3ConfigString,i3ConfigComment,i3ConfigFont,i3ConfigBoolean,i3ConfigNumber,i3ConfigParen,i3ConfigColor,i3ConfigVariable,i3ConfigColorsBlock,i3ConfigShOper,i3ConfigShCommand fold keepend extend
+
+" 5.16 Color block
+syn keyword i3ConfigColorsKeyword colors contained
+syn match i3ConfigColorsOpts /\(focused_\)\?\(background\|statusline\|separator\)\|\(focused\|active\|inactive\|urgent\)_workspace\|binding_mode/ contained
+syn region i3ConfigColorsBlock start=/^\s\+colors {$/ end=/^\s\+}$/ contained contains=i3ConfigColorsKeyword,i3ConfigColorsOpts,i3ConfigColor,i3ConfigVariable,i3ConfigComment,i3ConfigParen fold keepend extend
+
+" 6.0 Command criteria
+syn keyword i3ConfigConditionProp class instance window_role window_type machine id title urgent workspace con_mark con_id floating_from tiling_from contained
+syn keyword i3ConfigConditionSpecial __focused__ all floating tiling contained
+syn region i3ConfigCondition start=/\[/ end=/\]/ contained contains=i3ConfigShDelim,i3ConfigConditionProp,i3ConfigShOper,i3ConfigConditionSpecial,i3ConfigNumber,i3ConfigString keepend extend
+syn region i3ConfigCriteria start=/\[/ skip=/\\$/ end=/\(;\|$\)/ contained contains=i3ConfigCondition,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigBoolean,i3ConfigNumber,i3ConfigVariable,i3ConfigSeparator keepend transparent
+
+" 6.1 Actions through shell
+syn match i3ConfigExecActionKeyword /i3-msg/ contained
+syn region i3ConfigExecAction start=/[a-z3-]\+msg "/ skip=/ "\|\\$/ end=/"\|$/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend extend
+syn region i3ConfigExecAction start=/[a-z3-]\+msg '/ skip=/ '\|\\$/ end=/'\|$/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend extend
+syn region i3ConfigExecAction start=/[a-z3-]\+msg ['"-]\@!/ skip=/\\$/ end=/[&|;})'"]\@=\|$/ contained contains=i3ConfigExecActionKeyword,i3ConfigShCommand,i3ConfigNumber,i3ConfigShOper,i3ConfigCriteria,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigVariable keepend extend
+" 6.1 Executing applications (4.20)
+syn region i3ConfigAction start=/exec/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigExecKeyword,i3ConfigExecAction,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigSeparator keepend
+
+" 6.3 Manipulating layout
+syn keyword i3ConfigLayoutKeyword layout contained
+syn keyword i3ConfigLayoutOpts default tabbed stacking splitv splith toggle split all contained
+syn region i3ConfigAction start=/layout/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigLayoutKeyword,i3ConfigLayoutOpts,i3ConfigSeparator keepend transparent
+
+" 6.4 Focusing containers
+syn keyword i3ConfigFocusKeyword focus contained
+syn keyword i3ConfigFocusOpts left right up down workspace parent child next prev sibling floating tiling mode_toggle contained
+syn keyword i3ConfigFocusOutputOpts left right down up current primary nonprimary next prev contained
+syn region i3ConfigFocusOutput start=/ output / skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigWorkspaceOutput,i3ConfigFocusOutputOpts,i3ConfigString,i3ConfigNumber,i3ConfigSeparator keepend
+syn match i3ConfigFocusOutputLine /^focus output .*$/ contains=i3ConfigFocusKeyword,i3ConfigFocusOutput
+syn region i3ConfigAction start=/focus/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigFocusKeyword,i3ConfigFocusOpts,i3ConfigFocusOutput,i3ConfigString,i3ConfigSeparator keepend transparent
+
+" 6.8 Focusing workspaces (4.21)
+syn region i3ConfigAction start=/workspace / skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigWorkspaceKeyword,i3ConfigWorkspaceDir,i3ConfigNumber,i3ConfigString,i3ConfigGaps,i3ConfigWorkspaceOutput,i3ConfigVariable,i3ConfigBoolean,i3ConfigSeparator keepend transparent
+
+" 6.8.2 Renaming workspaces
+syn keyword i3ConfigRenameKeyword rename contained
+syn region i3ConfigAction start=/rename workspace/ end=/[,;]\|$/ contained contains=i3ConfigRenameKeyword,i3ConfigMoveDir,i3ConfigMoveType,i3ConfigNumber,i3ConfigVariable,i3ConfigString keepend transparent
+
+" 6.5,6.9-6.11 Moving containers
+syn keyword i3ConfigMoveKeyword move contained
+syn keyword i3ConfigMoveDir left right down up position absolute center to current contained
+syn keyword i3ConfigMoveType window container workspace output mark mouse scratchpad contained
+syn match i3ConfigUnit / px\| ppt/ contained
+syn region i3ConfigAction start=/move/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigMoveKeyword,i3ConfigMoveDir,i3ConfigMoveType,i3ConfigWorkspaceDir,i3ConfigUnit,i3ConfigNumber,i3ConfigVariable,i3ConfigString,i3ConfigSeparator,i3ConfigShParam keepend transparent
+
+" 6.12 Resizing containers/windows
+syn keyword i3ConfigResizeKeyword resize contained
+syn keyword i3ConfigResizeOpts grow shrink up down left right set width height or contained
+syn region i3ConfigAction start=/resize/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigResizeKeyword,i3ConfigResizeOpts,i3ConfigNumber,i3ConfigUnit,i3ConfigSeparator keepend transparent
+
+" 6.14 VIM-like marks
+syn match i3ConfigMark /mark\( --\(add\|replace\)\( --toggle\)\?\)\?/ contained contains=i3ConfigShParam
+syn region i3ConfigAction start=/\<mark/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigMark,i3ConfigNumber,i3ConfigString,i3ConfigSeparator keepend transparent
+
+" 6.24 Changing gaps (4.35)
+syn region i3ConfigAction start=/gaps/ skip=/\\$/ end=/[,;]\|$/ contained contains=i3ConfigGaps keepend transparent
+
+" Commands useable in keybinds
+syn keyword i3ConfigActionKeyword mode append_layout kill open fullscreen sticky split floating swap unmark show_marks title_window_icon title_format border restart reload exit scratchpad nop bar contained
+syn keyword i3ConfigOption default enable disable toggle key restore current horizontal vertical auto none normal pixel show container with id con_id padding hidden_state hide dock invisible contained
" Define the highlighting.
hi def link i3ConfigError Error
hi def link i3ConfigTodo Todo
-hi def link i3ConfigComment Comment
-hi def link i3ConfigFontContent Type
-hi def link i3ConfigFocusOnActivationType Type
-hi def link i3ConfigPopupOnFullscreenType Type
-hi def link i3ConfigOrientationKeyword Type
-hi def link i3ConfigMouseWarpingType Type
-hi def link i3ConfigFocusFollowsMouseType Type
-hi def link i3ConfigGapStyleKeyword Type
-hi def link i3ConfigTitleAlignKeyword Type
-hi def link i3ConfigSmartGapKeyword Type
-hi def link i3ConfigSmartBorderKeyword Type
-hi def link i3ConfigLayoutKeyword Type
-hi def link i3ConfigBorderStyleKeyword Type
-hi def link i3ConfigEdgeKeyword Type
-hi def link i3ConfigAction Type
-hi def link i3ConfigCommand Type
-hi def link i3ConfigOutput Type
-hi def link i3ConfigWindowCommandSpecial Type
-hi def link i3ConfigFocusWrappingType Type
-hi def link i3ConfigUnitOr Type
-hi def link i3ConfigFontSize Constant
+hi def link i3ConfigKeyword Keyword
+hi def link i3ConfigCommand Statement
+hi def link i3ConfigParamLine i3ConfigString
+hi def link i3ConfigOperator Operator
+hi def link i3ConfigSeparator i3ConfigOperator
+hi def link i3ConfigParen Delimiter
+hi def link i3ConfigBoolean Boolean
+hi def link i3ConfigString String
hi def link i3ConfigColor Constant
-hi def link i3ConfigNumber Constant
-hi def link i3ConfigUnit Constant
-hi def link i3ConfigVariableAndModifier Constant
-hi def link i3ConfigTimeUnit Constant
-hi def link i3ConfigModifier Constant
-hi def link i3ConfigString Constant
-hi def link i3ConfigNegativeSize Constant
-hi def link i3ConfigInclude Constant
-hi def link i3ConfigFontSeparator Special
-hi def link i3ConfigVariableModifier Special
-hi def link i3ConfigSizeSpecial Special
-hi def link i3ConfigWindowSpecial Special
-hi def link i3ConfigAssignSpecial Special
-hi def link i3ConfigFontNamespace PreProc
-hi def link i3ConfigBindArgument PreProc
-hi def link i3ConfigNoStartupId PreProc
-hi def link i3ConfigIncludeKeyword Identifier
-hi def link i3ConfigFontKeyword Identifier
-hi def link i3ConfigBindKeyword Identifier
-hi def link i3ConfigOrientation Identifier
-hi def link i3ConfigGapStyle Identifier
-hi def link i3ConfigTitleAlign Identifier
-hi def link i3ConfigSmartGap Identifier
-hi def link i3ConfigSmartBorder Identifier
-hi def link i3ConfigLayout Identifier
-hi def link i3ConfigBorderStyle Identifier
-hi def link i3ConfigEdge Identifier
-hi def link i3ConfigFloating Identifier
-hi def link i3ConfigFloatingModifier Identifier
-hi def link i3ConfigCommandKeyword Identifier
-hi def link i3ConfigNoFocusKeyword Identifier
-hi def link i3ConfigInitializeKeyword Identifier
-hi def link i3ConfigAssignKeyword Identifier
-hi def link i3ConfigResourceKeyword Identifier
-hi def link i3ConfigExecKeyword Identifier
-hi def link i3ConfigWorkspaceKeyword Identifier
-hi def link i3ConfigClientColorKeyword Identifier
-hi def link i3ConfigInterprocessKeyword Identifier
-hi def link i3ConfigMouseWarpingKeyword Identifier
-hi def link i3ConfigFocusFollowsMouseKeyword Identifier
-hi def link i3ConfigPopupOnFullscreenKeyword Identifier
-hi def link i3ConfigFocusWrappingKeyword Identifier
-hi def link i3ConfigForceXineramaKeyword Identifier
-hi def link i3ConfigAutomaticSwitchKeyword Identifier
-hi def link i3ConfigDelayUrgencyKeyword Identifier
-hi def link i3ConfigFocusOnActivationKeyword Identifier
-hi def link i3ConfigDrawingMarksKeyword Identifier
-hi def link i3ConfigBlockKeyword Identifier
-hi def link i3ConfigVariable Statement
-hi def link i3ConfigArbitraryCommand Type
+hi def link i3ConfigNumber Number
+hi def link i3ConfigIncludeKeyword i3ConfigKeyword
+hi def link i3ConfigComment Comment
+hi def link i3ConfigFontKeyword i3ConfigKeyword
+hi def link i3ConfigColonOperator i3ConfigOperator
+hi def link i3ConfigFontNamespace i3ConfigOption
+hi def link i3ConfigFontSize i3ConfigNumber
+hi def link i3ConfigFont i3ConfigString
+hi def link i3ConfigBindKeyword i3ConfigKeyword
+hi def link i3ConfigBindArgument i3ConfigShParam
+hi def link i3ConfigBindModifier i3ConfigOperator
+hi def link i3ConfigBindModkey Special
+hi def link i3ConfigBindCombo SpecialChar
+hi def link i3ConfigSizeSpecial i3ConfigOperator
+hi def link i3ConfigOrientationOpts i3ConfigOption
+hi def link i3ConfigWorkspaceLayoutOpts i3ConfigOption
+hi def link i3ConfigTitleAlignOpts i3ConfigOption
+hi def link i3ConfigBorderOpts i3ConfigOption
+hi def link i3ConfigEdgeOpts i3ConfigOption
+hi def link i3ConfigSmartBorderOpts i3ConfigOption
+hi def link i3ConfigVariable Variable
+hi def link i3ConfigSetKeyword i3ConfigKeyword
+hi def link i3ConfigResourceKeyword i3ConfigKeyword
+hi def link i3ConfigAssignKeyword i3ConfigKeyword
+hi def link i3ConfigAssignSpecial i3ConfigOption
+hi def link i3ConfigExecKeyword i3ConfigCommand
+hi def link i3ConfigExecAlwaysKeyword i3ConfigKeyword
+hi def link i3ConfigShParam PreProc
+hi def link i3ConfigShDelim Delimiter
+hi def link i3ConfigShOper Operator
+hi def link i3ConfigShCmdDelim i3ConfigShDelim
+hi def link i3ConfigShCommand Normal
+hi def link i3ConfigWorkspaceKeyword i3ConfigCommand
+hi def link i3ConfigWorkspaceOutput i3ConfigMoveType
+hi def link i3ConfigWorkspaceDir i3ConfigOption
+hi def link i3ConfigDotOperator i3ConfigOperator
+hi def link i3ConfigClientOpts i3ConfigOption
+hi def link i3ConfigIpcKeyword i3ConfigKeyword
+hi def link i3ConfigFocusFollowsMouseOpts i3ConfigOption
+hi def link i3ConfigMouseWarpingOpts i3ConfigOption
+hi def link i3ConfigPopupFullscreenOpts i3ConfigOption
+hi def link i3ConfigFocusWrappingOpts i3ConfigOption
+hi def link i3ConfigTimeUnit i3ConfigNumber
+hi def link i3ConfigFocusOnActivationOpts i3ConfigOption
+hi def link i3ConfigShowMarks i3ConfigCommand
+hi def link i3ConfigTilingDragOpts i3ConfigOption
+hi def link i3ConfigGapsOpts i3ConfigOption
+hi def link i3ConfigGaps i3ConfigCommand
+hi def link i3ConfigSmartGapOpts i3ConfigOption
+hi def link i3ConfigBarModifier i3ConfigKeyword
+hi def link i3ConfigBarOpts i3ConfigKeyword
+hi def link i3ConfigBarOptVals i3ConfigOption
+hi def link i3ConfigColorsKeyword i3ConfigKeyword
+hi def link i3ConfigColorsOpts i3ConfigOption
+hi def link i3ConfigConditionProp i3ConfigShParam
+hi def link i3ConfigConditionSpecial Constant
+hi def link i3ConfigExecActionKeyword i3ConfigShCommand
+hi def link i3ConfigExecAction i3ConfigString
+hi def link i3ConfigLayoutKeyword i3ConfigCommand
+hi def link i3ConfigLayoutOpts i3ConfigOption
+hi def link i3ConfigFocusKeyword i3ConfigCommand
+hi def link i3ConfigFocusOpts i3ConfigOption
+hi def link i3ConfigFocusOutputOpts i3ConfigOption
+hi def link i3ConfigRenameKeyword i3ConfigCommand
+hi def link i3ConfigMoveKeyword i3ConfigCommand
+hi def link i3ConfigMoveDir i3ConfigOption
+hi def link i3ConfigMoveType Constant
+hi def link i3ConfigUnit i3ConfigNumber
+hi def link i3ConfigResizeKeyword i3ConfigCommand
+hi def link i3ConfigResizeOpts i3ConfigOption
+hi def link i3ConfigMark i3ConfigCommand
+hi def link i3ConfigActionKeyword i3ConfigCommand
+hi def link i3ConfigOption Type
let b:current_syntax = "i3config"
diff --git a/runtime/syntax/iss.vim b/runtime/syntax/iss.vim
index 34bb698368..212c0f6dbe 100644
--- a/runtime/syntax/iss.vim
+++ b/runtime/syntax/iss.vim
@@ -2,10 +2,9 @@
" Language: Inno Setup File (iss file) and My InnoSetup extension
" Maintainer: Jason Mills (jmills@cs.mun.ca)
" Previous Maintainer: Dominique Stéphan (dominique@mggen.com)
-" Last Change: 2021 Aug 30
+" Last Change: 2023 Jan 26
"
" Todo:
-" - The parameter String: is matched as flag string (because of case ignore).
" - Pascal scripting syntax is not recognized.
" - Embedded double quotes confuse string matches. e.g. "asfd""asfa"
@@ -17,6 +16,9 @@ endif
" shut case off
syn case ignore
+" match keywords with colon
+syn iskeyword @,48-57,_,192-255,:
+
" Preprocessor
syn region issPreProc start="^\s*#" end="$"
@@ -30,25 +32,25 @@ syn match issDirective "^[^=]\+="
syn match issURL "http[s]\=:\/\/.*$"
" Parameters used for any section.
-" syn match issParam"[^: ]\+:"
-syn match issParam "Name:"
-syn match issParam "MinVersion:\|OnlyBelowVersion:\|Languages:"
-syn match issParam "Source:\|DestDir:\|DestName:\|CopyMode:"
-syn match issParam "Attribs:\|Permissions:\|FontInstall:\|Flags:"
-syn match issParam "FileName:\|Parameters:\|WorkingDir:\|HotKey:\|Comment:"
-syn match issParam "IconFilename:\|IconIndex:"
-syn match issParam "Section:\|Key:\|String:"
-syn match issParam "Root:\|SubKey:\|ValueType:\|ValueName:\|ValueData:"
-syn match issParam "RunOnceId:"
-syn match issParam "Type:\|Excludes:"
-syn match issParam "Components:\|Description:\|GroupDescription:\|Types:\|ExtraDiskSpaceRequired:"
-syn match issParam "StatusMsg:\|RunOnceId:\|Tasks:"
-syn match issParam "MessagesFile:\|LicenseFile:\|InfoBeforeFile:\|InfoAfterFile:"
+" syn match issParam "[^: ]\+:"
+syn keyword issParam Name:
+syn keyword issParam MinVersion: OnlyBelowVersion: Languages:
+syn keyword issParam Source: DestDir: DestName: CopyMode: ExternalSize:
+syn keyword issParam Attribs: Permissions: FontInstall: Flags:
+syn keyword issParam FileName: Parameters: WorkingDir: HotKey: Comment:
+syn keyword issParam IconFilename: IconIndex:
+syn keyword issParam Section: Key: String:
+syn keyword issParam Root: SubKey: ValueType: ValueName: ValueData:
+syn keyword issParam RunOnceId:
+syn keyword issParam Type: Excludes:
+syn keyword issParam Components: Description: GroupDescription: Types: ExtraDiskSpaceRequired:
+syn keyword issParam StatusMsg: RunOnceId: Tasks:
+syn keyword issParam MessagesFile: LicenseFile: InfoBeforeFile: InfoAfterFile:
syn match issComment "^\s*;.*$" contains=@Spell
" folder constant
-syn match issFolder "{[^{]*}" contains=@NoSpell
+syn match issFolder "{\@1<!{[^{]*}" contains=@NoSpell
" string
syn region issString start=+"+ end=+"+ contains=issFolder,@Spell
@@ -61,16 +63,16 @@ syn keyword issFilesCopyMode normal onlyifdoesntexist alwaysoverwrite alwaysskip
syn keyword issFilesAttribs readonly hidden system
syn keyword issFilesPermissions full modify readexec
syn keyword issFilesFlags allowunsafefiles comparetimestampalso confirmoverwrite deleteafterinstall
-syn keyword issFilesFlags dontcopy dontverifychecksum external fontisnttruetype ignoreversion
-syn keyword issFilesFlags isreadme onlyifdestfileexists onlyifdoesntexist overwritereadonly
+syn keyword issFilesFlags dontcopy dontverifychecksum external fontisnttruetype ignoreversion
+syn keyword issFilesFlags isreadme onlyifdestfileexists onlyifdoesntexist overwritereadonly
syn keyword issFilesFlags promptifolder recursesubdirs regserver regtypelib restartreplace
-syn keyword issFilesFlags sharedfile skipifsourcedoesntexist sortfilesbyextension touch
+syn keyword issFilesFlags sharedfile skipifsourcedoesntexist sortfilesbyextension touch
syn keyword issFilesFlags uninsremovereadonly uninsrestartdelete uninsneveruninstall
-syn keyword issFilesFlags replacesameversion nocompression noencryption noregerror
+syn keyword issFilesFlags replacesameversion setntfscompression nocompression noencryption noregerror
" [Icons]
-syn keyword issIconsFlags closeonexit createonlyiffileexists dontcloseonexit
+syn keyword issIconsFlags closeonexit createonlyiffileexists dontcloseonexit
syn keyword issIconsFlags runmaximized runminimized uninsneveruninstall useapppaths
" [INI]
@@ -79,13 +81,13 @@ syn keyword issINIFlags createkeyifdoesntexist uninsdeleteentry uninsdeletesecti
" [Registry]
syn keyword issRegRootKey HKCR HKCU HKLM HKU HKCC
syn keyword issRegValueType none string expandsz multisz dword binary
-syn keyword issRegFlags createvalueifdoesntexist deletekey deletevalue dontcreatekey
-syn keyword issRegFlags preservestringtype noerror uninsclearvalue
+syn keyword issRegFlags createvalueifdoesntexist deletekey deletevalue dontcreatekey
+syn keyword issRegFlags preservestringtype noerror uninsclearvalue
syn keyword issRegFlags uninsdeletekey uninsdeletekeyifempty uninsdeletevalue
" [Run] and [UninstallRun]
syn keyword issRunFlags hidewizard nowait postinstall runhidden runmaximized
-syn keyword issRunFlags runminimized shellexec skipifdoesntexist skipifnotsilent
+syn keyword issRunFlags runminimized shellexec skipifdoesntexist skipifnotsilent
syn keyword issRunFlags skipifsilent unchecked waituntilidle
" [Types]
@@ -98,7 +100,7 @@ syn keyword issComponentsFlags dontinheritcheck exclusive fixed restart disablen
syn keyword issInstallDeleteType files filesandordirs dirifempty
" [Tasks]
-syn keyword issTasksFlags checkedonce dontinheritcheck exclusive restart unchecked
+syn keyword issTasksFlags checkedonce dontinheritcheck exclusive restart unchecked
" Define the default highlighting.
@@ -112,7 +114,7 @@ hi def link issParam Type
hi def link issFolder Special
hi def link issString String
hi def link issURL Include
-hi def link issPreProc PreProc
+hi def link issPreProc PreProc
hi def link issDirsFlags Keyword
hi def link issFilesCopyMode Keyword
diff --git a/runtime/syntax/javascript.vim b/runtime/syntax/javascript.vim
index e513137984..e3b4cdf703 100644
--- a/runtime/syntax/javascript.vim
+++ b/runtime/syntax/javascript.vim
@@ -52,11 +52,11 @@ syn match javaScriptNumber "\<\d\+\(_\d\+\)*\.\(\d\+\(_\d\+\)*\([eE]
syn region javaScriptRegexpString start=+[,(=+]\s*/[^/*]+ms=e-1,me=e-1 skip=+\\\\\|\\/+ end=+/[gimuys]\{0,2\}\s*$+ end=+/[gimuys]\{0,2\}\s*[+;.,)\]}]+me=e-1 end=+/[gimuys]\{0,2\}\s\+\/+me=e-1 contains=@htmlPreproc,javaScriptComment oneline
syn keyword javaScriptConditional if else switch
-syn keyword javaScriptRepeat while for do in
+syn keyword javaScriptRepeat while for do in of
syn keyword javaScriptBranch break continue
syn keyword javaScriptOperator new delete instanceof typeof
syn keyword javaScriptType Array Boolean Date Function Number Object String RegExp
-syn keyword javaScriptStatement return with await
+syn keyword javaScriptStatement return with await yield
syn keyword javaScriptBoolean true false
syn keyword javaScriptNull null undefined
syn keyword javaScriptIdentifier arguments this var let
@@ -103,7 +103,7 @@ hi def link javaScriptStringD String
hi def link javaScriptStringT String
hi def link javaScriptCharacter Character
hi def link javaScriptSpecialCharacter javaScriptSpecial
-hi def link javaScriptNumber javaScriptValue
+hi def link javaScriptNumber Number
hi def link javaScriptConditional Conditional
hi def link javaScriptRepeat Repeat
hi def link javaScriptBranch Conditional
diff --git a/runtime/syntax/json5.vim b/runtime/syntax/json5.vim
new file mode 100644
index 0000000000..5b01d33aad
--- /dev/null
+++ b/runtime/syntax/json5.vim
@@ -0,0 +1,73 @@
+" Vim syntax file
+" Language: JSON5
+" Maintainer: Mazunki Hoksaas rolferen@gmail.com
+" Previous Maintainer: Guten Ye <ywzhaifei@gmail.com>
+" Last Change: 2019 Apr 1
+" Version: vim9.0-1
+" URL: https://github.com/json5/json5
+
+" Syntax setup
+if exists('b:current_syntax') && b:current_syntax == 'json5'
+ finish
+endif
+
+" Numbers
+syn match json5Number "[-+]\=\%(0\|[1-9]\d*\)\%(\.\d*\)\=\%([eE][-+]\=\d\+\)\="
+syn match json5Number "[-+]\=\%(\.\d\+\)\%([eE][-+]\=\d\+\)\="
+syn match json5Number "[-+]\=0[xX]\x*"
+syn match json5Number "[-+]\=Infinity\|NaN"
+
+" An integer part of 0 followed by other digits is not allowed
+syn match json5NumError "[-+]\=0\d\(\d\|\.\)*"
+
+" A hexadecimal number cannot have a fractional part
+syn match json5NumError "[-+]\=0x\x*\.\x*"
+
+" Strings
+syn region json5String start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=json5Escape,@Spell
+syn region json5String start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=json5Escape,@Spell
+
+" Escape sequences
+syn match json5Escape "\\['\"\\bfnrtv]" contained
+syn match json5Escape "\\u\x\{4}" contained
+
+" Boolean
+syn keyword json5Boolean true false
+
+" Null
+syn keyword json5Null null
+
+" Delimiters and Operators
+syn match json5Delimiter ","
+syn match json5Operator ":"
+
+" Braces
+syn match json5Braces "[{}\[\]]"
+
+" Keys
+syn match json5Key /@\?\%(\I\|\$\)\%(\i\|\$\)*\s*\ze::\@!/ contains=@Spell
+syn match json5Key /"\([^"]\|\\"\)\{-}"\ze\s*:/ contains=json5Escape,@Spell
+
+" Comment
+syn region json5LineComment start=+\/\/+ end=+$+ keepend contains=@Spell
+syn region json5LineComment start=+^\s*\/\/+ skip=+\n\s*\/\/+ end=+$+ keepend fold contains=@Spell
+syn region json5Comment start="/\*" end="\*/" fold contains=@Spell
+
+" Define the default highlighting
+hi def link json5String String
+hi def link json5Key Identifier
+hi def link json5Escape Special
+hi def link json5Number Number
+hi def link json5Delimiter Delimiter
+hi def link json5Operator Operator
+hi def link json5Braces Delimiter
+hi def link json5Null Keyword
+hi def link json5Boolean Boolean
+hi def link json5LineComment Comment
+hi def link json5Comment Comment
+hi def link json5NumError Error
+
+if !exists('b:current_syntax')
+ let b:current_syntax = 'json5'
+endif
+
diff --git a/runtime/syntax/kotlin.vim b/runtime/syntax/kotlin.vim
new file mode 100644
index 0000000000..9b85b8ef5c
--- /dev/null
+++ b/runtime/syntax/kotlin.vim
@@ -0,0 +1,157 @@
+" Vim syntax file
+" Language: Kotlin
+" Maintainer: Alexander Udalov
+" URL: https://github.com/udalov/kotlin-vim
+" Last Change: 30 December 2022
+
+if exists('b:current_syntax')
+ finish
+endif
+
+syn keyword ktStatement break continue return
+syn keyword ktConditional if else when
+syn keyword ktRepeat do for while
+syn keyword ktOperator in is by
+syn keyword ktKeyword get set out super this where
+syn keyword ktException try catch finally throw
+
+syn keyword ktInclude import package
+
+" Generated stdlib class names {{{
+" The following is generated by https://github.com/udalov/kotlin-vim/blob/master/extra/generate-stdlib-class-names.main.kts
+syn keyword ktType AbstractCollection AbstractCoroutineContextElement AbstractCoroutineContextKey AbstractDoubleTimeSource AbstractIterator AbstractList AbstractLongTimeSource
+syn keyword ktType AbstractMap AbstractMutableCollection AbstractMutableList AbstractMutableMap AbstractMutableSet AbstractSet AccessDeniedException Accessor Annotation
+syn keyword ktType AnnotationRetention AnnotationTarget Any Appendable ArithmeticException Array ArrayDeque ArrayList AssertionError Boolean BooleanArray BooleanIterator
+syn keyword ktType BuilderInference Byte ByteArray ByteIterator CName CallsInPlace CancellationException Char CharArray CharCategory CharDirectionality CharIterator CharProgression
+syn keyword ktType CharRange CharSequence CharacterCodingException Charsets ClassCastException Cloneable ClosedFloatingPointRange ClosedRange Collection Comparable
+syn keyword ktType ComparableTimeMark Comparator ConcurrentModificationException ConditionalEffect ContextFunctionTypeParams Continuation ContinuationInterceptor ContractBuilder
+syn keyword ktType CopyActionContext CopyActionResult CoroutineContext DeepRecursiveFunction DeepRecursiveScope Delegates Deprecated DeprecatedSinceKotlin DeprecationLevel
+syn keyword ktType Destructured Double DoubleArray DoubleIterator DslMarker Duration DurationUnit Effect Element EmptyCoroutineContext Entry Enum EnumEntries Error Exception
+syn keyword ktType ExperimentalContracts ExperimentalJsExport ExperimentalMultiplatform ExperimentalObjCName ExperimentalObjCRefinement ExperimentalPathApi ExperimentalStdlibApi
+syn keyword ktType ExperimentalSubclassOptIn ExperimentalTime ExperimentalTypeInference ExperimentalUnsignedTypes ExtensionFunctionType FileAlreadyExistsException
+syn keyword ktType FileSystemException FileTreeWalk FileVisitorBuilder FileWalkDirection Float FloatArray FloatIterator FreezingIsDeprecated Function Function0 Function1 Function10
+syn keyword ktType Function11 Function12 Function13 Function14 Function15 Function16 Function17 Function18 Function19 Function2 Function20 Function21 Function22 Function3 Function4
+syn keyword ktType Function5 Function6 Function7 Function8 Function9 FunctionN Getter Grouping HashMap HashSet HiddenFromObjC HidesFromObjC Ignore IllegalArgumentException
+syn keyword ktType IllegalStateException IndexOutOfBoundsException IndexedValue Int IntArray IntIterator IntProgression IntRange InvocationKind Iterable Iterator JsExport JsName
+syn keyword ktType JvmDefault JvmDefaultWithCompatibility JvmDefaultWithoutCompatibility JvmField JvmInline JvmMultifileClass JvmName JvmOverloads JvmRecord JvmSerializableLambda
+syn keyword ktType JvmStatic JvmSuppressWildcards JvmSynthetic JvmWildcard KAnnotatedElement KCallable KClass KClassifier KDeclarationContainer KFunction KMutableProperty
+syn keyword ktType KMutableProperty0 KMutableProperty1 KMutableProperty2 KParameter KProperty KProperty0 KProperty1 KProperty2 KType KTypeParameter KTypeProjection KVariance
+syn keyword ktType KVisibility Key Kind KotlinNullPointerException KotlinReflectionNotSupportedError KotlinVersion Lazy LazyThreadSafetyMode Level LinkedHashMap LinkedHashSet List
+syn keyword ktType ListIterator Long LongArray LongIterator LongProgression LongRange Map MatchGroup MatchGroupCollection MatchNamedGroupCollection MatchResult Metadata Monotonic
+syn keyword ktType MustBeDocumented MutableCollection MutableEntry MutableIterable MutableIterator MutableList MutableListIterator MutableMap MutableSet NoSuchElementException
+syn keyword ktType NoSuchFileException NoWhenBranchMatchedException NotImplementedError Nothing NullPointerException Number NumberFormatException ObjCName ObservableProperty
+syn keyword ktType OnErrorAction OnErrorResult OpenEndRange OptIn OptionalExpectation OverloadResolutionByLambdaReturnType Pair ParameterName PathWalkOption
+syn keyword ktType PropertyDelegateProvider PublishedApi PurelyImplements Random RandomAccess ReadOnlyProperty ReadWriteProperty RefinesInSwift Regex RegexOption Repeatable
+syn keyword ktType ReplaceWith RequiresOptIn RestrictsSuspension Result Retention Returns ReturnsNotNull RuntimeException Sequence SequenceScope Set Setter SharedImmutable Short
+syn keyword ktType ShortArray ShortIterator ShouldRefineInSwift SimpleEffect SinceKotlin Strictfp String StringBuilder SubclassOptInRequired Suppress Synchronized Target
+syn keyword ktType TestTimeSource ThreadLocal Throwable Throws TimeMark TimeSource TimedValue Transient Triple TypeCastException Typography UByte UByteArray UInt UIntArray
+syn keyword ktType UIntProgression UIntRange ULong ULongArray ULongProgression ULongRange UShort UShortArray UninitializedPropertyAccessException Unit UnsafeVariance
+syn keyword ktType UnsupportedOperationException ValueTimeMark Volatile WithComparableMarks
+" }}}
+
+syn keyword ktModifier annotation companion enum inner abstract final open override sealed vararg dynamic expect actual suspend
+syn keyword ktStructure class object interface typealias fun val var constructor init
+
+syn keyword ktReservedKeyword typeof
+
+syn keyword ktBoolean true false
+syn keyword ktConstant null
+
+syn keyword ktModifier reified external inline noinline crossinline
+
+syn match ktModifier "\v<data>\ze\@=.*<(class|object)>"
+syn match ktModifier "\v<value>\ze\@=.*<class>"
+syn match ktModifier "\v<(tailrec|operator|infix)>\ze\@=.*<fun>"
+syn match ktModifier "\v<const>\ze\@=.*<val>"
+syn match ktModifier "\v<lateinit>\ze\@=.*<var>"
+syn match ktModifier "\v<(internal|private|protected|public)>\ze\@=.*<(class|object|interface|typealias|fun|val|var|constructor|get|set)>"
+
+syn match ktOperator "\v\?:|::|\<\=? | \>\=?|[!=]\=\=?|<as>\??|[-*+/%]\=?|[!&|]"
+
+syn keyword ktTodo TODO FIXME XXX contained
+syn match ktShebang "\v^#!.*$"
+syn match ktLineComment "\v//.*$" contains=ktTodo,@Spell
+syn region ktComment matchgroup=ktCommentMatchGroup start="/\*" end="\*/" contains=ktComment,ktTodo,@Spell
+
+syn region ktDocComment start="/\*\*" end="\*/" contains=ktDocTag,ktTodo,@Spell
+syn match ktDocTag "\v\@(author|constructor|receiver|return|since|suppress)>" contained
+syn match ktDocTag "\v\@(exception|param|property|throws|see|sample)>\s*\S+" contains=ktDocTagParam contained
+syn match ktDocTagParam "\v(\s|\[)\S+" contained
+syn match ktComment "/\*\*/"
+
+syn match ktSpecialCharError "\v\\." contained
+syn match ktSpecialChar "\v\\([tbnr'"$\\]|u\x{4})" contained
+syn region ktString start='"' skip='\\"' end='"' contains=ktSimpleInterpolation,ktComplexInterpolation,ktSpecialChar,ktSpecialCharError,@Spell
+syn region ktString start='"""' end='""""*' contains=ktSimpleInterpolation,ktComplexInterpolation,@Spell
+syn match ktCharacter "\v'[^']*'" contains=ktSpecialChar,ktSpecialCharError
+syn match ktCharacter "\v'\\''" contains=ktSpecialChar
+syn match ktCharacter "\v'[^\\]'"
+
+syn match ktAnnotation "\v(\w)@<!\@[[:alnum:]_.]*(:[[:alnum:]_.]*)?"
+syn match ktLabel "\v\w+\@"
+syn match ktLabel "\v(\w)@<=\@\w+"
+
+syn match ktSimpleInterpolation "\v\$\h\w*" contained
+syn region ktComplexInterpolation matchgroup=ktComplexInterpolationBrace start="\v\$\{" end="\v\}" contains=ALLBUT,ktSimpleInterpolation,ktTodo,ktSpecialCharError,ktSpecialChar,ktDocTag,ktDocTagParam
+
+syn match ktNumber "\v<\d+[_[:digit:]]*(uL?|UL?|[LFf])?"
+syn match ktNumber "\v<0[Xx]\x+[_[:xdigit:]]*(uL?|UL?|L)?"
+syn match ktNumber "\v<0[Bb][01]+[_01]*(uL?|UL?|L)?"
+syn match ktFloat "\v<\d*(\d[eE][-+]?\d+|\.\d+([eE][-+]?\d+)?)[Ff]?"
+
+syn match ktEscapedName "\v`.*`"
+
+syn match ktExclExcl "!!"
+syn match ktArrow "->"
+
+syn region ktFold start="{" end="}" transparent fold
+
+exec "syntax sync ccomment ktComment minlines=10"
+
+hi def link ktStatement Statement
+hi def link ktConditional Conditional
+hi def link ktRepeat Repeat
+hi def link ktOperator Operator
+hi def link ktKeyword Keyword
+hi def link ktException Exception
+hi def link ktReservedKeyword Error
+
+hi def link ktInclude Include
+
+hi def link ktType Type
+hi def link ktModifier StorageClass
+hi def link ktStructure Structure
+hi def link ktTypedef Typedef
+
+hi def link ktBoolean Boolean
+hi def link ktConstant Constant
+
+hi def link ktTodo Todo
+hi def link ktShebang Comment
+hi def link ktLineComment Comment
+hi def link ktComment Comment
+hi def link ktCommentMatchGroup Comment
+hi def link ktDocComment Comment
+hi def link ktDocTag Special
+hi def link ktDocTagParam Identifier
+
+hi def link ktSpecialChar SpecialChar
+hi def link ktSpecialCharError Error
+hi def link ktString String
+hi def link ktCharacter Character
+
+hi def link ktAnnotation Identifier
+hi def link ktLabel Identifier
+
+hi def link ktSimpleInterpolation Identifier
+hi def link ktComplexInterpolationBrace Identifier
+
+hi def link ktNumber Number
+hi def link ktFloat Float
+
+hi def link ktExclExcl Special
+hi def link ktArrow Structure
+
+let b:current_syntax = 'kotlin'
+
+" vim:foldmethod=marker
diff --git a/runtime/syntax/krl.vim b/runtime/syntax/krl.vim
index a50790841e..6808a48fc4 100644
--- a/runtime/syntax/krl.vim
+++ b/runtime/syntax/krl.vim
@@ -2,7 +2,7 @@
" Language: Kuka Robot Language
" Maintainer: Patrick Meiser-Knosowski <knosowski@graeffrobotics.de>
" Version: 3.0.0
-" Last Change: 18. Apr 2022
+" Last Change: 22. Jun 2023
" Credits: Thanks for contributions to this to Michael Jagusch
" Thanks for beta testing to Thomas Baginski
"
@@ -109,11 +109,11 @@ highlight default link krlGeomOperator Operator
" Type, StorageClass and Typedef {{{
" Simple data types
-syn keyword krlType bool char real int containedin=krlAnyType
+syn keyword krlType bool char real int
" External program and function
-syn keyword krlType ext extfct extfctp extp containedin=krlAnyType
+syn keyword krlType ext extfct extfctp extp
" Communication
-syn keyword krlType signal channel containedin=krlAnyType
+syn keyword krlType signal channel
highlight default link krlType Type
" StorageClass
syn keyword krlStorageClass decl global const struc enum
@@ -200,19 +200,20 @@ syn keyword krlEnum adap_acc model_type control_parameter eko_mode
"
" Predefined structures and enums found in /steu/mada/$custom.dat
syn keyword krlStructure pro_io_t ser ext_mod_t coop_krc ws_config bin_type coop_update_t ldc_reaction
-syn keyword krlEnum axis_of_coordinates spline_para_variant target_status cp_vel_type cp_statmon
+syn keyword krlEnum axis_of_coordinates motion_mode spline_para_variant spreadstartpolicy target_status cp_vel_type cp_statmon
"
" Predefined structures and enums found in /steu/mada/$machine.dat
syn keyword krlStructure emstop_path boxstatesafein boxstatesafeout
syn keyword krlEnum digincode
"
" Predefined structures and enums found in /steu/mada/$option.dat
-syn keyword krlStructure msg_t
+syn keyword krlStructure installed_motion_modes msg_t
+syn keyword krlEnum step_enum
" syn keyword krlEnum
"
" Predefined structures and enums found in /r1/system/$config.dat
" BasisTech
-syn keyword krlStructure dig_out_type ctrl_in_t ctrl_out_t fct_out_t fct_in_t odat basis_sugg_t out_sugg_t md_state machine_def_t machine_tool_t machine_frame_t trigger_para constvel_para condstop_para adat tm_sugg_t tqm_tqdat_t sps_prog_type
+syn keyword krlStructure dig_out_type ctrl_in_t ctrl_out_t fct_out_t fct_in_t odat hdat basis_sugg_t out_sugg_t md_state machine_def_t machine_tool_t machine_frame_t trigger_para constvel_para condstop_para adat tm_sugg_t tqm_tqdat_t sps_prog_type
syn keyword krlEnum bas_command out_modetype ipo_m_t apo_mode_t funct_type p00_command timer_actiontype
"
" GripperTech
@@ -271,7 +272,9 @@ highlight default link krlStatement Statement
syn keyword krlConditional if then else endif switch case default endswitch skip endskip
highlight default link krlConditional Conditional
" Repeat
-syn keyword krlRepeat for to step endfor while endwhile repeat until loop endloop exit
+syn keyword krlRepeat for to endfor while endwhile repeat until loop endloop exit
+" STEP is used as variable in VKRC, this pattern should match STEP -, 5(constant number) or VAR
+syn match krlRepeat /\v\cstep\s+%(-|\w)/me=e-1
highlight default link krlRepeat Repeat
" Label
syn keyword krlLabel goto
@@ -390,7 +393,7 @@ if get(g:, 'krlShowError', 1)
" some more or less common typos
"
" vars or funcs >24 chars are not possible in krl. a234567890123456789012345
- syn match krlError0 /\w\{25,}/ containedin=krlFunction,krlNames,krlLabel,krlAnyType,krlEnumVal,krlSysvars
+ syn match krlError0 /\w\{25,}/ containedin=krlFunction,krlNames,krlLabel,krlEnumVal,krlSysvars
"
" should be interrupt (on|off) \w+
syn match krlError1 /\vinterrupt[ \t(]+[_$a-zA-Z0-9]+[_$a-zA-Z0-9.\[\]()+\-*/]*[ \t)]+o%(n|ff)>/
diff --git a/runtime/syntax/lc.vim b/runtime/syntax/lc.vim
new file mode 100644
index 0000000000..a334529385
--- /dev/null
+++ b/runtime/syntax/lc.vim
@@ -0,0 +1,31 @@
+" Vim syntax file
+" Language: Elsa
+" Maintainer: Miles Glapa-Grossklag <miles@glapa-grossklag.com>
+" Last Change: 2023-01-29
+
+if exists('b:current_syntax')
+ finish
+endif
+
+" Keywords
+syntax keyword elsaKeyword let eval
+syntax match elsaKeyword "\v:"
+highlight link elsaKeyword Keyword
+
+" Comments
+setlocal commentstring=--%s
+syntax match elsaComment "\v--.*$"
+highlight link elsaComment Comment
+
+" Operators
+syntax match elsaOperator "\v\="
+syntax match elsaOperator "\v\=[abd*~]\>"
+syntax match elsaOperator "\v-\>"
+syntax match elsaOperator "\v\\"
+highlight link elsaOperator Operator
+
+" Definitions
+syntax match elsaConstant "\v[A-Z]+[A-Z_0-9]*"
+highlight link elsaConstant Constant
+
+let b:current_syntax = 'elsa'
diff --git a/runtime/syntax/ld.vim b/runtime/syntax/ld.vim
index 6a117ee87b..7ac050131b 100644
--- a/runtime/syntax/ld.vim
+++ b/runtime/syntax/ld.vim
@@ -2,6 +2,7 @@
" Language: ld(1) script
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2006-04-19
+" Last Change: 2023 Apr 19
if exists("b:current_syntax")
finish
@@ -43,7 +44,7 @@ syn match ldSpecial '/DISCARD/'
syn keyword ldIdentifier ORIGIN LENGTH
syn match ldSpecSections '\.'
-syn match ldSections '\.\S\+'
+syn match ldSections '\.[^ \t)]\+'
syn match ldSpecSections '\.\%(text\|data\|bss\|symver\)\>'
syn match ldNumber display '\<0[xX]\x\+\>'
diff --git a/runtime/syntax/lite.vim b/runtime/syntax/lite.vim
index a8d26892d4..f6e41e7e18 100644
--- a/runtime/syntax/lite.vim
+++ b/runtime/syntax/lite.vim
@@ -5,7 +5,7 @@
" Email: Subject: send syntax_vim.tgz
" Last Change: 2001 Mai 01
"
-" Options lite_sql_query = 1 for SQL syntax highligthing inside strings
+" Options lite_sql_query = 1 for SQL syntax highlighting inside strings
" lite_minlines = x to sync at least x lines backwards
" quit when a syntax file was already loaded
diff --git a/runtime/syntax/livebook.vim b/runtime/syntax/livebook.vim
new file mode 100644
index 0000000000..133cab01e3
--- /dev/null
+++ b/runtime/syntax/livebook.vim
@@ -0,0 +1,8 @@
+" Placeholder Livebook syntax file.
+" This simply uses the markdown syntax.
+
+if exists("b:current_syntax")
+ finish
+endif
+
+runtime! syntax/markdown.vim
diff --git a/runtime/syntax/logtalk.vim b/runtime/syntax/logtalk.vim
index a7fe9ce925..bc70ef41b4 100644
--- a/runtime/syntax/logtalk.vim
+++ b/runtime/syntax/logtalk.vim
@@ -330,7 +330,7 @@ syn match logtalkKeyword "\<t\(an\|runcate\)\ze("
syn match logtalkKeyword "\<ceiling\ze("
-" Other arithemtic functors
+" Other arithmetic functors
syn match logtalkOperator "\*\*"
syn match logtalkKeyword "\<s\(in\|qrt\)\ze("
diff --git a/runtime/syntax/lss.vim b/runtime/syntax/lss.vim
index 6ee717bcb4..eceaf75674 100644
--- a/runtime/syntax/lss.vim
+++ b/runtime/syntax/lss.vim
@@ -9,7 +9,7 @@ if exists("b:current_syntax")
endif
" This setup is probably atypical for a syntax highlighting file, because
-" most of it is not really intended to be overrideable. Instead, the
+" most of it is not really intended to be overridable. Instead, the
" highlighting is supposed to correspond to the highlighting specified by
" the .lss file entries themselves; ie. the "bold" keyword should be bold,
" the "red" keyword should be red, and so forth. The exceptions to this
diff --git a/runtime/syntax/luau.vim b/runtime/syntax/luau.vim
new file mode 100644
index 0000000000..59eccac100
--- /dev/null
+++ b/runtime/syntax/luau.vim
@@ -0,0 +1,15 @@
+" Vim syntax file
+" Language: Luau
+" Maintainer: None yet
+" Last Change: 2023 Apr 30
+
+if exists("b:current_syntax")
+ finish
+endif
+
+" Luau is a superset of lua
+runtime! syntax/lua.vim
+
+let b:current_syntax = "luau"
+
+" vim: nowrap sw=2 sts=2 ts=8 noet:
diff --git a/runtime/syntax/lynx.vim b/runtime/syntax/lynx.vim
index fa7c26f629..fcaf923ebe 100644
--- a/runtime/syntax/lynx.vim
+++ b/runtime/syntax/lynx.vim
@@ -1,9 +1,9 @@
" Vim syntax file
-" Language: Lynx configuration file (lynx.cfg)
+" Language: Lynx Web Browser Configuration (lynx.cfg)
" Maintainer: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2013 Jun 20
+" Last Change: 2023 Nov 09
-" Lynx 2.8.7
+" Lynx 2.8.9
if exists("b:current_syntax")
finish
@@ -12,34 +12,35 @@ endif
let s:cpo_save = &cpo
set cpo&vim
-syn match lynxStart "^" transparent skipwhite nextgroup=lynxOption
+syn match lynxStart "^" skipwhite nextgroup=lynxOption
-syn match lynxComment "\(^\|\s\+\)#.*$" contains=lynxTodo
+syn match lynxComment "\%(^\|\s\+\)#.*" contains=lynxTodo
syn keyword lynxTodo TODO NOTE FIXME XXX contained
-syn match lynxDelimiter ":" skipwhite nextgroup=lynxBoolean,lynxNumber,lynxNone,lynxRCOption
+syn match lynxDelimiter ":" skipwhite nextgroup=lynxBoolean,lynxHttpProtocol,lynxNumber,lynxNone,lynxRCOption
syn case ignore
syn keyword lynxBoolean TRUE FALSE ON OFF contained
-syn keyword lynxNone NONE contained
+syn keyword lynxNone NONE contained
syn case match
-syn match lynxNumber "-\=\<\d\+\>" contained
+syn match lynxNumber "-\=\<\d\+\>" contained
+syn match lynxHttpProtocol "\<1\.[01]\>" contained
"{{{ Options
syn case ignore
-syn keyword lynxOption ACCEPT_ALL_COOKIES ALERTSECS ALWAYS_RESUBMIT_POSTS
- \ ALWAYS_TRUSTED_EXEC ANONFTP_PASSWORD ASSUMED_COLOR
- \ ASSUMED_DOC_CHARSET_CHOICE ASSUME_CHARSET ASSUME_LOCAL_CHARSET
- \ ASSUME_UNREC_CHARSET AUTO_SESSION AUTO_UNCACHE_DIRLISTS BAD_HTML
- \ BIBP_BIBHOST BIBP_GLOBAL_SERVER BLOCK_MULTI_BOOKMARKS BOLD_H1
- \ BOLD_HEADERS BOLD_NAME_ANCHORS BOOKMARK_FILE BROKEN_FTP_EPSV
- \ BROKEN_FTP_RETR BZIP2_PATH CASE_SENSITIVE_ALWAYS_ON
- \ CASE_SENSITIVE_SEARCHING CHARACTER_SET CHARSETS_DIRECTORY
- \ CHARSET_SWITCH_RULES CHECKMAIL CHMOD_PATH COLLAPSE_BR_TAGS COLOR
- \ COLOR_STYLE COMPRESS_PATH CONNECT_TIMEOUT COOKIE_ACCEPT_DOMAINS
- \ COOKIE_FILE COOKIE_LOOSE_INVALID_DOMAINS
+syn keyword lynxOption ACCEPT_ALL_COOKIES ALERTSECS
+ \ ALWAYS_RESUBMIT_POSTS ALWAYS_TRUSTED_EXEC ANONFTP_PASSWORD
+ \ ASSUMED_COLOR ASSUMED_DOC_CHARSET_CHOICE ASSUME_CHARSET
+ \ ASSUME_LOCAL_CHARSET ASSUME_UNREC_CHARSET AUTO_SESSION
+ \ AUTO_UNCACHE_DIRLISTS BAD_HTML BIBP_BIBHOST BIBP_GLOBAL_SERVER
+ \ BLOCK_MULTI_BOOKMARKS BOLD_H1 BOLD_HEADERS BOLD_NAME_ANCHORS
+ \ BOOKMARK_FILE BROKEN_FTP_EPSV BROKEN_FTP_RETR BZIP2_PATH
+ \ CASE_SENSITIVE_ALWAYS_ON CASE_SENSITIVE_SEARCHING CHARACTER_SET
+ \ CHARSETS_DIRECTORY CHARSET_SWITCH_RULES CHECKMAIL CHMOD_PATH
+ \ COLLAPSE_BR_TAGS COLOR COLOR_STYLE COMPRESS_PATH CONNECT_TIMEOUT
+ \ COOKIE_ACCEPT_DOMAINS COOKIE_FILE COOKIE_LOOSE_INVALID_DOMAINS
\ COOKIE_QUERY_INVALID_DOMAINS COOKIE_REJECT_DOMAINS COOKIE_SAVE_FILE
\ COOKIE_STRICT_INVALID_DOMAINS COPY_PATH CSO_PROXY CSWING_PATH
\ DEBUGSECS DEFAULT_BOOKMARK_FILE DEFAULT_CACHE_SIZE DEFAULT_COLORS
@@ -97,35 +98,45 @@ syn keyword lynxOption ACCEPT_ALL_COOKIES ALERTSECS ALWAYS_RESUBMIT_POSTS
\ USE_FIXED_RECORDS USE_MOUSE USE_SELECT_POPUPS UUDECODE_PATH
\ VERBOSE_IMAGES VIEWER VISITED_LINKS VI_KEYS VI_KEYS_ALWAYS_ON
\ WAIS_PROXY XHTML_PARSING XLOADIMAGE_COMMAND ZCAT_PATH ZIP_PATH
+ \ TRIM_BLANK_LINES GUESS_SCHEME HTTP_PROTOCOL HTML5_CHARSETS
+ \ TRIM_BLANK_LINES PREFERRED_CONTENT_TYPE SSL_CLIENT_CERT_FILE
+ \ SSL_CLIENT_KEY_FILE MAX_URI_SIZE UNIQUE_URLS MESSAGE_LANGUAGE
+ \ CONV_JISX0201KANA WAIT_VIEWER_TERMINATION BLAT_MAIL ALT_BLAT_MAIL
+ \ DONT_WRAP_PRE TRACK_INTERNAL_LINKS FORCE_HTML HIDDENLINKS SHORT_URL
+ \ LISTONLY LIST_INLINE LOCALHOST WITH_BACKSPACES
\ contained nextgroup=lynxDelimiter
syn keyword lynxRCOption accept_all_cookies assume_charset auto_session
\ bookmark_file case_sensitive_searching character_set
- \ cookie_accept_domains cookie_file cookie_loose_invalid_domains
- \ cookie_query_invalid_domains cookie_reject_domains
+ \ collapse_br_tags cookie_accept_domains cookie_file
+ \ cookie_loose_invalid_domains cookie_query_invalid_domains
+ \ cookie_reject_domains cookie_strict_invalid_domain
\ cookie_strict_invalid_domains dir_list_style display emacs_keys
\ file_editor file_sorting_method force_cookie_prompt force_ssl_prompt
- \ ftp_passive kblayout keypad_mode lineedit_mode locale_charset
- \ make_links_for_all_images make_pseudo_alts_for_inlines
- \ multi_bookmark no_pause personal_mail_address preferred_charset
- \ preferred_encoding preferred_language preferred_media_types raw_mode
- \ run_all_execution_links run_execution_links_on_local_files scrollbar
- \ select_popups send_useragent session_file set_cookies show_color
- \ show_cursor show_dotfiles show_kb_rate sub_bookmarks tagsoup
- \ underline_links user_mode useragent verbose_images vi_keys
- \ visited_links
+ \ ftp_passive html5_charsets http_protocol kblayout keypad_mode
+ \ lineedit_mode locale_charset make_links_for_all_images
+ \ make_pseudo_alts_for_inlines multi_bookmark no_pause
+ \ personal_mail_address preferred_charset preferred_encoding
+ \ preferred_language preferred_media_types raw_mode
+ \ run_all_execution_links run_execution_links_local
+ \ run_execution_links_on_local_files scrollbar select_popups
+ \ send_useragent session_file set_cookies show_color show_cursor
+ \ show_dotfiles show_kb_rate sub_bookmarks tagsoup underline_links
+ \ useragent user_mode verbose_images vi_keys visited_links
\ contained nextgroup=lynxDelimiter
syn case match
" }}}
" cfg2html.pl formatting directives
syn match lynxFormatDir "^\.h\d\s.*$"
-syn match lynxFormatDir "^\.\(ex\|nf\)\(\s\+\d\+\)\=$"
+syn match lynxFormatDir "^\.\%(ex\|nf\)\%(\s\+\d\+\)\=$"
syn match lynxFormatDir "^\.fi$"
+syn match lynxFormatDir "^\.url\>"
hi def link lynxBoolean Boolean
hi def link lynxComment Comment
hi def link lynxDelimiter Special
hi def link lynxFormatDir Special
+hi def link lynxHttpProtocol Constant
hi def link lynxNone Constant
hi def link lynxNumber Number
hi def link lynxOption Identifier
@@ -137,4 +148,4 @@ let b:current_syntax = "lynx"
let &cpo = s:cpo_save
unlet s:cpo_save
-" vim: ts=8 fdm=marker:
+" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker:
diff --git a/runtime/syntax/manual.vim b/runtime/syntax/manual.vim
index c0e53fa7b4..8388336f25 100644
--- a/runtime/syntax/manual.vim
+++ b/runtime/syntax/manual.vim
@@ -1,6 +1,7 @@
" Vim syntax support file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2016 Feb 01
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This file is used for ":syntax manual".
" It installs the Syntax autocommands, but no the FileType autocommands.
diff --git a/runtime/syntax/masm.vim b/runtime/syntax/masm.vim
index 3be0fd45d1..85e457106d 100644
--- a/runtime/syntax/masm.vim
+++ b/runtime/syntax/masm.vim
@@ -2,7 +2,7 @@
" Language: Microsoft Macro Assembler (80x86)
" Orig Author: Rob Brady <robb@datatone.com>
" Maintainer: Wu Yongwei <wuyongwei@gmail.com>
-" Last Change: 2022-04-24 20:07:04 +0800
+" Last Change: 2023-09-09 20:48:26 +0800
" Quit when a syntax file was already loaded
if exists("b:current_syntax")
@@ -194,8 +194,8 @@ syn keyword masmRegister R8W R9W R10W R11W R12W R13W R14W R15W
syn keyword masmRegister R8B R9B R10B R11B R12B R13B R14B R15B
" SSE/AVX registers
-syn match masmRegister "\(X\|Y\)MM[0-9]\>"
-syn match masmRegister "\(X\|Y\)MM1[0-5]\>"
+syn match masmRegister "\(X\|Y\|Z\)MM[12]\?[0-9]\>"
+syn match masmRegister "\(X\|Y\|Z\)MM3[01]\>"
" Instruction prefixes
syn keyword masmOpcode LOCK REP REPE REPNE REPNZ REPZ
@@ -338,11 +338,192 @@ syn keyword masmOpcode VINSERTF128 VEXTRACTF128 VMASKMOVPS VMASKMOVPD
syn keyword masmOpcode VPERMILPS VPERMILPD VPERM2F128
syn keyword masmOpcode VZEROALL VZEROUPPER
+" AVX-2 (Haswell and later)
+syn keyword masmOpcode VPBROADCASTB VPBROADCASTW VPBROADCASTD
+syn keyword masmOpcode VPBROADCASTQ VBROADCASTI128
+syn keyword masmOpcode VINSERTI128 VEXTRACTI128
+syn keyword masmOpcode VGATHERDPD VGATHERQPD VGATHERDPS VGATHERQPS
+syn keyword masmOpcode VPGATHERDD VPGATHERDQ VPGATHERQD VPGATHERQQ
+syn keyword masmOpcode VPMASKMOVD VPMASKMOVQ
+syn keyword masmOpcode PERMPS VPERMD VPERMPD VPERMQ VPERM2I128
+syn keyword masmOpcode VPBLENDD VPSLLVD VPSLLVQ VPSRLVD VPSRLVQ
+syn keyword masmOpcode VPSRAVD
+
+" AVX-512 (Knights Landing/Skylake-X and later)
+syn keyword masmOpcode KAND KANDN KMOV KUNPCK KNOT KOR KORTEST
+syn keyword masmOpcode KSHIFTL KSHIFTR KXNOR KXOR KADD KTEST
+syn keyword masmOpcode VBLENDMPD VBLENDMPS
+syn keyword masmOpcode VPBLENDMD VPBLENDMQ VPBLENDMB VPBLENDMW
+syn keyword masmOpcode VPCMPD VPCMPUD VPCMPQ VPCMPUQ
+syn keyword masmOpcode VPCMPB VPCMPUB VPCMPW VPCMPUW
+syn keyword masmOpcode VPTESTMD VPTESTMQ VPTESTNMD VPTESTNMQ
+syn keyword masmOpcode VPTESTMB VPTESTMW VPTESTNMB VPTESTNMW
+syn keyword masmOpcode VCOMPRESSPD VCOMPRESSPS VPCOMPRESSD VPCOMPRESSQ
+syn keyword masmOpcode VEXPANDPD VEXPANDPS VPEXPANDD VPEXPANDQ
+syn keyword masmOpcode VPERMB VPERMW VPERMT2B VPERMT2W VPERMI2PD
+syn keyword masmOpcode VPERMI2PS VPERMI2D VPERMI2Q VPERMI2B VPERMI2W
+syn keyword masmOpcode VPERMT2PS VPERMT2PD VPERMT2D VPERMT2Q
+syn keyword masmOpcode VSHUFF32x4 VSHUFF64x2 VSHUFI32x4 VSHUFI64x2
+syn keyword masmOpcode VPMULTISHIFTQB VPTERNLOGD VPTERNLOGQ
+syn keyword masmOpcode VPMOVQD VPMOVSQD VPMOVUSQD VPMOVQW VPMOVSQW
+syn keyword masmOpcode VPMOVUSQW VPMOVQB VPMOVSQB VPMOVUSQB VPMOVDW
+syn keyword masmOpcode VPMOVSDW VPMOVUSDW VPMOVDB VPMOVSDB VPMOVUSDB
+syn keyword masmOpcode VPMOVWB VPMOVSWB VPMOVUSWB
+syn keyword masmOpcode VCVTPS2UDQ VCVTPD2UDQ VCVTTPS2UDQ VCVTTPD2UDQ
+syn keyword masmOpcode VCVTSS2USI VCVTSD2USI VCVTTSS2USI VCVTTSD2USI
+syn keyword masmOpcode VCVTPS2QQ VCVTPD2QQ VCVTPS2UQQ VCVTPD2UQQ
+syn keyword masmOpcode VCVTTPS2QQ VCVTTPD2QQ VCVTTPS2UQQ VCVTTPD2UQQ
+syn keyword masmOpcode VCVTUDQ2PS VCVTUDQ2PD VCVTUSI2PS VCVTUSI2PD
+syn keyword masmOpcode VCVTUSI2SD VCVTUSI2SS VCVTUQQ2PS VCVTUQQ2PD
+syn keyword masmOpcode VCVTQQ2PD VCVTQQ2PS VGETEXPPD
+syn keyword masmOpcode VGETEXPPS VGETEXPSD VGETEXPSS
+syn keyword masmOpcode VGETMANTPD VGETMANTPS VGETMANTSD VGETMANTSS
+syn keyword masmOpcode VFIXUPIMMPD VFIXUPIMMPS VFIXUPIMMSD VFIXUPIMMSS
+syn keyword masmOpcode VRCP14PD VRCP14PS VRCP14SD VRCP14SS
+syn keyword masmOpcode VRNDSCALEPS VRNDSCALEPD VRNDSCALESS VRNDSCALESD
+syn keyword masmOpcode VRSQRT14PD VRSQRT14PS VRSQRT14SD VRSQRT14SS
+syn keyword masmOpcode VSCALEFPS VSCALEFPD VSCALEFSS VSCALEFSD
+syn keyword masmOpcode VBROADCASTI32X2 VBROADCASTI32X4 VBROADCASTI32X8
+syn keyword masmOpcode VBROADCASTI64X2 VBROADCASTI64X4
+syn keyword masmOpcode VALIGND VALIGNQ VDBPSADBW VPABSQ VPMAXSQ
+syn keyword masmOpcode VPMAXUQ VPMINSQ VPMINUQ VPROLD VPROLVD VPROLQ
+syn keyword masmOpcode VPROLVQ VPRORD VPRORVD VPRORQ VPRORVQ
+syn keyword masmOpcode VPSCATTERDD VPSCATTERDQ VPSCATTERQD VPSCATTERQQ
+syn keyword masmOpcode VSCATTERDPS VSCATTERDPD VSCATTERQPS VSCATTERQPD
+syn keyword masmOpcode VPCONFLICTD VPCONFLICTQ VPLZCNTD VPLZCNTQ
+syn keyword masmOpcode VPBROADCASTMB2Q VPBROADCASTMW2D
+syn keyword masmOpcode VEXP2PD VEXP2PS
+syn keyword masmOpcode VRCP28PD VRCP28PS VRCP28SD VRCP28SS
+syn keyword masmOpcode VRSQRT28PD VRSQRT28PS VRSQRT28SD VRSQRT28SS
+syn keyword masmOpcode VGATHERPF0DPS VGATHERPF0QPS VGATHERPF0DPD
+syn keyword masmOpcode VGATHERPF0QPD VGATHERPF1DPS VGATHERPF1QPS
+syn keyword masmOpcode VGATHERPF1DPD VGATHERPF1QPD VSCATTERPF0DPS
+syn keyword masmOpcode VSCATTERPF0QPS VSCATTERPF0DPD VSCATTERPF0QPD
+syn keyword masmOpcode VSCATTERPF1DPS VSCATTERPF1QPS VSCATTERPF1DPD
+syn keyword masmOpcode VSCATTERPF1QPD
+syn keyword masmOpcode V4FMADDPS V4FMADDSS V4FNMADDPS V4FNMADDSS
+syn keyword masmOpcode VP4DPWSSD VP4DPWSSDS
+syn keyword masmOpcode VFPCLASSPS VFPCLASSPD VFPCLASSSS VFPCLASSSD
+syn keyword masmOpcode VRANGEPS VRANGEPD VRANGESS VRANGESD
+syn keyword masmOpcode VREDUCEPS VREDUCEPD VREDUCESS VREDUCESD
+syn keyword masmOpcode VPMOVM2D VPMOVM2Q VPMOVM2B VPMOVM2W VPMOVD2M
+syn keyword masmOpcode VPMOVQ2M VPMOVB2M VPMOVW2M VPMULLQ
+syn keyword masmOpcode VPCOMPRESSB VPCOMPRESSW VPEXPANDB VPEXPANDW
+syn keyword masmOpcode VPSHLD VPSHLDV VPSHRD VPSHRDV
+syn keyword masmOpcode VPDPBUSD VPDPBUSDS VPDPWSSD VPDPWSSDS
+syn keyword masmOpcode VPMADD52LUQ VPMADD52HUQ
+syn keyword masmOpcode VPOPCNTD VPOPCNTQ VPOPCNTB VPOPCNTW
+syn keyword masmOpcode VPSHUFBITQMB VP2INTERSECTD VP2INTERSECTQ
+syn keyword masmOpcode VGF2P8AFFINEINVQB VGF2P8AFFINEQB
+syn keyword masmOpcode VGF2P8MULB VPCLMULQDQ
+syn keyword masmOpcode VAESDEC VAESDECLAST VAESENC VAESENCLAST
+syn keyword masmOpcode VCVTNE2PS2BF16 VCVTNEPS2BF16 VDPBF16PS
+syn keyword masmOpcode VADDPH VADDSH VSUBPH VSUBSH VMULPH VMULSH
+syn keyword masmOpcode VDIVPH VDIVSH VSQRTPH VSQRTSH
+syn keyword masmOpcode VFMADD132PH VFMADD213PH VFMADD231PH
+syn keyword masmOpcode VFMADD132SH VFMADD213SH VFMADD231SH
+syn keyword masmOpcode VFNMADD132PH VFNMADD213PH VFNMADD231PH
+syn keyword masmOpcode VFNMADD132SH VFNMADD213SH VFNMADD231SH
+syn keyword masmOpcode VFMSUB132PH VFMSUB213PH VFMSUB231PH
+syn keyword masmOpcode VFMSUB132SH VFMSUB213SH VFMSUB231SH
+syn keyword masmOpcode VFNMSUB132PH VFNMSUB213PH VFNMSUB231PH
+syn keyword masmOpcode VFNMSUB132SH VFNMSUB213SH VFNMSUB231SH
+syn keyword masmOpcode VFMADDSUB132PH VFMADDSUB213PH VFMADDSUB231PH
+syn keyword masmOpcode VFMSUBADD132PH VFMSUBADD213PH VFMSUBADD231PH
+syn keyword masmOpcode VREDUCEPH VREDUCESH VRNDSCALEPH VRNDSCALESH
+syn keyword masmOpcode VSCALEFPH VSCALEFSH VFMULCPH VFMULCSH VFCMULCPH
+syn keyword masmOpcode VFCMULCSH VFMADDCPH VFMADDCSH VFCMADDCPH
+syn keyword masmOpcode VFCMADDCSH VRCPPH VRCPSH VRSQRTPH VRSQRTSH
+syn keyword masmOpcode VCMPPH VCMPSH VCOMISH VUCOMISH VMAXPH VMAXSH
+syn keyword masmOpcode VMINPH VMINSH VFPCLASSPH VFPCLASSSH
+syn keyword masmOpcode VCVTW2PH VCVTUW2PH VCVTDQ2PH VCVTUDQ2PH
+syn keyword masmOpcode VCVTQQ2PH VCVTUQQ2PH VCVTPS2PHX VCVTPD2PH
+syn keyword masmOpcode VCVTSI2SH VCVTUSI2SH VCVTSS2SH VCVTSD2SH
+syn keyword masmOpcode VCVTPH2W VCVTTPH2W VCVTPH2UW VCVTTPH2UW
+syn keyword masmOpcode VCVTPH2DQ VCVTTPH2DQ VCVTPH2UDQ VCVTTPH2UDQ
+syn keyword masmOpcode VCVTPH2QQ VCVTTPH2QQ VCVTPH2UQQ VCVTTPH2UQQ
+syn keyword masmOpcode VCVTPH2PSX VCVTPH2PD VCVTSH2SI VCVTTSH2SI
+syn keyword masmOpcode VCVTSH2USI VCVTTSH2USI VCVTSH2SS VCVTSH2SD
+syn keyword masmOpcode VGETEXPPH VGETEXPSH VGETMANTPH VGETMANTSH
+syn keyword masmOpcode VMOVSH VMOVW VADDPD VADDPS VADDSD VADDSS
+syn keyword masmOpcode VANDPD VANDPS VANDNPD VANDNPS
+syn keyword masmOpcode VCMPPD VCMPPS VCMPSD VCMPSS
+syn keyword masmOpcode VCOMISD VCOMISS VDIVPD VDIVPS VDIVSD VDIVSS
+syn keyword masmOpcode VCVTDQ2PD VCVTDQ2PS VCVTPD2DQ VCVTPD2PS
+syn keyword masmOpcode VCVTPH2PS VCVTPS2PH VCVTPS2DQ VCVTPS2PD
+syn keyword masmOpcode VCVTSD2SI VCVTSD2SS VCVTSI2SD VCVTSI2SS
+syn keyword masmOpcode VCVTSS2SD VCVTSS2SI VCVTTPD2DQ VCVTTPS2DQ
+syn keyword masmOpcode VCVTTSD2SI VCVTTSS2SI VMAXPD VMAXPS
+syn keyword masmOpcode VMAXSD VMAXSS VMINPD VMINPS VMINSD VMINSS
+syn keyword masmOpcode VMOVAPD VMOVAPS VMOVD VMOVQ VMOVDDUP
+syn keyword masmOpcode VMOVHLPS VMOVHPD VMOVHPS VMOVLHPS VMOVLPD
+syn keyword masmOpcode VMOVLPS VMOVNTDQA VMOVNTDQ VMOVNTPD VMOVNTPS
+syn keyword masmOpcode VMOVSD VMOVSHDUP VMOVSLDUP VMOVSS VMOVUPD
+syn keyword masmOpcode VMOVUPS VMOVDQA32 VMOVDQA64 VMOVDQU8
+syn keyword masmOpcode VMOVDQU16 VMOVDQU32 VMOVDQU64 VMULPD VMULPS
+syn keyword masmOpcode VMULSD VMULSS VORPD VORPS VSQRTPD VSQRTPS
+syn keyword masmOpcode VSQRTSD VSQRTSS VSUBPD VSUBPS VSUBSD VSUBSS
+syn keyword masmOpcode VUCOMISD VUCOMISS VUNPCKHPD VUNPCKHPS VUNPCKLPD
+syn keyword masmOpcode VUNPCKLPS VXORPD VXORPS VEXTRACTPS VINSERTPS
+syn keyword masmOpcode VPEXTRB VPEXTRW VPEXTRD VPEXTRQ VPINSRB VPINSRW
+syn keyword masmOpcode VPINSRD VPINSRQ VPACKSSWB VPACKSSDW VPACKUSDW
+syn keyword masmOpcode VPACKUSWB VPADDB VPADDW VPADDD VPADDQ VPADDSB
+syn keyword masmOpcode VPADDSW VPADDUSB VPADDUSW VPANDD VPANDQ VPANDND
+syn keyword masmOpcode VPANDNQ VPAVGB VPAVGW VPCMPEQB VPCMPEQW
+syn keyword masmOpcode VPCMPEQD VPCMPEQQ VPCMPGTB VPCMPGTW VPCMPGTD
+syn keyword masmOpcode VPCMPGTQ VPMAXSB VPMAXSW VPMAXSD VPMAXSQ
+syn keyword masmOpcode VPMAXUB VPMAXUW VPMAXUD VPMAXUQ VPMINSB VPMINSW
+syn keyword masmOpcode VPMINSD VPMINSQ VPMINUB VPMINUW VPMINUD VPMINUQ
+syn keyword masmOpcode VPMOVSXBW VPMOVSXBD VPMOVSXBQ VPMOVSXWD
+syn keyword masmOpcode VPMOVSXWQ VPMOVSXDQ VPMOVZXBW VPMOVZXBD
+syn keyword masmOpcode VPMOVZXBQ VPMOVZXWD VPMOVZXWQ VPMOVZXDQ VPMULDQ
+syn keyword masmOpcode VPMULUDQ VPMULHRSW VPMULHUW VPMULHW VPMULLD
+syn keyword masmOpcode VPMULLQ VPMULLW VPORD VPORQ VPSUBB VPSUBW
+syn keyword masmOpcode VPSUBD VPSUBQ VPSUBSB VPSUBSW VPSUBUSB VPSUBUSW
+syn keyword masmOpcode VPUNPCKHBW VPUNPCKHWD VPUNPCKHDQ VPUNPCKHQDQ
+syn keyword masmOpcode VPUNPCKLBW VPUNPCKLWD VPUNPCKLDQ VPUNPCKLQDQ
+syn keyword masmOpcode VPXORD VPXORQ VPSADBW VPSHUFB VPSHUFHW VPSHUFLW
+syn keyword masmOpcode VPSHUFD VPSLLDQ VPSLLW VPSLLD VPSLLQ VPSRAW
+syn keyword masmOpcode VPSRAD VPSRAQ VPSRLDQ VPSRLW VPSRLD VPSRLQ
+syn keyword masmOpcode VPSLLVW VPSRLVW VPSHUFPD VPSHUFPS VEXTRACTF32X4
+syn keyword masmOpcode VEXTRACTF64X2 VEXTRACTF32X8 VEXTRACTF64X4
+syn keyword masmOpcode VEXTRACTI32X4 VEXTRACTI64X2 VEXTRACTI32X8
+syn keyword masmOpcode VEXTRACTI64X4 VINSERTF32x4 VINSERTF64X2
+syn keyword masmOpcode VINSERTF32X8 VINSERTF64x4 VINSERTI32X4
+syn keyword masmOpcode VINSERTI64X2 VINSERTI32X8 VINSERTI64X4
+syn keyword masmOpcode VPABSB VPABSW VPABSD VPABSQ VPALIGNR
+syn keyword masmOpcode VPMADDUBSW VPMADDWD
+syn keyword masmOpcode VFMADD132PD VFMADD213PD VFMADD231PD
+syn keyword masmOpcode VFMADD132PS VFMADD213PS VFMADD231PS
+syn keyword masmOpcode VFMADD132SD VFMADD213SD VFMADD231SD
+syn keyword masmOpcode VFMADD132SS VFMADD213SS VFMADD231SS
+syn keyword masmOpcode VFMADDSUB132PD VFMADDSUB213PD VFMADDSUB231PD
+syn keyword masmOpcode VFMADDSUB132PS VFMADDSUB213PS VFMADDSUB231PS
+syn keyword masmOpcode VFMSUBADD132PD VFMSUBADD213PD VFMSUBADD231PD
+syn keyword masmOpcode VFMSUBADD132PS VFMSUBADD213PS VFMSUBADD231PS
+syn keyword masmOpcode VFMSUB132PD VFMSUB213PD VFMSUB231PD
+syn keyword masmOpcode VFMSUB132PS VFMSUB213PS VFMSUB231PS
+syn keyword masmOpcode VFMSUB132SD VFMSUB213SD VFMSUB231SD
+syn keyword masmOpcode VFMSUB132SS VFMSUB213SS VFMSUB231SS
+syn keyword masmOpcode VFNMADD132PD VFNMADD213PD VFNMADD231PD
+syn keyword masmOpcode VFNMADD132PS VFNMADD213PS VFNMADD231PS
+syn keyword masmOpcode VFNMADD132SD VFNMADD213SD VFNMADD231SD
+syn keyword masmOpcode VFNMADD132SS VFNMADD213SS VFNMADD231SS
+syn keyword masmOpcode VFNMSUB132PD VFNMSUB213PD VFNMSUB231PD
+syn keyword masmOpcode VFNMSUB132PS VFNMSUB213PS VFNMSUB231PS
+syn keyword masmOpcode VFNMSUB132SD VFNMSUB213SD VFNMSUB231SD
+syn keyword masmOpcode VFNMSUB132SS VFNMSUB213SS VFNMSUB231SS
+syn keyword masmOpcode VPSRAVW VPSRAVQ
+
" Other opcodes in Pentium and later processors
syn keyword masmOpcode CMPXCHG8B CPUID UD2
syn keyword masmOpcode RSM RDMSR WRMSR RDPMC RDTSC SYSENTER SYSEXIT
syn match masmOpcode "CMOV\(P[EO]\|\(N\?\([ABGL]E\?\|[CEOPSZ]\)\)\)\>"
+" Not really used by MASM, but useful for viewing GCC-generated assembly code
+" in Intel syntax
+syn match masmHexadecimal "[-+]\?0[Xx]\x*"
+syn keyword masmOpcode MOVABS
" The default highlighting
hi def link masmLabel PreProc
diff --git a/runtime/syntax/meson.vim b/runtime/syntax/meson.vim
index 0af0d776f8..4eaf696322 100644
--- a/runtime/syntax/meson.vim
+++ b/runtime/syntax/meson.vim
@@ -3,7 +3,7 @@
" License: VIM License
" Maintainer: Nirbheek Chauhan <nirbheek.chauhan@gmail.com>
" Liam Beguin <liambeguin@gmail.com>
-" Last Change: 2021 Aug 16
+" Last Change: 2023 May 27
" Credits: Zvezdan Petkovic <zpetkovic@acm.org>
" Neil Schemenauer <nas@meson.ca>
" Dmitry Vasiliev
@@ -68,6 +68,7 @@ syn keyword mesonBuiltin
\ add_global_link_arguments
\ add_languages
\ add_project_arguments
+ \ add_project_dependencies
\ add_project_link_arguments
\ add_test_setup
\ alias_target
@@ -99,6 +100,7 @@ syn keyword mesonBuiltin
\ install_headers
\ install_man
\ install_subdir
+ \ install_symlink
\ install_emptydir
\ is_disabler
\ is_variable
@@ -115,6 +117,7 @@ syn keyword mesonBuiltin
\ shared_library
\ shared_module
\ static_library
+ \ structured_sources
\ subdir
\ subdir_done
\ subproject
@@ -125,6 +128,7 @@ syn keyword mesonBuiltin
\ vcs_tag
\ warning
\ range
+ \ debug
if exists("meson_space_error_highlight")
" trailing whitespace
@@ -146,7 +150,7 @@ hi def link mesonEscape Special
hi def link mesonNumber Number
hi def link mesonBuiltin Function
hi def link mesonBoolean Boolean
-if exists("meson_space_error_higlight")
+if exists("meson_space_error_highlight")
hi def link mesonSpaceError Error
endif
diff --git a/runtime/syntax/model.vim b/runtime/syntax/model.vim
index 5f3b7f8721..2df380c629 100644
--- a/runtime/syntax/model.vim
+++ b/runtime/syntax/model.vim
@@ -1,10 +1,11 @@
" Vim syntax file
" Language: Model
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2005 Jun 20
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" very basic things only (based on the vgrindefs file).
-" If you use this language, please improve it, and send me the patches!
+" If you use this language, please improve it, and send patches!
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/modula3.vim b/runtime/syntax/modula3.vim
index 390a1a90ff..67243db600 100644
--- a/runtime/syntax/modula3.vim
+++ b/runtime/syntax/modula3.vim
@@ -84,9 +84,7 @@ syn case ignore
let s:digits = "0123456789ABCDEF"
for s:radix in range(2, 16)
- " Nvim does not support interpolated strings yet.
- " exe $'syn match modula3Integer "\<{s:radix}_[{s:digits[:s:radix - 1]}]\+L\=\>"'
- exe 'syn match modula3Integer "\<' .. s:radix .. '_[' .. s:digits[:s:radix - 1] .. ']\+L\=\>"'
+ exe $'syn match modula3Integer "\<{s:radix}_[{s:digits[:s:radix - 1]}]\+L\=\>"'
endfor
unlet s:digits s:radix
diff --git a/runtime/syntax/mojo.vim b/runtime/syntax/mojo.vim
new file mode 100644
index 0000000000..b7dae24a15
--- /dev/null
+++ b/runtime/syntax/mojo.vim
@@ -0,0 +1,316 @@
+" Vim syntax file
+" Language: Mojo
+" Maintainer: Mahmoud Abduljawad <me@mahmoudajawad.com>
+" Last Change: 2023 Sep 09
+" Credits: Mahmoud Abduljawad <me@mahmoudajawad.com>
+" Neil Schemenauer <nas@python.ca>
+" Dmitry Vasiliev
+"
+" This is based on Vim Python highlighting
+"
+" - introduced highlighting of doctests
+" - updated keywords, built-ins, and exceptions
+" - corrected regular expressions for
+"
+" * functions
+" * decorators
+" * strings
+" * escapes
+" * numbers
+" * space error
+"
+" - corrected synchronization
+" - more highlighting is ON by default, except
+" - space error highlighting is OFF by default
+"
+" Optional highlighting can be controlled using these variables.
+"
+" let mojo_no_builtin_highlight = 1
+" let mojo_no_doctest_code_highlight = 1
+" let mojo_no_doctest_highlight = 1
+" let mojo_no_exception_highlight = 1
+" let mojo_no_number_highlight = 1
+" let mojo_space_error_highlight = 1
+"
+" All the options above can be switched on together.
+"
+" let mojo_highlight_all = 1
+"
+" The use of Python 2 compatible syntax highlighting can be enforced.
+" The straddling code (Python 2 and 3 compatible), up to Python 3.5,
+" will be also supported.
+"
+" let mojo_use_python2_syntax = 1
+"
+" This option will exclude all modern Python 3.6 or higher features.
+"
+
+" quit when a syntax file was already loaded.
+if exists("b:current_syntax")
+ finish
+endif
+
+" We need nocompatible mode in order to continue lines with backslashes.
+" Original setting will be restored.
+let s:cpo_save = &cpo
+set cpo&vim
+
+if exists("mojo_no_doctest_highlight")
+ let mojo_no_doctest_code_highlight = 1
+endif
+
+if exists("mojo_highlight_all")
+ if exists("mojo_no_builtin_highlight")
+ unlet mojo_no_builtin_highlight
+ endif
+ if exists("mojo_no_doctest_code_highlight")
+ unlet mojo_no_doctest_code_highlight
+ endif
+ if exists("mojo_no_doctest_highlight")
+ unlet mojo_no_doctest_highlight
+ endif
+ if exists("mojo_no_exception_highlight")
+ unlet mojo_no_exception_highlight
+ endif
+ if exists("mojo_no_number_highlight")
+ unlet mojo_no_number_highlight
+ endif
+ let mojo_space_error_highlight = 1
+endif
+
+" These keywords are based on Python syntax highlight, and adds to it struct,
+" fn, alias, var, let
+"
+syn keyword mojoStatement False None True
+syn keyword mojoStatement as assert break continue del global
+syn keyword mojoStatement lambda nonlocal pass return with yield
+syn keyword mojoStatement class def nextgroup=mojoFunction skipwhite
+syn keyword mojoStatement struct fn nextgroup=mojoFunction skipwhite
+syn keyword mojoStatement alias var let
+syn keyword mojoConditional elif else if
+syn keyword mojoRepeat for while
+syn keyword mojoOperator and in is not or
+syn keyword mojoException except finally raise try
+syn keyword mojoInclude from import
+syn keyword mojoAsync async await
+
+" Soft keywords
+" These keywords do not mean anything unless used in the right context.
+" See https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords
+" for more on this.
+syn match mojoConditional "^\s*\zscase\%(\s\+.*:.*$\)\@="
+syn match mojoConditional "^\s*\zsmatch\%(\s\+.*:\s*\%(#.*\)\=$\)\@="
+
+" Decorators
+" A dot must be allowed because of @MyClass.myfunc decorators.
+syn match mojoDecorator "@" display contained
+syn match mojoDecoratorName "@\s*\h\%(\w\|\.\)*" display contains=pythonDecorator
+
+" Python 3.5 introduced the use of the same symbol for matrix multiplication:
+" https://www.python.org/dev/peps/pep-0465/. We now have to exclude the
+" symbol from highlighting when used in that context.
+" Single line multiplication.
+syn match mojoMatrixMultiply
+ \ "\%(\w\|[])]\)\s*@"
+ \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue
+ \ transparent
+" Multiplication continued on the next line after backslash.
+syn match mojoMatrixMultiply
+ \ "[^\\]\\\s*\n\%(\s*\.\.\.\s\)\=\s\+@"
+ \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue
+ \ transparent
+" Multiplication in a parenthesized expression over multiple lines with @ at
+" the start of each continued line; very similar to decorators and complex.
+syn match mojoMatrixMultiply
+ \ "^\s*\%(\%(>>>\|\.\.\.\)\s\+\)\=\zs\%(\h\|\%(\h\|[[(]\).\{-}\%(\w\|[])]\)\)\s*\n\%(\s*\.\.\.\s\)\=\s\+@\%(.\{-}\n\%(\s*\.\.\.\s\)\=\s\+@\)*"
+ \ contains=ALLBUT,mojoDecoratorName,mojoDecorator,mojoFunction,mojoDoctestValue
+ \ transparent
+
+syn match mojoFunction "\h\w*" display contained
+
+syn match mojoComment "#.*$" contains=mojoTodo,@Spell
+syn keyword mojoTodo FIXME NOTE NOTES TODO XXX contained
+
+" Triple-quoted strings can contain doctests.
+syn region mojoString matchgroup=mojoQuotes
+ \ start=+[uU]\=\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1"
+ \ contains=mojoEscape,@Spell
+syn region mojoString matchgroup=mojoTripleQuotes
+ \ start=+[uU]\=\z('''\|"""\)+ end="\z1" keepend
+ \ contains=mojoEscape,mojoSpaceError,mojoDoctest,@Spell
+syn region mojoRawString matchgroup=mojoQuotes
+ \ start=+[uU]\=[rR]\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1"
+ \ contains=@Spell
+syn region mojoRawString matchgroup=pythonTripleQuotes
+ \ start=+[uU]\=[rR]\z('''\|"""\)+ end="\z1" keepend
+ \ contains=pythonSpaceError,mojoDoctest,@Spell
+
+syn match mojoEscape +\\[abfnrtv'"\\]+ contained
+syn match mojoEscape "\\\o\{1,3}" contained
+syn match mojoEscape "\\x\x\{2}" contained
+syn match mojoEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained
+" Python allows case-insensitive Unicode IDs: http://www.unicode.org/charts/
+syn match mojoEscape "\\N{\a\+\%(\s\a\+\)*}" contained
+syn match mojoEscape "\\$"
+
+" It is very important to understand all details before changing the
+" regular expressions below or their order.
+" The word boundaries are *not* the floating-point number boundaries
+" because of a possible leading or trailing decimal point.
+" The expressions below ensure that all valid number literals are
+" highlighted, and invalid number literals are not. For example,
+"
+" - a decimal point in '4.' at the end of a line is highlighted,
+" - a second dot in 1.0.0 is not highlighted,
+" - 08 is not highlighted,
+" - 08e0 or 08j are highlighted,
+"
+" and so on, as specified in the 'Python Language Reference'.
+" https://docs.python.org/reference/lexical_analysis.html#numeric-literals
+if !exists("mojo_no_number_highlight")
+ " numbers (including complex)
+ syn match mojoNumber "\<0[oO]\%(_\=\o\)\+\>"
+ syn match mojoNumber "\<0[xX]\%(_\=\x\)\+\>"
+ syn match mojoNumber "\<0[bB]\%(_\=[01]\)\+\>"
+ syn match mojoNumber "\<\%([1-9]\%(_\=\d\)*\|0\+\%(_\=0\)*\)\>"
+ syn match mojoNumber "\<\d\%(_\=\d\)*[jJ]\>"
+ syn match mojoNumber "\<\d\%(_\=\d\)*[eE][+-]\=\d\%(_\=\d\)*[jJ]\=\>"
+ syn match mojoNumber
+ \ "\<\d\%(_\=\d\)*\.\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\%(\W\|$\)\@="
+ syn match mojoNumber
+ \ "\%(^\|\W\)\zs\%(\d\%(_\=\d\)*\)\=\.\d\%(_\=\d\)*\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\>"
+endif
+
+" The built-ins are added in the same order of appearance in Mojo stdlib docs
+" https://docs.modular.com/mojo/lib.html
+"
+if !exists("mojo_no_builtin_highlight")
+ " Built-in functions
+ syn keyword mojoBuiltin slice constrained debug_assert put_new_line print
+ syn keyword mojoBuiltin print_no_newline len range rebind element_type
+ syn keyword mojoBuiltin ord chr atol isdigit index address string
+ " Built-in types
+ syn keyword mojoType Byte ListLiteral CoroutineContext Coroutine DType
+ syn keyword mojoType dtype type invalid bool int8 si8 unit8 ui8 int16
+ syn keyword mojoType si16 unit16 ui16 int32 si32 uint32 ui32 int64
+ syn keyword mojoType si64 uint64 ui64 bfloat16 bf16 float16 f16 float32
+ syn keyword mojoType f32 float64 f64 Error FloatLiteral Int Attr SIMD
+ syn keyword mojoType Int8 UInt8 Int16 UInt16 Int32 UInt32 Int64 UInt64
+ syn keyword mojoType Float16 Float32 Float64 element_type _65x13_type
+ syn keyword mojoType String StringLiteral StringRef Tuple AnyType
+ syn keyword mojoType NoneType None Lifetime
+ " avoid highlighting attributes as builtins
+ syn match mojoAttribute /\.\h\w*/hs=s+1
+ \ contains=ALLBUT,mojoBuiltin,mojoFunction,mojoAsync
+ \ transparent
+endif
+
+" From the 'Python Library Reference' class hierarchy at the bottom.
+" http://docs.python.org/library/exceptions.html
+if !exists("mojo_no_exception_highlight")
+ " builtin base exceptions (used mostly as base classes for other exceptions)
+ syn keyword mojoExceptions BaseException Exception
+ syn keyword mojoExceptions ArithmeticError BufferError LookupError
+ " builtin exceptions (actually raised)
+ syn keyword mojoExceptions AssertionError AttributeError EOFError
+ syn keyword mojoExceptions FloatingPointError GeneratorExit ImportError
+ syn keyword mojoExceptions IndentationError IndexError KeyError
+ syn keyword mojoExceptions KeyboardInterrupt MemoryError
+ syn keyword mojoExceptions ModuleNotFoundError NameError
+ syn keyword mojoExceptions NotImplementedError OSError OverflowError
+ syn keyword mojoExceptions RecursionError ReferenceError RuntimeError
+ syn keyword mojoExceptions StopAsyncIteration StopIteration SyntaxError
+ syn keyword mojoExceptions SystemError SystemExit TabError TypeError
+ syn keyword mojoExceptions UnboundLocalError UnicodeDecodeError
+ syn keyword mojoExceptions UnicodeEncodeError UnicodeError
+ syn keyword mojoExceptions UnicodeTranslateError ValueError
+ syn keyword mojoExceptions ZeroDivisionError
+ " builtin exception aliases for OSError
+ syn keyword mojoExceptions EnvironmentError IOError WindowsError
+ " builtin OS exceptions in Python 3
+ syn keyword mojoExceptions BlockingIOError BrokenPipeError
+ syn keyword mojoExceptions ChildProcessError ConnectionAbortedError
+ syn keyword mojoExceptions ConnectionError ConnectionRefusedError
+ syn keyword mojoExceptions ConnectionResetError FileExistsError
+ syn keyword mojoExceptions FileNotFoundError InterruptedError
+ syn keyword mojoExceptions IsADirectoryError NotADirectoryError
+ syn keyword mojoExceptions PermissionError ProcessLookupError TimeoutError
+ " builtin warnings
+ syn keyword mojoExceptions BytesWarning DeprecationWarning FutureWarning
+ syn keyword mojoExceptions ImportWarning PendingDeprecationWarning
+ syn keyword mojoExceptions ResourceWarning RuntimeWarning
+ syn keyword mojoExceptions SyntaxWarning UnicodeWarning
+ syn keyword mojoExceptions UserWarning Warning
+endif
+
+if exists("mojo_space_error_highlight")
+ " trailing whitespace
+ syn match mojoSpaceError display excludenl "\s\+$"
+ " mixed tabs and spaces
+ syn match mojoSpaceError display " \+\t"
+ syn match mojoSpaceError display "\t\+ "
+endif
+
+" Do not spell doctests inside strings.
+" Notice that the end of a string, either ''', or """, will end the contained
+" doctest too. Thus, we do *not* need to have it as an end pattern.
+if !exists("mojo_no_doctest_highlight")
+ if !exists("mojo_no_doctest_code_highlight")
+ syn region mojoDoctest
+ \ start="^\s*>>>\s" end="^\s*$"
+ \ contained contains=ALLBUT,mojoDoctest,mojoFunction,@Spell
+ syn region mojoDoctestValue
+ \ start=+^\s*\%(>>>\s\|\.\.\.\s\|"""\|'''\)\@!\S\++ end="$"
+ \ contained
+ else
+ syn region mojoDoctest
+ \ start="^\s*>>>" end="^\s*$"
+ \ contained contains=@NoSpell
+ endif
+endif
+
+" Sync at the beginning of class, function, or method definition.
+syn sync match mojoSync grouphere NONE "^\%(def\|class\)\s\+\h\w*\s*[(:]"
+
+" The default highlight links. Can be overridden later.
+hi def link mojoStatement Statement
+hi def link mojoConditional Conditional
+hi def link mojoRepeat Repeat
+hi def link mojoOperator Operator
+hi def link mojoException Exception
+hi def link mojoInclude Include
+hi def link mojoAsync Statement
+hi def link mojoDecorator Define
+hi def link mojoDecoratorName Function
+hi def link mojoFunction Function
+hi def link mojoComment Comment
+hi def link mojoTodo Todo
+hi def link mojoString String
+hi def link mojoRawString String
+hi def link mojoQuotes String
+hi def link mojoTripleQuotes mojoQuotes
+hi def link mojoEscape Special
+if !exists("mojo_no_number_highlight")
+ hi def link mojoNumber Number
+endif
+if !exists("mojo_no_builtin_highlight")
+ hi def link mojoBuiltin Function
+ hi def link mojoType Type
+endif
+if !exists("mojo_no_exception_highlight")
+ hi def link mojoExceptions Structure
+endif
+if exists("mojo_space_error_highlight")
+ hi def link mojoSpaceError Error
+endif
+if !exists("mojo_no_doctest_highlight")
+ hi def link mojoDoctest Special
+ hi def link mojoDoctestValue Define
+endif
+
+let b:current_syntax = "mojo"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim:set sw=2 sts=2 ts=8 noet:
diff --git a/runtime/syntax/muttrc.vim b/runtime/syntax/muttrc.vim
index 830664e0eb..bf53a42f94 100644
--- a/runtime/syntax/muttrc.vim
+++ b/runtime/syntax/muttrc.vim
@@ -1,10 +1,10 @@
" Vim syntax file
" Language: Mutt setup files
" Original: Preben 'Peppe' Guldberg <peppe-vim@wielders.org>
-" Maintainer: Kyle Wheeler <kyle-muttrc.vim@memoryhole.net>
-" Last Change: 21 May 2018
+" Maintainer: Luna Celeste <luna@unixpoet.dev>
+" Last Change: 14 Aug 2023
-" This file covers mutt version 1.10.0
+" This file covers mutt version 2.2.10
" quit when a syntax file was already loaded
if exists("b:current_syntax")
@@ -103,88 +103,96 @@ syn match muttrcKeyName contained "<F[0-9]\+>"
syn keyword muttrcVarBool skipwhite contained
\ allow_8bit allow_ansi arrow_cursor ascii_chars askbcc askcc attach_split
- \ auto_tag autoedit beep beep_new bounce_delivered braille_friendly
- \ browser_abbreviate_mailboxes change_folder_next check_mbox_size check_new
- \ collapse_unread confirmappend confirmcreate crypt_autoencrypt crypt_autopgp
- \ crypt_autosign crypt_autosmime crypt_confirmhook crypt_opportunistic_encrypt
- \ crypt_replyencrypt crypt_replysign crypt_replysignencrypted crypt_timestamp
- \ crypt_use_gpgme crypt_use_pka delete_untag digest_collapse duplicate_threads
- \ edit_hdrs edit_headers encode_from envelope_from fast_reply fcc_clear
- \ flag_safe followup_to force_name forw_decode forw_decrypt forw_quote
- \ forward_decode forward_decrypt forward_quote hdrs header
- \ header_color_partial help hidden_host hide_limited hide_missing
- \ hide_thread_subject hide_top_limited hide_top_missing history_remove_dups
- \ honor_disposition idn_decode idn_encode ignore_linear_white_space
- \ ignore_list_reply_to imap_check_subscribed imap_list_subscribed imap_passive
- \ imap_peek imap_servernoise implicit_autoview include_onlyfirst keep_flagged
+ \ auto_tag autoedit auto_subscribe background_edit background_confirm_quit beep beep_new
+ \ bounce_delivered braille_friendly browser_abbreviate_mailboxes browser_sticky_cursor
+ \ change_folder_next check_mbox_size check_new collapse_unread compose_confirm_detach_first
+ \ confirmappend confirmcreate copy_decode_weed count_alternatives crypt_autoencrypt crypt_autopgp
+ \ crypt_autosign crypt_autosmime crypt_confirmhook crypt_protected_headers_read
+ \ crypt_protected_headers_save crypt_protected_headers_write crypt_opportunistic_encrypt
+ \ crypt_opportunistic_encrypt_strong_keys crypt_replyencrypt crypt_replysign
+ \ crypt_replysignencrypted crypt_timestamp crypt_use_gpgme crypt_use_pka cursor_overlay
+ \ delete_untag digest_collapse duplicate_threads edit_hdrs edit_headers encode_from
+ \ envelope_from fast_reply fcc_before_send fcc_clear flag_safe followup_to force_name forw_decode
+ \ forw_decrypt forw_quote forward_decode forward_quote hdrs header
+ \ header_color_partial help hidden_host hide_limited hide_missing hide_thread_subject
+ \ hide_top_limited hide_top_missing history_remove_dups honor_disposition idn_decode idn_encode
+ \ ignore_linear_white_space ignore_list_reply_to imap_check_subscribed imap_condstore imap_deflate
+ \ imap_list_subscribed imap_passive imap_peek imap_qresync imap_servernoise
+ \ implicit_autoview include_encrypted include_onlyfirst keep_flagged local_date_header
\ mail_check_recent mail_check_stats mailcap_sanitize maildir_check_cur
\ maildir_header_cache_verify maildir_trash mark_old markers menu_move_off
\ menu_scroll message_cache_clean meta_key metoo mh_purge mime_forward_decode
- \ mime_type_query_first narrow_tree pager_stop pgp_auto_decode
+ \ mime_type_query_first muttlisp_inline_eval narrow_tree pager_stop pgp_auto_decode
\ pgp_auto_traditional pgp_autoencrypt pgp_autoinline pgp_autosign
- \ pgp_check_exit pgp_create_traditional pgp_ignore_subkeys pgp_long_ids
- \ pgp_replyencrypt pgp_replyinline pgp_replysign pgp_replysignencrypted
- \ pgp_retainable_sigs pgp_self_encrypt pgp_self_encrypt_as pgp_show_unusable
- \ pgp_strict_enc pgp_use_gpg_agent pipe_decode pipe_split pop_auth_try_all
- \ pop_last postpone_encrypt postpone_encrypt_as print_decode print_split
- \ prompt_after read_only reflow_space_quotes reflow_text reflow_wrap
- \ reply_self resolve resume_draft_files resume_edited_draft_files
- \ reverse_alias reverse_name reverse_realname rfc2047_parameters save_address
- \ save_empty save_name score sidebar_folder_indent sidebar_new_mail_only
- \ sidebar_next_new_wrap sidebar_short_path sidebar_sort sidebar_visible
- \ sig_dashes sig_on_top smart_wrap smime_ask_cert_label
- \ smime_decrypt_use_default_key smime_is_default smime_self_encrypt
- \ smime_self_encrypt_as sort_re ssl_force_tls ssl_use_sslv2 ssl_use_sslv3
- \ ssl_use_tlsv1 ssl_usesystemcerts ssl_verify_dates ssl_verify_host
- \ ssl_verify_partial_chains status_on_top strict_mime strict_threads suspend
- \ text_flowed thorough_search thread_received tilde ts_enabled uncollapse_jump
- \ use_8bitmime use_domain use_envelope_from use_from use_idn use_ipv6
- \ uncollapse_new user_agent wait_key weed wrap_search write_bcc
+ \ pgp_check_exit pgp_check_gpg_decrypt_status_fd pgp_create_traditional
+ \ pgp_ignore_subkeys pgp_long_ids pgp_replyencrypt pgp_replyinline
+ \ pgp_replysign pgp_replysignencrypted pgp_retainable_sigs pgp_self_encrypt
+ \ pgp_self_encrypt_as pgp_show_unusable pgp_strict_enc pgp_use_gpg_agent
+ \ pipe_decode pipe_decode_weed pipe_split pop_auth_try_all pop_last postpone_encrypt
+ \ postpone_encrypt_as print_decode print_decode_weed print_split prompt_after read_only
+ \ reflow_space_quotes reflow_text reflow_wrap reply_self resolve
+ \ resume_draft_files resume_edited_draft_files reverse_alias reverse_name
+ \ reverse_realname rfc2047_parameters save_address save_empty save_name score
+ \ sidebar_folder_indent sidebar_new_mail_only sidebar_next_new_wrap
+ \ sidebar_relative_shortpath_indent sidebar_short_path sidebar_sort sidebar_use_mailbox_shortcuts
+ \ sidebar_visible sig_on_top sig_dashes size_show_bytes size_show_fraction size_show_mb
+ \ size_units_on_left smart_wrap smime_ask_cert_label smime_decrypt_use_default_key
+ \ smime_is_default smime_self_encrypt smime_self_encrypt_as sort_re
+ \ ssl_force_tls ssl_use_sslv2 ssl_use_sslv3 ssl_use_tlsv1 ssl_use_tlsv1_3 ssl_usesystemcerts
+ \ ssl_verify_dates ssl_verify_host ssl_verify_partial_chains status_on_top
+ \ strict_mime strict_threads suspend text_flowed thorough_search
+ \ thread_received tilde ts_enabled tunnel_is_secure uncollapse_jump use_8bitmime use_domain
+ \ use_envelope_from use_from use_idn use_ipv6 uncollapse_new user_agent
+ \ wait_key weed wrap_search write_bcc
\ nextgroup=muttrcSetBoolAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syn keyword muttrcVarBool skipwhite contained
\ noallow_8bit noallow_ansi noarrow_cursor noascii_chars noaskbcc noaskcc
- \ noattach_split noauto_tag noautoedit nobeep nobeep_new nobounce_delivered
- \ nobraille_friendly nobrowser_abbreviate_mailboxes nochange_folder_next
- \ nocheck_mbox_size nocheck_new nocollapse_unread noconfirmappend
- \ noconfirmcreate nocrypt_autoencrypt nocrypt_autopgp nocrypt_autosign
- \ nocrypt_autosmime nocrypt_confirmhook nocrypt_opportunistic_encrypt
- \ nocrypt_replyencrypt nocrypt_replysign nocrypt_replysignencrypted
- \ nocrypt_timestamp nocrypt_use_gpgme nocrypt_use_pka nodelete_untag
- \ nodigest_collapse noduplicate_threads noedit_hdrs noedit_headers
- \ noencode_from noenvelope_from nofast_reply nofcc_clear noflag_safe
+ \ noattach_split noauto_tag noautoedit noauto_subscribe nobackground_edit
+ \ nobackground_confirm_quit nobeep nobeep_new nobounce_delivered
+ \ nobraille_friendly nobrowser_abbreviate_mailboxes nobrowser_sticky_cursor nochange_folder_next
+ \ nocheck_mbox_size nocheck_new nocompose_confirm_detach_first nocollapse_unread noconfirmappend
+ \ noconfirmcreate nocopy_decode_weed nocount_alternatives nocrypt_autoencrypt nocrypt_autopgp
+ \ nocrypt_autosign nocrypt_autosmime nocrypt_confirmhook nocrypt_protected_headers_read
+ \ nocrypt_protected_headers_save nocrypt_protected_headers_write nocrypt_opportunistic_encrypt
+ \ nocrypt_opportunistic_encrypt_strong_keys nocrypt_replyencrypt nocrypt_replysign
+ \ nocrypt_replysignencrypted nocrypt_timestamp nocrypt_use_gpgme nocrypt_use_pka nocursor_overlay
+ \ nodelete_untag nodigest_collapse noduplicate_threads noedit_hdrs noedit_headers
+ \ noencode_from noenvelope_from nofast_reply nofcc_before_send nofcc_clear noflag_safe
\ nofollowup_to noforce_name noforw_decode noforw_decrypt noforw_quote
- \ noforward_decode noforward_decrypt noforward_quote nohdrs noheader
+ \ noforward_decode noforward_quote nohdrs noheader
\ noheader_color_partial nohelp nohidden_host nohide_limited nohide_missing
\ nohide_thread_subject nohide_top_limited nohide_top_missing
\ nohistory_remove_dups nohonor_disposition noidn_decode noidn_encode
\ noignore_linear_white_space noignore_list_reply_to noimap_check_subscribed
- \ noimap_list_subscribed noimap_passive noimap_peek noimap_servernoise
- \ noimplicit_autoview noinclude_onlyfirst nokeep_flagged nomail_check_recent
- \ nomail_check_stats nomailcap_sanitize nomaildir_check_cur
- \ nomaildir_header_cache_verify nomaildir_trash nomark_old nomarkers
- \ nomenu_move_off nomenu_scroll nomessage_cache_clean nometa_key nometoo
- \ nomh_purge nomime_forward_decode nomime_type_query_first nonarrow_tree
- \ nopager_stop nopgp_auto_decode nopgp_auto_traditional nopgp_autoencrypt
- \ nopgp_autoinline nopgp_autosign nopgp_check_exit nopgp_create_traditional
+ \ noimap_condstore noimap_deflate noimap_list_subscribed noimap_passive noimap_peek
+ \ noimap_qresync noimap_servernoise noimplicit_autoview noinclude_encrypted noinclude_onlyfirst
+ \ nokeep_flagged nolocal_date_header nomail_check_recent nomail_check_stats nomailcap_sanitize
+ \ nomaildir_check_cur nomaildir_header_cache_verify nomaildir_trash nomark_old
+ \ nomarkers nomenu_move_off nomenu_scroll nomessage_cache_clean nometa_key
+ \ nometoo nomh_purge nomime_forward_decode nomime_type_query_first nomuttlisp_inline_eval
+ \ nonarrow_tree nopager_stop nopgp_auto_decode nopgp_auto_traditional nopgp_autoencrypt
+ \ nopgp_autoinline nopgp_autosign nopgp_check_exit
+ \ nopgp_check_gpg_decrypt_status_fd nopgp_create_traditional
\ nopgp_ignore_subkeys nopgp_long_ids nopgp_replyencrypt nopgp_replyinline
\ nopgp_replysign nopgp_replysignencrypted nopgp_retainable_sigs
\ nopgp_self_encrypt nopgp_self_encrypt_as nopgp_show_unusable
- \ nopgp_strict_enc nopgp_use_gpg_agent nopipe_decode nopipe_split
+ \ nopgp_strict_enc nopgp_use_gpg_agent nopipe_decode nopipe_decode_weed nopipe_split
\ nopop_auth_try_all nopop_last nopostpone_encrypt nopostpone_encrypt_as
- \ noprint_decode noprint_split noprompt_after noread_only
+ \ noprint_decode noprint_decode_weed noprint_split noprompt_after noread_only
\ noreflow_space_quotes noreflow_text noreflow_wrap noreply_self noresolve
\ noresume_draft_files noresume_edited_draft_files noreverse_alias
\ noreverse_name noreverse_realname norfc2047_parameters nosave_address
\ nosave_empty nosave_name noscore nosidebar_folder_indent
- \ nosidebar_new_mail_only nosidebar_next_new_wrap nosidebar_short_path
- \ nosidebar_sort nosidebar_visible nosig_dashes nosig_on_top nosmart_wrap
- \ nosmime_ask_cert_label nosmime_decrypt_use_default_key nosmime_is_default
- \ nosmime_self_encrypt nosmime_self_encrypt_as nosort_re nossl_force_tls
- \ nossl_use_sslv2 nossl_use_sslv3 nossl_use_tlsv1 nossl_usesystemcerts
+ \ nosidebar_new_mail_only nosidebar_next_new_wrap nosidebar_relative_shortpath_indent
+ \ nosidebar_short_path nosidebar_sort nosidebar_visible nosidebar_use_mailbox_shortcuts
+ \ nosig_dashes nosig_on_top nosize_show_bytes nosize_show_fraction nosize_show_mb
+ \ nosize_units_on_left nosmart_wrap nosmime_ask_cert_label nosmime_decrypt_use_default_key
+ \ nosmime_is_default nosmime_self_encrypt nosmime_self_encrypt_as nosort_re nossl_force_tls
+ \ nossl_use_sslv2 nossl_use_sslv3 nossl_use_tlsv1 nossl_use_tlsv1_3 nossl_usesystemcerts
\ nossl_verify_dates nossl_verify_host nossl_verify_partial_chains
\ nostatus_on_top nostrict_mime nostrict_threads nosuspend notext_flowed
- \ nothorough_search nothread_received notilde nots_enabled nouncollapse_jump
+ \ nothorough_search nothread_received notilde nots_enabled notunnel_is_secure nouncollapse_jump
\ nouse_8bitmime nouse_domain nouse_envelope_from nouse_from nouse_idn
\ nouse_ipv6 nouncollapse_new nouser_agent nowait_key noweed nowrap_search
\ nowrite_bcc
@@ -192,50 +200,53 @@ syn keyword muttrcVarBool skipwhite contained
syn keyword muttrcVarBool skipwhite contained
\ invallow_8bit invallow_ansi invarrow_cursor invascii_chars invaskbcc
- \ invaskcc invattach_split invauto_tag invautoedit invbeep invbeep_new
- \ invbounce_delivered invbraille_friendly invbrowser_abbreviate_mailboxes
- \ invchange_folder_next invcheck_mbox_size invcheck_new invcollapse_unread
- \ invconfirmappend invconfirmcreate invcrypt_autoencrypt invcrypt_autopgp
- \ invcrypt_autosign invcrypt_autosmime invcrypt_confirmhook
- \ invcrypt_opportunistic_encrypt invcrypt_replyencrypt invcrypt_replysign
- \ invcrypt_replysignencrypted invcrypt_timestamp invcrypt_use_gpgme
- \ invcrypt_use_pka invdelete_untag invdigest_collapse invduplicate_threads
+ \ invaskcc invattach_split invauto_tag invautoedit invauto_subscribe nobackground_edit
+ \ nobackground_confirm_quit invbeep invbeep_new invbounce_delivered invbraille_friendly
+ \ invbrowser_abbreviate_mailboxes invbrowser_sticky_cursor invchange_folder_next
+ \ invcheck_mbox_size invcheck_new invcollapse_unread invcompose_confirm_detach_first
+ \ invconfirmappend invcopy_decode_weed invconfirmcreate invcount_alternatives invcrypt_autopgp
+ \ invcrypt_autoencrypt invcrypt_autosign invcrypt_autosmime invcrypt_confirmhook
+ \ invcrypt_protected_headers_read invcrypt_protected_headers_save invcrypt_protected_headers_write
+ \ invcrypt_opportunistic_encrypt invcrypt_opportunistic_encrypt_strong_keys invcrypt_replysign
+ \ invcrypt_replyencrypt invcrypt_replysignencrypted invcrypt_timestamp invcrypt_use_gpgme
+ \ invcrypt_use_pka invcursor_overlay invdelete_untag invdigest_collapse invduplicate_threads
\ invedit_hdrs invedit_headers invencode_from invenvelope_from invfast_reply
- \ invfcc_clear invflag_safe invfollowup_to invforce_name invforw_decode
- \ invforw_decrypt invforw_quote invforward_decode invforward_decrypt
+ \ invfcc_before_send invfcc_clear invflag_safe invfollowup_to invforce_name invforw_decode
+ \ invforw_decrypt invforw_quote invforward_decode
\ invforward_quote invhdrs invheader invheader_color_partial invhelp
\ invhidden_host invhide_limited invhide_missing invhide_thread_subject
\ invhide_top_limited invhide_top_missing invhistory_remove_dups
\ invhonor_disposition invidn_decode invidn_encode
\ invignore_linear_white_space invignore_list_reply_to
- \ invimap_check_subscribed invimap_list_subscribed invimap_passive
- \ invimap_peek invimap_servernoise invimplicit_autoview invinclude_onlyfirst
- \ invkeep_flagged invmail_check_recent invmail_check_stats invmailcap_sanitize
- \ invmaildir_check_cur invmaildir_header_cache_verify invmaildir_trash
- \ invmark_old invmarkers invmenu_move_off invmenu_scroll
- \ invmessage_cache_clean invmeta_key invmetoo invmh_purge
- \ invmime_forward_decode invmime_type_query_first invnarrow_tree invpager_stop
- \ invpgp_auto_decode invpgp_auto_traditional invpgp_autoencrypt
+ \ invimap_check_subscribed invimap_condstore invimap_deflate invimap_list_subscribed
+ \ invimap_passive invimap_peek invimap_qresync invimap_servernoise invimplicit_autoview
+ \ invinclude_encrypted invinclude_onlyfirst invkeep_flagged invlocal_date_header
+ \ invmail_check_recent invmail_check_stats invmailcap_sanitize invmaildir_check_cur
+ \ invmaildir_header_cache_verify invmaildir_trash invmark_old invmarkers invmenu_move_off
+ \ invmenu_scroll invmessage_cache_clean invmeta_key invmetoo invmh_purge
+ \ invmime_forward_decode invmime_type_query_first invmuttlisp_inline_eval invnarrow_tree
+ \ invpager_stop invpgp_auto_decode invpgp_auto_traditional invpgp_autoencrypt
\ invpgp_autoinline invpgp_autosign invpgp_check_exit
- \ invpgp_create_traditional invpgp_ignore_subkeys invpgp_long_ids
- \ invpgp_replyencrypt invpgp_replyinline invpgp_replysign
- \ invpgp_replysignencrypted invpgp_retainable_sigs invpgp_self_encrypt
- \ invpgp_self_encrypt_as invpgp_show_unusable invpgp_strict_enc
- \ invpgp_use_gpg_agent invpipe_decode invpipe_split invpop_auth_try_all
- \ invpop_last invpostpone_encrypt invpostpone_encrypt_as invprint_decode
- \ invprint_split invprompt_after invread_only invreflow_space_quotes
- \ invreflow_text invreflow_wrap invreply_self invresolve invresume_draft_files
- \ invresume_edited_draft_files invreverse_alias invreverse_name
- \ invreverse_realname invrfc2047_parameters invsave_address invsave_empty
- \ invsave_name invscore invsidebar_folder_indent invsidebar_new_mail_only
- \ invsidebar_next_new_wrap invsidebar_short_path invsidebar_sort
- \ invsidebar_visible invsig_dashes invsig_on_top invsmart_wrap
- \ invsmime_ask_cert_label invsmime_decrypt_use_default_key invsmime_is_default
- \ invsmime_self_encrypt invsmime_self_encrypt_as invsort_re invssl_force_tls
- \ invssl_use_sslv2 invssl_use_sslv3 invssl_use_tlsv1 invssl_usesystemcerts
+ \ invpgp_check_gpg_decrypt_status_fd invpgp_create_traditional
+ \ invpgp_ignore_subkeys invpgp_long_ids invpgp_replyencrypt invpgp_replyinline
+ \ invpgp_replysign invpgp_replysignencrypted invpgp_retainable_sigs
+ \ invpgp_self_encrypt invpgp_self_encrypt_as invpgp_show_unusable
+ \ invpgp_strict_enc invpgp_use_gpg_agent invpipe_decode invpipe_decode_weed invpipe_split
+ \ invpop_auth_try_all invpop_last invpostpone_encrypt invpostpone_encrypt_as
+ \ invprint_decode invprint_decode_weed invprint_split invprompt_after invread_only
+ \ invreflow_space_quotes invreflow_text invreflow_wrap invreply_self invresolve
+ \ invresume_draft_file sinvresume_edited_draft_files invreverse_alias
+ \ invreverse_name invreverse_realname invrfc2047_parameters invsave_address
+ \ invsave_empty invsave_name invscore invsidebar_folder_indent
+ \ invsidebar_new_mail_only invsidebar_next_new_wrap invsidebar_relative_shortpath_indent
+ \ invsidebar_short_path invsidebar_sort sidebar_use_mailbox_shortcuts invsidebar_visible
+ \ invsig_dashes invsig_on_top invsize_show_bytes invsize_show_fraction invsize_show_mb
+ \ invsize_units_on_left invsmart_wrap invsmime_ask_cert_label invsmime_decrypt_use_default_key
+ \ invsmime_is_default invsmime_self_encrypt invsmime_self_encrypt_as invsort_re invssl_force_tls
+ \ invssl_use_sslv2 invssl_use_sslv3 invssl_use_tlsv1 invssl_use_tlsv1_3 invssl_usesystemcerts
\ invssl_verify_dates invssl_verify_host invssl_verify_partial_chains
\ invstatus_on_top invstrict_mime invstrict_threads invsuspend invtext_flowed
- \ invthorough_search invthread_received invtilde invts_enabled
+ \ invthorough_search invthread_received invtilde invts_enabled invtunnel_is_secure
\ invuncollapse_jump invuse_8bitmime invuse_domain invuse_envelope_from
\ invuse_from invuse_idn invuse_ipv6 invuncollapse_new invuser_agent
\ invwait_key invweed invwrap_search invwrite_bcc
@@ -243,32 +254,32 @@ syn keyword muttrcVarBool skipwhite contained
syn keyword muttrcVarQuad skipwhite contained
\ abort_nosubject abort_unmodified abort_noattach bounce copy crypt_verify_sig
- \ delete fcc_attach forward_edit honor_followup_to include mime_forward
- \ mime_forward_rest mime_fwd move pgp_mime_auto pgp_verify_sig pop_delete
- \ pop_reconnect postpone print quit recall reply_to ssl_starttls
+ \ delete fcc_attach forward_attachments forward_decrypt forward_edit honor_followup_to include
+ \ mime_forward mime_forward_rest mime_fwd move pgp_mime_auto pgp_verify_sig pop_delete
+ \ pop_reconnect postpone print quit recall reply_to send_multipart_alternative ssl_starttls
\ nextgroup=muttrcSetQuadAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syn keyword muttrcVarQuad skipwhite contained
\ noabort_nosubject noabort_unmodified noabort_noattach nobounce nocopy
- \ nocrypt_verify_sig nodelete nofcc_attach noforward_edit nohonor_followup_to
- \ noinclude nomime_forward nomime_forward_rest nomime_fwd nomove
+ \ nocrypt_verify_sig nodelete nofcc_attach noforward_attachments noforward_decrypt noforward_edit
+ \ nohonor_followup_to noinclude nomime_forward nomime_forward_rest nomime_fwd nomove
\ nopgp_mime_auto nopgp_verify_sig nopop_delete nopop_reconnect nopostpone
- \ noprint noquit norecall noreply_to nossl_starttls
+ \ noprint noquit norecall noreply_to nosend_multipart_alternative nossl_starttls
\ nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syn keyword muttrcVarQuad skipwhite contained
\ invabort_nosubject invabort_unmodified invabort_noattach invbounce invcopy
- \ invcrypt_verify_sig invdelete invfcc_attach invforward_edit
- \ invhonor_followup_to invinclude invmime_forward invmime_forward_rest
+ \ invcrypt_verify_sig invdelete invfcc_attach invforward_attachments invforward_decrypt
+ \ invforward_edit invhonor_followup_to invinclude invmime_forward invmime_forward_rest
\ invmime_fwd invmove invpgp_mime_auto invpgp_verify_sig invpop_delete
\ invpop_reconnect invpostpone invprint invquit invrecall invreply_to
- \ invssl_starttls
+ \ invsend_multipart_alternative invssl_starttls
\ nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syn keyword muttrcVarNum skipwhite contained
- \ connect_timeout error_history history imap_keepalive imap_pipeline_depth
+ \ connect_timeout error_history history imap_fetch_chunk_size imap_keepalive imap_pipeline_depth
\ imap_poll_timeout mail_check mail_check_stats_interval menu_context net_inc
- \ pager_context pager_index_lines pgp_timeout pop_checkinterval read_inc
+ \ pager_context pager_index_lines pager_skip_quoted_context pgp_timeout pop_checkinterval read_inc
\ save_history score_threshold_delete score_threshold_flag
\ score_threshold_read search_context sendmail_wait sidebar_width sleep_time
\ smime_timeout ssl_min_dh_prime_bits time_inc timeout wrap wrap_headers
@@ -365,10 +376,14 @@ syn keyword muttrcVarStr contained skipwhite alias_format nextgroup=muttrcVarEqu
syn match muttrcVarEqualsAliasFmt contained skipwhite "=" nextgroup=muttrcAliasFormatStr
syn keyword muttrcVarStr contained skipwhite attach_format nextgroup=muttrcVarEqualsAttachFmt
syn match muttrcVarEqualsAttachFmt contained skipwhite "=" nextgroup=muttrcAttachFormatStr
+syn keyword muttrcVarStr contained skipwhite background_format nextgroup=muttrcVarEqualsBackgroundFormatFmt
+syn match muttrcVarEqualsBackgroundFormatFmt contained skipwhite "=" nextgroup=muttrcBackgroundFormatStr
syn keyword muttrcVarStr contained skipwhite compose_format nextgroup=muttrcVarEqualsComposeFmt
syn match muttrcVarEqualsComposeFmt contained skipwhite "=" nextgroup=muttrcComposeFormatStr
syn keyword muttrcVarStr contained skipwhite folder_format nextgroup=muttrcVarEqualsFolderFmt
syn match muttrcVarEqualsFolderFmt contained skipwhite "=" nextgroup=muttrcFolderFormatStr
+syn keyword muttrcVarStr contained skipwhite message_id_format nextgroup=muttrcVarEqualsMessageIdFmt
+syn match muttrcVarEqualsMessageIdFmt contained skipwhite "=" nextgroup=muttrcMessageIdFormatStr
syn keyword muttrcVarStr contained skipwhite mix_entry_format nextgroup=muttrcVarEqualsMixFmt
syn match muttrcVarEqualsMixFmt contained skipwhite "=" nextgroup=muttrcMixFormatStr
syn keyword muttrcVarStr contained skipwhite pgp_entry_format nextgroup=muttrcVarEqualsPGPFmt
@@ -390,27 +405,29 @@ syn match muttrcVPrefix contained /[?&]/ nextgroup=muttrcVarBool,muttrcVarQuad,
syn match muttrcVarStr contained skipwhite 'my_[a-zA-Z0-9_]\+' nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
syn keyword muttrcVarStr contained skipwhite
- \ abort_noattach_regexp alias_file assumed_charset attach_charset attach_sep
+ \ abort_noattach_regexp alias_file assumed_charset attach_charset attach_save_dir attach_sep
\ attribution_locale certificate_file charset config_charset content_type
- \ default_hook display_filter dotlock_program dsn_notify dsn_return editor
- \ entropy_file envelope_from_address escape folder forw_format
+ \ crypt_protected_headers_subject default_hook display_filter dotlock_program dsn_notify
+ \ dsn_return editor entropy_file envelope_from_address escape fcc_delimiter folder forw_format
\ forward_attribution_intro forward_attribution_trailer forward_format from gecos_mask
\ hdr_format header_cache header_cache_compress header_cache_pagesize history_file
\ hostname imap_authenticators imap_delim_chars imap_headers imap_idle imap_login
- \ imap_pass imap_user indent_str indent_string ispell locale mailcap_path
- \ mark_macro_prefix mask mbox mbox_type message_cachedir mh_seq_flagged mh_seq_replied
- \ mh_seq_unseen mime_type_query_command mixmaster msg_format new_mail_command pager
- \ pgp_default_key pgp_decryption_okay pgp_good_sign pgp_mime_signature_description
+ \ imap_oauth_refresh_command imap_pass imap_user indent_str indent_string ispell locale
+ \ mailcap_pat hmark_macro_prefix mask mbox mbox_type message_cachedir mh_seq_flagged
+ \ mh_seq_replied mh_seq_unseen mime_type_query_command mixmaster msg_format new_mail_command
+ \ pager pgp_default_key pgp_decryption_okay pgp_good_sign pgp_mime_signature_description
\ pgp_mime_signature_filename pgp_sign_as pgp_sort_keys pipe_sep pop_authenticators
- \ pop_host pop_pass pop_user post_indent_str post_indent_string postpone_encrypt_as
- \ postponed preconnect print_cmd print_command query_command quote_regexp realname
- \ record reply_regexp send_charset sendmail shell sidebar_delim sidebar_delim_chars
- \ sidebar_divider_char sidebar_format sidebar_indent_string sidebar_sort_method
- \ signature simple_search smileys smime_ca_location smime_certificates
+ \ pop_host pop_oauth_refresh_command pop_pass pop_user post_indent_str post_indent_string
+ \ postpone_encrypt_as postponed preconnect print_cmd print_command query_command
+ \ quote_regexp realname record reply_regexp send_charset send_multipart_alternative_filter
+ \ sendmail shell sidebar_delim
+ \ sidebar_delim_chars sidebar_divider_char sidebar_format sidebar_indent_string
+ \ sidebar_sort_method signature simple_search smileys smime_ca_location smime_certificates
\ smime_default_key smime_encrypt_with smime_keys smime_sign_as smime_sign_digest_alg
- \ smtp_authenticators smtp_pass smtp_url sort sort_alias sort_aux sort_browser
- \ spam_separator spoolfile ssl_ca_certificates_file ssl_ciphers ssl_client_cert
- \ status_chars tmpdir to_chars trash ts_icon_format ts_status_format tunnel visual
+ \ smtp_authenticators smtp_oauth_refresh_command smtp_pass smtp_url sort sort_alias
+ \ sort_aux sort_browser sort_thread_groups spam_separator spoolfile ssl_ca_certificates_file
+ \ ssl_ciphers ssl_client_cert ssl_verify_host_override status_chars tmpdir to_chars trash
+ \ ts_icon_format ts_status_format tunnel visual
\ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr
" Present in 1.4.2.1 (pgp_create_traditional was a bool then)
@@ -422,11 +439,11 @@ syn keyword muttrcMenu contained alias attach browser compose editor index page
syn match muttrcMenuList "\S\+" contained contains=muttrcMenu
syn match muttrcMenuCommas /,/ contained
-syn keyword muttrcHooks contained skipwhite account-hook charset-hook iconv-hook message-hook folder-hook mbox-hook save-hook fcc-hook fcc-save-hook send-hook send2-hook reply-hook crypt-hook
+syn keyword muttrcHooks contained skipwhite account-hook charset-hook iconv-hook index-format-hook message-hook folder-hook mbox-hook save-hook fcc-hook fcc-save-hook send-hook send2-hook reply-hook crypt-hook
syn keyword muttrcCommand skipwhite
- \ alternative_order auto_view exec hdr_order iconv-hook ignore mailboxes
- \ mailto_allow mime_lookup my_hdr pgp-hook push score sidebar_whitelist source
+ \ alternative_order auto_view cd exec hdr_order iconv-hook ignore index-format-hook mailboxes
+ \ mailto_allow mime_lookup my_hdr pgp-hook push run score sidebar_whitelist source
\ unalternative_order unalternative_order unauto_view ungroup unhdr_order
\ unignore unmailboxes unmailto_allow unmime_lookup unmono unmy_hdr unscore
\ unsidebar_whitelist
@@ -470,19 +487,24 @@ syn match muttrcFunction contained "\<link-threads\>"
syn match muttrcFunction contained "\<\%(backward\|capitalize\|downcase\|forward\|kill\|upcase\)-word\>"
syn match muttrcFunction contained "\<\%(delete\|filter\|first\|last\|next\|pipe\|previous\|print\|save\|select\|tag\|undelete\)-entry\>"
syn match muttrcFunction contained "\<attach-\%(file\|key\)\>"
+syn match muttrcFunction contained "\<background-compose-menu\>"
+syn match muttrcFunction contained "\<browse-mailbox\>"
syn match muttrcFunction contained "\<change-\%(dir\|folder\|folder-readonly\)\>"
syn match muttrcFunction contained "\<check-\%(new\|traditional-pgp\)\>"
syn match muttrcFunction contained "\<current-\%(bottom\|middle\|top\)\>"
syn match muttrcFunction contained "\<decode-\%(copy\|save\)\>"
syn match muttrcFunction contained "\<delete-\%(char\|pattern\|subthread\)\>"
+syn match muttrcFunction contained "\<descend-directory\>"
syn match muttrcFunction contained "\<display-\%(address\|toggle-weed\)\>"
syn match muttrcFunction contained "\<echo\>"
syn match muttrcFunction contained "\<edit\%(-\%(bcc\|cc\|description\|encoding\|fcc\|file\|from\|headers\|label\|mime\|reply-to\|subject\|to\|type\)\)\?\>"
syn match muttrcFunction contained "\<enter-\%(command\|mask\)\>"
syn match muttrcFunction contained "\<error-history\>"
+syn match muttrcFunction contained "\<group-chat-reply\>"
syn match muttrcFunction contained "\<half-\%(up\|down\)\>"
syn match muttrcFunction contained "\<history-\%(up\|down\|search\)\>"
syn match muttrcFunction contained "\<kill-\%(eol\|eow\|line\)\>"
+syn match muttrcFunction contained "\<move-\%(down\|up\)\>"
syn match muttrcFunction contained "\<next-\%(line\|new\%(-then-unread\)\?\|page\|subthread\|undeleted\|unread\|unread-mailbox\)\>"
syn match muttrcFunction contained "\<previous-\%(line\|new\%(-then-unread\)\?\|page\|subthread\|undeleted\|unread\)\>"
syn match muttrcFunction contained "\<search\%(-\%(next\|opposite\|reverse\|toggle\)\)\?\>"
@@ -490,15 +512,15 @@ syn match muttrcFunction contained "\<show-\%(limit\|version\)\>"
syn match muttrcFunction contained "\<sort-\%(mailbox\|reverse\)\>"
syn match muttrcFunction contained "\<tag-\%(pattern\|\%(sub\)\?thread\|prefix\%(-cond\)\?\)\>"
syn match muttrcFunction contained "\<end-cond\>"
-syn match muttrcFunction contained "\<sidebar-\%(next\|next-new\|open\|page-down\|page-up\|prev\|prev-new\|toggle-visible\)\>"
+syn match muttrcFunction contained "\<sidebar-\%(first\|last\|next\|next-new\|open\|page-down\|page-up\|prev\|prev-new\|toggle-visible\)\>"
syn match muttrcFunction contained "\<toggle-\%(mailboxes\|new\|quoted\|subscribed\|unlink\|write\)\>"
syn match muttrcFunction contained "\<undelete-\%(pattern\|subthread\)\>"
syn match muttrcFunction contained "\<collapse-\%(parts\|thread\|all\)\>"
syn match muttrcFunction contained "\<rename-attachment\>"
syn match muttrcFunction contained "\<subjectrx\>"
syn match muttrcFunction contained "\<\%(un\)\?setenv\>"
-syn match muttrcFunction contained "\<view-\%(attach\|attachments\|file\|mailcap\|name\|text\)\>"
-syn match muttrcFunction contained "\<\%(backspace\|backward-char\|bol\|bottom\|bottom-page\|buffy-cycle\|clear-flag\|complete\%(-query\)\?\|copy-file\|create-alias\|detach-file\|eol\|exit\|extract-keys\|\%(imap-\)\?fetch-mail\|forget-passphrase\|forward-char\|group-reply\|help\|ispell\|jump\|limit\|list-reply\|mail\|mail-key\|mark-as-new\|middle-page\|new-mime\|noop\|pgp-menu\|query\|query-append\|quit\|quote-char\|read-subthread\|redraw-screen\|refresh\|rename-file\|reply\|select-new\|set-flag\|shell-escape\|skip-quoted\|sort\|subscribe\|sync-mailbox\|top\|top-page\|transpose-chars\|unsubscribe\|untag-pattern\|verify-key\|what-key\|write-fcc\)\>"
+syn match muttrcFunction contained "\<view-\%(alt\|alt-text\|alt-mailcap\|alt-pager\|attach\|attachments\|file\|mailcap\|name\|pager\|text\)\>"
+syn match muttrcFunction contained "\<\%(backspace\|backward-char\|bol\|bottom\|bottom-page\|buffy-cycle\|check-stats\|clear-flag\|complete\%(-query\)\?\|compose-to-sender\|copy-file\|create-alias\|detach-file\|eol\|exit\|extract-keys\|\%(imap-\)\?fetch-mail\|forget-passphrase\|forward-char\|group-reply\|help\|ispell\|jump\|limit\|list-action\|list-reply\|mail\|mail-key\|mark-as-new\|middle-page\|new-mime\|noop\|pgp-menu\|query\|query-append\|quit\|quote-char\|read-subthread\|redraw-screen\|refresh\|rename-file\|reply\|select-new\|set-flag\|shell-escape\|skip-headers\|skip-quoted\|sort\|subscribe\|sync-mailbox\|top\|top-page\|transpose-chars\|unsubscribe\|untag-pattern\|verify-key\|what-key\|write-fcc\)\>"
syn keyword muttrcFunction contained imap-logout-all
if use_mutt_sidebar == 1
syn match muttrcFunction contained "\<sidebar-\%(prev\|next\|open\|scroll-up\|scroll-down\)"
@@ -519,6 +541,9 @@ syn match muttrcPatHookNot contained /!\s*/ skipwhite nextgroup=muttrcPattern
syn match muttrcPatHooks /\<\%(mbox\|crypt\)-hook\>/ skipwhite nextgroup=muttrcPatHookNot,muttrcPattern
syn match muttrcPatHooks /\<\%(message\|reply\|send\|send2\|save\|\|fcc\%(-save\)\?\)-hook\>/ skipwhite nextgroup=muttrcPatHookNot,muttrcOptPattern
+syn match muttrcIndexFormatHookName contained /\S\+/ skipwhite nextgroup=muttrcPattern,muttrcString
+syn match muttrcIndexFormatHook /index-format-hook/ skipwhite nextgroup=muttrcIndexFormatHookName,muttrcString
+
syn match muttrcBindFunction contained /\S\+\>/ skipwhite contains=muttrcFunction
syn match muttrcBindFunctionNL contained /\s*\\$/ skipwhite skipnl nextgroup=muttrcBindFunction,muttrcBindFunctionNL
syn match muttrcBindKey contained /\S\+/ skipwhite contains=muttrcKey nextgroup=muttrcBindFunction,muttrcBindFunctionNL
@@ -751,6 +776,8 @@ hi def link muttrcShellString muttrcEscape
hi def link muttrcRXHooks muttrcCommand
hi def link muttrcRXHookNot Type
hi def link muttrcPatHooks muttrcCommand
+hi def link muttrcIndexFormatHookName muttrcCommand
+hi def link muttrcIndexFormatHook muttrcCommand
hi def link muttrcPatHookNot Type
hi def link muttrcFormatConditionals2 Type
hi def link muttrcIndexFormatStr muttrcString
@@ -761,11 +788,13 @@ hi def link muttrcAliasFormatEscapes muttrcEscape
hi def link muttrcAttachFormatStr muttrcString
hi def link muttrcAttachFormatEscapes muttrcEscape
hi def link muttrcAttachFormatConditionals muttrcFormatConditionals2
+hi def link muttrcBackgroundFormatStr muttrcString
hi def link muttrcComposeFormatStr muttrcString
hi def link muttrcComposeFormatEscapes muttrcEscape
hi def link muttrcFolderFormatStr muttrcString
hi def link muttrcFolderFormatEscapes muttrcEscape
hi def link muttrcFolderFormatConditionals muttrcFormatConditionals2
+hi def link muttrcMessageIdFormatStr muttrcString
hi def link muttrcMixFormatStr muttrcString
hi def link muttrcMixFormatEscapes muttrcEscape
hi def link muttrcMixFormatConditionals muttrcFormatConditionals2
@@ -787,10 +816,6 @@ hi def link muttrcTimeEscapes muttrcEscape
hi def link muttrcPGPTimeEscapes muttrcEscape
hi def link muttrcStrftimeEscapes Type
hi def link muttrcStrftimeFormatStr muttrcString
-hi def link muttrcFormatErrors Error
-
-hi def link muttrcBindFunctionNL SpecialChar
-hi def link muttrcBindKeyNL SpecialChar
hi def link muttrcBindMenuListNL SpecialChar
hi def link muttrcMacroDescrNL SpecialChar
hi def link muttrcMacroBodyNL SpecialChar
diff --git a/runtime/syntax/nasm.vim b/runtime/syntax/nasm.vim
index d763033225..e1dfc1db12 100644
--- a/runtime/syntax/nasm.vim
+++ b/runtime/syntax/nasm.vim
@@ -3,8 +3,12 @@
" Maintainer: Andrii Sokolov <andriy145@gmail.com>
" Original Author: Manuel M.H. Stol <Manuel.Stol@allieddata.nl>
" Former Maintainer: Manuel M.H. Stol <Manuel.Stol@allieddata.nl>
-" Contributors: Leonard König <leonard.r.koenig@gmail.com> (C string highlighting), Peter Stanhope <dev.rptr@gmail.com> (Add missing 64-bit mode registers)
-" Last Change: 2017 Jan 23
+" Contributors:
+" Leonard König <leonard.r.koenig@gmail.com> (C string highlighting),
+" Peter Stanhope <dev.rptr@gmail.com> (Add missing 64-bit mode registers)
+" Frédéric Hamel <rederic.hamel123@gmail.com> (F16c support, partial AVX
+" support, other)
+" Last Change: 2023 Sep 7
" NASM Home: http://www.nasm.us/
@@ -246,12 +250,12 @@ syn match nasmSegRegister "\<[C-GS]S\>"
syn match nasmSpcRegister "\<E\=IP\>"
syn match nasmFpuRegister "\<ST\o\>"
syn match nasmMmxRegister "\<MM\o\>"
-syn match nasmSseRegister "\<XMM\o\>"
+syn match nasmAvxRegister "\<[XYZ]MM\d\{1,2}\>"
syn match nasmCtrlRegister "\<CR\o\>"
syn match nasmDebugRegister "\<DR\o\>"
syn match nasmTestRegister "\<TR\o\>"
syn match nasmRegisterError "\<\(CR[15-9]\|DR[4-58-9]\|TR[0-28-9]\)\>"
-syn match nasmRegisterError "\<X\=MM[8-9]\>"
+syn match nasmRegisterError "\<[XYZ]MM\(3[2-9]\|[04-9]\d\)\>"
syn match nasmRegisterError "\<ST\((\d)\|[8-9]\>\)"
syn match nasmRegisterError "\<E\([A-D][HL]\|[C-GS]S\)\>"
" Memory reference operand (address):
@@ -277,7 +281,7 @@ syn match nasmInstrModifier "\<F\(ADD\|MUL\|\(DIV\|SUB\)R\=\)\s\+TO\>"lc=5,ms=
" NAsm directives
syn keyword nasmRepeat TIMES
syn keyword nasmDirective ALIGN[B] INCBIN EQU NOSPLIT SPLIT
-syn keyword nasmDirective ABSOLUTE BITS SECTION SEGMENT
+syn keyword nasmDirective ABSOLUTE BITS SECTION SEGMENT DEFAULT
syn keyword nasmDirective ENDSECTION ENDSEGMENT
syn keyword nasmDirective __SECT__
" Macro created standard directives: (requires %include)
@@ -309,7 +313,7 @@ syn match nasmStdInstruction "\<\(CMOV\|J\|SET\)\(N\=\([ABGL]E\=\|[CEOSZ]\)\|P
syn match nasmStdInstruction "\<POP\>"
syn keyword nasmStdInstruction AAA AAD AAM AAS ADC ADD AND
syn keyword nasmStdInstruction BOUND BSF BSR BSWAP BT[C] BTR BTS
-syn keyword nasmStdInstruction CALL CBW CDQ CLC CLD CMC CMP CMPSB CMPSD CMPSW CMPSQ
+syn keyword nasmStdInstruction CALL CBW CDQ CDQE CLC CLD CMC CMP CMPSB CMPSD CMPSW CMPSQ
syn keyword nasmStdInstruction CMPXCHG CMPXCHG8B CPUID CWD[E] CQO
syn keyword nasmStdInstruction DAA DAS DEC DIV ENTER
syn keyword nasmStdInstruction IDIV IMUL INC INT[O] IRET[D] IRETW IRETQ
@@ -319,6 +323,7 @@ syn keyword nasmStdInstruction LODSW LOOP[E] LOOPNE LOOPNZ LOOPZ LSS
syn keyword nasmStdInstruction MOVSB MOVSD MOVSW MOVSX MOVSQ MOVZX MUL NEG NOP NOT
syn keyword nasmStdInstruction OR POPA[D] POPAW POPF[D] POPFW POPFQ
syn keyword nasmStdInstruction PUSH[AD] PUSHAW PUSHF[D] PUSHFW PUSHFQ
+syn keyword nasmStdInstruction PAUSE
syn keyword nasmStdInstruction RCL RCR RETF RET[N] ROL ROR
syn keyword nasmStdInstruction SAHF SAL SAR SBB SCASB SCASD SCASW
syn keyword nasmStdInstruction SHL[D] SHR[D] STC STD STOSB STOSD STOSW STOSQ SUB
@@ -405,6 +410,62 @@ syn keyword nasmSseInstruction ORPS RCPPS RCPSS RSQRTPS RSQRTSS
syn keyword nasmSseInstruction SHUFPS SQRTPS SQRTSS STMXCSR SUBPS SUBSS
syn keyword nasmSseInstruction UCOMISS UNPCKHPS UNPCKLPS XORPS
+" F16c Instructions
+syn keyword nasmF16CInstruction VCVTPH2PS VCVTPS2PH
+
+" AVX Instructions
+syn keyword nasmAVXInstruction VCVTDQ2PD VCVTDQ2PS VCVTPD2DQ VCVTPD2P VCVTPD2PS
+syn keyword nasmAVXInstruction VCVTPS2DQ VCVTPS2PD
+syn keyword nasmAVXInstruction VCVTSD2SI VCVTSD2SS VCVTSI2SD VCVTSI2SS VCVTSS2SD VCVTSS2SI
+syn keyword nasmAVXInstruction VMAXPS VMAXSS VMINPS VMINSS VMOVAPS VMOVHLPS VMOVHPS
+syn keyword nasmAVXInstruction VMAXPD VMAXSD VMINPD VMINSD VMOVAPD VMOVHLPD VMOVHPD
+syn keyword nasmAVXInstruction VMOVLHPS VMOVLPS VMOVMSKPS VMOVNTPS VMOVSS VMOVUPS
+syn keyword nasmAVXInstruction VMULPS VMULSS VPXOR
+
+syn match nasmInstructnError "\<VP\a\{3}R\a\>"
+syn match nasmAVXInstruction "\<VP\(INS\|EXT\)R[BDQW]\>"
+
+syn keyword nasmAVXInstruction VORPS VPABSB VPABSD VPABSW
+syn keyword nasmAVXInstruction PACKSSDW VPACKSSWB VPACKUSDW VPACKUSWB VPADDD
+syn keyword nasmAVXInstruction PADDQ VPADDSB VPADDSW VPADDUSB VPADDUSW
+syn keyword nasmAVXInstruction PADDW VPALIGNR VPAND VPANDN VPAVGB
+syn keyword nasmAVXInstruction PAVGW VPBLENDD VPBLENDVB VPBLENDW VPBROADCASTB
+syn keyword nasmAVXInstruction PBROADCASTD VPBROADCASTQ VPBROADCASTW VPCLMULQDQ VPCMOV
+syn keyword nasmAVXInstruction PCMPEQB VPCMPEQD VPCMPEQQ VPCMPEQW VPCMPESTRI
+syn keyword nasmAVXInstruction PCMPESTRM VPCMPGTB VPCMPGTD VPCMPGTQ VPCMPGTW
+syn keyword nasmAVXInstruction PCMPISTRI VPCMPISTRM VPCOMB VPCOMD VPCOMQ
+syn keyword nasmAVXInstruction PCOMUB VPCOMUD VPCOMUQ VPCOMUW VPCOMW
+syn keyword nasmAVXInstruction PERM2FVPERM2IVPERMD VPERMIL2PD VPERMIL2PS VPERMILPD VPERMILPS
+syn keyword nasmAVXInstruction PERMPD VPERMPS VPERMQ VPEXTRB VPEXTRD
+syn keyword nasmAVXInstruction PEXTRQ VPEXTRW VPGATHERDD VPGATHERDQ VPGATHERQD
+syn keyword nasmAVXInstruction PGATHERQQ VPHADDBD VPHADDBQ VPHADDBW VPHADDD
+syn keyword nasmAVXInstruction PHADDDQ VPHADDSW VPHADDUBQ VPHADDUBW VPHADDUDQ
+syn keyword nasmAVXInstruction PHADDUWD VPHADDUWQ VPHADDW VPHADDWD VPHADDWQ
+syn keyword nasmAVXInstruction PHMINPOSUW VPHSUBBW VPHSUBD VPHSUBDQ VPHSUBSW
+syn keyword nasmAVXInstruction PHSUBW VPHSUBWD VPINSRB VPINSRD VPINSRQ
+syn keyword nasmAVXInstruction PINSRW VPMACSDD VPMACSDQH
+syn keyword nasmAVXInstruction VPMACSDQL VPMACSSDD VPMACSSDQL VPMACSSQH VPMACSSWD
+syn keyword nasmAVXInstruction VPMACSSWW VPMACSWD VPMACSWW VPMADCSSWD VPMADCSWD
+syn keyword nasmAVXInstruction VPMADDUBSW VPMADDWD VPMASKMOVD VPMASKMOVQ VPMAXSB
+syn keyword nasmAVXInstruction VPMAXSD VPMAXSW VPMAXUB VPMAXUD VPMAXUW
+syn keyword nasmAVXInstruction VPMINSB VPMINSD VPMINSW VPMINUB VPMINUD
+syn keyword nasmAVXInstruction VPMINUW VPMOVMSKB VPMOVSXBD VPMOVSXBQ VPMOVSXBW
+syn keyword nasmAVXInstruction VPMOVSXDQ VPMOVSXWD VPMOVSXWQ VPMOVZXBD VPMOVZXBQ
+syn keyword nasmAVXInstruction VPMOVZXBW VPMOVZXDQ VPMOVZXWD VPMOVZXWQ VPMULDQ
+syn keyword nasmAVXInstruction VPMULHRSW VPMULHUW VPMULHW VPMULLD VPMULLW
+syn keyword nasmAVXInstruction VPMULUDQ VPOR VPPERM VPROTB VPROTD
+syn keyword nasmAVXInstruction VPROTQ VPROTW VPSADBW VPSHAB VPSHAD
+syn keyword nasmAVXInstruction VPSHAQ VPSHAW VPSHLB VPSHLD VPSHLQ
+syn keyword nasmAVXInstruction VPSHLW VPSHUFB VPSHUFD VPSHUFHW VPSHUFLW
+syn keyword nasmAVXInstruction VPSIGNB VPSIGND VPSIGNW VPSLLD VPSLLDQ
+syn keyword nasmAVXInstruction VPSLLQ VPSLLVD VPSLLVQ VPSLLW VPSRAD
+syn keyword nasmAVXInstruction VPSRAVD VPSRAW VPSRLD VPSRLDQ VPSRLQ
+syn keyword nasmAVXInstruction VPSRLVD VPSRLVQ VPSRLW VPSUBB VPSUBD
+syn keyword nasmAVXInstruction VPSUBQ VPSUBSB VPSUBSW VPSUBUSB VPSUBUSW
+syn keyword nasmAVXInstruction VPSUBW VPTEST VPUNPCKHBW VPUNPCKHDQ VPUNPCKHQDQ
+syn keyword nasmAVXInstruction VPUNPCKHWD VPUNPCKLBW VPUNPCKLDQ VPUNPCKLQDQ VPUNPCKLWD
+syn keyword nasmAVXInstruction VPXOR VRCPPS
+
" Three Dimensional Now Packed Instructions: (requires 3DNow! unit)
syn keyword nasmNowInstruction FEMMS PAVGUSB PF2ID PFACC PFADD PFCMPEQ PFCMPGE
@@ -515,13 +576,14 @@ hi def link nasmDbgInstruction Debug
hi def link nasmFpuInstruction Statement
hi def link nasmMmxInstruction Statement
hi def link nasmSseInstruction Statement
+hi def link nasmF16cInstruction Statement
+hi def link nasmAVXInstruction Statement
hi def link nasmNowInstruction Statement
hi def link nasmAmdInstruction Special
hi def link nasmCrxInstruction Special
hi def link nasmUndInstruction Todo
hi def link nasmInstructnError Error
-
let b:current_syntax = "nasm"
" vim:ts=8 sw=4
diff --git a/runtime/syntax/netrc.vim b/runtime/syntax/netrc.vim
index 4d068a1b76..567aaa96de 100644
--- a/runtime/syntax/netrc.vim
+++ b/runtime/syntax/netrc.vim
@@ -2,6 +2,7 @@
" Language: netrc(5) configuration file
" Previous Maintainer: Nikolai Weibull <now@bitwi.se>
" Latest Revision: 2010-01-03
+" Last Change: 2023 Feb 27 by Keith Smiley
if exists("b:current_syntax")
finish
@@ -35,6 +36,8 @@ syn keyword netrcSpecial contained anonymous
syn match netrcInit contained '\<init$'
\ nextgroup=netrcMacro skipwhite skipnl
+syn match netrcComment '#.*$'
+
syn sync fromstart
hi def link netrcKeyword Keyword
@@ -45,6 +48,7 @@ hi def link netrcPassword String
hi def link netrcMacroName String
hi def link netrcSpecial Special
hi def link netrcInit Special
+hi def link netrcComment Comment
let b:current_syntax = "netrc"
diff --git a/runtime/syntax/nginx.vim b/runtime/syntax/nginx.vim
index 18dd50cbb2..d036c123de 100644
--- a/runtime/syntax/nginx.vim
+++ b/runtime/syntax/nginx.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: nginx.conf
" Maintainer: Chris Aumann <me@chr4.org>
-" Last Change: Apr 15, 2017
+" Last Change: Nov 25, 2023
if exists("b:current_syntax")
finish
@@ -84,6 +84,8 @@ syn keyword ngxListenOptions default_server contained
syn keyword ngxListenOptions ssl contained
syn keyword ngxListenOptions http2 contained
syn keyword ngxListenOptions spdy contained
+syn keyword ngxListenOptions http3 contained
+syn keyword ngxListenOptions quic contained
syn keyword ngxListenOptions proxy_protocol contained
syn keyword ngxListenOptions setfib contained
syn keyword ngxListenOptions fastopen contained
@@ -258,6 +260,7 @@ syn keyword ngxDirective hls_forward_args
syn keyword ngxDirective hls_fragment
syn keyword ngxDirective hls_mp4_buffer_size
syn keyword ngxDirective hls_mp4_max_buffer_size
+syn keyword ngxDirective http2
syn keyword ngxDirective http2_chunk_size
syn keyword ngxDirective http2_body_preread_size
syn keyword ngxDirective http2_idle_timeout
@@ -265,8 +268,17 @@ syn keyword ngxDirective http2_max_concurrent_streams
syn keyword ngxDirective http2_max_field_size
syn keyword ngxDirective http2_max_header_size
syn keyword ngxDirective http2_max_requests
+syn keyword ngxDirective http2_push
+syn keyword ngxDirective http2_push_preload
syn keyword ngxDirective http2_recv_buffer_size
syn keyword ngxDirective http2_recv_timeout
+syn keyword ngxDirective http3
+syn keyword ngxDirective http3_hq
+syn keyword ngxDirective http3_max_concurrent_pushes
+syn keyword ngxDirective http3_max_concurrent_streams
+syn keyword ngxDirective http3_push
+syn keyword ngxDirective http3_push_preload
+syn keyword ngxDirective http3_stream_buffer_size
syn keyword ngxDirective if_modified_since
syn keyword ngxDirective ignore_invalid_headers
syn keyword ngxDirective image_filter
@@ -444,6 +456,10 @@ syn keyword ngxDirective proxy_temp_path
syn keyword ngxDirective proxy_timeout
syn keyword ngxDirective proxy_upload_rate
syn keyword ngxDirective queue
+syn keyword ngxDirective quic_gso
+syn keyword ngxDirective quic_host_key
+syn keyword ngxDirective quic_mtu
+syn keyword ngxDirective quic_retry
syn keyword ngxDirective random_index
syn keyword ngxDirective read_ahead
syn keyword ngxDirective real_ip_header
@@ -545,8 +561,10 @@ syn keyword ngxDirective ssl_certificate
syn keyword ngxDirective ssl_certificate_key
syn keyword ngxDirective ssl_ciphers
syn keyword ngxDirective ssl_client_certificate
+syn keyword ngxDirective ssl_conf_command
syn keyword ngxDirective ssl_crl
syn keyword ngxDirective ssl_dhparam
+syn keyword ngxDirective ssl_early_data
syn keyword ngxDirective ssl_ecdh_curve
syn keyword ngxDirective ssl_engine
syn keyword ngxDirective ssl_handshake_timeout
@@ -556,6 +574,7 @@ syn keyword ngxSSLPreferServerCiphersOn on contained
syn keyword ngxSSLPreferServerCiphersOff off contained
syn keyword ngxDirective ssl_preread
syn keyword ngxDirective ssl_protocols nextgroup=ngxSSLProtocol,ngxSSLProtocolDeprecated skipwhite
+syn keyword ngxDirective ssl_reject_handshake
syn match ngxSSLProtocol 'TLSv1' contained nextgroup=ngxSSLProtocol,ngxSSLProtocolDeprecated skipwhite
syn match ngxSSLProtocol 'TLSv1\.1' contained nextgroup=ngxSSLProtocol,ngxSSLProtocolDeprecated skipwhite
syn match ngxSSLProtocol 'TLSv1\.2' contained nextgroup=ngxSSLProtocol,ngxSSLProtocolDeprecated skipwhite
@@ -622,6 +641,7 @@ syn keyword ngxDirective uwsgi_buffering
syn keyword ngxDirective uwsgi_buffers
syn keyword ngxDirective uwsgi_busy_buffers_size
syn keyword ngxDirective uwsgi_cache
+syn keyword ngxDirective uwsgi_cache_background_update
syn keyword ngxDirective uwsgi_cache_bypass
syn keyword ngxDirective uwsgi_cache_key
syn keyword ngxDirective uwsgi_cache_lock
@@ -2225,6 +2245,19 @@ syn keyword ngxDirectiveThirdParty xss_override_status
syn keyword ngxDirectiveThirdParty xss_check_status
syn keyword ngxDirectiveThirdParty xss_input_types
+" CT Module <https://github.com/grahamedgecombe/nginx-ct>
+" Certificate Transparency module for nginx
+syn keyword ngxDirectiveThirdParty ssl_ct
+syn keyword ngxDirectiveThirdParty ssl_ct_static_scts
+
+" Dynamic TLS records patch <https://github.com/cloudflare/sslconfig/blob/master/patches/nginx__dynamic_tls_records.patch>
+" TLS Dynamic Record Resizing
+syn keyword ngxDirectiveThirdParty ssl_dyn_rec_enable
+syn keyword ngxDirectiveThirdParty ssl_dyn_rec_size_hi
+syn keyword ngxDirectiveThirdParty ssl_dyn_rec_size_lo
+syn keyword ngxDirectiveThirdParty ssl_dyn_rec_threshold
+syn keyword ngxDirectiveThirdParty ssl_dyn_rec_timeout
+
" ZIP Module <https://www.nginx.com/resources/wiki/modules/zip/>
" ZIP archiver for nginx
diff --git a/runtime/syntax/nix.vim b/runtime/syntax/nix.vim
index c07676a4a8..ef52cddf46 100644
--- a/runtime/syntax/nix.vim
+++ b/runtime/syntax/nix.vim
@@ -1,11 +1,12 @@
" Vim syntax file
" Language: Nix
-" Maintainer: James Fleming <james@electronic-quill.net>
+" Maintainer: James Fleming <james@electronic-quill.net>
+" (Github username: equill)
" Original Author: Daiderd Jordan <daiderd@gmail.com>
" Acknowledgement: Based on vim-nix maintained by Daiderd Jordan <daiderd@gmail.com>
" https://github.com/LnL7/vim-nix
" License: MIT
-" Last Change: 2022 Dec 06
+" Last Change: 2023 Aug 19
if exists("b:current_syntax")
finish
@@ -68,7 +69,8 @@ syn match nixAttribute "[a-zA-Z_][a-zA-Z0-9_'-]*\ze\%([^a-zA-Z0-9_'.-]\|$\)" con
syn region nixAttributeAssignment start="=" end="\ze;" contained contains=@nixExpr
syn region nixAttributeDefinition start=/\ze[a-zA-Z_"$]/ end=";" contained contains=nixComment,nixAttribute,nixInterpolation,nixSimpleString,nixAttributeDot,nixAttributeAssignment
-syn region nixInheritAttributeScope start="(" end="\ze)" contained contains=@nixExpr
+syn region nixInheritAttributeSubExpr start="("ms=e+1 end="\ze)" contained contains=nixAttributeDot,@nixExpr
+syn region nixInheritAttributeScope start="\ze(" end=")" contained contains=nixInheritAttributeSubExpr
syn region nixAttributeDefinition matchgroup=nixInherit start="\<inherit\>" end=";" contained contains=nixComment,nixInheritAttributeScope,nixAttribute
syn region nixAttributeSet start="{" end="}" contains=nixComment,nixAttributeDefinition
@@ -97,7 +99,7 @@ syn match nixArgOperator '[a-zA-Z_][a-zA-Z0-9_'-]*\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{
"
" "\%(\s\|#.\{-\}\n\|\n\|/\*\_.\{-\}\*/\)*"
"
-" It is also used throught the whole file and is marked with 'v's as well.
+" It is also used throughout the whole file and is marked with 'v's as well.
"
" Fortunately the matching rules for function arguments are much simpler than
" for real attribute sets, because we can stop when we hit the first ellipsis or
diff --git a/runtime/syntax/nosyntax.vim b/runtime/syntax/nosyntax.vim
index 0ab3412373..a761d712b7 100644
--- a/runtime/syntax/nosyntax.vim
+++ b/runtime/syntax/nosyntax.vim
@@ -1,6 +1,7 @@
" Vim syntax support file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2006 Apr 16
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This file is used for ":syntax off".
" It removes the autocommands and stops highlighting for all buffers.
diff --git a/runtime/syntax/ora.vim b/runtime/syntax/ora.vim
index 99034793f2..ab091a2eee 100644
--- a/runtime/syntax/ora.vim
+++ b/runtime/syntax/ora.vim
@@ -449,7 +449,7 @@ hi def link oraString String "strings
hi def link oraSpecial Special "special characters
hi def link oraError Error "errors
-hi def link oraParenError oraError "errors caused by mismatching parantheses
+hi def link oraParenError oraError "errors caused by mismatching parentheses
hi def link oraComment Comment "comments
diff --git a/runtime/syntax/po.vim b/runtime/syntax/po.vim
index 15d09b18bd..08d6baec27 100644
--- a/runtime/syntax/po.vim
+++ b/runtime/syntax/po.vim
@@ -42,7 +42,7 @@ syn match poHeaderItem "\(Project-Id-Version\|Report-Msgid-Bugs-To\|POT-Crea
syn match poHeaderUndefined "\(PACKAGE VERSION\|YEAR-MO-DA HO:MI+ZONE\|FULL NAME <EMAIL@ADDRESS>\|LANGUAGE <LL@li.org>\|CHARSET\|ENCODING\|INTEGER\|EXPRESSION\)" contained
syn match poCopyrightUnset "SOME DESCRIPTIVE TITLE\|FIRST AUTHOR <EMAIL@ADDRESS>, YEAR\|Copyright (C) YEAR Free Software Foundation, Inc\|YEAR THE PACKAGE\'S COPYRIGHT HOLDER\|PACKAGE" contained
-" Translation comment block including: translator comment, automatic coments, flags and locations
+" Translation comment block including: translator comment, automatic comments, flags and locations
syn match poComment "^#.*$"
syn keyword poFlagFuzzy fuzzy contained
syn match poCommentTranslator "^# .*$" contains=poCopyrightUnset
diff --git a/runtime/syntax/poefilter.vim b/runtime/syntax/poefilter.vim
index f7e92034ee..6561f7a704 100644
--- a/runtime/syntax/poefilter.vim
+++ b/runtime/syntax/poefilter.vim
@@ -2,7 +2,7 @@
" Language: PoE item filter
" Maintainer: ObserverOfTime <chronobserver@disroot.org>
" Filenames: *.filter
-" Last Change: 2022 Oct 07
+" Last Change: 2023 Feb 10
if exists('b:current_syntax')
finish
@@ -17,7 +17,7 @@ syn match poefilterCommentTag /\[[0-9A-Z\[\]]\+\]/ contained
syn match poefilterComment /#.*$/ contains=poefilterTodo,poefilterCommentTag,@Spell
" Blocks
-syn keyword poefilterBlock Show Hide
+syn keyword poefilterBlock Show Hide Minimal
" Conditions
syn keyword poefilterCondition
diff --git a/runtime/syntax/ppd.vim b/runtime/syntax/ppd.vim
index da67e1f39f..6bd57f34e5 100644
--- a/runtime/syntax/ppd.vim
+++ b/runtime/syntax/ppd.vim
@@ -15,7 +15,7 @@ syn match ppdDefine "\*[a-zA-Z0-9\-_]\+:"
syn match ppdUI "\*[a-zA-Z]*\(Open\|Close\)UI"
syn match ppdUIGroup "\*[a-zA-Z]*\(Open\|Close\)Group"
syn match ppdGUIText "/.*:"
-syn match ppdContraints "^*UIConstraints:"
+syn match ppdConstraints "^*UIConstraints:"
" Define the default highlighting.
" Only when an item doesn't have highlighting yet
@@ -27,7 +27,7 @@ hi def link ppdUI Function
hi def link ppdUIGroup Function
hi def link ppdDef String
hi def link ppdGUIText Type
-hi def link ppdContraints Special
+hi def link ppdConstraints Special
let b:current_syntax = "ppd"
diff --git a/runtime/syntax/pymanifest.vim b/runtime/syntax/pymanifest.vim
new file mode 100644
index 0000000000..26bdf797e0
--- /dev/null
+++ b/runtime/syntax/pymanifest.vim
@@ -0,0 +1,44 @@
+" Vim syntax file
+" Language: PyPA manifest
+" Maintainer: ObserverOfTime <chronobserver@disroot.org>
+" Filenames: MANIFEST.in
+" Last Change: 2023 Aug 12
+
+if exists('b:current_syntax')
+ finish
+endif
+
+let s:cpo_save = &cpoptions
+set cpoptions&vim
+
+syn iskeyword @,-
+
+" Comments
+syn keyword pymanifestTodo contained TODO FIXME XXX
+syn match pymanifestComment /\\\@1<!#.*/ contains=pymanifestTodo
+
+" Commands
+syn keyword pymanifestCommand
+ \ include exclude
+ \ recursive-include recursive-exclude
+ \ global-include global-exclude
+ \ graft prune
+
+" Globs & character ranges
+syn match pymanifestGlob /\*\|\*\*\|?/
+syn match pymanifestRange /\\\@1<!\[.\{-}\]/
+
+" Line break
+syn match pymanifestLinebreak /\\$\|\\\ze\s\+#/
+
+hi def link pymanifestCommand Keyword
+hi def link pymanifestComment Comment
+hi def link pymanifestGlob SpecialChar
+hi def link pymanifestLinebreak SpecialKey
+hi def link pymanifestRange Special
+hi def link pymanifestTodo Todo
+
+let b:current_syntax = 'pymanifest'
+
+let &cpoptions = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/syntax/python.vim b/runtime/syntax/python.vim
index ef4da1b448..043ea6d19b 100644
--- a/runtime/syntax/python.vim
+++ b/runtime/syntax/python.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Python
" Maintainer: Zvezdan Petkovic <zpetkovic@acm.org>
-" Last Change: 2022 Jun 28
+" Last Change: 2023 Feb 28
" Credits: Neil Schemenauer <nas@python.ca>
" Dmitry Vasiliev
"
@@ -35,12 +35,26 @@
"
" let python_highlight_all = 1
"
+" The use of Python 2 compatible syntax highlighting can be enforced.
+" The straddling code (Python 2 and 3 compatible), up to Python 3.5,
+" will be also supported.
+"
+" let python_use_python2_syntax = 1
+"
+" This option will exclude all modern Python 3.6 or higher features.
+"
" quit when a syntax file was already loaded.
if exists("b:current_syntax")
finish
endif
+" Use of Python 2 and 3.5 or lower requested.
+if exists("python_use_python2_syntax")
+ runtime! syntax/python2.vim
+ finish
+endif
+
" We need nocompatible mode in order to continue lines with backslashes.
" Original setting will be restored.
let s:cpo_save = &cpo
@@ -91,8 +105,8 @@ syn keyword pythonInclude from import
syn keyword pythonAsync async await
" Soft keywords
-" These keywords do not mean anything unless used in the right context
-" See https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords
+" These keywords do not mean anything unless used in the right context.
+" See https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords
" for more on this.
syn match pythonConditional "^\s*\zscase\%(\s\+.*:.*$\)\@="
syn match pythonConditional "^\s*\zsmatch\%(\s\+.*:\s*\%(#.*\)\=$\)\@="
@@ -164,17 +178,17 @@ syn match pythonEscape "\\$"
" and so on, as specified in the 'Python Language Reference'.
" https://docs.python.org/reference/lexical_analysis.html#numeric-literals
if !exists("python_no_number_highlight")
- " numbers (including longs and complex)
- syn match pythonNumber "\<0[oO]\=\o\+[Ll]\=\>"
- syn match pythonNumber "\<0[xX]\x\+[Ll]\=\>"
- syn match pythonNumber "\<0[bB][01]\+[Ll]\=\>"
- syn match pythonNumber "\<\%([1-9]\d*\|0\)[Ll]\=\>"
- syn match pythonNumber "\<\d\+[jJ]\>"
- syn match pythonNumber "\<\d\+[eE][+-]\=\d\+[jJ]\=\>"
+ " numbers (including complex)
+ syn match pythonNumber "\<0[oO]\%(_\=\o\)\+\>"
+ syn match pythonNumber "\<0[xX]\%(_\=\x\)\+\>"
+ syn match pythonNumber "\<0[bB]\%(_\=[01]\)\+\>"
+ syn match pythonNumber "\<\%([1-9]\%(_\=\d\)*\|0\+\%(_\=0\)*\)\>"
+ syn match pythonNumber "\<\d\%(_\=\d\)*[jJ]\>"
+ syn match pythonNumber "\<\d\%(_\=\d\)*[eE][+-]\=\d\%(_\=\d\)*[jJ]\=\>"
syn match pythonNumber
- \ "\<\d\+\.\%([eE][+-]\=\d\+\)\=[jJ]\=\%(\W\|$\)\@="
+ \ "\<\d\%(_\=\d\)*\.\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\%(\W\|$\)\@="
syn match pythonNumber
- \ "\%(^\|\W\)\zs\d*\.\d\+\%([eE][+-]\=\d\+\)\=[jJ]\=\>"
+ \ "\%(^\|\W\)\zs\%(\d\%(_\=\d\)*\)\=\.\d\%(_\=\d\)*\%([eE][+-]\=\d\%(_\=\d\)*\)\=[jJ]\=\>"
endif
" Group the built-ins in the order in the 'Python Library Reference' for
diff --git a/runtime/syntax/python2.vim b/runtime/syntax/python2.vim
new file mode 100644
index 0000000000..3b30eabbae
--- /dev/null
+++ b/runtime/syntax/python2.vim
@@ -0,0 +1,345 @@
+" Vim syntax file
+" Language: Python 2
+" Maintainer: Zvezdan Petkovic <zpetkovic@acm.org>
+" Last Change: 2016 Oct 29
+" Credits: Neil Schemenauer <nas@python.ca>
+" Dmitry Vasiliev
+"
+" This version is a major rewrite by Zvezdan Petkovic.
+"
+" - introduced highlighting of doctests
+" - updated keywords, built-ins, and exceptions
+" - corrected regular expressions for
+"
+" * functions
+" * decorators
+" * strings
+" * escapes
+" * numbers
+" * space error
+"
+" - corrected synchronization
+" - more highlighting is ON by default, except
+" - space error highlighting is OFF by default
+"
+" Optional highlighting can be controlled using these variables.
+"
+" let python_no_builtin_highlight = 1
+" let python_no_doctest_code_highlight = 1
+" let python_no_doctest_highlight = 1
+" let python_no_exception_highlight = 1
+" let python_no_number_highlight = 1
+" let python_space_error_highlight = 1
+"
+" All the options above can be switched on together.
+"
+" let python_highlight_all = 1
+"
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+" NOTE: This file is a copy of the last commit of runtime/syntax/python.vim
+" that still supported Python 2. There is support for Python 3, up to 3.5,
+" and it was kept in the file as is, because it supports the straddling code
+" (Python 2 and 3 compatible) better.
+""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
+
+" quit when a syntax file was already loaded.
+if exists("b:current_syntax")
+ finish
+endif
+
+" We need nocompatible mode in order to continue lines with backslashes.
+" Original setting will be restored.
+let s:cpo_save = &cpo
+set cpo&vim
+
+if exists("python_no_doctest_highlight")
+ let python_no_doctest_code_highlight = 1
+endif
+
+if exists("python_highlight_all")
+ if exists("python_no_builtin_highlight")
+ unlet python_no_builtin_highlight
+ endif
+ if exists("python_no_doctest_code_highlight")
+ unlet python_no_doctest_code_highlight
+ endif
+ if exists("python_no_doctest_highlight")
+ unlet python_no_doctest_highlight
+ endif
+ if exists("python_no_exception_highlight")
+ unlet python_no_exception_highlight
+ endif
+ if exists("python_no_number_highlight")
+ unlet python_no_number_highlight
+ endif
+ let python_space_error_highlight = 1
+endif
+
+" Keep Python keywords in alphabetical order inside groups for easy
+" comparison with the table in the 'Python Language Reference'
+" https://docs.python.org/2/reference/lexical_analysis.html#keywords,
+" https://docs.python.org/3/reference/lexical_analysis.html#keywords.
+" Groups are in the order presented in NAMING CONVENTIONS in syntax.txt.
+" Exceptions come last at the end of each group (class and def below).
+"
+" Keywords 'with' and 'as' are new in Python 2.6
+" (use 'from __future__ import with_statement' in Python 2.5).
+"
+" Some compromises had to be made to support both Python 3 and 2.
+" We include Python 3 features, but when a definition is duplicated,
+" the last definition takes precedence.
+"
+" - 'False', 'None', and 'True' are keywords in Python 3 but they are
+" built-ins in 2 and will be highlighted as built-ins below.
+" - 'exec' is a built-in in Python 3 and will be highlighted as
+" built-in below.
+" - 'nonlocal' is a keyword in Python 3 and will be highlighted.
+" - 'print' is a built-in in Python 3 and will be highlighted as
+" built-in below (use 'from __future__ import print_function' in 2)
+" - async and await were added in Python 3.5 and are soft keywords.
+"
+syn keyword pythonStatement False None True
+syn keyword pythonStatement as assert break continue del exec global
+syn keyword pythonStatement lambda nonlocal pass print return with yield
+syn keyword pythonStatement class def nextgroup=pythonFunction skipwhite
+syn keyword pythonConditional elif else if
+syn keyword pythonRepeat for while
+syn keyword pythonOperator and in is not or
+syn keyword pythonException except finally raise try
+syn keyword pythonInclude from import
+syn keyword pythonAsync async await
+
+" Decorators (new in Python 2.4)
+" A dot must be allowed because of @MyClass.myfunc decorators.
+syn match pythonDecorator "@" display contained
+syn match pythonDecoratorName "@\s*\h\%(\w\|\.\)*" display contains=pythonDecorator
+
+" Python 3.5 introduced the use of the same symbol for matrix multiplication:
+" https://www.python.org/dev/peps/pep-0465/. We now have to exclude the
+" symbol from highlighting when used in that context.
+" Single line multiplication.
+syn match pythonMatrixMultiply
+ \ "\%(\w\|[])]\)\s*@"
+ \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue
+ \ transparent
+" Multiplication continued on the next line after backslash.
+syn match pythonMatrixMultiply
+ \ "[^\\]\\\s*\n\%(\s*\.\.\.\s\)\=\s\+@"
+ \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue
+ \ transparent
+" Multiplication in a parenthesized expression over multiple lines with @ at
+" the start of each continued line; very similar to decorators and complex.
+syn match pythonMatrixMultiply
+ \ "^\s*\%(\%(>>>\|\.\.\.\)\s\+\)\=\zs\%(\h\|\%(\h\|[[(]\).\{-}\%(\w\|[])]\)\)\s*\n\%(\s*\.\.\.\s\)\=\s\+@\%(.\{-}\n\%(\s*\.\.\.\s\)\=\s\+@\)*"
+ \ contains=ALLBUT,pythonDecoratorName,pythonDecorator,pythonFunction,pythonDoctestValue
+ \ transparent
+
+syn match pythonFunction "\h\w*" display contained
+
+syn match pythonComment "#.*$" contains=pythonTodo,@Spell
+syn keyword pythonTodo FIXME NOTE NOTES TODO XXX contained
+
+" Triple-quoted strings can contain doctests.
+syn region pythonString matchgroup=pythonQuotes
+ \ start=+[uU]\=\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1"
+ \ contains=pythonEscape,@Spell
+syn region pythonString matchgroup=pythonTripleQuotes
+ \ start=+[uU]\=\z('''\|"""\)+ end="\z1" keepend
+ \ contains=pythonEscape,pythonSpaceError,pythonDoctest,@Spell
+syn region pythonRawString matchgroup=pythonQuotes
+ \ start=+[uU]\=[rR]\z(['"]\)+ end="\z1" skip="\\\\\|\\\z1"
+ \ contains=@Spell
+syn region pythonRawString matchgroup=pythonTripleQuotes
+ \ start=+[uU]\=[rR]\z('''\|"""\)+ end="\z1" keepend
+ \ contains=pythonSpaceError,pythonDoctest,@Spell
+
+syn match pythonEscape +\\[abfnrtv'"\\]+ contained
+syn match pythonEscape "\\\o\{1,3}" contained
+syn match pythonEscape "\\x\x\{2}" contained
+syn match pythonEscape "\%(\\u\x\{4}\|\\U\x\{8}\)" contained
+" Python allows case-insensitive Unicode IDs: http://www.unicode.org/charts/
+syn match pythonEscape "\\N{\a\+\%(\s\a\+\)*}" contained
+syn match pythonEscape "\\$"
+
+" It is very important to understand all details before changing the
+" regular expressions below or their order.
+" The word boundaries are *not* the floating-point number boundaries
+" because of a possible leading or trailing decimal point.
+" The expressions below ensure that all valid number literals are
+" highlighted, and invalid number literals are not. For example,
+"
+" - a decimal point in '4.' at the end of a line is highlighted,
+" - a second dot in 1.0.0 is not highlighted,
+" - 08 is not highlighted,
+" - 08e0 or 08j are highlighted,
+"
+" and so on, as specified in the 'Python Language Reference'.
+" https://docs.python.org/2/reference/lexical_analysis.html#numeric-literals
+" https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals
+if !exists("python_no_number_highlight")
+ " numbers (including longs and complex)
+ syn match pythonNumber "\<0[oO]\=\o\+[Ll]\=\>"
+ syn match pythonNumber "\<0[xX]\x\+[Ll]\=\>"
+ syn match pythonNumber "\<0[bB][01]\+[Ll]\=\>"
+ syn match pythonNumber "\<\%([1-9]\d*\|0\)[Ll]\=\>"
+ syn match pythonNumber "\<\d\+[jJ]\>"
+ syn match pythonNumber "\<\d\+[eE][+-]\=\d\+[jJ]\=\>"
+ syn match pythonNumber
+ \ "\<\d\+\.\%([eE][+-]\=\d\+\)\=[jJ]\=\%(\W\|$\)\@="
+ syn match pythonNumber
+ \ "\%(^\|\W\)\zs\d*\.\d\+\%([eE][+-]\=\d\+\)\=[jJ]\=\>"
+endif
+
+" Group the built-ins in the order in the 'Python Library Reference' for
+" easier comparison.
+" https://docs.python.org/2/library/constants.html
+" https://docs.python.org/3/library/constants.html
+" http://docs.python.org/2/library/functions.html
+" http://docs.python.org/3/library/functions.html
+" http://docs.python.org/2/library/functions.html#non-essential-built-in-functions
+" http://docs.python.org/3/library/functions.html#non-essential-built-in-functions
+" Python built-in functions are in alphabetical order.
+if !exists("python_no_builtin_highlight")
+ " built-in constants
+ " 'False', 'True', and 'None' are also reserved words in Python 3
+ syn keyword pythonBuiltin False True None
+ syn keyword pythonBuiltin NotImplemented Ellipsis __debug__
+ " built-in functions
+ syn keyword pythonBuiltin abs all any bin bool bytearray callable chr
+ syn keyword pythonBuiltin classmethod compile complex delattr dict dir
+ syn keyword pythonBuiltin divmod enumerate eval filter float format
+ syn keyword pythonBuiltin frozenset getattr globals hasattr hash
+ syn keyword pythonBuiltin help hex id input int isinstance
+ syn keyword pythonBuiltin issubclass iter len list locals map max
+ syn keyword pythonBuiltin memoryview min next object oct open ord pow
+ syn keyword pythonBuiltin print property range repr reversed round set
+ syn keyword pythonBuiltin setattr slice sorted staticmethod str
+ syn keyword pythonBuiltin sum super tuple type vars zip __import__
+ " Python 2 only
+ syn keyword pythonBuiltin basestring cmp execfile file
+ syn keyword pythonBuiltin long raw_input reduce reload unichr
+ syn keyword pythonBuiltin unicode xrange
+ " Python 3 only
+ syn keyword pythonBuiltin ascii bytes exec
+ " non-essential built-in functions; Python 2 only
+ syn keyword pythonBuiltin apply buffer coerce intern
+ " avoid highlighting attributes as builtins
+ syn match pythonAttribute /\.\h\w*/hs=s+1
+ \ contains=ALLBUT,pythonBuiltin,pythonFunction,pythonAsync
+ \ transparent
+endif
+
+" From the 'Python Library Reference' class hierarchy at the bottom.
+" http://docs.python.org/2/library/exceptions.html
+" http://docs.python.org/3/library/exceptions.html
+if !exists("python_no_exception_highlight")
+ " builtin base exceptions (used mostly as base classes for other exceptions)
+ syn keyword pythonExceptions BaseException Exception
+ syn keyword pythonExceptions ArithmeticError BufferError
+ syn keyword pythonExceptions LookupError
+ " builtin base exceptions removed in Python 3
+ syn keyword pythonExceptions EnvironmentError StandardError
+ " builtin exceptions (actually raised)
+ syn keyword pythonExceptions AssertionError AttributeError
+ syn keyword pythonExceptions EOFError FloatingPointError GeneratorExit
+ syn keyword pythonExceptions ImportError IndentationError
+ syn keyword pythonExceptions IndexError KeyError KeyboardInterrupt
+ syn keyword pythonExceptions MemoryError NameError NotImplementedError
+ syn keyword pythonExceptions OSError OverflowError ReferenceError
+ syn keyword pythonExceptions RuntimeError StopIteration SyntaxError
+ syn keyword pythonExceptions SystemError SystemExit TabError TypeError
+ syn keyword pythonExceptions UnboundLocalError UnicodeError
+ syn keyword pythonExceptions UnicodeDecodeError UnicodeEncodeError
+ syn keyword pythonExceptions UnicodeTranslateError ValueError
+ syn keyword pythonExceptions ZeroDivisionError
+ " builtin OS exceptions in Python 3
+ syn keyword pythonExceptions BlockingIOError BrokenPipeError
+ syn keyword pythonExceptions ChildProcessError ConnectionAbortedError
+ syn keyword pythonExceptions ConnectionError ConnectionRefusedError
+ syn keyword pythonExceptions ConnectionResetError FileExistsError
+ syn keyword pythonExceptions FileNotFoundError InterruptedError
+ syn keyword pythonExceptions IsADirectoryError NotADirectoryError
+ syn keyword pythonExceptions PermissionError ProcessLookupError
+ syn keyword pythonExceptions RecursionError StopAsyncIteration
+ syn keyword pythonExceptions TimeoutError
+ " builtin exceptions deprecated/removed in Python 3
+ syn keyword pythonExceptions IOError VMSError WindowsError
+ " builtin warnings
+ syn keyword pythonExceptions BytesWarning DeprecationWarning FutureWarning
+ syn keyword pythonExceptions ImportWarning PendingDeprecationWarning
+ syn keyword pythonExceptions RuntimeWarning SyntaxWarning UnicodeWarning
+ syn keyword pythonExceptions UserWarning Warning
+ " builtin warnings in Python 3
+ syn keyword pythonExceptions ResourceWarning
+endif
+
+if exists("python_space_error_highlight")
+ " trailing whitespace
+ syn match pythonSpaceError display excludenl "\s\+$"
+ " mixed tabs and spaces
+ syn match pythonSpaceError display " \+\t"
+ syn match pythonSpaceError display "\t\+ "
+endif
+
+" Do not spell doctests inside strings.
+" Notice that the end of a string, either ''', or """, will end the contained
+" doctest too. Thus, we do *not* need to have it as an end pattern.
+if !exists("python_no_doctest_highlight")
+ if !exists("python_no_doctest_code_highlight")
+ syn region pythonDoctest
+ \ start="^\s*>>>\s" end="^\s*$"
+ \ contained contains=ALLBUT,pythonDoctest,pythonFunction,@Spell
+ syn region pythonDoctestValue
+ \ start=+^\s*\%(>>>\s\|\.\.\.\s\|"""\|'''\)\@!\S\++ end="$"
+ \ contained
+ else
+ syn region pythonDoctest
+ \ start="^\s*>>>" end="^\s*$"
+ \ contained contains=@NoSpell
+ endif
+endif
+
+" Sync at the beginning of class, function, or method definition.
+syn sync match pythonSync grouphere NONE "^\%(def\|class\)\s\+\h\w*\s*[(:]"
+
+" The default highlight links. Can be overridden later.
+hi def link pythonStatement Statement
+hi def link pythonConditional Conditional
+hi def link pythonRepeat Repeat
+hi def link pythonOperator Operator
+hi def link pythonException Exception
+hi def link pythonInclude Include
+hi def link pythonAsync Statement
+hi def link pythonDecorator Define
+hi def link pythonDecoratorName Function
+hi def link pythonFunction Function
+hi def link pythonComment Comment
+hi def link pythonTodo Todo
+hi def link pythonString String
+hi def link pythonRawString String
+hi def link pythonQuotes String
+hi def link pythonTripleQuotes pythonQuotes
+hi def link pythonEscape Special
+if !exists("python_no_number_highlight")
+ hi def link pythonNumber Number
+endif
+if !exists("python_no_builtin_highlight")
+ hi def link pythonBuiltin Function
+endif
+if !exists("python_no_exception_highlight")
+ hi def link pythonExceptions Structure
+endif
+if exists("python_space_error_highlight")
+ hi def link pythonSpaceError Error
+endif
+if !exists("python_no_doctest_highlight")
+ hi def link pythonDoctest Special
+ hi def link pythonDoctestValue Define
+endif
+
+let b:current_syntax = "python"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
+
+" vim:set sw=2 sts=2 ts=8 noet:
diff --git a/runtime/syntax/qf.vim b/runtime/syntax/qf.vim
index 5c987a97b6..6f2ea6a92e 100644
--- a/runtime/syntax/qf.vim
+++ b/runtime/syntax/qf.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Quickfix window
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last change: 2001 Jan 15
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/qml.vim b/runtime/syntax/qml.vim
new file mode 100644
index 0000000000..d6f2abec37
--- /dev/null
+++ b/runtime/syntax/qml.vim
@@ -0,0 +1,1130 @@
+" Vim syntax file
+" Language: QML
+" Previous Maintainer: Peter Hoeg <peter@hoeg.com>
+" Maintainer: Chase Knowlden <haroldknowlden@gmail.com>
+" Changes: `git log` is your friend
+" Last Change: 2023 Aug 16
+"
+" This file is bassed on the original work done by Warwick Allison
+" <warwick.allison@nokia.com> whose did about 99% of the work here.
+
+" Based on javascript syntax (as is QML)
+
+if exists("b:current_syntax")
+ finish
+endif
+
+if !exists("main_syntax")
+ let main_syntax = 'qml'
+endif
+
+" Drop fold if it set but vim doesn't support it.
+if !has("folding")
+ unlet! qml_fold
+endif
+
+syn case ignore
+
+syn cluster qmlExpr contains=qmlStringD,qmlStringS,qmlStringT,SqmlCharacter,qmlNumber,qmlObjectLiteralType,qmlBoolean,qmlType,qmlJsType,qmlNull,qmlGlobal,qmlFunction,qmlArrowFunction,qmlNullishCoalescing
+syn keyword qmlCommentTodo TODO FIXME XXX TBD contained
+syn match qmlLineComment "\/\/.*" contains=@Spell,qmlCommentTodo
+syn match qmlCommentSkip "^[ \t]*\*\($\|[ \t]\+\)"
+syn region qmlComment start="/\*" end="\*/" contains=@Spell,qmlCommentTodo fold
+syn match qmlSpecial "\\\d\d\d\|\\."
+syn region qmlStringD start=+"+ skip=+\\\\\|\\"\|\\$+ end=+"+ keepend contains=qmlSpecial,@htmlPreproc,@Spell
+syn region qmlStringS start=+'+ skip=+\\\\\|\\'\|\\$+ end=+'+ keepend contains=qmlSpecial,@htmlPreproc,@Spell
+syn region qmlStringT start=+`+ skip=+\\\\\|\\`\|\\$+ end=+`+ keepend contains=qmlTemplateExpr,qmlSpecial,@htmlPreproc,@Spell
+
+syntax region qmlTemplateExpr contained matchgroup=qmlBraces start=+${+ end=+}+ keepend contains=@qmlExpr
+
+syn match qmlCharacter "'\\.'"
+syn match qmlNumber "-\=\<\d\+L\=\>\|0[xX][0-9a-fA-F]\+\>"
+syn region qmlRegexpString start=+/[^/*]+me=e-1 skip=+\\\\\|\\/+ end=+/[gi]\{0,2\}\s*$+ end=+/[gi]\{0,2\}\s*[;.,)\]}]+me=e-1 contains=@htmlPreproc oneline
+syn match qmlObjectLiteralType "[A-Za-z][_A-Za-z0-9]*\s*\({\)\@="
+syn region qmlTernaryColon start="?" end=":" contains=@qmlExpr,qmlBraces,qmlParens,qmlLineComment
+syn match qmlBindingProperty "\<[A-Za-z][_A-Za-z.0-9]*\s*:"
+syn match qmlNullishCoalescing "??"
+
+syn keyword qmlConditional if else switch
+syn keyword qmlRepeat while for do in
+syn keyword qmlBranch break continue
+syn keyword qmlOperator new delete instanceof typeof
+syn keyword qmlJsType Array Boolean Date Function Number Object String RegExp
+syn keyword qmlType action alias bool color date double enumeration font int list point real rect size string time url variant vector2d vector3d vector4d coordinate geocircle geopath geopolygon georectangle geoshape matrix4x4 palette quaternion
+syn keyword qmlStatement return with
+syn keyword qmlBoolean true false
+syn keyword qmlNull null undefined
+syn keyword qmlIdentifier arguments this var let const
+syn keyword qmlLabel case default
+syn keyword qmlException try catch finally throw
+syn keyword qmlMessage alert confirm prompt status
+syn keyword qmlGlobal self
+syn keyword qmlDeclaration property signal component readonly required
+syn keyword qmlReserved abstract boolean byte char class debugger enum export extends final float goto implements import interface long native package pragma private protected public short static super synchronized throws transient volatile
+
+syn case match
+
+" List extracted in alphabatical order from: https://doc.qt.io/qt-5/qmltypes.html
+" Qt v5.15.1
+
+" Begin Literal Types {{{
+
+syntax keyword qmlObjectLiteralType Abstract3DSeries
+syntax keyword qmlObjectLiteralType AbstractActionInput
+syntax keyword qmlObjectLiteralType AbstractAnimation
+syntax keyword qmlObjectLiteralType AbstractAxis
+syntax keyword qmlObjectLiteralType AbstractAxis3D
+syntax keyword qmlObjectLiteralType AbstractAxisInput
+syntax keyword qmlObjectLiteralType AbstractBarSeries
+syntax keyword qmlObjectLiteralType AbstractButton
+syntax keyword qmlObjectLiteralType AbstractClipAnimator
+syntax keyword qmlObjectLiteralType AbstractClipBlendNode
+syntax keyword qmlObjectLiteralType AbstractDataProxy
+syntax keyword qmlObjectLiteralType AbstractGraph3D
+syntax keyword qmlObjectLiteralType AbstractInputHandler3D
+syntax keyword qmlObjectLiteralType AbstractPhysicalDevice
+syntax keyword qmlObjectLiteralType AbstractRayCaster
+syntax keyword qmlObjectLiteralType AbstractSeries
+syntax keyword qmlObjectLiteralType AbstractSkeleton
+syntax keyword qmlObjectLiteralType AbstractTexture
+syntax keyword qmlObjectLiteralType AbstractTextureImage
+syntax keyword qmlObjectLiteralType Accelerometer
+syntax keyword qmlObjectLiteralType AccelerometerReading
+syntax keyword qmlObjectLiteralType Accessible
+syntax keyword qmlObjectLiteralType Action
+syntax keyword qmlObjectLiteralType ActionGroup
+syntax keyword qmlObjectLiteralType ActionInput
+syntax keyword qmlObjectLiteralType AdditiveClipBlend
+syntax keyword qmlObjectLiteralType AdditiveColorGradient
+syntax keyword qmlObjectLiteralType Address
+syntax keyword qmlObjectLiteralType Affector
+syntax keyword qmlObjectLiteralType Age
+syntax keyword qmlObjectLiteralType AlphaCoverage
+syntax keyword qmlObjectLiteralType AlphaTest
+syntax keyword qmlObjectLiteralType Altimeter
+syntax keyword qmlObjectLiteralType AltimeterReading
+syntax keyword qmlObjectLiteralType AluminumAnodizedEmissiveMaterial
+syntax keyword qmlObjectLiteralType AluminumAnodizedMaterial
+syntax keyword qmlObjectLiteralType AluminumBrushedMaterial
+syntax keyword qmlObjectLiteralType AluminumEmissiveMaterial
+syntax keyword qmlObjectLiteralType AluminumMaterial
+syntax keyword qmlObjectLiteralType AmbientLightReading
+syntax keyword qmlObjectLiteralType AmbientLightSensor
+syntax keyword qmlObjectLiteralType AmbientTemperatureReading
+syntax keyword qmlObjectLiteralType AmbientTemperatureSensor
+syntax keyword qmlObjectLiteralType AnalogAxisInput
+syntax keyword qmlObjectLiteralType AnchorAnimation
+syntax keyword qmlObjectLiteralType AnchorChanges
+syntax keyword qmlObjectLiteralType AngleDirection
+syntax keyword qmlObjectLiteralType AnimatedImage
+syntax keyword qmlObjectLiteralType AnimatedSprite
+syntax keyword qmlObjectLiteralType Animation
+syntax keyword qmlObjectLiteralType AnimationController
+syntax keyword qmlObjectLiteralType AnimationGroup
+syntax keyword qmlObjectLiteralType Animator
+syntax keyword qmlObjectLiteralType ApplicationWindow
+syntax keyword qmlObjectLiteralType ApplicationWindowStyle
+syntax keyword qmlObjectLiteralType AreaLight
+syntax keyword qmlObjectLiteralType AreaSeries
+syntax keyword qmlObjectLiteralType Armature
+syntax keyword qmlObjectLiteralType AttenuationModelInverse
+syntax keyword qmlObjectLiteralType AttenuationModelLinear
+syntax keyword qmlObjectLiteralType Attractor
+syntax keyword qmlObjectLiteralType Attribute
+syntax keyword qmlObjectLiteralType Audio
+syntax keyword qmlObjectLiteralType AudioCategory
+syntax keyword qmlObjectLiteralType AudioEngine
+syntax keyword qmlObjectLiteralType AudioListener
+syntax keyword qmlObjectLiteralType AudioSample
+syntax keyword qmlObjectLiteralType AuthenticationDialogRequest
+syntax keyword qmlObjectLiteralType Axis
+syntax keyword qmlObjectLiteralType AxisAccumulator
+syntax keyword qmlObjectLiteralType AxisHelper
+syntax keyword qmlObjectLiteralType AxisSetting
+
+syntax keyword qmlObjectLiteralType BackspaceKey
+syntax keyword qmlObjectLiteralType Bar3DSeries
+syntax keyword qmlObjectLiteralType BarCategoryAxis
+syntax keyword qmlObjectLiteralType BarDataProxy
+syntax keyword qmlObjectLiteralType Bars3D
+syntax keyword qmlObjectLiteralType BarSeries
+syntax keyword qmlObjectLiteralType BarSet
+syntax keyword qmlObjectLiteralType BaseKey
+syntax keyword qmlObjectLiteralType BasicTableView
+syntax keyword qmlObjectLiteralType Behavior
+syntax keyword qmlObjectLiteralType Binding
+syntax keyword qmlObjectLiteralType Blend
+syntax keyword qmlObjectLiteralType BlendedClipAnimator
+syntax keyword qmlObjectLiteralType BlendEquation
+syntax keyword qmlObjectLiteralType BlendEquationArguments
+syntax keyword qmlObjectLiteralType Blending
+syntax keyword qmlObjectLiteralType BlitFramebuffer
+syntax keyword qmlObjectLiteralType BluetoothDiscoveryModel
+syntax keyword qmlObjectLiteralType BluetoothService
+syntax keyword qmlObjectLiteralType BluetoothSocket
+syntax keyword qmlObjectLiteralType Blur
+syntax keyword qmlObjectLiteralType bool
+syntax keyword qmlObjectLiteralType BorderImage
+syntax keyword qmlObjectLiteralType BorderImageMesh
+syntax keyword qmlObjectLiteralType BoundaryRule
+syntax keyword qmlObjectLiteralType Bounds
+syntax keyword qmlObjectLiteralType BoxPlotSeries
+syntax keyword qmlObjectLiteralType BoxSet
+syntax keyword qmlObjectLiteralType BrightnessContrast
+syntax keyword qmlObjectLiteralType BrushStrokes
+syntax keyword qmlObjectLiteralType Buffer
+syntax keyword qmlObjectLiteralType BufferBlit
+syntax keyword qmlObjectLiteralType BufferCapture
+syntax keyword qmlObjectLiteralType BufferInput
+syntax keyword qmlObjectLiteralType BusyIndicator
+syntax keyword qmlObjectLiteralType BusyIndicatorStyle
+syntax keyword qmlObjectLiteralType Button
+syntax keyword qmlObjectLiteralType ButtonAxisInput
+syntax keyword qmlObjectLiteralType ButtonGroup
+syntax keyword qmlObjectLiteralType ButtonStyle
+
+syntax keyword qmlObjectLiteralType Calendar
+syntax keyword qmlObjectLiteralType CalendarModel
+syntax keyword qmlObjectLiteralType CalendarStyle
+syntax keyword qmlObjectLiteralType Camera
+syntax keyword qmlObjectLiteralType Camera3D
+syntax keyword qmlObjectLiteralType CameraCapabilities
+syntax keyword qmlObjectLiteralType CameraCapture
+syntax keyword qmlObjectLiteralType CameraExposure
+syntax keyword qmlObjectLiteralType CameraFlash
+syntax keyword qmlObjectLiteralType CameraFocus
+syntax keyword qmlObjectLiteralType CameraImageProcessing
+syntax keyword qmlObjectLiteralType CameraLens
+syntax keyword qmlObjectLiteralType CameraRecorder
+syntax keyword qmlObjectLiteralType CameraSelector
+syntax keyword qmlObjectLiteralType CandlestickSeries
+syntax keyword qmlObjectLiteralType CandlestickSet
+syntax keyword qmlObjectLiteralType Canvas
+syntax keyword qmlObjectLiteralType CanvasGradient
+syntax keyword qmlObjectLiteralType CanvasImageData
+syntax keyword qmlObjectLiteralType CanvasPixelArray
+syntax keyword qmlObjectLiteralType Category
+syntax keyword qmlObjectLiteralType CategoryAxis
+syntax keyword qmlObjectLiteralType CategoryAxis3D
+syntax keyword qmlObjectLiteralType CategoryModel
+syntax keyword qmlObjectLiteralType CategoryRange
+syntax keyword qmlObjectLiteralType ChangeLanguageKey
+syntax keyword qmlObjectLiteralType ChartView
+syntax keyword qmlObjectLiteralType CheckBox
+syntax keyword qmlObjectLiteralType CheckBoxStyle
+syntax keyword qmlObjectLiteralType CheckDelegate
+syntax keyword qmlObjectLiteralType ChromaticAberration
+syntax keyword qmlObjectLiteralType CircularGauge
+syntax keyword qmlObjectLiteralType CircularGaugeStyle
+syntax keyword qmlObjectLiteralType ClearBuffers
+syntax keyword qmlObjectLiteralType ClipAnimator
+syntax keyword qmlObjectLiteralType ClipBlendValue
+syntax keyword qmlObjectLiteralType ClipPlane
+syntax keyword qmlObjectLiteralType CloseEvent
+syntax keyword qmlObjectLiteralType color
+syntax keyword qmlObjectLiteralType ColorAnimation
+syntax keyword qmlObjectLiteralType ColorDialog
+syntax keyword qmlObjectLiteralType ColorDialogRequest
+syntax keyword qmlObjectLiteralType ColorGradient
+syntax keyword qmlObjectLiteralType ColorGradientStop
+syntax keyword qmlObjectLiteralType Colorize
+syntax keyword qmlObjectLiteralType ColorMask
+syntax keyword qmlObjectLiteralType ColorMaster
+syntax keyword qmlObjectLiteralType ColorOverlay
+syntax keyword qmlObjectLiteralType Column
+syntax keyword qmlObjectLiteralType ColumnLayout
+syntax keyword qmlObjectLiteralType ComboBox
+syntax keyword qmlObjectLiteralType ComboBoxStyle
+syntax keyword qmlObjectLiteralType Command
+syntax keyword qmlObjectLiteralType Compass
+syntax keyword qmlObjectLiteralType CompassReading
+syntax keyword qmlObjectLiteralType Component
+syntax keyword qmlObjectLiteralType Component3D
+syntax keyword qmlObjectLiteralType ComputeCommand
+syntax keyword qmlObjectLiteralType ConeGeometry
+syntax keyword qmlObjectLiteralType ConeMesh
+syntax keyword qmlObjectLiteralType ConicalGradient
+syntax keyword qmlObjectLiteralType Connections
+syntax keyword qmlObjectLiteralType ContactDetail
+syntax keyword qmlObjectLiteralType ContactDetails
+syntax keyword qmlObjectLiteralType Container
+syntax keyword qmlObjectLiteralType Context2D
+syntax keyword qmlObjectLiteralType ContextMenuRequest
+syntax keyword qmlObjectLiteralType Control
+syntax keyword qmlObjectLiteralType coordinate
+syntax keyword qmlObjectLiteralType CoordinateAnimation
+syntax keyword qmlObjectLiteralType CopperMaterial
+syntax keyword qmlObjectLiteralType CuboidGeometry
+syntax keyword qmlObjectLiteralType CuboidMesh
+syntax keyword qmlObjectLiteralType CullFace
+syntax keyword qmlObjectLiteralType CullMode
+syntax keyword qmlObjectLiteralType CumulativeDirection
+syntax keyword qmlObjectLiteralType Custom3DItem
+syntax keyword qmlObjectLiteralType Custom3DLabel
+syntax keyword qmlObjectLiteralType Custom3DVolume
+syntax keyword qmlObjectLiteralType CustomCamera
+syntax keyword qmlObjectLiteralType CustomMaterial
+syntax keyword qmlObjectLiteralType CustomParticle
+syntax keyword qmlObjectLiteralType CylinderGeometry
+syntax keyword qmlObjectLiteralType CylinderMesh
+
+syntax keyword qmlObjectLiteralType Date
+syntax keyword qmlObjectLiteralType date
+syntax keyword qmlObjectLiteralType DateTimeAxis
+syntax keyword qmlObjectLiteralType DayOfWeekRow
+syntax keyword qmlObjectLiteralType DebugView
+syntax keyword qmlObjectLiteralType DefaultMaterial
+syntax keyword qmlObjectLiteralType DelayButton
+syntax keyword qmlObjectLiteralType DelayButtonStyle
+syntax keyword qmlObjectLiteralType DelegateChoice
+syntax keyword qmlObjectLiteralType DelegateChooser
+syntax keyword qmlObjectLiteralType DelegateModel
+syntax keyword qmlObjectLiteralType DelegateModelGroup
+syntax keyword qmlObjectLiteralType DepthInput
+syntax keyword qmlObjectLiteralType DepthOfFieldHQBlur
+syntax keyword qmlObjectLiteralType DepthRange
+syntax keyword qmlObjectLiteralType DepthTest
+syntax keyword qmlObjectLiteralType Desaturate
+syntax keyword qmlObjectLiteralType Dial
+syntax keyword qmlObjectLiteralType Dialog
+syntax keyword qmlObjectLiteralType DialogButtonBox
+syntax keyword qmlObjectLiteralType DialStyle
+syntax keyword qmlObjectLiteralType DiffuseMapMaterial
+syntax keyword qmlObjectLiteralType DiffuseSpecularMapMaterial
+syntax keyword qmlObjectLiteralType DiffuseSpecularMaterial
+syntax keyword qmlObjectLiteralType Direction
+syntax keyword qmlObjectLiteralType DirectionalBlur
+syntax keyword qmlObjectLiteralType DirectionalLight
+syntax keyword qmlObjectLiteralType DispatchCompute
+syntax keyword qmlObjectLiteralType Displace
+syntax keyword qmlObjectLiteralType DistanceReading
+syntax keyword qmlObjectLiteralType DistanceSensor
+syntax keyword qmlObjectLiteralType DistortionRipple
+syntax keyword qmlObjectLiteralType DistortionSphere
+syntax keyword qmlObjectLiteralType DistortionSpiral
+syntax keyword qmlObjectLiteralType Dithering
+syntax keyword qmlObjectLiteralType double
+syntax keyword qmlObjectLiteralType DoubleValidator
+syntax keyword qmlObjectLiteralType Drag
+syntax keyword qmlObjectLiteralType DragEvent
+syntax keyword qmlObjectLiteralType DragHandler
+syntax keyword qmlObjectLiteralType Drawer
+syntax keyword qmlObjectLiteralType DropArea
+syntax keyword qmlObjectLiteralType DropShadow
+syntax keyword qmlObjectLiteralType DwmFeatures
+syntax keyword qmlObjectLiteralType DynamicParameter
+
+syntax keyword qmlObjectLiteralType EdgeDetect
+syntax keyword qmlObjectLiteralType EditorialModel
+syntax keyword qmlObjectLiteralType Effect
+syntax keyword qmlObjectLiteralType EllipseShape
+syntax keyword qmlObjectLiteralType Emboss
+syntax keyword qmlObjectLiteralType Emitter
+syntax keyword qmlObjectLiteralType EnterKey
+syntax keyword qmlObjectLiteralType EnterKeyAction
+syntax keyword qmlObjectLiteralType Entity
+syntax keyword qmlObjectLiteralType EntityLoader
+syntax keyword qmlObjectLiteralType enumeration
+syntax keyword qmlObjectLiteralType EnvironmentLight
+syntax keyword qmlObjectLiteralType EventConnection
+syntax keyword qmlObjectLiteralType EventPoint
+syntax keyword qmlObjectLiteralType EventTouchPoint
+syntax keyword qmlObjectLiteralType ExclusiveGroup
+syntax keyword qmlObjectLiteralType ExtendedAttributes
+syntax keyword qmlObjectLiteralType ExtrudedTextGeometry
+syntax keyword qmlObjectLiteralType ExtrudedTextMesh
+
+syntax keyword qmlObjectLiteralType FastBlur
+syntax keyword qmlObjectLiteralType FileDialog
+syntax keyword qmlObjectLiteralType FileDialogRequest
+syntax keyword qmlObjectLiteralType FillerKey
+syntax keyword qmlObjectLiteralType FilterKey
+syntax keyword qmlObjectLiteralType FinalState
+syntax keyword qmlObjectLiteralType FindTextResult
+syntax keyword qmlObjectLiteralType FirstPersonCameraController
+syntax keyword qmlObjectLiteralType Flickable
+syntax keyword qmlObjectLiteralType Flip
+syntax keyword qmlObjectLiteralType Flipable
+syntax keyword qmlObjectLiteralType Flow
+syntax keyword qmlObjectLiteralType FocusScope
+syntax keyword qmlObjectLiteralType FolderDialog
+syntax keyword qmlObjectLiteralType FolderListModel
+syntax keyword qmlObjectLiteralType font
+syntax keyword qmlObjectLiteralType FontDialog
+syntax keyword qmlObjectLiteralType FontLoader
+syntax keyword qmlObjectLiteralType FontMetrics
+syntax keyword qmlObjectLiteralType FormValidationMessageRequest
+syntax keyword qmlObjectLiteralType ForwardRenderer
+syntax keyword qmlObjectLiteralType Frame
+syntax keyword qmlObjectLiteralType FrameAction
+syntax keyword qmlObjectLiteralType FrameGraphNode
+syntax keyword qmlObjectLiteralType Friction
+syntax keyword qmlObjectLiteralType FrontFace
+syntax keyword qmlObjectLiteralType FrostedGlassMaterial
+syntax keyword qmlObjectLiteralType FrostedGlassSinglePassMaterial
+syntax keyword qmlObjectLiteralType FrustumCamera
+syntax keyword qmlObjectLiteralType FrustumCulling
+syntax keyword qmlObjectLiteralType FullScreenRequest
+syntax keyword qmlObjectLiteralType Fxaa
+
+syntax keyword qmlObjectLiteralType Gamepad
+syntax keyword qmlObjectLiteralType GamepadManager
+syntax keyword qmlObjectLiteralType GammaAdjust
+syntax keyword qmlObjectLiteralType Gauge
+syntax keyword qmlObjectLiteralType GaugeStyle
+syntax keyword qmlObjectLiteralType GaussianBlur
+syntax keyword qmlObjectLiteralType geocircle
+syntax keyword qmlObjectLiteralType GeocodeModel
+syntax keyword qmlObjectLiteralType Geometry
+syntax keyword qmlObjectLiteralType GeometryRenderer
+syntax keyword qmlObjectLiteralType geopath
+syntax keyword qmlObjectLiteralType geopolygon
+syntax keyword qmlObjectLiteralType georectangle
+syntax keyword qmlObjectLiteralType geoshape
+syntax keyword qmlObjectLiteralType GestureEvent
+syntax keyword qmlObjectLiteralType GlassMaterial
+syntax keyword qmlObjectLiteralType GlassRefractiveMaterial
+syntax keyword qmlObjectLiteralType Glow
+syntax keyword qmlObjectLiteralType GoochMaterial
+syntax keyword qmlObjectLiteralType Gradient
+syntax keyword qmlObjectLiteralType GradientStop
+syntax keyword qmlObjectLiteralType GraphicsApiFilter
+syntax keyword qmlObjectLiteralType GraphicsInfo
+syntax keyword qmlObjectLiteralType Gravity
+syntax keyword qmlObjectLiteralType Grid
+syntax keyword qmlObjectLiteralType GridGeometry
+syntax keyword qmlObjectLiteralType GridLayout
+syntax keyword qmlObjectLiteralType GridMesh
+syntax keyword qmlObjectLiteralType GridView
+syntax keyword qmlObjectLiteralType GroupBox
+syntax keyword qmlObjectLiteralType GroupGoal
+syntax keyword qmlObjectLiteralType Gyroscope
+syntax keyword qmlObjectLiteralType GyroscopeReading
+
+syntax keyword qmlObjectLiteralType HandlerPoint
+syntax keyword qmlObjectLiteralType HandwritingInputPanel
+syntax keyword qmlObjectLiteralType HandwritingModeKey
+syntax keyword qmlObjectLiteralType HBarModelMapper
+syntax keyword qmlObjectLiteralType HBoxPlotModelMapper
+syntax keyword qmlObjectLiteralType HCandlestickModelMapper
+syntax keyword qmlObjectLiteralType HDRBloomTonemap
+syntax keyword qmlObjectLiteralType HeightMapSurfaceDataProxy
+syntax keyword qmlObjectLiteralType HideKeyboardKey
+syntax keyword qmlObjectLiteralType HistoryState
+syntax keyword qmlObjectLiteralType HolsterReading
+syntax keyword qmlObjectLiteralType HolsterSensor
+syntax keyword qmlObjectLiteralType HorizontalBarSeries
+syntax keyword qmlObjectLiteralType HorizontalHeaderView
+syntax keyword qmlObjectLiteralType HorizontalPercentBarSeries
+syntax keyword qmlObjectLiteralType HorizontalStackedBarSeries
+syntax keyword qmlObjectLiteralType Host
+syntax keyword qmlObjectLiteralType HoverHandler
+syntax keyword qmlObjectLiteralType HPieModelMapper
+syntax keyword qmlObjectLiteralType HueSaturation
+syntax keyword qmlObjectLiteralType HumidityReading
+syntax keyword qmlObjectLiteralType HumiditySensor
+syntax keyword qmlObjectLiteralType HXYModelMapper
+
+syntax keyword qmlObjectLiteralType Icon
+syntax keyword qmlObjectLiteralType IdleInhibitManagerV1
+syntax keyword qmlObjectLiteralType Image
+syntax keyword qmlObjectLiteralType ImageModel
+syntax keyword qmlObjectLiteralType ImageParticle
+syntax keyword qmlObjectLiteralType InnerShadow
+syntax keyword qmlObjectLiteralType InputChord
+syntax keyword qmlObjectLiteralType InputContext
+syntax keyword qmlObjectLiteralType InputEngine
+syntax keyword qmlObjectLiteralType InputHandler3D
+syntax keyword qmlObjectLiteralType InputMethod
+syntax keyword qmlObjectLiteralType InputModeKey
+syntax keyword qmlObjectLiteralType InputPanel
+syntax keyword qmlObjectLiteralType InputSequence
+syntax keyword qmlObjectLiteralType InputSettings
+syntax keyword qmlObjectLiteralType Instantiator
+syntax keyword qmlObjectLiteralType int
+syntax keyword qmlObjectLiteralType IntValidator
+syntax keyword qmlObjectLiteralType InvokedServices
+syntax keyword qmlObjectLiteralType IRProximityReading
+syntax keyword qmlObjectLiteralType IRProximitySensor
+syntax keyword qmlObjectLiteralType Item
+syntax keyword qmlObjectLiteralType ItemDelegate
+syntax keyword qmlObjectLiteralType ItemGrabResult
+syntax keyword qmlObjectLiteralType ItemModelBarDataProxy
+syntax keyword qmlObjectLiteralType ItemModelScatterDataProxy
+syntax keyword qmlObjectLiteralType ItemModelSurfaceDataProxy
+syntax keyword qmlObjectLiteralType ItemParticle
+syntax keyword qmlObjectLiteralType ItemSelectionModel
+syntax keyword qmlObjectLiteralType IviApplication
+syntax keyword qmlObjectLiteralType IviSurface
+
+syntax keyword qmlObjectLiteralType JavaScriptDialogRequest
+syntax keyword qmlObjectLiteralType Joint
+syntax keyword qmlObjectLiteralType JumpList
+syntax keyword qmlObjectLiteralType JumpListCategory
+syntax keyword qmlObjectLiteralType JumpListDestination
+syntax keyword qmlObjectLiteralType JumpListLink
+syntax keyword qmlObjectLiteralType JumpListSeparator
+
+syntax keyword qmlObjectLiteralType Key
+syntax keyword qmlObjectLiteralType KeyboardColumn
+syntax keyword qmlObjectLiteralType KeyboardDevice
+syntax keyword qmlObjectLiteralType KeyboardHandler
+syntax keyword qmlObjectLiteralType KeyboardLayout
+syntax keyword qmlObjectLiteralType KeyboardLayoutLoader
+syntax keyword qmlObjectLiteralType KeyboardRow
+syntax keyword qmlObjectLiteralType KeyboardStyle
+syntax keyword qmlObjectLiteralType KeyEvent
+syntax keyword qmlObjectLiteralType Keyframe
+syntax keyword qmlObjectLiteralType KeyframeAnimation
+syntax keyword qmlObjectLiteralType KeyframeGroup
+syntax keyword qmlObjectLiteralType KeyIcon
+syntax keyword qmlObjectLiteralType KeyNavigation
+syntax keyword qmlObjectLiteralType KeyPanel
+syntax keyword qmlObjectLiteralType Keys
+
+syntax keyword qmlObjectLiteralType Label
+syntax keyword qmlObjectLiteralType Layer
+syntax keyword qmlObjectLiteralType LayerFilter
+syntax keyword qmlObjectLiteralType Layout
+syntax keyword qmlObjectLiteralType LayoutMirroring
+syntax keyword qmlObjectLiteralType Legend
+syntax keyword qmlObjectLiteralType LerpClipBlend
+syntax keyword qmlObjectLiteralType LevelAdjust
+syntax keyword qmlObjectLiteralType LevelOfDetail
+syntax keyword qmlObjectLiteralType LevelOfDetailBoundingSphere
+syntax keyword qmlObjectLiteralType LevelOfDetailLoader
+syntax keyword qmlObjectLiteralType LevelOfDetailSwitch
+syntax keyword qmlObjectLiteralType LidReading
+syntax keyword qmlObjectLiteralType LidSensor
+syntax keyword qmlObjectLiteralType Light
+syntax keyword qmlObjectLiteralType Light3D
+syntax keyword qmlObjectLiteralType LightReading
+syntax keyword qmlObjectLiteralType LightSensor
+syntax keyword qmlObjectLiteralType LinearGradient
+syntax keyword qmlObjectLiteralType LineSeries
+syntax keyword qmlObjectLiteralType LineShape
+syntax keyword qmlObjectLiteralType LineWidth
+syntax keyword qmlObjectLiteralType list
+syntax keyword qmlObjectLiteralType ListElement
+syntax keyword qmlObjectLiteralType ListModel
+syntax keyword qmlObjectLiteralType ListView
+syntax keyword qmlObjectLiteralType Loader
+syntax keyword qmlObjectLiteralType Loader3D
+syntax keyword qmlObjectLiteralType Locale
+syntax keyword qmlObjectLiteralType Location
+syntax keyword qmlObjectLiteralType LoggingCategory
+syntax keyword qmlObjectLiteralType LogicalDevice
+syntax keyword qmlObjectLiteralType LogValueAxis
+syntax keyword qmlObjectLiteralType LogValueAxis3DFormatter
+syntax keyword qmlObjectLiteralType LottieAnimation
+
+syntax keyword qmlObjectLiteralType Magnetometer
+syntax keyword qmlObjectLiteralType MagnetometerReading
+syntax keyword qmlObjectLiteralType Map
+syntax keyword qmlObjectLiteralType MapCircle
+syntax keyword qmlObjectLiteralType MapCircleObject
+syntax keyword qmlObjectLiteralType MapCopyrightNotice
+syntax keyword qmlObjectLiteralType MapGestureArea
+syntax keyword qmlObjectLiteralType MapIconObject
+syntax keyword qmlObjectLiteralType MapItemGroup
+syntax keyword qmlObjectLiteralType MapItemView
+syntax keyword qmlObjectLiteralType MapObjectView
+syntax keyword qmlObjectLiteralType MapParameter
+syntax keyword qmlObjectLiteralType MapPinchEvent
+syntax keyword qmlObjectLiteralType MapPolygon
+syntax keyword qmlObjectLiteralType MapPolygonObject
+syntax keyword qmlObjectLiteralType MapPolyline
+syntax keyword qmlObjectLiteralType MapPolylineObject
+syntax keyword qmlObjectLiteralType MapQuickItem
+syntax keyword qmlObjectLiteralType MapRectangle
+syntax keyword qmlObjectLiteralType MapRoute
+syntax keyword qmlObjectLiteralType MapRouteObject
+syntax keyword qmlObjectLiteralType MapType
+syntax keyword qmlObjectLiteralType Margins
+syntax keyword qmlObjectLiteralType MaskedBlur
+syntax keyword qmlObjectLiteralType MaskShape
+syntax keyword qmlObjectLiteralType Material
+syntax keyword qmlObjectLiteralType Matrix4x4
+syntax keyword qmlObjectLiteralType matrix4x4
+syntax keyword qmlObjectLiteralType MediaPlayer
+syntax keyword qmlObjectLiteralType mediaplayer-qml-dynamic
+syntax keyword qmlObjectLiteralType MemoryBarrier
+syntax keyword qmlObjectLiteralType Menu
+syntax keyword qmlObjectLiteralType MenuBar
+syntax keyword qmlObjectLiteralType MenuBarItem
+syntax keyword qmlObjectLiteralType MenuBarStyle
+syntax keyword qmlObjectLiteralType MenuItem
+syntax keyword qmlObjectLiteralType MenuItemGroup
+syntax keyword qmlObjectLiteralType MenuSeparator
+syntax keyword qmlObjectLiteralType MenuStyle
+syntax keyword qmlObjectLiteralType Mesh
+syntax keyword qmlObjectLiteralType MessageDialog
+syntax keyword qmlObjectLiteralType MetalRoughMaterial
+syntax keyword qmlObjectLiteralType ModeKey
+syntax keyword qmlObjectLiteralType Model
+syntax keyword qmlObjectLiteralType MonthGrid
+syntax keyword qmlObjectLiteralType MorphingAnimation
+syntax keyword qmlObjectLiteralType MorphTarget
+syntax keyword qmlObjectLiteralType MotionBlur
+syntax keyword qmlObjectLiteralType MouseArea
+syntax keyword qmlObjectLiteralType MouseDevice
+syntax keyword qmlObjectLiteralType MouseEvent
+syntax keyword qmlObjectLiteralType MouseHandler
+syntax keyword qmlObjectLiteralType MultiPointHandler
+syntax keyword qmlObjectLiteralType MultiPointTouchArea
+syntax keyword qmlObjectLiteralType MultiSampleAntiAliasing
+
+syntax keyword qmlObjectLiteralType Navigator
+syntax keyword qmlObjectLiteralType NdefFilter
+syntax keyword qmlObjectLiteralType NdefMimeRecord
+syntax keyword qmlObjectLiteralType NdefRecord
+syntax keyword qmlObjectLiteralType NdefTextRecord
+syntax keyword qmlObjectLiteralType NdefUriRecord
+syntax keyword qmlObjectLiteralType NearField
+syntax keyword qmlObjectLiteralType Node
+syntax keyword qmlObjectLiteralType NodeInstantiator
+syntax keyword qmlObjectLiteralType NoDepthMask
+syntax keyword qmlObjectLiteralType NoDraw
+syntax keyword qmlObjectLiteralType NoPicking
+syntax keyword qmlObjectLiteralType NormalDiffuseMapAlphaMaterial
+syntax keyword qmlObjectLiteralType NormalDiffuseMapMaterial
+syntax keyword qmlObjectLiteralType NormalDiffuseSpecularMapMaterial
+syntax keyword qmlObjectLiteralType Number
+syntax keyword qmlObjectLiteralType NumberAnimation
+syntax keyword qmlObjectLiteralType NumberKey
+
+syntax keyword qmlObjectLiteralType Object3D
+syntax keyword qmlObjectLiteralType ObjectModel
+syntax keyword qmlObjectLiteralType ObjectPicker
+syntax keyword qmlObjectLiteralType OpacityAnimator
+syntax keyword qmlObjectLiteralType OpacityMask
+syntax keyword qmlObjectLiteralType OpenGLInfo
+syntax keyword qmlObjectLiteralType OrbitCameraController
+syntax keyword qmlObjectLiteralType OrientationReading
+syntax keyword qmlObjectLiteralType OrientationSensor
+syntax keyword qmlObjectLiteralType OrthographicCamera
+syntax keyword qmlObjectLiteralType Overlay
+
+syntax keyword qmlObjectLiteralType Package
+syntax keyword qmlObjectLiteralType Page
+syntax keyword qmlObjectLiteralType PageIndicator
+syntax keyword qmlObjectLiteralType palette
+syntax keyword qmlObjectLiteralType Pane
+syntax keyword qmlObjectLiteralType PaperArtisticMaterial
+syntax keyword qmlObjectLiteralType PaperOfficeMaterial
+syntax keyword qmlObjectLiteralType ParallelAnimation
+syntax keyword qmlObjectLiteralType Parameter
+syntax keyword qmlObjectLiteralType ParentAnimation
+syntax keyword qmlObjectLiteralType ParentChange
+syntax keyword qmlObjectLiteralType Particle
+syntax keyword qmlObjectLiteralType ParticleExtruder
+syntax keyword qmlObjectLiteralType ParticleGroup
+syntax keyword qmlObjectLiteralType ParticlePainter
+syntax keyword qmlObjectLiteralType ParticleSystem
+syntax keyword qmlObjectLiteralType Pass
+syntax keyword qmlObjectLiteralType Path
+syntax keyword qmlObjectLiteralType PathAngleArc
+syntax keyword qmlObjectLiteralType PathAnimation
+syntax keyword qmlObjectLiteralType PathArc
+syntax keyword qmlObjectLiteralType PathAttribute
+syntax keyword qmlObjectLiteralType PathCubic
+syntax keyword qmlObjectLiteralType PathCurve
+syntax keyword qmlObjectLiteralType PathElement
+syntax keyword qmlObjectLiteralType PathInterpolator
+syntax keyword qmlObjectLiteralType PathLine
+syntax keyword qmlObjectLiteralType PathMove
+syntax keyword qmlObjectLiteralType PathMultiline
+syntax keyword qmlObjectLiteralType PathPercent
+syntax keyword qmlObjectLiteralType PathPolyline
+syntax keyword qmlObjectLiteralType PathQuad
+syntax keyword qmlObjectLiteralType PathSvg
+syntax keyword qmlObjectLiteralType PathText
+syntax keyword qmlObjectLiteralType PathView
+syntax keyword qmlObjectLiteralType PauseAnimation
+syntax keyword qmlObjectLiteralType PdfDocument
+syntax keyword qmlObjectLiteralType PdfLinkModel
+syntax keyword qmlObjectLiteralType PdfNavigationStack
+syntax keyword qmlObjectLiteralType PdfSearchModel
+syntax keyword qmlObjectLiteralType PdfSelection
+syntax keyword qmlObjectLiteralType PercentBarSeries
+syntax keyword qmlObjectLiteralType PerspectiveCamera
+syntax keyword qmlObjectLiteralType PerVertexColorMaterial
+syntax keyword qmlObjectLiteralType PhongAlphaMaterial
+syntax keyword qmlObjectLiteralType PhongMaterial
+syntax keyword qmlObjectLiteralType PickEvent
+syntax keyword qmlObjectLiteralType PickingSettings
+syntax keyword qmlObjectLiteralType PickLineEvent
+syntax keyword qmlObjectLiteralType PickPointEvent
+syntax keyword qmlObjectLiteralType PickResult
+syntax keyword qmlObjectLiteralType PickTriangleEvent
+syntax keyword qmlObjectLiteralType Picture
+syntax keyword qmlObjectLiteralType PieMenu
+syntax keyword qmlObjectLiteralType PieMenuStyle
+syntax keyword qmlObjectLiteralType PieSeries
+syntax keyword qmlObjectLiteralType PieSlice
+syntax keyword qmlObjectLiteralType PinchArea
+syntax keyword qmlObjectLiteralType PinchEvent
+syntax keyword qmlObjectLiteralType PinchHandler
+syntax keyword qmlObjectLiteralType Place
+syntax keyword qmlObjectLiteralType PlaceAttribute
+syntax keyword qmlObjectLiteralType PlaceSearchModel
+syntax keyword qmlObjectLiteralType PlaceSearchSuggestionModel
+syntax keyword qmlObjectLiteralType PlaneGeometry
+syntax keyword qmlObjectLiteralType PlaneMesh
+syntax keyword qmlObjectLiteralType PlasticStructuredRedEmissiveMaterial
+syntax keyword qmlObjectLiteralType PlasticStructuredRedMaterial
+syntax keyword qmlObjectLiteralType Playlist
+syntax keyword qmlObjectLiteralType PlaylistItem
+syntax keyword qmlObjectLiteralType PlayVariation
+syntax keyword qmlObjectLiteralType Plugin
+syntax keyword qmlObjectLiteralType PluginParameter
+syntax keyword qmlObjectLiteralType point
+syntax keyword qmlObjectLiteralType PointDirection
+syntax keyword qmlObjectLiteralType PointerDevice
+syntax keyword qmlObjectLiteralType PointerDeviceHandler
+syntax keyword qmlObjectLiteralType PointerEvent
+syntax keyword qmlObjectLiteralType PointerHandler
+syntax keyword qmlObjectLiteralType PointerScrollEvent
+syntax keyword qmlObjectLiteralType PointHandler
+syntax keyword qmlObjectLiteralType PointLight
+syntax keyword qmlObjectLiteralType PointSize
+syntax keyword qmlObjectLiteralType PolarChartView
+syntax keyword qmlObjectLiteralType PolygonOffset
+syntax keyword qmlObjectLiteralType Popup
+syntax keyword qmlObjectLiteralType Position
+syntax keyword qmlObjectLiteralType Positioner
+syntax keyword qmlObjectLiteralType PositionSource
+syntax keyword qmlObjectLiteralType PressureReading
+syntax keyword qmlObjectLiteralType PressureSensor
+syntax keyword qmlObjectLiteralType PrincipledMaterial
+syntax keyword qmlObjectLiteralType Product
+syntax keyword qmlObjectLiteralType ProgressBar
+syntax keyword qmlObjectLiteralType ProgressBarStyle
+syntax keyword qmlObjectLiteralType PropertyAction
+syntax keyword qmlObjectLiteralType PropertyAnimation
+syntax keyword qmlObjectLiteralType PropertyChanges
+syntax keyword qmlObjectLiteralType ProximityFilter
+syntax keyword qmlObjectLiteralType ProximityReading
+syntax keyword qmlObjectLiteralType ProximitySensor
+
+syntax keyword qmlObjectLiteralType QAbstractState
+syntax keyword qmlObjectLiteralType QAbstractTransition
+syntax keyword qmlObjectLiteralType QmlSensors
+syntax keyword qmlObjectLiteralType QSignalTransition
+syntax keyword qmlObjectLiteralType Qt
+syntax keyword qmlObjectLiteralType QtMultimedia
+syntax keyword qmlObjectLiteralType QtObject
+syntax keyword qmlObjectLiteralType QtPositioning
+syntax keyword qmlObjectLiteralType QtRemoteObjects
+syntax keyword qmlObjectLiteralType quaternion
+syntax keyword qmlObjectLiteralType QuaternionAnimation
+syntax keyword qmlObjectLiteralType QuotaRequest
+
+syntax keyword qmlObjectLiteralType RadialBlur
+syntax keyword qmlObjectLiteralType RadialGradient
+syntax keyword qmlObjectLiteralType Radio
+syntax keyword qmlObjectLiteralType RadioButton
+syntax keyword qmlObjectLiteralType RadioButtonStyle
+syntax keyword qmlObjectLiteralType RadioData
+syntax keyword qmlObjectLiteralType RadioDelegate
+syntax keyword qmlObjectLiteralType RangeSlider
+syntax keyword qmlObjectLiteralType RasterMode
+syntax keyword qmlObjectLiteralType Ratings
+syntax keyword qmlObjectLiteralType RayCaster
+syntax keyword qmlObjectLiteralType real
+syntax keyword qmlObjectLiteralType rect
+syntax keyword qmlObjectLiteralType Rectangle
+syntax keyword qmlObjectLiteralType RectangleShape
+syntax keyword qmlObjectLiteralType RectangularGlow
+syntax keyword qmlObjectLiteralType RecursiveBlur
+syntax keyword qmlObjectLiteralType RegExpValidator
+syntax keyword qmlObjectLiteralType RegisterProtocolHandlerRequest
+syntax keyword qmlObjectLiteralType RegularExpressionValidator
+syntax keyword qmlObjectLiteralType RenderCapabilities
+syntax keyword qmlObjectLiteralType RenderCapture
+syntax keyword qmlObjectLiteralType RenderCaptureReply
+syntax keyword qmlObjectLiteralType RenderPass
+syntax keyword qmlObjectLiteralType RenderPassFilter
+syntax keyword qmlObjectLiteralType RenderSettings
+syntax keyword qmlObjectLiteralType RenderState
+syntax keyword qmlObjectLiteralType RenderStateSet
+syntax keyword qmlObjectLiteralType RenderStats
+syntax keyword qmlObjectLiteralType RenderSurfaceSelector
+syntax keyword qmlObjectLiteralType RenderTarget
+syntax keyword qmlObjectLiteralType RenderTargetOutput
+syntax keyword qmlObjectLiteralType RenderTargetSelector
+syntax keyword qmlObjectLiteralType Repeater
+syntax keyword qmlObjectLiteralType Repeater3D
+syntax keyword qmlObjectLiteralType ReviewModel
+syntax keyword qmlObjectLiteralType Rotation
+syntax keyword qmlObjectLiteralType RotationAnimation
+syntax keyword qmlObjectLiteralType RotationAnimator
+syntax keyword qmlObjectLiteralType RotationReading
+syntax keyword qmlObjectLiteralType RotationSensor
+syntax keyword qmlObjectLiteralType RoundButton
+syntax keyword qmlObjectLiteralType Route
+syntax keyword qmlObjectLiteralType RouteLeg
+syntax keyword qmlObjectLiteralType RouteManeuver
+syntax keyword qmlObjectLiteralType RouteModel
+syntax keyword qmlObjectLiteralType RouteQuery
+syntax keyword qmlObjectLiteralType RouteSegment
+syntax keyword qmlObjectLiteralType Row
+syntax keyword qmlObjectLiteralType RowLayout
+
+syntax keyword qmlObjectLiteralType Scale
+syntax keyword qmlObjectLiteralType ScaleAnimator
+syntax keyword qmlObjectLiteralType Scatter
+syntax keyword qmlObjectLiteralType Scatter3D
+syntax keyword qmlObjectLiteralType Scatter3DSeries
+syntax keyword qmlObjectLiteralType ScatterDataProxy
+syntax keyword qmlObjectLiteralType ScatterSeries
+syntax keyword qmlObjectLiteralType Scene2D
+syntax keyword qmlObjectLiteralType Scene3D
+syntax keyword qmlObjectLiteralType Scene3DView
+syntax keyword qmlObjectLiteralType SceneEnvironment
+syntax keyword qmlObjectLiteralType SceneLoader
+syntax keyword qmlObjectLiteralType ScissorTest
+syntax keyword qmlObjectLiteralType Screen
+syntax keyword qmlObjectLiteralType ScreenRayCaster
+syntax keyword qmlObjectLiteralType ScriptAction
+syntax keyword qmlObjectLiteralType ScrollBar
+syntax keyword qmlObjectLiteralType ScrollIndicator
+syntax keyword qmlObjectLiteralType ScrollView
+syntax keyword qmlObjectLiteralType ScrollViewStyle
+syntax keyword qmlObjectLiteralType SCurveTonemap
+syntax keyword qmlObjectLiteralType ScxmlStateMachine
+syntax keyword qmlObjectLiteralType SeamlessCubemap
+syntax keyword qmlObjectLiteralType SelectionListItem
+syntax keyword qmlObjectLiteralType SelectionListModel
+syntax keyword qmlObjectLiteralType Sensor
+syntax keyword qmlObjectLiteralType SensorGesture
+syntax keyword qmlObjectLiteralType SensorReading
+syntax keyword qmlObjectLiteralType SequentialAnimation
+syntax keyword qmlObjectLiteralType Settings
+syntax keyword qmlObjectLiteralType SettingsStore
+syntax keyword qmlObjectLiteralType SetUniformValue
+syntax keyword qmlObjectLiteralType Shader
+syntax keyword qmlObjectLiteralType ShaderEffect
+syntax keyword qmlObjectLiteralType ShaderEffectSource
+syntax keyword qmlObjectLiteralType ShaderImage
+syntax keyword qmlObjectLiteralType ShaderInfo
+syntax keyword qmlObjectLiteralType ShaderProgram
+syntax keyword qmlObjectLiteralType ShaderProgramBuilder
+syntax keyword qmlObjectLiteralType Shape
+syntax keyword qmlObjectLiteralType ShapeGradient
+syntax keyword qmlObjectLiteralType ShapePath
+syntax keyword qmlObjectLiteralType SharedGLTexture
+syntax keyword qmlObjectLiteralType ShellSurface
+syntax keyword qmlObjectLiteralType ShellSurfaceItem
+syntax keyword qmlObjectLiteralType ShiftHandler
+syntax keyword qmlObjectLiteralType ShiftKey
+syntax keyword qmlObjectLiteralType Shortcut
+syntax keyword qmlObjectLiteralType SignalSpy
+syntax keyword qmlObjectLiteralType SignalTransition
+syntax keyword qmlObjectLiteralType SinglePointHandler
+syntax keyword qmlObjectLiteralType size
+syntax keyword qmlObjectLiteralType Skeleton
+syntax keyword qmlObjectLiteralType SkeletonLoader
+syntax keyword qmlObjectLiteralType SkyboxEntity
+syntax keyword qmlObjectLiteralType Slider
+syntax keyword qmlObjectLiteralType SliderStyle
+syntax keyword qmlObjectLiteralType SmoothedAnimation
+syntax keyword qmlObjectLiteralType SortPolicy
+syntax keyword qmlObjectLiteralType Sound
+syntax keyword qmlObjectLiteralType SoundEffect
+syntax keyword qmlObjectLiteralType SoundInstance
+syntax keyword qmlObjectLiteralType SpaceKey
+syntax keyword qmlObjectLiteralType SphereGeometry
+syntax keyword qmlObjectLiteralType SphereMesh
+syntax keyword qmlObjectLiteralType SpinBox
+syntax keyword qmlObjectLiteralType SpinBoxStyle
+syntax keyword qmlObjectLiteralType SplineSeries
+syntax keyword qmlObjectLiteralType SplitHandle
+syntax keyword qmlObjectLiteralType SplitView
+syntax keyword qmlObjectLiteralType SpotLight
+syntax keyword qmlObjectLiteralType SpringAnimation
+syntax keyword qmlObjectLiteralType Sprite
+syntax keyword qmlObjectLiteralType SpriteGoal
+syntax keyword qmlObjectLiteralType SpriteSequence
+syntax keyword qmlObjectLiteralType Stack
+syntax keyword qmlObjectLiteralType StackedBarSeries
+syntax keyword qmlObjectLiteralType StackLayout
+syntax keyword qmlObjectLiteralType StackView
+syntax keyword qmlObjectLiteralType StackViewDelegate
+syntax keyword qmlObjectLiteralType StandardPaths
+syntax keyword qmlObjectLiteralType State
+syntax keyword qmlObjectLiteralType StateChangeScript
+syntax keyword qmlObjectLiteralType StateGroup
+syntax keyword qmlObjectLiteralType StateMachine
+syntax keyword qmlObjectLiteralType StateMachineLoader
+syntax keyword qmlObjectLiteralType StatusBar
+syntax keyword qmlObjectLiteralType StatusBarStyle
+syntax keyword qmlObjectLiteralType StatusIndicator
+syntax keyword qmlObjectLiteralType StatusIndicatorStyle
+syntax keyword qmlObjectLiteralType SteelMilledConcentricMaterial
+syntax keyword qmlObjectLiteralType StencilMask
+syntax keyword qmlObjectLiteralType StencilOperation
+syntax keyword qmlObjectLiteralType StencilOperationArguments
+syntax keyword qmlObjectLiteralType StencilTest
+syntax keyword qmlObjectLiteralType StencilTestArguments
+syntax keyword qmlObjectLiteralType Store
+syntax keyword qmlObjectLiteralType String
+syntax keyword qmlObjectLiteralType string
+syntax keyword qmlObjectLiteralType SubtreeEnabler
+syntax keyword qmlObjectLiteralType Supplier
+syntax keyword qmlObjectLiteralType Surface3D
+syntax keyword qmlObjectLiteralType Surface3DSeries
+syntax keyword qmlObjectLiteralType SurfaceDataProxy
+syntax keyword qmlObjectLiteralType SwipeDelegate
+syntax keyword qmlObjectLiteralType SwipeView
+syntax keyword qmlObjectLiteralType Switch
+syntax keyword qmlObjectLiteralType SwitchDelegate
+syntax keyword qmlObjectLiteralType SwitchStyle
+syntax keyword qmlObjectLiteralType SymbolModeKey
+syntax keyword qmlObjectLiteralType SystemPalette
+syntax keyword qmlObjectLiteralType SystemTrayIcon
+
+syntax keyword qmlObjectLiteralType Tab
+syntax keyword qmlObjectLiteralType TabBar
+syntax keyword qmlObjectLiteralType TabButton
+syntax keyword qmlObjectLiteralType TableModel
+syntax keyword qmlObjectLiteralType TableModelColumn
+syntax keyword qmlObjectLiteralType TableView
+syntax keyword qmlObjectLiteralType TableViewColumn
+syntax keyword qmlObjectLiteralType TableViewStyle
+syntax keyword qmlObjectLiteralType TabView
+syntax keyword qmlObjectLiteralType TabViewStyle
+syntax keyword qmlObjectLiteralType TapHandler
+syntax keyword qmlObjectLiteralType TapReading
+syntax keyword qmlObjectLiteralType TapSensor
+syntax keyword qmlObjectLiteralType TargetDirection
+syntax keyword qmlObjectLiteralType TaskbarButton
+syntax keyword qmlObjectLiteralType Technique
+syntax keyword qmlObjectLiteralType TechniqueFilter
+syntax keyword qmlObjectLiteralType TestCase
+syntax keyword qmlObjectLiteralType Text
+syntax keyword qmlObjectLiteralType Text2DEntity
+syntax keyword qmlObjectLiteralType TextArea
+syntax keyword qmlObjectLiteralType TextAreaStyle
+syntax keyword qmlObjectLiteralType TextEdit
+syntax keyword qmlObjectLiteralType TextField
+syntax keyword qmlObjectLiteralType TextFieldStyle
+syntax keyword qmlObjectLiteralType TextInput
+syntax keyword qmlObjectLiteralType TextMetrics
+syntax keyword qmlObjectLiteralType Texture
+syntax keyword qmlObjectLiteralType Texture1D
+syntax keyword qmlObjectLiteralType Texture1DArray
+syntax keyword qmlObjectLiteralType Texture2D
+syntax keyword qmlObjectLiteralType Texture2DArray
+syntax keyword qmlObjectLiteralType Texture2DMultisample
+syntax keyword qmlObjectLiteralType Texture2DMultisampleArray
+syntax keyword qmlObjectLiteralType Texture3D
+syntax keyword qmlObjectLiteralType TextureBuffer
+syntax keyword qmlObjectLiteralType TextureCubeMap
+syntax keyword qmlObjectLiteralType TextureCubeMapArray
+syntax keyword qmlObjectLiteralType TextureImage
+syntax keyword qmlObjectLiteralType TextureInput
+syntax keyword qmlObjectLiteralType TextureLoader
+syntax keyword qmlObjectLiteralType TextureRectangle
+syntax keyword qmlObjectLiteralType Theme3D
+syntax keyword qmlObjectLiteralType ThemeColor
+syntax keyword qmlObjectLiteralType ThresholdMask
+syntax keyword qmlObjectLiteralType ThumbnailToolBar
+syntax keyword qmlObjectLiteralType ThumbnailToolButton
+syntax keyword qmlObjectLiteralType TiltReading
+syntax keyword qmlObjectLiteralType TiltSensor
+syntax keyword qmlObjectLiteralType TiltShift
+syntax keyword qmlObjectLiteralType Timeline
+syntax keyword qmlObjectLiteralType TimelineAnimation
+syntax keyword qmlObjectLiteralType TimeoutTransition
+syntax keyword qmlObjectLiteralType Timer
+syntax keyword qmlObjectLiteralType ToggleButton
+syntax keyword qmlObjectLiteralType ToggleButtonStyle
+syntax keyword qmlObjectLiteralType ToolBar
+syntax keyword qmlObjectLiteralType ToolBarStyle
+syntax keyword qmlObjectLiteralType ToolButton
+syntax keyword qmlObjectLiteralType ToolSeparator
+syntax keyword qmlObjectLiteralType ToolTip
+syntax keyword qmlObjectLiteralType TooltipRequest
+syntax keyword qmlObjectLiteralType Torch
+syntax keyword qmlObjectLiteralType TorusGeometry
+syntax keyword qmlObjectLiteralType TorusMesh
+syntax keyword qmlObjectLiteralType TouchEventSequence
+syntax keyword qmlObjectLiteralType TouchInputHandler3D
+syntax keyword qmlObjectLiteralType TouchPoint
+syntax keyword qmlObjectLiteralType Trace
+syntax keyword qmlObjectLiteralType TraceCanvas
+syntax keyword qmlObjectLiteralType TraceInputArea
+syntax keyword qmlObjectLiteralType TraceInputKey
+syntax keyword qmlObjectLiteralType TraceInputKeyPanel
+syntax keyword qmlObjectLiteralType TrailEmitter
+syntax keyword qmlObjectLiteralType Transaction
+syntax keyword qmlObjectLiteralType Transform
+syntax keyword qmlObjectLiteralType Transition
+syntax keyword qmlObjectLiteralType Translate
+syntax keyword qmlObjectLiteralType TreeView
+syntax keyword qmlObjectLiteralType TreeViewStyle
+syntax keyword qmlObjectLiteralType Tumbler
+syntax keyword qmlObjectLiteralType TumblerColumn
+syntax keyword qmlObjectLiteralType TumblerStyle
+syntax keyword qmlObjectLiteralType Turbulence
+
+syntax keyword qmlObjectLiteralType UniformAnimator
+syntax keyword qmlObjectLiteralType url
+syntax keyword qmlObjectLiteralType User
+
+syntax keyword qmlObjectLiteralType ValueAxis
+syntax keyword qmlObjectLiteralType ValueAxis3D
+syntax keyword qmlObjectLiteralType ValueAxis3DFormatter
+syntax keyword qmlObjectLiteralType var
+syntax keyword qmlObjectLiteralType variant
+syntax keyword qmlObjectLiteralType VBarModelMapper
+syntax keyword qmlObjectLiteralType VBoxPlotModelMapper
+syntax keyword qmlObjectLiteralType VCandlestickModelMapper
+syntax keyword qmlObjectLiteralType vector2d
+syntax keyword qmlObjectLiteralType vector3d
+syntax keyword qmlObjectLiteralType Vector3dAnimation
+syntax keyword qmlObjectLiteralType vector4d
+syntax keyword qmlObjectLiteralType VertexBlendAnimation
+syntax keyword qmlObjectLiteralType VerticalHeaderView
+syntax keyword qmlObjectLiteralType Video
+syntax keyword qmlObjectLiteralType VideoOutput
+syntax keyword qmlObjectLiteralType View3D
+syntax keyword qmlObjectLiteralType Viewport
+syntax keyword qmlObjectLiteralType ViewTransition
+syntax keyword qmlObjectLiteralType Vignette
+syntax keyword qmlObjectLiteralType VirtualKeyboardSettings
+syntax keyword qmlObjectLiteralType VPieModelMapper
+syntax keyword qmlObjectLiteralType VXYModelMapper
+
+syntax keyword qmlObjectLiteralType Wander
+syntax keyword qmlObjectLiteralType WasdController
+syntax keyword qmlObjectLiteralType WavefrontMesh
+syntax keyword qmlObjectLiteralType WaylandClient
+syntax keyword qmlObjectLiteralType WaylandCompositor
+syntax keyword qmlObjectLiteralType WaylandHardwareLayer
+syntax keyword qmlObjectLiteralType WaylandOutput
+syntax keyword qmlObjectLiteralType WaylandQuickItem
+syntax keyword qmlObjectLiteralType WaylandSeat
+syntax keyword qmlObjectLiteralType WaylandSurface
+syntax keyword qmlObjectLiteralType WaylandView
+syntax keyword qmlObjectLiteralType Waypoint
+syntax keyword qmlObjectLiteralType WebChannel
+syntax keyword qmlObjectLiteralType WebEngine
+syntax keyword qmlObjectLiteralType WebEngineAction
+syntax keyword qmlObjectLiteralType WebEngineCertificateError
+syntax keyword qmlObjectLiteralType WebEngineClientCertificateOption
+syntax keyword qmlObjectLiteralType WebEngineClientCertificateSelection
+syntax keyword qmlObjectLiteralType WebEngineDownloadItem
+syntax keyword qmlObjectLiteralType WebEngineHistory
+syntax keyword qmlObjectLiteralType WebEngineHistoryListModel
+syntax keyword qmlObjectLiteralType WebEngineLoadRequest
+syntax keyword qmlObjectLiteralType WebEngineNavigationRequest
+syntax keyword qmlObjectLiteralType WebEngineNewViewRequest
+syntax keyword qmlObjectLiteralType WebEngineNotification
+syntax keyword qmlObjectLiteralType WebEngineProfile
+syntax keyword qmlObjectLiteralType WebEngineScript
+syntax keyword qmlObjectLiteralType WebEngineSettings
+syntax keyword qmlObjectLiteralType WebEngineView
+syntax keyword qmlObjectLiteralType WebSocket
+syntax keyword qmlObjectLiteralType WebSocketServer
+syntax keyword qmlObjectLiteralType WebView
+syntax keyword qmlObjectLiteralType WebViewLoadRequest
+syntax keyword qmlObjectLiteralType WeekNumberColumn
+syntax keyword qmlObjectLiteralType WheelEvent
+syntax keyword qmlObjectLiteralType WheelHandler
+syntax keyword qmlObjectLiteralType Window
+syntax keyword qmlObjectLiteralType WlScaler
+syntax keyword qmlObjectLiteralType WlShell
+syntax keyword qmlObjectLiteralType WlShellSurface
+syntax keyword qmlObjectLiteralType WorkerScript
+
+syntax keyword qmlObjectLiteralType XAnimator
+syntax keyword qmlObjectLiteralType XdgDecorationManagerV1
+syntax keyword qmlObjectLiteralType XdgOutputManagerV1
+syntax keyword qmlObjectLiteralType XdgPopup
+syntax keyword qmlObjectLiteralType XdgPopupV5
+syntax keyword qmlObjectLiteralType XdgPopupV6
+syntax keyword qmlObjectLiteralType XdgShell
+syntax keyword qmlObjectLiteralType XdgShellV5
+syntax keyword qmlObjectLiteralType XdgShellV6
+syntax keyword qmlObjectLiteralType XdgSurface
+syntax keyword qmlObjectLiteralType XdgSurfaceV5
+syntax keyword qmlObjectLiteralType XdgSurfaceV6
+syntax keyword qmlObjectLiteralType XdgToplevel
+syntax keyword qmlObjectLiteralType XdgToplevelV6
+syntax keyword qmlObjectLiteralType XmlListModel
+syntax keyword qmlObjectLiteralType XmlRole
+syntax keyword qmlObjectLiteralType XYPoint
+syntax keyword qmlObjectLiteralType XYSeries
+
+syntax keyword qmlObjectLiteralType YAnimator
+
+syntax keyword qmlObjectLiteralType ZoomBlur
+
+" }}}
+
+if get(g:, 'qml_fold', 0)
+ syn match qmlFunction "\<function\>"
+ syn region qmlFunctionFold start="^\z(\s*\)\<function\>.*[^};]$" end="^\z1}.*$" transparent fold keepend
+
+ syn sync match qmlSync grouphere qmlFunctionFold "\<function\>"
+ syn sync match qmlSync grouphere NONE "^}"
+
+ setlocal foldmethod=syntax
+ setlocal foldtext=getline(v:foldstart)
+else
+ syn keyword qmlFunction function
+ syn match qmlArrowFunction "=>"
+ syn match qmlBraces "[{}\[\]]"
+ syn match qmlParens "[()]"
+endif
+
+syn sync fromstart
+syn sync maxlines=100
+
+if main_syntax == "qml"
+ syn sync ccomment qmlComment
+endif
+
+hi def link qmlComment Comment
+hi def link qmlLineComment Comment
+hi def link qmlCommentTodo Todo
+hi def link qmlSpecial Special
+hi def link qmlStringS String
+hi def link qmlStringD String
+hi def link qmlStringT String
+hi def link qmlCharacter Character
+hi def link qmlNumber Number
+hi def link qmlConditional Conditional
+hi def link qmlRepeat Repeat
+hi def link qmlBranch Conditional
+hi def link qmlOperator Operator
+hi def link qmlJsType Type
+hi def link qmlType Type
+hi def link qmlObjectLiteralType Type
+hi def link qmlStatement Statement
+hi def link qmlFunction Function
+hi def link qmlArrowFunction Function
+hi def link qmlBraces Function
+hi def link qmlError Error
+hi def link qmlNull Keyword
+hi def link qmlBoolean Boolean
+hi def link qmlRegexpString String
+hi def link qmlNullishCoalescing Operator
+
+hi def link qmlIdentifier Identifier
+hi def link qmlLabel Label
+hi def link qmlException Exception
+hi def link qmlMessage Keyword
+hi def link qmlGlobal Keyword
+hi def link qmlReserved Keyword
+hi def link qmlDebug Debug
+hi def link qmlConstant Label
+hi def link qmlBindingProperty Label
+hi def link qmlDeclaration Function
+
+let b:current_syntax = "qml"
+if main_syntax == 'qml'
+ unlet main_syntax
+endif
diff --git a/runtime/syntax/quarto.vim b/runtime/syntax/quarto.vim
new file mode 100644
index 0000000000..d5d4ee257d
--- /dev/null
+++ b/runtime/syntax/quarto.vim
@@ -0,0 +1,17 @@
+" Language: Quarto (Markdown with chunks of R, Python and other languages)
+" Provisory Maintainer: Jakson Aquino <jalvesaq@gmail.com>
+" Homepage: https://github.com/jalvesaq/R-Vim-runtime
+" Last Change: Fri Feb 24, 2023 08:26AM
+"
+" The developers of tools for Quarto maintain Vim runtime files in their
+" Github repository and, if required, I will hand over the maintenance of
+" this script for them.
+
+runtime syntax/rmd.vim
+
+syn match quartoShortarg /\S\+/ contained
+syn keyword quartoShortkey var meta env pagebreak video include contained
+syn region quartoShortcode matchgroup=PreProc start='{{< ' end=' >}}' contains=quartoShortkey,quartoShortarg transparent keepend
+
+hi def link quartoShortkey Include
+hi def link quartoShortarg String
diff --git a/runtime/syntax/r.vim b/runtime/syntax/r.vim
index a8100cfded..9b3754ae23 100644
--- a/runtime/syntax/r.vim
+++ b/runtime/syntax/r.vim
@@ -5,7 +5,7 @@
" Tom Payne <tom@tompayne.org>
" Contributor: Johannes Ranke <jranke@uni-bremen.de>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Sun Mar 28, 2021 01:47PM
+" Last Change: Thu Nov 17, 2022 10:13PM
" Filenames: *.R *.r *.Rhistory *.Rt
"
" NOTE: The highlighting of R functions might be defined in
@@ -65,41 +65,35 @@ if g:r_syntax_hl_roxygen
" roxygen line containing only a roxygen comment marker, optionally followed
" by whitespace is called an empty roxygen line.
+ syn match rOCommentKey "^\s*#\{1,2}'" contained
+ syn region rOExamples start="^\s*#\{1,2}' @examples.*"rs=e+1,hs=e+1 end="^\(#\{1,2}' @.*\)\@=" end="^\(#\{1,2}'\)\@!" contained contains=rOTag fold
+
+ " R6 classes may contain roxygen lines independent of roxygen blocks
+ syn region rOR6Class start=/R6Class(/ end=/)/ transparent contains=ALLBUT,rError,rBraceError,rCurlyError fold
+ syn match rOR6Block "#\{1,2}'.*" contains=rOTag,rOExamples,@Spell containedin=rOR6Class contained
+ syn match rOR6Block "^\s*#\{1,2}'.*" contains=rOTag,rOExamples,@Spell containedin=rOR6Class contained
+
" First we match all roxygen blocks as containing only a title. In case an
" empty roxygen line ending the title or a tag is found, this will be
" overridden later by the definitions of rOBlock.
- syn match rOTitleBlock "\%^\(\s*#\{1,2}' .*\n\)\{1,}" contains=rOCommentKey,rOTitleTag
- syn match rOTitleBlock "^\s*\n\(\s*#\{1,2}' .*\n\)\{1,}" contains=rOCommentKey,rOTitleTag
+ syn match rOTitleBlock "\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{1,}" contains=rOCommentKey,rOTitleTag
" A title as part of a block is always at the beginning of the block, i.e.
" either at the start of a file or after a completely empty line.
- syn match rOTitle "\%^\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" contained contains=rOCommentKey,rOTitleTag
- syn match rOTitle "^\s*\n\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" contained contains=rOCommentKey,rOTitleTag
+ syn match rOTitle "\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" contained contains=rOCommentKey,rOTitleTag
syn match rOTitleTag contained "@title"
" When a roxygen block has a title and additional content, the title
" consists of one or more roxygen lines (as little as possible are matched),
" followed either by an empty roxygen line
- syn region rOBlock start="\%^\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
- syn region rOBlock start="^\s*\n\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
+ syn region rOBlock start="\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*$" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
" or by a roxygen tag (we match everything starting with @ but not @@ which is used as escape sequence for a literal @).
- syn region rOBlock start="\%^\(\s*#\{1,2}' .*\n\)\{-}\s*#\{1,2}' @\(@\)\@!" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
- syn region rOBlock start="^\s*\n\(\s*#\{1,2}' .*\n\)\{-}\s*#\{1,2}' @\(@\)\@!" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
+ syn region rOBlock start="\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{-}\s*#\{1,2}' @\(@\)\@!" end="^\s*\(#\{1,2}'\)\@!" contains=rOTitle,rOTag,rOExamples,@Spell keepend fold
" If a block contains an @rdname, @describeIn tag, it may have paragraph breaks, but does not have a title
- syn region rOBlockNoTitle start="\%^\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @rdname" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
- syn region rOBlockNoTitle start="^\s*\n\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @rdname" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
- syn region rOBlockNoTitle start="\%^\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @describeIn" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
- syn region rOBlockNoTitle start="^\s*\n\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @describeIn" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
-
- syn match rOCommentKey "^\s*#\{1,2}'" contained
- syn region rOExamples start="^\s*#\{1,2}' @examples.*"rs=e+1,hs=e+1 end="^\(#\{1,2}' @.*\)\@=" end="^\(#\{1,2}'\)\@!" contained contains=rOTag fold
-
- " R6 classes may contain roxygen lines independent of roxygen blocks
- syn region rOR6Class start=/R6Class(/ end=/)/ transparent contains=ALLBUT,rError,rBraceError,rCurlyError fold
- syn match rOR6Block "#\{1,2}'.*" contains=rOTag,rOExamples,@Spell containedin=rOR6Class contained
- syn match rOR6Block "^\s*#\{1,2}'.*" contains=rOTag,rOExamples,@Spell containedin=rOR6Class contained
+ syn region rOBlockNoTitle start="\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @rdname" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
+ syn region rOBlockNoTitle start="\(\%^\|^\s*\n\)\@<=\(\s*#\{1,2}' .*\n\)\{-1,}\s*#\{1,2}'\s*\n\(\s*#\{1,2}'.*\n\)\{-}\s*#\{1,2}' @describeIn" end="^\s*\(#\{1,2}'\)\@!" contains=rOTag,rOExamples,@Spell keepend fold
" rOTag list originally generated from the lists that were available in
" https://github.com/klutometis/roxygen/R/rd.R and
@@ -245,14 +239,15 @@ syn match rOperator "&"
syn match rOperator '-'
syn match rOperator '\*'
syn match rOperator '+'
-if &filetype != "rmd" && &filetype != "rrst"
- syn match rOperator "[|!<>^~/:]"
-else
+if &filetype == "quarto" || &filetype == "rmd" || &filetype == "rrst"
syn match rOperator "[|!<>^~`/:]"
+else
+ syn match rOperator "[|!<>^~/:]"
endif
syn match rOperator "%\{2}\|%\S\{-}%"
syn match rOperator '\([!><]\)\@<=='
syn match rOperator '=='
+syn match rOperator '|>'
syn match rOpError '\*\{3}'
syn match rOpError '//'
syn match rOpError '&&&'
@@ -318,10 +313,13 @@ if &filetype == "rhelp"
endif
" Type
+syn match rType "\\"
syn keyword rType array category character complex double function integer list logical matrix numeric vector data.frame
" Name of object with spaces
-if &filetype != "rmd" && &filetype != "rrst"
+if &filetype == "rmd" || &filetype == "rrst" || &filetype == "quarto"
+ syn region rNameWSpace start="`" end="`" contains=rSpaceFun containedin=rmdrChunk
+else
syn region rNameWSpace start="`" end="`" contains=rSpaceFun
endif
diff --git a/runtime/syntax/rapid.vim b/runtime/syntax/rapid.vim
new file mode 100644
index 0000000000..ed0da095c0
--- /dev/null
+++ b/runtime/syntax/rapid.vim
@@ -0,0 +1,687 @@
+" ABB Rapid Command syntax file for Vim
+" Language: ABB Rapid Command
+" Maintainer: Patrick Meiser-Knosowski <knosowski@graeffrobotics.de>
+" Version: 2.3.0
+" Last Change: 21. Jul 2023
+" Credits: Thanks for beta testing to Thomas Baginski
+"
+" Suggestions of improvement are very welcome. Please email me!
+"
+"
+"
+" Note to self:
+" for testing perfomance
+" open a 1000 lines file.
+" :syntime on
+" G
+" hold down CTRL-U until reaching top
+" :syntime report
+"
+"
+" TODO: - highlight rapid constants and maybe constants from common
+" technology packages
+" - optimize rapidErrorStringTooLong
+" - error highlight for missing 2nd point in MoveCirc et al
+
+" Init {{{
+" Remove any old syntax stuff that was loaded (5.x) or quit when a syntax file
+" was already loaded (6.x).
+if version < 600
+ syntax clear
+elseif exists("b:current_syntax")
+ finish
+endif
+
+let s:keepcpo= &cpo
+set cpo&vim
+
+" if colorscheme is tortus rapidNoHighLink defaults to 1
+if (get(g:,'colors_name'," ")=="tortus" || get(g:,'colors_name'," ")=="tortusless")
+ \&& !exists("g:rapidGroupName")
+ let g:rapidGroupName=1
+endif
+" rapidGroupName defaults to 0 if it's not initialized yet or 0
+if !get(g:,"rapidGroupName",0)
+ let g:rapidGroupName=0
+endif
+
+" Rapid does ignore case
+syn case ignore
+" spell checking
+syn spell notoplevel
+" }}} init
+
+" common highlighting {{{
+
+" Error {{{
+if get(g:,'rapidShowError',1)
+ "
+ " This error must be defined befor rapidCharCode and rapidEscapedBackSlash
+ " a string containing a single \ which is not a char code
+ syn match rapidErrorSingleBackslash /\\/ contained
+ highlight default link rapidErrorSingleBackslash Error
+ "
+endif
+" }}} Error
+
+" Constant values {{{
+" Boolean
+syn keyword rapidBoolean TRUE FALSE Edge High Low
+highlight default link rapidBoolean Boolean
+" Float (num)
+" syn match rapidFloat /\v%(\W|_)@1<=[+-]?\d+\.?\d*%(\s*[eE][+-]?\d+)?/
+syn match rapidFloat /\v\c%(<\d+\.|\.?<\d)\d*%(E[+-]?\d+)?>/ contains=rapidOperator
+highlight default link rapidFloat Float
+" integer in decimal, hexadecimal, octal and binary
+syn match rapidDec /\<[0-9]\+\>/
+highlight default link rapidDec Number
+syn match rapidHex /\<0x[0-9a-fA-F]\+\>/
+highlight default link rapidHex Number
+syn match rapidOct /\<0o[0-7]\+\>/
+highlight default link rapidOct Number
+syn match rapidBin /\<0b[01]\+\>/
+highlight default link rapidBin Number
+" String. Note: Don't rename group rapidString. Indent depend on this
+syn region rapidString matchgroup=rapidString start=/"/ skip=/""/ end=/"/ oneline contains=rapidStringDoubleQuote,rapidEscapedBackSlash,rapidCharCode,rapidErrorSingleBackslash,rapidErrorStringTooLong,@Spell
+highlight default link rapidString String
+" two adjacent "" in string for one double quote
+syn match rapidStringDoubleQuote /""/ contained
+highlight default link rapidStringDoubleQuote SpecialChar
+" character code in string
+syn match rapidCharCode /\\\x\x/ contained
+highlight default link rapidCharCode SpecialChar
+" escaped \ in string
+syn match rapidEscapedBackSlash /\\\\/ contained
+highlight default link rapidEscapedBackSlash SpecialChar
+" }}} Constant values
+
+" }}} common highlighting
+
+if bufname("%") =~ '\c\.cfg$'
+" {{{ highlighting for *.cfg
+
+ " special chars {{{
+ " syn match rapidOperator /:\|[+-]\|\*\|\/\|\\/
+ syn match rapidOperator /[-+*/:\\]/
+ syn match rapidOperator /^#/
+ highlight default link rapidOperator Operator
+ " }}} special chars
+
+ " sections {{{
+ syn match rapidException /^\w\+/
+ syn match rapidException /CFG_\d\+/
+ highlight default link rapidException Exception
+ " }}} sections
+
+ " Error {{{
+ if get(g:,'rapidShowError',1)
+ "
+ " This error must be defined after rapidString
+ " Any Name longer than 32 chars
+ syn match rapidErrorNameTooLong /-Name "[^"]\{33,}"/
+ highlight default link rapidErrorNameTooLong Error
+ "
+ endif
+ " }}} Error
+
+ " }}} highlighting for *.cfg
+else
+ " highlighting for *.mod, *.sys and *.prg {{{
+
+ " sync for regions from a line comment or the start of a function
+ syn sync match rapidSync grouphere NONE /\v\c^\s*%(!|%(task\s+|local\s+)?%(module|proc|func|trap|record)>)/
+
+ " Comment {{{
+ " TODO Comment
+ syn match rapidTodoComment contained /\<TODO\>\|\<FIXME\>\|\<XXX\>/
+ highlight default link rapidTodoComment Todo
+ " Debug comment
+ syn match rapidDebugComment contained /\<DEBUG\>/
+ highlight default link rapidDebugComment Debug
+ " Line comment
+ syn match rapidComment /!.*$/ contains=rapidTodoComment,rapidDebugComment,@Spell
+ highlight default link rapidComment Comment
+ " }}} Comment
+
+ " Header {{{
+ syn match rapidHeader /^%%%/
+ highlight default link rapidHeader PreProc
+ " }}} Header
+
+ " Operator {{{
+ " Boolean operator
+ syn keyword rapidOperator and or xor not div mod
+ " Arithmetic and compare operator
+ syn match rapidOperator /[-+*/<>:=]/
+ " conditional argument
+ syn match rapidOperator /?/
+ highlight default link rapidOperator Operator
+ " }}} Operator
+
+ " Type, StorageClass and Typedef {{{
+ " anytype (preceded by 'alias|pers|var|const|func'
+ " TODO: still missing are userdefined types which are part of a parameter:
+ " PROC message( mystring msMessagePart1{},
+ " \ myvar msMsg4{})
+ " TODO testing. Problem: does not highlight any type if it's part of an argument list
+ " syn match rapidAnyType /\v^\s*(global\s+|task\s+|local\s+)?(alias|pers|var|const|func)\s+\w+>/ contains=rapidStorageClass,rapidType,rapidTypeDef
+ " highlight default link rapidAnyType Type
+ syn keyword rapidType accdata aiotrigg bool btnres busstate buttondata byte
+ syn keyword rapidType cfgdomain clock cnvcmd confdata confsupdata corrdescr datapos deflectiondata dionum dir dnum
+ syn keyword rapidType egmframetype egmident egm_minmax egmstate egmstopmode errdomain errnum ErrorInfo errstr errtype event_type exec_level extjoint handler_type
+ syn keyword rapidType icondata identno inposdata intnum inttypes iodev iounit_state jointtarget
+ syn keyword rapidType listitem loaddata loadidnum loadsession mecunit motionprocessmode motsetdata
+ " syn keyword rapidType num
+ syn keyword rapidType opcalc opnum orient paridnum paridvalidnum pathrecid pnpdata pos pose proc_times progdisp o_jointtarget o_robtarget
+ syn keyword rapidType rawbytes restartdata rmqheader rmqmessage rmqslot robjoint robtarget
+ syn keyword rapidType searchdata sensor sensorstate sensorvardata shapedata signalai signalao signaldi signaldo signalgi signalgo signalorigin singdata socketdev socketstatus speeddata stopmovestartmove_mem stoppoint stoppointdata string stringdig sup_timeouts supervtype switch symnum syncident
+ syn keyword rapidType taskid tasks tasksatstart testsignal tooldata tpnum trapdata triggdata triggflag triggios triggiosdnum triggmode triggstrgo tsp_status tunegtype tunetype
+ syn keyword rapidType uishownum veldata visiondata wobjdata wzstationary wztemporary zonedata
+ " SoftMove data types
+ syn keyword rapidType css_offset_dir css_soft_dir cssframe
+ " arc data types
+ syn keyword rapidType advSeamData arcdata flystartdata seamdata arctrackdata opttrackdata weavedata welddata
+ " conveyor tracking data types
+ syn keyword rapidType indcnvdata
+ " Integrated Vision data types
+ syn keyword rapidType cameradev cameratarget
+ " arc Weldguide and MultiPass data types
+ syn keyword rapidType adaptdata trackdata multidata
+ " dispense data types
+ syn keyword rapidType beaddata equipdata
+ " Spot data types
+ syn keyword rapidType gundata gunnum spotdata forcedata simdata smeqdata smeqtype
+ " Tool change data types
+ syn keyword rapidType standno ToolInfo toolno
+ " Continuous Application Platform data types
+ syn keyword rapidType capaptrreferencedata capdata capevent caplatrackdata capmvsttim capspeeddata capspeeddata capstopmode captestno captrackdata capweavedata flypointdata processtimes restartblkdata supervtimeouts weavestartdata
+ " Bulls Eye data types
+ syn keyword rapidType be_device be_scan be_tooldesign
+ " Force Control data types
+ syn keyword rapidType fcboxvol fccondstatus fccylindervol fcdamping fcforcevector fcframe fclindir fcprocessdata fcplane fcrotdir fcspeedvector fcspherevol fcspdchgtunetype fcxyznum
+ " Discrete application platform data types
+ syn keyword rapidType dadescapp dadescprc daintdata
+ " VW Konzernstandard VWKS_1.07.02
+ syn keyword rapidType merker
+ syn keyword rapidType frgnum frgwert robnum
+ syn keyword rapidType fmnum applid calibdatavorr stepdata
+ syn keyword rapidType tsmethode tsdaten teilspeicherdaten
+ syn keyword rapidType greiferdaten greiferposition bauteildaten bauteilkontrolle g_datenident g_sensor g_signal g_teilident g_ventil
+ syn keyword rapidType strgnum typnum
+ syn keyword rapidType hubnum kopfnum
+ syn keyword rapidType applservicetype
+ syn keyword rapidType applfraesdaten kwdionum
+ syn keyword rapidType butechnum
+ syn keyword rapidType toolnum dbnum
+ " das folgende sind datentypen aber das kann man doch nicht machen...
+ " syn keyword rapidType position wert
+ syn keyword rapidType camdata camlimitdata cammode camprotocoldata camstatus camsequence campositionstatus
+ syn keyword rapidType saposnum sabereichnum autofocusnum focusposnum lascaledata laleistungnum larobnum laprognum uebwnum dgbanum dgjobnum gasspuelnum davalve gasuebwnum
+ syn keyword rapidType lsfigurnum lsstarttype
+ syn keyword rapidType lwprognum lwdiodnum lsstarttype
+ syn keyword rapidType lztype diskrethubnum lztipnum
+ syn keyword rapidType gblmethod
+ syn keyword rapidType buatypenum buatechnum buadirnum
+ highlight default link rapidType Type
+ " Storage class
+ syn keyword rapidStorageClass LOCAL TASK VAR PERS CONST ALIAS NOVIEW NOSTEPIN VIEWONLY READONLY SYSMODULE INOUT
+ highlight default link rapidStorageClass StorageClass
+ " Not a typedef but I like to have those highlighted different then types,
+ " structures or strorage classes
+ syn keyword rapidTypeDef MODULE ENDMODULE PROC ERROR UNDO BACKWARD ENDPROC RECORD ENDRECORD TRAP ENDTRAP FUNC ENDFUNC
+ highlight default link rapidTypeDef TypeDef
+ " }}} Type, StorageClass and Typedef
+
+ " Statements, keywords et al {{{
+ " syn keyword rapidStatement
+ " highlight default link rapidStatement Statement
+ " Conditional
+ syn keyword rapidConditional if then elseif else endif test case default endtest
+ highlight default link rapidConditional Conditional
+ " Repeat
+ syn keyword rapidRepeat do
+ syn match rapidRepeat /\c\v^\s*%(<while>|<for>)%([^!]+<do>)@=/
+ syn keyword rapidRepeat from to step endfor endwhile
+ highlight default link rapidRepeat Repeat
+ " Label
+ syn keyword rapidLabel goto
+ syn match rapidLabel /\c\v^\s*[[:upper:][:lower:]]\k*\:\ze%([^=]|$)/ contains=rapidConditional,rapidOperator
+ highlight default link rapidLabel Label
+ " Keyword
+ syn keyword rapidKeyword AccSet ActEventBuffer ActUnit Add AliasCamera AliasIO AliasIOReset BitClear BitSet BookErrNo BrakeCheck
+ syn keyword rapidKeyword CallByVar CancelLoad CheckProgRef CirPathMode Clear ClearIOBuff ClearPath ClearRawBytes ClkReset ClkStart ClkStop Close CloseDir ConfJ ConfL CONNECT CopyFile CopyRawBytes CornerPathWarning CorrClear CorrCon CorrDiscon CorrWrite
+ syn keyword rapidKeyword CSSAct CSSDeact CSSForceOffsetAct CSSForceOffsetDeact CSSOffsetTune CyclicBrakeCheck
+ syn keyword rapidKeyword DeactEventBuffer DeactUnit Decr DitherAct DitherDeact DropSensor
+ syn keyword rapidKeyword EGMActJoint EGMActMove EGMActPose EGMGetId EGMReset EGMSetupAI EGMSetupAO EGMSetupGI EGMSetupLTAPP EGMSetupUC EOffsOff EOffsOn EOffsSet EraseModule ErrLog ErrWrite
+ syn keyword rapidKeyword FitCircle FricIdInit FricIdEvaluate FricIdSetFricLevels
+ syn keyword rapidKeyword GetDataVal GetGroupSignalInfo GetJointData GetSysData GetTorqueMargin GetTrapData GripLoad HollowWristReset
+ syn keyword rapidKeyword IDelete IDisable IEnable IError Incr IndReset InvertDO IOBusStart IOBusState IoCtrlAxis_RefSync IoCtrlAxis_RefSyncOff IoCtrlAxis_RefSyncOn IODisable IOEnable IPers IRMQMessage ISignalAI ISignalAO ISignalDI ISignalDO ISignalGI ISignalGO ISleep ITimer IVarValue IWatch
+ syn keyword rapidKeyword Load LoadId MakeDir ManLoadIdProc MatrixSolve MatrixSolveQR MatrixSVD MechUnitLoad MotionProcessModeSet MotionSup MToolRotCalib MToolTCPCalib Open OpenDir
+ syn keyword rapidKeyword PackDNHeader PackRawBytes PathAccLim PathLengthReset PathLengthStart PathLengthStop PathRecStart PathRecStop PathResol PDispOff PDispOn PDispSet ProcerrRecovery PrxActivAndStoreRecord PrxActivRecord PrxDbgStoreRecord PrxDeactRecord PrxResetPos PrxResetRecords PrxSetPosOffset PrxSetRecordSampleTime PrxSetSyncalarm PrxStartRecord PrxStopRecord PrxStoreRecord PrxUseFileRecord PulseDO
+ syn keyword rapidKeyword ReadAnyBin ReadBlock ReadCfgData ReadErrData ReadRawBytes ReadVarArr RemoveAllCyclicBool RemoveCyclicBool RemoveDir RemoveFile RenameFile Reset ResetAxisDistance ResetAxisMoveTime ResetPPMoved ResetRetryCount ResetTorqueMargin RestoPath Rewind RMQEmptyQueue RMQFindSlot RMQGetMessage RMQGetMsgData RMQGetMsgHeader RMQReadWait RMQSendMessage RMQSendWait
+ syn keyword rapidKeyword SafetyControllerSyncRequest Save SaveCfgData SCWrite SenDevice Set SetAllDataVal SetAO SetDataSearch SetDataVal SetDO SetGO SetLeadThrough SetSysData SetupCyclicBool SiConnect SiClose SiGetCyclic SingArea SiSetCyclic SkipWarn SocketAccept SocketBind SocketClose SocketConnect SocketCreate SocketListen SocketReceive SocketReceiveFrom SocketSend SocketSendTo SoftAct SoftDeact SoftElbow SpeedLimAxis SpeedLimCheckPoint SpeedRefresh SpyStart SpyStop StartLoad STCalib STClose STIndGun STIndGunReset SToolRotCalib SToolTCPCalib STOpen StorePath STTune STTuneReset SupSyncSensorOff SupSyncSensorOn SyncMoveOff SyncMoveOn SyncMoveResume SyncMoveSuspend SyncMoveUndo SyncToSensor SystemStopAction
+ syn keyword rapidKeyword TestSignDefine TestSignReset TextTabInstall TPErase TPReadDnum TPReadFK TPReadNum TPShow TPWrite TriggCheckIO TriggDataCopy TriggDataReset TriggEquip TriggInt TriggIO TriggRampAO TriggSpeed TriggStopProc TryInt TuneReset TuneServo
+ syn keyword rapidKeyword UIMsgBox UIMsgWrite UIMsgWriteAbort UIShow UnLoad UnpackRawBytes VelSet WaitAI WaitAO WaitDI WaitDO WaitGI WaitGO WaitLoad WaitRob WaitSensor WaitSyncTask WaitTestAndSet WaitTime WaitUntil WarmStart WITH WorldAccLim Write WriteAnyBin WriteBin WriteBlock WriteCfgData WriteRawBytes WriteStrBin WriteVar WriteVarArr WZBoxDef WZCylDef WZDisable WZDOSet WZEnable WZFree WZHomeJointDef WZLimJointDef WZLimSup WZSphDef
+ " arc instructions
+ syn keyword rapidKeyword ArcRefresh RecoveryMenu RecoveryMenuWR RecoveryPosSet RecoveryPosReset SetWRProcName
+ " conveyor tracking instructions
+ syn keyword rapidKeyword UseACCProfile WaitWObj DropWObj RecordProfile WaitAndRecProf StoreProfile LoadProfile ActivateProfile DeactProfile CnvGenInstr CnvSync CnvGenInstr IndCnvInit IndCnvEnable IndCnvDisable IndCnvReset IndCnvAddObject
+ syn keyword rapidKeyword UseReachableTargets GetMaxUsageTime ResetMaxUsageTime CnvPredictReach
+ " Integrated Vision instructions
+ syn keyword rapidKeyword CamFlush CamGetParameter CamGetResult CamLoadJob CamReqImage CamSetExposure CamSetParameter CamSetProgramMode CamSetRunMode CamStartLoadJob CamWaitLoadJob
+ " arc Weldguide and MultiPass instructions
+ syn keyword rapidKeyword MPSavePath MPLoadPath MPReadInPath MPOffsEaxOnPath
+ " Paint instructions
+ syn keyword rapidKeyword ConsoleWrite IpsSetParam PntProdUserLog SetBrush SetBrushFac
+ " Spot instructions
+ syn keyword rapidKeyword SetForce Calibrate ReCalcTCP IndGunMove IndGunMoveReset OpenHighLift CloseHighLift SwSetIntSpotData SwSetIntForceData SwSetIntGunData SwSetIntSimData SwGetCalibData SwGetFixTipData
+ " Tool change instructions
+ syn keyword rapidKeyword TcCloseCover TcDropOffTool TcLockTool TcOpenCover TcPickupTool TcUnlockTool
+ " dispense instructions
+ syn keyword rapidKeyword SetTmSignal SyncWWObj
+ " Continuous Application Platform instructions
+ syn keyword rapidKeyword CapAPTrSetup CapAPTrSetupAI CapAPTrSetupAO CapAPTrSetupPERS CapCondSetDO CapEquiDist CapNoProcess CapRefresh CAPSetStopMode CapWeaveSync ICap InitSuperv IPathPos RemoveSuperv SetupSuperv
+ " Bulls Eye instructions
+ syn keyword rapidKeyword BECheckTcp BEDebugState BERefPointer BESetupToolJ BETcpExtend BEUpdateTcp
+ " Force Control instructions
+ syn keyword rapidKeyword FCAct FCCalib FCCondForce FCCondOrient FCCondPos FCCondReoriSpeed FCCondTCPSpeed FCCondTorque FCCondWaitWhile FCDeact FCPress1LStart FCPressC FCPressEnd FCPressL FCRefCircle FCRefForce FCRefLine FCRefMoveFrame FCRefRot FCRefSpiral FCRefSprForceCart FCRefStart FCRefStop FCRefTorque FCResetDampingTune FCResetLPFilterTune FCSpdChgAct FCSpdChgDeact FCSpdChgTunSet FCSpdChgTunReset FCSetDampingTune FCSetLPFilterTune FCSupvForce FCSupvOrient FCSupvPos FCSupvReoriSpeed FCSupvTCPSpeed FCSupvTorque
+ " Discrete application platform instructions
+ syn keyword rapidKeyword DaActProc DaDeactAllProc DaDeactProc DaDefExtSig DaDefProcData DaDefProcSig DaDefUserData DaGetCurrData DaSetCurrData DaSetupAppBehav DaStartManAction DaGetAppDescr DaGetAppIndex DaGetNumOfProcs DaGetNumOfRob DaGetPrcDescr
+ " Production Manager instructions
+ syn keyword rapidKeyword ExecEngine PMgrGetNextPart PMgrSetNextPart PMgrRunMenu
+ " Homepos-Running instructions
+ syn keyword rapidKeyword HR_Exit HR_ExitCycle HR_SavePos HR_SetMoveToStartPos HR_SetTypeDIndex HR_SetTypeIndex
+ highlight default link rapidKeyword Keyword
+ " Exception
+ syn keyword rapidException Exit ErrRaise ExitCycle Raise RaiseToUser Retry Return TryNext
+ syn match rapidException /\s\+Stop\s*[\\;]/me=e-1
+ highlight default link rapidException Exception
+ " }}} Statements, keywords et al
+
+ " Special keyword for move command {{{
+ " uncategorized yet
+ syn keyword rapidMovement MovePnP
+ syn keyword rapidMovement EGMMoveC EGMMoveL EGMRunJoint EGMRunPose EGMStop
+ syn keyword rapidMovement IndAMove IndCMove IndDMove IndRMove
+ " common instructions
+ syn keyword rapidMovement MoveAbsJ MoveC MoveExtJ MoveJ MoveL
+ syn keyword rapidMovement MoveCAO MoveCDO MoveCGO MoveCSync MoveJAO MoveJDO MoveJGO MoveJSync MoveLAO MoveLDO MoveLGO MoveLSync
+ syn keyword rapidMovement SearchC SearchExtJ SearchL
+ syn keyword rapidMovement TriggC TriggJ TriggL TriggJIOs TriggLIOs
+ " Arc instructions
+ syn keyword rapidMovement ArcC ArcC1 ArcC2 ArcCEnd ArcC1End ArcC2End ArcCStart ArcC1Start ArcC2Start
+ syn keyword rapidMovement ArcL ArcL1 ArcL2 ArcLEnd ArcL1End ArcL2End ArcLStart ArcL1Start ArcL2Start ArcMoveExtJ
+ " Arc Weldguide and MultiPass instructions
+ syn keyword rapidMovement ArcRepL ArcAdaptLStart ArcAdaptL ArcAdaptC ArcAdaptLEnd ArcAdaptCEnd ArcCalcLStart ArcCalcL ArcCalcC ArcCalcLEnd ArcCalcCEnd ArcAdaptRepL
+ syn keyword rapidMovement Break
+ " Continuous Application Platform instructions
+ syn keyword rapidMovement CapC CapL CapLATrSetup CSSDeactMoveL ContactL
+ " Dispense instructions
+ syn keyword rapidMovement DispL DispC
+ " Nut instructions"
+ syn keyword rapidMovement NutL NutJ
+ syn keyword rapidMovement PathRecMoveBwd PathRecMoveFwd
+ " Paint instructions"
+ syn keyword rapidMovement PaintL PaintLSig PaintLDO PaintC
+ syn keyword rapidMovement StartMove StartMoveRetry StepBwdPath StopMove StopMoveReset
+ " Spot instructions
+ syn keyword rapidMovement SpotL SpotJ SpotML SpotMJ CalibL CalibJ MeasureWearL
+ " Homepos-Running instructions
+ syn keyword rapidMovement SMoveJ SMoveJDO SMoveJGO SMoveJSync SMoveL SMoveLDO SMoveLGO SMoveLSync SSearchL STriggJ STriggL
+ syn keyword rapidMovement HR_ContMove HR_MoveBack HR_MoveRoutine HR_MoveTo HR_MoveToHome SCSSDeactMoveL
+ " Discrete application platform instructions
+ syn keyword rapidMovement DaProcML DaProcMJ
+ " VW Konzernstandard VWKS_1.07.02
+ syn keyword rapidMovement MoveABS MoveABS_FB MoveABS_FRG MoveABS_ROB
+ syn keyword rapidMovement MoveCIRC MoveCIRC_FB MoveCIRC_FRG MoveCIRC_ROB
+ syn keyword rapidMovement MoveLIN MoveLIN_FB MoveLIN_FRG MoveLIN_ROB
+ syn keyword rapidMovement MovePTP MovePTP_FB MovePTP_FRG MovePTP_ROB
+ syn keyword rapidMovement SearchCIRC SearchCIRC_M
+ syn keyword rapidMovement SearchLIN SearchLIN_M
+ syn keyword rapidMovement MoveABS_AO MoveABS_DO MoveABS_GO
+ syn keyword rapidMovement MoveCIRC_AO MoveCIRC_DO MoveCIRC_GO
+ syn keyword rapidMovement MoveLIN_AO MoveLIN_DO MoveLIN_GO
+ syn keyword rapidMovement KW_LoesenLIN
+ syn keyword rapidMovement SPZ_FraesenLIN SPZ_FraesenPTP SPZ_MessenLIN SPZ_MessenPTP SPZ_LIN SPZ_PTP
+ syn keyword rapidMovement BZ_LIN BZ_PTP
+ syn keyword rapidMovement KL_LIN KL_CIRC
+ syn keyword rapidMovement BP_LIN BP_PTP
+ syn keyword rapidMovement BU_CIRC BU_LIN
+ syn keyword rapidMovement CZ_LIN CZ_LIN_V CZ_PTP CZ_PTP_V
+ syn keyword rapidMovement FD_LIN FD_PTP
+ syn keyword rapidMovement KG_LIN KG_PTP
+ syn keyword rapidMovement DA_LIN
+ syn keyword rapidMovement LK_CIRC LK_LIN
+ syn keyword rapidMovement LL_CIRC LL_LIN
+ syn keyword rapidMovement LS_CIRC LS_LIN LS_LIN_F LS_PTP_F
+ syn keyword rapidMovement LW_CIRC LW_LIN
+ syn keyword rapidMovement LZ_LIN LZ_PTP LZ_ReinigenLIN LZ_ReinigenPTP
+ syn keyword rapidMovement MS_CIRC MS_LIN MS_ReinigenLIN MS_SearchLIN MS_PTP_CS MS_LIN_CS GBL_LIN GBL_PTP GBL_RefPointLIN
+ syn keyword rapidMovement NK_LIN
+ syn keyword rapidMovement NZ_LIN NZ_LIN_V NZ_PTP NZ_PTP_V
+ syn keyword rapidMovement PR_LIN PR_PTP
+ syn keyword rapidMovement RF_CIRC RF_LIN
+ syn keyword rapidMovement STP_FraesenLIN STP_FraesenPTP STP_LIN STP_PTP
+ syn keyword rapidMovement SM_LIN SM_PTP
+ syn keyword rapidMovement BUA_CIRC BUA_LIN BUA_MessenLIN BUA_MessenPTP
+ syn keyword rapidMovement KE_LIN
+ if g:rapidGroupName
+ highlight default link rapidMovement Movement
+ else
+ highlight default link rapidMovement Special
+ endif
+ " }}} special keyword for move command
+
+ " Any name {{{
+ syn match rapidNames /\v[[:upper:][:lower:]](\k|\.)*/
+ " }}} Any name
+
+ " Attempt to avoid false highlight of num in case of parameter name:
+ " TPWrite "goPosNo="\num:=GOutput(goPosNo);
+ " Must follow after rapidNames in this file
+ syn match rapidType /\c\v<num>\s*\ze[^ :]/
+
+ " Structure value {{{
+ " rapid structrure values. added to be able to conceal them
+ syn region rapidConcealableString matchgroup=rapidConcealableString start=/"/ skip=/""/ end=/"/ oneline keepend extend contained contains=rapidStringDoubleQuote,rapidEscapedBackSlash,rapidCharCode,rapidErrorSingleBackslash,rapidErrorStringTooLong,@Spell conceal
+ highlight default link rapidConcealableString String
+ syn region rapidStructVal matchgroup=rapidStructDelimiter start=/\[/ end=/\]/ contains=rapidStructVal,rapidBoolean,rapidDec,rapidHex,rapidOct,rapidBin,rapidFloat,rapidConcealableString,rapidDelimiter,rapidConstant,rapidErrNo,rapidIntNo,rapidOperator keepend extend conceal cchar=*
+ highlight default link rapidStructDelimiter Delimiter
+ " check edge cases like this one:
+ " LOCAL CONST listitem lstAuswService{18}:=[["","Service Position"],["","Bremsentest"],["","Referenzfahrt"],["","Manuelles Abfahren"],["","Justagestellung"],["","Transportposition"],
+ " ["","Spitze-Spitze Greifer 1, [RT]"],["","Spitze-Spitze Greifer 2, [FT]"],["","Spitze-Spitze Pruefspitze"],["","Werkobjekt Ablage"],["","Werkobjekt Modul 1"],
+ " ["","Werkobjekt Modul 2"],["","TCP von Greifer 1 vermessen, [RT]"],["","TCP von Greifer 2 vermessen, [FT]"],["","TCP von Basisdorn vermessen"],
+ " ["","Greifer abdocken"],["","Greifer andocken"],["","Kollision Check (Ohne Greifer)"]];
+ " }}} Structure value
+
+ " Delimiter {{{
+ syn match rapidDelimiter /[\\(){},;|]/
+ highlight default link rapidDelimiter Delimiter
+ " }}} Delimiter
+
+ " BuildInFunction {{{
+ " dispense functions
+ syn keyword rapidBuildInFunction contained GetSignal GetSignalDnum
+ " Integrated Vision Platform functions
+ syn keyword rapidBuildInFunction contained CamGetExposure CamGetLoadedJob CamGetName CamNumberOfResults
+ " Continuous Application Platform functions
+ syn keyword rapidBuildInFunction contained CapGetFailSigs
+ syn keyword rapidBuildInFunction contained Abs AbsDnum ACos ACosDnum AInput AOutput ArgName ASin ASinDnum ATan ATanDnum ATan2 ATan2Dnum
+ syn keyword rapidBuildInFunction contained BitAnd BitAndDnum BitCheck BitCheckDnum BitLSh BitLShDnum BitNeg BitNegDnum BitOr BitOrDnum BitRSh BitRShDnum BitXOr BitXOrDnum ByteToStr
+ syn keyword rapidBuildInFunction contained CalcJointT CalcRobT CalcRotAxFrameZ CalcRotAxisFrame CDate CJointT ClkRead CorrRead Cos CosDnum CPos CRobT CrossProd CSpeedOverride CTime CTool CWObj
+ syn keyword rapidBuildInFunction contained DecToHex DefAccFrame DefDFrame DefFrame Dim DInput Distance DnumToNum DnumToStr DotProd DOutput
+ syn keyword rapidBuildInFunction contained EGMGetState EulerZYX EventType ExecHandler ExecLevel Exp
+ syn keyword rapidBuildInFunction contained FileSize FileTime FileTimeDnum FSSize
+ syn keyword rapidBuildInFunction contained GetAxisDistance GetAxisMoveTime GetMaxNumberOfCyclicBool GetMecUnitName GetModalPayLoadMode GetMotorTorque GetNextCyclicBool GetNextMechUnit GetNextSym GetNumberOfCyclicBool GetServiceInfo GetSignalOrigin GetSysInfo GetTaskName GetTime GetTSPStatus GetUASUserName GInput GInputDnum GOutput GOutputDnum
+ syn keyword rapidBuildInFunction contained HexToDec
+ syn keyword rapidBuildInFunction contained IndInpos IndSpeed IOUnitState IsBrakeCheckActive IsCyclicBool IsFile IsLeadThrough IsMechUnitActive IsPers IsStopMoveAct IsStopStateEvent IsSyncMoveOn IsSysId IsVar
+ syn keyword rapidBuildInFunction contained Max MaxExtLinearSpeed MaxExtReorientSpeed MaxRobReorientSpeed MaxRobSpeed Min MirPos ModExist ModTime ModTimeDnum MotionPlannerNo
+ syn keyword rapidBuildInFunction contained NonMotionMode NOrient NumToDnum NumToStr
+ syn keyword rapidBuildInFunction contained Offs OpMode OrientZYX ORobT
+ syn keyword rapidBuildInFunction contained ParIdPosValid ParIdRobValid PathLengthGet PathLevel PathRecValidBwd PathRecValidFwd PFRestart PoseInv PoseMult PoseVect Pow PowDnum PPMovedInManMode Present ProgMemFree PrxGetMaxRecordpos
+ syn keyword rapidBuildInFunction contained RawBytesLen ReadBin ReadDir ReadMotor ReadNum ReadStr ReadStrBin ReadVar RelTool RemainingRetries RMQGetSlotName RobName RobOS Round RoundDnum RunMode
+ syn keyword rapidBuildInFunction contained SafetyControllerGetChecksum SafetyControllerGetOpModePinCode SafetyControllerGetSWVersion SafetyControllerGetUserChecksum Sin SinDnum SocketGetStatus SocketPeek Sqrt SqrtDnum STCalcForce STCalcTorque STIsCalib STIsClosed STIsIndGun STIsOpen StrDigCalc StrDigCmp StrFind StrLen StrMap StrMatch StrMemb StrOrder StrPart StrToByte StrToVal
+ syn keyword rapidBuildInFunction contained Tan TanDnum TaskRunMec TaskRunRob TasksInSync TaskIsActive TaskIsExecuting TestAndSet TestDI TestSignRead TextGet TextTabFreeToUse TextTabGet TriggDataValid Trunc TruncDnum Type
+ syn keyword rapidBuildInFunction contained UIAlphaEntry UIClientExist UIDnumEntry UIDnumTune UIListView UIMessageBox UINumEntry UINumTune
+ syn keyword rapidBuildInFunction contained ValidIO ValToStr Vectmagn
+ " Bulls Eye functions
+ syn keyword rapidBuildInFunction contained OffsToolXYZ OffsToolPolar
+ " Force Control functions
+ syn keyword rapidBuildInFunction contained FCGetForce FCGetProcessData FCIsForceMode FCLoadID
+ " Discrete application platform functions
+ syn keyword rapidBuildInFunction contained DaGetFstTimeEvt DaCheckMMSOpt DaGetMP DaGetRobotName DaGetTaskName
+ " Production Manager functions
+ syn keyword rapidBuildInFunction contained PMgrAtSafe PMgrAtService PMgrAtState PMgrAtStation PMgrNextStation PMgrTaskNumber PMgrTaskName
+ " Spot functions
+ syn keyword rapidBuildInFunction contained SwGetCurrTargetName SwGetCurrSpotName
+ " Homepos-Running functions
+ syn keyword rapidBuildInFunction contained HR_RobotInHome HR_GetTypeDIndex HR_GetTypeIndex
+ " Paint functions
+ syn keyword rapidBuildInFunction contained IndexLookup IpsCommand IpsGetParam PaintCommand PntQueueExtraGet PntQueueExtraSet PntQueuePeek
+ if g:rapidGroupName
+ highlight default link rapidBuildInFunction BuildInFunction
+ else
+ highlight default link rapidBuildInFunction Function
+ endif
+ " }}}
+
+ " Function {{{
+ syn match rapidFunction contains=rapidBuildInFunction /\v\c%(<%(PROC|MODULE)\s+)@10<!<[[:upper:][:lower:]]\k+ *\(/me=e-1
+ highlight default link rapidFunction Function
+ syn match rapidCallByVar /%\ze[^%]/
+ highlight default link rapidCallByVar Function
+ " }}} Function
+
+ " Constants {{{
+ " standard rapid constants
+ syn keyword rapidConstant pi stEmpty
+ syn keyword rapidConstant STR_DIGIT STR_LOWER STR_UPPER STR_WHITE
+ syn keyword rapidConstant flp1 diskhome diskram disktemp usbdisk1 usbdisk2 usbdisk3 usbdisk4 usbdisk5 usbdisk6 usbdisk7 usbdisk8 usbdisk9 usbdisk10
+ " stoppoint
+ syn keyword rapidConstant inpos stoptime fllwtime
+ " stoppointdata
+ syn keyword rapidConstant inpos20 inpos50 inpos100
+ syn keyword rapidConstant stoptime0_5 stoptime1_0 stoptime1_5
+ syn keyword rapidConstant fllwtime0_5 fllwtime1_0 fllwtime1_5
+ " default tool/wobj/load
+ syn keyword rapidConstant tool0 wobj0 load0
+ " zonedata
+ syn keyword rapidConstant fine z0 z1 z5 z10 z15 z20 z30 z40 z50 z60 z80 z100 z150 z200
+ " speeddata
+ syn keyword rapidConstant v5 v10 v20 v30 v40 v50 v60 v80 v100 v150 v200 v300 v400 v500 v600 v800 v1000 v1500 v2000 v2500 v3000 v4000 v5000 v6000 v7000 vmax
+ syn keyword rapidConstant vrot1 vrot2 vrot5 vrot10 vrot20 vrot50 vrot100 vlin10 vlin20 vlin50 vlin100 vlin200 vlin500 vlin1000
+ " error code starting with ERR_
+ syn keyword rapidConstant ERR_ACC_TOO_LOW ERR_ACTIV_PROF ERR_ADDR_INUSE ERR_ALIASIO_DEF ERR_ALIASIO_TYPE ERR_ALRDYCNT ERR_ALRDY_MOVING ERR_AO_LIM ERR_ARGDUPCND ERR_ARGNAME ERR_ARGNOTPER ERR_ARGNOTVAR ERR_ARGVALERR ERR_ARRAY_SIZE ERR_AXIS_ACT ERR_AXIS_IND ERR_AXIS_MOVING ERR_AXIS_PAR
+ syn keyword rapidConstant ERR_BUSSTATE ERR_BWDLIMIT
+ syn keyword rapidConstant ERR_CALC_DIVZERO ERR_CALC_NEG ERR_CALC_OVERFLOW ERR_CALLIO_INTER ERR_CALLPROC ERR_CAM_BUSY ERR_CAM_COM_TIMEOUT ERR_CAM_GET_MISMATCH ERR_CAM_MAXTIME ERR_CAM_NO_MORE_DATA ERR_CAM_NO_PROGMODE ERR_CAM_NO_RUNMODE ERR_CAM_SET_MISMATCH
+ syn keyword rapidConstant ERR_CFG_ILLTYPE ERR_CFG_ILL_DOMAIN ERR_CFG_INTERNAL ERR_CFG_LIMIT ERR_CFG_NOTFND ERR_CFG_OUTOFBOUNDS ERR_CFG_WRITEFILE
+ syn keyword rapidConstant ERR_CNTNOTVAR
+ syn keyword rapidConstant ERR_CNV_CONNECT ERR_CNV_DROPPED ERR_CNV_NOT_ACT
+ syn keyword rapidConstant ERR_COLL_STOP
+ syn keyword rapidConstant ERR_COMM_EXT ERR_COMM_INIT ERR_COMM_INIT_FAILED
+ syn keyword rapidConstant ERR_CONC_MAX ERR_CONTACTL ERR_CSV_INDEX
+ syn keyword rapidConstant ERR_DA_UNKPROC ERR_DATA_RECV ERR_DEV_MAXTIME ERR_DIPLAG_LIM ERR_DIVZERO ERR_DROP_LOAD ERR_EXCRTYMAX ERR_EXECPHR
+ syn keyword rapidConstant ERR_FILEACC ERR_FILEEXIST ERR_FILEOPEN ERR_FILESIZE ERR_FILNOTFND
+ syn keyword rapidConstant ERR_FNCNORET ERR_FRAME ERR_FRICTUNE_FATAL ERR_GLUEFLOW ERR_GO_LIM
+ syn keyword rapidConstant ERR_HAND_FAILEDGRIPPOS ERR_HAND_FAILEDMOVEPOS ERR_HAND_FAILEDVACUUM ERR_HAND_NOTCALIBRATED
+ syn keyword rapidConstant ERR_ILLDIM ERR_ILLQUAT ERR_ILLRAISE
+ syn keyword rapidConstant ERR_INDCNV_ORDER ERR_INOISSAFE ERR_INOMAX ERR_INPAR_RDONLY ERR_INT_MAXVAL ERR_INT_NOTVAL ERR_INVDIM
+ syn keyword rapidConstant ERR_IODISABLE ERR_IODN_TIMEOUT ERR_IOENABLE ERR_IOERROR ERR_IPSDEVICE_UNKNOWN ERR_IPSILLEGAL_CH_OR_FAC ERR_IPS_PARAM
+ syn keyword rapidConstant ERR_ITMSRC_UNDEF ERR_LINKREF ERR_LOADED ERR_LOADID_FATAL ERR_LOADID_RETRY ERR_LOADNO_INUSE ERR_LOADNO_NOUSE
+ syn keyword rapidConstant ERR_MSG_PENDING ERR_MAXINTVAL ERR_MOC_CNV_REC_FILE_UNKNOWN ERR_MODULE ERR_MOD_NOT_LOADED ERR_MOD_NOTLOADED
+ syn keyword rapidConstant ERR_MT_ABORT ERR_MT_HOME ERR_MT_HOMERUN
+ syn keyword rapidConstant ERR_NEGARG ERR_NAME_INVALID ERR_NORUNUNIT ERR_NOTARR ERR_NOTEQDIM ERR_NOTINTVAL ERR_NOTPRES ERR_NOTSAVED ERR_NOT_MOVETASK ERR_NO_ALIASIO_DEF ERR_NO_SGUN ERR_NUM_LIMIT
+ syn keyword rapidConstant ERR_OUTOFBND ERR_OUTSIDE_REACH ERR_OVERFLOW ERR_PATH ERR_PATHDIST ERR_PATH_STOP ERR_PERSSUPSEARCH ERR_PID_MOVESTOP ERR_PID_RAISE_PP ERR_PPA_TIMEOUT ERR_PRGMEMFULL ERR_PROCSIGNAL_OFF ERR_PROGSTOP
+ syn keyword rapidConstant ERR_RANYBIN_CHK ERR_RANYBIN_EOF ERR_RCVDATA ERR_REFUNKDAT ERR_REFUNKFUN ERR_REFUNKPRC ERR_REFUNKTRP
+ syn keyword rapidConstant ERR_RMQ_DIM ERR_RMQ_FULL ERR_RMQ_INVALID ERR_RMQ_INVMSG ERR_RMQ_MSGSIZE ERR_RMQ_NAME ERR_RMQ_NOMSG ERR_RMQ_TIMEOUT ERR_RMQ_VALUE
+ syn keyword rapidConstant ERR_ROBLIMIT ERR_SC_WRITE
+ syn keyword rapidConstant ERR_SGUN_ESTOP ERR_SGUN_MOTOFF ERR_SGUN_NEGVAL ERR_SGUN_NOTACT ERR_SGUN_NOTINIT ERR_SGUN_NOTOPEN ERR_SGUN_NOTSYNC
+ syn keyword rapidConstant ERR_SIG_NAME ERR_SIGSUPSEARCH ERR_SIG_NOT_VALID
+ syn keyword rapidConstant ERR_SOCK_ADDR_INVALID ERR_SOCK_ADDR_INUSE ERR_SOCK_CLOSED ERR_SOCK_IS_BOUND ERR_SOCK_IS_CONN ERR_SOCK_NET_UNREACH ERR_SOCK_NOT_BOUND ERR_SOCK_NOT_CONN ERR_SOCK_TIMEOUT ERR_SOCK_UNSPEC
+ syn keyword rapidConstant ERR_SPEEDLIM_VALUE ERR_SPEED_REFRESH_LIM
+ syn keyword rapidConstant ERR_STARTMOVE ERR_STORE_PROF ERR_STRTOOLNG ERR_SYMBOL_TYPE ERR_SYM_ACCESS ERR_SYNCMOVEOFF ERR_SYNCMOVEON ERR_SYNTAX
+ syn keyword rapidConstant ERR_TASKNAME
+ syn keyword rapidConstant ERR_TP_DIBREAK ERR_TP_DOBREAK ERR_TP_MAXTIME ERR_TP_NO_CLIENT ERR_TP_PERSBOOLBREAK
+ syn keyword rapidConstant ERR_TRUSTLEVEL ERR_TXTNOEXIST ERR_UDPUC_COMM
+ syn keyword rapidConstant ERR_UISHOW_FATAL ERR_UISHOW_FULL ERR_UI_INITVALUE ERR_UI_MAXMIN ERR_UI_NOTINT
+ syn keyword rapidConstant ERR_UNIT_PAR ERR_UNKINO ERR_UNKPROC ERR_UNLOAD ERR_USE_PROF
+ syn keyword rapidConstant ERR_WAITSYNCTASK ERR_WAIT_MAX ERR_WAIT_MAXTIME ERR_WHL_SEARCH ERR_WHLSEARCH ERR_WOBJ_MOVING
+ " error codes starting with CORR_
+ syn keyword rapidConstant CORR_NOFREE CORR_NOOBJECT CORR_NOTCONN
+ " error codes starting with SEN_
+ syn keyword rapidConstant SEN_BUSY SEN_CAALARM SEN_CAMCHECK SEN_EXALARM SEN_GENERRO SEN_NO_MEAS SEN_NOREADY SEN_TEMP SEN_TIMEOUT SEN_UNKNOWN SEN_VALUE
+ " error codes starting with SYS_
+ syn keyword rapidConstant SYS_ERR_ARL_INPAR_RDONLY SYS_ERR_HW_SMB_WARNING_BATTERY_LOW SYS_ERR_MOC_CNV_REC_FILE_UNKNOWN SYS_ERR_MOC_CNV_REC_NOT_READY
+ " error codes starting with TC_
+ syn keyword rapidConstant TC_ERR_AIR TC_ERR_CLOSE_COV TC_ERR_DOUNLOCK TC_ERR_IO TC_ERR_IOCFG TC_ERR_LOCK TC_ERR_NOTOOL TC_ERR_OPEN_COV TC_ERR_POWER TC_ERR_PULOCK TC_ERR_ROBPOS TC_ERR_ROBPOS_DROP TC_ERR_ROBPOS_PICK TC_ERR_SERVO_TOOL TC_ERR_STANDNUM TC_ERR_TOOL TC_ERR_TOOLCFG TC_ERR_TOOLNUM TC_ERR_UNLOCK
+ " long jump error
+ syn keyword rapidConstant LONG_JMP_ALL_ERR
+ " Arc and Arc sensor
+ syn keyword rapidConstant AW_IGNI_ERR AW_EQIP_ERR AW_START_ERR AW_STOP_ERR AW_TRACK_ERR AW_TRACKCORR_ERR AW_TRACKSTA_ERR AW_USERSIG_ERR AW_WELD_ERR AW_WIRE_ERR
+ " EGM egmframetype
+ syn keyword rapidConstant EGM_FRAME_BASE EGM_FRAME_TOOL EGM_FRAME_WOBJ EGM_FRAME_WORLD EGM_FRAME_JOINT
+ " Events
+ syn keyword rapidConstant EE_START EE_CYCLE_START EE_PROC_START EE_PRE_PROD EE_CLOSE_JIG EE_INDEX EE_PRE_PART EE_POST_PART EE_OPEN_JIG EE_SERVICE EE_POST_PROD EE_ABORT EE_WAIT_ORDER EE_POST_PROC
+ syn keyword rapidConstant EE_POWERON EE_POWERON_OR_START EE_RESTART EE_START_OR_RESTART EE_STOP EE_QSTOP EE_STOP_OR_QSTOP EE_RESET EE_STEP EE_STEP_FWD EE_STEP_BCK EE_BEFORE_INIT EE_AFTER_INIT EE_BEFORE_PROD EE_AFTER_PROD EE_BEFORE_MENU EE_AFTER_MENU
+ syn keyword rapidConstant EE_ERROR EE_HOMERUN EE_PROG_END EE_AFTER_PROG_NUMBER EE_PROGNO_UNKNOWN EE_PROD_UNKNOWN EE_MSG_WRITTEN EE_MSG_ACKNOWLEDGED EE_AFTER_PART EE_BEFORE_HOMERUN EE_AFTER_HOMERUN EE_BLOCKED
+ " motion process mode
+ syn keyword rapidConstant OPTIMAL_CYCLE_TIME_MODE LOW_SPEED_ACCURACY_MODE LOW_SPEED_STIFF_MODE ACCURACY_MODE MPM_USER_MODE_1 MPM_USER_MODE_2 MPM_USER_MODE_3 MPM_USER_MODE_4
+ " inttypes
+ syn keyword rapidConstant USINT UINT UDINT ULINT SINT INT DINT LINT
+ " opcalc
+ syn keyword rapidConstant OpAdd OpSub OpMult OpDiv OpMod
+ " triggmode
+ syn keyword rapidConstant TRIGG_MODE1 TRIGG_MODE2 TRIGG_MODE3
+ " tunetype
+ syn keyword rapidConstant TUNE_DF TUNE_KP TUNE_KV TUNE_TI TUNE_FRIC_LEV TUNE_FRIC_RAMP TUNE_DG TUNE_DH TUNE_DI TUNE_DK TUNE_DL
+ " cellopmode
+ syn keyword rapidConstant OP_NO_ROBOT OP_SERVICE OP_PRODUCTION
+ " execution mode
+ syn keyword rapidConstant CT_CONTINUOUS CT_COUNT_CYCLES CT_COUNT_CYC_ACTION CT_PERIODICAL
+ " Force Control
+ syn keyword rapidConstant FC_REFFRAME_TOOL FC_REFFRAME_WOBJ FC_LIN_X FC_LIN_Y FC_LIN_Z FC_ROT_X FC_ROT_Y FC_ROT_Z FC_SPEED_RATIO_MIN FC_NO_OF_SPEED_LEVELS
+ " tpnum
+ syn keyword rapidConstant TP_LATEST TP_PROGRAM TP_SCREENVIEWER
+ " paridvalidnum
+ syn keyword rapidConstant ROB_LOAD_VAL ROB_NOT_LOAD_VAL ROB_LM1_LOAD_VAL
+ " paridnum
+ syn keyword rapidConstant TOOL_LOAD_ID PAY_LOAD_ID IRBP_K IRBP_L IRBP_C IRBP_C_INDEX IRBP_T IRBP_R IRBP_A IRBP_B IRBP_D
+ " loadidnum
+ syn keyword rapidConstant MASS_KNOWN MASS_WITH_AX3
+ " sensorstate
+ syn keyword rapidConstant STATE_ERROR STATE_UNDEFINED STATE_CONNECTED STATE_OPERATING STATE_CLOSED
+ " signalorigin
+ syn keyword rapidConstant SIGORIG_NONE SIGORIG_CFG SIGORIG_ALIAS
+ " aiotrigg
+ syn keyword rapidConstant AIO_ABOVE_HIGH AIO_BELOW_HIGH AIO_ABOVE_LOW AIO_BELOW_LOW AIO_BETWEEN AIO_OUTSIDE AIO_ALWAYS
+ " socketstatus
+ syn keyword rapidConstant SOCKET_CREATED SOCKET_CONNECTED SOCKET_BOUND SOCKET_LISTENING SOCKET_CLOSED
+ " symnum of OpMode()
+ syn keyword rapidConstant OP_UNDEF OP_AUTO OP_MAN_PROG OP_MAN_TEST
+ " symnum of RunMode()
+ syn keyword rapidConstant RUN_UNDEF RUN_CONT_CYCLE RUN_INSTR_FWD RUN_INSTR_BWD RUN_SIM RUN_STEP_MOVE
+ " event_type of EventType()
+ syn keyword rapidConstant EVENT_NONE EVENT_POWERON EVENT_START EVENT_STOP EVENT_QSTOP EVENT_RESTART EVENT_RESET EVENT_STEP
+ " handler_type of ExecHandler()
+ syn keyword rapidConstant HANDLER_NONE HANDLER_BWD HANDLER_ERR HANDLER_UNDO
+ " event_level of ExecLevel()
+ syn keyword rapidConstant LEVEL_NORMAL LEVEL_TRAP LEVEL_SERVICE
+ " signalorigin of GetSignalOrigin()
+ syn keyword rapidConstant SIGORIG_NONE SIGORIG_CFG SIGORIG_ALIAS
+ " opnum
+ syn keyword rapidConstant LT LTEQ EQ NOTEQ GT GTEQ
+ " icondata
+ syn keyword rapidConstant iconNone iconInfo iconWarning iconError
+ " buttondata
+ syn keyword rapidConstant btnNone btnOK btnAbrtRtryIgn btnOKCancel btnRetryCancel btnYesNo btnYesNoCancel
+ " btnres
+ syn keyword rapidConstant resUnkwn resOK resAbort resRetry resIgnore resCancel resYes resNo
+ " cfgdomain
+ syn keyword rapidConstant ALL_DOMAINS EIO_DOMAIN MMC_DOMAIN MOC_DOMAIN PROC_DOMAIN SIO_DOMAIN SYS_DOMAIN
+ " errdomain
+ syn keyword rapidConstant COMMON_ERR OP_STATE SYSTEM_ERR HARDWARE_ERR PROGRAM_ERR MOTION_ERR OPERATOR_ERR IO_COM_ERR USER_DEF_ERR SAFETY_ERR PROCESS_ERR CFG_ERR OPTION_PROD_ERR ARCWELD_ERR SPOTWELD_ERR PAINT_ERR PICKWARE_ERR
+ " errtype
+ syn keyword rapidConstant TYPE_ALL TYPE_ERR TYPE_STATE TYPE_WARN
+ " Sensor Interface
+ syn keyword rapidConstant LTAPP__AGE LTAPP__ANGLE LTAPP__AREA LTAPP__CAMCHECK LTAPP__GAP LTAPP__JOINT_NO LTAPP__LASER_OFF LTAPP__MISMATCH LTAPP__PING LTAPP__POWER_UP LTAPP__RESET LTAPP__STEPDIR LTAPP__THICKNESS LTAPP__UNIT
+ syn keyword rapidConstant LTAPP__X LTAPP__Y LTAPP__Z LTAPP__APM_P1 LTAPP__APM_P2 LTAPP__APM_P3 LTAPP__APM_P4 LTAPP__APM_P5 LTAPP__APM_P6 LTAPP__ROT_Y LTAPP__ROT_Z LTAPP__X0 LTAPP__Y0 LTAPP__Z0 LTAPP__X1 LTAPP__Y1 LTAPP__Z1 LTAPP__X2 LTAPP__Y2 LTAPP__Z2
+ " iounit_state
+ syn keyword rapidConstant IOUNIT_LOG_STATE_DISABLED IOUNIT_LOG_STATE_ENABLED IOUNIT_PHYS_STATE_DEACTIVATED IOUNIT_PHYS_STATE_RUNNING IOUNIT_PHYS_STATE_ERROR IOUNIT_PHYS_STATE_UNCONNECTED IOUNIT_PHYS_STATE_UNCONFIGURED IOUNIT_PHYS_STATE_STARTUP IOUNIT_PHYS_STATE_INIT IOUNIT_RUNNING IOUNIT_RUNERROR IOUNIT_DISABLE IOUNIT_OTHERERR
+ " busstate
+ syn keyword rapidConstant IOBUS_LOG_STATE_STARTED IOBUS_LOG_STATE_STOPPED IOBUS_PHYS_STATE_ERROR IOBUS_PHYS_STATE_HALTED IOBUS_PHYS_STATE_INIT IOBUS_PHYS_STATE_RUNNING IOBUS_PHYS_STATE_STARTUP
+ syn keyword rapidConstant BUSSTATE_ERROR BUSSTATE_HALTED BUSSTATE_INIT BUSSTATE_RUN BUSSTATE_STARTUP
+ " SoftMove
+ syn keyword rapidConstant CSS_POSX CSS_NEGX CSS_POSY CSS_NEGY CSS_POSZ CSS_NEGZ CSS_X CSS_Y CSS_Z CSS_XY CSS_XZ CSS_YZ CSS_XYZ CSS_XYRZ CSS_ARM_ANGLE CSS_REFFRAME_TOOL CSS_REFFRAME_WOBJ
+ " tsp_status
+ syn keyword rapidConstant TSP_STATUS_NOT_NORMAL_TASK TSP_STATUS_DEACT TSP_STATUS_DEACT_SERV_ROUT TSP_STATUS_ACT TSP_UNCHECKED_RUN_SERV_ROUT TSP_NORMAL_UNCHECKED TSP_STATIC_UNCHECKED TSP_SEMISTATIC_UNCHECKED TSP_NORMAL_CHECKED TSP_STATIC_CHECKED TSP_SEMISTATIC_CHECKED
+ " IRC5P (paint controller)
+ syn keyword rapidConstant PW_EQUIP_ERR
+ " Bulls Eye
+ syn keyword rapidConstant BESuccess BENoOverwrite BENoNameMatch BENoBEDataMod BEArrayFull BEToolNotFound BEInvalidSignal BEAliasSet BERangeLimFail BERangeSingFail BERangeTiltFail BEScanPlaneErr BEBFrameNotRead BEScanRadZero BEHeightSrchErr BEBeamNotFound BEBeamSpinErr BESrchErrInBeam BESrchErrNoDet BENumOfScansErr BEDiaZeroOrLess BESliceCountErr BEGetNewTcpMax BEBeamOriFail BEGetTcpDelErr BERefPosSetErr BERefToolSetErr BERefBeamSetErr BEBFrameDefErr BESetupAlready BERefResetErr BESetupFailed BEToolNotSet BEStartChanged BEBeamMoveErr BECheckTcp BECheckErr BESkipUpdate BEStrtningErr BEAllNotSet BEQuikRefNotDef BEConvergErr BEInstFwdErr BEGetGantryErr BEUnknownErr
+ " Continuous Application Platform constants
+ syn keyword rapidConstant CAP_START START_PRE PRE_STARTED START_MAIN MAIN_STARTED STOP_WEAVESTART WEAVESTART_REGAIN MOTION_DELAY STARTSPEED_TIME MAIN_MOTION MOVE_STARTED RESTART NEW_INSTR AT_POINT AT_RESTARTPOINT LAST_SEGMENT PROCESS_END_POINT END_MAIN MAIN_ENDED PATH_END_POINT PROCESS_ENDED END_POST1 POST1_ENDED END_POST2 POST2_ENDED CAP_STOP CAP_PF_RESTART EQUIDIST AT_ERRORPOINT FLY_START FLY_END LAST_INSTR_ENDED END_PRE PRE_ENDED START_POST1 POST1_STARTED START_POST2 POST2_STARTED
+ syn keyword rapidConstant CAP_PRE_ERR CAP_PRESTART_ERR CAP_END_PRE_ERR CAP_START_ERR CAP_MAIN_ERR CAP_ENDMAIN_ERR CAP_START_POST1_ERR CAP_POST1_ERR CAP_POST1END_ERR CAP_START_POST2_ERR CAP_POST2_ERR CAP_POST2END_ERR CAP_TRACK_ERR CAP_TRACKSTA_ERR CAP_TRACKCOR_ERR CAP_TRACKCOM_ERR CAP_TRACKPFR_ERR CAP_SEN_NO_MEAS CAP_SEN_NOREADY CAP_SEN_GENERRO CAP_SEN_BUSY CAP_SEN_UNKNOWN CAP_SEN_ILLEGAL CAP_SEN_EXALARM CAP_SEN_CAALARM CAP_SEN_TEMP CAP_SEN_VALUE CAP_SEN_CAMCHECK CAP_SEN_TIMEOUT
+ " Machine Tending grppos
+ syn keyword rapidConstant gsOpen gsVacuumOff gsBackward gsClose gsVacuumOn gsForward gsReset
+ " Machine Tending grpaction
+ syn keyword rapidConstant gaSetAndCheck gaSet gaCheck gaCheckClose gaCheckClose
+ " Palletizing PowerPac
+ syn keyword rapidConstant PM_ERR_AXLIM PM_ERR_CALCCONF PM_ERR_FLOW_NOT_FOUND PM_ERR_INVALID_FLOW_STOP_OPTION PM_ERR_JOB_EMPTY PM_ERR_LIM_VALUE PM_ERR_NO_RUNNING_PROJECT PM_ERR_NO_TASK PM_ERR_NOT_VALID_RECOVER_ACTION PM_ERR_OPERATION_LOST PM_ERR_PALLET_EMPTY PM_ERR_PALLET_REDUCED PM_ERR_PART_VAL PM_ERR_PROJ_NOT_FOUND PM_ERR_REDO_LAST_PICK_REJECTED PM_ERR_TIMEOUT PM_ERR_WA_NOT_FOUND PM_ERR_WOBJ PM_ERR_WORKAREA_EXPECTED PM_ERR_WRONG_FLOW_STATE
+ syn keyword rapidConstant PM_ACK PM_NACK PM_LOST PM_RECOVER_CONTINUE_OPERATION PM_RECOVER_REDO_LAYER PM_RECOVER_NEXT_PALLET PM_RECOVER_REDO_LAST_PICK PM_FLOW_ERROR PM_FLOW_FINISH_CYCLE PM_FLOW_FINISH_LAYER PM_FLOW_FINISH_PALLET PM_FLOW_RUNNING PM_FLOW_STOP_IMMEDIATELY PM_FLOW_STOPPED PM_FLOW_STOPPING_AFTER_CYCLE PM_FLOW_STOPPING_AFTER_LAYER PM_FLOW_STOPPING_AFTER_PALLET PM_APPROACH_POS PM_DEPART_POS PM_TARGET_POS PM_EVENT_PROC PM_EVENT_DO PM_EVENT_GO PM_MOVE_JOINT PM_MOVE_LIN PM_SEARCH_X PM_SEARCH_Y PM_SEARCH_Z PM_SING_AREA_OFF PM_SING_AREA_WRI PM_STOP_NOT_USED PM_STOP PM_PSTOP PM_SSTOP PM_PROJECT_STOPPED PM_PROJECT_STOPPING PM_PROJECT_STARTING PM_PROJECT_RUNNING PM_PROJECT_ERROR
+ syn keyword rapidConstant MaxToolAngle MinToolAngle
+ " other constants
+ syn keyword rapidConstant GAP_SERVICE_TYPE GAP_SETUP_TYPE GAP_STATE_IDLE GAP_STATE_PART GAP_STATE_SERV GAP_STATE_SETUP GAP_STATE_UNKN GAP_TASK_NAME GAP_TASK_NO GAP_SHOW_ALWAYS GAP_SHOW_NEVER GAP_SHOW_SAFE GAP_SHOW_SERVICE
+ syn keyword rapidConstant EOF EOF_BIN EOF_NUM
+ syn keyword rapidConstant END_OF_LIST WAIT_MAX
+ syn keyword rapidErrNo ERRNO
+ syn keyword rapidIntNo INTNO
+ " VW Konzernstandard VWKS_1.07.02
+ syn keyword rapidIntNo KG_UNDEFINIERT KG_LETZTEPOS KG_GREIFPOS KG_ZWISCHENPOS KG_TOOLINFO KG_GREIFPOSKORR
+ syn keyword rapidIntNo BA1 BA2
+ syn keyword rapidIntNo SetupXY SetupZ KorrekturXY KorrekturZ
+ if g:rapidGroupName
+ highlight default link rapidConstant Sysvars
+ highlight default link rapidErrNo Sysvars
+ highlight default link rapidIntNo Sysvars
+ endif
+ " }}} ERRNO Constants
+
+ " Error {{{
+ if get(g:,'rapidShowError',1)
+ "
+ " vars or funcs >32 chars are not possible in rapid. a234567890123456789012345
+ syn match rapidErrorIdentifierNameTooLong /\k\{33,}/ containedin=rapidFunction,rapidNames,rapidLabel
+ highlight default link rapidErrorIdentifierNameTooLong Error
+ "
+ " a == b + 1
+ syn match rapidErrorShouldBeColonEqual /\c\v%(^\s*%(%(TASK\s+|LOCAL\s+)?%(VAR|PERS|CONST)\s+\k+\s+)?\k+%(\k|[.{},*/+-])*\s*)@<=\=/
+ highlight default link rapidErrorShouldBeColonEqual Error
+ "
+ " WaitUntil a==b
+ syn match rapidErrorShouldBeEqual /\c\v%(^\s*%(Return|WaitUntil|while)>[^!\\]+[^!<>])@<=%(\=|:)\=/
+ syn match rapidErrorShouldBeEqual /\c\v%(^\s*%(if|elseif)>[^!\\]+[^!<>])@<=%(\=|:)\=\ze[^!\\]+<then>/
+ highlight default link rapidErrorShouldBeEqual Error
+ "
+ " WaitUntil a=>b
+ syn match rapidErrorShoudBeLessOrGreaterEqual /\c\v%(^\s*%(Return|WaitUntil|if|elseif|while)>[^!]+[^!<>])@<=\=[><]/
+ highlight default link rapidErrorShoudBeLessOrGreaterEqual Error
+ "
+ " WaitUntil a><b
+ syn match rapidErrorShouldBeLessGreater /\c\v%(^\s*%(Return|WaitUntil|if|elseif|while)[^!]+)@<=\>\s*\</
+ highlight default link rapidErrorShouldBeLessGreater Error
+ "
+ " if (a==5) (b==6)
+ syn match rapidErrorMissingOperator /\c\v%(^\s*%(Return|WaitUntil|if|elseif|while)[^!]+[^!])@<=\)\s*\(/
+ highlight default link rapidErrorMissingOperator Error
+ "
+ " "for" missing "from"
+ syn match rapidErrorMissingFrom /\c\v^\s*for\s+%([[:upper:][:lower:]]%(\k|[.{},*/+-])*\s+from)@!\S+\s+\S+/
+ highlight default link rapidErrorMissingFrom Error
+ "
+ "
+ endif
+ " }}} Error
+
+" }}}
+endif
+
+" common Error {{{
+if get(g:,'rapidShowError',1)
+ "
+ " This error must be defined after rapidString
+ " string too long
+ " syn match rapidErrorStringTooLong /\v%("%(""|\\\\|\\\x\x|[^"\\]){80})@240<=%([^"]|"{2})+/ contained contains=rapidStringDoubleQuote,rapidEscapedBackSlash,rapidCharCode,rapidErrorSingleBackslash
+ highlight default link rapidErrorStringTooLong Error
+ "
+endif
+
+" }}} Error
+
+" Finish {{{
+let &cpo = s:keepcpo
+unlet s:keepcpo
+
+let b:current_syntax = "rapid"
+" }}} Finish
+
+" vim:sw=2 sts=2 et fdm=marker
diff --git a/runtime/syntax/rmd.vim b/runtime/syntax/rmd.vim
index cccd4110f5..93343dd729 100644
--- a/runtime/syntax/rmd.vim
+++ b/runtime/syntax/rmd.vim
@@ -1,7 +1,7 @@
-" markdown Text with R statements
-" Language: markdown with R code chunks
+" Language: Markdown with chunks of R, Python and other languages
+" Maintainer: Jakson Aquino <jalvesaq@gmail.com>
" Homepage: https://github.com/jalvesaq/R-Vim-runtime
-" Last Change: Wed Apr 21, 2021 09:55AM
+" Last Change: Wed May 17, 2023 06:34AM
"
" For highlighting pandoc extensions to markdown like citations and TeX and
" many other advanced features like folding of markdown sections, it is
@@ -13,126 +13,218 @@ if exists("b:current_syntax")
finish
endif
+let s:cpo_save = &cpo
+set cpo&vim
+
" Highlight the header of the chunks as R code
let g:rmd_syn_hl_chunk = get(g:, 'rmd_syn_hl_chunk', 0)
" Pandoc-syntax has more features, but it is slower.
" https://github.com/vim-pandoc/vim-pandoc-syntax
-let g:pandoc#syntax#codeblocks#embeds#langs = get(g:, 'pandoc#syntax#codeblocks#embeds#langs', ['r'])
+
+" Don't waste time loading syntax that will be discarded:
+let s:save_pandoc_lngs = get(g:, 'pandoc#syntax#codeblocks#embeds#langs', [])
+let g:pandoc#syntax#codeblocks#embeds#langs = []
+
+let g:rmd_dynamic_fenced_languages = get(g:, 'rmd_dynamic_fenced_languages', v:true)
+
+" Step_1: Source pandoc.vim if it is installed:
runtime syntax/pandoc.vim
if exists("b:current_syntax")
+ if hlexists('pandocDelimitedCodeBlock')
+ syn clear pandocDelimitedCodeBlock
+ endif
+
+ if len(s:save_pandoc_lngs) > 0 && !exists('g:rmd_fenced_languages')
+ let g:rmd_fenced_languages = deepcopy(s:save_pandoc_lngs)
+ endif
+
" Recognize inline R code
- syn region rmdrInline matchgroup=rmdInlineDelim start="`r " end="`" contains=@R containedin=pandocLaTeXRegion,yamlFlowString keepend
- hi def link rmdInlineDelim Delimiter
-
- " Fix recognition of language chunks (code adapted from pandoc, 2021-03-28)
- " Knitr requires braces in the block's header
- for s:lng in g:pandoc#syntax#codeblocks#embeds#langs
- let s:nm = matchstr(s:lng, '^[^=]*')
- exe 'syn clear pandocDelimitedCodeBlock_'.s:nm
- exe 'syn clear pandocDelimitedCodeBlockinBlockQuote_'.s:nm
- if g:rmd_syn_hl_chunk
- exe 'syn region rmd'.s:nm.'ChunkDelim matchgroup=rmdCodeDelim start="^\s*```\s*{\s*'.s:nm.'\>" matchgroup=rmdCodeDelim end="}$" keepend containedin=rmd'.s:nm.'Chunk contains=@R'
- exe 'syn region rmd'.s:nm.'Chunk start="^\s*```\s*{\s*'.s:nm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=rmd'.s:nm.'ChunkDelim,@'.toupper(s:nm)
+ syn region rmdrInline matchgroup=rmdInlineDelim start="`r " end="`" contains=@Rmdr containedin=pandocLaTeXRegion,yamlFlowString keepend
+else
+ " Step_2: Source markdown.vim if pandoc.vim is not installed
+
+ " Configuration if not using pandoc syntax:
+ " Add syntax highlighting of YAML header
+ let g:rmd_syn_hl_yaml = get(g:, 'rmd_syn_hl_yaml', 1)
+ " Add syntax highlighting of citation keys
+ let g:rmd_syn_hl_citations = get(g:, 'rmd_syn_hl_citations', 1)
+
+ " R chunks will not be highlighted by syntax/markdown because their headers
+ " follow a non standard pattern: "```{lang" instead of "^```lang".
+ " Make a copy of g:markdown_fenced_languages to highlight the chunks later:
+ if exists('g:markdown_fenced_languages') && !exists('g:rmd_fenced_languages')
+ let g:rmd_fenced_languages = deepcopy(g:markdown_fenced_languages)
+ endif
+
+ if exists('g:markdown_fenced_languages') && len(g:markdown_fenced_languages) > 0
+ let s:save_mfl = deepcopy(g:markdown_fenced_languages)
+ endif
+ " Don't waste time loading syntax that will be discarded:
+ let g:markdown_fenced_languages = []
+ runtime syntax/markdown.vim
+ if exists('s:save_mfl') > 0
+ let g:markdown_fenced_languages = deepcopy(s:save_mfl)
+ unlet s:save_mfl
+ endif
+ syn region rmdrInline matchgroup=rmdInlineDelim start="`r " end="`" contains=@Rmdr keepend
+
+ " Step_2a: Add highlighting for both YAML and citations which are pandoc
+ " specific, but also used in Rmd files
+
+ " You don't need this if either your markdown/syntax.vim already highlights
+ " the YAML header or you are writing standard markdown
+ if g:rmd_syn_hl_yaml
+ " Basic highlighting of YAML header
+ syn match rmdYamlFieldTtl /^\s*\zs\w\%(-\|\w\)*\ze:/ contained
+ syn match rmdYamlFieldTtl /^\s*-\s*\zs\w\%(-\|\w\)*\ze:/ contained
+ syn region yamlFlowString matchgroup=yamlFlowStringDelimiter start='"' skip='\\"' end='"' contains=yamlEscape,rmdrInline contained
+ syn region yamlFlowString matchgroup=yamlFlowStringDelimiter start="'" skip="''" end="'" contains=yamlSingleEscape,rmdrInline contained
+ syn match yamlEscape contained '\\\%([\\"abefnrtv\^0_ NLP\n]\|x\x\x\|u\x\{4}\|U\x\{8}\)'
+ syn match yamlSingleEscape contained "''"
+ syn match yamlComment /#.*/ contained
+ " A second colon is a syntax error, unless within a string or following !expr
+ syn match yamlColonError /:\s*[^'^"^!]*:/ contained
+ if &filetype == 'quarto'
+ syn region pandocYAMLHeader matchgroup=rmdYamlBlockDelim start=/\%(\%^\|\_^\s*\n\)\@<=\_^-\{3}\ze\n.\+/ end=/^---$/ keepend contains=rmdYamlFieldTtl,yamlFlowString,yamlComment,yamlColonError
else
- exe 'syn region rmd'.s:nm.'Chunk matchgroup=rmdCodeDelim start="^\s*```\s*{\s*'.s:nm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=@'.toupper(s:nm)
+ syn region pandocYAMLHeader matchgroup=rmdYamlBlockDelim start=/\%(\%^\|\_^\s*\n\)\@<=\_^-\{3}\ze\n.\+/ end=/^\([-.]\)\1\{2}$/ keepend contains=rmdYamlFieldTtl,yamlFlowString,yamlComment,yamlColonError
endif
- endfor
- unlet s:lng
- unlet s:nm
- hi def link rmdInlineDelim Delimiter
- hi def link rmdCodeDelim Delimiter
- let b:current_syntax = "rmd"
- finish
-endif
-
-" Configuration if not using pandoc syntax:
-" Add syntax highlighting of YAML header
-let g:rmd_syn_hl_yaml = get(g:, 'rmd_syn_hl_yaml', 1)
-" Add syntax highlighting of citation keys
-let g:rmd_syn_hl_citations = get(g:, 'rmd_syn_hl_citations', 1)
+ hi def link rmdYamlBlockDelim Delimiter
+ hi def link rmdYamlFieldTtl Identifier
+ hi def link yamlFlowString String
+ hi def link yamlComment Comment
+ hi def link yamlColonError Error
+ endif
-let s:cpo_save = &cpo
-set cpo&vim
+ " Conceal char for manual line break
+ if &encoding ==# 'utf-8'
+ syn match rmdNewLine ' $' conceal cchar=↵
+ endif
-" R chunks will not be highlighted by syntax/markdown because their headers
-" follow a non standard pattern: "```{lang" instead of "^```lang".
-" Make a copy of g:markdown_fenced_languages to highlight the chunks later:
-if exists('g:markdown_fenced_languages')
- if !exists('g:rmd_fenced_languages')
- let g:rmd_fenced_languages = deepcopy(g:markdown_fenced_languages)
- let g:markdown_fenced_languages = []
+ " You don't need this if either your markdown/syntax.vim already highlights
+ " citations or you are writing standard markdown
+ if g:rmd_syn_hl_citations
+ " From vim-pandoc-syntax
+ " parenthetical citations
+ syn match pandocPCite /\^\@<!\[[^\[\]]\{-}-\{0,1}@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*.\{-}\]/ contains=pandocEmphasis,pandocStrong,pandocLatex,pandocCiteKey,@Spell,pandocAmpersandEscape display
+ " in-text citations with location
+ syn match pandocICite /@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*\s\[.\{-1,}\]/ contains=pandocCiteKey,@Spell display
+ " cite keys
+ syn match pandocCiteKey /\(-\=@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*\)/ containedin=pandocPCite,pandocICite contains=@NoSpell display
+ syn match pandocCiteAnchor /[-@]/ contained containedin=pandocCiteKey display
+ syn match pandocCiteLocator /[\[\]]/ contained containedin=pandocPCite,pandocICite
+ hi def link pandocPCite Operator
+ hi def link pandocICite Operator
+ hi def link pandocCiteKey Label
+ hi def link pandocCiteAnchor Operator
+ hi def link pandocCiteLocator Operator
endif
-else
- let g:rmd_fenced_languages = ['r']
endif
-runtime syntax/markdown.vim
+" Step_3: Highlight code blocks.
+
+syn region rmdCodeBlock matchgroup=rmdCodeDelim start="^\s*```\s*{.*}$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend
+syn region rmdCodeBlock matchgroup=rmdCodeDelim start="^\s*```.+$" matchgroup=rmdCodeDelim end="^```$" keepend
+hi link rmdCodeBlock Special
" Now highlight chunks:
-for s:type in g:rmd_fenced_languages
- if s:type =~ '='
- let s:ft = substitute(s:type, '.*=', '', '')
- let s:nm = substitute(s:type, '=.*', '', '')
+syn region knitrBodyOptions start='^#| ' end='$' contained containedin=rComment,pythonComment contains=knitrBodyVar,knitrBodyValue transparent
+syn match knitrBodyValue ': \zs.*\ze$' keepend contained containedin=knitrBodyOptions
+syn match knitrBodyVar '| \zs\S\{-}\ze:' contained containedin=knitrBodyOptions
+
+let g:rmd_fenced_languages = get(g:, 'rmd_fenced_languages', ['r'])
+
+let s:no_syntax_vim = []
+function IncludeLanguage(lng)
+ if a:lng =~ '='
+ let ftpy = substitute(a:lng, '.*=', '', '')
+ let lnm = substitute(a:lng, '=.*', '', '')
else
- let s:ft = s:type
- let s:nm = s:type
+ let ftpy = a:lng
+ let lnm = a:lng
endif
- unlet! b:current_syntax
- exe 'syn include @Rmd'.s:nm.' syntax/'.s:ft.'.vim'
- if g:rmd_syn_hl_chunk
- exe 'syn region rmd'.s:nm.'ChunkDelim matchgroup=rmdCodeDelim start="^\s*```\s*{\s*'.s:nm.'\>" matchgroup=rmdCodeDelim end="}$" keepend containedin=rmd'.s:nm.'Chunk contains=@Rmdr'
- exe 'syn region rmd'.s:nm.'Chunk start="^\s*```\s*{\s*'.s:nm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=rmd'.s:nm.'ChunkDelim,@Rmd'.s:nm
+ if index(s:no_syntax_vim, ftpy) >= 0
+ return
+ endif
+ if len(globpath(&rtp, "syntax/" . ftpy . ".vim"))
+ unlet! b:current_syntax
+ exe 'syn include @Rmd'.lnm.' syntax/'.ftpy.'.vim'
+ let b:current_syntax = "rmd"
+ if g:rmd_syn_hl_chunk
+ exe 'syn match knitrChunkDelim /```\s*{\s*'.lnm.'/ contained containedin=knitrChunkBrace contains=knitrChunkLabel'
+ exe 'syn match knitrChunkLabelDelim /```\s*{\s*'.lnm.',\=\s*[-[:alnum:]]\{-1,}[,}]/ contained containedin=knitrChunkBrace'
+ syn match knitrChunkDelim /}\s*$/ contained containedin=knitrChunkBrace
+ exe 'syn match knitrChunkBrace /```\s*{\s*'.lnm.'.*$/ contained containedin=rmd'.lnm.'Chunk contains=knitrChunkDelim,knitrChunkLabelDelim,@Rmd'.lnm
+ exe 'syn region rmd'.lnm.'Chunk start="^\s*```\s*{\s*=\?'.lnm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=knitrChunkBrace,@Rmd'.lnm
+
+ hi link knitrChunkLabel Identifier
+ hi link knitrChunkDelim rmdCodeDelim
+ hi link knitrChunkLabelDelim rmdCodeDelim
+ else
+ exe 'syn region rmd'.lnm.'Chunk matchgroup=rmdCodeDelim start="^\s*```\s*{\s*=\?'.lnm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=@Rmd'.lnm
+ endif
else
- exe 'syn region rmd'.s:nm.'Chunk matchgroup=rmdCodeDelim start="^\s*```\s*{\s*'.s:nm.'\>.*$" matchgroup=rmdCodeDelim end="^\s*```\ze\s*$" keepend contains=@Rmd'.s:nm
+ " Avoid the cost of running globpath() whenever the buffer is saved
+ let s:no_syntax_vim += [ftpy]
endif
+endfunction
+
+for s:type in g:rmd_fenced_languages
+ call IncludeLanguage(s:type)
endfor
unlet! s:type
-" Recognize inline R code
-syn region rmdrInline matchgroup=rmdInlineDelim start="`r " end="`" contains=@Rmdr keepend
+function CheckRmdFencedLanguages()
+ let alines = getline(1, '$')
+ call filter(alines, "v:val =~ '^```{'")
+ call map(alines, "substitute(v:val, '^```{', '', '')")
+ call map(alines, "substitute(v:val, '\\W.*', '', '')")
+ for tpy in alines
+ if len(tpy) == 0
+ continue
+ endif
+ let has_lng = 0
+ for lng in g:rmd_fenced_languages
+ if tpy == lng
+ let has_lng = 1
+ continue
+ endif
+ endfor
+ if has_lng == 0
+ let g:rmd_fenced_languages += [tpy]
+ call IncludeLanguage(tpy)
+ endif
+ endfor
+endfunction
+
+if g:rmd_dynamic_fenced_languages
+ call CheckRmdFencedLanguages()
+ augroup RmdSyntax
+ autocmd!
+ autocmd BufWritePost <buffer> call CheckRmdFencedLanguages()
+ augroup END
+endif
+
+" Step_4: Highlight code recognized by pandoc but not defined in pandoc.vim yet:
+syn match pandocDivBegin '^:::\+ {.\{-}}' contains=pandocHeaderAttr
+syn match pandocDivEnd '^:::\+$'
+hi def link knitrBodyVar PreProc
+hi def link knitrBodyValue Constant
+hi def link knitrBodyOptions rComment
+hi def link pandocDivBegin Delimiter
+hi def link pandocDivEnd Delimiter
hi def link rmdInlineDelim Delimiter
hi def link rmdCodeDelim Delimiter
-" You don't need this if either your markdown/syntax.vim already highlights
-" the YAML header or you are writing standard markdown
-if g:rmd_syn_hl_yaml
- " Minimum highlighting of yaml header
- syn match rmdYamlFieldTtl /^\s*\zs\w*\ze:/ contained
- syn match rmdYamlFieldTtl /^\s*-\s*\zs\w*\ze:/ contained
- syn region yamlFlowString matchgroup=yamlFlowStringDelimiter start='"' skip='\\"' end='"' contains=yamlEscape,rmdrInline contained
- syn region yamlFlowString matchgroup=yamlFlowStringDelimiter start="'" skip="''" end="'" contains=yamlSingleEscape,rmdrInline contained
- syn match yamlEscape contained '\\\%([\\"abefnrtv\^0_ NLP\n]\|x\x\x\|u\x\{4}\|U\x\{8}\)'
- syn match yamlSingleEscape contained "''"
- syn region pandocYAMLHeader matchgroup=rmdYamlBlockDelim start=/\%(\%^\|\_^\s*\n\)\@<=\_^-\{3}\ze\n.\+/ end=/^\([-.]\)\1\{2}$/ keepend contains=rmdYamlFieldTtl,yamlFlowString
- hi def link rmdYamlBlockDelim Delimiter
- hi def link rmdYamlFieldTtl Identifier
- hi def link yamlFlowString String
-endif
-
-" You don't need this if either your markdown/syntax.vim already highlights
-" citations or you are writing standard markdown
-if g:rmd_syn_hl_citations
- " From vim-pandoc-syntax
- " parenthetical citations
- syn match pandocPCite /\^\@<!\[[^\[\]]\{-}-\{0,1}@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*.\{-}\]/ contains=pandocEmphasis,pandocStrong,pandocLatex,pandocCiteKey,@Spell,pandocAmpersandEscape display
- " in-text citations with location
- syn match pandocICite /@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*\s\[.\{-1,}\]/ contains=pandocCiteKey,@Spell display
- " cite keys
- syn match pandocCiteKey /\(-\=@[[:alnum:]_][[:alnum:]à-öø-ÿÀ-ÖØ-ß_:.#$%&\-+?<>~\/]*\)/ containedin=pandocPCite,pandocICite contains=@NoSpell display
- syn match pandocCiteAnchor /[-@]/ contained containedin=pandocCiteKey display
- syn match pandocCiteLocator /[\[\]]/ contained containedin=pandocPCite,pandocICite
- hi def link pandocPCite Operator
- hi def link pandocICite Operator
- hi def link pandocCiteKey Label
- hi def link pandocCiteAnchor Operator
- hi def link pandocCiteLocator Operator
+if len(s:save_pandoc_lngs)
+ let g:pandoc#syntax#codeblocks#embeds#langs = s:save_pandoc_lngs
endif
-
-let b:current_syntax = "rmd"
-
+unlet s:save_pandoc_lngs
let &cpo = s:cpo_save
unlet s:cpo_save
+let b:current_syntax = "rmd"
+
" vim: ts=8 sw=2
diff --git a/runtime/syntax/ruby.vim b/runtime/syntax/ruby.vim
index c951fcfe1d..e19d61a051 100644
--- a/runtime/syntax/ruby.vim
+++ b/runtime/syntax/ruby.vim
@@ -3,7 +3,7 @@
" Maintainer: Doug Kearns <dougkearns@gmail.com>
" URL: https://github.com/vim-ruby/vim-ruby
" Release Coordinator: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2021 Nov 03
+" Last Change: 2023 Mar 16
" ----------------------------------------------------------------------------
"
" Previous Maintainer: Mirko Nasato
@@ -145,9 +145,9 @@ syn cluster rubyStringSpecial contains=rubyInterpolation,rubyStringEscape
syn cluster rubyStringNotTop contains=@rubyStringSpecial,@rubyNestedBrackets,@rubySingleCharEscape
" Regular Expression Metacharacters {{{1
-syn region rubyRegexpComment matchgroup=rubyRegexpSpecial start="(?#" skip="\\\\\|\\)" end=")" contained
-syn region rubyRegexpParens matchgroup=rubyRegexpSpecial start="(\(?:\|?<\=[=!]\|?>\|?<[a-z_]\w*>\|?[imx]*-[imx]*:\=\|\%(?#\)\@!\)" skip="\\\\\|\\)" end=")" contained transparent contains=@rubyRegexpSpecial
-syn region rubyRegexpBrackets matchgroup=rubyRegexpCharClass start="\[\^\=" skip="\\\\\|\\\]" end="\]" contained transparent contains=rubyRegexpBrackets,rubyStringEscape,rubyRegexpEscape,rubyRegexpCharClass,rubyRegexpIntersection oneline
+syn region rubyRegexpComment matchgroup=rubyRegexpSpecial start="(?#" skip="\\\\\|\\)" end=")" contained
+syn region rubyRegexpParens matchgroup=rubyRegexpSpecial start="(\%(?:\|?<\=[=!]\|?>\|?<[a-z_]\w*>\|?[imx]*-[imx]*:\=\|\%(?#\)\@!\)" skip="\\\\\|\\)" end=")" contained transparent contains=@rubyRegexpSpecial
+syn region rubyRegexpBrackets matchgroup=rubyRegexpCharClass start="\[\^\=" skip="\\\\\|\\\]" end="\]" contained transparent contains=rubyRegexpBrackets,rubyStringEscape,rubyRegexpEscape,rubyRegexpCharClass,rubyRegexpIntersection oneline
syn match rubyRegexpCharClass "\\[DdHhRSsWw]" contained display
syn match rubyRegexpCharClass "\[:\^\=\%(alnum\|alpha\|ascii\|blank\|cntrl\|digit\|graph\|lower\|print\|punct\|space\|upper\|word\|xdigit\):\]" contained
syn match rubyRegexpCharClass "\\[pP]{^\=.\{-}}" contained display
@@ -346,7 +346,7 @@ syn cluster rubyDeclaration contains=rubyAliasDeclaration,rubyAliasDeclaration2,
syn match rubyControl "\%#=1\<\%(break\|in\|next\|redo\|retry\|return\)\>"
syn match rubyKeyword "\%#=1\<\%(super\|yield\)\>"
syn match rubyBoolean "\%#=1\<\%(true\|false\)\>[?!]\@!"
-syn match rubyPseudoVariable "\%#=1\<\(self\|nil\)\>[?!]\@!"
+syn match rubyPseudoVariable "\%#=1\<\%(self\|nil\)\>[?!]\@!"
syn match rubyPseudoVariable "\%#=1\<__\%(ENCODING\|dir\|FILE\|LINE\|callee\|method\)__\>"
syn match rubyBeginEnd "\%#=1\<\%(BEGIN\|END\)\>"
@@ -399,11 +399,6 @@ if !exists("b:ruby_no_expensive") && !exists("ruby_no_expensive")
SynFold 'for' syn region rubyRepeatExpression start="\<for\>" start="\%(\%(^\|\.\.\.\=\|[{:,;([<>~\*/%&^|+=-]\|\%(\<\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*\)\@<![!?]\)\s*\)\@<=\<\%(until\|while\)\>" matchgroup=rubyRepeat skip="\<end:" end="\<end\>" contains=ALLBUT,@rubyNotTop nextgroup=rubyOptionalDoLine
- if !exists("ruby_minlines")
- let ruby_minlines = 500
- endif
- exe "syn sync minlines=" . ruby_minlines
-
else
syn match rubyControl "\<def\>" nextgroup=rubyMethodDeclaration skipwhite skipnl
syn match rubyControl "\<class\>" nextgroup=rubyClassDeclaration skipwhite skipnl
@@ -412,13 +407,18 @@ else
syn match rubyKeyword "\<\%(alias\|undef\)\>"
endif
+if !exists("ruby_minlines")
+ let ruby_minlines = 500
+endif
+exe "syn sync minlines=" . ruby_minlines
+
" Special Methods {{{1
if !exists("ruby_no_special_methods")
syn match rubyAccess "\<\%(public\|protected\|private\)\>" " use re=2
syn match rubyAccess "\%#=1\<\%(public\|private\)_class_method\>"
syn match rubyAccess "\%#=1\<\%(public\|private\)_constant\>"
syn match rubyAccess "\%#=1\<module_function\>"
- syn match rubyAttribute "\%#=1\%(\%(^\|;\)\s*\)\@<=attr\>\(\s*[.=]\)\@!" " attr is a common variable name
+ syn match rubyAttribute "\%#=1\%(\%(^\|;\)\s*\)\@<=attr\>\%(\s*[.=]\)\@!" " attr is a common variable name
syn match rubyAttribute "\%#=1\<attr_\%(accessor\|reader\|writer\)\>"
syn match rubyControl "\%#=1\<\%(abort\|at_exit\|exit\|fork\|loop\|trap\)\>"
syn match rubyEval "\%#=1\<eval\>"
@@ -435,8 +435,8 @@ syn match rubySharpBang "\%^#!.*" display
syn keyword rubyTodo FIXME NOTE TODO OPTIMIZE HACK REVIEW XXX todo contained
syn match rubyEncoding "[[:alnum:]-_]\+" contained display
syn match rubyMagicComment "\c\%<3l#\s*\zs\%(coding\|encoding\):" contained nextgroup=rubyEncoding skipwhite
-syn match rubyMagicComment "\c\%<10l#\s*\zs\%(frozen_string_literal\|warn_indent\|warn_past_scope\):" contained nextgroup=rubyBoolean skipwhite
-syn match rubyMagicComment "\c\%<10l#\s*\zs\%(shareable_constant_value\):" contained nextgroup=rubyEncoding skipwhite
+syn match rubyMagicComment "\c\%<10l#\s*\zs\%(frozen[-_]string[-_]literal\|warn[-_]indent\|warn[-_]past[-_]scope\):" contained nextgroup=rubyBoolean skipwhite
+syn match rubyMagicComment "\c\%<10l#\s*\zs\%(shareable[-_]constant[-_]value\):" contained nextgroup=rubyEncoding skipwhite
syn match rubyComment "#.*" contains=@rubyCommentSpecial,rubySpaceError,@Spell
syn cluster rubyCommentSpecial contains=rubySharpBang,rubyTodo,rubyMagicComment
diff --git a/runtime/syntax/rust.vim b/runtime/syntax/rust.vim
index 57343301e0..55d3f14dc2 100644
--- a/runtime/syntax/rust.vim
+++ b/runtime/syntax/rust.vim
@@ -3,44 +3,57 @@
" Maintainer: Patrick Walton <pcwalton@mozilla.com>
" Maintainer: Ben Blum <bblum@cs.cmu.edu>
" Maintainer: Chris Morgan <me@chrismorgan.info>
-" Last Change: Feb 24, 2016
+" Last Change: 2023-09-11
" For bugs, patches and license go to https://github.com/rust-lang/rust.vim
if version < 600
- syntax clear
+ syntax clear
elseif exists("b:current_syntax")
- finish
+ finish
endif
" Syntax definitions {{{1
" Basic keywords {{{2
syn keyword rustConditional match if else
-syn keyword rustRepeat for loop while
+syn keyword rustRepeat loop while
+" `:syn match` must be used to prioritize highlighting `for` keyword.
+syn match rustRepeat /\<for\>/
+" Highlight `for` keyword in `impl ... for ... {}` statement. This line must
+" be put after previous `syn match` line to overwrite it.
+syn match rustKeyword /\%(\<impl\>.\+\)\@<=\<for\>/
+syn keyword rustRepeat in
syn keyword rustTypedef type nextgroup=rustIdentifier skipwhite skipempty
syn keyword rustStructure struct enum nextgroup=rustIdentifier skipwhite skipempty
syn keyword rustUnion union nextgroup=rustIdentifier skipwhite skipempty contained
syn match rustUnionContextual /\<union\_s\+\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*/ transparent contains=rustUnion
syn keyword rustOperator as
+syn keyword rustExistential existential nextgroup=rustTypedef skipwhite skipempty contained
+syn match rustExistentialContextual /\<existential\_s\+type/ transparent contains=rustExistential,rustTypedef
syn match rustAssert "\<assert\(\w\)*!" contained
syn match rustPanic "\<panic\(\w\)*!" contained
+syn match rustAsync "\<async\%(\s\|\n\)\@="
syn keyword rustKeyword break
-syn keyword rustKeyword box nextgroup=rustBoxPlacement skipwhite skipempty
+syn keyword rustKeyword box
syn keyword rustKeyword continue
+syn keyword rustKeyword crate
syn keyword rustKeyword extern nextgroup=rustExternCrate,rustObsoleteExternMod skipwhite skipempty
syn keyword rustKeyword fn nextgroup=rustFuncName skipwhite skipempty
-syn keyword rustKeyword in impl let
+syn keyword rustKeyword impl let
+syn keyword rustKeyword macro
syn keyword rustKeyword pub nextgroup=rustPubScope skipwhite skipempty
syn keyword rustKeyword return
+syn keyword rustKeyword yield
syn keyword rustSuper super
-syn keyword rustKeyword unsafe where
+syn keyword rustKeyword where
+syn keyword rustUnsafeKeyword unsafe
syn keyword rustKeyword use nextgroup=rustModPath skipwhite skipempty
" FIXME: Scoped impl's name is also fallen in this category
syn keyword rustKeyword mod trait nextgroup=rustIdentifier skipwhite skipempty
syn keyword rustStorage move mut ref static const
-syn match rustDefault /\<default\ze\_s\+\(impl\|fn\|type\|const\)\>/
-
-syn keyword rustInvalidBareKeyword crate
+syn match rustDefault /\<default\ze\_s\+\(impl\|fn\|type\|const\)\>/
+syn keyword rustAwait await
+syn match rustKeyword /\<try\>!\@!/ display
syn keyword rustPubScopeCrate crate contained
syn match rustPubScopeDelim /[()]/ contained
@@ -52,22 +65,14 @@ syn match rustExternCrateString /".*"\_s*as/ contained nextgroup=rustIdentifie
syn keyword rustObsoleteExternMod mod contained nextgroup=rustIdentifier skipwhite skipempty
syn match rustIdentifier contains=rustIdentifierPrime "\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained
-syn match rustFuncName "\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained
-
-syn region rustBoxPlacement matchgroup=rustBoxPlacementParens start="(" end=")" contains=TOP contained
-" Ideally we'd have syntax rules set up to match arbitrary expressions. Since
-" we don't, we'll just define temporary contained rules to handle balancing
-" delimiters.
-syn region rustBoxPlacementBalance start="(" end=")" containedin=rustBoxPlacement transparent
-syn region rustBoxPlacementBalance start="\[" end="\]" containedin=rustBoxPlacement transparent
-" {} are handled by rustFoldBraces
-
-syn region rustMacroRepeat matchgroup=rustMacroRepeatDelimiters start="$(" end=")" contains=TOP nextgroup=rustMacroRepeatCount
-syn match rustMacroRepeatCount ".\?[*+]" contained
+syn match rustFuncName "\%(r#\)\=\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*" display contained
+
+syn region rustMacroRepeat matchgroup=rustMacroRepeatDelimiters start="$(" end="),\=[*+]" contains=TOP
syn match rustMacroVariable "$\w\+"
+syn match rustRawIdent "\<r#\h\w*" contains=NONE
" Reserved (but not yet used) keywords {{{2
-syn keyword rustReservedKeyword alignof become do offsetof priv pure sizeof typeof unsized yield abstract virtual final override macro
+syn keyword rustReservedKeyword become do priv typeof unsized abstract virtual final override
" Built-in types {{{2
syn keyword rustType isize usize char bool u8 u16 u32 u64 u128 f32
@@ -138,18 +143,37 @@ syn match rustMacro '#\w\(\w\)*' contains=rustAssert,rustPanic
syn match rustEscapeError display contained /\\./
syn match rustEscape display contained /\\\([nrt0\\'"]\|x\x\{2}\)/
-syn match rustEscapeUnicode display contained /\\u{\x\{1,6}}/
+syn match rustEscapeUnicode display contained /\\u{\%(\x_*\)\{1,6}}/
syn match rustStringContinuation display contained /\\\n\s*/
-syn region rustString start=+b"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeError,rustStringContinuation
-syn region rustString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustStringContinuation,@Spell
-syn region rustString start='b\?r\z(#*\)"' end='"\z1' contains=@Spell
-
-syn region rustAttribute start="#!\?\[" end="\]" contains=rustString,rustDerive,rustCommentLine,rustCommentBlock,rustCommentLineDocError,rustCommentBlockDocError
+syn region rustString matchgroup=rustStringDelimiter start=+b"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeError,rustStringContinuation
+syn region rustString matchgroup=rustStringDelimiter start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustStringContinuation,@Spell
+syn region rustString matchgroup=rustStringDelimiter start='b\?r\z(#*\)"' end='"\z1' contains=@Spell
+
+" Match attributes with either arbitrary syntax or special highlighting for
+" derives. We still highlight strings and comments inside of the attribute.
+syn region rustAttribute start="#!\?\[" end="\]" contains=@rustAttributeContents,rustAttributeParenthesizedParens,rustAttributeParenthesizedCurly,rustAttributeParenthesizedBrackets,rustDerive
+syn region rustAttributeParenthesizedParens matchgroup=rustAttribute start="\w\%(\w\)*("rs=e end=")"re=s transparent contained contains=rustAttributeBalancedParens,@rustAttributeContents
+syn region rustAttributeParenthesizedCurly matchgroup=rustAttribute start="\w\%(\w\)*{"rs=e end="}"re=s transparent contained contains=rustAttributeBalancedCurly,@rustAttributeContents
+syn region rustAttributeParenthesizedBrackets matchgroup=rustAttribute start="\w\%(\w\)*\["rs=e end="\]"re=s transparent contained contains=rustAttributeBalancedBrackets,@rustAttributeContents
+syn region rustAttributeBalancedParens matchgroup=rustAttribute start="("rs=e end=")"re=s transparent contained contains=rustAttributeBalancedParens,@rustAttributeContents
+syn region rustAttributeBalancedCurly matchgroup=rustAttribute start="{"rs=e end="}"re=s transparent contained contains=rustAttributeBalancedCurly,@rustAttributeContents
+syn region rustAttributeBalancedBrackets matchgroup=rustAttribute start="\["rs=e end="\]"re=s transparent contained contains=rustAttributeBalancedBrackets,@rustAttributeContents
+syn cluster rustAttributeContents contains=rustString,rustCommentLine,rustCommentBlock,rustCommentLineDocError,rustCommentBlockDocError
syn region rustDerive start="derive(" end=")" contained contains=rustDeriveTrait
" This list comes from src/libsyntax/ext/deriving/mod.rs
" Some are deprecated (Encodable, Decodable) or to be removed after a new snapshot (Show).
syn keyword rustDeriveTrait contained Clone Hash RustcEncodable RustcDecodable Encodable Decodable PartialEq Eq PartialOrd Ord Rand Show Debug Default FromPrimitive Send Sync Copy
+" dyn keyword: It's only a keyword when used inside a type expression, so
+" we make effort here to highlight it only when Rust identifiers follow it
+" (not minding the case of pre-2018 Rust where a path starting with :: can
+" follow).
+"
+" This is so that uses of dyn variable names such as in 'let &dyn = &2'
+" and 'let dyn = 2' will not get highlighted as a keyword.
+syn match rustKeyword "\<dyn\ze\_s\+\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)" contains=rustDynKeyword
+syn keyword rustDynKeyword dyn contained
+
" Number literals
syn match rustDecNumber display "\<[0-9][0-9_]*\%([iu]\%(size\|8\|16\|32\|64\|128\)\)\="
syn match rustHexNumber display "\<0x[a-fA-F0-9_]\+\%([iu]\%(size\|8\|16\|32\|64\|128\)\)\="
@@ -168,29 +192,31 @@ syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE
syn match rustFloat display "\<[0-9][0-9_]*\%(\.[0-9][0-9_]*\)\=\%([eE][+-]\=[0-9_]\+\)\=\(f32\|f64\)"
" For the benefit of delimitMate
-syn region rustLifetimeCandidate display start=/&'\%(\([^'\\]\|\\\(['nrt0\\\"]\|x\x\{2}\|u{\x\{1,6}}\)\)'\)\@!/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime
-syn region rustGenericRegion display start=/<\%('\|[^[cntrl:][:space:][:punct:]]\)\@=')\S\@=/ end=/>/ contains=rustGenericLifetimeCandidate
+syn region rustLifetimeCandidate display start=/&'\%(\([^'\\]\|\\\(['nrt0\\\"]\|x\x\{2}\|u{\%(\x_*\)\{1,6}}\)\)'\)\@!/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime
+syn region rustGenericRegion display start=/<\%('\|[^[:cntrl:][:space:][:punct:]]\)\@=')\S\@=/ end=/>/ contains=rustGenericLifetimeCandidate
syn region rustGenericLifetimeCandidate display start=/\%(<\|,\s*\)\@<='/ end=/[[:cntrl:][:space:][:punct:]]\@=\|$/ contains=rustSigil,rustLifetime
"rustLifetime must appear before rustCharacter, or chars will get the lifetime highlighting
syn match rustLifetime display "\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*"
syn match rustLabel display "\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*:"
+syn match rustLabel display "\%(\<\%(break\|continue\)\s*\)\@<=\'\%([^[:cntrl:][:space:][:punct:][:digit:]]\|_\)\%([^[:cntrl:][:punct:][:space:]]\|_\)*"
syn match rustCharacterInvalid display contained /b\?'\zs[\n\r\t']\ze'/
" The groups negated here add up to 0-255 but nothing else (they do not seem to go beyond ASCII).
syn match rustCharacterInvalidUnicode display contained /b'\zs[^[:cntrl:][:graph:][:alnum:][:space:]]\ze'/
syn match rustCharacter /b'\([^\\]\|\\\(.\|x\x\{2}\)\)'/ contains=rustEscape,rustEscapeError,rustCharacterInvalid,rustCharacterInvalidUnicode
-syn match rustCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u{\x\{1,6}}\)\)'/ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustCharacterInvalid
+syn match rustCharacter /'\([^\\]\|\\\(.\|x\x\{2}\|u{\%(\x_*\)\{1,6}}\)\)'/ contains=rustEscape,rustEscapeUnicode,rustEscapeError,rustCharacterInvalid
syn match rustShebang /\%^#![^[].*/
syn region rustCommentLine start="//" end="$" contains=rustTodo,@Spell
syn region rustCommentLineDoc start="//\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell
syn region rustCommentLineDocError start="//\%(//\@!\|!\)" end="$" contains=rustTodo,@Spell contained
syn region rustCommentBlock matchgroup=rustCommentBlock start="/\*\%(!\|\*[*/]\@!\)\@!" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell
-syn region rustCommentBlockDoc matchgroup=rustCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell
+syn region rustCommentBlockDoc matchgroup=rustCommentBlockDoc start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNest,rustCommentBlockDocRustCode,@Spell
syn region rustCommentBlockDocError matchgroup=rustCommentBlockDocError start="/\*\%(!\|\*[*/]\@!\)" end="\*/" contains=rustTodo,rustCommentBlockDocNestError,@Spell contained
syn region rustCommentBlockNest matchgroup=rustCommentBlock start="/\*" end="\*/" contains=rustTodo,rustCommentBlockNest,@Spell contained transparent
syn region rustCommentBlockDocNest matchgroup=rustCommentBlockDoc start="/\*" end="\*/" contains=rustTodo,rustCommentBlockDocNest,@Spell contained transparent
syn region rustCommentBlockDocNestError matchgroup=rustCommentBlockDocError start="/\*" end="\*/" contains=rustTodo,rustCommentBlockDocNestError,@Spell contained transparent
+
" FIXME: this is a really ugly and not fully correct implementation. Most
" importantly, a case like ``/* */*`` should have the final ``*`` not being in
" a comment, but in practice at present it leaves comments open two levels
@@ -203,13 +229,67 @@ syn region rustCommentBlockDocNestError matchgroup=rustCommentBlockDocError star
" then you must deal with cases like ``/*/**/*/``. And don't try making it
" worse with ``\%(/\@<!\*\)\@<!``, either...
-syn keyword rustTodo contained TODO FIXME XXX NB NOTE
+syn keyword rustTodo contained TODO FIXME XXX NB NOTE SAFETY
+
+" asm! macro {{{2
+syn region rustAsmMacro matchgroup=rustMacro start="\<asm!\s*(" end=")" contains=rustAsmDirSpec,rustAsmSym,rustAsmConst,rustAsmOptionsGroup,rustComment.*,rustString.*
+
+" Clobbered registers
+syn keyword rustAsmDirSpec in out lateout inout inlateout contained nextgroup=rustAsmReg skipwhite skipempty
+syn region rustAsmReg start="(" end=")" contained contains=rustString
+
+" Symbol operands
+syn keyword rustAsmSym sym contained nextgroup=rustAsmSymPath skipwhite skipempty
+syn region rustAsmSymPath start="\S" end=",\|)"me=s-1 contained contains=rustComment.*,rustIdentifier
+
+" Const
+syn region rustAsmConstBalancedParens start="("ms=s+1 end=")" contained contains=@rustAsmConstExpr
+syn cluster rustAsmConstExpr contains=rustComment.*,rust.*Number,rustString,rustAsmConstBalancedParens
+syn region rustAsmConst start="const" end=",\|)"me=s-1 contained contains=rustStorage,@rustAsmConstExpr
+
+" Options
+syn region rustAsmOptionsGroup start="options\s*(" end=")" contained contains=rustAsmOptions,rustAsmOptionsKey
+syn keyword rustAsmOptionsKey options contained
+syn keyword rustAsmOptions pure nomem readonly preserves_flags noreturn nostack att_syntax contained
" Folding rules {{{2
" Trivial folding rules to begin with.
" FIXME: use the AST to make really good folding
syn region rustFoldBraces start="{" end="}" transparent fold
+if !exists("b:current_syntax_embed")
+ let b:current_syntax_embed = 1
+ syntax include @RustCodeInComment <sfile>:p:h/rust.vim
+ unlet b:current_syntax_embed
+
+ " Currently regions marked as ```<some-other-syntax> will not get
+ " highlighted at all. In the future, we can do as vim-markdown does and
+ " highlight with the other syntax. But for now, let's make sure we find
+ " the closing block marker, because the rules below won't catch it.
+ syn region rustCommentLinesDocNonRustCode matchgroup=rustCommentDocCodeFence start='^\z(\s*//[!/]\s*```\).\+$' end='^\z1$' keepend contains=rustCommentLineDoc
+
+ " We borrow the rules from rust’s src/librustdoc/html/markdown.rs, so that
+ " we only highlight as Rust what it would perceive as Rust (almost; it’s
+ " possible to trick it if you try hard, and indented code blocks aren’t
+ " supported because Markdown is a menace to parse and only mad dogs and
+ " Englishmen would try to handle that case correctly in this syntax file).
+ syn region rustCommentLinesDocRustCode matchgroup=rustCommentDocCodeFence start='^\z(\s*//[!/]\s*```\)[^A-Za-z0-9_-]*\%(\%(should_panic\|no_run\|ignore\|allow_fail\|rust\|test_harness\|compile_fail\|E\d\{4}\|edition201[58]\)\%([^A-Za-z0-9_-]\+\|$\)\)*$' end='^\z1$' keepend contains=@RustCodeInComment,rustCommentLineDocLeader
+ syn region rustCommentBlockDocRustCode matchgroup=rustCommentDocCodeFence start='^\z(\%(\s*\*\)\?\s*```\)[^A-Za-z0-9_-]*\%(\%(should_panic\|no_run\|ignore\|allow_fail\|rust\|test_harness\|compile_fail\|E\d\{4}\|edition201[58]\)\%([^A-Za-z0-9_-]\+\|$\)\)*$' end='^\z1$' keepend contains=@RustCodeInComment,rustCommentBlockDocStar
+ " Strictly, this may or may not be correct; this code, for example, would
+ " mishighlight:
+ "
+ " /**
+ " ```rust
+ " println!("{}", 1
+ " * 1);
+ " ```
+ " */
+ "
+ " … but I don’t care. Balance of probability, and all that.
+ syn match rustCommentBlockDocStar /^\s*\*\s\?/ contained
+ syn match rustCommentLineDocLeader "^\s*//\%(//\@!\|!\)" contained
+endif
+
" Default highlighting {{{1
hi def link rustDecNumber rustNumber
hi def link rustHexNumber rustNumber
@@ -219,7 +299,6 @@ hi def link rustIdentifierPrime rustIdentifier
hi def link rustTrait rustType
hi def link rustDeriveTrait rustTrait
-hi def link rustMacroRepeatCount rustMacroRepeatDelimiters
hi def link rustMacroRepeatDelimiters Macro
hi def link rustMacroVariable Define
hi def link rustSigil StorageClass
@@ -228,6 +307,7 @@ hi def link rustEscapeUnicode rustEscape
hi def link rustEscapeError Error
hi def link rustStringContinuation Special
hi def link rustString String
+hi def link rustStringDelimiter String
hi def link rustCharacterInvalid Error
hi def link rustCharacterInvalidUnicode rustCharacterInvalid
hi def link rustCharacter Character
@@ -241,12 +321,15 @@ hi def link rustFloat Float
hi def link rustArrowCharacter rustOperator
hi def link rustOperator Operator
hi def link rustKeyword Keyword
+hi def link rustDynKeyword rustKeyword
hi def link rustTypedef Keyword " More precise is Typedef, but it doesn't feel right for Rust
hi def link rustStructure Keyword " More precise is Structure
hi def link rustUnion rustStructure
+hi def link rustExistential rustKeyword
hi def link rustPubScopeDelim Delimiter
hi def link rustPubScopeCrate rustKeyword
hi def link rustSuper rustKeyword
+hi def link rustUnsafeKeyword Exception
hi def link rustReservedKeyword Error
hi def link rustRepeat Conditional
hi def link rustConditional Conditional
@@ -260,10 +343,13 @@ hi def link rustFuncCall Function
hi def link rustShebang Comment
hi def link rustCommentLine Comment
hi def link rustCommentLineDoc SpecialComment
+hi def link rustCommentLineDocLeader rustCommentLineDoc
hi def link rustCommentLineDocError Error
hi def link rustCommentBlock rustCommentLine
hi def link rustCommentBlockDoc rustCommentLineDoc
+hi def link rustCommentBlockDocStar rustCommentBlockDoc
hi def link rustCommentBlockDocError Error
+hi def link rustCommentDocCodeFence rustCommentLineDoc
hi def link rustAssert PreCondit
hi def link rustPanic PreCondit
hi def link rustMacro Macro
@@ -276,11 +362,15 @@ hi def link rustStorage StorageClass
hi def link rustObsoleteStorage Error
hi def link rustLifetime Special
hi def link rustLabel Label
-hi def link rustInvalidBareKeyword Error
hi def link rustExternCrate rustKeyword
hi def link rustObsoleteExternMod Error
-hi def link rustBoxPlacementParens Delimiter
hi def link rustQuestionMark Special
+hi def link rustAsync rustKeyword
+hi def link rustAwait rustKeyword
+hi def link rustAsmDirSpec rustKeyword
+hi def link rustAsmSym rustKeyword
+hi def link rustAsmOptions rustKeyword
+hi def link rustAsmOptionsKey rustAttribute
" Other Suggestions:
" hi rustAttribute ctermfg=cyan
@@ -293,3 +383,5 @@ syn sync minlines=200
syn sync maxlines=500
let b:current_syntax = "rust"
+
+" vim: set et sw=4 sts=4 ts=8:
diff --git a/runtime/syntax/scala.vim b/runtime/syntax/scala.vim
index c08e60e55a..cc098ce017 100644
--- a/runtime/syntax/scala.vim
+++ b/runtime/syntax/scala.vim
@@ -180,7 +180,7 @@ hi def link scalaNumber Number
syn region scalaRoundBrackets start="(" end=")" skipwhite contained contains=scalaTypeDeclaration,scalaSquareBrackets,scalaRoundBrackets
-syn region scalaSquareBrackets matchgroup=scalaSquareBracketsBrackets start="\[" end="\]" skipwhite nextgroup=scalaTypeExtension contains=scalaTypeDeclaration,scalaSquareBrackets,scalaTypeOperator,scalaTypeAnnotationParameter
+syn region scalaSquareBrackets matchgroup=scalaSquareBracketsBrackets start="\[" end="\]" skipwhite nextgroup=scalaTypeExtension contains=scalaTypeDeclaration,scalaSquareBrackets,scalaTypeOperator,scalaTypeAnnotationParameter,scalaString
syn match scalaTypeOperator /[-+=:<>]\+/ contained
syn match scalaTypeAnnotationParameter /@\<[`_A-Za-z0-9$]\+\>/ contained
hi def link scalaSquareBracketsBrackets Type
diff --git a/runtime/syntax/sdc.vim b/runtime/syntax/sdc.vim
index 0ca9becf73..dbfa35eeb6 100644
--- a/runtime/syntax/sdc.vim
+++ b/runtime/syntax/sdc.vim
@@ -25,7 +25,7 @@ syn keyword sdcNonIdealities set_load set_min_capacitance set_max_capacitance
syn keyword sdcCreateOperations create_clock create_timing_netlist update_timing_netlist
" command flags highlighting
-syn match sdcFlags "[[:space:]]-[[:alpha:]]*\>"
+syn match sdcFlags "[[:space:]]-[[:alpha:]_]*\>"
" Define the default highlighting.
hi def link sdcCollections Repeat
diff --git a/runtime/syntax/sgml.vim b/runtime/syntax/sgml.vim
index 00d58d11f2..ed8fa8cf12 100644
--- a/runtime/syntax/sgml.vim
+++ b/runtime/syntax/sgml.vim
@@ -294,7 +294,7 @@ syn sync minlines=100
hi def link sgmlTodo Todo
hi def link sgmlTag Function
hi def link sgmlEndTag Identifier
-" SGML specifig
+" SGML specific
hi def link sgmlAbbrEndTag Identifier
hi def link sgmlEmptyTag Function
hi def link sgmlEntity Statement
diff --git a/runtime/syntax/sh.vim b/runtime/syntax/sh.vim
index 6722d62c89..e2b1947197 100644
--- a/runtime/syntax/sh.vim
+++ b/runtime/syntax/sh.vim
@@ -2,8 +2,8 @@
" Language: shell (sh) Korn shell (ksh) bash (sh)
" Maintainer: Charles E. Campbell <NcampObell@SdrPchip.AorgM-NOSPAM>
" Previous Maintainer: Lennart Schultz <Lennart.Schultz@ecmwf.int>
-" Last Change: Nov 25, 2022
-" Version: 204
+" Last Change: Feb 28, 2023
+" Version: 208
" URL: http://www.drchip.org/astronaut/vim/index.html#SYNTAX_SH
" For options and settings, please use: :help ft-sh-syntax
" This file includes many ideas from Eric Brunet (eric.brunet@ens.fr) and heredoc fixes from Felipe Contreras
@@ -140,7 +140,7 @@ syn cluster shArithList contains=@shArithParenList,shParenError
syn cluster shCaseEsacList contains=shCaseStart,shCaseLabel,shCase,shCaseBar,shCaseIn,shComment,shDeref,shDerefSimple,shCaseCommandSub,shCaseExSingleQuote,shCaseSingleQuote,shCaseDoubleQuote,shCtrlSeq,@shErrorList,shStringSpecial,shCaseRange
syn cluster shCaseList contains=@shCommandSubList,shCaseEsac,shColon,shCommandSub,shCommandSubBQ,shComment,shDblBrace,shDo,shEcho,shExpr,shFor,shHereDoc,shIf,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shCtrlSeq
if exists("b:is_kornshell") || exists("b:is_bash")
- syn cluster shCaseList add=shForPP
+ syn cluster shCaseList add=shForPP,shDblParen
endif
syn cluster shCommandSubList contains=shAlias,shArithmetic,shCmdParenRegion,shCommandSub,shComment,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
syn cluster shCurlyList contains=shNumber,shComma,shDeref,shDerefSimple,shDerefSpecial
@@ -163,10 +163,10 @@ syn cluster shIdList contains=shArithmetic,shCommandSub,shCommandSubBQ,shWrapLin
syn cluster shIfList contains=@shLoopList,shDblBrace,shDblParen,shFunctionKey,shFunctionOne,shFunctionTwo
syn cluster shLoopList contains=@shCaseList,@shErrorList,shCaseEsac,shConditional,shDblBrace,shExpr,shFor,shIf,shOption,shSet,shTest,shTestOpr,shTouch
if exists("b:is_kornshell") || exists("b:is_bash")
- syn cluster shLoopoList add=shForPP
+ syn cluster shLoopList add=shForPP,shDblParen
endif
syn cluster shPPSLeftList contains=shAlias,shArithmetic,shCmdParenRegion,shCommandSub,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shEcho,shEscape,shExDoubleQuote,shExpr,shExSingleQuote,shHereDoc,shNumber,shOperator,shOption,shPosnParm,shHereString,shRedir,shSingleQuote,shSpecial,shStatement,shSubSh,shTest,shVariable
-syn cluster shPPSRightList contains=shComment,shDeref,shDerefSimple,shEscape,shPosnParm
+syn cluster shPPSRightList contains=shDeref,shDerefSimple,shEscape,shPosnParm
syn cluster shSubShList contains=@shCommandSubList,shCommandSubBQ,shCaseEsac,shColon,shCommandSub,shComment,shDo,shEcho,shExpr,shFor,shIf,shHereString,shRedir,shSetList,shSource,shStatement,shVariable,shCtrlSeq,shOperator
syn cluster shTestList contains=shArithmetic,shCharClass,shCommandSub,shCommandSubBQ,shCtrlSeq,shDeref,shDerefSimple,shDoubleQuote,shSpecialDQ,shExDoubleQuote,shExpr,shExSingleQuote,shNumber,shOperator,shSingleQuote,shTest,shTestOpr
syn cluster shNoZSList contains=shSpecialNoZS
@@ -190,8 +190,10 @@ syn region shEmbeddedEcho contained matchgroup=shStatement start="\<print\>" ski
" =====
if exists("b:is_kornshell") || exists("b:is_bash") || exists("b:is_posix")
syn match shStatement "\<alias\>"
- syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]\+\)\@=" skip="\\$" end="\>\|`"
- syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]\+=\)\@=" skip="\\$" end="="
+ syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]*\)\@=" skip="\\$" end="\>\|`"
+ syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]*=\)\@=" skip="\\$" end="="
+" syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]\+\)\@=" skip="\\$" end="\>\|`"
+" syn region shAlias matchgroup=shStatement start="\<alias\>\s\+\(\h[-._[:alnum:]]\+=\)\@=" skip="\\$" end="="
" Touch: {{{1
" =====
@@ -413,21 +415,21 @@ syn match shBQComment contained "#.\{-}\ze`" contains=@shCommentGroup
" (modified by Felipe Contreras)
" =========================================
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc01 start="<<\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc01 end="^\z1$" contains=@shDblQuoteList
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc02 start="<<-\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc02 end="^\s*\z1$" contains=@shDblQuoteList
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc02 start="<<-\s*\z([^ \t|>]\+\)" matchgroup=shHereDoc02 end="^\t*\z1$" contains=@shDblQuoteList
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc03 start="<<\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc03 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc04 start="<<-\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc04 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc04 start="<<-\s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc04 end="^\t*\z1$"
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc05 start="<<\s*'\z([^']\+\)'" matchgroup=shHereDoc05 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc06 start="<<-\s*'\z([^']\+\)'" matchgroup=shHereDoc06 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc06 start="<<-\s*'\z([^']\+\)'" matchgroup=shHereDoc06 end="^\t*\z1$"
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc07 start="<<\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc07 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc08 start="<<-\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc08 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc08 start="<<-\s*\"\z([^"]\+\)\"" matchgroup=shHereDoc08 end="^\t*\z1$"
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc09 start="<<\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc09 end="^\z1$" contains=@shDblQuoteList
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc10 start="<<-\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc10 end="^\s*\z1$" contains=@shDblQuoteList
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc10 start="<<-\s*\\\_$\_s*\z([^ \t|>]\+\)" matchgroup=shHereDoc10 end="^\t*\z1$" contains=@shDblQuoteList
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc11 start="<<\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc11 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc12 start="<<-\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc12 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc12 start="<<-\s*\\\_$\_s*\\\z([^ \t|>]\+\)" matchgroup=shHereDoc12 end="^\t*\z1$"
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc13 start="<<\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc13 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc14 start="<<-\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc14 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc14 start="<<-\s*\\\_$\_s*'\z([^']\+\)'" matchgroup=shHereDoc14 end="^\t*\z1$"
ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc15 start="<<\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc15 end="^\z1$"
-ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc16 start="<<-\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc16 end="^\s*\z1$"
+ShFoldHereDoc syn region shHereDoc matchgroup=shHereDoc16 start="<<-\s*\\\_$\_s*\"\z([^"]\+\)\"" matchgroup=shHereDoc16 end="^\t*\z1$"
" Here Strings: {{{1
@@ -483,7 +485,9 @@ endif
" Parameter Dereferencing: {{{1
" ========================
-if !exists("g:sh_no_error") && !(exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix"))
+" Note: sh04 failure with following line
+"if !exists("g:sh_no_error") && !(exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix"))
+if !exists("g:sh_no_error")
syn match shDerefWordError "[^}$[~]" contained
endif
syn match shDerefSimple "\$\%(\h\w*\|\d\)" nextgroup=@shNoZSList
@@ -499,7 +503,6 @@ endif
" ksh: ${.sh.*} variables: {{{1
" ========================================
if exists("b:is_kornshell")
-" syn match shDerefVar contained "[.]*" nextgroup=@shDerefVarList
syn match shDerefVar contained "\.\+" nextgroup=@shDerefVarList
endif
@@ -528,7 +531,7 @@ if exists("b:is_kornshell") || exists("b:is_posix")
endif
" sh ksh bash : ${var[... ]...} array reference: {{{1
-syn region shDerefVarArray contained matchgroup=shDeref start="\[" end="]" contains=@shCommandSubList nextgroup=shDerefOp,shDerefOpError
+syn region shDerefVarArray contained matchgroup=shDeref start="\[" end="]" contains=@shCommandSubList nextgroup=shDerefOp,shDerefOpError,shDerefOffset
" Special ${parameter OPERATOR word} handling: {{{1
" sh ksh bash : ${parameter:-word} word is default value
@@ -544,6 +547,7 @@ syn region shDerefVarArray contained matchgroup=shDeref start="\[" end="]" co
" bash : ${parameter,pattern} Case modification
" bash : ${parameter,,pattern} Case modification
" bash : ${@:start:qty} display command line arguments from start to start+qty-1 (inferred)
+" bash : ${parameter@operator} transforms parameter (operator∈[uULqEPARa])
syn cluster shDerefPatternList contains=shDerefPattern,shDerefString
if !exists("g:sh_no_error")
syn match shDerefOpError contained ":[[:punct:]]"
@@ -559,6 +563,7 @@ if exists("b:is_bash") || exists("b:is_kornshell") || exists("b:is_posix")
endif
if exists("b:is_bash")
syn match shDerefOp contained "[,^]\{1,2}" nextgroup=@shDerefPatternList
+ syn match shDerefOp contained "@[uULQEPAKa]"
endif
syn region shDerefString contained matchgroup=shDerefDelim start=+\%(\\\)\@<!'+ end=+'+ contains=shStringSpecial
syn region shDerefString contained matchgroup=shDerefDelim start=+\%(\\\)\@<!"+ skip=+\\"+ end=+"+ contains=@shDblQuoteList,shStringSpecial
diff --git a/runtime/syntax/shared/README.txt b/runtime/syntax/shared/README.txt
index f519d44faf..fade4b38a1 100644
--- a/runtime/syntax/shared/README.txt
+++ b/runtime/syntax/shared/README.txt
@@ -1,2 +1,2 @@
This directory "runtime/syntax/shared" contains Vim script files that are
-generated or used by more then one syntax file.
+generated or used by more than one syntax file.
diff --git a/runtime/syntax/shared/debversions.vim b/runtime/syntax/shared/debversions.vim
new file mode 100644
index 0000000000..6c944cd4e1
--- /dev/null
+++ b/runtime/syntax/shared/debversions.vim
@@ -0,0 +1,29 @@
+" Vim syntax file
+" Language: Debian version information
+" Maintainer: Debian Vim Maintainers
+" Last Change: 2023 Nov 01
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/shared/debversions.vim
+
+let s:cpo = &cpo
+set cpo-=C
+
+let g:debSharedSupportedVersions = [
+ \ 'oldstable', 'stable', 'testing', 'unstable', 'experimental', 'sid', 'rc-buggy',
+ \ 'bullseye', 'bookworm', 'trixie', 'forky',
+ \
+ \ 'trusty', 'xenial', 'bionic', 'focal', 'jammy', 'lunar', 'mantic', 'noble',
+ \ 'devel'
+ \ ]
+let g:debSharedUnsupportedVersions = [
+ \ 'buzz', 'rex', 'bo', 'hamm', 'slink', 'potato',
+ \ 'woody', 'sarge', 'etch', 'lenny', 'squeeze', 'wheezy',
+ \ 'jessie', 'stretch', 'buster',
+ \
+ \ 'warty', 'hoary', 'breezy', 'dapper', 'edgy', 'feisty',
+ \ 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid',
+ \ 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy',
+ \ 'utopic', 'vivid', 'wily', 'yakkety', 'zesty', 'artful', 'cosmic',
+ \ 'disco', 'eoan', 'hirsute', 'impish', 'kinetic', 'groovy'
+ \ ]
+
+let &cpo=s:cpo
diff --git a/runtime/syntax/shared/typescriptcommon.vim b/runtime/syntax/shared/typescriptcommon.vim
index ef362fc721..d06525115e 100644
--- a/runtime/syntax/shared/typescriptcommon.vim
+++ b/runtime/syntax/shared/typescriptcommon.vim
@@ -1,9 +1,9 @@
" Vim syntax file
" Language: TypeScript and TypeScriptReact
-" Maintainer: Bram Moolenaar, Herrington Darkholme
-" Last Change: 2021 Sep 22
+" Maintainer: Herrington Darkholme
+" Last Change: 2023 Aug 24
" Based On: Herrington Darkholme's yats.vim
-" Changes: See https:github.com/HerringtonDarkholme/yats.vim
+" Changes: See https://github.com/HerringtonDarkholme/yats.vim
" Credits: See yats.vim on github
if &cpo =~ 'C'
@@ -149,7 +149,7 @@ syntax match typescriptNumber /\<0[bB][01][01_]*\>/ nextgroup=@typescript
syntax match typescriptNumber /\<0[oO][0-7][0-7_]*\>/ nextgroup=@typescriptSymbols skipwhite skipempty
syntax match typescriptNumber /\<0[xX][0-9a-fA-F][0-9a-fA-F_]*\>/ nextgroup=@typescriptSymbols skipwhite skipempty
syntax match typescriptNumber /\<\%(\d[0-9_]*\%(\.\d[0-9_]*\)\=\|\.\d[0-9_]*\)\%([eE][+-]\=\d[0-9_]*\)\=\>/
- \ nextgroup=typescriptSymbols skipwhite skipempty
+ \ nextgroup=@typescriptSymbols skipwhite skipempty
syntax region typescriptObjectLiteral matchgroup=typescriptBraces
\ start=/{/ end=/}/
diff --git a/runtime/syntax/slrnrc.vim b/runtime/syntax/slrnrc.vim
index cf0734ae85..004bdd1bb1 100644
--- a/runtime/syntax/slrnrc.vim
+++ b/runtime/syntax/slrnrc.vim
@@ -94,7 +94,7 @@ syn region slrnrcColorObjStr contained matchgroup=slrnrcColorObj start=+"+ end=
syn keyword slrnrcColorVal contained default
syn keyword slrnrcColorVal contained black blue brightblue brightcyan brightgreen brightmagenta brightred brown cyan gray green lightgray magenta red white yellow
syn region slrnrcColorValStr contained matchgroup=slrnrcColorVal start=+"+ end=+"+ oneline contains=slrnrcColorVal,slrnrcSpaceError
-" Mathcing a function with three arguments
+" Matching a function with three arguments
syn keyword slrnrcColor contained color
syn match slrnrcColorInit contained "^\s*color\s\+\S\+" skipwhite nextgroup=slrnrcColorVal\(Str\)\= contains=slrnrcColor\(Obj\|ObjStr\)\=
syn match slrnrcColorLine "^\s*color\s\+\S\+\s\+\S\+" skipwhite nextgroup=slrnrcColorVal\(Str\)\= contains=slrnrcColor\(Init\|Val\|ValStr\)
diff --git a/runtime/syntax/solidity.vim b/runtime/syntax/solidity.vim
index e552446e10..a46d041a10 100644
--- a/runtime/syntax/solidity.vim
+++ b/runtime/syntax/solidity.vim
@@ -1,10 +1,10 @@
" Vim syntax file
-" Language: Solidity
-" Maintainer: Cothi (jiungdev@gmail.com)
-" Original Author: tomlion (https://github.com/tomlion/vim-solidity/blob/master/syntax/solidity.vim)
-" Last Changed: 2022 Sep 27
+" Language: Solidity
+" Maintainer: Cothi (jiungdev@gmail.com)
+" Original Author: tomlion (https://github.com/tomlion/vim-solidity/blob/master/syntax/solidity.vim)
+" Last Change: 2022 Sep 27
"
-" Additional contributors:
+" Contributors:
" Modified by thesis (https://github.com/thesis/vim-solidity/blob/main/indent/solidity.vim)
if exists("b:current_syntax")
diff --git a/runtime/syntax/spec.vim b/runtime/syntax/spec.vim
index d7d5877943..aed04bc900 100644
--- a/runtime/syntax/spec.vim
+++ b/runtime/syntax/spec.vim
@@ -56,7 +56,7 @@ syn match specListedFilesEtc contained '/etc/'me=e-1
syn match specListedFilesShare contained '/share/'me=e-1
syn cluster specListedFiles contains=specListedFilesBin,specListedFilesLib,specListedFilesDoc,specListedFilesEtc,specListedFilesShare,specListedFilesPrefix,specVariables,specSpecialChar
-"specComands
+"specCommands
syn match specConfigure contained '\./configure'
syn match specTarCommand contained '\<tar\s\+[cxvpzIf]\{,5}\s*'
syn keyword specCommandSpecial contained root
@@ -87,7 +87,7 @@ syn region specSectionMacroBracketArea oneline matchgroup=specSectionMacro start
"TODO %config valid parameters: missingok\|noreplace
"TODO %verify valid parameters: \(not\)\= \(md5\|atime\|...\)
syn region specFilesArea matchgroup=specSection start='^%[Ff][Ii][Ll][Ee][Ss]\>' skip='%\(attrib\|defattr\|attr\|dir\|config\|docdir\|doc\|lang\|license\|verify\|ghost\|exclude\)\>' end='^%[a-zA-Z]'me=e-2 contains=specFilesOpts,specFilesDirective,@specListedFiles,specComment,specCommandSpecial,specMacroIdentifier
-"tip: remember to include new itens in specFilesArea above
+"tip: remember to include new items in specFilesArea above
syn match specFilesDirective contained '%\(attrib\|defattr\|attr\|dir\|config\|docdir\|doc\|lang\|license\|verify\|ghost\|exclude\)\>'
"valid options for certain section headers
diff --git a/runtime/syntax/spup.vim b/runtime/syntax/spup.vim
index 9284abf63f..222caa779e 100644
--- a/runtime/syntax/spup.vim
+++ b/runtime/syntax/spup.vim
@@ -29,7 +29,7 @@ set cpo&vim
"let strict_subsections = 1
" highlight types usually found in DECLARE section
-if !exists("hightlight_types")
+if !exists("highlight_types")
let highlight_types = 1
endif
diff --git a/runtime/syntax/sqlinformix.vim b/runtime/syntax/sqlinformix.vim
index e01912bc40..71418c556f 100644
--- a/runtime/syntax/sqlinformix.vim
+++ b/runtime/syntax/sqlinformix.vim
@@ -162,7 +162,7 @@ hi def link sqlNumber Number
hi def link sqlBoolean Boolean
hi def link sqlString String
-" === Statment syntax group ===
+" === Statement syntax group ===
hi def link sqlStatement Statement
hi def link sqlConditional Conditional
hi def link sqlRepeat Repeat
diff --git a/runtime/syntax/sqlj.vim b/runtime/syntax/sqlj.vim
index c901145c3c..fd0f8f3d76 100644
--- a/runtime/syntax/sqlj.vim
+++ b/runtime/syntax/sqlj.vim
@@ -16,7 +16,7 @@ endif
" Read the Java syntax to start with
source <sfile>:p:h/java.vim
-" SQLJ extentions
+" SQLJ extensions
" The SQL reserved words, defined as keywords.
syn case ignore
diff --git a/runtime/syntax/squid.vim b/runtime/syntax/squid.vim
index a8abd180a0..186be91e61 100644
--- a/runtime/syntax/squid.vim
+++ b/runtime/syntax/squid.vim
@@ -31,7 +31,7 @@ syn keyword squidConf cache_effective_user cache_host cache_host_acl
syn keyword squidConf cache_host_domain cache_log cache_mem
syn keyword squidConf cache_mem_high cache_mem_low cache_mgr
syn keyword squidConf cachemgr_passwd cache_peer cache_peer_access
-syn keyword squidConf cahce_replacement_policy cache_stoplist
+syn keyword squidConf cache_replacement_policy cache_stoplist
syn keyword squidConf cache_stoplist_pattern cache_store_log cache_swap
syn keyword squidConf cache_swap_high cache_swap_log cache_swap_low
syn keyword squidConf client_db client_lifetime client_netmask
diff --git a/runtime/syntax/structurizr.vim b/runtime/syntax/structurizr.vim
index ab9e4ee609..363ee70438 100644
--- a/runtime/syntax/structurizr.vim
+++ b/runtime/syntax/structurizr.vim
@@ -1,7 +1,7 @@
" Vim syntax file
" Language: Structurizr DSL
" Maintainer: Bastian Venthur <venthur@debian.org>
-" Last Change: 2022-02-15
+" Last Change: 2022-05-22
" Remark: For a language reference, see
" https://github.com/structurizr/dsl
@@ -26,6 +26,7 @@ syn keyword skeyword configuration
syn keyword skeyword container
syn keyword skeyword containerinstance
syn keyword skeyword custom
+syn keyword skeyword default
syn keyword skeyword deployment
syn keyword skeyword deploymentenvironment
syn keyword skeyword deploymentgroup
@@ -40,6 +41,7 @@ syn keyword skeyword group
syn keyword skeyword healthcheck
syn keyword skeyword include
syn keyword skeyword infrastructurenode
+syn keyword skeyword instances
syn keyword skeyword model
syn keyword skeyword person
syn keyword skeyword perspectives
@@ -54,6 +56,7 @@ syn keyword skeyword tags
syn keyword skeyword technology
syn keyword skeyword terminology
syn keyword skeyword theme
+syn keyword skeyword themes
syn keyword skeyword title
syn keyword skeyword url
syn keyword skeyword users
diff --git a/runtime/syntax/swayconfig.vim b/runtime/syntax/swayconfig.vim
index 996b8f596c..7b1c889d6d 100644
--- a/runtime/syntax/swayconfig.vim
+++ b/runtime/syntax/swayconfig.vim
@@ -1,10 +1,9 @@
" Vim syntax file
-" Language: sway window manager config
-" Original Author: James Eapen <james.eapen@vai.org>
+" Language: sway config file
+" Original Author: Josef Litos (JosefLitos/i3config.vim)
" Maintainer: James Eapen <james.eapen@vai.org>
-" Version: 0.1.6
-" Reference version (jamespeapen/swayconfig.vim): 0.11.6
-" Last Change: 2022 Aug 08
+" Version: 1.0.0
+" Last Change: 2023-09-14
" References:
" http://i3wm.org/docs/userguide.html#configuring
@@ -19,88 +18,133 @@ endif
runtime! syntax/i3config.vim
-scriptencoding utf-8
+" i3 extensions
+syn keyword i3ConfigActionKeyword opacity urgent shortcuts_inhibitor splitv splith splitt contained
+syn keyword i3ConfigOption set plus minus allow deny csd v h t contained
-" Error
-"syn match swayConfigError /.*/
+syn keyword i3ConfigConditionProp app_id pid shell contained
+
+syn keyword i3ConfigWorkspaceDir prev_on_output next_on_output contained
-" binding
syn keyword swayConfigBindKeyword bindswitch bindgesture contained
-syn match swayConfigBind /^\s*\(bindswitch\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,swayConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword
-
-" bindgestures
-syn keyword swayConfigBindGestureCommand swipe pinch hold contained
-syn keyword swayConfigBindGestureDirection up down left right next prev contained
-syn keyword swayConfigBindGesturePinchDirection inward outward clockwise counterclockwise contained
-syn match swayConfigBindGestureHold /^\s*\(bindgesture\)\s\+hold\(:[1-5]\)\?\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction
-syn match swayConfigBindGestureSwipe /^\s*\(bindgesture\)\s\+swipe\(:[3-5]\)\?:\(up\|down\|left\|right\)\s\+.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,i3ConfigWorkspaceKeyword,i3ConfigAction
-syn match swayConfigBindGesturePinch /^\s*\(bindgesture\)\s\+pinch\(:[2-5]\)\?:\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\(+\(up\|down\|left\|right\|inward\|outward\|clockwise\|counterclockwise\)\)\?.*$/ contains=swayConfigBindKeyword,swayConfigBindGestureCommand,swayConfigBindGestureDirection,swayConfigBindGesturePinchDirection,i3ConfigWorkspaceKeyword,i3ConfigAction
-
-" floating
-syn keyword swayConfigFloatingKeyword floating contained
-syn match swayConfigFloating /^\s*floating\s\+\(enable\|disable\|toggle\)\s*$/ contains=swayConfigFloatingKeyword
-
-syn clear i3ConfigFloatingModifier
-syn keyword swayConfigFloatingModifier floating_modifier contained
-syn match swayConfigFloatingMouseAction /^\s\?.*floating_modifier\s\S\+\s\?\(normal\|inverted\|none\)\?$/ contains=swayConfigFloatingModifier,i3ConfigVariable
-
-" Gaps
-syn clear i3ConfigSmartBorderKeyword
-syn clear i3ConfigSmartBorder
-syn keyword swayConfigSmartBorderKeyword on no_gaps off contained
-syn match swayConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\|off\)\s\?$/ contains=swayConfigSmartBorderKeyword
-
-" Changing colors
-syn keyword swayConfigClientColorKeyword focused_tab_title contained
-syn match swayConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable,i3ConfigClientColorKeyword,swayConfigClientColorKeyword
-
-" Input config
+syn match i3ConfigBindArgument /--\(locked\|to-code\|no-repeat\|input-device=[:0-9a-zA-Z_/-]\+\|no-warn\)/ contained
+syn region i3ConfigBind start=/^\s*bind\(switch\|gesture\) / skip=/\\$/ end=/$/ contains=swayConfigBindKeyword,swayConfigBindswitch,swayConfigBindswitchArgument,swayConfigBindgesture,swayConfigBindgestureArgument,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean keepend
+
+syn match swayConfigBindBlockHeader /^\s*bind\(sym\|code\) .*{$/ contained contains=i3ConfigBindKeyword,i3ConfigBindArgument,i3ConfigParen
+syn match swayConfigBindBlockCombo /^\s\+\(--[a-z-]\+ \)*[$a-zA-Z0-9_+]\+ [a-z[]\@=/ contained contains=i3ConfigBindArgument,i3ConfigBindCombo
+syn region i3ConfigBind start=/^\s*bind\(sym\|code\) .*{$/ end=/^\s*}$/ contains=swayConfigBindBlockHeader,swayConfigBindBlockCombo,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean,i3ConfigComment,i3ConfigParen fold keepend extend
+" fix for extra long bindsym blocks that would be parsed incorrectly when scrolling up
+syn region i3ConfigBlockOrphan start=/^\s\+\S/ skip=/^\s\|^$/ end=/^}\?/ contains=swayConfigBindBlockCombo,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean,i3ConfigComment,i3ConfigParen keepend extend
+
+syn keyword i3ConfigClientOpts focused_tab_title contained
+
+syn region swayConfigExecBlock start=/exec\(_always\)\? {/ end=/^}$/ contains=i3ConfigExecKeyword,i3ConfigExecAlwaysKeyword,i3ConfigShCommand,i3ConfigShDelim,i3ConfigShOper,i3ConfigShParam,i3ConfigNumber,i3ConfigString,i3ConfigVariable,i3ConfigComment fold keepend extend
+
+syn keyword swayConfigFloatingModifierOpts normal inverse contained
+syn match i3ConfigKeyword /^floating_modifier [$a-zA-Z0-9+]\+ \(normal\|inverse\)$/ contains=i3ConfigVariable,i3ConfigBindModkey,swayConfigFloatingModifierOpts
+
+syn match i3ConfigKeyword /^hide_edge_borders --i3 \w*$/ contains=i3ConfigEdgeKeyword,i3ConfigShParam
+
+syn keyword i3ConfigBarOpts swaybar_command gaps height pango_markup status_edge_padding status_padding wrap_scroll tray_bindcode tray_bindsym icon_theme contained
+syn keyword i3ConfigBarOptVals overlay contained
+
+syn keyword i3ConfigExecActionKeyword swaymsg contained
+
+" Sway-only options
+" Xwayland
+syn keyword swayConfigXOpt enable disable force contained
+syn match i3ConfigKeyword /^xwayland \w*$/ contains=swayConfigXOpt
+
+" Inhibit idle
+syn keyword swayConfigInhibitKeyword inhibit_idle contained
+syn keyword swayConfigInhibitOpts focus fullscreen open none visible contained
+syn match i3ConfigAction /inhibit_idle \w*/ contained contains=swayConfigInhibitKeyword,swayConfigInhibitOpts
+
+" Bindswitch
+syn match swayConfigBindswitchArgument /--\(locked\|no-warn\|reload\)/ contained
+syn keyword swayConfigBindswitchType lid tablet contained
+syn keyword swayConfigBindswitchState toggle contained
+syn match swayConfigBindswitch /\(lid\|tablet\):\(on\|off\|toggle\) / contained contains=swayConfigBindswitchType,i3ConfigColonOperator,swayConfigBindswitchState,i3ConfigBoolean
+syn region i3ConfigBind start=/^\s*bindswitch\s\+.*{$/ end=/^\s*}$/ contains=swayConfigBindKeyword,swayConfigBindswitch,swayConfigBindswitchArgument,i3ConfigNumber,i3ConfigVariable,i3ConfigAction,i3ConfigActionKeyword,i3ConfigOption,i3ConfigSeparator,i3ConfigString,i3ConfigCriteria,swayConfigOutputCommand,i3ConfigBoolean,i3ConfigComment,i3ConfigParen fold keepend extend
+
+" Bindgesture
+syn match swayConfigBindgestureArgument /--\(exact\|input-device=[:0-9a-zA-Z_/-]\+\|no-warn\)/ contained
+syn keyword swayConfigBindgestureType hold swipe pinch contained
+syn keyword swayConfigBindgestureDir up down left right inward outward clockwise counterclockwise contained
+syn match swayConfigBindgesture /\(hold\(:[1-5]\)\?\|swipe\(:[3-5]\)\?\(:up\|:down\|:left\|:right\)\?\|pinch\(:[2-5]\)\?:\(+\?\(inward\|outward\|clockwise\|counterclockwise\|up\|down\|left\|right\)\)\+\) / contained contains=i3ConfigNumber,swayConfigBindgestureType,i3ConfigColonOperator,swayConfigBindgestureDir,i3ConfigBindModifier
+syn region i3ConfigBind start=/^\s*bindgesture\s\+.*{$/ end=/^\s*}$/ contains=swayConfigBindKeyword,swayConfigBindgesture,swayConfigBindgestureArgument,i3ConfigCriteria,i3ConfigAction,i3ConfigSeparator,i3ConfigActionKeyword,i3ConfigOption,i3ConfigString,i3ConfigNumber,i3ConfigVariable,i3ConfigBoolean,i3ConfigParen fold keepend extend
+
+" Tiling drag threshold
+syn match i3ConfigKeyword /^tiling_drag_threshold \d\+$/ contains=i3ConfigNumber
+
+" Titlebar commands
+syn match i3ConfigKeyword /^titlebar_border_thickness \(\d\+\|\$\S\+\)$/ contains=i3ConfigNumber,i3ConfigVariable
+syn match i3ConfigKeyword /^titlebar_padding \(\d\+\|\$\S\+\)\( \d\+\)\?$/ contains=i3ConfigNumber,i3ConfigVariable
+
+syn match swayConfigDeviceOps /[*,:;]/ contained
+
+" Input devices
syn keyword swayConfigInputKeyword input contained
-syn match swayConfigInput /^\s*input\s\+.*$/ contains=swayConfigInputKeyword
-
-" set display outputs
-syn match swayConfigOutput /^\s*output\s\+.*$/ contains=i3ConfigOutput
-
-" set display focus
-syn keyword swayConfigFocusKeyword focus contained
-syn keyword swayConfigFocusType output contained
-syn match swayConfigFocus /^\s*focus\soutput\s.*$/ contains=swayConfigFocusKeyword,swayConfigFocusType
-
-" focus follows mouse
-syn clear i3ConfigFocusFollowsMouseType
-syn clear i3ConfigFocusFollowsMouse
-
-syn keyword swayConfigFocusFollowsMouseType yes no always contained
-syn match swayConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\|always\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,swayConfigFocusFollowsMouseType
-
-
-" xwayland
-syn keyword swayConfigXwaylandKeyword xwayland contained
-syn match swayConfigXwaylandModifier /^\s*xwayland\s\+\(enable\|disable\|force\)\s\?$/ contains=swayConfigXwaylandKeyword
-
-" Group mode/bar
-syn clear i3ConfigBlock
-syn region swayConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigInitializeKeyword,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable,swayConfigInputKeyword,i3ConfigOutput transparent keepend extend
-
-"hi def link swayConfigError Error
-hi def link i3ConfigFloating Error
-hi def link swayConfigFloating Type
-hi def link swayConfigFloatingMouseAction Type
-hi def link swayConfigFocusKeyword Type
-hi def link swayConfigSmartBorderKeyword Type
-hi def link swayConfigInputKeyword Type
-hi def link swayConfigFocusFollowsMouseType Type
-hi def link swayConfigBindGestureCommand Identifier
-hi def link swayConfigBindGestureDirection Constant
-hi def link swayConfigBindGesturePinchDirection Constant
-hi def link swayConfigBindKeyword Identifier
-hi def link swayConfigClientColorKeyword Identifier
-hi def link swayConfigFloatingKeyword Identifier
-hi def link swayConfigFloatingModifier Identifier
-hi def link swayConfigFocusType Identifier
-hi def link swayConfigSmartBorder Identifier
-hi def link swayConfigXwaylandKeyword Identifier
-hi def link swayConfigXwaylandModifier Type
-hi def link swayConfigBindGesture PreProc
+syn keyword swayConfigInputType touchpad pointer keyboard touch tablet_tool tablet_pad switch contained
+syn match swayConfigInputTypePair /\<type:\w\+\>/ contained contains=i3ConfigColonOperator,swayConfigInputType
+syn region swayConfigInputStart start=/^input / end=/\s/ contained contains=swayConfigInputKeyword,swayConfigInputTypePair,i3ConfigString keepend extend
+syn keyword swayConfigInputOpts xkb_layout xkb_variant xkb_rules xkb_switch_layout xkb_numlock xkb_file xkb_capslock xkb_model repeat_delay repeat_rate map_to_output map_to_region map_from_region tool_mode accel_profile dwt dwtp drag_lock drag click_method middle_emulation tap events calibration_matrix natural_scroll left_handed pointer_accel scroll_button scroll_factor scroll_method tap_button_map contained
+syn keyword swayConfigInputOptVals absolute relative adaptive flat none button_areas clickfinger toggle two_finger edge on_button_down lrm lmr next prev pen eraser brush pencil airbrush disabled_on_external_mouse disable contained
+syn match swayConfigXkbOptsPairVal /:[0-9a-z_-]\+/ contained contains=i3ConfigColonOperator
+syn match swayConfigXkbOptsPair /[a-z]\+:[0-9a-z_-]\+/ contained contains=swayConfigXkbOptsPairVal
+syn match swayConfigInputXkbOpts /xkb_options \([a-z]\+:[0-9a-z_-]\+,\?\)\+/ contained contains=swayConfigXkbOptsPair,swayConfigDeviceOps
+syn region i3ConfigAction start=/input/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigInputStart,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigBoolean,swayConfigDeviceOps keepend transparent
+syn region i3ConfigInput start=/^input/ skip=/\\$/ end=/$/ contains=swayConfigInputStart,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigBoolean,swayConfigDeviceOps keepend
+syn region i3ConfigInput start=/^input .* {/ end=/}$/ contains=swayConfigInputStart,swayConfigInputXkbOpts,swayConfigInputOpts,swayConfigInputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigBoolean,swayConfigDeviceOps,i3ConfigParen keepend extend
+
+" Seat
+syn keyword swayConfigSeatKeyword seat contained
+syn keyword swayConfigSeatOpts attach cursor fallback hide_cursor idle_inhibit idle_wake keyboard_grouping shortcuts_inhibitor pointer_constraint xcursor_theme contained
+syn match swayConfigSeatOptVals /when-typing/ contained
+syn keyword swayConfigSeatOptVals move set press release none smart activate deactivate toggle escape enable disable contained
+syn region i3ConfigAction start=/seat/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps,swayConfigInputType keepend transparent
+syn region swayConfigSeat start=/seat/ skip=/\\$/ end=/$/ contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps,swayConfigInputType keepend
+syn region swayConfigSeat start=/seat .* {$/ end=/}$/ contains=swayConfigSeatKeyword,i3ConfigString,i3ConfigNumber,i3ConfigBoolean,swayConfigSeatOptVals,swayConfigSeatOpts,swayConfigDeviceOps,i3ConfigParen,swayConfigInputType keepend extend
+
+" Output monitors
+syn keyword swayConfigOutputKeyword output contained
+syn keyword swayConfigOutputOpts mode resolution res modeline position pos scale scale_filter subpixel background bg transform disable enable power dpms max_render_time adaptive_sync render_bit_depth contained
+syn keyword swayConfigOutputOptVals linear nearest smart rgb bgr vrgb vbgr none normal flipped fill stretch fit center tile solid_color clockwise anticlockwise toggle contained
+syn match swayConfigOutputOptVals /--custom\|flipped-\(90\|180\|270\)/ contained
+syn match swayConfigOutputFPS /@[0-9.]\+Hz/ contained
+syn match swayConfigOutputMode / [0-9]\+x[0-9]\+\(@[0-9.]\+Hz\)\?/ contained contains=swayConfigOutputFPS
+syn region i3ConfigAction start=/output/ skip=/\\$/ end=/\([,;]\|$\)/ contained contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps keepend transparent
+syn region swayConfigOutput start=/^output/ skip=/\\$/ end=/$/ contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps keepend
+syn region swayConfigOutput start=/^output .* {$/ end=/}$/ contains=swayConfigOutputKeyword,swayConfigOutputMode,swayConfigOutputOpts,swayConfigOutputOptVals,i3ConfigVariable,i3ConfigNumber,i3ConfigString,i3ConfigColor,i3ConfigBoolean,swayConfigDeviceOps,i3ConfigParen keepend extend
+
+" Define the highlighting.
+hi def link swayConfigFloatingModifierOpts i3ConfigOption
+hi def link swayConfigBindKeyword i3ConfigBindKeyword
+hi def link swayConfigXOpt i3ConfigOption
+hi def link swayConfigInhibitKeyword i3ConfigCommand
+hi def link swayConfigInhibitOpts i3ConfigOption
+hi def link swayConfigBindswitchArgument i3ConfigBindArgument
+hi def link swayConfigBindswitchType i3ConfigMoveType
+hi def link swayConfigBindswitchState i3ConfigMoveDir
+hi def link swayConfigBindgestureArgument i3ConfigBindArgument
+hi def link swayConfigBindgestureType i3ConfigMoveType
+hi def link swayConfigBindgestureDir i3ConfigMoveDir
+hi def link swayConfigDeviceOps i3ConfigOperator
+hi def link swayConfigInputKeyword i3ConfigCommand
+hi def link swayConfigInputType i3ConfigMoveType
+hi def link swayConfigInputTypePair i3ConfigMoveDir
+hi def link swayConfigInputOptVals i3ConfigShParam
+hi def link swayConfigInputOpts i3ConfigOption
+hi def link swayConfigXkbOptsPairVal i3ConfigString
+hi def link swayConfigXkbOptsPair i3ConfigShParam
+hi def link swayConfigInputXkbOpts i3ConfigOption
+hi def link swayConfigSeatKeyword i3ConfigCommand
+hi def link swayConfigSeatOptVals swayConfigInputOptVals
+hi def link swayConfigSeatOpts swayConfigInputOpts
+hi def link swayConfigOutputKeyword i3ConfigCommand
+hi def link swayConfigOutputOptVals swayConfigInputOptVals
+hi def link swayConfigOutputOpts swayConfigInputOpts
+hi def link swayConfigOutputFPS Constant
+hi def link swayConfigOutputMode i3ConfigNumber
let b:current_syntax = "swayconfig"
diff --git a/runtime/syntax/swig.vim b/runtime/syntax/swig.vim
new file mode 100644
index 0000000000..b62621264a
--- /dev/null
+++ b/runtime/syntax/swig.vim
@@ -0,0 +1,99 @@
+" Vim syntax file
+" Language: SWIG
+" Maintainer: Julien Marrec <julien.marrec 'at' gmail com>
+" Last Change: 2023 November 23
+
+if exists("b:current_syntax")
+ finish
+endif
+
+" Read the C++ syntax to start with
+runtime! syntax/cpp.vim
+unlet b:current_syntax
+
+" SWIG extentions
+syn keyword swigInclude %include %import %importfile %includefile %module
+
+syn keyword swigMostCommonDirective %alias %apply %beginfile %clear %constant %define %echo %enddef %endoffile
+syn keyword swigMostCommonDirective %extend %feature %director %fragment %ignore %inline
+syn keyword swigMostCommonDirective %keyword %name %namewarn %native %newobject %parms %pragma
+syn keyword swigMostCommonDirective %rename %template %typedef %typemap %types %varargs
+
+" SWIG: Language specific macros
+syn keyword swigOtherLanguageSpecific %luacode %go_import
+
+syn keyword swigCSharp %csattributes %csconst %csconstvalue %csmethodmodifiers %csnothrowexception
+syn keyword swigCSharp %dconstvalue %dmanifestconst %dmethodmodifiers
+
+syn keyword swigJava %javaconstvalue %javaexception %javamethodmodifiers %javaconst %nojavaexception
+
+syn keyword swigGuile %multiple_values %values_as_list %values_as_vector
+
+syn keyword swigPHP %rinit %rshutdown %minit %mshutdown
+
+syn keyword swigPython %pybinoperator %pybuffer_binary %pybuffer_mutable_binary %pybuffer_mutable_string %pybuffer_string
+syn keyword swigPython %pythonappend %pythonbegin %pythoncode %pythondynamic %pythonnondynamic %pythonprepend
+
+syn keyword swigRuby %markfunc %trackobjects %bang
+syn keyword swigScilab %scilabconst
+
+" SWIG: Insertion
+syn keyword swigInsertSection %insert %begin %runtime %header %wrapper %init
+
+" SWIG: Other directives
+syn keyword swigCstring %cstring_bounded_mutable %cstring_bounded_output %cstring_chunk_output %cstring_input_binary %cstring_mutable
+syn keyword swigCstring %cstring_output_allocate %cstring_output_allocate_size %cstring_output_maxsize %cstring_output_withsize
+syn keyword swigCWstring %cwstring_bounded_mutable %cwstring_bounded_output %cwstring_chunk_output %cwstring_input_binary %cwstring_mutable
+syn keyword swigCWstring %cwstring_output_allocate %cwstring_output_allocate_size %cwstring_output_maxsize %cwstring_output_withsize
+syn keyword swigCMalloc %malloc %calloc %realloc %free %sizeof %allocators
+
+syn keyword swigExceptionHandling %catches %raise %allowexception %exceptionclass %warn %warnfilter %exception
+syn keyword swigContract %contract %aggregate_check
+
+syn keyword swigDirective %addmethods %array_class %array_functions %attribute %attribute2 %attribute2ref
+syn keyword swigDirective %attribute_ref %attributeref %attributestring %attributeval %auto_ptr %callback
+syn keyword swigDirective %delete_array %delobject %extend_smart_pointer %factory %fastdispatch %freefunc %immutable
+syn keyword swigDirective %implicit %implicitconv %interface %interface_custom %interface_impl %intrusive_ptr %intrusive_ptr_no_wrap
+syn keyword swigDirective %mutable %naturalvar %nocallback %nocopyctor %nodefaultctor %nodefaultdtor %nonaturalvar %nonspace
+syn keyword swigDirective %nspace %pointer_cast %pointer_class %pointer_functions %predicate %proxycode
+syn keyword swigDirective %refobject %set_output %shared_ptr %std_comp_methods
+syn keyword swigDirective %std_nodefconst_type %typecheck %typemaps_string %unique_ptr %unrefobject %valuewrapper
+
+syn match swigVerbatimStartEnd "%[{}]"
+
+syn match swigUserDef "%\w\+"
+syn match swigVerbatimMacro "^\s*%#\w\+\%( .*\)\?$"
+
+" SWIG: typemap var and typemap macros (eg: $1, $*1_type, $&n_ltype, $self)
+syn match swigTypeMapVars "\$[*&_a-zA-Z0-9]\+"
+
+" Default highlighting
+hi def link swigInclude Include
+hi def link swigMostCommonDirective Structure
+hi def link swigDirective Macro
+hi def link swigContract swigExceptionHandling
+hi def link swigExceptionHandling Exception
+hi def link swigUserDef Function
+
+hi def link swigCMalloc Statement
+hi def link swigCstring Type
+hi def link swigCWstring Type
+
+hi def link swigCSharp swigOtherLanguageSpecific
+hi def link swigJava swigOtherLanguageSpecific
+hi def link swigGuile swigOtherLanguageSpecific
+hi def link swigPHP swigOtherLanguageSpecific
+hi def link swigPython swigOtherLanguageSpecific
+hi def link swigRuby swigOtherLanguageSpecific
+hi def link swigScilab swigOtherLanguageSpecific
+hi def link swigOtherLanguageSpecific Special
+
+hi def link swigInsertSection PreProc
+
+hi def link swigVerbatimStartEnd Statement
+hi def link swigVerbatimMacro Macro
+
+hi def link swigTypeMapVars SpecialChar
+
+let b:current_syntax = "swig"
+" vim: ts=8
diff --git a/runtime/syntax/synload.vim b/runtime/syntax/synload.vim
index 056e38bf79..3182c590b7 100644
--- a/runtime/syntax/synload.vim
+++ b/runtime/syntax/synload.vim
@@ -1,6 +1,7 @@
" Vim syntax support file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Apr 12
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This file sets up for syntax highlighting.
" It is loaded from "syntax.vim" and "manual.vim".
@@ -48,8 +49,8 @@ fun! s:SynSet()
" load each in sequence. Skip empty entries.
for name in split(s, '\.')
if !empty(name)
- exe "runtime! syntax/" . name . ".vim syntax/" . name . "/*.vim"
- exe "runtime! syntax/" . name . ".lua syntax/" . name . "/*.lua"
+ " XXX: "[.]" in the first pattern makes it a wildcard on Windows
+ exe $'runtime! syntax/{name}[.]{{vim,lua}} syntax/{name}/*.{{vim,lua}}'
endif
endfor
endif
diff --git a/runtime/syntax/syntax.vim b/runtime/syntax/syntax.vim
index 5ec99c7e05..887da05bfe 100644
--- a/runtime/syntax/syntax.vim
+++ b/runtime/syntax/syntax.vim
@@ -1,6 +1,7 @@
" Vim syntax support file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2022 Apr 12
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" This file is used for ":syntax on".
" It installs the autocommands and starts highlighting for all buffers.
diff --git a/runtime/syntax/tasm.vim b/runtime/syntax/tasm.vim
index 1d6e570752..b8b5e6992b 100644
--- a/runtime/syntax/tasm.vim
+++ b/runtime/syntax/tasm.vim
@@ -1,6 +1,6 @@
" Vim syntax file
" Language: TASM: turbo assembler by Borland
-" Maintaner: FooLman of United Force <foolman@bigfoot.com>
+" Maintainer: FooLman of United Force <foolman@bigfoot.com>
" Last Change: 2012 Feb 03 by Thilo Six, and 2018 Nov 27.
" quit when a syntax file was already loaded
diff --git a/runtime/syntax/template.vim b/runtime/syntax/template.vim
index 5bf580fc11..a59b7b0af9 100644
--- a/runtime/syntax/template.vim
+++ b/runtime/syntax/template.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Generic template
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2019 May 06
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/tf.vim b/runtime/syntax/tf.vim
index 47775b8637..df6adcf819 100644
--- a/runtime/syntax/tf.vim
+++ b/runtime/syntax/tf.vim
@@ -27,7 +27,7 @@ syn keyword tfVar bamf bg_output borg clearfull cleardone clock connect contai
syn keyword tfVar emulation end_color gag gethostbyname gpri hook hilite contained
syn keyword tfVar hiliteattr histsize hpri insert isize istrip kecho contained
syn keyword tfVar kprefix login lp lpquote maildelay matching max_iter contained
-syn keyword tfVar max_recur mecho more mprefix oldslash promt_sec contained
+syn keyword tfVar max_recur mecho more mprefix oldslash prompt_sec contained
syn keyword tfVar prompt_usec proxy_host proxy_port ptime qecho qprefix contained
syn keyword tfVar quite quitdone redef refreshtime scroll shpause snarf sockmload contained
syn keyword tfVar start_color tabsize telopt sub time_format visual contained
diff --git a/runtime/syntax/tpp.vim b/runtime/syntax/tpp.vim
index ca64b5dce1..e2b307b2a2 100644
--- a/runtime/syntax/tpp.vim
+++ b/runtime/syntax/tpp.vim
@@ -2,8 +2,8 @@
" Language: tpp - Text Presentation Program
" Maintainer: Debian Vim Maintainers
" Former Maintainer: Gerfried Fuchs <alfie@ist.org>
-" Last Change: 2018 Dec 27
-" URL: https://salsa.debian.org/vim-team/vim-debian/master/syntax/tpp.vim
+" Last Change: 2023 Jan 16
+" URL: https://salsa.debian.org/vim-team/vim-debian/blob/main/syntax/tpp.vim
" Filenames: *.tpp
" License: BSD
"
diff --git a/runtime/syntax/tsalt.vim b/runtime/syntax/tsalt.vim
index 8dd2a24df9..6f74ad2eb3 100644
--- a/runtime/syntax/tsalt.vim
+++ b/runtime/syntax/tsalt.vim
@@ -83,11 +83,11 @@ syn keyword tsaltFunction vGetChrs vGetChrsA vPutChr vPutChrs
syn keyword tsaltFunction vPutChrsA vRstrArea vSaveArea
" Dynamic Data Exchange (DDE) Operations
-syn keyword tsaltFunction DDEExecute DDEInitate DDEPoke DDERequest
+syn keyword tsaltFunction DDEExecute DDEInitiate DDEPoke DDERequest
syn keyword tsaltFunction DDETerminate DDETerminateAll
"END FUNCTIONS
-"PREDEFINED VARAIABLES
+"PREDEFINED VARIABLES
syn keyword tsaltSysVar _add_lf _alarm_on _answerback_str _asc_rcrtrans
syn keyword tsaltSysVar _asc_remabort _asc_rlftrans _asc_scpacing
syn keyword tsaltSysVar _asc_scrtrans _asc_secho _asc_slpacing
@@ -106,7 +106,7 @@ syn keyword tsaltSysVar _scr_chk_key _script_dir _sound_on
syn keyword tsaltSysVar _strip_high _swap_bs _telix_dir _up_dir
syn keyword tsaltSysVar _usage_fname _zmodauto _zmod_rcrash
syn keyword tsaltSysVar _zmod_scrash
-"END PREDEFINED VARAIABLES
+"END PREDEFINED VARIABLES
"TYPE
syn keyword tsaltType str int
diff --git a/runtime/syntax/tsscl.vim b/runtime/syntax/tsscl.vim
index fd2a5e2ba9..df804b2f88 100644
--- a/runtime/syntax/tsscl.vim
+++ b/runtime/syntax/tsscl.vim
@@ -22,7 +22,7 @@ syn case ignore
"
"
-" Begin syntax definitions for tss geomtery file.
+" Begin syntax definitions for tss geometry file.
"
" Load TSS geometry syntax file
diff --git a/runtime/syntax/typescript.vim b/runtime/syntax/typescript.vim
index af71938a8e..5389c21497 100644
--- a/runtime/syntax/typescript.vim
+++ b/runtime/syntax/typescript.vim
@@ -1,9 +1,9 @@
" Vim syntax file
" Language: TypeScript
-" Maintainer: Bram Moolenaar, Herrington Darkholme
-" Last Change: 2019 Nov 30
+" Maintainer: Herrington Darkholme
+" Last Change: 2023 Aug 13
" Based On: Herrington Darkholme's yats.vim
-" Changes: Go to https:github.com/HerringtonDarkholme/yats.vim for recent changes.
+" Changes: Go to https://github.com/HerringtonDarkholme/yats.vim for recent changes.
" Origin: https://github.com/othree/yajs
" Credits: Kao Wei-Ko(othree), Jose Elera Campana, Zhao Yi, Claudio Fleiner, Scott Shattuck
" (This file is based on their hard work), gumnos (From the #vim
diff --git a/runtime/syntax/typescriptreact.vim b/runtime/syntax/typescriptreact.vim
index c4c2d45745..1c510459f5 100644
--- a/runtime/syntax/typescriptreact.vim
+++ b/runtime/syntax/typescriptreact.vim
@@ -1,9 +1,9 @@
" Vim syntax file
" Language: TypeScript with React (JSX)
-" Maintainer: Bram Moolenaar
-" Last Change: 2019 Nov 30
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
" Based On: Herrington Darkholme's yats.vim
-" Changes: See https:github.com/HerringtonDarkholme/yats.vim
+" Changes: See https://github.com/HerringtonDarkholme/yats.vim
" Credits: See yats.vim on github
if !exists("main_syntax")
diff --git a/runtime/syntax/unison.vim b/runtime/syntax/unison.vim
new file mode 100644
index 0000000000..fed7551043
--- /dev/null
+++ b/runtime/syntax/unison.vim
@@ -0,0 +1,103 @@
+" Vim syntax file
+"
+" Language: unison
+" Maintainer: Anton Parkhomenko <anton@chuwy.me>
+" Last Change: Aug 7, 2023
+" Original Author: John Williams, Paul Chiusano and Rúnar Bjarnason
+
+if exists("b:current_syntax")
+ finish
+endif
+
+syntax include @markdown $VIMRUNTIME/syntax/markdown.vim
+
+syn cluster markdownLikeDocs contains=markdownBold,markdownItalic,markdownLinkText,markdownListMarker,markdownOrderedListMarker,markdownH1,markdownH2,markdownH3,markdownH4,markdownH5,markdownH6
+
+syn match unisonOperator "[-!#$%&\*\+/<=>\?@\\^|~]"
+syn match unisonDelimiter "[\[\](){},.]"
+
+" Strings and constants
+syn match unisonSpecialChar contained "\\\([0-9]\+\|o[0-7]\+\|x[0-9a-fA-F]\+\|[\"\\'&\\abfnrtv]\|^[A-Z^_\[\\\]]\)"
+syn match unisonSpecialChar contained "\\\(NUL\|SOH\|STX\|ETX\|EOT\|ENQ\|ACK\|BEL\|BS\|HT\|LF\|VT\|FF\|CR\|SO\|SI\|DLE\|DC1\|DC2\|DC3\|DC4\|NAK\|SYN\|ETB\|CAN\|EM\|SUB\|ESC\|FS\|GS\|RS\|US\|SP\|DEL\)"
+syn match unisonSpecialCharError contained "\\&\|'''\+"
+syn region unisonString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=unisonSpecialChar
+syn match unisonCharacter "[^a-zA-Z0-9_']'\([^\\]\|\\[^']\+\|\\'\)'"lc=1 contains=unisonSpecialChar,unisonSpecialCharError
+syn match unisonCharacter "^'\([^\\]\|\\[^']\+\|\\'\)'" contains=unisonSpecialChar,unisonSpecialCharError
+syn match unisonNumber "\<[0-9]\+\>\|\<0[xX][0-9a-fA-F]\+\>\|\<0[oO][0-7]\+\>"
+syn match unisonFloat "\<[0-9]\+\.[0-9]\+\([eE][-+]\=[0-9]\+\)\=\>"
+
+" Keyword definitions. These must be patterns instead of keywords
+" because otherwise they would match as keywords at the start of a
+" "literate" comment (see lu.vim).
+syn match unisonModule "\<namespace\>"
+syn match unisonImport "\<use\>"
+syn match unisonTypedef "\<\(unique\|structural\|∀\|forall\)\>"
+syn match unisonStatement "\<\(ability\|do\|type\|where\|match\|cases\|;\|let\|with\|handle\)\>"
+syn match unisonConditional "\<\(if\|else\|then\)\>"
+
+syn match unisonBoolean "\<\(true\|false\)\>"
+
+syn match unisonType "\<\C[A-Z][0-9A-Za-z_'!]*\>"
+syn match unisonName "\<\C[a-z_][0-9A-Za-z_'!]*\>"
+
+" Comments
+syn match unisonLineComment "---*\([^-!#$%&\*\+./<=>\?@\\^|~].*\)\?$"
+syn region unisonBlockComment start="{-" end="-}" contains=unisonBlockComment
+syn region unisonBelowFold start="^---" skip="." end="." contains=unisonBelowFold
+
+" Docs
+syn region unisonDocBlock matchgroup=unisonDoc start="{{" end="}}" contains=unisonDocTypecheck,unisonDocQuasiquote,unisonDocDirective,unisonDocCode,unisonDocCodeInline,unisonDocCodeRaw,unisonDocMono,@markdownLikeDocs
+syn region unisonDocQuasiquote contained matchgroup=unisonDocQuote start="{{" end= "}}" contains=TOP
+syn region unisonDocCode contained matchgroup=unisonDocCode start="^\s*```\s*$" end="^\s*```\s*$" contains=TOP
+syn region unisonDocTypecheck contained matchgroup=unisonDocCode start="^\s*@typecheck\s*```\s*$" end="^\s*```\s*$" contains=TOP
+syn region unisonDocCodeRaw contained matchgroup=unisonDocCode start="^\s*```\s*raw\s*$" end="^\s*```\s*$" contains=NoSyntax
+syn region unisonDocCodeInline contained matchgroup=unisonDocCode start="`\@<!``" end="`\@<!``" contains=TOP
+syn match unisonDocMono "''[^']*''"
+syn region unisonDocDirective contained matchgroup=unisonDocDirective start="\(@\([a-zA-Z0-9_']*\)\)\?{{\@!" end="}" contains=TOP
+
+syn match unisonDebug "\<\(todo\|bug\|Debug.trace\|Debug.evalToText\)\>"
+
+" things like
+" > my_func 1 3
+" test> Function.tap.tests.t1 = check let
+" use Nat == +
+" ( 99, 100 ) === (withInitialValue 0 do
+" : : :
+syn match unisonWatch "^[A-Za-z]*>"
+
+hi def link unisonWatch Debug
+hi def link unisonDocMono Delimiter
+hi def link unisonDocDirective Import
+hi def link unisonDocQuote Delimiter
+hi def link unisonDocCode Delimiter
+hi def link unisonDoc String
+hi def link unisonBelowFold Comment
+hi def link unisonBlockComment Comment
+hi def link unisonBoolean Boolean
+hi def link unisonCharacter Character
+hi def link unisonComment Comment
+hi def link unisonConditional Conditional
+hi def link unisonConditional Conditional
+hi def link unisonDebug Debug
+hi def link unisonDelimiter Delimiter
+hi def link unisonDocBlock String
+hi def link unisonDocDirective Import
+hi def link unisonDocIncluded Import
+hi def link unisonFloat Float
+hi def link unisonImport Include
+hi def link unisonLineComment Comment
+hi def link unisonLink Type
+hi def link unisonName Identifier
+hi def link unisonNumber Number
+hi def link unisonOperator Operator
+hi def link unisonSpecialChar SpecialChar
+hi def link unisonSpecialCharError Error
+hi def link unisonStatement Statement
+hi def link unisonString String
+hi def link unisonType Type
+hi def link unisonTypedef Typedef
+
+
+let b:current_syntax = "unison"
+
+" Options for vi: ts=8 sw=2 sts=2 nowrap noexpandtab ft=vim
diff --git a/runtime/syntax/urlshortcut.vim b/runtime/syntax/urlshortcut.vim
new file mode 100644
index 0000000000..f6cc3835a2
--- /dev/null
+++ b/runtime/syntax/urlshortcut.vim
@@ -0,0 +1,14 @@
+" Vim syntax file
+" Language: MS Windows URL shortcut file
+" Maintainer: ObserverOfTime <chronobserver@disroot.org>
+" LastChange: 2023-06-04
+
+" Quit when a syntax file was already loaded.
+if exists("b:current_syntax")
+ finish
+endif
+
+" Just use the dosini syntax for now
+runtime! syntax/dosini.vim
+
+let b:current_syntax = "urlshortcut"
diff --git a/runtime/syntax/vgrindefs.vim b/runtime/syntax/vgrindefs.vim
index 3de31b1437..a194c108cb 100644
--- a/runtime/syntax/vgrindefs.vim
+++ b/runtime/syntax/vgrindefs.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Vgrindefs
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2005 Jun 20
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" The Vgrindefs file is used to specify a language for vgrind
diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim
index a1c39e4dcf..fcd50bccd2 100644
--- a/runtime/syntax/vim.vim
+++ b/runtime/syntax/vim.vim
@@ -53,7 +53,7 @@ syn case ignore
syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo
" Default highlighting groups {{{2
-syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineFold CursorLineNr CursorLineSign DiffAdd DiffChange DiffDelete DiffText Directory EndOfBuffer ErrorMsg FoldColumn Folded IncSearch LineNr MatchParen Menu MessageWindow ModeMsg MoreMsg NonText Normal Pmenu PmenuSbar PmenuSel PmenuThumb Question QuickFixLine Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu
+syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineFold CursorLineNr CursorLineSign DiffAdd DiffChange DiffDelete DiffText Directory EndOfBuffer ErrorMsg FoldColumn Folded IncSearch LineNr MatchParen Menu MessageWindow ModeMsg MoreMsg NonText Normal Pmenu PmenuExtra PmenuExtraSel PmenuKind PmenuKindSel PmenuSbar PmenuSel PmenuThumb Question QuickFixLine Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu
syn match vimHLGroup contained "Conceal"
syn keyword vimOnlyHLGroup contained LineNrAbove LineNrBelow StatusLineTerm Terminal VisualNOS
syn keyword nvimHLGroup contained Substitute TermCursor TermCursorNC
@@ -61,7 +61,7 @@ syn keyword nvimHLGroup contained Substitute TermCursor TermCursorNC
syn case match
" Special Vim Highlighting (not automatic) {{{1
-" Set up folding commands {{{2
+" Set up folding commands for this syntax highlighting file {{{2
if exists("g:vimsyn_folding") && g:vimsyn_folding =~# '[afhlmpPrt]'
if g:vimsyn_folding =~# 'a'
com! -nargs=* VimFolda <args> fold
@@ -368,7 +368,7 @@ syn match vimSetMod contained "&vim\=\|[!&?<]\|all&"
" Let: {{{2
" ===
syn keyword vimLet let unl[et] skipwhite nextgroup=vimVar,vimFuncVar,vimLetHereDoc
-VimFoldh syn region vimLetHereDoc matchgroup=vimLetHereDocStart start='=<<\s\+\%(trim\s\+\)\=\%(eval\s\+\)\=\s*\z(\L\S*\)' matchgroup=vimLetHereDocStop end='^\s*\z1\s*$'
+VimFoldh syn region vimLetHereDoc matchgroup=vimLetHereDocStart start='=<<\s*\%(trim\s\+\%(eval\s\+\)\=\|eval\s\+\%(trim\s\+\)\=\)\=\z(\L\S*\)' matchgroup=vimLetHereDocStop end='^\s*\z1\s*$'
" Abbreviations: {{{2
" =============
diff --git a/runtime/syntax/viminfo.vim b/runtime/syntax/viminfo.vim
index 667e1bab2a..06c59766d7 100644
--- a/runtime/syntax/viminfo.vim
+++ b/runtime/syntax/viminfo.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Vim .viminfo file
-" Maintainer: Bram Moolenaar <Bram@vim.org>
-" Last Change: 2016 Jun 05
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Aug 10
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
" Quit when a (custom) syntax file was already loaded
if exists("b:current_syntax")
diff --git a/runtime/syntax/wast.vim b/runtime/syntax/wast.vim
deleted file mode 100644
index 245d5f6f19..0000000000
--- a/runtime/syntax/wast.vim
+++ /dev/null
@@ -1,84 +0,0 @@
-" Vim syntax file
-" Language: WebAssembly
-" Maintainer: rhysd <lin90162@yahoo.co.jp>
-" Last Change: Jul 29, 2018
-" For bugs, patches and license go to https://github.com/rhysd/vim-wasm
-
-if exists("b:current_syntax")
- finish
-endif
-
-let s:cpo_save = &cpo
-set cpo&vim
-
-syn cluster wastCluster contains=wastModule,wastInstWithType,wastInstGeneral,wastParamInst,wastControlInst,wastString,wastNamedVar,wastUnnamedVar,wastFloat,wastNumber,wastComment,wastList,wastType
-
-" Instructions
-" https://webassembly.github.io/spec/core/text/instructions.html
-" Note: memarg (align=,offset=) can be added to memory instructions
-syn match wastInstWithType "\%((\s*\)\@<=\<\%(i32\|i64\|f32\|f64\|memory\)\.[[:alnum:]_]\+\%(/\%(i32\|i64\|f32\|f64\)\)\=\>\%(\s\+\%(align\|offset\)=\)\=" contained display
-syn match wastInstGeneral "\%((\s*\)\@<=\<[[:alnum:]_]\+\>" contained display
-" https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
-syn match wastControlInst "\%((\s*\)\@<=\<\%(block\|end\|loop\|if\|else\|unreachable\|nop\|br\|br_if\|br_table\|return\|call\|call_indirect\)\>" contained display
-" https://webassembly.github.io/spec/core/text/instructions.html#parametric-instructions
-syn match wastParamInst "\%((\s*\)\@<=\<\%(drop\|select\)\>" contained display
-
-" Identifiers
-" https://webassembly.github.io/spec/core/text/values.html#text-id
-syn match wastNamedVar "$\+[[:alnum:]!#$%&'∗./:=><?@\\^_`~+-]*" contained display
-syn match wastUnnamedVar "$\+\d\+[[:alnum:]!#$%&'∗./:=><?@\\^_`~+-]\@!" contained display
-
-" String literals
-" https://webassembly.github.io/spec/core/text/values.html#strings
-syn region wastString start=+"+ skip=+\\\\\|\\"+ end=+"+ contained contains=wastStringSpecial
-syn match wastStringSpecial "\\\x\x\|\\[tnr'\\\"]\|\\u\x\+" contained containedin=wastString
-
-" Float literals
-" https://webassembly.github.io/spec/core/text/values.html#floating-point
-syn match wastFloat "\<-\=\d\%(_\=\d\)*\%(\.\d\%(_\=\d\)*\)\=\%([eE][-+]\=\d\%(_\=\d\)*\)\=" display contained
-syn match wastFloat "\<-\=0x\x\%(_\=\d\)*\%(\.\x\%(_\=\x\)*\)\=\%([pP][-+]\=\d\%(_\=\d\)*\)\=" display contained
-syn keyword wastFloat inf nan contained
-
-" Integer literals
-" https://webassembly.github.io/spec/core/text/values.html#integers
-syn match wastNumber "\<-\=\d\%(_\=\d\)*\>" display contained
-syn match wastNumber "\<-\=0x\x\%(_\=\x\)*\>" display contained
-
-" Comments
-" https://webassembly.github.io/spec/core/text/lexical.html#comments
-syn region wastComment start=";;" end="$" display
-syn region wastComment start="(;;\@!" end=";)"
-
-syn region wastList matchgroup=wastListDelimiter start="(;\@!" matchgroup=wastListDelimiter end=";\@<!)" contains=@wastCluster
-
-" Types
-" https://webassembly.github.io/spec/core/text/types.html
-syn keyword wastType i64 i32 f64 f32 param result anyfunc mut contained
-syn match wastType "\%((\_s*\)\@<=func\%(\_s*[()]\)\@=" display contained
-
-" Modules
-" https://webassembly.github.io/spec/core/text/modules.html
-syn keyword wastModule module type export import table memory global data elem contained
-syn match wastModule "\%((\_s*\)\@<=func\%(\_s\+\$\)\@=" display contained
-
-syn sync lines=100
-
-hi def link wastModule PreProc
-hi def link wastListDelimiter Delimiter
-hi def link wastInstWithType Operator
-hi def link wastInstGeneral Operator
-hi def link wastControlInst Statement
-hi def link wastParamInst Conditional
-hi def link wastString String
-hi def link wastStringSpecial Special
-hi def link wastNamedVar Identifier
-hi def link wastUnnamedVar PreProc
-hi def link wastFloat Float
-hi def link wastNumber Number
-hi def link wastComment Comment
-hi def link wastType Type
-
-let b:current_syntax = "wast"
-
-let &cpo = s:cpo_save
-unlet s:cpo_save
diff --git a/runtime/syntax/wat.vim b/runtime/syntax/wat.vim
new file mode 100644
index 0000000000..a6b926be98
--- /dev/null
+++ b/runtime/syntax/wat.vim
@@ -0,0 +1,97 @@
+" Vim syntax file
+" Language: WebAssembly
+" Maintainer: rhysd <lin90162@yahoo.co.jp>
+" Last Change: Nov 14, 2023
+" For bugs, patches and license go to https://github.com/rhysd/vim-wasm
+
+if exists("b:current_syntax")
+ finish
+endif
+
+let s:cpo_save = &cpo
+set cpo&vim
+
+syn cluster watNotTop contains=watModule,watInstWithType,watInstGetSet,watInstGeneral,watParamInst,watControlInst,watSimdInst,watString,watNamedVar,watUnnamedVar,watFloat,watNumber,watComment,watList,watType
+
+" Instructions
+" https://webassembly.github.io/spec/core/text/instructions.html
+" Note: memarg (align=,offset=) can be added to memory instructions
+syn match watInstWithType "\%((\s*\)\@<=\<\%(i32\|i64\|f32\|f64\|memory\)\.[[:alnum:]_]\+\%(/\%(i32\|i64\|f32\|f64\)\)\=\>\%(\s\+\%(align\|offset\)=\)\=" contained display
+syn match watInstGeneral "\%((\s*\)\@<=\<[[:alnum:]_]\+\>" contained display
+syn match watInstGetSet "\%((\s*\)\@<=\<\%(local\|global\)\.\%(get\|set\)\>" contained display
+" https://webassembly.github.io/spec/core/text/instructions.html#control-instructions
+syn match watControlInst "\%((\s*\)\@<=\<\%(block\|end\|loop\|if\|then\|else\|unreachable\|nop\|br\|br_if\|br_table\|return\|call\|call_indirect\)\>" contained display
+" https://webassembly.github.io/spec/core/text/instructions.html#parametric-instructions
+syn match watParamInst "\%((\s*\)\@<=\<\%(drop\|select\)\>" contained display
+" SIMD instructions
+" https://webassembly.github.io/simd/core/text/instructions.html#simd-instructions
+syn match watSimdInst "\<\%(v128\|i8x16\|i16x8\|i32x4\|i64x2\|f32x4\|f64x2)\)\.[[:alnum:]_]\+\%(\s\+\%(i8x16\|i16x8\|i32x4\|i64x2\|f32x4\|f64x2\)\)\=\>" contained display
+
+" Identifiers
+" https://webassembly.github.io/spec/core/text/values.html#text-id
+syn match watNamedVar "$\+[[:alnum:]!#$%&'∗./:=><?@\\^_`~+-]*" contained contains=watEscapeUtf8
+syn match watUnnamedVar "$\+\d\+[[:alnum:]!#$%&'∗./:=><?@\\^_`~+-]\@!" contained display
+" Presuming the source text is itself encoded correctly, strings that do not
+" contain any uses of hexadecimal byte escapes are always valid names.
+" https://webassembly.github.io/spec/core/text/values.html#names
+syn match watEscapedUtf8 "\\\x\{1,6}" contained containedin=watNamedVar display
+
+" String literals
+" https://webassembly.github.io/spec/core/text/values.html#strings
+syn region watString start=+"+ skip=+\\\\\|\\"+ end=+"+ contained contains=watStringSpecial
+syn match watStringSpecial "\\\x\x\|\\[tnr'\\\"]\|\\u\x\+" contained containedin=watString display
+
+" Float literals
+" https://webassembly.github.io/spec/core/text/values.html#floating-point
+syn match watFloat "\<-\=\d\%(_\=\d\)*\%(\.\d\%(_\=\d\)*\)\=\%([eE][-+]\=\d\%(_\=\d\)*\)\=" display contained
+syn match watFloat "\<-\=0x\x\%(_\=\x\)*\%(\.\x\%(_\=\x\)*\)\=\%([pP][-+]\=\d\%(_\=\d\)*\)\=" display contained
+syn keyword watFloat inf nan contained
+syn match watFloat "nan:0x\x\%(_\=\x\)*" display contained
+
+" Integer literals
+" https://webassembly.github.io/spec/core/text/values.html#integers
+syn match watNumber "\<-\=\d\%(_\=\d\)*\>" display contained
+syn match watNumber "\<-\=0x\x\%(_\=\x\)*\>" display contained
+
+" Comments
+" https://webassembly.github.io/spec/core/text/lexical.html#comments
+syn region watComment start=";;" end="$"
+syn region watComment start="(;;\@!" end=";)"
+
+syn region watList matchgroup=watListDelimiter start="(;\@!" matchgroup=watListDelimiter end=";\@<!)" contains=@watNotTop
+
+" Types
+" https://webassembly.github.io/spec/core/text/types.html
+" Note: `mut` was changed to `const`/`var` at Wasm 2.0
+syn keyword watType i64 i32 f64 f32 param result funcref func externref extern mut v128 const var contained
+syn match watType "\%((\_s*\)\@<=func\%(\_s*[()]\)\@=" display contained
+
+" Modules
+" https://webassembly.github.io/spec/core/text/modules.html
+syn keyword watModule module type export import table memory global data elem contained
+syn match watModule "\%((\_s*\)\@<=func\%(\_s\+\$\)\@=" display contained
+
+syn sync maxlines=100
+
+hi def link watModule PreProc
+hi def link watListDelimiter Delimiter
+hi def link watInstWithType Operator
+hi def link watInstGetSet Operator
+hi def link watInstGeneral Operator
+hi def link watControlInst Statement
+hi def link watSimdInst Operator
+hi def link watParamInst Conditional
+hi def link watString String
+hi def link watStringSpecial Special
+hi def link watNamedVar Identifier
+hi def link watUnnamedVar PreProc
+hi def link watFloat Float
+hi def link watNumber Number
+hi def link watComment Comment
+hi def link watType Type
+hi def link watEscapedUtf8 Special
+
+let b:current_syntax = "wat"
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/runtime/syntax/wget.vim b/runtime/syntax/wget.vim
index 8178d02bad..93206c2005 100644
--- a/runtime/syntax/wget.vim
+++ b/runtime/syntax/wget.vim
@@ -1,7 +1,7 @@
" Vim syntax file
-" Language: Wget configuration file (/etc/wgetrc ~/.wgetrc)
+" Language: Wget configuration file (/etc/wgetrc ~/.wgetrc)
" Maintainer: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2022 Apr 28
+" Last Change: 2023 Nov 05
" GNU Wget 1.21 built on linux-gnu.
@@ -12,7 +12,7 @@ endif
let s:cpo_save = &cpo
set cpo&vim
-syn match wgetComment "#.*$" contains=wgetTodo contained
+syn match wgetComment "#.*" contains=wgetTodo contained
syn keyword wgetTodo TODO NOTE FIXME XXX contained
@@ -21,208 +21,206 @@ syn region wgetString start=+'+ skip=+\\\\\|\\'+ end=+'+ contained oneline
syn case ignore
-syn keyword wgetBoolean on off yes no contained
-syn keyword wgetNumber inf contained
-
-syn match wgetNumber "\<\d\+>" contained
-syn match wgetQuota "\<\d\+[kmgt]\>" contained
-syn match wgetTime "\<\d\+[smhdw]\>" contained
+syn keyword wgetBoolean on off yes no contained
+syn keyword wgetNumber inf contained
+syn match wgetNumber "\<\d\+>" contained
+syn match wgetQuota "\<\d\+[kmgt]\>" contained
+syn match wgetTime "\<\d\+[smhdw]\>" contained
"{{{ Commands
let s:commands =<< trim EOL
accept
- accept_regex
- add_host_dir
- adjust_extension
- always_rest
- ask_password
- auth_no_challenge
+ accept-regex
+ add-host-dir
+ adjust-extension
+ always-rest
+ ask-password
+ auth-no-challenge
background
- backup_converted
+ backup-converted
backups
base
- bind_address
- bind_dns_address
- body_data
- body_file
- ca_certificate
- ca_directory
+ bind-address
+ bind-dns-address
+ body-data
+ body-file
+ ca-certificate
+ ca-directory
cache
certificate
- certificate_type
- check_certificate
- choose_config
+ certificate-type
+ check-certificate
+ choose-config
ciphers
compression
- connect_timeout
- content_disposition
- content_on_error
+ connect-timeout
+ content-disposition
+ content-on-error
continue
- convert_file_only
- convert_links
+ convert-file-only
+ convert-links
cookies
- crl_file
- cut_dirs
+ crl-file
+ cut-dirs
debug
- default_page
- delete_after
- dns_cache
- dns_servers
- dns_timeout
- dir_prefix
- dir_struct
+ default-page
+ delete-after
+ dns-cache
+ dns-servers
+ dns-timeout
+ dir-prefix
+ dir-struct
domains
- dot_bytes
- dots_in_line
- dot_spacing
- dot_style
- egd_file
- exclude_directories
- exclude_domains
- follow_ftp
- follow_tags
- force_html
- ftp_passwd
- ftp_password
- ftp_user
- ftp_proxy
- ftps_clear_data_connection
- ftps_fallback_to_ftp
- ftps_implicit
- ftps_resume_ssl
+ dot-bytes
+ dots-in-line
+ dot-spacing
+ dot-style
+ egd-file
+ exclude-directories
+ exclude-domains
+ follow-ftp
+ follow-tags
+ force-html
+ ftp-passwd
+ ftp-password
+ ftp-user
+ ftp-proxy
+ ftps-clear-data-connection
+ ftps-fallback-to-ftp
+ ftps-implicit
+ ftps-resume-ssl
hsts
- hsts_file
- ftp_stmlf
+ hsts-file
+ ftp-stmlf
glob
header
- html_extension
+ html-extension
htmlify
- http_keep_alive
- http_passwd
- http_password
- http_proxy
- https_proxy
- https_only
- http_user
- if_modified_since
- ignore_case
- ignore_length
- ignore_tags
- include_directories
- inet4_only
- inet6_only
+ http-keep-alive
+ http-passwd
+ http-password
+ http-proxy
+ https-proxy
+ https-only
+ http-user
+ if-modified-since
+ ignore-case
+ ignore-length
+ ignore-tags
+ include-directories
+ inet4-only
+ inet6-only
input
- input_meta_link
+ input-meta-link
iri
- keep_bad_hash
- keep_session_cookies
- kill_longer
- limit_rate
- load_cookies
+ keep-bad-hash
+ keep-session-cookies
+ kill-longer
+ limit-rate
+ load-cookies
locale
- local_encoding
+ local-encoding
logfile
login
- max_redirect
- metalink_index
- metalink_over_http
+ max-redirect
+ metalink-index
+ metalink-over-http
method
mirror
netrc
- no_clobber
- no_config
- no_parent
- no_proxy
+ no-clobber
+ no-config
+ no-parent
+ no-proxy
numtries
- output_document
- page_requisites
- passive_ftp
+ output-document
+ page-requisites
+ passive-ftp
passwd
password
- pinned_pubkey
- post_data
- post_file
- prefer_family
- preferred_location
- preserve_permissions
- private_key
- private_key_type
+ pinned-pubkey
+ post-data
+ post-file
+ prefer-family
+ preferred-location
+ preserve-permissions
+ private-key
+ private-key-type
progress
- protocol_directories
- proxy_passwd
- proxy_password
- proxy_user
+ protocol-directories
+ proxy-passwd
+ proxy-password
+ proxy-user
quiet
quota
- random_file
- random_wait
- read_timeout
- rec_level
+ random-file
+ random-wait
+ read-timeout
+ rec-level
recursive
referer
- regex_type
+ regex-type
reject
- rejected_log
- reject_regex
- relative_only
- remote_encoding
- remove_listing
- report_speed
- restrict_file_names
- retr_symlinks
- retry_connrefused
- retry_on_host_error
- retry_on_http_error
+ rejected-log
+ reject-regex
+ relative-only
+ remote-encoding
+ remove-listing
+ report-speed
+ restrict-file-names
+ retr-symlinks
+ retry-connrefused
+ retry-on-host-error
+ retry-on-http-error
robots
- save_cookies
- save_headers
- secure_protocol
- server_response
- show_all_dns_entries
- show_progress
- simple_host_check
- span_hosts
+ save-cookies
+ save-headers
+ secure-protocol
+ server-response
+ show-all-dns-entries
+ show-progress
+ simple-host-check
+ span-hosts
spider
- start_pos
- strict_comments
+ start-pos
+ strict-comments
sslcertfile
sslcertkey
timeout
timestamping
- use_server_timestamps
+ use-server-timestamps
tries
- trust_server_names
+ trust-server-names
unlink
- use_askpass
+ use-askpass
user
- use_proxy
- user_agent
+ use-proxy
+ user-agent
verbose
wait
- wait_retry
- warc_cdx
- warc_cdx_dedup
- warc_compression
- warc_digests
- warc_file
- warc_header
- warc_keep_log
- warc_max_size
- warc_temp_dir
+ wait-retry
+ warc-cdx
+ warc-cdx-dedup
+ warc-compression
+ warc-digests
+ warc-file
+ warc-header
+ warc-keep-log
+ warc-max-size
+ warc-temp-dir
wdebug
xattr
EOL
"}}}
-call map(s:commands, "substitute(v:val, '_', '[-_]\\\\=', 'g')")
-
for cmd in s:commands
- exe 'syn match wgetCommand "\<' . cmd . '\>" nextgroup=wgetAssignmentOperator skipwhite contained'
+ exe 'syn match wgetCommand "\<' .. substitute(cmd, '-', '[-_]\\=', "g") .. '\>" nextgroup=wgetAssignmentOperator skipwhite contained'
endfor
+unlet s:commands
syn case match
-syn match wgetStart "^" nextgroup=wgetCommand,wgetComment skipwhite
+syn match wgetLineStart "^" nextgroup=wgetCommand,wgetComment skipwhite
syn match wgetAssignmentOperator "=" nextgroup=wgetString,wgetBoolean,wgetNumber,wgetQuota,wgetTime skipwhite contained
hi def link wgetAssignmentOperator Special
diff --git a/runtime/syntax/wget2.vim b/runtime/syntax/wget2.vim
index a63c336f06..3e9abdf23d 100644
--- a/runtime/syntax/wget2.vim
+++ b/runtime/syntax/wget2.vim
@@ -1,9 +1,9 @@
" Vim syntax file
-" Language: Wget2 configuration file (/etc/wget2rc ~/.wget2rc)
+" Language: Wget2 configuration file (/etc/wget2rc ~/.wget2rc)
" Maintainer: Doug Kearns <dougkearns@gmail.com>
-" Last Change: 2022 Apr 28
+" Last Change: 2023 Nov 05
-" GNU Wget2 2.0.0 - multithreaded metalink/file/website downloader
+" GNU Wget2 2.1.0 - multithreaded metalink/file/website downloader
if exists("b:current_syntax")
finish
@@ -12,21 +12,20 @@ endif
let s:cpo_save = &cpo
set cpo&vim
-syn match wgetComment "#.*$" contains=wgetTodo contained
+syn match wget2Comment "#.*" contains=wget2Todo contained
-syn keyword wgetTodo TODO NOTE FIXME XXX contained
+syn keyword wget2Todo TODO NOTE FIXME XXX contained
-syn region wgetString start=+"+ skip=+\\\\\|\\"+ end=+"+ contained oneline
-syn region wgetString start=+'+ skip=+\\\\\|\\'+ end=+'+ contained oneline
+syn region wget2String start=+"+ skip=+\\\\\|\\"+ end=+"+ contained oneline
+syn region wget2String start=+'+ skip=+\\\\\|\\'+ end=+'+ contained oneline
syn case ignore
-syn keyword wgetBoolean on off yes no y n contained
-syn keyword wgetNumber infinity inf contained
-
-syn match wgetNumber "\<\d\+>" contained
-syn match wgetQuota "\<\d\+[kmgt]\>" contained
-syn match wgetTime "\<\d\+[smhd]\>" contained
+syn keyword wget2Boolean on off yes no y n contained
+syn keyword wget2Number infinity inf contained
+syn match wget2Number "\<\d\+>" contained
+syn match wget2Quota "\<\d\+[kmgt]\>" contained
+syn match wget2Time "\<\d\+[smhd]\>" contained
"{{{ Commands
let s:commands =<< trim EOL
@@ -67,6 +66,7 @@ let s:commands =<< trim EOL
cut-dirs
cut-file-get-vars
cut-url-get-vars
+ dane
debug
default-http-port
default-https-port
@@ -85,6 +85,7 @@ let s:commands =<< trim EOL
execute
filter-mime-type
filter-urls
+ follow-sitemaps
follow-tags
force-atom
force-css
@@ -221,28 +222,27 @@ let s:commands =<< trim EOL
EOL
"}}}
-call map(s:commands, "substitute(v:val, '_', '[-_]\\\\=', 'g')")
-
for cmd in s:commands
- exe 'syn match wgetCommand "\<' . cmd . '\>" nextgroup=wgetAssignmentOperator skipwhite contained'
+ exe 'syn match wget2Command "\<' .. substitute(cmd, '-', '[-_]\\=', "g") .. '\>" nextgroup=wget2AssignmentOperator skipwhite contained'
endfor
+unlet s:commands
syn case match
-syn match wgetStart "^" nextgroup=wgetCommand,wgetComment skipwhite
-syn match wgetAssignmentOperator "=" nextgroup=wgetString,wgetBoolean,wgetNumber,wgetQuota,wgetTime skipwhite contained
+syn match wget2LineStart "^" nextgroup=wget2Command,wget2Comment skipwhite
+syn match wget2AssignmentOperator "=" nextgroup=wget2String,wget2Boolean,wget2Number,wget2Quota,wget2Time skipwhite contained
-hi def link wgetAssignmentOperator Special
-hi def link wgetBoolean Boolean
-hi def link wgetCommand Identifier
-hi def link wgetComment Comment
-hi def link wgetNumber Number
-hi def link wgetQuota Number
-hi def link wgetString String
-hi def link wgetTime Number
-hi def link wgetTodo Todo
+hi def link wget2AssignmentOperator Special
+hi def link wget2Boolean Boolean
+hi def link wget2Command Identifier
+hi def link wget2Comment Comment
+hi def link wget2Number Number
+hi def link wget2Quota Number
+hi def link wget2String String
+hi def link wget2Time Number
+hi def link wget2Todo Todo
-let b:current_syntax = "wget"
+let b:current_syntax = "wget2"
let &cpo = s:cpo_save
unlet s:cpo_save
diff --git a/runtime/syntax/xcompose.vim b/runtime/syntax/xcompose.vim
new file mode 100644
index 0000000000..3637b9f3b6
--- /dev/null
+++ b/runtime/syntax/xcompose.vim
@@ -0,0 +1,37 @@
+" Vim syntax file
+" Language: XCompose
+" Maintainer: ObserverOfTime <chronobserver@disroot.org>
+" Filenames: .XCompose, Compose
+" Last Change: 2023 Nov 09
+
+" Comments
+syn keyword xcomposeTodo contained TODO FIXME XXX
+syn match xcomposeComment /#.*/ contains=xcomposeTodo
+
+" Includes
+syn keyword xcomposeInclude include nextgroup=xcomposeFile skipwhite
+syn match xcomposeFile /"\([^"]\|\\"\)\+"/ contained
+syn match xcomposeSubstitution /%[HLS]/ contained containedin=xcomposeFile
+
+" Modifiers
+syn keyword xcomposeModifier Ctrl Lock Caps Shift Alt Meta None
+syn match xcomposeModifierPrefix /\s*\zs[!~]\ze\s*/
+
+" Keysyms
+syn match xcomposeKeysym /<[A-Za-z0-9_]\+>/
+syn match xcomposeKeysym /[A-Za-z0-9_]\+/ contained
+syn match xcomposeString /"\([^"]\|\\"\)\+"/ contained nextgroup=xcomposeKeysym skipwhite
+syn match xcomposeColon /:/ nextgroup=xcomposeKeysym,xcomposeString skipwhite
+
+hi def link xcomposeColon Delimiter
+hi def link xcomposeComment Comment
+hi def link xcomposeFile String
+hi def link xcomposeInclude Include
+hi def link xcomposeKeysym Constant
+hi def link xcomposeModifier Function
+hi def link xcomposeModifierPrefix Operator
+hi def link xcomposeString String
+hi def link xcomposeSubstitution Special
+hi def link xcomposeTodo Todo
+
+let b:current_syntax = 'xcompose'
diff --git a/runtime/syntax/xf86conf.vim b/runtime/syntax/xf86conf.vim
index 545eda7db0..e8162f3a35 100644
--- a/runtime/syntax/xf86conf.vim
+++ b/runtime/syntax/xf86conf.vim
@@ -1,13 +1,12 @@
" Vim syntax file
-" This is a GENERATED FILE. Please always refer to source file at the URI below.
" Language: XF86Config (XFree86 configuration file)
" Former Maintainer: David Ne\v{c}as (Yeti) <yeti@physics.muni.cz>
-" Last Change: 2010 Nov 01
-" URL: http://trific.ath.cx/Ftp/vim/syntax/xf86conf.vim
+" Last Change By David: 2010 Nov 01
+" Last Change: 2023 Jan 23
" Required Vim Version: 6.0
"
" Options: let xf86conf_xfree86_version = 3 or 4
-" to force XFree86 3.x or 4.x XF86Config syntax
+" to force XFree86 3.x or 4.x XF86Config syntax
" Setup
" quit when a syntax file was already loaded
@@ -147,6 +146,8 @@ syn keyword xf86confKeyword Hskew HTimings InputDevice IOBase MemBase Mode nextg
syn keyword xf86confKeyword Modes Ramdac Screen TextClockFreq UseModes VendorName nextgroup=xf86confComment,xf86confValue
syn keyword xf86confKeyword VertRefresh VideoRam ViewPort Virtual VScan VTimings nextgroup=xf86confComment,xf86confValue
syn keyword xf86confKeyword Weight White nextgroup=xf86confComment,xf86confValue
+syn keyword xf86confMatch MatchDevicePath MatchDriver MatchLayout MatchOS MatchPnPID MatchProduct MatchTag MatchUSBID MatchVendor nextgroup=xf86confComment,xf86confString skipwhite
+syn keyword xf86confMatch MatchIsPointer MatchIsKeyboard MatchIsTouchpad MatchIsTouchscreen MatchIsJoystick nextgroup=xf86confComment,xf86confValue skipwhite
syn keyword xf86confModeLine ModeLine nextgroup=xf86confComment,xf86confModeLineValue skipwhite skipnl
" Constants
@@ -185,6 +186,7 @@ hi def link xf86confOctalNumberError xf86confError
hi def link xf86confError Error
hi def link xf86confOption xf86confKeyword
+hi def link xf86confMatch xf86confKeyword
hi def link xf86confModeLine xf86confKeyword
hi def link xf86confKeyword Type
diff --git a/runtime/syntax/xpm.vim b/runtime/syntax/xpm.vim
index be9f38723e..b094092b73 100644
--- a/runtime/syntax/xpm.vim
+++ b/runtime/syntax/xpm.vim
@@ -1,10 +1,12 @@
" Vim syntax file
" Language: X Pixmap
" Maintainer: Ronald Schild <rs@scutum.de>
-" Last Change: 2021 Oct 04
-" Version: 5.4n.1
+" Last Change: 2023 May 24
+" Version: 5.4n.2
" Jemma Nelson added termguicolors support
" Dominique Pellé fixed spelling support
+" Christian J. Robinson fixed use of global variables, moved
+" loop into a function
" quit when a syntax file was already loaded
if exists("b:current_syntax")
@@ -21,108 +23,119 @@ syn region xpmPixelString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=@xpmCo
if has("gui_running") || has("termguicolors") && &termguicolors
-let color = ""
-let chars = ""
-let colors = 0
-let cpp = 0
-let n = 0
-let i = 1
+function s:CreateSyntax() abort
+ let color = ""
+ let chars = ""
+ let colors = 0
+ let cpp = 0
+ let n = 0
+ let lines = getline(1, '$')
-while i <= line("$") " scanning all lines
+ for line in lines " scanning all lines
- let s = matchstr(getline(i), '".\{-1,}"')
- if s != "" " does line contain a string?
+ let s = matchstr(line, '".\{-1,}"')
- if n == 0 " first string is the Values string
+ if s != "" " does line contain a string?
- " get the 3rd value: colors = number of colors
- let colors = substitute(s, '"\s*\d\+\s\+\d\+\s\+\(\d\+\).*"', '\1', '')
- " get the 4th value: cpp = number of character per pixel
- let cpp = substitute(s, '"\s*\d\+\s\+\d\+\s\+\d\+\s\+\(\d\+\).*"', '\1', '')
- if cpp =~ '[^0-9]'
- break " if cpp is not made of digits there must be something wrong
- endif
+ if n == 0 " first string is the Values string
- " Highlight the Values string as normal string (no pixel string).
- " Only when there is no slash, it would terminate the pattern.
- if s !~ '/'
- exe 'syn match xpmValues /' . s . '/'
- endif
- hi link xpmValues String
+ let values = split(s[1 : -2])
+
+ " Values string invalid, bail out
+ if len(values) != 4 && len(values) != 6 && len(values) != 7
+ return
+ endif
- let n = 1 " n = color index
+ " get the 3rd value: colors = number of colors
+ let colors = str2nr(values[2])
+ " get the 4th value: cpp = number of character per pixel
+ let cpp = str2nr(values[3])
- elseif n <= colors " string is a color specification
+ " these values must be positive, nonzero
+ if colors < 1 || cpp < 1
+ return
+ endif
+
+ " Highlight the Values string as normal string (no pixel string).
+ " Only when there is no slash, it would terminate the pattern.
+ if s !~ '/'
+ exe 'syn match xpmValues /' .. s .. '/'
+ endif
+ hi link xpmValues String
- " get chars = <cpp> length string representing the pixels
- " (first incl. the following whitespace)
- let chars = substitute(s, '"\(.\{'.cpp.'}\s\).*"', '\1', '')
+ let n = 1 " n = color index
- " now get color, first try 'c' key if any (color visual)
- let color = substitute(s, '".*\sc\s\+\(.\{-}\)\s*\(\(g4\=\|[ms]\)\s.*\)*\s*"', '\1', '')
- if color == s
- " no 'c' key, try 'g' key (grayscale with more than 4 levels)
- let color = substitute(s, '".*\sg\s\+\(.\{-}\)\s*\(\(g4\|[ms]\)\s.*\)*\s*"', '\1', '')
+ elseif n <= colors " string is a color specification
+
+ " get chars = <cpp> length string representing the pixels
+ " (first incl. the following whitespace)
+ let chars = substitute(s, '"\(.\{' .. cpp .. '}\s\).*"', '\1', '')
+
+ " now get color, first try 'c' key if any (color visual)
+ let color = substitute(s, '".*\sc\s\+\(.\{-}\)\s*\(\(g4\=\|[ms]\)\s.*\)*\s*"', '\1', '')
if color == s
- " next try: 'g4' key (4-level grayscale)
- let color = substitute(s, '".*\sg4\s\+\(.\{-}\)\s*\([ms]\s.*\)*\s*"', '\1', '')
+ " no 'c' key, try 'g' key (grayscale with more than 4 levels)
+ let color = substitute(s, '".*\sg\s\+\(.\{-}\)\s*\(\(g4\|[ms]\)\s.*\)*\s*"', '\1', '')
if color == s
- " finally try 'm' key (mono visual)
- let color = substitute(s, '".*\sm\s\+\(.\{-}\)\s*\(s\s.*\)*\s*"', '\1', '')
+ " next try: 'g4' key (4-level grayscale)
+ let color = substitute(s, '".*\sg4\s\+\(.\{-}\)\s*\([ms]\s.*\)*\s*"', '\1', '')
if color == s
- let color = ""
+ " finally try 'm' key (mono visual)
+ let color = substitute(s, '".*\sm\s\+\(.\{-}\)\s*\(s\s.*\)*\s*"', '\1', '')
+ if color == s
+ let color = ""
+ endif
endif
endif
endif
- endif
- " Vim cannot handle RGB codes with more than 6 hex digits
- if color =~ '#\x\{10,}$'
- let color = substitute(color, '\(\x\x\)\x\x', '\1', 'g')
- elseif color =~ '#\x\{7,}$'
- let color = substitute(color, '\(\x\x\)\x', '\1', 'g')
- " nor with 3 digits
- elseif color =~ '#\x\{3}$'
- let color = substitute(color, '\(\x\)\(\x\)\(\x\)', '0\10\20\3', '')
- endif
+ " Vim cannot handle RGB codes with more than 6 hex digits
+ if color =~ '#\x\{10,}$'
+ let color = substitute(color, '\(\x\x\)\x\x', '\1', 'g')
+ elseif color =~ '#\x\{7,}$'
+ let color = substitute(color, '\(\x\x\)\x', '\1', 'g')
+ " nor with 3 digits
+ elseif color =~ '#\x\{3}$'
+ let color = substitute(color, '\(\x\)\(\x\)\(\x\)', '0\10\20\3', '')
+ endif
- " escape meta characters in patterns
- let s = escape(s, '/\*^$.~[]')
- let chars = escape(chars, '/\*^$.~[]')
-
- " now create syntax items
- " highlight the color string as normal string (no pixel string)
- exe 'syn match xpmCol'.n.'Def /'.s.'/ contains=xpmCol'.n.'inDef'
- exe 'hi link xpmCol'.n.'Def String'
-
- " but highlight the first whitespace after chars in its color
- exe 'syn match xpmCol'.n.'inDef /"'.chars.'/hs=s+'.(cpp+1).' contained'
- exe 'hi link xpmCol'.n.'inDef xpmColor'.n
-
- " remove the following whitespace from chars
- let chars = substitute(chars, '.$', '', '')
-
- " and create the syntax item contained in the pixel strings
- exe 'syn match xpmColor'.n.' /'.chars.'/ contained'
- exe 'syn cluster xpmColors add=xpmColor'.n
-
- " if no color or color = "None" show background
- if color == "" || substitute(color, '.*', '\L&', '') == 'none'
- exe 'hi xpmColor'.n.' guifg=bg'
- exe 'hi xpmColor'.n.' guibg=NONE'
- elseif color !~ "'"
- exe 'hi xpmColor'.n." guifg='".color."'"
- exe 'hi xpmColor'.n." guibg='".color."'"
+ " escape meta characters in patterns
+ let s = escape(s, '/\*^$.~[]')
+ let chars = escape(chars, '/\*^$.~[]')
+
+ " now create syntax items
+ " highlight the color string as normal string (no pixel string)
+ exe 'syn match xpmCol' .. n .. 'Def /' .. s .. '/ contains=xpmCol' .. n .. 'inDef'
+ exe 'hi link xpmCol' .. n .. 'Def String'
+
+ " but highlight the first whitespace after chars in its color
+ exe 'syn match xpmCol' .. n .. 'inDef /"' .. chars .. '/hs=s+' .. (cpp + 1) .. ' contained'
+ exe 'hi link xpmCol' .. n .. 'inDef xpmColor' .. n
+
+ " remove the following whitespace from chars
+ let chars = substitute(chars, '.$', '', '')
+
+ " and create the syntax item contained in the pixel strings
+ exe 'syn match xpmColor' .. n .. ' /' .. chars .. '/ contained'
+ exe 'syn cluster xpmColors add=xpmColor' .. n
+
+ " if no color or color = "None" show background
+ if color == "" || substitute(color, '.*', '\L&', '') == 'none'
+ exe 'hi xpmColor' .. n .. ' guifg=bg'
+ exe 'hi xpmColor' .. n .. ' guibg=NONE'
+ elseif color !~ "'"
+ exe 'hi xpmColor' .. n .. " guifg='" .. color .. "'"
+ exe 'hi xpmColor' .. n .. " guibg='" .. color .. "'"
+ endif
+ let n += 1
+ else
+ break " no more color string
endif
- let n = n + 1
- else
- break " no more color string
endif
- endif
- let i = i + 1
-endwhile
+ endfor
+endfunction
-unlet color chars colors cpp n i s
+call s:CreateSyntax()
endif " has("gui_running") || has("termguicolors") && &termguicolors
diff --git a/runtime/syntax/zig.vim b/runtime/syntax/zig.vim
index e09b5e8815..121b0195b0 100644
--- a/runtime/syntax/zig.vim
+++ b/runtime/syntax/zig.vim
@@ -34,6 +34,7 @@ let s:zig_syntax_keywords = {
\ , "usize"
\ , "comptime_int"
\ , "comptime_float"
+ \ , "c_char"
\ , "c_short"
\ , "c_ushort"
\ , "c_int"
@@ -96,6 +97,7 @@ let s:zig_syntax_keywords = {
\ , "@atomicStore"
\ , "@bitCast"
\ , "@breakpoint"
+ \ , "@trap"
\ , "@alignCast"
\ , "@alignOf"
\ , "@cDefine"
@@ -107,6 +109,7 @@ let s:zig_syntax_keywords = {
\ , "@cmpxchgStrong"
\ , "@compileError"
\ , "@compileLog"
+ \ , "@constCast"
\ , "@ctz"
\ , "@popCount"
\ , "@divExact"
@@ -126,9 +129,10 @@ let s:zig_syntax_keywords = {
\ , "@unionInit"
\ , "@frameAddress"
\ , "@import"
+ \ , "@inComptime"
\ , "@newStackCall"
\ , "@asyncCall"
- \ , "@intToPtr"
+ \ , "@ptrFromInt"
\ , "@max"
\ , "@min"
\ , "@memcpy"
@@ -145,7 +149,7 @@ let s:zig_syntax_keywords = {
\ , "@panic"
\ , "@prefetch"
\ , "@ptrCast"
- \ , "@ptrToInt"
+ \ , "@intFromPtr"
\ , "@rem"
\ , "@returnAddress"
\ , "@setCold"
@@ -169,25 +173,26 @@ let s:zig_syntax_keywords = {
\ , "@subWithOverflow"
\ , "@intCast"
\ , "@floatCast"
- \ , "@intToFloat"
- \ , "@floatToInt"
- \ , "@boolToInt"
- \ , "@errSetCast"
+ \ , "@floatFromInt"
+ \ , "@intFromFloat"
+ \ , "@intFromBool"
+ \ , "@errorCast"
\ , "@truncate"
\ , "@typeInfo"
\ , "@typeName"
\ , "@TypeOf"
\ , "@atomicRmw"
- \ , "@intToError"
- \ , "@errorToInt"
- \ , "@intToEnum"
- \ , "@enumToInt"
+ \ , "@errorFromInt"
+ \ , "@intFromError"
+ \ , "@enumFromInt"
+ \ , "@intFromEnum"
\ , "@setAlignStack"
\ , "@frame"
\ , "@Frame"
\ , "@frameSize"
\ , "@bitReverse"
\ , "@Vector"
+ \ , "@volatileCast"
\ , "@sin"
\ , "@cos"
\ , "@tan"
@@ -196,7 +201,7 @@ let s:zig_syntax_keywords = {
\ , "@log"
\ , "@log2"
\ , "@log10"
- \ , "@fabs"
+ \ , "@abs"
\ , "@floor"
\ , "@ceil"
\ , "@trunc"
diff --git a/runtime/syntax/zimbu.vim b/runtime/syntax/zimbu.vim
index 1a7a485e6f..472559520e 100644
--- a/runtime/syntax/zimbu.vim
+++ b/runtime/syntax/zimbu.vim
@@ -1,7 +1,8 @@
" Vim syntax file
" Language: Zimbu
-" Maintainer: Bram Moolenaar
-" Last Change: 2014 Nov 23
+" Maintainer: The·Vim·Project·<https://github.com/vim/vim>
+" Last Change: 2023 Aug 13
+" Note: Zimbu seems to be dead :(
if exists("b:current_syntax")
finish
diff --git a/runtime/syntax/zserio.vim b/runtime/syntax/zserio.vim
new file mode 100644
index 0000000000..5459915c01
--- /dev/null
+++ b/runtime/syntax/zserio.vim
@@ -0,0 +1,112 @@
+" Vim syntax file
+" Language: Zserio
+" Maintainer: Dominique Pellé <dominique.pelle@gmail.com>
+" Last Change: 2023 Jun 18
+"
+" Zserio is a serialization schema language for modeling binary
+" data types, bitstreams or file formats. Based on the zserio
+" language it is possible to automatically generate encoders and
+" decoders for a given schema in various target languages
+" (e.g. Java, C++, Python).
+"
+" Zserio is an evolution of the DataScript language.
+"
+" For more information, see:
+" - http://zserio.org/
+" - https://github.com/ndsev/zserio
+
+" quit when a syntax file was already loaded
+if exists("b:current_syntax")
+ finish
+endif
+
+let s:keepcpo= &cpo
+set cpo&vim
+
+syn case match
+
+syn keyword zserioPackage import package zserio_compatibility_version
+syn keyword zserioType bit bool string
+syn keyword zserioType int int8 int16 int32 int64
+syn keyword zserioType uint8 uint16 uint32 uint64
+syn keyword zserioType float16 float32 float64
+syn keyword zserioType varint varint16 varint32 varint64
+syn keyword zserioType varuint varsize varuint16 varuint32 varuint64
+syn keyword zserioAlign align
+syn keyword zserioLabel case default
+syn keyword zserioConditional if condition
+syn keyword zserioBoolean true false
+syn keyword zserioCompound struct union choice on enum bitmask subtype
+syn keyword zserioKeyword function return
+syn keyword zserioOperator lengthof valueof instanceof numbits isset
+syn keyword zserioRpc service pubsub topic publish subscribe
+syn keyword zserioRule rule_group rule
+syn keyword zserioStorageClass const implicit packed instantiate
+syn keyword zserioTodo contained TODO FIXME XXX
+syn keyword zserioSql sql sql_table sql_database sql_virtual sql_without_rowid
+syn keyword zserioSql explicit using
+
+" zserioCommentGroup allows adding matches for special things in comments.
+syn cluster zserioCommentGroup contains=zserioTodo
+
+syn match zserioOffset display "^\s*[a-zA-Z_:\.][a-zA-Z0-9_:\.]*\s*:"
+
+syn match zserioNumber display "\<\d\+\>"
+syn match zserioNumberHex display "\<0[xX]\x\+\>"
+syn match zserioNumberBin display "\<[01]\+[bB]\>" contains=zserioBinaryB
+syn match zserioBinaryB display contained "[bB]\>"
+syn match zserioOctal display "\<0\o\+\>" contains=zserioOctalZero
+syn match zserioOctalZero display contained "\<0"
+
+syn match zserioOctalError display "\<0\o*[89]\d*\>"
+
+syn match zserioCommentError display "\*/"
+syn match zserioCommentStartError display "/\*"me=e-1 contained
+
+syn region zserioCommentL
+ \ start="//" skip="\\$" end="$" keepend
+ \ contains=@zserioCommentGroup,@Spell
+syn region zserioComment
+ \ matchgroup=zserioCommentStart start="/\*" end="\*/"
+ \ contains=@zserioCommentGroup,zserioCommentStartError,@Spell extend
+
+syn region zserioString
+ \ start=+L\="+ skip=+\\\\\|\\"+ end=+"+ contains=@Spell
+
+syn sync ccomment zserioComment
+
+" Define the default highlighting.
+hi def link zserioType Type
+hi def link zserioEndian StorageClass
+hi def link zserioStorageClass StorageClass
+hi def link zserioAlign Label
+hi def link zserioLabel Label
+hi def link zserioOffset Label
+hi def link zserioSql PreProc
+hi def link zserioCompound Structure
+hi def link zserioConditional Conditional
+hi def link zserioBoolean Boolean
+hi def link zserioKeyword Statement
+hi def link zserioRpc Keyword
+hi def link zserioRule Keyword
+hi def link zserioString String
+hi def link zserioNumber Number
+hi def link zserioNumberBin Number
+hi def link zserioBinaryB Special
+hi def link zserioOctal Number
+hi def link zserioOctalZero Special
+hi def link zserioOctalError Error
+hi def link zserioNumberHex Number
+hi def link zserioTodo Todo
+hi def link zserioOperator Operator
+hi def link zserioPackage Include
+hi def link zserioCommentError Error
+hi def link zserioCommentStartError Error
+hi def link zserioCommentStart zserioComment
+hi def link zserioCommentL zserioComment
+hi def link zserioComment Comment
+
+let b:current_syntax = "zserio"
+
+let &cpo = s:keepcpo
+unlet s:keepcpo
diff --git a/runtime/syntax/zsh.vim b/runtime/syntax/zsh.vim
index 69671c59ca..084f8cdb41 100644
--- a/runtime/syntax/zsh.vim
+++ b/runtime/syntax/zsh.vim
@@ -88,7 +88,7 @@ syn match zshOperator '||\|&&\|;\|&!\='
syn match zshRedir '\d\=\(<<<\|<&\s*[0-9p-]\=\|<>\?\)'
" >, >>, and variants.
syn match zshRedir '\d\=\(>&\s*[0-9p-]\=\|&>>\?\|>>\?&\?\)[|!]\='
- " | and |&, but only if it's not preceeded or
+ " | and |&, but only if it's not preceded or
" followed by a | to avoid matching ||.
syn match zshRedir '|\@1<!|&\=|\@!'
diff --git a/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor
index e256711e70..2824f7ae83 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor
+++ b/runtime/tutor/en/vim-01-beginner.tutor
@@ -3,6 +3,7 @@
Neovim is a very powerful editor that has many commands, too many to explain in
a tutorial such as this. This tutorial is designed to describe enough of the
commands that you will be able to easily use Neovim as an all-purpose editor.
+
It is IMPORTANT to remember that this tutorial is set up to teach by use. That
means that you need to do the exercises to learn them properly. If you only
read the text, you will soon forget what is most important!
@@ -19,17 +20,16 @@ pressing [<Esc>](<Esc>) and then [u](u) will undo the latest change.
This tutorial is interactive, and there are a few things you should know.
- Type [<Enter>](<Enter>) on links [like this](holy-grail ) to open the linked help section.
- Or simply type [K](K) on any word to find its documentation!
-- You can close this help window with `:q`{vim}
-- Sometimes you will be required to modify text like
-
- this here
+- You can close this help window with `:q`{vim} `<Enter>`{normal}
+When there is a ✗ sign at the left, you will be required to modify text.
Once you have done the changes correctly, the ✗ sign at the left will change
to ✓. I imagine you can already see how neat Neovim can be.
+
Other times, you'll be prompted to run a command (I'll explain this later):
-~~~ cmd
- :help <Enter>
-~~~
+
+ `:help`{vim} `<Enter>`{normal}
+
or press a sequence of keys
~~~ normal
<Esc>0f<Space>d3wP$P
@@ -70,19 +70,19 @@ NOTE: The cursor keys should also work. But using hjkl you will be able to
2. Type:
- `:q!`{vim} `<Enter>`{normal}.
+ `:q!`{vim} `<Enter>`{normal}
This quits the editor, DISCARDING any changes you have made.
3. Open Neovim and get back here by executing the command that got you into
this tutorial. That might be:
- :Tutor <Enter>
+ `:Tutor`{vim} `<Enter>`{normal}
4. If you have these steps memorized and are confident, execute steps
1 through 3 to exit and re-enter the editor.
-NOTE: [:q!](:q) <Enter> discards any changes you made. In a few lessons you
+NOTE: [:q!](:q) `<Enter>`{normal} discards any changes you made. In a few lessons you
will learn how to save the changes to a file.
5. Move the cursor down to Lesson 1.3.
@@ -438,7 +438,7 @@ Notice that [c](c)e deletes the word and places you in Insert mode.
5. Type `c$`{normal} and type the rest of the line like the second and press `<Esc>`{normal}.
The end of this line needs some help to make it like the second.
-The end of this line needs to be corrected using the `c$`{normal} command.
+The end of this line needs to be corrected using the c$ command.
NOTE: You can use the Backspace key to correct mistakes while typing.
@@ -629,7 +629,7 @@ NOTE: All `:`{vim} commands are executed when you press `<Enter>`{normal}.
** To save the changes made to the text, type `:w`{vim} FILENAME. **
- 1. Type `:!ls`{vim} to get a listing of your directory.
+ 1. Type `:!{unix:(ls),win:(dir)}`{vim} to get a listing of your directory.
You already know you must hit `<Enter>`{normal} after this.
2. Choose a filename that does not exist yet, such as TEST.
@@ -641,14 +641,14 @@ NOTE: All `:`{vim} commands are executed when you press `<Enter>`{normal}.
(where TEST is the filename you chose.)
4. This saves the current file under the name TEST.
- To verify this, type `:!ls`{vim} again to see your directory.
+ To verify this, type `:!{unix:(ls),win:(dir)}`{vim} again to see your directory.
NOTE: If you were to exit Neovim and start it again with `nvim TEST`, the file
would be an exact copy of the tutorial when you saved it.
5. Now remove the file by typing:
~~~ cmd
- :!rm TEST
+ :!{unix:(rm),win:(del)} TEST
~~~
# Lesson 5.3: SELECTING TEXT TO WRITE
@@ -675,7 +675,7 @@ NOTE: If you were to exit Neovim and start it again with `nvim TEST`, the file
before you press `<Enter>`{normal}.
- 5. Neovim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it.
+ 5. Neovim will write the selected lines to the file TEST. Use `:!{unix:(ls),win:(dir)}`{vim} to see it.
Do not remove it yet! We will use it in the next lesson.
NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the cursor around to
@@ -703,7 +703,7 @@ NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move
NOTE: You can also read the output of an external command. For example,
- `:r !ls`{vim}
+ `:r !{unix:(ls),win:(dir)}`{vim}
reads the output of the `ls` command and puts it below the cursor.
@@ -712,8 +712,8 @@ NOTE: You can also read the output of an external command. For example,
1. [:!command](:!cmd) executes an external command.
Some useful examples are:
- `:!ls`{vim} - shows a directory listing
- `:!rm FILENAME`{vim} - removes file FILENAME
+ `:!{unix:(ls ),win:(dir)}`{vim} - shows a directory listing
+ `:!{unix:(rm ),win:(del)} FILENAME`{vim} - removes file FILENAME
2. [:w](:w) FILENAME writes the current Neovim file to disk with
name FILENAME.
@@ -724,7 +724,7 @@ NOTE: You can also read the output of an external command. For example,
4. [:r](:r) FILENAME retrieves disk file FILENAME and puts it
below the cursor position.
- 5. [:r !dir](:r!) reads the output of the dir command and
+ 5. {unix:([:r !ls](:r!) ),win:([:r !dir](:r!))} reads the output of the {unix:(ls),win:(dir)} command and
puts it below the cursor position.
# Lesson 6.1: THE OPEN COMMAND
@@ -928,7 +928,7 @@ To start using more features create an "init.vim" file.
** Command line completion with `<C-d>`{normal} and `<Tab>`{normal}. **
- 1. List the contents of the current directory: `:!ls`{vim}
+ 1. List the contents of the current directory: `:!{unix:(ls),win:(dir)}`{vim}
2. Type the start of a command: `:e`{vim}
diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json
index 5dccb824e0..8f88a3897d 100644
--- a/runtime/tutor/en/vim-01-beginner.tutor.json
+++ b/runtime/tutor/en/vim-01-beginner.tutor.json
@@ -1,6 +1,5 @@
{
"expect": {
- "25": -1,
"103": "The cow jumped over the moon.",
"125": "There is some text missing from this line.",
"126": "There is some text missing from this line.",
@@ -28,8 +27,8 @@
"399": "When this line was typed in, someone pressed some wrong keys!",
"419": "This line has a few words that need changing using the change operator.",
"420": "This line has a few words that need changing using the change operator.",
- "440": "The end of this line needs to be corrected using the `c$` command.",
- "441": "The end of this line needs to be corrected using the `c$` command.",
+ "440": "The end of this line needs to be corrected using the c$ command.",
+ "441": "The end of this line needs to be corrected using the c$ command.",
"504": -1,
"523": -1,
"546": "Usually the best time to see the flowers is in the spring.",
diff --git a/runtime/tutor/tutor.tutor b/runtime/tutor/tutor.tutor
index c2d5268e3d..3156b94dbb 100644
--- a/runtime/tutor/tutor.tutor
+++ b/runtime/tutor/tutor.tutor
@@ -229,11 +229,11 @@ These elements are specified in separate JSON files like this
~~~ json
{
- "expect": {
- "1": "This is how this line should look.",
- "2": "This is how this line should look.",
- "3": -1
- }
+ "expect": {
+ "1": "This is how this line should look.",
+ "2": "This is how this line should look.",
+ "3": -1
+ }
}
~~~
diff --git a/runtime/tutor/tutor.tutor.json b/runtime/tutor/tutor.tutor.json
index e8628e2f0e..41995b28fa 100644
--- a/runtime/tutor/tutor.tutor.json
+++ b/runtime/tutor/tutor.tutor.json
@@ -1,35 +1,35 @@
{
- "expect": {
- "63": "This is text with **important information**",
- "64": "This is text with **important information**",
- "71": "TODO: Document '&variable'",
- "72": "TODO: Document '&variable'",
- "78": "# This is a level 1 header",
- "79": "# This is a level 1 header",
- "80": "### This is a level 3 header",
- "81": "### This is a level 3 header",
- "82": "# This is a header with a label {*label*}",
- "83": "# This is a header with a label {*label*}",
- "108": "A link to help for the ['breakindent']('breakindent') option",
- "109": "A link to help for the ['breakindent']('breakindent') option",
- "123": "A link to the [Links](*links*) section",
- "124": "A link to the [Links](*links*) section",
- "139": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)",
- "140": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)",
- "157": "~~~ viml",
- "158": "echom 'the value of &number is'.string(&number)",
- "159": "~~~",
- "161": "~~~ viml",
- "162": "echom 'the value of &number is'.string(&number)",
- "163": "~~~",
- "188": "~~~ normal",
- "189": "d2w",
- "190": "~~~",
- "192": "~~~ normal",
- "193": "d2w",
- "194": "~~~",
- "206": "`d2w`{normal}",
- "207": "`d2w`{normal}",
- "244": -1
- }
+ "expect": {
+ "63": "This is text with **important information**",
+ "64": "This is text with **important information**",
+ "71": "TODO: Document '&variable'",
+ "72": "TODO: Document '&variable'",
+ "78": "# This is a level 1 header",
+ "79": "# This is a level 1 header",
+ "80": "### This is a level 3 header",
+ "81": "### This is a level 3 header",
+ "82": "# This is a header with a label {*label*}",
+ "83": "# This is a header with a label {*label*}",
+ "108": "A link to help for the ['breakindent']('breakindent') option",
+ "109": "A link to help for the ['breakindent']('breakindent') option",
+ "123": "A link to the [Links](*links*) section",
+ "124": "A link to the [Links](*links*) section",
+ "139": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)",
+ "140": "A link to [the vim-tutor-mode tutorial](@tutor:tutor)",
+ "157": "~~~ viml",
+ "158": "echom 'the value of &number is'.string(&number)",
+ "159": "~~~",
+ "161": "~~~ viml",
+ "162": "echom 'the value of &number is'.string(&number)",
+ "163": "~~~",
+ "188": "~~~ normal",
+ "189": "d2w",
+ "190": "~~~",
+ "192": "~~~ normal",
+ "193": "d2w",
+ "194": "~~~",
+ "206": "`d2w`{normal}",
+ "207": "`d2w`{normal}",
+ "244": -1
+ }
}
diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua
index f980e800cf..076ad374cf 100755
--- a/scripts/bump_deps.lua
+++ b/scripts/bump_deps.lua
@@ -63,12 +63,13 @@ local function rm_file_if_present(path_to_file)
end
local nvim_src_dir = vim.fn.getcwd()
+local deps_file = nvim_src_dir .. '/' .. 'cmake.deps/deps.txt'
local temp_dir = nvim_src_dir .. '/tmp'
run({ 'mkdir', '-p', temp_dir })
local function get_dependency(dependency_name)
local dependency_table = {
- ['LuaJIT'] = {
+ ['luajit'] = {
repo = 'LuaJIT/LuaJIT',
symbol = 'LUAJIT',
},
@@ -76,7 +77,7 @@ local function get_dependency(dependency_name)
repo = 'libuv/libuv',
symbol = 'LIBUV',
},
- ['Luv'] = {
+ ['luv'] = {
repo = 'luvit/luv',
symbol = 'LUV',
},
@@ -84,6 +85,26 @@ local function get_dependency(dependency_name)
repo = 'tree-sitter/tree-sitter',
symbol = 'TREESITTER',
},
+ ['tree-sitter-c'] = {
+ repo = 'tree-sitter/tree-sitter-c',
+ symbol = 'TREESITTER_C',
+ },
+ ['tree-sitter-lua'] = {
+ repo = 'MunifTanjim/tree-sitter-lua',
+ symbol = 'TREESITTER_LUA',
+ },
+ ['tree-sitter-vim'] = {
+ repo = 'neovim/tree-sitter-vim',
+ symbol = 'TREESITTER_VIM',
+ },
+ ['tree-sitter-vimdoc'] = {
+ repo = 'neovim/tree-sitter-vimdoc',
+ symbol = 'TREESITTER_VIMDOC',
+ },
+ ['tree-sitter-query'] = {
+ repo = 'nvim-treesitter/tree-sitter-query',
+ symbol = 'TREESITTER_QUERY',
+ },
}
local dependency = dependency_table[dependency_name]
if dependency == nil then
@@ -127,26 +148,24 @@ end
local function write_cmakelists_line(symbol, kind, value)
require_executable('sed')
- local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt'
run_die({
'sed',
'-i',
'-e',
- 's/set('
+ 's/'
.. symbol
.. '_'
.. kind
.. '.*$'
- .. '/set('
+ .. '/'
.. symbol
.. '_'
.. kind
.. ' '
.. value
- .. ')'
.. '/',
- cmakelists_path,
- }, 'Failed to write ' .. cmakelists_path)
+ deps_file,
+ }, 'Failed to write ' .. deps_file)
end
local function explicit_create_branch(dep)
@@ -181,8 +200,6 @@ local function update_cmakelists(dependency, archive, comment)
verify_branch(dependency.name)
- local changed_file = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt'
-
p('Updating ' .. dependency.name .. ' to ' .. archive.url .. '\n')
write_cmakelists_line(dependency.symbol, 'URL', archive.url:gsub('/', '\\/'))
write_cmakelists_line(dependency.symbol, 'SHA256', archive.sha)
@@ -190,7 +207,7 @@ local function update_cmakelists(dependency, archive, comment)
{
'git',
'commit',
- changed_file,
+ deps_file,
'-m',
commit_prefix .. 'bump ' .. dependency.name .. ' to ' .. comment,
},
@@ -201,10 +218,9 @@ end
local function verify_cmakelists_committed()
require_executable('git')
- local cmakelists_path = nvim_src_dir .. '/' .. 'cmake.deps/CMakeLists.txt'
run_die(
- { 'git', 'diff', '--quiet', 'HEAD', '--', cmakelists_path },
- cmakelists_path .. ' has uncommitted changes'
+ { 'git', 'diff', '--quiet', 'HEAD', '--', deps_file },
+ deps_file .. ' has uncommitted changes'
)
end
diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua
new file mode 100755
index 0000000000..e331dd996e
--- /dev/null
+++ b/scripts/gen_eval_files.lua
@@ -0,0 +1,815 @@
+-- Generator for various vimdoc and Lua type files
+
+local DEP_API_METADATA = 'build/api_metadata.mpack'
+local DEP_API_DOC = 'runtime/doc/api.mpack'
+
+--- @class vim.api.metadata
+--- @field name string
+--- @field parameters {[1]:string,[2]:string}[]
+--- @field return_type string
+--- @field deprecated_since integer
+--- @field eval boolean
+--- @field fast boolean
+--- @field handler_id integer
+--- @field impl_name string
+--- @field lua boolean
+--- @field method boolean
+--- @field remote boolean
+--- @field since integer
+
+local LUA_META_HEADER = {
+ '--- @meta _',
+ '-- THIS FILE IS GENERATED',
+ '-- DO NOT EDIT',
+ "error('Cannot require a meta file')",
+}
+
+local LUA_API_META_HEADER = {
+ '--- @meta _',
+ '-- THIS FILE IS GENERATED',
+ '-- DO NOT EDIT',
+ "error('Cannot require a meta file')",
+ '',
+ 'vim.api = {}',
+}
+
+local LUA_OPTION_META_HEADER = {
+ '--- @meta _',
+ '-- THIS FILE IS GENERATED',
+ '-- DO NOT EDIT',
+ "error('Cannot require a meta file')",
+ '',
+ '---@class vim.bo',
+ '---@field [integer] vim.bo',
+ 'vim.bo = vim.bo',
+ '',
+ '---@class vim.wo',
+ '---@field [integer] vim.wo',
+ 'vim.wo = vim.wo',
+}
+
+local LUA_KEYWORDS = {
+ ['and'] = true,
+ ['end'] = true,
+ ['function'] = true,
+ ['or'] = true,
+ ['if'] = true,
+ ['while'] = true,
+ ['repeat'] = true,
+}
+
+local OPTION_TYPES = {
+ bool = 'boolean',
+ number = 'integer',
+ string = 'string',
+}
+
+local API_TYPES = {
+ Window = 'integer',
+ Tabpage = 'integer',
+ Buffer = 'integer',
+ Boolean = 'boolean',
+ Object = 'any',
+ Integer = 'integer',
+ String = 'string',
+ Array = 'any[]',
+ LuaRef = 'function',
+ Dictionary = 'table<string,any>',
+ Float = 'number',
+ void = '',
+}
+
+--- @param x string
+--- @param sep? string
+--- @return string[]
+local function split(x, sep)
+ return vim.split(x, sep or '\n', { plain = true })
+end
+
+--- Convert an API type to Lua
+--- @param t string
+--- @return string
+local function api_type(t)
+ local as0 = t:match('^ArrayOf%((.*)%)')
+ if as0 then
+ local as = split(as0, ', ')
+ return api_type(as[1]) .. '[]'
+ end
+
+ local d = t:match('^Dict%((.*)%)')
+ if d then
+ return 'vim.api.keyset.' .. d
+ end
+
+ local d0 = t:match('^DictionaryOf%((.*)%)')
+ if d0 then
+ return 'table<string,' .. api_type(d0) .. '>'
+ end
+
+ return API_TYPES[t] or t
+end
+
+--- @param f string
+--- @param params {[1]:string,[2]:string}[]|true
+--- @return string
+local function render_fun_sig(f, params)
+ local param_str --- @type string
+ if params == true then
+ param_str = '...'
+ else
+ param_str = table.concat(
+ vim.tbl_map(
+ --- @param v {[1]:string,[2]:string}
+ --- @return string
+ function(v)
+ return v[1]
+ end,
+ params
+ ),
+ ', '
+ )
+ end
+
+ if LUA_KEYWORDS[f] then
+ return string.format("vim.fn['%s'] = function(%s) end", f, param_str)
+ else
+ return string.format('function vim.fn.%s(%s) end', f, param_str)
+ end
+end
+
+--- Uniquify names
+--- Fix any names that are lua keywords
+--- @param params {[1]:string,[2]:string,[3]:string}[]
+--- @return {[1]:string,[2]:string,[3]:string}[]
+local function process_params(params)
+ local seen = {} --- @type table<string,true>
+ local sfx = 1
+
+ for _, p in ipairs(params) do
+ if LUA_KEYWORDS[p[1]] then
+ p[1] = p[1] .. '_'
+ end
+ if seen[p[1]] then
+ p[1] = p[1] .. sfx
+ sfx = sfx + 1
+ else
+ seen[p[1]] = true
+ end
+ end
+
+ return params
+end
+
+--- @class vim.gen_vim_doc_fun
+--- @field signature string
+--- @field doc string[]
+--- @field parameters_doc table<string,string>
+--- @field return string[]
+--- @field seealso string[]
+--- @field annotations string[]
+
+--- @return table<string, vim.EvalFn>
+local function get_api_meta()
+ local mpack_f = assert(io.open(DEP_API_METADATA, 'rb'))
+ local metadata = vim.mpack.decode(mpack_f:read('*all')) --[[@as vim.api.metadata[] ]]
+ local ret = {} --- @type table<string, vim.EvalFn>
+
+ local doc_mpack_f = assert(io.open(DEP_API_DOC, 'rb'))
+ local doc_metadata = vim.mpack.decode(doc_mpack_f:read('*all')) --[[@as table<string,vim.gen_vim_doc_fun>]]
+
+ for _, fun in ipairs(metadata) do
+ if fun.lua then
+ local fdoc = doc_metadata[fun.name]
+
+ local params = {} --- @type {[1]:string,[2]:string}[]
+ for _, p in ipairs(fun.parameters) do
+ local ptype, pname = p[1], p[2]
+ params[#params + 1] = {
+ pname,
+ api_type(ptype),
+ fdoc and fdoc.parameters_doc[pname] or nil,
+ }
+ end
+
+ local r = {
+ signature = 'NA',
+ name = fun.name,
+ params = params,
+ returns = api_type(fun.return_type),
+ deprecated = fun.deprecated_since ~= nil,
+ }
+
+ if fdoc then
+ if #fdoc.doc > 0 then
+ r.desc = table.concat(fdoc.doc, '\n')
+ end
+ r.return_desc = (fdoc['return'] or {})[1]
+ end
+
+ ret[fun.name] = r
+ end
+ end
+ return ret
+end
+
+--- Convert vimdoc references to markdown literals
+--- Convert vimdoc codeblocks to markdown codeblocks
+---
+--- Ensure code blocks have one empty line before the start fence and after the closing fence.
+---
+--- @param x string
+--- @return string
+local function norm_text(x)
+ return (
+ x:gsub('|([^ ]+)|', '`%1`')
+ :gsub('\n*>lua', '\n\n```lua')
+ :gsub('\n*>vim', '\n\n```vim')
+ :gsub('\n+<$', '\n```')
+ :gsub('\n+<\n+', '\n```\n\n')
+ :gsub('%s+>\n+', '\n```\n')
+ :gsub('\n+<%s+\n?', '\n```\n')
+ )
+end
+
+--- @param _f string
+--- @param fun vim.EvalFn
+--- @param write fun(line: string)
+local function render_api_meta(_f, fun, write)
+ if not vim.startswith(fun.name, 'nvim_') then
+ return
+ end
+
+ write('')
+
+ if vim.startswith(fun.name, 'nvim__') then
+ write('--- @private')
+ end
+
+ if fun.deprecated then
+ write('--- @deprecated')
+ end
+
+ local desc = fun.desc
+ if desc then
+ for _, l in ipairs(split(norm_text(desc))) do
+ write('--- ' .. l)
+ end
+ write('---')
+ end
+
+ local param_names = {} --- @type string[]
+ local params = process_params(fun.params)
+ for _, p in ipairs(params) do
+ param_names[#param_names + 1] = p[1]
+ local pdesc = p[3]
+ if pdesc then
+ local pdesc_a = split(norm_text(pdesc))
+ write('--- @param ' .. p[1] .. ' ' .. p[2] .. ' ' .. pdesc_a[1])
+ for i = 2, #pdesc_a do
+ if not pdesc_a[i] then
+ break
+ end
+ write('--- ' .. pdesc_a[i])
+ end
+ else
+ write('--- @param ' .. p[1] .. ' ' .. p[2])
+ end
+ end
+ if fun.returns ~= '' then
+ if fun.returns_desc then
+ write('--- @return ' .. fun.returns .. ' : ' .. fun.returns_desc)
+ else
+ write('--- @return ' .. fun.returns)
+ end
+ end
+ local param_str = table.concat(param_names, ', ')
+
+ write(string.format('function vim.api.%s(%s) end', fun.name, param_str))
+end
+
+--- @return table<string, vim.EvalFn>
+local function get_api_keysets_meta()
+ local mpack_f = assert(io.open(DEP_API_METADATA, 'rb'))
+
+ --- @diagnostic disable-next-line:no-unknown
+ local metadata = assert(vim.mpack.decode(mpack_f:read('*all')))
+
+ local ret = {} --- @type table<string, vim.EvalFn>
+
+ --- @type {name: string, keys: string[], types: table<string,string>}[]
+ local keysets = metadata.keysets
+
+ for _, k in ipairs(keysets) do
+ local params = {}
+ for _, key in ipairs(k.keys) do
+ table.insert(params, {key..'?', api_type(k.types[key] or 'any')})
+ end
+ ret[k.name] = {
+ signature = 'NA',
+ name = k.name,
+ params = params,
+ }
+ end
+
+ return ret
+end
+
+--- @param _f string
+--- @param fun vim.EvalFn
+--- @param write fun(line: string)
+local function render_api_keyset_meta(_f, fun, write)
+ write('')
+ write('--- @class vim.api.keyset.' .. fun.name)
+ for _, p in ipairs(fun.params) do
+ write('--- @field ' .. p[1] .. ' ' .. p[2])
+ end
+end
+
+--- @return table<string, vim.EvalFn>
+local function get_eval_meta()
+ return require('src/nvim/eval').funcs
+end
+
+--- @param f string
+--- @param fun vim.EvalFn
+--- @param write fun(line: string)
+local function render_eval_meta(f, fun, write)
+ if fun.lua == false then
+ return
+ end
+
+ local funname = fun.name or f
+
+ local params = process_params(fun.params)
+
+ if fun.signature then
+ write('')
+ if fun.deprecated then
+ write('--- @deprecated')
+ end
+
+ local desc = fun.desc
+
+ if desc then
+ --- @type string
+ desc = desc:gsub('\n%s*\n%s*$', '\n')
+ for _, l in ipairs(split(desc)) do
+ l = l:gsub('^ ', ''):gsub('\t', ' '):gsub('@', '\\@')
+ write('--- ' .. l)
+ end
+ end
+
+ local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0
+
+ for i, param in ipairs(params) do
+ local pname, ptype = param[1], param[2]
+ local optional = (pname ~= '...' and i > req_args) and '?' or ''
+ write(string.format('--- @param %s%s %s', pname, optional, ptype))
+ end
+
+ if fun.returns ~= false then
+ write('--- @return ' .. (fun.returns or 'any'))
+ end
+
+ write(render_fun_sig(funname, params))
+
+ return
+ end
+
+ print('no doc for', funname)
+end
+
+--- @type table<string,true>
+local rendered_tags = {}
+
+--- @param name string
+--- @param fun vim.EvalFn
+--- @param write fun(line: string)
+local function render_sig_and_tag(name, fun, write)
+ local tags = { '*' .. name .. '()*' }
+
+ if fun.tags then
+ for _, t in ipairs(fun.tags) do
+ tags[#tags + 1] = '*' .. t .. '*'
+ end
+ end
+
+ local tag = table.concat(tags, ' ')
+ local siglen = #fun.signature
+ local conceal_offset = 2*(#tags - 1)
+ local tag_pad_len = math.max(1, 80 - #tag + conceal_offset)
+
+ if siglen + #tag > 80 then
+ write(string.rep(' ', tag_pad_len) .. tag)
+ write(fun.signature)
+ else
+ write(string.format('%s%s%s', fun.signature, string.rep(' ', tag_pad_len - siglen), tag))
+ end
+end
+
+--- @param f string
+--- @param fun vim.EvalFn
+--- @param write fun(line: string)
+local function render_eval_doc(f, fun, write)
+ if fun.deprecated then
+ return
+ end
+
+ if not fun.signature then
+ return
+ end
+
+ local desc = fun.desc
+
+ if not desc then
+ write(fun.signature)
+ return
+ end
+
+ local name = fun.name or f
+
+ if rendered_tags[name] then
+ write(fun.signature)
+ else
+ render_sig_and_tag(name, fun, write)
+ rendered_tags[name] = true
+ end
+
+ desc = vim.trim(desc)
+ local desc_l = split(desc)
+ for _, l in ipairs(desc_l) do
+ l = l:gsub('^ ', '')
+ if vim.startswith(l, '<') and not l:match('^<[^ \t]+>') then
+ write('<\t\t' .. l:sub(2))
+ elseif l:match('^>[a-z0-9]*$') then
+ write(l)
+ else
+ write('\t\t' .. l)
+ end
+ end
+
+ if #desc_l > 0 and not desc_l[#desc_l]:match('^<?$') then
+ write('')
+ end
+end
+
+--- @param d vim.option_defaults
+--- @param vimdoc? boolean
+--- @return string
+local function render_option_default(d, vimdoc)
+ local dt --- @type integer|boolean|string|fun(): string
+ if d.if_false ~= nil then
+ dt = d.if_false
+ else
+ dt = d.if_true
+ end
+
+ if vimdoc then
+ if d.doc then
+ return d.doc
+ end
+ if type(dt) == 'boolean' then
+ return dt and 'on' or 'off'
+ end
+ end
+
+ if dt == "" or dt == nil or type(dt) == 'function' then
+ dt = d.meta
+ end
+
+ local v --- @type string
+ if not vimdoc then
+ v = vim.inspect(dt) --[[@as string]]
+ else
+ v = type(dt) == 'string' and '"'..dt..'"' or tostring(dt)
+ end
+
+ --- @type table<string, string|false>
+ local envvars = {
+ TMPDIR = false,
+ VIMRUNTIME = false,
+ XDG_CONFIG_HOME = vim.env.HOME..'/.local/config',
+ XDG_DATA_HOME = vim.env.HOME..'/.local/share',
+ XDG_STATE_HOME = vim.env.HOME..'/.local/state',
+ }
+
+ for name, default in pairs(envvars) do
+ local value = vim.env[name] or default
+ if value then
+ v = v:gsub(vim.pesc(value), '$'..name)
+ end
+ end
+
+ return v
+end
+
+--- @param _f string
+--- @param opt vim.option_meta
+--- @param write fun(line: string)
+local function render_option_meta(_f, opt, write)
+ write('')
+ for _, l in ipairs(split(norm_text(opt.desc))) do
+ write('--- '..l)
+ end
+
+ write('--- @type '..OPTION_TYPES[opt.type])
+ write('vim.o.'..opt.full_name..' = '..render_option_default(opt.defaults))
+ if opt.abbreviation then
+ write('vim.o.'..opt.abbreviation..' = vim.o.'..opt.full_name)
+ end
+
+ for _, s in pairs {
+ {'wo', 'window'},
+ {'bo', 'buffer'},
+ {'go', 'global'},
+ } do
+ local id, scope = s[1], s[2]
+ if vim.list_contains(opt.scope, scope) or (id == 'go' and #opt.scope > 1) then
+ local pfx = 'vim.'..id..'.'
+ write(pfx..opt.full_name..' = vim.o.'..opt.full_name)
+ if opt.abbreviation then
+ write(pfx..opt.abbreviation..' = '..pfx..opt.full_name)
+ end
+ end
+ end
+end
+
+--- @param s string[]
+--- @return string
+local function scope_to_doc(s)
+ local m = {
+ global = 'global',
+ buffer = 'local to buffer',
+ window = 'local to window',
+ tab = 'local to tab page'
+ }
+
+ if #s == 1 then
+ return m[s[1]]
+ end
+ assert(s[1] == 'global')
+ return 'global or '..m[s[2]]..' |global-local|'
+end
+
+-- @param o vim.option_meta
+-- @return string
+local function scope_more_doc(o)
+ if
+ vim.list_contains({
+ 'bufhidden',
+ 'buftype',
+ 'filetype',
+ 'modified',
+ 'previewwindow',
+ 'readonly',
+ 'scroll',
+ 'syntax',
+ 'winfixheight',
+ 'winfixwidth',
+ }, o.full_name)
+ then
+ return ' |local-noglobal|'
+ end
+
+ return ''
+end
+
+--- @return table<string,vim.option_meta>
+local function get_option_meta()
+ local opts = require('src/nvim/options').options
+ local optinfo = vim.api.nvim_get_all_options_info()
+ local ret = {} --- @type table<string,vim.option_meta>
+ for _, o in ipairs(opts) do
+ if o.desc then
+ if o.full_name == 'cmdheight' then
+ table.insert(o.scope, 'tab')
+ end
+ local r = vim.deepcopy(o) --[[@as vim.option_meta]]
+ r.desc = o.desc:gsub('^ ', ''):gsub('\n ', '\n')
+ r.defaults = r.defaults or {}
+ if r.defaults.meta == nil then
+ r.defaults.meta = optinfo[o.full_name].default
+ end
+ ret[o.full_name] = r
+ end
+ end
+ return ret
+end
+
+--- @param opt vim.option_meta
+--- @return string[]
+local function build_option_tags(opt)
+ --- @type string[]
+ local tags = { opt.full_name }
+
+ tags[#tags+1] = opt.abbreviation
+ if opt.type == 'bool' then
+ for i = 1, #tags do
+ tags[#tags+1] = 'no'..tags[i]
+ end
+ end
+
+ for i, t in ipairs(tags) do
+ tags[i] = "'"..t.."'"
+ end
+
+ for _, t in ipairs(opt.tags or {}) do
+ tags[#tags+1] = t
+ end
+
+ for i, t in ipairs(tags) do
+ tags[i] = "*"..t.."*"
+ end
+
+ return tags
+end
+
+--- @param _f string
+--- @param opt vim.option_meta
+--- @param write fun(line: string)
+local function render_option_doc(_f, opt, write)
+ local tags = build_option_tags(opt)
+ local tag_str = table.concat(tags, ' ')
+ local conceal_offset = 2*(#tags - 1)
+ local tag_pad = string.rep('\t', math.ceil((64 - #tag_str + conceal_offset) / 8))
+ -- local pad = string.rep(' ', 80 - #tag_str + conceal_offset)
+ write(tag_pad..tag_str)
+
+ local name_str --- @type string
+ if opt.abbreviation then
+ name_str = string.format("'%s' '%s'", opt.full_name, opt.abbreviation)
+ else
+ name_str = string.format("'%s'", opt.full_name)
+ end
+
+ local otype = opt.type == 'bool' and 'boolean' or opt.type
+ if opt.defaults.doc or opt.defaults.if_true ~= nil or opt.defaults.meta ~= nil then
+ local v = render_option_default(opt.defaults, true)
+ local pad = string.rep('\t', math.max(1, math.ceil((24 - #name_str) / 8)))
+ if opt.defaults.doc then
+ local deflen = #string.format('%s%s%s (', name_str, pad, otype)
+ --- @type string
+ v = v:gsub('\n', '\n'..string.rep(' ', deflen - 2))
+ end
+ write(string.format('%s%s%s\t(default %s)', name_str, pad, otype, v))
+ else
+ write(string.format('%s\t%s', name_str, otype))
+ end
+
+ write('\t\t\t'..scope_to_doc(opt.scope)..scope_more_doc(opt))
+ for _, l in ipairs(split(opt.desc)) do
+ if l == '<' or l:match('^<%s') then
+ write(l)
+ else
+ write('\t'..l:gsub('\\<', '<'))
+ end
+ end
+end
+
+--- @class nvim.gen_eval_files.elem
+--- @field path string
+--- @field from? string Skip lines in path until this pattern is reached.
+--- @field funcs fun(): table<string, table>
+--- @field render fun(f:string,obj:table,write:fun(line:string))
+--- @field header? string[]
+--- @field footer? string[]
+
+--- @type nvim.gen_eval_files.elem[]
+local CONFIG = {
+ {
+ path = 'runtime/lua/vim/_meta/vimfn.lua',
+ header = LUA_META_HEADER,
+ funcs = get_eval_meta,
+ render = render_eval_meta,
+ },
+ {
+ path = 'runtime/lua/vim/_meta/api.lua',
+ header = LUA_API_META_HEADER,
+ funcs = get_api_meta,
+ render = render_api_meta,
+ },
+ {
+ path = 'runtime/lua/vim/_meta/api_keysets.lua',
+ header = LUA_META_HEADER,
+ funcs = get_api_keysets_meta,
+ render = render_api_keyset_meta,
+ },
+ {
+ path = 'runtime/doc/builtin.txt',
+ funcs = get_eval_meta,
+ render = render_eval_doc,
+ header = {
+ '*builtin.txt* Nvim',
+ '',
+ '',
+ '\t\t NVIM REFERENCE MANUAL',
+ '',
+ '',
+ 'Builtin functions\t\t*vimscript-functions* *builtin-functions*',
+ '',
+ 'For functions grouped by what they are used for see |function-list|.',
+ '',
+ '\t\t\t\t Type |gO| to see the table of contents.',
+ '==============================================================================',
+ '1. Details *builtin-function-details*',
+ '',
+ },
+ footer = {
+ '==============================================================================',
+ '2. Matching a pattern in a String *string-match*',
+ '',
+ 'This is common between several functions. A regexp pattern as explained at',
+ '|pattern| is normally used to find a match in the buffer lines. When a',
+ 'pattern is used to find a match in a String, almost everything works in the',
+ 'same way. The difference is that a String is handled like it is one line.',
+ 'When it contains a "\\n" character, this is not seen as a line break for the',
+ 'pattern. It can be matched with a "\\n" in the pattern, or with ".". Example:',
+ '>vim',
+ '\tlet a = "aaaa\\nxxxx"',
+ '\techo matchstr(a, "..\\n..")',
+ '\t" aa',
+ '\t" xx',
+ '\techo matchstr(a, "a.x")',
+ '\t" a',
+ '\t" x',
+ '',
+ 'Don\'t forget that "^" will only match at the first character of the String and',
+ '"$" at the last character of the string. They don\'t match after or before a',
+ '"\\n".',
+ '',
+ ' vim:tw=78:ts=8:noet:ft=help:norl:',
+ },
+ },
+ {
+ path = 'runtime/lua/vim/_meta/options.lua',
+ header = LUA_OPTION_META_HEADER,
+ funcs = get_option_meta,
+ render = render_option_meta,
+ },
+ {
+ path = 'runtime/doc/options.txt',
+ header = { '' },
+ from = 'A jump table for the options with a short description can be found at |Q_op|.',
+ footer = {
+ ' vim:tw=78:ts=8:noet:ft=help:norl:'
+ },
+ funcs = get_option_meta,
+ render = render_option_doc,
+ }
+}
+
+--- @param elem nvim.gen_eval_files.elem
+local function render(elem)
+ print('Rendering '..elem.path)
+ local from_lines = {} --- @type string[]
+ local from = elem.from
+ if from then
+ for line in io.lines(elem.path) do
+ from_lines[#from_lines+1] = line
+ if line:match(from) then
+ break
+ end
+ end
+ end
+
+ local o = assert(io.open(elem.path, 'w'))
+
+ --- @param l string
+ local function write(l)
+ local l1 = l:gsub('%s+$', '')
+ o:write(l1)
+ o:write('\n')
+ end
+
+ for _, l in ipairs(from_lines) do
+ write(l)
+ end
+
+ for _, l in ipairs(elem.header or {}) do
+ write(l)
+ end
+
+ local funcs = elem.funcs()
+
+ --- @type string[]
+ local fnames = vim.tbl_keys(funcs)
+ table.sort(fnames)
+
+ for _, f in ipairs(fnames) do
+ elem.render(f, funcs[f], write)
+ end
+
+ for _, l in ipairs(elem.footer or {}) do
+ write(l)
+ end
+
+ o:close()
+end
+
+local function main()
+ for _, c in ipairs(CONFIG) do
+ render(c)
+ end
+end
+
+main()
diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua
index 2563f2f410..633207e018 100644
--- a/scripts/gen_help_html.lua
+++ b/scripts/gen_help_html.lua
@@ -34,6 +34,8 @@ local spell_dict = {
neovim = 'Nvim',
lua = 'Lua',
VimL = 'Vimscript',
+ vimL = 'Vimscript',
+ viml = 'Vimscript',
}
local language = nil
@@ -43,6 +45,7 @@ local M = {}
-- All other files are "legacy" files which require fixed-width layout.
local new_layout = {
['api.txt'] = true,
+ ['lsp.txt'] = true,
['channel.txt'] = true,
['deprecated.txt'] = true,
['develop.txt'] = true,
@@ -57,21 +60,10 @@ local new_layout = {
-- TODO: These known invalid |links| require an update to the relevant docs.
local exclude_invalid = {
- ["'previewpopup'"] = "quickref.txt",
- ["'pvp'"] = "quickref.txt",
["'string'"] = "eval.txt",
Query = 'treesitter.txt',
- ['eq?'] = 'treesitter.txt',
- ['lsp-request'] = 'lsp.txt',
matchit = 'vim_diff.txt',
- ['matchit.txt'] = 'help.txt',
["set!"] = "treesitter.txt",
- ['v:_null_blob'] = 'builtin.txt',
- ['v:_null_dict'] = 'builtin.txt',
- ['v:_null_list'] = 'builtin.txt',
- ['v:_null_string'] = 'builtin.txt',
- ['vim.lsp.buf_request()'] = 'lsp.txt',
- ['vim.lsp.util.get_progress_messages()'] = 'lsp.txt',
}
-- False-positive "invalid URLs".
@@ -90,6 +82,11 @@ local exclude_invalid_urls = {
["http://www.jclark.com/"] = "quickfix.txt",
}
+-- Deprecated, brain-damaged files that I don't care about.
+local ignore_errors = {
+ ['pi_netrw.txt'] = true,
+}
+
local function tofile(fname, text)
local f = io.open(fname, 'w')
if not f then
@@ -145,11 +142,11 @@ local function trim(s, dir)
return vim.fn.trim(s, '\r\t\n ', dir or 0)
end
--- Remove common punctuation from URLs.
---
--- TODO: fix this in the parser instead... https://github.com/neovim/tree-sitter-vimdoc
---
--- @returns (fixed_url, removed_chars) where `removed_chars` is in the order found in the input.
+--- Removes common punctuation from URLs.
+---
+--- TODO: fix this in the parser instead... https://github.com/neovim/tree-sitter-vimdoc
+---
+--- @returns (fixed_url, removed_chars) where `removed_chars` is in the order found in the input.
local function fix_url(url)
local removed_chars = ''
local fixed_url = url
@@ -163,7 +160,7 @@ local function fix_url(url)
return fixed_url, removed_chars
end
--- Checks if a given line is a "noise" line that doesn't look good in HTML form.
+--- Checks if a given line is a "noise" line that doesn't look good in HTML form.
local function is_noise(line, noise_lines)
if (
-- First line is always noise.
@@ -188,7 +185,7 @@ local function is_noise(line, noise_lines)
return false
end
--- Creates a github issue URL at neovim/tree-sitter-vimdoc with prefilled content.
+--- Creates a github issue URL at neovim/tree-sitter-vimdoc with prefilled content.
local function get_bug_url_vimdoc(fname, to_fname, sample_text)
local this_url = string.format('https://neovim.io/doc/user/%s', vim.fs.basename(to_fname))
local bug_url = ('https://github.com/neovim/tree-sitter-vimdoc/issues/new?labels=bug&title=parse+error%3A+'
@@ -201,7 +198,7 @@ local function get_bug_url_vimdoc(fname, to_fname, sample_text)
return bug_url
end
--- Creates a github issue URL at neovim/neovim with prefilled content.
+--- Creates a github issue URL at neovim/neovim with prefilled content.
local function get_bug_url_nvim(fname, to_fname, sample_text, token_name)
local this_url = string.format('https://neovim.io/doc/user/%s', vim.fs.basename(to_fname))
local bug_url = ('https://github.com/neovim/neovim/issues/new?labels=bug&title=user+docs+HTML%3A+'
@@ -216,7 +213,7 @@ local function get_bug_url_nvim(fname, to_fname, sample_text, token_name)
return bug_url
end
--- Gets a "foo.html" name from a "foo.txt" helpfile name.
+--- Gets a "foo.html" name from a "foo.txt" helpfile name.
local function get_helppage(f)
if not f then
return nil
@@ -231,9 +228,9 @@ local function get_helppage(f)
return (f:gsub('%.txt$', '.html'))
end
--- Counts leading spaces (tab=8) to decide the indent size of multiline text.
---
--- Blank lines (empty or whitespace-only) are ignored.
+--- Counts leading spaces (tab=8) to decide the indent size of multiline text.
+---
+--- Blank lines (empty or whitespace-only) are ignored.
local function get_indent(s)
local min_indent = nil
for line in vim.gsplit(s, '\n') do
@@ -245,7 +242,7 @@ local function get_indent(s)
return min_indent or 0
end
--- Removes the common indent level, after expanding tabs to 8 spaces.
+--- Removes the common indent level, after expanding tabs to 8 spaces.
local function trim_indent(s)
local indent_size = get_indent(s)
local trimmed = ''
@@ -256,7 +253,7 @@ local function trim_indent(s)
return trimmed:sub(1, -2)
end
--- Gets raw buffer text in the node's range (+/- an offset), as a newline-delimited string.
+--- Gets raw buffer text in the node's range (+/- an offset), as a newline-delimited string.
local function getbuflinestr(node, bufnr, offset)
local line1, _, line2, _ = node:range()
line1 = line1 - offset
@@ -265,8 +262,8 @@ local function getbuflinestr(node, bufnr, offset)
return table.concat(lines, '\n')
end
--- Gets the whitespace just before `node` from the raw buffer text.
--- Needed for preformatted `old` lines.
+--- Gets the whitespace just before `node` from the raw buffer text.
+--- Needed for preformatted `old` lines.
local function getws(node, bufnr)
local line1, c1, line2, _ = node:range()
local raw = vim.fn.getbufline(bufnr, line1 + 1, line2 + 1)[1]
@@ -283,18 +280,21 @@ local function get_tagname(node, bufnr)
return helppage, tag
end
--- Returns true if the given invalid tagname is a false positive.
+--- Returns true if the given invalid tagname is a false positive.
local function ignore_invalid(s)
return not not (
exclude_invalid[s]
-- Strings like |~/====| appear in various places and the parser thinks they are links, but they
-- are just table borders.
or s:find('===')
- or s:find('---')
+ or s:find('%-%-%-')
)
end
-local function ignore_parse_error(s)
+local function ignore_parse_error(fname, s)
+ if ignore_errors[vim.fs.basename(fname)] then
+ return true
+ end
return (
-- Ignore parse errors for unclosed tag.
-- This is common in vimdocs and is treated as plaintext by :help.
@@ -315,7 +315,7 @@ local function has_ancestor(node, ancestor_name)
return false
end
--- Gets the first matching child node matching `name`.
+--- Gets the first matching child node matching `name`.
local function first(node, name)
for c, _ in node:iter_children() do
if c:named() and c:type() == name then
@@ -337,10 +337,10 @@ local function validate_link(node, bufnr, fname)
return helppage, tagname, ignored
end
--- TODO: port the logic from scripts/check_urls.vim
+--- TODO: port the logic from scripts/check_urls.vim
local function validate_url(text, fname)
local ignored = false
- if vim.fs.basename(fname) == 'pi_netrw.txt' then
+ if ignore_errors[vim.fs.basename(fname)] then
ignored = true
elseif text:find('http%:') and not exclude_invalid_urls[text] then
invalid_urls[text] = vim.fs.basename(fname)
@@ -348,10 +348,12 @@ local function validate_url(text, fname)
return ignored
end
--- Traverses the tree at `root` and checks that |tag| links point to valid helptags.
+--- Traverses the tree at `root` and checks that |tag| links point to valid helptags.
local function visit_validate(root, level, lang_tree, opt, stats)
level = level or 0
local node_name = (root.named and root:named()) and root:type() or nil
+ -- Parent kind (string).
+ local parent = root:parent() and root:parent():type() or nil
local toplevel = level < 1
local function node_text(node)
return vim.treesitter.get_node_text(node or root, opt.buf)
@@ -367,19 +369,21 @@ local function visit_validate(root, level, lang_tree, opt, stats)
end
if node_name == 'ERROR' then
- if ignore_parse_error(text) then
+ if ignore_parse_error(opt.fname, text) then
return
end
-- Store the raw text to give context to the error report.
- local sample_text = not toplevel and getbuflinestr(root, opt.buf, 3) or '[top level!]'
+ local sample_text = not toplevel and getbuflinestr(root, opt.buf, 0) or '[top level!]'
+ -- Flatten the sample text to a single, truncated line.
+ sample_text = vim.trim(sample_text):gsub('[\t\n]', ' '):sub(1, 80)
table.insert(stats.parse_errors, sample_text)
- elseif node_name == 'word' or node_name == 'uppercase_name' then
- if spell_dict[text] then
- if not invalid_spelling[text] then
- invalid_spelling[text] = { vim.fs.basename(opt.fname) }
- else
- table.insert(invalid_spelling[text], vim.fs.basename(opt.fname))
- end
+ elseif (node_name == 'word' or node_name == 'uppercase_name')
+ and (not vim.tbl_contains({'codespan', 'taglink', 'tag'}, parent))
+ then
+ local text_nopunct = vim.fn.trim(text, '.,', 0) -- Ignore some punctuation.
+ if spell_dict[text_nopunct] then
+ invalid_spelling[text_nopunct] = invalid_spelling[text_nopunct] or {}
+ invalid_spelling[text_nopunct][vim.fs.basename(opt.fname)] = node_text(root:parent())
end
elseif node_name == 'url' then
local fixed_url, _ = fix_url(trim(text))
@@ -461,6 +465,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
local hname = (node_text():gsub('%-%-%-%-+', ''):gsub('%=%=%=%=+', ''):gsub('%*.*%*', ''))
-- Use the first *tag* node as the heading anchor, if any.
local tagnode = first(root, 'tag')
+ -- Use the *tag* as the heading anchor id, if possible.
local tagname = tagnode and url_encode(node_text(tagnode:child(1), false)) or to_heading_tag(hname)
if node_name == 'h1' or #headings == 0 then
table.insert(headings, { name = hname, subheadings = {}, tag = tagname })
@@ -468,9 +473,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
table.insert(headings[#headings].subheadings, { name = hname, subheadings = {}, tag = tagname })
end
local el = node_name == 'h1' and 'h2' or 'h3'
- -- If we are re-using the *tag*, this heading anchor is redundant.
- local a = tagnode and '' or ('<a name="%s"></a>'):format(tagname)
- return ('%s<%s class="help-heading">%s</%s>\n'):format(a, el, text, el)
+ return ('<%s id="%s" class="help-heading">%s</%s>\n'):format(el, tagname, text, el)
elseif node_name == 'column_heading' or node_name == 'column_name' then
if root:has_error() then
return text
@@ -491,7 +494,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
return '' -- Discard common "noise" lines.
end
-- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout.
- local div = opt.old and root:child(0) and vim.tbl_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type())
+ local div = opt.old and root:child(0) and vim.list_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type())
return string.format('%s%s', div and trim(text) or text, div and '' or '\n')
elseif node_name == 'line_li' then
local sib = root:prev_sibling()
@@ -522,7 +525,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
s = fix_tab_after_conceal(s, node_text(root:next_sibling()))
end
return s
- elseif vim.tbl_contains({'codespan', 'keycode'}, node_name) then
+ elseif vim.list_contains({'codespan', 'keycode'}, node_name) then
if root:has_error() then
return text
end
@@ -538,7 +541,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
elseif node_name == 'language' then
language = node_text(root)
return ''
- elseif node_name == 'code' then
+ elseif node_name == 'code' then -- Highlighted codeblock (child).
if is_blank(text) then
return ''
end
@@ -554,7 +557,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
if root:has_error() then
return text
end
- local in_heading = vim.tbl_contains({'h1', 'h2', 'h3'}, parent)
+ local in_heading = vim.list_contains({'h1', 'h2', 'h3'}, parent)
local cssclass = (not in_heading and get_indent(node_text()) > 8) and 'help-tag-right' or 'help-tag'
local tagname = node_text(root:child(1), false)
if vim.tbl_count(stats.first_tags) < 2 then
@@ -563,12 +566,15 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
return ''
end
local el = in_heading and 'span' or 'code'
- local s = ('%s<a name="%s"></a><%s class="%s">%s</%s>'):format(ws(), url_encode(tagname), el, cssclass, trimmed, el)
+ local encoded_tagname = url_encode(tagname)
+ local s = ('%s<%s id="%s" class="%s"><a href="#%s">%s</a></%s>'):format(ws(), el, encoded_tagname, cssclass, encoded_tagname, trimmed, el)
if opt.old then
s = fix_tab_after_conceal(s, node_text(root:next_sibling()))
end
if in_heading and prev ~= 'tag' then
+ -- Don't set "id", let the heading use the tag as its "id" (used by search engines).
+ s = ('%s<%s class="%s"><a href="#%s">%s</a></%s>'):format(ws(), el, cssclass, encoded_tagname, trimmed, el)
-- Start the <span> container for tags in a heading.
-- This makes "justify-content:space-between" right-align the tags.
-- <h2>foo bar<span>tag1 tag2</span></h2>
@@ -579,7 +585,7 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
end
return s
elseif node_name == 'ERROR' then
- if ignore_parse_error(trimmed) then
+ if ignore_parse_error(opt.fname, trimmed) then
return text
end
@@ -601,7 +607,7 @@ local function get_helpfiles(include)
for f, type in vim.fs.dir(dir) do
if (vim.endswith(f, '.txt')
and type == 'file'
- and (not include or vim.tbl_contains(include, f))) then
+ and (not include or vim.list_contains(include, f))) then
local fullpath = vim.fn.fnamemodify(('%s/%s'):format(dir, f), ':p')
table.insert(rv, fullpath)
end
@@ -609,7 +615,7 @@ local function get_helpfiles(include)
return rv
end
--- Populates the helptags map.
+--- Populates the helptags map.
local function get_helptags(help_dir)
local m = {}
-- Load a random help file to convince taglist() to do its job.
@@ -624,17 +630,19 @@ local function get_helptags(help_dir)
return m
end
--- Use the help.so parser defined in the build, not whatever happens to be installed on the system.
+--- Use the vimdoc parser defined in the build, not whatever happens to be installed on the system.
local function ensure_runtimepath()
if not vim.o.runtimepath:find('build/lib/nvim/') then
vim.cmd[[set runtimepath^=./build/lib/nvim/]]
end
end
--- Opens `fname` in a buffer and gets a treesitter parser for the buffer contents.
---
--- @returns lang_tree, bufnr
-local function parse_buf(fname)
+--- Opens `fname` in a buffer and gets a treesitter parser for the buffer contents.
+---
+--- @param fname string help file to parse
+--- @param parser_path string? path to non-default vimdoc.so
+--- @returns lang_tree, bufnr
+local function parse_buf(fname, parser_path)
local buf
if type(fname) == 'string' then
vim.cmd('split '..vim.fn.fnameescape(fname)) -- Filename.
@@ -643,21 +651,25 @@ local function parse_buf(fname)
buf = fname
vim.cmd('sbuffer '..tostring(fname)) -- Buffer number.
end
- -- vim.treesitter.require_language('help', './build/lib/nvim/parser/help.so')
- local lang_tree = vim.treesitter.get_parser(buf, 'help')
+ if parser_path then
+ vim.treesitter.language.add('vimdoc', { path = parser_path })
+ end
+ local lang_tree = vim.treesitter.get_parser(buf)
return lang_tree, buf
end
--- Validates one :help file `fname`:
--- - checks that |tag| links point to valid helptags.
--- - recursively counts parse errors ("ERROR" nodes)
---
--- @returns { invalid_links: number, parse_errors: number }
-local function validate_one(fname)
+--- Validates one :help file `fname`:
+--- - checks that |tag| links point to valid helptags.
+--- - recursively counts parse errors ("ERROR" nodes)
+---
+--- @param fname string help file to validate
+--- @param parser_path string? path to non-default vimdoc.so
+--- @returns { invalid_links: number, parse_errors: string[] }
+local function validate_one(fname, parser_path)
local stats = {
parse_errors = {},
}
- local lang_tree, buf = parse_buf(fname)
+ local lang_tree, buf = parse_buf(fname, parser_path)
for _, tree in ipairs(lang_tree:trees()) do
visit_validate(tree:root(), 0, tree, { buf = buf, fname = fname, }, stats)
end
@@ -666,20 +678,21 @@ local function validate_one(fname)
return stats
end
--- Generates HTML from one :help file `fname` and writes the result to `to_fname`.
---
--- @param fname Source :help file
--- @param to_fname Destination .html file
--- @param old boolean Preformat paragraphs (for old :help files which are full of arbitrary whitespace)
---
--- @returns html, stats
-local function gen_one(fname, to_fname, old, commit)
+--- Generates HTML from one :help file `fname` and writes the result to `to_fname`.
+---
+--- @param fname string Source :help file
+--- @param to_fname string Destination .html file
+--- @param old boolean Preformat paragraphs (for old :help files which are full of arbitrary whitespace)
+--- @param parser_path string? path to non-default vimdoc.so
+---
+--- @returns html, stats
+local function gen_one(fname, to_fname, old, commit, parser_path)
local stats = {
noise_lines = {},
parse_errors = {},
first_tags = {}, -- Track the first few tags in doc.
}
- local lang_tree, buf = parse_buf(fname)
+ local lang_tree, buf = parse_buf(fname, parser_path)
local headings = {} -- Headings (for ToC). 2-dimensional: h1 contains h2/h3.
local title = to_titlecase(basename_noext(fname))
@@ -691,11 +704,17 @@ local function gen_one(fname, to_fname, old, commit)
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Neovim user documentation">
+
+ <!-- algolia docsearch https://docsearch.algolia.com/docs/docsearch-v3/ -->
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@docsearch/css@3" />
+ <link rel="preconnect" href="https://X185E15FPG-dsn.algolia.net" crossorigin />
+
<link href="/css/normalize.min.css" rel="stylesheet">
<link href="/css/bootstrap.css" rel="stylesheet">
<link href="/css/main.css" rel="stylesheet">
<link href="help.css" rel="stylesheet">
<link href="/highlight/styles/neovim.min.css" rel="stylesheet">
+
<script src="/highlight/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<title>%s - Neovim docs</title>
@@ -766,19 +785,20 @@ local function gen_one(fname, to_fname, old, commit)
main = ([[
<header class="container">
<nav class="navbar navbar-expand-lg">
- <div>
+ <div class="container-fluid">
<a href="/" class="navbar-brand" aria-label="logo">
<!--TODO: use <img src="….svg"> here instead. Need one that has green lettering instead of gray. -->
%s
<!--<img src="https://neovim.io/logos/neovim-logo.svg" width="173" height="50" alt="Neovim" />-->
</a>
+ <div id="docsearch"></div> <!-- algolia docsearch https://docsearch.algolia.com/docs/docsearch-v3/ -->
</div>
</nav>
</header>
<div class="container golden-grid help-body">
<div class="col-wide">
- <a name="%s"></a><a name="%s"></a><h1>%s</h1>
+ <a name="%s"></a><h1 id="%s">%s</h1>
<p>
<i>
Nvim <code>:help</code> pages, <a href="https://github.com/neovim/neovim/blob/master/scripts/gen_help_html.lua">generated</a>
@@ -789,7 +809,7 @@ local function gen_one(fname, to_fname, old, commit)
<hr/>
%s
</div>
- ]]):format(logo_svg, stats.first_tags[1] or '', stats.first_tags[2] or '', title, vim.fs.basename(fname), main)
+ ]]):format(logo_svg, stats.first_tags[2] or '', stats.first_tags[1] or '', title, vim.fs.basename(fname), main)
local toc = [[
<div class="col-narrow toc">
@@ -825,6 +845,18 @@ local function gen_one(fname, to_fname, old, commit)
parse_errors: %d %s | <span title="%s">noise_lines: %d</span>
</div>
<div>
+
+ <!-- algolia docsearch https://docsearch.algolia.com/docs/docsearch-v3/ -->
+ <script src="https://cdn.jsdelivr.net/npm/@docsearch/js@3"></script>
+ <script type="module">
+ docsearch({
+ container: '#docsearch',
+ appId: 'X185E15FPG',
+ apiKey: 'b5e6b2f9c636b2b471303205e59832ed',
+ indexName: 'nvim',
+ });
+ </script>
+
</footer>
]]):format(
os.date('%Y-%m-%d %H:%M'), commit, commit:sub(1, 7), #stats.parse_errors, bug_link,
@@ -908,6 +940,7 @@ local function gen_css(fname)
padding-top: 10px;
padding-bottom: 10px;
}
+
.old-help-para {
padding-top: 10px;
padding-bottom: 10px;
@@ -917,6 +950,13 @@ local function gen_css(fname)
font-size: 16px;
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
}
+ .old-help-para pre {
+ /* All text in .old-help-para is formatted as "white-space:pre" so text following <pre> is
+ already visually separated by the linebreak. */
+ margin-bottom: 0;
+ }
+
+ /* TODO: should this rule be deleted? help tags are rendered as <code> or <span>, not <a> */
a.help-tag, a.help-tag:focus, a.help-tag:hover {
color: inherit;
text-decoration: none;
@@ -927,6 +967,17 @@ local function gen_css(fname)
/* Tag pseudo-header common in :help docs. */
.help-tag-right {
color: var(--tag-color);
+ margin-left: auto;
+ margin-right: 0;
+ float: right;
+ }
+ .help-tag a,
+ .help-tag-right a {
+ color: inherit;
+ }
+ .help-tag a:not(:hover),
+ .help-tag-right a:not(:hover) {
+ text-decoration: none;
}
h1 .help-tag, h2 .help-tag, h3 .help-tag {
font-size: smaller;
@@ -967,6 +1018,9 @@ local function gen_css(fname)
font-size: 16px;
margin-top: 10px;
}
+ pre:last-child {
+ margin-bottom: 0;
+ }
pre:hover,
.help-heading:hover {
overflow: visible;
@@ -1038,18 +1092,21 @@ end
--- @param include table|nil Process only these filenames. Example: {'api.txt', 'autocmd.txt', 'channel.txt'}
---
--- @returns info dict
-function M.gen(help_dir, to_dir, include, commit)
+function M.gen(help_dir, to_dir, include, commit, parser_path)
vim.validate{
- help_dir={help_dir, function(d) return vim.fn.isdirectory(d) == 1 end, 'valid directory'},
+ help_dir={help_dir, function(d) return vim.fn.isdirectory(vim.fn.expand(d)) == 1 end, 'valid directory'},
to_dir={to_dir, 's'},
include={include, 't', true},
commit={commit, 's', true},
+ parser_path={parser_path, function(f) return f == nil or vim.fn.filereadable(vim.fn.expand(f)) == 1 end, 'valid vimdoc.{so,dll} filepath'},
}
local err_count = 0
ensure_runtimepath()
- tagmap = get_helptags(help_dir)
+ tagmap = get_helptags(vim.fn.expand(help_dir))
helpfiles = get_helpfiles(include)
+ to_dir = vim.fn.expand(to_dir)
+ parser_path = parser_path and vim.fn.expand(parser_path) or nil
print(('output dir: %s'):format(to_dir))
vim.fn.mkdir(to_dir, 'p')
@@ -1058,7 +1115,7 @@ function M.gen(help_dir, to_dir, include, commit)
for _, f in ipairs(helpfiles) do
local helpfile = vim.fs.basename(f)
local to_fname = ('%s/%s'):format(to_dir, get_helppage(helpfile))
- local html, stats = gen_one(f, to_fname, not new_layout[helpfile], commit or '?')
+ local html, stats = gen_one(f, to_fname, not new_layout[helpfile], commit or '?', parser_path)
tofile(to_fname, html)
print(('generated (%-4s errors): %-15s => %s'):format(#stats.parse_errors, helpfile, vim.fs.basename(to_fname)))
err_count = err_count + #stats.parse_errors
@@ -1081,20 +1138,27 @@ end
-- This is 10x faster than gen(), for use in CI.
--
-- @returns results dict
-function M.validate(help_dir, include)
+function M.validate(help_dir, include, parser_path)
vim.validate{
- help_dir={help_dir, function(d) return vim.fn.isdirectory(d) == 1 end, 'valid directory'},
+ help_dir={help_dir, function(d) return vim.fn.isdirectory(vim.fn.expand(d)) == 1 end, 'valid directory'},
include={include, 't', true},
+ parser_path={parser_path, function(f) return f == nil or vim.fn.filereadable(vim.fn.expand(f)) == 1 end, 'valid vimdoc.{so,dll} filepath'},
}
local err_count = 0
+ local files_to_errors = {}
ensure_runtimepath()
- tagmap = get_helptags(help_dir)
+ tagmap = get_helptags(vim.fn.expand(help_dir))
helpfiles = get_helpfiles(include)
+ parser_path = parser_path and vim.fn.expand(parser_path) or nil
for _, f in ipairs(helpfiles) do
local helpfile = vim.fs.basename(f)
- local rv = validate_one(f)
+ local rv = validate_one(f, parser_path)
print(('validated (%-4s errors): %s'):format(#rv.parse_errors, helpfile))
+ if #rv.parse_errors > 0 then
+ files_to_errors[helpfile] = rv.parse_errors
+ vim.print(('%s'):format(vim.iter(rv.parse_errors):fold('', function(s, v) return s..'\n '..v end)))
+ end
err_count = err_count + #rv.parse_errors
end
@@ -1104,6 +1168,7 @@ function M.validate(help_dir, include)
invalid_links = invalid_links,
invalid_urls = invalid_urls,
invalid_spelling = invalid_spelling,
+ parse_errors = files_to_errors,
}
end
diff --git a/scripts/gen_lsp.lua b/scripts/gen_lsp.lua
new file mode 100644
index 0000000000..6ff8dcb3f4
--- /dev/null
+++ b/scripts/gen_lsp.lua
@@ -0,0 +1,290 @@
+--[[
+Generates lua-ls annotations for lsp
+USAGE:
+nvim -l scripts/gen_lsp.lua gen # this will overwrite runtime/lua/vim/lsp/_meta/protocol.lua
+nvim -l scripts/gen_lsp.lua gen --version 3.18 --build/new_lsp_types.lua
+nvim -l scripts/gen_lsp.lua gen --version 3.18 --out runtime/lua/vim/lsp/_meta/protocol.lua
+nvim -l scripts/gen_lsp.lua gen --version 3.18 --methods
+--]]
+
+local M = {}
+
+local function tofile(fname, text)
+ local f = io.open(fname, 'w')
+ if not f then
+ error(('failed to write: %s'):format(f))
+ else
+ f:write(text)
+ f:close()
+ end
+end
+
+local function read_json(opt)
+ local uri = 'https://raw.githubusercontent.com/microsoft/language-server-protocol/gh-pages/_specifications/lsp/'
+ .. opt.version
+ .. '/metaModel/metaModel.json'
+
+ local res = vim.system({ 'curl', '--no-progress-meter', uri, '-o', '-' }):wait()
+ if res.code ~= 0 or (res.stdout or ''):len() < 999 then
+ print(('URL failed: %s'):format(uri))
+ vim.print(res)
+ error(res.stdout)
+ end
+ return vim.json.decode(res.stdout)
+end
+
+-- Gets the Lua symbol for a given fully-qualified LSP method name.
+local function name(s)
+ -- "$/" prefix is special: https://microsoft.github.io/language-server-protocol/specification/#dollarRequests
+ return s:gsub('^%$', 'dollar'):gsub('/', '_')
+end
+
+local function gen_methods(protocol)
+ local output = {
+ '-- Generated by gen_lsp.lua, keep at end of file.',
+ '--- LSP method names.',
+ '---',
+ '---@see https://microsoft.github.io/language-server-protocol/specification/#metaModel',
+ 'protocol.Methods = {',
+ }
+ local indent = (' '):rep(2)
+
+ local all = vim.list_extend(protocol.requests, protocol.notifications)
+ table.sort(all, function(a, b)
+ return name(a.method) < name(b.method)
+ end)
+ for _, item in ipairs(all) do
+ if item.method then
+ if item.documentation then
+ local document = vim.split(item.documentation, '\n?\n', { trimempty = true })
+ for _, docstring in ipairs(document) do
+ output[#output + 1] = indent .. '--- ' .. docstring
+ end
+ end
+ output[#output + 1] = ("%s%s = '%s',"):format(indent, name(item.method), item.method)
+ end
+ end
+ output[#output + 1] = '}'
+ output = vim.list_extend(
+ output,
+ vim.split(
+ [[
+local function freeze(t)
+ return setmetatable({}, {
+ __index = t,
+ __newindex = function()
+ error('cannot modify immutable table')
+ end,
+ })
+end
+protocol.Methods = freeze(protocol.Methods)
+
+return protocol
+]],
+ '\n',
+ { trimempty = true }
+ )
+ )
+
+ local fname = './runtime/lua/vim/lsp/protocol.lua'
+ local bufnr = vim.fn.bufadd(fname)
+ vim.fn.bufload(bufnr)
+ vim.api.nvim_set_current_buf(bufnr)
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local index = vim.iter(ipairs(lines)):find(function(key, item)
+ return vim.startswith(item, '-- Generated by') and key or nil
+ end)
+ index = index and index - 1 or vim.api.nvim_buf_line_count(bufnr) - 1
+ vim.api.nvim_buf_set_lines(bufnr, index, -1, true, output)
+ vim.cmd.write()
+end
+
+function M.gen(opt)
+ local protocol = read_json(opt)
+
+ if opt.methods then
+ gen_methods(protocol)
+ end
+
+ local output = {
+ '--[[',
+ 'This file is autogenerated from scripts/gen_lsp.lua',
+ 'Regenerate:',
+ [=[nvim -l scripts/gen_lsp.lua gen --version 3.18 --runtime/lua/vim/lsp/_meta/protocol.lua]=],
+ '--]]',
+ '',
+ '---@alias lsp.null nil',
+ '---@alias uinteger integer',
+ '---@alias lsp.decimal number',
+ '---@alias lsp.DocumentUri string',
+ '---@alias lsp.URI string',
+ '---@alias lsp.LSPObject table<string, lsp.LSPAny>',
+ '---@alias lsp.LSPArray lsp.LSPAny[]',
+ '---@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil',
+ '',
+ }
+
+ local anonymous_num = 0
+
+ local anonym_classes = {}
+
+ local simple_types = {
+ 'string',
+ 'boolean',
+ 'integer',
+ 'uinteger',
+ 'decimal',
+ }
+
+ local function parse_type(type)
+ if type.kind == 'reference' or type.kind == 'base' then
+ if vim.tbl_contains(simple_types, type.name) then
+ return type.name
+ end
+ return 'lsp.' .. type.name
+ elseif type.kind == 'array' then
+ return parse_type(type.element) .. '[]'
+ elseif type.kind == 'or' then
+ local val = ''
+ for _, item in ipairs(type.items) do
+ val = val .. parse_type(item) .. '|'
+ end
+ val = val:sub(0, -2)
+ return val
+ elseif type.kind == 'stringLiteral' then
+ return '"' .. type.value .. '"'
+ elseif type.kind == 'map' then
+ return 'table<' .. parse_type(type.key) .. ', ' .. parse_type(type.value) .. '>'
+ elseif type.kind == 'literal' then
+ -- can I use ---@param disabled? {reason: string}
+ -- use | to continue the inline class to be able to add docs
+ -- https://github.com/LuaLS/lua-language-server/issues/2128
+ anonymous_num = anonymous_num + 1
+ local anonym = { '---@class anonym' .. anonymous_num }
+ for _, field in ipairs(type.value.properties) do
+ if field.documentation then
+ field.documentation = field.documentation:gsub('\n', '\n---')
+ anonym[#anonym + 1] = '---' .. field.documentation
+ end
+ anonym[#anonym + 1] = '---@field '
+ .. field.name
+ .. (field.optional and '?' or '')
+ .. ' '
+ .. parse_type(field.type)
+ end
+ anonym[#anonym + 1] = ''
+ for _, line in ipairs(anonym) do
+ anonym_classes[#anonym_classes + 1] = line
+ end
+ return 'anonym' .. anonymous_num
+ elseif type.kind == 'tuple' then
+ local tuple = '{ '
+ for i, value in ipairs(type.items) do
+ tuple = tuple .. '[' .. i .. ']: ' .. parse_type(value) .. ', '
+ end
+ -- remove , at the end
+ tuple = tuple:sub(0, -3)
+ return tuple .. ' }'
+ end
+ vim.print(type)
+ return ''
+ end
+
+ for _, structure in ipairs(protocol.structures) do
+ if structure.documentation then
+ structure.documentation = structure.documentation:gsub('\n', '\n---')
+ output[#output + 1] = '---' .. structure.documentation
+ end
+ if structure.extends then
+ local class_string = '---@class lsp.'
+ .. structure.name
+ .. ': '
+ .. parse_type(structure.extends[1])
+ for _, mixin in ipairs(structure.mixins or {}) do
+ class_string = class_string .. ', ' .. parse_type(mixin)
+ end
+ output[#output + 1] = class_string
+ else
+ output[#output + 1] = '---@class lsp.' .. structure.name
+ end
+ for _, field in ipairs(structure.properties or {}) do
+ if field.documentation then
+ field.documentation = field.documentation:gsub('\n', '\n---')
+ output[#output + 1] = '---' .. field.documentation
+ end
+ output[#output + 1] = '---@field '
+ .. field.name
+ .. (field.optional and '?' or '')
+ .. ' '
+ .. parse_type(field.type)
+ end
+ output[#output + 1] = ''
+ end
+
+ for _, enum in ipairs(protocol.enumerations) do
+ if enum.documentation then
+ enum.documentation = enum.documentation:gsub('\n', '\n---')
+ output[#output + 1] = '---' .. enum.documentation
+ end
+ local enum_type = '---@alias lsp.' .. enum.name
+ for _, value in ipairs(enum.values) do
+ enum_type = enum_type
+ .. '\n---| '
+ .. (type(value.value) == 'string' and '"' .. value.value .. '"' or value.value)
+ .. ' # '
+ .. value.name
+ end
+ output[#output + 1] = enum_type
+ output[#output + 1] = ''
+ end
+
+ for _, alias in ipairs(protocol.typeAliases) do
+ if alias.documentation then
+ alias.documentation = alias.documentation:gsub('\n', '\n---')
+ output[#output + 1] = '---' .. alias.documentation
+ end
+ if alias.type.kind == 'or' then
+ local alias_type = '---@alias lsp.' .. alias.name .. ' '
+ for _, item in ipairs(alias.type.items) do
+ alias_type = alias_type .. parse_type(item) .. '|'
+ end
+ alias_type = alias_type:sub(0, -2)
+ output[#output + 1] = alias_type
+ else
+ output[#output + 1] = '---@alias lsp.' .. alias.name .. ' ' .. parse_type(alias.type)
+ end
+ output[#output + 1] = ''
+ end
+
+ for _, line in ipairs(anonym_classes) do
+ output[#output + 1] = line
+ end
+
+ tofile(opt.output_file, table.concat(output, '\n'))
+end
+
+local opt = {
+ output_file = 'runtime/lua/vim/lsp/_meta/protocol.lua',
+ version = nil,
+ methods = nil,
+}
+
+for i = 1, #_G.arg do
+ if _G.arg[i] == '--out' then
+ opt.output_file = _G.arg[i + 1]
+ elseif _G.arg[i] == '--version' then
+ opt.version = _G.arg[i + 1]
+ elseif _G.arg[i] == '--methods' then
+ opt.methods = true
+ elseif vim.startswith(_G.arg[i], '--') then
+ opt.output_file = _G.arg[i]:sub(3)
+ end
+end
+
+for _, a in ipairs(arg) do
+ if M[a] then
+ M[a](opt)
+ end
+end
+
+return M
diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py
index 8e1d6ef80a..8ed88cb8f5 100755
--- a/scripts/gen_vimdoc.py
+++ b/scripts/gen_vimdoc.py
@@ -2,7 +2,7 @@
"""Generates Nvim :help docs from C/Lua docstrings, using Doxygen.
Also generates *.mpack files. To inspect the *.mpack structure:
- :new | put=v:lua.vim.inspect(v:lua.vim.mpack.unpack(readfile('runtime/doc/api.mpack','B')))
+ :new | put=v:lua.vim.inspect(v:lua.vim.mpack.decode(readfile('runtime/doc/api.mpack','B')))
Flow:
main
@@ -42,8 +42,12 @@ import subprocess
import collections
import msgpack
import logging
+from typing import Tuple
+from pathlib import Path
from xml.dom import minidom
+Element = minidom.Element
+Document = minidom.Document
MIN_PYTHON_VERSION = (3, 6)
MIN_DOXYGEN_VERSION = (1, 9, 0)
@@ -55,18 +59,28 @@ if sys.version_info < MIN_PYTHON_VERSION:
doxygen_version = tuple((int(i) for i in subprocess.check_output(["doxygen", "-v"],
universal_newlines=True).split()[0].split('.')))
-# Until 0.9 is released, need this hacky way to check that "nvim -l foo.lua" works.
-nvim_version = list(line for line in subprocess.check_output(['nvim', '-h'], universal_newlines=True).split('\n')
- if '-l ' in line)
-
if doxygen_version < MIN_DOXYGEN_VERSION:
print("\nRequires doxygen {}.{}.{}+".format(*MIN_DOXYGEN_VERSION))
print("Your doxygen version is {}.{}.{}\n".format(*doxygen_version))
sys.exit(1)
-if len(nvim_version) == 0:
- print("\nRequires 'nvim -l' feature, see https://github.com/neovim/neovim/pull/18706")
- sys.exit(1)
+
+# Need a `nvim` that supports `-l`, try the local build
+nvim_path = Path(__file__).parent / "../build/bin/nvim"
+if nvim_path.exists():
+ nvim = str(nvim_path)
+else:
+ # Until 0.9 is released, use this hacky way to check that "nvim -l foo.lua" works.
+ nvim_out = subprocess.check_output(['nvim', '-h'], universal_newlines=True)
+ nvim_version = [line for line in nvim_out.split('\n')
+ if '-l ' in line]
+ if len(nvim_version) == 0:
+ print((
+ "\nYou need to have a local Neovim build or a `nvim` version 0.9 for `-l` "
+ "support to build the documentation."))
+ sys.exit(1)
+ nvim = 'nvim'
+
# DEBUG = ('DEBUG' in os.environ)
INCLUDE_C_DECL = ('INCLUDE_C_DECL' in os.environ)
@@ -122,7 +136,7 @@ CONFIG = {
# Section helptag.
'helptag_fmt': lambda name: f'*api-{name.lower()}*',
# Per-function helptag.
- 'fn_helptag_fmt': lambda fstem, name: f'*{name}()*',
+ 'fn_helptag_fmt': lambda fstem, name, istbl: f'*{name}()*',
# Module name overrides (for Lua).
'module_override': {},
# Append the docs for these modules, do not start a new section.
@@ -132,55 +146,111 @@ CONFIG = {
'mode': 'lua',
'filename': 'lua.txt',
'section_order': [
+ 'highlight.lua',
+ 'regex.lua',
+ 'diff.lua',
+ 'mpack.lua',
+ 'json.lua',
+ 'base64.lua',
+ 'spell.lua',
+ 'builtin.lua',
+ '_options.lua',
'_editor.lua',
'_inspector.lua',
'shared.lua',
+ 'loader.lua',
'uri.lua',
'ui.lua',
'filetype.lua',
'keymap.lua',
'fs.lua',
'secure.lua',
+ 'version.lua',
+ 'iter.lua',
+ 'snippet.lua',
+ 'text.lua',
],
'files': [
+ 'runtime/lua/vim/iter.lua',
'runtime/lua/vim/_editor.lua',
+ 'runtime/lua/vim/_options.lua',
'runtime/lua/vim/shared.lua',
+ 'runtime/lua/vim/loader.lua',
'runtime/lua/vim/uri.lua',
'runtime/lua/vim/ui.lua',
'runtime/lua/vim/filetype.lua',
'runtime/lua/vim/keymap.lua',
'runtime/lua/vim/fs.lua',
+ 'runtime/lua/vim/highlight.lua',
'runtime/lua/vim/secure.lua',
+ 'runtime/lua/vim/version.lua',
'runtime/lua/vim/_inspector.lua',
+ 'runtime/lua/vim/snippet.lua',
+ 'runtime/lua/vim/text.lua',
+ 'runtime/lua/vim/_meta/builtin.lua',
+ 'runtime/lua/vim/_meta/diff.lua',
+ 'runtime/lua/vim/_meta/mpack.lua',
+ 'runtime/lua/vim/_meta/json.lua',
+ 'runtime/lua/vim/_meta/base64.lua',
+ 'runtime/lua/vim/_meta/regex.lua',
+ 'runtime/lua/vim/_meta/spell.lua',
],
'file_patterns': '*.lua',
'fn_name_prefix': '',
+ 'fn_name_fmt': lambda fstem, name: (
+ name if fstem in [ 'vim.iter' ] else
+ f'vim.{name}' if fstem in [ '_editor', 'vim.regex'] else
+ f'vim.{name}' if fstem == '_options' and not name[0].isupper() else
+ f'{fstem}.{name}' if fstem.startswith('vim') else
+ name
+ ),
'section_name': {
'lsp.lua': 'core',
'_inspector.lua': 'inspector',
},
'section_fmt': lambda name: (
- 'Lua module: vim'
- if name.lower() == '_editor'
- else f'Lua module: {name.lower()}'),
+ 'Lua module: vim' if name.lower() == '_editor' else
+ 'LUA-VIMSCRIPT BRIDGE' if name.lower() == '_options' else
+ f'VIM.{name.upper()}' if name.lower() in [ 'highlight', 'mpack', 'json', 'base64', 'diff', 'spell', 'regex' ] else
+ 'VIM' if name.lower() == 'builtin' else
+ f'Lua module: vim.{name.lower()}'),
'helptag_fmt': lambda name: (
- '*lua-vim*'
- if name.lower() == '_editor'
- else f'*lua-{name.lower()}*'),
- 'fn_helptag_fmt': lambda fstem, name: (
- f'*vim.{name}()*'
- if fstem.lower() == '_editor'
- else f'*{fstem}.{name}()*'),
+ '*lua-vim*' if name.lower() == '_editor' else
+ '*lua-vimscript*' if name.lower() == '_options' else
+ f'*vim.{name.lower()}*'),
+ 'fn_helptag_fmt': lambda fstem, name, istbl: (
+ f'*vim.opt:{name.split(":")[-1]}()*' if ':' in name and name.startswith('Option') else
+ # Exclude fstem for methods
+ f'*{name}()*' if ':' in name else
+ f'*vim.{name}()*' if fstem.lower() == '_editor' else
+ f'*vim.{name}*' if fstem.lower() == '_options' and istbl else
+ # Prevents vim.regex.regex
+ f'*{fstem}()*' if fstem.endswith('.' + name) else
+ f'*{fstem}.{name}{"" if istbl else "()"}*'
+ ),
'module_override': {
# `shared` functions are exposed on the `vim` module.
'shared': 'vim',
'_inspector': 'vim',
'uri': 'vim',
'ui': 'vim.ui',
+ 'loader': 'vim.loader',
'filetype': 'vim.filetype',
'keymap': 'vim.keymap',
'fs': 'vim.fs',
+ 'highlight': 'vim.highlight',
'secure': 'vim.secure',
+ 'version': 'vim.version',
+ 'iter': 'vim.iter',
+ 'diff': 'vim',
+ 'builtin': 'vim',
+ 'mpack': 'vim.mpack',
+ 'json': 'vim.json',
+ 'base64': 'vim.base64',
+ 'regex': 'vim.regex',
+ 'spell': 'vim.spell',
+ 'snippet': 'vim.snippet',
+ 'text': 'vim.text',
},
'append_only': [
'shared.lua',
@@ -194,13 +264,13 @@ CONFIG = {
'buf.lua',
'diagnostic.lua',
'codelens.lua',
+ 'inlay_hint.lua',
'tagfunc.lua',
'semantic_tokens.lua',
'handlers.lua',
'util.lua',
'log.lua',
'rpc.lua',
- 'sync.lua',
'protocol.lua',
],
'files': [
@@ -218,14 +288,11 @@ CONFIG = {
'*lsp-core*'
if name.lower() == 'lsp'
else f'*lsp-{name.lower()}*'),
- 'fn_helptag_fmt': lambda fstem, name: (
- f'*vim.lsp.{name}()*'
- if fstem == 'lsp' and name != 'client'
- else (
- '*vim.lsp.client*'
- # HACK. TODO(justinmk): class/structure support in lua2dox
- if 'lsp.client' == f'{fstem}.{name}'
- else f'*vim.lsp.{fstem}.{name}()*')),
+ 'fn_helptag_fmt': lambda fstem, name, istbl: (
+ f'*vim.lsp.{name}{"" if istbl else "()"}*' if fstem == 'lsp' and name != 'client' else
+ # HACK. TODO(justinmk): class/structure support in lua2dox
+ '*vim.lsp.client*' if 'lsp.client' == f'{fstem}.{name}' else
+ f'*vim.lsp.{fstem}.{name}{"" if istbl else "()"}*'),
'module_override': {},
'append_only': [],
},
@@ -238,10 +305,11 @@ CONFIG = {
'files': ['runtime/lua/vim/diagnostic.lua'],
'file_patterns': '*.lua',
'fn_name_prefix': '',
+ 'include_tables': False,
'section_name': {'diagnostic.lua': 'diagnostic'},
'section_fmt': lambda _: 'Lua module: vim.diagnostic',
'helptag_fmt': lambda _: '*diagnostic-api*',
- 'fn_helptag_fmt': lambda fstem, name: f'*vim.{fstem}.{name}()*',
+ 'fn_helptag_fmt': lambda fstem, name, istbl: f'*vim.{fstem}.{name}{"" if istbl else "()"}*',
'module_override': {},
'append_only': [],
},
@@ -254,6 +322,7 @@ CONFIG = {
'query.lua',
'highlighter.lua',
'languagetree.lua',
+ 'dev.lua',
],
'files': [
'runtime/lua/vim/treesitter.lua',
@@ -270,7 +339,7 @@ CONFIG = {
'*lua-treesitter-core*'
if name.lower() == 'treesitter'
else f'*lua-treesitter-{name.lower()}*'),
- 'fn_helptag_fmt': lambda fstem, name: (
+ 'fn_helptag_fmt': lambda fstem, name, istbl: (
f'*vim.{fstem}.{name}()*'
if fstem == 'treesitter'
else f'*{name}()*'
@@ -288,12 +357,37 @@ param_exclude = (
# Annotations are displayed as line items after API function descriptions.
annotation_map = {
'FUNC_API_FAST': '|api-fast|',
- 'FUNC_API_CHECK_TEXTLOCK': 'not allowed when |textlock| is active',
+ 'FUNC_API_TEXTLOCK': 'not allowed when |textlock| is active or in the |cmdwin|',
+ 'FUNC_API_TEXTLOCK_ALLOW_CMDWIN': 'not allowed when |textlock| is active',
'FUNC_API_REMOTE_ONLY': '|RPC| only',
'FUNC_API_LUA_ONLY': 'Lua |vim.api| only',
}
+def nvim_api_info() -> Tuple[int, bool]:
+ """Returns NVIM_API_LEVEL, NVIM_API_PRERELEASE from CMakeLists.txt"""
+ if not hasattr(nvim_api_info, 'LEVEL'):
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ cmake_file_path = os.path.join(script_dir, '..', 'CMakeLists.txt')
+ with open(cmake_file_path, 'r') as cmake_file:
+ cmake_content = cmake_file.read()
+
+ api_level_match = re.search(r'set\(NVIM_API_LEVEL (\d+)\)', cmake_content)
+ api_prerelease_match = re.search(
+ r'set\(NVIM_API_PRERELEASE (\w+)\)', cmake_content
+ )
+
+ if not api_level_match or not api_prerelease_match:
+ raise RuntimeError(
+ 'Could not find NVIM_API_LEVEL or NVIM_API_PRERELEASE in CMakeLists.txt'
+ )
+
+ nvim_api_info.LEVEL = int(api_level_match.group(1))
+ nvim_api_info.PRERELEASE = api_prerelease_match.group(1).lower() == 'true'
+
+ return nvim_api_info.LEVEL, nvim_api_info.PRERELEASE
+
+
# Raises an error with details about `o`, if `cond` is in object `o`,
# or if `cond()` is callable and returns True.
def debug_this(o, cond=True):
@@ -381,7 +475,7 @@ def is_blank(text):
return '' == clean_lines(text)
-def get_text(n, preformatted=False):
+def get_text(n):
"""Recursively concatenates all text in a node tree."""
text = ''
if n.nodeType == n.TEXT_NODE:
@@ -390,11 +484,13 @@ def get_text(n, preformatted=False):
for node in n.childNodes:
text += get_text(node)
return '`{}`'.format(text)
+ if n.nodeName == 'sp': # space, used in "programlisting" nodes
+ return ' '
for node in n.childNodes:
if node.nodeType == node.TEXT_NODE:
text += node.data
elif node.nodeType == node.ELEMENT_NODE:
- text += get_text(node, preformatted)
+ text += get_text(node)
return text
@@ -512,17 +608,26 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation,
# text += (int(not space_preceding) * ' ')
if n.nodeName == 'preformatted':
- o = get_text(n, preformatted=True)
+ o = get_text(n)
ensure_nl = '' if o[-1] == '\n' else '\n'
if o[0:4] == 'lua\n':
text += '>lua{}{}\n<'.format(ensure_nl, o[3:-1])
elif o[0:4] == 'vim\n':
text += '>vim{}{}\n<'.format(ensure_nl, o[3:-1])
+ elif o[0:5] == 'help\n':
+ text += o[4:-1]
else:
text += '>{}{}\n<'.format(ensure_nl, o)
-
+ elif n.nodeName == 'programlisting': # codeblock (```)
+ o = get_text(n)
+ text += '>'
+ if 'filename' in n.attributes:
+ filename = n.attributes['filename'].value
+ text += filename.lstrip('.')
+
+ text += '\n{}\n<'.format(textwrap.indent(o, ' ' * 4))
elif is_inline(n):
- text = doc_wrap(get_text(n), indent=indent, width=width)
+ text = doc_wrap(get_text(n), prefix=prefix, indent=indent, width=width)
elif n.nodeName == 'verbatim':
# TODO: currently we don't use this. The "[verbatim]" hint is there as
# a reminder that we must decide how to format this if we do use it.
@@ -535,19 +640,19 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation,
indent=indent + (' ' * len(prefix)),
width=width
)
-
if is_blank(result):
continue
-
text += indent + prefix + result
elif n.nodeName in ('para', 'heading'):
+ did_prefix = False
for c in n.childNodes:
if (is_inline(c)
and '' != get_text(c).strip()
and text
and ' ' != text[-1]):
text += ' '
- text += render_node(c, text, indent=indent, width=width)
+ text += render_node(c, text, prefix=(prefix if not did_prefix else ''), indent=indent, width=width)
+ did_prefix = True
elif n.nodeName == 'itemizedlist':
for c in n.childNodes:
text += '{}\n'.format(render_node(c, text, prefix='• ',
@@ -562,17 +667,26 @@ def render_node(n, text, prefix='', indent='', width=text_width - indentation,
indent=indent, width=width))
i = i + 1
elif n.nodeName == 'simplesect' and 'note' == n.getAttribute('kind'):
- text += '\nNote:\n '
+ text += ind(' ')
for c in n.childNodes:
- text += render_node(c, text, indent=' ', width=width)
- text += '\n'
+ if is_blank(render_node(c, text, prefix='• ', indent=' ', width=width)):
+ continue
+ text += render_node(c, text, prefix='• ', indent=' ', width=width)
+ # text += '\n'
elif n.nodeName == 'simplesect' and 'warning' == n.getAttribute('kind'):
text += 'Warning:\n '
for c in n.childNodes:
text += render_node(c, text, indent=' ', width=width)
text += '\n'
- elif (n.nodeName == 'simplesect'
- and n.getAttribute('kind') in ('return', 'see')):
+ elif n.nodeName == 'simplesect' and 'see' == n.getAttribute('kind'):
+ text += ind(' ')
+ # Example:
+ # <simplesect kind="see">
+ # <para>|autocommand|</para>
+ # </simplesect>
+ for c in n.childNodes:
+ text += render_node(c, text, prefix='• ', indent=' ', width=width)
+ elif n.nodeName == 'simplesect' and 'return' == n.getAttribute('kind'):
text += ind(' ')
for c in n.childNodes:
text += render_node(c, text, indent=' ', width=width)
@@ -590,6 +704,7 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
Keys:
'text': Text from this <para> element
+ 'note': List of @note strings
'params': <parameterlist> map
'return': List of @return strings
'seealso': List of @see strings
@@ -597,14 +712,17 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
"""
chunks = {
'text': '',
+ 'note': [],
'params': collections.OrderedDict(),
'return': [],
'seealso': [],
+ 'prerelease': False,
'xrefs': []
}
# Ordered dict of ordered lists.
groups = collections.OrderedDict([
+ ('note', []),
('params', []),
('return', []),
('seealso', []),
@@ -615,7 +733,6 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
# nodes to appear together.
text = ''
kind = ''
- last = ''
if is_inline(parent):
# Flatten inline text from a tree of non-block nodes.
text = doc_wrap(render_node(parent, "", fmt_vimhelp=fmt_vimhelp),
@@ -628,15 +745,24 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
elif child.nodeName == 'xrefsect':
groups['xrefs'].append(child)
elif child.nodeName == 'simplesect':
- last = kind
kind = child.getAttribute('kind')
- if kind == 'return' or (kind == 'note' and last == 'return'):
+ if kind == 'note':
+ groups['note'].append(child)
+ elif kind == 'return':
groups['return'].append(child)
elif kind == 'see':
groups['seealso'].append(child)
- elif kind in ('note', 'warning'):
+ elif kind == 'warning':
text += render_node(child, text, indent=indent,
width=width, fmt_vimhelp=fmt_vimhelp)
+ elif kind == 'since':
+ since_match = re.match(r'^(\d+)', get_text(child))
+ since = int(since_match.group(1)) if since_match else 0
+ NVIM_API_LEVEL, NVIM_API_PRERELEASE = nvim_api_info()
+ if since > NVIM_API_LEVEL or (
+ since == NVIM_API_LEVEL and NVIM_API_PRERELEASE
+ ):
+ chunks['prerelease'] = True
else:
raise RuntimeError('unhandled simplesect: {}\n{}'.format(
child.nodeName, child.toprettyxml(indent=' ', newl='\n')))
@@ -659,10 +785,17 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
if len(groups['params']) > 0:
for child in groups['params']:
update_params_map(child, ret_map=chunks['params'], width=width)
+ for child in groups['note']:
+ chunks['note'].append(render_node(
+ child, '', indent=indent, width=width, fmt_vimhelp=fmt_vimhelp).rstrip())
for child in groups['return']:
chunks['return'].append(render_node(
child, '', indent=indent, width=width, fmt_vimhelp=fmt_vimhelp))
for child in groups['seealso']:
+ # Example:
+ # <simplesect kind="see">
+ # <para>|autocommand|</para>
+ # </simplesect>
chunks['seealso'].append(render_node(
child, '', indent=indent, width=width, fmt_vimhelp=fmt_vimhelp))
@@ -678,8 +811,28 @@ def para_as_map(parent, indent='', width=text_width - indentation, fmt_vimhelp=F
return chunks, xrefs
+def is_program_listing(para):
+ """
+ Return True if `para` contains a "programlisting" (i.e. a Markdown code
+ block ```).
+
+ Sometimes a <para> element will have only a single "programlisting" child
+ node, but othertimes it will have extra whitespace around the
+ "programlisting" node.
-def fmt_node_as_vimhelp(parent, width=text_width - indentation, indent='',
+ @param para XML <para> node
+ @return True if <para> is a programlisting
+ """
+
+ # Remove any child text nodes that are only whitespace
+ children = [
+ n for n in para.childNodes
+ if n.nodeType != n.TEXT_NODE or n.data.strip() != ''
+ ]
+
+ return len(children) == 1 and children[0].nodeName == 'programlisting'
+
+def fmt_node_as_vimhelp(parent: Element, width=text_width - indentation, indent='',
fmt_vimhelp=False):
"""Renders (nested) Doxygen <para> nodes as Vim :help text.
@@ -692,6 +845,8 @@ def fmt_node_as_vimhelp(parent, width=text_width - indentation, indent='',
max_name_len = max_name(m.keys()) + 4
out = ''
for name, desc in m.items():
+ if name == 'self':
+ continue
name = ' • {}'.format('{{{}}}'.format(name).ljust(max_name_len))
out += '{}{}\n'.format(name, desc)
return out.rstrip()
@@ -705,13 +860,28 @@ def fmt_node_as_vimhelp(parent, width=text_width - indentation, indent='',
for child in parent.childNodes:
para, _ = para_as_map(child, indent, width, fmt_vimhelp)
+ # 'programlisting' blocks are Markdown code blocks. Do not include
+ # these as a separate paragraph, but append to the last non-empty line
+ # in the text
+ if is_program_listing(child):
+ while rendered_blocks and rendered_blocks[-1] == '':
+ rendered_blocks.pop()
+ rendered_blocks[-1] += ' ' + para['text']
+ continue
+
# Generate text from the gathered items.
chunks = [para['text']]
+ notes = [" This API is pre-release (unstable)."] if para['prerelease'] else []
+ notes += para['note']
+ if len(notes) > 0:
+ chunks.append('\nNote: ~')
+ for s in notes:
+ chunks.append(s)
if len(para['params']) > 0 and has_nonexcluded_params(para['params']):
chunks.append('\nParameters: ~')
chunks.append(fmt_param_doc(para['params']))
if len(para['return']) > 0:
- chunks.append('\nReturn: ~')
+ chunks.append('\nReturn (multiple): ~' if len(para['return']) > 1 else '\nReturn: ~')
for s in para['return']:
chunks.append(s)
if len(para['seealso']) > 0:
@@ -757,6 +927,13 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
if return_type == '':
continue
+ if 'local_function' in return_type: # Special from lua2dox.lua.
+ continue
+
+ istbl = return_type.startswith('table') # Special from lua2dox.lua.
+ if istbl and not CONFIG[target].get('include_tables', True):
+ continue
+
if return_type.startswith(('ArrayOf', 'DictionaryOf')):
parts = return_type.strip('_').split('_')
return_type = '{}({})'.format(parts[0], ', '.join(parts[1:]))
@@ -805,6 +982,7 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
and any(x[1] == 'self' for x in params):
split_return = return_type.split(' ')
name = f'{split_return[1]}:{name}'
+ params = [x for x in params if x[1] != 'self']
c_args = []
for param_type, param_name in params:
@@ -818,12 +996,20 @@ def extract_from_xml(filename, target, width, fmt_vimhelp):
if '.' in compoundname:
fstem = compoundname.split('.')[0]
fstem = CONFIG[target]['module_override'].get(fstem, fstem)
- vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name)
+ vimtag = CONFIG[target]['fn_helptag_fmt'](fstem, name, istbl)
+
+ if 'fn_name_fmt' in CONFIG[target]:
+ name = CONFIG[target]['fn_name_fmt'](fstem, name)
+
+ if istbl:
+ aopen, aclose = '', ''
+ else:
+ aopen, aclose = '(', ')'
- prefix = '%s(' % name
- suffix = '%s)' % ', '.join('{%s}' % a[1] for a in params
- if a[0] not in ('void', 'Error', 'Arena',
- 'lua_State'))
+ prefix = name + aopen
+ suffix = ', '.join('{%s}' % a[1] for a in params
+ if a[0] not in ('void', 'Error', 'Arena',
+ 'lua_State')) + aclose
if not fmt_vimhelp:
c_decl = '%s %s(%s);' % (return_type, name, ', '.join(c_args))
@@ -1001,6 +1187,42 @@ def delete_lines_below(filename, tokenstr):
fp.writelines(lines[0:i])
+def extract_defgroups(base: str, dom: Document):
+ '''Generate module-level (section) docs (@defgroup).'''
+ section_docs = {}
+
+ for compound in dom.getElementsByTagName('compound'):
+ if compound.getAttribute('kind') != 'group':
+ continue
+
+ # Doxygen "@defgroup" directive.
+ groupname = get_text(find_first(compound, 'name'))
+ groupxml = os.path.join(base, '%s.xml' %
+ compound.getAttribute('refid'))
+
+ group_parsed = minidom.parse(groupxml)
+ doc_list = []
+ brief_desc = find_first(group_parsed, 'briefdescription')
+ if brief_desc:
+ for child in brief_desc.childNodes:
+ doc_list.append(fmt_node_as_vimhelp(child))
+
+ desc = find_first(group_parsed, 'detaileddescription')
+ if desc:
+ doc = fmt_node_as_vimhelp(desc)
+
+ if doc:
+ doc_list.append(doc)
+
+ # Can't use '.' in @defgroup, so convert to '--'
+ # "vim.json" => "vim-dot-json"
+ groupname = groupname.replace('-dot-', '.')
+
+ section_docs[groupname] = "\n".join(doc_list)
+
+ return section_docs
+
+
def main(doxygen_config, args):
"""Generates:
@@ -1042,62 +1264,44 @@ def main(doxygen_config, args):
fn_map_full = {} # Collects all functions as each module is processed.
sections = {}
- intros = {}
sep = '=' * text_width
base = os.path.join(output_dir, 'xml')
dom = minidom.parse(os.path.join(base, 'index.xml'))
- # generate docs for section intros
- for compound in dom.getElementsByTagName('compound'):
- if compound.getAttribute('kind') != 'group':
- continue
-
- groupname = get_text(find_first(compound, 'name'))
- groupxml = os.path.join(base, '%s.xml' %
- compound.getAttribute('refid'))
-
- group_parsed = minidom.parse(groupxml)
- doc_list = []
- brief_desc = find_first(group_parsed, 'briefdescription')
- if brief_desc:
- for child in brief_desc.childNodes:
- doc_list.append(fmt_node_as_vimhelp(child))
-
- desc = find_first(group_parsed, 'detaileddescription')
- if desc:
- doc = fmt_node_as_vimhelp(desc)
-
- if doc:
- doc_list.append(doc)
-
- intros[groupname] = "\n".join(doc_list)
+ section_docs = extract_defgroups(base, dom)
+ # Generate docs for all functions in the current module.
for compound in dom.getElementsByTagName('compound'):
if compound.getAttribute('kind') != 'file':
continue
filename = get_text(find_first(compound, 'name'))
if filename.endswith('.c') or filename.endswith('.lua'):
- xmlfile = os.path.join(base,
- '{}.xml'.format(compound.getAttribute('refid')))
+ xmlfile = os.path.join(base, '{}.xml'.format(compound.getAttribute('refid')))
# Extract unformatted (*.mpack).
fn_map, _ = extract_from_xml(xmlfile, target, 9999, False)
# Extract formatted (:help).
functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp(
- os.path.join(base, '{}.xml'.format(
- compound.getAttribute('refid'))), target)
+ os.path.join(base, '{}.xml'.format(compound.getAttribute('refid'))), target)
if not functions_text and not deprecated_text:
continue
else:
- name = os.path.splitext(
- os.path.basename(filename))[0].lower()
+ filename = os.path.basename(filename)
+ name = os.path.splitext(filename)[0].lower()
sectname = name.upper() if name == 'ui' else name.title()
+ sectname = CONFIG[target]['section_name'].get(filename, sectname)
+ title = CONFIG[target]['section_fmt'](sectname)
+ section_tag = CONFIG[target]['helptag_fmt'](sectname)
+ # Module/Section id matched against @defgroup.
+ # "*api-buffer*" => "api-buffer"
+ section_id = section_tag.strip('*')
+
doc = ''
- intro = intros.get(f'api-{name}')
- if intro:
- doc += '\n\n' + intro
+ section_doc = section_docs.get(section_id)
+ if section_doc:
+ doc += '\n\n' + section_doc
if functions_text:
doc += '\n\n' + functions_text
@@ -1107,12 +1311,7 @@ def main(doxygen_config, args):
doc += deprecated_text
if doc:
- filename = os.path.basename(filename)
- sectname = CONFIG[target]['section_name'].get(
- filename, sectname)
- title = CONFIG[target]['section_fmt'](sectname)
- helptag = CONFIG[target]['helptag_fmt'](sectname)
- sections[filename] = (title, helptag, doc)
+ sections[filename] = (title, section_tag, doc)
fn_map_full.update(fn_map)
if len(sections) == 0:
@@ -1127,15 +1326,14 @@ def main(doxygen_config, args):
for filename in CONFIG[target]['section_order']:
try:
- title, helptag, section_doc = sections.pop(filename)
+ title, section_tag, section_doc = sections.pop(filename)
except KeyError:
msg(f'warning: empty docs, skipping (target={target}): {filename}')
msg(f' existing docs: {sections.keys()}')
continue
if filename not in CONFIG[target]['append_only']:
docs += sep
- docs += '\n%s%s' % (title,
- helptag.rjust(text_width - len(title)))
+ docs += '\n{}{}'.format(title, section_tag.rjust(text_width - len(title)))
docs += section_doc
docs += '\n\n\n'
@@ -1160,10 +1358,12 @@ def main(doxygen_config, args):
msg_report()
-def filter_source(filename):
+def filter_source(filename, keep_tmpfiles):
+ output_dir = out_dir.format(target='lua2dox')
name, extension = os.path.splitext(filename)
if extension == '.lua':
- p = subprocess.run(['nvim', '-l', lua2dox, filename], stdout=subprocess.PIPE)
+ args = [str(nvim), '-l', lua2dox, filename] + (['--outdir', output_dir] if keep_tmpfiles else [])
+ p = subprocess.run(args, stdout=subprocess.PIPE)
op = ('?' if 0 != p.returncode else p.stdout.decode('utf-8'))
print(op)
else:
@@ -1186,7 +1386,7 @@ def parse_args():
ap.add_argument('source_filter', nargs='*',
help="Filter source file(s)")
ap.add_argument('-k', '--keep-tmpfiles', action='store_true',
- help="Keep temporary files")
+ help="Keep temporary files (tmp-xx-doc/ directories, including tmp-lua2dox-doc/ for lua2dox.lua quasi-C output)")
ap.add_argument('-t', '--target',
help=f'One of ({targets}), defaults to "all"')
return ap.parse_args()
@@ -1234,8 +1434,13 @@ if __name__ == "__main__":
log.setLevel(args.log_level)
log.addHandler(logging.StreamHandler())
+ # When invoked as a filter, args won't be passed, so use an env var.
+ if args.keep_tmpfiles:
+ os.environ['NVIM_KEEP_TMPFILES'] = '1'
+ keep_tmpfiles = ('NVIM_KEEP_TMPFILES' in os.environ)
+
if len(args.source_filter) > 0:
- filter_source(args.source_filter[0])
+ filter_source(args.source_filter[0], keep_tmpfiles)
else:
main(Doxyfile, args)
diff --git a/scripts/genappimage.sh b/scripts/genappimage.sh
index 9944b5eb31..b0bf186f85 100755
--- a/scripts/genappimage.sh
+++ b/scripts/genappimage.sh
@@ -26,7 +26,7 @@ APP_DIR="$APP.AppDir"
########################################################################
# Build and install nvim into the AppImage
-make CMAKE_BUILD_TYPE=RelWithDebInfo CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX=${APP_DIR}/usr -DCMAKE_INSTALL_MANDIR=man"
+make CMAKE_BUILD_TYPE="${NVIM_BUILD_TYPE}" CMAKE_EXTRA_FLAGS="-DCMAKE_INSTALL_PREFIX=${APP_DIR}/usr -DCMAKE_INSTALL_MANDIR=man"
make install
########################################################################
diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua
index 87a8e62503..d2c8601c25 100644
--- a/scripts/lintcommit.lua
+++ b/scripts/lintcommit.lua
@@ -1,13 +1,14 @@
-- Usage:
-- # verbose
--- nvim -es +"lua require('scripts.lintcommit').main()"
+-- nvim -l scripts/lintcommit.lua main --trace
--
-- # silent
--- nvim -es +"lua require('scripts.lintcommit').main({trace=false})"
+-- nvim -l scripts/lintcommit.lua main
--
-- # self-test
--- nvim -es +"lua require('scripts.lintcommit')._test()"
+-- nvim -l scripts/lintcommit.lua _test
+--- @type table<string,fun(opt: LintcommitOptions)>
local M = {}
local _trace = false
@@ -19,11 +20,6 @@ local function p(s)
vim.cmd('set verbose=0')
end
-local function die()
- p('')
- vim.cmd("cquit 1")
-end
-
-- Executes and returns the output of `cmd`, or nil on failure.
--
-- Prints `cmd` if `trace` is enabled.
@@ -35,7 +31,7 @@ local function run(cmd, or_die)
if vim.v.shell_error ~= 0 then
if or_die then
p(rv)
- die()
+ os.exit(1)
end
return nil
end
@@ -51,7 +47,7 @@ local function validate_commit(commit_message)
return nil
end
- local commit_split = vim.split(commit_message, ":")
+ local commit_split = vim.split(commit_message, ":", {plain = true})
-- Return nil if the type is vim-patch since most of the normal rules don't
-- apply.
if commit_split[1] == "vim-patch" then
@@ -63,13 +59,22 @@ local function validate_commit(commit_message)
return [[Commit message is too long, a maximum of 80 characters is allowed.]]
end
+ local before_colon = commit_split[1]
- if vim.tbl_count(commit_split) < 2 then
+ local after_idx = 2
+ if before_colon:match('^[^%(]*%([^%)]*$') then
+ -- Need to find the end of commit scope when commit scope contains colons.
+ while after_idx <= vim.tbl_count(commit_split) do
+ after_idx = after_idx + 1
+ if commit_split[after_idx - 1]:find(')') then
+ break
+ end
+ end
+ end
+ if after_idx > vim.tbl_count(commit_split) then
return [[Commit message does not include colons.]]
end
-
- local before_colon = commit_split[1]
- local after_colon = commit_split[2]
+ local after_colon = commit_split[after_idx]
-- Check if commit introduces a breaking change.
if vim.endswith(before_colon, "!") then
@@ -77,18 +82,20 @@ local function validate_commit(commit_message)
end
-- Check if type is correct
- local type = vim.split(before_colon, "%(")[1]
- local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'dist', 'vim-patch'}
+ local type = vim.split(before_colon, "(", {plain = true})[1]
+ local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'vim-patch'}
if not vim.tbl_contains(allowed_types, type) then
return string.format(
- 'Invalid commit type "%s". Allowed types are:\n %s',
+ [[Invalid commit type "%s". Allowed types are:
+ %s.
+ If none of these seem appropriate then use "fix"]],
type,
vim.inspect(allowed_types))
end
-- Check if scope is appropriate
if before_colon:match("%(") then
- local scope = vim.trim(before_colon:match("%((.*)%)"))
+ local scope = vim.trim(commit_message:match("%((.-)%)"))
if scope == '' then
return [[Scope can't be empty]]
@@ -126,10 +133,10 @@ local function validate_commit(commit_message)
return [[There should only be one whitespace after the colon.]]
end
- -- Check that first character after space isn't uppercase.
- if string.match(after_colon:sub(2,2), '%u') then
- return [[First character should not be uppercase.]]
- end
+ -- Allow lowercase or ALL_UPPER but not Titlecase.
+ if after_colon:match('^ *%u%l') then
+ return [[Description first word should not be Capitalized.]]
+ end
-- Check that description isn't just whitespaces
if vim.trim(after_colon) == "" then
@@ -139,6 +146,7 @@ local function validate_commit(commit_message)
return nil
end
+--- @param opt? LintcommitOptions
function M.main(opt)
_trace = not opt or not not opt.trace
@@ -149,10 +157,11 @@ function M.main(opt)
ancestor = run({'git', 'merge-base', 'upstream/master', branch})
end
local commits_str = run({'git', 'rev-list', ancestor..'..'..branch}, true)
+ assert(commits_str)
- local commits = {}
+ local commits = {} --- @type string[]
for substring in commits_str:gmatch("%S+") do
- table.insert(commits, substring)
+ table.insert(commits, substring)
end
local failed = 0
@@ -164,13 +173,16 @@ function M.main(opt)
local invalid_msg = validate_commit(msg)
if invalid_msg then
failed = failed + 1
+
+ -- Some breathing room
+ if failed == 1 then
+ p('\n')
+ end
+
p(string.format([[
Invalid commit message: "%s"
Commit: %s
%s
- See also:
- https://github.com/neovim/neovim/blob/master/CONTRIBUTING.md#commit-messages
- https://www.conventionalcommits.org/en/v1.0.0/
]],
msg,
commit_id,
@@ -180,7 +192,12 @@ Invalid commit message: "%s"
end
if failed > 0 then
- die() -- Exit with error.
+ p([[
+See also:
+ https://github.com/neovim/neovim/blob/master/CONTRIBUTING.md#commit-messages
+
+]])
+ os.exit(1)
else
p('')
end
@@ -198,7 +215,6 @@ function M._test()
['refactor: normal message'] = true,
['revert: normal message'] = true,
['test: normal message'] = true,
- ['dist: normal message'] = true,
['ci(window): message with scope'] = true,
['ci!: message with breaking change'] = true,
['ci(tui)!: message with scope and breaking change'] = true,
@@ -223,8 +239,19 @@ function M._test()
['refactor(): empty scope'] = false,
['ci( ): whitespace as scope'] = false,
['ci: period at end of sentence.'] = false,
- ['ci: Starting sentence capitalized'] = false,
+ ['ci: Capitalized first word'] = false,
+ ['ci: UPPER_CASE First Word'] = true,
['unknown: using unknown type'] = false,
+ ['feat: foo:bar'] = true,
+ ['feat(something): foo:bar'] = true,
+ ['feat(:grep): read from pipe'] = true,
+ ['feat(:grep/:make): read from pipe'] = true,
+ ['feat(:grep): foo:bar'] = true,
+ ['feat(:grep/:make): foo:bar'] = true,
+ ['feat(:grep)'] = false,
+ ['feat(:grep/:make)'] = false,
+ ['feat(:grep'] = false,
+ ['feat(:grep/:make'] = false,
['ci: you\'re saying this commit message just goes on and on and on and on and on and on for way too long?'] = false,
}
@@ -238,9 +265,30 @@ function M._test()
end
if failed > 0 then
- die() -- Exit with error.
+ os.exit(1)
end
end
-return M
+--- @class LintcommitOptions
+--- @field trace? boolean
+local opt = {}
+
+for _, a in ipairs(arg) do
+ if vim.startswith(a, '--') then
+ local nm, val = a:sub(3), true
+ if vim.startswith(a, '--no') then
+ nm, val = a:sub(5), false
+ end
+
+ if nm == 'trace' then
+ opt.trace = val
+ end
+ end
+end
+
+for _, a in ipairs(arg) do
+ if M[a] then
+ M[a](opt)
+ end
+end
diff --git a/scripts/lua2dox.lua b/scripts/lua2dox.lua
index 19f8f8141d..1c8bc5a3cb 100644
--- a/scripts/lua2dox.lua
+++ b/scripts/lua2dox.lua
@@ -1,6 +1,6 @@
---[[--------------------------------------------------------------------------
--- Copyright (C) 2012 by Simon Dales --
--- simon@purrsoft.co.uk --
+-----------------------------------------------------------------------------
+-- Copyright (C) 2012 by Simon Dales --
+-- simon@purrsoft.co.uk --
-- --
-- This program is free software; you can redistribute it and/or modify --
-- it under the terms of the GNU General Public License as published by --
@@ -16,7 +16,7 @@
-- along with this program; if not, write to the --
-- Free Software Foundation, Inc., --
-- 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. --
-----------------------------------------------------------------------------]]
+-----------------------------------------------------------------------------
--[[!
Lua-to-Doxygen converter
@@ -24,11 +24,18 @@ Lua-to-Doxygen converter
Partially from lua2dox
http://search.cpan.org/~alec/Doxygen-Lua-0.02/lib/Doxygen/Lua.pm
-Running
+RUNNING
-------
This script "lua2dox.lua" gets called by "gen_vimdoc.py".
+DEBUGGING/DEVELOPING
+---------------------
+
+1. To debug, run gen_vimdoc.py with --keep-tmpfiles:
+ python3 scripts/gen_vimdoc.py -t treesitter --keep-tmpfiles
+2. The filtered result will be written to ./tmp-lua2dox-doc/….lua.c
+
Doxygen must be on your system. You can experiment like so:
- Run "doxygen -g" to create a default Doxyfile.
@@ -41,498 +48,395 @@ It only has to be good enough for doxygen to see it as legal.
One limitation is that each line is treated separately (except for long comments).
The implication is that class and function declarations must be on the same line.
-Some functions can have their parameter lists extended over multiple lines to make it look neat.
-Managing this where there are also some comments is a bit more coding than I want to do at this stage,
-so it will probably not document accurately if we do do this.
-However I have put in a hack that will insert the "missing" close paren.
+There is hack that will insert the "missing" close paren.
The effect is that you will get the function documented, but not with the parameter list you might expect.
]]
-local function class()
- local newClass = {} -- a new class newClass
- -- the class will be the metatable for all its newInstanceects,
- -- and they will look up their methods in it.
- newClass.__index = newClass
-
- -- expose a constructor which can be called by <classname>(<args>)
- setmetatable(newClass, {
- __call = function(class_tbl, ...)
- local newInstance = {}
- setmetatable(newInstance, newClass)
- --if init then
- -- init(newInstance,...)
- if class_tbl.init then
- class_tbl.init(newInstance, ...)
- end
- return newInstance
- end
- })
- return newClass
-end
+local TYPES = { 'integer', 'number', 'string', 'table', 'list', 'boolean', 'function' }
+
+local TAGGED_TYPES = { 'TSNode', 'LanguageTree' }
---! \brief write to stdout
-local function TCore_IO_write(Str)
- if Str then
- io.write(Str)
+-- Document these as 'table'
+local ALIAS_TYPES = {
+ 'Range', 'Range4', 'Range6', 'TSMetadata',
+ 'vim.filetype.add.filetypes',
+ 'vim.filetype.match.args'
+}
+
+local debug_outfile = nil --- @type string?
+local debug_output = {}
+
+--- write to stdout
+--- @param str? string
+local function write(str)
+ if not str then
+ return
end
-end
---! \brief write to stdout
-local function TCore_IO_writeln(Str)
- if Str then
- io.write(Str)
+ io.write(str)
+ if debug_outfile then
+ table.insert(debug_output, str)
end
- io.write('\n')
end
---! \brief trims a string
-local function string_trim(Str)
- return Str:match('^%s*(.-)%s*$')
+--- write to stdout
+--- @param str? string
+local function writeln(str)
+ write(str)
+ write('\n')
end
---! \brief split a string
---!
---! \param Str
---! \param Pattern
---! \returns table of string fragments
----@return string[]
-local function string_split(Str, Pattern)
- local splitStr = {}
- local fpat = '(.-)' .. Pattern
- local last_end = 1
- local str, e, cap = string.find(Str, fpat, 1)
- while str do
- if str ~= 1 or cap ~= '' then
- table.insert(splitStr, cap)
- end
- last_end = e + 1
- str, e, cap = string.find(Str, fpat, last_end)
- end
- if last_end <= #Str then
- cap = string.sub(Str, last_end)
- table.insert(splitStr, cap)
+--- an input file buffer
+--- @class StreamRead
+--- @field currentLine string?
+--- @field contentsLen integer
+--- @field currentLineNo integer
+--- @field filecontents string[]
+local StreamRead = {}
+
+--- @return StreamRead
+--- @param filename string
+function StreamRead.new(filename)
+ assert(filename, ('invalid file: %s'):format(filename))
+ -- get lines from file
+ -- syphon lines to our table
+ local filecontents = {} --- @type string[]
+ for line in io.lines(filename) do
+ filecontents[#filecontents+1] = line
end
- return splitStr
+
+ return setmetatable({
+ filecontents = filecontents,
+ contentsLen = #filecontents,
+ currentLineNo = 1,
+ }, { __index = StreamRead })
end
--------------------------------
---! \brief file buffer
---!
---! an input file buffer
-local TStream_Read = class()
-
---! \brief get contents of file
---!
---! \param Filename name of file to read (or nil == stdin)
-function TStream_Read.getContents(this, Filename)
- assert(Filename, ('invalid file: %s'):format(Filename))
- -- get lines from file
- -- syphon lines to our table
- local filecontents = {}
- for line in io.lines(Filename) do
- table.insert(filecontents, line)
+-- get a line
+function StreamRead:getLine()
+ if self.currentLine then
+ self.currentLine = nil
+ return self.currentLine
end
- if filecontents then
- this.filecontents = filecontents
- this.contentsLen = #filecontents
- this.currentLineNo = 1
+ -- get line
+ if self.currentLineNo <= self.contentsLen then
+ local line = self.filecontents[self.currentLineNo]
+ self.currentLineNo = self.currentLineNo + 1
+ return line
end
- return filecontents
+ return ''
end
---! \brief get lineno
-function TStream_Read.getLineNo(this)
- return this.currentLineNo
+-- save line fragment
+--- @param line_fragment string
+function StreamRead:ungetLine(line_fragment)
+ self.currentLine = line_fragment
end
---! \brief get a line
-function TStream_Read.getLine(this)
- local line
- if this.currentLine then
- line = this.currentLine
- this.currentLine = nil
- else
- -- get line
- if this.currentLineNo <= this.contentsLen then
- line = this.filecontents[this.currentLineNo]
- this.currentLineNo = this.currentLineNo + 1
- else
- line = ''
- end
- end
- return line
+-- is it eof?
+function StreamRead:eof()
+ return not self.currentLine and self.currentLineNo > self.contentsLen
end
---! \brief save line fragment
-function TStream_Read.ungetLine(this, LineFrag)
- this.currentLine = LineFrag
+-- input filter
+--- @class Lua2DoxFilter
+local Lua2DoxFilter = {}
+setmetatable(Lua2DoxFilter, { __index = Lua2DoxFilter })
+
+--- trim comment off end of string
+---
+--- @param line string
+--- @return string, string?
+local function removeCommentFromLine(line)
+ local pos_comment = line:find('%-%-')
+ if not pos_comment then
+ return line
+ end
+ return line:sub(1, pos_comment - 1), line:sub(pos_comment)
end
---! \brief is it eof?
-function TStream_Read.eof(this)
- if this.currentLine or this.currentLineNo <= this.contentsLen then
- return false
+--- Processes "@…" directives in a docstring line.
+---
+--- @param line string
+--- @param generics table<string,string>
+--- @return string?
+local function process_magic(line, generics)
+ line = line:gsub('^%s+@', '@')
+ line = line:gsub('@package', '@private')
+ line = line:gsub('@nodoc', '@private')
+
+ if not vim.startswith(line, '@') then -- it's a magic comment
+ return '/// ' .. line
end
- return true
-end
---! \brief output stream
-local TStream_Write = class()
+ local magic = line:sub(2)
+ local magic_split = vim.split(magic, ' ', { plain = true })
+ local directive = magic_split[1]
---! \brief constructor
-function TStream_Write.init(this)
- this.tailLine = {}
-end
+ if vim.list_contains({
+ 'cast', 'diagnostic', 'overload', 'meta', 'type'
+ }, directive) then
+ -- Ignore LSP directives
+ return '// gg:"' .. line .. '"'
+ end
---! \brief write immediately
-function TStream_Write.write(_, Str)
- TCore_IO_write(Str)
-end
+ if directive == 'defgroup' or directive == 'addtogroup' then
+ -- Can't use '.' in defgroup, so convert to '--'
+ return '/// @' .. magic:gsub('%.', '-dot-')
+ end
---! \brief write immediately
-function TStream_Write.writeln(_, Str)
- TCore_IO_writeln(Str)
-end
+ if directive == 'generic' then
+ local generic_name, generic_type = line:match('@generic%s*(%w+)%s*:?%s*(.*)')
+ if generic_type == '' then
+ generic_type = 'any'
+ end
+ generics[generic_name] = generic_type
+ return
+ end
---! \brief write immediately
-function TStream_Write.writelnComment(_, Str)
- TCore_IO_write('// ZZ: ')
- TCore_IO_writeln(Str)
-end
+ local type_index = 2
---! \brief write to tail
-function TStream_Write.writelnTail(this, Line)
- if not Line then
- Line = ''
+ if directive == 'param' then
+ for _, type in ipairs(TYPES) do
+ magic = magic:gsub('^param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. ')%)', 'param %1 %2')
+ magic =
+ magic:gsub('^param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. '|nil)%)', 'param %1 %2')
+ end
+ magic_split = vim.split(magic, ' ', { plain = true })
+ type_index = 3
+ elseif directive == 'return' then
+ for _, type in ipairs(TYPES) do
+ magic = magic:gsub('^return%s+.*%((' .. type .. ')%)', 'return %1')
+ magic = magic:gsub('^return%s+.*%((' .. type .. '|nil)%)', 'return %1')
+ end
+ -- Remove first "#" comment char, if any. https://github.com/LuaLS/lua-language-server/wiki/Annotations#return
+ magic = magic:gsub('# ', '', 1)
+ -- handle the return of vim.spell.check
+ magic = magic:gsub('({.*}%[%])', '`%1`')
+ magic_split = vim.split(magic, ' ', { plain = true })
end
- table.insert(this.tailLine, Line)
-end
---! \brief output tail lines
-function TStream_Write.write_tailLines(this)
- for _, line in ipairs(this.tailLine) do
- TCore_IO_writeln(line)
- end
- TCore_IO_write('// Lua2DoX new eof')
-end
+ local ty = magic_split[type_index]
+
+ if ty then
+ -- fix optional parameters
+ if magic_split[2]:find('%?$') then
+ if not ty:find('nil') then
+ ty = ty .. '|nil'
+ end
+ magic_split[2] = magic_split[2]:sub(1, -2)
+ end
---! \brief input filter
-local TLua2DoX_filter = class()
+ -- replace generic types
+ for k, v in pairs(generics) do
+ ty = ty:gsub(k, v) --- @type string
+ end
---! \brief allow us to do errormessages
-function TLua2DoX_filter.warning(this, Line, LineNo, Legend)
- this.outStream:writelnTail(
- '//! \todo warning! ' .. Legend .. ' (@' .. LineNo .. ')"' .. Line .. '"'
- )
-end
+ for _, type in ipairs(TAGGED_TYPES) do
+ ty = ty:gsub(type, '|%1|')
+ end
+
+ for _, type in ipairs(ALIAS_TYPES) do
+ ty = ty:gsub('^'..type..'$', 'table') --- @type string
+ end
+
+ -- surround some types by ()
+ for _, type in ipairs(TYPES) do
+ ty = ty
+ :gsub('^(' .. type .. '|nil):?$', '(%1)')
+ :gsub('^(' .. type .. '):?$', '(%1)')
+ end
+
+ magic_split[type_index] = ty
---! \brief trim comment off end of string
---!
---! If the string has a comment on the end, this trims it off.
---!
-local function TString_removeCommentFromLine(Line)
- local pos_comment = string.find(Line, '%-%-')
- local tailComment
- if pos_comment then
- Line = string.sub(Line, 1, pos_comment - 1)
- tailComment = string.sub(Line, pos_comment)
end
- return Line, tailComment
+
+ magic = table.concat(magic_split, ' ')
+
+ return '/// @' .. magic
end
---! \brief get directive from magic
-local function getMagicDirective(Line)
- local macro, tail
- local macroStr = '[\\@]'
- local pos_macro = string.find(Line, macroStr)
- if pos_macro then
- --! ....\\ macro...stuff
- --! ....\@ macro...stuff
- local line = string.sub(Line, pos_macro + 1)
- local space = string.find(line, '%s+')
- if space then
- macro = string.sub(line, 1, space - 1)
- tail = string_trim(string.sub(line, space + 1))
+--- @param line string
+--- @param in_stream StreamRead
+--- @return string
+local function process_block_comment(line, in_stream)
+ local comment_parts = {} --- @type string[]
+ local done --- @type boolean?
+
+ while not done and not in_stream:eof() do
+ local thisComment --- @type string?
+ local closeSquare = line:find(']]')
+ if not closeSquare then -- need to look on another line
+ thisComment = line .. '\n'
+ line = in_stream:getLine()
else
- macro = line
- tail = ''
+ thisComment = line:sub(1, closeSquare - 1)
+ done = true
+
+ -- unget the tail of the line
+ -- in most cases it's empty. This may make us less efficient but
+ -- easier to program
+ in_stream:ungetLine(vim.trim(line:sub(closeSquare + 2)))
end
+ comment_parts[#comment_parts+1] = thisComment
end
- return macro, tail
+
+ local comment = table.concat(comment_parts)
+
+ if comment:sub(1, 1) == '@' then -- it's a long magic comment
+ return '/*' .. comment .. '*/ '
+ end
+
+ -- discard
+ return '/* zz:' .. comment .. '*/ '
end
---! \brief check comment for fn
-local function checkComment4fn(Fn_magic, MagicLines)
- local fn_magic = Fn_magic
- -- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
+--- @param line string
+--- @return string
+local function process_function_header(line)
+ local pos_fn = assert(line:find('function'))
+ -- we've got a function
+ local fn = removeCommentFromLine(vim.trim(line:sub(pos_fn + 8)))
+
+ if fn:sub(1, 1) == '(' then
+ -- it's an anonymous function
+ return '// ZZ: '..line
+ end
+ -- fn has a name, so is interesting
+
+ -- want to fix for iffy declarations
+ if fn:find('[%({]') then
+ -- we might have a missing close paren
+ if not fn:find('%)') then
+ fn = fn .. ' ___MissingCloseParenHere___)'
+ end
+ end
- local magicLines = string_split(MagicLines, '\n')
+ -- Big hax
+ if fn:find(':') then
+ fn = fn:gsub(':', '.', 1)
- local macro, tail
+ local paren_start = fn:find('(', 1, true)
+ local paren_finish = fn:find(')', 1, true)
- for _, line in ipairs(magicLines) do
- macro, tail = getMagicDirective(line)
- if macro == 'fn' then
- fn_magic = tail
- -- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
- --else
- --TCore_IO_writeln('// not found fn "' .. line .. '"')
+ -- Nothing in between the parens
+ local comma --- @type string
+ if paren_finish == paren_start + 1 then
+ comma = ''
+ else
+ comma = ', '
end
+
+ fn = fn:sub(1, paren_start)
+ .. 'self'
+ .. comma
+ .. fn:sub(paren_start + 1)
+ end
+
+ if line:match('local') then
+ -- Special: tell gen_vimdoc.py this is a local function.
+ return 'local_function ' .. fn .. '{}'
end
- return fn_magic
+ -- add vanilla function
+ return 'function ' .. fn .. '{}'
end
-local types = { 'number', 'string', 'table', 'list', 'boolean', 'function' }
-
---! \brief run the filter
-function TLua2DoX_filter.readfile(this, AppStamp, Filename)
- local inStream = TStream_Read()
- local outStream = TStream_Write()
- this.outStream = outStream -- save to this obj
-
- if inStream:getContents(Filename) then
- -- output the file
- local line
- local fn_magic -- function name/def from magic comment
-
- outStream:writelnTail('// #######################')
- outStream:writelnTail('// app run:' .. AppStamp)
- outStream:writelnTail('// #######################')
- outStream:writelnTail()
-
- local state = '' -- luacheck: ignore 231 variable is set but never accessed.
- local offset = 0
- local generic = {}
- local l = 0
- while not (inStream:eof()) do
- line = string_trim(inStream:getLine())
- l = l + 1
- if string.sub(line, 1, 2) == '--' then -- it's a comment
- -- Allow people to write style similar to EmmyLua (since they are basically the same)
- -- instead of silently skipping things that start with ---
- if string.sub(line, 3, 3) == '@' then -- it's a magic comment
- offset = 0
- elseif string.sub(line, 1, 4) == '---@' then -- it's a magic comment
- offset = 1
- end
-
- if string.sub(line, 3, 3) == '@' or string.sub(line, 1, 4) == '---@' then -- it's a magic comment
- state = 'in_magic_comment'
- local magic = string.sub(line, 4 + offset)
-
- local magic_split = string_split(magic, ' ')
- if magic_split[1] == 'param' then
- for _, type in ipairs(types) do
- magic = magic:gsub('^param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. ')%)', 'param %1 %2')
- magic =
- magic:gsub('^param%s+([a-zA-Z_?]+)%s+.*%((' .. type .. '|nil)%)', 'param %1 %2')
- end
- magic_split = string_split(magic, ' ')
- elseif magic_split[1] == 'return' then
- for _, type in ipairs(types) do
- magic = magic:gsub('^return%s+.*%((' .. type .. ')%)', 'return %1')
- magic = magic:gsub('^return%s+.*%((' .. type .. '|nil)%)', 'return %1')
- end
- magic_split = string_split(magic, ' ')
- end
-
- if magic_split[1] == 'generic' then
- local generic_name, generic_type = line:match('@generic%s*(%w+)%s*:?%s*(.*)')
- if generic_type == '' then
- generic_type = 'any'
- end
- generic[generic_name] = generic_type
- else
- local type_index = 2
- if magic_split[1] == 'param' then
- type_index = type_index + 1
- end
-
- if magic_split[type_index] then
- -- fix optional parameters
- if magic_split[type_index] and magic_split[2]:find('%?$') then
- if not magic_split[type_index]:find('nil') then
- magic_split[type_index] = magic_split[type_index] .. '|nil'
- end
- magic_split[2] = magic_split[2]:sub(1, -2)
- end
- -- replace generic types
- if magic_split[type_index] then
- for k, v in pairs(generic) do
- magic_split[type_index] = magic_split[type_index]:gsub(k, v)
- end
- end
- -- surround some types by ()
- for _, type in ipairs(types) do
- magic_split[type_index] =
- magic_split[type_index]:gsub('^(' .. type .. '|nil):?$', '(%1)')
- magic_split[type_index] =
- magic_split[type_index]:gsub('^(' .. type .. '):?$', '(%1)')
- end
- end
-
- magic = table.concat(magic_split, ' ')
-
- outStream:writeln('/// @' .. magic)
- fn_magic = checkComment4fn(fn_magic, magic)
- end
- elseif string.sub(line, 3, 3) == '-' then -- it's a nonmagic doc comment
- local comment = string.sub(line, 4)
- outStream:writeln('/// ' .. comment)
- elseif string.sub(line, 3, 4) == '[[' then -- it's a long comment
- line = string.sub(line, 5) -- nibble head
- local comment = ''
- local closeSquare, hitend, thisComment
- while not hitend and (not inStream:eof()) do
- closeSquare = string.find(line, ']]')
- if not closeSquare then -- need to look on another line
- thisComment = line .. '\n'
- line = inStream:getLine()
- else
- thisComment = string.sub(line, 1, closeSquare - 1)
- hitend = true
-
- -- unget the tail of the line
- -- in most cases it's empty. This may make us less efficient but
- -- easier to program
- inStream:ungetLine(string_trim(string.sub(line, closeSquare + 2)))
- end
- comment = comment .. thisComment
- end
- if string.sub(comment, 1, 1) == '@' then -- it's a long magic comment
- outStream:write('/*' .. comment .. '*/ ')
- fn_magic = checkComment4fn(fn_magic, comment)
- else -- discard
- outStream:write('/* zz:' .. comment .. '*/ ')
- fn_magic = nil
- end
- -- TODO(justinmk): Uncomment this if we want "--" lines to continue the
- -- preceding magic ("---", "--@", …) lines.
- -- elseif state == 'in_magic_comment' then -- next line of magic comment
- -- outStream:writeln('/// '.. line:sub(3))
- else -- discard
- outStream:writeln('// zz:"' .. line .. '"')
- fn_magic = nil
- end
- elseif string.find(line, '^function') or string.find(line, '^local%s+function') then
- generic = {}
- state = 'in_function' -- it's a function
- local pos_fn = string.find(line, 'function')
- -- function
- -- ....v...
- if pos_fn then
- -- we've got a function
- local fn = TString_removeCommentFromLine(string_trim(string.sub(line, pos_fn + 8)))
- if fn_magic then
- fn = fn_magic
- end
-
- if string.sub(fn, 1, 1) == '(' then
- -- it's an anonymous function
- outStream:writelnComment(line)
- else
- -- fn has a name, so is interesting
-
- -- want to fix for iffy declarations
- local open_paren = string.find(fn, '[%({]')
- if open_paren then
- -- we might have a missing close paren
- if not string.find(fn, '%)') then
- fn = fn .. ' ___MissingCloseParenHere___)'
- end
- end
-
- -- Big hax
- if string.find(fn, ':') then
- -- TODO: We need to add a first parameter of "SELF" here
- -- local colon_place = string.find(fn, ":")
- -- local name = string.sub(fn, 1, colon_place)
- fn = fn:gsub(':', '.', 1)
- outStream:writeln('/// @param self')
-
- local paren_start = string.find(fn, '(', 1, true)
- local paren_finish = string.find(fn, ')', 1, true)
-
- -- Nothing in between the parens
- local comma
- if paren_finish == paren_start + 1 then
- comma = ''
- else
- comma = ', '
- end
- fn = string.sub(fn, 1, paren_start)
- .. 'self'
- .. comma
- .. string.sub(fn, paren_start + 1)
- end
-
- -- add vanilla function
- outStream:writeln('function ' .. fn .. '{}')
- end
- else
- this:warning(inStream:getLineNo(), 'something weird here')
- end
- fn_magic = nil -- mustn't inadvertently use it again
-
- -- TODO: If we can make this learn how to generate these, that would be helpful.
- -- elseif string.find(line, "^M%['.*'%] = function") then
- -- state = 'in_function' -- it's a function
- -- outStream:writeln("function textDocument/publishDiagnostics(...){}")
-
- -- fn_magic = nil -- mustn't inadvertently use it again
- else
- state = '' -- unknown
- if #line > 0 then -- we don't know what this line means, so just comment it out
- outStream:writeln('// zz: ' .. line)
- else
- outStream:writeln() -- keep this line blank
- end
- end
+--- @param line string
+--- @param in_stream StreamRead
+--- @param generics table<string,string>>
+--- @return string?
+local function process_line(line, in_stream, generics)
+ local line_raw = line
+ line = vim.trim(line)
+
+ if vim.startswith(line, '---') then
+ return process_magic(line:sub(4), generics)
+ end
+
+ if vim.startswith(line, '--'..'[[') then -- it's a long comment
+ return process_block_comment(line:sub(5), in_stream)
+ end
+
+ -- Hax... I'm sorry
+ -- M.fun = vim.memoize(function(...)
+ -- ->
+ -- function M.fun(...)
+ line = line:gsub('^(.+) = .*_memoize%([^,]+, function%((.*)%)$', 'function %1(%2)')
+
+ if line:find('^function') or line:find('^local%s+function') then
+ return process_function_header(line)
+ end
+
+ if not line:match('^local') then
+ local v = line_raw:match('^([A-Za-z][.a-zA-Z_]*)%s+%=')
+ if v and v:match('%.') then
+ -- Special: this lets gen_vimdoc.py handle tables.
+ return 'table '..v..'() {}'
end
+ end
- -- output the tail
- outStream:write_tailLines()
- else
- outStream:writeln('!empty file')
+ if #line > 0 then -- we don't know what this line means, so just comment it out
+ return '// zz: ' .. line
end
+
+ return ''
end
---! \brief this application
-local TApp = class()
+-- Processes the file and writes filtered output to stdout.
+---@param filename string
+function Lua2DoxFilter:filter(filename)
+ local in_stream = StreamRead.new(filename)
---! \brief constructor
-function TApp.init(this)
- this.timestamp = os.date('%c %Z', os.time())
- this.name = 'Lua2DoX'
- this.version = '0.2 20130128'
- this.copyright = 'Copyright (c) Simon Dales 2012-13'
-end
+ local generics = {} --- @type table<string,string>
-function TApp.getRunStamp(this)
- return this.name .. ' (' .. this.version .. ') ' .. this.timestamp
-end
+ while not in_stream:eof() do
+ local line = in_stream:getLine()
+
+ local out_line = process_line(line, in_stream, generics)
+
+ if not vim.startswith(vim.trim(line), '---') then
+ generics = {}
+ end
-function TApp.getVersion(this)
- return this.name .. ' (' .. this.version .. ') '
+ if out_line then
+ writeln(out_line)
+ end
+ end
end
-function TApp.getCopyright(this)
- return this.copyright
+--- @class TApp
+--- @field timestamp string|osdate
+--- @field name string
+--- @field version string
+--- @field copyright string
+--- this application
+local TApp = {
+ timestamp = os.date('%c %Z', os.time()),
+ name = 'Lua2DoX',
+ version = '0.2 20130128',
+ copyright = 'Copyright (c) Simon Dales 2012-13'
+}
+
+setmetatable(TApp, { __index = TApp })
+
+function TApp:getRunStamp()
+ return self.name .. ' (' .. self.version .. ') ' .. self.timestamp
end
-local This_app = TApp()
+function TApp:getVersion()
+ return self.name .. ' (' .. self.version .. ') '
+end
--main
-local argv1 = arg[1]
-if argv1 == '--help' then
- TCore_IO_writeln(This_app:getVersion())
- TCore_IO_writeln(This_app:getCopyright())
- TCore_IO_writeln([[
+if arg[1] == '--help' then
+ writeln(TApp:getVersion())
+ writeln(TApp.copyright)
+ writeln([[
run as:
nvim -l scripts/lua2dox.lua <param>
--------------
@@ -540,16 +444,32 @@ if argv1 == '--help' then
<filename> : interprets filename
--version : show version/copyright info
--help : this help text]])
-elseif argv1 == '--version' then
- TCore_IO_writeln(This_app:getVersion())
- TCore_IO_writeln(This_app:getCopyright())
-else
- -- it's a filter
- local appStamp = This_app:getRunStamp()
- local filename = argv1
-
- local filter = TLua2DoX_filter()
- filter:readfile(appStamp, filename)
-end
+elseif arg[1] == '--version' then
+ writeln(TApp:getVersion())
+ writeln(TApp.copyright)
+else -- It's a filter.
+ local filename = arg[1]
+
+ if arg[2] == '--outdir' then
+ local outdir = arg[3]
+ if type(outdir) ~= 'string' or (0 ~= vim.fn.filereadable(outdir) and 0 == vim.fn.isdirectory(outdir)) then
+ error(('invalid --outdir: "%s"'):format(tostring(outdir)))
+ end
+ vim.fn.mkdir(outdir, 'p')
+ debug_outfile = string.format('%s/%s.c', outdir, vim.fs.basename(filename))
+ end
+
+ Lua2DoxFilter:filter(filename)
+
+ -- output the tail
+ writeln('// #######################')
+ writeln('// app run:' .. TApp:getRunStamp())
+ writeln('// #######################')
+ writeln()
---eof
+ if debug_outfile then
+ local f = assert(io.open(debug_outfile, 'w'))
+ f:write(table.concat(debug_output))
+ f:close()
+ end
+end
diff --git a/scripts/pvscheck.sh b/scripts/pvscheck.sh
deleted file mode 100755
index 97757c0848..0000000000
--- a/scripts/pvscheck.sh
+++ /dev/null
@@ -1,495 +0,0 @@
-#!/bin/sh
-
-# Assume that "local" is available.
-# shellcheck disable=SC2039
-
-set -e
-# Note: -u causes problems with posh, it barks at “undefined” $@ when no
-# arguments provided.
-test -z "$POSH_VERSION" && set -u
-
-log_info() {
- >&2 printf "pvscheck.sh: %s\n" "$@"
-}
-
-get_jobs_num() {
- if [ -n "${TRAVIS:-}" ] ; then
- # HACK: /proc/cpuinfo on Travis CI is misleading, so hardcode 1.
- echo 1
- else
- echo $(( $(grep -c "^processor" /proc/cpuinfo) + 1 ))
- fi
-}
-
-help() {
- echo 'Usage:'
- echo ' pvscheck.sh [--pvs URL] [--deps] [--environment-cc]'
- echo ' [target-directory [branch]]'
- echo ' pvscheck.sh [--pvs URL] [--recheck] [--environment-cc] [--update]'
- echo ' [target-directory]'
- echo ' pvscheck.sh [--pvs URL] --only-analyse [target-directory]'
- echo ' pvscheck.sh [--pvs URL] --pvs-install {target-directory}'
- echo ' pvscheck.sh --patch [--only-build]'
- echo
- echo ' --pvs: Fetch pvs-studio from URL.'
- echo
- echo ' --pvs detect: Auto-detect latest version (by scraping viva64.com).'
- echo
- echo ' --deps: (for regular run) Use top-level Makefile and build deps.'
- echo ' Without this it assumes all dependencies are already'
- echo ' installed.'
- echo
- echo ' --environment-cc: (for regular run and --recheck) Do not export'
- echo ' CC=clang. Build is still run with CFLAGS=-O0.'
- echo
- echo ' --only-build: (for --patch) Only patch files in ./build directory.'
- echo
- echo ' --pvs-install: Only install PVS-studio to the specified location.'
- echo
- echo ' --patch: patch sources in the current directory.'
- echo ' Does not patch already patched files.'
- echo ' Does not run analysis.'
- echo
- echo ' --recheck: run analysis on a prepared target directory.'
- echo
- echo ' --update: when rechecking first do a pull.'
- echo
- echo ' --only-analyse: run analysis on a prepared target directory '
- echo ' without building Neovim.'
- echo
- echo ' target-directory: Directory where build should occur.'
- echo ' Default: ../neovim-pvs'
- echo
- echo ' branch: Branch to check.'
- echo ' Default: master.'
-}
-
-getopts_error() {
- local msg="$1" ; shift
- local do_help=
- if test "$msg" = "--help" ; then
- msg="$1" ; shift
- do_help=1
- fi
- printf '%s\n' "$msg" >&2
- if test -n "$do_help" ; then
- printf '\n' >&2
- help >&2
- fi
- echo 'return 1'
- return 1
-}
-
-# Usage `eval "$(getopts_long long_defs -- positionals_defs -- "$@")"`
-#
-# long_defs: list of pairs of arguments like `longopt action`.
-# positionals_defs: list of arguments like `action`.
-#
-# `action` is a space-separated commands:
-#
-# store_const [const] [varname] [default]
-# Store constant [const] (default 1) (note: eval’ed) if argument is present
-# (long options only). Assumes long option accepts no arguments.
-# store [varname] [default]
-# Store value. Assumes long option needs an argument.
-# run {func} [varname] [default]
-# Run function {func} and store its output to the [varname]. Assumes no
-# arguments accepted (long options only).
-# modify {func} [varname] [default]
-# Like run, but assumes a single argument, passed to function {func} as $1.
-#
-# All actions stores empty value if neither [varname] nor [default] are
-# present. [default] is evaled by top-level `eval`, so be careful. Also note
-# that no arguments may contain spaces, including [default] and [const].
-getopts_long() {
- local positional=
- local opt_bases=""
- while test $# -gt 0 ; do
- local arg="$1" ; shift
- local opt_base=
- local act=
- local opt_name=
- if test -z "$positional" ; then
- if test "$arg" = "--" ; then
- positional=0
- continue
- fi
- act="$1" ; shift
- opt_name="$(echo "$arg" | tr '-' '_')"
- opt_base="longopt_$opt_name"
- else
- if test "$arg" = "--" ; then
- break
- fi
- : $(( positional+=1 ))
- act="$arg"
- opt_name="arg_$positional"
- opt_base="positional_$positional"
- fi
- opt_bases="$opt_bases $opt_base"
- eval "local varname_$opt_base=$opt_name"
- local i=0
- for act_subarg in $act ; do
- eval "local act_$(( i+=1 ))_$opt_base=\"\$act_subarg\""
- done
- done
- # Process options
- local positional=0
- local force_positional=
- while test $# -gt 0 ; do
- local argument="$1" ; shift
- local opt_base=
- local has_equal=
- local equal_arg=
- local is_positional=
- if test "$argument" = "--" ; then
- force_positional=1
- continue
- elif test -z "$force_positional" && test "${argument#--}" != "$argument"
- then
- local opt_name="${argument#--}"
- local opt_name_striparg="${opt_name%%=*}"
- if test "$opt_name" = "$opt_name_striparg" ; then
- has_equal=0
- else
- has_equal=1
- equal_arg="${argument#*=}"
- opt_name="$opt_name_striparg"
- fi
- # Use trailing x to prevent stripping newlines
- opt_name="$(printf '%sx' "$opt_name" | tr '-' '_')"
- opt_name="${opt_name%x}"
- if test -n "$(printf '%sx' "$opt_name" | tr -d 'a-z_')" ; then
- getopts_error "Option contains invalid characters: $opt_name"
- fi
- opt_base="longopt_$opt_name"
- else
- : $(( positional+=1 ))
- opt_base="positional_$positional"
- is_positional=1
- fi
- if test -n "$opt_base" ; then
- eval "local occurred_$opt_base=1"
-
- eval "local act_1=\"\${act_1_$opt_base:-}\""
- eval "local varname=\"\${varname_$opt_base:-}\""
- local need_val=
- local func=
- case "$act_1" in
- (store_const)
- eval "local const=\"\${act_2_${opt_base}:-1}\""
- eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
- printf 'local %s=%s\n' "$varname" "$const"
- ;;
- (store)
- eval "varname=\"\${act_2_${opt_base}:-$varname}\""
- need_val=1
- ;;
- (run)
- eval "func=\"\${act_2_${opt_base}}\""
- eval "varname=\"\${act_3_${opt_base}:-$varname}\""
- printf 'local %s="$(%s)"\n' "$varname" "$func"
- ;;
- (modify)
- eval "func=\"\${act_2_${opt_base}}\""
- eval "varname=\"\${act_3_${opt_base}:-$varname}\""
- need_val=1
- ;;
- ("")
- getopts_error --help "Wrong argument: $argument"
- ;;
- esac
- if test -n "$need_val" ; then
- local val=
- if test -z "$is_positional" ; then
- if test $has_equal = 1 ; then
- val="$equal_arg"
- else
- if test $# -eq 0 ; then
- getopts_error "Missing argument for $opt_name"
- fi
- val="$1" ; shift
- fi
- else
- val="$argument"
- fi
- local escaped_val="'$(printf "%s" "$val" | sed "s/'/'\\\\''/g")'"
- case "$act_1" in
- (store)
- printf 'local %s=%s\n' "$varname" "$escaped_val"
- ;;
- (modify)
- printf 'local %s="$(%s %s)"\n' "$varname" "$func" "$escaped_val"
- ;;
- esac
- fi
- fi
- done
- # Print default values when no values were provided
- local opt_base=
- for opt_base in $opt_bases ; do
- eval "local occurred=\"\${occurred_$opt_base:-}\""
- if test -n "$occurred" ; then
- continue
- fi
- eval "local act_1=\"\$act_1_$opt_base\""
- eval "local varname=\"\$varname_$opt_base\""
- case "$act_1" in
- (store)
- eval "local varname=\"\${act_2_${opt_base}:-$varname}\""
- eval "local default=\"\${act_3_${opt_base}:-}\""
- printf 'local %s=%s\n' "$varname" "$default"
- ;;
- (store_const|run|modify)
- eval "local varname=\"\${act_3_${opt_base}:-$varname}\""
- eval "local default=\"\${act_4_${opt_base}:-}\""
- printf 'local %s=%s\n' "$varname" "$default"
- ;;
- esac
- done
-}
-
-get_pvs_comment() {
- local tgt="$1" ; shift
-
- cat > "$tgt/pvs-comment" << EOF
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-EOF
-}
-
-install_pvs() {(
- local tgt="$1" ; shift
- local pvs_url="$1" ; shift
-
- cd "$tgt"
-
- if test -d pvs-studio ; then
- log_info 'install_pvs: "pvs-studio" directory already exists, skipping install'
- return 0
- fi
-
- mkdir pvs-studio
- cd pvs-studio
-
- curl -L -o pvs-studio.tar.gz "$pvs_url"
- tar xzf pvs-studio.tar.gz
- rm pvs-studio.tar.gz
- local pvsdir="$(find . -maxdepth 1 -mindepth 1)"
- find "$pvsdir" -maxdepth 1 -mindepth 1 -exec mv '{}' . \;
- rmdir "$pvsdir"
-)}
-
-create_compile_commands() {(
- local tgt="$1" ; shift
- local deps="$1" ; shift
- local environment_cc="$1" ; shift
-
- if test -z "$environment_cc" ; then
- export CC=clang
- fi
- export CFLAGS=' -O0 '
-
- if test -z "$deps" ; then
- mkdir -p "$tgt/build"
- (
- cd "$tgt/build"
-
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX="$PWD/root"
- make -j"$(get_jobs_num)"
- )
- else
- (
- cd "$tgt"
-
- make -j"$(get_jobs_num)" CMAKE_EXTRA_FLAGS=" -DCMAKE_INSTALL_PREFIX=$PWD/root -DCMAKE_BUILD_TYPE=Debug "
- )
- fi
- find "$tgt/build/src/nvim/auto" -name '*.test-include.c' -delete
-)}
-
-# Warning: realdir below only cares about directories unlike realpath.
-#
-# realpath is not available in Ubuntu trusty yet.
-realdir() {(
- local dir="$1"
- local add=""
- while ! cd "$dir" 2>/dev/null ; do
- add="${dir##*/}/$add"
- local new_dir="${dir%/*}"
- if test "$new_dir" = "$dir" ; then
- return 1
- fi
- dir="$new_dir"
- done
- printf '%s\n' "$PWD/$add"
-)}
-
-patch_sources() {(
- local tgt="$1" ; shift
- local only_build="${1}" ; shift
-
- get_pvs_comment "$tgt"
-
- local sh_script='
- pvs_comment="$(cat pvs-comment ; echo -n EOS)"
- filehead="$(head -c $(( ${#pvs_comment} - 3 )) "$1" ; echo -n EOS)"
- if test "x$filehead" != "x$pvs_comment" ; then
- cat pvs-comment "$1" > "$1.tmp"
- mv "$1.tmp" "$1"
- fi
- '
-
- cd "$tgt"
-
- if test "$only_build" != "--only-build" ; then
- find \
- src/nvim test/functional/fixtures test/unit/fixtures \
- \( -name '*.c' -a '!' -path '*xdiff*' \) \
- -exec /bin/sh -c "$sh_script" - '{}' \;
- fi
-
- find \
- build/src/nvim/auto build/config \
- -name '*.c' -not -name '*.test-include.c' \
- -exec /bin/sh -c "$sh_script" - '{}' \;
-
- rm pvs-comment
-)}
-
-run_analysis() {(
- local tgt="$1" ; shift
-
- cd "$tgt"
-
- if [ ! -r PVS-Studio.lic ]; then
- pvs-studio-analyzer credentials -o PVS-Studio.lic 'PVS-Studio Free' 'FREE-FREE-FREE-FREE'
- fi
-
- # pvs-studio-analyzer exits with a non-zero exit code when there are detected
- # errors, so ignore its return
- pvs-studio-analyzer \
- analyze \
- --lic-file PVS-Studio.lic \
- --threads "$(get_jobs_num)" \
- --exclude-path src/cjson \
- --exclude-path src/xdiff \
- --output-file PVS-studio.log \
- --file build/compile_commands.json \
- --sourcetree-root . || true
-
- rm -rf PVS-studio.{xml,err,tsk,html.d}
- local plog_args="PVS-studio.log --srcRoot . --excludedCodes V002,V011,V1028,V1042,V1051,V1074"
- plog-converter $plog_args --renderTypes xml --output PVS-studio.xml
- plog-converter $plog_args --renderTypes errorfile --output PVS-studio.err
- plog-converter $plog_args --renderTypes tasklist --output PVS-studio.tsk
- plog-converter $plog_args --renderTypes fullhtml --output PVS-studio.html.d
-)}
-
-detect_url() {
- local url="${1:-detect}"
- if test "$url" = detect ; then
- curl --silent -L 'https://pvs-studio.com/en/pvs-studio/download-all/' \
- | grep -o 'https\{0,1\}://[^"<>]\{1,\}/pvs-studio[^/"<>]*-x86_64\.tgz' \
- || echo FAILED
- else
- printf '%s' "$url"
- fi
-}
-
-do_check() {
- local tgt="$1" ; shift
- local branch="$1" ; shift
- local pvs_url="$1" ; shift
- local deps="$1" ; shift
- local environment_cc="$1" ; shift
-
- if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
- pvs_url="$(detect_url detect)"
- if test -z "$pvs_url" || test "$pvs_url" = FAILED ; then
- echo "failed to auto-detect PVS URL"
- exit 1
- fi
- echo "Auto-detected PVS URL: ${pvs_url}"
- fi
-
- git clone --branch="$branch" . "$tgt"
-
- install_pvs "$tgt" "$pvs_url"
-
- do_recheck "$tgt" "$deps" "$environment_cc" ""
-}
-
-do_recheck() {
- local tgt="$1" ; shift
- local deps="$1" ; shift
- local environment_cc="$1" ; shift
- local update="$1" ; shift
-
- if test -n "$update" ; then
- (
- cd "$tgt"
- local branch="$(git rev-parse --abbrev-ref HEAD)"
- git checkout --detach
- git fetch -f origin "${branch}:${branch}"
- git checkout -f "$branch"
- )
- fi
-
- create_compile_commands "$tgt" "$deps" "$environment_cc"
-
- do_analysis "$tgt"
-}
-
-do_analysis() {
- local tgt="$1" ; shift
-
- if test -d "$tgt/pvs-studio" ; then
- local saved_pwd="$PWD"
- cd "$tgt/pvs-studio"
- export PATH="$PWD/bin${PATH+:}${PATH}"
- cd "$saved_pwd"
- fi
-
- run_analysis "$tgt"
-}
-
-main() {
- eval "$(
- getopts_long \
- help store_const \
- pvs 'modify detect_url pvs_url' \
- patch store_const \
- only-build 'store_const --only-build' \
- recheck store_const \
- only-analyse store_const \
- pvs-install store_const \
- deps store_const \
- environment-cc store_const \
- update store_const \
- -- \
- 'modify realdir tgt "$PWD/../neovim-pvs"' \
- 'store branch master' \
- -- "$@"
- )"
-
- if test -n "$help" ; then
- help
- return 0
- fi
-
- if test -n "$patch" ; then
- patch_sources "$tgt" "$only_build"
- elif test -n "$pvs_install" ; then
- install_pvs "$tgt" "$pvs_url"
- elif test -n "$recheck" ; then
- do_recheck "$tgt" "$deps" "$environment_cc" "$update"
- elif test -n "$only_analyse" ; then
- do_analysis "$tgt"
- else
- do_check "$tgt" "$branch" "$pvs_url" "$deps" "$environment_cc"
- fi
-}
-
-main "$@"
diff --git a/scripts/update_terminfo.sh b/scripts/update_terminfo.sh
index 775048f246..e12365ba8f 100755
--- a/scripts/update_terminfo.sh
+++ b/scripts/update_terminfo.sh
@@ -61,17 +61,11 @@ print_bold "[*] Writing $target... "
sorted_terms="$(echo "${!entries[@]}" | tr ' ' '\n' | sort | xargs)"
cat > "$target" <<EOF
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// uncrustify:off
-//
// Generated by scripts/update_terminfo.sh and $(tic -V)
-//
-#ifndef NVIM_TUI_TERMINFO_DEFS_H
-#define NVIM_TUI_TERMINFO_DEFS_H
+#pragma once
#include <stdint.h>
EOF
@@ -90,7 +84,4 @@ for term in $sorted_terms; do
printf '};\n'
done >> "$target"
-cat >> "$target" <<EOF
-#endif // NVIM_TUI_TERMINFO_DEFS_H
-EOF
print_bold 'done\n'
diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh
index f9f7330097..47c6d293bc 100755
--- a/scripts/vim-patch.sh
+++ b/scripts/vim-patch.sh
@@ -119,10 +119,10 @@ get_vim_sources() {
}
commit_message() {
- if [[ -n "$vim_tag" ]]; then
- printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthor}"
+ if [[ "${vim_message}" == "vim-patch:${vim_version}:"* ]]; then
+ printf '%s\n\n%s\n\n%s' "${vim_message}" "${vim_commit_url}" "${vim_coauthors}"
else
- printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthor"
+ printf 'vim-patch:%s\n\n%s\n\n%s\n\n%s' "$vim_version" "$vim_message" "$vim_commit_url" "$vim_coauthors"
fi
}
@@ -174,11 +174,17 @@ assign_commit_details() {
vim_commit_url="https://github.com/vim/vim/commit/${vim_commit}"
vim_message="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:%B' "${vim_commit}" \
- | sed -e 's/\(#[0-9]\{1,\}\)/vim\/vim\1/g')"
- vim_coauthor="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")"
+ | sed -Ee 's/(#[0-9]{1,})/vim\/vim\1/g')"
+ local vim_coauthor0
+ vim_coauthor0="$(git -C "${VIM_SOURCE_DIR}" log -1 --pretty='format:Co-authored-by: %an <%ae>' "${vim_commit}")"
+ # Extract co-authors from the commit message.
+ vim_coauthors="$(echo "${vim_message}" | (grep -E '^Co-authored-by: ' || true) | (grep -Fxv "${vim_coauthor0}" || true))"
+ vim_coauthors="$(echo "${vim_coauthor0}"; echo "${vim_coauthors}")"
+ # Remove Co-authored-by and Signed-off-by lines from the commit message.
+ vim_message="$(echo "${vim_message}" | grep -Ev '^(Co-authored|Signed-off)-by: ')"
if [[ ${munge_commit_line} == "true" ]]; then
# Remove first line of commit message.
- vim_message="$(echo "${vim_message}" | sed -e '1s/^patch /vim-patch:/')"
+ vim_message="$(echo "${vim_message}" | sed -Ee '1s/^patch /vim-patch:/')"
fi
patch_file="vim-${vim_version}.patch"
}
@@ -190,100 +196,113 @@ preprocess_patch() {
# Remove Filelist, README
local na_files='Filelist\|README.*'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/\<\%('"${na_files}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/\<\%('"${na_files}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
# Remove *.proto, Make*, INSTALL*, gui_*, beval.*, some if_*, gvim, libvterm, tee, VisVim, xpm, xxd
local na_src='auto\|configure.*\|GvimExt\|hardcopy.*\|libvterm\|proto\|tee\|VisVim\|xpm\|xxd\|Make.*\|INSTALL.*\|beval.*\|gui.*\|if_cscop\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
- # Remove runtime files ported to Lua.
- local na_rt='filetype\.vim\|scripts\.vim\|autoload\/ft\/dist\.vim\|print\/.*'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('"${na_rt}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ # Remove runtime/print/
+ local na_rt='print\/.*'
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('"${na_rt}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
# Remove unwanted Vim doc files.
local na_doc='channel\.txt\|if_cscop\.txt\|netbeans\.txt\|os_\w\+\.txt\|print\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|vim9\.txt\|sponsor\.txt\|intro\.txt\|tags'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\<\%('"${na_doc}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/doc/\<\%('"${na_doc}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
# Remove "Last change ..." changes in doc files.
2>/dev/null $nvim --cmd 'set dir=/tmp' +'%s/^@@.*\n.*For Vim version.*Last change.*\n.*For Vim version.*Last change.*//' +w +q "$file"
# Remove gui, option, setup, screen dumps, testdir/Make_*.mak files
local na_src_testdir='gen_opt_test\.vim\|gui_.*\|Make_amiga\.mak\|Make_dos\.mak\|Make_ming\.mak\|Make_vms\.mms\|dumps/.*\.dump\|setup_gui\.vim'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('"${na_src_testdir}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
# Remove testdir/test_*.vim files
- local na_src_testdir='balloon.*\|channel.*\|crypt\.vim\|cscope\.vim\|gui.*\|hardcopy\.vim\|job_fails\.vim\|json\.vim\|mzscheme\.vim\|netbeans.*\|paste\.vim\|popupwin.*\|restricted\.vim\|shortpathname\.vim\|tcl\.vim\|terminal.*\|xxd\.vim'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<test_\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ local na_src_testdir='balloon.*\|behave\.vim\|channel.*\|crypt\.vim\|cscope\.vim\|gui.*\|hardcopy\.vim\|job_fails\.vim\|json\.vim\|listener\.vim\|mzscheme\.vim\|netbeans.*\|paste\.vim\|popupwin.*\|python2\.vim\|pyx2\.vim\|restricted\.vim\|shortpathname\.vim\|sound\.vim\|tcl\.vim\|terminal.*\|xxd\.vim'
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<test_\%('"${na_src_testdir}"'\)\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
- # Remove version.c #7555
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%(version\.c\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ # Remove N/A src/*.[ch] files: sound.c, version.c
+ local na_src_c='sound\|version'
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%('"${na_src_c}"'\)\.[ch]\>@exe "norm! d/\\v(^diff)|%$\r"' +w +q "$file"
# Remove some *.po files. #5622
local na_po='sjiscorr\.c\|ja\.sjis\.po\|ko\.po\|pl\.cp1250\.po\|pl\.po\|ru\.cp1251\.po\|uk\.cp1251\.po\|zh_CN\.cp936\.po\|zh_CN\.po\|zh_TW\.po'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/po/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/po/\<\%('${na_po}'\)\>@exe "norm! d/\\v(^diff)|%$\r"+' +w +q "$file"
# Remove vimrc_example.vim
local na_vimrcexample='vimrc_example\.vim'
- 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('${na_vimrcexample}'\)\>@norm! d/\v(^diff)|%$ ' +w +q "$file"
+ 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/runtime/\<\%('${na_vimrcexample}'\)\>@exe "norm! d/\\v(^diff)|%$\r"+' +w +q "$file"
+
+ # Rename src/testdir/ paths to test/old/testdir/
+ LC_ALL=C sed -Ee 's/( [ab])\/src\/testdir/\1\/test\/old\/testdir/g' \
+ "$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename src/ paths to src/nvim/
- LC_ALL=C sed -e 's/\( [ab]\/src\)/\1\/nvim/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src)/\1\/nvim/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename evalfunc.c to eval/funcs.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalfunc\.c/\1\/eval\/funcs\.c/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalfunc\.c/\1\/eval\/funcs.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename evalvars.c to eval/vars.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalvars\.c/\1\/eval\/vars\.c/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalvars\.c/\1\/eval\/vars.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename userfunc.c to eval/userfunc.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/userfunc\.c/\1\/eval\/userfunc\.c/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/userfunc\.c/\1\/eval\/userfunc.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename evalbuffer.c to eval/buffer.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalbuffer\.c/\1\/eval\/buffer\.c/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalbuffer\.c/\1\/eval\/buffer.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename evalwindow.c to eval/window.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/evalwindow\.c/\1\/eval\/window\.c/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/evalwindow\.c/\1\/eval\/window.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename map.c to mapping.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/map\(\.[ch]\)/\1\/mapping\2/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/map\.c/\1\/mapping.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename profiler.c to profile.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/profiler\(\.[ch]\)/\1\/profile\2/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/profiler\.c/\1\/profile.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename scriptfile.c to runtime.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/scriptfile\(\.[ch]\)/\1\/runtime\2/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/scriptfile\.c/\1\/runtime.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename session.c to ex_session.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/session\.c/\1\/ex_session.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename highlight.c to highlight_group.c
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/highlight\(\.[ch]\)/\1\/highlight_group\2/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/highlight\.c/\1\/highlight_group.c/g' \
+ "$file" > "$file".tmp && mv "$file".tmp "$file"
+
+ # Rename locale.c to os/lang.c
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/locale\.c/\1\/os\/lang.c/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename keymap.h to keycodes.h
- LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/keymap\.h/\1\/keycodes.h/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/keymap\.h/\1\/keycodes.h/g' \
+ "$file" > "$file".tmp && mv "$file".tmp "$file"
+
+ # Rename option.h to option_vars.h
+ LC_ALL=C sed -Ee 's/( [ab]\/src\/nvim)\/option\.h/\1\/option_vars.h/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename terminal.txt to nvim_terminal_emulator.txt
- LC_ALL=C sed -e 's/\( [ab]\/runtime\/doc\)\/terminal\.txt/\1\/nvim_terminal_emulator.txt/g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/runtime\/doc)\/terminal\.txt/\1\/nvim_terminal_emulator.txt/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename test_urls.vim to check_urls.vim
- LC_ALL=C sed -e 's@\( [ab]\)/runtime/doc/test\(_urls\.vim\)@\1/scripts/check\2@g' \
+ LC_ALL=C sed -Ee 's/( [ab])\/runtime\/doc\/test(_urls\.vim)/\1\/scripts\/check\2/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
# Rename path to check_colors.vim
- LC_ALL=C sed -e 's@\( [ab]/runtime\)/colors/\(tools/check_colors\.vim\)@\1/\2@g' \
+ LC_ALL=C sed -Ee 's/( [ab]\/runtime)\/colors\/(tools\/check_colors\.vim)/\1\/\2/g' \
"$file" > "$file".tmp && mv "$file".tmp "$file"
}
@@ -293,7 +312,7 @@ uncrustify_patch() {
exit 1
}
- local patch_path=$NVIM_SOURCE_DIR/build/vim_patch
+ local patch_path="$NVIM_SOURCE_DIR"/build/vim_patch
rm -rf "$patch_path"
mkdir -p "$patch_path"/{a,b}
@@ -310,7 +329,7 @@ uncrustify_patch() {
# than once. This is obviously a bug that needs to be fixed on uncrustify's
# end, but in the meantime this workaround is sufficient.
for _ in {1..2}; do
- uncrustify -c "$NVIM_SOURCE_DIR"/src/uncrustify.cfg -q --replace --no-backup "$patch_path"/{a,b}/src/*.[ch]
+ "$NVIM_SOURCE_DIR"/build/usr/bin/uncrustify -c "$NVIM_SOURCE_DIR"/src/uncrustify.cfg -q --replace --no-backup "$patch_path"/{a,b}/src/*.[ch]
done
(cd "$patch_path" && (git --no-pager diff --no-index --no-prefix --patch --unified=5 --color=never a/ b/ || true))
@@ -324,7 +343,7 @@ get_vimpatch() {
msg_ok "Found Vim revision '${vim_commit}'."
local patch_content
- if check_executable uncrustify; then
+ if check_executable "$NVIM_SOURCE_DIR"/build/usr/bin/uncrustify; then
patch_content="$(uncrustify_patch "${vim_commit}")"
else
patch_content="$(git --no-pager show --unified=5 --color=never -1 --pretty=medium "${vim_commit}")"
@@ -522,7 +541,7 @@ list_vimpatch_tokens() {
| grep -oE 'vim-patch:[^ ,{:]{7,}' \
| sort \
| uniq \
- | sed -nE 's/^(vim-patch:([0-9]+\.[^ ]+|[0-9a-z]{7,7})).*/\1/p'
+ | sed -nEe 's/^(vim-patch:([0-9]+\.[^ ]+|[0-9a-z]{7,7})).*/\1/p'
}
# Prints all patch-numbers (for the current v:version) for which there is
@@ -530,7 +549,7 @@ list_vimpatch_tokens() {
list_vimpatch_numbers() {
# Transform "vim-patch:X.Y.ZZZZ" to "ZZZZ".
list_vimpatch_tokens | while read -r vimpatch_token; do
- echo "$vimpatch_token" | grep '8\.1\.' | sed -E 's/.*vim-patch:8\.1\.([0-9a-z]+).*/\1/'
+ echo "$vimpatch_token" | grep -F '8.1.' | sed -Ee 's/.*vim-patch:8\.1\.([0-9a-z]+).*/\1/'
done
}
@@ -551,10 +570,8 @@ _set_tokens_and_tags() {
# Create an associative array mapping Vim commits to tags.
eval "vim_commit_tags=(
- $(git -C "${VIM_SOURCE_DIR}" for-each-ref refs/tags \
- --format '[%(objectname)]=%(refname:strip=2)' \
- --sort='-*authordate' \
- --shell)
+ $(git -C "${VIM_SOURCE_DIR}" show-ref --tags --dereference \
+ | sed -nEe 's/^([0-9a-f]+) refs\/tags\/(v[0-9.]+)(\^\{\})?$/["\1"]="\2"/p')
)"
# Exit in case of errors from the above eval (empty vim_commit_tags).
if ! (( "${#vim_commit_tags[@]}" )); then
@@ -594,6 +611,7 @@ _set_missing_vimpatches() {
# Massage arguments for git-log.
declare -A git_log_replacements=(
[^\(.*/\)?src/nvim/\(.*\)]="\${BASH_REMATCH[1]}src/\${BASH_REMATCH[2]}"
+ [^\(.*/\)?test/old/\(.*\)]="\${BASH_REMATCH[1]}src/\${BASH_REMATCH[2]}"
[^\(.*/\)?\.vim-src/\(.*\)]="\${BASH_REMATCH[2]}"
)
local i j
@@ -654,7 +672,7 @@ show_vimpatches() {
printf "Vim patches missing from Neovim:\n"
local -A runtime_commits
- for commit in $(git -C "${VIM_SOURCE_DIR}" log --format="%H %D" -- runtime | sed 's/,\? tag: / /g'); do
+ for commit in $(git -C "${VIM_SOURCE_DIR}" log --format="%H %D" -- runtime | sed -Ee 's/,\? tag: / /g'); do
runtime_commits[$commit]=1
done
@@ -748,7 +766,7 @@ review_commit() {
local nvim_patch
nvim_patch="$(curl -Ssf "${nvim_patch_url}")"
local vim_version
- vim_version="$(head -n 4 <<< "${nvim_patch}" | sed -n 's/'"${git_patch_prefix}"'vim-patch:\([a-z0-9.]*\)\(:.*\)\{0,1\}$/\1/p')"
+ vim_version="$(head -n 4 <<< "${nvim_patch}" | sed -nEe 's/'"${git_patch_prefix}"'vim-patch:([a-z0-9.]*)(:.*){0,1}$/\1/p')"
echo
if [[ -n "${vim_version}" ]]; then
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
deleted file mode 100644
index fd57f2d5e4..0000000000
--- a/snap/snapcraft.yaml
+++ /dev/null
@@ -1,89 +0,0 @@
-name: nvim
-base: core18
-adopt-info: nvim
-summary: Vim-fork focused on extensibility and agility.
-description: |
- Neovim is a project that seeks to aggressively refactor Vim in order to:
-
- Simplify maintenance and encourage contributions
- Split the work between multiple developers
- Enable the implementation of new/modern user interfaces without any modifications to the core source
- Improve extensibility with a new plugin architecture
- For lots more details, see the wiki!
-
-grade: stable # must be 'stable' to release into candidate/stable channels
-confinement: classic
-
-apps:
- nvim:
- command: usr/bin/nvim
- environment:
- HOME: /home/$USER
- VIM: $SNAP/usr/share/nvim
- VIMRUNTIME: $SNAP/usr/share/nvim/runtime
- desktop: usr/share/applications/nvim.desktop
-
-parts:
- nvim:
- source: https://github.com/neovim/neovim.git
- override-pull: |
- snapcraftctl pull
- latest_tag="$(git tag -l --sort=refname|head -1)"
- git checkout "${latest_tag}"
- major="$(awk '/NVIM_VERSION_MAJOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
- minor="$(awk '/NVIM_VERSION_MINOR/{gsub(")","",$2); print $2}' CMakeLists.txt)"
- patch="$(awk '/NVIM_VERSION_PATCH/{gsub(")","",$2); print $2}' CMakeLists.txt)"
- version_prefix="v$major.$minor.$patch"
- git_described="$(git describe --first-parent --dirty 2> /dev/null | perl -lpe 's/v\d.\d.\d-//g')"
- git_described="${git_described:-$(git describe --first-parent --tags --always --dirty)}"
- if [ "${version_prefix}" != "${git_described}" ]; then
- VERSION="${version_prefix}-${git_described}-${latest_tag}"
- else
- VERSION="${version_prefix}-${latest_tag}"
- fi
- snapcraftctl set-version "${VERSION}"
- plugin: make
- make-parameters:
- - CMAKE_BUILD_TYPE=RelWithDebInfo
- - CMAKE_INSTALL_PREFIX=/usr
- - CMAKE_FLAGS=-DPREFER_LUA=ON
- - DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
- override-build: |
- echo "Building on $SNAP_ARCH"
- set -x
- case "$SNAP_ARCH" in
- "arm64" | "ppc64el" | "s390x")
- make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
- CMAKE_BUILD_TYPE=RelWithDebInfo \
- CMAKE_INSTALL_PREFIX=/usr \
- CMAKE_FLAGS=-DPREFER_LUA=ON \
- DEPS_CMAKE_FLAGS="-DUSE_BUNDLED_LUA=ON -DUSE_BUNDLED_LUAJIT=OFF"
- ;;
- *)
- make -j"${SNAPCRAFT_PARALLEL_BUILD_COUNT}" \
- CMAKE_BUILD_TYPE=RelWithDebInfo \
- CMAKE_INSTALL_PREFIX=/usr
- ;;
- esac
- make DESTDIR="$SNAPCRAFT_PART_INSTALL" install
- # Fix Desktop file
- sed -i 's|^Exec=nvim|Exec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
- sed -i 's|^TryExec=nvim|TryExec=/snap/bin/nvim.nvim|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
- sed -i 's|^Icon=.*|Icon=${SNAP}/usr/share/icons/hicolor/128x128/apps/nvim.png|' ${SNAPCRAFT_PART_INSTALL}/usr/share/applications/nvim.desktop
- build-packages:
- - ninja-build
- - libtool
- - libtool-bin
- - autoconf
- - automake
- - cmake
- - gawk
- - g++
- - git
- - gettext
- - pkg-config
- - unzip
- - wget
- prime:
- - -usr/share/man
-
diff --git a/src/.asan-blacklist b/src/.asan-blacklist
deleted file mode 100644
index 928d81bd5a..0000000000
--- a/src/.asan-blacklist
+++ /dev/null
@@ -1,3 +0,0 @@
-# multiqueue.h pointer arithmetic is not accepted by asan
-fun:multiqueue_node_data
-fun:tv_dict_watcher_node_data
diff --git a/src/bit.c b/src/bit.c
new file mode 100644
index 0000000000..85d2414582
--- /dev/null
+++ b/src/bit.c
@@ -0,0 +1,191 @@
+/*
+** Lua BitOp -- a bit operations library for Lua 5.1/5.2.
+** http://bitop.luajit.org/
+**
+** Copyright (C) 2008-2012 Mike Pall. All rights reserved.
+**
+** Permission is hereby granted, free of charge, to any person obtaining
+** a copy of this software and associated documentation files (the
+** "Software"), to deal in the Software without restriction, including
+** without limitation the rights to use, copy, modify, merge, publish,
+** distribute, sublicense, and/or sell copies of the Software, and to
+** permit persons to whom the Software is furnished to do so, subject to
+** the following conditions:
+**
+** The above copyright notice and this permission notice shall be
+** included in all copies or substantial portions of the Software.
+**
+** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+**
+** [ MIT license: http://www.opensource.org/licenses/mit-license.php ]
+*/
+
+#define LUA_BITOP_VERSION "1.0.2"
+
+#define LUA_LIB
+#include "lua.h"
+#include "lauxlib.h"
+
+#include "bit.h"
+
+#ifdef _MSC_VER
+/* MSVC is stuck in the last century and doesn't have C99's stdint.h. */
+typedef __int32 int32_t;
+typedef unsigned __int32 uint32_t;
+typedef unsigned __int64 uint64_t;
+#else
+#include <stdint.h>
+#endif
+
+typedef int32_t SBits;
+typedef uint32_t UBits;
+
+typedef union {
+ lua_Number n;
+#ifdef LUA_NUMBER_DOUBLE
+ uint64_t b;
+#else
+ UBits b;
+#endif
+} BitNum;
+
+/* Convert argument to bit type. */
+static UBits barg(lua_State *L, int idx)
+{
+ BitNum bn;
+ UBits b;
+#if LUA_VERSION_NUM < 502
+ bn.n = lua_tonumber(L, idx);
+#else
+ bn.n = luaL_checknumber(L, idx);
+#endif
+#if defined(LUA_NUMBER_DOUBLE)
+ bn.n += 6755399441055744.0; /* 2^52+2^51 */
+#ifdef SWAPPED_DOUBLE
+ b = (UBits)(bn.b >> 32);
+#else
+ b = (UBits)bn.b;
+#endif
+#elif defined(LUA_NUMBER_INT) || defined(LUA_NUMBER_LONG) || \
+ defined(LUA_NUMBER_LONGLONG) || defined(LUA_NUMBER_LONG_LONG) || \
+ defined(LUA_NUMBER_LLONG)
+ if (sizeof(UBits) == sizeof(lua_Number))
+ b = bn.b;
+ else
+ b = (UBits)(SBits)bn.n;
+#elif defined(LUA_NUMBER_FLOAT)
+#error "A 'float' lua_Number type is incompatible with this library"
+#else
+#error "Unknown number type, check LUA_NUMBER_* in luaconf.h"
+#endif
+#if LUA_VERSION_NUM < 502
+ if (b == 0 && !lua_isnumber(L, idx)) {
+ luaL_typerror(L, idx, "number");
+ }
+#endif
+ return b;
+}
+
+/* Return bit type. */
+#define BRET(b) lua_pushnumber(L, (lua_Number)(SBits)(b)); return 1;
+
+static int bit_tobit(lua_State *L) { BRET(barg(L, 1)) }
+static int bit_bnot(lua_State *L) { BRET(~barg(L, 1)) }
+
+#define BIT_OP(func, opr) \
+ static int func(lua_State *L) { int i; UBits b = barg(L, 1); \
+ for (i = lua_gettop(L); i > 1; i--) b opr barg(L, i); BRET(b) }
+BIT_OP(bit_band, &=)
+BIT_OP(bit_bor, |=)
+BIT_OP(bit_bxor, ^=)
+
+#define bshl(b, n) (b << n)
+#define bshr(b, n) (b >> n)
+#define bsar(b, n) ((SBits)b >> n)
+#define brol(b, n) ((b << n) | (b >> (32-n)))
+#define bror(b, n) ((b << (32-n)) | (b >> n))
+#define BIT_SH(func, fn) \
+ static int func(lua_State *L) { \
+ UBits b = barg(L, 1); UBits n = barg(L, 2) & 31; BRET(fn(b, n)) }
+BIT_SH(bit_lshift, bshl)
+BIT_SH(bit_rshift, bshr)
+BIT_SH(bit_arshift, bsar)
+BIT_SH(bit_rol, brol)
+BIT_SH(bit_ror, bror)
+
+static int bit_bswap(lua_State *L)
+{
+ UBits b = barg(L, 1);
+ b = (b >> 24) | ((b >> 8) & 0xff00) | ((b & 0xff00) << 8) | (b << 24);
+ BRET(b)
+}
+
+static int bit_tohex(lua_State *L)
+{
+ UBits b = barg(L, 1);
+ SBits n = lua_isnone(L, 2) ? 8 : (SBits)barg(L, 2);
+ const char *hexdigits = "0123456789abcdef";
+ char buf[8];
+ int i;
+ if (n < 0) { n = -n; hexdigits = "0123456789ABCDEF"; }
+ if (n > 8) n = 8;
+ for (i = (int)n; --i >= 0; ) { buf[i] = hexdigits[b & 15]; b >>= 4; }
+ lua_pushlstring(L, buf, (size_t)n);
+ return 1;
+}
+
+static const struct luaL_Reg bit_funcs[] = {
+ { "tobit", bit_tobit },
+ { "bnot", bit_bnot },
+ { "band", bit_band },
+ { "bor", bit_bor },
+ { "bxor", bit_bxor },
+ { "lshift", bit_lshift },
+ { "rshift", bit_rshift },
+ { "arshift", bit_arshift },
+ { "rol", bit_rol },
+ { "ror", bit_ror },
+ { "bswap", bit_bswap },
+ { "tohex", bit_tohex },
+ { NULL, NULL }
+};
+
+/* Signed right-shifts are implementation-defined per C89/C99.
+** But the de facto standard are arithmetic right-shifts on two's
+** complement CPUs. This behaviour is required here, so test for it.
+*/
+#define BAD_SAR (bsar(-8, 2) != (SBits)-2)
+
+LUALIB_API int luaopen_bit(lua_State *L)
+{
+ UBits b;
+ lua_pushnumber(L, (lua_Number)1437217655L);
+ b = barg(L, -1);
+ if (b != (UBits)1437217655L || BAD_SAR) { /* Perform a simple self-test. */
+ const char *msg = "compiled with incompatible luaconf.h";
+#ifdef LUA_NUMBER_DOUBLE
+#ifdef _WIN32
+ if (b == (UBits)1610612736L)
+ msg = "use D3DCREATE_FPU_PRESERVE with DirectX";
+#endif
+ if (b == (UBits)1127743488L)
+ msg = "not compiled with SWAPPED_DOUBLE";
+#endif
+ if (BAD_SAR)
+ msg = "arithmetic right-shift broken";
+ luaL_error(L, "bit library self-test failed (%s)", msg);
+ }
+#if LUA_VERSION_NUM < 502
+ luaL_register(L, "bit", bit_funcs);
+#else
+ luaL_newlib(L, bit_funcs);
+#endif
+ return 1;
+}
+
diff --git a/src/bit.h b/src/bit.h
new file mode 100644
index 0000000000..7e54e6d519
--- /dev/null
+++ b/src/bit.h
@@ -0,0 +1,3 @@
+#include <lua.h>
+
+int luaopen_bit(lua_State *L);
diff --git a/src/cjson/fpconv.c b/src/cjson/fpconv.c
index b2f7a214c2..0ef7f18cbe 100644
--- a/src/cjson/fpconv.c
+++ b/src/cjson/fpconv.c
@@ -202,7 +202,7 @@ int fpconv_g_fmt(char *str, double num, int precision)
return len;
}
-void fpconv_init()
+void fpconv_init(void)
{
fpconv_update_locale();
}
diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c
index c243f93c05..6af43d8eb7 100644
--- a/src/cjson/lua_cjson.c
+++ b/src/cjson/lua_cjson.c
@@ -1,3 +1,5 @@
+// Upstream: https://github.com/openresty/lua-cjson/blob/master/lua_cjson.c
+
/* Lua CJSON - JSON support for Lua
*
* Copyright (c) 2010-2012 Mark Pulford <mark@kyne.com.au>
@@ -252,6 +254,7 @@ static json_config_t *json_fetch_config(lua_State *l)
/* Ensure the correct number of arguments have been provided.
* Pad with nil to allow other functions to simply check arg[i]
* to find whether an argument was provided */
+/*
static json_config_t *json_arg_init(lua_State *l, int args)
{
luaL_argcheck(l, lua_gettop(l) <= args, args + 1,
@@ -262,8 +265,10 @@ static json_config_t *json_arg_init(lua_State *l, int args)
return json_fetch_config(l);
}
+*/
/* Process integer options for configuration functions */
+/*
static int json_integer_option(lua_State *l, int optindex, int *setting,
int min, int max)
{
@@ -281,8 +286,10 @@ static int json_integer_option(lua_State *l, int optindex, int *setting,
return 1;
}
+*/
/* Process enumerated arguments for a configuration function */
+/*
static int json_enum_option(lua_State *l, int optindex, int *setting,
const char **options, int bool_true)
{
@@ -307,11 +314,13 @@ static int json_enum_option(lua_State *l, int optindex, int *setting,
return 1;
}
+*/
/* Configures handling of extremely sparse arrays:
* convert: Convert extremely sparse arrays into objects? Otherwise error.
* ratio: 0: always allow sparse; 1: never allow sparse; >1: use ratio
* safe: Always use an array when the max index <= safe */
+/*
static int json_cfg_encode_sparse_array(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 3);
@@ -322,42 +331,52 @@ static int json_cfg_encode_sparse_array(lua_State *l)
return 3;
}
+*/
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
+/*
static int json_cfg_encode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_max_depth, 1, INT_MAX);
}
+*/
/* Configures the maximum number of nested arrays/objects allowed when
* encoding */
+/*
static int json_cfg_decode_max_depth(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->decode_max_depth, 1, INT_MAX);
}
+*/
/* Configures number precision when converting doubles to text */
+/*
static int json_cfg_encode_number_precision(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_integer_option(l, 1, &cfg->encode_number_precision, 1, 16);
}
+*/
/* Configures how to treat empty table when encode lua table */
+/*
static int json_cfg_encode_empty_table_as_object(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
return json_enum_option(l, 1, &cfg->encode_empty_table_as_object, NULL, 1);
}
+*/
/* Configures how to decode arrays */
+/*
static int json_cfg_decode_array_with_array_mt(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
@@ -366,8 +385,10 @@ static int json_cfg_decode_array_with_array_mt(lua_State *l)
return 1;
}
+*/
/* Configures JSON encoding buffer persistence */
+/*
static int json_cfg_encode_keep_buffer(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
@@ -377,7 +398,7 @@ static int json_cfg_encode_keep_buffer(lua_State *l)
json_enum_option(l, 1, &cfg->encode_keep_buffer, NULL, 1);
- /* Init / free the buffer if the setting has changed */
+ // Init / free the buffer if the setting has changed
if (old_value ^ cfg->encode_keep_buffer) {
if (cfg->encode_keep_buffer)
strbuf_init(&cfg->encode_buf, 0);
@@ -387,6 +408,7 @@ static int json_cfg_encode_keep_buffer(lua_State *l)
return 1;
}
+*/
#if defined(DISABLE_INVALID_NUMBERS) && !defined(USE_INTERNAL_FPCONV)
void json_verify_invalid_number_setting(lua_State *l, int *setting)
@@ -400,6 +422,7 @@ void json_verify_invalid_number_setting(lua_State *l, int *setting)
#define json_verify_invalid_number_setting(l, s) do { } while(0)
#endif
+/*
static int json_cfg_encode_invalid_numbers(lua_State *l)
{
static const char *options[] = { "off", "on", "null", NULL };
@@ -411,7 +434,9 @@ static int json_cfg_encode_invalid_numbers(lua_State *l)
return 1;
}
+*/
+/*
static int json_cfg_decode_invalid_numbers(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
@@ -422,7 +447,9 @@ static int json_cfg_decode_invalid_numbers(lua_State *l)
return 1;
}
+*/
+/*
static int json_cfg_encode_escape_forward_slash(lua_State *l)
{
int ret;
@@ -436,6 +463,7 @@ static int json_cfg_encode_escape_forward_slash(lua_State *l)
}
return ret;
}
+*/
static int json_destroy_config(lua_State *l)
{
@@ -1417,9 +1445,9 @@ static int json_decode(lua_State *l)
lua_getfield(l, 2, "luanil");
/* We only handle the luanil option for now */
- if (lua_isnil(l, -1)) {
- lua_pop(l, 1);
- break;
+ if (lua_isnil(l, -1)) {
+ lua_pop(l, 1);
+ break;
}
luaL_checktype(l, -1, LUA_TTABLE);
@@ -1534,6 +1562,8 @@ int lua_cjson_new(lua_State *l)
luaL_Reg reg[] = {
{ "encode", json_encode },
{ "decode", json_decode },
+ // Nvim: don't expose options which cause global side-effects.
+ /*
{ "encode_empty_table_as_object", json_cfg_encode_empty_table_as_object },
{ "decode_array_with_array_mt", json_cfg_decode_array_with_array_mt },
{ "encode_sparse_array", json_cfg_encode_sparse_array },
@@ -1544,6 +1574,7 @@ int lua_cjson_new(lua_State *l)
{ "encode_invalid_numbers", json_cfg_encode_invalid_numbers },
{ "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
{ "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash },
+ */
{ "new", lua_cjson_new },
{ NULL, NULL }
};
@@ -1589,23 +1620,31 @@ int lua_cjson_new(lua_State *l)
json_create_config(l);
compat_luaL_setfuncs(l, reg, 1);
- /* Set cjson.null */
+ // Nvim: don't expose "null", it is identical to vim.NIL.
+ /*
nlua_pushref(l, nlua_get_nil_ref(l));
lua_setfield(l, -2, "null");
+ */
- /* Set cjson.empty_array_mt */
+ // Nvim: don't expose empty_array_mt.
+ /*
lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array));
lua_rawget(l, LUA_REGISTRYINDEX);
lua_setfield(l, -2, "empty_array_mt");
+ */
- /* Set cjson.array_mt */
+ // Nvim: don't expose array_mt.
+ /*
lua_pushlightuserdata(l, json_lightudata_mask(&json_array));
lua_rawget(l, LUA_REGISTRYINDEX);
lua_setfield(l, -2, "array_mt");
+ */
- /* Set cjson.empty_array */
+ // Nvim: don't expose empty_array.
+ /*
lua_pushlightuserdata(l, json_lightudata_mask(&json_array));
lua_setfield(l, -2, "empty_array");
+ */
/* Set module name / version fields */
lua_pushliteral(l, CJSON_MODNAME);
diff --git a/src/clint.py b/src/clint.py
index 155f5f2ce5..1f588322f3 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -42,11 +42,10 @@ import copy
import getopt
import os
import re
-import sre_compile
import string
import sys
import json
-import collections # for defaultdict
+import collections
_USAGE = """
@@ -150,10 +149,9 @@ Syntax: clint.py [--verbose=#] [--output=vs7] [--filter=-x,+y,...]
# If you add a new error message with a new category, add it to the list
# here! cpplint_unittest.py should tell you if you forget to do this.
_ERROR_CATEGORIES = [
- 'build/deprecated',
'build/endif_comment',
'build/header_guard',
- 'build/include_alpha',
+ 'build/include_defs',
'build/printf_format',
'build/storage_class',
'readability/bool',
@@ -170,14 +168,9 @@ _ERROR_CATEGORIES = [
'runtime/printf_format',
'runtime/threadsafe_fn',
'runtime/deprecated',
- 'syntax/parenthesis',
- 'whitespace/alignment',
- 'whitespace/braces',
'whitespace/comments',
'whitespace/indent',
- 'whitespace/newline',
'whitespace/operators',
- 'whitespace/parens',
'whitespace/todo',
'whitespace/cast',
]
@@ -186,7 +179,7 @@ _ERROR_CATEGORIES = [
# flag. By default all errors are on, so only add here categories that should be
# off by default (i.e., categories that must be enabled by the --filter= flags).
# All entries here should start with a '-' or '+', as in the --filter= flag.
-_DEFAULT_FILTERS = ['-build/include_alpha']
+_DEFAULT_FILTERS = []
# These constants define the current inline assembly state
_NO_ASM = 0 # Outside of inline assembly block
@@ -308,14 +301,14 @@ def Match(pattern, s):
# performance reasons; factoring it out into a separate function turns out
# to be noticeably expensive.
if pattern not in _regexp_compile_cache:
- _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ _regexp_compile_cache[pattern] = re.compile(pattern)
return _regexp_compile_cache[pattern].match(s)
def Search(pattern, s):
"""Searches the string for the pattern, caching the compiled regexp."""
if pattern not in _regexp_compile_cache:
- _regexp_compile_cache[pattern] = sre_compile.compile(pattern)
+ _regexp_compile_cache[pattern] = re.compile(pattern)
return _regexp_compile_cache[pattern].search(s)
@@ -481,38 +474,6 @@ def _SetFilters(filters):
_cpplint_state.SetFilters(filters)
-class _FunctionState:
-
- """Tracks current function name and the number of lines in its body."""
-
- _NORMAL_TRIGGER = 250 # for --v=0, 500 for --v=1, etc.
- _TEST_TRIGGER = 400 # about 50% more than _NORMAL_TRIGGER.
-
- def __init__(self):
- self.in_a_function = False
- self.lines_in_function = 0
- self.current_function = ''
-
- def Begin(self, function_name):
- """Start analyzing function body.
-
- Args:
- function_name: The name of the function being tracked.
- """
- self.in_a_function = True
- self.lines_in_function = 0
- self.current_function = function_name
-
- def Count(self):
- """Count line in current function body."""
- if self.in_a_function:
- self.lines_in_function += 1
-
- def End(self):
- """Stop analyzing function body."""
- self.in_a_function = False
-
-
class FileInfo:
"""Provides utility functions for filenames.
@@ -549,29 +510,6 @@ class FileInfo:
# Don't know what to do; header guard warnings may be wrong...
return fullname
- def Split(self):
- """Splits the file into the directory, basename, and extension.
-
- For 'chrome/browser/browser.cc', Split() would
- return ('chrome/browser', 'browser', '.cc')
-
- Returns:
- A tuple of (directory, basename, extension).
- """
-
- googlename = self.RelativePath()
- project, rest = os.path.split(googlename)
- return (project,) + os.path.splitext(rest)
-
- def BaseName(self):
- """File base name - text after the final slash, before final period."""
- return self.Split()[1]
-
- def Extension(self):
- """File extension - text following the final period."""
- return self.Split()[2]
-
-
def _ShouldPrintError(category, confidence, linenum):
"""If confidence >= verbose, category passes filter and isn't suppressed."""
@@ -925,110 +863,8 @@ def CloseExpression(clean_lines, linenum, pos):
return (line, clean_lines.NumLines(), -1)
-def FindStartOfExpressionInLine(line, endpos, depth, startchar, endchar):
- """Find position at the matching startchar.
-
- This is almost the reverse of FindEndOfExpressionInLine, but note
- that the input position and returned position differs by 1.
-
- Args:
- line: a CleansedLines line.
- endpos: start searching at this position.
- depth: nesting level at endpos.
- startchar: expression opening character.
- endchar: expression closing character.
-
- Returns:
- On finding matching startchar: (index at matching startchar, 0)
- Otherwise: (-1, new depth at beginning of this line)
- """
- for i in range(endpos, -1, -1):
- if line[i] == endchar:
- depth += 1
- elif line[i] == startchar:
- depth -= 1
- if depth == 0:
- return (i, 0)
- return (-1, depth)
-
-
-def ReverseCloseExpression(clean_lines, linenum, pos):
- """If input points to ) or } or ] or >, finds the position that opens it.
-
- If lines[linenum][pos] points to a ')' or '}' or ']' or '>', finds the
- linenum/pos that correspond to the opening of the expression.
-
- Args:
- clean_lines: A CleansedLines instance containing the file.
- linenum: The number of the line to check.
- pos: A position on the line.
-
- Returns:
- A tuple (line, linenum, pos) pointer *at* the opening brace, or
- (line, 0, -1) if we never find the matching opening brace. Note
- we ignore strings and comments when matching; and the line we
- return is the 'cleansed' line at linenum.
- """
- line = clean_lines.elided[linenum]
- endchar = line[pos]
- startchar = None
- if endchar not in ')}]>':
- return (line, 0, -1)
- if endchar == ')':
- startchar = '('
- if endchar == ']':
- startchar = '['
- if endchar == '}':
- startchar = '{'
- if endchar == '>':
- startchar = '<'
-
- # Check last line
- (start_pos, num_open) = FindStartOfExpressionInLine(
- line, pos, 0, startchar, endchar)
- if start_pos > -1:
- return (line, linenum, start_pos)
-
- # Continue scanning backward
- while linenum > 0:
- linenum -= 1
- line = clean_lines.elided[linenum]
- (start_pos, num_open) = FindStartOfExpressionInLine(
- line, len(line) - 1, num_open, startchar, endchar)
- if start_pos > -1:
- return (line, linenum, start_pos)
-
- # Did not find startchar before beginning of file, give up
- return (line, 0, -1)
-
-
-def GetHeaderGuardCPPVariable(filename):
- """Returns the CPP variable that should be used as a header guard.
-
- Args:
- filename: The name of a C++ header file.
-
- Returns:
- The CPP variable that should be used as a header guard in the
- named file.
-
- """
-
- # Restores original filename in case that cpplint is invoked from Emacs's
- # flymake.
- filename = re.sub(r'_flymake\.h$', '.h', filename)
- filename = re.sub(r'/\.flymake/([^/]*)$', r'/\1', filename)
-
- fileinfo = FileInfo(filename)
- file_path_from_root = fileinfo.RelativePath()
- return 'NVIM_' + re.sub(r'[-./\s]', '_', file_path_from_root).upper()
-
-
def CheckForHeaderGuard(filename, lines, error):
- """Checks that the file contains a header guard.
-
- Logs an error if no #ifndef header guard is present. For other
- headers, checks that the full pathname is used.
+ """Checks that the file contains "#pragma once".
Args:
filename: The name of the C++ header file.
@@ -1040,65 +876,133 @@ def CheckForHeaderGuard(filename, lines, error):
}:
return
- cppvar = GetHeaderGuardCPPVariable(filename)
-
- ifndef = None
- ifndef_linenum = 0
- define = None
- endif = None
- endif_linenum = 0
- for linenum, line in enumerate(lines):
- linesplit = line.split()
- if len(linesplit) >= 2:
- # find the first occurrence of #ifndef and #define, save arg
- if not ifndef and linesplit[0] == '#ifndef':
- # set ifndef to the header guard presented on the #ifndef line.
- ifndef = linesplit[1]
- ifndef_linenum = linenum
- if not define and linesplit[0] == '#define':
- define = linesplit[1]
- # find the last occurrence of #endif, save entire line
- if line.startswith('#endif'):
- endif = line
- endif_linenum = linenum
-
- if not ifndef:
- error(filename, 0, 'build/header_guard', 5,
- 'No #ifndef header guard found, suggested CPP variable is: %s' %
- cppvar)
- return
-
- if not define:
+ if "#pragma once" not in lines:
error(filename, 0, 'build/header_guard', 5,
- 'No #define header guard found, suggested CPP variable is: %s' %
- cppvar)
- return
+ 'No "#pragma once" found in header')
- # The guard should be PATH_FILE_H_, but we also allow PATH_FILE_H__
- # for backward compatibility.
- if ifndef != cppvar:
- error_level = 0
- if ifndef != cppvar + '_':
- error_level = 5
+def CheckIncludes(filename, lines, error):
+ """Checks that headers only include _defs headers
- ParseNolintSuppressions(lines[ifndef_linenum], ifndef_linenum)
- error(filename, ifndef_linenum, 'build/header_guard', error_level,
- '#ifndef header guard has wrong style, please use: %s' % cppvar)
-
- if define != ifndef:
- error(filename, 0, 'build/header_guard', 5,
- '#ifndef and #define don\'t match, suggested CPP variable is: %s'
- % cppvar)
+ Args:
+ filename: The name of the C++ header file.
+ lines: An array of strings, each representing a line of the file.
+ error: The function to call with any errors found.
+ """
+ if filename.endswith('.c.h') or filename.endswith('.in.h') or FileInfo(filename).RelativePath() in {
+ 'func_attr.h',
+ 'os/pty_process.h',
+ }:
return
- if endif != ('#endif // %s' % cppvar):
- error_level = 0
- if endif != ('#endif // %s' % (cppvar + '_')):
- error_level = 5
+ # These should be synced with the ignored headers in the `iwyu` target in
+ # the Makefile.
+ check_includes_ignore = [
+ "src/nvim/api/extmark.h",
+ "src/nvim/api/private/dispatch.h",
+ "src/nvim/api/private/helpers.h",
+ "src/nvim/api/private/validate.h",
+ "src/nvim/api/ui.h",
+ "src/nvim/ascii_defs.h",
+ "src/nvim/assert_defs.h",
+ "src/nvim/autocmd.h",
+ "src/nvim/autocmd_defs.h",
+ "src/nvim/buffer.h",
+ "src/nvim/buffer_defs.h",
+ "src/nvim/channel.h",
+ "src/nvim/charset.h",
+ "src/nvim/cmdexpand.h",
+ "src/nvim/cmdhist.h",
+ "src/nvim/decoration.h",
+ "src/nvim/diff.h",
+ "src/nvim/drawline.h",
+ "src/nvim/drawscreen.h",
+ "src/nvim/eval.h",
+ "src/nvim/eval/encode.h",
+ "src/nvim/eval/typval.h",
+ "src/nvim/eval/typval_defs.h",
+ "src/nvim/eval/userfunc.h",
+ "src/nvim/eval/window.h",
+ "src/nvim/event/libuv_process.h",
+ "src/nvim/event/loop.h",
+ "src/nvim/event/multiqueue.h",
+ "src/nvim/event/process.h",
+ "src/nvim/event/rstream.h",
+ "src/nvim/event/signal.h",
+ "src/nvim/event/socket.h",
+ "src/nvim/event/stream.h",
+ "src/nvim/event/time.h",
+ "src/nvim/event/wstream.h",
+ "src/nvim/ex_cmds.h",
+ "src/nvim/ex_cmds_defs.h",
+ "src/nvim/ex_docmd.h",
+ "src/nvim/extmark.h",
+ "src/nvim/file_search.h",
+ "src/nvim/fileio.h",
+ "src/nvim/fold.h",
+ "src/nvim/garray.h",
+ "src/nvim/getchar.h",
+ "src/nvim/globals.h",
+ "src/nvim/grid.h",
+ "src/nvim/highlight.h",
+ "src/nvim/highlight_group.h",
+ "src/nvim/input.h",
+ "src/nvim/insexpand.h",
+ "src/nvim/keycodes.h",
+ "src/nvim/log.h",
+ "src/nvim/lua/executor.h",
+ "src/nvim/main.h",
+ "src/nvim/mark.h",
+ "src/nvim/mouse.h",
+ "src/nvim/move.h",
+ "src/nvim/msgpack_rpc/channel.h",
+ "src/nvim/msgpack_rpc/channel_defs.h",
+ "src/nvim/msgpack_rpc/helpers.h",
+ "src/nvim/msgpack_rpc/unpacker.h",
+ "src/nvim/option.h",
+ "src/nvim/os/fileio.h",
+ "src/nvim/os/input.h",
+ "src/nvim/os/pty_conpty_win.h",
+ "src/nvim/os/pty_process_unix.h",
+ "src/nvim/os/pty_process_win.h",
+ "src/nvim/path.h",
+ "src/nvim/plines.h",
+ "src/nvim/popupmenu.h",
+ "src/nvim/search.h",
+ "src/nvim/spell.h",
+ "src/nvim/syntax.h",
+ "src/nvim/textobject.h",
+ "src/nvim/tui/input.h",
+ "src/nvim/tui/tui.h",
+ "src/nvim/ui.h",
+ "src/nvim/ui_client.h",
+ "src/nvim/ui_compositor.h",
+ "src/nvim/viml/parser/expressions.h",
+ "src/nvim/viml/parser/parser.h",
+ "src/nvim/window.h",
+ ]
+
+ skip_headers = [
+ "klib/kvec.h",
+ "klib/klist.h",
+ "auto/config.h",
+ "nvim/func_attr.h"
+ ]
+
+ for i in check_includes_ignore:
+ if filename.endswith(i):
+ return
- ParseNolintSuppressions(lines[endif_linenum], endif_linenum)
- error(filename, endif_linenum, 'build/header_guard', error_level,
- '#endif line should be "#endif // %s"' % cppvar)
+ for i, line in enumerate(lines):
+ matched = Match(r'#\s*include\s*"([^"]*)"', line)
+ if matched:
+ name = matched.group(1)
+ if name in skip_headers:
+ continue
+ if (not name.endswith('.h.generated.h') and
+ not name.endswith('_defs.h') and
+ not name.endswith('/defs.h')):
+ error(filename, i, 'build/include_defs', 5,
+ 'Headers should not include non-"_defs" headers')
def CheckForBadCharacters(filename, lines, error):
@@ -1545,82 +1449,6 @@ def CheckForNonStandardConstructs(filename, clean_lines, linenum, error):
error(filename, linenum, 'build/endif_comment', 5,
'Uncommented text after #endif is non-standard. Use a comment.')
- if Search(r'(\w+|[+-]?\d+(\.\d*)?)\s*(<|>)\?=?\s*(\w+|[+-]?\d+)(\.\d*)?',
- line):
- error(filename, linenum, 'build/deprecated', 3,
- '>? and <? (max and min) operators are'
- ' non-standard and deprecated.')
-
-
-def CheckSpacingForFunctionCall(filename, line, linenum, error):
- """Checks for the correctness of various spacing around function calls.
-
- Args:
- filename: The name of the current file.
- line: The text of the line to check.
- linenum: The number of the line to check.
- error: The function to call with any errors found.
- """
-
- # Since function calls often occur inside if/for/while/switch
- # expressions - which have their own, more liberal conventions - we
- # first see if we should be looking inside such an expression for a
- # function call, to which we can apply more strict standards.
- fncall = line # if there's no control flow construct, look at whole line
- for pattern in (r'\bif\s*\((.*)\)\s*{',
- r'\bfor\s*\((.*)\)\s*{',
- r'\bwhile\s*\((.*)\)\s*[{;]',
- r'\bswitch\s*\((.*)\)\s*{'):
- match = Search(pattern, line)
- if match:
- # look inside the parens for function calls
- fncall = match.group(1)
- break
-
- # Except in if/for/while/switch/case, there should never be space
- # immediately inside parens (eg "f( 3, 4 )"). We make an exception
- # for nested parens ( (a+b) + c ). Likewise, there should never be
- # a space before a ( when it's a function argument. I assume it's a
- # function argument when the char before the whitespace is legal in
- # a function name (alnum + _) and we're not starting a macro. Also ignore
- # pointers and references to arrays and functions coz they're too tricky:
- # we use a very simple way to recognize these:
- # " (something)(maybe-something)" or
- # " (something)(maybe-something," or
- # " (something)[something]"
- # Note that we assume the contents of [] to be short enough that
- # they'll never need to wrap.
- if ( # Ignore control structures.
- not Search(r'\b(if|for|while|switch|case|return|sizeof)\b', fncall) and
- # Ignore pointers/references to functions.
- not Search(r' \([^)]+\)\([^)]*(\)|,$)', fncall) and
- # Ignore pointers/references to arrays.
- not Search(r' \([^)]+\)\[[^\]]+\]', fncall)):
- # a ( used for a fn call
- if Search(r'\w\s*\(\s(?!\s*\\$)', fncall):
- error(filename, linenum, 'whitespace/parens', 4,
- 'Extra space after ( in function call')
- elif Search(r'\(\s+(?!(\s*\\)|\()', fncall):
- error(filename, linenum, 'whitespace/parens', 2,
- 'Extra space after (')
- if (Search(r'\w\s+\(', fncall) and
- not Search(r'#\s*define|typedef', fncall) and
- not Search(r'\w\s+\((\w+::)*\*\w+\)\(', fncall)):
- error(filename, linenum, 'whitespace/parens', 4,
- 'Extra space before ( in function call')
- # If the ) is followed only by a newline or a { + newline, assume it's
- # part of a control statement (if/while/etc), and don't complain
- if Search(r'[^)]\s+\)\s*[^{\s]', fncall):
- # If the closing parenthesis is preceded by only whitespaces,
- # try to give a more descriptive error message.
- if Search(r'^\s+\)', fncall):
- error(filename, linenum, 'whitespace/parens', 2,
- 'Closing ) should be moved to the previous line')
- else:
- error(filename, linenum, 'whitespace/parens', 2,
- 'Extra space before )')
-
-
def IsBlankLine(line):
"""Returns true if the given line is blank.
@@ -1636,75 +1464,6 @@ def IsBlankLine(line):
return not line or line.isspace()
-def CheckForFunctionLengths(filename, clean_lines, linenum,
- function_state, error):
- """Reports for long function bodies.
-
- For an overview why this is done, see:
- http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Write_Short_Functions
-
- Uses a simplistic algorithm assuming other style guidelines
- (especially spacing) are followed.
- Only checks unindented functions, so class members are unchecked.
- Trivial bodies are unchecked, so constructors with huge initializer lists
- may be missed.
- Blank/comment lines are not counted so as to avoid encouraging the removal
- of vertical space and comments just to get through a lint check.
- NOLINT *on the last line of a function* disables this check.
-
- Args:
- filename: The name of the current file.
- clean_lines: A CleansedLines instance containing the file.
- linenum: The number of the line to check.
- function_state: Current function name and lines in body so far.
- error: The function to call with any errors found.
- """
- lines = clean_lines.lines
- line = lines[linenum]
- joined_line = ''
-
- starting_func = False
- regexp = r'(\w(\w|::|\*|\&|\s)*)\(' # decls * & space::name( ...
- match_result = Match(regexp, line)
- if match_result:
- # If the name is all caps and underscores, figure it's a macro and
- # ignore it, unless it's TEST or TEST_F.
- function_name = match_result.group(1).split()[-1]
- if function_name == 'TEST' or function_name == 'TEST_F' or (
- not Match(r'[A-Z_]+$', function_name)):
- starting_func = True
-
- if starting_func:
- body_found = False
- for start_linenum in range(linenum, clean_lines.NumLines()):
- start_line = lines[start_linenum]
- joined_line += ' ' + start_line.lstrip()
- # Declarations and trivial functions
- if Search(r'(;|})', start_line):
- body_found = True
- break # ... ignore
- elif Search(r'{', start_line):
- body_found = True
- function = Search(r'((\w|:)*)\(', line).group(1)
- if Match(r'TEST', function): # Handle TEST... macros
- parameter_regexp = Search(r'(\(.*\))', joined_line)
- if parameter_regexp: # Ignore bad syntax
- function += parameter_regexp.group(1)
- else:
- function += '()'
- function_state.Begin(function)
- break
- if not body_found:
- # No body for the function (or evidence of a non-function) was
- # found.
- error(filename, linenum, 'readability/fn_size', 5,
- 'Lint failed to find start of function body.')
- elif Match(r'^\}\s*$', line): # function end
- function_state.End()
- elif not Match(r'^\s*$', line):
- function_state.Count() # Count non-blank/non-comment lines.
-
-
_RE_PATTERN_TODO = re.compile(r'^//(\s*)TODO(\(.+?\))?(:?)(\s|$)?')
@@ -1727,9 +1486,7 @@ def CheckComment(comment, filename, linenum, error):
username = match.group(2)
if not username:
- error(filename, linenum, 'readability/todo', 2,
- 'Missing username in TODO; it should look like '
- '"// TODO(my_username): Stuff."')
+ return
colon = match.group(3)
if not colon:
@@ -1871,89 +1628,6 @@ def FindPreviousMatchingAngleBracket(clean_lines, linenum, init_prefix):
# Exhausted all earlier lines and still no matching angle bracket.
return False
-
-def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0):
- """Checks for the correctness of alignment inside expressions
-
- Args:
- filename: The name of the current file.
- clean_lines: A CleansedLines instance containing the file.
- linenum: The number of the line to check.
- error: The function to call with any errors found.
- startpos: Position where to start searching for expression start.
- """
- level_starts = {}
- line = clean_lines.elided_with_space_strings[linenum]
- prev_line_start = Search(r'\S', line).start()
- depth_line_starts = {}
- pos = min([
- idx
- for idx in (
- line.find(k, startpos)
- for k in BRACES
- if k != '{'
- )
- if idx >= 0
- ] + [len(line) + 1])
- if pos == len(line) + 1:
- return
- ignore_error_levels = set()
- firstlinenum = linenum
- for linenum, pos, brace, depth in GetExprBracesPosition(
- clean_lines, linenum, pos
- ):
- line = clean_lines.elided_with_space_strings[linenum]
- if depth is None:
- if pos < len(line) - 1:
- CheckExpressionAlignment(filename, clean_lines, linenum, error,
- pos + 1)
- return
- elif depth <= 0:
- error(filename, linenum, 'syntax/parenthesis', 4,
- 'Unbalanced parenthesis')
- return
- if brace == 's':
- assert firstlinenum != linenum
- if level_starts[depth][1]:
- if line[pos] == BRACES[depth_line_starts[depth][1]]:
- if pos != depth_line_starts[depth][0]:
- if depth not in ignore_error_levels:
- error(filename, linenum, 'whitespace/indent', 2,
- 'End of the inner expression should have '
- 'the same indent as start')
- else:
- if (pos != level_starts[depth][0] + 1
- + (level_starts[depth][2] == '{')):
- if depth not in ignore_error_levels:
- error(filename, linenum, 'whitespace/alignment', 2,
- ('Inner expression should be aligned '
- 'as opening brace + 1 (+ 2 in case of {{). '
- 'Relevant opening is on line {0!r}').format(
- level_starts[depth][3]))
- prev_line_start = pos
- elif brace == 'e':
- pass
- else:
- opening = brace in BRACES
- if opening:
- # Only treat {} as part of the expression if it is preceded by
- # "=" (brace initializer) or "(type)" (construct like (struct
- # foo) { ... }).
- if brace == '{' and not (Search(
- r'(?:= *|\((?:struct )?\w+(\s*\[\w*\])?\)) *$',
- line[:pos])
- ):
- ignore_error_levels.add(depth)
- line_ended_with_opening = (
- pos == len(line) - 2 * (line.endswith(' \\')) - 1)
- level_starts[depth] = (pos, line_ended_with_opening, brace,
- linenum)
- if line_ended_with_opening:
- depth_line_starts[depth] = (prev_line_start, brace)
- else:
- del level_starts[depth]
-
-
def CheckSpacing(filename, clean_lines, linenum, error):
"""Checks for the correctness of various spacing issues in the code.
@@ -2067,8 +1741,7 @@ def CheckSpacing(filename, clean_lines, linenum, error):
# sometimes people put non-spaces on one side when aligning ='s among
# many lines (not that this is behavior that I approve of...)
if Search(r'[\w.]=[\w.]', line) and not Search(r'\b(if|while) ', line):
- error(filename, linenum, 'whitespace/operators', 4,
- 'Missing spaces around =')
+ return
# It's ok not to have spaces around binary operators like + - * /, but if
# there's too little whitespace, we get concerned. It's hard to tell,
@@ -2085,14 +1758,11 @@ def CheckSpacing(filename, clean_lines, linenum, error):
# check non-include lines for spacing around < and >.
match = Search(r'[^<>=!\s](==|!=|<=|>=)[^<>=!\s]', line)
if match:
- error(filename, linenum, 'whitespace/operators', 3,
- 'Missing spaces around %s' % match.group(1))
+ return
# Boolean operators should be placed on the next line.
if Search(r'(?:&&|\|\|)$', line):
- error(filename, linenum, 'whitespace/operators', 4,
- 'Boolean operator should be placed on the same line as the start '
- 'of its right operand')
+ return
# We allow no-spaces around << when used like this: 10<<20, but
# not otherwise (particularly, not when used as streams)
@@ -2114,8 +1784,7 @@ def CheckSpacing(filename, clean_lines, linenum, error):
match = Search(r'[^\s<]<([^\s=<].*)', reduced_line)
if (match and not FindNextMatchingAngleBracket(clean_lines, linenum,
match.group(1))):
- error(filename, linenum, 'whitespace/operators', 3,
- 'Missing spaces around <')
+ return
# Look for > that is not surrounded by spaces. Similar to the
# above, we only trigger if both sides are missing spaces to avoid
@@ -2124,8 +1793,7 @@ def CheckSpacing(filename, clean_lines, linenum, error):
if (match and
not FindPreviousMatchingAngleBracket(clean_lines, linenum,
match.group(1))):
- error(filename, linenum, 'whitespace/operators', 3,
- 'Missing spaces around >')
+ return
# We allow no-spaces around >> for almost anything. This is because
# C++11 allows ">>" to close nested templates, which accounts for
@@ -2163,78 +1831,33 @@ def CheckSpacing(filename, clean_lines, linenum, error):
if not (match.group(3) == ';' and
len(match.group(2)) == 1 + len(match.group(4)) or
not match.group(2) and Search(r'\bfor\s*\(.*; \)', line)):
- error(filename, linenum, 'whitespace/parens', 5,
- 'Mismatching spaces inside () in %s' % match.group(1))
+ return
if len(match.group(2)) not in [0, 1]:
- error(filename, linenum, 'whitespace/parens', 5,
- 'Should have zero or one spaces inside ( and ) in %s' %
- match.group(1))
-
- # Next we will look for issues with function calls.
- CheckSpacingForFunctionCall(filename, line, linenum, error)
+ return
# Check whether everything inside expressions is aligned correctly
if any(line.find(k) >= 0 for k in BRACES if k != '{'):
- CheckExpressionAlignment(filename, clean_lines, linenum, error)
+ return
# Except after an opening paren, or after another opening brace (in case of
# an initializer list, for instance), you should have spaces before your
# braces. And since you should never have braces at the beginning of a line,
# this is an easy test.
match = Match(r'^(.*[^ ({]){', line)
- if match:
- # Try a bit harder to check for brace initialization. This
- # happens in one of the following forms:
- # Constructor() : initializer_list_{} { ... }
- # Constructor{}.MemberFunction()
- # Type variable{};
- # FunctionCall(type{}, ...);
- # LastArgument(..., type{});
- # LOG(INFO) << type{} << " ...";
- # map_of_type[{...}] = ...;
- #
- # We check for the character following the closing brace, and
- # silence the warning if it's one of those listed above, i.e.
- # "{.;,)<]".
- #
- # To account for nested initializer list, we allow any number of
- # closing braces up to "{;,)<". We can't simply silence the
- # warning on first sight of closing brace, because that would
- # cause false negatives for things that are not initializer lists.
- # Silence this: But not this:
- # Outer{ if (...) {
- # Inner{...} if (...){ // Missing space before {
- # }; }
- #
- # There is a false negative with this approach if people inserted
- # spurious semicolons, e.g. "if (cond){};", but we will catch the
- # spurious semicolon with a separate check.
- (endline, endlinenum, endpos) = CloseExpression(
- clean_lines, linenum, len(match.group(1)))
- trailing_text = ''
- if endpos > -1:
- trailing_text = endline[endpos:]
- for offset in range(endlinenum + 1,
- min(endlinenum + 3, clean_lines.NumLines() - 1)):
- trailing_text += clean_lines.elided[offset]
# Make sure '} else {' has spaces.
if Search(r'}else', line):
- error(filename, linenum, 'whitespace/braces', 5,
- 'Missing space before else')
+ return
# You shouldn't have spaces before your brackets, except maybe after
# 'delete []' or 'new char * []'.
if Search(r'\w\s+\[', line):
- error(filename, linenum, 'whitespace/braces', 5,
- 'Extra space before [')
+ return
if Search(r'\{(?!\})\S', line):
- error(filename, linenum, 'whitespace/braces', 5,
- 'Missing space after {')
+ return
if Search(r'\S(?<!\{)\}', line):
- error(filename, linenum, 'whitespace/braces', 5,
- 'Missing space before }')
+ return
cast_line = re.sub(r'^# *define +\w+\([^)]*\)', '', line)
match = Search(r'(?<!\bkvec_t)'
@@ -2245,6 +1868,7 @@ def CheckSpacing(filename, clean_lines, linenum, error):
r'(?<!\bkbtree_t)'
r'(?<!\bkbitr_t)'
r'(?<!\bPMap)'
+ r'(?<!\bSet)'
r'(?<!\bArrayOf)'
r'(?<!\bDictionaryOf)'
r'(?<!\bDict)'
@@ -2349,30 +1973,6 @@ def CheckStyle(filename, clean_lines, linenum, error):
error: The function to call with any errors found.
"""
- # Don't use "elided" lines here, otherwise we can't check commented lines.
- # Don't want to use "raw" either, because we don't want to check inside
- # C++11 raw strings,
- raw_lines = clean_lines.lines_without_raw_strings
- line = raw_lines[linenum]
-
- # One or three blank spaces at the beginning of the line is weird; it's
- # hard to reconcile that with 2-space indents.
- # NOTE: here are the conditions rob pike used for his tests. Mine aren't
- # as sophisticated, but it may be worth becoming so:
- # RLENGTH==initial_spaces
- # if(RLENGTH > 20) complain = 0;
- # if(match($0, " +(error|private|public|protected):")) complain = 0;
- # if(match(prev, "&& *$")) complain = 0;
- # if(match(prev, "\\|\\| *$")) complain = 0;
- # if(match(prev, "[\",=><] *$")) complain = 0;
- # if(match($0, " <<")) complain = 0;
- # if(match(prev, " +for \\(")) complain = 0;
- # if(prevodd && match(prevprev, " +for \\(")) complain = 0;
- initial_spaces = 0
-
- while initial_spaces < len(line) and line[initial_spaces] == ' ':
- initial_spaces += 1
-
# Some more style checks
CheckBraces(filename, clean_lines, linenum, error)
CheckSpacing(filename, clean_lines, linenum, error)
@@ -2612,7 +2212,7 @@ def CheckLanguage(filename, clean_lines, linenum, error):
def ProcessLine(filename, clean_lines, line,
- function_state, nesting_state, error,
+ nesting_state, error,
extra_check_functions=[]):
"""Processes a single line in the file.
@@ -2621,8 +2221,6 @@ def ProcessLine(filename, clean_lines, line,
clean_lines : An array of strings, each representing a line of
the file, with comments stripped.
line : Number of line being processed.
- function_state : A _FunctionState instance which counts function
- lines, etc.
nesting_state : A _NestingState instance which maintains
information about the current stack of nested
blocks being parsed.
@@ -2639,7 +2237,6 @@ def ProcessLine(filename, clean_lines, line,
nesting_state.Update(clean_lines, line)
if nesting_state.stack and nesting_state.stack[-1].inline_asm != _NO_ASM:
return
- CheckForFunctionLengths(filename, clean_lines, line, function_state, error)
CheckForMultilineCommentsAndStrings(filename, clean_lines, line, error)
CheckForOldStyleComments(filename, init_lines[line], line, error)
CheckStyle(filename, clean_lines, line, error)
@@ -2670,7 +2267,6 @@ def ProcessFileData(filename, file_extension, lines, error,
lines = (['// marker so line numbers and indices both start at 1'] + lines +
['// marker so line numbers end in a known way'])
- function_state = _FunctionState()
nesting_state = _NestingState()
ResetNolintSuppressions()
@@ -2695,12 +2291,13 @@ def ProcessFileData(filename, file_extension, lines, error,
if file_extension == 'h':
CheckForHeaderGuard(filename, lines, error)
+ CheckIncludes(filename, lines, error)
RemoveMultiLineComments(filename, lines, error)
clean_lines = CleansedLines(lines, init_lines)
for line in range(clean_lines.NumLines()):
ProcessLine(filename, clean_lines, line,
- function_state, nesting_state, error,
+ nesting_state, error,
extra_check_functions)
# We check here rather than inside ProcessLine so that we see raw
@@ -2744,12 +2341,10 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]):
lines = codecs.open(
filename, 'r', 'utf8', 'replace').read().split('\n')
- carriage_return_found = False
# Remove trailing '\r'.
for linenum in range(len(lines)):
if lines[linenum].endswith('\r'):
lines[linenum] = lines[linenum].rstrip('\r')
- carriage_return_found = True
except OSError:
sys.stderr.write(
@@ -2768,13 +2363,6 @@ def ProcessFile(filename, vlevel, extra_check_functions=[]):
else:
ProcessFileData(filename, file_extension, lines, Error,
extra_check_functions)
- if carriage_return_found and os.linesep != '\r\n':
- # Use 0 for linenum since outputting only one error for potentially
- # several lines.
- Error(filename, 0, 'whitespace/newline', 1,
- 'One or more unexpected \\r (^M) found;'
- 'better to use only a \\n')
-
def PrintUsage(message):
"""Prints a brief usage string and exits, optionally with an error message.
@@ -2810,6 +2398,8 @@ def ParseArguments(args):
Returns:
The list of filenames to lint.
"""
+ opts = []
+ filenames = []
try:
(opts, filenames) = getopt.getopt(args, '', ['help',
'output=',
diff --git a/src/klib/khash.h b/src/klib/khash.h
deleted file mode 100644
index 57a41f9c13..0000000000
--- a/src/klib/khash.h
+++ /dev/null
@@ -1,730 +0,0 @@
-/* The MIT License
-
- Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
-
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
-
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
- BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
- ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
- */
-
-/*
- Example:
-
- #include "nvim/khash.h"
- KHASH_MAP_INIT_INT(32, char)
- int main() {
- int ret, is_missing;
- khiter_t k;
- khash_t(32) *h = kh_init(32);
- k = kh_put(32, h, 5, &ret);
- kh_value(h, k) = 10;
- k = kh_get(32, h, 10);
- is_missing = (k == kh_end(h));
- k = kh_get(32, h, 5);
- kh_del(32, h, k);
- for (k = kh_begin(h); k != kh_end(h); ++k)
- if (kh_exist(h, k)) kh_value(h, k) = 1;
- kh_destroy(32, h);
- return 0;
- }
- */
-
-/*
- 2013-05-02 (0.2.8):
-
- * Use quadratic probing. When the capacity is power of 2, stepping function
- i*(i+1)/2 guarantees to traverse each bucket. It is better than double
- hashing on cache performance and is more robust than linear probing.
-
- In theory, double hashing should be more robust than quadratic probing.
- However, my implementation is probably not for large hash tables, because
- the second hash function is closely tied to the first hash function,
- which reduce the effectiveness of double hashing.
-
- Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
-
- 2011-12-29 (0.2.7):
-
- * Minor code clean up; no actual effect.
-
- 2011-09-16 (0.2.6):
-
- * The capacity is a power of 2. This seems to dramatically improve the
- speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
-
- - http://code.google.com/p/ulib/
- - http://nothings.org/computer/judy/
-
- * Allow to optionally use linear probing which usually has better
- performance for random input. Double hashing is still the default as it
- is more robust to certain non-random input.
-
- * Added Wang's integer hash function (not used by default). This hash
- function is more robust to certain non-random input.
-
- 2011-02-14 (0.2.5):
-
- * Allow to declare global functions.
-
- 2009-09-26 (0.2.4):
-
- * Improve portability
-
- 2008-09-19 (0.2.3):
-
- * Corrected the example
- * Improved interfaces
-
- 2008-09-11 (0.2.2):
-
- * Improved speed a little in kh_put()
-
- 2008-09-10 (0.2.1):
-
- * Added kh_clear()
- * Fixed a compiling error
-
- 2008-09-02 (0.2.0):
-
- * Changed to token concatenation which increases flexibility.
-
- 2008-08-31 (0.1.2):
-
- * Fixed a bug in kh_get(), which has not been tested previously.
-
- 2008-08-31 (0.1.1):
-
- * Added destructor
- */
-
-#ifndef NVIM_LIB_KHASH_H
-#define NVIM_LIB_KHASH_H
-
-/*!
- @header
-
- Generic hash table library.
- */
-
-#define AC_VERSION_KHASH_H "0.2.8"
-
-#include <limits.h>
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "nvim/func_attr.h"
-#include "nvim/memory.h"
-
-// compiler specific configuration
-
-#if UINT_MAX == 0xffffffffu
-typedef unsigned int khint32_t;
-#elif ULONG_MAX == 0xffffffffu
-typedef unsigned long khint32_t;
-#endif
-
-#if ULONG_MAX == ULLONG_MAX
-typedef unsigned long khint64_t;
-#else
-typedef unsigned long long khint64_t;
-#endif
-
-#ifdef _MSC_VER
-# define kh_inline __inline
-#else
-# define kh_inline inline
-#endif
-
-typedef khint32_t khint_t;
-typedef khint_t khiter_t;
-
-#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
-#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
-#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
-#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(khint_t)(1ul<<((i&0xfU)<<1)))
-#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(khint_t)(2ul<<((i&0xfU)<<1)))
-#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(khint_t)(3ul<<((i&0xfU)<<1)))
-#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=(khint_t)1ul<<((i&0xfU)<<1))
-
-#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
-
-#ifndef kroundup32
-# define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, \
- ++(x))
-#endif
-
-#ifndef kcalloc
-# define kcalloc(N, Z) xcalloc(N, Z)
-#endif
-#ifndef kmalloc
-# define kmalloc(Z) xmalloc(Z)
-#endif
-#ifndef krealloc
-# define krealloc(P, Z) xrealloc(P, Z)
-#endif
-#ifndef kfree
-# define kfree(P) XFREE_CLEAR(P)
-#endif
-
-#define __ac_HASH_UPPER 0.77
-
-#define __KHASH_TYPE(name, khkey_t, khval_t) \
- typedef struct { \
- khint_t n_buckets, size, n_occupied, upper_bound; \
- khint32_t *flags; \
- khkey_t *keys; \
- khval_t *vals; \
- } kh_##name##_t;
-
-#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
- extern kh_##name##_t *kh_init_##name(void); \
- extern void kh_dealloc_##name(kh_##name##_t *h); \
- extern void kh_destroy_##name(kh_##name##_t *h); \
- extern void kh_clear_##name(kh_##name##_t *h); \
- extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
- extern void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
- extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
- extern void kh_del_##name(kh_##name##_t *h, khint_t x);
-
-#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, \
- __hash_equal) \
- SCOPE kh_##name##_t *kh_init_##name(void) \
- REAL_FATTR_UNUSED; \
- SCOPE kh_##name##_t *kh_init_##name(void) { \
- return (kh_##name##_t *)kcalloc(1, sizeof(kh_##name##_t)); \
- } \
- SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_dealloc_##name(kh_##name##_t *h) \
- { \
- kfree(h->keys); \
- kfree(h->flags); \
- kfree(h->vals); \
- } \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- { \
- if (h) { \
- kh_dealloc_##name(h); \
- kfree(h); \
- } \
- } \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- { \
- if (h && h->flags) { \
- memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
- h->size = h->n_occupied = 0; \
- } \
- } \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- { \
- if (h->n_buckets) { \
- khint_t k, i, last, mask, step = 0; \
- mask = h->n_buckets - 1; \
- k = __hash_func(key); i = k & mask; \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || \
- !__hash_equal(h->keys[i], key))) { \
- i = (i + (++step)) & mask; \
- if (i == last) { \
- return h->n_buckets; \
- } \
- } \
- return __ac_iseither(h->flags, i) ? h->n_buckets : i; \
- } else { \
- return 0; \
- } \
- } \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- { /* This function uses 0.25*n_buckets bytes of working space instead of */ \
- /* [sizeof(key_t+val_t)+.25]*n_buckets. */ \
- khint32_t *new_flags = 0; \
- khint_t j = 1; \
- { \
- kroundup32(new_n_buckets); \
- if (new_n_buckets < 4) { \
- new_n_buckets = 4; \
- } \
- /* requested size is too small */ \
- if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) { \
- j = 0; \
- } else { /* hash table size to be changed (shrink or expand); rehash */ \
- new_flags = (khint32_t *)kmalloc(__ac_fsize(new_n_buckets) \
- * sizeof(khint32_t)); \
- memset(new_flags, 0xaa, \
- __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- if (h->n_buckets < new_n_buckets) { /* expand */ \
- khkey_t *new_keys = (khkey_t *)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- h->keys = new_keys; \
- if (kh_is_map) { \
- khval_t *new_vals = \
- (khval_t *)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- h->vals = new_vals; \
- } \
- } /* otherwise shrink */ \
- } \
- } \
- if (j) { /* rehashing is needed */ \
- for (j = 0; j != h->n_buckets; ++j) { \
- if (__ac_iseither(h->flags, j) == 0) { \
- khkey_t key = h->keys[j]; \
- khval_t val; \
- khint_t new_mask; \
- new_mask = new_n_buckets - 1; \
- if (kh_is_map) { \
- val = h->vals[j]; \
- } \
- __ac_set_isdel_true(h->flags, j); \
- /* kick-out process; sort of like in Cuckoo hashing */ \
- while (1) { \
- khint_t k, i, step = 0; \
- k = __hash_func(key); \
- i = k & new_mask; \
- while (!__ac_isempty(new_flags, i)) { \
- i = (i + (++step)) & new_mask; \
- } \
- __ac_set_isempty_false(new_flags, i); \
- /* kick out the existing element */ \
- if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { \
- { \
- khkey_t tmp = h->keys[i]; \
- h->keys[i] = key; \
- key = tmp; \
- } \
- if (kh_is_map) { \
- khval_t tmp = h->vals[i]; \
- h->vals[i] = val; \
- val = tmp; \
- } \
- /* mark it as deleted in the old hash table */ \
- __ac_set_isdel_true(h->flags, i); \
- } else { /* write the element and jump out of the loop */ \
- h->keys[i] = key; \
- if (kh_is_map) { \
- h->vals[i] = val; \
- } \
- break; \
- } \
- } \
- } \
- } \
- if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
- h->keys = (khkey_t *)krealloc((void *)h->keys, \
- new_n_buckets * sizeof(khkey_t)); \
- if (kh_is_map) { \
- h->vals = (khval_t *)krealloc((void *)h->vals, \
- new_n_buckets * sizeof(khval_t)); \
- } \
- } \
- kfree(h->flags); /* free the working space */ \
- h->flags = new_flags; \
- h->n_buckets = new_n_buckets; \
- h->n_occupied = h->size; \
- h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
- } \
- } \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- REAL_FATTR_UNUSED; \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- { \
- khint_t x; \
- if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
- if (h->n_buckets > (h->size << 1)) { \
- kh_resize_##name(h, h->n_buckets - 1); /* clear "deleted" elements */ \
- } else { \
- kh_resize_##name(h, h->n_buckets + 1); /* expand the hash table */ \
- } \
- } /* TODO: implement automatically shrinking; */ \
- /* resize() already support shrinking */ \
- { \
- khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
- x = site = h->n_buckets; \
- k = __hash_func(key); \
- i = k & mask; \
- if (__ac_isempty(h->flags, i)) { \
- x = i; /* for speed up */ \
- } else { \
- last = i; \
- while (!__ac_isempty(h->flags, i) \
- && (__ac_isdel(h->flags, i) \
- || !__hash_equal(h->keys[i], key))) { \
- if (__ac_isdel(h->flags, i)) { \
- site = i; \
- } \
- i = (i + (++step)) & mask; \
- if (i == last) { \
- x = site; \
- break; \
- } \
- } \
- if (x == h->n_buckets) { \
- if (__ac_isempty(h->flags, i) && site != h->n_buckets) { \
- x = site; \
- } else { \
- x = i; \
- } \
- } \
- } \
- } \
- if (__ac_isempty(h->flags, x)) { /* not present at all */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- h->size++; \
- h->n_occupied++; \
- *ret = 1; \
- } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- h->size++; \
- *ret = 2; \
- } else { \
- *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
- } \
- return x; \
- } \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- REAL_FATTR_UNUSED; \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- { \
- if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
- __ac_set_isdel_true(h->flags, x); \
- --h->size; \
- } \
- }
-
-#define KHASH_DECLARE(name, khkey_t, khval_t) \
- __KHASH_TYPE(name, khkey_t, khval_t) \
- __KHASH_PROTOTYPES(name, khkey_t, khval_t)
-
-#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- __KHASH_TYPE(name, khkey_t, khval_t) \
- __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-// --- BEGIN OF HASH FUNCTIONS ---
-
-/*! @function
- @abstract Integer hash function
- @param key The integer [khint32_t]
- @return The hash value [khint_t]
- */
-#define kh_int_hash_func(key) (khint32_t)(key)
-/*! @function
- @abstract Integer comparison function
- */
-#define kh_int_hash_equal(a, b) ((a) == (b))
-/*! @function
- @abstract 64-bit integer hash function
- @param key The integer [khint64_t]
- @return The hash value [khint_t]
- */
-#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
-/*! @function
- @abstract 64-bit integer comparison function
- */
-#define kh_int64_hash_equal(a, b) ((a) == (b))
-/*! @function
- @abstract const char* hash function
- @param s Pointer to a null terminated string
- @return The hash value
- */
-static kh_inline khint_t __ac_X31_hash_string(const char *s)
-{
- khint_t h = (khint_t)*s;
- if (h) {
- for (++s; *s; ++s) { h = (h << 5) - h + (uint8_t)*s; }
- }
- return h;
-}
-/*! @function
- @abstract Another interface to const char* hash function
- @param key Pointer to a null terminated string [const char*]
- @return The hash value [khint_t]
- */
-#define kh_str_hash_func(key) __ac_X31_hash_string(key)
-/*! @function
- @abstract Const char* comparison function
- */
-#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
-
-static kh_inline khint_t __ac_Wang_hash(khint_t key)
-{
- key += ~(key << 15);
- key ^= (key >> 10);
- key += (key << 3);
- key ^= (key >> 6);
- key += ~(key << 11);
- key ^= (key >> 16);
- return key;
-}
-#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key)
-
-// --- END OF HASH FUNCTIONS ---
-
-// Other convenient macros...
-
-/*!
- @abstract Type of the hash table.
- @param name Name of the hash table [symbol]
- */
-#define khash_t(name) kh_##name##_t
-
-/*! @function
- @abstract Initiate a hash table.
- @param name Name of the hash table [symbol]
- @return Pointer to the hash table [khash_t(name)*]
- */
-#define kh_init(name) kh_init_##name()
-
-/*! @function
- @abstract Destroy a hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- */
-#define kh_destroy(name, h) kh_destroy_##name(h)
-
-/*! @function
- @abstract Free memory referenced directly inside a hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- */
-#define kh_dealloc(name, h) kh_dealloc_##name(h)
-
-/*! @function
- @abstract Reset a hash table without deallocating memory.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- */
-#define kh_clear(name, h) kh_clear_##name(h)
-
-/*! @function
- @abstract Resize a hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param s New size [khint_t]
- */
-#define kh_resize(name, h, s) kh_resize_##name(h, s)
-
-/*! @function
- @abstract Insert a key to the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Key [type of keys]
- @param r Extra return code: -1 if the operation failed;
- 0 if the key is present in the hash table;
- 1 if the bucket is empty (never used); 2 if the element in
- the bucket has been deleted [int*]
- @return Iterator to the inserted element [khint_t]
- */
-#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
-
-/*! @function
- @abstract Retrieve a key from the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Key [type of keys]
- @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
- */
-#define kh_get(name, h, k) kh_get_##name(h, k)
-
-/*! @function
- @abstract Remove a key from the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Iterator to the element to be deleted [khint_t]
- */
-#define kh_del(name, h, k) kh_del_##name(h, k)
-
-/*! @function
- @abstract Test whether a bucket contains data.
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return 1 if containing data; 0 otherwise [int]
- */
-#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
-
-/*! @function
- @abstract Get key given an iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return Key [type of keys]
- */
-#define kh_key(h, x) ((h)->keys[x])
-
-/*! @function
- @abstract Get value given an iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return Value [type of values]
- @discussion For hash sets, calling this results in segfault.
- */
-#define kh_val(h, x) ((h)->vals[x])
-
-/*! @function
- @abstract Alias of kh_val()
- */
-#define kh_value(h, x) ((h)->vals[x])
-
-/*! @function
- @abstract Get the start iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @return The start iterator [khint_t]
- */
-#define kh_begin(h) (khint_t)(0)
-
-/*! @function
- @abstract Get the end iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @return The end iterator [khint_t]
- */
-#define kh_end(h) ((h)->n_buckets)
-
-/*! @function
- @abstract Get the number of elements in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @return Number of elements in the hash table [khint_t]
- */
-#define kh_size(h) ((h)->size)
-
-/*! @function
- @abstract Get the number of buckets in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @return Number of buckets in the hash table [khint_t]
- */
-#define kh_n_buckets(h) ((h)->n_buckets)
-
-/*! @function
- @abstract Iterate over the entries in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @param kvar Variable to which key will be assigned
- @param vvar Variable to which value will be assigned
- @param code Block of code to execute
- */
-#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
- for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
- if (!kh_exist(h, __i)) continue; \
- (kvar) = kh_key(h, __i); \
- (vvar) = kh_val(h, __i); \
- code; \
- } }
-
-/*! @function
- @abstract Iterate over the values in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @param vvar Variable to which value will be assigned
- @param code Block of code to execute
- */
-#define kh_foreach_value(h, vvar, code) { khint_t __i; \
- for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
- if (!kh_exist(h, __i)) continue; \
- (vvar) = kh_val(h, __i); \
- code; \
- } }
-
-/*! @function
- @abstract Iterate over the keys in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @param kvar Variable to which value will be assigned
- @param code Block of code to execute
- */
-#define kh_foreach_key(h, kvar, code) \
- { \
- khint_t __i; \
- for (__i = kh_begin(h); __i != kh_end(h); __i++) { \
- if (!kh_exist(h, __i)) { \
- continue; \
- } \
- (kvar) = kh_key(h, __i); \
- code; \
- } \
- }
-
-// More convenient interfaces
-
-/*! @function
- @abstract Instantiate a hash set containing integer keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_INT(name) \
- KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing integer keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_INT(name, khval_t) \
- KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing 64-bit integer keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_INT64(name) \
- KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing 64-bit integer keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_INT64(name, khval_t) \
- KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
-
-typedef const char *kh_cstr_t;
-/*! @function
- @abstract Instantiate a hash map containing const char* keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_STR(name) \
- KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing const char* keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_STR(name, khval_t) \
- KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
-
-/*! @function
- @abstract Return a literal for an empty hash table.
- @param name Name of the hash table [symbol]
- */
-#define KHASH_EMPTY_TABLE(name) \
- ((kh_##name##_t) { \
- .n_buckets = 0, \
- .size = 0, \
- .n_occupied = 0, \
- .upper_bound = 0, \
- .flags = NULL, \
- .keys = NULL, \
- .vals = NULL, \
- })
-#endif // NVIM_LIB_KHASH_H
diff --git a/src/klib/klist.h b/src/klib/klist.h
index a9abbc6dc2..4274c53919 100644
--- a/src/klib/klist.h
+++ b/src/klib/klist.h
@@ -38,7 +38,7 @@
kmptype_t **buf; \
} kmp_##name##_t; \
static inline kmp_##name##_t *kmp_init_##name(void) { \
- return xcalloc(1, sizeof(kmp_##name##_t)); \
+ return (kmp_##name##_t *)xcalloc(1, sizeof(kmp_##name##_t)); \
} \
static inline void kmp_destroy_##name(kmp_##name##_t *mp) \
REAL_FATTR_UNUSED; \
@@ -52,7 +52,7 @@
static inline kmptype_t *kmp_alloc_##name(kmp_##name##_t *mp) { \
mp->cnt++; \
if (mp->n == 0) { \
- return xcalloc(1, sizeof(kmptype_t)); \
+ return (kmptype_t *)xcalloc(1, sizeof(kmptype_t)); \
} \
return mp->buf[--mp->n]; \
} \
@@ -60,7 +60,7 @@
mp->cnt--; \
if (mp->n == mp->max) { \
mp->max = mp->max ? (mp->max << 1) : 16; \
- mp->buf = xrealloc(mp->buf, sizeof(kmptype_t *) * mp->max); \
+ mp->buf = (kmptype_t **)xrealloc(mp->buf, sizeof(kmptype_t *) * mp->max); \
} \
mp->buf[mp->n++] = p; \
}
@@ -84,7 +84,7 @@
size_t size; \
} kl_##name##_t; \
static inline kl_##name##_t *kl_init_##name(void) { \
- kl_##name##_t *kl = xcalloc(1, sizeof(kl_##name##_t)); \
+ kl_##name##_t *kl = (kl_##name##_t *)xcalloc(1, sizeof(kl_##name##_t)); \
kl->mp = kmp_init(name); \
kl->head = kl->tail = kmp_alloc(name, kl->mp); \
kl->head->next = 0; \
diff --git a/src/klib/kvec.h b/src/klib/kvec.h
index f6674a0adf..5677a93b1b 100644
--- a/src/klib/kvec.h
+++ b/src/klib/kvec.h
@@ -170,8 +170,6 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict
return dest;
}
-// -V:kvi_push:512
-
/// Resize vector with preallocated array
///
/// @note May not resize to an array smaller then init_array: if requested,
@@ -207,6 +205,16 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict
/* 2^x initial array size. */ \
kvi_resize(v, (v).capacity << 1)
+/// fit at least "len" more items
+#define kvi_ensure_more_space(v, len) \
+ do { \
+ if ((v).capacity < (v).size + len) { \
+ (v).capacity = (v).size + len; \
+ kv_roundup32((v).capacity); \
+ kvi_resize((v), (v).capacity); \
+ } \
+ } while (0)
+
/// Get location where to store new element to a vector with preallocated array
///
/// @param[in,out] v Vector to push to.
@@ -223,6 +231,19 @@ static inline void *_memcpy_free(void *const restrict dest, void *const restrict
#define kvi_push(v, x) \
(*kvi_pushp(v) = (x))
+/// Copy a vector to a preallocated vector
+///
+/// @param[out] v1 destination
+/// @param[in] v0 source (can be either vector or preallocated vector)
+#define kvi_copy(v1, v0) \
+ do { \
+ if ((v1).capacity < (v0).size) { \
+ kvi_resize(v1, (v0).size); \
+ } \
+ (v1).size = (v0).size; \
+ memcpy((v1).items, (v0).items, sizeof((v1).items[0]) * (v0).size); \
+ } while (0)
+
/// Free array of elements of a vector with preallocated array if needed
///
/// @param[out] v Vector to free.
diff --git a/src/man/nvim.1 b/src/man/nvim.1
index c32bdeadc6..4dc099f98c 100644
--- a/src/man/nvim.1
+++ b/src/man/nvim.1
@@ -193,7 +193,7 @@ is
do not read or write a ShaDa file.
.Ic ":help shada"
.It Fl -noplugin
-Skip loading plugins.
+Skip loading plugins (by setting the 'noloadplugins' option).
Implied by
.Cm -u NONE .
.It Fl -clean
@@ -339,6 +339,11 @@ Print version information and exit.
.El
.Sh ENVIRONMENT
.Bl -tag -width Fl
+.It Ev NVIM_APPNAME
+The name of sub-directories used within each XDG user directory.
+Defaults to
+.Cm nvim .
+:help $NVIM_APPNAME
.It Ev NVIM_LOG_FILE
Low-level log file, usually found at ~/.local/state/nvim/log.
:help $NVIM_LOG_FILE
diff --git a/src/mpack/conv.c b/src/mpack/conv.c
index 6bd446ca49..c53ce1427f 100644
--- a/src/mpack/conv.c
+++ b/src/mpack/conv.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include "conv.h"
static int mpack_fits_single(double v);
diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c
index 53d7092a0c..ff21e29789 100644
--- a/src/mpack/lmpack.c
+++ b/src/mpack/lmpack.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/*
* This module exports three classes, and each instance of those classes has its
* own private registry for temporary reference storage(keeping state between
@@ -27,7 +24,7 @@
#include <lua.h>
#include <luaconf.h>
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "lmpack.h"
@@ -233,7 +230,7 @@ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array)
while (lua_next(L, -2)) {
lua_pop(L, 1); /* pop value */
isarr = isarr
- && lua_isnumber(L, -1) /* lua number */
+ && lua_type(L, -1) == LUA_TNUMBER /* lua number */
&& (n = lua_tonumber(L, -1)) > 0 /* greater than 0 */
&& (size_t)n == n; /* and integer */
max = isarr && (size_t)n > max ? (size_t)n : max;
@@ -246,7 +243,7 @@ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array)
}
end:
- if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) // -V560
+ if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1)
/* msgpack spec doesn't allow lengths > 32 bits */
len = (mpack_uint32_t)-1;
assert(top == lua_gettop(L));
@@ -644,7 +641,13 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node)
mpack_node_t *n;
int has_meta = lua_getmetatable(L, -1);
- if (packer->ext != LUA_NOREF && has_meta) {
+ int has_mtdict = false;
+ if (has_meta && packer->mtdict != LUA_NOREF) {
+ lmpack_geti(L, packer->reg, packer->mtdict); // [table, metatable, mtdict]
+ has_mtdict = lua_rawequal(L, -1, -2);
+ lua_pop(L, 1); // [table, metatable];
+ }
+ if (packer->ext != LUA_NOREF && has_meta && !has_mtdict) {
/* check if there's a handler for this metatable */
lmpack_geti(L, packer->reg, packer->ext);
lua_pushvalue(L, -2);
@@ -701,14 +704,7 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node)
}
}
- int is_array = 1;
if (has_meta) {
- // stack: [table, metatable]
- if (packer->mtdict != LUA_NOREF) {
- lmpack_geti(L, packer->reg, packer->mtdict); // [table, metatable, mtdict]
- is_array = !lua_rawequal(L, -1, -2);
- lua_pop(L, 1); // [table, metatable];
- }
lua_pop(L, 1); // [table]
}
@@ -726,6 +722,7 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node)
lua_pop(L, 1);
}
+ int is_array = !has_mtdict;
len = lmpack_objlen(L, &is_array);
if (is_array) {
node->tok = mpack_pack_array(len);
diff --git a/src/mpack/mpack_core.c b/src/mpack/mpack_core.c
index 3424f444b9..b9b92523bd 100644
--- a/src/mpack/mpack_core.c
+++ b/src/mpack/mpack_core.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <string.h>
#include "mpack_core.h"
diff --git a/src/mpack/object.c b/src/mpack/object.c
index e2d893bc88..ec128690d7 100644
--- a/src/mpack/object.c
+++ b/src/mpack/object.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <string.h>
#include "object.h"
@@ -128,8 +125,11 @@ MPACK_API int mpack_unparse(mpack_parser_t *parser, char **buf, size_t *buflen,
return status;
}
-MPACK_API void mpack_parser_copy(mpack_parser_t *dst, mpack_parser_t *src)
+MPACK_API void mpack_parser_copy(mpack_parser_t *d, mpack_parser_t *s)
{
+ // workaround UBSAN being NOT happy with a flexible array member with arr[N>1] initial size
+ mpack_one_parser_t *dst = (mpack_one_parser_t *)d;
+ mpack_one_parser_t *src = (mpack_one_parser_t *)s;
mpack_uint32_t i;
mpack_uint32_t dst_capacity = dst->capacity;
assert(src->capacity <= dst_capacity);
@@ -148,8 +148,9 @@ static int mpack_parser_full(mpack_parser_t *parser)
return parser->size == parser->capacity;
}
-static mpack_node_t *mpack_parser_push(mpack_parser_t *parser)
+static mpack_node_t *mpack_parser_push(mpack_parser_t *p)
{
+ mpack_one_parser_t *parser = (mpack_one_parser_t *)p;
mpack_node_t *top;
assert(parser->size < parser->capacity);
top = parser->items + parser->size + 1;
@@ -162,8 +163,9 @@ static mpack_node_t *mpack_parser_push(mpack_parser_t *parser)
return top;
}
-static mpack_node_t *mpack_parser_pop(mpack_parser_t *parser)
+static mpack_node_t *mpack_parser_pop(mpack_parser_t *p)
{
+ mpack_one_parser_t *parser = (mpack_one_parser_t *)p;
mpack_node_t *top, *parent;
assert(parser->size);
top = parser->items + parser->size;
diff --git a/src/mpack/rpc.c b/src/mpack/rpc.c
index 2d251284ba..3b2b328065 100644
--- a/src/mpack/rpc.c
+++ b/src/mpack/rpc.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <string.h>
#include "rpc.h"
diff --git a/src/nlua0.c b/src/nlua0.c
new file mode 100644
index 0000000000..2cfa16b804
--- /dev/null
+++ b/src/nlua0.c
@@ -0,0 +1,18 @@
+#include <lua.h>
+
+#include "mpack/lmpack.h"
+
+LUA_API int luaopen_nlua0(lua_State* L);
+
+LUA_API int luaopen_nlua0(lua_State* L) {
+ lua_getglobal(L, "vim");
+ luaopen_mpack(L);
+ lua_setfield(L, -2, "mpack");
+
+ int luaopen_lpeg(lua_State *);
+ luaopen_lpeg(L);
+ lua_setfield(L, -3, "lpeg");
+ lua_pop(L, 2);
+
+ return 1;
+}
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 7b56af59da..3505f8be4f 100755..100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -1,49 +1,63 @@
add_library(main_lib INTERFACE)
add_executable(nvim main.c)
-add_library(libuv_lib INTERFACE)
-find_package(LibUV 1.28.0 REQUIRED)
-target_include_directories(libuv_lib SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIRS})
-target_link_libraries(libuv_lib INTERFACE ${LIBUV_LIBRARIES})
-
-find_package(Msgpack 1.0.0 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${MSGPACK_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${MSGPACK_LIBRARIES})
-
-find_package(LibLUV 1.43.0 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBLUV_INCLUDE_DIRS})
-# Use "luv" as imported library, to work around CMake using "-lluv" for
-# "luv.so". #10407
-add_library(luv UNKNOWN IMPORTED)
-set_target_properties(luv PROPERTIES IMPORTED_LOCATION ${LIBLUV_LIBRARIES})
-target_link_libraries(main_lib INTERFACE luv)
+set_target_properties(nvim
+ PROPERTIES
+ EXPORT_COMPILE_COMMANDS ON
+ ENABLE_EXPORTS TRUE)
-find_package(TreeSitter REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${TreeSitter_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${TreeSitter_LIBRARIES})
+#-------------------------------------------------------------------------------
+# Dependencies
+#-------------------------------------------------------------------------------
-find_package(unibilium 2.0 REQUIRED)
-target_link_libraries(main_lib INTERFACE unibilium)
+add_library(libuv INTERFACE)
+find_package(libuv CONFIG QUIET)
+if(TARGET libuv::uv_a)
+ target_link_libraries(libuv INTERFACE libuv::uv_a)
+ mark_as_advanced(libuv_DIR)
+else()
+ # Fall back to find module for libuv versions older than v1.45.0 which don't
+ # provide a config file
+ find_package(Libuv 1.28.0 REQUIRED MODULE)
+ target_include_directories(libuv SYSTEM BEFORE INTERFACE ${LIBUV_INCLUDE_DIR})
+ target_link_libraries(libuv INTERFACE ${LIBUV_LIBRARIES})
+endif()
-find_package(LibTermkey 0.22 REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBTERMKEY_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${LIBTERMKEY_LIBRARIES})
+add_library(nlua0 MODULE)
+if(WIN32)
+ target_compile_definitions(nlua0 PUBLIC LUA_BUILD_AS_DLL LUA_LIB)
+ set_target_properties(nlua0 PROPERTIES ENABLE_EXPORTS TRUE)
+elseif(APPLE)
+ set_target_properties(nlua0 PROPERTIES LINK_FLAGS "-undefined dynamic_lookup")
+endif()
-find_package(libvterm 0.3 REQUIRED)
-target_link_libraries(main_lib INTERFACE libvterm)
+find_package(Luv 1.43.0 REQUIRED)
+target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUV_INCLUDE_DIR})
+target_link_libraries(main_lib INTERFACE ${LUV_LIBRARY})
find_package(Iconv REQUIRED)
-target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${Iconv_INCLUDE_DIRS})
-target_link_libraries(main_lib INTERFACE ${Iconv_LIBRARIES})
-
-option(ENABLE_LIBINTL "enable libintl" ON)
-if(ENABLE_LIBINTL)
- # LibIntl (not Intl) selects our FindLibIntl.cmake script. #8464
- find_package(LibIntl REQUIRED)
- target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LibIntl_INCLUDE_DIRS})
- if (LibIntl_FOUND)
- target_link_libraries(main_lib INTERFACE ${LibIntl_LIBRARY})
- endif()
+find_package(Lpeg REQUIRED)
+find_package(Libtermkey 0.22 REQUIRED)
+find_package(Libvterm 0.3.3 REQUIRED)
+find_package(Msgpack 1.0.0 REQUIRED)
+find_package(Treesitter 0.20.8 REQUIRED)
+find_package(Unibilium 2.0 REQUIRED)
+
+target_link_libraries(main_lib INTERFACE
+ iconv
+ libtermkey
+ libvterm
+ msgpack
+ treesitter
+ unibilium
+ lpeg)
+target_link_libraries(nlua0 PUBLIC lpeg)
+
+# Libintl (not Intl) selects our FindLibintl.cmake script. #8464
+find_package(Libintl REQUIRED)
+target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LIBINTL_INCLUDE_DIR})
+if (LIBINTL_LIBRARY)
+ target_link_libraries(main_lib INTERFACE ${LIBINTL_LIBRARY})
endif()
# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing.
@@ -51,103 +65,114 @@ option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF)
if(PREFER_LUA)
find_package(Lua 5.1 EXACT REQUIRED)
target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUA_INCLUDE_DIR})
+ target_include_directories(nlua0 SYSTEM BEFORE PUBLIC ${LUA_INCLUDE_DIR})
target_link_libraries(main_lib INTERFACE ${LUA_LIBRARIES})
# Passive (not REQUIRED): if LUAJIT_FOUND is not set, fixtures for unittests is skipped.
- find_package(LuaJit)
+ find_package(Luajit)
else()
- find_package(LuaJit REQUIRED)
- target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIRS})
- target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARIES})
-endif()
-
-option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF)
-if(ENABLE_IWYU)
- find_program(IWYU_PRG NAMES include-what-you-use iwyu)
- if(NOT IWYU_PRG)
- message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!")
+ find_package(Luajit REQUIRED)
+ target_include_directories(main_lib SYSTEM BEFORE INTERFACE ${LUAJIT_INCLUDE_DIR})
+ target_link_libraries(main_lib INTERFACE ${LUAJIT_LIBRARY})
+ target_include_directories(nlua0 SYSTEM BEFORE PUBLIC ${LUAJIT_INCLUDE_DIR})
+ if(WIN32)
+ target_link_libraries(nlua0 PUBLIC ${LUAJIT_LIBRARY})
endif()
-
- set(iwyu_flags "${IWYU_PRG};")
- string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.libc.imp;")
- string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/gcc.symbols.imp")
-
- set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}")
- target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
-if(MSVC)
- # TODO(dundargoc): bump warning level
- target_compile_options(main_lib INTERFACE -W2)
-
- # Disable warnings that give too many false positives.
- target_compile_options(main_lib INTERFACE -wd4311 -wd4146)
- target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
+#-------------------------------------------------------------------------------
+# Compiler and linker options
+#-------------------------------------------------------------------------------
- target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest)
-else()
+if(NOT MSVC)
target_compile_options(main_lib INTERFACE -Wall -Wextra -pedantic -Wno-unused-parameter
- -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion
+ -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion -Wvla
-Wdouble-promotion
-Wmissing-noreturn
-Wmissing-format-attribute
- -Wmissing-prototypes)
-endif()
+ -Wmissing-prototypes
+ -fsigned-char)
-# On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
-# 3.4.1 used there.
-if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang")
- target_compile_options(main_lib INTERFACE -Wno-c11-extensions)
+ # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems
+ # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042
+ # For ptsname(). #6743
+ target_compile_definitions(main_lib INTERFACE _GNU_SOURCE)
endif()
-check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
-if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
- target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough)
+# -fstack-protector breaks Mingw-w64 builds
+if(NOT MINGW)
+ check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG)
+ if(HAS_FSTACK_PROTECTOR_STRONG_FLAG)
+ target_compile_options(main_lib INTERFACE -fstack-protector-strong)
+ target_link_libraries(main_lib INTERFACE -fstack-protector-strong)
+ else()
+ check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG)
+ if(HAS_FSTACK_PROTECTOR_FLAG)
+ target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+ target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+ endif()
+ endif()
endif()
-option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF)
-if(ENABLE_COMPILER_SUGGESTIONS)
- # Clang doesn't have -Wsuggest-attribute so check for each one.
- check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE)
- if(HAVE_WSUGGEST_ATTRIBUTE_PURE)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure)
- endif()
+# Compiler specific options
+if(MSVC)
+ target_compile_options(main_lib INTERFACE -W3)
- check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST)
- if(HAVE_WSUGGEST_ATTRIBUTE_CONST)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const)
- endif()
+ # Disable warnings that give too many false positives.
+ target_compile_options(main_lib INTERFACE -wd4311 -wd4146 -wd4003 -wd4715)
+ target_compile_definitions(main_lib INTERFACE _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
- check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
- if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc)
- endif()
+ target_sources(main_lib INTERFACE ${CMAKE_CURRENT_LIST_DIR}/os/nvim.manifest)
+elseif(MINGW)
+ # Use POSIX compatible stdio in Mingw
+ target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO)
- check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD)
- if(HAVE_WSUGGEST_ATTRIBUTE_COLD)
- target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold)
+ # Enable wmain
+ target_link_libraries(nvim PRIVATE -municode)
+elseif(CMAKE_C_COMPILER_ID STREQUAL "GNU")
+ target_compile_options(main_lib INTERFACE -fno-common
+ $<$<CONFIG:Release>:-Wno-unused-result>
+ $<$<CONFIG:RelWithDebInfo>:-Wno-unused-result>
+ $<$<CONFIG:MinSizeRel>:-Wno-unused-result>)
+elseif(CMAKE_C_COMPILER_ID MATCHES "Clang")
+ # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang
+ # 3.4.1 used there.
+ if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+ target_compile_options(main_lib INTERFACE -Wno-c11-extensions)
+ endif()
+
+ # workaround for clang-11 on macOS, supported on later versions
+ if(NOT APPLE)
+ target_link_libraries(nvim PRIVATE -Wl,--no-undefined)
endif()
endif()
-if(MINGW)
- # Use POSIX compatible stdio in Mingw
- target_compile_definitions(main_lib INTERFACE __USE_MINGW_ANSI_STDIO)
-endif()
-if(WIN32)
- # Windows Vista is the minimum supported version
- target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0600 MSWIN)
+# Platform specific options
+if(UNIX)
+ target_link_libraries(main_lib INTERFACE m)
+ if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ target_link_libraries(main_lib INTERFACE util)
+ endif()
endif()
-# OpenBSD's GCC (4.2.1) doesn't have -Wvla
-check_c_compiler_flag(-Wvla HAS_WVLA_FLAG)
-if(HAS_WVLA_FLAG)
- target_compile_options(main_lib INTERFACE -Wvla)
+if(CMAKE_SYSTEM_NAME MATCHES "Windows")
+ target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN)
+ target_link_libraries(main_lib INTERFACE netapi32)
+elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
+ target_link_libraries(nvim PRIVATE "-framework CoreServices")
+
+ # Actually export symbols - symbols may not be visible even though
+ # ENABLE_EXPORTS is set to true. See
+ # https://github.com/neovim/neovim/issues/25295
+ set_target_properties(nvim PROPERTIES LINK_FLAGS "-Wl,-export_dynamic")
+elseif(CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
+ target_link_libraries(main_lib INTERFACE pthread c++abi)
+elseif(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
+ target_link_libraries(nvim PRIVATE -lsocket)
endif()
-check_c_compiler_flag(-fno-common HAVE_FNO_COMMON)
-if (HAVE_FNO_COMMON)
- target_compile_options(main_lib INTERFACE -fno-common)
+check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
+if(HAVE_WIMPLICIT_FALLTHROUGH_FLAG)
+ target_compile_options(main_lib INTERFACE -Wimplicit-fallthrough)
endif()
check_c_compiler_flag(-fdiagnostics-color=auto HAS_DIAG_COLOR_FLAG)
@@ -159,94 +184,6 @@ if(HAS_DIAG_COLOR_FLAG)
endif()
endif()
-option(CI_BUILD "CI, extra flags will be set" OFF)
-if(CI_BUILD)
- message(STATUS "CI build enabled")
- if(MSVC)
- target_compile_options(main_lib INTERFACE -WX)
- else()
- target_compile_options(main_lib INTERFACE -Werror)
- if(DEFINED ENV{BUILD_UCHAR})
- # Get some test coverage for unsigned char
- target_compile_options(main_lib INTERFACE -funsigned-char)
- endif()
- endif()
-endif()
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}")
-check_c_source_compiles("
-#include <msgpack.h>
-
-int
-main(void)
-{
- return MSGPACK_OBJECT_FLOAT32;
-}
-" MSGPACK_HAS_FLOAT32)
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}")
-if(MSGPACK_HAS_FLOAT32)
- target_compile_definitions(main_lib INTERFACE NVIM_MSGPACK_HAS_FLOAT32)
-endif()
-
-list(APPEND CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}")
-list(APPEND CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}")
-check_c_source_compiles("
-#include <tree_sitter/api.h>
-int
-main(void)
-{
- TSQueryCursor *cursor = ts_query_cursor_new();
- ts_query_cursor_set_match_limit(cursor, 32);
- return 0;
-}
-" TS_HAS_SET_MATCH_LIMIT)
-if(TS_HAS_SET_MATCH_LIMIT)
- target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_MATCH_LIMIT)
-endif()
-check_c_source_compiles("
-#include <stdlib.h>
-#include <tree_sitter/api.h>
-int
-main(void)
-{
- ts_set_allocator(malloc, calloc, realloc, free);
- return 0;
-}
-" TS_HAS_SET_ALLOCATOR)
-if(TS_HAS_SET_ALLOCATOR)
- target_compile_definitions(main_lib INTERFACE NVIM_TS_HAS_SET_ALLOCATOR)
-endif()
-list(REMOVE_ITEM CMAKE_REQUIRED_INCLUDES "${TreeSitter_INCLUDE_DIRS}")
-list(REMOVE_ITEM CMAKE_REQUIRED_LIBRARIES "${TreeSitter_LIBRARIES}")
-
-# Include <string.h> because some toolchains define _FORTIFY_SOURCE=2 in
-# internal header files, which should in turn be #included by <string.h>.
-check_c_source_compiles("
-#include <string.h>
-
-#if defined(_FORTIFY_SOURCE) && _FORTIFY_SOURCE > 1
-#error \"_FORTIFY_SOURCE > 1\"
-#endif
-int
-main(void)
-{
- return 0;
-}
-" HAS_ACCEPTABLE_FORTIFY)
-
-if(NOT HAS_ACCEPTABLE_FORTIFY)
- message(STATUS "Unsupported _FORTIFY_SOURCE found, forcing _FORTIFY_SOURCE=1")
- # Extract possible prefix to _FORTIFY_SOURCE (e.g. -Wp,-D_FORTIFY_SOURCE).
- string(REGEX MATCH "[^\ ]+-D_FORTIFY_SOURCE" _FORTIFY_SOURCE_PREFIX "${CMAKE_C_FLAGS}")
- string(REPLACE "-D_FORTIFY_SOURCE" "" _FORTIFY_SOURCE_PREFIX "${_FORTIFY_SOURCE_PREFIX}" )
- if(NOT _FORTIFY_SOURCE_PREFIX STREQUAL "")
- message(STATUS "Detected _FORTIFY_SOURCE Prefix=${_FORTIFY_SOURCE_PREFIX}")
- endif()
- # -U in add_definitions doesn't end up in the correct spot, so we add it to
- # the flags variable instead.
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${_FORTIFY_SOURCE_PREFIX}-U_FORTIFY_SOURCE ${_FORTIFY_SOURCE_PREFIX}-D_FORTIFY_SOURCE=1")
-endif()
-
target_compile_definitions(main_lib INTERFACE INCLUDE_GENERATED_DECLARATIONS)
# Remove --sort-common from linker flags, as this seems to cause bugs (see #2641, #3374).
@@ -267,58 +204,114 @@ if(CMAKE_EXE_LINKER_FLAGS MATCHES "--sort-common" OR
string(REGEX REPLACE "-Wl($| )" "" CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS}")
endif()
-if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
- if(CMAKE_SYSTEM_NAME STREQUAL "SunOS")
- target_link_libraries(nvim PRIVATE -Wl,--no-undefined -lsocket)
- elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- target_link_libraries(nvim PRIVATE -Wl,--no-undefined)
+#-------------------------------------------------------------------------------
+# Cmake options
+#-------------------------------------------------------------------------------
+
+if(ENABLE_ASAN_UBSAN)
+ message(STATUS "Enabling address sanitizer and undefined behavior sanitizer for nvim.")
+ if(NOT MSVC)
+ if(CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ target_compile_options(nvim PRIVATE -fsanitize-recover=all)
+ else()
+ target_compile_options(nvim PRIVATE -fno-sanitize-recover=all)
+ endif()
+ target_compile_options(nvim PRIVATE
+ -fno-omit-frame-pointer
+ -fno-optimize-sibling-calls
+ -fsanitize=undefined)
endif()
- # For O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW flags on older systems
- # (pre POSIX.1-2008: glibc 2.11 and earlier). #4042
- # For ptsname(). #6743
- target_compile_definitions(main_lib INTERFACE _GNU_SOURCE)
+ target_compile_options(nvim PRIVATE -fsanitize=address)
+ target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined)
+ target_compile_definitions(nvim PRIVATE ENABLE_ASAN_UBSAN)
+elseif(ENABLE_MSAN)
+ message(STATUS "Enabling memory sanitizer for nvim.")
+ target_compile_options(nvim PRIVATE
+ -fsanitize=memory
+ -fsanitize-memory-track-origins
+ -fno-omit-frame-pointer
+ -fno-optimize-sibling-calls)
+ target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins)
+elseif(ENABLE_TSAN)
+ message(STATUS "Enabling thread sanitizer for nvim.")
+ target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE)
+ target_link_libraries(nvim PRIVATE -fsanitize=thread)
endif()
-option(USE_GCOV "Enable gcov support" OFF)
-if(USE_GCOV)
- if(CLANG_TSAN)
- # GCOV and TSAN results in false data race reports
- message(FATAL_ERROR "USE_GCOV cannot be used with CLANG_TSAN")
+option(CI_BUILD "CI, extra flags will be set" OFF)
+if(CI_BUILD)
+ message(STATUS "CI build enabled")
+ if(MSVC)
+ target_compile_options(main_lib INTERFACE -WX)
+ else()
+ target_compile_options(main_lib INTERFACE -Werror)
endif()
- message(STATUS "Enabling gcov support")
- target_compile_options(main_lib INTERFACE --coverage)
- target_link_libraries(main_lib INTERFACE --coverage)
- target_compile_definitions(main_lib INTERFACE USE_GCOV)
endif()
-if(WIN32)
- if(MINGW)
- # Enable wmain
- target_link_libraries(nvim PRIVATE -municode)
+option(ENABLE_IWYU "Run include-what-you-use with the compiler." OFF)
+if(ENABLE_IWYU)
+ find_program(IWYU_PRG NAMES include-what-you-use iwyu)
+ if(NOT IWYU_PRG)
+ message(FATAL_ERROR "ENABLE_IWYU is ON but include-what-you-use is not found!")
endif()
-elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
- target_link_libraries(nvim PRIVATE "-framework CoreServices")
+
+ set(iwyu_flags "${IWYU_PRG};")
+ string(APPEND iwyu_flags "-Xiwyu;--no_default_mappings;")
+ string(APPEND iwyu_flags "-Xiwyu;--no_fwd_decls;")
+ string(APPEND iwyu_flags "-Xiwyu;--mapping_file=${PROJECT_SOURCE_DIR}/cmake.config/iwyu/mapping.imp")
+
+ set_target_properties(nvim PROPERTIES C_INCLUDE_WHAT_YOU_USE "${iwyu_flags}")
+ target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
-if(UNIX)
- # -fstack-protector breaks non Unix builds even in Mingw-w64
- check_c_compiler_flag(-fstack-protector-strong HAS_FSTACK_PROTECTOR_STRONG_FLAG)
- check_c_compiler_flag(-fstack-protector HAS_FSTACK_PROTECTOR_FLAG)
- if(HAS_FSTACK_PROTECTOR_STRONG_FLAG)
- target_compile_options(main_lib INTERFACE -fstack-protector-strong)
- target_link_libraries(main_lib INTERFACE -fstack-protector-strong)
- elseif(HAS_FSTACK_PROTECTOR_FLAG)
- target_compile_options(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
- target_link_libraries(main_lib INTERFACE -fstack-protector --param ssp-buffer-size=4)
+option(ENABLE_COMPILER_SUGGESTIONS "Enable -Wsuggest compiler warnings" OFF)
+if(ENABLE_COMPILER_SUGGESTIONS)
+ # Clang doesn't have -Wsuggest-attribute so check for each one.
+ check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ if(HAVE_WSUGGEST_ATTRIBUTE_PURE)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=pure)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ if(HAVE_WSUGGEST_ATTRIBUTE_CONST)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=const)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=malloc)
+ endif()
+
+ check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ if(HAVE_WSUGGEST_ATTRIBUTE_COLD)
+ target_compile_options(main_lib INTERFACE -Wsuggest-attribute=cold)
+ endif()
+endif()
+
+option(ENABLE_GCOV "Enable gcov support" OFF)
+if(ENABLE_GCOV)
+ if(ENABLE_TSAN)
+ # GCOV and TSAN results in false data race reports
+ message(FATAL_ERROR "ENABLE_GCOV cannot be used with ENABLE_TSAN")
endif()
+ message(STATUS "Enabling gcov support")
+ target_compile_options(main_lib INTERFACE --coverage)
+ target_link_libraries(main_lib INTERFACE --coverage)
+ target_compile_definitions(main_lib INTERFACE USE_GCOV)
endif()
+#-------------------------------------------------------------------------------
+# Variables
+#-------------------------------------------------------------------------------
+
set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GENERATED_DIR ${PROJECT_BINARY_DIR}/src/nvim/auto)
set(BINARY_LIB_DIR ${PROJECT_BINARY_DIR}/lib/nvim/)
set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua)
set(API_UI_EVENTS_GENERATOR ${GENERATOR_DIR}/gen_api_ui_events.lua)
+set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload.lua)
set(GENERATOR_C_GRAMMAR ${GENERATOR_DIR}/c_grammar.lua)
set(GENERATOR_HASHY ${GENERATOR_DIR}/hashy.lua)
set(API_METADATA ${PROJECT_BINARY_DIR}/api_metadata.mpack)
@@ -335,7 +328,6 @@ set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata
set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h)
set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h)
set(GENERATED_FUNCS ${GENERATED_DIR}/funcs.generated.h)
-set(GENERATED_KEYSETS ${GENERATED_DIR}/keysets.generated.h)
set(GENERATED_KEYSETS_DEFS ${GENERATED_DIR}/keysets_defs.generated.h)
set(GENERATED_EVENTS_ENUM ${GENERATED_INCLUDES_DIR}/auevents_enum.generated.h)
set(GENERATED_EVENTS_NAMES_MAP ${GENERATED_DIR}/auevents_name_map.generated.h)
@@ -343,21 +335,25 @@ set(GENERATED_OPTIONS ${GENERATED_DIR}/options.generated.h)
set(EX_CMDS_GENERATOR ${GENERATOR_DIR}/gen_ex_cmds.lua)
set(FUNCS_GENERATOR ${GENERATOR_DIR}/gen_eval.lua)
set(EVENTS_GENERATOR ${GENERATOR_DIR}/gen_events.lua)
-set(KEYSETS_GENERATOR ${GENERATOR_DIR}/gen_keysets.lua)
set(OPTIONS_GENERATOR ${GENERATOR_DIR}/gen_options.lua)
set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/src/unicode)
set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
-set(LUA_EDITOR_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_editor.lua)
-set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
-set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
-set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua)
-set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua)
-set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua)
-set(LUA_INIT_PACKAGES_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_init_packages.lua)
-set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.lua)
+set(NVIM_RUNTIME_DIR ${PROJECT_SOURCE_DIR}/runtime)
+set(LUA_EDITOR_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_editor.lua)
+set(LUA_SHARED_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/shared.lua)
+set(LUA_LOADER_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/loader.lua)
+set(LUA_INSPECT_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/inspect.lua)
+set(LUA_FS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/fs.lua)
+set(LUA_F_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/F.lua)
+set(LUA_DEFAULTS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_defaults.lua)
+set(LUA_OPTIONS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_options.lua)
+set(LUA_FILETYPE_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/filetype.lua)
+set(LUA_INIT_PACKAGES_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_init_packages.lua)
+set(LUA_KEYMAP_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/keymap.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
+set(LUAJIT_RUNTIME_DIR ${DEPS_PREFIX}/share/luajit-2.1/jit)
glob_wrapper(UNICODE_FILES ${UNICODE_DIR}/*.txt)
glob_wrapper(API_HEADERS api/*.h)
@@ -370,6 +366,10 @@ target_include_directories(main_lib INTERFACE ${GENERATED_INCLUDES_DIR})
target_include_directories(main_lib INTERFACE "${PROJECT_BINARY_DIR}/cmake.config")
target_include_directories(main_lib INTERFACE "${PROJECT_SOURCE_DIR}/src")
+target_include_directories(nlua0 PUBLIC "${PROJECT_SOURCE_DIR}/src")
+target_include_directories(nlua0 PUBLIC "${PROJECT_BINARY_DIR}/cmake.config")
+target_include_directories(nlua0 PUBLIC ${GENERATED_INCLUDES_DIR})
+
file(MAKE_DIRECTORY ${TOUCHES_DIR})
file(MAKE_DIRECTORY ${GENERATED_DIR})
file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR})
@@ -379,6 +379,17 @@ glob_wrapper(NVIM_HEADERS *.h)
glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c)
glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h)
+glob_wrapper(NLUA0_SOURCES ../mpack/*.c)
+
+if(PREFER_LUA)
+ # luajit not used, use a vendored copy of the bit module
+ list(APPEND EXTERNAL_SOURCES ${PROJECT_SOURCE_DIR}/src/bit.c)
+ list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/bit.c)
+ target_compile_definitions(main_lib INTERFACE NVIM_VENDOR_BIT)
+endif()
+
+list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/nlua0.c)
+
foreach(subdir
os
api
@@ -409,12 +420,6 @@ list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS})
foreach(sfile ${NVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
- if(${f} MATCHES "^(regexp_nfa.c)$")
- list(APPEND to_remove ${sfile})
- endif()
- if(${f} MATCHES "^(regexp_bt.c)$")
- list(APPEND to_remove ${sfile})
- endif()
if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
list(APPEND to_remove ${sfile})
endif()
@@ -434,20 +439,29 @@ list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306
if(MSVC)
set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} /wd4090 /wd4244")
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -wd4090 -wd4244 -wd4267")
else()
set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes")
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes -Wno-misleading-indentation")
endif()
-if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
- target_compile_definitions(main_lib INTERFACE MIN_LOG_LEVEL=${MIN_LOG_LEVEL})
+# Log level (NVIM_LOG_DEBUG in log.h)
+if(CI_BUILD)
+ # Don't debug log on CI, it gets too verbose in the main build log.
+ # TODO(bfredl): debug log level also exposes some errors with EXITFREE in ASAN build.
+else()
+ # Minimize logging for release-type builds.
+ target_compile_definitions(nvim PRIVATE $<$<CONFIG:Debug>:NVIM_LOG_DEBUG>)
endif()
-if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN)
+if(ENABLE_ASAN_UBSAN OR ENABLE_MSAN OR ENABLE_TSAN)
target_compile_definitions(main_lib INTERFACE EXITFREE)
endif()
+#-------------------------------------------------------------------------------
+# Header generation
+#-------------------------------------------------------------------------------
+
get_target_property(prop main_lib INTERFACE_COMPILE_DEFINITIONS)
foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop})
if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS")
@@ -455,42 +469,54 @@ foreach(gen_cdef DO_NOT_DEFINE_EMPTY_ATTRIBUTES ${prop})
endif()
endforeach()
-get_target_property(prop main_lib INTERFACE_INCLUDE_DIRECTORIES)
-foreach(gen_include ${prop})
- list(APPEND gen_cflags "-I${gen_include}")
+get_directory_property(targets BUILDSYSTEM_TARGETS)
+foreach(target ${targets})
+ get_target_property(prop ${target} INTERFACE_INCLUDE_DIRECTORIES)
+ foreach(gen_include ${prop})
+ list(APPEND gen_cflags "-I${gen_include}")
+ endforeach()
endforeach()
-if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT)
- list(APPEND gen_cflags "-isysroot")
- list(APPEND gen_cflags "${CMAKE_OSX_SYSROOT}")
+
+if(APPLE AND CMAKE_OSX_SYSROOT)
+ list(APPEND gen_cflags "-isysroot" "${CMAKE_OSX_SYSROOT}")
+endif()
+if(MSVC)
+ list(APPEND gen_cflags -wd4003)
endif()
-string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type)
-separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS})
-separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}})
-set(gen_cflags ${gen_cflags} ${C_FLAGS_${build_type}_ARRAY} ${C_FLAGS_ARRAY})
+list(APPEND gen_cflags -O2)
set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h)
add_custom_target(update_version_stamp
COMMAND ${CMAKE_COMMAND}
- -DNVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR}
- -DNVIM_VERSION_MINOR=${NVIM_VERSION_MINOR}
- -DNVIM_VERSION_PATCH=${NVIM_VERSION_PATCH}
- -DNVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE}
- -DOUTPUT=${NVIM_VERSION_GIT_H}
- -DNVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR}
+ -D NVIM_VERSION_MAJOR=${NVIM_VERSION_MAJOR}
+ -D NVIM_VERSION_MINOR=${NVIM_VERSION_MINOR}
+ -D NVIM_VERSION_PATCH=${NVIM_VERSION_PATCH}
+ -D NVIM_VERSION_PRERELEASE=${NVIM_VERSION_PRERELEASE}
+ -D OUTPUT=${NVIM_VERSION_GIT_H}
+ -D NVIM_SOURCE_DIR=${CMAKE_SOURCE_DIR}
-P ${PROJECT_SOURCE_DIR}/cmake/GenerateVersion.cmake
BYPRODUCTS ${NVIM_VERSION_GIT_H})
+set(NVIM_VERSION_DEF_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef.h)
+add_custom_command(
+ OUTPUT "${NVIM_VERSION_DEF_H}"
+ COMMAND "${CMAKE_COMMAND}"
+ -E copy
+ "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef-$<CONFIG>.h"
+ "${NVIM_VERSION_DEF_H}"
+ DEPENDS "${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef-$<CONFIG>.h")
+
+set(LUA_GEN ${LUA_GEN_PRG} ${GENERATOR_PRELOAD} ${PROJECT_SOURCE_DIR} $<TARGET_FILE:nlua0>)
+set(LUA_GEN_DEPS ${GENERATOR_PRELOAD} $<TARGET_FILE:nlua0>)
+
# NVIM_GENERATED_FOR_HEADERS: generated headers to be included in headers
# NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources
# NVIM_GENERATED_SOURCES: generated source files
# These lists must be mutually exclusive.
foreach(sfile ${NVIM_SOURCES}
- "${CMAKE_CURRENT_LIST_DIR}/regexp_bt.c"
- "${CMAKE_CURRENT_LIST_DIR}/regexp_nfa.c"
${GENERATED_API_DISPATCH}
"${GENERATED_UI_EVENTS_CALL}"
"${GENERATED_UI_EVENTS_REMOTE}"
- "${GENERATED_KEYSETS}"
"${GENERATED_UI_EVENTS_CLIENT}"
)
get_filename_component(full_d ${sfile} DIRECTORY)
@@ -514,15 +540,15 @@ foreach(sfile ${NVIM_SOURCES}
set(PREPROC_OUTPUT -E -o ${gf_i})
endif()
- set(depends "${HEADER_GENERATOR}" "${sfile}")
+ set(depends "${HEADER_GENERATOR}" "${sfile}" "${LUA_GEN_DEPS}")
if("${f}" STREQUAL "version.c")
# Ensure auto/versiondef_git.h exists after "make clean".
- list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}")
+ list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}" "${NVIM_VERSION_DEF_H}")
endif()
add_custom_command(
OUTPUT "${gf_c_h}" "${gf_h_h}"
COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags}
- COMMAND "${LUA_PRG}" "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
+ COMMAND ${LUA_GEN} "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
DEPENDS ${depends})
list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}")
list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}")
@@ -542,13 +568,15 @@ add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
add_custom_command(
OUTPUT ${GENERATED_API_DISPATCH} ${GENERATED_FUNCS_METADATA}
- ${API_METADATA} ${LUA_API_C_BINDINGS}
- COMMAND ${LUA_GEN_PRG} ${API_DISPATCH_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
+ ${API_METADATA} ${LUA_API_C_BINDINGS} ${GENERATED_KEYSETS_DEFS}
+ COMMAND ${LUA_GEN} ${API_DISPATCH_GENERATOR}
${GENERATED_API_DISPATCH}
${GENERATED_FUNCS_METADATA} ${API_METADATA}
${LUA_API_C_BINDINGS}
+ ${GENERATED_KEYSETS_DEFS}
${API_HEADERS}
DEPENDS
+ ${LUA_GEN_DEPS}
${API_HEADERS}
${MSGPACK_RPC_HEADERS}
${API_DISPATCH_GENERATOR}
@@ -561,25 +589,32 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E env
"LUAC_PRG=${LUAC_PRG}"
${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE}
+ # NB: vim._init_packages and vim.inspect must be be first and second ones
+ # respectively, otherwise --luamod-dev won't work properly.
${LUA_INIT_PACKAGES_MODULE_SOURCE} "vim._init_packages"
${LUA_INSPECT_MODULE_SOURCE} "vim.inspect"
${LUA_EDITOR_MODULE_SOURCE} "vim._editor"
- ${LUA_SHARED_MODULE_SOURCE} "vim.shared"
- ${LUA_F_MODULE_SOURCE} "vim.F"
- ${LUA_META_MODULE_SOURCE} "vim._meta"
${LUA_FILETYPE_MODULE_SOURCE} "vim.filetype"
+ ${LUA_FS_MODULE_SOURCE} "vim.fs"
+ ${LUA_F_MODULE_SOURCE} "vim.F"
${LUA_KEYMAP_MODULE_SOURCE} "vim.keymap"
+ ${LUA_LOADER_MODULE_SOURCE} "vim.loader"
+ ${LUA_DEFAULTS_MODULE_SOURCE} "vim._defaults"
+ ${LUA_OPTIONS_MODULE_SOURCE} "vim._options"
+ ${LUA_SHARED_MODULE_SOURCE} "vim.shared"
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_INIT_PACKAGES_MODULE_SOURCE}
- ${LUA_EDITOR_MODULE_SOURCE}
- ${LUA_SHARED_MODULE_SOURCE}
${LUA_INSPECT_MODULE_SOURCE}
- ${LUA_F_MODULE_SOURCE}
- ${LUA_META_MODULE_SOURCE}
+ ${LUA_EDITOR_MODULE_SOURCE}
${LUA_FILETYPE_MODULE_SOURCE}
- ${LUA_LOAD_PACKAGE_MODULE_SOURCE}
+ ${LUA_FS_MODULE_SOURCE}
+ ${LUA_F_MODULE_SOURCE}
${LUA_KEYMAP_MODULE_SOURCE}
+ ${LUA_LOADER_MODULE_SOURCE}
+ ${LUA_DEFAULTS_MODULE_SOURCE}
+ ${LUA_OPTIONS_MODULE_SOURCE}
+ ${LUA_SHARED_MODULE_SOURCE}
VERBATIM
)
@@ -592,13 +627,14 @@ add_custom_command(
${GENERATED_UI_EVENTS_REMOTE}
${GENERATED_UI_EVENTS_METADATA}
${GENERATED_UI_EVENTS_CLIENT}
- COMMAND ${LUA_GEN_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}
- ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
- ${GENERATED_UI_EVENTS_CALL}
- ${GENERATED_UI_EVENTS_REMOTE}
- ${GENERATED_UI_EVENTS_METADATA}
- ${GENERATED_UI_EVENTS_CLIENT}
+ COMMAND ${LUA_GEN} ${API_UI_EVENTS_GENERATOR}
+ ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
+ ${GENERATED_UI_EVENTS_CALL}
+ ${GENERATED_UI_EVENTS_REMOTE}
+ ${GENERATED_UI_EVENTS_METADATA}
+ ${GENERATED_UI_EVENTS_CLIENT}
DEPENDS
+ ${LUA_GEN_DEPS}
${API_UI_EVENTS_GENERATOR}
${GENERATOR_C_GRAMMAR}
${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h
@@ -614,7 +650,6 @@ list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_API_DISPATCH}"
"${GENERATED_EX_CMDS_DEFS}"
"${GENERATED_EVENTS_NAMES_MAP}"
- "${GENERATED_KEYSETS}"
"${GENERATED_OPTIONS}"
"${GENERATED_UNICODE_TABLES}"
"${VIM_MODULE_FILE}"
@@ -625,35 +660,25 @@ list(APPEND NVIM_GENERATED_SOURCES
)
add_custom_command(OUTPUT ${GENERATED_EX_CMDS_ENUM} ${GENERATED_EX_CMDS_DEFS}
- COMMAND ${LUA_PRG} ${EX_CMDS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_INCLUDES_DIR} ${GENERATED_DIR}
- DEPENDS ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua
+ COMMAND ${LUA_GEN} ${EX_CMDS_GENERATOR} ${GENERATED_INCLUDES_DIR} ${GENERATED_DIR}
+ DEPENDS ${LUA_GEN_DEPS} ${EX_CMDS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/ex_cmds.lua
)
add_custom_command(OUTPUT ${GENERATED_FUNCS} ${FUNCS_DATA}
- COMMAND ${LUA_PRG} ${FUNCS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${LUA_SHARED_MODULE_SOURCE} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
- DEPENDS ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${API_METADATA}
+ COMMAND ${LUA_GEN} ${FUNCS_GENERATOR} ${GENERATED_DIR} ${API_METADATA} ${FUNCS_DATA}
+ DEPENDS ${LUA_GEN_DEPS} ${FUNCS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/eval.lua ${API_METADATA}
)
list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_FUNCS}")
add_custom_command(OUTPUT ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
- COMMAND ${LUA_PRG} ${EVENTS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
- DEPENDS ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua
-)
-
-add_custom_command(OUTPUT ${GENERATED_KEYSETS} ${GENERATED_KEYSETS_DEFS}
- COMMAND ${LUA_PRG} ${KEYSETS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${LUA_SHARED_MODULE_SOURCE} ${GENERATED_KEYSETS} ${GENERATED_KEYSETS_DEFS}
- DEPENDS ${KEYSETS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/api/keysets.lua ${GENERATOR_HASHY}
+ COMMAND ${LUA_GEN} ${EVENTS_GENERATOR} ${GENERATED_EVENTS_ENUM} ${GENERATED_EVENTS_NAMES_MAP}
+ DEPENDS ${LUA_GEN_DEPS} ${EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/auevents.lua
)
add_custom_command(OUTPUT ${GENERATED_OPTIONS}
- COMMAND ${LUA_PRG} ${OPTIONS_GENERATOR}
- ${CMAKE_CURRENT_LIST_DIR} ${GENERATED_OPTIONS}
- DEPENDS ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua
+ COMMAND ${LUA_GEN} ${OPTIONS_GENERATOR} ${GENERATED_OPTIONS}
+ DEPENDS ${LUA_GEN_DEPS} ${OPTIONS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR}/options.lua
)
# NVIM_GENERATED_FOR_SOURCES and NVIM_GENERATED_FOR_HEADERS must be mutually exclusive.
@@ -664,43 +689,26 @@ foreach(hfile ${NVIM_GENERATED_FOR_HEADERS})
endif()
endforeach()
-if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
- target_link_libraries(main_lib INTERFACE pthread c++abi)
-endif()
-
-if(WIN32)
- target_link_libraries(main_lib INTERFACE netapi32)
-endif()
-
-if(UNIX)
- target_link_libraries(main_lib INTERFACE m)
- if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS")
- target_link_libraries(main_lib INTERFACE util)
- endif()
-endif()
-
-if(NOT LUAJIT_FOUND)
- message(STATUS "luajit not found, skipping unit tests")
-elseif(CMAKE_BUILD_TYPE MATCHES Debug)
+if(PREFER_LUA)
+ message(STATUS "luajit not used, skipping unit tests")
+else()
glob_wrapper(UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c)
- list(APPEND NVIM_SOURCES ${UNIT_TEST_FIXTURES})
- target_compile_definitions(main_lib INTERFACE UNIT_TESTING)
+ target_sources(nvim PRIVATE ${UNIT_TEST_FIXTURES})
+ target_compile_definitions(nvim PRIVATE UNIT_TESTING)
endif()
-target_sources(nvim PRIVATE ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS}
- ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS})
-
-set_target_properties(nvim
- PROPERTIES
- EXPORT_COMPILE_COMMANDS ON
- ENABLE_EXPORTS TRUE)
+target_sources(main_lib INTERFACE
+ ${NVIM_GENERATED_FOR_SOURCES}
+ ${NVIM_GENERATED_FOR_HEADERS}
+ ${NVIM_GENERATED_SOURCES}
+ ${NVIM_SOURCES}
+ ${NVIM_HEADERS}
+ ${EXTERNAL_SOURCES}
+ ${EXTERNAL_HEADERS})
-if(${CMAKE_VERSION} VERSION_LESS 3.20)
- set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
-endif()
+target_sources(nlua0 PUBLIC ${NLUA0_SOURCES})
-target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv_lib)
+target_link_libraries(nvim PRIVATE main_lib PUBLIC libuv)
install_helper(TARGETS nvim)
if(MSVC)
install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL)
@@ -709,8 +717,11 @@ endif()
if(ENABLE_LTO)
include(CheckIPOSupported)
check_ipo_supported(RESULT IPO_SUPPORTED)
- if(IPO_SUPPORTED AND (NOT CMAKE_BUILD_TYPE MATCHES Debug))
- set_target_properties(nvim PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE)
+ if(IPO_SUPPORTED)
+ set_target_properties(nvim PROPERTIES
+ INTERPROCEDURAL_OPTIMIZATION_RELEASE TRUE
+ INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO TRUE
+ INTERPROCEDURAL_OPTIMIZATION_MINSIZEREL TRUE)
endif()
endif()
@@ -725,9 +736,10 @@ if(WIN32)
add_custom_target(nvim_dll_deps DEPENDS nvim
COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/windows_runtime_deps
COMMAND ${CMAKE_COMMAND}
- "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}"
- -DBINARY="${PROJECT_BINARY_DIR}/bin/nvim${CMAKE_EXECUTABLE_SUFFIX}"
- -DDST=${PROJECT_BINARY_DIR}/windows_runtime_deps
+ -D CMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}
+ -D BINARY="${PROJECT_BINARY_DIR}/bin/nvim${CMAKE_EXECUTABLE_SUFFIX}"
+ -D DST=${PROJECT_BINARY_DIR}/windows_runtime_deps
+ -D CI_BUILD=${CI_BUILD}
-P ${PROJECT_SOURCE_DIR}/cmake/WindowsDllCopy.cmake)
add_dependencies(nvim_runtime_deps nvim_dll_deps)
@@ -738,77 +750,22 @@ if(WIN32)
set(EXTERNAL_BLOBS_SCRIPT
"file(MAKE_DIRECTORY \"${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms\")")
foreach(DEP_FILE IN ITEMS
- curl-ca-bundle.crt
- curl.exe
- diff.exe
- tee.exe
- win32yank.exe
- xxd.exe
-
- # Dependencies for neovim-qt
- bearer/qgenericbearer.dll
- iconengines/qsvgicon.dll
- imageformats/qgif.dll
- imageformats/qicns.dll
- imageformats/qico.dll
- imageformats/qjpeg.dll
- imageformats/qsvg.dll
- imageformats/qtga.dll
- imageformats/qtiff.dll
- imageformats/qwbmp.dll
- imageformats/qwebp.dll
- platforms/qwindows.dll
- styles/qwindowsvistastyle.dll
- translations/qt_ar.qm
- translations/qt_bg.qm
- translations/qt_ca.qm
- translations/qt_cs.qm
- translations/qt_da.qm
- translations/qt_de.qm
- translations/qt_en.qm
- translations/qt_es.qm
- translations/qt_fi.qm
- translations/qt_fr.qm
- translations/qt_gd.qm
- translations/qt_he.qm
- translations/qt_hu.qm
- translations/qt_it.qm
- translations/qt_ja.qm
- translations/qt_ko.qm
- translations/qt_lv.qm
- translations/qt_pl.qm
- translations/qt_ru.qm
- translations/qt_sk.qm
- translations/qt_uk.qm
- D3Dcompiler_47.dll
- libEGL.dll
- libgcc_s_seh-1.dll
- libGLESv2.dll
- libstdc++-6.dll
- libwinpthread-1.dll
- nvim-qt.exe
- opengl32sw.dll
- Qt5Core.dll
- Qt5Gui.dll
- Qt5Network.dll
- Qt5Svg.dll
- Qt5Widgets.dll
-
- )
- get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY)
- set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n"
- "file(COPY \"${DEPS_PREFIX}/bin/${DEP_FILE}\"
- DESTINATION \"${PROJECT_BINARY_DIR}/windows_runtime_deps/${DEP_FILE_DIR}\")")
+ cat.exe
+ tee.exe
+ win32yank.exe
+ xxd.exe)
+ get_filename_component(DEP_FILE_DIR ${DEP_FILE} DIRECTORY)
+ set(EXTERNAL_BLOBS_SCRIPT "${EXTERNAL_BLOBS_SCRIPT}\n"
+ "file(COPY \"${DEPS_PREFIX}/bin/${DEP_FILE}\"
+ DESTINATION \"${PROJECT_BINARY_DIR}/windows_runtime_deps/${DEP_FILE_DIR}\")")
endforeach()
file(WRITE ${PROJECT_BINARY_DIR}/external_blobs.cmake ${EXTERNAL_BLOBS_SCRIPT})
add_custom_target(external_blobs
COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/external_blobs.cmake)
- set_target_properties(external_blobs PROPERTIES FOLDER deps)
add_dependencies(nvim_runtime_deps external_blobs)
else()
add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046.
endif()
-set_target_properties(nvim_runtime_deps PROPERTIES FOLDER deps)
file(MAKE_DIRECTORY ${BINARY_LIB_DIR})
@@ -824,14 +781,16 @@ install(DIRECTORY ${BINARY_LIB_DIR}
DESTINATION ${CMAKE_INSTALL_LIBDIR}/nvim/
USE_SOURCE_PERMISSIONS)
-add_library(
- libnvim
- STATIC
- EXCLUDE_FROM_ALL
- ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
- ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}
-)
+if(NOT PREFER_LUA)
+ # install luajit runtime files if bundled
+ if(EXISTS ${LUAJIT_RUNTIME_DIR})
+ install(DIRECTORY ${LUAJIT_RUNTIME_DIR}
+ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/nvim/runtime/lua
+ USE_SOURCE_PERMISSIONS)
+ endif()
+endif()
+
+add_library(libnvim STATIC EXCLUDE_FROM_ALL)
if(MSVC)
set(LIBNVIM_NAME libnvim)
else()
@@ -843,94 +802,63 @@ set_target_properties(
OUTPUT_NAME ${LIBNVIM_NAME}
)
target_compile_definitions(libnvim PRIVATE MAKE_LIB)
-target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv_lib)
-
-if(CLANG_ASAN_UBSAN)
- message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
- if(CI_BUILD)
- # Try to recover from all sanitize issues so we get reports about all failures
- target_compile_options(nvim PRIVATE -fsanitize-recover=all)
- else()
- target_compile_options(nvim PRIVATE -fno-sanitize-recover=all)
- endif()
- target_compile_options(nvim PRIVATE
- -fno-omit-frame-pointer
- -fno-optimize-sibling-calls
- -fsanitize=address
- -fsanitize=undefined
- -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist)
- target_link_libraries(nvim PRIVATE -fsanitize=address -fsanitize=undefined)
-elseif(CLANG_MSAN)
- message(STATUS "Enabling Clang memory sanitizer for nvim.")
- target_compile_options(nvim PRIVATE
- -fsanitize=memory
- -fsanitize-memory-track-origins
- -fno-omit-frame-pointer
- -fno-optimize-sibling-calls)
- target_link_libraries(nvim PRIVATE -fsanitize=memory -fsanitize-memory-track-origins)
-elseif(CLANG_TSAN)
- message(STATUS "Enabling Clang thread sanitizer for nvim.")
- target_compile_options(nvim PRIVATE -fsanitize=thread -fPIE)
- target_link_libraries(nvim PRIVATE -fsanitize=thread)
-endif()
+target_link_libraries(libnvim PRIVATE main_lib PUBLIC libuv)
-function(get_test_target prefix sfile relative_path_var target_var)
- get_filename_component(full_d "${sfile}" DIRECTORY)
- file(RELATIVE_PATH d "${PROJECT_SOURCE_DIR}/src/nvim" "${full_d}")
- if(d MATCHES "^[.][.]")
- file(RELATIVE_PATH d "${GENERATED_DIR}" "${full_d}")
- endif()
- get_filename_component(r "${sfile}" NAME)
- if(NOT d MATCHES "^[.]?$")
- set(r "${d}/${r}")
- endif()
- string(REGEX REPLACE "[/.]" "-" suffix "${r}")
- set(${relative_path_var} ${r} PARENT_SCOPE)
- if(prefix STREQUAL "")
- set(${target_var} "${suffix}" PARENT_SCOPE)
- else()
- set(${target_var} "${prefix}-${suffix}" PARENT_SCOPE)
- endif()
-endfunction()
+#-------------------------------------------------------------------------------
+# Lint
+#-------------------------------------------------------------------------------
+find_program(CLANG_TIDY_PRG clang-tidy)
+set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
if(WIN32)
- set(NO_SINGLE_CHECK_HEADERS
+ list(APPEND EXCLUDE_CLANG_TIDY
os/pty_process_unix.h
os/unix_defs.h)
else()
- set(NO_SINGLE_CHECK_HEADERS
+ list(APPEND EXCLUDE_CLANG_TIDY
os/win_defs.h
os/pty_process_win.h
os/pty_conpty_win.h
os/os_win_console.h)
endif()
-foreach(hfile ${NVIM_HEADERS})
- get_test_target(test-includes "${hfile}" relative_path texe)
-
- if(NOT ${hfile} MATCHES "[.](c|in)[.]h$")
- set(tsource "${GENERATED_DIR}/${relative_path}.test-include.c")
- write_file("${tsource}" "#include \"${hfile}\"\nint main(int argc, char **argv) { return 0; }")
- add_executable(
- ${texe}
- EXCLUDE_FROM_ALL
- ${tsource} ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_HEADERS})
- target_link_libraries(${texe} PRIVATE main_lib)
- set_target_properties(${texe} PROPERTIES FOLDER test)
-
- list(FIND NO_SINGLE_CHECK_HEADERS "${relative_path}" hfile_exclude_idx)
- if(${hfile_exclude_idx} EQUAL -1)
- list(APPEND HEADER_CHECK_TARGETS ${texe})
- endif()
- endif()
-endforeach()
-add_custom_target(check-single-includes DEPENDS ${HEADER_CHECK_TARGETS})
+add_glob_target(
+ TARGET lintc-clang-tidy
+ COMMAND ${CLANG_TIDY_PRG}
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FLAGS --quiet
+ EXCLUDE ${EXCLUDE_CLANG_TIDY})
+
+# These are the same warnings as https://neovim.io/doc/reports/clang/. The
+# checks we ignore are meant to be removed eventually, but we can only do so
+# after we properly fix the problems without breaking CI.
+add_glob_target(
+ TARGET clang-analyzer
+ COMMAND ${CLANG_TIDY_PRG}
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FLAGS --quiet
+ --checks='
+ -*,
+ clang-analyzer-*,
+ -clang-analyzer-core.NonNullParamChecker,
+ -clang-analyzer-core.NullDereference,
+ -clang-analyzer-core.UndefinedBinaryOperatorResult,
+ -clang-analyzer-core.uninitialized.Assign,
+ -clang-analyzer-optin.performance.Padding,
+ -clang-analyzer-security.insecureAPI.strcpy,
+ '
+ EXCLUDE ${EXCLUDE_CLANG_TIDY})
+
+add_custom_target(copy_compile_commands
+ COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_BINARY_DIR}/compile_commands.json ${PROJECT_SOURCE_DIR}/compile_commands.json)
+add_dependencies(copy_compile_commands nvim)
+add_dependencies(lintc-clang-tidy copy_compile_commands)
+add_dependencies(clang-analyzer copy_compile_commands)
if(CI_BUILD)
set(LINT_OUTPUT_FORMAT gh_action)
else()
set(LINT_OUTPUT_FORMAT vs7)
endif()
-
add_glob_target(
TARGET lintc-clint
COMMAND ${PROJECT_SOURCE_DIR}/src/clint.py
@@ -939,21 +867,17 @@ add_glob_target(
EXCLUDE
tui/terminfo_defs.h)
-add_custom_target(uncrustify-version
- COMMAND ${CMAKE_COMMAND}
- -D UNCRUSTIFY_PRG=${UNCRUSTIFY_PRG}
- -D CONFIG_FILE=${PROJECT_SOURCE_DIR}/src/uncrustify.cfg
- -P ${PROJECT_SOURCE_DIR}/cmake/CheckUncrustifyVersion.cmake)
+set(UNCRUSTIFY_PRG ${DEPS_BIN_DIR}/uncrustify)
+set(UNCRUSTIFY_CONFIG ${PROJECT_SOURCE_DIR}/src/uncrustify.cfg)
+
+add_custom_target(uncrustify_update_config
+ ${UNCRUSTIFY_PRG} -c ${UNCRUSTIFY_CONFIG} --update-config-with-doc -o ${UNCRUSTIFY_CONFIG})
add_glob_target(
TARGET lintc-uncrustify
COMMAND ${UNCRUSTIFY_PRG}
- FLAGS -c "${PROJECT_SOURCE_DIR}/src/uncrustify.cfg" -q --check
+ FLAGS -c ${UNCRUSTIFY_CONFIG} -q --check
FILES ${LINT_NVIM_SOURCES})
-add_dependencies(lintc-uncrustify uncrustify-version)
-
-add_custom_target(lintc)
-add_dependencies(lintc lintc-clint lintc-uncrustify)
add_custom_target(formatc
COMMAND ${CMAKE_COMMAND}
@@ -961,7 +885,13 @@ add_custom_target(formatc
-D LANG=c
-P ${PROJECT_SOURCE_DIR}/cmake/Format.cmake
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
-add_dependencies(formatc uncrustify-version)
+
+add_dependencies(lintc-uncrustify uncrustify_update_config)
+add_dependencies(formatc uncrustify_update_config)
+add_dependencies(uncrustify_update_config uncrustify)
+
+add_custom_target(lintc)
+add_dependencies(lintc lintc-clint lintc-uncrustify lintc-clang-tidy)
add_custom_target(generated-sources DEPENDS
${NVIM_GENERATED_FOR_SOURCES}
@@ -971,5 +901,64 @@ add_custom_target(generated-sources DEPENDS
add_subdirectory(po)
-include(GetCompileFlags)
-get_compile_flags(NVIM_VERSION_CFLAGS)
+#-------------------------------------------------------------------------------
+# Docs
+#-------------------------------------------------------------------------------
+
+set(VIMDOC_FILES
+ ${NVIM_RUNTIME_DIR}/doc/api.mpack
+ ${NVIM_RUNTIME_DIR}/doc/api.txt
+ ${NVIM_RUNTIME_DIR}/doc/diagnostic.mpack
+ ${NVIM_RUNTIME_DIR}/doc/diagnostic.txt
+ ${NVIM_RUNTIME_DIR}/doc/lsp.mpack
+ ${NVIM_RUNTIME_DIR}/doc/lsp.txt
+ ${NVIM_RUNTIME_DIR}/doc/lua.mpack
+ ${NVIM_RUNTIME_DIR}/doc/lua.txt
+ ${NVIM_RUNTIME_DIR}/doc/treesitter.mpack
+ ${NVIM_RUNTIME_DIR}/doc/treesitter.txt
+)
+
+glob_wrapper(API_SOURCES ${PROJECT_SOURCE_DIR}/src/nvim/api/*.c)
+
+glob_wrapper(LUA_SOURCES
+ ${NVIM_RUNTIME_DIR}/lua/vim/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/filetype/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/lsp/*.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/treesitter/*.lua
+)
+
+add_custom_command(
+ OUTPUT ${VIMDOC_FILES}
+ COMMAND ${PROJECT_SOURCE_DIR}/scripts/gen_vimdoc.py
+ DEPENDS
+ nvim
+ ${API_SOURCES}
+ ${LUA_SOURCES}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+)
+
+set(GEN_EVAL_FILES
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/vimfn.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/api.lua
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/api_keysets.lua
+ ${NVIM_RUNTIME_DIR}/doc/builtin.txt
+ ${NVIM_RUNTIME_DIR}/lua/vim/_meta/options.lua
+ ${NVIM_RUNTIME_DIR}/doc/options.txt
+)
+
+add_custom_command(
+ OUTPUT ${GEN_EVAL_FILES}
+ COMMAND $<TARGET_FILE:nvim> -l ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
+ DEPENDS
+ ${API_METADATA}
+ ${PROJECT_SOURCE_DIR}/scripts/gen_eval_files.lua
+ ${PROJECT_SOURCE_DIR}/src/nvim/eval.lua
+ ${PROJECT_SOURCE_DIR}/src/nvim/options.lua
+ ${NVIM_RUNTIME_DIR}/doc/api.mpack
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+)
+
+add_custom_target(doc-eval DEPENDS ${GEN_EVAL_FILES})
+add_custom_target(doc-vim DEPENDS ${VIMDOC_FILES})
+add_custom_target(doc)
+add_dependencies(doc doc-vim doc-eval)
diff --git a/src/nvim/README.md b/src/nvim/README.md
index 6876227e48..b484d59cb3 100644
--- a/src/nvim/README.md
+++ b/src/nvim/README.md
@@ -1,8 +1,7 @@
Nvim core
=========
-Module-specific details are documented at the top of each module (`terminal.c`,
-`screen.c`, …).
+Module-specific details are documented at the top of each module (`terminal.c`, `undo.c`, …).
See `:help dev` for guidelines.
@@ -29,17 +28,17 @@ Logs
Low-level log messages sink to `$NVIM_LOG_FILE`.
-UI events are logged at DEBUG level (`LOGLVL_DBG`).
+UI events are logged at DEBUG level.
rm -rf build/
- make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0"
+ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG"
Use `LOG_CALLSTACK()` (Linux only) to log the current stacktrace. To log to an
alternate file (e.g. stderr) use `LOG_CALLSTACK_TO_FILE(FILE*)`. Requires
`-no-pie` ([ref](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=860394#15)):
rm -rf build/
- make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0 -DCMAKE_C_FLAGS=-no-pie"
+ make CMAKE_EXTRA_FLAGS="-DLOG_DEBUG -DCMAKE_C_FLAGS=-no-pie"
Many log messages have a shared prefix, such as "UI" or "RPC". Use the shell to
filter the log, e.g. at DEBUG level you might want to exclude UI messages:
@@ -60,9 +59,9 @@ Requires clang 3.4 or later, and `llvm-symbolizer` must be in `$PATH`:
Build Nvim with sanitizer instrumentation (choose one):
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON"
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_MSAN=ON"
- CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_TSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_ASAN_UBSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_MSAN=ON"
+ CC=clang make CMAKE_EXTRA_FLAGS="-DENABLE_TSAN=ON"
Create a directory to store logs:
@@ -70,10 +69,9 @@ Create a directory to store logs:
Configure the sanitizer(s) via these environment variables:
- # Change to detect_leaks=1 to detect memory leaks (slower).
+ # Change to detect_leaks=1 to detect memory leaks (slower, noisier).
export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan"
# Show backtraces in the logs.
- export UBSAN_OPTIONS=print_stacktrace=1
export MSAN_OPTIONS="log_path=${HOME}/logs/msan"
export TSAN_OPTIONS="log_path=${HOME}/logs/tsan"
@@ -81,6 +79,13 @@ Logs will be written to `${HOME}/logs/*san.PID` then.
For more information: https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
+Reproducible build
+------------------
+
+To make a reproducible build of Nvim, set cmake variable `LUA_GEN_PRG` to
+a LuaJIT binary built with `LUAJIT_SECURITY_PRN=0`. See commit
+cb757f2663e6950e655c6306d713338dfa66b18d.
+
Debug: Performance
------------------
@@ -191,6 +196,31 @@ possible to see exactly what terminfo values Nvim is using on any system.
nvim -V3log
+### TUI Debugging with gdb/lldb
+
+Launching the nvim TUI involves two processes, one for main editor state and one
+for rendering the TUI. Both of these processes use the nvim binary, so somewhat
+confusingly setting a breakpoint in either will generally succeed but may not be
+hit depending on which process the breakpoints were set in.
+
+To debug the main process, you can debug the nvim binary with the `--headless`
+flag which does not launch the TUI and will allow you to set breakpoints in code
+not related to TUI rendering like so:
+
+ lldb -- ./build/bin/nvim --headless --listen ~/.cache/nvim/debug-server.pipe
+
+While in lldb, enter `run`. You can then attach to the headless process in a
+new terminal window to interact with the editor like so:
+
+ ./build/bin/nvim --remote-ui --server ~/.cache/nvim/debug-server.pipe
+
+Conversely for debugging TUI rendering, you can start a headless process and
+debug the remote-ui process multiple times without losing editor state.
+
+For details on using nvim-dap and automatically debugging the child (main)
+process, see
+[here](https://zignar.net/2023/02/17/debugging-neovim-with-neovim-and-nvim-dap/)
+
### TUI trace
The ancient `script` command is still the "state of the art" for tracing
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index a2cb297b15..08d9d8e117 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -1,27 +1,27 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
+#include "klib/kvec.h"
#include "nvim/api/autocmd.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.c.generated.h"
@@ -32,13 +32,11 @@
// Copy string or array of strings into an empty array.
// Get the event number, unless it is an error. Then goto `goto_name`.
#define GET_ONE_EVENT(event_nr, event_str, goto_name) \
- char *__next_ev; \
event_T event_nr = \
- event_name2nr(event_str.data.string.data, &__next_ev); \
- if (event_nr >= NUM_EVENTS) { \
- api_set_error(err, kErrorTypeValidation, "unexpected event"); \
+ event_name2nr_str(event_str.data.string); \
+ VALIDATE_S((event_nr < NUM_EVENTS), "event", event_str.data.string.data, { \
goto goto_name; \
- }
+ });
// ID for associating autocmds created via nvim_create_autocmd
// Used to delete autocmds from nvim_del_autocmd
@@ -47,19 +45,20 @@ static int64_t next_autocmd_id = 1;
/// Get all autocommands that match the corresponding {opts}.
///
/// These examples will get autocommands matching ALL the given criteria:
-/// <pre>lua
-/// -- Matches all criteria
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// event = {"BufEnter", "BufWinEnter"},
-/// pattern = {"*.c", "*.h"}
-/// })
///
-/// -- All commands from one group
-/// autocommands = vim.api.nvim_get_autocmds({
-/// group = "MyGroup",
-/// })
-/// </pre>
+/// ```lua
+/// -- Matches all criteria
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// event = {"BufEnter", "BufWinEnter"},
+/// pattern = {"*.c", "*.h"}
+/// })
+///
+/// -- All commands from one group
+/// autocommands = vim.api.nvim_get_autocmds({
+/// group = "MyGroup",
+/// })
+/// ```
///
/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
/// match any combination of them.
@@ -107,25 +106,24 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
group = augroup_find(opts->group.data.string.data);
- if (group < 0) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ VALIDATE_S((group >= 0), "group", opts->group.data.string.data, {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
group = (int)opts->group.data.integer;
- char *name = augroup_name(group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup passed.");
+ char *name = group == 0 ? NULL : augroup_name(group);
+ VALIDATE_INT(augroup_exists(name), "group", opts->group.data.integer, {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "group must be a string or an integer.");
- goto cleanup;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
+ goto cleanup;
+ });
}
- if (opts->event.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, event)) {
check_event = true;
Object v = opts->event;
@@ -134,57 +132,49 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
event_set[event_nr] = true;
} else if (v.type == kObjectTypeArray) {
FOREACH_ITEM(v.data.array, event_v, {
- if (event_v.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Every event must be a string in 'event'");
+ VALIDATE_T("event item", kObjectTypeString, event_v.type, {
goto cleanup;
- }
+ });
GET_ONE_EVENT(event_nr, event_v, cleanup);
event_set[event_nr] = true;
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "Not a valid 'event' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_EXP(false, "event", "String or Array", NULL, {
+ goto cleanup;
+ });
}
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ VALIDATE((!HAS_KEY(opts, get_autocmds, pattern) || !HAS_KEY(opts, get_autocmds, buffer)),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int pattern_filter_count = 0;
- if (opts->pattern.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, get_autocmds, pattern)) {
Object v = opts->pattern;
if (v.type == kObjectTypeString) {
pattern_filters[pattern_filter_count] = v.data.string.data;
pattern_filter_count += 1;
} else if (v.type == kObjectTypeArray) {
- if (v.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err, kErrorTypeValidation,
- "Too many patterns. Please limit yourself to %d or fewer",
- AUCMD_MAX_PATTERNS);
+ VALIDATE((v.data.array.size <= AUCMD_MAX_PATTERNS),
+ "Too many patterns (maximum of %d)", AUCMD_MAX_PATTERNS, {
goto cleanup;
- }
+ });
FOREACH_ITEM(v.data.array, item, {
- if (item.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string");
+ VALIDATE_T("pattern", kObjectTypeString, item.type, {
goto cleanup;
- }
+ });
pattern_filters[pattern_filter_count] = item.data.string.data;
pattern_filter_count += 1;
});
} else {
- api_set_error(err, kErrorTypeValidation,
- "Not a valid 'pattern' value. Must be a string or an array");
- goto cleanup;
+ VALIDATE_EXP(false, "pattern", "String or Array", api_typename(v.type), {
+ goto cleanup;
+ });
}
}
@@ -194,34 +184,33 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
} else if (opts->buffer.type == kObjectTypeArray) {
if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) {
- api_set_error(err,
- kErrorTypeValidation,
- "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS);
+ api_set_error(err, kErrorTypeValidation, "Too many buffers (maximum of %d)",
+ AUCMD_MAX_PATTERNS);
goto cleanup;
}
FOREACH_ITEM(opts->buffer.data.array, bufnr, {
- if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer");
+ VALIDATE_EXP((bufnr.type == kObjectTypeInteger || bufnr.type == kObjectTypeBuffer),
+ "buffer", "Integer", api_typename(bufnr.type), {
goto cleanup;
- }
+ });
buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
- snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal));
+ snprintf(pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
+ ADD(buffers, CSTR_TO_OBJ(pattern_buflocal));
+ });
+ } else if (HAS_KEY(opts, get_autocmds, buffer)) {
+ VALIDATE_EXP(false, "buffer", "Integer or Array", api_typename(opts->buffer.type), {
+ goto cleanup;
});
- } else if (opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid value for 'buffer': must be an integer or array of integers");
- goto cleanup;
}
FOREACH_ITEM(buffers, bufnr, {
@@ -234,8 +223,12 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
continue;
}
- for (AutoPat *ap = au_get_autopat_for_event(event); ap != NULL; ap = ap->next) {
- if (ap->cmds == NULL) {
+ AutoCmdVec *acs = au_get_autocmds_for_event(event);
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+
+ if (ap == NULL) {
continue;
}
@@ -247,19 +240,16 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
// Skip 'pattern' from invalid patterns if passed.
if (pattern_filter_count > 0) {
bool passed = false;
- for (int i = 0; i < pattern_filter_count; i++) {
- assert(i < AUCMD_MAX_PATTERNS);
- assert(pattern_filters[i]);
+ for (int j = 0; j < pattern_filter_count; j++) {
+ assert(j < AUCMD_MAX_PATTERNS);
+ assert(pattern_filters[j]);
- char *pat = pattern_filters[i];
+ char *pat = pattern_filters[j];
int patlen = (int)strlen(pat);
if (aupat_is_buflocal(pat, patlen)) {
- aupat_normalize_buflocal_pat(pattern_buflocal,
- pat,
- patlen,
+ aupat_normalize_buflocal_pat(pattern_buflocal, pat, patlen,
aupat_get_buflocal_nr(pat, patlen));
-
pat = pattern_buflocal;
}
@@ -274,85 +264,71 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err)
}
}
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ Dictionary autocmd_info = ARRAY_DICT_INIT;
- Dictionary autocmd_info = ARRAY_DICT_INIT;
-
- if (ap->group != AUGROUP_DEFAULT) {
- PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
- PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
- }
+ if (ap->group != AUGROUP_DEFAULT) {
+ PUT(autocmd_info, "group", INTEGER_OBJ(ap->group));
+ PUT(autocmd_info, "group_name", CSTR_TO_OBJ(augroup_name(ap->group)));
+ }
- if (ac->id > 0) {
- PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
- }
+ if (ac->id > 0) {
+ PUT(autocmd_info, "id", INTEGER_OBJ(ac->id));
+ }
- if (ac->desc != NULL) {
- PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
- }
+ if (ac->desc != NULL) {
+ PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc));
+ }
- if (ac->exec.type == CALLABLE_CB) {
- PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
+ if (ac->exec.type == CALLABLE_CB) {
+ PUT(autocmd_info, "command", STRING_OBJ(STRING_INIT));
- Callback *cb = &ac->exec.callable.cb;
- switch (cb->type) {
- case kCallbackLua:
- if (nlua_ref_is_function(cb->data.luaref)) {
- PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
- }
- break;
- case kCallbackFuncref:
- case kCallbackPartial:
- PUT(autocmd_info, "callback", STRING_OBJ(cstr_as_string(callback_to_string(cb))));
- break;
- default:
- abort();
+ Callback *cb = &ac->exec.callable.cb;
+ switch (cb->type) {
+ case kCallbackLua:
+ if (nlua_ref_is_function(cb->data.luaref)) {
+ PUT(autocmd_info, "callback", LUAREF_OBJ(api_new_luaref(cb->data.luaref)));
}
- } else {
- PUT(autocmd_info,
- "command",
- STRING_OBJ(cstr_as_string(xstrdup(ac->exec.callable.cmd))));
+ break;
+ case kCallbackFuncref:
+ case kCallbackPartial:
+ PUT(autocmd_info, "callback", CSTR_AS_OBJ(callback_to_string(cb)));
+ break;
+ case kCallbackNone:
+ abort();
}
+ } else {
+ PUT(autocmd_info, "command", CSTR_TO_OBJ(ac->exec.callable.cmd));
+ }
- PUT(autocmd_info,
- "pattern",
- STRING_OBJ(cstr_to_string((char *)ap->pat)));
-
- PUT(autocmd_info,
- "event",
- STRING_OBJ(cstr_to_string((char *)event_nr2name(event))));
-
- PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
-
- if (ap->buflocal_nr) {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
- PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
- } else {
- PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
- }
+ PUT(autocmd_info, "pattern", CSTR_TO_OBJ(ap->pat));
+ PUT(autocmd_info, "event", CSTR_TO_OBJ(event_nr2name(event)));
+ PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once));
- // TODO(sctx): It would be good to unify script_ctx to actually work with lua
- // right now it's just super weird, and never really gives you the info that
- // you would expect from this.
- //
- // I think we should be able to get the line number, filename, etc. from lua
- // when we're executing something, and it should be easy to then save that
- // info here.
- //
- // I think it's a big loss not getting line numbers of where options, autocmds,
- // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
- //
- // Once we do that, we can put these into the autocmd_info, but I don't think it's
- // useful to do that at this time.
- //
- // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
- // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
-
- ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ if (ap->buflocal_nr) {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true));
+ PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr));
+ } else {
+ PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false));
}
+
+ // TODO(sctx): It would be good to unify script_ctx to actually work with lua
+ // right now it's just super weird, and never really gives you the info that
+ // you would expect from this.
+ //
+ // I think we should be able to get the line number, filename, etc. from lua
+ // when we're executing something, and it should be easy to then save that
+ // info here.
+ //
+ // I think it's a big loss not getting line numbers of where options, autocmds,
+ // etc. are set (just getting "Sourced (lua)" or something is not that helpful.
+ //
+ // Once we do that, we can put these into the autocmd_info, but I don't think it's
+ // useful to do that at this time.
+ //
+ // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
+ // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
+
+ ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info));
}
}
@@ -365,28 +341,31 @@ cleanup:
/// function _name_ string) or `command` (Ex command string).
///
/// Example using Lua callback:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// callback = function(ev)
-/// print(string.format('event fired: %s', vim.inspect(ev)))
-/// end
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// callback = function(ev)
+/// print(string.format('event fired: %s', vim.inspect(ev)))
+/// end
+/// })
+/// ```
///
/// Example using an Ex command as the handler:
-/// <pre>lua
-/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
-/// pattern = {"*.c", "*.h"},
-/// command = "echo 'Entering a C or C++ file'",
-/// })
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, {
+/// pattern = {"*.c", "*.h"},
+/// command = "echo 'Entering a C or C++ file'",
+/// })
+/// ```
///
/// Note: `pattern` is NOT automatically expanded (unlike with |:autocmd|), thus names like "$HOME"
/// and "~" must be expanded explicitly:
-/// <pre>lua
-/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
-/// </pre>
+///
+/// ```lua
+/// pattern = vim.fn.expand("~") .. "/some/path/*.py"
+/// ```
///
/// @param event (string|array) Event(s) that will trigger the handler (`callback` or `command`).
/// @param opts Options dict:
@@ -421,10 +400,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
{
int64_t autocmd_id = -1;
char *desc = NULL;
-
Array patterns = ARRAY_DICT_INIT;
Array event_array = ARRAY_DICT_INIT;
-
AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT;
Callback cb = CALLBACK_NONE;
@@ -432,30 +409,23 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
goto cleanup;
}
- if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "specify either 'callback' or 'command', not both");
+ VALIDATE((!HAS_KEY(opts, create_autocmd, callback) || !HAS_KEY(opts, create_autocmd, command)),
+ "%s", "Cannot use both 'callback' and 'command'", {
goto cleanup;
- } else if (opts->callback.type != kObjectTypeNil) {
- // TODO(tjdevries): It's possible we could accept callable tables,
- // but we don't do that many other places, so for the moment let's
- // not do that.
+ });
+
+ if (HAS_KEY(opts, create_autocmd, callback)) {
+ // NOTE: We could accept callable tables, but that isn't common in the API.
Object *callback = &opts->callback;
switch (callback->type) {
case kObjectTypeLuaRef:
- if (callback->data.luaref == LUA_NOREF) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass an actual value");
+ VALIDATE_S((callback->data.luaref != LUA_NOREF), "callback", "<no value>", {
goto cleanup;
- }
-
- if (!nlua_ref_is_function(callback->data.luaref)) {
- api_set_error(err,
- kErrorTypeValidation,
- "must pass a function for callback");
+ });
+ VALIDATE_S(nlua_ref_is_function(callback->data.luaref), "callback", "<not a function>", {
goto cleanup;
- }
+ });
cb.type = kCallbackLua;
cb.data.luaref = api_new_luaref(callback->data.luaref);
@@ -465,61 +435,50 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
cb.data.funcref = string_to_cstr(callback->data.string);
break;
default:
- api_set_error(err,
- kErrorTypeException,
- "'callback' must be a lua function or name of vim function");
- goto cleanup;
+ VALIDATE_EXP(false, "callback", "Lua function or Vim function name",
+ api_typename(callback->type), {
+ goto cleanup;
+ });
}
aucmd.type = CALLABLE_CB;
aucmd.callable.cb = cb;
- } else if (opts->command.type != kObjectTypeNil) {
- Object *command = &opts->command;
- if (command->type == kObjectTypeString) {
- aucmd.type = CALLABLE_EX;
- aucmd.callable.cmd = string_to_cstr(command->data.string);
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'command' must be a string");
- goto cleanup;
- }
+ } else if (HAS_KEY(opts, create_autocmd, command)) {
+ aucmd.type = CALLABLE_EX;
+ aucmd.callable.cmd = string_to_cstr(opts->command);
} else {
- api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'");
- goto cleanup;
+ VALIDATE(false, "%s", "Required: 'command' or 'callback'", {
+ goto cleanup;
+ });
}
- bool is_once = api_object_to_bool(opts->once, "once", false, err);
- bool is_nested = api_object_to_bool(opts->nested, "nested", false, err);
-
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ bool has_buffer = HAS_KEY(opts, create_autocmd, buffer);
+
+ VALIDATE((!HAS_KEY(opts, create_autocmd, pattern) || !has_buffer),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
+ goto cleanup;
+ });
+
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
- if (opts->desc.type != kObjectTypeNil) {
- if (opts->desc.type == kObjectTypeString) {
- desc = opts->desc.data.string.data;
- } else {
- api_set_error(err,
- kErrorTypeValidation,
- "'desc' must be a string");
- goto cleanup;
- }
+ if (HAS_KEY(opts, create_autocmd, desc)) {
+ desc = opts->desc.data;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ("*"));
}
- if (event_array.size == 0) {
- api_set_error(err, kErrorTypeValidation, "'event' is a required key");
+ VALIDATE_R((event_array.size > 0), "event", {
goto cleanup;
- }
+ });
autocmd_id = next_autocmd_id++;
FOREACH_ITEM(event_array, event_str, {
@@ -535,8 +494,8 @@ Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autoc
pat.data.string.data,
(int)pat.data.string.size,
au_group,
- is_once,
- is_nested,
+ opts->once,
+ opts->nested,
desc,
aucmd);
});
@@ -556,25 +515,22 @@ cleanup:
return autocmd_id;
}
-/// Delete an autocommand by id.
+/// Deletes an autocommand by id.
///
-/// NOTE: Only autocommands created via the API have an id.
-/// @param id Integer The id returned by nvim_create_autocmd
-/// @see |nvim_create_autocmd()|
+/// @param id Integer Autocommand id returned by |nvim_create_autocmd()|
void nvim_del_autocmd(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- if (id <= 0) {
- api_set_error(err, kErrorTypeException, "Invalid autocmd id");
+ VALIDATE_INT((id > 0), "autocmd id", id, {
return;
- }
+ });
if (!autocmd_delete_id(id)) {
api_set_error(err, kErrorTypeException, "Failed to delete autocmd");
}
}
-/// Clear all autocommands that match the corresponding {opts}. To delete
-/// a particular autocmd, see |nvim_del_autocmd()|.
+/// Clears all autocommands selected by {opts}. To delete autocmds see |nvim_del_autocmd()|.
+///
/// @param opts Parameters
/// - event: (string|table)
/// Examples:
@@ -610,25 +566,26 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
goto cleanup;
}
- if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "Cannot use both 'pattern' and 'buffer'");
+ bool has_buffer = HAS_KEY(opts, clear_autocmds, buffer);
+
+ VALIDATE((!HAS_KEY(opts, clear_autocmds, pattern) || !has_buffer),
+ "%s", "Cannot use both 'pattern' and 'buffer'", {
goto cleanup;
- }
+ });
int au_group = get_augroup_from_object(opts->group, err);
if (au_group == AUGROUP_ERROR) {
goto cleanup;
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
// When we create the autocmds, we want to say that they are all matched, so that's *
// but when we clear them, we want to say that we didn't pass a pattern, so that's NUL
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
// If we didn't pass any events, that means clear all events.
@@ -636,7 +593,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOR_ALL_AUEVENTS(event) {
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event, pat, au_group, err)) {
goto cleanup;
}
});
@@ -647,7 +604,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err)
FOREACH_ITEM(patterns, pat_object, {
char *pat = pat_object.data.string.data;
- if (!clear_autocmd(event_nr, (char *)pat, au_group, err)) {
+ if (!clear_autocmd(event_nr, pat, au_group, err)) {
goto cleanup;
}
});
@@ -662,11 +619,12 @@ cleanup:
/// Create or get an autocommand group |autocmd-groups|.
///
/// To get an existing group id, do:
-/// <pre>lua
-/// local id = vim.api.nvim_create_augroup("MyGroup", {
-/// clear = false
-/// })
-/// </pre>
+///
+/// ```lua
+/// local id = vim.api.nvim_create_augroup("MyGroup", {
+/// clear = false
+/// })
+/// ```
///
/// @param name String: The name of the group
/// @param opts Dictionary Parameters
@@ -691,7 +649,7 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
if (clear_autocmds) {
FOR_ALL_AUEVENTS(event) {
- aupat_del_for_event_and_group(event, augroup);
+ aucmd_del_for_event_and_group(event, augroup);
}
}
});
@@ -711,11 +669,9 @@ Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augrou
void nvim_del_augroup_by_id(Integer id, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
- char *name = augroup_name((int)id);
+ TRY_WRAP(err, {
+ char *name = id == 0 ? NULL : augroup_name((int)id);
augroup_del(name, false);
- try_end(err);
});
}
@@ -728,10 +684,8 @@ void nvim_del_augroup_by_id(Integer id, Error *err)
void nvim_del_augroup_by_name(String name, Error *err)
FUNC_API_SINCE(9)
{
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
augroup_del(name.data, false);
- try_end(err);
});
}
@@ -772,62 +726,59 @@ void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *opts, Error *err)
break;
case kObjectTypeString:
au_group = augroup_find(opts->group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", opts->group.data.string.data);
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", opts->group.data.string.data, {
goto cleanup;
- }
+ });
break;
case kObjectTypeInteger:
au_group = (int)opts->group.data.integer;
- char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ char *name = au_group == 0 ? NULL : augroup_name(au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
goto cleanup;
- }
+ });
break;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- goto cleanup;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(opts->group.type), {
+ goto cleanup;
+ });
}
- if (opts->buffer.type != kObjectTypeNil) {
- Object buf_obj = opts->buffer;
- if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) {
- api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type);
+ bool has_buffer = false;
+ if (HAS_KEY(opts, exec_autocmds, buffer)) {
+ VALIDATE((!HAS_KEY(opts, exec_autocmds, pattern)),
+ "%s", "Cannot use both 'pattern' and 'buffer' for the same autocmd", {
goto cleanup;
- }
+ });
- buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err);
+ has_buffer = true;
+ buf = find_buffer_by_handle(opts->buffer, err);
if (ERROR_SET(err)) {
goto cleanup;
}
}
- if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) {
+ if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, has_buffer, opts->buffer, err)) {
goto cleanup;
}
if (patterns.size == 0) {
- ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ ADD(patterns, STATIC_CSTR_TO_OBJ(""));
}
- if (opts->data.type != kObjectTypeNil) {
+ if (HAS_KEY(opts, exec_autocmds, data)) {
data = &opts->data;
}
- modeline = api_object_to_bool(opts->modeline, "modeline", true, err);
+ modeline = GET_BOOL_OR_TRUE(opts, exec_autocmds, modeline);
bool did_aucmd = false;
FOREACH_ITEM(event_array, event_str, {
GET_ONE_EVENT(event_nr, event_str, cleanup)
FOREACH_ITEM(patterns, pat, {
- char *fname = opts->buffer.type == kObjectTypeNil ? pat.data.string.data : NULL;
- did_aucmd |=
- apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
+ char *fname = !has_buffer ? pat.data.string.data : NULL;
+ did_aucmd |= apply_autocmds_group(event_nr, fname, NULL, true, au_group, buf, NULL, data);
})
})
@@ -840,45 +791,19 @@ cleanup:
api_free_array(patterns);
}
-static bool check_autocmd_string_array(Array arr, char *k, Error *err)
-{
- FOREACH_ITEM(arr, entry, {
- if (entry.type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All entries in '%s' must be strings",
- k);
- return false;
- }
-
- // Disallow newlines in the middle of the line.
- const String l = entry.data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- })
- return true;
-}
-
static bool unpack_string_or_array(Array *array, Object *v, char *k, bool required, Error *err)
{
if (v->type == kObjectTypeString) {
ADD(*array, copy_object(*v, NULL));
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, k, err)) {
+ if (!check_string_array(v->data.array, k, true, err)) {
return false;
}
*array = copy_array(v->data.array, NULL);
} else {
- if (required) {
- api_set_error(err,
- kErrorTypeValidation,
- "'%s' must be an array or a string.",
- k);
+ VALIDATE_EXP(!required, k, "Array or String", api_typename(v->type), {
return false;
- }
+ });
}
return true;
@@ -894,88 +819,71 @@ static int get_augroup_from_object(Object group, Error *err)
return AUGROUP_DEFAULT;
case kObjectTypeString:
au_group = augroup_find(group.data.string.data);
- if (au_group == AUGROUP_ERROR) {
- api_set_error(err,
- kErrorTypeValidation,
- "invalid augroup: %s", group.data.string.data);
-
+ VALIDATE_S((au_group != AUGROUP_ERROR), "group", group.data.string.data, {
return AUGROUP_ERROR;
- }
+ });
return au_group;
case kObjectTypeInteger:
au_group = (int)group.data.integer;
- char *name = augroup_name(au_group);
- if (!augroup_exists(name)) {
- api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group);
+ char *name = au_group == 0 ? NULL : augroup_name(au_group);
+ VALIDATE_INT(augroup_exists(name), "group", (int64_t)au_group, {
return AUGROUP_ERROR;
- }
-
+ });
return au_group;
default:
- api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer.");
- return AUGROUP_ERROR;
+ VALIDATE_EXP(false, "group", "String or Integer", api_typename(group.type), {
+ return AUGROUP_ERROR;
+ });
}
}
-static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer,
- Error *err)
+static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, bool has_buffer,
+ Buffer buffer, Error *err)
{
const char pattern_buflocal[BUFLOCAL_PAT_LEN];
- if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation,
- "cannot pass both: 'pattern' and 'buffer' for the same autocmd");
- return false;
- } else if (pattern.type != kObjectTypeNil) {
+ if (pattern.type != kObjectTypeNil) {
Object *v = &pattern;
if (v->type == kObjectTypeString) {
- char *pat = v->data.string.data;
+ const char *pat = v->data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
} else if (v->type == kObjectTypeArray) {
- if (!check_autocmd_string_array(v->data.array, "pattern", err)) {
+ if (!check_string_array(v->data.array, "pattern", true, err)) {
return false;
}
Array array = v->data.array;
FOREACH_ITEM(array, entry, {
- char *pat = entry.data.string.data;
+ const char *pat = entry.data.string.data;
size_t patlen = aucmd_pattern_length(pat);
while (patlen) {
- ADD(*patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen)));
+ ADD(*patterns, STRING_OBJ(cbuf_to_string(pat, patlen)));
pat = aucmd_next_pattern(pat, patlen);
patlen = aucmd_pattern_length(pat);
}
})
} else {
- api_set_error(err,
- kErrorTypeValidation,
- "'pattern' must be a string or table");
- return false;
- }
- } else if (buffer.type != kObjectTypeNil) {
- if (buffer.type != kObjectTypeInteger && buffer.type != kObjectTypeBuffer) {
- api_set_error(err,
- kErrorTypeValidation,
- "'buffer' must be an integer");
- return false;
+ VALIDATE_EXP(false, "pattern", "String or Table", api_typename(v->type), {
+ return false;
+ });
}
-
- buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err);
+ } else if (has_buffer) {
+ buf_T *buf = find_buffer_by_handle(buffer, err);
if (ERROR_SET(err)) {
return false;
}
snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle);
- ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal)));
+ ADD(*patterns, CSTR_TO_OBJ(pattern_buflocal));
}
return true;
diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h
index f9432830d9..4ab3ddb943 100644
--- a/src/nvim/api/autocmd.h
+++ b/src/nvim/api/autocmd.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_AUTOCMD_H
-#define NVIM_API_AUTOCMD_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/autocmd.h.generated.h"
#endif
-#endif // NVIM_API_AUTOCMD_H
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index fe9e6077d6..0df231868d 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -1,10 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Some of this code was adapted from 'if_py_both.h' from the original
// vim source
-#include <assert.h>
#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
@@ -14,9 +10,11 @@
#include "klib/kvec.h"
#include "lua.h"
#include "nvim/api/buffer.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -26,6 +24,7 @@
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
#include "nvim/mapping.h"
@@ -34,10 +33,11 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.c.generated.h"
@@ -47,7 +47,7 @@
///
/// \brief For more information on buffers, see |buffers|
///
-/// Unloaded Buffers:~
+/// Unloaded Buffers: ~
///
/// Buffers may be unloaded by the |:bunload| command or the buffer's
/// |'bufhidden'| option. When a buffer is unloaded its file contents are freed
@@ -83,12 +83,16 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// Activates buffer-update events on a channel, or as Lua callbacks.
///
/// Example (Lua): capture buffer updates in a global `events` variable
-/// (use "print(vim.inspect(events))" to see its contents):
-/// <pre>lua
-/// events = {}
-/// vim.api.nvim_buf_attach(0, false, {
-/// on_lines=function(...) table.insert(events, {...}) end})
-/// </pre>
+/// (use "vim.print(events)" to see its contents):
+///
+/// ```lua
+/// events = {}
+/// vim.api.nvim_buf_attach(0, false, {
+/// on_lines = function(...)
+/// table.insert(events, {...})
+/// end,
+/// })
+/// ```
///
/// @see |nvim_buf_detach()|
/// @see |api-buffer-updates-lua|
@@ -111,7 +115,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err)
/// - byte count of previous contents
/// - deleted_codepoints (if `utf_sizes` is true)
/// - deleted_codeunits (if `utf_sizes` is true)
-/// - on_bytes: lua callback invoked on change.
+/// - on_bytes: Lua callback invoked on change.
/// This callback receives more granular information about the
/// change compared to on_lines.
/// Return `true` to detach.
@@ -179,11 +183,9 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (is_lua) {
for (size_t j = 0; cbs[j].name; j++) {
if (strequal(cbs[j].name, k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[j].name);
+ VALIDATE_T(cbs[j].name, kObjectTypeLuaRef, v->type, {
goto error;
- }
+ });
*(cbs[j].dest) = v->data.luaref;
v->data.luaref = LUA_NOREF;
key_used = true;
@@ -194,26 +196,23 @@ Boolean nvim_buf_attach(uint64_t channel_id, Buffer buffer, Boolean send_buffer,
if (key_used) {
continue;
} else if (strequal("utf_sizes", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean");
+ VALIDATE_T("utf_sizes", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.utf_sizes = v->data.boolean;
key_used = true;
} else if (strequal("preview", k.data)) {
- if (v->type != kObjectTypeBoolean) {
- api_set_error(err, kErrorTypeValidation, "preview must be boolean");
+ VALIDATE_T("preview", kObjectTypeBoolean, v->type, {
goto error;
- }
+ });
cb.preview = v->data.boolean;
key_used = true;
}
}
- if (!key_used) {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(key_used, "'opts' key", k.data, {
goto error;
- }
+ });
}
return buf_updates_register(buf, channel_id, cb, send_buffer);
@@ -252,6 +251,9 @@ void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last, Error *e
if (!buf) {
return;
}
+ if (last < 0) {
+ last = buf->b_ml.ml_line_count;
+ }
redraw_buf_range_later(buf, (linenr_T)first + 1, (linenr_T)last);
}
@@ -297,10 +299,9 @@ ArrayOf(String) nvim_buf_get_lines(uint64_t channel_id,
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
if (start >= end) {
// Return 0-length array
@@ -325,28 +326,6 @@ end:
return rv;
}
-static bool check_string_array(Array arr, bool disallow_nl, Error *err)
-{
- for (size_t i = 0; i < arr.size; i++) {
- if (arr.items[i].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "All items in the replacement array must be strings");
- return false;
- }
- // Disallow newlines in the middle of the line.
- if (disallow_nl) {
- const String l = arr.items[i].data.string;
- if (memchr(l.data, NL, l.size)) {
- api_set_error(err, kErrorTypeValidation,
- "String cannot contain newlines");
- return false;
- }
- }
- }
- return true;
-}
-
/// Sets (replaces) a line-range in the buffer.
///
/// Indexing is zero-based, end-exclusive. Negative indices are interpreted
@@ -371,7 +350,7 @@ static bool check_string_array(Array arr, bool disallow_nl, Error *err)
void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integer end,
Boolean strict_indexing, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -379,24 +358,27 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
start = normalize_index(buf, start, true, &oob);
end = normalize_index(buf, end, true, &oob);
- if (strict_indexing && oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!strict_indexing || !oob), "%s", "Index out of bounds", {
return;
- }
-
- if (start > end) {
- api_set_error(err,
- kErrorTypeValidation,
- "Argument \"start\" is higher than \"end\"");
+ });
+ VALIDATE((start <= end), "%s", "'start' is higher than 'end'", {
return;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
return;
}
@@ -415,27 +397,25 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
goto end;
}
- if (u_save((linenr_T)(start - 1), (linenr_T)end) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)(start - 1), (linenr_T)end) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
- bcount_t deleted_bytes = get_region_bytecount(curbuf, (linenr_T)start, (linenr_T)end, 0, 0);
+ bcount_t deleted_bytes = get_region_bytecount(buf, (linenr_T)start, (linenr_T)end, 0, 0);
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -453,12 +433,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -473,12 +452,11 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE(lnum < MAXLNUM, "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -493,20 +471,21 @@ void nvim_buf_set_lines(uint64_t channel_id, Buffer buffer, Integer start, Integ
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- // Only adjust marks if we managed to switch to a window that holds
- // the buffer, otherwise line numbers will be invalid.
- mark_adjust((linenr_T)start,
- (linenr_T)(end - 1),
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
-
- extmark_splice(curbuf, (int)start - 1, 0, (int)(end - start), 0,
+ linenr_T adjust = end > start ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start, (linenr_T)(end - 1), adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
+
+ extmark_splice(buf, (int)start - 1, 0, (int)(end - start), 0,
deleted_bytes, (int)new_len, 0, inserted_bytes,
kExtmarkUndo);
- changed_lines((linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
- fix_cursor((linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ changed_lines(buf, (linenr_T)start, 0, (linenr_T)end, (linenr_T)extra, true);
+
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ fix_cursor(win, (linenr_T)start, (linenr_T)end, (linenr_T)extra);
+ }
+ }
end:
for (size_t i = 0; i < new_len; i++) {
@@ -514,7 +493,6 @@ end:
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
}
@@ -533,7 +511,10 @@ end:
///
/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
///
+/// Prefer |nvim_put()| if you want to insert text at the cursor position.
+///
/// @see |nvim_buf_set_lines()|
+/// @see |nvim_put()|
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -546,10 +527,11 @@ end:
void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, Integer start_col,
Integer end_row, Integer end_col, ArrayOf(String) replacement, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
MAXSIZE_TEMP_ARRAY(scratch, 1);
if (replacement.size == 0) {
- ADD_C(scratch, STRING_OBJ(STATIC_CSTR_AS_STRING("")));
+ ADD_C(scratch, STATIC_CSTR_AS_OBJ(""));
replacement = scratch;
}
@@ -558,48 +540,54 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
return;
}
+ // load buffer first if it's not loaded
+ if (buf->b_ml.ml_mfp == NULL) {
+ if (!buf_ensure_loaded(buf)) {
+ api_set_error(err, kErrorTypeException, "Failed to load buffer");
+ return;
+ }
+ }
+
bool oob = false;
// check range is ordered and everything!
// start_row, end_row within buffer len (except add text past the end?)
start_row = normalize_index(buf, start_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "start_row out of bounds");
+ VALIDATE_RANGE((!oob), "start_row", {
return;
- }
+ });
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "end_row out of bounds");
+ VALIDATE_RANGE((!oob), "end_row", {
return;
- }
+ });
char *str_at_start = NULL;
char *str_at_end = NULL;
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row, false));
+ str_at_start = xstrdup(ml_get_buf(buf, (linenr_T)start_row));
size_t len_at_start = strlen(str_at_start);
- if (start_col < 0 || (size_t)start_col > len_at_start) {
- api_set_error(err, kErrorTypeValidation, "start_col out of bounds");
+ start_col = start_col < 0 ? (int64_t)len_at_start + start_col + 1 : start_col;
+ VALIDATE_RANGE((start_col >= 0 && (size_t)start_col <= len_at_start), "start_col", {
goto early_end;
- }
+ });
// Another call to ml_get_buf() may free the line, so make a copy.
- str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row, false));
+ str_at_end = xstrdup(ml_get_buf(buf, (linenr_T)end_row));
size_t len_at_end = strlen(str_at_end);
- if (end_col < 0 || (size_t)end_col > len_at_end) {
- api_set_error(err, kErrorTypeValidation, "end_col out of bounds");
+ end_col = end_col < 0 ? (int64_t)len_at_end + end_col + 1 : end_col;
+ VALIDATE_RANGE((end_col >= 0 && (size_t)end_col <= len_at_end), "end_col", {
goto early_end;
- }
+ });
- if (start_row > end_row || (end_row == start_row && start_col > end_col)) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row && !(end_row == start_row && start_col > end_col)),
+ "%s", "'start' is higher than 'end'", {
goto early_end;
- }
+ });
bool disallow_nl = (channel_id != VIML_INTERNAL_CALL);
- if (!check_string_array(replacement, disallow_nl, err)) {
+ if (!check_string_array(replacement, "replacement string", disallow_nl, err)) {
goto early_end;
}
@@ -616,7 +604,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (int64_t i = 1; i < end_row - start_row; i++) {
int64_t lnum = start_row + i;
- const char *bufline = ml_get_buf(buf, (linenr_T)lnum, false);
+ const char *bufline = ml_get_buf(buf, (linenr_T)lnum);
old_byte += (bcount_t)(strlen(bufline)) + 1;
}
old_byte += (bcount_t)end_col + 1;
@@ -662,8 +650,6 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
}
try_start();
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
if (!MODIFIABLE(buf)) {
api_set_error(err, kErrorTypeException, "Buffer is not 'modifiable'");
@@ -672,7 +658,7 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// Small note about undo states: unlike set_lines, we want to save the
// undo state of one past the end_row, since end_row is inclusive.
- if (u_save((linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
+ if (u_save_buf(buf, (linenr_T)start_row - 1, (linenr_T)end_row + 1) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to save undo information");
goto end;
}
@@ -683,9 +669,9 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
// If the size of the range is reducing (ie, new_len < old_len) we
// need to delete some old_len. We do this at the start, by
// repeatedly deleting line "start".
- size_t to_delete = (new_len < old_len) ? (size_t)(old_len - new_len) : 0;
+ size_t to_delete = (new_len < old_len) ? old_len - new_len : 0;
for (size_t i = 0; i < to_delete; i++) {
- if (ml_delete((linenr_T)start_row, false) == FAIL) {
+ if (ml_delete_buf(buf, (linenr_T)start_row, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to delete line");
goto end;
}
@@ -702,12 +688,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = 0; i < to_replace; i++) {
int64_t lnum = start_row + (int64_t)i;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_replace((linenr_T)lnum, lines[i], false) == FAIL) {
+ if (ml_replace_buf(buf, (linenr_T)lnum, lines[i], false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to replace line");
goto end;
}
@@ -720,12 +705,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
for (size_t i = to_replace; i < new_len; i++) {
int64_t lnum = start_row + (int64_t)i - 1;
- if (lnum >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Index value is too high");
+ VALIDATE((lnum < MAXLNUM), "%s", "Index out of bounds", {
goto end;
- }
+ });
- if (ml_append((linenr_T)lnum, lines[i], 0, false) == FAIL) {
+ if (ml_append_buf(buf, (linenr_T)lnum, lines[i], 0, false) == FAIL) {
api_set_error(err, kErrorTypeException, "Failed to insert line");
goto end;
}
@@ -736,35 +720,39 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In
extra++;
}
+ colnr_T col_extent = (colnr_T)(end_col
+ - ((end_row == start_row) ? start_col : 0));
+
// Adjust marks. Invalidate any which lie in the
// changed range, and move any in the remainder of the buffer.
- mark_adjust((linenr_T)start_row,
- (linenr_T)end_row,
- MAXLNUM,
- (linenr_T)extra,
- kExtmarkNOOP);
+ // Do not adjust any cursors. need to use column-aware logic (below)
+ linenr_T adjust = end_row >= start_row ? MAXLNUM : 0;
+ mark_adjust_buf(buf, (linenr_T)start_row, (linenr_T)end_row, adjust, (linenr_T)extra,
+ true, true, kExtmarkNOOP);
- colnr_T col_extent = (colnr_T)(end_col
- - ((end_row == start_row) ? start_col : 0));
extmark_splice(buf, (int)start_row - 1, (colnr_T)start_col,
(int)(end_row - start_row), col_extent, old_byte,
(int)new_len - 1, (colnr_T)last_item.size, new_byte,
kExtmarkUndo);
- changed_lines((linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
+ changed_lines(buf, (linenr_T)start_row, 0, (linenr_T)end_row + 1, (linenr_T)extra, true);
- // adjust cursor like an extmark ( i e it was inside last_part_len)
- if (curwin->w_cursor.lnum == end_row && curwin->w_cursor.col > end_col) {
- curwin->w_cursor.col -= col_extent - (colnr_T)last_item.size;
+ FOR_ALL_TAB_WINDOWS(tp, win) {
+ if (win->w_buffer == buf) {
+ if (win->w_cursor.lnum >= start_row && win->w_cursor.lnum <= end_row) {
+ fix_cursor_cols(win, (linenr_T)start_row, (colnr_T)start_col, (linenr_T)end_row,
+ (colnr_T)end_col, (linenr_T)new_len, (colnr_T)last_item.size);
+ } else {
+ fix_cursor(win, (linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
+ }
+ }
}
- fix_cursor((linenr_T)start_row, (linenr_T)end_row, (linenr_T)extra);
end:
for (size_t i = 0; i < new_len; i++) {
xfree(lines[i]);
}
xfree(lines);
- aucmd_restbuf(&aco);
try_end(err);
early_end:
@@ -800,10 +788,9 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
{
Array rv = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return rv;
- }
+ });
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -820,18 +807,16 @@ ArrayOf(String) nvim_buf_get_text(uint64_t channel_id, Buffer buffer,
start_row = normalize_index(buf, start_row, false, &oob);
end_row = normalize_index(buf, end_row, false, &oob);
- if (oob) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((!oob), "%s", "Index out of bounds", {
return rv;
- }
+ });
// nvim_buf_get_lines doesn't care if the start row is greater than the end
// row (it will just return an empty array), but nvim_buf_get_text does in
// order to maintain symmetry with nvim_buf_set_text.
- if (start_row > end_row) {
- api_set_error(err, kErrorTypeValidation, "start is higher than end");
+ VALIDATE((start_row <= end_row), "%s", "'start' is higher than 'end'", {
return rv;
- }
+ });
bool replace_nl = (channel_id != VIML_INTERNAL_CALL);
@@ -907,10 +892,9 @@ Integer nvim_buf_get_offset(Buffer buffer, Integer index, Error *err)
return -1;
}
- if (index < 0 || index > buf->b_ml.ml_line_count) {
- api_set_error(err, kErrorTypeValidation, "Index out of bounds");
+ VALIDATE((index >= 0 && index <= buf->b_ml.ml_line_count), "%s", "Index out of bounds", {
return 0;
- }
+ });
return ml_find_line_or_offset(buf, (int)index + 1, NULL, true);
}
@@ -1100,7 +1084,7 @@ Boolean nvim_buf_is_loaded(Buffer buffer)
/// - unload: Unloaded only, do not delete. See |:bunload|
void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1118,8 +1102,9 @@ void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
} else if (strequal("unload", k.data)) {
unload = api_object_to_bool(v, "unload", false, err);
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return;
+ VALIDATE_S(false, "'opts' key", k.data, {
+ return;
+ });
}
}
@@ -1174,20 +1159,16 @@ Boolean nvim_buf_del_mark(Buffer buffer, String name, Error *err)
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
fmark_T *fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, *name.data);
// fm is NULL when there's no mark with the given name
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name: '%c'",
- *name.data);
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return res;
- }
+ });
// mark.lnum is 0 when the mark is not valid in the buffer, or is not set.
if (fm->mark.lnum != 0 && fm->fnum == buf->handle) {
@@ -1224,19 +1205,18 @@ Boolean nvim_buf_set_mark(Buffer buffer, String name, Integer line, Integer col,
return res;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
res = set_mark(buf, name, line, col, err);
return res;
}
-/// Returns a tuple (row,col) representing the position of the named mark. See
-/// |mark-motions|.
+/// Returns a `(row,col)` tuple representing the position of the named mark.
+/// "End of line" column position is returned as |v:maxcol| (big number).
+/// See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
@@ -1257,21 +1237,18 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- }
+ });
fmark_T *fm;
pos_T pos;
char mark = *name.data;
fm = mark_get(buf, curwin, NULL, kMarkAllNoResolve, mark);
- if (fm == NULL) {
- api_set_error(err, kErrorTypeValidation, "Invalid mark name");
+ VALIDATE_S((fm != NULL), "mark name", name.data, {
return rv;
- }
+ });
// (0, 0) uppercase/file mark set in another buffer.
if (fm->fnum != buf->handle) {
pos.lnum = 0;
@@ -1295,15 +1272,15 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
/// Otherwise a temporary scratch window (called the "autocmd window" for
/// historical reasons) will be used.
///
-/// This is useful e.g. to call vimL functions that only work with the current
-/// buffer/window currently, like |termopen()|.
+/// This is useful e.g. to call Vimscript functions that only work with the
+/// current buffer/window currently, like |termopen()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param fun Function to call inside the buffer (currently lua callable
+/// @param fun Function to call inside the buffer (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -1362,42 +1339,92 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// Check if deleting lines made the cursor position invalid.
// Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted).
-static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra)
+static void fix_cursor(win_T *win, linenr_T lo, linenr_T hi, linenr_T extra)
{
- if (curwin->w_cursor.lnum >= lo) {
+ if (win->w_cursor.lnum >= lo) {
// Adjust cursor position if it's in/after the changed lines.
- if (curwin->w_cursor.lnum >= hi) {
- curwin->w_cursor.lnum += extra;
- check_cursor_col();
+ if (win->w_cursor.lnum >= hi) {
+ win->w_cursor.lnum += extra;
} else if (extra < 0) {
- check_cursor();
- } else {
- check_cursor_col();
+ check_cursor_lnum(win);
}
- changed_cline_bef_curs();
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
}
- invalidate_botline();
+ invalidate_botline(win);
}
-// Normalizes 0-based indexes to buffer line numbers
-static int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+/// Fix cursor position after replacing text
+/// between (start_row, start_col) and (end_row, end_col).
+///
+/// win->w_cursor.lnum is assumed to be >= start_row and <= end_row.
+static void fix_cursor_cols(win_T *win, linenr_T start_row, colnr_T start_col, linenr_T end_row,
+ colnr_T end_col, linenr_T new_rows, colnr_T new_cols_at_end_row)
{
- assert(buf->b_ml.ml_line_count > 0);
- int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
- // Fix if < 0
- index = index < 0 ? max_index + index + 1 : index;
-
- // Check for oob
- if (index > max_index) {
- *oob = true;
- index = max_index;
- } else if (index < 0) {
- *oob = true;
- index = 0;
- }
- // Convert the index to a vim line number
- index++;
- return index;
+ colnr_T mode_col_adj = win == curwin && (State & MODE_INSERT) ? 0 : 1;
+
+ colnr_T end_row_change_start = new_rows == 1 ? start_col : 0;
+ colnr_T end_row_change_end = end_row_change_start + new_cols_at_end_row;
+
+ // check if cursor is after replaced range or not
+ if (win->w_cursor.lnum == end_row && win->w_cursor.col + mode_col_adj > end_col) {
+ // if cursor is after replaced range, it's shifted
+ // to keep it's position the same, relative to end_col
+
+ linenr_T old_rows = end_row - start_row + 1;
+ win->w_cursor.lnum += new_rows - old_rows;
+ win->w_cursor.col += end_row_change_end - end_col;
+ } else {
+ // if cursor is inside replaced range
+ // and the new range got smaller,
+ // it's shifted to keep it inside the new range
+ //
+ // if cursor is before range or range did not
+ // got smaller, position is not changed
+
+ colnr_T old_coladd = win->w_cursor.coladd;
+
+ // it's easier to work with a single value here.
+ // col and coladd are fixed by a later call
+ // to check_cursor_col_win when necessary
+ win->w_cursor.col += win->w_cursor.coladd;
+ win->w_cursor.coladd = 0;
+
+ linenr_T new_end_row = start_row + new_rows - 1;
+
+ // make sure cursor row is in the new row range
+ if (win->w_cursor.lnum > new_end_row) {
+ win->w_cursor.lnum = new_end_row;
+
+ // don't simply move cursor up, but to the end
+ // of new_end_row, if it's not at or after
+ // it already (in case virtualedit is active)
+ // column might be additionally adjusted below
+ // to keep it inside col range if needed
+ colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, new_end_row));
+ if (win->w_cursor.col < len) {
+ win->w_cursor.col = len;
+ }
+ }
+
+ // if cursor is at the last row and
+ // it wasn't after eol before, move it exactly
+ // to end_row_change_end
+ if (win->w_cursor.lnum == new_end_row
+ && win->w_cursor.col > end_row_change_end && old_coladd == 0) {
+ win->w_cursor.col = end_row_change_end;
+
+ // make sure cursor is inside range, not after it,
+ // except when doing so would move it before new range
+ if (win->w_cursor.col - mode_col_adj >= end_row_change_start) {
+ win->w_cursor.col -= mode_col_adj;
+ }
+ }
+ }
+
+ check_cursor_col_win(win);
+ changed_cline_bef_curs(win);
+ invalidate_botline(win);
}
/// Initialise a string array either:
@@ -1481,7 +1508,7 @@ bool buf_collect_lines(buf_T *buf, size_t n, linenr_T start, int start_idx, bool
return false;
}
- char *bufstr = ml_get_buf(buf, lnum, false);
+ char *bufstr = ml_get_buf(buf, lnum);
push_linestr(lstate, l, bufstr, strlen(bufstr), start_idx + (int)i, replace_nl);
}
diff --git a/src/nvim/api/buffer.h b/src/nvim/api/buffer.h
index 0814da63cd..f3971c1d30 100644
--- a/src/nvim/api/buffer.h
+++ b/src/nvim/api/buffer.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_API_BUFFER_H
-#define NVIM_API_BUFFER_H
+#pragma once
-#include <lauxlib.h>
+#include <lua.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/buffer.h.generated.h"
#endif
-#endif // NVIM_API_BUFFER_H
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index abd265f2cf..2a57ce9a19 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -1,36 +1,37 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/command.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
-#include "nvim/decoration.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/ops.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -99,16 +100,15 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
{
Dictionary result = ARRAY_DICT_INIT;
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return result;
- }
+ });
// Parse command line
exarg_T ea;
CmdParseInfo cmdinfo;
char *cmdline = string_to_cstr(str);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
if (errormsg != NULL) {
@@ -127,7 +127,7 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
// otherwise split arguments by whitespace.
if (ea.argt & EX_NOSPC) {
if (*ea.arg != NUL) {
- ADD(args, STRING_OBJ(cstrn_to_string((char *)ea.arg, length)));
+ ADD(args, STRING_OBJ(cstrn_to_string(ea.arg, length)));
}
} else {
size_t end = 0;
@@ -153,9 +153,9 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
}
if (cmd != NULL) {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)cmd->uc_name));
+ PUT(result, "cmd", CSTR_TO_OBJ(cmd->uc_name));
} else {
- PUT(result, "cmd", CSTR_TO_OBJ((char *)get_command_name(NULL, ea.cmdidx)));
+ PUT(result, "cmd", CSTR_TO_OBJ(get_command_name(NULL, ea.cmdidx)));
}
if (ea.argt & EX_RANGE) {
@@ -237,14 +237,14 @@ Dictionary nvim_parse_cmd(String str, Dictionary opts, Error *err)
break;
}
PUT(result, "addr", CSTR_TO_OBJ(addr));
- PUT(result, "nextcmd", CSTR_TO_OBJ((char *)ea.nextcmd));
+ PUT(result, "nextcmd", CSTR_TO_OBJ(ea.nextcmd));
Dictionary mods = ARRAY_DICT_INIT;
Dictionary filter = ARRAY_DICT_INIT;
PUT(filter, "pattern", cmdinfo.cmdmod.cmod_filter_pat
? CSTR_TO_OBJ(cmdinfo.cmdmod.cmod_filter_pat)
- : STRING_OBJ(STATIC_CSTR_TO_STRING("")));
+ : STATIC_CSTR_TO_OBJ(""));
PUT(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
PUT(mods, "filter", DICTIONARY_OBJ(filter));
@@ -305,9 +305,9 @@ end:
/// make their usage simpler with |vim.cmd()|. For example, instead of
/// `vim.cmd.bdelete{ count = 2 }`, you may do `vim.cmd.bdelete(2)`.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @see |nvim_exec()|
+/// @see |nvim_exec2()|
/// @see |nvim_command()|
///
/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
@@ -340,32 +340,22 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
} \
} while (0)
-#define OBJ_TO_CMOD_FLAG(flag, value, default, varname) \
+#define VALIDATE_MOD(cond, mod_, name_) \
do { \
- if (api_object_to_bool(value, varname, default, err)) { \
- cmdinfo.cmdmod.cmod_flags |= (flag); \
- } \
- if (ERROR_SET(err)) { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, "Command cannot accept %s: %s", (mod_), (name_)); \
goto end; \
} \
} while (0)
-#define VALIDATION_ERROR(...) \
- do { \
- api_set_error(err, kErrorTypeValidation, __VA_ARGS__); \
- goto end; \
- } while (0)
-
- bool output;
- OBJ_TO_BOOL(output, opts->output, false, "'output'");
-
- // First, parse the command name and check if it exists and is valid.
- if (!HAS_KEY(cmd->cmd) || cmd->cmd.type != kObjectTypeString
- || cmd->cmd.data.string.data[0] == NUL) {
- VALIDATION_ERROR("'cmd' must be a non-empty String");
- }
+ VALIDATE_R(HAS_KEY(cmd, cmd, cmd), "cmd", {
+ goto end;
+ });
+ VALIDATE_EXP((cmd->cmd.data[0] != NUL), "cmd", "non-empty String", NULL, {
+ goto end;
+ });
- cmdname = string_to_cstr(cmd->cmd.data.string);
+ cmdname = string_to_cstr(cmd->cmd);
ea.cmd = cmdname;
char *p = find_ex_command(&ea, NULL);
@@ -382,12 +372,18 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
p = (ret && !aborting()) ? find_ex_command(&ea, NULL) : ea.cmd;
}
- if (p == NULL || ea.cmdidx == CMD_SIZE) {
- VALIDATION_ERROR("Command not found: %s", cmdname);
- }
- if (is_cmd_ni(ea.cmdidx)) {
- VALIDATION_ERROR("Command not implemented: %s", cmdname);
- }
+ VALIDATE((p != NULL && ea.cmdidx != CMD_SIZE), "Command not found: %s", cmdname, {
+ goto end;
+ });
+ VALIDATE(!is_cmd_ni(ea.cmdidx), "Command not implemented: %s", cmdname, {
+ goto end;
+ });
+ const char *fullname = IS_USER_CMDIDX(ea.cmdidx)
+ ? get_user_command_name(ea.useridx, ea.cmdidx)
+ : get_command_name(NULL, ea.cmdidx);
+ VALIDATE(strncmp(fullname, cmdname, strlen(cmdname)) == 0, "Invalid command: \"%s\"", cmdname, {
+ goto end;
+ });
// Get the command flags so that we can know what type of arguments the command uses.
// Not required for a user command since `find_ex_command` already deals with it in that case.
@@ -396,15 +392,11 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
// Parse command arguments since it's needed to get the command address type.
- if (HAS_KEY(cmd->args)) {
- if (cmd->args.type != kObjectTypeArray) {
- VALIDATION_ERROR("'args' must be an Array");
- }
-
+ if (HAS_KEY(cmd, cmd, args)) {
// Process all arguments. Convert non-String arguments to String and check if String arguments
// have non-whitespace characters.
- for (size_t i = 0; i < cmd->args.data.array.size; i++) {
- Object elem = cmd->args.data.array.items[i];
+ for (size_t i = 0; i < cmd->args.size; i++) {
+ Object elem = cmd->args.items[i];
char *data_str;
switch (elem.type) {
@@ -421,18 +413,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
snprintf(data_str, NUMBUFLEN, "%" PRId64, elem.data.integer);
break;
case kObjectTypeString:
- if (string_iswhite(elem.data.string)) {
- VALIDATION_ERROR("String command argument must have at least one non-whitespace "
- "character");
- }
- data_str = xstrndup(elem.data.string.data, elem.data.string.size);
+ VALIDATE_EXP(!string_iswhite(elem.data.string), "command arg", "non-whitespace", NULL, {
+ goto end;
+ });
+ data_str = string_to_cstr(elem.data.string);
break;
default:
- VALIDATION_ERROR("Invalid type for command argument");
+ VALIDATE_EXP(false, "command arg", "valid type", api_typename(elem.type), {
+ goto end;
+ });
break;
}
- ADD(args, STRING_OBJ(cstr_as_string(data_str)));
+ ADD(args, CSTR_AS_OBJ(data_str));
}
bool argc_valid;
@@ -456,32 +449,30 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
break;
}
- if (!argc_valid) {
- VALIDATION_ERROR("Incorrect number of arguments supplied");
- }
+ VALIDATE(argc_valid, "%s", "Wrong number of arguments", {
+ goto end;
+ });
}
// Simply pass the first argument (if it exists) as the arg pointer to `set_cmd_addr_type()`
// since it only ever checks the first argument.
set_cmd_addr_type(&ea, args.size > 0 ? args.items[0].data.string.data : NULL);
- if (HAS_KEY(cmd->range)) {
- if (!(ea.argt & EX_RANGE)) {
- VALIDATION_ERROR("Command cannot accept a range");
- } else if (cmd->range.type != kObjectTypeArray) {
- VALIDATION_ERROR("'range' must be an Array");
- } else if (cmd->range.data.array.size > 2) {
- VALIDATION_ERROR("'range' cannot contain more than two elements");
- }
+ if (HAS_KEY(cmd, cmd, range)) {
+ VALIDATE_MOD((ea.argt & EX_RANGE), "range", cmd->cmd.data);
+ VALIDATE_EXP((cmd->range.size <= 2), "range", "<=2 elements", NULL, {
+ goto end;
+ });
- Array range = cmd->range.data.array;
+ Array range = cmd->range;
ea.addr_count = (int)range.size;
for (size_t i = 0; i < range.size; i++) {
Object elem = range.items[i];
- if (elem.type != kObjectTypeInteger || elem.data.integer < 0) {
- VALIDATION_ERROR("'range' element must be a non-negative Integer");
- }
+ VALIDATE_EXP((elem.type == kObjectTypeInteger && elem.data.integer >= 0),
+ "range element", "non-negative Integer", NULL, {
+ goto end;
+ });
}
if (range.size > 0) {
@@ -489,9 +480,9 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
ea.line2 = (linenr_T)range.items[range.size - 1].data.integer;
}
- if (invalid_range(&ea) != NULL) {
- VALIDATION_ERROR("Invalid range provided");
- }
+ VALIDATE_S((invalid_range(&ea) == NULL), "range", "", {
+ goto end;
+ });
}
if (ea.addr_count == 0) {
if (ea.argt & EX_DFLALL) {
@@ -506,48 +497,42 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
}
}
- if (HAS_KEY(cmd->count)) {
- if (!(ea.argt & EX_COUNT)) {
- VALIDATION_ERROR("Command cannot accept a count");
- } else if (cmd->count.type != kObjectTypeInteger || cmd->count.data.integer < 0) {
- VALIDATION_ERROR("'count' must be a non-negative Integer");
- }
- set_cmd_count(&ea, cmd->count.data.integer, true);
+ if (HAS_KEY(cmd, cmd, count)) {
+ VALIDATE_MOD((ea.argt & EX_COUNT), "count", cmd->cmd.data);
+ VALIDATE_EXP((cmd->count >= 0), "count", "non-negative Integer", NULL, {
+ goto end;
+ });
+ set_cmd_count(&ea, (linenr_T)cmd->count, true);
}
- if (HAS_KEY(cmd->reg)) {
- if (!(ea.argt & EX_REGSTR)) {
- VALIDATION_ERROR("Command cannot accept a register");
- } else if (cmd->reg.type != kObjectTypeString || cmd->reg.data.string.size != 1) {
- VALIDATION_ERROR("'reg' must be a single character");
- }
- char regname = cmd->reg.data.string.data[0];
- if (regname == '=') {
- VALIDATION_ERROR("Cannot use register \"=");
- } else if (!valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx))) {
- VALIDATION_ERROR("Invalid register: \"%c", regname);
- }
+ if (HAS_KEY(cmd, cmd, reg)) {
+ VALIDATE_MOD((ea.argt & EX_REGSTR), "register", cmd->cmd.data);
+ VALIDATE_EXP((cmd->reg.size == 1),
+ "reg", "single character", cmd->reg.data, {
+ goto end;
+ });
+ char regname = cmd->reg.data[0];
+ VALIDATE((regname != '='), "%s", "Cannot use register \"=", {
+ goto end;
+ });
+ VALIDATE(valid_yank_reg(regname, ea.cmdidx != CMD_put && !IS_USER_CMDIDX(ea.cmdidx)),
+ "Invalid register: \"%c", regname, {
+ goto end;
+ });
ea.regname = (uint8_t)regname;
}
- OBJ_TO_BOOL(ea.forceit, cmd->bang, false, "'bang'");
- if (ea.forceit && !(ea.argt & EX_BANG)) {
- VALIDATION_ERROR("Command cannot accept a bang");
- }
-
- if (HAS_KEY(cmd->magic)) {
- if (cmd->magic.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'magic' must be a Dictionary");
- }
+ ea.forceit = cmd->bang;
+ VALIDATE_MOD((!ea.forceit || (ea.argt & EX_BANG)), "bang", cmd->cmd.data);
- Dict(cmd_magic) magic = { 0 };
- if (!api_dict_to_keydict(&magic, KeyDict_cmd_magic_get_field,
- cmd->magic.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, magic)) {
+ Dict(cmd_magic) magic[1] = { 0 };
+ if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, err)) {
goto end;
}
- OBJ_TO_BOOL(cmdinfo.magic.file, magic.file, ea.argt & EX_XFILE, "'magic.file'");
- OBJ_TO_BOOL(cmdinfo.magic.bar, magic.bar, ea.argt & EX_TRLBAR, "'magic.bar'");
+ cmdinfo.magic.file = HAS_KEY(magic, cmd_magic, file) ? magic->file : (ea.argt & EX_XFILE);
+ cmdinfo.magic.bar = HAS_KEY(magic, cmd_magic, bar) ? magic->bar : (ea.argt & EX_TRLBAR);
if (cmdinfo.magic.file) {
ea.argt |= EX_XFILE;
} else {
@@ -558,107 +543,90 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.magic.bar = ea.argt & EX_TRLBAR;
}
- if (HAS_KEY(cmd->mods)) {
- if (cmd->mods.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods' must be a Dictionary");
- }
-
- Dict(cmd_mods) mods = { 0 };
- if (!api_dict_to_keydict(&mods, KeyDict_cmd_mods_get_field, cmd->mods.data.dictionary, err)) {
+ if (HAS_KEY(cmd, cmd, mods)) {
+ Dict(cmd_mods) mods[1] = { 0 };
+ if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) {
goto end;
}
- if (HAS_KEY(mods.filter)) {
- if (mods.filter.type != kObjectTypeDictionary) {
- VALIDATION_ERROR("'mods.filter' must be a Dictionary");
- }
-
- Dict(cmd_mods_filter) filter = { 0 };
+ if (HAS_KEY(mods, cmd_mods, filter)) {
+ Dict(cmd_mods_filter) filter[1] = { 0 };
if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
- mods.filter.data.dictionary, err)) {
+ mods->filter, err)) {
goto end;
}
- if (HAS_KEY(filter.pattern)) {
- if (filter.pattern.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.filter.pattern' must be a String");
- }
-
- OBJ_TO_BOOL(cmdinfo.cmdmod.cmod_filter_force, filter.force, false, "'mods.filter.force'");
+ if (HAS_KEY(filter, cmd_mods_filter, pattern)) {
+ cmdinfo.cmdmod.cmod_filter_force = filter->force;
// "filter! // is not no-op, so add a filter if either the pattern is non-empty or if filter
// is inverted.
- if (*filter.pattern.data.string.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
- cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter.pattern.data.string);
+ if (*filter->pattern.data != NUL || cmdinfo.cmdmod.cmod_filter_force) {
+ cmdinfo.cmdmod.cmod_filter_pat = string_to_cstr(filter->pattern);
cmdinfo.cmdmod.cmod_filter_regmatch.regprog = vim_regcomp(cmdinfo.cmdmod.cmod_filter_pat,
RE_MAGIC);
}
}
}
- if (HAS_KEY(mods.tab)) {
- if (mods.tab.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.tab' must be an Integer");
- } else if ((int)mods.tab.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, tab)) {
+ if ((int)mods->tab >= 0) {
// Silently ignore negative integers to allow mods.tab to be set to -1.
- cmdinfo.cmdmod.cmod_tab = (int)mods.tab.data.integer + 1;
+ cmdinfo.cmdmod.cmod_tab = (int)mods->tab + 1;
}
}
- if (HAS_KEY(mods.verbose)) {
- if (mods.verbose.type != kObjectTypeInteger) {
- VALIDATION_ERROR("'mods.verbose' must be an Integer");
- } else if ((int)mods.verbose.data.integer >= 0) {
+ if (HAS_KEY(mods, cmd_mods, verbose)) {
+ if ((int)mods->verbose >= 0) {
// Silently ignore negative integers to allow mods.verbose to be set to -1.
- cmdinfo.cmdmod.cmod_verbose = (int)mods.verbose.data.integer + 1;
+ cmdinfo.cmdmod.cmod_verbose = (int)mods->verbose + 1;
}
}
- bool vertical;
- OBJ_TO_BOOL(vertical, mods.vertical, false, "'mods.vertical'");
- cmdinfo.cmdmod.cmod_split |= (vertical ? WSP_VERT : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->vertical ? WSP_VERT : 0);
- bool horizontal;
- OBJ_TO_BOOL(horizontal, mods.horizontal, false, "'mods.horizontal'");
- cmdinfo.cmdmod.cmod_split |= (horizontal ? WSP_HOR : 0);
+ cmdinfo.cmdmod.cmod_split |= (mods->horizontal ? WSP_HOR : 0);
- if (HAS_KEY(mods.split)) {
- if (mods.split.type != kObjectTypeString) {
- VALIDATION_ERROR("'mods.split' must be a String");
- }
-
- if (*mods.split.data.string.data == NUL) {
+ if (HAS_KEY(mods, cmd_mods, split)) {
+ if (*mods->split.data == NUL) {
// Empty string, do nothing.
- } else if (strcmp(mods.split.data.string.data, "aboveleft") == 0
- || strcmp(mods.split.data.string.data, "leftabove") == 0) {
+ } else if (strcmp(mods->split.data, "aboveleft") == 0
+ || strcmp(mods->split.data, "leftabove") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_ABOVE;
- } else if (strcmp(mods.split.data.string.data, "belowright") == 0
- || strcmp(mods.split.data.string.data, "rightbelow") == 0) {
+ } else if (strcmp(mods->split.data, "belowright") == 0
+ || strcmp(mods->split.data, "rightbelow") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BELOW;
- } else if (strcmp(mods.split.data.string.data, "topleft") == 0) {
+ } else if (strcmp(mods->split.data, "topleft") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_TOP;
- } else if (strcmp(mods.split.data.string.data, "botright") == 0) {
+ } else if (strcmp(mods->split.data, "botright") == 0) {
cmdinfo.cmdmod.cmod_split |= WSP_BOT;
} else {
- VALIDATION_ERROR("Invalid value for 'mods.split'");
+ VALIDATE_S(false, "mods.split", "", {
+ goto end;
+ });
}
}
- OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods.silent, false, "'mods.silent'");
- OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods.emsg_silent, false, "'mods.emsg_silent'");
- OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods.unsilent, false, "'mods.unsilent'");
- OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods.sandbox, false, "'mods.sandbox'");
- OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods.noautocmd, false, "'mods.noautocmd'");
- OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods.browse, false, "'mods.browse'");
- OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods.confirm, false, "'mods.confirm'");
- OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods.hide, false, "'mods.hide'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods.keepalt, false, "'mods.keepalt'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods.keepjumps, false, "'mods.keepjumps'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods.keepmarks, false, "'mods.keepmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods.keeppatterns, false, "'mods.keeppatterns'");
- OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods.lockmarks, false, "'mods.lockmarks'");
- OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods.noswapfile, false, "'mods.noswapfile'");
+#define OBJ_TO_CMOD_FLAG(flag, value) \
+ if (value) { \
+ cmdinfo.cmdmod.cmod_flags |= (flag); \
+ }
+
+ OBJ_TO_CMOD_FLAG(CMOD_SILENT, mods->silent);
+ OBJ_TO_CMOD_FLAG(CMOD_ERRSILENT, mods->emsg_silent);
+ OBJ_TO_CMOD_FLAG(CMOD_UNSILENT, mods->unsilent);
+ OBJ_TO_CMOD_FLAG(CMOD_SANDBOX, mods->sandbox);
+ OBJ_TO_CMOD_FLAG(CMOD_NOAUTOCMD, mods->noautocmd);
+ OBJ_TO_CMOD_FLAG(CMOD_BROWSE, mods->browse);
+ OBJ_TO_CMOD_FLAG(CMOD_CONFIRM, mods->confirm);
+ OBJ_TO_CMOD_FLAG(CMOD_HIDE, mods->hide);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPALT, mods->keepalt);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPJUMPS, mods->keepjumps);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPMARKS, mods->keepmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_KEEPPATTERNS, mods->keeppatterns);
+ OBJ_TO_CMOD_FLAG(CMOD_LOCKMARKS, mods->lockmarks);
+ OBJ_TO_CMOD_FLAG(CMOD_NOSWAPFILE, mods->noswapfile);
if (cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT) {
// CMOD_ERRSILENT must imply CMOD_SILENT, otherwise apply_cmdmod() and undo_cmdmod() won't
@@ -666,9 +634,10 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
cmdinfo.cmdmod.cmod_flags |= CMOD_SILENT;
}
- if ((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)) {
- VALIDATION_ERROR("Command cannot be run in sandbox");
- }
+ VALIDATE(!((cmdinfo.cmdmod.cmod_flags & CMOD_SANDBOX) && !(ea.argt & EX_SBOXOK)),
+ "%s", "Command cannot be run in sandbox", {
+ goto end;
+ });
}
// Finally, build the command line string that will be stored inside ea.cmdlinep.
@@ -681,14 +650,13 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
garray_T * const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
- TRY_WRAP({
- try_start();
- if (output) {
+ TRY_WRAP(err, {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
@@ -697,21 +665,19 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
execute_cmd(&ea, &cmdinfo, false);
});
- if (output) {
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
msg_col = save_msg_col;
}
-
- try_end(err);
});
if (ERROR_SET(err)) {
goto clear_ga;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
retv = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -725,7 +691,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Error
goto end;
}
clear_ga:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
end:
@@ -739,7 +705,7 @@ end:
#undef OBJ_TO_BOOL
#undef OBJ_TO_CMOD_FLAG
-#undef VALIDATION_ERROR
+#undef VALIDATE_MOD
}
/// Check if a string contains only whitespace characters.
@@ -870,8 +836,8 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
offset += eap->arglens[i];
}
// If there isn't an argument, make eap->arg point to end of cmdline.
- eap->arg = argc > 0 ? eap->args[0] :
- cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
+ eap->arg = argc > 0 ? eap->args[0]
+ : cmdline.items + cmdline.size - 1; // Subtract 1 to account for NUL
// Finally, make cmdlinep point to the cmdline string.
*cmdlinep = cmdline.items;
@@ -888,18 +854,17 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
}
}
-/// Create a new user command |user-commands|
+/// Creates a global |user-commands| command.
///
-/// {name} is the name of the new command. The name must begin with an uppercase letter.
-///
-/// {command} is the replacement text or Lua function to execute.
+/// For Lua usage see |lua-guide-commands-create|.
///
/// Example:
-/// <pre>vim
-/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {})
-/// :SayHello
-/// Hello world!
-/// </pre>
+///
+/// ```vim
+/// :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true})
+/// :SayHello
+/// Hello world!
+/// ```
///
/// @param name Name of the new user command. Must begin with an uppercase letter.
/// @param command Replacement command to execute when this user command is executed. When called
@@ -909,6 +874,7 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - args: (string) The args passed to the command, if any |<args>|
/// - fargs: (table) The args split by unescaped whitespace (when more than one
/// argument is allowed), if any |<f-args>|
+/// - nargs: (string) Number of arguments |:command-nargs|
/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>|
/// - line1: (number) The starting line of the command range |<line1>|
/// - line2: (number) The final line of the command range |<line2>|
@@ -918,20 +884,22 @@ static void build_cmdline_str(char **cmdlinep, exarg_T *eap, CmdParseInfo *cmdin
/// - mods: (string) Command modifiers, if any |<mods>|
/// - smods: (table) Command modifiers in a structured format. Has the same
/// structure as the "mods" key of |nvim_parse_cmd()|.
-/// @param opts Optional command attributes. See |command-attributes| for more details. To use
-/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to
-/// "true". In addition to the string options listed in |:command-complete|, the
-/// "complete" key also accepts a Lua function which works like the "customlist"
-/// completion mode |:command-completion-customlist|. Additional parameters:
-/// - desc: (string) Used for listing the command when a Lua function is used for
-/// {command}.
-/// - force: (boolean, default true) Override any previous definition.
-/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
+/// @param opts Optional |command-attributes|.
+/// - Set boolean attributes such as |:command-bang| or |:command-bar| to true (but
+/// not |:command-buffer|, use |nvim_buf_create_user_command()| instead).
+/// - "complete" |:command-complete| also accepts a Lua function which works like
+/// |:command-completion-customlist|.
+/// - Other parameters:
+/// - desc: (string) Used for listing the command when a Lua function is used for
+/// {command}.
+/// - force: (boolean, default true) Override any previous definition.
+/// - preview: (function) Preview callback for 'inccommand' |:command-preview|
/// @param[out] err Error details, if any.
-void nvim_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err)
+void nvim_create_user_command(uint64_t channel_id, String name, Object command,
+ Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
- create_user_command(name, command, opts, 0, err);
+ create_user_command(channel_id, name, command, opts, 0, err);
}
/// Delete a user-defined command.
@@ -944,12 +912,12 @@ void nvim_del_user_command(String name, Error *err)
nvim_buf_del_user_command(-1, name, err);
}
-/// Create a new user command |user-commands| in the given buffer.
+/// Creates a buffer-local command |user-commands|.
///
/// @param buffer Buffer handle, or 0 for current buffer.
/// @param[out] err Error details, if any.
/// @see nvim_create_user_command
-void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
+void nvim_buf_create_user_command(uint64_t channel_id, Buffer buffer, String name, Object command,
Dict(user_command) *opts, Error *err)
FUNC_API_SINCE(9)
{
@@ -960,7 +928,7 @@ void nvim_buf_create_user_command(Buffer buffer, String name, Object command,
buf_T *save_curbuf = curbuf;
curbuf = target_buf;
- create_user_command(name, command, opts, UC_BUFFER, err);
+ create_user_command(channel_id, name, command, opts, UC_BUFFER, err);
curbuf = save_curbuf;
}
@@ -998,36 +966,33 @@ void nvim_buf_del_user_command(Buffer buffer, String name, Error *err)
}
}
- api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data);
+ api_set_error(err, kErrorTypeException, "Invalid command (not found): %s", name.data);
}
-void create_user_command(String name, Object command, Dict(user_command) *opts, int flags,
- Error *err)
+void create_user_command(uint64_t channel_id, String name, Object command, Dict(user_command) *opts,
+ int flags, Error *err)
{
uint32_t argt = 0;
- long def = -1;
+ int64_t def = -1;
cmd_addr_T addr_type_arg = ADDR_NONE;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
const char *rep = NULL;
LuaRef luaref = LUA_NOREF;
LuaRef compl_luaref = LUA_NOREF;
LuaRef preview_luaref = LUA_NOREF;
- if (!uc_validate_name(name.data)) {
- api_set_error(err, kErrorTypeValidation, "Invalid command name");
+ VALIDATE_S(uc_validate_name(name.data), "command name", name.data, {
goto err;
- }
-
- if (mb_islower(name.data[0])) {
- api_set_error(err, kErrorTypeValidation, "'name' must begin with an uppercase letter");
+ });
+ VALIDATE_S(!mb_islower(name.data[0]), "command name (must start with uppercase)",
+ name.data, {
goto err;
- }
-
- if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive");
+ });
+ VALIDATE((!HAS_KEY(opts, user_command, range) || !HAS_KEY(opts, user_command, count)), "%s",
+ "Cannot use both 'range' and 'count'", {
goto err;
- }
+ });
if (opts->nargs.type == kObjectTypeInteger) {
switch (opts->nargs.data.integer) {
@@ -1038,14 +1003,14 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_INT(false, "nargs", (int64_t)opts->nargs.data.integer, {
+ goto err;
+ });
}
} else if (opts->nargs.type == kObjectTypeString) {
- if (opts->nargs.data.string.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
+ VALIDATE_S((opts->nargs.data.string.size <= 1), "nargs", opts->nargs.data.string.data, {
goto err;
- }
+ });
switch (opts->nargs.data.string.data[0]) {
case '*':
@@ -1058,18 +1023,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_EXTRA | EX_NEEDARG;
break;
default:
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ VALIDATE_S(false, "nargs", opts->nargs.data.string.data, {
+ goto err;
+ });
}
- } else if (HAS_KEY(opts->nargs)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, nargs)) {
+ VALIDATE_S(false, "nargs", "", {
+ goto err;
+ });
}
- if (HAS_KEY(opts->complete) && !argt) {
- api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'");
+ VALIDATE((!HAS_KEY(opts, user_command, complete) || argt),
+ "%s", "'complete' used without 'nargs'", {
goto err;
- }
+ });
if (opts->range.type == kObjectTypeBoolean) {
if (opts->range.data.boolean) {
@@ -1077,20 +1044,20 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
addr_type_arg = ADDR_LINES;
}
} else if (opts->range.type == kObjectTypeString) {
- if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) {
- argt |= EX_RANGE | EX_DFLALL;
- addr_type_arg = ADDR_LINES;
- } else {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
+ VALIDATE_S((opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1),
+ "range", "", {
goto err;
- }
+ });
+ argt |= EX_RANGE | EX_DFLALL;
+ addr_type_arg = ADDR_LINES;
} else if (opts->range.type == kObjectTypeInteger) {
argt |= EX_RANGE | EX_ZEROR;
def = opts->range.data.integer;
addr_type_arg = ADDR_LINES;
- } else if (HAS_KEY(opts->range)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, range)) {
+ VALIDATE_S(false, "range", "", {
+ goto err;
+ });
}
if (opts->count.type == kObjectTypeBoolean) {
@@ -1103,76 +1070,72 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
argt |= EX_COUNT | EX_ZEROR | EX_RANGE;
addr_type_arg = ADDR_OTHER;
def = opts->count.data.integer;
- } else if (HAS_KEY(opts->count)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'");
- goto err;
+ } else if (HAS_KEY(opts, user_command, count)) {
+ VALIDATE_S(false, "count", "", {
+ goto err;
+ });
}
- if (opts->addr.type == kObjectTypeString) {
- if (parse_addr_type_arg(opts->addr.data.string.data, (int)opts->addr.data.string.size,
- &addr_type_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
+ if (HAS_KEY(opts, user_command, addr)) {
+ VALIDATE_T("addr", kObjectTypeString, opts->addr.type, {
goto err;
- }
+ });
+
+ VALIDATE_S(OK == parse_addr_type_arg(opts->addr.data.string.data,
+ (int)opts->addr.data.string.size, &addr_type_arg), "addr",
+ opts->addr.data.string.data, {
+ goto err;
+ });
if (addr_type_arg != ADDR_LINES) {
argt |= EX_ZEROR;
}
- } else if (HAS_KEY(opts->addr)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'");
- goto err;
}
- if (api_object_to_bool(opts->bang, "bang", false, err)) {
+ if (opts->bang) {
argt |= EX_BANG;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->bar, "bar", false, err)) {
+ if (opts->bar) {
argt |= EX_TRLBAR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->register_, "register", false, err)) {
+ if (opts->register_) {
argt |= EX_REGSTR;
- } else if (ERROR_SET(err)) {
- goto err;
}
- if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) {
+ if (opts->keepscript) {
argt |= EX_KEEPSCRIPT;
- } else if (ERROR_SET(err)) {
- goto err;
}
- bool force = api_object_to_bool(opts->force, "force", true, err);
+ bool force = GET_BOOL_OR_TRUE(opts, user_command, force);
if (ERROR_SET(err)) {
goto err;
}
if (opts->complete.type == kObjectTypeLuaRef) {
- compl = EXPAND_USER_LUA;
+ context = EXPAND_USER_LUA;
compl_luaref = api_new_luaref(opts->complete.data.luaref);
} else if (opts->complete.type == kObjectTypeString) {
- if (parse_compl_arg(opts->complete.data.string.data,
- (int)opts->complete.data.string.size, &compl, &argt,
- &compl_arg) != OK) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
+ VALIDATE_S(OK == parse_compl_arg(opts->complete.data.string.data,
+ (int)opts->complete.data.string.size, &context, &argt,
+ &compl_arg),
+ "complete", opts->complete.data.string.data, {
goto err;
- }
- } else if (HAS_KEY(opts->complete)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'");
- goto err;
+ });
+ } else if (HAS_KEY(opts, user_command, complete)) {
+ VALIDATE_EXP(false, "complete", "Function or String", NULL, {
+ goto err;
+ });
}
- if (opts->preview.type == kObjectTypeLuaRef) {
+ if (HAS_KEY(opts, user_command, preview)) {
+ VALIDATE_T("preview", kObjectTypeLuaRef, opts->preview.type, {
+ goto err;
+ });
+
argt |= EX_PREVIEW;
preview_luaref = api_new_luaref(opts->preview.data.luaref);
- } else if (HAS_KEY(opts->preview)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value for 'preview'");
- goto err;
}
switch (command.type) {
@@ -1188,15 +1151,18 @@ void create_user_command(String name, Object command, Dict(user_command) *opts,
rep = command.data.string.data;
break;
default:
- api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function");
- goto err;
+ VALIDATE_EXP(false, "command", "Function or String", NULL, {
+ goto err;
+ });
}
- if (uc_add_command(name.data, name.size, rep, argt, def, flags, compl, compl_arg, compl_luaref,
- preview_luaref, addr_type_arg, luaref, force) != OK) {
- api_set_error(err, kErrorTypeException, "Failed to create user command");
- // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
- }
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ if (uc_add_command(name.data, name.size, rep, argt, def, flags, context, compl_arg,
+ compl_luaref, preview_luaref, addr_type_arg, luaref, force) != OK) {
+ api_set_error(err, kErrorTypeException, "Failed to create user command");
+ // Do not goto err, since uc_add_command now owns luaref, compl_luaref, and compl_arg
+ }
+ });
return;
@@ -1209,6 +1175,8 @@ err:
///
/// Currently only |user-commands| are supported, not builtin Ex commands.
///
+/// @see |nvim_get_all_options_info()|
+///
/// @param opts Optional parameters. Currently only supports
/// {"builtin":false}
/// @param[out] err Error details, if any.
@@ -1231,13 +1199,12 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
FUNC_API_SINCE(4)
{
bool global = (buffer == -1);
- bool builtin = api_object_to_bool(opts->builtin, "builtin", false, err);
if (ERROR_SET(err)) {
return (Dictionary)ARRAY_DICT_INIT;
}
if (global) {
- if (builtin) {
+ if (opts->builtin) {
api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
return (Dictionary)ARRAY_DICT_INIT;
}
@@ -1245,7 +1212,7 @@ Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Error
}
buf_T *buf = find_buffer_by_handle(buffer, err);
- if (builtin || !buf) {
+ if (opts->builtin || !buf) {
return (Dictionary)ARRAY_DICT_INIT;
}
return commands_array(buf);
diff --git a/src/nvim/api/command.h b/src/nvim/api/command.h
index b1c9230551..1cccbfb4c7 100644
--- a/src/nvim/api/command.h
+++ b/src/nvim/api/command.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_API_COMMAND_H
-#define NVIM_API_COMMAND_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/ex_cmds.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/command.h.generated.h"
#endif
-#endif // NVIM_API_COMMAND_H
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index 332e2b5fc3..2ec11236d7 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -1,36 +1,50 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
-#include <stdlib.h>
+#include <string.h>
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/options.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/vimscript.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/highlight.h"
+#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/option.h"
+#include "nvim/pos_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.c.generated.h"
#endif
+/// @deprecated Use nvim_exec2() instead.
+/// @see nvim_exec2
+String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ Dict(exec_opts) opts = { .output = output };
+ return exec_impl(channel_id, src, &opts, err);
+}
+
/// @deprecated
-/// @see nvim_exec
+/// @see nvim_exec2
String nvim_command_output(uint64_t channel_id, String command, Error *err)
FUNC_API_SINCE(1)
FUNC_API_DEPRECATED_SINCE(7)
{
- return nvim_exec(channel_id, command, true, err);
+ Dict(exec_opts) opts = { .output = true };
+ return exec_impl(channel_id, command, &opts, err);
}
/// @deprecated Use nvim_exec_lua() instead.
@@ -140,25 +154,71 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
return 0;
}
- Decoration *existing = decor_find_virttext(buf, (int)line, ns_id);
+ DecorVirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
if (existing) {
- clear_virttext(&existing->virt_text);
- existing->virt_text = virt_text;
- existing->virt_text_width = width;
+ clear_virttext(&existing->data.virt_text);
+ existing->data.virt_text = virt_text;
+ existing->width = width;
return src_id;
}
- Decoration decor = DECORATION_INIT;
- decor.virt_text = virt_text;
- decor.virt_text_width = width;
- decor.priority = 0;
+ DecorVirtText *vt = xmalloc(sizeof *vt);
+ *vt = (DecorVirtText)DECOR_VIRT_TEXT_INIT;
+ vt->data.virt_text = virt_text;
+ vt->width = width;
+ vt->priority = 0;
- extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true,
- false, kExtmarkNoUndo);
+ DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID };
+
+ extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true,
+ false, false, false, NULL);
return src_id;
}
+/// Gets a highlight definition by id. |hlID()|
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param hl_id Highlight id as returned by |hlID()|
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_name
+Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary dic = ARRAY_DICT_INIT;
+ VALIDATE_INT((syn_get_final_id((int)hl_id) != 0), "highlight id", hl_id, {
+ return dic;
+ });
+ int attrcode = syn_id2attr((int)hl_id);
+ return hl_get_attr_by_id(attrcode, rgb, arena, err);
+}
+
+/// Gets a highlight definition by name.
+///
+/// @deprecated use |nvim_get_hl()| instead
+///
+/// @param name Highlight group name
+/// @param rgb Export RGB colors
+/// @param[out] err Error details, if any
+/// @return Highlight definition map
+/// @see nvim_get_hl_by_id
+Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
+ FUNC_API_SINCE(3)
+ FUNC_API_DEPRECATED_SINCE(9)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+ int id = syn_name2id(name.data);
+
+ VALIDATE_S((id != 0), "highlight name", name.data, {
+ return result;
+ });
+ return nvim_get_hl_by_id(id, rgb, arena, err);
+}
+
/// Inserts a sequence of lines to a buffer at a certain index
///
/// @deprecated use nvim_buf_set_lines(buffer, lnum, lnum, true, lines)
@@ -449,3 +509,195 @@ static int64_t convert_index(int64_t index)
{
return index < 0 ? index - 1 : index;
}
+
+/// Gets the option information for one option
+///
+/// @deprecated Use @ref nvim_get_option_info2 instead.
+///
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option Information
+Dictionary nvim_get_option_info(String name, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_vimoption(name, OPT_GLOBAL, curbuf, curwin, err);
+}
+
+/// Sets the global value of an option.
+///
+/// @deprecated
+/// @param channel_id
+/// @param name Option name
+/// @param value New option value
+/// @param[out] err Error details, if any
+void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ set_option_to(channel_id, NULL, kOptReqGlobal, name, value, err);
+}
+
+/// Gets the global value of an option.
+///
+/// @deprecated
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value (global)
+Object nvim_get_option(String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ return get_option_from(NULL, kOptReqGlobal, name, err);
+}
+
+/// Gets a buffer option value
+///
+/// @deprecated
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(buf, kOptReqBuf, name, err);
+}
+
+/// Sets a buffer option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (!buf) {
+ return;
+ }
+
+ set_option_to(channel_id, buf, kOptReqBuf, name, value, err);
+}
+
+/// Gets a window option value
+///
+/// @deprecated
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param[out] err Error details, if any
+/// @return Option value
+Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return (Object)OBJECT_INIT;
+ }
+
+ return get_option_from(win, kOptReqWin, name, err);
+}
+
+/// Sets a window option value. Passing `nil` as value deletes the option (only
+/// works if there's a global fallback)
+///
+/// @deprecated
+/// @param channel_id
+/// @param window Window handle, or 0 for current window
+/// @param name Option name
+/// @param value Option value
+/// @param[out] err Error details, if any
+void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
+ FUNC_API_SINCE(1)
+ FUNC_API_DEPRECATED_SINCE(11)
+{
+ win_T *win = find_window_by_handle(window, err);
+
+ if (!win) {
+ return;
+ }
+
+ set_option_to(channel_id, win, kOptReqWin, name, value, err);
+}
+
+/// Gets the value of a global or local (buffer, window) option.
+///
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param[out] err Details of an error that may have occurred.
+///
+/// @return the option value.
+static Object get_option_from(void *from, OptReqScope req_scope, String name, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return (Object)OBJECT_INIT;
+ });
+
+ OptVal value = get_option_value_strict(name.data, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return (Object)OBJECT_INIT;
+ }
+
+ VALIDATE_S(value.type != kOptValTypeNil, "option name", name.data, {
+ return (Object)OBJECT_INIT;
+ });
+
+ return optval_as_object(value);
+}
+
+/// Sets the value of a global or local (buffer, window) option.
+///
+/// @param[in] to Pointer to buffer or window for local option value.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param name The option name.
+/// @param value New option value.
+/// @param[out] err Details of an error that may have occurred.
+static void set_option_to(uint64_t channel_id, void *to, OptReqScope req_scope, String name,
+ Object value, Error *err)
+{
+ VALIDATE_S(name.size > 0, "option name", "<empty>", {
+ return;
+ });
+
+ int flags = get_option_attrs(name.data);
+ VALIDATE_S(flags != 0, "option name", name.data, {
+ return;
+ });
+
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
+
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
+ return;
+ });
+
+ // For global-win-local options -> setlocal
+ // For win-local options -> setglobal and setlocal (opt_flags == 0)
+ const int opt_flags = (req_scope == kOptReqWin && !(flags & SOPT_GLOBAL))
+ ? 0
+ : (req_scope == kOptReqGlobal) ? OPT_GLOBAL : OPT_LOCAL;
+
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, opt_flags, req_scope, to, err);
+ });
+}
diff --git a/src/nvim/api/deprecated.h b/src/nvim/api/deprecated.h
index 79095167e1..e20d8304e0 100644
--- a/src/nvim/api/deprecated.h
+++ b/src/nvim/api/deprecated.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_API_DEPRECATED_H
-#define NVIM_API_DEPRECATED_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/deprecated.h.generated.h"
#endif
-#endif // NVIM_API_DEPRECATED_H
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index ab3b3485e4..d71498d6ed 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -1,29 +1,31 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
+#include "nvim/marktree.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.c.generated.h"
@@ -32,12 +34,10 @@
void api_extmark_free_all_mem(void)
{
String name;
- handle_T id;
- map_foreach(&namespace_ids, name, id, {
- (void)id;
+ map_foreach_key(&namespace_ids, name, {
xfree(name.data);
})
- map_destroy(String, handle_T)(&namespace_ids);
+ map_destroy(String, &namespace_ids);
}
/// Creates a new namespace or gets an existing one. \*namespace\*
@@ -54,14 +54,14 @@ void api_extmark_free_all_mem(void)
Integer nvim_create_namespace(String name)
FUNC_API_SINCE(5)
{
- handle_T id = map_get(String, handle_T)(&namespace_ids, name);
+ handle_T id = map_get(String, int)(&namespace_ids, name);
if (id > 0) {
return id;
}
id = next_namespace_id++;
if (name.size > 0) {
String name_alloc = copy_string(name, NULL);
- map_put(String, handle_T)(&namespace_ids, name_alloc, id);
+ map_put(String, int)(&namespace_ids, name_alloc, id);
}
return (Integer)id;
}
@@ -83,7 +83,7 @@ Dictionary nvim_get_namespaces(void)
return retval;
}
-const char *describe_ns(NS ns_id)
+const char *describe_ns(NS ns_id, const char *unknown)
{
String name;
handle_T id;
@@ -92,7 +92,7 @@ const char *describe_ns(NS ns_id)
return name.data;
}
})
- return "(UNKNOWN PLUGIN)";
+ return unknown;
}
// Is the Namespace in use?
@@ -104,92 +104,73 @@ bool ns_initialized(uint32_t ns)
return ns < (uint32_t)next_namespace_id;
}
-static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict)
+Array virt_text_to_array(VirtText vt, bool hl_name)
{
+ Array chunks = ARRAY_DICT_INIT;
+ Array hl_array = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < kv_size(vt); i++) {
+ char *text = kv_A(vt, i).text;
+ int hl_id = kv_A(vt, i).hl_id;
+ if (text == NULL) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ continue;
+ }
+ Array chunk = ARRAY_DICT_INIT;
+ ADD(chunk, CSTR_TO_OBJ(text));
+ if (hl_array.size > 0) {
+ if (hl_id > 0) {
+ ADD(hl_array, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunk, ARRAY_OBJ(hl_array));
+ hl_array = (Array)ARRAY_DICT_INIT;
+ } else if (hl_id > 0) {
+ ADD(chunk, hl_group_name(hl_id, hl_name));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+ assert(hl_array.size == 0);
+ return chunks;
+}
+
+static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_name)
+{
+ MTKey start = extmark.start;
Array rv = ARRAY_DICT_INIT;
if (id) {
- ADD(rv, INTEGER_OBJ((Integer)extmark->mark_id));
+ ADD(rv, INTEGER_OBJ((Integer)start.id));
}
- ADD(rv, INTEGER_OBJ(extmark->row));
- ADD(rv, INTEGER_OBJ(extmark->col));
+ ADD(rv, INTEGER_OBJ(start.pos.row));
+ ADD(rv, INTEGER_OBJ(start.pos.col));
if (add_dict) {
Dictionary dict = ARRAY_DICT_INIT;
- PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark->right_gravity));
+ PUT(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
- if (extmark->end_row >= 0) {
- PUT(dict, "end_row", INTEGER_OBJ(extmark->end_row));
- PUT(dict, "end_col", INTEGER_OBJ(extmark->end_col));
- PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark->end_right_gravity));
- }
+ PUT(dict, "right_gravity", BOOLEAN_OBJ(mt_right(start)));
- const Decoration *decor = &extmark->decor;
- if (decor->hl_id) {
- String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
- PUT(dict, "hl_group", STRING_OBJ(name));
- PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol));
- }
- if (decor->hl_mode) {
- PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode])));
+ if (extmark.end_pos.row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_pos.row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_pos.col));
+ PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity));
}
- if (kv_size(decor->virt_text)) {
- Array chunks = ARRAY_DICT_INIT;
- for (size_t i = 0; i < decor->virt_text.size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &decor->virt_text.items[i];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- PUT(dict, "virt_text", ARRAY_OBJ(chunks));
- PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide));
- if (decor->virt_text_pos == kVTWinCol) {
- PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col));
- }
- PUT(dict, "virt_text_pos",
- STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos])));
+ if (mt_no_undo(start)) {
+ PUT(dict, "undo_restore", BOOLEAN_OBJ(false));
}
- if (decor->ui_watched) {
- PUT(dict, "ui_watched", BOOLEAN_OBJ(true));
+ if (mt_invalidate(start)) {
+ PUT(dict, "invalidate", BOOLEAN_OBJ(true));
}
-
- if (kv_size(decor->virt_lines)) {
- Array all_chunks = ARRAY_DICT_INIT;
- bool virt_lines_leftcol = false;
- for (size_t i = 0; i < decor->virt_lines.size; i++) {
- Array chunks = ARRAY_DICT_INIT;
- VirtText *vt = &decor->virt_lines.items[i].line;
- virt_lines_leftcol = decor->virt_lines.items[i].left_col;
- for (size_t j = 0; j < vt->size; j++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &vt->items[j];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
- ADD(all_chunks, ARRAY_OBJ(chunks));
- }
- PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks));
- PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above));
- PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ if (mt_invalid(start)) {
+ PUT(dict, "invalid", BOOLEAN_OBJ(true));
}
- if (decor->hl_id || kv_size(decor->virt_text) || decor->ui_watched) {
- PUT(dict, "priority", INTEGER_OBJ(decor->priority));
- }
+ decor_to_dict_legacy(&dict, mt_decor(start), hl_name);
- if (dict.size) {
- ADD(rv, DICTIONARY_OBJ(dict));
- }
+ ADD(rv, DICTIONARY_OBJ(dict));
}
return rv;
@@ -202,6 +183,7 @@ static Array extmark_to_array(const ExtmarkInfo *extmark, bool id, bool add_dict
/// @param id Extmark id
/// @param opts Optional parameters. Keys:
/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
/// @param[out] err Error details, if any
/// @return 0-indexed (row, col) tuple or empty list () if extmark id was
/// absent
@@ -218,80 +200,92 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
+ });
bool details = false;
+ bool hl_name = true;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ details = api_object_to_bool(*v, "details", false, err);
+ if (ERROR_SET(err)) {
+ return rv;
+ }
+ } else if (strequal("hl_name", k.data)) {
+ hl_name = api_object_to_bool(*v, "hl_name", false, err);
+ if (ERROR_SET(err)) {
return rv;
}
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_S(false, "'opts' key", k.data, {
+ return rv;
+ });
}
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row < 0) {
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+ if (extmark.start.pos.row < 0) {
return rv;
}
- return extmark_to_array(&extmark, false, details);
+ return extmark_to_array(extmark, false, details, hl_name);
}
-/// Gets |extmarks| in "traversal order" from a |charwise| region defined by
-/// buffer positions (inclusive, 0-indexed |api-indexing|).
+/// Gets |extmarks| (including |signs|) in "traversal order" from a |charwise|
+/// region defined by buffer positions (inclusive, 0-indexed |api-indexing|).
///
/// Region can be given as (row,col) tuples, or valid extmark ids (whose
/// positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
/// respectively, thus the following are equivalent:
-/// <pre>lua
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
-/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {})
+/// vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {})
+/// ```
///
/// If `end` is less than `start`, traversal works backwards. (Useful
/// with `limit`, to get the first marks prior to a given position.)
///
+/// Note: when using extmark ranges (marks with a end_row/end_col position)
+/// the `overlap` option might be useful. Otherwise only the start position
+/// of an extmark will be considered.
+///
/// Example:
-/// <pre>lua
-/// local a = vim.api
-/// local pos = a.nvim_win_get_cursor(0)
-/// local ns = a.nvim_create_namespace('my-plugin')
-/// -- Create new extmark at line 1, column 1.
-/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {})
-/// -- Create new extmark at line 3, column 1.
-/// local m2 = a.nvim_buf_set_extmark(0, ns, 2, 0, {})
-/// -- Get extmarks only from line 3.
-/// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
-/// -- Get all marks in this buffer + namespace.
-/// local all = a.nvim_buf_get_extmarks(0, ns, 0, -1, {})
-/// print(vim.inspect(ms))
-/// </pre>
+///
+/// ```lua
+/// local api = vim.api
+/// local pos = api.nvim_win_get_cursor(0)
+/// local ns = api.nvim_create_namespace('my-plugin')
+/// -- Create new extmark at line 1, column 1.
+/// local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {})
+/// -- Create new extmark at line 3, column 1.
+/// local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {})
+/// -- Get extmarks only from line 3.
+/// local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {})
+/// -- Get all marks in this buffer + namespace.
+/// local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {})
+/// vim.print(ms)
+/// ```
///
/// @param buffer Buffer handle, or 0 for current buffer
-/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param ns_id Namespace id from |nvim_create_namespace()| or -1 for all namespaces
/// @param start Start of range: a 0-indexed (row, col) or valid extmark id
/// (whose position defines the bound). |api-indexing|
/// @param end End of range (inclusive): a 0-indexed (row, col) or valid
/// extmark id (whose position defines the bound). |api-indexing|
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
-/// - details Whether to include the details dict
+/// - details: Whether to include the details dict
+/// - hl_name: Whether to include highlight group name instead of id, true if omitted
+/// - overlap: Also include marks which overlap the range, even if
+/// their start position is less than `start`
+/// - type: Filter marks by type: "highlight", "sign", "virt_text" and "virt_lines"
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end, Dictionary opts,
- Error *err)
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object end,
+ Dict(get_extmarks) *opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -301,38 +295,32 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
return rv;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_id == -1 || ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return rv;
- }
-
- Integer limit = -1;
- bool details = false;
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("limit", k.data)) {
- if (v->type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "limit is not an integer");
- return rv;
- }
- limit = v->data.integer;
- } else if (strequal("details", k.data)) {
- if (v->type == kObjectTypeBoolean) {
- details = v->data.boolean;
- } else if (v->type == kObjectTypeInteger) {
- details = v->data.integer;
- } else {
- api_set_error(err, kErrorTypeValidation, "details is not an boolean");
- return rv;
- }
+ });
+
+ bool details = opts->details;
+ bool hl_name = GET_BOOL_OR_TRUE(opts, get_extmarks, hl_name);
+
+ ExtmarkType type = kExtmarkNone;
+ if (HAS_KEY(opts, get_extmarks, type)) {
+ if (strequal(opts->type.data, "sign")) {
+ type = kExtmarkSign;
+ } else if (strequal(opts->type.data, "virt_text")) {
+ type = kExtmarkVirtText;
+ } else if (strequal(opts->type.data, "virt_lines")) {
+ type = kExtmarkVirtLines;
+ } else if (strequal(opts->type.data, "highlight")) {
+ type = kExtmarkHighlight;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- return rv;
+ VALIDATE_EXP(false, "type", "sign, virt_text, virt_lines or highlight", opts->type.data, {
+ return rv;
+ });
}
}
+ Integer limit = HAS_KEY(opts, get_extmarks, limit) ? opts->limit : -1;
+
if (limit == 0) {
return rv;
} else if (limit < 0) {
@@ -357,11 +345,12 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
reverse = true;
}
- ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col,
- u_row, u_col, (int64_t)limit, reverse);
+ // note: ns_id=-1 allowed, represented as UINT32_MAX
+ ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row,
+ u_col, (int64_t)limit, reverse, type, opts->overlap);
for (size_t i = 0; i < kv_size(marks); i++) {
- ADD(rv, ARRAY_OBJ(extmark_to_array(&kv_A(marks, i), true, (bool)details)));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, details, hl_name)));
}
kv_destroy(marks);
@@ -379,6 +368,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// Using the optional arguments, it is possible to use this to highlight
/// a range of text, and also to associate virtual text to the mark.
///
+/// If present, the position defined by `end_col` and `end_row` should be after
+/// the start position in order for the extmark to cover a range.
+/// An earlier end position is not an error, but then it behaves like an empty
+/// range (no highlighting).
+///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param line Line where to place the mark, 0-based. |api-indexing|
@@ -402,24 +396,28 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// either as a string or as an integer, the latter which
/// can be obtained using |nvim_get_hl_id_by_name()|.
/// - virt_text_pos : position of virtual text. Possible values:
-/// - "eol": right after eol character (default)
+/// - "eol": right after eol character (default).
/// - "overlay": display over the specified column, without
/// shifting the underlying text.
/// - "right_align": display right aligned in the window.
+/// - "inline": display at the specified column, and
+/// shift the buffer text to the right as needed.
/// - virt_text_win_col : position the virtual text at a fixed
/// window column (starting from the first
-/// text column)
+/// text column of the screen line) instead
+/// of "virt_text_pos".
/// - virt_text_hide : hide the virtual text when the background
-/// text is selected or hidden due to
-/// horizontal scroll 'nowrap'
+/// text is selected or hidden because of
+/// scrolling with 'nowrap' or 'smoothscroll'.
+/// Currently only affects "overlay" virt_text.
/// - hl_mode : control how highlights are combined with the
/// highlights of the text. Currently only affects
/// virt_text highlights, but might affect `hl_group`
/// in later versions.
-/// - "replace": only show the virt_text color. This is the
-/// default
-/// - "combine": combine with background text color
+/// - "replace": only show the virt_text color. This is the default.
+/// - "combine": combine with background text color.
/// - "blend": blend with background text color.
+/// Not supported for "inline" virt_text.
///
/// - virt_lines : virtual lines to add next to this mark
/// This should be an array over lines, where each line in
@@ -443,11 +441,17 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// buffer.
/// - right_gravity : boolean that indicates the direction
/// the extmark will be shifted in when new text is inserted
-/// (true for right, false for left). defaults to true.
+/// (true for right, false for left). Defaults to true.
/// - end_right_gravity : boolean that indicates the direction
/// the extmark end position (if it exists) will be shifted
/// in when new text is inserted (true for right, false
/// for left). Defaults to false.
+/// - undo_restore : Restore the exact position of the mark
+/// if text around the mark was deleted and then restored by undo.
+/// Defaults to true.
+/// - invalidate : boolean that indicates whether to hide the
+/// extmark if the entirety of its range is deleted. If
+/// "undo_restore" is false, the extmark is deleted instead.
/// - priority: a priority value for the highlight group or sign
/// attribute. For example treesitter highlighting uses a
/// value of 100.
@@ -493,286 +497,255 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
Dict(set_extmark) *opts, Error *err)
FUNC_API_SINCE(7)
{
- Decoration decor = DECORATION_INIT;
- bool has_decor = false;
+ DecorHighlightInline hl = DECOR_HIGHLIGHT_INLINE_INIT;
+ // TODO(bfredl): in principle signs with max one (1) hl group and max 4 bytes of text.
+ // should be a candidate for inlining as well.
+ DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorVirtText virt_text = DECOR_VIRT_TEXT_INIT;
+ DecorVirtText virt_lines = DECOR_VIRT_LINES_INIT;
+ bool has_hl = false;
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
goto error;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
goto error;
- }
+ });
uint32_t id = 0;
- if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) {
- id = (uint32_t)opts->id.data.integer;
- } else if (HAS_KEY(opts->id)) {
- api_set_error(err, kErrorTypeValidation, "id is not a positive integer");
- goto error;
+ if (HAS_KEY(opts, set_extmark, id)) {
+ VALIDATE_EXP((opts->id > 0), "id", "positive Integer", NULL, {
+ goto error;
+ });
+
+ id = (uint32_t)opts->id;
}
int line2 = -1;
+ bool did_end_line = false;
// For backward compatibility we support "end_line" as an alias for "end_row"
- if (HAS_KEY(opts->end_line)) {
- if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line");
+ if (HAS_KEY(opts, set_extmark, end_line)) {
+ VALIDATE(!HAS_KEY(opts, set_extmark, end_row),
+ "%s", "cannot use both 'end_row' and 'end_line'", {
goto error;
- }
- opts->end_row = opts->end_line;
- }
+ });
-#define OPTION_TO_BOOL(target, name, val) \
- target = api_object_to_bool(opts->name, #name, val, err); \
- if (ERROR_SET(err)) { \
- goto error; \
+ opts->end_row = opts->end_line;
+ did_end_line = true;
}
- bool strict = true;
- OPTION_TO_BOOL(strict, strict, true);
+ bool strict = GET_BOOL_OR_TRUE(opts, set_extmark, strict);
- if (opts->end_row.type == kObjectTypeInteger) {
- Integer val = opts->end_row.data.integer;
- if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) {
- api_set_error(err, kErrorTypeValidation, "end_row value outside range");
+ if (HAS_KEY(opts, set_extmark, end_row) || did_end_line) {
+ Integer val = opts->end_row;
+ VALIDATE_RANGE((val >= 0 && !(val > buf->b_ml.ml_line_count && strict)), "end_row", {
goto error;
- } else {
- line2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_row)) {
- api_set_error(err, kErrorTypeValidation, "end_row is not an integer");
- goto error;
+ });
+ line2 = (int)val;
}
colnr_T col2 = -1;
- if (opts->end_col.type == kObjectTypeInteger) {
- Integer val = opts->end_col.data.integer;
- if (val < 0 || val > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ if (HAS_KEY(opts, set_extmark, end_col)) {
+ Integer val = opts->end_col;
+ VALIDATE_RANGE((val >= 0 && val <= MAXCOL), "end_col", {
goto error;
- } else {
- col2 = (int)val;
- }
- } else if (HAS_KEY(opts->end_col)) {
- api_set_error(err, kErrorTypeValidation, "end_col is not an integer");
- goto error;
+ });
+ col2 = (int)val;
}
// uncrustify:off
+ // TODO(bfredl): keyset type alias for hl_group? (nil|int|string)
struct {
const char *name;
Object *opt;
int *dest;
} hls[] = {
- { "hl_group" , &opts->hl_group , &decor.hl_id },
- { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id },
- { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id },
- { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id },
- { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id },
+ { "hl_group" , &opts->hl_group , &hl.hl_id },
+ { "sign_hl_group" , &opts->sign_hl_group , &sign.hl_id },
+ { "number_hl_group" , &opts->number_hl_group , &sign.number_hl_id },
+ { "line_hl_group" , &opts->line_hl_group , &sign.line_hl_id },
+ { "cursorline_hl_group", &opts->cursorline_hl_group, &sign.cursorline_hl_id },
{ NULL, NULL, NULL },
};
// uncrustify:on
for (int j = 0; hls[j].name && hls[j].dest; j++) {
- if (HAS_KEY(*hls[j].opt)) {
+ if (hls[j].opt->type != kObjectTypeNil) {
+ if (j > 0) {
+ sign.flags |= kSHIsSign;
+ } else {
+ has_hl = true;
+ }
*hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err);
if (ERROR_SET(err)) {
goto error;
}
- has_decor = true;
}
}
- if (opts->conceal.type == kObjectTypeString) {
- String c = opts->conceal.data.string;
- decor.conceal = true;
- if (c.size) {
- decor.conceal_char = utf_ptr2char(c.data);
+ if (HAS_KEY(opts, set_extmark, conceal)) {
+ hl.flags |= kSHConceal;
+ has_hl = true;
+ String c = opts->conceal;
+ if (c.size > 0) {
+ int ch;
+ hl.conceal_char = utfc_ptr2schar_len(c.data, (int)c.size, &ch);
+ if (!hl.conceal_char || !vim_isprintc(ch)) {
+ api_set_error(err, kErrorTypeValidation, "conceal char has to be printable");
+ goto error;
+ }
}
- has_decor = true;
- } else if (HAS_KEY(opts->conceal)) {
- api_set_error(err, kErrorTypeValidation, "conceal is not a String");
- goto error;
}
- if (opts->virt_text.type == kObjectTypeArray) {
- decor.virt_text = parse_virt_text(opts->virt_text.data.array, err,
- &decor.virt_text_width);
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, virt_text)) {
+ virt_text.data.virt_text = parse_virt_text(opts->virt_text, err, &virt_text.width);
if (ERROR_SET(err)) {
goto error;
}
- } else if (HAS_KEY(opts->virt_text)) {
- api_set_error(err, kErrorTypeValidation, "virt_text is not an Array");
- goto error;
}
- if (opts->virt_text_pos.type == kObjectTypeString) {
- String str = opts->virt_text_pos.data.string;
+ if (HAS_KEY(opts, set_extmark, virt_text_pos)) {
+ String str = opts->virt_text_pos;
if (strequal("eol", str.data)) {
- decor.virt_text_pos = kVTEndOfLine;
+ virt_text.pos = kVPosEndOfLine;
} else if (strequal("overlay", str.data)) {
- decor.virt_text_pos = kVTOverlay;
+ virt_text.pos = kVPosOverlay;
} else if (strequal("right_align", str.data)) {
- decor.virt_text_pos = kVTRightAlign;
+ virt_text.pos = kVPosRightAlign;
+ } else if (strequal("inline", str.data)) {
+ virt_text.pos = kVPosInline;
} else {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "virt_text_pos", str.data, {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->virt_text_pos)) {
- api_set_error(err, kErrorTypeValidation, "virt_text_pos is not a String");
- goto error;
}
- if (opts->virt_text_win_col.type == kObjectTypeInteger) {
- decor.col = (int)opts->virt_text_win_col.data.integer;
- decor.virt_text_pos = kVTWinCol;
- } else if (HAS_KEY(opts->virt_text_win_col)) {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_win_col is not a Number of the correct size");
- goto error;
+ if (HAS_KEY(opts, set_extmark, virt_text_win_col)) {
+ virt_text.col = (int)opts->virt_text_win_col;
+ virt_text.pos = kVPosWinCol;
}
- OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false);
- OPTION_TO_BOOL(decor.hl_eol, hl_eol, false);
+ hl.flags |= opts->hl_eol ? kSHHlEol : 0;
+ virt_text.flags |= opts->virt_text_hide ? kVTHide : 0;
- if (opts->hl_mode.type == kObjectTypeString) {
- String str = opts->hl_mode.data.string;
+ if (HAS_KEY(opts, set_extmark, hl_mode)) {
+ String str = opts->hl_mode;
if (strequal("replace", str.data)) {
- decor.hl_mode = kHlModeReplace;
+ virt_text.hl_mode = kHlModeReplace;
} else if (strequal("combine", str.data)) {
- decor.hl_mode = kHlModeCombine;
+ virt_text.hl_mode = kHlModeCombine;
} else if (strequal("blend", str.data)) {
- decor.hl_mode = kHlModeBlend;
+ if (virt_text.pos == kVPosInline) {
+ VALIDATE(false, "%s", "cannot use 'blend' hl_mode with inline virtual text", {
+ goto error;
+ });
+ }
+ virt_text.hl_mode = kHlModeBlend;
} else {
- api_set_error(err, kErrorTypeValidation,
- "virt_text_pos: invalid value");
- goto error;
+ VALIDATE_S(false, "hl_mode", str.data, {
+ goto error;
+ });
}
- } else if (HAS_KEY(opts->hl_mode)) {
- api_set_error(err, kErrorTypeValidation, "hl_mode is not a String");
- goto error;
}
- bool virt_lines_leftcol = false;
- OPTION_TO_BOOL(virt_lines_leftcol, virt_lines_leftcol, false);
+ bool virt_lines_leftcol = opts->virt_lines_leftcol;
- if (opts->virt_lines.type == kObjectTypeArray) {
- Array a = opts->virt_lines.data.array;
+ if (HAS_KEY(opts, set_extmark, virt_lines)) {
+ Array a = opts->virt_lines;
for (size_t j = 0; j < a.size; j++) {
- if (a.items[j].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "virt_text_line item is not an Array");
+ VALIDATE_T("virt_text_line", kObjectTypeArray, a.items[j].type, {
goto error;
- }
+ });
int dummig;
VirtText jtem = parse_virt_text(a.items[j].data.array, err, &dummig);
- kv_push(decor.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
+ kv_push(virt_lines.data.virt_lines, ((struct virt_line){ jtem, virt_lines_leftcol }));
if (ERROR_SET(err)) {
goto error;
}
- has_decor = true;
}
- } else if (HAS_KEY(opts->virt_lines)) {
- api_set_error(err, kErrorTypeValidation, "virt_lines is not an Array");
- goto error;
}
- OPTION_TO_BOOL(decor.virt_lines_above, virt_lines_above, false);
-
- if (opts->priority.type == kObjectTypeInteger) {
- Integer val = opts->priority.data.integer;
+ virt_lines.flags |= opts->virt_lines_above ? kVTLinesAbove : 0;
- if (val < 0 || val > UINT16_MAX) {
- api_set_error(err, kErrorTypeValidation, "priority is not a valid value");
+ if (HAS_KEY(opts, set_extmark, priority)) {
+ VALIDATE_RANGE((opts->priority >= 0 && opts->priority <= UINT16_MAX), "priority", {
goto error;
- }
- decor.priority = (DecorPriority)val;
- } else if (HAS_KEY(opts->priority)) {
- api_set_error(err, kErrorTypeValidation, "priority is not a Number of the correct size");
- goto error;
+ });
+ hl.priority = (DecorPriority)opts->priority;
+ sign.priority = (DecorPriority)opts->priority;
+ virt_text.priority = (DecorPriority)opts->priority;
+ virt_lines.priority = (DecorPriority)opts->priority;
}
- if (opts->sign_text.type == kObjectTypeString) {
- if (!init_sign_text(&decor.sign_text,
- opts->sign_text.data.string.data)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value");
+ if (HAS_KEY(opts, set_extmark, sign_text)) {
+ sign.text.ptr = NULL;
+ VALIDATE_S(init_sign_text(NULL, &sign.text.ptr, opts->sign_text.data),
+ "sign_text", "", {
goto error;
- }
- has_decor = true;
- } else if (HAS_KEY(opts->sign_text)) {
- api_set_error(err, kErrorTypeValidation, "sign_text is not a String");
- goto error;
+ });
+ sign.flags |= kSHIsSign;
}
- bool right_gravity = true;
- OPTION_TO_BOOL(right_gravity, right_gravity, true);
+ bool right_gravity = GET_BOOL_OR_TRUE(opts, set_extmark, right_gravity);
// Only error out if they try to set end_right_gravity without
// setting end_col or end_row
- if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) {
- api_set_error(err, kErrorTypeValidation,
- "cannot set end_right_gravity without setting end_row or end_col");
+ VALIDATE(!(line2 == -1 && col2 == -1 && HAS_KEY(opts, set_extmark, end_right_gravity)),
+ "%s", "cannot set end_right_gravity without end_row or end_col", {
goto error;
- }
-
- bool end_right_gravity = false;
- OPTION_TO_BOOL(end_right_gravity, end_right_gravity, false);
+ });
size_t len = 0;
- bool ephemeral = false;
- OPTION_TO_BOOL(ephemeral, ephemeral, false);
-
- if (opts->spell.type == kObjectTypeNil) {
- decor.spell = kNone;
- } else {
- bool spell = false;
- OPTION_TO_BOOL(spell, spell, false);
- decor.spell = spell ? kTrue : kFalse;
- has_decor = true;
+ if (HAS_KEY(opts, set_extmark, spell)) {
+ hl.flags |= (opts->spell) ? kSHSpellOn : kSHSpellOff;
+ has_hl = true;
}
- OPTION_TO_BOOL(decor.ui_watched, ui_watched, false);
- if (decor.ui_watched) {
- has_decor = true;
+ if (opts->ui_watched) {
+ hl.flags |= kSHUIWatched;
+ if (virt_text.pos == kVPosOverlay) {
+ // TODO(bfredl): in a revised interface this should be the default.
+ hl.flags |= kSHUIWatchedOverlay;
+ }
+ has_hl = true;
}
- if (line < 0) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ VALIDATE_RANGE((line >= 0), "line", {
goto error;
- } else if (line > buf->b_ml.ml_line_count) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "line value outside range");
+ });
+
+ if (line > buf->b_ml.ml_line_count) {
+ VALIDATE_RANGE(!strict, "line", {
goto error;
- } else {
- line = buf->b_ml.ml_line_count;
- }
+ });
+ line = buf->b_ml.ml_line_count;
} else if (line < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1, false));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line + 1));
}
if (col == -1) {
col = (Integer)len;
} else if (col > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
+ VALIDATE_RANGE(!strict, "col", {
goto error;
- } else {
- col = (Integer)len;
- }
+ });
+ col = (Integer)len;
} else if (col < -1) {
- api_set_error(err, kErrorTypeValidation, "col value outside range");
- goto error;
+ VALIDATE_RANGE(false, "col", {
+ goto error;
+ });
}
if (col2 >= 0) {
if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
- len = ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ len = opts->ephemeral ? MAXCOL : strlen(ml_get_buf(buf, (linenr_T)line2 + 1));
} else if (line2 == buf->b_ml.ml_line_count) {
// We are trying to add an extmark past final newline
len = 0;
@@ -781,36 +754,96 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
line2 = (int)line;
}
if (col2 > (Integer)len) {
- if (strict) {
- api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ VALIDATE_RANGE(!strict, "end_col", {
goto error;
- } else {
- col2 = (int)len;
- }
+ });
+ col2 = (int)len;
}
} else if (line2 >= 0) {
col2 = 0;
}
- // TODO(bfredl): synergize these two branches even more
- if (ephemeral && decor_state.buf == buf) {
- decor_add_ephemeral((int)line, (int)col, line2, col2, &decor, (uint64_t)ns_id, id);
+ if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) {
+ int r = (int)line;
+ int c = (int)col;
+ if (line2 == -1) {
+ line2 = r;
+ col2 = c;
+ }
+
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_text, NULL), true);
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
+ }
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
+ }
} else {
- if (ephemeral) {
+ if (opts->ephemeral) {
api_set_error(err, kErrorTypeException, "not yet implemented");
goto error;
}
+ uint16_t decor_flags = 0;
+
+ DecorVirtText *decor_alloc = NULL;
+ if (kv_size(virt_text.data.virt_text)) {
+ decor_alloc = decor_put_vt(virt_text, decor_alloc);
+ if (virt_text.pos == kVPosInline) {
+ decor_flags |= MT_FLAG_DECOR_VIRT_TEXT_INLINE;
+ }
+ }
+ if (kv_size(virt_lines.data.virt_lines)) {
+ decor_alloc = decor_put_vt(virt_lines, decor_alloc);
+ decor_flags |= MT_FLAG_DECOR_VIRT_LINES;
+ }
+
+ uint32_t decor_indexed = DECOR_ID_INVALID;
+ if (sign.flags & kSHIsSign) {
+ decor_indexed = decor_put_sh(sign);
+ if (sign.text.ptr != NULL) {
+ decor_flags |= MT_FLAG_DECOR_SIGNTEXT;
+ }
+ if (sign.number_hl_id || sign.line_hl_id || sign.cursorline_hl_id) {
+ decor_flags |= MT_FLAG_DECOR_SIGNHL;
+ }
+ }
+
+ DecorInline decor = DECOR_INLINE_INIT;
+ if (decor_alloc || decor_indexed != DECOR_ID_INVALID || schar_high(hl.conceal_char)) {
+ if (has_hl) {
+ DecorSignHighlight sh = decor_sh_from_inline(hl);
+ sh.next = decor_indexed;
+ decor_indexed = decor_put_sh(sh);
+ }
+ decor.ext = true;
+ decor.data.ext = (DecorExt){ .sh_idx = decor_indexed, .vt = decor_alloc };
+ } else {
+ decor.data.hl = hl;
+ }
+
+ if (has_hl) {
+ decor_flags |= MT_FLAG_DECOR_HL;
+ }
+
extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
- has_decor ? &decor : NULL, right_gravity, end_right_gravity,
- kExtmarkNoUndo);
+ decor, decor_flags, right_gravity, opts->end_right_gravity,
+ !GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
+ opts->invalidate, err);
+ if (ERROR_SET(err)) {
+ decor_free(decor);
+ return 0;
+ }
}
return (Integer)id;
error:
- clear_virttext(&decor.virt_text);
- xfree(decor.sign_text);
+ clear_virttext(&virt_text.data.virt_text);
+ clear_virtlines(&virt_lines.data.virt_lines);
return 0;
}
@@ -829,12 +862,11 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er
if (!buf) {
return false;
}
- if (!ns_initialized((uint32_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
return false;
- }
+ });
- return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id);
+ return extmark_del_id(buf, (uint32_t)ns_id, (uint32_t)id);
}
uint32_t src2ns(Integer *src_id)
@@ -887,14 +919,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
return 0;
}
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line >= 0 && line < MAXLNUM), "line number", {
return 0;
- }
- if (col_start < 0 || col_start > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
+ });
+ VALIDATE_RANGE((col_start >= 0 && col_start <= MAXCOL), "column", {
return 0;
- }
+ });
+
if (col_end < 0 || col_end > MAXCOL) {
col_end = MAXCOL;
}
@@ -919,13 +950,11 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
end_line++;
}
- Decoration decor = DECORATION_INIT;
- decor.hl_id = hl_id;
+ DecorInline decor = DECOR_INLINE_INIT;
+ decor.data.hl.hl_id = hl_id;
- extmark_set(buf, ns, NULL,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- &decor, true, false, kExtmarkNoUndo);
+ extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
+ decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
return ns_id;
}
@@ -950,10 +979,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
return;
}
- if (line_start < 0 || line_start >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
+ VALIDATE_RANGE((line_start >= 0 && line_start < MAXLNUM), "line number", {
return;
- }
+ });
+
if (line_end < 0 || line_end > MAXLNUM) {
line_end = MAXLNUM;
}
@@ -964,14 +993,14 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Set or change decoration provider for a |namespace|
///
-/// This is a very general purpose interface for having lua callbacks
+/// This is a very general purpose interface for having Lua callbacks
/// being triggered during the redraw code.
///
/// The expected usage is to set |extmarks| for the currently
/// redrawn buffer. |nvim_buf_set_extmark()| can be called to add marks
/// on a per-window or per-lines basis. Use the `ephemeral` key to only
/// use the mark for the current screen redraw (the callback will be called
-/// again for the next redraw ).
+/// again for the next redraw).
///
/// Note: this function should not be called often. Rather, the callbacks
/// themselves can be used to throttle unneeded callbacks. the `on_start`
@@ -983,11 +1012,13 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// for the extmarks set/modified inside the callback anyway.
///
/// Note: doing anything other than setting extmarks is considered experimental.
-/// Doing things like changing options are not expliticly forbidden, but is
+/// Doing things like changing options are not explicitly forbidden, but is
/// likely to have unexpected consequences (such as 100% CPU consumption).
/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
/// for the moment.
///
+/// Note: It is not allowed to remove or update extmarks in 'on_line' callbacks.
+///
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param opts Table of callbacks:
/// - on_start: called first on each screen redraw
@@ -996,7 +1027,8 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// window callbacks)
/// ["buf", bufnr, tick]
/// - on_win: called when starting to redraw a
-/// specific window.
+/// specific window. botline_guess is an approximation
+/// that does not exceed the last line number.
/// ["win", winid, bufnr, topline, botline_guess]
/// - on_line: called for each buffer line being redrawn.
/// (The interaction with fold lines is subject to change)
@@ -1015,7 +1047,7 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
struct {
const char *name;
- Object *source;
+ LuaRef *source;
LuaRef *dest;
} cbs[] = {
{ "on_start", &opts->on_start, &p->redraw_start },
@@ -1029,26 +1061,18 @@ void nvim_set_decoration_provider(Integer ns_id, Dict(set_decoration_provider) *
};
for (size_t i = 0; cbs[i].source && cbs[i].dest && cbs[i].name; i++) {
- Object *v = cbs[i].source;
- if (v->type == kObjectTypeNil) {
+ LuaRef *v = cbs[i].source;
+ if (*v <= 0) {
continue;
}
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", cbs[i].name);
- goto error;
- }
- *(cbs[i].dest) = v->data.luaref;
- v->data.luaref = LUA_NOREF;
+ *(cbs[i].dest) = *v;
+ *v = LUA_NOREF;
}
p->active = true;
p->hl_valid++;
p->hl_cached = false;
- return;
-error:
- decor_provider_clear(p);
}
/// Gets the line and column of an |extmark|.
@@ -1075,75 +1099,40 @@ static bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, in
*col = MAXCOL;
return true;
} else if (id < 0) {
- api_set_error(err, kErrorTypeValidation, "Mark id must be positive");
- return false;
+ VALIDATE_INT(false, "mark id", id, {
+ return false;
+ });
}
- ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
- if (extmark.row >= 0) {
- *row = extmark.row;
- *col = extmark.col;
- return true;
- } else {
- api_set_error(err, kErrorTypeValidation, "No mark with requested id");
+ MTPair extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id);
+
+ VALIDATE_INT((extmark.start.pos.row >= 0), "mark id (not found)", id, {
return false;
- }
+ });
+ *row = extmark.start.pos.row;
+ *col = extmark.start.pos.col;
+ return true;
// Check if it is a position
} else if (obj.type == kObjectTypeArray) {
Array pos = obj.data.array;
- if (pos.size != 2
- || pos.items[0].type != kObjectTypeInteger
- || pos.items[1].type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Position must have 2 integer elements");
+ VALIDATE_EXP((pos.size == 2
+ && pos.items[0].type == kObjectTypeInteger
+ && pos.items[1].type == kObjectTypeInteger),
+ "mark position", "2 Integer items", NULL, {
return false;
- }
+ });
+
Integer pos_row = pos.items[0].data.integer;
Integer pos_col = pos.items[1].data.integer;
- *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
+ *row = (int)(pos_row >= 0 ? pos_row : MAXLNUM);
*col = (colnr_T)(pos_col >= 0 ? pos_col : MAXCOL);
return true;
} else {
- api_set_error(err, kErrorTypeValidation,
- "Position must be a mark id Integer or position Array");
- return false;
- }
-}
-// adapted from sign.c:sign_define_init_text.
-// TODO(lewis6991): Consider merging
-static int init_sign_text(char **sign_text, char *text)
-{
- char *s;
-
- char *endp = text + (int)strlen(text);
-
- // Count cells and check for non-printable chars
- int cells = 0;
- for (s = text; s < endp; s += utfc_ptr2len(s)) {
- if (!vim_isprintc(utf_ptr2char(s))) {
- break;
- }
- cells += utf_ptr2cells(s);
- }
- // Currently must be empty, one or two display cells
- if (s != endp || cells > 2) {
- return FAIL;
- }
- if (cells < 1) {
- return OK;
- }
-
- // Allocate one byte more if we need to pad up
- // with a space.
- size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- *sign_text = xstrnsave(text, len);
-
- if (cells == 1) {
- STRCPY(*sign_text + len - 1, " ");
+ VALIDATE_EXP(false, "mark position", "mark id Integer or 2-item Array", NULL, {
+ return false;
+ });
}
-
- return OK;
}
VirtText parse_virt_text(Array chunks, Error *err, int *width)
@@ -1151,17 +1140,14 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
VirtText virt_text = KV_INITIAL_VALUE;
int w = 0;
for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ VALIDATE_T("chunk", kObjectTypeArray, chunks.items[i].type, {
goto free_exit;
- }
+ });
Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
+ VALIDATE((chunk.size > 0 && chunk.size <= 2 && chunk.items[0].type == kObjectTypeString),
+ "%s", "Invalid chunk: expected Array with 1 or 2 Strings", {
goto free_exit;
- }
+ });
String str = chunk.items[0].data.string;
@@ -1176,8 +1162,7 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
goto free_exit;
}
if (j < arr.size - 1) {
- kv_push(virt_text, ((VirtTextChunk){ .text = NULL,
- .hl_id = hl_id }));
+ kv_push(virt_text, ((VirtTextChunk){ .text = NULL, .hl_id = hl_id }));
}
}
} else {
@@ -1194,10 +1179,23 @@ VirtText parse_virt_text(Array chunks, Error *err, int *width)
kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
}
- *width = w;
+ if (width != NULL) {
+ *width = w;
+ }
return virt_text;
free_exit:
clear_virttext(&virt_text);
return virt_text;
}
+
+String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return NULL_STRING;
+ }
+
+ return mt_inspect(buf->b_marktree, keys, dot);
+}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index 0a627a889c..124feaabfb 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -1,17 +1,17 @@
-#ifndef NVIM_API_EXTMARK_H
-#define NVIM_API_EXTMARK_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/decoration.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/decoration_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
-EXTERN Map(String, handle_T) namespace_ids INIT(= MAP_INIT);
-EXTERN handle_T next_namespace_id INIT(= 1);
+EXTERN Map(String, int) namespace_ids INIT( = MAP_INIT);
+EXTERN handle_T next_namespace_id INIT( = 1);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.h.generated.h"
#endif
-#endif // NVIM_API_EXTMARK_H
diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua
deleted file mode 100644
index 30dcef6127..0000000000
--- a/src/nvim/api/keysets.lua
+++ /dev/null
@@ -1,229 +0,0 @@
-return {
- { 'context', {
- "types";
- }};
- { 'set_decoration_provider', {
- "on_start";
- "on_buf";
- "on_win";
- "on_line";
- "on_end";
- "_on_hl_def";
- "_on_spell_nav";
- }};
- { 'set_extmark', {
- "id";
- "end_line";
- "end_row";
- "end_col";
- "hl_group";
- "virt_text";
- "virt_text_pos";
- "virt_text_win_col";
- "virt_text_hide";
- "hl_eol";
- "hl_mode";
- "ephemeral";
- "priority";
- "right_gravity";
- "end_right_gravity";
- "virt_lines";
- "virt_lines_above";
- "virt_lines_leftcol";
- "strict";
- "sign_text";
- "sign_hl_group";
- "number_hl_group";
- "line_hl_group";
- "cursorline_hl_group";
- "conceal";
- "spell";
- "ui_watched";
- }};
- { 'keymap', {
- "noremap";
- "nowait";
- "silent";
- "script";
- "expr";
- "unique";
- "callback";
- "desc";
- "replace_keycodes";
- }};
- { 'get_commands', {
- "builtin";
- }};
- { 'user_command', {
- "addr";
- "bang";
- "bar";
- "complete";
- "count";
- "desc";
- "force";
- "keepscript";
- "nargs";
- "preview";
- "range";
- "register";
- }};
- { 'float_config', {
- "row";
- "col";
- "width";
- "height";
- "anchor";
- "relative";
- "win";
- "bufpos";
- "external";
- "focusable";
- "zindex";
- "border";
- "title";
- "title_pos";
- "style";
- "noautocmd";
- }};
- { 'runtime', {
- "is_lua";
- "do_source";
- }};
- { 'eval_statusline', {
- "winid";
- "maxwidth";
- "fillchar";
- "highlights";
- "use_winbar";
- "use_tabline";
- }};
- { 'option', {
- "scope";
- "win";
- "buf";
- }};
- { 'highlight', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- "default";
- "cterm";
- "foreground"; "fg";
- "background"; "bg";
- "ctermfg";
- "ctermbg";
- "special"; "sp";
- "link";
- "global_link";
- "fallback";
- "blend";
- "fg_indexed";
- "bg_indexed";
- }};
- { 'highlight_cterm', {
- "bold";
- "standout";
- "strikethrough";
- "underline";
- "undercurl";
- "underdouble";
- "underdotted";
- "underdashed";
- "italic";
- "reverse";
- "altfont";
- "nocombine";
- }};
- -- Autocmds
- { 'clear_autocmds', {
- "buffer";
- "event";
- "group";
- "pattern";
- }};
- { 'create_autocmd', {
- "buffer";
- "callback";
- "command";
- "desc";
- "group";
- "nested";
- "once";
- "pattern";
- }};
- { 'exec_autocmds', {
- "buffer";
- "group";
- "modeline";
- "pattern";
- "data";
- }};
- { 'get_autocmds', {
- "event";
- "group";
- "pattern";
- "buffer";
- }};
- { 'create_augroup', {
- "clear";
- }};
- { 'cmd', {
- "cmd";
- "range";
- "count";
- "reg";
- "bang";
- "args";
- "magic";
- "mods";
- "nargs";
- "addr";
- "nextcmd";
- }};
- { 'cmd_magic', {
- "file";
- "bar";
- }};
- { 'cmd_mods', {
- "silent";
- "emsg_silent";
- "unsilent";
- "filter";
- "sandbox";
- "noautocmd";
- "browse";
- "confirm";
- "hide";
- "horizontal";
- "keepalt";
- "keepjumps";
- "keepmarks";
- "keeppatterns";
- "lockmarks";
- "noswapfile";
- "tab";
- "verbose";
- "vertical";
- "split";
- }};
- { 'cmd_mods_filter', {
- "pattern";
- "force";
- }};
- { 'cmd_opts', {
- "output";
- }};
- { 'echo_opts', {
- "verbose";
- }};
-}
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
new file mode 100644
index 0000000000..e59eda5686
--- /dev/null
+++ b/src/nvim/api/keysets_defs.h
@@ -0,0 +1,315 @@
+#pragma once
+
+#include "nvim/api/private/defs.h"
+
+typedef struct {
+ OptionalKeys is_set__context_;
+ Array types;
+} Dict(context);
+
+typedef struct {
+ OptionalKeys is_set__set_decoration_provider_;
+ LuaRef on_start;
+ LuaRef on_buf;
+ LuaRef on_win;
+ LuaRef on_line;
+ LuaRef on_end;
+ LuaRef _on_hl_def;
+ LuaRef _on_spell_nav;
+} Dict(set_decoration_provider);
+
+typedef struct {
+ OptionalKeys is_set__set_extmark_;
+ Integer id;
+ Integer end_line;
+ Integer end_row;
+ Integer end_col;
+ Object hl_group;
+ Array virt_text;
+ String virt_text_pos;
+ Integer virt_text_win_col;
+ Boolean virt_text_hide;
+ Boolean hl_eol;
+ String hl_mode;
+ Boolean invalidate;
+ Boolean ephemeral;
+ Integer priority;
+ Boolean right_gravity;
+ Boolean end_right_gravity;
+ Array virt_lines;
+ Boolean virt_lines_above;
+ Boolean virt_lines_leftcol;
+ Boolean strict;
+ String sign_text;
+ Object sign_hl_group;
+ Object number_hl_group;
+ Object line_hl_group;
+ Object cursorline_hl_group;
+ String conceal;
+ Boolean spell;
+ Boolean ui_watched;
+ Boolean undo_restore;
+} Dict(set_extmark);
+
+typedef struct {
+ OptionalKeys is_set__get_extmarks_;
+ Integer limit;
+ Boolean details;
+ Boolean hl_name;
+ Boolean overlap;
+ String type;
+} Dict(get_extmarks);
+
+typedef struct {
+ OptionalKeys is_set__keymap_;
+ Boolean noremap;
+ Boolean nowait;
+ Boolean silent;
+ Boolean script;
+ Boolean expr;
+ Boolean unique;
+ LuaRef callback;
+ String desc;
+ Boolean replace_keycodes;
+} Dict(keymap);
+
+typedef struct {
+ Boolean builtin;
+} Dict(get_commands);
+
+typedef struct {
+ OptionalKeys is_set__user_command_;
+ Object addr;
+ Boolean bang;
+ Boolean bar;
+ Object complete;
+ Object count;
+ Object desc;
+ Boolean force;
+ Boolean keepscript;
+ Object nargs;
+ Object preview;
+ Object range;
+ Boolean register_;
+} Dict(user_command);
+
+typedef struct {
+ OptionalKeys is_set__float_config_;
+ Float row;
+ Float col;
+ Integer width;
+ Integer height;
+ String anchor;
+ String relative;
+ Window win;
+ Array bufpos;
+ Boolean external;
+ Boolean focusable;
+ Integer zindex;
+ Object border;
+ Object title;
+ String title_pos;
+ Object footer;
+ String footer_pos;
+ String style;
+ Boolean noautocmd;
+ Boolean fixed;
+ Boolean hide;
+} Dict(float_config);
+
+typedef struct {
+ Boolean is_lua;
+ Boolean do_source;
+} Dict(runtime);
+
+typedef struct {
+ OptionalKeys is_set__eval_statusline_;
+ Window winid;
+ Integer maxwidth;
+ String fillchar;
+ Boolean highlights;
+ Boolean use_winbar;
+ Boolean use_tabline;
+ Integer use_statuscol_lnum;
+} Dict(eval_statusline);
+
+typedef struct {
+ OptionalKeys is_set__option_;
+ String scope;
+ Window win;
+ Buffer buf;
+ String filetype;
+} Dict(option);
+
+typedef struct {
+ OptionalKeys is_set__highlight_;
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+ Boolean default_;
+ Object cterm;
+ Object foreground;
+ Object fg;
+ Object background;
+ Object bg;
+ Object ctermfg;
+ Object ctermbg;
+ Object special;
+ Object sp;
+ Object link;
+ Object global_link;
+ Boolean fallback;
+ Integer blend;
+ Boolean fg_indexed;
+ Boolean bg_indexed;
+ Boolean force;
+} Dict(highlight);
+
+typedef struct {
+ Boolean bold;
+ Boolean standout;
+ Boolean strikethrough;
+ Boolean underline;
+ Boolean undercurl;
+ Boolean underdouble;
+ Boolean underdotted;
+ Boolean underdashed;
+ Boolean italic;
+ Boolean reverse;
+ Boolean altfont;
+ Boolean nocombine;
+} Dict(highlight_cterm);
+
+typedef struct {
+ OptionalKeys is_set__get_highlight_;
+ Integer id;
+ String name;
+ Boolean link;
+ Boolean create;
+} Dict(get_highlight);
+
+typedef struct {
+ OptionalKeys is_set__get_ns_;
+ Window winid;
+} Dict(get_ns);
+
+typedef struct {
+ OptionalKeys is_set__win_text_height_;
+ Integer start_row;
+ Integer end_row;
+ Integer start_vcol;
+ Integer end_vcol;
+} Dict(win_text_height);
+
+typedef struct {
+ OptionalKeys is_set__clear_autocmds_;
+ Buffer buffer;
+ Object event;
+ Object group;
+ Object pattern;
+} Dict(clear_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__create_autocmd_;
+ Buffer buffer;
+ Object callback;
+ String command;
+ String desc;
+ Object group;
+ Boolean nested;
+ Boolean once;
+ Object pattern;
+} Dict(create_autocmd);
+
+typedef struct {
+ OptionalKeys is_set__exec_autocmds_;
+ Buffer buffer;
+ Object group;
+ Boolean modeline;
+ Object pattern;
+ Object data;
+} Dict(exec_autocmds);
+
+typedef struct {
+ OptionalKeys is_set__get_autocmds_;
+ Object event;
+ Object group;
+ Object pattern;
+ Object buffer;
+} Dict(get_autocmds);
+
+typedef struct {
+ Object clear;
+} Dict(create_augroup);
+
+typedef struct {
+ OptionalKeys is_set__cmd_;
+ String cmd;
+ Array range;
+ Integer count;
+ String reg;
+ Boolean bang;
+ Array args;
+ Dictionary magic;
+ Dictionary mods;
+ Object nargs;
+ Object addr;
+ Object nextcmd;
+} Dict(cmd);
+
+typedef struct {
+ OptionalKeys is_set__cmd_magic_;
+ Boolean file;
+ Boolean bar;
+} Dict(cmd_magic);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_;
+ Boolean silent;
+ Boolean emsg_silent;
+ Boolean unsilent;
+ Dictionary filter;
+ Boolean sandbox;
+ Boolean noautocmd;
+ Boolean browse;
+ Boolean confirm;
+ Boolean hide;
+ Boolean horizontal;
+ Boolean keepalt;
+ Boolean keepjumps;
+ Boolean keepmarks;
+ Boolean keeppatterns;
+ Boolean lockmarks;
+ Boolean noswapfile;
+ Integer tab;
+ Integer verbose;
+ Boolean vertical;
+ String split;
+} Dict(cmd_mods);
+
+typedef struct {
+ OptionalKeys is_set__cmd_mods_filter_;
+ String pattern;
+ Boolean force;
+} Dict(cmd_mods_filter);
+
+typedef struct {
+ Boolean output;
+} Dict(cmd_opts);
+
+typedef struct {
+ Boolean verbose;
+} Dict(echo_opts);
+
+typedef struct {
+ Boolean output;
+} Dict(exec_opts);
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index bfcb99754f..c012a69c7b 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -1,80 +1,135 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-#include <inttypes.h>
-#include <limits.h>
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/options.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/eval/window.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/vim.h"
+#include "nvim/option_vars.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.c.generated.h"
#endif
-static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_type, void **from,
+static int validate_option_value_args(Dict(option) *opts, char *name, int *scope,
+ OptReqScope *req_scope, void **from, char **filetype,
Error *err)
{
- if (opts->scope.type == kObjectTypeString) {
- if (!strcmp(opts->scope.data.string.data, "local")) {
+#define HAS_KEY_X(d, v) HAS_KEY(d, option, v)
+ if (HAS_KEY_X(opts, scope)) {
+ if (!strcmp(opts->scope.data, "local")) {
*scope = OPT_LOCAL;
- } else if (!strcmp(opts->scope.data.string.data, "global")) {
+ } else if (!strcmp(opts->scope.data, "global")) {
*scope = OPT_GLOBAL;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'");
- return FAIL;
+ VALIDATE_EXP(false, "scope", "'local' or 'global'", NULL, {
+ return FAIL;
+ });
}
- } else if (HAS_KEY(opts->scope)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: scope");
- return FAIL;
}
- *opt_type = SREQ_GLOBAL;
+ *req_scope = kOptReqGlobal;
+
+ if (filetype != NULL && HAS_KEY_X(opts, filetype)) {
+ *filetype = opts->filetype.data;
+ }
- if (opts->win.type == kObjectTypeInteger) {
- *opt_type = SREQ_WIN;
- *from = find_window_by_handle((int)opts->win.data.integer, err);
+ if (HAS_KEY_X(opts, win)) {
+ *req_scope = kOptReqWin;
+ *from = find_window_by_handle(opts->win, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->win)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: win");
- return FAIL;
}
- if (opts->buf.type == kObjectTypeInteger) {
+ if (HAS_KEY_X(opts, buf)) {
*scope = OPT_LOCAL;
- *opt_type = SREQ_BUF;
- *from = find_buffer_by_handle((int)opts->buf.data.integer, err);
+ *req_scope = kOptReqBuf;
+ *from = find_buffer_by_handle(opts->buf, err);
if (ERROR_SET(err)) {
return FAIL;
}
- } else if (HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: buf");
- return FAIL;
}
- if (HAS_KEY(opts->scope) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "scope and buf cannot be used together");
+ VALIDATE((!HAS_KEY_X(opts, filetype)
+ || !(HAS_KEY_X(opts, buf) || HAS_KEY_X(opts, scope) || HAS_KEY_X(opts, win))),
+ "%s", "cannot use 'filetype' with 'scope', 'buf' or 'win'", {
return FAIL;
- }
+ });
+
+ VALIDATE((!HAS_KEY_X(opts, scope) || !HAS_KEY_X(opts, buf)), "%s",
+ "cannot use both 'scope' and 'buf'", {
+ return FAIL;
+ });
- if (HAS_KEY(opts->win) && HAS_KEY(opts->buf)) {
- api_set_error(err, kErrorTypeValidation, "buf and win cannot be used together");
+ VALIDATE((!HAS_KEY_X(opts, win) || !HAS_KEY_X(opts, buf)),
+ "%s", "cannot use both 'buf' and 'win'", {
return FAIL;
+ });
+
+ int flags = get_option_attrs(name);
+ if (flags == 0) {
+ // hidden or unknown option
+ api_set_error(err, kErrorTypeValidation, "Unknown option '%s'", name);
+ } else if (*req_scope == kOptReqBuf || *req_scope == kOptReqWin) {
+ // if 'buf' or 'win' is passed, make sure the option supports it
+ int req_flags = *req_scope == kOptReqBuf ? SOPT_BUF : SOPT_WIN;
+ if (!(flags & req_flags)) {
+ char *tgt = *req_scope & kOptReqBuf ? "buf" : "win";
+ char *global = flags & SOPT_GLOBAL ? "global " : "";
+ char *req = flags & SOPT_BUF ? "buffer-local "
+ : flags & SOPT_WIN ? "window-local " : "";
+
+ api_set_error(err, kErrorTypeValidation, "'%s' cannot be passed for %s%soption '%s'",
+ tgt, global, req, name);
+ }
}
return OK;
+#undef HAS_KEY_X
+}
+
+/// Create a dummy buffer and run the FileType autocmd on it.
+static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err)
+{
+ if (filetype == NULL) {
+ return NULL;
+ }
+
+ // Allocate a buffer without putting it in the buffer list.
+ buf_T *ftbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
+ if (ftbuf == NULL) {
+ api_set_error(err, kErrorTypeException, "Could not create internal buffer");
+ return NULL;
+ }
+
+ // Set curwin/curbuf to buf and save a few things.
+ aucmd_prepbuf(aco, ftbuf);
+
+ TRY_WRAP(err, {
+ set_option_value("bufhidden", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value("swapfile", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("modeline", BOOLEAN_OPTVAL(false), OPT_LOCAL); // 'nomodeline'
+
+ ftbuf->b_p_ft = xstrdup(filetype);
+ do_filetype_autocmd(ftbuf, false);
+ });
+
+ return ftbuf;
}
/// Gets the value of an option. The behavior of this function matches that of
@@ -89,53 +144,61 @@ static int validate_option_value_args(Dict(option) *opts, int *scope, int *opt_t
/// - win: |window-ID|. Used for getting window local options.
/// - buf: Buffer number. Used for getting buffer local options.
/// Implies {scope} is "local".
+/// - filetype: |filetype|. Used to get the default option for a
+/// specific filetype. Cannot be used with any other option.
+/// Note: this will trigger |ftplugin| and all |FileType|
+/// autocommands for the corresponding filetype.
/// @param[out] err Error details, if any
/// @return Option value
Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
FUNC_API_SINCE(9)
{
Object rv = OBJECT_INIT;
+ OptVal value = NIL_OPTVAL;
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *from = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &from, err)) {
- return rv;
+ char *filetype = NULL;
+
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, &filetype, err)) {
+ goto err;
}
- long numval = 0;
- char *stringval = NULL;
- getoption_T result = access_option_value_for(name.data, &numval, &stringval, scope, opt_type,
- from, true, err);
+ aco_save_T aco;
+
+ buf_T *ftbuf = do_ft_buf(filetype, &aco, err);
if (ERROR_SET(err)) {
- return rv;
+ goto err;
}
- switch (result) {
- case gov_string:
- rv = STRING_OBJ(cstr_as_string(stringval));
- break;
- case gov_number:
- rv = INTEGER_OBJ(numval);
- break;
- case gov_bool:
- switch (numval) {
- case 0:
- case 1:
- rv = BOOLEAN_OBJ(numval);
- break;
- default:
- // Boolean options that return something other than 0 or 1 should return nil. Currently this
- // only applies to 'autoread' which uses -1 as a local value to indicate "unset"
- rv = NIL;
- break;
- }
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data);
- return rv;
+ if (ftbuf != NULL) {
+ assert(!from);
+ from = ftbuf;
+ }
+
+ bool hidden;
+ value = get_option_value_for(name.data, NULL, scope, &hidden, req_scope, from, err);
+
+ if (ftbuf != NULL) {
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+
+ assert(curbuf != ftbuf); // safety check
+ wipe_buffer(ftbuf, false);
+ }
+
+ if (ERROR_SET(err)) {
+ goto err;
}
+ VALIDATE_S(!hidden && value.type != kOptValTypeNil, "option", name.data, {
+ goto err;
+ });
+
+ return optval_as_object(value);
+err:
+ optval_free(value);
return rv;
}
@@ -153,13 +216,14 @@ Object nvim_get_option_value(String name, Dict(option) *opts, Error *err)
/// - win: |window-ID|. Used for setting window local option.
/// - buf: Buffer number. Used for setting buffer local option.
/// @param[out] err Error details, if any
-void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err)
+void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(option) *opts,
+ Error *err)
FUNC_API_SINCE(9)
{
int scope = 0;
- int opt_type = SREQ_GLOBAL;
+ OptReqScope req_scope = kOptReqGlobal;
void *to = NULL;
- if (!validate_option_value_args(opts, &scope, &opt_type, &to, err)) {
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &to, NULL, err)) {
return;
}
@@ -169,41 +233,35 @@ void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error
// - option is global or local to window (global-local)
//
// Then force scope to local since we don't want to change the global option
- if (opt_type == SREQ_WIN && scope == 0) {
- int flags = get_option_value_strict(name.data, NULL, NULL, opt_type, to);
+ if (req_scope == kOptReqWin && scope == 0) {
+ int flags = get_option_attrs(name.data);
if (flags & SOPT_GLOBAL) {
scope = OPT_LOCAL;
}
}
- long numval = 0;
- char *stringval = NULL;
+ bool error = false;
+ OptVal optval = object_as_optval(value, &error);
- switch (value.type) {
- case kObjectTypeInteger:
- numval = value.data.integer;
- break;
- case kObjectTypeBoolean:
- numval = value.data.boolean ? 1 : 0;
- break;
- case kObjectTypeString:
- stringval = value.data.string.data;
- break;
- case kObjectTypeNil:
- scope |= OPT_CLEAR;
- break;
- default:
- api_set_error(err, kErrorTypeValidation, "invalid value for option");
+ // Handle invalid option value type.
+ // Don't use `name` in the error message here, because `name` can be any String.
+ // No need to check if value type actually matches the types for the option, as set_option_value()
+ // already handles that.
+ VALIDATE_EXP(!error, "value", "valid option type", api_typename(value.type), {
return;
- }
+ });
- access_option_value_for(name.data, &numval, &stringval, scope, opt_type, to, false, err);
+ WITH_SCRIPT_CONTEXT(channel_id, {
+ set_option_value_for(name.data, optval, scope, req_scope, to, err);
+ });
}
/// Gets the option information for all options.
///
/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info()|.
+/// dictionaries as detailed at |nvim_get_option_info2()|.
+///
+/// @see |nvim_get_commands()|
///
/// @return dictionary of all options
Dictionary nvim_get_all_options_info(Error *err)
@@ -212,7 +270,7 @@ Dictionary nvim_get_all_options_info(Error *err)
return get_all_vimoptions();
}
-/// Gets the option information for one option
+/// Gets the option information for one option from arbitrary buffer or window
///
/// Resulting dictionary has keys:
/// - name: Name of the option (like 'filetype')
@@ -231,324 +289,289 @@ Dictionary nvim_get_all_options_info(Error *err)
/// - commalist: List of comma separated values
/// - flaglist: List of single char flags
///
+/// When {scope} is not provided, the last set information applies to the local
+/// value in the current buffer or window if it is available, otherwise the
+/// global value information is returned. This behavior can be disabled by
+/// explicitly specifying {scope} in the {opts} table.
///
-/// @param name Option name
+/// @param name Option name
+/// @param opts Optional parameters
+/// - scope: One of "global" or "local". Analogous to
+/// |:setglobal| and |:setlocal|, respectively.
+/// - win: |window-ID|. Used for getting window local options.
+/// - buf: Buffer number. Used for getting buffer local options.
+/// Implies {scope} is "local".
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info(String name, Error *err)
- FUNC_API_SINCE(7)
+Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Error *err)
+ FUNC_API_SINCE(11)
{
- return get_vimoption(name, err);
-}
-/// Sets the global value of an option.
-///
-/// @param channel_id
-/// @param name Option name
-/// @param value New option value
-/// @param[out] err Error details, if any
-void nvim_set_option(uint64_t channel_id, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- set_option_to(channel_id, NULL, SREQ_GLOBAL, name, value, err);
-}
+ int scope = 0;
+ OptReqScope req_scope = kOptReqGlobal;
+ void *from = NULL;
+ if (!validate_option_value_args(opts, name.data, &scope, &req_scope, &from, NULL, err)) {
+ return (Dictionary)ARRAY_DICT_INIT;
+ }
-/// Gets the global value of an option.
-///
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value (global)
-Object nvim_get_option(String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
-{
- return get_option_from(NULL, SREQ_GLOBAL, name, err);
+ buf_T *buf = (req_scope == kOptReqBuf) ? (buf_T *)from : curbuf;
+ win_T *win = (req_scope == kOptReqWin) ? (win_T *)from : curwin;
+
+ return get_vimoption(name, scope, buf, win, err);
}
-/// Gets a buffer option value
+/// Switch current context to get/set option value for window/buffer.
+///
+/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_buf_get_option(Buffer buffer, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @return true if context was switched, false otherwise.
+static bool switch_option_context(void *const ctx, OptReqScope req_scope, void *const from,
+ Error *err)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
+ switch (req_scope) {
+ case kOptReqWin: {
+ win_T *const win = (win_T *)from;
+ switchwin_T *const switchwin = (switchwin_T *)ctx;
+
+ if (win == curwin) {
+ return false;
+ }
- if (!buf) {
- return (Object)OBJECT_INIT;
+ if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
+ == FAIL) {
+ restore_win_noblock(switchwin, true);
+
+ if (try_end(err)) {
+ return false;
+ }
+ api_set_error(err, kErrorTypeException, "Problem while switching windows");
+ return false;
+ }
+ return true;
}
+ case kOptReqBuf: {
+ buf_T *const buf = (buf_T *)from;
+ aco_save_T *const aco = (aco_save_T *)ctx;
- return get_option_from(buf, SREQ_BUF, name, err);
+ if (buf == curbuf) {
+ return false;
+ }
+ aucmd_prepbuf(aco, buf);
+ return true;
+ }
+ case kOptReqGlobal:
+ return false;
+ }
+ UNREACHABLE;
}
-/// Sets a buffer option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_buf_set_option(uint64_t channel_id, Buffer buffer, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
+/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
+/// params.
+static void restore_option_context(void *const ctx, OptReqScope req_scope)
{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
+ switch (req_scope) {
+ case kOptReqWin:
+ restore_win_noblock((switchwin_T *)ctx, true);
+ break;
+ case kOptReqBuf:
+ aucmd_restbuf((aco_save_T *)ctx);
+ break;
+ case kOptReqGlobal:
+ break;
}
-
- set_option_to(channel_id, buf, SREQ_BUF, name, value, err);
}
-/// Gets a window option value
+/// Get attributes for an option.
///
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param[out] err Error details, if any
-/// @return Option value
-Object nvim_win_get_option(Window window, String name, Arena *arena, Error *err)
- FUNC_API_SINCE(1)
+/// @param name Option name.
+///
+/// @return Option attributes.
+/// 0 for hidden or unknown option.
+/// See SOPT_* in option_defs.h for other flags.
+int get_option_attrs(char *name)
{
- win_T *win = find_window_by_handle(window, err);
+ int opt_idx = findoption(name);
- if (!win) {
- return (Object)OBJECT_INIT;
+ if (opt_idx < 0) {
+ return 0;
}
- return get_option_from(win, SREQ_WIN, name, err);
-}
+ vimoption_T *opt = get_option(opt_idx);
-/// Sets a window option value. Passing `nil` as value deletes the option (only
-/// works if there's a global fallback)
-///
-/// @param channel_id
-/// @param window Window handle, or 0 for current window
-/// @param name Option name
-/// @param value Option value
-/// @param[out] err Error details, if any
-void nvim_win_set_option(uint64_t channel_id, Window window, String name, Object value, Error *err)
- FUNC_API_SINCE(1)
-{
- win_T *win = find_window_by_handle(window, err);
+ if (is_tty_option(opt->fullname)) {
+ return SOPT_STRING | SOPT_GLOBAL;
+ }
- if (!win) {
- return;
+ // Hidden option
+ if (opt->var == NULL) {
+ return 0;
}
- set_option_to(channel_id, win, SREQ_WIN, name, value, err);
-}
+ int attrs = 0;
-/// Gets the value of a global or local (buffer, window) option.
-///
-/// @param from If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-/// @return the option value
-static Object get_option_from(void *from, int type, String name, Error *err)
-{
- Object rv = OBJECT_INIT;
+ if (opt->flags & P_BOOL) {
+ attrs |= SOPT_BOOL;
+ } else if (opt->flags & P_NUM) {
+ attrs |= SOPT_NUM;
+ } else if (opt->flags & P_STRING) {
+ attrs |= SOPT_STRING;
+ }
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return rv;
- }
-
- // Return values
- int64_t numval;
- char *stringval = NULL;
- int flags = get_option_value_strict(name.data, &numval, &stringval, type, from);
-
- if (!flags) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name: '%s'",
- name.data);
- return rv;
- }
-
- if (flags & SOPT_BOOL) {
- rv.type = kObjectTypeBoolean;
- rv.data.boolean = numval ? true : false;
- } else if (flags & SOPT_NUM) {
- rv.type = kObjectTypeInteger;
- rv.data.integer = numval;
- } else if (flags & SOPT_STRING) {
- if (stringval) {
- rv.type = kObjectTypeString;
- rv.data.string.data = stringval;
- rv.data.string.size = strlen(stringval);
- } else {
- api_set_error(err, kErrorTypeException,
- "Failed to get value for option '%s'",
- name.data);
- }
- } else {
- api_set_error(err,
- kErrorTypeException,
- "Unknown type for option '%s'",
- name.data);
+ if (opt->indir == PV_NONE || (opt->indir & PV_BOTH)) {
+ attrs |= SOPT_GLOBAL;
+ }
+ if (opt->indir & PV_WIN) {
+ attrs |= SOPT_WIN;
+ } else if (opt->indir & PV_BUF) {
+ attrs |= SOPT_BUF;
}
- return rv;
+ return attrs;
}
-/// Sets the value of a global or local (buffer, window) option.
+/// Check if option has a value in the requested scope.
///
-/// @param to If `type` is `SREQ_WIN` or `SREQ_BUF`, this must be a pointer
-/// to the window or buffer.
-/// @param type One of `SREQ_GLOBAL`, `SREQ_WIN` or `SREQ_BUF`
-/// @param name The option name
-/// @param[out] err Details of an error that may have occurred
-void set_option_to(uint64_t channel_id, void *to, int type, String name, Object value, Error *err)
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+///
+/// @return true if option has a value in the requested scope, false otherwise.
+static bool option_has_scope(char *name, OptReqScope req_scope)
{
- if (name.size == 0) {
- api_set_error(err, kErrorTypeValidation, "Empty option name");
- return;
+ int opt_idx = findoption(name);
+
+ if (opt_idx < 0) {
+ return false;
}
- int flags = get_option_value_strict(name.data, NULL, NULL, type, to);
+ vimoption_T *opt = get_option(opt_idx);
- if (flags == 0) {
- api_set_error(err, kErrorTypeValidation, "Invalid option name '%s'",
- name.data);
- return;
+ // Hidden option.
+ if (opt->var == NULL) {
+ return false;
+ }
+ // TTY option.
+ if (is_tty_option(opt->fullname)) {
+ return req_scope == kOptReqGlobal;
}
- if (value.type == kObjectTypeNil) {
- if (type == SREQ_GLOBAL) {
- api_set_error(err, kErrorTypeException, "Cannot unset option '%s'",
- name.data);
- return;
- } else if (!(flags & SOPT_GLOBAL)) {
- api_set_error(err,
- kErrorTypeException,
- "Cannot unset option '%s' "
- "because it doesn't have a global value",
- name.data);
- return;
- } else {
- unset_global_local_option(name.data, to);
- return;
- }
+ switch (req_scope) {
+ case kOptReqGlobal:
+ return opt->var != VAR_WIN;
+ case kOptReqBuf:
+ return opt->indir & PV_BUF;
+ case kOptReqWin:
+ return opt->indir & PV_WIN;
}
+ UNREACHABLE;
+}
- long numval = 0;
- char *stringval = NULL;
+/// Get the option value in the requested scope.
+///
+/// @param name Option name.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Pointer to buffer or window for local option value.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value in the requested scope. Returns a Nil option value if option is not found,
+/// hidden or if it isn't present in the requested scope. (i.e. has no global, window-local or
+/// buffer-local value depending on opt_scope).
+OptVal get_option_value_strict(char *name, OptReqScope req_scope, void *from, Error *err)
+{
+ OptVal retv = NIL_OPTVAL;
- if (flags & SOPT_BOOL) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(err,
- kErrorTypeValidation,
- "Option '%s' requires a Boolean value",
- name.data);
- return;
- }
+ if (!option_has_scope(name, req_scope)) {
+ return retv;
+ }
+ if (get_tty_option(name, &retv.data.string.data)) {
+ retv.type = kOptValTypeString;
+ return retv;
+ }
- numval = value.data.boolean;
- } else if (flags & SOPT_NUM) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires an integer value",
- name.data);
- return;
- }
+ int opt_idx = findoption(name);
+ assert(opt_idx != 0); // option_has_scope() already verifies if option name is valid.
- if (value.data.integer > INT_MAX || value.data.integer < INT_MIN) {
- api_set_error(err, kErrorTypeValidation,
- "Value for option '%s' is out of range",
- name.data);
- return;
- }
+ vimoption_T *opt = get_option(opt_idx);
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return retv;
+ }
- numval = (int)value.data.integer;
- } else {
- if (value.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Option '%s' requires a string value",
- name.data);
- return;
- }
+ char *varp = get_varp_scope(opt, req_scope == kOptReqGlobal ? OPT_GLOBAL : OPT_LOCAL);
+ retv = optval_from_varp(opt_idx, varp);
- stringval = value.data.string.data;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
- // For global-win-local options -> setlocal
- // For win-local options -> setglobal and setlocal (opt_flags == 0)
- const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) ? 0 :
- (type == SREQ_GLOBAL) ? OPT_GLOBAL : OPT_LOCAL;
-
- WITH_SCRIPT_CONTEXT(channel_id, {
- access_option_value_for(name.data, &numval, &stringval, opt_flags, type, to, false, err);
- });
+ return retv;
}
-static getoption_T access_option_value(char *key, long *numval, char **stringval, int opt_flags,
- bool get, Error *err)
+/// Get option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+///
+/// @return Option value. Must be freed by caller.
+OptVal get_option_value_for(const char *const name, uint32_t *flagsp, int scope, bool *hidden,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- if (get) {
- return get_option_value(key, numval, stringval, NULL, opt_flags);
- } else {
- char *errmsg;
- if ((errmsg = set_option_value(key, *numval, *stringval, opt_flags))) {
- if (try_end(err)) {
- return 0;
- }
+ switchwin_T switchwin;
+ aco_save_T aco;
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
- api_set_error(err, kErrorTypeException, "%s", errmsg);
- }
- return 0;
+ bool switched = switch_option_context(ctx, req_scope, from, err);
+ if (ERROR_SET(err)) {
+ return NIL_OPTVAL;
+ }
+
+ OptVal retv = get_option_value(name, flagsp, scope, hidden);
+
+ if (switched) {
+ restore_option_context(ctx, req_scope);
}
+
+ return retv;
}
-static getoption_T access_option_value_for(char *key, long *numval, char **stringval, int opt_flags,
- int opt_type, void *from, bool get, Error *err)
+/// Set option value for buffer / window.
+///
+/// @param[in] name Option name.
+/// @param[in] value Option value.
+/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
+/// @param req_scope Requested option scope. See OptReqScope in option.h.
+/// @param[in] from Target buffer/window.
+/// @param[out] err Error message, if any.
+void set_option_value_for(const char *const name, OptVal value, const int opt_flags,
+ const OptReqScope req_scope, void *const from, Error *err)
{
- bool need_switch = false;
switchwin_T switchwin;
aco_save_T aco;
- getoption_T result = 0;
-
- try_start();
- switch (opt_type) {
- case SREQ_WIN:
- need_switch = (win_T *)from != curwin;
- if (need_switch) {
- if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true)
- == FAIL) {
- restore_win_noblock(&switchwin, true);
- if (try_end(err)) {
- return result;
- }
- api_set_error(err, kErrorTypeException, "Problem while switching windows");
- return result;
- }
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- restore_win_noblock(&switchwin, true);
- }
- break;
- case SREQ_BUF:
- need_switch = (buf_T *)from != curbuf;
- if (need_switch) {
- aucmd_prepbuf(&aco, (buf_T *)from);
- }
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- if (need_switch) {
- aucmd_restbuf(&aco);
- }
- break;
- case SREQ_GLOBAL:
- result = access_option_value(key, numval, stringval, opt_flags, get, err);
- break;
- }
+ void *ctx = req_scope == kOptReqWin ? (void *)&switchwin
+ : (req_scope == kOptReqBuf ? (void *)&aco : NULL);
+ bool switched = switch_option_context(ctx, req_scope, from, err);
if (ERROR_SET(err)) {
- return result;
+ return;
}
- try_end(err);
+ const char *const errmsg = set_option_value(name, value, opt_flags);
+ if (errmsg) {
+ api_set_error(err, kErrorTypeException, "%s", errmsg);
+ }
- return result;
+ if (switched) {
+ restore_option_context(ctx, req_scope);
+ }
}
diff --git a/src/nvim/api/options.h b/src/nvim/api/options.h
index efbfec3a6c..c16c6088b3 100644
--- a/src/nvim/api/options.h
+++ b/src/nvim/api/options.h
@@ -1,9 +1,11 @@
-#ifndef NVIM_API_OPTIONS_H
-#define NVIM_API_OPTIONS_H
+#pragma once
+
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/options.h.generated.h"
#endif
-
-#endif // NVIM_API_OPTIONS_H
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index 58ff552ab7..90023171e5 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -1,25 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Helper structure for vim_to_object
typedef struct {
@@ -49,9 +45,9 @@ typedef struct {
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
do { \
const size_t len_ = (size_t)(len); \
- const char *const str_ = (const char *)(str); \
+ const char *const str_ = (str); \
assert(len_ == 0 || str_ != NULL); \
- kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_?str_:""), len_))); \
+ kvi_push(edata->stack, STRING_OBJ(cbuf_to_string((len_ ? str_ : ""), len_))); \
} while (0)
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
@@ -204,6 +200,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#define TYPVAL_ENCODE_FIRST_ARG_TYPE EncodedData *const
#define TYPVAL_ENCODE_FIRST_ARG_NAME edata
#include "nvim/eval/typval_encode.c.h"
+
#undef TYPVAL_ENCODE_SCOPE
#undef TYPVAL_ENCODE_NAME
#undef TYPVAL_ENCODE_FIRST_ARG_TYPE
@@ -256,12 +253,14 @@ Object vim_to_object(typval_T *obj)
return ret;
}
-/// Converts from type Object to a VimL value.
+/// Converts from type Object to a Vimscript value.
///
/// @param obj Object to convert from.
/// @param tv Conversion result is placed here. On failure member v_type is
/// set to VAR_UNKNOWN (no allocation was made for this variable).
-/// returns true if conversion is successful, otherwise false.
+/// @param err Error object.
+///
+/// @returns true if conversion is successful, otherwise false.
bool object_to_vim(Object obj, typval_T *tv, Error *err)
{
tv->v_type = VAR_UNKNOWN;
@@ -275,7 +274,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeBoolean:
tv->v_type = VAR_BOOL;
- tv->vval.v_bool = obj.data.boolean? kBoolVarTrue: kBoolVarFalse;
+ tv->vval.v_bool = obj.data.boolean ? kBoolVarTrue : kBoolVarFalse;
break;
case kObjectTypeBuffer:
@@ -283,7 +282,7 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
case kObjectTypeTabpage:
case kObjectTypeInteger:
STATIC_ASSERT(sizeof(obj.data.integer) <= sizeof(varnumber_T),
- "Integer size must be <= VimL number size");
+ "Integer size must be <= Vimscript number size");
tv->v_type = VAR_NUMBER;
tv->vval.v_number = (varnumber_T)obj.data.integer;
break;
@@ -363,9 +362,6 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err)
tv->vval.v_string = xstrdup(name);
break;
}
-
- default:
- abort();
}
return true;
diff --git a/src/nvim/api/private/converter.h b/src/nvim/api/private/converter.h
index 80ee640295..fc82abf332 100644
--- a/src/nvim/api/private/converter.h
+++ b/src/nvim/api/private/converter.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_API_PRIVATE_CONVERTER_H
-#define NVIM_API_PRIVATE_CONVERTER_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/converter.h.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_CONVERTER_H
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index 8acbf0d9de..25c8377518 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_PRIVATE_DEFS_H
-#define NVIM_API_PRIVATE_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -7,7 +6,7 @@
#include "klib/kvec.h"
#include "nvim/func_attr.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define ARRAY_DICT_INIT KV_INITIAL_VALUE
#define STRING_INIT { .data = NULL, .size = 0 }
@@ -42,10 +41,10 @@ typedef enum {
/// Mask for all internal calls
#define INTERNAL_CALL_MASK (((uint64_t)1) << (sizeof(uint64_t) * 8 - 1))
-/// Internal call from VimL code
+/// Internal call from Vimscript code
#define VIML_INTERNAL_CALL INTERNAL_CALL_MASK
-/// Internal call from lua code
+/// Internal call from Lua code
#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1)
static inline bool is_internal_call(uint64_t channel_id)
@@ -124,14 +123,18 @@ struct key_value_pair {
Object value;
};
-typedef Object *(*field_hash)(void *retval, const char *str, size_t len);
+typedef uint64_t OptionalKeys;
+
+// this is the prefix of all keysets with optional keys
+typedef struct {
+ OptionalKeys is_set_;
+} OptKeySet;
+
typedef struct {
char *str;
size_t ptr_off;
+ ObjectType type; // kObjectTypeNil == untyped
+ int opt_index;
} KeySetLink;
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keysets_defs.generated.h"
-#endif
-
-#endif // NVIM_API_PRIVATE_DEFS_H
+typedef KeySetLink *(*FieldHashfn)(const char *str, size_t len);
diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c
index f427bba00e..53fcd148bd 100644
--- a/src/nvim/api/private/dispatch.c
+++ b/src/nvim/api/private/dispatch.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include "nvim/api/private/defs.h"
diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h
index 4ae61b2bfb..6a2c9eaf54 100644
--- a/src/nvim/api/private/dispatch.h
+++ b/src/nvim/api/private/dispatch.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_API_PRIVATE_DISPATCH_H
-#define NVIM_API_PRIVATE_DISPATCH_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/memory.h"
-#include "nvim/types.h"
+#include "nvim/memory_defs.h"
+#include "nvim/types_defs.h"
typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, Array args, Arena *arena, Error *error);
@@ -27,7 +26,6 @@ extern const MsgpackRpcRequestHandler method_handlers[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/dispatch.h.generated.h"
-# include "api/private/dispatch_wrappers.h.generated.h"
+# include "api/private/dispatch_wrappers.h.generated.h" // IWYU pragma: export
+# include "keysets_defs.generated.h"
#endif
-
-#endif // NVIM_API_PRIVATE_DISPATCH_H
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index 519f2cc5bf..be39836a5b 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1,13 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <inttypes.h>
#include <limits.h>
#include <msgpack/unpack.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -16,21 +13,26 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/version.h"
@@ -40,10 +42,10 @@
# include "api/private/ui_events_metadata.generated.h"
#endif
-/// Start block that may cause VimL exceptions while evaluating another code
+/// Start block that may cause Vimscript exceptions while evaluating another code
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code” must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code” must not be affected.
///
/// @param[out] tstate Location where try state should be saved.
void try_enter(TryState *const tstate)
@@ -234,8 +236,7 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
// Delete the key
if (di == NULL) {
// Doesn't exist, fail
- api_set_error(err, kErrorTypeValidation, "Key not found: %s",
- key.data);
+ api_set_error(err, kErrorTypeValidation, "Key not found: %s", key.data);
} else {
// Notify watchers
if (watched) {
@@ -264,13 +265,23 @@ Object dict_set_var(dict_T *dict, String key, Object value, bool del, bool retva
di = tv_dict_item_alloc_len(key.data, key.size);
tv_dict_add(dict, di);
} else {
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- }
// Return the old value
if (retval) {
rv = vim_to_object(&di->di_tv);
}
+ bool type_error = false;
+ if (dict == &vimvardict
+ && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
+ tv_clear(&tv);
+ if (type_error) {
+ api_set_error(err, kErrorTypeValidation,
+ "Setting v:%s to value with wrong type", key.data);
+ }
+ return rv;
+ }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
tv_clear(&di->di_tv);
}
@@ -478,6 +489,27 @@ Array string_to_array(const String input, bool crlf)
return ret;
}
+/// Normalizes 0-based indexes to buffer line numbers.
+int64_t normalize_index(buf_T *buf, int64_t index, bool end_exclusive, bool *oob)
+{
+ assert(buf->b_ml.ml_line_count > 0);
+ int64_t max_index = buf->b_ml.ml_line_count + (int)end_exclusive - 1;
+ // A negative index counts from the bottom.
+ index = index < 0 ? max_index + index + 1 : index;
+
+ // Check for oob and clamp.
+ if (index > max_index) {
+ *oob = true;
+ index = max_index;
+ } else if (index < 0) {
+ *oob = true;
+ index = 0;
+ }
+ // Convert the index to a 1-based line number.
+ index++;
+ return index;
+}
+
/// Returns a substring of a buffer line
///
/// @param buf Buffer handle
@@ -495,7 +527,7 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
return rv;
}
- char *bufstr = ml_get_buf(buf, (linenr_T)lnum, false);
+ char *bufstr = ml_get_buf(buf, (linenr_T)lnum);
size_t line_length = strlen(bufstr);
start_col = start_col < 0 ? (int64_t)line_length + start_col + 1 : start_col;
@@ -577,9 +609,6 @@ void api_free_object(Object value)
case kObjectTypeLuaRef:
api_free_luaref(value.data.luaref);
break;
-
- default:
- abort();
}
}
@@ -660,10 +689,10 @@ static void init_ui_event_metadata(Dictionary *metadata)
msgpack_unpacked_destroy(&unpacked);
PUT(*metadata, "ui_events", ui_events);
Array ui_options = ARRAY_DICT_INIT;
- ADD(ui_options, STRING_OBJ(cstr_to_string("rgb")));
+ ADD(ui_options, CSTR_TO_OBJ("rgb"));
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (ui_ext_names[i][0] != '_') {
- ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i])));
+ ADD(ui_options, CSTR_TO_OBJ(ui_ext_names[i]));
}
}
PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options));
@@ -692,17 +721,17 @@ static void init_type_metadata(Dictionary *metadata)
Dictionary buffer_metadata = ARRAY_DICT_INIT;
PUT(buffer_metadata, "id",
INTEGER_OBJ(kObjectTypeBuffer - EXT_OBJECT_TYPE_SHIFT));
- PUT(buffer_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_buf_")));
+ PUT(buffer_metadata, "prefix", CSTR_TO_OBJ("nvim_buf_"));
Dictionary window_metadata = ARRAY_DICT_INIT;
PUT(window_metadata, "id",
INTEGER_OBJ(kObjectTypeWindow - EXT_OBJECT_TYPE_SHIFT));
- PUT(window_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_win_")));
+ PUT(window_metadata, "prefix", CSTR_TO_OBJ("nvim_win_"));
Dictionary tabpage_metadata = ARRAY_DICT_INIT;
PUT(tabpage_metadata, "id",
INTEGER_OBJ(kObjectTypeTabpage - EXT_OBJECT_TYPE_SHIFT));
- PUT(tabpage_metadata, "prefix", STRING_OBJ(cstr_to_string("nvim_tabpage_")));
+ PUT(tabpage_metadata, "prefix", CSTR_TO_OBJ("nvim_tabpage_"));
PUT(types, "Buffer", DICTIONARY_OBJ(buffer_metadata));
PUT(types, "Window", DICTIONARY_OBJ(window_metadata));
@@ -767,10 +796,8 @@ Object copy_object(Object obj, Arena *arena)
case kObjectTypeLuaRef:
return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
-
- default:
- abort();
}
+ UNREACHABLE;
}
void api_set_error(Error *err, ErrorType errType, const char *format, ...)
@@ -806,7 +833,7 @@ bool api_object_to_bool(Object obj, const char *what, bool nil_value, Error *err
} else if (obj.type == kObjectTypeInteger) {
return obj.data.integer; // C semantics: non-zero int is true
} else if (obj.type == kObjectTypeNil) {
- return nil_value; // caller decides what NIL (missing retval in lua) means
+ return nil_value; // caller decides what NIL (missing retval in Lua) means
} else {
api_set_error(err, kErrorTypeValidation, "%s is not a boolean", what);
return false;
@@ -821,12 +848,40 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
} else if (obj.type == kObjectTypeInteger) {
return MAX((int)obj.data.integer, 0);
} else {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a valid highlight", what);
+ api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what);
return 0;
}
}
+char *api_typename(ObjectType t)
+{
+ switch (t) {
+ case kObjectTypeNil:
+ return "nil";
+ case kObjectTypeBoolean:
+ return "Boolean";
+ case kObjectTypeInteger:
+ return "Integer";
+ case kObjectTypeFloat:
+ return "Float";
+ case kObjectTypeString:
+ return "String";
+ case kObjectTypeArray:
+ return "Array";
+ case kObjectTypeDictionary:
+ return "Dict";
+ case kObjectTypeLuaRef:
+ return "Function";
+ case kObjectTypeBuffer:
+ return "Buffer";
+ case kObjectTypeWindow:
+ return "Window";
+ case kObjectTypeTabpage:
+ return "Tabpage";
+ }
+ UNREACHABLE;
+}
+
HlMessage parse_hl_msg(Array chunks, Error *err)
{
HlMessage hl_msg = KV_INITIAL_VALUE;
@@ -865,17 +920,84 @@ free_exit:
return (HlMessage)KV_INITIAL_VALUE;
}
-bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err)
+// see also nlua_pop_keydict for the lua specific implementation
+bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
String k = dict.items[i].key;
- Object *field = hashy(rv, k.data, k.size);
+ KeySetLink *field = hashy(k.data, k.size);
if (!field) {
api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s'", (int)k.size, k.data);
return false;
}
- *field = dict.items[i].value;
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)retval;
+ ks->is_set_ |= (1ULL << field->opt_index);
+ }
+
+ char *mem = ((char *)retval + field->ptr_off);
+ Object *value = &dict.items[i].value;
+ if (field->type == kObjectTypeNil) {
+ *(Object *)mem = *value;
+ } else if (field->type == kObjectTypeInteger) {
+ VALIDATE_T(field->str, kObjectTypeInteger, value->type, {
+ return false;
+ });
+ *(Integer *)mem = value->data.integer;
+ } else if (field->type == kObjectTypeFloat) {
+ Float *val = (Float *)mem;
+ if (value->type == kObjectTypeInteger) {
+ *val = (Float)value->data.integer;
+ } else {
+ VALIDATE_T(field->str, kObjectTypeFloat, value->type, {
+ return false;
+ });
+ *val = value->data.floating;
+ }
+ } else if (field->type == kObjectTypeBoolean) {
+ // caller should check HAS_KEY to override the nil behavior, or GET_BOOL_OR_TRUE
+ // to directly use true when nil
+ *(Boolean *)mem = api_object_to_bool(*value, field->str, false, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+ } else if (field->type == kObjectTypeString) {
+ VALIDATE_T(field->str, kObjectTypeString, value->type, {
+ return false;
+ });
+ *(String *)mem = value->data.string;
+ } else if (field->type == kObjectTypeArray) {
+ VALIDATE_T(field->str, kObjectTypeArray, value->type, {
+ return false;
+ });
+ *(Array *)mem = value->data.array;
+ } else if (field->type == kObjectTypeDictionary) {
+ Dictionary *val = (Dictionary *)mem;
+ // allow empty array as empty dict for lua (directly or via lua-client RPC)
+ if (value->type == kObjectTypeArray && value->data.array.size == 0) {
+ *val = (Dictionary)ARRAY_DICT_INIT;
+ } else if (value->type == kObjectTypeDictionary) {
+ *val = value->data.dictionary;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ if (value->type == kObjectTypeInteger || value->type == field->type) {
+ *(handle_T *)mem = (handle_T)value->data.integer;
+ } else {
+ api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ return false;
+ }
+ } else if (field->type == kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation, "Invalid key: '%.*s' is only allowed from Lua",
+ (int)k.size, k.data);
+ return false;
+ } else {
+ abort();
+ }
}
return true;
@@ -884,7 +1006,18 @@ bool api_dict_to_keydict(void *rv, field_hash hashy, Dictionary dict, Error *err
void api_free_keydict(void *dict, KeySetLink *table)
{
for (size_t i = 0; table[i].str; i++) {
- api_free_object(*(Object *)((char *)dict + table[i].ptr_off));
+ char *mem = ((char *)dict + table[i].ptr_off);
+ if (table[i].type == kObjectTypeNil) {
+ api_free_object(*(Object *)mem);
+ } else if (table[i].type == kObjectTypeString) {
+ api_free_string(*(String *)mem);
+ } else if (table[i].type == kObjectTypeArray) {
+ api_free_array(*(Array *)mem);
+ } else if (table[i].type == kObjectTypeDictionary) {
+ api_free_dictionary(*(Dictionary *)mem);
+ } else if (table[i].type == kObjectTypeLuaRef) {
+ api_free_luaref(*(LuaRef *)mem);
+ }
}
}
@@ -930,12 +1063,14 @@ bool set_mark(buf_T *buf, String name, Integer line, Integer col, Error *err)
}
/// Get default statusline highlight for window
-const char *get_default_stl_hl(win_T *wp, bool use_winbar)
+const char *get_default_stl_hl(win_T *wp, bool use_winbar, int stc_hl_id)
{
if (wp == NULL) {
return "TabLineFill";
} else if (use_winbar) {
return (wp == curwin) ? "WinBar" : "WinBarNC";
+ } else if (stc_hl_id > 0) {
+ return syn_id2name(stc_hl_id);
} else {
return (wp == curwin) ? "StatusLine" : "StatusLineNC";
}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index ec97ba9ec6..e61dd5f992 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -1,26 +1,28 @@
-#ifndef NVIM_API_PRIVATE_HELPERS_H
-#define NVIM_API_PRIVATE_HELPERS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/decoration.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_eval_defs.h"
#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/message.h"
#define OBJECT_OBJ(o) o
#define BOOLEAN_OBJ(b) ((Object) { \
.type = kObjectTypeBoolean, \
.data.boolean = b })
-#define BOOL(b) BOOLEAN_OBJ(b)
#define INTEGER_OBJ(i) ((Object) { \
.type = kObjectTypeInteger, \
@@ -34,6 +36,7 @@
.type = kObjectTypeString, \
.data.string = s })
+#define CSTR_AS_OBJ(s) STRING_OBJ(cstr_as_string(s))
#define CSTR_TO_OBJ(s) STRING_OBJ(cstr_to_string(s))
#define BUFFER_OBJ(s) ((Object) { \
@@ -63,8 +66,9 @@
#define NIL ((Object)OBJECT_INIT)
#define NULL_STRING ((String)STRING_INIT)
-// currently treat key=vim.NIL as if the key was missing
-#define HAS_KEY(o) ((o).type != kObjectTypeNil)
+#define HAS_KEY(d, typ, key) (((d)->is_set__##typ##_ & (1 << KEYSET_OPTIDX_##typ##__##key)) != 0)
+
+#define GET_BOOL_OR_TRUE(d, typ, key) (HAS_KEY(d, typ, key) ? (d)->key : true)
#define PUT(dict, k, v) \
kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v }))
@@ -72,8 +76,6 @@
#define PUT_C(dict, k, v) \
kv_push_c(dict, ((KeyValuePair) { .key = cstr_as_string(k), .value = v }))
-#define PUT_BOOL(dict, name, condition) PUT(dict, name, BOOLEAN_OBJ(condition));
-
#define ADD(array, item) \
kv_push(array, item)
@@ -94,7 +96,7 @@
#define cbuf_as_string(d, s) ((String) { .data = d, .size = s })
-#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_STRING(s) ((String) { .data = s, .size = sizeof("" s) - 1 })
/// Create a new String instance, putting data in allocated memory
///
@@ -103,6 +105,9 @@
.data = xmemdupz(s, sizeof(s) - 1), \
.size = sizeof(s) - 1 })
+#define STATIC_CSTR_AS_OBJ(s) STRING_OBJ(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OBJ(s) STRING_OBJ(STATIC_CSTR_TO_STRING(s))
+
// Helpers used by the generated msgpack-rpc api wrappers
#define api_init_boolean
#define api_init_integer
@@ -122,18 +127,18 @@
#define api_free_window(value)
#define api_free_tabpage(value)
-EXTERN PMap(handle_T) buffer_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) window_handles INIT(= MAP_INIT);
-EXTERN PMap(handle_T) tabpage_handles INIT(= MAP_INIT);
+EXTERN PMap(int) buffer_handles INIT( = MAP_INIT);
+EXTERN PMap(int) window_handles INIT( = MAP_INIT);
+EXTERN PMap(int) tabpage_handles INIT( = MAP_INIT);
-#define handle_get_buffer(h) pmap_get(handle_T)(&buffer_handles, (h))
-#define handle_get_window(h) pmap_get(handle_T)(&window_handles, (h))
-#define handle_get_tabpage(h) pmap_get(handle_T)(&tabpage_handles, (h))
+#define handle_get_buffer(h) pmap_get(int)(&buffer_handles, (h))
+#define handle_get_window(h) pmap_get(int)(&window_handles, (h))
+#define handle_get_tabpage(h) pmap_get(int)(&tabpage_handles, (h))
/// Structure used for saving state for :try
///
-/// Used when caller is supposed to be operating when other VimL code is being
-/// processed and that “other VimL code” must not be affected.
+/// Used when caller is supposed to be operating when other Vimscript code is being
+/// processed and that “other Vimscript code” must not be affected.
typedef struct {
except_T *current_exception;
msglist_T *private_msg_list;
@@ -149,14 +154,26 @@ typedef struct {
// which would otherwise be ignored. This pattern is from do_cmdline().
//
// TODO(bfredl): prepare error-handling at "top level" (nv_event).
-#define TRY_WRAP(code) \
+#define TRY_WRAP(err, code) \
do { \
msglist_T **saved_msg_list = msg_list; \
msglist_T *private_msg_list; \
msg_list = &private_msg_list; \
private_msg_list = NULL; \
- code \
- msg_list = saved_msg_list; /* Restore the exception context. */ \
+ try_start(); \
+ code; \
+ try_end(err); \
+ msg_list = saved_msg_list; /* Restore the exception context. */ \
+ } while (0)
+
+// Execute code with cursor position saved and restored and textlock active.
+#define TEXTLOCK_WRAP(code) \
+ do { \
+ const pos_T save_cursor = curwin->w_cursor; \
+ textlock++; \
+ code; \
+ textlock--; \
+ curwin->w_cursor = save_cursor; \
} while (0)
// Useful macro for executing some `code` for each item in an array.
@@ -169,18 +186,17 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/private/helpers.h.generated.h"
-# include "keysets.h.generated.h"
#endif
#define WITH_SCRIPT_CONTEXT(channel_id, code) \
do { \
const sctx_T save_current_sctx = current_sctx; \
+ const uint64_t save_channel_id = current_channel_id; \
current_sctx.sc_sid = \
(channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \
current_sctx.sc_lnum = 0; \
current_channel_id = channel_id; \
code; \
+ current_channel_id = save_channel_id; \
current_sctx = save_current_sctx; \
} while (0);
-
-#endif // NVIM_API_PRIVATE_HELPERS_H
diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c
new file mode 100644
index 0000000000..e198c671eb
--- /dev/null
+++ b/src/nvim/api/private/validate.c
@@ -0,0 +1,76 @@
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/globals.h"
+
+/// Creates "Invalid …" message and sets it on `err`.
+void api_err_invalid(Error *err, const char *name, const char *val_s, int64_t val_n, bool quote_val)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ // No value.
+ if (val_s && val_s[0] == '\0') {
+ api_set_error(err, errtype, has_space ? "Invalid %s" : "Invalid '%s'", name);
+ return;
+ }
+
+ // Number value.
+ if (val_s == NULL) {
+ api_set_error(err, errtype, has_space ? "Invalid %s: %" PRId64 : "Invalid '%s': %" PRId64,
+ name, val_n);
+ return;
+ }
+
+ // String value.
+ if (has_space) {
+ api_set_error(err, errtype, quote_val ? "Invalid %s: '%s'" : "Invalid %s: %s", name, val_s);
+ } else {
+ api_set_error(err, errtype, quote_val ? "Invalid '%s': '%s'" : "Invalid '%s': %s", name, val_s);
+ }
+}
+
+/// Creates "Invalid …: expected …" message and sets it on `err`.
+void api_err_exp(Error *err, const char *name, const char *expected, const char *actual)
+{
+ ErrorType errtype = kErrorTypeValidation;
+ // Treat `name` without whitespace as a parameter (surround in quotes).
+ // Treat `name` with whitespace as a description (no quotes).
+ char *has_space = strchr(name, ' ');
+
+ if (!actual) {
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s" : "Invalid '%s': expected %s",
+ name, expected);
+ return;
+ }
+
+ api_set_error(err, errtype,
+ has_space ? "Invalid %s: expected %s, got %s" : "Invalid '%s': expected %s, got %s",
+ name, expected, actual);
+}
+
+bool check_string_array(Array arr, char *name, bool disallow_nl, Error *err)
+{
+ snprintf(IObuff, sizeof(IObuff), "'%s' item", name);
+ for (size_t i = 0; i < arr.size; i++) {
+ VALIDATE_T(IObuff, kObjectTypeString, arr.items[i].type, {
+ return false;
+ });
+ // Disallow newlines in the middle of the line.
+ if (disallow_nl) {
+ const String l = arr.items[i].data.string;
+ VALIDATE(!memchr(l.data, NL, l.size), "'%s' item contains newlines", name, {
+ return false;
+ });
+ }
+ }
+ return true;
+}
diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h
new file mode 100644
index 0000000000..d1c977cd6e
--- /dev/null
+++ b/src/nvim/api/private/validate.h
@@ -0,0 +1,96 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/assert_defs.h"
+#include "nvim/macros_defs.h"
+
+#define VALIDATE(cond, fmt_, fmt_arg1, code) \
+ do { \
+ if (!(cond)) { \
+ api_set_error(err, kErrorTypeValidation, fmt_, fmt_arg1); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_INT(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, NULL, val_, false); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_S(cond, name, val_, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, val_, 0, true); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_EXP(cond, name, expected, actual, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_exp(err, name, expected, actual); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_T(name, expected_t, actual_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if (expected_t != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has type `expected_t`.
+#define VALIDATE_T2(obj_, expected_t, code) \
+ do { \
+ STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ if ((obj_).type != expected_t) { \
+ api_err_exp(err, STR(obj_), api_typename(expected_t), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that `obj_` has Dict type. Also allows empty Array in a Lua context.
+#define VALIDATE_T_DICT(name, obj_, code) \
+ do { \
+ if ((obj_).type != kObjectTypeDictionary \
+ && !(channel_id == LUA_INTERNAL_CALL \
+ && (obj_).type == kObjectTypeArray \
+ && (obj_).data.array.size == 0)) { \
+ api_err_exp(err, name, api_typename(kObjectTypeDictionary), api_typename((obj_).type)); \
+ code; \
+ } \
+ } while (0)
+
+/// Checks that actual_t is either the correct handle type or a type erased handle (integer)
+#define VALIDATE_T_HANDLE(name, expected_t, actual_t, code) \
+ do { \
+ if (expected_t != actual_t && kObjectTypeInteger != actual_t) { \
+ api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_RANGE(cond, name, code) \
+ do { \
+ if (!(cond)) { \
+ api_err_invalid(err, name, "out of range", 0, false); \
+ code; \
+ } \
+ } while (0)
+
+#define VALIDATE_R(cond, name, code) \
+ VALIDATE(cond, "Required: '%s'", name, code);
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "api/private/validate.h.generated.h"
+#endif
diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c
index 21eb326c3b..c854a22477 100644
--- a/src/nvim/api/tabpage.c
+++ b/src/nvim/api/tabpage.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdlib.h>
@@ -9,6 +6,7 @@
#include "nvim/api/tabpage.h"
#include "nvim/api/vim.h"
#include "nvim/buffer_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/window.h"
diff --git a/src/nvim/api/tabpage.h b/src/nvim/api/tabpage.h
index 2689cf6ae6..2f19845bd9 100644
--- a/src/nvim/api/tabpage.h
+++ b/src/nvim/api/tabpage.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_API_TABPAGE_H
-#define NVIM_API_TABPAGE_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/tabpage.h.generated.h"
#endif
-#endif // NVIM_API_TABPAGE_H
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index e67607a7e4..836a68546c 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,10 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/pack.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
@@ -12,25 +10,27 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/autocmd.h"
#include "nvim/channel.h"
+#include "nvim/eval.h"
#include "nvim/event/loop.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
#include "nvim/option.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#define BUF_POS(data) ((size_t)((data)->buf_wptr - (data)->buf))
@@ -72,6 +72,11 @@ static void mpack_uint(char **buf, uint32_t val)
}
}
+static void mpack_bool(char **buf, bool val)
+{
+ mpack_w(buf, 0xc2 | (val ? 1 : 0));
+}
+
static void mpack_array(char **buf, uint32_t len)
{
if (len < 0x10) {
@@ -110,23 +115,32 @@ void remote_ui_disconnect(uint64_t channel_id)
}
UIData *data = ui->data;
kv_destroy(data->call_buf);
- pmap_del(uint64_t)(&connected_uis, channel_id);
+ pmap_del(uint64_t)(&connected_uis, channel_id, NULL);
ui_detach_impl(ui, channel_id);
+
+ // Destroy `ui`.
+ XFREE_CLEAR(ui->term_name);
xfree(ui);
}
-/// Wait until ui has connected on stdio channel.
-void remote_ui_wait_for_attach(void)
+/// Wait until ui has connected on stdio channel if only_stdio
+/// is true, otherwise any channel.
+void remote_ui_wait_for_attach(bool only_stdio)
{
- Channel *channel = find_channel(CHAN_STDIO);
- if (!channel) {
- // this function should only be called in --embed mode, stdio channel
- // can be assumed.
- abort();
- }
+ if (only_stdio) {
+ Channel *channel = find_channel(CHAN_STDIO);
+ if (!channel) {
+ // this function should only be called in --embed mode, stdio channel
+ // can be assumed.
+ abort();
+ }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
- pmap_has(uint64_t)(&connected_uis, CHAN_STDIO));
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1,
+ map_has(uint64_t, &connected_uis, CHAN_STDIO));
+ } else {
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, -1,
+ ui_active());
+ }
}
/// Activates UI events on the channel.
@@ -148,7 +162,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI already attached to channel: %" PRId64, channel_id);
return;
@@ -162,15 +176,9 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
UI *ui = xcalloc(1, sizeof(UI));
ui->width = (int)width;
ui->height = (int)height;
- ui->pum_nlines = 0;
- ui->pum_pos = false;
- ui->pum_width = 0.0;
- ui->pum_height = 0.0;
ui->pum_row = -1.0;
ui->pum_col = -1.0;
ui->rgb = true;
- ui->override = false;
-
CLEAR_FIELD(ui->ui_ext);
for (size_t i = 0; i < options.size; i++) {
@@ -202,6 +210,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
data->flushed_events = false;
data->ncalls_pos = NULL;
data->ncalls = 0;
+ data->ncells_pending = 0;
data->buf_wptr = data->buf;
data->temp_buf = NULL;
data->wildmenu_active = false;
@@ -210,6 +219,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictiona
pmap_put(uint64_t)(&connected_uis, channel_id, ui);
ui_attach_impl(ui, channel_id);
+
+ may_trigger_vim_suspend_resume(false);
}
/// @deprecated
@@ -226,12 +237,16 @@ void ui_attach(uint64_t channel_id, Integer width, Integer height, Boolean enabl
void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
FUNC_API_SINCE(11) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
}
+ if (gained) {
+ may_trigger_vim_suspend_resume(false);
+ }
+
do_autocmd_focusgained((bool)gained);
}
@@ -244,7 +259,7 @@ void nvim_ui_set_focus(uint64_t channel_id, Boolean gained, Error *error)
void nvim_ui_detach(uint64_t channel_id, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -252,14 +267,15 @@ void nvim_ui_detach(uint64_t channel_id, Error *err)
remote_ui_disconnect(channel_id);
}
-// TODO(bfredl): use me to detach a specifc ui from the server
+// TODO(bfredl): use me to detach a specific ui from the server
void remote_ui_stop(UI *ui)
-{}
+{
+}
void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -280,7 +296,7 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, Integer height, Erro
void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *error)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(error, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -290,22 +306,20 @@ void nvim_ui_set_option(uint64_t channel_id, String name, Object value, Error *e
ui_set_option(ui, false, name, value, error);
}
-static void ui_set_option(UI *ui, bool init, String name, Object value, Error *error)
+static void ui_set_option(UI *ui, bool init, String name, Object value, Error *err)
{
if (strequal(name.data, "override")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "override must be a Boolean");
+ VALIDATE_T("override", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->override = value.data.boolean;
return;
}
if (strequal(name.data, "rgb")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean");
+ VALIDATE_T("rgb", kObjectTypeBoolean, value.type, {
return;
- }
+ });
ui->rgb = value.data.boolean;
// A little drastic, but only takes effect for legacy uis. For linegrid UI
// only changes metadata for nvim_list_uis(), no refresh needed.
@@ -316,63 +330,53 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
if (strequal(name.data, "term_name")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_name must be a String");
+ VALIDATE_T("term_name", kObjectTypeString, value.type, {
return;
- }
+ });
set_tty_option("term", string_to_cstr(value.data.string));
+ ui->term_name = string_to_cstr(value.data.string);
return;
}
if (strequal(name.data, "term_colors")) {
- if (value.type != kObjectTypeInteger) {
- api_set_error(error, kErrorTypeValidation, "term_colors must be a Integer");
+ VALIDATE_T("term_colors", kObjectTypeInteger, value.type, {
return;
- }
+ });
t_colors = (int)value.data.integer;
- return;
- }
-
- if (strequal(name.data, "term_background")) {
- if (value.type != kObjectTypeString) {
- api_set_error(error, kErrorTypeValidation, "term_background must be a String");
- return;
- }
- set_tty_background(value.data.string.data);
+ ui->term_colors = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_fd")) {
- if (value.type != kObjectTypeInteger || value.data.integer < 0) {
- api_set_error(error, kErrorTypeValidation, "stdin_fd must be a non-negative Integer");
+ VALIDATE_T("stdin_fd", kObjectTypeInteger, value.type, {
return;
- }
-
- if (starting != NO_SCREEN) {
- api_set_error(error, kErrorTypeValidation,
- "stdin_fd can only be used with first attached ui");
+ });
+ VALIDATE_INT((value.data.integer >= 0), "stdin_fd", value.data.integer, {
return;
- }
+ });
+ VALIDATE((starting == NO_SCREEN), "%s", "stdin_fd can only be used with first attached UI", {
+ return;
+ });
stdin_fd = (int)value.data.integer;
return;
}
if (strequal(name.data, "stdin_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdin_tty must be a Boolean");
+ VALIDATE_T("stdin_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdin_isatty = value.data.boolean;
+ ui->stdin_tty = value.data.boolean;
return;
}
if (strequal(name.data, "stdout_tty")) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "stdout_tty must be a Boolean");
+ VALIDATE_T("stdout_tty", kObjectTypeBoolean, value.type, {
return;
- }
+ });
stdout_isatty = value.data.boolean;
+ ui->stdout_tty = value.data.boolean;
return;
}
@@ -382,17 +386,15 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
for (UIExtension i = 0; i < kUIExtCount; i++) {
if (strequal(name.data, ui_ext_names[i])
|| (i == kUIPopupmenu && is_popupmenu)) {
- if (value.type != kObjectTypeBoolean) {
- api_set_error(error, kErrorTypeValidation, "%s must be a Boolean",
- name.data);
+ VALIDATE_EXP((value.type == kObjectTypeBoolean), name.data, "Boolean",
+ api_typename(value.type), {
return;
- }
+ });
bool boolval = value.data.boolean;
if (!init && i == kUILinegrid && boolval != ui->ui_ext[i]) {
- // There shouldn't be a reason for an UI to do this ever
+ // There shouldn't be a reason for a UI to do this ever
// so explicitly don't support this.
- api_set_error(error, kErrorTypeValidation,
- "ext_linegrid option cannot be changed");
+ api_set_error(err, kErrorTypeValidation, "ext_linegrid option cannot be changed");
}
ui->ui_ext[i] = boolval;
if (!init) {
@@ -402,8 +404,7 @@ static void ui_set_option(UI *ui, bool init, String name, Object value, Error *e
}
}
- api_set_error(error, kErrorTypeValidation, "No such UI option: %s",
- name.data);
+ api_set_error(err, kErrorTypeValidation, "No such UI option: %s", name.data);
}
/// Tell Nvim to resize a grid. Triggers a grid_resize event with the requested
@@ -420,7 +421,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -442,7 +443,7 @@ void nvim_ui_try_resize_grid(uint64_t channel_id, Integer grid, Integer width, I
void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -483,7 +484,7 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
Error *err)
FUNC_API_SINCE(7) FUNC_API_REMOTE_ONLY
{
- if (!pmap_has(uint64_t)(&connected_uis, channel_id)) {
+ if (!map_has(uint64_t, &connected_uis, channel_id)) {
api_set_error(err, kErrorTypeException,
"UI not attached to channel: %" PRId64, channel_id);
return;
@@ -511,6 +512,33 @@ void nvim_ui_pum_set_bounds(uint64_t channel_id, Float width, Float height, Floa
ui->pum_pos = true;
}
+/// Tells Nvim when a terminal event has occurred
+///
+/// The following terminal events are supported:
+///
+/// - "termresponse": The terminal sent an OSC or DCS response sequence to
+/// Nvim. The payload is the received response. Sets
+/// |v:termresponse| and fires |TermResponse|.
+///
+/// @param channel_id
+/// @param event Event name
+/// @param payload Event payload
+/// @param[out] err Error details, if any.
+void nvim_ui_term_event(uint64_t channel_id, String event, Object value, Error *err)
+ FUNC_API_SINCE(12) FUNC_API_REMOTE_ONLY
+{
+ if (strequal("termresponse", event.data)) {
+ if (value.type != kObjectTypeString) {
+ api_set_error(err, kErrorTypeValidation, "termresponse must be a string");
+ return;
+ }
+
+ const String termresponse = value.data.string;
+ set_vim_var_string(VV_TERMRESPONSE, termresponse.data, (ptrdiff_t)termresponse.size);
+ apply_autocmds_group(EVENT_TERMRESPONSE, NULL, NULL, false, AUGROUP_ALL, NULL, NULL, &value);
+ }
+}
+
static void flush_event(UIData *data)
{
if (data->cur_event) {
@@ -644,6 +672,8 @@ void remote_ui_grid_resize(UI *ui, Integer grid, Integer width, Integer height)
Array args = data->call_buf;
if (ui->ui_ext[kUILinegrid]) {
ADD_C(args, INTEGER_OBJ(grid));
+ } else {
+ data->client_col = -1; // force cursor update
}
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
@@ -731,8 +761,8 @@ void remote_ui_hl_attr_define(UI *ui, Integer id, HlAttrs rgb_attrs, HlAttrs cte
ADD_C(args, INTEGER_OBJ(id));
MAXSIZE_TEMP_DICT(rgb, HLATTRS_DICT_SIZE);
MAXSIZE_TEMP_DICT(cterm, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb, rgb_attrs, true);
- hlattrs2dict(&cterm, rgb_attrs, false);
+ hlattrs2dict(&rgb, NULL, rgb_attrs, true, false);
+ hlattrs2dict(&cterm, NULL, rgb_attrs, false, false);
ADD_C(args, DICTIONARY_OBJ(rgb));
ADD_C(args, DICTIONARY_OBJ(cterm));
@@ -755,7 +785,7 @@ void remote_ui_highlight_set(UI *ui, int id)
}
data->hl_id = id;
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
- hlattrs2dict(&dict, syn_attr2entry(id), ui->rgb);
+ hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
ADD_C(args, DICTIONARY_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -798,7 +828,7 @@ void remote_ui_put(UI *ui, const char *cell)
UIData *data = ui->data;
data->client_col++;
Array args = data->call_buf;
- ADD_C(args, STRING_OBJ(cstr_as_string((char *)cell)));
+ ADD_C(args, CSTR_AS_OBJ((char *)cell));
push_call(ui, "put", args);
}
@@ -812,7 +842,7 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
data->ncalls++;
char **buf = &data->buf_wptr;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol);
@@ -822,21 +852,24 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
size_t ncells = (size_t)(endcol - startcol);
int last_hl = -1;
uint32_t nelem = 0;
+ bool was_space = false;
for (size_t i = 0; i < ncells; i++) {
repeat++;
- if (i == ncells - 1 || attrs[i] != attrs[i + 1]
- || strcmp(chunk[i], chunk[i + 1]) != 0) {
- if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5)) {
+ if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
+ if (UI_BUF_SIZE - BUF_POS(data) < 2 * (1 + 2 + sizeof(schar_T) + 5 + 5) + 1) {
// close to overflowing the redraw buffer. finish this event,
// flush, and start a new "grid_line" event at the current position.
// For simplicity leave place for the final "clear" element
// as well, hence the factor of 2 in the check.
mpack_w2(&lenpos, nelem);
+
+ // We only ever set the wrap field on the final "grid_line" event for the line.
+ mpack_bool(buf, false);
remote_ui_flush_buf(ui);
prepare_call(ui, "grid_line");
data->ncalls++;
- mpack_array(buf, 4);
+ mpack_array(buf, 5);
mpack_uint(buf, (uint32_t)grid);
mpack_uint(buf, (uint32_t)row);
mpack_uint(buf, (uint32_t)startcol + (uint32_t)i - repeat + 1);
@@ -847,31 +880,46 @@ void remote_ui_raw_line(UI *ui, Integer grid, Integer row, Integer startcol, Int
uint32_t csize = (repeat > 1) ? 3 : ((attrs[i] != last_hl) ? 2 : 1);
nelem++;
mpack_array(buf, csize);
- mpack_str(buf, (const char *)chunk[i]);
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ mpack_str(buf, sc_buf);
if (csize >= 2) {
mpack_uint(buf, (uint32_t)attrs[i]);
if (csize >= 3) {
mpack_uint(buf, repeat);
}
}
+ data->ncells_pending += MIN(repeat, 2);
last_hl = attrs[i];
repeat = 0;
+ was_space = chunk[i] == schar_from_ascii(' ');
}
}
- if (endcol < clearcol) {
+ // If the last chunk was all spaces, add a clearing chunk even if there are
+ // no more cells to clear, so there is no ambiguity about what to clear.
+ if (endcol < clearcol || was_space) {
nelem++;
+ data->ncells_pending += 1;
mpack_array(buf, 3);
mpack_str(buf, " ");
mpack_uint(buf, (uint32_t)clearattr);
mpack_uint(buf, (uint32_t)(clearcol - endcol));
}
mpack_w2(&lenpos, nelem);
+ mpack_bool(buf, flags & kLineFlagWrap);
+
+ if (data->ncells_pending > 500) {
+ // pass off cells to UI to let it start processing them
+ remote_ui_flush_buf(ui);
+ }
} else {
for (int i = 0; i < endcol - startcol; i++) {
remote_ui_cursor_goto(ui, row, startcol + i);
remote_ui_highlight_set(ui, attrs[i]);
- remote_ui_put(ui, (const char *)chunk[i]);
- if (utf_ambiguous_width(utf_ptr2char((char *)chunk[i]))) {
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, chunk[i]);
+ remote_ui_put(ui, sc_buf);
+ if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
data->client_col = -1; // force cursor update
}
}
@@ -917,6 +965,8 @@ void remote_ui_flush_buf(UI *ui)
// we have sent events to the client, but possibly not yet the final "flush"
// event.
data->flushed_events = true;
+
+ data->ncells_pending = 0;
}
/// An intentional flush (vsync) when Nvim is finished redrawing the screen
@@ -945,7 +995,7 @@ static Array translate_contents(UI *ui, Array contents, Arena *arena)
int attr = (int)item.items[0].data.integer;
if (attr) {
Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&rgb_attrs, syn_attr2entry(attr), ui->rgb);
+ hlattrs2dict(&rgb_attrs, NULL, syn_attr2entry(attr), ui->rgb, false);
ADD(new_item, DICTIONARY_OBJ(rgb_attrs));
} else {
ADD(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
diff --git a/src/nvim/api/ui.h b/src/nvim/api/ui.h
index b3fe0fa2bb..26a91d0dbc 100644
--- a/src/nvim/api/ui.h
+++ b/src/nvim/api/ui.h
@@ -1,14 +1,14 @@
-#ifndef NVIM_API_UI_H
-#define NVIM_API_UI_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/map.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: keep
+#include "nvim/map_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/ui.h.generated.h"
-# include "ui_events_remote.h.generated.h"
+# include "ui_events_remote.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_API_UI_H
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index a08e8dbfeb..bda0c72423 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_API_UI_EVENTS_IN_H
-#define NVIM_API_UI_EVENTS_IN_H
+#pragma once
// This file is not compiled, just parsed for definitions
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -82,7 +81,7 @@ void grid_clear(Integer grid)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL;
void grid_cursor_goto(Integer grid, Integer row, Integer col)
FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL;
-void grid_line(Integer grid, Integer row, Integer col_start, Array data)
+void grid_line(Integer grid, Integer row, Integer col_start, Array data, Boolean wrap)
FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL;
void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows,
Integer cols)
@@ -114,7 +113,7 @@ void msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep_char)
FUNC_API_SINCE(6) FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IGNORE;
void win_viewport(Integer grid, Window win, Integer topline, Integer botline, Integer curline,
- Integer curcol, Integer line_count)
+ Integer curcol, Integer line_count, Integer scroll_delta)
FUNC_API_SINCE(7) FUNC_API_CLIENT_IGNORE;
void win_extmark(Integer grid, Window win, Integer ns_id, Integer mark_id, Integer row, Integer col)
@@ -167,4 +166,6 @@ void msg_history_show(Array entries)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
void msg_history_clear(void)
FUNC_API_SINCE(10) FUNC_API_REMOTE_ONLY;
-#endif // NVIM_API_UI_EVENTS_IN_H
+
+void error_exit(Integer status)
+ FUNC_API_SINCE(12);
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index a53b30dd8a..d631b10af9 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -1,8 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,25 +9,29 @@
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/deprecated.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/vim.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/channel.h"
#include "nvim/context.h"
+#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
@@ -38,7 +40,7 @@
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -47,24 +49,24 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/process.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
+#include "nvim/sign.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#define LINE_BUFFER_MIN_SIZE 4096
@@ -73,44 +75,6 @@
# include "api/vim.c.generated.h"
#endif
-/// Gets a highlight definition by name.
-///
-/// @param name Highlight group name
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary result = ARRAY_DICT_INIT;
- int id = syn_name2id(name.data);
-
- if (id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
- return result;
- }
- return nvim_get_hl_by_id(id, rgb, arena, err);
-}
-
-/// Gets a highlight definition by id. |hlID()|
-/// @param hl_id Highlight id as returned by |hlID()|
-/// @param rgb Export RGB colors
-/// @param[out] err Error details, if any
-/// @return Highlight definition map
-/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
- FUNC_API_SINCE(3)
-{
- Dictionary dic = ARRAY_DICT_INIT;
- if (syn_get_final_id((int)hl_id) == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight id: %" PRId64, hl_id);
- return dic;
- }
- int attrcode = syn_id2attr((int)hl_id);
- return hl_get_attr_by_id(attrcode, rgb, arena, err);
-}
-
/// Gets a highlight group by name
///
/// similar to |hlID()|, but allocates a new ID if not present.
@@ -120,12 +84,26 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group(name.data, name.size);
}
-Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
+/// Gets all or specific highlight groups in a namespace.
+///
+/// @note When the `link` attribute is defined in the highlight definition
+/// map, other attributes will not be taking effect (see |:hi-link|).
+///
+/// @param ns_id Get highlight groups for namespace ns_id |nvim_get_namespaces()|.
+/// Use 0 to get global highlight groups |:highlight|.
+/// @param opts Options dict:
+/// - name: (string) Get a highlight definition by name.
+/// - id: (integer) Get a highlight definition by id.
+/// - link: (boolean, default true) Show linked group name instead of effective definition |:hi-link|.
+/// - create: (boolean, default true) When highlight group doesn't exist create it.
+///
+/// @param[out] err Error details, if any.
+/// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|,
+/// or only a single highlight definition map if requested by name or id.
+Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+ FUNC_API_SINCE(11)
{
- if (ns_id == 0) {
- return get_global_hl_defs(arena);
- }
- abort();
+ return ns_get_hl_defs((NS)ns_id, opts, arena, err);
}
/// Sets a highlight group.
@@ -140,8 +118,14 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// values of the Normal group. If the Normal group has not been defined,
/// using these values results in an error.
///
+///
+/// @note If `link` is used in combination with other attributes; only the
+/// `link` will take effect (see |:hi-link|).
+///
/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|.
/// Use 0 to set a highlight group globally |:highlight|.
+/// Highlights from non-global namespaces are not active by default, use
+/// |nvim_set_hl_ns()| or |nvim_win_set_hl_ns()| to activate them.
/// @param name Highlight group name, e.g. "ErrorMsg"
/// @param val Highlight definition map, accepts the following keys:
/// - fg (or foreground): color name or "#RRGGBB", see note.
@@ -166,6 +150,7 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Arena *arena, Error *err)
/// - cterm: cterm attribute map, like |highlight-args|. If not set,
/// cterm attributes will match those from the attribute map
/// documented above.
+/// - force: if true force update the highlight group when it exists.
/// @param[out] err Error details, if any
///
// TODO(bfredl): val should take update vs reset flag
@@ -173,10 +158,9 @@ 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, name.size);
- if (hl_id == 0) {
- api_set_error(err, kErrorTypeException, "Invalid highlight name: %s", name.data);
+ VALIDATE_S((hl_id != 0), "highlight name", name.data, {
return;
- }
+ });
int link_id = -1;
HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
@@ -185,25 +169,47 @@ void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err)
}
}
-/// Set active namespace for highlights. This can be set for a single window,
-/// see |nvim_win_set_hl_ns()|.
+/// Gets the active highlight namespace.
+///
+/// @param opts Optional parameters
+/// - winid: (number) |window-ID| for retrieving a window's highlight
+/// namespace. A value of -1 is returned when |nvim_win_set_hl_ns()|
+/// has not been called for the window (or was called with a namespace
+/// of -1).
+/// @param[out] err Error details, if any
+/// @return Namespace id, or -1
+Integer nvim_get_hl_ns(Dict(get_ns) *opts, Error *err)
+ FUNC_API_SINCE(12)
+{
+ if (HAS_KEY(opts, get_ns, winid)) {
+ win_T *win = find_window_by_handle(opts->winid, err);
+ if (!win) {
+ return 0;
+ }
+ return win->w_ns_hl;
+ } else {
+ return ns_hl_global;
+ }
+}
+
+/// Set active namespace for highlights defined with |nvim_set_hl()|. This can be set for
+/// a single window, see |nvim_win_set_hl_ns()|.
///
/// @param ns_id the namespace to use
/// @param[out] err Error details, if any
void nvim_set_hl_ns(Integer ns_id, Error *err)
FUNC_API_SINCE(10)
{
- if (ns_id < 0) {
- api_set_error(err, kErrorTypeValidation, "no such namespace");
+ VALIDATE_INT((ns_id >= 0), "namespace", ns_id, {
return;
- }
+ });
ns_hl_global = (NS)ns_id;
hl_check_ns();
redraw_all_later(UPD_NOT_VALID);
}
-/// Set active namespace for highlights while redrawing.
+/// Set active namespace for highlights defined with |nvim_set_hl()| while redrawing.
///
/// This function meant to be called while redrawing, primarily from
/// |nvim_set_decoration_provider()| on_win and on_line callbacks, which
@@ -229,10 +235,11 @@ void nvim_set_hl_ns_fast(Integer ns_id, Error *err)
/// nvim_feedkeys().
///
/// Example:
-/// <pre>vim
-/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
-/// :call nvim_feedkeys(key, 'n', v:false)
-/// </pre>
+///
+/// ```vim
+/// :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true)
+/// :call nvim_feedkeys(key, 'n', v:false)
+/// ```
///
/// @param keys to be typed
/// @param mode behavior flags, see |feedkeys()|
@@ -323,6 +330,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
Integer nvim_input(String keys)
FUNC_API_SINCE(1) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
return (Integer)input_enqueue(keys);
}
@@ -352,6 +360,8 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
Integer col, Error *err)
FUNC_API_SINCE(6) FUNC_API_FAST
{
+ may_trigger_vim_suspend_resume(false);
+
if (button.data == NULL || action.data == NULL) {
goto error;
}
@@ -403,11 +413,9 @@ void nvim_input_mouse(String button, String action, String modifier, Integer gri
continue;
}
int mod = name_to_mod_mask(byte);
- if (mod == 0) {
- api_set_error(err, kErrorTypeValidation,
- "invalid modifier %c", byte);
+ VALIDATE((mod != 0), "Invalid modifier: %c", byte, {
return;
- }
+ });
modmask |= mod;
}
@@ -448,7 +456,7 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
}
char *ptr = NULL;
- replace_termcodes(str.data, str.size, &ptr, flags, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(str.data, str.size, &ptr, 0, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}
@@ -500,15 +508,14 @@ Object nvim_notify(String msg, Integer log_level, Dictionary opts, Error *err)
Integer nvim_strwidth(String text, Error *err)
FUNC_API_SINCE(1)
{
- if (text.size > INT_MAX) {
- api_set_error(err, kErrorTypeValidation, "String is too long");
+ VALIDATE_S((text.size <= INT_MAX), "text length", "(too long)", {
return 0;
- }
+ });
return (Integer)mb_string2cells(text.data);
}
-/// Gets the paths contained in 'runtimepath'.
+/// Gets the paths contained in |runtime-search-path|.
///
/// @return List of paths
ArrayOf(String) nvim_list_runtime_paths(Error *err)
@@ -542,20 +549,23 @@ ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
int flags = DIP_DIRFILE | (all ? DIP_ALL : 0);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
do_in_runtimepath((name.size ? name.data : ""), flags, find_runtime_cb, &rv);
- try_end(err);
});
return rv;
}
-static void find_runtime_cb(char *fname, void *cookie)
+static bool find_runtime_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
Array *rv = (Array *)cookie;
- if (fname != NULL) {
- ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ for (int i = 0; i < num_fnames; i++) {
+ ADD(*rv, CSTR_TO_OBJ(fnames[i]));
+ if (!all) {
+ return true;
+ }
}
+
+ return num_fnames > 0;
}
String nvim__get_lib_dir(void)
@@ -567,28 +577,24 @@ String nvim__get_lib_dir(void)
///
/// @param pat pattern of files to search for
/// @param all whether to return all matches or only the first
-/// @param opts is_lua: only search lua subdirs
+/// @param opts is_lua: only search Lua subdirs
/// @return list of absolute paths to the found files
ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, Error *err)
FUNC_API_SINCE(8)
FUNC_API_FAST
{
- bool is_lua = api_object_to_bool(opts->is_lua, "is_lua", false, err);
- bool source = api_object_to_bool(opts->do_source, "do_source", false, err);
- if (source && !nlua_is_deferred_safe()) {
- api_set_error(err, kErrorTypeValidation, "'do_source' cannot be used in fast callback");
- }
-
+ VALIDATE(!opts->do_source || nlua_is_deferred_safe(), "%s", "'do_source' used in fast callback",
+ {});
if (ERROR_SET(err)) {
return (Array)ARRAY_DICT_INIT;
}
- ArrayOf(String) res = runtime_get_named(is_lua, pat, all);
+ ArrayOf(String) res = runtime_get_named(opts->is_lua, pat, all);
- if (source) {
+ if (opts->do_source) {
for (size_t i = 0; i < res.size; i++) {
String name = res.items[i].data.string;
- (void)do_source(name.data, false, DOSO_NONE);
+ (void)do_source(name.data, false, DOSO_NONE, NULL);
}
}
@@ -602,10 +608,9 @@ ArrayOf(String) nvim__get_runtime(Array pat, Boolean all, Dict(runtime) *opts, E
void nvim_set_current_dir(String dir, Error *err)
FUNC_API_SINCE(1)
{
- if (dir.size >= MAXPATHL) {
- api_set_error(err, kErrorTypeValidation, "Directory name is too long");
+ VALIDATE_S((dir.size < MAXPATHL), "directory name", "(too long)", {
return;
- }
+ });
char string[MAXPATHL];
memcpy(string, dir.data, dir.size);
@@ -639,7 +644,7 @@ String nvim_get_current_line(Error *err)
/// @param[out] err Error details, if any
void nvim_set_current_line(String line, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err);
}
@@ -649,7 +654,7 @@ void nvim_set_current_line(String line, Error *err)
/// @param[out] err Error details, if any
void nvim_del_current_line(Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err);
}
@@ -664,16 +669,15 @@ Object nvim_get_var(String name, Error *err)
{
dictitem_T *di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
if (di == NULL) { // try to autoload script
- if (!script_autoload(name.data, name.size, false) || aborting()) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ bool found = script_autoload(name.data, name.size, false) && !aborting();
+ VALIDATE(found, "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
di = tv_dict_find(&globvardict, name.data, (ptrdiff_t)name.size);
}
- if (di == NULL) {
- api_set_error(err, kErrorTypeValidation, "Key not found: %s", name.data);
+ VALIDATE((di != NULL), "Key not found: %s", name.data, {
return (Object)OBJECT_INIT;
- }
+ });
return vim_to_object(&di->di_tv);
}
@@ -738,15 +742,13 @@ void nvim_echo(Array chunks, Boolean history, Dict(echo_opts) *opts, Error *err)
goto error;
}
- bool verbose = api_object_to_bool(opts->verbose, "verbose", false, err);
-
- if (verbose) {
+ if (opts->verbose) {
verbose_enter();
}
msg_multiattr(hl_msg, history ? "echomsg" : "echo", history);
- if (verbose) {
+ if (opts->verbose) {
verbose_leave();
verbose_stop(); // flush now
}
@@ -767,7 +769,7 @@ error:
void nvim_out_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, false);
+ write_msg(str, false, false);
}
/// Writes a message to the Vim error buffer. Does not append "\n", the
@@ -777,7 +779,7 @@ void nvim_out_write(String str)
void nvim_err_write(String str)
FUNC_API_SINCE(1)
{
- write_msg(str, true);
+ write_msg(str, true, false);
}
/// Writes a message to the Vim error buffer. Appends "\n", so the buffer is
@@ -788,8 +790,7 @@ void nvim_err_write(String str)
void nvim_err_writeln(String str)
FUNC_API_SINCE(1)
{
- nvim_err_write(str);
- nvim_err_write((String) { .data = "\n", .size = 1 });
+ write_msg(str, true, true);
}
/// Gets the current list of buffer handles
@@ -832,7 +833,7 @@ Buffer nvim_get_current_buf(void)
/// @param[out] err Error details, if any
void nvim_set_current_buf(Buffer buffer, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -887,7 +888,7 @@ Window nvim_get_current_win(void)
/// @param[out] err Error details, if any
void nvim_set_current_win(Window window, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
win_T *win = find_window_by_handle(window, err);
@@ -918,7 +919,7 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
FUNC_API_SINCE(6)
{
try_start();
- buf_T *buf = buflist_new(NULL, NULL, (linenr_T)0,
+ buf_T *buf = buflist_new(NULL, NULL, 0,
BLN_NOOPT | BLN_NEW | (listed ? BLN_LISTED : 0));
try_end(err);
if (buf == NULL) {
@@ -936,14 +937,23 @@ Buffer nvim_create_buf(Boolean listed, Boolean scratch, Error *err)
goto fail;
}
+ // Set last_changedtick to avoid triggering a TextChanged autocommand right
+ // after it was added.
+ buf->b_last_changedtick = buf_get_changedtick(buf);
+ buf->b_last_changedtick_i = buf_get_changedtick(buf);
+ buf->b_last_changedtick_pum = buf_get_changedtick(buf);
+
+ // Only strictly needed for scratch, but could just as well be consistent
+ // and do this now. buffer is created NOW, not when it latter first happen
+ // to reach a window or aucmd_prepbuf() ..
+ buf_copy_options(buf, BCO_ENTER | BCO_NOHELP);
+
if (scratch) {
- aco_save_T aco;
- aucmd_prepbuf(&aco, buf);
- set_option_value("bufhidden", 0L, "hide", OPT_LOCAL);
- set_option_value("buftype", 0L, "nofile", OPT_LOCAL);
- set_option_value("swapfile", 0L, NULL, OPT_LOCAL);
- set_option_value("modeline", 0L, NULL, OPT_LOCAL); // 'nomodeline'
- aucmd_restbuf(&aco);
+ set_string_option_direct_in_buf(buf, "bufhidden", -1, "hide", OPT_LOCAL, 0);
+ set_string_option_direct_in_buf(buf, "buftype", -1, "nofile", OPT_LOCAL, 0);
+ assert(buf->b_ml.ml_mfp->mf_fd < 0); // ml_open() should not have opened swapfile already
+ buf->b_p_swf = false;
+ buf->b_p_ml = false;
}
return buf->b_fnum;
@@ -970,7 +980,7 @@ fail:
///
/// @param buffer the buffer to use (expected to be empty)
/// @param opts Optional parameters.
-/// - on_input: lua callback for input sent, i e keypresses in terminal
+/// - on_input: Lua callback for input sent, i e keypresses in terminal
/// mode. Note: keypresses are sent raw as they would be to the pty
/// master end. For instance, a carriage return is sent
/// as a "\r", not as a "\n". |textlock| applies. It is possible
@@ -980,27 +990,31 @@ fail:
/// @return Channel id, or 0 on error
Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
return 0;
}
+ if (cmdwin_type != 0 && buf == curbuf) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
LuaRef cb = LUA_NOREF;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
Object *v = &opts.items[i].value;
if (strequal("on_input", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation,
- "%s is not a function", "on_input");
+ VALIDATE_T("on_input", kObjectTypeLuaRef, v->type, {
return 0;
- }
+ });
cb = v->data.luaref;
v->data.luaref = LUA_NOREF;
break;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ VALIDATE_S(false, "'opts' key", k.data, {});
}
}
@@ -1016,9 +1030,12 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- Terminal *term = terminal_open(buf, topts);
- terminal_check_size(term);
- chan->term = term;
+ channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
+ if (chan->term != NULL) {
+ terminal_check_size(chan->term);
+ }
+ channel_decref(chan);
return (Integer)chan->id;
}
@@ -1040,7 +1057,7 @@ static void term_write(char *buf, size_t size, void *data) // NOLINT(readabilit
static void term_resize(uint16_t width, uint16_t height, void *data)
{
- // TODO(bfredl): lua callback
+ // TODO(bfredl): Lua callback
}
static void term_close(void *data)
@@ -1075,9 +1092,7 @@ void nvim_chan_send(Integer chan, String data, Error *err)
channel_send((uint64_t)chan, data.data, data.size,
false, &error);
- if (error) {
- api_set_error(err, kErrorTypeValidation, "%s", error);
- }
+ VALIDATE(!error, "%s", error, {});
}
/// Gets the current list of tabpage handles.
@@ -1117,7 +1132,7 @@ Tabpage nvim_get_current_tabpage(void)
/// @param[out] err Error details, if any
void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
FUNC_API_SINCE(1)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK
{
tabpage_T *tp = find_tab_by_handle(tabpage, err);
@@ -1159,15 +1174,14 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// - false: Client must cancel the paste.
Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
static bool draining = false;
bool cancel = false;
- if (phase < -1 || phase > 3) {
- api_set_error(err, kErrorTypeValidation, "Invalid phase: %" PRId64, phase);
+ VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
- }
+ });
Array args = ARRAY_DICT_INIT;
Object rv = OBJECT_INIT;
if (phase == -1 || phase == 1) { // Start of paste-stream.
@@ -1231,23 +1245,20 @@ theend:
/// @param[out] err Error details, if any
void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
yankreg_T *reg = xcalloc(1, sizeof(yankreg_T));
- if (!prepare_yankreg_from_object(reg, type, lines.size)) {
- api_set_error(err, kErrorTypeValidation, "Invalid type: '%s'", type.data);
+ VALIDATE_S((prepare_yankreg_from_object(reg, type, lines.size)), "type", type.data, {
goto cleanup;
- }
+ });
if (lines.size == 0) {
goto cleanup; // Nothing to do.
}
for (size_t i = 0; i < lines.size; i++) {
- if (lines.items[i].type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation,
- "Invalid lines (expected array of strings)");
+ VALIDATE_T("line", kObjectTypeString, lines.items[i].type, {
goto cleanup;
- }
+ });
String line = lines.items[i].data.string;
reg->y_array[i] = xmemdupz(line.data, line.size);
memchrsub(reg->y_array[i], NUL, NL, line.size);
@@ -1255,14 +1266,12 @@ void nvim_put(ArrayOf(String) lines, String type, Boolean after, Boolean follow,
finish_yankreg_from_object(reg, false);
- TRY_WRAP({
- try_start();
+ TRY_WRAP(err, {
bool VIsual_was_active = VIsual_active;
msg_silent++; // Avoid "N more lines" message.
do_put(0, reg, after ? FORWARD : BACKWARD, 1, follow ? PUT_CURSEND : 0);
msg_silent--;
VIsual_active = VIsual_was_active;
- try_end(err);
});
cleanup:
@@ -1291,9 +1300,9 @@ void nvim_subscribe(uint64_t channel_id, String event)
void nvim_unsubscribe(uint64_t channel_id, String event)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
- size_t length = (event.size < METHOD_MAXLEN ?
- event.size :
- METHOD_MAXLEN);
+ size_t length = (event.size < METHOD_MAXLEN
+ ? event.size
+ : METHOD_MAXLEN);
char e[METHOD_MAXLEN + 1];
memcpy(e, event.data, length);
e[length] = NUL;
@@ -1304,10 +1313,11 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
/// "#rrggbb" hexadecimal string.
///
/// Example:
-/// <pre>vim
-/// :echo nvim_get_color_by_name("Pink")
-/// :echo nvim_get_color_by_name("#cbcbcb")
-/// </pre>
+///
+/// ```vim
+/// :echo nvim_get_color_by_name("Pink")
+/// :echo nvim_get_color_by_name("#cbcbcb")
+/// ```
///
/// @param name Color name or "#rrggbb" string
/// @return 24-bit RGB value, or -1 for invalid argument.
@@ -1348,11 +1358,8 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
- if (opts->types.type == kObjectTypeArray) {
- types = opts->types.data.array;
- } else if (opts->types.type != kObjectTypeNil) {
- api_set_error(err, kErrorTypeValidation, "invalid value for key: types");
- return (Dictionary)ARRAY_DICT_INIT;
+ if (HAS_KEY(opts, context, types)) {
+ types = opts->types;
}
int int_types = types.size > 0 ? 0 : kCtxAll;
@@ -1373,8 +1380,9 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
} else if (strequal(s, "funcs")) {
int_types |= kCtxFuncs;
} else {
- api_set_error(err, kErrorTypeValidation, "unexpected type: %s", s);
- return (Dictionary)ARRAY_DICT_INIT;
+ VALIDATE_S(false, "type", s, {
+ return (Dictionary)ARRAY_DICT_INIT;
+ });
}
}
}
@@ -1390,7 +1398,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Error *err)
/// Sets the current editor state from the given |context| map.
///
/// @param dict |Context| map.
-Object nvim_load_context(Dictionary dict)
+Object nvim_load_context(Dictionary dict, Error *err)
FUNC_API_SINCE(6)
{
Context ctx = CONTEXT_INIT;
@@ -1398,8 +1406,8 @@ Object nvim_load_context(Dictionary dict)
int save_did_emsg = did_emsg;
did_emsg = false;
- ctx_from_dict(dict, &ctx);
- if (!did_emsg) {
+ ctx_from_dict(dict, &ctx, err);
+ if (!ERROR_SET(err)) {
ctx_restore(&ctx, kCtxAll);
}
@@ -1421,7 +1429,7 @@ Dictionary nvim_get_mode(void)
get_mode(modestr);
bool blocked = input_blocking();
- PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr)));
+ PUT(rv, "mode", CSTR_TO_OBJ(modestr));
PUT(rv, "blocking", BOOLEAN_OBJ(blocked));
return rv;
@@ -1446,29 +1454,31 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode)
/// Empty {rhs} is |<Nop>|. |keycodes| are replaced as usual.
///
/// Example:
-/// <pre>vim
-/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
-/// </pre>
+///
+/// ```vim
+/// call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true})
+/// ```
///
/// is equivalent to:
-/// <pre>vim
-/// nmap <nowait> <Space><NL> <Nop>
-/// </pre>
+///
+/// ```vim
+/// nmap <nowait> <Space><NL> <Nop>
+/// ```
///
/// @param channel_id
/// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …)
/// or "!" for |:map!|, or empty string for |:map|.
+/// "ia", "ca" or "!a" for abbreviation in Insert mode, Cmdline mode, or both, respectively
/// @param lhs Left-hand-side |{lhs}| of the mapping.
/// @param rhs Right-hand-side |{rhs}| of the mapping.
-/// @param opts Optional parameters map: keys are |:map-arguments|, values are booleans (default
-/// false). Accepts all |:map-arguments| as keys excluding |<buffer>| but including
-/// |:noremap| and "desc". Unknown key is an error.
-/// "desc" can be used to give a description to the mapping.
-/// When called from Lua, also accepts a "callback" key that takes a Lua function to
-/// call when the mapping is executed.
-/// When "expr" is true, "replace_keycodes" (boolean) can be used to replace keycodes
-/// in the resulting string (see |nvim_replace_termcodes()|), and a Lua callback
-/// returning `nil` is equivalent to returning an empty string.
+/// @param opts Optional parameters map: Accepts all |:map-arguments| as keys except |<buffer>|,
+/// values are booleans (default false). Also:
+/// - "noremap" disables |recursive_mapping|, like |:noremap|
+/// - "desc" human-readable description.
+/// - "callback" Lua function called in place of {rhs}.
+/// - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the
+/// resulting string (see |nvim_replace_termcodes()|). Returning nil from the Lua
+/// "callback" is equivalent to returning an empty string.
/// @param[out] err Error details, if any.
void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts,
Error *err)
@@ -1527,7 +1537,10 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
/// - "commit" hash or similar identifier of commit
/// @param type Must be one of the following values. Client libraries should
/// default to "remote" unless overridden by the user.
-/// - "remote" remote client connected to Nvim.
+/// - "remote" remote client connected "Nvim flavored" MessagePack-RPC (responses
+/// must be in reverse order of requests). |msgpack-rpc|
+/// - "msgpack-rpc" remote client connected to Nvim via fully MessagePack-RPC
+/// compliant protocol.
/// - "ui" gui frontend
/// - "embedder" application using Nvim as a component (for example,
/// IDE/editor implementing a vim mode).
@@ -1651,34 +1664,20 @@ Array nvim_call_atomic(uint64_t channel_id, Array calls, Arena *arena, Error *er
size_t i; // also used for freeing the variables
for (i = 0; i < calls.size; i++) {
- if (calls.items[i].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays");
+ VALIDATE_T("'calls' item", kObjectTypeArray, calls.items[i].type, {
goto theend;
- }
+ });
Array call = calls.items[i].data.array;
- if (call.size != 2) {
- api_set_error(err,
- kErrorTypeValidation,
- "Items in calls array must be arrays of size 2");
+ VALIDATE_EXP((call.size == 2), "'calls' item", "2-item Array", NULL, {
goto theend;
- }
-
- if (call.items[0].type != kObjectTypeString) {
- api_set_error(err,
- kErrorTypeValidation,
- "Name must be String");
+ });
+ VALIDATE_T("name", kObjectTypeString, call.items[0].type, {
goto theend;
- }
+ });
String name = call.items[0].data.string;
-
- if (call.items[1].type != kObjectTypeArray) {
- api_set_error(err,
- kErrorTypeValidation,
- "Args must be Array");
+ VALIDATE_T("call args", kObjectTypeArray, call.items[1].type, {
goto theend;
- }
+ });
Array args = call.items[1].data.array;
MsgpackRpcRequestHandler handler =
@@ -1726,34 +1725,44 @@ theend:
///
/// @param message Message to write
/// @param to_err true: message is an error (uses `emsg` instead of `msg`)
-static void write_msg(String message, bool to_err)
+/// @param writeln Append a trailing newline
+static void write_msg(String message, bool to_err, bool writeln)
{
static StringBuilder out_line_buf = KV_INITIAL_VALUE;
static StringBuilder err_line_buf = KV_INITIAL_VALUE;
+ StringBuilder *line_buf = to_err ? &err_line_buf : &out_line_buf;
-#define PUSH_CHAR(i, line_buf, msg) \
- if (kv_max(line_buf) == 0) { \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
+#define PUSH_CHAR(c) \
+ if (kv_max(*line_buf) == 0) { \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
} \
- if (message.data[i] == NL) { \
- kv_push(line_buf, NUL); \
- msg(line_buf.items); \
- kv_drop(line_buf, kv_size(line_buf)); \
- kv_resize(line_buf, LINE_BUFFER_MIN_SIZE); \
- continue; \
- } \
- kv_push(line_buf, message.data[i]);
+ if (c == NL) { \
+ kv_push(*line_buf, NUL); \
+ if (to_err) { \
+ emsg(line_buf->items); \
+ } else { \
+ msg(line_buf->items, 0); \
+ } \
+ if (msg_silent == 0) { \
+ msg_didout = true; \
+ } \
+ kv_drop(*line_buf, kv_size(*line_buf)); \
+ kv_resize(*line_buf, LINE_BUFFER_MIN_SIZE); \
+ } else if (c == NUL) { \
+ kv_push(*line_buf, NL); \
+ } else { \
+ kv_push(*line_buf, c); \
+ }
no_wait_return++;
for (uint32_t i = 0; i < message.size; i++) {
if (got_int) {
break;
}
- if (to_err) {
- PUSH_CHAR(i, err_line_buf, emsg);
- } else {
- PUSH_CHAR(i, out_line_buf, msg);
- }
+ PUSH_CHAR(message.data[i]);
+ }
+ if (writeln) {
+ PUSH_CHAR(NL);
}
no_wait_return--;
msg_end();
@@ -1850,10 +1859,9 @@ Array nvim_get_proc_children(Integer pid, Error *err)
Array rvobj = ARRAY_DICT_INIT;
int *proc_list = NULL;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
goto end;
- }
+ });
size_t proc_count;
int rv = os_proc_children((int)pid, &proc_list, &proc_count);
@@ -1892,10 +1900,10 @@ Object nvim_get_proc(Integer pid, Error *err)
rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT;
rvobj.type = kObjectTypeDictionary;
- if (pid <= 0 || pid > INT_MAX) {
- api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid);
+ VALIDATE_INT((pid > 0 && pid <= INT_MAX), "pid", pid, {
return NIL;
- }
+ });
+
#ifdef MSWIN
rvobj.data.dictionary = os_proc_info((int)pid);
if (rvobj.data.dictionary.size == 0) { // Process not found.
@@ -1937,10 +1945,9 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, Di
Error *err)
FUNC_API_SINCE(6)
{
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
+ VALIDATE((opts.size == 0), "%s", "opts dict isn't empty", {
return;
- }
+ });
if (finish) {
insert = true;
@@ -1961,13 +1968,10 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
g = &pum_grid;
} else if (grid > 1) {
win_T *wp = get_win_by_grid_handle((handle_T)grid);
- if (wp != NULL && wp->w_grid_alloc.chars != NULL) {
- g = &wp->w_grid_alloc;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "No grid with the given handle");
+ VALIDATE_INT((wp != NULL && wp->w_grid_alloc.chars != NULL), "grid handle", grid, {
return ret;
- }
+ });
+ g = &wp->w_grid_alloc;
}
if (row < 0 || row >= g->rows
@@ -1976,7 +1980,9 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
}
ret = arena_array(arena, 3);
size_t off = g->line_offset[(size_t)row] + (size_t)col;
- ADD_C(ret, STRING_OBJ(cstr_as_string((char *)g->chars[off])));
+ char *sc_buf = arena_alloc(arena, MAX_SCHAR_SIZE, false);
+ schar_get(sc_buf, g->chars[off]);
+ ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
@@ -1992,6 +1998,14 @@ void nvim__screenshot(String path)
ui_call_screenshot(path);
}
+/// For testing. The condition in schar_cache_clear_if_full is hard to
+/// reach, so this function can be used to force a cache clear in a test.
+void nvim__invalidate_glyph_cache(void)
+{
+ schar_cache_clear();
+ must_redraw = UPD_CLEAR;
+}
+
Object nvim__unpack(String str, Error *err)
FUNC_API_FAST
{
@@ -2000,7 +2014,7 @@ Object nvim__unpack(String str, Error *err)
/// Deletes an uppercase/file named mark. See |mark-motions|.
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @return true if the mark was deleted, else false.
/// @see |nvim_buf_del_mark()|
@@ -2009,29 +2023,26 @@ Boolean nvim_del_mark(String name, Error *err)
FUNC_API_SINCE(8)
{
bool res = false;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return res;
- }
+ });
// Only allow file/uppercase marks
// TODO(muniter): Refactor this ASCII_ISUPPER macro to a proper function
- if (ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)) {
- res = set_mark(NULL, name, 0, 0, err);
- } else {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
- }
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
+ return res;
+ });
+ res = set_mark(NULL, name, 0, 0, err);
return res;
}
-/// Return a tuple (row, col, buffer, buffername) representing the position of
-/// the uppercase/file named mark. See |mark-motions|.
+/// Returns a `(row, col, buffer, buffername)` tuple representing the position
+/// of the uppercase/file named mark. "End of line" column position is returned
+/// as |v:maxcol| (big number). See |mark-motions|.
///
/// Marks are (1,0)-indexed. |api-indexing|
///
-/// @note fails with error if a lowercase or buffer local named mark is used.
+/// @note Lowercase name (or other buffer-local mark) is an error.
/// @param name Mark name
/// @param opts Optional parameters. Reserved for future use.
/// @return 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is
@@ -2043,16 +2054,13 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
{
Array rv = ARRAY_DICT_INIT;
- if (name.size != 1) {
- api_set_error(err, kErrorTypeValidation,
- "Mark name must be a single character");
+ VALIDATE_S((name.size == 1), "mark name (must be a single char)", name.data, {
return rv;
- } else if (!(ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data))) {
- api_set_error(err, kErrorTypeValidation,
- "Only file/uppercase marks allowed, invalid mark name: '%c'",
- *name.data);
+ });
+ VALIDATE_S((ASCII_ISUPPER(*name.data) || ascii_isdigit(*name.data)),
+ "mark name (must be file/uppercase)", name.data, {
return rv;
- }
+ });
xfmark_T *mark = mark_get_global(false, *name.data); // false avoids loading the mark buffer
pos_T pos = mark->fmark.mark;
@@ -2092,7 +2100,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
ADD(rv, INTEGER_OBJ(row));
ADD(rv, INTEGER_OBJ(col));
ADD(rv, INTEGER_OBJ(bufnr));
- ADD(rv, STRING_OBJ(cstr_to_string(filename)));
+ ADD(rv, CSTR_TO_OBJ(filename));
if (allocated) {
xfree(filename);
@@ -2113,6 +2121,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err)
/// - use_winbar: (boolean) Evaluate winbar instead of statusline.
/// - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid}
/// is ignored. Mutually exclusive with {use_winbar}.
+/// - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline.
///
/// @param[out] err Error details, if any.
/// @return Dictionary containing statusline information, with these keys:
@@ -2130,106 +2139,117 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
int maxwidth;
int fillchar = 0;
+ int statuscol_lnum = 0;
Window window = 0;
- bool use_winbar = false;
- bool use_tabline = false;
- bool highlights = false;
if (str.size < 2 || memcmp(str.data, "%!", 2) != 0) {
const char *const errmsg = check_stl_option(str.data);
- if (errmsg) {
- api_set_error(err, kErrorTypeValidation, "%s", errmsg);
+ VALIDATE(!errmsg, "%s", errmsg, {
return result;
- }
+ });
}
- if (HAS_KEY(opts->winid)) {
- if (opts->winid.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "winid must be an integer");
- return result;
- }
-
- window = (Window)opts->winid.data.integer;
+ if (HAS_KEY(opts, eval_statusline, winid)) {
+ window = opts->winid;
}
- if (HAS_KEY(opts->fillchar)) {
- if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0
- || ((size_t)utf_ptr2len(opts->fillchar.data.string.data)
- != opts->fillchar.data.string.size)) {
- api_set_error(err, kErrorTypeValidation, "fillchar must be a single character");
+ if (HAS_KEY(opts, eval_statusline, fillchar)) {
+ VALIDATE_EXP((*opts->fillchar.data != 0
+ && ((size_t)utf_ptr2len(opts->fillchar.data) == opts->fillchar.size)),
+ "fillchar", "single character", NULL, {
return result;
- }
- fillchar = utf_ptr2char(opts->fillchar.data.string.data);
+ });
+ fillchar = utf_ptr2char(opts->fillchar.data);
}
- if (HAS_KEY(opts->highlights)) {
- highlights = api_object_to_bool(opts->highlights, "highlights", false, err);
- if (ERROR_SET(err)) {
- return result;
- }
- }
- if (HAS_KEY(opts->use_winbar)) {
- use_winbar = api_object_to_bool(opts->use_winbar, "use_winbar", false, err);
+ int use_bools = (int)opts->use_winbar + (int)opts->use_tabline;
- if (ERROR_SET(err)) {
- return result;
- }
+ win_T *wp = opts->use_tabline ? curwin : find_window_by_handle(window, err);
+ if (wp == NULL) {
+ api_set_error(err, kErrorTypeException, "unknown winid %d", window);
+ return result;
}
- if (HAS_KEY(opts->use_tabline)) {
- use_tabline = api_object_to_bool(opts->use_tabline, "use_tabline", false, err);
- if (ERROR_SET(err)) {
+ if (HAS_KEY(opts, eval_statusline, use_statuscol_lnum)) {
+ statuscol_lnum = (int)opts->use_statuscol_lnum;
+ VALIDATE_RANGE(statuscol_lnum > 0 && statuscol_lnum <= wp->w_buffer->b_ml.ml_line_count,
+ "use_statuscol_lnum", {
return result;
- }
+ });
+ use_bools++;
}
- if (use_winbar && use_tabline) {
- api_set_error(err, kErrorTypeValidation, "use_winbar and use_tabline are mutually exclusive");
+ VALIDATE(use_bools <= 1, "%s",
+ "Can only use one of 'use_winbar', 'use_tabline' and 'use_statuscol_lnum'", {
return result;
- }
+ });
- win_T *wp, *ewp;
+ int stc_hl_id = 0;
+ statuscol_T statuscol = { 0 };
+ SignTextAttrs sattrs[SIGN_SHOW_MAX] = { 0 };
- if (use_tabline) {
- wp = NULL;
- ewp = curwin;
+ if (opts->use_tabline) {
fillchar = ' ';
} else {
- wp = find_window_by_handle(window, err);
- if (wp == NULL) {
- api_set_error(err, kErrorTypeException, "unknown winid %d", window);
- return result;
- }
- ewp = wp;
-
if (fillchar == 0) {
- if (use_winbar) {
+ if (opts->use_winbar) {
fillchar = wp->w_p_fcs_chars.wbr;
} else {
int attr;
fillchar = fillchar_status(&attr, wp);
}
}
- }
+ if (statuscol_lnum) {
+ int line_id = 0;
+ int cul_id = 0;
+ int num_id = 0;
+ linenr_T lnum = statuscol_lnum;
+ wp->w_scwidth = win_signcol_count(wp);
+ decor_redraw_signs(wp, wp->w_buffer, lnum - 1, sattrs, &line_id, &cul_id, &num_id);
+
+ statuscol.sattrs = sattrs;
+ statuscol.foldinfo = fold_info(wp, lnum);
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+
+ if (wp->w_p_cul) {
+ if (statuscol.foldinfo.fi_level != 0 && statuscol.foldinfo.fi_lines > 0) {
+ wp->w_cursorline = statuscol.foldinfo.fi_lnum;
+ }
+ statuscol.use_cul = lnum == wp->w_cursorline && (wp->w_p_culopt_flags & CULOPT_NBR);
+ }
- if (HAS_KEY(opts->maxwidth)) {
- if (opts->maxwidth.type != kObjectTypeInteger) {
- api_set_error(err, kErrorTypeValidation, "maxwidth must be an integer");
- return result;
+ statuscol.sign_cul_id = statuscol.use_cul ? cul_id : 0;
+ if (num_id) {
+ stc_hl_id = num_id;
+ } else if (statuscol.use_cul) {
+ stc_hl_id = HLF_CLN + 1;
+ } else if (wp->w_p_rnu) {
+ stc_hl_id = (lnum < wp->w_cursor.lnum ? HLF_LNA : HLF_LNB) + 1;
+ } else {
+ stc_hl_id = HLF_N + 1;
+ }
+
+ set_vim_var_nr(VV_LNUM, lnum);
+ set_vim_var_nr(VV_RELNUM, labs(get_cursor_rel_lnum(wp, lnum)));
+ set_vim_var_nr(VV_VIRTNUM, 0);
}
+ }
- maxwidth = (int)opts->maxwidth.data.integer;
+ if (HAS_KEY(opts, eval_statusline, maxwidth)) {
+ maxwidth = (int)opts->maxwidth;
} else {
- maxwidth = (use_tabline || (!use_winbar && global_stl_height() > 0)) ? Columns : wp->w_width;
+ maxwidth = statuscol_lnum ? win_col_off(wp)
+ : (opts->use_tabline
+ || (!opts->use_winbar
+ && global_stl_height() > 0)) ? Columns : wp->w_width;
}
char buf[MAXPATHL];
stl_hlrec_t *hltab;
- stl_hlrec_t **hltab_ptr = highlights ? &hltab : NULL;
// Temporarily reset 'cursorbind' to prevent side effects from moving the cursor away and back.
- int p_crb_save = ewp->w_p_crb;
- ewp->w_p_crb = false;
+ int p_crb_save = wp->w_p_crb;
+ wp->w_p_crb = false;
- int width = build_stl_str_hl(ewp,
+ int width = build_stl_str_hl(wp,
buf,
sizeof(buf),
str.data,
@@ -2237,25 +2257,25 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
0,
fillchar,
maxwidth,
- hltab_ptr,
+ opts->highlights ? &hltab : NULL,
NULL,
- NULL);
+ statuscol_lnum ? &statuscol : NULL);
PUT(result, "width", INTEGER_OBJ(width));
// Restore original value of 'cursorbind'
- ewp->w_p_crb = p_crb_save;
+ wp->w_p_crb = p_crb_save;
- if (highlights) {
+ if (opts->highlights) {
Array hl_values = ARRAY_DICT_INIT;
const char *grpname;
- char user_group[6];
+ char user_group[15]; // strlen("User") + strlen("2147483647") + NUL
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
Dictionary hl_info = ARRAY_DICT_INIT;
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
PUT(hl_info, "start", INTEGER_OBJ(0));
PUT(hl_info, "group", CSTR_TO_OBJ(grpname));
@@ -2266,10 +2286,10 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
Dictionary hl_info = ARRAY_DICT_INIT;
- PUT(hl_info, "start", INTEGER_OBJ((char *)sp->start - buf));
+ PUT(hl_info, "start", INTEGER_OBJ(sp->start - buf));
if (sp->userhl == 0) {
- grpname = get_default_stl_hl(wp, use_winbar);
+ grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp, opts->use_winbar, stc_hl_id);
} else if (sp->userhl < 0) {
grpname = syn_id2name(-sp->userhl);
} else {
@@ -2281,7 +2301,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error *
}
PUT(result, "highlights", ARRAY_OBJ(hl_values));
}
- PUT(result, "str", CSTR_TO_OBJ((char *)buf));
+ PUT(result, "str", CSTR_TO_OBJ(buf));
return result;
}
diff --git a/src/nvim/api/vim.h b/src/nvim/api/vim.h
index de56c67665..b620158751 100644
--- a/src/nvim/api/vim.h
+++ b/src/nvim/api/vim.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIM_H
-#define NVIM_API_VIM_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vim.h.generated.h"
#endif
-#endif // NVIM_API_VIM_H
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index af1b23b712..c75bf21572 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
@@ -8,23 +5,22 @@
#include <string.h>
#include "klib/kvec.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
#include "nvim/runtime.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -38,39 +34,57 @@
/// Unlike |nvim_command()| this function supports heredocs, script-scope (s:),
/// etc.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @see |execute()|
/// @see |nvim_command()|
/// @see |nvim_cmd()|
///
/// @param src Vimscript code
-/// @param output Capture and return all (non-error, non-shell |:!|) output
+/// @param opts Optional parameters.
+/// - output: (boolean, default false) Whether to capture and return
+/// all (non-error, non-shell |:!|) output.
/// @param[out] err Error details (Vim error), if any
-/// @return Output (non-error, non-shell |:!|) if `output` is true,
-/// else empty string.
-String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
- FUNC_API_SINCE(7)
+/// @return Dictionary containing information about execution, with these keys:
+/// - output: (string|nil) Output if `opts.output` is true.
+Dictionary nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
+ FUNC_API_SINCE(11)
+{
+ Dictionary result = ARRAY_DICT_INIT;
+
+ String output = exec_impl(channel_id, src, opts, err);
+ if (ERROR_SET(err)) {
+ return result;
+ }
+
+ if (opts->output) {
+ PUT(result, "output", STRING_OBJ(output));
+ }
+
+ return result;
+}
+
+String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
{
const int save_msg_silent = msg_silent;
garray_T *const save_capture_ga = capture_ga;
const int save_msg_col = msg_col;
garray_T capture_local;
- if (output) {
+ if (opts->output) {
ga_init(&capture_local, 1, 80);
capture_ga = &capture_local;
}
try_start();
- if (output) {
+ if (opts->output) {
msg_silent++;
msg_col = 0; // prevent leading spaces
}
const sctx_T save_current_sctx = api_set_sctx(channel_id);
- do_source_str(src.data, "nvim_exec()");
- if (output) {
+ do_source_str(src.data, "nvim_exec2()");
+ if (opts->output) {
capture_ga = save_capture_ga;
msg_silent = save_msg_silent;
// Put msg_col back where it was, since nothing should have been written.
@@ -84,7 +98,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
goto theend;
}
- if (output && capture_local.ga_len > 1) {
+ if (opts->output && capture_local.ga_len > 1) {
String s = (String){
.data = capture_local.ga_data,
.size = (size_t)capture_local.ga_len,
@@ -98,7 +112,7 @@ String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err)
return s; // Caller will free the memory.
}
theend:
- if (output) {
+ if (opts->output) {
ga_clear(&capture_local);
}
return (String)STRING_INIT;
@@ -106,10 +120,10 @@ theend:
/// Executes an Ex command.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script
-/// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured
+/// Prefer using |nvim_cmd()| or |nvim_exec2()| over this. To evaluate multiple lines of Vim script
+/// or an Ex command directly, use |nvim_exec2()|. To construct an Ex command using a structured
/// format and then execute it, use |nvim_cmd()|. To modify an Ex command before evaluating it, use
/// |nvim_parse_cmd()| in conjunction with |nvim_cmd()|.
///
@@ -123,12 +137,12 @@ void nvim_command(String command, Error *err)
try_end(err);
}
-/// Evaluates a VimL |expression|.
+/// Evaluates a Vimscript |expression|.
/// Dictionaries and Lists are recursively expanded.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param expr VimL expression string
+/// @param expr Vimscript expression string
/// @param[out] err Error details, if any
/// @return Evaluation result or expanded object
Object nvim_eval(String expr, Error *err)
@@ -137,39 +151,42 @@ Object nvim_eval(String expr, Error *err)
static int recursive = 0; // recursion depth
Object rv = OBJECT_INIT;
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
- typval_T rettv;
- int ok = eval0(expr.data, &rettv, NULL, true);
+ recursive++;
- if (!try_end(err)) {
- if (ok == FAIL) {
- // Should never happen, try_end() should get the error. #8371
- api_set_error(err, kErrorTypeException,
- "Failed to evaluate expression: '%.*s'", 256, expr.data);
- } else {
- rv = vim_to_object(&rettv);
- }
- }
+ typval_T rettv;
+ int ok;
- tv_clear(&rettv);
- recursive--;
+ TRY_WRAP(err, {
+ ok = eval0(expr.data, &rettv, NULL, &EVALARG_EVALUATE);
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
});
+ if (!ERROR_SET(err)) {
+ if (ok == FAIL) {
+ // Should never happen, try_end() (in TRY_WRAP) should get the error. #8371
+ api_set_error(err, kErrorTypeException,
+ "Failed to evaluate expression: '%.*s'", 256, expr.data);
+ } else {
+ rv = vim_to_object(&rettv);
+ }
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
return rv;
}
-/// Calls a VimL function.
+/// Calls a Vimscript function.
///
/// @param fn Function name
/// @param args Function arguments
@@ -196,34 +213,37 @@ static Object _call_function(String fn, Array args, dict_T *self, Error *err)
}
}
- TRY_WRAP({
- // Initialize `force_abort` and `suppress_errthrow` at the top level.
- if (!recursive) {
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- // `did_emsg` is set by emsg(), which cancels execution.
- did_emsg = false;
- }
- recursive++;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_firstline = curwin->w_cursor.lnum;
- funcexe.fe_lastline = curwin->w_cursor.lnum;
- funcexe.fe_evaluate = true;
- funcexe.fe_selfdict = self;
+ // Initialize `force_abort` and `suppress_errthrow` at the top level.
+ if (!recursive) {
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ // `did_emsg` is set by emsg(), which cancels execution.
+ did_emsg = false;
+ }
+ recursive++;
+
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_firstline = curwin->w_cursor.lnum;
+ funcexe.fe_lastline = curwin->w_cursor.lnum;
+ funcexe.fe_evaluate = true;
+ funcexe.fe_selfdict = self;
+
+ TRY_WRAP(err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (see above) to capture abort-causing non-exception errors.
(void)call_func(fn.data, (int)fn.size, &rettv, (int)args.size,
vim_args, &funcexe);
- if (!try_end(err)) {
- rv = vim_to_object(&rettv);
- }
- tv_clear(&rettv);
- recursive--;
});
+ if (!ERROR_SET(err)) {
+ rv = vim_to_object(&rettv);
+ }
+
+ tv_clear(&rettv);
+ recursive--;
+
free_vim_args:
while (i > 0) {
tv_clear(&vim_args[--i]);
@@ -232,9 +252,9 @@ free_vim_args:
return rv;
}
-/// Calls a VimL function with the given arguments.
+/// Calls a Vimscript function with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
/// @param fn Function to call
/// @param args Function arguments packed in an Array
@@ -246,12 +266,12 @@ Object nvim_call_function(String fn, Array args, Error *err)
return _call_function(fn, args, NULL, err);
}
-/// Calls a VimL |Dictionary-function| with the given arguments.
+/// Calls a Vimscript |Dictionary-function| with the given arguments.
///
-/// On execution error: fails with VimL error, updates v:errmsg.
+/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param dict Dictionary, or String evaluating to a VimL |self| dict
-/// @param fn Name of the function defined on the VimL dict
+/// @param dict Dictionary, or String evaluating to a Vimscript |self| dict
+/// @param fn Name of the function defined on the Vimscript dict
/// @param args Function arguments packed in an Array
/// @param[out] err Error details, if any
/// @return Result of the function call
@@ -265,10 +285,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Error *err)
switch (dict.type) {
case kObjectTypeString:
try_start();
- if (eval0(dict.data.string.data, &rettv, NULL, true) == FAIL) {
+ if (eval0(dict.data.string.data, &rettv, NULL, &EVALARG_EVALUATE) == FAIL) {
api_set_error(err, kErrorTypeException,
"Failed to evaluate dict expression");
}
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
if (try_end(err)) {
return rv;
}
@@ -336,7 +357,7 @@ typedef struct {
typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// @endcond
-/// Parse a VimL expression.
+/// Parse a Vimscript expression.
///
/// @param[in] expr Expression to parse. Always treated as a single line.
/// @param[in] flags Flags:
@@ -375,7 +396,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// stringified without "kExprNode" prefix.
/// - "start": a pair [line, column] describing where node is "started"
/// where "line" is always 0 (will not be 0 if you will be
-/// using nvim_parse_viml() on e.g. ":let", but that is not
+/// using this API on e.g. ":let", but that is not
/// present yet). Both elements are Integers.
/// - "len": “length” of the node. This and "start" are there for
/// debugging purposes primary (debugging parser and providing
@@ -475,7 +496,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
};
err_dict.items[0] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("message"),
- .value = STRING_OBJ(cstr_to_string(east.err.msg)),
+ .value = CSTR_TO_OBJ(east.err.msg),
};
if (east.err.arg == NULL) {
err_dict.items[1] = (KeyValuePair) {
@@ -512,7 +533,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line);
chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col);
chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col);
- chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group));
+ chunk_arr.items[3] = CSTR_TO_OBJ(chunk.group);
hl.items[i] = ARRAY_OBJ(chunk_arr);
}
ret.items[ret.size++] = (KeyValuePair) {
@@ -589,7 +610,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
kv_drop(ast_conv_stack, 1);
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("type"),
- .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])),
+ .value = CSTR_TO_OBJ(east_node_type_tab[node->type]),
};
Array start_array = {
.items = xmalloc(2 * sizeof(start_array.items[0])),
@@ -674,11 +695,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, E
case kExprNodeComparison:
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("cmp_type"),
- .value = STRING_OBJ(cstr_to_string(eltkn_cmp_type_tab[node->data.cmp.type])),
+ .value = CSTR_TO_OBJ(eltkn_cmp_type_tab[node->data.cmp.type]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("ccs_strategy"),
- .value = STRING_OBJ(cstr_to_string(ccs_tab[node->data.cmp.ccs])),
+ .value = CSTR_TO_OBJ(ccs_tab[node->data.cmp.ccs]),
};
ret_node->items[ret_node->size++] = (KeyValuePair) {
.key = STATIC_CSTR_TO_STRING("invert"),
diff --git a/src/nvim/api/vimscript.h b/src/nvim/api/vimscript.h
index be808b6b25..c315e932e9 100644
--- a/src/nvim/api/vimscript.h
+++ b/src/nvim/api/vimscript.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_VIMSCRIPT_H
-#define NVIM_API_VIMSCRIPT_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/vimscript.h.generated.h"
#endif
-#endif // NVIM_API_VIMSCRIPT_H
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 0ffeac1bff..4e23717dc6 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -1,30 +1,32 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/extmark.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/win_config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
-#include "nvim/extmark_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/ui.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.c.generated.h"
@@ -55,16 +57,19 @@
/// this should not be used to specify arbitrary WM screen positions.
///
/// Example (Lua): window-relative float
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', row=3, col=3, width=12, height=3})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', row=3, col=3, width=12, height=3})
+/// ```
///
/// Example (Lua): buffer-relative float (travels as buffer is scrolled)
-/// <pre>lua
-/// vim.api.nvim_open_win(0, false,
-/// {relative='win', width=12, height=3, bufpos={100,10}})
-/// </pre>
+///
+/// ```lua
+/// vim.api.nvim_open_win(0, false,
+/// {relative='win', width=12, height=3, bufpos={100,10}})
+/// })
+/// ```
///
/// @param buffer Buffer to display, or 0 for current buffer
/// @param enter Enter the window (make it the current window)
@@ -108,8 +113,8 @@
/// The default value for floats are 50. In general, values below 100 are
/// recommended, unless there is a good reason to overshadow builtin
/// elements.
-/// - style: Configure the appearance of the window. Currently only takes
-/// one non-empty value:
+/// - style: (optional) Configure the appearance of the window. Currently
+/// only supports one value:
/// - "minimal" Nvim will display the window with many UI options
/// disabled. This is useful when displaying a temporary
/// float where the text should not be edited. Disables
@@ -129,7 +134,7 @@
/// - "solid": Adds padding by a single whitespace cell.
/// - "shadow": A drop shadow effect by blending with the background.
/// - If it is an array, it should have a length of eight or any divisor of
-/// eight. The array will specifify the eight chars building up the border
+/// eight. The array will specify the eight chars building up the border
/// in a clockwise fashion starting with the top-left corner. As an
/// example, the double box style could be specified as
/// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
@@ -144,22 +149,41 @@
/// By default, `FloatBorder` highlight is used, which links to `WinSeparator`
/// when not defined. It could also be specified by character:
/// [ ["+", "MyCorner"], ["x", "MyBorder"] ].
-/// - title: Title (optional) in window border, String or list.
-/// List is [text, highlight] tuples. if is string the default
-/// highlight group is `FloatTitle`.
-/// - title_pos: Title position must set with title option.
-/// value can be of `left` `center` `right` default is left.
+/// - title: Title (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatTitle`.
+/// - title_pos: Title position. Must be set with `title` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
+/// - footer: Footer (optional) in window border, string or list.
+/// List should consist of `[text, highlight]` tuples.
+/// If string, the default highlight group is `FloatFooter`.
+/// - footer_pos: Footer position. Must be set with `footer` option.
+/// Value can be one of "left", "center", or "right".
+/// Default is `"left"`.
/// - noautocmd: If true then no buffer-related autocommand events such as
/// |BufEnter|, |BufLeave| or |BufWinEnter| may fire from
/// calling this function.
+/// - fixed: If true when anchor is NW or SW, the float window
+/// would be kept fixed even if the window would be truncated.
+/// - hide: If true the floating window will be hidden.
///
/// @param[out] err Error details, if any
///
/// @return Window handle, or 0 on error
Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!buf) {
+ return 0;
+ }
+ if (cmdwin_type != 0 && (enter || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return 0;
+ }
+
FloatConfig fconfig = FLOAT_CONFIG_INIT;
if (!parse_float_config(config, &fconfig, false, true, err)) {
return 0;
@@ -173,7 +197,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E
}
// autocmds in win_enter or win_set_buf below may close the window
if (win_valid(wp) && buffer > 0) {
- win_set_buf(wp->handle, buffer, fconfig.noautocmd, err);
+ Boolean noautocmd = !enter || fconfig.noautocmd;
+ win_set_buf(wp, buf, noautocmd, err);
+ if (!fconfig.noautocmd) {
+ apply_autocmds(EVENT_WINNEW, NULL, NULL, false, buf);
+ }
}
if (!win_valid(wp)) {
api_set_error(err, kErrorTypeException, "Window was closed immediately");
@@ -222,12 +250,56 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err)
win_config_float(win, fconfig);
win->w_pos_changed = true;
}
- if (fconfig.style == kWinStyleMinimal) {
- win_set_minimal_style(win);
- didset_window_options(win, true);
+ if (HAS_KEY(config, float_config, style)) {
+ if (fconfig.style == kWinStyleMinimal) {
+ win_set_minimal_style(win);
+ didset_window_options(win, true);
+ }
}
}
+static Dictionary config_put_bordertext(Dictionary config, FloatConfig *fconfig,
+ BorderTextType bordertext_type)
+{
+ VirtText vt;
+ AlignTextPos align;
+ char *field_name;
+ char *field_pos_name;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ vt = fconfig->title_chunks;
+ align = fconfig->title_pos;
+ field_name = "title";
+ field_pos_name = "title_pos";
+ break;
+ case kBorderTextFooter:
+ vt = fconfig->footer_chunks;
+ align = fconfig->footer_pos;
+ field_name = "footer";
+ field_pos_name = "footer_pos";
+ break;
+ }
+
+ Array bordertext = virt_text_to_array(vt, true);
+ PUT(config, field_name, ARRAY_OBJ(bordertext));
+
+ char *pos;
+ switch (align) {
+ case kAlignLeft:
+ pos = "left";
+ break;
+ case kAlignCenter:
+ pos = "center";
+ break;
+ case kAlignRight:
+ pos = "right";
+ break;
+ }
+ PUT(config, field_pos_name, CSTR_TO_OBJ(pos));
+
+ return config;
+}
+
/// Gets window configuration.
///
/// The returned value may be given to |nvim_open_win()|.
@@ -251,6 +323,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "focusable", BOOLEAN_OBJ(config->focusable));
PUT(rv, "external", BOOLEAN_OBJ(config->external));
+ PUT(rv, "hide", BOOLEAN_OBJ(config->hide));
if (wp->w_floating) {
PUT(rv, "width", INTEGER_OBJ(config->width));
@@ -265,7 +338,7 @@ Dictionary nvim_win_get_config(Window window, Error *err)
PUT(rv, "bufpos", ARRAY_OBJ(pos));
}
}
- PUT(rv, "anchor", STRING_OBJ(cstr_to_string(float_anchor_str[config->anchor])));
+ PUT(rv, "anchor", CSTR_TO_OBJ(float_anchor_str[config->anchor]));
PUT(rv, "row", FLOAT_OBJ(config->row));
PUT(rv, "col", FLOAT_OBJ(config->col));
PUT(rv, "zindex", INTEGER_OBJ(config->zindex));
@@ -275,13 +348,13 @@ Dictionary nvim_win_get_config(Window window, Error *err)
for (size_t i = 0; i < 8; i++) {
Array tuple = ARRAY_DICT_INIT;
- String s = cstrn_to_string((const char *)config->border_chars[i], sizeof(schar_T));
+ String s = cstrn_to_string(config->border_chars[i], MAX_SCHAR_SIZE);
int hi_id = config->border_hl_ids[i];
char *hi_name = syn_id2name(hi_id);
if (hi_name[0]) {
ADD(tuple, STRING_OBJ(s));
- ADD(tuple, STRING_OBJ(cstr_to_string((const char *)hi_name)));
+ ADD(tuple, CSTR_TO_OBJ(hi_name));
ADD(border, ARRAY_OBJ(tuple));
} else {
ADD(border, STRING_OBJ(s));
@@ -289,34 +362,17 @@ Dictionary nvim_win_get_config(Window window, Error *err)
}
PUT(rv, "border", ARRAY_OBJ(border));
if (config->title) {
- Array titles = ARRAY_DICT_INIT;
- VirtText title_datas = config->title_chunks;
- for (size_t i = 0; i < title_datas.size; i++) {
- Array tuple = ARRAY_DICT_INIT;
- ADD(tuple, CSTR_TO_OBJ((const char *)title_datas.items[i].text));
- if (title_datas.items[i].hl_id > 0) {
- ADD(tuple,
- STRING_OBJ(cstr_to_string((const char *)syn_id2name(title_datas.items[i].hl_id))));
- }
- ADD(titles, ARRAY_OBJ(tuple));
- }
- PUT(rv, "title", ARRAY_OBJ(titles));
- char *title_pos;
- if (config->title_pos == kAlignLeft) {
- title_pos = "left";
- } else if (config->title_pos == kAlignCenter) {
- title_pos = "center";
- } else {
- title_pos = "right";
- }
- PUT(rv, "title_pos", CSTR_TO_OBJ(title_pos));
+ rv = config_put_bordertext(rv, config, kBorderTextTitle);
+ }
+ if (config->footer) {
+ rv = config_put_bordertext(rv, config, kBorderTextFooter);
}
}
}
const char *rel = (wp->w_floating && !config->external
? float_relative_str[config->relative] : "");
- PUT(rv, "relative", STRING_OBJ(cstr_to_string(rel)));
+ PUT(rv, "relative", CSTR_TO_OBJ(rel));
return rv;
}
@@ -370,68 +426,91 @@ static bool parse_float_bufpos(Array bufpos, lpos_T *out)
return true;
}
-static void parse_border_title(Object title, Object title_pos, FloatConfig *fconfig, Error *err)
+static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!parse_title_pos(title_pos, fconfig, err)) {
- return;
- }
-
- if (title.type == kObjectTypeString) {
- if (title.data.string.size == 0) {
- fconfig->title = false;
+ bool *is_present;
+ VirtText *chunks;
+ int *width;
+ int default_hl_id;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ is_present = &fconfig->title;
+ chunks = &fconfig->title_chunks;
+ width = &fconfig->title_width;
+ default_hl_id = syn_check_group(S_LEN("FloatTitle"));
+ break;
+ case kBorderTextFooter:
+ is_present = &fconfig->footer;
+ chunks = &fconfig->footer_chunks;
+ width = &fconfig->footer_width;
+ default_hl_id = syn_check_group(S_LEN("FloatFooter"));
+ break;
+ }
+
+ if (bordertext.type == kObjectTypeString) {
+ if (bordertext.data.string.size == 0) {
+ *is_present = false;
return;
}
- int hl_id = syn_check_group(S_LEN("FloatTitle"));
- kv_push(fconfig->title_chunks, ((VirtTextChunk){ .text = xstrdup(title.data.string.data),
- .hl_id = hl_id }));
- fconfig->title_width = (int)mb_string2cells(title.data.string.data);
- fconfig->title = true;
+ kv_push(*chunks, ((VirtTextChunk){ .text = xstrdup(bordertext.data.string.data),
+ .hl_id = default_hl_id }));
+ *width = (int)mb_string2cells(bordertext.data.string.data);
+ *is_present = true;
return;
}
- if (title.type != kObjectTypeArray) {
+ if (bordertext.type != kObjectTypeArray) {
api_set_error(err, kErrorTypeValidation, "title must be string or array");
return;
}
- if (title.data.array.size == 0) {
+ if (bordertext.data.array.size == 0) {
api_set_error(err, kErrorTypeValidation, "title cannot be an empty array");
return;
}
- fconfig->title_width = 0;
- fconfig->title_chunks = parse_virt_text(title.data.array, err, &fconfig->title_width);
+ *width = 0;
+ *chunks = parse_virt_text(bordertext.data.array, err, width);
- fconfig->title = true;
+ *is_present = true;
}
-static bool parse_title_pos(Object title_pos, FloatConfig *fconfig, Error *err)
+static bool parse_bordertext_pos(String bordertext_pos, BorderTextType bordertext_type,
+ FloatConfig *fconfig, Error *err)
{
- if (!HAS_KEY(title_pos)) {
- fconfig->title_pos = kAlignLeft;
+ AlignTextPos *align;
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ align = &fconfig->title_pos;
+ break;
+ case kBorderTextFooter:
+ align = &fconfig->footer_pos;
+ break;
+ }
+
+ if (bordertext_pos.size == 0) {
+ *align = kAlignLeft;
return true;
}
- if (title_pos.type != kObjectTypeString) {
- api_set_error(err, kErrorTypeValidation, "title_pos must be string");
- return false;
- }
-
- if (title_pos.data.string.size == 0) {
- fconfig->title_pos = kAlignLeft;
- return true;
- }
-
- char *pos = title_pos.data.string.data;
+ char *pos = bordertext_pos.data;
if (strequal(pos, "left")) {
- fconfig->title_pos = kAlignLeft;
+ *align = kAlignLeft;
} else if (strequal(pos, "center")) {
- fconfig->title_pos = kAlignCenter;
+ *align = kAlignCenter;
} else if (strequal(pos, "right")) {
- fconfig->title_pos = kAlignRight;
+ *align = kAlignRight;
} else {
- api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ switch (bordertext_type) {
+ case kBorderTextTitle:
+ api_set_error(err, kErrorTypeValidation, "invalid title_pos value");
+ break;
+ case kBorderTextFooter:
+ api_set_error(err, kErrorTypeValidation, "invalid footer_pos value");
+ break;
+ }
return false;
}
return true;
@@ -441,7 +520,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{
struct {
const char *name;
- schar_T chars[8];
+ char chars[8][MAX_SCHAR_SIZE];
bool shadow_color;
} defaults[] = {
{ "double", { "╔", "═", "╗", "║", "╝", "═", "╚", "║" }, false },
@@ -452,7 +531,7 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
{ NULL, { { NUL } }, false },
};
- schar_T *chars = fconfig->border_chars;
+ char (*chars)[MAX_SCHAR_SIZE] = fconfig->border_chars;
int *hl_ids = fconfig->border_hl_ids;
fconfig->border = true;
@@ -521,8 +600,9 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
String str = style.data.string;
if (str.size == 0 || strequal(str.data, "none")) {
fconfig->border = false;
- // title does not work with border equal none
+ // border text does not work with border equal none
fconfig->title = false;
+ fconfig->footer = false;
return;
}
for (size_t i = 0; defaults[i].name; i++) {
@@ -549,110 +629,90 @@ static void parse_border_style(Object style, FloatConfig *fconfig, Error *err)
static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig, bool reconf,
bool new_win, Error *err)
{
+#define HAS_KEY_X(d, key) HAS_KEY(d, float_config, key)
bool has_relative = false, relative_is_win = false;
- if (config->relative.type == kObjectTypeString) {
- // ignore empty string, to match nvim_win_get_config
- if (config->relative.data.string.size > 0) {
- if (!parse_float_relative(config->relative.data.string, &fconfig->relative)) {
- api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
- return false;
- }
+ // ignore empty string, to match nvim_win_get_config
+ if (HAS_KEY_X(config, relative) && config->relative.size > 0) {
+ if (!parse_float_relative(config->relative, &fconfig->relative)) {
+ api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
+ return false;
+ }
- if (!(HAS_KEY(config->row) && HAS_KEY(config->col)) && !HAS_KEY(config->bufpos)) {
- api_set_error(err, kErrorTypeValidation,
- "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
- }
+ if (!(HAS_KEY_X(config, row) && HAS_KEY_X(config, col)) && !HAS_KEY_X(config, bufpos)) {
+ api_set_error(err, kErrorTypeValidation,
+ "'relative' requires 'row'/'col' or 'bufpos'");
+ return false;
+ }
- has_relative = true;
- fconfig->external = false;
- if (fconfig->relative == kFloatRelativeWindow) {
- relative_is_win = true;
- fconfig->bufpos.lnum = -1;
- }
+ has_relative = true;
+ fconfig->external = false;
+ if (fconfig->relative == kFloatRelativeWindow) {
+ relative_is_win = true;
+ fconfig->bufpos.lnum = -1;
}
- } else if (HAS_KEY(config->relative)) {
- api_set_error(err, kErrorTypeValidation, "'relative' key must be String");
- return false;
}
- if (config->anchor.type == kObjectTypeString) {
- if (!parse_float_anchor(config->anchor.data.string, &fconfig->anchor)) {
+ if (HAS_KEY_X(config, anchor)) {
+ if (!parse_float_anchor(config->anchor, &fconfig->anchor)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
return false;
}
- } else if (HAS_KEY(config->anchor)) {
- api_set_error(err, kErrorTypeValidation, "'anchor' key must be String");
- return false;
}
- if (HAS_KEY(config->row)) {
+ if (HAS_KEY_X(config, row)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'row'");
return false;
- } else if (config->row.type == kObjectTypeInteger) {
- fconfig->row = (double)config->row.data.integer;
- } else if (config->row.type == kObjectTypeFloat) {
- fconfig->row = config->row.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'row' key must be Integer or Float");
- return false;
}
+ fconfig->row = config->row;
}
- if (HAS_KEY(config->col)) {
+ if (HAS_KEY_X(config, col)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'col'");
return false;
- } else if (config->col.type == kObjectTypeInteger) {
- fconfig->col = (double)config->col.data.integer;
- } else if (config->col.type == kObjectTypeFloat) {
- fconfig->col = config->col.data.floating;
- } else {
- api_set_error(err, kErrorTypeValidation,
- "'col' key must be Integer or Float");
- return false;
}
+ fconfig->col = config->col;
}
- if (HAS_KEY(config->bufpos)) {
+ if (HAS_KEY_X(config, bufpos)) {
if (!has_relative) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'bufpos'");
return false;
- } else if (config->bufpos.type == kObjectTypeArray) {
- if (!parse_float_bufpos(config->bufpos.data.array, &fconfig->bufpos)) {
+ } else {
+ if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
return false;
}
- if (!HAS_KEY(config->row)) {
+ if (!HAS_KEY_X(config, row)) {
fconfig->row = (fconfig->anchor & kFloatAnchorSouth) ? 0 : 1;
}
- if (!HAS_KEY(config->col)) {
+ if (!HAS_KEY_X(config, col)) {
fconfig->col = 0;
}
- } else {
- api_set_error(err, kErrorTypeValidation, "'bufpos' key must be Array");
- return false;
}
}
- if (config->width.type == kObjectTypeInteger && config->width.data.integer > 0) {
- fconfig->width = (int)config->width.data.integer;
- } else if (HAS_KEY(config->width)) {
- api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, width)) {
+ if (config->width > 0) {
+ fconfig->width = (int)config->width;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
return false;
}
- if (config->height.type == kObjectTypeInteger && config->height.data.integer > 0) {
- fconfig->height = (int)config->height.data.integer;
- } else if (HAS_KEY(config->height)) {
- api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, height)) {
+ if (config->height > 0) {
+ fconfig->height = (int)config->height;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
+ return false;
+ }
} else if (!reconf) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
return false;
@@ -660,26 +720,20 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (relative_is_win) {
fconfig->window = curwin->handle;
- if (config->win.type == kObjectTypeInteger || config->win.type == kObjectTypeWindow) {
- if (config->win.data.integer > 0) {
- fconfig->window = (Window)config->win.data.integer;
+ if (HAS_KEY_X(config, win)) {
+ if (config->win > 0) {
+ fconfig->window = config->win;
}
- } else if (HAS_KEY(config->win)) {
- api_set_error(err, kErrorTypeValidation, "'win' key must be Integer or Window");
- return false;
}
} else {
- if (HAS_KEY(config->win)) {
+ if (HAS_KEY_X(config, win)) {
api_set_error(err, kErrorTypeValidation, "'win' key is only valid with relative='win'");
return false;
}
}
- if (HAS_KEY(config->external)) {
- fconfig->external = api_object_to_bool(config->external, "'external' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ if (HAS_KEY_X(config, external)) {
+ fconfig->external = config->external;
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
@@ -698,30 +752,22 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
return false;
}
- if (HAS_KEY(config->focusable)) {
- fconfig->focusable = api_object_to_bool(config->focusable, "'focusable' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
- }
-
- if (config->zindex.type == kObjectTypeInteger && config->zindex.data.integer > 0) {
- fconfig->zindex = (int)config->zindex.data.integer;
- } else if (HAS_KEY(config->zindex)) {
- api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
- return false;
+ if (HAS_KEY_X(config, focusable)) {
+ fconfig->focusable = config->focusable;
}
- if (HAS_KEY(config->title_pos)) {
- if (!HAS_KEY(config->title)) {
- api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ if (HAS_KEY_X(config, zindex)) {
+ if (config->zindex > 0) {
+ fconfig->zindex = (int)config->zindex;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
return false;
}
}
- if (HAS_KEY(config->title)) {
+ if (HAS_KEY_X(config, title)) {
// title only work with border
- if (!HAS_KEY(config->border) && !fconfig->border) {
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "title requires border to be set");
return false;
}
@@ -729,42 +775,84 @@ static bool parse_float_config(Dict(float_config) *config, FloatConfig *fconfig,
if (fconfig->title) {
clear_virttext(&fconfig->title_chunks);
}
- parse_border_title(config->title, config->title_pos, fconfig, err);
+
+ parse_bordertext(config->title, kBorderTextTitle, fconfig, err);
+ if (ERROR_SET(err)) {
+ return false;
+ }
+
+ // handles unset 'title_pos' same as empty string
+ if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, title_pos)) {
+ api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
+ return false;
+ }
+ }
+
+ if (HAS_KEY_X(config, footer)) {
+ // footer only work with border
+ if (!HAS_KEY_X(config, border) && !fconfig->border) {
+ api_set_error(err, kErrorTypeException, "footer requires border to be set");
+ return false;
+ }
+
+ if (fconfig->footer) {
+ clear_virttext(&fconfig->footer_chunks);
+ }
+
+ parse_bordertext(config->footer, kBorderTextFooter, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
+
+ // handles unset 'footer_pos' same as empty string
+ if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) {
+ return false;
+ }
+ } else {
+ if (HAS_KEY_X(config, footer_pos)) {
+ api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set");
+ return false;
+ }
}
- if (HAS_KEY(config->border)) {
+ if (HAS_KEY_X(config, border)) {
parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) {
return false;
}
}
- if (config->style.type == kObjectTypeString) {
- if (config->style.data.string.data[0] == NUL) {
+ if (HAS_KEY_X(config, style)) {
+ if (config->style.data[0] == NUL) {
fconfig->style = kWinStyleUnused;
- } else if (striequal(config->style.data.string.data, "minimal")) {
+ } else if (striequal(config->style.data, "minimal")) {
fconfig->style = kWinStyleMinimal;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
+ return false;
}
- } else if (HAS_KEY(config->style)) {
- api_set_error(err, kErrorTypeValidation, "'style' key must be String");
- return false;
}
- if (HAS_KEY(config->noautocmd)) {
+ if (HAS_KEY_X(config, noautocmd)) {
if (!new_win) {
api_set_error(err, kErrorTypeValidation, "Invalid key: 'noautocmd'");
return false;
}
- fconfig->noautocmd = api_object_to_bool(config->noautocmd, "'noautocmd' key", false, err);
- if (ERROR_SET(err)) {
- return false;
- }
+ fconfig->noautocmd = config->noautocmd;
+ }
+
+ if (HAS_KEY_X(config, fixed)) {
+ fconfig->fixed = config->fixed;
+ }
+
+ if (HAS_KEY_X(config, hide)) {
+ fconfig->hide = config->hide;
}
return true;
+#undef HAS_KEY_X
}
diff --git a/src/nvim/api/win_config.h b/src/nvim/api/win_config.h
index d3e5ede5e9..6df8ed13fa 100644
--- a/src/nvim/api/win_config.h
+++ b/src/nvim/api/win_config.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_API_WIN_CONFIG_H
-#define NVIM_API_WIN_CONFIG_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/win_config.h.generated.h"
#endif
-#endif // NVIM_API_WIN_CONFIG_H
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index e2c234ab29..de5b40940f 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -1,27 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/window.h"
-#include "nvim/ascii.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/window.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/lua/executor.h"
-#include "nvim/memline_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/window.h"
/// Gets the current buffer in a window
@@ -48,15 +51,26 @@ Buffer nvim_win_get_buf(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_set_buf(Window window, Buffer buffer, Error *err)
FUNC_API_SINCE(5)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- win_set_buf(window, buffer, false, err);
+ win_T *win = find_window_by_handle(window, err);
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+ if (!win || !buf) {
+ return;
+ }
+ if (cmdwin_type != 0 && (win == curwin || win == cmdwin_old_curwin || buf == curbuf)) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return;
+ }
+ win_set_buf(win, buf, false, err);
}
/// Gets the (1,0)-indexed, buffer-relative cursor position for a given window
/// (different windows showing the same buffer have independent cursor
/// positions). |api-indexing|
///
+/// @see |getcurpos()|
+///
/// @param window Window handle, or 0 for current window
/// @param[out] err Error details, if any
/// @return (row, col) tuple
@@ -167,13 +181,8 @@ void nvim_win_set_height(Window window, Integer height, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setheight((int)height);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setheight_win((int)height, win);
try_end(err);
}
@@ -214,13 +223,8 @@ void nvim_win_set_width(Window window, Integer width, Error *err)
return;
}
- win_T *savewin = curwin;
- curwin = win;
- curbuf = curwin->w_buffer;
try_start();
- win_setwidth((int)width);
- curwin = savewin;
- curbuf = curwin->w_buffer;
+ win_setwidth_win((int)width, win);
try_end(err);
}
@@ -359,10 +363,10 @@ Boolean nvim_win_is_valid(Window window)
/// @param[out] err Error details, if any
void nvim_win_hide(Window window, Error *err)
FUNC_API_SINCE(7)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -391,19 +395,10 @@ void nvim_win_hide(Window window, Error *err)
/// @param[out] err Error details, if any
void nvim_win_close(Window window, Boolean force, Error *err)
FUNC_API_SINCE(6)
- FUNC_API_CHECK_TEXTLOCK
+ FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return;
- }
-
- if (cmdwin_type != 0) {
- if (win == curwin) {
- cmdwin_result = Ctrl_C;
- } else {
- api_set_error(err, kErrorTypeException, "%s", _(e_cmdwin));
- }
+ if (!win || !can_close_in_cmdwin(win, err)) {
return;
}
@@ -420,11 +415,11 @@ void nvim_win_close(Window window, Boolean force, Error *err)
/// @see |nvim_buf_call()|
///
/// @param window Window handle, or 0 for current window
-/// @param fun Function to call inside the window (currently lua callable
+/// @param fun Function to call inside the window (currently Lua callable
/// only)
/// @param[out] err Error details, if any
-/// @return Return value of function. NB: will deepcopy lua values
-/// currently, use upvalues to send lua references in and out.
+/// @return Return value of function. NB: will deepcopy Lua values
+/// currently, use upvalues to send Lua references in and out.
Object nvim_win_call(Window window, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
FUNC_API_LUA_ONLY
@@ -445,8 +440,9 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err)
return res;
}
-/// Set highlight namespace for a window. This will use highlights defined in
-/// this namespace, but fall back to global highlights (ns=0) when missing.
+/// Set highlight namespace for a window. This will use highlights defined with
+/// |nvim_set_hl()| for this namespace, but fall back to global highlights (ns=0) when
+/// missing.
///
/// This takes precedence over the 'winhighlight' option.
///
@@ -469,3 +465,106 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
win->w_hl_needs_update = true;
redraw_later(win, UPD_NOT_VALID);
}
+
+/// Computes the number of screen lines occupied by a range of text in a given window.
+/// Works for off-screen text and takes folds into account.
+///
+/// Diff filler or virtual lines above a line are counted as a part of that line,
+/// unless the line is on "start_row" and "start_vcol" is specified.
+///
+/// Diff filler or virtual lines below the last buffer line are counted in the result
+/// when "end_row" is omitted.
+///
+/// Line indexing is similar to |nvim_buf_get_text()|.
+///
+/// @param window Window handle, or 0 for current window.
+/// @param opts Optional parameters:
+/// - start_row: Starting line index, 0-based inclusive.
+/// When omitted start at the very top.
+/// - end_row: Ending line index, 0-based inclusive.
+/// When omitted end at the very bottom.
+/// - start_vcol: Starting virtual column index on "start_row",
+/// 0-based inclusive, rounded down to full screen lines.
+/// When omitted include the whole line.
+/// - end_vcol: Ending virtual column index on "end_row",
+/// 0-based exclusive, rounded up to full screen lines.
+/// When omitted include the whole line.
+/// @return Dictionary containing text height information, with these keys:
+/// - all: The total number of screen lines occupied by the range.
+/// - fill: The number of diff filler or virtual lines among them.
+///
+/// @see |virtcol()| for text width.
+Dictionary nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *arena,
+ Error *err)
+ FUNC_API_SINCE(12)
+{
+ Dictionary rv = arena_dict(arena, 2);
+
+ win_T *const win = find_window_by_handle(window, err);
+ if (!win) {
+ return rv;
+ }
+ buf_T *const buf = win->w_buffer;
+ const linenr_T line_count = buf->b_ml.ml_line_count;
+
+ linenr_T start_lnum = 1;
+ linenr_T end_lnum = line_count;
+ int64_t start_vcol = -1;
+ int64_t end_vcol = -1;
+
+ bool oob = false;
+
+ if (HAS_KEY(opts, win_text_height, start_row)) {
+ start_lnum = (linenr_T)normalize_index(buf, opts->start_row, false, &oob);
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_row)) {
+ end_lnum = (linenr_T)normalize_index(buf, opts->end_row, false, &oob);
+ }
+
+ VALIDATE(!oob, "%s", "Line index out of bounds", {
+ return rv;
+ });
+ VALIDATE((start_lnum <= end_lnum), "%s", "'start_row' is higher than 'end_row'", {
+ return rv;
+ });
+
+ if (HAS_KEY(opts, win_text_height, start_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, start_row),
+ "%s", "'start_vcol' specified without 'start_row'", {
+ return rv;
+ });
+ start_vcol = opts->start_vcol;
+ VALIDATE_RANGE((start_vcol >= 0 && start_vcol <= MAXCOL), "start_vcol", {
+ return rv;
+ });
+ }
+
+ if (HAS_KEY(opts, win_text_height, end_vcol)) {
+ VALIDATE(HAS_KEY(opts, win_text_height, end_row),
+ "%s", "'end_vcol' specified without 'end_row'", {
+ return rv;
+ });
+ end_vcol = opts->end_vcol;
+ VALIDATE_RANGE((end_vcol >= 0 && end_vcol <= MAXCOL), "end_vcol", {
+ return rv;
+ });
+ }
+
+ if (start_lnum == end_lnum && start_vcol >= 0 && end_vcol >= 0) {
+ VALIDATE((start_vcol <= end_vcol), "%s", "'start_vcol' is higher than 'end_vcol'", {
+ return rv;
+ });
+ }
+
+ int64_t fill = 0;
+ int64_t all = win_text_height(win, start_lnum, start_vcol, end_lnum, end_vcol, &fill);
+ if (!HAS_KEY(opts, win_text_height, end_row)) {
+ const int64_t end_fill = win_get_fill(win, line_count + 1);
+ fill += end_fill;
+ all += end_fill;
+ }
+ PUT_C(rv, "all", INTEGER_OBJ(all));
+ PUT_C(rv, "fill", INTEGER_OBJ(fill));
+ return rv;
+}
diff --git a/src/nvim/api/window.h b/src/nvim/api/window.h
index 0f36c12a9f..a5c9f86225 100644
--- a/src/nvim/api/window.h
+++ b/src/nvim/api/window.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_API_WINDOW_H
-#define NVIM_API_WINDOW_H
+#pragma once
-#include "nvim/api/private/defs.h"
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/window.h.generated.h"
#endif
-#endif // NVIM_API_WINDOW_H
diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c
index 41024cafda..84f4297c99 100644
--- a/src/nvim/arabic.c
+++ b/src/nvim/arabic.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file arabic.c
///
/// Functions for Arabic language.
@@ -22,14 +19,12 @@
#include <stdbool.h>
#include <stddef.h>
-#include <stdint.h>
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
-#include "nvim/macros.h"
-#include "nvim/mbyte.h"
-#include "nvim/option_defs.h"
-#include "nvim/vim.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_vars.h"
// Unicode values for Arabic characters.
enum {
@@ -297,13 +292,12 @@ static int A_is_valid(int c)
}
// Do Arabic shaping on character "c". Returns the shaped character.
-// out: "ccp" points to the first byte of the character to be shaped.
// in/out: "c1p" points to the first composing char for "c".
// in: "prev_c" is the previous character (not shaped)
// in: "prev_c1" is the first composing char for the previous char
// (not shaped)
// in: "next_c" is the next character (not shaped).
-int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c)
+int arabic_shape(int c, int *c1p, int prev_c, int prev_c1, int next_c)
{
// Deal only with Arabic character, pass back all others
if (!A_is_ok(c)) {
@@ -347,14 +341,6 @@ int arabic_shape(int c, int *ccp, int *c1p, int prev_c, int prev_c1, int next_c)
curr_c = c;
}
- if ((curr_c != c) && (ccp != NULL)) {
- char buf[MB_MAXBYTES + 1];
-
- // Update the first byte of the character
- utf_char2bytes(curr_c, buf);
- *ccp = (uint8_t)buf[0];
- }
-
// Return the shaped character
return curr_c;
}
diff --git a/src/nvim/arabic.h b/src/nvim/arabic.h
index 3c34de1449..ac27153a37 100644
--- a/src/nvim/arabic.h
+++ b/src/nvim/arabic.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_ARABIC_H
-#define NVIM_ARABIC_H
-
-#include <stdbool.h>
+#pragma once
#define ARABIC_CHAR(ch) (((ch) & 0xFF00) == 0x0600)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "arabic.h.generated.h"
#endif
-#endif // NVIM_ARABIC_H
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
index c6a4be7e13..d2734e6c5a 100644
--- a/src/nvim/arglist.c
+++ b/src/nvim/arglist.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// arglist.c: functions for dealing with the argument list
#include <assert.h>
@@ -10,9 +7,11 @@
#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
@@ -21,23 +20,25 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// State used by the :all command to open all the files in the argument list in
@@ -63,7 +64,9 @@ typedef struct {
# include "arglist.c.generated.h"
#endif
-static char e_cannot_change_arglist_recursively[]
+static const char e_window_layout_changed_unexpectedly[]
+ = N_("E249: Window layout changed unexpectedly");
+static const char e_cannot_change_arglist_recursively[]
= N_("E1156: Cannot change the argument list recursively");
enum {
@@ -135,7 +138,7 @@ void alist_expand(int *fnum_list, int fnum_len)
// Don't use 'suffixes' here. This should work like the shell did the
// expansion. Also, the vimrc file isn't read yet, thus the user
// can't set the options.
- p_su = empty_option;
+ p_su = empty_string_option;
for (int i = 0; i < GARGCOUNT; i++) {
old_arg_files[i] = xstrdup(GARGLIST[i].ae_fname);
}
@@ -252,9 +255,8 @@ void alist_slash_adjust(void)
static char *do_one_arg(char *str)
{
char *p;
- bool inbacktick;
- inbacktick = false;
+ bool inbacktick = false;
for (p = str; *str; str++) {
// When the backslash is used for escaping the special meaning of a
// character we need to keep it until wildcard expansion.
@@ -388,7 +390,7 @@ static void arglist_del_files(garray_T *alist_ga)
bool didone = false;
for (int match = 0; match < ARGCOUNT; match++) {
- if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), (colnr_T)0)) {
+ if (vim_regexec(&regmatch, alist_name(&ARGLIST[match]), 0)) {
didone = true;
xfree(ARGLIST[match].ae_fname);
memmove(ARGLIST + match, ARGLIST + match + 1,
@@ -619,8 +621,6 @@ void ex_argument(exarg_T *eap)
/// Edit file "argn" of the argument lists.
void do_argfile(exarg_T *eap, int argn)
{
- int other;
- char *p;
int old_arg_idx = curwin->w_arg_idx;
if (argn < 0 || argn >= ARGCOUNT) {
@@ -646,9 +646,9 @@ void do_argfile(exarg_T *eap, int argn)
} else {
// if 'hidden' set, only check for changed file when re-editing
// the same buffer
- other = true;
+ int other = true;
if (buf_hide(curbuf)) {
- p = fix_fname(alist_name(&ARGLIST[argn]));
+ char *p = fix_fname(alist_name(&ARGLIST[argn]));
other = otherfile(p);
xfree(p);
}
@@ -683,8 +683,6 @@ void do_argfile(exarg_T *eap, int argn)
/// ":next", and commands that behave like it.
void ex_next(exarg_T *eap)
{
- int i;
-
// check for changed buffer now, if this fails the argument list is not
// redefined.
if (buf_hide(curbuf)
@@ -692,6 +690,7 @@ void ex_next(exarg_T *eap)
|| !check_changed(curbuf, CCGD_AW
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)) {
+ int i;
if (*eap->arg != NUL) { // redefine file list
if (do_arglist(eap->arg, AL_SET, 0, true) == FAIL) {
return;
@@ -835,10 +834,8 @@ char *get_arglist_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
/// Get the file name for an argument list entry.
char *alist_name(aentry_T *aep)
{
- buf_T *bp;
-
// Use the name from the associated buffer if it exists.
- bp = buflist_findnr(aep->ae_fnum);
+ buf_T *bp = buflist_findnr(aep->ae_fnum);
if (bp == NULL || bp->b_fname == NULL) {
return aep->ae_fname;
}
@@ -855,15 +852,20 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
if (aall->had_tab > 0) {
goto_tabpage_tp(first_tabpage, true, true);
}
- for (;;) {
+ while (true) {
win_T *wpnext = NULL;
tabpage_T *tpnext = curtab->tp_next;
- for (win_T *wp = firstwin; wp != NULL; wp = wpnext) {
+ // Try to close floating windows first
+ for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
int i;
- wpnext = wp->w_next;
+ wpnext = wp->w_floating
+ ? wp->w_prev->w_floating ? wp->w_prev : firstwin
+ : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next;
buf_T *buf = wp->w_buffer;
if (buf->b_ffname == NULL
- || (!aall->keep_tabs && (buf->b_nwindows > 1 || wp->w_width != Columns))) {
+ || (!aall->keep_tabs
+ && (buf->b_nwindows > 1 || wp->w_width != Columns
+ || (wp->w_floating && !is_aucmd_win(wp))))) {
i = aall->opened_len;
} else {
// check if the buffer in this window is in the arglist
@@ -918,7 +920,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
(void)autowrite(buf, false);
// Check if autocommands removed the window.
if (!win_valid(wp) || !bufref_valid(&bufref)) {
- wpnext = firstwin; // Start all over...
+ wpnext = lastwin->w_floating ? lastwin : firstwin; // Start all over...
continue;
}
}
@@ -931,7 +933,7 @@ static void arg_all_close_unused_windows(arg_all_state_T *aall)
// check if autocommands removed the next window
if (!win_valid(wpnext)) {
// start all over...
- wpnext = firstwin;
+ wpnext = lastwin->w_floating ? lastwin : firstwin;
}
}
}
@@ -978,8 +980,10 @@ static void arg_all_open_windows(arg_all_state_T *aall, int count)
if (aall->keep_tabs) {
aall->new_curwin = wp;
aall->new_curtab = curtab;
+ } else if (wp->w_floating) {
+ break;
} else if (wp->w_frame->fr_parent != curwin->w_frame->fr_parent) {
- emsg(_("E249: window layout changed unexpectedly"));
+ emsg(_(e_window_layout_changed_unexpectedly));
i = count;
break;
} else {
@@ -1074,6 +1078,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
aall.alist->al_refcount++;
arglist_locked = true;
+ tabpage_T *const new_lu_tp = curtab;
+
// Try closing all windows that are not in the argument list.
// Also close windows that are not full width;
// When 'hidden' or "forceit" set the buffer becomes hidden.
@@ -1081,6 +1087,11 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
// When the ":tab" modifier was used do this for all tab pages.
arg_all_close_unused_windows(&aall);
+ // Now set the last used tabpage to where we started.
+ if (valid_tabpage(new_lu_tp)) {
+ lastused_tabpage = new_lu_tp;
+ }
+
// Open a window for files in the argument list that don't have one.
// ARGCOUNT may change while doing this, because of autocommands.
if (count > aall.opened_len || count <= 0) {
@@ -1092,7 +1103,8 @@ static void do_arg_all(int count, int forceit, int keep_tabs)
autocmd_no_leave++;
last_curwin = curwin;
last_curtab = curtab;
- win_enter(lastwin, false);
+ // lastwin may be aucmd_win
+ win_enter(lastwin_nofloating(), false);
// Open up to "count" windows.
arg_all_open_windows(&aall, count);
@@ -1144,7 +1156,7 @@ char *arg_all(void)
// Do this loop two times:
// first time: compute the total length
// second time: concatenate the names
- for (;;) {
+ while (true) {
int len = 0;
for (int idx = 0; idx < ARGCOUNT; idx++) {
char *p = alist_name(&ARGLIST[idx]);
@@ -1233,8 +1245,7 @@ static void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rett
tv_list_alloc_ret(rettv, argcount);
if (arglist != NULL) {
for (int idx = 0; idx < argcount; idx++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)alist_name(&arglist[idx]), -1);
+ tv_list_append_string(rettv->vval.v_list, alist_name(&arglist[idx]), -1);
}
}
}
@@ -1270,7 +1281,7 @@ void f_argv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = NULL;
int idx = (int)tv_get_number_chk(&argvars[0], NULL);
if (arglist != NULL && idx >= 0 && idx < argcount) {
- rettv->vval.v_string = xstrdup((const char *)alist_name(&arglist[idx]));
+ rettv->vval.v_string = xstrdup(alist_name(&arglist[idx]));
} else if (idx == -1) {
get_arglist_as_rettv(arglist, argcount, rettv);
}
diff --git a/src/nvim/arglist.h b/src/nvim/arglist.h
index b2e0f411d4..97729f466c 100644
--- a/src/nvim/arglist.h
+++ b/src/nvim/arglist.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_ARGLIST_H
-#define NVIM_ARGLIST_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/arglist_defs.h" // IWYU pragma: export
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "arglist.h.generated.h"
#endif
-
-#endif // NVIM_ARGLIST_H
diff --git a/src/nvim/arglist_defs.h b/src/nvim/arglist_defs.h
new file mode 100644
index 0000000000..a79d540a6e
--- /dev/null
+++ b/src/nvim/arglist_defs.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "nvim/garray_defs.h"
+
+/// Argument list: Array of file names.
+/// Used for the global argument list and the argument lists local to a window.
+typedef struct arglist {
+ garray_T al_ga; ///< growarray with the array of file names
+ int al_refcount; ///< number of windows using this arglist
+ int id; ///< id of this arglist
+} alist_T;
+
+/// For each argument remember the file name as it was given, and the buffer
+/// number that contains the expanded file name (required for when ":cd" is
+/// used).
+typedef struct argentry {
+ char *ae_fname; ///< file name as specified
+ int ae_fnum; ///< buffer number with expanded file name
+} aentry_T;
diff --git a/src/nvim/ascii.h b/src/nvim/ascii_defs.h
index 96d6fe214a..4125336796 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii_defs.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_ASCII_H
-#define NVIM_ASCII_H
+#pragma once
#include <stdbool.h>
#include "nvim/func_attr.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/os/os_defs.h"
// Definitions of various common control characters.
@@ -184,5 +183,3 @@ static inline bool ascii_isspace(int c)
{
return (c >= 9 && c <= 13) || c == ' ';
}
-
-#endif // NVIM_ASCII_H
diff --git a/src/nvim/assert.h b/src/nvim/assert_defs.h
index 1941f4c33c..cfc27ee994 100644
--- a/src/nvim/assert.h
+++ b/src/nvim/assert_defs.h
@@ -1,7 +1,7 @@
-#ifndef NVIM_ASSERT_H
-#define NVIM_ASSERT_H
+#pragma once
#include "auto/config.h"
+#include "nvim/log.h"
// support static asserts (aka compile-time asserts)
@@ -57,7 +57,7 @@
// the easiest case, when the mode is C11 (generic compiler) or Clang
// advertises explicit support for c_static_assert, meaning it won't warn.
-#if __STDC_VERSION__ >= 201112L || __has_feature(c_static_assert)
+#if __STDC_VERSION__ >= 201112 || __has_feature(c_static_assert)
# define STATIC_ASSERT_STATEMENT(cond, msg) _Static_assert(cond, msg)
// if we're dealing with gcc >= 4.6 in C99 mode, we can still use
// _Static_assert but we need to suppress warnings, this is pretty ugly.
@@ -165,5 +165,3 @@
# define STRICT_SUB(a, b, c, t) \
do { *(c) = (t)((a) - (b)); } while (0)
#endif
-
-#endif // NVIM_ASSERT_H
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index fb0062e524..e50ca76d0e 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -72,6 +72,10 @@ return {
'InsertLeavePre', -- just before leaving Insert mode
'LspAttach', -- after an LSP client attaches to a buffer
'LspDetach', -- after an LSP client detaches from a buffer
+ 'LspRequest', -- after an LSP request is started, canceled, or completed
+ 'LspNotify', -- after an LSP notice has been sent to the server
+ 'LspTokenUpdate', -- after a visible LSP token is updated
+ 'LspProgress', -- after a LSP progress update
'MenuPopup', -- just before popup menu is displayed
'ModeChanged', -- after changing the mode
'OptionSet', -- after setting any option
@@ -81,6 +85,7 @@ return {
'RecordingEnter', -- when starting to record a macro
'RecordingLeave', -- just before a macro stops recording
'RemoteReply', -- upon string reception from a remote vim
+ 'SafeState', -- going to wait for a character
'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file
'ShellCmdPost', -- after ":!cmd"
@@ -152,6 +157,10 @@ return {
DiagnosticChanged=true,
LspAttach=true,
LspDetach=true,
+ LspNotify=true,
+ LspRequest=true,
+ LspProgress=true,
+ LspTokenUpdate=true,
RecordingEnter=true,
RecordingLeave=true,
Signal=true,
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index 01ebdfdafe..74a1dbdbc3 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -1,45 +1,43 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// autocmd.c: Autocommand related functions
#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <time.h>
+#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
-#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
+#include "nvim/event/multiqueue.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
-#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
+#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/insexpand.h"
#include "nvim/lua/executor.h"
#include "nvim/main.h"
-#include "nvim/map.h"
-#include "nvim/memline_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
@@ -48,20 +46,24 @@
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "auevents_name_map.generated.h"
# include "autocmd.c.generated.h"
#endif
+static const char e_autocommand_nesting_too_deep[]
+ = N_("E218: Autocommand nesting too deep");
+
// Naming Conventions:
// - general autocmd behavior start with au_
// - AutoCmd start with aucmd_
@@ -70,41 +72,13 @@
// - Groups start with augroup_
// - Events start with event_
+// The autocommands are stored in a contiguous vector for each event.
//
-// The autocommands are stored in a list for each event.
-// Autocommands for the same pattern, that are consecutive, are joined
-// together, to avoid having to match the pattern too often.
-// The result is an array of Autopat lists, which point to AutoCmd lists:
-//
-// last_autopat[0] -----------------------------+
-// V
-// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL
-// Autopat.cmds Autopat.cmds
-// | |
-// V V
-// AutoCmd.next AutoCmd.next
-// | |
-// V V
-// AutoCmd.next NULL
-// |
-// V
-// NULL
-//
-// last_autopat[1] --------+
-// V
-// first_autopat[1] --> Autopat.next --> NULL
-// Autopat.cmds
-// |
-// V
-// AutoCmd.next
-// |
-// V
-// NULL
-// etc.
-//
-// The order of AutoCmds is important, this is the order in which they were
-// defined and will have to be executed.
+// The order of AutoCmds is important, this is the order in which they
+// were defined and will have to be executed.
//
+// To avoid having to match the pattern too often, patterns are reference
+// counted and reused for consecutive autocommands.
// Code for automatic commands.
static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands
@@ -121,7 +95,7 @@ static int current_augroup = AUGROUP_DEFAULT;
// Whether we need to delete marked patterns.
// While deleting autocmds, they aren't actually remover, just marked.
-static int au_need_clean = false;
+static bool au_need_clean = false;
static int autocmd_blocked = 0; // block all autocmds
@@ -130,27 +104,22 @@ static bool autocmd_include_groups = false;
static char *old_termresponse = NULL;
-/// Iterates over all the AutoPats for a particular event
-#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \
- for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT
-
// Map of autocmd group names and ids.
// name -> ID
// ID -> name
static Map(String, int) map_augroup_name_to_id = MAP_INIT;
static Map(int, String) map_augroup_id_to_name = MAP_INIT;
-static void augroup_map_del(int id, char *name)
+static void augroup_map_del(int id, const char *name)
{
if (name != NULL) {
- String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string(name));
- map_del(String, int)(&map_augroup_name_to_id, key);
+ String key;
+ map_del(String, int)(&map_augroup_name_to_id, cstr_as_string((char *)name), &key);
api_free_string(key);
}
if (id > 0) {
- String mapped = map_get(int, String)(&map_augroup_id_to_name, id);
+ String mapped = map_del(int, String)(&map_augroup_id_to_name, id, NULL);
api_free_string(mapped);
- map_del(int, String)(&map_augroup_id_to_name, id);
}
}
@@ -162,204 +131,191 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE
return deleted_augroup;
}
-// Show the autocommands for one AutoPat.
-static void aupat_show(AutoPat *ap, event_T event, int previous_group)
+static void au_show_for_all_events(int group, const char *pat)
{
- // Check for "got_int" (here and at various places below), which is set
- // when "q" has been hit for the "--more--" prompt
- if (got_int) {
- return;
+ FOR_ALL_AUEVENTS(event) {
+ au_show_for_event(group, event, pat);
}
+}
- // pattern has been removed
- if (ap->pat == NULL) {
+static void au_show_for_event(int group, event_T event, const char *pat)
+{
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ // Return early if there are no autocmds for this event
+ if (kv_size(*acs) == 0) {
return;
}
- char *name = augroup_name(ap->group);
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
+ int patlen;
+ if (*pat != NUL) {
+ patlen = (int)aucmd_pattern_length(pat);
- msg_putchar('\n');
- if (got_int) {
- return;
- }
- // When switching groups, we need to show the new group information.
- if (ap->group != previous_group) {
- // show the group name, if it's not the default group
- if (ap->group != AUGROUP_DEFAULT) {
- if (name == NULL) {
- msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
- } else {
- msg_puts_attr(name, HL_ATTR(HLF_T));
- }
- msg_puts(" ");
+ // detect special <buffer[=X]> buffer-local patterns
+ if (aupat_is_buflocal(pat, patlen)) {
+ // normalize pat into standard "<buffer>#N" form
+ aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
+ pat = buflocal_pat;
+ patlen = (int)strlen(buflocal_pat);
}
- // show the event name
- msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
- msg_putchar('\n');
- if (got_int) {
+
+ if (patlen == 0) {
return;
}
+ assert(*pat != NUL);
+ } else {
+ pat = NULL;
+ patlen = 0;
}
- msg_col = 4;
- msg_outtrans(ap->pat);
-
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- // skip removed commands
- if (aucmd_exec_is_deleted(ac->exec)) {
- continue;
- }
+ // Loop through all the specified patterns.
+ while (true) {
+ AutoPat *last_ap = NULL;
+ int last_group = AUGROUP_ERROR;
+ const char *last_group_name = NULL;
- if (msg_col >= 14) {
- msg_putchar('\n');
- }
- msg_col = 14;
- if (got_int) {
- return;
- }
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
- char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
- if (ac->desc != NULL) {
- size_t msglen = 100;
- char *msg = (char *)xmallocz(msglen);
- if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- snprintf(msg, msglen, " [%s]", ac->desc);
- } else {
- snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
+ // Skip deleted autocommands.
+ if (ac->pat == NULL) {
+ continue;
}
- msg_outtrans(msg);
- XFREE_CLEAR(msg);
- } else if (ac->exec.type == CALLABLE_CB) {
- msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
- } else {
- msg_outtrans(exec_to_string);
- }
- XFREE_CLEAR(exec_to_string);
- if (p_verbose > 0) {
- last_set_msg(ac->script_ctx);
- }
- if (got_int) {
- return;
- }
- if (ac->next != NULL) {
- msg_putchar('\n');
- if (got_int) {
- return;
+
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if ((group != AUGROUP_ALL && ac->pat->group != group)
+ || (pat != NULL
+ && (ac->pat->patlen != patlen || strncmp(pat, ac->pat->pat, (size_t)patlen) != 0))) {
+ continue;
}
- }
- }
-}
-static void au_show_for_all_events(int group, char *pat)
-{
- FOR_ALL_AUEVENTS(event) {
- au_show_for_event(group, event, pat);
- }
-}
+ // Show event name and group only if one of them changed.
+ if (ac->pat->group != last_group) {
+ last_group = ac->pat->group;
+ last_group_name = augroup_name(ac->pat->group);
-static void au_show_for_event(int group, event_T event, char *pat)
-{
- // Return early if there are no autocmds for this event
- if (au_event_is_empty(event)) {
- return;
- }
+ if (got_int) {
+ return;
+ }
- // always need to show group information before the first pattern for the event
- int previous_group = AUGROUP_ERROR;
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- if (*pat == NUL) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (group == AUGROUP_ALL || ap->group == group) {
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ // When switching groups, we need to show the new group information.
+ // show the group name, if it's not the default group
+ if (ac->pat->group != AUGROUP_DEFAULT) {
+ if (last_group_name == NULL) {
+ msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E));
+ } else {
+ msg_puts_attr(last_group_name, HL_ATTR(HLF_T));
+ }
+ msg_puts(" ");
+ }
+ // show the event name
+ msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T));
}
- }
- return;
- }
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
- // Loop through all the specified patterns.
- int patlen = (int)aucmd_pattern_length(pat);
- while (patlen) {
- // detect special <buffer[=X]> buffer-local patterns
- if (aupat_is_buflocal(pat, patlen)) {
- // normalize pat into standard "<buffer>#N" form
- aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen));
- pat = (char *)buflocal_pat;
- patlen = (int)strlen(buflocal_pat);
- }
+ // Show pattern only if it changed.
+ if (last_ap != ac->pat) {
+ last_ap = ac->pat;
- assert(*pat != NUL);
+ msg_putchar('\n');
+ if (got_int) {
+ return;
+ }
- // Find AutoPat entries with this pattern.
- // always goes at or after the last one, so start at the end.
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if ((group == AUGROUP_ALL || ap->group == group)
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Show autocmd's for this autopat, or buflocals <buffer=X>
- aupat_show(ap, event, previous_group);
- previous_group = ap->group;
+ msg_col = 4;
+ msg_outtrans(ac->pat->pat, 0);
+ }
+
+ if (got_int) {
+ return;
+ }
+
+ if (msg_col >= 14) {
+ msg_putchar('\n');
+ }
+ msg_col = 14;
+ if (got_int) {
+ return;
+ }
+
+ char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
+ if (ac->desc != NULL) {
+ size_t msglen = 100;
+ char *msg = xmallocz(msglen);
+ if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ snprintf(msg, msglen, " [%s]", ac->desc);
+ } else {
+ snprintf(msg, msglen, "%s [%s]", exec_to_string, ac->desc);
}
+ msg_outtrans(msg, 0);
+ XFREE_CLEAR(msg);
+ } else if (ac->exec.type == CALLABLE_CB) {
+ msg_puts_attr(exec_to_string, HL_ATTR(HLF_8));
+ } else {
+ msg_outtrans(exec_to_string, 0);
+ }
+ XFREE_CLEAR(exec_to_string);
+ if (p_verbose > 0) {
+ last_set_msg(ac->script_ctx);
+ }
+
+ if (got_int) {
+ return;
}
}
- pat = aucmd_next_pattern(pat, (size_t)patlen);
- patlen = (int)aucmd_pattern_length(pat);
+ // If a pattern is provided, find next pattern. Otherwise exit after single iteration.
+ if (pat != NULL) {
+ pat = aucmd_next_pattern(pat, (size_t)patlen);
+ patlen = (int)aucmd_pattern_length(pat);
+ if (patlen == 0) {
+ break;
+ }
+ } else {
+ break;
+ }
}
}
-// Mark an autocommand handler for deletion.
-static void aupat_del(AutoPat *ap)
-{
- XFREE_CLEAR(ap->pat);
- ap->buflocal_nr = -1;
- au_need_clean = true;
-}
-
-void aupat_del_for_event_and_group(event_T event, int group)
+// Delete autocommand.
+static void aucmd_del(AutoCmd *ac)
{
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == group) {
- aupat_del(ap);
- }
+ if (ac->pat != NULL && --ac->pat->refcount == 0) {
+ XFREE_CLEAR(ac->pat->pat);
+ vim_regfree(ac->pat->reg_prog);
+ xfree(ac->pat);
}
+ ac->pat = NULL;
+ aucmd_exec_free(&ac->exec);
+ XFREE_CLEAR(ac->desc);
- au_cleanup();
+ au_need_clean = true;
}
-// Mark all commands for a pattern for deletion.
-static void aupat_remove_cmds(AutoPat *ap)
+void aucmd_del_for_event_and_group(event_T event, int group)
{
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- aucmd_exec_free(&ac->exec);
-
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat != NULL && ac->pat->group == group) {
+ aucmd_del(ac);
}
}
- au_need_clean = true;
-}
-// Delete one command from an autocmd pattern.
-static void aucmd_del(AutoCmd *ac)
-{
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
- au_need_clean = true;
+ au_cleanup();
}
-/// Cleanup autocommands and patterns that have been deleted.
+/// Cleanup autocommands that have been deleted.
/// This is only done when not executing autocommands.
static void au_cleanup(void)
{
@@ -369,72 +325,39 @@ static void au_cleanup(void)
// Loop over all events.
FOR_ALL_AUEVENTS(event) {
- // Loop over all autocommand patterns.
- AutoPat **prev_ap = &(first_autopat[(int)event]);
- for (AutoPat *ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- bool has_cmd = false;
-
- // Loop over all commands for this pattern.
- AutoCmd **prev_ac = &(ap->cmds);
- for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
- // Remove the command if the pattern is to be deleted or when
- // the command has been marked for deletion.
- if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) {
- *prev_ac = ac->next;
- aucmd_exec_free(&ac->exec);
- if (ac->desc != NULL) {
- XFREE_CLEAR(ac->desc);
- }
-
- xfree(ac);
- } else {
- has_cmd = true;
- prev_ac = &(ac->next);
- }
+ // Loop over all autocommands.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ size_t nsize = 0;
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (nsize != i) {
+ kv_A(*acs, nsize) = *ac;
}
-
- if (ap->pat != NULL && !has_cmd) {
- // Pattern was not marked for deletion, but all of its commands were.
- // So mark the pattern for deletion.
- aupat_del(ap);
- }
-
- // Remove the pattern if it has been marked for deletion.
- if (ap->pat == NULL) {
- if (ap->next == NULL) {
- if (prev_ap == &(first_autopat[(int)event])) {
- last_autopat[(int)event] = NULL;
- } else {
- // this depends on the "next" field being the first in
- // the struct
- last_autopat[(int)event] = (AutoPat *)prev_ap;
- }
- }
- *prev_ap = ap->next;
- vim_regfree(ap->reg_prog);
- xfree(ap);
- } else {
- prev_ap = &(ap->next);
+ if (ac->pat != NULL) {
+ nsize++;
}
}
+ if (nsize == 0) {
+ kv_destroy(*acs);
+ } else {
+ acs->size = nsize;
+ }
}
au_need_clean = false;
}
-// Get the first AutoPat for a particular event.
-AutoPat *au_get_autopat_for_event(event_T event)
+AutoCmdVec *au_get_autocmds_for_event(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[(int)event];
+ return &autocmds[(int)event];
}
-// Called when buffer is freed, to remove/invalidate related buffer-local
-// autocmds.
+// Called when buffer is freed, to remove/invalidate related buffer-local autocmds.
void aubuflocal_remove(buf_T *buf)
{
// invalidate currently executing autocommands
- for (AutoPatCmd *apc = active_apc_list; apc; apc = apc->next) {
+ for (AutoPatCmd *apc = active_apc_list; apc != NULL; apc = apc->next) {
if (buf->b_fnum == apc->arg_bufnr) {
apc->arg_bufnr = 0;
}
@@ -442,16 +365,19 @@ void aubuflocal_remove(buf_T *buf)
// invalidate buflocals looping through events
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->buflocal_nr == buf->b_fnum) {
- aupat_del(ap);
-
- if (p_verbose >= 6) {
- verbose_enter();
- smsg(_("auto-removing autocommand: %s <buffer=%d>"),
- event_nr2name(event), buf->b_fnum);
- verbose_leave();
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat == NULL || ac->pat->buflocal_nr != buf->b_fnum) {
+ continue;
+ }
+
+ aucmd_del(ac);
+
+ if (p_verbose >= 6) {
+ verbose_enter();
+ smsg(0, _("auto-removing autocommand: %s <buffer=%d>"), event_nr2name(event), buf->b_fnum);
+ verbose_leave();
}
}
}
@@ -460,7 +386,7 @@ void aubuflocal_remove(buf_T *buf)
// Add an autocmd group name or return existing group matching name.
// Return its ID.
-int augroup_add(char *name)
+int augroup_add(const char *name)
{
assert(STRICMP(name, "end") != 0);
@@ -496,20 +422,21 @@ int augroup_add(char *name)
/// `--- DELETED ---` groups around)
void augroup_del(char *name, bool stupid_legacy_mode)
{
- int i = augroup_find(name);
- if (i == AUGROUP_ERROR) { // the group doesn't exist
+ int group = augroup_find(name);
+ if (group == AUGROUP_ERROR) { // the group doesn't exist
semsg(_("E367: No such group: \"%s\""), name);
return;
- }
- if (i == current_augroup) {
+ } else if (group == current_augroup) {
emsg(_("E936: Cannot delete the current group"));
return;
}
if (stupid_legacy_mode) {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i && ap->pat != NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL && ap->group == group) {
give_warning(_("W19: Deleting augroup that is still in use"), true);
map_put(String, int)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED);
augroup_map_del(ap->group, NULL);
@@ -519,16 +446,18 @@ void augroup_del(char *name, bool stupid_legacy_mode)
}
} else {
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- if (ap->group == i) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->pat != NULL && ac->pat->group == group) {
+ aucmd_del(ac);
}
}
}
}
// Remove the group because it's not currently in use.
- augroup_map_del(i, name);
+ augroup_map_del(group, name);
au_cleanup();
}
@@ -637,28 +566,25 @@ void do_augroup(char *arg, int del_group)
void free_all_autocmds(void)
{
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) {
- aupat_del(ap);
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ aucmd_del(&kv_A(*acs, i));
}
+ kv_destroy(*acs);
+ au_need_clean = false;
}
- au_need_clean = true;
- au_cleanup();
-
// Delete the augroup_map, including free the data
String name;
- int id;
- map_foreach(&map_augroup_name_to_id, name, id, {
- (void)id;
+ map_foreach_key(&map_augroup_name_to_id, name, {
api_free_string(name);
})
- map_destroy(String, int)(&map_augroup_name_to_id);
+ map_destroy(String, &map_augroup_name_to_id);
- map_foreach(&map_augroup_id_to_name, id, name, {
- (void)id;
+ map_foreach_value(&map_augroup_id_to_name, name, {
api_free_string(name);
})
- map_destroy(int, String)(&map_augroup_id_to_name);
+ map_destroy(int, &map_augroup_id_to_name);
// aucmd_win[] is freed in win_free_all()
}
@@ -675,9 +601,9 @@ bool is_aucmd_win(win_T *win)
return false;
}
-// Return the event number for event name "start".
-// Return NUM_EVENTS if the event name was not found.
-// Return a pointer to the next event name in "end".
+/// Return the event number for event name "start".
+/// Return NUM_EVENTS if the event name was not found.
+/// Return a pointer to the next event name in "end".
event_T event_name2nr(const char *start, char **end)
{
const char *p;
@@ -701,6 +627,18 @@ event_T event_name2nr(const char *start, char **end)
return event_names[i].event;
}
+/// Return the event number for event name "str".
+/// Return NUM_EVENTS if the event name was not found.
+event_T event_name2nr_str(String str)
+{
+ for (int i = 0; event_names[i].name != NULL; i++) {
+ if (str.size == event_names[i].len && STRNICMP(str.data, event_names[i].name, str.size) == 0) {
+ return event_names[i].event;
+ }
+ }
+ return NUM_EVENTS;
+}
+
/// Return the name for event
///
/// @param[in] event Event to return name for.
@@ -728,8 +666,7 @@ static bool event_ignored(event_T event)
while (*p != NUL) {
if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) {
return true;
- }
- if (event_name2nr(p, &p) == event) {
+ } else if (event_name2nr(p, &p) == event) {
return true;
}
}
@@ -901,7 +838,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
}
}
- bool is_showing = !forceit && *cmd == NUL;
+ const bool is_showing = !forceit && *cmd == NUL;
// Print header when showing autocommands.
if (is_showing) {
@@ -926,8 +863,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
while (*arg && *arg != '|' && !ascii_iswhite(*arg)) {
event_T event = event_name2nr(arg, &arg);
assert(event < NUM_EVENTS);
- if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) == FAIL) {
break;
}
}
@@ -940,11 +876,10 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit)
xfree(envpat);
}
-void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool delete, int group)
+void do_all_autocmd_events(const char *pat, bool once, int nested, char *cmd, bool del, int group)
{
FOR_ALL_AUEVENTS(event) {
- if (do_autocmd_event(event, pat, once, nested, cmd, delete, group)
- == FAIL) {
+ if (do_autocmd_event(event, pat, once, nested, cmd, del, group) == FAIL) {
return;
}
}
@@ -957,30 +892,21 @@ void do_all_autocmd_events(char *pat, bool once, int nested, char *cmd, bool del
// If *cmd == NUL: show entries.
// If forceit == true: delete entries.
// If group is not AUGROUP_ALL: only use this group.
-int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd, bool delete,
+int do_autocmd_event(event_T event, const char *pat, bool once, int nested, char *cmd, bool del,
int group)
FUNC_ATTR_NONNULL_ALL
{
// Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events
- assert(*pat != NUL || delete);
+ assert(*pat != NUL || del);
- AutoPat *ap;
- AutoPat **prev_ap;
- int findgroup;
- int buflocal_nr;
char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
bool is_adding_cmd = *cmd != NUL;
-
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// Delete all aupat for an event.
- if (*pat == NUL && delete) {
- aupat_del_for_event_and_group(event, findgroup);
+ if (*pat == NUL && del) {
+ aucmd_del_for_event_and_group(event, findgroup);
return OK;
}
@@ -989,9 +915,8 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
while (patlen) {
// detect special <buffer[=X]> buffer-local patterns
int is_buflocal = aupat_is_buflocal(pat, patlen);
-
if (is_buflocal) {
- buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
+ const int buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
// normalize pat into standard "<buffer>#N" form
aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr);
@@ -1000,34 +925,28 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
patlen = (int)strlen(buflocal_pat);
}
- if (delete) {
+ if (del) {
assert(*pat != NUL);
- // Find AutoPat entries with this pattern.
- prev_ap = &first_autopat[(int)event];
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- // Remove existing autocommands.
- // If adding any new autocmd's for this AutoPat, don't
- // delete the pattern from the autopat list, append to
- // this list.
- if (is_adding_cmd && ap->next == NULL) {
- aupat_remove_cmds(ap);
- break;
- }
- aupat_del(ap);
- }
+ // Find existing autocommands with this pattern.
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
+ // Accept a pattern when:
+ // - a group was specified and it's that group
+ // - the length of the pattern matches
+ // - the pattern matches.
+ // For <buffer[=X]>, this condition works because we normalize
+ // all buffer-local patterns.
+ if (ap != NULL && ap->group == findgroup && ap->patlen == patlen
+ && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
+ // Remove existing autocommands.
+ // If adding any new autocmd's for this AutoPat, don't
+ // delete the pattern from the autopat list, append to
+ // this list.
+ aucmd_del(ac);
}
- prev_ap = &ap->next;
}
}
@@ -1046,32 +965,23 @@ int do_autocmd_event(event_T event, char *pat, bool once, int nested, char *cmd,
return OK;
}
-int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group, bool once,
+int autocmd_register(int64_t id, event_T event, const char *pat, int patlen, int group, bool once,
bool nested, char *desc, AucmdExecutable aucmd)
{
// 0 is not a valid group.
assert(group != 0);
- AutoPat *ap;
- AutoPat **prev_ap;
- AutoCmd *ac;
- int findgroup;
- char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
-
if (patlen > (int)strlen(pat)) {
return FAIL;
}
- if (group == AUGROUP_ALL) {
- findgroup = current_augroup;
- } else {
- findgroup = group;
- }
+ const int findgroup = group == AUGROUP_ALL ? current_augroup : group;
// detect special <buffer[=X]> buffer-local patterns
- int is_buflocal = aupat_is_buflocal(pat, patlen);
+ const int is_buflocal = aupat_is_buflocal(pat, patlen);
int buflocal_nr = 0;
+ char buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>"
if (is_buflocal) {
buflocal_nr = aupat_get_buflocal_nr(pat, patlen);
@@ -1082,67 +992,52 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
patlen = (int)strlen(buflocal_pat);
}
- // always goes at or after the last one, so start at the end.
- if (last_autopat[(int)event] != NULL) {
- prev_ap = &last_autopat[(int)event];
- } else {
- prev_ap = &first_autopat[(int)event];
- }
-
- while ((ap = *prev_ap) != NULL) {
- if (ap->pat != NULL) {
- // Accept a pattern when:
- // - a group was specified and it's that group
- // - the length of the pattern matches
- // - the pattern matches.
- // For <buffer[=X]>, this condition works because we normalize
- // all buffer-local patterns.
- if (ap->group == findgroup
- && ap->patlen == patlen
- && strncmp(pat, ap->pat, (size_t)patlen) == 0) {
- if (ap->next == NULL) {
- // Add autocmd to this autopat, if it's the last one.
- break;
- }
- }
+ // Try to reuse pattern from the last existing autocommand.
+ AutoPat *ap = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (ptrdiff_t i = (ptrdiff_t)kv_size(*acs) - 1; i >= 0; i--) {
+ ap = kv_A(*acs, i).pat;
+ if (ap == NULL) {
+ continue; // Skip deleted autocommands.
+ }
+ // Set result back to NULL if the last pattern doesn't match.
+ if (ap->group != findgroup || ap->patlen != patlen
+ || strncmp(pat, ap->pat, (size_t)patlen) != 0) {
+ ap = NULL;
}
- prev_ap = &ap->next;
+ break;
}
- // If the pattern we want to add a command to does appear at the
- // end of the list (or not is not in the list at all), add the
- // pattern at the end of the list.
+ // No matching pattern found, allocate a new one.
if (ap == NULL) {
// refuse to add buffer-local ap if buffer number is invalid
- if (is_buflocal
- && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
+ if (is_buflocal && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) {
semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr);
return FAIL;
}
ap = xmalloc(sizeof(AutoPat));
- ap->pat = xstrnsave(pat, (size_t)patlen);
- ap->patlen = patlen;
if (is_buflocal) {
ap->buflocal_nr = buflocal_nr;
ap->reg_prog = NULL;
} else {
- char *reg_pat;
-
ap->buflocal_nr = 0;
- reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
+ char *reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true);
if (reg_pat != NULL) {
ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC);
}
xfree(reg_pat);
if (reg_pat == NULL || ap->reg_prog == NULL) {
- xfree(ap->pat);
xfree(ap);
return FAIL;
}
}
+ ap->refcount = 0;
+ ap->pat = xmemdupz(pat, (size_t)patlen);
+ ap->patlen = patlen;
+
// need to initialize last_mode for the first ModeChanged autocmd
if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) {
get_mode(last_mode);
@@ -1151,7 +1046,8 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
// If the event is CursorMoved, update the last cursor position
// position to avoid immediately triggering the autocommand
if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) {
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
// Initialize the fields checked by the WinScrolled and
@@ -1169,53 +1065,34 @@ int autocmd_register(int64_t id, event_T event, char *pat, int patlen, int group
use_tabpage(save_curtab);
}
- ap->cmds = NULL;
- *prev_ap = ap;
- last_autopat[(int)event] = ap;
- ap->next = NULL;
- if (group == AUGROUP_ALL) {
- ap->group = current_augroup;
- } else {
- ap->group = group;
- }
- }
-
- // Add the autocmd at the end of the AutoCmd list.
- AutoCmd **prev_ac = &(ap->cmds);
- while ((ac = *prev_ac) != NULL) {
- prev_ac = &ac->next;
+ ap->group = group == AUGROUP_ALL ? current_augroup : group;
}
- ac = xmalloc(sizeof(AutoCmd));
- *prev_ac = ac;
+ ap->refcount++;
+ // Add the autocmd at the end of the AutoCmd vector.
+ AutoCmd *ac = kv_pushp(autocmds[(int)event]);
+ ac->pat = ap;
ac->id = id;
ac->exec = aucmd_exec_copy(aucmd);
ac->script_ctx = current_sctx;
ac->script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&ac->script_ctx);
- ac->next = NULL;
ac->once = once;
ac->nested = nested;
- ac->desc = NULL;
-
- // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there.
- // perhaps: <lua>DESCRIPTION or similar
- if (desc != NULL) {
- ac->desc = xstrdup(desc);
- }
+ ac->desc = desc == NULL ? NULL : xstrdup(desc);
return OK;
}
-size_t aucmd_pattern_length(char *pat)
+size_t aucmd_pattern_length(const char *pat)
FUNC_ATTR_PURE
{
if (*pat == NUL) {
return 0;
}
- char *endpat;
+ const char *endpat;
for (; *pat; pat = endpat + 1) {
// Find end of the pattern.
@@ -1226,8 +1103,7 @@ size_t aucmd_pattern_length(char *pat)
continue;
}
int brace_level = 0;
- for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\');
- endpat++) {
+ for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); endpat++) {
if (*endpat == '{') {
brace_level++;
} else if (*endpat == '}') {
@@ -1241,14 +1117,13 @@ size_t aucmd_pattern_length(char *pat)
return strlen(pat);
}
-char *aucmd_next_pattern(char *pat, size_t patlen)
+const char *aucmd_next_pattern(const char *pat, size_t patlen)
FUNC_ATTR_PURE
{
pat = pat + patlen;
if (*pat == ',') {
pat = pat + 1;
}
-
return pat;
}
@@ -1291,7 +1166,7 @@ int do_doautocmd(char *arg_start, bool do_msg, bool *did_something)
}
if (nothing_done && do_msg && !aborting()) {
- smsg(_("No matching autocommands: %s"), arg_start);
+ smsg(0, _("No matching autocommands: %s"), arg_start);
}
if (did_something != NULL) {
*did_something = !nothing_done;
@@ -1422,6 +1297,8 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
aco->save_curwin_handle = curwin->handle;
aco->save_curbuf = curbuf;
aco->save_prevwin_handle = prevwin == NULL ? 0 : prevwin->handle;
+ aco->save_State = State;
+
if (win != NULL) {
// There is a window for "buf" in the current tab page, make it the
// curwin. This is preferred, it has the least side effects (esp. if
@@ -1448,7 +1325,7 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf)
block_autocmds(); // We don't want BufEnter/WinEnter autocommands.
if (need_append) {
win_append(lastwin, auc_win);
- pmap_put(handle_T)(&window_handles, auc_win->handle, auc_win);
+ pmap_put(int)(&window_handles, auc_win->handle, auc_win);
win_config_float(auc_win, auc_win->w_float_config);
}
// Prevent chdir() call in win_enter_ext(), through do_autochdir()
@@ -1480,7 +1357,6 @@ void aucmd_restbuf(aco_save_T *aco)
if (aco->use_aucmd_win_idx >= 0) {
win_T *awp = aucmd_win[aco->use_aucmd_win_idx].auc_win;
- curbuf->b_nwindows--;
// Find "awp", it can't be closed, but it may be in another tab page.
// Do not trigger autocommands here.
block_autocmds();
@@ -1496,9 +1372,17 @@ void aucmd_restbuf(aco_save_T *aco)
}
}
win_found:
-
+ curbuf->b_nwindows--;
+ const bool save_stop_insert_mode = stop_insert_mode;
+ // May need to stop Insert mode if we were in a prompt buffer.
+ leaving_window(curwin);
+ // Do not stop Insert mode when already in Insert mode before.
+ if (aco->save_State & MODE_INSERT) {
+ stop_insert_mode = save_stop_insert_mode;
+ }
+ // Remove the window.
win_remove(curwin, NULL);
- pmap_del(handle_T)(&window_handles, curwin->handle);
+ pmap_del(int)(&window_handles, curwin->handle, NULL);
if (curwin->w_grid_alloc.chars != NULL) {
ui_comp_remove_grid(&curwin->w_grid_alloc);
ui_call_win_hide(curwin->w_grid_alloc.handle);
@@ -1535,6 +1419,7 @@ win_found:
globaldir = aco->globaldir;
// the buffer contents may have changed
+ VIsual_active = aco->save_VIsual_active;
check_cursor();
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
curwin->w_topline = curbuf->b_ml.ml_line_count;
@@ -1563,14 +1448,16 @@ win_found:
curwin = save_curwin;
curbuf = curwin->w_buffer;
prevwin = win_find_by_handle(aco->save_prevwin_handle);
+
// In case the autocommand moves the cursor to a position that does not
// exist in curbuf
+ VIsual_active = aco->save_VIsual_active;
check_cursor();
}
}
- check_cursor(); // just in case lines got deleted
VIsual_active = aco->save_VIsual_active;
+ check_cursor(); // just in case lines got deleted
if (VIsual_active) {
check_pos(curbuf, &VIsual);
}
@@ -1627,8 +1514,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
return false;
}
- bool did_cmd = apply_autocmds_group(event, fname, fname_io, force,
- AUGROUP_ALL, buf, NULL, NULL);
+ bool did_cmd = apply_autocmds_group(event, fname, fname_io, force, AUGROUP_ALL, buf, NULL, NULL);
if (did_cmd && aborting()) {
*retval = FAIL;
}
@@ -1640,7 +1526,7 @@ bool apply_autocmds_retval(event_T event, char *fname, char *fname_io, bool forc
/// @param event the autocommand to check
bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return first_autopat[event] != NULL;
+ return kv_size(autocmds[(int)event]) != 0;
}
/// Return true when there is a CursorHold/CursorHoldI autocommand defined for
@@ -1648,7 +1534,6 @@ bool has_event(event_T event) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return has_event((get_real_state() == MODE_NORMAL_BUSY ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI));
- // return first_autopat[] != NULL;
}
/// Return true if the CursorHold/CursorHoldI event can be triggered.
@@ -1682,9 +1567,8 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
char *sfname = NULL; // short file name
bool retval = false;
static int nesting = 0;
- AutoPat *ap;
char *save_cmdarg;
- long save_cmdbang;
+ varnumber_T save_cmdbang;
static int filechangeshell_busy = false;
proftime_T wait_time;
bool did_save_redobuff = false;
@@ -1693,8 +1577,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Quickly return if there are no autocommands for this event or
// autocommands are blocked.
- if (event == NUM_EVENTS || first_autopat[(int)event] == NULL
- || is_autocmd_blocked()) {
+ if (event == NUM_EVENTS || kv_size(autocmds[(int)event]) == 0 || is_autocmd_blocked()) {
goto BYPASS_AU;
}
@@ -1712,8 +1595,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// FileChangedShell never nests, because it can create an endless loop.
if (filechangeshell_busy
- && (event == EVENT_FILECHANGEDSHELL
- || event == EVENT_FILECHANGEDSHELLPOST)) {
+ && (event == EVENT_FILECHANGEDSHELL || event == EVENT_FILECHANGEDSHELLPOST)) {
goto BYPASS_AU;
}
@@ -1725,20 +1607,20 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Allow nesting of autocommands, but restrict the depth, because it's
// possible to create an endless loop.
if (nesting == 10) {
- emsg(_("E218: autocommand nesting too deep"));
+ emsg(_(e_autocommand_nesting_too_deep));
goto BYPASS_AU;
}
// Check if these autocommands are disabled. Used when doing ":all" or
// ":ball".
if ((autocmd_no_enter && (event == EVENT_WINENTER || event == EVENT_BUFENTER))
- || (autocmd_no_leave
- && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
+ || (autocmd_no_leave && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) {
goto BYPASS_AU;
}
// Save the autocmd_* variables and info about the current buffer.
char *save_autocmd_fname = autocmd_fname;
+ bool save_autocmd_fname_full = autocmd_fname_full;
int save_autocmd_bufnr = autocmd_bufnr;
char *save_autocmd_match = autocmd_match;
int save_autocmd_busy = autocmd_busy;
@@ -1767,13 +1649,10 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Allocate MAXPATHL for when eval_vars() resolves the fullpath.
autocmd_fname = xstrnsave(autocmd_fname, MAXPATHL);
}
+ autocmd_fname_full = false; // call FullName_save() later
// Set the buffer number to be used for <abuf>.
- if (buf == NULL) {
- autocmd_bufnr = 0;
- } else {
- autocmd_bufnr = buf->b_fnum;
- }
+ autocmd_bufnr = buf == NULL ? 0 : buf->b_fnum;
// When the file name is NULL or empty, use the file name of buffer "buf".
// Always use the full path of the file name to match with, in case
@@ -1814,6 +1693,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
|| event == EVENT_USER || event == EVENT_WINCLOSED
|| event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) {
fname = xstrdup(fname);
+ autocmd_fname_full = true; // don't expand it later
} else {
fname = FullName_save(fname, false);
}
@@ -1876,18 +1756,24 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
// Find first autocommand that matches
AutoPatCmd patcmd = {
- .curpat = first_autopat[(int)event],
- .group = group,
+ // aucmd_next will set lastpat back to NULL if there are no more autocommands left to run
+ .lastpat = NULL,
+ // current autocommand index
+ .auidx = 0,
+ // save vector size, to avoid an endless loop when more patterns
+ // are added when executing autocommands
+ .ausize = kv_size(autocmds[(int)event]),
.fname = fname,
.sfname = sfname,
.tail = tail,
+ .group = group,
.event = event,
.arg_bufnr = autocmd_bufnr,
};
- auto_next_pat(&patcmd, false);
+ aucmd_next(&patcmd);
- // found one, start executing the autocommands
- if (patcmd.curpat != NULL) {
+ // Found first autocommand, start executing them
+ if (patcmd.lastpat != NULL) {
// add to active_apc_list
patcmd.next = active_apc_list;
active_apc_list = &patcmd;
@@ -1896,20 +1782,14 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
patcmd.data = data;
// set v:cmdarg (only when there is a matching pattern)
- save_cmdbang = (long)get_vim_var_nr(VV_CMDBANG);
+ save_cmdbang = get_vim_var_nr(VV_CMDBANG);
if (eap != NULL) {
save_cmdarg = set_cmdarg(eap, NULL);
- set_vim_var_nr(VV_CMDBANG, (long)eap->forceit);
+ set_vim_var_nr(VV_CMDBANG, eap->forceit);
} else {
save_cmdarg = NULL; // avoid gcc warning
}
retval = true;
- // mark the last pattern, to avoid an endless loop when more patterns
- // are added when executing autocommands
- for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) {
- ap->last = false;
- }
- ap->last = true;
// Make sure cursor and topline are valid. The first time the current
// values are saved, restored by reset_lnums(). When nested only the
@@ -1920,9 +1800,14 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
check_lnums_nested(true);
}
+ const int save_did_emsg = did_emsg;
+ const bool save_ex_pressedreturn = get_pressedreturn();
+
// Execute the autocmd. The `getnextac` callback handles iteration.
- do_cmdline(NULL, getnextac, (void *)&patcmd,
- DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+ do_cmdline(NULL, getnextac, &patcmd, DOCMD_NOWAIT | DOCMD_VERBOSE | DOCMD_REPEAT);
+
+ did_emsg += save_did_emsg;
+ set_pressedreturn(save_ex_pressedreturn);
if (nesting == 1) {
// restore cursor and topline, unless they were changed
@@ -1947,6 +1832,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
estack_pop();
xfree(autocmd_fname);
autocmd_fname = save_autocmd_fname;
+ autocmd_fname_full = save_autocmd_fname_full;
autocmd_bufnr = save_autocmd_bufnr;
autocmd_match = save_autocmd_match;
current_sctx = save_current_sctx;
@@ -2028,8 +1914,7 @@ void unblock_autocmds(void)
// When v:termresponse was set while autocommands were blocked, trigger
// the autocommands now. Esp. useful when executing a shell command
// during startup (nvim -d).
- if (!is_autocmd_blocked()
- && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
+ if (!is_autocmd_blocked() && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) {
apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, false, curbuf);
}
}
@@ -2040,91 +1925,84 @@ bool is_autocmd_blocked(void)
return autocmd_blocked != 0;
}
-/// Find next autocommand pattern that matches.
-/// stop when 'last' flag is set
-void auto_next_pat(AutoPatCmd *apc, int stop_at_last)
+/// Find next matching autocommand.
+/// If next autocommand was not found, sets lastpat to NULL and cmdidx to SIZE_MAX on apc.
+static void aucmd_next(AutoPatCmd *apc)
{
- AutoPat *ap;
- AutoCmd *cp;
- char *s;
-
estack_T *const entry = ((estack_T *)exestack.ga_data) + exestack.ga_len - 1;
- // Clear the exestack entry for this ETYPE_AUCMD entry.
- XFREE_CLEAR(entry->es_name);
- entry->es_info.aucmd = NULL;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
+ assert(apc->ausize <= kv_size(*acs));
+ for (size_t i = apc->auidx; i < apc->ausize && !got_int; i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ AutoPat *const ap = ac->pat;
- for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) {
- apc->curpat = NULL;
-
- // Only use a pattern when it has not been removed, has commands and
- // the group matches. For buffer-local autocommands only check the
- // buffer number.
- if (ap->pat != NULL && ap->cmds != NULL
- && (apc->group == AUGROUP_ALL || apc->group == ap->group)) {
- // execution-condition
+ // Skip deleted autocommands.
+ if (ap == NULL) {
+ continue;
+ }
+ // Skip matching if pattern didn't change.
+ if (ap != apc->lastpat) {
+ // Skip autocommands that don't match the group.
+ if (apc->group != AUGROUP_ALL && apc->group != ap->group) {
+ continue;
+ }
+ // Skip autocommands that don't match the pattern or buffer number.
if (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- apc->fname,
- apc->sfname,
- apc->tail,
- ap->allow_dirs)
- : ap->buflocal_nr == apc->arg_bufnr) {
- const char *const name = event_nr2name(apc->event);
- s = _("%s Autocommands for \"%s\"");
-
- const size_t sourcing_name_len
- = (strlen(s) + strlen(name) + (size_t)ap->patlen + 1);
-
- char *const namep = xmalloc(sourcing_name_len);
- snprintf(namep, sourcing_name_len, s, name, ap->pat);
- if (p_verbose >= 8) {
- verbose_enter();
- smsg(_("Executing %s"), namep);
- verbose_leave();
- }
+ ? !match_file_pat(NULL, &ap->reg_prog, apc->fname, apc->sfname, apc->tail, ap->allow_dirs)
+ : ap->buflocal_nr != apc->arg_bufnr) {
+ continue;
+ }
- // Update the exestack entry for this autocmd.
- entry->es_name = namep;
- entry->es_info.aucmd = apc;
+ const char *const name = event_nr2name(apc->event);
+ const char *const s = _("%s Autocommands for \"%s\"");
- apc->curpat = ap;
- apc->nextcmd = ap->cmds;
- // mark last command
- for (cp = ap->cmds; cp->next != NULL; cp = cp->next) {
- cp->last = false;
- }
- cp->last = true;
+ const size_t sourcing_name_len = strlen(s) + strlen(name) + (size_t)ap->patlen + 1;
+ char *const namep = xmalloc(sourcing_name_len);
+ snprintf(namep, sourcing_name_len, s, name, ap->pat);
+ if (p_verbose >= 8) {
+ verbose_enter();
+ smsg(0, _("Executing %s"), namep);
+ verbose_leave();
}
- line_breakcheck();
- if (apc->curpat != NULL) { // found a match
- break;
- }
- }
- if (stop_at_last && ap->last) {
- break;
+
+ // Update the exestack entry for this autocmd.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_name = namep;
+ entry->es_info.aucmd = apc;
}
+
+ apc->lastpat = ap;
+ apc->auidx = i;
+
+ line_breakcheck();
+ return;
}
+
+ // Clear the exestack entry for this ETYPE_AUCMD entry.
+ XFREE_CLEAR(entry->es_name);
+ entry->es_info.aucmd = NULL;
+
+ apc->lastpat = NULL;
+ apc->auidx = SIZE_MAX;
}
static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
{
- bool ret = false;
Callback callback = ac->exec.callable.cb;
if (callback.type == kCallbackLua) {
Dictionary data = ARRAY_DICT_INIT;
PUT(data, "id", INTEGER_OBJ(ac->id));
PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event)));
- PUT(data, "match", CSTR_TO_OBJ((char *)autocmd_match));
- PUT(data, "file", CSTR_TO_OBJ((char *)autocmd_fname));
+ PUT(data, "match", CSTR_TO_OBJ(autocmd_match));
+ PUT(data, "file", CSTR_TO_OBJ(autocmd_fname));
PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr));
if (apc->data) {
PUT(data, "data", copy_object(*apc->data, NULL));
}
- int group = apc->curpat->group;
+ int group = ac->pat->group;
switch (group) {
case AUGROUP_ERROR:
abort(); // unreachable
@@ -2142,18 +2020,19 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
ADD_C(args, DICTIONARY_OBJ(data));
Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL);
+ bool ret = false;
if (result.type == kObjectTypeBoolean) {
ret = result.data.boolean;
}
api_free_dictionary(data);
api_free_object(result);
+ return ret;
} else {
typval_T argsin = TV_INITIAL_VALUE;
typval_T rettv = TV_INITIAL_VALUE;
callback_call(&callback, 0, &argsin, &rettv);
+ return false;
}
-
- return ret;
}
/// Get next autocommand command.
@@ -2166,51 +2045,23 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
(void)indent;
(void)do_concat;
- AutoPatCmd *acp = (AutoPatCmd *)cookie;
- char *retval;
+ AutoPatCmd *const apc = (AutoPatCmd *)cookie;
+ AutoCmdVec *const acs = &autocmds[(int)apc->event];
- // Can be called again after returning the last line.
- if (acp->curpat == NULL) {
+ aucmd_next(apc);
+ if (apc->lastpat == NULL) {
return NULL;
}
- // repeat until we find an autocommand to execute
- for (;;) {
- // skip removed commands
- while (acp->nextcmd != NULL
- && aucmd_exec_is_deleted(acp->nextcmd->exec)) {
- if (acp->nextcmd->last) {
- acp->nextcmd = NULL;
- } else {
- acp->nextcmd = acp->nextcmd->next;
- }
- }
-
- if (acp->nextcmd != NULL) {
- break;
- }
-
- // at end of commands, find next pattern that matches
- if (acp->curpat->last) {
- acp->curpat = NULL;
- } else {
- acp->curpat = acp->curpat->next;
- }
- if (acp->curpat != NULL) {
- auto_next_pat(acp, true);
- }
- if (acp->curpat == NULL) {
- return NULL;
- }
- }
-
- AutoCmd *ac = acp->nextcmd;
+ assert(apc->auidx < kv_size(*acs));
+ AutoCmd *const ac = &kv_A(*acs, apc->auidx);
+ assert(ac->pat != NULL);
bool oneshot = ac->once;
if (p_verbose >= 9) {
verbose_enter_scroll();
char *exec_to_string = aucmd_exec_to_string(ac, ac->exec);
- smsg(_("autocommand %s"), exec_to_string);
+ smsg(0, _("autocommand %s"), exec_to_string);
msg_puts("\n"); // don't overwrite this either
XFREE_CLEAR(exec_to_string);
verbose_leave_scroll();
@@ -2220,10 +2071,12 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// lua code, so that it works properly
autocmd_nested = ac->nested;
current_sctx = ac->script_ctx;
- acp->script_ctx = current_sctx;
+ apc->script_ctx = current_sctx;
+ char *retval;
if (ac->exec.type == CALLABLE_CB) {
- if (call_autocmd_callback(ac, acp)) {
+ // Can potentially reallocate kvec_t data and invalidate the ac pointer
+ if (call_autocmd_callback(ac, apc)) {
// If an autocommand callback returns true, delete the autocommand
oneshot = true;
}
@@ -2238,19 +2091,20 @@ char *getnextac(int c, void *cookie, int indent, bool do_concat)
// 2. make where we call do_cmdline for autocmds not have to return anything,
// and instead we loop over all the matches and just execute one-by-one.
// However, my expectation would be that could be expensive.
- retval = xstrdup("");
+ retval = xcalloc(1, 1);
} else {
retval = xstrdup(ac->exec.callable.cmd);
}
// Remove one-shot ("once") autocmd in anticipation of its execution.
if (oneshot) {
- aucmd_del(ac);
+ aucmd_del(&kv_A(*acs, apc->auidx));
}
- if (ac->last) {
- acp->nextcmd = NULL;
+
+ if (apc->auidx < apc->ausize) {
+ apc->auidx++;
} else {
- acp->nextcmd = ac->next;
+ apc->auidx = SIZE_MAX;
}
return retval;
@@ -2282,15 +2136,12 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
forward_slash(fname);
#endif
- for (AutoPat *ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) {
- if (ap->pat != NULL && ap->cmds != NULL
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ if (ap != NULL
&& (ap->buflocal_nr == 0
- ? match_file_pat(NULL,
- &ap->reg_prog,
- fname,
- sfname,
- tail,
- ap->allow_dirs)
+ ? match_file_pat(NULL, &ap->reg_prog, fname, sfname, tail, ap->allow_dirs)
: buf != NULL && ap->buflocal_nr == buf->b_fnum)) {
retval = true;
break;
@@ -2305,13 +2156,10 @@ bool has_autocmd(event_T event, char *sfname, buf_T *buf)
return retval;
}
-// Function given to ExpandGeneric() to obtain the list of autocommand group
-// names.
+// Function given to ExpandGeneric() to obtain the list of autocommand group names.
char *expand_get_augroup_name(expand_T *xp, int idx)
{
- // Required for ExpandGeneric
- (void)xp;
-
+ (void)xp; // Required for ExpandGeneric
return augroup_name(idx + 1);
}
@@ -2364,8 +2212,7 @@ char *set_context_in_autocmd(expand_T *xp, char *arg, int doautocmd)
// Function given to ExpandGeneric() to obtain the list of event names.
char *expand_get_event_name(expand_T *xp, int idx)
{
- // xp is a required parameter to be used with ExpandGeneric
- (void)xp;
+ (void)xp; // xp is a required parameter to be used with ExpandGeneric
// List group names
char *name = augroup_name(idx + 1);
@@ -2382,6 +2229,13 @@ char *expand_get_event_name(expand_T *xp, int idx)
return event_names[idx - next_augroup_id].name;
}
+/// Function given to ExpandGeneric() to obtain the list of event names. Don't
+/// include groups.
+char *get_event_name_no_group(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ return event_names[idx].name;
+}
+
/// Check whether given autocommand is supported
///
/// @param[in] event Event to check.
@@ -2406,7 +2260,8 @@ bool autocmd_supported(const char *const event)
/// exists("#Event#pat")
///
/// @param arg autocommand string
-bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
+bool au_exists(const char *const arg)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
buf_T *buflocal_buf = NULL;
bool retval = false;
@@ -2453,8 +2308,8 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
// Find the first autocommand for this event.
// If there isn't any, return false;
// If there is one and no pattern given, return true;
- AutoPat *ap = first_autopat[(int)event];
- if (ap == NULL) {
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ if (kv_size(*acs) == 0) {
goto theend;
}
@@ -2465,10 +2320,11 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT
}
// Check if there is an autocommand with the given pattern.
- for (; ap != NULL; ap = ap->next) {
- // only use a pattern when it has not been removed and has commands.
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoPat *const ap = kv_A(*acs, i).pat;
+ // Only use a pattern when it has not been removed.
// For buffer-local autocommands, path_fnamecmp() works fine.
- if (ap->pat != NULL && ap->cmds != NULL
+ if (ap != NULL
&& (group == AUGROUP_ALL || ap->group == group)
&& (pattern == NULL
|| (buflocal_buf == NULL
@@ -2485,17 +2341,15 @@ theend:
}
// Checks if a pattern is buflocal
-bool aupat_is_buflocal(char *pat, int patlen)
+bool aupat_is_buflocal(const char *pat, int patlen)
FUNC_ATTR_PURE
{
- return patlen >= 8
- && strncmp(pat, "<buffer", 7) == 0
- && (pat)[patlen - 1] == '>';
+ return patlen >= 8 && strncmp(pat, "<buffer", 7) == 0 && (pat)[patlen - 1] == '>';
}
-int aupat_get_buflocal_nr(char *pat, int patlen)
+int aupat_get_buflocal_nr(const char *pat, int patlen)
{
- assert(aupat_is_buflocal((char *)pat, patlen));
+ assert(aupat_is_buflocal(pat, patlen));
// "<buffer>"
if (patlen == 8) {
@@ -2518,7 +2372,7 @@ int aupat_get_buflocal_nr(char *pat, int patlen)
}
// normalize buffer pattern
-void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int buflocal_nr)
+void aupat_normalize_buflocal_pat(char *dest, const char *pat, int patlen, int buflocal_nr)
{
assert(aupat_is_buflocal(pat, patlen));
@@ -2527,13 +2381,10 @@ void aupat_normalize_buflocal_pat(char *dest, char *pat, int patlen, int bufloca
}
// normalize pat into standard "<buffer>#N" form
- snprintf(dest,
- BUFLOCAL_PAT_LEN,
- "<buffer=%d>",
- buflocal_nr);
+ snprintf(dest, BUFLOCAL_PAT_LEN, "<buffer=%d>", buflocal_nr);
}
-int autocmd_delete_event(int group, event_T event, char *pat)
+int autocmd_delete_event(int group, event_T event, const char *pat)
FUNC_ATTR_NONNULL_ALL
{
return do_autocmd_event(event, pat, false, false, "", true, group);
@@ -2549,12 +2400,12 @@ bool autocmd_delete_id(int64_t id)
// Note that since multiple AutoCmd objects can have the same ID, we need to do a full scan.
FOR_ALL_AUEVENTS(event) {
- FOR_ALL_AUPATS_IN_EVENT(event, ap) { // -V756
- for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) {
- if (ac->id == id) {
- aucmd_del(ac);
- success = true;
- }
+ AutoCmdVec *const acs = &autocmds[(int)event];
+ for (size_t i = 0; i < kv_size(*acs); i++) {
+ AutoCmd *const ac = &kv_A(*acs, i);
+ if (ac->id == id) {
+ aucmd_del(ac);
+ success = true;
}
}
}
@@ -2617,25 +2468,10 @@ AucmdExecutable aucmd_exec_copy(AucmdExecutable src)
abort();
}
-bool aucmd_exec_is_deleted(AucmdExecutable acc)
- FUNC_ATTR_PURE
-{
- switch (acc.type) {
- case CALLABLE_EX:
- return acc.callable.cmd == NULL;
- case CALLABLE_CB:
- return acc.callable.cb.type == kCallbackNone;
- case CALLABLE_NONE:
- return true;
- }
-
- abort();
-}
-
bool au_event_is_empty(event_T event)
FUNC_ATTR_PURE
{
- return first_autopat[event] == NULL;
+ return kv_size(autocmds[(int)event]) == 0;
}
// Arg Parsing Functions
@@ -2682,7 +2518,7 @@ static int arg_augroup_get(char **argp)
return AUGROUP_ALL;
}
- char *group_name = xstrnsave(arg, (size_t)(p - arg));
+ char *group_name = xmemdupz(arg, (size_t)(p - arg));
int group = augroup_find(group_name);
if (group == AUGROUP_ERROR) {
group = AUGROUP_ALL; // no match, use all groups
@@ -2709,11 +2545,38 @@ static bool arg_autocmd_flag_get(bool *flag, char **cmd_ptr, char *pattern, int
return false;
}
+/// When kFalse: VimSuspend should be triggered next.
+/// When kTrue: VimResume should be triggered next.
+/// When kNone: Currently triggering VimSuspend or VimResume.
+static TriState pending_vimresume = kFalse;
+
+static void vimresume_event(void **argv)
+{
+ apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
+ pending_vimresume = kFalse;
+}
+
+/// Trigger VimSuspend or VimResume autocommand.
+void may_trigger_vim_suspend_resume(bool suspend)
+{
+ if (suspend && pending_vimresume == kFalse) {
+ pending_vimresume = kNone;
+ apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
+ pending_vimresume = kTrue;
+ } else if (!suspend && pending_vimresume == kTrue) {
+ pending_vimresume = kNone;
+ multiqueue_put(main_loop.events, vimresume_event, 0);
+ }
+}
+
// UI Enter
void do_autocmd_uienter(uint64_t chanid, bool attached)
{
static bool recursive = false;
+ if (starting == NO_SCREEN) {
+ return; // user config hasn't been sourced yet
+ }
if (recursive) {
return; // disallow recursion
}
@@ -2724,8 +2587,7 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
assert(chanid < VARNUMBER_MAX);
tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid);
tv_dict_set_keys_readonly(dict);
- apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE,
- NULL, NULL, false, curbuf);
+ apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, NULL, NULL, false, curbuf);
restore_v_event(dict, &save_v_event);
recursive = false;
@@ -2736,15 +2598,13 @@ void do_autocmd_uienter(uint64_t chanid, bool attached)
void do_autocmd_focusgained(bool gained)
{
static bool recursive = false;
- static Timestamp last_time = (time_t)0;
- bool need_redraw = false;
+ static Timestamp last_time = 0;
if (recursive) {
return; // disallow recursion
}
recursive = true;
- need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST),
- NULL, NULL, false, curbuf);
+ apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), NULL, NULL, false, curbuf);
// When activated: Check if any file was modified outside of Vim.
// Only do this when not done within the last two seconds as:
@@ -2752,60 +2612,38 @@ void do_autocmd_focusgained(bool gained)
// has a granularity of 2 seconds.
// 2. We could get multiple notifications in a row.
if (gained && last_time + (Timestamp)2000 < os_now()) {
- need_redraw = check_timestamps(true);
+ check_timestamps(true);
last_time = os_now();
}
- if (need_redraw) {
- // Something was executed, make sure the cursor is put back where it
- // belongs.
- need_wait_return = false;
-
- if (State & MODE_CMDLINE) {
- redrawcmdline();
- } else if ((State & MODE_NORMAL) || (State & MODE_INSERT)) {
- if (must_redraw != 0) {
- update_screen();
- }
+ recursive = false;
+}
- setcursor();
- }
+void do_filetype_autocmd(buf_T *buf, bool force)
+{
+ static int ft_recursive = 0;
- ui_flush();
+ if (ft_recursive > 0 && !force) {
+ return; // disallow recursion
}
- if (need_maketitle) {
- maketitle();
- }
+ char **varp = &buf->b_p_ft;
+ int secure_save = secure;
- recursive = false;
-}
+ // Reset the secure flag, since the value of 'filetype' has
+ // been checked to be safe.
+ secure = 0;
-static void define_autocmd(event_T event, char *pat, char *group, bool once, bool nested, char *cmd)
-{
- AucmdExecutable exec = AUCMD_EXECUTABLE_INIT;
- exec.type = CALLABLE_EX;
- exec.callable.cmd = cmd; // autocmd_register() makes a copy
- int group_id = augroup_add(group);
- autocmd_register(0, event, pat, (int)strlen(pat), group_id, once, nested, NULL, exec);
-}
-
-/// initialization of default autocmds
-void init_default_autocmds(void)
-{
- // open terminals when opening files that start with term://
-#define PROTO "term://"
- define_autocmd(EVENT_BUFREADCMD, PROTO "*", "nvim_terminal", false, true,
- "if !exists('b:term_title')|call termopen("
- // Capture the command string
- "matchstr(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
- // capture the working directory
- "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), "
- "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})"
- "|endif");
-#undef PROTO
- // limit syntax synchronization in the command window
- define_autocmd(EVENT_CMDWINENTER, "[:>]", "nvim_cmdwin", false, false,
- "syntax sync minlines=1 maxlines=1");
+ ft_recursive++;
+ did_filetype = true;
+ // Only pass true for "force" when it is true or
+ // used recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname, force || ft_recursive == 1, buf);
+ ft_recursive--;
+
+ // Just in case the old "buf" is now invalid
+ if (varp != &(buf->b_p_ft)) {
+ varp = NULL;
+ }
+ secure = secure_save;
}
diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h
index 791b589167..259a56cf5c 100644
--- a/src/nvim/autocmd.h
+++ b/src/nvim/autocmd.h
@@ -1,81 +1,18 @@
-#ifndef NVIM_AUTOCMD_H
-#define NVIM_AUTOCMD_H
+#pragma once
#include <stdbool.h>
+#include <stddef.h>
#include <stdint.h>
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/autocmd_defs.h" // IWYU pragma: export
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
-#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
-
-struct AutoCmd_S;
-struct AutoPatCmd_S;
-struct AutoPat_S;
-
-// event_T definition
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "auevents_enum.generated.h"
-#endif
-
-// Struct to save values in before executing autocommands for a buffer that is
-// not the current buffer.
-typedef struct {
- buf_T *save_curbuf; ///< saved curbuf
- int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0
- handle_T save_curwin_handle; ///< ID of saved curwin
- handle_T new_curwin_handle; ///< ID of new curwin
- handle_T save_prevwin_handle; ///< ID of saved prevwin
- bufref_T new_curbuf; ///< new curbuf
- char *globaldir; ///< saved value of globaldir
- bool save_VIsual_active; ///< saved VIsual_active
-} aco_save_T;
-
-typedef struct AutoCmd_S AutoCmd;
-struct AutoCmd_S {
- AucmdExecutable exec;
- bool once; // "One shot": removed after execution
- bool nested; // If autocommands nest here
- bool last; // last command in list
- int64_t id; // ID used for uniquely tracking an autocmd.
- sctx_T script_ctx; // script context where it is defined
- char *desc; // Description for the autocmd.
- AutoCmd *next; // Next AutoCmd in list
-};
-
-typedef struct AutoPat_S AutoPat;
-struct AutoPat_S {
- AutoPat *next; // next AutoPat in AutoPat list; MUST
- // be the first entry
- char *pat; // pattern as typed (NULL when pattern
- // has been removed)
- regprog_T *reg_prog; // compiled regprog for pattern
- AutoCmd *cmds; // list of commands to do
- int group; // group ID
- int patlen; // strlen() of pat
- int buflocal_nr; // !=0 for buffer-local AutoPat
- char allow_dirs; // Pattern may match whole path
- char last; // last pattern for apply_autocmds()
-};
-
-/// Struct used to keep status while executing autocommands for an event.
-typedef struct AutoPatCmd_S AutoPatCmd;
-struct AutoPatCmd_S {
- AutoPat *curpat; // next AutoPat to examine
- AutoCmd *nextcmd; // next AutoCmd to execute
- int group; // group being used
- char *fname; // fname to match with
- char *sfname; // sfname to match with
- char *tail; // tail of fname
- event_T event; // current event
- sctx_T script_ctx; // script context where it is defined
- int arg_bufnr; // initially equal to <abuf>, set to zero when buf is deleted
- Object *data; // arbitrary data
- AutoPatCmd *next; // chain of active apc-s for auto-invalidation
-};
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
// Set by the apply_autocmds_group function if the given event is equal to
// EVENT_FILETYPE. Used by the readfile function in order to determine if
@@ -83,7 +20,12 @@ struct AutoPatCmd_S {
//
// Relying on this value requires one to reset it prior calling
// apply_autocmds_group.
-EXTERN bool au_did_filetype INIT(= false);
+EXTERN bool au_did_filetype INIT( = false);
+
+/// For CursorMoved event
+EXTERN win_T *last_cursormoved_win INIT( = NULL);
+/// For CursorMoved event, only used when last_cursormoved_win == curwin
+EXTERN pos_T last_cursormoved INIT( = { 0, 0, 0 });
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "autocmd.h.generated.h"
@@ -100,5 +42,3 @@ EXTERN bool au_did_filetype INIT(= false);
/// Iterates over all the events for auto commands
#define FOR_ALL_AUEVENTS(event) \
for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT
-
-#endif // NVIM_AUTOCMD_H
diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h
new file mode 100644
index 0000000000..4639ec2731
--- /dev/null
+++ b/src/nvim/autocmd_defs.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/regexp_defs.h"
+#include "nvim/types_defs.h"
+
+// event_T definition
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "auevents_enum.generated.h"
+#endif
+
+/// Struct to save values in before executing autocommands for a buffer that is
+/// not the current buffer.
+typedef struct {
+ buf_T *save_curbuf; ///< saved curbuf
+ int use_aucmd_win_idx; ///< index in aucmd_win[] if >= 0
+ handle_T save_curwin_handle; ///< ID of saved curwin
+ handle_T new_curwin_handle; ///< ID of new curwin
+ handle_T save_prevwin_handle; ///< ID of saved prevwin
+ bufref_T new_curbuf; ///< new curbuf
+ char *globaldir; ///< saved value of globaldir
+ bool save_VIsual_active; ///< saved VIsual_active
+ int save_State; ///< saved State
+} aco_save_T;
+
+typedef struct {
+ size_t refcount; ///< Reference count (freed when reaches zero)
+ char *pat; ///< Pattern as typed
+ regprog_T *reg_prog; ///< Compiled regprog for pattern
+ int group; ///< Group ID
+ int patlen; ///< strlen() of pat
+ int buflocal_nr; ///< !=0 for buffer-local AutoPat
+ char allow_dirs; ///< Pattern may match whole path
+} AutoPat;
+
+typedef struct {
+ AucmdExecutable exec; ///< Command or callback function
+ AutoPat *pat; ///< Pattern reference (NULL when autocmd was removed)
+ int64_t id; ///< ID used for uniquely tracking an autocmd
+ char *desc; ///< Description for the autocmd
+ sctx_T script_ctx; ///< Script context where it is defined
+ bool once; ///< "One shot": removed after execution
+ bool nested; ///< If autocommands nest here
+} AutoCmd;
+
+/// Struct used to keep status while executing autocommands for an event.
+typedef struct AutoPatCmd_S AutoPatCmd;
+struct AutoPatCmd_S {
+ AutoPat *lastpat; ///< Last matched AutoPat
+ size_t auidx; ///< Current autocmd index to execute
+ size_t ausize; ///< Saved AutoCmd vector size
+ char *fname; ///< Fname to match with
+ char *sfname; ///< Sfname to match with
+ char *tail; ///< Tail of fname
+ int group; ///< Group being used
+ event_T event; ///< Current event
+ sctx_T script_ctx; ///< Script context where it is defined
+ int arg_bufnr; ///< Initially equal to <abuf>, set to zero when buf is deleted
+ Object *data; ///< Arbitrary data
+ AutoPatCmd *next; ///< Chain of active apc-s for auto-invalidation
+};
+
+typedef kvec_t(AutoCmd) AutoCmdVec;
diff --git a/src/nvim/base64.c b/src/nvim/base64.c
new file mode 100644
index 0000000000..295dedd8d3
--- /dev/null
+++ b/src/nvim/base64.c
@@ -0,0 +1,215 @@
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "auto/config.h" // IWYU pragma: keep
+#include "nvim/base64.h"
+#include "nvim/memory.h"
+
+#ifdef HAVE_BE64TOH
+# include ENDIAN_INCLUDE_FILE
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "base64.c.generated.h" // IWYU pragma: export
+#endif
+
+static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+// Indices are 1-based because we use 0 to indicate a letter that is not part of the alphabet
+static const uint8_t char_to_index[256] = {
+ ['A'] = 1, ['B'] = 2, ['C'] = 3, ['D'] = 4, ['E'] = 5, ['F'] = 6, ['G'] = 7, ['H'] = 8,
+ ['I'] = 9, ['J'] = 10, ['K'] = 11, ['L'] = 12, ['M'] = 13, ['N'] = 14, ['O'] = 15, ['P'] = 16,
+ ['Q'] = 17, ['R'] = 18, ['S'] = 19, ['T'] = 20, ['U'] = 21, ['V'] = 22, ['W'] = 23, ['X'] = 24,
+ ['Y'] = 25, ['Z'] = 26, ['a'] = 27, ['b'] = 28, ['c'] = 29, ['d'] = 30, ['e'] = 31, ['f'] = 32,
+ ['g'] = 33, ['h'] = 34, ['i'] = 35, ['j'] = 36, ['k'] = 37, ['l'] = 38, ['m'] = 39, ['n'] = 40,
+ ['o'] = 41, ['p'] = 42, ['q'] = 43, ['r'] = 44, ['s'] = 45, ['t'] = 46, ['u'] = 47, ['v'] = 48,
+ ['w'] = 49, ['x'] = 50, ['y'] = 51, ['z'] = 52, ['0'] = 53, ['1'] = 54, ['2'] = 55, ['3'] = 56,
+ ['4'] = 57, ['5'] = 58, ['6'] = 59, ['7'] = 60, ['8'] = 61, ['9'] = 62, ['+'] = 63, ['/'] = 64,
+};
+
+#ifndef HAVE_BE64TOH
+static inline uint64_t htobe64(uint64_t host_64bits)
+{
+# ifdef ORDER_BIG_ENDIAN
+ return host_64bits;
+# else
+ uint8_t *buf = (uint8_t *)&host_64bits;
+ uint64_t ret = 0;
+ for (size_t i = 8; i; i--) {
+ ret |= ((uint64_t)buf[i - 1]) << ((8 - i) * 8);
+ }
+ return ret;
+# endif
+}
+
+static inline uint32_t htobe32(uint32_t host_32bits)
+{
+# ifdef ORDER_BIG_ENDIAN
+ return host_32bits;
+# else
+ uint8_t *buf = (uint8_t *)&host_32bits;
+ uint32_t ret = 0;
+ for (size_t i = 4; i; i--) {
+ ret |= ((uint32_t)buf[i - 1]) << ((4 - i) * 8);
+ }
+ return ret;
+# endif
+}
+#endif
+
+/// Encode a string using Base64.
+///
+/// @param src String to encode
+/// @param src_len Length of the string
+/// @return Base64 encoded string
+char *base64_encode(const char *src, size_t src_len)
+{
+ assert(src != NULL);
+
+ const size_t out_len = ((src_len + 2) / 3) * 4;
+ char *dest = xmalloc(out_len + 1);
+
+ size_t src_i = 0;
+ size_t out_i = 0;
+
+ const uint8_t *s = (const uint8_t *)src;
+
+ // Read 8 bytes at a time as much as we can
+ for (; src_i + 7 < src_len; src_i += 6) {
+ uint64_t bits_h;
+ memcpy(&bits_h, &s[src_i], sizeof(uint64_t));
+ const uint64_t bits_be = htobe64(bits_h);
+ dest[out_i + 0] = alphabet[(bits_be >> 58) & 0x3F];
+ dest[out_i + 1] = alphabet[(bits_be >> 52) & 0x3F];
+ dest[out_i + 2] = alphabet[(bits_be >> 46) & 0x3F];
+ dest[out_i + 3] = alphabet[(bits_be >> 40) & 0x3F];
+ dest[out_i + 4] = alphabet[(bits_be >> 34) & 0x3F];
+ dest[out_i + 5] = alphabet[(bits_be >> 28) & 0x3F];
+ dest[out_i + 6] = alphabet[(bits_be >> 22) & 0x3F];
+ dest[out_i + 7] = alphabet[(bits_be >> 16) & 0x3F];
+ out_i += sizeof(uint64_t);
+ }
+
+ for (; src_i + 3 < src_len; src_i += 3) {
+ uint32_t bits_h;
+ memcpy(&bits_h, &s[src_i], sizeof(uint32_t));
+ const uint32_t bits_be = htobe32(bits_h);
+ dest[out_i + 0] = alphabet[(bits_be >> 26) & 0x3F];
+ dest[out_i + 1] = alphabet[(bits_be >> 20) & 0x3F];
+ dest[out_i + 2] = alphabet[(bits_be >> 14) & 0x3F];
+ dest[out_i + 3] = alphabet[(bits_be >> 8) & 0x3F];
+ out_i += sizeof(uint32_t);
+ }
+
+ if (src_i + 2 < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[((s[src_i] & 0x3) << 4) | (s[src_i + 1] >> 4)];
+ dest[out_i + 2] = alphabet[(s[src_i + 1] & 0xF) << 2 | (s[src_i + 2] >> 6)];
+ dest[out_i + 3] = alphabet[(s[src_i + 2] & 0x3F)];
+ out_i += 4;
+ } else if (src_i + 1 < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[((s[src_i] & 0x3) << 4) | (s[src_i + 1] >> 4)];
+ dest[out_i + 2] = alphabet[(s[src_i + 1] & 0xF) << 2];
+ out_i += 3;
+ } else if (src_i < src_len) {
+ dest[out_i + 0] = alphabet[s[src_i] >> 2];
+ dest[out_i + 1] = alphabet[(s[src_i] & 0x3) << 4];
+ out_i += 2;
+ }
+
+ for (; out_i < out_len; out_i++) {
+ dest[out_i] = '=';
+ }
+
+ dest[out_len] = '\0';
+
+ return dest;
+}
+
+/// Decode a Base64 encoded string.
+///
+/// @param src Base64 encoded string
+/// @param src_len Length of {src}
+/// @return Decoded string
+char *base64_decode(const char *src, size_t src_len)
+{
+ assert(src != NULL);
+
+ char *dest = NULL;
+
+ if (src_len % 4 != 0) {
+ goto invalid;
+ }
+
+ size_t out_len = (src_len / 4) * 3;
+ if (src_len >= 1 && src[src_len - 1] == '=') {
+ out_len--;
+ }
+ if (src_len >= 2 && src[src_len - 2] == '=') {
+ out_len--;
+ }
+
+ const uint8_t *s = (const uint8_t *)src;
+
+ dest = xmalloc(out_len + 1);
+
+ int acc = 0;
+ int acc_len = 0;
+ size_t out_i = 0;
+ size_t src_i = 0;
+ int leftover_i = -1;
+
+ for (; src_i < src_len; src_i++) {
+ const uint8_t c = s[src_i];
+ const uint8_t d = char_to_index[c];
+ if (d == 0) {
+ if (c == '=') {
+ leftover_i = (int)src_i;
+ break;
+ }
+ goto invalid;
+ }
+
+ acc = ((acc << 6) & 0xFFF) + (d - 1);
+ acc_len += 6;
+ if (acc_len >= 8) {
+ acc_len -= 8;
+ dest[out_i] = (char)(acc >> acc_len);
+ out_i += 1;
+ }
+ }
+
+ if (acc_len > 4 || ((acc & ((1 << acc_len) - 1)) != 0)) {
+ goto invalid;
+ }
+
+ if (leftover_i > -1) {
+ int padding_len = acc_len / 2;
+ int padding_chars = 0;
+ for (; (size_t)leftover_i < src_len; leftover_i++) {
+ const uint8_t c = s[leftover_i];
+ if (c != '=') {
+ goto invalid;
+ }
+ padding_chars += 1;
+ }
+
+ if (padding_chars != padding_len) {
+ goto invalid;
+ }
+ }
+
+ dest[out_len] = '\0';
+
+ return dest;
+
+invalid:
+ if (dest) {
+ xfree((void *)dest);
+ }
+
+ return NULL;
+}
diff --git a/src/nvim/base64.h b/src/nvim/base64.h
new file mode 100644
index 0000000000..511aa27d10
--- /dev/null
+++ b/src/nvim/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <stddef.h> // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "base64.h.generated.h"
+#endif
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 5dcb10751f..8a594dea92 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// buffer.c: functions for dealing with the buffer structure
//
@@ -32,8 +29,8 @@
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
@@ -47,6 +44,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -67,48 +65,51 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
+#include "nvim/memfile_defs.h"
#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
-#include "nvim/sign.h"
#include "nvim/spell.h"
+#include "nvim/state_defs.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.c.generated.h"
#endif
-static char *e_auabort = N_("E855: Autocommands caused command to abort");
-static char e_attempt_to_delete_buffer_that_is_in_use_str[]
+static const char *e_auabort = N_("E855: Autocommands caused command to abort");
+static const char e_attempt_to_delete_buffer_that_is_in_use_str[]
= N_("E937: Attempt to delete a buffer that is in use: %s");
// Number of times free_buffer() was called.
@@ -135,21 +136,20 @@ int get_highest_fnum(void)
static int read_buffer(int read_stdin, exarg_T *eap, int flags)
{
int retval = OK;
- linenr_T line_count;
bool silent = shortmess(SHM_FILEINFO);
// Read from the buffer which the text is already filled in and append at
// the end. This makes it possible to retry when 'fileformat' or
// 'fileencoding' was guessed wrong.
- line_count = curbuf->b_ml.ml_line_count;
+ linenr_T line_count = curbuf->b_ml.ml_line_count;
retval = readfile(read_stdin ? NULL : curbuf->b_ffname,
read_stdin ? NULL : curbuf->b_fname,
- line_count, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ line_count, 0, (linenr_T)MAXLNUM, eap,
flags | READ_BUFFER, silent);
if (retval == OK) {
// Delete the binary lines.
while (--line_count >= 0) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
} else {
// Delete the converted lines.
@@ -165,7 +165,7 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags)
// Set or reset 'modified' before executing autocommands, so that
// it can be changed there.
if (!readonlymode && !buf_is_empty(curbuf)) {
- changed();
+ changed(curbuf);
} else if (retval != FAIL) {
unchanged(curbuf, false, true);
}
@@ -176,20 +176,22 @@ static int read_buffer(int read_stdin, exarg_T *eap, int flags)
return retval;
}
-/// Ensure buffer "buf" is loaded. Does not trigger the swap-exists action.
-void buffer_ensure_loaded(buf_T *buf)
+/// Ensure buffer "buf" is loaded.
+bool buf_ensure_loaded(buf_T *buf)
{
if (buf->b_ml.ml_mfp != NULL) {
- return;
+ // already open (common case)
+ return true;
}
aco_save_T aco;
// Make sure the buffer is in a window.
aucmd_prepbuf(&aco, buf);
- swap_exists_action = SEA_NONE;
- open_buffer(false, NULL, 0);
+ // status can be OK or NOTDONE (which also means ok/done)
+ int status = open_buffer(false, NULL, 0);
aucmd_restbuf(&aco);
+ return (status != FAIL);
}
/// Open current buffer, that is: open the memfile and read the file into
@@ -205,7 +207,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
int flags = flags_arg;
int retval = OK;
bufref_T old_curbuf;
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
int read_fifo = false;
bool silent = shortmess(SHM_FILEINFO);
@@ -249,6 +251,11 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
return FAIL;
}
+ // Do not sync this buffer yet, may first want to read the file.
+ if (curbuf->b_ml.ml_mfp != NULL) {
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
+ }
+
// The autocommands in readfile() may change the buffer, but only AFTER
// reading the file.
set_bufref(&old_curbuf, curbuf);
@@ -269,7 +276,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
int save_bin = curbuf->b_p_bin;
int perm;
- perm = os_getperm((const char *)curbuf->b_ffname);
+ perm = os_getperm(curbuf->b_ffname);
if (perm >= 0 && (0 || S_ISFIFO(perm)
|| S_ISSOCK(perm)
# ifdef OPEN_CHR_FILES
@@ -285,7 +292,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
#endif
retval = readfile(curbuf->b_ffname, curbuf->b_fname,
- (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ 0, 0, (linenr_T)MAXLNUM, eap,
flags | READ_NEW | (read_fifo ? READ_FIFO : 0), silent);
#ifdef UNIX
if (read_fifo) {
@@ -308,8 +315,8 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
// it possible to retry when 'fileformat' or 'fileencoding' was
// guessed wrong.
curbuf->b_p_bin = true;
- retval = readfile(NULL, NULL, (linenr_T)0,
- (linenr_T)0, (linenr_T)MAXLNUM, NULL,
+ retval = readfile(NULL, NULL, 0,
+ 0, (linenr_T)MAXLNUM, NULL,
flags | (READ_NEW + READ_STDIN), silent);
curbuf->b_p_bin = save_bin;
if (retval == OK) {
@@ -317,6 +324,12 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
}
}
+ // Can now sync this buffer in ml_sync_all().
+ if (curbuf->b_ml.ml_mfp != NULL
+ && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) {
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
+ }
+
// if first time loading this buffer, init b_chartab[]
if (curbuf->b_flags & BF_NEVERLOADED) {
(void)buf_init_chartab(curbuf, false);
@@ -333,7 +346,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL)
|| modified_was_set // ":set modified" used in autocmd
|| (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL)) {
- changed();
+ changed(curbuf);
} else if (retval != FAIL && !read_stdin && !read_fifo) {
unchanged(curbuf, false, true);
}
@@ -342,6 +355,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags_arg)
// Set last_changedtick to avoid triggering a TextChanged autocommand right
// after it was added.
curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
curbuf->b_last_changedtick_pum = buf_get_changedtick(curbuf);
// require "!" to overwrite the file, because it wasn't read completely
@@ -409,8 +423,8 @@ bool bufref_valid(bufref_T *bufref)
FUNC_ATTR_PURE
{
return bufref->br_buf_free_count == buf_free_count
- ? true
- : buf_valid(bufref->br_buf) && bufref->br_fnum == bufref->br_buf->b_fnum;
+ ? true
+ : buf_valid(bufref->br_buf) && bufref->br_fnum == bufref->br_buf->b_fnum;
}
/// Check that "buf" points to a valid buffer in the buffer list.
@@ -465,7 +479,7 @@ static bool can_unload_buffer(buf_T *buf)
/// Possible values:
/// 0 buffer becomes hidden
/// DOBUF_UNLOAD buffer is unloaded
-/// DOBUF_DELETE buffer is unloaded and removed from buffer list
+/// DOBUF_DEL buffer is unloaded and removed from buffer list
/// DOBUF_WIPE buffer is unloaded and really deleted
/// When doing all but the first one on the current buffer, the
/// caller should get a new buffer very soon!
@@ -735,12 +749,11 @@ void buf_clear_file(buf_T *buf)
void buf_clear(void)
{
linenr_T line_count = curbuf->b_ml.ml_line_count;
+ extmark_free_all(curbuf); // delete any extmarks
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
deleted_lines_mark(1, line_count); // prepare for display
- ml_close(curbuf, true); // free memline_T
- buf_clear_file(curbuf);
}
/// buf_freeall() - free all things allocated for a buffer that are related to
@@ -834,7 +847,7 @@ void buf_freeall(buf_T *buf, int flags)
/// itself (not the file, that must have been done already).
static void free_buffer(buf_T *buf)
{
- pmap_del(handle_T)(&buffer_handles, buf->b_fnum);
+ pmap_del(int)(&buffer_handles, buf->b_fnum, NULL);
buf_free_count++;
// b:changedtick uses an item in buf_T.
free_buffer_stuff(buf, kBffClearWinInfo);
@@ -848,9 +861,9 @@ static void free_buffer(buf_T *buf)
xfree(buf->b_prompt_text);
callback_free(&buf->b_prompt_callback);
callback_free(&buf->b_prompt_interrupt);
- clear_fmark(&buf->b_last_cursor);
- clear_fmark(&buf->b_last_insert);
- clear_fmark(&buf->b_last_change);
+ clear_fmark(&buf->b_last_cursor, 0);
+ clear_fmark(&buf->b_last_insert, 0);
+ clear_fmark(&buf->b_last_change, 0);
for (size_t i = 0; i < NMARKS; i++) {
free_fmark(buf->b_namedm[i]);
}
@@ -906,7 +919,6 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
buf_init_changedtick(buf);
}
uc_clear(&buf->b_ucmds); // clear local user commands
- buf_delete_signs(buf, "*"); // delete any signs
extmark_free_all(buf); // delete any extmarks
map_clear_mode(buf, MAP_ALL_MODES, true, false); // clear local mappings
map_clear_mode(buf, MAP_ALL_MODES, true, true); // clear local abbrevs
@@ -918,10 +930,14 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
/// Go to another buffer. Handles the result of the ATTENTION dialog.
void goto_buffer(exarg_T *eap, int start, int dir, int count)
{
+ const int save_sea = swap_exists_action;
+
bufref_T old_curbuf;
set_bufref(&old_curbuf, curbuf);
- swap_exists_action = SEA_DIALOG;
+ if (swap_exists_action == SEA_NONE) {
+ swap_exists_action = SEA_DIALOG;
+ }
(void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
start, dir, count, eap->forceit);
@@ -934,7 +950,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
// Quitting means closing the split window, nothing else.
win_close(curwin, true, false);
- swap_exists_action = SEA_NONE;
+ swap_exists_action = save_sea;
swap_exists_did_quit = true;
// Restore the error/interrupt/exception state if not discarded by a
@@ -953,7 +969,7 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
void handle_swap_exists(bufref_T *old_curbuf)
{
cleanup_T cs;
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
buf_T *buf;
if (swap_exists_action == SEA_QUIT) {
@@ -972,7 +988,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
|| old_curbuf->br_buf == curbuf) {
// Block autocommands here because curwin->w_buffer is NULL.
block_autocmds();
- buf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED);
+ buf = buflist_new(NULL, NULL, 1, BLN_CURBUF | BLN_LISTED);
unblock_autocmds();
} else {
buf = old_curbuf->br_buf;
@@ -1005,7 +1021,7 @@ void handle_swap_exists(bufref_T *old_curbuf)
// new aborting error, interrupt, or uncaught exception.
leave_cleanup(&cs);
}
- swap_exists_action = SEA_NONE; // -V519
+ swap_exists_action = SEA_NONE;
}
/// do_bufdel() - delete or unload buffer(s)
@@ -1027,9 +1043,8 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
{
int do_current = 0; // delete current buffer?
int deleted = 0; // number of buffers deleted
- char *errormsg = NULL; // return value
+ char *errormsg = NULL; // return value
int bnr; // buffer number
- char *p;
if (addr_count == 0) {
(void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit);
@@ -1066,7 +1081,7 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
break;
}
if (!ascii_isdigit(*arg)) {
- p = skiptowhite_esc(arg);
+ char *p = skiptowhite_esc(arg);
bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, false, false);
if (bnr < 0) { // failed
break;
@@ -1085,22 +1100,22 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
if (deleted == 0) {
if (command == DOBUF_UNLOAD) {
- STRCPY(IObuff, _("E515: No buffers were unloaded"));
+ xstrlcpy(IObuff, _("E515: No buffers were unloaded"), IOSIZE);
} else if (command == DOBUF_DEL) {
- STRCPY(IObuff, _("E516: No buffers were deleted"));
+ xstrlcpy(IObuff, _("E516: No buffers were deleted"), IOSIZE);
} else {
- STRCPY(IObuff, _("E517: No buffers were wiped out"));
+ xstrlcpy(IObuff, _("E517: No buffers were wiped out"), IOSIZE);
}
errormsg = IObuff;
} else if (deleted >= p_report) {
if (command == DOBUF_UNLOAD) {
- smsg(NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted),
+ smsg(0, NGETTEXT("%d buffer unloaded", "%d buffers unloaded", deleted),
deleted);
} else if (command == DOBUF_DEL) {
- smsg(NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted),
+ smsg(0, NGETTEXT("%d buffer deleted", "%d buffers deleted", deleted),
deleted);
} else {
- smsg(NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted),
+ smsg(0, NGETTEXT("%d buffer wiped out", "%d buffers wiped out", deleted),
deleted);
}
}
@@ -1113,7 +1128,6 @@ char *do_bufdel(int command, char *arg, int addr_count, int start_bnr, int end_b
/// Used when it is wiped out and it's the last buffer.
static int empty_curbuf(int close_others, int forceit, int action)
{
- int retval;
buf_T *buf = curbuf;
if (action == DOBUF_UNLOAD) {
@@ -1146,8 +1160,7 @@ static int empty_curbuf(int close_others, int forceit, int action)
}
setpcmark();
- retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE,
- forceit ? ECMD_FORCEIT : 0, curwin);
+ int retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, forceit ? ECMD_FORCEIT : 0, curwin);
// do_ecmd() may create a new buffer, then we have to delete
// the old one. But do_ecmd() may have done that already, check
@@ -1404,7 +1417,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (buf == NULL) { // No previous buffer, Try 2'nd approach
forward = true;
buf = curbuf->b_next;
- for (;;) {
+ while (true) {
if (buf == NULL) {
if (!forward) { // tried both directions
break;
@@ -1460,16 +1473,11 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
// make "buf" the current buffer
if (action == DOBUF_SPLIT) { // split window first
- // If 'switchbuf' contains "useopen": jump to first window containing
- // "buf" if one exists
- if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) {
- return OK;
- }
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "buf" if one exists
- if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) {
+ // If 'switchbuf' is set jump to the window containing "buf".
+ if (swbuf_goto_win_with_buf(buf) != NULL) {
return OK;
}
+
if (win_split(0, 0) == FAIL) {
return FAIL;
}
@@ -1525,7 +1533,7 @@ void set_curbuf(buf_T *buf, int action)
buf_T *prevbuf;
int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL
|| action == DOBUF_WIPE);
- long old_tw = curbuf->b_p_tw;
+ OptInt old_tw = curbuf->b_p_tw;
setpcmark();
if ((cmdmod.cmod_flags & CMOD_KEEPALT) == 0) {
@@ -1676,7 +1684,7 @@ void enter_buffer(buf_T *buf)
maketitle();
// when autocmds didn't change it
if (curwin->w_topline == 1 && !curwin->w_topline_was_set) {
- scroll_cursor_halfway(false); // redisplay at correct position
+ scroll_cursor_halfway(false, false); // redisplay at correct position
}
// Change directories when the 'acd' option is set.
@@ -1688,7 +1696,7 @@ void enter_buffer(buf_T *buf)
// May need to set the spell language. Can only do this after the buffer
// has been properly setup.
if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- (void)did_set_spelllang(curwin);
+ (void)parse_spelllang(curwin);
}
curbuf->b_last_used = time(NULL);
@@ -1791,7 +1799,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
xfree(ffname);
if (lnum != 0) {
buflist_setfpos(buf, (flags & BLN_NOCURWIN) ? NULL : curwin,
- lnum, (colnr_T)0, false);
+ lnum, 0, false);
}
if ((flags & BLN_NOOPT) == 0) {
// Copy the options now, if 'cpo' doesn't have 's' and not done already.
@@ -1836,7 +1844,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf = xcalloc(1, sizeof(buf_T));
// init b: variables
buf->b_vars = tv_dict_alloc();
- buf->b_signcols.valid = false;
+ buf->b_signcols.sentinel = 0;
init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE);
buf_init_changedtick(buf);
}
@@ -1871,12 +1879,12 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
lastbuf = buf;
buf->b_fnum = top_file_num++;
- pmap_put(handle_T)(&buffer_handles, buf->b_fnum, buf);
+ pmap_put(int)(&buffer_handles, buf->b_fnum, buf);
if (top_file_num < 0) { // wrap around (may cause duplicates)
emsg(_("W14: Warning: List of file names overflow"));
if (emsg_silent == 0 && !in_assert_fails) {
ui_flush();
- os_delay(3001L, true); // make sure it is noticed
+ os_delay(3001, true); // make sure it is noticed
}
top_file_num = 1;
}
@@ -1905,7 +1913,7 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
buf->b_flags |= BF_DUMMY;
}
buf_clear_file(buf);
- clrallmarks(buf); // clear marks
+ clrallmarks(buf, 0); // clear marks
fmarks_check_names(buf); // check file marks for this file
buf->b_p_bl = (flags & BLN_LISTED) ? true : false; // init 'buflisted'
kv_destroy(buf->update_channels);
@@ -2037,12 +2045,11 @@ void free_buf_options(buf_T *buf, int free_p_ff)
/// Return FAIL for failure, OK for success.
int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
{
- buf_T *buf;
win_T *wp = NULL;
fmark_T *fm = NULL;
colnr_T col;
- buf = buflist_findnr(n);
+ buf_T *buf = buflist_findnr(n);
if (buf == NULL) {
if ((options & GETF_ALT) && n == 0) {
emsg(_(e_noalt));
@@ -2073,17 +2080,8 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
}
if (options & GETF_SWITCH) {
- // If 'switchbuf' contains "useopen": jump to first window containing
- // "buf" if one exists
- if (swb_flags & SWB_USEOPEN) {
- wp = buf_jump_open_win(buf);
- }
-
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "buf" if one exists
- if (wp == NULL && (swb_flags & SWB_USETAB)) {
- wp = buf_jump_open_tab(buf);
- }
+ // If 'switchbuf' is set jump to the window containing "buf".
+ wp = swbuf_goto_win_with_buf(buf);
// If 'switchbuf' contains "split", "vsplit" or "newtab" and the
// current buffer isn't empty: open new tab or window
@@ -2123,12 +2121,10 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
/// Go to the last known line number for the current buffer.
void buflist_getfpos(void)
{
- pos_T *fpos;
-
- fpos = &buflist_findfmark(curbuf)->mark;
+ pos_T *fpos = &buflist_findfmark(curbuf)->mark;
curwin->w_cursor.lnum = fpos->lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
if (p_sol) {
curwin->w_cursor.col = 0;
@@ -2145,18 +2141,17 @@ void buflist_getfpos(void)
/// @return buffer or NULL if not found
buf_T *buflist_findname_exp(char *fname)
{
- char *ffname;
buf_T *buf = NULL;
// First make the name into a full path name
- ffname = FullName_save(fname,
+ char *ffname = FullName_save(fname,
#ifdef UNIX
- // force expansion, get rid of symbolic links
- true
+ // force expansion, get rid of symbolic links
+ true
#else
- false
+ false
#endif
- ); // NOLINT(whitespace/parens)
+ ); // NOLINT(whitespace/parens)
if (ffname != NULL) {
buf = buflist_findname(ffname);
xfree(ffname);
@@ -2206,12 +2201,6 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted,
FUNC_ATTR_NONNULL_ARG(1)
{
int match = -1;
- int find_listed;
- char *pat;
- char *patend;
- int attempt;
- char *p;
- int toggledollar;
if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) {
if (*pattern == '%') {
@@ -2232,23 +2221,24 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted,
// Repeat this for finding an unlisted buffer if there was no matching
// listed buffer.
- pat = file_pat_to_reg_pat((char *)pattern, (char *)pattern_end, NULL, false);
+ char *pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, false);
if (pat == NULL) {
return -1;
}
- patend = pat + strlen(pat) - 1;
- toggledollar = (patend > pat && *patend == '$');
+ char *patend = pat + strlen(pat) - 1;
+ int toggledollar = (patend > pat && *patend == '$');
// First try finding a listed buffer. If not found and "unlisted"
// is true, try finding an unlisted buffer.
- find_listed = true;
- for (;;) {
- for (attempt = 0; attempt <= 3; attempt++) {
+
+ int find_listed = true;
+ while (true) {
+ for (int attempt = 0; attempt <= 3; attempt++) {
// may add '^' and '$'
if (toggledollar) {
*patend = (attempt < 2) ? NUL : '$'; // add/remove '$'
}
- p = pat;
+ char *p = pat;
if (*p == '^' && !(attempt & 1)) { // add/remove '^'
p++;
}
@@ -2338,7 +2328,6 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
int count = 0;
int round;
char *p;
- int attempt;
bufmatch_T *matches = NULL;
*num_file = 0; // return values in case of FAIL
@@ -2366,7 +2355,7 @@ int ExpandBufnames(char *pat, int *num_file, char ***file, int options)
fuzmatch_str_T *fuzmatch = NULL;
// attempt == 0: try match with '\<', match at start of word
// attempt == 1: try match without '\<', match anywhere
- for (attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) {
+ for (int attempt = 0; attempt <= (fuzzy ? 0 : 1); attempt++) {
regmatch_T regmatch;
if (!fuzzy) {
if (attempt > 0 && patc == pat) {
@@ -2522,7 +2511,6 @@ static char *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case)
static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case)
{
char *match = NULL;
- char *p;
// extra check for valid arguments
if (name == NULL || rmp->regprog == NULL) {
@@ -2531,12 +2519,12 @@ static char *fname_match(regmatch_T *rmp, char *name, bool ignore_case)
// Ignore case when 'fileignorecase' or the argument is set.
rmp->rm_ic = p_fic || ignore_case;
- if (vim_regexec(rmp, name, (colnr_T)0)) {
+ if (vim_regexec(rmp, name, 0)) {
match = name;
} else if (rmp->regprog != NULL) {
// Replace $(HOME) with '~' and try matching again.
- p = home_replace_save(NULL, name);
- if (vim_regexec(rmp, p, (colnr_T)0)) {
+ char *p = home_replace_save(NULL, name);
+ if (vim_regexec(rmp, p, 0)) {
match = name;
}
xfree(p);
@@ -2793,7 +2781,7 @@ void buflist_list(exarg_T *eap)
for (;
buf != NULL && !got_int;
buf = buflist_data != NULL
- ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) {
+ ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) {
const bool is_terminal = buf->terminal;
const bool job_running = buf->terminal && terminal_running(buf->terminal);
@@ -2827,8 +2815,8 @@ void buflist_list(exarg_T *eap)
}
const int changed_char = (buf->b_flags & BF_READERR)
- ? 'x'
- : (bufIsChanged(buf) ? '+' : ' ');
+ ? 'x'
+ : (bufIsChanged(buf) ? '+' : ' ');
int ro_char = !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' ');
if (buf->terminal) {
ro_char = channel_job_running((uint64_t)buf->b_p_channel) ? 'R' : 'F';
@@ -2860,7 +2848,7 @@ void buflist_list(exarg_T *eap)
buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf));
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
line_breakcheck();
}
@@ -3034,7 +3022,7 @@ char *getaltfname(bool errmsg)
/// Used by qf_init(), main() and doarglist()
int buflist_add(char *fname, int flags)
{
- buf_T *buf = buflist_new(fname, NULL, (linenr_T)0, flags);
+ buf_T *buf = buflist_new(fname, NULL, 0, flags);
if (buf != NULL) {
return buf->b_fnum;
}
@@ -3153,10 +3141,8 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
char *name;
int n;
char *p;
- char *buffer;
- size_t len;
- buffer = xmalloc(IOSIZE);
+ char *buffer = xmalloc(IOSIZE);
if (fullname > 1) { // 2 CTRL-G: include buffer number
vim_snprintf(buffer, IOSIZE, "buf %d: ", curbuf->b_fnum);
@@ -3181,11 +3167,11 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
bool dontwrite = bt_dontwrite(curbuf);
vim_snprintf_add(buffer, IOSIZE, "\"%s%s%s%s%s%s",
curbufIsChanged()
- ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
+ ? (shortmess(SHM_MOD) ? " [+]" : _(" [Modified]")) : " ",
(curbuf->b_flags & BF_NOTEDITED) && !dontwrite
? _("[Not edited]") : "",
(curbuf->b_flags & BF_NEW) && !dontwrite
- ? new_file_message() : "",
+ ? _("[New]") : "",
(curbuf->b_flags & BF_READERR)
? _("[Read errors]") : "",
curbuf->b_p_ro
@@ -3196,12 +3182,12 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
? " " : "");
// With 32 bit longs and more than 21,474,836 lines multiplying by 100
// causes an overflow, thus for large numbers divide instead.
- if (curwin->w_cursor.lnum > 1000000L) {
- n = (int)(((long)curwin->w_cursor.lnum) /
- ((long)curbuf->b_ml.ml_line_count / 100L));
+ if (curwin->w_cursor.lnum > 1000000) {
+ n = ((curwin->w_cursor.lnum) /
+ (curbuf->b_ml.ml_line_count / 100));
} else {
- n = (int)(((long)curwin->w_cursor.lnum * 100L) /
- (long)curbuf->b_ml.ml_line_count);
+ n = ((curwin->w_cursor.lnum * 100) /
+ curbuf->b_ml.ml_line_count);
}
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
vim_snprintf_add(buffer, IOSIZE, "%s", _(no_lines_msg));
@@ -3219,12 +3205,12 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
(int64_t)curbuf->b_ml.ml_line_count,
n);
validate_virtcol();
- len = strlen(buffer);
+ size_t len = strlen(buffer);
col_print(buffer + len, IOSIZE - len,
(int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1);
}
- (void)append_arg_number(curwin, buffer, IOSIZE, !shortmess(SHM_FILE));
+ (void)append_arg_number(curwin, buffer, IOSIZE);
if (dont_truncate) {
// Temporarily set msg_scroll to avoid the message being truncated.
@@ -3232,10 +3218,10 @@ void fileinfo(int fullname, int shorthelp, int dont_truncate)
msg_start();
n = msg_scroll;
msg_scroll = true;
- msg(buffer);
+ msg(buffer, 0);
msg_scroll = n;
} else {
- p = msg_trunc_attr(buffer, false, 0);
+ p = msg_trunc(buffer, false, 0);
if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) {
// Need to repeat the message after redrawing when:
// - When restart_edit is set (otherwise there will be a delay
@@ -3268,7 +3254,6 @@ void maketitle(void)
char *icon_str = NULL;
int maxlen = 0;
int len;
- int mustset;
char buf[IOSIZE];
if (!redrawing()) {
@@ -3310,8 +3295,7 @@ void maketitle(void)
SPACE_FOR_FNAME + 1);
buf_p += MIN(size, SPACE_FOR_FNAME);
} else {
- buf_p += transstr_buf((const char *)path_tail(curbuf->b_fname),
- buf_p, SPACE_FOR_FNAME + 1, true);
+ buf_p += transstr_buf(path_tail(curbuf->b_fname), -1, buf_p, SPACE_FOR_FNAME + 1, true);
}
switch (bufIsChanged(curbuf)
@@ -3376,7 +3360,7 @@ void maketitle(void)
*buf_p = NUL;
}
- append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)), false);
+ append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)));
xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf)));
@@ -3392,7 +3376,7 @@ void maketitle(void)
#undef SPACE_FOR_ARGNR
}
}
- mustset = value_change(title_str, &lasttitle);
+ int mustset = value_change(title_str, &lasttitle);
if (p_icon) {
icon_str = buf;
@@ -3460,7 +3444,6 @@ void resettitle(void)
{
ui_call_set_icon(cstr_as_string(lasticon));
ui_call_set_title(cstr_as_string(lasttitle));
- ui_flush();
}
#if defined(EXITFREE)
@@ -3472,8 +3455,8 @@ void free_titles(void)
#endif
-/// Get relative cursor position in window into "buf[buflen]", in the form 99%,
-/// using "Top", "Bot" or "All" when appropriate.
+/// Get relative cursor position in window into "buf[buflen]", in the localized
+/// percentage form like %99, 99%; using "Top", "Bot" or "All" when appropriate.
void get_rel_pos(win_T *wp, char *buf, int buflen)
{
// Need at least 3 chars for writing.
@@ -3481,8 +3464,8 @@ void get_rel_pos(win_T *wp, char *buf, int buflen)
return;
}
- long above; // number of lines above window
- long below; // number of lines below window
+ linenr_T above; // number of lines above window
+ linenr_T below; // number of lines below window
above = wp->w_topline - 1;
above += win_get_fill(wp, wp->w_topline) - wp->w_topfill;
@@ -3497,21 +3480,31 @@ void get_rel_pos(win_T *wp, char *buf, int buflen)
} else if (above <= 0) {
xstrlcpy(buf, _("Top"), (size_t)buflen);
} else {
- vim_snprintf(buf, (size_t)buflen, "%2d%%", above > 1000000L
- ? (int)(above / ((above + below) / 100L))
- : (int)(above * 100L / (above + below)));
+ int perc = (above > 1000000
+ ? (above / ((above + below) / 100))
+ : (above * 100 / (above + below)));
+
+ char *p = buf;
+ size_t l = (size_t)buflen;
+ if (perc < 10) {
+ // prepend one space
+ buf[0] = ' ';
+ p++;
+ l--;
+ }
+ // localized percentage value
+ vim_snprintf(p, l, _("%d%%"), perc);
}
}
-/// Append (file 2 of 8) to "buf[buflen]", if editing more than one file.
+/// Append (2 of 8) to "buf[buflen]", if editing more than one file.
///
/// @param wp window whose buffers to check
/// @param[in,out] buf string buffer to add the text to
/// @param buflen length of the string buffer
-/// @param add_file if true, add "file" before the arg number
///
/// @return true if it was appended.
-bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
+bool append_arg_number(win_T *wp, char *buf, int buflen)
FUNC_ATTR_NONNULL_ALL
{
// Nothing to do
@@ -3519,23 +3512,10 @@ bool append_arg_number(win_T *wp, char *buf, int buflen, bool add_file)
return false;
}
- char *p = buf + strlen(buf); // go to the end of the buffer
-
- // Early out if the string is getting too long
- if (p - buf + 35 >= buflen) {
- return false;
- }
+ const char *msg = wp->w_arg_idx_invalid ? _(" ((%d) of %d)") : _(" (%d of %d)");
- *p++ = ' ';
- *p++ = '(';
- if (add_file) {
- STRCPY(p, "file ");
- p += 5;
- }
- vim_snprintf(p, (size_t)(buflen - (p - buf)),
- wp->w_arg_idx_invalid
- ? "(%d) of %d)"
- : "%d of %d)", wp->w_arg_idx + 1, ARGCOUNT);
+ char *p = buf + strlen(buf); // go to the end of the buffer
+ vim_snprintf(p, (size_t)(buflen - (p - buf)), msg, wp->w_arg_idx + 1, ARGCOUNT);
return true;
}
@@ -3558,7 +3538,7 @@ void fname_expand(buf_T *buf, char **ffname, char **sfname)
#ifdef MSWIN
if (!buf->b_p_bin) {
// If the file name is a shortcut file, use the file it links to.
- char *rfname = os_resolve_shortcut((const char *)(*ffname));
+ char *rfname = os_resolve_shortcut(*ffname);
if (rfname != NULL) {
xfree(*ffname);
*ffname = rfname;
@@ -3584,7 +3564,7 @@ void ex_buffer_all(exarg_T *eap)
bool p_ea_save;
int open_wins = 0;
int r;
- long count; // Maximum number of windows to open.
+ linenr_T count; // Maximum number of windows to open.
int all; // When true also load inactive buffers.
int had_tab = cmdmod.cmod_tab;
tabpage_T *tpnext;
@@ -3600,6 +3580,10 @@ void ex_buffer_all(exarg_T *eap)
all = true;
}
+ // Stop Visual mode, the cursor and "VIsual" may very well be invalid after
+ // switching to another buffer.
+ reset_VIsual_and_resel();
+
setpcmark();
// Close superfluous windows (two windows for the same buffer).
@@ -3607,23 +3591,30 @@ void ex_buffer_all(exarg_T *eap)
if (had_tab > 0) {
goto_tabpage_tp(first_tabpage, true, true);
}
- for (;;) {
+ while (true) {
tpnext = curtab->tp_next;
- for (wp = firstwin; wp != NULL; wp = wpnext) {
- wpnext = wp->w_next;
+ // Try to close floating windows first
+ for (wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
+ wpnext = wp->w_floating
+ ? wp->w_prev->w_floating ? wp->w_prev : firstwin
+ : (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next;
if ((wp->w_buffer->b_nwindows > 1
+ || wp->w_floating
|| ((cmdmod.cmod_split & WSP_VERT)
? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch
- tabline_height() - global_stl_height()
: wp->w_width != Columns)
|| (had_tab > 0 && wp != firstwin))
&& !ONE_WINDOW
- && !(wp->w_closing
- || wp->w_buffer->b_locked > 0)) {
- win_close(wp, false, false);
- wpnext = firstwin; // just in case an autocommand does
- // something strange with windows
- tpnext = first_tabpage; // start all over...
+ && !(wp->w_closing || wp->w_buffer->b_locked > 0)
+ && !is_aucmd_win(wp)) {
+ if (win_close(wp, false, false) == FAIL) {
+ break;
+ }
+ // Just in case an autocommand does something strange with
+ // windows: start all over...
+ wpnext = lastwin->w_floating ? lastwin : firstwin;
+ tpnext = first_tabpage;
open_wins = 0;
} else {
open_wins++;
@@ -3643,7 +3634,8 @@ void ex_buffer_all(exarg_T *eap)
//
// Don't execute Win/Buf Enter/Leave autocommands here.
autocmd_no_enter++;
- win_enter(lastwin, false);
+ // lastwin may be aucmd_win
+ win_enter(lastwin_nofloating(), false);
autocmd_no_leave++;
for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) {
// Check if this buffer needs a window
@@ -3661,7 +3653,7 @@ void ex_buffer_all(exarg_T *eap)
} else {
// Check if this buffer already has a window
for (wp = firstwin; wp != NULL; wp = wp->w_next) {
- if (wp->w_buffer == buf) {
+ if (!wp->w_floating && wp->w_buffer == buf) {
break;
}
}
@@ -3735,7 +3727,7 @@ void ex_buffer_all(exarg_T *eap)
// Close superfluous windows.
for (wp = lastwin; open_wins > count;) {
r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer)
- || autowrite(wp->w_buffer, false) == OK);
+ || autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp);
if (!win_valid(wp)) {
// BufWrite Autocommands made the window invalid, start over
wp = lastwin;
@@ -3802,12 +3794,10 @@ static int chk_modeline(linenr_T lnum, int flags)
char *s;
char *e;
char *linecopy; // local copy of any modeline found
- int prev;
intmax_t vers;
- int end;
int retval = OK;
- prev = -1;
+ int prev = -1;
for (s = ml_get(lnum); *s != NUL; s++) {
if (prev == -1 || ascii_isspace(prev)) {
if ((prev != -1 && strncmp(s, "ex:", (size_t)3) == 0)
@@ -3853,7 +3843,7 @@ static int chk_modeline(linenr_T lnum, int flags)
// prepare for emsg()
estack_push(ETYPE_MODELINE, "modelines", lnum);
- end = false;
+ bool end = false;
while (end == false) {
s = skipwhite(s);
if (*s == NUL) {
@@ -4036,153 +4026,64 @@ char *buf_spname(buf_T *buf)
return NULL;
}
-static int buf_signcols_inner(buf_T *buf, int maximum)
-{
- sign_entry_T *sign; // a sign in the sign list
- int signcols = 0;
- int linesum = 0;
- linenr_T curline = 0;
-
- buf->b_signcols.sentinel = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > curline) {
- // Counted all signs, now add extmark signs
- if (curline > 0) {
- linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1,
- maximum - linesum);
- }
- curline = sign->se_lnum;
- if (linesum > signcols) {
- signcols = linesum;
- buf->b_signcols.sentinel = curline;
- if (signcols >= maximum) {
- return maximum;
- }
- }
- linesum = 0;
- }
- if (sign->se_has_text_or_icon) {
- linesum++;
- }
- }
-
- if (curline > 0) {
- linesum += decor_signcols(buf, &decor_state, (int)curline - 1, (int)curline - 1,
- maximum - linesum);
- }
- if (linesum > signcols) {
- signcols = linesum;
- if (signcols >= maximum) {
- return maximum;
- }
- }
-
- // Check extmarks between signs
- linesum = decor_signcols(buf, &decor_state, 0, (int)buf->b_ml.ml_line_count - 1, maximum);
-
- if (linesum > signcols) {
- signcols = linesum;
- buf->b_signcols.sentinel = curline;
- if (signcols >= maximum) {
- return maximum;
- }
- }
-
- return signcols;
-}
-
-/// Invalidate the signcolumn if needed after deleting
-/// signs between line1 and line2 (inclusive).
-///
-/// @param buf buffer to check
-/// @param line1 start of region being deleted
-/// @param line2 end of region being deleted
+/// Invalidate the signcolumn if needed after deleting a sign ranging from line1 to line2.
void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
- if (!buf->b_signcols.valid) {
- return;
- }
-
- if (!buf->b_signcols.sentinel) {
- buf->b_signcols.valid = false;
- return;
- }
-
linenr_T sent = buf->b_signcols.sentinel;
-
if (sent >= line1 && sent <= line2) {
- // Only invalidate when removing signs at the sentinel line.
- buf->b_signcols.valid = false;
+ // When removed sign overlaps the sentinel line, entire buffer needs to be checked.
+ buf->b_signcols.sentinel = buf->b_signcols.size = 0;
}
}
-/// Re-calculate the signcolumn after adding a sign.
-///
-/// @param buf buffer to check
-/// @param added sign being added
-void buf_signcols_add_check(buf_T *buf, sign_entry_T *added)
+/// Invalidate the signcolumn if needed after adding a sign ranging from line1 to line2.
+void buf_signcols_add_check(buf_T *buf, linenr_T line1, linenr_T line2)
{
- if (!buf->b_signcols.valid) {
- return;
- }
-
- if (!added || !buf->b_signcols.sentinel) {
- buf->b_signcols.valid = false;
+ if (!buf->b_signcols.sentinel) {
return;
}
- if (added->se_lnum == buf->b_signcols.sentinel) {
+ linenr_T sent = buf->b_signcols.sentinel;
+ if (sent >= line1 && sent <= line2) {
+ // If added sign overlaps sentinel line, increment without invalidating.
if (buf->b_signcols.size == buf->b_signcols.max) {
buf->b_signcols.max++;
}
buf->b_signcols.size++;
- redraw_buf_later(buf, UPD_NOT_VALID);
return;
}
- sign_entry_T *s;
-
- // Get first sign for added lnum
- for (s = added; s->se_prev && s->se_lnum == s->se_prev->se_lnum; s = s->se_prev) {}
-
- // Count signs for lnum
- int linesum = 1;
- for (; s->se_next && s->se_lnum == s->se_next->se_lnum; s = s->se_next) {
- linesum++;
+ if (line1 < buf->b_signcols.invalid_top) {
+ buf->b_signcols.invalid_top = line1;
}
- linesum += decor_signcols(buf, &decor_state, (int)s->se_lnum - 1, (int)s->se_lnum - 1,
- SIGN_SHOW_MAX - linesum);
-
- if (linesum > buf->b_signcols.size) {
- buf->b_signcols.size = linesum;
- buf->b_signcols.max = linesum;
- buf->b_signcols.sentinel = added->se_lnum;
- redraw_buf_later(buf, UPD_NOT_VALID);
+ if (line2 > buf->b_signcols.invalid_bot) {
+ buf->b_signcols.invalid_bot = line2;
}
}
-int buf_signcols(buf_T *buf, int maximum)
+int buf_signcols(buf_T *buf, int max)
{
- // The maximum can be determined from 'signcolumn' which is window scoped so
- // need to invalidate signcols if the maximum is greater than the previous
- // maximum.
- if (maximum > buf->b_signcols.max) {
- buf->b_signcols.valid = false;
- }
-
- if (!buf->b_signcols.valid) {
- int signcols = buf_signcols_inner(buf, maximum);
- // Check if we need to redraw
- if (signcols != buf->b_signcols.size) {
- buf->b_signcols.size = signcols;
- buf->b_signcols.max = maximum;
- redraw_buf_later(buf, UPD_NOT_VALID);
+ if (!buf->b_signs_with_text) {
+ buf->b_signcols.size = 0;
+ } else if (max <= 1 && buf->b_signs_with_text >= (size_t)max) {
+ buf->b_signcols.size = max;
+ } else {
+ linenr_T sent = buf->b_signcols.sentinel;
+ if (!sent || max > buf->b_signcols.max) {
+ // Recheck if the window scoped maximum 'signcolumn' is greater than the
+ // previous maximum or if there is no sentinel line yet.
+ buf->b_signcols.invalid_top = sent ? sent : 1;
+ buf->b_signcols.invalid_bot = sent ? sent : buf->b_ml.ml_line_count;
}
- buf->b_signcols.valid = true;
+ if (buf->b_signcols.invalid_bot) {
+ decor_validate_signcols(buf, max);
+ }
}
+ buf->b_signcols.max = max;
+ buf->b_signcols.invalid_top = MAXLNUM;
+ buf->b_signcols.invalid_bot = 0;
return buf->b_signcols.size;
}
@@ -4223,7 +4124,7 @@ bool buf_contents_changed(buf_T *buf)
bool differ = true;
// Allocate a buffer without putting it in the buffer list.
- buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ buf_T *newbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
if (newbuf == NULL) {
return true;
}
@@ -4236,15 +4137,19 @@ bool buf_contents_changed(buf_T *buf)
aco_save_T aco;
aucmd_prepbuf(&aco, newbuf);
+ // We don't want to trigger autocommands now, they may have nasty
+ // side-effects like wiping buffers
+ block_autocmds();
+
if (ml_open(curbuf) == OK
&& readfile(buf->b_ffname, buf->b_fname,
- (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM,
+ 0, 0, (linenr_T)MAXLNUM,
&ea, READ_NEW | READ_DUMMY, false) == OK) {
// compare the two files line by line
if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) {
differ = false;
for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- if (strcmp(ml_get_buf(buf, lnum, false), ml_get(lnum)) != 0) {
+ if (strcmp(ml_get_buf(buf, lnum), ml_get(lnum)) != 0) {
differ = true;
break;
}
@@ -4260,6 +4165,8 @@ bool buf_contents_changed(buf_T *buf)
wipe_buffer(newbuf, false);
}
+ unblock_autocmds();
+
return differ;
}
@@ -4298,9 +4205,9 @@ int buf_open_scratch(handle_T bufnr, char *bufname)
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
(void)setfname(curbuf, bufname, NULL, true);
apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, false, curbuf);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
RESET_BINDING(curwin);
return OK;
}
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index 610d9e37ec..36e70d1927 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -1,76 +1,74 @@
-#ifndef NVIM_BUFFER_H
-#define NVIM_BUFFER_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: export
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/func_attr.h"
-#include "nvim/grid_defs.h" // for StlClickRecord
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memline.h"
#include "nvim/memline_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
-// Values for buflist_getfile()
+/// Values for buflist_getfile()
enum getf_values {
- GETF_SETMARK = 0x01, // set pcmark before jumping
- GETF_ALT = 0x02, // jumping to alternate file (not buf num)
- GETF_SWITCH = 0x04, // respect 'switchbuf' settings when jumping
+ GETF_SETMARK = 0x01, ///< set pcmark before jumping
+ GETF_ALT = 0x02, ///< jumping to alternate file (not buf num)
+ GETF_SWITCH = 0x04, ///< respect 'switchbuf' settings when jumping
};
// Return values of getfile()
enum getf_retvalues {
- GETFILE_ERROR = 1, // normal error
- GETFILE_NOT_WRITTEN = 2, // "not written" error
- GETFILE_SAME_FILE = 0, // success, same file
- GETFILE_OPEN_OTHER = (-1), // success, opened another file
+ GETFILE_ERROR = 1, ///< normal error
+ GETFILE_NOT_WRITTEN = 2, ///< "not written" error
+ GETFILE_SAME_FILE = 0, ///< success, same file
+ GETFILE_OPEN_OTHER = -1, ///< success, opened another file
GETFILE_UNUSED = 8,
};
-// Values for buflist_new() flags
+/// Values for buflist_new() flags
enum bln_values {
- BLN_CURBUF = 1, // May re-use curbuf for new buffer
- BLN_LISTED = 2, // Put new buffer in buffer list
- BLN_DUMMY = 4, // Allocating dummy buffer
- BLN_NEW = 8, // create a new buffer
- BLN_NOOPT = 16, // Don't copy options to existing buffer
+ BLN_CURBUF = 1, ///< May re-use curbuf for new buffer
+ BLN_LISTED = 2, ///< Put new buffer in buffer list
+ BLN_DUMMY = 4, ///< Allocating dummy buffer
+ BLN_NEW = 8, ///< create a new buffer
+ BLN_NOOPT = 16, ///< Don't copy options to existing buffer
// BLN_DUMMY_OK = 32, // also find an existing dummy buffer
// BLN_REUSE = 64, // may re-use number from buf_reuse
- BLN_NOCURWIN = 128, // buffer is not associated with curwin
+ BLN_NOCURWIN = 128, ///< buffer is not associated with curwin
};
-// Values for action argument for do_buffer()
+/// Values for action argument for do_buffer()
enum dobuf_action_values {
- DOBUF_GOTO = 0, // go to specified buffer
- DOBUF_SPLIT = 1, // split window and go to specified buffer
- DOBUF_UNLOAD = 2, // unload specified buffer(s)
- DOBUF_DEL = 3, // delete specified buffer(s) from buflist
- DOBUF_WIPE = 4, // delete specified buffer(s) really
+ DOBUF_GOTO = 0, ///< go to specified buffer
+ DOBUF_SPLIT = 1, ///< split window and go to specified buffer
+ DOBUF_UNLOAD = 2, ///< unload specified buffer(s)
+ DOBUF_DEL = 3, ///< delete specified buffer(s) from buflist
+ DOBUF_WIPE = 4, ///< delete specified buffer(s) really
};
-// Values for start argument for do_buffer()
+/// Values for start argument for do_buffer()
enum dobuf_start_values {
- DOBUF_CURRENT = 0, // "count" buffer from current buffer
- DOBUF_FIRST = 1, // "count" buffer from first buffer
- DOBUF_LAST = 2, // "count" buffer from last buffer
- DOBUF_MOD = 3, // "count" mod. buffer from current buffer
+ DOBUF_CURRENT = 0, ///< "count" buffer from current buffer
+ DOBUF_FIRST = 1, ///< "count" buffer from first buffer
+ DOBUF_LAST = 2, ///< "count" buffer from last buffer
+ DOBUF_MOD = 3, ///< "count" mod. buffer from current buffer
};
-// flags for buf_freeall()
+/// flags for buf_freeall()
enum bfa_values {
- BFA_DEL = 1, // buffer is going to be deleted
- BFA_WIPE = 2, // buffer is going to be wiped out
- BFA_KEEP_UNDO = 4, // do not free undo information
- BFA_IGNORE_ABORT = 8, // do not abort for aborting()
+ BFA_DEL = 1, ///< buffer is going to be deleted
+ BFA_WIPE = 2, ///< buffer is going to be wiped out
+ BFA_KEEP_UNDO = 4, ///< do not free undo information
+ BFA_IGNORE_ABORT = 8, ///< do not abort for aborting()
};
-EXTERN char *msg_loclist INIT(= N_("[Location List]"));
-EXTERN char *msg_qflist INIT(= N_("[Quickfix List]"));
+EXTERN char *msg_loclist INIT( = N_("[Location List]"));
+EXTERN char *msg_qflist INIT( = N_("[Quickfix List]"));
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.h.generated.h"
@@ -137,8 +135,5 @@ static inline void buf_inc_changedtick(buf_T *const buf)
static inline bool buf_is_empty(buf_T *buf)
{
- return buf->b_ml.ml_line_count == 1
- && *ml_get_buf(buf, (linenr_T)1, false) == '\0';
+ return buf->b_ml.ml_line_count == 1 && *ml_get_buf(buf, 1) == '\0';
}
-
-#endif // NVIM_BUFFER_H
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 4c99191170..e59539f900 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -1,14 +1,13 @@
-#ifndef NVIM_BUFFER_DEFS_H
-#define NVIM_BUFFER_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
-typedef struct file_buffer buf_T; // Forward declaration
+typedef struct file_buffer buf_T;
-// Reference to a buffer that stores the value of buf_free_count.
-// bufref_valid() only needs to check "buf" when the count differs.
+/// Reference to a buffer that stores the value of buf_free_count.
+/// bufref_valid() only needs to check "buf" when the count differs.
typedef struct {
buf_T *br_buf;
int br_fnum;
@@ -17,19 +16,19 @@ typedef struct {
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/garray.h"
+#include "nvim/arglist_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/extmark_defs.h"
+#include "nvim/garray_defs.h"
#include "nvim/grid_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/highlight_defs.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
+#include "nvim/mapping_defs.h"
#include "nvim/mark_defs.h"
#include "nvim/marktree.h"
-// for float window title
-#include "nvim/extmark_defs.h"
-// for click definitions
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/statusline_defs.h"
#include "nvim/undo_defs.h"
@@ -102,28 +101,6 @@ typedef struct taggy {
char *user_data; // used with tagfunc
} taggy_T;
-typedef struct buffblock buffblock_T;
-typedef struct buffheader buffheader_T;
-
-// structure used to store one block of the stuff/redo/recording buffers
-struct buffblock {
- buffblock_T *b_next; // pointer to next buffblock
- char b_str[1]; // contents (actually longer)
-};
-
-// header used for the stuff buffer and the redo buffer
-struct buffheader {
- buffblock_T bh_first; // first (dummy) block of list
- buffblock_T *bh_curr; // buffblock for appending
- size_t bh_index; // index for reading
- size_t bh_space; // space in bh_curr for appending
-};
-
-typedef struct {
- buffheader_T sr_redobuff;
- buffheader_T sr_old_redobuff;
-} save_redo_T;
-
// Structure that contains all options that are local to a window.
// Used twice in a window: for the current buffer and for all buffers.
// Also used in wininfo_T.
@@ -147,18 +124,18 @@ typedef struct {
#define w_p_fen_save w_onebuf_opt.wo_fen_save
char *wo_fdi;
#define w_p_fdi w_onebuf_opt.wo_fdi // 'foldignore'
- long wo_fdl;
+ OptInt wo_fdl;
#define w_p_fdl w_onebuf_opt.wo_fdl // 'foldlevel'
- long wo_fdl_save;
+ OptInt wo_fdl_save;
// 'foldlevel' state saved for diff mode
#define w_p_fdl_save w_onebuf_opt.wo_fdl_save
char *wo_fdm;
#define w_p_fdm w_onebuf_opt.wo_fdm // 'foldmethod'
char *wo_fdm_save;
#define w_p_fdm_save w_onebuf_opt.wo_fdm_save // 'fdm' saved for diff mode
- long wo_fml;
+ OptInt wo_fml;
#define w_p_fml w_onebuf_opt.wo_fml // 'foldminlines'
- long wo_fdn;
+ OptInt wo_fdn;
#define w_p_fdn w_onebuf_opt.wo_fdn // 'foldnestmax'
char *wo_fde;
#define w_p_fde w_onebuf_opt.wo_fde // 'foldexpr'
@@ -178,7 +155,7 @@ typedef struct {
#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit'
unsigned wo_ve_flags;
#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit'
- long wo_nuw;
+ OptInt wo_nuw;
#define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth'
int wo_wfh;
#define w_p_wfh w_onebuf_opt.wo_wfh // 'winfixheight'
@@ -190,8 +167,10 @@ typedef struct {
#define w_p_rl w_onebuf_opt.wo_rl // 'rightleft'
char *wo_rlc;
#define w_p_rlc w_onebuf_opt.wo_rlc // 'rightleftcmd'
- long wo_scr;
+ OptInt wo_scr;
#define w_p_scr w_onebuf_opt.wo_scr // 'scroll'
+ int wo_sms;
+#define w_p_sms w_onebuf_opt.wo_sms // 'smoothscroll'
int wo_spell;
#define w_p_spell w_onebuf_opt.wo_spell // 'spell'
int wo_cuc;
@@ -222,7 +201,7 @@ typedef struct {
#define w_p_wrap_save w_onebuf_opt.wo_wrap_save
char *wo_cocu; // 'concealcursor'
#define w_p_cocu w_onebuf_opt.wo_cocu
- long wo_cole; // 'conceallevel'
+ OptInt wo_cole; // 'conceallevel'
#define w_p_cole w_onebuf_opt.wo_cole
int wo_crb;
#define w_p_crb w_onebuf_opt.wo_crb // 'cursorbind'
@@ -230,13 +209,17 @@ typedef struct {
#define w_p_crb_save w_onebuf_opt.wo_crb_save
char *wo_scl;
#define w_p_scl w_onebuf_opt.wo_scl // 'signcolumn'
+ OptInt wo_siso;
+#define w_p_siso w_onebuf_opt.wo_siso // 'sidescrolloff' local value
+ OptInt wo_so;
+#define w_p_so w_onebuf_opt.wo_so // 'scrolloff' local value
char *wo_winhl;
#define w_p_winhl w_onebuf_opt.wo_winhl // 'winhighlight'
char *wo_lcs;
#define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars'
char *wo_fcs;
#define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars'
- long wo_winbl;
+ OptInt wo_winbl;
#define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend'
LastSet wo_script_ctx[WV_COUNT]; // SCTXs for window-local options
@@ -263,27 +246,7 @@ struct wininfo_S {
int wi_changelistidx; // copy of w_changelistidx
};
-// Argument list: Array of file names.
-// Used for the global argument list and the argument lists local to a window.
-//
-// TODO(neovim): move struct arglist to another header
-typedef struct arglist {
- garray_T al_ga; // growarray with the array of file names
- int al_refcount; // number of windows using this arglist
- int id; ///< id of this arglist
-} alist_T;
-
-// For each argument remember the file name as it was given, and the buffer
-// number that contains the expanded file name (required for when ":cd" is
-// used).
-//
-// TODO(Felipe): move aentry_T to another header
-typedef struct argentry {
- char *ae_fname; // file name as specified
- int ae_fnum; // buffer number with expanded file name
-} aentry_T;
-
-#define ALIST(win) (win)->w_alist
+#define ALIST(win) (win)->w_alist
#define GARGLIST ((aentry_T *)global_alist.al_ga.ga_data)
#define ARGLIST ((aentry_T *)ALIST(curwin)->al_ga.ga_data)
#define WARGLIST(wp) ((aentry_T *)ALIST(wp)->al_ga.ga_data)
@@ -292,81 +255,6 @@ typedef struct argentry {
#define ARGCOUNT (ALIST(curwin)->al_ga.ga_len)
#define WARGCOUNT(wp) (ALIST(wp)->al_ga.ga_len)
-// Used for the typeahead buffer: typebuf.
-typedef struct {
- uint8_t *tb_buf; // buffer for typed characters
- uint8_t *tb_noremap; // mapping flags for characters in tb_buf[]
- int tb_buflen; // size of tb_buf[]
- int tb_off; // current position in tb_buf[]
- int tb_len; // number of valid bytes in tb_buf[]
- int tb_maplen; // nr of mapped bytes in tb_buf[]
- int tb_silent; // nr of silently mapped bytes in tb_buf[]
- int tb_no_abbr_cnt; // nr of bytes without abbrev. in tb_buf[]
- int tb_change_cnt; // nr of time tb_buf was changed; never zero
-} typebuf_T;
-
-// Struct to hold the saved typeahead for save_typeahead().
-typedef struct {
- typebuf_T save_typebuf;
- bool typebuf_valid; // true when save_typebuf valid
- int old_char;
- int old_mod_mask;
- buffheader_T save_readbuf1;
- buffheader_T save_readbuf2;
- String save_inputbuf;
-} tasave_T;
-
-// Structure used for mappings and abbreviations.
-typedef struct mapblock mapblock_T;
-struct mapblock {
- mapblock_T *m_next; // next mapblock in list
- char *m_keys; // mapped from, lhs
- char *m_str; // mapped to, rhs
- char *m_orig_str; // rhs as entered by the user
- LuaRef m_luaref; // lua function reference as rhs
- int m_keylen; // strlen(m_keys)
- int m_mode; // valid mode
- int m_simplified; // m_keys was simplified, do no use this map
- // if keys are typed
- int m_noremap; // if non-zero no re-mapping for m_str
- char m_silent; // <silent> used, don't echo commands
- char m_nowait; // <nowait> used
- char m_expr; // <expr> used, m_str is an expression
- sctx_T m_script_ctx; // SCTX where map was defined
- char *m_desc; // description of mapping
- bool m_replace_keycodes; // replace keycodes in result of expression
-};
-
-/// Used for highlighting in the status line.
-typedef struct stl_hlrec stl_hlrec_t;
-struct stl_hlrec {
- char *start;
- int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
-};
-
-/// Used for building the status line.
-typedef struct stl_item stl_item_t;
-struct stl_item {
- // Where the item starts in the status line output buffer
- char *start;
- // Function to run for ClickFunc items.
- char *cmd;
- // The minimum width of the item
- int minwid;
- // The maximum width of the item
- int maxwid;
- enum {
- Normal,
- Empty,
- Group,
- Separate,
- Highlight,
- TabPage,
- ClickFunc,
- Trunc,
- } type;
-};
-
// values for b_syn_spell: what to do with toplevel text
#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
#define SYNSPL_TOP 1 // spell check toplevel text
@@ -376,17 +264,14 @@ struct stl_item {
#define SYNFLD_START 0 // use level of item at start of line
#define SYNFLD_MINIMUM 1 // use lowest local minimum level on line
-// avoid #ifdefs for when b_spell is not available
-#define B_SPELL(buf) ((buf)->b_spell)
-
typedef struct qf_info_S qf_info_T;
// Used for :syntime: timing of executing a syntax pattern.
typedef struct {
proftime_T total; // total time used
proftime_T slowest; // time of slowest call
- long count; // nr of times used
- long match; // nr of times matched
+ int count; // nr of times used
+ int match; // nr of times matched
} syn_time_T;
// These are items normally related to a buffer. But when using ":ownsyntax"
@@ -472,7 +357,7 @@ typedef struct {
#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, false, false }
-EXTERN int curbuf_splice_pending INIT(= 0);
+EXTERN int curbuf_splice_pending INIT( = 0);
#define BUF_HAS_QF_ENTRY 1
#define BUF_HAS_LL_ENTRY 2
@@ -529,10 +414,10 @@ struct file_buffer {
/// This is a dictionary item used to store b:changedtick.
ChangedtickDictItem changedtick_di;
- varnumber_T b_last_changedtick; // b:changedtick when TextChanged or
- // TextChangedI was last triggered.
- varnumber_T b_last_changedtick_pum; // b:changedtick when TextChangedP was
+ varnumber_T b_last_changedtick; // b:changedtick when TextChanged was
// last triggered.
+ varnumber_T b_last_changedtick_i; // b:changedtick for TextChangedI
+ varnumber_T b_last_changedtick_pum; // b:changedtick for TextChangedP
bool b_saving; // Set to true if we are in the middle of
// saving the buffer.
@@ -551,10 +436,10 @@ struct file_buffer {
disptick_T b_mod_tick_decor; // last display tick decoration providers
// where invoked
- long b_mtime; // last change time of original file
- long b_mtime_ns; // nanoseconds of last change time
- long b_mtime_read; // last change time when reading
- long b_mtime_read_ns; // nanoseconds of last read time
+ int64_t b_mtime; // last change time of original file
+ int64_t b_mtime_ns; // nanoseconds of last change time
+ int64_t b_mtime_read; // last change time when reading
+ int64_t b_mtime_read_ns; // nanoseconds of last read time
uint64_t b_orig_size; // size of original file in bytes
int b_orig_mode; // mode of original file
time_t b_last_used; // time when the buffer was last used; used
@@ -599,13 +484,13 @@ struct file_buffer {
u_header_T *b_u_newhead; // pointer to newest header; may not be valid
// if b_u_curhead is not NULL
u_header_T *b_u_curhead; // pointer to current header
- int b_u_numhead; // current number of headers
- bool b_u_synced; // entry lists are synced
- long b_u_seq_last; // last used undo sequence number
- long b_u_save_nr_last; // counter for last file write
- long b_u_seq_cur; // uh_seq of header below which we are now
- time_t b_u_time_cur; // uh_time of header below which we are now
- long b_u_save_nr_cur; // file write nr after which we are now
+ int b_u_numhead; // current number of headers
+ bool b_u_synced; // entry lists are synced
+ int b_u_seq_last; // last used undo sequence number
+ int b_u_save_nr_last; // counter for last file write
+ int b_u_seq_cur; // uh_seq of header below which we are now
+ time_t b_u_time_cur; // uh_time of header below which we are now
+ int b_u_save_nr_cur; // file write nr after which we are now
// variables for "U" command in undo.c
char *b_u_line_ptr; // saved line for "U" command
@@ -615,8 +500,8 @@ struct file_buffer {
bool b_scanned; // ^N/^P have scanned this buffer
// flags for use of ":lmap" and IM control
- long b_p_iminsert; // input mode for insert
- long b_p_imsearch; // input mode for search
+ OptInt b_p_iminsert; // input mode for insert
+ OptInt b_p_imsearch; // input mode for search
#define B_IMODE_USE_INSERT (-1) // Use b_p_iminsert value for search
#define B_IMODE_NONE 0 // Input via none
#define B_IMODE_LMAP 1 // Input via langmap
@@ -637,7 +522,7 @@ struct file_buffer {
int b_p_ai; ///< 'autoindent'
int b_p_ai_nopaste; ///< b_p_ai saved for paste mode
char *b_p_bkc; ///< 'backupco
- unsigned int b_bkc_flags; ///< flags for 'backupco
+ unsigned b_bkc_flags; ///< flags for 'backupco
int b_p_ci; ///< 'copyindent'
int b_p_bin; ///< 'binary'
int b_p_bomb; ///< 'bomb'
@@ -645,7 +530,7 @@ struct file_buffer {
char *b_p_bt; ///< 'buftype'
int b_has_qf_entry; ///< quickfix exists for buffer
int b_p_bl; ///< 'buflisted'
- long b_p_channel; ///< 'channel'
+ OptInt b_p_channel; ///< 'channel'
int b_p_cin; ///< 'cindent'
char *b_p_cino; ///< 'cinoptions'
char *b_p_cink; ///< 'cinkeys'
@@ -698,27 +583,27 @@ struct file_buffer {
int b_p_pi; ///< 'preserveindent'
char *b_p_qe; ///< 'quoteescape'
int b_p_ro; ///< 'readonly'
- long b_p_sw; ///< 'shiftwidth'
- long b_p_scbk; ///< 'scrollback'
+ OptInt b_p_sw; ///< 'shiftwidth'
+ OptInt b_p_scbk; ///< 'scrollback'
int b_p_si; ///< 'smartindent'
- long b_p_sts; ///< 'softtabstop'
- long b_p_sts_nopaste; ///< b_p_sts saved for paste mode
+ OptInt b_p_sts; ///< 'softtabstop'
+ OptInt b_p_sts_nopaste; ///< b_p_sts saved for paste mode
char *b_p_sua; ///< 'suffixesadd'
int b_p_swf; ///< 'swapfile'
- long b_p_smc; ///< 'synmaxcol'
+ OptInt b_p_smc; ///< 'synmaxcol'
char *b_p_syn; ///< 'syntax'
- long b_p_ts; ///< 'tabstop'
- long b_p_tw; ///< 'textwidth'
- long b_p_tw_nobin; ///< b_p_tw saved for binary mode
- long b_p_tw_nopaste; ///< b_p_tw saved for paste mode
- long b_p_wm; ///< 'wrapmargin'
- long b_p_wm_nobin; ///< b_p_wm saved for binary mode
- long b_p_wm_nopaste; ///< b_p_wm saved for paste mode
+ OptInt b_p_ts; ///< 'tabstop'
+ OptInt b_p_tw; ///< 'textwidth'
+ OptInt b_p_tw_nobin; ///< b_p_tw saved for binary mode
+ OptInt b_p_tw_nopaste; ///< b_p_tw saved for paste mode
+ OptInt b_p_wm; ///< 'wrapmargin'
+ OptInt b_p_wm_nobin; ///< b_p_wm saved for binary mode
+ OptInt b_p_wm_nopaste; ///< b_p_wm saved for paste mode
char *b_p_vsts; ///< 'varsofttabstop'
- long *b_p_vsts_array; ///< 'varsofttabstop' in internal format
+ colnr_T *b_p_vsts_array; ///< 'varsofttabstop' in internal format
char *b_p_vsts_nopaste; ///< b_p_vsts saved for paste mode
char *b_p_vts; ///< 'vartabstop'
- long *b_p_vts_array; ///< 'vartabstop' in internal format
+ colnr_T *b_p_vts_array; ///< 'vartabstop' in internal format
char *b_p_keymap; ///< 'keymap'
// local values for options which are normally global
@@ -735,7 +620,7 @@ struct file_buffer {
char *b_p_tsr; ///< 'thesaurus' local value
char *b_p_tsrfu; ///< 'thesaurusfunc' local value
Callback b_tsrfu_cb; ///< 'thesaurusfunc' callback
- long b_p_ul; ///< 'undolevels' local value
+ OptInt b_p_ul; ///< 'undolevels' local value
int b_p_udf; ///< 'undofile'
char *b_p_lw; ///< 'lispwords' local value
@@ -807,8 +692,7 @@ struct file_buffer {
bool b_help; // true for help file buffer (when set b_p_bt
// is "help")
bool b_spell; // True for a spell file buffer, most fields
- // are not used! Use the B_SPELL macro to
- // access b_spell without #ifdef.
+ // are not used!
char *b_prompt_text; // set by prompt_setprompt()
Callback b_prompt_callback; // set by prompt_setcallback()
@@ -820,12 +704,12 @@ struct file_buffer {
// normally points to this, but some windows
// may use a different synblock_T.
- sign_entry_T *b_signlist; // list of placed signs
struct {
int size; // last calculated number of sign columns
- bool valid; // calculated sign columns is valid
+ int max; // maximum value size is valid for.
linenr_T sentinel; // a line number which is holding up the signcolumn
- int max; // Maximum value size is valid for.
+ linenr_T invalid_top; // first invalid line number that needs to be checked
+ linenr_T invalid_bot; // last invalid line number that needs to be checked
} b_signcols;
Terminal *terminal; // Terminal instance associated with the buffer
@@ -836,8 +720,10 @@ struct file_buffer {
MarkTree b_marktree[1];
Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces
+ size_t b_virt_text_inline; // number of inline virtual texts
size_t b_virt_line_blocks; // number of virt_line blocks
size_t b_signs; // number of sign extmarks
+ size_t b_signs_with_text; // number of sign extmarks with text
// array of channel_id:s which have asked to receive updates for this
// buffer.
@@ -898,16 +784,16 @@ struct diffblock_S {
typedef struct tabpage_S tabpage_T;
struct tabpage_S {
handle_T handle;
- tabpage_T *tp_next; ///< next tabpage or NULL
- frame_T *tp_topframe; ///< topframe for the windows
- win_T *tp_curwin; ///< current window in this Tab page
- win_T *tp_prevwin; ///< previous window in this Tab page
- win_T *tp_firstwin; ///< first window in this Tab page
- win_T *tp_lastwin; ///< last window in this Tab page
- long tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left
- long tp_old_Columns; ///< Columns when Tab page was left, -1 when
- ///< calling win_new_screen_cols() postponed
- long tp_ch_used; ///< value of 'cmdheight' when frame size was set
+ tabpage_T *tp_next; ///< next tabpage or NULL
+ frame_T *tp_topframe; ///< topframe for the windows
+ win_T *tp_curwin; ///< current window in this Tab page
+ win_T *tp_prevwin; ///< previous window in this Tab page
+ win_T *tp_firstwin; ///< first window in this Tab page
+ win_T *tp_lastwin; ///< last window in this Tab page
+ int64_t tp_old_Rows_avail; ///< ROWS_AVAIL when Tab page was left
+ int64_t tp_old_Columns; ///< Columns when Tab page was left, -1 when
+ ///< calling win_new_screen_cols() postponed
+ OptInt tp_ch_used; ///< value of 'cmdheight' when frame size was set
diff_T *tp_first_diff;
buf_T *(tp_diffbuf[DB_COUNT]);
@@ -1022,7 +908,7 @@ enum {
// NE -> kFloatAnchorEast
// SW -> kFloatAnchorSouth
// SE -> kFloatAnchorSouth | kFloatAnchorEast
-EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" });
+EXTERN const char *const float_anchor_str[] INIT( = { "NW", "NE", "SW", "SE" });
typedef enum {
kFloatRelativeEditor = 0,
@@ -1031,8 +917,8 @@ typedef enum {
kFloatRelativeMouse = 3,
} FloatRelative;
-EXTERN const char *const float_relative_str[] INIT(= { "editor", "win",
- "cursor", "mouse" });
+EXTERN const char *const float_relative_str[] INIT( = { "editor", "win",
+ "cursor", "mouse" });
typedef enum {
kWinStyleUnused = 0,
@@ -1045,6 +931,11 @@ typedef enum {
kAlignRight = 2,
} AlignTextPos;
+typedef enum {
+ kBorderTextTitle = 0,
+ kBorderTextFooter = 1,
+} BorderTextType;
+
typedef struct {
Window window;
lpos_T bufpos;
@@ -1057,15 +948,21 @@ typedef struct {
int zindex;
WinStyle style;
bool border;
- bool title;
bool shadow;
- schar_T border_chars[8];
+ char border_chars[8][MAX_SCHAR_SIZE];
int border_hl_ids[8];
int border_attr[8];
+ bool title;
AlignTextPos title_pos;
VirtText title_chunks;
int title_width;
+ bool footer;
+ AlignTextPos footer_pos;
+ VirtText footer_chunks;
+ int footer_width;
bool noautocmd;
+ bool fixed;
+ bool hide;
} FloatConfig;
#define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \
@@ -1075,7 +972,9 @@ typedef struct {
.focusable = true, \
.zindex = kZIndexFloatDefault, \
.style = kWinStyleUnused, \
- .noautocmd = false })
+ .noautocmd = false, \
+ .hide = false, \
+ .fixed = false })
// Structure to store last cursor position and topline. Used by check_lnums()
// and reset_lnums().
@@ -1086,6 +985,45 @@ typedef struct {
pos_T w_cursor_corr; // corrected cursor position
} pos_save_T;
+/// Characters from the 'listchars' option.
+typedef struct {
+ int eol;
+ int ext;
+ int prec;
+ int nbsp;
+ int space;
+ int tab1; ///< first tab character
+ int tab2; ///< second tab character
+ int tab3; ///< third tab character
+ int lead;
+ int trail;
+ int *multispace;
+ int *leadmultispace;
+ int conceal;
+} lcs_chars_T;
+
+/// Characters from the 'fillchars' option.
+typedef struct {
+ int stl;
+ int stlnc;
+ int wbr;
+ int horiz;
+ int horizup;
+ int horizdown;
+ int vert;
+ int vertleft;
+ int vertright;
+ int verthoriz;
+ int fold;
+ int foldopen; ///< when fold is open
+ int foldclosed; ///< when fold is closed
+ int foldsep; ///< continuous fold marker
+ int diff;
+ int msgsep;
+ int eob;
+ int lastline;
+} fcs_chars_T;
+
/// Structure which contains all information that belongs to a window.
///
/// All row numbers are relative to the start of the window, except w_winrow.
@@ -1111,22 +1049,24 @@ struct window_S {
win_T *w_prev; ///< link to previous window
win_T *w_next; ///< link to next window
bool w_closing; ///< window is being closed, don't let
- /// autocommands close it too.
+ ///< autocommands close it too.
frame_T *w_frame; ///< frame containing this window
pos_T w_cursor; ///< cursor position in buffer
colnr_T w_curswant; ///< Column we want to be at. This is
- /// used to try to stay in the same column
- /// for up/down cursor motions.
+ ///< used to try to stay in the same column
+ ///< for up/down cursor motions.
int w_set_curswant; // If set, then update w_curswant the next
// time through cursupdate() to the
// current virtual column
+ linenr_T w_cursorline; ///< Where 'cursorline' should be drawn,
+ ///< can be different from w_cursor.lnum
+ ///< for closed folds.
linenr_T w_last_cursorline; ///< where last 'cursorline' was drawn
- pos_T w_last_cursormoved; ///< for CursorMoved event
// the next seven are used to update the visual part
char w_old_visual_mode; ///< last known VIsual_mode
@@ -1139,44 +1079,11 @@ struct window_S {
linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn
- // 'listchars' characters. Defaults set in set_chars_option().
- struct {
- int eol;
- int ext;
- int prec;
- int nbsp;
- int space;
- int tab1; ///< first tab character
- int tab2; ///< second tab character
- int tab3; ///< third tab character
- int lead;
- int trail;
- int *multispace;
- int *leadmultispace;
- int conceal;
- } w_p_lcs_chars;
-
- // 'fillchars' characters. Defaults set in set_chars_option().
- struct {
- int stl;
- int stlnc;
- int wbr;
- int horiz;
- int horizup;
- int horizdown;
- int vert;
- int vertleft;
- int vertright;
- int verthoriz;
- int fold;
- int foldopen; ///< when fold is open
- int foldclosed; ///< when fold is closed
- int foldsep; ///< continuous fold marker
- int diff;
- int msgsep;
- int eob;
- int lastline;
- } w_p_fcs_chars;
+ /// 'listchars' characters. Defaults set in set_chars_option().
+ lcs_chars_T w_p_lcs_chars;
+
+ /// 'fillchars' characters. Defaults set in set_chars_option().
+ fcs_chars_T w_p_fcs_chars;
// "w_topline", "w_leftcol" and "w_skipcol" specify the offsets for
// displaying the buffer.
@@ -1189,11 +1096,12 @@ struct window_S {
bool w_botfill; // true when filler lines are actually
// below w_topline (at end of file)
bool w_old_botfill; // w_botfill at last redraw
- colnr_T w_leftcol; // window column number of the left most
+ colnr_T w_leftcol; // screen column number of the left most
// character in the window; used when
// 'wrap' is off
- colnr_T w_skipcol; // starting column when a single line
- // doesn't fit in the window
+ colnr_T w_skipcol; // starting screen column for the first
+ // line in the window; used when 'wrap' is
+ // on; does not include win_col_off()
// six fields that are only used when there is a WinScrolled autocommand
linenr_T w_last_topline; ///< last known value for w_topline
@@ -1219,6 +1127,7 @@ struct window_S {
int w_hsep_height; // Number of horizontal separator rows (0 or 1)
int w_vsep_width; // Number of vertical separator columns (0 or 1).
pos_save_T w_save_cursor; // backup of cursor pos and topline
+ bool w_do_win_fix_cursor; // if true cursor may be invalid
int w_winrow_off; ///< offset from winrow to the inner window area
int w_wincol_off; ///< offset from wincol to the inner window area
@@ -1246,8 +1155,13 @@ struct window_S {
int w_valid;
pos_T w_valid_cursor; // last known position of w_cursor, used to adjust w_valid
colnr_T w_valid_leftcol; // last known w_leftcol
+ colnr_T w_valid_skipcol; // last known w_skipcol
bool w_viewport_invalid;
+ linenr_T w_viewport_last_topline; // topline when the viewport was last updated
+ linenr_T w_viewport_last_botline; // botline when the viewport was last updated
+ linenr_T w_viewport_last_topfill; // topfill when the viewport was last updated
+ linenr_T w_viewport_last_skipcol; // skipcol when the viewport was last updated
// w_cline_height is the number of physical lines taken by the buffer line
// that the cursor is on. We use this to avoid extra calls to plines_win().
@@ -1293,6 +1207,8 @@ struct window_S {
int w_nrwidth; // width of 'number' and 'relativenumber'
// column being used
int w_scwidth; // width of 'signcolumn'
+ int w_minscwidth; // minimum width or SCL_NO/SCL_NUM
+ int w_maxscwidth; // maximum width or SCL_NO/SCL_NUM
// === end of cached values ===
@@ -1305,13 +1221,16 @@ struct window_S {
bool w_redr_border; // if true border must be redrawn
bool w_redr_statuscol; // if true 'statuscolumn' must be redrawn
- // remember what is shown in the ruler for this window (if 'ruler' set)
- pos_T w_ru_cursor; // cursor position shown in ruler
- colnr_T w_ru_virtcol; // virtcol shown in ruler
- linenr_T w_ru_topline; // topline shown in ruler
- linenr_T w_ru_line_count; // line count used for ruler
- int w_ru_topfill; // topfill shown in ruler
- char w_ru_empty; // true if ruler shows 0-1 (empty line)
+ // remember what is shown in the 'statusline'-format elements
+ pos_T w_stl_cursor; // cursor position when last redrawn
+ colnr_T w_stl_virtcol; // virtcol when last redrawn
+ linenr_T w_stl_topline; // topline when last redrawn
+ linenr_T w_stl_line_count; // line count when last redrawn
+ int w_stl_topfill; // topfill when last redrawn
+ char w_stl_empty; // true if elements show 0-1 (empty line)
+ int w_stl_recording; // reg_recording when last redrawn
+ int w_stl_state; // get_real_state() when last redrawn
+ int w_stl_visual_mode; // VIsual_mode when last redrawn
int w_alt_fnum; // alternate file (for # and CTRL-^)
@@ -1329,6 +1248,8 @@ struct window_S {
// this window, w_allbuf_opt is for all buffers in this window.
winopt_T w_onebuf_opt;
winopt_T w_allbuf_opt;
+ // transform a pointer to a "onebuf" option into a "allbuf" option
+#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
// A few options have local flags for P_INSECURE.
uint32_t w_p_stl_flags; // flags for 'statusline'
@@ -1337,8 +1258,6 @@ struct window_S {
uint32_t w_p_fdt_flags; // flags for 'foldtext'
int *w_p_cc_cols; // array of columns to highlight or NULL
uint8_t w_p_culopt_flags; // flags for cursorline highlighting
- long w_p_siso; // 'sidescrolloff' local value
- long w_p_so; // 'scrolloff' local value
int w_briopt_min; // minimum width for breakindent
int w_briopt_shift; // additional shift for breakindent
@@ -1346,10 +1265,7 @@ struct window_S {
int w_briopt_list; // additional indent for lists
int w_briopt_vcol; // indent for specific column
- // transform a pointer to a "onebuf" option into a "allbuf" option
-#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T))
-
- long w_scbind_pos;
+ int w_scbind_pos;
ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary.
dict_T *w_vars; ///< Dictionary with w: variables.
@@ -1416,30 +1332,8 @@ struct window_S {
size_t w_statuscol_click_defs_size;
};
-/// Struct to hold info for 'statuscolumn'
-typedef struct statuscol statuscol_T;
-
-struct statuscol {
- int width; ///< width of the status column
- int cur_attr; ///< current attributes in text
- int num_attr; ///< attributes used for line number
- int fold_attr; ///< attributes used for fold column
- int sign_attr[SIGN_SHOW_MAX + 1]; ///< attributes used for signs
- int truncate; ///< truncated width
- bool draw; ///< draw statuscolumn or not
- char fold_text[9 * 4 + 1]; ///< text in fold column (%C)
- char *sign_text[SIGN_SHOW_MAX + 1]; ///< text in sign column (%s)
- char text[MAXPATHL]; ///< text in status column
- char *textp; ///< current position in text
- char *text_end; ///< end of text (the NUL byte)
- stl_hlrec_t *hlrec; ///< highlight groups
- stl_hlrec_t *hlrecp; ///< current highlight group
-};
-
/// Macros defined in Vim, but not in Neovim
// uncrustify:off
#define CHANGEDTICK(buf) \
(=== Include buffer.h & use buf_(get|set|inc) _changedtick ===)
// uncrustify:on
-
-#endif // NVIM_BUFFER_DEFS_H
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index 075ac2adbf..01bcb9d7be 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -1,28 +1,25 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/buffer.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
-#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.c.generated.h" // IWYU pragma: export
@@ -188,9 +185,9 @@ void buf_updates_unload(buf_T *buf, bool can_reload)
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
- textlock++;
- nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
- textlock--;
+ TEXTLOCK_WRAP({
+ nlua_call_ref(thecb, keep ? "reload" : "detach", args, false, NULL);
+ });
}
if (keep) {
@@ -305,9 +302,11 @@ void buf_updates_send_changes(buf_T *buf, linenr_T firstline, int64_t num_added,
args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints);
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
- textlock++;
- Object res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL);
- textlock--;
+
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_lines, "lines", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
@@ -354,9 +353,10 @@ void buf_updates_send_splice(buf_T *buf, int start_row, colnr_T start_col, bcoun
ADD_C(args, INTEGER_OBJ(new_col));
ADD_C(args, INTEGER_OBJ(new_byte));
- textlock++;
- Object res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL);
- textlock--;
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_bytes, "bytes", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
@@ -389,10 +389,10 @@ void buf_updates_changedtick(buf_T *buf)
// next argument is b:changedtick
ADD_C(args, INTEGER_OBJ(buf_get_changedtick(buf)));
- textlock++;
- Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
- args, false, NULL);
- textlock--;
+ Object res;
+ TEXTLOCK_WRAP({
+ res = nlua_call_ref(cb.on_changedtick, "changedtick", args, false, NULL);
+ });
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
buffer_update_callbacks_free(cb);
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index 961fec879b..e844f3b2a8 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_BUFFER_UPDATES_H
-#define NVIM_BUFFER_UPDATES_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/extmark.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/extmark_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
#endif
-
-#endif // NVIM_BUFFER_UPDATES_H
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
new file mode 100644
index 0000000000..f774fcb057
--- /dev/null
+++ b/src/nvim/bufwrite.c
@@ -0,0 +1,1943 @@
+// bufwrite.c: functions for writing a buffer
+
+#include <fcntl.h>
+#include <iconv.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <uv.h>
+
+#include "auto/config.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
+#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/bufwrite.h"
+#include "nvim/change.h"
+#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_eval.h"
+#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/highlight.h"
+#include "nvim/iconv_defs.h"
+#include "nvim/input.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mbyte.h"
+#include "nvim/memline.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/input.h"
+#include "nvim/path.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sha256.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
+#include "nvim/ui.h"
+#include "nvim/undo.h"
+#include "nvim/vim_defs.h"
+
+static const char *err_readonly = "is read-only (cannot override: \"W\" in 'cpoptions')";
+static const char e_patchmode_cant_touch_empty_original_file[]
+ = N_("E206: Patchmode: can't touch empty original file");
+static const char e_write_error_conversion_failed_make_fenc_empty_to_override[]
+ = N_("E513: Write error, conversion failed (make 'fenc' empty to override)");
+static const char e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override[]
+ = N_("E513: Write error, conversion failed in line %" PRIdLINENR
+ " (make 'fenc' empty to override)");
+static const char e_write_error_file_system_full[]
+ = N_("E514: Write error (file system full?)");
+static const char e_no_matching_autocommands_for_buftype_str_buffer[]
+ = N_("E676: No matching autocommands for buftype=%s buffer");
+
+typedef struct {
+ const char *num;
+ char *msg;
+ int arg;
+ bool alloc;
+} Error_T;
+
+#define SMALLBUFSIZE 256 // size of emergency write buffer
+
+// Structure to pass arguments from buf_write() to buf_write_bytes().
+struct bw_info {
+ int bw_fd; // file descriptor
+ char *bw_buf; // buffer with data to be written
+ int bw_len; // length of data
+ int bw_flags; // FIO_ flags
+ uint8_t bw_rest[CONV_RESTLEN]; // not converted bytes
+ int bw_restlen; // nr of bytes in bw_rest[]
+ int bw_first; // first write call
+ char *bw_conv_buf; // buffer for writing converted chars
+ size_t bw_conv_buflen; // size of bw_conv_buf
+ int bw_conv_error; // set for conversion error
+ linenr_T bw_conv_error_lnum; // first line with error or zero
+ linenr_T bw_start_lnum; // line number at start of buffer
+ iconv_t bw_iconv_fd; // descriptor for iconv() or -1
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "bufwrite.c.generated.h"
+#endif
+
+/// Convert a Unicode character to bytes.
+///
+/// @param c character to convert
+/// @param[in,out] pp pointer to store the result at
+/// @param flags FIO_ flags that specify which encoding to use
+///
+/// @return true for an error, false when it's OK.
+static bool ucs2bytes(unsigned c, char **pp, int flags)
+ FUNC_ATTR_NONNULL_ALL
+{
+ uint8_t *p = (uint8_t *)(*pp);
+ bool error = false;
+
+ if (flags & FIO_UCS4) {
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)c;
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)(c >> 16);
+ *p++ = (uint8_t)(c >> 24);
+ } else {
+ *p++ = (uint8_t)(c >> 24);
+ *p++ = (uint8_t)(c >> 16);
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)c;
+ }
+ } else if (flags & (FIO_UCS2 | FIO_UTF16)) {
+ if (c >= 0x10000) {
+ if (flags & FIO_UTF16) {
+ // Make two words, ten bits of the character in each. First
+ // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff
+ c -= 0x10000;
+ if (c >= 0x100000) {
+ error = true;
+ }
+ int cc = (int)(((c >> 10) & 0x3ff) + 0xd800);
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)cc;
+ *p++ = (uint8_t)(cc >> 8);
+ } else {
+ *p++ = (uint8_t)(cc >> 8);
+ *p++ = (uint8_t)cc;
+ }
+ c = (c & 0x3ff) + 0xdc00;
+ } else {
+ error = true;
+ }
+ }
+ if (flags & FIO_ENDIAN_L) {
+ *p++ = (uint8_t)c;
+ *p++ = (uint8_t)(c >> 8);
+ } else {
+ *p++ = (uint8_t)(c >> 8);
+ *p++ = (uint8_t)c;
+ }
+ } else { // Latin1
+ if (c >= 0x100) {
+ error = true;
+ *p++ = 0xBF;
+ } else {
+ *p++ = (uint8_t)c;
+ }
+ }
+
+ *pp = (char *)p;
+ return error;
+}
+
+static int buf_write_convert_with_iconv(struct bw_info *ip, char **bufp, int *lenp)
+{
+ const char *from;
+ size_t fromlen;
+ size_t tolen;
+
+ int len = *lenp;
+
+ // Convert with iconv().
+ if (ip->bw_restlen > 0) {
+ // Need to concatenate the remainder of the previous call and
+ // the bytes of the current call. Use the end of the
+ // conversion buffer for this.
+ fromlen = (size_t)len + (size_t)ip->bw_restlen;
+ char *fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
+ memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen);
+ memmove(fp + ip->bw_restlen, *bufp, (size_t)len);
+ from = fp;
+ tolen = ip->bw_conv_buflen - fromlen;
+ } else {
+ from = *bufp;
+ fromlen = (size_t)len;
+ tolen = ip->bw_conv_buflen;
+ }
+ char *to = ip->bw_conv_buf;
+
+ if (ip->bw_first) {
+ size_t save_len = tolen;
+
+ // output the initial shift state sequence
+ (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen);
+
+ // There is a bug in iconv() on Linux (which appears to be
+ // wide-spread) which sets "to" to NULL and messes up "tolen".
+ if (to == NULL) {
+ to = ip->bw_conv_buf;
+ tolen = save_len;
+ }
+ ip->bw_first = false;
+ }
+
+ // If iconv() has an error or there is not enough room, fail.
+ if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen)
+ == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
+ || fromlen > CONV_RESTLEN) {
+ ip->bw_conv_error = true;
+ return FAIL;
+ }
+
+ // copy remainder to ip->bw_rest[] to be used for the next call.
+ if (fromlen > 0) {
+ memmove(ip->bw_rest, (void *)from, fromlen);
+ }
+ ip->bw_restlen = (int)fromlen;
+
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(to - ip->bw_conv_buf);
+
+ return OK;
+}
+
+static int buf_write_convert(struct bw_info *ip, char **bufp, int *lenp)
+{
+ int flags = ip->bw_flags; // extra flags
+
+ if (flags & FIO_UTF8) {
+ // Convert latin1 in the buffer to UTF-8 in the file.
+ char *p = ip->bw_conv_buf; // translate to buffer
+ for (int wlen = 0; wlen < *lenp; wlen++) {
+ p += utf_char2bytes((uint8_t)(*bufp)[wlen], p);
+ }
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(p - ip->bw_conv_buf);
+ } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) {
+ unsigned c;
+ int n = 0;
+ // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or
+ // Latin1 chars in the file.
+ // translate in-place (can only get shorter) or to buffer
+ char *p = flags & FIO_LATIN1 ? *bufp : ip->bw_conv_buf;
+ for (int wlen = 0; wlen < *lenp; wlen += n) {
+ if (wlen == 0 && ip->bw_restlen != 0) {
+ // Use remainder of previous call. Append the start of
+ // buf[] to get a full sequence. Might still be too
+ // short!
+ int l = MIN(*lenp, CONV_RESTLEN - ip->bw_restlen);
+ memmove(ip->bw_rest + ip->bw_restlen, *bufp, (size_t)l);
+ n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l);
+ if (n > ip->bw_restlen + *lenp) {
+ // We have an incomplete byte sequence at the end to
+ // be written. We can't convert it without the
+ // remaining bytes. Keep them for the next call.
+ if (ip->bw_restlen + *lenp > CONV_RESTLEN) {
+ return FAIL;
+ }
+ ip->bw_restlen += *lenp;
+ break;
+ }
+ if (n > 1) {
+ c = (unsigned)utf_ptr2char((char *)ip->bw_rest);
+ } else {
+ c = ip->bw_rest[0];
+ }
+ if (n >= ip->bw_restlen) {
+ n -= ip->bw_restlen;
+ ip->bw_restlen = 0;
+ } else {
+ ip->bw_restlen -= n;
+ memmove(ip->bw_rest, ip->bw_rest + n,
+ (size_t)ip->bw_restlen);
+ n = 0;
+ }
+ } else {
+ n = utf_ptr2len_len(*bufp + wlen, *lenp - wlen);
+ if (n > *lenp - wlen) {
+ // We have an incomplete byte sequence at the end to
+ // be written. We can't convert it without the
+ // remaining bytes. Keep them for the next call.
+ if (*lenp - wlen > CONV_RESTLEN) {
+ return FAIL;
+ }
+ ip->bw_restlen = *lenp - wlen;
+ memmove(ip->bw_rest, *bufp + wlen,
+ (size_t)ip->bw_restlen);
+ break;
+ }
+ if (n > 1) {
+ c = (unsigned)utf_ptr2char(*bufp + wlen);
+ } else {
+ c = (uint8_t)(*bufp)[wlen];
+ }
+ }
+
+ if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) {
+ ip->bw_conv_error = true;
+ ip->bw_conv_error_lnum = ip->bw_start_lnum;
+ }
+ if (c == NL) {
+ ip->bw_start_lnum++;
+ }
+ }
+ if (flags & FIO_LATIN1) {
+ *lenp = (int)(p - *bufp);
+ } else {
+ *bufp = ip->bw_conv_buf;
+ *lenp = (int)(p - ip->bw_conv_buf);
+ }
+ }
+
+ if (ip->bw_iconv_fd != (iconv_t)-1) {
+ if (buf_write_convert_with_iconv(ip, bufp, lenp) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ return OK;
+}
+
+/// Call write() to write a number of bytes to the file.
+/// Handles 'encoding' conversion.
+///
+/// @return FAIL for failure, OK otherwise.
+static int buf_write_bytes(struct bw_info *ip)
+{
+ char *buf = ip->bw_buf; // data to write
+ int len = ip->bw_len; // length of data
+ int flags = ip->bw_flags; // extra flags
+
+ // Skip conversion when writing the BOM.
+ if (!(flags & FIO_NOCONVERT)) {
+ if (buf_write_convert(ip, &buf, &len) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ if (ip->bw_fd < 0) {
+ // Only checking conversion, which is OK if we get here.
+ return OK;
+ }
+ int wlen = write_eintr(ip->bw_fd, buf, (size_t)len);
+ return (wlen < len) ? FAIL : OK;
+}
+
+/// Check modification time of file, before writing to it.
+/// The size isn't checked, because using a tool like "gzip" takes care of
+/// using the same timestamp but can't set the size.
+static int check_mtime(buf_T *buf, FileInfo *file_info)
+{
+ if (buf->b_mtime_read != 0
+ && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
+ msg_scroll = true; // Don't overwrite messages here.
+ msg_silent = 0; // Must give this prompt.
+ // Don't use emsg() here, don't want to flush the buffers.
+ msg(_("WARNING: The file has been changed since reading it!!!"), HL_ATTR(HLF_E));
+ if (ask_yesno(_("Do you really want to write to it"), true) == 'n') {
+ return FAIL;
+ }
+ msg_scroll = false; // Always overwrite the file message now.
+ }
+ return OK;
+}
+
+/// Generate a BOM in "buf[4]" for encoding "name".
+///
+/// @return the length of the BOM (zero when no BOM).
+static int make_bom(char *buf_in, char *name)
+{
+ uint8_t *buf = (uint8_t *)buf_in;
+ int flags = get_fio_flags(name);
+
+ // Can't put a BOM in a non-Unicode file.
+ if (flags == FIO_LATIN1 || flags == 0) {
+ return 0;
+ }
+
+ if (flags == FIO_UTF8) { // UTF-8
+ buf[0] = 0xef;
+ buf[1] = 0xbb;
+ buf[2] = 0xbf;
+ return 3;
+ }
+ char *p = (char *)buf;
+ (void)ucs2bytes(0xfeff, &p, flags);
+ return (int)((uint8_t *)p - buf);
+}
+
+static int buf_write_do_autocmds(buf_T *buf, char **fnamep, char **sfnamep, char **ffnamep,
+ linenr_T start, linenr_T *endp, exarg_T *eap, bool append,
+ bool filtering, bool reset_changed, bool overwriting, bool whole,
+ const pos_T orig_start, const pos_T orig_end)
+{
+ linenr_T old_line_count = buf->b_ml.ml_line_count;
+ int msg_save = msg_scroll;
+
+ aco_save_T aco;
+ bool did_cmd = false;
+ bool nofile_err = false;
+ bool empty_memline = buf->b_ml.ml_mfp == NULL;
+ bufref_T bufref;
+
+ char *sfname = *sfnamep;
+
+ // Apply PRE autocommands.
+ // Set curbuf to the buffer to be written.
+ // Careful: The autocommands may call buf_write() recursively!
+ bool buf_ffname = *ffnamep == buf->b_ffname;
+ bool buf_sfname = sfname == buf->b_sfname;
+ bool buf_fname_f = *fnamep == buf->b_ffname;
+ bool buf_fname_s = *fnamep == buf->b_sfname;
+
+ // Set curwin/curbuf to buf and save a few things.
+ aucmd_prepbuf(&aco, buf);
+ set_bufref(&bufref, buf);
+
+ if (append) {
+ did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, sfname, sfname, false, curbuf, eap);
+ if (!did_cmd) {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_FILEAPPENDPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ } else if (filtering) {
+ apply_autocmds_exarg(EVENT_FILTERWRITEPRE,
+ NULL, sfname, false, curbuf, eap);
+ } else if (reset_changed && whole) {
+ bool was_changed = curbufIsChanged();
+
+ did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, sfname, sfname, false, curbuf, eap);
+ if (did_cmd) {
+ if (was_changed && !curbufIsChanged()) {
+ // Written everything correctly and BufWriteCmd has reset
+ // 'modified': Correct the undo information so that an
+ // undo now sets 'modified'.
+ u_unchanged(curbuf);
+ u_update_save_nr(curbuf);
+ }
+ } else {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_BUFWRITEPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ } else {
+ did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, sfname, sfname, false, curbuf, eap);
+ if (!did_cmd) {
+ if (overwriting && bt_nofilename(curbuf)) {
+ nofile_err = true;
+ } else {
+ apply_autocmds_exarg(EVENT_FILEWRITEPRE,
+ sfname, sfname, false, curbuf, eap);
+ }
+ }
+ }
+
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+
+ // In three situations we return here and don't write the file:
+ // 1. the autocommands deleted or unloaded the buffer.
+ // 2. The autocommands abort script processing.
+ // 3. If one of the "Cmd" autocommands was executed.
+ if (!bufref_valid(&bufref)) {
+ buf = NULL;
+ }
+ if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
+ || did_cmd || nofile_err
+ || aborting()) {
+ if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ no_wait_return--;
+ msg_scroll = msg_save;
+ if (nofile_err) {
+ semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt);
+ }
+
+ if (nofile_err
+ || aborting()) {
+ // An aborting error, interrupt or exception in the
+ // autocommands.
+ return FAIL;
+ }
+ if (did_cmd) {
+ if (buf == NULL) {
+ // The buffer was deleted. We assume it was written
+ // (can't retry anyway).
+ return OK;
+ }
+ if (overwriting) {
+ // Assume the buffer was written, update the timestamp.
+ ml_timestamp(buf);
+ if (append) {
+ buf->b_flags &= ~BF_NEW;
+ } else {
+ buf->b_flags &= ~BF_WRITE_MASK;
+ }
+ }
+ if (reset_changed && buf->b_changed && !append
+ && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
+ // Buffer still changed, the autocommands didn't work properly.
+ return FAIL;
+ }
+ return OK;
+ }
+ if (!aborting()) {
+ emsg(_("E203: Autocommands deleted or unloaded buffer to be written"));
+ }
+ return FAIL;
+ }
+
+ // The autocommands may have changed the number of lines in the file.
+ // When writing the whole file, adjust the end.
+ // When writing part of the file, assume that the autocommands only
+ // changed the number of lines that are to be written (tricky!).
+ if (buf->b_ml.ml_line_count != old_line_count) {
+ if (whole) { // write all
+ *endp = buf->b_ml.ml_line_count;
+ } else if (buf->b_ml.ml_line_count > old_line_count) { // more lines
+ *endp += buf->b_ml.ml_line_count - old_line_count;
+ } else { // less lines
+ *endp -= old_line_count - buf->b_ml.ml_line_count;
+ if (*endp < start) {
+ no_wait_return--;
+ msg_scroll = msg_save;
+ emsg(_("E204: Autocommand changed number of lines in unexpected way"));
+ return FAIL;
+ }
+ }
+ }
+
+ // The autocommands may have changed the name of the buffer, which may
+ // be kept in fname, ffname and sfname.
+ if (buf_ffname) {
+ *ffnamep = buf->b_ffname;
+ }
+ if (buf_sfname) {
+ *sfnamep = buf->b_sfname;
+ }
+ if (buf_fname_f) {
+ *fnamep = buf->b_ffname;
+ }
+ if (buf_fname_s) {
+ *fnamep = buf->b_sfname;
+ }
+ return NOTDONE;
+}
+
+static void buf_write_do_post_autocmds(buf_T *buf, char *fname, exarg_T *eap, bool append,
+ bool filtering, bool reset_changed, bool whole)
+{
+ aco_save_T aco;
+
+ curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read
+
+ // Apply POST autocommands.
+ // Careful: The autocommands may call buf_write() recursively!
+ aucmd_prepbuf(&aco, buf);
+
+ if (append) {
+ apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname,
+ false, curbuf, eap);
+ } else if (filtering) {
+ apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname,
+ false, curbuf, eap);
+ } else if (reset_changed && whole) {
+ apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname,
+ false, curbuf, eap);
+ } else {
+ apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname,
+ false, curbuf, eap);
+ }
+
+ // restore curwin/curbuf and a few other things
+ aucmd_restbuf(&aco);
+}
+
+static inline Error_T set_err_num(const char *num, const char *msg)
+{
+ return (Error_T){ .num = num, .msg = (char *)msg, .arg = 0 };
+}
+
+static inline Error_T set_err(const char *msg)
+{
+ return (Error_T){ .num = NULL, .msg = (char *)msg, .arg = 0 };
+}
+
+static inline Error_T set_err_arg(const char *msg, int arg)
+{
+ return (Error_T){ .num = NULL, .msg = (char *)msg, .arg = arg };
+}
+
+static void emit_err(Error_T *e)
+{
+ if (e->num != NULL) {
+ if (e->arg != 0) {
+ semsg("%s: %s%s: %s", e->num, IObuff, e->msg, os_strerror(e->arg));
+ } else {
+ semsg("%s: %s%s", e->num, IObuff, e->msg);
+ }
+ } else if (e->arg != 0) {
+ semsg(e->msg, os_strerror(e->arg));
+ } else {
+ emsg(e->msg);
+ }
+ if (e->alloc) {
+ xfree(e->msg);
+ }
+}
+
+#if defined(UNIX)
+
+static int get_fileinfo_os(char *fname, FileInfo *file_info_old, bool overwriting, int *perm,
+ bool *device, bool *newfile, Error_T *err)
+{
+ *perm = -1;
+ if (!os_fileinfo(fname, file_info_old)) {
+ *newfile = true;
+ } else {
+ *perm = (int)file_info_old->stat.st_mode;
+ if (!S_ISREG(file_info_old->stat.st_mode)) { // not a file
+ if (S_ISDIR(file_info_old->stat.st_mode)) {
+ *err = set_err_num("E502", _("is a directory"));
+ return FAIL;
+ }
+ if (os_nodetype(fname) != NODE_WRITABLE) {
+ *err = set_err_num("E503", _("is not a file or writable device"));
+ return FAIL;
+ }
+ // It's a device of some kind (or a fifo) which we can write to
+ // but for which we can't make a backup.
+ *device = true;
+ *newfile = true;
+ *perm = -1;
+ }
+ }
+ return OK;
+}
+
+#else
+
+static int get_fileinfo_os(char *fname, FileInfo *file_info_old, bool overwriting, int *perm,
+ bool *device, bool *newfile, Error_T *err)
+{
+ // Check for a writable device name.
+ char nodetype = fname == NULL ? NODE_OTHER : (char)os_nodetype(fname);
+ if (nodetype == NODE_OTHER) {
+ *err = set_err_num("E503", _("is not a file or writable device"));
+ return FAIL;
+ }
+ if (nodetype == NODE_WRITABLE) {
+ *device = true;
+ *newfile = true;
+ *perm = -1;
+ } else {
+ *perm = os_getperm(fname);
+ if (*perm < 0) {
+ *newfile = true;
+ } else if (os_isdir(fname)) {
+ *err = set_err_num("E502", _("is a directory"));
+ return FAIL;
+ }
+ if (overwriting) {
+ os_fileinfo(fname, file_info_old);
+ }
+ }
+ return OK;
+}
+
+#endif
+
+/// @param buf
+/// @param fname File name
+/// @param overwriting
+/// @param forceit
+/// @param[out] file_info_old
+/// @param[out] perm
+/// @param[out] device
+/// @param[out] newfile
+/// @param[out] readonly
+static int get_fileinfo(buf_T *buf, char *fname, bool overwriting, bool forceit,
+ FileInfo *file_info_old, int *perm, bool *device, bool *newfile,
+ bool *readonly, Error_T *err)
+{
+ if (get_fileinfo_os(fname, file_info_old, overwriting, perm, device, newfile, err) == FAIL) {
+ return FAIL;
+ }
+
+ *readonly = false; // overwritten file is read-only
+
+ if (!*device && !*newfile) {
+ // Check if the file is really writable (when renaming the file to
+ // make a backup we won't discover it later).
+ *readonly = !os_file_is_writable(fname);
+
+ if (!forceit && *readonly) {
+ if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
+ *err = set_err_num("E504", _(err_readonly));
+ } else {
+ *err = set_err_num("E505", _("is read-only (add ! to override)"));
+ }
+ return FAIL;
+ }
+
+ // If 'forceit' is false, check if the timestamp hasn't changed since reading the file.
+ if (overwriting && !forceit) {
+ int retval = check_mtime(buf, file_info_old);
+ if (retval == FAIL) {
+ return FAIL;
+ }
+ }
+ }
+ return OK;
+}
+
+static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_old, vim_acl_T acl,
+ int perm, unsigned bkc, bool file_readonly, bool forceit,
+ int *backup_copyp, char **backupp, Error_T *err)
+{
+ FileInfo file_info;
+ const bool no_prepend_dot = false;
+
+ if ((bkc & BKC_YES) || append) { // "yes"
+ *backup_copyp = true;
+ } else if ((bkc & BKC_AUTO)) { // "auto"
+ // Don't rename the file when:
+ // - it's a hard link
+ // - it's a symbolic link
+ // - we don't have write permission in the directory
+ if (os_fileinfo_hardlinks(file_info_old) > 1
+ || !os_fileinfo_link(fname, &file_info)
+ || !os_fileinfo_id_equal(&file_info, file_info_old)) {
+ *backup_copyp = true;
+ } else {
+ // Check if we can create a file and set the owner/group to
+ // the ones from the original file.
+ // First find a file name that doesn't exist yet (use some
+ // arbitrary numbers).
+ xstrlcpy(IObuff, fname, IOSIZE);
+ for (int i = 4913;; i += 123) {
+ char *tail = path_tail(IObuff);
+ size_t size = (size_t)(tail - IObuff);
+ snprintf(tail, IOSIZE - size, "%d", i);
+ if (!os_fileinfo_link(IObuff, &file_info)) {
+ break;
+ }
+ }
+ int fd = os_open(IObuff,
+ O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
+ if (fd < 0) { // can't write in directory
+ *backup_copyp = true;
+ } else {
+#ifdef UNIX
+ os_fchown(fd, (uv_uid_t)file_info_old->stat.st_uid, (uv_gid_t)file_info_old->stat.st_gid);
+ if (!os_fileinfo(IObuff, &file_info)
+ || file_info.stat.st_uid != file_info_old->stat.st_uid
+ || file_info.stat.st_gid != file_info_old->stat.st_gid
+ || (int)file_info.stat.st_mode != perm) {
+ *backup_copyp = true;
+ }
+#endif
+ // Close the file before removing it, on MS-Windows we
+ // can't delete an open file.
+ close(fd);
+ os_remove(IObuff);
+ }
+ }
+ }
+
+ // Break symlinks and/or hardlinks if we've been asked to.
+ if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) {
+#ifdef UNIX
+ bool file_info_link_ok = os_fileinfo_link(fname, &file_info);
+
+ // Symlinks.
+ if ((bkc & BKC_BREAKSYMLINK)
+ && file_info_link_ok
+ && !os_fileinfo_id_equal(&file_info, file_info_old)) {
+ *backup_copyp = false;
+ }
+
+ // Hardlinks.
+ if ((bkc & BKC_BREAKHARDLINK)
+ && os_fileinfo_hardlinks(file_info_old) > 1
+ && (!file_info_link_ok
+ || os_fileinfo_id_equal(&file_info, file_info_old))) {
+ *backup_copyp = false;
+ }
+#endif
+ }
+
+ // make sure we have a valid backup extension to use
+ char *backup_ext = *p_bex == NUL ? ".bak" : p_bex;
+
+ if (*backup_copyp) {
+ int some_error = false;
+
+ // Try to make the backup in each directory in the 'bdir' option.
+ //
+ // Unix semantics has it, that we may have a writable file,
+ // that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
+ // - the directory is not writable,
+ // - the file may be a symbolic link,
+ // - the file may belong to another user/group, etc.
+ //
+ // For these reasons, the existing writable file must be truncated
+ // and reused. Creation of a backup COPY will be attempted.
+ char *dirp = p_bdir;
+ while (*dirp) {
+ // Isolate one directory name, using an entry in 'bdir'.
+ size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ char *p = IObuff + dir_len;
+ bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
+ if (trailing_pathseps) {
+ IObuff[dir_len - 2] = NUL;
+ }
+ if (*dirp == NUL && !os_isdir(IObuff)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
+ semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ }
+ }
+ if (trailing_pathseps) {
+ // Ends with '//', Use Full path
+ if ((p = make_percent_swname(IObuff, fname))
+ != NULL) {
+ *backupp = modname(p, backup_ext, no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ char *rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ some_error = true; // out of memory
+ goto nobackup;
+ }
+
+ FileInfo file_info_new;
+ {
+ //
+ // Make the backup file name.
+ //
+ if (*backupp == NULL) {
+ *backupp = modname(rootname, backup_ext, no_prepend_dot);
+ }
+
+ if (*backupp == NULL) {
+ xfree(rootname);
+ some_error = true; // out of memory
+ goto nobackup;
+ }
+
+ // Check if backup file already exists.
+ if (os_fileinfo(*backupp, &file_info_new)) {
+ if (os_fileinfo_id_equal(&file_info_new, file_info_old)) {
+ //
+ // Backup file is same as original file.
+ // May happen when modname() gave the same file back (e.g. silly
+ // link). If we don't check here, we either ruin the file when
+ // copying or erase it after writing.
+ //
+ XFREE_CLEAR(*backupp); // no backup file to delete
+ } else if (!p_bk) {
+ // We are not going to keep the backup file, so don't
+ // delete an existing one, and try to use another name instead.
+ // Change one character, just before the extension.
+ //
+ char *wp = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
+ if (wp < *backupp) { // empty file name ???
+ wp = *backupp;
+ }
+ *wp = 'z';
+ while (*wp > 'a' && os_fileinfo(*backupp, &file_info_new)) {
+ (*wp)--;
+ }
+ // They all exist??? Must be something wrong.
+ if (*wp == 'a') {
+ XFREE_CLEAR(*backupp);
+ }
+ }
+ }
+ }
+ xfree(rootname);
+
+ // Try to create the backup file
+ if (*backupp != NULL) {
+ // remove old backup, if present
+ os_remove(*backupp);
+
+ // set file protection same as original file, but
+ // strip s-bit.
+ (void)os_setperm(*backupp, perm & 0777);
+
+#ifdef UNIX
+ //
+ // Try to set the group of the backup same as the original file. If
+ // this fails, set the protection bits for the group same as the
+ // protection bits for others.
+ //
+ if (file_info_new.stat.st_gid != file_info_old->stat.st_gid
+ && os_chown(*backupp, (uv_uid_t)-1, (uv_gid_t)file_info_old->stat.st_gid) != 0) {
+ os_setperm(*backupp, (perm & 0707) | ((perm & 07) << 3));
+ }
+# ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+# endif
+#endif
+
+ // copy the file
+ if (os_copy(fname, *backupp, UV_FS_COPYFILE_FICLONE) != 0) {
+ *err = set_err(_("E509: Cannot create backup file (add ! to override)"));
+ XFREE_CLEAR(*backupp);
+ *backupp = NULL;
+ continue;
+ }
+
+#ifdef UNIX
+ os_file_settime(*backupp,
+ (double)file_info_old->stat.st_atim.tv_sec,
+ (double)file_info_old->stat.st_mtim.tv_sec);
+#endif
+ os_set_acl(*backupp, acl);
+#ifdef HAVE_XATTR
+ os_copy_xattr(fname, *backupp);
+#endif
+ *err = set_err(NULL);
+ break;
+ }
+ }
+
+nobackup:
+ if (*backupp == NULL && err->msg == NULL) {
+ *err = set_err(_("E509: Cannot create backup file (add ! to override)"));
+ }
+ // Ignore errors when forceit is true.
+ if ((some_error || err->msg != NULL) && !forceit) {
+ return FAIL;
+ }
+ *err = set_err(NULL);
+ } else {
+ // Make a backup by renaming the original file.
+
+ // If 'cpoptions' includes the "W" flag, we don't want to
+ // overwrite a read-only file. But rename may be possible
+ // anyway, thus we need an extra check here.
+ if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
+ *err = set_err_num("E504", _(err_readonly));
+ return FAIL;
+ }
+
+ // Form the backup file name - change path/fo.o.h to
+ // path/fo.o.h.bak Try all directories in 'backupdir', first one
+ // that works is used.
+ char *dirp = p_bdir;
+ while (*dirp) {
+ // Isolate one directory name and make the backup file name.
+ size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
+ char *p = IObuff + dir_len;
+ bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
+ if (trailing_pathseps) {
+ IObuff[dir_len - 2] = NUL;
+ }
+ if (*dirp == NUL && !os_isdir(IObuff)) {
+ int ret;
+ char *failed_dir;
+ if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir, NULL)) != 0) {
+ semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
+ failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ }
+ }
+ if (trailing_pathseps) {
+ // path ends with '//', use full path
+ if ((p = make_percent_swname(IObuff, fname))
+ != NULL) {
+ *backupp = modname(p, backup_ext, no_prepend_dot);
+ xfree(p);
+ }
+ }
+
+ if (*backupp == NULL) {
+ char *rootname = get_file_in_dir(fname, IObuff);
+ if (rootname == NULL) {
+ *backupp = NULL;
+ } else {
+ *backupp = modname(rootname, backup_ext, no_prepend_dot);
+ xfree(rootname);
+ }
+ }
+
+ if (*backupp != NULL) {
+ // If we are not going to keep the backup file, don't
+ // delete an existing one, try to use another name.
+ // Change one character, just before the extension.
+ if (!p_bk && os_path_exists(*backupp)) {
+ p = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
+ if (p < *backupp) { // empty file name ???
+ p = *backupp;
+ }
+ *p = 'z';
+ while (*p > 'a' && os_path_exists(*backupp)) {
+ (*p)--;
+ }
+ // They all exist??? Must be something wrong!
+ if (*p == 'a') {
+ XFREE_CLEAR(*backupp);
+ }
+ }
+ }
+ if (*backupp != NULL) {
+ // Delete any existing backup and move the current version
+ // to the backup. For safety, we don't remove the backup
+ // until the write has finished successfully. And if the
+ // 'backup' option is set, leave it around.
+
+ // If the renaming of the original file to the backup file
+ // works, quit here.
+ ///
+ if (vim_rename(fname, *backupp) == 0) {
+ break;
+ }
+
+ XFREE_CLEAR(*backupp); // don't do the rename below
+ }
+ }
+ if (*backupp == NULL && !forceit) {
+ *err = set_err(_("E510: Can't make backup file (add ! to override)"));
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
+/// buf_write() - write to file "fname" lines "start" through "end"
+///
+/// We do our own buffering here because fwrite() is so slow.
+///
+/// If "forceit" is true, we don't care for errors when attempting backups.
+/// In case of an error everything possible is done to restore the original
+/// file. But when "forceit" is true, we risk losing it.
+///
+/// When "reset_changed" is true and "append" == false and "start" == 1 and
+/// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
+///
+/// This function must NOT use NameBuff (because it's called by autowrite()).
+///
+///
+/// @param eap for forced 'ff' and 'fenc', can be NULL!
+/// @param append append to the file
+///
+/// @return FAIL for failure, OK otherwise
+int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T end, exarg_T *eap,
+ int append, int forceit, int reset_changed, int filtering)
+{
+ int retval = OK;
+ int msg_save = msg_scroll;
+ int prev_got_int = got_int;
+ // writing everything
+ int whole = (start == 1 && end == buf->b_ml.ml_line_count);
+ int write_undo_file = false;
+ context_sha256_T sha_ctx;
+ unsigned bkc = get_bkc_value(buf);
+
+ if (fname == NULL || *fname == NUL) { // safety check
+ return FAIL;
+ }
+ if (buf->b_ml.ml_mfp == NULL) {
+ // This can happen during startup when there is a stray "w" in the
+ // vimrc file.
+ emsg(_(e_empty_buffer));
+ return FAIL;
+ }
+
+ // Disallow writing in secure mode.
+ if (check_secure()) {
+ return FAIL;
+ }
+
+ // Avoid a crash for a long name.
+ if (strlen(fname) >= MAXPATHL) {
+ emsg(_(e_longname));
+ return FAIL;
+ }
+
+ // must init bw_conv_buf and bw_iconv_fd before jumping to "fail"
+ struct bw_info write_info; // info for buf_write_bytes()
+ write_info.bw_conv_buf = NULL;
+ write_info.bw_conv_error = false;
+ write_info.bw_conv_error_lnum = 0;
+ write_info.bw_restlen = 0;
+ write_info.bw_iconv_fd = (iconv_t)-1;
+
+ // After writing a file changedtick changes but we don't want to display
+ // the line.
+ ex_no_reprint = true;
+
+ // If there is no file name yet, use the one for the written file.
+ // BF_NOTEDITED is set to reflect this (in case the write fails).
+ // Don't do this when the write is for a filter command.
+ // Don't do this when appending.
+ // Only do this when 'cpoptions' contains the 'F' flag.
+ if (buf->b_ffname == NULL
+ && reset_changed
+ && whole
+ && buf == curbuf
+ && !bt_nofilename(buf)
+ && !filtering
+ && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
+ && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) {
+ if (set_rw_fname(fname, sfname) == FAIL) {
+ return FAIL;
+ }
+ buf = curbuf; // just in case autocmds made "buf" invalid
+ }
+
+ if (sfname == NULL) {
+ sfname = fname;
+ }
+
+ // For Unix: Use the short file name whenever possible.
+ // Avoids problems with networks and when directory names are changed.
+ // Don't do this for Windows, a "cd" in a sub-shell may have moved us to
+ // another directory, which we don't detect.
+ char *ffname = fname; // remember full fname
+#ifdef UNIX
+ fname = sfname;
+#endif
+
+// true if writing over original
+ int overwriting = buf->b_ffname != NULL && path_fnamecmp(ffname, buf->b_ffname) == 0;
+
+ no_wait_return++; // don't wait for return yet
+
+ const pos_T orig_start = buf->b_op_start;
+ const pos_T orig_end = buf->b_op_end;
+
+ // Set '[ and '] marks to the lines to be written.
+ buf->b_op_start.lnum = start;
+ buf->b_op_start.col = 0;
+ buf->b_op_end.lnum = end;
+ buf->b_op_end.col = 0;
+
+ int res = buf_write_do_autocmds(buf, &fname, &sfname, &ffname, start, &end, eap, append,
+ filtering, reset_changed, overwriting, whole, orig_start,
+ orig_end);
+ if (res != NOTDONE) {
+ return res;
+ }
+
+ if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
+ // restore the original '[ and '] positions
+ buf->b_op_start = orig_start;
+ buf->b_op_end = orig_end;
+ }
+
+ if (shortmess(SHM_OVER) && !exiting) {
+ msg_scroll = false; // overwrite previous file message
+ } else {
+ msg_scroll = true; // don't overwrite previous file message
+ }
+ if (!filtering) {
+ // show that we are busy
+#ifndef UNIX
+ filemess(buf, sfname, "", 0);
+#else
+ filemess(buf, fname, "", 0);
+#endif
+ }
+ msg_scroll = false; // always overwrite the file message now
+
+ char *buffer = verbose_try_malloc(WRITEBUFSIZE);
+ int bufsize;
+ char smallbuf[SMALLBUFSIZE];
+ // can't allocate big buffer, use small one (to be able to write when out of
+ // memory)
+ if (buffer == NULL) {
+ buffer = smallbuf;
+ bufsize = SMALLBUFSIZE;
+ } else {
+ bufsize = WRITEBUFSIZE;
+ }
+
+ Error_T err = { 0 };
+ int perm; // file permissions
+ bool newfile = false; // true if file doesn't exist yet
+ bool device = false; // writing to a device
+ bool file_readonly = false; // overwritten file is read-only
+ char *backup = NULL;
+ char *fenc_tofree = NULL; // allocated "fenc"
+
+ // Get information about original file (if there is one).
+ FileInfo file_info_old;
+
+ vim_acl_T acl = NULL; // ACL copied from original file to
+ // backup or new file
+
+ if (get_fileinfo(buf, fname, overwriting, forceit, &file_info_old, &perm, &device, &newfile,
+ &file_readonly, &err) == FAIL) {
+ goto fail;
+ }
+
+ // For systems that support ACL: get the ACL from the original file.
+ if (!newfile) {
+ acl = os_get_acl(fname);
+ }
+
+ // If 'backupskip' is not empty, don't make a backup for some files.
+ bool dobackup = (p_wb || p_bk || *p_pm != NUL);
+ if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) {
+ dobackup = false;
+ }
+
+ int backup_copy = false; // copy the original file?
+
+ // Save the value of got_int and reset it. We don't want a previous
+ // interruption cancel writing, only hitting CTRL-C while writing should
+ // abort it.
+ prev_got_int = got_int;
+ got_int = false;
+
+ // Mark the buffer as 'being saved' to prevent changed buffer warnings
+ buf->b_saving = true;
+
+ // If we are not appending or filtering, the file exists, and the
+ // 'writebackup', 'backup' or 'patchmode' option is set, need a backup.
+ // When 'patchmode' is set also make a backup when appending.
+ //
+ // Do not make any backup, if 'writebackup' and 'backup' are both switched
+ // off. This helps when editing large files on almost-full disks.
+ if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
+ if (buf_write_make_backup(fname, append, &file_info_old, acl, perm, bkc, file_readonly, forceit,
+ &backup_copy, &backup, &err) == FAIL) {
+ retval = FAIL;
+ goto fail;
+ }
+ }
+
+#if defined(UNIX)
+ int made_writable = false; // 'w' bit has been set
+
+ // When using ":w!" and the file was read-only: make it writable
+ if (forceit && perm >= 0 && !(perm & 0200)
+ && file_info_old.stat.st_uid == getuid()
+ && vim_strchr(p_cpo, CPO_FWRITE) == NULL) {
+ perm |= 0200;
+ (void)os_setperm(fname, perm);
+ made_writable = true;
+ }
+#endif
+
+ // When using ":w!" and writing to the current file, 'readonly' makes no
+ // sense, reset it, unless 'Z' appears in 'cpoptions'.
+ if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) {
+ buf->b_p_ro = false;
+ need_maketitle = true; // set window title later
+ status_redraw_all(); // redraw status lines later
+ }
+
+ if (end > buf->b_ml.ml_line_count) {
+ end = buf->b_ml.ml_line_count;
+ }
+ if (buf->b_ml.ml_flags & ML_EMPTY) {
+ start = end + 1;
+ }
+
+ char *wfname = NULL; // name of file to write to
+
+ // If the original file is being overwritten, there is a small chance that
+ // we crash in the middle of writing. Therefore the file is preserved now.
+ // This makes all block numbers positive so that recovery does not need
+ // the original file.
+ // Don't do this if there is a backup file and we are exiting.
+ if (reset_changed && !newfile && overwriting
+ && !(exiting && backup != NULL)) {
+ ml_preserve(buf, false, !!p_fs);
+ if (got_int) {
+ err = set_err(_(e_interr));
+ goto restore_backup;
+ }
+ }
+
+ // Default: write the file directly. May write to a temp file for
+ // multi-byte conversion.
+ wfname = fname;
+
+ char *fenc; // effective 'fileencoding'
+
+ // Check for forced 'fileencoding' from "++opt=val" argument.
+ if (eap != NULL && eap->force_enc != 0) {
+ fenc = eap->cmd + eap->force_enc;
+ fenc = enc_canonize(fenc);
+ fenc_tofree = fenc;
+ } else {
+ fenc = buf->b_p_fenc;
+ }
+
+ // Check if the file needs to be converted.
+ int converted = need_conversion(fenc);
+ int wb_flags = 0;
+
+ // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or
+ // Latin1 to Unicode conversion. This is handled in buf_write_bytes().
+ // Prepare the flags for it and allocate bw_conv_buf when needed.
+ if (converted) {
+ wb_flags = get_fio_flags(fenc);
+ if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) {
+ // Need to allocate a buffer to translate into.
+ if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) {
+ write_info.bw_conv_buflen = (size_t)bufsize * 2;
+ } else { // FIO_UCS4
+ write_info.bw_conv_buflen = (size_t)bufsize * 4;
+ }
+ write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
+ if (!write_info.bw_conv_buf) {
+ end = 0;
+ }
+ }
+ }
+
+ if (converted && wb_flags == 0) {
+ // Use iconv() conversion when conversion is needed and it's not done
+ // internally.
+ write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8");
+ if (write_info.bw_iconv_fd != (iconv_t)-1) {
+ // We're going to use iconv(), allocate a buffer to convert in.
+ write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT;
+ write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
+ if (!write_info.bw_conv_buf) {
+ end = 0;
+ }
+ write_info.bw_first = true;
+ } else {
+ // When the file needs to be converted with 'charconvert' after
+ // writing, write to a temp file instead and let the conversion
+ // overwrite the original file.
+ if (*p_ccv != NUL) {
+ wfname = vim_tempname();
+ if (wfname == NULL) { // Can't write without a tempfile!
+ err = set_err(_("E214: Can't find temp file for writing"));
+ goto restore_backup;
+ }
+ }
+ }
+ }
+
+ int notconverted = false;
+
+ if (converted && wb_flags == 0
+ && write_info.bw_iconv_fd == (iconv_t)-1
+ && wfname == fname) {
+ if (!forceit) {
+ err = set_err(_("E213: Cannot convert (add ! to write without conversion)"));
+ goto restore_backup;
+ }
+ notconverted = true;
+ }
+
+ int no_eol = false; // no end-of-line written
+ int nchars;
+ linenr_T lnum;
+ int fileformat;
+ int checking_conversion;
+
+ int fd;
+
+ // If conversion is taking place, we may first pretend to write and check
+ // for conversion errors. Then loop again to write for real.
+ // When not doing conversion this writes for real right away.
+ for (checking_conversion = true;; checking_conversion = false) {
+ // There is no need to check conversion when:
+ // - there is no conversion
+ // - we make a backup file, that can be restored in case of conversion
+ // failure.
+ if (!converted || dobackup) {
+ checking_conversion = false;
+ }
+
+ if (checking_conversion) {
+ // Make sure we don't write anything.
+ fd = -1;
+ write_info.bw_fd = fd;
+ } else {
+ // Open the file "wfname" for writing.
+ // We may try to open the file twice: If we can't write to the file
+ // and forceit is true we delete the existing file and try to
+ // create a new one. If this still fails we may have lost the
+ // original file! (this may happen when the user reached his
+ // quotum for number of files).
+ // Appending will fail if the file does not exist and forceit is
+ // false.
+ const int fflags = O_WRONLY | (append
+ ? (forceit ? (O_APPEND | O_CREAT) : O_APPEND)
+ : (O_CREAT | O_TRUNC));
+ const int mode = perm < 0 ? 0666 : (perm & 0777);
+
+ while ((fd = os_open(wfname, fflags, mode)) < 0) {
+ // A forced write will try to create a new file if the old one
+ // is still readonly. This may also happen when the directory
+ // is read-only. In that case the os_remove() will fail.
+ if (err.msg == NULL) {
+#ifdef UNIX
+ FileInfo file_info;
+
+ // Don't delete the file when it's a hard or symbolic link.
+ if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1)
+ || (os_fileinfo_link(fname, &file_info)
+ && !os_fileinfo_id_equal(&file_info, &file_info_old))) {
+ err = set_err(_("E166: Can't open linked file for writing"));
+ } else {
+ err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
+ if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
+ // we write to the file, thus it should be marked
+ // writable after all
+ if (!(perm & 0200)) {
+ made_writable = true;
+ }
+ perm |= 0200;
+ if (file_info_old.stat.st_uid != getuid()
+ || file_info_old.stat.st_gid != getgid()) {
+ perm &= 0777;
+ }
+ if (!append) { // don't remove when appending
+ os_remove(wfname);
+ }
+ continue;
+ }
+ }
+#else
+ err = set_err_arg(_("E212: Can't open file for writing: %s"), fd);
+ if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL && perm >= 0) {
+ if (!append) { // don't remove when appending
+ os_remove(wfname);
+ }
+ continue;
+ }
+#endif
+ }
+
+restore_backup:
+ {
+ // If we failed to open the file, we don't need a backup. Throw it
+ // away. If we moved or removed the original file try to put the
+ // backup in its place.
+ if (backup != NULL && wfname == fname) {
+ if (backup_copy) {
+ // There is a small chance that we removed the original,
+ // try to move the copy in its place.
+ // This may not work if the vim_rename() fails.
+ // In that case we leave the copy around.
+ // If file does not exist, put the copy in its place
+ if (!os_path_exists(fname)) {
+ vim_rename(backup, fname);
+ }
+ // if original file does exist throw away the copy
+ if (os_path_exists(fname)) {
+ os_remove(backup);
+ }
+ } else {
+ // try to put the original file back
+ vim_rename(backup, fname);
+ }
+ }
+
+ // if original file no longer exists give an extra warning
+ if (!newfile && !os_path_exists(fname)) {
+ end = 0;
+ }
+ }
+
+ if (wfname != fname) {
+ xfree(wfname);
+ }
+ goto fail;
+ }
+ write_info.bw_fd = fd;
+ }
+ err = set_err(NULL);
+
+ write_info.bw_buf = buffer;
+ nchars = 0;
+
+ // use "++bin", "++nobin" or 'binary'
+ int write_bin;
+ if (eap != NULL && eap->force_bin != 0) {
+ write_bin = (eap->force_bin == FORCE_BIN);
+ } else {
+ write_bin = buf->b_p_bin;
+ }
+
+ // Skip the BOM when appending and the file already existed, the BOM
+ // only makes sense at the start of the file.
+ if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) {
+ write_info.bw_len = make_bom(buffer, fenc);
+ if (write_info.bw_len > 0) {
+ // don't convert
+ write_info.bw_flags = FIO_NOCONVERT | wb_flags;
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0;
+ } else {
+ nchars += write_info.bw_len;
+ }
+ }
+ }
+ write_info.bw_start_lnum = start;
+
+ write_undo_file = (buf->b_p_udf && overwriting && !append
+ && !filtering && reset_changed && !checking_conversion);
+ if (write_undo_file) {
+ // Prepare for computing the hash value of the text.
+ sha256_start(&sha_ctx);
+ }
+
+ write_info.bw_len = bufsize;
+ write_info.bw_flags = wb_flags;
+ fileformat = get_fileformat_force(buf, eap);
+ char *s = buffer;
+ int len = 0;
+ for (lnum = start; lnum <= end; lnum++) {
+ // The next while loop is done once for each character written.
+ // Keep it fast!
+ char *ptr = ml_get_buf(buf, lnum) - 1;
+ if (write_undo_file) {
+ sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1));
+ }
+ char c;
+ while ((c = *++ptr) != NUL) {
+ if (c == NL) {
+ *s = NUL; // replace newlines with NULs
+ } else if (c == CAR && fileformat == EOL_MAC) {
+ *s = NL; // Mac: replace CRs with NLs
+ } else {
+ *s = c;
+ }
+ s++;
+ if (++len != bufsize) {
+ continue;
+ }
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error: break loop
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ write_info.bw_start_lnum = lnum;
+ }
+ // write failed or last line has no EOL: stop here
+ if (end == 0
+ || (lnum == end
+ && (write_bin || !buf->b_p_fixeol)
+ && ((write_bin && lnum == buf->b_no_eol_lnum)
+ || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
+ lnum++; // written the line, count it
+ no_eol = true;
+ break;
+ }
+ if (fileformat == EOL_UNIX) {
+ *s++ = NL;
+ } else {
+ *s++ = CAR; // EOL_MAC or EOL_DOS: write CR
+ if (fileformat == EOL_DOS) { // write CR-NL
+ if (++len == bufsize) {
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error: break loop
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+ }
+ *s++ = NL;
+ }
+ }
+ if (++len == bufsize) {
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // Write error: break loop.
+ break;
+ }
+ nchars += bufsize;
+ s = buffer;
+ len = 0;
+
+ os_breakcheck();
+ if (got_int) {
+ end = 0; // Interrupted, break loop.
+ break;
+ }
+ }
+ }
+ if (len > 0 && end > 0) {
+ write_info.bw_len = len;
+ if (buf_write_bytes(&write_info) == FAIL) {
+ end = 0; // write error
+ }
+ nchars += len;
+ }
+
+ if (!buf->b_p_fixeol && buf->b_p_eof) {
+ // write trailing CTRL-Z
+ (void)write_eintr(write_info.bw_fd, "\x1a", 1);
+ }
+
+ // Stop when writing done or an error was encountered.
+ if (!checking_conversion || end == 0) {
+ break;
+ }
+
+ // If no error happened until now, writing should be ok, so loop to
+ // really write the buffer.
+ }
+
+ // If we started writing, finish writing. Also when an error was
+ // encountered.
+ if (!checking_conversion) {
+ // On many journalling file systems there is a bug that causes both the
+ // original and the backup file to be lost when halting the system right
+ // after writing the file. That's because only the meta-data is
+ // journalled. Syncing the file slows down the system, but assures it has
+ // been written to disk and we don't lose it.
+ // For a device do try the fsync() but don't complain if it does not work
+ // (could be a pipe).
+ // If the 'fsync' option is false, don't fsync(). Useful for laptops.
+ int error;
+ if (p_fs && (error = os_fsync(fd)) != 0 && !device
+ // fsync not supported on this storage.
+ && error != UV_ENOTSUP) {
+ err = set_err_arg(e_fsync, error);
+ end = 0;
+ }
+
+ if (!backup_copy) {
+#ifdef HAVE_XATTR
+ os_copy_xattr(backup, wfname);
+#endif
+ }
+
+#ifdef UNIX
+ // When creating a new file, set its owner/group to that of the original
+ // file. Get the new device and inode number.
+ if (backup != NULL && !backup_copy) {
+ // don't change the owner when it's already OK, some systems remove
+ // permission or ACL stuff
+ FileInfo file_info;
+ if (!os_fileinfo(wfname, &file_info)
+ || file_info.stat.st_uid != file_info_old.stat.st_uid
+ || file_info.stat.st_gid != file_info_old.stat.st_gid) {
+ os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
+ if (perm >= 0) { // Set permission again, may have changed.
+ (void)os_setperm(wfname, perm);
+ }
+ }
+ buf_set_file_id(buf);
+ } else if (!buf->file_id_valid) {
+ // Set the file_id when creating a new file.
+ buf_set_file_id(buf);
+ }
+#endif
+
+ if ((error = os_close(fd)) != 0) {
+ err = set_err_arg(_("E512: Close failed: %s"), error);
+ end = 0;
+ }
+
+#ifdef UNIX
+ if (made_writable) {
+ perm &= ~0200; // reset 'w' bit for security reasons
+ }
+#endif
+ if (perm >= 0) { // Set perm. of new file same as old file.
+ (void)os_setperm(wfname, perm);
+ }
+ // Probably need to set the ACL before changing the user (can't set the
+ // ACL on a file the user doesn't own).
+ if (!backup_copy) {
+ os_set_acl(wfname, acl);
+ }
+
+ if (wfname != fname) {
+ // The file was written to a temp file, now it needs to be converted
+ // with 'charconvert' to (overwrite) the output file.
+ if (end != 0) {
+ if (eval_charconvert("utf-8", fenc, wfname, fname) == FAIL) {
+ write_info.bw_conv_error = true;
+ end = 0;
+ }
+ }
+ os_remove(wfname);
+ xfree(wfname);
+ }
+ }
+
+ if (end == 0) {
+ // Error encountered.
+ if (err.msg == NULL) {
+ if (write_info.bw_conv_error) {
+ if (write_info.bw_conv_error_lnum == 0) {
+ err = set_err(_(e_write_error_conversion_failed_make_fenc_empty_to_override));
+ } else {
+ err = set_err(xmalloc(300));
+ err.alloc = true;
+ vim_snprintf(err.msg, 300, // NOLINT(runtime/printf)
+ _(e_write_error_conversion_failed_in_line_nr_make_fenc_empty_to_override),
+ write_info.bw_conv_error_lnum);
+ }
+ } else if (got_int) {
+ err = set_err(_(e_interr));
+ } else {
+ err = set_err(_(e_write_error_file_system_full));
+ }
+ }
+
+ // If we have a backup file, try to put it in place of the new file,
+ // because the new file is probably corrupt. This avoids losing the
+ // original file when trying to make a backup when writing the file a
+ // second time.
+ // When "backup_copy" is set we need to copy the backup over the new
+ // file. Otherwise rename the backup file.
+ // If this is OK, don't give the extra warning message.
+ if (backup != NULL) {
+ if (backup_copy) {
+ // This may take a while, if we were interrupted let the user
+ // know we got the message.
+ if (got_int) {
+ msg(_(e_interr), 0);
+ ui_flush();
+ }
+
+ // copy the file.
+ if (os_copy(backup, fname, UV_FS_COPYFILE_FICLONE)
+ == 0) {
+ end = 1; // success
+ }
+ } else {
+ if (vim_rename(backup, fname) == 0) {
+ end = 1;
+ }
+ }
+ }
+ goto fail;
+ }
+
+ lnum -= start; // compute number of written lines
+ no_wait_return--; // may wait for return now
+
+#if !defined(UNIX)
+ fname = sfname; // use shortname now, for the messages
+#endif
+ if (!filtering) {
+ add_quoted_fname(IObuff, IOSIZE, buf, fname);
+ bool insert_space = false;
+ if (write_info.bw_conv_error) {
+ xstrlcat(IObuff, _(" CONVERSION ERROR"), IOSIZE);
+ insert_space = true;
+ if (write_info.bw_conv_error_lnum != 0) {
+ vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"),
+ (int64_t)write_info.bw_conv_error_lnum);
+ }
+ } else if (notconverted) {
+ xstrlcat(IObuff, _("[NOT converted]"), IOSIZE);
+ insert_space = true;
+ } else if (converted) {
+ xstrlcat(IObuff, _("[converted]"), IOSIZE);
+ insert_space = true;
+ }
+ if (device) {
+ xstrlcat(IObuff, _("[Device]"), IOSIZE);
+ insert_space = true;
+ } else if (newfile) {
+ xstrlcat(IObuff, _("[New]"), IOSIZE);
+ insert_space = true;
+ }
+ if (no_eol) {
+ xstrlcat(IObuff, _("[noeol]"), IOSIZE);
+ insert_space = true;
+ }
+ // may add [unix/dos/mac]
+ if (msg_add_fileformat(fileformat)) {
+ insert_space = true;
+ }
+ msg_add_lines(insert_space, lnum, nchars); // add line/char count
+ if (!shortmess(SHM_WRITE)) {
+ if (append) {
+ xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"), IOSIZE);
+ } else {
+ xstrlcat(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"), IOSIZE);
+ }
+ }
+
+ set_keep_msg(msg_trunc(IObuff, false, 0), 0);
+ }
+
+ // When written everything correctly: reset 'modified'. Unless not
+ // writing to the original file and '+' is not in 'cpoptions'.
+ if (reset_changed && whole && !append
+ && !write_info.bw_conv_error
+ && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
+ unchanged(buf, true, false);
+ const varnumber_T changedtick = buf_get_changedtick(buf);
+ if (buf->b_last_changedtick + 1 == changedtick) {
+ // b:changedtick may be incremented in unchanged() but that should not
+ // trigger a TextChanged event.
+ buf->b_last_changedtick = changedtick;
+ }
+ u_unchanged(buf);
+ u_update_save_nr(buf);
+ }
+
+ // If written to the current file, update the timestamp of the swap file
+ // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime.
+ if (overwriting) {
+ ml_timestamp(buf);
+ if (append) {
+ buf->b_flags &= ~BF_NEW;
+ } else {
+ buf->b_flags &= ~BF_WRITE_MASK;
+ }
+ }
+
+ // If we kept a backup until now, and we are in patch mode, then we make
+ // the backup file our 'original' file.
+ if (*p_pm && dobackup) {
+ char *const org = modname(fname, p_pm, false);
+
+ if (backup != NULL) {
+ // If the original file does not exist yet
+ // the current backup file becomes the original file
+ if (org == NULL) {
+ emsg(_("E205: Patchmode: can't save original file"));
+ } else if (!os_path_exists(org)) {
+ vim_rename(backup, org);
+ XFREE_CLEAR(backup); // don't delete the file
+#ifdef UNIX
+ os_file_settime(org,
+ (double)file_info_old.stat.st_atim.tv_sec,
+ (double)file_info_old.stat.st_mtim.tv_sec);
+#endif
+ }
+ } else {
+ // If there is no backup file, remember that a (new) file was
+ // created.
+ int empty_fd;
+
+ if (org == NULL
+ || (empty_fd = os_open(org,
+ O_CREAT | O_EXCL | O_NOFOLLOW,
+ perm < 0 ? 0666 : (perm & 0777))) < 0) {
+ emsg(_(e_patchmode_cant_touch_empty_original_file));
+ } else {
+ close(empty_fd);
+ }
+ }
+ if (org != NULL) {
+ os_setperm(org, os_getperm(fname) & 0777);
+ xfree(org);
+ }
+ }
+
+ // Remove the backup unless 'backup' option is set
+ if (!p_bk && backup != NULL
+ && !write_info.bw_conv_error
+ && os_remove(backup) != 0) {
+ emsg(_("E207: Can't delete backup file"));
+ }
+
+ goto nofail;
+
+ // Finish up. We get here either after failure or success.
+fail:
+ no_wait_return--; // may wait for return now
+nofail:
+
+ // Done saving, we accept changed buffer warnings again
+ buf->b_saving = false;
+
+ xfree(backup);
+ if (buffer != smallbuf) {
+ xfree(buffer);
+ }
+ xfree(fenc_tofree);
+ xfree(write_info.bw_conv_buf);
+ if (write_info.bw_iconv_fd != (iconv_t)-1) {
+ iconv_close(write_info.bw_iconv_fd);
+ write_info.bw_iconv_fd = (iconv_t)-1;
+ }
+ os_free_acl(acl);
+
+ if (err.msg != NULL) {
+ // - 100 to save some space for further error message
+#ifndef UNIX
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, sfname);
+#else
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, fname);
+#endif
+ emit_err(&err);
+
+ retval = FAIL;
+ if (end == 0) {
+ const int attr = HL_ATTR(HLF_E); // Set highlight for error messages.
+ msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"),
+ attr | MSG_HIST);
+ msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
+ attr | MSG_HIST);
+
+ // Update the timestamp to avoid an "overwrite changed file"
+ // prompt when writing again.
+ if (os_fileinfo(fname, &file_info_old)) {
+ buf_store_file_info(buf, &file_info_old);
+ buf->b_mtime_read = buf->b_mtime;
+ buf->b_mtime_read_ns = buf->b_mtime_ns;
+ }
+ }
+ }
+ msg_scroll = msg_save;
+
+ // When writing the whole file and 'undofile' is set, also write the undo
+ // file.
+ if (retval == OK && write_undo_file) {
+ uint8_t hash[UNDO_HASH_SIZE];
+
+ sha256_finish(&sha_ctx, hash);
+ u_write_undo(NULL, false, buf, hash);
+ }
+
+ if (!should_abort(retval)) {
+ buf_write_do_post_autocmds(buf, fname, eap, append, filtering, reset_changed, whole);
+ if (aborting()) { // autocmds may abort script processing
+ retval = false;
+ }
+ }
+
+ got_int |= prev_got_int;
+
+ return retval;
+}
diff --git a/src/nvim/bufwrite.h b/src/nvim/bufwrite.h
new file mode 100644
index 0000000000..9ed6216847
--- /dev/null
+++ b/src/nvim/bufwrite.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "bufwrite.h.generated.h"
+#endif
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 06696610b0..81a55b92ee 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// change.c: functions related to changing text
#include <assert.h>
@@ -8,8 +5,8 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -24,14 +21,14 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -39,17 +36,18 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "change.c.generated.h"
@@ -91,7 +89,7 @@ void change_warning(buf_T *buf, int col)
(void)msg_end();
if (msg_silent == 0 && !silent_mode && ui_active()) {
ui_flush();
- os_delay(1002L, true); // give the user time to think about it
+ os_delay(1002, true); // give the user time to think about it
}
buf->b_did_warn = true;
redraw_cmdline = false; // don't redraw and erase the message
@@ -101,28 +99,28 @@ void change_warning(buf_T *buf, int col)
}
}
-/// Call this function when something in the current buffer is changed.
+/// Call this function when something in a buffer is changed.
///
/// Most often called through changed_bytes() and changed_lines(), which also
/// mark the area of the display to be redrawn.
///
/// Careful: may trigger autocommands that reload the buffer.
-void changed(void)
+void changed(buf_T *buf)
{
- if (!curbuf->b_changed) {
+ if (!buf->b_changed) {
int save_msg_scroll = msg_scroll;
// Give a warning about changing a read-only file. This may also
// check-out the file, thus change "curbuf"!
- change_warning(curbuf, 0);
+ change_warning(buf, 0);
// Create a swap file if that is wanted.
// Don't do this for "nofile" and "nowrite" buffer types.
- if (curbuf->b_may_swap && !bt_dontwrite(curbuf)) {
+ if (buf->b_may_swap && !bt_dontwrite(buf)) {
bool save_need_wait_return = need_wait_return;
need_wait_return = false;
- ml_open_file(curbuf);
+ ml_open_file(buf);
// The ml_open_file() can cause an ATTENTION message.
// Wait two seconds, to make sure the user reads this unexpected
@@ -130,16 +128,16 @@ void changed(void)
// and don't let the emsg() set msg_scroll.
if (need_wait_return && emsg_silent == 0 && !in_assert_fails) {
ui_flush();
- os_delay(2002L, true);
+ os_delay(2002, true);
wait_return(true);
msg_scroll = save_msg_scroll;
} else {
need_wait_return = save_need_wait_return;
}
}
- changed_internal();
+ changed_internal(buf);
}
- buf_inc_changedtick(curbuf);
+ buf_inc_changedtick(buf);
// If a pattern is highlighted, the position may now be invalid.
highlight_match = false;
@@ -147,12 +145,12 @@ void changed(void)
/// Internal part of changed(), no user interaction.
/// Also used for recovery.
-void changed_internal(void)
+void changed_internal(buf_T *buf)
{
- curbuf->b_changed = true;
- curbuf->b_changed_invalid = true;
- ml_setflags(curbuf);
- redraw_buf_status_later(curbuf);
+ buf->b_changed = true;
+ buf->b_changed_invalid = true;
+ ml_setflags(buf);
+ redraw_buf_status_later(buf);
redraw_tabline = true;
need_maketitle = true; // set window title later
}
@@ -160,13 +158,15 @@ void changed_internal(void)
/// Common code for when a change was made.
/// See changed_lines() for the arguments.
/// Careful: may trigger autocommands that reload the buffer.
-static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra)
+static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra)
{
// mark the buffer as modified
- changed();
+ changed(buf);
- if (curwin->w_p_diff && diff_internal()) {
- curtab->tp_diff_update = true;
+ FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
+ if (win->w_buffer == buf && win->w_p_diff && diff_internal()) {
+ curtab->tp_diff_update = true;
+ }
}
// set the '. mark
@@ -174,22 +174,25 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
fmarkv_T view = INIT_FMARKV;
// Set the markview only if lnum is visible, as changes might be done
// outside of the current window view.
- if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
- view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+
+ if (curwin->w_buffer == buf) {
+ if (lnum >= curwin->w_topline && lnum <= curwin->w_botline) {
+ view = mark_view_make(curwin->w_topline, curwin->w_cursor);
+ }
}
- RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), curbuf->handle, view);
+ RESET_FMARK(&buf->b_last_change, ((pos_T) { lnum, col, 0 }), buf->handle, view);
// Create a new entry if a new undo-able change was started or we
// don't have an entry yet.
- if (curbuf->b_new_change || curbuf->b_changelistlen == 0) {
+ if (buf->b_new_change || buf->b_changelistlen == 0) {
int add;
- if (curbuf->b_changelistlen == 0) {
+ if (buf->b_changelistlen == 0) {
add = true;
} else {
// Don't create a new entry when the line number is the same
// as the last one and the column is not too far away. Avoids
// creating many entries for typing "xxxxx".
- pos_T *p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark;
+ pos_T *p = &buf->b_changelist[buf->b_changelistlen - 1].mark;
if (p->lnum != lnum) {
add = true;
} else {
@@ -204,17 +207,17 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
// This is the first of a new sequence of undo-able changes
// and it's at some distance of the last change. Use a new
// position in the changelist.
- curbuf->b_new_change = false;
+ buf->b_new_change = false;
- if (curbuf->b_changelistlen == JUMPLISTSIZE) {
+ if (buf->b_changelistlen == JUMPLISTSIZE) {
// changelist is full: remove oldest entry
- curbuf->b_changelistlen = JUMPLISTSIZE - 1;
- memmove(curbuf->b_changelist, curbuf->b_changelist + 1,
- sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1));
+ buf->b_changelistlen = JUMPLISTSIZE - 1;
+ memmove(buf->b_changelist, buf->b_changelist + 1,
+ sizeof(buf->b_changelist[0]) * (JUMPLISTSIZE - 1));
FOR_ALL_TAB_WINDOWS(tp, wp) {
// Correct position in changelist for other windows on
// this buffer.
- if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) {
+ if (wp->w_buffer == buf && wp->w_changelistidx > 0) {
wp->w_changelistidx--;
}
}
@@ -222,37 +225,53 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
FOR_ALL_TAB_WINDOWS(tp, wp) {
// For other windows, if the position in the changelist is
// at the end it stays at the end.
- if (wp->w_buffer == curbuf
- && wp->w_changelistidx == curbuf->b_changelistlen) {
+ if (wp->w_buffer == buf
+ && wp->w_changelistidx == buf->b_changelistlen) {
wp->w_changelistidx++;
}
}
- curbuf->b_changelistlen++;
+ buf->b_changelistlen++;
}
}
- curbuf->b_changelist[curbuf->b_changelistlen - 1] =
- curbuf->b_last_change;
+ buf->b_changelist[buf->b_changelistlen - 1] =
+ buf->b_last_change;
// The current window is always after the last change, so that "g,"
// takes you back to it.
- curwin->w_changelistidx = curbuf->b_changelistlen;
+ if (curwin->w_buffer == buf) {
+ curwin->w_changelistidx = buf->b_changelistlen;
+ }
}
- if (VIsual_active) {
+ if (curwin->w_buffer == buf && VIsual_active) {
check_visual_pos();
}
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer == curbuf) {
+ if (wp->w_buffer == buf) {
// Mark this window to be redrawn later.
if (wp->w_redr_type < UPD_VALID) {
wp->w_redr_type = UPD_VALID;
}
+ linenr_T last = lnume + xtra - 1; // last line after the change
+
+ // Reset "w_skipcol" if the topline length has become smaller to
+ // such a degree that nothing will be visible anymore, accounting
+ // for 'smoothscroll' <<< or 'listchars' "precedes" marker.
+ if (wp->w_skipcol > 0
+ && (last < wp->w_topline
+ || (wp->w_topline >= lnum
+ && wp->w_topline < lnume
+ && win_linetabsize(wp, wp->w_topline, ml_get(wp->w_topline), MAXCOL)
+ <= (wp->w_skipcol
+ + sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp)))))) {
+ wp->w_skipcol = 0;
+ }
+
// Check if a change in the buffer has invalidated the cached
// values for the cursor.
// Update the folds for this window. Can't postpone this, because
// a following operator might work on the whole fold: ">>dd".
- linenr_T last = lnume + xtra - 1; // last line after the change
foldUpdate(wp, lnum, last);
// The change may cause lines above or below the change to become
@@ -281,7 +300,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
if (wp->w_cursor.lnum > lnum) {
changed_line_abv_curs_win(wp);
} else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) {
- changed_cline_bef_curs_win(wp);
+ changed_cline_bef_curs(wp);
}
if (wp->w_botline >= lnum) {
// Assume that botline doesn't change (inserted lines make
@@ -296,7 +315,14 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid) {
if (wp->w_lines[i].wl_lnum >= lnum) {
- if (wp->w_lines[i].wl_lnum < lnume) {
+ // Do not change wl_lnum at index zero, it is used to
+ // compare with w_topline. Invalidate it instead.
+ // If the buffer has virt_lines, invalidate the line
+ // after the changed lines as the virt_lines for a
+ // changed line may become invalid.
+ if (i == 0 || wp->w_lines[i].wl_lnum < lnume
+ || (wp->w_lines[i].wl_lnum == lnume
+ && wp->w_buffer->b_virt_line_blocks > 0)) {
// line included in change
wp->w_lines[i].wl_valid = false;
} else if (xtra != 0) {
@@ -345,9 +371,10 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T
}
// when the cursor line is changed always trigger CursorMoved
- if (lnum <= curwin->w_cursor.lnum
+ if (last_cursormoved_win == curwin && curwin->w_buffer == buf
+ && lnum <= curwin->w_cursor.lnum
&& lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) {
- curwin->w_last_cursormoved.lnum = 0;
+ last_cursormoved.lnum = 0;
}
}
@@ -377,7 +404,16 @@ static void changedOneline(buf_T *buf, linenr_T lnum)
void changed_bytes(linenr_T lnum, colnr_T col)
{
changedOneline(curbuf, lnum);
- changed_common(lnum, col, lnum + 1, 0);
+ changed_common(curbuf, lnum, col, lnum + 1, 0);
+ // When text has been changed at the end of the line, possibly the start of
+ // the next line may have SpellCap that should be removed or it needs to be
+ // displayed. Schedule the next line for redrawing just in case.
+ // Don't do this when displaying '$' at the end of changed text.
+ if (spell_check_window(curwin)
+ && lnum < curbuf->b_ml.ml_line_count
+ && vim_strchr(p_cpo, CPO_DOLLAR) == NULL) {
+ redrawWinline(curwin, lnum + 1);
+ }
// notify any channels that are watching
buf_updates_send_changes(curbuf, lnum, 1, 1);
@@ -398,13 +434,13 @@ void changed_bytes(linenr_T lnum, colnr_T col)
/// insert/delete bytes at column
///
/// Like changed_bytes() but also adjust extmark for "new" bytes.
-void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
+void inserted_bytes(linenr_T lnum, colnr_T start_col, int old_col, int new_col)
{
if (curbuf_splice_pending == 0) {
- extmark_splice_cols(curbuf, (int)lnum - 1, col, old, new, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum - 1, start_col, old_col, new_col, kExtmarkUndo);
}
- changed_bytes(lnum, col);
+ changed_bytes(lnum, start_col);
}
/// Appended "count" lines below line "lnum" in the current buffer.
@@ -412,14 +448,14 @@ void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
void appended_lines(linenr_T lnum, linenr_T count)
{
- changed_lines(lnum + 1, 0, lnum + 1, count, true);
+ changed_lines(curbuf, lnum + 1, 0, lnum + 1, count, true);
}
/// Like appended_lines(), but adjust marks first.
-void appended_lines_mark(linenr_T lnum, long count)
+void appended_lines_mark(linenr_T lnum, int count)
{
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0L, kExtmarkUndo);
- changed_lines(lnum + 1, 0, lnum + 1, (linenr_T)count, true);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, (linenr_T)count, 0, kExtmarkUndo);
+ changed_lines(curbuf, lnum + 1, 0, lnum + 1, (linenr_T)count, true);
}
/// Deleted "count" lines at line "lnum" in the current buffer.
@@ -427,13 +463,13 @@ void appended_lines_mark(linenr_T lnum, long count)
/// Takes care of marking the buffer to be redrawn and sets the changed flag.
void deleted_lines(linenr_T lnum, linenr_T count)
{
- changed_lines(lnum, 0, lnum + count, -count, true);
+ changed_lines(curbuf, lnum, 0, lnum + count, -count, true);
}
/// Like deleted_lines(), but adjust marks first.
/// Make sure the cursor is on a valid line before calling, a GUI callback may
/// be triggered to display the cursor.
-void deleted_lines_mark(linenr_T lnum, long count)
+void deleted_lines_mark(linenr_T lnum, int count)
{
bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY;
@@ -441,16 +477,17 @@ void deleted_lines_mark(linenr_T lnum, long count)
// if we deleted the entire buffer, we need to implicitly add a new empty line
extmark_adjust(curbuf, lnum, (linenr_T)(lnum + count - 1), MAXLNUM,
-(linenr_T)count + (made_empty ? 1 : 0), kExtmarkUndo);
- changed_lines(lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
+ changed_lines(curbuf, lnum, 0, lnum + (linenr_T)count, (linenr_T)(-count), true);
}
/// Marks the area to be redrawn after a change.
+/// Consider also calling changed_line_display_buf().
///
/// @param buf the buffer where lines were changed
/// @param lnum first line with change
/// @param lnume line below last changed line
/// @param xtra number of extra lines (negative when deleting)
-void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
+void buf_redraw_changed_lines_later(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
{
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
@@ -477,7 +514,7 @@ void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
}
}
-/// Changed lines for the current buffer.
+/// Changed lines for a buffer.
/// Must be called AFTER the change and after mark_adjust().
/// - mark the buffer changed by calling changed()
/// - mark the windows on this buffer to be redisplayed
@@ -495,11 +532,12 @@ void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_T xtra)
/// @param do_buf_event some callers like undo/redo call changed_lines() and
/// then increment changedtick *again*. This flag allows these callers to send
/// the nvim_buf_lines_event events after they're done modifying changedtick.
-void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bool do_buf_event)
+void changed_lines(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra,
+ bool do_buf_event)
{
- changed_lines_buf(curbuf, lnum, lnume, xtra);
+ buf_redraw_changed_lines_later(buf, lnum, lnume, xtra);
- if (xtra == 0 && curwin->w_p_diff && !diff_internal()) {
+ if (xtra == 0 && curwin->w_p_diff && curwin->w_buffer == buf && !diff_internal()) {
// When the number of lines doesn't change then mark_adjust() isn't
// called and other diff buffers still need to be marked for
// displaying.
@@ -510,19 +548,19 @@ void changed_lines(linenr_T lnum, colnr_T col, linenr_T lnume, linenr_T xtra, bo
redraw_later(wp, UPD_VALID);
wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
- changed_lines_buf(wp->w_buffer, wlnum,
- lnume - lnum + wlnum, 0L);
+ buf_redraw_changed_lines_later(wp->w_buffer, wlnum,
+ lnume - lnum + wlnum, 0);
}
}
}
}
- changed_common(lnum, col, lnume, xtra);
+ changed_common(buf, lnum, col, lnume, xtra);
if (do_buf_event) {
int64_t num_added = (int64_t)(lnume + xtra - lnum);
int64_t num_removed = lnume - lnum;
- buf_updates_send_changes(curbuf, lnum, num_added, num_removed);
+ buf_updates_send_changes(buf, lnum, num_added, num_removed);
}
}
@@ -582,7 +620,7 @@ bool file_ff_differs(buf_T *buf, bool ignore_empty)
if (ignore_empty
&& (buf->b_flags & BF_NEW)
&& buf->b_ml.ml_line_count == 1
- && *ml_get_buf(buf, (linenr_T)1, false) == NUL) {
+ && *ml_get_buf(buf, 1) == NUL) {
return false;
}
if (buf->b_start_ffc != *buf->b_p_ff) {
@@ -627,7 +665,7 @@ void ins_bytes_len(char *p, size_t len)
/// convert bytes to a character.
void ins_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
size_t n = (size_t)utf_char2bytes(c, buf);
// When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte.
@@ -709,8 +747,7 @@ void ins_char_bytes(char *buf, size_t charlen)
// Copy bytes after the changed character(s).
char *p = newp + col;
if (linelen > col + oldlen) {
- memmove(p + newlen, oldp + col + oldlen,
- (size_t)(linelen - col - oldlen));
+ memmove(p + newlen, oldp + col + oldlen, linelen - col - oldlen);
}
// Insert or overwrite the new character.
@@ -783,15 +820,15 @@ int del_char(bool fixpos)
if (*get_cursor_pos_ptr() == NUL) {
return FAIL;
}
- return del_chars(1L, fixpos);
+ return del_chars(1, fixpos);
}
/// Like del_bytes(), but delete characters instead of bytes.
-int del_chars(long count, int fixpos)
+int del_chars(int count, int fixpos)
{
int bytes = 0;
char *p = get_cursor_pos_ptr();
- for (long i = 0; i < count && *p != NUL; i++) {
+ for (int i = 0; i < count && *p != NUL; i++) {
int l = utfc_ptr2len(p);
bytes += l;
p += l;
@@ -832,12 +869,9 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// If 'delcombine' is set and deleting (less than) one character, only
// delete the last combining character.
- if (p_deco && use_delcombine
- && utfc_ptr2len(oldp + col) >= count) {
- int cc[MAX_MCO];
-
- (void)utfc_ptr2char(oldp + col, cc);
- if (cc[0] != NUL) {
+ if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) {
+ char *p0 = oldp + col;
+ if (utf_composinglike(p0, p0 + utf_ptr2len(p0))) {
// Find the last composing char, there can be several.
int n = col;
do {
@@ -949,7 +983,7 @@ int copy_indent(int size, char *src)
// Add tabs required for indent.
if (!curbuf->b_p_et) {
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_col,
curbuf->b_p_ts,
curbuf->b_p_vts_array);
@@ -1093,7 +1127,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
*p_extra = NUL;
}
- u_clearline(); // cannot do "U" command when adding lines
+ u_clearline(curbuf); // cannot do "U" command when adding lines
did_si = false;
ai_col = 0;
@@ -1160,12 +1194,16 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
if (p[0] == '/' && p[-1] == '*') {
// End of C comment, indent should line up
// with the line containing the start of
- // the comment
+ // the comment.
curwin->w_cursor.col = (colnr_T)(p - ptr);
if ((pos = findmatch(NULL, NUL)) != NULL) {
curwin->w_cursor.lnum = pos->lnum;
newindent = get_indent();
+ break;
}
+ // this may make "ptr" invalid, get it again
+ ptr = ml_get(curwin->w_cursor.lnum);
+ p = ptr + curwin->w_cursor.col;
}
}
}
@@ -1305,7 +1343,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
// find start of middle part
- (void)copy_option_part(&p, (char *)lead_middle, COM_MAX_LEN, ",");
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
require_blank = false;
}
@@ -1316,7 +1354,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
p++;
}
- (void)copy_option_part(&p, (char *)lead_middle, COM_MAX_LEN, ",");
+ (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ",");
while (*p && p[-1] != ':') { // find end of end flags
// Check whether we allow automatic ending of comments
@@ -1325,7 +1363,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
}
p++;
}
- size_t n = copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
if (end_comment_pending == -1) { // we can set it now
end_comment_pending = (unsigned char)lead_end[n - 1];
@@ -1346,7 +1384,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// Doing "o" on a start of comment inserts the middle leader.
if (lead_len > 0) {
if (current_flag == COM_START) {
- lead_repl = (char *)lead_middle;
+ lead_repl = lead_middle;
lead_repl_len = (int)strlen(lead_middle);
}
@@ -1655,14 +1693,13 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// concatenate leader and p_extra, if there is a leader
if (lead_len > 0) {
if (flags & OPENLINE_COM_LIST && second_line_indent > 0) {
- int i;
int padding = second_line_indent
- (newindent + (int)strlen(leader));
// Here whitespace is inserted after the comment char.
// Below, set_indent(newindent, SIN_INSERT) will insert the
// whitespace needed before the comment char.
- for (i = 0; i < padding; i++) {
+ for (int i = 0; i < padding; i++) {
STRCAT(leader, " ");
less_cols--;
newcol++;
@@ -1682,12 +1719,12 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
curwin->w_cursor.lnum--;
}
if ((State & VREPLACE_FLAG) == 0 || old_cursor.lnum >= orig_line_count) {
- if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) {
+ if (ml_append(curwin->w_cursor.lnum, p_extra, 0, false) == FAIL) {
goto theend;
}
// Postpone calling changed_lines(), because it would mess up folding
// with markers.
- mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
+ mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
did_append = true;
} else {
// In MODE_VREPLACE state we are starting to replace the next line.
@@ -1777,15 +1814,15 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
saved_line = NULL;
if (did_append) {
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- curwin->w_cursor.lnum + 1, 1L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
+ curwin->w_cursor.lnum + 1, 1, true);
did_append = false;
// Move marks after the line break to the new line.
if (flags & OPENLINE_MARKFIX) {
mark_col_adjust(curwin->w_cursor.lnum,
curwin->w_cursor.col + less_cols_off,
- 1L, (long)-less_cols, 0);
+ 1, -less_cols, 0);
}
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
@@ -1803,7 +1840,7 @@ int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
curwin->w_cursor.lnum = old_cursor.lnum + 1;
}
if (did_append) {
- changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1, true);
// bail out and just get the final length of the line we just manipulated
bcount_t extra = (bcount_t)strlen(ml_get(curwin->w_cursor.lnum));
extmark_splice(curbuf, (int)curwin->w_cursor.lnum - 1, 0,
@@ -1897,9 +1934,9 @@ void truncate_line(int fixpos)
/// Delete "nlines" lines at the cursor.
/// Saves the lines for undo first if "undo" is true.
-void del_lines(long nlines, bool undo)
+void del_lines(linenr_T nlines, bool undo)
{
- long n;
+ int n;
linenr_T first = curwin->w_cursor.lnum;
if (nlines <= 0) {
@@ -1928,7 +1965,7 @@ void del_lines(long nlines, bool undo)
// Correct the cursor position before calling deleted_lines_mark(), it may
// trigger a callback to display the cursor.
curwin->w_cursor.col = 0;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
// adjust marks, mark the buffer as changed and prepare for displaying
deleted_lines_mark(first, n);
@@ -1946,9 +1983,7 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space)
int got_com = false;
char part_buf[COM_MAX_LEN]; // buffer for one option part
char *string; // pointer to comment string
- char *list;
int middle_match_len = 0;
- char *prev_list;
char *saved_flags = NULL;
int result = 0;
@@ -1961,13 +1996,13 @@ int get_leader_len(char *line, char **flags, bool backward, bool include_space)
while (line[i] != NUL) {
// scan through the 'comments' option for a match
int found_one = false;
- for (list = curbuf->b_p_com; *list;) {
+ for (char *list = curbuf->b_p_com; *list;) {
// Get one option part into part_buf[]. Advance "list" to next
// one. Put "string" at start of string.
if (!got_com && flags != NULL) {
*flags = list; // remember where flags started
}
- prev_list = list;
+ char *prev_list = list;
(void)copy_option_part(&list, part_buf, COM_MAX_LEN, ",");
string = vim_strchr(part_buf, ':');
if (string == NULL) { // missing ':', ignore this part
@@ -2163,7 +2198,6 @@ int get_last_leader_offset(char *line, char **flags)
if (found_one) {
char part_buf2[COM_MAX_LEN]; // buffer for one option part
- int len1, len2, off;
result = i;
// If this comment nests, continue searching.
@@ -2181,7 +2215,7 @@ int get_last_leader_offset(char *line, char **flags)
while (ascii_iswhite(*com_leader)) {
com_leader++;
}
- len1 = (int)strlen(com_leader);
+ int len1 = (int)strlen(com_leader);
for (list = curbuf->b_p_com; *list;) {
char *flags_save = list;
@@ -2195,14 +2229,14 @@ int get_last_leader_offset(char *line, char **flags)
while (ascii_iswhite(*string)) {
string++;
}
- len2 = (int)strlen(string);
+ int len2 = (int)strlen(string);
if (len2 == 0) {
continue;
}
// Now we have to verify whether string ends with a substring
// beginning the com_leader.
- for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
+ for (int off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
off--;
if (!strncmp(string + off, com_leader, (size_t)(len2 - off))) {
if (i - off < lower_check_bound) {
diff --git a/src/nvim/change.h b/src/nvim/change.h
index d1d016c630..06155b6da6 100644
--- a/src/nvim/change.h
+++ b/src/nvim/change.h
@@ -1,19 +1,18 @@
-#ifndef NVIM_CHANGE_H
-#define NVIM_CHANGE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/pos.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// flags for open_line()
-#define OPENLINE_DELSPACES 0x01 // delete spaces after cursor
-#define OPENLINE_DO_COM 0x02 // format comments
-#define OPENLINE_KEEPTRAIL 0x04 // keep trailing spaces
-#define OPENLINE_MARKFIX 0x08 // fix mark positions
-#define OPENLINE_COM_LIST 0x10 // format comments with list/2nd line indent
-#define OPENLINE_FORMAT 0x20 // formatting long comment
+/// flags for open_line()
+enum {
+ OPENLINE_DELSPACES = 0x01, ///< delete spaces after cursor
+ OPENLINE_DO_COM = 0x02, ///< format comments
+ OPENLINE_KEEPTRAIL = 0x04, ///< keep trailing spaces
+ OPENLINE_MARKFIX = 0x08, ///< fix mark positions
+ OPENLINE_COM_LIST = 0x10, ///< format comments with list/2nd line indent
+ OPENLINE_FORMAT = 0x20, ///< formatting long comment
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "change.h.generated.h"
#endif
-
-#endif // NVIM_CHANGE_H
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 65bb87bc2c..e8fe80a3b6 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -1,13 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
-#include "lauxlib.h"
+#include "klib/kvec.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
@@ -17,10 +15,10 @@
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
#include "nvim/event/socket.h"
#include "nvim/event/wstream.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
@@ -32,12 +30,14 @@
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
+#include "nvim/path.h"
#include "nvim/rbuffer.h"
+
#ifdef MSWIN
+# include "nvim/os/fs.h"
# include "nvim/os/os_win_console.h"
# include "nvim/os/pty_conpty_win.h"
#endif
-#include "nvim/path.h"
static bool did_stdio = false;
@@ -79,7 +79,7 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
// allow double close, even though we can't say what parts was valid.
return true;
}
- *error = (const char *)e_invchan;
+ *error = e_invchan;
return false;
}
@@ -89,19 +89,19 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
if (chan->is_rpc) {
rpc_close(chan);
} else if (part == kChannelPartRpc) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
} else if ((part == kChannelPartStdin || part == kChannelPartStdout)
&& chan->is_rpc) {
- *error = (const char *)e_invstreamrpc;
+ *error = e_invstreamrpc;
return false;
}
switch (chan->streamtype) {
case kChannelStreamSocket:
if (!close_main) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
stream_may_close(&chan->stream.socket);
@@ -132,14 +132,14 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
stream_may_close(&chan->stream.stdio.out);
}
if (part == kChannelPartStderr) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
break;
case kChannelStreamStderr:
if (part != kChannelPartAll && part != kChannelPartStderr) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
if (!chan->stream.err.closed) {
@@ -154,7 +154,7 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
case kChannelStreamInternal:
if (!close_main) {
- *error = (const char *)e_invstream;
+ *error = e_invstream;
return false;
}
if (chan->term) {
@@ -166,9 +166,6 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
channel_decref(chan);
}
break;
-
- default:
- abort();
}
return true;
@@ -207,7 +204,7 @@ Channel *channel_alloc(ChannelStreamType type)
void channel_create_event(Channel *chan, const char *ext_source)
{
-#if MIN_LOG_LEVEL <= LOGLVL_INF
+#ifdef NVIM_LOG_DEBUG
const char *source;
if (ext_source) {
@@ -216,7 +213,7 @@ void channel_create_event(Channel *chan, const char *ext_source)
source = ext_source;
} else {
eval_fmt_source_name_line(IObuff, sizeof(IObuff));
- source = (const char *)IObuff;
+ source = IObuff;
}
assert(chan->id <= VARNUMBER_MAX);
@@ -225,6 +222,7 @@ void channel_create_event(Channel *chan, const char *ext_source)
// TODO(bfredl): do the conversion in one step. Also would be nice
// to pretty print top level dict in defined order
(void)object_to_vim(DICTIONARY_OBJ(info), &tv, NULL);
+ assert(tv.v_type == VAR_DICT);
char *str = encode_tv2json(&tv, NULL);
ILOG("new channel %" PRIu64 " (%s) : %s", chan->id, source, str);
xfree(str);
@@ -277,7 +275,7 @@ static void free_channel_event(void **argv)
callback_reader_free(&chan->on_stderr);
callback_free(&chan->on_exit);
- pmap_del(uint64_t)(&channels, chan->id);
+ pmap_del(uint64_t)(&channels, chan->id, NULL);
multiqueue_free(chan->events);
xfree(chan);
}
@@ -287,7 +285,7 @@ static void channel_destroy_early(Channel *chan)
if ((chan->id != --next_chan_id)) {
abort();
}
- pmap_del(uint64_t)(&channels, chan->id);
+ pmap_del(uint64_t)(&channels, chan->id, NULL);
chan->id = 0;
if ((--chan->refcount != 0)) {
@@ -307,6 +305,7 @@ static void close_cb(Stream *stream, void *data)
///
/// @param[in] argv Arguments vector specifying the command to run,
/// NULL-terminated
+/// @param[in] exepath The path to the executable. If NULL, use `argv[0]`.
/// @param[in] on_stdout Callback to read the job's stdout
/// @param[in] on_stderr Callback to read the job's stderr
/// @param[in] on_exit Callback to receive the job's exit status
@@ -328,10 +327,11 @@ static void close_cb(Stream *stream, void *data)
/// < 0 if the job can't start
///
/// @returns [allocated] channel
-Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader on_stderr,
- Callback on_exit, bool pty, bool rpc, bool overlapped, bool detach,
- ChannelStdinMode stdin_mode, const char *cwd, uint16_t pty_width,
- uint16_t pty_height, dict_T *env, varnumber_T *status_out)
+Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_stdout,
+ CallbackReader on_stderr, Callback on_exit, bool pty, bool rpc,
+ bool overlapped, bool detach, ChannelStdinMode stdin_mode,
+ const char *cwd, uint16_t pty_width, uint16_t pty_height, dict_T *env,
+ varnumber_T *status_out)
{
Channel *chan = channel_alloc(kChannelStreamProc);
chan->on_data = on_stdout;
@@ -362,6 +362,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
Process *proc = &chan->stream.proc;
proc->argv = argv;
+ proc->exepath = exepath;
proc->cb = channel_process_exit_cb;
proc->events = chan->events;
proc->detach = detach;
@@ -369,8 +370,8 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
proc->env = env;
proc->overlapped = overlapped;
- char *cmd = xstrdup(proc->argv[0]);
- bool has_in, has_out, has_err;
+ char *cmd = xstrdup(process_get_exepath(proc));
+ bool has_out, has_err;
if (proc->type == kProcessTypePty) {
has_out = true;
has_err = false;
@@ -380,14 +381,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, CallbackReader
proc->fwd_err = chan->on_stderr.fwd_err;
}
- switch (stdin_mode) {
- case kChannelStdinPipe:
- has_in = true;
- break;
- case kChannelStdinNull:
- has_in = false;
- break;
- }
+ bool has_in = stdin_mode == kChannelStdinPipe;
int status = process_spawn(proc, has_in, has_out, has_err);
if (status) {
@@ -790,10 +784,9 @@ void channel_terminal_open(buf_T *buf, Channel *chan)
topts.write_cb = term_write;
topts.resize_cb = term_resize;
topts.close_cb = term_close;
- buf->b_p_channel = (long)chan->id; // 'channel' option
- Terminal *term = terminal_open(buf, topts);
- chan->term = term;
+ buf->b_p_channel = (OptInt)chan->id; // 'channel' option
channel_incref(chan);
+ terminal_open(&chan->term, buf, topts);
}
static void term_write(char *buf, size_t size, void *data)
@@ -836,13 +829,12 @@ static void term_close(void *data)
multiqueue_put(chan->events, term_delayed_free, 1, data);
}
-void channel_info_changed(Channel *chan, bool new)
+void channel_info_changed(Channel *chan, bool new_chan)
{
- event_T event = new ? EVENT_CHANOPEN : EVENT_CHANINFO;
+ event_T event = new_chan ? EVENT_CHANOPEN : EVENT_CHANINFO;
if (has_event(event)) {
channel_incref(chan);
- multiqueue_put(main_loop.events, set_info_event,
- 2, chan, event);
+ multiqueue_put(main_loop.events, set_info_event, 2, chan, event);
}
}
@@ -856,6 +848,7 @@ static void set_info_event(void **argv)
Dictionary info = channel_info(chan->id);
typval_T retval;
(void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL);
+ assert(retval.v_type == VAR_DICT);
tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict);
tv_dict_set_keys_readonly(dict);
@@ -890,14 +883,14 @@ Dictionary channel_info(uint64_t id)
stream_desc = "job";
if (chan->stream.proc.type == kProcessTypePty) {
const char *name = pty_process_tty_name(&chan->stream.pty);
- PUT(info, "pty", STRING_OBJ(cstr_to_string(name)));
+ PUT(info, "pty", CSTR_TO_OBJ(name));
}
char **p = chan->stream.proc.argv;
Array argv = ARRAY_DICT_INIT;
if (p != NULL) {
while (*p != NULL) {
- ADD(argv, STRING_OBJ(cstr_to_string(*p)));
+ ADD(argv, CSTR_TO_OBJ(*p));
p++;
}
}
@@ -920,11 +913,8 @@ Dictionary channel_info(uint64_t id)
case kChannelStreamSocket:
stream_desc = "socket";
break;
-
- default:
- abort();
}
- PUT(info, "stream", STRING_OBJ(cstr_to_string(stream_desc)));
+ PUT(info, "stream", CSTR_TO_OBJ(stream_desc));
if (chan->is_rpc) {
mode_desc = "rpc";
@@ -935,17 +925,33 @@ Dictionary channel_info(uint64_t id)
} else {
mode_desc = "bytes";
}
- PUT(info, "mode", STRING_OBJ(cstr_to_string(mode_desc)));
+ PUT(info, "mode", CSTR_TO_OBJ(mode_desc));
return info;
}
+/// Simple int64_t comparison function for use with qsort()
+static int int64_t_cmp(const void *a, const void *b)
+{
+ int64_t diff = *(int64_t *)a - *(int64_t *)b;
+ return (diff < 0) ? -1 : (diff > 0);
+}
+
Array channel_all_info(void)
{
- Channel *channel;
- Array ret = ARRAY_DICT_INIT;
- map_foreach_value(&channels, channel, {
- ADD(ret, DICTIONARY_OBJ(channel_info(channel->id)));
+ // order the items in the array by channel number, for Determinism™
+ kvec_t(int64_t) ids = KV_INITIAL_VALUE;
+ kv_resize(ids, map_size(&channels));
+ uint64_t id;
+ map_foreach_key(&channels, id, {
+ kv_push(ids, (int64_t)id);
});
+ qsort(ids.items, ids.size, sizeof ids.items[0], int64_t_cmp);
+
+ Array ret = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < ids.size; i++) {
+ ADD(ret, DICTIONARY_OBJ(channel_info((uint64_t)ids.items[i])));
+ }
+ kv_destroy(ids);
return ret;
}
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index 5743eaead5..5c9d708ac2 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -1,26 +1,23 @@
-#ifndef NVIM_CHANNEL_H
-#define NVIM_CHANNEL_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
-#include "nvim/garray.h"
-#include "nvim/macros.h"
+#include "nvim/garray_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define CHAN_STDIO 1
#define CHAN_STDERR 2
@@ -110,9 +107,9 @@ struct Channel {
bool callback_scheduled;
};
-EXTERN PMap(uint64_t) channels INIT(= MAP_INIT);
+EXTERN PMap(uint64_t) channels INIT( = MAP_INIT);
-EXTERN Callback on_print INIT(= CALLBACK_INIT);
+EXTERN Callback on_print INIT( = CALLBACK_INIT);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
@@ -121,7 +118,7 @@ EXTERN Callback on_print INIT(= CALLBACK_INIT);
/// @returns Channel with the id or NULL if not found
static inline Channel *find_channel(uint64_t id)
{
- return pmap_get(uint64_t)(&channels, id);
+ return (Channel *)pmap_get(uint64_t)(&channels, id);
}
static inline Stream *channel_instream(Channel *chan)
@@ -163,5 +160,3 @@ static inline Stream *channel_outstream(Channel *chan)
}
abort();
}
-
-#endif // NVIM_CHANNEL_H
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index 5aec9ccf9d..48a9808b31 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file charset.c
///
/// Code related to character sets.
@@ -11,33 +8,26 @@
#include <limits.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/indent.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-#include "nvim/mark.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/move.h"
#include "nvim/option.h"
#include "nvim/path.h"
-#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/state.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "charset.c.generated.h"
@@ -55,7 +45,7 @@ static bool chartab_initialized = false;
((chartab)[(unsigned)(c) >> 6] & (1ull << ((c) & 0x3f)))
// Table used below, see init_chartab() for an explanation
-static char_u g_chartab[256];
+static uint8_t g_chartab[256];
// Flags for g_chartab[].
#define CT_CELL_MASK 0x07 ///< mask: nr of display cells (1, 2 or 4)
@@ -97,10 +87,6 @@ int init_chartab(void)
int buf_init_chartab(buf_T *buf, int global)
{
int c;
- int c2;
- int i;
- bool tilde;
- bool do_isalpha;
if (global) {
// Set the default size for printable characters:
@@ -119,19 +105,13 @@ int buf_init_chartab(buf_T *buf, int global)
while (c < 256) {
if (c >= 0xa0) {
// UTF-8: bytes 0xa0 - 0xff are printable (latin1)
- g_chartab[c++] = CT_PRINT_CHAR + 1;
+ // Also assume that every multi-byte char is a filename character.
+ g_chartab[c++] = (CT_PRINT_CHAR | CT_FNAME_CHAR) + 1;
} else {
// the rest is unprintable by default
g_chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2;
}
}
-
- // Assume that every multi-byte char is a filename character.
- for (c = 1; c < 256; c++) {
- if (c >= 0xa0) {
- g_chartab[c] |= CT_FNAME_CHAR;
- }
- }
}
// Init word char flags all to false
@@ -145,7 +125,7 @@ int buf_init_chartab(buf_T *buf, int global)
// Walk through the 'isident', 'iskeyword', 'isfname' and 'isprint'
// options Each option is a list of characters, character numbers or
// ranges, separated by commas, e.g.: "200-210,x,#-178,-"
- for (i = global ? 0 : 3; i <= 3; i++) {
+ for (int i = global ? 0 : 3; i <= 3; i++) {
const char *p;
if (i == 0) {
// first round: 'isident'
@@ -162,8 +142,8 @@ int buf_init_chartab(buf_T *buf, int global)
}
while (*p) {
- tilde = false;
- do_isalpha = false;
+ bool tilde = false;
+ bool do_isalpha = false;
if ((*p == '^') && (p[1] != NUL)) {
tilde = true;
@@ -175,7 +155,7 @@ int buf_init_chartab(buf_T *buf, int global)
} else {
c = mb_ptr2char_adv(&p);
}
- c2 = -1;
+ int c2 = -1;
if ((*p == '-') && (p[1] != NUL)) {
p++;
@@ -286,7 +266,7 @@ void trans_characters(char *buf, int bufsize)
if ((trs_len = utfc_ptr2len(buf)) > 1) {
len -= trs_len;
} else {
- trs = (char *)transchar_byte((uint8_t)(*buf));
+ trs = transchar_byte((uint8_t)(*buf));
trs_len = (int)strlen(trs);
if (trs_len > 1) {
@@ -321,15 +301,13 @@ size_t transstr_len(const char *const s, bool untab)
while (*p) {
const size_t l = (size_t)utfc_ptr2len(p);
if (l > 1) {
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
-
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
len += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9];
- len += transchar_hex(hexbuf, pcc[i]);
+ len += transchar_hex(hexbuf, c);
}
}
p += l;
@@ -354,29 +332,29 @@ size_t transstr_len(const char *const s, bool untab)
/// @param[in] untab remove tab characters
///
/// @return length of the resulting string, without the NUL byte.
-size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool untab)
+size_t transstr_buf(const char *const s, const ssize_t slen, char *const buf, const size_t buflen,
+ bool untab)
FUNC_ATTR_NONNULL_ALL
{
const char *p = s;
char *buf_p = buf;
- char *const buf_e = buf_p + len - 1;
+ char *const buf_e = buf_p + buflen - 1;
- while (*p != NUL && buf_p < buf_e) {
+ while ((slen < 0 || (p - s) < slen) && *p != NUL && buf_p < buf_e) {
const size_t l = (size_t)utfc_ptr2len(p);
if (l > 1) {
if (buf_p + l > buf_e) {
break; // Exceeded `buf` size.
}
- int pcc[MAX_MCO + 1];
- pcc[0] = utfc_ptr2char(p, &pcc[1]);
- if (vim_isprintc(pcc[0])) {
+ if (vim_isprintc(utf_ptr2char(p))) {
memmove(buf_p, p, l);
buf_p += l;
} else {
- for (size_t i = 0; i < ARRAY_SIZE(pcc) && pcc[i]; i++) {
+ for (size_t off = 0; off < l; off += (size_t)utf_ptr2len(p + off)) {
+ int c = utf_ptr2char(p + off);
char hexbuf[9]; // <up to 6 bytes>NUL
- const size_t hexlen = transchar_hex(hexbuf, pcc[i]);
+ const size_t hexlen = transchar_hex(hexbuf, c);
if (buf_p + hexlen > buf_e) {
break;
}
@@ -388,7 +366,7 @@ size_t transstr_buf(const char *const s, char *const buf, const size_t len, bool
} else if (*p == TAB && !untab) {
*buf_p++ = *p++;
} else {
- const char *const tb = (const char *)transchar_byte((uint8_t)(*p++));
+ const char *const tb = transchar_byte((uint8_t)(*p++));
const size_t tb_len = strlen(tb);
if (buf_p + tb_len > buf_e) {
break; // Exceeded `buf` size.
@@ -416,7 +394,7 @@ char *transstr(const char *const s, bool untab)
// multi-byte characters.
const size_t len = transstr_len(s, untab) + 1;
char *const buf = xmalloc(len);
- transstr_buf(s, buf, len, untab);
+ transstr_buf(s, -1, buf, len, untab);
return buf;
}
@@ -431,7 +409,7 @@ size_t kv_transstr(StringBuilder *str, const char *const s, bool untab)
// multi-byte characters.
const size_t len = transstr_len(s, untab);
kv_ensure_space(*str, len + 1);
- transstr_buf(s, str->items + str->size, len + 1, untab);
+ transstr_buf(s, -1, str->items + str->size, len + 1, untab);
str->size += len; // do not include NUL byte
return len;
}
@@ -445,7 +423,6 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
FUNC_ATTR_NONNULL_RET
{
garray_T ga;
- int i;
int len = orglen;
#define GA_CHAR(i) ((char *)ga.ga_data)[i]
@@ -475,7 +452,7 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
}
// Make each character lower case.
- i = 0;
+ int i = 0;
while (STR_CHAR(i) != NUL) {
int c = utf_ptr2char(STR_PTR(i));
int olen = utf_ptr2len(STR_PTR(i));
@@ -531,7 +508,7 @@ char *str_foldcase(char *str, int orglen, char *buf, int buflen)
// Does NOT work for multi-byte characters, c must be <= 255.
// Also doesn't work for the first byte of a multi-byte, "c" must be a
// character!
-static char_u transchar_charbuf[11];
+static uint8_t transchar_charbuf[11];
/// Translate a character into a printable one, leaving printable ASCII intact
///
@@ -542,11 +519,10 @@ static char_u transchar_charbuf[11];
/// @return translated character into a static buffer.
char *transchar(int c)
{
- return (char *)transchar_buf(curbuf, c);
+ return transchar_buf(curbuf, c);
}
-char_u *transchar_buf(const buf_T *buf, int c)
- FUNC_ATTR_NONNULL_ALL
+char *transchar_buf(const buf_T *buf, int c)
{
int i = 0;
if (IS_SPECIAL(c)) {
@@ -558,33 +534,46 @@ char_u *transchar_buf(const buf_T *buf, int c)
}
if ((!chartab_initialized && (c >= ' ' && c <= '~'))
- || ((c <= 0xFF) && vim_isprintc_strict(c))) {
+ || ((c <= 0xFF) && vim_isprintc(c))) {
// printable character
- transchar_charbuf[i] = (char_u)c;
+ transchar_charbuf[i] = (uint8_t)c;
transchar_charbuf[i + 1] = NUL;
} else if (c <= 0xFF) {
- transchar_nonprint(buf, transchar_charbuf + i, c);
+ transchar_nonprint(buf, (char *)transchar_charbuf + i, c);
} else {
transchar_hex((char *)transchar_charbuf + i, c);
}
- return transchar_charbuf;
+ return (char *)transchar_charbuf;
+}
+
+/// Like transchar(), but called with a byte instead of a character.
+///
+/// Checks for an illegal UTF-8 byte. Uses 'fileformat' of the current buffer.
+///
+/// @param[in] c Byte to translate.
+///
+/// @return pointer to translated character in transchar_charbuf.
+char *transchar_byte(const int c)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return transchar_byte_buf(curbuf, c);
}
-/// Like transchar(), but called with a byte instead of a character
+/// Like transchar_buf(), but called with a byte instead of a character.
///
-/// Checks for an illegal UTF-8 byte.
+/// Checks for an illegal UTF-8 byte. Uses 'fileformat' of "buf", unless it is NULL.
///
/// @param[in] c Byte to translate.
///
/// @return pointer to translated character in transchar_charbuf.
-char_u *transchar_byte(const int c)
+char *transchar_byte_buf(const buf_T *buf, const int c)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (c >= 0x80) {
- transchar_nonprint(curbuf, transchar_charbuf, c);
- return transchar_charbuf;
+ transchar_nonprint(buf, (char *)transchar_charbuf, c);
+ return (char *)transchar_charbuf;
}
- return (char_u *)transchar(c);
+ return transchar_buf(buf, c);
}
/// Convert non-printable characters to 2..4 printable ones
@@ -596,13 +585,12 @@ char_u *transchar_byte(const int c)
/// at least 5 bytes (conversion result + NUL).
/// @param[in] c Character to convert. NUL is assumed to be NL according to
/// `:h NL-used-for-NUL`.
-void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
- FUNC_ATTR_NONNULL_ALL
+void transchar_nonprint(const buf_T *buf, char *charbuf, int c)
{
if (c == NL) {
// we use newline in place of a NUL
c = NUL;
- } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) {
+ } else if (buf != NULL && c == CAR && get_fileformat(buf) == EOL_MAC) {
// we use CR in place of NL in this case
c = NL;
}
@@ -610,12 +598,12 @@ void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
if (dy_flags & DY_UHEX || c > 0x7f) {
// 'display' has "uhex"
- transchar_hex((char *)charbuf, c);
+ transchar_hex(charbuf, c);
} else {
// 0x00 - 0x1f and 0x7f
charbuf[0] = '^';
// DEL displayed as ^?
- charbuf[1] = (char_u)(c ^ 0x40);
+ charbuf[1] = (char)(uint8_t)(c ^ 0x40);
charbuf[2] = NUL;
}
@@ -633,8 +621,8 @@ size_t transchar_hex(char *const buf, const int c)
size_t i = 0;
buf[i++] = '<';
- if (c > 255) {
- if (c > 255 * 256) {
+ if (c > 0xFF) {
+ if (c > 0xFFFF) {
buf[i++] = (char)nr2hex((unsigned)c >> 20);
buf[i++] = (char)nr2hex((unsigned)c >> 16);
}
@@ -648,6 +636,17 @@ size_t transchar_hex(char *const buf, const int c)
return i;
}
+/// Mirror text "str" for right-left displaying.
+/// Only works for single-byte characters (e.g., numbers).
+void rl_mirror_ascii(char *str, char *end)
+{
+ for (char *p1 = str, *p2 = (end ? end : str + strlen(str)) - 1; p1 < p2; p1++, p2--) {
+ char t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+ }
+}
+
/// Convert the lower 4 bits of byte "c" to its hex character
///
/// Lower case letters are used to avoid the confusion of <F1> being 0xf1 or
@@ -732,7 +731,7 @@ int ptr2cells(const char *p_in)
/// @param s
///
/// @return number of character cells.
-int vim_strsize(char *s)
+int vim_strsize(const char *s)
{
return vim_strnsize(s, MAXCOL);
}
@@ -746,7 +745,7 @@ int vim_strsize(char *s)
/// @param len
///
/// @return Number of character cells.
-int vim_strnsize(char *s, int len)
+int vim_strnsize(const char *s, int len)
{
assert(s != NULL);
int size = 0;
@@ -835,8 +834,10 @@ bool vim_iswordp_buf(const char *const p, buf_T *const buf)
return vim_iswordc_buf(c, buf);
}
-/// Check that "c" is a valid file-name character.
+/// Check that "c" is a valid file-name character as specified with the
+/// 'isfname' option.
/// Assume characters above 0x100 are valid (multi-byte).
+/// To be used for commands like "gf".
///
/// @param c character to check
bool vim_isfilec(int c)
@@ -845,6 +846,14 @@ bool vim_isfilec(int c)
return c >= 0x100 || (c > 0 && (g_chartab[c] & CT_FNAME_CHAR));
}
+/// Check if "c" is a valid file-name character, including characters left
+/// out of 'isfname' to make "gf" work, such as comma, space, '@', etc.
+bool vim_is_fname_char(int c)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return vim_isfilec(c) || c == ',' || c == ' ' || c == '@';
+}
+
/// Check that "c" is a valid file-name character or a wildcard character
/// Assume characters above 0x100 are valid (multi-byte).
/// Explicitly interpret ']' as a wildcard character as path_has_wildcard("]")
@@ -861,7 +870,6 @@ bool vim_isfilec_or_wc(int c)
}
/// Check that "c" is a printable character.
-/// Assume characters above 0x100 are printable for double-byte encodings.
///
/// @param c character to check
bool vim_isprintc(int c)
@@ -873,316 +881,19 @@ bool vim_isprintc(int c)
return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
}
-/// Strict version of vim_isprintc(c), don't return true if "c" is the head
-/// byte of a double-byte character.
-///
-/// @param c character to check
-///
-/// @return true if "c" is a printable character.
-bool vim_isprintc_strict(int c)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (c >= 0x100) {
- return utf_printable(c);
- }
- return c > 0 && (g_chartab[c] & CT_PRINT_CHAR);
-}
-
-/// Check that virtual column "vcol" is in the rightmost column of window "wp".
-///
-/// @param wp window
-/// @param vcol column number
-bool in_win_border(win_T *wp, colnr_T vcol)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
-{
- if (wp->w_width_inner == 0) {
- // there is no border
- return false;
- }
- int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number)
-
- if ((int)vcol < width1 - 1) {
- return false;
- }
-
- if ((int)vcol == width1 - 1) {
- return true;
- }
- int width2 = width1 + win_col_off2(wp); // width of further lines
-
- if (width2 <= 0) {
- return false;
- }
- return (vcol - width1) % width2 == width2 - 1;
-}
-
-/// Get virtual column number of pos.
-/// start: on the first position of this character (TAB, ctrl)
-/// cursor: where the cursor is on this character (first char, except for TAB)
-/// end: on the last position of this character (TAB, ctrl)
-///
-/// This is used very often, keep it fast!
-///
-/// @param wp
-/// @param pos
-/// @param start
-/// @param cursor
-/// @param end
-void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
-{
- char *ptr; // points to current char
- char *posptr; // points to char at pos->col
- int incr;
- int head;
- long *vts = wp->w_buffer->b_p_vts_array;
- int ts = (int)wp->w_buffer->b_p_ts;
-
- colnr_T vcol = 0;
- char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line
-
- if (pos->col == MAXCOL) {
- // continue until the NUL
- posptr = NULL;
- } else {
- // In a few cases the position can be beyond the end of the line.
- for (colnr_T i = 0; i < pos->col; i++) {
- if (ptr[i] == NUL) {
- pos->col = i;
- break;
- }
- }
- posptr = ptr + pos->col;
- posptr -= utf_head_off(line, posptr);
- }
-
- chartabsize_T cts;
- init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
-
- // This function is used very often, do some speed optimizations.
- // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
- // and there are no virtual text use a simple loop.
- // Also use this when 'list' is set but tabs take their normal size.
- if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL))
- && !wp->w_p_lbr
- && *get_showbreak_value(wp) == NUL
- && !wp->w_p_bri
- && !cts.cts_has_virt_text) {
- for (;;) {
- head = 0;
- int c = (uint8_t)(*ptr);
-
- // make sure we don't go past the end of the line
- if (c == NUL) {
- // NUL at end of line only takes one column
- incr = 1;
- break;
- }
-
- // A tab gets expanded, depending on the current column
- if (c == TAB) {
- incr = tabstop_padding(vcol, ts, vts);
- } else {
- // For utf-8, if the byte is >= 0x80, need to look at
- // further bytes to find the cell width.
- if (c >= 0x80) {
- incr = utf_ptr2cells(ptr);
- } else {
- incr = g_chartab[c] & CT_CELL_MASK;
- }
-
- // If a double-cell char doesn't fit at the end of a line
- // it wraps to the next line, it's like this char is three
- // cells wide.
- if ((incr == 2)
- && wp->w_p_wrap
- && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1)
- && in_win_border(wp, vcol)) {
- incr++;
- head = 1;
- }
- }
-
- if ((posptr != NULL) && (ptr >= posptr)) {
- // character at pos->col
- break;
- }
-
- vcol += incr;
- MB_PTR_ADV(ptr);
- }
- } else {
- for (;;) {
- // A tab gets expanded, depending on the current column
- // Other things also take up space.
- head = 0;
- incr = win_lbr_chartabsize(&cts, &head);
-
- // make sure we don't go past the end of the line
- if (*cts.cts_ptr == NUL) {
- // NUL at end of line only takes one column
- incr = 1;
- break;
- }
-
- if ((posptr != NULL) && (cts.cts_ptr >= posptr)) {
- // character at pos->col
- break;
- }
-
- cts.cts_vcol += incr;
- MB_PTR_ADV(cts.cts_ptr);
- }
- vcol = cts.cts_vcol;
- ptr = cts.cts_ptr;
- }
- clear_chartabsize_arg(&cts);
-
- if (start != NULL) {
- *start = vcol + head;
- }
-
- if (end != NULL) {
- *end = vcol + incr - 1;
- }
-
- if (cursor != NULL) {
- // cursor is after inserted text
- vcol += cts.cts_cur_text_width;
- if ((*ptr == TAB)
- && (State & MODE_NORMAL)
- && !wp->w_p_list
- && !virtual_active()
- && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) {
- // cursor at end
- *cursor = vcol + incr - 1;
- } else {
- // cursor at start
- *cursor = vcol + head;
- }
- }
-}
-
-/// Get virtual cursor column in the current window, pretending 'list' is off.
-///
-/// @param posp
-///
-/// @retujrn The virtual cursor column.
-colnr_T getvcol_nolist(pos_T *posp)
-{
- int list_save = curwin->w_p_list;
- colnr_T vcol;
-
- curwin->w_p_list = false;
- if (posp->coladd) {
- getvvcol(curwin, posp, NULL, &vcol, NULL);
- } else {
- getvcol(curwin, posp, NULL, &vcol, NULL);
- }
- curwin->w_p_list = list_save;
- return vcol;
-}
-
-/// Get virtual column in virtual mode.
-///
-/// @param wp
-/// @param pos
-/// @param start
-/// @param cursor
-/// @param end
-void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
-{
- colnr_T col;
-
- if (virtual_active()) {
- // For virtual mode, only want one value
- getvcol(wp, pos, &col, NULL, NULL);
-
- colnr_T coladd = pos->coladd;
- colnr_T endadd = 0;
-
- // Cannot put the cursor on part of a wide character.
- char *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false);
-
- if (pos->col < (colnr_T)strlen(ptr)) {
- int c = utf_ptr2char(ptr + pos->col);
- if ((c != TAB) && vim_isprintc(c)) {
- endadd = (colnr_T)(char2cells(c) - 1);
- if (coladd > endadd) {
- // past end of line
- endadd = 0;
- } else {
- coladd = 0;
- }
- }
- }
- col += coladd;
-
- if (start != NULL) {
- *start = col;
- }
-
- if (cursor != NULL) {
- *cursor = col;
- }
-
- if (end != NULL) {
- *end = col + endadd;
- }
- } else {
- getvcol(wp, pos, start, cursor, end);
- }
-}
-
-/// Get the leftmost and rightmost virtual column of pos1 and pos2.
-/// Used for Visual block mode.
-///
-/// @param wp
-/// @param pos1
-/// @param pos2
-/// @param left
-/// @param right
-void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right)
-{
- colnr_T from1;
- colnr_T from2;
- colnr_T to1;
- colnr_T to2;
-
- if (lt(*pos1, *pos2)) {
- getvvcol(wp, pos1, &from1, NULL, &to1);
- getvvcol(wp, pos2, &from2, NULL, &to2);
- } else {
- getvvcol(wp, pos2, &from1, NULL, &to1);
- getvvcol(wp, pos1, &from2, NULL, &to2);
- }
-
- if (from2 < from1) {
- *left = from2;
- } else {
- *left = from1;
- }
-
- if (to2 > to1) {
- if ((*p_sel == 'e') && (from2 - 1 >= to1)) {
- *right = from2 - 1;
- } else {
- *right = to2;
- }
- } else {
- *right = to1;
- }
-}
-
/// skipwhite: skip over ' ' and '\t'.
///
/// @param[in] p String to skip in.
///
/// @return Pointer to character after the skipped whitespace.
-char *skipwhite(const char *const p)
+char *skipwhite(const char *p)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
FUNC_ATTR_NONNULL_RET
{
- return skipwhite_len(p, strlen(p));
+ while (ascii_iswhite(*p)) {
+ p++;
+ }
+ return (char *)p;
}
/// Like `skipwhite`, but skip up to `len` characters.
@@ -1337,7 +1048,7 @@ char *skiptowhite(const char *p)
/// @param p
///
/// @return Pointer to the next whitespace character.
-char *skiptowhite_esc(char *p)
+char *skiptowhite_esc(const char *p)
FUNC_ATTR_PURE
{
while (*p != ' ' && *p != '\t' && *p != NUL) {
@@ -1346,7 +1057,7 @@ char *skiptowhite_esc(char *p)
}
p++;
}
- return p;
+ return (char *)p;
}
/// Skip over text until '\n' or NUL.
@@ -1431,14 +1142,14 @@ long getdigits_long(char **pp, bool strict, long def)
/// Gets a int32_t number from a string.
///
/// @see getdigits
-int32_t getdigits_int32(char **pp, bool strict, long def)
+int32_t getdigits_int32(char **pp, bool strict, int32_t def)
{
intmax_t number = getdigits(pp, strict, def);
-#if SIZEOF_INTMAX_T > SIZEOF_INT32_T
+#if SIZEOF_INTMAX_T > 4
if (strict) {
assert(number >= INT32_MIN && number <= INT32_MAX);
} else if (!(number >= INT32_MIN && number <= INT32_MAX)) {
- return (int32_t)def;
+ return def;
}
#endif
return (int32_t)number;
@@ -1488,9 +1199,10 @@ bool vim_isblankline(char *lbuf)
/// @param strict If true, fail if the number has unexpected trailing
/// alphanumeric chars: *len is set to 0 and nothing else is
/// returned.
+/// @param overflow When not NULL, set to true for overflow.
void vim_str2nr(const char *const start, int *const prep, int *const len, const int what,
varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen,
- const bool strict)
+ const bool strict, bool *const overflow)
FUNC_ATTR_NONNULL_ARG(1)
{
const char *ptr = start;
@@ -1591,7 +1303,6 @@ void vim_str2nr(const char *const start, int *const prep, int *const len, const
// Do the conversion manually to avoid sscanf() quirks.
abort(); // Should’ve used goto earlier.
- // -V:PARSE_NUMBER:560
#define PARSE_NUMBER(base, cond, conv) \
do { \
const char *const after_prefix = ptr; \
@@ -1614,6 +1325,9 @@ void vim_str2nr(const char *const start, int *const prep, int *const len, const
un = (base) * un + digit; \
} else { \
un = UVARNUMBER_MAX; \
+ if (overflow != NULL) { \
+ *overflow = true; \
+ } \
} \
ptr++; \
} \
@@ -1652,12 +1366,18 @@ vim_str2nr_proceed:
// avoid ubsan error for overflow
if (un > VARNUMBER_MAX) {
*nptr = VARNUMBER_MIN;
+ if (overflow != NULL) {
+ *overflow = true;
+ }
} else {
*nptr = -(varnumber_T)un;
}
} else {
if (un > VARNUMBER_MAX) {
un = VARNUMBER_MAX;
+ if (overflow != NULL) {
+ *overflow = true;
+ }
}
*nptr = (varnumber_T)un;
}
diff --git a/src/nvim/charset.h b/src/nvim/charset.h
index e1ef06ef1d..cfab0f8517 100644
--- a/src/nvim/charset.h
+++ b/src/nvim/charset.h
@@ -1,14 +1,14 @@
-#ifndef NVIM_CHARSET_H
-#define NVIM_CHARSET_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
/// Return the folded-case equivalent of the given character
///
@@ -54,6 +54,5 @@ static inline bool vim_isbreak(int c)
/// Used very often if 'linebreak' is set
static inline bool vim_isbreak(int c)
{
- return breakat_flags[(char_u)c];
+ return breakat_flags[(uint8_t)c];
}
-#endif // NVIM_CHARSET_H
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index 5e4b49db24..367b86ec55 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -1,21 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// cmdexpand.c: functions for command-line completion
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "auto/config.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -30,30 +26,33 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/hashtab.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
@@ -63,15 +62,12 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-/// Type used by ExpandGeneric()
-typedef char *(*CompleteListItemGetter)(expand_T *, int);
-
/// Type used by call_user_expand_func
typedef void *(*user_expand_func_T)(const char *, int, typval_T *);
@@ -107,6 +103,8 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& xp->xp_context != EXPAND_HELP
&& xp->xp_context != EXPAND_LUA
&& xp->xp_context != EXPAND_OLD_SETTING
+ && xp->xp_context != EXPAND_STRING_SETTING
+ && xp->xp_context != EXPAND_SETTING_SUBTRACT
&& xp->xp_context != EXPAND_OWNSYNTAX
&& xp->xp_context != EXPAND_PACKADD
&& xp->xp_context != EXPAND_RUNTIME
@@ -157,8 +155,9 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
// and wildmatch characters, except '~'.
for (int i = 0; i < numfiles; i++) {
// for ":set path=" we need to escape spaces twice
- if (xp->xp_backslash == XP_BS_THREE) {
- p = vim_strsave_escaped(files[i], " ");
+ if (xp->xp_backslash & XP_BS_THREE) {
+ char *pat = (xp->xp_backslash & XP_BS_COMMA) ? " ," : " ";
+ p = vim_strsave_escaped(files[i], pat);
xfree(files[i]);
files[i] = p;
#if defined(BACKSLASH_IN_FILENAME)
@@ -166,6 +165,12 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
xfree(files[i]);
files[i] = p;
#endif
+ } else if (xp->xp_backslash & XP_BS_COMMA) {
+ if (vim_strchr(files[i], ',') != NULL) {
+ p = vim_strsave_escaped(files[i], ",");
+ xfree(files[i]);
+ files[i] = p;
+ }
}
#ifdef BACKSLASH_IN_FILENAME
p = vim_strsave_fnameescape(files[i], vse_what);
@@ -222,10 +227,7 @@ static void ExpandEscape(expand_T *xp, char *str, int numfiles, char **files, in
int nextwild(expand_T *xp, int type, int options, bool escape)
{
CmdlineInfo *const ccline = get_cmdline_info();
- int i, j;
- char *p1;
char *p2;
- int difflen;
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
@@ -248,7 +250,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
ui_flush();
}
- i = (int)(xp->xp_pattern - ccline->cmdbuff);
+ int i = (int)(xp->xp_pattern - ccline->cmdbuff);
assert(ccline->cmdpos >= i);
xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
@@ -258,9 +260,10 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
// Get next/previous match for a previous expanded pattern.
p2 = ExpandOne(xp, NULL, NULL, 0, type);
} else {
+ char *p1;
if (cmdline_fuzzy_completion_supported(xp)) {
// If fuzzy matching, don't modify the search string
- p1 = xstrdup(xp->xp_pattern);
+ p1 = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
} else {
p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context);
}
@@ -282,6 +285,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
+ int j;
for (j = 0; (size_t)j < xp->xp_pattern_len; j++) {
if (ccline->cmdbuff[i + j] == '*'
|| ccline->cmdbuff[i + j] == '?') {
@@ -295,7 +299,7 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
}
if (p2 != NULL && !got_int) {
- difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len);
+ int difflen = (int)strlen(p2) - (int)(xp->xp_pattern_len);
if (ccline->cmdlen + difflen + 4 > ccline->cmdbufflen) {
realloc_cmdbuff(ccline->cmdlen + difflen + 4);
xp->xp_pattern = ccline->cmdbuff + i;
@@ -441,11 +445,8 @@ static int wildmenu_match_len(expand_T *xp, char *s)
/// @param matches list of matches
static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int match, int showtail)
{
- int row;
- char *buf;
int len;
int clen; // length in screen cells
- int fillchar;
int attr;
int i;
bool highlight = true;
@@ -454,15 +455,13 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
char *selend = NULL;
static int first_match = 0;
bool add_left = false;
- char *s;
- int emenu;
int l;
if (matches == NULL) { // interrupted completion?
return;
}
- buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1);
+ char *buf = xmalloc((size_t)Columns * MB_MAXBYTES + 1);
if (match == -1) { // don't show match but original text
match = 0;
@@ -485,13 +484,13 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
clen += 2;
}
// jumping right, put match at the left
- if ((long)clen > Columns) {
+ if (clen > Columns) {
first_match = match;
// if showing the last match, we can add some on the left
clen = 2;
for (i = match; i < num_matches; i++) {
clen += wildmenu_match_len(xp, SHOW_MATCH(i)) + 2;
- if ((long)clen >= Columns) {
+ if (clen >= Columns) {
break;
}
}
@@ -503,14 +502,14 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
if (add_left) {
while (first_match > 0) {
clen += wildmenu_match_len(xp, SHOW_MATCH(first_match - 1)) + 2;
- if ((long)clen >= Columns) {
+ if (clen >= Columns) {
break;
}
first_match--;
}
}
- fillchar = fillchar_status(&attr, curwin);
+ int fillchar = fillchar_status(&attr, curwin);
if (first_match == 0) {
*buf = NUL;
@@ -528,10 +527,9 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
selstart_col = clen;
}
- s = SHOW_MATCH(i);
+ char *s = SHOW_MATCH(i);
// Check for menu separators - replace with '|'
- emenu = (xp->xp_context == EXPAND_MENUS
- || xp->xp_context == EXPAND_MENUNAMES);
+ int emenu = (xp->xp_context == EXPAND_MENUS || xp->xp_context == EXPAND_MENUNAMES);
if (emenu && menu_is_separator(s)) {
STRCPY(buf + len, transchar('|'));
l = (int)strlen(buf + len);
@@ -570,7 +568,7 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
buf[len] = NUL;
- row = cmdline_row - 1;
+ int row = cmdline_row - 1;
if (row >= 0) {
if (wild_menu_showing == 0 || wild_menu_showing == WM_LIST) {
if (msg_scrolled > 0) {
@@ -601,17 +599,17 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
// Tricky: wildmenu can be drawn either over a status line, or at empty
// scrolled space in the message output
- ScreenGrid *grid = (wild_menu_showing == WM_SCROLLED)
- ? &msg_grid_adj : &default_grid;
+ grid_line_start((wild_menu_showing == WM_SCROLLED) ? &msg_grid_adj : &default_grid, row);
- grid_puts(grid, buf, row, 0, attr);
+ grid_line_puts(0, buf, -1, attr);
if (selstart != NULL && highlight) {
*selend = NUL;
- grid_puts(grid, selstart, row, selstart_col, HL_ATTR(HLF_WM));
+ grid_line_puts(selstart_col, selstart, -1, HL_ATTR(HLF_WM));
}
- grid_fill(grid, row, row + 1, clen, Columns,
- fillchar, fillchar, attr);
+ grid_line_fill(clen, Columns, fillchar, attr);
+
+ grid_line_flush();
}
win_redraw_last_status(topframe);
@@ -619,14 +617,14 @@ static void redraw_wildmenu(expand_T *xp, int num_matches, char **matches, int m
}
/// Get the next or prev cmdline completion match. The index of the match is set
-/// in "p_findex"
-static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char *orig_save)
+/// in "xp->xp_selected"
+static char *get_next_or_prev_match(int mode, expand_T *xp)
{
if (xp->xp_numfiles <= 0) {
return NULL;
}
- int findex = *p_findex;
+ int findex = xp->xp_selected;
if (mode == WILD_PREV) {
if (findex == -1) {
@@ -680,14 +678,14 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char
// When wrapping around, return the original string, set findex to -1.
if (findex < 0) {
- if (orig_save == NULL) {
+ if (xp->xp_orig == NULL) {
findex = xp->xp_numfiles - 1;
} else {
findex = -1;
}
}
if (findex >= xp->xp_numfiles) {
- if (orig_save == NULL) {
+ if (xp->xp_orig == NULL) {
findex = 0;
} else {
findex = -1;
@@ -699,9 +697,9 @@ static char *get_next_or_prev_match(int mode, expand_T *xp, int *p_findex, char
} else if (p_wmnu) {
redraw_wildmenu(xp, xp->xp_numfiles, xp->xp_files, findex, cmd_showtail);
}
- *p_findex = findex;
+ xp->xp_selected = findex;
- return xstrdup(findex == -1 ? orig_save : xp->xp_files[findex]);
+ return xstrdup(findex == -1 ? xp->xp_orig : xp->xp_files[findex]);
}
/// Start the command-line expansion and get the matches.
@@ -799,7 +797,7 @@ static char *find_longest_match(expand_T *xp, int options)
}
}
- return xstrndup(xp->xp_files[0], len);
+ return xmemdupz(xp->xp_files[0], len);
}
/// Do wildcard expansion on the string "str".
@@ -808,8 +806,8 @@ static char *find_longest_match(expand_T *xp, int options)
/// Return NULL for failure.
///
/// "orig" is the originally expanded string, copied to allocated memory. It
-/// should either be kept in orig_save or freed. When "mode" is WILD_NEXT or
-/// WILD_PREV "orig" should be NULL.
+/// should either be kept in "xp->xp_orig" or freed. When "mode" is WILD_NEXT
+/// or WILD_PREV "orig" should be NULL.
///
/// Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode"
/// is WILD_EXPAND_FREE or WILD_ALL.
@@ -844,46 +842,43 @@ static char *find_longest_match(expand_T *xp, int options)
char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
{
char *ss = NULL;
- static int findex;
- static char *orig_save = NULL; // kept value of orig
int orig_saved = false;
- int i;
// first handle the case of using an old match
if (mode == WILD_NEXT || mode == WILD_PREV
|| mode == WILD_PAGEUP || mode == WILD_PAGEDOWN
|| mode == WILD_PUM_WANT) {
- return get_next_or_prev_match(mode, xp, &findex, orig_save);
+ return get_next_or_prev_match(mode, xp);
}
if (mode == WILD_CANCEL) {
- ss = xstrdup(orig_save ? orig_save : "");
+ ss = xstrdup(xp->xp_orig ? xp->xp_orig : "");
} else if (mode == WILD_APPLY) {
- ss = xstrdup(findex == -1
- ? (orig_save ? orig_save : "")
- : xp->xp_files[findex]);
+ ss = xstrdup(xp->xp_selected == -1
+ ? (xp->xp_orig ? xp->xp_orig : "")
+ : xp->xp_files[xp->xp_selected]);
}
// free old names
if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) {
FreeWild(xp->xp_numfiles, xp->xp_files);
xp->xp_numfiles = -1;
- XFREE_CLEAR(orig_save);
+ XFREE_CLEAR(xp->xp_orig);
// The entries from xp_files may be used in the PUM, remove it.
if (compl_match_array != NULL) {
cmdline_pum_remove();
}
}
- findex = 0;
+ xp->xp_selected = 0;
if (mode == WILD_FREE) { // only release file name
return NULL;
}
if (xp->xp_numfiles == -1 && mode != WILD_APPLY && mode != WILD_CANCEL) {
- xfree(orig_save);
- orig_save = orig;
+ xfree(xp->xp_orig);
+ xp->xp_orig = orig;
orig_saved = true;
ss = ExpandOne_start(mode, xp, str, options);
@@ -892,23 +887,38 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
// Find longest common part
if (mode == WILD_LONGEST && xp->xp_numfiles > 0) {
ss = find_longest_match(xp, options);
- findex = -1; // next p_wc gets first one
+ xp->xp_selected = -1; // next p_wc gets first one
}
// Concatenate all matching names. Unless interrupted, this can be slow
// and the result probably won't be used.
- // TODO(philix): use xstpcpy instead of strcat in a loop (ExpandOne)
if (mode == WILD_ALL && xp->xp_numfiles > 0 && !got_int) {
size_t len = 0;
- for (i = 0; i < xp->xp_numfiles; i++) {
+ for (int i = 0; i < xp->xp_numfiles; i++) {
+ if (i > 0) {
+ if (xp->xp_prefix == XP_PREFIX_NO) {
+ len += 2; // prefix "no"
+ } else if (xp->xp_prefix == XP_PREFIX_INV) {
+ len += 3; // prefix "inv"
+ }
+ }
len += strlen(xp->xp_files[i]) + 1;
}
ss = xmalloc(len);
*ss = NUL;
- for (i = 0; i < xp->xp_numfiles; i++) {
- STRCAT(ss, xp->xp_files[i]);
+ char *ssp = ss;
+ for (int i = 0; i < xp->xp_numfiles; i++) {
+ if (i > 0) {
+ if (xp->xp_prefix == XP_PREFIX_NO) {
+ ssp = xstpcpy(ssp, "no");
+ } else if (xp->xp_prefix == XP_PREFIX_INV) {
+ ssp = xstpcpy(ssp, "inv");
+ }
+ }
+ ssp = xstpcpy(ssp, xp->xp_files[i]);
+
if (i != xp->xp_numfiles - 1) {
- STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " ");
+ ssp = xstpcpy(ssp, (options & WILD_USE_NL) ? "\n" : " ");
}
}
}
@@ -917,7 +927,7 @@ char *ExpandOne(expand_T *xp, char *str, char *orig, int options, int mode)
ExpandCleanup(xp);
}
- // Free "orig" if it wasn't stored in "orig_save".
+ // Free "orig" if it wasn't stored in "xp->xp_orig".
if (!orig_saved) {
xfree(orig);
}
@@ -931,6 +941,7 @@ void ExpandInit(expand_T *xp)
{
CLEAR_POINTER(xp);
xp->xp_backslash = XP_BS_NONE;
+ xp->xp_prefix = XP_PREFIX_NONE;
xp->xp_numfiles = -1;
}
@@ -941,6 +952,7 @@ void ExpandCleanup(expand_T *xp)
FreeWild(xp->xp_numfiles, xp->xp_files);
xp->xp_numfiles = -1;
}
+ XFREE_CLEAR(xp->xp_orig);
}
/// Display one line of completion matches. Multiple matches are displayed in
@@ -960,12 +972,12 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in
int lastlen = 999;
for (int j = linenr; j < numMatches; j += lines) {
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
- msg_outtrans_attr(matches[j], HL_ATTR(HLF_D));
+ msg_outtrans(matches[j], HL_ATTR(HLF_D));
p = matches[j] + strlen(matches[j]) + 1;
msg_advance(maxlen + 1);
- msg_puts((const char *)p);
+ msg_puts(p);
msg_advance(maxlen + 3);
- msg_outtrans_long_attr(p + 2, HL_ATTR(HLF_D));
+ msg_outtrans_long(p + 2, HL_ATTR(HLF_D));
break;
}
for (int i = maxlen - lastlen; --i >= 0;) {
@@ -1002,7 +1014,7 @@ static void showmatches_oneline(expand_T *xp, char **matches, int numMatches, in
isdir = false;
p = SHOW_MATCH(j);
}
- lastlen = msg_outtrans_attr(p, isdir ? dir_attr : 0);
+ lastlen = msg_outtrans(p, isdir ? dir_attr : 0);
}
if (msg_col > 0) { // when not wrapped around
msg_clr_eos();
@@ -1018,7 +1030,7 @@ int showmatches(expand_T *xp, int wildmenu)
CmdlineInfo *const ccline = get_cmdline_info();
int numMatches;
char **matches;
- int i, j;
+ int j;
int maxlen;
int lines;
int columns;
@@ -1027,8 +1039,8 @@ int showmatches(expand_T *xp, int wildmenu)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
- i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
- &numMatches, &matches);
+ int i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
+ &numMatches, &matches);
showtail = expand_showtail(xp);
if (i != EXPAND_OK) {
return i;
@@ -1066,7 +1078,7 @@ int showmatches(expand_T *xp, int wildmenu)
} else {
// find the length of the longest file name
maxlen = 0;
- for (i = 0; i < numMatches; i++) {
+ for (int i = 0; i < numMatches; i++) {
if (!showtail && (xp->xp_context == EXPAND_FILES
|| xp->xp_context == EXPAND_SHELLCMD
|| xp->xp_context == EXPAND_BUFFERS)) {
@@ -1102,7 +1114,7 @@ int showmatches(expand_T *xp, int wildmenu)
}
// list the files line by line
- for (i = 0; i < lines; i++) {
+ for (int i = 0; i < lines; i++) {
showmatches_oneline(xp, matches, numMatches, lines, i, maxlen, showtail, attr);
if (got_int) {
got_int = false;
@@ -1126,11 +1138,10 @@ int showmatches(expand_T *xp, int wildmenu)
/// Return the tail of file name path "s", ignoring a trailing "/".
static char *showmatches_gettail(char *s, bool eager)
{
- char *p;
char *t = s;
bool had_sep = false;
- for (p = s; *p != NUL;) {
+ for (char *p = s; *p != NUL;) {
if (vim_ispathsep(*p)
#ifdef BACKSLASH_IN_FILENAME
&& !rem_backslash(p)
@@ -1155,9 +1166,6 @@ static char *showmatches_gettail(char *s, bool eager)
/// returned.
static bool expand_showtail(expand_T *xp)
{
- char *s;
- char *end;
-
// When not completing file names a "/" may mean something different.
if (xp->xp_context != EXPAND_FILES
&& xp->xp_context != EXPAND_SHELLCMD
@@ -1165,12 +1173,12 @@ static bool expand_showtail(expand_T *xp)
return false;
}
- end = path_tail(xp->xp_pattern);
+ char *end = path_tail(xp->xp_pattern);
if (end == xp->xp_pattern) { // there is no path separator
return false;
}
- for (s = xp->xp_pattern; s < end; s++) {
+ for (char *s = xp->xp_pattern; s < end; s++) {
// Skip escaped wildcards. Only when the backslash is not a path
// separator, on DOS the '*' "path\*\file" must not be skipped.
if (rem_backslash(s)) {
@@ -1194,10 +1202,6 @@ char *addstar(char *fname, size_t len, int context)
FUNC_ATTR_NONNULL_RET
{
char *retval;
- size_t i, j;
- size_t new_len;
- char *tail;
- int ends_in_star;
if (context != EXPAND_FILES
&& context != EXPAND_FILES_IN_PATH
@@ -1210,7 +1214,6 @@ char *addstar(char *fname, size_t len, int context)
// For help tags the translation is done in find_help_tags().
// For a tag pattern starting with "/" no translation is needed.
if (context == EXPAND_HELP
- || context == EXPAND_CHECKHEALTH
|| context == EXPAND_COLORS
|| context == EXPAND_COMPILER
|| context == EXPAND_OWNSYNTAX
@@ -1218,11 +1221,13 @@ char *addstar(char *fname, size_t len, int context)
|| context == EXPAND_PACKADD
|| context == EXPAND_RUNTIME
|| ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS)
- && fname[0] == '/')) {
+ && fname[0] == '/')
+ || context == EXPAND_CHECKHEALTH
+ || context == EXPAND_LUA) {
retval = xstrnsave(fname, len);
} else {
- new_len = len + 2; // +2 for '^' at start, NUL at end
- for (i = 0; i < len; i++) {
+ size_t new_len = len + 2; // +2 for '^' at start, NUL at end
+ for (size_t i = 0; i < len; i++) {
if (fname[i] == '*' || fname[i] == '~') {
new_len++; // '*' needs to be replaced by ".*"
// '~' needs to be replaced by "\~"
@@ -1241,8 +1246,8 @@ char *addstar(char *fname, size_t len, int context)
retval = xmalloc(new_len);
{
retval[0] = '^';
- j = 1;
- for (i = 0; i < len; i++, j++) {
+ size_t j = 1;
+ for (size_t i = 0; i < len; i++, j++) {
// Skip backslash. But why? At least keep it for custom
// expansion.
if (context != EXPAND_USER_DEFINED
@@ -1289,8 +1294,8 @@ char *addstar(char *fname, size_t len, int context)
// $ could be anywhere in the tail.
// ` could be anywhere in the file name.
// When the name ends in '$' don't add a star, remove the '$'.
- tail = path_tail(retval);
- ends_in_star = (len > 0 && retval[len - 1] == '*');
+ char *tail = path_tail(retval);
+ int ends_in_star = (len > 0 && retval[len - 1] == '*');
#ifndef BACKSLASH_IN_FILENAME
for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) {
if (retval[k] != '\\') {
@@ -1331,8 +1336,8 @@ char *addstar(char *fname, size_t len, int context)
/// EXPAND_FILES After command with EX_XFILE set, or after setting
/// with P_EXPAND set. eg :e ^I, :w>>^I
/// EXPAND_DIRECTORIES In some cases this is used instead of the latter
-/// when we know only directories are of interest. eg
-/// :set dir=^I
+/// when we know only directories are of interest.
+/// E.g. :set dir=^I and :cd ^I
/// EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd".
/// EXPAND_SETTINGS Complete variable names. eg :set d^I
/// EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I
@@ -1375,7 +1380,6 @@ void set_expand_context(expand_T *xp)
static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, int *complp)
{
const char *p = NULL;
- size_t len = 0;
const bool fuzzy = cmdline_fuzzy_complete(cmd);
// Isolate the command and search for it in the command table.
@@ -1410,7 +1414,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in
if (p == cmd && vim_strchr("@*!=><&~#", (uint8_t)(*p)) != NULL) {
p++;
}
- len = (size_t)(p - cmd);
+ size_t len = (size_t)(p - cmd);
if (len == 0) {
xp->xp_context = EXPAND_UNSUCCESSFUL;
@@ -1442,7 +1446,7 @@ static const char *set_cmd_index(const char *cmd, exarg_T *eap, expand_T *xp, in
p = cmd + 1;
} else if (cmd[0] >= 'A' && cmd[0] <= 'Z') {
eap->cmd = (char *)cmd;
- p = (const char *)find_ucmd(eap, (char *)p, NULL, xp, complp);
+ p = find_ucmd(eap, (char *)p, NULL, xp, complp);
if (p == NULL) {
eap->cmdidx = CMD_SIZE; // Ambiguous user command.
}
@@ -1468,7 +1472,7 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
// Allow spaces within back-quotes to count as part of the argument
// being expanded.
xp->xp_pattern = skipwhite(arg);
- const char *p = (const char *)xp->xp_pattern;
+ const char *p = xp->xp_pattern;
while (*p != NUL) {
int c = utf_ptr2char(p);
if (c == '\\' && p[1] != NUL) {
@@ -1521,7 +1525,7 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
// Check for environment variable.
if (*xp->xp_pattern == '$') {
- for (p = (const char *)xp->xp_pattern + 1; *p != NUL; p++) {
+ for (p = xp->xp_pattern + 1; *p != NUL; p++) {
if (!vim_isIDc((uint8_t)(*p))) {
break;
}
@@ -1537,30 +1541,43 @@ static void set_context_for_wildcard_arg(exarg_T *eap, const char *arg, bool use
}
// Check for user names.
if (*xp->xp_pattern == '~') {
- for (p = (const char *)xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {}
+ for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; p++) {}
// Complete ~user only if it partially matches a user name.
// A full match ~user<Tab> will be replaced by user's home
// directory i.e. something like ~user<Tab> -> /home/user/
- if (*p == NUL && p > (const char *)xp->xp_pattern + 1
- && match_user(xp->xp_pattern + 1) >= 1) {
+ if (*p == NUL && p > xp->xp_pattern + 1 && match_user(xp->xp_pattern + 1) >= 1) {
xp->xp_context = EXPAND_USER;
xp->xp_pattern++;
}
}
}
+/// Set the completion context for the "++opt=arg" argument. Always returns NULL.
+static const char *set_context_in_argopt(expand_T *xp, const char *arg)
+{
+ char *p = vim_strchr(arg, '=');
+ if (p == NULL) {
+ xp->xp_pattern = (char *)arg;
+ } else {
+ xp->xp_pattern = p + 1;
+ }
+
+ xp->xp_context = EXPAND_ARGOPT;
+ return NULL;
+}
+
/// Set the completion context for the :filter command. Returns a pointer to the
/// next command after the :filter command.
static const char *set_context_in_filter_cmd(expand_T *xp, const char *arg)
{
if (*arg != NUL) {
- arg = (const char *)skip_vimgrep_pat((char *)arg, NULL, NULL);
+ arg = skip_vimgrep_pat((char *)arg, NULL, NULL);
}
if (arg == NULL || *arg == NUL) {
xp->xp_context = EXPAND_NOTHING;
return NULL;
}
- return (const char *)skipwhite(arg);
+ return skipwhite(arg);
}
/// Set the completion context for the :match command. Returns a pointer to the
@@ -1570,13 +1587,13 @@ static const char *set_context_in_match_cmd(expand_T *xp, const char *arg)
if (*arg == NUL || !ends_excmd(*arg)) {
// also complete "None"
set_context_in_echohl_cmd(xp, arg);
- arg = (const char *)skipwhite(skiptowhite(arg));
+ arg = skipwhite(skiptowhite(arg));
if (*arg != NUL) {
xp->xp_context = EXPAND_NOTHING;
- arg = (const char *)skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset());
+ arg = skip_regexp((char *)arg + 1, (uint8_t)(*arg), magic_isset());
}
}
- return (const char *)find_nextcmd(arg);
+ return find_nextcmd(arg);
}
/// Returns a pointer to the next command after a :global or a :v command.
@@ -1609,7 +1626,7 @@ static const char *find_cmd_after_substitute_cmd(const char *arg)
if (delim) {
// Skip "from" part.
arg++;
- arg = (const char *)skip_regexp((char *)arg, delim, magic_isset());
+ arg = skip_regexp((char *)arg, delim, magic_isset());
if (arg[0] != NUL && arg[0] == delim) {
// Skip "to" part.
@@ -1641,7 +1658,7 @@ static const char *find_cmd_after_substitute_cmd(const char *arg)
static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
{
// Skip count.
- arg = (const char *)skipwhite(skipdigits(arg));
+ arg = skipwhite(skipdigits(arg));
if (*arg != '/') {
return NULL;
}
@@ -1653,7 +1670,7 @@ static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
}
}
if (*arg) {
- arg = (const char *)skipwhite(arg + 1);
+ arg = skipwhite(arg + 1);
// Check for trailing illegal characters.
if (*arg == NUL || strchr("|\"\n", *arg) == NULL) {
@@ -1670,7 +1687,7 @@ static const char *find_cmd_after_isearch_cmd(expand_T *xp, const char *arg)
static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg)
{
while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
- arg = (const char *)xp->xp_pattern + 1;
+ arg = xp->xp_pattern + 1;
}
xp->xp_context = EXPAND_USER_VARS;
@@ -1687,7 +1704,7 @@ static const char *set_context_in_unlet_cmd(expand_T *xp, const char *arg)
/// Set the completion context for the :language command. Always returns NULL.
static const char *set_context_in_lang_cmd(expand_T *xp, const char *arg)
{
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
xp->xp_context = EXPAND_LANGUAGE;
xp->xp_pattern = (char *)arg;
@@ -1880,11 +1897,11 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_dsplit:
return find_cmd_after_isearch_cmd(xp, arg);
case CMD_autocmd:
- return (const char *)set_context_in_autocmd(xp, (char *)arg, false);
+ return set_context_in_autocmd(xp, (char *)arg, false);
case CMD_doautocmd:
case CMD_doautoall:
- return (const char *)set_context_in_autocmd(xp, (char *)arg, true);
+ return set_context_in_autocmd(xp, (char *)arg, true);
case CMD_set:
set_context_in_set_cmd(xp, (char *)arg, 0);
break;
@@ -1961,7 +1978,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_bwipeout:
case CMD_bunload:
while ((xp->xp_pattern = strchr(arg, ' ')) != NULL) {
- arg = (const char *)xp->xp_pattern + 1;
+ arg = xp->xp_pattern + 1;
}
FALLTHROUGH;
case CMD_buffer:
@@ -2000,8 +2017,8 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_snoremap:
case CMD_xmap:
case CMD_xnoremap:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
- false, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
+ false, cmdidx);
case CMD_unmap:
case CMD_nunmap:
case CMD_vunmap:
@@ -2011,8 +2028,8 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_lunmap:
case CMD_sunmap:
case CMD_xunmap:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
- true, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, false,
+ true, cmdidx);
case CMD_mapclear:
case CMD_nmapclear:
case CMD_vmapclear:
@@ -2032,13 +2049,13 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_cnoreabbrev:
case CMD_iabbrev:
case CMD_inoreabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
- false, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
+ false, cmdidx);
case CMD_unabbreviate:
case CMD_cunabbrev:
case CMD_iunabbrev:
- return (const char *)set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
- true, cmdidx);
+ return set_context_in_map_cmd(xp, (char *)cmd, (char *)arg, forceit, true,
+ true, cmdidx);
case CMD_menu:
case CMD_noremenu:
case CMD_unmenu:
@@ -2067,7 +2084,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_tunmenu:
case CMD_popup:
case CMD_emenu:
- return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
+ return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
case CMD_colorscheme:
xp->xp_context = EXPAND_COLORS;
@@ -2098,10 +2115,9 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
set_context_in_runtime_cmd(xp, arg);
break;
-#ifdef HAVE_WORKING_LIBINTL
case CMD_language:
return set_context_in_lang_cmd(xp, arg);
-#endif
+
case CMD_profile:
set_context_in_profile_cmd(xp, arg);
break;
@@ -2109,10 +2125,6 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
xp->xp_context = EXPAND_CHECKHEALTH;
xp->xp_pattern = (char *)arg;
break;
- case CMD_behave:
- xp->xp_context = EXPAND_BEHAVE;
- xp->xp_pattern = (char *)arg;
- break;
case CMD_messages:
xp->xp_context = EXPAND_MESSAGES;
@@ -2130,7 +2142,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_argdelete:
while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) {
- arg = (const char *)(xp->xp_pattern + 1);
+ arg = (xp->xp_pattern + 1);
}
xp->xp_context = EXPAND_ARGLIST;
xp->xp_pattern = (char *)arg;
@@ -2145,6 +2157,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
return set_context_in_scriptnames_cmd(xp, arg);
case CMD_lua:
+ case CMD_equal:
xp->xp_context = EXPAND_LUA;
break;
@@ -2189,7 +2202,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
// 3. skip over a range specifier of the form: addr [,addr] [;addr] ..
- cmd = (const char *)skip_range(cmd, &xp->xp_context);
+ cmd = skip_range(cmd, &xp->xp_context);
xp->xp_pattern = (char *)cmd;
if (*cmd == NUL) {
return NULL;
@@ -2221,15 +2234,25 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
ea.argt = excmd_get_argt(ea.cmdidx);
}
- const char *arg = (const char *)skipwhite(p);
+ const char *arg = skipwhite(p);
- // Skip over ++argopt argument
- if ((ea.argt & EX_ARGOPT) && *arg != NUL && strncmp(arg, "++", 2) == 0) {
- p = arg;
- while (*p && !ascii_isspace(*p)) {
- MB_PTR_ADV(p);
+ // Does command allow "++argopt" argument?
+ if (ea.argt & EX_ARGOPT) {
+ while (*arg != NUL && strncmp(arg, "++", 2) == 0) {
+ p = arg + 2;
+ while (*p && !ascii_isspace(*p)) {
+ MB_PTR_ADV(p);
+ }
+
+ // Still touching the command after "++"?
+ if (*p == NUL) {
+ if (ea.argt & EX_ARGOPT) {
+ return set_context_in_argopt(xp, arg + 2);
+ }
+ }
+
+ arg = skipwhite(p);
}
- arg = (const char *)skipwhite(p);
}
if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) {
@@ -2237,7 +2260,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
if (*++arg == '>') {
arg++;
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
} else if (*arg == '!' && ea.cmdidx == CMD_write) { // :w !filter
arg++;
usefilter = true;
@@ -2256,14 +2279,14 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
while (*arg == *cmd) { // allow any number of '>' or '<'
arg++;
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
// Does command allow "+command"?
if ((ea.argt & EX_CMDARG) && !usefilter && *arg == '+') {
// Check if we're in the +command
p = arg + 1;
- arg = (const char *)skip_cmd_arg((char *)arg, false);
+ arg = skip_cmd_arg((char *)arg, false);
// Still touching the command after '+'?
if (*arg == NUL) {
@@ -2271,7 +2294,7 @@ static const char *set_one_cmd_context(expand_T *xp, const char *buff)
}
// Skip space(s) after +command to get to the real argument.
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
// Check for '|' to separate commands and '"' to start comments.
@@ -2347,7 +2370,7 @@ void set_cmd_context(expand_T *xp, char *str, int len, int col, int use_ccline)
old_char = str[col];
}
str[col] = NUL;
- const char *nextcomm = (const char *)str;
+ const char *nextcomm = str;
if (use_ccline && ccline->cmdfirstc == '=') {
// pass CMD_SIZE because there is no real command
@@ -2434,15 +2457,23 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
pat = xstrdup(pat);
for (int i = 0; pat[i]; i++) {
if (pat[i] == '\\') {
- if (xp->xp_backslash == XP_BS_THREE
+ if (xp->xp_backslash & XP_BS_THREE
&& pat[i + 1] == '\\'
&& pat[i + 2] == '\\'
&& pat[i + 3] == ' ') {
STRMOVE(pat + i, pat + i + 3);
- }
- if (xp->xp_backslash == XP_BS_ONE
- && pat[i + 1] == ' ') {
+ } else if (xp->xp_backslash & XP_BS_ONE
+ && pat[i + 1] == ' ') {
+ STRMOVE(pat + i, pat + i + 1);
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == '\\'
+ && pat[i + 2] == ',') {
+ STRMOVE(pat + i, pat + i + 2);
+#ifdef BACKSLASH_IN_FILENAME
+ } else if ((xp->xp_backslash & XP_BS_COMMA)
+ && pat[i + 1] == ',') {
STRMOVE(pat + i, pat + i + 1);
+#endif
}
}
}
@@ -2483,19 +2514,6 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
}
/// Function given to ExpandGeneric() to obtain the possible arguments of the
-/// ":behave {mswin,xterm}" command.
-static char *get_behave_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
-{
- if (idx == 0) {
- return "mswin";
- }
- if (idx == 1) {
- return "xterm";
- }
- return NULL;
-}
-
-/// Function given to ExpandGeneric() to obtain the possible arguments of the
/// ":breakadd {expr, file, func, here}" command.
/// ":breakdel {func, file, here}" command.
static char *get_breakadd_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
@@ -2529,7 +2547,7 @@ static char *get_scriptnames_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx)
return NULL;
}
- scriptitem_T *si = &SCRIPT_ITEM(idx + 1);
+ scriptitem_T *si = SCRIPT_ITEM(idx + 1);
home_replace(NULL, si->sn_name, NameBuff, MAXPATHL, true);
return NameBuff;
}
@@ -2589,7 +2607,6 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
int escaped;
} tab[] = {
{ EXPAND_COMMANDS, get_command_name, false, true },
- { EXPAND_BEHAVE, get_behave_arg, true, true },
{ EXPAND_MAPCLEAR, get_mapclear_arg, true, true },
{ EXPAND_MESSAGES, get_messages_arg, true, true },
{ EXPAND_HISTORY, get_history_arg, true, true },
@@ -2606,15 +2623,13 @@ static int ExpandOther(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches
{ EXPAND_MENUNAMES, get_menu_names, false, true },
{ EXPAND_SYNTAX, get_syntax_name, true, true },
{ EXPAND_SYNTIME, get_syntime_arg, true, true },
- { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, false },
+ { EXPAND_HIGHLIGHT, get_highlight_name, true, false },
{ EXPAND_EVENTS, expand_get_event_name, true, false },
{ EXPAND_AUGROUP, expand_get_augroup_name, true, false },
{ EXPAND_SIGN, get_sign_name, true, true },
{ EXPAND_PROFILE, get_profile_name, true, true },
-#ifdef HAVE_WORKING_LIBINTL
{ EXPAND_LANGUAGE, get_lang_arg, true, false },
{ EXPAND_LOCALES, get_locales, true, false },
-#endif
{ EXPAND_ENV_VARS, get_env_name, true, true },
{ EXPAND_USER, get_users, true, false },
{ EXPAND_ARGLIST, get_arglist_name, true, false },
@@ -2701,8 +2716,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
return OK;
}
if (xp->xp_context == EXPAND_OLD_SETTING) {
- ExpandOldSetting(numMatches, matches);
- return OK;
+ return ExpandOldSetting(numMatches, matches);
}
if (xp->xp_context == EXPAND_BUFFERS) {
return ExpandBufnames(pat, numMatches, matches, options);
@@ -2772,8 +2786,14 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
if (xp->xp_context == EXPAND_SETTINGS
|| xp->xp_context == EXPAND_BOOL_SETTINGS) {
ret = ExpandSettings(xp, &regmatch, pat, numMatches, matches, fuzzy);
+ } else if (xp->xp_context == EXPAND_STRING_SETTING) {
+ ret = ExpandStringSetting(xp, &regmatch, numMatches, matches);
+ } else if (xp->xp_context == EXPAND_SETTING_SUBTRACT) {
+ ret = ExpandSettingSubtract(xp, &regmatch, numMatches, matches);
} else if (xp->xp_context == EXPAND_MAPPINGS) {
ret = ExpandMappings(pat, &regmatch, numMatches, matches);
+ } else if (xp->xp_context == EXPAND_ARGOPT) {
+ ret = expand_argopt(pat, xp, &regmatch, matches, numMatches);
} else if (xp->xp_context == EXPAND_USER_DEFINED) {
ret = ExpandUserDefined(pat, xp, &regmatch, matches, numMatches);
} else {
@@ -2795,9 +2815,8 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
/// program. Matching strings are copied into an array, which is returned.
///
/// @param func returns a string from the list
-static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch,
- char ***matches, int *numMatches, CompleteListItemGetter func,
- int escaped)
+void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regmatch, char ***matches,
+ int *numMatches, CompleteListItemGetter func, bool escaped)
{
const bool fuzzy = cmdline_fuzzy_complete(pat);
*matches = NULL;
@@ -2823,7 +2842,7 @@ static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regma
int score = 0;
if (xp->xp_pattern[0] != NUL) {
if (!fuzzy) {
- match = vim_regexec(regmatch, str, (colnr_T)0);
+ match = vim_regexec(regmatch, str, 0);
} else {
score = fuzzy_match_str(str, pat);
match = (score != 0);
@@ -2870,8 +2889,10 @@ static void ExpandGeneric(const char *const pat, expand_T *xp, regmatch_T *regma
// in the specified order.
const bool sort_matches = !fuzzy
&& xp->xp_context != EXPAND_MENUNAMES
+ && xp->xp_context != EXPAND_STRING_SETTING
&& xp->xp_context != EXPAND_MENUS
- && xp->xp_context != EXPAND_SCRIPTNAMES;
+ && xp->xp_context != EXPAND_SCRIPTNAMES
+ && xp->xp_context != EXPAND_ARGOPT;
// <SNR> functions should be sorted to the end.
const bool funcsort = xp->xp_context == EXPAND_EXPRESSION
@@ -2925,7 +2946,7 @@ static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char
// Check if this name was already found.
hash_T hash = hash_hash(name + l);
hashitem_T *hi =
- hash_lookup(ht, (const char *)(name + l), strlen(name + l), hash);
+ hash_lookup(ht, name + l, strlen(name + l), hash);
if (HASHITEM_EMPTY(hi)) {
// Remove the path that was prepended.
STRMOVE(name, name + l);
@@ -2950,19 +2971,16 @@ static void expand_shellcmd_onedir(char *buf, char *s, size_t l, char *pat, char
static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int flagsarg)
FUNC_ATTR_NONNULL_ALL
{
- char *pat;
- int i;
char *path = NULL;
garray_T ga;
char *buf = xmalloc(MAXPATHL);
- size_t l;
- char *s, *e;
+ char *e;
int flags = flagsarg;
bool did_curdir = false;
// for ":set path=" and ":set tags=" halve backslashes for escaped space
- pat = xstrdup(filepat);
- for (i = 0; pat[i]; i++) {
+ char *pat = xstrdup(filepat);
+ for (int i = 0; pat[i]; i++) {
if (pat[i] == '\\' && pat[i + 1] == ' ') {
STRMOVE(pat + i, pat + i + 1);
}
@@ -2992,7 +3010,7 @@ static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int
ga_init(&ga, (int)sizeof(char *), 10);
hashtab_T found_ht;
hash_init(&found_ht);
- for (s = path;; s = e) {
+ for (char *s = path;; s = e) {
e = vim_strchr(s, ENV_SEPCHAR);
if (e == NULL) {
e = s + strlen(s);
@@ -3013,7 +3031,7 @@ static void expand_shellcmd(char *filepat, char ***matches, int *numMatches, int
flags &= ~EW_DIR;
}
- l = (size_t)(e - s);
+ size_t l = (size_t)(e - s);
if (l > MAXPATHL - 5) {
break;
}
@@ -3041,7 +3059,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
CmdlineInfo *const ccline = get_cmdline_info();
char keep = 0;
typval_T args[4];
- char *pat = NULL;
const sctx_T save_current_sctx = current_sctx;
if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
@@ -3053,7 +3070,7 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
ccline->cmdbuff[ccline->cmdlen] = 0;
}
- pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
+ char *pat = xstrnsave(xp->xp_pattern, xp->xp_pattern_len);
args[0].v_type = VAR_STRING;
args[1].v_type = VAR_STRING;
args[2].v_type = VAR_NUMBER;
@@ -3084,7 +3101,7 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
*matches = NULL;
*numMatches = 0;
- char *const retstr = call_user_expand_func((user_expand_func_T)call_func_retstr, xp);
+ char *const retstr = call_user_expand_func(call_func_retstr, xp);
if (retstr == NULL) {
return FAIL;
}
@@ -3108,7 +3125,7 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
int score = 0;
if (xp->xp_pattern[0] != NUL) {
if (!fuzzy) {
- match = vim_regexec(regmatch, s, (colnr_T)0);
+ match = vim_regexec(regmatch, s, 0);
} else {
score = fuzzy_match_str(s, pat);
match = (score != 0);
@@ -3121,11 +3138,11 @@ static int ExpandUserDefined(const char *const pat, expand_T *xp, regmatch_T *re
if (match) {
if (!fuzzy) {
- GA_APPEND(char *, &ga, xstrnsave(s, (size_t)(e - s)));
+ GA_APPEND(char *, &ga, xmemdupz(s, (size_t)(e - s)));
} else {
GA_APPEND(fuzmatch_str_T, &ga, ((fuzmatch_str_T){
.idx = ga.ga_len,
- .str = xstrnsave(s, (size_t)(e - s)),
+ .str = xmemdupz(s, (size_t)(e - s)),
.score = score,
}));
}
@@ -3156,7 +3173,7 @@ static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches)
{
*matches = NULL;
*numMatches = 0;
- list_T *const retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp);
+ list_T *const retlist = call_user_expand_func(call_func_retlist, xp);
if (retlist == NULL) {
return FAIL;
}
@@ -3170,7 +3187,7 @@ static int ExpandUserList(expand_T *xp, char ***matches, int *numMatches)
continue; // Skip non-string items and empty strings.
}
- GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
+ GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string));
});
tv_list_unref(retlist);
@@ -3199,7 +3216,7 @@ static int ExpandUserLua(expand_T *xp, int *num_file, char ***file)
continue; // Skip non-string items and empty strings.
}
- GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string));
+ GA_APPEND(char *, &ga, xstrdup(TV_LIST_ITEM_TV(li)->vval.v_string));
});
tv_list_unref(retlist);
@@ -3229,8 +3246,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dir
char **p;
int num_p = 0;
- (void)ExpandFromContext(&xpc, buf, &p, &num_p,
- WILD_SILENT | expand_options);
+ (void)ExpandFromContext(&xpc, buf, &p, &num_p, WILD_SILENT | expand_options);
if (num_p > 0) {
ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT | expand_options);
@@ -3238,7 +3254,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dir
ga_grow(ga, num_p);
// take over the pointers and put them in "ga"
for (int i = 0; i < num_p; i++) {
- ((char_u **)ga->ga_data)[ga->ga_len] = (char_u *)p[i];
+ ((char **)ga->ga_data)[ga->ga_len] = p[i];
ga->ga_len++;
}
xfree(p);
@@ -3483,14 +3499,12 @@ void wildmenu_cleanup(CmdlineInfo *cclp)
/// "getcompletion()" function
void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *pat;
expand_T xpc;
bool filtered = false;
int options = WILD_SILENT | WILD_USE_NL | WILD_ADD_SLASH
| WILD_NO_BEEP | WILD_HOME_REPLACE;
- if (argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), "type must be a string");
+ if (tv_check_for_string_arg(argvars, 1) == FAIL) {
return;
}
const char *const type = tv_get_string(&argvars[1]);
@@ -3515,21 +3529,44 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const char *pattern = tv_get_string(&argvars[0]);
if (strcmp(type, "cmdline") == 0) {
- set_one_cmd_context(&xpc, pattern);
+ const int cmdline_len = (int)strlen(pattern);
+ set_cmd_context(&xpc, (char *)pattern, cmdline_len, cmdline_len, false);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
- xpc.xp_col = (int)strlen(pattern);
+ xpc.xp_col = cmdline_len;
goto theend;
}
ExpandInit(&xpc);
xpc.xp_pattern = (char *)pattern;
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
+ xpc.xp_line = (char *)pattern;
+
xpc.xp_context = cmdcomplete_str_to_type(type);
if (xpc.xp_context == EXPAND_NOTHING) {
semsg(_(e_invarg2), type);
return;
}
+ if (xpc.xp_context == EXPAND_USER_DEFINED) {
+ // Must be "custom,funcname" pattern
+ if (strncmp(type, "custom,", 7) != 0) {
+ semsg(_(e_invarg2), type);
+ return;
+ }
+
+ xpc.xp_arg = (char *)(type + 7);
+ }
+
+ if (xpc.xp_context == EXPAND_USER_LIST) {
+ // Must be "customlist,funcname" pattern
+ if (strncmp(type, "customlist,", 11) != 0) {
+ semsg(_(e_invarg2), type);
+ return;
+ }
+
+ xpc.xp_arg = (char *)(type + 11);
+ }
+
if (xpc.xp_context == EXPAND_MENUS) {
set_context_in_menu_cmd(&xpc, "menu", xpc.xp_pattern, false);
xpc.xp_pattern_len = strlen(xpc.xp_pattern);
@@ -3544,6 +3581,8 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
theend:
+ ;
+ char *pat;
if (cmdline_fuzzy_completion_supported(&xpc)) {
// when fuzzy matching, don't modify the search string
pat = xstrdup(xpc.xp_pattern);
@@ -3555,8 +3594,7 @@ theend:
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
xfree(pat);
ExpandCleanup(&xpc);
diff --git a/src/nvim/cmdexpand.h b/src/nvim/cmdexpand.h
index 810e289f7c..b0772d26a3 100644
--- a/src/nvim/cmdexpand.h
+++ b/src/nvim/cmdexpand.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_CMDEXPAND_H
-#define NVIM_CMDEXPAND_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: export
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/ex_getln.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
@@ -44,4 +44,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdexpand.h.generated.h"
#endif
-#endif // NVIM_CMDEXPAND_H
diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h
new file mode 100644
index 0000000000..97307c4e50
--- /dev/null
+++ b/src/nvim/cmdexpand_defs.h
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "nvim/eval/typval_defs.h"
+#include "nvim/types_defs.h"
+
+typedef enum {
+ XP_PREFIX_NONE, ///< prefix not used
+ XP_PREFIX_NO, ///< "no" prefix for bool option
+ XP_PREFIX_INV, ///< "inv" prefix for bool option
+} xp_prefix_T;
+
+enum { EXPAND_BUF_LEN = 256, };
+
+/// used for completion on the command line
+typedef struct expand {
+ char *xp_pattern; ///< start of item to expand, guaranteed
+ ///< to be part of xp_line
+ int xp_context; ///< type of expansion
+ size_t xp_pattern_len; ///< bytes in xp_pattern before cursor
+ xp_prefix_T xp_prefix;
+ char *xp_arg; ///< completion function
+ LuaRef xp_luaref; ///< Ref to Lua completion function
+ sctx_T xp_script_ctx; ///< SCTX for completion function
+ int xp_backslash; ///< one of the XP_BS_ values
+#ifndef BACKSLASH_IN_FILENAME
+ bool xp_shell; ///< true for a shell command, more
+ ///< characters need to be escaped
+#endif
+ int xp_numfiles; ///< number of files found by file name completion
+ int xp_col; ///< cursor position in line
+ int xp_selected; ///< selected index in completion
+ char *xp_orig; ///< originally expanded string
+ char **xp_files; ///< list of files
+ char *xp_line; ///< text being completed
+ char xp_buf[EXPAND_BUF_LEN]; ///< buffer for returned match
+} expand_T;
+
+/// values for xp_backslash
+enum {
+ XP_BS_NONE = 0, ///< nothing special for backslashes
+ XP_BS_ONE = 0x1, ///< uses one backslash before a space
+ XP_BS_THREE = 0x2, ///< uses three backslashes before a space
+ XP_BS_COMMA = 0x4, ///< commas need to be escaped with a backslash
+};
+
+/// values for xp_context when doing command line completion
+enum {
+ EXPAND_UNSUCCESSFUL = -2,
+ EXPAND_OK = -1,
+ EXPAND_NOTHING = 0,
+ EXPAND_COMMANDS,
+ EXPAND_FILES,
+ EXPAND_DIRECTORIES,
+ EXPAND_SETTINGS,
+ EXPAND_BOOL_SETTINGS,
+ EXPAND_TAGS,
+ EXPAND_OLD_SETTING,
+ EXPAND_HELP,
+ EXPAND_BUFFERS,
+ EXPAND_EVENTS,
+ EXPAND_MENUS,
+ EXPAND_SYNTAX,
+ EXPAND_HIGHLIGHT,
+ EXPAND_AUGROUP,
+ EXPAND_USER_VARS,
+ EXPAND_MAPPINGS,
+ EXPAND_TAGS_LISTFILES,
+ EXPAND_FUNCTIONS,
+ EXPAND_USER_FUNC,
+ EXPAND_EXPRESSION,
+ EXPAND_MENUNAMES,
+ EXPAND_USER_COMMANDS,
+ EXPAND_USER_CMD_FLAGS,
+ EXPAND_USER_NARGS,
+ EXPAND_USER_COMPLETE,
+ EXPAND_ENV_VARS,
+ EXPAND_LANGUAGE,
+ EXPAND_COLORS,
+ EXPAND_COMPILER,
+ EXPAND_USER_DEFINED,
+ EXPAND_USER_LIST,
+ EXPAND_USER_LUA,
+ EXPAND_SHELLCMD,
+ EXPAND_SIGN,
+ EXPAND_PROFILE,
+ EXPAND_FILETYPE,
+ EXPAND_FILES_IN_PATH,
+ EXPAND_OWNSYNTAX,
+ EXPAND_LOCALES,
+ EXPAND_HISTORY,
+ EXPAND_USER,
+ EXPAND_SYNTIME,
+ EXPAND_USER_ADDR_TYPE,
+ EXPAND_PACKADD,
+ EXPAND_MESSAGES,
+ EXPAND_MAPCLEAR,
+ EXPAND_ARGLIST,
+ EXPAND_DIFF_BUFFERS,
+ EXPAND_BREAKPOINT,
+ EXPAND_SCRIPTNAMES,
+ EXPAND_RUNTIME,
+ EXPAND_STRING_SETTING,
+ EXPAND_SETTING_SUBTRACT,
+ EXPAND_ARGOPT,
+ EXPAND_CHECKHEALTH,
+ EXPAND_LUA,
+};
+
+/// Type used by ExpandGeneric()
+typedef char *(*CompleteListItemGetter)(expand_T *, int);
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index 2df82d9355..4556b74396 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// cmdhist.c: Functions for the history of the command-line.
#include <assert.h>
@@ -10,24 +7,25 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdhist.c.generated.h"
@@ -204,7 +202,7 @@ static inline void clear_hist_entry(histentry_T *hisptr)
/// If 'move_to_front' is true, matching entry is moved to end of history.
///
/// @param move_to_front Move the entry to the front if it exists
-static int in_history(int type, char *str, int move_to_front, int sep)
+static int in_history(int type, const char *str, int move_to_front, int sep)
{
int last_i = -1;
@@ -238,7 +236,7 @@ static int in_history(int type, char *str, int move_to_front, int sep)
}
list_T *const list = history[type][i].additional_elements;
- str = history[type][i].hisstr;
+ char *const save_hisstr = history[type][i].hisstr;
while (i != hisidx[type]) {
if (++i >= hislen) {
i = 0;
@@ -248,7 +246,7 @@ static int in_history(int type, char *str, int move_to_front, int sep)
}
tv_list_unref(list);
history[type][i].hisnum = ++hisnum[type];
- history[type][i].hisstr = str;
+ history[type][i].hisstr = save_hisstr;
history[type][i].timestamp = os_time();
history[type][i].additional_elements = NULL;
return true;
@@ -295,7 +293,7 @@ static int last_maptick = -1; // last seen maptick
/// @param histype may be one of the HIST_ values.
/// @param in_map consider maptick when inside a mapping
/// @param sep separator character used (search hist)
-void add_to_history(int histype, char *new_entry, int in_map, int sep)
+void add_to_history(int histype, const char *new_entry, int in_map, int sep)
{
histentry_T *hisptr;
@@ -368,7 +366,6 @@ static int get_history_idx(int histype)
static int calc_hist_idx(int histype, int num)
{
int i;
- int wrapped = false;
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
|| (i = hisidx[histype]) < 0 || num == 0) {
@@ -377,6 +374,7 @@ static int calc_hist_idx(int histype, int num)
histentry_T *hist = history[histype];
if (num > 0) {
+ int wrapped = false;
while (hist[i].hisnum > num) {
if (--i < 0) {
if (wrapped) {
@@ -454,13 +452,14 @@ static int del_history_entry(int histype, char *str)
regmatch.rm_ic = false; // always match case
bool found = false;
- int i = idx, last = idx;
+ int i = idx;
+ int last = idx;
do {
histentry_T *hisptr = &history[histype][i];
if (hisptr->hisstr == NULL) {
break;
}
- if (vim_regexec(&regmatch, hisptr->hisstr, (colnr_T)0)) {
+ if (vim_regexec(&regmatch, hisptr->hisstr, 0)) {
found = true;
hist_free_entry(hisptr);
} else {
@@ -519,14 +518,12 @@ static int del_history_idx(int histype, int idx)
/// "histadd()" function
void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- HistoryType histype;
-
rettv->vval.v_number = false;
if (check_secure()) {
return;
}
const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ HistoryType histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
if (histype == HIST_INVALID) {
return;
}
@@ -538,7 +535,7 @@ void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
init_history();
- add_to_history(histype, (char *)str, false, NUL);
+ add_to_history(histype, str, false, NUL);
rettv->vval.v_number = true;
}
@@ -568,14 +565,12 @@ void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "histget()" function
void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- HistoryType type;
- int idx;
-
const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
if (str == NULL) {
rettv->vval.v_string = NULL;
} else {
- type = get_histtype(str, strlen(str), false);
+ int idx;
+ HistoryType type = get_histtype(str, strlen(str), false);
if (argvars[1].v_type == VAR_UNKNOWN) {
idx = get_history_idx(type);
} else {
@@ -592,8 +587,8 @@ void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
const char *const histname = tv_get_string_chk(&argvars[0]);
HistoryType i = histname == NULL
- ? HIST_INVALID
- : get_histtype(histname, strlen(histname), false);
+ ? HIST_INVALID
+ : get_histtype(histname, strlen(histname), false);
if (i != HIST_INVALID) {
i = get_history_idx(i);
}
@@ -603,18 +598,15 @@ void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// :history command - print a history
void ex_history(exarg_T *eap)
{
- histentry_T *hist;
int histype1 = HIST_CMD;
int histype2 = HIST_CMD;
int hisidx1 = 1;
int hisidx2 = -1;
- int idx;
- int i, j, k;
char *end;
char *arg = eap->arg;
if (hislen == 0) {
- msg(_("'history' option is zero"));
+ msg(_("'history' option is zero"), 0);
return;
}
@@ -626,7 +618,7 @@ void ex_history(exarg_T *eap)
}
histype1 = get_histtype(arg, (size_t)(end - arg), false);
if (histype1 == HIST_INVALID) {
- if (STRNICMP(arg, "all", end - (char *)arg) == 0) {
+ if (STRNICMP(arg, "all", end - arg) == 0) {
histype1 = 0;
histype2 = HIST_COUNT - 1;
} else {
@@ -640,19 +632,24 @@ void ex_history(exarg_T *eap)
end = arg;
}
if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
- semsg(_(e_trailing_arg), end);
+ if (*end != NUL) {
+ semsg(_(e_trailing_arg), end);
+ } else {
+ semsg(_(e_val_too_large), arg);
+ }
return;
}
for (; !got_int && histype1 <= histype2; histype1++) {
- STRCPY(IObuff, "\n # ");
+ xstrlcpy(IObuff, "\n # ", IOSIZE);
assert(history_names[histype1] != NULL);
- STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
+ xstrlcat(IObuff, history_names[histype1], IOSIZE);
+ xstrlcat(IObuff, " history", IOSIZE);
msg_puts_title(IObuff);
- idx = hisidx[histype1];
- hist = history[histype1];
- j = hisidx1;
- k = hisidx2;
+ int idx = hisidx[histype1];
+ histentry_T *hist = history[histype1];
+ int j = hisidx1;
+ int k = hisidx2;
if (j < 0) {
j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
}
@@ -660,7 +657,7 @@ void ex_history(exarg_T *eap)
k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
}
if (idx >= 0 && j <= k) {
- for (i = idx + 1; !got_int; i++) {
+ for (int i = idx + 1; !got_int; i++) {
if (i == hislen) {
i = 0;
}
@@ -673,9 +670,9 @@ void ex_history(exarg_T *eap)
trunc_string(hist[i].hisstr, IObuff + strlen(IObuff),
Columns - 10, IOSIZE - (int)strlen(IObuff));
} else {
- STRCAT(IObuff, hist[i].hisstr);
+ xstrlcat(IObuff, hist[i].hisstr, IOSIZE);
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
}
if (i == idx) {
break;
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
index f86a2f855c..cce0f92898 100644
--- a/src/nvim/cmdhist.h
+++ b/src/nvim/cmdhist.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_CMDHIST_H
-#define NVIM_CMDHIST_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: export
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: export
#include "nvim/os/time.h"
+#include "nvim/types_defs.h"
/// Present history tables
typedef enum {
@@ -31,4 +31,3 @@ typedef struct hist_entry {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cmdhist.h.generated.h"
#endif
-#endif // NVIM_CMDHIST_H
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 9de6c16536..59309fcf16 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Context: snapshot of the entire editor state as one big object/map
#include <assert.h>
@@ -9,20 +6,20 @@
#include <stdio.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
+#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vimscript.h"
#include "nvim/context.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_docmd.h"
-#include "nvim/gettext.h"
+#include "nvim/func_attr.h"
#include "nvim/hashtab.h"
#include "nvim/keycodes.h"
#include "nvim/memory.h"
-#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/shada.h"
@@ -142,9 +139,8 @@ bool ctx_restore(Context *ctx, const int flags)
free_ctx = true;
}
- char *op_shada;
- get_option_value("shada", NULL, &op_shada, NULL, OPT_GLOBAL);
- set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL);
+ OptVal op_shada = get_option_value("shada", NULL, OPT_GLOBAL, NULL);
+ set_option_value("shada", STATIC_CSTR_AS_OPTVAL("!,'100,%"), OPT_GLOBAL);
if (flags & kCtxRegs) {
ctx_restore_regs(ctx);
@@ -170,8 +166,8 @@ bool ctx_restore(Context *ctx, const int flags)
ctx_free(ctx);
}
- set_option_value("shada", 0L, op_shada, OPT_GLOBAL);
- xfree(op_shada);
+ set_option_value("shada", op_shada, OPT_GLOBAL);
+ optval_free(op_shada);
return true;
}
@@ -271,8 +267,9 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly)
size_t cmd_len = sizeof("func! ") + strlen(name);
char *cmd = xmalloc(cmd_len);
snprintf(cmd, cmd_len, "func! %s", name);
- String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd),
- true, &err);
+ Dict(exec_opts) opts = { .output = true };
+ String func_body = exec_impl(VIML_INTERNAL_CALL, cstr_as_string(cmd),
+ &opts, &err);
xfree(cmd);
if (!ERROR_SET(&err)) {
ADD(ctx->funcs, STRING_OBJ(func_body));
@@ -320,24 +317,28 @@ static inline Array sbuf_to_array(msgpack_sbuffer sbuf)
/// Convert readfile()-style array to msgpack_sbuffer.
///
/// @param[in] array readfile()-style array to convert.
+/// @param[out] err Error object.
///
/// @return msgpack_sbuffer with conversion result.
-static inline msgpack_sbuffer array_to_sbuf(Array array)
+static inline msgpack_sbuffer array_to_sbuf(Array array, Error *err)
+ FUNC_ATTR_NONNULL_ALL
{
msgpack_sbuffer sbuf;
msgpack_sbuffer_init(&sbuf);
typval_T list_tv;
- Error err = ERROR_INIT;
- object_to_vim(ARRAY_OBJ(array), &list_tv, &err);
+ if (!object_to_vim(ARRAY_OBJ(array), &list_tv, err)) {
+ return sbuf;
+ }
+ assert(list_tv.v_type == VAR_LIST);
if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) {
- emsg(_("E474: Failed to convert list to msgpack string buffer"));
+ api_set_error(err, kErrorTypeException, "%s",
+ "E474: Failed to convert list to msgpack string buffer");
}
sbuf.alloc = sbuf.size;
tv_clear(&list_tv);
- api_clear_error(&err);
return sbuf;
}
@@ -366,31 +367,32 @@ Dictionary ctx_to_dict(Context *ctx)
///
/// @param[in] dict Context Dictionary representation.
/// @param[out] ctx Context object to store conversion result into.
+/// @param[out] err Error object.
///
/// @return types of included context items.
-int ctx_from_dict(Dictionary dict, Context *ctx)
+int ctx_from_dict(Dictionary dict, Context *ctx, Error *err)
FUNC_ATTR_NONNULL_ALL
{
assert(ctx != NULL);
int types = 0;
- for (size_t i = 0; i < dict.size; i++) {
+ for (size_t i = 0; i < dict.size && !ERROR_SET(err); i++) {
KeyValuePair item = dict.items[i];
if (item.value.type != kObjectTypeArray) {
continue;
}
if (strequal(item.key.data, "regs")) {
types |= kCtxRegs;
- ctx->regs = array_to_sbuf(item.value.data.array);
+ ctx->regs = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "jumps")) {
types |= kCtxJumps;
- ctx->jumps = array_to_sbuf(item.value.data.array);
+ ctx->jumps = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "bufs")) {
types |= kCtxBufs;
- ctx->bufs = array_to_sbuf(item.value.data.array);
+ ctx->bufs = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "gvars")) {
types |= kCtxGVars;
- ctx->gvars = array_to_sbuf(item.value.data.array);
+ ctx->gvars = array_to_sbuf(item.value.data.array, err);
} else if (strequal(item.key.data, "funcs")) {
types |= kCtxFuncs;
ctx->funcs = copy_object(item.value, NULL).data.array;
diff --git a/src/nvim/context.h b/src/nvim/context.h
index 7a1224d876..1c18a1af7c 100644
--- a/src/nvim/context.h
+++ b/src/nvim/context.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_CONTEXT_H
-#define NVIM_CONTEXT_H
+#pragma once
-#include <msgpack.h>
#include <msgpack/sbuffer.h>
#include <stddef.h>
@@ -45,5 +43,3 @@ extern int kCtxAll;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "context.h.generated.h"
#endif
-
-#endif // NVIM_CONTEXT_H
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index b1dbc68ea3..1110fe1e64 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -1,32 +1,28 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
-#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/fold.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor.c.generated.h"
@@ -108,14 +104,14 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
|| (VIsual_active && *p_sel != 'o')
|| ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL);
- char *line = ml_get_buf(curbuf, pos->lnum, false);
+ char *line = ml_get_buf(curbuf, pos->lnum);
if (wcol >= MAXCOL) {
idx = (int)strlen(line) - 1 + one_more;
col = wcol;
if ((addspaces || finetune) && !VIsual_active) {
- curwin->w_curswant = linetabsize(line) + one_more;
+ curwin->w_curswant = linetabsize(curwin, pos->lnum) + one_more;
if (curwin->w_curswant > 0) {
curwin->w_curswant--;
}
@@ -129,7 +125,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a
&& curwin->w_width_inner != 0
&& wcol >= (colnr_T)width
&& width > 0) {
- csize = linetabsize(line);
+ csize = linetabsize(curwin, pos->lnum);
if (csize > 0) {
csize--;
}
@@ -315,7 +311,7 @@ void check_pos(buf_T *buf, pos_T *pos)
}
if (pos->col > 0) {
- char *line = ml_get_buf(buf, pos->lnum, false);
+ char *line = ml_get_buf(buf, pos->lnum);
colnr_T len = (colnr_T)strlen(line);
if (pos->col > len) {
pos->col = len;
@@ -324,18 +320,18 @@ void check_pos(buf_T *buf, pos_T *pos)
}
/// Make sure curwin->w_cursor.lnum is valid.
-void check_cursor_lnum(void)
+void check_cursor_lnum(win_T *win)
{
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
+ buf_T *buf = win->w_buffer;
+ if (win->w_cursor.lnum > buf->b_ml.ml_line_count) {
// If there is a closed fold at the end of the file, put the cursor in
// its first line. Otherwise in the last line.
- if (!hasFolding(curbuf->b_ml.ml_line_count,
- &curwin->w_cursor.lnum, NULL)) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ if (!hasFolding(buf->b_ml.ml_line_count, &win->w_cursor.lnum, NULL)) {
+ win->w_cursor.lnum = buf->b_ml.ml_line_count;
}
}
- if (curwin->w_cursor.lnum <= 0) {
- curwin->w_cursor.lnum = 1;
+ if (win->w_cursor.lnum <= 0) {
+ win->w_cursor.lnum = 1;
}
}
@@ -351,9 +347,9 @@ void check_cursor_col_win(win_T *win)
{
colnr_T oldcol = win->w_cursor.col;
colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd;
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
- colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false));
+ colnr_T len = (colnr_T)strlen(ml_get_buf(win->w_buffer, win->w_cursor.lnum));
if (len == 0) {
win->w_cursor.col = 0;
} else if (win->w_cursor.col >= len) {
@@ -406,7 +402,7 @@ void check_cursor_col_win(win_T *win)
/// Make sure curwin->w_cursor in on a valid character
void check_cursor(void)
{
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
check_cursor_col();
}
@@ -439,24 +435,28 @@ void adjust_cursor_col(void)
}
}
-/// When curwin->w_leftcol has changed, adjust the cursor position.
+/// Set "curwin->w_leftcol" to "leftcol".
+/// Adjust the cursor position if needed.
///
/// @return true if the cursor was moved.
-bool leftcol_changed(void)
+bool set_leftcol(colnr_T leftcol)
{
+ // Return quickly when there is no change.
+ if (curwin->w_leftcol == leftcol) {
+ return false;
+ }
+ curwin->w_leftcol = leftcol;
+
+ changed_cline_bef_curs(curwin);
// TODO(hinidu): I think it should be colnr_T or int, but p_siso is long.
// Perhaps we can change p_siso to int.
- int64_t lastcol;
- colnr_T s, e;
- bool retval = false;
-
- changed_cline_bef_curs();
- lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1;
+ int64_t lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1;
validate_virtcol();
+ bool retval = false;
// If the cursor is right or left of the screen, move it to last or first
- // character.
- long siso = get_sidescrolloff_value(curwin);
+ // visible character.
+ int siso = get_sidescrolloff_value(curwin);
if (curwin->w_virtcol > (colnr_T)(lastcol - siso)) {
retval = true;
coladvance((colnr_T)(lastcol - siso));
@@ -468,6 +468,7 @@ bool leftcol_changed(void)
// If the start of the character under the cursor is not on the screen,
// advance the cursor one more char. If this fails (last char of the
// line) adjust the scrolling.
+ colnr_T s, e;
getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e);
if (e > (colnr_T)lastcol) {
retval = true;
@@ -476,7 +477,7 @@ bool leftcol_changed(void)
retval = true;
if (coladvance(e + 1) == FAIL) { // there isn't another character
curwin->w_leftcol = s; // adjust w_leftcol instead
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
}
}
@@ -494,20 +495,19 @@ int gchar_cursor(void)
/// Write a character at the current cursor position.
/// It is directly written into the block.
-void pchar_cursor(char_u c)
+void pchar_cursor(char c)
{
- *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true)
- + curwin->w_cursor.col) = (char)c;
+ *(ml_get_buf_mut(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col) = c;
}
/// @return pointer to cursor line.
char *get_cursor_line_ptr(void)
{
- return ml_get_buf(curbuf, curwin->w_cursor.lnum, false);
+ return ml_get_buf(curbuf, curwin->w_cursor.lnum);
}
/// @return pointer to cursor position.
char *get_cursor_pos_ptr(void)
{
- return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + curwin->w_cursor.col;
+ return ml_get_buf(curbuf, curwin->w_cursor.lnum) + curwin->w_cursor.col;
}
diff --git a/src/nvim/cursor.h b/src/nvim/cursor.h
index 1cbe8a609e..d50976b598 100644
--- a/src/nvim/cursor.h
+++ b/src/nvim/cursor.h
@@ -1,12 +1,8 @@
-#ifndef NVIM_CURSOR_H
-#define NVIM_CURSOR_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor.h.generated.h"
#endif
-
-#endif // NVIM_CURSOR_H
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index f21e632036..5aff3b5598 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -1,52 +1,51 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor_shape.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/memory.h"
-#include "nvim/option_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor_shape.c.generated.h"
#endif
+static const char e_digit_expected[] = N_("E548: Digit expected");
+
/// Handling of cursor and mouse pointer shapes in various modes.
cursorentry_T shape_table[SHAPE_IDX_COUNT] = {
// Values are set by 'guicursor' and 'mouseshape'.
// Adjust the SHAPE_IDX_ defines when changing this!
- { "normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE },
- { "visual", 0, 0, 0, 700L, 400L, 250L, 0, 0, "v", SHAPE_CURSOR + SHAPE_MOUSE },
- { "insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "i", SHAPE_CURSOR + SHAPE_MOUSE },
- { "replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "r", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_normal", 0, 0, 0, 700L, 400L, 250L, 0, 0, "c", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_insert", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ci", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_replace", 0, 0, 0, 700L, 400L, 250L, 0, 0, "cr", SHAPE_CURSOR + SHAPE_MOUSE },
- { "operator", 0, 0, 0, 700L, 400L, 250L, 0, 0, "o", SHAPE_CURSOR + SHAPE_MOUSE },
- { "visual_select", 0, 0, 0, 700L, 400L, 250L, 0, 0, "ve", SHAPE_CURSOR + SHAPE_MOUSE },
- { "cmdline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "e", SHAPE_MOUSE },
- { "statusline_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "s", SHAPE_MOUSE },
- { "statusline_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "sd", SHAPE_MOUSE },
- { "vsep_hover", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vs", SHAPE_MOUSE },
- { "vsep_drag", 0, 0, 0, 0L, 0L, 0L, 0, 0, "vd", SHAPE_MOUSE },
- { "more", 0, 0, 0, 0L, 0L, 0L, 0, 0, "m", SHAPE_MOUSE },
- { "more_lastline", 0, 0, 0, 0L, 0L, 0L, 0, 0, "ml", SHAPE_MOUSE },
- { "showmatch", 0, 0, 0, 100L, 100L, 100L, 0, 0, "sm", SHAPE_CURSOR },
+ { "normal", 0, 0, 0, 700, 400, 250, 0, 0, "n", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "visual", 0, 0, 0, 700, 400, 250, 0, 0, "v", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "insert", 0, 0, 0, 700, 400, 250, 0, 0, "i", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "replace", 0, 0, 0, 700, 400, 250, 0, 0, "r", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_normal", 0, 0, 0, 700, 400, 250, 0, 0, "c", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_insert", 0, 0, 0, 700, 400, 250, 0, 0, "ci", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_replace", 0, 0, 0, 700, 400, 250, 0, 0, "cr", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "operator", 0, 0, 0, 700, 400, 250, 0, 0, "o", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "visual_select", 0, 0, 0, 700, 400, 250, 0, 0, "ve", SHAPE_CURSOR + SHAPE_MOUSE },
+ { "cmdline_hover", 0, 0, 0, 0, 0, 0, 0, 0, "e", SHAPE_MOUSE },
+ { "statusline_hover", 0, 0, 0, 0, 0, 0, 0, 0, "s", SHAPE_MOUSE },
+ { "statusline_drag", 0, 0, 0, 0, 0, 0, 0, 0, "sd", SHAPE_MOUSE },
+ { "vsep_hover", 0, 0, 0, 0, 0, 0, 0, 0, "vs", SHAPE_MOUSE },
+ { "vsep_drag", 0, 0, 0, 0, 0, 0, 0, 0, "vd", SHAPE_MOUSE },
+ { "more", 0, 0, 0, 0, 0, 0, 0, 0, "m", SHAPE_MOUSE },
+ { "more_lastline", 0, 0, 0, 0, 0, 0, 0, 0, "ml", SHAPE_MOUSE },
+ { "showmatch", 0, 0, 0, 100, 100, 100, 0, 0, "sm", SHAPE_CURSOR },
};
/// Converts cursor_shapes into an Array of Dictionaries
@@ -60,8 +59,8 @@ Array mode_style_array(Arena *arena)
for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
cursorentry_T *cur = &shape_table[i];
Dictionary dic = arena_dict(arena, 3 + ((cur->used_for & SHAPE_CURSOR) ? 9 : 0));
- PUT_C(dic, "name", STRING_OBJ(cstr_as_string(cur->full_name)));
- PUT_C(dic, "short_name", STRING_OBJ(cstr_as_string(cur->name)));
+ PUT_C(dic, "name", CSTR_AS_OBJ(cur->full_name));
+ PUT_C(dic, "short_name", CSTR_AS_OBJ(cur->name));
if (cur->used_for & SHAPE_MOUSE) {
PUT_C(dic, "mouse_shape", INTEGER_OBJ(cur->mshape));
}
@@ -101,7 +100,7 @@ Array mode_style_array(Arena *arena)
/// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
///
/// @returns error message for an illegal option, NULL otherwise.
-char *parse_shape_opt(int what)
+const char *parse_shape_opt(int what)
{
char *colonp;
char *commap;
@@ -113,10 +112,9 @@ char *parse_shape_opt(int what)
int len;
int i;
int found_ve = false; // found "ve" flag
- int round;
// First round: check for errors; second round: do it for real.
- for (round = 1; round <= 2; round++) {
+ for (int round = 1; round <= 2; round++) {
if (round == 2 || *p_guicursor == NUL) {
// Set all entries to default (block, blinkon0, default color).
// This is the default for anything that is not set.
@@ -194,7 +192,7 @@ char *parse_shape_opt(int what)
if (len != 0) {
p += len;
if (!ascii_isdigit(*p)) {
- return N_("E548: digit expected");
+ return e_digit_expected;
}
int n = getdigits_int(&p, false, 0);
if (len == 3) { // "ver" or "hor"
@@ -362,9 +360,9 @@ static void clear_shape_table(void)
{
for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
shape_table[idx].shape = SHAPE_BLOCK;
- shape_table[idx].blinkwait = 0L;
- shape_table[idx].blinkon = 0L;
- shape_table[idx].blinkoff = 0L;
+ shape_table[idx].blinkwait = 0;
+ shape_table[idx].blinkon = 0;
+ shape_table[idx].blinkoff = 0;
shape_table[idx].id = 0;
shape_table[idx].id_lm = 0;
}
diff --git a/src/nvim/cursor_shape.h b/src/nvim/cursor_shape.h
index 93bddd47c7..4c1d6d4eec 100644
--- a/src/nvim/cursor_shape.h
+++ b/src/nvim/cursor_shape.h
@@ -1,8 +1,7 @@
-#ifndef NVIM_CURSOR_SHAPE_H
-#define NVIM_CURSOR_SHAPE_H
+#pragma once
-#include "nvim/api/private/defs.h"
-#include "nvim/types.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/memory_defs.h" // IWYU pragma: keep
/// struct to store values from 'guicursor' and 'mouseshape'
/// Indexes in shape_table[]
@@ -44,9 +43,9 @@ typedef struct cursor_entry {
CursorShape shape; ///< cursor shape: one of the SHAPE_ defines
int mshape; ///< mouse shape: one of the MSHAPE defines
int percentage; ///< percentage of cell for bar
- long blinkwait; ///< blinking, wait time before blinking starts
- long blinkon; ///< blinking, on time
- long blinkoff; ///< blinking, off time
+ int blinkwait; ///< blinking, wait time before blinking starts
+ int blinkon; ///< blinking, on time
+ int blinkoff; ///< blinking, off time
int id; ///< highlight group ID
int id_lm; ///< highlight group ID for :lmap mode
char *name; ///< mode short name
@@ -58,4 +57,3 @@ extern cursorentry_T shape_table[SHAPE_IDX_COUNT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "cursor_shape.h.generated.h"
#endif
-#endif // NVIM_CURSOR_SHAPE_H
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index f7e70a78ce..a343c1ad6b 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file debugger.c
///
/// Vim script debugger functions
@@ -10,33 +7,33 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/state_defs.h"
+#include "nvim/vim_defs.h"
/// batch mode debugging: don't save and restore typeahead.
static bool debug_greedy = false;
@@ -75,7 +72,6 @@ void do_debug(char *cmd)
tasave_T typeaheadbuf;
bool typeahead_saved = false;
int save_ignore_script = 0;
- int n;
char *cmdline = NULL;
char *p;
char *tail = NULL;
@@ -103,31 +99,31 @@ void do_debug(char *cmd)
debug_mode = true;
if (!debug_did_msg) {
- msg(_("Entering Debug mode. Type \"cont\" to continue."));
+ msg(_("Entering Debug mode. Type \"cont\" to continue."), 0);
}
if (debug_oldval != NULL) {
- smsg(_("Oldval = \"%s\""), debug_oldval);
+ smsg(0, _("Oldval = \"%s\""), debug_oldval);
xfree(debug_oldval);
debug_oldval = NULL;
}
if (debug_newval != NULL) {
- smsg(_("Newval = \"%s\""), debug_newval);
+ smsg(0, _("Newval = \"%s\""), debug_newval);
xfree(debug_newval);
debug_newval = NULL;
}
char *sname = estack_sfile(ESTACK_NONE);
if (sname != NULL) {
- msg(sname);
+ msg(sname, 0);
}
xfree(sname);
if (SOURCING_LNUM != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
+ smsg(0, _("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
- smsg(_("cmd: %s"), cmd);
+ smsg(0, _("cmd: %s"), cmd);
}
// Repeat getting a command and executing it.
- for (;;) {
+ while (true) {
msg_scroll = true;
need_wait_return = false;
@@ -146,7 +142,7 @@ void do_debug(char *cmd)
}
// don't debug any function call, e.g. from an expression mapping
- n = debug_break_level;
+ int n = debug_break_level;
debug_break_level = -1;
xfree(cmdline);
@@ -236,7 +232,7 @@ void do_debug(char *cmd)
}
if (last_cmd != 0) {
- // Execute debug command: decided where to break next and return.
+ // Execute debug command: decide where to break next and return.
switch (last_cmd) {
case CMD_CONT:
debug_break_level = -1;
@@ -346,14 +342,14 @@ static void do_checkbacktracelevel(void)
{
if (debug_backtrace_level < 0) {
debug_backtrace_level = 0;
- msg(_("frame is zero"));
+ msg(_("frame is zero"), 0);
} else {
char *sname = estack_sfile(ESTACK_NONE);
int max = get_maxbacktrace_level(sname);
if (debug_backtrace_level > max) {
debug_backtrace_level = max;
- smsg(_("frame at highest level: %d"), max);
+ smsg(0, _("frame at highest level: %d"), max);
}
xfree(sname);
}
@@ -372,9 +368,9 @@ static void do_showbacktrace(char *cmd)
*next = NUL;
}
if (i == max - debug_backtrace_level) {
- smsg("->%d %s", max - i, cur);
+ smsg(0, "->%d %s", max - i, cur);
} else {
- smsg(" %d %s", max - i, cur);
+ smsg(0, " %d %s", max - i, cur);
}
i++;
if (next == NULL) {
@@ -387,9 +383,9 @@ static void do_showbacktrace(char *cmd)
}
if (SOURCING_LNUM != 0) {
- smsg(_("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
+ smsg(0, _("line %" PRId64 ": %s"), (int64_t)SOURCING_LNUM, cmd);
} else {
- smsg(_("cmd: %s"), cmd);
+ smsg(0, _("cmd: %s"), cmd);
}
}
@@ -433,7 +429,7 @@ void dbg_check_breakpoint(exarg_T *eap)
} else {
p = "";
}
- smsg(_("Breakpoint in \"%s%s\" line %" PRId64),
+ smsg(0, _("Breakpoint in \"%s%s\" line %" PRId64),
p,
debug_breakpoint_name + (*p == NUL ? 0 : 3),
(int64_t)debug_breakpoint_lnum);
@@ -481,6 +477,7 @@ static garray_T dbg_breakp = { 0, 0, sizeof(struct debuggy), 4, NULL };
#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx])
#define DEBUGGY(gap, idx) (((struct debuggy *)(gap)->ga_data)[idx])
static int last_breakp = 0; // nr of last defined breakpoint
+static bool has_expr_breakpoint = false;
// Profiling uses file and func names similar to breakpoints.
static garray_T prof_ga = { 0, 0, sizeof(struct debuggy), 4, NULL };
@@ -495,7 +492,7 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp)
{
// Disable error messages, a bad expression would make Vim unusable.
emsg_off++;
- typval_T *const tv = eval_expr(bp->dbg_name);
+ typval_T *const tv = eval_expr(bp->dbg_name, NULL);
emsg_off--;
return tv;
}
@@ -510,7 +507,6 @@ static typval_T *eval_expr_no_emsg(struct debuggy *const bp)
static int dbg_parsearg(char *arg, garray_T *gap)
{
char *p = arg;
- char *q;
bool here = false;
ga_grow(gap, 1);
@@ -556,7 +552,7 @@ static int dbg_parsearg(char *arg, garray_T *gap)
}
if (bp->dbg_type == DBG_FUNC) {
- bp->dbg_name = xstrdup(p);
+ bp->dbg_name = xstrdup(strncmp(p, "g:", 2) == 0 ? p + 2 : p);
} else if (here) {
bp->dbg_name = xstrdup(curbuf->b_ffname);
} else if (bp->dbg_type == DBG_EXPR) {
@@ -566,7 +562,7 @@ static int dbg_parsearg(char *arg, garray_T *gap)
// Expand the file name in the same way as do_source(). This means
// doing it twice, so that $DIR/file gets expanded when $DIR is
// "~/dir".
- q = expand_env_save(p);
+ char *q = expand_env_save(p);
if (q == NULL) {
return FAIL;
}
@@ -626,6 +622,9 @@ void ex_breakadd(exarg_T *eap)
// DBG_EXPR
DEBUGGY(gap, gap->ga_len++).dbg_nr = ++last_breakp;
debug_tick++;
+ if (gap == &dbg_breakp) {
+ has_expr_breakpoint = true;
+ }
}
}
@@ -639,10 +638,20 @@ void ex_debuggreedy(exarg_T *eap)
}
}
+static void update_has_expr_breakpoint(void)
+{
+ has_expr_breakpoint = false;
+ for (int i = 0; i < dbg_breakp.ga_len; i++) {
+ if (BREAKP(i).dbg_type == DBG_EXPR) {
+ has_expr_breakpoint = true;
+ break;
+ }
+ }
+}
+
/// ":breakdel" and ":profdel".
void ex_breakdel(exarg_T *eap)
{
- struct debuggy *bp, *bpi;
int todel = -1;
bool del_all = false;
linenr_T best_lnum = 0;
@@ -669,9 +678,9 @@ void ex_breakdel(exarg_T *eap)
if (dbg_parsearg(eap->arg, gap) == FAIL) {
return;
}
- bp = &DEBUGGY(gap, gap->ga_len);
+ struct debuggy *bp = &DEBUGGY(gap, gap->ga_len);
for (int i = 0; i < gap->ga_len; i++) {
- bpi = &DEBUGGY(gap, i);
+ struct debuggy *bpi = &DEBUGGY(gap, i);
if (bp->dbg_type == bpi->dbg_type
&& strcmp(bp->dbg_name, bpi->dbg_name) == 0
&& (bp->dbg_lnum == bpi->dbg_lnum
@@ -714,29 +723,32 @@ void ex_breakdel(exarg_T *eap)
if (GA_EMPTY(gap)) {
ga_clear(gap);
}
+ if (gap == &dbg_breakp) {
+ update_has_expr_breakpoint();
+ }
}
/// ":breaklist".
void ex_breaklist(exarg_T *eap)
{
if (GA_EMPTY(&dbg_breakp)) {
- msg(_("No breakpoints defined"));
+ msg(_("No breakpoints defined"), 0);
return;
}
for (int i = 0; i < dbg_breakp.ga_len; i++) {
struct debuggy *bp = &BREAKP(i);
if (bp->dbg_type == DBG_FILE) {
- home_replace(NULL, bp->dbg_name, (char *)NameBuff, MAXPATHL, true);
+ home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true);
}
if (bp->dbg_type != DBG_EXPR) {
- smsg(_("%3d %s %s line %" PRId64),
+ smsg(0, _("%3d %s %s line %" PRId64),
bp->dbg_nr,
bp->dbg_type == DBG_FUNC ? "func" : "file",
bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff,
(int64_t)bp->dbg_lnum);
} else {
- smsg(_("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
+ smsg(0, _("%3d expr %s"), bp->dbg_nr, bp->dbg_name);
}
}
}
@@ -759,8 +771,8 @@ linenr_T dbg_find_breakpoint(bool file, char *fname, linenr_T after)
/// @returns true if profiling is on for a function or sourced file.
bool has_profiling(bool file, char *fname, bool *fp)
{
- return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp)
- != (linenr_T)0;
+ return debuggy_find(file, fname, 0, &prof_ga, fp)
+ != 0;
}
/// Common code for dbg_find_breakpoint() and has_profiling().
@@ -779,7 +791,7 @@ static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *g
// Return quickly when there are no breakpoints.
if (GA_EMPTY(gap)) {
- return (linenr_T)0;
+ return 0;
}
// Replace K_SNR in function name with "<SNR>".
@@ -802,7 +814,7 @@ static linenr_T debuggy_find(bool file, char *fname, linenr_T after, garray_T *g
// while matching should abort it.
prev_got_int = got_int;
got_int = false;
- if (vim_regexec_prog(&bp->dbg_prog, false, name, (colnr_T)0)) {
+ if (vim_regexec_prog(&bp->dbg_prog, false, name, 0)) {
lnum = bp->dbg_lnum;
if (fp != NULL) {
*fp = bp->dbg_forceit;
diff --git a/src/nvim/debugger.h b/src/nvim/debugger.h
index 1f1139b3bc..c4caeffd66 100644
--- a/src/nvim/debugger.h
+++ b/src/nvim/debugger.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_DEBUGGER_H
-#define NVIM_DEBUGGER_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "debugger.h.generated.h"
#endif
-#endif // NVIM_DEBUGGER_H
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 63c55ec602..11204a1b31 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -1,23 +1,40 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/buffer.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
-#include "nvim/sign_defs.h"
+#include "nvim/move.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.c.generated.h"
#endif
+// TODO(bfredl): These should maybe be per-buffer, so that all resources
+// associated with a buffer can be freed when the buffer is unloaded.
+kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE;
+uint32_t decor_freelist = UINT32_MAX;
+
+// Decorations might be requested to be deleted in a callback in the middle of redrawing.
+// In this case, there might still be live references to the memory allocated for the decoration.
+// Keep a "to free" list which can be safely processed when redrawing is done.
+DecorVirtText *to_free_virt = NULL;
+uint32_t to_free_sh = UINT32_MAX;
+
/// Add highlighting to a buffer, bounded by two cursor positions,
/// with an offset.
///
@@ -36,8 +53,8 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
{
colnr_T hl_start = 0;
colnr_T hl_end = 0;
- Decoration decor = DECORATION_INIT;
- decor.hl_id = hl_id;
+ DecorInline decor = DECOR_INLINE_INIT;
+ decor.data.hl.hl_id = hl_id;
// TODO(bfredl): if decoration had blocky mode, we could avoid this loop
for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) {
@@ -62,68 +79,265 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
hl_start = pos_start.col + offset;
hl_end = pos_end.col + offset;
}
+
extmark_set(buf, (uint32_t)src_id, NULL,
(int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end,
- &decor, true, false, kExtmarkNoUndo);
+ decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL);
}
}
-void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
+void decor_redraw(buf_T *buf, int row1, int row2, DecorInline decor)
{
if (row2 >= row1) {
- if (!decor
- || decor->hl_id
- || decor_has_sign(decor)
- || decor->conceal
- || decor->spell != kNone) {
- redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ }
+
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ redraw_buf_line_later(buf, row1 + 1 + ((vt->flags & kVTLinesAbove) ? 0 : 1), true);
+ changed_line_display_buf(buf);
+ } else {
+ if (vt->pos == kVPosInline) {
+ changed_line_display_buf(buf);
+ }
+ }
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ decor_redraw_sh(buf, row1, row2, *sh);
+ idx = sh->next;
}
+ } else {
+ decor_redraw_sh(buf, row1, row2, decor_sh_from_inline(decor.data.hl));
}
+}
- if (decor && decor_virt_pos(*decor)) {
+void decor_redraw_sh(buf_T *buf, int row1, int row2, DecorSignHighlight sh)
+{
+ if (sh.hl_id || (sh.flags & (kSHIsSign|kSHSpellOn|kSHSpellOff))) {
+ if (row2 >= row1) {
+ redraw_buf_range_later(buf, row1 + 1, row2 + 1);
+ }
+ }
+ if (sh.flags & kSHUIWatched) {
redraw_buf_line_later(buf, row1 + 1, false);
}
+}
- if (decor && kv_size(decor->virt_lines)) {
- redraw_buf_line_later(buf, row1 + 1 + (decor->virt_lines_above?0:1), true);
+uint32_t decor_put_sh(DecorSignHighlight item)
+{
+ if (decor_freelist != UINT32_MAX) {
+ uint32_t pos = decor_freelist;
+ decor_freelist = kv_A(decor_items, decor_freelist).next;
+ kv_A(decor_items, pos) = item;
+ return pos;
+ } else {
+ uint32_t pos = (uint32_t)kv_size(decor_items);
+ kv_push(decor_items, item);
+ return pos;
}
}
-void decor_remove(buf_T *buf, int row, int row2, Decoration *decor)
+DecorVirtText *decor_put_vt(DecorVirtText vt, DecorVirtText *next)
+{
+ DecorVirtText *decor_alloc = xmalloc(sizeof *decor_alloc);
+ *decor_alloc = vt;
+ decor_alloc->next = next;
+ return decor_alloc;
+}
+
+DecorSignHighlight decor_sh_from_inline(DecorHighlightInline item)
+{
+ // TODO(bfredl): Eventually simple signs will be inlinable as well
+ assert(!(item.flags & kSHIsSign));
+ DecorSignHighlight conv = {
+ .flags = item.flags,
+ .priority = item.priority,
+ .text.sc[0] = item.conceal_char,
+ .hl_id = item.hl_id,
+ .number_hl_id = 0,
+ .line_hl_id = 0,
+ .cursorline_hl_id = 0,
+ .next = DECOR_ID_INVALID,
+ };
+
+ return conv;
+}
+
+void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2)
+{
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ buf_put_decor_virt(buf, vt);
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ buf_put_decor_sh(buf, sh, row, row2);
+ idx = sh->next;
+ }
+ }
+}
+
+void buf_put_decor_virt(buf_T *buf, DecorVirtText *vt)
+{
+ if (vt->flags &kVTIsLines) {
+ buf->b_virt_line_blocks++;
+ } else {
+ if (vt->pos == kVPosInline) {
+ buf->b_virt_text_inline++;
+ }
+ }
+ if (vt->next) {
+ buf_put_decor_virt(buf, vt->next);
+ }
+}
+
+static int sign_add_id = 0;
+void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row, int row2)
+{
+ if (sh->flags & kSHIsSign) {
+ sh->sign_add_id = sign_add_id++;
+ buf->b_signs++;
+ if (sh->text.ptr) {
+ buf->b_signs_with_text++;
+ buf_signcols_add_check(buf, row + 1, row2 + 1);
+ }
+ }
+}
+
+void buf_decor_remove(buf_T *buf, int row, int row2, DecorInline decor, bool free)
{
decor_redraw(buf, row, row2, decor);
- if (decor) {
- if (kv_size(decor->virt_lines)) {
- assert(buf->b_virt_line_blocks > 0);
- buf->b_virt_line_blocks--;
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ buf_remove_decor_virt(buf, vt);
+ vt = vt->next;
}
- if (decor_has_sign(decor)) {
- assert(buf->b_signs > 0);
- buf->b_signs--;
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ buf_remove_decor_sh(buf, row, row2, sh);
+ idx = sh->next;
}
- if (row2 >= row && decor->sign_text) {
- buf_signcols_del_check(buf, row + 1, row2 + 1);
+ if (free) {
+ decor_free(decor);
}
}
- decor_free(decor);
}
-void decor_free(Decoration *decor)
+void buf_remove_decor_virt(buf_T *buf, DecorVirtText *vt)
{
- if (decor) {
- clear_virttext(&decor->virt_text);
- for (size_t i = 0; i < kv_size(decor->virt_lines); i++) {
- clear_virttext(&kv_A(decor->virt_lines, i).line);
+ if (vt->flags &kVTIsLines) {
+ assert(buf->b_virt_line_blocks > 0);
+ buf->b_virt_line_blocks--;
+ } else {
+ if (vt->pos == kVPosInline) {
+ assert(buf->b_virt_text_inline > 0);
+ buf->b_virt_text_inline--;
+ }
+ }
+}
+
+void buf_remove_decor_sh(buf_T *buf, int row, int row2, DecorSignHighlight *sh)
+{
+ if (sh->flags & kSHIsSign) {
+ assert(buf->b_signs > 0);
+ buf->b_signs--;
+ if (sh->text.ptr) {
+ assert(buf->b_signs_with_text > 0);
+ buf->b_signs_with_text--;
+ if (row2 >= row) {
+ buf_signcols_del_check(buf, row + 1, row2 + 1);
+ }
+ }
+ }
+}
+
+void decor_free(DecorInline decor)
+{
+ if (!decor.ext) {
+ return;
+ }
+ DecorVirtText *vt = decor.data.ext.vt;
+ uint32_t idx = decor.data.ext.sh_idx;
+
+ if (decor_state.running_decor_provider) {
+ while (vt) {
+ if (vt->next == NULL) {
+ vt->next = to_free_virt;
+ to_free_virt = decor.data.ext.vt;
+ break;
+ }
+ vt = vt->next;
+ }
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->next == DECOR_ID_INVALID) {
+ sh->next = to_free_sh;
+ to_free_sh = decor.data.ext.sh_idx;
+ break;
+ }
+ idx = sh->next;
+ }
+ } else {
+ // safe to delete right now
+ decor_free_inner(vt, idx);
+ }
+}
+
+void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
+{
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ clear_virtlines(&vt->data.virt_lines);
+ } else {
+ clear_virttext(&vt->data.virt_text);
+ }
+ DecorVirtText *tofree = vt;
+ vt = vt->next;
+ xfree(tofree);
+ }
+
+ uint32_t idx = first_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->flags & kSHIsSign) {
+ xfree(sh->text.ptr);
+ }
+ if (sh->flags & kSHIsSign) {
+ xfree(sh->sign_name);
}
- kv_destroy(decor->virt_lines);
- xfree(decor->sign_text);
- xfree(decor);
+ sh->flags = 0;
+ if (sh->next == DECOR_ID_INVALID) {
+ sh->next = decor_freelist;
+ decor_freelist = first_idx;
+ break;
+ }
+ idx = sh->next;
}
}
+void decor_check_to_be_deleted(void)
+{
+ assert(!decor_state.running_decor_provider);
+ decor_free_inner(to_free_virt, to_free_sh);
+ to_free_virt = NULL;
+ to_free_sh = DECOR_ID_INVALID;
+}
+
void decor_state_free(DecorState *state)
{
- xfree(state->active.items);
+ kv_destroy(state->active);
}
void clear_virttext(VirtText *text)
@@ -135,20 +349,59 @@ void clear_virttext(VirtText *text)
*text = (VirtText)KV_INITIAL_VALUE;
}
-Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
+void clear_virtlines(VirtLines *lines)
+{
+ for (size_t i = 0; i < kv_size(*lines); i++) {
+ clear_virttext(&kv_A(*lines, i).line);
+ }
+ kv_destroy(*lines);
+ *lines = (VirtLines)KV_INITIAL_VALUE;
+}
+
+void decor_check_invalid_glyphs(void)
+{
+ for (size_t i = 0; i < kv_size(decor_items); i++) {
+ DecorSignHighlight *it = &kv_A(decor_items, i);
+ if ((it->flags & kSHConceal) && schar_high(it->text.sc[0])) {
+ it->text.sc[0] = schar_from_char(schar_get_first_codepoint(it->text.sc[0]));
+ }
+ }
+}
+
+/// Get the next chunk of a virtual text item.
+///
+/// @param[in] vt The virtual text item
+/// @param[in,out] pos Position in the virtual text item
+/// @param[in,out] attr Highlight attribute
+///
+/// @return The text of the chunk, or NULL if there are no more chunks
+char *next_virt_text_chunk(VirtText vt, size_t *pos, int *attr)
+{
+ char *text = NULL;
+ for (; text == NULL && *pos < kv_size(vt); (*pos)++) {
+ text = kv_A(vt, *pos).text;
+ int hl_id = kv_A(vt, *pos).hl_id;
+ *attr = hl_combine_attr(*attr, hl_id > 0 ? syn_id2attr(hl_id) : 0);
+ }
+ return text;
+}
+
+DecorVirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
{
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, row, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row > row) {
break;
- } else if (marktree_decor_level(mark) < kDecorLevelVisible) {
+ } else if (mt_invalid(mark) || !(mark.flags & MT_FLAG_DECOR_EXT)) {
goto next_mark;
}
- Decoration *decor = mark.decor_full;
- if ((ns_id == 0 || ns_id == mark.ns)
- && decor && kv_size(decor->virt_text)) {
+ DecorVirtText *decor = mark.decor_data.ext.vt;
+ while (decor && (decor->flags & kVTIsLines)) {
+ decor = decor->next;
+ }
+ if ((ns_id == 0 || ns_id == mark.ns) && decor) {
return decor;
}
next_mark:
@@ -157,115 +410,107 @@ next_mark:
return NULL;
}
-bool decor_redraw_reset(buf_T *buf, DecorState *state)
+bool decor_redraw_reset(win_T *wp, DecorState *state)
{
state->row = -1;
- state->buf = buf;
+ state->win = wp;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
- if (item.virt_text_owned) {
- clear_virttext(&item.decor.virt_text);
+ if (item.owned && item.kind == kDecorKindVirtText) {
+ clear_virttext(&item.data.vt->data.virt_text);
+ xfree(item.data.vt);
}
}
kv_size(state->active) = 0;
- return buf->b_marktree->n_keys;
+ return wp->w_buffer->b_marktree->n_keys;
}
-Decoration get_decor(mtkey_t mark)
+/// @return true if decor has a virtual position (virtual text or ui_watched)
+bool decor_virt_pos(const DecorRange *decor)
{
- if (mark.decor_full) {
- return *mark.decor_full;
- }
- Decoration fake = DECORATION_INIT;
- fake.hl_id = mark.hl_id;
- fake.priority = mark.priority;
- fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL);
- return fake;
+ return (decor->kind == kDecorKindVirtText || decor->kind == kDecorKindUIWatched);
}
-/// @return true if decor has a virtual position (virtual text or ui_watched)
-static bool decor_virt_pos(Decoration decor)
+VirtTextPos decor_virt_pos_kind(const DecorRange *decor)
{
- return kv_size(decor.virt_text) || decor.ui_watched;
+ if (decor->kind == kDecorKindVirtText) {
+ return decor->data.vt->pos;
+ }
+ if (decor->kind == kDecorKindUIWatched) {
+ return decor->data.ui.pos;
+ }
+ return kVPosEndOfLine; // not used; return whatever
}
-bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
+bool decor_redraw_start(win_T *wp, int top_row, DecorState *state)
{
+ buf_T *buf = wp->w_buffer;
state->top_row = top_row;
- marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
- if (!state->itr->node) {
+ if (!marktree_itr_get_overlap(buf->b_marktree, top_row, 0, state->itr)) {
return false;
}
- marktree_itr_rewind(buf->b_marktree, state->itr);
- while (true) {
- mtkey_t mark = marktree_itr_current(state->itr);
- if (mark.pos.row < 0) { // || mark.row > end_row
- break;
- }
- if ((mark.pos.row < top_row && mt_end(mark))
- || marktree_decor_level(mark) < kDecorLevelVisible) {
- goto next_mark;
- }
+ MTPair pair;
- Decoration decor = get_decor(mark);
-
- mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
- // Exclude start marks if the end mark position is above the top row
- // Exclude end marks if we have already added the start mark
- if ((mt_start(mark) && altpos.row < top_row && !decor_virt_pos(decor))
- || (mt_end(mark) && altpos.row >= top_row)) {
- goto next_mark;
+ while (marktree_itr_step_overlap(buf->b_marktree, state->itr, &pair)) {
+ MTKey m = pair.start;
+ if (mt_invalid(m) || !mt_decor_any(m)) {
+ continue;
}
- if (mt_end(mark)) {
- decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col,
- &decor, false, mark.ns, mark.id);
- } else {
- if (altpos.row == -1) {
- altpos.row = mark.pos.row;
- altpos.col = mark.pos.col;
- }
- decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col,
- &decor, false, mark.ns, mark.id);
- }
-
-next_mark:
- if (marktree_itr_node_done(state->itr)) {
- marktree_itr_next(buf->b_marktree, state->itr);
- break;
- }
- marktree_itr_next(buf->b_marktree, state->itr);
+ decor_range_add_from_inline(state, pair.start.pos.row, pair.start.pos.col, pair.end_pos.row,
+ pair.end_pos.col,
+ mt_decor(m), false, m.ns, m.id);
}
return true; // TODO(bfredl): check if available in the region
}
-bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
+bool decor_redraw_line(win_T *wp, int row, DecorState *state)
{
if (state->row == -1) {
- decor_redraw_start(buf, row, state);
+ decor_redraw_start(wp, row, state);
}
state->row = row;
state->col_until = -1;
state->eol_col = -1;
- return true; // TODO(bfredl): be more precise
+
+ if (kv_size(state->active)) {
+ return true;
+ }
+
+ MTKey k = marktree_itr_current(state->itr);
+ return (k.pos.row >= 0 && k.pos.row <= row);
}
-static void decor_add(DecorState *state, int start_row, int start_col, int end_row, int end_col,
- Decoration *decor, bool owned, uint64_t ns_id, uint64_t mark_id)
+static void decor_range_add_from_inline(DecorState *state, int start_row, int start_col,
+ int end_row, int end_col, DecorInline decor, bool owned,
+ uint32_t ns, uint32_t mark_id)
{
- int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
-
- DecorRange range = { start_row, start_col, end_row, end_col,
- *decor, attr_id,
- kv_size(decor->virt_text) && owned, -1, ns_id, mark_id };
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ decor_range_add_virt(state, start_row, start_col, end_row, end_col, vt, owned);
+ vt = vt->next;
+ }
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ decor_range_add_sh(state, start_row, start_col, end_row, end_col, sh, owned, ns, mark_id);
+ idx = sh->next;
+ }
+ } else {
+ DecorSignHighlight sh = decor_sh_from_inline(decor.data.hl);
+ decor_range_add_sh(state, start_row, start_col, end_row, end_col, &sh, owned, ns, mark_id);
+ }
+}
+static void decor_range_insert(DecorState *state, DecorRange range)
+{
kv_pushp(state->active);
size_t index;
for (index = kv_size(state->active) - 1; index > 0; index--) {
DecorRange item = kv_A(state->active, index - 1);
- if (item.decor.priority <= range.decor.priority) {
+ if (item.priority <= range.priority) {
break;
}
kv_A(state->active, index) = kv_A(state->active, index - 1);
@@ -273,8 +518,82 @@ static void decor_add(DecorState *state, int start_row, int start_col, int end_r
kv_A(state->active, index) = range;
}
-int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *state)
+void decor_range_add_virt(DecorState *state, int start_row, int start_col, int end_row, int end_col,
+ DecorVirtText *vt, bool owned)
+{
+ bool is_lines = vt->flags & kVTIsLines;
+ DecorRange range = {
+ .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col,
+ .kind = is_lines ? kDecorKindVirtLines : kDecorKindVirtText,
+ .data.vt = vt,
+ .attr_id = 0,
+ .owned = owned,
+ .priority = vt->priority,
+ .draw_col = -10,
+ };
+ decor_range_insert(state, range);
+}
+
+void decor_range_add_sh(DecorState *state, int start_row, int start_col, int end_row, int end_col,
+ DecorSignHighlight *sh, bool owned, uint32_t ns, uint32_t mark_id)
+{
+ if (sh->flags & kSHIsSign) {
+ return;
+ }
+
+ DecorRange range = {
+ .start_row = start_row, .start_col = start_col, .end_row = end_row, .end_col = end_col,
+ .kind = kDecorKindHighlight,
+ .data.sh = *sh,
+ .attr_id = 0,
+ .owned = owned,
+ .priority = sh->priority,
+ .draw_col = -10,
+ };
+
+ if (sh->hl_id || (sh->flags & (kSHConceal | kSHSpellOn | kSHSpellOff))) {
+ if (sh->hl_id) {
+ range.attr_id = syn_id2attr(sh->hl_id);
+ }
+ decor_range_insert(state, range);
+ }
+
+ if (sh->flags & (kSHUIWatched)) {
+ range.kind = kDecorKindUIWatched;
+ range.data.ui.ns_id = ns;
+ range.data.ui.mark_id = mark_id;
+ range.data.ui.pos = (sh->flags & kSHUIWatchedOverlay) ? kVPosOverlay : kVPosEndOfLine;
+ decor_range_insert(state, range);
+ }
+}
+
+/// Initialize the draw_col of a newly-added virtual text item.
+static void decor_init_draw_col(int win_col, bool hidden, DecorRange *item)
{
+ DecorVirtText *vt = item->kind == kDecorKindVirtText ? item->data.vt : NULL;
+ VirtTextPos pos = decor_virt_pos_kind(item);
+ if (win_col < 0 && pos != kVPosInline) {
+ item->draw_col = win_col;
+ } else if (pos == kVPosOverlay) {
+ item->draw_col = (vt && (vt->flags & kVTHide) && hidden) ? INT_MIN : win_col;
+ } else {
+ item->draw_col = -1;
+ }
+}
+
+void decor_recheck_draw_col(int win_col, bool hidden, DecorState *state)
+{
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->draw_col == -3) {
+ decor_init_draw_col(win_col, hidden, item);
+ }
+ }
+}
+
+int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *state)
+{
+ buf_T *buf = wp->w_buffer;
if (col <= state->col_until) {
return state->current;
}
@@ -282,7 +601,7 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
while (true) {
// TODO(bfredl): check duplicate entry in "intersection"
// branch
- mtkey_t mark = marktree_itr_current(state->itr);
+ MTKey mark = marktree_itr_current(state->itr);
if (mark.pos.row < 0 || mark.pos.row > state->row) {
break;
} else if (mark.pos.row == state->row && mark.pos.col > col) {
@@ -290,21 +609,17 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState *
break;
}
- if (mt_end(mark)
- || marktree_decor_level(mark) < kDecorLevelVisible) {
+ if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark)) {
goto next_mark;
}
- Decoration decor = get_decor(mark);
-
- mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
+ MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
if (endpos.row == -1) {
endpos = mark.pos;
}
- decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
- &decor, false, mark.ns, mark.id);
+ decor_range_add_from_inline(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col,
+ mt_decor(mark), false, mark.ns, mark.id);
next_mark:
marktree_itr_next(buf->b_marktree, state->itr);
@@ -312,8 +627,8 @@ next_mark:
int attr = 0;
size_t j = 0;
- bool conceal = 0;
- int conceal_char = 0;
+ int conceal = 0;
+ schar_T conceal_char = 0;
int conceal_attr = 0;
TriState spell = kNone;
@@ -322,7 +637,7 @@ next_mark:
bool active = false, keep = true;
if (item.end_row < state->row
|| (item.end_row == state->row && item.end_col <= col)) {
- if (!(item.start_row >= state->row && decor_virt_pos(item.decor))) {
+ if (!(item.start_row >= state->row && decor_virt_pos(&item))) {
keep = false;
}
} else {
@@ -341,26 +656,32 @@ next_mark:
if (active && item.attr_id > 0) {
attr = hl_combine_attr(attr, item.attr_id);
}
- if (active && item.decor.conceal) {
- conceal = true;
- if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) {
- conceal_char = item.decor.conceal_char;
+ if (active && item.kind == kDecorKindHighlight && (item.data.sh.flags & kSHConceal)) {
+ conceal = 1;
+ if (item.start_row == state->row && item.start_col == col) {
+ DecorSignHighlight *sh = &item.data.sh;
+ conceal = 2;
+ conceal_char = sh->text.sc[0];
state->col_until = MIN(state->col_until, item.start_col);
conceal_attr = item.attr_id;
}
}
- if (active && item.decor.spell != kNone) {
- spell = item.decor.spell;
+ if (active && item.kind == kDecorKindHighlight) {
+ if (item.data.sh.flags & kSHSpellOn) {
+ spell = kTrue;
+ } else if (item.data.sh.flags & kSHSpellOff) {
+ spell = kFalse;
+ }
}
- if ((item.start_row == state->row && item.start_col <= col)
- && decor_virt_pos(item.decor)
- && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) {
- item.win_col = (item.decor.virt_text_hide && hidden) ? -2 : win_col;
+ if (item.start_row == state->row && item.start_col <= col
+ && decor_virt_pos(&item) && item.draw_col == -10) {
+ decor_init_draw_col(win_col, hidden, &item);
}
if (keep) {
kv_A(state->active, j++) = item;
- } else if (item.virt_text_owned) {
- clear_virttext(&item.decor.virt_text);
+ } else if (item.owned && item.kind == kDecorKindVirtText) {
+ clear_virttext(&item.data.vt->data.virt_text);
+ xfree(item.data.vt);
}
}
kv_size(state->active) = j;
@@ -372,183 +693,172 @@ next_mark:
return attr;
}
-void decor_redraw_signs(buf_T *buf, int row, int *num_signs, SignTextAttrs sattrs[],
- HlPriAttr *num_attrs, HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
+typedef struct {
+ DecorSignHighlight *sh;
+ uint32_t id;
+} SignItem;
+
+int sign_item_cmp(const void *p1, const void *p2)
+{
+ const SignItem *s1 = (SignItem *)p1;
+ const SignItem *s2 = (SignItem *)p2;
+ int n = s2->sh->priority - s1->sh->priority;
+
+ return n ? n : (n = (int)(s2->id - s1->id))
+ ? n : (s2->sh->sign_add_id - s1->sh->sign_add_id);
+}
+
+/// Return the sign attributes on the currently refreshed row.
+///
+/// @param[out] sattrs Output array for sign text and texthl id
+/// @param[out] line_attr Highest priority linehl id
+/// @param[out] cul_attr Highest priority culhl id
+/// @param[out] num_attr Highest priority numhl id
+void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[], int *line_id,
+ int *cul_id, int *num_id)
{
- if (!buf->b_signs) {
+ MarkTreeIter itr[1];
+ if (!buf->b_signs || !marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) {
return;
}
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, row, 0, itr);
+ MTPair pair;
+ int num_text = 0;
+ kvec_t(SignItem) signs = KV_INITIAL_VALUE;
+ // TODO(bfredl): integrate with main decor loop.
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if (!mt_invalid(pair.start) && mt_decor_sign(pair.start)) {
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(pair.start));
+ num_text += (sh->text.ptr != NULL);
+ kv_push(signs, ((SignItem){ sh, pair.start.id }));
+ }
+ }
- while (true) {
- mtkey_t mark = marktree_itr_current(itr);
- if (mark.pos.row < 0 || mark.pos.row > row) {
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row != row) {
break;
}
-
- if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) {
- goto next_mark;
+ if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)) {
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
+ num_text += (sh->text.ptr != NULL);
+ kv_push(signs, ((SignItem){ sh, mark.id }));
}
- Decoration *decor = mark.decor_full;
+ marktree_itr_next(buf->b_marktree, itr);
+ }
- if (!decor || !decor_has_sign(decor)) {
- goto next_mark;
- }
+ if (kv_size(signs)) {
+ int width = wp->w_minscwidth == SCL_NUM ? 1 : wp->w_scwidth;
+ int idx = MIN(width, num_text) - 1;
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(kv_A(signs, 0)), sign_item_cmp);
- if (decor->sign_text) {
- int j;
- for (j = (*num_signs); j > 0; j--) {
- if (sattrs[j - 1].priority >= decor->priority) {
- break;
- }
- sattrs[j] = sattrs[j - 1];
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ DecorSignHighlight *sh = kv_A(signs, i).sh;
+ if (idx >= 0 && sh->text.ptr) {
+ sattrs[idx].text = sh->text.ptr;
+ sattrs[idx--].hl_id = sh->hl_id;
}
- if (j < SIGN_SHOW_MAX) {
- sattrs[j] = (SignTextAttrs) {
- .text = decor->sign_text,
- .hl_attr_id = decor->sign_hl_id == 0 ? 0 : syn_id2attr(decor->sign_hl_id),
- .priority = decor->priority
- };
- (*num_signs)++;
+ if (*num_id == 0) {
+ *num_id = sh->number_hl_id;
}
- }
-
- struct { HlPriAttr *dest; int hl; } cattrs[] = {
- { line_attrs, decor->line_hl_id },
- { num_attrs, decor->number_hl_id },
- { cul_attrs, decor->cursorline_hl_id },
- { NULL, -1 },
- };
- for (int i = 0; cattrs[i].dest; i++) {
- if (cattrs[i].hl != 0 && decor->priority >= cattrs[i].dest->priority) {
- *cattrs[i].dest = (HlPriAttr) {
- .attr_id = syn_id2attr(cattrs[i].hl),
- .priority = decor->priority
- };
+ if (*line_id == 0) {
+ *line_id = sh->line_hl_id;
+ }
+ if (*cul_id == 0) {
+ *cul_id = sh->cursorline_hl_id;
}
}
-
-next_mark:
- marktree_itr_next(buf->b_marktree, itr);
+ kv_destroy(signs);
}
}
-// Get the maximum required amount of sign columns needed between row and
-// end_row.
-int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max)
+DecorSignHighlight *decor_find_sign(DecorInline decor)
{
- int count = 0; // count for the number of signs on a given row
- int count_remove = 0; // how much to decrement count by when iterating marks for a new row
- int signcols = 0; // highest value of count
- int currow = -1; // current row
-
- if (max <= 1 && buf->b_signs >= (size_t)max) {
- return max;
- }
-
- if (buf->b_signs == 0) {
- return 0;
+ if (!decor.ext) {
+ return NULL;
}
-
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, 0, -1, itr);
+ uint32_t decor_id = decor.data.ext.sh_idx;
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
- if (mark.pos.row < 0 || mark.pos.row > end_row) {
- break;
+ if (decor_id == DECOR_ID_INVALID) {
+ return NULL;
}
-
- if ((mark.pos.row < row && mt_end(mark))
- || marktree_decor_level(mark) < kDecorLevelVisible
- || !mark.decor_full) {
- goto next_mark;
- }
-
- Decoration decor = get_decor(mark);
-
- if (!decor.sign_text) {
- goto next_mark;
+ DecorSignHighlight *sh = &kv_A(decor_items, decor_id);
+ if (sh->flags & kSHIsSign) {
+ return sh;
}
+ decor_id = sh->next;
+ }
+}
- if (mark.pos.row > currow) {
- count -= count_remove;
- count_remove = 0;
- currow = mark.pos.row;
+// Increase the signcolumn size and update the sentinel line if necessary for
+// the invalidated range.
+void decor_validate_signcols(buf_T *buf, int max)
+{
+ int signcols = 0; // highest value of count
+ int currow = buf->b_signcols.invalid_top - 1;
+ // TODO(bfredl): only need to use marktree_itr_get_overlap once.
+ // then we can process both start and end events and update state for each row
+ for (; currow < buf->b_signcols.invalid_bot; currow++) {
+ MarkTreeIter itr[1];
+ if (!marktree_itr_get_overlap(buf->b_marktree, currow, 0, itr)) {
+ continue;
}
- if (!mt_paired(mark)) {
- if (mark.pos.row >= row) {
+ int count = 0;
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if (!mt_invalid(pair.start) && (pair.start.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
- if (count > signcols) {
- signcols = count;
- if (signcols >= max) {
- return max;
- }
- }
- count_remove++;
}
- goto next_mark;
}
- mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
-
- if (mt_end(mark)) {
- if (mark.pos.row >= row && altpos.row <= end_row) {
- count_remove++;
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row != currow) {
+ break;
}
- } else {
- if (altpos.row >= row) {
+ if (!mt_invalid(mark) && !mt_end(mark) && (mark.flags & MT_FLAG_DECOR_SIGNTEXT)) {
count++;
- if (count > signcols) {
- signcols = count;
- if (signcols >= max) {
- return max;
- }
- }
}
+ marktree_itr_next(buf->b_marktree, itr);
}
-next_mark:
- marktree_itr_next(buf->b_marktree, itr);
+ if (count > signcols) {
+ if (count >= buf->b_signcols.size) {
+ buf->b_signcols.size = count;
+ buf->b_signcols.sentinel = currow + 1;
+ }
+ if (count >= max) {
+ return;
+ }
+ signcols = count;
+ }
}
-
- return signcols;
}
void decor_redraw_end(DecorState *state)
{
- state->buf = NULL;
+ state->win = NULL;
}
-bool decor_redraw_eol(buf_T *buf, DecorState *state, int *eol_attr, int eol_col)
+bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
{
- decor_redraw_col(buf, MAXCOL, MAXCOL, false, state);
+ decor_redraw_col(wp, MAXCOL, MAXCOL, false, state);
state->eol_col = eol_col;
- bool has_virttext = false;
+ bool has_virt_pos = false;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange item = kv_A(state->active, i);
- if (item.start_row == state->row && decor_virt_pos(item.decor)) {
- has_virttext = true;
+ if (item.start_row == state->row && decor_virt_pos(&item)) {
+ has_virt_pos = true;
}
- if (item.decor.hl_eol && item.start_row <= state->row) {
+ if (item.kind == kDecorKindHighlight
+ && (item.data.sh.flags & kSHHlEol) && item.start_row <= state->row) {
*eol_attr = hl_combine_attr(*eol_attr, item.attr_id);
}
}
- return has_virttext;
-}
-
-void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, Decoration *decor,
- uint64_t ns_id, uint64_t mark_id)
-{
- if (end_row == -1) {
- end_row = start_row;
- end_col = start_col;
- }
- decor_add(&decor_state, start_row, start_col, end_row, end_col, decor, true, ns_id, mark_id);
+ return has_virt_pos;
}
/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet
@@ -561,30 +871,42 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
return 0;
}
- int virt_lines = 0;
- int row = MAX(lnum - 2, 0);
- int end_row = (int)lnum;
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, row, 0, itr);
+ assert(lnum > 0);
bool below_fold = lnum > 1 && hasFoldingWin(wp, lnum - 1, NULL, NULL, true, NULL);
if (has_fold == kNone) {
has_fold = hasFoldingWin(wp, lnum, NULL, NULL, true, NULL);
}
+
+ const int row = lnum - 1;
+ const int start_row = below_fold ? row : MAX(row - 1, 0);
+ const int end_row = has_fold ? row : row + 1;
+ if (start_row >= end_row) {
+ return 0;
+ }
+
+ int virt_lines = 0;
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, start_row, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0 || mark.pos.row >= end_row) {
break;
- } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) {
+ } else if (mt_end(mark) || !(mark.flags & MT_FLAG_DECOR_VIRT_LINES)) {
goto next_mark;
}
- bool above = mark.pos.row > (lnum - 2);
- bool has_fold_cur = above ? has_fold : below_fold;
- Decoration *decor = mark.decor_full;
- if (!has_fold_cur && decor && decor->virt_lines_above == above) {
- virt_lines += (int)kv_size(decor->virt_lines);
- if (lines) {
- kv_splice(*lines, decor->virt_lines);
+ DecorVirtText *vt = mark.decor_data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ bool above = vt->flags & kVTLinesAbove;
+ int draw_row = mark.pos.row + (above ? 0 : 1);
+ if (draw_row == row) {
+ virt_lines += (int)kv_size(vt->data.virt_lines);
+ if (lines) {
+ kv_splice(*lines, vt->data.virt_lines);
+ }
+ }
}
+ vt = vt->next;
}
next_mark:
marktree_itr_next(buf->b_marktree, itr);
@@ -592,3 +914,153 @@ next_mark:
return virt_lines;
}
+
+/// This assumes maximum one entry of each kind, which will not always be the case.
+void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name)
+{
+ DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT;
+ DecorVirtText *virt_text = NULL;
+ DecorVirtText *virt_lines = NULL;
+ int32_t priority = -1; // sentinel value which cannot actually be set
+
+ if (decor.ext) {
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ if (vt->flags & kVTIsLines) {
+ virt_lines = vt;
+ } else {
+ virt_text = vt;
+ }
+ vt = vt->next;
+ }
+
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ if (sh->flags & (kSHIsSign)) {
+ sh_sign = *sh;
+ } else {
+ sh_hl = *sh;
+ }
+ idx = sh->next;
+ }
+ } else {
+ sh_hl = decor_sh_from_inline(decor.data.hl);
+ }
+
+ if (sh_hl.hl_id) {
+ PUT(*dict, "hl_group", hl_group_name(sh_hl.hl_id, hl_name));
+ PUT(*dict, "hl_eol", BOOLEAN_OBJ(sh_hl.flags & kSHHlEol));
+ if (sh_hl.flags & kSHConceal) {
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, sh_hl.text.sc[0]);
+ PUT(*dict, "conceal", CSTR_TO_OBJ(buf));
+ }
+
+ if (sh_hl.flags & kSHSpellOn) {
+ PUT(*dict, "spell", BOOLEAN_OBJ(true));
+ } else if (sh_hl.flags & kSHSpellOff) {
+ PUT(*dict, "spell", BOOLEAN_OBJ(false));
+ }
+
+ priority = sh_hl.priority;
+ }
+
+ if (sh_hl.flags & kSHUIWatched) {
+ PUT(*dict, "ui_watched", BOOLEAN_OBJ(true));
+ }
+
+ if (virt_text) {
+ if (virt_text->hl_mode) {
+ PUT(*dict, "hl_mode", CSTR_TO_OBJ(hl_mode_str[virt_text->hl_mode]));
+ }
+
+ Array chunks = virt_text_to_array(virt_text->data.virt_text, hl_name);
+ PUT(*dict, "virt_text", ARRAY_OBJ(chunks));
+ PUT(*dict, "virt_text_hide", BOOLEAN_OBJ(virt_text->flags & kVTHide));
+ if (virt_text->pos == kVPosWinCol) {
+ PUT(*dict, "virt_text_win_col", INTEGER_OBJ(virt_text->col));
+ }
+ PUT(*dict, "virt_text_pos",
+ CSTR_TO_OBJ(virt_text_pos_str[virt_text->pos]));
+ priority = virt_text->priority;
+ }
+
+ if (virt_lines) {
+ Array all_chunks = ARRAY_DICT_INIT;
+ bool virt_lines_leftcol = false;
+ for (size_t i = 0; i < kv_size(virt_lines->data.virt_lines); i++) {
+ virt_lines_leftcol = kv_A(virt_lines->data.virt_lines, i).left_col;
+ Array chunks = virt_text_to_array(kv_A(virt_lines->data.virt_lines, i).line, hl_name);
+ ADD(all_chunks, ARRAY_OBJ(chunks));
+ }
+ PUT(*dict, "virt_lines", ARRAY_OBJ(all_chunks));
+ PUT(*dict, "virt_lines_above", BOOLEAN_OBJ(virt_lines->flags & kVTLinesAbove));
+ PUT(*dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol));
+ priority = virt_lines->priority;
+ }
+
+ if (sh_sign.flags & kSHIsSign) {
+ if (sh_sign.text.ptr) {
+ PUT(*dict, "sign_text", CSTR_TO_OBJ(sh_sign.text.ptr));
+ }
+
+ if (sh_sign.sign_name) {
+ PUT(*dict, "sign_name", CSTR_TO_OBJ(sh_sign.sign_name));
+ }
+
+ // uncrustify:off
+
+ struct { char *name; const int val; } hls[] = {
+ { "sign_hl_group" , sh_sign.hl_id },
+ { "number_hl_group" , sh_sign.number_hl_id },
+ { "line_hl_group" , sh_sign.line_hl_id },
+ { "cursorline_hl_group", sh_sign.cursorline_hl_id },
+ { NULL, 0 },
+ };
+
+ // uncrustify:on
+
+ for (int j = 0; hls[j].name; j++) {
+ if (hls[j].val) {
+ PUT(*dict, hls[j].name, hl_group_name(hls[j].val, hl_name));
+ }
+ }
+ priority = sh_sign.priority;
+ }
+
+ if (priority != -1) {
+ PUT(*dict, "priority", INTEGER_OBJ(priority));
+ }
+}
+
+uint16_t decor_type_flags(DecorInline decor)
+{
+ if (decor.ext) {
+ uint16_t type_flags = kExtmarkNone;
+ DecorVirtText *vt = decor.data.ext.vt;
+ while (vt) {
+ type_flags |= (vt->flags & kVTIsLines) ? kExtmarkVirtLines : kExtmarkVirtText;
+ vt = vt->next;
+ }
+ uint32_t idx = decor.data.ext.sh_idx;
+ while (idx != DECOR_ID_INVALID) {
+ DecorSignHighlight *sh = &kv_A(decor_items, idx);
+ type_flags |= (sh->flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight;
+ idx = sh->next;
+ }
+ return type_flags;
+ } else {
+ return (decor.data.hl.flags & kSHIsSign) ? kExtmarkSign : kExtmarkHighlight;
+ }
+}
+
+Object hl_group_name(int hl_id, bool hl_name)
+{
+ if (hl_name) {
+ return CSTR_TO_OBJ(syn_id2name(hl_id));
+ } else {
+ return INTEGER_OBJ(hl_id);
+ }
+}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index c9ec8ede7f..f5448c051b 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -1,120 +1,81 @@
-#ifndef NVIM_DECORATION_H
-#define NVIM_DECORATION_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/buffer_defs.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/macros.h"
+#include "nvim/decoration_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/marktree.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
-// actual Decoration data is in extmark_defs.h
+// actual Decor* data is in decoration_defs.h
-typedef uint16_t DecorPriority;
-#define DECOR_PRIORITY_BASE 0x1000
+EXTERN const char *const virt_text_pos_str[] INIT( = { "eol", "overlay", "win_col", "right_align",
+ "inline" });
-typedef enum {
- kVTEndOfLine,
- kVTOverlay,
- kVTWinCol,
- kVTRightAlign,
-} VirtTextPos;
-
-EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" });
+EXTERN const char *const hl_mode_str[] INIT( = { "", "replace", "combine", "blend" });
typedef enum {
- kHlModeUnknown,
- kHlModeReplace,
- kHlModeCombine,
- kHlModeBlend,
-} HlMode;
-
-EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" });
-
-#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
-
-typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
-
-struct Decoration {
- VirtText virt_text;
- VirtLines virt_lines;
-
- int hl_id; // highlight group
- VirtTextPos virt_text_pos;
- HlMode hl_mode;
-
- // TODO(bfredl): at some point turn this into FLAGS
- bool virt_text_hide;
- bool hl_eol;
- bool virt_lines_above;
- bool conceal;
- TriState spell;
- // TODO(bfredl): style, etc
- DecorPriority priority;
- int col; // fixed col value, like win_col
- int virt_text_width; // width of virt_text
- char *sign_text;
- int sign_hl_id;
- int number_hl_id;
- int line_hl_id;
- int cursorline_hl_id;
- // TODO(bfredl): in principle this should be a schar_T, but we
- // probably want some kind of glyph cache for that..
- int conceal_char;
- bool ui_watched; // watched for win_extmark
-};
-#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \
- kHlModeUnknown, false, false, false, false, kNone, \
- DECOR_PRIORITY_BASE, 0, 0, NULL, 0, 0, 0, 0, 0, false }
+ kDecorKindHighlight,
+ kDecorKindSign,
+ kDecorKindVirtText,
+ kDecorKindVirtLines,
+ kDecorKindUIWatched,
+} DecorRangeKind;
typedef struct {
int start_row;
int start_col;
int end_row;
int end_col;
- Decoration decor;
- int attr_id; // cached lookup of decor.hl_id
- bool virt_text_owned;
- int win_col;
- uint64_t ns_id;
- uint64_t mark_id;
+ // next pointers MUST NOT be used, these are separate ranges
+ // vt->next could be pointing to freelist memory at this point
+ union {
+ DecorSignHighlight sh;
+ DecorVirtText *vt;
+ struct {
+ uint32_t ns_id;
+ uint32_t mark_id;
+ VirtTextPos pos;
+ } ui;
+ } data;
+ int attr_id; // cached lookup of inl.hl_id if it was a highlight
+ bool owned; // ephemeral decoration, free memory immediately
+ DecorPriority priority;
+ DecorRangeKind kind;
+ /// Screen column to draw the virtual text.
+ /// When -1, the virtual text may be drawn after deciding where.
+ /// When -3, the virtual text should be drawn on the next screen line.
+ /// When -10, the virtual text has just been added.
+ /// When INT_MIN, the virtual text should no longer be drawn.
+ int draw_col;
} DecorRange;
typedef struct {
MarkTreeIter itr[1];
kvec_t(DecorRange) active;
- buf_T *buf;
+ win_T *win;
int top_row;
int row;
int col_until;
int current;
int eol_col;
- bool conceal;
- int conceal_char;
+ int conceal;
+ schar_T conceal_char;
int conceal_attr;
TriState spell;
-} DecorState;
-EXTERN DecorState decor_state INIT(= { 0 });
+ bool running_decor_provider;
+} DecorState;
-static inline bool decor_has_sign(Decoration *decor)
-{
- return decor->sign_text
- || decor->sign_hl_id
- || decor->number_hl_id
- || decor->line_hl_id
- || decor->cursorline_hl_id;
-}
+EXTERN DecorState decor_state INIT( = { 0 });
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.h.generated.h"
#endif
-
-#endif // NVIM_DECORATION_H
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
new file mode 100644
index 0000000000..dc5d7b9ae4
--- /dev/null
+++ b/src/nvim/decoration_defs.h
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "klib/kvec.h"
+#include "nvim/types_defs.h"
+
+#define DECOR_ID_INVALID UINT32_MAX
+
+typedef struct {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
+
+typedef kvec_t(VirtTextChunk) VirtText;
+#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+
+typedef enum {
+ kVPosEndOfLine,
+ kVPosOverlay,
+ kVPosWinCol,
+ kVPosRightAlign,
+ kVPosInline,
+} VirtTextPos;
+
+typedef kvec_t(struct virt_line { VirtText line; bool left_col; }) VirtLines;
+
+typedef uint16_t DecorPriority;
+#define DECOR_PRIORITY_BASE 0x1000
+
+typedef enum {
+ kHlModeUnknown,
+ kHlModeReplace,
+ kHlModeCombine,
+ kHlModeBlend,
+} HlMode;
+
+enum {
+ kSHIsSign = 1,
+ kSHHlEol = 2,
+ kSHUIWatched = 4,
+ kSHUIWatchedOverlay = 8,
+ kSHSpellOn = 16,
+ kSHSpellOff = 32,
+ kSHConceal = 64,
+};
+
+typedef struct {
+ uint16_t flags;
+ DecorPriority priority;
+ int hl_id;
+ schar_T conceal_char;
+} DecorHighlightInline;
+
+#define DECOR_HIGHLIGHT_INLINE_INIT { 0, DECOR_PRIORITY_BASE, 0, 0 }
+typedef struct {
+ uint16_t flags;
+ DecorPriority priority;
+ int hl_id; // if sign: highlight of sign text
+ // TODO(bfredl): Later signs should use sc[2] as well.
+ union {
+ char *ptr; // sign
+ schar_T sc[2]; // conceal text (only sc[0] used)
+ } text;
+ // NOTE: if more functionality is added to a Highlight these should be overloaded
+ // or restructured
+ char *sign_name;
+ int sign_add_id;
+ int number_hl_id;
+ int line_hl_id;
+ int cursorline_hl_id;
+ uint32_t next;
+} DecorSignHighlight;
+
+#define DECOR_SIGN_HIGHLIGHT_INIT { 0, DECOR_PRIORITY_BASE, 0, { .ptr = NULL }, NULL, 0, 0, 0, 0, \
+ DECOR_ID_INVALID }
+
+enum {
+ kVTIsLines = 1,
+ kVTHide = 2,
+ kVTLinesAbove = 4,
+};
+
+typedef struct DecorVirtText DecorVirtText;
+struct DecorVirtText {
+ uint8_t flags;
+ uint8_t hl_mode;
+ DecorPriority priority;
+ int width; // width of virt_text
+ int col;
+ VirtTextPos pos;
+ // TODO(bfredl): reduce this to one datatype, later
+ union {
+ VirtText virt_text;
+ VirtLines virt_lines;
+ } data;
+ DecorVirtText *next;
+};
+#define DECOR_VIRT_TEXT_INIT { 0, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, kVPosEndOfLine, \
+ { .virt_text = KV_INITIAL_VALUE }, NULL, }
+#define DECOR_VIRT_LINES_INIT { kVTIsLines, kHlModeUnknown, DECOR_PRIORITY_BASE, 0, 0, \
+ kVPosEndOfLine, { .virt_lines = KV_INITIAL_VALUE }, NULL, }
+
+typedef struct {
+ uint32_t sh_idx;
+ DecorVirtText *vt;
+} DecorExt;
+
+// Stored inline in marktree, with MT_FLAG_DECOR_EXT in MTKey.flags
+typedef union {
+ DecorHighlightInline hl;
+ DecorExt ext;
+} DecorInlineData;
+
+// Not stored in the marktree, but used when passing around args
+//
+// Convention: an empty "no decoration" value should always be encoded
+// with ext=false and an unset DecorHighlightInline (no flags, no hl_id)
+typedef struct {
+ bool ext;
+ DecorInlineData data;
+} DecorInline;
+
+// initializes in a valid state for the DecorHighlightInline branch
+#define DECOR_INLINE_INIT { .ext = false, .data.hl = DECOR_HIGHLIGHT_INLINE_INIT }
diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c
index ed21474935..172eb569c9 100644
--- a/src/nvim/decoration_provider.c
+++ b/src/nvim/decoration_provider.c
@@ -1,33 +1,37 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <stdio.h>
+#include <lauxlib.h>
#include <string.h>
#include "klib/kvec.h"
-#include "lauxlib.h"
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/message.h"
+#include "nvim/pos_defs.h"
static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE;
#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
{ ns_id, false, LUA_NOREF, LUA_NOREF, \
LUA_NOREF, LUA_NOREF, LUA_NOREF, \
- LUA_NOREF, -1, false, false }
+ LUA_NOREF, -1, false, false, 0 }
+
+static void decor_provider_error(DecorProvider *provider, const char *name, const char *msg)
+{
+ const char *ns_name = describe_ns(provider->ns_id, "(UNKNOWN PLUGIN)");
+ ELOG("error in provider %s.%s: %s", ns_name, name, msg);
+ msg_schedule_semsg_multiline("Error in decoration provider %s.%s:\n%s", ns_name, name, msg);
+}
-static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args,
- bool default_true, char **perr)
+static bool decor_provider_invoke(DecorProvider *provider, const char *name, LuaRef ref, Array args,
+ bool default_true)
{
Error err = ERROR_INIT;
@@ -39,26 +43,25 @@ static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, Array
if (!ERROR_SET(&err)
&& api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
+ provider->error_count = 0;
return true;
}
if (ERROR_SET(&err)) {
- const char *ns_name = describe_ns(ns_id);
- ELOG("error in provider %s:%s: %s", ns_name, name, err.msg);
- bool verbose_errs = true; // TODO(bfredl):
- if (verbose_errs && perr && *perr == NULL) {
- static char errbuf[IOSIZE];
- snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg);
- *perr = xstrdup(errbuf);
+ decor_provider_error(provider, name, err.msg);
+ provider->error_count++;
+
+ if (provider->error_count >= DP_MAX_ERROR) {
+ provider->active = false;
}
}
+ api_clear_error(&err);
api_free_object(ret);
return false;
}
-void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col,
- char **err)
+void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int end_row, int end_col)
{
for (size_t i = 0; i < kv_size(decor_providers); i++) {
DecorProvider *p = &kv_A(decor_providers, i);
@@ -74,7 +77,7 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
ADD_C(args, INTEGER_OBJ(start_col));
ADD_C(args, INTEGER_OBJ(end_row));
ADD_C(args, INTEGER_OBJ(end_col));
- decor_provider_invoke(p->ns_id, "spell", p->spell_nav, args, true, err);
+ decor_provider_invoke(p, "spell", p->spell_nav, args, true);
}
}
}
@@ -83,7 +86,7 @@ void decor_providers_invoke_spell(win_T *wp, int start_row, int start_col, int e
///
/// @param[out] providers Decoration providers
/// @param[out] err Provider err
-void decor_providers_start(DecorProviders *providers, char **err)
+void decor_providers_start(DecorProviders *providers)
{
kvi_init(*providers);
@@ -97,7 +100,7 @@ void decor_providers_start(DecorProviders *providers, char **err)
if (p->redraw_start != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, INTEGER_OBJ((int)display_tick));
- active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err);
+ active = decor_provider_invoke(p, "start", p->redraw_start, args, true);
} else {
active = true;
}
@@ -116,13 +119,17 @@ void decor_providers_start(DecorProviders *providers, char **err)
/// @param[out] line_providers Enabled line providers to invoke in win_line
/// @param[out] err Provider error
void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
- DecorProviders *line_providers, char **err)
+ DecorProviders *line_providers)
{
kvi_init(*line_providers);
+ // this might change in the future
+ // then we would need decor_state.running_decor_provider just like "on_line" below
+ assert(kv_size(decor_state.active) == 0);
- linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
- ? wp->w_botline
- : (wp->w_topline + wp->w_height_inner));
+ linenr_T knownmax = MIN(wp->w_buffer->b_ml.ml_line_count,
+ ((wp->w_valid & VALID_BOTLINE)
+ ? wp->w_botline
+ : MAX(wp->w_topline + wp->w_height_inner, wp->w_botline)));
for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k);
@@ -132,8 +139,8 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
// TODO(bfredl): we are not using this, but should be first drawn line?
ADD_C(args, INTEGER_OBJ(wp->w_topline - 1));
- ADD_C(args, INTEGER_OBJ(knownmax));
- if (decor_provider_invoke(p->ns_id, "win", p->redraw_win, args, true, err)) {
+ ADD_C(args, INTEGER_OBJ(knownmax - 1));
+ if (decor_provider_invoke(p, "win", p->redraw_win, args, true)) {
kvi_push(*line_providers, p);
}
}
@@ -147,9 +154,9 @@ void decor_providers_invoke_win(win_T *wp, DecorProviders *providers,
/// @param row Row to invoke line callback for
/// @param[out] has_decor Set when at least one provider invokes a line callback
/// @param[out] err Provider error
-void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor,
- char **err)
+void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor)
{
+ decor_state.running_decor_provider = true;
for (size_t k = 0; k < kv_size(*providers); k++) {
DecorProvider *p = kv_A(*providers, k);
if (p && p->redraw_line != LUA_NOREF) {
@@ -157,7 +164,7 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
ADD_C(args, WINDOW_OBJ(wp->handle));
ADD_C(args, BUFFER_OBJ(wp->w_buffer->handle));
ADD_C(args, INTEGER_OBJ(row));
- if (decor_provider_invoke(p->ns_id, "line", p->redraw_line, args, true, err)) {
+ if (decor_provider_invoke(p, "line", p->redraw_line, args, true)) {
*has_decor = true;
} else {
// return 'false' or error: skip rest of this window
@@ -167,6 +174,7 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
hl_check_ns();
}
}
+ decor_state.running_decor_provider = false;
}
/// For each provider invoke the 'buf' callback for a given buffer.
@@ -174,14 +182,15 @@ void decor_providers_invoke_line(win_T *wp, DecorProviders *providers, int row,
/// @param buf Buffer
/// @param providers Decoration providers
/// @param[out] err Provider error
-void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **err)
+void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers)
{
for (size_t i = 0; i < kv_size(*providers); i++) {
DecorProvider *p = kv_A(*providers, i);
if (p && p->redraw_buf != LUA_NOREF) {
- MAXSIZE_TEMP_ARRAY(args, 1);
+ MAXSIZE_TEMP_ARRAY(args, 2);
ADD_C(args, BUFFER_OBJ(buf->handle));
- decor_provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true, err);
+ ADD_C(args, INTEGER_OBJ((int64_t)display_tick));
+ decor_provider_invoke(p, "buf", p->redraw_buf, args, true);
}
}
}
@@ -191,16 +200,17 @@ void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **er
/// @param providers Decoration providers
/// @param displaytick Display tick
/// @param[out] err Provider error
-void decor_providers_invoke_end(DecorProviders *providers, char **err)
+void decor_providers_invoke_end(DecorProviders *providers)
{
for (size_t i = 0; i < kv_size(*providers); i++) {
DecorProvider *p = kv_A(*providers, i);
if (p && p->active && p->redraw_end != LUA_NOREF) {
MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, INTEGER_OBJ((int)display_tick));
- decor_provider_invoke(p->ns_id, "end", p->redraw_end, args, true, err);
+ decor_provider_invoke(p, "end", p->redraw_end, args, true);
}
}
+ decor_check_to_be_deleted();
}
/// Mark all cached state of per-namespace highlights as invalid. Revalidate
diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h
index b91ddabdfd..e0dd67a6f7 100644
--- a/src/nvim/decoration_provider.h
+++ b/src/nvim/decoration_provider.h
@@ -1,12 +1,14 @@
-#ifndef NVIM_DECORATION_PROVIDER_H
-#define NVIM_DECORATION_PROVIDER_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "klib/kvec.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
+
+#define DP_MAX_ERROR 3
typedef struct {
NS ns_id;
@@ -20,14 +22,14 @@ typedef struct {
LuaRef spell_nav;
int hl_valid;
bool hl_cached;
+
+ uint8_t error_count;
} DecorProvider;
typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders;
-EXTERN bool provider_active INIT(= false);
+EXTERN bool provider_active INIT( = false);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration_provider.h.generated.h"
#endif
-
-#endif // NVIM_DECORATION_PROVIDER_H
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 032de561b3..0b7f6f266b 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file diff.c
///
/// Code for diff'ing two, three or four buffers.
@@ -19,9 +16,10 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -31,9 +29,10 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
-#include "nvim/extmark_defs.h"
+#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -46,17 +45,18 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#include "xdiff/xdiff.h"
@@ -80,7 +80,7 @@ static bool diff_need_update = false; // ex_diffupdate needs to be called
#define ALL_WHITE_DIFF (DIFF_IWHITE | DIFF_IWHITEALL | DIFF_IWHITEEOL)
static int diff_flags = DIFF_INTERNAL | DIFF_FILLER | DIFF_CLOSE_OFF;
-static long diff_algorithm = 0;
+static int diff_algorithm = 0;
static int linematch_lines = 0;
#define LBUFLEN 50 // length of line in diff file
@@ -104,9 +104,9 @@ typedef struct {
// used for recording hunks from xdiff
typedef struct {
linenr_T lnum_orig;
- long count_orig;
+ int count_orig;
linenr_T lnum_new;
- long count_new;
+ int count_new;
} diffhunk_T;
// two diff inputs and one result
@@ -264,24 +264,25 @@ void diff_invalidate(buf_T *buf)
}
}
-/// Called by mark_adjust(): update line numbers in "curbuf".
+/// Called by mark_adjust(): update line numbers in "buf".
///
/// @param line1
/// @param line2
/// @param amount
/// @param amount_after
-void diff_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after)
+void diff_mark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
+ linenr_T amount_after)
{
- // Handle all tab pages that use the current buffer in a diff.
+ // Handle all tab pages that use "buf" in a diff.
FOR_ALL_TABS(tp) {
- int idx = diff_buf_idx_tp(curbuf, tp);
+ int idx = diff_buf_idx_tp(buf, tp);
if (idx != DB_COUNT) {
diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);
}
}
}
-/// Update line numbers in tab page "tp" for "curbuf" with index "idx".
+/// Update line numbers in tab page "tp" for the buffer with index "idx".
///
/// This attempts to update the changes as much as possible:
/// When inserting/deleting lines outside of existing change blocks, create a
@@ -326,7 +327,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
diff_T *dp = tp->tp_first_diff;
linenr_T lnum_deleted = line1; // lnum of remaining deletion
- for (;;) {
+ while (true) {
// If the change is after the previous diff block and before the next
// diff block, thus not touching an existing change, create a new diff
// block. Don't do this when ex_diffgetput() is busy.
@@ -340,8 +341,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
dnext->df_lnum[idx] = line1;
dnext->df_count[idx] = inserted;
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) {
if (dprev == NULL) {
dnext->df_lnum[i] = line1;
@@ -472,8 +472,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
// check if this block touches the previous one, may merge them.
if ((dprev != NULL) && !dp->is_linematched
&& (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) {
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if (tp->tp_diffbuf[i] != NULL) {
dprev->df_count[i] += dp->df_count[i];
}
@@ -586,7 +585,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
linenr_T off_org = 0;
linenr_T off_new = 0;
int dir = FORWARD;
- for (;;) {
+ while (true) {
// Repeat until a line is found which is different or the number of
// lines has become zero.
while (dp->df_count[i_org] > 0) {
@@ -594,9 +593,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
if (dir == BACKWARD) {
off_org = dp->df_count[i_org] - 1;
}
- char *line_org = xstrdup(ml_get_buf(tp->tp_diffbuf[i_org],
- dp->df_lnum[i_org] + off_org,
- false));
+ char *line_org = xstrdup(ml_get_buf(tp->tp_diffbuf[i_org], dp->df_lnum[i_org] + off_org));
int i_new;
for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) {
@@ -614,8 +611,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp)
}
if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new],
- dp->df_lnum[i_new] + off_new,
- false)) != 0) {
+ dp->df_lnum[i_new] + off_new)) != 0) {
break;
}
}
@@ -754,7 +750,7 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
// xdiff requires one big block of memory with all the text.
for (linenr_T lnum = start; lnum <= end; lnum++) {
- len += strlen(ml_get_buf(buf, lnum, false)) + 1;
+ len += strlen(ml_get_buf(buf, lnum)) + 1;
}
char *ptr = try_malloc(len);
if (ptr == NULL) {
@@ -764,18 +760,18 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
buf->b_diff_failed = true;
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Not enough memory to use internal diff for buffer \"%s\""),
+ smsg(0, _("Not enough memory to use internal diff for buffer \"%s\""),
buf->b_fname);
verbose_leave();
}
return FAIL;
}
m->ptr = ptr;
- m->size = (long)len;
+ m->size = (int)len;
len = 0;
for (linenr_T lnum = start; lnum <= end; lnum++) {
- char *s = ml_get_buf(buf, lnum, false);
+ char *s = ml_get_buf(buf, lnum);
if (diff_flags & DIFF_ICASE) {
while (*s != NUL) {
char cbuf[MB_MAXBYTES + 1];
@@ -824,7 +820,7 @@ static int diff_write(buf_T *buf, diffin_T *din)
// so it shouldn't update the '[ and '] marks.
cmdmod.cmod_flags |= CMOD_LOCKMARKS;
int r = buf_write(buf, din->din_fname, NULL,
- (linenr_T)1, buf->b_ml.ml_line_count,
+ 1, buf->b_ml.ml_line_count,
NULL, false, false, false, true);
cmdmod.cmod_flags = save_cmod_flags;
free_string_option(buf->b_p_ff);
@@ -1010,7 +1006,7 @@ static int check_external_diff(diffio_T *diffio)
// May try twice, first with "-a" and then without.
int io_error = false;
TriState ok = kFalse;
- for (;;) {
+ while (true) {
ok = kFalse;
FILE *fd = os_fopen(diffio->dio_orig.din_fname, "w");
@@ -1040,7 +1036,7 @@ static int check_external_diff(diffio_T *diffio)
} else {
char linebuf[LBUFLEN];
- for (;;) {
+ while (true) {
// For normal diff there must be a line that contains
// "1c1". For unified diff "@@ -1 +1 @@".
if (vim_fgets(linebuf, LBUFLEN, fd)) {
@@ -1208,7 +1204,7 @@ void ex_diffpatch(exarg_T *eap)
// Write the current buffer to "tmp_orig".
if (buf_write(curbuf, tmp_orig, NULL,
- (linenr_T)1, curbuf->b_ml.ml_line_count,
+ 1, curbuf->b_ml.ml_line_count,
NULL, false, false, false, true) == FAIL) {
goto theend;
}
@@ -1387,14 +1383,14 @@ void ex_diffthis(exarg_T *eap)
diff_win_options(curwin, true);
}
-static void set_diff_option(win_T *wp, int value)
+static void set_diff_option(win_T *wp, bool value)
{
win_T *old_curwin = curwin;
curwin = wp;
curbuf = curwin->w_buffer;
curbuf->b_ro_locked++;
- set_option_value_give_err("diff", (long)value, NULL, OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(value), OPT_LOCAL);
curbuf->b_ro_locked--;
curwin = old_curwin;
curbuf = curwin->w_buffer;
@@ -1558,7 +1554,7 @@ static bool extract_hunk_internal(diffout_T *dout, diffhunk_T *hunk, int *line_i
// Extract hunk by parsing the diff output from file and calculate the diffstyle.
static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle)
{
- for (;;) {
+ while (true) {
char line[LBUFLEN]; // only need to hold the diff line
if (vim_fgets(line, LBUFLEN, fd)) {
return true; // end of file
@@ -1579,10 +1575,10 @@ static bool extract_hunk(FILE *fd, diffhunk_T *hunk, diffstyle_T *diffstyle)
*diffstyle = DIFF_ED;
} else if ((strncmp(line, "@@ ", 3) == 0)) {
*diffstyle = DIFF_UNIFIED;
- } else if ((strncmp(line, "--- ", 4) == 0) // -V501
- && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501
+ } else if ((strncmp(line, "--- ", 4) == 0)
+ && (vim_fgets(line, LBUFLEN, fd) == 0)
&& (strncmp(line, "+++ ", 4) == 0)
- && (vim_fgets(line, LBUFLEN, fd) == 0) // -V501
+ && (vim_fgets(line, LBUFLEN, fd) == 0)
&& (strncmp(line, "@@ ", 3) == 0)) {
*diffstyle = DIFF_UNIFIED;
} else {
@@ -1745,7 +1741,7 @@ static void diff_read(int idx_orig, int idx_new, diffio_T *dio)
}
}
- for (;;) {
+ while (true) {
diffhunk_T hunk = { 0 };
bool eof = dio->dio_internal
? extract_hunk_internal(dout, &hunk, &line_idx)
@@ -2242,11 +2238,9 @@ static bool diff_equal_entry(diff_T *dp, int idx1, int idx2)
}
for (int i = 0; i < dp->df_count[idx1]; i++) {
- char *line = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx1],
- dp->df_lnum[idx1] + i, false));
+ char *line = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx1], dp->df_lnum[idx1] + i));
- int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2],
- dp->df_lnum[idx2] + i, false));
+ int cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], dp->df_lnum[idx2] + i));
xfree(line);
if (cmp != 0) {
@@ -2382,7 +2376,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]);
if (lnum >= dp->df_lnum[fromidx]) {
- if (diff_flags & DIFF_LINEMATCH) {
+ if (dp->is_linematched) {
calculate_topfill_and_topline(fromidx, toidx, fromwin->w_topline,
fromwin->w_topfill, &towin->w_topfill, &towin->w_topline);
} else {
@@ -2447,7 +2441,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
}
// When w_topline changes need to recompute w_botline and cursor position
- invalidate_botline_win(towin);
+ invalidate_botline(towin);
changed_line_abv_curs_win(towin);
check_topfill(towin, false);
@@ -2464,11 +2458,12 @@ int diffopt_changed(void)
int linematch_lines_new = 0;
int diff_flags_new = 0;
int diff_foldcolumn_new = 2;
- long diff_algorithm_new = 0;
- long diff_indent_heuristic = 0;
+ int diff_algorithm_new = 0;
+ int diff_indent_heuristic = 0;
char *p = p_dip;
while (*p != NUL) {
+ // Note: Keep this in sync with p_dip_values
if (strncmp(p, "filler", 6) == 0) {
p += 6;
diff_flags_new |= DIFF_FILLER;
@@ -2515,6 +2510,7 @@ int diffopt_changed(void)
p += 8;
diff_flags_new |= DIFF_INTERNAL;
} else if (strncmp(p, "algorithm:", 10) == 0) {
+ // Note: Keep this in sync with p_dip_algorithm_values.
p += 10;
if (strncmp(p, "myers", 5) == 0) {
p += 5;
@@ -2571,7 +2567,7 @@ int diffopt_changed(void)
// recompute the scroll binding with the new option value, may
// remove or add filler lines
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
return OK;
}
@@ -2615,7 +2611,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
// Make a copy of the line, the next ml_get() will invalidate it.
- char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum, false));
+ char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum));
int idx = diff_buf_idx(wp->w_buffer);
if (idx == DB_COUNT) {
@@ -2652,15 +2648,14 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
bool added = true;
linenr_T off = lnum - dp->df_lnum[idx];
- int i;
- for (i = 0; i < DB_COUNT; i++) {
+ for (int i = 0; i < DB_COUNT; i++) {
if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) {
// Skip lines that are not in the other change (filler lines).
if (off >= dp->df_count[i]) {
continue;
}
added = false;
- char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off, false);
+ char *line_new = ml_get_buf(curtab->tp_diffbuf[i], dp->df_lnum[i] + off);
// Search for start of difference
si_org = si_new = 0;
@@ -2951,7 +2946,7 @@ void ex_diffgetput(exarg_T *eap)
}
const int idx_from = eap->cmdidx == CMD_diffget ? idx_other : idx_cur;
- const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other;
+ const int idx_to = eap->cmdidx == CMD_diffget ? idx_cur : idx_other;
// May give the warning for a changed buffer here, which can trigger the
// FileChangedRO autocommand, which may do nasty things and mess
@@ -3096,15 +3091,18 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) {
break;
}
- char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr, false));
+ char *p = xstrdup(ml_get_buf(curtab->tp_diffbuf[idx_from], nr));
ml_append(lnum + i - 1, p, 0, false);
xfree(p);
added++;
if (buf_empty && (curbuf->b_ml.ml_line_count == 2)) {
// Added the first line into an empty buffer, need to
// delete the dummy empty line.
+ // This has a side effect of incrementing curbuf->deleted_bytes,
+ // which results in inaccurate reporting of the byte count of
+ // previous contents in buffer-update events.
buf_empty = false;
- ml_delete((linenr_T)2, false);
+ ml_delete(2, false);
}
}
linenr_T new_count = dp->df_count[idx_to] + added;
@@ -3133,7 +3131,7 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
if (added != 0) {
// Adjust marks. This will change the following entries!
- mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, added, kExtmarkUndo);
+ mark_adjust(lnum, lnum + count - 1, MAXLNUM, added, kExtmarkNOOP);
if (curwin->w_cursor.lnum >= lnum) {
// Adjust the cursor position if it's in/after the changed
// lines.
@@ -3144,7 +3142,8 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
}
}
}
- changed_lines(lnum, 0, lnum + count, added, true);
+ extmark_adjust(curbuf, lnum, lnum + count - 1, MAXLNUM, added, kExtmarkUndo);
+ changed_lines(curbuf, lnum, 0, lnum + count, added, true);
if (did_free) {
// Diff is deleted, update folds in other windows.
@@ -3214,7 +3213,7 @@ bool diff_mode_buf(buf_T *buf)
/// @param count
///
/// @return FAIL if there isn't such a diff block.
-int diff_move_to(int dir, long count)
+int diff_move_to(int dir, int count)
{
linenr_T lnum = curwin->w_cursor.lnum;
int idx = diff_buf_idx(curbuf);
@@ -3357,7 +3356,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
if (idx == DB_COUNT) {
// safety check
- return (linenr_T)0;
+ return 0;
}
if (curtab->tp_diff_invalid) {
@@ -3383,7 +3382,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
if (i == DB_COUNT) {
// safety check
- return (linenr_T)0;
+ return 0;
}
linenr_T n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]);
@@ -3393,13 +3392,12 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
return n;
}
-///
/// Handle an ED style diff line.
-/// Return FAIL if the line does not contain diff info.
///
+/// @return FAIL if the line does not contain diff info.
static int parse_diff_ed(char *line, diffhunk_T *hunk)
{
- long l1, l2;
+ int l1, l2;
// The line must be one of three formats:
// change: {first}[,{last}]c{first}[,{last}]
@@ -3409,7 +3407,7 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
linenr_T f1 = getdigits_int32(&p, true, 0);
if (*p == ',') {
p++;
- l1 = getdigits(&p, true, 0);
+ l1 = getdigits_int(&p, true, 0);
} else {
l1 = f1;
}
@@ -3417,10 +3415,10 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
return FAIL; // invalid diff format
}
int difftype = (uint8_t)(*p++);
- long f2 = getdigits(&p, true, 0);
+ int f2 = getdigits_int(&p, true, 0);
if (*p == ',') {
p++;
- l2 = getdigits(&p, true, 0);
+ l2 = getdigits_int(&p, true, 0);
} else {
l2 = f2;
}
@@ -3445,31 +3443,29 @@ static int parse_diff_ed(char *line, diffhunk_T *hunk)
return OK;
}
-///
/// Parses unified diff with zero(!) context lines.
/// Return FAIL if there is no diff information in "line".
-///
static int parse_diff_unified(char *line, diffhunk_T *hunk)
{
// Parse unified diff hunk header:
// @@ -oldline,oldcount +newline,newcount @@
char *p = line;
if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') {
- long oldcount;
- long newline;
- long newcount;
- long oldline = getdigits(&p, true, 0);
+ int oldcount;
+ linenr_T newline;
+ int newcount;
+ linenr_T oldline = getdigits_int32(&p, true, 0);
if (*p == ',') {
p++;
- oldcount = getdigits(&p, true, 0);
+ oldcount = getdigits_int(&p, true, 0);
} else {
oldcount = 1;
}
if (*p++ == ' ' && *p++ == '+') {
- newline = getdigits(&p, true, 0);
+ newline = getdigits_int(&p, true, 0);
if (*p == ',') {
p++;
- newcount = getdigits(&p, true, 0);
+ newcount = getdigits_int(&p, true, 0);
} else {
newcount = 1;
}
@@ -3487,9 +3483,9 @@ static int parse_diff_unified(char *line, diffhunk_T *hunk)
newline = 1;
}
- hunk->lnum_orig = (linenr_T)oldline;
+ hunk->lnum_orig = oldline;
hunk->count_orig = oldcount;
- hunk->lnum_new = (linenr_T)newline;
+ hunk->lnum_new = newline;
hunk->count_new = newcount;
return OK;
@@ -3498,18 +3494,16 @@ static int parse_diff_unified(char *line, diffhunk_T *hunk)
return FAIL;
}
-///
/// Callback function for the xdl_diff() function.
/// Stores the diff output in a grow array.
-///
-static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv)
+static int xdiff_out(int start_a, int count_a, int start_b, int count_b, void *priv)
{
diffout_T *dout = (diffout_T *)priv;
GA_APPEND(diffhunk_T, &(dout->dout_ga), ((diffhunk_T){
- .lnum_orig = (linenr_T)start_a + 1,
+ .lnum_orig = (linenr_T)start_a + 1,
.count_orig = count_a,
- .lnum_new = (linenr_T)start_b + 1,
- .count_new = count_b,
+ .lnum_new = (linenr_T)start_b + 1,
+ .count_new = count_b,
}));
return 0;
}
diff --git a/src/nvim/diff.h b/src/nvim/diff.h
index 1f64465336..8b58887890 100644
--- a/src/nvim/diff.h
+++ b/src/nvim/diff.h
@@ -1,20 +1,18 @@
-#ifndef NVIM_DIFF_H
-#define NVIM_DIFF_H
+#pragma once
#include <stdbool.h>
#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
// Value set from 'diffopt'.
-EXTERN int diff_context INIT(= 6); // context for folds
-EXTERN int diff_foldcolumn INIT(= 2); // 'foldcolumn' for diff mode
-EXTERN bool diff_need_scrollbind INIT(= false);
+EXTERN int diff_context INIT( = 6); // context for folds
+EXTERN int diff_foldcolumn INIT( = 2); // 'foldcolumn' for diff mode
+EXTERN bool diff_need_scrollbind INIT( = false);
-EXTERN bool need_diff_redraw INIT(= false); // need to call diff_redraw()
+EXTERN bool need_diff_redraw INIT( = false); // need to call diff_redraw()
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "diff.h.generated.h"
#endif
-#endif // NVIM_DIFF_H
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index a057978a5e..99d5cf1035 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file digraph.c
///
/// code for digraphs
@@ -10,47 +7,49 @@
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/normal.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/runtime.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
typedef int result_T;
typedef struct digraph {
- char_u char1;
- char_u char2;
+ uint8_t char1;
+ uint8_t char2;
result_T result;
} digr_T;
-static char e_digraph_must_be_just_two_characters_str[]
+static const char e_digraph_must_be_just_two_characters_str[]
= N_("E1214: Digraph must be just two characters: %s");
-static char e_digraph_argument_must_be_one_character_str[]
+static const char e_digraph_argument_must_be_one_character_str[]
= N_("E1215: Digraph must be one character: %s");
-static char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[]
+static const char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[]
= N_("E1216: digraph_setlist() argument must be a list of lists with two items");
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -879,6 +878,7 @@ static digr_T digraphdefault[] =
{ '1', '\'', 0x2032 },
{ '2', '\'', 0x2033 },
{ '3', '\'', 0x2034 },
+ { '4', '\'', 0x2057 },
{ '1', '"', 0x2035 },
{ '2', '"', 0x2036 },
{ '3', '"', 0x2037 },
@@ -1493,7 +1493,7 @@ char *get_digraph_for_char(int val_arg)
{
const int val = val_arg;
const digr_T *dp;
- static char_u r[3];
+ static char r[3];
for (int use_defaults = 0; use_defaults <= 1; use_defaults++) {
if (use_defaults == 0) {
@@ -1503,10 +1503,10 @@ char *get_digraph_for_char(int val_arg)
}
for (int i = 0; use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) {
if (dp->result == val) {
- r[0] = dp->char1;
- r[1] = dp->char2;
+ r[0] = (char)dp->char1;
+ r[1] = (char)dp->char2;
r[2] = NUL;
- return (char *)r;
+ return r;
}
dp++;
}
@@ -1623,7 +1623,7 @@ int digraph_get(int char1, int char2, bool meta_char)
if (((retval = getexactdigraph(char1, char2, meta_char)) == char2)
&& (char1 != char2)
- && ((retval = getexactdigraph(char2, char1, meta_char)) // -V764
+ && ((retval = getexactdigraph(char2, char1, meta_char))
== char1)) {
return char2;
}
@@ -1645,8 +1645,8 @@ static void registerdigraph(int char1, int char2, int n)
// Add a new digraph to the table.
dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs);
- dp->char1 = (char_u)char1;
- dp->char2 = (char_u)char2;
+ dp->char1 = (uint8_t)char1;
+ dp->char2 = (uint8_t)char2;
dp->result = n;
}
@@ -1656,7 +1656,7 @@ static void registerdigraph(int char1, int char2, int n)
bool check_digraph_chars_valid(int char1, int char2)
{
if (char2 == 0) {
- char msg[MB_MAXBYTES + 1];
+ char msg[MB_MAXCHAR + 1];
msg[utf_char2bytes(char1, msg)] = NUL;
semsg(_(e_digraph_must_be_just_two_characters_str), msg);
return false;
@@ -1705,7 +1705,7 @@ static void digraph_header(const char *msg)
if (msg_col > 0) {
msg_putchar('\n');
}
- msg_outtrans_attr(msg, HL_ATTR(HLF_CM));
+ msg_outtrans(msg, HL_ATTR(HLF_CM));
msg_putchar('\n');
}
@@ -1859,7 +1859,7 @@ static void printdigraph(const digr_T *dp, result_T *previous)
*p++ = (char)dp->char2;
*p++ = ' ';
*p = NUL;
- msg_outtrans(buf);
+ msg_outtrans(buf, 0);
p = buf;
// add a space to draw a composing char on
@@ -1869,14 +1869,14 @@ static void printdigraph(const digr_T *dp, result_T *previous)
p += utf_char2bytes(dp->result, p);
*p = NUL;
- msg_outtrans_attr(buf, HL_ATTR(HLF_8));
+ msg_outtrans(buf, HL_ATTR(HLF_8));
p = buf;
if (char2cells(dp->result) == 1) {
*p++ = ' ';
}
assert(p >= buf);
vim_snprintf(p, sizeof(buf) - (size_t)(p - buf), " %3d", dp->result);
- msg_outtrans(buf);
+ msg_outtrans(buf, 0);
}
/// Get the two digraph characters from a typval.
@@ -2076,8 +2076,6 @@ char *keymap_init(void)
/// @param eap
void ex_loadkeymap(exarg_T *eap)
{
- char *s;
-
#define KMAP_LLEN 200 // max length of "to" and "from" together
char buf[KMAP_LLEN + 11];
char *save_cpo = p_cpo;
@@ -2097,7 +2095,7 @@ void ex_loadkeymap(exarg_T *eap)
p_cpo = "C";
// Get each line of the sourced file, break at the end.
- for (;;) {
+ while (true) {
char *line = eap->getline(0, eap->cookie, 0, true);
if (line == NULL) {
@@ -2108,11 +2106,11 @@ void ex_loadkeymap(exarg_T *eap)
if ((*p != '"') && (*p != NUL)) {
kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga);
- s = skiptowhite(p);
- kp->from = xstrnsave(p, (size_t)(s - p));
+ char *s = skiptowhite(p);
+ kp->from = xmemdupz(p, (size_t)(s - p));
p = skipwhite(s);
s = skiptowhite(p);
- kp->to = xstrnsave(p, (size_t)(s - p));
+ kp->to = xmemdupz(p, (size_t)(s - p));
if ((strlen(kp->from) + strlen(kp->to) >= KMAP_LLEN)
|| (*kp->from == NUL)
@@ -2180,3 +2178,42 @@ static void keymap_unload(void)
curbuf->b_kmap_state &= ~KEYMAP_LOADED;
status_redraw_curbuf();
}
+
+/// Get the value to show for the language mappings, active 'keymap'.
+///
+/// @param fmt format string containing one %s item
+/// @param buf buffer for the result
+/// @param len length of buffer
+bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
+{
+ char *p;
+
+ if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) {
+ return false;
+ }
+
+ buf_T *old_curbuf = curbuf;
+ win_T *old_curwin = curwin;
+ char *s;
+
+ curbuf = wp->w_buffer;
+ curwin = wp;
+ STRCPY(buf, "b:keymap_name"); // must be writable
+ emsg_skip++;
+ s = p = eval_to_string(buf, false);
+ emsg_skip--;
+ curbuf = old_curbuf;
+ curwin = old_curwin;
+ if (p == NULL || *p == NUL) {
+ if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) {
+ p = wp->w_buffer->b_p_keymap;
+ } else {
+ p = "lang";
+ }
+ }
+ if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) {
+ buf[0] = NUL;
+ }
+ xfree(s);
+ return buf[0] != NUL;
+}
diff --git a/src/nvim/digraph.h b/src/nvim/digraph.h
index 71330ae9b1..267004124b 100644
--- a/src/nvim/digraph.h
+++ b/src/nvim/digraph.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_DIGRAPH_H
-#define NVIM_DIGRAPH_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "digraph.h.generated.h"
#endif
-#endif // NVIM_DIGRAPH_H
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 01ff207c2b..e0887ed1d0 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -1,18 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// drawline.c: Functions for drawing window lines on the screen.
-// This is the middle level, drawscreen.c is the top and grid.c/screen.c the lower level.
+// This is the middle level, drawscreen.c is the top and grid.c the lower level.
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -21,10 +18,10 @@
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawline.h"
+#include "nvim/drawscreen.h"
#include "nvim/eval.h"
-#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
-#include "nvim/garray.h"
+#include "nvim/fold_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -37,10 +34,10 @@
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/sign.h"
#include "nvim/spell.h"
#include "nvim/state.h"
@@ -48,12 +45,11 @@
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
-#define MB_FILLER_CHAR '<' // character used when a double-width character
- // doesn't fit.
+#define MB_FILLER_CHAR '<' // character used when a double-width character doesn't fit.
/// possible draw states in win_line(), drawn in sequence.
typedef enum {
@@ -68,19 +64,104 @@ typedef enum {
WL_LINE, // text in the line
} LineDrawState;
-/// for line_putchar. Contains the state that needs to be remembered from
-/// putting one character to the next.
+/// structure with variables passed between win_line() and other functions
typedef struct {
- const char *p;
- int prev_c; ///< previous Arabic character
- int prev_c1; ///< first composing char for prev_c
-} LineState;
-#define LINE_STATE(p) { p, 0, 0 }
+ LineDrawState draw_state; ///< what to draw next
+
+ linenr_T lnum; ///< line number to be drawn
+ foldinfo_T foldinfo; ///< fold info for this line
+
+ int startrow; ///< first row in the window to be drawn
+ int row; ///< row in the window, excl w_winrow
+
+ colnr_T vcol; ///< virtual column, before wrapping
+ int col; ///< visual column on screen, after wrapping
+ int boguscols; ///< nonexistent columns added to "col" to force wrapping
+ int vcol_off; ///< offset for concealed characters
+
+ int off; ///< offset relative start of line
+
+ int cul_attr; ///< set when 'cursorline' active
+ int line_attr; ///< attribute for the whole line
+ int line_attr_lowprio; ///< low-priority attribute for the line
+
+ int fromcol; ///< start of inverting
+ int tocol; ///< end of inverting
+
+ colnr_T vcol_sbr; ///< virtual column after showbreak
+ bool need_showbreak; ///< overlong line, skipping first x chars
+
+ int char_attr; ///< attributes for next character
+
+ int n_extra; ///< number of extra bytes
+ int n_attr; ///< chars with special attr
+ char *p_extra; ///< string of extra chars, plus NUL, only used
+ ///< when c_extra and c_final are NUL
+ int extra_attr; ///< attributes for p_extra
+ int c_extra; ///< extra chars, all the same
+ int c_final; ///< final char, mandatory if set
+
+ int n_closing; ///< number of chars in fdc which will be closing
+
+ bool extra_for_extmark; ///< n_extra set for inline virtual text
+
+ // saved "extra" items for when draw_state becomes WL_LINE (again)
+ int saved_n_extra;
+ char *saved_p_extra;
+ bool saved_extra_for_extmark;
+ int saved_c_extra;
+ int saved_c_final;
+ int saved_char_attr;
+
+ char extra[57]; ///< sign, line number and 'fdc' must fit in here
+
+ hlf_T diff_hlf; ///< type of diff highlighting
+
+ int n_virt_lines; ///< nr of virtual lines
+ int filler_lines; ///< nr of filler lines to be drawn
+ int filler_todo; ///< nr of filler lines still to do + 1
+ SignTextAttrs sattrs[SIGN_SHOW_MAX]; ///< sign attributes for the sign column
+ /// do consider wrapping in linebreak mode only after encountering
+ /// a non whitespace char
+ bool need_lbr;
+
+ VirtText virt_inline;
+ size_t virt_inline_i;
+ HlMode virt_inline_hl_mode;
+
+ bool reset_extra_attr;
+
+ int skip_cells; ///< nr of cells to skip for w_leftcol
+ ///< or w_skipcol or concealing
+ int skipped_cells; ///< nr of skipped cells for virtual text
+ ///< to be added to wlv.vcol later
+} winlinevars_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawline.c.generated.h"
#endif
+static char *extra_buf = NULL;
+static size_t extra_buf_size = 0;
+
+static char *get_extra_buf(size_t size)
+{
+ size = MAX(size, 64);
+ if (extra_buf_size < size) {
+ xfree(extra_buf);
+ extra_buf = xmalloc(size);
+ extra_buf_size = size;
+ }
+ return extra_buf;
+}
+
+#ifdef EXITFREE
+void drawline_free_all_mem(void)
+{
+ xfree(extra_buf);
+}
+#endif
+
/// Advance **color_cols
///
/// @return true when there are columns to draw.
@@ -114,7 +195,7 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
return;
}
- width1 = wp->w_width - cur_col_off;
+ width1 = wp->w_width_inner - cur_col_off;
width2 = width1 + win_col_off2(wp);
*left_col = 0;
@@ -137,110 +218,96 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
/// Put a single char from an UTF-8 buffer into a line buffer.
///
-/// Handles composing chars and arabic shaping state.
-static int line_putchar(buf_T *buf, LineState *s, schar_T *dest, int maxcells, bool rl, int vcol)
+/// If `*pp` is a double-width char and only one cell is left, emit a space,
+/// and don't advance *pp
+///
+/// Handles composing chars
+static int line_putchar(buf_T *buf, const char **pp, schar_T *dest, int maxcells, int vcol)
{
- const char *p = s->p;
+ const char *p = *pp;
int cells = utf_ptr2cells(p);
int c_len = utfc_ptr2len(p);
- int u8c, u8cc[MAX_MCO];
+ assert(maxcells > 0);
if (cells > maxcells) {
- return -1;
+ dest[0] = schar_from_ascii(' ');
+ return 1;
}
- u8c = utfc_ptr2char(p, u8cc);
+
if (*p == TAB) {
cells = MIN(tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array), maxcells);
+ }
+
+ if (cells < maxcells && dest[cells] == 0) {
+ dest[cells] = schar_from_ascii(' ');
+ }
+ if (*p == TAB) {
for (int c = 0; c < cells; c++) {
- schar_from_ascii(dest[c], ' ');
+ dest[c] = schar_from_ascii(' ');
}
- goto done;
- } else if ((uint8_t)(*p) < 0x80 && u8cc[0] == 0) {
- schar_from_ascii(dest[0], *p);
- s->prev_c = u8c;
} else {
- if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
- int firstbyte = (uint8_t)(*p);
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (rl) {
- pc = s->prev_c;
- pc1 = s->prev_c1;
- nc = utf_ptr2char(p + c_len);
- s->prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(p + c_len, pcc);
- nc = s->prev_c;
- pc1 = pcc[0];
- }
- s->prev_c = u8c;
-
- u8c = arabic_shape(u8c, &firstbyte, &u8cc[0], pc, pc1, nc);
- } else {
- s->prev_c = u8c;
+ int u8c;
+ dest[0] = utfc_ptr2schar(p, &u8c);
+ if (cells > 1) {
+ dest[1] = 0;
}
- schar_from_cc(dest[0], u8c, u8cc);
- }
- if (cells > 1) {
- dest[1][0] = 0;
}
-done:
- s->p += c_len;
- return cells;
-}
-static inline void provider_err_virt_text(linenr_T lnum, char *err)
-{
- Decoration err_decor = DECORATION_INIT;
- int hl_err = syn_check_group(S_LEN("ErrorMsg"));
- kv_push(err_decor.virt_text,
- ((VirtTextChunk){ .text = err,
- .hl_id = hl_err }));
- err_decor.virt_text_width = (int)mb_string2cells(err);
- decor_add_ephemeral(lnum - 1, 0, lnum - 1, 0, &err_decor, 0, 0);
+ *pp += c_len;
+ return cells;
}
-static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int max_col,
- int win_row)
+static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int win_row)
{
DecorState *state = &decor_state;
+ const int max_col = wp->w_grid.cols;
int right_pos = max_col;
bool do_eol = state->eol_col > -1;
for (size_t i = 0; i < kv_size(state->active); i++) {
DecorRange *item = &kv_A(state->active, i);
- if (!(item->start_row == state->row
- && (kv_size(item->decor.virt_text) || item->decor.ui_watched))) {
+ if (!(item->start_row == state->row && decor_virt_pos(item))) {
continue;
}
- if (item->win_col == -1) {
- if (item->decor.virt_text_pos == kVTRightAlign) {
- right_pos -= item->decor.virt_text_width;
- item->win_col = right_pos;
- } else if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
- item->win_col = state->eol_col;
- } else if (item->decor.virt_text_pos == kVTWinCol) {
- item->win_col = MAX(item->decor.col + col_off, 0);
+
+ DecorVirtText *vt = NULL;
+ if (item->kind == kDecorKindVirtText) {
+ assert(item->data.vt);
+ vt = item->data.vt;
+ }
+ if (decor_virt_pos(item) && item->draw_col == -1) {
+ bool updated = true;
+ VirtTextPos pos = decor_virt_pos_kind(item);
+ if (pos == kVPosRightAlign) {
+ right_pos -= vt->width;
+ item->draw_col = right_pos;
+ } else if (pos == kVPosEndOfLine && do_eol) {
+ item->draw_col = state->eol_col;
+ } else if (pos == kVPosWinCol) {
+ item->draw_col = MAX(col_off + vt->col, 0);
+ } else {
+ updated = false;
+ }
+ if (updated && (item->draw_col < 0 || item->draw_col >= wp->w_grid.cols)) {
+ // Out of window, don't draw at all.
+ item->draw_col = INT_MIN;
}
}
- if (item->win_col < 0) {
+ if (item->draw_col < 0) {
continue;
}
- int col;
- if (item->decor.ui_watched) {
+ int col = 0;
+ if (item->kind == kDecorKindUIWatched) {
// send mark position to UI
- col = item->win_col;
- WinExtmark m = { (NS)item->ns_id, item->mark_id, win_row, col };
+ col = item->draw_col;
+ WinExtmark m = { (NS)item->data.ui.ns_id, item->data.ui.mark_id, win_row, col };
kv_push(win_extmark_arr, m);
}
- if (kv_size(item->decor.virt_text)) {
- col = draw_virt_text_item(buf, item->win_col, item->decor.virt_text,
- item->decor.hl_mode, max_col, item->win_col - col_off);
+ if (vt) {
+ int vcol = item->draw_col - col_off;
+ col = draw_virt_text_item(buf, item->draw_col, vt->data.virt_text,
+ vt->hl_mode, max_col, vcol);
}
- item->win_col = -2; // deactivate
- if (item->decor.virt_text_pos == kVTEndOfLine && do_eol) {
+ item->draw_col = INT_MIN; // deactivate
+ if (vt && vt->pos == kVPosEndOfLine && do_eol) {
state->eol_col = col + 1;
}
@@ -251,28 +318,22 @@ static void draw_virt_text(win_T *wp, buf_T *buf, int col_off, int *end_col, int
static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, int max_col,
int vcol)
{
- LineState s = LINE_STATE("");
+ const char *p = "";
int virt_attr = 0;
size_t virt_pos = 0;
while (col < max_col) {
- if (!*s.p) {
+ if (!*p) {
if (virt_pos >= kv_size(vt)) {
break;
}
virt_attr = 0;
- do {
- s.p = kv_A(vt, virt_pos).text;
- int hl_id = kv_A(vt, virt_pos).hl_id;
- virt_attr = hl_combine_attr(virt_attr,
- hl_id > 0 ? syn_id2attr(hl_id) : 0);
- virt_pos++;
- } while (!s.p && virt_pos < kv_size(vt));
- if (!s.p) {
+ p = next_virt_text_chunk(vt, &virt_pos, &virt_attr);
+ if (p == NULL) {
break;
}
}
- if (!*s.p) {
+ if (*p == NUL) {
continue;
}
int attr;
@@ -280,19 +341,18 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
if (hl_mode == kHlModeCombine) {
attr = hl_combine_attr(linebuf_attr[col], virt_attr);
} else if (hl_mode == kHlModeBlend) {
- through = (*s.p == ' ');
+ through = (*p == ' ');
attr = hl_blend_attrs(linebuf_attr[col], virt_attr, &through);
} else {
attr = virt_attr;
}
schar_T dummy[2];
- int cells = line_putchar(buf, &s, through ? dummy : &linebuf_char[col],
- max_col - col, false, vcol);
- // if we failed to emit a char, we still need to advance
- cells = MAX(cells, 1);
-
+ int maxcells = max_col - col;
+ int cells = line_putchar(buf, &p, through ? dummy : &linebuf_char[col],
+ maxcells, vcol);
for (int c = 0; c < cells; c++) {
- linebuf_attr[col++] = attr;
+ linebuf_attr[col] = attr;
+ col++;
}
vcol += cells;
}
@@ -300,102 +360,243 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode,
}
/// Return true if CursorLineSign highlight is to be used.
-static bool use_cursor_line_sign(win_T *wp, linenr_T lnum)
+static bool use_cursor_line_highlight(win_T *wp, linenr_T lnum)
{
return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
+ && lnum == wp->w_cursorline
&& (wp->w_p_culopt_flags & CULOPT_NBR);
}
-// Get information needed to display the sign in line 'lnum' in window 'wp'.
-// If 'nrcol' is true, the sign is going to be displayed in the number column.
-// Otherwise the sign is going to be displayed in the sign column.
-//
-// @param count max number of signs
-// @param[out] n_extrap number of characters from pp_extra to display
-// @param sign_idxp Index of the displayed sign
-static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, SignTextAttrs sattrs[],
- int row, int startrow, int filler_lines, int filler_todo,
- int *c_extrap, int *c_finalp, char *extra, size_t extra_size,
- char **pp_extra, int *n_extrap, int *char_attrp, int sign_idx,
- int cul_attr)
+static char fdc_buf[MB_MAXCHAR * 10 + 1];
+
+/// Setup for drawing the 'foldcolumn', if there is one.
+static void handle_foldcolumn(win_T *wp, winlinevars_T *wlv)
{
- // Draw cells with the sign value or blank.
- *c_extrap = ' ';
- *c_finalp = NUL;
- if (nrcol) {
- *n_extrap = number_width(wp) + 1;
+ int fdc = compute_foldcolumn(wp, 0);
+ if (fdc <= 0) {
+ return;
+ }
+
+ // Use a separate buffer as `extra_buf` might be in use.
+ wlv->n_extra = (int)fill_foldcolumn(fdc_buf, wp, wlv->foldinfo, wlv->lnum,
+ &wlv->n_closing);
+ fdc_buf[wlv->n_extra] = NUL;
+ wlv->p_extra = fdc_buf;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ if (use_cursor_line_highlight(wp, wlv->lnum)) {
+ wlv->char_attr = win_hl_attr(wp, HLF_CLF);
} else {
- if (use_cursor_line_sign(wp, lnum)) {
- *char_attrp = win_hl_attr(wp, HLF_CLS);
+ wlv->char_attr = win_hl_attr(wp, HLF_FC);
+ }
+}
+
+/// Fills the foldcolumn at "p" for window "wp".
+/// Only to be called when 'foldcolumn' > 0.
+///
+/// @param[out] p Char array to write into
+/// @param lnum Absolute current line number
+/// @param closed Whether it is in 'foldcolumn' mode
+///
+/// Assume monocell characters
+/// @return number of chars added to \param p
+size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum, int *n_closing)
+{
+ int i = 0;
+ int fdc = compute_foldcolumn(wp, 0); // available cell width
+ size_t char_counter = 0;
+ int symbol = 0;
+ int len = 0;
+ bool closed = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
+ // Init to all spaces.
+ memset(p, ' ', MB_MAXCHAR * (size_t)fdc + 1);
+
+ int level = foldinfo.fi_level;
+
+ // If the column is too narrow, we start at the lowest level that
+ // fits and use numbers to indicate the depth.
+ int first_level = level - fdc - closed + 1;
+ if (first_level < 1) {
+ first_level = 1;
+ }
+
+ for (i = 0; i < MIN(fdc, level); i++) {
+ if (foldinfo.fi_lnum == lnum
+ && first_level + i >= foldinfo.fi_low_level) {
+ symbol = wp->w_p_fcs_chars.foldopen;
+ } else if (first_level == 1) {
+ symbol = wp->w_p_fcs_chars.foldsep;
+ } else if (first_level + i <= 9) {
+ symbol = '0' + first_level + i;
} else {
- *char_attrp = win_hl_attr(wp, HLF_SC);
+ symbol = '>';
+ }
+
+ len = utf_char2bytes(symbol, &p[char_counter]);
+ char_counter += (size_t)len;
+ if (first_level + i >= level) {
+ i++;
+ break;
}
- *n_extrap = win_signcol_width(wp);
}
- if (row == startrow + filler_lines && filler_todo <= 0) {
- SignTextAttrs *sattr = sign_get_attr(sign_idx, sattrs, wp->w_scwidth);
- if (sattr != NULL) {
- *pp_extra = sattr->text;
- if (*pp_extra != NULL) {
- *c_extrap = NUL;
- *c_finalp = NUL;
-
- if (nrcol) {
- int n, width = number_width(wp) - 2;
- for (n = 0; n < width; n++) {
- extra[n] = ' ';
- }
- extra[n] = NUL;
- STRCAT(extra, *pp_extra);
- STRCAT(extra, " ");
- *pp_extra = extra;
- *n_extrap = (int)strlen(*pp_extra);
- } else {
- size_t symbol_blen = strlen(*pp_extra);
+ int n_closing_val = i;
- // TODO(oni-link): Is sign text already extended to
- // full cell width?
- assert((size_t)win_signcol_width(wp) >= mb_string2cells((char *)(*pp_extra)));
- // symbol(s) bytes + (filling spaces) (one byte each)
- *n_extrap = (int)symbol_blen + win_signcol_width(wp) -
- (int)mb_string2cells(*pp_extra);
+ if (closed) {
+ if (symbol != 0) {
+ // rollback previous write
+ char_counter -= (size_t)len;
+ memset(&p[char_counter], ' ', (size_t)len);
+ n_closing_val--;
+ }
+ len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
+ char_counter += (size_t)len;
+ }
- assert(extra_size > symbol_blen);
- memset(extra, ' ', extra_size);
- memcpy(extra, *pp_extra, symbol_blen);
+ if (n_closing) {
+ *n_closing = n_closing_val;
+ }
- *pp_extra = extra;
- (*pp_extra)[*n_extrap] = NUL;
- }
- }
+ return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
+}
- if (use_cursor_line_sign(wp, lnum) && cul_attr > 0) {
- *char_attrp = cul_attr;
- } else {
- *char_attrp = sattr->hl_attr_id;
- }
+/// Get information needed to display the sign in line "wlv->lnum" in window "wp".
+/// If "nrcol" is true, the sign is going to be displayed in the number column.
+/// Otherwise the sign is going to be displayed in the sign column. If there is no
+/// sign, draw blank cells instead.
+static void get_sign_display_info(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx,
+ int sign_cul_attr)
+{
+ SignTextAttrs sattr = wlv->sattrs[sign_idx];
+ wlv->c_final = NUL;
+
+ if (sattr.text && wlv->row == wlv->startrow + wlv->filler_lines && wlv->filler_todo <= 0) {
+ size_t fill = nrcol ? (size_t)number_width(wp) - SIGN_WIDTH : 0;
+ size_t sign_len = strlen(sattr.text);
+
+ // Spaces + sign: " " + ">>" + ' '
+ wlv->n_extra = (int)(fill + sign_len + nrcol);
+ if (nrcol) {
+ memset(wlv->extra, ' ', (size_t)wlv->n_extra);
+ }
+ memcpy(wlv->extra + fill, sattr.text, sign_len);
+ wlv->p_extra = wlv->extra;
+ wlv->c_extra = NUL;
+ wlv->char_attr = (use_cursor_line_highlight(wp, wlv->lnum) && sign_cul_attr)
+ ? sign_cul_attr : sattr.hl_id ? syn_id2attr(sattr.hl_id) : 0;
+ } else {
+ wlv->c_extra = ' ';
+ wlv->n_extra = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
+ if (!nrcol) {
+ wlv->char_attr = win_hl_attr(wp, use_cursor_line_highlight(wp, wlv->lnum) ? HLF_CLS : HLF_SC);
}
}
}
-static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int *line_attr,
- int *num_attr, int *cul_attr)
+static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len)
{
- HlPriAttr line_attrs = { *line_attr, 0 };
- HlPriAttr num_attrs = { *num_attr, 0 };
- HlPriAttr cul_attrs = { *cul_attr, 0 };
+ linenr_T num;
+ char *fmt = "%*" PRIdLINENR " ";
- // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
- int num_signs = buf_get_signattrs(buf, lnum, sattrs, &num_attrs, &line_attrs, &cul_attrs);
- decor_redraw_signs(buf, lnum - 1, &num_signs, sattrs, &num_attrs, &line_attrs, &cul_attrs);
+ if (wp->w_p_nu && !wp->w_p_rnu) {
+ // 'number' + 'norelativenumber'
+ num = lnum;
+ } else {
+ // 'relativenumber', don't use negative numbers
+ num = abs(get_cursor_rel_lnum(wp, lnum));
+ if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
+ // 'number' + 'relativenumber'
+ num = lnum;
+ fmt = "%-*" PRIdLINENR " ";
+ }
+ }
- *line_attr = line_attrs.attr_id;
- *num_attr = num_attrs.attr_id;
- *cul_attr = cul_attrs.attr_id;
+ snprintf(buf, buf_len, fmt, number_width(wp), num);
+}
- return num_signs;
+/// Return true if CursorLineNr highlight is to be used for the number column.
+/// - 'cursorline' must be set
+/// - "wlv->lnum" must be the cursor line
+/// - 'cursorlineopt' has "number"
+/// - don't highlight filler lines (when in diff mode)
+/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
+/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
+/// all screenlines of the wrapped line.
+static bool use_cursor_line_nr(win_T *wp, winlinevars_T *wlv)
+{
+ return wp->w_p_cul
+ && wlv->lnum == wp->w_cursorline
+ && (wp->w_p_culopt_flags & CULOPT_NBR)
+ && (wlv->row == wlv->startrow + wlv->filler_lines
+ || (wlv->row > wlv->startrow + wlv->filler_lines
+ && (wp->w_p_culopt_flags & CULOPT_LINE)));
+}
+
+static int get_line_number_attr(win_T *wp, winlinevars_T *wlv)
+{
+ if (use_cursor_line_nr(wp, wlv)) {
+ // TODO(vim): Can we use CursorLine instead of CursorLineNr
+ // when CursorLineNr isn't set?
+ return win_hl_attr(wp, HLF_CLN);
+ }
+
+ if (wp->w_p_rnu) {
+ if (wlv->lnum < wp->w_cursor.lnum) {
+ // Use LineNrAbove
+ return win_hl_attr(wp, HLF_LNA);
+ }
+ if (wlv->lnum > wp->w_cursor.lnum) {
+ // Use LineNrBelow
+ return win_hl_attr(wp, HLF_LNB);
+ }
+ }
+
+ return win_hl_attr(wp, HLF_N);
+}
+
+/// Display the absolute or relative line number. After the first row fill with
+/// blanks when the 'n' flag isn't in 'cpo'.
+static void handle_lnum_col(win_T *wp, winlinevars_T *wlv, int sign_num_attr, int sign_cul_attr)
+{
+ bool has_cpo_n = vim_strchr(p_cpo, CPO_NUMCOL) != NULL;
+
+ if ((wp->w_p_nu || wp->w_p_rnu)
+ && (wlv->row == wlv->startrow + wlv->filler_lines || !has_cpo_n)
+ // there is no line number in a wrapped line when "n" is in
+ // 'cpoptions', but 'breakindent' assumes it anyway.
+ && !((has_cpo_n && !wp->w_p_bri) && wp->w_skipcol > 0 && wlv->lnum == wp->w_topline)) {
+ // If 'signcolumn' is set to 'number' and a sign is present in "lnum",
+ // then display the sign instead of the line number.
+ if (wp->w_minscwidth == SCL_NUM && wlv->sattrs[0].text) {
+ get_sign_display_info(true, wp, wlv, 0, sign_cul_attr);
+ } else {
+ // Draw the line number (empty space after wrapping).
+ if (wlv->row == wlv->startrow + wlv->filler_lines
+ && (wp->w_skipcol == 0 || wlv->row > 0 || (wp->w_p_nu && wp->w_p_rnu))) {
+ get_line_number_str(wp, wlv->lnum, wlv->extra, sizeof(wlv->extra));
+ if (wp->w_skipcol > 0 && wlv->startrow == 0) {
+ for (wlv->p_extra = wlv->extra; *wlv->p_extra == ' '; wlv->p_extra++) {
+ *wlv->p_extra = '-';
+ }
+ }
+ if (wp->w_p_rl) { // reverse line numbers
+ char *num = skipwhite(wlv->extra);
+ rl_mirror_ascii(num, skiptowhite(num));
+ }
+ wlv->p_extra = wlv->extra;
+ wlv->c_extra = NUL;
+ } else {
+ wlv->c_extra = ' ';
+ }
+ wlv->c_final = NUL;
+ wlv->n_extra = number_width(wp) + 1;
+ if (sign_num_attr > 0) {
+ wlv->char_attr = sign_num_attr;
+ } else {
+ wlv->char_attr = get_line_number_attr(wp, wlv);
+ }
+ }
+ }
}
/// Prepare and build the 'statuscolumn' string for line "lnum" in window "wp".
@@ -404,37 +605,10 @@ static int get_sign_attrs(buf_T *buf, linenr_T lnum, SignTextAttrs *sattrs, int
/// the start of the buffer line "lnum" and once for the wrapped lines.
///
/// @param[out] stcp Status column attributes
-static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines,
- int cul_attr, int sign_num_attr, int sign_cul_attr, statuscol_T *stcp,
- foldinfo_T foldinfo, SignTextAttrs *sattrs)
+static void get_statuscol_str(win_T *wp, linenr_T lnum, int virtnum, statuscol_T *stcp)
{
- long relnum = -1;
- bool use_cul = use_cursor_line_sign(wp, lnum);
- int virtnum = row - startrow - filler_lines;
-
- // When called the first time for line "lnum" set num_attr
- if (stcp->num_attr == 0) {
- stcp->num_attr = sign_num_attr ? sign_num_attr
- : get_line_number_attr(wp, lnum, row, startrow, filler_lines);
- }
- // When called for the first non-filler row of line "lnum" set num v:vars and fold column
- if (virtnum == 0) {
- relnum = labs(get_cursor_rel_lnum(wp, lnum));
- if (compute_foldcolumn(wp, 0)) {
- size_t n = fill_foldcolumn(stcp->fold_text, wp, foldinfo, lnum);
- stcp->fold_text[n] = NUL;
- stcp->fold_attr = win_hl_attr(wp, use_cul ? HLF_CLF : HLF_FC);
- }
- }
- // Make sure to clear->set->clear sign column for filler->first->wrapped lines
- int i = 0;
- for (; i < wp->w_scwidth; i++) {
- SignTextAttrs *sattr = virtnum ? NULL : sign_get_attr(i, sattrs, wp->w_scwidth);
- stcp->sign_text[i] = sattr && sattr->text ? sattr->text : " ";
- stcp->sign_attr[i] = sattr ? (use_cul && sign_cul_attr ? sign_cul_attr : sattr->hl_attr_id)
- : win_hl_attr(wp, use_cul ? HLF_CLS : HLF_SC);
- }
- stcp->sign_text[i] = NULL;
+ // When called for the first non-filler row of line "lnum" set num v:vars
+ linenr_T relnum = virtnum == 0 ? abs(get_cursor_rel_lnum(wp, lnum)) : -1;
// When a buffer's line count has changed, make a best estimate for the full
// width of the status column by building with "w_nrwidth_line_count". Add
@@ -442,17 +616,23 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i
if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) {
wp->w_statuscol_line_count = wp->w_nrwidth_line_count;
set_vim_var_nr(VV_VIRTNUM, 0);
- build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp->width,
- ' ', stcp->text, &stcp->hlrec, stcp);
- stcp->width += stcp->truncate;
+ build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, stcp);
+ if (stcp->truncate > 0) {
+ // Add truncated width to avoid unnecessary redraws
+ int addwidth = MIN(stcp->truncate, MAX_NUMBERWIDTH - wp->w_nrwidth);
+ stcp->truncate = 0;
+ stcp->width += addwidth;
+ wp->w_nrwidth += addwidth;
+ wp->w_nrwidth_width = wp->w_nrwidth;
+ wp->w_valid &= ~VALID_WCOL;
+ }
}
set_vim_var_nr(VV_VIRTNUM, virtnum);
- int width = build_statuscol_str(wp, lnum, relnum, stcp->width,
- ' ', stcp->text, &stcp->hlrec, stcp);
+ int width = build_statuscol_str(wp, lnum, relnum, stcp);
// Force a redraw in case of error or when truncated
if (*wp->w_p_stc == NUL || (stcp->truncate > 0 && wp->w_nrwidth < MAX_NUMBERWIDTH)) {
- if (stcp->truncate) { // Avoid truncating 'statuscolumn'
+ if (stcp->truncate > 0) { // Avoid truncating 'statuscolumn'
wp->w_nrwidth = MIN(MAX_NUMBERWIDTH, wp->w_nrwidth + stcp->truncate);
wp->w_nrwidth_width = wp->w_nrwidth;
} else { // 'statuscolumn' reset due to error
@@ -478,201 +658,374 @@ static void get_statuscol_str(win_T *wp, linenr_T lnum, int row, int startrow, i
}
/// Get information needed to display the next segment in the 'statuscolumn'.
-/// If not yet at the end, prepare for next segment and decrement "draw_state".
+/// If not yet at the end, prepare for next segment and decrement "wlv->draw_state".
///
/// @param stcp Status column attributes
-/// @param[out] draw_state Current draw state in win_line()
-static void get_statuscol_display_info(statuscol_T *stcp, LineDrawState *draw_state, int *char_attr,
- int *n_extrap, int *c_extrap, int *c_finalp, char **pp_extra)
+/// @param[in,out] wlv
+static void get_statuscol_display_info(statuscol_T *stcp, winlinevars_T *wlv)
{
- *c_extrap = NUL;
- *c_finalp = NUL;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
do {
- *draw_state = WL_STC;
- *char_attr = stcp->cur_attr;
- *pp_extra = stcp->textp;
- *n_extrap = (int)((stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end) - stcp->textp);
+ wlv->draw_state = WL_STC;
+ wlv->char_attr = stcp->cur_attr;
+ wlv->p_extra = stcp->textp;
+ char *const section_end = stcp->hlrecp->start ? stcp->hlrecp->start : stcp->text_end;
+ wlv->n_extra = (int)(section_end - stcp->textp);
// Prepare for next highlight section if not yet at the end
- if (stcp->textp + *n_extrap < stcp->text_end) {
+ if (section_end < stcp->text_end) {
int hl = stcp->hlrecp->userhl;
stcp->textp = stcp->hlrecp->start;
- stcp->cur_attr = hl < 0 ? syn_id2attr(-stcp->hlrecp->userhl)
- : hl > 0 ? hl : stcp->num_attr;
+ stcp->cur_attr = hl < 0 ? syn_id2attr(-hl) : stcp->num_attr;
stcp->hlrecp++;
- *draw_state = WL_STC - 1;
+ wlv->draw_state = WL_STC - 1;
}
// Skip over empty highlight sections
- } while (*n_extrap == 0 && stcp->textp < stcp->text_end);
-}
-
-/// Return true if CursorLineNr highlight is to be used for the number column.
-///
-/// - 'cursorline' must be set
-/// - lnum must be the cursor line
-/// - 'cursorlineopt' has "number"
-/// - don't highlight filler lines (when in diff mode)
-/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number
-/// itself on the first screenline of the wrapped line, otherwise highlight the number column of
-/// all screenlines of the wrapped line.
-static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
-{
- return wp->w_p_cul
- && lnum == wp->w_cursor.lnum
- && (wp->w_p_culopt_flags & CULOPT_NBR)
- && (row == startrow + filler_lines
- || (row > startrow + filler_lines
- && (wp->w_p_culopt_flags & CULOPT_LINE)));
+ } while (wlv->n_extra == 0 && stcp->textp < stcp->text_end);
+ if (wlv->n_extra > 0) {
+ static char transbuf[(MAX_NUMBERWIDTH + 9 + 9 * 2) * MB_MAXBYTES + 1];
+ wlv->n_extra = (int)transstr_buf(wlv->p_extra, wlv->n_extra, transbuf, sizeof transbuf, true);
+ wlv->p_extra = transbuf;
+ }
}
-static inline void get_line_number_str(win_T *wp, linenr_T lnum, char *buf, size_t buf_len)
+static void handle_breakindent(win_T *wp, winlinevars_T *wlv)
{
- long num;
- char *fmt = "%*ld ";
+ if (wp->w_briopt_sbr && wlv->draw_state == WL_BRI - 1
+ && *get_showbreak_value(wp) != NUL) {
+ // draw indent after showbreak value
+ wlv->draw_state = WL_BRI;
+ } else if (wp->w_briopt_sbr && wlv->draw_state == WL_SBR) {
+ // after the showbreak, draw the breakindent
+ wlv->draw_state = WL_BRI - 1;
+ }
- if (wp->w_p_nu && !wp->w_p_rnu) {
- // 'number' + 'norelativenumber'
- num = (long)lnum;
- } else {
- // 'relativenumber', don't use negative numbers
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- // 'number' + 'relativenumber'
- num = lnum;
- fmt = "%-*ld ";
+ // draw 'breakindent': indent wrapped text accordingly
+ if (wlv->draw_state == WL_BRI - 1 && wlv->n_extra == 0) {
+ wlv->draw_state = WL_BRI;
+ // if wlv->need_showbreak is set, breakindent also applies
+ if (wp->w_p_bri && (wlv->row != wlv->startrow || wlv->need_showbreak)
+ && wlv->filler_lines == 0) {
+ wlv->char_attr = 0;
+ if (wlv->diff_hlf != (hlf_T)0) {
+ wlv->char_attr = win_hl_attr(wp, (int)wlv->diff_hlf);
+ }
+ wlv->p_extra = NULL;
+ wlv->c_extra = ' ';
+ wlv->c_final = NUL;
+ wlv->n_extra = get_breakindent_win(wp, ml_get_buf(wp->w_buffer, wlv->lnum));
+ if (wlv->row == wlv->startrow) {
+ wlv->n_extra -= win_col_off2(wp);
+ if (wlv->n_extra < 0) {
+ wlv->n_extra = 0;
+ }
+ }
+ if (wp->w_skipcol > 0 && wlv->startrow == 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
+ wlv->need_showbreak = false;
+ }
+ // Correct end of highlighted area for 'breakindent',
+ // required wen 'linebreak' is also set.
+ if (wlv->tocol == wlv->vcol) {
+ wlv->tocol += wlv->n_extra;
+ }
}
}
-
- snprintf(buf, buf_len, fmt, number_width(wp), num);
}
-static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines)
+static void handle_showbreak_and_filler(win_T *wp, winlinevars_T *wlv)
{
- if (wp->w_p_rnu) {
- if (lnum < wp->w_cursor.lnum) {
- // Use LineNrAbove
- return win_hl_attr(wp, HLF_LNA);
- }
- if (lnum > wp->w_cursor.lnum) {
- // Use LineNrBelow
- return win_hl_attr(wp, HLF_LNB);
+ if (wlv->filler_todo > wlv->filler_lines - wlv->n_virt_lines) {
+ // TODO(bfredl): check this doesn't inhibit TUI-style
+ // clear-to-end-of-line.
+ wlv->c_extra = ' ';
+ wlv->c_final = NUL;
+ wlv->n_extra = wp->w_grid.cols - wlv->col;
+ wlv->char_attr = 0;
+ } else if (wlv->filler_todo > 0) {
+ // Draw "deleted" diff line(s)
+ if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
+ wlv->c_extra = '-';
+ wlv->c_final = NUL;
+ } else {
+ wlv->c_extra = wp->w_p_fcs_chars.diff;
+ wlv->c_final = NUL;
}
+ wlv->n_extra = wp->w_grid.cols - wlv->col;
+ wlv->char_attr = win_hl_attr(wp, HLF_DED);
}
- if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) {
- // TODO(vim): Can we use CursorLine instead of CursorLineNr
- // when CursorLineNr isn't set?
- return win_hl_attr(wp, HLF_CLN);
- }
+ char *const sbr = get_showbreak_value(wp);
+ if (*sbr != NUL && wlv->need_showbreak) {
+ // Draw 'showbreak' at the start of each broken line.
+ wlv->p_extra = sbr;
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ wlv->n_extra = (int)strlen(sbr);
+ wlv->char_attr = win_hl_attr(wp, HLF_AT);
+ if (wp->w_skipcol == 0 || wlv->startrow != 0 || !wp->w_p_wrap) {
+ wlv->need_showbreak = false;
+ }
+ wlv->vcol_sbr = wlv->vcol + mb_charlen(sbr);
- return win_hl_attr(wp, HLF_N);
+ // Correct start of highlighted area for 'showbreak'.
+ if (wlv->fromcol >= wlv->vcol && wlv->fromcol < wlv->vcol_sbr) {
+ wlv->fromcol = wlv->vcol_sbr;
+ }
+
+ // Correct end of highlighted area for 'showbreak',
+ // required when 'linebreak' is also set.
+ if (wlv->tocol == wlv->vcol) {
+ wlv->tocol += wlv->n_extra;
+ }
+ // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
+ if (wlv->cul_attr) {
+ wlv->char_attr = hl_combine_attr(wlv->cul_attr, wlv->char_attr);
+ }
+ }
}
-static void apply_cursorline_highlight(win_T *wp, linenr_T lnum, int *line_attr, int *cul_attr,
- int *line_attr_lowprio)
+static void apply_cursorline_highlight(win_T *wp, winlinevars_T *wlv)
{
- *cul_attr = win_hl_attr(wp, HLF_CUL);
- HlAttrs ae = syn_attr2entry(*cul_attr);
+ wlv->cul_attr = win_hl_attr(wp, HLF_CUL);
+ HlAttrs ae = syn_attr2entry(wlv->cul_attr);
// We make a compromise here (#7383):
// * low-priority CursorLine if fg is not set
// * high-priority ("same as Vim" priority) CursorLine if fg is set
if (ae.rgb_fg_color == -1 && ae.cterm_fg_color == 0) {
- *line_attr_lowprio = *cul_attr;
+ wlv->line_attr_lowprio = wlv->cul_attr;
} else {
if (!(State & MODE_INSERT) && bt_quickfix(wp->w_buffer)
- && qf_current_entry(wp) == lnum) {
- *line_attr = hl_combine_attr(*cul_attr, *line_attr);
+ && qf_current_entry(wp) == wlv->lnum) {
+ wlv->line_attr = hl_combine_attr(wlv->cul_attr, wlv->line_attr);
} else {
- *line_attr = *cul_attr;
+ wlv->line_attr = wlv->cul_attr;
}
}
}
-static bool check_mb_utf8(int *c, int *u8cc)
+/// Checks if there is more inline virtual text that need to be drawn.
+static bool has_more_inline_virt(winlinevars_T *wlv, ptrdiff_t v)
{
- if (utf_char2len(*c) > 1) {
- *u8cc = 0;
- *c = 0xc0;
+ if (wlv->virt_inline_i < kv_size(wlv->virt_inline)) {
return true;
}
+ DecorState *state = &decor_state;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row != state->row
+ || item->kind != kDecorKindVirtText
+ || item->data.vt->pos != kVPosInline
+ || item->data.vt->width == 0) {
+ continue;
+ }
+ if (item->draw_col >= -1 && item->start_col >= v) {
+ return true;
+ }
+ }
return false;
}
-/// Display line "lnum" of window 'wp' on the screen.
+static void handle_inline_virtual_text(win_T *wp, winlinevars_T *wlv, ptrdiff_t v)
+{
+ while (wlv->n_extra == 0) {
+ if (wlv->virt_inline_i >= kv_size(wlv->virt_inline)) {
+ // need to find inline virtual text
+ wlv->virt_inline = VIRTTEXT_EMPTY;
+ wlv->virt_inline_i = 0;
+ DecorState *state = &decor_state;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ DecorRange *item = &kv_A(state->active, i);
+ if (item->start_row != state->row
+ || item->kind != kDecorKindVirtText
+ || item->data.vt->pos != kVPosInline
+ || item->data.vt->width == 0) {
+ continue;
+ }
+ if (item->draw_col >= -1 && item->start_col == v) {
+ wlv->virt_inline = item->data.vt->data.virt_text;
+ wlv->virt_inline_hl_mode = item->data.vt->hl_mode;
+ item->draw_col = INT_MIN;
+ break;
+ }
+ }
+ if (!kv_size(wlv->virt_inline)) {
+ // no more inline virtual text here
+ break;
+ }
+ } else {
+ // already inside existing inline virtual text with multiple chunks
+ int attr = 0;
+ char *text = next_virt_text_chunk(wlv->virt_inline, &wlv->virt_inline_i, &attr);
+ if (text == NULL) {
+ continue;
+ }
+ wlv->p_extra = text;
+ wlv->n_extra = (int)strlen(text);
+ if (wlv->n_extra == 0) {
+ continue;
+ }
+ wlv->c_extra = NUL;
+ wlv->c_final = NUL;
+ wlv->extra_attr = attr;
+ wlv->n_attr = mb_charlen(text);
+ // If the text didn't reach until the first window
+ // column we need to skip cells.
+ if (wlv->skip_cells > 0) {
+ // FIXME: this should use virt_text_width instead
+ int virt_text_len = wlv->n_attr;
+ if (virt_text_len > wlv->skip_cells) {
+ int len = mb_charlen2bytelen(wlv->p_extra, wlv->skip_cells);
+ wlv->n_extra -= len;
+ wlv->p_extra += len;
+ wlv->n_attr -= wlv->skip_cells;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += wlv->skip_cells;
+ wlv->skip_cells = 0;
+ } else {
+ // the whole text is left of the window, drop
+ // it and advance to the next one
+ wlv->skip_cells -= virt_text_len;
+ // Skipped cells needed to be accounted for in vcol.
+ wlv->skipped_cells += virt_text_len;
+ wlv->n_attr = 0;
+ wlv->n_extra = 0;
+ // go to the start so the next virtual text chunk can be selected.
+ continue;
+ }
+ }
+ assert(wlv->n_extra > 0);
+ wlv->extra_for_extmark = true;
+ }
+ }
+}
+
+static colnr_T get_trailcol(win_T *wp, const char *ptr, const char *line)
+{
+ colnr_T trailcol = MAXCOL;
+ // find start of trailing whitespace
+ if (wp->w_p_lcs_chars.trail) {
+ trailcol = (colnr_T)strlen(ptr);
+ while (trailcol > 0 && ascii_iswhite(ptr[trailcol - 1])) {
+ trailcol--;
+ }
+ trailcol += (colnr_T)(ptr - line);
+ }
+
+ return trailcol;
+}
+
+static colnr_T get_leadcol(win_T *wp, const char *ptr, const char *line)
+{
+ colnr_T leadcol = 0;
+
+ // find end of leading whitespace
+ if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
+ leadcol = 0;
+ while (ascii_iswhite(ptr[leadcol])) {
+ leadcol++;
+ }
+ if (ptr[leadcol] == NUL) {
+ // in a line full of spaces all of them are treated as trailing
+ leadcol = 0;
+ } else {
+ // keep track of the first column not filled with spaces
+ leadcol += (colnr_T)(ptr - line + 1);
+ }
+ }
+
+ return leadcol;
+}
+
+/// Start a screen line at column zero.
+static void win_line_start(win_T *wp, winlinevars_T *wlv, bool save_extra)
+{
+ wlv->col = 0;
+ wlv->off = 0;
+ wlv->need_lbr = false;
+
+ if (save_extra) {
+ // reset the drawing state for the start of a wrapped line
+ wlv->draw_state = WL_START;
+ wlv->saved_n_extra = wlv->n_extra;
+ wlv->saved_p_extra = wlv->p_extra;
+ wlv->saved_extra_for_extmark = wlv->extra_for_extmark;
+ wlv->saved_c_extra = wlv->c_extra;
+ wlv->saved_c_final = wlv->c_final;
+ wlv->need_lbr = true;
+ wlv->saved_char_attr = wlv->char_attr;
+
+ wlv->n_extra = 0;
+ }
+}
+
+/// Called when wlv->draw_state is set to WL_LINE.
+static void win_line_continue(winlinevars_T *wlv)
+{
+ if (wlv->saved_n_extra > 0) {
+ // Continue item from end of wrapped line.
+ wlv->n_extra = wlv->saved_n_extra;
+ wlv->saved_n_extra = 0;
+ wlv->c_extra = wlv->saved_c_extra;
+ wlv->c_final = wlv->saved_c_final;
+ wlv->p_extra = wlv->saved_p_extra;
+ wlv->extra_for_extmark = wlv->saved_extra_for_extmark;
+ wlv->char_attr = wlv->saved_char_attr;
+ } else {
+ wlv->char_attr = 0;
+ }
+}
+
+/// Display line "lnum" of window "wp" on the screen.
/// wp->w_virtcol needs to be valid.
///
/// @param lnum line to display
/// @param startrow first row relative to window grid
/// @param endrow last grid row to be redrawn
-/// @param nochange not updating for changed text
/// @param number_only only update the number column
+/// @param spv 'spell' related variables kept between calls for "wp"
/// @param foldinfo fold info for this line
/// @param[in, out] providers decoration providers active this line
/// items will be disables if they cause errors
/// or explicitly return `false`.
///
/// @return the number of last row the line occupies.
-int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, bool number_only,
- foldinfo_T foldinfo, DecorProviders *providers, char **provider_err)
+int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool number_only, spellvars_T *spv,
+ foldinfo_T foldinfo, DecorProviders *providers)
{
- int c = 0; // init for GCC
- long vcol = 0; // virtual column (for tabs)
- long vcol_sbr = -1; // virtual column after showbreak
- long vcol_prev = -1; // "vcol" of previous character
+ winlinevars_T wlv; // variables passed between functions
+
+ colnr_T vcol_prev = -1; // "wlv.vcol" of previous character
char *line; // current line
char *ptr; // current position in "line"
- int row; // row in the window, excl w_winrow
ScreenGrid *grid = &wp->w_grid; // grid specific to the window
- char extra[57]; // sign, line number and 'fdc' must
- // fit in here
- int n_extra = 0; // number of extra chars
- char *p_extra = NULL; // string of extra chars, plus NUL
- char *p_extra_free = NULL; // p_extra needs to be freed
- int c_extra = NUL; // extra chars, all the same
- int c_final = NUL; // final char, mandatory if set
- int extra_attr = 0; // attributes when n_extra != 0
static char *at_end_str = ""; // used for p_extra when displaying curwin->w_p_lcs_chars.eol
// at end-of-line
- int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
- int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
- bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
-
- // saved "extra" items for when draw_state becomes WL_LINE (again)
- int saved_n_extra = 0;
- char *saved_p_extra = NULL;
- int saved_c_extra = 0;
- int saved_c_final = 0;
- int saved_char_attr = 0;
+ const bool has_fold = foldinfo.fi_level != 0 && foldinfo.fi_lines > 0;
- int n_attr = 0; // chars with special attr
int saved_attr2 = 0; // char_attr saved for n_attr
int n_attr3 = 0; // chars with overruling special attr
int saved_attr3 = 0; // char_attr saved for n_attr3
- int n_skip = 0; // nr of chars to skip for 'nowrap'
-
- int fromcol = -10; // start of inverting
- int tocol = MAXCOL; // end of inverting
int fromcol_prev = -2; // start of inverting after cursor
bool noinvcur = false; // don't invert the cursor
bool lnum_in_visual_area = false;
pos_T pos;
- long v;
+ ptrdiff_t v;
- int char_attr = 0; // attributes for next character
bool attr_pri = false; // char_attr has priority
bool area_highlighting = false; // Visual or incsearch highlighting in this line
int vi_attr = 0; // attributes for Visual and incsearch highlighting
int area_attr = 0; // attributes desired by highlighting
int search_attr = 0; // attributes desired by 'hlsearch'
int vcol_save_attr = 0; // saved attr for 'cursorcolumn'
- int syntax_attr = 0; // attributes desired by syntax
+ int decor_attr = 0; // attributes desired by syntax and extmarks
bool has_syntax = false; // this buffer has syntax highl.
+ int folded_attr = 0; // attributes for folded line
int save_did_emsg;
int eol_hl_off = 0; // 1 if highlighted char after EOL
bool draw_color_col = false; // highlight colorcolumn
int *color_cols = NULL; // pointer to according columns array
- bool has_spell = false; // this buffer has spell checking
#define SPWORDLEN 150
char nextline[SPWORDLEN * 2]; // text with start of the next line
int nextlinecol = 0; // column where nextline[] starts
@@ -680,44 +1033,35 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// starts
int spell_attr = 0; // attributes desired by spelling
int word_end = 0; // last byte with same spell_attr
- static linenr_T checked_lnum = 0; // line number for "checked_col"
- static int checked_col = 0; // column in "checked_lnum" up to which
- // there are no spell errors
- static int cap_col = -1; // column to check for Cap word
- static linenr_T capcol_lnum = 0; // line number where "cap_col"
int cur_checked_col = 0; // checked column for current line
int extra_check = 0; // has syntax or linebreak
int multi_attr = 0; // attributes desired by multibyte
int mb_l = 1; // multi-byte byte length
int mb_c = 0; // decoded multi-byte character
- bool mb_utf8 = false; // screen char is UTF-8 char
- int u8cc[MAX_MCO]; // composing UTF-8 chars
- int filler_lines; // nr of filler lines to be drawn
- int filler_todo; // nr of filler lines still to do + 1
- hlf_T diff_hlf = (hlf_T)0; // type of diff highlighting
+ schar_T mb_schar; // complete screen char
int change_start = MAXCOL; // first col of changed area
int change_end = -1; // last col of changed area
- colnr_T trailcol = MAXCOL; // start of trailing spaces
- colnr_T leadcol = 0; // start of leading spaces
bool in_multispace = false; // in multiple consecutive spaces
int multispace_pos = 0; // position in lcs-multispace string
- bool need_showbreak = false; // overlong line, skip first x chars
- int line_attr = 0; // attribute for the whole line
int line_attr_save;
- int line_attr_lowprio = 0; // low-priority attribute for the line
int line_attr_lowprio_save;
- int prev_c = 0; // previous Arabic character
- int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
bool has_decor = false; // this buffer has decoration
+
+ int saved_search_attr = 0; // search_attr to be used when n_extra goes to zero
+ int saved_area_attr = 0; // idem for area_attr
+ int saved_decor_attr = 0; // idem for decor_attr
+ bool saved_search_attr_from_match = false;
+
int win_col_offset = 0; // offset for window columns
+ bool area_active = false; // whether in Visual selection, for virtual text
+ bool decor_need_recheck = false; // call decor_recheck_draw_col() at next char
char buf_fold[FOLD_TEXT_LEN]; // Hold value returned by get_foldtext
+ VirtText fold_vt = VIRTTEXT_EMPTY;
+ char *foldtext_free = NULL;
- bool area_active = false;
-
- int cul_attr = 0; // set when 'cursorline' active
// 'cursorlineopt' has "screenline" and cursor is in this line
bool cul_screenline = false;
// margin columns for the screen line, needed for when 'cursorlineopt'
@@ -725,39 +1069,40 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int left_curline_col = 0;
int right_curline_col = 0;
- LineDrawState draw_state = WL_START; // what to draw next
-
- int match_conc = 0; ///< cchar for match functions
- bool on_last_col = false;
- int syntax_flags = 0;
- int syntax_seqnr = 0;
- int prev_syntax_id = 0;
- int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
- bool is_concealing = false;
- int boguscols = 0; ///< nonexistent columns added to
- ///< force wrapping
- int vcol_off = 0; ///< offset for concealed characters
- int did_wcol = false;
+ int match_conc = 0; ///< cchar for match functions
+ bool on_last_col = false;
+ int syntax_flags = 0;
+ int syntax_seqnr = 0;
+ int prev_syntax_id = 0;
+ int conceal_attr = win_hl_attr(wp, HLF_CONCEAL);
+ bool is_concealing = false;
+ int did_wcol = false;
int old_boguscols = 0;
-#define VCOL_HLC (vcol - vcol_off)
+#define VCOL_HLC (wlv.vcol - wlv.vcol_off)
#define FIX_FOR_BOGUSCOLS \
{ \
- n_extra += vcol_off; \
- vcol -= vcol_off; \
- vcol_off = 0; \
- col -= boguscols; \
- old_boguscols = boguscols; \
- boguscols = 0; \
+ wlv.n_extra += wlv.vcol_off; \
+ wlv.vcol -= wlv.vcol_off; \
+ wlv.vcol_off = 0; \
+ wlv.col -= wlv.boguscols; \
+ old_boguscols = wlv.boguscols; \
+ wlv.boguscols = 0; \
}
- if (startrow > endrow) { // past the end already!
- return startrow;
- }
+ assert(startrow < endrow);
- row = startrow;
+ CLEAR_FIELD(wlv);
+
+ wlv.lnum = lnum;
+ wlv.foldinfo = foldinfo;
+ wlv.startrow = startrow;
+ wlv.row = startrow;
+ wlv.fromcol = -10;
+ wlv.tocol = MAXCOL;
+ wlv.vcol_sbr = -1;
buf_T *buf = wp->w_buffer;
- bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
+ const bool end_fill = (lnum == buf->b_ml.ml_line_count + 1);
if (!number_only) {
// To speed up the loop below, set extra_check when there is linebreak,
@@ -781,15 +1126,9 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
- has_decor = decor_redraw_line(buf, lnum - 1, &decor_state);
-
- decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor, provider_err);
+ has_decor = decor_redraw_line(wp, lnum - 1, &decor_state);
- if (*provider_err) {
- provider_err_virt_text(lnum, *provider_err);
- has_decor = true;
- *provider_err = NULL;
- }
+ decor_providers_invoke_line(wp, providers, lnum - 1, &has_decor);
if (has_decor) {
extra_check = true;
@@ -798,45 +1137,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Check for columns to display for 'colorcolumn'.
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
if (color_cols != NULL) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
- }
-
- if (wp->w_p_spell
- && !has_fold
- && !end_fill
- && *wp->w_s->b_p_spl != NUL
- && !GA_EMPTY(&wp->w_s->b_langp)
- && *(char **)(wp->w_s->b_langp.ga_data) != NULL) {
- // Prepare for spell checking.
- has_spell = true;
- extra_check = true;
-
- // Get the start of the next line, so that words that wrap to the next
- // line are found too: "et<line-break>al.".
- // Trick: skip a few chars for C/shell/Vim comments
- nextline[SPWORDLEN] = NUL;
- if (lnum < wp->w_buffer->b_ml.ml_line_count) {
- line = ml_get_buf(wp->w_buffer, lnum + 1, false);
- spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
- }
-
- // When a word wrapped from the previous line the start of the current
- // line is valid.
- if (lnum == checked_lnum) {
- cur_checked_col = checked_col;
- }
- checked_lnum = 0;
-
- // When there was a sentence end in the previous line may require a
- // word starting with capital in this line. In line 1 always check
- // the first word.
- if (lnum != capcol_lnum) {
- cap_col = -1;
- }
- if (lnum == 1) {
- cap_col = 0;
- }
- capcol_lnum = 0;
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
// handle Visual active in this window
@@ -856,37 +1157,37 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (VIsual_mode == Ctrl_V) {
// block mode
if (lnum_in_visual_area) {
- fromcol = wp->w_old_cursor_fcol;
- tocol = wp->w_old_cursor_lcol;
+ wlv.fromcol = wp->w_old_cursor_fcol;
+ wlv.tocol = wp->w_old_cursor_lcol;
}
} else {
// non-block mode
if (lnum > top->lnum && lnum <= bot->lnum) {
- fromcol = 0;
+ wlv.fromcol = 0;
} else if (lnum == top->lnum) {
if (VIsual_mode == 'V') { // linewise
- fromcol = 0;
+ wlv.fromcol = 0;
} else {
- getvvcol(wp, top, (colnr_T *)&fromcol, NULL, NULL);
+ getvvcol(wp, top, (colnr_T *)&wlv.fromcol, NULL, NULL);
if (gchar_pos(top) == NUL) {
- tocol = fromcol + 1;
+ wlv.tocol = wlv.fromcol + 1;
}
}
}
if (VIsual_mode != 'V' && lnum == bot->lnum) {
if (*p_sel == 'e' && bot->col == 0
&& bot->coladd == 0) {
- fromcol = -10;
- tocol = MAXCOL;
+ wlv.fromcol = -10;
+ wlv.tocol = MAXCOL;
} else if (bot->col == MAXCOL) {
- tocol = MAXCOL;
+ wlv.tocol = MAXCOL;
} else {
pos = *bot;
if (*p_sel == 'e') {
- getvvcol(wp, &pos, (colnr_T *)&tocol, NULL, NULL);
+ getvvcol(wp, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
} else {
- getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&tocol);
- tocol++;
+ getvvcol(wp, &pos, NULL, NULL, (colnr_T *)&wlv.tocol);
+ wlv.tocol++;
}
}
}
@@ -899,7 +1200,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
// if inverting in this line set area_highlighting
- if (fromcol >= 0) {
+ if (wlv.fromcol >= 0) {
area_highlighting = true;
vi_attr = win_hl_attr(wp, HLF_V);
}
@@ -911,18 +1212,18 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& lnum <= curwin->w_cursor.lnum + search_match_lines) {
if (lnum == curwin->w_cursor.lnum) {
getvcol(curwin, &(curwin->w_cursor),
- (colnr_T *)&fromcol, NULL, NULL);
+ (colnr_T *)&wlv.fromcol, NULL, NULL);
} else {
- fromcol = 0;
+ wlv.fromcol = 0;
}
if (lnum == curwin->w_cursor.lnum + search_match_lines) {
pos.lnum = lnum;
pos.col = search_match_endcol;
- getvcol(curwin, &pos, (colnr_T *)&tocol, NULL, NULL);
+ getvcol(curwin, &pos, (colnr_T *)&wlv.tocol, NULL, NULL);
}
// do at least one character; happens when past end of line
- if (fromcol == tocol && search_match_endcol) {
- tocol = fromcol + 1;
+ if (wlv.fromcol == wlv.tocol && search_match_endcol) {
+ wlv.tocol = wlv.fromcol + 1;
}
area_highlighting = true;
vi_attr = win_hl_attr(wp, HLF_I);
@@ -932,88 +1233,135 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int bg_attr = win_bg_attr(wp);
int linestatus = 0;
- filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
- if (filler_lines < 0 || linestatus < 0) {
- if (filler_lines == -1 || linestatus == -1) {
+ wlv.filler_lines = diff_check_with_linestatus(wp, lnum, &linestatus);
+ if (wlv.filler_lines < 0 || linestatus < 0) {
+ if (wlv.filler_lines == -1 || linestatus == -1) {
if (diff_find_change(wp, lnum, &change_start, &change_end)) {
- diff_hlf = HLF_ADD; // added line
+ wlv.diff_hlf = HLF_ADD; // added line
} else if (change_start == 0) {
- diff_hlf = HLF_TXD; // changed text
+ wlv.diff_hlf = HLF_TXD; // changed text
} else {
- diff_hlf = HLF_CHD; // changed line
+ wlv.diff_hlf = HLF_CHD; // changed line
}
} else {
- diff_hlf = HLF_ADD; // added line
+ wlv.diff_hlf = HLF_ADD; // added line
}
if (linestatus == 0) {
- filler_lines = 0;
+ wlv.filler_lines = 0;
}
area_highlighting = true;
}
VirtLines virt_lines = KV_INITIAL_VALUE;
- int n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold);
- filler_lines += n_virt_lines;
+ wlv.n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold);
+ wlv.filler_lines += wlv.n_virt_lines;
if (lnum == wp->w_topline) {
- filler_lines = wp->w_topfill;
- n_virt_lines = MIN(n_virt_lines, filler_lines);
+ wlv.filler_lines = wp->w_topfill;
+ wlv.n_virt_lines = MIN(wlv.n_virt_lines, wlv.filler_lines);
}
- filler_todo = filler_lines;
+ wlv.filler_todo = wlv.filler_lines;
// Cursor line highlighting for 'cursorline' in the current window.
- if (lnum == wp->w_cursor.lnum) {
- // Do not show the cursor line in the text when Visual mode is active,
- // because it's not clear what is selected then.
- if (wp->w_p_cul && !(wp == curwin && VIsual_active)
- && wp->w_p_culopt_flags != CULOPT_NBR) {
- cul_screenline = (wp->w_p_wrap
- && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
- if (!cul_screenline) {
- apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
- } else {
- margin_columns_win(wp, &left_curline_col, &right_curline_col);
- }
- area_highlighting = true;
+ if (wp->w_p_cul && wp->w_p_culopt_flags != CULOPT_NBR && lnum == wp->w_cursorline
+ // Do not show the cursor line in the text when Visual mode is active,
+ // because it's not clear what is selected then.
+ && !(wp == curwin && VIsual_active)) {
+ cul_screenline = (wp->w_p_wrap && (wp->w_p_culopt_flags & CULOPT_SCRLINE));
+ if (!cul_screenline) {
+ apply_cursorline_highlight(wp, &wlv);
+ } else {
+ margin_columns_win(wp, &left_curline_col, &right_curline_col);
}
+ area_highlighting = true;
}
- SignTextAttrs sattrs[SIGN_SHOW_MAX]; // sign attributes for the sign column
- int sign_num_attr = 0; // sign attribute for the number column
- int sign_cul_attr = 0; // sign attribute for cursorline
- CLEAR_FIELD(sattrs);
- int num_signs = get_sign_attrs(buf, lnum, sattrs, &line_attr, &sign_num_attr, &sign_cul_attr);
+ int line_attr = 0;
+ int sign_cul_attr = 0;
+ int sign_num_attr = 0;
+ // TODO(bfredl, vigoux): line_attr should not take priority over decoration!
+ decor_redraw_signs(wp, buf, wlv.lnum - 1, wlv.sattrs, &line_attr, &sign_cul_attr, &sign_num_attr);
+
+ statuscol_T statuscol = { 0 };
+ if (*wp->w_p_stc != NUL) {
+ // Draw the 'statuscolumn' if option is set.
+ statuscol.draw = true;
+ statuscol.sattrs = wlv.sattrs;
+ statuscol.foldinfo = foldinfo;
+ statuscol.width = win_col_off(wp) - (cmdwin_type != 0 && wp == curwin);
+ statuscol.use_cul = use_cursor_line_highlight(wp, lnum);
+ statuscol.sign_cul_id = statuscol.use_cul ? sign_cul_attr : 0;
+ statuscol.num_attr = sign_num_attr > 0 ? syn_id2attr(sign_num_attr) : 0;
+ } else {
+ if (sign_cul_attr > 0) {
+ sign_cul_attr = syn_id2attr(sign_cul_attr);
+ }
+ if (sign_num_attr > 0) {
+ sign_num_attr = syn_id2attr(sign_num_attr);
+ }
+ }
+ if (line_attr > 0) {
+ wlv.line_attr = syn_id2attr(line_attr);
+ }
// Highlight the current line in the quickfix window.
if (bt_quickfix(wp->w_buffer) && qf_current_entry(wp) == lnum) {
- line_attr = win_hl_attr(wp, HLF_QFL);
+ wlv.line_attr = win_hl_attr(wp, HLF_QFL);
}
- if (line_attr_lowprio || line_attr) {
+ if (wlv.line_attr_lowprio || wlv.line_attr) {
area_highlighting = true;
}
if (cul_screenline) {
- line_attr_save = line_attr;
- line_attr_lowprio_save = line_attr_lowprio;
+ line_attr_save = wlv.line_attr;
+ line_attr_lowprio_save = wlv.line_attr_lowprio;
}
- line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum, false);
- ptr = line;
-
- if (has_spell && !number_only) {
- // For checking first word with a capital skip white space.
- if (cap_col == 0) {
- cap_col = (int)getwhitecols(line);
- }
+ if (spv->spv_has_spell && !number_only) {
+ // Prepare for spell checking.
+ extra_check = true;
- // To be able to spell-check over line boundaries copy the end of the
- // current line into nextline[]. Above the start of the next line was
- // copied to nextline[SPWORDLEN].
+ // When a word wrapped from the previous line the start of the
+ // current line is valid.
+ if (lnum == spv->spv_checked_lnum) {
+ cur_checked_col = spv->spv_checked_col;
+ }
+ // Previous line was not spell checked, check for capital. This happens
+ // for the first line in an updated region or after a closed fold.
+ if (spv->spv_capcol_lnum == 0 && check_need_cap(wp, lnum, 0)) {
+ spv->spv_cap_col = 0;
+ } else if (lnum != spv->spv_capcol_lnum) {
+ spv->spv_cap_col = -1;
+ }
+ spv->spv_checked_lnum = 0;
+
+ // Get the start of the next line, so that words that wrap to the
+ // next line are found too: "et<line-break>al.".
+ // Trick: skip a few chars for C/shell/Vim comments
+ nextline[SPWORDLEN] = NUL;
+ if (lnum < wp->w_buffer->b_ml.ml_line_count) {
+ line = ml_get_buf(wp->w_buffer, lnum + 1);
+ spell_cat_line(nextline + SPWORDLEN, line, SPWORDLEN);
+ }
+ assert(!end_fill);
+ line = ml_get_buf(wp->w_buffer, lnum);
+
+ // If current line is empty, check first word in next line for capital.
+ ptr = skipwhite(line);
+ if (*ptr == NUL) {
+ spv->spv_cap_col = 0;
+ spv->spv_capcol_lnum = lnum + 1;
+ } else if (spv->spv_cap_col == 0) {
+ // For checking first word with a capital skip white space.
+ spv->spv_cap_col = (int)(ptr - line);
+ }
+
+ // Copy the end of the current line into nextline[].
if (nextline[SPWORDLEN] == NUL) {
// No next line or it is empty.
nextlinecol = MAXCOL;
nextline_idx = 0;
} else {
- v = (long)strlen(line);
+ v = (ptrdiff_t)strlen(line);
if (v < SPWORDLEN) {
// Short line, use it completely and append the start of the
// next line.
@@ -1024,12 +1372,21 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
} else {
// Long line, use only the last SPWORDLEN bytes.
nextlinecol = (int)v - SPWORDLEN;
- memmove(nextline, line + nextlinecol, SPWORDLEN); // -V1086
+ memmove(nextline, line + nextlinecol, SPWORDLEN);
nextline_idx = SPWORDLEN + 1;
}
}
}
+ line = end_fill ? "" : ml_get_buf(wp->w_buffer, lnum);
+ ptr = line;
+
+ colnr_T trailcol = MAXCOL; // start of trailing spaces
+ colnr_T leadcol = 0; // start of leading spaces
+
+ int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used
+ int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used
+
if (wp->w_p_list && !has_fold && !end_fill) {
if (wp->w_p_lcs_chars.space
|| wp->w_p_lcs_chars.multispace != NULL
@@ -1039,50 +1396,52 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
|| wp->w_p_lcs_chars.nbsp) {
extra_check = true;
}
- // find start of trailing whitespace
- if (wp->w_p_lcs_chars.trail) {
- trailcol = (colnr_T)strlen(ptr);
- while (trailcol > (colnr_T)0 && ascii_iswhite(ptr[trailcol - 1])) {
- trailcol--;
- }
- trailcol += (colnr_T)(ptr - line);
- }
- // find end of leading whitespace
- if (wp->w_p_lcs_chars.lead || wp->w_p_lcs_chars.leadmultispace != NULL) {
- leadcol = 0;
- while (ascii_iswhite(ptr[leadcol])) {
- leadcol++;
- }
- if (ptr[leadcol] == NUL) {
- // in a line full of spaces all of them are treated as trailing
- leadcol = (colnr_T)0;
- } else {
- // keep track of the first column not filled with spaces
- leadcol += (colnr_T)(ptr - line) + 1;
- }
- }
+ trailcol = get_trailcol(wp, ptr, line);
+ leadcol = get_leadcol(wp, ptr, line);
}
// 'nowrap' or 'wrap' and a single line that doesn't fit: Advance to the
// first character to be displayed.
if (wp->w_p_wrap) {
- v = wp->w_skipcol;
+ v = startrow == 0 ? wp->w_skipcol : 0;
} else {
v = wp->w_leftcol;
}
if (v > 0 && !number_only) {
char *prev_ptr = ptr;
chartabsize_T cts;
- int charsize;
+ int charsize = 0;
+ int head = 0;
- init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, ptr);
+ init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, ptr);
+ cts.cts_max_head_vcol = (int)v;
while (cts.cts_vcol < v && *cts.cts_ptr != NUL) {
- charsize = win_lbr_chartabsize(&cts, NULL);
+ head = 0;
+ charsize = win_lbr_chartabsize(&cts, &head);
cts.cts_vcol += charsize;
prev_ptr = cts.cts_ptr;
MB_PTR_ADV(cts.cts_ptr);
+ if (wp->w_p_list) {
+ in_multispace = *prev_ptr == ' ' && (*cts.cts_ptr == ' '
+ || (prev_ptr > line && prev_ptr[-1] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ } else if (cts.cts_ptr >= line + leadcol
+ && wp->w_p_lcs_chars.multispace != NULL) {
+ multispace_pos++;
+ if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ } else if (cts.cts_ptr < line + leadcol
+ && wp->w_p_lcs_chars.leadmultispace != NULL) {
+ multispace_pos++;
+ if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
+ multispace_pos = 0;
+ }
+ }
+ }
}
- vcol = cts.cts_vcol;
+ wlv.vcol = cts.cts_vcol;
ptr = cts.cts_ptr;
clear_chartabsize_arg(&cts);
@@ -1092,40 +1451,39 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// - 'virtualedit' is set, or
// - the visual mode is active,
// the end of the line may be before the start of the displayed part.
- if (vcol < v && (wp->w_p_cuc
- || draw_color_col
- || virtual_active()
- || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
- vcol = v;
+ if (wlv.vcol < v && (wp->w_p_cuc
+ || draw_color_col
+ || virtual_active()
+ || (VIsual_active && wp->w_buffer == curwin->w_buffer))) {
+ wlv.vcol = (colnr_T)v;
}
// Handle a character that's not completely on the screen: Put ptr at
// that character but skip the first few screen characters.
- if (vcol > v) {
- vcol -= charsize;
+ if (wlv.vcol > v) {
+ wlv.vcol -= charsize;
ptr = prev_ptr;
- // If the character fits on the screen, don't need to skip it.
- // Except for a TAB.
- if (utf_ptr2cells(ptr) >= charsize || *ptr == TAB) {
- n_skip = (int)(v - vcol);
- }
+ }
+
+ if (v > wlv.vcol) {
+ wlv.skip_cells = (int)v - wlv.vcol - head;
}
// Adjust for when the inverted text is before the screen,
// and when the start of the inverted text is before the screen.
- if (tocol <= vcol) {
- fromcol = 0;
- } else if (fromcol >= 0 && fromcol < vcol) {
- fromcol = (int)vcol;
+ if (wlv.tocol <= wlv.vcol) {
+ wlv.fromcol = 0;
+ } else if (wlv.fromcol >= 0 && wlv.fromcol < wlv.vcol) {
+ wlv.fromcol = wlv.vcol;
}
// When w_skipcol is non-zero, first line needs 'showbreak'
if (wp->w_p_wrap) {
- need_showbreak = true;
+ wlv.need_showbreak = true;
}
// When spell checking a word we need to figure out the start of the
// word and if it's badly spelled or not.
- if (has_spell) {
+ if (spv->spv_has_spell) {
size_t len;
colnr_T linecol = (colnr_T)(ptr - line);
hlf_T spell_hlf = HLF_COUNT;
@@ -1136,7 +1494,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
len = spell_move_to(wp, FORWARD, true, true, &spell_hlf);
// spell_move_to() may call ml_get() and make "line" invalid
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + linecol;
if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
@@ -1165,20 +1523,20 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Correct highlighting for cursor that can't be disabled.
// Avoids having to check this for each character.
- if (fromcol >= 0) {
+ if (wlv.fromcol >= 0) {
if (noinvcur) {
- if ((colnr_T)fromcol == wp->w_virtcol) {
+ if ((colnr_T)wlv.fromcol == wp->w_virtcol) {
// highlighting starts at cursor, let it start just after the
// cursor
- fromcol_prev = fromcol;
- fromcol = -1;
- } else if ((colnr_T)fromcol < wp->w_virtcol) {
+ fromcol_prev = wlv.fromcol;
+ wlv.fromcol = -1;
+ } else if ((colnr_T)wlv.fromcol < wp->w_virtcol) {
// restart highlighting after the cursor
fromcol_prev = wp->w_virtcol;
}
}
- if (fromcol >= tocol) {
- fromcol = -1;
+ if (wlv.fromcol >= wlv.tocol) {
+ wlv.fromcol = -1;
}
}
@@ -1190,15 +1548,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
ptr = line + v; // "line" may have been updated
}
- int off = 0; // Offset relative start of line
- int col = 0; // Visual column on screen.
- if (wp->w_p_rl) {
- // Rightleft window: process the text in the normal direction, but put
- // it in linebuf_char[off] from right to left. Start at the
- // rightmost column of the window.
- col = grid->cols - 1;
- off += col;
- }
+ win_line_start(wp, &wlv, false);
// won't highlight after TERM_ATTRS_MAX columns
int term_attrs[TERM_ATTRS_MAX] = { 0 };
@@ -1207,375 +1557,230 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
extra_check = true;
}
- statuscol_T statuscol = { 0 };
- if (*wp->w_p_stc != NUL) {
- // Draw the 'statuscolumn' if option is set.
- statuscol.draw = true;
- statuscol.width = win_col_off(wp);
- }
-
int sign_idx = 0;
int virt_line_index;
int virt_line_offset = -1;
// Repeat for the whole displayed line.
- for (;;) {
+ while (true) {
int has_match_conc = 0; ///< match wants to conceal
int decor_conceal = 0;
bool did_decrement_ptr = false;
// Skip this quickly when working on the text.
- if (draw_state != WL_LINE) {
+ if (wlv.draw_state != WL_LINE) {
if (cul_screenline) {
- cul_attr = 0;
- line_attr = line_attr_save;
- line_attr_lowprio = line_attr_lowprio_save;
+ wlv.cul_attr = 0;
+ wlv.line_attr = line_attr_save;
+ wlv.line_attr_lowprio = line_attr_lowprio_save;
}
- if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
- draw_state = WL_CMDLINE;
+ if (wlv.draw_state == WL_CMDLINE - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_CMDLINE;
if (cmdwin_type != 0 && wp == curwin) {
// Draw the cmdline character.
- n_extra = 1;
- c_extra = cmdwin_type;
- c_final = NUL;
- char_attr = win_hl_attr(wp, HLF_AT);
+ wlv.n_extra = 1;
+ wlv.c_extra = cmdwin_type;
+ wlv.c_final = NUL;
+ wlv.char_attr = win_hl_attr(wp, HLF_AT);
}
}
- if (draw_state == WL_FOLD - 1 && n_extra == 0) {
- if (filler_todo > 0) {
- int index = filler_todo - (filler_lines - n_virt_lines);
+ if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) {
+ if (wlv.filler_todo > 0) {
+ int index = wlv.filler_todo - (wlv.filler_lines - wlv.n_virt_lines);
if (index > 0) {
virt_line_index = (int)kv_size(virt_lines) - index;
assert(virt_line_index >= 0);
virt_line_offset = kv_A(virt_lines, virt_line_index).left_col ? 0 : win_col_off(wp);
}
}
- if (!virt_line_offset) {
+ if (virt_line_offset == 0) {
// Skip the column states if there is a "virt_left_col" line.
- draw_state = WL_BRI - 1;
+ wlv.draw_state = WL_BRI - 1;
} else if (statuscol.draw) {
// Skip fold, sign and number states if 'statuscolumn' is set.
- draw_state = WL_STC - 1;
- }
- }
-
- if (draw_state == WL_FOLD - 1 && n_extra == 0) {
- int fdc = compute_foldcolumn(wp, 0);
- draw_state = WL_FOLD;
- if (fdc > 0) {
- // Draw the 'foldcolumn'. Allocate a buffer, "extra" may
- // already be in use.
- xfree(p_extra_free);
- p_extra_free = xmalloc(MAX_MCO * (size_t)fdc + 1);
- n_extra = (int)fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
- p_extra_free[n_extra] = NUL;
- p_extra = p_extra_free;
- c_extra = NUL;
- c_final = NUL;
- if (use_cursor_line_sign(wp, lnum)) {
- char_attr = win_hl_attr(wp, HLF_CLF);
- } else {
- char_attr = win_hl_attr(wp, HLF_FC);
- }
+ wlv.draw_state = WL_STC - 1;
}
}
+ if (wlv.draw_state == WL_FOLD - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_FOLD;
+ handle_foldcolumn(wp, &wlv);
+ }
+
// sign column, this is hit until sign_idx reaches count
- if (draw_state == WL_SIGN - 1 && n_extra == 0) {
- draw_state = WL_SIGN;
- // Show the sign column when there are any signs in this buffer
+ if (wlv.draw_state == WL_SIGN - 1 && wlv.n_extra == 0) {
+ // Show the sign column when desired.
+ wlv.draw_state = WL_SIGN;
if (wp->w_scwidth > 0) {
- get_sign_display_info(false, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx,
- sign_cul_attr);
- sign_idx++;
- if (sign_idx < wp->w_scwidth) {
- draw_state = WL_SIGN - 1;
+ get_sign_display_info(false, wp, &wlv, sign_idx, sign_cul_attr);
+ if (++sign_idx < wp->w_scwidth) {
+ wlv.draw_state = WL_SIGN - 1;
} else {
sign_idx = 0;
}
}
}
- if (draw_state == WL_NR - 1 && n_extra == 0) {
- draw_state = WL_NR;
- // Display the absolute or relative line number. After the
- // first fill with blanks when the 'n' flag isn't in 'cpo'
- if ((wp->w_p_nu || wp->w_p_rnu)
- && (row == startrow + filler_lines
- || vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
- // If 'signcolumn' is set to 'number' and a sign is present
- // in 'lnum', then display the sign instead of the line
- // number.
- if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u' && num_signs > 0) {
- get_sign_display_info(true, wp, lnum, sattrs, row,
- startrow, filler_lines, filler_todo,
- &c_extra, &c_final, extra, sizeof(extra),
- &p_extra, &n_extra, &char_attr, sign_idx,
- sign_cul_attr);
- } else {
- // Draw the line number (empty space after wrapping).
- if (row == startrow + filler_lines) {
- get_line_number_str(wp, lnum, extra, sizeof(extra));
- if (wp->w_skipcol > 0) {
- for (p_extra = extra; *p_extra == ' '; p_extra++) {
- *p_extra = '-';
- }
- }
- if (wp->w_p_rl) { // reverse line numbers
- // like rl_mirror(), but keep the space at the end
- char *p2 = skipwhite(extra);
- p2 = skiptowhite(p2) - 1;
- for (char *p1 = skipwhite(extra); p1 < p2; p1++, p2--) {
- const char t = *p1;
- *p1 = *p2;
- *p2 = t;
- }
- }
- p_extra = extra;
- c_extra = NUL;
- } else {
- c_extra = ' ';
- }
- c_final = NUL;
- n_extra = number_width(wp) + 1;
- if (sign_num_attr > 0) {
- char_attr = sign_num_attr;
- } else {
- char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines);
- }
- }
- }
+ if (wlv.draw_state == WL_NR - 1 && wlv.n_extra == 0) {
+ // Show the line number, if desired.
+ wlv.draw_state = WL_NR;
+ handle_lnum_col(wp, &wlv, sign_num_attr, sign_cul_attr);
}
- if (draw_state == WL_STC - 1 && n_extra == 0) {
- draw_state = WL_STC;
+ if (wlv.draw_state == WL_STC - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_STC;
// Draw the 'statuscolumn' if option is set.
if (statuscol.draw) {
+ if (sign_num_attr == 0) {
+ statuscol.num_attr = get_line_number_attr(wp, &wlv);
+ }
if (statuscol.textp == NULL) {
- get_statuscol_str(wp, lnum, row, startrow, filler_lines, cul_attr,
- sign_num_attr, sign_cul_attr, &statuscol, foldinfo, sattrs);
+ v = (ptr - line);
+ get_statuscol_str(wp, lnum, wlv.row - startrow - wlv.filler_lines, &statuscol);
+ if (!end_fill) {
+ // Get the line again as evaluating 'statuscolumn' may free it.
+ line = ml_get_buf(wp->w_buffer, lnum);
+ ptr = line + v;
+ }
if (wp->w_redr_statuscol) {
break;
}
}
- get_statuscol_display_info(&statuscol, &draw_state, &char_attr,
- &n_extra, &c_extra, &c_final, &p_extra);
+ get_statuscol_display_info(&statuscol, &wlv);
}
}
- if (draw_state == WL_STC && n_extra == 0) {
- win_col_offset = off;
+ if (wlv.draw_state == WL_STC && wlv.n_extra == 0) {
+ win_col_offset = wlv.off;
}
- if (wp->w_briopt_sbr && draw_state == WL_BRI - 1
- && n_extra == 0 && *get_showbreak_value(wp) != NUL) {
- // draw indent after showbreak value
- draw_state = WL_BRI;
- } else if (wp->w_briopt_sbr && draw_state == WL_SBR && n_extra == 0) {
- // after the showbreak, draw the breakindent
- draw_state = WL_BRI - 1;
- }
-
- // draw 'breakindent': indent wrapped text accordingly
- if (draw_state == WL_BRI - 1 && n_extra == 0) {
- draw_state = WL_BRI;
- // if need_showbreak is set, breakindent also applies
- if (wp->w_p_bri && (row != startrow || need_showbreak)
- && filler_lines == 0) {
- char_attr = 0;
-
- if (diff_hlf != (hlf_T)0) {
- char_attr = win_hl_attr(wp, (int)diff_hlf);
- }
- p_extra = NULL;
- c_extra = ' ';
- c_final = NUL;
- n_extra =
- get_breakindent_win(wp, ml_get_buf(wp->w_buffer, lnum, false));
- if (row == startrow) {
- n_extra -= win_col_off2(wp);
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
- if (wp->w_skipcol > 0 && wp->w_p_wrap && wp->w_briopt_sbr) {
- need_showbreak = false;
- }
- // Correct end of highlighted area for 'breakindent',
- // required wen 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- }
+ // Check if 'breakindent' applies and show it.
+ // May change wlv.draw_state to WL_BRI or WL_BRI - 1.
+ if (wlv.n_extra == 0) {
+ handle_breakindent(wp, &wlv);
}
- if (draw_state == WL_SBR - 1 && n_extra == 0) {
- draw_state = WL_SBR;
- if (filler_todo > filler_lines - n_virt_lines) {
- // TODO(bfredl): check this doesn't inhibit TUI-style
- // clear-to-end-of-line.
- c_extra = ' ';
- c_final = NUL;
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = 0;
- } else if (filler_todo > 0) {
- // Draw "deleted" diff line(s)
- if (char2cells(wp->w_p_fcs_chars.diff) > 1) {
- c_extra = '-';
- c_final = NUL;
- } else {
- c_extra = wp->w_p_fcs_chars.diff;
- c_final = NUL;
- }
- if (wp->w_p_rl) {
- n_extra = col + 1;
- } else {
- n_extra = grid->cols - col;
- }
- char_attr = win_hl_attr(wp, HLF_DED);
- }
- char *const sbr = get_showbreak_value(wp);
- if (*sbr != NUL && need_showbreak) {
- // Draw 'showbreak' at the start of each broken line.
- p_extra = sbr;
- c_extra = NUL;
- c_final = NUL;
- n_extra = (int)strlen(sbr);
- char_attr = win_hl_attr(wp, HLF_AT);
- if (wp->w_skipcol == 0 || !wp->w_p_wrap) {
- need_showbreak = false;
- }
- vcol_sbr = vcol + mb_charlen(sbr);
- // Correct end of highlighted area for 'showbreak',
- // required when 'linebreak' is also set.
- if (tocol == vcol) {
- tocol += n_extra;
- }
- // Combine 'showbreak' with 'cursorline', prioritizing 'showbreak'.
- if (cul_attr) {
- char_attr = hl_combine_attr(cul_attr, char_attr);
- }
- }
+ if (wlv.draw_state == WL_SBR - 1 && wlv.n_extra == 0) {
+ wlv.draw_state = WL_SBR;
+ handle_showbreak_and_filler(wp, &wlv);
}
- if (draw_state == WL_LINE - 1 && n_extra == 0) {
+ if (wlv.draw_state == WL_LINE - 1 && wlv.n_extra == 0) {
sign_idx = 0;
- draw_state = WL_LINE;
-
- if (has_decor && row == startrow + filler_lines) {
- // hide virt_text on text hidden by 'nowrap'
- decor_redraw_col(wp->w_buffer, (int)vcol, off, true, &decor_state);
- }
-
- if (saved_n_extra) {
- // Continue item from end of wrapped line.
- n_extra = saved_n_extra;
- c_extra = saved_c_extra;
- c_final = saved_c_final;
- p_extra = saved_p_extra;
- char_attr = saved_char_attr;
- } else {
- char_attr = 0;
+ wlv.draw_state = WL_LINE;
+ if (has_decor && wlv.row == startrow + wlv.filler_lines) {
+ // hide virt_text on text hidden by 'nowrap' or 'smoothscroll'
+ decor_redraw_col(wp, (colnr_T)(ptr - line) - 1, wlv.off, true, &decor_state);
}
+ win_line_continue(&wlv); // use wlv.saved_ values
}
}
- if (cul_screenline && draw_state == WL_LINE
- && vcol >= left_curline_col
- && vcol < right_curline_col) {
- apply_cursorline_highlight(wp, lnum, &line_attr, &cul_attr, &line_attr_lowprio);
+ if (cul_screenline && wlv.draw_state == WL_LINE
+ && wlv.vcol >= left_curline_col
+ && wlv.vcol < right_curline_col) {
+ apply_cursorline_highlight(wp, &wlv);
}
// When still displaying '$' of change command, stop at cursor
if (((dollar_vcol >= 0
&& wp == curwin
&& lnum == wp->w_cursor.lnum
- && vcol >= (long)wp->w_virtcol)
- || (number_only && draw_state > WL_STC))
- && filler_todo <= 0) {
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, -grid->cols, wp->w_p_rl, wp, bg_attr, false);
+ && wlv.vcol >= wp->w_virtcol)
+ || (number_only && wlv.draw_state > WL_STC))
+ && wlv.filler_todo <= 0) {
+ draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
+ // don't clear anything after wlv.col
+ win_put_linebuf(wp, wlv.row, 0, wlv.col, wlv.col, bg_attr, false);
// Pretend we have finished updating the window. Except when
// 'cursorcolumn' is set.
if (wp->w_p_cuc) {
- row = wp->w_cline_row + wp->w_cline_height;
+ wlv.row = wp->w_cline_row + wp->w_cline_height;
} else {
- row = grid->rows;
+ wlv.row = grid->rows;
}
break;
}
- if (draw_state == WL_LINE
- && has_fold
- && col == win_col_offset
- && n_extra == 0
- && row == startrow + filler_lines) {
- char_attr = win_hl_attr(wp, HLF_FL);
-
- linenr_T lnume = lnum + foldinfo.fi_lines - 1;
- memset(buf_fold, ' ', FOLD_TEXT_LEN);
- p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
- n_extra = (int)strlen(p_extra);
-
- if (p_extra != buf_fold) {
- xfree(p_extra_free);
- p_extra_free = p_extra;
- }
- c_extra = NUL;
- c_final = NUL;
- p_extra[n_extra] = NUL;
+ const bool draw_folded = wlv.draw_state == WL_LINE && has_fold
+ && wlv.row == startrow + wlv.filler_lines;
+ if (draw_folded && wlv.n_extra == 0) {
+ wlv.char_attr = folded_attr = win_hl_attr(wp, HLF_FL);
}
- if (draw_state == WL_LINE
- && has_fold
- && col < grid->cols
- && n_extra == 0
- && row == startrow + filler_lines) {
- // fill rest of line with 'fold'
- c_extra = wp->w_p_fcs_chars.fold;
- c_final = NUL;
+ int extmark_attr = 0;
+ if (wlv.draw_state == WL_LINE
+ && (area_highlighting || spv->spv_has_spell || extra_check)) {
+ if (wlv.n_extra == 0 || !wlv.extra_for_extmark) {
+ wlv.reset_extra_attr = false;
+ }
- n_extra = wp->w_p_rl ? (col + 1) : (grid->cols - col);
- }
+ if (has_decor && wlv.n_extra == 0) {
+ // Duplicate the Visual area check after this block,
+ // but don't check inside p_extra here.
+ if (wlv.vcol == wlv.fromcol
+ || (wlv.vcol + 1 == wlv.fromcol
+ && (wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1))
+ || (vcol_prev == fromcol_prev
+ && vcol_prev < wlv.vcol
+ && wlv.vcol < wlv.tocol)) {
+ area_active = true;
+ } else if (area_active
+ && (wlv.vcol == wlv.tocol
+ || (noinvcur && wlv.vcol == wp->w_virtcol))) {
+ area_active = false;
+ }
+
+ bool selected = (area_active || (area_highlighting && noinvcur
+ && wlv.vcol == wp->w_virtcol));
+ if (decor_need_recheck) {
+ decor_recheck_draw_col(wlv.off, selected, &decor_state);
+ decor_need_recheck = false;
+ }
+ extmark_attr = decor_redraw_col(wp, (colnr_T)v, wlv.off, selected, &decor_state);
+
+ if (!has_fold && wp->w_buffer->b_virt_text_inline > 0) {
+ handle_inline_virtual_text(wp, &wlv, v);
+ if (wlv.n_extra > 0 && wlv.virt_inline_hl_mode <= kHlModeReplace) {
+ // restore search_attr and area_attr when n_extra is down to zero
+ // TODO(bfredl): this is ugly as fuck. look if we can do this some other way.
+ saved_search_attr = search_attr;
+ saved_area_attr = area_attr;
+ saved_decor_attr = decor_attr;
+ saved_search_attr_from_match = search_attr_from_match;
+ search_attr = 0;
+ area_attr = 0;
+ decor_attr = 0;
+ search_attr_from_match = false;
+ }
+ }
+ }
- if (draw_state == WL_LINE
- && has_fold
- && col >= grid->cols
- && n_extra != 0
- && row == startrow + filler_lines) {
- // Truncate the folding.
- n_extra = 0;
- }
+ int *area_attr_p
+ = wlv.extra_for_extmark && wlv.virt_inline_hl_mode <= kHlModeReplace
+ ? &saved_area_attr : &area_attr;
- if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
// handle Visual or match highlighting in this line
- if (vcol == fromcol
- || (vcol + 1 == fromcol && n_extra == 0
- && utf_ptr2cells(ptr) > 1)
- || ((int)vcol_prev == fromcol_prev
- && vcol_prev < vcol // not at margin
- && vcol < tocol)) {
- area_attr = vi_attr; // start highlighting
- if (area_highlighting) {
- area_active = true;
- }
- } else if (area_attr != 0 && (vcol == tocol
- || (noinvcur
- && (colnr_T)vcol == wp->w_virtcol))) {
- area_attr = 0; // stop highlighting
+ if (wlv.vcol == wlv.fromcol
+ || (wlv.vcol + 1 == wlv.fromcol
+ && ((wlv.n_extra == 0 && utf_ptr2cells(ptr) > 1)
+ || (wlv.n_extra > 0 && wlv.p_extra != NULL
+ && utf_ptr2cells(wlv.p_extra) > 1)))
+ || (vcol_prev == fromcol_prev
+ && vcol_prev < wlv.vcol // not at margin
+ && wlv.vcol < wlv.tocol)) {
+ *area_attr_p = vi_attr; // start highlighting
+ area_active = true;
+ } else if (*area_attr_p != 0
+ && (wlv.vcol == wlv.tocol
+ || (noinvcur && wlv.vcol == wp->w_virtcol))) {
+ *area_attr_p = 0; // stop highlighting
area_active = false;
}
- if (!n_extra) {
+ if (!has_fold && wlv.n_extra == 0) {
// Check for start/end of 'hlsearch' and other matches.
// After end, check for start/end of next match.
// When another match, have to check for start again.
@@ -1592,22 +1797,24 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
}
}
- if (diff_hlf != (hlf_T)0) {
- if (diff_hlf == HLF_CHD && ptr - line >= change_start
- && n_extra == 0) {
- diff_hlf = HLF_TXD; // changed text
+ if (wlv.diff_hlf != (hlf_T)0) {
+ // When there is extra text (eg: virtual text) it gets the
+ // diff highlighting for the line, but not for changed text.
+ if (wlv.diff_hlf == HLF_CHD && ptr - line >= change_start
+ && wlv.n_extra == 0) {
+ wlv.diff_hlf = HLF_TXD; // changed text
}
- if (diff_hlf == HLF_TXD && ptr - line > change_end
- && n_extra == 0) {
- diff_hlf = HLF_CHD; // changed line
+ if (wlv.diff_hlf == HLF_TXD && ((ptr - line > change_end && wlv.n_extra == 0)
+ || (wlv.n_extra > 0 && wlv.extra_for_extmark))) {
+ wlv.diff_hlf = HLF_CHD; // changed line
}
- line_attr = win_hl_attr(wp, (int)diff_hlf);
+ wlv.line_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
// Overlay CursorLine onto diff-mode highlight.
- if (cul_attr) {
- line_attr = 0 != line_attr_lowprio // Low-priority CursorLine
- ? hl_combine_attr(hl_combine_attr(cul_attr, line_attr),
- hl_get_underline())
- : hl_combine_attr(line_attr, cul_attr);
+ if (wlv.cul_attr) {
+ wlv.line_attr = 0 != wlv.line_attr_lowprio // Low-priority CursorLine
+ ? hl_combine_attr(hl_combine_attr(wlv.cul_attr, wlv.line_attr),
+ hl_get_underline())
+ : hl_combine_attr(wlv.line_attr, wlv.cul_attr);
}
}
@@ -1615,29 +1822,61 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
attr_pri = true;
if (area_attr != 0) {
- char_attr = hl_combine_attr(line_attr, area_attr);
+ wlv.char_attr = hl_combine_attr(wlv.line_attr, area_attr);
if (!highlight_match) {
// let search highlight show in Visual area if possible
- char_attr = hl_combine_attr(search_attr, char_attr);
+ wlv.char_attr = hl_combine_attr(search_attr, wlv.char_attr);
}
} else if (search_attr != 0) {
- char_attr = hl_combine_attr(line_attr, search_attr);
- } else if (line_attr != 0 && ((fromcol == -10 && tocol == MAXCOL)
- || vcol < fromcol || vcol_prev < fromcol_prev
- || vcol >= tocol)) {
- // Use line_attr when not in the Visual or 'incsearch' area
+ wlv.char_attr = hl_combine_attr(wlv.line_attr, search_attr);
+ } else if (wlv.line_attr != 0
+ && ((wlv.fromcol == -10 && wlv.tocol == MAXCOL)
+ || wlv.vcol < wlv.fromcol
+ || vcol_prev < fromcol_prev
+ || wlv.vcol >= wlv.tocol)) {
+ // Use wlv.line_attr when not in the Visual or 'incsearch' area
// (area_attr may be 0 when "noinvcur" is set).
- char_attr = line_attr;
+ wlv.char_attr = wlv.line_attr;
} else {
attr_pri = false;
- if (has_syntax) {
- char_attr = syntax_attr;
- } else {
- char_attr = 0;
- }
+ wlv.char_attr = decor_attr;
+ }
+
+ if (folded_attr != 0) {
+ wlv.char_attr = hl_combine_attr(folded_attr, wlv.char_attr);
}
}
+ if (draw_folded && wlv.n_extra == 0 && wlv.col == win_col_offset) {
+ linenr_T lnume = lnum + foldinfo.fi_lines - 1;
+ memset(buf_fold, ' ', FOLD_TEXT_LEN);
+ wlv.p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold, &fold_vt);
+ wlv.n_extra = (int)strlen(wlv.p_extra);
+
+ if (wlv.p_extra != buf_fold) {
+ foldtext_free = wlv.p_extra;
+ }
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
+ wlv.p_extra[wlv.n_extra] = NUL;
+
+ // Get the line again as evaluating 'foldtext' may free it.
+ line = ml_get_buf(wp->w_buffer, lnum);
+ ptr = line + v;
+ }
+
+ if (draw_folded && wlv.n_extra == 0 && wlv.col < grid->cols) {
+ // Fill rest of line with 'fold'.
+ wlv.c_extra = wp->w_p_fcs_chars.fold;
+ wlv.c_final = NUL;
+ wlv.n_extra = grid->cols - wlv.col;
+ }
+
+ if (draw_folded && wlv.n_extra != 0 && wlv.col >= grid->cols) {
+ // Truncate the folding.
+ wlv.n_extra = 0;
+ }
+
// Get the next character to put on the screen.
//
// The "p_extra" points to the extra stuff that is inserted to
@@ -1647,151 +1886,119 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// "p_extra" must end in a NUL to avoid utfc_ptr2len() reads past
// "p_extra[n_extra]".
// For the '$' of the 'list' option, n_extra == 1, p_extra == "".
- if (n_extra > 0) {
- if (c_extra != NUL || (n_extra == 1 && c_final != NUL)) {
- c = (n_extra == 1 && c_final != NUL) ? c_final : c_extra;
- mb_c = c; // doesn't handle non-utf-8 multi-byte!
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ if (wlv.n_extra > 0) {
+ if (wlv.c_extra != NUL || (wlv.n_extra == 1 && wlv.c_final != NUL)) {
+ mb_c = (wlv.n_extra == 1 && wlv.c_final != NUL) ? wlv.c_final : wlv.c_extra;
+ mb_schar = schar_from_char(mb_c);
+ wlv.n_extra--;
} else {
- assert(p_extra != NULL);
- c = (uint8_t)(*p_extra);
- mb_c = c;
- // If the UTF-8 character is more than one byte:
- // Decode it into "mb_c".
- mb_l = utfc_ptr2len(p_extra);
- mb_utf8 = false;
- if (mb_l > n_extra) {
- mb_l = 1;
- } else if (mb_l > 1) {
- mb_c = utfc_ptr2char(p_extra, u8cc);
- mb_utf8 = true;
- c = 0xc0;
- }
- if (mb_l == 0) { // at the NUL at end-of-line
+ assert(wlv.p_extra != NULL);
+ mb_l = utfc_ptr2len(wlv.p_extra);
+ mb_schar = utfc_ptr2schar(wlv.p_extra, &mb_c);
+ // mb_l=0 at the end-of-line NUL
+ if (mb_l > wlv.n_extra || mb_l == 0) {
mb_l = 1;
}
// If a double-width char doesn't fit display a '>' in the last column.
- if ((wp->w_p_rl ? (col <= 0) : (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
+ // Don't advance the pointer but put the character at the start of the next line.
+ if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ mb_c = '>';
mb_l = 1;
(void)mb_l;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
- if (cul_attr) {
- multi_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, multi_attr)
- : hl_combine_attr(multi_attr, cul_attr);
+ if (wlv.cul_attr) {
+ multi_attr = 0 != wlv.line_attr_lowprio
+ ? hl_combine_attr(wlv.cul_attr, multi_attr)
+ : hl_combine_attr(multi_attr, wlv.cul_attr);
}
-
- // put the pointer back to output the double-width
- // character at the start of the next line.
- n_extra++;
- p_extra--;
} else {
- n_extra -= mb_l - 1;
- p_extra += mb_l - 1;
+ wlv.n_extra -= mb_l;
+ wlv.p_extra += mb_l;
}
- p_extra++;
}
- n_extra--;
- } else if (foldinfo.fi_lines > 0) {
+
+ // Only restore search_attr and area_attr after "n_extra" in
+ // the next screen line is also done.
+ if (wlv.n_extra <= 0) {
+ if (wlv.saved_n_extra <= 0) {
+ if (search_attr == 0) {
+ search_attr = saved_search_attr;
+ saved_search_attr = 0;
+ }
+ if (area_attr == 0 && *ptr != NUL) {
+ area_attr = saved_area_attr;
+ saved_area_attr = 0;
+ }
+ if (decor_attr == 0) {
+ decor_attr = saved_decor_attr;
+ saved_decor_attr = 0;
+ }
+
+ if (wlv.extra_for_extmark) {
+ // wlv.extra_attr should be used at this position but not
+ // any further.
+ wlv.reset_extra_attr = true;
+ }
+ }
+ wlv.extra_for_extmark = false;
+ }
+ } else if (has_fold) {
// skip writing the buffer line itself
- c = NUL;
- XFREE_CLEAR(p_extra_free);
+ mb_c = NUL;
} else {
- int c0;
+ char *prev_ptr = ptr;
- XFREE_CLEAR(p_extra_free);
+ // first byte of next char
+ int c0 = (uint8_t)(*ptr);
+ if (c0 == NUL) {
+ // no more cells to skip
+ wlv.skip_cells = 0;
+ }
// Get a character from the line itself.
- c0 = c = (uint8_t)(*ptr);
- mb_c = c;
- // If the UTF-8 character is more than one byte: Decode it
- // into "mb_c".
mb_l = utfc_ptr2len(ptr);
- mb_utf8 = false;
- if (mb_l > 1) {
- mb_c = utfc_ptr2char(ptr, u8cc);
- // Overlong encoded ASCII or ASCII with composing char
- // is displayed normally, except a NUL.
- if (mb_c < 0x80) {
- c0 = c = mb_c;
- }
- mb_utf8 = true;
-
- // At start of the line we can have a composing char.
- // Draw it as a space with a composing char.
- if (utf_iscomposing(mb_c)) {
- int i;
-
- for (i = MAX_MCO - 1; i > 0; i--) {
- u8cc[i] = u8cc[i - 1];
- }
- u8cc[0] = mb_c;
- mb_c = ' ';
- }
+ mb_schar = utfc_ptr2schar(ptr, &mb_c);
+
+ // Overlong encoded ASCII or ASCII with composing char
+ // is displayed normally, except a NUL.
+ if (mb_l > 1 && mb_c < 0x80) {
+ c0 = mb_c;
}
- if ((mb_l == 1 && c >= 0x80)
+ if ((mb_l == 1 && c0 >= 0x80)
|| (mb_l >= 1 && mb_c == 0)
|| (mb_l > 1 && (!vim_isprintc(mb_c)))) {
// Illegal UTF-8 byte: display as <xx>.
- // Non-BMP character : display as ? or fullwidth ?.
- transchar_hex(extra, mb_c);
+ // Non-printable character : display as ? or fullwidth ?.
+ transchar_hex(wlv.extra, mb_c);
if (wp->w_p_rl) { // reverse
- rl_mirror(extra);
+ rl_mirror_ascii(wlv.extra, NULL);
}
- p_extra = extra;
- c = (uint8_t)(*p_extra);
- mb_c = mb_ptr2char_adv((const char **)&p_extra);
- mb_utf8 = (c >= 0x80);
- n_extra = (int)strlen(p_extra);
- c_extra = NUL;
- c_final = NUL;
+ wlv.p_extra = wlv.extra;
+ mb_c = mb_ptr2char_adv((const char **)&wlv.p_extra);
+ mb_schar = schar_from_char(mb_c);
+ wlv.n_extra = (int)strlen(wlv.p_extra);
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = wlv.char_attr; // save current attr
}
} else if (mb_l == 0) { // at the NUL at end-of-line
mb_l = 1;
- } else if (p_arshape && !p_tbidi && ARABIC_CHAR(mb_c)) {
- // Do Arabic shaping.
- int pc, pc1, nc;
- int pcc[MAX_MCO];
-
- // The idea of what is the previous and next
- // character depends on 'rightleft'.
- if (wp->w_p_rl) {
- pc = prev_c;
- pc1 = prev_c1;
- nc = utf_ptr2char(ptr + mb_l);
- prev_c1 = u8cc[0];
- } else {
- pc = utfc_ptr2char(ptr + mb_l, pcc);
- nc = prev_c;
- pc1 = pcc[0];
- }
- prev_c = mb_c;
-
- mb_c = arabic_shape(mb_c, &c, &u8cc[0], pc, pc1, nc);
- } else {
- prev_c = mb_c;
}
// If a double-width char doesn't fit display a '>' in the
// last column; the character is displayed at the start of the
// next line.
- if ((wp->w_p_rl ? (col <= 0) :
- (col >= grid->cols - 1))
- && utf_char2cells(mb_c) == 2) {
- c = '>';
- mb_c = c;
- mb_utf8 = false;
+ if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ mb_c = '>';
mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
multi_attr = win_hl_attr(wp, HLF_AT);
// Put pointer back so that the character will be
// displayed at the start of the next line.
@@ -1803,27 +2010,27 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// If a double-width char doesn't fit at the left side display a '<' in
// the first column. Don't do this for unprintable characters.
- if (n_skip > 0 && mb_l > 1 && n_extra == 0) {
- n_extra = 1;
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- c = ' ';
+ if (wlv.skip_cells > 0 && mb_l > 1 && wlv.n_extra == 0) {
+ wlv.n_extra = 1;
+ wlv.c_extra = MB_FILLER_CHAR;
+ wlv.c_final = NUL;
+ mb_c = ' ';
+ mb_l = 1;
+ mb_schar = schar_from_ascii(mb_c);
if (area_attr == 0 && search_attr == 0) {
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_AT);
- saved_attr2 = char_attr; // save current attr
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ saved_attr2 = wlv.char_attr; // save current attr
}
- mb_c = c;
- mb_utf8 = false;
- mb_l = 1;
}
ptr++;
+ decor_attr = 0;
if (extra_check) {
bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
bool can_spell = !no_plain_buffer;
- // Get syntax attribute, unless still at the start of the line
+ // Get extmark and syntax attributes, unless still at the start of the line
// (double-wide char that doesn't fit).
v = (ptr - line);
if (has_syntax && v > 0) {
@@ -1832,10 +2039,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
save_did_emsg = did_emsg;
did_emsg = false;
- syntax_attr = get_syntax_attr((colnr_T)v - 1,
- has_spell ? &can_spell : NULL, false);
+ decor_attr = get_syntax_attr((colnr_T)v - 1,
+ spv->spv_has_spell ? &can_spell : NULL, false);
- if (did_emsg) { // -V547
+ if (did_emsg) {
wp->w_s->b_syn_error = true;
has_syntax = false;
} else {
@@ -1848,50 +2055,35 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Need to get the line again, a multi-line regexp may
// have made it invalid.
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + v;
- if (!attr_pri) {
- if (cul_attr) {
- char_attr = 0 != line_attr_lowprio
- ? hl_combine_attr(cul_attr, syntax_attr)
- : hl_combine_attr(syntax_attr, cul_attr);
- } else {
- char_attr = syntax_attr;
- }
- } else {
- char_attr = hl_combine_attr(syntax_attr, char_attr);
- }
// no concealing past the end of the line, it interferes
// with line highlighting.
- if (c == NUL) {
- syntax_flags = 0;
- } else {
- syntax_flags = get_syntax_info(&syntax_seqnr);
- }
- } else if (!attr_pri) {
- char_attr = 0;
+ syntax_flags = (mb_c == 0) ? 0 : get_syntax_info(&syntax_seqnr);
}
if (has_decor && v > 0) {
- bool selected = (area_active || (area_highlighting && noinvcur
- && (colnr_T)vcol == wp->w_virtcol));
- int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v - 1, off,
- selected, &decor_state);
- if (extmark_attr != 0) {
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, extmark_attr);
+ // extmarks take preceedence over syntax.c
+ decor_attr = hl_combine_attr(decor_attr, extmark_attr);
+ decor_conceal = decor_state.conceal;
+ can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
+ }
+
+ if (decor_attr) {
+ if (!attr_pri) {
+ if (wlv.cul_attr) {
+ wlv.char_attr = 0 != wlv.line_attr_lowprio
+ ? hl_combine_attr(wlv.cul_attr, decor_attr)
+ : hl_combine_attr(decor_attr, wlv.cul_attr);
} else {
- char_attr = hl_combine_attr(extmark_attr, char_attr);
+ wlv.char_attr = decor_attr;
}
+ } else {
+ wlv.char_attr = hl_combine_attr(decor_attr, wlv.char_attr);
}
-
- decor_conceal = decor_state.conceal;
- if (decor_conceal && decor_state.conceal_char) {
- decor_conceal = 2; // really??
- }
-
- can_spell = TRISTATE_TO_BOOL(decor_state.spell, can_spell);
+ } else if (!attr_pri) {
+ wlv.char_attr = 0;
}
// Check spelling (unless at the end of the line).
@@ -1899,17 +2091,13 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// @Spell cluster is not used or the current syntax item
// contains the @Spell cluster.
v = (ptr - line);
- if (has_spell && v >= word_end && v > cur_checked_col) {
+ if (spv->spv_has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0;
- if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, syntax_attr);
- }
- if (c != 0 && ((!has_syntax && !no_plain_buffer) || can_spell)) {
- char *prev_ptr;
+ // do not calculate cap_col at the end of the line or when
+ // only white space is following
+ if (mb_c != 0 && (*skipwhite(prev_ptr) != NUL) && can_spell) {
char *p;
- int len;
hlf_T spell_hlf = HLF_COUNT;
- prev_ptr = ptr - mb_l;
v -= mb_l - 1;
// Use nextline[] if possible, it has the start of the
@@ -1919,10 +2107,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
} else {
p = prev_ptr;
}
- cap_col -= (int)(prev_ptr - line);
- size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange);
+ spv->spv_cap_col -= (int)(prev_ptr - line);
+ size_t tmplen = spell_check(wp, p, &spell_hlf, &spv->spv_cap_col, spv->spv_unchanged);
assert(tmplen <= INT_MAX);
- len = (int)tmplen;
+ int len = (int)tmplen;
word_end = (int)v + len;
// In Insert mode only highlight a word that
@@ -1941,8 +2129,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& (p - nextline) + len > nextline_idx) {
// Remember that the good word continues at the
// start of the next line.
- checked_lnum = lnum + 1;
- checked_col = (int)((p - nextline) + len - nextline_idx);
+ spv->spv_checked_lnum = lnum + 1;
+ spv->spv_checked_col = (int)((p - nextline) + len - nextline_idx);
}
// Turn index into actual attributes.
@@ -1950,212 +2138,212 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
spell_attr = highlight_attr[spell_hlf];
}
- if (cap_col > 0) {
- if (p != prev_ptr
- && (p - nextline) + cap_col >= nextline_idx) {
+ if (spv->spv_cap_col > 0) {
+ if (p != prev_ptr && (p - nextline) + spv->spv_cap_col >= nextline_idx) {
// Remember that the word in the next line
// must start with a capital.
- capcol_lnum = lnum + 1;
- cap_col = (int)((p - nextline) + cap_col
- - nextline_idx);
+ spv->spv_capcol_lnum = lnum + 1;
+ spv->spv_cap_col = (int)((p - nextline) + spv->spv_cap_col - nextline_idx);
} else {
// Compute the actual column.
- cap_col += (int)(prev_ptr - line);
+ spv->spv_cap_col += (int)(prev_ptr - line);
}
}
}
}
if (spell_attr != 0) {
if (!attr_pri) {
- char_attr = hl_combine_attr(char_attr, spell_attr);
+ wlv.char_attr = hl_combine_attr(wlv.char_attr, spell_attr);
} else {
- char_attr = hl_combine_attr(spell_attr, char_attr);
+ wlv.char_attr = hl_combine_attr(spell_attr, wlv.char_attr);
}
}
if (wp->w_buffer->terminal) {
- char_attr = hl_combine_attr(term_attrs[vcol], char_attr);
+ wlv.char_attr = hl_combine_attr(term_attrs[wlv.vcol], wlv.char_attr);
}
+ // we don't want linebreak to apply for lines that start with
+ // leading spaces, followed by long letters (since it would add
+ // a break at the beginning of a line and this might be unexpected)
+ //
+ // So only allow to linebreak, once we have found chars not in
+ // 'breakat' in the line.
+ if (wp->w_p_lbr && !wlv.need_lbr && mb_c != NUL
+ && !vim_isbreak((uint8_t)(*ptr))) {
+ wlv.need_lbr = true;
+ }
// Found last space before word: check for line break.
- if (wp->w_p_lbr && c0 == c && vim_isbreak(c)
- && !vim_isbreak((int)(*ptr))) {
+ if (wp->w_p_lbr && c0 == mb_c && mb_c < 128 && wlv.need_lbr
+ && vim_isbreak(mb_c) && !vim_isbreak((uint8_t)(*ptr))) {
int mb_off = utf_head_off(line, ptr - 1);
char *p = ptr - (mb_off + 1);
chartabsize_T cts;
- init_chartabsize_arg(&cts, wp, lnum, (colnr_T)vcol, line, p);
- n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
+ init_chartabsize_arg(&cts, wp, lnum, wlv.vcol, line, p);
+ // do not want virtual text to be counted here
+ cts.cts_has_virt_text = false;
+ wlv.n_extra = win_lbr_chartabsize(&cts, NULL) - 1;
+ clear_chartabsize_arg(&cts);
- // We have just drawn the showbreak value, no need to add
- // space for it again.
- if (vcol == vcol_sbr) {
- n_extra -= mb_charlen(get_showbreak_value(wp));
- if (n_extra < 0) {
- n_extra = 0;
- }
- }
- if (on_last_col && c != TAB) {
+ if (on_last_col && mb_c != TAB) {
// Do not continue search/match highlighting over the
// line break, but for TABs the highlighting should
// include the complete width of the character
search_attr = 0;
}
- if (c == TAB && n_extra + col > grid->cols) {
- n_extra = tabstop_padding((colnr_T)vcol, wp->w_buffer->b_p_ts,
- wp->w_buffer->b_p_vts_array) - 1;
+ if (mb_c == TAB && wlv.n_extra + wlv.col > grid->cols) {
+ wlv.n_extra = tabstop_padding(wlv.vcol, wp->w_buffer->b_p_ts,
+ wp->w_buffer->b_p_vts_array) - 1;
}
- c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
- c_final = NUL;
- if (ascii_iswhite(c)) {
- if (c == TAB) {
+ wlv.c_extra = mb_off > 0 ? MB_FILLER_CHAR : ' ';
+ wlv.c_final = NUL;
+ if (mb_c < 128 && ascii_iswhite(mb_c)) {
+ if (mb_c == TAB) {
// See "Tab alignment" below.
FIX_FOR_BOGUSCOLS;
}
if (!wp->w_p_list) {
- c = ' ';
+ mb_c = ' ';
+ mb_schar = schar_from_ascii(mb_c);
}
}
- clear_chartabsize_arg(&cts);
}
- in_multispace = c == ' ' && ((ptr > line + 1 && ptr[-2] == ' ') || *ptr == ' ');
- if (!in_multispace) {
- multispace_pos = 0;
+ if (wp->w_p_list) {
+ in_multispace = mb_c == ' ' && (*ptr == ' ' || (prev_ptr > line && prev_ptr[-1] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
}
// 'list': Change char 160 to 'nbsp' and space to 'space'.
// But not when the character is followed by a composing
// character (use mb_l to check that).
if (wp->w_p_list
- && ((((c == 160 && mb_l == 1)
- || (mb_utf8
- && ((mb_c == 160 && mb_l == 2)
- || (mb_c == 0x202f && mb_l == 3))))
+ && ((((mb_c == 160 && mb_l == 2) || (mb_c == 0x202f && mb_l == 3))
&& wp->w_p_lcs_chars.nbsp)
- || (c == ' '
+ || (mb_c == ' '
&& mb_l == 1
&& (wp->w_p_lcs_chars.space
|| (in_multispace && wp->w_p_lcs_chars.multispace != NULL))
&& ptr - line >= leadcol
&& ptr - line <= trailcol))) {
if (in_multispace && wp->w_p_lcs_chars.multispace != NULL) {
- c = wp->w_p_lcs_chars.multispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.multispace[multispace_pos++];
if (wp->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else {
- c = (c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
+ mb_c = (mb_c == ' ') ? wp->w_p_lcs_chars.space : wp->w_p_lcs_chars.nbsp;
}
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.n_attr = 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_char(mb_c);
}
- if (c == ' ' && ((trailcol != MAXCOL && ptr > line + trailcol)
- || (leadcol != 0 && ptr < line + leadcol))) {
+ if (mb_c == ' ' && mb_l == 1 && ((trailcol != MAXCOL && ptr > line + trailcol)
+ || (leadcol != 0 && ptr < line + leadcol))) {
if (leadcol != 0 && in_multispace && ptr < line + leadcol
&& wp->w_p_lcs_chars.leadmultispace != NULL) {
- c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
+ mb_c = wp->w_p_lcs_chars.leadmultispace[multispace_pos++];
if (wp->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
multispace_pos = 0;
}
} else if (ptr > line + trailcol && wp->w_p_lcs_chars.trail) {
- c = wp->w_p_lcs_chars.trail;
+ mb_c = wp->w_p_lcs_chars.trail;
} else if (ptr < line + leadcol && wp->w_p_lcs_chars.lead) {
- c = wp->w_p_lcs_chars.lead;
+ mb_c = wp->w_p_lcs_chars.lead;
} else if (leadcol != 0 && wp->w_p_lcs_chars.space) {
- c = wp->w_p_lcs_chars.space;
+ mb_c = wp->w_p_lcs_chars.space;
}
- n_attr = 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.n_attr = 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_char(mb_c);
}
}
// Handling of non-printable characters.
- if (!vim_isprintc(c)) {
+ if (!vim_isprintc(mb_c)) {
// when getting a character from the file, we may have to
// turn it into something else on the way to putting it on the screen.
- if (c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ if (mb_c == TAB && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
int tab_len = 0;
- long vcol_adjusted = vcol; // removed showbreak length
+ colnr_T vcol_adjusted = wlv.vcol; // removed showbreak length
char *const sbr = get_showbreak_value(wp);
// Only adjust the tab_len, when at the first column after the
// showbreak value was drawn.
- if (*sbr != NUL && vcol == vcol_sbr && wp->w_p_wrap) {
- vcol_adjusted = vcol - mb_charlen(sbr);
+ if (*sbr != NUL && wlv.vcol == wlv.vcol_sbr && wp->w_p_wrap) {
+ vcol_adjusted = wlv.vcol - mb_charlen(sbr);
}
// tab amount depends on current column
- tab_len = tabstop_padding((colnr_T)vcol_adjusted,
+ tab_len = tabstop_padding(vcol_adjusted,
wp->w_buffer->b_p_ts,
wp->w_buffer->b_p_vts_array) - 1;
if (!wp->w_p_lbr || !wp->w_p_list) {
- n_extra = tab_len;
+ wlv.n_extra = tab_len;
} else {
char *p;
- int i;
- int saved_nextra = n_extra;
+ int saved_nextra = wlv.n_extra;
- if (vcol_off > 0) {
+ if (wlv.vcol_off > 0) {
// there are characters to conceal
- tab_len += vcol_off;
+ tab_len += wlv.vcol_off;
}
// boguscols before FIX_FOR_BOGUSCOLS macro from above.
if (wp->w_p_lcs_chars.tab1 && old_boguscols > 0
- && n_extra > tab_len) {
- tab_len += n_extra - tab_len;
+ && wlv.n_extra > tab_len) {
+ tab_len += wlv.n_extra - tab_len;
}
- // If n_extra > 0, it gives the number of chars
- // to use for a tab, else we need to calculate the width
- // for a tab.
- int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2));
- if (wp->w_p_lcs_chars.tab3) {
- len += utf_char2len(wp->w_p_lcs_chars.tab3);
- }
- if (n_extra > 0) {
- len += n_extra - tab_len;
- }
- c = wp->w_p_lcs_chars.tab1;
- p = xmalloc((size_t)len + 1);
- memset(p, ' ', (size_t)len);
- p[len] = NUL;
- xfree(p_extra_free);
- p_extra_free = p;
- for (i = 0; i < tab_len; i++) {
- if (*p == NUL) {
- tab_len = i;
- break;
+ if (tab_len > 0) {
+ // If wlv.n_extra > 0, it gives the number of chars
+ // to use for a tab, else we need to calculate the
+ // width for a tab.
+ int tab2_len = utf_char2len(wp->w_p_lcs_chars.tab2);
+ int len = tab_len * tab2_len;
+ if (wp->w_p_lcs_chars.tab3) {
+ len += utf_char2len(wp->w_p_lcs_chars.tab3) - tab2_len;
+ }
+ if (wlv.n_extra > 0) {
+ len += wlv.n_extra - tab_len;
}
- int lcs = wp->w_p_lcs_chars.tab2;
+ mb_c = wp->w_p_lcs_chars.tab1;
+ p = get_extra_buf((size_t)len + 1);
+ memset(p, ' ', (size_t)len);
+ p[len] = NUL;
+ wlv.p_extra = p;
+ for (int i = 0; i < tab_len; i++) {
+ if (*p == NUL) {
+ tab_len = i;
+ break;
+ }
+ int lcs = wp->w_p_lcs_chars.tab2;
- // if tab3 is given, use it for the last char
- if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
- lcs = wp->w_p_lcs_chars.tab3;
+ // if tab3 is given, use it for the last char
+ if (wp->w_p_lcs_chars.tab3 && i == tab_len - 1) {
+ lcs = wp->w_p_lcs_chars.tab3;
+ }
+ p += utf_char2bytes(lcs, p);
+ wlv.n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
}
- p += utf_char2bytes(lcs, p);
- n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0);
- }
- p_extra = p_extra_free;
- // n_extra will be increased by FIX_FOX_BOGUSCOLS
- // macro below, so need to adjust for that here
- if (vcol_off > 0) {
- n_extra -= vcol_off;
+ // n_extra will be increased by FIX_FOX_BOGUSCOLS
+ // macro below, so need to adjust for that here
+ if (wlv.vcol_off > 0) {
+ wlv.n_extra -= wlv.vcol_off;
+ }
}
}
{
- int vc_saved = vcol_off;
+ int vc_saved = wlv.vcol_off;
// Tab alignment should be identical regardless of
// 'conceallevel' value. So tab compensates of all
@@ -2168,108 +2356,100 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Make sure, the highlighting for the tab char will be
// correctly set further below (effectively reverts the
// FIX_FOR_BOGSUCOLS macro).
- if (n_extra == tab_len + vc_saved && wp->w_p_list
+ if (wlv.n_extra == tab_len + vc_saved && wp->w_p_list
&& wp->w_p_lcs_chars.tab1) {
tab_len += vc_saved;
}
}
- mb_utf8 = false; // don't draw as UTF-8
if (wp->w_p_list) {
- c = (n_extra == 0 && wp->w_p_lcs_chars.tab3)
- ? wp->w_p_lcs_chars.tab3
- : wp->w_p_lcs_chars.tab1;
- if (wp->w_p_lbr) {
- c_extra = NUL; // using p_extra from above
+ mb_c = (wlv.n_extra == 0 && wp->w_p_lcs_chars.tab3)
+ ? wp->w_p_lcs_chars.tab3 : wp->w_p_lcs_chars.tab1;
+ if (wp->w_p_lbr && wlv.p_extra != NULL && *wlv.p_extra != NUL) {
+ wlv.c_extra = NUL; // using p_extra from above
} else {
- c_extra = wp->w_p_lcs_chars.tab2;
+ wlv.c_extra = wp->w_p_lcs_chars.tab2;
}
- c_final = wp->w_p_lcs_chars.tab3;
- n_attr = tab_len + 1;
- extra_attr = win_hl_attr(wp, HLF_0);
- saved_attr2 = char_attr; // save current attr
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ wlv.c_final = wp->w_p_lcs_chars.tab3;
+ wlv.n_attr = tab_len + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_0);
+ saved_attr2 = wlv.char_attr; // save current attr
} else {
- c_final = NUL;
- c_extra = ' ';
- c = ' ';
+ wlv.c_final = NUL;
+ wlv.c_extra = ' ';
+ mb_c = ' ';
}
- } else if (c == NUL
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c == NUL
&& (wp->w_p_list
- || ((fromcol >= 0 || fromcol_prev >= 0)
- && tocol > vcol
+ || ((wlv.fromcol >= 0 || fromcol_prev >= 0)
+ && wlv.tocol > wlv.vcol
&& VIsual_mode != Ctrl_V
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))
+ && wlv.col < grid->cols
&& !(noinvcur
&& lnum == wp->w_cursor.lnum
- && (colnr_T)vcol == wp->w_virtcol)))
+ && wlv.vcol == wp->w_virtcol)))
&& lcs_eol_one > 0) {
// Display a '$' after the line or highlight an extra
// character if the line break is included.
// For a diff line the highlighting continues after the "$".
- if (diff_hlf == (hlf_T)0
- && line_attr == 0
- && line_attr_lowprio == 0) {
+ if (wlv.diff_hlf == (hlf_T)0
+ && wlv.line_attr == 0
+ && wlv.line_attr_lowprio == 0) {
// In virtualedit, visual selections may extend beyond end of line
- if (area_highlighting && virtual_active()
- && tocol != MAXCOL && vcol < tocol) {
- n_extra = 0;
- } else {
- p_extra = at_end_str;
- n_extra = 1;
- c_extra = NUL;
- c_final = NUL;
+ if (!(area_highlighting && virtual_active()
+ && wlv.tocol != MAXCOL && wlv.vcol < wlv.tocol)) {
+ wlv.p_extra = at_end_str;
}
+ wlv.n_extra = 0;
}
if (wp->w_p_list && wp->w_p_lcs_chars.eol > 0) {
- c = wp->w_p_lcs_chars.eol;
+ mb_c = wp->w_p_lcs_chars.eol;
} else {
- c = ' ';
+ mb_c = ' ';
}
lcs_eol_one = -1;
ptr--; // put it back at the NUL
- extra_attr = win_hl_attr(wp, HLF_AT);
- n_attr = 1;
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
- } else if (c != NUL) {
- p_extra = (char *)transchar_buf(wp->w_buffer, c);
- if (n_extra == 0) {
- n_extra = byte2cells(c) - 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ wlv.n_attr = 1;
+ mb_schar = schar_from_char(mb_c);
+ } else if (mb_c != NUL) {
+ wlv.p_extra = transchar_buf(wp->w_buffer, mb_c);
+ if (wlv.n_extra == 0) {
+ wlv.n_extra = byte2cells(mb_c) - 1;
}
if ((dy_flags & DY_UHEX) && wp->w_p_rl) {
- rl_mirror(p_extra); // reverse "<12>"
+ rl_mirror_ascii(wlv.p_extra, NULL); // reverse "<12>"
}
- c_extra = NUL;
- c_final = NUL;
+ wlv.c_extra = NUL;
+ wlv.c_final = NUL;
if (wp->w_p_lbr) {
char *p;
- c = (uint8_t)(*p_extra);
- p = xmalloc((size_t)n_extra + 1);
- memset(p, ' ', (size_t)n_extra);
+ mb_c = (uint8_t)(*wlv.p_extra);
+ p = get_extra_buf((size_t)wlv.n_extra + 1);
+ memset(p, ' ', (size_t)wlv.n_extra);
strncpy(p, // NOLINT(runtime/printf)
- p_extra + 1,
- (size_t)strlen(p_extra) - 1);
- p[n_extra] = NUL;
- xfree(p_extra_free);
- p_extra_free = p_extra = p;
+ wlv.p_extra + 1,
+ (size_t)strlen(wlv.p_extra) - 1);
+ p[wlv.n_extra] = NUL;
+ wlv.p_extra = p;
} else {
- n_extra = byte2cells(c) - 1;
- c = (uint8_t)(*p_extra++);
+ wlv.n_extra = byte2cells(mb_c) - 1;
+ mb_c = (uint8_t)(*wlv.p_extra++);
}
- n_attr = n_extra + 1;
- extra_attr = win_hl_attr(wp, HLF_8);
- saved_attr2 = char_attr; // save current attr
- mb_utf8 = false; // don't draw as UTF-8
+ wlv.n_attr = wlv.n_extra + 1;
+ wlv.extra_attr = win_hl_attr(wp, HLF_8);
+ saved_attr2 = wlv.char_attr; // save current attr
+ mb_schar = schar_from_ascii(mb_c);
} else if (VIsual_active
&& (VIsual_mode == Ctrl_V || VIsual_mode == 'v')
&& virtual_active()
- && tocol != MAXCOL
- && vcol < tocol
- && (wp->w_p_rl ? (col >= 0) : (col < grid->cols))) {
- c = ' ';
+ && wlv.tocol != MAXCOL
+ && wlv.vcol < wlv.tocol
+ && wlv.col < grid->cols) {
+ mb_c = ' ';
+ mb_schar = schar_from_char(mb_c);
ptr--; // put it back at the NUL
}
}
@@ -2278,7 +2458,8 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
&& (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp))
&& ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0)
&& !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) {
- char_attr = conceal_attr;
+ wlv.char_attr = conceal_attr;
+ bool is_conceal_char = false;
if (((prev_syntax_id != syntax_seqnr && (syntax_flags & HL_CONCEAL) != 0)
|| has_match_conc > 1 || decor_conceal > 1)
&& (syn_get_sub_char() != NUL
@@ -2289,49 +2470,47 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// First time at this concealed item: display one
// character.
if (has_match_conc && match_conc) {
- c = match_conc;
+ mb_c = match_conc;
} else if (decor_conceal && decor_state.conceal_char) {
- c = decor_state.conceal_char;
+ mb_schar = decor_state.conceal_char;
+ mb_c = schar_get_first_codepoint(mb_schar);
+ is_conceal_char = true;
if (decor_state.conceal_attr) {
- char_attr = decor_state.conceal_attr;
+ wlv.char_attr = decor_state.conceal_attr;
}
} else if (syn_get_sub_char() != NUL) {
- c = syn_get_sub_char();
+ mb_c = syn_get_sub_char();
} else if (wp->w_p_lcs_chars.conceal != NUL) {
- c = wp->w_p_lcs_chars.conceal;
+ mb_c = wp->w_p_lcs_chars.conceal;
} else {
- c = ' ';
+ mb_c = ' ';
}
prev_syntax_id = syntax_seqnr;
- if (n_extra > 0) {
- vcol_off += n_extra;
+ if (wlv.n_extra > 0) {
+ wlv.vcol_off += wlv.n_extra;
}
- vcol += n_extra;
- if (wp->w_p_wrap && n_extra > 0) {
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- boguscols += n_extra;
- col += n_extra;
- }
+ wlv.vcol += wlv.n_extra;
+ if (wp->w_p_wrap && wlv.n_extra > 0) {
+ wlv.boguscols += wlv.n_extra;
+ wlv.col += wlv.n_extra;
}
- n_extra = 0;
- n_attr = 0;
- } else if (n_skip == 0) {
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
+ } else if (wlv.skip_cells == 0) {
is_concealing = true;
- n_skip = 1;
+ wlv.skip_cells = 1;
+ }
+ if (!is_conceal_char) {
+ mb_schar = schar_from_char(mb_c);
}
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
} else {
prev_syntax_id = 0;
is_concealing = false;
}
- if (n_skip > 0 && did_decrement_ptr) {
+ if (wlv.skip_cells > 0 && did_decrement_ptr) {
// not showing the '>', put pointer back to avoid getting stuck
ptr++;
}
@@ -2339,23 +2518,25 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// In the cursor line and we may be concealing characters: correct
// the cursor column when we reach its position.
- if (!did_wcol && draw_state == WL_LINE
+ if (!did_wcol && wlv.draw_state == WL_LINE
&& wp == curwin && lnum == wp->w_cursor.lnum
&& conceal_cursor_line(wp)
- && (int)wp->w_virtcol <= vcol + n_skip) {
- if (wp->w_p_rl) {
- wp->w_wcol = grid->cols - col + boguscols - 1;
- } else {
- wp->w_wcol = col - boguscols;
- }
- wp->w_wrow = row;
+ && (int)wp->w_virtcol <= wlv.vcol + wlv.skip_cells) {
+ wp->w_wcol = wlv.col - wlv.boguscols;
+ wp->w_wrow = wlv.row;
did_wcol = true;
wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
// Don't override visual selection highlighting.
- if (n_attr > 0 && draw_state == WL_LINE && !search_attr_from_match) {
- char_attr = hl_combine_attr(char_attr, extra_attr);
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && !search_attr_from_match) {
+ wlv.char_attr = hl_combine_attr(wlv.char_attr, wlv.extra_attr);
+ if (wlv.reset_extra_attr) {
+ wlv.reset_extra_attr = false;
+ wlv.extra_attr = 0;
+ // search_attr_from_match can be restored now that the extra_attr has been applied
+ search_attr_from_match = saved_search_attr_from_match;
+ }
}
// Handle the case where we are in column 0 but not on the first
@@ -2363,41 +2544,40 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// special character (via 'listchars' option "precedes:<char>".
if (lcs_prec_todo != NUL
&& wp->w_p_list
- && (wp->w_p_wrap ? (wp->w_skipcol > 0 && row == 0) : wp->w_leftcol > 0)
- && filler_todo <= 0
- && draw_state > WL_STC
- && c != NUL) {
- c = wp->w_p_lcs_chars.prec;
+ && (wp->w_p_wrap ? (wp->w_skipcol > 0 && wlv.row == 0) : wp->w_leftcol > 0)
+ && wlv.filler_todo <= 0
+ && wlv.draw_state > WL_STC
+ && mb_c != NUL) {
+ mb_c = wp->w_p_lcs_chars.prec;
lcs_prec_todo = NUL;
if (utf_char2cells(mb_c) > 1) {
// Double-width character being overwritten by the "precedes"
// character, need to fill up half the character.
- c_extra = MB_FILLER_CHAR;
- c_final = NUL;
- n_extra = 1;
- n_attr = 2;
- extra_attr = win_hl_attr(wp, HLF_AT);
- }
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
- saved_attr3 = char_attr; // save current attr
- char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
+ wlv.c_extra = MB_FILLER_CHAR;
+ wlv.c_final = NUL;
+ wlv.n_extra = 1;
+ wlv.n_attr = 2;
+ wlv.extra_attr = win_hl_attr(wp, HLF_AT);
+ }
+ mb_schar = schar_from_char(mb_c);
+ saved_attr3 = wlv.char_attr; // save current attr
+ wlv.char_attr = win_hl_attr(wp, HLF_AT); // overwriting char_attr
n_attr3 = 1;
}
// At end of the text line or just after the last character.
- if (c == NUL && eol_hl_off == 0) {
+ if (mb_c == NUL && eol_hl_off == 0) {
// flag to indicate whether prevcol equals startcol of search_hl or
// one of the matches
bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &screen_search_hl,
- (long)(ptr - line) - 1); // NOLINT(google-readability-casting)
+ (colnr_T)(ptr - line) - 1);
// Invert at least one char, used for Visual and empty line or
// highlight match at end of line. If it's beyond the last
// char on the screen, just overwrite that one (tricky!) Not
// needed when a '$' was displayed for 'list'.
if (wp->w_p_lcs_chars.eol == lcs_eol_one
- && ((area_attr != 0 && vcol == fromcol
+ && ((area_attr != 0 && wlv.vcol == wlv.fromcol
&& (VIsual_mode != Ctrl_V
|| lnum == VIsual.lnum
|| lnum == curwin->w_cursor.lnum))
@@ -2405,66 +2585,60 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
|| prevcol_hl_flag)) {
int n = 0;
- if (wp->w_p_rl) {
- if (col < 0) {
- n = 1;
- }
- } else {
- if (col >= grid->cols) {
- n = -1;
- }
+ if (wlv.col >= grid->cols) {
+ n = -1;
}
if (n != 0) {
// At the window boundary, highlight the last character
// instead (better than nothing).
- off += n;
- col += n;
+ wlv.off += n;
+ wlv.col += n;
} else {
// Add a blank character to highlight.
- schar_from_ascii(linebuf_char[off], ' ');
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
}
if (area_attr == 0 && !has_fold) {
// Use attributes from match with highest priority among
// 'search_hl' and the match list.
get_search_match_hl(wp,
&screen_search_hl,
- (long)(ptr - line), // NOLINT(google-readability-casting)
- &char_attr);
+ (colnr_T)(ptr - line),
+ &wlv.char_attr);
}
- int eol_attr = char_attr;
- if (cul_attr) {
- eol_attr = hl_combine_attr(cul_attr, eol_attr);
+ int eol_attr = wlv.char_attr;
+ if (wlv.cul_attr) {
+ eol_attr = hl_combine_attr(wlv.cul_attr, eol_attr);
}
- linebuf_attr[off] = eol_attr;
- if (wp->w_p_rl) {
- col--;
- off--;
- } else {
- col++;
- off++;
- }
- vcol++;
+ linebuf_attr[wlv.off] = eol_attr;
+ linebuf_vcol[wlv.off] = MAXCOL;
+ wlv.col++;
+ wlv.off++;
+ wlv.vcol++;
eol_hl_off = 1;
}
+ }
+
+ // At end of the text line.
+ if (mb_c == NUL) {
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
if (wp->w_p_wrap) {
- v = wp->w_skipcol;
+ v = wlv.startrow == 0 ? wp->w_skipcol : 0;
} else {
v = wp->w_leftcol;
}
// check if line ends before left margin
- if (vcol < v + col - win_col_off(wp)) {
- vcol = v + col - win_col_off(wp);
+ if (wlv.vcol < v + wlv.col - win_col_off(wp)) {
+ wlv.vcol = (colnr_T)v + wlv.col - win_col_off(wp);
}
// Get rid of the boguscols now, we want to draw until the right
// edge for 'cursorcolumn'.
- col -= boguscols;
- // boguscols = 0; // Disabled because value never read after this
+ wlv.col -= wlv.boguscols;
+ wlv.boguscols = 0;
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
bool has_virttext = false;
@@ -2474,19 +2648,16 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
? 1 : 0);
if (has_decor) {
- has_virttext = decor_redraw_eol(wp->w_buffer, &decor_state, &line_attr,
- col + eol_skip);
+ has_virttext = decor_redraw_eol(wp, &decor_state, &wlv.line_attr, wlv.col + eol_skip);
}
if (((wp->w_p_cuc
- && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off
- && (int)wp->w_virtcol <
- (long)grid->cols * (row - startrow + 1) + v
+ && wp->w_virtcol >= VCOL_HLC - eol_hl_off
+ && wp->w_virtcol < grid->cols * (ptrdiff_t)(wlv.row - startrow + 1) + v
&& lnum != wp->w_cursor.lnum)
- || draw_color_col || line_attr_lowprio || line_attr
- || diff_hlf != (hlf_T)0 || has_virttext)) {
+ || draw_color_col || wlv.line_attr_lowprio || wlv.line_attr
+ || wlv.diff_hlf != 0 || has_virttext)) {
int rightmost_vcol = 0;
- int i;
if (wp->w_p_cuc) {
rightmost_vcol = wp->w_virtcol;
@@ -2494,7 +2665,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
if (draw_color_col) {
// determine rightmost colorcolumn to possibly draw
- for (i = 0; color_cols[i] >= 0; i++) {
+ for (int i = 0; color_cols[i] >= 0; i++) {
if (rightmost_vcol < color_cols[i]) {
rightmost_vcol = color_cols[i];
}
@@ -2505,72 +2676,73 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
int mc_attr = win_hl_attr(wp, HLF_MC);
int diff_attr = 0;
- if (diff_hlf == HLF_TXD) {
- diff_hlf = HLF_CHD;
+ if (wlv.diff_hlf == HLF_TXD) {
+ wlv.diff_hlf = HLF_CHD;
}
- if (diff_hlf != 0) {
- diff_attr = win_hl_attr(wp, (int)diff_hlf);
+ if (wlv.diff_hlf != 0) {
+ diff_attr = win_hl_attr(wp, (int)wlv.diff_hlf);
}
- int base_attr = hl_combine_attr(line_attr_lowprio, diff_attr);
- if (base_attr || line_attr || has_virttext) {
+ int base_attr = hl_combine_attr(wlv.line_attr_lowprio, diff_attr);
+ if (base_attr || wlv.line_attr || has_virttext) {
rightmost_vcol = INT_MAX;
}
- int col_stride = wp->w_p_rl ? -1 : 1;
-
- while (wp->w_p_rl ? col >= 0 : col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- col += col_stride;
+ while (wlv.col < grid->cols) {
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
+ linebuf_vcol[wlv.off] = MAXCOL;
+ wlv.col++;
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
int col_attr = base_attr;
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol) {
+ if (wp->w_p_cuc && VCOL_HLC == wp->w_virtcol) {
col_attr = cuc_attr;
} else if (draw_color_col && VCOL_HLC == *color_cols) {
- col_attr = mc_attr;
+ col_attr = hl_combine_attr(wlv.line_attr_lowprio, mc_attr);
}
- col_attr = hl_combine_attr(col_attr, line_attr);
+ col_attr = hl_combine_attr(col_attr, wlv.line_attr);
- linebuf_attr[off] = col_attr;
- off += col_stride;
+ linebuf_attr[wlv.off] = col_attr;
+ wlv.off++;
if (VCOL_HLC >= rightmost_vcol) {
break;
}
- vcol += 1;
+ wlv.vcol += 1;
}
}
// TODO(bfredl): integrate with the common beyond-the-end-loop
if (wp->w_buffer->terminal) {
- // terminal buffers may need to highlight beyond the end of the
- // logical line
- int n = wp->w_p_rl ? -1 : 1;
- while (col >= 0 && col < grid->cols) {
- schar_from_ascii(linebuf_char[off], ' ');
- linebuf_attr[off] = vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[vcol];
- off += n;
- vcol += n;
- col += n;
+ // terminal buffers may need to highlight beyond the end of the logical line
+ while (wlv.col >= 0 && wlv.col < grid->cols) {
+ linebuf_char[wlv.off] = schar_from_ascii(' ');
+ linebuf_attr[wlv.off] = wlv.vcol >= TERM_ATTRS_MAX ? 0 : term_attrs[wlv.vcol];
+ linebuf_vcol[wlv.off] = wlv.vcol;
+ wlv.off++;
+ wlv.vcol++;
+ wlv.col++;
}
}
- draw_virt_text(wp, buf, win_col_offset, &col, grid->cols, row);
- grid_put_linebuf(grid, row, 0, col, grid->cols, wp->w_p_rl, wp, bg_attr, false);
- row++;
+ if (kv_size(fold_vt) > 0) {
+ draw_virt_text_item(buf, win_col_offset, fold_vt, kHlModeCombine, grid->cols, 0);
+ }
+ draw_virt_text(wp, buf, win_col_offset, &wlv.col, wlv.row);
+ win_put_linebuf(wp, wlv.row, 0, wlv.col, grid->cols, bg_attr, false);
+ wlv.row++;
// Update w_cline_height and w_cline_folded if the cursor line was
// updated (saves a call to plines_win() later).
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
curwin->w_cline_row = startrow;
- curwin->w_cline_height = row - startrow;
- curwin->w_cline_folded = foldinfo.fi_lines > 0;
+ curwin->w_cline_height = wlv.row - startrow;
+ curwin->w_cline_folded = has_fold;
curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
conceal_cursor_used = conceal_cursor_line(curwin);
}
@@ -2580,24 +2752,29 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Show "extends" character from 'listchars' if beyond the line end and
// 'list' is set.
if (wp->w_p_lcs_chars.ext != NUL
- && draw_state == WL_LINE
+ && wlv.draw_state == WL_LINE
&& wp->w_p_list
&& !wp->w_p_wrap
- && filler_todo <= 0
- && (wp->w_p_rl ? col == 0 : col == grid->cols - 1)
- && !has_fold
- && (*ptr != NUL
- || lcs_eol_one > 0
- || (n_extra && (c_extra != NUL || *p_extra != NUL)))) {
- c = wp->w_p_lcs_chars.ext;
- char_attr = win_hl_attr(wp, HLF_AT);
- mb_c = c;
- mb_utf8 = check_mb_utf8(&c, u8cc);
+ && wlv.filler_todo <= 0
+ && wlv.col == grid->cols - 1
+ && !has_fold) {
+ if (has_decor && *ptr == NUL && lcs_eol_one == 0) {
+ // Tricky: there might be a virtual text just _after_ the last char
+ decor_redraw_col(wp, (colnr_T)v, wlv.off, false, &decor_state);
+ }
+ if (*ptr != NUL
+ || lcs_eol_one > 0
+ || (wlv.n_extra > 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
+ || has_more_inline_virt(&wlv, v)) {
+ mb_c = wp->w_p_lcs_chars.ext;
+ wlv.char_attr = win_hl_attr(wp, HLF_AT);
+ mb_schar = schar_from_char(mb_c);
+ }
}
// advance to the next 'colorcolumn'
if (draw_color_col) {
- draw_color_col = advance_color_col((int)VCOL_HLC, &color_cols);
+ draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
}
// Highlight the cursor column if 'cursorcolumn' is set. But don't
@@ -2607,84 +2784,84 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// Also highlight the 'colorcolumn' if 'breakindent' and/or 'showbreak'
// options are set
vcol_save_attr = -1;
- if ((draw_state == WL_LINE
- || draw_state == WL_BRI
- || draw_state == WL_SBR)
+ if ((wlv.draw_state == WL_LINE
+ || wlv.draw_state == WL_BRI
+ || wlv.draw_state == WL_SBR)
&& !lnum_in_visual_area
&& search_attr == 0
&& area_attr == 0
- && filler_todo <= 0) {
- if (wp->w_p_cuc && VCOL_HLC == (long)wp->w_virtcol
+ && wlv.filler_todo <= 0) {
+ if (wp->w_p_cuc && VCOL_HLC == wp->w_virtcol
&& lnum != wp->w_cursor.lnum) {
- vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), char_attr);
+ vcol_save_attr = wlv.char_attr;
+ wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_CUC), wlv.char_attr);
} else if (draw_color_col && VCOL_HLC == *color_cols) {
- vcol_save_attr = char_attr;
- char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), char_attr);
+ vcol_save_attr = wlv.char_attr;
+ wlv.char_attr = hl_combine_attr(win_hl_attr(wp, HLF_MC), wlv.char_attr);
}
}
// Apply lowest-priority line attr now, so everything can override it.
- if (draw_state == WL_LINE) {
- char_attr = hl_combine_attr(line_attr_lowprio, char_attr);
+ if (wlv.draw_state == WL_LINE) {
+ wlv.char_attr = hl_combine_attr(wlv.line_attr_lowprio, wlv.char_attr);
+ }
+
+ if (wlv.draw_state == WL_LINE) {
+ vcol_prev = wlv.vcol;
}
// Store character to be displayed.
// Skip characters that are left of the screen for 'nowrap'.
- vcol_prev = vcol;
- if (draw_state < WL_LINE || n_skip <= 0) {
- //
+ if (wlv.draw_state < WL_LINE || wlv.skip_cells <= 0) {
// Store the character.
- //
- if (wp->w_p_rl && utf_char2cells(mb_c) > 1) {
- // A double-wide character is: put first half in left cell.
- off--;
- col--;
- }
- if (mb_utf8) {
- schar_from_cc(linebuf_char[off], mb_c, u8cc);
- } else {
- schar_from_ascii(linebuf_char[off], (char)c);
- }
+ linebuf_char[wlv.off] = mb_schar;
if (multi_attr) {
- linebuf_attr[off] = multi_attr;
+ linebuf_attr[wlv.off] = multi_attr;
multi_attr = 0;
} else {
- linebuf_attr[off] = char_attr;
+ linebuf_attr[wlv.off] = wlv.char_attr;
+ }
+
+ if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
+ linebuf_vcol[wlv.off] = wlv.vcol;
+ } else if (wlv.draw_state == WL_FOLD) {
+ if (wlv.n_closing > 0) {
+ linebuf_vcol[wlv.off] = -3;
+ wlv.n_closing--;
+ } else {
+ linebuf_vcol[wlv.off] = -2;
+ }
+ } else {
+ linebuf_vcol[wlv.off] = -1;
}
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
- off++;
- col++;
+ wlv.off++;
+ wlv.col++;
// UTF-8: Put a 0 in the second screen char.
- linebuf_char[off][0] = 0;
- if (draw_state > WL_STC && filler_todo <= 0) {
- vcol++;
- }
- // When "tocol" is halfway through a character, set it to the end of
- // the character, otherwise highlighting won't stop.
- if (tocol == vcol) {
- tocol++;
+ linebuf_char[wlv.off] = 0;
+ linebuf_attr[wlv.off] = linebuf_attr[wlv.off - 1];
+
+ if (wlv.draw_state > WL_STC && wlv.filler_todo <= 0) {
+ linebuf_vcol[wlv.off] = ++wlv.vcol;
+ } else {
+ linebuf_vcol[wlv.off] = -1;
}
- if (wp->w_p_rl) {
- // now it's time to backup one cell
- off--;
- col--;
+
+ // When "wlv.tocol" is halfway through a character, set it to the end
+ // of the character, otherwise highlighting won't stop.
+ if (wlv.tocol == wlv.vcol) {
+ wlv.tocol++;
}
}
- if (wp->w_p_rl) {
- off--;
- col--;
- } else {
- off++;
- col++;
- }
+ wlv.off++;
+ wlv.col++;
} else if (wp->w_p_cole > 0 && is_concealing) {
- n_skip--;
- vcol_off++;
- if (n_extra > 0) {
- vcol_off += n_extra;
+ wlv.skip_cells--;
+ wlv.vcol_off++;
+ if (wlv.n_extra > 0) {
+ wlv.vcol_off += wlv.n_extra;
}
if (wp->w_p_wrap) {
// Special voodoo required if 'wrap' is on.
@@ -2694,182 +2871,205 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange,
// take up the same screen space when parts are concealed,
// so that cursor line computations aren't messed up.
//
- // To avoid the fictitious advance of 'col' causing
+ // To avoid the fictitious advance of 'wlv.col' causing
// trailing junk to be written out of the screen line
// we are building, 'boguscols' keeps track of the number
// of bad columns we have advanced.
- if (n_extra > 0) {
- vcol += n_extra;
- if (wp->w_p_rl) {
- col -= n_extra;
- boguscols -= n_extra;
- } else {
- col += n_extra;
- boguscols += n_extra;
- }
- n_extra = 0;
- n_attr = 0;
+ if (wlv.n_extra > 0) {
+ wlv.vcol += wlv.n_extra;
+ wlv.col += wlv.n_extra;
+ wlv.boguscols += wlv.n_extra;
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
}
if (utf_char2cells(mb_c) > 1) {
// Need to fill two screen columns.
- if (wp->w_p_rl) {
- boguscols--;
- col--;
- } else {
- boguscols++;
- col++;
- }
+ wlv.boguscols++;
+ wlv.col++;
}
- if (wp->w_p_rl) {
- boguscols--;
- col--;
- } else {
- boguscols++;
- col++;
- }
+ wlv.boguscols++;
+ wlv.col++;
} else {
- if (n_extra > 0) {
- vcol += n_extra;
- n_extra = 0;
- n_attr = 0;
+ if (wlv.n_extra > 0) {
+ wlv.vcol += wlv.n_extra;
+ wlv.n_extra = 0;
+ wlv.n_attr = 0;
}
}
} else {
- n_skip--;
+ wlv.skip_cells--;
}
- // Only advance the "vcol" when after the 'number' or 'relativenumber'
- // column.
- if (draw_state > WL_STC
- && filler_todo <= 0) {
- vcol++;
+ // The skipped cells need to be accounted for in vcol.
+ if (wlv.draw_state > WL_STC && wlv.skipped_cells > 0) {
+ wlv.vcol += wlv.skipped_cells;
+ wlv.skipped_cells = 0;
+ }
+
+ // Only advance the "wlv.vcol" when after the 'number' or
+ // 'relativenumber' column.
+ if (wlv.draw_state > WL_STC
+ && wlv.filler_todo <= 0) {
+ wlv.vcol++;
}
if (vcol_save_attr >= 0) {
- char_attr = vcol_save_attr;
+ wlv.char_attr = vcol_save_attr;
}
// restore attributes after "predeces" in 'listchars'
- if (draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) {
- char_attr = saved_attr3;
+ if (wlv.draw_state > WL_STC && n_attr3 > 0 && --n_attr3 == 0) {
+ wlv.char_attr = saved_attr3;
}
// restore attributes after last 'listchars' or 'number' char
- if (n_attr > 0 && draw_state == WL_LINE && --n_attr == 0) {
- char_attr = saved_attr2;
+ if (wlv.n_attr > 0 && wlv.draw_state == WL_LINE && --wlv.n_attr == 0) {
+ wlv.char_attr = saved_attr2;
+ }
+
+ if (has_decor && wlv.filler_todo <= 0 && wlv.col >= grid->cols) {
+ // At the end of screen line: might need to peek for decorations just after
+ // this position.
+ if (!has_fold && wp->w_p_wrap && wlv.n_extra == 0) {
+ decor_redraw_col(wp, (int)(ptr - line), -3, false, &decor_state);
+ // Check position/hiding of virtual text again on next screen line.
+ decor_need_recheck = true;
+ } else if (has_fold || !wp->w_p_wrap) {
+ // Without wrapping, we might need to display right_align and win_col
+ // virt_text for the entire text line.
+ decor_redraw_col(wp, MAXCOL, -1, true, &decor_state);
+ }
}
// At end of screen line and there is more to come: Display the line
// so far. If there is no more to display it is caught above.
- if ((wp->w_p_rl ? (col < 0) : (col >= grid->cols))
- && (!has_fold || virt_line_offset >= 0)
- && (draw_state != WL_LINE
+ if (wlv.col >= grid->cols && (!has_fold || virt_line_offset >= 0)
+ && (wlv.draw_state != WL_LINE
|| *ptr != NUL
- || filler_todo > 0
+ || wlv.filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
- && p_extra != at_end_str)
- || (n_extra != 0
- && (c_extra != NUL || *p_extra != NUL)))) {
+ && wlv.p_extra != at_end_str)
+ || (wlv.n_extra != 0 && (wlv.c_extra != NUL || *wlv.p_extra != NUL))
+ || has_more_inline_virt(&wlv, v))) {
bool wrap = wp->w_p_wrap // Wrapping enabled.
- && filler_todo <= 0 // Not drawing diff filler lines.
+ && wlv.filler_todo <= 0 // Not drawing diff filler lines.
&& lcs_eol_one != -1 // Haven't printed the lcs_eol character.
- && row != endrow - 1 // Not the last line being displayed.
+ && wlv.row != endrow - 1 // Not the last line being displayed.
&& (grid->cols == Columns // Window spans the width of the screen,
|| ui_has(kUIMultigrid)) // or has dedicated grid.
&& !wp->w_p_rl; // Not right-to-left.
- int draw_col = col - boguscols;
+ int draw_col = wlv.col - wlv.boguscols;
if (virt_line_offset >= 0) {
draw_virt_text_item(buf, virt_line_offset, kv_A(virt_lines, virt_line_index).line,
kHlModeReplace, grid->cols, 0);
- } else {
- draw_virt_text(wp, buf, win_col_offset, &draw_col, grid->cols, row);
+ } else if (wlv.filler_todo <= 0) {
+ draw_virt_text(wp, buf, win_col_offset, &draw_col, wlv.row);
}
- grid_put_linebuf(grid, row, 0, draw_col, grid->cols, wp->w_p_rl, wp, bg_attr, wrap);
+ win_put_linebuf(wp, wlv.row, 0, draw_col, grid->cols, bg_attr, wrap);
if (wrap) {
ScreenGrid *current_grid = grid;
- int current_row = row, dummy_col = 0; // dummy_col unused
+ int current_row = wlv.row;
+ int dummy_col = 0; // unused
grid_adjust(&current_grid, &current_row, &dummy_col);
// Force a redraw of the first column of the next line.
current_grid->attrs[current_grid->line_offset[current_row + 1]] = -1;
-
- // Remember that the line wraps, used for modeless copy.
- current_grid->line_wraps[current_row] = true;
}
- boguscols = 0;
- row++;
+ wlv.boguscols = 0;
+ wlv.vcol_off = 0;
+ wlv.row++;
// When not wrapping and finished diff lines, or when displayed
// '$' and highlighting until last column, break here.
- if ((!wp->w_p_wrap && filler_todo <= 0) || lcs_eol_one == -1) {
+ if ((!wp->w_p_wrap && wlv.filler_todo <= 0) || lcs_eol_one == -1) {
break;
}
// When the window is too narrow draw all "@" lines.
- if (draw_state != WL_LINE && filler_todo <= 0) {
- win_draw_end(wp, '@', ' ', true, row, wp->w_grid.rows, HLF_AT);
- set_empty_rows(wp, row);
- row = endrow;
+ if (wlv.draw_state != WL_LINE && wlv.filler_todo <= 0) {
+ win_draw_end(wp, '@', ' ', true, wlv.row, wp->w_grid.rows, HLF_AT);
+ set_empty_rows(wp, wlv.row);
+ wlv.row = endrow;
}
// When line got too long for screen break here.
- if (row == endrow) {
- row++;
+ if (wlv.row == endrow) {
+ wlv.row++;
break;
}
- col = 0;
- off = 0;
- if (wp->w_p_rl) {
- col = grid->cols - 1; // col is not used if breaking!
- off += col;
- }
+ win_line_start(wp, &wlv, true);
- // reset the drawing state for the start of a wrapped line
- draw_state = WL_START;
- saved_n_extra = n_extra;
- saved_p_extra = p_extra;
- saved_c_extra = c_extra;
- saved_c_final = c_final;
- saved_char_attr = char_attr;
- n_extra = 0;
lcs_prec_todo = wp->w_p_lcs_chars.prec;
- if (filler_todo <= 0) {
- need_showbreak = true;
+ if (wlv.filler_todo <= 0) {
+ wlv.need_showbreak = true;
}
if (statuscol.draw) {
- if (row == startrow + filler_lines + 1 || row == startrow + filler_lines) {
- // Re-evaluate 'statuscolumn' for the first wrapped row and non filler line
- statuscol.textp = NULL;
- } else if (statuscol.textp) {
- // Draw the already built 'statuscolumn' on the next wrapped or filler line
- statuscol.textp = statuscol.text;
- statuscol.hlrecp = statuscol.hlrec;
- } // Fall back to default columns if the 'n' flag isn't in 'cpo'
- statuscol.draw = vim_strchr(p_cpo, CPO_NUMCOL) == NULL;
- }
- filler_todo--;
+ if (vim_strchr(p_cpo, CPO_NUMCOL) && wlv.row > startrow + wlv.filler_lines) {
+ statuscol.draw = false; // don't draw status column if "n" is in 'cpo'
+ } else {
+ statuscol.textp = NULL; // re-evaluate with new v:virtnum
+ }
+ }
+ wlv.filler_todo--;
virt_line_offset = -1;
// When the filler lines are actually below the last line of the
// file, don't draw the line itself, break here.
- if (filler_todo == 0 && (wp->w_botfill || end_fill)) {
+ if (wlv.filler_todo == 0 && (wp->w_botfill || end_fill)) {
break;
}
}
} // for every character in the line
- // After an empty line check first word for capital.
- if (*skipwhite(line) == NUL) {
- capcol_lnum = lnum + 1;
- cap_col = 0;
+ clear_virttext(&fold_vt);
+ kv_destroy(virt_lines);
+ xfree(foldtext_free);
+ return wlv.row;
+}
+
+static void win_put_linebuf(win_T *wp, int row, int coloff, int endcol, int clear_width,
+ int bg_attr, bool wrap)
+{
+ ScreenGrid *grid = &wp->w_grid;
+
+ int start_col = 0;
+
+ if (wp->w_p_rl) {
+ linebuf_mirror(&start_col, &clear_width, grid->cols);
+ endcol = grid->cols - 1 - endcol;
}
- kv_destroy(virt_lines);
- xfree(p_extra_free);
- return row;
+ // Take care of putting "<<<" on the first line for 'smoothscroll'.
+ if (row == 0 && wp->w_skipcol > 0
+ // do not overwrite the 'showbreak' text with "<<<"
+ && *get_showbreak_value(wp) == NUL
+ // do not overwrite the 'listchars' "precedes" text with "<<<"
+ && !(wp->w_p_list && wp->w_p_lcs_chars.prec != 0)) {
+ int off = 0;
+ if (wp->w_p_nu && wp->w_p_rnu) {
+ // do not overwrite the line number, change "123 text" to "123<<<xt".
+ while (off < grid->cols && ascii_isdigit(schar_get_ascii(linebuf_char[off]))) {
+ off++;
+ }
+ }
+
+ for (int i = 0; i < 3 && off < grid->cols; i++) {
+ if (off + 1 < grid->cols && linebuf_char[off + 1] == NUL) {
+ // When the first half of a double-width character is
+ // overwritten, change the second half to a space.
+ linebuf_char[off + 1] = schar_from_ascii(' ');
+ }
+ linebuf_char[off] = schar_from_ascii('<');
+ linebuf_attr[off] = HL_ATTR(HLF_AT);
+ off++;
+ }
+ }
+
+ grid_adjust(&grid, &row, &coloff);
+ grid_put_linebuf(grid, row, coloff, start_col, endcol, clear_width, wp->w_p_rl, bg_attr, wrap);
}
diff --git a/src/nvim/drawline.h b/src/nvim/drawline.h
index 9f60b46e1b..5a7f220a13 100644
--- a/src/nvim/drawline.h
+++ b/src/nvim/drawline.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_DRAWLINE_H
-#define NVIM_DRAWLINE_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "klib/kvec.h"
#include "nvim/decoration_provider.h"
-#include "nvim/fold.h"
-#include "nvim/macros.h"
-#include "nvim/screen.h"
-#include "nvim/types.h"
+#include "nvim/fold_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
// Maximum columns for terminal highlight attributes
#define TERM_ATTRS_MAX 1024
@@ -20,11 +19,21 @@ typedef struct {
int win_row;
int win_col;
} WinExtmark;
-EXTERN kvec_t(WinExtmark) win_extmark_arr INIT(= KV_INITIAL_VALUE);
+EXTERN kvec_t(WinExtmark) win_extmark_arr INIT( = KV_INITIAL_VALUE);
-EXTERN bool conceal_cursor_used INIT(= false);
+EXTERN bool conceal_cursor_used INIT( = false);
+
+// Spell checking variables passed from win_update() to win_line().
+typedef struct {
+ bool spv_has_spell; ///< drawn window has spell checking
+ bool spv_unchanged; ///< not updating for changed text
+ int spv_checked_col; ///< column in "checked_lnum" up to
+ ///< which there are no spell errors
+ linenr_T spv_checked_lnum; ///< line number for "checked_col"
+ int spv_cap_col; ///< column to check for Cap word
+ linenr_T spv_capcol_lnum; ///< line number for "cap_col"
+} spellvars_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawline.h.generated.h"
#endif
-#endif // NVIM_DRAWLINE_H
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index 04c342e068..6cc623cb72 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// drawscreen.c: Code for updating all the windows on the screen.
// This is the top level, drawline.c is the middle and grid.c/screen.c the lower level.
@@ -56,24 +53,32 @@
#include <assert.h>
#include <inttypes.h>
+#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
+#include "nvim/digraph.h"
#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
+#include "nvim/eval.h"
#include "nvim/ex_getln.h"
-#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
+#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -86,20 +91,25 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
+#include "nvim/search.h"
+#include "nvim/sign_defs.h"
+#include "nvim/spell.h"
+#include "nvim/state.h"
#include "nvim/statusline.h"
+#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// corner value flags for hsep_connected and vsep_connected
@@ -118,8 +128,6 @@ static bool redraw_popupmenu = false;
static bool msg_grid_invalid = false;
static bool resizing_autocmd = false;
-static char *provider_err = NULL;
-
/// Check if the cursor line needs to be redrawn because of 'concealcursor'.
///
/// When cursor is moved at the same time, both lines will be redrawn regardless.
@@ -202,7 +210,7 @@ bool default_grid_alloc(void)
void screenclear(void)
{
- check_for_delay(false);
+ msg_check_for_delay(false);
if (starting == NO_SCREEN || default_grid.chars == NULL) {
return;
@@ -212,7 +220,6 @@ void screenclear(void)
for (int i = 0; i < default_grid.rows; i++) {
grid_clear_line(&default_grid, default_grid.line_offset[i],
default_grid.cols, true);
- default_grid.line_wraps[i] = false;
}
ui_call_grid_clear(1); // clear the display
@@ -318,11 +325,15 @@ void screen_resize(int width, int height)
resizing_autocmd = false;
redraw_all_later(UPD_CLEAR);
+ if (State != MODE_ASKMORE && State != MODE_EXTERNCMD && State != MODE_CONFIRM) {
+ screenclear();
+ }
+
if (starting != NO_SCREEN) {
maketitle();
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
// We only redraw when it's needed:
// - While at the more prompt or executing an external command, don't
@@ -370,6 +381,32 @@ void screen_resize(int width, int height)
resizing_screen = false;
}
+/// Check if the new Nvim application "screen" dimensions are valid.
+/// Correct it if it's too small or way too big.
+void check_screensize(void)
+{
+ // Limit Rows and Columns to avoid an overflow in Rows * Columns.
+ if (Rows < min_rows()) {
+ // need room for one window and command line
+ Rows = min_rows();
+ } else if (Rows > 1000) {
+ Rows = 1000;
+ }
+
+ if (Columns < MIN_COLUMNS) {
+ Columns = MIN_COLUMNS;
+ } else if (Columns > 10000) {
+ Columns = 10000;
+ }
+}
+
+/// Return true if redrawing should currently be done.
+bool redrawing(void)
+{
+ return !RedrawingDisabled
+ && !(p_lz && char_avail() && !KeyTyped && !do_redraw);
+}
+
/// Redraw the parts of the screen that is marked for redraw.
///
/// Most code shouldn't call this directly, rather use redraw_later() and
@@ -411,6 +448,15 @@ int update_screen(void)
display_tick++; // let syntax code know we're in a next round of
// display updating
+ // glyph cache full, very rare
+ if (schar_cache_clear_if_full()) {
+ // must use CLEAR, as the contents of screen buffers cannot be
+ // compared to their previous state here.
+ // TODO(bfredl): if start to cache schar_T values in places (like fcs/lcs)
+ // we need to revalidate these here as well!
+ type = MAX(type, UPD_CLEAR);
+ }
+
// Tricky: vim code can reset msg_scrolled behind our back, so need
// separate bookkeeping for now.
if (msg_did_scroll) {
@@ -430,7 +476,7 @@ int update_screen(void)
// non-displayed part of msg_grid is considered invalid.
for (int i = 0; i < MIN(msg_scrollsize(), msg_grid.rows); i++) {
grid_clear_line(&msg_grid, msg_grid.line_offset[i],
- msg_grid.cols, false);
+ msg_grid.cols, i < p_ch);
}
}
msg_grid.throttled = false;
@@ -507,7 +553,7 @@ int update_screen(void)
ui_comp_set_screen_valid(true);
DecorProviders providers;
- decor_providers_start(&providers, &provider_err);
+ decor_providers_start(&providers);
// "start" callback could have changed highlights for global elements
if (win_check_ns_hl(NULL)) {
@@ -516,7 +562,7 @@ int update_screen(void)
}
if (clear_cmdline) { // going to clear cmdline (done below)
- check_for_delay(false);
+ msg_check_for_delay(false);
}
// Force redraw when width of 'number' or 'relativenumber' column
@@ -540,9 +586,9 @@ int update_screen(void)
draw_tabline();
}
- // Correct stored syntax highlighting info for changes in each displayed
- // buffer. Each buffer must only be done once.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ // Correct stored syntax highlighting info for changes in each displayed
+ // buffer. Each buffer must only be done once.
update_window_hl(wp, type >= UPD_NOT_VALID || hl_changed);
buf_T *buf = wp->w_buffer;
@@ -554,10 +600,19 @@ int update_screen(void)
}
if (buf->b_mod_tick_decor < display_tick) {
- decor_providers_invoke_buf(buf, &providers, &provider_err);
+ decor_providers_invoke_buf(buf, &providers);
buf->b_mod_tick_decor = display_tick;
}
}
+
+ // Reset 'statuscolumn' if there is no dedicated signcolumn but it is invalid.
+ if (*wp->w_p_stc != NUL && wp->w_minscwidth <= SCL_NO
+ && (wp->w_buffer->b_signcols.invalid_bot || !wp->w_buffer->b_signcols.sentinel)) {
+ wp->w_nrwidth_line_count = 0;
+ wp->w_valid &= ~VALID_WCOL;
+ wp->w_redr_type = UPD_NOT_VALID;
+ wp->w_buffer->b_signcols.invalid_bot = 0;
+ }
}
// Go from top to bottom through the windows, redrawing the ones that need it.
@@ -624,7 +679,7 @@ int update_screen(void)
}
did_intro = true;
- decor_providers_invoke_end(&providers, &provider_err);
+ decor_providers_invoke_end(&providers);
kvi_destroy(providers);
// either cmdline is cleared, not drawn or mode is last drawn
@@ -632,20 +687,56 @@ int update_screen(void)
return OK;
}
-static void win_border_redr_title(win_T *wp, ScreenGrid *grid, int col)
+/// Prepare for 'hlsearch' highlighting.
+void start_search_hl(void)
+{
+ if (!p_hls || no_hlsearch) {
+ return;
+ }
+
+ end_search_hl(); // just in case it wasn't called before
+ last_pat_prog(&screen_search_hl.rm);
+ // Set the time limit to 'redrawtime'.
+ screen_search_hl.tm = profile_setlimit(p_rdt);
+}
+
+/// Clean up for 'hlsearch' highlighting.
+void end_search_hl(void)
{
- VirtText title_chunks = wp->w_float_config.title_chunks;
+ if (screen_search_hl.rm.regprog == NULL) {
+ return;
+ }
- for (size_t i = 0; i < title_chunks.size; i++) {
- char *text = title_chunks.items[i].text;
- int cell = (int)mb_string2cells(text);
- int hl_id = title_chunks.items[i].hl_id;
- int attr = hl_id ? syn_id2attr(hl_id) : 0;
- grid_puts(grid, text, 0, col, attr);
- col += cell;
+ vim_regfree(screen_search_hl.rm.regprog);
+ screen_search_hl.rm.regprog = NULL;
+}
+
+static void win_redr_bordertext(win_T *wp, VirtText vt, int col)
+{
+ for (size_t i = 0; i < kv_size(vt);) {
+ int attr = 0;
+ char *text = next_virt_text_chunk(vt, &i, &attr);
+ if (text == NULL) {
+ break;
+ }
+ attr = hl_apply_winblend(wp, attr);
+ col += grid_line_puts(col, text, -1, attr);
}
}
+int win_get_bordertext_col(int total_col, int text_width, AlignTextPos align)
+{
+ switch (align) {
+ case kAlignLeft:
+ return 1;
+ case kAlignCenter:
+ return MAX((total_col - text_width) / 2 + 1, 1);
+ case kAlignRight:
+ return MAX(total_col - text_width + 1, 1);
+ }
+ UNREACHABLE;
+}
+
static void win_redr_border(win_T *wp)
{
wp->w_redr_border = false;
@@ -655,102 +746,459 @@ static void win_redr_border(win_T *wp)
ScreenGrid *grid = &wp->w_grid_alloc;
- schar_T *chars = wp->w_float_config.border_chars;
+ schar_T chars[8];
+ for (int i = 0; i < 8; i++) {
+ chars[i] = schar_from_str(wp->w_float_config.border_chars[i]);
+ }
int *attrs = wp->w_float_config.border_attr;
int *adj = wp->w_border_adj;
- int irow = wp->w_height_inner + wp->w_winbar_height, icol = wp->w_width_inner;
+ int irow = wp->w_height_inner + wp->w_winbar_height;
+ int icol = wp->w_width_inner;
if (adj[0]) {
- grid_puts_line_start(grid, 0);
+ grid_line_start(grid, 0);
if (adj[3]) {
- grid_put_schar(grid, 0, 0, chars[0], attrs[0]);
+ grid_line_put_schar(0, chars[0], attrs[0]);
}
for (int i = 0; i < icol; i++) {
- grid_put_schar(grid, 0, i + adj[3], chars[1], attrs[1]);
+ grid_line_put_schar(i + adj[3], chars[1], attrs[1]);
}
if (wp->w_float_config.title) {
- int title_col = 0;
- int title_width = wp->w_float_config.title_width;
- AlignTextPos title_pos = wp->w_float_config.title_pos;
-
- if (title_pos == kAlignCenter) {
- title_col = (icol - title_width) / 2 + 1;
- } else {
- title_col = title_pos == kAlignLeft ? 1 : icol - title_width + 1;
- }
-
- win_border_redr_title(wp, grid, title_col);
+ int title_col = win_get_bordertext_col(icol, wp->w_float_config.title_width,
+ wp->w_float_config.title_pos);
+ win_redr_bordertext(wp, wp->w_float_config.title_chunks, title_col);
}
if (adj[1]) {
- grid_put_schar(grid, 0, icol + adj[3], chars[2], attrs[2]);
+ grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
}
- grid_puts_line_flush(false);
+ grid_line_flush();
}
for (int i = 0; i < irow; i++) {
if (adj[3]) {
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], 0, chars[7], attrs[7]);
- grid_puts_line_flush(false);
+ grid_line_start(grid, i + adj[0]);
+ grid_line_put_schar(0, chars[7], attrs[7]);
+ grid_line_flush();
}
if (adj[1]) {
- int ic = (i == 0 && !adj[0] && chars[2][0]) ? 2 : 3;
- grid_puts_line_start(grid, i + adj[0]);
- grid_put_schar(grid, i + adj[0], icol + adj[3], chars[ic], attrs[ic]);
- grid_puts_line_flush(false);
+ int ic = (i == 0 && !adj[0] && chars[2]) ? 2 : 3;
+ grid_line_start(grid, i + adj[0]);
+ grid_line_put_schar(icol + adj[3], chars[ic], attrs[ic]);
+ grid_line_flush();
}
}
if (adj[2]) {
- grid_puts_line_start(grid, irow + adj[0]);
+ grid_line_start(grid, irow + adj[0]);
if (adj[3]) {
- grid_put_schar(grid, irow + adj[0], 0, chars[6], attrs[6]);
+ grid_line_put_schar(0, chars[6], attrs[6]);
}
+
for (int i = 0; i < icol; i++) {
- int ic = (i == 0 && !adj[3] && chars[6][0]) ? 6 : 5;
- grid_put_schar(grid, irow + adj[0], i + adj[3], chars[ic], attrs[ic]);
+ int ic = (i == 0 && !adj[3] && chars[6]) ? 6 : 5;
+ grid_line_put_schar(i + adj[3], chars[ic], attrs[ic]);
+ }
+
+ if (wp->w_float_config.footer) {
+ int footer_col = win_get_bordertext_col(icol, wp->w_float_config.footer_width,
+ wp->w_float_config.footer_pos);
+ win_redr_bordertext(wp, wp->w_float_config.footer_chunks, footer_col);
}
if (adj[1]) {
- grid_put_schar(grid, irow + adj[0], icol + adj[3], chars[4], attrs[4]);
+ grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
+ }
+ grid_line_flush();
+ }
+}
+
+/// Set cursor to its position in the current window.
+void setcursor(void)
+{
+ setcursor_mayforce(false);
+}
+
+/// Set cursor to its position in the current window.
+/// @param force when true, also when not redrawing.
+void setcursor_mayforce(bool force)
+{
+ if (force || redrawing()) {
+ validate_cursor();
+
+ ScreenGrid *grid = &curwin->w_grid;
+ int row = curwin->w_wrow;
+ int col = curwin->w_wcol;
+ if (curwin->w_p_rl) {
+ // With 'rightleft' set and the cursor on a double-wide character,
+ // position it on the leftmost column.
+ col = curwin->w_width_inner - curwin->w_wcol
+ - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2
+ && vim_isprintc(gchar_cursor())) ? 2 : 1);
}
- grid_puts_line_flush(false);
+
+ grid_adjust(&grid, &row, &col);
+ ui_grid_cursor_goto(grid->handle, row, col);
}
}
/// Show current cursor info in ruler and various other places
///
/// @param always if false, only show ruler if position has changed.
-void show_cursor_info(bool always)
+void show_cursor_info_later(bool force)
{
- if (!always && !redrawing()) {
- return;
+ int state = get_real_state();
+ int empty_line = (State & MODE_INSERT) == 0
+ && *ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum) == NUL;
+
+ // Only draw when something changed.
+ validate_virtcol_win(curwin);
+ if (force
+ || curwin->w_cursor.lnum != curwin->w_stl_cursor.lnum
+ || curwin->w_cursor.col != curwin->w_stl_cursor.col
+ || curwin->w_virtcol != curwin->w_stl_virtcol
+ || curwin->w_cursor.coladd != curwin->w_stl_cursor.coladd
+ || curwin->w_topline != curwin->w_stl_topline
+ || curwin->w_buffer->b_ml.ml_line_count != curwin->w_stl_line_count
+ || curwin->w_topfill != curwin->w_stl_topfill
+ || empty_line != curwin->w_stl_empty
+ || reg_recording != curwin->w_stl_recording
+ || state != curwin->w_stl_state
+ || (VIsual_active && VIsual_mode != curwin->w_stl_visual_mode)) {
+ if (curwin->w_status_height || global_stl_height()) {
+ curwin->w_redr_status = true;
+ } else {
+ redraw_cmdline = true;
+ }
+
+ if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
+ curwin->w_redr_status = true;
+ }
+
+ if ((p_icon && (stl_syntax & STL_IN_ICON))
+ || (p_title && (stl_syntax & STL_IN_TITLE))) {
+ need_maketitle = true;
+ }
+ }
+
+ curwin->w_stl_cursor = curwin->w_cursor;
+ curwin->w_stl_virtcol = curwin->w_virtcol;
+ curwin->w_stl_empty = (char)empty_line;
+ curwin->w_stl_topline = curwin->w_topline;
+ curwin->w_stl_line_count = curwin->w_buffer->b_ml.ml_line_count;
+ curwin->w_stl_topfill = curwin->w_topfill;
+ curwin->w_stl_recording = reg_recording;
+ curwin->w_stl_state = state;
+ if (VIsual_active) {
+ curwin->w_stl_visual_mode = VIsual_mode;
}
+}
+
+/// @return true when postponing displaying the mode message: when not redrawing
+/// or inside a mapping.
+bool skip_showmode(void)
+{
+ // Call char_avail() only when we are going to show something, because it
+ // takes a bit of time. redrawing() may also call char_avail().
+ if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) {
+ redraw_mode = true; // show mode later
+ return true;
+ }
+ return false;
+}
+
+/// Show the current mode and ruler.
+///
+/// If clear_cmdline is true, clear the rest of the cmdline.
+/// If clear_cmdline is false there may be a message there that needs to be
+/// cleared only if a mode is shown.
+/// If redraw_mode is true show or clear the mode.
+/// @return the length of the message (0 if no message).
+int showmode(void)
+{
+ int length = 0;
+
+ if (ui_has(kUIMessages) && clear_cmdline) {
+ msg_ext_clear(true);
+ }
+
+ // don't make non-flushed message part of the showmode
+ msg_ext_ui_flush();
+
+ msg_grid_validate();
+
+ int do_mode = ((p_smd && msg_silent == 0)
+ && ((State & MODE_TERMINAL)
+ || (State & MODE_INSERT)
+ || restart_edit != NUL
+ || VIsual_active));
+
+ bool can_show_mode = (p_ch != 0 || ui_has(kUIMessages));
+ if ((do_mode || reg_recording != 0) && can_show_mode) {
+ int sub_attr;
+ if (skip_showmode()) {
+ return 0; // show mode later
+ }
+
+ bool nwr_save = need_wait_return;
+
+ // wait a bit before overwriting an important message
+ msg_check_for_delay(false);
+
+ // if the cmdline is more than one line high, erase top lines
+ bool need_clear = clear_cmdline;
+ if (clear_cmdline && cmdline_row < Rows - 1) {
+ msg_clr_cmdline(); // will reset clear_cmdline
+ }
+
+ // Position on the last line in the window, column 0
+ msg_pos_mode();
+ int attr = HL_ATTR(HLF_CM); // Highlight mode
+
+ // When the screen is too narrow to show the entire mode message,
+ // avoid scrolling and truncate instead.
+ msg_no_more = true;
+ int save_lines_left = lines_left;
+ lines_left = 0;
+
+ if (do_mode) {
+ msg_puts_attr("--", attr);
+ // CTRL-X in Insert mode
+ if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
+ // These messages can get long, avoid a wrap in a narrow window.
+ // Prefer showing edit_submode_extra. With external messages there
+ // is no imposed limit.
+ if (ui_has(kUIMessages)) {
+ length = INT_MAX;
+ } else {
+ length = (Rows - msg_row) * Columns - 3;
+ }
+ if (edit_submode_extra != NULL) {
+ length -= vim_strsize(edit_submode_extra);
+ }
+ if (length > 0) {
+ if (edit_submode_pre != NULL) {
+ length -= vim_strsize(edit_submode_pre);
+ }
+ if (length - vim_strsize(edit_submode) > 0) {
+ if (edit_submode_pre != NULL) {
+ msg_puts_attr(edit_submode_pre, attr);
+ }
+ msg_puts_attr(edit_submode, attr);
+ }
+ if (edit_submode_extra != NULL) {
+ msg_puts_attr(" ", attr); // Add a space in between.
+ if ((int)edit_submode_highl < HLF_COUNT) {
+ sub_attr = win_hl_attr(curwin, (int)edit_submode_highl);
+ } else {
+ sub_attr = attr;
+ }
+ msg_puts_attr(edit_submode_extra, sub_attr);
+ }
+ }
+ } else {
+ if (State & MODE_TERMINAL) {
+ msg_puts_attr(_(" TERMINAL"), attr);
+ } else if (State & VREPLACE_FLAG) {
+ msg_puts_attr(_(" VREPLACE"), attr);
+ } else if (State & REPLACE_FLAG) {
+ msg_puts_attr(_(" REPLACE"), attr);
+ } else if (State & MODE_INSERT) {
+ if (p_ri) {
+ msg_puts_attr(_(" REVERSE"), attr);
+ }
+ msg_puts_attr(_(" INSERT"), attr);
+ } else if (restart_edit == 'I' || restart_edit == 'i'
+ || restart_edit == 'a' || restart_edit == 'A') {
+ if (curbuf->terminal) {
+ msg_puts_attr(_(" (terminal)"), attr);
+ } else {
+ msg_puts_attr(_(" (insert)"), attr);
+ }
+ } else if (restart_edit == 'R') {
+ msg_puts_attr(_(" (replace)"), attr);
+ } else if (restart_edit == 'V') {
+ msg_puts_attr(_(" (vreplace)"), attr);
+ }
+ if (State & MODE_LANGMAP) {
+ if (curwin->w_p_arab) {
+ msg_puts_attr(_(" Arabic"), attr);
+ } else if (get_keymap_str(curwin, " (%s)",
+ NameBuff, MAXPATHL)) {
+ msg_puts_attr(NameBuff, attr);
+ }
+ }
+ if ((State & MODE_INSERT) && p_paste) {
+ msg_puts_attr(_(" (paste)"), attr);
+ }
+
+ if (VIsual_active) {
+ char *p;
+
+ // Don't concatenate separate words to avoid translation
+ // problems.
+ switch ((VIsual_select ? 4 : 0)
+ + (VIsual_mode == Ctrl_V) * 2
+ + (VIsual_mode == 'V')) {
+ case 0:
+ p = N_(" VISUAL"); break;
+ case 1:
+ p = N_(" VISUAL LINE"); break;
+ case 2:
+ p = N_(" VISUAL BLOCK"); break;
+ case 4:
+ p = N_(" SELECT"); break;
+ case 5:
+ p = N_(" SELECT LINE"); break;
+ default:
+ p = N_(" SELECT BLOCK"); break;
+ }
+ msg_puts_attr(_(p), attr);
+ }
+ msg_puts_attr(" --", attr);
+ }
+
+ need_clear = true;
+ }
+ if (reg_recording != 0
+ && edit_submode == NULL // otherwise it gets too long
+ ) {
+ recording_mode(attr);
+ need_clear = true;
+ }
+
+ mode_displayed = true;
+ if (need_clear || clear_cmdline || redraw_mode) {
+ msg_clr_eos();
+ }
+ msg_didout = false; // overwrite this message
+ length = msg_col;
+ msg_col = 0;
+ msg_no_more = false;
+ lines_left = save_lines_left;
+ need_wait_return = nwr_save; // never ask for hit-return for this
+ } else if (clear_cmdline && msg_silent == 0) {
+ // Clear the whole command line. Will reset "clear_cmdline".
+ msg_clr_cmdline();
+ } else if (redraw_mode) {
+ msg_pos_mode();
+ msg_clr_eos();
+ }
+
+ // NB: also handles clearing the showmode if it was empty or disabled
+ msg_ext_flush_showmode();
+
+ // In Visual mode the size of the selected area must be redrawn.
+ if (VIsual_active) {
+ clear_showcmd();
+ }
+
+ // If the current or last window has no status line and global statusline is disabled,
+ // the ruler is after the mode message and must be redrawn
+ win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating();
+ if (redrawing() && ruler_win->w_status_height == 0 && global_stl_height() == 0
+ && !(p_ch == 0 && !ui_has(kUIMessages))) {
+ grid_line_start(&msg_grid_adj, Rows - 1);
+ win_redr_ruler(ruler_win);
+ grid_line_flush();
+ }
+
+ redraw_cmdline = false;
+ redraw_mode = false;
+ clear_cmdline = false;
+
+ return length;
+}
+
+/// Position for a mode message.
+static void msg_pos_mode(void)
+{
+ msg_col = 0;
+ msg_row = Rows - 1;
+}
- win_check_ns_hl(curwin);
- if ((*p_stl != NUL || *curwin->w_p_stl != NUL)
- && (curwin->w_status_height || global_stl_height())) {
- redraw_custom_statusline(curwin);
+/// Delete mode message. Used when ESC is typed which is expected to end
+/// Insert mode (but Insert mode didn't end yet!).
+/// Caller should check "mode_displayed".
+void unshowmode(bool force)
+{
+ // Don't delete it right now, when not redrawing or inside a mapping.
+ if (!redrawing() || (!force && char_avail() && !KeyTyped)) {
+ redraw_cmdline = true; // delete mode later
} else {
- win_redr_ruler(curwin, always);
+ clearmode();
}
- if (*p_wbr != NUL || *curwin->w_p_wbr != NUL) {
- win_redr_winbar(curwin);
+}
+
+// Clear the mode message.
+void clearmode(void)
+{
+ const int save_msg_row = msg_row;
+ const int save_msg_col = msg_col;
+
+ msg_ext_ui_flush();
+ msg_pos_mode();
+ if (reg_recording != 0) {
+ recording_mode(HL_ATTR(HLF_CM));
}
+ msg_clr_eos();
+ msg_ext_flush_showmode();
- if (need_maketitle
- || (p_icon && (stl_syntax & STL_IN_ICON))
- || (p_title && (stl_syntax & STL_IN_TITLE))) {
- maketitle();
+ msg_col = save_msg_col;
+ msg_row = save_msg_row;
+}
+
+static void recording_mode(int attr)
+{
+ msg_puts_attr(_("recording"), attr);
+ if (shortmess(SHM_RECORDING)) {
+ return;
}
- win_check_ns_hl(NULL);
- // Redraw the tab pages line if needed.
- if (redraw_tabline) {
- draw_tabline();
+ char s[4];
+ snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
+ msg_puts_attr(s, attr);
+}
+
+#define COL_RULER 17 // columns needed by standard ruler
+
+/// Compute columns for ruler and shown command. 'sc_col' is also used to
+/// decide what the maximum length of a message on the status line can be.
+/// If there is a status line for the last window, 'sc_col' is independent
+/// of 'ru_col'.
+void comp_col(void)
+{
+ bool last_has_status = last_stl_height(false) > 0;
+
+ sc_col = 0;
+ ru_col = 0;
+ if (p_ru) {
+ ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
+ // no last status line, adjust sc_col
+ if (!last_has_status) {
+ sc_col = ru_col;
+ }
+ }
+ if (p_sc) {
+ sc_col += SHOWCMD_COLS;
+ if (!p_ru || last_has_status) { // no need for separating space
+ sc_col++;
+ }
+ }
+ assert(sc_col >= 0
+ && INT_MIN + sc_col <= Columns);
+ sc_col = Columns - sc_col;
+ assert(ru_col >= 0
+ && INT_MIN + ru_col <= Columns);
+ ru_col = Columns - ru_col;
+ if (sc_col <= 0) { // screen too narrow, will become a mess
+ sc_col = 1;
}
+ if (ru_col <= 0) {
+ ru_col = 1;
+ }
+ set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
}
static void redraw_win_signcol(win_T *wp)
@@ -762,6 +1210,7 @@ static void redraw_win_signcol(win_T *wp)
wp->w_scwidth = win_signcol_count(wp);
if (wp->w_scwidth != scwidth) {
changed_line_abv_curs_win(wp);
+ redraw_later(wp, UPD_NOT_VALID);
}
}
@@ -844,8 +1293,8 @@ static void draw_vsep_win(win_T *wp)
}
// draw the vertical separator right of this window
- int hl;
- int c = fillchar_vsep(wp, &hl);
+ int hl = win_hl_attr(wp, HLF_C);
+ int c = wp->w_p_fcs_chars.vert;
grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp),
W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl);
}
@@ -858,30 +1307,32 @@ static void draw_hsep_win(win_T *wp)
}
// draw the horizontal separator below this window
- int hl;
- int c = fillchar_hsep(wp, &hl);
+ int hl = win_hl_attr(wp, HLF_C);
+ int c = wp->w_p_fcs_chars.horiz;
grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1,
wp->w_wincol, W_ENDCOL(wp), c, c, hl);
}
/// Get the separator connector for specified window corner of window "wp"
-static int get_corner_sep_connector(win_T *wp, WindowCorner corner)
+static schar_T get_corner_sep_connector(win_T *wp, WindowCorner corner)
{
// It's impossible for windows to be connected neither vertically nor horizontally
// So if they're not vertically connected, assume they're horizontally connected
+ int c;
if (vsep_connected(wp, corner)) {
if (hsep_connected(wp, corner)) {
- return wp->w_p_fcs_chars.verthoriz;
+ c = wp->w_p_fcs_chars.verthoriz;
} else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) {
- return wp->w_p_fcs_chars.vertright;
+ c = wp->w_p_fcs_chars.vertright;
} else {
- return wp->w_p_fcs_chars.vertleft;
+ c = wp->w_p_fcs_chars.vertleft;
}
} else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) {
- return wp->w_p_fcs_chars.horizdown;
+ c = wp->w_p_fcs_chars.horizdown;
} else {
- return wp->w_p_fcs_chars.horizup;
+ c = wp->w_p_fcs_chars.horizup;
}
+ return schar_from_char(c);
}
/// Draw separator connecting characters on the corners of window "wp"
@@ -917,21 +1368,31 @@ static void draw_sep_connectors_win(win_T *wp)
win_at_left = frp->fr_parent == NULL;
// Draw the appropriate separator connector in every corner where drawing them is necessary
- if (!(win_at_top || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT),
- wp->w_winrow - 1, wp->w_wincol - 1, hl);
- }
- if (!(win_at_top || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT),
- wp->w_winrow - 1, W_ENDCOL(wp), hl);
- }
- if (!(win_at_bottom || win_at_left)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT),
- W_ENDROW(wp), wp->w_wincol - 1, hl);
- }
- if (!(win_at_bottom || win_at_right)) {
- grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT),
- W_ENDROW(wp), W_ENDCOL(wp), hl);
+ // Make sure not to send cursor position updates to ui.
+ bool top_left = !(win_at_top || win_at_left);
+ bool top_right = !(win_at_top || win_at_right);
+ bool bot_left = !(win_at_bottom || win_at_left);
+ bool bot_right = !(win_at_bottom || win_at_right);
+
+ if (top_left) {
+ grid_line_start(&default_grid, wp->w_winrow - 1);
+ grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_TOP_LEFT), hl);
+ grid_line_flush();
+ }
+ if (top_right) {
+ grid_line_start(&default_grid, wp->w_winrow - 1);
+ grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_TOP_RIGHT), hl);
+ grid_line_flush();
+ }
+ if (bot_left) {
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(wp->w_wincol - 1, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), hl);
+ grid_line_flush();
+ }
+ if (bot_right) {
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(W_ENDCOL(wp), get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), hl);
+ grid_line_flush();
}
}
@@ -992,9 +1453,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
int type = wp->w_redr_type;
if (type >= UPD_NOT_VALID) {
- // TODO(bfredl): should only be implied for CLEAR, not NOT_VALID!
wp->w_redr_status = true;
-
wp->w_lines_valid = 0;
}
@@ -1027,25 +1486,41 @@ static void win_update(win_T *wp, DecorProviders *providers)
win_extmark_arr.size = 0;
- decor_redraw_reset(buf, &decor_state);
+ decor_redraw_reset(wp, &decor_state);
DecorProviders line_providers;
- decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ decor_providers_invoke_win(wp, providers, &line_providers);
redraw_win_signcol(wp);
init_search_hl(wp, &screen_search_hl);
- // Force redraw when width of 'number' or 'relativenumber' column
- // changes.
- int nrwidth = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0;
- if (wp->w_nrwidth != nrwidth) {
- type = UPD_NOT_VALID;
- wp->w_nrwidth = nrwidth;
-
- if (buf->terminal) {
- terminal_check_size(buf->terminal);
+ // Make sure skipcol is valid, it depends on various options and the window
+ // width.
+ if (wp->w_skipcol > 0) {
+ int w = 0;
+ int width1 = wp->w_width_inner - win_col_off(wp);
+ int width2 = width1 + win_col_off2(wp);
+ int add = width1;
+
+ while (w < wp->w_skipcol) {
+ if (w > 0) {
+ add = width2;
+ }
+ w += add;
+ }
+ if (w != wp->w_skipcol) {
+ // always round down, the higher value may not be valid
+ wp->w_skipcol = w - add;
}
+ }
+
+ const int nrwidth_before = wp->w_nrwidth;
+ int nrwidth_new = (wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc) ? number_width(wp) : 0;
+ // Force redraw when width of 'number' or 'relativenumber' column changes.
+ if (wp->w_nrwidth != nrwidth_new) {
+ type = UPD_NOT_VALID;
+ wp->w_nrwidth = nrwidth_new;
} else if (buf->b_mod_set
&& buf->b_mod_xlines != 0
&& wp->w_redraw_top != 0) {
@@ -1099,8 +1574,6 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
}
if (mod_top != 0 && hasAnyFolding(wp)) {
- linenr_T lnumt, lnumb;
-
// A change in a line can cause lines above it to become folded or
// unfolded. Find the top most buffer line that may be affected.
// If the line was previously folded and displayed, get the first
@@ -1111,8 +1584,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// the line below it. If there is no valid entry, use w_topline.
// Find the first valid w_lines[] entry below mod_bot. Set lnumb
// to this line. If there is no valid entry, use MAXLNUM.
- lnumt = wp->w_topline;
- lnumb = MAXLNUM;
+ linenr_T lnumt = wp->w_topline;
+ linenr_T lnumb = MAXLNUM;
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid) {
if (wp->w_lines[i].wl_lastlnum < mod_top) {
@@ -1168,11 +1641,11 @@ static void win_update(win_T *wp, DecorProviders *providers)
// When only displaying the lines at the top, set top_end. Used when
// window has scrolled down for msg_scrolled.
if (type == UPD_REDRAW_TOP) {
- long j = 0;
+ int j = 0;
for (int i = 0; i < wp->w_lines_valid; i++) {
j += wp->w_lines[i].wl_size;
if (j >= wp->w_upd_rows) {
- top_end = (int)j;
+ top_end = j;
break;
}
}
@@ -1205,7 +1678,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
|| (wp->w_topline == wp->w_lines[0].wl_lnum
&& wp->w_topfill > wp->w_old_topfill))) {
// New topline is above old topline: May scroll down.
- long j;
+ int j;
if (hasAnyFolding(wp)) {
linenr_T ln;
@@ -1223,7 +1696,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
j = wp->w_lines[0].wl_lnum - wp->w_topline;
}
if (j < wp->w_grid.rows - 2) { // not too far off
- int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1);
+ int i = plines_m_win(wp, wp->w_topline, wp->w_lines[0].wl_lnum - 1, true);
// insert extra lines for previously invisible filler lines
if (wp->w_lines[0].wl_lnum != wp->w_topline) {
i += win_get_fill(wp, wp->w_lines[0].wl_lnum) - wp->w_old_topfill;
@@ -1265,7 +1738,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// needs updating.
// try to find wp->w_topline in wp->w_lines[].wl_lnum
- long j = -1;
+ int j = -1;
int row = 0;
for (int i = 0; i < wp->w_lines_valid; i++) {
if (wp->w_lines[i].wl_valid
@@ -1304,7 +1777,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// bot_start to the first row that needs redrawing.
bot_start = 0;
int idx = 0;
- for (;;) {
+ while (true) {
wp->w_lines[idx] = wp->w_lines[j];
// stop at line that didn't fit, unless it is still
// valid (no lines deleted)
@@ -1417,7 +1890,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// First compute the actual start and end column.
if (VIsual_mode == Ctrl_V) {
colnr_T fromc, toc;
- unsigned int save_ve_flags = curwin->w_ve_flags;
+ unsigned save_ve_flags = curwin->w_ve_flags;
if (curwin->w_p_lbr) {
curwin->w_ve_flags = VE_ALL;
@@ -1441,7 +1914,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
pos.lnum += cursor_above ? 1 : -1) {
colnr_T t;
- pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum, false));
+ pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum));
getvvcol(wp, &pos, NULL, NULL, &t);
if (toc < t) {
toc = t;
@@ -1548,19 +2021,34 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_old_visual_col = 0;
}
- bool cursorline_standout = win_cursorline_standout(wp);
+ foldinfo_T cursorline_fi = { 0 };
+ wp->w_cursorline = win_cursorline_standout(wp) ? wp->w_cursor.lnum : 0;
+ if (wp->w_p_cul) {
+ // Make sure that the cursorline on a closed fold is redrawn
+ cursorline_fi = fold_info(wp, wp->w_cursor.lnum);
+ if (cursorline_fi.fi_level != 0 && cursorline_fi.fi_lines > 0) {
+ wp->w_cursorline = cursorline_fi.fi_lnum;
+ }
+ }
win_check_ns_hl(wp);
+ spellvars_T spv = { 0 };
+ linenr_T lnum = wp->w_topline; // first line shown in window
+ // Initialize spell related variables for the first drawn line.
+ if (spell_check_window(wp)) {
+ spv.spv_has_spell = true;
+ spv.spv_unchanged = mod_top == 0;
+ }
+
// Update all the window rows.
int idx = 0; // first entry in w_lines[].wl_size
int row = 0; // current window row to display
int srow = 0; // starting row of the current line
- linenr_T lnum = wp->w_topline; // first line shown in window
bool eof = false; // if true, we hit the end of the file
bool didline = false; // if true, we finished the last line
- for (;;) {
+ while (true) {
// stop updating when reached the end of the window (check for _past_
// the end of the window is at the end of the loop)
if (row == wp->w_grid.rows) {
@@ -1604,7 +2092,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// if lines were inserted or deleted
|| (wp->w_match_head != NULL
&& buf->b_mod_xlines != 0)))))
- || (cursorline_standout && lnum == wp->w_cursor.lnum)
+ || lnum == wp->w_cursorline
|| lnum == wp->w_last_cursorline) {
if (lnum == mod_top) {
top_to_mod = false;
@@ -1620,8 +2108,6 @@ static void win_update(win_T *wp, DecorProviders *providers)
&& !(dollar_vcol >= 0 && mod_bot == mod_top + 1)
&& row >= top_end) {
int old_rows = 0;
- int new_rows = 0;
- int xtra_rows;
linenr_T l;
int i;
@@ -1656,14 +2142,20 @@ static void win_update(win_T *wp, DecorProviders *providers)
bot_start = 0;
bot_scroll_start = 0;
} else {
+ int new_rows = 0;
// Able to count old number of rows: Count new window
// rows, and may insert/delete lines
- long j = idx;
+ int j = idx;
for (l = lnum; l < mod_bot; l++) {
if (hasFoldingWin(wp, l, NULL, &l, true, NULL)) {
new_rows++;
} else if (l == wp->w_topline) {
- new_rows += plines_win_nofill(wp, l, true) + wp->w_topfill;
+ int n = plines_win_nofill(wp, l, false) + wp->w_topfill;
+ n -= adjust_plines_for_skipcol(wp);
+ if (n > wp->w_height_inner) {
+ n = wp->w_height_inner;
+ }
+ new_rows += n;
} else {
new_rows += plines_win(wp, l, true);
}
@@ -1674,7 +2166,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
break;
}
}
- xtra_rows = new_rows - old_rows;
+ int xtra_rows = new_rows - old_rows;
if (xtra_rows < 0) {
// May scroll text up. If there is not enough
// remaining text or scrolling fails, must redraw the
@@ -1711,17 +2203,17 @@ static void win_update(win_T *wp, DecorProviders *providers)
int x = row + new_rows;
// move entries in w_lines[] upwards
- for (;;) {
+ while (true) {
// stop at last valid entry in w_lines[]
if (i >= wp->w_lines_valid) {
- wp->w_lines_valid = (int)j;
+ wp->w_lines_valid = j;
break;
}
wp->w_lines[j] = wp->w_lines[i];
// stop at a line that won't fit
if (x + (int)wp->w_lines[j].wl_size
> wp->w_grid.rows) {
- wp->w_lines_valid = (int)j + 1;
+ wp->w_lines_valid = j + 1;
break;
}
x += wp->w_lines[j++].wl_size;
@@ -1756,7 +2248,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
// When lines are folded, display one line for all of them.
// Otherwise, display normally (can be several display lines when
// 'wrap' is on).
- foldinfo_T foldinfo = fold_info(wp, lnum);
+ foldinfo_T foldinfo = wp->w_p_cul && lnum == wp->w_cursor.lnum
+ ? cursorline_fi : fold_info(wp, lnum);
if (foldinfo.fi_lines == 0
&& idx < wp->w_lines_valid
@@ -1778,9 +2271,10 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
// Display one line
- row = win_line(wp, lnum, srow,
- foldinfo.fi_lines ? srow : wp->w_grid.rows,
- mod_top == 0, false, foldinfo, &line_providers, &provider_err);
+ spellvars_T zero_spv = { 0 };
+ row = win_line(wp, lnum, srow, wp->w_grid.rows, false,
+ foldinfo.fi_lines > 0 ? &zero_spv : &spv,
+ foldinfo, &line_providers);
if (foldinfo.fi_lines == 0) {
wp->w_lines[idx].wl_folded = false;
@@ -1792,6 +2286,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_lines[idx].wl_folded = true;
wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
did_update = DID_FOLD;
+ spv.spv_capcol_lnum = 0;
}
}
@@ -1815,9 +2310,9 @@ static void win_update(win_T *wp, DecorProviders *providers)
if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) {
// 'relativenumber' set and cursor moved vertically: The
// text doesn't need to be drawn, but the number column does.
- foldinfo_T info = fold_info(wp, lnum);
- (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, true,
- info, &line_providers, &provider_err);
+ foldinfo_T info = wp->w_p_cul && lnum == wp->w_cursor.lnum
+ ? cursorline_fi : fold_info(wp, lnum);
+ (void)win_line(wp, lnum, srow, wp->w_grid.rows, true, &spv, info, &line_providers);
}
// This line does not need to be drawn, advance to the next one.
@@ -1827,6 +2322,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
lnum = wp->w_lines[idx - 1].wl_lastlnum + 1;
did_update = DID_NONE;
+ spv.spv_capcol_lnum = 0;
}
// 'statuscolumn' width has changed or errored, start from the top.
@@ -1837,7 +2333,8 @@ static void win_update(win_T *wp, DecorProviders *providers)
lnum = wp->w_topline;
wp->w_lines_valid = 0;
wp->w_valid &= ~VALID_WCOL;
- decor_providers_invoke_win(wp, providers, &line_providers, &provider_err);
+ decor_redraw_reset(wp, &decor_state);
+ decor_providers_invoke_win(wp, providers, &line_providers);
continue;
}
@@ -1850,7 +2347,7 @@ static void win_update(win_T *wp, DecorProviders *providers)
// Now that the window has been redrawn with the old and new cursor line,
// update w_last_cursorline.
- wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0;
+ wp->w_last_cursorline = wp->w_cursorline;
wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
@@ -1880,24 +2377,21 @@ static void win_update(win_T *wp, DecorProviders *providers)
wp->w_botline = lnum;
wp->w_filler_rows = wp->w_grid.rows - srow;
} else if (dy_flags & DY_TRUNCATE) { // 'display' has "truncate"
- int scr_row = wp->w_grid.rows - 1;
- int symbol = wp->w_p_fcs_chars.lastline;
- char fillbuf[12]; // 2 characters of 6 bytes
- int charlen = utf_char2bytes(symbol, &fillbuf[0]);
- utf_char2bytes(symbol, &fillbuf[charlen]);
-
// Last line isn't finished: Display "@@@" in the last screen line.
- grid_puts_len(&wp->w_grid, fillbuf, MIN(wp->w_grid.cols, 2) * charlen, scr_row, 0, at_attr);
- grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.cols, symbol, ' ', at_attr);
+ grid_line_start(&wp->w_grid, wp->w_grid.rows - 1);
+ grid_line_fill(0, MIN(wp->w_grid.cols, 3), wp->w_p_fcs_chars.lastline, at_attr);
+ grid_line_fill(3, wp->w_grid.cols, ' ', at_attr);
+ grid_line_flush();
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline"
- int start_col = wp->w_grid.cols - 3;
- int symbol = wp->w_p_fcs_chars.lastline;
-
// Last line isn't finished: Display "@@@" at the end.
- grid_fill(&wp->w_grid, wp->w_grid.rows - 1, wp->w_grid.rows,
- MAX(start_col, 0), wp->w_grid.cols, symbol, symbol, at_attr);
+ // If this would split a doublewidth char in two, we need to display "@@@@" instead
+ grid_line_start(&wp->w_grid, wp->w_grid.rows - 1);
+ int width = grid_line_getchar(MAX(wp->w_grid.cols - 3, 0), NULL) == NUL ? 4 : 3;
+ grid_line_fill(MAX(wp->w_grid.cols - width, 0), wp->w_grid.cols,
+ wp->w_p_fcs_chars.lastline, at_attr);
+ grid_line_flush();
set_empty_rows(wp, srow);
wp->w_botline = lnum;
} else {
@@ -1908,13 +2402,14 @@ static void win_update(win_T *wp, DecorProviders *providers)
} else {
if (eof) { // we hit the end of the file
wp->w_botline = buf->b_ml.ml_line_count + 1;
- long j = win_get_fill(wp, wp->w_botline);
+ int j = win_get_fill(wp, wp->w_botline);
if (j > 0 && !wp->w_botfill && row < wp->w_grid.rows) {
// Display filler text below last line. win_line() will check
// for ml_line_count+1 and only draw filler lines
- foldinfo_T info = { 0 };
- row = win_line(wp, wp->w_botline, row, wp->w_grid.rows,
- false, false, info, &line_providers, &provider_err);
+ spellvars_T zero_spv = { 0 };
+ foldinfo_T zero_foldinfo = { 0 };
+ row = win_line(wp, wp->w_botline, row, wp->w_grid.rows, false, &zero_spv,
+ zero_foldinfo, &line_providers);
}
} else if (dollar_vcol == -1) {
wp->w_botline = lnum;
@@ -1988,12 +2483,162 @@ static void win_update(win_T *wp, DecorProviders *providers)
}
}
+ if (nrwidth_before != wp->w_nrwidth && buf->terminal) {
+ terminal_check_size(buf->terminal);
+ }
+
// restore got_int, unless CTRL-C was hit while redrawing
if (!got_int) {
got_int = save_got_int;
}
}
+/// Scroll `line_count` lines at 'row' in window 'wp'.
+///
+/// Positive `line_count` means scrolling down, so that more space is available
+/// at 'row'. Negative `line_count` implies deleting lines at `row`.
+void win_scroll_lines(win_T *wp, int row, int line_count)
+{
+ if (!redrawing() || line_count == 0) {
+ return;
+ }
+
+ // No lines are being moved, just draw over the entire area
+ if (row + abs(line_count) >= wp->w_grid.rows) {
+ return;
+ }
+
+ if (line_count < 0) {
+ grid_del_lines(&wp->w_grid, row, -line_count,
+ wp->w_grid.rows, 0, wp->w_grid.cols);
+ } else {
+ grid_ins_lines(&wp->w_grid, row, line_count,
+ wp->w_grid.rows, 0, wp->w_grid.cols);
+ }
+}
+
+/// Call grid_fill() with columns adjusted for 'rightleft' if needed.
+/// Return the new offset.
+static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, int endrow,
+ int attr)
+{
+ int nn = off + width;
+ const int endcol = wp->w_grid.cols;
+
+ if (nn > endcol) {
+ nn = endcol;
+ }
+
+ if (wp->w_p_rl) {
+ grid_fill(&wp->w_grid, row, endrow, endcol - nn, endcol - off, c1, c2, attr);
+ } else {
+ grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr);
+ }
+
+ return nn;
+}
+
+/// Clear lines near the end of the window and mark the unused lines with "c1".
+/// Use "c2" as filler character.
+/// When "draw_margin" is true, then draw the sign/fold/number columns.
+void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
+{
+ assert(hl >= 0 && hl < HLF_COUNT);
+ int n = 0;
+
+ if (draw_margin) {
+ // draw the fold column
+ int fdc = compute_foldcolumn(wp, 0);
+ if (fdc > 0) {
+ n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow,
+ win_hl_attr(wp, HLF_FC));
+ }
+ // draw the sign column
+ int count = wp->w_scwidth;
+ if (count > 0) {
+ n = win_fill_end(wp, ' ', ' ', n, SIGN_WIDTH * count, row,
+ endrow, win_hl_attr(wp, HLF_SC));
+ }
+ // draw the number column
+ if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
+ n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow,
+ win_hl_attr(wp, HLF_N));
+ }
+ }
+
+ int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl));
+
+ const int endcol = wp->w_grid.cols;
+ if (wp->w_p_rl) {
+ grid_fill(&wp->w_grid, row, endrow, 0, endcol - 1 - n, c2, c2, attr);
+ grid_fill(&wp->w_grid, row, endrow, endcol - 1 - n, endcol - n, c1, c2, attr);
+ } else {
+ grid_fill(&wp->w_grid, row, endrow, n, endcol, c1, c2, attr);
+ }
+}
+
+/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
+/// space is available for window "wp", minus "col".
+int compute_foldcolumn(win_T *wp, int col)
+{
+ int fdc = win_fdccol_count(wp);
+ int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
+ int wwidth = wp->w_grid.cols;
+
+ if (fdc > wwidth - (col + wmw)) {
+ fdc = wwidth - (col + wmw);
+ }
+ return fdc;
+}
+
+/// Return the width of the 'number' and 'relativenumber' column.
+/// Caller may need to check if 'number' or 'relativenumber' is set.
+/// Otherwise it depends on 'numberwidth' and the line count.
+int number_width(win_T *wp)
+{
+ linenr_T lnum;
+
+ if (wp->w_p_rnu && !wp->w_p_nu) {
+ // cursor line shows "0"
+ lnum = wp->w_height_inner;
+ } else {
+ // cursor line shows absolute line number
+ lnum = wp->w_buffer->b_ml.ml_line_count;
+ }
+
+ if (lnum == wp->w_nrwidth_line_count) {
+ return wp->w_nrwidth_width;
+ }
+ wp->w_nrwidth_line_count = lnum;
+
+ // reset for 'statuscolumn'
+ if (*wp->w_p_stc != NUL) {
+ wp->w_statuscol_line_count = 0; // make sure width is re-estimated
+ wp->w_nrwidth_width = (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw;
+ return wp->w_nrwidth_width;
+ }
+
+ int n = 0;
+ do {
+ lnum /= 10;
+ n++;
+ } while (lnum > 0);
+
+ // 'numberwidth' gives the minimal width plus one
+ if (n < wp->w_p_nuw - 1) {
+ n = (int)wp->w_p_nuw - 1;
+ }
+
+ // If 'signcolumn' is set to 'number' and there is a sign to display, then
+ // the minimal width for the number column is 2.
+ if (n < 2 && wp->w_buffer->b_signs_with_text && wp->w_minscwidth == SCL_NUM) {
+ n = 2;
+ }
+
+ wp->w_nrwidth_width = n;
+ return n;
+}
+
/// Redraw a window later, with wp->w_redr_type >= type.
///
/// Set must_redraw only if not already set to a higher value.
@@ -2097,7 +2742,7 @@ void status_redraw_all(void)
bool is_stl_global = global_stl_height() != 0;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if ((!is_stl_global && wp->w_status_height) || (is_stl_global && wp == curwin)
+ if ((!is_stl_global && wp->w_status_height) || wp == curwin
|| wp->w_winbar_height) {
wp->w_redr_status = true;
redraw_later(wp, UPD_VALID);
@@ -2123,6 +2768,11 @@ void status_redraw_buf(buf_T *buf)
redraw_later(wp, UPD_VALID);
}
}
+ // Redraw the ruler if it is in the command line and was not marked for redraw above
+ if (p_ru && !curwin->w_status_height && !curwin->w_redr_status) {
+ redraw_cmdline = true;
+ redraw_later(curwin, UPD_VALID);
+ }
}
/// Redraw all status lines that need to be redrawn.
@@ -2182,3 +2832,48 @@ void redrawWinline(win_T *wp, linenr_T lnum)
redraw_later(wp, UPD_VALID);
}
}
+
+/// Return true if the cursor line in window "wp" may be concealed, according
+/// to the 'concealcursor' option.
+bool conceal_cursor_line(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int c;
+
+ if (*wp->w_p_cocu == NUL) {
+ return false;
+ }
+ if (get_real_state() & MODE_VISUAL) {
+ c = 'v';
+ } else if (State & MODE_INSERT) {
+ c = 'i';
+ } else if (State & MODE_NORMAL) {
+ c = 'n';
+ } else if (State & MODE_CMDLINE) {
+ c = 'c';
+ } else {
+ return false;
+ }
+ return vim_strchr(wp->w_p_cocu, c) != NULL;
+}
+
+/// Whether cursorline is drawn in a special way
+///
+/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
+bool win_cursorline_standout(const win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
+}
+
+/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
+/// Also when concealing is on and 'concealcursor' is not active.
+void redraw_for_cursorline(win_T *wp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
+ && (wp->w_p_rnu || win_cursorline_standout(wp))) {
+ // win_line() will redraw the number column and cursorline only.
+ redraw_later(wp, UPD_VALID);
+ }
+}
diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h
index c14703dfa9..565b01bcd1 100644
--- a/src/nvim/drawscreen.h
+++ b/src/nvim/drawscreen.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_DRAWSCREEN_H
-#define NVIM_DRAWSCREEN_H
+#pragma once
#include <stdbool.h>
+#include "nvim/buffer_defs.h"
#include "nvim/drawline.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
/// flags for update_screen()
/// The higher the value, the higher the priority
@@ -20,9 +20,13 @@ enum {
/// While redrawing the screen this flag is set. It means the screen size
/// ('lines' and 'rows') must not be changed.
-EXTERN bool updating_screen INIT(= 0);
+EXTERN bool updating_screen INIT( = 0);
+
+EXTERN match_T screen_search_hl INIT( = { 0 }); // used for 'hlsearch' highlight matching
+
+#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
+#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "drawscreen.h.generated.h"
#endif
-#endif // NVIM_DRAWSCREEN_H
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 96df6a3044..71a12ea1b0 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// edit.c: functions for Insert mode
#include <assert.h>
@@ -10,8 +7,9 @@
#include <string.h>
#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
+#include "nvim/autocmd_defs.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -26,17 +24,18 @@
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -48,11 +47,11 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
@@ -60,10 +59,10 @@
#include "nvim/terminal.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
typedef struct insert_state {
@@ -73,7 +72,7 @@ typedef struct insert_state {
int cmdchar;
int cmdchar_todo; // cmdchar to handle once in init_prompt
int startln;
- long count;
+ int count;
int c;
int lastc;
int i;
@@ -138,7 +137,7 @@ static void insert_enter(InsertState *s)
did_restart_edit = restart_edit;
// sleep before redrawing, needed for "CTRL-O :" that results in an
// error message
- check_for_delay(true);
+ msg_check_for_delay(true);
// set Insstart_orig to Insstart
update_Insstart_orig = true;
@@ -194,7 +193,7 @@ static void insert_enter(InsertState *s)
}
}
- Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
+ Insstart_textlen = linetabsize_str(get_cursor_line_ptr());
Insstart_blank_vcol = MAXCOL;
if (!did_ai) {
@@ -232,8 +231,9 @@ static void insert_enter(InsertState *s)
may_trigger_modechanged();
stop_insert_mode = false;
- // need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ // need to position cursor again when on a TAB and
+ // when on a char with inline virtual text
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
@@ -355,6 +355,7 @@ static void insert_enter(InsertState *s)
ins_apply_autocmds(EVENT_INSERTLEAVE);
}
did_cursorhold = false;
+ curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
}
static int insert_check(VimState *state)
@@ -394,6 +395,13 @@ static int insert_check(VimState *state)
Insstart_orig = Insstart;
}
+ if (curbuf->terminal) {
+ // Exit Insert mode and go to Terminal mode.
+ stop_insert_mode = true;
+ restart_edit = 'I';
+ stuffcharReadbuff(K_NOP);
+ }
+
if (stop_insert_mode && !ins_compl_active()) {
// ":stopinsert" used
s->count = 0;
@@ -523,10 +531,6 @@ static int insert_execute(VimState *state, int key)
did_cursorhold = true;
}
- if (p_hkmap && KeyTyped) {
- s->c = hkmap(s->c); // Hebrew mode mapping
- }
-
// Special handling of keys while the popup menu is visible or wanted
// and the cursor is still in the completed word. Only when there is
// a match, skip this when no matches were found.
@@ -558,10 +562,9 @@ static int insert_execute(VimState *state, int key)
if (ins_compl_accept_char(s->c)) {
// Trigger InsertCharPre.
char *str = do_insert_char_pre(s->c);
- char *p;
if (str != NULL) {
- for (p = str; *p != NUL; MB_PTR_ADV(p)) {
+ for (char *p = str; *p != NUL; MB_PTR_ADV(p)) {
ins_compl_addleader(utf_ptr2char(p));
}
xfree(str);
@@ -615,7 +618,9 @@ static int insert_execute(VimState *state, int key)
}
}
- s->c = do_digraph(s->c);
+ if (s->c != K_EVENT) {
+ s->c = do_digraph(s->c);
+ }
if ((s->c == Ctrl_V || s->c == Ctrl_Q) && ctrl_x_mode_cmdline()) {
insert_do_complete(s);
@@ -754,7 +759,7 @@ static int insert_handle_key(InsertState *s)
case Ctrl_A:
// For ^@ the trailing ESC will end the insert, unless there is an
// error.
- if (stuff_inserted(NUL, 1L, (s->c == Ctrl_A)) == FAIL
+ if (stuff_inserted(NUL, 1, (s->c == Ctrl_A)) == FAIL
&& s->c != Ctrl_A) {
return 0; // exit insert mode
}
@@ -880,14 +885,18 @@ static int insert_handle_key(InsertState *s)
case K_EVENT: // some event
state_handle_k_event();
+ // If CTRL-G U was used apply it to the next typed key.
+ if (dont_sync_undo == kTrue) {
+ dont_sync_undo = kNone;
+ }
goto check_pum;
- case K_COMMAND: // some command
+ case K_COMMAND: // <Cmd>command<CR>
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
goto check_pum;
case K_LUA:
- map_execute_lua();
+ map_execute_lua(false);
check_pum:
// nvim_select_popupmenu_item() can be called from the handling of
@@ -1122,12 +1131,11 @@ normalchar:
if (!p_paste) {
// Trigger InsertCharPre.
char *str = do_insert_char_pre(s->c);
- char *p;
if (str != NULL) {
if (*str != NUL && stop_arrow() != FAIL) {
// Insert the new value of v:char literally.
- for (p = str; *p != NUL; MB_PTR_ADV(p)) {
+ for (char *p = str; *p != NUL; MB_PTR_ADV(p)) {
s->c = utf_ptr2char(p);
if (s->c == CAR || s->c == K_KENTER || s->c == NL) {
ins_eol(s->c);
@@ -1229,7 +1237,7 @@ static void insert_do_cindent(InsertState *s)
/// @param count repeat count for the command
///
/// @return true if a CTRL-O command caused the return (insert mode pending).
-bool edit(int cmdchar, bool startln, long count)
+bool edit(int cmdchar, bool startln, int count)
{
if (curbuf->terminal) {
if (ex_normal_busy) {
@@ -1252,12 +1260,14 @@ bool edit(int cmdchar, bool startln, long count)
// Don't allow changes in the buffer while editing the cmdline. The
// caller of getcmdline() may get confused.
// Don't allow recursive insert mode when busy with completion.
- if (textlock != 0 || ins_compl_active() || compl_busy || pum_visible()) {
+ // Allow in dummy buffers since they are only used internally
+ if (textlock != 0 || ins_compl_active() || compl_busy || pum_visible()
+ || expr_map_locked()) {
emsg(_(e_textlock));
return false;
}
- InsertState state, *s = &state;
+ InsertState s[1];
memset(s, 0, sizeof(InsertState));
s->state.execute = insert_execute;
s->state.check = insert_check;
@@ -1289,7 +1299,8 @@ void ins_redraw(bool ready)
// Trigger CursorMoved if the cursor moved. Not when the popup menu is
// visible, the command might delete it.
if (ready && has_event(EVENT_CURSORMOVEDI)
- && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)
+ && (last_cursormoved_win != curwin
+ || !equalpos(last_cursormoved, curwin->w_cursor))
&& !pum_visible()) {
// Need to update the screen first, to make sure syntax
// highlighting is correct after making a change (e.g., inserting
@@ -1302,12 +1313,13 @@ void ins_redraw(bool ready)
// getcurpos()
update_curswant();
ins_apply_autocmds(EVENT_CURSORMOVEDI);
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
- // Trigger TextChangedI if changedtick differs.
+ // Trigger TextChangedI if changedtick_i differs.
if (ready && has_event(EVENT_TEXTCHANGEDI)
- && curbuf->b_last_changedtick != buf_get_changedtick(curbuf)
+ && curbuf->b_last_changedtick_i != buf_get_changedtick(curbuf)
&& !pum_visible()) {
aco_save_T aco;
varnumber_T tick = buf_get_changedtick(curbuf);
@@ -1316,16 +1328,16 @@ void ins_redraw(bool ready)
aucmd_prepbuf(&aco, curbuf);
apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, false, curbuf);
aucmd_restbuf(&aco);
- curbuf->b_last_changedtick = buf_get_changedtick(curbuf);
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
if (tick != buf_get_changedtick(curbuf)) { // see ins_apply_autocmds()
u_save(curwin->w_cursor.lnum,
(linenr_T)(curwin->w_cursor.lnum + 1));
}
}
- // Trigger TextChangedP if changedtick differs. When the popupmenu closes
- // TextChangedI will need to trigger for backwards compatibility, thus use
- // different b_last_changedtick* variables.
+ // Trigger TextChangedP if changedtick_pum differs. When the popupmenu
+ // closes TextChangedI will need to trigger for backwards compatibility,
+ // thus use different b_last_changedtick* variables.
if (ready && has_event(EVENT_TEXTCHANGEDP)
&& curbuf->b_last_changedtick_pum != buf_get_changedtick(curbuf)
&& pum_visible()) {
@@ -1355,13 +1367,21 @@ void ins_redraw(bool ready)
curbuf->b_changed_invalid = false;
}
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(ready
+ && !ins_compl_active()
+ && !pum_visible());
+
pum_check_clear();
+ show_cursor_info_later(false);
if (must_redraw) {
update_screen();
- } else if (clear_cmdline || redraw_cmdline) {
- showmode(); // clear cmdline and show mode
+ } else {
+ redraw_statuslines();
+ if (clear_cmdline || redraw_cmdline || redraw_mode) {
+ showmode(); // clear cmdline and show mode
+ }
}
- show_cursor_info(false);
setcursor();
emsg_on_display = false; // may remove error message now
}
@@ -1369,7 +1389,6 @@ void ins_redraw(bool ready)
// Handle a CTRL-V or CTRL-Q typed in Insert mode.
static void ins_ctrl_v(void)
{
- int c;
bool did_putchar = false;
// may need to redraw when no more chars available now
@@ -1384,7 +1403,7 @@ static void ins_ctrl_v(void)
add_to_showcmd_c(Ctrl_V);
// Do not include modifiers into the key for CTRL-SHIFT-V.
- c = get_literal(mod_mask & MOD_MASK_SHIFT);
+ int c = get_literal(mod_mask & MOD_MASK_SHIFT);
if (did_putchar) {
// when the line fits in 'columns' the '^' is at the start of the next
// line and will not removed by the redraw
@@ -1399,20 +1418,19 @@ static void ins_ctrl_v(void)
// Put a character directly onto the screen. It's not stored in a buffer.
// Used while handling CTRL-K, CTRL-V, etc. in Insert mode.
static int pc_status;
-#define PC_STATUS_UNSET 0 // pc_bytes was not set
-#define PC_STATUS_RIGHT 1 // right half of double-wide char
-#define PC_STATUS_LEFT 2 // left half of double-wide char
-#define PC_STATUS_SET 3 // pc_bytes was filled
-static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes
+#define PC_STATUS_UNSET 0 // nothing was put on screen
+#define PC_STATUS_RIGHT 1 // right half of double-wide char
+#define PC_STATUS_LEFT 2 // left half of double-wide char
+#define PC_STATUS_SET 3 // pc_schar was filled
+static schar_T pc_schar; // saved char
static int pc_attr;
static int pc_row;
static int pc_col;
void edit_putchar(int c, bool highlight)
{
- int attr;
-
if (curwin->w_grid_alloc.chars != NULL || default_grid.chars != NULL) {
+ int attr;
update_topline(curwin); // just in case w_topline isn't valid
validate_cursor();
if (highlight) {
@@ -1421,30 +1439,34 @@ void edit_putchar(int c, bool highlight)
attr = 0;
}
pc_row = curwin->w_wrow;
- pc_col = 0;
pc_status = PC_STATUS_UNSET;
+ grid_line_start(&curwin->w_grid, pc_row);
if (curwin->w_p_rl) {
- pc_col += curwin->w_grid.cols - 1 - curwin->w_wcol;
- const int fix_col = grid_fix_col(&curwin->w_grid, pc_col, pc_row);
+ pc_col = curwin->w_grid.cols - 1 - curwin->w_wcol;
- if (fix_col != pc_col) {
- grid_putchar(&curwin->w_grid, ' ', pc_row, fix_col, attr);
+ if (grid_line_getchar(pc_col, NULL) == NUL) {
+ grid_line_put_schar(pc_col - 1, schar_from_ascii(' '), attr);
curwin->w_wcol--;
pc_status = PC_STATUS_RIGHT;
}
} else {
- pc_col += curwin->w_wcol;
- if (grid_lefthalve(&curwin->w_grid, pc_row, pc_col)) {
+ pc_col = curwin->w_wcol;
+
+ if (grid_line_getchar(pc_col + 1, NULL) == NUL) {
+ // pc_col is the left half of a double-width char
pc_status = PC_STATUS_LEFT;
}
}
// save the character to be able to put it back
if (pc_status == PC_STATUS_UNSET) {
- grid_getbytes(&curwin->w_grid, pc_row, pc_col, (char *)pc_bytes, &pc_attr);
+ pc_schar = grid_line_getchar(pc_col, &pc_attr);
pc_status = PC_STATUS_SET;
}
- grid_putchar(&curwin->w_grid, c, pc_row, pc_col, attr);
+
+ char buf[MB_MAXCHAR + 1];
+ grid_line_puts(pc_col, buf, utf_char2bytes(c, buf), attr);
+ grid_line_flush();
}
}
@@ -1470,10 +1492,9 @@ char *prompt_text(void)
static void init_prompt(int cmdchar_todo)
{
char *prompt = prompt_text();
- char *text;
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- text = get_cursor_line_ptr();
+ char *text = get_cursor_line_ptr();
if (strncmp(text, prompt, strlen(prompt)) != 0) {
// prompt is missing, insert it or append a line with it
if (*text == NUL) {
@@ -1517,29 +1538,32 @@ bool prompt_curpos_editable(void)
// Undo the previous edit_putchar().
void edit_unputchar(void)
{
- if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled) {
+ if (pc_status != PC_STATUS_UNSET) {
if (pc_status == PC_STATUS_RIGHT) {
curwin->w_wcol++;
}
if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) {
redrawWinline(curwin, curwin->w_cursor.lnum);
} else {
- grid_puts(&curwin->w_grid, (char *)pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr);
+ // TODO(bfredl): this could be smarter and also handle the dubyawidth case
+ grid_line_start(&curwin->w_grid, pc_row);
+ grid_line_put_schar(pc_col, pc_schar, pc_attr);
+ grid_line_flush();
}
}
}
-// Called when p_dollar is set: display a '$' at the end of the changed text
-// Only works when cursor is in the line that changes.
-void display_dollar(colnr_T col)
+/// Called when "$" is in 'cpoptions': display a '$' at the end of the changed
+/// text. Only works when cursor is in the line that changes.
+void display_dollar(colnr_T col_arg)
{
- colnr_T save_col;
+ colnr_T col = col_arg < 0 ? 0 : col_arg;
if (!redrawing()) {
return;
}
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
curwin->w_cursor.col = col;
// If on the last byte of a multi-byte move to the first byte.
@@ -1574,16 +1598,9 @@ void undisplay_dollar(void)
/// @param call_changed_bytes call changed_bytes()
void change_indent(int type, int amount, int round, int replaced, int call_changed_bytes)
{
- int vcol;
- int last_vcol;
int insstart_less; // reduction for Insstart.col
- int new_cursor_col;
- char *ptr;
- int save_p_list;
- int start_col;
- colnr_T vc;
colnr_T orig_col = 0; // init for GCC
- char *new_line, *orig_line = NULL; // init for GCC
+ char *orig_line = NULL; // init for GCC
// MODE_VREPLACE state needs to know what the line was like before changing
if (State & VREPLACE_FLAG) {
@@ -1592,18 +1609,18 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
}
// for the following tricks we don't want list mode
- save_p_list = curwin->w_p_list;
+ int save_p_list = curwin->w_p_list;
curwin->w_p_list = false;
- vc = getvcol_nolist(&curwin->w_cursor);
- vcol = vc;
+ colnr_T vc = getvcol_nolist(&curwin->w_cursor);
+ int vcol = vc;
// For Replace mode we need to fix the replace stack later, which is only
// possible when the cursor is in the indent. Remember the number of
// characters before the cursor if it's possible.
- start_col = curwin->w_cursor.col;
+ int start_col = curwin->w_cursor.col;
// determine offset from first non-blank
- new_cursor_col = curwin->w_cursor.col;
+ int new_cursor_col = curwin->w_cursor.col;
beginline(BL_WHITE);
new_cursor_col -= curwin->w_cursor.col;
@@ -1656,8 +1673,8 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol);
// Advance the cursor until we reach the right screen column.
- last_vcol = 0;
- ptr = get_cursor_line_ptr();
+ int last_vcol = 0;
+ char *ptr = get_cursor_line_ptr();
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, 0, 0, ptr, ptr);
while (cts.cts_vcol <= (int)curwin->w_virtcol) {
@@ -1699,7 +1716,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
curwin->w_cursor.col = (colnr_T)new_cursor_col;
}
curwin->w_set_curswant = true;
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
// May have to adjust the start of the insert.
if (State & MODE_INSERT) {
@@ -1742,7 +1759,7 @@ void change_indent(int type, int amount, int round, int replaced, int call_chang
// then put it back again the way we wanted it.
if (State & VREPLACE_FLAG) {
// Save new line
- new_line = xstrdup(get_cursor_line_ptr());
+ char *new_line = xstrdup(get_cursor_line_ptr());
// We only put back the new line up to the cursor
new_line[curwin->w_cursor.col] = NUL;
@@ -1847,9 +1864,7 @@ static bool del_char_after_col(int limit_col)
/// @param no_simplify do not include modifiers into the key
int get_literal(bool no_simplify)
{
- int cc;
int nc;
- int i;
bool hex = false;
bool octal = false;
int unicode = 0;
@@ -1859,9 +1874,9 @@ int get_literal(bool no_simplify)
}
no_mapping++; // don't map the next key hits
- cc = 0;
- i = 0;
- for (;;) {
+ int cc = 0;
+ int i = 0;
+ while (true) {
nc = plain_vgetc();
if (!no_simplify) {
nc = merge_modifiers(nc, &mod_mask);
@@ -1949,9 +1964,6 @@ int get_literal(bool no_simplify)
/// @param ctrlv `c` was typed after CTRL-V
static void insert_special(int c, int allow_modmask, int ctrlv)
{
- char *p;
- int len;
-
// Special function key, translate into "<Key>". Up to the last '>' is
// inserted with ins_str(), so as not to replace characters in replace
// mode.
@@ -1961,8 +1973,8 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
allow_modmask = true;
}
if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) {
- p = (char *)get_special_key_name(c, mod_mask);
- len = (int)strlen(p);
+ char *p = get_special_key_name(c, mod_mask);
+ int len = (int)strlen(p);
c = (uint8_t)p[len - 1];
if (len > 2) {
if (stop_arrow() == FAIL) {
@@ -2041,7 +2053,7 @@ void insertchar(int c, int flags, int second_indent)
if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0
&& (force_format || virtcol > (colnr_T)textwidth)) {
- do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0);
+ do_internal = (fex_format(curwin->w_cursor.lnum, 1, c) != 0);
// It may be required to save for undo again, e.g. when setline()
// was called.
ins_need_undo = true;
@@ -2057,7 +2069,7 @@ void insertchar(int c, int flags, int second_indent)
// Check whether this character should end a comment.
if (did_ai && c == end_comment_pending) {
- char_u lead_end[COM_MAX_LEN]; // end-comment string
+ char lead_end[COM_MAX_LEN]; // end-comment string
// Need to remove existing (middle) comment leader and insert end
// comment leader. First, check what comment leader we can find.
@@ -2068,7 +2080,7 @@ void insertchar(int c, int flags, int second_indent)
while (*p && p[-1] != ':') { // find end of middle flags
p++;
}
- int middle_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int middle_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
// Don't count trailing white space for middle_len
while (middle_len > 0 && ascii_iswhite(lead_end[middle_len - 1])) {
middle_len--;
@@ -2078,7 +2090,7 @@ void insertchar(int c, int flags, int second_indent)
while (*p && p[-1] != ':') { // find end of end flags
p++;
}
- int end_len = (int)copy_option_part(&p, (char *)lead_end, COM_MAX_LEN, ",");
+ int end_len = (int)copy_option_part(&p, lead_end, COM_MAX_LEN, ",");
// Skip white space before the cursor
i = curwin->w_cursor.col;
@@ -2089,13 +2101,13 @@ void insertchar(int c, int flags, int second_indent)
i -= middle_len;
// Check some expected things before we go on
- if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) {
+ if (i >= 0 && (uint8_t)lead_end[end_len - 1] == end_comment_pending) {
// Backspace over all the stuff we want to replace
backspace_until_column(i);
// Insert the end-comment string, except for the last
// character, which will get inserted as normal later.
- ins_bytes_len((char *)lead_end, (size_t)(end_len - 1));
+ ins_bytes_len(lead_end, (size_t)(end_len - 1));
}
}
}
@@ -2146,9 +2158,6 @@ void insertchar(int c, int flags, int second_indent)
|| (virtcol += byte2cells((uint8_t)buf[i - 1])) < (colnr_T)textwidth)
&& !(!no_abbr && !vim_iswordc(c) && vim_iswordc((uint8_t)buf[i - 1]))) {
c = vgetc();
- if (p_hkmap && KeyTyped) {
- c = hkmap(c); // Hebrew mode mapping
- }
buf[i++] = (char)c;
}
@@ -2169,7 +2178,7 @@ void insertchar(int c, int flags, int second_indent)
int cc;
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
utf_char2bytes(c, buf);
buf[cc] = NUL;
@@ -2260,7 +2269,7 @@ int stop_arrow(void)
// right, except when nothing was inserted yet.
update_Insstart_orig = false;
}
- Insstart_textlen = (colnr_T)linetabsize(get_cursor_line_ptr());
+ Insstart_textlen = linetabsize_str(get_cursor_line_ptr());
if (u_save_cursor() == OK) {
arrow_used = false;
@@ -2294,26 +2303,24 @@ int stop_arrow(void)
/// @param nomove <c-\><c-o>, don't move cursor
static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
{
- int cc;
- char *ptr;
-
stop_redo_ins();
replace_flush(); // abandon replace stack
// Save the inserted text for later redo with ^@ and CTRL-A.
// Don't do it when "restart_edit" was set and nothing was inserted,
// otherwise CTRL-O w and then <Left> will clear "last_insert".
- ptr = get_inserted();
- if (did_restart_edit == 0 || (ptr != NULL
- && (int)strlen(ptr) > new_insert_skip)) {
+ char *ptr = get_inserted();
+ int added = ptr == NULL ? 0 : (int)strlen(ptr) - new_insert_skip;
+ if (did_restart_edit == 0 || added > 0) {
xfree(last_insert);
last_insert = ptr;
- last_insert_skip = new_insert_skip;
+ last_insert_skip = added < 0 ? 0 : new_insert_skip;
} else {
xfree(ptr);
}
if (!arrow_used && end_insert_pos != NULL) {
+ int cc;
// Auto-format now. It may seem strange to do this when stopping an
// insertion (or moving the cursor), but it's required when appending
// a line and having it end in a space. But only do it when something
@@ -2365,7 +2372,7 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
curwin->w_cursor = *end_insert_pos;
check_cursor_col(); // make sure it is not past the line
- for (;;) {
+ while (true) {
if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) {
curwin->w_cursor.col--;
}
@@ -2413,16 +2420,14 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
// Used for the replace command.
void set_last_insert(int c)
{
- char *s;
-
xfree(last_insert);
last_insert = xmalloc(MB_MAXBYTES * 3 + 5);
- s = last_insert;
+ char *s = last_insert;
// Use the CTRL-V only when entering a special char
if (c < ' ' || c == DEL) {
*s++ = Ctrl_V;
}
- s = (char *)add_char2buf(c, (char_u *)s);
+ s = add_char2buf(c, s);
*s++ = ESC;
*s++ = NUL;
last_insert_skip = 0;
@@ -2449,15 +2454,16 @@ void beginline(int flags)
curwin->w_cursor.coladd = 0;
if (flags & (BL_WHITE | BL_SOL)) {
- char_u *ptr;
+ char *ptr;
- for (ptr = (char_u *)get_cursor_line_ptr(); ascii_iswhite(*ptr)
+ for (ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr)
&& !((flags & BL_FIX) && ptr[1] == NUL); ptr++) {
curwin->w_cursor.col++;
}
}
curwin->w_set_curswant = true;
}
+ adjust_skipcol();
}
// oneright oneleft cursor_down cursor_up
@@ -2469,15 +2475,14 @@ void beginline(int flags)
int oneright(void)
{
char *ptr;
- int l;
if (virtual_active()) {
pos_T prevpos = curwin->w_cursor;
// Adjust for multi-wide char (excluding TAB)
ptr = get_cursor_pos_ptr();
- coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr))) ?
- ptr2cells(ptr) : 1));
+ coladvance(getviscol() + ((*ptr != TAB && vim_isprintc(utf_ptr2char(ptr)))
+ ? ptr2cells(ptr) : 1));
curwin->w_set_curswant = true;
// Return OK if the cursor moved, FAIL otherwise (at window edge).
return (prevpos.col != curwin->w_cursor.col
@@ -2489,7 +2494,7 @@ int oneright(void)
return FAIL; // already at the very end
}
- l = utfc_ptr2len(ptr);
+ int l = utfc_ptr2len(ptr);
// move "l" bytes right, but don't end up on the NUL, unless 'virtualedit'
// contains "onemore".
@@ -2499,13 +2504,13 @@ int oneright(void)
curwin->w_cursor.col += l;
curwin->w_set_curswant = true;
+ adjust_skipcol();
return OK;
}
int oneleft(void)
{
if (virtual_active()) {
- int width;
int v = getviscol();
if (v == 0) {
@@ -2513,8 +2518,8 @@ int oneleft(void)
}
// We might get stuck on 'showbreak', skip over it.
- width = 1;
- for (;;) {
+ int width = 1;
+ while (true) {
coladvance(v - width);
// getviscol() is slow, skip it when 'showbreak' is empty,
// 'breakindent' is not set and there are no multi-byte
@@ -2534,6 +2539,7 @@ int oneleft(void)
}
curwin->w_set_curswant = true;
+ adjust_skipcol();
return OK;
}
@@ -2547,20 +2553,16 @@ int oneleft(void)
// if the character on the left of the current cursor is a multi-byte
// character, move to its first byte
mb_adjust_cursor();
+ adjust_skipcol();
return OK;
}
/// Move the cursor up "n" lines in window "wp".
/// Takes care of closed folds.
-/// Returns the new cursor line or zero for failure.
-linenr_T cursor_up_inner(win_T *wp, long n)
+void cursor_up_inner(win_T *wp, linenr_T n)
{
linenr_T lnum = wp->w_cursor.lnum;
- // This fails if the cursor is already in the first line.
- if (lnum <= 1) {
- return 0;
- }
if (n >= lnum) {
lnum = 1;
} else if (hasAnyFolding(wp)) {
@@ -2586,19 +2588,20 @@ linenr_T cursor_up_inner(win_T *wp, long n)
lnum = 1;
}
} else {
- lnum -= (linenr_T)n;
+ lnum -= n;
}
wp->w_cursor.lnum = lnum;
- return lnum;
}
/// @param upd_topline When true: update topline
-int cursor_up(long n, int upd_topline)
+int cursor_up(linenr_T n, int upd_topline)
{
- if (n > 0 && cursor_up_inner(curwin, n) == 0) {
+ // This fails if the cursor is already in the first line.
+ if (n > 0 && curwin->w_cursor.lnum <= 1) {
return FAIL;
}
+ cursor_up_inner(curwin, n);
// try to advance to the column we want to be at
coladvance(curwin->w_curswant);
@@ -2612,18 +2615,11 @@ int cursor_up(long n, int upd_topline)
/// Move the cursor down "n" lines in window "wp".
/// Takes care of closed folds.
-/// Returns the new cursor line or zero for failure.
-linenr_T cursor_down_inner(win_T *wp, long n)
+void cursor_down_inner(win_T *wp, int n)
{
linenr_T lnum = wp->w_cursor.lnum;
linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
- // Move to last line of fold, will fail if it's the end-of-file.
- (void)hasFoldingWin(wp, lnum, NULL, &lnum, true, NULL);
- // This fails if the cursor is already in the last line.
- if (lnum >= line_count) {
- return FAIL;
- }
if (lnum + n >= line_count) {
lnum = line_count;
} else if (hasAnyFolding(wp)) {
@@ -2631,6 +2627,7 @@ linenr_T cursor_down_inner(win_T *wp, long n)
// count each sequence of folded lines as one logical line
while (n--) {
+ // Move to last line of fold, will fail if it's the end-of-file.
if (hasFoldingWin(wp, lnum, NULL, &last, true, NULL)) {
lnum = last + 1;
} else {
@@ -2648,15 +2645,16 @@ linenr_T cursor_down_inner(win_T *wp, long n)
}
wp->w_cursor.lnum = lnum;
- return lnum;
}
/// @param upd_topline When true: update topline
-int cursor_down(long n, int upd_topline)
+int cursor_down(int n, int upd_topline)
{
- if (n > 0 && cursor_down_inner(curwin, n) == 0) {
+ // This fails if the cursor is already in the last line.
+ if (n > 0 && curwin->w_cursor.lnum >= curwin->w_buffer->b_ml.ml_line_count) {
return FAIL;
}
+ cursor_down_inner(curwin, n);
// try to advance to the column we want to be at
coladvance(curwin->w_curswant);
@@ -2675,14 +2673,12 @@ int cursor_down(long n, int upd_topline)
/// @param c Command character to be inserted
/// @param count Repeat this many times
/// @param no_esc Don't add an ESC at the end
-int stuff_inserted(int c, long count, int no_esc)
+int stuff_inserted(int c, int count, int no_esc)
{
char *esc_ptr;
- char *ptr;
- char *last_ptr;
char last = NUL;
- ptr = (char *)get_last_insert();
+ char *ptr = get_last_insert();
if (ptr == NULL) {
emsg(_(e_noinstext));
return FAIL;
@@ -2700,7 +2696,7 @@ int stuff_inserted(int c, long count, int no_esc)
// when the last char is either "0" or "^" it will be quoted if no ESC
// comes after it OR if it will inserted more than once and "ptr"
// starts with ^D. -- Acevedo
- last_ptr = (esc_ptr ? esc_ptr : ptr + strlen(ptr)) - 1;
+ char *last_ptr = (esc_ptr ? esc_ptr : ptr + strlen(ptr)) - 1;
if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^')
&& (no_esc || (*ptr == Ctrl_D && count > 1))) {
last = *last_ptr;
@@ -2708,7 +2704,7 @@ int stuff_inserted(int c, long count, int no_esc)
}
do {
- stuffReadbuff((const char *)ptr);
+ stuffReadbuff(ptr);
// A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^".
if (last) {
stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^");
@@ -2731,27 +2727,24 @@ int stuff_inserted(int c, long count, int no_esc)
return OK;
}
-char_u *get_last_insert(void)
+char *get_last_insert(void)
FUNC_ATTR_PURE
{
if (last_insert == NULL) {
return NULL;
}
- return (char_u *)last_insert + last_insert_skip;
+ return last_insert + last_insert_skip;
}
// Get last inserted string, and remove trailing <Esc>.
// Returns pointer to allocated memory (must be freed) or NULL.
char *get_last_insert_save(void)
{
- char *s;
- int len;
-
if (last_insert == NULL) {
return NULL;
}
- s = xstrdup(last_insert + last_insert_skip);
- len = (int)strlen(s);
+ char *s = xstrdup(last_insert + last_insert_skip);
+ int len = (int)strlen(s);
if (len > 0 && s[len - 1] == ESC) { // remove trailing ESC
s[len - 1] = NUL;
}
@@ -2793,11 +2786,11 @@ static bool echeck_abbr(int c)
// that the NL replaced. The extra one stores the characters after the cursor
// that were deleted (always white space).
-static char_u *replace_stack = NULL;
+static uint8_t *replace_stack = NULL;
static ssize_t replace_stack_nr = 0; // next entry in replace stack
static ssize_t replace_stack_len = 0; // max. number of entries
-/// Push character that is replaced onto the the replace stack.
+/// Push character that is replaced onto the replace stack.
///
/// replace_offset is normally 0, in which case replace_push will add a new
/// character at the end of the stack. If replace_offset is not 0, that many
@@ -2814,11 +2807,11 @@ void replace_push(int c)
replace_stack_len += 50;
replace_stack = xrealloc(replace_stack, (size_t)replace_stack_len);
}
- char_u *p = replace_stack + replace_stack_nr - replace_offset;
+ uint8_t *p = replace_stack + replace_stack_nr - replace_offset;
if (replace_offset) {
memmove(p + 1, p, (size_t)replace_offset);
}
- *p = (char_u)c;
+ *p = (uint8_t)c;
replace_stack_nr++;
}
@@ -2829,9 +2822,8 @@ void replace_push(int c)
int replace_push_mb(char *p)
{
int l = utfc_ptr2len(p);
- int j;
- for (j = l - 1; j >= 0; j--) {
+ for (int j = l - 1; j >= 0; j--) {
replace_push(p[j]);
}
return l;
@@ -2881,14 +2873,12 @@ static void replace_pop_ins(void)
static void mb_replace_pop_ins(int cc)
{
int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
- int c;
+ uint8_t buf[MB_MAXBYTES + 1];
if ((n = MB_BYTE2LEN(cc)) > 1) {
- buf[0] = (char_u)cc;
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)replace_pop();
+ buf[0] = (uint8_t)cc;
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)replace_pop();
}
ins_bytes_len((char *)buf, (size_t)n);
} else {
@@ -2896,8 +2886,8 @@ static void mb_replace_pop_ins(int cc)
}
// Handle composing chars.
- for (;;) {
- c = replace_pop();
+ while (true) {
+ int c = replace_pop();
if (c == -1) { // stack empty
break;
}
@@ -2907,16 +2897,16 @@ static void mb_replace_pop_ins(int cc)
break;
}
- buf[0] = (char_u)c;
+ buf[0] = (uint8_t)c;
assert(n > 1);
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)replace_pop();
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)replace_pop();
}
if (utf_iscomposing(utf_ptr2char((char *)buf))) {
ins_bytes_len((char *)buf, (size_t)n);
} else {
// Not a composing char, put it back.
- for (i = n - 1; i >= 0; i--) {
+ for (int i = n - 1; i >= 0; i--) {
replace_push(buf[i]);
}
break;
@@ -2942,18 +2932,13 @@ static void replace_flush(void)
// using composing characters, use del_char_after_col() instead of del_char().
static void replace_do_bs(int limit_col)
{
- int cc;
- int orig_len = 0;
- int ins_len;
- int orig_vcols = 0;
colnr_T start_vcol;
- char *p;
- int i;
- int vcol;
const int l_State = State;
- cc = replace_pop();
+ int cc = replace_pop();
if (cc > 0) {
+ int orig_len = 0;
+ int orig_vcols = 0;
if (l_State & VREPLACE_FLAG) {
// Get the number of screen cells used by the character we are
// going to delete.
@@ -2969,10 +2954,10 @@ static void replace_do_bs(int limit_col)
if (l_State & VREPLACE_FLAG) {
// Get the number of screen cells used by the inserted characters
- p = get_cursor_pos_ptr();
- ins_len = (int)strlen(p) - orig_len;
- vcol = start_vcol;
- for (i = 0; i < ins_len; i++) {
+ char *p = get_cursor_pos_ptr();
+ int ins_len = (int)strlen(p) - orig_len;
+ int vcol = start_vcol;
+ for (int i = 0; i < ins_len; i++) {
vcol += win_chartabsize(curwin, p + i, vcol);
i += utfc_ptr2len(p) - 1;
}
@@ -3131,7 +3116,7 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return true;
}
- if (keytyped == get_special_key_code((char_u *)look + 1)) {
+ if (keytyped == get_special_key_code(look + 1)) {
return true;
}
}
@@ -3223,105 +3208,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty)
return false;
}
-// Map Hebrew keyboard when in hkmap mode.
-int hkmap(int c)
- FUNC_ATTR_PURE
-{
- if (p_hkmapp) { // phonetic mapping, by Ilya Dogolazky
- enum {
- hALEF = 0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD,
- KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN,
- PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV,
- };
- static char_u map[26] = {
- (char_u)hALEF, // a
- (char_u)BET, // b
- (char_u)hKAF, // c
- (char_u)DALET, // d
- (char_u) - 1, // e
- (char_u)PEIsofit, // f
- (char_u)GIMEL, // g
- (char_u)HEI, // h
- (char_u)IUD, // i
- (char_u)HET, // j
- (char_u)KOF, // k
- (char_u)LAMED, // l
- (char_u)MEM, // m
- (char_u)NUN, // n
- (char_u)SAMEH, // o
- (char_u)PEI, // p
- (char_u) - 1, // q
- (char_u)RESH, // r
- (char_u)ZAIN, // s
- (char_u)TAV, // t
- (char_u)TET, // u
- (char_u)VAV, // v
- (char_u)hSHIN, // w
- (char_u) - 1, // x
- (char_u)AIN, // y
- (char_u)ZADI, // z
- };
-
- if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') {
- return (int)(map[CHAR_ORD(c)] - 1 + p_aleph);
- } else if (c == 'x') { // '-1'='sofit'
- return 'X';
- } else if (c == 'q') {
- return '\''; // {geresh}={'}
- } else if (c == 246) {
- return ' '; // \"o --> ' ' for a german keyboard
- } else if (c == 228) {
- return ' '; // \"a --> ' ' -- / --
- } else if (c == 252) {
- return ' '; // \"u --> ' ' -- / --
- } else if (c >= 'a' && c <= 'z') {
- // NOTE: islower() does not do the right thing for us on Linux so we
- // do this the same was as 5.7 and previous, so it works correctly on
- // all systems. Specifically, the e.g. Delete and Arrow keys are
- // munged and won't work if e.g. searching for Hebrew text.
- return (int)(map[CHAR_ORD_LOW(c)] + p_aleph);
- } else {
- return c;
- }
- } else {
- switch (c) {
- case '`':
- return ';';
- case '/':
- return '.';
- case '\'':
- return ',';
- case 'q':
- return '/';
- case 'w':
- return '\'';
-
- // Hebrew letters - set offset from 'a'
- case ',':
- c = '{'; break;
- case '.':
- c = 'v'; break;
- case ';':
- c = 't'; break;
- default: {
- static char_u str[] = "zqbcxlsjphmkwonu ydafe rig";
-
- if (c < 'a' || c > 'z') {
- return c;
- }
- c = str[CHAR_ORD_LOW(c)];
- break;
- }
- }
-
- return (int)(CHAR_ORD_LOW(c) + p_aleph);
- }
-}
-
static void ins_reg(void)
{
bool need_redraw = false;
- int regname;
int literally = 0;
int vis_active = VIsual_active;
@@ -3339,7 +3228,7 @@ static void ins_reg(void)
// deleted when ESC is hit.
no_mapping++;
allow_keys++;
- regname = plain_vgetc();
+ int regname = plain_vgetc();
LANGMAP_ADJUST(regname, true);
if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) {
// Get a third key for literal register insertion
@@ -3410,8 +3299,6 @@ static void ins_reg(void)
// CTRL-G commands in Insert mode.
static void ins_ctrl_g(void)
{
- int c;
-
// Right after CTRL-X the cursor will be after the ruler.
setcursor();
@@ -3419,7 +3306,7 @@ static void ins_ctrl_g(void)
// deleted when ESC is hit.
no_mapping++;
allow_keys++;
- c = plain_vgetc();
+ int c = plain_vgetc();
no_mapping--;
allow_keys--;
switch (c) {
@@ -3455,6 +3342,10 @@ static void ins_ctrl_g(void)
dont_sync_undo = kNone;
break;
+ case ESC:
+ // Esc after CTRL-G cancels it.
+ break;
+
// Unknown CTRL-G command, reserved for future expansion.
default:
vim_beep(BO_CTRLG);
@@ -3487,7 +3378,7 @@ static void ins_ctrl_hat(void)
/// @param nomove when true, don't move the cursor
///
/// @return true when leaving insert mode, false when repeating the insert.
-static bool ins_esc(long *count, int cmdchar, bool nomove)
+static bool ins_esc(int *count, int cmdchar, bool nomove)
FUNC_ATTR_NONNULL_ARG(1)
{
static bool disabled_redraw = false;
@@ -3562,6 +3453,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
}
} else {
curwin->w_cursor.col--;
+ curwin->w_valid &= ~(VALID_WCOL|VALID_VIRTCOL);
// Correct cursor for multi-byte character.
mb_adjust_cursor();
}
@@ -3569,8 +3461,9 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
State = MODE_NORMAL;
may_trigger_modechanged();
- // need to position cursor again when on a TAB
- if (gchar_cursor() == TAB) {
+ // need to position cursor again when on a TAB and
+ // when on a char with inline virtual text
+ if (gchar_cursor() == TAB || curbuf->b_virt_text_inline > 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL);
}
@@ -3581,14 +3474,15 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
// Otherwise remove the mode message.
if (reg_recording != 0 || restart_edit != NUL) {
showmode();
- } else if (p_smd && (got_int || !skip_showmode())) {
- msg("");
+ } else if (p_smd && (got_int || !skip_showmode())
+ && !(p_ch == 0 && !ui_has(kUIMessages))) {
+ msg("", 0);
}
// Exit Insert mode
return true;
}
-// Toggle language: hkmap and revins_on.
+// Toggle language: revins_on.
// Move to end of reverse inserted text.
static void ins_ctrl_(void)
{
@@ -3607,7 +3501,6 @@ static void ins_ctrl_(void)
} else {
revins_scol = -1;
}
- p_hkmap = curwin->w_p_rl ^ p_ri; // be consistent!
showmode();
}
@@ -3659,8 +3552,9 @@ static bool ins_start_select(int c)
// <Insert> key in Insert mode: toggle insert/replace mode.
static void ins_insert(int replaceState)
{
- set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG) ? "i" :
- replaceState == MODE_VREPLACE ? "v" : "r"), 1);
+ set_vim_var_string(VV_INSERTMODE, ((State & REPLACE_FLAG)
+ ? "i"
+ : replaceState == MODE_VREPLACE ? "v" : "r"), 1);
ins_apply_autocmds(EVENT_INSERTCHANGE);
if (State & REPLACE_FLAG) {
State = MODE_INSERT | (State & MODE_LANGMAP);
@@ -3676,6 +3570,7 @@ static void ins_insert(int replaceState)
// Pressed CTRL-O in Insert mode.
static void ins_ctrl_o(void)
{
+ restart_VIsual_select = 0;
if (State & VREPLACE_FLAG) {
restart_edit = 'V';
} else if (State & REPLACE_FLAG) {
@@ -3786,15 +3681,9 @@ static void ins_bs_one(colnr_T *vcolp)
static bool ins_bs(int c, int mode, int *inserted_space_p)
FUNC_ATTR_NONNULL_ARG(3)
{
- linenr_T lnum;
int cc;
int temp = 0; // init for GCC
- colnr_T save_col;
- colnr_T mincol;
bool did_backspace = false;
- int in_indent;
- int oldState;
- int cpc[MAX_MCO]; // composing characters
bool call_fix_indent = false;
// can't delete anything in an empty file
@@ -3818,7 +3707,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (stop_arrow() == FAIL) {
return false;
}
- in_indent = inindent(0);
+ int in_indent = inindent(0);
if (in_indent) {
can_cindent = false;
}
@@ -3844,7 +3733,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// Delete newline!
if (curwin->w_cursor.col == 0) {
- lnum = Insstart.lnum;
+ linenr_T lnum = Insstart.lnum;
if (curwin->w_cursor.lnum == lnum || revins_on) {
if (u_save((linenr_T)(curwin->w_cursor.lnum - 2),
(linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL) {
@@ -3875,7 +3764,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// again when auto-formatting.
if (has_format_option(FO_AUTO)
&& has_format_option(FO_WHITE_PAR)) {
- char *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, true);
+ char *ptr = ml_get_buf_mut(curbuf, curwin->w_cursor.lnum);
int len;
len = (int)strlen(ptr);
@@ -3900,11 +3789,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// Do the next ins_char() in MODE_NORMAL state, to
// prevent ins_char() from replacing characters and
// avoiding showmatch().
- oldState = State;
+ int oldState = State;
State = MODE_NORMAL;
// restore characters (blanks) deleted after cursor
while (cc > 0) {
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
mb_replace_pop_ins(cc);
curwin->w_cursor.col = save_col;
cc = replace_pop();
@@ -3920,12 +3809,12 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (revins_on) { // put cursor on last inserted char
dec_cursor();
}
- mincol = 0;
+ colnr_T mincol = 0;
// keep indent
if (mode == BACKSPACE_LINE
&& (curbuf->b_p_ai || cindent_on())
&& !revins_on) {
- save_col = curwin->w_cursor.col;
+ colnr_T save_col = curwin->w_cursor.col;
beginline(BL_WHITE);
if (curwin->w_cursor.col < save_col) {
mincol = curwin->w_cursor.col;
@@ -3944,22 +3833,20 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
&& (*(get_cursor_pos_ptr() - 1) == TAB
|| (*(get_cursor_pos_ptr() - 1) == ' '
&& (!*inserted_space_p || arrow_used)))))) {
- int ts;
colnr_T vcol;
colnr_T want_vcol;
- colnr_T start_vcol;
*inserted_space_p = false;
// Compute the virtual column where we want to be. Since
// 'showbreak' may get in the way, need to get the last column of
// the previous character.
getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL);
- start_vcol = vcol;
+ colnr_T start_vcol = vcol;
dec_cursor();
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol);
inc_cursor();
if (p_sta && in_indent) {
- ts = get_sw_value(curbuf);
+ int ts = get_sw_value(curbuf);
want_vcol = (want_vcol / ts) * ts;
} else {
want_vcol = tabstop_start(want_vcol,
@@ -3999,7 +3886,6 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
}
} else {
// Delete up to starting point, start of line or previous word.
- int prev_cclass = 0;
int cclass = mb_get_class(get_cursor_pos_ptr());
do {
@@ -4008,7 +3894,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
}
cc = gchar_cursor();
// look multi-byte character class
- prev_cclass = cclass;
+ int prev_cclass = cclass;
cclass = mb_get_class(get_cursor_pos_ptr());
if (mode == BACKSPACE_WORD && !ascii_isspace(cc)) { // start of word?
mode = BACKSPACE_WORD_NOT_SPACE;
@@ -4026,15 +3912,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
if (State & REPLACE_FLAG) {
replace_do_bs(-1);
} else {
- const int l_p_deco = p_deco;
- if (l_p_deco) {
- (void)utfc_ptr2char(get_cursor_pos_ptr(), cpc);
+ bool has_composing = false;
+ if (p_deco) {
+ char *p0 = get_cursor_pos_ptr();
+ has_composing = utf_composinglike(p0, p0 + utf_ptr2len(p0));
}
(void)del_char(false);
// If there are combining characters and 'delcombine' is set
- // move the cursor back. Don't back up before the base
- // character.
- if (l_p_deco && cpc[0] != NUL) {
+ // move the cursor back. Don't back up before the base character.
+ if (has_composing) {
inc_cursor();
}
if (revins_chars) {
@@ -4099,92 +3985,15 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
return did_backspace;
}
-static void ins_mouse(int c)
-{
- pos_T tpos;
- win_T *old_curwin = curwin;
-
- undisplay_dollar();
- tpos = curwin->w_cursor;
- if (do_mouse(NULL, c, BACKWARD, 1, 0)) {
- win_T *new_curwin = curwin;
-
- if (curwin != old_curwin && win_valid(old_curwin)) {
- // Mouse took us to another window. We need to go back to the
- // previous one to stop insert there properly.
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
- if (bt_prompt(curbuf)) {
- // Restart Insert mode when re-entering the prompt buffer.
- curbuf->b_prompt_insert = 'A';
- }
- }
- start_arrow(curwin == old_curwin ? &tpos : NULL);
- if (curwin != new_curwin && win_valid(new_curwin)) {
- curwin = new_curwin;
- curbuf = curwin->w_buffer;
- }
- can_cindent = true;
- }
-
- // redraw status lines (in case another window became active)
- redraw_statuslines();
-}
-
-static void ins_mousescroll(int dir)
-{
- win_T *const old_curwin = curwin;
- pos_T tpos = curwin->w_cursor;
-
- if (mouse_row >= 0 && mouse_col >= 0) {
- int row = mouse_row, col = mouse_col, grid = mouse_grid;
-
- // find the window at the pointer coordinates
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp == NULL) {
- return;
- }
- curwin = wp;
- curbuf = curwin->w_buffer;
- }
- if (curwin == old_curwin) {
- undisplay_dollar();
- }
-
- // Don't scroll the window in which completion is being done.
- if (!pum_visible() || curwin != old_curwin) {
- if (dir == MSCR_DOWN || dir == MSCR_UP) {
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline));
- } else if (p_mousescroll_vert > 0) {
- scroll_redraw(dir, p_mousescroll_vert);
- }
- } else {
- mouse_scroll_horiz(dir);
- }
- }
-
- curwin->w_redr_status = true;
-
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
-
- if (!equalpos(curwin->w_cursor, tpos)) {
- start_arrow(&tpos);
- can_cindent = true;
- }
-}
-
static void ins_left(void)
{
- pos_T tpos;
const bool end_change = dont_sync_undo == kFalse; // end undoable change
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (oneleft() == OK) {
start_arrow_with_change(&tpos, end_change);
if (!end_change) {
@@ -4210,13 +4019,11 @@ static void ins_left(void)
static void ins_home(int c)
{
- pos_T tpos;
-
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (c == K_C_HOME) {
curwin->w_cursor.lnum = 1;
}
@@ -4228,13 +4035,11 @@ static void ins_home(int c)
static void ins_end(int c)
{
- pos_T tpos;
-
if ((fdo_flags & FDO_HOR) && KeyTyped) {
foldOpenCursor();
}
undisplay_dollar();
- tpos = curwin->w_cursor;
+ pos_T tpos = curwin->w_cursor;
if (c == K_C_END) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
@@ -4256,7 +4061,7 @@ static void ins_s_left(void)
if (!end_change) {
AppendCharToRedobuff(K_S_LEFT);
}
- (void)bck_word(1L, false, false);
+ (void)bck_word(1, false, false);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
@@ -4315,7 +4120,7 @@ static void ins_s_right(void)
if (!end_change) {
AppendCharToRedobuff(K_S_RIGHT);
}
- (void)fwd_word(1L, false, 0);
+ (void)fwd_word(1, false, 0);
curwin->w_set_curswant = true;
} else {
vim_beep(BO_CRSR);
@@ -4326,13 +4131,12 @@ static void ins_s_right(void)
/// @param startcol when true move to Insstart.col
static void ins_up(bool startcol)
{
- pos_T tpos;
linenr_T old_topline = curwin->w_topline;
int old_topfill = curwin->w_topfill;
undisplay_dollar();
- tpos = curwin->w_cursor;
- if (cursor_up(1L, true) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (cursor_up(1, true) == OK) {
if (startcol) {
coladvance(getvcol_nolist(&Insstart));
}
@@ -4349,8 +4153,6 @@ static void ins_up(bool startcol)
static void ins_pageup(void)
{
- pos_T tpos;
-
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
@@ -4362,8 +4164,8 @@ static void ins_pageup(void)
return;
}
- tpos = curwin->w_cursor;
- if (onepage(BACKWARD, 1L) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (onepage(BACKWARD, 1) == OK) {
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -4374,13 +4176,12 @@ static void ins_pageup(void)
/// @param startcol when true move to Insstart.col
static void ins_down(bool startcol)
{
- pos_T tpos;
linenr_T old_topline = curwin->w_topline;
int old_topfill = curwin->w_topfill;
undisplay_dollar();
- tpos = curwin->w_cursor;
- if (cursor_down(1L, true) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (cursor_down(1, true) == OK) {
if (startcol) {
coladvance(getvcol_nolist(&Insstart));
}
@@ -4397,8 +4198,6 @@ static void ins_down(bool startcol)
static void ins_pagedown(void)
{
- pos_T tpos;
-
undisplay_dollar();
if (mod_mask & MOD_MASK_CTRL) {
@@ -4410,8 +4209,8 @@ static void ins_pagedown(void)
return;
}
- tpos = curwin->w_cursor;
- if (onepage(FORWARD, 1L) == OK) {
+ pos_T tpos = curwin->w_cursor;
+ if (onepage(FORWARD, 1) == OK) {
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -4425,7 +4224,6 @@ static void ins_pagedown(void)
static bool ins_tab(void)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- int i;
int temp;
if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) {
@@ -4504,7 +4302,6 @@ static bool ins_tab(void)
char *ptr;
char *saved_line = NULL; // init for GCC
pos_T pos;
- pos_T fpos;
pos_T *cursor;
colnr_T want_vcol, vcol;
int change_col = -1;
@@ -4528,7 +4325,7 @@ static bool ins_tab(void)
}
// Find first white before the cursor
- fpos = curwin->w_cursor;
+ pos_T fpos = curwin->w_cursor;
while (fpos.col > 0 && ascii_iswhite(ptr[-1])) {
fpos.col--;
ptr--;
@@ -4553,7 +4350,7 @@ static bool ins_tab(void)
// Use as many TABs as possible. Beware of 'breakindent', 'showbreak'
// and 'linebreak' adding extra virtual columns.
while (ascii_iswhite(*ptr)) {
- i = lbr_chartabsize(&cts);
+ int i = lbr_chartabsize(&cts);
if (cts.cts_vcol + i > want_vcol) {
break;
}
@@ -4595,9 +4392,9 @@ static bool ins_tab(void)
fpos.col += repl_off;
// Delete following spaces.
- i = cursor->col - fpos.col;
+ int i = cursor->col - fpos.col;
if (i > 0) {
- STRMOVE(ptr, (char *)ptr + i);
+ STRMOVE(ptr, ptr + i);
// correct replace stack.
if ((State & REPLACE_FLAG)
&& !(State & VREPLACE_FLAG)) {
@@ -4606,9 +4403,8 @@ static bool ins_tab(void)
}
}
if (!(State & VREPLACE_FLAG)) {
- extmark_splice_cols(curbuf, (int)fpos.lnum - 1, change_col,
- cursor->col - change_col, fpos.col - change_col,
- kExtmarkUndo);
+ inserted_bytes(fpos.lnum, change_col,
+ cursor->col - change_col, fpos.col - change_col);
}
}
cursor->col -= i;
@@ -4689,8 +4485,6 @@ bool ins_eol(int c)
// done.
static int ins_digraph(void)
{
- int c;
- int cc;
bool did_putchar = false;
pc_status = PC_STATUS_UNSET;
@@ -4707,7 +4501,7 @@ static int ins_digraph(void)
// mode message to be deleted when ESC is hit
no_mapping++;
allow_keys++;
- c = plain_vgetc();
+ int c = plain_vgetc();
no_mapping--;
allow_keys--;
if (did_putchar) {
@@ -4736,7 +4530,7 @@ static int ins_digraph(void)
}
no_mapping++;
allow_keys++;
- cc = plain_vgetc();
+ int cc = plain_vgetc();
no_mapping--;
allow_keys--;
if (did_putchar) {
@@ -4759,9 +4553,7 @@ static int ins_digraph(void)
// Returns the char to be inserted, or NUL if none found.
int ins_copychar(linenr_T lnum)
{
- int c;
- char *ptr, *prev_ptr;
- char *line;
+ char *ptr;
if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) {
vim_beep(BO_COPY);
@@ -4769,9 +4561,9 @@ int ins_copychar(linenr_T lnum)
}
// try to advance to the cursor column
- line = ml_get(lnum);
- prev_ptr = line;
validate_virtcol();
+ char *line = ml_get(lnum);
+ char *prev_ptr = line;
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, lnum, 0, line, line);
@@ -4787,7 +4579,7 @@ int ins_copychar(linenr_T lnum)
}
clear_chartabsize_arg(&cts);
- c = utf_ptr2char(ptr);
+ int c = utf_ptr2char(ptr);
if (c == NUL) {
vim_beep(BO_COPY);
}
@@ -4809,8 +4601,6 @@ static int ins_ctrl_ey(int tc)
} else {
c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1));
if (c != NUL) {
- long tw_save;
-
// The character must be taken literally, insert like it
// was typed after a CTRL-V, and pretend 'textwidth'
// wasn't set. Digits, 'o' and 'x' are special after a
@@ -4818,7 +4608,7 @@ static int ins_ctrl_ey(int tc)
if (c < 256 && !isalnum(c)) {
AppendToRedobuff(CTRL_V_STR);
}
- tw_save = curbuf->b_p_tw;
+ OptInt tw_save = curbuf->b_p_tw;
curbuf->b_p_tw = -1;
insert_special(c, true, false);
curbuf->b_p_tw = tw_save;
@@ -4835,13 +4625,14 @@ static int ins_ctrl_ey(int tc)
// Used when inserting a "normal" character.
static void ins_try_si(int c)
{
- pos_T *pos, old_pos;
- char *ptr;
- int i;
- bool temp;
+ pos_T *pos;
// do some very smart indenting when entering '{' or '}'
if (((did_si || can_si_back) && c == '{') || (can_si && c == '}' && inindent(0))) {
+ pos_T old_pos;
+ char *ptr;
+ int i;
+ bool temp;
// for '}' set indent equal to indent of line containing matching '{'
if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) {
old_pos = curwin->w_cursor;
@@ -4974,9 +4765,8 @@ void set_can_cindent(bool val)
int ins_apply_autocmds(event_T event)
{
varnumber_T tick = buf_get_changedtick(curbuf);
- int r;
- r = apply_autocmds(event, NULL, NULL, false, curbuf);
+ int r = apply_autocmds(event, NULL, NULL, false, curbuf);
// If u_savesub() was called then we are not prepared to start
// a new line. Call u_save() with no contents to fix that.
diff --git a/src/nvim/edit.h b/src/nvim/edit.h
index 91c519f015..434b653f7b 100644
--- a/src/nvim/edit.h
+++ b/src/nvim/edit.h
@@ -1,32 +1,39 @@
-#ifndef NVIM_EDIT_H
-#define NVIM_EDIT_H
+#pragma once
-#include "nvim/autocmd.h"
-#include "nvim/vim.h"
+#include "nvim/autocmd_defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// Values for in_cinkeys()
-#define KEY_OPEN_FORW 0x101
-#define KEY_OPEN_BACK 0x102
-#define KEY_COMPLETE 0x103 // end of completion
+/// Values for in_cinkeys()
+enum {
+ KEY_OPEN_FORW = 0x101,
+ KEY_OPEN_BACK = 0x102,
+ KEY_COMPLETE = 0x103, ///< end of completion
+};
-// Values for change_indent()
-#define INDENT_SET 1 // set indent
-#define INDENT_INC 2 // increase indent
-#define INDENT_DEC 3 // decrease indent
+/// Values for change_indent()
+enum {
+ INDENT_SET = 1, ///< set indent
+ INDENT_INC = 2, ///< increase indent
+ INDENT_DEC = 3, ///< decrease indent
+};
-// flags for beginline()
-#define BL_WHITE 1 // cursor on first non-white in the line
-#define BL_SOL 2 // use 'sol' option
-#define BL_FIX 4 // don't leave cursor on a NUL
+/// flags for beginline()
+enum {
+ BL_WHITE = 1, ///< cursor on first non-white in the line
+ BL_SOL = 2, ///< use 'sol' option
+ BL_FIX = 4, ///< don't leave cursor on a NUL
+};
-// flags for insertchar()
-#define INSCHAR_FORMAT 1 // force formatting
-#define INSCHAR_DO_COM 2 // format comments
-#define INSCHAR_CTRLV 4 // char typed just after CTRL-V
-#define INSCHAR_NO_FEX 8 // don't use 'formatexpr'
-#define INSCHAR_COM_LIST 16 // format comments with list/2nd line indent
+/// flags for insertchar()
+enum {
+ INSCHAR_FORMAT = 1, ///< force formatting
+ INSCHAR_DO_COM = 2, ///< format comments
+ INSCHAR_CTRLV = 4, ///< char typed just after CTRL-V
+ INSCHAR_NO_FEX = 8, ///< don't use 'formatexpr'
+ INSCHAR_COM_LIST = 16, ///< format comments with list/2nd line indent
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "edit.h.generated.h"
#endif
-#endif // NVIM_EDIT_H
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 4392ea306f..f4479d06a6 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -1,23 +1,24 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval.c: Expression evaluation.
#include <assert.h>
#include <ctype.h>
-#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include "auto/config.h"
+#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
@@ -28,7 +29,6 @@
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/ex_cmds.h"
@@ -36,20 +36,21 @@
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid_defs.h"
+#include "nvim/hashtab.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lib/queue.h"
-#include "nvim/locale.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -59,14 +60,16 @@
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
@@ -74,28 +77,43 @@
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/usercmd.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// TODO(ZyX-I): Remove DICT_MAXNEST, make users be non-recursive instead
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
-static char *e_missbrac = N_("E111: Missing ']'");
-static char *e_list_end = N_("E697: Missing end of List ']': %s");
-static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary");
-static char *e_nowhitespace
+#define MAX_CALLBACK_DEPTH 20
+
+static const char *e_missbrac = N_("E111: Missing ']'");
+static const char *e_list_end = N_("E697: Missing end of List ']': %s");
+static const char e_cannot_slice_dictionary[]
+ = N_("E719: Cannot slice a Dictionary");
+static const char e_cannot_index_special_variable[]
+ = N_("E909: Cannot index a special variable");
+static const char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis");
-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");
-static char e_expression_too_recursive_str[] = N_("E1169: Expression too recursive: %s");
-static char e_dot_can_only_be_used_on_dictionary_str[]
+static const char *e_write2 = N_("E80: Error while writing: %s");
+static const char e_cannot_index_a_funcref[]
+ = N_("E695: Cannot index a Funcref");
+static const char e_variable_nested_too_deep_for_making_copy[]
+ = N_("E698: Variable nested too deep for making a copy");
+static const char e_string_list_or_blob_required[]
+ = N_("E1098: String, List or Blob required");
+static const char e_expression_too_recursive_str[]
+ = N_("E1169: Expression too recursive: %s");
+static const char e_dot_can_only_be_used_on_dictionary_str[]
= N_("E1203: Dot can only be used on a dictionary: %s");
+static const char e_empty_function_name[]
+ = N_("E1192: Empty function name");
+static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
+ = N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
static char * const namespace_char = "abglstvw";
@@ -109,22 +127,11 @@ static hashtab_T compat_hashtab;
/// Used for checking if local variables or arguments used in a lambda.
bool *eval_lavars_used = NULL;
-/// Array to hold the hashtab with variables local to each sourced script.
-/// Each item holds a variable (nameless) that points to the dict_T.
-typedef struct {
- ScopeDictDictItem sv_var;
- dict_T sv_dict;
-} scriptvar_T;
-
-static garray_T ga_scripts = { 0, 0, sizeof(scriptvar_T *), 4, NULL };
-#define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1])
+#define SCRIPT_SV(id) (SCRIPT_ITEM(id)->sn_vars)
#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab)
static int echo_attr = 0; // attributes used for ":echo"
-// The names of packages that once were loaded are remembered.
-static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL };
-
/// Info used by a ":for" loop.
typedef struct {
int fi_semicolon; // true if ending in '; var]'
@@ -259,6 +266,7 @@ static struct vimvar {
VV(VV_ARGV, "argv", VAR_LIST, VV_RO),
VV(VV_COLLATE, "collate", VAR_STRING, VV_RO),
VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO),
+ VV(VV_MAXCOL, "maxcol", VAR_NUMBER, VV_RO),
// Neovim
VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO),
VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO),
@@ -293,6 +301,13 @@ static partial_T *vvlua_partial;
/// v: hashtab
#define vimvarht vimvardict.dv_hashtab
+/// Enum used by filter(), map() and mapnew()
+typedef enum {
+ FILTERMAP_FILTER,
+ FILTERMAP_MAP,
+ FILTERMAP_MAPNEW,
+} filtermap_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.c.generated.h"
#endif
@@ -406,11 +421,11 @@ void eval_init(void)
// add to v: scope dict, unless the value is not always available
if (p->vv_type != VAR_UNKNOWN) {
- hash_add(&vimvarht, (char *)p->vv_di.di_key);
+ hash_add(&vimvarht, p->vv_di.di_key);
}
if (p->vv_flags & VV_COMPAT) {
// add to compat scope dict
- hash_add(&compat_hashtab, (char *)p->vv_di.di_key);
+ hash_add(&compat_hashtab, p->vv_di.di_key);
}
}
vimvars[VV_VERSION].vv_nr = VIM_VERSION_100;
@@ -440,8 +455,8 @@ void eval_init(void)
set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED));
set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown));
set_vim_var_nr(VV_STDERR, CHAN_STDERR);
- set_vim_var_nr(VV_SEARCHFORWARD, 1L);
- set_vim_var_nr(VV_HLSEARCH, 1L);
+ set_vim_var_nr(VV_SEARCHFORWARD, 1);
+ set_vim_var_nr(VV_HLSEARCH, 1);
set_vim_var_nr(VV_COUNT1, 1);
set_vim_var_nr(VV_TYPE_NUMBER, VAR_TYPE_NUMBER);
set_vim_var_nr(VV_TYPE_STRING, VAR_TYPE_STRING);
@@ -459,6 +474,7 @@ void eval_init(void)
set_vim_var_nr(VV_NUMBERMIN, VARNUMBER_MIN);
set_vim_var_nr(VV_NUMBERSIZE, sizeof(varnumber_T) * 8);
set_vim_var_special(VV_EXITING, kSpecialVarNull);
+ set_vim_var_nr(VV_MAXCOL, MAXCOL);
set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
@@ -473,7 +489,7 @@ void eval_init(void)
}
#if defined(EXITFREE)
-void eval_clear(void)
+static void evalvars_clear(void)
{
for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {
struct vimvar *p = &vimvars[i];
@@ -488,27 +504,28 @@ void eval_clear(void)
hash_init(&vimvarht); // garbage_collect() will access it
hash_clear(&compat_hashtab);
- free_scriptnames();
-# ifdef HAVE_WORKING_LIBINTL
- free_locales();
-# endif
-
// global variables
vars_clear(&globvarht);
- // autoloaded script names
- ga_clear_strings(&ga_loaded);
-
- // Script-local variables. First clear all the variables and in a second
- // loop free the scriptvar_T, because a variable in one script might hold
- // a reference to the whole scope of another script.
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
+ // Script-local variables. Clear all the variables here.
+ // The scriptvar_T is cleared later in free_scriptnames(), because a
+ // variable in one script might hold a reference to the whole scope of
+ // another script.
+ for (int i = 1; i <= script_items.ga_len; i++) {
vars_clear(&SCRIPT_VARS(i));
}
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
- xfree(SCRIPT_SV(i));
- }
- ga_clear(&ga_scripts);
+}
+
+void eval_clear(void)
+{
+ evalvars_clear();
+ free_scriptnames(); // must come after evalvars_clear().
+# ifdef HAVE_WORKING_LIBINTL
+ free_locales();
+# endif
+
+ // autoloaded script names
+ free_autoload_scriptnames();
// unreferenced lists and dicts
(void)garbage_collect(false);
@@ -604,7 +621,7 @@ int var_redir_start(char *name, int append)
/// :redir => foo
/// :let foo
/// :redir END
-void var_redir_str(char *value, int value_len)
+void var_redir_str(const char *value, int value_len)
{
if (redir_lval == NULL) {
return;
@@ -654,19 +671,27 @@ void var_redir_stop(void)
int eval_charconvert(const char *const enc_from, const char *const enc_to,
const char *const fname_from, const char *const fname_to)
{
- bool err = false;
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_CC_FROM, enc_from, -1);
set_vim_var_string(VV_CC_TO, enc_to, -1);
set_vim_var_string(VV_FNAME_IN, fname_from, -1);
set_vim_var_string(VV_FNAME_OUT, fname_to, -1);
+ sctx_T *ctx = get_option_sctx("charconvert");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ bool err = false;
if (eval_to_bool(p_ccv, &err, NULL, false)) {
err = true;
}
+
set_vim_var_string(VV_CC_FROM, NULL, -1);
set_vim_var_string(VV_CC_TO, NULL, -1);
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
if (err) {
return FAIL;
@@ -676,28 +701,57 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to,
void eval_diff(const char *const origfile, const char *const newfile, const char *const outfile)
{
- bool err = false;
-
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_FNAME_IN, origfile, -1);
set_vim_var_string(VV_FNAME_NEW, newfile, -1);
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
- (void)eval_to_bool(p_dex, &err, NULL, false);
+
+ sctx_T *ctx = get_option_sctx("diffexpr");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ // errors are ignored
+ typval_T *tv = eval_expr(p_dex, NULL);
+ tv_free(tv);
+
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_NEW, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
}
void eval_patch(const char *const origfile, const char *const difffile, const char *const outfile)
{
- bool err = false;
-
+ const sctx_T saved_sctx = current_sctx;
set_vim_var_string(VV_FNAME_IN, origfile, -1);
set_vim_var_string(VV_FNAME_DIFF, difffile, -1);
set_vim_var_string(VV_FNAME_OUT, outfile, -1);
- (void)eval_to_bool(p_pex, &err, NULL, false);
+
+ sctx_T *ctx = get_option_sctx("patchexpr");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
+
+ // errors are ignored
+ typval_T *tv = eval_expr(p_pex, NULL);
+ tv_free(tv);
+
set_vim_var_string(VV_FNAME_IN, NULL, -1);
set_vim_var_string(VV_FNAME_DIFF, NULL, -1);
set_vim_var_string(VV_FNAME_OUT, NULL, -1);
+ current_sctx = saved_sctx;
+}
+
+void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip)
+{
+ *evalarg = (evalarg_T){ .eval_flags = skip ? 0 : EVAL_EVALUATE };
+ if (eap != NULL) {
+ if (getline_equal(eap->getline, eap->cookie, getsourceline)) {
+ evalarg->eval_getline = eap->getline;
+ evalarg->eval_cookie = eap->cookie;
+ }
+ }
}
/// Top level evaluation function, returning a boolean.
@@ -706,15 +760,18 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch
/// @param skip only parse, don't execute
///
/// @return true or false.
-int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip)
+int eval_to_bool(char *arg, bool *error, exarg_T *eap, int skip)
{
typval_T tv;
bool retval = false;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, skip);
if (skip) {
emsg_skip++;
}
- if (eval0(arg, &tv, nextcmd, !skip) == FAIL) {
+ if (eval0(arg, &tv, eap, &evalarg) == FAIL) {
*error = true;
} else {
*error = false;
@@ -726,19 +783,23 @@ int eval_to_bool(char *arg, bool *error, char **nextcmd, int skip)
if (skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
return retval;
}
/// Call eval1() and give an error message if not done at a lower level.
-static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate)
+static int eval1_emsg(char **arg, typval_T *rettv, exarg_T *eap)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
const char *const start = *arg;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
+ evalarg_T evalarg;
- const int ret = eval1(arg, rettv, evaluate);
+ fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
+
+ const int ret = eval1(arg, rettv, &evalarg);
if (ret == FAIL) {
// Report the invalid expression unless the expression evaluation has
// been cancelled due to an aborting error, an interrupt, or an
@@ -750,6 +811,7 @@ static int eval1_emsg(char **arg, typval_T *rettv, bool evaluate)
semsg(_(e_invexpr2), start);
}
}
+ clear_evalarg(&evalarg, eap);
return ret;
}
@@ -762,39 +824,50 @@ bool eval_expr_valid_arg(const typval_T *const tv)
&& (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
}
-int eval_expr_typval(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+/// Evaluate an expression, which can be a function, partial or string.
+/// Pass arguments "argv[argc]".
+/// Return the result in "rettv" and OK or FAIL.
+///
+/// @param want_func if true, treat a string as a function name, not an expression
+int eval_expr_typval(const typval_T *expr, bool want_func, typval_T *argv, int argc,
+ typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
{
+ char buf[NUMBUFLEN];
funcexe_T funcexe = FUNCEXE_INIT;
- if (expr->v_type == VAR_FUNC) {
- const char *const s = expr->vval.v_string;
+ if (expr->v_type == VAR_PARTIAL) {
+ partial_T *const partial = expr->vval.v_partial;
+ if (partial == NULL) {
+ return FAIL;
+ }
+ const char *const s = partial_name(partial);
if (s == NULL || *s == NUL) {
return FAIL;
}
funcexe.fe_evaluate = true;
+ funcexe.fe_partial = partial;
if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
- } else if (expr->v_type == VAR_PARTIAL) {
- partial_T *const partial = expr->vval.v_partial;
- const char *const s = partial_name(partial);
+ } else if (expr->v_type == VAR_FUNC || want_func) {
+ const char *const s = (expr->v_type == VAR_FUNC
+ ? expr->vval.v_string
+ : tv_get_string_buf_chk(expr, buf));
if (s == NULL || *s == NUL) {
return FAIL;
}
funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
return FAIL;
}
} else {
- char buf[NUMBUFLEN];
char *s = (char *)tv_get_string_buf_chk(expr, buf);
if (s == NULL) {
return FAIL;
}
s = skipwhite(s);
- if (eval1_emsg(&s, rettv, true) == FAIL) {
+ if (eval1_emsg(&s, rettv, NULL) == FAIL) {
return FAIL;
}
if (*skipwhite(s) != NUL) { // check for trailing chars after expr
@@ -813,7 +886,7 @@ bool eval_expr_to_bool(const typval_T *expr, bool *error)
{
typval_T argv, rettv;
- if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, &argv, 0, &rettv) == FAIL) {
*error = true;
return false;
}
@@ -825,22 +898,23 @@ bool eval_expr_to_bool(const typval_T *expr, bool *error)
/// Top level evaluation function, returning a string
///
/// @param[in] arg String to evaluate.
-/// @param nextcmd Pointer to the start of the next Ex command.
/// @param[in] skip If true, only do parsing to nextcmd without reporting
/// errors or actually evaluating anything.
///
/// @return [allocated] string result of evaluation or NULL in case of error or
/// when skipping.
-char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip)
+char *eval_to_string_skip(char *arg, exarg_T *eap, const bool skip)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
typval_T tv;
char *retval;
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, skip);
if (skip) {
emsg_skip++;
}
- if (eval0((char *)arg, &tv, (char **)nextcmd, !skip) == FAIL || skip) {
+ if (eval0(arg, &tv, eap, &evalarg) == FAIL || skip) {
retval = NULL;
} else {
retval = xstrdup(tv_get_string(&tv));
@@ -849,6 +923,7 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip
if (skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
return retval;
}
@@ -856,48 +931,65 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip
/// Skip over an expression at "*pp".
///
/// @return FAIL for an error, OK otherwise.
-int skip_expr(char **pp)
+int skip_expr(char **pp, evalarg_T *const evalarg)
{
- typval_T rettv;
+ const int save_flags = evalarg == NULL ? 0 : evalarg->eval_flags;
+
+ // Don't evaluate the expression.
+ if (evalarg != NULL) {
+ evalarg->eval_flags &= ~EVAL_EVALUATE;
+ }
*pp = skipwhite(*pp);
- return eval1(pp, &rettv, false);
+ typval_T rettv;
+ int res = eval1(pp, &rettv, NULL);
+
+ if (evalarg != NULL) {
+ evalarg->eval_flags = save_flags;
+ }
+
+ return res;
+}
+
+/// Convert "tv" to a string.
+///
+/// @param convert when true convert a List into a sequence of lines.
+///
+/// @return an allocated string.
+static char *typval2string(typval_T *tv, bool convert)
+{
+ if (convert && tv->v_type == VAR_LIST) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ if (tv->vval.v_list != NULL) {
+ tv_list_join(&ga, tv->vval.v_list, "\n");
+ if (tv_list_len(tv->vval.v_list) > 0) {
+ ga_append(&ga, NL);
+ }
+ }
+ ga_append(&ga, NUL);
+ return (char *)ga.ga_data;
+ }
+ return xstrdup(tv_get_string(tv));
}
/// Top level evaluation function, returning a string.
///
-/// @param convert when true convert a List into a sequence of lines and convert
-/// a Float to a String.
+/// @param convert when true convert a List into a sequence of lines.
///
-/// @return pointer to allocated memory, or NULL for failure.
-char *eval_to_string(char *arg, char **nextcmd, bool convert)
+/// @return pointer to allocated memory, or NULL for failure.
+char *eval_to_string(char *arg, bool convert)
{
typval_T tv;
char *retval;
- garray_T ga;
- if (eval0(arg, &tv, nextcmd, true) == FAIL) {
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = NULL;
} else {
- if (convert && tv.v_type == VAR_LIST) {
- ga_init(&ga, (int)sizeof(char), 80);
- if (tv.vval.v_list != NULL) {
- tv_list_join(&ga, tv.vval.v_list, "\n");
- if (tv_list_len(tv.vval.v_list) > 0) {
- ga_append(&ga, NL);
- }
- }
- ga_append(&ga, NUL);
- retval = (char *)ga.ga_data;
- } else if (convert && tv.v_type == VAR_FLOAT) {
- char numbuf[NUMBUFLEN];
- vim_snprintf(numbuf, NUMBUFLEN, "%g", tv.vval.v_float);
- retval = xstrdup(numbuf);
- } else {
- retval = xstrdup(tv_get_string(&tv));
- }
+ retval = typval2string(&tv, convert);
tv_clear(&tv);
}
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
return retval;
}
@@ -906,7 +998,7 @@ char *eval_to_string(char *arg, char **nextcmd, bool convert)
/// textlock.
///
/// @param use_sandbox when true, use the sandbox.
-char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox)
+char *eval_to_string_safe(char *arg, const bool use_sandbox)
{
char *retval;
funccal_entry_T funccal_entry;
@@ -916,7 +1008,7 @@ char *eval_to_string_safe(char *arg, char **nextcmd, int use_sandbox)
sandbox++;
}
textlock++;
- retval = eval_to_string(arg, nextcmd, false);
+ retval = eval_to_string(arg, false);
if (use_sandbox) {
sandbox--;
}
@@ -937,7 +1029,7 @@ varnumber_T eval_to_number(char *expr)
emsg_off++;
- if (eval1(&p, &rettv, true) == FAIL) {
+ if (eval1(&p, &rettv, &EVALARG_EVALUATE) == FAIL) {
retval = -1;
} else {
retval = tv_get_number_chk(&rettv, NULL);
@@ -952,12 +1044,18 @@ varnumber_T eval_to_number(char *expr)
///
/// @return an allocated typval_T with the result or
/// NULL when there is an error.
-typval_T *eval_expr(char *arg)
+typval_T *eval_expr(char *arg, exarg_T *eap)
{
typval_T *tv = xmalloc(sizeof(*tv));
- if (eval0(arg, tv, NULL, true) == FAIL) {
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
+
+ if (eval0(arg, tv, eap, &evalarg) == FAIL) {
XFREE_CLEAR(tv);
}
+
+ clear_evalarg(&evalarg, eap);
return tv;
}
@@ -970,7 +1068,7 @@ void list_vim_vars(int *first)
/// List script-local variables, if there is a script.
void list_script_vars(int *first)
{
- if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= ga_scripts.ga_len) {
+ if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
list_hashtable_vars(&SCRIPT_VARS(current_sctx.sc_sid), "s:", false, first);
}
}
@@ -992,7 +1090,7 @@ void prepare_vimvar(int idx, typval_T *save_tv)
{
*save_tv = vimvars[idx].vv_tv;
if (vimvars[idx].vv_type == VAR_UNKNOWN) {
- hash_add(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
+ hash_add(&vimvarht, vimvars[idx].vv_di.di_key);
}
}
@@ -1005,7 +1103,7 @@ void restore_vimvar(int idx, typval_T *save_tv)
return;
}
- hashitem_T *hi = hash_find(&vimvarht, (char *)vimvars[idx].vv_di.di_key);
+ hashitem_T *hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key);
if (HASHITEM_EMPTY(hi)) {
internal_error("restore_vimvar()");
} else {
@@ -1023,6 +1121,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
typval_T rettv;
list_T *list = NULL;
char *p = skipwhite(expr);
+ const sctx_T saved_sctx = current_sctx;
// Set "v:val" to the bad word.
prepare_vimvar(VV_VAL, &save_val);
@@ -1031,8 +1130,12 @@ list_T *eval_spell_expr(char *badword, char *expr)
if (p_verbose == 0) {
emsg_off++;
}
+ sctx_T *ctx = get_option_sctx("spellsuggest");
+ if (ctx != NULL) {
+ current_sctx = *ctx;
+ }
- if (eval1(&p, &rettv, true) == OK) {
+ if (eval1(&p, &rettv, &EVALARG_EVALUATE) == OK) {
if (rettv.v_type != VAR_LIST) {
tv_clear(&rettv);
} else {
@@ -1044,6 +1147,7 @@ list_T *eval_spell_expr(char *badword, char *expr)
emsg_off--;
}
restore_vimvar(VV_VAL, &save_val);
+ current_sctx = saved_sctx;
return list;
}
@@ -1123,7 +1227,7 @@ fail:
///
/// @return [allocated] NULL when calling function fails, allocated string
/// otherwise.
-char *call_func_retstr(const char *const func, int argc, typval_T *argv)
+void *call_func_retstr(const char *const func, int argc, typval_T *argv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
typval_T rettv;
@@ -1153,7 +1257,7 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
typval_T rettv;
// All arguments are passed as strings, no conversion to number.
- if (call_vim_function((char *)func, argc, argv, &rettv) == FAIL) {
+ if (call_vim_function(func, argc, argv, &rettv) == FAIL) {
return NULL;
}
@@ -1167,11 +1271,13 @@ void *call_func_retlist(const char *func, int argc, typval_T *argv)
/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding
/// it in "*cp". Doesn't give error messages.
-int eval_foldexpr(char *arg, int *cp)
+int eval_foldexpr(win_T *wp, int *cp)
{
- typval_T tv;
- varnumber_T retval;
- int use_sandbox = was_set_insecurely(curwin, "foldexpr", OPT_LOCAL);
+ const sctx_T saved_sctx = current_sctx;
+ const bool use_sandbox = was_set_insecurely(wp, "foldexpr", OPT_LOCAL);
+
+ char *arg = wp->w_p_fde;
+ current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx;
emsg_off++;
if (use_sandbox) {
@@ -1179,7 +1285,10 @@ int eval_foldexpr(char *arg, int *cp)
}
textlock++;
*cp = NUL;
- if (eval0(arg, &tv, NULL, true) == FAIL) {
+
+ typval_T tv;
+ varnumber_T retval;
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = 0;
} else {
// If the result is a number, just return the number.
@@ -1191,22 +1300,61 @@ int eval_foldexpr(char *arg, int *cp)
// If the result is a string, check if there is a non-digit before
// the number.
char *s = tv.vval.v_string;
- if (!ascii_isdigit(*s) && *s != '-') {
- *cp = (char_u)(*s++);
+ if (*s != NUL && !ascii_isdigit(*s) && *s != '-') {
+ *cp = (uint8_t)(*s++);
}
retval = atol(s);
}
tv_clear(&tv);
}
+
emsg_off--;
if (use_sandbox) {
sandbox--;
}
textlock--;
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
+ current_sctx = saved_sctx;
return (int)retval;
}
+/// Evaluate 'foldtext', returning an Array or a String (NULL_STRING on failure).
+Object eval_foldtext(win_T *wp)
+{
+ const bool use_sandbox = was_set_insecurely(wp, "foldtext", OPT_LOCAL);
+ char *arg = wp->w_p_fdt;
+ funccal_entry_T funccal_entry;
+
+ save_funccal(&funccal_entry);
+ if (use_sandbox) {
+ sandbox++;
+ }
+ textlock++;
+
+ typval_T tv;
+ Object retval;
+ if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
+ retval = STRING_OBJ(NULL_STRING);
+ } else {
+ if (tv.v_type == VAR_LIST) {
+ retval = vim_to_object(&tv);
+ } else {
+ retval = STRING_OBJ(cstr_to_string(tv_get_string(&tv)));
+ }
+ tv_clear(&tv);
+ }
+ clear_evalarg(&EVALARG_EVALUATE, NULL);
+
+ if (use_sandbox) {
+ sandbox--;
+ }
+ textlock--;
+ restore_funccal();
+
+ return retval;
+}
+
/// Get an lvalue
///
/// Lvalue may be
@@ -1242,9 +1390,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (skip) {
// When skipping just find the end of the name.
- lp->ll_name = (const char *)name;
- return (char *)find_name_end(name, NULL, NULL,
- FNE_INCL_BR | fne_flags);
+ lp->ll_name = name;
+ return (char *)find_name_end(name, NULL, NULL, FNE_INCL_BR | fne_flags);
}
// Find the end of the name.
@@ -1277,8 +1424,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_name_len = strlen(lp->ll_name);
}
} else {
- lp->ll_name = (const char *)name;
- lp->ll_name_len = (size_t)((const char *)p - lp->ll_name);
+ lp->ll_name = name;
+ lp->ll_name_len = (size_t)(p - lp->ll_name);
}
// Without [idx] or .key we are done.
@@ -1321,14 +1468,22 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
}
return NULL;
}
- if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL)
- && !(lp->ll_tv->v_type == VAR_DICT && lp->ll_tv->vval.v_dict != NULL)
- && !(lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob != NULL)) {
+ if (lp->ll_tv->v_type != VAR_LIST
+ && lp->ll_tv->v_type != VAR_DICT
+ && lp->ll_tv->v_type != VAR_BLOB) {
if (!quiet) {
emsg(_("E689: Can only index a List, Dictionary or Blob"));
}
return NULL;
}
+
+ // a NULL list/blob works like an empty list/blob, allocate one now.
+ if (lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list == NULL) {
+ tv_list_alloc_ret(lp->ll_tv, kListLenUnknown);
+ } else if (lp->ll_tv->v_type == VAR_BLOB && lp->ll_tv->vval.v_blob == NULL) {
+ tv_blob_alloc_ret(lp->ll_tv);
+ }
+
if (lp->ll_range) {
if (!quiet) {
emsg(_("E708: [:] must come last"));
@@ -1355,7 +1510,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
empty1 = true;
} else {
empty1 = false;
- if (eval1(&p, &var1, true) == FAIL) { // Recursive!
+ if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive!
return NULL;
}
if (!tv_check_str(&var1)) {
@@ -1370,7 +1525,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (*p == ':') {
if (lp->ll_tv->v_type == VAR_DICT) {
if (!quiet) {
- emsg(_(e_dictrange));
+ emsg(_(e_cannot_slice_dictionary));
}
tv_clear(&var1);
return NULL;
@@ -1389,7 +1544,8 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_empty2 = true;
} else {
lp->ll_empty2 = false;
- if (eval1(&p, &var2, true) == FAIL) { // Recursive!
+ // Recursive!
+ if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) {
tv_clear(&var1);
return NULL;
}
@@ -1425,25 +1581,23 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
}
lp->ll_list = NULL;
lp->ll_dict = lp->ll_tv->vval.v_dict;
- lp->ll_di = tv_dict_find(lp->ll_dict, (const char *)key, len);
+ lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
// When assigning to a scope dictionary check that a function and
// variable name is valid (only variable name unless it is l: or
// g: dictionary). Disallow overwriting a builtin function.
if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
char prevval;
- int wrong;
-
if (len != -1) {
prevval = key[len];
key[len] = NUL;
} else {
prevval = 0; // Avoid compiler warning.
}
- wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
- && tv_is_func(*rettv)
- && var_wrong_func_name((const char *)key, lp->ll_di == NULL))
- || !valid_varname((const char *)key));
+ bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
+ && tv_is_func(*rettv)
+ && var_wrong_func_name(key, lp->ll_di == NULL))
+ || !valid_varname(key));
if (len != -1) {
key[len] = prevval;
}
@@ -1480,7 +1634,7 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
if (len == -1) {
lp->ll_newkey = xstrdup(key);
} else {
- lp->ll_newkey = xstrnsave(key, (size_t)len);
+ lp->ll_newkey = xmemdupz(key, (size_t)len);
}
tv_clear(&var1);
break;
@@ -1500,26 +1654,19 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_n1 = 0;
} else {
// Is number or string.
- lp->ll_n1 = (long)tv_get_number(&var1);
+ lp->ll_n1 = (int)tv_get_number(&var1);
}
tv_clear(&var1);
const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
- if (lp->ll_n1 < 0 || lp->ll_n1 > bloblen
- || (lp->ll_range && lp->ll_n1 == bloblen)) {
- if (!quiet) {
- semsg(_(e_blobidx), (int64_t)lp->ll_n1);
- }
+ if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
tv_clear(&var2);
return NULL;
}
if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (long)tv_get_number(&var2);
+ lp->ll_n2 = (int)tv_get_number(&var2);
tv_clear(&var2);
- if (lp->ll_n2 < 0 || lp->ll_n2 >= bloblen || lp->ll_n2 < lp->ll_n1) {
- if (!quiet) {
- semsg(_(e_blobidx), (int64_t)lp->ll_n2);
- }
+ if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
return NULL;
}
}
@@ -1532,24 +1679,15 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
lp->ll_n1 = 0;
} else {
// Is number or string.
- lp->ll_n1 = (long)tv_get_number(&var1);
+ lp->ll_n1 = (int)tv_get_number(&var1);
}
tv_clear(&var1);
lp->ll_dict = NULL;
lp->ll_list = lp->ll_tv->vval.v_list;
- lp->ll_li = tv_list_find(lp->ll_list, (int)lp->ll_n1);
- if (lp->ll_li == NULL) {
- if (lp->ll_n1 < 0) {
- lp->ll_n1 = 0;
- lp->ll_li = tv_list_find(lp->ll_list, (int)lp->ll_n1);
- }
- }
+ lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
if (lp->ll_li == NULL) {
tv_clear(&var2);
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n1);
- }
return NULL;
}
@@ -1558,27 +1696,11 @@ char *get_lval(char *const name, typval_T *const rettv, lval_T *const lp, const
// When no index given: "lp->ll_empty2" is true.
// Otherwise "lp->ll_n2" is set to the second index.
if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (long)tv_get_number(&var2); // Is number or string.
+ lp->ll_n2 = (int)tv_get_number(&var2); // Is number or string.
tv_clear(&var2);
- if (lp->ll_n2 < 0) {
- listitem_T *ni = tv_list_find(lp->ll_list, (int)lp->ll_n2);
- if (ni == NULL) {
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n2);
- }
- return NULL;
- }
- lp->ll_n2 = tv_list_idx_of_item(lp->ll_list, ni);
- }
-
- // Check that lp->ll_n2 isn't before lp->ll_n1.
- if (lp->ll_n1 < 0) {
- lp->ll_n1 = tv_list_idx_of_item(lp->ll_list, lp->ll_li);
- }
- if (lp->ll_n2 < lp->ll_n1) {
- if (!quiet) {
- semsg(_(e_listidx), (int64_t)lp->ll_n2);
- }
+ if (tv_list_check_range_index_two(lp->ll_list,
+ &lp->ll_n1, lp->ll_li,
+ &lp->ll_n2, quiet) == FAIL) {
return NULL;
}
}
@@ -1607,11 +1729,10 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
const char *op)
{
int cc;
- listitem_T *ri;
dictitem_T *di;
if (lp->ll_tv == NULL) {
- cc = (char_u)(*endp);
+ cc = (uint8_t)(*endp);
*endp = NUL;
if (lp->ll_blob != NULL) {
if (op != NULL && *op != '=') {
@@ -1627,33 +1748,14 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_n2 = tv_blob_len(lp->ll_blob) - 1;
}
- if (lp->ll_n2 - lp->ll_n1 + 1 != tv_blob_len(rettv->vval.v_blob)) {
- emsg(_("E972: Blob value does not have the right number of bytes"));
+ if (tv_blob_set_range(lp->ll_blob, lp->ll_n1, lp->ll_n2, rettv) == FAIL) {
return;
}
- if (lp->ll_empty2) {
- lp->ll_n2 = tv_blob_len(lp->ll_blob);
- }
-
- for (int il = (int)lp->ll_n1, ir = 0; il <= (int)lp->ll_n2; il++) {
- tv_blob_set(lp->ll_blob, il, tv_blob_get(rettv->vval.v_blob, ir++));
- }
} else {
bool error = false;
const char val = (char)tv_get_number_chk(rettv, &error);
if (!error) {
- garray_T *const gap = &lp->ll_blob->bv_ga;
-
- // Allow for appending a byte. Setting a byte beyond
- // the end is an error otherwise.
- if (lp->ll_n1 < gap->ga_len || lp->ll_n1 == gap->ga_len) {
- ga_grow(&lp->ll_blob->bv_ga, 1);
- tv_blob_set(lp->ll_blob, (int)lp->ll_n1, (uint8_t)val);
- if (lp->ll_n1 == gap->ga_len) {
- gap->ga_len++;
- }
- }
- // error for invalid range was already given in get_lval()
+ tv_blob_set_append(lp->ll_blob, lp->ll_n1, (uint8_t)val);
}
}
} else if (op != NULL && *op != '=') {
@@ -1667,8 +1769,8 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
// handle +=, -=, *=, /=, %= and .=
di = NULL;
- if (get_var_tv(lp->ll_name, (int)strlen(lp->ll_name),
- &tv, &di, true, false) == OK) {
+ if (eval_variable(lp->ll_name, (int)strlen(lp->ll_name),
+ &tv, &di, true, false) == OK) {
if ((di == NULL
|| (!var_check_ro(di->di_flags, lp->ll_name, TV_CSTRING)
&& !tv_check_lock(&di->di_tv, lp->ll_name, TV_CSTRING)))
@@ -1687,60 +1789,13 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
lp->ll_name, TV_CSTRING)) {
// Skip
} else if (lp->ll_range) {
- listitem_T *ll_li = lp->ll_li;
- int ll_n1 = (int)lp->ll_n1;
-
if (is_const) {
emsg(_("E996: Cannot lock a range"));
return;
}
- // Check whether any of the list items is locked
- for (ri = tv_list_first(rettv->vval.v_list);
- ri != NULL && ll_li != NULL;) {
- if (value_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, lp->ll_name,
- TV_CSTRING)) {
- return;
- }
- ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
- if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) {
- break;
- }
- ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li);
- ll_n1++;
- }
-
- // Assign the List values to the list items.
- for (ri = tv_list_first(rettv->vval.v_list); ri != NULL;) {
- if (op != NULL && *op != '=') {
- eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), op);
- } else {
- tv_clear(TV_LIST_ITEM_TV(lp->ll_li));
- tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li));
- }
- ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri);
- if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) {
- break;
- }
- assert(lp->ll_li != NULL);
- if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) {
- // Need to add an empty item.
- tv_list_append_number(lp->ll_list, 0);
- // ll_li may have become invalid after append, don’t use it.
- lp->ll_li = tv_list_last(lp->ll_list); // Valid again.
- } else {
- lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- }
- lp->ll_n1++;
- }
- if (ri != NULL) {
- emsg(_("E710: List value has more items than target"));
- } else if (lp->ll_empty2
- ? (lp->ll_li != NULL
- && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL)
- : lp->ll_n1 != lp->ll_n2) {
- emsg(_("E711: List value has not enough items"));
- }
+ (void)tv_list_assign_range(lp->ll_list, rettv->vval.v_list,
+ lp->ll_n1, lp->ll_n2, lp->ll_empty2, op, lp->ll_name);
} else {
typval_T oldtv = TV_INITIAL_VALUE;
dict_T *dict = lp->ll_dict;
@@ -1762,7 +1817,7 @@ void set_var_lval(lval_T *lp, char *endp, typval_T *rettv, int copy, const bool
}
// Need to add an item to the Dictionary.
- di = tv_dict_item_alloc((const char *)lp->ll_newkey);
+ di = tv_dict_item_alloc(lp->ll_newkey);
if (tv_dict_add(lp->ll_tv->vval.v_dict, di) == FAIL) {
xfree(di);
return;
@@ -1798,7 +1853,7 @@ notify:
} else {
dictitem_T *di_ = lp->ll_di;
assert(di_->di_key != NULL);
- tv_dict_watcher_notify(dict, (char *)di_->di_key, lp->ll_tv, &oldtv);
+ tv_dict_watcher_notify(dict, di_->di_key, lp->ll_tv, &oldtv);
tv_clear(&oldtv);
}
}
@@ -1811,22 +1866,23 @@ notify:
/// @param[out] *errp set to true for an error, false otherwise;
///
/// @return a pointer that holds the info. Null when there is an error.
-void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
+void *eval_for_line(const char *arg, bool *errp, exarg_T *eap, evalarg_T *const evalarg)
{
forinfo_T *fi = xcalloc(1, sizeof(forinfo_T));
- const char *expr;
typval_T tv;
list_T *l;
+ const bool skip = !(evalarg->eval_flags & EVAL_EVALUATE);
*errp = true; // Default: there is an error.
- expr = skip_var_list((char *)arg, &fi->fi_varcount, &fi->fi_semicolon);
+ const char *expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon);
if (expr == NULL) {
return fi;
}
expr = skipwhite(expr);
- if (expr[0] != 'i' || expr[1] != 'n' || !ascii_iswhite(expr[2])) {
+ if (expr[0] != 'i' || expr[1] != 'n'
+ || !(expr[2] == NUL || ascii_iswhite(expr[2]))) {
emsg(_("E690: Missing \"in\" after :for"));
return fi;
}
@@ -1834,7 +1890,8 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
if (skip) {
emsg_skip++;
}
- if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) {
+ expr = skipwhite(expr + 2);
+ if (eval0((char *)expr, &tv, eap, evalarg) == OK) {
*errp = false;
if (!skip) {
if (tv.v_type == VAR_LIST) {
@@ -1856,7 +1913,7 @@ void *eval_for_line(const char *arg, bool *errp, char **nextcmdp, int skip)
// Make a copy, so that the iteration still works when the
// blob is changed.
- tv_blob_copy(&tv, &btv);
+ tv_blob_copy(tv.vval.v_blob, &btv);
fi->fi_blob = btv.vval.v_blob;
}
tv_clear(&tv);
@@ -1909,7 +1966,7 @@ bool next_for_item(void *fi_void, char *arg)
typval_T tv;
tv.v_type = VAR_STRING;
tv.v_lock = VAR_FIXED;
- tv.vval.v_string = xstrnsave(fi->fi_string + fi->fi_byte_idx, (size_t)len);
+ tv.vval.v_string = xmemdupz(fi->fi_string + fi->fi_byte_idx, (size_t)len);
fi->fi_byte_idx += len;
const int result
= ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK;
@@ -1949,14 +2006,12 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
FUNC_ATTR_NONNULL_ALL
{
bool got_eq = false;
- int c;
- char *p;
if (cmdidx == CMD_let || cmdidx == CMD_const) {
xp->xp_context = EXPAND_USER_VARS;
if (strpbrk(arg, "\"'+-*/%.=!?~|&$([<>,#") == NULL) {
// ":let var1 var2 ...": find last space.
- for (p = arg + strlen(arg); p >= arg;) {
+ for (char *p = arg + strlen(arg); p >= arg;) {
xp->xp_pattern = p;
MB_PTR_BACK(arg, p);
if (ascii_iswhite(*p)) {
@@ -1970,7 +2025,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
: EXPAND_EXPRESSION;
}
while ((xp->xp_pattern = strpbrk(arg, "\"'+-*/%.=!?~|&$([<>,#")) != NULL) {
- c = (uint8_t)(*xp->xp_pattern);
+ int c = (uint8_t)(*xp->xp_pattern);
if (c == '&') {
c = (uint8_t)xp->xp_pattern[1];
if (c == '&') {
@@ -2027,7 +2082,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
}
arg = xp->xp_pattern;
if (*arg != NUL) {
- while ((c = (char_u)(*++arg)) != NUL && (c == ' ' || c == '\t')) {}
+ while ((c = (uint8_t)(*++arg)) != NUL && (c == ' ' || c == '\t')) {}
}
}
@@ -2037,7 +2092,7 @@ void set_context_for_expression(expand_T *xp, char *arg, cmdidx_T cmdidx)
|| cmdidx == CMD_echon
|| cmdidx == CMD_echomsg)
&& xp->xp_context == EXPAND_EXPRESSION) {
- for (;;) {
+ while (true) {
char *const n = skiptowhite(arg);
if (n == arg || ascii_iswhite_or_nul(*skipwhite(n))) {
@@ -2181,11 +2236,11 @@ int pattern_match(const char *pat, const char *text, bool ic)
// avoid 'l' flag in 'cpoptions'
char *save_cpo = p_cpo;
- p_cpo = empty_option;
- regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING);
+ p_cpo = empty_string_option;
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
regmatch.rm_ic = ic;
- matches = vim_regexec_nl(&regmatch, (char *)text, (colnr_T)0);
+ matches = vim_regexec_nl(&regmatch, text, 0);
vim_regfree(regmatch.regprog);
}
p_cpo = save_cpo;
@@ -2199,21 +2254,24 @@ int pattern_match(const char *pat, const char *text, bool ic)
/// @param basetv "expr" for "expr->name(arg)"
///
/// @return OK or FAIL.
-static int eval_func(char **const arg, char *const name, const int name_len, typval_T *const rettv,
- const bool evaluate, typval_T *const basetv)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+static int eval_func(char **const arg, evalarg_T *const evalarg, char *const name,
+ const int name_len, typval_T *const rettv, const int flags,
+ typval_T *const basetv)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5)
{
+ const bool evaluate = flags & EVAL_EVALUATE;
char *s = name;
int len = name_len;
+ bool found_var = false;
if (!evaluate) {
- check_vars((const char *)s, (size_t)len);
+ check_vars(s, (size_t)len);
}
// If "s" is the name of a variable of type VAR_FUNC
// use its contents.
partial_T *partial;
- s = deref_func_name((const char *)s, &len, &partial, !evaluate);
+ s = deref_func_name(s, &len, &partial, !evaluate, &found_var);
// Need to make a copy, in case evaluating the arguments makes
// the name invalid.
@@ -2226,7 +2284,8 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
funcexe.fe_evaluate = evaluate;
funcexe.fe_partial = partial;
funcexe.fe_basetv = basetv;
- int ret = get_func_tv(s, len, rettv, arg, &funcexe);
+ funcexe.fe_found_var = found_var;
+ int ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe);
xfree(s);
@@ -2250,6 +2309,26 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
return ret;
}
+/// After using "evalarg" filled from "eap": free the memory.
+void clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
+{
+ if (evalarg != NULL) {
+ if (evalarg->eval_tofree != NULL) {
+ if (eap != NULL) {
+ // We may need to keep the original command line, e.g. for
+ // ":let" it has the variable names. But we may also need the
+ // new one, "nextcmd" points into it. Keep both.
+ xfree(eap->cmdline_tofree);
+ eap->cmdline_tofree = *eap->cmdlinep;
+ *eap->cmdlinep = evalarg->eval_tofree;
+ } else {
+ xfree(evalarg->eval_tofree);
+ }
+ evalarg->eval_tofree = NULL;
+ }
+ }
+}
+
/// The "evaluate" argument: When false, the argument is only parsed but not
/// executed. The function may return OK, but the rettv will be of type
/// VAR_UNKNOWN. The function still returns FAIL for a syntax error.
@@ -2259,17 +2338,17 @@ static int eval_func(char **const arg, char *const name, const int name_len, typ
/// Put the result in "rettv" when returning OK and "evaluate" is true.
/// Note: "rettv.v_lock" is not set.
///
+/// @param evalarg can be NULL, &EVALARG_EVALUATE or a pointer.
+///
/// @return OK or FAIL.
-int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
+int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
{
- int ret;
- char *p;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
bool end_error = false;
- p = skipwhite(arg);
- ret = eval1(&p, rettv, evaluate);
+ char *p = skipwhite(arg);
+ int ret = eval1(&p, rettv, evalarg);
if (ret != FAIL) {
end_error = !ends_excmd(*p);
@@ -2282,7 +2361,8 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
// been cancelled due to an aborting error, an interrupt, or an
// exception, or we already gave a more specific error.
// Also check called_emsg for when using assert_fails().
- if (!aborting() && did_emsg == did_emsg_before
+ if (!aborting()
+ && did_emsg == did_emsg_before
&& called_emsg == called_emsg_before) {
if (end_error) {
semsg(_(e_trailing_arg), p);
@@ -2290,10 +2370,21 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
semsg(_(e_invexpr2), arg);
}
}
- ret = FAIL;
+
+ if (eap != NULL && p != NULL) {
+ // Some of the expression may not have been consumed.
+ // Only execute a next command if it cannot be a "||" operator.
+ // The next command may be "catch".
+ char *nextcmd = check_nextcmd(p);
+ if (nextcmd != NULL && *nextcmd != '|') {
+ eap->nextcmd = nextcmd;
+ }
+ }
+ return FAIL;
}
- if (nextcmd != NULL) {
- *nextcmd = check_nextcmd(p);
+
+ if (eap != NULL) {
+ eap->nextcmd = check_nextcmd(p);
}
return ret;
@@ -2301,6 +2392,7 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
/// Handle top level expression:
/// expr2 ? expr1 : expr1
+/// expr2 ?? expr1
///
/// "arg" must point to the first non-white of the expression.
/// "arg" is advanced to the next non-white after the recognized expression.
@@ -2308,55 +2400,89 @@ int eval0(char *arg, typval_T *rettv, char **nextcmd, int evaluate)
/// Note: "rettv.v_lock" is not set.
///
/// @return OK or FAIL.
-int eval1(char **arg, typval_T *rettv, int evaluate)
+int eval1(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- bool result;
- typval_T var2;
-
// Get the first variable.
- if (eval2(arg, rettv, evaluate) == FAIL) {
+ if (eval2(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- if ((*arg)[0] == '?') {
- result = false;
+ char *p = *arg;
+ if (*p == '?') {
+ const bool op_falsy = p[1] == '?';
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = false;
if (evaluate) {
bool error = false;
- if (tv_get_number_chk(rettv, &error) != 0) {
+ if (op_falsy) {
+ result = tv2bool(rettv);
+ } else if (tv_get_number_chk(rettv, &error) != 0) {
result = true;
}
- tv_clear(rettv);
+ if (error || !op_falsy || !result) {
+ tv_clear(rettv);
+ }
if (error) {
return FAIL;
}
}
- // Get the second variable.
+ // Get the second variable. Recursive!
+ if (op_falsy) {
+ (*arg)++;
+ }
*arg = skipwhite(*arg + 1);
- if (eval1(arg, rettv, evaluate && result) == FAIL) { // recursive!
+ evalarg_used->eval_flags = (op_falsy ? !result : result)
+ ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval1(arg, &var2, evalarg_used) == FAIL) {
+ evalarg_used->eval_flags = orig_flags;
return FAIL;
}
+ if (!op_falsy || !result) {
+ *rettv = var2;
+ }
- // Check for the ":".
- if ((*arg)[0] != ':') {
- emsg(_("E109: Missing ':' after '?'"));
- if (evaluate && result) {
- tv_clear(rettv);
+ if (!op_falsy) {
+ // Check for the ":".
+ p = *arg;
+ if (*p != ':') {
+ emsg(_("E109: Missing ':' after '?'"));
+ if (evaluate && result) {
+ tv_clear(rettv);
+ }
+ evalarg_used->eval_flags = orig_flags;
+ return FAIL;
}
- return FAIL;
- }
- // Get the third variable.
- *arg = skipwhite(*arg + 1);
- if (eval1(arg, &var2, evaluate && !result) == FAIL) { // Recursive!
- if (evaluate && result) {
- tv_clear(rettv);
+ // Get the third variable. Recursive!
+ *arg = skipwhite(*arg + 1);
+ evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ if (eval1(arg, &var2, evalarg_used) == FAIL) {
+ if (evaluate && result) {
+ tv_clear(rettv);
+ }
+ evalarg_used->eval_flags = orig_flags;
+ return FAIL;
+ }
+ if (evaluate && !result) {
+ *rettv = var2;
}
- return FAIL;
}
- if (evaluate && !result) {
- *rettv = var2;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2370,21 +2496,29 @@ int eval1(char **arg, typval_T *rettv, int evaluate)
/// "arg" is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval2(char **arg, typval_T *rettv, int evaluate)
+static int eval2(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- bool error = false;
-
// Get the first variable.
- if (eval3(arg, rettv, evaluate) == FAIL) {
+ if (eval3(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- // Repeat until there is no following "||".
- bool first = true;
- bool result = false;
- while ((*arg)[0] == '|' && (*arg)[1] == '|') {
- if (evaluate && first) {
+ // Handle the "||" operator.
+ char *p = *arg;
+ if (p[0] == '|' && p[1] == '|') {
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = false;
+
+ if (evaluate) {
+ bool error = false;
if (tv_get_number_chk(rettv, &error) != 0) {
result = true;
}
@@ -2392,28 +2526,41 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
if (error) {
return FAIL;
}
- first = false;
}
- // Get the second variable.
- *arg = skipwhite(*arg + 2);
- if (eval3(arg, &var2, evaluate && !result) == FAIL) {
- return FAIL;
- }
+ // Repeat until there is no following "||".
+ while (p[0] == '|' && p[1] == '|') {
+ // Get the second variable.
+ *arg = skipwhite(*arg + 2);
+ evalarg_used->eval_flags = !result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval3(arg, &var2, evalarg_used) == FAIL) {
+ return FAIL;
+ }
- // Compute the result.
- if (evaluate && !result) {
- if (tv_get_number_chk(&var2, &error) != 0) {
- result = true;
+ // Compute the result.
+ if (evaluate && !result) {
+ bool error = false;
+ if (tv_get_number_chk(&var2, &error) != 0) {
+ result = true;
+ }
+ tv_clear(&var2);
+ if (error) {
+ return FAIL;
+ }
}
- tv_clear(&var2);
- if (error) {
- return FAIL;
+ if (evaluate) {
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
+
+ p = *arg;
}
- if (evaluate) {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2427,21 +2574,29 @@ static int eval2(char **arg, typval_T *rettv, int evaluate)
/// `arg` is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval3(char **arg, typval_T *rettv, int evaluate)
+static int eval3(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- bool error = false;
-
// Get the first variable.
- if (eval4(arg, rettv, evaluate) == FAIL) {
+ if (eval4(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- // Repeat until there is no following "&&".
- bool first = true;
- bool result = true;
- while ((*arg)[0] == '&' && (*arg)[1] == '&') {
- if (evaluate && first) {
+ char *p = *arg;
+ // Handle the "&&" operator.
+ if (p[0] == '&' && p[1] == '&') {
+ evalarg_T *evalarg_used = evalarg;
+ evalarg_T local_evalarg;
+ if (evalarg == NULL) {
+ local_evalarg = (evalarg_T){ .eval_flags = 0 };
+ evalarg_used = &local_evalarg;
+ }
+ const int orig_flags = evalarg_used->eval_flags;
+ const bool evaluate = evalarg_used->eval_flags & EVAL_EVALUATE;
+
+ bool result = true;
+
+ if (evaluate) {
+ bool error = false;
if (tv_get_number_chk(rettv, &error) == 0) {
result = false;
}
@@ -2449,28 +2604,41 @@ static int eval3(char **arg, typval_T *rettv, int evaluate)
if (error) {
return FAIL;
}
- first = false;
}
- // Get the second variable.
- *arg = skipwhite(*arg + 2);
- if (eval4(arg, &var2, evaluate && result) == FAIL) {
- return FAIL;
- }
+ // Repeat until there is no following "&&".
+ while (p[0] == '&' && p[1] == '&') {
+ // Get the second variable.
+ *arg = skipwhite(*arg + 2);
+ evalarg_used->eval_flags = result ? orig_flags : (orig_flags & ~EVAL_EVALUATE);
+ typval_T var2;
+ if (eval4(arg, &var2, evalarg_used) == FAIL) {
+ return FAIL;
+ }
- // Compute the result.
- if (evaluate && result) {
- if (tv_get_number_chk(&var2, &error) == 0) {
- result = false;
+ // Compute the result.
+ if (evaluate && result) {
+ bool error = false;
+ if (tv_get_number_chk(&var2, &error) == 0) {
+ result = false;
+ }
+ tv_clear(&var2);
+ if (error) {
+ return FAIL;
+ }
}
- tv_clear(&var2);
- if (error) {
- return FAIL;
+ if (evaluate) {
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = result;
}
+
+ p = *arg;
}
- if (evaluate) {
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = result;
+
+ if (evalarg == NULL) {
+ clear_evalarg(&local_evalarg, NULL);
+ } else {
+ evalarg->eval_flags = orig_flags;
}
}
@@ -2493,20 +2661,18 @@ static int eval3(char **arg, typval_T *rettv, int evaluate)
/// "arg" is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval4(char **arg, typval_T *rettv, int evaluate)
+static int eval4(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
typval_T var2;
- char *p;
exprtype_T type = EXPR_UNKNOWN;
int len = 2;
- bool ic;
// Get the first variable.
- if (eval5(arg, rettv, evaluate) == FAIL) {
+ if (eval5(arg, rettv, evalarg) == FAIL) {
return FAIL;
}
- p = *arg;
+ char *p = *arg;
switch (p[0]) {
case '=':
if (p[1] == '=') {
@@ -2552,6 +2718,7 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
// If there is a comparative operator, use it.
if (type != EXPR_UNKNOWN) {
+ bool ic;
// extra question mark appended: ignore case
if (p[len] == '?') {
ic = true;
@@ -2565,11 +2732,11 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
// Get the second variable.
*arg = skipwhite(p + len);
- if (eval5(arg, &var2, evaluate) == FAIL) {
+ if (eval5(arg, &var2, evalarg) == FAIL) {
tv_clear(rettv);
return FAIL;
}
- if (evaluate) {
+ if (evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE)) {
const int ret = typval_compare(rettv, &var2, type, ic);
tv_clear(&var2);
@@ -2580,8 +2747,41 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
return OK;
}
+/// Make a copy of blob "tv1" and append blob "tv2".
+static void eval_addblob(typval_T *tv1, typval_T *tv2)
+{
+ const blob_T *const b1 = tv1->vval.v_blob;
+ const blob_T *const b2 = tv2->vval.v_blob;
+ blob_T *const b = tv_blob_alloc();
+
+ for (int i = 0; i < tv_blob_len(b1); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b1, i));
+ }
+ for (int i = 0; i < tv_blob_len(b2); i++) {
+ ga_append(&b->bv_ga, tv_blob_get(b2, i));
+ }
+
+ tv_clear(tv1);
+ tv_blob_set_ret(tv1, b);
+}
+
+/// Make a copy of list "tv1" and append list "tv2".
+static int eval_addlist(typval_T *tv1, typval_T *tv2)
+{
+ typval_T var3;
+ // Concatenate Lists.
+ if (tv_list_concat(tv1->vval.v_list, tv2->vval.v_list, &var3) == FAIL) {
+ tv_clear(tv1);
+ tv_clear(tv2);
+ return FAIL;
+ }
+ tv_clear(tv1);
+ *tv1 = var3;
+ return OK;
+}
+
/// Handle fourth level expression:
-/// + number addition
+/// + number addition, concatenation of list or blob
/// - number subtraction
/// . string concatenation
/// .. string concatenation
@@ -2590,29 +2790,24 @@ static int eval4(char **arg, typval_T *rettv, int evaluate)
/// `arg` is advanced to the next non-white after the recognized expression.
///
/// @return OK or FAIL.
-static int eval5(char **arg, typval_T *rettv, int evaluate)
+static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
- typval_T var2;
- typval_T var3;
- int op;
- varnumber_T n1, n2;
- float_T f1 = 0, f2 = 0;
- char *p;
-
// Get the first variable.
- if (eval6(arg, rettv, evaluate, false) == FAIL) {
+ if (eval6(arg, rettv, evalarg, false) == FAIL) {
return FAIL;
}
// Repeat computing, until no '+', '-' or '.' is following.
- for (;;) {
- op = (char_u)(**arg);
- if (op != '+' && op != '-' && op != '.') {
+ while (true) {
+ int op = (uint8_t)(**arg);
+ bool concat = op == '.';
+ if (op != '+' && op != '-' && !concat) {
break;
}
+ const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
if ((op != '+' || (rettv->v_type != VAR_LIST && rettv->v_type != VAR_BLOB))
- && (op == '.' || rettv->v_type != VAR_FLOAT)) {
+ && (op == '.' || rettv->v_type != VAR_FLOAT) && evaluate) {
// For "list + ...", an illegal use of the first operand as
// a number cannot be determined before evaluating the 2nd
// operand: if this is also a list, all is ok.
@@ -2620,7 +2815,7 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
// we know that the first operand needs to be a string or number
// without evaluating the 2nd operand. So check before to avoid
// side effects after an error.
- if (evaluate && !tv_check_str(rettv)) {
+ if ((op == '.' && !tv_check_str(rettv)) || (op != '.' && !tv_check_num(rettv))) {
tv_clear(rettv);
return FAIL;
}
@@ -2631,7 +2826,8 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
(*arg)++;
}
*arg = skipwhite(*arg + 1);
- if (eval6(arg, &var2, evaluate, op == '.') == FAIL) {
+ typval_T var2;
+ if (eval6(arg, &var2, evalarg, op == '.') == FAIL) {
tv_clear(rettv);
return FAIL;
}
@@ -2649,38 +2845,20 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
tv_clear(&var2);
return FAIL;
}
- p = concat_str(s1, s2);
+ char *p = concat_str(s1, s2);
tv_clear(rettv);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = p;
- } else if (op == '+' && rettv->v_type == VAR_BLOB
- && var2.v_type == VAR_BLOB) {
- const blob_T *const b1 = rettv->vval.v_blob;
- const blob_T *const b2 = var2.vval.v_blob;
- blob_T *const b = tv_blob_alloc();
-
- for (int i = 0; i < tv_blob_len(b1); i++) {
- ga_append(&b->bv_ga, tv_blob_get(b1, i));
- }
- for (int i = 0; i < tv_blob_len(b2); i++) {
- ga_append(&b->bv_ga, tv_blob_get(b2, i));
- }
-
- tv_clear(rettv);
- tv_blob_set_ret(rettv, b);
- } else if (op == '+' && rettv->v_type == VAR_LIST
- && var2.v_type == VAR_LIST) {
- // Concatenate Lists.
- if (tv_list_concat(rettv->vval.v_list, var2.vval.v_list, &var3)
- == FAIL) {
- tv_clear(rettv);
- tv_clear(&var2);
+ } else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) {
+ eval_addblob(rettv, &var2);
+ } else if (op == '+' && rettv->v_type == VAR_LIST && var2.v_type == VAR_LIST) {
+ if (eval_addlist(rettv, &var2) == FAIL) {
return FAIL;
}
- tv_clear(rettv);
- *rettv = var3;
} else {
bool error = false;
+ varnumber_T n1, n2;
+ float_T f1 = 0, f2 = 0;
if (rettv->v_type == VAR_FLOAT) {
f1 = rettv->vval.v_float;
@@ -2750,32 +2928,30 @@ static int eval5(char **arg, typval_T *rettv, int evaluate)
/// expression. Is advanced to the next non-whitespace
/// character after the recognized expression.
/// @param[out] rettv Location where result is saved.
-/// @param[in] evaluate If not true, rettv is not populated.
/// @param[in] want_string True if "." is string_concatenation, otherwise
/// float
/// @return OK or FAIL.
-static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
+static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string)
FUNC_ATTR_NO_SANITIZE_UNDEFINED
{
- typval_T var2;
- int op;
- varnumber_T n1, n2;
bool use_float = false;
- float_T f1 = 0, f2 = 0;
- bool error = false;
// Get the first variable.
- if (eval7(arg, rettv, evaluate, want_string) == FAIL) {
+ if (eval7(arg, rettv, evalarg, want_string) == FAIL) {
return FAIL;
}
// Repeat computing, until no '*', '/' or '%' is following.
- for (;;) {
- op = (char_u)(**arg);
+ while (true) {
+ int op = (uint8_t)(**arg);
if (op != '*' && op != '/' && op != '%') {
break;
}
+ varnumber_T n1, n2;
+ float_T f1 = 0, f2 = 0;
+ bool error = false;
+ const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
if (evaluate) {
if (rettv->v_type == VAR_FLOAT) {
f1 = rettv->vval.v_float;
@@ -2794,7 +2970,8 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
// Get the second variable.
*arg = skipwhite(*arg + 1);
- if (eval7(arg, &var2, evaluate, false) == FAIL) {
+ typval_T var2;
+ if (eval7(arg, &var2, evalarg, false) == FAIL) {
return FAIL;
}
@@ -2885,8 +3062,9 @@ static int eval6(char **arg, typval_T *rettv, int evaluate, int want_string)
/// @param want_string after "." operator
///
/// @return OK or FAIL.
-static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
+static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
int ret = OK;
static int recurse = 0;
@@ -2927,29 +3105,35 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
case '7':
case '8':
case '9':
- ret = get_number_tv(arg, rettv, evaluate, want_string);
+ ret = eval_number(arg, rettv, evaluate, want_string);
+
+ // Apply prefixed "-" and "+" now. Matters especially when
+ // "->" follows.
+ if (ret == OK && evaluate && end_leader > start_leader) {
+ ret = eval7_leader(rettv, true, start_leader, &end_leader);
+ }
break;
// String constant: "string".
case '"':
- ret = get_string_tv(arg, rettv, evaluate);
+ ret = eval_string(arg, rettv, evaluate, false);
break;
// Literal string constant: 'str''ing'.
case '\'':
- ret = get_lit_string_tv(arg, rettv, evaluate);
+ ret = eval_lit_string(arg, rettv, evaluate, false);
break;
// List: [expr, expr]
case '[':
- ret = get_list_tv(arg, rettv, evaluate);
+ ret = eval_list(arg, rettv, evalarg);
break;
// Dictionary: #{key: val, key: val}
case '#':
if ((*arg)[1] == '{') {
(*arg)++;
- ret = eval_dict(arg, rettv, evaluate, true);
+ ret = eval_dict(arg, rettv, evalarg, true);
} else {
ret = NOTDONE;
}
@@ -2958,19 +3142,24 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Lambda: {arg, arg -> expr}
// Dictionary: {'key': val, 'key': val}
case '{':
- ret = get_lambda_tv(arg, rettv, evaluate);
+ ret = get_lambda_tv(arg, rettv, evalarg);
if (ret == NOTDONE) {
- ret = eval_dict(arg, rettv, evaluate, false);
+ ret = eval_dict(arg, rettv, evalarg, false);
}
break;
// Option value: &name
case '&':
- ret = get_option_tv((const char **)arg, rettv, evaluate);
+ ret = eval_option((const char **)arg, rettv, evaluate);
break;
// Environment variable: $VAR.
+ // Interpolated string: $"string" or $'string'.
case '$':
- ret = get_env_tv(arg, rettv, evaluate);
+ if ((*arg)[1] == '"' || (*arg)[1] == '\'') {
+ ret = eval_interp_string(arg, rettv, evaluate);
+ } else {
+ ret = eval_env_var(arg, rettv, evaluate);
+ }
break;
// Register contents: @r.
@@ -2988,7 +3177,8 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// nested expression: (expression).
case '(':
*arg = skipwhite(*arg + 1);
- ret = eval1(arg, rettv, evaluate); // recursive!
+
+ ret = eval1(arg, rettv, evalarg); // recursive!
if (**arg == ')') {
(*arg)++;
} else if (ret == OK) {
@@ -3016,12 +3206,17 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
if (len <= 0) {
ret = FAIL;
} else {
- if (**arg == '(') { // recursive!
- ret = eval_func(arg, s, len, rettv, evaluate, NULL);
+ const int flags = evalarg == NULL ? 0 : evalarg->eval_flags;
+ if (*skipwhite(*arg) == '(') {
+ // "name(..." recursive!
+ *arg = skipwhite(*arg);
+ ret = eval_func(arg, evalarg, s, len, rettv, flags, NULL);
} else if (evaluate) {
- ret = get_var_tv((const char *)s, len, rettv, NULL, true, false);
+ // get value of variable
+ ret = eval_variable(s, len, rettv, NULL, true, false);
} else {
- check_vars((const char *)s, (size_t)len);
+ // skip the name
+ check_vars(s, (size_t)len);
ret = OK;
}
}
@@ -3033,13 +3228,12 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
// Handle following '[', '(' and '.' for expr[expr], expr.name,
// expr(expr), expr->name(expr)
if (ret == OK) {
- ret = handle_subscript((const char **)arg, rettv, evaluate, true,
- (char *)start_leader, &end_leader);
+ ret = handle_subscript((const char **)arg, rettv, evalarg, true);
}
// Apply logical NOT and unary '-', from right to left, ignore '+'.
if (ret == OK && evaluate && end_leader > start_leader) {
- ret = eval7_leader(rettv, (char *)start_leader, &end_leader);
+ ret = eval7_leader(rettv, false, start_leader, &end_leader);
}
recurse--;
@@ -3049,12 +3243,14 @@ static int eval7(char **arg, typval_T *rettv, int evaluate, int want_string)
/// Apply the leading "!" and "-" before an eval7 expression to "rettv".
/// Adjusts "end_leaderp" until it is at "start_leader".
///
+/// @param numeric_only if true only handle "+" and "-".
+///
/// @return OK on success, FAIL on failure.
-static int eval7_leader(typval_T *const rettv, const char *const start_leader,
- const char **const end_leaderp)
+static int eval7_leader(typval_T *const rettv, const bool numeric_only,
+ const char *const start_leader, const char **const end_leaderp)
FUNC_ATTR_NONNULL_ALL
{
- const char *end_leader = (char *)(*end_leaderp);
+ const char *end_leader = *end_leaderp;
int ret = OK;
bool error = false;
varnumber_T val = 0;
@@ -3072,6 +3268,10 @@ static int eval7_leader(typval_T *const rettv, const char *const start_leader,
while (end_leader > start_leader) {
end_leader--;
if (*end_leader == '!') {
+ if (numeric_only) {
+ end_leader++;
+ break;
+ }
if (rettv->v_type == VAR_FLOAT) {
f = !(bool)f;
} else {
@@ -3104,15 +3304,16 @@ static int eval7_leader(typval_T *const rettv, const char *const start_leader,
/// to the name of the Lua function to call (after the
/// "v:lua." prefix).
/// @return OK on success, FAIL on failure.
-static int call_func_rettv(char **const arg, typval_T *const rettv, const bool evaluate,
- dict_T *const selfdict, typval_T *const basetv,
+static int call_func_rettv(char **const arg, evalarg_T *const evalarg, typval_T *const rettv,
+ const bool evaluate, dict_T *const selfdict, typval_T *const basetv,
const char *const lua_funcname)
- FUNC_ATTR_NONNULL_ARG(1, 2)
+ FUNC_ATTR_NONNULL_ARG(1, 3)
{
partial_T *pt = NULL;
typval_T functv;
const char *funcname;
bool is_lua = false;
+ int ret;
// need to copy the funcref so that we can clear rettv
if (evaluate) {
@@ -3126,6 +3327,11 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
funcname = is_lua ? lua_funcname : partial_name(pt);
} else {
funcname = functv.vval.v_string;
+ if (funcname == NULL || *funcname == NUL) {
+ emsg(_(e_empty_function_name));
+ ret = FAIL;
+ goto theend;
+ }
}
} else {
funcname = "";
@@ -3138,9 +3344,10 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
funcexe.fe_partial = pt;
funcexe.fe_selfdict = selfdict;
funcexe.fe_basetv = basetv;
- const int ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv,
- arg, &funcexe);
+ ret = get_func_tv(funcname, is_lua ? (int)(*arg - funcname) : -1, rettv,
+ arg, evalarg, &funcexe);
+theend:
// Clear the funcref afterwards, so that deleting it while
// evaluating the arguments is possible (see test55).
if (evaluate) {
@@ -3158,16 +3365,17 @@ static int call_func_rettv(char **const arg, typval_T *const rettv, const bool e
/// @return FAIL or OK.
///
/// @note "*arg" is advanced to after the ')'.
-static int eval_lambda(char **const arg, typval_T *const rettv, const bool evaluate,
+static int eval_lambda(char **const arg, typval_T *const rettv, evalarg_T *const evalarg,
const bool verbose)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
// Skip over the ->.
*arg += 2;
typval_T base = *rettv;
rettv->v_type = VAR_UNKNOWN;
- int ret = get_lambda_tv(arg, rettv, evaluate);
+ int ret = get_lambda_tv(arg, rettv, evalarg);
if (ret != OK) {
return FAIL;
} else if (**arg != '(') {
@@ -3181,7 +3389,7 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu
tv_clear(rettv);
ret = FAIL;
} else {
- ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, NULL);
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, NULL);
}
// Clear the funcref afterwards, so that deleting it while
@@ -3198,10 +3406,12 @@ static int eval_lambda(char **const arg, typval_T *const rettv, const bool evalu
/// @param *arg points to the '-'.
///
/// @return FAIL or OK. "*arg" is advanced to after the ')'.
-static int eval_method(char **const arg, typval_T *const rettv, const bool evaluate,
+static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const evalarg,
const bool verbose)
- FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
+
// Skip over the ->.
*arg += 2;
typval_T base = *rettv;
@@ -3213,7 +3423,7 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
char *lua_funcname = NULL;
if (strncmp(name, "v:lua.", 6) == 0) {
lua_funcname = name + 6;
- *arg = (char *)skip_luafunc_name((const char *)lua_funcname);
+ *arg = (char *)skip_luafunc_name(lua_funcname);
*arg = skipwhite(*arg); // to detect trailing whitespace later
len = (int)(*arg - lua_funcname);
} else {
@@ -3251,9 +3461,9 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
rettv->vval.v_partial = vvlua_partial;
rettv->vval.v_partial->pt_refcount++;
}
- ret = call_func_rettv(arg, rettv, evaluate, NULL, &base, lua_funcname);
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
} else {
- ret = eval_func(arg, name, len, rettv, evaluate, &base);
+ ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base);
}
}
@@ -3272,44 +3482,17 @@ static int eval_method(char **const arg, typval_T *const rettv, const bool evalu
/// @param verbose give error messages
///
/// @returns FAIL or OK. "*arg" is advanced to after the ']'.
-static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
+static int eval_index(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool verbose)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
bool empty1 = false;
bool empty2 = false;
- long n1, n2 = 0;
- ptrdiff_t len = -1;
- int range = false;
- char *key = NULL;
+ bool range = false;
+ const char *key = NULL;
+ ptrdiff_t keylen = -1;
- switch (rettv->v_type) {
- case VAR_FUNC:
- case VAR_PARTIAL:
- if (verbose) {
- emsg(_("E695: Cannot index a Funcref"));
- }
- return FAIL;
- case VAR_FLOAT:
- if (verbose) {
- emsg(_(e_float_as_string));
- }
- return FAIL;
- case VAR_BOOL:
- case VAR_SPECIAL:
- if (verbose) {
- emsg(_("E909: Cannot index a special variable"));
- }
+ if (check_can_index(rettv, evaluate, verbose) == FAIL) {
return FAIL;
- case VAR_UNKNOWN:
- if (evaluate) {
- return FAIL;
- }
- FALLTHROUGH;
- case VAR_STRING:
- case VAR_NUMBER:
- case VAR_LIST:
- case VAR_DICT:
- case VAR_BLOB:
- break;
}
typval_T var1 = TV_INITIAL_VALUE;
@@ -3317,11 +3500,11 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
if (**arg == '.') {
// dict.name
key = *arg + 1;
- for (len = 0; eval_isdictc(key[len]); len++) {}
- if (len == 0) {
+ for (keylen = 0; eval_isdictc(key[keylen]); keylen++) {}
+ if (keylen == 0) {
return FAIL;
}
- *arg = skipwhite(key + len);
+ *arg = skipwhite(key + keylen);
} else {
// something[idx]
//
@@ -3329,7 +3512,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
*arg = skipwhite(*arg + 1);
if (**arg == ':') {
empty1 = true;
- } else if (eval1(arg, &var1, evaluate) == FAIL) { // Recursive!
+ } else if (eval1(arg, &var1, evalarg) == FAIL) { // Recursive!
return FAIL;
} else if (evaluate && !tv_check_str(&var1)) {
// Not a number or string.
@@ -3343,7 +3526,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
*arg = skipwhite(*arg + 1);
if (**arg == ']') {
empty2 = true;
- } else if (eval1(arg, &var2, evaluate) == FAIL) { // Recursive!
+ } else if (eval1(arg, &var2, evalarg) == FAIL) { // Recursive!
if (!empty1) {
tv_clear(&var1);
}
@@ -3373,195 +3556,194 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
}
if (evaluate) {
- n1 = 0;
- if (!empty1 && rettv->v_type != VAR_DICT && !tv_is_luafunc(rettv)) {
- n1 = tv_get_number(&var1);
+ int res = eval_index_inner(rettv, range,
+ empty1 ? NULL : &var1, empty2 ? NULL : &var2, false,
+ key, keylen, verbose);
+ if (!empty1) {
tv_clear(&var1);
}
if (range) {
- if (empty2) {
- n2 = -1;
- } else {
- n2 = tv_get_number(&var2);
- tv_clear(&var2);
- }
+ tv_clear(&var2);
}
+ return res;
+ }
+ return OK;
+}
- switch (rettv->v_type) {
- case VAR_NUMBER:
- case VAR_STRING: {
- const char *const s = tv_get_string(rettv);
- char *v;
- len = (ptrdiff_t)strlen(s);
- if (range) {
- // The resulting variable is a substring. If the indexes
- // are out of range the result is empty.
- if (n1 < 0) {
- n1 = len + n1;
- if (n1 < 0) {
- n1 = 0;
- }
- }
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len;
- }
- if (n1 >= len || n2 < 0 || n1 > n2) {
- v = NULL;
- } else {
- v = xmemdupz(s + n1, (size_t)(n2 - n1 + 1));
- }
- } else {
- // The resulting variable is a string of a single
- // character. If the index is too big or negative the
- // result is empty.
- if (n1 >= len || n1 < 0) {
- v = NULL;
- } else {
- v = xmemdupz(s + n1, 1);
- }
+/// Check if "rettv" can have an [index] or [sli:ce]
+static int check_can_index(typval_T *rettv, bool evaluate, bool verbose)
+{
+ switch (rettv->v_type) {
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ if (verbose) {
+ emsg(_(e_cannot_index_a_funcref));
+ }
+ return FAIL;
+ case VAR_FLOAT:
+ if (verbose) {
+ emsg(_(e_using_float_as_string));
+ }
+ return FAIL;
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ if (verbose) {
+ emsg(_(e_cannot_index_special_variable));
+ }
+ return FAIL;
+ case VAR_UNKNOWN:
+ if (evaluate) {
+ emsg(_(e_cannot_index_special_variable));
+ return FAIL;
+ }
+ FALLTHROUGH;
+ case VAR_STRING:
+ case VAR_NUMBER:
+ case VAR_LIST:
+ case VAR_DICT:
+ case VAR_BLOB:
+ break;
+ }
+ return OK;
+}
+
+/// slice() function
+void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (check_can_index(argvars, true, false) == OK) {
+ tv_copy(argvars, rettv);
+ eval_index_inner(rettv, true, argvars + 1,
+ argvars[2].v_type == VAR_UNKNOWN ? NULL : argvars + 2,
+ true, NULL, 0, false);
+ }
+}
+
+/// Apply index or range to "rettv".
+///
+/// @param var1 the first index, NULL for [:expr].
+/// @param var2 the second index, NULL for [expr] and [expr: ]
+/// @param exclusive true for slice(): second index is exclusive, use character
+/// index for string.
+/// Alternatively, "key" is not NULL, then key[keylen] is the dict index.
+static int eval_index_inner(typval_T *rettv, bool is_range, typval_T *var1, typval_T *var2,
+ bool exclusive, const char *key, ptrdiff_t keylen, bool verbose)
+{
+ varnumber_T n1 = 0;
+ varnumber_T n2 = 0;
+ if (var1 != NULL && rettv->v_type != VAR_DICT) {
+ n1 = tv_get_number(var1);
+ }
+
+ if (is_range) {
+ if (rettv->v_type == VAR_DICT) {
+ if (verbose) {
+ emsg(_(e_cannot_slice_dictionary));
}
- tv_clear(rettv);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = v;
- break;
+ return FAIL;
}
- case VAR_BLOB:
- len = tv_blob_len(rettv->vval.v_blob);
- if (range) {
- // The resulting variable is a sub-blob. If the indexes
- // are out of range the result is empty.
- if (n1 < 0) {
- n1 = len + n1;
- if (n1 < 0) {
- n1 = 0;
- }
- }
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len - 1;
- }
- if (n1 >= len || n2 < 0 || n1 > n2) {
- tv_clear(rettv);
- rettv->v_type = VAR_BLOB;
- rettv->vval.v_blob = NULL;
- } else {
- blob_T *const blob = tv_blob_alloc();
- ga_grow(&blob->bv_ga, (int)(n2 - n1 + 1));
- blob->bv_ga.ga_len = (int)(n2 - n1 + 1);
- for (long i = n1; i <= n2; i++) {
- tv_blob_set(blob, (int)(i - n1), tv_blob_get(rettv->vval.v_blob, (int)i));
- }
- tv_clear(rettv);
- tv_blob_set_ret(rettv, blob);
- }
+ if (var2 != NULL) {
+ n2 = tv_get_number(var2);
+ } else {
+ n2 = VARNUMBER_MAX;
+ }
+ }
+
+ switch (rettv->v_type) {
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ case VAR_FUNC:
+ case VAR_FLOAT:
+ case VAR_PARTIAL:
+ case VAR_UNKNOWN:
+ break; // Not evaluating, skipping over subscript
+
+ case VAR_NUMBER:
+ case VAR_STRING: {
+ const char *const s = tv_get_string(rettv);
+ char *v;
+ int len = (int)strlen(s);
+ if (exclusive) {
+ if (is_range) {
+ v = string_slice(s, n1, n2, exclusive);
} else {
- // The resulting variable is a byte value.
- // If the index is too big or negative that is an error.
- if (n1 < 0) {
- n1 = len + n1;
- }
- if (n1 < len && n1 >= 0) {
- const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)n1);
- tv_clear(rettv);
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = v;
- } else {
- semsg(_(e_blobidx), (int64_t)n1);
- }
+ v = char_from_string(s, n1);
}
- break;
- case VAR_LIST:
- len = tv_list_len(rettv->vval.v_list);
+ } else if (is_range) {
+ // The resulting variable is a substring. If the indexes
+ // are out of range the result is empty.
if (n1 < 0) {
n1 = len + n1;
- }
- if (!empty1 && (n1 < 0 || n1 >= len)) {
- // For a range we allow invalid values and return an empty
- // list. A list index out of range is an error.
- if (!range) {
- if (verbose) {
- semsg(_(e_listidx), (int64_t)n1);
- }
- return FAIL;
+ if (n1 < 0) {
+ n1 = 0;
}
- n1 = len;
}
- if (range) {
- list_T *l;
- listitem_T *item;
-
- if (n2 < 0) {
- n2 = len + n2;
- } else if (n2 >= len) {
- n2 = len - 1;
- }
- if (!empty2 && (n2 < 0 || n2 + 1 < n1)) {
- n2 = -1;
- }
- l = tv_list_alloc(n2 - n1 + 1);
- item = tv_list_find(rettv->vval.v_list, (int)n1);
- while (n1++ <= n2) {
- tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
- item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
- }
- tv_clear(rettv);
- tv_list_set_ret(rettv, l);
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len;
+ }
+ if (n1 >= len || n2 < 0 || n1 > n2) {
+ v = NULL;
} else {
- tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1);
- tv_clear(rettv);
- *rettv = var1;
+ v = xmemdupz(s + n1, (size_t)n2 - (size_t)n1 + 1);
}
- break;
- case VAR_DICT: {
- if (range) {
- if (verbose) {
- emsg(_(e_dictrange));
- }
- if (len == -1) {
- tv_clear(&var1);
- }
- return FAIL;
+ } else {
+ // The resulting variable is a string of a single
+ // character. If the index is too big or negative the
+ // result is empty.
+ if (n1 >= len || n1 < 0) {
+ v = NULL;
+ } else {
+ v = xmemdupz(s + n1, 1);
}
+ }
+ tv_clear(rettv);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = v;
+ break;
+ }
- if (len == -1) {
- key = (char *)tv_get_string_chk(&var1);
- if (key == NULL) {
- tv_clear(&var1);
- return FAIL;
- }
- }
+ case VAR_BLOB:
+ tv_blob_slice_or_index(rettv->vval.v_blob, is_range, n1, n2, exclusive, rettv);
+ break;
- dictitem_T *const item = tv_dict_find(rettv->vval.v_dict,
- (const char *)key, len);
+ case VAR_LIST:
+ if (var1 == NULL) {
+ n1 = 0;
+ }
+ if (var2 == NULL) {
+ n2 = VARNUMBER_MAX;
+ }
+ if (tv_list_slice_or_index(rettv->vval.v_list,
+ is_range, n1, n2, exclusive, rettv, verbose) == FAIL) {
+ return FAIL;
+ }
+ break;
- if (item == NULL && verbose) {
- semsg(_(e_dictkey), key);
- }
- if (len == -1) {
- tv_clear(&var1);
- }
- if (item == NULL || tv_is_luafunc(&item->di_tv)) {
+ case VAR_DICT: {
+ if (key == NULL) {
+ key = tv_get_string_chk(var1);
+ if (key == NULL) {
return FAIL;
}
+ }
- tv_copy(&item->di_tv, &var1);
- tv_clear(rettv);
- *rettv = var1;
- break;
+ dictitem_T *const item = tv_dict_find(rettv->vval.v_dict, key, keylen);
+
+ if (item == NULL && verbose) {
+ semsg(_(e_dictkey), key);
}
- case VAR_BOOL:
- case VAR_SPECIAL:
- case VAR_FUNC:
- case VAR_FLOAT:
- case VAR_PARTIAL:
- case VAR_UNKNOWN:
- break; // Not evaluating, skipping over subscript
+ if (item == NULL || tv_is_luafunc(&item->di_tv)) {
+ return FAIL;
}
- }
+ typval_T tmp;
+ tv_copy(&item->di_tv, &tmp);
+ tv_clear(rettv);
+ *rettv = tmp;
+ break;
+ }
+ }
return OK;
}
@@ -3573,7 +3755,7 @@ static int eval_index(char **arg, typval_T *rettv, int evaluate, int verbose)
/// @param[in] evaluate If not true, rettv is not populated.
///
/// @return OK or FAIL.
-int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate)
+int eval_option(const char **const arg, typval_T *const rettv, const bool evaluate)
FUNC_ATTR_NONNULL_ARG(1)
{
const bool working = (**arg == '+'); // has("+option")
@@ -3593,38 +3775,38 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
return OK;
}
- long numval;
- char *stringval;
int ret = OK;
-
+ bool hidden;
char c = *option_end;
*option_end = NUL;
- getoption_T opt_type = get_option_value(*arg, &numval,
- rettv == NULL ? NULL : &stringval, NULL, scope);
+ OptVal value = get_option_value(*arg, NULL, scope, &hidden);
- if (opt_type == gov_unknown) {
- if (rettv != NULL) {
+ if (rettv != NULL) {
+ switch (value.type) {
+ case kOptValTypeNil:
semsg(_("E113: Unknown option: %s"), *arg);
- }
- ret = FAIL;
- } else if (rettv != NULL) {
- if (opt_type == gov_hidden_string) {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- } else if (opt_type == gov_hidden_bool || opt_type == gov_hidden_number) {
+ ret = FAIL;
+ break;
+ case kOptValTypeBoolean:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
- } else if (opt_type == gov_bool || opt_type == gov_number) {
+ rettv->vval.v_number = value.data.boolean;
+ break;
+ case kOptValTypeNumber:
rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = numval;
- } else { // string option
+ rettv->vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = stringval;
+ rettv->vval.v_string = value.data.string.data;
+ break;
+ }
+ } else {
+ // Value isn't being used, free it.
+ optval_free(value);
+
+ if (value.type == kOptValTypeNil || (working && hidden)) {
+ ret = FAIL;
}
- } else if (working && (opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number
- || opt_type == gov_hidden_string)) {
- ret = FAIL;
}
*option_end = c; // put back for error messages
@@ -3636,7 +3818,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval
/// Allocate a variable for a number constant. Also deals with "0z" for blob.
///
/// @return OK or FAIL.
-static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_string)
+static int eval_number(char **arg, typval_T *rettv, bool evaluate, bool want_string)
{
char *p = skipdigits(*arg + 1);
bool get_float = false;
@@ -3702,7 +3884,7 @@ static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_s
// decimal, hex or octal number
int len;
varnumber_T n;
- vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true);
+ vim_str2nr(*arg, NULL, &len, STR2NR_ALL, &n, NULL, 0, true, NULL);
if (len == 0) {
if (evaluate) {
semsg(_(e_invexpr2), *arg);
@@ -3718,60 +3900,88 @@ static int get_number_tv(char **arg, typval_T *rettv, bool evaluate, bool want_s
return OK;
}
-/// Allocate a variable for a string constant.
+/// Evaluate a string constant and put the result in "rettv".
+/// "*arg" points to the double quote or to after it when "interpolate" is true.
+/// When "interpolate" is true reduce "{{" to "{", reduce "}}" to "}" and stop
+/// at a single "{".
///
/// @return OK or FAIL.
-static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
- unsigned int extra = 0;
+ const char *const arg_end = *arg + strlen(*arg);
+ unsigned extra = interpolate ? 1 : 0;
+ const int off = interpolate ? 0 : 1;
// Find the end of the string, skipping backslashed characters.
- for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
+ for (p = *arg + off; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
p++;
// A "\<x>" form occupies at least 4 characters, and produces up
// to 9 characters (6 for the char and 3 for a modifier):
// reserve space for 5 extra.
if (*p == '<') {
+ int modifiers = 0;
+ int flags = FSK_KEYCODE | FSK_IN_STRING;
+
extra += 5;
+
+ // Skip to the '>' to avoid using '{' inside for string
+ // interpolation.
+ if (p[1] != '*') {
+ flags |= FSK_SIMPLIFY;
+ }
+ if (find_special_key((const char **)&p, (size_t)(arg_end - p),
+ &modifiers, flags, NULL) != 0) {
+ p--; // leave "p" on the ">"
+ }
+ }
+ } else if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') { // start of expression
+ break;
}
+ p++;
+ if (p[-1] == '}' && *p != '}') { // single '}' is an error
+ semsg(_(e_stray_closing_curly_str), *arg);
+ return FAIL;
+ }
+ extra--; // "{{" becomes "{", "}}" becomes "}"
}
}
- if (*p != '"') {
+ if (*p != '"' && !(interpolate && *p == '{')) {
semsg(_("E114: Missing quote: %s"), *arg);
return FAIL;
}
// If only parsing, set *arg and return here
if (!evaluate) {
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
// Copy the string into allocated memory, handling backslashed
// characters.
- const int len = (int)(p - *arg + extra);
- char *name = xmalloc((size_t)len);
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = name;
+ const int len = (int)(p - *arg + extra);
+ rettv->vval.v_string = xmalloc((size_t)len);
+ char *end = rettv->vval.v_string;
- for (p = *arg + 1; *p != NUL && *p != '"';) {
+ for (p = *arg + off; *p != NUL && *p != '"';) {
if (*p == '\\') {
switch (*++p) {
case 'b':
- *name++ = BS; ++p; break;
+ *end++ = BS; ++p; break;
case 'e':
- *name++ = ESC; ++p; break;
+ *end++ = ESC; ++p; break;
case 'f':
- *name++ = FF; ++p; break;
+ *end++ = FF; ++p; break;
case 'n':
- *name++ = NL; ++p; break;
+ *end++ = NL; ++p; break;
case 'r':
- *name++ = CAR; ++p; break;
+ *end++ = CAR; ++p; break;
case 't':
- *name++ = TAB; ++p; break;
+ *end++ = TAB; ++p; break;
case 'X': // hex: "\x1", "\x12"
case 'x':
@@ -3797,9 +4007,9 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
// For "\u" store the number according to
// 'encoding'.
if (c != 'X') {
- name += utf_char2bytes(nr, name);
+ end += utf_char2bytes(nr, end);
} else {
- *name++ = (char)nr;
+ *end++ = (char)nr;
}
}
break;
@@ -3813,14 +4023,14 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
case '5':
case '6':
case '7':
- *name = (char)(*p++ - '0');
+ *end = (char)(*p++ - '0');
if (*p >= '0' && *p <= '7') {
- *name = (char)((*name << 3) + *p++ - '0');
+ *end = (char)((*end << 3) + *p++ - '0');
if (*p >= '0' && *p <= '7') {
- *name = (char)((*name << 3) + *p++ - '0');
+ *end = (char)((*end << 3) + *p++ - '0');
}
}
- name++;
+ end++;
break;
// Special key, e.g.: "\<C-W>"
@@ -3830,11 +4040,12 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
if (p[1] != '*') {
flags |= FSK_SIMPLIFY;
}
- extra = trans_special((const char **)&p, strlen(p), name, flags, false, NULL);
+ extra = trans_special((const char **)&p, (size_t)(arg_end - p),
+ end, flags, false, NULL);
if (extra != 0) {
- name += extra;
- if (name >= rettv->vval.v_string + len) {
- iemsg("get_string_tv() used more space than allocated");
+ end += extra;
+ if (end >= rettv->vval.v_string + len) {
+ iemsg("eval_string() used more space than allocated");
}
break;
}
@@ -3842,15 +4053,21 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
FALLTHROUGH;
default:
- mb_copy_char((const char **)&p, &name);
+ mb_copy_char((const char **)&p, &end);
break;
}
} else {
- mb_copy_char((const char **)&p, &name);
+ if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') { // start of expression
+ break;
+ }
+ p++; // reduce "{{" to "{" and "}}" to "}"
+ }
+ mb_copy_char((const char **)&p, &end);
}
}
- *name = NUL;
- if (*p != NUL) { // just in case
+ *end = NUL;
+ if (*p == '"' && !interpolate) {
p++;
}
*arg = p;
@@ -3859,63 +4076,146 @@ static int get_string_tv(char **arg, typval_T *rettv, int evaluate)
}
/// Allocate a variable for a 'str''ing' constant.
+/// When "interpolate" is true reduce "{{" to "{" and stop at a single "{".
///
-/// @return OK or FAIL.
-static int get_lit_string_tv(char **arg, typval_T *rettv, int evaluate)
+/// @return OK when a "rettv" was set to the string.
+/// FAIL on error, "rettv" is not set.
+static int eval_lit_string(char **arg, typval_T *rettv, bool evaluate, bool interpolate)
{
char *p;
- int reduce = 0;
+ int reduce = interpolate ? -1 : 0;
+ const int off = interpolate ? 0 : 1;
// Find the end of the string, skipping ''.
- for (p = *arg + 1; *p != NUL; MB_PTR_ADV(p)) {
+ for (p = *arg + off; *p != NUL; MB_PTR_ADV(p)) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
reduce++;
p++;
+ } else if (interpolate) {
+ if (*p == '{') {
+ if (p[1] != '{') {
+ break;
+ }
+ p++;
+ reduce++;
+ } else if (*p == '}') {
+ p++;
+ if (*p != '}') {
+ semsg(_(e_stray_closing_curly_str), *arg);
+ return FAIL;
+ }
+ reduce++;
+ }
}
}
- if (*p != '\'') {
+ if (*p != '\'' && !(interpolate && *p == '{')) {
semsg(_("E115: Missing quote: %s"), *arg);
return FAIL;
}
// If only parsing return after setting "*arg"
if (!evaluate) {
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
- // Copy the string into allocated memory, handling '' to ' reduction.
+ // Copy the string into allocated memory, handling '' to ' reduction and
+ // any expressions.
char *str = xmalloc((size_t)((p - *arg) - reduce));
rettv->v_type = VAR_STRING;
rettv->vval.v_string = str;
- for (p = *arg + 1; *p != NUL;) {
+ for (p = *arg + off; *p != NUL;) {
if (*p == '\'') {
if (p[1] != '\'') {
break;
}
p++;
+ } else if (interpolate && (*p == '{' || *p == '}')) {
+ if (*p == '{' && p[1] != '{') {
+ break;
+ }
+ p++;
}
mb_copy_char((const char **)&p, &str);
}
*str = NUL;
- *arg = p + 1;
+ *arg = p + off;
return OK;
}
+/// Evaluate a single or double quoted string possibly containing expressions.
+/// "arg" points to the '$'. The result is put in "rettv".
+///
+/// @return OK or FAIL.
+int eval_interp_string(char **arg, typval_T *rettv, bool evaluate)
+{
+ int ret = OK;
+
+ garray_T ga;
+ ga_init(&ga, 1, 80);
+
+ // *arg is on the '$' character, move it to the first string character.
+ (*arg)++;
+ const int quote = (uint8_t)(**arg);
+ (*arg)++;
+
+ while (true) {
+ typval_T tv;
+ // Get the string up to the matching quote or to a single '{'.
+ // "arg" is advanced to either the quote or the '{'.
+ if (quote == '"') {
+ ret = eval_string(arg, &tv, evaluate, true);
+ } else {
+ ret = eval_lit_string(arg, &tv, evaluate, true);
+ }
+ if (ret == FAIL) {
+ break;
+ }
+ if (evaluate) {
+ ga_concat(&ga, tv.vval.v_string);
+ tv_clear(&tv);
+ }
+
+ if (**arg != '{') {
+ // found terminating quote
+ (*arg)++;
+ break;
+ }
+ char *p = eval_one_expr_in_str(*arg, &ga, evaluate);
+ if (p == NULL) {
+ ret = FAIL;
+ break;
+ }
+ *arg = p;
+ }
+
+ rettv->v_type = VAR_STRING;
+ if (ret != FAIL && evaluate) {
+ ga_append(&ga, NUL);
+ }
+ rettv->vval.v_string = ga.ga_data;
+ return OK;
+}
+
/// @return the function name of the partial.
char *partial_name(partial_T *pt)
FUNC_ATTR_PURE
{
- if (pt->pt_name != NULL) {
- return pt->pt_name;
+ if (pt != NULL) {
+ if (pt->pt_name != NULL) {
+ return pt->pt_name;
+ }
+ if (pt->pt_func != NULL) {
+ return pt->pt_func->uf_name;
+ }
}
- return (char *)pt->pt_func->uf_name;
+ return "";
}
static void partial_free(partial_T *pt)
@@ -3945,9 +4245,11 @@ void partial_unref(partial_T *pt)
/// Allocate a variable for a List and fill it from "*arg".
///
+/// @param arg "*arg" points to the "[".
/// @return OK or FAIL.
-static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_list(char **arg, typval_T *rettv, evalarg_T *const evalarg)
{
+ const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE;
list_T *l = NULL;
if (evaluate) {
@@ -3957,7 +4259,7 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != ']' && **arg != NUL) {
typval_T tv;
- if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive!
+ if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive!
goto failret;
}
if (evaluate) {
@@ -3965,14 +4267,20 @@ static int get_list_tv(char **arg, typval_T *rettv, int evaluate)
tv_list_append_owned_tv(l, tv);
}
+ // the comma must come after the value
+ bool had_comma = **arg == ',';
+ if (had_comma) {
+ *arg = skipwhite(*arg + 1);
+ }
+
if (**arg == ']') {
break;
}
- if (**arg != ',') {
+
+ if (!had_comma) {
semsg(_("E696: Missing comma in List: %s"), *arg);
goto failret;
}
- *arg = skipwhite(*arg + 1);
}
if (**arg != ']') {
@@ -4119,7 +4427,7 @@ bool garbage_collect(bool testing)
ABORTING(set_ref_in_previous_funccal)(copyID);
// script-local variables
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
+ for (int i = 1; i <= script_items.ga_len; i++) {
ABORTING(set_ref_in_ht)(&SCRIPT_VARS(i), copyID, NULL);
}
@@ -4368,7 +4676,7 @@ bool set_ref_in_ht(hashtab_T *ht, int copyID, list_stack_T **list_stack)
ht_stack_T *ht_stack = NULL;
hashtab_T *cur_ht = ht;
- for (;;) {
+ while (true) {
if (!abort) {
// Mark each item in the hashtab. If the item contains a hashtab
// it is added to ht_stack, if it contains a list it is added to
@@ -4406,7 +4714,7 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack)
list_stack_T *list_stack = NULL;
list_T *cur_l = l;
- for (;;) {
+ while (true) {
// Mark each item in the list. If the item contains a hashtab
// it is added to ht_stack, if it contains a list it is added to
// list_stack.
@@ -4582,18 +4890,21 @@ static int get_literal_key(char **arg, typval_T *tv)
}
for (p = *arg; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; p++) {}
tv->v_type = VAR_STRING;
- tv->vval.v_string = xstrnsave(*arg, (size_t)(p - *arg));
+ tv->vval.v_string = xmemdupz(*arg, (size_t)(p - *arg));
*arg = skipwhite(p);
return OK;
}
/// Allocate a variable for a Dictionary and fill it from "*arg".
-/// "literal" is true for #{key: val}
+///
+/// @param arg "*arg" points to the "{".
+/// @param literal true for #{key: val}
///
/// @return OK or FAIL. Returns NOTDONE for {expr}.
-static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
+static int eval_dict(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool literal)
{
+ const bool evaluate = evalarg == NULL ? false : evalarg->eval_flags & EVAL_EVALUATE;
typval_T tv;
char *key = NULL;
char *curly_expr = skipwhite(*arg + 1);
@@ -4607,7 +4918,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
// "#{abc}" is never a curly-braces expression.
if (*curly_expr != '}'
&& !literal
- && eval1(&curly_expr, &tv, false) == OK
+ && eval1(&curly_expr, &tv, NULL) == OK
&& *skipwhite(curly_expr) == '}') {
return NOTDONE;
}
@@ -4624,7 +4935,7 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
while (**arg != '}' && **arg != NUL) {
if ((literal
? get_literal_key(arg, &tvkey)
- : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive!
+ : eval1(arg, &tvkey, evalarg)) == FAIL) { // recursive!
goto failret;
}
if (**arg != ':') {
@@ -4642,21 +4953,21 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
}
*arg = skipwhite(*arg + 1);
- if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive!
+ if (eval1(arg, &tv, evalarg) == FAIL) { // Recursive!
if (evaluate) {
tv_clear(&tvkey);
}
goto failret;
}
if (evaluate) {
- dictitem_T *item = tv_dict_find(d, (const char *)key, -1);
+ dictitem_T *item = tv_dict_find(d, key, -1);
if (item != NULL) {
semsg(_("E721: Duplicate key in Dictionary: \"%s\""), key);
tv_clear(&tvkey);
tv_clear(&tv);
goto failret;
}
- item = tv_dict_item_alloc((const char *)key);
+ item = tv_dict_item_alloc(key);
item->di_tv = tv;
item->di_tv.v_lock = VAR_UNLOCKED;
if (tv_dict_add(d, item) == FAIL) {
@@ -4665,14 +4976,19 @@ static int eval_dict(char **arg, typval_T *rettv, int evaluate, bool literal)
}
tv_clear(&tvkey);
+ // the comma must come after the value
+ bool had_comma = **arg == ',';
+ if (had_comma) {
+ *arg = skipwhite(*arg + 1);
+ }
+
if (**arg == '}') {
break;
}
- if (**arg != ',') {
+ if (!had_comma) {
semsg(_("E722: Missing comma in Dictionary: %s"), *arg);
goto failret;
}
- *arg = skipwhite(*arg + 1);
}
if (**arg != '}') {
@@ -4729,7 +5045,7 @@ size_t string2float(const char *const text, float_T *const ret_value)
/// @param arg Points to the '$'. It is advanced to after the name.
///
/// @return FAIL if the name is invalid.
-static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
+static int eval_env_var(char **arg, typval_T *rettv, int evaluate)
{
(*arg)++;
char *name = *arg;
@@ -4755,6 +5071,7 @@ static int get_env_tv(char **arg, typval_T *rettv, int evaluate)
name[len] = (char)cc;
rettv->v_type = VAR_STRING;
rettv->vval.v_string = string;
+ rettv->v_lock = VAR_UNLOCKED;
}
return OK;
@@ -4769,44 +5086,280 @@ void assert_error(garray_T *gap)
// Make sure v:errors is a list.
set_vim_var_list(VV_ERRORS, tv_list_alloc(1));
}
- tv_list_append_string(vimvars[VV_ERRORS].vv_list,
- (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len);
+ tv_list_append_string(vimvars[VV_ERRORS].vv_list, gap->ga_data, (ptrdiff_t)gap->ga_len);
}
-/// Implementation of map() and filter().
-void filter_map(typval_T *argvars, typval_T *rettv, int map)
+/// Implementation of map() and filter() for a Dict.
+static void filter_map_dict(dict_T *d, filtermap_T filtermap, const char *func_name,
+ const char *arg_errmsg, typval_T *expr, typval_T *rettv)
{
- list_T *l = NULL;
- dict_T *d = NULL;
- blob_T *b = NULL;
- int rem = false;
- char *ermsg = map ? "map()" : "filter()";
- const char *const arg_errmsg = (map
- ? N_("map() argument")
- : N_("filter() argument"));
- int save_did_emsg;
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_DICT;
+ rettv->vval.v_dict = NULL;
+ }
+ if (d == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ dict_T *d_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_dict_alloc_ret(rettv);
+ d_ret = rettv->vval.v_dict;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_STRING;
+
+ const VarLockStatus prev_lock = d->dv_lock;
+ if (d->dv_lock == VAR_UNLOCKED) {
+ d->dv_lock = VAR_LOCKED;
+ }
+ hash_lock(&d->dv_hashtab);
+ TV_DICT_ITER(d, di, {
+ if (filtermap == FILTERMAP_MAP
+ && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
+ || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
+ break;
+ }
+ vimvars[VV_KEY].vv_str = xstrdup(di->di_key);
+ typval_T newtv;
+ bool rem;
+ int r = filter_map_one(&di->di_tv, expr, filtermap, &newtv, &rem);
+ tv_clear(&vimvars[VV_KEY].vv_tv);
+ if (r == FAIL || did_emsg) {
+ tv_clear(&newtv);
+ break;
+ }
+ if (filtermap == FILTERMAP_MAP) {
+ // map(): replace the dict item value
+ tv_clear(&di->di_tv);
+ newtv.v_lock = VAR_UNLOCKED;
+ di->di_tv = newtv;
+ } else if (filtermap == FILTERMAP_MAPNEW) {
+ // mapnew(): add the item value to the new dict
+ r = tv_dict_add_tv(d_ret, di->di_key, strlen(di->di_key), &newtv);
+ tv_clear(&newtv);
+ if (r == FAIL) {
+ break;
+ }
+ } else if (filtermap == FILTERMAP_FILTER && rem) {
+ // filter(false): remove the item from the dict
+ if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
+ || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
+ break;
+ }
+ tv_dict_item_remove(d, di);
+ }
+ });
+ hash_unlock(&d->dv_hashtab);
+ d->dv_lock = prev_lock;
+}
+
+/// Implementation of map() and filter() for a Blob.
+static void filter_map_blob(blob_T *blob_arg, filtermap_T filtermap, typval_T *expr,
+ const char *arg_errmsg, typval_T *rettv)
+{
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ }
+ blob_T *b = blob_arg;
+ if (b == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(b->bv_lock, arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ blob_T *b_ret = b;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_blob_copy(b, rettv);
+ b_ret = rettv->vval.v_blob;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ const VarLockStatus prev_lock = b->bv_lock;
+ if (b->bv_lock == 0) {
+ b->bv_lock = VAR_LOCKED;
+ }
+
+ for (int i = 0, idx = 0; i < b->bv_ga.ga_len; i++) {
+ const varnumber_T val = tv_blob_get(b, i);
+ typval_T tv = {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = val,
+ };
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg) {
+ break;
+ }
+ if (newtv.v_type != VAR_NUMBER && newtv.v_type != VAR_BOOL) {
+ tv_clear(&newtv);
+ emsg(_(e_invalblob));
+ break;
+ }
+ if (filtermap != FILTERMAP_FILTER) {
+ if (newtv.vval.v_number != val) {
+ tv_blob_set(b_ret, i, (uint8_t)newtv.vval.v_number);
+ }
+ } else if (rem) {
+ char *const p = (char *)blob_arg->bv_ga.ga_data;
+ memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
+ b->bv_ga.ga_len--;
+ i--;
+ }
+ idx++;
+ }
+
+ b->bv_lock = prev_lock;
+}
+
+/// Implementation of map() and filter() for a String.
+static void filter_map_string(const char *str, filtermap_T filtermap, typval_T *expr,
+ typval_T *rettv)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+ int len = 0;
int idx = 0;
+ for (const char *p = str; *p != NUL; p += len) {
+ len = utfc_ptr2len(p);
+ typval_T tv = {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
- // Always return the first argument, also on failure.
- tv_copy(&argvars[0], rettv);
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(&tv, expr, filtermap, &newtv, &rem) == FAIL
+ || did_emsg) {
+ tv_clear(&newtv);
+ tv_clear(&tv);
+ break;
+ } else if (filtermap != FILTERMAP_FILTER) {
+ if (newtv.v_type != VAR_STRING) {
+ tv_clear(&newtv);
+ tv_clear(&tv);
+ emsg(_(e_stringreq));
+ break;
+ } else {
+ ga_concat(&ga, newtv.vval.v_string);
+ }
+ } else if (!rem) {
+ ga_concat(&ga, tv.vval.v_string);
+ }
- if (argvars[0].v_type == VAR_BLOB) {
- if ((b = argvars[0].vval.v_blob) == NULL) {
- return;
+ tv_clear(&newtv);
+ tv_clear(&tv);
+
+ idx++;
+ }
+ ga_append(&ga, NUL);
+ rettv->vval.v_string = ga.ga_data;
+}
+
+/// Implementation of map() and filter() for a List.
+static void filter_map_list(list_T *l, filtermap_T filtermap, const char *func_name,
+ const char *arg_errmsg, typval_T *expr, typval_T *rettv)
+{
+ if (filtermap == FILTERMAP_MAPNEW) {
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = NULL;
+ }
+ if (l == NULL
+ || (filtermap == FILTERMAP_FILTER
+ && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
+ return;
+ }
+
+ list_T *l_ret = NULL;
+
+ if (filtermap == FILTERMAP_MAPNEW) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ l_ret = rettv->vval.v_list;
+ }
+
+ vimvars[VV_KEY].vv_type = VAR_NUMBER;
+
+ const VarLockStatus prev_lock = tv_list_locked(l);
+ if (tv_list_locked(l) == VAR_UNLOCKED) {
+ tv_list_set_lock(l, VAR_LOCKED);
+ }
+
+ int idx = 0;
+ for (listitem_T *li = tv_list_first(l); li != NULL;) {
+ if (filtermap == FILTERMAP_MAP
+ && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, TV_TRANSLATE)) {
+ break;
}
- } else if (argvars[0].v_type == VAR_LIST) {
- if ((l = argvars[0].vval.v_list) == NULL
- || (!map
- && value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE))) {
- return;
+ vimvars[VV_KEY].vv_nr = idx;
+ typval_T newtv;
+ bool rem;
+ if (filter_map_one(TV_LIST_ITEM_TV(li), expr, filtermap, &newtv, &rem) == FAIL) {
+ break;
}
- } else if (argvars[0].v_type == VAR_DICT) {
- if ((d = argvars[0].vval.v_dict) == NULL
- || (!map && value_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) {
- return;
+ if (did_emsg) {
+ tv_clear(&newtv);
+ break;
}
- } else {
- semsg(_(e_listdictblobarg), ermsg);
+ if (filtermap == FILTERMAP_MAP) {
+ // map(): replace the list item value
+ tv_clear(TV_LIST_ITEM_TV(li));
+ newtv.v_lock = VAR_UNLOCKED;
+ *TV_LIST_ITEM_TV(li) = newtv;
+ } else if (filtermap == FILTERMAP_MAPNEW) {
+ // mapnew(): append the list item value
+ tv_list_append_owned_tv(l_ret, newtv);
+ }
+ if (filtermap == FILTERMAP_FILTER && rem) {
+ li = tv_list_item_remove(l, li);
+ } else {
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ idx++;
+ }
+
+ tv_list_set_lock(l, prev_lock);
+}
+
+/// Implementation of map() and filter().
+static void filter_map(typval_T *argvars, typval_T *rettv, filtermap_T filtermap)
+{
+ const char *const func_name = (filtermap == FILTERMAP_MAP
+ ? "map()"
+ : (filtermap == FILTERMAP_MAPNEW
+ ? "mapnew()"
+ : "filter()"));
+ const char *const arg_errmsg = (filtermap == FILTERMAP_MAP
+ ? N_("map() argument")
+ : (filtermap == FILTERMAP_MAPNEW
+ ? N_("mapnew() argument")
+ : N_("filter() argument")));
+
+ // map() and filter() return the first argument, also on failure.
+ if (filtermap != FILTERMAP_MAPNEW && argvars[0].v_type != VAR_STRING) {
+ tv_copy(&argvars[0], rettv);
+ }
+
+ if (argvars[0].v_type != VAR_BLOB
+ && argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_DICT
+ && argvars[0].v_type != VAR_STRING) {
+ semsg(_(e_argument_of_str_must_be_list_string_dictionary_or_blob), func_name);
return;
}
@@ -4820,104 +5373,22 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
// We reset "did_emsg" to be able to detect whether an error
// occurred during evaluation of the expression.
- save_did_emsg = did_emsg;
+ int save_did_emsg = did_emsg;
did_emsg = false;
typval_T save_key;
prepare_vimvar(VV_KEY, &save_key);
if (argvars[0].v_type == VAR_DICT) {
- vimvars[VV_KEY].vv_type = VAR_STRING;
-
- const VarLockStatus prev_lock = d->dv_lock;
- if (map && d->dv_lock == VAR_UNLOCKED) {
- d->dv_lock = VAR_LOCKED;
- }
- hashtab_T *ht = &d->dv_hashtab;
- hash_lock(ht);
- int todo = (int)ht->ht_used;
- for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
-
- dictitem_T *di = TV_DICT_HI2DI(hi);
- if (map
- && (value_check_lock(di->di_tv.v_lock, arg_errmsg, TV_TRANSLATE)
- || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE))) {
- break;
- }
-
- vimvars[VV_KEY].vv_str = xstrdup((char *)di->di_key);
- int r = filter_map_one(&di->di_tv, expr, map, &rem);
- tv_clear(&vimvars[VV_KEY].vv_tv);
- if (r == FAIL || did_emsg) {
- break;
- }
- if (!map && rem) {
- if (var_check_fixed(di->di_flags, arg_errmsg, TV_TRANSLATE)
- || var_check_ro(di->di_flags, arg_errmsg, TV_TRANSLATE)) {
- break;
- }
- tv_dict_item_remove(d, di);
- }
- }
- }
- hash_unlock(ht);
- d->dv_lock = prev_lock;
+ filter_map_dict(argvars[0].vval.v_dict, filtermap, func_name,
+ arg_errmsg, expr, rettv);
} else if (argvars[0].v_type == VAR_BLOB) {
- vimvars[VV_KEY].vv_type = VAR_NUMBER;
-
- for (int i = 0; i < b->bv_ga.ga_len; i++) {
- typval_T tv;
- tv.v_type = VAR_NUMBER;
- const varnumber_T val = tv_blob_get(b, i);
- tv.vval.v_number = val;
- vimvars[VV_KEY].vv_nr = idx;
- if (filter_map_one(&tv, expr, map, &rem) == FAIL || did_emsg) {
- break;
- }
- if (tv.v_type != VAR_NUMBER) {
- emsg(_(e_invalblob));
- return;
- }
- if (map) {
- if (tv.vval.v_number != val) {
- tv_blob_set(b, i, (uint8_t)tv.vval.v_number);
- }
- } else if (rem) {
- char *const p = argvars[0].vval.v_blob->bv_ga.ga_data;
- memmove(p + i, p + i + 1, (size_t)(b->bv_ga.ga_len - i - 1));
- b->bv_ga.ga_len--;
- i--;
- }
- idx++;
- }
+ filter_map_blob(argvars[0].vval.v_blob, filtermap, expr, arg_errmsg, rettv);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ filter_map_string(tv_get_string(&argvars[0]), filtermap, expr, rettv);
} else {
assert(argvars[0].v_type == VAR_LIST);
- vimvars[VV_KEY].vv_type = VAR_NUMBER;
-
- const VarLockStatus prev_lock = tv_list_locked(l);
- if (map && tv_list_locked(l) == VAR_UNLOCKED) {
- tv_list_set_lock(l, VAR_LOCKED);
- }
- for (listitem_T *li = tv_list_first(l); li != NULL;) {
- if (map
- && value_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg,
- TV_TRANSLATE)) {
- break;
- }
- vimvars[VV_KEY].vv_nr = idx;
- if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL
- || did_emsg) {
- break;
- }
- if (!map && rem) {
- li = tv_list_item_remove(l, li);
- } else {
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- idx++;
- }
- tv_list_set_lock(l, prev_lock);
+ filter_map_list(argvars[0].vval.v_list, filtermap, func_name,
+ arg_errmsg, expr, rettv);
}
restore_vimvar(VV_KEY, &save_key);
@@ -4927,30 +5398,32 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map)
}
}
-static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp)
- FUNC_ATTR_NONNULL_ARG(1, 2)
+/// Handle one item for map() and filter().
+/// Sets v:val to "tv". Caller must set v:key.
+///
+/// @param tv original value
+/// @param expr callback
+/// @param newtv for map() an mapnew(): new value
+/// @param remp for filter(): remove flag
+static int filter_map_one(typval_T *tv, typval_T *expr, const filtermap_T filtermap,
+ typval_T *newtv, bool *remp)
+ FUNC_ATTR_NONNULL_ALL
{
- typval_T rettv;
typval_T argv[3];
int retval = FAIL;
tv_copy(tv, &vimvars[VV_VAL].vv_tv);
argv[0] = vimvars[VV_KEY].vv_tv;
argv[1] = vimvars[VV_VAL].vv_tv;
- if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, argv, 2, newtv) == FAIL) {
goto theend;
}
- if (map) {
- // map(): replace the list item value.
- tv_clear(tv);
- rettv.v_lock = VAR_UNLOCKED;
- *tv = rettv;
- } else {
+ if (filtermap == FILTERMAP_FILTER) {
bool error = false;
// filter(): when expr is zero remove the item
- *remp = (tv_get_number_chk(&rettv, &error) == 0);
- tv_clear(&rettv);
+ *remp = (tv_get_number_chk(newtv, &error) == 0);
+ tv_clear(newtv);
// On type error, nothing has been removed; return FAIL to stop the
// loop. The error message was given by tv_get_number_chk().
if (error) {
@@ -4963,6 +5436,26 @@ theend:
return retval;
}
+/// "filter()" function
+void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_FILTER);
+}
+
+/// "map()" function
+void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAP);
+}
+
+/// "mapnew()" function
+void f_mapnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ filter_map(argvars, rettv, FILTERMAP_MAPNEW);
+}
+
+/// "function()" function
+/// "funcref()" function
void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
{
char *s;
@@ -4996,14 +5489,12 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
}
if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
|| (is_funcref && trans_name == NULL)) {
- semsg(_(e_invarg2), (use_string
- ? tv_get_string(&argvars[0])
- : (const char *)s));
+ semsg(_(e_invarg2), (use_string ? tv_get_string(&argvars[0]) : s));
// Don't check an autoload name for existence here.
} else if (trans_name != NULL
&& (is_funcref
? find_func(trans_name) == NULL
- : !translated_function_exists((const char *)trans_name))) {
+ : !translated_function_exists(trans_name))) {
semsg(_("E700: Unknown function: %s"), s);
} else {
int dict_idx = 0;
@@ -5032,8 +5523,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
arg_idx = 1;
}
if (dict_idx > 0) {
- if (argvars[dict_idx].v_type != VAR_DICT) {
- emsg(_("E922: expected a dict"));
+ if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) {
xfree(name);
goto theend;
}
@@ -5052,7 +5542,7 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
if (tv_list_len(list) == 0) {
arg_idx = 0;
} else if (tv_list_len(list) > MAX_FUNC_ARGS) {
- emsg_funcname((char *)e_toomanyarg, s);
+ emsg_funcname(e_toomanyarg, s);
xfree(name);
goto theend;
}
@@ -5122,7 +5612,7 @@ theend:
xfree(trans_name);
}
-/// Get the line number from VimL object
+/// Get the line number from Vimscript object
///
/// @note Unlike tv_get_lnum(), this one supports only "$" special string.
///
@@ -5156,7 +5646,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- const char *prompt = "";
+ const char *prompt;
const char *defstr = "";
typval_T *cancelreturn = NULL;
typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE;
@@ -5247,7 +5737,7 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
p = lastnl + 1;
msg_start();
msg_clr_eos();
- msg_puts_attr_len(prompt, p - prompt, echo_attr);
+ msg_puts_len(prompt, p - prompt, echo_attr);
msg_didout = false;
msg_starthere();
}
@@ -5275,9 +5765,9 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const
cmd_silent = cmd_silent_save;
}
-/// Builds a process argument vector from a VimL object (typval_T).
+/// Builds a process argument vector from a Vimscript object (typval_T).
///
-/// @param[in] cmd_tv VimL object
+/// @param[in] cmd_tv Vimscript object
/// @param[out] cmd Returns the command or executable name.
/// @param[out] executable Returns `false` if argv[0] is not executable.
///
@@ -5379,15 +5869,15 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
return;
}
const void *iter = NULL;
+ const char *appname = get_appname();
do {
size_t dir_len;
const char *dir;
iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
char *dir_with_nvim = xmemdupz(dir, dir_len);
- dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true);
- tv_list_append_string(list, dir_with_nvim, (ssize_t)strlen(dir_with_nvim));
- xfree(dir_with_nvim);
+ dir_with_nvim = concat_fnames_realloc(dir_with_nvim, appname, true);
+ tv_list_append_allocated_string(list, dir_with_nvim);
}
} while (iter != NULL);
xfree(dirs);
@@ -5429,7 +5919,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
char **argv = tv_to_argv(&argvars[0], NULL, &executable);
if (!argv) {
if (!executable) {
- set_vim_var_nr(VV_SHELL_ERROR, (long)-1);
+ set_vim_var_nr(VV_SHELL_ERROR, -1);
}
xfree(input);
return; // Already did emsg.
@@ -5438,7 +5928,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
if (p_verbose > 3) {
char *cmdstr = shell_argv_to_str(argv);
verbose_enter_scroll();
- smsg(_("Executing command: \"%s\""), cmdstr);
+ smsg(0, _("Executing command: \"%s\""), cmdstr);
msg_puts("\n\n");
verbose_leave_scroll();
xfree(cmdstr);
@@ -5459,7 +5949,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist
xfree(input);
- set_vim_var_nr(VV_SHELL_ERROR, (long)status);
+ set_vim_var_nr(VV_SHELL_ERROR, status);
if (res == NULL) {
if (retlist) {
@@ -5559,11 +6049,23 @@ bool callback_from_typval(Callback *const callback, const typval_T *const arg)
return true;
}
+static int callback_depth = 0;
+
+int get_callback_depth(void)
+{
+ return callback_depth;
+}
+
/// @return whether the callback could be called.
bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
{
+ if (callback_depth > MAX_CALLBACK_DEPTH) {
+ emsg(_(e_command_too_recursive));
+ return false;
+ }
+
partial_T *partial;
char *name;
Array args = ARRAY_DICT_INIT;
@@ -5596,9 +6098,6 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
case kCallbackNone:
return false;
break;
-
- default:
- abort();
}
funcexe_T funcexe = FUNCEXE_INIT;
@@ -5606,7 +6105,11 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
funcexe.fe_lastline = curwin->w_cursor.lnum;
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
- return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+
+ callback_depth++;
+ int ret = call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+ callback_depth--;
+ return ret;
}
bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
@@ -5624,7 +6127,7 @@ bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
return set_ref_in_item(&tv, copyID, ht_stack, list_stack);
break;
- default:
+ case kCallbackLua:
abort();
}
return false;
@@ -5678,7 +6181,7 @@ void add_timer_info_all(typval_T *rettv)
tv_list_alloc_ret(rettv, map_size(&timers));
timer_T *timer;
map_foreach_value(&timers, timer, {
- if (!timer->stopped) {
+ if (!timer->stopped || timer->refcount > 1) {
add_timer_info(rettv, timer);
}
})
@@ -5735,7 +6238,7 @@ void timer_due_cb(TimeWatcher *tw, void *data)
timer_decref(timer);
}
-uint64_t timer_start(const long timeout, const int repeat_count, const Callback *const callback)
+uint64_t timer_start(const int64_t timeout, const int repeat_count, const Callback *const callback)
{
timer_T *timer = xmalloc(sizeof *timer);
timer->refcount = 1;
@@ -5775,7 +6278,7 @@ static void timer_close_cb(TimeWatcher *tw, void *data)
timer_T *timer = (timer_T *)data;
multiqueue_free(timer->tw.events);
callback_free(&timer->callback);
- pmap_del(uint64_t)(&timers, (uint64_t)timer->timer_id);
+ pmap_del(uint64_t)(&timers, (uint64_t)timer->timer_id, NULL);
timer_decref(timer);
}
@@ -5883,27 +6386,63 @@ write_blob_error:
return false;
}
-/// Read a blob from a file `fd`.
+/// Read blob from file "fd".
+/// Caller has allocated a blob in "rettv".
///
/// @param[in] fd File to read from.
-/// @param[in,out] blob Blob to write to.
+/// @param[in,out] rettv Blob to write to.
+/// @param[in] offset Read the file from the specified offset.
+/// @param[in] size Read the specified size, or -1 if no limit.
///
-/// @return true on success, or false on failure.
-bool read_blob(FILE *const fd, blob_T *const blob)
+/// @return OK on success, or FAIL on failure.
+int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
FUNC_ATTR_NONNULL_ALL
{
+ blob_T *const blob = rettv->vval.v_blob;
FileInfo file_info;
if (!os_fileinfo_fd(fileno(fd), &file_info)) {
- return false;
+ return FAIL; // can't read the file, error
}
- const int size = (int)os_fileinfo_size(&file_info);
- ga_grow(&blob->bv_ga, size);
- blob->bv_ga.ga_len = size;
+
+ int whence;
+ off_T size = size_arg;
+ const off_T file_size = (off_T)os_fileinfo_size(&file_info);
+ if (offset >= 0) {
+ // The size defaults to the whole file. If a size is given it is
+ // limited to not go past the end of the file.
+ if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
+ // size may become negative, checked below
+ size = (off_T)os_fileinfo_size(&file_info) - offset;
+ }
+ whence = SEEK_SET;
+ } else {
+ // limit the offset to not go before the start of the file
+ if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
+ offset = -file_size;
+ }
+ // Size defaults to reading until the end of the file.
+ if (size == -1 || size > -offset) {
+ size = -offset;
+ }
+ whence = SEEK_END;
+ }
+ if (size <= 0) {
+ return OK;
+ }
+ if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
+ return OK;
+ }
+
+ ga_grow(&blob->bv_ga, (int)size);
+ blob->bv_ga.ga_len = (int)size;
if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
< (size_t)blob->bv_ga.ga_len) {
- return false;
+ // An empty blob is returned on error.
+ tv_blob_free(rettv->vval.v_blob);
+ rettv->vval.v_blob = NULL;
+ return FAIL;
}
- return true;
+ return OK;
}
/// Saves a typval_T as a string.
@@ -5914,7 +6453,7 @@ bool read_blob(FILE *const fd, blob_T *const blob)
/// @param[out] len Length of the resulting string or -1 on error.
/// @param[in] endnl If true, the output will end in a newline (if a list).
/// @param[in] crlf If true, list items will be joined with CRLF (if a list).
-/// @returns an allocated string if `tv` represents a VimL string, list, or
+/// @returns an allocated string if `tv` represents a Vimscript string, list, or
/// number; NULL otherwise.
char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crlf)
FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL
@@ -5941,7 +6480,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crl
buf_T *buf = buflist_findnr((int)tv->vval.v_number);
if (buf) {
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) {
+ for (char *p = ml_get_buf(buf, lnum); *p != NUL; p++) {
*len += 1;
}
*len += 1;
@@ -5959,7 +6498,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl, bool crl
char *ret = xmalloc((size_t)(*len) + 1);
char *end = ret;
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- for (char *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) {
+ for (char *p = ml_get_buf(buf, lnum); *p != NUL; p++) {
*end++ = (*p == '\n') ? NUL : *p;
}
*end++ = '\n';
@@ -6011,7 +6550,7 @@ int buf_byteidx_to_charidx(buf_T *buf, linenr_T lnum, int byteidx)
lnum = buf->b_ml.ml_line_count;
}
- char *str = ml_get_buf(buf, lnum, false);
+ char *str = ml_get_buf(buf, lnum);
if (*str == NUL) {
return 0;
@@ -6049,7 +6588,7 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx)
lnum = buf->b_ml.ml_line_count;
}
- char *str = ml_get_buf(buf, lnum, false);
+ char *str = ml_get_buf(buf, lnum);
// Convert the character offset to a byte offset
char *t = str;
@@ -6060,7 +6599,7 @@ int buf_charidx_to_byteidx(buf_T *buf, linenr_T lnum, int charidx)
return (int)(t - str);
}
-/// Translate a VimL object into a position
+/// Translate a Vimscript object into a position
///
/// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid
/// type.
@@ -6087,14 +6626,14 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
// Get the line number.
- pos.lnum = (linenr_T)tv_list_find_nr(l, 0L, &error);
+ pos.lnum = (linenr_T)tv_list_find_nr(l, 0, &error);
if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) {
// Invalid line number.
return NULL;
}
// Get the column number.
- pos.col = (colnr_T)tv_list_find_nr(l, 1L, &error);
+ pos.col = (colnr_T)tv_list_find_nr(l, 1, &error);
if (error) {
return NULL;
}
@@ -6106,7 +6645,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
// We accept "$" for the column number: last column.
- listitem_T *li = tv_list_find(l, 1L);
+ listitem_T *li = tv_list_find(l, 1);
if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING
&& TV_LIST_ITEM_TV(li)->vval.v_string != NULL
&& strcmp(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) {
@@ -6121,7 +6660,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.col--;
// Get the virtual offset. Defaults to zero.
- pos.coladd = (colnr_T)tv_list_find_nr(l, 2L, &error);
+ pos.coladd = (colnr_T)tv_list_find_nr(l, 2, &error);
if (error) {
pos.coladd = 0;
}
@@ -6154,7 +6693,7 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
}
pos = fm->mark;
// Vimscript behavior, only provide fnum if mark is global.
- *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum: *ret_fnum;
+ *ret_fnum = ASCII_ISUPPER(mname) || ascii_isdigit(mname) ? fm->fnum : *ret_fnum;
}
if (pos.lnum != 0) {
if (charcol) {
@@ -6166,6 +6705,10 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret
pos.coladd = 0;
if (name[0] == 'w' && dollar_lnum) {
+ // the "w_valid" flags are not reset when moving the cursor, but they
+ // do matter for update_topline() and validate_botline().
+ check_cursor_moved(curwin);
+
pos.col = 0;
if (name[1] == '0') { // "w0": first visible line
update_topline(curwin);
@@ -6220,25 +6763,25 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
}
int i = 0;
- long n;
+ int n;
if (fnump != NULL) {
- n = tv_list_find_nr(l, i++, NULL); // fnum
+ n = (int)tv_list_find_nr(l, i++, NULL); // fnum
if (n < 0) {
return FAIL;
}
if (n == 0) {
n = curbuf->b_fnum; // Current buffer.
}
- *fnump = (int)n;
+ *fnump = n;
}
- n = tv_list_find_nr(l, i++, NULL); // lnum
+ n = (int)tv_list_find_nr(l, i++, NULL); // lnum
if (n < 0) {
return FAIL;
}
- posp->lnum = (linenr_T)n;
+ posp->lnum = n;
- n = tv_list_find_nr(l, i++, NULL); // col
+ n = (int)tv_list_find_nr(l, i++, NULL); // col
if (n < 0) {
return FAIL;
}
@@ -6252,15 +6795,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool c
}
n = buf_charidx_to_byteidx(buf,
posp->lnum == 0 ? curwin->w_cursor.lnum : posp->lnum,
- (int)n) + 1;
+ n) + 1;
}
- posp->col = (colnr_T)n;
+ posp->col = n;
- n = tv_list_find_nr(l, i, NULL); // off
+ n = (int)tv_list_find_nr(l, i, NULL); // off
if (n < 0) {
posp->coladd = 0;
} else {
- posp->coladd = (colnr_T)n;
+ posp->coladd = n;
}
if (curswantp != NULL) {
@@ -6314,7 +6857,7 @@ int get_id_len(const char **const arg)
}
len = (int)(p - *arg);
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return len;
}
@@ -6352,7 +6895,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
if (expr_start != NULL) {
if (!evaluate) {
len += (int)(p - *arg);
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return len;
}
@@ -6363,7 +6906,7 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo
return -1;
}
*alias = temp_string;
- *arg = (const char *)skipwhite(p);
+ *arg = skipwhite(p);
return (int)strlen(temp_string);
}
@@ -6482,15 +7025,14 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex
}
char *retval = NULL;
- char *nextcmd = NULL;
*expr_start = NUL;
*expr_end = NUL;
char c1 = *in_end;
*in_end = NUL;
- char *temp_result = eval_to_string(expr_start + 1, &nextcmd, false);
- if (temp_result != NULL && nextcmd == NULL) {
+ char *temp_result = eval_to_string(expr_start + 1, false);
+ if (temp_result != NULL) {
retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start)
+ (size_t)(in_end - expr_end) + 1);
STRCPY(retval, in_start);
@@ -6578,7 +7120,7 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE
/// Set v:char to character "c".
void set_vim_var_char(int c)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(c, buf)] = NUL;
set_vim_var_string(VV_CHAR, buf, -1);
@@ -6587,7 +7129,7 @@ void set_vim_var_char(int c)
/// Set v:count to "count" and v:count1 to "count1".
///
/// @param set_prevcount if true, first set v:prevcount from v:count.
-void set_vcount(long count, long count1, int set_prevcount)
+void set_vcount(int64_t count, int64_t count1, bool set_prevcount)
{
if (set_prevcount) {
vimvars[VV_PREVCOUNT].vv_nr = vimvars[VV_COUNT].vv_nr;
@@ -6681,14 +7223,23 @@ void set_vim_var_dict(const VimVarIndex idx, dict_T *const val)
tv_dict_set_keys_readonly(val);
}
+/// Set v:variable to tv.
+///
+/// @param[in] idx Index of variable to set.
+/// @param[in] val Value to set to. Will be copied.
+void set_vim_var_tv(const VimVarIndex idx, typval_T *const tv)
+{
+ tv_clear(&vimvars[idx].vv_di.di_tv);
+ tv_copy(tv, &vimvars[idx].vv_di.di_tv);
+}
+
/// Set the v:argv list.
void set_argv_var(char **argv, int argc)
{
list_T *l = tv_list_alloc(argc);
- int i;
tv_list_set_lock(l, VAR_FIXED);
- for (i = 0; i < argc; i++) {
+ for (int i = 0; i < argc; i++) {
tv_list_append_string(l, (const char *const)argv[i], -1);
TV_LIST_ITEM_TV(tv_list_last(l))->v_lock = VAR_FIXED;
}
@@ -6747,20 +7298,18 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
{
char *oldval = vimvars[VV_CMDARG].vv_str;
if (eap == NULL) {
- xfree(oldval);
- vimvars[VV_CMDARG].vv_str = oldarg;
- return NULL;
+ goto error;
}
size_t len = 0;
if (eap->force_bin == FORCE_BIN) {
- len = 6;
+ len += 6; // " ++bin"
} else if (eap->force_bin == FORCE_NOBIN) {
- len = 8;
+ len += 8; // " ++nobin"
}
if (eap->read_edit) {
- len += 7;
+ len += 7; // " ++edit"
}
if (eap->force_ff != 0) {
@@ -6773,48 +7322,89 @@ char *set_cmdarg(exarg_T *eap, char *oldarg)
len += 7 + 4; // " ++bad=" + "keep" or "drop"
}
if (eap->mkdir_p != 0) {
- len += 4;
+ len += 4; // " ++p"
}
const size_t newval_len = len + 1;
char *newval = xmalloc(newval_len);
+ size_t xlen = 0;
+ int rc = 0;
if (eap->force_bin == FORCE_BIN) {
- snprintf(newval, newval_len, " ++bin");
+ rc = snprintf(newval, newval_len, " ++bin");
} else if (eap->force_bin == FORCE_NOBIN) {
- snprintf(newval, newval_len, " ++nobin");
+ rc = snprintf(newval, newval_len, " ++nobin");
} else {
*newval = NUL;
}
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
if (eap->read_edit) {
- STRCAT(newval, " ++edit");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++edit");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
if (eap->force_ff != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++ff=%s",
- eap->force_ff == 'u' ? "unix" :
- eap->force_ff == 'd' ? "dos" : "mac");
+ rc = snprintf(newval + xlen,
+ newval_len - xlen,
+ " ++ff=%s",
+ eap->force_ff == 'u' ? "unix"
+ : eap->force_ff == 'd' ? "dos" : "mac");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
if (eap->force_enc != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++enc=%s",
- eap->cmd + eap->force_enc);
+ rc = snprintf(newval + (xlen), newval_len - xlen, " ++enc=%s", eap->cmd + eap->force_enc);
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
+
if (eap->bad_char == BAD_KEEP) {
- STRCPY(newval + strlen(newval), " ++bad=keep");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=keep");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
} else if (eap->bad_char == BAD_DROP) {
- STRCPY(newval + strlen(newval), " ++bad=drop");
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=drop");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
} else if (eap->bad_char != 0) {
- snprintf(newval + strlen(newval), newval_len, " ++bad=%c",
- eap->bad_char);
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++bad=%c", eap->bad_char);
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
- if (eap->mkdir_p) {
- snprintf(newval, newval_len, " ++p");
+ if (eap->mkdir_p != 0) {
+ rc = snprintf(newval + xlen, newval_len - xlen, " ++p");
+ if (rc < 0) {
+ goto error;
+ }
+ xlen += (size_t)rc;
}
+ assert(xlen <= newval_len);
vimvars[VV_CMDARG].vv_str = newval;
return oldval;
+
+error:
+ xfree(oldval);
+ vimvars[VV_CMDARG].vv_str = oldarg;
+ return NULL;
}
/// Check if variable "name[len]" is a local variable or an argument.
@@ -6871,6 +7461,88 @@ int check_luafunc_name(const char *const str, const bool paren)
return (int)(p - str);
}
+/// Return the character "str[index]" where "index" is the character index. If
+/// "index" is out of range NULL is returned.
+char *char_from_string(const char *str, varnumber_T index)
+{
+ size_t nbyte = 0;
+ varnumber_T nchar = index;
+
+ if (str == NULL || index < 0) {
+ return NULL;
+ }
+ size_t slen = strlen(str);
+ while (nchar > 0 && nbyte < slen) {
+ nbyte += (size_t)utf_ptr2len(str + nbyte);
+ nchar--;
+ }
+ if (nbyte >= slen) {
+ return NULL;
+ }
+ return xmemdupz(str + nbyte, (size_t)utf_ptr2len(str + nbyte));
+}
+
+/// Get the byte index for character index "idx" in string "str" with length
+/// "str_len".
+/// If going over the end return "str_len".
+/// If "idx" is negative count from the end, -1 is the last character.
+/// When going over the start return -1.
+static ssize_t char_idx2byte(const char *str, size_t str_len, varnumber_T idx)
+{
+ varnumber_T nchar = idx;
+ size_t nbyte = 0;
+
+ if (nchar >= 0) {
+ while (nchar > 0 && nbyte < str_len) {
+ nbyte += (size_t)utf_ptr2len(str + nbyte);
+ nchar--;
+ }
+ } else {
+ nbyte = str_len;
+ while (nchar < 0 && nbyte > 0) {
+ nbyte--;
+ nbyte -= (size_t)utf_head_off(str, str + nbyte);
+ nchar++;
+ }
+ if (nchar < 0) {
+ return -1;
+ }
+ }
+ return (ssize_t)nbyte;
+}
+
+/// Return the slice "str[first:last]" using character indexes.
+///
+/// @param exclusive true for slice().
+///
+/// Return NULL when the result is empty.
+char *string_slice(const char *str, varnumber_T first, varnumber_T last, bool exclusive)
+{
+ if (str == NULL) {
+ return NULL;
+ }
+ size_t slen = strlen(str);
+ ssize_t start_byte = char_idx2byte(str, slen, first);
+ if (start_byte < 0) {
+ start_byte = 0; // first index very negative: use zero
+ }
+ ssize_t end_byte;
+ if ((last == -1 && !exclusive) || last == VARNUMBER_MAX) {
+ end_byte = (ssize_t)slen;
+ } else {
+ end_byte = char_idx2byte(str, slen, last);
+ if (!exclusive && end_byte >= 0 && end_byte < (ssize_t)slen) {
+ // end index is inclusive
+ end_byte += utf_ptr2len(str + end_byte);
+ }
+ }
+
+ if (start_byte >= (ssize_t)slen || end_byte <= start_byte) {
+ return NULL;
+ }
+ return xmemdupz(str + start_byte, (size_t)(end_byte - start_byte));
+}
+
/// Handle:
/// - expr[expr], expr[expr:expr] subscript
/// - ".name" lookup
@@ -6879,13 +7551,13 @@ int check_luafunc_name(const char *const str, const bool paren)
///
/// Can all be combined in any order: dict.func(expr)[idx]['func'](expr)->len()
///
-/// @param evaluate do more than finding the end
/// @param verbose give error messages
/// @param start_leader start of '!' and '-' prefixes
/// @param end_leaderp end of '!' and '-' prefixes
-int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int verbose,
- const char *const start_leader, const char **const end_leaderp)
+int handle_subscript(const char **const arg, typval_T *rettv, evalarg_T *const evalarg,
+ bool verbose)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
int ret = OK;
dict_T *selfdict = NULL;
const char *lua_funcname = NULL;
@@ -6914,7 +7586,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
&& !ascii_iswhite(*(*arg - 1)))
|| (**arg == '-' && (*arg)[1] == '>'))) {
if (**arg == '(') {
- ret = call_func_rettv((char **)arg, rettv, evaluate, selfdict, NULL, lua_funcname);
+ ret = call_func_rettv((char **)arg, evalarg, rettv, evaluate, selfdict, NULL, lua_funcname);
// Stop the expression evaluation when immediately aborting on
// error, or when an interrupt occurred or an exception was thrown
@@ -6928,19 +7600,12 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
tv_dict_unref(selfdict);
selfdict = NULL;
} else if (**arg == '-') {
- // Expression "-1.0->method()" applies the leader "-" before
- // applying ->.
- if (evaluate && *end_leaderp > start_leader) {
- ret = eval7_leader(rettv, (char *)start_leader, end_leaderp);
- }
- if (ret == OK) {
- if ((*arg)[2] == '{') {
- // expr->{lambda}()
- ret = eval_lambda((char **)arg, rettv, evaluate, verbose);
- } else {
- // expr->name()
- ret = eval_method((char **)arg, rettv, evaluate, verbose);
- }
+ if ((*arg)[2] == '{') {
+ // expr->{lambda}()
+ ret = eval_lambda((char **)arg, rettv, evalarg, verbose);
+ } else {
+ // expr->name()
+ ret = eval_method((char **)arg, rettv, evalarg, verbose);
}
} else { // **arg == '[' || **arg == '.'
tv_dict_unref(selfdict);
@@ -6952,7 +7617,7 @@ int handle_subscript(const char **const arg, typval_T *rettv, int evaluate, int
} else {
selfdict = NULL;
}
- if (eval_index((char **)arg, rettv, evaluate, verbose) == FAIL) {
+ if (eval_index((char **)arg, rettv, evalarg, verbose) == FAIL) {
tv_clear(rettv);
ret = FAIL;
}
@@ -7105,7 +7770,7 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
if (funccal == NULL) { // global variable
*d = &globvardict;
} else { // l: variable
- *d = &funccal->l_vars;
+ *d = &funccal->fc_l_vars;
}
goto end;
}
@@ -7129,13 +7794,13 @@ hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, const char
} else if (*name == 'v') { // v: variable
*d = &vimvardict;
} else if (*name == 'a' && funccal != NULL) { // function argument
- *d = &funccal->l_avars;
+ *d = &funccal->fc_l_avars;
} else if (*name == 'l' && funccal != NULL) { // local variable
- *d = &funccal->l_vars;
+ *d = &funccal->fc_l_vars;
} else if (*name == 's' // script variable
&& (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR
|| current_sctx.sc_sid == SID_LUA)
- && current_sctx.sc_sid <= ga_scripts.ga_len) {
+ && current_sctx.sc_sid <= script_items.ga_len) {
// For anonymous scripts without a script item, create one now so script vars can be used
if (current_sctx.sc_sid == SID_LUA) {
// try to resolve lua filename & line no so it can be shown in lastset messages.
@@ -7186,27 +7851,9 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var
/// sourcing this script and when executing functions defined in the script.
void new_script_vars(scid_T id)
{
- scriptvar_T *sv;
-
- ga_grow(&ga_scripts, id - ga_scripts.ga_len);
-
- // Re-allocating ga_data means that an ht_array pointing to
- // ht_smallarray becomes invalid. We can recognize this: ht_mask is
- // at its init value. Also reset "v_dict", it's always the same.
- for (int i = 1; i <= ga_scripts.ga_len; i++) {
- hashtab_T *ht = &SCRIPT_VARS(i);
- if (ht->ht_mask == HT_INIT_SIZE - 1) {
- ht->ht_array = ht->ht_smallarray;
- }
- sv = SCRIPT_SV(i);
- sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict;
- }
-
- while (ga_scripts.ga_len < id) {
- sv = SCRIPT_SV(ga_scripts.ga_len + 1) = xcalloc(1, sizeof(scriptvar_T));
- init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
- ga_scripts.ga_len++;
- }
+ scriptvar_T *sv = xcalloc(1, sizeof(scriptvar_T));
+ init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE);
+ SCRIPT_ITEM(id)->sn_vars = sv;
}
/// Initialize dictionary "dict" as a scope and set variable "dict_var" to
@@ -7259,7 +7906,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
int ret = OK;
if (recurse >= DICT_MAXNEST) {
- emsg(_("E698: variable nested too deep for making a copy"));
+ emsg(_(e_variable_nested_too_deep_for_making_copy));
return FAIL;
}
recurse++;
@@ -7305,7 +7952,7 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c
}
break;
case VAR_BLOB:
- tv_blob_copy(from, to);
+ tv_blob_copy(from->vval.v_blob, to);
break;
case VAR_DICT:
to->v_type = VAR_DICT;
@@ -7342,6 +7989,9 @@ void ex_echo(exarg_T *eap)
bool need_clear = true;
const int did_emsg_before = did_emsg;
const int called_emsg_before = called_emsg;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip) {
emsg_skip++;
@@ -7353,7 +8003,7 @@ void ex_echo(exarg_T *eap)
{
char *p = arg;
- if (eval1(&arg, &rettv, !eap->skip) == FAIL) {
+ if (eval1(&arg, &rettv, &evalarg) == FAIL) {
// Report the invalid expression unless the expression evaluation
// has been cancelled due to an aborting error, an interrupt, or an
// exception.
@@ -7385,7 +8035,7 @@ void ex_echo(exarg_T *eap)
char *tofree = encode_tv2echo(&rettv, NULL);
if (*tofree != NUL) {
msg_ext_set_kind("echo");
- msg_multiline_attr(tofree, echo_attr, true, &need_clear);
+ msg_multiline(tofree, echo_attr, true, &need_clear);
}
xfree(tofree);
}
@@ -7393,6 +8043,7 @@ void ex_echo(exarg_T *eap)
arg = skipwhite(arg);
}
eap->nextcmd = check_nextcmd(arg);
+ clear_evalarg(&evalarg, eap);
if (eap->skip) {
emsg_skip--;
@@ -7431,23 +8082,23 @@ void ex_execute(exarg_T *eap)
emsg_skip++;
}
while (*arg != NUL && *arg != '|' && *arg != '\n') {
- ret = eval1_emsg(&arg, &rettv, !eap->skip);
+ ret = eval1_emsg(&arg, &rettv, eap);
if (ret == FAIL) {
break;
}
if (!eap->skip) {
const char *const argstr = eap->cmdidx == CMD_execute
- ? tv_get_string(&rettv)
- : rettv.v_type == VAR_STRING
- ? encode_tv2echo(&rettv, NULL)
- : encode_tv2string(&rettv, NULL);
+ ? tv_get_string(&rettv)
+ : rettv.v_type == VAR_STRING
+ ? encode_tv2echo(&rettv, NULL)
+ : encode_tv2string(&rettv, NULL);
const size_t len = strlen(argstr);
ga_grow(&ga, (int)len + 2);
if (!GA_EMPTY(&ga)) {
- ((char_u *)(ga.ga_data))[ga.ga_len++] = ' ';
+ ((char *)(ga.ga_data))[ga.ga_len++] = ' ';
}
- memcpy((char_u *)(ga.ga_data) + ga.ga_len, argstr, len + 1);
+ memcpy((char *)(ga.ga_data) + ga.ga_len, argstr, len + 1);
if (eap->cmdidx != CMD_execute) {
xfree((void *)argstr);
}
@@ -7459,22 +8110,14 @@ void ex_execute(exarg_T *eap)
}
if (ret != FAIL && ga.ga_data != NULL) {
- if (eap->cmdidx == CMD_echomsg || eap->cmdidx == CMD_echoerr) {
- // Mark the already saved text as finishing the line, so that what
- // follows is displayed on a new line when scrolling back at the
- // more prompt.
- msg_sb_eol();
- }
-
if (eap->cmdidx == CMD_echomsg) {
msg_ext_set_kind("echomsg");
- msg_attr(ga.ga_data, echo_attr);
- ui_flush();
+ msg(ga.ga_data, echo_attr);
} else if (eap->cmdidx == CMD_echoerr) {
// We don't want to abort following commands, restore did_emsg.
int save_did_emsg = did_emsg;
msg_ext_set_kind("echoerr");
- emsg(ga.ga_data);
+ emsg_multiline(ga.ga_data, true);
if (!force_abort) {
did_emsg = save_did_emsg;
}
@@ -7528,80 +8171,6 @@ const char *find_option_end(const char **const arg, int *const scope)
return p;
}
-/// Return the autoload script name for a function or variable name
-/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-///
-/// @return [allocated] autoload script name.
-char *autoload_name(const char *const name, const size_t name_len)
- FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // Get the script file name: replace '#' with '/', append ".vim".
- char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
- memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
- memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
- size_t auchar_idx = 0;
- for (size_t i = sizeof("autoload/") - 1;
- i - sizeof("autoload/") + 1 < name_len;
- i++) {
- if (scriptname[i] == AUTOLOAD_CHAR) {
- scriptname[i] = '/';
- auchar_idx = i;
- }
- }
- memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
-
- return scriptname;
-}
-
-/// If name has a package name try autoloading the script for it
-///
-/// @param[in] name Variable/function name.
-/// @param[in] name_len Name length.
-/// @param[in] reload If true, load script again when already loaded.
-///
-/// @return true if a package was loaded.
-bool script_autoload(const char *const name, const size_t name_len, const bool reload)
-{
- // If there is no '#' after name[0] there is no package name.
- const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
- if (p == NULL || p == name) {
- return false;
- }
-
- bool ret = false;
- char *tofree = autoload_name(name, name_len);
- char *scriptname = tofree;
-
- // Find the name in the list of previously loaded package names. Skip
- // "autoload/", it's always the same.
- int i = 0;
- for (; i < ga_loaded.ga_len; i++) {
- if (strcmp(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) {
- break;
- }
- }
- if (!reload && i < ga_loaded.ga_len) {
- ret = false; // Was loaded already.
- } else {
- // Remember the name if it wasn't loaded already.
- if (i == ga_loaded.ga_len) {
- GA_APPEND(char *, &ga_loaded, scriptname);
- tofree = NULL;
- }
-
- // Try loading the package from $VIMRUNTIME/autoload/<name>.vim
- if (source_runtime(scriptname, 0) == OK) {
- ret = true;
- }
- }
-
- xfree(tofree);
- return ret;
-}
-
static var_flavour_T var_flavour(char *varname)
FUNC_ATTR_PURE
{
@@ -7650,7 +8219,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, typv
} else {
hi = (const hashitem_T *)iter;
}
- *name = (char *)TV_DICT_HI2DI(hi)->di_key;
+ *name = TV_DICT_HI2DI(hi)->di_key;
tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv);
while ((size_t)(++hi - hifirst) < hinum) {
if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) {
@@ -7674,10 +8243,10 @@ int store_session_globals(FILE *fd)
TV_DICT_ITER(&globvardict, this_var, {
if ((this_var->di_tv.v_type == VAR_NUMBER
|| this_var->di_tv.v_type == VAR_STRING)
- && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) {
+ && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) {
// Escape special characters with a backslash. Turn a LF and
// CR into \n and \r.
- char *const p = (char *)vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r");
+ char *const p = vim_strsave_escaped(tv_get_string(&this_var->di_tv), "\\\"\n\r");
for (char *t = p; *t != NUL; t++) {
if (*t == '\n') {
*t = 'n';
@@ -7696,7 +8265,7 @@ int store_session_globals(FILE *fd)
}
xfree(p);
} else if (this_var->di_tv.v_type == VAR_FLOAT
- && var_flavour((char *)this_var->di_key) == VAR_FLAVOUR_SESSION) {
+ && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) {
float_T f = this_var->di_tv.vval.v_float;
int sign = ' ';
@@ -7737,7 +8306,7 @@ void option_last_set_msg(LastSet last_set)
msg_puts(p);
if (last_set.script_ctx.sc_lnum > 0) {
msg_puts(_(line_msg));
- msg_outnum((long)last_set.script_ctx.sc_lnum);
+ msg_outnum(last_set.script_ctx.sc_lnum);
}
if (should_free) {
xfree(p);
@@ -7843,7 +8412,7 @@ repeat:
// ":~" - path relative to the home directory
// ":8" - shortname path - postponed till after
while (src[*usedlen] == ':'
- && ((c = (char_u)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
+ && ((c = (uint8_t)src[*usedlen + 1]) == '.' || c == '~' || c == '8')) {
*usedlen += 2;
if (c == '8') {
continue;
@@ -7987,7 +8556,7 @@ repeat:
// "path/to/this.file.ext" :r:r:r
// ^ ^------------- tail
// +--------------------- *fnamep
- if (s > MAX(tail, (char *)(*fnamep))) {
+ if (s > MAX(tail, *fnamep)) {
*fnamelen = (size_t)(s - *fnamep);
}
}
@@ -8013,13 +8582,13 @@ repeat:
// find end of pattern
p = vim_strchr(s, sep);
if (p != NULL) {
- char *const pat = xstrnsave(s, (size_t)(p - s));
+ char *const pat = xmemdupz(s, (size_t)(p - s));
s = p + 1;
// find end of substitution
p = vim_strchr(s, sep);
if (p != NULL) {
- char *const sub = xstrnsave(s, (size_t)(p - s));
- char *const str = xstrnsave(*fnamep, *fnamelen);
+ char *const sub = xmemdupz(s, (size_t)(p - s));
+ char *const str = xmemdupz(*fnamep, *fnamelen);
*usedlen = (size_t)(p + 1 - src);
s = do_string_sub(str, pat, sub, NULL, flags);
*fnamep = s;
@@ -8065,14 +8634,13 @@ repeat:
/// @return an allocated string, NULL for error.
char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char *flags)
{
- int sublen;
regmatch_T regmatch;
garray_T ga;
char *zero_width = NULL;
// Make 'cpoptions' empty, so that the 'l' flag doesn't work here
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
ga_init(&ga, 1, 200);
@@ -8081,6 +8649,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
regmatch.rm_ic = p_ic;
regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
+ int sublen;
char *tail = str;
char *end = str + strlen(str);
while (vim_regexec_nl(&regmatch, str, (colnr_T)(tail - str))) {
@@ -8089,7 +8658,7 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
if (zero_width == regmatch.startp[0]) {
// avoid getting stuck on a match with an empty string
int i = utfc_ptr2len(tail);
- memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
+ memmove((char *)ga.ga_data + ga.ga_len, tail, (size_t)i);
ga.ga_len += i;
tail += i;
continue;
@@ -8103,12 +8672,16 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
// - The substituted text.
// - The text after the match.
sublen = vim_regsub(&regmatch, sub, expr, tail, 0, REGSUB_MAGIC);
+ if (sublen <= 0) {
+ ga_clear(&ga);
+ break;
+ }
ga_grow(&ga, (int)((end - tail) + sublen -
(regmatch.endp[0] - regmatch.startp[0])));
// copy the text up to where the match is
int i = (int)(regmatch.startp[0] - tail);
- memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i);
+ memmove((char *)ga.ga_data + ga.ga_len, tail, (size_t)i);
// add the substituted text
(void)vim_regsub(&regmatch, sub, expr,
(char *)ga.ga_data + ga.ga_len + i, sublen,
@@ -8132,14 +8705,14 @@ char *do_string_sub(char *str, char *pat, char *sub, typval_T *expr, const char
char *ret = xstrdup(ga.ga_data == NULL ? str : ga.ga_data);
ga_clear(&ga);
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, evaluating {sub} expression or {expr} changed the value.
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
@@ -8203,7 +8776,7 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
}
list_T *args = tv_list_alloc(1);
- tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1);
+ tv_list_append_string(args, argvars[0].vval.v_string, -1);
*rettv = eval_call_provider(name, "eval", args, false);
}
@@ -8217,7 +8790,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
return (typval_T){
.v_type = VAR_NUMBER,
.v_lock = VAR_UNLOCKED,
- .vval.v_number = (varnumber_T)0
+ .vval.v_number = 0
};
}
@@ -8231,6 +8804,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo
.es_entry = ((estack_T *)exestack.ga_data)[exestack.ga_len - 1],
.autocmd_fname = autocmd_fname,
.autocmd_match = autocmd_match,
+ .autocmd_fname_full = autocmd_fname_full,
.autocmd_bufnr = autocmd_bufnr,
.funccalp = (void *)get_current_funccal()
};
@@ -8281,7 +8855,7 @@ bool eval_has_provider(const char *feat)
return false;
}
- char name[32]; // Normalized: "python_compiled" => "python".
+ char name[32]; // Normalized: "python3_compiled" => "python3".
snprintf(name, sizeof(name), "%s", feat);
strchrsub(name, '_', '\0'); // Chop any "_xx" suffix.
@@ -8289,14 +8863,14 @@ bool eval_has_provider(const char *feat)
typval_T tv;
// Get the g:loaded_xx_provider variable.
int len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
- if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) {
// Trigger autoload once.
len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name);
script_autoload(buf, (size_t)len, false);
// Retry the (non-autoload-style) variable.
len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name);
- if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) {
+ if (eval_variable(buf, len, &tv, NULL, false, true) == FAIL) {
// Show a hint if Call() is defined but g:loaded_xx_provider is missing.
snprintf(buf, sizeof(buf), "provider#%s#Call", name);
if (!!find_func(buf) && p_lpl) {
@@ -8308,8 +8882,8 @@ bool eval_has_provider(const char *feat)
}
bool ok = (tv.v_type == VAR_NUMBER)
- ? 2 == tv.vval.v_number // Value of 2 means "loaded and working".
- : false;
+ ? 2 == tv.vval.v_number // Value of 2 means "loaded and working".
+ : false;
if (ok) {
// Call() must be defined if provider claims to be working.
@@ -8337,33 +8911,27 @@ void eval_fmt_source_name_line(char *buf, size_t bufsize)
/// ":checkhealth [plugins]"
void ex_checkhealth(exarg_T *eap)
{
- bool found = !!find_func("health#check");
- if (!found
- && script_autoload("health#check", sizeof("health#check") - 1, false)) {
- found = !!find_func("health#check");
+ Error err = ERROR_INIT;
+ MAXSIZE_TEMP_ARRAY(args, 1);
+ ADD_C(args, CSTR_AS_OBJ(eap->arg));
+ NLUA_EXEC_STATIC("return vim.health._check(...)", args, &err);
+ if (!ERROR_SET(&err)) {
+ return;
}
- if (!found) {
- const char *vimruntime_env = os_getenv("VIMRUNTIME");
- if (vimruntime_env == NULL) {
- emsg(_("E5009: $VIMRUNTIME is empty or unset"));
+
+ const char *vimruntime_env = os_getenv("VIMRUNTIME");
+ if (vimruntime_env == NULL) {
+ emsg(_("E5009: $VIMRUNTIME is empty or unset"));
+ } else {
+ bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env);
+ if (rtp_ok) {
+ semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env);
} else {
- bool rtp_ok = NULL != strstr(p_rtp, vimruntime_env);
- if (rtp_ok) {
- semsg(_("E5009: Invalid $VIMRUNTIME: %s"), vimruntime_env);
- } else {
- emsg(_("E5009: Invalid 'runtimepath'"));
- }
+ emsg(_("E5009: Invalid 'runtimepath'"));
}
- return;
}
-
- size_t bufsize = strlen(eap->arg) + sizeof("call health#check('')");
- char *buf = xmalloc(bufsize);
- snprintf(buf, bufsize, "call health#check('%s')", eap->arg);
-
- do_cmdline_cmd(buf);
-
- xfree(buf);
+ semsg_multiline(err.msg);
+ api_clear_error(&err);
}
void invoke_prompt_callback(void)
@@ -8425,7 +8993,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
const bool type_is = type == EXPR_IS || type == EXPR_ISNOT;
if (type_is && typ1->v_type != typ2->v_type) {
- // For "is" a different type always means false, for "notis"
+ // For "is" a different type always means false, for "isnot"
// it means true.
n1 = type == EXPR_ISNOT;
} else if (typ1->v_type == VAR_BLOB || typ2->v_type == VAR_BLOB) {
@@ -8506,8 +9074,9 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
}
if ((typ1->v_type == VAR_PARTIAL && typ1->vval.v_partial == NULL)
|| (typ2->v_type == VAR_PARTIAL && typ2->vval.v_partial == NULL)) {
- // when a partial is NULL assume not equal
- n1 = false;
+ // When both partials are NULL, then they are equal.
+ // Otherwise they are not equal.
+ n1 = (typ1->vval.v_partial == typ2->vval.v_partial);
} else if (type_is) {
if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) {
// strings are considered the same if their value is
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 86bc76e793..1fc2891917 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -1,15 +1,18 @@
-#ifndef NVIM_EVAL_H
-#define NVIM_EVAL_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/globals.h"
+#include "nvim/hashtab_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/os/fileio.h"
#include "nvim/os/stdpaths_defs.h"
@@ -51,11 +54,11 @@ typedef struct lval_S {
list_T *ll_list; ///< The list or NULL.
bool ll_range; ///< true when a [i:j] range was used.
bool ll_empty2; ///< Second index is empty: [i:].
- long ll_n1; ///< First index for list.
- long ll_n2; ///< Second index for list range.
+ int ll_n1; ///< First index for list.
+ int ll_n2; ///< Second index for list range.
dict_T *ll_dict; ///< The Dictionary or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
- char *ll_newkey; ///< New key for Dict in allocated memory or NULL.
+ char *ll_newkey; ///< New key for Dict in allocated memory or NULL.
blob_T *ll_blob; ///< The Blob or NULL.
} lval_T;
@@ -157,6 +160,7 @@ typedef enum {
VV_ARGV,
VV_COLLATE,
VV_EXITING,
+ VV_MAXCOL,
// Nvim
VV_STDERR,
VV_MSGPACK_TYPES,
@@ -222,22 +226,12 @@ typedef struct {
int repeat_count;
int refcount;
int emsg_count; ///< Errors in a repeating timer.
- long timeout;
+ int64_t timeout;
bool stopped;
bool paused;
Callback callback;
} timer_T;
-/// Type of assert_* check being performed
-typedef enum {
- ASSERT_EQUAL,
- ASSERT_NOTEQUAL,
- ASSERT_MATCH,
- ASSERT_NOTMATCH,
- ASSERT_INRANGE,
- ASSERT_OTHER,
-} assert_type_T;
-
/// types for expressions.
typedef enum {
EXPR_UNKNOWN = 0,
@@ -265,7 +259,30 @@ typedef int (*ex_unletlock_callback)(lval_T *, char *, exarg_T *, int);
// Used for checking if local variables or arguments used in a lambda.
extern bool *eval_lavars_used;
+/// Struct passed through eval() functions.
+/// See EVALARG_EVALUATE for a fixed value with eval_flags set to EVAL_EVALUATE.
+typedef struct {
+ int eval_flags; ///< EVAL_ flag values below
+
+ /// copied from exarg_T when "getline" is "getsourceline". Can be NULL.
+ LineGetter eval_getline;
+ void *eval_cookie; ///< argument for eval_getline()
+
+ /// pointer to the last line obtained with getsourceline()
+ char *eval_tofree;
+} evalarg_T;
+
+/// Flag for expression evaluation.
+enum {
+ EVAL_EVALUATE = 1, ///< when missing don't actually evaluate
+};
+
+// Character used as separated in autoload function/variable names.
+#define AUTOLOAD_CHAR '#'
+
+/// Passed to an eval() function to enable evaluation.
+EXTERN evalarg_T EVALARG_EVALUATE INIT( = { EVAL_EVALUATE, NULL, NULL, NULL });
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval.h.generated.h"
#endif
-#endif // NVIM_EVAL_H
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 9a5ab51c71..55f4721c3a 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -2,462 +2,12821 @@
--
-- Keys:
--
--- args Number of arguments, list with maximum and minimum number of arguments
--- or list with a minimum number of arguments only. Defaults to zero
--- arguments.
--- base For methods: the argument to use as the base argument (1-indexed):
--- base->method()
--- Defaults to BASE_NONE (function cannot be used as a method).
--- func Name of the C function which implements the VimL function. Defaults to
--- `f_{funcname}`.
--- fast Function can run in |api-fast| events. Defaults to false.
-
-local varargs = function(nr)
- return {nr}
-end
+--- @class vim.EvalFn
+--- @field name? string
+--- @field args? integer|integer[] Number of arguments, list with maximum and minimum number of arguments
+--- or list with a minimum number of arguments only. Defaults to zero
+--- arguments.
+--- @field base? integer For methods: the argument to use as the base argument (1-indexed):
+--- base->method()
+--- Defaults to BASE_NONE (function cannot be used as a method).
+--- @field func? string Name of the C function which implements the Vimscript function. Defaults to
+--- `f_{funcname}`.
+--- @field float_func? string
+--- @field fast? boolean Function can run in |api-fast| events. Defaults to false.
+--- @field deprecated? true
+--- @field returns? string|false
+--- @field returns_desc? string
+--- @field signature string
+--- @field desc? string
+--- @field params {[1]:string, [2]:string, [3]:string}[]
+--- @field lua? false Do not render type information
+--- @field tags? string[] Extra tags
+--- @field data? string Used by gen_eval.lua
-- Usable with the base key: use the last function argument as the method base.
-- Value is from funcs.h file. "BASE_" prefix is omitted.
-- local LAST = "BASE_LAST" (currently unused after port of v8.2.1168)
-return {
- funcs={
- abs={args=1, base=1},
- acos={args=1, base=1, float_func="acos"}, -- WJMc
- add={args=2, base=1},
- ['and']={args=2, base=1},
- api_info={fast=true},
- append={args=2, base=2},
- appendbufline={args=3, base=3},
- argc={args={0, 1}},
- argidx={},
- arglistid={args={0, 2}},
- argv={args={0, 2}},
- asin={args=1, base=1, float_func="asin"}, -- WJMc
- assert_beeps={args=1, base=1},
- assert_equal={args={2, 3}, base=2},
- assert_equalfile={args={2, 3}, base=1},
- assert_exception={args={1, 2}},
- assert_fails={args={1, 5}, base=1},
- assert_false={args={1, 2}, base=1},
- assert_inrange={args={3, 4}, base=3},
- assert_match={args={2, 3}, base=2},
- assert_nobeep={args=1, base=1},
- assert_notequal={args={2, 3}, base=2},
- assert_notmatch={args={2, 3}, base=2},
- assert_report={args=1, base=1},
- assert_true={args={1, 2}, base=1},
- atan={args=1, base=1, float_func="atan"},
- atan2={args=2, base=1},
- browse={args=4},
- browsedir={args=2},
- bufadd={args=1, base=1},
- bufexists={args=1, base=1},
- buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete
- buffer_name={args={0, 1}, base=1, func='f_bufname'}, -- obsolete
- buffer_number={args={0, 1}, base=1, func='f_bufnr'}, -- obsolete
- buflisted={args=1, base=1},
- bufload={args=1, base=1},
- bufloaded={args=1, base=1},
- bufname={args={0, 1}, base=1},
- bufnr={args={0, 2}, base=1},
- bufwinid={args=1, base=1},
- bufwinnr={args=1, base=1},
- byte2line={args=1, base=1},
- byteidx={args=2, base=1, fast=true},
- byteidxcomp={args=2, base=1, fast=true},
- call={args={2, 3}, base=1},
- ceil={args=1, base=1, float_func="ceil"},
- changenr={},
- chanclose={args={1, 2}},
- chansend={args=2},
- char2nr={args={1, 2}, base=1, fast=true},
- charclass={args=1, base=1},
- charcol={args={1, 2}, base=1},
- charidx={args={2, 3}, base=1},
- chdir={args=1, base=1},
- cindent={args=1, base=1},
- clearmatches={args={0, 1}, base=1},
- col={args={1, 2}, base=1},
- complete={args=2, base=2},
- complete_add={args=1, base=1},
- complete_check={},
- complete_info={args={0, 1}, base=1},
- confirm={args={1, 4}, base=1},
- copy={args=1, base=1},
- cos={args=1, base=1, float_func="cos"},
- cosh={args=1, base=1, float_func="cosh"},
- count={args={2, 4}, base=1},
- ctxget={args={0, 1}},
- ctxpop={},
- ctxpush={args={0, 1}},
- ctxset={args={1, 2}},
- ctxsize={},
- cursor={args={1, 3}, base=1},
- debugbreak={args={1, 1}, base=1},
- deepcopy={args={1, 2}, base=1},
- delete={args={1,2}, base=1},
- deletebufline={args={2,3}, base=1},
- dictwatcheradd={args=3},
- dictwatcherdel={args=3},
- did_filetype={fast=true},
- diff_filler={args=1, base=1},
- diff_hlID={args=2, base=1},
- digraph_get={args=1, base=1},
- digraph_getlist={args={0, 1}, base=1},
- digraph_set={args=2, base=1},
- digraph_setlist={args=1, base=1},
- empty={args=1, base=1},
- environ={fast=true},
- escape={args=2, base=1, fast=true},
- eval={args=1, base=1},
- eventhandler={},
- executable={args=1, base=1, fast=true},
- execute={args={1, 2}, base=1},
- exepath={args=1, base=1},
- exists={args=1, base=1},
- exp={args=1, base=1, float_func="exp"},
- expand={args={1, 3}, base=1},
- expandcmd={args={1, 2}, base=1},
- extend={args={2, 3}, base=1},
- feedkeys={args={1, 2}, base=1},
- file_readable={args=1, base=1, func='f_filereadable'}, -- obsolete
- filereadable={args=1, base=1, fast=true},
- filewritable={args=1, base=1, fast=true},
- filter={args=2, base=1},
- finddir={args={1, 3}, base=1},
- findfile={args={1, 3}, base=1},
- flatten={args={1, 2}, base=1},
- float2nr={args=1, base=1},
- floor={args=1, base=1, float_func="floor"},
- fmod={args=2, base=1},
- fnameescape={args=1, base=1, fast=true},
- fnamemodify={args=2, base=1, fast=true},
- foldclosed={args=1, base=1},
- foldclosedend={args=1, base=1},
- foldlevel={args=1, base=1},
- foldtext={},
- foldtextresult={args=1, base=1},
- foreground={},
- fullcommand={args=1, base=1},
- funcref={args={1, 3}, base=1},
- ['function']={args={1, 3}, base=1},
- garbagecollect={args={0, 1}},
- get={args={2, 3}, base=1},
- getbufinfo={args={0, 1}, base=1},
- getbufline={args={2, 3}, base=1},
- getbufoneline={args=2, base=1},
- getbufvar={args={2, 3}, base=1},
- getcellwidths={},
- getchangelist={args={0, 1}, base=1},
- getchar={args={0, 1}},
- getcharmod={},
- getcharpos={args=1, base=1},
- getcharsearch={},
- getcharstr={args={0, 1}},
- getcmdcompltype={},
- getcmdline={},
- getcmdpos={},
- getcmdscreenpos={},
- getcmdtype={},
- getcmdwintype={},
- getcompletion={args={2, 3}, base=1},
- 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}},
- getfperm={args=1, base=1, fast=true},
- getfsize={args=1, base=1, fast=true},
- getftime={args=1, base=1, fast=true},
- getftype={args=1, base=1, fast=true},
- getjumplist={args={0, 2}, base=1},
- getline={args={1, 2}, base=1},
- getloclist={args={1, 2}},
- getmarklist={args={0, 1}, base=1},
- getmatches={args={0, 1}},
- getmousepos={},
- getpid={fast=true},
- getpos={args=1, base=1},
- getqflist={args={0, 1}},
- getreg={args={0, 3}, base=1},
- getreginfo={args={0, 1}, base=1},
- getregtype={args={0, 1}, base=1},
- gettabinfo={args={0, 1}, base=1},
- gettabvar={args={2, 3}, base=1},
- gettabwinvar={args={3, 4}, base=1},
- gettagstack={args={0, 1}, base=1},
- gettext={args=1, base=1},
- getwininfo={args={0, 1}, base=1},
- getwinpos={args={0, 1}, base=1},
- getwinposx={},
- getwinposy={},
- getwinvar={args={2, 3}, base=1},
- glob={args={1, 4}, base=1},
- glob2regpat={args=1, base=1},
- globpath={args={2, 5}, base=2},
- has={args=1},
- has_key={args=2, base=1},
- haslocaldir={args={0, 2}, base=1},
- hasmapto={args={1, 3}, base=1},
- highlightID={args=1, base=1, func='f_hlID'}, -- obsolete
- highlight_exists={args=1, base=1, func='f_hlexists'}, -- obsolete
- histadd={args=2, base=2},
- histdel={args={1, 2}, base=1},
- histget={args={1, 2}, base=1},
- histnr={args=1, base=1},
- hlID={args=1, base=1},
- hlexists={args=1, base=1},
- hostname={fast=true},
- iconv={args=3, base=1, fast=true},
- indent={args=1, base=1},
- index={args={2, 4}, base=1},
- input={args={1, 3}, base=1},
- inputdialog={args={1, 3}, base=1},
- inputlist={args=1, base=1},
- inputrestore={},
- inputsave={},
- inputsecret={args={1, 2}, base=1},
- insert={args={2, 3}, base=1},
- interrupt={args=0},
- invert={args=1, base=1},
- isdirectory={args=1, base=1, fast=true},
- isinf={args=1, base=1},
- islocked={args=1, base=1},
- isnan={args=1, base=1},
- id={args=1},
- items={args=1, base=1},
- jobclose={args={1, 2}, func="f_chanclose"},
- jobpid={args=1},
- jobresize={args=3},
- jobsend={args=2, func="f_chansend"},
- jobstart={args={1, 2}},
- jobstop={args=1},
- jobwait={args={1, 2}},
- join={args={1, 2}, base=1},
- json_decode={args=1, base=1},
- json_encode={args=1, base=1},
- keys={args=1, base=1},
- keytrans={args=1, base=1},
- last_buffer_nr={}, -- obsolete
- len={args=1, base=1},
- libcall={args=3, base=3},
- libcallnr={args=3, base=3},
- line={args={1, 2}, base=1},
- line2byte={args=1, base=1},
- lispindent={args=1, base=1},
- list2str={args={1, 2}, base=1},
- localtime={},
- log={args=1, base=1, float_func="log"},
- log10={args=1, base=1, float_func="log10"},
- luaeval={args={1, 2}, base=1},
- map={args=2, base=1},
- maparg={args={1, 4}, base=1},
- mapcheck={args={1, 3}, base=1},
- mapset={args=3, base=1},
- match={args={2, 4}, base=1},
- matchadd={args={2, 5}, base=1},
- matchaddpos={args={2, 5}, base=1},
- 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},
- max={args=1, base=1},
- menu_get={args={1, 2}},
- menu_info={args={1, 2}, base=1},
- min={args=1, base=1},
- mkdir={args={1, 3}, base=1},
- mode={args={0, 1}, base=1},
- msgpackdump={args={1, 2}},
- msgpackparse={args=1},
- nextnonblank={args=1, base=1},
- nr2char={args={1, 2}, base=1},
- ['or']={args=2, base=1},
- pathshorten={args={1, 2}, base=1},
- pow={args=2, base=1},
- prevnonblank={args=1, base=1},
- printf={args=varargs(1), base=2},
- prompt_getprompt={args=1, base=1},
- prompt_setcallback={args={2, 2}, base=1},
- prompt_setinterrupt={args={2, 2}, base=1},
- prompt_setprompt={args={2, 2}, base=1},
- pum_getpos={},
- pumvisible={},
- py3eval={args=1, base=1},
- 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},
- readblob={args=1, base=1},
- readdir={args={1, 2}, base=1},
- readfile={args={1, 3}, base=1},
- reduce={args={2, 3}, base=1},
- reg_executing={},
- reg_recording={},
- reg_recorded={},
- reltime={args={0, 2}, base=1, fast=true},
- reltimefloat={args=1, base=1, fast=true},
- reltimestr={args=1, base=1, fast=true},
- remove={args={2, 3}, base=1},
- rename={args=2, base=1},
- ['repeat']={args=2, base=1, fast=true},
- resolve={args=1, base=1, fast=true},
- reverse={args=1, base=1},
- round={args=1, base=1, float_func="round"},
- rpcnotify={args=varargs(2)},
- rpcrequest={args=varargs(2)},
- rpcstart={args={1, 2}},
- rpcstop={args=1},
- rubyeval={args=1, base=1},
- screenattr={args=2, base=1},
- screenchar={args=2, base=1},
- screenchars={args=2, base=1},
- screencol={},
- screenpos={args=3, base=1},
- screenrow={},
- screenstring={args=2, base=1},
- search={args={1, 5}, base=1},
- searchcount={args={0, 1}, base=1},
- searchdecl={args={1, 3}, base=1},
- searchpair={args={3, 7}},
- searchpairpos={args={3, 7}},
- searchpos={args={1, 5}, base=1},
- serverlist={},
- serverstart={args={0, 1}},
- serverstop={args=1},
- setbufline={args=3, base=3},
- setbufvar={args=3, base=3},
- setcellwidths={args=1, base=1},
- setcharpos={args=2, base=2},
- setcharsearch={args=1, base=1},
- setcmdpos={args=1, base=1},
- setcmdline={args={1, 2}, base=1},
- setcursorcharpos={args={1, 3}, base=1},
- setenv={args=2, base=2},
- setfperm={args=2, base=1},
- setline={args=2, base=2},
- setloclist={args={2, 4}, base=2},
- setmatches={args={1, 2}, base=1},
- setpos={args=2, base=2},
- setqflist={args={1, 3}, base=1},
- setreg={args={2, 3}, base=2},
- settabvar={args=3, base=3},
- settabwinvar={args=4, base=4},
- settagstack={args={2, 3}, base=2},
- setwinvar={args=3, base=3},
- sha256={args=1, base=1},
- shellescape={args={1, 2}, base=1},
- shiftwidth={args={0, 1}, base=1},
- sign_define={args={1, 2}, base=1},
- sign_getdefined={args={0, 1}, base=1},
- sign_getplaced={args={0, 2}, base=1},
- sign_jump={args=3, base=1},
- sign_place={args={4, 5}, base=1},
- sign_placelist={args=1, base=1},
- sign_undefine={args={0, 1}, base=1},
- sign_unplace={args={1, 2}, base=1},
- sign_unplacelist={args=1, base=1},
- simplify={args=1, base=1},
- sin={args=1, base=1, float_func="sin"},
- sinh={args=1, base=1, float_func="sinh"},
- sockconnect={args={2,3}},
- sort={args={1, 3}, base=1},
- soundfold={args=1, base=1},
- stdioopen={args=1},
- spellbadword={args={0, 1}, base=1},
- spellsuggest={args={1, 3}, base=1},
- split={args={1, 3}, base=1},
- sqrt={args=1, base=1, float_func="sqrt"},
- srand={args={0, 1}, base=1},
- stdpath={args=1, fast=true},
- str2float={args=1, base=1},
- str2list={args={1, 2}, base=1},
- str2nr={args={1, 3}, base=1},
- strcharlen={args=1, base=1},
- strcharpart={args={2, 3}, base=1, fast=true},
- strchars={args={1, 2}, base=1},
- strdisplaywidth={args={1, 2}, base=1},
- strftime={args={1, 2}, base=1},
- strgetchar={args=2, base=1},
- stridx={args={2, 3}, base=1, fast=true},
- string={args=1, base=1},
- strlen={args=1, base=1},
- strpart={args={2, 4}, base=1, fast=true},
- strptime={args=2, base=1},
- strridx={args={2, 3}, base=1},
- strtrans={args=1, base=1, fast=true},
- strwidth={args=1, base=1, fast=true},
- submatch={args={1, 2}, base=1},
- substitute={args=4, base=1},
- swapinfo={args=1, base=1},
- swapname={args=1, base=1},
- synID={args=3},
- synIDattr={args={2, 3}, base=1},
- synIDtrans={args=1, base=1},
- synconcealed={args=2},
- synstack={args=2},
- system={args={1, 2}, base=1},
- systemlist={args={1, 3}, base=1},
- tabpagebuflist={args={0, 1}, base=1},
- tabpagenr={args={0, 1}},
- tabpagewinnr={args={1, 2}, base=1},
- tagfiles={},
- taglist={args={1, 2}, base=1},
- tan={args=1, base=1, float_func="tan"},
- tanh={args=1, base=1, float_func="tanh"},
- tempname={},
- termopen={args={1, 2}},
- test_garbagecollect_now={},
- test_write_list_log={args=1},
- timer_info={args={0, 1}, base=1},
- timer_pause={args=2, base=1},
- timer_start={args={2, 3}, base=1},
- timer_stop={args=1, base=1},
- timer_stopall={args=0},
- tolower={args=1, base=1, fast=true},
- toupper={args=1, base=1, fast=true},
- tr={args=3, base=1},
- trim={args={1, 3}, base=1},
- trunc={args=1, base=1, float_func="trunc"},
- type={args=1, base=1, fast=true},
- undofile={args=1, base=1},
- undotree={},
- uniq={args={1, 3}, base=1},
- values={args=1, base=1},
- virtcol={args=1, base=1},
- virtcol2col={args=3, base=1},
- visualmode={args={0, 1}},
- wait={args={2,3}},
- wildmenumode={},
- win_execute={args={2, 3}, base=2},
- win_findbuf={args=1, base=1},
- win_getid={args={0, 2}, base=1},
- win_gettype={args={0, 1}, base=1},
- win_gotoid={args=1, base=1},
- win_id2tabwin={args=1, base=1},
- win_id2win={args=1, base=1},
- win_move_separator={args=2, base=1},
- win_move_statusline={args=2, base=1},
- win_screenpos={args=1, base=1},
- win_splitmove={args={2, 3}, base=1},
- winbufnr={args=1, base=1},
- wincol={},
- windowsversion={fast=true},
- winheight={args=1, base=1},
- winlayout={args={0, 1}, base=1},
- winline={},
- winnr={args={0, 1}, base=1},
- winrestcmd={},
- winrestview={args=1, base=1},
- winsaveview={},
- winwidth={args=1, base=1},
- wordcount={},
- writefile={args={2, 3}, base=1},
- xor={args=2, base=1},
+local M = {}
+
+local VARARGS = { { '...', 'any' } }
+
+--- @type table<string,vim.EvalFn>
+M.funcs = {
+ abs = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the absolute value of {expr}. When {expr} evaluates to
+ a |Float| abs() returns a |Float|. When {expr} can be
+ converted to a |Number| abs() returns a |Number|. Otherwise
+ abs() gives an error message and returns -1.
+ Examples: >vim
+ echo abs(1.456)
+ < 1.456 >vim
+ echo abs(-5.456)
+ < 5.456 >vim
+ echo abs(-4)
+ < 4
+
+ ]=],
+ name = 'abs',
+ params = { { 'expr', 'any' } },
+ signature = 'abs({expr})',
+ returns = 'number',
+ },
+ acos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the arc cosine of {expr} measured in radians, as a
+ |Float| in the range of [0, pi].
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ [-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo acos(0)
+ < 1.570796 >vim
+ echo acos(-0.5)
+ < 2.094395
+
+ ]=],
+ float_func = 'acos',
+ name = 'acos',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'acos({expr})',
+ },
+ add = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Append the item {expr} to |List| or |Blob| {object}. Returns
+ the resulting |List| or |Blob|. Examples: >vim
+ let alist = add([1, 2, 3], item)
+ call add(mylist, "woodstock")
+ <Note that when {expr} is a |List| it is appended as a single
+ item. Use |extend()| to concatenate |Lists|.
+ When {object} is a |Blob| then {expr} must be a number.
+ Use |insert()| to add an item at another position.
+ Returns 1 if {object} is not a |List| or a |Blob|.
+
+ ]=],
+ name = 'add',
+ params = { { 'object', 'any' }, { 'expr', 'any' } },
+ returns = 'any',
+ signature = 'add({object}, {expr})',
+ },
+ ['and'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise AND on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `or()` and `xor()`.
+ Example: >vim
+ let flag = and(bits, 0x80)
+ <
+ ]=],
+ name = 'and',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'and({expr}, {expr})',
+ },
+ api_info = {
+ desc = [=[
+ Returns Dictionary of |api-metadata|.
+
+ View it in a nice human-readable format: >vim
+ lua vim.print(vim.fn.api_info())
+ <
+ ]=],
+ fast = true,
+ name = 'api_info',
+ params = {},
+ returns = 'table',
+ signature = 'api_info()',
+ },
+ append = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ When {text} is a |List|: Append each item of the |List| as a
+ text line below line {lnum} in the current buffer.
+ Otherwise append {text} as one text line below line {lnum} in
+ the current buffer.
+ Any type of item is accepted and converted to a String.
+ {lnum} can be zero to insert a line before the first one.
+ {lnum} is used like with |getline()|.
+ Returns 1 for failure ({lnum} out of range or out of memory),
+ 0 for success. When {text} is an empty list zero is returned,
+ no matter the value of {lnum}. Example: >vim
+ let failed = append(line('$'), "# THE END")
+ let failed = append(0, ["Chapter 1", "the beginning"])
+ <
+
+ ]=],
+ name = 'append',
+ params = { { 'lnum', 'integer' }, { 'text', 'any' } },
+ returns = '0|1',
+ signature = 'append({lnum}, {text})',
+ },
+ appendbufline = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Like |append()| but append the text in buffer {expr}.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ For the use of {buf}, see |bufname()|.
+
+ {lnum} is the line number to append below. Note that using
+ |line()| would use the current buffer, not the one appending
+ to. Use "$" to append at the end of the buffer. Other string
+ values are not supported.
+
+ On success 0 is returned, on failure 1 is returned.
+
+ If {buf} is not a valid buffer or {lnum} is not valid, an
+ error message is given. Example: >vim
+ let failed = appendbufline(13, 0, "# THE START")
+ <However, when {text} is an empty list then no error is given
+ for an invalid {lnum}, since {lnum} isn't actually used.
+
+ ]=],
+ name = 'appendbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'string' } },
+ returns = '0|1',
+ signature = 'appendbufline({buf}, {lnum}, {text})',
+ },
+ argc = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is the number of files in the argument list. See
+ |arglist|.
+ If {winid} is not supplied, the argument list of the current
+ window is used.
+ If {winid} is -1, the global argument list is used.
+ Otherwise {winid} specifies the window of which the argument
+ list is used: either the window number or the window ID.
+ Returns -1 if the {winid} argument is invalid.
+ ]=],
+ name = 'argc',
+ params = { { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'argc([{winid}])',
+ },
+ argidx = {
+ desc = [=[
+ The result is the current index in the argument list. 0 is
+ the first file. argc() - 1 is the last one. See |arglist|.
+ ]=],
+ name = 'argidx',
+ params = {},
+ returns = 'integer',
+ signature = 'argidx()',
+ },
+ arglistid = {
+ args = { 0, 2 },
+ desc = [=[
+ Return the argument list ID. This is a number which
+ identifies the argument list being used. Zero is used for the
+ global argument list. See |arglist|.
+ Returns -1 if the arguments are invalid.
+
+ Without arguments use the current window.
+ With {winnr} only use this window in the current tab page.
+ With {winnr} and {tabnr} use the window in the specified tab
+ page.
+ {winnr} can be the window number or the |window-ID|.
+ ]=],
+ name = 'arglistid',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = 'integer',
+ signature = 'arglistid([{winnr} [, {tabnr}]])',
+ },
+ argv = {
+ args = { 0, 2 },
+ desc = [=[
+ The result is the {nr}th file in the argument list. See
+ |arglist|. "argv(0)" is the first one. Example: >vim
+ let i = 0
+ while i < argc()
+ let f = escape(fnameescape(argv(i)), '.')
+ exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>'
+ let i = i + 1
+ endwhile
+ <Without the {nr} argument, or when {nr} is -1, a |List| with
+ the whole |arglist| is returned.
+
+ The {winid} argument specifies the window ID, see |argc()|.
+ For the Vim command line arguments see |v:argv|.
+
+ Returns an empty string if {nr}th argument is not present in
+ the argument list. Returns an empty List if the {winid}
+ argument is invalid.
+ ]=],
+ name = 'argv',
+ params = { { 'nr', 'integer' }, { 'winid', 'integer' } },
+ returns = 'string|string[]',
+ signature = 'argv([{nr} [, {winid}]])',
+ },
+ asin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the arc sine of {expr} measured in radians, as a |Float|
+ in the range of [-pi/2, pi/2].
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ [-1, 1].
+ Returns NaN if {expr} is outside the range [-1, 1]. Returns
+ 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo asin(0.8)
+ < 0.927295 >vim
+ echo asin(-0.5)
+ < -0.523599
+
+ ]=],
+ float_func = 'asin',
+ name = 'asin',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'asin({expr})',
+ },
+ assert_beeps = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce a beep or visual bell.
+ Also see |assert_fails()|, |assert_nobeep()| and
+ |assert-return|.
+
+ ]=],
+ name = 'assert_beeps',
+ params = { { 'cmd', 'any' } },
+ returns = '0|1',
+ signature = 'assert_beeps({cmd})',
+ },
+ assert_equal = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ When {expected} and {actual} are not equal an error message is
+ added to |v:errors| and 1 is returned. Otherwise zero is
+ returned. |assert-return|
+ The error is in the form "Expected {expected} but got
+ {actual}". When {msg} is present it is prefixed to that.
+
+ There is no automatic conversion, the String "4" is different
+ from the Number 4. And the number 4 is different from the
+ Float 4.0. The value of 'ignorecase' is not used here, case
+ always matters.
+ Example: >vim
+ assert_equal('foo', 'bar')
+ <Will result in a string to be added to |v:errors|:
+ test.vim line 12: Expected 'foo' but got 'bar' ~
+
+ ]=],
+ name = 'assert_equal',
+ params = { { 'expected', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_equal({expected}, {actual} [, {msg}])',
+ },
+ assert_equalfile = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When the files {fname-one} and {fname-two} do not contain
+ exactly the same text an error message is added to |v:errors|.
+ Also see |assert-return|.
+ When {fname-one} or {fname-two} does not exist the error will
+ mention that.
+
+ ]=],
+ name = 'assert_equalfile',
+ params = {},
+ returns = '0|1',
+ signature = 'assert_equalfile({fname-one}, {fname-two})',
+ },
+ assert_exception = {
+ args = { 1, 2 },
+ desc = [=[
+ When v:exception does not contain the string {error} an error
+ message is added to |v:errors|. Also see |assert-return|.
+ This can be used to assert that a command throws an exception.
+ Using the error number, followed by a colon, avoids problems
+ with translations: >vim
+ try
+ commandthatfails
+ call assert_false(1, 'command should have failed')
+ catch
+ call assert_exception('E492:')
+ endtry
+ <
+ ]=],
+ name = 'assert_exception',
+ params = { { 'error', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_exception({error} [, {msg}])',
+ },
+ assert_fails = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it does
+ NOT produce an error or when {error} is not found in the
+ error message. Also see |assert-return|.
+
+ When {error} is a string it must be found literally in the
+ first reported error. Most often this will be the error code,
+ including the colon, e.g. "E123:". >vim
+ assert_fails('bad cmd', 'E987:')
+ <
+ When {error} is a |List| with one or two strings, these are
+ used as patterns. The first pattern is matched against the
+ first reported error: >vim
+ assert_fails('cmd', ['E987:.*expected bool'])
+ <The second pattern, if present, is matched against the last
+ reported error. To only match the last error use an empty
+ string for the first error: >vim
+ assert_fails('cmd', ['', 'E987:'])
+ <
+ If {msg} is empty then it is not used. Do this to get the
+ default message when passing the {lnum} argument.
+
+ When {lnum} is present and not negative, and the {error}
+ argument is present and matches, then this is compared with
+ the line number at which the error was reported. That can be
+ the line number in a function or in a script.
+
+ When {context} is present it is used as a pattern and matched
+ against the context (script name or function name) where
+ {lnum} is located in.
+
+ Note that beeping is not considered an error, and some failing
+ commands only beep. Use |assert_beeps()| for those.
+
+ ]=],
+ name = 'assert_fails',
+ params = {
+ { 'cmd', 'any' },
+ { 'error', 'any' },
+ { 'msg', 'any' },
+ { 'lnum', 'integer' },
+ { 'context', 'any' },
+ },
+ returns = '0|1',
+ signature = 'assert_fails({cmd} [, {error} [, {msg} [, {lnum} [, {context}]]]])',
+ },
+ assert_false = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ When {actual} is not false an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ The error is in the form "Expected False but got {actual}".
+ When {msg} is present it is prepended to that.
+ Also see |assert-return|.
+
+ A value is false when it is zero. When {actual} is not a
+ number the assert fails.
+
+ ]=],
+ name = 'assert_false',
+ params = { { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_false({actual} [, {msg}])',
+ },
+ assert_inrange = {
+ args = { 3, 4 },
+ base = 3,
+ desc = [=[
+ This asserts number and |Float| values. When {actual} is lower
+ than {lower} or higher than {upper} an error message is added
+ to |v:errors|. Also see |assert-return|.
+ The error is in the form "Expected range {lower} - {upper},
+ but got {actual}". When {msg} is present it is prefixed to
+ that.
+ ]=],
+ name = 'assert_inrange',
+ params = { { 'lower', 'any' }, { 'upper', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_inrange({lower}, {upper}, {actual} [, {msg}])',
+ },
+ assert_match = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ When {pattern} does not match {actual} an error message is
+ added to |v:errors|. Also see |assert-return|.
+ The error is in the form "Pattern {pattern} does not match
+ {actual}". When {msg} is present it is prefixed to that.
+
+ {pattern} is used as with |expr-=~|: The matching is always done
+ like 'magic' was set and 'cpoptions' is empty, no matter what
+ the actual value of 'magic' or 'cpoptions' is.
+
+ {actual} is used as a string, automatic conversion applies.
+ Use "^" and "$" to match with the start and end of the text.
+ Use both to match the whole text.
+
+ Example: >vim
+ assert_match('^f.*o$', 'foobar')
+ <Will result in a string to be added to |v:errors|:
+ test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
+
+ ]=],
+ name = 'assert_match',
+ params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_match({pattern}, {actual} [, {msg}])',
+ },
+ assert_nobeep = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Run {cmd} and add an error message to |v:errors| if it
+ produces a beep or visual bell.
+ Also see |assert_beeps()|.
+
+ ]=],
+ name = 'assert_nobeep',
+ params = { { 'cmd', 'any' } },
+ returns = '0|1',
+ signature = 'assert_nobeep({cmd})',
+ },
+ assert_notequal = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ The opposite of `assert_equal()`: add an error message to
+ |v:errors| when {expected} and {actual} are equal.
+ Also see |assert-return|.
+
+ ]=],
+ name = 'assert_notequal',
+ params = { { 'expected', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_notequal({expected}, {actual} [, {msg}])',
+ },
+ assert_notmatch = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ The opposite of `assert_match()`: add an error message to
+ |v:errors| when {pattern} matches {actual}.
+ Also see |assert-return|.
+
+ ]=],
+ name = 'assert_notmatch',
+ params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_notmatch({pattern}, {actual} [, {msg}])',
+ },
+ assert_report = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Report a test failure directly, using String {msg}.
+ Always returns one.
+
+ ]=],
+ name = 'assert_report',
+ params = { { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_report({msg})',
+ },
+ assert_true = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ When {actual} is not true an error message is added to
+ |v:errors|, like with |assert_equal()|.
+ Also see |assert-return|.
+ A value is |TRUE| when it is a non-zero number or |v:true|.
+ When {actual} is not a number or |v:true| the assert fails.
+ When {msg} is given it precedes the default message.
+
+ ]=],
+ name = 'assert_true',
+ params = { { 'actual', 'any' }, { 'msg', 'any' } },
+ returns = '0|1',
+ signature = 'assert_true({actual} [, {msg}])',
+ },
+ atan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the principal value of the arc tangent of {expr}, in
+ the range [-pi/2, +pi/2] radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo atan(100)
+ < 1.560797 >vim
+ echo atan(-4.01)
+ < -1.326405
+
+ ]=],
+ float_func = 'atan',
+ name = 'atan',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'atan({expr})',
+ },
+ atan2 = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the arc tangent of {expr1} / {expr2}, measured in
+ radians, as a |Float| in the range [-pi, pi].
+ {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
+ Examples: >vim
+ echo atan2(-1, 1)
+ < -0.785398 >vim
+ echo atan2(1, -1)
+ < 2.356194
+
+ ]=],
+ name = 'atan2',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ returns = 'number',
+ signature = 'atan2({expr1}, {expr2})',
+ },
+ blob2list = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a List containing the number value of each byte in Blob
+ {blob}. Examples: >vim
+ blob2list(0z0102.0304) " returns [1, 2, 3, 4]
+ blob2list(0z) " returns []
+ <Returns an empty List on error. |list2blob()| does the
+ opposite.
+
+ ]=],
+ name = 'blob2list',
+ params = { { 'blob', 'any' } },
+ returns = 'any[]',
+ signature = 'blob2list({blob})',
+ },
+ browse = {
+ args = 4,
+ desc = [=[
+ Put up a file requester. This only works when "has("browse")"
+ returns |TRUE| (only in some GUI versions).
+ The input fields are:
+ {save} when |TRUE|, select file to write
+ {title} title for the requester
+ {initdir} directory to start browsing in
+ {default} default file name
+ An empty string is returned when the "Cancel" button is hit,
+ something went wrong, or browsing is not possible.
+ ]=],
+ name = 'browse',
+ params = { { 'save', 'any' }, { 'title', 'any' }, { 'initdir', 'any' }, { 'default', 'any' } },
+ returns = '0|1',
+ signature = 'browse({save}, {title}, {initdir}, {default})',
+ },
+ browsedir = {
+ args = 2,
+ desc = [=[
+ Put up a directory requester. This only works when
+ "has("browse")" returns |TRUE| (only in some GUI versions).
+ On systems where a directory browser is not supported a file
+ browser is used. In that case: select a file in the directory
+ to be used.
+ The input fields are:
+ {title} title for the requester
+ {initdir} directory to start browsing in
+ When the "Cancel" button is hit, something went wrong, or
+ browsing is not possible, an empty string is returned.
+ ]=],
+ name = 'browsedir',
+ params = { { 'title', 'any' }, { 'initdir', 'any' } },
+ returns = '0|1',
+ signature = 'browsedir({title}, {initdir})',
+ },
+ bufadd = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Add a buffer to the buffer list with name {name} (must be a
+ String).
+ If a buffer for file {name} already exists, return that buffer
+ number. Otherwise return the buffer number of the newly
+ created buffer. When {name} is an empty string then a new
+ buffer is always created.
+ The buffer will not have 'buflisted' set and not be loaded
+ yet. To add some text to the buffer use this: >vim
+ let bufnr = bufadd('someName')
+ call bufload(bufnr)
+ call setbufline(bufnr, 1, ['some', 'text'])
+ <Returns 0 on error.
+ ]=],
+ name = 'bufadd',
+ params = { { 'name', 'string' } },
+ returns = 'integer',
+ signature = 'bufadd({name})',
+ },
+ bufexists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists.
+ If the {buf} argument is a number, buffer numbers are used.
+ Number zero is the alternate buffer for the current window.
+
+ If the {buf} argument is a string it must match a buffer name
+ exactly. The name can be:
+ - Relative to the current directory.
+ - A full path.
+ - The name of a buffer with 'buftype' set to "nofile".
+ - A URL name.
+ Unlisted buffers will be found.
+ Note that help files are listed by their short name in the
+ output of |:buffers|, but bufexists() requires using their
+ long name to be able to find them.
+ bufexists() may report a buffer exists, but to use the name
+ with a |:buffer| command you may need to use |expand()|. Esp
+ for MS-Windows 8.3 names in the form "c:\DOCUME~1"
+ Use "bufexists(0)" to test for the existence of an alternate
+ file name.
+
+ ]=],
+ name = 'bufexists',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'bufexists({buf})',
+ },
+ buffer_exists = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufexists()|.
+ ]=],
+ func = 'f_bufexists',
+ name = 'buffer_exists',
+ params = VARARGS,
+ returns = '0|1',
+ signature = 'buffer_exists({buf})',
+ },
+ buffer_name = {
+ args = { 0, 1 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufname()|.
+ ]=],
+ func = 'f_bufname',
+ name = 'buffer_name',
+ params = VARARGS,
+ returns = 'string',
+ signature = 'buffer_name([{buf}])',
+ },
+ buffer_number = {
+ args = { 0, 1 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |bufnr()|.
+ ]=],
+ func = 'f_bufnr',
+ name = 'buffer_number',
+ params = VARARGS,
+ returns = 'integer',
+ signature = 'buffer_number([{buf} [, {create}]])',
+ },
+ buflisted = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists and is listed (has the 'buflisted' option set).
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'buflisted',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'buflisted({buf})',
+ },
+ bufload = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Ensure the buffer {buf} is loaded. When the buffer name
+ refers to an existing file then the file is read. Otherwise
+ the buffer will be empty. If the buffer was already loaded
+ then there is no change. If the buffer is not related to a
+ file then no file is read (e.g., when 'buftype' is "nofile").
+ If there is an existing swap file for the file of the buffer,
+ there will be no dialog, the buffer will be loaded anyway.
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'bufload',
+ params = { { 'buf', 'any' } },
+ returns = false,
+ signature = 'bufload({buf})',
+ },
+ bufloaded = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if a buffer called
+ {buf} exists and is loaded (shown in a window or hidden).
+ The {buf} argument is used like with |bufexists()|.
+
+ ]=],
+ name = 'bufloaded',
+ params = { { 'buf', 'any' } },
+ returns = '0|1',
+ signature = 'bufloaded({buf})',
+ },
+ bufname = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is the name of a buffer. Mostly as it is displayed
+ by the `:ls` command, but not using special names such as
+ "[No Name]".
+ If {buf} is omitted the current buffer is used.
+ If {buf} is a Number, that buffer number's name is given.
+ Number zero is the alternate buffer for the current window.
+ If {buf} is a String, it is used as a |file-pattern| to match
+ with the buffer names. This is always done like 'magic' is
+ set and 'cpoptions' is empty. When there is more than one
+ match an empty string is returned.
+ "" or "%" can be used for the current buffer, "#" for the
+ alternate buffer.
+ A full match is preferred, otherwise a match at the start, end
+ or middle of the buffer name is accepted. If you only want a
+ full match then put "^" at the start and "$" at the end of the
+ pattern.
+ Listed buffers are found first. If there is a single match
+ with a listed buffer, that one is returned. Next unlisted
+ buffers are searched for.
+ If the {buf} is a String, but you want to use it as a buffer
+ number, force it to be a Number by adding zero to it: >vim
+ echo bufname("3" + 0)
+ <If the buffer doesn't exist, or doesn't have a name, an empty
+ string is returned. >vim
+ echo bufname("#") " alternate buffer name
+ echo bufname(3) " name of buffer 3
+ echo bufname("%") " name of current buffer
+ echo bufname("file2") " name of buffer where "file2" matches.
+ <
+ ]=],
+ name = 'bufname',
+ params = { { 'buf', 'any' } },
+ returns = 'string',
+ signature = 'bufname([{buf}])',
+ },
+ bufnr = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ The result is the number of a buffer, as it is displayed by
+ the `:ls` command. For the use of {buf}, see |bufname()|
+ above.
+ If the buffer doesn't exist, -1 is returned. Or, if the
+ {create} argument is present and TRUE, a new, unlisted,
+ buffer is created and its number is returned.
+ bufnr("$") is the last buffer: >vim
+ let last_buffer = bufnr("$")
+ <The result is a Number, which is the highest buffer number
+ of existing buffers. Note that not all buffers with a smaller
+ number necessarily exist, because ":bwipeout" may have removed
+ them. Use bufexists() to test for the existence of a buffer.
+
+ ]=],
+ name = 'bufnr',
+ params = { { 'buf', 'any' }, { 'create', 'any' } },
+ returns = 'integer',
+ signature = 'bufnr([{buf} [, {create}]])',
+ },
+ bufwinid = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the |window-ID| of the first
+ window associated with buffer {buf}. For the use of {buf},
+ see |bufname()| above. If buffer {buf} doesn't exist or
+ there is no such window, -1 is returned. Example: >vim
+
+ echo "A window containing buffer 1 is " .. (bufwinid(1))
+ <
+ Only deals with the current tab page. See |win_findbuf()| for
+ finding more.
+
+ ]=],
+ name = 'bufwinid',
+ params = { { 'buf', 'any' } },
+ returns = 'integer',
+ signature = 'bufwinid({buf})',
+ },
+ bufwinnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Like |bufwinid()| but return the window number instead of the
+ |window-ID|.
+ If buffer {buf} doesn't exist or there is no such window, -1
+ is returned. Example: >vim
+
+ echo "A window containing buffer 1 is " .. (bufwinnr(1))
+
+ <The number can be used with |CTRL-W_w| and ":wincmd w"
+ |:wincmd|.
+
+ ]=],
+ name = 'bufwinnr',
+ params = { { 'buf', 'any' } },
+ returns = 'integer',
+ signature = 'bufwinnr({buf})',
+ },
+ byte2line = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number that contains the character at byte
+ count {byte} in the current buffer. This includes the
+ end-of-line character, depending on the 'fileformat' option
+ for the current buffer. The first character has byte count
+ one.
+ Also see |line2byte()|, |go| and |:goto|.
+
+ Returns -1 if the {byte} value is invalid.
+
+ ]=],
+ name = 'byte2line',
+ params = { { 'byte', 'any' } },
+ returns = 'integer',
+ signature = 'byte2line({byte})',
+ },
+ byteidx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return byte index of the {nr}th character in the String
+ {expr}. Use zero for the first character, it then returns
+ zero.
+ If there are no multibyte characters the returned value is
+ equal to {nr}.
+ Composing characters are not counted separately, their byte
+ length is added to the preceding base character. See
+ |byteidxcomp()| below for counting composing characters
+ separately.
+ When {utf16} is present and TRUE, {nr} is used as the UTF-16
+ index in the String {expr} instead of as the character index.
+ The UTF-16 index is the index in the string when it is encoded
+ with 16-bit words. If the specified UTF-16 index is in the
+ middle of a character (e.g. in a 4-byte character), then the
+ byte index of the first byte in the character is returned.
+ Refer to |string-offset-encoding| for more information.
+ Example : >vim
+ echo matchstr(str, ".", byteidx(str, 3))
+ <will display the fourth character. Another way to do the
+ same: >vim
+ let s = strpart(str, byteidx(str, 3))
+ echo strpart(s, 0, byteidx(s, 1))
+ <Also see |strgetchar()| and |strcharpart()|.
+
+ If there are less than {nr} characters -1 is returned.
+ If there are exactly {nr} characters the length of the string
+ in bytes is returned.
+ See |charidx()| and |utf16idx()| for getting the character and
+ UTF-16 index respectively from the byte index.
+ Examples: >vim
+ echo byteidx('a😊😊', 2) " returns 5
+ echo byteidx('a😊😊', 2, 1) " returns 1
+ echo byteidx('a😊😊', 3, 1) " returns 5
+ <
+ ]=],
+ fast = true,
+ name = 'byteidx',
+ params = { { 'expr', 'any' }, { 'nr', 'integer' }, { 'utf16', 'any' } },
+ returns = 'integer',
+ signature = 'byteidx({expr}, {nr} [, {utf16}])',
+ },
+ byteidxcomp = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like byteidx(), except that a composing character is counted
+ as a separate character. Example: >vim
+ let s = 'e' .. nr2char(0x301)
+ echo byteidx(s, 1)
+ echo byteidxcomp(s, 1)
+ echo byteidxcomp(s, 2)
+ <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).
+
+ ]=],
+ fast = true,
+ name = 'byteidxcomp',
+ params = { { 'expr', 'any' }, { 'nr', 'integer' }, { 'utf16', 'any' } },
+ returns = 'integer',
+ signature = 'byteidxcomp({expr}, {nr} [, {utf16}])',
+ },
+ call = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Call function {func} with the items in |List| {arglist} as
+ arguments.
+ {func} can either be a |Funcref| or the name of a function.
+ a:firstline and a:lastline are set to the cursor line.
+ Returns the return value of the called function.
+ {dict} is for functions with the "dict" attribute. It will be
+ used to set the local variable "self". |Dictionary-function|
+
+ ]=],
+ name = 'call',
+ params = { { 'func', 'any' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ returns = 'any',
+ signature = 'call({func}, {arglist} [, {dict}])',
+ tags = { 'E699' },
+ },
+ ceil = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the smallest integral value greater than or equal to
+ {expr} as a |Float| (round up).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Examples: >vim
+ echo ceil(1.456)
+ < 2.0 >vim
+ echo ceil(-5.456)
+ < -5.0 >vim
+ echo ceil(4.0)
+ < 4.0
+
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+
+ ]=],
+ float_func = 'ceil',
+ name = 'ceil',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'ceil({expr})',
+ },
+ chanclose = {
+ args = { 1, 2 },
+ desc = [=[
+ 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
+ with `"rpc":v:true`) If {stream} is omitted, all streams
+ are closed. If the channel is a pty, this will then close the
+ pty master, sending SIGHUP to the job process.
+ For a socket, there is only one stream, and {stream} should be
+ omitted.
+ ]=],
+ name = 'chanclose',
+ params = { { 'id', 'any' }, { 'stream', 'any' } },
+ returns = '0|1',
+ signature = 'chanclose({id} [, {stream}])',
+ },
+ changenr = {
+ desc = [=[
+ Return the number of the most recent change. This is the same
+ number as what is displayed with |:undolist| and can be used
+ with the |:undo| command.
+ When a change was made it is the number of that change. After
+ redo it is the number of the redone change. After undo it is
+ one less than the number of the undone change.
+ Returns 0 if the undo list is empty.
+ ]=],
+ name = 'changenr',
+ params = {},
+ returns = 'integer',
+ signature = 'changenr()',
+ },
+ chansend = {
+ args = 2,
+ desc = [=[
+ Send data to channel {id}. For a job, it writes it to the
+ stdin of the process. For the stdio channel |channel-stdio|,
+ it writes to Nvim's stdout. Returns the number of bytes
+ written if the write succeeded, 0 otherwise.
+ See |channel-bytes| for more information.
+
+ {data} may be a string, string convertible, |Blob|, or a list.
+ If {data} is a list, the items will be joined by newlines; any
+ newlines in an item will be sent as NUL. To send a final
+ newline, include a final empty string. Example: >vim
+ call chansend(id, ["abc", "123\n456", ""])
+ <will send "abc<NL>123<NUL>456<NL>".
+
+ chansend() writes raw data, not RPC messages. If the channel
+ was created with `"rpc":v:true` then the channel expects RPC
+ messages, use |rpcnotify()| and |rpcrequest()| instead.
+ ]=],
+ name = 'chansend',
+ params = { { 'id', 'any' }, { 'data', 'any' } },
+ returns = '0|1',
+ signature = 'chansend({id}, {data})',
+ },
+ char2nr = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return Number value of the first char in {string}.
+ Examples: >vim
+ echo char2nr(" ") " returns 32
+ echo char2nr("ABC") " returns 65
+ echo char2nr("á") " returns 225
+ echo char2nr("á"[0]) " returns 195
+ echo char2nr("\<M-x>") " returns 128
+ <Non-ASCII characters are always treated as UTF-8 characters.
+ {utf8} is ignored, it exists only for backwards-compatibility.
+ A combining character is a separate character.
+ |nr2char()| does the opposite.
+
+ Returns 0 if {string} is not a |String|.
+
+ ]=],
+ fast = true,
+ name = 'char2nr',
+ params = { { 'string', 'string' }, { 'utf8', 'any' } },
+ returns = '0|1',
+ signature = 'char2nr({string} [, {utf8}])',
+ },
+ charclass = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the character class of the first character in {string}.
+ The character class is one of:
+ 0 blank
+ 1 punctuation
+ 2 word character
+ 3 emoji
+ other specific Unicode class
+ The class is used in patterns and word motions.
+ Returns 0 if {string} is not a |String|.
+ ]=],
+ name = 'charclass',
+ params = { { 'string', 'string' } },
+ returns = "0|1|2|3|'other'",
+ signature = 'charclass({string})',
+ },
+ charcol = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ 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 "여보세요": >vim
+ echo charcol('.') " returns 3
+ echo col('.') " returns 7
+
+ ]=],
+ name = 'charcol',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'charcol({expr} [, {winid}])',
+ },
+ charidx = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Return the character index of the byte at {idx} in {string}.
+ The index of the first character is zero.
+ If there are no multibyte characters the returned value is
+ equal to {idx}.
+
+ When {countcc} is omitted or |FALSE|, then composing characters
+ are not counted separately, their byte length is added to the
+ preceding base character.
+ When {countcc} is |TRUE|, then composing characters are
+ counted as separate characters.
+
+ When {utf16} is present and TRUE, {idx} is used as the UTF-16
+ index in the String {expr} instead of as the byte index.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes. If there are exactly {idx} bytes the length
+ of the string in characters is returned.
+
+ An error is given and -1 is returned if the first argument is
+ not a string, the second argument is not a number or when the
+ third argument is present and is not zero or one.
+
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the character index and |utf16idx()| for getting the
+ UTF-16 index from the character index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo charidx('áb́ć', 3) " returns 1
+ echo charidx('áb́ć', 6, 1) " returns 4
+ echo charidx('áb́ć', 16) " returns -1
+ echo charidx('a😊😊', 4, 0, 1) " returns 2
+ <
+ ]=],
+ name = 'charidx',
+ params = {
+ { 'string', 'string' },
+ { 'idx', 'integer' },
+ { 'countcc', 'any' },
+ { 'utf16', 'any' },
+ },
+ returns = 'integer',
+ signature = 'charidx({string}, {idx} [, {countcc} [, {utf16}]])',
+ },
+ chdir = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Change the current working directory to {dir}. The scope of
+ the directory change depends on the directory of the current
+ window:
+ - If the current window has a window-local directory
+ (|:lcd|), then changes the window local directory.
+ - Otherwise, if the current tabpage has a local
+ directory (|:tcd|) then changes the tabpage local
+ directory.
+ - Otherwise, changes the global directory.
+ {dir} must be a String.
+ If successful, returns the previous working directory. Pass
+ this to another chdir() to restore the directory.
+ On failure, returns an empty string.
+
+ Example: >vim
+ let save_dir = chdir(newdir)
+ if save_dir != ""
+ " ... do some work
+ call chdir(save_dir)
+ endif
+
+ ]=],
+ name = 'chdir',
+ params = { { 'dir', 'string' } },
+ returns = 'string',
+ signature = 'chdir({dir})',
+ },
+ cindent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the amount of indent for line {lnum} according the C
+ indenting rules, as with 'cindent'.
+ The indent is counted in spaces, the value of 'tabstop' is
+ relevant. {lnum} is used just like in |getline()|.
+ When {lnum} is invalid -1 is returned.
+ See |C-indenting|.
+
+ ]=],
+ name = 'cindent',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'cindent({lnum})',
+ },
+ clearmatches = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Clears all matches previously defined for the current window
+ by |matchadd()| and the |:match| commands.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'clearmatches',
+ params = { { 'win', 'any' } },
+ returns = false,
+ signature = 'clearmatches([{win}])',
+ },
+ col = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the byte index of the column
+ position given with {expr}. The accepted positions are:
+ . the cursor position
+ $ the end of the cursor line (the result is the
+ number of bytes in the cursor line plus one)
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+ Additionally {expr} can be [lnum, col]: a |List| with the line
+ and column number. Most useful when the column is "$", to get
+ the last column of a specific line. When "lnum" or "col" is
+ out of range then col() returns zero.
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+ To get the line number use |line()|. To get both use
+ |getpos()|.
+ 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: >vim
+ echo col(".") " column of cursor
+ echo col("$") " length of cursor line plus one
+ echo col("'t") " column of mark t
+ echo col("'" .. markname) " column of mark markname
+ <The first column is 1. Returns 0 if {expr} is invalid or when
+ the window with ID {winid} is not found.
+ For an uppercase mark the column may actually be in another
+ buffer.
+ For the cursor position, when 'virtualedit' is active, the
+ column is one higher if the cursor is after the end of the
+ line. Also, when using a <Cmd> mapping the cursor isn't
+ moved, this can be used to obtain the column in Insert mode: >vim
+ imap <F2> <Cmd>echo col(".").."\n"<CR>
+
+ ]=],
+ name = 'col',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'col({expr} [, {winid}])',
+ },
+ complete = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set the matches for Insert mode completion.
+ Can only be used in Insert mode. You need to use a mapping
+ with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O
+ or with an expression mapping.
+ {startcol} is the byte offset in the line where the completed
+ text start. The text up to the cursor is the original text
+ that will be replaced by the matches. Use col('.') for an
+ empty string. "col('.') - 1" will replace one character by a
+ match.
+ {matches} must be a |List|. Each |List| item is one match.
+ See |complete-items| for the kind of items that are possible.
+ "longest" in 'completeopt' is ignored.
+ Note that the after calling this function you need to avoid
+ inserting anything that would cause completion to stop.
+ The match can be selected with CTRL-N and CTRL-P as usual with
+ Insert mode completion. The popup menu will appear if
+ specified, see |ins-completion-menu|.
+ Example: >vim
+ inoremap <F5> <C-R>=ListMonths()<CR>
+
+ func ListMonths()
+ call complete(col('.'), ['January', 'February', 'March',
+ \ 'April', 'May', 'June', 'July', 'August', 'September',
+ \ 'October', 'November', 'December'])
+ return ''
+ endfunc
+ <This isn't very useful, but it shows how it works. Note that
+ an empty string is returned to avoid a zero being inserted.
+
+ ]=],
+ name = 'complete',
+ params = { { 'startcol', 'any' }, { 'matches', 'any' } },
+ returns = false,
+ signature = 'complete({startcol}, {matches})',
+ tags = { 'E785' },
+ },
+ complete_add = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Add {expr} to the list of matches. Only to be used by the
+ function specified with the 'completefunc' option.
+ Returns 0 for failure (empty string or out of memory),
+ 1 when the match was added, 2 when the match was already in
+ the list.
+ See |complete-functions| for an explanation of {expr}. It is
+ the same as one item in the list that 'omnifunc' would return.
+
+ ]=],
+ name = 'complete_add',
+ params = { { 'expr', 'any' } },
+ returns = '0|1|2',
+ signature = 'complete_add({expr})',
+ },
+ complete_check = {
+ desc = [=[
+ Check for a key typed while looking for completion matches.
+ This is to be used when looking for matches takes some time.
+ Returns |TRUE| when searching for matches is to be aborted,
+ zero otherwise.
+ Only to be used by the function specified with the
+ 'completefunc' option.
+ ]=],
+ name = 'complete_check',
+ params = {},
+ returns = '0|1',
+ signature = 'complete_check()',
+ },
+ complete_info = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns a |Dictionary| with information about Insert mode
+ completion. See |ins-completion|.
+ The items are:
+ mode Current completion mode name string.
+ See |complete_info_mode| for the values.
+ pum_visible |TRUE| if popup menu is visible.
+ See |pumvisible()|.
+ items List of completion matches. Each item is a
+ dictionary containing the entries "word",
+ "abbr", "menu", "kind", "info" and "user_data".
+ See |complete-items|.
+ selected Selected item index. First index is zero.
+ Index is -1 if no item is selected (showing
+ typed text only, or the last completion after
+ no item is selected when using the <Up> or
+ <Down> keys)
+ inserted Inserted string. [NOT IMPLEMENTED YET]
+
+ *complete_info_mode*
+ mode values are:
+ "" Not in completion mode
+ "keyword" Keyword completion |i_CTRL-X_CTRL-N|
+ "ctrl_x" Just pressed CTRL-X |i_CTRL-X|
+ "scroll" Scrolling with |i_CTRL-X_CTRL-E| or
+ |i_CTRL-X_CTRL-Y|
+ "whole_line" Whole lines |i_CTRL-X_CTRL-L|
+ "files" File names |i_CTRL-X_CTRL-F|
+ "tags" Tags |i_CTRL-X_CTRL-]|
+ "path_defines" Definition completion |i_CTRL-X_CTRL-D|
+ "path_patterns" Include completion |i_CTRL-X_CTRL-I|
+ "dictionary" Dictionary |i_CTRL-X_CTRL-K|
+ "thesaurus" Thesaurus |i_CTRL-X_CTRL-T|
+ "cmdline" Vim Command line |i_CTRL-X_CTRL-V|
+ "function" User defined completion |i_CTRL-X_CTRL-U|
+ "omni" Omni completion |i_CTRL-X_CTRL-O|
+ "spell" Spelling suggestions |i_CTRL-X_s|
+ "eval" |complete()| completion
+ "unknown" Other internal modes
+
+ If the optional {what} list argument is supplied, then only
+ the items listed in {what} are returned. Unsupported items in
+ {what} are silently ignored.
+
+ To get the position and size of the popup menu, see
+ |pum_getpos()|. It's also available in |v:event| during the
+ |CompleteChanged| event.
+
+ Returns an empty |Dictionary| on error.
+
+ Examples: >vim
+ " Get all items
+ call complete_info()
+ " Get only 'mode'
+ call complete_info(['mode'])
+ " Get only 'mode' and 'pum_visible'
+ call complete_info(['mode', 'pum_visible'])
+
+ ]=],
+ name = 'complete_info',
+ params = { { 'what', 'any' } },
+ returns = 'table',
+ signature = 'complete_info([{what}])',
+ },
+ confirm = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ confirm() offers the user a dialog, from which a choice can be
+ made. It returns the number of the choice. For the first
+ choice this is 1.
+
+ {msg} is displayed in a dialog with {choices} as the
+ alternatives. When {choices} is missing or empty, "&OK" is
+ used (and translated).
+ {msg} is a String, use '\n' to include a newline. Only on
+ some systems the string is wrapped when it doesn't fit.
+
+ {choices} is a String, with the individual choices separated
+ by '\n', e.g. >vim
+ confirm("Save changes?", "&Yes\n&No\n&Cancel")
+ <The letter after the '&' is the shortcut key for that choice.
+ Thus you can type 'c' to select "Cancel". The shortcut does
+ not need to be the first letter: >vim
+ confirm("file has been modified", "&Save\nSave &All")
+ <For the console, the first letter of each choice is used as
+ the default shortcut key. Case is ignored.
+
+ The optional {type} String argument gives the type of dialog.
+ It can be one of these values: "Error", "Question", "Info",
+ "Warning" or "Generic". Only the first character is relevant.
+ When {type} is omitted, "Generic" is used.
+
+ The optional {type} argument gives the type of dialog. This
+ is only used for the icon of the Win32 GUI. It can be one of
+ these values: "Error", "Question", "Info", "Warning" or
+ "Generic". Only the first character is relevant.
+ When {type} is omitted, "Generic" is used.
+
+ If the user aborts the dialog by pressing <Esc>, CTRL-C,
+ or another valid interrupt key, confirm() returns 0.
+
+ An example: >vim
+ let choice = confirm("What do you want?",
+ \ "&Apples\n&Oranges\n&Bananas", 2)
+ if choice == 0
+ echo "make up your mind!"
+ elseif choice == 3
+ echo "tasteful"
+ else
+ echo "I prefer bananas myself."
+ endif
+ <In a GUI dialog, buttons are used. The layout of the buttons
+ depends on the 'v' flag in 'guioptions'. If it is included,
+ the buttons are always put vertically. Otherwise, confirm()
+ tries to put the buttons in one horizontal line. If they
+ don't fit, a vertical layout is used anyway. For some systems
+ the horizontal layout is always used.
+
+ ]=],
+ name = 'confirm',
+ params = { { 'msg', 'any' }, { 'choices', 'any' }, { 'default', 'any' }, { 'type', 'any' } },
+ returns = 'integer',
+ signature = 'confirm({msg} [, {choices} [, {default} [, {type}]]])',
+ },
+ copy = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Make a copy of {expr}. For Numbers and Strings this isn't
+ different from using {expr} directly.
+ When {expr} is a |List| a shallow copy is created. This means
+ that the original |List| can be changed without changing the
+ copy, and vice versa. But the items are identical, thus
+ changing an item changes the contents of both |Lists|.
+ A |Dictionary| is copied in a similar way as a |List|.
+ Also see |deepcopy()|.
+ ]=],
+ name = 'copy',
+ params = { { 'expr', 'any' } },
+ returns = 'any',
+ signature = 'copy({expr})',
+ },
+ cos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the cosine of {expr}, measured in radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo cos(100)
+ < 0.862319 >vim
+ echo cos(-4.01)
+ < -0.646043
+
+ ]=],
+ float_func = 'cos',
+ name = 'cos',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'cos({expr})',
+ },
+ cosh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic cosine of {expr} as a |Float| in the range
+ [1, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo cosh(0.5)
+ < 1.127626 >vim
+ echo cosh(-0.5)
+ < -1.127626
+
+ ]=],
+ float_func = 'cosh',
+ name = 'cosh',
+ params = { { 'expr', 'any' } },
+ returns = 'number',
+ signature = 'cosh({expr})',
+ },
+ count = {
+ args = { 2, 4 },
+ base = 1,
+ tags = { 'E706' },
+ desc = [=[
+ Return the number of times an item with value {expr} appears
+ in |String|, |List| or |Dictionary| {comp}.
+
+ If {start} is given then start with the item with this index.
+ {start} can only be used with a |List|.
+
+ When {ic} is given and it's |TRUE| then case is ignored.
+
+ When {comp} is a string then the number of not overlapping
+ occurrences of {expr} is returned. Zero is returned when
+ {expr} is an empty string.
+
+ ]=],
+ name = 'count',
+ params = { { 'comp', 'any' }, { 'expr', 'any' }, { 'ic', 'any' }, { 'start', 'any' } },
+ returns = 'integer',
+ signature = 'count({comp}, {expr} [, {ic} [, {start}]])',
+ },
+ ctxget = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |Dictionary| representing the |context| at {index}
+ from the top of the |context-stack| (see |context-dict|).
+ If {index} is not given, it is assumed to be 0 (i.e.: top).
+ ]=],
+ name = 'ctxget',
+ params = { { 'index', 'any' } },
+ returns = 'table',
+ signature = 'ctxget([{index}])',
+ },
+ ctxpop = {
+ desc = [=[
+ Pops and restores the |context| at the top of the
+ |context-stack|.
+ ]=],
+ name = 'ctxpop',
+ params = {},
+ signature = 'ctxpop()',
+ },
+ ctxpush = {
+ args = { 0, 1 },
+ desc = [=[
+ Pushes the current editor state (|context|) on the
+ |context-stack|.
+ If {types} is given and is a |List| of |String|s, it specifies
+ which |context-types| to include in the pushed context.
+ Otherwise, all context types are included.
+ ]=],
+ name = 'ctxpush',
+ params = { { 'types', 'any' } },
+ signature = 'ctxpush([{types}])',
+ },
+ ctxset = {
+ args = { 1, 2 },
+ desc = [=[
+ 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|).
+ If {index} is not given, it is assumed to be 0 (i.e.: top).
+ ]=],
+ name = 'ctxset',
+ params = { { 'context', 'any' }, { 'index', 'any' } },
+ signature = 'ctxset({context} [, {index}])',
+ },
+ ctxsize = {
+ desc = [=[
+ Returns the size of the |context-stack|.
+ ]=],
+ name = 'ctxsize',
+ params = {},
+ signature = 'ctxsize()',
+ },
+ cursor = {
+ args = { 1, 3 },
+ base = 1,
+ name = 'cursor',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ signature = 'cursor({lnum}, {col} [, {off}])',
+ },
+ cursor__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Positions the cursor at the column (byte count) {col} in the
+ line {lnum}. The first column is one.
+
+ When there is one argument {list} this is used as a |List|
+ with two, three or four item:
+ [{lnum}, {col}]
+ [{lnum}, {col}, {off}]
+ [{lnum}, {col}, {off}, {curswant}]
+ This is like the return value of |getpos()| or |getcurpos()|,
+ but without the first item.
+
+ To position the cursor using {col} as the character count, use
+ |setcursorcharpos()|.
+
+ Does not change the jumplist.
+ {lnum} is used like with |getline()|, except that if {lnum} is
+ zero, the cursor will stay in the current line.
+ If {lnum} is greater than the number of lines in the buffer,
+ the cursor will be positioned at the last line in the buffer.
+ If {col} is greater than the number of bytes in the line,
+ the cursor will be positioned at the last character in the
+ line.
+ If {col} is zero, the cursor will stay in the current column.
+ If {curswant} is given it is used to set the preferred column
+ for vertical movement. Otherwise {col} is used.
+
+ When 'virtualedit' is used {off} specifies the offset in
+ screen columns from the start of the character. E.g., a
+ position within a <Tab> or after the last character.
+ Returns 0 when the position could be set, -1 otherwise.
+
+ ]=],
+ name = 'cursor',
+ params = { { 'list', 'any' } },
+ signature = 'cursor({list})',
+ },
+ debugbreak = {
+ args = { 1, 1 },
+ base = 1,
+ desc = [=[
+ Specifically used to interrupt a program being debugged. It
+ will cause process {pid} to get a SIGTRAP. Behavior for other
+ processes is undefined. See |terminal-debug|.
+ (Sends a SIGINT to a process {pid} other than MS-Windows)
+
+ Returns |TRUE| if successfully interrupted the program.
+ Otherwise returns |FALSE|.
+
+ ]=],
+ name = 'debugbreak',
+ params = { { 'pid', 'any' } },
+ signature = 'debugbreak({pid})',
+ },
+ deepcopy = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E698' },
+ desc = [=[
+ 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
+ that the original |List| can be changed without changing the
+ copy, and vice versa. When an item is a |List|, a copy for it
+ is made, recursively. Thus changing an item in the copy does
+ not change the contents of the original |List|.
+
+ When {noref} is omitted or zero a contained |List| or
+ |Dictionary| is only copied once. All references point to
+ this single copy. With {noref} set to 1 every occurrence of a
+ |List| or |Dictionary| results in a new copy. This also means
+ that a cyclic reference causes deepcopy() to fail.
+ *E724*
+ Nesting is possible up to 100 levels. When there is an item
+ that refers back to a higher level making a deep copy with
+ {noref} set to 1 will fail.
+ Also see |copy()|.
+
+ ]=],
+ name = 'deepcopy',
+ params = { { 'expr', 'any' }, { 'noref', 'any' } },
+ signature = 'deepcopy({expr} [, {noref}])',
+ },
+ delete = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Without {flags} or with {flags} empty: Deletes the file by the
+ name {fname}.
+
+ This also works when {fname} is a symbolic link. The symbolic
+ link itself is deleted, not what it points to.
+
+ When {flags} is "d": Deletes the directory by the name
+ {fname}. This fails when directory {fname} is not empty.
+
+ When {flags} is "rf": Deletes the directory by the name
+ {fname} and everything in it, recursively. BE CAREFUL!
+ Note: on MS-Windows it is not possible to delete a directory
+ that is being used.
+
+ The result is a Number, which is 0/false if the delete
+ operation was successful and -1/true when the deletion failed
+ or partly failed.
+
+ ]=],
+ name = 'delete',
+ params = { { 'fname', 'string' }, { 'flags', 'string' } },
+ returns = 'integer',
+ signature = 'delete({fname} [, {flags}])',
+ },
+ deletebufline = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ 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.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ For the use of {buf}, see |bufname()| above.
+
+ {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}.
+
+ ]=],
+ name = 'deletebufline',
+ params = { { 'buf', 'any' }, { 'first', 'any' }, { 'last', 'any' } },
+ signature = 'deletebufline({buf}, {first} [, {last}])',
+ },
+ dictwatcheradd = {
+ args = 3,
+ desc = [=[
+ Adds a watcher to a dictionary. A dictionary watcher is
+ identified by three components:
+
+ - A dictionary({dict});
+ - A key pattern({pattern}).
+ - A function({callback}).
+
+ After this is called, every change on {dict} and on keys
+ matching {pattern} will result in {callback} being invoked.
+
+ For example, to watch all global variables: >vim
+ silent! call dictwatcherdel(g:, '*', 'OnDictChanged')
+ function! OnDictChanged(d,k,z)
+ echomsg string(a:k) string(a:z)
+ endfunction
+ call dictwatcheradd(g:, '*', 'OnDictChanged')
+ <
+ For now {pattern} only accepts very simple patterns that can
+ contain a "*" at the end of the string, in which case it will
+ match every key that begins with the substring before the "*".
+ That means if "*" is not the last character of {pattern}, only
+ keys that are exactly equal as {pattern} will be matched.
+
+ The {callback} receives three arguments:
+
+ - The dictionary being watched.
+ - The key which changed.
+ - A dictionary containing the new and old values for the key.
+
+ The type of change can be determined by examining the keys
+ present on the third argument:
+
+ - If contains both `old` and `new`, the key was updated.
+ - If it contains only `new`, the key was added.
+ - If it contains only `old`, the key was deleted.
+
+ This function can be used by plugins to implement options with
+ validation and parsing logic.
+ ]=],
+ name = 'dictwatcheradd',
+ params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ signature = 'dictwatcheradd({dict}, {pattern}, {callback})',
+ },
+ dictwatcherdel = {
+ args = 3,
+ desc = [=[
+ Removes a watcher added with |dictwatcheradd()|. All three
+ arguments must match the ones passed to |dictwatcheradd()| in
+ order for the watcher to be successfully deleted.
+ ]=],
+ name = 'dictwatcherdel',
+ params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ signature = 'dictwatcherdel({dict}, {pattern}, {callback})',
+ },
+ did_filetype = {
+ desc = [=[
+ Returns |TRUE| when autocommands are being executed and the
+ FileType event has been triggered at least once. Can be used
+ to avoid triggering the FileType event again in the scripts
+ that detect the file type. |FileType|
+ Returns |FALSE| when `:setf FALLBACK` was used.
+ When editing another file, the counter is reset, thus this
+ really checks if the FileType event has been triggered for the
+ current buffer. This allows an autocommand that starts
+ editing another buffer to set 'filetype' and load a syntax
+ file.
+ ]=],
+ fast = true,
+ name = 'did_filetype',
+ params = {},
+ signature = 'did_filetype()',
+ },
+ diff_filler = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the number of filler lines above line {lnum}.
+ These are the lines that were inserted at this point in
+ another diff'ed window. These filler lines are shown in the
+ display but don't exist in the buffer.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ Returns 0 if the current window is not in diff mode.
+
+ ]=],
+ name = 'diff_filler',
+ params = { { 'lnum', 'integer' } },
+ signature = 'diff_filler({lnum})',
+ },
+ diff_hlID = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Returns the highlight ID for diff mode at line {lnum} column
+ {col} (byte index). When the current line does not have a
+ diff change zero is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ {col} is 1 for the leftmost column, {lnum} is 1 for the first
+ line.
+ The highlight ID can be used with |synIDattr()| to obtain
+ syntax information about the highlighting.
+
+ ]=],
+ name = 'diff_hlID',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'diff_hlID({lnum}, {col})',
+ },
+ digraph_get = {
+ args = 1,
+ base = 1,
+ tags = { 'E1214' },
+ desc = [=[
+ Return the digraph of {chars}. This should be a string with
+ exactly two characters. If {chars} are not just two
+ characters, or the digraph of {chars} does not exist, an error
+ is given and an empty string is returned.
+
+ Also see |digraph_getlist()|.
+
+ Examples: >vim
+ " Get a built-in digraph
+ echo digraph_get('00') " Returns '∞'
+
+ " Get a user-defined digraph
+ call digraph_set('aa', 'あ')
+ echo digraph_get('aa') " Returns 'あ'
+ <
+ ]=],
+ name = 'digraph_get',
+ params = { { 'chars', 'any' } },
+ signature = 'digraph_get({chars})',
+ },
+ digraph_getlist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a list of digraphs. If the {listall} argument is given
+ and it is TRUE, return all digraphs, including the default
+ digraphs. Otherwise, return only user-defined digraphs.
+
+ Also see |digraph_get()|.
+
+ Examples: >vim
+ " Get user-defined digraphs
+ echo digraph_getlist()
+
+ " Get all the digraphs, including default digraphs
+ echo digraph_getlist(1)
+ <
+ ]=],
+ name = 'digraph_getlist',
+ params = { { 'listall', 'any' } },
+ signature = 'digraph_getlist([{listall}])',
+ },
+ digraph_set = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Add digraph {chars} to the list. {chars} must be a string
+ with two characters. {digraph} is a string with one UTF-8
+ encoded character. *E1215*
+ Be careful, composing characters are NOT ignored. This
+ function is similar to |:digraphs| command, but useful to add
+ digraphs start with a white space.
+
+ The function result is v:true if |digraph| is registered. If
+ this fails an error message is given and v:false is returned.
+
+ If you want to define multiple digraphs at once, you can use
+ |digraph_setlist()|.
+
+ Example: >vim
+ call digraph_set(' ', 'あ')
+ <
+ Can be used as a |method|: >vim
+ GetString()->digraph_set('あ')
+ <
+ ]=],
+ name = 'digraph_set',
+ params = { { 'chars', 'any' }, { 'digraph', 'any' } },
+ signature = 'digraph_set({chars}, {digraph})',
+ },
+ digraph_setlist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Similar to |digraph_set()| but this function can add multiple
+ digraphs at once. {digraphlist} is a list composed of lists,
+ where each list contains two strings with {chars} and
+ {digraph} as in |digraph_set()|. *E1216*
+ Example: >vim
+ call digraph_setlist([['aa', 'あ'], ['ii', 'い']])
+ <
+ It is similar to the following: >vim
+ for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']]
+ call digraph_set(chars, digraph)
+ endfor
+ <Except that the function returns after the first error,
+ following digraphs will not be added.
+
+ Can be used as a |method|: >vim
+ GetList()->digraph_setlist()
+ <
+ ]=],
+ name = 'digraph_setlist',
+ params = { { 'digraphlist', 'any' } },
+ signature = 'digraph_setlist({digraphlist})',
+ },
+ empty = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the Number 1 if {expr} is empty, zero otherwise.
+ - A |List| or |Dictionary| is empty when it does not have any
+ items.
+ - A |String| is empty when its length is zero.
+ - A |Number| and |Float| are empty when their value is zero.
+ - |v:false| and |v:null| are empty, |v:true| is not.
+ - A |Blob| is empty when its length is zero.
+
+ ]=],
+ name = 'empty',
+ params = { { 'expr', 'any' } },
+ signature = 'empty({expr})',
+ },
+ environ = {
+ desc = [=[
+ Return all of environment variables as dictionary. You can
+ check if an environment variable exists like this: >vim
+ echo has_key(environ(), 'HOME')
+ <Note that the variable name may be CamelCase; to ignore case
+ use this: >vim
+ echo index(keys(environ()), 'HOME', 0, 1) != -1
+ <
+ ]=],
+ fast = true,
+ name = 'environ',
+ params = {},
+ signature = 'environ()',
+ },
+ escape = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Escape the characters in {chars} that occur in {string} with a
+ backslash. Example: >vim
+ echo escape('c:\program files\vim', ' \')
+ <results in: >
+ c:\\program\ files\\vim
+ <Also see |shellescape()| and |fnameescape()|.
+
+ ]=],
+ fast = true,
+ name = 'escape',
+ params = { { 'string', 'string' }, { 'chars', 'any' } },
+ signature = 'escape({string}, {chars})',
+ },
+ eval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate {string} and return the result. Especially useful to
+ turn the result of |string()| back into the original value.
+ This works for Numbers, Floats, Strings, Blobs and composites
+ of them. Also works for |Funcref|s that refer to existing
+ functions.
+
+ ]=],
+ name = 'eval',
+ params = { { 'string', 'string' } },
+ signature = 'eval({string})',
+ },
+ eventhandler = {
+ desc = [=[
+ Returns 1 when inside an event handler. That is that Vim got
+ interrupted while waiting for the user to type a character,
+ e.g., when dropping a file on Vim. This means interactive
+ commands cannot be used. Otherwise zero is returned.
+ ]=],
+ name = 'eventhandler',
+ params = {},
+ signature = 'eventhandler()',
+ },
+ executable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ This function checks if an executable with the name {expr}
+ exists. {expr} must be the name of the program without any
+ arguments.
+ executable() uses the value of $PATH and/or the normal
+ searchpath for programs. *PATHEXT*
+ On MS-Windows the ".exe", ".bat", etc. can optionally be
+ included. Then the extensions in $PATHEXT are tried. Thus if
+ "foo.exe" does not exist, "foo.exe.bat" can be found. If
+ $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot
+ by itself can be used in $PATHEXT to try using the name
+ without an extension. When 'shell' looks like a Unix shell,
+ then the name is also tried without adding an extension.
+ On MS-Windows it only checks if the file exists and is not a
+ directory, not if it's really executable.
+ On Windows an executable in the same directory as Vim is
+ always found (it is added to $PATH at |startup|).
+ The result is a Number:
+ 1 exists
+ 0 does not exist
+ -1 not implemented on this system
+ |exepath()| can be used to get the full path of an executable.
+
+ ]=],
+ fast = true,
+ name = 'executable',
+ params = { { 'expr', 'any' } },
+ returns = '0|1|-1',
+ signature = 'executable({expr})',
+ },
+ execute = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Execute {command} and capture its output.
+ If {command} is a |String|, returns {command} output.
+ If {command} is a |List|, returns concatenated outputs.
+ Line continuations in {command} are not recognized.
+ Examples: >vim
+ echo execute('echon "foo"')
+ < foo >vim
+ echo execute(['echon "foo"', 'echon "bar"'])
+ < foobar
+
+ The optional {silent} argument can have these values:
+ "" no `:silent` used
+ "silent" `:silent` used
+ "silent!" `:silent!` used
+ The default is "silent". Note that with "silent!", unlike
+ `:redir`, error messages are dropped.
+
+ To get a list of lines use `split()` on the result: >vim
+ execute('args')->split("\n")
+
+ <This function is not available in the |sandbox|.
+ Note: If nested, an outer execute() will not observe output of
+ the inner calls.
+ Note: Text attributes (highlights) are not captured.
+ To execute a command in another window than the current one
+ use `win_execute()`.
+
+ ]=],
+ name = 'execute',
+ params = {
+ { 'command', 'string|string[]' },
+ { 'silent', "''|'silent'|'silent!'" }
+ },
+ returns = 'string',
+ signature = 'execute({command} [, {silent}])',
+ },
+ exepath = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the full path of {expr} if it is an executable and
+ given as a (partial or full) path or is found in $PATH.
+ Returns empty string otherwise.
+ If {expr} starts with "./" the |current-directory| is used.
+
+ ]=],
+ name = 'exepath',
+ params = { { 'expr', 'any' } },
+ signature = 'exepath({expr})',
+ },
+ exists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| if {expr} is
+ defined, zero otherwise.
+
+ For checking for a supported feature use |has()|.
+ 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.: >vim
+ let l = [1, 2, 3]
+ echo exists("l[5]")
+ < 0 >vim
+ 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.
+ $ENVNAME environment variable (could also be
+ done by comparing with an empty
+ string)
+ `*funcname` built-in function (see |functions|)
+ or user defined function (see
+ |user-function|). Also works for a
+ variable that is a Funcref.
+ :cmdname Ex command: built-in command, user
+ command or command modifier |:command|.
+ Returns:
+ 1 for match with start of a command
+ 2 full match with a command
+ 3 matches several user commands
+ To check for a supported command
+ always check the return value to be 2.
+ :2match The |:2match| command.
+ :3match The |:3match| command (but you
+ probably should not use it, it is
+ reserved for internal usage)
+ #event autocommand defined for this event
+ #event#pattern autocommand defined for this event and
+ pattern (the pattern is taken
+ literally and compared to the
+ autocommand patterns character by
+ character)
+ #group autocommand group exists
+ #group#event autocommand defined for this group and
+ event.
+ #group#event#pattern
+ autocommand defined for this group,
+ event and pattern.
+ ##event autocommand for this event is
+ supported.
+
+ Examples: >vim
+ echo exists("&mouse")
+ echo exists("$HOSTNAME")
+ echo exists("*strftime")
+ echo exists("*s:MyFunc")
+ echo exists("*MyFunc")
+ echo exists("bufcount")
+ echo exists(":Make")
+ echo exists("#CursorHold")
+ echo exists("#BufReadPre#*.gz")
+ echo exists("#filetypeindent")
+ echo exists("#filetypeindent#FileType")
+ echo exists("#filetypeindent#FileType#*")
+ echo exists("##ColorScheme")
+ <There must be no space between the symbol (&/$/*/#) and the
+ name.
+ There must be no extra characters after the name, although in
+ a few cases this is ignored. That may become stricter in the
+ future, thus don't count on it!
+ Working example: >vim
+ echo exists(":make")
+ <NOT working example: >vim
+ echo exists(":make install")
+
+ <Note that the argument must be a string, not the name of the
+ variable itself. For example: >vim
+ echo exists(bufcount)
+ <This doesn't check for existence of the "bufcount" variable,
+ but gets the value of "bufcount", and checks if that exists.
+
+ ]=],
+ name = 'exists',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'exists({expr})',
+ },
+ exp = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the exponential of {expr} as a |Float| in the range
+ [0, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo exp(2)
+ < 7.389056 >vim
+ echo exp(-1)
+ < 0.367879
+
+ ]=],
+ float_func = 'exp',
+ name = 'exp',
+ params = { { 'expr', 'any' } },
+ signature = 'exp({expr})',
+ },
+ expand = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Expand wildcards and the following special keywords in
+ {string}. 'wildignorecase' applies.
+
+ If {list} is given and it is |TRUE|, a List will be returned.
+ Otherwise the result is a String and when there are several
+ matches, they are separated by <NL> characters.
+
+ If the expansion fails, the result is an empty string. A name
+ for a non-existing file is not included, unless {string} does
+ not start with '%', '#' or '<', see below.
+
+ When {string} starts with '%', '#' or '<', the expansion is
+ done like for the |cmdline-special| variables with their
+ associated modifiers. Here is a short overview:
+
+ % current file name
+ # alternate file name
+ #n alternate file name n
+ <cfile> file name under the cursor
+ <afile> autocmd file name
+ <abuf> autocmd buffer number (as a String!)
+ <amatch> autocmd matched name
+ <cexpr> C expression under the cursor
+ <sfile> sourced script file or function name
+ <slnum> sourced script line number or function
+ line number
+ <sflnum> script file line number, also when in
+ a function
+ <SID> "<SNR>123_" where "123" is the
+ current script ID |<SID>|
+ <script> sourced script file, or script file
+ where the current function was defined
+ <stack> call stack
+ <cword> word under the cursor
+ <cWORD> WORD under the cursor
+ <client> the {clientid} of the last received
+ message
+ Modifiers:
+ :p expand to full path
+ :h head (last path component removed)
+ :t tail (last path component only)
+ :r root (one extension removed)
+ :e extension only
+
+ Example: >vim
+ let &tags = expand("%:p:h") .. "/tags"
+ <Note that when expanding a string that starts with '%', '#' or
+ '<', any following text is ignored. This does NOT work: >vim
+ let doesntwork = expand("%:h.bak")
+ <Use this: >vim
+ let doeswork = expand("%:h") .. ".bak"
+ <Also note that expanding "<cfile>" and others only returns the
+ referenced file name without further expansion. If "<cfile>"
+ is "~/.cshrc", you need to do another expand() to have the
+ "~/" expanded into the path of the home directory: >vim
+ echo expand(expand("<cfile>"))
+ <
+ There cannot be white space between the variables and the
+ following modifier. The |fnamemodify()| function can be used
+ to modify normal file names.
+
+ When using '%' or '#', and the current or alternate file name
+ is not defined, an empty string is used. Using "%:p" in a
+ buffer with no name, results in the current directory, with a
+ '/' added.
+ When 'verbose' is set then expanding '%', '#' and <> items
+ will result in an error message if the argument cannot be
+ expanded.
+
+ When {string} does not start with '%', '#' or '<', it is
+ expanded like a file name is expanded on the command line.
+ 'suffixes' and 'wildignore' are used, unless the optional
+ {nosuf} argument is given and it is |TRUE|.
+ Names for non-existing files are included. The "**" item can
+ be used to search in a directory tree. For example, to find
+ all "README" files in the current directory and below: >vim
+ echo expand("**/README")
+ <
+ expand() can also be used to expand variables and environment
+ variables that are only known in a shell. But this can be
+ slow, because a shell may be used to do the expansion. See
+ |expr-env-expand|.
+ The expanded variable is still handled like a list of file
+ names. When an environment variable cannot be expanded, it is
+ left unchanged. Thus ":echo expand('$FOOBAR')" results in
+ "$FOOBAR".
+
+ See |glob()| for finding existing files. See |system()| for
+ getting the raw output of an external command.
+
+ ]=],
+ name = 'expand',
+ params = { { 'string', 'string' }, { 'nosuf', 'boolean' }, { 'list', 'any' } },
+ returns = 'string|string[]',
+ signature = 'expand({string} [, {nosuf} [, {list}]])',
+ },
+ expandcmd = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Expand special items in String {string} like what is done for
+ an Ex command such as `:edit`. This expands special keywords,
+ like with |expand()|, and environment variables, anywhere in
+ {string}. "~user" and "~/path" are only expanded at the
+ start.
+
+ The following items are supported in the {options} Dict
+ argument:
+ errmsg If set to TRUE, error messages are displayed
+ if an error is encountered during expansion.
+ By default, error messages are not displayed.
+
+ Returns the expanded string. If an error is encountered
+ during expansion, the unmodified {string} is returned.
+
+ Example: >vim
+ echo expandcmd('make %<.o')
+ < >
+ make /path/runtime/doc/builtin.o
+ < >vim
+ echo expandcmd('make %<.o', {'errmsg': v:true})
+ <
+ ]=],
+ name = 'expandcmd',
+ params = { { 'string', 'string' }, { 'options', 'table' } },
+ signature = 'expandcmd({string} [, {options}])',
+ },
+ extend = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ {expr1} and {expr2} must be both |Lists| or both
+ |Dictionaries|.
+
+ If they are |Lists|: Append {expr2} to {expr1}.
+ If {expr3} is given insert the items of {expr2} before the
+ item with index {expr3} in {expr1}. When {expr3} is zero
+ insert before the first item. When {expr3} is equal to
+ len({expr1}) then {expr2} is appended.
+ Examples: >vim
+ echo sort(extend(mylist, [7, 5]))
+ call extend(mylist, [2, 3], 1)
+ <When {expr1} is the same List as {expr2} then the number of
+ items copied is equal to the original length of the List.
+ E.g., when {expr3} is 1 you get N new copies of the first item
+ (where N is the original length of the List).
+ Use |add()| to concatenate one item to a list. To concatenate
+ two lists into a new list use the + operator: >vim
+ let newlist = [1, 2, 3] + [4, 5]
+ <
+ If they are |Dictionaries|:
+ Add all entries from {expr2} to {expr1}.
+ If a key exists in both {expr1} and {expr2} then {expr3} is
+ used to decide what to do:
+ {expr3} = "keep": keep the value of {expr1}
+ {expr3} = "force": use the value of {expr2}
+ {expr3} = "error": give an error message *E737*
+ When {expr3} is omitted then "force" is assumed.
+
+ {expr1} is changed when {expr2} is not empty. If necessary
+ make a copy of {expr1} first.
+ {expr2} remains unchanged.
+ When {expr1} is locked and {expr2} is not empty the operation
+ fails.
+ Returns {expr1}. Returns 0 on error.
+
+ ]=],
+ name = 'extend',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ signature = 'extend({expr1}, {expr2} [, {expr3}])',
+ },
+ extendnew = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like |extend()| but instead of adding items to {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged.
+ ]=],
+ name = 'extendnew',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ signature = 'extendnew({expr1}, {expr2} [, {expr3}])',
+ },
+ feedkeys = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Characters in {string} are queued for processing as if they
+ come from a mapping or were typed by the user.
+
+ By default the string is added to the end of the typeahead
+ buffer, thus if a mapping is still being executed the
+ characters come after them. Use the 'i' flag to insert before
+ other characters, they will be executed next, before any
+ characters from a mapping.
+
+ The function does not wait for processing of keys contained in
+ {string}.
+
+ To include special keys into {string}, use double-quotes
+ and "\..." notation |expr-quote|. For example,
+ feedkeys("\<CR>") simulates pressing of the <Enter> key. But
+ feedkeys('\<CR>') pushes 5 characters.
+ The |<Ignore>| keycode may be used to exit the
+ wait-for-character without doing anything.
+
+ {mode} is a String, which can contain these character flags:
+ 'm' Remap keys. This is default. If {mode} is absent,
+ keys are remapped.
+ 'n' Do not remap keys.
+ 't' Handle keys as if typed; otherwise they are handled as
+ if coming from a mapping. This matters for undo,
+ opening folds, etc.
+ 'i' Insert the string instead of appending (see above).
+ 'x' Execute commands until typeahead is empty. This is
+ similar to using ":normal!". You can call feedkeys()
+ several times without 'x' and then one time with 'x'
+ (possibly with an empty {string}) to execute all the
+ typeahead. Note that when Vim ends in Insert mode it
+ will behave as if <Esc> is typed, to avoid getting
+ stuck, waiting for a character to be typed before the
+ script continues.
+ Note that if you manage to call feedkeys() while
+ executing commands, thus calling it recursively, then
+ all typeahead will be consumed by the last call.
+ '!' When used with 'x' will not end Insert mode. Can be
+ used in a test when a timer is set to exit Insert mode
+ a little later. Useful for testing CursorHoldI.
+
+ Return value is always 0.
+
+ ]=],
+ name = 'feedkeys',
+ params = { { 'string', 'string' }, { 'mode', 'string' } },
+ signature = 'feedkeys({string} [, {mode}])',
+ },
+ file_readable = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |filereadable()|.
+ ]=],
+ func = 'f_filereadable',
+ name = 'file_readable',
+ params = { { 'file', 'string' } },
+ signature = 'file_readable({file})',
+ },
+ filereadable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when a file with the
+ name {file} exists, and can be read. If {file} doesn't exist,
+ or is a directory, the result is |FALSE|. {file} is any
+ expression, which is used as a String.
+ If you don't care about the file being readable you can use
+ |glob()|.
+ {file} is used as-is, you may want to expand wildcards first: >vim
+ echo filereadable('~/.vimrc')
+ < >
+ 0
+ < >vim
+ echo filereadable(expand('~/.vimrc'))
+ < >
+ 1
+ <
+
+ ]=],
+ fast = true,
+ name = 'filereadable',
+ params = { { 'file', 'string' } },
+ returns = '0|1',
+ signature = 'filereadable({file})',
+ },
+ filewritable = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is 1 when a file with the
+ name {file} exists, and can be written. If {file} doesn't
+ exist, or is not writable, the result is 0. If {file} is a
+ directory, and we can write to it, the result is 2.
+
+ ]=],
+ fast = true,
+ name = 'filewritable',
+ params = { { 'file', 'string' } },
+ returns = '0|1',
+ signature = 'filewritable({file})',
+ },
+ filter = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ For each item in {expr1} evaluate {expr2} and when the result
+ is zero or false remove the item from the |List| or
+ |Dictionary|. Similarly for each byte in a |Blob| and each
+ character in a |String|.
+
+ {expr2} must be a |string| or |Funcref|.
+
+ If {expr2} is a |string|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Examples: >vim
+ call filter(mylist, 'v:val !~ "OLD"')
+ <Removes the items where "OLD" appears. >vim
+ call filter(mydict, 'v:key >= 8')
+ <Removes the items with a key below 8. >vim
+ call filter(var, 0)
+ <Removes all the items, thus clears the |List| or |Dictionary|.
+
+ Note that {expr2} is the result of expression and is then
+ used as an expression again. Often it is good to use a
+ |literal-string| to avoid having to double backslashes.
+
+ If {expr2} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ The function must return |TRUE| if the item should be kept.
+ Example that keeps the odd items of a list: >vim
+ func Odd(idx, val)
+ return a:idx % 2 == 1
+ endfunc
+ call filter(mylist, function('Odd'))
+ <It is shorter when using a |lambda|: >vim
+ call filter(myList, {idx, val -> idx * val <= 42})
+ <If you do not use "val" you can leave it out: >vim
+ call filter(myList, {idx -> idx % 2 == 1})
+ <
+ For a |List| and a |Dictionary| the operation is done
+ in-place. If you want it to remain unmodified make a copy
+ first: >vim
+ let l = filter(copy(mylist), 'v:val =~ "KEEP"')
+
+ <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+
+ ]=],
+ name = 'filter',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'filter({expr1}, {expr2})',
+ },
+ finddir = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Find directory {name} in {path}. Supports both downwards and
+ upwards recursive directory searches. See |file-searching|
+ for the syntax of {path}.
+
+ Returns the path of the first found match. When the found
+ directory is below the current directory a relative path is
+ returned. Otherwise a full path is returned.
+ If {path} is omitted or empty then 'path' is used.
+
+ If the optional {count} is given, find {count}'s occurrence of
+ {name} in {path} instead of the first one.
+ When {count} is negative return all the matches in a |List|.
+
+ Returns an empty string if the directory is not found.
+
+ This is quite similar to the ex-command `:find`.
+
+ ]=],
+ name = 'finddir',
+ params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'any' } },
+ signature = 'finddir({name} [, {path} [, {count}]])',
+ },
+ findfile = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Just like |finddir()|, but find a file instead of a directory.
+ Uses 'suffixesadd'.
+ Example: >vim
+ echo findfile("tags.vim", ".;")
+ <Searches from the directory of the current file upwards until
+ it finds the file "tags.vim".
+
+ ]=],
+ name = 'findfile',
+ params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'any' } },
+ signature = 'findfile({name} [, {path} [, {count}]])',
+ },
+ flatten = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Flatten {list} up to {maxdepth} levels. Without {maxdepth}
+ the result is a |List| without nesting, as if {maxdepth} is
+ a very large number.
+ The {list} is changed in place, use |flattennew()| if you do
+ not want that.
+ *E900*
+ {maxdepth} means how deep in nested lists changes are made.
+ {list} is not modified when {maxdepth} is 0.
+ {maxdepth} must be positive number.
+
+ If there is an error the number zero is returned.
+
+ Example: >vim
+ echo flatten([1, [2, [3, 4]], 5])
+ < [1, 2, 3, 4, 5] >vim
+ echo flatten([1, [2, [3, 4]], 5], 1)
+ < [1, 2, [3, 4], 5]
+
+ ]=],
+ name = 'flatten',
+ params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ returns = 'any[]|0',
+ signature = 'flatten({list} [, {maxdepth}])',
+ },
+ flattennew = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Like |flatten()| but first make a copy of {list}.
+ ]=],
+ name = 'flattennew',
+ params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ returns = 'any[]|0',
+ signature = 'flattennew({list} [, {maxdepth}])',
+ },
+ float2nr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} to a Number by omitting the part after the
+ decimal point.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0 if {expr} is not a |Float| or a |Number|.
+ When the value of {expr} is out of range for a |Number| the
+ result is truncated to 0x7fffffff or -0x7fffffff (or when
+ 64-bit Number support is enabled, 0x7fffffffffffffff or
+ -0x7fffffffffffffff). NaN results in -0x80000000 (or when
+ 64-bit Number support is enabled, -0x8000000000000000).
+ Examples: >vim
+ echo float2nr(3.95)
+ < 3 >vim
+ echo float2nr(-23.45)
+ < -23 >vim
+ echo float2nr(1.0e100)
+ < 2147483647 (or 9223372036854775807) >vim
+ echo float2nr(-1.0e150)
+ < -2147483647 (or -9223372036854775807) >vim
+ echo float2nr(1.0e-100)
+ < 0
+
+ ]=],
+ name = 'float2nr',
+ params = { { 'expr', 'any' } },
+ signature = 'float2nr({expr})',
+ },
+ floor = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the largest integral value less than or equal to
+ {expr} as a |Float| (round down).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo floor(1.856)
+ < 1.0 >vim
+ echo floor(-5.456)
+ < -6.0 >vim
+ echo floor(4.0)
+ < 4.0
+
+ ]=],
+ float_func = 'floor',
+ name = 'floor',
+ params = { { 'expr', 'any' } },
+ signature = 'floor({expr})',
+ },
+ fmod = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the remainder of {expr1} / {expr2}, even if the
+ division is not representable. Returns {expr1} - i * {expr2}
+ for some integer i such that if {expr2} is non-zero, the
+ result has the same sign as {expr1} and magnitude less than
+ the magnitude of {expr2}. If {expr2} is zero, the value
+ returned is zero. The value returned is a |Float|.
+ {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr1} or {expr2} is not a |Float| or a
+ |Number|.
+ Examples: >vim
+ echo fmod(12.33, 1.22)
+ < 0.13 >vim
+ echo fmod(-12.33, 1.22)
+ < -0.13
+
+ ]=],
+ name = 'fmod',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'fmod({expr1}, {expr2})',
+ },
+ fnameescape = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Escape {string} for use as file name command argument. All
+ characters that have a special meaning, such as `'%'` and `'|'`
+ are escaped with a backslash.
+ For most systems the characters escaped are
+ " \t\n*?[{`$\\%#'\"|!<". For systems where a backslash
+ appears in a filename, it depends on the value of 'isfname'.
+ A leading '+' and '>' is also escaped (special after |:edit|
+ and |:write|). And a "-" by itself (special after |:cd|).
+ Returns an empty string on error.
+ Example: >vim
+ let fname = '+some str%nge|name'
+ exe "edit " .. fnameescape(fname)
+ <results in executing: >vim
+ edit \+some\ str\%nge\|name
+ <
+ ]=],
+ fast = true,
+ name = 'fnameescape',
+ params = { { 'string', 'string' } },
+ returns = 'string',
+ signature = 'fnameescape({string})',
+ },
+ fnamemodify = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Modify file name {fname} according to {mods}. {mods} is a
+ string of characters like it is used for file names on the
+ command line. See |filename-modifiers|.
+ Example: >vim
+ echo fnamemodify("main.c", ":p:h")
+ <results in: >
+ /home/user/vim/vim/src
+ <If {mods} is empty or an unsupported modifier is used then
+ {fname} is returned.
+ When {fname} is empty then with {mods} ":h" returns ".", so
+ that `:cd` can be used with it. This is different from
+ expand('%:h') without a buffer name, which returns an empty
+ string.
+ Note: Environment variables don't work in {fname}, use
+ |expand()| first then.
+
+ ]=],
+ fast = true,
+ name = 'fnamemodify',
+ params = { { 'fname', 'string' }, { 'mods', 'string' } },
+ returns = 'string',
+ signature = 'fnamemodify({fname}, {mods})',
+ },
+ foldclosed = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number. If the line {lnum} is in a closed
+ fold, the result is the number of the first line in that fold.
+ If the line {lnum} is not in a closed fold, -1 is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldclosed',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldclosed({lnum})',
+ },
+ foldclosedend = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number. If the line {lnum} is in a closed
+ fold, the result is the number of the last line in that fold.
+ If the line {lnum} is not in a closed fold, -1 is returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldclosedend',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldclosedend({lnum})',
+ },
+ foldlevel = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the foldlevel of line {lnum}
+ in the current buffer. For nested folds the deepest level is
+ returned. If there is no fold at line {lnum}, zero is
+ returned. It doesn't matter if the folds are open or closed.
+ When used while updating folds (from 'foldexpr') -1 is
+ returned for lines where folds are still to be updated and the
+ foldlevel is unknown. As a special case the level of the
+ previous line is usually available.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+
+ ]=],
+ name = 'foldlevel',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'foldlevel({lnum})',
+ },
+ foldtext = {
+ desc = [=[
+ Returns a String, to be displayed for a closed fold. This is
+ the default function used for the 'foldtext' option and should
+ only be called from evaluating 'foldtext'. It uses the
+ |v:foldstart|, |v:foldend| and |v:folddashes| variables.
+ The returned string looks like this: >
+ +-- 45 lines: abcdef
+ <The number of leading dashes depends on the foldlevel. The
+ "45" is the number of lines in the fold. "abcdef" is the text
+ in the first non-blank line of the fold. Leading white space,
+ "//" or "/*" and the text from the 'foldmarker' and
+ 'commentstring' options is removed.
+ When used to draw the actual foldtext, the rest of the line
+ will be filled with the fold char from the 'fillchars'
+ setting.
+ Returns an empty string when there is no fold.
+ ]=],
+ name = 'foldtext',
+ params = {},
+ returns = 'string',
+ signature = 'foldtext()',
+ },
+ foldtextresult = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the text that is displayed for the closed fold at line
+ {lnum}. Evaluates 'foldtext' in the appropriate context.
+ When there is no closed fold at {lnum} an empty string is
+ returned.
+ {lnum} is used like with |getline()|. Thus "." is the current
+ line, "'m" mark m, etc.
+ Useful when exporting folded text, e.g., to HTML.
+
+ ]=],
+ name = 'foldtextresult',
+ params = { { 'lnum', 'integer' } },
+ returns = 'string',
+ signature = 'foldtextresult({lnum})',
+ },
+ foreground = {
+ args = 0,
+ params = {},
+ signature = '',
+ lua = false,
+ },
+ fullcommand = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the full command name from a short abbreviated command
+ name; see |20.2| for details on command abbreviations.
+
+ The string argument {name} may start with a `:` and can
+ include a [range], these are skipped and not returned.
+ Returns an empty string if a command doesn't exist or if it's
+ ambiguous (for user-defined commands).
+
+ For example `fullcommand('s')`, `fullcommand('sub')`,
+ `fullcommand(':%substitute')` all return "substitute".
+
+ ]=],
+ name = 'fullcommand',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'fullcommand({name})',
+ },
+ funcref = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Just like |function()|, but the returned Funcref will lookup
+ the function by reference, not by name. This matters when the
+ function {name} is redefined later.
+
+ Unlike |function()|, {name} must be an existing user function.
+ It only works for an autoloaded function if it has already
+ been loaded (to avoid mistakenly loading the autoload script
+ when only intending to use the function name, use |function()|
+ instead). {name} cannot be a builtin function.
+ Returns 0 on error.
+
+ ]=],
+ name = 'funcref',
+ params = { { 'name', 'string' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ signature = 'funcref({name} [, {arglist}] [, {dict}])',
+ },
+ ['function'] = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return a |Funcref| variable that refers to function {name}.
+ {name} can be the name of a user defined function or an
+ internal function.
+
+ {name} can also be a Funcref or a partial. When it is a
+ partial the dict stored in it will be used and the {dict}
+ argument is not allowed. E.g.: >vim
+ let FuncWithArg = function(dict.Func, [arg])
+ let Broken = function(dict.Func, [arg], dict)
+ <
+ When using the Funcref the function will be found by {name},
+ also when it was redefined later. Use |funcref()| to keep the
+ same function.
+
+ When {arglist} or {dict} is present this creates a partial.
+ That means the argument list and/or the dictionary is stored in
+ the Funcref and will be used when the Funcref is called.
+
+ The arguments are passed to the function in front of other
+ arguments, but after any argument from |method|. Example: >vim
+ func Callback(arg1, arg2, name)
+ "...
+ endfunc
+ let Partial = function('Callback', ['one', 'two'])
+ "...
+ call Partial('name')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'name')
+
+ <With a |method|: >vim
+ func Callback(one, two, three)
+ "...
+ endfunc
+ let Partial = function('Callback', ['two'])
+ "...
+ eval 'one'->Partial('three')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'three')
+
+ <The function() call can be nested to add more arguments to the
+ Funcref. The extra arguments are appended to the list of
+ arguments. Example: >vim
+ func Callback(arg1, arg2, name)
+ "...
+ endfunc
+ let Func = function('Callback', ['one'])
+ let Func2 = function(Func, ['two'])
+ "...
+ call Func2('name')
+ <Invokes the function as with: >vim
+ call Callback('one', 'two', 'name')
+
+ <The Dictionary is only useful when calling a "dict" function.
+ In that case the {dict} is passed in as "self". Example: >vim
+ function Callback() dict
+ echo "called for " .. self.name
+ endfunction
+ "...
+ let context = {"name": "example"}
+ let Func = function('Callback', context)
+ "...
+ call Func() " will echo: called for example
+ <The use of function() is not needed when there are no extra
+ arguments, these two are equivalent, if Callback() is defined
+ as context.Callback(): >vim
+ let Func = function('Callback', context)
+ let Func = context.Callback
+
+ <The argument list and the Dictionary can be combined: >vim
+ function Callback(arg1, count) dict
+ "...
+ endfunction
+ let context = {"name": "example"}
+ let Func = function('Callback', ['one'], context)
+ "...
+ call Func(500)
+ <Invokes the function as with: >vim
+ call context.Callback('one', 500)
+ <
+ Returns 0 on error.
+
+ ]=],
+ name = 'function',
+ params = { { 'name', 'string' }, { 'arglist', 'any' }, { 'dict', 'any' } },
+ signature = 'function({name} [, {arglist}] [, {dict}])',
+ tags = { 'partial', 'E700', 'E923' },
+ },
+ garbagecollect = {
+ args = { 0, 1 },
+ desc = [=[
+ Cleanup unused |Lists| and |Dictionaries| that have circular
+ references.
+
+ There is hardly ever a need to invoke this function, as it is
+ automatically done when Vim runs out of memory or is waiting
+ for the user to press a key after 'updatetime'. Items without
+ circular references are always freed when they become unused.
+ This is useful if you have deleted a very big |List| and/or
+ |Dictionary| with circular references in a script that runs
+ for a long time.
+
+ When the optional {atexit} argument is one, garbage
+ collection will also be done when exiting Vim, if it wasn't
+ done before. This is useful when checking for memory leaks.
+
+ The garbage collection is not done immediately but only when
+ it's safe to perform. This is when waiting for the user to
+ type a character.
+ ]=],
+ name = 'garbagecollect',
+ params = { { 'atexit', 'any' } },
+ signature = 'garbagecollect([{atexit}])',
+ },
+ get = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item {idx} from |List| {list}. When this item is not
+ available return {default}. Return zero when {default} is
+ omitted.
+ ]=],
+ name = 'get',
+ params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } },
+ signature = 'get({list}, {idx} [, {default}])',
+ },
+ get__1 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get byte {idx} from |Blob| {blob}. When this byte is not
+ available return {default}. Return -1 when {default} is
+ omitted.
+ ]=],
+ name = 'get',
+ params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } },
+ signature = 'get({blob}, {idx} [, {default}])',
+ },
+ get__2 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item with key {key} from |Dictionary| {dict}. When this
+ item is not available return {default}. Return zero when
+ {default} is omitted. Useful example: >vim
+ let val = get(g:, 'var_name', 'default')
+ <This gets the value of g:var_name if it exists, and uses
+ "default" when it does not exist.
+ ]=],
+ name = 'get',
+ params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } },
+ signature = 'get({dict}, {key} [, {default}])',
+ },
+ get__3 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get item {what} from Funcref {func}. Possible values for
+ {what} are:
+ "name" The function name
+ "func" The function
+ "dict" The dictionary
+ "args" The list with arguments
+ Returns zero on error.
+ ]=],
+ name = 'get',
+ params = { { 'func', 'function' }, { 'what', 'string' } },
+ returns = 'any',
+ signature = 'get({func}, {what})',
+ },
+ getbufinfo = {
+ args = { 0, 1 },
+ base = 1,
+ name = 'getbufinfo',
+ params = { { 'buf', 'integer|string' } },
+ signature = 'getbufinfo([{buf}])',
+ returns = 'vim.fn.getbufinfo.ret.item[]',
+ },
+ getbufinfo__1 = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get information about buffers as a List of Dictionaries.
+
+ Without an argument information about all the buffers is
+ returned.
+
+ When the argument is a |Dictionary| only the buffers matching
+ the specified criteria are returned. The following keys can
+ be specified in {dict}:
+ buflisted include only listed buffers.
+ bufloaded include only loaded buffers.
+ bufmodified include only modified buffers.
+
+ Otherwise, {buf} specifies a particular buffer to return
+ information for. For the use of {buf}, see |bufname()|
+ above. If the buffer is found the returned List has one item.
+ Otherwise the result is an empty list.
+
+ Each returned List item is a dictionary with the following
+ entries:
+ bufnr Buffer number.
+ changed TRUE if the buffer is modified.
+ changedtick Number of changes made to the buffer.
+ hidden TRUE if the buffer is hidden.
+ lastused Timestamp in seconds, like
+ |localtime()|, when the buffer was
+ last used.
+ listed TRUE if the buffer is listed.
+ lnum Line number used for the buffer when
+ opened in the current window.
+ Only valid if the buffer has been
+ displayed in the window in the past.
+ If you want the line number of the
+ last known cursor position in a given
+ window, use |line()|: >vim
+ echo line('.', {winid})
+ <
+ linecount Number of lines in the buffer (only
+ valid when loaded)
+ loaded TRUE if the buffer is loaded.
+ name Full path to the file in the buffer.
+ signs List of signs placed in the buffer.
+ Each list item is a dictionary with
+ the following fields:
+ id sign identifier
+ lnum line number
+ name sign name
+ variables A reference to the dictionary with
+ buffer-local variables.
+ windows List of |window-ID|s that display this
+ buffer
+
+ Examples: >vim
+ for buf in getbufinfo()
+ echo buf.name
+ endfor
+ for buf in getbufinfo({'buflisted':1})
+ if buf.changed
+ " ....
+ endif
+ endfor
+ <
+ To get buffer-local options use: >vim
+ getbufvar({bufnr}, '&option_name')
+ <
+ ]=],
+ name = 'getbufinfo',
+ params = { { 'dict', 'vim.fn.getbufinfo.dict' } },
+ signature = 'getbufinfo([{dict}])',
+ returns = 'vim.fn.getbufinfo.ret.item[]',
+ },
+ getbufline = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return a |List| with the lines starting from {lnum} to {end}
+ (inclusive) in the buffer {buf}. If {end} is omitted, a
+ |List| with only the line {lnum} is returned. See
+ `getbufoneline()` for only getting the line.
+
+ For the use of {buf}, see |bufname()| above.
+
+ For {lnum} and {end} "$" can be used for the last line of the
+ buffer. Otherwise a number must be used.
+
+ When {lnum} is smaller than 1 or bigger than the number of
+ lines in the buffer, an empty |List| is returned.
+
+ When {end} is greater than the number of lines in the buffer,
+ it is treated as {end} is set to the number of lines in the
+ buffer. When {end} is before {lnum} an empty |List| is
+ returned.
+
+ This function works only for loaded buffers. For unloaded and
+ non-existing buffers, an empty |List| is returned.
+
+ Example: >vim
+ let lines = getbufline(bufnr("myfile"), 1, "$")
+
+ ]=],
+ name = 'getbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'end', 'integer' } },
+ signature = 'getbufline({buf}, {lnum} [, {end}])',
+ },
+ getbufoneline = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Just like `getbufline()` but only get one line and return it
+ as a string.
+ ]=],
+ name = 'getbufoneline',
+ params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' } },
+ signature = 'getbufoneline({buf}, {lnum})',
+ returns = 'string',
+ },
+ getbufvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is the value of option or local buffer variable
+ {varname} in buffer {buf}. Note that the name without "b:"
+ must be used.
+ The {varname} argument is a string.
+ When {varname} is empty returns a |Dictionary| with all the
+ buffer-local variables.
+ When {varname} is equal to "&" returns a |Dictionary| with all
+ the buffer-local options.
+ Otherwise, when {varname} starts with "&" returns the value of
+ a buffer-local option.
+ This also works for a global or buffer-local option, but it
+ doesn't work for a global variable, window-local variable or
+ window-local option.
+ For the use of {buf}, see |bufname()| above.
+ When the buffer or variable doesn't exist {def} or an empty
+ string is returned, there is no error message.
+ Examples: >vim
+ let bufmodified = getbufvar(1, "&mod")
+ echo "todo myvar = " .. getbufvar("todo", "myvar")
+
+ ]=],
+ name = 'getbufvar',
+ params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'getbufvar({buf}, {varname} [, {def}])',
+ },
+ getcellwidths = {
+ desc = [=[
+ Returns a |List| of cell widths of character ranges overridden
+ by |setcellwidths()|. The format is equal to the argument of
+ |setcellwidths()|. If no character ranges have their cell
+ widths overridden, an empty List is returned.
+ ]=],
+ name = 'getcellwidths',
+ params = {},
+ signature = 'getcellwidths()',
+ },
+ getchangelist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns the |changelist| for the buffer {buf}. For the use
+ of {buf}, see |bufname()| above. If buffer {buf} doesn't
+ exist, an empty list is returned.
+
+ The returned list contains two entries: a list with the change
+ locations and the current position in the list. Each
+ entry in the change list is a dictionary with the following
+ entries:
+ col column number
+ coladd column offset for 'virtualedit'
+ lnum line number
+ If buffer {buf} is the current buffer, then the current
+ position refers to the position in the list. For other
+ buffers, it is set to the length of the list.
+
+ ]=],
+ name = 'getchangelist',
+ params = { { 'buf', 'integer|string' } },
+ returns = 'table[]',
+ signature = 'getchangelist([{buf}])',
+ },
+ getchar = {
+ args = { 0, 1 },
+ desc = [=[
+ Get a single character from the user or input stream.
+ If [expr] is omitted, wait until a character is available.
+ If [expr] is 0, only get a character when one is available.
+ Return zero otherwise.
+ If [expr] is 1, only check if a character is available, it is
+ not consumed. Return zero if no character available.
+ If you prefer always getting a string use |getcharstr()|.
+
+ Without [expr] and when [expr] is 0 a whole character or
+ special key is returned. If it is a single character, the
+ result is a Number. Use |nr2char()| to convert it to a String.
+ Otherwise a String is returned with the encoded character.
+ For a special key it's a String with a sequence of bytes
+ starting with 0x80 (decimal: 128). This is the same value as
+ the String "\<Key>", e.g., "\<Left>". The returned value is
+ also a String when a modifier (shift, control, alt) was used
+ that is not included in the character.
+
+ When [expr] is 0 and Esc is typed, there will be a short delay
+ while Vim waits to see if this is the start of an escape
+ sequence.
+
+ When [expr] is 1 only the first byte is returned. For a
+ one-byte character it is the character itself as a number.
+ Use nr2char() to convert it to a String.
+
+ Use getcharmod() to obtain any additional modifiers.
+
+ When the user clicks a mouse button, the mouse event will be
+ returned. The position can then be found in |v:mouse_col|,
+ |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|.
+ |getmousepos()| can also be used. Mouse move events will be
+ ignored.
+ This example positions the mouse as it would normally happen: >vim
+ let c = getchar()
+ if c == "\<LeftMouse>" && v:mouse_win > 0
+ exe v:mouse_win .. "wincmd w"
+ exe v:mouse_lnum
+ exe "normal " .. v:mouse_col .. "|"
+ endif
+ <
+ There is no prompt, you will somehow have to make clear to the
+ user that a character has to be typed. The screen is not
+ redrawn, e.g. when resizing the window.
+
+ There is no mapping for the character.
+ Key codes are replaced, thus when the user presses the <Del>
+ key you get the code for the <Del> key, not the raw character
+ sequence. Examples: >vim
+ getchar() == "\<Del>"
+ getchar() == "\<S-Left>"
+ <This example redefines "f" to ignore case: >vim
+ nmap f :call FindChar()<CR>
+ function FindChar()
+ let c = nr2char(getchar())
+ while col('.') < col('$') - 1
+ normal l
+ if getline('.')[col('.') - 1] ==? c
+ break
+ endif
+ endwhile
+ endfunction
+ <
+ ]=],
+ name = 'getchar',
+ params = {},
+ returns = 'integer',
+ signature = 'getchar([expr])',
+ },
+ getcharmod = {
+ desc = [=[
+ The result is a Number which is the state of the modifiers for
+ the last obtained character with getchar() or in another way.
+ These values are added together:
+ 2 shift
+ 4 control
+ 8 alt (meta)
+ 16 meta (when it's different from ALT)
+ 32 mouse double click
+ 64 mouse triple click
+ 96 mouse quadruple click (== 32 + 64)
+ 128 command (Macintosh only)
+ Only the modifiers that have not been included in the
+ character itself are obtained. Thus Shift-a results in "A"
+ without a modifier. Returns 0 if no modifiers are used.
+ ]=],
+ name = 'getcharmod',
+ params = {},
+ returns = 'integer',
+ signature = 'getcharmod()',
+ },
+ getcharpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ 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.
+ If |getpos()| returns a very large column number, equal to
+ |v:maxcol|, then getcharpos() will return the character index
+ of the last character.
+
+ Example:
+ With the cursor on '세' in line 5 with text "여보세요": >vim
+ getcharpos('.') returns [0, 5, 3, 0]
+ getpos('.') returns [0, 5, 7, 0]
+ <
+ ]=],
+ name = 'getcharpos',
+ params = { { 'expr', 'any' } },
+ returns = 'integer[]',
+ signature = 'getcharpos({expr})',
+ },
+ getcharsearch = {
+ desc = [=[
+ Return the current character search information as a {dict}
+ with the following entries:
+
+ char character previously used for a character
+ search (|t|, |f|, |T|, or |F|); empty string
+ if no character search has been performed
+ forward direction of character search; 1 for forward,
+ 0 for backward
+ until type of character search; 1 for a |t| or |T|
+ character search, 0 for an |f| or |F|
+ character search
+
+ This can be useful to always have |;| and |,| search
+ forward/backward regardless of the direction of the previous
+ character search: >vim
+ nnoremap <expr> ; getcharsearch().forward ? ';' : ','
+ nnoremap <expr> , getcharsearch().forward ? ',' : ';'
+ <Also see |setcharsearch()|.
+ ]=],
+ name = 'getcharsearch',
+ params = {},
+ returns = 'table[]',
+ signature = 'getcharsearch()',
+ },
+ getcharstr = {
+ args = { 0, 1 },
+ desc = [=[
+ Get a single character from the user or input stream as a
+ string.
+ If [expr] is omitted, wait until a character is available.
+ If [expr] is 0 or false, only get a character when one is
+ available. Return an empty string otherwise.
+ If [expr] is 1 or true, only check if a character is
+ available, it is not consumed. Return an empty string
+ if no character is available.
+ Otherwise this works like |getchar()|, except that a number
+ result is converted to a string.
+ ]=],
+ name = 'getcharstr',
+ params = {},
+ returns = 'string',
+ signature = 'getcharstr([expr])',
+ },
+ getcmdcompltype = {
+ desc = [=[
+ Return the type of the current command-line completion.
+ Only works when the command line is being edited, thus
+ requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
+ See |:command-completion| for the return string.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ Returns an empty string when completion is not defined.
+ ]=],
+ name = 'getcmdcompltype',
+ params = {},
+ returns = 'string',
+ signature = 'getcmdcompltype()',
+ },
+ getcmdline = {
+ desc = [=[
+ Return the current command-line. Only works when the command
+ line is being edited, thus requires use of |c_CTRL-\_e| or
+ |c_CTRL-R_=|.
+ Example: >vim
+ cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
+ <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and
+ |setcmdline()|.
+ Returns an empty string when entering a password or using
+ |inputsecret()|.
+ ]=],
+ name = 'getcmdline',
+ params = {},
+ returns = 'string',
+ signature = 'getcmdline()',
+ },
+ getcmdpos = {
+ desc = [=[
+ Return the position of the cursor in the command line as a
+ byte count. The first column is 1.
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns 0 otherwise.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ ]=],
+ name = 'getcmdpos',
+ params = {},
+ returns = 'integer',
+ signature = 'getcmdpos()',
+ },
+ getcmdscreenpos = {
+ desc = [=[
+ Return the screen position of the cursor in the command line
+ as a byte count. The first column is 1.
+ Instead of |getcmdpos()|, it adds the prompt position.
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns 0 otherwise.
+ Also see |getcmdpos()|, |setcmdpos()|, |getcmdline()| and
+ |setcmdline()|.
+ ]=],
+ name = 'getcmdscreenpos',
+ params = {},
+ signature = 'getcmdscreenpos()',
+ },
+ getcmdtype = {
+ desc = [=[
+ Return the current command-line type. Possible return values
+ are:
+ : normal Ex command
+ > debug mode command |debug-mode|
+ / forward search command
+ ? backward search command
+ @ |input()| command
+ `-` |:insert| or |:append| command
+ = |i_CTRL-R_=|
+ Only works when editing the command line, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
+ Returns an empty string otherwise.
+ Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|.
+ ]=],
+ name = 'getcmdtype',
+ params = {},
+ returns = "':'|'>'|'/'|'?'|'@'|'-'|'='",
+ signature = 'getcmdtype()',
+ },
+ getcmdwintype = {
+ desc = [=[
+ Return the current |command-line-window| type. Possible return
+ values are the same as |getcmdtype()|. Returns an empty string
+ when not in the command-line window.
+ ]=],
+ name = 'getcmdwintype',
+ params = {},
+ returns = "':'|'>'|'/'|'?'|'@'|'-'|'='",
+ signature = 'getcmdwintype()',
+ },
+ getcompletion = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Return a list of command-line completion matches. The String
+ {type} argument specifies what for. The following completion
+ types are supported:
+
+ arglist file names in argument list
+ augroup autocmd groups
+ buffer buffer names
+ breakpoint |:breakadd| and |:breakdel| suboptions
+ cmdline |cmdline-completion| result
+ color color schemes
+ command Ex command
+ compiler compilers
+ custom,{func} custom completion, defined via {func}
+ customlist,{func} custom completion, defined via {func}
+ diff_buffer |:diffget| and |:diffput| completion
+ dir directory names
+ environment environment variable names
+ event autocommand events
+ expression Vim expression
+ file file and directory names
+ file_in_path file and directory names in |'path'|
+ filetype filetype names |'filetype'|
+ function function name
+ help help subjects
+ highlight highlight groups
+ history |:history| suboptions
+ locale locale names (as output of locale -a)
+ mapclear buffer argument
+ mapping mapping name
+ menu menus
+ messages |:messages| suboptions
+ option options
+ packadd optional package |pack-add| names
+ runtime |:runtime| completion
+ scriptnames sourced script names |:scriptnames|
+ shellcmd Shell command
+ sign |:sign| suboptions
+ syntax syntax file names |'syntax'|
+ syntime |:syntime| suboptions
+ tag tags
+ tag_listfiles tags, file names
+ user user names
+ var user variables
+
+ If {pat} is an empty string, then all the matches are
+ returned. Otherwise only items matching {pat} are returned.
+ See |wildcards| for the use of special characters in {pat}.
+
+ If the optional {filtered} flag is set to 1, then 'wildignore'
+ is applied to filter the results. Otherwise all the matches
+ are returned. The 'wildignorecase' option always applies.
+
+ If the 'wildoptions' option contains "fuzzy", then fuzzy
+ matching is used to get the completion matches. Otherwise
+ regular expression matching is used. Thus this function
+ follows the user preference, what happens on the command line.
+ If you do not want this you can make 'wildoptions' empty
+ before calling getcompletion() and restore it afterwards.
+
+ If {type} is "cmdline", then the |cmdline-completion| result is
+ returned. For example, to complete the possible values after
+ a ":call" command: >vim
+ echo getcompletion('call ', 'cmdline')
+ <
+ If there are no matches, an empty list is returned. An
+ invalid value for {type} produces an error.
+
+ ]=],
+ name = 'getcompletion',
+ params = { { 'pat', 'any' }, { 'type', 'any' }, { 'filtered', 'any' } },
+ returns = 'string[]',
+ signature = 'getcompletion({pat}, {type} [, {filtered}])',
+ },
+ getcurpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get the position of the cursor. This is like getpos('.'), but
+ includes an extra "curswant" item in the list:
+ [0, lnum, col, off, curswant] ~
+ The "curswant" number is the preferred column when moving the
+ cursor vertically. After |$| command it will be a very large
+ number equal to |v:maxcol|. 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: >vim
+ let save_cursor = getcurpos()
+ MoveTheCursorAround
+ call setpos('.', save_cursor)
+ <Note that this only works within the window. See
+ |winrestview()| for restoring more state.
+
+ ]=],
+ name = 'getcurpos',
+ params = { { 'winid', 'integer' } },
+ signature = 'getcurpos([{winid}])',
+ },
+ getcursorcharpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ 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 "여보세요": >vim
+ getcursorcharpos() " returns [0, 3, 2, 0, 3]
+ getcurpos() " returns [0, 3, 4, 0, 3]
+ <
+ ]=],
+ name = 'getcursorcharpos',
+ params = { { 'winid', 'integer' } },
+ signature = 'getcursorcharpos([{winid}])',
+ },
+ getcwd = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ 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
+ ignored.
+ Tabs and windows are identified by their respective numbers,
+ 0 means current tab or window. Missing tab number implies 0.
+ Thus the following are equivalent: >vim
+ getcwd(0)
+ getcwd(0, 0)
+ <If {winnr} is -1 it is ignored, only the tab is resolved.
+ {winnr} can be the window number or the |window-ID|.
+ If both {winnr} and {tabnr} are -1 the global working
+ directory is returned.
+ Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+
+ ]=],
+ name = 'getcwd',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = 'string',
+ signature = 'getcwd([{winnr} [, {tabnr}]])',
+ },
+ getenv = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the value of environment variable {name}. The {name}
+ argument is a string, without a leading '$'. Example: >vim
+ myHome = getenv('HOME')
+
+ <When the variable does not exist |v:null| is returned. That
+ is different from a variable set to an empty string.
+ See also |expr-env|.
+
+ ]=],
+ name = 'getenv',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'getenv({name})',
+ },
+ getfontname = {
+ args = { 0, 1 },
+ desc = [=[
+ Without an argument returns the name of the normal font being
+ used. Like what is used for the Normal highlight group
+ |hl-Normal|.
+ With an argument a check is done whether String {name} is a
+ valid font name. If not then an empty string is returned.
+ Otherwise the actual font name is returned, or {name} if the
+ GUI does not support obtaining the real name.
+ Only works when the GUI is running, thus not in your vimrc or
+ gvimrc file. Use the |GUIEnter| autocommand to use this
+ function just after the GUI has started.
+ ]=],
+ name = 'getfontname',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'getfontname([{name}])',
+ },
+ getfperm = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is the read, write, and execute
+ permissions of the given file {fname}.
+ If {fname} does not exist or its directory cannot be read, an
+ empty string is returned.
+ The result is of the form "rwxrwxrwx", where each group of
+ "rwx" flags represent, in turn, the permissions of the owner
+ of the file, the group the file belongs to, and other users.
+ If a user does not have a given permission the flag for this
+ is replaced with the string "-". Examples: >vim
+ echo getfperm("/etc/passwd")
+ echo getfperm(expand("~/.config/nvim/init.vim"))
+ <This will hopefully (from a security point of view) display
+ the string "rw-r--r--" or even "rw-------".
+
+ For setting permissions use |setfperm()|.
+ ]=],
+ fast = true,
+ name = 'getfperm',
+ params = { { 'fname', 'string' } },
+ returns = 'string',
+ signature = 'getfperm({fname})',
+ },
+ getfsize = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the size in bytes of the
+ given file {fname}.
+ If {fname} is a directory, 0 is returned.
+ If the file {fname} can't be found, -1 is returned.
+ If the size of {fname} is too big to fit in a Number then -2
+ is returned.
+
+ ]=],
+ fast = true,
+ name = 'getfsize',
+ params = { { 'fname', 'string' } },
+ returns = 'integer',
+ signature = 'getfsize({fname})',
+ },
+ getftime = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the last modification time of
+ the given file {fname}. The value is measured as seconds
+ since 1st Jan 1970, and may be passed to strftime(). See also
+ |localtime()| and |strftime()|.
+ If the file {fname} can't be found -1 is returned.
+
+ ]=],
+ fast = true,
+ name = 'getftime',
+ params = { { 'fname', 'string' } },
+ returns = 'integer',
+ signature = 'getftime({fname})',
+ },
+ getftype = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is a description of the kind of
+ file of the given file {fname}.
+ If {fname} does not exist an empty string is returned.
+ Here is a table over different kinds of files and their
+ results:
+ Normal file "file"
+ Directory "dir"
+ Symbolic link "link"
+ Block device "bdev"
+ Character device "cdev"
+ Socket "socket"
+ FIFO "fifo"
+ All other "other"
+ Example: >vim
+ getftype("/home")
+ <Note that a type such as "link" will only be returned on
+ systems that support it. On some systems only "dir" and
+ "file" are returned.
+
+ ]=],
+ fast = true,
+ name = 'getftype',
+ params = { { 'fname', 'string' } },
+ returns = "'file'|'dir'|'link'|'bdev'|'cdev'|'socket'|'fifo'|'other'",
+ signature = 'getftype({fname})',
+ },
+ getjumplist = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Returns the |jumplist| for the specified window.
+
+ Without arguments use the current window.
+ With {winnr} only use this window in the current tab page.
+ {winnr} can also be a |window-ID|.
+ With {winnr} and {tabnr} use the window in the specified tab
+ page. If {winnr} or {tabnr} is invalid, an empty list is
+ returned.
+
+ The returned list contains two entries: a list with the jump
+ locations and the last used jump position number in the list.
+ Each entry in the jump location list is a dictionary with
+ the following entries:
+ bufnr buffer number
+ col column number
+ coladd column offset for 'virtualedit'
+ filename filename if available
+ lnum line number
+
+ ]=],
+ name = 'getjumplist',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ signature = 'getjumplist([{winnr} [, {tabnr}]])',
+ returns = 'vim.fn.getjumplist.ret',
+ },
+ getline = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Without {end} the result is a String, which is line {lnum}
+ from the current buffer. Example: >vim
+ getline(1)
+ <When {lnum} is a String that doesn't start with a
+ digit, |line()| is called to translate the String into a Number.
+ To get the line under the cursor: >vim
+ getline(".")
+ <When {lnum} is a number smaller than 1 or bigger than the
+ number of lines in the buffer, an empty string is returned.
+
+ When {end} is given the result is a |List| where each item is
+ a line from the current buffer in the range {lnum} to {end},
+ including line {end}.
+ {end} is used in the same way as {lnum}.
+ Non-existing lines are silently omitted.
+ When {end} is before {lnum} an empty |List| is returned.
+ Example: >vim
+ let start = line('.')
+ let end = search("^$") - 1
+ let lines = getline(start, end)
+
+ <To get lines from another buffer see |getbufline()| and
+ |getbufoneline()|
+ ]=],
+ name = 'getline',
+ params = { { 'lnum', 'integer' }, { 'end', 'any' } },
+ returns = 'string|string[]',
+ signature = 'getline({lnum} [, {end}])',
+ },
+ getloclist = {
+ args = { 1, 2 },
+ desc = [=[
+ 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.
+
+ For a location list window, the displayed location list is
+ returned. For an invalid window number {nr}, an empty list is
+ returned. Otherwise, same as |getqflist()|.
+
+ If the optional {what} dictionary argument is supplied, then
+ returns the items listed in {what} as a dictionary. Refer to
+ |getqflist()| for the supported items in {what}.
+
+ In addition to the items supported by |getqflist()| in {what},
+ the following item is supported by |getloclist()|:
+
+ filewinid id of the window used to display files
+ from the location list. This field is
+ applicable only when called from a
+ location list window. See
+ |location-list-file-window| for more
+ details.
+
+ Returns a |Dictionary| with default values if there is no
+ location list for the window {nr}.
+ Returns an empty Dictionary if window {nr} does not exist.
+
+ Examples (See also |getqflist-examples|): >vim
+ echo getloclist(3, {'all': 0})
+ echo getloclist(5, {'filewinid': 0})
+ <
+ ]=],
+ name = 'getloclist',
+ params = { { 'nr', 'integer' }, { 'what', 'any' } },
+ signature = 'getloclist({nr} [, {what}])',
+ },
+ getmarklist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Without the {buf} argument returns a |List| with information
+ about all the global marks. |mark|
+
+ If the optional {buf} argument is specified, returns the
+ local marks defined in buffer {buf}. For the use of {buf},
+ see |bufname()|. If {buf} is invalid, an empty list is
+ returned.
+
+ Each item in the returned List is a |Dict| with the following:
+ mark name of the mark prefixed by "'"
+ pos a |List| with the position of the mark:
+ [bufnum, lnum, col, off]
+ Refer to |getpos()| for more information.
+ file file name
+
+ Refer to |getpos()| for getting information about a specific
+ mark.
+
+ ]=],
+ name = 'getmarklist',
+ params = { { 'buf', 'any' } },
+ signature = 'getmarklist([{buf}])',
+ },
+ getmatches = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with all matches previously defined for the
+ current window by |matchadd()| and the |:match| commands.
+ |getmatches()| is useful in combination with |setmatches()|,
+ as |setmatches()| can restore a list of matches saved by
+ |getmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window. If {win} is invalid,
+ an empty list is returned.
+ Example: >vim
+ echo getmatches()
+ < >
+ [{"group": "MyGroup1", "pattern": "TODO",
+ "priority": 10, "id": 1}, {"group": "MyGroup2",
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+ < >vim
+ let m = getmatches()
+ call clearmatches()
+ echo getmatches()
+ < >
+ []
+ < >vim
+ call setmatches(m)
+ echo getmatches()
+ < >
+ [{"group": "MyGroup1", "pattern": "TODO",
+ "priority": 10, "id": 1}, {"group": "MyGroup2",
+ "pattern": "FIXME", "priority": 10, "id": 2}]
+ < >vim
+ unlet m
+ <
+ ]=],
+ name = 'getmatches',
+ params = { { 'win', 'any' } },
+ signature = 'getmatches([{win}])',
+ },
+ getmousepos = {
+ desc = [=[
+ Returns a |Dictionary| with the last known position of the
+ mouse. This can be used in a mapping for a mouse click. The
+ items are:
+ screenrow screen row
+ screencol screen column
+ winid Window ID of the click
+ winrow row inside "winid"
+ wincol column inside "winid"
+ line text line inside "winid"
+ column text column inside "winid"
+ coladd offset (in screen columns) from the
+ start of the clicked char
+ All numbers are 1-based.
+
+ If not over a window, e.g. when in the command line, then only
+ "screenrow" and "screencol" are valid, the others are zero.
+
+ When on the status line below a window or the vertical
+ separator right of a window, the "line" and "column" values
+ are zero.
+
+ When the position is after the text then "column" is the
+ length of the text in bytes plus one.
+
+ If the mouse is over a focusable floating window then that
+ window is used.
+
+ When using |getchar()| the Vim variables |v:mouse_lnum|,
+ |v:mouse_col| and |v:mouse_winid| also provide these values.
+ ]=],
+ name = 'getmousepos',
+ params = {},
+ signature = 'getmousepos()',
+ returns = 'vim.fn.getmousepos.ret',
+ },
+ getpid = {
+ desc = [=[
+ Return a Number which is the process ID of the Vim process.
+ This is a unique number, until Vim exits.
+ ]=],
+ fast = true,
+ name = 'getpid',
+ params = {},
+ returns = 'integer',
+ signature = 'getpid()',
+ },
+ getpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the position for String {expr}. For possible values of
+ {expr} see |line()|. For getting the cursor position see
+ |getcurpos()|.
+ The result is a |List| with four numbers:
+ [bufnum, lnum, col, off]
+ "bufnum" is zero, unless a mark like '0 or 'A is used, then it
+ is the buffer number of the mark.
+ "lnum" and "col" are the position in the buffer. The first
+ column is 1.
+ The "off" number is zero, unless 'virtualedit' is used. Then
+ it is the offset in screen columns from the start of the
+ character. E.g., a position within a <Tab> or after the last
+ character.
+ Note that for '< and '> Visual mode matters: when it is "V"
+ (visual line mode) the column of '< is zero and the column of
+ '> is a large number equal to |v:maxcol|.
+ The column number in the returned List is the byte position
+ within the line. To get the character position in the line,
+ use |getcharpos()|.
+ A very large column number equal to |v:maxcol| can be returned,
+ in which case it means "after the end of the line".
+ If {expr} is invalid, returns a list with all zeros.
+ This can be used to save and restore the position of a mark: >vim
+ let save_a_mark = getpos("'a")
+ " ...
+ call setpos("'a", save_a_mark)
+ <Also see |getcharpos()|, |getcurpos()| and |setpos()|.
+
+ ]=],
+ name = 'getpos',
+ params = { { 'expr', 'string' } },
+ returns = 'integer[]',
+ signature = 'getpos({expr})',
+ },
+ getqflist = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with all the current quickfix errors. Each
+ list item is a dictionary with these entries:
+ bufnr number of buffer that has the file name, use
+ bufname() to get the name
+ module module name
+ lnum line number in the buffer (first line is 1)
+ end_lnum
+ end of line number if the item is multiline
+ col column number (first column is 1)
+ end_col end of column number if the item has range
+ vcol |TRUE|: "col" is visual column
+ |FALSE|: "col" is byte index
+ nr error number
+ pattern search pattern used to locate the error
+ text description of the error
+ type type of the error, 'E', '1', etc.
+ valid |TRUE|: recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
+
+ When there is no error list or it's empty, an empty list is
+ returned. Quickfix list entries with a non-existing buffer
+ number are returned with "bufnr" set to zero (Note: some
+ functions accept buffer number zero for the alternate buffer,
+ you may need to explicitly check for zero).
+
+ Useful application: Find pattern matches in multiple files and
+ do something with them: >vim
+ vimgrep /theword/jg *.c
+ for d in getqflist()
+ echo bufname(d.bufnr) ':' d.lnum '=' d.text
+ endfor
+ <
+ If the optional {what} dictionary argument is supplied, then
+ returns only the items listed in {what} as a dictionary. The
+ following string items are supported in {what}:
+ changedtick get the total number of changes made
+ to the list |quickfix-changedtick|
+ context get the |quickfix-context|
+ efm errorformat to use when parsing "lines". If
+ not present, then the 'errorformat' option
+ value is used.
+ id get information for the quickfix list with
+ |quickfix-ID|; zero means the id for the
+ current list or the list specified by "nr"
+ idx get information for the quickfix entry at this
+ index in the list specified by "id" or "nr".
+ If set to zero, then uses the current entry.
+ See |quickfix-index|
+ items quickfix list entries
+ lines parse a list of lines using 'efm' and return
+ the resulting entries. Only a |List| type is
+ accepted. The current quickfix list is not
+ modified. See |quickfix-parse|.
+ nr get information for this quickfix list; zero
+ means the current quickfix list and "$" means
+ the last quickfix list
+ qfbufnr number of the buffer displayed in the quickfix
+ window. Returns 0 if the quickfix buffer is
+ not present. See |quickfix-buffer|.
+ size number of entries in the quickfix list
+ title get the list title |quickfix-title|
+ winid get the quickfix |window-ID|
+ all all of the above quickfix properties
+ Non-string items in {what} are ignored. To get the value of a
+ particular item, set it to zero.
+ If "nr" is not present then the current quickfix list is used.
+ If both "nr" and a non-zero "id" are specified, then the list
+ specified by "id" is used.
+ To get the number of lists in the quickfix stack, set "nr" to
+ "$" in {what}. The "nr" value in the returned dictionary
+ contains the quickfix stack size.
+ When "lines" is specified, all the other items except "efm"
+ are ignored. The returned dictionary contains the entry
+ "items" with the list of entries.
+
+ The returned dictionary contains the following entries:
+ changedtick total number of changes made to the
+ list |quickfix-changedtick|
+ context quickfix list context. See |quickfix-context|
+ If not present, set to "".
+ id quickfix list ID |quickfix-ID|. If not
+ present, set to 0.
+ idx index of the quickfix entry in the list. If not
+ present, set to 0.
+ items quickfix list entries. If not present, set to
+ an empty list.
+ nr quickfix list number. If not present, set to 0
+ qfbufnr number of the buffer displayed in the quickfix
+ window. If not present, set to 0.
+ size number of entries in the quickfix list. If not
+ present, set to 0.
+ title quickfix list title text. If not present, set
+ to "".
+ winid quickfix |window-ID|. If not present, set to 0
+
+ Examples (See also |getqflist-examples|): >vim
+ echo getqflist({'all': 1})
+ echo getqflist({'nr': 2, 'title': 1})
+ echo getqflist({'lines' : ["F1:10:L10"]})
+ <
+ ]=],
+ name = 'getqflist',
+ params = { { 'what', 'any' } },
+ signature = 'getqflist([{what}])',
+ },
+ getreg = {
+ args = { 0, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is the contents of register
+ {regname}. Example: >vim
+ let cliptext = getreg('*')
+ <When register {regname} was not set the result is an empty
+ string.
+ The {regname} argument must be a string.
+
+ getreg('=') returns the last evaluated value of the expression
+ register. (For use in maps.)
+ getreg('=', 1) returns the expression itself, so that it can
+ be restored with |setreg()|. For other registers the extra
+ argument is ignored, thus you can always give it.
+
+ If {list} is present and |TRUE|, the result type is changed
+ to |List|. Each list item is one text line. Use it if you care
+ about zero bytes possibly present inside register: without
+ third argument both NLs and zero bytes are represented as NLs
+ (see |NL-used-for-Nul|).
+ When the register was not set an empty list is returned.
+
+ If {regname} is not specified, |v:register| is used.
+
+ ]=],
+ name = 'getreg',
+ params = { { 'regname', 'string' }, { 'list', 'any' } },
+ returns = 'string|string[]',
+ signature = 'getreg([{regname} [, 1 [, {list}]]])',
+ },
+ getreginfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns detailed information about register {regname} as a
+ Dictionary with the following entries:
+ regcontents List of lines contained in register
+ {regname}, like
+ getreg({regname}, 1, 1).
+ regtype the type of register {regname}, as in
+ |getregtype()|.
+ isunnamed Boolean flag, v:true if this register
+ is currently pointed to by the unnamed
+ register.
+ points_to for the unnamed register, gives the
+ single letter name of the register
+ currently pointed to (see |quotequote|).
+ For example, after deleting a line
+ with `dd`, this field will be "1",
+ which is the register that got the
+ deleted text.
+
+ The {regname} argument is a string. If {regname} is invalid
+ or not set, an empty Dictionary will be returned.
+ If {regname} is not specified, |v:register| is used.
+ The returned Dictionary can be passed to |setreg()|.
+
+ ]=],
+ name = 'getreginfo',
+ params = { { 'regname', 'string' } },
+ returns = 'table',
+ signature = 'getreginfo([{regname}])',
+ },
+ getregtype = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is type of register {regname}.
+ The value will be one of:
+ "v" for |charwise| text
+ "V" for |linewise| text
+ "<CTRL-V>{width}" for |blockwise-visual| text
+ "" for an empty or unknown register
+ <CTRL-V> is one character with value 0x16.
+ The {regname} argument is a string. If {regname} is not
+ specified, |v:register| is used.
+
+ ]=],
+ name = 'getregtype',
+ params = { { 'regname', 'string' } },
+ returns = 'string',
+ signature = 'getregtype([{regname}])',
+ },
+ getscriptinfo = {
+ args = { 0, 1 },
+ desc = [=[
+ Returns a |List| with information about all the sourced Vim
+ scripts in the order they were sourced, like what
+ `:scriptnames` shows.
+
+ The optional Dict argument {opts} supports the following
+ optional items:
+ name Script name match pattern. If specified,
+ and "sid" is not specified, information about
+ scripts with a name that match the pattern
+ "name" are returned.
+ sid Script ID |<SID>|. If specified, only
+ information about the script with ID "sid" is
+ returned and "name" is ignored.
+
+ Each item in the returned List is a |Dict| with the following
+ items:
+ autoload Always set to FALSE.
+ functions List of script-local function names defined in
+ the script. Present only when a particular
+ script is specified using the "sid" item in
+ {opts}.
+ name Vim script file name.
+ sid Script ID |<SID>|.
+ variables A dictionary with the script-local variables.
+ Present only when a particular script is
+ specified using the "sid" item in {opts}.
+ Note that this is a copy, the value of
+ script-local variables cannot be changed using
+ this dictionary.
+ version Vim script version, always 1
+
+ Examples: >vim
+ echo getscriptinfo({'name': 'myscript'})
+ echo getscriptinfo({'sid': 15}).variables
+ <
+ ]=],
+ name = 'getscriptinfo',
+ params = { { 'opts', 'table' } },
+ signature = 'getscriptinfo([{opts}])',
+ },
+ gettabinfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ If {tabnr} is not specified, then information about all the
+ tab pages is returned as a |List|. Each List item is a
+ |Dictionary|. Otherwise, {tabnr} specifies the tab page
+ number and information about that one is returned. If the tab
+ page does not exist an empty List is returned.
+
+ Each List item is a |Dictionary| with the following entries:
+ tabnr tab page number.
+ variables a reference to the dictionary with
+ tabpage-local variables
+ windows List of |window-ID|s in the tab page.
+
+ ]=],
+ name = 'gettabinfo',
+ params = { { 'tabnr', 'integer' } },
+ signature = 'gettabinfo([{tabnr}])',
+ },
+ gettabvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Get the value of a tab-local variable {varname} in tab page
+ {tabnr}. |t:var|
+ Tabs are numbered starting with one.
+ The {varname} argument is a string. When {varname} is empty a
+ dictionary with all tab-local variables is returned.
+ Note that the name without "t:" must be used.
+ When the tab or variable doesn't exist {def} or an empty
+ string is returned, there is no error message.
+
+ ]=],
+ name = 'gettabvar',
+ params = { { 'tabnr', 'integer' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'gettabvar({tabnr}, {varname} [, {def}])',
+ },
+ gettabwinvar = {
+ args = { 3, 4 },
+ base = 1,
+ desc = [=[
+ Get the value of window-local variable {varname} in window
+ {winnr} in tab page {tabnr}.
+ The {varname} argument is a string. When {varname} is empty a
+ dictionary with all window-local variables is returned.
+ When {varname} is equal to "&" get the values of all
+ window-local options in a |Dictionary|.
+ Otherwise, when {varname} starts with "&" get the value of a
+ window-local option.
+ Note that {varname} must be the name without "w:".
+ Tabs are numbered starting with one. For the current tabpage
+ use |getwinvar()|.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is zero the current window is used.
+ This also works for a global option, buffer-local option and
+ window-local option, but it doesn't work for a global variable
+ or buffer-local variable.
+ When the tab, window or variable doesn't exist {def} or an
+ empty string is returned, there is no error message.
+ Examples: >vim
+ let list_is_on = gettabwinvar(1, 2, '&list')
+ echo "myvar = " .. gettabwinvar(3, 1, 'myvar')
+ <
+ To obtain all window-local variables use: >vim
+ gettabwinvar({tabnr}, {winnr}, '&')
+ <
+ ]=],
+ name = 'gettabwinvar',
+ params = {
+ { 'tabnr', 'integer' },
+ { 'winnr', 'integer' },
+ { 'varname', 'string' },
+ { 'def', 'any' },
+ },
+ signature = 'gettabwinvar({tabnr}, {winnr}, {varname} [, {def}])',
+ },
+ gettagstack = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a Dict, which is the tag stack of window {winnr}.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is not specified, the current window is used.
+ When window {winnr} doesn't exist, an empty Dict is returned.
+
+ The returned dictionary contains the following entries:
+ curidx Current index in the stack. When at
+ top of the stack, set to (length + 1).
+ Index of bottom of the stack is 1.
+ items List of items in the stack. Each item
+ is a dictionary containing the
+ entries described below.
+ length Number of entries in the stack.
+
+ Each item in the stack is a dictionary with the following
+ entries:
+ bufnr buffer number of the current jump
+ from cursor position before the tag jump.
+ See |getpos()| for the format of the
+ returned list.
+ matchnr current matching tag number. Used when
+ multiple matching tags are found for a
+ name.
+ tagname name of the tag
+
+ See |tagstack| for more information about the tag stack.
+
+ ]=],
+ name = 'gettagstack',
+ params = { { 'winnr', 'integer' } },
+ signature = 'gettagstack([{winnr}])',
+ },
+ gettext = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Translate String {text} if possible.
+ This is mainly for use in the distributed Vim scripts. When
+ generating message translations the {text} is extracted by
+ xgettext, the translator can add the translated message in the
+ .po file and Vim will lookup the translation when gettext() is
+ called.
+ For {text} double quoted strings are preferred, because
+ xgettext does not understand escaping in single quoted
+ strings.
+ ]=],
+ name = 'gettext',
+ params = { { 'text', 'any' } },
+ signature = 'gettext({text})',
+ },
+ getwininfo = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns information about windows as a |List| with Dictionaries.
+
+ If {winid} is given Information about the window with that ID
+ is returned, as a |List| with one item. If the window does not
+ exist the result is an empty list.
+
+ Without {winid} information about all the windows in all the
+ tab pages is returned.
+
+ Each List item is a |Dictionary| with the following entries:
+ botline last complete displayed buffer line
+ bufnr number of buffer in the window
+ height window height (excluding winbar)
+ loclist 1 if showing a location list
+ quickfix 1 if quickfix or location list window
+ terminal 1 if a terminal window
+ tabnr tab page number
+ topline first displayed buffer line
+ variables a reference to the dictionary with
+ window-local variables
+ width window width
+ winbar 1 if the window has a toolbar, 0
+ otherwise
+ wincol leftmost screen column of the window;
+ "col" from |win_screenpos()|
+ textoff number of columns occupied by any
+ 'foldcolumn', 'signcolumn' and line
+ number in front of the text
+ winid |window-ID|
+ winnr window number
+ winrow topmost screen line of the window;
+ "row" from |win_screenpos()|
+
+ ]=],
+ name = 'getwininfo',
+ params = { { 'winid', 'integer' } },
+ signature = 'getwininfo([{winid}])',
+ returns = 'vim.fn.getwininfo.ret.item[]'
+ },
+ getwinpos = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a |List| with two numbers, the result of
+ |getwinposx()| and |getwinposy()| combined:
+ [x-pos, y-pos]
+ {timeout} can be used to specify how long to wait in msec for
+ a response from the terminal. When omitted 100 msec is used.
+
+ Use a longer time for a remote terminal.
+ When using a value less than 10 and no response is received
+ within that time, a previously reported position is returned,
+ if available. This can be used to poll for the position and
+ do some work in the meantime: >vim
+ while 1
+ let res = getwinpos(1)
+ if res[0] >= 0
+ break
+ endif
+ " Do some work here
+ endwhile
+ <
+ ]=],
+ name = 'getwinpos',
+ params = { { 'timeout', 'integer' } },
+ signature = 'getwinpos([{timeout}])',
+ },
+ getwinposx = {
+ desc = [=[
+ The result is a Number, which is the X coordinate in pixels of
+ the left hand side of the GUI Vim window. The result will be
+ -1 if the information is not available.
+ The value can be used with `:winpos`.
+ ]=],
+ name = 'getwinposx',
+ params = {},
+ returns = 'integer',
+ signature = 'getwinposx()',
+ },
+ getwinposy = {
+ desc = [=[
+ The result is a Number, which is the Y coordinate in pixels of
+ the top of the GUI Vim window. The result will be -1 if the
+ information is not available.
+ The value can be used with `:winpos`.
+ ]=],
+ name = 'getwinposy',
+ params = {},
+ returns = 'integer',
+ signature = 'getwinposy()',
+ },
+ getwinvar = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Like |gettabwinvar()| for the current tabpage.
+ Examples: >vim
+ let list_is_on = getwinvar(2, '&list')
+ echo "myvar = " .. getwinvar(1, 'myvar')
+
+ ]=],
+ name = 'getwinvar',
+ params = { { 'winnr', 'integer' }, { 'varname', 'string' }, { 'def', 'any' } },
+ signature = 'getwinvar({winnr}, {varname} [, {def}])',
+ },
+ glob = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ Expand the file wildcards in {expr}. See |wildcards| for the
+ use of special characters.
+
+ Unless the optional {nosuf} argument is given and is |TRUE|,
+ the 'suffixes' and 'wildignore' options apply: Names matching
+ one of the patterns in 'wildignore' will be skipped and
+ 'suffixes' affect the ordering of matches.
+ 'wildignorecase' always applies.
+
+ When {list} is present and it is |TRUE| the result is a |List|
+ with all matching files. The advantage of using a List is,
+ you also get filenames containing newlines correctly.
+ Otherwise the result is a String and when there are several
+ matches, they are separated by <NL> characters.
+
+ If the expansion fails, the result is an empty String or List.
+
+ You can also use |readdir()| if you need to do complicated
+ things, such as limiting the number of matches.
+
+ A name for a non-existing file is not included. A symbolic
+ link is only included if it points to an existing file.
+ However, when the {alllinks} argument is present and it is
+ |TRUE| then all symbolic links are included.
+
+ For most systems backticks can be used to get files names from
+ any external command. Example: >vim
+ let tagfiles = glob("`find . -name tags -print`")
+ let &tags = substitute(tagfiles, "\n", ",", "g")
+ <The result of the program inside the backticks should be one
+ item per line. Spaces inside an item are allowed.
+
+ See |expand()| for expanding special Vim variables. See
+ |system()| for getting the raw output of an external command.
+
+ ]=],
+ name = 'glob',
+ params = { { 'expr', 'any' }, { 'nosuf', 'boolean' }, { 'list', 'any' }, { 'alllinks', 'any' } },
+ signature = 'glob({expr} [, {nosuf} [, {list} [, {alllinks}]]])',
+ },
+ glob2regpat = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert a file pattern, as used by glob(), into a search
+ pattern. The result can be used to match with a string that
+ is a file name. E.g. >vim
+ if filename =~ glob2regpat('Make*.mak')
+ " ...
+ endif
+ <This is equivalent to: >vim
+ if filename =~ '^Make.*\.mak$'
+ " ...
+ endif
+ <When {string} is an empty string the result is "^$", match an
+ empty string.
+ Note that the result depends on the system. On MS-Windows
+ a backslash usually means a path separator.
+
+ ]=],
+ name = 'glob2regpat',
+ params = { { 'string', 'string' } },
+ signature = 'glob2regpat({string})',
+ },
+ globpath = {
+ args = { 2, 5 },
+ base = 2,
+ desc = [=[
+ Perform glob() for String {expr} on all directories in {path}
+ and concatenate the results. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim")
+ <
+ {path} is a comma-separated list of directory names. Each
+ directory name is prepended to {expr} and expanded like with
+ |glob()|. A path separator is inserted when needed.
+ To add a comma inside a directory name escape it with a
+ backslash. Note that on MS-Windows a directory may have a
+ trailing backslash, remove it if you put a comma after it.
+ If the expansion fails for one of the directories, there is no
+ error message.
+
+ Unless the optional {nosuf} argument is given and is |TRUE|,
+ the 'suffixes' and 'wildignore' options apply: Names matching
+ one of the patterns in 'wildignore' will be skipped and
+ 'suffixes' affect the ordering of matches.
+
+ When {list} is present and it is |TRUE| the result is a |List|
+ with all matching files. The advantage of using a List is, you
+ also get filenames containing newlines correctly. Otherwise
+ the result is a String and when there are several matches,
+ they are separated by <NL> characters. Example: >vim
+ echo globpath(&rtp, "syntax/c.vim", 0, 1)
+ <
+ {allinks} is used as with |glob()|.
+
+ The "**" item can be used to search in a directory tree.
+ For example, to find all "README.txt" files in the directories
+ in 'runtimepath' and below: >vim
+ echo globpath(&rtp, "**/README.txt")
+ <Upwards search and limiting the depth of "**" is not
+ supported, thus using 'path' will not always work properly.
+
+ ]=],
+ name = 'globpath',
+ params = {
+ { 'path', 'string' },
+ { 'expr', 'any' },
+ { 'nosuf', 'boolean' },
+ { 'list', 'any' },
+ { 'allinks', 'any' },
+ },
+ signature = 'globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])',
+ },
+ has = {
+ args = 1,
+ desc = [=[
+ Returns 1 if {feature} is supported, 0 otherwise. The
+ {feature} argument is a feature name like "nvim-0.2.1" or
+ "win32", see below. See also |exists()|.
+
+ To get the system name use |vim.uv|.os_uname() in Lua: >lua
+ print(vim.uv.os_uname().sysname)
+
+ <If the code has a syntax error then Vimscript may skip the
+ rest of the line. Put |:if| and |:endif| on separate lines to
+ avoid the syntax error: >vim
+ if has('feature')
+ let x = this_breaks_without_the_feature()
+ endif
+ <
+ Vim's compile-time feature-names (prefixed with "+") are not
+ recognized because Nvim is always compiled with all possible
+ features. |feature-compile|
+
+ Feature names can be:
+ 1. Nvim version. For example the "nvim-0.2.1" feature means
+ that Nvim is version 0.2.1 or later: >vim
+ if has("nvim-0.2.1")
+ " ...
+ endif
+
+ <2. Runtime condition or other pseudo-feature. For example the
+ "win32" feature checks if the current system is Windows: >vim
+ if has("win32")
+ " ...
+ endif
+ < *feature-list*
+ List of supported pseudo-feature names:
+ acl |ACL| support.
+ bsd BSD system (not macOS, use "mac" for that).
+ clipboard |clipboard| provider is available.
+ fname_case Case in file names matters (for Darwin and MS-Windows
+ this is not present).
+ gui_running Nvim has a GUI.
+ 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|
+ 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.
+
+ *has-patch*
+ 3. Vim patch. For example the "patch123" feature means that
+ Vim patch 123 at the current |v:version| was included: >vim
+ if v:version > 602 || v:version == 602 && has("patch148")
+ " ...
+ endif
+
+ <4. Vim version. For example the "patch-7.4.237" feature means
+ that Nvim is Vim-compatible to version 7.4.237 or later. >vim
+ if has("patch-7.4.237")
+ " ...
+ endif
+ <
+ ]=],
+ name = 'has',
+ params = { { 'feature', 'any' } },
+ returns = '0|1',
+ signature = 'has({feature})',
+ },
+ has_key = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if |Dictionary| {dict}
+ has an entry with key {key}. FALSE otherwise. The {key}
+ argument is a string.
+
+ ]=],
+ name = 'has_key',
+ params = { { 'dict', 'any' }, { 'key', 'any' } },
+ returns = '0|1',
+ signature = 'has_key({dict}, {key})',
+ },
+ haslocaldir = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ 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.
+
+ Tabs and windows are identified by their respective numbers,
+ 0 means current tab or window. Missing argument implies 0.
+ Thus the following are equivalent: >vim
+ echo haslocaldir()
+ echo haslocaldir(0)
+ echo haslocaldir(0, 0)
+ <With {winnr} use that window in the current tabpage.
+ With {winnr} and {tabnr} use the window in that tabpage.
+ {winnr} can be the window number or the |window-ID|.
+ If {winnr} is -1 it is ignored, only the tab is resolved.
+ Throw error if the arguments are invalid. |E5000| |E5001| |E5002|
+
+ ]=],
+ name = 'haslocaldir',
+ params = { { 'winnr', 'integer' }, { 'tabnr', 'integer' } },
+ returns = '0|1',
+ signature = 'haslocaldir([{winnr} [, {tabnr}]])',
+ },
+ hasmapto = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if there is a mapping
+ that contains {what} in somewhere in the rhs (what it is
+ mapped to) and this mapping exists in one of the modes
+ indicated by {mode}.
+ The arguments {what} and {mode} are strings.
+ When {abbr} is there and it is |TRUE| use abbreviations
+ instead of mappings. Don't forget to specify Insert and/or
+ Command-line mode.
+ Both the global mappings and the mappings local to the current
+ buffer are checked for a match.
+ If no matching mapping is found FALSE is returned.
+ The following characters are recognized in {mode}:
+ n Normal mode
+ v Visual and Select mode
+ x Visual mode
+ s Select mode
+ o Operator-pending mode
+ i Insert mode
+ l Language-Argument ("r", "f", "t", etc.)
+ c Command-line mode
+ When {mode} is omitted, "nvo" is used.
+
+ This function is useful to check if a mapping already exists
+ to a function in a Vim script. Example: >vim
+ if !hasmapto('\ABCdoit')
+ map <Leader>d \ABCdoit
+ endif
+ <This installs the mapping to "\ABCdoit" only if there isn't
+ already a mapping to "\ABCdoit".
+
+ ]=],
+ name = 'hasmapto',
+ params = { { 'what', 'any' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ returns = '0|1',
+ signature = 'hasmapto({what} [, {mode} [, {abbr}]])',
+ },
+ highlightID = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |hlID()|.
+ ]=],
+ func = 'f_hlID',
+ params = { { 'name', 'string' } },
+ signature = 'highlightID({name})',
+ },
+ highlight_exists = {
+ args = 1,
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |hlexists()|.
+ ]=],
+ func = 'f_hlexists',
+ params = { { 'name', 'string' } },
+ signature = 'highlight_exists({name})',
+ },
+ histadd = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Add the String {item} to the history {history} which can be
+ one of: *hist-names*
+ "cmd" or ":" command line history
+ "search" or "/" search pattern history
+ "expr" or "=" typed expression history
+ "input" or "@" input line history
+ "debug" or ">" debug command history
+ empty the current or last used history
+ The {history} string does not need to be the whole name, one
+ character is sufficient.
+ If {item} does already exist in the history, it will be
+ shifted to become the newest entry.
+ The result is a Number: TRUE if the operation was successful,
+ otherwise FALSE is returned.
+
+ Example: >vim
+ call histadd("input", strftime("%Y %b %d"))
+ let date=input("Enter date: ")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'histadd',
+ params = { { 'history', 'any' }, { 'item', 'any' } },
+ returns = '0|1',
+ signature = 'histadd({history}, {item})',
+ },
+ histdel = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Clear {history}, i.e. delete all its entries. See |hist-names|
+ for the possible values of {history}.
+
+ If the parameter {item} evaluates to a String, it is used as a
+ regular expression. All entries matching that expression will
+ be removed from the history (if there are any).
+ Upper/lowercase must match, unless "\c" is used |/\c|.
+ If {item} evaluates to a Number, it will be interpreted as
+ an index, see |:history-indexing|. The respective entry will
+ be removed if it exists.
+
+ The result is TRUE for a successful operation, otherwise FALSE
+ is returned.
+
+ Examples:
+ Clear expression register history: >vim
+ call histdel("expr")
+ <
+ Remove all entries starting with "*" from the search history: >vim
+ call histdel("/", '^\*')
+ <
+ The following three are equivalent: >vim
+ call histdel("search", histnr("search"))
+ call histdel("search", -1)
+ call histdel("search", '^' .. histget("search", -1) .. '$')
+ <
+ To delete the last search pattern and use the last-but-one for
+ the "n" command and 'hlsearch': >vim
+ call histdel("search", -1)
+ let @/ = histget("search", -1)
+ <
+ ]=],
+ name = 'histdel',
+ params = { { 'history', 'any' }, { 'item', 'any' } },
+ returns = '0|1',
+ signature = 'histdel({history} [, {item}])',
+ },
+ histget = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a String, the entry with Number {index} from
+ {history}. See |hist-names| for the possible values of
+ {history}, and |:history-indexing| for {index}. If there is
+ no such entry, an empty String is returned. When {index} is
+ omitted, the most recent item from the history is used.
+
+ Examples:
+ Redo the second last search from history. >vim
+ execute '/' .. histget("search", -2)
+
+ <Define an Ex command ":H {num}" that supports re-execution of
+ the {num}th entry from the output of |:history|. >vim
+ command -nargs=1 H execute histget("cmd", 0+<args>)
+ <
+ ]=],
+ name = 'histget',
+ params = { { 'history', 'any' }, { 'index', 'any' } },
+ returns = 'string',
+ signature = 'histget({history} [, {index}])',
+ },
+ histnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is the Number of the current entry in {history}.
+ See |hist-names| for the possible values of {history}.
+ If an error occurred, -1 is returned.
+
+ Example: >vim
+ let inp_index = histnr("expr")
+
+ ]=],
+ name = 'histnr',
+ params = { { 'history', 'any' } },
+ returns = 'integer',
+ signature = 'histnr({history})',
+ },
+ hlID = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the ID of the highlight group
+ with name {name}. When the highlight group doesn't exist,
+ zero is returned.
+ This can be used to retrieve information about the highlight
+ group. For example, to get the background color of the
+ "Comment" group: >vim
+ echo synIDattr(synIDtrans(hlID("Comment")), "bg")
+ <
+ ]=],
+ name = 'hlID',
+ params = { { 'name', 'string' } },
+ returns = 'integer',
+ signature = 'hlID({name})',
+ },
+ hlexists = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is TRUE if a highlight group
+ called {name} exists. This is when the group has been
+ defined in some way. Not necessarily when highlighting has
+ been defined for it, it may also have been used for a syntax
+ item.
+
+ ]=],
+ name = 'hlexists',
+ params = { { 'name', 'string' } },
+ returns = '0|1',
+ signature = 'hlexists({name})',
+ },
+ hostname = {
+ desc = [=[
+ The result is a String, which is the name of the machine on
+ which Vim is currently running. Machine names greater than
+ 256 characters long are truncated.
+ ]=],
+ fast = true,
+ name = 'hostname',
+ params = {},
+ returns = 'string',
+ signature = 'hostname()',
+ },
+ iconv = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a String, which is the text {string} converted
+ from encoding {from} to encoding {to}.
+ When the conversion completely fails an empty string is
+ returned. When some characters could not be converted they
+ are replaced with "?".
+ The encoding names are whatever the iconv() library function
+ can accept, see ":!man 3 iconv".
+ Note that Vim uses UTF-8 for all Unicode encodings, conversion
+ from/to UCS-2 is automatically changed to use UTF-8. You
+ cannot use UCS-2 in a string anyway, because of the NUL bytes.
+
+ ]=],
+ fast = true,
+ name = 'iconv',
+ params = { { 'string', 'string' }, { 'from', 'any' }, { 'to', 'any' } },
+ signature = 'iconv({string}, {from}, {to})',
+ },
+ id = {
+ args = 1,
+ desc = [=[
+ Returns a |String| which is a unique identifier of the
+ container type (|List|, |Dict|, |Blob| and |Partial|). It is
+ guaranteed that for the mentioned types `id(v1) ==# id(v2)`
+ returns true iff `type(v1) == type(v2) && v1 is v2`.
+ Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and
+ `v:_null_blob` have the same `id()` with different types
+ because they are internally represented as NULL pointers.
+ `id()` returns a hexadecimal representanion of the pointers to
+ the containers (i.e. like `0x994a40`), same as `printf("%p",
+ {expr})`, but it is advised against counting on the exact
+ format of the return value.
+
+ It is not guaranteed that `id(no_longer_existing_container)`
+ will not be equal to some other `id()`: new containers may
+ reuse identifiers of the garbage-collected ones.
+ ]=],
+ name = 'id',
+ params = { { 'expr', 'any' } },
+ signature = 'id({expr})',
+ },
+ indent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is indent of line {lnum} in the
+ current buffer. The indent is counted in spaces, the value
+ of 'tabstop' is relevant. {lnum} is used just like in
+ |getline()|.
+ When {lnum} is invalid -1 is returned.
+
+ ]=],
+ name = 'indent',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'indent({lnum})',
+ },
+ index = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Find {expr} in {object} and return its index. See
+ |indexof()| for using a lambda to select the item.
+
+ If {object} is a |List| return the lowest index where the item
+ has a value equal to {expr}. There is no automatic
+ conversion, so the String "4" is different from the Number 4.
+ And the Number 4 is different from the Float 4.0. The value
+ of 'ignorecase' is not used here, case matters as indicated by
+ the {ic} argument.
+
+ If {object} is a |Blob| return the lowest index where the byte
+ value is equal to {expr}.
+
+ If {start} is given then start looking at the item with index
+ {start} (may be negative for an item relative to the end).
+
+ When {ic} is given and it is |TRUE|, ignore case. Otherwise
+ case must match.
+
+ -1 is returned when {expr} is not found in {object}.
+ Example: >vim
+ let idx = index(words, "the")
+ if index(numbers, 123) >= 0
+ " ...
+ endif
+
+ ]=],
+ name = 'index',
+ params = { { 'object', 'any' }, { 'expr', 'any' }, { 'start', 'any' }, { 'ic', 'any' } },
+ signature = 'index({object}, {expr} [, {start} [, {ic}]])',
+ },
+ indexof = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Returns the index of an item in {object} where {expr} is
+ v:true. {object} must be a |List| or a |Blob|.
+
+ If {object} is a |List|, evaluate {expr} for each item in the
+ List until the expression is v:true and return the index of
+ this item.
+
+ If {object} is a |Blob| evaluate {expr} for each byte in the
+ Blob until the expression is v:true and return the index of
+ this byte.
+
+ {expr} must be a |string| or |Funcref|.
+
+ If {expr} is a |string|: If {object} is a |List|, inside
+ {expr} |v:key| has the index of the current List item and
+ |v:val| has the value of the item. If {object} is a |Blob|,
+ inside {expr} |v:key| has the index of the current byte and
+ |v:val| has the byte value.
+
+ If {expr} is a |Funcref| it must take two arguments:
+ 1. the key or the index of the current item.
+ 2. the value of the current item.
+ The function must return |TRUE| if the item is found and the
+ search should stop.
+
+ The optional argument {opts} is a Dict and supports the
+ following items:
+ startidx start evaluating {expr} at the item with this
+ index; may be negative for an item relative to
+ the end
+ Returns -1 when {expr} evaluates to v:false for all the items.
+ Example: >vim
+ let l = [#{n: 10}, #{n: 20}, #{n: 30}]
+ echo indexof(l, "v:val.n == 20")
+ echo indexof(l, {i, v -> v.n == 30})
+ echo indexof(l, "v:val.n == 20", #{startidx: 1})
+
+ ]=],
+ name = 'indexof',
+ params = { { 'object', 'any' }, { 'expr', 'any' }, { 'opts', 'table' } },
+ signature = 'indexof({object}, {expr} [, {opts}])',
+ },
+ input = {
+ args = { 1, 3 },
+ base = 1,
+ desc = '',
+ name = 'input',
+ params = { { 'prompt', 'any' }, { 'text', 'any' }, { 'completion', 'any' } },
+ signature = 'input({prompt} [, {text} [, {completion}]])',
+ },
+ input__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is whatever the user typed on
+ the command-line. The {prompt} argument is either a prompt
+ string, or a blank string (for no prompt). A '\n' can be used
+ in the prompt to start a new line.
+
+ In the second form it accepts a single dictionary with the
+ following keys, any of which may be omitted:
+
+ Key Default Description ~
+ prompt "" Same as {prompt} in the first form.
+ default "" Same as {text} in the first form.
+ completion nothing Same as {completion} in the first form.
+ cancelreturn "" The value returned when the dialog is
+ cancelled.
+ highlight nothing Highlight handler: |Funcref|.
+
+ The highlighting set with |:echohl| is used for the prompt.
+ The input is entered just like a command-line, with the same
+ editing commands and mappings. There is a separate history
+ for lines typed for input().
+ Example: >vim
+ if input("Coffee or beer? ") == "beer"
+ echo "Cheers!"
+ endif
+ <
+ If the optional {text} argument is present and not empty, this
+ is used for the default reply, as if the user typed this.
+ Example: >vim
+ let color = input("Color? ", "white")
+
+ <The optional {completion} argument specifies the type of
+ completion supported for the input. Without it completion is
+ not performed. The supported completion types are the same as
+ that can be supplied to a user-defined command using the
+ "-complete=" argument. Refer to |:command-completion| for
+ more information. Example: >vim
+ let fname = input("File: ", "", "file")
+
+ < *input()-highlight* *E5400* *E5402*
+ The optional `highlight` key allows specifying function which
+ will be used for highlighting user input. This function
+ receives user input as its only argument and must return
+ a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group]
+ where
+ hl_start_col is the first highlighted column,
+ hl_end_col is the last highlighted column (+ 1!),
+ hl_group is |:hi| group used for highlighting.
+ *E5403* *E5404* *E5405* *E5406*
+ Both hl_start_col and hl_end_col + 1 must point to the start
+ of the multibyte character (highlighting must not break
+ multibyte characters), hl_end_col + 1 may be equal to the
+ input length. Start column must be in range [0, len(input)),
+ end column must be in range (hl_start_col, len(input)],
+ sections must be ordered so that next hl_start_col is greater
+ then or equal to previous hl_end_col.
+
+ Example (try some input with parentheses): >vim
+ highlight RBP1 guibg=Red ctermbg=red
+ highlight RBP2 guibg=Yellow ctermbg=yellow
+ highlight RBP3 guibg=Green ctermbg=green
+ highlight RBP4 guibg=Blue ctermbg=blue
+ let g:rainbow_levels = 4
+ function! RainbowParens(cmdline)
+ let ret = []
+ let i = 0
+ let lvl = 0
+ while i < len(a:cmdline)
+ if a:cmdline[i] is# '('
+ call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+ let lvl += 1
+ elseif a:cmdline[i] is# ')'
+ let lvl -= 1
+ call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)])
+ endif
+ let i += 1
+ endwhile
+ return ret
+ endfunction
+ call input({'prompt':'>','highlight':'RainbowParens'})
+ <
+ Highlight function is called at least once for each new
+ displayed input string, before command-line is redrawn. It is
+ expected that function is pure for the duration of one input()
+ call, i.e. it produces the same output for the same input, so
+ output may be memoized. Function is run like under |:silent|
+ modifier. If the function causes any errors, it will be
+ skipped for the duration of the current input() call.
+
+ Highlighting is disabled if command-line contains arabic
+ characters.
+
+ NOTE: This function must not be used in a startup file, for
+ the versions that only run in GUI mode (e.g., the Win32 GUI).
+ Note: When input() is called from within a mapping it will
+ consume remaining characters from that mapping, because a
+ mapping is handled like the characters were typed.
+ Use |inputsave()| before input() and |inputrestore()|
+ after input() to avoid that. Another solution is to avoid
+ that further characters follow in the mapping, e.g., by using
+ |:execute| or |:normal|.
+
+ Example with a mapping: >vim
+ nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR>
+ function GetFoo()
+ call inputsave()
+ let g:Foo = input("enter search pattern: ")
+ call inputrestore()
+ endfunction
+
+ ]=],
+ name = 'input',
+ params = { { 'opts', 'table' } },
+ signature = 'input({opts})',
+ },
+ inputdialog = {
+ args = { 1, 3 },
+ base = 1,
+ deprecated = true,
+ desc = [=[
+ Use |input()| instead.
+ ]=],
+ params = VARARGS,
+ signature = 'input(...)',
+ },
+ inputlist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ {textlist} must be a |List| of strings. This |List| is
+ displayed, one string per line. The user will be prompted to
+ enter a number, which is returned.
+ The user can also select an item by clicking on it with the
+ mouse, if the mouse is enabled in the command line ('mouse' is
+ "a" or includes "c"). For the first string 0 is returned.
+ When clicking above the first item a negative number is
+ returned. When clicking on the prompt one more than the
+ length of {textlist} is returned.
+ Make sure {textlist} has less than 'lines' entries, otherwise
+ it won't work. It's a good idea to put the entry number at
+ the start of the string. And put a prompt in the first item.
+ Example: >vim
+ let color = inputlist(['Select color:', '1. red',
+ \ '2. green', '3. blue'])
+
+ ]=],
+ name = 'inputlist',
+ params = { { 'textlist', 'any' } },
+ signature = 'inputlist({textlist})',
+ },
+ inputrestore = {
+ desc = [=[
+ Restore typeahead that was saved with a previous |inputsave()|.
+ Should be called the same number of times inputsave() is
+ called. Calling it more often is harmless though.
+ Returns TRUE when there is nothing to restore, FALSE otherwise.
+ ]=],
+ name = 'inputrestore',
+ params = {},
+ signature = 'inputrestore()',
+ },
+ inputsave = {
+ desc = [=[
+ Preserve typeahead (also from mappings) and clear it, so that
+ a following prompt gets input from the user. Should be
+ followed by a matching inputrestore() after the prompt. Can
+ be used several times, in which case there must be just as
+ many inputrestore() calls.
+ Returns TRUE when out of memory, FALSE otherwise.
+ ]=],
+ name = 'inputsave',
+ params = {},
+ signature = 'inputsave()',
+ },
+ inputsecret = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ This function acts much like the |input()| function with but
+ two exceptions:
+ a) the user's response will be displayed as a sequence of
+ asterisks ("*") thereby keeping the entry secret, and
+ b) the user's response will not be recorded on the input
+ |history| stack.
+ The result is a String, which is whatever the user actually
+ typed on the command-line in response to the issued prompt.
+ NOTE: Command-line completion is not supported.
+
+ ]=],
+ name = 'inputsecret',
+ params = { { 'prompt', 'any' }, { 'text', 'any' } },
+ signature = 'inputsecret({prompt} [, {text}])',
+ },
+ insert = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When {object} is a |List| or a |Blob| insert {item} at the start
+ of it.
+
+ If {idx} is specified insert {item} before the item with index
+ {idx}. If {idx} is zero it goes before the first item, just
+ like omitting {idx}. A negative {idx} is also possible, see
+ |list-index|. -1 inserts just before the last item.
+
+ Returns the resulting |List| or |Blob|. Examples: >vim
+ let mylist = insert([2, 3, 5], 1)
+ call insert(mylist, 4, -1)
+ call insert(mylist, 6, len(mylist))
+ <The last example can be done simpler with |add()|.
+ Note that when {item} is a |List| it is inserted as a single
+ item. Use |extend()| to concatenate |Lists|.
+
+ ]=],
+ name = 'insert',
+ params = { { 'object', 'any' }, { 'item', 'any' }, { 'idx', 'integer' } },
+ signature = 'insert({object}, {item} [, {idx}])',
+ },
+ interrupt = {
+ args = 0,
+ desc = [=[
+ Interrupt script execution. It works more or less like the
+ user typing CTRL-C, most commands won't execute and control
+ returns to the user. This is useful to abort execution
+ from lower down, e.g. in an autocommand. Example: >vim
+ function s:check_typoname(file)
+ if fnamemodify(a:file, ':t') == '['
+ echomsg 'Maybe typo'
+ call interrupt()
+ endif
+ endfunction
+ au BufWritePre * call s:check_typoname(expand('<amatch>'))
+ <
+ ]=],
+ name = 'interrupt',
+ params = {},
+ signature = 'interrupt()',
+ },
+ invert = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Bitwise invert. The argument is converted to a number. A
+ List, Dict or Float argument causes an error. Example: >vim
+ let bits = invert(bits)
+ <
+ ]=],
+ name = 'invert',
+ params = { { 'expr', 'any' } },
+ signature = 'invert({expr})',
+ },
+ isdirectory = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when a directory
+ with the name {directory} exists. If {directory} doesn't
+ exist, or isn't a directory, the result is |FALSE|. {directory}
+ is any expression, which is used as a String.
+
+ ]=],
+ fast = true,
+ name = 'isdirectory',
+ params = { { 'directory', 'any' } },
+ returns = '0|1',
+ signature = 'isdirectory({directory})',
+ },
+ isinf = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return 1 if {expr} is a positive infinity, or -1 a negative
+ infinity, otherwise 0. >vim
+ echo isinf(1.0 / 0.0)
+ < 1 >vim
+ echo isinf(-1.0 / 0.0)
+ < -1
+
+ ]=],
+ name = 'isinf',
+ params = { { 'expr', 'any' } },
+ returns = '1|0|-1',
+ signature = 'isinf({expr})',
+ },
+ islocked = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when {expr} is the
+ name of a locked variable.
+ The string argument {expr} must be the name of a variable,
+ |List| item or |Dictionary| entry, not the variable itself!
+ Example: >vim
+ let alist = [0, ['a', 'b'], 2, 3]
+ lockvar 1 alist
+ echo islocked('alist') " 1
+ echo islocked('alist[1]') " 0
+
+ <When {expr} is a variable that does not exist you get an error
+ message. Use |exists()| to check for existence.
+
+ ]=],
+ name = 'islocked',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'islocked({expr})',
+ tags = { 'E786' },
+ },
+ isnan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return |TRUE| if {expr} is a float with value NaN. >vim
+ echo isnan(0.0 / 0.0)
+ < 1
+
+ ]=],
+ name = 'isnan',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'isnan({expr})',
+ },
+ items = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the key-value pairs of {dict}. Each
+ |List| item is a list with two items: the key of a {dict}
+ entry and the value of this entry. The |List| is in arbitrary
+ order. Also see |keys()| and |values()|.
+ Example: >vim
+ for [key, value] in items(mydict)
+ echo key .. ': ' .. value
+ endfor
+
+ ]=],
+ name = 'items',
+ params = { { 'dict', 'any' } },
+ signature = 'items({dict})',
+ },
+ jobclose = {
+ args = { 1, 2 },
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |chanclose()|
+ ]=],
+ func = 'f_chanclose',
+ params = VARARGS,
+ signature = 'jobclose({id} [, {stream}])',
+ },
+ jobpid = {
+ args = 1,
+ desc = [=[
+ Return the PID (process id) of |job-id| {job}.
+ ]=],
+ name = 'jobpid',
+ params = { { 'job', 'any' } },
+ returns = 'integer',
+ signature = 'jobpid({job})',
+ },
+ jobresize = {
+ args = 3,
+ desc = [=[
+ Resize the pseudo terminal window of |job-id| {job} to {width}
+ columns and {height} rows.
+ Fails if the job was not started with `"pty":v:true`.
+ ]=],
+ name = 'jobresize',
+ params = { { 'job', 'any' }, { 'width', 'integer' }, { 'height', 'integer' } },
+ signature = 'jobresize({job}, {width}, {height})',
+ },
+ jobsend = {
+ args = 2,
+ deprecated = true,
+ desc = [=[
+ Obsolete name for |chansend()|
+ ]=],
+ func = 'f_chansend',
+ params = VARARGS,
+ signature = 'jobsend({id}, {data})',
+ },
+ jobstart = {
+ args = { 1, 2 },
+ desc = [=[
+ Note: Prefer |vim.system()| in Lua (unless using the `pty` option).
+
+ 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: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+ <(See |shell-unquoting| for details.)
+
+ Example: >vim
+ call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}})
+ <
+ Returns |job-id| on success, 0 on invalid arguments (or job
+ table is full), -1 if {cmd}[0] or 'shell' is not executable.
+ The returned job-id is a valid |channel-id| representing the
+ job's stdio streams. Use |chansend()| (or |rpcnotify()| and
+ |rpcrequest()| if "rpc" was enabled) to send data to stdin and
+ |chanclose()| to close the streams without stopping the job.
+
+ See |job-control| and |RPC|.
+
+ NOTE: on Windows if {cmd} is a List:
+ - cmd[0] must be an executable (not a "built-in"). If it is
+ in $PATH it can be called by name, without an extension: >vim
+ call jobstart(['ping', 'neovim.io'])
+ < If it is a full or partial path, extension is required: >vim
+ call jobstart(['System32\ping.exe', 'neovim.io'])
+ < - {cmd} is collapsed to a string of quoted args as expected
+ by CommandLineToArgvW https://msdn.microsoft.com/bb776391
+ unless cmd[0] is some form of "cmd.exe".
+
+ *jobstart-env*
+ The job environment is initialized as follows:
+ $NVIM is set to |v:servername| of the parent Nvim
+ $NVIM_LISTEN_ADDRESS is unset
+ $NVIM_LOG_FILE is unset
+ $VIM is unset
+ $VIMRUNTIME is unset
+ You can set these with the `env` option.
+
+ *jobstart-options*
+ {opts} is a dictionary with these keys:
+ clear_env: (boolean) `env` defines the job environment
+ exactly, instead of merging current environment.
+ cwd: (string, default=|current-directory|) Working
+ directory of the job.
+ detach: (boolean) Detach the job process: it will not be
+ killed when Nvim exits. If the process exits
+ before Nvim, `on_exit` will be invoked.
+ env: (dict) Map of environment variable name:value
+ pairs extending (or replace with "clear_env")
+ the current environment. |jobstart-env|
+ height: (number) Height of the `pty` terminal.
+ |on_exit|: (function) Callback invoked when the job exits.
+ |on_stdout|: (function) Callback invoked when the job emits
+ stdout data.
+ |on_stderr|: (function) Callback invoked when the job emits
+ stderr data.
+ overlapped: (boolean) Sets FILE_FLAG_OVERLAPPED for the
+ stdio passed to the child process. Only on
+ MS-Windows; ignored on other platforms.
+ pty: (boolean) Connect the job to a new pseudo
+ terminal, and its streams to the master file
+ descriptor. `on_stdout` receives all output,
+ `on_stderr` is ignored. |terminal-start|
+ rpc: (boolean) Use |msgpack-rpc| to communicate with
+ the job over stdio. Then `on_stdout` is ignored,
+ but `on_stderr` can still be used.
+ stderr_buffered: (boolean) Collect data until EOF (stream closed)
+ before invoking `on_stderr`. |channel-buffered|
+ stdout_buffered: (boolean) Collect data until EOF (stream
+ closed) before invoking `on_stdout`. |channel-buffered|
+ stdin: (string) Either "pipe" (default) to connect the
+ job's stdin to a channel or "null" to disconnect
+ stdin.
+ width: (number) Width of the `pty` terminal.
+
+ {opts} is passed as |self| dictionary to the callback; the
+ caller may set other keys to pass application-specific data.
+
+ Returns:
+ - |channel-id| on success
+ - 0 on invalid arguments
+ - -1 if {cmd}[0] is not executable.
+ See also |job-control|, |channel|, |msgpack-rpc|.
+ ]=],
+ name = 'jobstart',
+ params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ signature = 'jobstart({cmd} [, {opts}])',
+ },
+ jobstop = {
+ args = 1,
+ desc = [=[
+ Stop |job-id| {id} by sending SIGTERM to the job process. If
+ the process does not terminate after a timeout then SIGKILL
+ will be sent. When the job terminates its |on_exit| handler
+ (if any) will be invoked.
+ See |job-control|.
+
+ Returns 1 for valid job id, 0 for invalid id, including jobs have
+ exited or stopped.
+ ]=],
+ name = 'jobstop',
+ params = { { 'id', 'any' } },
+ signature = 'jobstop({id})',
+ },
+ jobwait = {
+ args = { 1, 2 },
+ desc = [=[
+ Waits for jobs and their |on_exit| handlers to complete.
+
+ {jobs} is a List of |job-id|s to wait for.
+ {timeout} is the maximum waiting time in milliseconds. If
+ omitted or -1, wait forever.
+
+ Timeout of 0 can be used to check the status of a job: >vim
+ let running = jobwait([{job-id}], 0)[0] == -1
+ <
+ During jobwait() callbacks for jobs not in the {jobs} list may
+ be invoked. The screen will not redraw unless |:redraw| is
+ invoked by a callback.
+
+ Returns a list of len({jobs}) integers, where each integer is
+ the status of the corresponding job:
+ Exit-code, if the job exited
+ -1 if the timeout was exceeded
+ -2 if the job was interrupted (by |CTRL-C|)
+ -3 if the job-id is invalid
+ ]=],
+ name = 'jobwait',
+ params = { { 'jobs', 'any' }, { 'timeout', 'integer' } },
+ signature = 'jobwait({jobs} [, {timeout}])',
+ },
+ join = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Join the items in {list} together into one String.
+ When {sep} is specified it is put in between the items. If
+ {sep} is omitted a single space is used.
+ Note that {sep} is not added at the end. You might want to
+ add it there too: >vim
+ let lines = join(mylist, "\n") .. "\n"
+ <String items are used as-is. |Lists| and |Dictionaries| are
+ converted into a string like with |string()|.
+ The opposite function is |split()|.
+
+ ]=],
+ name = 'join',
+ params = { { 'list', 'any' }, { 'sep', 'any' } },
+ signature = 'join({list} [, {sep}])',
+ },
+ json_decode = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} from JSON object. Accepts |readfile()|-style
+ list as the input, as well as regular string. May output any
+ Vim value. In the following cases it will output
+ |msgpack-special-dict|:
+ 1. Dictionary contains duplicate key.
+ 2. Dictionary contains empty key.
+ 3. String contains NUL byte. Two special dictionaries: for
+ dictionary and for string will be emitted in case string
+ with NUL byte was a dictionary key.
+
+ Note: function treats its input as UTF-8 always. The JSON
+ standard allows only a few encodings, of which UTF-8 is
+ recommended and the only one required to be supported.
+ Non-UTF-8 characters are an error.
+
+ ]=],
+ name = 'json_decode',
+ params = { { 'expr', 'any' } },
+ signature = 'json_decode({expr})',
+ },
+ json_encode = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert {expr} into a JSON string. Accepts
+ |msgpack-special-dict| as the input. Will not convert
+ |Funcref|s, mappings with non-string keys (can be created as
+ |msgpack-special-dict|), values with self-referencing
+ containers, strings which contain non-UTF-8 characters,
+ pseudo-UTF-8 strings which contain codepoints reserved for
+ surrogate pairs (such strings are not valid UTF-8 strings).
+ Non-printable characters are converted into "\u1234" escapes
+ or special escapes like "\t", other are dumped as-is.
+ |Blob|s are converted to arrays of the individual bytes.
+
+ ]=],
+ name = 'json_encode',
+ params = { { 'expr', 'any' } },
+ signature = 'json_encode({expr})',
+ },
+ keys = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the keys of {dict}. The |List| is in
+ arbitrary order. Also see |items()| and |values()|.
+
+ ]=],
+ name = 'keys',
+ params = { { 'dict', 'any' } },
+ signature = 'keys({dict})',
+ },
+ keytrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Turn the internal byte representation of keys into a form that
+ can be used for |:map|. E.g. >vim
+ let xx = "\<C-Home>"
+ echo keytrans(xx)
+ < <C-Home>
+
+ ]=],
+ name = 'keytrans',
+ params = { { 'string', 'string' } },
+ signature = 'keytrans({string})',
+ },
+ last_buffer_nr = {
+ deprecated = true,
+ desc = [=[
+ Obsolete name for bufnr("$").
+ ]=],
+ params = {},
+ signature = 'last_buffer_nr()',
+ },
+ len = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the length of the argument.
+ When {expr} is a String or a Number the length in bytes is
+ used, as with |strlen()|.
+ When {expr} is a |List| the number of items in the |List| is
+ returned.
+ When {expr} is a |Blob| the number of bytes is returned.
+ When {expr} is a |Dictionary| the number of entries in the
+ |Dictionary| is returned.
+ Otherwise an error is given and returns zero.
+
+ ]=],
+ name = 'len',
+ params = { { 'expr', 'any' } },
+ signature = 'len({expr})',
+ tags = { 'E701' },
+ },
+ libcall = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Call function {funcname} in the run-time library {libname}
+ with single argument {argument}.
+ This is useful to call functions in a library that you
+ especially made to be used with Vim. Since only one argument
+ is possible, calling standard library functions is rather
+ limited.
+ The result is the String returned by the function. If the
+ function returns NULL, this will appear as an empty string ""
+ to Vim.
+ If the function returns a number, use libcallnr()!
+ If {argument} is a number, it is passed to the function as an
+ int; if {argument} is a string, it is passed as a
+ null-terminated string.
+
+ libcall() allows you to write your own 'plug-in' extensions to
+ Vim without having to recompile the program. It is NOT a
+ means to call system functions! If you try to do so Vim will
+ very probably crash.
+
+ For Win32, the functions you write must be placed in a DLL
+ and use the normal C calling convention (NOT Pascal which is
+ used in Windows System DLLs). The function must take exactly
+ one parameter, either a character pointer or a long integer,
+ and must return a character pointer or NULL. The character
+ pointer returned must point to memory that will remain valid
+ after the function has returned (e.g. in static data in the
+ DLL). If it points to allocated memory, that memory will
+ leak away. Using a static buffer in the function should work,
+ it's then freed when the DLL is unloaded.
+
+ WARNING: If the function returns a non-valid pointer, Vim may
+ crash! This also happens if the function returns a number,
+ because Vim thinks it's a pointer.
+ For Win32 systems, {libname} should be the filename of the DLL
+ without the ".DLL" suffix. A full path is only required if
+ the DLL is not in the usual places.
+ For Unix: When compiling your own plugins, remember that the
+ object code must be compiled as position-independent ('PIC').
+ Examples: >vim
+ echo libcall("libc.so", "getenv", "HOME")
+
+ ]=],
+ name = 'libcall',
+ params = { { 'libname', 'string' }, { 'funcname', 'string' }, { 'argument', 'any' } },
+ signature = 'libcall({libname}, {funcname}, {argument})',
+ tags = { 'E364', 'E368' },
+ },
+ libcallnr = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Just like |libcall()|, but used for a function that returns an
+ int instead of a string.
+ Examples: >vim
+ echo libcallnr("/usr/lib/libc.so", "getpid", "")
+ call libcallnr("libc.so", "printf", "Hello World!\n")
+ call libcallnr("libc.so", "sleep", 10)
+ <
+ ]=],
+ name = 'libcallnr',
+ params = { { 'libname', 'string' }, { 'funcname', 'string' }, { 'argument', 'any' } },
+ signature = 'libcallnr({libname}, {funcname}, {argument})',
+ },
+ line = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the line number of the file
+ position given with {expr}. The {expr} argument is a string.
+ The accepted positions are:
+ . the cursor position
+ $ the last line in the current buffer
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ w0 first line visible in current window (one if the
+ display isn't updated, e.g. in silent Ex mode)
+ w$ last line visible in current window (this is one
+ less than "w0" if no lines are visible)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+ Note that a mark in another file can be used. The line number
+ then applies to another buffer.
+ To get the column number use |col()|. To get both use
+ |getpos()|.
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+ Returns 0 for invalid values of {expr} and {winid}.
+ Examples: >vim
+ echo line(".") " line number of the cursor
+ echo line(".", winid) " idem, in window "winid"
+ echo line("'t") " line number of mark t
+ echo line("'" .. marker) " line number of mark marker
+ <
+ To jump to the last known position when opening a file see
+ |last-position-jump|.
+
+ ]=],
+ name = 'line',
+ params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ returns = 'integer',
+ signature = 'line({expr} [, {winid}])',
+ },
+ line2byte = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the byte count from the start of the buffer for line
+ {lnum}. This includes the end-of-line character, depending on
+ the 'fileformat' option for the current buffer. The first
+ line returns 1. UTF-8 encoding is used, 'fileencoding' is
+ ignored. This can also be used to get the byte count for the
+ line just below the last line: >vim
+ echo line2byte(line("$") + 1)
+ <This is the buffer size plus one. If 'fileencoding' is empty
+ it is the file size plus one. {lnum} is used like with
+ |getline()|. When {lnum} is invalid -1 is returned.
+ Also see |byte2line()|, |go| and |:goto|.
+
+ ]=],
+ name = 'line2byte',
+ params = { { 'lnum', 'integer' } },
+ returns = 'integer',
+ signature = 'line2byte({lnum})',
+ },
+ lispindent = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Get the amount of indent for line {lnum} according the lisp
+ indenting rules, as with 'lisp'.
+ The indent is counted in spaces, the value of 'tabstop' is
+ relevant. {lnum} is used just like in |getline()|.
+ When {lnum} is invalid, -1 is returned.
+
+ ]=],
+ name = 'lispindent',
+ params = { { 'lnum', 'integer' } },
+ signature = 'lispindent({lnum})',
+ },
+ list2blob = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a Blob concatenating all the number values in {list}.
+ Examples: >vim
+ echo list2blob([1, 2, 3, 4]) " returns 0z01020304
+ echo list2blob([]) " returns 0z
+ <Returns an empty Blob on error. If one of the numbers is
+ negative or more than 255 error *E1239* is given.
+
+ |blob2list()| does the opposite.
+
+ ]=],
+ name = 'list2blob',
+ params = { { 'list', 'any' } },
+ signature = 'list2blob({list})',
+ },
+ list2str = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Convert each number in {list} to a character string can
+ concatenate them all. Examples: >vim
+ echo list2str([32]) " returns " "
+ echo list2str([65, 66, 67]) " returns "ABC"
+ <The same can be done (slowly) with: >vim
+ echo join(map(list, {nr, val -> nr2char(val)}), '')
+ <|str2list()| does the opposite.
+
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ With UTF-8 composing characters work as expected: >vim
+ echo list2str([97, 769]) " returns "á"
+ <
+ Returns an empty string on error.
+
+ ]=],
+ name = 'list2str',
+ params = { { 'list', 'any' }, { 'utf8', 'any' } },
+ signature = 'list2str({list} [, {utf8}])',
+ },
+ localtime = {
+ desc = [=[
+ Return the current time, measured as seconds since 1st Jan
+ 1970. See also |strftime()|, |strptime()| and |getftime()|.
+ ]=],
+ name = 'localtime',
+ params = {},
+ signature = 'localtime()',
+ },
+ log = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the natural logarithm (base e) of {expr} as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number| in the range
+ (0, inf].
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo log(10)
+ < 2.302585 >vim
+ echo log(exp(5))
+ < 5.0
+
+ ]=],
+ float_func = 'log',
+ name = 'log',
+ params = { { 'expr', 'any' } },
+ signature = 'log({expr})',
+ },
+ log10 = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the logarithm of Float {expr} to base 10 as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo log10(1000)
+ < 3.0 >vim
+ echo log10(0.01)
+ < -2.0
+
+ ]=],
+ float_func = 'log10',
+ name = 'log10',
+ params = { { 'expr', 'any' } },
+ signature = 'log10({expr})',
+ },
+ luaeval = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Evaluate Lua expression {expr} and return its result converted
+ to Vim data structures. See |lua-eval| for more details.
+
+ ]=],
+ lua = false,
+ name = 'luaeval',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'luaeval({expr} [, {expr}])',
+ },
+ map = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+ When {expr1} is a |List|| or |Dictionary|, replace each
+ item in {expr1} with the result of evaluating {expr2}.
+ For a |Blob| each byte is replaced.
+ For a |String|, each character, including composing
+ characters, is replaced.
+ If the item type changes you may want to use |mapnew()| to
+ create a new List or Dictionary.
+
+ {expr2} must be a |String| or |Funcref|.
+
+ If {expr2} is a |String|, inside {expr2} |v:val| has the value
+ of the current item. For a |Dictionary| |v:key| has the key
+ of the current item and for a |List| |v:key| has the index of
+ the current item. For a |Blob| |v:key| has the index of the
+ current byte. For a |String| |v:key| has the index of the
+ current character.
+ Example: >vim
+ call map(mylist, '"> " .. v:val .. " <"')
+ <This puts "> " before and " <" after each item in "mylist".
+
+ Note that {expr2} is the result of an expression and is then
+ used as an expression again. Often it is good to use a
+ |literal-string| to avoid having to double backslashes. You
+ still have to double ' quotes
+
+ If {expr2} is a |Funcref| it is called with two arguments:
+ 1. The key or the index of the current item.
+ 2. the value of the current item.
+ The function must return the new value of the item. Example
+ that changes each value by "key-value": >vim
+ func KeyValue(key, val)
+ return a:key .. '-' .. a:val
+ endfunc
+ call map(myDict, function('KeyValue'))
+ <It is shorter when using a |lambda|: >vim
+ call map(myDict, {key, val -> key .. '-' .. val})
+ <If you do not use "val" you can leave it out: >vim
+ call map(myDict, {key -> 'item: ' .. key})
+ <If you do not use "key" you can use a short name: >vim
+ call map(myDict, {_, val -> 'item: ' .. val})
+ <
+ The operation is done in-place for a |List| and |Dictionary|.
+ If you want it to remain unmodified make a copy first: >vim
+ let tlist = map(copy(mylist), ' v:val .. "\t"')
+
+ <Returns {expr1}, the |List| or |Dictionary| that was filtered,
+ or a new |Blob| or |String|.
+ When an error is encountered while evaluating {expr2} no
+ further items in {expr1} are processed.
+ When {expr2} is a Funcref errors inside a function are ignored,
+ unless it was defined with the "abort" flag.
+
+ ]=],
+ name = 'map',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'map({expr1}, {expr2})',
+ },
+ maparg = {
+ args = { 1, 4 },
+ base = 1,
+ desc = [=[
+ When {dict} is omitted or zero: Return the rhs of mapping
+ {name} in mode {mode}. The returned String has special
+ characters translated like in the output of the ":map" command
+ listing. When {dict} is TRUE a dictionary is returned, see
+ below. To get a list of all mappings see |maplist()|.
+
+ When there is no mapping for {name}, an empty String is
+ returned if {dict} is FALSE, otherwise returns an empty Dict.
+ When the mapping for {name} is empty, then "<Nop>" is
+ returned.
+
+ The {name} can have special key names, like in the ":map"
+ command.
+
+ {mode} can be one of these strings:
+ "n" Normal
+ "v" Visual (including Select)
+ "o" Operator-pending
+ "i" Insert
+ "c" Cmd-line
+ "s" Select
+ "x" Visual
+ "l" langmap |language-mapping|
+ "t" Terminal
+ "" Normal, Visual and Operator-pending
+ When {mode} is omitted, the modes for "" are used.
+
+ When {abbr} is there and it is |TRUE| use abbreviations
+ instead of mappings.
+
+ When {dict} is there and it is |TRUE| return a dictionary
+ containing all the information of the mapping with the
+ following items: *mapping-dict*
+ "lhs" The {lhs} of the mapping as it would be typed
+ "lhsraw" The {lhs} of the mapping as raw bytes
+ "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate
+ form, only present when it differs from "lhsraw"
+ "rhs" The {rhs} of the mapping as typed.
+ "silent" 1 for a |:map-silent| mapping, else 0.
+ "noremap" 1 if the {rhs} of the mapping is not remappable.
+ "script" 1 if mapping was defined with <script>.
+ "expr" 1 for an expression mapping (|:map-<expr>|).
+ "buffer" 1 for a buffer local mapping (|:map-local|).
+ "mode" Modes for which the mapping is defined. In
+ addition to the modes mentioned above, these
+ characters will be used:
+ " " Normal, Visual and Operator-pending
+ "!" Insert and Commandline mode
+ (|mapmode-ic|)
+ "sid" The script local ID, used for <sid> mappings
+ (|<SID>|). Negative for special contexts.
+ "scriptversion" The version of the script, always 1.
+ "lnum" The line number in "sid", zero if unknown.
+ "nowait" Do not wait for other, longer mappings.
+ (|:map-<nowait>|).
+ "abbr" True if this is an |abbreviation|.
+ "mode_bits" Nvim's internal binary representation of "mode".
+ |mapset()| ignores this; only "mode" is used.
+ See |maplist()| for usage examples. The values
+ are from src/nvim/state_defs.h and may change in
+ the future.
+
+ The dictionary can be used to restore a mapping with
+ |mapset()|.
+
+ The mappings local to the current buffer are checked first,
+ then the global mappings.
+ This function can be used to map a key even when it's already
+ mapped, and have it do the original mapping too. Sketch: >vim
+ exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n')
+
+ ]=],
+ name = 'maparg',
+ params = {
+ { 'name', 'string' },
+ { 'mode', 'string' },
+ { 'abbr', 'boolean' },
+ { 'dict', 'boolean' },
+ },
+ returns = 'string|table<string,any>',
+ signature = 'maparg({name} [, {mode} [, {abbr} [, {dict}]]])',
+ },
+ mapcheck = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Check if there is a mapping that matches with {name} in mode
+ {mode}. See |maparg()| for {mode} and special names in
+ {name}.
+ When {abbr} is there and it is non-zero use abbreviations
+ instead of mappings.
+ A match happens with a mapping that starts with {name} and
+ with a mapping which is equal to the start of {name}.
+
+ matches mapping "a" "ab" "abc" ~
+ mapcheck("a") yes yes yes
+ mapcheck("abc") yes yes yes
+ mapcheck("ax") yes no no
+ mapcheck("b") no no no
+
+ The difference with maparg() is that mapcheck() finds a
+ mapping that matches with {name}, while maparg() only finds a
+ mapping for {name} exactly.
+ When there is no mapping that starts with {name}, an empty
+ String is returned. If there is one, the RHS of that mapping
+ is returned. If there are several mappings that start with
+ {name}, the RHS of one of them is returned. This will be
+ "<Nop>" if the RHS is empty.
+ The mappings local to the current buffer are checked first,
+ then the global mappings.
+ This function can be used to check if a mapping can be added
+ without being ambiguous. Example: >vim
+ if mapcheck("_vv") == ""
+ map _vv :set guifont=7x13<CR>
+ endif
+ <This avoids adding the "_vv" mapping when there already is a
+ mapping for "_v" or for "_vvv".
+
+ ]=],
+ name = 'mapcheck',
+ params = { { 'name', 'string' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ signature = 'mapcheck({name} [, {mode} [, {abbr}]])',
+ },
+ maplist = {
+ args = { 0, 1 },
+ desc = [[
+ Returns a |List| of all mappings. Each List item is a |Dict|,
+ the same as what is returned by |maparg()|, see
+ |mapping-dict|. When {abbr} is there and it is |TRUE| use
+ abbreviations instead of mappings.
+
+ Example to show all mappings with "MultiMatch" in rhs: >vim
+ echo maplist()->filter({_, m ->
+ \ match(get(m, 'rhs', ''), 'MultiMatch') >= 0
+ \ })
+ <It can be tricky to find mappings for particular |:map-modes|.
+ |mapping-dict|'s "mode_bits" can simplify this. For example,
+ the mode_bits for Normal, Insert or Command-line modes are
+ 0x19. To find all the mappings available in those modes you
+ can do: >vim
+ let saved_maps = []
+ for m in maplist()
+ if and(m.mode_bits, 0x19) != 0
+ eval saved_maps->add(m)
+ endif
+ endfor
+ echo saved_maps->mapnew({_, m -> m.lhs})
+ <The values of the mode_bits are defined in Nvim's
+ src/nvim/state_defs.h file and they can be discovered at
+ runtime using |:map-commands| and "maplist()". Example: >vim
+ omap xyzzy <Nop>
+ let op_bit = maplist()->filter(
+ \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
+ ounmap xyzzy
+ echo printf("Operator-pending mode bit: 0x%x", op_bit)
+ ]],
+ name = 'maplist',
+ params = {},
+ signature = 'maplist([{abbr}])'
+ },
+ mapnew = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Like |map()| but instead of replacing items in {expr1} a new
+ List or Dictionary is created and returned. {expr1} remains
+ unchanged. Items can still be changed by {expr2}, if you
+ don't want that use |deepcopy()| first.
+ ]=],
+ name = 'mapnew',
+ params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ signature = 'mapnew({expr1}, {expr2})',
+ },
+ mapset = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Restore a mapping from a dictionary, possibly returned by
+ |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
+ is true, is set on the current buffer; it is up to the caller
+ to ensure that the intended buffer is the current buffer. This
+ feature allows copying mappings from one buffer to another.
+ The dict.mode value may restore a single mapping that covers
+ more than one mode, like with mode values of '!', ' ', "nox",
+ or 'v'. *E1276*
+
+ In the first form, {mode} and {abbr} should be the same as
+ for the call to |maparg()|. *E460*
+ {mode} is used to define the mode in which the mapping is set,
+ not the "mode" entry in {dict}.
+ Example for saving and restoring a mapping: >vim
+ let save_map = maparg('K', 'n', 0, 1)
+ nnoremap K somethingelse
+ " ...
+ call mapset('n', 0, save_map)
+ <Note that if you are going to replace a map in several modes,
+ e.g. with `:map!`, you need to save/restore the mapping for
+ all of them, when they might differ.
+
+ In the second form, with {dict} as the only argument, mode
+ and abbr are taken from the dict.
+ Example: >vim
+ let save_maps = maplist()->filter(
+ \ {_, m -> m.lhs == 'K'})
+ nnoremap K somethingelse
+ cnoremap K somethingelse2
+ " ...
+ unmap K
+ for d in save_maps
+ call mapset(d)
+ endfor
+ ]=],
+ name = 'mapset',
+ params = { { 'mode', 'string' }, { 'abbr', 'any' }, { 'dict', 'any' } },
+ signature = 'mapset({mode}, {abbr}, {dict})',
+ },
+ match = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ When {expr} is a |List| then this returns the index of the
+ first item where {pat} matches. Each item is used as a
+ String, |Lists| and |Dictionaries| are used as echoed.
+
+ Otherwise, {expr} is used as a String. The result is a
+ Number, which gives the index (byte offset) in {expr} where
+ {pat} matches.
+
+ A match at the first character or |List| item returns zero.
+ If there is no match -1 is returned.
+
+ For getting submatches see |matchlist()|.
+ Example: >vim
+ echo match("testing", "ing") " results in 4
+ echo match([1, 'x'], '\a') " results in 1
+ <See |string-match| for how {pat} is used.
+ *strpbrk()*
+ Vim doesn't have a strpbrk() function. But you can do: >vim
+ let sepidx = match(line, '[.,;: \t]')
+ < *strcasestr()*
+ Vim doesn't have a strcasestr() function. But you can add
+ "\c" to the pattern to ignore case: >vim
+ let idx = match(haystack, '\cneedle')
+ <
+ If {start} is given, the search starts from byte index
+ {start} in a String or item {start} in a |List|.
+ The result, however, is still the index counted from the
+ first character/item. Example: >vim
+ echo match("testing", "ing", 2)
+ <result is again "4". >vim
+ echo match("testing", "ing", 4)
+ <result is again "4". >vim
+ echo match("testing", "t", 2)
+ <result is "3".
+ For a String, if {start} > 0 then it is like the string starts
+ {start} bytes later, thus "^" will match at {start}. Except
+ when {count} is given, then it's like matches before the
+ {start} byte are ignored (this is a bit complicated to keep it
+ backwards compatible).
+ For a String, if {start} < 0, it will be set to 0. For a list
+ the index is counted from the end.
+ If {start} is out of range ({start} > strlen({expr}) for a
+ String or {start} > len({expr}) for a |List|) -1 is returned.
+
+ When {count} is given use the {count}th match. When a match
+ is found in a String the search for the next one starts one
+ character further. Thus this example results in 1: >vim
+ echo match("testing", "..", 0, 2)
+ <In a |List| the search continues in the next item.
+ Note that when {count} is added the way {start} works changes,
+ see above.
+
+ See |pattern| for the patterns that are accepted.
+ The 'ignorecase' option is used to set the ignore-caseness of
+ the pattern. 'smartcase' is NOT used. The matching is always
+ done like 'magic' is set and 'cpoptions' is empty.
+ Note that a match at the start is preferred, thus when the
+ pattern is using "*" (any number of matches) it tends to find
+ zero matches at the start instead of a number of matches
+ further down in the text.
+
+ ]=],
+ name = 'match',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'match({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchadd = {
+ args = { 2, 5 },
+ base = 1,
+ desc = [=[
+ 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
+ match using |matchdelete()|. The ID is bound to the window.
+ Matching is case sensitive and magic, unless case sensitivity
+ or magicness are explicitly overridden in {pattern}. The
+ 'magic', 'smartcase' and 'ignorecase' options are not used.
+ The "Conceal" value is special, it causes the match to be
+ concealed.
+
+ The optional {priority} argument assigns a priority to the
+ match. A match with a high priority will have its
+ highlighting overrule that of a match with a lower priority.
+ A priority is specified as an integer (negative numbers are no
+ exception). If the {priority} argument is not specified, the
+ default priority is 10. The priority of 'hlsearch' is zero,
+ hence all matches with a priority greater than zero will
+ overrule it. Syntax highlighting (see 'syntax') is a separate
+ mechanism, and regardless of the chosen priority a match will
+ always overrule syntax highlighting.
+
+ The optional {id} argument allows the request for a specific
+ match ID. If a specified ID is already taken, an error
+ message will appear and the match will not be added. An ID
+ is specified as a positive integer (zero excluded). IDs 1, 2
+ and 3 are reserved for |:match|, |:2match| and |:3match|,
+ respectively. 3 is reserved for use by the |matchparen|
+ plugin.
+ If the {id} argument is not specified or -1, |matchadd()|
+ automatically chooses a free ID, which is at least 1000.
+
+ The optional {dict} argument allows for further custom
+ values. Currently this is used to specify a match specific
+ conceal character that will be shown for |hl-Conceal|
+ highlighted matches. The dict can have the following members:
+
+ conceal Special character to show instead of the
+ match (only for |hl-Conceal| highlighted
+ matches, see |:syn-cchar|)
+ window Instead of the current window use the
+ window with this number or window ID.
+
+ The number of matches is not limited, as it is the case with
+ the |:match| commands.
+
+ Returns -1 on error.
+
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchadd("MyGroup", "TODO")
+ <Deletion of the pattern: >vim
+ call matchdelete(m)
+
+ <A list of matches defined by |matchadd()| and |:match| are
+ available from |getmatches()|. All matches can be deleted in
+ one operation by |clearmatches()|.
+
+ ]=],
+ name = 'matchadd',
+ params = {
+ { 'group', 'any' },
+ { 'pattern', 'any' },
+ { 'priority', 'any' },
+ { 'id', 'any' },
+ { 'dict', 'any' },
+ },
+ signature = 'matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])',
+ tags = { 'E798', 'E799', 'E801', 'E957' },
+ },
+ matchaddpos = {
+ args = { 2, 5 },
+ base = 1,
+ desc = [=[
+ Same as |matchadd()|, but requires a list of positions {pos}
+ instead of a pattern. This command is faster than |matchadd()|
+ because it does not require to handle regular expressions and
+ sets buffer line boundaries to redraw screen. It is supposed
+ to be used when fast match additions and deletions are
+ required, for example to highlight matching parentheses.
+ *E5030* *E5031*
+ {pos} is a list of positions. Each position can be one of
+ these:
+ - A number. This whole line will be highlighted. The first
+ line has number 1.
+ - A list with one number, e.g., [23]. The whole line with this
+ number will be highlighted.
+ - A list with two numbers, e.g., [23, 11]. The first number is
+ the line number, the second one is the column number (first
+ column is 1, the value must correspond to the byte index as
+ |col()| would return). The character at this position will
+ be highlighted.
+ - A list with three numbers, e.g., [23, 11, 3]. As above, but
+ the third number gives the length of the highlight in bytes.
+
+ Entries with zero and negative line numbers are silently
+ ignored, as well as entries with negative column numbers and
+ lengths.
+
+ Returns -1 on error.
+
+ Example: >vim
+ highlight MyGroup ctermbg=green guibg=green
+ let m = matchaddpos("MyGroup", [[23, 24], 34])
+ <Deletion of the pattern: >vim
+ call matchdelete(m)
+
+ <Matches added by |matchaddpos()| are returned by
+ |getmatches()|.
+
+ ]=],
+ name = 'matchaddpos',
+ params = {
+ { 'group', 'any' },
+ { 'pos', 'any' },
+ { 'priority', 'any' },
+ { 'id', 'any' },
+ { 'dict', 'any' },
+ },
+ signature = 'matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])',
+ },
+ matcharg = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Selects the {nr} match item, as set with a |:match|,
+ |:2match| or |:3match| command.
+ Return a |List| with two elements:
+ The name of the highlight group used
+ The pattern used.
+ When {nr} is not 1, 2 or 3 returns an empty |List|.
+ When there is no match item set returns ['', ''].
+ This is useful to save and restore a |:match|.
+ Highlighting matches using the |:match| commands are limited
+ to three matches. |matchadd()| does not have this limitation.
+
+ ]=],
+ name = 'matcharg',
+ params = { { 'nr', 'integer' } },
+ signature = 'matcharg({nr})',
+ },
+ matchdelete = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Deletes a match with ID {id} previously defined by |matchadd()|
+ or one of the |:match| commands. Returns 0 if successful,
+ otherwise -1. See example for |matchadd()|. All matches can
+ be deleted in one operation by |clearmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'matchdelete',
+ params = { { 'id', 'any' }, { 'win', 'any' } },
+ signature = 'matchdelete({id} [, {win}])',
+ tags = { 'E802', 'E803' },
+ },
+ matchend = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return the index of first character
+ after the match. Example: >vim
+ echo matchend("testing", "ing")
+ <results in "7".
+ *strspn()* *strcspn()*
+ Vim doesn't have a strspn() or strcspn() function, but you can
+ do it with matchend(): >vim
+ let span = matchend(line, '[a-zA-Z]')
+ let span = matchend(line, '[^a-zA-Z]')
+ <Except that -1 is returned when there are no matches.
+
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchend("testing", "ing", 2)
+ <results in "7". >vim
+ echo matchend("testing", "ing", 5)
+ <result is "-1".
+ When {expr} is a |List| the result is equal to |match()|.
+
+ ]=],
+ name = 'matchend',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchend({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchfuzzy = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ 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 return only matches
+ that contain the characters in {str} in the
+ given sequence.
+ limit Maximum number of matches in {list} to be
+ returned. Zero means no limit.
+
+ 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.
+
+ When {limit} is given, matchfuzzy() will find up to this
+ number of matches in {list} and return them in sorted order.
+
+ Refer to |fuzzy-matching| for more information about fuzzy
+ matching strings.
+
+ Example: >vim
+ echo matchfuzzy(["clay", "crow"], "cay")
+ <results in ["clay"]. >vim
+ echo getbufinfo()->map({_, v -> v.name})->matchfuzzy("ndl")
+ <results in a list of buffer names fuzzy matching "ndl". >vim
+ echo getbufinfo()->matchfuzzy("ndl", {'key' : 'name'})
+ <results in a list of buffer information dicts with buffer
+ names fuzzy matching "ndl". >vim
+ echo getbufinfo()->matchfuzzy("spl",
+ \ {'text_cb' : {v -> v.name}})
+ <results in a list of buffer information dicts with buffer
+ names fuzzy matching "spl". >vim
+ echo v:oldfiles->matchfuzzy("test")
+ <results in a list of file names fuzzy matching "test". >vim
+ let l = readfile("buffer.c")->matchfuzzy("str")
+ <results in a list of lines in "buffer.c" fuzzy matching "str". >vim
+ echo ['one two', 'two one']->matchfuzzy('two one')
+ <results in `['two one', 'one two']` . >vim
+ echo ['one two', 'two one']->matchfuzzy('two one',
+ \ {'matchseq': 1})
+ <results in `['two one']`.
+ ]=],
+ name = 'matchfuzzy',
+ params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ signature = 'matchfuzzy({list}, {str} [, {dict}])',
+ },
+ matchfuzzypos = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ 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: >vim
+ echo matchfuzzypos(['testing'], 'tsg')
+ <results in [["testing"], [[0, 2, 6]], [99]] >vim
+ echo matchfuzzypos(['clay', 'lacy'], 'la')
+ <results in [["lacy", "clay"], [[0, 1], [1, 2]], [153, 133]] >vim
+ echo [{'text': 'hello', 'id' : 10}]
+ \ ->matchfuzzypos('ll', {'key' : 'text'})
+ <results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]`
+ ]=],
+ name = 'matchfuzzypos',
+ params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ signature = 'matchfuzzypos({list}, {str} [, {dict}])',
+ },
+ matchlist = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return a |List|. The first item in the
+ list is the matched string, same as what matchstr() would
+ return. Following items are submatches, like "\1", "\2", etc.
+ in |:substitute|. When an optional submatch didn't match an
+ empty string is used. Example: >vim
+ echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)')
+ <Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', '']
+ When there is no match an empty list is returned.
+
+ You can pass in a List, but that is not very useful.
+
+ ]=],
+ name = 'matchlist',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchlist({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchstr = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |match()|, but return the matched string. Example: >vim
+ echo matchstr("testing", "ing")
+ <results in "ing".
+ When there is no match "" is returned.
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstr("testing", "ing", 2)
+ <results in "ing". >vim
+ echo matchstr("testing", "ing", 5)
+ <result is "".
+ When {expr} is a |List| then the matching item is returned.
+ The type isn't changed, it's not necessarily a String.
+
+ ]=],
+ name = 'matchstr',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchstr({expr}, {pat} [, {start} [, {count}]])',
+ },
+ matchstrpos = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |matchstr()|, but return the matched string, the start
+ position and the end position of the match. Example: >vim
+ echo matchstrpos("testing", "ing")
+ <results in ["ing", 4, 7].
+ When there is no match ["", -1, -1] is returned.
+ The {start}, if given, has the same meaning as for |match()|. >vim
+ echo matchstrpos("testing", "ing", 2)
+ <results in ["ing", 4, 7]. >vim
+ echo matchstrpos("testing", "ing", 5)
+ <result is ["", -1, -1].
+ When {expr} is a |List| then the matching item, the index
+ of first item where {pat} matches, the start position and the
+ end position of the match are returned. >vim
+ echo matchstrpos([1, '__x'], '\a')
+ <result is ["x", 1, 2, 3].
+ The type isn't changed, it's not necessarily a String.
+
+ ]=],
+ name = 'matchstrpos',
+ params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ signature = 'matchstrpos({expr}, {pat} [, {start} [, {count}]])',
+ },
+ max = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the maximum value of all items in {expr}. Example: >vim
+ 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
+ an error. An empty |List| or |Dictionary| results in zero.
+
+ ]=],
+ name = 'max',
+ params = { { 'expr', 'any' } },
+ signature = 'max({expr})',
+ },
+ menu_get = {
+ args = { 1, 2 },
+ desc = [=[
+ Returns a |List| of |Dictionaries| describing |menus| (defined
+ by |:menu|, |:amenu|, …), including |hidden-menus|.
+
+ {path} matches a menu by name, or all menus if {path} is an
+ empty string. Example: >vim
+ echo menu_get('File','')
+ echo menu_get('')
+ <
+ {modes} is a string of zero or more modes (see |maparg()| or
+ |creating-menus| for the list of modes). "a" means "all".
+
+ Example: >vim
+ nnoremenu &Test.Test inormal
+ inoremenu Test.Test insert
+ vnoremenu Test.Test x
+ echo menu_get("")
+
+ <returns something like this: >
+
+ [ {
+ "hidden": 0,
+ "name": "Test",
+ "priority": 500,
+ "shortcut": 84,
+ "submenus": [ {
+ "hidden": 0,
+ "mappings": {
+ i": {
+ "enabled": 1,
+ "noremap": 1,
+ "rhs": "insert",
+ "sid": 1,
+ "silent": 0
+ },
+ n": { ... },
+ s": { ... },
+ v": { ... }
+ },
+ "name": "Test",
+ "priority": 500,
+ "shortcut": 0
+ } ]
+ } ]
+ <
+ ]=],
+ name = 'menu_get',
+ params = { { 'path', 'string' }, { 'modes', 'any' } },
+ signature = 'menu_get({path} [, {modes}])',
+ },
+ menu_info = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return information about the specified menu {name} in
+ mode {mode}. The menu name should be specified without the
+ shortcut character ('&'). If {name} is "", then the top-level
+ menu names are returned.
+
+ {mode} can be one of these strings:
+ "n" Normal
+ "v" Visual (including Select)
+ "o" Operator-pending
+ "i" Insert
+ "c" Cmd-line
+ "s" Select
+ "x" Visual
+ "t" Terminal-Job
+ "" Normal, Visual and Operator-pending
+ "!" Insert and Cmd-line
+ When {mode} is omitted, the modes for "" are used.
+
+ Returns a |Dictionary| containing the following items:
+ accel menu item accelerator text |menu-text|
+ display display name (name without '&')
+ enabled v:true if this menu item is enabled
+ Refer to |:menu-enable|
+ icon name of the icon file (for toolbar)
+ |toolbar-icon|
+ iconidx index of a built-in icon
+ modes modes for which the menu is defined. In
+ addition to the modes mentioned above, these
+ characters will be used:
+ " " Normal, Visual and Operator-pending
+ name menu item name.
+ noremenu v:true if the {rhs} of the menu item is not
+ remappable else v:false.
+ priority menu order priority |menu-priority|
+ rhs right-hand-side of the menu item. The returned
+ string has special characters translated like
+ in the output of the ":menu" command listing.
+ When the {rhs} of a menu item is empty, then
+ "<Nop>" is returned.
+ script v:true if script-local remapping of {rhs} is
+ allowed else v:false. See |:menu-script|.
+ shortcut shortcut key (character after '&' in
+ the menu name) |menu-shortcut|
+ silent v:true if the menu item is created
+ with <silent> argument |:menu-silent|
+ submenus |List| containing the names of
+ all the submenus. Present only if the menu
+ item has submenus.
+
+ Returns an empty dictionary if the menu item is not found.
+
+ Examples: >vim
+ echo menu_info('Edit.Cut')
+ echo menu_info('File.Save', 'n')
+
+ " Display the entire menu hierarchy in a buffer
+ func ShowMenu(name, pfx)
+ let m = menu_info(a:name)
+ call append(line('$'), a:pfx .. m.display)
+ for child in m->get('submenus', [])
+ call ShowMenu(a:name .. '.' .. escape(child, '.'),
+ \ a:pfx .. ' ')
+ endfor
+ endfunc
+ new
+ for topmenu in menu_info('').submenus
+ call ShowMenu(topmenu, '')
+ endfor
+ <
+ ]=],
+ name = 'menu_info',
+ params = { { 'name', 'string' }, { 'mode', 'string' } },
+ signature = 'menu_info({name} [, {mode}])',
+ },
+ min = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the minimum value of all items in {expr}. Example: >vim
+ 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
+ an error. An empty |List| or |Dictionary| results in zero.
+
+ ]=],
+ name = 'min',
+ params = { { 'expr', 'any' } },
+ signature = 'min({expr})',
+ },
+ mkdir = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Create directory {name}.
+
+ When {flags} is present it must be a string. An empty string
+ has no effect.
+
+ If {flags} contains "p" then intermediate directories are
+ created as necessary.
+
+ If {flags} contains "D" then {name} is deleted at the end of
+ the current function, as with: >vim
+ defer delete({name}, 'd')
+ <
+ If {flags} contains "R" then {name} is deleted recursively at
+ the end of the current function, as with: >vim
+ defer delete({name}, 'rf')
+ <Note that when {name} has more than one part and "p" is used
+ some directories may already exist. Only the first one that
+ is created and what it contains is scheduled to be deleted.
+ E.g. when using: >vim
+ call mkdir('subdir/tmp/autoload', 'pR')
+ <and "subdir" already exists then "subdir/tmp" will be
+ scheduled for deletion, like with: >vim
+ defer delete('subdir/tmp', 'rf')
+ <
+ 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
+ unreadable for others.
+
+ {prot} is applied for all parts of {name}. Thus if you create
+ /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: >vim
+ 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 {flags} set to
+ "p" mkdir() will silently exit.
+
+ The function result is a Number, which is TRUE if the call was
+ successful or FALSE if the directory creation failed or partly
+ failed.
+
+ ]=],
+ name = 'mkdir',
+ params = { { 'name', 'string' }, { 'flags', 'string' }, { 'prot', 'any' } },
+ signature = 'mkdir({name} [, {flags} [, {prot}]])',
+ tags = { 'E739' },
+ },
+ mode = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a string that indicates the current mode.
+ If [expr] is supplied and it evaluates to a non-zero Number or
+ a non-empty String (|non-zero-arg|), then the full mode is
+ returned, otherwise only the first letter is returned.
+ Also see |state()|.
+
+ n Normal
+ no Operator-pending
+ nov Operator-pending (forced charwise |o_v|)
+ noV Operator-pending (forced linewise |o_V|)
+ noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|)
+ CTRL-V is one character
+ niI Normal using |i_CTRL-O| in |Insert-mode|
+ niR Normal using |i_CTRL-O| in |Replace-mode|
+ niV Normal using |i_CTRL-O| in |Virtual-Replace-mode|
+ nt Normal in |terminal-emulator| (insert goes to
+ Terminal mode)
+ ntT Normal using |t_CTRL-\_CTRL-O| in |Terminal-mode|
+ v Visual by character
+ vs Visual by character using |v_CTRL-O| in Select mode
+ V Visual by line
+ Vs Visual by line using |v_CTRL-O| in Select mode
+ CTRL-V Visual blockwise
+ CTRL-Vs Visual blockwise using |v_CTRL-O| in Select mode
+ s Select by character
+ S Select by line
+ CTRL-S Select blockwise
+ i Insert
+ ic Insert mode completion |compl-generic|
+ ix Insert mode |i_CTRL-X| completion
+ R Replace |R|
+ Rc Replace mode completion |compl-generic|
+ Rx Replace mode |i_CTRL-X| completion
+ Rv Virtual Replace |gR|
+ Rvc Virtual Replace mode completion |compl-generic|
+ Rvx Virtual Replace mode |i_CTRL-X| completion
+ c Command-line editing
+ cr Command-line editing overstrike mode |c_<Insert>|
+ cv Vim Ex mode |gQ|
+ cvr Vim Ex mode while in overstrike mode |c_<Insert>|
+ r Hit-enter prompt
+ rm The -- more -- prompt
+ r? A |:confirm| query of some sort
+ ! Shell or external command is executing
+ t Terminal mode: keys go to the job
+
+ This is useful in the 'statusline' option or RPC calls. In
+ most other places it always returns "c" or "n".
+ Note that in the future more modes and more specific modes may
+ be added. It's better not to compare the whole string but only
+ the leading character(s).
+ Also see |visualmode()|.
+
+ ]=],
+ name = 'mode',
+ params = {},
+ signature = 'mode([expr])',
+ },
+ msgpackdump = {
+ args = { 1, 2 },
+ desc = [=[
+ Convert a list of Vimscript objects to msgpack. Returned value is a
+ |readfile()|-style list. When {type} contains "B", a |Blob| is
+ returned instead. Example: >vim
+ call writefile(msgpackdump([{}]), 'fname.mpack', 'b')
+ <or, using a |Blob|: >vim
+ call writefile(msgpackdump([{}], 'B'), 'fname.mpack')
+ <
+ This will write the single 0x80 byte to a `fname.mpack` file
+ (dictionary with zero items is represented by 0x80 byte in
+ messagepack).
+
+ Limitations: *E5004* *E5005*
+ 1. |Funcref|s cannot be dumped.
+ 2. Containers that reference themselves cannot be dumped.
+ 3. Dictionary keys are always dumped as STR strings.
+ 4. Other strings and |Blob|s are always dumped as BIN strings.
+ 5. Points 3. and 4. do not apply to |msgpack-special-dict|s.
+ ]=],
+ name = 'msgpackdump',
+ params = { { 'list', 'any' }, { 'type', 'any' } },
+ signature = 'msgpackdump({list} [, {type}])',
+ },
+ msgpackparse = {
+ args = 1,
+ desc = [=[
+ Convert a |readfile()|-style list or a |Blob| to a list of
+ Vimscript objects.
+ Example: >vim
+ let fname = expand('~/.config/nvim/shada/main.shada')
+ let mpack = readfile(fname, 'b')
+ let shada_objects = msgpackparse(mpack)
+ <This will read ~/.config/nvim/shada/main.shada file to
+ `shada_objects` list.
+
+ Limitations:
+ 1. Mapping ordering is not preserved unless messagepack
+ mapping is dumped using generic mapping
+ (|msgpack-special-map|).
+ 2. Since the parser aims to preserve all data untouched
+ (except for 1.) some strings are parsed to
+ |msgpack-special-dict| format which is not convenient to
+ use.
+ *msgpack-special-dict*
+ Some messagepack strings may be parsed to special
+ dictionaries. Special dictionaries are dictionaries which
+
+ 1. Contain exactly two keys: `_TYPE` and `_VAL`.
+ 2. `_TYPE` key is one of the types found in |v:msgpack_types|
+ variable.
+ 3. Value for `_VAL` has the following format (Key column
+ contains name of the key from |v:msgpack_types|):
+
+ Key Value ~
+ nil Zero, ignored when dumping. Not returned by
+ |msgpackparse()| since |v:null| was introduced.
+ boolean One or zero. When dumping it is only checked that
+ value is a |Number|. Not returned by |msgpackparse()|
+ since |v:true| and |v:false| were introduced.
+ integer |List| with four numbers: sign (-1 or 1), highest two
+ bits, number with bits from 62nd to 31st, lowest 31
+ bits. I.e. to get actual number one will need to use
+ code like >
+ _VAL[0] * ((_VAL[1] << 62)
+ & (_VAL[2] << 31)
+ & _VAL[3])
+ < Special dictionary with this type will appear in
+ |msgpackparse()| output under one of the following
+ circumstances:
+ 1. |Number| is 32-bit and value is either above
+ INT32_MAX or below INT32_MIN.
+ 2. |Number| is 64-bit and value is above INT64_MAX. It
+ cannot possibly be below INT64_MIN because msgpack
+ C parser does not support such values.
+ float |Float|. This value cannot possibly appear in
+ |msgpackparse()| output.
+ string |readfile()|-style list of strings. This value will
+ appear in |msgpackparse()| output if string contains
+ zero byte or if string is a mapping key and mapping is
+ being represented as special dictionary for other
+ reasons.
+ binary |String|, or |Blob| if binary string contains zero
+ byte. This value cannot appear in |msgpackparse()|
+ output since blobs were introduced.
+ array |List|. This value cannot appear in |msgpackparse()|
+ output.
+ *msgpack-special-map*
+ map |List| of |List|s with two items (key and value) each.
+ This value will appear in |msgpackparse()| output if
+ parsed mapping contains one of the following keys:
+ 1. Any key that is not a string (including keys which
+ are binary strings).
+ 2. String with NUL byte inside.
+ 3. Duplicate key.
+ 4. Empty key.
+ ext |List| with two values: first is a signed integer
+ representing extension type. Second is
+ |readfile()|-style list of strings.
+ ]=],
+ name = 'msgpackparse',
+ params = { { 'data', 'any' } },
+ signature = 'msgpackparse({data})',
+ },
+ nextnonblank = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number of the first line at or below {lnum}
+ that is not blank. Example: >vim
+ if getline(nextnonblank(1)) =~ "Java" | endif
+ <When {lnum} is invalid or there is no non-blank line at or
+ below it, zero is returned.
+ {lnum} is used like with |getline()|.
+ See also |prevnonblank()|.
+
+ ]=],
+ name = 'nextnonblank',
+ params = { { 'lnum', 'integer' } },
+ signature = 'nextnonblank({lnum})',
+ },
+ nr2char = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a string with a single character, which has the number
+ value {expr}. Examples: >vim
+ echo nr2char(64) " returns '@'
+ echo nr2char(32) " returns ' '
+ <Example for "utf-8": >vim
+ echo nr2char(300) " returns I with bow character
+ <
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ Note that a NUL character in the file is specified with
+ nr2char(10), because NULs are represented with newline
+ characters. nr2char(0) is a real NUL and terminates the
+ string, thus results in an empty string.
+
+ ]=],
+ name = 'nr2char',
+ params = { { 'expr', 'any' }, { 'utf8', 'any' } },
+ signature = 'nr2char({expr} [, {utf8}])',
+ },
+ nvim_api__ = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Call nvim |api| functions. The type checking of arguments will
+ be stricter than for most other builtins. For instance,
+ if Integer is expected, a |Number| must be passed in, a
+ |String| will not be autoconverted.
+ Buffer numbers, as returned by |bufnr()| could be used as
+ first argument to nvim_buf_... functions. All functions
+ expecting an object (buffer, window or tabpage) can
+ also take the numerical value 0 to indicate the current
+ (focused) object.
+ ]=],
+ lua = false,
+ name = 'nvim_...',
+ params = VARARGS,
+ signature = 'nvim_...({...})',
+ tags = { 'E5555', 'eval-api' },
+ },
+ ['or'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise OR on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `and()` and `xor()`.
+ Example: >vim
+ let bits = or(bits, 0x80)
+
+ <Rationale: The reason this is a function and not using the "|"
+ character like many languages, is that Vi has always used "|"
+ to separate commands. In many places it would not be clear if
+ "|" is an operator or a command separator.
+ ]=],
+ name = 'or',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'or({expr}, {expr})',
+ },
+ pathshorten = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ 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 {len} letters in length.
+ If {len} is omitted or smaller than 1 then 1 is used (single
+ letters). Leading '~' and '.' characters are kept. Examples: >vim
+ echo pathshorten('~/.config/nvim/autoload/file1.vim')
+ < ~/.c/n/a/file1.vim ~
+ >vim
+ echo pathshorten('~/.config/nvim/autoload/file2.vim', 2)
+ < ~/.co/nv/au/file2.vim ~
+ It doesn't matter if the path exists or not.
+ Returns an empty string on error.
+
+ ]=],
+ name = 'pathshorten',
+ params = { { 'path', 'string' }, { 'len', 'any' } },
+ signature = 'pathshorten({path} [, {len}])',
+ },
+ perleval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate |perl| expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type,
+ non-string keys result in error.
+
+ Note: If you want an array or hash, {expr} must return a
+ reference to it.
+ Example: >vim
+ echo perleval('[1 .. 4]')
+ < [1, 2, 3, 4]
+
+ ]=],
+ name = 'perleval',
+ params = { { 'expr', 'any' } },
+ signature = 'perleval({expr})',
+ },
+ pow = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Return the power of {x} to the exponent {y} as a |Float|.
+ {x} and {y} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {x} or {y} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo pow(3, 3)
+ < 27.0 >vim
+ echo pow(2, 16)
+ < 65536.0 >vim
+ echo pow(32, 0.20)
+ < 2.0
+
+ ]=],
+ name = 'pow',
+ params = { { 'x', 'any' }, { 'y', 'any' } },
+ signature = 'pow({x}, {y})',
+ },
+ prevnonblank = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the line number of the first line at or above {lnum}
+ that is not blank. Example: >vim
+ let ind = indent(prevnonblank(v:lnum - 1))
+ <When {lnum} is invalid or there is no non-blank line at or
+ above it, zero is returned.
+ {lnum} is used like with |getline()|.
+ Also see |nextnonblank()|.
+
+ ]=],
+ name = 'prevnonblank',
+ params = { { 'lnum', 'integer' } },
+ signature = 'prevnonblank({lnum})',
+ },
+ printf = {
+ args = { 1 },
+ base = 2,
+ desc = [=[
+ Return a String with {fmt}, where "%" items are replaced by
+ the formatted form of their respective arguments. Example: >vim
+ echo printf("%4d: E%d %.30s", lnum, errno, msg)
+ <May result in:
+ " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~
+
+ When used as a |method| the base is passed as the second
+ argument: >vim
+ Compute()->printf("result: %d")
+ <
+ You can use `call()` to pass the items as a list.
+
+ Often used items are:
+ %s string
+ %6S string right-aligned in 6 display cells
+ %6s string right-aligned in 6 bytes
+ %.9s string truncated to 9 bytes
+ %c single byte
+ %d decimal number
+ %5d decimal number padded with spaces to 5 characters
+ %b binary number
+ %08b binary number padded with zeros to at least 8 characters
+ %B binary number using upper case letters
+ %x hex number
+ %04x hex number padded with zeros to at least 4 characters
+ %X hex number using upper case letters
+ %o octal number
+ %f floating point number as 12.23, inf, -inf or nan
+ %F floating point number as 12.23, INF, -INF or NAN
+ %e floating point number as 1.23e3, inf, -inf or nan
+ %E floating point number as 1.23E3, INF, -INF or NAN
+ %g floating point number, as %f or %e depending on value
+ %G floating point number, as %F or %E depending on value
+ %% the % character itself
+ %p representation of the pointer to the container
+
+ Conversion specifications start with '%' and end with the
+ conversion type. All other characters are copied unchanged to
+ the result.
+
+ The "%" starts a conversion specification. The following
+ arguments appear in sequence:
+
+ % [pos-argument] [flags] [field-width] [.precision] type
+
+ pos-argument
+ At most one positional argument specifier. These
+ take the form {n$}, where n is >= 1.
+
+ flags
+ Zero or more of the following flags:
+
+ # The value should be converted to an "alternate
+ form". For c, d, and s conversions, this option
+ has no effect. For o conversions, the precision
+ of the number is increased to force the first
+ character of the output string to a zero (except
+ if a zero value is printed with an explicit
+ precision of zero).
+ For x and X conversions, a non-zero result has
+ the string "0x" (or "0X" for X conversions)
+ prepended to it.
+
+ 0 (zero) Zero padding. For all conversions the converted
+ value is padded on the left with zeros rather
+ than blanks. If a precision is given with a
+ numeric conversion (d, o, x, and X), the 0 flag
+ is ignored.
+
+ - A negative field width flag; the converted value
+ is to be left adjusted on the field boundary.
+ The converted value is padded on the right with
+ blanks, rather than on the left with blanks or
+ zeros. A - overrides a 0 if both are given.
+
+ ' ' (space) A blank should be left before a positive
+ number produced by a signed conversion (d).
+
+ + A sign must always be placed before a number
+ produced by a signed conversion. A + overrides
+ a space if both are used.
+
+ field-width
+ An optional decimal digit string specifying a minimum
+ field width. If the converted value has fewer bytes
+ than the field width, it will be padded with spaces on
+ the left (or right, if the left-adjustment flag has
+ been given) to fill out the field width. For the S
+ conversion the count is in cells.
+
+ .precision
+ An optional precision, in the form of a period '.'
+ followed by an optional digit string. If the digit
+ string is omitted, the precision is taken as zero.
+ This gives the minimum number of digits to appear for
+ d, o, x, and X conversions, the maximum number of
+ bytes to be printed from a string for s conversions,
+ or the maximum number of cells to be printed from a
+ string for S conversions.
+ For floating point it is the number of digits after
+ the decimal point.
+
+ type
+ A character that specifies the type of conversion to
+ be applied, see below.
+
+ A field width or precision, or both, may be indicated by an
+ asterisk "*" instead of a digit string. In this case, a
+ Number argument supplies the field width or precision. A
+ negative field width is treated as a left adjustment flag
+ followed by a positive field width; a negative precision is
+ treated as though it were missing. Example: >vim
+ echo printf("%d: %.*s", nr, width, line)
+ <This limits the length of the text used from "line" to
+ "width" bytes.
+
+ If the argument to be formatted is specified using a posional
+ argument specifier, and a '*' is used to indicate that a
+ number argument is to be used to specify the width or
+ precision, the argument(s) to be used must also be specified
+ using a {n$} positional argument specifier. See |printf-$|.
+
+ The conversion specifiers and their meanings are:
+
+ *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X*
+ dbBoxX The Number argument is converted to signed decimal (d),
+ unsigned binary (b and B), unsigned octal (o), or
+ unsigned hexadecimal (x and X) notation. The letters
+ "abcdef" are used for x conversions; the letters
+ "ABCDEF" are used for X conversions. The precision, if
+ any, gives the minimum number of digits that must
+ appear; if the converted value requires fewer digits, it
+ is padded on the left with zeros. In no case does a
+ non-existent or small field width cause truncation of a
+ numeric field; if the result of a conversion is wider
+ than the field width, the field is expanded to contain
+ the conversion result.
+ The 'h' modifier indicates the argument is 16 bits.
+ The 'l' modifier indicates the argument is a long
+ integer. The size will be 32 bits or 64 bits
+ depending on your platform.
+ The "ll" modifier indicates the argument is 64 bits.
+ The b and B conversion specifiers never take a width
+ modifier and always assume their argument is a 64 bit
+ integer.
+ Generally, these modifiers are not useful. They are
+ ignored when type is known from the argument.
+
+ i alias for d
+ D alias for ld
+ U alias for lu
+ O alias for lo
+
+ *printf-c*
+ c The Number argument is converted to a byte, and the
+ resulting character is written.
+
+ *printf-s*
+ s The text of the String argument is used. If a
+ precision is specified, no more bytes than the number
+ specified are used.
+ If the argument is not a String type, it is
+ automatically converted to text with the same format
+ as ":echo".
+ *printf-S*
+ S The text of the String argument is used. If a
+ precision is specified, no more display cells than the
+ number specified are used.
+
+ *printf-f* *E807*
+ f F The Float argument is converted into a string of the
+ form 123.456. The precision specifies the number of
+ digits after the decimal point. When the precision is
+ zero the decimal point is omitted. When the precision
+ is not specified 6 is used. A really big number
+ (out of range or dividing by zero) results in "inf"
+ or "-inf" with %f (INF or -INF with %F).
+ "0.0 / 0.0" results in "nan" with %f (NAN with %F).
+ Example: >vim
+ echo printf("%.2f", 12.115)
+ < 12.12
+ Note that roundoff depends on the system libraries.
+ Use |round()| when in doubt.
+
+ *printf-e* *printf-E*
+ e E The Float argument is converted into a string of the
+ form 1.234e+03 or 1.234E+03 when using 'E'. The
+ precision specifies the number of digits after the
+ decimal point, like with 'f'.
+
+ *printf-g* *printf-G*
+ g G The Float argument is converted like with 'f' if the
+ value is between 0.001 (inclusive) and 10000000.0
+ (exclusive). Otherwise 'e' is used for 'g' and 'E'
+ for 'G'. When no precision is specified superfluous
+ zeroes and '+' signs are removed, except for the zero
+ immediately after the decimal point. Thus 10000000.0
+ results in 1.0e7.
+
+ *printf-%*
+ % A '%' is written. No argument is converted. The
+ complete conversion specification is "%%".
+
+ When a Number argument is expected a String argument is also
+ accepted and automatically converted.
+ When a Float or String argument is expected a Number argument
+ is also accepted and automatically converted.
+ Any other argument type results in an error message.
+
+ *E766* *E767*
+ The number of {exprN} arguments must exactly match the number
+ of "%" items. If there are not sufficient or too many
+ arguments an error is given. Up to 18 arguments can be used.
+
+ *printf-$*
+ In certain languages, error and informative messages are
+ more readable when the order of words is different from the
+ corresponding message in English. To accommodate translations
+ having a different word order, positional arguments may be
+ used to indicate this. For instance: >vim
+
+ #, c-format
+ msgid "%s returning %s"
+ msgstr "waarde %2$s komt terug van %1$s"
+ <
+ In this example, the sentence has its 2 string arguments
+ reversed in the output. >vim
+
+ echo printf(
+ "In The Netherlands, vim's creator's name is: %1$s %2$s",
+ "Bram", "Moolenaar")
+ < In The Netherlands, vim's creator's name is: Bram Moolenaar >vim
+
+ echo printf(
+ "In Belgium, vim's creator's name is: %2$s %1$s",
+ "Bram", "Moolenaar")
+ < In Belgium, vim's creator's name is: Moolenaar Bram
+
+ Width (and precision) can be specified using the '*' specifier.
+ In this case, you must specify the field width position in the
+ argument list. >vim
+
+ echo printf("%1$*2$.*3$d", 1, 2, 3)
+ < 001 >vim
+ echo printf("%2$*3$.*1$d", 1, 2, 3)
+ < 2 >vim
+ echo printf("%3$*1$.*2$d", 1, 2, 3)
+ < 03 >vim
+ echo printf("%1$*2$.*3$g", 1.4142, 2, 3)
+ < 1.414
+
+ You can mix specifying the width and/or precision directly
+ and via positional arguments: >vim
+
+ echo printf("%1$4.*2$f", 1.4142135, 6)
+ < 1.414214 >vim
+ echo printf("%1$*2$.4f", 1.4142135, 6)
+ < 1.4142 >vim
+ echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
+ < 1.41
+
+ *E1500*
+ You cannot mix positional and non-positional arguments: >vim
+ echo printf("%s%1$s", "One", "Two")
+ < E1500: Cannot mix positional and non-positional arguments:
+ %s%1$s
+
+ *E1501*
+ You cannot skip a positional argument in a format string: >vim
+ echo printf("%3$s%1$s", "One", "Two", "Three")
+ < E1501: format argument 2 unused in $-style format:
+ %3$s%1$s
+
+ *E1502*
+ You can re-use a [field-width] (or [precision]) argument: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$d", 1, 2)
+ < 1 at width 2 is: 01
+
+ However, you can't use it as a different type: >vim
+ echo printf("%1$d at width %2$ld is: %01$*2$d", 1, 2)
+ < E1502: Positional argument 2 used as field width reused as
+ different type: long int/int
+
+ *E1503*
+ When a positional argument is used, but not the correct number
+ or arguments is given, an error is raised: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.*3$d", 1, 2)
+ < E1503: Positional argument 3 out of bounds: %1$d at width
+ %2$d is: %01$*2$.*3$d
+
+ Only the first error is reported: >vim
+ echo printf("%01$*2$.*3$d %4$d", 1, 2)
+ < E1503: Positional argument 3 out of bounds: %01$*2$.*3$d
+ %4$d
+
+ *E1504*
+ A positional argument can be used more than once: >vim
+ echo printf("%1$s %2$s %1$s", "One", "Two")
+ < One Two One
+
+ However, you can't use a different type the second time: >vim
+ echo printf("%1$s %2$s %1$d", "One", "Two")
+ < E1504: Positional argument 1 type used inconsistently:
+ int/string
+
+ *E1505*
+ Various other errors that lead to a format string being
+ wrongly formatted lead to: >vim
+ echo printf("%1$d at width %2$d is: %01$*2$.3$d", 1, 2)
+ < E1505: Invalid format specifier: %1$d at width %2$d is:
+ %01$*2$.3$d
+
+ *E1507*
+ This internal error indicates that the logic to parse a
+ positional format argument ran into a problem that couldn't be
+ otherwise reported. Please file a bug against Vim if you run
+ into this, copying the exact format string and parameters that
+ were used.
+
+ ]=],
+ name = 'printf',
+ params = { { 'fmt', 'any' }, { 'expr1', 'any' } },
+ signature = 'printf({fmt}, {expr1} ...)',
+ },
+ prompt_getprompt = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns the effective prompt text for buffer {buf}. {buf} can
+ be a buffer name or number. See |prompt-buffer|.
+
+ If the buffer doesn't exist or isn't a prompt buffer, an empty
+ string is returned.
+
+ ]=],
+ name = 'prompt_getprompt',
+ params = { { 'buf', 'any' } },
+ signature = 'prompt_getprompt({buf})',
+ },
+ prompt_setcallback = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set prompt callback for buffer {buf} to {expr}. When {expr}
+ is an empty string the callback is removed. This has only
+ effect if {buf} has 'buftype' set to "prompt".
+
+ The callback is invoked when pressing Enter. The current
+ buffer will always be the prompt buffer. A new line for a
+ prompt is added before invoking the callback, thus the prompt
+ for which the callback was invoked will be in the last but one
+ line.
+ If the callback wants to add text to the buffer, it must
+ insert it above the last line, since that is where the current
+ prompt is. This can also be done asynchronously.
+ The callback is invoked with one argument, which is the text
+ that was entered at the prompt. This can be an empty string
+ if the user only typed Enter.
+ Example: >vim
+ func s:TextEntered(text)
+ if a:text == 'exit' || a:text == 'quit'
+ stopinsert
+ " Reset 'modified' to allow the buffer to be closed.
+ " We assume there is nothing useful to be saved.
+ set nomodified
+ close
+ else
+ " Do something useful with "a:text". In this example
+ " we just repeat it.
+ call append(line('$') - 1, 'Entered: "' .. a:text .. '"')
+ endif
+ endfunc
+ call prompt_setcallback(bufnr(), function('s:TextEntered'))
+
+ ]=],
+ name = 'prompt_setcallback',
+ params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ signature = 'prompt_setcallback({buf}, {expr})',
+ },
+ prompt_setinterrupt = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set a callback for buffer {buf} to {expr}. When {expr} is an
+ empty string the callback is removed. This has only effect if
+ {buf} has 'buftype' set to "prompt".
+
+ This callback will be invoked when pressing CTRL-C in Insert
+ mode. Without setting a callback Vim will exit Insert mode,
+ as in any buffer.
+
+ ]=],
+ name = 'prompt_setinterrupt',
+ params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ signature = 'prompt_setinterrupt({buf}, {expr})',
+ },
+ prompt_setprompt = {
+ args = { 2, 2 },
+ base = 1,
+ desc = [=[
+ Set prompt for buffer {buf} to {text}. You most likely want
+ {text} to end in a space.
+ The result is only visible if {buf} has 'buftype' set to
+ "prompt". Example: >vim
+ call prompt_setprompt(bufnr(''), 'command: ')
+ <
+ ]=],
+ name = 'prompt_setprompt',
+ params = { { 'buf', 'any' }, { 'text', 'any' } },
+ signature = 'prompt_setprompt({buf}, {text})',
+ },
+ pum_getpos = {
+ desc = [=[
+ If the popup menu (see |ins-completion-menu|) is not visible,
+ returns an empty |Dictionary|, otherwise, returns a
+ |Dictionary| with the following keys:
+ height nr of items visible
+ width screen cells
+ row top screen row (0 first row)
+ col leftmost screen column (0 first col)
+ size total nr of items
+ scrollbar |TRUE| if scrollbar is visible
+
+ The values are the same as in |v:event| during |CompleteChanged|.
+ ]=],
+ name = 'pum_getpos',
+ params = {},
+ signature = 'pum_getpos()',
+ },
+ pumvisible = {
+ desc = [=[
+ Returns non-zero when the popup menu is visible, zero
+ otherwise. See |ins-completion-menu|.
+ This can be used to avoid some things that would remove the
+ popup menu.
+ ]=],
+ name = 'pumvisible',
+ params = {},
+ signature = 'pumvisible()',
+ },
+ py3eval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though, Unicode strings are additionally converted to
+ UTF-8).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type with
+ keys converted to strings.
+
+ ]=],
+ name = 'py3eval',
+ params = { { 'expr', 'any' } },
+ signature = 'py3eval({expr})',
+ },
+ pyeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers and strings are returned as they are (strings are
+ copied though).
+ Lists are represented as Vim |List| type.
+ Dictionaries are represented as Vim |Dictionary| type,
+ non-string keys result in error.
+
+ ]=],
+ func = 'f_py3eval',
+ name = 'pyeval',
+ params = { { 'expr', 'any' } },
+ signature = 'pyeval({expr})',
+ tags = { 'E858', 'E859' },
+ },
+ pyxeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Python expression {expr} and return its result
+ converted to Vim data structures.
+ Uses Python 2 or 3, see |python_x| and 'pyxversion'.
+ See also: |pyeval()|, |py3eval()|
+
+ ]=],
+ func = 'f_py3eval',
+ name = 'pyxeval',
+ params = { { 'expr', 'any' } },
+ signature = 'pyxeval({expr})',
+ },
+ rand = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ 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.
+ Returns -1 if {expr} is invalid.
+
+ Examples: >vim
+ echo rand()
+ let seed = srand()
+ echo rand(seed)
+ echo rand(seed) % 16 " random number 0 - 15
+ <
+ ]=],
+ name = 'rand',
+ params = { { 'expr', 'any' } },
+ signature = 'rand([{expr}])',
+ },
+ range = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Returns a |List| with Numbers:
+ - If only {expr} is specified: [0, 1, ..., {expr} - 1]
+ - If {max} is specified: [{expr}, {expr} + 1, ..., {max}]
+ - If {stride} is specified: [{expr}, {expr} + {stride}, ...,
+ {max}] (increasing {expr} with {stride} each time, not
+ producing a value past {max}).
+ When the maximum is one before the start the result is an
+ empty list. When the maximum is more than one before the
+ start this is an error.
+ Examples: >vim
+ echo range(4) " [0, 1, 2, 3]
+ echo range(2, 4) " [2, 3, 4]
+ echo range(2, 9, 3) " [2, 5, 8]
+ echo range(2, -2, -1) " [2, 1, 0, -1, -2]
+ echo range(0) " []
+ echo range(2, 0) " error!
+ <
+ ]=],
+ name = 'range',
+ params = { { 'expr', 'any' }, { 'max', 'any' }, { 'stride', 'any' } },
+ signature = 'range({expr} [, {max} [, {stride}]])',
+ tags = { 'E726', 'E727' },
+ },
+ readblob = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Read file {fname} in binary mode and return a |Blob|.
+ If {offset} is specified, read the file from the specified
+ offset. If it is a negative value, it is used as an offset
+ from the end of the file. E.g., to read the last 12 bytes: >vim
+ echo readblob('file.bin', -12)
+ <If {size} is specified, only the specified size will be read.
+ E.g. to read the first 100 bytes of a file: >vim
+ echo readblob('file.bin', 0, 100)
+ <If {size} is -1 or omitted, the whole data starting from
+ {offset} will be read.
+ This can be also used to read the data from a character device
+ on Unix when {size} is explicitly set. Only if the device
+ supports seeking {offset} can be used. Otherwise it should be
+ zero. E.g. to read 10 bytes from a serial console: >vim
+ echo readblob('/dev/ttyS0', 0, 10)
+ <When the file can't be opened an error message is given and
+ the result is an empty |Blob|.
+ When the offset is beyond the end of the file the result is an
+ empty blob.
+ When trying to read more bytes than are available the result
+ is truncated.
+ Also see |readfile()| and |writefile()|.
+ ]=],
+ name = 'readblob',
+ params = { { 'fname', 'string' }, { 'offset', 'any' }, { 'size', 'any' } },
+ signature = 'readblob({fname} [, {offset} [, {size}]])',
+ },
+ readdir = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a list with file and directory names in {directory}.
+ You can also use |glob()| if you don't need to do complicated
+ things, such as limiting the number of matches.
+
+ When {expr} is omitted all entries are included.
+ When {expr} is given, it is evaluated to check what to do:
+ If {expr} results in -1 then no further entries will
+ be handled.
+ If {expr} results in 0 then this entry will not be
+ added to the list.
+ If {expr} results in 1 then this entry will be added
+ to the list.
+ Each time {expr} is evaluated |v:val| is set to the entry name.
+ When {expr} is a function the name is passed as the argument.
+ For example, to get a list of files ending in ".txt": >vim
+ echo readdir(dirname, {n -> n =~ '.txt$'})
+ <To skip hidden and backup files: >vim
+ echo readdir(dirname, {n -> n !~ '^\.\|\~$'})
+
+ <If you want to get a directory tree: >vim
+ function! s:tree(dir)
+ return {a:dir : map(readdir(a:dir),
+ \ {_, x -> isdirectory(x) ?
+ \ {x : s:tree(a:dir .. '/' .. x)} : x})}
+ endfunction
+ echo s:tree(".")
+ <
+ Returns an empty List on error.
+
+ ]=],
+ name = 'readdir',
+ params = { { 'directory', 'any' }, { 'expr', 'any' } },
+ signature = 'readdir({directory} [, {expr}])',
+ },
+ readfile = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Read file {fname} and return a |List|, each line of the file
+ as an item. Lines are broken at NL characters. Macintosh
+ files separated with CR will result in a single long line
+ (unless a NL appears somewhere).
+ All NUL characters are replaced with a NL character.
+ When {type} contains "b" binary mode is used:
+ - When the last line ends in a NL an extra empty list item is
+ added.
+ - No CR characters are removed.
+ Otherwise:
+ - CR characters that appear before a NL are removed.
+ - Whether the last line ends in a NL or not does not matter.
+ - Any UTF-8 byte order mark is removed from the text.
+ When {max} is given this specifies the maximum number of lines
+ to be read. Useful if you only want to check the first ten
+ lines of a file: >vim
+ for line in readfile(fname, '', 10)
+ if line =~ 'Date' | echo line | endif
+ endfor
+ <When {max} is negative -{max} lines from the end of the file
+ are returned, or as many as there are.
+ When {max} is zero the result is an empty list.
+ Note that without {max} the whole file is read into memory.
+ Also note that there is no recognition of encoding. Read a
+ file into a buffer if you need to.
+ Deprecated (use |readblob()| instead): When {type} contains
+ "B" a |Blob| is returned with the binary data of the file
+ unmodified.
+ When the file can't be opened an error message is given and
+ the result is an empty list.
+ Also see |writefile()|.
+
+ ]=],
+ name = 'readfile',
+ params = { { 'fname', 'string' }, { 'type', 'any' }, { 'max', 'any' } },
+ signature = 'readfile({fname} [, {type} [, {max}]])',
+ },
+ reduce = {
+ args = { 2, 3 },
+ base = 1,
+ tags = { 'E998' },
+ desc = [=[
+ {func} is called for every item in {object}, which can be a
+ |String|, |List| or a |Blob|. {func} is called with two
+ arguments: the result so far and current item. After
+ processing all items the result is returned.
+
+ {initial} is the initial result. When omitted, the first item
+ in {object} is used and {func} is first called for the second
+ item. If {initial} is not given and {object} is empty no
+ result can be computed, an E998 error is given.
+
+ Examples: >vim
+ echo reduce([1, 3, 5], { acc, val -> acc + val })
+ echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+ echo reduce(0z1122, { acc, val -> 2 * acc + val })
+ echo reduce('xyz', { acc, val -> acc .. ',' .. val })
+ <
+ ]=],
+ name = 'reduce',
+ params = { { 'object', 'any' }, { 'func', 'any' }, { 'initial', 'any' } },
+ signature = 'reduce({object}, {func} [, {initial}])',
+ },
+ reg_executing = {
+ desc = [=[
+ Returns the single letter name of the register being executed.
+ Returns an empty string when no register is being executed.
+ See |@|.
+ ]=],
+ name = 'reg_executing',
+ params = {},
+ signature = 'reg_executing()',
+ },
+ reg_recorded = {
+ desc = [=[
+ Returns the single letter name of the last recorded register.
+ Returns an empty string when nothing was recorded yet.
+ See |q| and |Q|.
+ ]=],
+ name = 'reg_recorded',
+ params = {},
+ signature = 'reg_recorded()',
+ },
+ reg_recording = {
+ desc = [=[
+ Returns the single letter name of the register being recorded.
+ Returns an empty string when not recording. See |q|.
+ ]=],
+ name = 'reg_recording',
+ params = {},
+ signature = 'reg_recording()',
+ },
+ reltime = {
+ args = { 0, 2 },
+ base = 1,
+ fast = true,
+ name = 'reltime',
+ params = {},
+ signature = 'reltime()',
+ },
+ reltime__1 = {
+ args = { 0, 2 },
+ base = 1,
+ fast = true,
+ name = 'reltime',
+ params = { { 'start', 'any' } },
+ signature = 'reltime({start})',
+ },
+ reltime__2 = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Return an item that represents a time value. The item is a
+ list with items that depend on the system.
+ The item can be passed to |reltimestr()| to convert it to a
+ string or |reltimefloat()| to convert to a Float.
+
+ Without an argument it returns the current "relative time", an
+ implementation-defined value meaningful only when used as an
+ argument to |reltime()|, |reltimestr()| and |reltimefloat()|.
+
+ With one argument it returns the time passed since the time
+ specified in the argument.
+ With two arguments it returns the time passed between {start}
+ and {end}.
+
+ The {start} and {end} arguments must be values returned by
+ reltime(). Returns zero on error.
+
+ Note: |localtime()| returns the current (non-relative) time.
+ ]=],
+ fast = true,
+ name = 'reltime',
+ params = { { 'start', 'any' }, { 'end', 'any' } },
+ signature = 'reltime({start}, {end})',
+ },
+ reltimefloat = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a Float that represents the time value of {time}.
+ Unit of time is seconds.
+ Example:
+ let start = reltime()
+ call MyFunction()
+ let seconds = reltimefloat(reltime(start))
+ See the note of reltimestr() about overhead.
+ Also see |profiling|.
+ If there is an error an empty string is returned
+
+ ]=],
+ fast = true,
+ name = 'reltimefloat',
+ params = { { 'time', 'any' } },
+ signature = 'reltimefloat({time})',
+ },
+ reltimestr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a String that represents the time value of {time}.
+ This is the number of seconds, a dot and the number of
+ microseconds. Example: >vim
+ let start = reltime()
+ call MyFunction()
+ echo reltimestr(reltime(start))
+ <Note that overhead for the commands will be added to the time.
+ Leading spaces are used to make the string align nicely. You
+ can use split() to remove it. >vim
+ echo split(reltimestr(reltime(start)))[0]
+ <Also see |profiling|.
+ If there is an error an empty string is returned
+
+ ]=],
+ fast = true,
+ name = 'reltimestr',
+ params = { { 'time', 'any' } },
+ signature = 'reltimestr({time})',
+ },
+ remove = {
+ args = { 2, 3 },
+ base = 1,
+ name = 'remove',
+ params = { { 'list', 'any' }, { 'idx', 'integer' } },
+ signature = 'remove({list}, {idx})',
+ },
+ remove__1 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Without {end}: Remove the item at {idx} from |List| {list} and
+ return the item.
+ With {end}: Remove items from {idx} to {end} (inclusive) and
+ return a |List| with these items. When {idx} points to the same
+ item as {end} a list with one item is returned. When {end}
+ points to an item before {idx} this is an error.
+ See |list-index| for possible values of {idx} and {end}.
+ Returns zero on error.
+ Example: >vim
+ echo "last item: " .. remove(mylist, -1)
+ call remove(mylist, 0, 9)
+ <
+ Use |delete()| to remove a file.
+
+ ]=],
+ name = 'remove',
+ params = { { 'list', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ signature = 'remove({list}, {idx}, {end})',
+ },
+ remove__2 = {
+ args = { 2, 3 },
+ base = 1,
+ name = 'remove',
+ params = { { 'blob', 'any' }, { 'idx', 'integer' } },
+ signature = 'remove({blob}, {idx})',
+ },
+ remove__3 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Without {end}: Remove the byte at {idx} from |Blob| {blob} and
+ return the byte.
+ With {end}: Remove bytes from {idx} to {end} (inclusive) and
+ return a |Blob| with these bytes. When {idx} points to the same
+ byte as {end} a |Blob| with one byte is returned. When {end}
+ points to a byte before {idx} this is an error.
+ Returns zero on error.
+ Example: >vim
+ echo "last byte: " .. remove(myblob, -1)
+ call remove(mylist, 0, 9)
+ <
+ ]=],
+ name = 'remove',
+ params = { { 'blob', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ signature = 'remove({blob}, {idx}, {end})',
+ },
+ remove__4 = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Remove the entry from {dict} with key {key} and return it.
+ Example: >vim
+ echo "removed " .. remove(dict, "one")
+ <If there is no {key} in {dict} this is an error.
+ Returns zero on error.
+ ]=],
+ name = 'remove',
+ params = { { 'dict', 'any' }, { 'key', 'any' } },
+ signature = 'remove({dict}, {key})',
+ },
+ rename = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Rename the file by the name {from} to the name {to}. This
+ should also work to move files across file systems. The
+ result is a Number, which is 0 if the file was renamed
+ successfully, and non-zero when the renaming failed.
+ NOTE: If {to} exists it is overwritten without warning.
+ This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'rename',
+ params = { { 'from', 'any' }, { 'to', 'any' } },
+ signature = 'rename({from}, {to})',
+ },
+ ['repeat'] = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Repeat {expr} {count} times and return the concatenated
+ result. Example: >vim
+ let separator = repeat('-', 80)
+ <When {count} is zero or negative the result is empty.
+ When {expr} is a |List| or a |Blob| the result is {expr}
+ concatenated {count} times. Example: >vim
+ let longlist = repeat(['a', 'b'], 3)
+ <Results in ['a', 'b', 'a', 'b', 'a', 'b'].
+
+ ]=],
+ fast = true,
+ name = 'repeat',
+ params = { { 'expr', 'any' }, { 'count', 'any' } },
+ signature = 'repeat({expr}, {count})',
+ },
+ resolve = {
+ args = 1,
+ base = 1,
+ tags = { 'E655' },
+ desc = [=[
+ On MS-Windows, when {filename} is a shortcut (a .lnk file),
+ returns the path the shortcut points to in a simplified form.
+ On Unix, repeat resolving symbolic links in all path
+ components of {filename} and return the simplified result.
+ To cope with link cycles, resolving of symbolic links is
+ stopped after 100 iterations.
+ On other systems, return the simplified {filename}.
+ The simplification step is done as by |simplify()|.
+ resolve() keeps a leading path component specifying the
+ current directory (provided the result is still a relative
+ path name) and also keeps a trailing path separator.
+
+ ]=],
+ fast = true,
+ name = 'resolve',
+ params = { { 'filename', 'any' } },
+ signature = 'resolve({filename})',
+ },
+ reverse = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Reverse the order of items in {object}. {object} can be a
+ |List|, a |Blob| or a |String|. For a List and a Blob the
+ items are reversed in-place and {object} is returned.
+ For a String a new String is returned.
+ Returns zero if {object} is not a List, Blob or a String.
+ If you want a List or Blob to remain unmodified make a copy
+ first: >vim
+ let revlist = reverse(copy(mylist))
+ <
+ ]=],
+ name = 'reverse',
+ params = { { 'object', 'any' } },
+ signature = 'reverse({object})',
+ },
+ round = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Round off {expr} to the nearest integral value and return it
+ as a |Float|. If {expr} lies halfway between two integral
+ values, then use the larger one (away from zero).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo round(0.456)
+ < 0.0 >vim
+ echo round(4.5)
+ < 5.0 >vim
+ echo round(-4.5)
+ < -5.0
+
+ ]=],
+ float_func = 'round',
+ name = 'round',
+ params = { { 'expr', 'any' } },
+ signature = 'round({expr})',
+ },
+ rpcnotify = {
+ args = { 2 },
+ desc = [=[
+ Sends {event} to {channel} via |RPC| and returns immediately.
+ If {channel} is 0, the event is broadcast to all channels.
+ Example: >vim
+ au VimLeave call rpcnotify(0, "leaving")
+ <
+ ]=],
+ name = 'rpcnotify',
+ params = { { 'channel', 'any' }, { 'event', 'any' }, { 'args', 'any' } },
+ signature = 'rpcnotify({channel}, {event} [, {args}...])',
+ },
+ rpcrequest = {
+ args = { 2 },
+ desc = [=[
+ Sends a request to {channel} to invoke {method} via
+ |RPC| and blocks until a response is received.
+ Example: >vim
+ let result = rpcrequest(rpc_chan, "func", 1, 2, 3)
+ <
+ ]=],
+ name = 'rpcrequest',
+ params = { { 'channel', 'any' }, { 'method', 'any' }, { 'args', 'any' } },
+ signature = 'rpcrequest({channel}, {method} [, {args}...])',
+ },
+ rpcstart = {
+ args = { 1, 2 },
+ desc = [=[
+ Deprecated. Replace >vim
+ let id = rpcstart('prog', ['arg1', 'arg2'])
+ <with >vim
+ let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true})
+ <
+ ]=],
+ name = 'rpcstart',
+ params = { { 'prog', 'any' }, { 'argv', 'any' } },
+ signature = 'rpcstart({prog} [, {argv}])',
+ },
+ rpcstop = {
+ args = 1,
+ deprecated = true,
+ desc = [=[
+ Use |jobstop()| instead to stop any job, or
+ `chanclose(id, "rpc")` to close RPC communication
+ without stopping the job. Use chanclose(id) to close
+ any socket.
+ ]=],
+ params = VARARGS,
+ signature = 'rpcstop(...)',
+ },
+ rubyeval = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Evaluate Ruby expression {expr} and return its result
+ converted to Vim data structures.
+ Numbers, floats and strings are returned as they are (strings
+ are copied though).
+ Arrays are represented as Vim |List| type.
+ Hashes are represented as Vim |Dictionary| type.
+ Other objects are represented as strings resulted from their
+ "Object#to_s" method.
+
+ ]=],
+ name = 'rubyeval',
+ params = { { 'expr', 'any' } },
+ signature = 'rubyeval({expr})',
+ },
+ screenattr = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Like |screenchar()|, but return the attribute. This is a rather
+ arbitrary number that can only be used to compare to the
+ attribute at other positions.
+ Returns -1 when row or col is out of range.
+
+ ]=],
+ name = 'screenattr',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenattr({row}, {col})',
+ },
+ screenchar = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the character at position
+ [row, col] on the screen. This works for every possible
+ screen position, also status lines, window separators and the
+ command line. The top left position is row one, column one
+ The character excludes composing characters. For double-byte
+ encodings it may only be the first byte.
+ This is mainly to be used for testing.
+ Returns -1 when row or col is out of range.
+
+ ]=],
+ name = 'screenchar',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenchar({row}, {col})',
+ },
+ screenchars = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a |List| of Numbers. The first number is the same
+ as what |screenchar()| returns. Further numbers are
+ composing characters on top of the base character.
+ This is mainly to be used for testing.
+ Returns an empty List when row or col is out of range.
+
+ ]=],
+ name = 'screenchars',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenchars({row}, {col})',
+ },
+ screencol = {
+ desc = [=[
+ The result is a Number, which is the current screen column of
+ the cursor. The leftmost column has number 1.
+ This function is mainly used for testing.
+
+ Note: Always returns the current screen column, thus if used
+ in a command (e.g. ":echo screencol()") it will return the
+ column inside the command line, which is 1 when the command is
+ executed. To get the cursor position in the file use one of
+ the following mappings: >vim
+ nnoremap <expr> GG ":echom " .. screencol() .. "\n"
+ nnoremap <silent> GG :echom screencol()<CR>
+ noremap GG <Cmd>echom screencol()<Cr>
+ <
+ ]=],
+ name = 'screencol',
+ params = {},
+ signature = 'screencol()',
+ },
+ screenpos = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a Dict with the screen position of the text
+ character in window {winid} at buffer line {lnum} and column
+ {col}. {col} is a one-based byte index.
+ The Dict has these members:
+ row screen row
+ col first screen column
+ endcol last screen column
+ curscol cursor screen column
+ If the specified position is not visible, all values are zero.
+ The "endcol" value differs from "col" when the character
+ occupies more than one screen cell. E.g. for a Tab "col" can
+ be 1 and "endcol" can be 8.
+ The "curscol" value is where the cursor would be placed. For
+ a Tab it would be the same as "endcol", while for a double
+ width character it would be the same as "col".
+ The |conceal| feature is ignored here, the column numbers are
+ as if 'conceallevel' is zero. You can set the cursor to the
+ right position and use |screencol()| to get the value with
+ |conceal| taken into account.
+ If the position is in a closed fold the screen position of the
+ first character is returned, {col} is not used.
+ Returns an empty Dict if {winid} is invalid.
+
+ ]=],
+ name = 'screenpos',
+ params = { { 'winid', 'integer' }, { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'screenpos({winid}, {lnum}, {col})',
+ },
+ screenrow = {
+ desc = [=[
+ The result is a Number, which is the current screen row of the
+ cursor. The top line has number one.
+ This function is mainly used for testing.
+ Alternatively you can use |winline()|.
+
+ Note: Same restrictions as with |screencol()|.
+ ]=],
+ name = 'screenrow',
+ params = {},
+ signature = 'screenrow()',
+ },
+ screenstring = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a String that contains the base character and
+ any composing characters at position [row, col] on the screen.
+ This is like |screenchars()| but returning a String with the
+ characters.
+ This is mainly to be used for testing.
+ Returns an empty String when row or col is out of range.
+
+ ]=],
+ name = 'screenstring',
+ params = { { 'row', 'any' }, { 'col', 'integer' } },
+ signature = 'screenstring({row}, {col})',
+ },
+ search = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Search for regexp pattern {pattern}. The search starts at the
+ cursor position (you can use |cursor()| to set it).
+
+ When a match has been found its line number is returned.
+ If there is no match a 0 is returned and the cursor doesn't
+ move. No error message is given.
+
+ {flags} is a String, which can contain these character flags:
+ 'b' search Backward instead of forward
+ 'c' accept a match at the Cursor position
+ 'e' move to the End of the match
+ 'n' do Not move the cursor
+ 'p' return number of matching sub-Pattern (see below)
+ 's' Set the ' mark at the previous location of the cursor
+ 'w' Wrap around the end of the file
+ 'W' don't Wrap around the end of the file
+ 'z' start searching at the cursor column instead of Zero
+ If neither 'w' or 'W' is given, the 'wrapscan' option applies.
+
+ If the 's' flag is supplied, the ' mark is set, only if the
+ cursor is moved. The 's' flag cannot be combined with the 'n'
+ flag.
+
+ 'ignorecase', 'smartcase' and 'magic' are used.
+
+ When the 'z' flag is not given, forward searching always
+ starts in column zero and then matches before the cursor are
+ skipped. When the 'c' flag is present in 'cpo' the next
+ search starts after the match. Without the 'c' flag the next
+ search starts one column after the start of the match. This
+ matters for overlapping matches. See |cpo-c|. You can also
+ insert "\ze" to change where the match ends, see |/\ze|.
+
+ When searching backwards and the 'z' flag is given then the
+ search starts in column zero, thus no match in the current
+ line will be found (unless wrapping around the end of the
+ file).
+
+ When the {stopline} argument is given then the search stops
+ after searching this line. This is useful to restrict the
+ search to a range of lines. Examples: >vim
+ let match = search('(', 'b', line("w0"))
+ let end = search('END', '', line("w$"))
+ <When {stopline} is used and it is not zero this also implies
+ that the search does not wrap around the end of the file.
+ A zero value is equal to not giving the argument.
+
+ When the {timeout} argument is given the search stops when
+ more than this many milliseconds have passed. Thus when
+ {timeout} is 500 the search stops after half a second.
+ The value must not be negative. A zero value is like not
+ giving the argument.
+
+ If the {skip} expression is given it is evaluated with the
+ cursor positioned on the start of a match. If it evaluates to
+ non-zero this match is skipped. This can be used, for
+ example, to skip a match in a comment or a string.
+ {skip} can be a string, which is evaluated as an expression, a
+ function reference or a lambda.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
+ *search()-sub-match*
+ With the 'p' flag the returned value is one more than the
+ first sub-match in \(\). One if none of them matched but the
+ whole pattern did match.
+ To get the column number too use |searchpos()|.
+
+ The cursor will be positioned at the match, unless the 'n'
+ flag is used.
+
+ Example (goes over all files in the argument list): >vim
+ let n = 1
+ while n <= argc() " loop over all files in arglist
+ exe "argument " .. n
+ " start at the last char in the file and wrap for the
+ " first search to find match at start of file
+ normal G$
+ let flags = "w"
+ while search("foo", flags) > 0
+ s/foo/bar/g
+ let flags = "W"
+ endwhile
+ update " write the file if modified
+ let n = n + 1
+ endwhile
+ <
+ Example for using some flags: >vim
+ echo search('\<if\|\(else\)\|\(endif\)', 'ncpe')
+ <This will search for the keywords "if", "else", and "endif"
+ under or after the cursor. Because of the 'p' flag, it
+ returns 1, 2, or 3 depending on which keyword is found, or 0
+ if the search fails. With the cursor on the first word of the
+ line:
+ if (foo == 0) | let foo = foo + 1 | endif ~
+ the function returns 1. Without the 'c' flag, the function
+ finds the "endif" and returns 3. The same thing happens
+ without the 'e' flag if the cursor is on the "f" of "if".
+ The 'n' flag tells the function not to move the cursor.
+
+ ]=],
+ name = 'search',
+ params = {
+ { 'pattern', 'any' },
+ { 'flags', 'string' },
+ { 'stopline', 'any' },
+ { 'timeout', 'integer' },
+ { 'skip', 'any' },
+ },
+ signature = 'search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
+ },
+ searchcount = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get or update the last search count, like what is displayed
+ without the "S" flag in 'shortmess'. This works even if
+ 'shortmess' does contain the "S" flag.
+
+ This returns a |Dictionary|. The dictionary is empty if the
+ previous pattern was not set and "pattern" was not specified.
+
+ key type meaning ~
+ current |Number| current position of match;
+ 0 if the cursor position is
+ before the first match
+ exact_match |Boolean| 1 if "current" is matched on
+ "pos", otherwise 0
+ total |Number| total count of matches found
+ incomplete |Number| 0: search was fully completed
+ 1: recomputing was timed out
+ 2: max count exceeded
+
+ For {options} see further down.
+
+ To get the last search count when |n| or |N| was pressed, call
+ this function with `recompute: 0` . This sometimes returns
+ wrong information because |n| and |N|'s maximum count is 99.
+ If it exceeded 99 the result must be max count + 1 (100). If
+ you want to get correct information, specify `recompute: 1`: >vim
+
+ " result == maxcount + 1 (100) when many matches
+ let result = searchcount(#{recompute: 0})
+
+ " Below returns correct result (recompute defaults
+ " to 1)
+ let result = searchcount()
+ <
+ The function is useful to add the count to 'statusline': >vim
+ function! LastSearchCount() abort
+ let result = searchcount(#{recompute: 0})
+ if empty(result)
+ return ''
+ endif
+ if result.incomplete ==# 1 " timed out
+ return printf(' /%s [?/??]', @/)
+ elseif result.incomplete ==# 2 " max count exceeded
+ if result.total > result.maxcount &&
+ \ result.current > result.maxcount
+ return printf(' /%s [>%d/>%d]', @/,
+ \ result.current, result.total)
+ elseif result.total > result.maxcount
+ return printf(' /%s [%d/>%d]', @/,
+ \ result.current, result.total)
+ endif
+ endif
+ return printf(' /%s [%d/%d]', @/,
+ \ result.current, result.total)
+ endfunction
+ let &statusline ..= '%{LastSearchCount()}'
+
+ " Or if you want to show the count only when
+ " 'hlsearch' was on
+ " let &statusline ..=
+ " \ '%{v:hlsearch ? LastSearchCount() : ""}'
+ <
+ You can also update the search count, which can be useful in a
+ |CursorMoved| or |CursorMovedI| autocommand: >vim
+
+ autocmd CursorMoved,CursorMovedI *
+ \ let s:searchcount_timer = timer_start(
+ \ 200, function('s:update_searchcount'))
+ function! s:update_searchcount(timer) abort
+ if a:timer ==# s:searchcount_timer
+ call searchcount(#{
+ \ recompute: 1, maxcount: 0, timeout: 100})
+ redrawstatus
+ endif
+ endfunction
+ <
+ This can also be used to count matched texts with specified
+ pattern in the current buffer using "pattern": >vim
+
+ " Count '\<foo\>' in this buffer
+ " (Note that it also updates search count)
+ let result = searchcount(#{pattern: '\<foo\>'})
+
+ " To restore old search count by old pattern,
+ " search again
+ call searchcount()
+ <
+ {options} must be a |Dictionary|. It can contain:
+ key type meaning ~
+ recompute |Boolean| if |TRUE|, recompute the count
+ like |n| or |N| was executed.
+ otherwise returns the last
+ computed result (when |n| or
+ |N| was used when "S" is not
+ in 'shortmess', or this
+ function was called).
+ (default: |TRUE|)
+ pattern |String| recompute if this was given
+ and different with |@/|.
+ this works as same as the
+ below command is executed
+ before calling this function >vim
+ let @/ = pattern
+ < (default: |@/|)
+ timeout |Number| 0 or negative number is no
+ timeout. timeout milliseconds
+ for recomputing the result
+ (default: 0)
+ maxcount |Number| 0 or negative number is no
+ limit. max count of matched
+ text while recomputing the
+ result. if search exceeded
+ total count, "total" value
+ becomes `maxcount + 1`
+ (default: 0)
+ pos |List| `[lnum, col, off]` value
+ when recomputing the result.
+ this changes "current" result
+ value. see |cursor()|, |getpos()|
+ (default: cursor's position)
+
+ ]=],
+ name = 'searchcount',
+ params = { { 'options', 'table' } },
+ signature = 'searchcount([{options}])',
+ },
+ searchdecl = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Search for the declaration of {name}.
+
+ With a non-zero {global} argument it works like |gD|, find
+ first match in the file. Otherwise it works like |gd|, find
+ first match in the function.
+
+ With a non-zero {thisblock} argument matches in a {} block
+ that ends before the cursor position are ignored. Avoids
+ finding variable declarations only valid in another scope.
+
+ Moves the cursor to the found match.
+ Returns zero for success, non-zero for failure.
+ Example: >vim
+ if searchdecl('myvar') == 0
+ echo getline('.')
+ endif
+ <
+ ]=],
+ name = 'searchdecl',
+ params = { { 'name', 'string' }, { 'global', 'any' }, { 'thisblock', 'any' } },
+ signature = 'searchdecl({name} [, {global} [, {thisblock}]])',
+ },
+ searchpair = {
+ args = { 3, 7 },
+ desc = [=[
+ Search for the match of a nested start-end pair. This can be
+ used to find the "endif" that matches an "if", while other
+ if/endif pairs in between are ignored.
+ The search starts at the cursor. The default is to search
+ forward, include 'b' in {flags} to search backward.
+ If a match is found, the cursor is positioned at it and the
+ line number is returned. If no match is found 0 or -1 is
+ returned and the cursor doesn't move. No error message is
+ given.
+
+ {start}, {middle} and {end} are patterns, see |pattern|. They
+ must not contain \( \) pairs. Use of \%( \) is allowed. When
+ {middle} is not empty, it is found when searching from either
+ direction, but only when not in a nested start-end pair. A
+ typical use is: >vim
+ echo searchpair('\<if\>', '\<else\>', '\<endif\>')
+ <By leaving {middle} empty the "else" is skipped.
+
+ {flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with
+ |search()|. Additionally:
+ 'r' Repeat until no more matches found; will find the
+ outer pair. Implies the 'W' flag.
+ 'm' Return number of matches instead of line number with
+ the match; will be > 1 when 'r' is used.
+ Note: it's nearly always a good idea to use the 'W' flag, to
+ avoid wrapping around the end of the file.
+
+ When a match for {start}, {middle} or {end} is found, the
+ {skip} expression is evaluated with the cursor positioned on
+ the start of the match. It should return non-zero if this
+ match is to be skipped. E.g., because it is inside a comment
+ or a string.
+ When {skip} is omitted or empty, every match is accepted.
+ When evaluating {skip} causes an error the search is aborted
+ and -1 returned.
+ {skip} can be a string, a lambda, a funcref or a partial.
+ Anything else makes the function fail.
+
+ For {stopline} and {timeout} see |search()|.
+
+ The value of 'ignorecase' is used. 'magic' is ignored, the
+ patterns are used like it's on.
+
+ The search starts exactly at the cursor. A match with
+ {start}, {middle} or {end} at the next character, in the
+ direction of searching, is the first one found. Example: >vim
+ if 1
+ if 2
+ endif 2
+ endif 1
+ <When starting at the "if 2", with the cursor on the "i", and
+ searching forwards, the "endif 2" is found. When starting on
+ the character just before the "if 2", the "endif 1" will be
+ found. That's because the "if 2" will be found first, and
+ then this is considered to be a nested if/endif from "if 2" to
+ "endif 2".
+ When searching backwards and {end} is more than one character,
+ it may be useful to put "\zs" at the end of the pattern, so
+ that when the cursor is inside a match with the end it finds
+ the matching start.
+
+ Example, to find the "endif" command in a Vim script: >vim
+
+ echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W',
+ \ 'getline(".") =~ "^\\s*\""')
+
+ <The cursor must be at or after the "if" for which a match is
+ to be found. Note that single-quote strings are used to avoid
+ having to double the backslashes. The skip expression only
+ catches comments at the start of a line, not after a command.
+ Also, a word "en" or "if" halfway through a line is considered
+ a match.
+ Another example, to search for the matching "{" of a "}": >vim
+
+ echo searchpair('{', '', '}', 'bW')
+
+ <This works when the cursor is at or before the "}" for which a
+ match is to be found. To reject matches that syntax
+ highlighting recognized as strings: >vim
+
+ echo searchpair('{', '', '}', 'bW',
+ \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"')
+ <
+ ]=],
+ name = 'searchpair',
+ params = {},
+ signature = 'searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
+ },
+ searchpairpos = {
+ args = { 3, 7 },
+ desc = [=[
+ Same as |searchpair()|, but returns a |List| with the line and
+ column position of the match. The first element of the |List|
+ is the line number and the second element is the byte index of
+ the column position of the match. If no match is found,
+ returns [0, 0]. >vim
+
+ let [lnum,col] = searchpairpos('{', '', '}', 'n')
+ <
+ See |match-parens| for a bigger and more useful example.
+ ]=],
+ name = 'searchpairpos',
+ params = {},
+ signature = 'searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
+ },
+ searchpos = {
+ args = { 1, 5 },
+ base = 1,
+ desc = [=[
+ Same as |search()|, but returns a |List| with the line and
+ column position of the match. The first element of the |List|
+ is the line number and the second element is the byte index of
+ the column position of the match. If no match is found,
+ returns [0, 0].
+ Example: >vim
+ let [lnum, col] = searchpos('mypattern', 'n')
+
+ <When the 'p' flag is given then there is an extra item with
+ the sub-pattern match number |search()-sub-match|. Example: >vim
+ let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np')
+ <In this example "submatch" is 2 when a lowercase letter is
+ found |/\l|, 3 when an uppercase letter is found |/\u|.
+
+ ]=],
+ name = 'searchpos',
+ params = {
+ { 'pattern', 'any' },
+ { 'flags', 'string' },
+ { 'stopline', 'any' },
+ { 'timeout', 'integer' },
+ { 'skip', 'any' },
+ },
+ signature = 'searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
+ },
+ serverlist = {
+ desc = [=[
+ Returns a list of server addresses, or empty if all servers
+ were stopped. |serverstart()| |serverstop()|
+ Example: >vim
+ echo serverlist()
+ <
+ ]=],
+ name = 'serverlist',
+ params = {},
+ signature = 'serverlist()',
+ },
+ serverstart = {
+ args = { 0, 1 },
+ desc = [=[
+ Opens a socket or named pipe at {address} and listens for
+ |RPC| messages. Clients can send |API| commands to the
+ returned address to control Nvim.
+
+ Returns the address string (which may differ from the
+ {address} argument, see below).
+
+ - If {address} has a colon (":") it is a TCP/IPv4/IPv6 address
+ where the last ":" separates host and port (empty or zero
+ assigns a random port).
+ - Else {address} is the path to a named pipe (except on Windows).
+ - If {address} has no slashes ("/") it is treated as the
+ "name" part of a generated path in this format: >vim
+ stdpath("run").."/{name}.{pid}.{counter}"
+ < - If {address} is omitted the name is "nvim". >vim
+ echo serverstart()
+ < >
+ => /tmp/nvim.bram/oknANW/nvim.15430.5
+ <
+ Example bash command to list all Nvim servers: >bash
+ ls ${XDG_RUNTIME_DIR:-${TMPDIR}nvim.${USER}}/*/nvim.*.0
+
+ <Example named pipe: >vim
+ if has('win32')
+ echo serverstart('\\.\pipe\nvim-pipe-1234')
+ else
+ echo serverstart('nvim.sock')
+ endif
+ <
+ Example TCP/IP address: >vim
+ echo serverstart('::1:12345')
+ <
+ ]=],
+ name = 'serverstart',
+ params = { { 'address', 'any' } },
+ signature = 'serverstart([{address}])',
+ },
+ serverstop = {
+ args = 1,
+ desc = [=[
+ Closes the pipe or socket at {address}.
+ Returns TRUE if {address} is valid, else FALSE.
+ If |v:servername| is stopped it is set to the next available
+ address in |serverlist()|.
+ ]=],
+ name = 'serverstop',
+ params = { { 'address', 'any' } },
+ signature = 'serverstop({address})',
+ },
+ setbufline = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set line {lnum} to {text} in buffer {buf}. This works like
+ |setline()| for the specified buffer.
+
+ This function works only for loaded buffers. First call
+ |bufload()| if needed.
+
+ To insert lines use |appendbufline()|.
+
+ {text} can be a string to set one line, or a List of strings
+ to set multiple lines. If the List extends below the last
+ line then those lines are added. If the List is empty then
+ nothing is changed and zero is returned.
+
+ For the use of {buf}, see |bufname()| above.
+
+ {lnum} is used like with |setline()|.
+ Use "$" to refer to the last line in buffer {buf}.
+ When {lnum} is just below the last line the {text} will be
+ added below the last line.
+ On success 0 is returned, on failure 1 is returned.
+
+ If {buf} is not a valid buffer or {lnum} is not valid, an
+ error message is given.
+
+ ]=],
+ name = 'setbufline',
+ params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'any' } },
+ signature = 'setbufline({buf}, {lnum}, {text})',
+ },
+ setbufvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set option or local variable {varname} in buffer {buf} to
+ {val}.
+ This also works for a global or local window option, but it
+ doesn't work for a global or local window variable.
+ For a local window option the global value is unchanged.
+ For the use of {buf}, see |bufname()| above.
+ The {varname} argument is a string.
+ Note that the variable name without "b:" must be used.
+ Examples: >vim
+ call setbufvar(1, "&mod", 1)
+ call setbufvar("todo", "myvar", "foobar")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'setbufvar',
+ params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'setbufvar({buf}, {varname}, {val})',
+ },
+ setcellwidths = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Specify overrides for cell widths of character ranges. This
+ tells Vim how wide characters are when displayed in the
+ terminal, counted in screen cells. The values override
+ 'ambiwidth'. Example: >vim
+ call setcellwidths([
+ \ [0x111, 0x111, 1],
+ \ [0x2194, 0x2199, 2],
+ \ ])
+
+ <The {list} argument is a List of Lists with each three
+ numbers: [{low}, {high}, {width}]. *E1109* *E1110*
+ {low} and {high} can be the same, in which case this refers to
+ one character. Otherwise it is the range of characters from
+ {low} to {high} (inclusive). *E1111* *E1114*
+ Only characters with value 0x80 and higher can be used.
+
+ {width} must be either 1 or 2, indicating the character width
+ in screen cells. *E1112*
+ An error is given if the argument is invalid, also when a
+ range overlaps with another. *E1113*
+
+ If the new value causes 'fillchars' or 'listchars' to become
+ invalid it is rejected and an error is given.
+
+ To clear the overrides pass an empty {list}: >vim
+ call setcellwidths([])
+
+ <You can use the script $VIMRUNTIME/tools/emoji_list.vim to see
+ the effect for known emoji characters. Move the cursor
+ through the text to check if the cell widths of your terminal
+ match with what Vim knows about each emoji. If it doesn't
+ look right you need to adjust the {list} argument.
+ ]=],
+ name = 'setcellwidths',
+ params = { { 'list', 'any' } },
+ signature = 'setcellwidths({list})',
+ },
+ setcharpos = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ 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: >vim
+ call setcharpos('.', [0, 8, 4, 0])
+ <positions the cursor on the fourth character '요'. >vim
+ call setpos('.', [0, 8, 4, 0])
+ <positions the cursor on the second character '보'.
+
+ ]=],
+ name = 'setcharpos',
+ params = { { 'expr', 'any' }, { 'list', 'any' } },
+ signature = 'setcharpos({expr}, {list})',
+ },
+ setcharsearch = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Set the current character search information to {dict},
+ which contains one or more of the following entries:
+
+ char character which will be used for a subsequent
+ |,| or |;| command; an empty string clears the
+ character search
+ forward direction of character search; 1 for forward,
+ 0 for backward
+ until type of character search; 1 for a |t| or |T|
+ character search, 0 for an |f| or |F|
+ character search
+
+ This can be useful to save/restore a user's character search
+ from a script: >vim
+ let prevsearch = getcharsearch()
+ " Perform a command which clobbers user's search
+ call setcharsearch(prevsearch)
+ <Also see |getcharsearch()|.
+
+ ]=],
+ name = 'setcharsearch',
+ params = { { 'dict', 'any' } },
+ signature = 'setcharsearch({dict})',
+ },
+ setcmdline = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Set the command line to {str} and set the cursor position to
+ {pos}.
+ If {pos} is omitted, the cursor is positioned after the text.
+ Returns 0 when successful, 1 when not editing the command
+ line.
+
+ ]=],
+ name = 'setcmdline',
+ params = { { 'str', 'any' }, { 'pos', 'any' } },
+ signature = 'setcmdline({str} [, {pos}])',
+ },
+ setcmdpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Set the cursor position in the command line to byte position
+ {pos}. The first position is 1.
+ Use |getcmdpos()| to obtain the current position.
+ Only works while editing the command line, thus you must use
+ |c_CTRL-\_e|, |c_CTRL-R_=| or |c_CTRL-R_CTRL-R| with '='. For
+ |c_CTRL-\_e| and |c_CTRL-R_CTRL-R| with '=' the position is
+ set after the command line is set to the expression. For
+ |c_CTRL-R_=| it is set after evaluating the expression but
+ before inserting the resulting text.
+ When the number is too big the cursor is put at the end of the
+ line. A number smaller than one has undefined results.
+ Returns 0 when successful, 1 when not editing the command
+ line.
+
+ ]=],
+ name = 'setcmdpos',
+ params = { { 'pos', 'any' } },
+ signature = 'setcmdpos({pos})',
+ },
+ setcursorcharpos = {
+ args = { 1, 3 },
+ base = 1,
+ name = 'setcursorcharpos',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ signature = 'setcursorcharpos({lnum}, {col} [, {off}])',
+ },
+ setcursorcharpos__1 = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ 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: >vim
+ call setcursorcharpos(4, 3)
+ <positions the cursor on the third character '세'. >vim
+ call cursor(4, 3)
+ <positions the cursor on the first character '여'.
+
+ ]=],
+ name = 'setcursorcharpos',
+ params = { { 'list', 'any' } },
+ signature = 'setcursorcharpos({list})',
+ },
+ setenv = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set environment variable {name} to {val}. Example: >vim
+ call setenv('HOME', '/home/myhome')
+
+ <When {val} is |v:null| the environment variable is deleted.
+ See also |expr-env|.
+
+ ]=],
+ name = 'setenv',
+ params = { { 'name', 'string' }, { 'val', 'any' } },
+ signature = 'setenv({name}, {val})',
+ },
+ setfperm = {
+ args = 2,
+ base = 1,
+ tags = { 'chmod' },
+ desc = [=[
+ Set the file permissions for {fname} to {mode}.
+ {mode} must be a string with 9 characters. It is of the form
+ "rwxrwxrwx", where each group of "rwx" flags represent, in
+ turn, the permissions of the owner of the file, the group the
+ file belongs to, and other users. A '-' character means the
+ permission is off, any other character means on. Multi-byte
+ characters are not supported.
+
+ For example "rw-r-----" means read-write for the user,
+ readable by the group, not accessible by others. "xx-x-----"
+ would do the same thing.
+
+ Returns non-zero for success, zero for failure.
+
+ To read permissions see |getfperm()|.
+ ]=],
+ name = 'setfperm',
+ params = { { 'fname', 'string' }, { 'mode', 'string' } },
+ signature = 'setfperm({fname}, {mode})',
+ },
+ setline = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set line {lnum} of the current buffer to {text}. To insert
+ lines use |append()|. To set lines in another buffer use
+ |setbufline()|.
+
+ {lnum} is used like with |getline()|.
+ When {lnum} is just below the last line the {text} will be
+ added below the last line.
+ {text} can be any type or a List of any type, each item is
+ converted to a String. When {text} is an empty List then
+ nothing is changed and FALSE is returned.
+
+ If this succeeds, FALSE is returned. If this fails (most likely
+ because {lnum} is invalid) TRUE is returned.
+
+ Example: >vim
+ call setline(5, strftime("%c"))
+
+ <When {text} is a |List| then line {lnum} and following lines
+ will be set to the items in the list. Example: >vim
+ call setline(5, ['aaa', 'bbb', 'ccc'])
+ <This is equivalent to: >vim
+ for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']]
+ call setline(n, l)
+ endfor
+
+ <Note: The '[ and '] marks are not set.
+
+ ]=],
+ name = 'setline',
+ params = { { 'lnum', 'integer' }, { 'text', 'any' } },
+ signature = 'setline({lnum}, {text})',
+ },
+ setloclist = {
+ args = { 2, 4 },
+ base = 2,
+ desc = [=[
+ Create or replace or add to 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.
+
+ For a location list window, the displayed location list is
+ modified. For an invalid window number {nr}, -1 is returned.
+ Otherwise, same as |setqflist()|.
+ Also see |location-list|.
+
+ For {action} see |setqflist-action|.
+
+ If the optional {what} dictionary argument is supplied, then
+ only the items listed in {what} are set. Refer to |setqflist()|
+ for the list of supported keys in {what}.
+
+ ]=],
+ name = 'setloclist',
+ params = { { 'nr', 'integer' }, { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ signature = 'setloclist({nr}, {list} [, {action} [, {what}]])',
+ },
+ setmatches = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Restores a list of matches saved by |getmatches()| for the
+ current window. Returns 0 if successful, otherwise -1. All
+ current matches are cleared before the list is restored. See
+ example for |getmatches()|.
+ If {win} is specified, use the window with this number or
+ window ID instead of the current window.
+
+ ]=],
+ name = 'setmatches',
+ params = { { 'list', 'any' }, { 'win', 'any' } },
+ signature = 'setmatches({list} [, {win}])',
+ },
+ setpos = {
+ args = 2,
+ base = 2,
+ desc = [=[
+ Set the position for String {expr}. Possible values:
+ . the cursor
+ 'x mark x
+
+ {list} must be a |List| with four or five numbers:
+ [bufnum, lnum, col, off]
+ [bufnum, lnum, col, off, curswant]
+
+ "bufnum" is the buffer number. Zero can be used for the
+ current buffer. When setting an uppercase mark "bufnum" is
+ used for the mark position. For other marks it specifies the
+ buffer to set the mark in. You can use the |bufnr()| function
+ to turn a file name into a buffer number.
+ For setting the cursor and the ' mark "bufnum" is ignored,
+ since these are associated with a window, not a buffer.
+ Does not change the jumplist.
+
+ "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. 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
+ character. E.g., a position within a <Tab> or after the last
+ character.
+
+ The "curswant" number is only used when setting the cursor
+ position. It sets the preferred column for when moving the
+ cursor vertically. When the "curswant" number is missing the
+ preferred column is not set. When it is present and setting a
+ mark position it is not used.
+
+ Note that for '< and '> changing the line number may result in
+ the marks to be effectively be swapped, so that '< is always
+ before '>.
+
+ Returns 0 when the position could be set, -1 otherwise.
+ An error message is given if {expr} is invalid.
+
+ 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
+ |k| motions will jump to previous columns! Use |cursor()| to
+ also set the preferred column. Also see the "curswant" key in
+ |winrestview()|.
+
+ ]=],
+ name = 'setpos',
+ params = { { 'expr', 'any' }, { 'list', 'any' } },
+ signature = 'setpos({expr}, {list})',
+ },
+ setqflist = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Create or replace or add to the quickfix list.
+
+ If the optional {what} dictionary argument is supplied, then
+ only the items listed in {what} are set. The first {list}
+ argument is ignored. See below for the supported items in
+ {what}.
+ *setqflist-what*
+ When {what} is not present, the items in {list} are used. Each
+ item must be a dictionary. Non-dictionary items in {list} are
+ ignored. Each dictionary item can contain the following
+ entries:
+
+ bufnr buffer number; must be the number of a valid
+ buffer
+ filename name of a file; only used when "bufnr" is not
+ present or it is invalid.
+ module name of a module; if given it will be used in
+ quickfix error window instead of the filename.
+ lnum line number in the file
+ end_lnum end of lines, if the item spans multiple lines
+ pattern search pattern used to locate the error
+ col column number
+ vcol when non-zero: "col" is visual column
+ when zero: "col" is byte index
+ end_col end column, if the item spans multiple columns
+ nr error number
+ text description of the error
+ type single-character error type, 'E', 'W', etc.
+ valid recognized error message
+ user_data
+ custom data associated with the item, can be
+ any type.
+
+ The "col", "vcol", "nr", "type" and "text" entries are
+ optional. Either "lnum" or "pattern" entry can be used to
+ locate a matching error line.
+ If the "filename" and "bufnr" entries are not present or
+ neither the "lnum" or "pattern" entries are present, then the
+ item will not be handled as an error line.
+ If both "pattern" and "lnum" are present then "pattern" will
+ be used.
+ If the "valid" entry is not supplied, then the valid flag is
+ set when "bufnr" is a valid buffer or "filename" exists.
+ If you supply an empty {list}, the quickfix list will be
+ cleared.
+ Note that the list is not exactly the same as what
+ |getqflist()| returns.
+
+ {action} values: *setqflist-action* *E927*
+ 'a' The items from {list} are added to the existing
+ quickfix list. If there is no existing list, then a
+ new list is created.
+
+ 'r' The items from the current quickfix list are replaced
+ with the items from {list}. This can also be used to
+ clear the list: >vim
+ call setqflist([], 'r')
+ <
+ 'f' All the quickfix lists in the quickfix stack are
+ freed.
+
+ If {action} is not present or is set to ' ', then a new list
+ is created. The new quickfix list is added after the current
+ quickfix list in the stack and all the following lists are
+ freed. To add a new quickfix list at the end of the stack,
+ set "nr" in {what} to "$".
+
+ The following items can be specified in dictionary {what}:
+ context quickfix list context. See |quickfix-context|
+ efm errorformat to use when parsing text from
+ "lines". If this is not present, then the
+ 'errorformat' option value is used.
+ See |quickfix-parse|
+ id quickfix list identifier |quickfix-ID|
+ idx index of the current entry in the quickfix
+ list specified by "id" or "nr". If set to '$',
+ then the last entry in the list is set as the
+ current entry. See |quickfix-index|
+ items list of quickfix entries. Same as the {list}
+ argument.
+ lines use 'errorformat' to parse a list of lines and
+ add the resulting entries to the quickfix list
+ {nr} or {id}. Only a |List| value is supported.
+ See |quickfix-parse|
+ nr list number in the quickfix stack; zero
+ means the current quickfix list and "$" means
+ the last quickfix list.
+ quickfixtextfunc
+ function to get the text to display in the
+ quickfix window. The value can be the name of
+ a function or a funcref or a lambda. Refer to
+ |quickfix-window-function| for an explanation
+ of how to write the function and an example.
+ title quickfix list title text. See |quickfix-title|
+ Unsupported keys in {what} are ignored.
+ If the "nr" item is not present, then the current quickfix list
+ is modified. When creating a new quickfix list, "nr" can be
+ set to a value one greater than the quickfix stack size.
+ When modifying a quickfix list, to guarantee that the correct
+ list is modified, "id" should be used instead of "nr" to
+ specify the list.
+
+ Examples (See also |setqflist-examples|): >vim
+ call setqflist([], 'r', {'title': 'My search'})
+ call setqflist([], 'r', {'nr': 2, 'title': 'Errors'})
+ call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]})
+ <
+ Returns zero for success, -1 for failure.
+
+ This function can be used to create a quickfix list
+ independent of the 'errorformat' setting. Use a command like
+ `:cc 1` to jump to the first position.
+
+ ]=],
+ name = 'setqflist',
+ params = { { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ signature = 'setqflist({list} [, {action} [, {what}]])',
+ },
+ setreg = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Set the register {regname} to {value}.
+ If {regname} is "" or "@", the unnamed register '"' is used.
+ The {regname} argument is a string.
+
+ {value} may be any value returned by |getreg()| or
+ |getreginfo()|, including a |List| or |Dict|.
+ If {options} contains "a" or {regname} is upper case,
+ then the value is appended.
+
+ {options} can also contain a register type specification:
+ "c" or "v" |charwise| mode
+ "l" or "V" |linewise| mode
+ "b" or "<CTRL-V>" |blockwise-visual| mode
+ If a number immediately follows "b" or "<CTRL-V>" then this is
+ used as the width of the selection - if it is not specified
+ then the width of the block is set to the number of characters
+ in the longest line (counting a <Tab> as 1 character).
+ If {options} contains "u" or '"', then the unnamed register is
+ set to point to register {regname}.
+
+ If {options} contains no register settings, then the default
+ is to use character mode unless {value} ends in a <NL> for
+ string {value} and linewise mode for list {value}. Blockwise
+ mode is never selected automatically.
+ Returns zero for success, non-zero for failure.
+
+ *E883*
+ Note: you may not use |List| containing more than one item to
+ set search and expression registers. Lists containing no
+ items act like empty strings.
+
+ Examples: >vim
+ call setreg(v:register, @*)
+ call setreg('*', @%, 'ac')
+ call setreg('a', "1\n2\n3", 'b5')
+ call setreg('"', { 'points_to': 'a'})
+
+ <This example shows using the functions to save and restore a
+ register: >vim
+ let var_a = getreginfo()
+ call setreg('a', var_a)
+ <or: >vim
+ let var_a = getreg('a', 1, 1)
+ let var_amode = getregtype('a')
+ " ....
+ call setreg('a', var_a, var_amode)
+ <Note: you may not reliably restore register value
+ without using the third argument to |getreg()| as without it
+ newlines are represented as newlines AND Nul bytes are
+ represented as newlines as well, see |NL-used-for-Nul|.
+
+ You can also change the type of a register by appending
+ nothing: >vim
+ call setreg('a', '', 'al')
+
+ ]=],
+ name = 'setreg',
+ params = { { 'regname', 'string' }, { 'value', 'any' }, { 'options', 'table' } },
+ signature = 'setreg({regname}, {value} [, {options}])',
+ },
+ settabvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Set tab-local variable {varname} to {val} in tab page {tabnr}.
+ |t:var|
+ The {varname} argument is a string.
+ Note that the variable name without "t:" must be used.
+ Tabs are numbered starting with one.
+ This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'settabvar',
+ params = { { 'tabnr', 'integer' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'settabvar({tabnr}, {varname}, {val})',
+ },
+ settabwinvar = {
+ args = 4,
+ base = 4,
+ desc = [=[
+ Set option or local variable {varname} in window {winnr} to
+ {val}.
+ Tabs are numbered starting with one. For the current tabpage
+ use |setwinvar()|.
+ {winnr} can be the window number or the |window-ID|.
+ When {winnr} is zero the current window is used.
+ This also works for a global or local buffer option, but it
+ doesn't work for a global or local buffer variable.
+ For a local buffer option the global value is unchanged.
+ Note that the variable name without "w:" must be used.
+ Examples: >vim
+ call settabwinvar(1, 1, "&list", 0)
+ call settabwinvar(3, 2, "myvar", "foobar")
+ <This function is not available in the |sandbox|.
+
+ ]=],
+ name = 'settabwinvar',
+ params = {
+ { 'tabnr', 'integer' },
+ { 'winnr', 'integer' },
+ { 'varname', 'string' },
+ { 'val', 'any' },
+ },
+ signature = 'settabwinvar({tabnr}, {winnr}, {varname}, {val})',
+ },
+ settagstack = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Modify the tag stack of the window {nr} using {dict}.
+ {nr} can be the window number or the |window-ID|.
+
+ For a list of supported items in {dict}, refer to
+ |gettagstack()|. "curidx" takes effect before changing the tag
+ stack.
+ *E962*
+ How the tag stack is modified depends on the {action}
+ argument:
+ - If {action} is not present or is set to 'r', then the tag
+ stack is replaced.
+ - If {action} is set to 'a', then new entries from {dict} are
+ pushed (added) onto the tag stack.
+ - If {action} is set to 't', then all the entries from the
+ current entry in the tag stack or "curidx" in {dict} are
+ removed and then new entries are pushed to the stack.
+
+ The current index is set to one after the length of the tag
+ stack after the modification.
+
+ Returns zero for success, -1 for failure.
+
+ Examples (for more examples see |tagstack-examples|):
+ Empty the tag stack of window 3: >vim
+ call settagstack(3, {'items' : []})
+
+ < Save and restore the tag stack: >vim
+ let stack = gettagstack(1003)
+ " do something else
+ call settagstack(1003, stack)
+ unlet stack
+ <
+ ]=],
+ name = 'settagstack',
+ params = { { 'nr', 'integer' }, { 'dict', 'any' }, { 'action', 'any' } },
+ signature = 'settagstack({nr}, {dict} [, {action}])',
+ },
+ setwinvar = {
+ args = 3,
+ base = 3,
+ desc = [=[
+ Like |settabwinvar()| for the current tab page.
+ Examples: >vim
+ call setwinvar(1, "&list", 0)
+ call setwinvar(2, "myvar", "foobar")
+
+ ]=],
+ name = 'setwinvar',
+ params = { { 'nr', 'integer' }, { 'varname', 'string' }, { 'val', 'any' } },
+ signature = 'setwinvar({nr}, {varname}, {val})',
+ },
+ sha256 = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns a String with 64 hex characters, which is the SHA256
+ checksum of {string}.
+
+ ]=],
+ name = 'sha256',
+ params = { { 'string', 'string' } },
+ signature = 'sha256({string})',
+ },
+ shellescape = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Escape {string} for use as a shell command argument.
+
+ On Windows when 'shellslash' is not set, encloses {string} in
+ double-quotes and doubles all double-quotes within {string}.
+ Otherwise encloses {string} in single-quotes and replaces all
+ "'" with "'\''".
+
+ If {special} is a |non-zero-arg|:
+ - Special items such as "!", "%", "#" and "<cword>" will be
+ preceded by a backslash. The backslash will be removed again
+ by the |:!| command.
+ - The <NL> character is escaped.
+
+ If 'shell' contains "csh" in the tail:
+ - The "!" character will be escaped. This is because csh and
+ tcsh use "!" for history replacement even in single-quotes.
+ - The <NL> character is escaped (twice if {special} is
+ a |non-zero-arg|).
+
+ If 'shell' contains "fish" in the tail, the "\" character will
+ be escaped because in fish it is used as an escape character
+ inside single quotes.
+
+ Example of use with a |:!| command: >vim
+ exe '!dir ' .. shellescape(expand('<cfile>'), 1)
+ <This results in a directory listing for the file under the
+ cursor. Example of use with |system()|: >vim
+ call system("chmod +w -- " .. shellescape(expand("%")))
+ <See also |::S|.
+
+ ]=],
+ name = 'shellescape',
+ params = { { 'string', 'string' }, { 'special', 'any' } },
+ signature = 'shellescape({string} [, {special}])',
+ },
+ shiftwidth = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Returns the effective value of 'shiftwidth'. This is the
+ 'shiftwidth' value unless it is zero, in which case it is the
+ 'tabstop' value. To be backwards compatible in indent
+ plugins, use this: >vim
+ if exists('*shiftwidth')
+ func s:sw()
+ return shiftwidth()
+ endfunc
+ else
+ func s:sw()
+ return &sw
+ endfunc
+ endif
+ <And then use s:sw() instead of &sw.
+
+ When there is one argument {col} this is used as column number
+ for which to return the 'shiftwidth' value. This matters for the
+ 'vartabstop' feature. If no {col} argument is given, column 1
+ will be assumed.
+
+ ]=],
+ name = 'shiftwidth',
+ params = { { 'col', 'integer' } },
+ signature = 'shiftwidth([{col}])',
+ returns = 'integer',
+ },
+ sign_define = {
+ args = { 1, 2 },
+ base = 1,
+ name = 'sign_define',
+ params = { { 'name', 'string' }, { 'dict', 'vim.fn.sign_define.dict' } },
+ signature = 'sign_define({name} [, {dict}])',
+ returns = '0|-1',
+ },
+ sign_define__1 = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Define a new sign named {name} or modify the attributes of an
+ existing sign. This is similar to the |:sign-define| command.
+
+ Prefix {name} with a unique text to avoid name collisions.
+ There is no {group} like with placing signs.
+
+ The {name} can be a String or a Number. The optional {dict}
+ argument specifies the sign attributes. The following values
+ are supported:
+ icon full path to the bitmap file for the sign.
+ linehl highlight group used for the whole line the
+ sign is placed in.
+ numhl highlight group used for the line number where
+ the sign is placed.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled.
+
+ If the sign named {name} already exists, then the attributes
+ of the sign are updated.
+
+ The one argument {list} can be used to define a list of signs.
+ Each list item is a dictionary with the above items in {dict}
+ and a "name" item for the sign name.
+
+ Returns 0 on success and -1 on failure. When the one argument
+ {list} is used, then returns a List of values one for each
+ defined sign.
+
+ Examples: >vim
+ call sign_define("mySign", {
+ \ "text" : "=>",
+ \ "texthl" : "Error",
+ \ "linehl" : "Search"})
+ call sign_define([
+ \ {'name' : 'sign1',
+ \ 'text' : '=>'},
+ \ {'name' : 'sign2',
+ \ 'text' : '!!'}
+ \ ])
+ <
+ ]=],
+ name = 'sign_define',
+ params = { { 'list', 'vim.fn.sign_define.dict[]' } },
+ signature = 'sign_define({list})',
+ returns = '(0|-1)[]',
+ },
+ sign_getdefined = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Get a list of defined signs and their attributes.
+ This is similar to the |:sign-list| command.
+
+ If the {name} is not supplied, then a list of all the defined
+ signs is returned. Otherwise the attribute of the specified
+ sign is returned.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ icon full path to the bitmap file of the sign
+ linehl highlight group used for the whole line the
+ sign is placed in; not present if not set.
+ name name of the sign
+ numhl highlight group used for the line number where
+ the sign is placed; not present if not set.
+ text text that is displayed when there is no icon
+ or the GUI is not being used.
+ texthl highlight group used for the text item; not
+ present if not set.
+ culhl highlight group used for the text item when
+ the cursor is on the same line as the sign and
+ 'cursorline' is enabled; not present if not
+ set.
+
+ Returns an empty List if there are no signs and when {name} is
+ not found.
+
+ Examples: >vim
+ " Get a list of all the defined signs
+ echo sign_getdefined()
+
+ " Get the attribute of the sign named mySign
+ echo sign_getdefined("mySign")
+ <
+ ]=],
+ name = 'sign_getdefined',
+ params = { { 'name', 'string' } },
+ signature = 'sign_getdefined([{name}])',
+ returns = 'vim.fn.sign_getdefined.ret.item[]',
+ },
+ sign_getplaced = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Return a list of signs placed in a buffer or all the buffers.
+ This is similar to the |:sign-place-list| command.
+
+ If the optional buffer name {buf} is specified, then only the
+ list of signs placed in that buffer is returned. For the use
+ of {buf}, see |bufname()|. The optional {dict} can contain
+ the following entries:
+ group select only signs in this group
+ id select sign with this identifier
+ lnum select signs placed in this line. For the use
+ of {lnum}, see |line()|.
+ If {group} is "*", then signs in all the groups including the
+ global group are returned. If {group} is not supplied or is an
+ empty string, then only signs in the global group are
+ returned. If no arguments are supplied, then signs in the
+ global group placed in all the buffers are returned.
+ See |sign-group|.
+
+ Each list item in the returned value is a dictionary with the
+ following entries:
+ bufnr number of the buffer with the sign
+ signs list of signs placed in {bufnr}. Each list
+ item is a dictionary with the below listed
+ entries
+
+ The dictionary for each sign contains the following entries:
+ group sign group. Set to '' for the global group.
+ id identifier of the sign
+ lnum line number where the sign is placed
+ name name of the defined sign
+ priority sign priority
+
+ The returned signs in a buffer are ordered by their line
+ number and priority.
+
+ Returns an empty list on failure or if there are no placed
+ signs.
+
+ Examples: >vim
+ " Get a List of signs placed in eval.c in the
+ " global group
+ echo sign_getplaced("eval.c")
+
+ " Get a List of signs in group 'g1' placed in eval.c
+ echo sign_getplaced("eval.c", {'group' : 'g1'})
+
+ " Get a List of signs placed at line 10 in eval.c
+ echo sign_getplaced("eval.c", {'lnum' : 10})
+
+ " Get sign with identifier 10 placed in a.py
+ echo sign_getplaced("a.py", {'id' : 10})
+
+ " Get sign with id 20 in group 'g1' placed in a.py
+ echo sign_getplaced("a.py", {'group' : 'g1',
+ \ 'id' : 20})
+
+ " Get a List of all the placed signs
+ echo sign_getplaced()
+ <
+ ]=],
+ name = 'sign_getplaced',
+ params = { { 'buf', 'any' }, { 'dict', 'vim.fn.sign_getplaced.dict' } },
+ signature = 'sign_getplaced([{buf} [, {dict}]])',
+ returns = 'vim.fn.sign_getplaced.ret.item[]',
+ },
+ sign_jump = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ Open the buffer {buf} or jump to the window that contains
+ {buf} and position the cursor at sign {id} in group {group}.
+ This is similar to the |:sign-jump| command.
+
+ If {group} is an empty string, then the global group is used.
+ For the use of {buf}, see |bufname()|.
+
+ Returns the line number of the sign. Returns -1 if the
+ arguments are invalid.
+
+ Example: >vim
+ " Jump to sign 10 in the current buffer
+ call sign_jump(10, '', '')
+ <
+ ]=],
+ name = 'sign_jump',
+ params = { { 'id', 'integer' }, { 'group', 'string' }, { 'buf', 'integer|string' } },
+ signature = 'sign_jump({id}, {group}, {buf})',
+ returns = 'integer'
+ },
+ sign_place = {
+ args = { 4, 5 },
+ base = 1,
+ desc = [=[
+ Place the sign defined as {name} at line {lnum} in file or
+ buffer {buf} and assign {id} and {group} to sign. This is
+ similar to the |:sign-place| command.
+
+ If the sign identifier {id} is zero, then a new identifier is
+ allocated. Otherwise the specified number is used. {group} is
+ the sign group name. To use the global sign group, use an
+ empty string. {group} functions as a namespace for {id}, thus
+ two groups can use the same IDs. Refer to |sign-identifier|
+ and |sign-group| for more information.
+
+ {name} refers to a defined sign.
+ {buf} refers to a buffer name or number. For the accepted
+ values, see |bufname()|.
+
+ The optional {dict} argument supports the following entries:
+ lnum line number in the file or buffer
+ {buf} where the sign is to be placed.
+ For the accepted values, see |line()|.
+ priority priority of the sign. See
+ |sign-priority| for more information.
+
+ If the optional {dict} is not specified, then it modifies the
+ placed sign {id} in group {group} to use the defined sign
+ {name}.
+
+ Returns the sign identifier on success and -1 on failure.
+
+ Examples: >vim
+ " Place a sign named sign1 with id 5 at line 20 in
+ " buffer json.c
+ call sign_place(5, '', 'sign1', 'json.c',
+ \ {'lnum' : 20})
+
+ " Updates sign 5 in buffer json.c to use sign2
+ call sign_place(5, '', 'sign2', 'json.c')
+
+ " Place a sign named sign3 at line 30 in
+ " buffer json.c with a new identifier
+ let id = sign_place(0, '', 'sign3', 'json.c',
+ \ {'lnum' : 30})
+
+ " Place a sign named sign4 with id 10 in group 'g3'
+ " at line 40 in buffer json.c with priority 90
+ call sign_place(10, 'g3', 'sign4', 'json.c',
+ \ {'lnum' : 40, 'priority' : 90})
+ <
+ ]=],
+ name = 'sign_place',
+ params = {
+ { 'id', 'any' },
+ { 'group', 'any' },
+ { 'name', 'string' },
+ { 'buf', 'any' },
+ { 'dict', 'vim.fn.sign_place.dict' },
+ },
+ signature = 'sign_place({id}, {group}, {name}, {buf} [, {dict}])',
+ returns = 'integer'
+ },
+ sign_placelist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Place one or more signs. This is similar to the
+ |sign_place()| function. The {list} argument specifies the
+ List of signs to place. Each list item is a dict with the
+ following sign attributes:
+ buffer Buffer name or number. For the accepted
+ values, see |bufname()|.
+ group Sign group. {group} functions as a namespace
+ for {id}, thus two groups can use the same
+ IDs. If not specified or set to an empty
+ string, then the global group is used. See
+ |sign-group| for more information.
+ id Sign identifier. If not specified or zero,
+ then a new unique identifier is allocated.
+ Otherwise the specified number is used. See
+ |sign-identifier| for more information.
+ lnum Line number in the buffer where the sign is to
+ be placed. For the accepted values, see
+ |line()|.
+ name Name of the sign to place. See |sign_define()|
+ for more information.
+ priority Priority of the sign. When multiple signs are
+ placed on a line, the sign with the highest
+ priority is used. If not specified, the
+ default value of 10 is used. See
+ |sign-priority| for more information.
+
+ If {id} refers to an existing sign, then the existing sign is
+ modified to use the specified {name} and/or {priority}.
+
+ Returns a List of sign identifiers. If failed to place a
+ sign, the corresponding list item is set to -1.
+
+ Examples: >vim
+ " Place sign s1 with id 5 at line 20 and id 10 at line
+ " 30 in buffer a.c
+ let [n1, n2] = sign_placelist([
+ \ {'id' : 5,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 20},
+ \ {'id' : 10,
+ \ 'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 30}
+ \ ])
+
+ " Place sign s1 in buffer a.c at line 40 and 50
+ " with auto-generated identifiers
+ let [n1, n2] = sign_placelist([
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 40},
+ \ {'name' : 's1',
+ \ 'buffer' : 'a.c',
+ \ 'lnum' : 50}
+ \ ])
+ <
+ ]=],
+ name = 'sign_placelist',
+ params = { { 'list', 'vim.fn.sign_placelist.list.item[]' } },
+ signature = 'sign_placelist({list})',
+ returns = 'integer[]'
+ },
+ sign_undefine = {
+ args = { 0, 1 },
+ base = 1,
+ name = 'sign_undefine',
+ params = { { 'name', 'string' } },
+ signature = 'sign_undefine([{name}])',
+ returns = '0|-1',
+ },
+ sign_undefine__1 = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Deletes a previously defined sign {name}. This is similar to
+ the |:sign-undefine| command. If {name} is not supplied, then
+ deletes all the defined signs.
+
+ The one argument {list} can be used to undefine a list of
+ signs. Each list item is the name of a sign.
+
+ Returns 0 on success and -1 on failure. For the one argument
+ {list} call, returns a list of values one for each undefined
+ sign.
+
+ Examples: >vim
+ " Delete a sign named mySign
+ call sign_undefine("mySign")
+
+ " Delete signs 'sign1' and 'sign2'
+ call sign_undefine(["sign1", "sign2"])
+
+ " Delete all the signs
+ call sign_undefine()
+ <
+ ]=],
+ name = 'sign_undefine',
+ params = { { 'list', 'string[]' } },
+ signature = 'sign_undefine({list})',
+ returns = 'integer[]',
+ },
+ sign_unplace = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Remove a previously placed sign in one or more buffers. This
+ is similar to the |:sign-unplace| command.
+
+ {group} is the sign group name. To use the global sign group,
+ use an empty string. If {group} is set to "*", then all the
+ groups including the global group are used.
+ The signs in {group} are selected based on the entries in
+ {dict}. The following optional entries in {dict} are
+ supported:
+ buffer buffer name or number. See |bufname()|.
+ id sign identifier
+ If {dict} is not supplied, then all the signs in {group} are
+ removed.
+
+ Returns 0 on success and -1 on failure.
+
+ Examples: >vim
+ " Remove sign 10 from buffer a.vim
+ call sign_unplace('', {'buffer' : "a.vim", 'id' : 10})
+
+ " Remove sign 20 in group 'g1' from buffer 3
+ call sign_unplace('g1', {'buffer' : 3, 'id' : 20})
+
+ " Remove all the signs in group 'g2' from buffer 10
+ call sign_unplace('g2', {'buffer' : 10})
+
+ " Remove sign 30 in group 'g3' from all the buffers
+ call sign_unplace('g3', {'id' : 30})
+
+ " Remove all the signs placed in buffer 5
+ call sign_unplace('*', {'buffer' : 5})
+
+ " Remove the signs in group 'g4' from all the buffers
+ call sign_unplace('g4')
+
+ " Remove sign 40 from all the buffers
+ call sign_unplace('*', {'id' : 40})
+
+ " Remove all the placed signs from all the buffers
+ call sign_unplace('*')
+
+ ]=],
+ name = 'sign_unplace',
+ params = { { 'group', 'string' }, { 'dict', 'vim.fn.sign_unplace.dict' } },
+ signature = 'sign_unplace({group} [, {dict}])',
+ returns = '0|-1',
+ },
+ sign_unplacelist = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Remove previously placed signs from one or more buffers. This
+ is similar to the |sign_unplace()| function.
+
+ The {list} argument specifies the List of signs to remove.
+ Each list item is a dict with the following sign attributes:
+ buffer buffer name or number. For the accepted
+ values, see |bufname()|. If not specified,
+ then the specified sign is removed from all
+ the buffers.
+ group sign group name. If not specified or set to an
+ empty string, then the global sign group is
+ used. If set to "*", then all the groups
+ including the global group are used.
+ id sign identifier. If not specified, then all
+ the signs in the specified group are removed.
+
+ Returns a List where an entry is set to 0 if the corresponding
+ sign was successfully removed or -1 on failure.
+
+ Example: >vim
+ " Remove sign with id 10 from buffer a.vim and sign
+ " with id 20 from buffer b.vim
+ call sign_unplacelist([
+ \ {'id' : 10, 'buffer' : "a.vim"},
+ \ {'id' : 20, 'buffer' : 'b.vim'},
+ \ ])
+ <
+ ]=],
+ name = 'sign_unplacelist',
+ params = { { 'list', 'vim.fn.sign_unplacelist.list.item' } },
+ signature = 'sign_unplacelist({list})',
+ returns = '(0|-1)[]',
+ },
+ simplify = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Simplify the file name as much as possible without changing
+ the meaning. Shortcuts (on MS-Windows) or symbolic links (on
+ Unix) are not resolved. If the first path component in
+ {filename} designates the current directory, this will be
+ valid for the result as well. A trailing path separator is
+ not removed either. On Unix "//path" is unchanged, but
+ "///path" is simplified to "/path" (this follows the Posix
+ standard).
+ Example: >vim
+ simplify("./dir/.././/file/") == "./file/"
+ <Note: The combination "dir/.." is only removed if "dir" is
+ a searchable directory or does not exist. On Unix, it is also
+ removed when "dir" is a symbolic link within the same
+ directory. In order to resolve all the involved symbolic
+ links before simplifying the path name, use |resolve()|.
+
+ ]=],
+ name = 'simplify',
+ params = { { 'filename', 'any' } },
+ signature = 'simplify({filename})',
+ },
+ sin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the sine of {expr}, measured in radians, as a |Float|.
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sin(100)
+ < -0.506366 >vim
+ echo sin(-4.01)
+ < 0.763301
+
+ ]=],
+ float_func = 'sin',
+ name = 'sin',
+ params = { { 'expr', 'any' } },
+ signature = 'sin({expr})',
+ },
+ sinh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic sine of {expr} as a |Float| in the range
+ [-inf, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sinh(0.5)
+ < 0.521095 >vim
+ echo sinh(-0.9)
+ < -1.026517
+
+ ]=],
+ float_func = 'sinh',
+ name = 'sinh',
+ params = { { 'expr', 'any' } },
+ signature = 'sinh({expr})',
+ },
+ slice = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Similar to using a |slice| "expr[start : end]", but "end" is
+ used exclusive. And for a string the indexes are used as
+ character indexes instead of byte indexes.
+ Also, composing characters are not counted.
+ When {end} is omitted the slice continues to the last item.
+ When {end} is -1 the last item is omitted.
+ Returns an empty value if {start} or {end} are invalid.
+
+ ]=],
+ name = 'slice',
+ params = { { 'expr', 'any' }, { 'start', 'any' }, { 'end', 'any' } },
+ signature = 'slice({expr}, {start} [, {end}])',
+ },
+ sockconnect = {
+ args = { 2, 3 },
+ desc = [=[
+ Connect a socket to an address. If {mode} is "pipe" then
+ {address} should be the path of a local domain socket (on
+ unix) or named pipe (on Windows). If {mode} is "tcp" then
+ {address} should be of the form "host:port" where the host
+ should be an ip address or host name, and port the port
+ number.
+
+ For "pipe" mode, see |luv-pipe-handle|. For "tcp" mode, see
+ |luv-tcp-handle|.
+
+ Returns a |channel| ID. Close the socket with |chanclose()|.
+ Use |chansend()| to send data over a bytes socket, and
+ |rpcrequest()| and |rpcnotify()| to communicate with a RPC
+ socket.
+
+ {opts} is an optional dictionary with these keys:
+ |on_data| : callback invoked when data was read from socket
+ data_buffered : read socket data in |channel-buffered| mode.
+ rpc : If set, |msgpack-rpc| will be used to communicate
+ over the socket.
+ Returns:
+ - The channel ID on success (greater than zero)
+ - 0 on invalid arguments or connection failure.
+ ]=],
+ name = 'sockconnect',
+ params = { { 'mode', 'string' }, { 'address', 'any' }, { 'opts', 'table' } },
+ signature = 'sockconnect({mode}, {address} [, {opts}])',
+ },
+ sort = {
+ args = { 1, 3 },
+ base = 1,
+ tags = { 'E702' },
+ desc = [=[
+ Sort the items in {list} in-place. Returns {list}.
+
+ If you want a list to remain unmodified make a copy first: >vim
+ let sortedlist = sort(copy(mylist))
+
+ <When {how} is omitted or is a string, then sort() uses the
+ string representation of each item to sort on. Numbers sort
+ after Strings, |Lists| after Numbers. For sorting text in the
+ current buffer use |:sort|.
+
+ When {how} is given and it is 'i' then case is ignored.
+ For backwards compatibility, the value one can be used to
+ ignore case. Zero means to not ignore case.
+
+ When {how} is given and it is 'l' then the current collation
+ locale is used for ordering. Implementation details: strcoll()
+ is used to compare strings. See |:language| check or set the
+ collation locale. |v:collate| can also be used to check the
+ current locale. Sorting using the locale typically ignores
+ case. Example: >vim
+ " ö is sorted similarly to o with English locale.
+ language collate en_US.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ < ['n', 'o', 'O', 'ö', 'p', 'z'] ~
+ >vim
+ " ö is sorted after z with Swedish locale.
+ language collate sv_SE.UTF8
+ echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l')
+ < ['n', 'o', 'O', 'p', 'z', 'ö'] ~
+ This does not work properly on Mac.
+
+ When {how} is given and it is 'n' then all items will be
+ sorted numerical (Implementation detail: this uses the
+ strtod() function to parse numbers, Strings, Lists, Dicts and
+ Funcrefs will be considered as being 0).
+
+ When {how} is given and it is 'N' then all items will be
+ sorted numerical. This is like 'n' but a string containing
+ digits will be used as the number they represent.
+
+ When {how} is given and it is 'f' then all items will be
+ sorted numerical. All values must be a Number or a Float.
+
+ When {how} is a |Funcref| or a function name, this function
+ is called to compare items. The function is invoked with two
+ items as argument and must return zero if they are equal, 1 or
+ bigger if the first one sorts after the second one, -1 or
+ smaller if the first one sorts before the second one.
+
+ {dict} is for functions with the "dict" attribute. It will be
+ used to set the local variable "self". |Dictionary-function|
+
+ The sort is stable, items which compare equal (as number or as
+ string) will keep their relative position. E.g., when sorting
+ on numbers, text strings will sort next to each other, in the
+ same order as they were originally.
+
+
+ Example: >vim
+ func MyCompare(i1, i2)
+ return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1
+ endfunc
+ eval mylist->sort("MyCompare")
+ <A shorter compare version for this specific simple case, which
+ ignores overflow: >vim
+ func MyCompare(i1, i2)
+ return a:i1 - a:i2
+ endfunc
+ <For a simple expression you can use a lambda: >vim
+ eval mylist->sort({i1, i2 -> i1 - i2})
+ <
+ ]=],
+ name = 'sort',
+ params = { { 'list', 'any' }, { 'how', 'any' }, { 'dict', 'any' } },
+ signature = 'sort({list} [, {how} [, {dict}]])',
+ },
+ soundfold = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the sound-folded equivalent of {word}. Uses the first
+ language in 'spelllang' for the current window that supports
+ soundfolding. 'spell' must be set. When no sound folding is
+ possible the {word} is returned unmodified.
+ This can be used for making spelling suggestions. Note that
+ the method can be quite slow.
+
+ ]=],
+ name = 'soundfold',
+ params = { { 'word', 'any' } },
+ signature = 'soundfold({word})',
+ },
+ spellbadword = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Without argument: The result is the badly spelled word under
+ or after the cursor. The cursor is moved to the start of the
+ bad word. When no bad word is found in the cursor line the
+ result is an empty string and the cursor doesn't move.
+
+ With argument: The result is the first word in {sentence} that
+ is badly spelled. If there are no spelling mistakes the
+ result is an empty string.
+
+ The return value is a list with two items:
+ - The badly spelled word or an empty string.
+ - The type of the spelling error:
+ "bad" spelling mistake
+ "rare" rare word
+ "local" word only valid in another region
+ "caps" word should start with Capital
+ Example: >vim
+ echo spellbadword("the quik brown fox")
+ < ['quik', 'bad'] ~
+
+ The spelling information for the current window and the value
+ of 'spelllang' are used.
+
+ ]=],
+ name = 'spellbadword',
+ params = { { 'sentence', 'any' } },
+ signature = 'spellbadword([{sentence}])',
+ },
+ spellsuggest = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return a |List| with spelling suggestions to replace {word}.
+ When {max} is given up to this number of suggestions are
+ returned. Otherwise up to 25 suggestions are returned.
+
+ When the {capital} argument is given and it's non-zero only
+ suggestions with a leading capital will be given. Use this
+ after a match with 'spellcapcheck'.
+
+ {word} can be a badly spelled word followed by other text.
+ This allows for joining two words that were split. The
+ suggestions also include the following text, thus you can
+ replace a line.
+
+ {word} may also be a good word. Similar words will then be
+ returned. {word} itself is not included in the suggestions,
+ although it may appear capitalized.
+
+ The spelling information for the current window is used. The
+ values of 'spelllang' and 'spellsuggest' are used.
+
+ ]=],
+ name = 'spellsuggest',
+ params = { { 'word', 'any' }, { 'max', 'any' }, { 'capital', 'any' } },
+ signature = 'spellsuggest({word} [, {max} [, {capital}]])',
+ },
+ split = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Make a |List| out of {string}. When {pattern} is omitted or
+ empty each white-separated sequence of characters becomes an
+ item.
+ Otherwise the string is split where {pattern} matches,
+ removing the matched characters. 'ignorecase' is not used
+ here, add \c to ignore case. |/\c|
+ When the first or last item is empty it is omitted, unless the
+ {keepempty} argument is given and it's non-zero.
+ Other empty items are kept when {pattern} matches at least one
+ character or when {keepempty} is non-zero.
+ Example: >vim
+ let words = split(getline('.'), '\W\+')
+ <To split a string in individual characters: >vim
+ for c in split(mystring, '\zs') | endfor
+ <If you want to keep the separator you can also use '\zs' at
+ the end of the pattern: >vim
+ echo split('abc:def:ghi', ':\zs')
+ < >
+ ['abc:', 'def:', 'ghi']
+ <
+ Splitting a table where the first element can be empty: >vim
+ let items = split(line, ':', 1)
+ <The opposite function is |join()|.
+
+ ]=],
+ name = 'split',
+ params = { { 'string', 'string' }, { 'pattern', 'any' }, { 'keepempty', 'any' } },
+ signature = 'split({string} [, {pattern} [, {keepempty}]])',
+ },
+ sqrt = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the non-negative square root of Float {expr} as a
+ |Float|.
+ {expr} must evaluate to a |Float| or a |Number|. When {expr}
+ is negative the result is NaN (Not a Number). Returns 0.0 if
+ {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo sqrt(100)
+ < 10.0 >vim
+ echo sqrt(-4.01)
+ < str2float("nan")
+ NaN may be different, it depends on system libraries.
+
+ ]=],
+ float_func = 'sqrt',
+ name = 'sqrt',
+ params = { { 'expr', 'any' } },
+ signature = 'sqrt({expr})',
+ },
+ srand = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ 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: >vim
+ let seed = srand()
+ let seed = srand(userinput)
+ echo rand(seed)
+ <
+ ]=],
+ name = 'srand',
+ params = { { 'expr', 'any' } },
+ signature = 'srand([{expr}])',
+ },
+ stdioopen = {
+ args = 1,
+ desc = [=[
+ With |--headless| this opens stdin and stdout as a |channel|.
+ May be called only once. See |channel-stdio|. stderr is not
+ handled by this function, see |v:stderr|.
+
+ Close the stdio handles with |chanclose()|. Use |chansend()|
+ to send data to stdout, and |rpcrequest()| and |rpcnotify()|
+ to communicate over RPC.
+
+ {opts} is a dictionary with these keys:
+ |on_stdin| : callback invoked when stdin is written to.
+ on_print : callback invoked when Nvim needs to print a
+ message, with the message (whose type is string)
+ as sole argument.
+ stdin_buffered : read stdin in |channel-buffered| mode.
+ rpc : If set, |msgpack-rpc| will be used to communicate
+ over stdio
+ Returns:
+ - |channel-id| on success (value is always 1)
+ - 0 on invalid arguments
+ ]=],
+ name = 'stdioopen',
+ params = { { 'opts', 'table' } },
+ signature = 'stdioopen({opts})',
+ },
+ stdpath = {
+ args = 1,
+ tags = { 'E6100' },
+ desc = [=[
+ Returns |standard-path| locations of various default files and
+ directories.
+
+ {what} Type Description ~
+ cache String Cache directory: arbitrary temporary
+ storage for plugins, etc.
+ config String User configuration directory. |init.vim|
+ is stored here.
+ config_dirs List Other configuration directories.
+ data String User data directory.
+ data_dirs List Other data directories.
+ log String Logs directory (for use by plugins too).
+ run String Run directory: temporary, local storage
+ for sockets, named pipes, etc.
+ state String Session state directory: storage for file
+ drafts, swap, undo, |shada|.
+
+ Example: >vim
+ echo stdpath("config")
+ <
+ ]=],
+ fast = true,
+ name = 'stdpath',
+ params = { { 'what', "'cache'|'config'|'config_dirs'|'data'|'data_dirs'|'log'|'run'|'state'" } },
+ returns = 'string|string[]',
+ signature = 'stdpath({what})',
+ },
+ state = {
+ args = {0, 1},
+ base = 1,
+ desc = [=[
+ Return a string which contains characters indicating the
+ current state. Mostly useful in callbacks that want to do
+ work that may not always be safe. Roughly this works like:
+ - callback uses state() to check if work is safe to do.
+ Yes: then do it right away.
+ No: add to work queue and add a |SafeState| autocommand.
+ - When SafeState is triggered and executes your autocommand,
+ check with `state()` if the work can be done now, and if yes
+ remove it from the queue and execute.
+ Remove the autocommand if the queue is now empty.
+ Also see |mode()|.
+
+ When {what} is given only characters in this string will be
+ added. E.g, this checks if the screen has scrolled: >vim
+ if state('s') == ''
+ " screen has not scrolled
+ <
+ These characters indicate the state, generally indicating that
+ something is busy:
+ m halfway a mapping, :normal command, feedkeys() or
+ stuffed command
+ o operator pending, e.g. after |d|
+ a Insert mode autocomplete active
+ x executing an autocommand
+ S not triggering SafeState, e.g. after |f| or a count
+ c callback invoked, including timer (repeats for
+ recursiveness up to "ccc")
+ s screen has scrolled for messages
+ ]=],
+ fast = true,
+ name = 'state',
+ params = { { 'what', 'string' } },
+ signature = 'state([{what}])',
+ },
+ str2float = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Convert String {string} to a Float. This mostly works the
+ same as when using a floating point number in an expression,
+ see |floating-point-format|. But it's a bit more permissive.
+ E.g., "1e40" is accepted, while in an expression you need to
+ write "1.0e40". The hexadecimal form "0x123" is also
+ accepted, but not others, like binary or octal.
+ When {quoted} is present and non-zero then embedded single
+ quotes before the dot are ignored, thus "1'000.0" is a
+ thousand.
+ Text after the number is silently ignored.
+ The decimal point is always '.', no matter what the locale is
+ set to. A comma ends the number: "12,345.67" is converted to
+ 12.0. You can strip out thousands separators with
+ |substitute()|: >vim
+ let f = str2float(substitute(text, ',', '', 'g'))
+ <
+ Returns 0.0 if the conversion fails.
+
+ ]=],
+ name = 'str2float',
+ params = { { 'string', 'string' }, { 'quoted', 'any' } },
+ signature = 'str2float({string} [, {quoted}])',
+ },
+ str2list = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Return a list containing the number values which represent
+ each character in String {string}. Examples: >vim
+ echo str2list(" ") " returns [32]
+ echo str2list("ABC") " returns [65, 66, 67]
+ <|list2str()| does the opposite.
+
+ UTF-8 encoding is always used, {utf8} option has no effect,
+ and exists only for backwards-compatibility.
+ With UTF-8 composing characters are handled properly: >vim
+ echo str2list("á") " returns [97, 769]
+
+ ]=],
+ name = 'str2list',
+ params = { { 'string', 'string' }, { 'utf8', 'any' } },
+ signature = 'str2list({string} [, {utf8}])',
+ },
+ str2nr = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Convert string {string} to a number.
+ {base} is the conversion base, it can be 2, 8, 10 or 16.
+ When {quoted} is present and non-zero then embedded single
+ quotes are ignored, thus "1'000'000" is a million.
+
+ When {base} is omitted base 10 is used. This also means that
+ a leading zero doesn't cause octal conversion to be used, as
+ with the default String to Number conversion. Example: >vim
+ let nr = str2nr('0123')
+ <
+ When {base} is 16 a leading "0x" or "0X" is ignored. With a
+ different base the result will be zero. Similarly, when
+ {base} is 8 a leading "0", "0o" or "0O" is ignored, and when
+ {base} is 2 a leading "0b" or "0B" is ignored.
+ Text after the number is silently ignored.
+
+ Returns 0 if {string} is empty or on error.
+
+ ]=],
+ name = 'str2nr',
+ params = { { 'string', 'string' }, { 'base', 'any' } },
+ signature = 'str2nr({string} [, {base}])',
+ },
+ strcharlen = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of characters
+ in String {string}. Composing characters are ignored.
+ |strchars()| can count the number of characters, counting
+ composing characters separately.
+
+ Returns 0 if {string} is empty or on error.
+
+ Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+
+ ]=],
+ name = 'strcharlen',
+ params = { { 'string', 'string' } },
+ signature = 'strcharlen({string})',
+ },
+ strcharpart = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Like |strpart()| but using character index and length instead
+ of byte index and length.
+ When {skipcc} is omitted or zero, composing characters are
+ counted separately.
+ When {skipcc} set to 1, Composing characters are ignored,
+ similar to |slice()|.
+ When a character index is used where a character does not
+ exist it is omitted and counted as one character. For
+ example: >vim
+ echo strcharpart('abc', -1, 2)
+ <results in 'a'.
+
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strcharpart',
+ params = { { 'src', 'any' }, { 'start', 'any' }, { 'len', 'any' }, { 'skipcc', 'any' } },
+ signature = 'strcharpart({src}, {start} [, {len} [, {skipcc}]])',
+ },
+ strchars = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of characters
+ in String {string}.
+ When {skipcc} is omitted or zero, composing characters are
+ counted separately.
+ When {skipcc} set to 1, Composing characters are ignored.
+ |strcharlen()| always does this.
+
+ Returns zero on error.
+
+ Also see |strlen()|, |strdisplaywidth()| and |strwidth()|.
+
+ {skipcc} is only available after 7.4.755. For backward
+ compatibility, you can define a wrapper function: >vim
+ if has("patch-7.4.755")
+ function s:strchars(str, skipcc)
+ return strchars(a:str, a:skipcc)
+ endfunction
+ else
+ function s:strchars(str, skipcc)
+ if a:skipcc
+ return strlen(substitute(a:str, ".", "x", "g"))
+ else
+ return strchars(a:str)
+ endif
+ endfunction
+ endif
+ <
+ ]=],
+ name = 'strchars',
+ params = { { 'string', 'string' }, { 'skipcc', 'any' } },
+ returns = 'integer',
+ signature = 'strchars({string} [, {skipcc}])',
+ },
+ strdisplaywidth = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of display cells
+ String {string} occupies on the screen when it starts at {col}
+ (first column is zero). When {col} is omitted zero is used.
+ Otherwise it is the screen column where to start. This
+ matters for Tab characters.
+ The option settings of the current window are used. This
+ matters for anything that's displayed differently, such as
+ 'tabstop' and 'display'.
+ When {string} contains characters with East Asian Width Class
+ Ambiguous, this function's return value depends on 'ambiwidth'.
+ Returns zero on error.
+ Also see |strlen()|, |strwidth()| and |strchars()|.
+
+ ]=],
+ name = 'strdisplaywidth',
+ params = { { 'string', 'string' }, { 'col', 'integer' } },
+ returns = 'integer',
+ signature = 'strdisplaywidth({string} [, {col}])',
+ },
+ strftime = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is a formatted date and time, as
+ specified by the {format} string. The given {time} is used,
+ or the current time if no time is given. The accepted
+ {format} depends on your system, thus this is not portable!
+ See the manual page of the C function strftime() for the
+ format. The maximum length of the result is 80 characters.
+ See also |localtime()|, |getftime()| and |strptime()|.
+ The language can be changed with the |:language| command.
+ Examples: >vim
+ echo strftime("%c") " Sun Apr 27 11:49:23 1997
+ echo strftime("%Y %b %d %X") " 1997 Apr 27 11:53:25
+ echo strftime("%y%m%d %T") " 970427 11:53:55
+ echo strftime("%H:%M") " 11:55
+ echo strftime("%c", getftime("file.c"))
+ " Show mod time of file.c.
+
+ ]=],
+ name = 'strftime',
+ params = { { 'format', 'any' }, { 'time', 'any' } },
+ returns = 'string',
+ signature = 'strftime({format} [, {time}])',
+ },
+ strgetchar = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Get a Number corresponding to the character at {index} in
+ {str}. This uses a zero-based character index, not a byte
+ index. Composing characters are considered separate
+ characters here. Use |nr2char()| to convert the Number to a
+ String.
+ Returns -1 if {index} is invalid.
+ Also see |strcharpart()| and |strchars()|.
+
+ ]=],
+ name = 'strgetchar',
+ params = { { 'str', 'string' }, { 'index', 'integer' } },
+ returns = 'integer',
+ signature = 'strgetchar({str}, {index})',
+ },
+ stridx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which gives the byte index in
+ {haystack} of the first occurrence of the String {needle}.
+ If {start} is specified, the search starts at index {start}.
+ This can be used to find a second match: >vim
+ let colon1 = stridx(line, ":")
+ let colon2 = stridx(line, ":", colon1 + 1)
+ <The search is done case-sensitive.
+ For pattern searches use |match()|.
+ -1 is returned if the {needle} does not occur in {haystack}.
+ See also |strridx()|.
+ Examples: >vim
+ echo stridx("An Example", "Example") " 3
+ echo stridx("Starting point", "Start") " 0
+ echo stridx("Starting point", "start") " -1
+ < *strstr()* *strchr()*
+ stridx() works similar to the C function strstr(). When used
+ with a single character it works similar to strchr().
+
+ ]=],
+ fast = true,
+ name = 'stridx',
+ params = { { 'haystack', 'string' }, { 'needle', 'string' }, { 'start', 'integer' } },
+ returns = 'integer',
+ signature = 'stridx({haystack}, {needle} [, {start}])',
+ },
+ string = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return {expr} converted to a String. If {expr} is a Number,
+ Float, String, Blob or a composition of them, then the result
+ can be parsed back with |eval()|.
+ {expr} type result ~
+ String 'string'
+ Number 123
+ Float 123.123456 or 1.123456e8 or
+ `str2float('inf')`
+ Funcref `function('name')`
+ Blob 0z00112233.44556677.8899
+ List [item, item]
+ Dictionary `{key: value, key: value}`
+ Note that in String values the ' character is doubled.
+ Also see |strtrans()|.
+ Note 2: Output format is mostly compatible with YAML, except
+ for infinite and NaN floating-point values representations
+ which use |str2float()|. Strings are also dumped literally,
+ only single quote is escaped, which does not allow using YAML
+ for parsing back binary strings. |eval()| should always work for
+ strings and floats though and this is the only official
+ method, use |msgpackdump()| or |json_encode()| if you need to
+ share data with other application.
+
+ ]=],
+ name = 'string',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'string({expr})',
+ },
+ strlen = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the length of the String
+ {string} in bytes.
+ If the argument is a Number it is first converted to a String.
+ For other types an error is given and zero is returned.
+ If you want to count the number of multibyte characters use
+ |strchars()|.
+ Also see |len()|, |strdisplaywidth()| and |strwidth()|.
+
+ ]=],
+ name = 'strlen',
+ params = { { 'string', 'string' } },
+ returns = 'integer',
+ signature = 'strlen({string})',
+ },
+ strpart = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is part of {src}, starting from
+ byte {start}, with the byte length {len}.
+ When {chars} is present and TRUE then {len} is the number of
+ characters positions (composing characters are not counted
+ separately, thus "1" means one base character and any
+ following composing characters).
+ To count {start} as characters instead of bytes use
+ |strcharpart()|.
+
+ When bytes are selected which do not exist, this doesn't
+ result in an error, the bytes are simply omitted.
+ If {len} is missing, the copy continues from {start} till the
+ end of the {src}. >vim
+ echo strpart("abcdefg", 3, 2) " returns 'de'
+ echo strpart("abcdefg", -2, 4) " returns 'ab'
+ echo strpart("abcdefg", 5, 4) " returns 'fg'
+ echo strpart("abcdefg", 3) " returns 'defg'
+
+ <Note: To get the first character, {start} must be 0. For
+ example, to get the character under the cursor: >vim
+ strpart(getline("."), col(".") - 1, 1, v:true)
+ <
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strpart',
+ params = {
+ { 'src', 'string' },
+ { 'start', 'integer' },
+ { 'len', 'integer' },
+ { 'chars', '0|1' },
+ },
+ returns = 'string',
+ signature = 'strpart({src}, {start} [, {len} [, {chars}]])',
+ },
+ strptime = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is a unix timestamp representing
+ the date and time in {timestring}, which is expected to match
+ the format specified in {format}.
+
+ The accepted {format} depends on your system, thus this is not
+ portable! See the manual page of the C function strptime()
+ for the format. Especially avoid "%c". The value of $TZ also
+ matters.
+
+ If the {timestring} cannot be parsed with {format} zero is
+ returned. If you do not know the format of {timestring} you
+ can try different {format} values until you get a non-zero
+ result.
+
+ See also |strftime()|.
+ Examples: >vim
+ echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23")
+ < 862156163 >vim
+ echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55"))
+ < Sun Apr 27 11:53:55 1997 >vim
+ echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600)
+ < Sun Apr 27 12:53:55 1997
+
+ ]=],
+ name = 'strptime',
+ params = { { 'format', 'string' }, { 'timestring', 'string' } },
+ returns = 'integer',
+ signature = 'strptime({format}, {timestring})',
+ },
+ strridx = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which gives the byte index in
+ {haystack} of the last occurrence of the String {needle}.
+ When {start} is specified, matches beyond this index are
+ ignored. This can be used to find a match before a previous
+ match: >vim
+ let lastcomma = strridx(line, ",")
+ let comma2 = strridx(line, ",", lastcomma - 1)
+ <The search is done case-sensitive.
+ For pattern searches use |match()|.
+ -1 is returned if the {needle} does not occur in {haystack}.
+ If the {needle} is empty the length of {haystack} is returned.
+ See also |stridx()|. Examples: >vim
+ echo strridx("an angry armadillo", "an") 3
+ < *strrchr()*
+ When used with a single character it works similar to the C
+ function strrchr().
+
+ ]=],
+ name = 'strridx',
+ params = {
+ { 'haystack', 'string' },
+ { 'needle', 'string' },
+ { 'start', 'integer' },
+ },
+ returns = 'integer',
+ signature = 'strridx({haystack}, {needle} [, {start}])',
+ },
+ strtrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a String, which is {string} with all unprintable
+ characters translated into printable characters |'isprint'|.
+ Like they are shown in a window. Example: >vim
+ echo strtrans(@a)
+ <This displays a newline in register a as "^@" instead of
+ starting a new line.
+
+ Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'strtrans',
+ params = { { 'string', 'string' } },
+ returns = 'string',
+ signature = 'strtrans({string})',
+ },
+ strutf16len = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of UTF-16 code
+ units in String {string} (after converting it to UTF-16).
+
+ When {countcc} is TRUE, composing characters are counted
+ separately.
+ When {countcc} is omitted or FALSE, composing characters are
+ ignored.
+
+ Returns zero on error.
+
+ Also see |strlen()| and |strcharlen()|.
+ Examples: >vim
+ echo strutf16len('a') " returns 1
+ echo strutf16len('©') " returns 1
+ echo strutf16len('😊') " returns 2
+ echo strutf16len('ą́') " returns 1
+ echo strutf16len('ą́', v:true) " returns 3
+ <
+ ]=],
+ name = 'strutf16len',
+ params = { { 'string', 'string' }, { 'countcc', '0|1' } },
+ returns = 'integer',
+ signature = 'strutf16len({string} [, {countcc}])',
+ },
+ strwidth = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of display cells
+ String {string} occupies. A Tab character is counted as one
+ cell, alternatively use |strdisplaywidth()|.
+ When {string} contains characters with East Asian Width Class
+ Ambiguous, this function's return value depends on 'ambiwidth'.
+ Returns zero on error.
+ Also see |strlen()|, |strdisplaywidth()| and |strchars()|.
+
+ ]=],
+ fast = true,
+ name = 'strwidth',
+ params = { { 'string', 'string' } },
+ returns = 'integer',
+ signature = 'strwidth({string})',
+ },
+ submatch = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E935' },
+ desc = [=[
+ Only for an expression in a |:substitute| command or
+ substitute() function.
+ Returns the {nr}th submatch of the matched text. When {nr}
+ is 0 the whole matched text is returned.
+ Note that a NL in the string can stand for a line break of a
+ multi-line match or a NUL character in the text.
+ Also see |sub-replace-expression|.
+
+ If {list} is present and non-zero then submatch() returns
+ a list of strings, similar to |getline()| with two arguments.
+ NL characters in the text represent NUL characters in the
+ text.
+ Only returns more than one item for |:substitute|, inside
+ |substitute()| this list will always contain one or zero
+ items, since there are no real line breaks.
+
+ When substitute() is used recursively only the submatches in
+ the current (deepest) call can be obtained.
+
+ Returns an empty string or list on error.
+
+ Examples: >vim
+ s/\d\+/\=submatch(0) + 1/
+ echo substitute(text, '\d\+', '\=submatch(0) + 1', '')
+ <This finds the first number in the line and adds one to it.
+ A line break is included as a newline character.
+
+ ]=],
+ name = 'submatch',
+ params = { { 'nr', 'integer' }, { 'list', 'integer' } },
+ returns = 'string|string[]',
+ signature = 'submatch({nr} [, {list}])',
+ },
+ substitute = {
+ args = 4,
+ base = 1,
+ desc = [=[
+ The result is a String, which is a copy of {string}, in which
+ the first match of {pat} is replaced with {sub}.
+ When {flags} is "g", all matches of {pat} in {string} are
+ replaced. Otherwise {flags} should be "".
+
+ This works like the ":substitute" command (without any flags).
+ But the matching with {pat} is always done like the 'magic'
+ option is set and 'cpoptions' is empty (to make scripts
+ portable). 'ignorecase' is still relevant, use |/\c| or |/\C|
+ if you want to ignore or match case and ignore 'ignorecase'.
+ 'smartcase' is not used. See |string-match| for how {pat} is
+ used.
+
+ A "~" in {sub} is not replaced with the previous {sub}.
+ Note that some codes in {sub} have a special meaning
+ |sub-replace-special|. For example, to replace something with
+ "\n" (two characters), use "\\\\n" or '\\n'.
+
+ When {pat} does not match in {string}, {string} is returned
+ unmodified.
+
+ Example: >vim
+ let &path = substitute(&path, ",\\=[^,]*$", "", "")
+ <This removes the last component of the 'path' option. >vim
+ echo substitute("testing", ".*", "\\U\\0", "")
+ <results in "TESTING".
+
+ When {sub} starts with "\=", the remainder is interpreted as
+ an expression. See |sub-replace-expression|. Example: >vim
+ echo substitute(s, '%\(\x\x\)',
+ \ '\=nr2char("0x" .. submatch(1))', 'g')
+
+ <When {sub} is a Funcref that function is called, with one
+ optional argument. Example: >vim
+ echo substitute(s, '%\(\x\x\)', SubNr, 'g')
+ <The optional argument is a list which contains the whole
+ matched string and up to nine submatches, like what
+ |submatch()| returns. Example: >vim
+ echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g')
+
+ <Returns an empty string on error.
+
+ ]=],
+ name = 'substitute',
+ params = {
+ { 'string', 'string' },
+ { 'pat', 'string' },
+ { 'sub', 'string' },
+ { 'flags', 'string' },
+ },
+ returns = 'string',
+ signature = 'substitute({string}, {pat}, {sub}, {flags})',
+ },
+ swapfilelist = {
+ desc = [=[
+ Returns a list of swap file names, like what "vim -r" shows.
+ See the |-r| command argument. The 'directory' option is used
+ for the directories to inspect. If you only want to get a
+ list of swap files in the current directory then temporarily
+ set 'directory' to a dot: >vim
+ let save_dir = &directory
+ let &directory = '.'
+ let swapfiles = swapfilelist()
+ let &directory = save_dir
+ ]=],
+ name = 'swapfilelist',
+ params = {},
+ returns = 'string[]',
+ signature = 'swapfilelist()',
+ },
+ swapinfo = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a dictionary, which holds information about the
+ swapfile {fname}. The available fields are:
+ version Vim version
+ user user name
+ host host name
+ fname original file name
+ pid PID of the Nvim process that created the swap
+ file, or zero if not running.
+ mtime last modification time in seconds
+ inode Optional: INODE number of the file
+ dirty 1 if file was modified, 0 if not
+ In case of failure an "error" item is added with the reason:
+ Cannot open file: file not found or in accessible
+ Cannot read file: cannot read first block
+ Not a swap file: does not contain correct block ID
+ Magic number mismatch: Info in first block is invalid
+
+ ]=],
+ name = 'swapinfo',
+ params = { { 'fname', 'string' } },
+ signature = 'swapinfo({fname})',
+ },
+ swapname = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is the swap file path of the buffer {buf}.
+ For the use of {buf}, see |bufname()| above.
+ If buffer {buf} is the current buffer, the result is equal to
+ |:swapname| (unless there is no swap file).
+ If buffer {buf} has no swap file, returns an empty string.
+
+ ]=],
+ name = 'swapname',
+ params = { { 'buf', 'integer|string' } },
+ returns = 'string',
+ signature = 'swapname({buf})',
+ },
+ synID = {
+ args = 3,
+ desc = [=[
+ The result is a Number, which is the syntax ID at the position
+ {lnum} and {col} in the current window.
+ The syntax ID can be used with |synIDattr()| and
+ |synIDtrans()| to obtain syntax information about text.
+
+ {col} is 1 for the leftmost column, {lnum} is 1 for the first
+ line. 'synmaxcol' applies, in a longer line zero is returned.
+ Note that when the position is after the last character,
+ that's where the cursor can be in Insert mode, synID() returns
+ zero. {lnum} is used like with |getline()|.
+
+ When {trans} is |TRUE|, transparent items are reduced to the
+ item that they reveal. This is useful when wanting to know
+ the effective color. When {trans} is |FALSE|, the transparent
+ item is returned. This is useful when wanting to know which
+ syntax item is effective (e.g. inside parens).
+ Warning: This function can be very slow. Best speed is
+ obtained by going through the file in forward direction.
+
+ Returns zero on error.
+
+ Example (echoes the name of the syntax item under the cursor): >vim
+ echo synIDattr(synID(line("."), col("."), 1), "name")
+ <
+ ]=],
+ name = 'synID',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'trans', '0|1' } },
+ returns = 'integer',
+ signature = 'synID({lnum}, {col}, {trans})',
+ },
+ synIDattr = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ The result is a String, which is the {what} attribute of
+ syntax ID {synID}. This can be used to obtain information
+ about a syntax item.
+ {mode} can be "gui" or "cterm", to get the attributes
+ for that mode. When {mode} is omitted, or an invalid value is
+ used, the attributes for the currently active highlighting are
+ used (GUI or cterm).
+ Use synIDtrans() to follow linked highlight groups.
+ {what} result
+ "name" the name of the syntax item
+ "fg" foreground color (GUI: color name used to set
+ the color, cterm: color number as a string,
+ term: empty string)
+ "bg" background color (as with "fg")
+ "font" font name (only available in the GUI)
+ |highlight-font|
+ "sp" special color (as with "fg") |guisp|
+ "fg#" like "fg", but for the GUI and the GUI is
+ running the name in "#RRGGBB" form
+ "bg#" like "fg#" for "bg"
+ "sp#" like "fg#" for "sp"
+ "bold" "1" if bold
+ "italic" "1" if italic
+ "reverse" "1" if reverse
+ "inverse" "1" if inverse (= reverse)
+ "standout" "1" if standout
+ "underline" "1" if underlined
+ "undercurl" "1" if undercurled
+ "underdouble" "1" if double underlined
+ "underdotted" "1" if dotted underlined
+ "underdashed" "1" if dashed underlined
+ "strikethrough" "1" if struckthrough
+ "altfont" "1" if alternative font
+ "nocombine" "1" if nocombine
+
+ Returns an empty string on error.
+
+ Example (echoes the color of the syntax item under the
+ cursor): >vim
+ echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg")
+ <
+ Can also be used as a |method|: >vim
+ echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg")
+ <
+ ]=],
+ name = 'synIDattr',
+ params = { { 'synID', 'integer' }, { 'what', 'string' }, { 'mode', 'string' } },
+ returns = 'string',
+ signature = 'synIDattr({synID}, {what} [, {mode}])',
+ },
+ synIDtrans = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the translated syntax ID of
+ {synID}. This is the syntax group ID of what is being used to
+ highlight the character. Highlight links given with
+ ":highlight link" are followed.
+
+ Returns zero on error.
+
+ ]=],
+ name = 'synIDtrans',
+ params = { { 'synID', 'integer' } },
+ returns = 'integer',
+ signature = 'synIDtrans({synID})',
+ },
+ synconcealed = {
+ args = 2,
+ desc = [=[
+ The result is a |List| with currently three items:
+ 1. The first item in the list is 0 if the character at the
+ position {lnum} and {col} is not part of a concealable
+ region, 1 if it is. {lnum} is used like with |getline()|.
+ 2. The second item in the list is a string. If the first item
+ is 1, the second item contains the text which will be
+ displayed in place of the concealed text, depending on the
+ current setting of 'conceallevel' and 'listchars'.
+ 3. The third and final item in the list is a number
+ representing the specific syntax region matched in the
+ line. When the character is not concealed the value is
+ zero. This allows detection of the beginning of a new
+ concealable region if there are two consecutive regions
+ with the same replacement character. For an example, if
+ the text is "123456" and both "23" and "45" are concealed
+ and replaced by the character "X", then:
+ call returns ~
+ synconcealed(lnum, 1) [0, '', 0]
+ synconcealed(lnum, 2) [1, 'X', 1]
+ synconcealed(lnum, 3) [1, 'X', 1]
+ synconcealed(lnum, 4) [1, 'X', 2]
+ synconcealed(lnum, 5) [1, 'X', 2]
+ synconcealed(lnum, 6) [0, '', 0]
+ ]=],
+ name = 'synconcealed',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ returns = '{[1]: integer, [2]: string, [3]: integer}[]',
+ signature = 'synconcealed({lnum}, {col})',
+ },
+ synstack = {
+ args = 2,
+ desc = [=[
+ Return a |List|, which is the stack of syntax items at the
+ position {lnum} and {col} in the current window. {lnum} is
+ used like with |getline()|. Each item in the List is an ID
+ like what |synID()| returns.
+ The first item in the List is the outer region, following are
+ items contained in that one. The last one is what |synID()|
+ returns, unless not the whole item is highlighted or it is a
+ transparent item.
+ This function is useful for debugging a syntax file.
+ Example that shows the syntax stack under the cursor: >vim
+ for id in synstack(line("."), col("."))
+ echo synIDattr(id, "name")
+ endfor
+ <When the position specified with {lnum} and {col} is invalid
+ an empty list is returned. The position just after the last
+ character in a line and the first column in an empty line are
+ valid positions.
+ ]=],
+ name = 'synstack',
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
+ returns = 'integer[]',
+ signature = 'synstack({lnum}, {col})',
+ },
+ system = {
+ args = { 1, 2 },
+ base = 1,
+ tags = { 'E677' },
+ desc = [=[
+ Note: Prefer |vim.system()| in Lua.
+
+ Gets the output of {cmd} as a |string| (|systemlist()| returns
+ a |List|) and sets |v:shell_error| to the error code.
+ {cmd} is treated as in |jobstart()|:
+ If {cmd} is a List it runs directly (no 'shell').
+ If {cmd} is a String it runs in the 'shell', like this: >vim
+ call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}'])
+
+ <Not to be used for interactive commands.
+
+ Result is a String, filtered to avoid platform-specific quirks:
+ - <CR><NL> is replaced with <NL>
+ - NUL characters are replaced with SOH (0x01)
+
+ Example: >vim
+ echo system(['ls', expand('%:h')])
+
+ <If {input} is a string it is written to a pipe and passed as
+ stdin to the command. The string is written as-is, line
+ separators are not changed.
+ If {input} is a |List| it is written to the pipe as
+ |writefile()| does with {binary} set to "b" (i.e. with
+ a newline between each list item, and newlines inside list
+ items converted to NULs).
+ When {input} is given and is a valid buffer id, the content of
+ the buffer is written to the file line by line, each line
+ terminated by NL (and NUL where the text has NL).
+ *E5677*
+ Note: system() cannot write to or read from backgrounded ("&")
+ shell commands, e.g.: >vim
+ echo system("cat - &", "foo")
+ <which is equivalent to: >
+ $ echo foo | bash -c 'cat - &'
+ <The pipes are disconnected (unless overridden by shell
+ redirection syntax) before input can reach it. Use
+ |jobstart()| instead.
+
+ Note: Use |shellescape()| or |::S| with |expand()| or
+ |fnamemodify()| to escape special characters in a command
+ argument. 'shellquote' and 'shellxquote' must be properly
+ configured. Example: >vim
+ echo system('ls '..shellescape(expand('%:h')))
+ echo system('ls '..expand('%:h:S'))
+
+ <Unlike ":!cmd" there is no automatic check for changed files.
+ Use |:checktime| to force a check.
+
+ ]=],
+ name = 'system',
+ params = {
+ { 'cmd', 'string|string[]' },
+ { 'input', 'string|string[]|integer' },
+ },
+ returns = 'string',
+ signature = 'system({cmd} [, {input}])',
+ },
+ systemlist = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Same as |system()|, but returns a |List| with lines (parts of
+ output separated by NL) with NULs transformed into NLs. Output
+ is the same as |readfile()| will output with {binary} argument
+ set to "b", except that a final newline is not preserved,
+ unless {keepempty} is non-zero.
+ Note that on MS-Windows you may get trailing CR characters.
+
+ To see the difference between "echo hello" and "echo -n hello"
+ use |system()| and |split()|: >vim
+ echo split(system('echo hello'), '\n', 1)
+ <
+ Returns an empty string on error.
+
+ ]=],
+ name = 'systemlist',
+ params = {
+ { 'cmd', 'string|string[]' },
+ { 'input', 'string|string[]|integer' },
+ { 'keepempty', 'integer' },
+ },
+ -- TODO(lewis6991): Not sure the '' return case is possible via vim.fn
+ -- returns = "string[]|''",
+ returns = 'string[]',
+ signature = 'systemlist({cmd} [, {input} [, {keepempty}]])',
+ },
+ tabpagebuflist = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a |List|, where each item is the number of the
+ buffer associated with each window in the current tab page.
+ {arg} specifies the number of the tab page to be used. When
+ omitted the current tab page is used.
+ When {arg} is invalid the number zero is returned.
+ To get a list of all buffers in all tabs use this: >vim
+ let buflist = []
+ for i in range(tabpagenr('$'))
+ call extend(buflist, tabpagebuflist(i + 1))
+ endfor
+ <Note that a buffer may appear in more than one window.
+
+ ]=],
+ name = 'tabpagebuflist',
+ params = { { 'arg', 'any' } },
+ signature = 'tabpagebuflist([{arg}])',
+ },
+ tabpagenr = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is a Number, which is the number of the current
+ tab page. The first tab page has number 1.
+
+ The optional argument {arg} supports the following values:
+ $ the number of the last tab page (the tab page
+ count).
+ # the number of the last accessed tab page
+ (where |g<Tab>| goes to). If there is no
+ previous tab page, 0 is returned.
+ The number can be used with the |:tab| command.
+
+ Returns zero on error.
+ ]=],
+ name = 'tabpagenr',
+ params = { { 'arg', "'$'|'#'" } },
+ returns = 'integer',
+ signature = 'tabpagenr([{arg}])',
+ },
+ tabpagewinnr = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Like |winnr()| but for tab page {tabarg}.
+ {tabarg} specifies the number of tab page to be used.
+ {arg} is used like with |winnr()|:
+ - When omitted the current window number is returned. This is
+ the window which will be used when going to this tab page.
+ - When "$" the number of windows is returned.
+ - When "#" the previous window nr is returned.
+ Useful examples: >vim
+ tabpagewinnr(1) " current window of tab page 1
+ tabpagewinnr(4, '$') " number of windows in tab page 4
+ <When {tabarg} is invalid zero is returned.
+
+ ]=],
+ name = 'tabpagewinnr',
+ params = { { 'tabarg', 'integer' }, { 'arg', "'$'|'#'" } },
+ returns = 'integer',
+ signature = 'tabpagewinnr({tabarg} [, {arg}])',
+ },
+ tagfiles = {
+ desc = [=[
+ Returns a |List| with the file names used to search for tags
+ for the current buffer. This is the 'tags' option expanded.
+ ]=],
+ name = 'tagfiles',
+ params = {},
+ returns = 'string[]',
+ signature = 'tagfiles()',
+ },
+ taglist = {
+ args = { 1, 2 },
+ base = 1,
+ desc = [=[
+ Returns a |List| of tags matching the regular expression {expr}.
+
+ If {filename} is passed it is used to prioritize the results
+ in the same way that |:tselect| does. See |tag-priority|.
+ {filename} should be the full path of the file.
+
+ Each list item is a dictionary with at least the following
+ entries:
+ name Name of the tag.
+ filename Name of the file where the tag is
+ defined. It is either relative to the
+ current directory or a full path.
+ cmd Ex command used to locate the tag in
+ the file.
+ kind Type of the tag. The value for this
+ entry depends on the language specific
+ kind values. Only available when
+ using a tags file generated by
+ Universal/Exuberant ctags or hdrtag.
+ static A file specific tag. Refer to
+ |static-tag| for more information.
+ More entries may be present, depending on the content of the
+ tags file: access, implementation, inherits and signature.
+ Refer to the ctags documentation for information about these
+ fields. For C code the fields "struct", "class" and "enum"
+ may appear, they give the name of the entity the tag is
+ contained in.
+
+ The ex-command "cmd" can be either an ex search pattern, a
+ line number or a line number followed by a byte number.
+
+ If there are no matching tags, then an empty list is returned.
+
+ To get an exact tag match, the anchors '^' and '$' should be
+ used in {expr}. This also make the function work faster.
+ Refer to |tag-regexp| for more information about the tag
+ search regular expression pattern.
+
+ Refer to |'tags'| for information about how the tags file is
+ located by Vim. Refer to |tags-file-format| for the format of
+ the tags file generated by the different ctags tools.
+
+ ]=],
+ name = 'taglist',
+ params = { { 'expr', 'any' }, { 'filename', 'string' } },
+ signature = 'taglist({expr} [, {filename}])',
+ },
+ tan = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the tangent of {expr}, measured in radians, as a |Float|
+ in the range [-inf, inf].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo tan(10)
+ < 0.648361 >vim
+ echo tan(-4.01)
+ < -1.181502
+
+ ]=],
+ float_func = 'tan',
+ name = 'tan',
+ params = { { 'expr', 'number' } },
+ returns = 'number',
+ signature = 'tan({expr})',
+ },
+ tanh = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the hyperbolic tangent of {expr} as a |Float| in the
+ range [-1, 1].
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo tanh(0.5)
+ < 0.462117 >vim
+ echo tanh(-1)
+ < -0.761594
+
+ ]=],
+ float_func = 'tanh',
+ name = 'tanh',
+ params = { { 'expr', 'number' } },
+ returns = 'number',
+ signature = 'tanh({expr})',
+ },
+ tempname = {
+ desc = [=[
+ Generates a (non-existent) filename located in the Nvim root
+ |tempdir|. Scripts can use the filename as a temporary file.
+ Example: >vim
+ let tmpfile = tempname()
+ exe "redir > " .. tmpfile
+ <
+ ]=],
+ name = 'tempname',
+ params = {},
+ returns = 'string',
+ signature = 'tempname()',
+ },
+ termopen = {
+ args = { 1, 2 },
+ desc = [=[
+ Spawns {cmd} in a new pseudo-terminal session connected
+ to the current (unmodified) buffer. Parameters and behavior
+ are the same as |jobstart()| except "pty", "width", "height",
+ and "TERM" are ignored: "height" and "width" are taken from
+ the current window. Note that termopen() implies a "pty" arg
+ to jobstart(), and thus has the implications documented at
+ |jobstart()|.
+
+ Returns the same values as jobstart().
+
+ Terminal environment is initialized as in |jobstart-env|,
+ except $TERM is set to "xterm-256color". Full behavior is
+ described in |terminal|.
+ ]=],
+ name = 'termopen',
+ params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ signature = 'termopen({cmd} [, {opts}])',
+ },
+ test_garbagecollect_now = {
+ args = 0,
+ desc = [=[
+ Like |garbagecollect()|, but executed right away. This must
+ only be called directly to avoid any structure to exist
+ internally, and |v:testing| must have been set before calling
+ any function.
+ ]=],
+ params = {},
+ signature = 'test_garbagecollect_now()',
+ lua = false,
+ },
+ test_write_list_log = {
+ args = 1,
+ params = { { 'fname', 'string' } },
+ signature = '',
+ lua = false,
+ },
+ timer_info = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return a list with information about timers.
+ When {id} is given only information about this timer is
+ returned. When timer {id} does not exist an empty list is
+ returned.
+ When {id} is omitted information about all timers is returned.
+
+ For each timer the information is stored in a |Dictionary| with
+ these items:
+ "id" the timer ID
+ "time" time the timer was started with
+ "repeat" number of times the timer will still fire;
+ -1 means forever
+ "callback" the callback
+
+ ]=],
+ name = 'timer_info',
+ params = { { 'id', 'any' } },
+ signature = 'timer_info([{id}])',
+ },
+ timer_pause = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Pause or unpause a timer. A paused timer does not invoke its
+ callback when its time expires. Unpausing a timer may cause
+ the callback to be invoked almost immediately if enough time
+ has passed.
+
+ Pausing a timer is useful to avoid the callback to be called
+ for a short time.
+
+ If {paused} evaluates to a non-zero Number or a non-empty
+ String, then the timer is paused, otherwise it is unpaused.
+ See |non-zero-arg|.
+
+ ]=],
+ name = 'timer_pause',
+ params = { { 'timer', 'any' }, { 'paused', 'any' } },
+ signature = 'timer_pause({timer}, {paused})',
+ },
+ timer_start = {
+ args = { 2, 3 },
+ base = 1,
+ tags = { 'timer' },
+ desc = [=[
+ Create a timer and return the timer ID.
+
+ {time} is the waiting time in milliseconds. This is the
+ minimum time before invoking the callback. When the system is
+ busy or Vim is not waiting for input the time will be longer.
+ Zero can be used to execute the callback when Vim is back in
+ the main loop.
+
+ {callback} is the function to call. It can be the name of a
+ function or a |Funcref|. It is called with one argument, which
+ is the timer ID. The callback is only invoked when Vim is
+ waiting for input.
+
+ {options} is a dictionary. Supported entries:
+ "repeat" Number of times to repeat the callback.
+ -1 means forever. Default is 1.
+ If the timer causes an error three times in a
+ row the repeat is cancelled.
+
+ Returns -1 on error.
+
+ Example: >vim
+ func MyHandler(timer)
+ echo 'Handler called'
+ endfunc
+ let timer = timer_start(500, 'MyHandler',
+ \ {'repeat': 3})
+ <This invokes MyHandler() three times at 500 msec intervals.
+
+ ]=],
+ name = 'timer_start',
+ params = { { 'time', 'any' }, { 'callback', 'any' }, { 'options', 'table' } },
+ signature = 'timer_start({time}, {callback} [, {options}])',
+ },
+ timer_stop = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Stop a timer. The timer callback will no longer be invoked.
+ {timer} is an ID returned by timer_start(), thus it must be a
+ Number. If {timer} does not exist there is no error.
+
+ ]=],
+ name = 'timer_stop',
+ params = { { 'timer', 'any' } },
+ signature = 'timer_stop({timer})',
+ },
+ timer_stopall = {
+ args = 0,
+ desc = [=[
+ Stop all timers. The timer callbacks will no longer be
+ invoked. Useful if some timers is misbehaving. If there are
+ no timers there is no error.
+ ]=],
+ name = 'timer_stopall',
+ params = {},
+ signature = 'timer_stopall()',
+ },
+ tolower = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a copy of the String given, with all uppercase
+ characters turned into lowercase (just like applying |gu| to
+ the string). Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'tolower',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'tolower({expr})',
+ },
+ toupper = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a copy of the String given, with all lowercase
+ characters turned into uppercase (just like applying |gU| to
+ the string). Returns an empty string on error.
+
+ ]=],
+ fast = true,
+ name = 'toupper',
+ params = { { 'expr', 'any' } },
+ returns = 'string',
+ signature = 'toupper({expr})',
+ },
+ tr = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a copy of the {src} string with all characters
+ which appear in {fromstr} replaced by the character in that
+ position in the {tostr} string. Thus the first character in
+ {fromstr} is translated into the first character in {tostr}
+ and so on. Exactly like the unix "tr" command.
+ This code also deals with multibyte characters properly.
+
+ Returns an empty string on error.
+
+ Examples: >vim
+ echo tr("hello there", "ht", "HT")
+ <returns "Hello THere" >vim
+ echo tr("<blob>", "<>", "{}")
+ <returns "{blob}"
+
+ ]=],
+ name = 'tr',
+ params = { { 'src', 'string' }, { 'fromstr', 'string' }, { 'tostr', 'string' } },
+ returns = 'string',
+ signature = 'tr({src}, {fromstr}, {tostr})',
+ },
+ trim = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ Return {text} as a String where any character in {mask} is
+ removed from the beginning and/or end of {text}.
+
+ If {mask} is not given, or is an empty string, {mask} is all
+ characters up to 0x20, which includes Tab, space, NL and CR,
+ plus the non-breaking space character 0xa0.
+
+ The optional {dir} argument specifies where to remove the
+ characters:
+ 0 remove from the beginning and end of {text}
+ 1 remove only at the beginning of {text}
+ 2 remove only at the end of {text}
+ When omitted both ends are trimmed.
+
+ This function deals with multibyte characters properly.
+ Returns an empty string on error.
+
+ Examples: >vim
+ echo trim(" some text ")
+ <returns "some text" >vim
+ echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") .. "_TAIL"
+ <returns "RESERVE_TAIL" >vim
+ echo trim("rm<Xrm<>X>rrm", "rm<>")
+ <returns "Xrm<>X" (characters in the middle are not removed) >vim
+ echo trim(" vim ", " ", 2)
+ <returns " vim"
+
+ ]=],
+ name = 'trim',
+ params = { { 'text', 'any' }, { 'mask', 'string' }, { 'dir', '0|1|2' } },
+ returns = 'string',
+ signature = 'trim({text} [, {mask} [, {dir}]])',
+ },
+ trunc = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the largest integral value with magnitude less than or
+ equal to {expr} as a |Float| (truncate towards zero).
+ {expr} must evaluate to a |Float| or a |Number|.
+ Returns 0.0 if {expr} is not a |Float| or a |Number|.
+ Examples: >vim
+ echo trunc(1.456)
+ < 1.0 >vim
+ echo trunc(-5.456)
+ < -5.0 >vim
+ echo trunc(4.0)
+ < 4.0
+
+ ]=],
+ float_func = 'trunc',
+ name = 'trunc',
+ params = { { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'trunc({expr})',
+ },
+ type = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number representing the type of {expr}.
+ Instead of using the number directly, it is better to use the
+ v:t_ variable that has the value:
+ Number: 0 |v:t_number|
+ String: 1 |v:t_string|
+ Funcref: 2 |v:t_func|
+ List: 3 |v:t_list|
+ Dictionary: 4 |v:t_dict|
+ Float: 5 |v:t_float|
+ Boolean: 6 |v:t_bool| (|v:false| and |v:true|)
+ Null: 7 (|v:null|)
+ Blob: 10 |v:t_blob|
+ For backward compatibility, this method can be used: >vim
+ if type(myvar) == type(0) | endif
+ if type(myvar) == type("") | endif
+ if type(myvar) == type(function("tr")) | endif
+ if type(myvar) == type([]) | endif
+ if type(myvar) == type({}) | endif
+ if type(myvar) == type(0.0) | endif
+ if type(myvar) == type(v:true) | endif
+ <In place of checking for |v:null| type it is better to check
+ for |v:null| directly as it is the only value of this type: >vim
+ if myvar is v:null | endif
+ <To check if the v:t_ variables exist use this: >vim
+ if exists('v:t_number') | endif
+
+ ]=],
+ fast = true,
+ name = 'type',
+ params = { { 'expr', 'any' } },
+ returns = 'integer',
+ signature = 'type({expr})',
+ },
+ undofile = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the name of the undo file that would be used for a file
+ with name {name} when writing. This uses the 'undodir'
+ option, finding directories that exist. It does not check if
+ the undo file exists.
+ {name} is always expanded to the full path, since that is what
+ is used internally.
+ If {name} is empty undofile() returns an empty string, since a
+ buffer without a file name will not write an undo file.
+ Useful in combination with |:wundo| and |:rundo|.
+
+ ]=],
+ name = 'undofile',
+ params = { { 'name', 'string' } },
+ returns = 'string',
+ signature = 'undofile({name})',
+ },
+ undotree = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return the current state of the undo tree for the current
+ buffer, or for a specific buffer if {buf} is given. The
+ result is a dictionary with the following items:
+ "seq_last" The highest undo sequence number used.
+ "seq_cur" The sequence number of the current position in
+ the undo tree. This differs from "seq_last"
+ when some changes were undone.
+ "time_cur" Time last used for |:earlier| and related
+ commands. Use |strftime()| to convert to
+ something readable.
+ "save_last" Number of the last file write. Zero when no
+ write yet.
+ "save_cur" Number of the current position in the undo
+ tree.
+ "synced" Non-zero when the last undo block was synced.
+ This happens when waiting from input from the
+ user. See |undo-blocks|.
+ "entries" A list of dictionaries with information about
+ undo blocks.
+
+ The first item in the "entries" list is the oldest undo item.
+ Each List item is a |Dictionary| with these items:
+ "seq" Undo sequence number. Same as what appears in
+ |:undolist|.
+ "time" Timestamp when the change happened. Use
+ |strftime()| to convert to something readable.
+ "newhead" Only appears in the item that is the last one
+ that was added. This marks the last change
+ and where further changes will be added.
+ "curhead" Only appears in the item that is the last one
+ that was undone. This marks the current
+ position in the undo tree, the block that will
+ be used by a redo command. When nothing was
+ undone after the last change this item will
+ not appear anywhere.
+ "save" Only appears on the last block before a file
+ write. The number is the write count. The
+ first write has number 1, the last one the
+ "save_last" mentioned above.
+ "alt" Alternate entry. This is again a List of undo
+ blocks. Each item may again have an "alt"
+ item.
+ ]=],
+ name = 'undotree',
+ params = { { 'buf', 'integer|string' } },
+ signature = 'undotree([{buf}])',
+ },
+ uniq = {
+ args = { 1, 3 },
+ base = 1,
+ tags = { 'E882' },
+ desc = [=[
+ Remove second and succeeding copies of repeated adjacent
+ {list} items in-place. Returns {list}. If you want a list
+ to remain unmodified make a copy first: >vim
+ let newlist = uniq(copy(mylist))
+ <The default compare function uses the string representation of
+ each item. For the use of {func} and {dict} see |sort()|.
+
+ Returns zero if {list} is not a |List|.
+
+ ]=],
+ name = 'uniq',
+ params = { { 'list', 'any' }, { 'func', 'any' }, { 'dict', 'any' } },
+ returns = 'any[]|0',
+ signature = 'uniq({list} [, {func} [, {dict}]])',
+ },
+ utf16idx = {
+ args = { 2, 4 },
+ base = 1,
+ desc = [=[
+ Same as |charidx()| but returns the UTF-16 code unit index of
+ the byte at {idx} in {string} (after converting it to UTF-16).
+
+ When {charidx} is present and TRUE, {idx} is used as the
+ character index in the String {string} instead of as the byte
+ index.
+ An {idx} in the middle of a UTF-8 sequence is rounded
+ downwards to the beginning of that sequence.
+
+ Returns -1 if the arguments are invalid or if there are less
+ than {idx} bytes in {string}. If there are exactly {idx} bytes
+ the length of the string in UTF-16 code units is returned.
+
+ See |byteidx()| and |byteidxcomp()| for getting the byte index
+ from the UTF-16 index and |charidx()| for getting the
+ character index from the UTF-16 index.
+ Refer to |string-offset-encoding| for more information.
+ Examples: >vim
+ echo utf16idx('a😊😊', 3) " returns 2
+ echo utf16idx('a😊😊', 7) " returns 4
+ echo utf16idx('a😊😊', 1, 0, 1) " returns 2
+ echo utf16idx('a😊😊', 2, 0, 1) " returns 4
+ echo utf16idx('aą́c', 6) " returns 2
+ echo utf16idx('aą́c', 6, 1) " returns 4
+ echo utf16idx('a😊😊', 9) " returns -1
+ <
+ ]=],
+ name = 'utf16idx',
+ params = {
+ { 'string', 'string' },
+ { 'idx', 'integer' },
+ { 'countcc', 'any' },
+ { 'charidx', 'any' },
+ },
+ returns = 'integer',
+ signature = 'utf16idx({string}, {idx} [, {countcc} [, {charidx}]])',
+ },
+ values = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a |List| with all the values of {dict}. The |List| is
+ in arbitrary order. Also see |items()| and |keys()|.
+ Returns zero if {dict} is not a |Dict|.
+
+ ]=],
+ name = 'values',
+ params = { { 'dict', 'any' } },
+ signature = 'values({dict})',
+ },
+ virtcol = {
+ args = { 1, 3 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the screen column of the file
+ position given with {expr}. That is, the last screen position
+ occupied by the character at that position, when the screen
+ would be of unlimited width. When there is a <Tab> at the
+ position, the returned Number will be the column at the end of
+ the <Tab>. For example, for a <Tab> in column 1, with 'ts'
+ set to 8, it returns 8. |conceal| is ignored.
+ For the byte position use |col()|.
+
+ For the use of {expr} see |col()|.
+
+ When 'virtualedit' is used {expr} can be [lnum, col, off],
+ where "off" is the offset in screen columns from the start of
+ the character. E.g., a position within a <Tab> or after the
+ last character. When "off" is omitted zero is used. When
+ Virtual editing is active in the current mode, a position
+ beyond the end of the line can be returned. Also see
+ |'virtualedit'|
+
+ The accepted positions are:
+ . the cursor position
+ $ the end of the cursor line (the result is the
+ number of displayed characters in the cursor line
+ plus one)
+ 'x position of mark x (if the mark is not set, 0 is
+ returned)
+ v In Visual mode: the start of the Visual area (the
+ cursor is the end). When not in Visual mode
+ returns the cursor position. Differs from |'<| in
+ that it's updated right away.
+
+ If {list} is present and non-zero then virtcol() returns a
+ List with the first and last screen position occupied by the
+ character.
+
+ With the optional {winid} argument the values are obtained for
+ that window instead of the current window.
+
+ Note that only marks in the current file can be used.
+ Examples: >vim
+ " With text "foo^Lbar" and cursor on the "^L":
+
+ echo virtcol(".") " returns 5
+ echo virtcol(".", 1) " returns [4, 5]
+ echo virtcol("$") " returns 9
+
+ " With text " there", with 't at 'h':
+
+ echo virtcol("'t") " returns 6
+ <The first column is 1. 0 or [0, 0] is returned for an error.
+ A more advanced example that echoes the maximum length of
+ all lines: >vim
+ echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
+
+ ]=],
+ name = 'virtcol',
+ params = { { 'expr', 'any' }, { 'list', 'any' }, { 'winid', 'integer' } },
+ signature = 'virtcol({expr} [, {list} [, {winid}]])',
+ },
+ virtcol2col = {
+ args = 3,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the byte index of the
+ character in window {winid} at buffer line {lnum} and virtual
+ column {col}.
+
+ If buffer line {lnum} is an empty line, 0 is returned.
+
+ If {col} is greater than the last virtual column in line
+ {lnum}, then the byte index of the character at the last
+ virtual column is returned.
+
+ For a multi-byte character, the column number of the first
+ byte in the character is returned.
+
+ The {winid} argument can be the window number or the
+ |window-ID|. If this is zero, then the current window is used.
+
+ Returns -1 if the window {winid} doesn't exist or the buffer
+ line {lnum} or virtual column {col} is invalid.
+
+ See also |screenpos()|, |virtcol()| and |col()|.
+
+ ]=],
+ name = 'virtcol2col',
+ params = { { 'winid', 'integer' }, { 'lnum', 'integer' }, { 'col', 'integer' } },
+ signature = 'virtcol2col({winid}, {lnum}, {col})',
+ },
+ visualmode = {
+ args = { 0, 1 },
+ desc = [=[
+ The result is a String, which describes the last Visual mode
+ used in the current buffer. Initially it returns an empty
+ string, but once Visual mode has been used, it returns "v",
+ "V", or "<CTRL-V>" (a single CTRL-V character) for
+ character-wise, line-wise, or block-wise Visual mode
+ respectively.
+ Example: >vim
+ exe "normal " .. visualmode()
+ <This enters the same Visual mode as before. It is also useful
+ in scripts if you wish to act differently depending on the
+ Visual mode that was used.
+ If Visual mode is active, use |mode()| to get the Visual mode
+ (e.g., in a |:vmap|).
+ If {expr} is supplied and it evaluates to a non-zero Number or
+ a non-empty String, then the Visual mode will be cleared and
+ the old value is returned. See |non-zero-arg|.
+ ]=],
+ name = 'visualmode',
+ params = { { 'expr', 'any' } },
+ signature = 'visualmode([{expr}])',
+ },
+ wait = {
+ args = { 2, 3 },
+ desc = [=[
+ Waits until {condition} evaluates to |TRUE|, where {condition}
+ is a |Funcref| or |string| containing an expression.
+
+ {timeout} is the maximum waiting time in milliseconds, -1
+ means forever.
+
+ Condition is evaluated on user events, internal events, and
+ every {interval} milliseconds (default: 200).
+
+ Returns a status integer:
+ 0 if the condition was satisfied before timeout
+ -1 if the timeout was exceeded
+ -2 if the function was interrupted (by |CTRL-C|)
+ -3 if an error occurred
+ ]=],
+ name = 'wait',
+ params = { { 'timeout', 'integer' }, { 'condition', 'any' }, { 'interval', 'any' } },
+ signature = 'wait({timeout}, {condition} [, {interval}])',
+ },
+ wildmenumode = {
+ desc = [=[
+ Returns |TRUE| when the wildmenu is active and |FALSE|
+ otherwise. See 'wildmenu' and 'wildmode'.
+ This can be used in mappings to handle the 'wildcharm' option
+ gracefully. (Makes only sense with |mapmode-c| mappings).
+
+ For example to make <c-j> work like <down> in wildmode, use: >vim
+ cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>"
+ <
+ (Note, this needs the 'wildcharm' option set appropriately).
+ ]=],
+ name = 'wildmenumode',
+ params = {},
+ signature = 'wildmenumode()',
+ },
+ win_execute = {
+ args = { 2, 3 },
+ base = 2,
+ desc = [=[
+ Like `execute()` but in the context of window {id}.
+ The window will temporarily be made the current window,
+ without triggering autocommands or changing directory. When
+ executing {command} autocommands will be triggered, this may
+ have unexpected side effects. Use `:noautocmd` if needed.
+ Example: >vim
+ call win_execute(winid, 'syntax enable')
+ <Doing the same with `setwinvar()` would not trigger
+ autocommands and not actually show syntax highlighting.
+
+ When window {id} does not exist then no error is given and
+ an empty string is returned.
+
+ ]=],
+ name = 'win_execute',
+ params = { { 'id', 'any' }, { 'command', 'any' }, { 'silent', 'boolean' } },
+ signature = 'win_execute({id}, {command} [, {silent}])',
+ },
+ win_findbuf = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Returns a |List| with |window-ID|s for windows that contain
+ buffer {bufnr}. When there is none the list is empty.
+
+ ]=],
+ name = 'win_findbuf',
+ params = { { 'bufnr', 'any' } },
+ returns = 'integer[]',
+ signature = 'win_findbuf({bufnr})',
+ },
+ win_getid = {
+ args = { 0, 2 },
+ base = 1,
+ desc = [=[
+ Get the |window-ID| for the specified window.
+ When {win} is missing use the current window.
+ With {win} this is the window number. The top window has
+ number 1.
+ Without {tab} use the current tab, otherwise the tab with
+ number {tab}. The first tab has number one.
+ Return zero if the window cannot be found.
+
+ ]=],
+ name = 'win_getid',
+ params = { { 'win', 'any' }, { 'tab', 'any' } },
+ returns = 'integer',
+ signature = 'win_getid([{win} [, {tab}]])',
+ },
+ win_gettype = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ Return the type of the window:
+ "autocmd" autocommand window. Temporary window
+ used to execute autocommands.
+ "command" command-line window |cmdwin|
+ (empty) normal window
+ "loclist" |location-list-window|
+ "popup" floating window |api-floatwin|
+ "preview" preview window |preview-window|
+ "quickfix" |quickfix-window|
+ "unknown" window {nr} not found
+
+ When {nr} is omitted return the type of the current window.
+ When {nr} is given return the type of this window by number or
+ |window-ID|.
+
+ Also see the 'buftype' option.
+
+ ]=],
+ name = 'win_gettype',
+ params = { { 'nr', 'integer' } },
+ returns = "'autocmd'|'command'|''|'loclist'|'popup'|'preview'|'quickfix'|'unknown'",
+ signature = 'win_gettype([{nr}])',
+ },
+ win_gotoid = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Go to window with ID {expr}. This may also change the current
+ tabpage.
+ Return TRUE if successful, FALSE if the window cannot be found.
+
+ ]=],
+ name = 'win_gotoid',
+ params = { { 'expr', 'any' } },
+ returns = '0|1',
+ signature = 'win_gotoid({expr})',
+ },
+ win_id2tabwin = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return a list with the tab number and window number of window
+ with ID {expr}: [tabnr, winnr].
+ Return [0, 0] if the window cannot be found.
+
+ ]=],
+ name = 'win_id2tabwin',
+ params = { { 'expr', 'any' } },
+ signature = 'win_id2tabwin({expr})',
+ },
+ win_id2win = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the window number of window with ID {expr}.
+ Return 0 if the window cannot be found in the current tabpage.
+
+ ]=],
+ name = 'win_id2win',
+ params = { { 'expr', 'any' } },
+ signature = 'win_id2win({expr})',
+ },
+ win_move_separator = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Move window {nr}'s vertical separator (i.e., the right border)
+ by {offset} columns, as if being dragged by the mouse. {nr}
+ can be a window number or |window-ID|. A positive {offset}
+ moves right and a negative {offset} moves left. Moving a
+ window's vertical separator will change the width of the
+ window and the width of other windows adjacent to the vertical
+ separator. The magnitude of movement may be smaller than
+ specified (e.g., as a consequence of maintaining
+ 'winminwidth'). Returns TRUE if the window can be found and
+ FALSE otherwise.
+ This will fail for the rightmost window and a full-width
+ window, since it has no separator on the right.
+ Only works for the current tab page. *E1308*
+
+ ]=],
+ name = 'win_move_separator',
+ params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ signature = 'win_move_separator({nr}, {offset})',
+ },
+ win_move_statusline = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Move window {nr}'s status line (i.e., the bottom border) by
+ {offset} rows, as if being dragged by the mouse. {nr} can be a
+ window number or |window-ID|. A positive {offset} moves down
+ and a negative {offset} moves up. Moving a window's status
+ line will change the height of the window and the height of
+ other windows adjacent to the status line. The magnitude of
+ movement may be smaller than specified (e.g., as a consequence
+ of maintaining 'winminheight'). Returns TRUE if the window can
+ be found and FALSE otherwise.
+ Only works for the current tab page.
+
+ ]=],
+ name = 'win_move_statusline',
+ params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ signature = 'win_move_statusline({nr}, {offset})',
+ },
+ win_screenpos = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Return the screen position of window {nr} as a list with two
+ numbers: [row, col]. The first window always has position
+ [1, 1], unless there is a tabline, then it is [2, 1].
+ {nr} can be the window number or the |window-ID|. Use zero
+ for the current window.
+ Returns [0, 0] if the window cannot be found in the current
+ tabpage.
+
+ ]=],
+ name = 'win_screenpos',
+ params = { { 'nr', 'integer' } },
+ signature = 'win_screenpos({nr})',
+ },
+ win_splitmove = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ Move the window {nr} to a new split of the window {target}.
+ This is similar to moving to {target}, creating a new window
+ using |:split| but having the same contents as window {nr}, and
+ then closing {nr}.
+
+ Both {nr} and {target} can be window numbers or |window-ID|s.
+ Both must be in the current tab page.
+
+ Returns zero for success, non-zero for failure.
+
+ {options} is a |Dictionary| with the following optional entries:
+ "vertical" When TRUE, the split is created vertically,
+ like with |:vsplit|.
+ "rightbelow" When TRUE, the split is made below or to the
+ right (if vertical). When FALSE, it is done
+ above or to the left (if vertical). When not
+ present, the values of 'splitbelow' and
+ 'splitright' are used.
+
+ ]=],
+ name = 'win_splitmove',
+ params = { { 'nr', 'integer' }, { 'target', 'any' }, { 'options', 'table' } },
+ signature = 'win_splitmove({nr}, {target} [, {options}])',
+ },
+ winbufnr = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of the buffer
+ associated with window {nr}. {nr} can be the window number or
+ the |window-ID|.
+ When {nr} is zero, the number of the buffer in the current
+ window is returned.
+ When window {nr} doesn't exist, -1 is returned.
+ Example: >vim
+ echo "The file in the current window is " .. bufname(winbufnr(0))
+ <
+ ]=],
+ name = 'winbufnr',
+ params = { { 'nr', 'integer' } },
+ returns = 'integer',
+ signature = 'winbufnr({nr})',
+ },
+ wincol = {
+ desc = [=[
+ The result is a Number, which is the virtual column of the
+ cursor in the window. This is counting screen cells from the
+ left side of the window. The leftmost column is one.
+ ]=],
+ name = 'wincol',
+ params = {},
+ returns = 'integer',
+ signature = 'wincol()',
+ },
+ windowsversion = {
+ desc = [=[
+ The result is a String. For MS-Windows it indicates the OS
+ version. E.g, Windows 10 is "10.0", Windows 8 is "6.2",
+ Windows XP is "5.1". For non-MS-Windows systems the result is
+ an empty string.
+ ]=],
+ fast = true,
+ name = 'windowsversion',
+ params = {},
+ returns = 'string',
+ signature = 'windowsversion()',
+ },
+ winheight = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the height of window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is zero, the height of the current window is
+ returned. When window {nr} doesn't exist, -1 is returned.
+ An existing window always has a height of zero or more.
+ This excludes any window toolbar line.
+ Examples: >vim
+ echo "The current window has " .. winheight(0) .. " lines."
+
+ ]=],
+ name = 'winheight',
+ params = { { 'nr', 'integer' } },
+ returns = 'integer',
+ signature = 'winheight({nr})',
+ },
+ winlayout = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a nested List containing the layout of windows
+ in a tabpage.
+
+ Without {tabnr} use the current tabpage, otherwise the tabpage
+ with number {tabnr}. If the tabpage {tabnr} is not found,
+ returns an empty list.
+
+ For a leaf window, it returns: >
+ ["leaf", {winid}]
+ <
+ For horizontally split windows, which form a column, it
+ returns: >
+ ["col", [{nested list of windows}]]
+ <For vertically split windows, which form a row, it returns: >
+ ["row", [{nested list of windows}]]
+ <
+ Example: >vim
+ " Only one window in the tab page
+ echo winlayout()
+ < >
+ ['leaf', 1000]
+ < >vim
+ " Two horizontally split windows
+ echo winlayout()
+ < >
+ ['col', [['leaf', 1000], ['leaf', 1001]]]
+ < >vim
+ " The second tab page, with three horizontally split
+ " windows, with two vertically split windows in the
+ " middle window
+ echo winlayout(2)
+ < >
+ ['col', [['leaf', 1002], ['row', [['leaf', 1003],
+ ['leaf', 1001]]], ['leaf', 1000]]]
+ <
+ ]=],
+ name = 'winlayout',
+ params = { { 'tabnr', 'integer' } },
+ signature = 'winlayout([{tabnr}])',
+ },
+ winline = {
+ desc = [=[
+ The result is a Number, which is the screen line of the cursor
+ in the window. This is counting screen lines from the top of
+ the window. The first line is one.
+ If the cursor was moved the view on the file will be updated
+ first, this may cause a scroll.
+ ]=],
+ name = 'winline',
+ params = {},
+ returns = 'integer',
+ signature = 'winline()',
+ },
+ winnr = {
+ args = { 0, 1 },
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the number of the current
+ window. The top window has number 1.
+ Returns zero for a popup window.
+
+ The optional argument {arg} supports the following values:
+ $ the number of the last window (the window
+ count).
+ # the number of the last accessed window (where
+ |CTRL-W_p| goes to). If there is no previous
+ window or it is in another tab page 0 is
+ returned.
+ {N}j the number of the Nth window below the
+ current window (where |CTRL-W_j| goes to).
+ {N}k the number of the Nth window above the current
+ window (where |CTRL-W_k| goes to).
+ {N}h the number of the Nth window left of the
+ current window (where |CTRL-W_h| goes to).
+ {N}l the number of the Nth window right of the
+ current window (where |CTRL-W_l| goes to).
+ The number can be used with |CTRL-W_w| and ":wincmd w"
+ |:wincmd|.
+ When {arg} is invalid an error is given and zero is returned.
+ Also see |tabpagewinnr()| and |win_getid()|.
+ Examples: >vim
+ let window_count = winnr('$')
+ let prev_window = winnr('#')
+ let wnum = winnr('3k')
+
+ ]=],
+ name = 'winnr',
+ params = { { 'arg', 'any' } },
+ signature = 'winnr([{arg}])',
+ },
+ winrestcmd = {
+ desc = [=[
+ Returns a sequence of |:resize| commands that should restore
+ the current window sizes. Only works properly when no windows
+ are opened or closed and the current window and tab page is
+ unchanged.
+ Example: >vim
+ let cmd = winrestcmd()
+ call MessWithWindowSizes()
+ exe cmd
+ <
+ ]=],
+ name = 'winrestcmd',
+ params = {},
+ signature = 'winrestcmd()',
+ },
+ winrestview = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ Uses the |Dictionary| returned by |winsaveview()| to restore
+ the view of the current window.
+ Note: The {dict} does not have to contain all values, that are
+ returned by |winsaveview()|. If values are missing, those
+ settings won't be restored. So you can use: >vim
+ call winrestview({'curswant': 4})
+ <
+ This will only set the curswant value (the column the cursor
+ wants to move on vertical movements) of the cursor to column 5
+ (yes, that is 5), while all other settings will remain the
+ same. This is useful, if you set the cursor position manually.
+
+ If you have changed the values the result is unpredictable.
+ If the window size changed the result won't be the same.
+
+ ]=],
+ name = 'winrestview',
+ params = { { 'dict', 'vim.fn.winrestview.dict' } },
+ signature = 'winrestview({dict})',
+ },
+ winsaveview = {
+ desc = [=[
+ Returns a |Dictionary| that contains information to restore
+ the view of the current window. Use |winrestview()| to
+ restore the view.
+ This is useful if you have a mapping that jumps around in the
+ buffer and you want to go back to the original view.
+ This does not save fold information. Use the 'foldenable'
+ option to temporarily switch off folding, so that folds are
+ not opened when moving around. This may have side effects.
+ The return value includes:
+ lnum cursor line number
+ col cursor column (Note: the first column
+ zero, as opposed to what |getcurpos()|
+ returns)
+ coladd cursor column offset for 'virtualedit'
+ curswant column for vertical movement (Note:
+ the first column is zero, as opposed
+ to what |getcurpos()| returns). After
+ |$| command it will be a very large
+ number equal to |v:maxcol|.
+ topline first line in the window
+ topfill filler lines, only in diff mode
+ leftcol first column displayed; only used when
+ 'wrap' is off
+ skipcol columns skipped
+ Note that no option values are saved.
+ ]=],
+ name = 'winsaveview',
+ params = {},
+ signature = 'winsaveview()',
+ returns = 'vim.fn.winsaveview.ret'
+ },
+ winwidth = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is the width of window {nr}.
+ {nr} can be the window number or the |window-ID|.
+ When {nr} is zero, the width of the current window is
+ returned. When window {nr} doesn't exist, -1 is returned.
+ An existing window always has a width of zero or more.
+ Examples: >vim
+ echo "The current window has " .. winwidth(0) .. " columns."
+ if winwidth(0) <= 50
+ 50 wincmd |
+ endif
+ <For getting the terminal or screen size, see the 'columns'
+ option.
+
+ ]=],
+ name = 'winwidth',
+ params = { { 'nr', 'integer' } },
+ signature = 'winwidth({nr})',
+ },
+ wordcount = {
+ desc = [=[
+ The result is a dictionary of byte/chars/word statistics for
+ the current buffer. This is the same info as provided by
+ |g_CTRL-G|
+ The return value includes:
+ bytes Number of bytes in the buffer
+ chars Number of chars in the buffer
+ words Number of words in the buffer
+ cursor_bytes Number of bytes before cursor position
+ (not in Visual mode)
+ cursor_chars Number of chars before cursor position
+ (not in Visual mode)
+ cursor_words Number of words before cursor position
+ (not in Visual mode)
+ visual_bytes Number of bytes visually selected
+ (only in Visual mode)
+ visual_chars Number of chars visually selected
+ (only in Visual mode)
+ visual_words Number of words visually selected
+ (only in Visual mode)
+ ]=],
+ name = 'wordcount',
+ params = {},
+ signature = 'wordcount()',
+ },
+ writefile = {
+ args = { 2, 3 },
+ base = 1,
+ desc = [=[
+ When {object} is a |List| write it to file {fname}. Each list
+ item is separated with a NL. Each list item must be a String
+ or Number.
+ All NL characters are replaced with a NUL character.
+ Inserting CR characters needs to be done before passing {list}
+ to writefile().
+
+ When {object} is a |Blob| write the bytes to file {fname}
+ unmodified, also when binary mode is not specified.
+
+ {flags} must be a String. These characters are recognized:
+
+ 'b' Binary mode is used: There will not be a NL after the
+ last list item. An empty item at the end does cause the
+ last line in the file to end in a NL.
+
+ 'a' Append mode is used, lines are appended to the file: >vim
+ call writefile(["foo"], "event.log", "a")
+ call writefile(["bar"], "event.log", "a")
+ <
+ 'D' Delete the file when the current function ends. This
+ works like: >vim
+ defer delete({fname})
+ < Fails when not in a function. Also see |:defer|.
+
+ 's' fsync() is called after writing the file. This flushes
+ the file to disk, if possible. This takes more time but
+ avoids losing the file if the system crashes.
+
+ 'S' fsync() is not called, even when 'fsync' is set.
+
+ When {flags} does not contain "S" or "s" then fsync() is
+ called if the 'fsync' option is set.
+
+ An existing file is overwritten, if possible.
+
+ When the write fails -1 is returned, otherwise 0. There is an
+ error message if the file can't be created or when writing
+ fails.
+
+ Also see |readfile()|.
+ To copy a file byte for byte: >vim
+ let fl = readfile("foo", "b")
+ call writefile(fl, "foocopy", "b")
+
+ ]=],
+ name = 'writefile',
+ params = { { 'object', 'any' }, { 'fname', 'string' }, { 'flags', 'string' } },
+ signature = 'writefile({object}, {fname} [, {flags}])',
+ },
+ xor = {
+ args = 2,
+ base = 1,
+ desc = [=[
+ Bitwise XOR on the two arguments. The arguments are converted
+ to a number. A List, Dict or Float argument causes an error.
+ Also see `and()` and `or()`.
+ Example: >vim
+ let bits = xor(bits, 0x80)
+ <
+ ]=],
+ name = 'xor',
+ params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ signature = 'xor({expr}, {expr})',
},
}
+
+return M
diff --git a/src/nvim/eval/buffer.c b/src/nvim/eval/buffer.c
index 2f37d1ba2e..c60a104381 100644
--- a/src/nvim/eval/buffer.c
+++ b/src/nvim/eval/buffer.c
@@ -1,12 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/buffer.c: Buffer related builtin functions
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -17,17 +14,18 @@
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/move.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/sign.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
typedef struct {
win_T *cob_curwin_save;
@@ -126,7 +124,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
FUNC_ATTR_NONNULL_ARG(4, 5)
{
linenr_T lnum = lnum_arg + (append ? 1 : 0);
- long added = 0;
+ int added = 0;
// When using the current buffer ml_mfp will be set if needed. Useful when
// setline() is used on startup. For other buffers the buffer must be
@@ -160,10 +158,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
if (lines->v_type == VAR_LIST) {
l = lines->vval.v_list;
if (l == NULL || tv_list_len(l) == 0) {
- // set proper return code
- if (lnum > curbuf->b_ml.ml_line_count) {
- rettv->vval.v_number = 1; // FAIL
- }
+ // not appending anything always succeeds
goto cleanup;
}
li = tv_list_first(l);
@@ -172,7 +167,7 @@ static void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append, typval_
}
// Default result is zero == OK.
- for (;;) {
+ while (true) {
if (lines->v_type == VAR_LIST) {
// List argument, get next string.
if (li == NULL) {
@@ -304,7 +299,10 @@ void f_bufload(typval_T *argvars, typval_T *unused, EvalFuncData fptr)
buf_T *buf = get_buf_arg(&argvars[0]);
if (buf != NULL) {
- buffer_ensure_loaded(buf);
+ if (swap_exists_action != SEA_READONLY) {
+ swap_exists_action = SEA_NONE;
+ }
+ buf_ensure_loaded(buf);
}
}
@@ -442,7 +440,7 @@ void f_deletebufline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (last > curbuf->b_ml.ml_line_count) {
last = curbuf->b_ml.ml_line_count;
}
- const long count = last - first + 1;
+ const int count = last - first + 1;
// When coming here from Insert mode, sync undo, so that this can be
// undone separately from what was previously inserted.
@@ -487,8 +485,7 @@ static dict_T *get_buffer_info(buf_T *buf)
dict_T *const dict = tv_dict_alloc();
tv_dict_add_nr(dict, S_LEN("bufnr"), buf->b_fnum);
- tv_dict_add_str(dict, S_LEN("name"),
- buf->b_ffname != NULL ? (const char *)buf->b_ffname : "");
+ tv_dict_add_str(dict, S_LEN("name"), buf->b_ffname != NULL ? buf->b_ffname : "");
tv_dict_add_nr(dict, S_LEN("lnum"),
buf == curbuf ? curwin->w_cursor.lnum : buflist_findlnum(buf));
tv_dict_add_nr(dict, S_LEN("linecount"), buf->b_ml.ml_line_count);
@@ -496,8 +493,7 @@ static dict_T *get_buffer_info(buf_T *buf)
tv_dict_add_nr(dict, S_LEN("listed"), buf->b_p_bl);
tv_dict_add_nr(dict, S_LEN("changed"), bufIsChanged(buf));
tv_dict_add_nr(dict, S_LEN("changedtick"), buf_get_changedtick(buf));
- tv_dict_add_nr(dict, S_LEN("hidden"),
- buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0);
+ tv_dict_add_nr(dict, S_LEN("hidden"), buf->b_ml.ml_mfp != NULL && buf->b_nwindows == 0);
// Get a reference to buffer variables
tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars);
@@ -511,7 +507,7 @@ static dict_T *get_buffer_info(buf_T *buf)
}
tv_dict_add_list(dict, S_LEN("windows"), windows);
- if (buf->b_signlist != NULL) {
+ if (buf->b_signs) {
// List of signs placed in this buffer
tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf));
}
@@ -609,13 +605,12 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli
}
tv_list_alloc_ret(rettv, end - start + 1);
while (start <= end) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)ml_get_buf(buf, start++, false), -1);
+ tv_list_append_string(rettv->vval.v_list, ml_get_buf(buf, start++), -1);
}
} else {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count)
- ? xstrdup(ml_get_buf(buf, start, false)) : NULL);
+ ? xstrdup(ml_get_buf(buf, start)) : NULL);
}
}
diff --git a/src/nvim/eval/buffer.h b/src/nvim/eval/buffer.h
index 4a2f8f9e94..1d346b99a5 100644
--- a/src/nvim/eval/buffer.h
+++ b/src/nvim/eval/buffer.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_EVAL_BUFFER_H
-#define NVIM_EVAL_BUFFER_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/buffer.h.generated.h"
#endif
-#endif // NVIM_EVAL_BUFFER_H
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index cd1479f150..03f79fca84 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <msgpack/object.h>
#include <stdbool.h>
@@ -10,22 +7,22 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
-#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Helper structure for container_struct
typedef struct {
@@ -148,7 +145,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
assert(!(key.is_special_string
|| key.val.vval.v_string == NULL
|| *key.val.vval.v_string == NUL));
- dictitem_T *const obj_di = tv_dict_item_alloc((const char *)key.val.vval.v_string);
+ dictitem_T *const obj_di = tv_dict_item_alloc(key.val.vval.v_string);
tv_clear(&key.val);
if (tv_dict_add(last_container.container.vval.v_dict, obj_di)
== FAIL) {
@@ -179,8 +176,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
&& (obj.is_special_string
|| obj.val.vval.v_string == NULL
|| *obj.val.vval.v_string == NUL
- || tv_dict_find(last_container.container.vval.v_dict,
- (const char *)obj.val.vval.v_string, -1))) {
+ || tv_dict_find(last_container.container.vval.v_dict, obj.val.vval.v_string, -1))) {
tv_clear(&obj.val);
// Restart
@@ -228,7 +224,7 @@ static inline int json_decoder_pop(ValuesStackItem obj, ValuesStack *const stack
///
/// @param[out] ret_tv Address where new special dictionary is saved.
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. @see ListLenSpecials.
///
@@ -439,7 +435,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
t += 4;
uvarnumber_T ch;
vim_str2nr(ubuf, NULL, NULL,
- STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true);
+ STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true, NULL);
if (ch == 0) {
hasnul = true;
}
@@ -608,7 +604,7 @@ parse_json_number_check:
// Convert integer
varnumber_T nr;
int num_len;
- vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true);
+ vim_str2nr(s, NULL, &num_len, 0, &nr, NULL, (int)(p - s), true, NULL);
if ((int)exp_num_len != num_len) {
semsg(_("E685: internal error: while converting number \"%.*s\" "
"to integer vim_str2nr consumed %i bytes in place of %zu"),
@@ -646,7 +642,7 @@ parse_json_number_ret:
} \
} while (0)
-/// Convert JSON string into VimL object
+/// Convert JSON string into Vimscript object
///
/// @param[in] buf String to convert. UTF-8 encoding is assumed.
/// @param[in] buf_len Length of the string.
@@ -922,7 +918,7 @@ json_decode_string_ret:
#undef DICT_LEN
-/// Convert msgpack object to a VimL one
+/// Convert msgpack object to a Vimscript one
int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -966,7 +962,7 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
}
break;
case MSGPACK_OBJECT_NEGATIVE_INTEGER:
- if (mobj.via.i64 >= VARNUMBER_MIN) { // -V547
+ if (mobj.via.i64 >= VARNUMBER_MIN) {
*rettv = (typval_T) {
.v_type = VAR_NUMBER,
.v_lock = VAR_UNLOCKED,
@@ -987,12 +983,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF));
}
break;
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
*rettv = (typval_T) {
.v_type = VAR_FLOAT,
.v_lock = VAR_UNLOCKED,
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
index f1be5a1f69..c0d10a469a 100644
--- a/src/nvim/eval/decode.h
+++ b/src/nvim/eval/decode.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_EVAL_DECODE_H
-#define NVIM_EVAL_DECODE_H
+#pragma once
-#include <msgpack.h>
-#include <stddef.h>
+#include <msgpack.h> // IWYU pragma: keep
+#include <stddef.h> // IWYU pragma: keep
-#include "nvim/eval/typval.h"
-#include "nvim/globals.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/decode.h.generated.h"
#endif
-#endif // NVIM_EVAL_DECODE_H
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index c2f1eae8af..8505c30fad 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -1,9 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file encode.c
///
-/// File containing functions for encoding and decoding VimL values.
+/// File containing functions for encoding and decoding Vimscript values.
///
/// Split out from eval.c.
@@ -17,7 +14,7 @@
#include "klib/kvec.h"
#include "msgpack/pack.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
@@ -25,14 +22,14 @@
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h" // For _()
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h" // For _()
const char *const encode_bool_var_names[] = {
[kBoolVarTrue] = "v:true",
@@ -152,9 +149,9 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
? 0
: (v.data.l.li == NULL
? tv_list_len(v.data.l.list) - 1
- : (int)tv_list_idx_of_item(v.data.l.list,
- TV_LIST_ITEM_PREV(v.data.l.list,
- v.data.l.li))));
+ : tv_list_idx_of_item(v.data.l.list,
+ TV_LIST_ITEM_PREV(v.data.l.list,
+ v.data.l.li))));
const listitem_T *const li = (v.data.l.li == NULL
? tv_list_last(v.data.l.list)
: TV_LIST_ITEM_PREV(v.data.l.list,
@@ -298,7 +295,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
- const char *const buf_ = (const char *)(buf); \
+ const char *const buf_ = (buf); \
if ((buf) == NULL) { \
ga_concat(gap, "''"); \
} else { \
@@ -376,7 +373,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
do { \
- const char *const fun_ = (const char *)(fun); \
+ const char *const fun_ = (fun); \
if (fun_ == NULL) { \
internal_error("string(): NULL function name"); \
ga_concat(gap, "function(NULL"); \
@@ -418,7 +415,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
ga_concat(gap, "v:null")
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- ga_concat(gap, ((num)? "v:true": "v:false"))
+ ga_concat(gap, ((num) ? "v:true" : "v:false"))
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num)
@@ -544,7 +541,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#undef TYPVAL_ENCODE_CONV_BOOL
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- ga_concat(gap, ((num)? "true": "false"))
+ ga_concat(gap, ((num) ? "true" : "false"))
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \
@@ -712,7 +709,7 @@ static inline int convert_to_json_string(garray_T *const gap, const char *const
#undef TYPVAL_ENCODE_CONV_STRING
#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
do { \
- if (convert_to_json_string(gap, (const char *)(buf), (len)) != OK) { \
+ if (convert_to_json_string(gap, (buf), (len)) != OK) { \
return FAIL; \
} \
} while (0)
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
index 41e7614fc0..26a3286f2b 100644
--- a/src/nvim/eval/encode.h
+++ b/src/nvim/eval/encode.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_ENCODE_H
-#define NVIM_EVAL_ENCODE_H
+#pragma once
#include <msgpack.h>
#include <msgpack/pack.h>
@@ -9,10 +8,9 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/vim.h"
+#include "nvim/garray_defs.h"
-/// Convert VimL value to msgpack string
+/// Convert Vimscript value to msgpack string
///
/// @param[out] packer Packer to save results in.
/// @param[in] tv Dumped value.
@@ -21,7 +19,7 @@
/// @return OK in case of success, FAIL otherwise.
int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname);
-/// Convert VimL value to :echo output
+/// Convert Vimscript value to :echo output
///
/// @param[out] packer Packer to save results in.
/// @param[in] tv Dumped value.
@@ -74,4 +72,3 @@ extern const char *const encode_special_var_names[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/encode.h.generated.h"
#endif
-#endif // NVIM_EVAL_ENCODE_H
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 9caea2fef1..dc23fcdc72 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdlib.h>
@@ -8,19 +5,21 @@
#include "nvim/eval/executor.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/message.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/executor.c.generated.h" // IWYU pragma: export
#endif
-char *e_listidx = N_("E684: list index out of range: %" PRId64);
+char *e_list_index_out_of_range_nr
+ = N_("E684: List index out of range: %" PRId64);
/// Handle tv1 += tv2, -=, *=, /=, %=, .=
///
diff --git a/src/nvim/eval/executor.h b/src/nvim/eval/executor.h
index 3d789f76a5..d36ce08542 100644
--- a/src/nvim/eval/executor.h
+++ b/src/nvim/eval/executor.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_EVAL_EXECUTOR_H
-#define NVIM_EVAL_EXECUTOR_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-extern char *e_listidx;
+extern char *e_list_index_out_of_range_nr;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/executor.h.generated.h"
#endif
-#endif // NVIM_EVAL_EXECUTOR_H
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 48f3cd4293..13425b21d1 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <fcntl.h>
#include <float.h>
@@ -11,11 +8,13 @@
#include <msgpack/pack.h>
#include <msgpack/unpack.h>
#include <signal.h>
+#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
@@ -25,14 +24,15 @@
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/context.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -44,6 +44,7 @@
#include "nvim/eval/executor.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -57,41 +58,40 @@
#include "nvim/ex_getln.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/hashtab.h"
+#include "nvim/grid.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/input.h"
+#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
-#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/dl.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/shell.h"
@@ -100,7 +100,7 @@
#include "nvim/path.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
@@ -113,9 +113,8 @@
#include "nvim/syntax.h"
#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Describe data to return from find_some_match()
@@ -145,11 +144,17 @@ PRAGMA_DIAG_POP
PRAGMA_DIAG_POP
#endif
-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");
-static char e_using_number_as_bool_nr[]
- = N_("E1023: Using a Number as a Bool: %d");
+static const char *e_listblobarg = N_("E899: Argument of %s must be a List or Blob");
+static const char *e_invalwindow = N_("E957: Invalid window number");
+static const char e_argument_of_str_must_be_list_string_or_dictionary[]
+ = N_("E706: Argument of %s must be a List, String or Dictionary");
+static const char e_invalid_submatch_number_nr[]
+ = N_("E935: Invalid submatch number: %d");
+static const char *e_reduceempty = N_("E998: Reduce of an empty %s with no initial value");
+static const char e_string_list_or_blob_required[]
+ = N_("E1098: String, List or Blob required");
+static const char e_missing_function_argument[]
+ = N_("E1132: Missing function argument");
/// Dummy va_list for passing to vim_snprintf
///
@@ -226,6 +231,31 @@ const EvalFuncDef *find_internal_func(const char *const name)
return index >= 0 ? &functions[index] : NULL;
}
+/// Check the argument count to use for internal function "fdef".
+/// @return -1 for failure, 0 if no method base accepted, 1 if method base is
+/// first argument, 2 if method base is second argument, etc.
+int check_internal_func(const EvalFuncDef *const fdef, const int argcount)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int res;
+
+ if (argcount < fdef->min_argc) {
+ res = FCERR_TOOFEW;
+ } else if (argcount > fdef->max_argc) {
+ res = FCERR_TOOMANY;
+ } else {
+ return fdef->base_arg;
+ }
+
+ const char *const name = fdef->name;
+ if (res == FCERR_TOOMANY) {
+ semsg(_(e_toomanyarg), name);
+ } else {
+ semsg(_(e_toofewarg), name);
+ }
+ return -1;
+}
+
int call_internal_func(const char *const fname, const int argcount, typval_T *const argvars,
typval_T *const rettv)
FUNC_ATTR_NONNULL_ALL
@@ -261,6 +291,9 @@ int call_internal_method(const char *const fname, const int argcount, typval_T *
typval_T argv[MAX_FUNC_ARGS + 1];
const ptrdiff_t base_index = fdef->base_arg == BASE_LAST ? argcount : fdef->base_arg - 1;
+ if (argcount < base_index) {
+ return FCERR_TOOFEW;
+ }
memcpy(argv, argvars, (size_t)base_index * sizeof(typval_T));
argv[base_index] = *basetv;
memcpy(argv + base_index + 1, argvars + base_index,
@@ -319,11 +352,12 @@ static void api_wrapper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
Object result = handler.fn(VIML_INTERNAL_CALL, args, &res_arena, &err);
if (ERROR_SET(&err)) {
- semsg_multiline((const char *)e_api_error, err.msg);
+ semsg_multiline(e_api_error, err.msg);
goto end;
}
if (!object_to_vim(result, rettv, &err)) {
+ assert(ERROR_SET(&err));
semsg(_("Error converting the call result: %s"), err.msg);
}
@@ -448,7 +482,7 @@ buf_T *tv_get_buf(typval_T *tv, int curtab_only)
int save_magic = p_magic;
p_magic = true;
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
buf_T *buf = buflist_findnr(buflist_findpat(name, name + strlen(name),
true, false, curtab_only));
@@ -492,7 +526,7 @@ buf_T *get_buf_arg(typval_T *arg)
/// "byte2line(byte)" function
static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long boff = tv_get_number(&argvars[0]) - 1;
+ int boff = (int)tv_get_number(&argvars[0]) - 1;
if (boff < 0) {
rettv->vval.v_number = -1;
} else {
@@ -501,41 +535,6 @@ static void f_byte2line(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-static void byteidx(typval_T *argvars, typval_T *rettv, int comp)
-{
- const char *const str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- rettv->vval.v_number = -1;
- if (str == NULL || idx < 0) {
- return;
- }
-
- const char *t = str;
- for (; idx > 0; idx--) {
- if (*t == NUL) { // EOL reached.
- return;
- }
- if (comp) {
- t += utf_ptr2len(t);
- } else {
- t += utfc_ptr2len(t);
- }
- }
- rettv->vval.v_number = (varnumber_T)(t - str);
-}
-
-/// "byteidx()" function
-static void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, false);
-}
-
-/// "byteidxcomp()" function
-static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- byteidx(argvars, rettv, true);
-}
-
/// "call(func, arglist [, dict])" function
static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -563,14 +562,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
func = (char *)tv_get_string(&argvars[0]);
}
- if (*func == NUL) {
- return; // type error or empty name
+ if (func == NULL || *func == NUL) {
+ return; // type error, empty name or null function
}
dict_T *selfdict = NULL;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
if (owned) {
func_unref(func);
}
@@ -653,7 +651,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool crlf = false;
#else
Channel *chan = find_channel(id);
- bool crlf = (chan != NULL && chan->term) ? true: false;
+ bool crlf = (chan != NULL && chan->term) ? true : false;
#endif
if (argvars[1].v_type == VAR_BLOB) {
@@ -761,53 +759,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_col(argvars, rettv, true);
}
-/// "charidx()" function
-static void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- if (argvars[0].v_type != VAR_STRING
- || argvars[1].v_type != VAR_NUMBER
- || (argvars[2].v_type != VAR_UNKNOWN
- && argvars[2].v_type != VAR_NUMBER
- && argvars[2].v_type != VAR_BOOL)) {
- emsg(_(e_invarg));
- return;
- }
-
- const char *str = tv_get_string_chk(&argvars[0]);
- varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
- if (str == NULL || idx < 0) {
- return;
- }
- int countcc = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- countcc = (int)tv_get_number(&argvars[2]);
- }
- if (countcc < 0 || countcc > 1) {
- semsg(_(e_using_number_as_bool_nr), countcc);
- return;
- }
-
- int (*ptr2len)(const char *);
- if (countcc) {
- ptr2len = utf_ptr2len;
- } else {
- ptr2len = utfc_ptr2len;
- }
-
- const char *p;
- int len;
- for (p = str, len = 0; p <= str + idx; len++) {
- if (*p == NUL) {
- return;
- }
- p += ptr2len(p);
- }
-
- rettv->vval.v_number = len > 0 ? len - 1 : 0;
-}
-
/// "chdir(dir)" function
static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -925,8 +876,7 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!error) {
- rettv->vval.v_number = do_dialog(type, NULL, (char *)message, (char *)buttons, def, NULL,
- false);
+ rettv->vval.v_number = do_dialog(type, NULL, message, buttons, def, NULL, false);
}
}
@@ -936,10 +886,90 @@ static void f_copy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
var_item_copy(NULL, &argvars[0], rettv, false, 0);
}
+/// Count the number of times "needle" occurs in string "haystack".
+///
+/// @param ic ignore case
+static varnumber_T count_string(const char *haystack, const char *needle, bool ic)
+{
+ varnumber_T n = 0;
+ const char *p = haystack;
+
+ if (p == NULL || needle == NULL || *needle == NUL) {
+ return 0;
+ }
+
+ if (ic) {
+ const size_t len = strlen(needle);
+
+ while (*p != NUL) {
+ if (mb_strnicmp(p, needle, len) == 0) {
+ n++;
+ p += len;
+ } else {
+ MB_PTR_ADV(p);
+ }
+ }
+ } else {
+ const char *next;
+ while ((next = strstr(p, needle)) != NULL) {
+ n++;
+ p = next + strlen(needle);
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in List "l" starting at index "idx".
+///
+/// @param ic ignore case
+static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
+{
+ if (tv_list_len(l) == 0) {
+ return 0;
+ }
+
+ listitem_T *li = tv_list_find(l, (int)idx);
+ if (li == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), idx);
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic, false)) {
+ n++;
+ }
+ }
+
+ return n;
+}
+
+/// Count the number of times item "needle" occurs in Dict "d".
+///
+/// @param ic ignore case
+static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
+{
+ if (d == NULL) {
+ return 0;
+ }
+
+ varnumber_T n = 0;
+
+ TV_DICT_ITER(d, di, {
+ if (tv_equal(&di->di_tv, needle, ic, false)) {
+ n++;
+ }
+ });
+
+ return n;
+}
+
/// "count()" function
static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long n = 0;
+ varnumber_T n = 0;
int ic = 0;
bool error = false;
@@ -947,78 +977,30 @@ static void f_count(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ic = (int)tv_get_number_chk(&argvars[2], &error);
}
- if (argvars[0].v_type == VAR_STRING) {
- const char *expr = tv_get_string_chk(&argvars[1]);
- const char *p = argvars[0].vval.v_string;
-
- if (!error && expr != NULL && *expr != NUL && p != NULL) {
- if (ic) {
- const size_t len = strlen(expr);
-
- while (*p != NUL) {
- if (mb_strnicmp((char *)p, (char *)expr, len) == 0) {
- n++;
- p += len;
- } else {
- MB_PTR_ADV(p);
- }
- }
- } else {
- char *next;
- while ((next = strstr((char *)p, (char *)expr)) != NULL) {
- n++;
- p = next + strlen(expr);
- }
- }
+ if (!error && argvars[0].v_type == VAR_STRING) {
+ n = count_string(argvars[0].vval.v_string, tv_get_string_chk(&argvars[1]), ic);
+ } else if (!error && argvars[0].v_type == VAR_LIST) {
+ int64_t idx = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ idx = (int64_t)tv_get_number_chk(&argvars[3], &error);
}
- } else if (argvars[0].v_type == VAR_LIST) {
- list_T *l = argvars[0].vval.v_list;
-
- if (l != NULL) {
- listitem_T *li = tv_list_first(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- long idx = tv_get_number_chk(&argvars[3], &error);
- if (!error) {
- li = tv_list_find(l, (int)idx);
- if (li == NULL) {
- semsg(_(e_listidx), (int64_t)idx);
- }
- }
- }
- if (error) {
- li = NULL;
- }
- }
-
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) {
- n++;
- }
- }
+ if (!error) {
+ n = count_list(argvars[0].vval.v_list, &argvars[1], idx, ic);
}
- } else if (argvars[0].v_type == VAR_DICT) {
+ } else if (!error && argvars[0].v_type == VAR_DICT) {
dict_T *d = argvars[0].vval.v_dict;
if (d != NULL) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[3].v_type != VAR_UNKNOWN) {
- emsg(_(e_invarg));
- }
- }
-
- int todo = error ? 0 : (int)d->dv_hashtab.ht_used;
- for (hashitem_T *hi = d->dv_hashtab.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &argvars[1], ic, false)) {
- n++;
- }
- }
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ emsg(_(e_invarg));
+ } else {
+ n = count_dict(argvars[0].vval.v_dict, &argvars[1], ic);
}
}
- } else {
- semsg(_(e_listdictarg), "count()");
+ } else if (!error) {
+ semsg(_(e_argument_of_str_must_be_list_string_or_dictionary), "count()");
}
rettv->vval.v_number = n;
}
@@ -1042,7 +1024,7 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
Dictionary ctx_dict = ctx_to_dict(ctx);
Error err = ERROR_INIT;
- object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ (void)object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
api_free_dictionary(ctx_dict);
api_clear_error(&err);
}
@@ -1064,17 +1046,17 @@ static void f_ctxpush(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
TV_LIST_ITER(argvars[0].vval.v_list, li, {
typval_T *tv_li = TV_LIST_ITEM_TV(li);
if (tv_li->v_type == VAR_STRING) {
- if (strequal((char *)tv_li->vval.v_string, "regs")) {
+ if (strequal(tv_li->vval.v_string, "regs")) {
types |= kCtxRegs;
- } else if (strequal((char *)tv_li->vval.v_string, "jumps")) {
+ } else if (strequal(tv_li->vval.v_string, "jumps")) {
types |= kCtxJumps;
- } else if (strequal((char *)tv_li->vval.v_string, "bufs")) {
+ } else if (strequal(tv_li->vval.v_string, "bufs")) {
types |= kCtxBufs;
- } else if (strequal((char *)tv_li->vval.v_string, "gvars")) {
+ } else if (strequal(tv_li->vval.v_string, "gvars")) {
types |= kCtxGVars;
- } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) {
+ } else if (strequal(tv_li->vval.v_string, "sfuncs")) {
types |= kCtxSFuncs;
- } else if (strequal((char *)tv_li->vval.v_string, "funcs")) {
+ } else if (strequal(tv_li->vval.v_string, "funcs")) {
types |= kCtxFuncs;
}
}
@@ -1108,14 +1090,16 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- int save_did_emsg = did_emsg;
+ const int save_did_emsg = did_emsg;
did_emsg = false;
Dictionary dict = vim_to_object(&argvars[0]).data.dictionary;
Context tmp = CONTEXT_INIT;
- ctx_from_dict(dict, &tmp);
+ Error err = ERROR_INIT;
+ ctx_from_dict(dict, &tmp, &err);
- if (did_emsg) {
+ if (ERROR_SET(&err)) {
+ semsg("%s", err.msg);
ctx_free(&tmp);
} else {
ctx_free(ctx);
@@ -1123,6 +1107,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
api_free_dictionary(dict);
+ api_clear_error(&err);
did_emsg = save_did_emsg;
}
@@ -1134,12 +1119,13 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Set the cursor position.
-/// If 'charcol' is true, then use the column number as a character offset.
+/// If "charcol" is true, then use the column number as a character offset.
/// Otherwise use the column number as a byte offset.
static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
{
- long lnum, col;
- long coladd = 0;
+ linenr_T lnum;
+ colnr_T col;
+ colnr_T coladd = 0;
bool set_curswant = true;
rettv->vval.v_number = -1;
@@ -1167,12 +1153,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
} else if (lnum == 0) {
lnum = curwin->w_cursor.lnum;
}
- col = (long)tv_get_number_chk(&argvars[1], NULL);
+ col = (colnr_T)tv_get_number_chk(&argvars[1], NULL);
if (charcol) {
- col = buf_charidx_to_byteidx(curbuf, (linenr_T)lnum, (int)col) + 1;
+ col = buf_charidx_to_byteidx(curbuf, lnum, (int)col) + 1;
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- coladd = (long)tv_get_number_chk(&argvars[2], NULL);
+ coladd = (colnr_T)tv_get_number_chk(&argvars[2], NULL);
}
} else {
emsg(_(e_invarg));
@@ -1182,12 +1168,12 @@ static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol)
return; // type error; errmsg already given
}
if (lnum > 0) {
- curwin->w_cursor.lnum = (linenr_T)lnum;
+ curwin->w_cursor.lnum = lnum;
}
if (col > 0) {
- curwin->w_cursor.col = (colnr_T)col - 1;
+ curwin->w_cursor.col = col - 1;
}
- curwin->w_cursor.coladd = (colnr_T)coladd;
+ curwin->w_cursor.coladd = coladd;
// Make sure the cursor is in a valid position.
check_cursor();
@@ -1236,18 +1222,16 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "deepcopy()" function
static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int noref = 0;
+ if (tv_check_for_opt_bool_arg(argvars, 1) == FAIL) {
+ return;
+ }
+ varnumber_T noref = 0;
if (argvars[1].v_type != VAR_UNKNOWN) {
- noref = (int)tv_get_bool_chk(&argvars[1], NULL);
- }
- if (noref < 0 || noref > 1) {
- semsg(_(e_using_number_as_bool_nr), noref);
- } else {
- var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0
- ? get_copyID()
- : 0));
+ noref = tv_get_bool_chk(&argvars[1], NULL);
}
+
+ var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0));
}
/// "delete()" function
@@ -1546,11 +1530,11 @@ static void f_eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
const char *s = tv_get_string_chk(&argvars[0]);
if (s != NULL) {
- s = (const char *)skipwhite(s);
+ s = skipwhite(s);
}
const char *const expr_start = s;
- if (s == NULL || eval1((char **)&s, rettv, true) == FAIL) {
+ if (s == NULL || eval1((char **)&s, rettv, &EVALARG_EVALUATE) == FAIL) {
if (expr_start != NULL && !aborting()) {
semsg(_(e_invexpr2), expr_start);
}
@@ -1722,7 +1706,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(exp);
}
} else if (*p == '&' || *p == '+') { // Option.
- n = (get_option_tv(&p, NULL, true) == OK);
+ n = (eval_option(&p, NULL, true) == OK);
if (*skipwhite(p) != NUL) {
n = false; // Trailing garbage.
}
@@ -1752,7 +1736,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *p_csl_save = p_csl;
// avoid using 'completeslash' here
- p_csl = empty_option;
+ p_csl = empty_string_option;
#endif
rettv->v_type = VAR_STRING;
@@ -1769,7 +1753,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
emsg_off++;
}
size_t len;
- char *errormsg = NULL;
+ const char *errormsg = NULL;
char *result = eval_vars((char *)s, s, &len, NULL, &errormsg, NULL, false);
if (p_verbose == 0) {
emsg_off--;
@@ -1779,7 +1763,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (rettv->v_type == VAR_LIST) {
tv_list_alloc_ret(rettv, (result != NULL));
if (result != NULL) {
- tv_list_append_string(rettv->vval.v_list, (const char *)result, -1);
+ tv_list_append_string(rettv->vval.v_list, result, -1);
}
XFREE_CLEAR(result);
} else {
@@ -1805,8 +1789,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ExpandOne(&xpc, (char *)s, NULL, options, WILD_ALL_KEEP);
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)xpc.xp_files[i], -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
ExpandCleanup(&xpc);
}
@@ -1835,7 +1818,7 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Expand all the special characters in a command string.
static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *errormsg = NULL;
+ const char *errormsg = NULL;
bool emsgoff = true;
if (argvars[1].v_type == VAR_DICT
@@ -1870,8 +1853,8 @@ static void f_expandcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = cmdstr;
}
-/// "flatten(list[, {maxdepth}])" function
-static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "flatten()" and "flattennew()" functions
+static void flatten_common(typval_T *argvars, typval_T *rettv, bool make_copy)
{
bool error = false;
@@ -1880,11 +1863,11 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- long maxdepth;
+ int maxdepth;
if (argvars[1].v_type == VAR_UNKNOWN) {
maxdepth = 999999;
} else {
- maxdepth = (long)tv_get_number_chk(&argvars[1], &error);
+ maxdepth = (int)tv_get_number_chk(&argvars[1], &error);
if (error) {
return;
}
@@ -1895,92 +1878,179 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
list_T *list = argvars[0].vval.v_list;
- if (list != NULL
- && !value_check_lock(tv_list_locked(list),
- N_("flatten() argument"),
- TV_TRANSLATE)
- && tv_list_flatten(list, maxdepth) == OK) {
- tv_copy(&argvars[0], rettv);
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = list;
+ if (list == NULL) {
+ return;
+ }
+
+ if (make_copy) {
+ list = tv_list_copy(NULL, list, false, get_copyID());
+ rettv->vval.v_list = list;
+ if (list == NULL) {
+ return;
+ }
+ } else {
+ if (value_check_lock(tv_list_locked(list), N_("flatten() argument"), TV_TRANSLATE)) {
+ return;
+ }
+ tv_list_ref(list);
}
+
+ tv_list_flatten(list, NULL, tv_list_len(list), maxdepth);
}
-/// "extend(list, list [, idx])" function
-/// "extend(dict, dict [, action])" function
-static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "flatten(list[, {maxdepth}])" function
+static void f_flatten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *const arg_errmsg = N_("extend() argument");
+ flatten_common(argvars, rettv, false);
+}
- if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
- bool error = false;
+/// "flattennew(list[, {maxdepth}])" function
+static void f_flattennew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ flatten_common(argvars, rettv, true);
+}
- list_T *const l1 = argvars[0].vval.v_list;
- list_T *const l2 = argvars[1].vval.v_list;
- if (!value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
- listitem_T *item;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- long before = (long)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return; // Type error; errmsg already given.
- }
+/// extend() a List. Append List argvars[1] to List argvars[0] before index
+/// argvars[3] and return the resulting list in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_list(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ bool error = false;
- if (before == tv_list_len(l1)) {
- item = NULL;
- } else {
- item = tv_list_find(l1, (int)before);
- if (item == NULL) {
- semsg(_(e_listidx), (int64_t)before);
- return;
- }
- }
- } else {
+ list_T *l1 = argvars[0].vval.v_list;
+ list_T *const l2 = argvars[1].vval.v_list;
+ if (is_new || !value_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) {
+ if (is_new) {
+ l1 = tv_list_copy(NULL, l1, false, get_copyID());
+ if (l1 == NULL) {
+ return;
+ }
+ }
+
+ listitem_T *item;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int before = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (before == tv_list_len(l1)) {
item = NULL;
+ } else {
+ item = tv_list_find(l1, before);
+ if (item == NULL) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)before);
+ return;
+ }
}
- tv_list_extend(l1, l2, item);
+ } else {
+ item = NULL;
+ }
+ tv_list_extend(l1, l2, item);
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_list = l1,
+ };
+ } else {
tv_copy(&argvars[0], rettv);
}
- } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type ==
- VAR_DICT) {
- dict_T *const d1 = argvars[0].vval.v_dict;
- dict_T *const d2 = argvars[1].vval.v_dict;
- if (d1 == NULL) {
- const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
- (void)locked;
- assert(locked == true);
- } else if (d2 == NULL) {
- // Do nothing
- tv_copy(&argvars[0], rettv);
- } else if (!value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
- const char *action = "force";
- // Check the third argument.
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const av[] = { "keep", "force", "error" };
+ }
+}
- action = tv_get_string_chk(&argvars[2]);
- if (action == NULL) {
- return; // Type error; error message already given.
- }
- size_t i;
- for (i = 0; i < ARRAY_SIZE(av); i++) {
- if (strcmp(action, av[i]) == 0) {
- break;
- }
- }
- if (i == 3) {
- semsg(_(e_invarg2), action);
- return;
+/// extend() a Dict. Append Dict argvars[1] to Dict argvars[0] and return the
+/// resulting Dict in "rettv".
+///
+/// @param is_new true for extendnew()
+static void extend_dict(typval_T *argvars, const char *arg_errmsg, bool is_new, typval_T *rettv)
+{
+ dict_T *d1 = argvars[0].vval.v_dict;
+ dict_T *const d2 = argvars[1].vval.v_dict;
+ if (d1 == NULL) {
+ const bool locked = value_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE);
+ (void)locked;
+ assert(locked == true);
+ } else if (d2 == NULL) {
+ // Do nothing
+ tv_copy(&argvars[0], rettv);
+ } else if (is_new || !value_check_lock(d1->dv_lock, arg_errmsg, TV_TRANSLATE)) {
+ if (is_new) {
+ d1 = tv_dict_copy(NULL, d1, false, get_copyID());
+ if (d1 == NULL) {
+ return;
+ }
+ }
+
+ const char *action = "force";
+ // Check the third argument.
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const av[] = { "keep", "force", "error" };
+
+ action = tv_get_string_chk(&argvars[2]);
+ if (action == NULL) {
+ return; // Type error; error message already given.
+ }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(av); i++) {
+ if (strcmp(action, av[i]) == 0) {
+ break;
}
}
+ if (i == 3) {
+ semsg(_(e_invarg2), action);
+ return;
+ }
+ }
- tv_dict_extend(d1, d2, action);
+ tv_dict_extend(d1, d2, action);
+ if (is_new) {
+ *rettv = (typval_T){
+ .v_type = VAR_DICT,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_dict = d1,
+ };
+ } else {
tv_copy(&argvars[0], rettv);
}
+ }
+}
+
+/// "extend()" or "extendnew()" function.
+///
+/// @param is_new true for extendnew()
+static void extend(typval_T *argvars, typval_T *rettv, char *arg_errmsg, bool is_new)
+{
+ if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) {
+ extend_list(argvars, arg_errmsg, is_new, rettv);
+ } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == VAR_DICT) {
+ extend_dict(argvars, arg_errmsg, is_new, rettv);
} else {
- semsg(_(e_listdictarg), "extend()");
+ semsg(_(e_listdictarg), is_new ? "extendnew()" : "extend()");
}
}
+/// "extend(list, list [, idx])" function
+/// "extend(dict, dict [, action])" function
+static void f_extend(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extend() argument");
+ extend(argvars, rettv, errmsg, false);
+}
+
+/// "extendnew(list, list [, idx])" function
+/// "extendnew(dict, dict [, action])" function
+static void f_extendnew(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *errmsg = N_("extendnew() argument");
+ extend(argvars, rettv, errmsg, true);
+}
+
/// "feedkeys()" function
static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2053,6 +2123,9 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
}
if (*fname != NUL && !error) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
do {
if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) {
xfree(fresult);
@@ -2063,13 +2136,17 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
find_what, curbuf->b_ffname,
(find_what == FINDFILE_DIR
? ""
- : curbuf->b_p_sua));
+ : curbuf->b_p_sua),
+ &file_to_find, &search_ctx);
first = false;
if (fresult != NULL && rettv->v_type == VAR_LIST) {
- tv_list_append_string(rettv->vval.v_list, (const char *)fresult, -1);
+ tv_list_append_string(rettv->vval.v_list, fresult, -1);
}
} while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
}
if (rettv->v_type == VAR_STRING) {
@@ -2077,12 +2154,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
}
}
-/// "filter()" function
-static void f_filter(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, false);
-}
-
/// "finddir({fname}[, {path}[, {count}]])" function
static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2164,7 +2235,8 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "foreground()" function
static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{}
+{
+}
static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2433,7 +2505,7 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
[kCdScopeTabpage] = 0, // Number of tab to look at.
};
- char *cwd = NULL; // Current working directory to print
+ char *cwd = NULL; // Current working directory to print
char *from = NULL; // The original string to copy
tabpage_T *tp = curtab; // The tabpage to look at.
@@ -2681,47 +2753,6 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_buf_local_marks(buf, rettv->vval.v_list);
}
-/// "getmousepos()" function
-static void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int row = mouse_row;
- int col = mouse_col;
- int grid = mouse_grid;
- varnumber_T winid = 0;
- varnumber_T winrow = 0;
- varnumber_T wincol = 0;
- linenr_T lnum = 0;
- varnumber_T column = 0;
-
- tv_dict_alloc_ret(rettv);
- dict_T *d = rettv->vval.v_dict;
-
- tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
- tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
-
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp != NULL) {
- int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
- // The height is adjusted by 1 when there is a bottom border. This is not
- // necessary for a top border since `row` starts at -1 in that case.
- if (row < height + wp->w_border_adj[2]) {
- winid = wp->handle;
- winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border
- wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border
- if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
- (void)mouse_comp_pos(wp, &row, &col, &lnum);
- col = vcol2col(wp, lnum, col);
- column = col + 1;
- }
- }
- }
- tv_dict_add_nr(d, S_LEN("winid"), winid);
- tv_dict_add_nr(d, S_LEN("winrow"), winrow);
- tv_dict_add_nr(d, S_LEN("wincol"), wincol);
- tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
- tv_dict_add_nr(d, S_LEN("column"), column);
-}
-
/// "getpid()" function
static void f_getpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2840,7 +2871,8 @@ static void f_gettagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Dummy timer callback. Used by f_wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{}
+{
+}
/// Dummy timer close callback. Used by f_wait().
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
@@ -2867,8 +2899,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
int timeout = (int)argvars[0].vval.v_number;
typval_T expr = argvars[1];
int interval = argvars[2].v_type == VAR_NUMBER
- ? (int)argvars[2].vval.v_number
- : 200; // Default.
+ ? (int)argvars[2].vval.v_number
+ : 200; // Default.
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
// Start dummy timer.
@@ -2882,8 +2914,11 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool error = false;
const int called_emsg_before = called_emsg;
+ // Flush screen updates before blocking.
+ ui_flush();
+
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
- eval_expr_typval(&expr, &argv, 0, &exprval) != OK
+ eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK
|| tv_get_number_chk(&exprval, &error)
|| called_emsg > called_emsg_before || error || got_int);
@@ -2941,8 +2976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
WILD_ALL_KEEP);
tv_list_alloc_ret(rettv, xpc.xp_numfiles);
for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i],
- -1);
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
}
ExpandCleanup(&xpc);
}
@@ -3013,14 +3047,12 @@ static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "gettext()" function
static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_STRING
- || argvars[0].vval.v_string == NULL
- || *argvars[0].vval.v_string == NUL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[0]));
- } else {
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
+ if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) {
+ return;
}
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrdup(_(argvars[0].vval.v_string));
}
/// "has()" function
@@ -3146,6 +3178,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
"windows",
"winaltkeys",
"writebackup",
+#ifdef HAVE_XATTR
+ "xattr",
+#endif
"nvim",
};
@@ -3161,7 +3196,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!n) {
- if (STRNICMP(name, "patch", 5) == 0) {
+ if (STRNICMP(name, "gui_running", 11) == 0) {
+ n = ui_gui_attached();
+ } else if (STRNICMP(name, "patch", 5) == 0) {
if (name[5] == '-'
&& strlen(name) >= 11
&& ascii_isdigit(name[6])
@@ -3216,7 +3253,7 @@ static bool has_wsl(void)
static TriState has_wsl = kNone;
if (has_wsl == kNone) {
Error err = ERROR_INIT;
- Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.loop.os_uname()['release']:lower()"
+ Object o = nlua_exec(STATIC_CSTR_AS_STRING("return vim.uv.os_uname()['release']:lower()"
":match('microsoft') and true or false"),
(Array)ARRAY_DICT_INIT, &err);
assert(!ERROR_SET(&err));
@@ -3250,7 +3287,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
[kCdScopeTabpage] = 0, // Number of tab to look at.
};
- tabpage_T *tp = curtab; // The tabpage to look at.
+ tabpage_T *tp = curtab; // The tabpage to look at.
win_T *win = curwin; // The window to look at.
rettv->v_type = VAR_NUMBER;
@@ -3349,34 +3386,6 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = xstrdup(hostname);
}
-/// iconv() function
-static void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- vimconv_T vimconv;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *const str = tv_get_string(&argvars[0]);
- char buf1[NUMBUFLEN];
- char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
- char buf2[NUMBUFLEN];
- char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
- vimconv.vc_type = CONV_NONE;
- convert_setup(&vimconv, from, to);
-
- // If the encodings are equal, no conversion needed.
- if (vimconv.vc_type == CONV_NONE) {
- rettv->vval.v_string = xstrdup(str);
- } else {
- rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
- }
-
- convert_setup(&vimconv, NULL, NULL);
- xfree(from);
- xfree(to);
-}
-
/// "indent()" function
static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3391,7 +3400,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "index()" function
static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- long idx = 0;
+ int idx = 0;
bool ic = false;
rettv->vval.v_number = -1;
@@ -3418,7 +3427,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
for (idx = start; idx < tv_blob_len(b); idx++) {
typval_T tv;
tv.v_type = VAR_NUMBER;
- tv.vval.v_number = tv_blob_get(b, (int)idx);
+ tv.vval.v_number = tv_blob_get(b, idx);
if (tv_equal(&tv, &argvars[1], ic, false)) {
rettv->vval.v_number = idx;
return;
@@ -3444,7 +3453,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (error || idx == -1) {
item = NULL;
} else {
- item = tv_list_find(l, (int)idx);
+ item = tv_list_find(l, idx);
assert(item != NULL);
}
if (argvars[3].v_type != VAR_UNKNOWN) {
@@ -3463,6 +3472,138 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+/// Evaluate "expr" with the v:key and v:val arguments and return the result.
+/// The expression is expected to return a boolean value. The caller should set
+/// the VV_KEY and VV_VAL vim variables before calling this function.
+static varnumber_T indexof_eval_expr(typval_T *expr)
+{
+ typval_T argv[3];
+ argv[0] = *get_vim_var_tv(VV_KEY);
+ argv[1] = *get_vim_var_tv(VV_VAL);
+ typval_T newtv;
+ newtv.v_type = VAR_UNKNOWN;
+
+ if (eval_expr_typval(expr, false, argv, 2, &newtv) == FAIL) {
+ return false;
+ }
+
+ bool error = false;
+ varnumber_T found = tv_get_bool_chk(&newtv, &error);
+ tv_clear(&newtv);
+
+ return error ? false : found;
+}
+
+/// Evaluate "expr" for each byte in the Blob "b" starting with the byte at
+/// "startidx" and return the index of the byte where "expr" is TRUE. Returns
+/// -1 if "expr" doesn't evaluate to TRUE for any of the bytes.
+static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
+{
+ if (b == NULL) {
+ return -1;
+ }
+
+ if (startidx < 0) {
+ // negative index: index from the last byte
+ startidx = tv_blob_len(b) + startidx;
+ if (startidx < 0) {
+ startidx = 0;
+ }
+ }
+
+ for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) {
+ set_vim_var_nr(VV_KEY, idx);
+ set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx));
+
+ if (indexof_eval_expr(expr)) {
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+/// Evaluate "expr" for each item in the List "l" starting with the item at
+/// "startidx" and return the index of the item where "expr" is TRUE. Returns
+/// -1 if "expr" doesn't evaluate to TRUE for any of the items.
+static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
+{
+ if (l == NULL) {
+ return -1;
+ }
+
+ listitem_T *item;
+ varnumber_T idx = 0;
+ if (startidx == 0) {
+ item = tv_list_first(l);
+ } else {
+ // Start at specified item.
+ idx = tv_list_uidx(l, (int)startidx);
+ if (idx == -1) {
+ item = NULL;
+ } else {
+ item = tv_list_find(l, (int)idx);
+ assert(item != NULL);
+ }
+ }
+
+ for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
+ set_vim_var_nr(VV_KEY, idx);
+ tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL));
+
+ bool found = indexof_eval_expr(expr);
+ tv_clear(get_vim_var_tv(VV_VAL));
+
+ if (found) {
+ return idx;
+ }
+ }
+
+ return -1;
+}
+
+/// "indexof()" function
+static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_list_or_blob_arg(argvars, 0) == FAIL
+ || tv_check_for_string_or_func_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 2) == FAIL) {
+ return;
+ }
+
+ if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+ || (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) {
+ return;
+ }
+
+ varnumber_T startidx = 0;
+ if (argvars[2].v_type == VAR_DICT) {
+ startidx = tv_dict_get_number_def(argvars[2].vval.v_dict, "startidx", 0);
+ }
+
+ typval_T save_val;
+ typval_T save_key;
+ prepare_vimvar(VV_VAL, &save_val);
+ prepare_vimvar(VV_KEY, &save_key);
+
+ // We reset "did_emsg" to be able to detect whether an error occurred
+ // during evaluation of the expression.
+ const int save_did_emsg = did_emsg;
+ did_emsg = false;
+
+ if (argvars[0].v_type == VAR_BLOB) {
+ rettv->vval.v_number = indexof_blob(argvars[0].vval.v_blob, startidx, &argvars[1]);
+ } else {
+ rettv->vval.v_number = indexof_list(argvars[0].vval.v_list, startidx, &argvars[1]);
+ }
+
+ restore_vimvar(VV_KEY, &save_key);
+ restore_vimvar(VV_VAL, &save_val);
+ did_emsg |= save_did_emsg;
+}
+
static bool inputsecret_flag = false;
/// "input()" function
@@ -3544,7 +3685,6 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "insert()" function
static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- list_T *l;
bool error = false;
if (argvars[0].v_type == VAR_BLOB) {
@@ -3556,11 +3696,11 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- long before = 0;
+ int before = 0;
const int len = tv_blob_len(b);
if (argvars[2].v_type != VAR_UNKNOWN) {
- before = (long)tv_get_number_chk(&argvars[2], &error);
+ before = (int)tv_get_number_chk(&argvars[2], &error);
if (error) {
return; // type error; errmsg already given
}
@@ -3587,9 +3727,13 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_copy(&argvars[0], rettv);
} else if (argvars[0].v_type != VAR_LIST) {
semsg(_(e_listblobarg), "insert()");
- } else if (!value_check_lock(tv_list_locked((l = argvars[0].vval.v_list)),
- N_("insert() argument"), TV_TRANSLATE)) {
- long before = 0;
+ } else {
+ list_T *l = argvars[0].vval.v_list;
+ if (value_check_lock(tv_list_locked(l), N_("insert() argument"), TV_TRANSLATE)) {
+ return;
+ }
+
+ int64_t before = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
before = tv_get_number_chk(&argvars[2], &error);
}
@@ -3602,7 +3746,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (before != tv_list_len(l)) {
item = tv_list_find(l, (int)before);
if (item == NULL) {
- semsg(_(e_listidx), (int64_t)before);
+ semsg(_(e_list_index_out_of_range_nr), before);
l = NULL;
}
}
@@ -3614,8 +3758,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// "interrupt()" function
-static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED,
- EvalFuncData fptr FUNC_ATTR_UNUSED)
+static void f_interrupt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
got_int = true;
}
@@ -3792,7 +3935,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
if (!clear_env) {
typval_T temp_env = TV_INITIAL_VALUE;
- f_environ(NULL, &temp_env, (EvalFuncData){ .nullptr = NULL });
+ f_environ(NULL, &temp_env, (EvalFuncData){ .null = NULL });
tv_dict_extend(env, temp_env.vval.v_dict, "force");
tv_dict_free(temp_env.vval.v_dict);
@@ -3809,12 +3952,13 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
}
}
#ifndef MSWIN
- // Set COLORTERM to "truecolor" if termguicolors is set and 256
- // otherwise, but only if it was set in the parent terminal at all
- dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
- if (dv) {
- tv_dict_item_remove(env, dv);
- tv_dict_add_str(env, S_LEN("COLORTERM"), p_tgc ? "truecolor" : "256");
+ // Set COLORTERM to "truecolor" if termguicolors is set
+ if (p_tgc) {
+ dictitem_T *dv = tv_dict_find(env, S_LEN("COLORTERM"));
+ if (dv) {
+ tv_dict_item_remove(env, dv);
+ }
+ tv_dict_add_str(env, S_LEN("COLORTERM"), "truecolor");
}
#endif
}
@@ -3846,7 +3990,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
#ifdef MSWIN
TV_DICT_ITER(job_env->di_tv.vval.v_dict, var, {
// Always use upper-case keys for Windows so we detect duplicate keys
- char *const key = strcase_save((const char *)var->di_key, true);
+ char *const key = strcase_save(var->di_key, true);
size_t len = strlen(key);
dictitem_T *dv = tv_dict_find(env, key, len);
if (dv) {
@@ -3992,7 +4136,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
env = create_environment(job_env, clear_env, pty, term_name);
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit, pty,
+ Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit, pty,
rpc, overlapped, detach, stdin_mode, cwd,
width, height, env, &rettv->vval.v_number);
if (chan) {
@@ -4049,6 +4193,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
ui_busy_start();
+ ui_flush();
list_T *args = argvars[0].vval.v_list;
Channel **jobs = xcalloc((size_t)tv_list_len(args), sizeof(*jobs));
MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop);
@@ -4361,30 +4506,24 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
}
-/// "map()" function
-static void f_map(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- filter_map(argvars, rettv, true);
-}
-
static void find_some_match(typval_T *const argvars, typval_T *const rettv,
const SomeMatchType type)
{
char *str = NULL;
- long len = 0;
+ int64_t len = 0;
char *expr = NULL;
regmatch_T regmatch;
- long start = 0;
- long nth = 1;
+ int64_t start = 0;
+ int64_t nth = 1;
colnr_T startcol = 0;
bool match = false;
list_T *l = NULL;
- long idx = 0;
+ int idx = 0;
char *tofree = NULL;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
rettv->vval.v_number = -1;
switch (type) {
@@ -4418,7 +4557,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
li = tv_list_first(l);
} else {
expr = str = (char *)tv_get_string(&argvars[0]);
- len = (long)strlen(str);
+ len = (int64_t)strlen(str);
}
char patbuf[NUMBUFLEN];
@@ -4439,7 +4578,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (idx == -1) {
goto theend;
}
- li = tv_list_find(l, (int)idx);
+ li = tv_list_find(l, idx);
} else {
if (start < 0) {
start = 0;
@@ -4466,11 +4605,11 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
}
}
- regmatch.regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING);
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
if (regmatch.regprog != NULL) {
regmatch.rm_ic = p_ic;
- for (;;) {
+ while (true) {
if (l != NULL) {
if (li == NULL) {
match = false;
@@ -4531,8 +4670,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (regmatch.endp[i] == NULL) {
tv_list_append_string(rettv->vval.v_list, NULL, 0);
} else {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)regmatch.startp[i],
+ tv_list_append_string(rettv->vval.v_list, regmatch.startp[i],
(regmatch.endp[i] - regmatch.startp[i]));
}
}
@@ -4542,7 +4680,7 @@ static void find_some_match(typval_T *const argvars, typval_T *const rettv,
if (l != NULL) {
tv_copy(TV_LIST_ITEM_TV(li), rettv);
} else {
- rettv->vval.v_string = xmemdupz((const char *)regmatch.startp[0],
+ rettv->vval.v_string = xmemdupz(regmatch.startp[0],
(size_t)(regmatch.endp[0] -
regmatch.startp[0]));
}
@@ -4671,7 +4809,7 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "mkdir()" function
static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int prot = 0755; // -V536
+ int prot = 0755;
rettv->vval.v_number = FAIL;
if (check_secure()) {
@@ -4689,6 +4827,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
*path_tail_with_sep((char *)dir) = NUL;
}
+ bool defer = false;
+ bool defer_recurse = false;
+ char *created = NULL;
if (argvars[1].v_type != VAR_UNKNOWN) {
if (argvars[2].v_type != VAR_UNKNOWN) {
prot = (int)tv_get_number_chk(&argvars[2], NULL);
@@ -4696,9 +4837,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
}
- if (strcmp(tv_get_string(&argvars[1]), "p") == 0) {
+ const char *arg2 = tv_get_string(&argvars[1]);
+ defer = vim_strchr(arg2, 'D') != NULL;
+ defer_recurse = vim_strchr(arg2, 'R') != NULL;
+ if ((defer || defer_recurse) && !can_add_defer()) {
+ return;
+ }
+
+ if (vim_strchr(arg2, 'p') != NULL) {
char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir);
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir,
+ defer || defer_recurse ? &created : NULL);
if (ret != 0) {
semsg(_(e_mkdir), failed_dir, os_strerror(ret));
xfree(failed_dir);
@@ -4706,10 +4855,27 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
rettv->vval.v_number = OK;
- return;
}
}
- rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+ if (rettv->vval.v_number == FAIL) {
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+ }
+
+ // Handle "D" and "R": deferred deletion of the created directory.
+ if (rettv->vval.v_number == OK
+ && created == NULL && (defer || defer_recurse)) {
+ created = FullName_save(dir, false);
+ }
+ if (created != NULL) {
+ typval_T tv[2];
+ tv[0].v_type = VAR_STRING;
+ tv[0].v_lock = VAR_UNLOCKED;
+ tv[0].vval.v_string = created;
+ tv[1].v_type = VAR_STRING;
+ tv[1].v_lock = VAR_UNLOCKED;
+ tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
+ add_defer("delete", 2, tv);
+ }
}
/// "mode()" function
@@ -4729,6 +4895,50 @@ static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_STRING;
}
+static void may_add_state_char(garray_T *gap, const char *include, uint8_t c)
+{
+ if (include == NULL || vim_strchr(include, c) != NULL) {
+ ga_append(gap, c);
+ }
+}
+
+/// "state()" function
+static void f_state(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 20);
+ const char *include = NULL;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ include = tv_get_string(&argvars[0]);
+ }
+
+ if (!(stuff_empty() && typebuf.tb_len == 0 && !using_script())) {
+ may_add_state_char(&ga, include, 'm');
+ }
+ if (op_pending()) {
+ may_add_state_char(&ga, include, 'o');
+ }
+ if (autocmd_busy) {
+ may_add_state_char(&ga, include, 'x');
+ }
+ if (ins_compl_active()) {
+ may_add_state_char(&ga, include, 'a');
+ }
+ if (!get_was_safe_state()) {
+ may_add_state_char(&ga, include, 'S');
+ }
+ for (int i = 0; i < get_callback_depth() && i < 3; i++) {
+ may_add_state_char(&ga, include, 'c');
+ }
+ if (msg_scrolled > 0) {
+ may_add_state_char(&ga, include, 's');
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ga.ga_data;
+}
+
/// "msgpackdump()" function
static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
FUNC_ATTR_NONNULL_ALL
@@ -4752,7 +4962,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
int idx = 0;
TV_LIST_ITER(list, li, {
- vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx);
+ vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx);
idx++;
if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
break;
@@ -4787,9 +4997,10 @@ static int msgpackparse_convert_item(const msgpack_object data, const msgpack_un
tv_list_append_owned_tv(ret_list, tv);
return OK;
}
- default:
+ case MSGPACK_UNPACK_EXTRA_BYTES:
abort();
}
+ UNREACHABLE;
}
static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list)
@@ -4810,7 +5021,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret
}
msgpack_unpacked unpacked;
msgpack_unpacked_init(&unpacked);
- do {
+ while (true) {
if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
emsg(_(e_outofmem));
goto end;
@@ -4840,7 +5051,7 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret
if (rlret == OK) {
break;
}
- } while (true);
+ }
end:
msgpack_unpacker_free(unpacker);
@@ -4925,7 +5136,7 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- char buf[MB_MAXBYTES];
+ char buf[MB_MAXCHAR];
const int len = utf_char2bytes((int)num, buf);
rettv->v_type = VAR_STRING;
@@ -5054,7 +5265,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, EvalFuncDa
}
callback_free(&buf->b_prompt_interrupt);
- buf->b_prompt_interrupt= interrupt_callback;
+ buf->b_prompt_interrupt = interrupt_callback;
}
/// "prompt_getprompt({buffer})" function
@@ -5210,10 +5421,10 @@ static void f_rand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
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));
+ typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0));
+ typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1));
+ typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2));
+ typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3));
if (tvx->v_type != VAR_NUMBER) {
goto theend;
}
@@ -5341,7 +5552,7 @@ static varnumber_T readdir_checkitem(void *context, const char *name)
argv[0].vval.v_string = (char *)name;
typval_T rettv;
- if (eval_expr_typval(expr, argv, 1, &rettv) == FAIL) {
+ if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) {
goto theend;
}
@@ -5385,18 +5596,27 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
int io_size = sizeof(buf);
char *prev = NULL; // previously read bytes, if any
- ptrdiff_t prevlen = 0; // length of data in prev
+ ptrdiff_t prevlen = 0; // length of data in prev
ptrdiff_t prevsize = 0; // size of prev buffer
- long maxline = MAXLNUM;
+ int64_t maxline = MAXLNUM;
+ off_T offset = 0;
+ off_T size = -1;
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
- blob = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
+ if (always_blob) {
+ offset = (off_T)tv_get_number(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ size = (off_T)tv_get_number(&argvars[2]);
+ }
+ } else {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
+ blob = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
}
}
@@ -5415,11 +5635,8 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
if (blob) {
tv_blob_alloc_ret(rettv);
- if (!read_blob(fd, rettv->vval.v_blob)) {
+ if (read_blob(fd, rettv, offset, size) == FAIL) {
semsg(_(e_notread), fname);
- // An empty blob is returned on error.
- tv_blob_free(rettv->vval.v_blob);
- rettv->vval.v_blob = NULL;
}
fclose(fd);
return;
@@ -5441,7 +5658,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
p++) {
if (readlen <= 0 || *p == '\n') {
- char *s = NULL;
+ char *s = NULL;
size_t len = (size_t)(p - start);
// Finished a line. Remove CRs before NL.
@@ -5458,7 +5675,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
}
if (prevlen == 0) {
assert(len < INT_MAX);
- s = xstrnsave(start, len);
+ s = xmemdupz(start, len);
} else {
// Change "prev" buffer to be the right size. This way
// the bytes are only copied once, and very long lines are
@@ -5497,11 +5714,11 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
// Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
// these may be in the "prev" string.
char back1 = p >= buf + 1 ? p[-1]
- : prevlen >= 1 ? prev[prevlen - 1] : NUL;
+ : prevlen >= 1 ? prev[prevlen - 1] : NUL;
char back2 = p >= buf + 2 ? p[-2]
- : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1]
- : prevlen >=
- 2 ? prev[prevlen - 2] : NUL;
+ : (p == buf + 1 && prevlen >= 1
+ ? prev[prevlen - 1]
+ : prevlen >= 2 ? prev[prevlen - 2] : NUL);
if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) {
char *dest = p - 2;
@@ -5514,9 +5731,9 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
// have to shuffle buf to close gap
int adjust_prevlen = 0;
- if (dest < buf) { // -V782
+ if (dest < buf) {
// adjust_prevlen must be 1 or 2.
- adjust_prevlen = (int)(buf - dest); // -V782
+ adjust_prevlen = (int)(buf - dest);
dest = buf;
}
if (readlen > p - buf + 1) {
@@ -5544,7 +5761,7 @@ static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_bl
prevsize = p - start;
} else {
ptrdiff_t grow50pc = (prevsize * 3) / 2;
- ptrdiff_t growmin = (p - start) * 2 + prevlen;
+ ptrdiff_t growmin = (p - start) * 2 + prevlen;
prevsize = grow50pc > growmin ? grow50pc : growmin;
}
prev = xrealloc(prev, (size_t)prevsize);
@@ -5651,8 +5868,8 @@ static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL
}
bool error = false;
- varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0L, &error);
- varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1L, &error);
+ varnumber_T n1 = tv_list_find_nr(arg->vval.v_list, 0, &error);
+ varnumber_T n2 = tv_list_find_nr(arg->vval.v_list, 1, &error);
if (error) {
return FAIL;
}
@@ -5766,6 +5983,37 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
while (n-- > 0) {
tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL);
}
+ } else if (argvars[0].v_type == VAR_BLOB) {
+ tv_blob_alloc_ret(rettv);
+ if (argvars[0].vval.v_blob == NULL || n <= 0) {
+ return;
+ }
+
+ const int slen = argvars[0].vval.v_blob->bv_ga.ga_len;
+ const int len = (int)(slen * n);
+ if (len <= 0) {
+ return;
+ }
+
+ ga_grow(&rettv->vval.v_blob->bv_ga, len);
+
+ rettv->vval.v_blob->bv_ga.ga_len = len;
+
+ int i;
+ for (i = 0; i < slen; i++) {
+ if (tv_blob_get(argvars[0].vval.v_blob, i) != 0) {
+ break;
+ }
+ }
+
+ if (i == slen) {
+ // No need to copy since all bytes are already zero
+ return;
+ }
+
+ for (i = 0; i < n; i++) {
+ tv_blob_set_range(rettv->vval.v_blob, i * slen, (i + 1) * slen - 1, argvars);
+ }
} else {
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
@@ -5839,8 +6087,8 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *const buf = xmallocz(MAXPATHL);
char *cpy;
- for (;;) {
- for (;;) {
+ while (true) {
+ while (true) {
len = readlink(p, buf, MAXPATHL);
if (len <= 0) {
break;
@@ -5857,13 +6105,13 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
// Ensure that the result will have a trailing path separator
- // if the argument has one. */
+ // if the argument has one.
if (remain == NULL && has_trailing_pathsep) {
add_pathsep(buf);
}
// Separate the first path component in the link value and
- // concatenate the remainders. */
+ // concatenate the remainders.
q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
if (*q != NUL) {
cpy = remain;
@@ -5877,7 +6125,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
q = path_tail(p);
if (q > p && *q == NUL) {
// Ignore trailing path separator.
- q[-1] = NUL;
+ p[q - p - 1] = NUL;
q = path_tail(p);
}
if (q > p && !path_is_absolute(buf)) {
@@ -5956,7 +6204,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
# else
char *v = os_realpath(fname, NULL);
- rettv->vval.v_string = (char_u *)(v == NULL ? xstrdup(fname) : v);
+ rettv->vval.v_string = v == NULL ? xstrdup(fname) : v;
# endif
#endif
@@ -5966,6 +6214,10 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "reverse({list})" function
static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ if (tv_check_for_string_or_list_or_blob_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
if (argvars[0].v_type == VAR_BLOB) {
blob_T *const b = argvars[0].vval.v_blob;
const int len = tv_blob_len(b);
@@ -5976,9 +6228,14 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_blob_set(b, len - i - 1, tmp);
}
tv_blob_set_ret(rettv, b);
- } else if (argvars[0].v_type != VAR_LIST) {
- semsg(_(e_listblobarg), "reverse()");
- } else {
+ } else if (argvars[0].v_type == VAR_STRING) {
+ rettv->v_type = VAR_STRING;
+ if (argvars[0].vval.v_string != NULL) {
+ rettv->vval.v_string = reverse_text(argvars[0].vval.v_string);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+ } else if (argvars[0].v_type == VAR_LIST) {
list_T *const l = argvars[0].vval.v_list;
if (!value_check_lock(tv_list_locked(l), N_("reverse() argument"),
TV_TRANSLATE)) {
@@ -5988,102 +6245,181 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "reduce(list, { accumulator, element -> value } [, initial])" function
-static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// Implementation of reduce() for list "argvars[0]", using the function "expr"
+/// starting with the optional initial value argvars[2] and return the result in
+/// "rettv".
+static void reduce_list(typval_T *argvars, typval_T *expr, typval_T *rettv)
{
- if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB) {
- emsg(_(e_listblobreq));
- return;
- }
+ list_T *const l = argvars[0].vval.v_list;
+ const int called_emsg_start = called_emsg;
- const char *func_name;
- partial_T *partial = NULL;
- if (argvars[1].v_type == VAR_FUNC) {
- func_name = argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- partial = argvars[1].vval.v_partial;
- func_name = partial_name(partial);
+ typval_T initial;
+ const listitem_T *li = NULL;
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_list_len(l) == 0) {
+ semsg(_(e_reduceempty), "List");
+ return;
+ }
+ const listitem_T *const first = tv_list_first(l);
+ initial = *TV_LIST_ITEM_TV(first);
+ li = TV_LIST_ITEM_NEXT(l, first);
} else {
- func_name = tv_get_string(&argvars[1]);
+ initial = argvars[2];
+ li = tv_list_first(l);
}
- if (*func_name == NUL) {
- return; // type error or empty name
+
+ tv_copy(&initial, rettv);
+
+ if (l == NULL) {
+ return;
}
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
+ const VarLockStatus prev_locked = tv_list_locked(l);
- typval_T initial;
- typval_T argv[3];
- if (argvars[0].v_type == VAR_LIST) {
- list_T *const l = argvars[0].vval.v_list;
- const listitem_T *li;
+ tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
+ for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ argv[1] = *TV_LIST_ITEM_TV(li);
+ rettv->v_type = VAR_UNKNOWN;
- if (argvars[2].v_type == VAR_UNKNOWN) {
- if (tv_list_len(l) == 0) {
- semsg(_(e_reduceempty), "List");
- return;
- }
- const listitem_T *const first = tv_list_first(l);
- initial = *TV_LIST_ITEM_TV(first);
- li = TV_LIST_ITEM_NEXT(l, first);
- } else {
- initial = argvars[2];
- li = tv_list_first(l);
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ tv_clear(&argv[0]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
}
+ }
+ tv_list_set_lock(l, prev_locked);
+}
- tv_copy(&initial, rettv);
+/// Implementation of reduce() for String "argvars[0]" using the function "expr"
+/// starting with the optional initial value "argvars[2]" and return the result
+/// in "rettv".
+static void reduce_string(typval_T *argvars, typval_T *expr, typval_T *rettv)
+{
+ const char *p = tv_get_string(&argvars[0]);
+ int len;
+ const int called_emsg_start = called_emsg;
- if (l != NULL) {
- const VarLockStatus prev_locked = tv_list_locked(l);
- const int called_emsg_start = called_emsg;
-
- tv_list_set_lock(l, VAR_FIXED); // disallow the list changing here
- for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- argv[0] = *rettv;
- argv[1] = *TV_LIST_ITEM_TV(li);
- rettv->v_type = VAR_UNKNOWN;
- const int r = call_func((char *)func_name, -1, rettv, 2, argv, &funcexe);
- tv_clear(&argv[0]);
- if (r == FAIL || called_emsg != called_emsg_start) {
- break;
- }
- }
- tv_list_set_lock(l, prev_locked);
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (*p == NUL) {
+ semsg(_(e_reduceempty), "String");
+ return;
}
+ len = utfc_ptr2len(p);
+ *rettv = (typval_T){
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
+ p += len;
+ } else if (tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
} else {
- const blob_T *const b = argvars[0].vval.v_blob;
- int i;
+ tv_copy(&argvars[2], rettv);
+ }
- if (argvars[2].v_type == VAR_UNKNOWN) {
- if (tv_blob_len(b) == 0) {
- semsg(_(e_reduceempty), "Blob");
- return;
- }
- initial.v_type = VAR_NUMBER;
- initial.vval.v_number = tv_blob_get(b, 0);
- i = 1;
- } else if (argvars[2].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
+ for (; *p != NUL; p += len) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ len = utfc_ptr2len(p);
+ argv[1] = (typval_T){
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = xmemdupz(p, (size_t)len),
+ };
+
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ tv_clear(&argv[0]);
+ tv_clear(&argv[1]);
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ break;
+ }
+ }
+}
+
+/// Implementation of reduce() for Blob "argvars[0]" using the function "expr"
+/// starting with the optional initial value "argvars[2]" and return the result
+/// in "rettv".
+static void reduce_blob(typval_T *argvars, typval_T *expr, typval_T *rettv)
+{
+ const blob_T *const b = argvars[0].vval.v_blob;
+ const int called_emsg_start = called_emsg;
+
+ typval_T initial;
+ int i;
+ if (argvars[2].v_type == VAR_UNKNOWN) {
+ if (tv_blob_len(b) == 0) {
+ semsg(_(e_reduceempty), "Blob");
return;
- } else {
- initial = argvars[2];
- i = 0;
}
+ initial = (typval_T){
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = tv_blob_get(b, 0),
+ };
+ i = 1;
+ } else if (tv_check_for_number_arg(argvars, 2) == FAIL) {
+ return;
+ } else {
+ initial = argvars[2];
+ i = 0;
+ }
+
+ tv_copy(&initial, rettv);
+ for (; i < tv_blob_len(b); i++) {
+ typval_T argv[3];
+ argv[0] = *rettv;
+ argv[1] = (typval_T){
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_number = tv_blob_get(b, i),
+ };
- tv_copy(&initial, rettv);
- for (; i < tv_blob_len(b); i++) {
- argv[0] = *rettv;
- argv[1].v_type = VAR_NUMBER;
- argv[1].vval.v_number = tv_blob_get(b, i);
- if (call_func((char *)func_name, -1, rettv, 2, argv, &funcexe) == FAIL) {
- return;
- }
+ const int r = eval_expr_typval(expr, true, argv, 2, rettv);
+
+ if (r == FAIL || called_emsg != called_emsg_start) {
+ return;
}
}
}
+/// "reduce(list, { accumulator, element -> value } [, initial])" function
+/// "reduce(blob, { accumulator, element -> value } [, initial])" function
+/// "reduce(string, { accumulator, element -> value } [, initial])" function
+static void f_reduce(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (argvars[0].v_type != VAR_STRING
+ && argvars[0].v_type != VAR_LIST
+ && argvars[0].v_type != VAR_BLOB) {
+ emsg(_(e_string_list_or_blob_required));
+ return;
+ }
+
+ const char *func_name;
+ if (argvars[1].v_type == VAR_FUNC) {
+ func_name = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ func_name = partial_name(argvars[1].vval.v_partial);
+ } else {
+ func_name = tv_get_string(&argvars[1]);
+ }
+ if (func_name == NULL || *func_name == NUL) {
+ emsg(_(e_missing_function_argument));
+ return;
+ }
+
+ if (argvars[0].v_type == VAR_LIST) {
+ reduce_list(argvars, &argvars[1], rettv);
+ } else if (argvars[0].v_type == VAR_STRING) {
+ reduce_string(argvars, &argvars[1], rettv);
+ } else {
+ reduce_blob(argvars, &argvars[1], rettv);
+ }
+}
+
#define SP_NOMOVE 0x01 ///< don't move cursor
#define SP_REPEAT 0x02 ///< repeat to find outer pair
#define SP_RETCOUNT 0x04 ///< return matchcount
@@ -6161,8 +6497,8 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
{
bool save_p_ws = p_ws;
int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
+ linenr_T lnum_stop = 0;
+ int64_t time_limit = 0;
int options = SEARCH_KEEP;
bool use_skip = false;
@@ -6184,7 +6520,7 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
// Optional arguments: line number to stop searching, timeout and skip.
if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[2], NULL);
+ lnum_stop = (linenr_T)tv_get_number_chk(&argvars[2], NULL);
if (lnum_stop < 0) {
goto theend;
}
@@ -6214,14 +6550,14 @@ static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp)
pos_T pos = save_cursor = curwin->w_cursor;
pos_T firstpos = { 0 };
searchit_arg_T sia = {
- .sa_stop_lnum = (linenr_T)lnum_stop,
+ .sa_stop_lnum = lnum_stop,
.sa_tm = &tm,
};
int subpatnum;
// Repeat until {skip} returns false.
- for (;;) {
+ while (true) {
subpatnum
= searchit(curwin, curbuf, &pos, NULL, dir, (char *)pat, 1, options, RE_SEARCH, &sia);
// finding the first match again means there is no match where {skip}
@@ -6359,6 +6695,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
sctx_T save_current_sctx;
char *save_autocmd_fname, *save_autocmd_match;
+ bool save_autocmd_fname_full;
int save_autocmd_bufnr;
funccal_entry_T funccal_entry;
@@ -6368,6 +6705,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
save_current_sctx = current_sctx;
save_autocmd_fname = autocmd_fname;
save_autocmd_match = autocmd_match;
+ save_autocmd_fname_full = autocmd_fname_full;
save_autocmd_bufnr = autocmd_bufnr;
save_funccal(&funccal_entry);
@@ -6376,6 +6714,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((estack_T *)exestack.ga_data)[exestack.ga_len++] = provider_caller_scope.es_entry;
autocmd_fname = provider_caller_scope.autocmd_fname;
autocmd_match = provider_caller_scope.autocmd_match;
+ autocmd_fname_full = provider_caller_scope.autocmd_fname_full;
autocmd_bufnr = provider_caller_scope.autocmd_bufnr;
set_current_funccal((funccall_T *)(provider_caller_scope.funccalp));
}
@@ -6393,6 +6732,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
exestack.ga_len--;
autocmd_fname = save_autocmd_fname;
autocmd_match = save_autocmd_match;
+ autocmd_fname_full = save_autocmd_fname_full;
autocmd_bufnr = save_autocmd_bufnr;
restore_funccal();
}
@@ -6401,7 +6741,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const char *name = NULL;
Channel *chan = find_channel(chan_id);
if (chan) {
- name = rpc_client_name(chan);
+ name = get_client_info(chan, "name");
}
msg_ext_set_kind("rpc_error");
if (name) {
@@ -6416,6 +6756,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (!object_to_vim(result, rettv, &err)) {
+ assert(ERROR_SET(&err));
semsg(_("Error converting the call result: %s"), err.msg);
}
@@ -6481,7 +6822,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// The last item of argv must be NULL
argv[i] = NULL;
- Channel *chan = channel_job_start(argv, CALLBACK_READER_INIT,
+ Channel *chan = channel_job_start(argv, NULL, CALLBACK_READER_INIT,
CALLBACK_READER_INIT, CALLBACK_NONE,
false, true, false, false,
kChannelStdinPipe, NULL, 0, 0, NULL,
@@ -6552,7 +6893,9 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
c = -1;
} else {
- c = utf_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col]);
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+ c = utf_ptr2char(buf);
}
rettv->vval.v_number = c;
}
@@ -6566,21 +6909,22 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
ScreenGrid *grid;
screenchar_adjust(&grid, &row, &col);
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
if (row < 0 || row >= grid->rows || col < 0 || col >= grid->cols) {
- tv_list_alloc_ret(rettv, 0);
return;
}
- int pcc[MAX_MCO];
- int c = utfc_ptr2char((char *)grid->chars[grid->line_offset[row] + (size_t)col], pcc);
- int composing_len = 0;
- while (pcc[composing_len] != 0) {
- composing_len++;
- }
- tv_list_alloc_ret(rettv, composing_len + 1);
- tv_list_append_number(rettv->vval.v_list, c);
- for (int i = 0; i < composing_len; i++) {
- tv_list_append_number(rettv->vval.v_list, pcc[i]);
- }
+
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+
+ // schar values are already processed chars which are always NUL-terminated.
+ // A single [0] is expected when char is NUL.
+ size_t i = 0;
+ do {
+ int c = utf_ptr2char(buf + i);
+ tv_list_append_number(rettv->vval.v_list, c);
+ i += (size_t)utf_ptr2len(buf + i);
+ } while (buf[i] != NUL);
}
/// "screencol()" function
@@ -6613,7 +6957,9 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
return;
}
- rettv->vval.v_string = xstrdup((char *)grid->chars[grid->line_offset[row] + (size_t)col]);
+ char buf[MAX_SCHAR_SIZE + 1];
+ schar_get(buf, grid_getchar(grid, row, col, NULL));
+ rettv->vval.v_string = xstrdup(buf);
}
/// "search()" function
@@ -6652,8 +6998,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
bool save_p_ws = p_ws;
int flags = 0;
int retval = 0; // default: FAIL
- long lnum_stop = 0;
- long time_limit = 0;
+ linenr_T lnum_stop = 0;
+ int64_t time_limit = 0;
// Get the three pattern arguments: start, middle, end. Will result in an
// error if not a valid argument.
@@ -6695,7 +7041,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
skip = &argvars[4];
if (argvars[5].v_type != VAR_UNKNOWN) {
- lnum_stop = tv_get_number_chk(&argvars[5], NULL);
+ lnum_stop = (linenr_T)tv_get_number_chk(&argvars[5], NULL);
if (lnum_stop < 0) {
semsg(_(e_invarg2), tv_get_string(&argvars[5]));
goto theend;
@@ -6710,8 +7056,8 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
}
- retval = (int)do_searchpair(spat, mpat, epat, dir, skip,
- flags, match_pos, (linenr_T)lnum_stop, time_limit);
+ retval = do_searchpair(spat, mpat, epat, dir, skip,
+ flags, match_pos, lnum_stop, time_limit);
theend:
p_ws = save_p_ws;
@@ -6756,19 +7102,19 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
/// @param time_limit stop after this many msec
///
/// @returns 0 or -1 for no match,
-long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir,
- const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop,
- long time_limit)
+int do_searchpair(const char *spat, const char *mpat, const char *epat, int dir,
+ const typval_T *skip, int flags, pos_T *match_pos, linenr_T lnum_stop,
+ int64_t time_limit)
FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- long retval = 0;
+ int retval = 0;
int nest = 1;
bool use_skip = false;
int options = SEARCH_KEEP;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
// Set the time limit, if there is one.
proftime_T tm = profile_setlimit(time_limit);
@@ -6801,13 +7147,13 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
pos_T foundpos;
clearpos(&foundpos);
char *pat = pat3;
- for (;;) {
+ while (true) {
searchit_arg_T sia = {
.sa_stop_lnum = lnum_stop,
.sa_tm = &tm,
};
- int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1L,
+ int n = searchit(curwin, curbuf, &pos, NULL, dir, pat, 1,
options, RE_SEARCH, &sia);
if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) {
// didn't find it or found the first match again: FAIL
@@ -6894,14 +7240,14 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir
xfree(pat2);
xfree(pat3);
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, evaluating the {skip} expression changed the value.
// If it's still empty it was changed and restored, need to restore in
// the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
free_string_option(save_cpo);
}
@@ -7008,7 +7354,7 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Set the cursor or mark position.
-/// If 'charpos' is true, then use the column number as a character offset.
+/// If "charpos" is true, then use the column number as a character offset.
/// Otherwise use the column number as a byte offset.
static void set_position(typval_T *argvars, typval_T *rettv, bool charpos)
{
@@ -7056,8 +7402,7 @@ static void f_setcharpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
@@ -7068,8 +7413,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
char *const csearch = tv_dict_get_string(d, "char", false);
if (csearch != NULL) {
- int pcc[MAX_MCO];
- const int c = utfc_ptr2char(csearch, pcc);
+ int c = utf_ptr2char(csearch);
set_last_csearch(c, csearch, utfc_ptr2len(csearch));
}
@@ -7097,6 +7441,13 @@ static void f_setenv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char valbuf[NUMBUFLEN];
const char *name = tv_get_string_buf(&argvars[0], namebuf);
+ // setting an environment variable may be dangerous, e.g. you could
+ // setenv GCONV_PATH=/tmp and then have iconv() unexpectedly call
+ // a shell command using some shared library:
+ if (check_secure()) {
+ return;
+ }
+
if (argvars[1].v_type == VAR_SPECIAL
&& argvars[1].vval.v_special == kSpecialVarNull) {
vim_unsetenv_ext(name);
@@ -7143,7 +7494,7 @@ static void f_setpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
/// Translate a register type string to the yank type and block length
-static int get_yank_type(char **const pp, MotionType *const yank_type, long *const block_len)
+static int get_yank_type(char **const pp, MotionType *const yank_type, int *const block_len)
FUNC_ATTR_NONNULL_ALL
{
char *stropt = *pp;
@@ -7161,7 +7512,7 @@ static int get_yank_type(char **const pp, MotionType *const yank_type, long *con
*yank_type = kMTBlockWise;
if (ascii_isdigit(stropt[1])) {
stropt++;
- *block_len = getdigits_long(&stropt, false, 0) - 1;
+ *block_len = getdigits_int(&stropt, false, 0) - 1;
stropt--;
}
break;
@@ -7177,7 +7528,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
bool append = false;
- long block_len = -1;
+ int block_len = -1;
MotionType yank_type = kMTUnknown;
rettv->vval.v_number = 1; // FAIL is default.
@@ -7316,7 +7667,7 @@ free_lstval:
/// "settagstack()" function
static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- static char *e_invact2 = N_("E962: Invalid action: '%s'");
+ static const char *e_invact2 = N_("E962: Invalid action: '%s'");
char action = 'r';
rettv->vval.v_number = -1;
@@ -7328,8 +7679,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
// second argument: dict with items to set in the tag stack
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 1) == FAIL) {
return;
}
dict_T *d = argvars[1].vval.v_dict;
@@ -7340,8 +7690,10 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// third argument: action - 'a' for append and 'r' for replace.
// default is to replace the stack.
if (argvars[2].v_type == VAR_UNKNOWN) {
- action = 'r';
- } else if (argvars[2].v_type == VAR_STRING) {
+ // action = 'r';
+ } else if (tv_check_for_string_arg(argvars, 2) == FAIL) {
+ return;
+ } else {
const char *actstr;
actstr = tv_get_string_chk(&argvars[2]);
if (actstr == NULL) {
@@ -7354,9 +7706,6 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invact2), actstr);
return;
}
- } else {
- emsg(_(e_stringreq));
- return;
}
if (set_tagstack(wp, d, action) == OK) {
@@ -7391,11 +7740,11 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = 0;
if (argvars[0].v_type != VAR_UNKNOWN) {
- long col = (long)tv_get_number_chk(argvars, NULL);
+ colnr_T col = (colnr_T)tv_get_number_chk(argvars, NULL);
if (col < 0) {
return; // type error; errmsg already given
}
- rettv->vval.v_number = get_sw_value_col(curbuf, (colnr_T)col);
+ rettv->vval.v_number = get_sw_value_col(curbuf, col);
return;
}
rettv->vval.v_number = get_sw_value(curbuf);
@@ -7523,7 +7872,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -7567,10 +7916,11 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
tv_list_alloc_ret(rettv, 2);
tv_list_append_string(rettv->vval.v_list, word, (ssize_t)len);
tv_list_append_string(rettv->vval.v_list,
- (attr == HLF_SPB ? "bad" :
- attr == HLF_SPR ? "rare" :
- attr == HLF_SPL ? "local" :
- attr == HLF_SPC ? "caps" : NULL), -1);
+ (attr == HLF_SPB
+ ? "bad" : (attr == HLF_SPR
+ ? "rare" : (attr == HLF_SPL
+ ? "local" : (attr == HLF_SPC
+ ? "caps" : NULL)))), -1);
}
/// "spellsuggest()" function
@@ -7580,7 +7930,7 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -7629,7 +7979,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
const char *str = tv_get_string(&argvars[0]);
const char *pat = NULL;
@@ -7654,7 +8004,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
regmatch_T regmatch = {
- .regprog = vim_regcomp((char *)pat, RE_MAGIC + RE_STRING),
+ .regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING),
.startp = { NULL },
.endp = { NULL },
.rm_ic = false,
@@ -7665,18 +8015,18 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (*str == NUL) {
match = false; // Empty item at the end.
} else {
- match = vim_regexec_nl(&regmatch, (char *)str, col);
+ match = vim_regexec_nl(&regmatch, str, col);
}
const char *end;
if (match) {
- end = (const char *)regmatch.startp[0];
+ end = regmatch.startp[0];
} else {
end = str + strlen(str);
}
if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0
&& *str != NUL
&& match
- && end < (const char *)regmatch.endp[0])) {
+ && end < regmatch.endp[0])) {
tv_list_append_string(rettv->vval.v_list, str, end - str);
}
if (!match) {
@@ -7689,7 +8039,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Don't get stuck at the same match.
col = utfc_ptr2len(regmatch.endp[0]);
}
- str = (const char *)regmatch.endp[0];
+ str = regmatch.endp[0];
}
vim_regfree(regmatch.regprog);
@@ -7747,60 +8097,6 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_FLOAT;
}
-/// "str2list()" function
-static void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_list_alloc_ret(rettv, kListLenUnknown);
- const char *p = tv_get_string(&argvars[0]);
-
- for (; *p != NUL; p += utf_ptr2len(p)) {
- tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
- }
-}
-
-/// "str2nr()" function
-static void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int base = 10;
- int what = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- base = (int)tv_get_number(&argvars[1]);
- if (base != 2 && base != 8 && base != 10 && base != 16) {
- emsg(_(e_invarg));
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
- what |= STR2NR_QUOTE;
- }
- }
-
- char *p = skipwhite(tv_get_string(&argvars[0]));
- bool isneg = (*p == '-');
- if (*p == '+' || *p == '-') {
- p = skipwhite(p + 1);
- }
- switch (base) {
- case 2:
- what |= STR2NR_BIN | STR2NR_FORCE;
- break;
- case 8:
- what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
- break;
- case 16:
- what |= STR2NR_HEX | STR2NR_FORCE;
- break;
- }
- varnumber_T n;
- vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false);
- // Text after the number is silently ignored.
- if (isneg) {
- rettv->vval.v_number = -n;
- } else {
- rettv->vval.v_number = n;
- }
-}
-
/// "strftime({format}[, {time}])" function
static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -7851,235 +8147,6 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(enc);
}
-/// "strgetchar()" function
-static void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- const char *const str = tv_get_string_chk(&argvars[0]);
- if (str == NULL) {
- return;
- }
- bool error = false;
- varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- return;
- }
-
- const size_t len = strlen(str);
- size_t byteidx = 0;
-
- while (charidx >= 0 && byteidx < len) {
- if (charidx == 0) {
- rettv->vval.v_number = utf_ptr2char(str + byteidx);
- break;
- }
- charidx--;
- byteidx += (size_t)utf_ptr2len(str + byteidx);
- }
-}
-
-/// "stridx()" function
-static void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
- const char *const haystack_start = haystack;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
-
- const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
- &error);
- if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
- return;
- }
- if (start_idx >= 0) {
- haystack += start_idx;
- }
- }
-
- const char *pos = strstr(haystack, needle);
- if (pos != NULL) {
- rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
- }
-}
-
-/// "string()" function
-static void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
-}
-
-/// "strlen()" function
-static void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
-}
-
-static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
-{
- const char *s = tv_get_string(&argvars[0]);
- varnumber_T len = 0;
- int (*func_mb_ptr2char_adv)(const char **pp);
-
- func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
- while (*s != NUL) {
- func_mb_ptr2char_adv(&s);
- len++;
- }
- rettv->vval.v_number = len;
-}
-
-/// "strcharlen()" function
-static void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- strchar_common(argvars, rettv, true);
-}
-
-/// "strchars()" function
-static void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int skipcc = false;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- skipcc = (int)tv_get_bool(&argvars[1]);
- }
- if (skipcc < 0 || skipcc > 1) {
- semsg(_(e_using_number_as_bool_nr), skipcc);
- } else {
- strchar_common(argvars, rettv, skipcc);
- }
-}
-
-/// "strdisplaywidth()" function
-static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
- int col = 0;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- col = (int)tv_get_number(&argvars[1]);
- }
-
- rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
-}
-
-/// "strwidth()" function
-static void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const s = tv_get_string(&argvars[0]);
-
- rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
-}
-
-/// "strcharpart()" function
-static void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- int nbyte = 0;
- bool error = false;
- varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
- if (!error) {
- if (nchar > 0) {
- while (nchar > 0 && (size_t)nbyte < slen) {
- nbyte += utf_ptr2len(p + nbyte);
- nchar--;
- }
- } else {
- nbyte = (int)nchar;
- }
- }
- int len = 0;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- int charlen = (int)tv_get_number(&argvars[2]);
- while (charlen > 0 && nbyte + len < (int)slen) {
- int off = nbyte + len;
-
- if (off < 0) {
- len += 1;
- } else {
- len += utf_ptr2len(p + off);
- }
- charlen--;
- }
- } else {
- len = (int)slen - nbyte; // default: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (nbyte < 0) {
- len += nbyte;
- nbyte = 0;
- } else if ((size_t)nbyte > slen) {
- nbyte = (int)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (nbyte + len > (int)slen) {
- len = (int)slen - nbyte;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrndup(p + nbyte, (size_t)len);
-}
-
-/// "strpart()" function
-static void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- bool error = false;
-
- const char *const p = tv_get_string(&argvars[0]);
- const size_t slen = strlen(p);
-
- varnumber_T n = tv_get_number_chk(&argvars[1], &error);
- varnumber_T len;
- if (error) {
- len = 0;
- } else if (argvars[2].v_type != VAR_UNKNOWN) {
- len = tv_get_number(&argvars[2]);
- } else {
- len = (varnumber_T)slen - n; // Default len: all bytes that are available.
- }
-
- // Only return the overlap between the specified part and the actual
- // string.
- if (n < 0) {
- len += n;
- n = 0;
- } else if (n > (varnumber_T)slen) {
- n = (varnumber_T)slen;
- }
- if (len < 0) {
- len = 0;
- } else if (n + len > (varnumber_T)slen) {
- len = (varnumber_T)slen - n;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
- int off;
-
- // length in characters
- for (off = (int)n; off < (int)slen && len > 0; len--) {
- off += utfc_ptr2len(p + off);
- }
- len = off - n;
- }
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
-}
-
/// "strptime({format}, {timestring})" function
static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8112,56 +8179,6 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(enc);
}
-/// "strridx()" function
-static void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- const char *const needle = tv_get_string_chk(&argvars[1]);
- const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
-
- rettv->vval.v_number = -1;
- if (needle == NULL || haystack == NULL) {
- return; // Type error; errmsg already given.
- }
-
- const size_t haystack_len = strlen(haystack);
- ptrdiff_t end_idx;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // Third argument: upper limit for index.
- end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
- if (end_idx < 0) {
- return; // Can never find a match.
- }
- } else {
- end_idx = (ptrdiff_t)haystack_len;
- }
-
- const char *lastmatch = NULL;
- if (*needle == NUL) {
- // Empty string matches past the end.
- lastmatch = haystack + end_idx;
- } else {
- for (const char *rest = haystack; *rest != NUL; rest++) {
- rest = strstr(rest, needle);
- if (rest == NULL || rest > haystack + end_idx) {
- break;
- }
- lastmatch = rest;
- }
- }
-
- if (lastmatch != NULL) {
- rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
- }
-}
-
-/// "strtrans()" function
-static void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
-}
-
/// "submatch()" function
static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8172,7 +8189,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (no < 0 || no >= NSUBEXP) {
- semsg(_("E935: invalid submatch number: %d"), no);
+ semsg(_(e_invalid_submatch_number_nr), no);
return;
}
int retList = 0;
@@ -8222,11 +8239,18 @@ static void f_substitute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+/// "swapfilelist()" function
+static void f_swapfilelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ recover_names(NULL, false, rettv->vval.v_list, 0, NULL);
+}
+
/// "swapinfo(swap_filename)" function
static void f_swapinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
tv_dict_alloc_ret(rettv);
- get_b0_dict(tv_get_string(argvars), rettv->vval.v_dict);
+ swapfile_dict(tv_get_string(argvars), rettv->vval.v_dict);
}
/// "swapname(expr)" function
@@ -8346,7 +8370,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (char *)(p == NULL ? p : xstrdup(p));
+ rettv->vval.v_string = p == NULL ? NULL : xstrdup(p);
}
/// "synIDtrans(id)" function
@@ -8389,8 +8413,8 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
cchar = syn_get_sub_char();
if (cchar == NUL && curwin->w_p_cole == 1) {
cchar = (curwin->w_p_lcs_chars.conceal == NUL)
- ? ' '
- : curwin->w_p_lcs_chars.conceal;
+ ? ' '
+ : curwin->w_p_lcs_chars.conceal;
}
if (cchar != NUL) {
utf_char2bytes(cchar, str);
@@ -8429,7 +8453,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// f_system - the VimL system() function
+/// f_system - the Vimscript system() function
static void f_system(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
get_system_output_as_rettv(argvars, rettv, false);
@@ -8510,7 +8534,10 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (check_secure()) {
return;
}
-
+ if (text_locked()) {
+ text_locked_msg();
+ return;
+ }
if (curbuf->b_changed) {
emsg(_("Can only call this function in an unmodified buffer"));
return;
@@ -8577,7 +8604,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const bool detach = false;
ChannelStdinMode stdin_mode = kChannelStdinPipe;
uint16_t term_width = (uint16_t)MAX(0, curwin->w_width_inner - win_col_off(curwin));
- Channel *chan = channel_job_start(argv, on_stdout, on_stderr, on_exit,
+ Channel *chan = channel_job_start(argv, NULL, on_stdout, on_stderr, on_exit,
pty, rpc, overlapped, detach, stdin_mode,
cwd, term_width, (uint16_t)curwin->w_height_inner,
env, &rettv->vval.v_number);
@@ -8623,21 +8650,24 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
INTEGER_OBJ(pid), false, false, &err);
api_clear_error(&err);
+ channel_incref(chan);
channel_terminal_open(curbuf, chan);
channel_create_event(chan, NULL);
+ channel_decref(chan);
}
/// "timer_info([timer])" function
static void f_timer_info(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ if (tv_check_for_opt_number_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
if (argvars[0].v_type != VAR_UNKNOWN) {
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
- return;
- }
- tv_list_alloc_ret(rettv, 1);
timer_T *timer = find_timer_by_nr(tv_get_number(&argvars[0]));
- if (timer != NULL && !timer->stopped) {
+ if (timer != NULL && (!timer->stopped || timer->refcount > 1)) {
add_timer_info(rettv, timer);
}
} else {
@@ -8677,11 +8707,10 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[2].v_type != VAR_UNKNOWN) {
- dict_T *dict = argvars[2].vval.v_dict;
- if (argvars[2].v_type != VAR_DICT || dict == NULL) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
+ dict_T *dict = argvars[2].vval.v_dict;
dictitem_T *const di = tv_dict_find(dict, S_LEN("repeat"));
if (di != NULL) {
repeat = (int)tv_get_number(&di->di_tv);
@@ -8701,8 +8730,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "timer_stop(timerid)" function
static void f_timer_stop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_NUMBER) {
- emsg(_(e_number_exp));
+ if (tv_check_for_number_arg(argvars, 0) == FAIL) {
return;
}
@@ -8719,186 +8747,6 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, EvalFuncData fp
timer_stop_all();
}
-/// "tolower(string)" function
-static void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
-}
-
-/// "toupper(string)" function
-static void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
-}
-
-/// "tr(string, fromstr, tostr)" function
-static void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf[NUMBUFLEN];
- char buf2[NUMBUFLEN];
-
- const char *in_str = tv_get_string(&argvars[0]);
- const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
- const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
-
- // Default return value: empty string.
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (fromstr == NULL || tostr == NULL) {
- return; // Type error; errmsg already given.
- }
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
-
- // fromstr and tostr have to contain the same number of chars.
- bool first = true;
- while (*in_str != NUL) {
- const char *cpstr = in_str;
- const int inlen = utfc_ptr2len(in_str);
- int cplen = inlen;
- int idx = 0;
- int fromlen;
- for (const char *p = fromstr; *p != NUL; p += fromlen) {
- fromlen = utfc_ptr2len(p);
- if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
- int tolen;
- for (p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- if (idx-- == 0) {
- cplen = tolen;
- cpstr = (char *)p;
- break;
- }
- }
- if (*p == NUL) { // tostr is shorter than fromstr.
- goto error;
- }
- break;
- }
- idx++;
- }
-
- if (first && cpstr == in_str) {
- // Check that fromstr and tostr have the same number of
- // (multi-byte) characters. Done only once when a character
- // of in_str doesn't appear in fromstr.
- first = false;
- int tolen;
- for (const char *p = tostr; *p != NUL; p += tolen) {
- tolen = utfc_ptr2len(p);
- idx--;
- }
- if (idx != 0) {
- goto error;
- }
- }
-
- ga_grow(&ga, cplen);
- memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
- ga.ga_len += cplen;
-
- in_str += inlen;
- }
-
- // add a terminating NUL
- ga_append(&ga, NUL);
-
- rettv->vval.v_string = ga.ga_data;
- return;
-error:
- semsg(_(e_invarg2), fromstr);
- ga_clear(&ga);
-}
-
-/// "trim({expr})" function
-static void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
- const char *mask = NULL;
- const char *prev;
- const char *p;
- int dir = 0;
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
- if (head == NULL) {
- return;
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_STRING) {
- semsg(_(e_invarg2), tv_get_string(&argvars[1]));
- return;
- }
-
- if (argvars[1].v_type == VAR_STRING) {
- mask = tv_get_string_buf_chk(&argvars[1], buf2);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- bool error = false;
- // leading or trailing characters to trim
- dir = (int)tv_get_number_chk(&argvars[2], &error);
- if (error) {
- return;
- }
- if (dir < 0 || dir > 2) {
- semsg(_(e_invarg2), tv_get_string(&argvars[2]));
- return;
- }
- }
- }
-
- int c1;
- if (dir == 0 || dir == 1) {
- // Trim leading characters
- while (*head != NUL) {
- c1 = utf_ptr2char((char *)head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char((char *)p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- MB_PTR_ADV(head);
- }
- }
-
- const char *tail = head + strlen(head);
- if (dir == 0 || dir == 2) {
- // Trim trailing characters
- for (; tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = utf_ptr2char((char *)prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == utf_ptr2char((char *)p)) {
- break;
- }
- }
- if (*p == NUL) {
- break;
- }
- }
- }
- }
- rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
-}
-
/// "type(expr)" function
static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8931,49 +8779,31 @@ static void f_type(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = n;
}
-/// "undofile(name)" function
-static void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+/// "virtcol({expr}, [, {list} [, {winid}]])" function
+static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- rettv->v_type = VAR_STRING;
- const char *const fname = tv_get_string(&argvars[0]);
-
- if (*fname == NUL) {
- // If there is no file name there will be no undo file.
- rettv->vval.v_string = NULL;
- } else {
- char *ffname = FullName_save(fname, true);
+ colnr_T vcol_start = 0;
+ colnr_T vcol_end = 0;
+ switchwin_T switchwin;
+ bool winchanged = false;
- if (ffname != NULL) {
- rettv->vval.v_string = u_get_undo_file_name(ffname, false);
+ if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) {
+ // use the window specified in the third argument
+ tabpage_T *tp;
+ win_T *wp = win_id2wp_tp((int)tv_get_number(&argvars[2]), &tp);
+ if (wp == NULL || tp == NULL) {
+ goto theend;
}
- xfree(ffname);
- }
-}
-/// "undotree()" function
-static void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_dict_alloc_ret(rettv);
-
- dict_T *dict = rettv->vval.v_dict;
-
- tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced);
- tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last);
- tv_dict_add_nr(dict, S_LEN("save_last"),
- (varnumber_T)curbuf->b_u_save_nr_last);
- tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)curbuf->b_u_seq_cur);
- tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur);
- tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur);
+ if (switch_win_noblock(&switchwin, wp, tp, true) != OK) {
+ goto theend;
+ }
- tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead));
-}
+ check_cursor();
+ winchanged = true;
+ }
-/// "virtcol(string)" function
-static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- colnr_T vcol = 0;
int fnum = curbuf->b_fnum;
-
pos_T *fp = var2fpos(&argvars[0], false, &fnum, false);
if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count
&& fnum == curbuf->b_fnum) {
@@ -8986,11 +8816,23 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
fp->col = (colnr_T)len;
}
}
- getvvcol(curwin, fp, NULL, NULL, &vcol);
- vcol++;
+ getvvcol(curwin, fp, &vcol_start, NULL, &vcol_end);
+ vcol_start++;
+ vcol_end++;
}
- rettv->vval.v_number = vcol;
+theend:
+ if (argvars[1].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[1])) {
+ tv_list_alloc_ret(rettv, 2);
+ tv_list_append_number(rettv->vval.v_list, vcol_start);
+ tv_list_append_number(rettv->vval.v_list, vcol_end);
+ } else {
+ rettv->vval.v_number = vcol_end;
+ }
+
+ if (winchanged) {
+ restore_win_noblock(&switchwin, true);
+ }
}
/// "visualmode()" function
@@ -9054,6 +8896,7 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
bool binary = false;
bool append = false;
+ bool defer = false;
bool do_fsync = !!p_fs;
bool mkdir_p = false;
if (argvars[2].v_type != VAR_UNKNOWN) {
@@ -9067,6 +8910,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
binary = true; break;
case 'a':
append = true; break;
+ case 'D':
+ defer = true; break;
case 's':
do_fsync = true; break;
case 'S':
@@ -9086,6 +8931,11 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (fname == NULL) {
return;
}
+
+ if (defer && !can_add_defer()) {
+ return;
+ }
+
FileDescriptor fp;
int error;
if (*fname == NUL) {
@@ -9094,9 +8944,17 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
((append ? kFileAppend : kFileTruncate)
| (mkdir_p ? kFileMkDir : kFileCreate)
| kFileCreate), 0666)) != 0) {
- semsg(_("E482: Can't open file %s for writing: %s"),
- fname, os_strerror(error));
+ semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
} else {
+ if (defer) {
+ typval_T tv = {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = FullName_save(fname, false),
+ };
+ add_defer("delete", 1, &tv);
+ }
+
bool write_ok;
if (argvars[0].v_type == VAR_BLOB) {
write_ok = write_blob(&fp, argvars[0].vval.v_blob);
diff --git a/src/nvim/eval/funcs.h b/src/nvim/eval/funcs.h
index 1ae031a952..0c345dacb4 100644
--- a/src/nvim/eval/funcs.h
+++ b/src/nvim/eval/funcs.h
@@ -1,23 +1,24 @@
-#ifndef NVIM_EVAL_FUNCS_H
-#define NVIM_EVAL_FUNCS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/api/private/dispatch.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
-/// Prototype of C function that implements VimL function
+/// Prototype of C function that implements Vimscript function
typedef void (*VimLFunc)(typval_T *args, typval_T *rvar, EvalFuncData data);
/// Special flags for base_arg @see EvalFuncDef
-#define BASE_NONE 0 ///< Not a method (no base argument).
-#define BASE_LAST UINT8_MAX ///< Use the last argument as the method base.
+enum {
+ BASE_NONE = 0, ///< Not a method (no base argument).
+ BASE_LAST = UINT8_MAX, ///< Use the last argument as the method base.
+};
-/// Structure holding VimL function definition
+/// Structure holding Vimscript function definition
typedef struct {
char *name; ///< Name of the function.
uint8_t min_argc; ///< Minimal number of arguments.
@@ -31,4 +32,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/funcs.h.generated.h"
#endif
-#endif // NVIM_EVAL_FUNCS_H
diff --git a/src/nvim/eval/gc.c b/src/nvim/eval/gc.c
index 6a54c4ddc1..bcebd87f71 100644
--- a/src/nvim/eval/gc.c
+++ b/src/nvim/eval/gc.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include "nvim/eval/gc.h"
diff --git a/src/nvim/eval/gc.h b/src/nvim/eval/gc.h
index 3185750c3b..36149ec060 100644
--- a/src/nvim/eval/gc.h
+++ b/src/nvim/eval/gc.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_EVAL_GC_H
-#define NVIM_EVAL_GC_H
+#pragma once
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
extern dict_T *gc_first_dict;
extern list_T *gc_first_list;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "eval/gc.h.generated.h"
+# include "eval/gc.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_EVAL_GC_H
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index c298064d86..069cdced34 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1,16 +1,14 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "lauxlib.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
@@ -21,33 +19,82 @@
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/mbyte_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/input.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+/// struct storing information about current sort
+typedef struct {
+ int item_compare_ic;
+ bool item_compare_lc;
+ bool item_compare_numeric;
+ bool item_compare_numbers;
+ bool item_compare_float;
+ const char *item_compare_func;
+ partial_T *item_compare_partial;
+ dict_T *item_compare_selfdict;
+ bool item_compare_func_err;
+} sortinfo_T;
+
+/// Structure representing one list item, used for sort array.
+typedef struct {
+ listitem_T *item; ///< Sorted list item.
+ int idx; ///< Sorted list item index.
+} ListSortItem;
+
+typedef int (*ListSorter)(const void *, const void *);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
#endif
-static char e_string_required_for_argument_nr[]
+static const char e_variable_nested_too_deep_for_unlock[]
+ = N_("E743: Variable nested too deep for (un)lock");
+static const char e_using_invalid_value_as_string[]
+ = N_("E908: Using an invalid value as a String");
+static const char e_string_required_for_argument_nr[]
= N_("E1174: String required for argument %d");
-static char e_non_empty_string_required_for_argument_nr[]
+static const char e_non_empty_string_required_for_argument_nr[]
= N_("E1175: Non-empty string required for argument %d");
-static char e_number_required_for_argument_nr[]
+static const char e_dict_required_for_argument_nr[]
+ = N_("E1206: Dictionary required for argument %d");
+static const char e_number_required_for_argument_nr[]
= N_("E1210: Number required for argument %d");
-static char e_string_or_list_required_for_argument_nr[]
+static const char e_list_required_for_argument_nr[]
+ = N_("E1211: List required for argument %d");
+static const char e_bool_required_for_argument_nr[]
+ = N_("E1212: Bool required for argument %d");
+static const char e_float_or_number_required_for_argument_nr[]
+ = N_("E1219: Float or Number required for argument %d");
+static const char e_string_or_number_required_for_argument_nr[]
+ = N_("E1220: String or Number required for argument %d");
+static const char e_string_or_list_required_for_argument_nr[]
= N_("E1222: String or List required for argument %d");
+static const char e_list_or_blob_required_for_argument_nr[]
+ = N_("E1226: List or Blob required for argument %d");
+static const char e_blob_required_for_argument_nr[]
+ = N_("E1238: Blob required for argument %d");
+static const char e_invalid_value_for_blob_nr[]
+ = N_("E1239: Invalid value for blob: %d");
+static const char e_string_list_or_blob_required_for_argument_nr[]
+ = N_("E1252: String, List or Blob required for argument %d");
+static const char e_string_or_function_required_for_argument_nr[]
+ = N_("E1256: String or function required for argument %d");
+static const char e_non_null_dict_required_for_argument_nr[]
+ = N_("E1297: Non-NULL Dictionary required for argument %d");
bool tv_in_free_unref_items = false;
@@ -58,70 +105,6 @@ bool tv_in_free_unref_items = false;
const char *const tv_empty_string = "";
//{{{1 Lists
-//{{{2 List log
-#ifdef LOG_LIST_ACTIONS
-ListLog *list_log_first = NULL;
-ListLog *list_log_last = NULL;
-
-/// Write list log to the given file
-///
-/// @param[in] fname File to write log to. Will be appended to if already
-/// present.
-void list_write_log(const char *const fname)
- FUNC_ATTR_NONNULL_ALL
-{
- FileDescriptor fp;
- const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600);
- if (fo_ret != 0) {
- semsg(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret));
- return;
- }
- for (ListLog *chunk = list_log_first; chunk != NULL;) {
- for (size_t i = 0; i < chunk->size; i++) {
- char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2];
- // act : hex " c:" len "[]" "\n\0"
- const ListLogEntry entry = chunk->entries[i];
- const size_t snp_len = (size_t)snprintf(buf, sizeof(buf),
- "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR
- "\n",
- entry.action, entry.l, entry.len, entry.li1,
- entry.li2);
- assert(snp_len + 1 == sizeof(buf));
- const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len);
- if (fw_ret != (ptrdiff_t)snp_len) {
- assert(fw_ret < 0);
- if (i) {
- memmove(chunk->entries, chunk->entries + i,
- sizeof(chunk->entries[0]) * (chunk->size - i));
- chunk->size -= i;
- }
- semsg(_("E5143: Failed to write to file %s: %s"),
- fname, os_strerror((int)fw_ret));
- return;
- }
- }
- list_log_first = chunk->next;
- xfree(chunk);
- chunk = list_log_first;
- }
- const int fc_ret = file_close(&fp, true);
- if (fc_ret != 0) {
- semsg(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret));
- }
-}
-
-# ifdef EXITFREE
-/// Free list log
-void list_free_log(void)
-{
- for (ListLog *chunk = list_log_first; chunk != NULL;) {
- list_log_first = chunk->next;
- xfree(chunk);
- chunk = list_log_first;
- }
-}
-# endif
-#endif
//{{{2 List item
/// Allocate a list item
@@ -210,7 +193,7 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item)
/// Caller should take care of the reference count.
///
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. Currently does nothing.
/// @see ListLenSpecials.
@@ -228,7 +211,6 @@ list_T *tv_list_alloc(const ptrdiff_t len)
list->lv_used_prev = NULL;
list->lv_used_next = gc_first_list;
gc_first_list = list;
- list_log(list, NULL, (void *)(uintptr_t)len, "alloc");
list->lua_table_ref = LUA_NOREF;
return list;
}
@@ -259,8 +241,6 @@ void tv_list_init_static10(staticList10_T *const sl)
li->li_prev = li - 1;
li->li_next = li + 1;
}
- list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1],
- "s10init");
#undef SL_SIZE
}
@@ -272,7 +252,6 @@ void tv_list_init_static(list_T *const l)
{
CLEAR_POINTER(l);
l->lv_refcount = DO_NOT_FREE_CNT;
- list_log(l, NULL, NULL, "sinit");
}
/// Free items contained in a list
@@ -281,7 +260,6 @@ void tv_list_init_static(list_T *const l)
void tv_list_free_contents(list_T *const l)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, NULL, NULL, "freecont");
for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) {
// Remove the item before deleting it.
l->lv_first = item->li_next;
@@ -311,7 +289,6 @@ void tv_list_free_list(list_T *const l)
if (l->lv_used_next != NULL) {
l->lv_used_next->lv_used_prev = l->lv_used_prev;
}
- list_log(l, NULL, NULL, "freelist");
NLUA_CLEAR_REF(l->lua_table_ref);
xfree(l);
@@ -358,7 +335,6 @@ void tv_list_unref(list_T *const l)
void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *const item2)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "drop");
// Notify watchers.
for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) {
l->lv_len--;
@@ -376,14 +352,12 @@ void tv_list_drop_items(list_T *const l, listitem_T *const item, listitem_T *con
item->li_prev->li_next = item2->li_next;
}
l->lv_idx_item = NULL;
- list_log(l, l->lv_first, l->lv_last, "afterdrop");
}
/// Like tv_list_drop_items, but also frees all removed items
void tv_list_remove_items(list_T *const l, listitem_T *const item, listitem_T *const item2)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "remove");
tv_list_drop_items(l, item, item2);
for (listitem_T *li = item;;) {
tv_clear(TV_LIST_ITEM_TV(li));
@@ -407,7 +381,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con
list_T *const tgt_l, const int cnt)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, item2, "move");
tv_list_drop_items(l, item, item2);
item->li_prev = tgt_l->lv_last;
item2->li_next = NULL;
@@ -418,7 +391,6 @@ void tv_list_move_items(list_T *const l, listitem_T *const item, listitem_T *con
}
tgt_l->lv_last = item2;
tgt_l->lv_len += cnt;
- list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt");
}
/// Insert list item
@@ -446,11 +418,10 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, listitem_T *const ite
}
item->li_prev = ni;
l->lv_len++;
- list_log(l, ni, item, "insert");
}
}
-/// Insert VimL value into a list
+/// Insert Vimscript value into a list
///
/// @param[out] l List to insert to.
/// @param[in,out] tv Value to insert. Is copied (@see tv_copy()) to an
@@ -472,7 +443,6 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, listitem_T *const it
void tv_list_append(list_T *const l, listitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- list_log(l, item, NULL, "append");
if (l->lv_last == NULL) {
// empty list
l->lv_first = item;
@@ -487,7 +457,7 @@ void tv_list_append(list_T *const l, listitem_T *const item)
item->li_next = NULL;
}
-/// Append VimL value to the end of list
+/// Append Vimscript value to the end of list
///
/// @param[out] l List to append to.
/// @param[in,out] tv Value to append. Is copied (@see tv_copy()) to an
@@ -647,55 +617,166 @@ tv_list_copy_error:
return NULL;
}
-/// Flatten "list" in place to depth "maxdepth".
+/// Get the list item in "l" with index "n1". "n1" is adjusted if needed.
+/// Return NULL if there is no such item.
+listitem_T *tv_list_check_range_index_one(list_T *const l, int *const n1, const bool quiet)
+{
+ listitem_T *li = tv_list_find_index(l, n1);
+ if (li == NULL) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n1));
+ }
+ return NULL;
+ }
+ return li;
+}
+
+/// Check that "n2" can be used as the second index in a range of list "l".
+/// If "n1" or "n2" is negative it is changed to the positive index.
+/// "li1" is the item for item "n1".
+/// Return OK or FAIL.
+int tv_list_check_range_index_two(list_T *const l, int *const n1, const listitem_T *const li1,
+ int *const n2, const bool quiet)
+{
+ if (*n2 < 0) {
+ listitem_T *ni = tv_list_find(l, *n2);
+ if (ni == NULL) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
+ }
+ return FAIL;
+ }
+ *n2 = tv_list_idx_of_item(l, ni);
+ }
+
+ // Check that n2 isn't before n1.
+ if (*n1 < 0) {
+ *n1 = tv_list_idx_of_item(l, li1);
+ }
+ if (*n2 < *n1) {
+ if (!quiet) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)(*n2));
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Assign values from list "src" into a range of "dest".
+/// "idx1_arg" is the index of the first item in "dest" to be replaced.
+/// "idx2" is the index of last item to be replaced, but when "empty_idx2" is
+/// true then replace all items after "idx1".
+/// "op" is the operator, normally "=" but can be "+=" and the like.
+/// "varname" is used for error messages.
+/// Returns OK or FAIL.
+int tv_list_assign_range(list_T *const dest, list_T *const src, const int idx1_arg, const int idx2,
+ const bool empty_idx2, const char *const op, const char *const varname)
+{
+ int idx1 = idx1_arg;
+ listitem_T *const first_li = tv_list_find_index(dest, &idx1);
+ listitem_T *src_li;
+
+ // Check whether any of the list items is locked before making any changes.
+ int idx = idx1;
+ listitem_T *dest_li = first_li;
+ for (src_li = tv_list_first(src); src_li != NULL && dest_li != NULL;) {
+ if (value_check_lock(TV_LIST_ITEM_TV(dest_li)->v_lock, varname, TV_CSTRING)) {
+ return FAIL;
+ }
+ src_li = TV_LIST_ITEM_NEXT(src, src_li);
+ if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
+ break;
+ }
+ dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
+ idx++;
+ }
+
+ // Assign the List values to the list items.
+ idx = idx1;
+ dest_li = first_li;
+ for (src_li = tv_list_first(src); src_li != NULL;) {
+ assert(dest_li != NULL);
+ if (op != NULL && *op != '=') {
+ eexe_mod_op(TV_LIST_ITEM_TV(dest_li), TV_LIST_ITEM_TV(src_li), op);
+ } else {
+ tv_clear(TV_LIST_ITEM_TV(dest_li));
+ tv_copy(TV_LIST_ITEM_TV(src_li), TV_LIST_ITEM_TV(dest_li));
+ }
+ src_li = TV_LIST_ITEM_NEXT(src, src_li);
+ if (src_li == NULL || (!empty_idx2 && idx2 == idx)) {
+ break;
+ }
+ if (TV_LIST_ITEM_NEXT(dest, dest_li) == NULL) {
+ // Need to add an empty item.
+ tv_list_append_number(dest, 0);
+ // "dest_li" may have become invalid after append, don’t use it.
+ dest_li = tv_list_last(dest); // Valid again.
+ } else {
+ dest_li = TV_LIST_ITEM_NEXT(dest, dest_li);
+ }
+ idx++;
+ }
+ if (src_li != NULL) {
+ emsg(_("E710: List value has more items than target"));
+ return FAIL;
+ }
+ if (empty_idx2
+ ? (dest_li != NULL && TV_LIST_ITEM_NEXT(dest, dest_li) != NULL)
+ : idx != idx2) {
+ emsg(_("E711: List value has not enough items"));
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Flatten up to "maxitems" in "list", starting at "first" to depth "maxdepth".
+/// When "first" is NULL use the first item.
/// Does nothing if "maxdepth" is 0.
///
/// @param[in,out] list List to flatten
/// @param[in] maxdepth Maximum depth that will be flattened
///
/// @return OK or FAIL
-int tv_list_flatten(list_T *list, long maxdepth)
- FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
+void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t maxdepth)
+ FUNC_ATTR_NONNULL_ARG(1)
{
listitem_T *item;
- listitem_T *to_free;
- int n;
+ int done = 0;
if (maxdepth == 0) {
- return OK;
+ return;
}
- n = 0;
- item = list->lv_first;
- while (item != NULL) {
+ if (first == NULL) {
+ item = list->lv_first;
+ } else {
+ item = first;
+ }
+
+ while (item != NULL && done < maxitems) {
+ listitem_T *next = item->li_next;
+
fast_breakcheck();
if (got_int) {
- return FAIL;
+ return;
}
if (item->li_tv.v_type == VAR_LIST) {
- listitem_T *next = item->li_next;
+ list_T *itemlist = item->li_tv.vval.v_list;
tv_list_drop_items(list, item, item);
- tv_list_extend(list, item->li_tv.vval.v_list, next);
- tv_clear(&item->li_tv);
- to_free = item;
+ tv_list_extend(list, itemlist, next);
- if (item->li_prev == NULL) {
- item = list->lv_first;
- } else {
- item = item->li_prev->li_next;
+ if (maxdepth > 0) {
+ tv_list_flatten(list,
+ item->li_prev == NULL ? list->lv_first : item->li_prev->li_next,
+ itemlist->lv_len, maxdepth - 1);
}
- xfree(to_free);
-
- if (++n >= maxdepth) {
- n = 0;
- item = next;
- }
- } else {
- n = 0;
- item = item->li_next;
+ tv_clear(&item->li_tv);
+ xfree(item);
}
+
+ done++;
+ item = next;
}
- return OK;
}
/// Extend first list with the second
@@ -750,9 +831,67 @@ int tv_list_concat(list_T *const l1, list_T *const l2, typval_T *const tv)
return OK;
}
+static list_T *tv_list_slice(list_T *ol, varnumber_T n1, varnumber_T n2)
+{
+ list_T *l = tv_list_alloc(n2 - n1 + 1);
+ listitem_T *item = tv_list_find(ol, (int)n1);
+ for (; n1 <= n2; n1++) {
+ tv_list_append_tv(l, TV_LIST_ITEM_TV(item));
+ item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item);
+ }
+ return l;
+}
+
+int tv_list_slice_or_index(list_T *list, bool range, varnumber_T n1_arg, varnumber_T n2_arg,
+ bool exclusive, typval_T *rettv, bool verbose)
+{
+ int len = tv_list_len(rettv->vval.v_list);
+ varnumber_T n1 = n1_arg;
+ varnumber_T n2 = n2_arg;
+
+ if (n1 < 0) {
+ n1 = len + n1;
+ }
+ if (n1 < 0 || n1 >= len) {
+ // For a range we allow invalid values and return an empty list.
+ // A list index out of range is an error.
+ if (!range) {
+ if (verbose) {
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)n1);
+ }
+ return FAIL;
+ }
+ n1 = len;
+ }
+ if (range) {
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len - (exclusive ? 0 : 1);
+ }
+ if (exclusive) {
+ n2--;
+ }
+ if (n2 < 0 || n2 + 1 < n1) {
+ n2 = -1;
+ }
+ list_T *l = tv_list_slice(rettv->vval.v_list, n1, n2);
+ tv_clear(rettv);
+ tv_list_set_ret(rettv, l);
+ } else {
+ // copy the item to "var1" to avoid that freeing the list makes it
+ // invalid.
+ typval_T var1;
+ tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, (int)n1)), &var1);
+ tv_clear(rettv);
+ *rettv = var1;
+ }
+ return OK;
+}
+
typedef struct {
- char_u *s;
- char_u *tofree;
+ char *s;
+ char *tofree;
} Join;
/// Join list into a string, helper function
@@ -785,7 +924,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con
sumlen += len;
Join *const p = GA_APPEND_VIA_PTR(Join, join_gap);
- p->tofree = p->s = (char_u *)s;
+ p->tofree = p->s = s;
line_breakcheck();
});
@@ -806,7 +945,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, const char *con
const Join *const p = ((const Join *)join_gap->ga_data) + i;
if (p->s != NULL) {
- ga_concat(gap, (char *)p->s);
+ ga_concat(gap, p->s);
}
line_breakcheck();
}
@@ -886,8 +1025,8 @@ void f_list2str(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char buf[MB_MAXBYTES + 1];
TV_LIST_ITER_CONST(l, li, {
- buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), (char *)buf)] = NUL;
- ga_concat(&ga, (char *)buf);
+ buf[utf_char2bytes((int)tv_get_number(TV_LIST_ITEM_TV(li)), buf)] = NUL;
+ ga_concat(&ga, buf);
});
ga_append(&ga, NUL);
@@ -905,14 +1044,14 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
return;
}
- long idx = tv_get_number_chk(&argvars[1], &error);
+ int64_t idx = tv_get_number_chk(&argvars[1], &error);
listitem_T *item;
if (error) {
// Type error: do nothing, errmsg already given.
} else if ((item = tv_list_find(l, (int)idx)) == NULL) {
- semsg(_(e_listidx), (int64_t)idx);
+ semsg(_(e_list_index_out_of_range_nr), idx);
} else {
if (argvars[2].v_type == VAR_UNKNOWN) {
// Remove one item, return its value.
@@ -922,11 +1061,11 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
} else {
listitem_T *item2;
// Remove range of items, return list with values.
- long end = tv_get_number_chk(&argvars[2], &error);
+ int64_t end = tv_get_number_chk(&argvars[2], &error);
if (error) {
// Type error: do nothing.
} else if ((item2 = tv_list_find(l, (int)end)) == NULL) {
- semsg(_(e_listidx), (int64_t)end);
+ semsg(_(e_list_index_out_of_range_nr), end);
} else {
int cnt = 0;
@@ -948,18 +1087,6 @@ void tv_list_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
}
-/// struct storing information about current sort
-typedef struct {
- int item_compare_ic;
- bool item_compare_lc;
- bool item_compare_numeric;
- bool item_compare_numbers;
- bool item_compare_float;
- const char *item_compare_func;
- partial_T *item_compare_partial;
- dict_T *item_compare_selfdict;
- bool item_compare_func_err;
-} sortinfo_T;
static sortinfo_T *sortinfo = NULL;
#define ITEM_COMPARE_FAIL 999
@@ -1027,12 +1154,11 @@ static int item_compare(const void *s1, const void *s2, bool keep_zero)
if (sortinfo->item_compare_lc) {
res = strcoll(p1, p2);
} else {
- res = sortinfo->item_compare_ic ? STRICMP(p1, p2): strcmp(p1, p2);
+ res = sortinfo->item_compare_ic ? STRICMP(p1, p2) : strcmp(p1, p2);
}
} else {
- double n1, n2;
- n1 = strtod(p1, &p1);
- n2 = strtod(p2, &p2);
+ double n1 = strtod(p1, &p1);
+ double n2 = strtod(p2, &p2);
res = n1 == n2 ? 0 : n1 > n2 ? 1 : -1;
}
@@ -1062,8 +1188,6 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2)
static int item_compare2(const void *s1, const void *s2, bool keep_zero)
{
- ListSortItem *si1, *si2;
- int res;
typval_T rettv;
typval_T argv[3];
const char *func_name;
@@ -1074,13 +1198,13 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
return 0;
}
- si1 = (ListSortItem *)s1;
- si2 = (ListSortItem *)s2;
+ ListSortItem *si1 = (ListSortItem *)s1;
+ ListSortItem *si2 = (ListSortItem *)s2;
if (partial == NULL) {
func_name = sortinfo->item_compare_func;
} else {
- func_name = (const char *)partial_name(partial);
+ func_name = partial_name(partial);
}
// Copy the values. This is needed to be able to set v_lock to VAR_FIXED
@@ -1093,7 +1217,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
funcexe.fe_selfdict = sortinfo->item_compare_selfdict;
- res = call_func(func_name, -1, &rettv, 2, argv, &funcexe);
+ int res = call_func(func_name, -1, &rettv, 2, argv, &funcexe);
tv_clear(&argv[0]);
tv_clear(&argv[1]);
@@ -1135,149 +1259,188 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2)
return item_compare2(s1, s2, false);
}
-/// "sort({list})" function
-static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+/// sort() List "l"
+static void do_sort(list_T *l, sortinfo_T *info)
{
- ListSortItem *ptrs;
- long len;
- long i;
+ const int len = tv_list_len(l);
- // Pointer to current info struct used in compare function. Save and restore
- // the current one for nested calls.
- sortinfo_T info;
- sortinfo_T *old_sortinfo = sortinfo;
- sortinfo = &info;
+ // Make an array with each entry pointing to an item in the List.
+ ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
- const char *const arg_errmsg = (sort
- ? N_("sort() argument")
- : N_("uniq() argument"));
+ // f_sort(): ptrs will be the list to sort
+ int i = 0;
+ TV_LIST_ITER(l, li, {
+ ptrs[i].item = li;
+ ptrs[i].idx = i;
+ i++;
+ });
- if (argvars[0].v_type != VAR_LIST) {
- semsg(_(e_listarg), sort ? "sort()" : "uniq()");
- } else {
- list_T *const l = argvars[0].vval.v_list;
- if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
- goto theend;
+ info->item_compare_func_err = false;
+ ListSorter item_compare_func = ((info->item_compare_func == NULL
+ && info->item_compare_partial == NULL)
+ ? item_compare_not_keeping_zero
+ : item_compare2_not_keeping_zero);
+
+ // Sort the array with item pointers.
+ qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
+ if (!info->item_compare_func_err) {
+ // Clear the list and append the items in the sorted order.
+ l->lv_first = NULL;
+ l->lv_last = NULL;
+ l->lv_idx_item = NULL;
+ l->lv_len = 0;
+ for (i = 0; i < len; i++) {
+ tv_list_append(l, ptrs[i].item);
}
- tv_list_set_ret(rettv, l);
+ }
+ if (info->item_compare_func_err) {
+ emsg(_("E702: Sort compare function failed"));
+ }
- len = tv_list_len(l);
- if (len <= 1) {
- goto theend; // short list sorts pretty quickly
- }
-
- info.item_compare_ic = false;
- info.item_compare_lc = false;
- info.item_compare_numeric = false;
- info.item_compare_numbers = false;
- info.item_compare_float = false;
- info.item_compare_func = NULL;
- info.item_compare_partial = NULL;
- info.item_compare_selfdict = NULL;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- // optional second argument: {func}
- if (argvars[1].v_type == VAR_FUNC) {
- info.item_compare_func = (const char *)argvars[1].vval.v_string;
- } else if (argvars[1].v_type == VAR_PARTIAL) {
- info.item_compare_partial = argvars[1].vval.v_partial;
- } else {
- bool error = false;
+ xfree(ptrs);
+}
- i = tv_get_number_chk(&argvars[1], &error);
- if (error) {
- goto theend; // type error; errmsg already given
- }
- if (i == 1) {
- info.item_compare_ic = true;
- } else if (argvars[1].v_type != VAR_NUMBER) {
- info.item_compare_func = tv_get_string(&argvars[1]);
- } else if (i != 0) {
- emsg(_(e_invarg));
- goto theend;
- }
- if (info.item_compare_func != NULL) {
- if (*info.item_compare_func == NUL) {
- // empty string means default sort
- info.item_compare_func = NULL;
- } else if (strcmp(info.item_compare_func, "n") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numeric = true;
- } else if (strcmp(info.item_compare_func, "N") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_numbers = true;
- } else if (strcmp(info.item_compare_func, "f") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_float = true;
- } else if (strcmp(info.item_compare_func, "i") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_ic = true;
- } else if (strcmp(info.item_compare_func, "l") == 0) {
- info.item_compare_func = NULL;
- info.item_compare_lc = true;
- }
- }
- }
+/// uniq() List "l"
+static void do_uniq(list_T *l, sortinfo_T *info)
+{
+ const int len = tv_list_len(l);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // optional third argument: {dict}
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- goto theend;
- }
- info.item_compare_selfdict = argvars[2].vval.v_dict;
- }
- }
+ // Make an array with each entry pointing to an item in the List.
+ ListSortItem *ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
- // Make an array with each entry pointing to an item in the List.
- ptrs = xmalloc((size_t)((unsigned)len * sizeof(ListSortItem)));
+ // f_uniq(): ptrs will be a stack of items to remove.
- if (sort) {
- info.item_compare_func_err = false;
- tv_list_item_sort(l, ptrs,
- ((info.item_compare_func == NULL
- && info.item_compare_partial == NULL)
- ? item_compare_not_keeping_zero
- : item_compare2_not_keeping_zero),
- &info.item_compare_func_err);
- if (info.item_compare_func_err) {
- emsg(_("E702: Sort compare function failed"));
- }
+ info->item_compare_func_err = false;
+ ListSorter item_compare_func = ((info->item_compare_func == NULL
+ && info->item_compare_partial == NULL)
+ ? item_compare_keeping_zero
+ : item_compare2_keeping_zero);
+
+ for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)); li != NULL;) {
+ listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
+ if (item_compare_func(&prev_li, &li) == 0) {
+ li = tv_list_item_remove(l, li);
} else {
- ListSorter item_compare_func_ptr;
+ li = TV_LIST_ITEM_NEXT(l, li);
+ }
+ if (info->item_compare_func_err) {
+ emsg(_("E882: Uniq compare function failed"));
+ break;
+ }
+ }
- // f_uniq(): ptrs will be a stack of items to remove.
- info.item_compare_func_err = false;
- if (info.item_compare_func != NULL
- || info.item_compare_partial != NULL) {
- item_compare_func_ptr = item_compare2_keeping_zero;
- } else {
- item_compare_func_ptr = item_compare_keeping_zero;
- }
+ xfree(ptrs);
+}
- for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l))
- ; li != NULL;) {
- listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li);
- if (item_compare_func_ptr(&prev_li, &li) == 0) {
- li = tv_list_item_remove(l, li);
- } else {
- li = TV_LIST_ITEM_NEXT(l, li);
- }
- if (info.item_compare_func_err) {
- emsg(_("E882: Uniq compare function failed"));
- break;
- }
+/// Parse the optional arguments to sort() and uniq() and return the values in "info".
+static int parse_sort_uniq_args(typval_T *argvars, sortinfo_T *info)
+{
+ info->item_compare_ic = false;
+ info->item_compare_lc = false;
+ info->item_compare_numeric = false;
+ info->item_compare_numbers = false;
+ info->item_compare_float = false;
+ info->item_compare_func = NULL;
+ info->item_compare_partial = NULL;
+ info->item_compare_selfdict = NULL;
+
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ return OK;
+ }
+
+ // optional second argument: {func}
+ if (argvars[1].v_type == VAR_FUNC) {
+ info->item_compare_func = argvars[1].vval.v_string;
+ } else if (argvars[1].v_type == VAR_PARTIAL) {
+ info->item_compare_partial = argvars[1].vval.v_partial;
+ } else {
+ bool error = false;
+ int nr = (int)tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return FAIL; // type error; errmsg already given
+ }
+ if (nr == 1) {
+ info->item_compare_ic = true;
+ } else if (argvars[1].v_type != VAR_NUMBER) {
+ info->item_compare_func = tv_get_string(&argvars[1]);
+ } else if (nr != 0) {
+ emsg(_(e_invarg));
+ return FAIL;
+ }
+ if (info->item_compare_func != NULL) {
+ if (*info->item_compare_func == NUL) {
+ // empty string means default sort
+ info->item_compare_func = NULL;
+ } else if (strcmp(info->item_compare_func, "n") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_numeric = true;
+ } else if (strcmp(info->item_compare_func, "N") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_numbers = true;
+ } else if (strcmp(info->item_compare_func, "f") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_float = true;
+ } else if (strcmp(info->item_compare_func, "i") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_ic = true;
+ } else if (strcmp(info->item_compare_func, "l") == 0) {
+ info->item_compare_func = NULL;
+ info->item_compare_lc = true;
}
}
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // optional third argument: {dict}
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
+ return FAIL;
+ }
+ info->item_compare_selfdict = argvars[2].vval.v_dict;
+ }
+
+ return OK;
+}
- xfree(ptrs);
+/// "sort()" or "uniq()" function
+static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort)
+{
+ if (argvars[0].v_type != VAR_LIST) {
+ semsg(_(e_listarg), sort ? "sort()" : "uniq()");
+ return;
+ }
+
+ // Pointer to current info struct used in compare function. Save and restore
+ // the current one for nested calls.
+ sortinfo_T info;
+ sortinfo_T *old_sortinfo = sortinfo;
+ sortinfo = &info;
+
+ const char *const arg_errmsg = (sort ? N_("sort() argument") : N_("uniq() argument"));
+ list_T *const l = argvars[0].vval.v_list;
+ if (value_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) {
+ goto theend;
+ }
+ tv_list_set_ret(rettv, l);
+
+ const int len = tv_list_len(l);
+ if (len <= 1) {
+ goto theend; // short list sorts pretty quickly
+ }
+ if (parse_sort_uniq_args(argvars, &info) == FAIL) {
+ goto theend;
+ }
+
+ if (sort) {
+ do_sort(l, &info);
+ } else {
+ do_uniq(l, &info);
}
theend:
sortinfo = old_sortinfo;
}
-/// "sort"({list})" function
+/// "sort({list})" function
void f_sort(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
do_sort_uniq(argvars, rettv, true);
@@ -1336,7 +1499,6 @@ void tv_list_reverse(list_T *const l)
if (tv_list_len(l) <= 1) {
return;
}
- list_log(l, NULL, NULL, "reverse");
#define SWAP(a, b) \
do { \
tmp = (a); \
@@ -1354,47 +1516,6 @@ void tv_list_reverse(list_T *const l)
l->lv_idx = l->lv_len - l->lv_idx - 1;
}
-// FIXME Add unit tests for tv_list_item_sort().
-
-/// Sort list using libc qsort
-///
-/// @param[in,out] l List to sort, will be sorted in-place.
-/// @param ptrs Preallocated array of items to sort, must have at least
-/// tv_list_len(l) entries. Should not be initialized.
-/// @param[in] item_compare_func Function used to compare list items.
-/// @param errp Location where information about whether error occurred is
-/// saved by item_compare_func. If boolean there appears to be
-/// true list will not be modified. Must be initialized to false
-/// by the caller.
-void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs,
- const ListSorter item_compare_func, const bool *errp)
- FUNC_ATTR_NONNULL_ARG(3, 4)
-{
- const int len = tv_list_len(l);
- if (len <= 1) {
- return;
- }
- list_log(l, NULL, NULL, "sort");
- int i = 0;
- TV_LIST_ITER(l, li, {
- ptrs[i].item = li;
- ptrs[i].idx = i;
- i++;
- });
- // Sort the array with item pointers.
- qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func);
- if (!(*errp)) {
- // Clear the list and append the items in the sorted order.
- l->lv_first = NULL;
- l->lv_last = NULL;
- l->lv_idx_item = NULL;
- l->lv_len = 0;
- for (i = 0; i < len; i++) {
- tv_list_append(l, ptrs[i].item);
- }
- }
-}
-
//{{{2 Indexing/searching
/// Locate item with a given index in a list and return it
@@ -1463,7 +1584,6 @@ listitem_T *tv_list_find(list_T *const l, int n)
// Cache the used index.
l->lv_idx = idx;
l->lv_idx_item = item;
- list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find");
return item;
}
@@ -1501,19 +1621,34 @@ const char *tv_list_find_str(list_T *const l, const int n)
{
const listitem_T *const li = tv_list_find(l, n);
if (li == NULL) {
- semsg(_(e_listidx), (int64_t)n);
+ semsg(_(e_list_index_out_of_range_nr), (int64_t)n);
return NULL;
}
return tv_get_string(TV_LIST_ITEM_TV(li));
}
+/// Like tv_list_find() but when a negative index is used that is not found use
+/// zero and set "idx" to zero. Used for first index of a range.
+static listitem_T *tv_list_find_index(list_T *const l, int *const idx)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ listitem_T *li = tv_list_find(l, *idx);
+ if (li == NULL) {
+ if (*idx < 0) {
+ *idx = 0;
+ li = tv_list_find(l, *idx);
+ }
+ }
+ return li;
+}
+
/// Locate item in a list and return its index
///
/// @param[in] l List to search.
/// @param[in] item Item to search for.
///
/// @return Index of an item or -1 if item is not in the list.
-long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item)
+int tv_list_idx_of_item(const list_T *const l, const listitem_T *const item)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
if (l == NULL) {
@@ -1673,7 +1808,7 @@ char *callback_to_string(Callback *cb)
}
const size_t msglen = 100;
- char *msg = (char *)xmallocz(msglen);
+ char *msg = xmallocz(msglen);
switch (cb->type) {
case kCallbackFuncref:
@@ -1883,7 +2018,7 @@ void tv_dict_item_free(dictitem_T *const item)
dictitem_T *tv_dict_item_copy(dictitem_T *const di)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- dictitem_T *const new_di = tv_dict_item_alloc((const char *)di->di_key);
+ dictitem_T *const new_di = tv_dict_item_alloc(di->di_key);
tv_copy(&di->di_tv, &new_di->di_tv);
return new_di;
}
@@ -1895,7 +2030,7 @@ dictitem_T *tv_dict_item_copy(dictitem_T *const di)
void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- hashitem_T *const hi = hash_find(&dict->dv_hashtab, (char *)item->di_key);
+ hashitem_T *const hi = hash_find(&dict->dv_hashtab, item->di_key);
if (HASHITEM_EMPTY(hi)) {
semsg(_(e_intern2), "tv_dict_item_remove()");
} else {
@@ -2086,6 +2221,16 @@ varnumber_T tv_dict_get_number_def(const dict_T *const d, const char *const key,
return tv_get_number(&di->di_tv);
}
+varnumber_T tv_dict_get_bool(const dict_T *const d, const char *const key, const int def)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ dictitem_T *const di = tv_dict_find(d, key, -1);
+ if (di == NULL) {
+ return def;
+ }
+ return tv_get_bool(&di->di_tv);
+}
+
/// Converts a dict to an environment
char **tv_dict_to_env(dict_T *denv)
{
@@ -2100,9 +2245,9 @@ char **tv_dict_to_env(dict_T *denv)
TV_DICT_ITER(denv, var, {
const char *str = tv_get_string(&var->di_tv);
assert(str);
- size_t len = strlen((char *)var->di_key) + strlen(str) + strlen("=") + 1;
+ size_t len = strlen(var->di_key) + strlen(str) + strlen("=") + 1;
env[i] = xmalloc(len);
- snprintf(env[i], len, "%s=%s", (char *)var->di_key, str);
+ snprintf(env[i], len, "%s=%s", var->di_key, str);
i++;
});
@@ -2229,10 +2374,10 @@ int tv_dict_wrong_func_name(dict_T *d, typval_T *tv, const char *name)
int tv_dict_add(dict_T *const d, dictitem_T *const item)
FUNC_ATTR_NONNULL_ALL
{
- if (tv_dict_wrong_func_name(d, &item->di_tv, (const char *)item->di_key)) {
+ if (tv_dict_wrong_func_name(d, &item->di_tv, item->di_key)) {
return FAIL;
}
- return hash_add(&d->dv_hashtab, (char *)item->di_key);
+ return hash_add(&d->dv_hashtab, item->di_key);
}
/// Add a list entry to dictionary
@@ -2466,9 +2611,9 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
HASHTAB_ITER(&d2->dv_hashtab, hi2, {
dictitem_T *const di2 = TV_DICT_HI2DI(hi2);
- dictitem_T *const di1 = tv_dict_find(d1, (const char *)di2->di_key, -1);
+ dictitem_T *const di1 = tv_dict_find(d1, di2->di_key, -1);
// Check the key to be valid when adding to any scope.
- if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname((const char *)di2->di_key)) {
+ if (d1->dv_scope != VAR_NO_SCOPE && !valid_varname(di2->di_key)) {
break;
}
if (di1 == NULL) {
@@ -2478,14 +2623,14 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
dictitem_T *const new_di = di2;
if (tv_dict_add(d1, new_di) == OK) {
hash_remove(&d2->dv_hashtab, hi2);
- tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
+ tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL);
}
} else {
dictitem_T *const new_di = tv_dict_item_copy(di2);
if (tv_dict_add(d1, new_di) == FAIL) {
tv_dict_item_free(new_di);
} else if (watched) {
- tv_dict_watcher_notify(d1, (const char *)new_di->di_key, &new_di->di_tv, NULL);
+ tv_dict_watcher_notify(d1, new_di->di_key, &new_di->di_tv, NULL);
}
}
} else if (*action == 'e') {
@@ -2499,7 +2644,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
break;
}
// Disallow replacing a builtin function.
- if (tv_dict_wrong_func_name(d1, &di2->di_tv, (const char *)di2->di_key)) {
+ if (tv_dict_wrong_func_name(d1, &di2->di_tv, di2->di_key)) {
break;
}
@@ -2511,8 +2656,7 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
tv_copy(&di2->di_tv, &di1->di_tv);
if (watched) {
- tv_dict_watcher_notify(d1, (const char *)di1->di_key, &di1->di_tv,
- &oldtv);
+ tv_dict_watcher_notify(d1, di1->di_key, &di1->di_tv, &oldtv);
tv_clear(&oldtv);
}
}
@@ -2547,7 +2691,7 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool
}
TV_DICT_ITER(d1, di1, {
- dictitem_T *const di2 = tv_dict_find(d2, (const char *)di1->di_key, -1);
+ dictitem_T *const di2 = tv_dict_find(d2, di1->di_key, -1);
if (di2 == NULL) {
return false;
}
@@ -2586,12 +2730,12 @@ dict_T *tv_dict_copy(const vimconv_T *const conv, dict_T *const orig, const bool
}
dictitem_T *new_di;
if (conv == NULL || conv->vc_type == CONV_NONE) {
- new_di = tv_dict_item_alloc((const char *)di->di_key);
+ new_di = tv_dict_item_alloc(di->di_key);
} else {
- size_t len = strlen((char *)di->di_key);
- char *const key = (char *)string_convert(conv, (char *)di->di_key, &len);
+ size_t len = strlen(di->di_key);
+ char *const key = string_convert(conv, di->di_key, &len);
if (key == NULL) {
- new_di = tv_dict_item_alloc_len((const char *)di->di_key, len);
+ new_di = tv_dict_item_alloc_len(di->di_key, len);
} else {
new_di = tv_dict_item_alloc_len(key, len);
xfree(key);
@@ -2705,6 +2849,136 @@ bool tv_blob_equal(const blob_T *const b1, const blob_T *const b2)
return true;
}
+/// Returns a slice of "blob" from index "n1" to "n2" in "rettv". The length of
+/// the blob is "len". Returns an empty blob if the indexes are out of range.
+static int tv_blob_slice(const blob_T *blob, int len, varnumber_T n1, varnumber_T n2,
+ bool exclusive, typval_T *rettv)
+{
+ // The resulting variable is a sub-blob. If the indexes
+ // are out of range the result is empty.
+ if (n1 < 0) {
+ n1 = len + n1;
+ if (n1 < 0) {
+ n1 = 0;
+ }
+ }
+ if (n2 < 0) {
+ n2 = len + n2;
+ } else if (n2 >= len) {
+ n2 = len - (exclusive ? 0 : 1);
+ }
+ if (exclusive) {
+ n2--;
+ }
+ if (n1 >= len || n2 < 0 || n1 > n2) {
+ tv_clear(rettv);
+ rettv->v_type = VAR_BLOB;
+ rettv->vval.v_blob = NULL;
+ } else {
+ blob_T *const new_blob = tv_blob_alloc();
+ ga_grow(&new_blob->bv_ga, (int)(n2 - n1 + 1));
+ new_blob->bv_ga.ga_len = (int)(n2 - n1 + 1);
+ for (int i = (int)n1; i <= (int)n2; i++) {
+ tv_blob_set(new_blob, i - (int)n1, tv_blob_get(rettv->vval.v_blob, i));
+ }
+ tv_clear(rettv);
+ tv_blob_set_ret(rettv, new_blob);
+ }
+
+ return OK;
+}
+
+/// Return the byte value in "blob" at index "idx" in "rettv". If the index is
+/// too big or negative that is an error. The length of the blob is "len".
+static int tv_blob_index(const blob_T *blob, int len, varnumber_T idx, typval_T *rettv)
+{
+ // The resulting variable is a byte value.
+ // If the index is too big or negative that is an error.
+ if (idx < 0) {
+ idx = len + idx;
+ }
+ if (idx < len && idx >= 0) {
+ const int v = (int)tv_blob_get(rettv->vval.v_blob, (int)idx);
+ tv_clear(rettv);
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = v;
+ } else {
+ semsg(_(e_blobidx), idx);
+ return FAIL;
+ }
+
+ return OK;
+}
+
+int tv_blob_slice_or_index(const blob_T *blob, int is_range, varnumber_T n1, varnumber_T n2,
+ bool exclusive, typval_T *rettv)
+{
+ int len = tv_blob_len(rettv->vval.v_blob);
+
+ if (is_range) {
+ return tv_blob_slice(blob, len, n1, n2, exclusive, rettv);
+ } else {
+ return tv_blob_index(blob, len, n1, rettv);
+ }
+}
+
+/// Check if "n1" is a valid index for a blob with length "bloblen".
+int tv_blob_check_index(int bloblen, varnumber_T n1, bool quiet)
+{
+ if (n1 < 0 || n1 > bloblen) {
+ if (!quiet) {
+ semsg(_(e_blobidx), n1);
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check if "n1"-"n2" is a valid range for a blob with length "bloblen".
+int tv_blob_check_range(int bloblen, varnumber_T n1, varnumber_T n2, bool quiet)
+{
+ if (n2 < 0 || n2 >= bloblen || n2 < n1) {
+ if (!quiet) {
+ semsg(_(e_blobidx), n2);
+ }
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Set bytes "n1" to "n2" (inclusive) in "dest" to the value of "src".
+/// Caller must make sure "src" is a blob.
+/// Returns FAIL if the number of bytes does not match.
+int tv_blob_set_range(blob_T *dest, varnumber_T n1, varnumber_T n2, typval_T *src)
+{
+ if (n2 - n1 + 1 != tv_blob_len(src->vval.v_blob)) {
+ emsg(_("E972: Blob value does not have the right number of bytes"));
+ return FAIL;
+ }
+
+ for (int il = (int)n1, ir = 0; il <= (int)n2; il++) {
+ tv_blob_set(dest, il, tv_blob_get(src->vval.v_blob, ir++));
+ }
+ return OK;
+}
+
+/// Store one byte "byte" in blob "blob" at "idx".
+/// Append one byte if needed.
+void tv_blob_set_append(blob_T *blob, int idx, uint8_t byte)
+{
+ garray_T *gap = &blob->bv_ga;
+
+ // Allow for appending a byte. Setting a byte beyond
+ // the end is an error otherwise.
+ if (idx <= gap->ga_len) {
+ if (idx == gap->ga_len) {
+ ga_grow(gap, 1);
+ gap->ga_len++;
+ }
+ tv_blob_set(blob, idx, byte);
+ }
+}
+
/// "remove({blob})" function
void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
{
@@ -2715,7 +2989,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
bool error = false;
- long idx = tv_get_number_chk(&argvars[1], &error);
+ int64_t idx = tv_get_number_chk(&argvars[1], &error);
if (!error) {
const int len = tv_blob_len(b);
@@ -2725,7 +2999,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
idx = len + idx;
}
if (idx < 0 || idx >= len) {
- semsg(_(e_blobidx), (int64_t)idx);
+ semsg(_(e_blobidx), idx);
return;
}
if (argvars[2].v_type == VAR_UNKNOWN) {
@@ -2736,7 +3010,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
b->bv_ga.ga_len--;
} else {
// Remove range of items, return blob with values.
- long end = tv_get_number_chk(&argvars[2], &error);
+ int64_t end = tv_get_number_chk(&argvars[2], &error);
if (error) {
return;
}
@@ -2745,7 +3019,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
end = len + end;
}
if (end >= len || idx > end) {
- semsg(_(e_blobidx), (int64_t)end);
+ semsg(_(e_blobidx), end);
return;
}
blob_T *const blob = tv_blob_alloc();
@@ -2764,6 +3038,51 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
}
}
+/// blob2list() function
+void f_blob2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+
+ if (tv_check_for_blob_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ blob_T *const blob = argvars->vval.v_blob;
+ list_T *const l = rettv->vval.v_list;
+ for (int i = 0; i < tv_blob_len(blob); i++) {
+ tv_list_append_number(l, tv_blob_get(blob, i));
+ }
+}
+
+/// list2blob() function
+void f_list2blob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_blob_alloc_ret(rettv);
+ blob_T *const blob = rettv->vval.v_blob;
+
+ if (tv_check_for_list_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ list_T *const l = argvars->vval.v_list;
+ if (l == NULL) {
+ return;
+ }
+
+ TV_LIST_ITER_CONST(l, li, {
+ bool error = false;
+ varnumber_T n = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
+ if (error || n < 0 || n > 255) {
+ if (!error) {
+ semsg(_(e_invalid_value_for_blob_nr), (int)n);
+ }
+ ga_clear(&blob->bv_ga);
+ return;
+ }
+ ga_append(&blob->bv_ga, (uint8_t)n);
+ });
+}
+
//{{{1 Generic typval operations
//{{{2 Init/alloc/clear
//{{{3 Alloc
@@ -2774,7 +3093,7 @@ void tv_blob_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
///
/// @param[out] ret_tv Structure where list is saved.
/// @param[in] len Expected number of items to be populated before list
-/// becomes accessible from VimL. It is still valid to
+/// becomes accessible from Vimscript. It is still valid to
/// underpopulate a list, value only controls how many elements
/// will be allocated in advance. @see ListLenSpecials.
///
@@ -2820,19 +3139,20 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi
emsg(_(e_dictreq));
return;
}
+
+ tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));
if (tv->vval.v_dict == NULL) {
+ // NULL dict behaves like an empty dict
return;
}
- tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));
-
TV_DICT_ITER(tv->vval.v_dict, di, {
typval_T tv_item = { .v_lock = VAR_UNLOCKED };
switch (what) {
case kDictListKeys:
tv_item.v_type = VAR_STRING;
- tv_item.vval.v_string = xstrdup((char *)di->di_key);
+ tv_item.vval.v_string = xstrdup(di->di_key);
break;
case kDictListValues:
tv_copy(&di->di_tv, &tv_item);
@@ -2847,7 +3167,7 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi
tv_list_append_owned_tv(sub_l, (typval_T) {
.v_type = VAR_STRING,
.v_lock = VAR_UNLOCKED,
- .vval.v_string = xstrdup((const char *)di->di_key),
+ .vval.v_string = xstrdup(di->di_key),
});
tv_list_append_tv(sub_l, &di->di_tv);
@@ -2881,10 +3201,10 @@ void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "has_key()" function
void f_has_key(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (argvars[0].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_dict_arg(argvars, 0) == FAIL) {
return;
}
+
if (argvars[0].vval.v_dict == NULL) {
return;
}
@@ -2936,22 +3256,19 @@ void tv_blob_alloc_ret(typval_T *const ret_tv)
///
/// @param[in] from Blob object to copy from.
/// @param[out] to Blob object to copy to.
-void tv_blob_copy(typval_T *const from, typval_T *const to)
- FUNC_ATTR_NONNULL_ALL
+void tv_blob_copy(blob_T *const from, typval_T *const to)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- assert(from->v_type == VAR_BLOB);
-
to->v_type = VAR_BLOB;
to->v_lock = VAR_UNLOCKED;
- if (from->vval.v_blob == NULL) {
+ if (from == NULL) {
to->vval.v_blob = NULL;
} else {
tv_blob_alloc_ret(to);
- int len = from->vval.v_blob->bv_ga.ga_len;
+ int len = from->bv_ga.ga_len;
if (len > 0) {
- to->vval.v_blob->bv_ga.ga_data
- = xmemdup(from->vval.v_blob->bv_ga.ga_data, (size_t)len);
+ to->vval.v_blob->bv_ga.ga_data = xmemdup(from->bv_ga.ga_data, (size_t)len);
}
to->vval.v_blob->bv_ga.ga_len = len;
to->vval.v_blob->bv_ga.ga_maxlen = len;
@@ -3019,7 +3336,7 @@ static inline int _nothing_conv_func_start(typval_T *const tv, char *const fun)
}
} else {
func_unref(fun);
- if ((const char *)fun != tv_empty_string) {
+ if (fun != tv_empty_string) {
xfree(fun);
}
tv->vval.v_string = NULL;
@@ -3225,7 +3542,7 @@ void tv_clear(typval_T *const tv)
//{{{3 Free
-/// Free allocated VimL object and value stored inside
+/// Free allocated Vimscript object and value stored inside
///
/// @param tv Object to free.
void tv_free(typval_T *tv)
@@ -3335,7 +3652,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo
static int recurse = 0;
if (recurse >= DICT_MAXNEST) {
- emsg(_("E743: variable nested too deep for (un)lock"));
+ emsg(_(e_variable_nested_too_deep_for_unlock));
return;
}
if (deep == 0) {
@@ -3403,7 +3720,7 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock, const boo
recurse--;
}
-/// Check whether VimL value is locked itself or refers to a locked container
+/// Check whether Vimscript value is locked itself or refers to a locked container
///
/// @warning Fixed container is not the same as locked.
///
@@ -3502,7 +3819,7 @@ bool value_check_lock(VarLockStatus lock, const char *name, size_t name_len)
static int tv_equal_recurse_limit;
-/// Compare two VimL values
+/// Compare two Vimscript values
///
/// Like "==", but strings and numbers are different, as well as floats and
/// numbers.
@@ -3643,13 +3960,13 @@ bool tv_check_str_or_nr(const typval_T *const tv)
#define FUNC_ERROR "E703: Using a Funcref as a Number"
static const char *const num_errors[] = {
- [VAR_PARTIAL]= N_(FUNC_ERROR),
- [VAR_FUNC]= N_(FUNC_ERROR),
- [VAR_LIST]= N_("E745: Using a List as a Number"),
- [VAR_DICT]= N_("E728: Using a Dictionary as a Number"),
- [VAR_FLOAT]= N_("E805: Using a Float as a Number"),
- [VAR_BLOB]= N_("E974: Using a Blob as a Number"),
- [VAR_UNKNOWN]= N_("E685: using an invalid value as a Number"),
+ [VAR_PARTIAL] = N_(FUNC_ERROR),
+ [VAR_FUNC] = N_(FUNC_ERROR),
+ [VAR_LIST] = N_("E745: Using a List as a Number"),
+ [VAR_DICT] = N_("E728: Using a Dictionary as a Number"),
+ [VAR_FLOAT] = N_("E805: Using a Float as a Number"),
+ [VAR_BLOB] = N_("E974: Using a Blob as a Number"),
+ [VAR_UNKNOWN] = N_("E685: using an invalid value as a Number"),
};
#undef FUNC_ERROR
@@ -3685,21 +4002,20 @@ bool tv_check_num(const typval_T *const tv)
return false;
}
-#define FUNC_ERROR "E729: using Funcref as a String"
+#define FUNC_ERROR "E729: Using a Funcref as a String"
static const char *const str_errors[] = {
- [VAR_PARTIAL]= N_(FUNC_ERROR),
- [VAR_FUNC]= N_(FUNC_ERROR),
- [VAR_LIST]= N_("E730: using List as a String"),
- [VAR_DICT]= N_("E731: using Dictionary as a String"),
- [VAR_FLOAT]= ((const char *)e_float_as_string),
- [VAR_BLOB]= N_("E976: using Blob as a String"),
- [VAR_UNKNOWN]= N_("E908: using an invalid value as a String"),
+ [VAR_PARTIAL] = N_(FUNC_ERROR),
+ [VAR_FUNC] = N_(FUNC_ERROR),
+ [VAR_LIST] = N_("E730: Using a List as a String"),
+ [VAR_DICT] = N_("E731: Using a Dictionary as a String"),
+ [VAR_BLOB] = N_("E976: Using a Blob as a String"),
+ [VAR_UNKNOWN] = e_using_invalid_value_as_string,
};
#undef FUNC_ERROR
-/// Check that given value is a VimL String or can be "cast" to it.
+/// Check that given value is a Vimscript String or can be "cast" to it.
///
/// Error messages are compatible with tv_get_string_chk() previously used for
/// the same purpose.
@@ -3715,12 +4031,12 @@ bool tv_check_str(const typval_T *const tv)
case VAR_BOOL:
case VAR_SPECIAL:
case VAR_STRING:
+ case VAR_FLOAT:
return true;
case VAR_PARTIAL:
case VAR_FUNC:
case VAR_LIST:
case VAR_DICT:
- case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
@@ -3732,7 +4048,7 @@ bool tv_check_str(const typval_T *const tv)
//{{{2 Get
-/// Get the number value of a VimL object
+/// Get the number value of a Vimscript object
///
/// @note Use tv_get_number_chk() if you need to determine whether there was an
/// error.
@@ -3748,7 +4064,7 @@ varnumber_T tv_get_number(const typval_T *const tv)
return tv_get_number_chk(tv, &error);
}
-/// Get the number value of a VimL object
+/// Get the number value of a Vimscript object
///
/// @param[in] tv Object to get value from.
/// @param[out] ret_error If type error occurred then `true` will be written
@@ -3777,7 +4093,7 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
case VAR_STRING: {
varnumber_T n = 0;
if (tv->vval.v_string != NULL) {
- vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false);
+ vim_str2nr(tv->vval.v_string, NULL, NULL, STR2NR_ALL, &n, NULL, 0, false, NULL);
}
return n;
}
@@ -3795,7 +4111,19 @@ varnumber_T tv_get_number_chk(const typval_T *const tv, bool *const ret_error)
return (ret_error == NULL ? -1 : 0);
}
-/// Get the line number from VimL object
+varnumber_T tv_get_bool(const typval_T *const tv)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return tv_get_number_chk(tv, NULL);
+}
+
+varnumber_T tv_get_bool_chk(const typval_T *const tv, bool *const ret_error)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ return tv_get_number_chk(tv, ret_error);
+}
+
+/// Get the line number from Vimscript object
///
/// @param[in] tv Object to get value from. Is expected to be a number or
/// a special string like ".", "$", … (works with current buffer
@@ -3818,7 +4146,7 @@ linenr_T tv_get_lnum(const typval_T *const tv)
return lnum;
}
-/// Get the floating-point value of a VimL object
+/// Get the floating-point value of a Vimscript object
///
/// Raises an error if object is not number or floating-point.
///
@@ -3887,6 +4215,14 @@ int tv_check_for_nonempty_string_arg(const typval_T *const args, const int idx)
return OK;
}
+/// Check for an optional string argument at "idx"
+int tv_check_for_opt_string_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_string_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a number.
int tv_check_for_number_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -3906,6 +4242,109 @@ int tv_check_for_opt_number_arg(const typval_T *const args, const int idx)
|| tv_check_for_number_arg(args, idx) != FAIL) ? OK : FAIL;
}
+/// Give an error and return FAIL unless "args[idx]" is a float or a number.
+int tv_check_for_float_or_nr_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_FLOAT && args[idx].v_type != VAR_NUMBER) {
+ semsg(_(e_float_or_number_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a bool.
+int tv_check_for_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_BOOL
+ && !(args[idx].v_type == VAR_NUMBER
+ && (args[idx].vval.v_number == 0
+ || args[idx].vval.v_number == 1))) {
+ semsg(_(e_bool_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional bool argument at "idx".
+/// Return FAIL if the type is wrong.
+int tv_check_for_opt_bool_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type == VAR_UNKNOWN) {
+ return OK;
+ }
+ return tv_check_for_bool_arg(args, idx);
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a blob.
+int tv_check_for_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a list.
+int tv_check_for_list_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_LIST) {
+ semsg(_(e_list_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a dict.
+int tv_check_for_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_DICT) {
+ semsg(_(e_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a non-NULL dict.
+int tv_check_for_nonnull_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (tv_check_for_dict_arg(args, idx) == FAIL) {
+ return FAIL;
+ }
+ if (args[idx].vval.v_dict == NULL) {
+ semsg(_(e_non_null_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional dict argument at "idx"
+int tv_check_for_opt_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_dict_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a string or
+/// a number.
+int tv_check_for_string_or_number_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_STRING && args[idx].v_type != VAR_NUMBER) {
+ semsg(_(e_string_or_number_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a string or a list.
int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
@@ -3917,7 +4356,53 @@ int tv_check_for_string_or_list_arg(const typval_T *const args, const int idx)
return OK;
}
-/// Get the string value of a "stringish" VimL object.
+/// Give an error and return FAIL unless "args[idx]" is a string, a list or a blob.
+int tv_check_for_string_or_list_or_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_STRING
+ && args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_string_list_or_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Check for an optional string or list argument at "idx"
+int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ return (args[idx].v_type == VAR_UNKNOWN
+ || tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a string
+/// or a function reference.
+int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_PARTIAL
+ && args[idx].v_type != VAR_FUNC
+ && args[idx].v_type != VAR_STRING) {
+ semsg(_(e_string_or_function_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Give an error and return FAIL unless "args[idx]" is a list or a blob.
+int tv_check_for_list_or_blob_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_LIST && args[idx].v_type != VAR_BLOB) {
+ semsg(_(e_list_or_blob_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
+/// Get the string value of a "stringish" Vimscript object.
///
/// @param[in] tv Object to get value of.
/// @param buf Buffer used to hold numbers and special variables converted to
@@ -3933,11 +4418,14 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
{
switch (tv->v_type) {
case VAR_NUMBER:
- snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number); // -V576
+ snprintf(buf, NUMBUFLEN, "%" PRIdVARNUMBER, tv->vval.v_number);
+ return buf;
+ case VAR_FLOAT:
+ vim_snprintf(buf, NUMBUFLEN, "%g", tv->vval.v_float);
return buf;
case VAR_STRING:
if (tv->vval.v_string != NULL) {
- return (const char *)tv->vval.v_string;
+ return tv->vval.v_string;
}
return "";
case VAR_BOOL:
@@ -3950,7 +4438,6 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
case VAR_FUNC:
case VAR_LIST:
case VAR_DICT:
- case VAR_FLOAT:
case VAR_BLOB:
case VAR_UNKNOWN:
emsg(_(str_errors[tv->v_type]));
@@ -3960,7 +4447,7 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf)
return NULL;
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @warning For number and special values it uses a single, static buffer. It
/// may be used only once, next call to tv_get_string may reuse it. Use
@@ -3979,7 +4466,7 @@ const char *tv_get_string_chk(const typval_T *const tv)
return tv_get_string_buf_chk(tv, mybuf);
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @warning For number and special values it uses a single, static buffer. It
/// may be used only once, next call to tv_get_string may reuse it. Use
@@ -4001,7 +4488,7 @@ const char *tv_get_string(const typval_T *const tv)
return tv_get_string_buf((typval_T *)tv, mybuf);
}
-/// Get the string value of a "stringish" VimL object.
+/// Get the string value of a "stringish" Vimscript object.
///
/// @note tv_get_string_chk() and tv_get_string_buf_chk() are similar, but
/// return NULL on error.
@@ -4023,3 +4510,34 @@ const char *tv_get_string_buf(const typval_T *const tv, char *const buf)
return res != NULL ? res : "";
}
+
+/// Return true when "tv" is not falsy: non-zero, non-empty string, non-empty
+/// list, etc. Mostly like what JavaScript does, except that empty list and
+/// empty dictionary are false.
+bool tv2bool(const typval_T *const tv)
+{
+ switch (tv->v_type) {
+ case VAR_NUMBER:
+ return tv->vval.v_number != 0;
+ case VAR_FLOAT:
+ return tv->vval.v_float != 0.0;
+ case VAR_PARTIAL:
+ return tv->vval.v_partial != NULL;
+ case VAR_FUNC:
+ case VAR_STRING:
+ return tv->vval.v_string != NULL && *tv->vval.v_string != NUL;
+ case VAR_LIST:
+ return tv->vval.v_list != NULL && tv->vval.v_list->lv_len > 0;
+ case VAR_DICT:
+ return tv->vval.v_dict != NULL && tv->vval.v_dict->dv_hashtab.ht_used > 0;
+ case VAR_BOOL:
+ return tv->vval.v_bool == kBoolVarTrue;
+ case VAR_SPECIAL:
+ return tv->vval.v_special != kSpecialVarNull;
+ case VAR_BLOB:
+ return tv->vval.v_blob != NULL && tv->vval.v_blob->bv_ga.ga_len > 0;
+ case VAR_UNKNOWN:
+ break;
+ }
+ return false;
+}
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 3f59cd3547..58f74a9796 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_TYPVAL_H
-#define NVIM_EVAL_TYPVAL_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
@@ -7,84 +6,16 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: export
#include "nvim/func_attr.h"
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/hashtab.h"
#include "nvim/lib/queue.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte_defs.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-
-#ifdef LOG_LIST_ACTIONS
-# include "nvim/memory.h"
-
-extern ListLog *list_log_first; ///< First list log chunk, NULL if missing
-extern ListLog *list_log_last; ///< Last list log chunk
-
-static inline ListLog *list_log_alloc(const size_t size)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT;
-
-/// Allocate a new log chunk and update globals
-///
-/// @param[in] size Number of entries in a new chunk.
-///
-/// @return [allocated] Newly allocated chunk.
-static inline ListLog *list_log_new(const size_t size)
-{
- ListLog *ret = xmalloc(offsetof(ListLog, entries)
- + size * sizeof(ret->entries[0]));
- ret->size = 0;
- ret->capacity = size;
- ret->next = NULL;
- if (list_log_first == NULL) {
- list_log_first = ret;
- } else {
- list_log_last->next = ret;
- }
- list_log_last = ret;
- return ret;
-}
-
-static inline void list_log(const list_T *const l, const listitem_T *const li1,
- const listitem_T *const li2, const char *const action)
- REAL_FATTR_ALWAYS_INLINE;
-
-/// Add new entry to log
-///
-/// If last chunk was filled it uses twice as much memory to allocate the next
-/// chunk.
-///
-/// @param[in] l List to which entry belongs.
-/// @param[in] li1 List item 1.
-/// @param[in] li2 List item 2, often used for integers and not list items.
-/// @param[in] action Logged action.
-static inline void list_log(const list_T *const l, const listitem_T *const li1,
- const listitem_T *const li2, const char *const action)
-{
- ListLog *tgt;
- if (list_log_first == NULL) {
- tgt = list_log_new(128);
- } else if (list_log_last->size == list_log_last->capacity) {
- tgt = list_log_new(list_log_last->capacity * 2);
- } else {
- tgt = list_log_last;
- }
- tgt->entries[tgt->size++] = (ListLogEntry) {
- .l = (uintptr_t)l,
- .li1 = (uintptr_t)li1,
- .li2 = (uintptr_t)li2,
- .len = (l == NULL ? 0 : l->lv_len),
- .action = action,
- };
-}
-#else
-# define list_log(...)
-# define list_write_log(...)
-# define list_free_log()
-#endif
+#include "nvim/types_defs.h"
// In a hashtab item "hi_key" points to "di_key" in a dictitem.
// This avoids adding a pointer to the hashtab item.
@@ -174,7 +105,6 @@ static inline int tv_list_len(const list_T *l)
/// @param[in] l List to check.
static inline int tv_list_len(const list_T *const l)
{
- list_log(l, NULL, NULL, "len");
if (l == NULL) {
return 0;
}
@@ -258,10 +188,8 @@ static inline listitem_T *tv_list_first(const list_T *l)
static inline listitem_T *tv_list_first(const list_T *const l)
{
if (l == NULL) {
- list_log(l, NULL, NULL, "first");
return NULL;
}
- list_log(l, l->lv_first, NULL, "first");
return l->lv_first;
}
@@ -276,10 +204,8 @@ static inline listitem_T *tv_list_last(const list_T *l)
static inline listitem_T *tv_list_last(const list_T *const l)
{
if (l == NULL) {
- list_log(l, NULL, NULL, "last");
return NULL;
}
- list_log(l, l->lv_last, NULL, "last");
return l->lv_last;
}
@@ -308,7 +234,7 @@ static inline long tv_dict_len(const dict_T *d)
static inline long tv_dict_len(const dict_T *const d)
{
if (d == NULL) {
- return 0L;
+ return 0;
}
return (long)d->dv_hashtab.ht_used;
}
@@ -372,7 +298,7 @@ static inline uint8_t tv_blob_get(const blob_T *const b, int idx)
return ((uint8_t *)b->bv_ga.ga_data)[idx];
}
-static inline void tv_blob_set(blob_T *b, int idx, uint8_t c)
+static inline void tv_blob_set(blob_T *blob, int idx, uint8_t c)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
/// Store the byte `c` at index `idx` in the blob.
@@ -380,12 +306,12 @@ static inline void tv_blob_set(blob_T *b, int idx, uint8_t c)
/// @param[in] b Blob to index. Cannot be NULL.
/// @param[in] idx Index in a blob. Must be valid.
/// @param[in] c Value to store.
-static inline void tv_blob_set(blob_T *const b, int idx, uint8_t c)
+static inline void tv_blob_set(blob_T *const blob, int idx, uint8_t c)
{
- ((uint8_t *)b->bv_ga.ga_data)[idx] = c;
+ ((uint8_t *)blob->bv_ga.ga_data)[idx] = c;
}
-/// Initialize VimL object
+/// Initialize Vimscript object
///
/// Initializes to unlocked VAR_UNKNOWN object.
///
@@ -413,10 +339,9 @@ extern bool tv_in_free_unref_items;
/// @param[in] l List to iterate over.
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
-#define _TV_LIST_ITER_MOD(modifier, l, li, code) \
+#define TV_LIST_ITER_MOD(modifier, l, li, code) \
do { \
modifier list_T *const l_ = (l); \
- list_log(l_, NULL, NULL, "iter" #modifier); \
if (l_ != NULL) { \
for (modifier listitem_T *li = l_->lv_first; \
li != NULL; li = li->li_next) { \
@@ -434,7 +359,7 @@ extern bool tv_in_free_unref_items;
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
#define TV_LIST_ITER(l, li, code) \
- _TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens)
+ TV_LIST_ITER_MOD( , l, li, code) // NOLINT(whitespace/parens)
/// Iterate over a list
///
@@ -445,7 +370,7 @@ extern bool tv_in_free_unref_items;
/// @param li Name of the variable with current listitem_T entry.
/// @param code Cycle body.
#define TV_LIST_ITER_CONST(l, li, code) \
- _TV_LIST_ITER_MOD(const, l, li, code)
+ TV_LIST_ITER_MOD(const, l, li, code)
// Below macros are macros to avoid duplicating code for functionally identical
// const and non-const function variants.
@@ -498,7 +423,7 @@ static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f)
///
/// Raises an error if object is not number or floating-point.
///
-/// @param[in] tv VimL object to get value from.
+/// @param[in] tv Vimscript object to get value from.
/// @param[out] ret_f Location where resulting float is stored.
///
/// @return true in case of success, false if tv is not a number or float.
@@ -518,13 +443,15 @@ static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret
static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
REAL_FATTR_NONNULL_ALL REAL_FATTR_NONNULL_RET REAL_FATTR_PURE
- REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
+ REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE
+ FUNC_ATTR_NO_SANITIZE_ADDRESS;
/// Compute the `DictWatcher` address from a QUEUE node.
///
/// This only exists for .asan-blacklist (ASAN doesn't handle QUEUE_DATA pointer
/// arithmetic).
static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
+ FUNC_ATTR_NO_SANITIZE_ADDRESS
{
return QUEUE_DATA(q, DictWatcher, node);
}
@@ -557,16 +484,10 @@ static inline bool tv_is_func(const typval_T tv)
#ifdef UNIT_TESTING
// Do not use enum constants, see commit message.
-EXTERN const size_t kTVCstring INIT(= TV_CSTRING);
-EXTERN const size_t kTVTranslate INIT(= TV_TRANSLATE);
+EXTERN const size_t kTVCstring INIT( = TV_CSTRING);
+EXTERN const size_t kTVTranslate INIT( = TV_TRANSLATE);
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.h.generated.h"
#endif
-
-#define tv_get_bool tv_get_number
-#define tv_get_bool_chk tv_get_number_chk
-#define tv_dict_get_bool tv_dict_get_number_def
-
-#endif // NVIM_EVAL_TYPVAL_H
diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h
index 4615198441..c6bd11ccdb 100644
--- a/src/nvim/eval/typval_defs.h
+++ b/src/nvim/eval/typval_defs.h
@@ -1,16 +1,15 @@
-#ifndef NVIM_EVAL_TYPVAL_DEFS_H
-#define NVIM_EVAL_TYPVAL_DEFS_H
+#pragma once
#include <inttypes.h>
#include <limits.h>
-#include "nvim/garray.h"
-#include "nvim/hashtab.h"
+#include "nvim/garray_defs.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/lib/queue.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-/// Type used for VimL VAR_NUMBER values
+/// Type used for Vimscript VAR_NUMBER values
typedef int64_t varnumber_T;
typedef uint64_t uvarnumber_T;
@@ -100,7 +99,7 @@ typedef enum {
VAR_FIXED = 2, ///< Locked forever.
} VarLockStatus;
-/// VimL variable types, for use in typval_T.v_type
+/// Vimscript variable types, for use in typval_T.v_type
typedef enum {
VAR_UNKNOWN = 0, ///< Unknown (unspecified) value.
VAR_NUMBER, ///< Number, .v_number is used.
@@ -115,6 +114,19 @@ typedef enum {
VAR_BLOB, ///< Blob, .v_blob is used.
} VarType;
+/// Type values for type().
+enum {
+ VAR_TYPE_NUMBER = 0,
+ VAR_TYPE_STRING = 1,
+ VAR_TYPE_FUNC = 2,
+ VAR_TYPE_LIST = 3,
+ VAR_TYPE_DICT = 4,
+ VAR_TYPE_FLOAT = 5,
+ VAR_TYPE_BOOL = 6,
+ VAR_TYPE_SPECIAL = 7,
+ VAR_TYPE_BLOB = 10,
+};
+
/// Structure that holds an internal variable value
typedef struct {
VarType v_type; ///< Variable type.
@@ -206,7 +218,7 @@ typedef struct {
struct { \
typval_T di_tv; /* Structure that holds scope dictionary itself. */ \
uint8_t di_flags; /* Flags. */ \
- char_u di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \
+ char di_key[__VA_ARGS__]; /* Key value. */ /* NOLINT(runtime/arrays)*/ \
}
/// Structure to hold a scope dictionary
@@ -273,6 +285,12 @@ typedef struct {
linenr_T sc_lnum; ///< line number
} sctx_T;
+/// 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
+ uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT.
+} LastSet;
+
/// Maximum number of function arguments
enum { MAX_FUNC_ARGS = 20, };
/// Short variable name length
@@ -284,27 +302,27 @@ enum { FIXVAR_CNT = 12, };
typedef struct funccall_S funccall_T;
struct funccall_S {
- ufunc_T *func; ///< Function being called.
- int linenr; ///< Next line to be executed.
- int returned; ///< ":return" used.
- /// Fixed variables for arguments.
- TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fixvar[FIXVAR_CNT];
- dict_T l_vars; ///< l: local function variables.
- ScopeDictDictItem l_vars_var; ///< Variable for l: scope.
- dict_T l_avars; ///< a: argument variables.
- ScopeDictDictItem l_avars_var; ///< Variable for a: scope.
- list_T l_varlist; ///< List for a:000.
- listitem_T l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
- typval_T *rettv; ///< Return value.
- linenr_T breakpoint; ///< Next line with breakpoint or zero.
- int dbg_tick; ///< debug_tick when breakpoint was set.
- int level; ///< Top nesting level of executed function.
- proftime_T prof_child; ///< Time spent in a child.
- funccall_T *caller; ///< Calling function or NULL; or next funccal in
- ///< list pointed to by previous_funccal.
- int fc_refcount; ///< Number of user functions that reference this funccall.
- int fc_copyID; ///< CopyID used for garbage collection.
- garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
+ ufunc_T *fc_func; ///< Function being called.
+ int fc_linenr; ///< Next line to be executed.
+ int fc_returned; ///< ":return" used.
+ TV_DICTITEM_STRUCT(VAR_SHORT_LEN + 1) fc_fixvar[FIXVAR_CNT]; ///< Fixed variables for arguments.
+ dict_T fc_l_vars; ///< l: local function variables.
+ ScopeDictDictItem fc_l_vars_var; ///< Variable for l: scope.
+ dict_T fc_l_avars; ///< a: argument variables.
+ ScopeDictDictItem fc_l_avars_var; ///< Variable for a: scope.
+ list_T fc_l_varlist; ///< List for a:000.
+ listitem_T fc_l_listitems[MAX_FUNC_ARGS]; ///< List items for a:000.
+ typval_T *fc_rettv; ///< Return value.
+ linenr_T fc_breakpoint; ///< Next line with breakpoint or zero.
+ int fc_dbg_tick; ///< "debug_tick" when breakpoint was set.
+ int fc_level; ///< Top nesting level of executed function.
+ garray_T fc_defer; ///< Functions to be called on return.
+ proftime_T fc_prof_child; ///< Time spent in a child.
+ funccall_T *fc_caller; ///< Calling function or NULL; or next funccal in
+ ///< list pointed to by previous_funccal.
+ int fc_refcount; ///< Number of user functions that reference this funccall.
+ int fc_copyID; ///< CopyID used for garbage collection.
+ garray_T fc_ufuncs; ///< List of ufunc_T* which keep a reference to "fc_func".
};
/// Structure to hold info for a user function.
@@ -366,34 +384,3 @@ typedef struct list_stack_S {
list_T *list;
struct list_stack_S *prev;
} list_stack_T;
-
-/// Structure representing one list item, used for sort array.
-typedef struct {
- listitem_T *item; ///< Sorted list item.
- int idx; ///< Sorted list item index.
-} ListSortItem;
-
-typedef int (*ListSorter)(const void *, const void *);
-
-#ifdef LOG_LIST_ACTIONS
-/// List actions log entry
-typedef struct {
- uintptr_t l; ///< List log entry belongs to.
- uintptr_t li1; ///< First list item log entry belongs to, if applicable.
- uintptr_t li2; ///< Second list item log entry belongs to, if applicable.
- int len; ///< List length when log entry was created.
- const char *action; ///< Logged action.
-} ListLogEntry;
-
-typedef struct list_log ListLog;
-
-/// List actions log
-struct list_log {
- ListLog *next; ///< Next chunk or NULL.
- size_t capacity; ///< Number of entries in current chunk.
- size_t size; ///< Current chunk size.
- ListLogEntry entries[]; ///< Actual log entries.
-};
-#endif
-
-#endif // NVIM_EVAL_TYPVAL_DEFS_H
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index 6d29286a58..2e0b68d486 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -252,15 +252,13 @@
#include "nvim/func_attr.h"
#include "klib/kvec.h"
-// -V::1063
-
/// Dummy variable used because some macros need lvalue
///
/// Must not be written to, if needed one must check that address of the
/// macros argument is (not) equal to `&TYPVAL_ENCODE_NODICT_VAR`.
const dict_T *const TYPVAL_ENCODE_NODICT_VAR = NULL;
-static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
void *const val, int *const val_copyID,
const MPConvStack *const mpstack, const int copyID,
@@ -283,7 +281,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return NOTDONE in case of success, what to return in case of failure.
-static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
+static inline int TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, void *const val, int *const val_copyID,
const MPConvStack *const mpstack, const int copyID, const MPConvStackValType conv_type,
const char *const objname)
@@ -296,7 +294,7 @@ static inline int _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(
return NOTDONE;
}
-static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
MPConvStack *const mpstack, MPConvStackVal *const cur_mpsv,
typval_T *const tv, const int copyID,
@@ -320,7 +318,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return OK in case of success, FAIL in case of failure.
-static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
+static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack,
MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname)
{
@@ -347,8 +345,8 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_PARTIAL: {
partial_T *const pt = tv->vval.v_partial;
(void)pt;
- TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt))); // -V547
- _mp_push(*mpstack, ((MPConvStackVal) { // -V779
+ TYPVAL_ENCODE_CONV_FUNC_START(tv, (pt == NULL ? NULL : partial_name(pt)));
+ kvi_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvPartial,
.tv = tv,
.saved_copyID = copyID - 1,
@@ -367,11 +365,11 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
break;
}
const int saved_copyID = tv_list_copyid(tv->vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
- kMPConvList);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID,
+ kMPConvList);
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list));
assert(saved_copyID != copyID);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.type = kMPConvList,
.tv = tv,
.saved_copyID = saved_copyID,
@@ -382,7 +380,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
},
},
}));
- TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, _mp_last(*mpstack));
+ TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, kv_last(*mpstack));
break;
}
case VAR_BOOL:
@@ -396,7 +394,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
case VAR_SPECIAL:
switch (tv->vval.v_special) {
case kSpecialVarNull:
- TYPVAL_ENCODE_CONV_NIL(tv); // -V1037
+ TYPVAL_ENCODE_CONV_NIL(tv);
break;
}
break;
@@ -509,7 +507,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
if (is_string) {
TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
- } else { // -V523
+ } else {
TYPVAL_ENCODE_CONV_STRING(tv, buf, len);
}
xfree(buf);
@@ -520,12 +518,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
goto _convert_one_value_regular_dict;
}
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
- lv_copyID, copyID,
- kMPConvList);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list,
+ lv_copyID, copyID,
+ kMPConvList);
TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(val_di->di_tv.vval.v_list));
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvList,
.saved_copyID = saved_copyID,
@@ -544,8 +542,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
list_T *const val_list = val_di->di_tv.vval.v_list;
if (val_list == NULL || tv_list_len(val_list) == 0) {
- TYPVAL_ENCODE_CONV_EMPTY_DICT( // -V501
- tv, TYPVAL_ENCODE_NODICT_VAR);
+ TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR);
break;
}
TV_LIST_ITER_CONST(val_list, li, {
@@ -555,12 +552,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
});
const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list);
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
- kMPConvPairs);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID,
+ kMPConvPairs);
TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR,
tv_list_len(val_list));
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvPairs,
.saved_copyID = saved_copyID,
@@ -603,12 +600,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
_convert_one_value_regular_dict: {}
const int saved_copyID = tv->vval.v_dict->dv_copyID;
- _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
- kMPConvDict);
+ TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_dict, dv_copyID, copyID,
+ kMPConvDict);
TYPVAL_ENCODE_CONV_DICT_START(tv, tv->vval.v_dict,
tv->vval.v_dict->dv_hashtab.ht_used);
assert(saved_copyID != copyID);
- _mp_push(*mpstack, ((MPConvStackVal) {
+ kvi_push(*mpstack, ((MPConvStackVal) {
.tv = tv,
.type = kMPConvDict,
.saved_copyID = saved_copyID,
@@ -622,20 +619,20 @@ _convert_one_value_regular_dict: {}
},
}));
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, tv->vval.v_dict,
- _mp_last(*mpstack));
+ kv_last(*mpstack));
break;
}
case VAR_UNKNOWN:
- internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
+ internal_error(STR(TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()");
return FAIL;
}
typval_encode_stop_converting_one_item:
return OK;
// Prevent “unused label” warnings.
- goto typval_encode_stop_converting_one_item; // -V779
+ goto typval_encode_stop_converting_one_item;
}
-TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME,
typval_T *const tv, const char *const objname)
REAL_FATTR_NONNULL_ARG(2, 3) REAL_FATTR_WARN_UNUSED_RESULT;
@@ -649,29 +646,29 @@ TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
/// @param[in] objname Object name, used for error reporting.
///
/// @return OK in case of success, FAIL in case of failure.
-TYPVAL_ENCODE_SCOPE int _TYPVAL_ENCODE_ENCODE(
+TYPVAL_ENCODE_SCOPE int TYPVAL_ENCODE_ENCODE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, typval_T *const top_tv,
const char *const objname)
{
const int copyID = get_copyID();
MPConvStack mpstack;
- _mp_init(mpstack);
- if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
- NULL,
- top_tv, copyID, objname)
+ kvi_init(mpstack);
+ if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ NULL,
+ top_tv, copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
/// Label common for this and convert_one_value functions, used for escaping
/// from macros like TYPVAL_ENCODE_CONV_DICT_START.
typval_encode_stop_converting_one_item:
- while (_mp_size(mpstack)) {
- MPConvStackVal *cur_mpsv = &_mp_last(mpstack);
+ while (kv_size(mpstack)) {
+ MPConvStackVal *cur_mpsv = &kv_last(mpstack);
typval_T *tv = NULL;
switch (cur_mpsv->type) {
case kMPConvDict: {
if (!cur_mpsv->data.d.todo) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
cur_mpsv->data.d.dict->dv_copyID = cur_mpsv->saved_copyID;
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, *cur_mpsv->data.d.dictp);
continue;
@@ -695,7 +692,7 @@ typval_encode_stop_converting_one_item:
}
case kMPConvList:
if (cur_mpsv->data.l.li == NULL) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv);
continue;
@@ -709,7 +706,7 @@ typval_encode_stop_converting_one_item:
break;
case kMPConvPairs: {
if (cur_mpsv->data.l.li == NULL) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID);
TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR);
continue;
@@ -722,8 +719,8 @@ typval_encode_stop_converting_one_item:
TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK(encode_vim_to__error_ret,
*TV_LIST_ITEM_TV(tv_list_first(kv_pair)));
if (
- _TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv,
- TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname)
+ TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv,
+ TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
@@ -745,7 +742,7 @@ typval_encode_stop_converting_one_item:
cur_mpsv->data.p.stage = kMPConvPartialSelf;
if (pt != NULL && pt->pt_argc > 0) {
TYPVAL_ENCODE_CONV_LIST_START(NULL, pt->pt_argc);
- _mp_push(mpstack, ((MPConvStackVal) {
+ kvi_push(mpstack, ((MPConvStackVal) {
.type = kMPConvPartialList,
.tv = NULL,
.saved_copyID = copyID - 1,
@@ -769,10 +766,10 @@ typval_encode_stop_converting_one_item:
continue;
}
const int saved_copyID = dict->dv_copyID;
- const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME,
- dict, &dict->dv_copyID,
- &mpstack, copyID, kMPConvDict,
- objname);
+ const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME,
+ dict, &dict->dv_copyID,
+ &mpstack, copyID, kMPConvDict,
+ objname);
if (te_csr_ret != NOTDONE) {
if (te_csr_ret == FAIL) {
goto encode_vim_to__error_ret;
@@ -783,7 +780,7 @@ typval_encode_stop_converting_one_item:
TYPVAL_ENCODE_CONV_DICT_START(NULL, pt->pt_dict,
dict->dv_hashtab.ht_used);
assert(saved_copyID != copyID && saved_copyID != copyID - 1);
- _mp_push(mpstack, ((MPConvStackVal) {
+ kvi_push(mpstack, ((MPConvStackVal) {
.type = kMPConvDict,
.tv = NULL,
.saved_copyID = saved_copyID,
@@ -797,7 +794,7 @@ typval_encode_stop_converting_one_item:
},
}));
TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(NULL, pt->pt_dict,
- _mp_last(mpstack));
+ kv_last(mpstack));
} else {
TYPVAL_ENCODE_CONV_FUNC_BEFORE_SELF(tv, -1);
}
@@ -805,14 +802,14 @@ typval_encode_stop_converting_one_item:
}
case kMPConvPartialEnd:
TYPVAL_ENCODE_CONV_FUNC_END(tv);
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
break;
}
continue;
}
case kMPConvPartialList:
if (!cur_mpsv->data.a.todo) {
- (void)_mp_pop(mpstack);
+ (void)kv_pop(mpstack);
TYPVAL_ENCODE_CONV_LIST_END(NULL);
continue;
} else if (cur_mpsv->data.a.argv != cur_mpsv->data.a.arg) {
@@ -823,17 +820,17 @@ typval_encode_stop_converting_one_item:
break;
}
assert(tv != NULL);
- if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
- cur_mpsv, tv, copyID, objname)
+ if (TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack,
+ cur_mpsv, tv, copyID, objname)
== FAIL) {
goto encode_vim_to__error_ret;
}
}
- _mp_destroy(mpstack);
+ kvi_destroy(mpstack);
return OK;
encode_vim_to__error_ret:
- _mp_destroy(mpstack);
+ kvi_destroy(mpstack);
return FAIL;
// Prevent “unused label” warnings.
- goto typval_encode_stop_converting_one_item; // -V779
+ goto typval_encode_stop_converting_one_item;
}
diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h
index 2f19144da3..a6e0bd4b2b 100644
--- a/src/nvim/eval/typval_encode.h
+++ b/src/nvim/eval/typval_encode.h
@@ -2,8 +2,7 @@
///
/// Contains common definitions for eval/typval_encode.c.h. Most of time should
/// not be included directly.
-#ifndef NVIM_EVAL_TYPVAL_ENCODE_H
-#define NVIM_EVAL_TYPVAL_ENCODE_H
+#pragma once
#include <assert.h>
#include <inttypes.h>
@@ -11,7 +10,7 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/func_attr.h"
/// Type of the stack entry
@@ -30,7 +29,7 @@ typedef enum {
kMPConvPartialEnd, ///< Already converted everything.
} MPConvPartialStage;
-/// Structure representing current VimL to messagepack conversion state
+/// Structure representing current Vimscript to messagepack conversion state
typedef struct {
MPConvStackValType type; ///< Type of the stack entry.
typval_T *tv; ///< Currently converted typval_T.
@@ -60,17 +59,9 @@ typedef struct {
} data; ///< Data to convert.
} MPConvStackVal;
-/// Stack used to convert VimL values to messagepack.
+/// Stack used to convert Vimscript values to messagepack.
typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack;
-// Defines for MPConvStack
-#define _mp_size kv_size
-#define _mp_init kvi_init
-#define _mp_destroy kvi_destroy
-#define _mp_push kvi_push
-#define _mp_pop kv_pop
-#define _mp_last kv_last
-
static inline size_t tv_strlen(const typval_T *tv)
REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT
REAL_FATTR_NONNULL_ALL;
@@ -96,21 +87,21 @@ static inline size_t tv_strlen(const typval_T *const tv)
/// copyID (variable) it is set to copyID.
/// @param[in] copyID CopyID used by the caller.
/// @param conv_type Type of the conversion, @see MPConvStackValType.
-#define _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \
- conv_type) \
+#define TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val, copyID_attr, copyID, \
+ conv_type) \
do { \
- const int te_csr_ret = _TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \
- (val), &(val)->copyID_attr, mpstack, \
- copyID, conv_type, objname); \
+ const int te_csr_ret = TYPVAL_ENCODE_CHECK_SELF_REFERENCE(TYPVAL_ENCODE_FIRST_ARG_NAME, \
+ (val), &(val)->copyID_attr, mpstack, \
+ copyID, conv_type, objname); \
if (te_csr_ret != NOTDONE) { \
return te_csr_ret; \
} \
} while (0)
-#define _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \
+#define TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf) \
pref##name##suf
-#define _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \
- _TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf)
+#define TYPVAL_ENCODE_FUNC_NAME_INNER(pref, name, suf) \
+ TYPVAL_ENCODE_FUNC_NAME_INNER_2(pref, name, suf)
/// Construct function name, possibly using macros
///
@@ -122,23 +113,21 @@ static inline size_t tv_strlen(const typval_T *const tv)
/// @param[in] suf Suffix.
///
/// @return Concat: pref + #TYPVAL_ENCODE_NAME + suf.
-#define _TYPVAL_ENCODE_FUNC_NAME(pref, suf) \
- _TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf)
+#define TYPVAL_ENCODE_FUNC_NAME(pref, suf) \
+ TYPVAL_ENCODE_FUNC_NAME_INNER(pref, TYPVAL_ENCODE_NAME, suf)
/// Self reference checker function name
-#define _TYPVAL_ENCODE_CHECK_SELF_REFERENCE \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference)
+#define TYPVAL_ENCODE_CHECK_SELF_REFERENCE \
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _check_self_reference)
/// Entry point function name
-#define _TYPVAL_ENCODE_ENCODE \
- _TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, )
+#define TYPVAL_ENCODE_ENCODE \
+ TYPVAL_ENCODE_FUNC_NAME(encode_vim_to_, )
/// Name of the …convert_one_value function
-#define _TYPVAL_ENCODE_CONVERT_ONE_VALUE \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value)
+#define TYPVAL_ENCODE_CONVERT_ONE_VALUE \
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _convert_one_value)
/// Name of the dummy const dict_T *const variable
#define TYPVAL_ENCODE_NODICT_VAR \
- _TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var)
-
-#endif // NVIM_EVAL_TYPVAL_ENCODE_H
+ TYPVAL_ENCODE_FUNC_NAME(_typval_encode_, _nodict_var)
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 6c6dc3fa43..23b3c4e1b2 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -1,20 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// User defined function support
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
@@ -26,18 +23,20 @@
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/hashtab.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
#include "nvim/profile.h"
@@ -45,14 +44,21 @@
#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/userfunc.c.generated.h"
#endif
+/// structure used as item in "fc_defer"
+typedef struct {
+ char *dr_name; ///< function name, allocated
+ typval_T dr_argvars[MAX_FUNC_ARGS + 1];
+ int dr_argcount;
+} defer_T;
+
static hashtab_T func_hashtab;
// Used by get_func_tv()
@@ -65,12 +71,21 @@ static funccall_T *current_funccal = NULL;
// item in it is still being used.
static funccall_T *previous_funccal = NULL;
-static char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it");
-static char *e_funcdict = N_("E717: Dictionary entry already exists");
-static char *e_funcref = N_("E718: Funcref required");
-static char *e_nofunc = N_("E130: Unknown function: %s");
-static char e_no_white_space_allowed_before_str_str[]
+static const char *e_unknown_function_str = N_("E117: Unknown function: %s");
+static const char *e_funcexts = N_("E122: Function %s already exists, add ! to replace it");
+static const char *e_funcdict = N_("E717: Dictionary entry already exists");
+static const char *e_funcref = N_("E718: Funcref required");
+static const char *e_nofunc = N_("E130: Unknown function: %s");
+static const char e_function_list_was_modified[]
+ = N_("E454: Function list was modified");
+static const char e_function_nesting_too_deep[]
+ = N_("E1058: Function nesting too deep");
+static const char e_no_white_space_allowed_before_str_str[]
= N_("E1068: No white space allowed before '%s': %s");
+static const char e_missing_heredoc_end_marker_str[]
+ = N_("E1145: Missing heredoc end marker: %s");
+static const char e_cannot_use_partial_with_dictionary_for_defer[]
+ = N_("E1300: Cannot use a partial with dictionary for :defer");
void func_init(void)
{
@@ -90,7 +105,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
bool mustend = false;
char *arg = *argp;
char *p = arg;
- char_u c;
+ uint8_t c;
int i;
if (newargs != NULL) {
@@ -128,7 +143,7 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
}
if (newargs != NULL) {
ga_grow(newargs, 1);
- c = (char_u)(*p);
+ c = (uint8_t)(*p);
*p = NUL;
arg = xstrdup(arg);
@@ -152,14 +167,14 @@ static int get_function_args(char **argp, char endchar, garray_T *newargs, int *
p = skipwhite(p) + 1;
p = skipwhite(p);
char *expr = p;
- if (eval1(&p, &rettv, false) != FAIL) {
+ if (eval1(&p, &rettv, NULL) != FAIL) {
ga_grow(default_args, 1);
// trim trailing whitespace
while (p > expr && ascii_iswhite(p[-1])) {
p--;
}
- c = (char_u)(*p);
+ c = (uint8_t)(*p);
*p = NUL;
expr = xstrdup(expr);
((char **)(default_args->ga_data))[default_args->ga_len] = expr;
@@ -223,9 +238,9 @@ static void register_closure(ufunc_T *fp)
funccal_unref(fp->uf_scoped, fp, false);
fp->uf_scoped = current_funccal;
current_funccal->fc_refcount++;
- ga_grow(&current_funccal->fc_funcs, 1);
- ((ufunc_T **)current_funccal->fc_funcs.ga_data)
- [current_funccal->fc_funcs.ga_len++] = fp;
+ ga_grow(&current_funccal->fc_ufuncs, 1);
+ ((ufunc_T **)current_funccal->fc_ufuncs.ga_data)
+ [current_funccal->fc_ufuncs.ga_len++] = fp;
}
/// @return a name for a lambda. Returned in static memory.
@@ -252,22 +267,22 @@ static void set_ufunc_name(ufunc_T *fp, char *name)
/// Parse a lambda expression and get a Funcref from "*arg".
///
/// @return OK or FAIL. Returns NOTDONE for dict or {expr}.
-int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
+int get_lambda_tv(char **arg, typval_T *rettv, evalarg_T *evalarg)
{
+ const bool evaluate = evalarg != NULL && (evalarg->eval_flags & EVAL_EVALUATE);
garray_T newargs = GA_EMPTY_INIT_VALUE;
garray_T *pnewargs;
ufunc_T *fp = NULL;
partial_T *pt = NULL;
int varargs;
- int ret;
- char *start = skipwhite(*arg + 1);
- char *s, *e;
bool *old_eval_lavars = eval_lavars_used;
bool eval_lavars = false;
+ char *tofree = NULL;
// First, check if this is a lambda expression. "->" must exists.
- ret = get_function_args(&start, '-', NULL, NULL, NULL, true);
- if (ret == FAIL || *start != '>') {
+ char *s = skipwhite(*arg + 1);
+ int ret = get_function_args(&s, '-', NULL, NULL, NULL, true);
+ if (ret == FAIL || *s != '>') {
return NOTDONE;
}
@@ -290,12 +305,18 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
// Get the start and the end of the expression.
*arg = skipwhite((*arg) + 1);
- s = *arg;
- ret = skip_expr(arg);
+ char *start = *arg;
+ ret = skip_expr(arg, evalarg);
+ char *end = *arg;
if (ret == FAIL) {
goto errret;
}
- e = *arg;
+ if (evalarg != NULL) {
+ // avoid that the expression gets freed when another line break follows
+ tofree = evalarg->eval_tofree;
+ evalarg->eval_tofree = NULL;
+ }
+
*arg = skipwhite(*arg);
if (**arg != '}') {
semsg(_("E451: Expected }: %s"), *arg);
@@ -317,11 +338,11 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
ga_grow(&newlines, 1);
// Add "return " before the expression.
- size_t len = (size_t)(7 + e - s + 1);
+ size_t len = (size_t)(7 + end - start + 1);
p = xmalloc(len);
((char **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
- xstrlcpy(p + 7, s, (size_t)(e - s) + 1);
+ xstrlcpy(p + 7, start, (size_t)(end - start) + 1);
if (strstr(p + 7, "a:") == NULL) {
// No a: variables are used for sure.
flags |= FC_NOARGS;
@@ -359,12 +380,22 @@ int get_lambda_tv(char **arg, typval_T *rettv, bool evaluate)
}
eval_lavars_used = old_eval_lavars;
+ if (evalarg != NULL && evalarg->eval_tofree == NULL) {
+ evalarg->eval_tofree = tofree;
+ } else {
+ xfree(tofree);
+ }
return OK;
errret:
ga_clear_strings(&newargs);
xfree(fp);
xfree(pt);
+ if (evalarg != NULL && evalarg->eval_tofree == NULL) {
+ evalarg->eval_tofree = tofree;
+ } else {
+ xfree(tofree);
+ }
eval_lavars_used = old_eval_lavars;
return FAIL;
}
@@ -382,9 +413,11 @@ errret:
/// is not needed.
/// @param[in] no_autoload If true, do not source autoload scripts if function
/// was not found.
+/// @param[out] found_var If not NULL and a variable was found set it to true.
///
/// @return name of the function.
-char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload)
+char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, bool no_autoload,
+ bool *found_var)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
if (partialp != NULL) {
@@ -392,18 +425,25 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b
}
dictitem_T *const v = find_var(name, (size_t)(*lenp), NULL, no_autoload);
- if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
- if (v->di_tv.vval.v_string == NULL) { // just in case
+ if (v == NULL) {
+ return (char *)name;
+ }
+ typval_T *const tv = &v->di_tv;
+ if (found_var != NULL) {
+ *found_var = true;
+ }
+
+ if (tv->v_type == VAR_FUNC) {
+ if (tv->vval.v_string == NULL) { // just in case
*lenp = 0;
return "";
}
- *lenp = (int)strlen(v->di_tv.vval.v_string);
- return v->di_tv.vval.v_string;
+ *lenp = (int)strlen(tv->vval.v_string);
+ return tv->vval.v_string;
}
- if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) {
- partial_T *const pt = v->di_tv.vval.v_partial;
-
+ if (tv->v_type == VAR_PARTIAL) {
+ partial_T *const pt = tv->vval.v_partial;
if (pt == NULL) { // just in case
*lenp = 0;
return "";
@@ -421,63 +461,82 @@ char *deref_func_name(const char *name, int *lenp, partial_T **const partialp, b
/// Give an error message with a function name. Handle <SNR> things.
///
-/// @param ermsg must be passed without translation (use N_() instead of _()).
+/// @param errmsg must be passed without translation (use N_() instead of _()).
/// @param name function name
-void emsg_funcname(char *ermsg, const char *name)
+void emsg_funcname(const char *errmsg, const char *name)
{
- char *p;
+ char *p = (char *)name;
- if ((uint8_t)(*name) == K_SPECIAL) {
+ if ((uint8_t)name[0] == K_SPECIAL && name[1] != NUL && name[2] != NUL) {
p = concat_str("<SNR>", name + 3);
- } else {
- p = (char *)name;
}
- semsg(_(ermsg), p);
+ semsg(_(errmsg), p);
if (p != name) {
xfree(p);
}
}
-/// Allocate a variable for the result of a function.
-///
-/// @param name name of the function
-/// @param len length of "name" or -1 to use strlen()
-/// @param arg argument, pointing to the '('
-/// @param funcexe various values
-///
-/// @return OK or FAIL.
-int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_T *funcexe)
+/// Get function arguments at "*arg" and advance it.
+/// Return them in "*argvars[MAX_FUNC_ARGS + 1]" and the count in "argcount".
+/// On failure FAIL is returned but the "argvars[argcount]" are still set.
+static int get_func_arguments(char **arg, evalarg_T *const evalarg, int partial_argc,
+ typval_T *argvars, int *argcount)
{
- char *argp;
+ char *argp = *arg;
int ret = OK;
- typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
- int argcount = 0; // number of arguments found
// Get the arguments.
- argp = *arg;
- while (argcount < MAX_FUNC_ARGS
- - (funcexe->fe_partial == NULL ? 0 : funcexe->fe_partial->pt_argc)) {
+ while (*argcount < MAX_FUNC_ARGS - partial_argc) {
argp = skipwhite(argp + 1); // skip the '(' or ','
+
if (*argp == ')' || *argp == ',' || *argp == NUL) {
break;
}
- if (eval1(&argp, &argvars[argcount], funcexe->fe_evaluate) == FAIL) {
+ if (eval1(&argp, &argvars[*argcount], evalarg) == FAIL) {
ret = FAIL;
break;
}
- argcount++;
+ (*argcount)++;
if (*argp != ',') {
break;
}
}
+
+ argp = skipwhite(argp);
if (*argp == ')') {
argp++;
} else {
ret = FAIL;
}
+ *arg = argp;
+ return ret;
+}
+/// Call a function and put the result in "rettv".
+///
+/// @param name name of the function
+/// @param len length of "name" or -1 to use strlen()
+/// @param arg argument, pointing to the '('
+/// @param funcexe various values
+///
+/// @return OK or FAIL.
+int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, evalarg_T *const evalarg,
+ funcexe_T *funcexe)
+{
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int argcount = 0; // number of arguments found
+ const bool evaluate = evalarg == NULL ? false : (evalarg->eval_flags & EVAL_EVALUATE);
+
+ char *argp = *arg;
+ int ret = get_func_arguments(&argp, evalarg,
+ (funcexe->fe_partial == NULL
+ ? 0
+ : funcexe->fe_partial->pt_argc),
+ argvars, &argcount);
+
+ assert(ret == OK || ret == FAIL); // suppress clang false positive
if (ret == OK) {
int i = 0;
@@ -495,7 +554,7 @@ int get_func_tv(const char *name, int len, typval_T *rettv, char **arg, funcexe_
ret = call_func(name, len, rettv, argcount, argvars, funcexe);
funcargs.ga_len -= i;
- } else if (!aborting()) {
+ } else if (!aborting() && evaluate) {
if (argcount == MAX_FUNC_ARGS) {
emsg_funcname(N_("E740: Too many arguments for function %s"), name);
} else {
@@ -594,24 +653,28 @@ ufunc_T *find_func(const char *name)
/// Copy the function name of "fp" to buffer "buf".
/// "buf" must be able to hold the function name plus three bytes.
/// Takes care of script-local function names.
-static void cat_func_name(char *buf, ufunc_T *fp)
+static void cat_func_name(char *buf, size_t buflen, ufunc_T *fp)
{
- if ((uint8_t)fp->uf_name[0] == K_SPECIAL) {
- STRCPY(buf, "<SNR>");
- STRCAT(buf, fp->uf_name + 3);
+ int len = -1;
+ size_t uflen = strlen(fp->uf_name);
+ assert(uflen > 0);
+
+ if ((uint8_t)fp->uf_name[0] == K_SPECIAL && uflen > 3) {
+ len = snprintf(buf, buflen, "<SNR>%s", fp->uf_name + 3);
} else {
- STRCPY(buf, fp->uf_name);
+ len = snprintf(buf, buflen, "%s", fp->uf_name);
}
+
+ (void)len; // Avoid unused warning on release builds
+ assert(len > 0);
}
/// Add a number variable "name" to dict "dp" with value "nr".
static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
{
-#ifndef __clang_analyzer__
STRCPY(v->di_key, name);
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&dp->dv_hashtab, (char *)v->di_key);
+ hash_add(&dp->dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_NUMBER;
v->di_tv.v_lock = VAR_FIXED;
v->di_tv.vval.v_number = nr;
@@ -620,8 +683,8 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr)
/// Free "fc"
static void free_funccal(funccall_T *fc)
{
- for (int i = 0; i < fc->fc_funcs.ga_len; i++) {
- ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i];
+ for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) {
+ ufunc_T *fp = ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i];
// When garbage collecting a funccall_T may be freed before the
// function that references it, clear its uf_scoped field.
@@ -631,9 +694,9 @@ static void free_funccal(funccall_T *fc)
fp->uf_scoped = NULL;
}
}
- ga_clear(&fc->fc_funcs);
+ ga_clear(&fc->fc_ufuncs);
- func_ptr_unref(fc->func);
+ func_ptr_unref(fc->fc_func);
xfree(fc);
}
@@ -643,13 +706,13 @@ static void free_funccal(funccall_T *fc)
static void free_funccal_contents(funccall_T *fc)
{
// Free all l: variables.
- vars_clear(&fc->l_vars.dv_hashtab);
+ vars_clear(&fc->fc_l_vars.dv_hashtab);
// Free all a: variables.
- vars_clear(&fc->l_avars.dv_hashtab);
+ vars_clear(&fc->fc_l_avars.dv_hashtab);
// Free the a:000 variables.
- TV_LIST_ITER(&fc->l_varlist, li, {
+ TV_LIST_ITER(&fc->fc_l_varlist, li, {
tv_clear(TV_LIST_ITEM_TV(li));
});
@@ -663,11 +726,11 @@ static void cleanup_function_call(funccall_T *fc)
bool may_free_fc = fc->fc_refcount <= 0;
bool free_fc = true;
- current_funccal = fc->caller;
+ current_funccal = fc->fc_caller;
// Free all l: variables if not referred.
- if (may_free_fc && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT) {
- vars_clear(&fc->l_vars.dv_hashtab);
+ if (may_free_fc && fc->fc_l_vars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear(&fc->fc_l_vars.dv_hashtab);
} else {
free_fc = false;
}
@@ -675,25 +738,25 @@ static void cleanup_function_call(funccall_T *fc)
// If the a:000 list and the l: and a: dicts are not referenced and
// there is no closure using it, we can free the funccall_T and what's
// in it.
- if (may_free_fc && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) {
- vars_clear_ext(&fc->l_avars.dv_hashtab, false);
+ if (may_free_fc && fc->fc_l_avars.dv_refcount == DO_NOT_FREE_CNT) {
+ vars_clear_ext(&fc->fc_l_avars.dv_hashtab, false);
} else {
free_fc = false;
// Make a copy of the a: variables, since we didn't do that above.
- TV_DICT_ITER(&fc->l_avars, di, {
+ TV_DICT_ITER(&fc->fc_l_avars, di, {
tv_copy(&di->di_tv, &di->di_tv);
});
}
- if (may_free_fc && fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ if (may_free_fc && fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated)
== DO_NOT_FREE_CNT) {
- fc->l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
+ fc->fc_l_varlist.lv_first = NULL; // NOLINT(runtime/deprecated)
} else {
free_fc = false;
// Make a copy of the a:000 items, since we didn't do that above.
- TV_LIST_ITER(&fc->l_varlist, li, {
+ TV_LIST_ITER(&fc->fc_l_varlist, li, {
tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li));
});
}
@@ -706,7 +769,7 @@ static void cleanup_function_call(funccall_T *fc)
// "fc" is still in use. This can happen when returning "a:000",
// assigning "l:" to a global variable or defining a closure.
// Link "fc" in the list for garbage collection later.
- fc->caller = previous_funccal;
+ fc->fc_caller = previous_funccal;
previous_funccal = fc;
if (want_garbage_collect) {
@@ -729,26 +792,23 @@ static void cleanup_function_call(funccall_T *fc)
/// @param[in] force When true, we are exiting.
static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
{
- funccall_T **pfc;
- int i;
-
if (fc == NULL) {
return;
}
fc->fc_refcount--;
if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) {
- for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) {
+ for (funccall_T **pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->fc_caller) {
if (fc == *pfc) {
- *pfc = fc->caller;
+ *pfc = fc->fc_caller;
free_funccal_contents(fc);
return;
}
}
}
- for (i = 0; i < fc->fc_funcs.ga_len; i++) {
- if (((ufunc_T **)(fc->fc_funcs.ga_data))[i] == fp) {
- ((ufunc_T **)(fc->fc_funcs.ga_data))[i] = NULL;
+ for (int i = 0; i < fc->fc_ufuncs.ga_len; i++) {
+ if (((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] == fp) {
+ ((ufunc_T **)(fc->fc_ufuncs.ga_data))[i] = NULL;
}
}
}
@@ -759,7 +819,7 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force)
/// @return true if the entry was deleted, false if it wasn't found.
static bool func_remove(ufunc_T *fp)
{
- hashitem_T *hi = hash_find(&func_hashtab, (char *)UF2HIKEY(fp));
+ hashitem_T *hi = hash_find(&func_hashtab, UF2HIKEY(fp));
if (HASHITEM_EMPTY(hi)) {
return false;
}
@@ -824,6 +884,27 @@ static void func_clear_free(ufunc_T *fp, bool force)
func_free(fp);
}
+/// Allocate a funccall_T, link it in current_funccal and fill in "fp" and "rettv".
+/// Must be followed by one call to remove_funccal() or cleanup_function_call().
+funccall_T *create_funccal(ufunc_T *fp, typval_T *rettv)
+{
+ funccall_T *fc = xcalloc(1, sizeof(funccall_T));
+ fc->fc_caller = current_funccal;
+ current_funccal = fc;
+ fc->fc_func = fp;
+ func_ptr_ref(fp);
+ fc->fc_rettv = rettv;
+ return fc;
+}
+
+/// Restore current_funccal.
+void remove_funccal(void)
+{
+ funccall_T *fc = current_funccal;
+ current_funccal = fc->fc_caller;
+ free_funccal(fc);
+}
+
/// Call a user function
///
/// @param fp Function to call.
@@ -838,11 +919,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
{
bool using_sandbox = false;
- funccall_T *fc;
int save_did_emsg;
static int depth = 0;
dictitem_T *v;
- int fixvar_idx = 0; // index in fixvar[]
+ int fixvar_idx = 0; // index in fc_fixvar[]
int ai;
bool islambda = false;
char numbuf[NUMBUFLEN];
@@ -873,40 +953,32 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// check for CTRL-C hit
line_breakcheck();
// prepare the funccall_T structure
- fc = xcalloc(1, sizeof(funccall_T));
- fc->caller = current_funccal;
- current_funccal = fc;
- fc->func = fp;
- fc->rettv = rettv;
- fc->level = ex_nesting_level;
+ funccall_T *fc = create_funccal(fp, rettv);
+ fc->fc_level = ex_nesting_level;
// Check if this function has a breakpoint.
- fc->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, (linenr_T)0);
- fc->dbg_tick = debug_tick;
-
+ fc->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, 0);
+ fc->fc_dbg_tick = debug_tick;
// Set up fields for closure.
- ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
- func_ptr_ref(fp);
+ ga_init(&fc->fc_ufuncs, sizeof(ufunc_T *), 1);
if (strncmp(fp->uf_name, "<lambda>", 8) == 0) {
islambda = true;
}
- // Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables
+ // Note about using fc->fc_fixvar[]: This is an array of FIXVAR_CNT variables
// with names up to VAR_SHORT_LEN long. This avoids having to alloc/free
// each argument variable and saves a lot of time.
//
// Init l: variables.
- init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE);
+ init_var_dict(&fc->fc_l_vars, &fc->fc_l_vars_var, VAR_DEF_SCOPE);
if (selfdict != NULL) {
// Set l:self to "selfdict". Use "name" to avoid a warning from
// some compiler that checks the destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
name = (char *)v->di_key;
STRCPY(name, "self");
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_DICT;
v->di_tv.v_lock = VAR_UNLOCKED;
v->di_tv.vval.v_dict = selfdict;
@@ -916,38 +988,36 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Init a: variables, unless none found (in lambda).
// Set a:0 to "argcount" less number of named arguments, if >= 0.
// Set a:000 to a list with room for the "..." arguments.
- init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
+ init_var_dict(&fc->fc_l_avars, &fc->fc_l_avars_var, VAR_SCOPE);
if ((fp->uf_flags & FC_NOARGS) == 0) {
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++], "0",
(varnumber_T)(argcount >= fp->uf_args.ga_len
? argcount - fp->uf_args.ga_len : 0));
}
- fc->l_avars.dv_lock = VAR_FIXED;
+ fc->fc_l_avars.dv_lock = VAR_FIXED;
if ((fp->uf_flags & FC_NOARGS) == 0) {
// Use "name" to avoid a warning from some compiler that checks the
// destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
-#ifndef __clang_analyzer__
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
name = (char *)v->di_key;
STRCPY(name, "000");
-#endif
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key);
v->di_tv.v_type = VAR_LIST;
v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
+ v->di_tv.vval.v_list = &fc->fc_l_varlist;
}
- tv_list_init_static(&fc->l_varlist);
- tv_list_set_lock(&fc->l_varlist, VAR_FIXED);
+ tv_list_init_static(&fc->fc_l_varlist);
+ tv_list_set_lock(&fc->fc_l_varlist, VAR_FIXED);
// Set a:firstline to "firstline" and a:lastline to "lastline".
// Set a:name to named arguments.
// Set a:N to the "..." arguments.
// Skipped when no a: variables used (in lambda).
if ((fp->uf_flags & FC_NOARGS) == 0) {
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++],
"firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ add_nr_var(&fc->fc_l_avars, (dictitem_T *)&fc->fc_fixvar[fixvar_idx++],
"lastline", (varnumber_T)lastline);
}
bool default_arg_err = false;
@@ -973,7 +1043,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
default_expr = ((char **)(fp->uf_def_args.ga_data))
[ai + fp->uf_def_args.ga_len];
- if (eval1(&default_expr, &def_rettv, true) == FAIL) {
+ if (eval1(&default_expr, &def_rettv, &EVALARG_EVALUATE) == FAIL) {
default_arg_err = true;
break;
}
@@ -988,7 +1058,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
name = numbuf;
}
if (fixvar_idx < FIXVAR_CNT && strlen(name) <= VAR_SHORT_LEN) {
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ v = (dictitem_T *)&fc->fc_fixvar[fixvar_idx++];
v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
} else {
v = xmalloc(sizeof(dictitem_T) + strlen(name));
@@ -1010,17 +1080,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// Named arguments can be accessed without the "a:" prefix in lambda
// expressions. Add to the l: dict.
tv_copy(&v->di_tv, &v->di_tv);
- hash_add(&fc->l_vars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_vars.dv_hashtab, v->di_key);
} else {
- hash_add(&fc->l_avars.dv_hashtab, (char *)v->di_key);
+ hash_add(&fc->fc_l_avars.dv_hashtab, v->di_key);
}
if (ai >= 0 && ai < MAX_FUNC_ARGS) {
- listitem_T *li = &fc->l_listitems[ai];
+ listitem_T *li = &fc->fc_l_listitems[ai];
*TV_LIST_ITEM_TV(li) = argvars[i];
TV_LIST_ITEM_TV(li)->v_lock = VAR_FIXED;
- tv_list_append(&fc->l_varlist, li);
+ tv_list_append(&fc->fc_l_varlist, li);
}
}
@@ -1037,7 +1107,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
no_wait_return++;
verbose_enter_scroll();
- smsg(_("calling %s"), SOURCING_NAME);
+ smsg(0, _("calling %s"), SOURCING_NAME);
if (p_verbose >= 14) {
msg_puts("(");
for (int i = 0; i < argcount; i++) {
@@ -1045,7 +1115,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
msg_puts(", ");
}
if (argvars[i].v_type == VAR_NUMBER) {
- msg_outnum((long)argvars[i].vval.v_number);
+ msg_outnum((int)argvars[i].vval.v_number);
} else {
// Do not want errors such as E724 here.
emsg_off++;
@@ -1075,7 +1145,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
bool func_not_yet_profiling_but_should =
do_profiling_yes
- && !fp->uf_profiling && has_profiling(false, (char *)fp->uf_name, NULL);
+ && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL);
if (func_not_yet_profiling_but_should) {
started_profiling = true;
@@ -1085,7 +1155,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
bool func_or_func_caller_profiling =
do_profiling_yes
&& (fp->uf_profiling
- || (fc->caller != NULL && fc->caller->func->uf_profiling));
+ || (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling));
if (func_or_func_caller_profiling) {
fp->uf_tm_count++;
@@ -1110,7 +1180,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// A Lambda always has the command "return {expr}". It is much faster
// to evaluate {expr} directly.
ex_nesting_level++;
- (void)eval1(&p, rettv, true);
+ (void)eval1(&p, rettv, &EVALARG_EVALUATE);
ex_nesting_level--;
} else {
// call do_cmdline() to execute the lines
@@ -1118,6 +1188,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
}
+ // Invoke functions added with ":defer".
+ handle_defer_one(current_funccal);
+
RedrawingDisabled--;
// when the function was aborted because of an error, return -1
@@ -1130,15 +1203,15 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
if (func_or_func_caller_profiling) {
call_start = profile_end(call_start);
- call_start = profile_sub_wait(wait_start, call_start); // -V614
+ call_start = profile_sub_wait(wait_start, call_start);
fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start);
fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start,
fp->uf_tm_children);
- if (fc->caller != NULL && fc->caller->func->uf_profiling) {
- fc->caller->func->uf_tm_children =
- profile_add(fc->caller->func->uf_tm_children, call_start);
- fc->caller->func->uf_tml_children =
- profile_add(fc->caller->func->uf_tml_children, call_start);
+ if (fc->fc_caller != NULL && fc->fc_caller->fc_func->uf_profiling) {
+ fc->fc_caller->fc_func->uf_tm_children =
+ profile_add(fc->fc_caller->fc_func->uf_tm_children, call_start);
+ fc->fc_caller->fc_func->uf_tml_children =
+ profile_add(fc->fc_caller->fc_func->uf_tml_children, call_start);
}
if (started_profiling) {
// make a ":profdel func" stop profiling the function
@@ -1152,10 +1225,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
verbose_enter_scroll();
if (aborting()) {
- smsg(_("%s aborted"), SOURCING_NAME);
- } else if (fc->rettv->v_type == VAR_NUMBER) {
- smsg(_("%s returning #%" PRId64 ""),
- SOURCING_NAME, (int64_t)fc->rettv->vval.v_number);
+ smsg(0, _("%s aborted"), SOURCING_NAME);
+ } else if (fc->fc_rettv->v_type == VAR_NUMBER) {
+ smsg(0, _("%s returning #%" PRId64 ""),
+ SOURCING_NAME, (int64_t)fc->fc_rettv->vval.v_number);
} else {
char buf[MSG_BUF_LEN];
@@ -1163,7 +1236,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
// have some idea how it starts and ends. smsg() would always
// truncate it at the end. Don't want errors such as E724 here.
emsg_off++;
- char *s = encode_tv2string(fc->rettv, NULL);
+ char *s = encode_tv2string(fc->fc_rettv, NULL);
char *tofree = s;
emsg_off--;
if (s != NULL) {
@@ -1171,7 +1244,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN);
s = buf;
}
- smsg(_("%s returning %s"), SOURCING_NAME, s);
+ smsg(0, _("%s returning %s"), SOURCING_NAME, s);
xfree(tofree);
}
}
@@ -1194,7 +1267,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rett
no_wait_return++;
verbose_enter_scroll();
- smsg(_("continuing in %s"), SOURCING_NAME);
+ smsg(0, _("continuing in %s"), SOURCING_NAME);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -1230,6 +1303,21 @@ static bool func_name_refcount(const char *name)
return isdigit((uint8_t)(*name)) || *name == '<';
}
+/// Check the argument count for user function "fp".
+/// @return FCERR_UNKNOWN if OK, FCERR_TOOFEW or FCERR_TOOMANY otherwise.
+static int check_user_func_argcount(ufunc_T *fp, int argcount)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const int regular_args = fp->uf_args.ga_len;
+
+ if (argcount < regular_args - fp->uf_def_args.ga_len) {
+ return FCERR_TOOFEW;
+ } else if (!fp->uf_varargs && argcount > regular_args) {
+ return FCERR_TOOMANY;
+ }
+ return FCERR_UNKNOWN;
+}
+
/// Call a user function after checking the arguments.
static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv,
funcexe_T *funcexe, dict_T *selfdict)
@@ -1242,12 +1330,11 @@ static int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, ty
if ((fp->uf_flags & FC_RANGE) && funcexe->fe_doesrange != NULL) {
*funcexe->fe_doesrange = true;
}
- int error;
- if (argcount < fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
- error = FCERR_TOOFEW;
- } else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) {
- error = FCERR_TOOMANY;
- } else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
+ int error = check_user_func_argcount(fp, argcount);
+ if (error != FCERR_UNKNOWN) {
+ return error;
+ }
+ if ((fp->uf_flags & FC_DICT) && selfdict == NULL) {
error = FCERR_DICT;
} else {
// Call the user function.
@@ -1301,8 +1388,8 @@ void free_all_functions(void)
// Clean up the current_funccal chain and the funccal stack.
while (current_funccal != NULL) {
- tv_clear(current_funccal->rettv);
- cleanup_function_call(current_funccal); // -V595
+ tv_clear(current_funccal->fc_rettv);
+ cleanup_function_call(current_funccal);
if (current_funccal == NULL && funccal_stack != NULL) {
restore_funccal();
}
@@ -1434,12 +1521,16 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv
/// Give an error message for the result of a function.
/// Nothing if "error" is FCERR_NONE.
-static void user_func_error(int error, const char *name)
- FUNC_ATTR_NONNULL_ALL
+static void user_func_error(int error, const char *name, funcexe_T *funcexe)
+ FUNC_ATTR_NONNULL_ARG(2)
{
switch (error) {
case FCERR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
+ if (funcexe->fe_found_var) {
+ semsg(_(e_not_callable_type_str), name);
+ } else {
+ emsg_funcname(e_unknown_function_str, name);
+ }
break;
case FCERR_NOTMETHOD:
emsg_funcname(N_("E276: Cannot use function as a method: %s"), name);
@@ -1451,7 +1542,7 @@ static void user_func_error(int error, const char *name)
emsg_funcname(_(e_toomanyarg), name);
break;
case FCERR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"), name);
+ emsg_funcname(_(e_toofewarg), name);
break;
case FCERR_SCRIPT:
emsg_funcname(N_("E120: Using <SID> not in a script context: %s"), name);
@@ -1510,7 +1601,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
int argv_base = 0;
partial_T *partial = funcexe->fe_partial;
- // Initialize rettv so that it is safe for caller to invoke clear_tv(rettv)
+ // Initialize rettv so that it is safe for caller to invoke tv_clear(rettv)
// even when call_func() returns FAIL.
rettv->v_type = VAR_UNKNOWN;
@@ -1523,7 +1614,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
if (fp == NULL) {
// Make a copy of the name, if it comes from a funcref variable it could
// be changed or deleted in the called function.
- name = xstrnsave(funcname, (size_t)len);
+ name = xmemdupz(funcname, (size_t)len);
fname = fname_trans_sid(name, fname_buf, &tofree, &error);
}
@@ -1576,7 +1667,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
XFREE_CLEAR(name);
funcname = "v:lua";
}
- } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
+ } else if (fp != NULL || !builtin_function(rfname, -1)) {
// User defined function.
if (fp == NULL) {
fp = find_func(rfname);
@@ -1590,8 +1681,7 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
fp = find_func(rfname);
}
// Try loading a package.
- if (fp == NULL && script_autoload((const char *)rfname, strlen(rfname),
- true) && !aborting()) {
+ if (fp == NULL && script_autoload(rfname, strlen(rfname), true) && !aborting()) {
// Loaded a package, search for the function again.
fp = find_func(rfname);
}
@@ -1635,7 +1725,7 @@ theend:
// Report an error unless the argument evaluation or function call has been
// cancelled due to an aborting error, an interrupt, or an exception.
if (!aborting()) {
- user_func_error(error, (name != NULL) ? name : funcname);
+ user_func_error(error, (name != NULL) ? name : funcname, funcexe);
}
// clear the copies made from the partial
@@ -1654,22 +1744,41 @@ char *printable_func_name(ufunc_T *fp)
return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
}
+/// When "prev_ht_changed" does not equal "ht_changed" give an error and return
+/// true. Otherwise return false.
+static int function_list_modified(const int prev_ht_changed)
+{
+ if (prev_ht_changed != func_hashtab.ht_changed) {
+ emsg(_(e_function_list_was_modified));
+ return true;
+ }
+ return false;
+}
+
/// List the head of the function: "name(arg1, arg2)".
///
/// @param[in] fp Function pointer.
/// @param[in] indent Indent line.
/// @param[in] force Include bang "!" (i.e.: "function!").
-static void list_func_head(ufunc_T *fp, int indent, bool force)
+static int list_func_head(ufunc_T *fp, bool indent, bool force)
{
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
msg_start();
+
+ // a callback at the more prompt may have deleted the function
+ if (function_list_modified(prev_ht_changed)) {
+ return FAIL;
+ }
+
if (indent) {
msg_puts(" ");
}
msg_puts(force ? "function! " : "function ");
if (fp->uf_name_exp != NULL) {
- msg_puts((const char *)fp->uf_name_exp);
+ msg_puts(fp->uf_name_exp);
} else {
- msg_puts((const char *)fp->uf_name);
+ msg_puts(fp->uf_name);
}
msg_putchar('(');
int j;
@@ -1677,7 +1786,7 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
if (j) {
msg_puts(", ");
}
- msg_puts((const char *)FUNCARG(fp, j));
+ msg_puts(FUNCARG(fp, j));
if (j >= fp->uf_args.ga_len - fp->uf_def_args.ga_len) {
msg_puts(" = ");
msg_puts(((char **)(fp->uf_def_args.ga_data))
@@ -1707,6 +1816,8 @@ static void list_func_head(ufunc_T *fp, int indent, bool force)
if (p_verbose > 0) {
last_set_msg(fp->uf_script_ctx);
}
+
+ return OK;
}
/// Get a function name, translating "<SID>" and "<SNR>".
@@ -1727,21 +1838,17 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
FUNC_ATTR_NONNULL_ARG(1)
{
char *name = NULL;
- const char *start;
- const char *end;
- int lead;
int len;
lval_T lv;
if (fdp != NULL) {
CLEAR_POINTER(fdp);
}
- start = *pp;
+ const char *start = *pp;
// Check for hard coded <SNR>: already translated function ID (from a user
// command).
- if ((unsigned char)(*pp)[0] == K_SPECIAL && (unsigned char)(*pp)[1] == KS_EXTRA
- && (*pp)[2] == KE_SNR) {
+ if ((uint8_t)(*pp)[0] == K_SPECIAL && (uint8_t)(*pp)[1] == KS_EXTRA && (*pp)[2] == KE_SNR) {
*pp += 3;
len = get_id_len((const char **)pp) + 3;
return xmemdupz(start, (size_t)len);
@@ -1749,14 +1856,14 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
// A name starting with "<SID>" or "<SNR>" is local to a script. But
// don't skip over "s:", get_lval() needs it for "s:dict.func".
- lead = eval_fname_script(start);
+ int lead = eval_fname_script(start);
if (lead > 2) {
start += lead;
}
// Note that TFN_ flags use the same values as GLV_ flags.
- end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
- lead > 2 ? 0 : FNE_CHECK_START);
+ const char *end = get_lval((char *)start, NULL, &lv, false, skip, flags | GLV_READ_ONLY,
+ lead > 2 ? 0 : FNE_CHECK_START);
if (end == start) {
if (!skip) {
emsg(_("E129: Function name required"));
@@ -1772,7 +1879,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
semsg(_(e_invarg2), start);
}
} else {
- *pp = (char *)find_name_end((char *)start, NULL, NULL, FNE_INCL_BR);
+ *pp = (char *)find_name_end(start, NULL, NULL, FNE_INCL_BR);
}
goto theend;
}
@@ -1827,14 +1934,13 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
// Check if the name is a Funcref. If so, use the value.
if (lv.ll_exp_name != NULL) {
len = (int)strlen(lv.ll_exp_name);
- name = deref_func_name(lv.ll_exp_name, &len, partial,
- flags & TFN_NO_AUTOLOAD);
- if ((const char *)name == lv.ll_exp_name) {
+ name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
+ if (name == lv.ll_exp_name) {
name = NULL;
}
} else if (!(flags & TFN_NO_DEREF)) {
len = (int)(end - *pp);
- name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD);
+ name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD, NULL);
if (name == *pp) {
name = NULL;
}
@@ -1882,8 +1988,7 @@ char *trans_function_name(char **pp, bool skip, int flags, funcdict_T *fdp, part
lead = 0; // do nothing
} else if (lead > 0) {
lead = 3;
- if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name))
- || eval_fname_sid((const char *)(*pp))) {
+ if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) || eval_fname_sid(*pp)) {
// It's "s:" or "<SID>".
if (current_sctx.sc_sid <= 0) {
emsg(_(e_usingsid));
@@ -1970,7 +2075,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
if (strncmp(p, "<lambda>", 8) == 0) {
p += 8;
(void)getdigits(&p, false, 0);
- saved = xstrndup(*name, (size_t)(p - *name));
+ saved = xmemdupz(*name, (size_t)(p - *name));
if (fudi != NULL) {
CLEAR_POINTER(fudi);
}
@@ -1989,7 +2094,7 @@ char *save_function_name(char **name, bool skip, int flags, funcdict_T *fudi)
/// Otherwise functions matching "regmatch".
static void list_functions(regmatch_T *regmatch)
{
- const int changed = func_hashtab.ht_changed;
+ const int prev_ht_changed = func_hashtab.ht_changed;
size_t todo = func_hashtab.ht_used;
const hashitem_T *const ht_array = func_hashtab.ht_array;
@@ -1997,15 +2102,15 @@ static void list_functions(regmatch_T *regmatch)
if (!HASHITEM_EMPTY(hi)) {
ufunc_T *fp = HI2UF(hi);
todo--;
- if ((fp->uf_flags & FC_DEAD) == 0
- && (regmatch == NULL
- ? (!message_filtered((char *)fp->uf_name)
- && !func_name_refcount(fp->uf_name))
- : (!isdigit((uint8_t)(*fp->uf_name))
- && vim_regexec(regmatch, (char *)fp->uf_name, 0)))) {
- list_func_head(fp, false, false);
- if (changed != func_hashtab.ht_changed) {
- emsg(_("E454: function list was modified"));
+ if (regmatch == NULL
+ ? (!message_filtered(fp->uf_name)
+ && !func_name_refcount(fp->uf_name))
+ : (!isdigit((uint8_t)(*fp->uf_name))
+ && vim_regexec(regmatch, fp->uf_name, 0))) {
+ if (list_func_head(fp, false, false) == FAIL) {
+ return;
+ }
+ if (function_list_modified(prev_ht_changed)) {
return;
}
}
@@ -2018,11 +2123,7 @@ void ex_function(exarg_T *eap)
{
char *theline;
char *line_to_free = NULL;
- char c;
- int saved_did_emsg;
bool saved_wait_return = need_wait_return;
- char *name = NULL;
- char *p;
char *arg;
char *line_arg = NULL;
garray_T newargs;
@@ -2032,16 +2133,9 @@ void ex_function(exarg_T *eap)
int flags = 0;
ufunc_T *fp;
bool overwrite = false;
- int indent;
- int nesting;
- dictitem_T *v;
funcdict_T fudi;
static int func_nr = 0; // number for nameless function
- int paren;
hashtab_T *ht;
- hashitem_T *hi;
- linenr_T sourcing_lnum_off;
- linenr_T sourcing_lnum_top;
bool is_heredoc = false;
char *skip_until = NULL;
char *heredoc_trimmed = NULL;
@@ -2059,11 +2153,11 @@ void ex_function(exarg_T *eap)
// ":function /pat": list functions matching pattern.
if (*eap->arg == '/') {
- p = skip_regexp(eap->arg + 1, '/', true);
+ char *p = skip_regexp(eap->arg + 1, '/', true);
if (!eap->skip) {
regmatch_T regmatch;
- c = *p;
+ char c = *p;
*p = NUL;
regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC);
*p = c;
@@ -2094,9 +2188,9 @@ void ex_function(exarg_T *eap)
// "fudi.fd_di" set, "fudi.fd_newkey" == NULL
// s:func script-local function name
// g:func global function name, same as "func"
- p = eap->arg;
- name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
- paren = (vim_strchr(p, '(') != NULL);
+ char *p = eap->arg;
+ char *name = save_function_name(&p, eap->skip, TFN_NO_AUTOLOAD, &fudi);
+ int paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) {
// Return on an invalid expression in braces, unless the expression
// evaluation has been cancelled due to an aborting error, an
@@ -2113,7 +2207,7 @@ void ex_function(exarg_T *eap)
// An error in a function call during evaluation of an expression in magic
// braces should not cause the function not to be defined.
- saved_did_emsg = did_emsg;
+ const int saved_did_emsg = did_emsg;
did_emsg = false;
//
@@ -2134,28 +2228,37 @@ void ex_function(exarg_T *eap)
if (!eap->skip && !got_int) {
fp = find_func(name);
if (fp != NULL) {
- list_func_head(fp, !eap->forceit, eap->forceit);
- for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
- if (FUNCLINE(fp, j) == NULL) {
- continue;
- }
- msg_putchar('\n');
- if (!eap->forceit) {
- msg_outnum((long)j + 1);
- if (j < 9) {
- msg_putchar(' ');
+ // Check no function was added or removed from a callback, e.g. at
+ // the more prompt. "fp" may then be invalid.
+ const int prev_ht_changed = func_hashtab.ht_changed;
+
+ if (list_func_head(fp, !eap->forceit, eap->forceit) == OK) {
+ for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) {
+ if (FUNCLINE(fp, j) == NULL) {
+ continue;
}
- if (j < 99) {
- msg_putchar(' ');
+ msg_putchar('\n');
+ if (!eap->forceit) {
+ msg_outnum(j + 1);
+ if (j < 9) {
+ msg_putchar(' ');
+ }
+ if (j < 99) {
+ msg_putchar(' ');
+ }
+ if (function_list_modified(prev_ht_changed)) {
+ break;
+ }
+ }
+ msg_prt_line(FUNCLINE(fp, j), false);
+ line_breakcheck(); // show multiple lines at a time!
+ }
+ if (!got_int) {
+ msg_putchar('\n');
+ if (!function_list_modified(prev_ht_changed)) {
+ msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
}
- msg_prt_line(FUNCLINE(fp, j), false);
- ui_flush(); // show a line at a time
- os_breakcheck();
- }
- if (!got_int) {
- msg_putchar('\n');
- msg_puts(eap->forceit ? "endfunction" : " endfunction");
}
} else {
emsg_funcname(N_("E123: Undefined function: %s"), name);
@@ -2195,7 +2298,7 @@ void ex_function(exarg_T *eap)
j++;
}
if (arg[j] != NUL) {
- emsg_funcname((char *)e_invarg2, arg);
+ emsg_funcname(e_invarg2, arg);
}
}
// Disallow using the g: dict.
@@ -2211,11 +2314,11 @@ void ex_function(exarg_T *eap)
if (KeyTyped && ui_has(kUICmdline)) {
show_block = true;
- ui_ext_cmdline_block_append(0, (const char *)eap->cmd);
+ ui_ext_cmdline_block_append(0, eap->cmd);
}
// find extra arguments "range", "dict", "abort" and "closure"
- for (;;) {
+ while (true) {
p = skipwhite(p);
if (strncmp(p, "range", 5) == 0) {
flags |= FC_RANGE;
@@ -2271,11 +2374,11 @@ void ex_function(exarg_T *eap)
}
// Save the starting line number.
- sourcing_lnum_top = SOURCING_LNUM;
+ linenr_T sourcing_lnum_top = SOURCING_LNUM;
- indent = 2;
- nesting = 0;
- for (;;) {
+ int indent = 2;
+ int nesting = 0;
+ while (true) {
if (KeyTyped) {
msg_scroll = true;
saved_wait_return = false;
@@ -2295,7 +2398,7 @@ void ex_function(exarg_T *eap)
} else {
xfree(line_to_free);
if (eap->getline == NULL) {
- theline = getcmdline(':', 0L, indent, do_concat);
+ theline = getcmdline(':', 0, indent, do_concat);
} else {
theline = eap->getline(':', eap->cookie, indent, do_concat);
}
@@ -2305,16 +2408,20 @@ void ex_function(exarg_T *eap)
lines_left = Rows - 1;
}
if (theline == NULL) {
- emsg(_("E126: Missing :endfunction"));
+ if (skip_until != NULL) {
+ semsg(_(e_missing_heredoc_end_marker_str), skip_until);
+ } else {
+ emsg(_("E126: Missing :endfunction"));
+ }
goto erret;
}
if (show_block) {
assert(indent >= 0);
- ui_ext_cmdline_block_append((size_t)indent, (const char *)theline);
+ ui_ext_cmdline_block_append((size_t)indent, theline);
}
// Detect line continuation: SOURCING_LNUM increased more than one.
- sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
+ linenr_T sourcing_lnum_off = get_sourced_lnum(eap->getline, eap->cookie);
if (SOURCING_LNUM < sourcing_lnum_off) {
sourcing_lnum_off -= SOURCING_LNUM;
} else {
@@ -2334,7 +2441,7 @@ void ex_function(exarg_T *eap)
p = theline;
} else if (is_heredoc) {
p = skipwhite(theline) == theline
- ? theline : theline + strlen(heredoc_trimmed);
+ ? theline : theline + strlen(heredoc_trimmed);
} else {
p = theline + strlen(heredoc_trimmed);
}
@@ -2360,7 +2467,7 @@ void ex_function(exarg_T *eap)
} else if (line_arg != NULL && *skipwhite(line_arg) != NUL) {
nextcmd = line_arg;
} else if (*p != NUL && *p != '"' && p_verbose > 0) {
- give_warning2(_("W22: Text found after :endfunction: %s"), p, true);
+ swmsg(true, _("W22: Text found after :endfunction: %s"), p);
}
if (nextcmd != NULL) {
// Another command follows. If the line came from "eap" we
@@ -2392,11 +2499,11 @@ void ex_function(exarg_T *eap)
if (*p == '!') {
p = skipwhite(p + 1);
}
- p += eval_fname_script((const char *)p);
+ p += eval_fname_script(p);
xfree(trans_function_name(&p, true, 0, NULL, NULL));
if (*skipwhite(p) == '(') {
if (nesting == MAX_FUNC_NESTING - 1) {
- emsg(_("E1058: function nesting too deep"));
+ emsg(_(e_function_nesting_too_deep));
} else {
nesting++;
indent += 2;
@@ -2439,35 +2546,45 @@ void ex_function(exarg_T *eap)
&& (!ASCII_ISALPHA(p[2]) || p[2] == 's')))) {
// ":python <<" continues until a dot, like ":append"
p = skipwhite(arg + 2);
+ if (strncmp(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline));
+ }
if (*p == NUL) {
skip_until = xstrdup(".");
} else {
- skip_until = xstrdup(p);
+ skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p));
}
+ do_concat = false;
+ is_heredoc = true;
}
// Check for ":let v =<< [trim] EOF"
// and ":let [a, b] =<< [trim] EOF"
- arg = skipwhite(skiptowhite(p));
- if (*arg == '[') {
- arg = vim_strchr(arg, ']');
- }
- if (arg != NULL) {
- arg = skipwhite(skiptowhite(arg));
- if (arg[0] == '='
- && arg[1] == '<'
- && arg[2] == '<'
- && (p[0] == 'l'
- && p[1] == 'e'
- && (!ASCII_ISALNUM(p[2])
- || (p[2] == 't' && !ASCII_ISALNUM(p[3]))))) {
+ arg = p;
+ if (checkforcmd(&arg, "let", 2)) {
+ while (vim_strchr("$@&", *arg) != NULL) {
+ arg++;
+ }
+ arg = skipwhite(find_name_end(arg, NULL, NULL, FNE_INCL_BR));
+ if (arg[0] == '=' && arg[1] == '<' && arg[2] == '<') {
p = skipwhite(arg + 3);
- if (strncmp(p, "trim", 4) == 0) {
- // Ignore leading white space.
- p = skipwhite(p + 4);
- heredoc_trimmed = xstrnsave(theline, (size_t)(skipwhite(theline) - theline));
+ while (true) {
+ if (strncmp(p, "trim", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ heredoc_trimmed = xmemdupz(theline, (size_t)(skipwhite(theline) - theline));
+ continue;
+ }
+ if (strncmp(p, "eval", 4) == 0) {
+ // Ignore leading white space.
+ p = skipwhite(p + 4);
+ continue;
+ }
+ break;
}
- skip_until = xstrnsave(p, (size_t)(skiptowhite(p) - p));
+ skip_until = xmemdupz(p, (size_t)(skiptowhite(p) - p));
do_concat = false;
is_heredoc = true;
}
@@ -2503,7 +2620,7 @@ void ex_function(exarg_T *eap)
// If there are no errors, add the function
if (fudi.fd_dict == NULL) {
- v = find_var((const char *)name, strlen(name), &ht, false);
+ dictitem_T *v = find_var(name, strlen(name), &ht, false);
if (v != NULL && v->di_tv.v_type == VAR_FUNC) {
emsg_funcname(N_("E707: Function name conflicts with variable: %s"), name);
goto erret;
@@ -2550,13 +2667,11 @@ void ex_function(exarg_T *eap)
goto erret;
}
if (fudi.fd_di == NULL) {
- if (value_check_lock(fudi.fd_dict->dv_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ if (value_check_lock(fudi.fd_dict->dv_lock, eap->arg, TV_CSTRING)) {
// Can't add a function to a locked dictionary
goto erret;
}
- } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, (const char *)eap->arg,
- TV_CSTRING)) {
+ } else if (value_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg, TV_CSTRING)) {
// Can't change an existing function if it is locked
goto erret;
}
@@ -2564,22 +2679,19 @@ void ex_function(exarg_T *eap)
// Give the function a sequential number. Can only be used with a
// Funcref!
xfree(name);
- sprintf(numbuf, "%d", ++func_nr); // NOLINT(runtime/printf)
+ snprintf(numbuf, sizeof(numbuf), "%d", ++func_nr);
name = xstrdup(numbuf);
}
if (fp == NULL) {
if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) {
- int slen, plen;
- char *scriptname;
-
// Check that the autoload name matches the script name.
int j = FAIL;
if (SOURCING_NAME != NULL) {
- scriptname = autoload_name(name, strlen(name));
+ char *scriptname = autoload_name(name, strlen(name));
p = vim_strchr(scriptname, '/');
- plen = (int)strlen(p);
- slen = (int)strlen(SOURCING_NAME);
+ int plen = (int)strlen(p);
+ int slen = (int)strlen(SOURCING_NAME);
if (slen > plen && path_fnamecmp(p, SOURCING_NAME + slen - plen) == 0) {
j = OK;
}
@@ -2597,7 +2709,7 @@ void ex_function(exarg_T *eap)
if (fudi.fd_dict != NULL) {
if (fudi.fd_di == NULL) {
// Add new dict entry
- fudi.fd_di = tv_dict_item_alloc((const char *)fudi.fd_newkey);
+ fudi.fd_di = tv_dict_item_alloc(fudi.fd_newkey);
if (tv_dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) {
xfree(fudi.fd_di);
xfree(fp);
@@ -2617,8 +2729,8 @@ void ex_function(exarg_T *eap)
// insert the new function in the function list
set_ufunc_name(fp, name);
if (overwrite) {
- hi = hash_find(&func_hashtab, name);
- hi->hi_key = (char *)UF2HIKEY(fp);
+ hashitem_T *hi = hash_find(&func_hashtab, name);
+ hi->hi_key = UF2HIKEY(fp);
} else if (hash_add(&func_hashtab, UF2HIKEY(fp)) == FAIL) {
xfree(fp);
goto erret;
@@ -2687,7 +2799,7 @@ int eval_fname_script(const char *const p)
bool translated_function_exists(const char *name)
{
if (builtin_function(name, -1)) {
- return find_internal_func((char *)name) != NULL;
+ return find_internal_func(name) != NULL;
}
return find_func(name) != NULL;
}
@@ -2726,7 +2838,6 @@ char *get_user_func_name(expand_T *xp, int idx)
static size_t done;
static int changed;
static hashitem_T *hi;
- ufunc_T *fp;
if (idx == 0) {
done = 0;
@@ -2741,7 +2852,7 @@ char *get_user_func_name(expand_T *xp, int idx)
while (HASHITEM_EMPTY(hi)) {
hi++;
}
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if ((fp->uf_flags & FC_DICT)
|| strncmp(fp->uf_name, "<lambda>", 8) == 0) {
@@ -2749,14 +2860,14 @@ char *get_user_func_name(expand_T *xp, int idx)
}
if (strlen(fp->uf_name) + 4 >= IOSIZE) {
- return (char *)fp->uf_name; // Prevent overflow.
+ return fp->uf_name; // Prevent overflow.
}
- cat_func_name(IObuff, fp);
+ cat_func_name(IObuff, IOSIZE, fp);
if (xp->xp_context != EXPAND_USER_FUNC) {
- STRCAT(IObuff, "(");
+ xstrlcat(IObuff, "(", IOSIZE);
if (!fp->uf_varargs && GA_EMPTY(&fp->uf_args)) {
- STRCAT(IObuff, ")");
+ xstrlcat(IObuff, ")", IOSIZE);
}
}
return IObuff;
@@ -2768,12 +2879,10 @@ char *get_user_func_name(expand_T *xp, int idx)
void ex_delfunction(exarg_T *eap)
{
ufunc_T *fp = NULL;
- char *p;
- char *name;
funcdict_T fudi;
- p = eap->arg;
- name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
+ char *p = eap->arg;
+ char *name = trans_function_name(&p, eap->skip, 0, &fudi, NULL);
xfree(fudi.fd_newkey);
if (name == NULL) {
if (fudi.fd_dict != NULL && !eap->skip) {
@@ -2850,13 +2959,11 @@ void ex_delfunction(exarg_T *eap)
/// becomes zero.
void func_unref(char *name)
{
- ufunc_T *fp = NULL;
-
if (name == NULL || !func_name_refcount(name)) {
return;
}
- fp = find_func(name);
+ ufunc_T *fp = find_func(name);
if (fp == NULL && isdigit((uint8_t)(*name))) {
#ifdef EXITFREE
if (!entered_free_all_mem) {
@@ -2892,12 +2999,10 @@ void func_ptr_unref(ufunc_T *fp)
/// Count a reference to a Function.
void func_ref(char *name)
{
- ufunc_T *fp;
-
if (name == NULL || !func_name_refcount(name)) {
return;
}
- fp = find_func(name);
+ ufunc_T *fp = find_func(name);
if (fp != NULL) {
(fp->uf_refcount)++;
} else if (isdigit((uint8_t)(*name))) {
@@ -2924,10 +3029,10 @@ static inline bool fc_referenced(const funccall_T *const fc)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ALL
{
- return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated)
+ return ((fc->fc_l_varlist.lv_refcount // NOLINT(runtime/deprecated)
!= DO_NOT_FREE_CNT)
- || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT
- || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_l_vars.dv_refcount != DO_NOT_FREE_CNT
+ || fc->fc_l_avars.dv_refcount != DO_NOT_FREE_CNT
|| fc->fc_refcount > 0);
}
@@ -2935,9 +3040,9 @@ static inline bool fc_referenced(const funccall_T *const fc)
/// referenced from anywhere that is in use.
static int can_free_funccal(funccall_T *fc, int copyID)
{
- return fc->l_varlist.lv_copyID != copyID
- && fc->l_vars.dv_copyID != copyID
- && fc->l_avars.dv_copyID != copyID
+ return fc->fc_l_varlist.lv_copyID != copyID
+ && fc->fc_l_vars.dv_copyID != copyID
+ && fc->fc_l_avars.dv_copyID != copyID
&& fc->fc_copyID != copyID;
}
@@ -2953,13 +3058,15 @@ void ex_return(exarg_T *eap)
return;
}
+ evalarg_T evalarg = { .eval_flags = eap->skip ? 0 : EVAL_EVALUATE };
+
if (eap->skip) {
emsg_skip++;
}
eap->nextcmd = NULL;
if ((*arg != NUL && *arg != '|' && *arg != '\n')
- && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) {
+ && eval0(arg, &rettv, eap, &evalarg) != FAIL) {
if (!eap->skip) {
returning = do_return(eap, false, true, &rettv);
} else {
@@ -2988,36 +3095,234 @@ void ex_return(exarg_T *eap)
if (eap->skip) {
emsg_skip--;
}
+ clear_evalarg(&evalarg, eap);
+}
+
+/// Lower level implementation of "call". Only called when not skipping.
+static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg,
+ const funcexe_T *const funcexe_init, evalarg_T *const evalarg)
+{
+ bool doesrange;
+ bool failed = false;
+
+ for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
+ if (eap->addr_count > 0) {
+ if (lnum > curbuf->b_ml.ml_line_count) {
+ // If the function deleted lines or switched to another buffer
+ // the line number may become invalid.
+ emsg(_(e_invrange));
+ break;
+ }
+ curwin->w_cursor.lnum = lnum;
+ curwin->w_cursor.col = 0;
+ curwin->w_cursor.coladd = 0;
+ }
+ *arg = startarg;
+
+ funcexe_T funcexe = *funcexe_init;
+ funcexe.fe_doesrange = &doesrange;
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+ if (get_func_tv(name, -1, &rettv, arg, evalarg, &funcexe) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ // Handle a function returning a Funcref, Dictionary or List.
+ if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
+ failed = true;
+ break;
+ }
+
+ tv_clear(&rettv);
+ if (doesrange) {
+ break;
+ }
+
+ // Stop when immediately aborting on error, or when an interrupt
+ // occurred or an exception was thrown but not caught.
+ // get_func_tv() returned OK, so that the check for trailing
+ // characters below is executed.
+ if (aborting()) {
+ break;
+ }
+ }
+
+ return failed;
+}
+
+/// Core part of ":defer func(arg)". "arg" points to the "(" and is advanced.
+///
+/// @return FAIL or OK.
+static int ex_defer_inner(char *name, char **arg, const partial_T *const partial,
+ evalarg_T *const evalarg)
+{
+ typval_T argvars[MAX_FUNC_ARGS + 1]; // vars for arguments
+ int partial_argc = 0; // number of partial arguments
+ int argcount = 0; // number of arguments found
+
+ if (current_funccal == NULL) {
+ semsg(_(e_str_not_inside_function), "defer");
+ return FAIL;
+ }
+ if (partial != NULL) {
+ if (partial->pt_dict != NULL) {
+ emsg(_(e_cannot_use_partial_with_dictionary_for_defer));
+ return FAIL;
+ }
+ if (partial->pt_argc > 0) {
+ partial_argc = partial->pt_argc;
+ for (int i = 0; i < partial_argc; i++) {
+ tv_copy(&partial->pt_argv[i], &argvars[i]);
+ }
+ }
+ }
+ int r = get_func_arguments(arg, evalarg, false, argvars + partial_argc, &argcount);
+ argcount += partial_argc;
+
+ if (r == OK) {
+ if (builtin_function(name, -1)) {
+ const EvalFuncDef *const fdef = find_internal_func(name);
+ if (fdef == NULL) {
+ emsg_funcname(e_unknown_function_str, name);
+ r = FAIL;
+ } else if (check_internal_func(fdef, argcount) == -1) {
+ r = FAIL;
+ }
+ } else {
+ ufunc_T *ufunc = find_func(name);
+ // we tolerate an unknown function here, it might be defined later
+ if (ufunc != NULL) {
+ int error = check_user_func_argcount(ufunc, argcount);
+ if (error != FCERR_UNKNOWN) {
+ user_func_error(error, name, NULL);
+ r = FAIL;
+ }
+ }
+ }
+ }
+
+ if (r == FAIL) {
+ while (--argcount >= 0) {
+ tv_clear(&argvars[argcount]);
+ }
+ return FAIL;
+ }
+ add_defer(name, argcount, argvars);
+ return OK;
+}
+
+/// Return true if currently inside a function call.
+/// Give an error message and return false when not.
+bool can_add_defer(void)
+{
+ if (get_current_funccal() == NULL) {
+ semsg(_(e_str_not_inside_function), "defer");
+ return false;
+ }
+ return true;
+}
+
+/// Add a deferred call for "name" with arguments "argvars[argcount]".
+/// Consumes "argvars[]".
+/// Caller must check that current_funccal is not NULL.
+void add_defer(char *name, int argcount_arg, typval_T *argvars)
+{
+ char *saved_name = xstrdup(name);
+ int argcount = argcount_arg;
+
+ if (current_funccal->fc_defer.ga_itemsize == 0) {
+ ga_init(&current_funccal->fc_defer, sizeof(defer_T), 10);
+ }
+ defer_T *dr = GA_APPEND_VIA_PTR(defer_T, &current_funccal->fc_defer);
+ dr->dr_name = saved_name;
+ dr->dr_argcount = argcount;
+ while (argcount > 0) {
+ argcount--;
+ dr->dr_argvars[argcount] = argvars[argcount];
+ }
+}
+
+/// Invoked after a function has finished: invoke ":defer" functions.
+static void handle_defer_one(funccall_T *funccal)
+{
+ for (int idx = funccal->fc_defer.ga_len - 1; idx >= 0; idx--) {
+ defer_T *dr = ((defer_T *)funccal->fc_defer.ga_data) + idx;
+
+ if (dr->dr_name == NULL) {
+ // already being called, can happen if function does ":qa"
+ continue;
+ }
+
+ funcexe_T funcexe = { .fe_evaluate = true };
+
+ typval_T rettv;
+ rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
+
+ char *name = dr->dr_name;
+ dr->dr_name = NULL;
+
+ // If the deferred function is called after an exception, then only the
+ // first statement in the function will be executed (because of the
+ // exception). So save and restore the try/catch/throw exception
+ // state.
+ exception_state_T estate;
+ exception_state_save(&estate);
+ exception_state_clear();
+
+ call_func(name, -1, &rettv, dr->dr_argcount, dr->dr_argvars, &funcexe);
+
+ exception_state_restore(&estate);
+
+ tv_clear(&rettv);
+ xfree(name);
+ for (int i = dr->dr_argcount - 1; i >= 0; i--) {
+ tv_clear(&dr->dr_argvars[i]);
+ }
+ }
+ ga_clear(&funccal->fc_defer);
+}
+
+/// Called when exiting: call all defer functions.
+void invoke_all_defer(void)
+{
+ for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->fc_caller) {
+ handle_defer_one(fc);
+ }
+
+ for (funccal_entry_T *fce = funccal_stack; fce != NULL; fce = fce->next) {
+ for (funccall_T *fc = fce->top_funccal; fc != NULL; fc = fc->fc_caller) {
+ handle_defer_one(fc);
+ }
+ }
}
/// ":1,25call func(arg1, arg2)" function call.
+/// ":defer func(arg1, arg2)" deferred function call.
void ex_call(exarg_T *eap)
{
char *arg = eap->arg;
- char *startarg;
- char *name;
- char *tofree;
- int len;
- typval_T rettv;
- linenr_T lnum;
- bool doesrange;
bool failed = false;
funcdict_T fudi;
partial_T *partial = NULL;
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip) {
+ typval_T rettv;
// trans_function_name() doesn't work well when skipping, use eval0()
// instead to skip to any following command, e.g. for:
// :if 0 | call dict.foo().bar() | endif.
emsg_skip++;
- if (eval0(eap->arg, &rettv, &eap->nextcmd, false) != FAIL) {
+ if (eval0(eap->arg, &rettv, eap, &evalarg) != FAIL) {
tv_clear(&rettv);
}
emsg_skip--;
+ clear_evalarg(&evalarg, eap);
return;
}
- tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
+ char *tofree = trans_function_name(&arg, false, TFN_INT, &fudi, &partial);
if (fudi.fd_newkey != NULL) {
// Still need to give an error message for missing key.
semsg(_(e_dictkey), fudi.fd_newkey);
@@ -3036,66 +3341,31 @@ void ex_call(exarg_T *eap)
// If it is the name of a variable of type VAR_FUNC or VAR_PARTIAL use its
// contents. For VAR_PARTIAL get its partial, unless we already have one
// from trans_function_name().
- len = (int)strlen(tofree);
- name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false);
+ int len = (int)strlen(tofree);
+ bool found_var = false;
+ char *name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, false, &found_var);
// Skip white space to allow ":call func ()". Not good, but required for
// backward compatibility.
- startarg = skipwhite(arg);
- rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this.
+ char *startarg = skipwhite(arg);
if (*startarg != '(') {
semsg(_(e_missingparen), eap->arg);
goto end;
}
- lnum = eap->line1;
- for (; lnum <= eap->line2; lnum++) {
- if (eap->addr_count > 0) { // -V560
- if (lnum > curbuf->b_ml.ml_line_count) {
- // If the function deleted lines or switched to another buffer
- // the line number may become invalid.
- emsg(_(e_invrange));
- break;
- }
- curwin->w_cursor.lnum = lnum;
- curwin->w_cursor.col = 0;
- curwin->w_cursor.coladd = 0;
- }
+ if (eap->cmdidx == CMD_defer) {
arg = startarg;
-
+ failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
+ } else {
funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_partial = partial;
+ funcexe.fe_selfdict = fudi.fd_dict;
funcexe.fe_firstline = eap->line1;
funcexe.fe_lastline = eap->line2;
- funcexe.fe_doesrange = &doesrange;
+ funcexe.fe_found_var = found_var;
funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
- funcexe.fe_selfdict = fudi.fd_dict;
- if (get_func_tv(name, -1, &rettv, &arg, &funcexe) == FAIL) {
- failed = true;
- break;
- }
-
- // Handle a function returning a Funcref, Dictionary or List.
- if (handle_subscript((const char **)&arg, &rettv, true, true,
- (const char *)name, (const char **)&name)
- == FAIL) {
- failed = true;
- break;
- }
-
- tv_clear(&rettv);
- if (doesrange) {
- break;
- }
-
- // Stop when immediately aborting on error, or when an interrupt
- // occurred or an exception was thrown but not caught.
- // get_func_tv() returned OK, so that the check for trailing
- // characters below is executed.
- if (aborting()) {
- break;
- }
+ failed = ex_call_inner(eap, name, &arg, startarg, &funcexe, &evalarg);
}
// When inside :try we need to check for following "| catch" or "| endtry".
@@ -3111,6 +3381,7 @@ void ex_call(exarg_T *eap)
eap->nextcmd = check_nextcmd(arg);
}
}
+ clear_evalarg(&evalarg, eap);
end:
tv_dict_unref(fudi.fd_dict);
@@ -3129,19 +3400,18 @@ end:
/// false when the return gets pending.
int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (reanimate) {
// Undo the return.
- current_funccal->returned = false;
+ current_funccal->fc_returned = false;
}
// Cleanup (and deactivate) conditionals, but stop when a try conditional
// not in its finally clause (which then is to be executed next) is found.
// In this case, make the ":return" pending for execution at the ":endtry".
// Otherwise, return normally.
- idx = cleanup_conditionals(eap->cstack, 0, true);
+ int idx = cleanup_conditionals(eap->cstack, 0, true);
if (idx >= 0) {
cstack->cs_pending[idx] = CSTP_RETURN;
@@ -3154,8 +3424,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
// When undoing a return in order to make it pending, get the stored
// return rettv.
if (reanimate) {
- assert(current_funccal->rettv);
- rettv = current_funccal->rettv;
+ assert(current_funccal->fc_rettv);
+ rettv = current_funccal->fc_rettv;
}
if (rettv != NULL) {
@@ -3170,20 +3440,20 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv)
// The pending return value could be overwritten by a ":return"
// without argument in a finally clause; reset the default
// return value.
- current_funccal->rettv->v_type = VAR_NUMBER;
- current_funccal->rettv->vval.v_number = 0;
+ current_funccal->fc_rettv->v_type = VAR_NUMBER;
+ current_funccal->fc_rettv->vval.v_number = 0;
}
}
report_make_pending(CSTP_RETURN, rettv);
} else {
- current_funccal->returned = true;
+ current_funccal->fc_returned = true;
// If the return is carried out now, store the return value. For
// a return immediately after reanimation, the value is already
// there.
if (!reanimate && rettv != NULL) {
- tv_clear(current_funccal->rettv);
- *current_funccal->rettv = *(typval_T *)rettv;
+ tv_clear(current_funccal->fc_rettv);
+ *current_funccal->fc_rettv = *(typval_T *)rettv;
if (!is_cmd) {
xfree(rettv);
}
@@ -3207,7 +3477,7 @@ char *get_return_cmd(void *rettv)
s = "";
}
- STRCPY(IObuff, ":return ");
+ xstrlcpy(IObuff, ":return ", IOSIZE);
xstrlcpy(IObuff + 8, s, IOSIZE - 8);
if (strlen(s) + 8 >= IOSIZE) {
STRCPY(IObuff + IOSIZE - 4, "...");
@@ -3223,34 +3493,33 @@ char *get_return_cmd(void *rettv)
char *get_func_line(int c, void *cookie, int indent, bool do_concat)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
char *retval;
- garray_T *gap; // growarray with function lines
// If breakpoints have been added/deleted need to check for it.
- if (fcp->dbg_tick != debug_tick) {
- fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM);
- fcp->dbg_tick = debug_tick;
+ if (fcp->fc_dbg_tick != debug_tick) {
+ fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
+ fcp->fc_dbg_tick = debug_tick;
}
if (do_profiling == PROF_YES) {
func_line_end(cookie);
}
- gap = &fp->uf_lines;
+ garray_T *gap = &fp->uf_lines; // growarray with function lines
if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned) {
+ || fcp->fc_returned) {
retval = NULL;
} else {
// Skip NULL lines (continuation lines).
- while (fcp->linenr < gap->ga_len
- && ((char **)(gap->ga_data))[fcp->linenr] == NULL) {
- fcp->linenr++;
+ while (fcp->fc_linenr < gap->ga_len
+ && ((char **)(gap->ga_data))[fcp->fc_linenr] == NULL) {
+ fcp->fc_linenr++;
}
- if (fcp->linenr >= gap->ga_len) {
+ if (fcp->fc_linenr >= gap->ga_len) {
retval = NULL;
} else {
- retval = xstrdup(((char **)(gap->ga_data))[fcp->linenr++]);
- SOURCING_LNUM = fcp->linenr;
+ retval = xstrdup(((char **)(gap->ga_data))[fcp->fc_linenr++]);
+ SOURCING_LNUM = fcp->fc_linenr;
if (do_profiling == PROF_YES) {
func_line_start(cookie);
}
@@ -3258,11 +3527,11 @@ char *get_func_line(int c, void *cookie, int indent, bool do_concat)
}
// Did we encounter a breakpoint?
- if (fcp->breakpoint != 0 && fcp->breakpoint <= SOURCING_LNUM) {
- dbg_breakpoint((char *)fp->uf_name, SOURCING_LNUM);
+ if (fcp->fc_breakpoint != 0 && fcp->fc_breakpoint <= SOURCING_LNUM) {
+ dbg_breakpoint(fp->uf_name, SOURCING_LNUM);
// Find next breakpoint.
- fcp->breakpoint = dbg_find_breakpoint(false, (char *)fp->uf_name, SOURCING_LNUM);
- fcp->dbg_tick = debug_tick;
+ fcp->fc_breakpoint = dbg_find_breakpoint(false, fp->uf_name, SOURCING_LNUM);
+ fcp->fc_dbg_tick = debug_tick;
}
return retval;
@@ -3276,21 +3545,20 @@ int func_has_ended(void *cookie)
// Ignore the "abort" flag if the abortion behavior has been changed due to
// an error inside a try conditional.
- return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
- || fcp->returned;
+ return ((fcp->fc_func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try())
+ || fcp->fc_returned;
}
/// @return true if cookie indicates a function which "abort"s on errors.
int func_has_abort(void *cookie)
{
- return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT;
+ return ((funccall_T *)cookie)->fc_func->uf_flags & FC_ABORT;
}
/// Turn "dict.Func" into a partial for "Func" bound to "dict".
/// Changes "rettv" in-place.
void make_partial(dict_T *const selfdict, typval_T *const rettv)
{
- char *fname;
char *tofree = NULL;
ufunc_T *fp;
char fname_buf[FLEN_FIXED + 1];
@@ -3299,9 +3567,9 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
if (rettv->v_type == VAR_PARTIAL && rettv->vval.v_partial->pt_func != NULL) {
fp = rettv->vval.v_partial->pt_func;
} else {
- fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
- ? rettv->vval.v_string
- : rettv->vval.v_partial->pt_name;
+ char *fname = rettv->v_type == VAR_FUNC || rettv->v_type == VAR_STRING
+ ? rettv->vval.v_string
+ : rettv->vval.v_partial->pt_name;
// Translate "s:func" to the stored function name.
fname = fname_trans_sid(fname, fname_buf, &tofree, &error);
fp = find_func(fname);
@@ -3320,7 +3588,6 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
pt->pt_name = rettv->vval.v_string;
} else {
partial_T *ret_pt = rettv->vval.v_partial;
- int i;
// Partial: copy the function name, use selfdict and copy
// args. Can't take over name or args, the partial might
@@ -3336,7 +3603,7 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
size_t arg_size = sizeof(typval_T) * (size_t)ret_pt->pt_argc;
pt->pt_argv = (typval_T *)xmalloc(arg_size);
pt->pt_argc = ret_pt->pt_argc;
- for (i = 0; i < pt->pt_argc; i++) {
+ for (int i = 0; i < pt->pt_argc; i++) {
tv_copy(&ret_pt->pt_argv[i], &pt->pt_argv[i]);
}
}
@@ -3350,31 +3617,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv)
/// @return the name of the executed function.
char *func_name(void *cookie)
{
- return ((funccall_T *)cookie)->func->uf_name;
+ return ((funccall_T *)cookie)->fc_func->uf_name;
}
/// @return the address holding the next breakpoint line for a funccall cookie.
linenr_T *func_breakpoint(void *cookie)
{
- return &((funccall_T *)cookie)->breakpoint;
+ return &((funccall_T *)cookie)->fc_breakpoint;
}
/// @return the address holding the debug tick for a funccall cookie.
int *func_dbg_tick(void *cookie)
{
- return &((funccall_T *)cookie)->dbg_tick;
+ return &((funccall_T *)cookie)->fc_dbg_tick;
}
/// @return the nesting level for a funccall cookie.
int func_level(void *cookie)
{
- return ((funccall_T *)cookie)->level;
+ return ((funccall_T *)cookie)->fc_level;
}
/// @return true when a function was ended by a ":return" command.
int current_func_returned(void)
{
- return current_funccal->returned;
+ return current_funccal->fc_returned;
}
bool free_unref_funccal(int copyID, int testing)
@@ -3385,12 +3652,12 @@ bool free_unref_funccal(int copyID, int testing)
for (funccall_T **pfc = &previous_funccal; *pfc != NULL;) {
if (can_free_funccal(*pfc, copyID)) {
funccall_T *fc = *pfc;
- *pfc = fc->caller;
+ *pfc = fc->fc_caller;
free_funccal_contents(fc);
did_free = true;
did_free_funccal = true;
} else {
- pfc = &(*pfc)->caller;
+ pfc = &(*pfc)->fc_caller;
}
}
if (did_free_funccal) {
@@ -3407,7 +3674,7 @@ funccall_T *get_funccal(void)
funccall_T *funccal = current_funccal;
if (debug_backtrace_level > 0) {
for (int i = 0; i < debug_backtrace_level; i++) {
- funccall_T *temp_funccal = funccal->caller;
+ funccall_T *temp_funccal = funccal->fc_caller;
if (temp_funccal) {
funccal = temp_funccal;
} else {
@@ -3427,7 +3694,7 @@ hashtab_T *get_funccal_local_ht(void)
if (current_funccal == NULL) {
return NULL;
}
- return &get_funccal()->l_vars.dv_hashtab;
+ return &get_funccal()->fc_l_vars.dv_hashtab;
}
/// @return the l: scope variable or
@@ -3437,7 +3704,7 @@ dictitem_T *get_funccal_local_var(void)
if (current_funccal == NULL) {
return NULL;
}
- return (dictitem_T *)&get_funccal()->l_vars_var;
+ return (dictitem_T *)&get_funccal()->fc_l_vars_var;
}
/// @return the hashtable used for argument in the current funccal or
@@ -3447,7 +3714,7 @@ hashtab_T *get_funccal_args_ht(void)
if (current_funccal == NULL) {
return NULL;
}
- return &get_funccal()->l_avars.dv_hashtab;
+ return &get_funccal()->fc_l_avars.dv_hashtab;
}
/// @return the a: scope variable or
@@ -3457,14 +3724,14 @@ dictitem_T *get_funccal_args_var(void)
if (current_funccal == NULL) {
return NULL;
}
- return (dictitem_T *)&current_funccal->l_avars_var;
+ return (dictitem_T *)&current_funccal->fc_l_avars_var;
}
/// List function variables, if there is a function.
void list_func_vars(int *first)
{
if (current_funccal != NULL) {
- list_hashtable_vars(&current_funccal->l_vars.dv_hashtab, "l:", false,
+ list_hashtable_vars(&current_funccal->fc_l_vars.dv_hashtab, "l:", false,
first);
}
}
@@ -3473,8 +3740,8 @@ void list_func_vars(int *first)
/// funccal, return the dict that contains it. Otherwise return NULL.
dict_T *get_current_funccal_dict(hashtab_T *ht)
{
- if (current_funccal != NULL && ht == &current_funccal->l_vars.dv_hashtab) {
- return &current_funccal->l_vars;
+ if (current_funccal != NULL && ht == &current_funccal->fc_l_vars.dv_hashtab) {
+ return &current_funccal->fc_l_vars;
}
return NULL;
}
@@ -3482,7 +3749,7 @@ dict_T *get_current_funccal_dict(hashtab_T *ht)
/// Search hashitem in parent scope.
hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) {
return NULL;
}
@@ -3492,7 +3759,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
const char *varname;
// Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
while (current_funccal != NULL) {
hashtab_T *ht = find_var_ht(name, namelen, &varname);
if (ht != NULL && *varname != NUL) {
@@ -3502,10 +3769,10 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
break;
}
}
- if (current_funccal == current_funccal->func->uf_scoped) {
+ if (current_funccal == current_funccal->fc_func->uf_scoped) {
break;
}
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
}
current_funccal = old_current_funccal;
@@ -3515,7 +3782,7 @@ hashitem_T *find_hi_in_scoped_ht(const char *name, hashtab_T **pht)
/// Search variable in parent scope.
dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no_autoload)
{
- if (current_funccal == NULL || current_funccal->func->uf_scoped == NULL) {
+ if (current_funccal == NULL || current_funccal->fc_func->uf_scoped == NULL) {
return NULL;
}
@@ -3524,7 +3791,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
const char *varname;
// Search in parent scope which is possible to reference from lambda
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
while (current_funccal) {
hashtab_T *ht = find_var_ht(name, namelen, &varname);
if (ht != NULL && *varname != NUL) {
@@ -3534,10 +3801,10 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
break;
}
}
- if (current_funccal == current_funccal->func->uf_scoped) {
+ if (current_funccal == current_funccal->fc_func->uf_scoped) {
break;
}
- current_funccal = current_funccal->func->uf_scoped;
+ current_funccal = current_funccal->fc_func->uf_scoped;
}
current_funccal = old_current_funccal;
@@ -3548,11 +3815,11 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, int no
bool set_ref_in_previous_funccal(int copyID)
{
for (funccall_T *fc = previous_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
fc->fc_copyID = copyID + 1;
- if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL)
- || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL)) {
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_list(&fc->fc_l_varlist, copyID + 1, NULL)) {
return true;
}
}
@@ -3563,10 +3830,10 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
{
if (fc->fc_copyID != copyID) {
fc->fc_copyID = copyID;
- if (set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL)
- || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL)
- || set_ref_in_list(&fc->l_varlist, copyID, NULL)
- || set_ref_in_func(NULL, fc->func, copyID)) {
+ if (set_ref_in_ht(&fc->fc_l_vars.dv_hashtab, copyID, NULL)
+ || set_ref_in_ht(&fc->fc_l_avars.dv_hashtab, copyID, NULL)
+ || set_ref_in_list(&fc->fc_l_varlist, copyID, NULL)
+ || set_ref_in_func(NULL, fc->fc_func, copyID)) {
return true;
}
}
@@ -3577,7 +3844,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
bool set_ref_in_call_stack(int copyID)
{
for (funccall_T *fc = current_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
if (set_ref_in_funccal(fc, copyID)) {
return true;
}
@@ -3587,7 +3854,7 @@ bool set_ref_in_call_stack(int copyID)
for (funccal_entry_T *entry = funccal_stack; entry != NULL;
entry = entry->next) {
for (funccall_T *fc = entry->top_funccal; fc != NULL;
- fc = fc->caller) {
+ fc = fc->fc_caller) {
if (set_ref_in_funccal(fc, copyID)) {
return true;
}
@@ -3600,15 +3867,11 @@ bool set_ref_in_call_stack(int copyID)
/// Set "copyID" in all functions available by name.
bool set_ref_in_functions(int copyID)
{
- int todo;
- hashitem_T *hi = NULL;
- ufunc_T *fp;
-
- todo = (int)func_hashtab.ht_used;
- for (hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
+ int todo = (int)func_hashtab.ht_used;
+ for (hashitem_T *hi = func_hashtab.ht_array; todo > 0 && !got_int; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if (!func_name_refcount(fp->uf_name)
&& set_ref_in_func(NULL, fp, copyID)) {
return true;
@@ -3638,22 +3901,20 @@ bool set_ref_in_func_args(int copyID)
bool set_ref_in_func(char *name, ufunc_T *fp_in, int copyID)
{
ufunc_T *fp = fp_in;
- funccall_T *fc;
int error = FCERR_NONE;
char fname_buf[FLEN_FIXED + 1];
char *tofree = NULL;
- char *fname;
bool abort = false;
if (name == NULL && fp_in == NULL) {
return false;
}
if (fp_in == NULL) {
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ char *fname = fname_trans_sid(name, fname_buf, &tofree, &error);
fp = find_func(fname);
}
if (fp != NULL) {
- for (fc = fp->uf_scoped; fc != NULL; fc = fc->func->uf_scoped) {
+ for (funccall_T *fc = fp->uf_scoped; fc != NULL; fc = fc->fc_func->uf_scoped) {
abort = abort || set_ref_in_funccal(fc, copyID);
}
}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index c8583f232c..8050caab2b 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -1,22 +1,21 @@
-#ifndef NVIM_EVAL_USERFUNC_H
-#define NVIM_EVAL_USERFUNC_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/hashtab.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/hashtab_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
struct funccal_entry;
// From user function to hashitem and back.
#define UF2HIKEY(fp) ((fp)->uf_name)
-#define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name)))
+#define HIKEY2UF(p) ((ufunc_T *)((p) - offsetof(ufunc_T, uf_name)))
#define HI2UF(hi) HIKEY2UF((hi)->hi_key)
// flags used in uf_flags
@@ -27,10 +26,10 @@ struct funccal_entry;
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
#define FC_SANDBOX 0x40 // function defined in the sandbox
-#define FC_DEAD 0x80 // function kept only for reference to dfunc
-#define FC_EXPORT 0x100 // "export def Func()"
+// #define FC_DEAD 0x80 // function kept only for reference to dfunc
+// #define FC_EXPORT 0x100 // "export def Func()"
#define FC_NOARGS 0x200 // no a: variables in lambda
-#define FC_VIM9 0x400 // defined in vim9 script file
+// #define FC_VIM9 0x400 // defined in vim9 script file
#define FC_LUAREF 0x800 // luaref callback
/// Structure used by trans_function_name()
@@ -74,6 +73,8 @@ typedef struct {
partial_T *fe_partial; ///< for extra arguments
dict_T *fe_selfdict; ///< Dictionary for "self"
typval_T *fe_basetv; ///< base for base->method()
+ bool fe_found_var; ///< if the function is not found then give an
+ ///< error that a variable is not callable.
} funcexe_T;
#define FUNCEXE_INIT (funcexe_T) { \
@@ -85,6 +86,7 @@ typedef struct {
.fe_partial = NULL, \
.fe_selfdict = NULL, \
.fe_basetv = NULL, \
+ .fe_found_var = false, \
}
#define FUNCARG(fp, j) ((char **)(fp->uf_args.ga_data))[j]
@@ -93,4 +95,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/userfunc.h.generated.h"
#endif
-#endif // NVIM_EVAL_USERFUNC_H
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 9ed245d6c4..2968f75f4d 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/vars.c: functions for dealing with variables
#include <assert.h>
@@ -8,9 +5,11 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
@@ -19,7 +18,6 @@
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
@@ -27,19 +25,23 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -50,8 +52,102 @@
#define DICT_MAXNEST 100 // maximum nesting of lists and dicts
-static char *e_letunexp = N_("E18: Unexpected characters in :let");
-static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char *e_letunexp = N_("E18: Unexpected characters in :let");
+static const char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
+static const char e_setting_v_str_to_value_with_wrong_type[]
+ = N_("E963: Setting v:%s to value with wrong type");
+static const char e_cannot_use_heredoc_here[]
+ = N_("E991: Cannot use =<< here");
+
+/// Evaluate one Vim expression {expr} in string "p" and append the
+/// resulting string to "gap". "p" points to the opening "{".
+/// When "evaluate" is false only skip over the expression.
+/// Return a pointer to the character after "}", NULL for an error.
+char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
+{
+ char *block_start = skipwhite(p + 1); // skip the opening {
+ char *block_end = block_start;
+
+ if (*block_start == NUL) {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (skip_expr(&block_end, NULL) == FAIL) {
+ return NULL;
+ }
+ block_end = skipwhite(block_end);
+ if (*block_end != '}') {
+ semsg(_(e_missing_close_curly_str), p);
+ return NULL;
+ }
+ if (evaluate) {
+ *block_end = NUL;
+ char *expr_val = eval_to_string(block_start, true);
+ *block_end = '}';
+ if (expr_val == NULL) {
+ return NULL;
+ }
+ ga_concat(gap, expr_val);
+ xfree(expr_val);
+ }
+
+ return block_end + 1;
+}
+
+/// Evaluate all the Vim expressions {expr} in "str" and return the resulting
+/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}".
+/// Used for a heredoc assignment.
+/// Returns NULL for an error.
+char *eval_all_expr_in_str(char *str)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 80);
+ char *p = str;
+
+ while (*p != NUL) {
+ bool escaped_brace = false;
+
+ // Look for a block start.
+ char *lit_start = p;
+ while (*p != '{' && *p != '}' && *p != NUL) {
+ p++;
+ }
+
+ if (*p != NUL && *p == p[1]) {
+ // Escaped brace, unescape and continue.
+ // Include the brace in the literal string.
+ p++;
+ escaped_brace = true;
+ } else if (*p == '}') {
+ semsg(_(e_stray_closing_curly_str), str);
+ ga_clear(&ga);
+ return NULL;
+ }
+
+ // Append the literal part.
+ ga_concat_len(&ga, lit_start, (size_t)(p - lit_start));
+
+ if (*p == NUL) {
+ break;
+ }
+
+ if (escaped_brace) {
+ // Skip the second brace.
+ p++;
+ continue;
+ }
+
+ // Evaluate the expression and append the result.
+ p = eval_one_expr_in_str(p, &ga, true);
+ if (p == NULL) {
+ ga_clear(&ga);
+ return NULL;
+ }
+ }
+ ga_append(&ga, NUL);
+
+ return ga.ga_data;
+}
/// Get a list of lines from a HERE document. The here document is a list of
/// lines surrounded by a marker.
@@ -65,64 +161,91 @@ static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s");
/// marker, then the leading indentation before the lines (matching the
/// indentation in the 'cmd' line) is stripped.
///
-/// @return a List with {lines} or NULL.
-static list_T *heredoc_get(exarg_T *eap, char *cmd)
+/// When getting lines for an embedded script (e.g. python, lua, perl, ruby,
+/// tcl, mzscheme), "script_get" is set to true. In this case, if the marker is
+/// missing, then '.' is accepted as a marker.
+///
+/// @return a List with {lines} or NULL on failure.
+list_T *heredoc_get(exarg_T *eap, char *cmd, bool script_get)
{
char *marker;
- char *p;
int marker_indent_len = 0;
int text_indent_len = 0;
char *text_indent = NULL;
+ char dot[] = ".";
if (eap->getline == NULL) {
- emsg(_("E991: cannot use =<< here"));
+ emsg(_(e_cannot_use_heredoc_here));
return NULL;
}
// Check for the optional 'trim' word before the marker
cmd = skipwhite(cmd);
- if (strncmp(cmd, "trim", 4) == 0
- && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
- cmd = skipwhite(cmd + 4);
-
- // Trim the indentation from all the lines in the here document.
- // The amount of indentation trimmed is the same as the indentation of
- // the first line after the :let command line. To find the end marker
- // the indent of the :let command line is trimmed.
- p = *eap->cmdlinep;
- while (ascii_iswhite(*p)) {
- p++;
- marker_indent_len++;
+ bool evalstr = false;
+ bool eval_failed = false;
+ while (true) {
+ if (strncmp(cmd, "trim", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+
+ // Trim the indentation from all the lines in the here document.
+ // The amount of indentation trimmed is the same as the indentation
+ // of the first line after the :let command line. To find the end
+ // marker the indent of the :let command line is trimmed.
+ char *p = *eap->cmdlinep;
+ while (ascii_iswhite(*p)) {
+ p++;
+ marker_indent_len++;
+ }
+ text_indent_len = -1;
+
+ continue;
+ }
+ if (strncmp(cmd, "eval", 4) == 0
+ && (cmd[4] == NUL || ascii_iswhite(cmd[4]))) {
+ cmd = skipwhite(cmd + 4);
+ evalstr = true;
+ continue;
}
- text_indent_len = -1;
+ break;
}
// The marker is the next word.
if (*cmd != NUL && *cmd != '"') {
marker = skipwhite(cmd);
- p = skiptowhite(marker);
+ char *p = skiptowhite(marker);
if (*skipwhite(p) != NUL && *skipwhite(p) != '"') {
semsg(_(e_trailing_arg), p);
return NULL;
}
*p = NUL;
- if (islower((uint8_t)(*marker))) {
+ if (!script_get && islower((uint8_t)(*marker))) {
emsg(_("E221: Marker cannot start with lower case letter"));
return NULL;
}
} else {
- emsg(_("E172: Missing marker"));
- return NULL;
+ // When getting lines for an embedded script, if the marker is missing,
+ // accept '.' as the marker.
+ if (script_get) {
+ marker = dot;
+ } else {
+ emsg(_("E172: Missing marker"));
+ return NULL;
+ }
}
+ char *theline = NULL;
list_T *l = tv_list_alloc(0);
- for (;;) {
+ while (true) {
int mi = 0;
int ti = 0;
- char *theline = eap->getline(NUL, eap->cookie, 0, false);
+ xfree(theline);
+ theline = eap->getline(NUL, eap->cookie, 0, false);
if (theline == NULL) {
- semsg(_("E990: Missing end marker '%s'"), marker);
+ if (!script_get) {
+ semsg(_("E990: Missing end marker '%s'"), marker);
+ }
break;
}
@@ -133,18 +256,24 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
mi = marker_indent_len;
}
if (strcmp(marker, theline + mi) == 0) {
- xfree(theline);
break;
}
+
+ // If expression evaluation failed in the heredoc, then skip till the
+ // end marker.
+ if (eval_failed) {
+ continue;
+ }
+
if (text_indent_len == -1 && *theline != NUL) {
// set the text indent from the first line.
- p = theline;
+ char *p = theline;
text_indent_len = 0;
while (ascii_iswhite(*p)) {
p++;
text_indent_len++;
}
- text_indent = xstrnsave(theline, (size_t)text_indent_len);
+ text_indent = xmemdupz(theline, (size_t)text_indent_len);
}
// with "trim": skip the indent matching the first line
if (text_indent != NULL) {
@@ -155,11 +284,28 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
}
}
- tv_list_append_string(l, theline + ti, -1);
- xfree(theline);
+ char *str = theline + ti;
+ if (evalstr && !eap->skip) {
+ str = eval_all_expr_in_str(str);
+ if (str == NULL) {
+ // expression evaluation failed
+ eval_failed = true;
+ continue;
+ }
+ xfree(theline);
+ theline = str;
+ }
+
+ tv_list_append_string(l, str, -1);
}
+ xfree(theline);
xfree(text_indent);
+ if (eval_failed) {
+ // expression evaluation in the heredoc failed
+ tv_list_free(l);
+ return NULL;
+ }
return l;
}
@@ -175,32 +321,23 @@ static list_T *heredoc_get(exarg_T *eap, char *cmd)
/// ":let var ..= expr" assignment command.
/// ":let [var1, var2] = expr" unpack list.
/// ":let [name, ..., ; lastname] = expr" unpack list.
-void ex_let(exarg_T *eap)
-{
- ex_let_const(eap, false);
-}
-
+///
/// ":cons[t] var = expr1" define constant
/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list
/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list
-void ex_const(exarg_T *eap)
-{
- ex_let_const(eap, true);
-}
-
-static void ex_let_const(exarg_T *eap, const bool is_const)
+void ex_let(exarg_T *eap)
{
+ const bool is_const = eap->cmdidx == CMD_const;
char *arg = eap->arg;
char *expr = NULL;
typval_T rettv;
- int i;
int var_count = 0;
int semicolon = 0;
char op[2];
- char *argend;
+ const char *argend;
int first = true;
- argend = (char *)skip_var_list(arg, &var_count, &semicolon);
+ argend = skip_var_list(arg, &var_count, &semicolon);
if (argend == NULL) {
return;
}
@@ -208,14 +345,16 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
argend--;
}
expr = skipwhite(argend);
- if (*expr != '=' && !((vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
- && expr[1] == '=') || strncmp(expr, "..=", 3) == 0)) {
+ bool concat = strncmp(expr, "..=", 3) == 0;
+ bool has_assign = *expr == '=' || (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL
+ && expr[1] == '=');
+ if (!has_assign && !concat) {
// ":let" without "=": list variables
if (*arg == '[') {
emsg(_(e_invarg));
} else if (!ends_excmd(*arg)) {
// ":let var1 var2"
- arg = (char *)list_arg_vars(eap, (const char *)arg, &first);
+ arg = (char *)list_arg_vars(eap, arg, &first);
} else if (!eap->skip) {
// ":let"
list_glob_vars(&first);
@@ -227,50 +366,58 @@ static void ex_let_const(exarg_T *eap, const bool is_const)
list_vim_vars(&first);
}
eap->nextcmd = check_nextcmd(arg);
- } else if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
+ return;
+ }
+
+ if (expr[0] == '=' && expr[1] == '<' && expr[2] == '<') {
// HERE document
- list_T *l = heredoc_get(eap, expr + 3);
+ list_T *l = heredoc_get(eap, expr + 3, false);
if (l != NULL) {
tv_list_set_ret(&rettv, l);
if (!eap->skip) {
op[0] = '=';
op[1] = NUL;
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
}
tv_clear(&rettv);
}
- } else {
- op[0] = '=';
- op[1] = NUL;
- if (*expr != '=') {
- if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
- op[0] = *expr; // +=, -=, *=, /=, %= or .=
- if (expr[0] == '.' && expr[1] == '.') { // ..=
- expr++;
- }
- }
- expr += 2;
- } else {
- expr += 1;
- }
+ return;
+ }
- expr = skipwhite(expr);
+ rettv.v_type = VAR_UNKNOWN;
- if (eap->skip) {
- emsg_skip++;
- }
- i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip);
- if (eap->skip) {
- if (i != FAIL) {
- tv_clear(&rettv);
+ op[0] = '=';
+ op[1] = NUL;
+ if (*expr != '=') {
+ if (vim_strchr("+-*/%.", (uint8_t)(*expr)) != NULL) {
+ op[0] = *expr; // +=, -=, *=, /=, %= or .=
+ if (expr[0] == '.' && expr[1] == '.') { // ..=
+ expr++;
}
- emsg_skip--;
- } else if (i != FAIL) {
- (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count,
- is_const, (char *)op);
- tv_clear(&rettv);
}
+ expr += 2;
+ } else {
+ expr += 1;
+ }
+
+ expr = skipwhite(expr);
+
+ if (eap->skip) {
+ emsg_skip++;
+ }
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
+ int eval_res = eval0(expr, &rettv, eap, &evalarg);
+ if (eap->skip) {
+ emsg_skip--;
+ }
+ clear_evalarg(&evalarg, eap);
+
+ if (!eap->skip && eval_res != FAIL) {
+ (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, is_const, op);
+ }
+ if (eval_res != FAIL) {
+ tv_clear(&rettv);
}
}
@@ -369,15 +516,13 @@ int ex_let_vars(char *arg_start, typval_T *tv, int copy, int semicolon, int var_
/// @return NULL for an error.
const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
{
- const char *p;
- const char *s;
-
if (*arg == '[') {
+ const char *s;
// "[var, var]": find the matching ']'.
- p = arg;
- for (;;) {
+ const char *p = arg;
+ while (true) {
p = skipwhite(p + 1); // skip whites after '[', ';' or ','
- s = skip_var_one((char *)p);
+ s = skip_var_one(p);
if (s == p) {
semsg(_(e_invarg2), p);
return NULL;
@@ -400,7 +545,7 @@ const char *skip_var_list(const char *arg, int *var_count, int *semicolon)
}
return p + 1;
}
- return skip_var_one((char *)arg);
+ return skip_var_one(arg);
}
/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key,
@@ -410,8 +555,8 @@ static const char *skip_var_one(const char *arg)
if (*arg == '@' && arg[1] != NUL) {
return arg + 2;
}
- return (char *)find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
- NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
+ return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg,
+ NULL, NULL, FNE_INCL_BR | FNE_CHECK_START);
}
/// List variables for hashtab "ht" with prefix "prefix".
@@ -432,7 +577,7 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs
// apply :filter /pat/ to variable name
xstrlcpy(buf, prefix, IOSIZE);
- xstrlcat(buf, (char *)di->di_key, IOSIZE);
+ xstrlcat(buf, di->di_key, IOSIZE);
if (message_filtered(buf)) {
continue;
}
@@ -504,13 +649,12 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
if (tofree != NULL) {
name = tofree;
}
- if (get_var_tv(name, len, &tv, NULL, true, false)
- == FAIL) {
+ if (eval_variable(name, len, &tv, NULL, true, false) == FAIL) {
error = true;
} else {
// handle d.key, l[idx], f(expr)
const char *const arg_subsc = arg;
- if (handle_subscript(&arg, &tv, true, true, name, &name) == FAIL) {
+ if (handle_subscript(&arg, &tv, &EVALARG_EVALUATE, true) == FAIL) {
error = true;
} else {
if (arg == arg_subsc && len == 2 && name[1] == ':') {
@@ -553,12 +697,191 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
xfree(tofree);
}
- arg = (const char *)skipwhite(arg);
+ arg = skipwhite(arg);
}
return arg;
}
+/// Set an environment variable, part of ex_let_one().
+static char *ex_let_env(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an environment variable"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ arg++;
+ char *name = arg;
+ int len = get_env_len((const char **)&arg);
+ if (len == 0) {
+ semsg(_(e_invarg2), name - 1);
+ } else {
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
+ emsg(_(e_letunexp));
+ } else if (!check_secure()) {
+ char *tofree = NULL;
+ const char c1 = name[len];
+ name[len] = NUL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = vim_getenv(name);
+ if (s != NULL) {
+ tofree = concat_str(s, p);
+ p = tofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ vim_setenv_ext(name, p);
+ arg_end = arg;
+ }
+ name[len] = c1;
+ xfree(tofree);
+ }
+ }
+ return arg_end;
+}
+
+/// Set an option, part of ex_let_one().
+static char *ex_let_option(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock an option"));
+ return NULL;
+ }
+
+ // Find the end of the name.
+ char *arg_end = NULL;
+ int scope;
+ char *const p = (char *)find_option_end((const char **)&arg, &scope);
+ if (p == NULL
+ || (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
+ emsg(_(e_letunexp));
+ return NULL;
+ }
+
+ const char c1 = *p;
+ *p = NUL;
+
+ uint32_t opt_p_flags;
+ bool hidden;
+ OptVal curval = get_option_value(arg, &opt_p_flags, scope, &hidden);
+ OptVal newval = NIL_OPTVAL;
+ if (curval.type == kOptValTypeNil && arg[0] != 't' && arg[1] != '_') {
+ semsg(_(e_unknown_option2), arg);
+ goto theend;
+ }
+ if (op != NULL && *op != '='
+ && ((curval.type != kOptValTypeString && *op == '.')
+ || (curval.type == kOptValTypeString && *op != '.'))) {
+ semsg(_(e_letwrong), op);
+ goto theend;
+ }
+
+ bool error;
+ newval = tv_to_optval(tv, arg, opt_p_flags, &error);
+ if (error) {
+ goto theend;
+ }
+
+ // Don't assume current and new values are of the same type in order to future-proof the code for
+ // when an option can have multiple types.
+ const bool is_num = ((curval.type == kOptValTypeNumber || curval.type == kOptValTypeBoolean)
+ && (newval.type == kOptValTypeNumber || newval.type == kOptValTypeBoolean));
+ const bool is_string = curval.type == kOptValTypeString && newval.type == kOptValTypeString;
+
+ if (op != NULL && *op != '=') {
+ if (!hidden && is_num) { // number or bool
+ OptInt cur_n = curval.type == kOptValTypeNumber ? curval.data.number : curval.data.boolean;
+ OptInt new_n = newval.type == kOptValTypeNumber ? newval.data.number : newval.data.boolean;
+
+ switch (*op) {
+ case '+':
+ new_n = cur_n + new_n; break;
+ case '-':
+ new_n = cur_n - new_n; break;
+ case '*':
+ new_n = cur_n * new_n; break;
+ case '/':
+ new_n = num_divide(cur_n, new_n); break;
+ case '%':
+ new_n = num_modulus(cur_n, new_n); break;
+ }
+
+ if (curval.type == kOptValTypeNumber) {
+ newval = NUMBER_OPTVAL(new_n);
+ } else {
+ newval = BOOLEAN_OPTVAL(TRISTATE_FROM_INT(new_n));
+ }
+ } else if (!hidden && is_string
+ && curval.data.string.data != NULL && newval.data.string.data != NULL) { // string
+ OptVal newval_old = newval;
+ newval = CSTR_AS_OPTVAL(concat_str(curval.data.string.data, newval.data.string.data));
+ optval_free(newval_old);
+ }
+ }
+
+ const char *err = set_option_value(arg, newval, scope);
+ arg_end = p;
+ if (err != NULL) {
+ emsg(_(err));
+ }
+
+theend:
+ *p = c1;
+ optval_free(curval);
+ optval_free(newval);
+ return arg_end;
+}
+
+/// Set a register, part of ex_let_one().
+static char *ex_let_register(char *arg, typval_T *const tv, const bool is_const,
+ const char *const endchars, const char *const op)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (is_const) {
+ emsg(_("E996: Cannot lock a register"));
+ return NULL;
+ }
+
+ char *arg_end = NULL;
+ arg++;
+ if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ semsg(_(e_letwrong), op);
+ } else if (endchars != NULL
+ && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
+ emsg(_(e_letunexp));
+ } else {
+ char *ptofree = NULL;
+ const char *p = tv_get_string_chk(tv);
+ if (p != NULL && op != NULL && *op == '.') {
+ char *s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
+ if (s != NULL) {
+ ptofree = concat_str(s, p);
+ p = ptofree;
+ xfree(s);
+ }
+ }
+ if (p != NULL) {
+ write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
+ arg_end = arg + 1;
+ }
+ xfree(ptofree);
+ }
+ return arg_end;
+}
+
/// Set one item of `:let var = expr` or `:let [v1, v2] = list` to its value
///
/// @param[in] arg Start of the variable name.
@@ -575,176 +898,22 @@ static char *ex_let_one(char *arg, typval_T *const tv, const bool copy, const bo
FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
{
char *arg_end = NULL;
- int len;
- // ":let $VAR = expr": Set environment variable.
if (*arg == '$') {
- if (is_const) {
- emsg(_("E996: Cannot lock an environment variable"));
- return NULL;
- }
- // Find the end of the name.
- arg++;
- char *name = arg;
- len = get_env_len((const char **)&arg);
- if (len == 0) {
- semsg(_(e_invarg2), name - 1);
- } else {
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg))) == NULL) {
- emsg(_(e_letunexp));
- } else if (!check_secure()) {
- char *tofree = NULL;
- const char c1 = name[len];
- name[len] = NUL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- char *s = vim_getenv(name);
- if (s != NULL) {
- tofree = concat_str(s, p);
- p = (const char *)tofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- vim_setenv_ext(name, p);
- arg_end = arg;
- }
- name[len] = c1;
- xfree(tofree);
- }
- }
+ // ":let $VAR = expr": Set environment variable.
+ return ex_let_env(arg, tv, is_const, endchars, op);
+ } else if (*arg == '&') {
// ":let &option = expr": Set option value.
// ":let &l:option = expr": Set local option value.
// ":let &g:option = expr": Set global option value.
- } else if (*arg == '&') {
- if (is_const) {
- emsg(_("E996: Cannot lock an option"));
- return NULL;
- }
- // Find the end of the name.
- int scope;
- char *const p = (char *)find_option_end((const char **)&arg, &scope);
- if (p == NULL
- || (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL)) {
- emsg(_(e_letunexp));
- } else {
- varnumber_T n = 0;
- getoption_T opt_type;
- long numval;
- char *stringval = NULL;
- const char *s = NULL;
- bool failed = false;
- uint32_t opt_p_flags;
- char *tofree = NULL;
-
- const char c1 = *p;
- *p = NUL;
-
- opt_type = get_option_value(arg, &numval, &stringval, &opt_p_flags, scope);
- if (opt_type == gov_bool
- || opt_type == gov_number
- || opt_type == gov_hidden_bool
- || opt_type == gov_hidden_number) {
- // number, possibly hidden
- n = (long)tv_get_number(tv);
- }
-
- if ((opt_p_flags & P_FUNC) && tv_is_func(*tv)) {
- // If the option can be set to a function reference or a lambda
- // and the passed value is a function reference, then convert it to
- // the name (string) of the function reference.
- s = tofree = encode_tv2string(tv, NULL);
- } else if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
- // Avoid setting a string option to the text "v:false" or similar.
- s = tv_get_string_chk(tv);
- }
-
- if (op != NULL && *op != '=') {
- if (((opt_type == gov_bool || opt_type == gov_number) && *op == '.')
- || (opt_type == gov_string && *op != '.')) {
- semsg(_(e_letwrong), op);
- failed = true; // don't set the value
- } else {
- // number or bool
- if (opt_type == gov_number || opt_type == gov_bool) {
- switch (*op) {
- case '+':
- n = numval + n; break;
- case '-':
- n = numval - n; break;
- case '*':
- n = numval * n; break;
- case '/':
- n = num_divide(numval, n); break;
- case '%':
- n = num_modulus(numval, n); break;
- }
- s = NULL;
- } else if (opt_type == gov_string && stringval != NULL && s != NULL) {
- // string
- char *const oldstringval = stringval;
- stringval = concat_str(stringval, s);
- xfree(oldstringval);
- s = stringval;
- }
- }
- }
-
- if (!failed) {
- if (opt_type != gov_string || s != NULL) {
- char *err = set_option_value(arg, n, s, scope);
- arg_end = p;
- if (err != NULL) {
- emsg(_(err));
- }
- } else {
- emsg(_(e_stringreq));
- }
- }
- *p = c1;
- xfree(stringval);
- xfree(tofree);
- }
- // ":let @r = expr": Set register contents.
+ return ex_let_option(arg, tv, is_const, endchars, op);
} else if (*arg == '@') {
- if (is_const) {
- emsg(_("E996: Cannot lock a register"));
- return NULL;
- }
- arg++;
- if (op != NULL && vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- semsg(_(e_letwrong), op);
- } else if (endchars != NULL
- && vim_strchr(endchars, (uint8_t)(*skipwhite(arg + 1))) == NULL) {
- emsg(_(e_letunexp));
- } else {
- char *s;
-
- char *ptofree = NULL;
- const char *p = tv_get_string_chk(tv);
- if (p != NULL && op != NULL && *op == '.') {
- s = get_reg_contents(*arg == '@' ? '"' : *arg, kGRegExprSrc);
- if (s != NULL) {
- ptofree = concat_str(s, p);
- p = (const char *)ptofree;
- xfree(s);
- }
- }
- if (p != NULL) {
- write_reg_contents(*arg == '@' ? '"' : *arg, p, (ssize_t)strlen(p), false);
- arg_end = arg + 1;
- }
- xfree(ptofree);
- }
+ // ":let @r = expr": Set register contents.
+ return ex_let_register(arg, tv, is_const, endchars, op);
+ } else if (eval_isnamec1(*arg) || *arg == '{') {
// ":let var = expr": Set internal variable.
// ":let {expr} = expr": Idem, name made with curly braces
- } else if (eval_isnamec1(*arg) || *arg == '{') {
lval_T lv;
-
char *const p = get_lval(arg, tv, &lv, false, false, 0, FNE_CHECK_START);
if (p != NULL && lv.ll_name != NULL) {
if (endchars != NULL && vim_strchr(endchars, (uint8_t)(*skipwhite(p))) == NULL) {
@@ -803,7 +972,7 @@ static void ex_unletlock(exarg_T *eap, char *argstart, int deep, ex_unletlock_ca
do {
if (*arg == '$') {
- lv.ll_name = (const char *)arg;
+ lv.ll_name = arg;
lv.ll_tv = NULL;
arg++;
if (get_env_len((const char **)&arg) == 0) {
@@ -861,10 +1030,9 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
{
int forceit = eap->forceit;
int ret = OK;
- int cc;
if (lp->ll_tv == NULL) {
- cc = (uint8_t)(*name_end);
+ int cc = (uint8_t)(*name_end);
*name_end = NUL;
// Environment variable, normal name or expanded name.
@@ -886,57 +1054,57 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
lp->ll_name_len))) {
return FAIL;
} else if (lp->ll_range) {
- assert(lp->ll_list != NULL);
- // Delete a range of List items.
- listitem_T *const first_li = lp->ll_li;
- listitem_T *last_li = first_li;
- for (;;) {
- listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li);
- if (value_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock,
- lp->ll_name,
- lp->ll_name_len)) {
- return false;
- }
- lp->ll_li = li;
- lp->ll_n1++;
- if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) {
- break;
- }
- last_li = lp->ll_li;
- }
- tv_list_remove_items(lp->ll_list, first_li, last_li);
+ tv_list_unlet_range(lp->ll_list, lp->ll_li, lp->ll_n1, !lp->ll_empty2, lp->ll_n2);
+ } else if (lp->ll_list != NULL) {
+ // unlet a List item.
+ tv_list_item_remove(lp->ll_list, lp->ll_li);
} else {
- if (lp->ll_list != NULL) {
- // unlet a List item.
- tv_list_item_remove(lp->ll_list, lp->ll_li);
- } else {
- // unlet a Dictionary item.
- dict_T *d = lp->ll_dict;
- assert(d != NULL);
- dictitem_T *di = lp->ll_di;
- bool watched = tv_dict_is_watched(d);
- char *key = NULL;
- typval_T oldtv;
+ // unlet a Dictionary item.
+ dict_T *d = lp->ll_dict;
+ assert(d != NULL);
+ dictitem_T *di = lp->ll_di;
+ bool watched = tv_dict_is_watched(d);
+ char *key = NULL;
+ typval_T oldtv;
- if (watched) {
- tv_copy(&di->di_tv, &oldtv);
- // need to save key because dictitem_remove will free it
- key = xstrdup((char *)di->di_key);
- }
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ // need to save key because dictitem_remove will free it
+ key = xstrdup(di->di_key);
+ }
- tv_dict_item_remove(d, di);
+ tv_dict_item_remove(d, di);
- if (watched) {
- tv_dict_watcher_notify(d, key, NULL, &oldtv);
- tv_clear(&oldtv);
- xfree(key);
- }
+ if (watched) {
+ tv_dict_watcher_notify(d, key, NULL, &oldtv);
+ tv_clear(&oldtv);
+ xfree(key);
}
}
return ret;
}
+/// Unlet one item or a range of items from a list.
+/// Return OK or FAIL.
+static void tv_list_unlet_range(list_T *const l, listitem_T *const li_first, const int n1_arg,
+ const bool has_n2, const int n2)
+{
+ assert(l != NULL);
+ // Delete a range of List items.
+ listitem_T *li_last = li_first;
+ int n1 = n1_arg;
+ while (true) {
+ listitem_T *const li = TV_LIST_ITEM_NEXT(l, li_last);
+ n1++;
+ if (li == NULL || (has_n2 && n2 < n1)) {
+ break;
+ }
+ li_last = li;
+ }
+ tv_list_remove_items(l, li_first, li_last);
+}
+
/// unlet a variable
///
/// @param[in] name Variable name to unlet.
@@ -1081,8 +1249,8 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
/// @param dip non-NULL when typval's dict item is needed
/// @param verbose may give error message
/// @param no_autoload do not use script autoloading
-int get_var_tv(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
- bool no_autoload)
+int eval_variable(const char *name, int len, typval_T *rettv, dictitem_T **dip, bool verbose,
+ bool no_autoload)
{
int ret = OK;
typval_T *tv = NULL;
@@ -1157,7 +1325,7 @@ void vars_clear_ext(hashtab_T *ht, int free_val)
}
}
hash_clear(ht);
- ht->ht_used = 0;
+ hash_init(ht);
}
/// Delete a variable from hashtab "ht" at item "hi".
@@ -1175,7 +1343,7 @@ void delete_var(hashtab_T *ht, hashitem_T *hi)
static void list_one_var(dictitem_T *v, const char *prefix, int *first)
{
char *const s = encode_tv2echo(&v->di_tv, NULL);
- list_one_var_a(prefix, (const char *)v->di_key, (ptrdiff_t)strlen((char *)v->di_key),
+ list_one_var_a(prefix, v->di_key, (ptrdiff_t)strlen(v->di_key),
v->di_tv.v_type, (s == NULL ? "" : s), first);
xfree(s);
}
@@ -1186,11 +1354,11 @@ static void list_one_var(dictitem_T *v, const char *prefix, int *first)
static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t name_len,
const VarType type, const char *string, int *first)
{
- // don't use msg() or msg_attr() to avoid overwriting "v:statusmsg"
+ // don't use msg() to avoid overwriting "v:statusmsg"
msg_start();
msg_puts(prefix);
if (name != NULL) { // "a:" vars don't have a name stored
- msg_puts_attr_len(name, name_len, 0);
+ msg_puts_len(name, name_len, 0);
}
msg_putchar(' ');
msg_advance(22);
@@ -1212,7 +1380,7 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
msg_putchar(' ');
}
- msg_outtrans((char *)string);
+ msg_outtrans(string, 0);
if (type == VAR_FUNC || type == VAR_PARTIAL) {
msg_puts("()");
@@ -1223,6 +1391,62 @@ static void list_one_var_a(const char *prefix, const char *name, const ptrdiff_t
}
}
+/// Additional handling for setting a v: variable.
+///
+/// @return true if the variable should be set normally,
+/// false if nothing else needs to be done.
+bool before_set_vvar(const char *const varname, dictitem_T *const di, typval_T *const tv,
+ const bool copy, const bool watched, bool *const type_error)
+{
+ if (di->di_tv.v_type == VAR_STRING) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ XFREE_CLEAR(di->di_tv.vval.v_string);
+ if (copy || tv->v_type != VAR_STRING) {
+ const char *const val = tv_get_string(tv);
+ // Careful: when assigning to v:errmsg and tv_get_string()
+ // causes an error message the variable will already be set.
+ if (di->di_tv.vval.v_string == NULL) {
+ di->di_tv.vval.v_string = xstrdup(val);
+ }
+ } else {
+ // Take over the string to avoid an extra alloc/free.
+ di->di_tv.vval.v_string = tv->vval.v_string;
+ tv->vval.v_string = NULL;
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type == VAR_NUMBER) {
+ typval_T oldtv = TV_INITIAL_VALUE;
+ if (watched) {
+ tv_copy(&di->di_tv, &oldtv);
+ }
+ di->di_tv.vval.v_number = tv_get_number(tv);
+ if (strcmp(varname, "searchforward") == 0) {
+ set_search_direction(di->di_tv.vval.v_number ? '/' : '?');
+ } else if (strcmp(varname, "hlsearch") == 0) {
+ no_hlsearch = !di->di_tv.vval.v_number;
+ redraw_all_later(UPD_SOME_VALID);
+ }
+ // Notify watchers
+ if (watched) {
+ tv_dict_watcher_notify(&vimvardict, varname, &di->di_tv, &oldtv);
+ tv_clear(&oldtv);
+ }
+ return false;
+ } else if (di->di_tv.v_type != tv->v_type) {
+ *type_error = true;
+ return false;
+ }
+ return true;
+}
+
/// Set variable to the given value
///
/// If the variable already exists, the value is updated. Otherwise the variable
@@ -1252,31 +1476,29 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
const bool is_const)
FUNC_ATTR_NONNULL_ALL
{
- dictitem_T *v;
- hashtab_T *ht;
- dict_T *dict;
-
const char *varname;
- ht = find_var_ht_dict(name, name_len, &varname, &dict);
+ dict_T *dict;
+ hashtab_T *ht = find_var_ht_dict(name, name_len, &varname, &dict);
const bool watched = tv_dict_is_watched(dict);
if (ht == NULL || *varname == NUL) {
semsg(_(e_illvar), name);
return;
}
- v = find_var_in_ht(ht, 0, varname, name_len - (size_t)(varname - name), true);
+ const size_t varname_len = name_len - (size_t)(varname - name);
+ dictitem_T *di = find_var_in_ht(ht, 0, varname, varname_len, true);
// Search in parent scope which is possible to reference from lambda
- if (v == NULL) {
- v = find_var_in_scoped_ht(name, name_len, true);
+ if (di == NULL) {
+ di = find_var_in_scoped_ht(name, name_len, true);
}
- if (tv_is_func(*tv) && var_wrong_func_name(name, v == NULL)) {
+ if (tv_is_func(*tv) && var_wrong_func_name(name, di == NULL)) {
return;
}
typval_T oldtv = TV_INITIAL_VALUE;
- if (v != NULL) {
+ if (di != NULL) {
if (is_const) {
emsg(_(e_cannot_mod));
return;
@@ -1286,9 +1508,9 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// - Whether the variable is read-only
// - Whether the variable value is locked
// - Whether the variable is locked
- if (var_check_ro(v->di_flags, name, name_len)
- || value_check_lock(v->di_tv.v_lock, name, name_len)
- || var_check_lock(v->di_flags, name, name_len)) {
+ if (var_check_ro(di->di_flags, name, name_len)
+ || value_check_lock(di->di_tv.v_lock, name, name_len)
+ || var_check_lock(di->di_flags, name, name_len)) {
return;
}
@@ -1296,42 +1518,19 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Handle setting internal v: variables separately where needed to
// prevent changing the type.
- if (is_vimvarht(ht)) {
- if (v->di_tv.v_type == VAR_STRING) {
- XFREE_CLEAR(v->di_tv.vval.v_string);
- if (copy || tv->v_type != VAR_STRING) {
- const char *const val = tv_get_string(tv);
-
- // Careful: when assigning to v:errmsg and tv_get_string()
- // causes an error message the variable will already be set.
- if (v->di_tv.vval.v_string == NULL) {
- v->di_tv.vval.v_string = xstrdup(val);
- }
- } else {
- // Take over the string to avoid an extra alloc/free.
- v->di_tv.vval.v_string = tv->vval.v_string;
- tv->vval.v_string = NULL;
- }
- return;
- } else if (v->di_tv.v_type == VAR_NUMBER) {
- v->di_tv.vval.v_number = tv_get_number(tv);
- if (strcmp(varname, "searchforward") == 0) {
- set_search_direction(v->di_tv.vval.v_number ? '/' : '?');
- } else if (strcmp(varname, "hlsearch") == 0) {
- no_hlsearch = !v->di_tv.vval.v_number;
- redraw_all_later(UPD_SOME_VALID);
- }
- return;
- } else if (v->di_tv.v_type != tv->v_type) {
- semsg(_("E963: setting %s to value with wrong type"), name);
- return;
+ bool type_error = false;
+ if (is_vimvarht(ht)
+ && !before_set_vvar(varname, di, tv, copy, watched, &type_error)) {
+ if (type_error) {
+ semsg(_(e_setting_v_str_to_value_with_wrong_type), varname);
}
+ return;
}
if (watched) {
- tv_copy(&v->di_tv, &oldtv);
+ tv_copy(&di->di_tv, &oldtv);
}
- tv_clear(&v->di_tv);
+ tv_clear(&di->di_tv);
} else { // Add a new variable.
// Can't add "v:" or "a:" variable.
if (is_vimvarht(ht) || ht == get_funccal_args_ht()) {
@@ -1347,28 +1546,28 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Make sure dict is valid
assert(dict != NULL);
- v = xmalloc(offsetof(dictitem_T, di_key) + strlen(varname) + 1);
- STRCPY(v->di_key, varname);
- if (hash_add(ht, (char *)v->di_key) == FAIL) {
- xfree(v);
+ di = xmalloc(offsetof(dictitem_T, di_key) + varname_len + 1);
+ memcpy(di->di_key, varname, varname_len + 1);
+ if (hash_add(ht, di->di_key) == FAIL) {
+ xfree(di);
return;
}
- v->di_flags = DI_FLAGS_ALLOC;
+ di->di_flags = DI_FLAGS_ALLOC;
if (is_const) {
- v->di_flags |= DI_FLAGS_LOCK;
+ di->di_flags |= DI_FLAGS_LOCK;
}
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) {
- tv_copy(tv, &v->di_tv);
+ tv_copy(tv, &di->di_tv);
} else {
- v->di_tv = *tv;
- v->di_tv.v_lock = VAR_UNLOCKED;
+ di->di_tv = *tv;
+ di->di_tv.v_lock = VAR_UNLOCKED;
tv_init(tv);
}
if (watched) {
- tv_dict_watcher_notify(dict, (char *)v->di_key, &v->di_tv, &oldtv);
+ tv_dict_watcher_notify(dict, di->di_key, &di->di_tv, &oldtv);
tv_clear(&oldtv);
}
@@ -1376,7 +1575,7 @@ void set_var_const(const char *name, const size_t name_len, typval_T *const tv,
// Like :lockvar! name: lock the value and what it contains, but only
// if the reference count is up to one. That locks only literal
// values.
- tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true);
+ tv_item_lock(&di->di_tv, DICT_MAXNEST, true, true);
}
}
@@ -1569,7 +1768,7 @@ static void get_var_from(const char *varname, typval_T *rettv, typval_T *deftv,
tv_dict_set_ret(rettv, opts);
done = true;
}
- } else if (get_option_tv(&varname, rettv, true) == OK) {
+ } else if (eval_option(&varname, rettv, true) == OK) {
// Local option
done = true;
}
@@ -1639,28 +1838,121 @@ static void getwinvar(typval_T *argvars, typval_T *rettv, int off)
get_var_from(varname, rettv, &argvars[off + 2], 'w', tp, win, NULL);
}
-/// Set option "varname" to the value of "varp" for the current buffer/window.
-static void set_option_from_tv(const char *varname, typval_T *varp)
+/// Convert typval to option value for a particular option.
+///
+/// @param[in] tv typval to convert.
+/// @param[in] option Option name.
+/// @param[in] flags Option flags.
+/// @param[out] error Whether an error occurred.
+///
+/// @return Typval converted to OptVal. Must be freed by caller.
+/// Returns NIL_OPTVAL for invalid option name.
+static OptVal tv_to_optval(typval_T *tv, const char *option, uint32_t flags, bool *error)
{
- long numval = 0;
- const char *strval;
- bool error = false;
+ OptVal value = NIL_OPTVAL;
char nbuf[NUMBUFLEN];
-
- if (varp->v_type == VAR_BOOL) {
- if (is_string_option(varname)) {
+ bool err = false;
+
+ if ((flags & P_FUNC) && tv_is_func(*tv)) {
+ // If the option can be set to a function reference or a lambda
+ // and the passed value is a function reference, then convert it to
+ // the name (string) of the function reference.
+ char *strval = encode_tv2string(tv, NULL);
+ err = strval == NULL;
+ value = CSTR_AS_OPTVAL(strval);
+ } else if (flags & (P_NUM | P_BOOL)) {
+ varnumber_T n = (flags & P_NUM) ? tv_get_number_chk(tv, &err)
+ : tv_get_bool_chk(tv, &err);
+ // This could be either "0" or a string that's not a number.
+ // So we need to check if it's actually a number.
+ if (!err && tv->v_type == VAR_STRING && n == 0) {
+ unsigned idx;
+ for (idx = 0; tv->vval.v_string[idx] == '0'; idx++) {}
+ if (tv->vval.v_string[idx] != NUL || idx == 0) {
+ // There's another character after zeros or the string is empty.
+ // In both cases, we are trying to set a num option using a string.
+ err = true;
+ semsg(_("E521: Number required: &%s = '%s'"), option, tv->vval.v_string);
+ }
+ }
+ value = (flags & P_NUM) ? NUMBER_OPTVAL((OptInt)n) : BOOLEAN_OPTVAL(TRISTATE_FROM_INT(n));
+ } else if ((flags & P_STRING) || is_tty_option(option)) {
+ // Avoid setting string option to a boolean or a special value.
+ if (tv->v_type != VAR_BOOL && tv->v_type != VAR_SPECIAL) {
+ const char *strval = tv_get_string_buf_chk(tv, nbuf);
+ err = strval == NULL;
+ value = CSTR_TO_OPTVAL(strval);
+ } else if (flags & P_STRING) {
+ err = true;
emsg(_(e_stringreq));
- return;
}
- numval = (long)varp->vval.v_number;
- strval = "0"; // avoid using "false"
} else {
- numval = (long)tv_get_number_chk(varp, &error);
- strval = tv_get_string_buf_chk(varp, nbuf);
+ abort(); // This should never happen.
+ }
+
+ if (error != NULL) {
+ *error = err;
}
- if (!error && strval != NULL) {
- set_option_value_give_err(varname, numval, strval, OPT_LOCAL);
+ return value;
+}
+
+/// Convert an option value to typval.
+///
+/// @param[in] value Option value to convert.
+///
+/// @return OptVal converted to typval.
+typval_T optval_as_tv(OptVal value)
+{
+ typval_T rettv = { .v_type = VAR_SPECIAL, .vval = { .v_special = kSpecialVarNull } };
+
+ switch (value.type) {
+ case kOptValTypeNil:
+ break;
+ case kOptValTypeBoolean:
+ switch (value.data.boolean) {
+ case kTrue:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarTrue;
+ break;
+ case kFalse:
+ rettv.v_type = VAR_BOOL;
+ rettv.vval.v_bool = kBoolVarFalse;
+ break;
+ case kNone:
+ break; // return v:null for None boolean value
+ }
+ break;
+ case kOptValTypeNumber:
+ rettv.v_type = VAR_NUMBER;
+ rettv.vval.v_number = value.data.number;
+ break;
+ case kOptValTypeString:
+ rettv.v_type = VAR_STRING;
+ rettv.vval.v_string = value.data.string.data;
+ break;
+ }
+
+ return rettv;
+}
+
+/// Set option "varname" to the value of "varp" for the current buffer/window.
+static void set_option_from_tv(const char *varname, typval_T *varp)
+{
+ int opt_idx = findoption(varname);
+ if (opt_idx < 0) {
+ semsg(_(e_unknown_option2), varname);
+ return;
}
+ uint32_t opt_p_flags = get_option(opt_idx)->flags;
+
+ bool error = false;
+ OptVal value = tv_to_optval(varp, varname, opt_p_flags, &error);
+
+ if (!error) {
+ set_option_value_give_err(varname, value, OPT_LOCAL);
+ }
+
+ optval_free(value);
}
/// "setwinvar()" and "settabwinvar()" functions
@@ -1718,10 +2010,10 @@ bool var_exists(const char *var)
if (tofree != NULL) {
name = tofree;
}
- n = get_var_tv(name, len, &tv, NULL, false, true) == OK;
+ n = eval_variable(name, len, &tv, NULL, false, true) == OK;
if (n) {
// Handle d.key, l[idx], f(expr).
- n = handle_subscript(&var, &tv, true, false, name, &name) == OK;
+ n = handle_subscript(&var, &tv, &EVALARG_EVALUATE, false) == OK;
if (n) {
tv_clear(&tv);
}
diff --git a/src/nvim/eval/vars.h b/src/nvim/eval/vars.h
index b87c9d62cb..6ddf449ff1 100644
--- a/src/nvim/eval/vars.h
+++ b/src/nvim/eval/vars.h
@@ -1,9 +1,13 @@
-#ifndef NVIM_EVAL_VARS_H
-#define NVIM_EVAL_VARS_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include <stddef.h> // IWYU pragma: keep
+
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/hashtab_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/vars.h.generated.h"
#endif
-#endif // NVIM_EVAL_VARS_H
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index f58a0c488a..e0abbad477 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// eval/window.c: Window related builtin functions
#include <stdbool.h>
@@ -9,7 +6,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -21,23 +18,22 @@
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
-#include "nvim/memline_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/window.c.generated.h"
#endif
-static char *e_invalwindow = N_("E957: Invalid window number");
-static char e_cannot_resize_window_in_another_tab_page[]
+static const char *e_invalwindow = N_("E957: Invalid window number");
+static const char e_cannot_resize_window_in_another_tab_page[]
= N_("E1308: Cannot resize a window in another tab page");
static int win_getid(typval_T *argvars)
@@ -98,6 +94,7 @@ win_T *win_id2wp(int id)
}
/// Return the window and tab pointer of window "id".
+/// Returns NULL when not found.
win_T *win_id2wp_tp(int id, tabpage_T **tpp)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
@@ -192,9 +189,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp)
if (wvp->v_type != VAR_UNKNOWN) {
if (tvp->v_type != VAR_UNKNOWN) {
- long n = tv_get_number(tvp);
+ int n = (int)tv_get_number(tvp);
if (n >= 0) {
- tp = find_tabpage((int)n);
+ tp = find_tabpage(n);
}
} else {
tp = curtab;
@@ -265,7 +262,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
} else {
// Extract the window count (if specified). e.g. winnr('3j')
char *endp;
- long count = strtol((char *)arg, &endp, 10);
+ int count = (int)strtol(arg, &endp, 10);
if (count <= 0) {
// if count is not specified, default to 1
count = 1;
@@ -639,7 +636,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (wp == NULL || targetwin == NULL || wp == targetwin
|| !win_valid(wp) || !win_valid(targetwin)
- || win_valid_floating(wp) || win_valid_floating(targetwin)) {
+ || win_float_valid(wp) || win_float_valid(targetwin)) {
emsg(_(e_invalwindow));
rettv->vval.v_number = -1;
return;
@@ -651,8 +648,7 @@ void f_win_splitmove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
dict_T *d;
dictitem_T *di;
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_invarg));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
@@ -796,51 +792,50 @@ void f_winrestcmd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "winrestview()" function
void f_winrestview(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- dict_T *dict = argvars[0].vval.v_dict;
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
+ return;
+ }
- if (argvars[0].v_type != VAR_DICT || dict == NULL) {
- emsg(_(e_invarg));
- } else {
- dictitem_T *di;
- if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
- curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
- curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
- curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
- curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
- curwin->w_set_curswant = false;
- }
- if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
- set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
- }
- if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
- curwin->w_topfill = (int)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
- curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
- }
- if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
- curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
- }
+ dict_T *dict = argvars[0].vval.v_dict;
+ dictitem_T *di;
+ if ((di = tv_dict_find(dict, S_LEN("lnum"))) != NULL) {
+ curwin->w_cursor.lnum = (linenr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("col"))) != NULL) {
+ curwin->w_cursor.col = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("coladd"))) != NULL) {
+ curwin->w_cursor.coladd = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("curswant"))) != NULL) {
+ curwin->w_curswant = (colnr_T)tv_get_number(&di->di_tv);
+ curwin->w_set_curswant = false;
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topline"))) != NULL) {
+ set_topline(curwin, (linenr_T)tv_get_number(&di->di_tv));
+ }
+ if ((di = tv_dict_find(dict, S_LEN("topfill"))) != NULL) {
+ curwin->w_topfill = (int)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("leftcol"))) != NULL) {
+ curwin->w_leftcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
+ if ((di = tv_dict_find(dict, S_LEN("skipcol"))) != NULL) {
+ curwin->w_skipcol = (colnr_T)tv_get_number(&di->di_tv);
+ }
- check_cursor();
- win_new_height(curwin, curwin->w_height);
- win_new_width(curwin, curwin->w_width);
- changed_window_setting();
+ check_cursor();
+ win_new_height(curwin, curwin->w_height);
+ win_new_width(curwin, curwin->w_width);
+ changed_window_setting();
- if (curwin->w_topline <= 0) {
- curwin->w_topline = 1;
- }
- if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- }
- check_topfill(curwin, true);
+ if (curwin->w_topline <= 0) {
+ curwin->w_topline = 1;
+ }
+ if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
+ curwin->w_topline = curbuf->b_ml.ml_line_count;
}
+ check_topfill(curwin, true);
}
/// "winsaveview()" function
diff --git a/src/nvim/eval/window.h b/src/nvim/eval/window.h
index 995f0a55a9..ed879c895a 100644
--- a/src/nvim/eval/window.h
+++ b/src/nvim/eval/window.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVAL_WINDOW_H
-#define NVIM_EVAL_WINDOW_H
+#pragma once
#include <stdbool.h>
#include <string.h>
@@ -11,9 +10,10 @@
#include "nvim/globals.h"
#include "nvim/mark.h"
#include "nvim/option_defs.h"
-#include "nvim/os/os.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Structure used by switch_win() to pass values to restore_win()
@@ -75,4 +75,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/window.h.generated.h"
#endif
-#endif // NVIM_EVAL_WINDOW_H
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index cf079681d0..571f61dfdb 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_DEFS_H
-#define NVIM_EVENT_DEFS_H
+#pragma once
#include <assert.h>
#include <stdarg.h>
@@ -34,5 +33,3 @@ static inline Event event_create(argv_callback cb, int argc, ...)
VA_EVENT_INIT(&event, cb, argc);
return event;
}
-
-#endif // NVIM_EVENT_DEFS_H
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c
index e528d21a71..be48b39af1 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_process.c
@@ -1,17 +1,14 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <locale.h>
#include <stdint.h>
#include <uv.h>
#include "nvim/eval/typval.h"
#include "nvim/event/libuv_process.h"
-#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/os/os.h"
#include "nvim/ui_client.h"
@@ -24,7 +21,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
FUNC_ATTR_NONNULL_ALL
{
Process *proc = (Process *)uvproc;
- uvproc->uvopts.file = proc->argv[0];
+ uvproc->uvopts.file = process_get_exepath(proc);
uvproc->uvopts.args = proc->argv;
uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
#ifdef MSWIN
@@ -68,25 +65,22 @@ int libuv_process_spawn(LibuvProcess *uvproc)
#ifdef MSWIN
uvproc->uvstdio[0].flags |= proc->overlapped ? UV_OVERLAPPED_PIPE : 0;
#endif
- uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->in.uv.pipe);
+ uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
}
if (!proc->out.closed) {
uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
#ifdef MSWIN
// pipe must be readable for IOCP to work on Windows.
- uvproc->uvstdio[1].flags |= proc->overlapped ?
- (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
+ uvproc->uvstdio[1].flags |= proc->overlapped
+ ? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
#endif
- uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->out.uv.pipe);
+ uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.uv.pipe);
}
if (!proc->err.closed) {
uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
- uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t,
- &proc->err.uv.pipe);
+ uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.uv.pipe);
} else if (proc->fwd_err) {
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
uvproc->uvstdio[2].data.fd = STDERR_FILENO;
diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_process.h
index 4472839944..e3e2bfeb76 100644
--- a/src/nvim/event/libuv_process.h
+++ b/src/nvim/event/libuv_process.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_LIBUV_PROCESS_H
-#define NVIM_EVENT_LIBUV_PROCESS_H
+#pragma once
#include <uv.h>
@@ -24,4 +23,3 @@ static inline LibuvProcess libuv_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/libuv_process.h.generated.h"
#endif
-#endif // NVIM_EVENT_LIBUV_PROCESS_H
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index ab2524c1a9..d61666e6d4 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -20,6 +17,7 @@ void loop_init(Loop *loop, void *data)
{
uv_loop_init(&loop->uv);
loop->recursive = 0;
+ loop->closing = false;
loop->uv.data = loop;
loop->children = kl_init(WatcherPtr);
loop->events = multiqueue_new_parent(loop_on_put, loop);
@@ -61,9 +59,9 @@ bool loop_uv_run(Loop *loop, int64_t ms, bool once)
mode = UV_RUN_NOWAIT;
}
- do { // -V1044
+ do {
uv_run(&loop->uv, mode);
- } while (ms > 0 && !once && !*timeout_expired); // -V560
+ } while (ms > 0 && !once && !*timeout_expired);
if (ms > 0) {
uv_timer_stop(&loop->poll_timer);
@@ -152,6 +150,7 @@ static void loop_walk_cb(uv_handle_t *handle, void *arg)
bool loop_close(Loop *loop, bool wait)
{
bool rv = true;
+ loop->closing = true;
uv_mutex_destroy(&loop->mutex);
uv_close((uv_handle_t *)&loop->children_watcher, NULL);
uv_close((uv_handle_t *)&loop->children_kill_timer, NULL);
@@ -163,7 +162,7 @@ bool loop_close(Loop *loop, bool wait)
while (true) {
// Run the loop to tickle close-callbacks (which should then free memory).
// Use UV_RUN_NOWAIT to avoid a hang. #11820
- uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); // -V547
+ uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT);
if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) {
break;
}
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index b2265a726d..5665332e95 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -1,6 +1,6 @@
-#ifndef NVIM_EVENT_LOOP_H
-#define NVIM_EVENT_LOOP_H
+#pragma once
+#include <stdbool.h>
#include <stdint.h>
#include <uv.h>
@@ -41,6 +41,7 @@ typedef struct loop {
uv_async_t async;
uv_mutex_t mutex;
int recursive;
+ bool closing; ///< Set to true if loop_close() has been called
} Loop;
#define CREATE_EVENT(multiqueue, handler, argc, ...) \
@@ -53,8 +54,6 @@ typedef struct loop {
} \
} while (0)
-// -V:LOOP_PROCESS_EVENTS_UNTIL:547
-
// Poll for events until a condition or timeout
#define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \
do { \
@@ -87,5 +86,3 @@ typedef struct loop {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/loop.h.generated.h"
#endif
-
-#endif // NVIM_EVENT_LOOP_H
diff --git a/src/nvim/event/multiqueue.c b/src/nvim/event/multiqueue.c
index e05084b656..3ab41bd299 100644
--- a/src/nvim/event/multiqueue.c
+++ b/src/nvim/event/multiqueue.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Multi-level queue for selective async event processing.
// Not threadsafe; access must be synchronized externally.
//
@@ -40,7 +37,7 @@
//
// The main reason for this queue hierarchy is to allow focusing on a single
// event emitter while blocking the main loop. For example, if the `jobwait`
-// VimL function is called on job1, the main loop will temporarily stop polling
+// Vimscript function is called on job1, the main loop will temporarily stop polling
// the event loop queue and poll job1 queue instead. Same with channels, when
// calling `rpcrequest` we want to temporarily stop processing events from
// other sources and focus on a specific channel.
@@ -48,10 +45,10 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
-#include <uv.h>
#include "nvim/event/defs.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/func_attr.h"
#include "nvim/lib/queue.h"
#include "nvim/memory.h"
@@ -112,13 +109,13 @@ static MultiQueue *multiqueue_new(MultiQueue *parent, PutCallback put_cb, void *
return rv;
}
-void multiqueue_free(MultiQueue *this)
+void multiqueue_free(MultiQueue *self)
{
- assert(this);
+ assert(self);
QUEUE *q;
- QUEUE_FOREACH(q, &this->headtail, {
+ QUEUE_FOREACH(q, &self->headtail, {
MultiQueueItem *item = multiqueue_node_data(q);
- if (this->parent) {
+ if (self->parent) {
QUEUE_REMOVE(&item->data.item.parent_item->node);
xfree(item->data.item.parent_item);
}
@@ -126,29 +123,29 @@ void multiqueue_free(MultiQueue *this)
xfree(item);
})
- xfree(this);
+ xfree(self);
}
/// Removes the next item and returns its Event.
-Event multiqueue_get(MultiQueue *this)
+Event multiqueue_get(MultiQueue *self)
{
- return multiqueue_empty(this) ? NILEVENT : multiqueue_remove(this);
+ return multiqueue_empty(self) ? NILEVENT : multiqueue_remove(self);
}
-void multiqueue_put_event(MultiQueue *this, Event event)
+void multiqueue_put_event(MultiQueue *self, Event event)
{
- assert(this);
- multiqueue_push(this, event);
- if (this->parent && this->parent->put_cb) {
- this->parent->put_cb(this->parent, this->parent->data);
+ assert(self);
+ multiqueue_push(self, event);
+ if (self->parent && self->parent->put_cb) {
+ self->parent->put_cb(self->parent, self->parent->data);
}
}
-void multiqueue_process_events(MultiQueue *this)
+void multiqueue_process_events(MultiQueue *self)
{
- assert(this);
- while (!multiqueue_empty(this)) {
- Event event = multiqueue_remove(this);
+ assert(self);
+ while (!multiqueue_empty(self)) {
+ Event event = multiqueue_remove(self);
if (event.handler) {
event.handler(event.argv);
}
@@ -156,30 +153,30 @@ void multiqueue_process_events(MultiQueue *this)
}
/// Removes all events without processing them.
-void multiqueue_purge_events(MultiQueue *this)
+void multiqueue_purge_events(MultiQueue *self)
{
- assert(this);
- while (!multiqueue_empty(this)) {
- (void)multiqueue_remove(this);
+ assert(self);
+ while (!multiqueue_empty(self)) {
+ (void)multiqueue_remove(self);
}
}
-bool multiqueue_empty(MultiQueue *this)
+bool multiqueue_empty(MultiQueue *self)
{
- assert(this);
- return QUEUE_EMPTY(&this->headtail);
+ assert(self);
+ return QUEUE_EMPTY(&self->headtail);
}
-void multiqueue_replace_parent(MultiQueue *this, MultiQueue *new_parent)
+void multiqueue_replace_parent(MultiQueue *self, MultiQueue *new_parent)
{
- assert(multiqueue_empty(this));
- this->parent = new_parent;
+ assert(multiqueue_empty(self));
+ self->parent = new_parent;
}
/// Gets the count of all events currently in the queue.
-size_t multiqueue_size(MultiQueue *this)
+size_t multiqueue_size(MultiQueue *self)
{
- return this->size;
+ return self->size;
}
/// Gets an Event from an item.
@@ -213,38 +210,39 @@ static Event multiqueueitem_get_event(MultiQueueItem *item, bool remove)
return ev;
}
-static Event multiqueue_remove(MultiQueue *this)
+static Event multiqueue_remove(MultiQueue *self)
{
- assert(!multiqueue_empty(this));
- QUEUE *h = QUEUE_HEAD(&this->headtail);
+ assert(!multiqueue_empty(self));
+ QUEUE *h = QUEUE_HEAD(&self->headtail);
QUEUE_REMOVE(h);
MultiQueueItem *item = multiqueue_node_data(h);
- assert(!item->link || !this->parent); // Only a parent queue has link-nodes
+ assert(!item->link || !self->parent); // Only a parent queue has link-nodes
Event ev = multiqueueitem_get_event(item, true);
- this->size--;
+ self->size--;
xfree(item);
return ev;
}
-static void multiqueue_push(MultiQueue *this, Event event)
+static void multiqueue_push(MultiQueue *self, Event event)
{
MultiQueueItem *item = xmalloc(sizeof(MultiQueueItem));
item->link = false;
item->data.item.event = event;
item->data.item.parent_item = NULL;
- QUEUE_INSERT_TAIL(&this->headtail, &item->node);
- if (this->parent) {
+ QUEUE_INSERT_TAIL(&self->headtail, &item->node);
+ if (self->parent) {
// push link node to the parent queue
item->data.item.parent_item = xmalloc(sizeof(MultiQueueItem));
item->data.item.parent_item->link = true;
- item->data.item.parent_item->data.queue = this;
- QUEUE_INSERT_TAIL(&this->parent->headtail,
+ item->data.item.parent_item->data.queue = self;
+ QUEUE_INSERT_TAIL(&self->parent->headtail,
&item->data.item.parent_item->node);
}
- this->size++;
+ self->size++;
}
static MultiQueueItem *multiqueue_node_data(QUEUE *q)
+ FUNC_ATTR_NO_SANITIZE_ADDRESS
{
return QUEUE_DATA(q, MultiQueueItem, node);
}
diff --git a/src/nvim/event/multiqueue.h b/src/nvim/event/multiqueue.h
index 2c5ba9d436..e01ee1e710 100644
--- a/src/nvim/event/multiqueue.h
+++ b/src/nvim/event/multiqueue.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_MULTIQUEUE_H
-#define NVIM_EVENT_MULTIQUEUE_H
+#pragma once
#include <uv.h>
@@ -15,4 +14,3 @@ typedef void (*PutCallback)(MultiQueue *multiq, void *data);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/multiqueue.h.generated.h"
#endif
-#endif // NVIM_EVENT_MULTIQUEUE_H
diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c
index 1a524a56ca..864fc2c1d8 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/process.c
@@ -1,19 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <signal.h>
-#include <stdlib.h>
#include <uv.h>
#include "klib/klist.h"
#include "nvim/event/libuv_process.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/os/process.h"
#include "nvim/os/pty_process.h"
@@ -78,8 +74,6 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
case kProcessTypePty:
status = pty_process_spawn((PtyProcess *)proc);
break;
- default:
- abort();
}
if (status) {
@@ -104,24 +98,21 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
}
if (in) {
- stream_init(NULL, &proc->in, -1,
- STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe));
+ stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
proc->in.internal_data = proc;
proc->in.internal_close_cb = on_process_stream_close;
proc->refcount++;
}
if (out) {
- stream_init(NULL, &proc->out, -1,
- STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe));
+ stream_init(NULL, &proc->out, -1, (uv_stream_t *)&proc->out.uv.pipe);
proc->out.internal_data = proc;
proc->out.internal_close_cb = on_process_stream_close;
proc->refcount++;
}
if (err) {
- stream_init(NULL, &proc->err, -1,
- STRUCT_CAST(uv_stream_t, &proc->err.uv.pipe));
+ stream_init(NULL, &proc->err, -1, (uv_stream_t *)&proc->err.uv.pipe);
proc->err.internal_data = proc;
proc->err.internal_close_cb = on_process_stream_close;
proc->refcount++;
@@ -131,7 +122,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
proc->internal_close_cb = decref;
proc->refcount++;
kl_push(WatcherPtr, proc->loop->children, proc);
- DLOG("new: pid=%d argv=[%s]", proc->pid, proc->argv[0]);
+ DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc));
return 0;
}
@@ -239,8 +230,6 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
process_close_streams(proc);
pty_process_close_master((PtyProcess *)proc);
break;
- default:
- abort();
}
// (Re)start timer to verify that stopped process(es) died.
@@ -340,8 +329,6 @@ static void process_close(Process *proc)
case kProcessTypePty:
pty_process_close((PtyProcess *)proc);
break;
- default:
- abort();
}
}
@@ -382,7 +369,7 @@ static void flush_stream(Process *proc, Stream *stream)
}
// Stream can be closed if it is empty.
- if (num_bytes == stream->num_bytes) { // -V547
+ if (num_bytes == stream->num_bytes) {
if (stream->read_cb && !stream->did_eof) {
// Stream callback could miss EOF handling if a child keeps the stream
// open. But only send EOF if we haven't already.
@@ -422,7 +409,13 @@ static void exit_event(void **argv)
}
if (!exiting) {
- os_exit(status);
+ if (ui_client_channel_id) {
+ ui_client_exit_status = status;
+ os_exit(status);
+ } else {
+ assert(status == 0); // Called from rpc_close(), which passes 0 as status.
+ preserve_exit(NULL);
+ }
}
}
diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h
index e0057faffb..234fc815af 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/process.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_EVENT_PROCESS_H
-#define NVIM_EVENT_PROCESS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
@@ -33,6 +31,7 @@ struct process {
uint64_t stopped_time; // process_stop() timestamp
const char *cwd;
char **argv;
+ const char *exepath;
dict_T *env;
Stream in, out, err;
/// Exit handler. If set, user must call process_free().
@@ -55,6 +54,7 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
.stopped_time = 0,
.cwd = NULL,
.argv = NULL,
+ .exepath = NULL,
.in = { .closed = false },
.out = { .closed = false },
.err = { .closed = false },
@@ -67,6 +67,12 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
};
}
+/// Get the path to the executable of the process.
+static inline const char *process_get_exepath(Process *proc)
+{
+ return proc->exepath != NULL ? proc->exepath : proc->argv[0];
+}
+
static inline bool process_is_stopped(Process *proc)
{
bool exited = (proc->status >= 0);
@@ -76,4 +82,3 @@ static inline bool process_is_stopped(Process *proc)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/process.h.generated.h"
#endif
-#endif // NVIM_EVENT_PROCESS_H
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index a88d62fd6b..73828a2271 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -1,17 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <sys/types.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/os/os_defs.h"
#include "nvim/rbuffer.h"
@@ -155,7 +154,7 @@ static void fread_idle_cb(uv_idle_t *handle)
uintmax_t fpos_intmax = stream->fpos;
if (fpos_intmax > INT64_MAX) {
ELOG("stream offset overflow");
- preserve_exit();
+ preserve_exit("stream offset overflow");
}
// Synchronous read
diff --git a/src/nvim/event/rstream.h b/src/nvim/event/rstream.h
index 23ed00bfea..b2a62acf83 100644
--- a/src/nvim/event/rstream.h
+++ b/src/nvim/event/rstream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_RSTREAM_H
-#define NVIM_EVENT_RSTREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -11,4 +10,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/rstream.h.generated.h"
#endif
-#endif // NVIM_EVENT_RSTREAM_H
diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c
index 8256ca2091..e64d526856 100644
--- a/src/nvim/event/signal.c
+++ b/src/nvim/event/signal.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stddef.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/signal.h"
+#include "nvim/func_attr.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/signal.c.generated.h"
diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h
index f9adf62c20..946de1b4f0 100644
--- a/src/nvim/event/signal.h
+++ b/src/nvim/event/signal.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_SIGNAL_H
-#define NVIM_EVENT_SIGNAL_H
+#pragma once
#include <uv.h>
@@ -23,4 +22,3 @@ struct signal_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/signal.h.generated.h"
#endif
-#endif // NVIM_EVENT_SIGNAL_H
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 10756015ad..e787e023f0 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -8,16 +5,17 @@
#include <string.h>
#include <uv.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/event/loop.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
@@ -64,10 +62,10 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint
uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle);
uv_tcp_nodelay(&watcher->uv.tcp.handle, true);
- watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle);
+ watcher->stream = (uv_stream_t *)(&watcher->uv.tcp.handle);
} else {
uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0);
- watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.pipe.handle);
+ watcher->stream = (uv_stream_t *)(&watcher->uv.pipe.handle);
}
watcher->stream->data = watcher;
@@ -102,9 +100,8 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
// contain 0 in this case, unless uv_tcp_getsockname() is used first.
uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas,
&(int){ sizeof(sas) });
- uint16_t port = (uint16_t)((sas.ss_family == AF_INET)
- ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port
- : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port);
+ uint16_t port = (sas.ss_family == AF_INET) ? ((struct sockaddr_in *)(&sas))->sin_port
+ : ((struct sockaddr_in6 *)(&sas))->sin6_port;
// v:servername uses the string from watcher->addr
size_t len = strlen(watcher->addr);
snprintf(watcher->addr + len, sizeof(watcher->addr) - len, ":%" PRIu16,
@@ -142,11 +139,11 @@ int socket_watcher_accept(SocketWatcher *watcher, Stream *stream)
uv_stream_t *client;
if (watcher->stream->type == UV_TCP) {
- client = STRUCT_CAST(uv_stream_t, &stream->uv.tcp);
+ client = (uv_stream_t *)(&stream->uv.tcp);
uv_tcp_init(watcher->uv.tcp.handle.loop, (uv_tcp_t *)client);
uv_tcp_nodelay((uv_tcp_t *)client, true);
} else {
- client = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+ client = (uv_stream_t *)&stream->uv.pipe;
uv_pipe_init(watcher->uv.pipe.handle.loop, (uv_pipe_t *)client, 0);
}
@@ -165,7 +162,7 @@ void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb)
FUNC_ATTR_NONNULL_ARG(1)
{
watcher->close_cb = cb;
- uv_close(STRUCT_CAST(uv_handle_t, watcher->stream), close_cb);
+ uv_close((uv_handle_t *)watcher->stream, close_cb);
}
static void connection_event(void **argv)
@@ -224,7 +221,7 @@ bool socket_connect(Loop *loop, Stream *stream, bool is_tcp, const char *address
const struct addrinfo hints = { .ai_family = AF_UNSPEC,
.ai_socktype = SOCK_STREAM,
- .ai_flags = AI_NUMERICSERV };
+ .ai_flags = AI_NUMERICSERV };
int retval = uv_getaddrinfo(&loop->uv, &addr_req, NULL,
addr, host_end + 1, &hints);
if (retval != 0) {
@@ -242,11 +239,11 @@ tcp_retry:
uv_pipe_t *pipe = &stream->uv.pipe;
uv_pipe_init(&loop->uv, pipe, 0);
uv_pipe_connect(&req, pipe, address, connect_cb);
- uv_stream = STRUCT_CAST(uv_stream_t, pipe);
+ uv_stream = (uv_stream_t *)pipe;
}
status = 1;
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, timeout, status != 1);
- if (status == 0) { // -V547
+ if (status == 0) {
stream_init(NULL, stream, -1, uv_stream);
success = true;
} else if (is_tcp && addrinfo->ai_next) {
diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h
index c6fcdec4bb..504af3c7a8 100644
--- a/src/nvim/event/socket.h
+++ b/src/nvim/event/socket.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_SOCKET_H
-#define NVIM_EVENT_SOCKET_H
+#pragma once
#include <uv.h>
@@ -39,4 +38,3 @@ struct socket_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/socket.h.generated.h"
#endif
-#endif // NVIM_EVENT_SOCKET_H
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 0a4918636a..aff116bad9 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
@@ -9,8 +6,8 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/rbuffer.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -37,9 +34,8 @@ int stream_set_blocking(int fd, bool blocking)
uv_loop_init(&loop);
uv_pipe_init(&loop, &stream, 0);
uv_pipe_open(&stream, fd);
- int retval = uv_stream_set_blocking(STRUCT_CAST(uv_stream_t, &stream),
- blocking);
- uv_close(STRUCT_CAST(uv_handle_t, &stream), NULL);
+ int retval = uv_stream_set_blocking((uv_stream_t *)&stream, blocking);
+ uv_close((uv_handle_t *)&stream, NULL);
uv_run(&loop, UV_RUN_NOWAIT); // not necessary, but couldn't hurt.
uv_loop_close(&loop);
return retval;
@@ -71,12 +67,12 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
SetConsoleMode(stream->uv.tty.handle, dwMode);
}
- stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.tty);
+ stream->uvstream = (uv_stream_t *)&stream->uv.tty;
} else {
#endif
uv_pipe_init(&loop->uv, &stream->uv.pipe, 0);
uv_pipe_open(&stream->uv.pipe, fd);
- stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe);
+ stream->uvstream = (uv_stream_t *)&stream->uv.pipe;
#ifdef MSWIN
}
#endif
diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h
index 33d2d6e775..d02707dc45 100644
--- a/src/nvim/event/stream.h
+++ b/src/nvim/event/stream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_STREAM_H
-#define NVIM_EVENT_STREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -61,4 +60,3 @@ struct stream {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/stream.h.generated.h"
#endif
-#endif // NVIM_EVENT_STREAM_H
diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c
index c997e3c558..f678f25f3f 100644
--- a/src/nvim/event/time.c
+++ b/src/nvim/event/time.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdint.h>
#include <uv.h>
#include "nvim/event/loop.h"
#include "nvim/event/time.h"
+#include "nvim/func_attr.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/time.c.generated.h"
diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h
index e84488fdd6..3514566901 100644
--- a/src/nvim/event/time.h
+++ b/src/nvim/event/time.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_TIME_H
-#define NVIM_EVENT_TIME_H
+#pragma once
#include <stdbool.h>
#include <uv.h>
@@ -23,4 +22,3 @@ struct time_watcher {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/time.h.generated.h"
#endif
-#endif // NVIM_EVENT_TIME_H
diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c
index 65391ba5cf..e8f757874b 100644
--- a/src/nvim/event/wstream.c
+++ b/src/nvim/event/wstream.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <uv.h>
@@ -8,7 +5,8 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#define DEFAULT_MAXMEM 1024 * 1024 * 2000
diff --git a/src/nvim/event/wstream.h b/src/nvim/event/wstream.h
index ef1311c619..4cba7bde8f 100644
--- a/src/nvim/event/wstream.h
+++ b/src/nvim/event/wstream.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EVENT_WSTREAM_H
-#define NVIM_EVENT_WSTREAM_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -23,4 +22,3 @@ struct wbuffer {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/wstream.h.generated.h"
#endif
-#endif // NVIM_EVENT_WSTREAM_H
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 437a05f61d..e369397047 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_cmds.c: some functions for command line commands
#include <assert.h>
#include <ctype.h>
#include <float.h>
#include <inttypes.h>
+#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stddef.h>
@@ -18,11 +16,12 @@
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
@@ -44,17 +43,16 @@
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/input.h"
-#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -66,26 +64,28 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
/// Case matching style to use for :substitute
@@ -126,20 +126,28 @@ typedef struct {
# include "ex_cmds.c.generated.h"
#endif
+static const char e_non_numeric_argument_to_z[]
+ = N_("E144: Non-numeric argument to :z");
+
/// ":ascii" and "ga" implementation
-void do_ascii(const exarg_T *const eap)
+void do_ascii(exarg_T *eap)
{
- char *dig;
- int cc[MAX_MCO];
- int c = utfc_ptr2char(get_cursor_pos_ptr(), cc);
- if (c == NUL) {
- msg("NUL");
+ char *data = get_cursor_pos_ptr();
+ size_t len = (size_t)utfc_ptr2len(data);
+
+ if (len == 0) {
+ msg("NUL", 0);
return;
}
- size_t iobuff_len = 0;
+ bool need_clear = true;
+ msg_sb_eol();
+ msg_start();
+
+ int c = utf_ptr2char(data);
+ size_t off = 0;
- int ci = 0;
+ // TODO(bfredl): merge this with the main loop
if (c < 0x80) {
if (c == NL) { // NUL is stored as NL.
c = NUL;
@@ -148,56 +156,39 @@ void do_ascii(const exarg_T *const eap)
? NL // NL is stored as CR.
: c);
char buf1[20];
- if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) {
+ if (vim_isprintc(c) && (c < ' ' || c > '~')) {
char buf3[7];
- transchar_nonprint(curbuf, (char_u *)buf3, c);
- vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3);
+ transchar_nonprint(curbuf, buf3, c);
+ vim_snprintf(buf1, sizeof(buf1), " <%s>", buf3);
} else {
buf1[0] = NUL;
}
char buf2[20];
buf2[0] = NUL;
- dig = get_digraph_for_char(cval);
+ char *dig = get_digraph_for_char(cval);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
- transchar(c), buf1, buf2, cval, cval, cval, dig);
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"),
+ transchar(c), buf1, buf2, cval, cval, cval, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- _("<%s>%s%s %d, Hex %02x, Octal %03o"),
- transchar(c), buf1, buf2, cval, cval, cval);
- }
-
- c = cc[ci++];
- }
-
-#define SPACE_FOR_DESC (1 + 1 + 1 + MB_MAXBYTES + 16 + 4 + 3 + 3 + 1)
- // Space for description:
- // - 1 byte for separator (starting from second entry)
- // - 1 byte for "<"
- // - 1 byte for space to draw composing character on (optional, but really
- // mostly required)
- // - up to MB_MAXBYTES bytes for character itself
- // - 16 bytes for raw text ("> , Hex , Octal ").
- // - at least 4 bytes for hexadecimal representation
- // - at least 3 bytes for decimal representation
- // - at least 3 bytes for octal representation
- // - 1 byte for NUL
- //
- // Taking into account MAX_MCO and characters which need 8 bytes for
- // hexadecimal representation, but not taking translation into account:
- // resulting string will occupy less then 400 bytes (conservative estimate).
- //
- // Less then 1000 bytes if translation multiplies number of bytes needed for
- // raw text by 6, so it should always fit into 1025 bytes reserved for IObuff.
+ vim_snprintf(IObuff, sizeof(IObuff),
+ _("<%s>%s%s %d, Hex %02x, Octal %03o"),
+ transchar(c), buf1, buf2, cval, cval, cval);
+ }
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data); // needed for overlong ascii?
+ }
// Repeat for combining characters, also handle multiby here.
- while (c >= 0x80 && iobuff_len < sizeof(IObuff) - SPACE_FOR_DESC) {
+ while (off < len) {
+ c = utf_ptr2char(data + off);
+
+ size_t iobuff_len = 0;
// This assumes every multi-byte char is printable...
- if (iobuff_len > 0) {
+ if (off > 0) {
IObuff[iobuff_len++] = ' ';
}
IObuff[iobuff_len++] = '<';
@@ -206,43 +197,37 @@ void do_ascii(const exarg_T *const eap)
}
iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len);
- dig = get_digraph_for_char(c);
+ char *dig = get_digraph_for_char(c);
if (dig != NULL) {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Oct %o, Digr %s")
- : _("> %d, Hex %08x, Oct %o, Digr %s")),
- c, c, c, dig);
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Oct %o, Digr %s")
+ : _("> %d, Hex %08x, Oct %o, Digr %s")),
+ c, c, c, dig);
} else {
- iobuff_len += (size_t)vim_snprintf(IObuff + iobuff_len,
- sizeof(IObuff) - iobuff_len,
- (c < 0x10000
- ? _("> %d, Hex %04x, Octal %o")
- : _("> %d, Hex %08x, Octal %o")),
- c, c, c);
- }
- if (ci == MAX_MCO) {
- break;
+ vim_snprintf(IObuff + iobuff_len, sizeof(IObuff) - iobuff_len,
+ (c < 0x10000
+ ? _("> %d, Hex %04x, Octal %o")
+ : _("> %d, Hex %08x, Octal %o")),
+ c, c, c);
}
- c = cc[ci++];
- }
- if (ci != MAX_MCO && c != 0) {
- xstrlcpy(IObuff + iobuff_len, " ...", sizeof(IObuff) - iobuff_len);
+
+ msg_multiline(IObuff, 0, true, &need_clear);
+
+ off += (size_t)utf_ptr2len(data + off); // needed for overlong ascii?
}
- msg(IObuff);
+ if (need_clear) {
+ msg_clr_eos();
+ }
+ msg_end();
}
/// ":left", ":center" and ":right": align text.
void ex_align(exarg_T *eap)
{
- pos_T save_curpos;
- int len;
int indent = 0;
int new_indent;
- int has_tab;
- int width;
if (curwin->w_p_rl) {
// switch left and right aligning
@@ -253,8 +238,8 @@ void ex_align(exarg_T *eap)
}
}
- width = atoi(eap->arg);
- save_curpos = curwin->w_cursor;
+ int width = atoi(eap->arg);
+ pos_T save_curpos = curwin->w_cursor;
if (eap->cmdidx == CMD_left) { // width is used for new indent
if (width >= 0) {
indent = width;
@@ -283,8 +268,8 @@ void ex_align(exarg_T *eap)
if (eap->cmdidx == CMD_left) { // left align
new_indent = indent;
} else {
- has_tab = false; // avoid uninit warnings
- len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent();
+ int has_tab = false; // avoid uninit warnings
+ int len = linelen(eap->cmdidx == CMD_right ? &has_tab : NULL) - get_indent();
if (len <= 0) { // skip blank lines
continue;
@@ -319,7 +304,7 @@ void ex_align(exarg_T *eap)
}
(void)set_indent(new_indent, 0); // set indent
}
- changed_lines(eap->line1, 0, eap->line2 + 1, 0L, true);
+ changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, 0, true);
curwin->w_cursor = save_curpos;
beginline(BL_WHITE | BL_FIX);
}
@@ -327,29 +312,24 @@ void ex_align(exarg_T *eap)
/// @return the length of the current line, excluding trailing white space.
static int linelen(int *has_tab)
{
- char *line;
- char *first;
char *last;
- int len;
// Get the line. If it's empty bail out early (could be the empty string
// for an unloaded buffer).
- line = get_cursor_line_ptr();
+ char *line = get_cursor_line_ptr();
if (*line == NUL) {
return 0;
}
// find the first non-blank character
- first = skipwhite(line);
+ char *first = skipwhite(line);
// find the character after the last non-blank character
for (last = first + strlen(first);
last > first && ascii_iswhite(last[-1]); last--) {}
char save = *last;
*last = NUL;
- // Get line length.
- len = linetabsize(line);
- // Check for embedded TAB.
- if (has_tab != NULL) {
+ int len = linetabsize_str(line); // Get line length.
+ if (has_tab != NULL) { // Check for embedded TAB.
*has_tab = vim_strchr(first, TAB) != NULL;
}
*last = save;
@@ -389,7 +369,7 @@ typedef struct {
static int string_compare(const void *s1, const void *s2) FUNC_ATTR_NONNULL_ALL
{
if (sort_lc) {
- return strcoll((char *)s1, (char *)s2);
+ return strcoll((const char *)s1, (const char *)s2);
}
return sort_ic ? STRICMP(s1, s2) : strcmp(s1, s2);
}
@@ -418,10 +398,10 @@ static int sort_compare(const void *s1, const void *s2)
result = l1.st_u.num.is_number - l2.st_u.num.is_number;
} else {
result = l1.st_u.num.value == l2.st_u.num.value
- ? 0
- : l1.st_u.num.value > l2.st_u.num.value
- ? 1
- : -1;
+ ? 0
+ : l1.st_u.num.value > l2.st_u.num.value
+ ? 1
+ : -1;
}
} else if (sort_flt) {
result = l1.st_u.value_flt == l2.st_u.value_flt
@@ -452,19 +432,10 @@ static int sort_compare(const void *s1, const void *s2)
void ex_sort(exarg_T *eap)
{
regmatch_T regmatch;
- int len;
- linenr_T lnum;
- long maxlen = 0;
+ int maxlen = 0;
size_t count = (size_t)(eap->line2 - eap->line1) + 1;
size_t i;
- char *p;
- char *s;
- char *s2;
- char c; // temporary character storage
bool unique = false;
- long deleted;
- colnr_T start_col;
- colnr_T end_col;
int sort_what = 0;
// Sorting one line is really quick!
@@ -484,7 +455,7 @@ void ex_sort(exarg_T *eap)
size_t format_found = 0;
bool change_occurred = false; // Buffer contents changed.
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
if (ascii_iswhite(*p)) {
// Skip
} else if (*p == 'i') {
@@ -517,7 +488,7 @@ void ex_sort(exarg_T *eap)
eap->nextcmd = check_nextcmd(p);
break;
} else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) {
- s = skip_regexp_err(p + 1, *p, true);
+ char *s = skip_regexp_err(p + 1, *p, true);
if (s == NULL) {
goto sortend;
}
@@ -559,15 +530,15 @@ void ex_sort(exarg_T *eap)
// numbers sorting it's the number to sort on. This means the pattern
// matching and number conversion only has to be done once per line.
// Also get the longest line length for allocating "sortbuf".
- for (lnum = eap->line1; lnum <= eap->line2; lnum++) {
- s = ml_get(lnum);
- len = (int)strlen(s);
+ for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
+ char *s = ml_get(lnum);
+ int len = (int)strlen(s);
if (maxlen < len) {
maxlen = len;
}
- start_col = 0;
- end_col = len;
+ colnr_T start_col = 0;
+ colnr_T end_col = len;
if (regmatch.regprog != NULL && vim_regexec(&regmatch, s, 0)) {
if (sort_rx) {
start_col = (colnr_T)(regmatch.startp[0] - s);
@@ -580,13 +551,13 @@ void ex_sort(exarg_T *eap)
}
if (sort_nr || sort_flt) {
- // Make sure vim_str2nr doesn't read any digits past the end
+ // Make sure vim_str2nr() doesn't read any digits past the end
// of the match, by temporarily terminating the string there
- s2 = s + end_col;
- c = *s2;
+ char *s2 = s + end_col;
+ char c = *s2; // temporary character storage
*s2 = NUL;
// Sorting on number: Store the number itself.
- p = s + start_col;
+ char *p = s + start_col;
if (sort_nr) {
if (sort_what & STR2NR_HEX) {
s = skiptohex(p);
@@ -605,7 +576,7 @@ void ex_sort(exarg_T *eap)
} else {
nrs[lnum - eap->line1].st_u.num.is_number = true;
vim_str2nr(s, NULL, NULL, sort_what,
- &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false);
+ &nrs[lnum - eap->line1].st_u.num.value, NULL, 0, false, NULL);
}
} else {
s = skipwhite(p);
@@ -651,7 +622,7 @@ void ex_sort(exarg_T *eap)
bcount_t old_count = 0, new_count = 0;
// Insert the lines in the sorted order below the last one.
- lnum = eap->line2;
+ linenr_T lnum = eap->line2;
for (i = 0; i < count; i++) {
const linenr_T get_lnum = nrs[eap->forceit ? count - i - 1 : i].lnum;
@@ -661,14 +632,14 @@ void ex_sort(exarg_T *eap)
change_occurred = true;
}
- s = ml_get(get_lnum);
+ char *s = ml_get(get_lnum);
size_t bytelen = strlen(s) + 1; // include EOL in bytelen
old_count += (bcount_t)bytelen;
if (!unique || i == 0 || string_compare(s, sortbuf1) != 0) {
// Copy the line into a buffer, it may become invalid in
// ml_append(). And it's needed for "unique".
STRCPY(sortbuf1, s);
- if (ml_append(lnum++, sortbuf1, (colnr_T)0, false) == FAIL) {
+ if (ml_append(lnum++, sortbuf1, 0, false) == FAIL) {
break;
}
new_count += (bcount_t)bytelen;
@@ -689,13 +660,12 @@ void ex_sort(exarg_T *eap)
}
// Adjust marks for deleted (or added) lines and prepare for displaying.
- deleted = (long)count - (lnum - eap->line2);
+ linenr_T deleted = (linenr_T)count - (lnum - eap->line2);
if (deleted > 0) {
- mark_adjust(eap->line2 - (linenr_T)deleted, eap->line2, (long)MAXLNUM, (linenr_T)(-deleted),
- kExtmarkNOOP);
+ mark_adjust(eap->line2 - deleted, eap->line2, MAXLNUM, -deleted, kExtmarkNOOP);
msgmore(-deleted);
} else if (deleted < 0) {
- mark_adjust(eap->line2, MAXLNUM, (linenr_T)(-deleted), 0L, kExtmarkNOOP);
+ mark_adjust(eap->line2, MAXLNUM, -deleted, 0, kExtmarkNOOP);
}
if (change_occurred || deleted != 0) {
@@ -703,7 +673,7 @@ void ex_sort(exarg_T *eap)
(int)count, 0, old_count,
lnum - eap->line2, 0, new_count, kExtmarkUndo);
- changed_lines(eap->line1, 0, eap->line2 + 1, (linenr_T)(-deleted), true);
+ changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, -deleted, true);
}
curwin->w_cursor.lnum = eap->line1;
@@ -724,7 +694,6 @@ sortend:
/// @return FAIL for failure, OK otherwise
int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
{
- char *str;
linenr_T l;
linenr_T extra; // Num lines added before line1
linenr_T num_lines; // Num lines moved
@@ -761,8 +730,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return FAIL;
}
for (extra = 0, l = line1; l <= line2; l++) {
- str = xstrdup(ml_get(l + extra));
- ml_append(dest + l - line1, str, (colnr_T)0, false);
+ char *str = xstrdup(ml_get(l + extra));
+ ml_append(dest + l - line1, str, 0, false);
xfree(str);
if (dest < line1) {
extra++;
@@ -783,16 +752,16 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
// And Finally we adjust the marks we put at the end of the file back to
// their final destination at the new text position -- webb
last_line = curbuf->b_ml.ml_line_count;
- mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(line1, line2, last_line - line2, 0, kExtmarkNOOP);
disable_fold_update++;
- changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
disable_fold_update--;
int line_off = 0;
bcount_t byte_off = 0;
if (dest >= line2) {
- mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(line2 + 1, dest, -num_lines, 0, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(win, &win->w_folds, line1, line2, dest);
@@ -805,7 +774,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
line_off = -num_lines;
byte_off = -extent_byte;
} else {
- mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
+ mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
if (win->w_buffer == curbuf) {
foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2);
@@ -820,10 +789,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
mark_adjust_nofold(last_line - num_lines + 1, last_line,
- -(last_line - dest - extra), 0L, kExtmarkNOOP);
+ -(last_line - dest - extra), 0, kExtmarkNOOP);
disable_fold_update++;
- changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
+ changed_lines(curbuf, last_line - num_lines + 1, 0, last_line + 1, -extra, false);
disable_fold_update--;
// send update regarding the new lines that were added
@@ -838,8 +807,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
ml_delete(line1 + extra, true);
}
if (!global_busy && num_lines > p_report) {
- smsg(NGETTEXT("%" PRId64 " line moved",
- "%" PRId64 " lines moved", num_lines),
+ smsg(0, NGETTEXT("%" PRId64 " line moved",
+ "%" PRId64 " lines moved", num_lines),
(int64_t)num_lines);
}
@@ -861,9 +830,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (dest > last_line + 1) {
dest = last_line + 1;
}
- changed_lines(line1, 0, dest, 0L, false);
+ changed_lines(curbuf, line1, 0, dest, 0, false);
} else {
- changed_lines(dest + 1, 0, line1 + num_lines, 0L, false);
+ changed_lines(curbuf, dest + 1, 0, line1 + num_lines, 0, false);
}
// send nvim_buf_lines_event regarding lines that were deleted
@@ -875,10 +844,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
/// ":copy"
void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
{
- linenr_T count;
- char *p;
-
- count = line2 - line1 + 1;
+ linenr_T count = line2 - line1 + 1;
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
curbuf->b_op_start.lnum = n + 1;
curbuf->b_op_end.lnum = n + count;
@@ -902,8 +868,8 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
while (line1 <= line2) {
// need to use xstrdup() because the line will be unlocked within
// ml_append()
- p = xstrdup(ml_get(line1));
- ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, false);
+ char *p = xstrdup(ml_get(line1));
+ ml_append(curwin->w_cursor.lnum, p, 0, false);
xfree(p);
// situation 2: skip already copied lines
@@ -925,7 +891,7 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n)
check_pos(curbuf, &VIsual);
}
- msgmore((long)count);
+ msgmore(count);
}
static char *prevcmd = NULL; // the previous command
@@ -938,6 +904,17 @@ void free_prev_shellcmd(void)
#endif
+/// Check that "prevcmd" is not NULL. If it is NULL then give an error message
+/// and return false.
+static int prevcmd_is_set(void)
+{
+ if (prevcmd == NULL) {
+ emsg(_(e_noprev));
+ return false;
+ }
+ return true;
+}
+
/// Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd"
/// Bangs in the argument are replaced with the previously entered command.
/// Remember the argument.
@@ -949,15 +926,9 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
linenr_T line2 = eap->line2; // end of range
char *newcmd = NULL; // the new command
bool free_newcmd = false; // need to free() newcmd
- char *t;
- char *p;
- char *trailarg;
- size_t len;
int scroll_save = msg_scroll;
- //
// Disallow shell commands in secure mode
- //
if (check_secure()) {
return;
}
@@ -973,21 +944,20 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
bool ins_prevcmd = forceit;
// Skip leading white space to avoid a strange error with some shells.
- trailarg = skipwhite(arg);
+ char *trailarg = skipwhite(arg);
do {
- len = strlen(trailarg) + 1;
+ size_t len = strlen(trailarg) + 1;
if (newcmd != NULL) {
len += strlen(newcmd);
}
if (ins_prevcmd) {
- if (prevcmd == NULL) {
- emsg(_(e_noprev));
+ if (!prevcmd_is_set()) {
xfree(newcmd);
return;
}
len += strlen(prevcmd);
}
- t = xmalloc(len);
+ char *t = xmalloc(len);
*t = NUL;
if (newcmd != NULL) {
STRCAT(t, newcmd);
@@ -995,7 +965,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
if (ins_prevcmd) {
STRCAT(t, prevcmd);
}
- p = t + strlen(t);
+ char *p = t + strlen(t);
STRCAT(t, trailarg);
xfree(newcmd);
newcmd = t;
@@ -1018,10 +988,20 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
}
} while (trailarg != NULL);
- xfree(prevcmd);
- prevcmd = newcmd;
+ // Only set "prevcmd" if there is a command to run, otherwise keep te one
+ // we have.
+ if (strlen(newcmd) > 0) {
+ xfree(prevcmd);
+ prevcmd = newcmd;
+ } else {
+ free_newcmd = true;
+ }
if (bangredo) { // put cmd in redo buffer for ! command
+ if (!prevcmd_is_set()) {
+ goto theend;
+ }
+
// If % or # appears in the command, it must have been escaped.
// Reescape them, so that redoing them does not substitute them by the
// buffername.
@@ -1034,6 +1014,9 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
}
// Add quotes around the command, for shells that need them.
if (*p_shq != NUL) {
+ if (free_newcmd) {
+ xfree(newcmd);
+ }
newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1);
STRCPY(newcmd, p_shq);
STRCAT(newcmd, prevcmd);
@@ -1045,7 +1028,7 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
msg_start();
msg_putchar(':');
msg_putchar('!');
- msg_outtrans(newcmd);
+ msg_outtrans(newcmd, 0);
msg_clr_eos();
ui_cursor_goto(msg_row, msg_col);
@@ -1056,6 +1039,8 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
do_filter(line1, line2, eap, newcmd, do_in, do_out);
apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, false, curbuf);
}
+
+theend:
if (free_newcmd) {
xfree(newcmd);
}
@@ -1081,10 +1066,6 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
{
char *itmp = NULL;
char *otmp = NULL;
- linenr_T linecount;
- linenr_T read_linecount;
- pos_T cursor_save;
- char *cmd_buf;
buf_T *old_curbuf = curbuf;
int shell_flags = 0;
const pos_T orig_start = curbuf->b_op_start;
@@ -1100,12 +1081,12 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
// regions of the buffer for foldUpdate(), linecount, etc.
cmdmod.cmod_flags &= ~CMOD_LOCKMARKS;
- cursor_save = curwin->w_cursor;
- linecount = line2 - line1 + 1;
+ pos_T cursor_save = curwin->w_cursor;
+ linenr_T linecount = line2 - line1 + 1;
curwin->w_cursor.lnum = line1;
curwin->w_cursor.col = 0;
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
// When using temp files:
// 1. * Form temp file names
@@ -1166,7 +1147,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
// Create the shell command in allocated memory.
- cmd_buf = make_filter_cmd(cmd, itmp, otmp);
+ char *cmd_buf = make_filter_cmd(cmd, itmp, otmp);
ui_cursor_goto(Rows - 1, 0);
if (do_out) {
@@ -1176,7 +1157,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
}
redraw_curbuf_later(UPD_VALID);
}
- read_linecount = curbuf->b_ml.ml_line_count;
+ linenr_T read_linecount = curbuf->b_ml.ml_line_count;
// Pass on the kShellOptDoOut flag when the output is being redirected.
call_shell(cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL);
@@ -1193,7 +1174,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
if (do_out) {
if (otmp != NULL) {
- if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, eap,
+ if (readfile(otmp, NULL, line2, 0, (linenr_T)MAXLNUM, eap,
READ_FILTER, false) != OK) {
if (!aborting()) {
msg_putchar('\n');
@@ -1222,13 +1203,13 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
// end of each line?
if (read_linecount >= linecount) {
// move all marks from old lines to new lines
- mark_adjust(line1, line2, linecount, 0L, kExtmarkNOOP);
+ mark_adjust(line1, line2, linecount, 0, kExtmarkNOOP);
} else {
// move marks from old lines to new lines, delete marks
// that are in deleted lines
- mark_adjust(line1, line1 + read_linecount - 1, linecount, 0L,
+ mark_adjust(line1, line1 + read_linecount - 1, linecount, 0,
kExtmarkNOOP);
- mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L,
+ mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0,
kExtmarkNOOP);
}
}
@@ -1255,12 +1236,12 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
if (do_in) {
vim_snprintf(msg_buf, sizeof(msg_buf),
_("%" PRId64 " lines filtered"), (int64_t)linecount);
- if (msg(msg_buf) && !msg_scroll) {
+ if (msg(msg_buf, 0) && !msg_scroll) {
// save message to display it after redraw
set_keep_msg(msg_buf, 0);
}
} else {
- msgmore((long)linecount);
+ msgmore(linecount);
}
}
} else {
@@ -1325,7 +1306,9 @@ void do_shell(char *cmd, int flags)
// 1" command to the terminal.
ui_cursor_goto(msg_row, msg_col);
(void)call_shell(cmd, (ShellOpts)flags, NULL);
- msg_didout = true;
+ if (msg_silent == 0) {
+ msg_didout = true;
+ }
did_check_timestamps = false;
need_check_timestamps = true;
@@ -1367,12 +1350,12 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
{
bool is_fish_shell =
#if defined(UNIX)
- strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+ strncmp(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
#else
false;
#endif
- bool is_pwsh = strncmp((char *)invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0
- || strncmp((char *)invocation_path_tail(p_sh, NULL), "powershell",
+ bool is_pwsh = strncmp(invocation_path_tail(p_sh, NULL), "pwsh", 4) == 0
+ || strncmp(invocation_path_tail(p_sh, NULL), "powershell",
10) == 0;
size_t len = strlen(cmd) + 1; // At least enough space for cmd + NULL.
@@ -1382,8 +1365,8 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
: 0;
if (itmp != NULL) {
- len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530
- : strlen(itmp) + sizeof(" { " " < " " } ") - 1;
+ len += is_pwsh ? strlen(itmp) + sizeof("& { Get-Content " " | & " " }") - 1 + 6 // +6: #20530
+ : strlen(itmp) + sizeof(" { " " < " " } ") - 1;
}
if (otmp != NULL) {
len += strlen(otmp) + strlen(p_srr) + 2; // two extra spaces (" "),
@@ -1394,7 +1377,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
if (is_pwsh) {
if (itmp != NULL) {
xstrlcpy(buf, "& { Get-Content ", len - 1); // FIXME: should we add "-Encoding utf8"?
- xstrlcat(buf, (const char *)itmp, len - 1);
+ xstrlcat(buf, itmp, len - 1);
xstrlcat(buf, " | & ", len - 1); // FIXME: add `&` ourself or leave to user?
xstrlcat(buf, cmd, len - 1);
xstrlcat(buf, " }", len - 1);
@@ -1407,7 +1390,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
// redirecting input and/or output.
if (itmp != NULL || otmp != NULL) {
char *fmt = is_fish_shell ? "begin; %s; end"
- : "(%s)";
+ : "(%s)";
vim_snprintf(buf, len, fmt, cmd);
} else {
xstrlcpy(buf, cmd, len);
@@ -1415,7 +1398,7 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
if (itmp != NULL) {
xstrlcat(buf, " < ", len - 1);
- xstrlcat(buf, (const char *)itmp, len - 1);
+ xstrlcat(buf, itmp, len - 1);
}
#else
// For shells that don't understand braces around commands, at least allow
@@ -1432,9 +1415,9 @@ char *make_filter_cmd(char *cmd, char *itmp, char *otmp)
}
}
xstrlcat(buf, " < ", len);
- xstrlcat(buf, (const char *)itmp, len);
+ xstrlcat(buf, itmp, len);
if (*p_shq == NUL) {
- const char *const p = find_pipe((const char *)cmd);
+ const char *const p = find_pipe(cmd);
if (p != NULL) {
xstrlcat(buf, " ", len - 1); // Insert a space before the '|' for DOS
xstrlcat(buf, p, len - 1);
@@ -1507,7 +1490,6 @@ void print_line(linenr_T lnum, int use_number, int list)
print_line_no_prefix(lnum, use_number, list);
if (save_silent) {
msg_putchar('\n');
- ui_flush();
silent_mode = save_silent;
}
info_message = false;
@@ -1515,10 +1497,7 @@ void print_line(linenr_T lnum, int use_number, int list)
int rename_buffer(char *new_fname)
{
- char *fname, *sfname, *xfname;
- buf_T *buf;
-
- buf = curbuf;
+ buf_T *buf = curbuf;
apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, false, curbuf);
// buffer changed, don't change name now
if (buf != curbuf) {
@@ -1532,9 +1511,9 @@ int rename_buffer(char *new_fname)
// name, which will become the alternate file name.
// But don't set the alternate file name if the buffer didn't have a
// name.
- fname = curbuf->b_ffname;
- sfname = curbuf->b_sfname;
- xfname = curbuf->b_fname;
+ char *fname = curbuf->b_ffname;
+ char *sfname = curbuf->b_sfname;
+ char *xfname = curbuf->b_fname;
curbuf->b_ffname = NULL;
curbuf->b_sfname = NULL;
if (setfname(curbuf, new_fname, NULL, true) == FAIL) {
@@ -1628,17 +1607,15 @@ int do_write(exarg_T *eap)
{
int other;
char *fname = NULL; // init to shut up gcc
- char *ffname;
int retval = FAIL;
char *free_fname = NULL;
buf_T *alt_buf = NULL;
- int name_was_missing;
if (not_writing()) { // check 'write' option
return FAIL;
}
- ffname = eap->arg;
+ char *ffname = eap->arg;
if (*ffname == NUL) {
if (eap->cmdidx == CMD_saveas) {
emsg(_(e_argreq));
@@ -1660,7 +1637,7 @@ int do_write(exarg_T *eap)
if (other) {
if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL
|| eap->cmdidx == CMD_saveas) {
- alt_buf = setaltfname(ffname, fname, (linenr_T)1);
+ alt_buf = setaltfname(ffname, fname, 1);
} else {
alt_buf = buflist_findname(ffname);
}
@@ -1764,7 +1741,7 @@ int do_write(exarg_T *eap)
}
}
- name_was_missing = curbuf->b_ffname == NULL;
+ int name_was_missing = curbuf->b_ffname == NULL;
retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2,
eap, eap->append, eap->forceit, true, false);
@@ -1824,7 +1801,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth
if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
char buff[DIALOG_MSG_SIZE];
- dialog_msg((char *)buff, _("Overwrite existing file \"%s\"?"), fname);
+ dialog_msg(buff, _("Overwrite existing file \"%s\"?"), fname);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) {
return FAIL;
}
@@ -1860,7 +1837,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char *fname, char *ffname, int oth
if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
char buff[DIALOG_MSG_SIZE];
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("Swap file \"%s\" exists, overwrite anyway?"),
swapname);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2)
@@ -1906,6 +1883,9 @@ void do_wqall(exarg_T *eap)
int save_forceit = eap->forceit;
if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) {
+ if (before_quit_all(eap) == FAIL) {
+ return;
+ }
exiting = true;
}
@@ -1980,11 +1960,11 @@ static int check_readonly(int *forceit, buf_T *buf)
char buff[DIALOG_MSG_SIZE];
if (buf->b_p_ro) {
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("'readonly' option is set for \"%s\".\nDo you wish to write anyway?"),
buf->b_fname);
} else {
- dialog_msg((char *)buff,
+ dialog_msg(buff,
_("File permissions of \"%s\" are read-only.\nIt may still be possible to "
"write it.\nDo you wish to try?"),
buf->b_fname);
@@ -2067,7 +2047,7 @@ int getfile(int fnum, char *ffname_arg, char *sfname_arg, int setpm, linenr_T ln
if (lnum != 0) {
curwin->w_cursor.lnum = lnum;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
retval = GETFILE_SAME_FILE; // it's in the same file
} else if (do_ecmd(fnum, ffname, sfname, NULL, lnum,
@@ -2127,17 +2107,14 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
bufref_T old_curbuf;
char *free_fname = NULL;
int retval = FAIL;
- long n;
- pos_T orig_pos;
linenr_T topline = 0;
int newcol = -1;
int solcol = -1;
- pos_T *pos;
char *command = NULL;
bool did_get_winopts = false;
int readfile_flags = 0;
bool did_inc_redrawing_disabled = false;
- long *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ OptInt *so_ptr = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
if (eap != NULL) {
command = eap->do_ecmd_cmd;
@@ -2209,9 +2186,17 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// End Visual mode before switching to another buffer, so the text can be
// copied into the GUI selection buffer.
+ // Careful: may trigger ModeChanged() autocommand
+
+ // Should we block autocommands here?
reset_VIsual();
- if ((command != NULL || newlnum > (linenr_T)0)
+ // autocommands freed window :(
+ if (oldwin != NULL && !win_valid(oldwin)) {
+ oldwin = NULL;
+ }
+
+ if ((command != NULL || newlnum > 0)
&& *get_vim_var_str(VV_SWAPCOMMAND) == NUL) {
// Set v:swapcommand for the SwapExists autocommands.
const size_t len = (command != NULL) ? strlen(command) + 3 : 30;
@@ -2251,7 +2236,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
if (command != NULL) {
tlnum = (linenr_T)atol(command);
if (tlnum <= 0) {
- tlnum = 1L;
+ tlnum = 1;
}
}
// Add BLN_NOCURWIN to avoid a new wininfo items are associated
@@ -2263,7 +2248,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
}
goto theend;
}
- buf = buflist_new(ffname, sfname, 0L,
+ buf = buflist_new(ffname, sfname, 0,
BLN_CURBUF | (flags & ECMD_SET_HELP ? 0 : BLN_LISTED));
// Autocmds may change curwin and curbuf.
if (oldwin != NULL) {
@@ -2300,7 +2285,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// May jump to last used line number for a loaded buffer or when asked
// for explicitly
if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) {
- pos = &buflist_findfmark(buf)->mark;
+ pos_T *pos = &buflist_findfmark(buf)->mark;
newlnum = pos->lnum;
solcol = pos->col;
}
@@ -2555,7 +2540,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// Careful: open_buffer() and apply_autocmds() may change the current
// buffer and window.
- orig_pos = curwin->w_cursor;
+ pos_T orig_pos = curwin->w_cursor;
topline = curwin->w_topline;
if (!oldbuf) { // need to read the file
swap_exists_action = SEA_DIALOG;
@@ -2621,7 +2606,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// If the window options were changed may need to set the spell language.
// Can only do this after the buffer has been properly setup.
if (did_get_winopts && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) {
- (void)did_set_spelllang(curwin);
+ (void)parse_spelllang(curwin);
}
if (command == NULL) {
@@ -2631,7 +2616,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
check_cursor();
} else if (newlnum > 0) { // line number from caller or old position
curwin->w_cursor.lnum = newlnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
if (solcol >= 0 && !p_sol) {
// 'sol' is off: Use last known column.
curwin->w_cursor.col = solcol;
@@ -2660,11 +2645,11 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// Obey the 'O' flag in 'cpoptions': overwrite any previous file
// message.
- if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) {
+ if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
- check_for_delay(false);
+ msg_check_for_delay(false);
}
msg_start();
msg_scroll = msg_scroll_save;
@@ -2690,7 +2675,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
RedrawingDisabled--;
did_inc_redrawing_disabled = false;
if (!skip_redraw) {
- n = *so_ptr;
+ OptInt n = *so_ptr;
if (topline == 0 && command == NULL) {
*so_ptr = 999; // force cursor to be vertically centered in the window
}
@@ -2737,7 +2722,6 @@ void ex_append(exarg_T *eap)
linenr_T lnum = eap->line2;
int indent = 0;
char *p;
- int vcol;
int empty = (curbuf->b_ml.ml_flags & ML_EMPTY);
// the ! flag toggles autoindent
@@ -2764,7 +2748,7 @@ void ex_append(exarg_T *eap)
State |= MODE_LANGMAP;
}
- for (;;) {
+ while (true) {
msg_scroll = true;
need_wait_return = false;
if (curbuf->b_p_ai) {
@@ -2785,7 +2769,7 @@ void ex_append(exarg_T *eap)
if (p == NULL) {
p = eap->nextcmd + strlen(eap->nextcmd);
}
- theline = xstrnsave(eap->nextcmd, (size_t)(p - eap->nextcmd));
+ theline = xmemdupz(eap->nextcmd, (size_t)(p - eap->nextcmd));
if (*p != NUL) {
p++;
}
@@ -2804,7 +2788,7 @@ void ex_append(exarg_T *eap)
}
// Look for the "." after automatic indent.
- vcol = 0;
+ int vcol = 0;
for (p = theline; indent > vcol; p++) {
if (*p == ' ') {
vcol++;
@@ -2827,19 +2811,19 @@ void ex_append(exarg_T *eap)
}
did_undo = true;
- ml_append(lnum, theline, (colnr_T)0, false);
+ ml_append(lnum, theline, 0, false);
if (empty) {
// there are no marks below the inserted lines
- appended_lines(lnum, 1L);
+ appended_lines(lnum, 1);
} else {
- appended_lines_mark(lnum, 1L);
+ appended_lines_mark(lnum, 1);
}
xfree(theline);
lnum++;
if (empty) {
- ml_delete(2L, false);
+ ml_delete(2, false);
empty = 0;
}
}
@@ -2863,7 +2847,7 @@ void ex_append(exarg_T *eap)
curbuf->b_op_start.col = curbuf->b_op_end.col = 0;
}
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
need_wait_return = false; // don't use wait_return() now
@@ -2893,7 +2877,7 @@ void ex_change(exarg_T *eap)
}
// make sure the cursor is not beyond the end of the file now
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
deleted_lines_mark(eap->line1, (eap->line2 - lnum));
// ":append" on the line above the deleted lines.
@@ -2903,12 +2887,9 @@ void ex_change(exarg_T *eap)
void ex_z(exarg_T *eap)
{
- char *x;
int64_t bigness;
- char *kind;
int minus = 0;
- linenr_T start, end, curs, i;
- int j;
+ linenr_T start, end, curs;
linenr_T lnum = eap->line2;
// Vi compatible: ":z!" uses display height, without a count uses
@@ -2924,8 +2905,8 @@ void ex_z(exarg_T *eap)
bigness = 1;
}
- x = eap->arg;
- kind = x;
+ char *x = eap->arg;
+ char *kind = x;
if (*kind == '-' || *kind == '+' || *kind == '='
|| *kind == '^' || *kind == '.') {
x++;
@@ -2936,7 +2917,7 @@ void ex_z(exarg_T *eap)
if (*x != 0) {
if (!ascii_isdigit(*x)) {
- emsg(_("E144: non-numeric argument to :z"));
+ emsg(_(e_non_numeric_argument_to_z));
return;
}
bigness = atol(x);
@@ -2946,7 +2927,7 @@ void ex_z(exarg_T *eap)
bigness = 2 * curbuf->b_ml.ml_line_count;
}
- p_window = bigness;
+ p_window = (int)bigness;
if (*kind == '=') {
bigness += 2;
}
@@ -3009,11 +2990,11 @@ void ex_z(exarg_T *eap)
curs = 1;
}
- for (i = start; i <= end; i++) {
+ for (linenr_T i = start; i <= end; i++) {
if (minus && i == lnum) {
msg_putchar('\n');
- for (j = 1; j < Columns; j++) {
+ for (int j = 1; j < Columns; j++) {
msg_putchar('-');
}
}
@@ -3023,7 +3004,7 @@ void ex_z(exarg_T *eap)
if (minus && i == lnum) {
msg_putchar('\n');
- for (j = 1; j < Columns; j++) {
+ for (int j = 1; j < Columns; j++) {
msg_putchar('-');
}
}
@@ -3149,21 +3130,21 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, const char *sub, const ch
/// Slightly more memory that is strictly necessary is allocated to reduce the
/// frequency of memory (re)allocation.
///
-/// @param[in,out] new_start pointer to the memory for the replacement text
-/// @param[in] needed_len amount of memory needed
+/// @param[in,out] new_start pointer to the memory for the replacement text
+/// @param[in,out] new_start_len pointer to length of new_start
+/// @param[in] needed_len amount of memory needed
///
/// @returns pointer to the end of the allocated memory
-static char *sub_grow_buf(char **new_start, int needed_len)
- FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_RET
+static char *sub_grow_buf(char **new_start, int *new_start_len, int needed_len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- int new_start_len = 0;
char *new_end;
if (*new_start == NULL) {
// Get some space for a temporary buffer to do the
// substitution into (and some extra space to avoid
// too many calls to xmalloc()/free()).
- new_start_len = needed_len + 50;
- *new_start = xmalloc((size_t)new_start_len);
+ *new_start_len = needed_len + 50;
+ *new_start = xmalloc((size_t)(*new_start_len));
**new_start = NUL;
new_end = *new_start;
} else {
@@ -3172,9 +3153,9 @@ static char *sub_grow_buf(char **new_start, int needed_len)
// extra to avoid too many calls to xmalloc()/free()).
size_t len = strlen(*new_start);
needed_len += (int)len;
- if (needed_len > new_start_len) {
- new_start_len = needed_len + 50;
- *new_start = xrealloc(*new_start, (size_t)new_start_len);
+ if (needed_len > *new_start_len) {
+ *new_start_len = needed_len + 50;
+ *new_start = xrealloc(*new_start, (size_t)(*new_start_len));
}
new_end = *new_start + len;
}
@@ -3242,6 +3223,25 @@ static char *sub_parse_flags(char *cmd, subflags_T *subflags, int *which_pat)
return cmd;
}
+/// Skip over the "sub" part in :s/pat/sub/ where "delimiter" is the separating
+/// character.
+static char *skip_substitute(char *start, int delimiter)
+{
+ char *p = start;
+
+ while (p[0]) {
+ if (p[0] == delimiter) { // end delimiter found
+ *p++ = NUL; // replace it with a NUL
+ break;
+ }
+ if (p[0] == '\\' && p[1] != 0) { // skip escaped characters
+ p++;
+ }
+ MB_PTR_ADV(p);
+ }
+ return p;
+}
+
static int check_regexp_delim(int c)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -3259,9 +3259,11 @@ static int check_regexp_delim(int c)
///
/// The usual escapes are supported as described in the regexp docs.
///
-/// @param do_buf_event If `true`, send buffer updates.
+/// @param cmdpreview_ns The namespace to show 'inccommand' preview highlights.
+/// If <= 0, preview shouldn't be shown.
/// @return 0, 1 or 2. See show_cmdpreview() for more information on what the return value means.
-static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_ns,
+ const handle_T cmdpreview_bufnr)
{
#define ADJUST_SUB_FIRSTLNUM() \
do { \
@@ -3287,7 +3289,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
} \
} while (0)
- long i = 0;
+ int i = 0;
regmmatch_T regmatch;
static subflags_T subflags = {
.do_all = false,
@@ -3308,14 +3310,14 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
int which_pat;
char *cmd = eap->arg;
linenr_T first_line = 0; // first changed line
- linenr_T last_line= 0; // below last changed line AFTER the change
+ linenr_T last_line = 0; // below last changed line AFTER the change
linenr_T old_line_count = curbuf->b_ml.ml_line_count;
char *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done
PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
static int pre_hl_id = 0;
pos_T old_cursor = curwin->w_cursor;
- long start_nsubs;
+ int start_nsubs;
bool did_save = false;
@@ -3366,20 +3368,11 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// Small incompatibility: vi sees '\n' as end of the command, but in
// Vim we want to use '\n' to find/substitute a NUL.
- sub = cmd; // remember the start of the substitution
-
- while (cmd[0]) {
- if (cmd[0] == delimiter) { // end delimiter found
- *cmd++ = NUL; // replace it with a NUL
- break;
- }
- if (cmd[0] == '\\' && cmd[1] != 0) { // skip escaped characters
- cmd++;
- }
- MB_PTR_ADV(cmd);
- }
+ char *p = cmd; // remember the start of the substitution
+ cmd = skip_substitute(cmd, delimiter);
+ sub = xstrdup(p);
- if (!eap->skip && !cmdpreview) {
+ if (!eap->skip && cmdpreview_ns <= 0) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup(sub),
.timestamp = os_time(),
@@ -3392,14 +3385,15 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
return 0;
}
pat = NULL; // search_regcomp() will use previous pattern
- sub = old_sub.sub;
+ sub = xstrdup(old_sub.sub);
// Vi compatibility quirk: repeating with ":s" keeps the cursor in the
// last column after using "$".
endcolumn = (curwin->w_curswant == MAXCOL);
}
- if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, !cmdpreview)) {
+ if (sub != NULL && sub_joining_lines(eap, pat, sub, cmd, cmdpreview_ns <= 0)) {
+ xfree(sub);
return 0;
}
@@ -3411,9 +3405,16 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// check for a trailing count
cmd = skipwhite(cmd);
if (ascii_isdigit(*cmd)) {
- i = getdigits_long(&cmd, true, 0);
+ i = getdigits_int(&cmd, true, INT_MAX);
if (i <= 0 && !eap->skip && subflags.do_error) {
emsg(_(e_zerocount));
+ xfree(sub);
+ return 0;
+ } else if (i >= INT_MAX) {
+ char buf[20];
+ vim_snprintf(buf, sizeof(buf), "%d", i);
+ semsg(_(e_val_too_large), buf);
+ xfree(sub);
return 0;
}
eap->line1 = eap->line2;
@@ -3429,25 +3430,29 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
eap->nextcmd = check_nextcmd(cmd);
if (eap->nextcmd == NULL) {
semsg(_(e_trailing_arg), cmd);
+ xfree(sub);
return 0;
}
}
if (eap->skip) { // not executing commands, only parsing
+ xfree(sub);
return 0;
}
if (!subflags.do_count && !MODIFIABLE(curbuf)) {
// Substitution is not allowed in non-'modifiable' buffer
emsg(_(e_modifiable));
+ xfree(sub);
return 0;
}
if (search_regcomp(pat, NULL, RE_SUBST, which_pat,
- (cmdpreview ? 0 : SEARCH_HIS), &regmatch) == FAIL) {
+ (cmdpreview_ns > 0 ? 0 : SEARCH_HIS), &regmatch) == FAIL) {
if (subflags.do_error) {
emsg(_(e_invcmd));
}
+ xfree(sub);
return 0;
}
@@ -3462,22 +3467,20 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
assert(sub != NULL);
- char *sub_copy = NULL;
-
// If the substitute pattern starts with "\=" then it's an expression.
// Make a copy, a recursive function may free it.
// Otherwise, '~' in the substitute pattern is replaced with the old
// pattern. We do it here once to avoid it to be replaced over and over
// again.
if (sub[0] == '\\' && sub[1] == '=') {
- sub = xstrdup(sub);
- sub_copy = sub;
+ char *p = xstrdup(sub);
+ xfree(sub);
+ sub = p;
} else {
- char *newsub = regtilde(sub, magic_isset(), cmdpreview);
- if (newsub != sub) {
- // newsub was allocated, free it later.
- sub_copy = newsub;
- sub = newsub;
+ char *p = regtilde(sub, magic_isset(), cmdpreview_ns > 0);
+ if (p != sub) {
+ xfree(sub);
+ sub = p;
}
}
@@ -3487,20 +3490,21 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
for (linenr_T lnum = eap->line1;
lnum <= line2 && !got_quit && !aborting()
- && (!cmdpreview || preview_lines.lines_needed <= (linenr_T)p_cwh
+ && (cmdpreview_ns <= 0 || preview_lines.lines_needed <= (linenr_T)p_cwh
|| lnum <= curwin->w_botline);
lnum++) {
- long nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
- (colnr_T)0, NULL, NULL);
+ int nmatch = vim_regexec_multi(&regmatch, curwin, curbuf, lnum,
+ 0, NULL, NULL);
if (nmatch) {
colnr_T copycol;
colnr_T matchcol;
colnr_T prev_matchcol = MAXCOL;
char *new_end, *new_start = NULL;
+ int new_start_len = 0;
char *p1;
bool did_sub = false;
int lastone;
- long nmatch_tl = 0; // nr of lines matched below lnum
+ linenr_T nmatch_tl = 0; // nr of lines matched below lnum
int do_again; // do it again after joining lines
bool skip_match = false;
linenr_T sub_firstlnum; // nr of first sub line
@@ -3542,7 +3546,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// accordingly.
//
// The new text is built up in new_start[]. It has some extra
- // room to avoid using xmalloc()/free() too often.
+ // room to avoid using xmalloc()/free() too often. new_start_len is
+ // the length of the allocated memory at new_start.
//
// Make a copy of the old line, so it won't be taken away when
// updating the screen or handling a multi-line match. The "old_"
@@ -3563,10 +3568,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// 3. substitute the string.
// 4. if subflags.do_all is set, find next match
// 5. break if there isn't another match in this line
- for (;;) {
+ while (true) {
SubResult current_match = {
.start = { 0, 0 },
- .end = { 0, 0 },
+ .end = { 0, 0 },
.pre_match = 0,
};
// lnum is where the match start, but maybe not the pattern match,
@@ -3648,7 +3653,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
}
}
- if (subflags.do_ask && !cmdpreview) {
+ if (subflags.do_ask && cmdpreview_ns <= 0) {
int typed = 0;
// change State to MODE_CONFIRM, so that the mouse works
@@ -3754,6 +3759,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
update_topline(curwin);
validate_cursor();
redraw_later(curwin, UPD_SOME_VALID);
+ show_cursor_info_later(true);
update_screen();
highlight_match = false;
redraw_later(curwin, UPD_SOME_VALID);
@@ -3768,11 +3774,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// needed
msg_no_more = true;
msg_ext_set_kind("confirm_sub");
- smsg_attr(HL_ATTR(HLF_R), // Same highlight as wait_return().
- _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
+ // Same highlight as wait_return().
+ smsg(HL_ATTR(HLF_R), _("replace with %s (y/n/a/q/l/^E/^Y)?"), sub);
msg_no_more = false;
- msg_scroll = (int)i;
- show_cursor_info(true);
+ msg_scroll = i;
if (!ui_has(kUIMessages)) {
ui_cursor_goto(msg_row, msg_col);
}
@@ -3856,17 +3861,21 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1;
current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch;
skip_match = true;
+ // safety check
+ if (nmatch < 0) {
+ goto skip;
+ }
}
// Save the line numbers for the preview buffer
// NOTE: If the pattern matches a final newline, the next line will
// be shown also, but should not be highlighted. Intentional for now.
- if (cmdpreview && !has_second_delim) {
+ if (cmdpreview_ns > 0 && !has_second_delim) {
current_match.start.col = regmatch.startpos[0].col;
if (current_match.end.lnum == 0) {
current_match.end.lnum = sub_firstlnum + (linenr_T)nmatch - 1;
}
- current_match.end.col = regmatch.endpos[0].col;
+ current_match.end.col = regmatch.endpos[0].col;
ADJUST_SUB_FIRSTLNUM();
lnum += (linenr_T)nmatch - 1;
@@ -3876,8 +3885,8 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
// 3. Substitute the string. During 'inccommand' preview only do this if
// there is a replace pattern.
- if (!cmdpreview || has_second_delim) {
- long lnum_start = lnum; // save the start lnum
+ if (cmdpreview_ns <= 0 || has_second_delim) {
+ linenr_T lnum_start = lnum; // save the start lnum
int save_ma = curbuf->b_p_ma;
int save_sandbox = sandbox;
if (subflags.do_count) {
@@ -3921,15 +3930,19 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
p1 = ml_get(sub_firstlnum + (linenr_T)nmatch - 1);
nmatch_tl += nmatch - 1;
}
- size_t copy_len = (size_t)(regmatch.startpos[0].col - copycol);
- new_end = sub_grow_buf(&new_start,
+ int copy_len = regmatch.startpos[0].col - copycol;
+ new_end = sub_grow_buf(&new_start, &new_start_len,
(colnr_T)strlen(p1) - regmatch.endpos[0].col
- + (colnr_T)copy_len + sublen + 1);
+ + copy_len + sublen + 1);
// copy the text up to the part that matched
- memmove(new_end, sub_firstline + copycol, copy_len);
+ memmove(new_end, sub_firstline + copycol, (size_t)copy_len);
new_end += copy_len;
+ if (new_start_len - copy_len < sublen) {
+ sublen = new_start_len - copy_len - 1;
+ }
+
// Finally, at this point we can know where the match actually will
// start in the new text
int start_col = (int)(new_end - new_start);
@@ -3977,10 +3990,10 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
*p1 = NUL; // truncate up to the CR
ml_append(lnum - 1, new_start,
(colnr_T)(p1 - new_start + 1), false);
- mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, kExtmarkNOOP);
+ mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1, 0, kExtmarkNOOP);
if (subflags.do_ask) {
- appended_lines(lnum - 1, 1L);
+ appended_lines(lnum - 1, 1);
} else {
if (first_line == 0) {
first_line = lnum;
@@ -4015,7 +4028,7 @@ static int do_sub(exarg_T *eap, proftime_T timeout, long cmdpreview_ns, handle_T
}
extmark_splice(curbuf, (int)lnum_start - 1, start_col,
end.lnum - start.lnum, matchcols, replaced_bytes,
- lnum - (linenr_T)lnum_start, subcols, sublen - 1, kExtmarkUndo);
+ lnum - lnum_start, subcols, sublen - 1, kExtmarkUndo);
}
// 4. If subflags.do_all is set, find next match.
@@ -4078,13 +4091,12 @@ skip:
for (i = 0; i < nmatch_tl; i++) {
ml_delete(lnum, false);
}
- mark_adjust(lnum, lnum + (linenr_T)nmatch_tl - 1,
- (long)MAXLNUM, (linenr_T)(-nmatch_tl), kExtmarkNOOP);
+ mark_adjust(lnum, lnum + nmatch_tl - 1, MAXLNUM, -nmatch_tl, kExtmarkNOOP);
if (subflags.do_ask) {
- deleted_lines(lnum, (linenr_T)nmatch_tl);
+ deleted_lines(lnum, nmatch_tl);
}
lnum--;
- line2 -= (linenr_T)nmatch_tl; // nr of lines decreases
+ line2 -= nmatch_tl; // nr of lines decreases
nmatch_tl = 0;
}
@@ -4126,7 +4138,7 @@ skip:
#define PUSH_PREVIEW_LINES() \
do { \
- if (cmdpreview) { \
+ if (cmdpreview_ns > 0) { \
linenr_T match_lines = current_match.end.lnum \
- current_match.start.lnum +1; \
if (preview_lines.subresults.size > 0) { \
@@ -4178,7 +4190,7 @@ skip:
// the line number before the change (same as adding the number of
// deleted lines).
i = curbuf->b_ml.ml_line_count - old_line_count;
- changed_lines(first_line, 0, last_line - (linenr_T)i, (linenr_T)i, false);
+ changed_lines(curbuf, first_line, 0, last_line - (linenr_T)i, (linenr_T)i, false);
int64_t num_added = last_line - first_line;
int64_t num_removed = num_added - i;
@@ -4209,8 +4221,8 @@ skip:
beginline(BL_WHITE | BL_FIX);
}
}
- if (!cmdpreview && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) {
- msg("");
+ if (cmdpreview_ns <= 0 && !do_sub_msg(subflags.do_count) && subflags.do_ask && p_ch > 0) {
+ msg("", 0);
}
} else {
global_need_beginline = true;
@@ -4225,7 +4237,7 @@ skip:
} else if (got_match) {
// did find something but nothing substituted
if (p_ch > 0) {
- msg("");
+ msg("", 0);
}
} else if (subflags.do_error) {
// nothing found
@@ -4239,7 +4251,7 @@ skip:
}
vim_regfree(regmatch.regprog);
- xfree(sub_copy);
+ xfree(sub);
// Restore the flag values, they can be used for ":&&".
subflags.do_all = save_do_all;
@@ -4248,7 +4260,7 @@ skip:
int retv = 0;
// Show 'inccommand' preview if there are matched lines.
- if (cmdpreview && !aborting()) {
+ if (cmdpreview_ns > 0 && !aborting()) {
if (got_quit || profile_passed_limit(timeout)) { // Too slow, disable.
set_string_option_direct("icm", -1, "", OPT_FREE, SID_NONE);
} else if (*p_icm != NUL && pat != NULL) {
@@ -4287,19 +4299,19 @@ bool do_sub_msg(bool count_only)
}
char *msg_single = count_only
- ? NGETTEXT("%" PRId64 " match on %" PRId64 " line",
- "%" PRId64 " matches on %" PRId64 " line", sub_nsubs)
- : NGETTEXT("%" PRId64 " substitution on %" PRId64 " line",
- "%" PRId64 " substitutions on %" PRId64 " line", sub_nsubs);
+ ? NGETTEXT("%" PRId64 " match on %" PRId64 " line",
+ "%" PRId64 " matches on %" PRId64 " line", sub_nsubs)
+ : NGETTEXT("%" PRId64 " substitution on %" PRId64 " line",
+ "%" PRId64 " substitutions on %" PRId64 " line", sub_nsubs);
char *msg_plural = count_only
- ? NGETTEXT("%" PRId64 " match on %" PRId64 " lines",
- "%" PRId64 " matches on %" PRId64 " lines", sub_nsubs)
- : NGETTEXT("%" PRId64 " substitution on %" PRId64 " lines",
- "%" PRId64 " substitutions on %" PRId64 " lines", sub_nsubs);
- vim_snprintf_add((char *)msg_buf, sizeof(msg_buf),
+ ? NGETTEXT("%" PRId64 " match on %" PRId64 " lines",
+ "%" PRId64 " matches on %" PRId64 " lines", sub_nsubs)
+ : NGETTEXT("%" PRId64 " substitution on %" PRId64 " lines",
+ "%" PRId64 " substitutions on %" PRId64 " lines", sub_nsubs);
+ vim_snprintf_add(msg_buf, sizeof(msg_buf),
NGETTEXT(msg_single, msg_plural, sub_nlines),
(int64_t)sub_nsubs, (int64_t)sub_nlines);
- if (msg(msg_buf)) {
+ if (msg(msg_buf, 0)) {
// save message to display it after redraw
set_keep_msg(msg_buf, 0);
}
@@ -4340,15 +4352,12 @@ static void global_exe_one(char *const cmd, const linenr_T lnum)
void ex_global(exarg_T *eap)
{
linenr_T lnum; // line number according to old situation
- int ndone = 0;
int type; // first char of cmd: 'v' or 'g'
- char *cmd; // command argument
+ char *cmd; // command argument
char delim; // delimiter, normally '/'
char *pat;
regmmatch_T regmatch;
- int match;
- int which_pat;
// When nesting the command works on one line. This allows for
// ":g/found/v/notfound/command".
@@ -4365,7 +4374,7 @@ void ex_global(exarg_T *eap)
type = (uint8_t)(*eap->cmd);
}
cmd = eap->arg;
- which_pat = RE_LAST; // default: use last used regexp
+ int which_pat = RE_LAST; // default: use last used regexp
// undocumented vi feature:
// "\/" and "\?": use previous search pattern.
@@ -4407,15 +4416,16 @@ void ex_global(exarg_T *eap)
if (global_busy) {
lnum = curwin->w_cursor.lnum;
- match = (int)vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
+ int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
if ((type == 'g' && match) || (type == 'v' && !match)) {
global_exe_one(cmd, lnum);
}
} else {
+ int ndone = 0;
// pass 1: set marks for each (not) matching line
for (lnum = eap->line1; lnum <= eap->line2 && !got_int; lnum++) {
// a match on this line?
- match = (int)vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
+ int match = vim_regexec_multi(&regmatch, curwin, curbuf, lnum, 0, NULL, NULL);
if (regmatch.regprog == NULL) {
break; // re-compiling regprog failed
}
@@ -4428,12 +4438,12 @@ void ex_global(exarg_T *eap)
// pass 2: execute the command for each line that has been marked
if (got_int) {
- msg(_(e_interr));
+ msg(_(e_interr), 0);
} else if (ndone == 0) {
if (type == 'v') {
- smsg(_("Pattern found in every line: %s"), used_pat);
+ smsg(0, _("Pattern found in every line: %s"), used_pat);
} else {
- smsg(_("Pattern not found: %s"), used_pat);
+ smsg(0, _("Pattern not found: %s"), used_pat);
}
} else {
global_exe(cmd);
@@ -4541,7 +4551,7 @@ bool prepare_tagpreview(bool undo_sync)
///
/// @return 1 if preview window isn't needed, 2 if preview window is needed.
static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, int hl_id,
- long cmdpreview_ns, handle_T cmdpreview_bufnr)
+ int cmdpreview_ns, handle_T cmdpreview_bufnr)
FUNC_ATTR_NONNULL_ALL
{
char *save_shm_p = xstrdup(p_shm);
@@ -4589,15 +4599,15 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
linenr_T linenr_origbuf = 0; // last line added to original buffer
linenr_T next_linenr = 0; // next line to show for the match
- // Temporarily switch to preview buffer
- aco_save_T aco;
-
for (size_t matchidx = 0; matchidx < lines.subresults.size; matchidx++) {
SubResult match = lines.subresults.items[matchidx];
if (cmdpreview_buf) {
lpos_T p_start = { 0, match.start.col }; // match starts here in preview
- lpos_T p_end = { 0, match.end.col }; // ... and ends here
+ lpos_T p_end = { 0, match.end.col }; // ... and ends here
+
+ // You Might Gonna Need It
+ buf_ensure_loaded(cmdpreview_buf);
if (match.pre_match == 0) {
next_linenr = match.start.lnum;
@@ -4622,7 +4632,7 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
if (next_linenr == orig_buf->b_ml.ml_line_count + 1) {
line = "";
} else {
- line = ml_get_buf(orig_buf, next_linenr, false);
+ line = ml_get_buf(orig_buf, next_linenr);
line_size = strlen(line) + (size_t)col_width + 1;
// Reallocate if line not long enough
@@ -4634,21 +4644,18 @@ static int show_sub(exarg_T *eap, pos_T old_cusr, PreviewLines *preview_lines, i
// Put "|lnum| line" into `str` and append it to the preview buffer.
snprintf(str, line_size, "|%*" PRIdLINENR "| %s", col_width - 3,
next_linenr, line);
- // Temporarily switch to preview buffer
- aucmd_prepbuf(&aco, cmdpreview_buf);
if (linenr_preview == 0) {
- ml_replace(1, str, true);
+ ml_replace_buf(cmdpreview_buf, 1, str, true);
} else {
- ml_append(linenr_preview, str, (colnr_T)line_size, false);
+ ml_append_buf(cmdpreview_buf, linenr_preview, str, (colnr_T)line_size, false);
}
- aucmd_restbuf(&aco);
linenr_preview += 1;
}
linenr_origbuf = match.end.lnum;
- bufhl_add_hl_pos_offset(cmdpreview_buf, (int)cmdpreview_ns, hl_id, p_start, p_end, col_width);
+ bufhl_add_hl_pos_offset(cmdpreview_buf, cmdpreview_ns, hl_id, p_start, p_end, col_width);
}
- bufhl_add_hl_pos_offset(orig_buf, (int)cmdpreview_ns, hl_id, match.start, match.end, 0);
+ bufhl_add_hl_pos_offset(orig_buf, cmdpreview_ns, hl_id, match.start, match.end, 0);
}
xfree(str);
@@ -4666,7 +4673,7 @@ void ex_substitute(exarg_T *eap)
}
/// :substitute command preview callback.
-int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+int ex_substitute_preview(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr)
{
// Only preview once the pattern delimiter has been typed
if (*eap->arg && !ASCII_ISALNUM(*eap->arg)) {
@@ -4688,8 +4695,6 @@ int ex_substitute_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_
/// @return a pointer to the char just past the pattern plus flags.
char *skip_vimgrep_pat(char *p, char **s, int *flags)
{
- int c;
-
if (vim_isIDc((uint8_t)(*p))) {
// ":vimgrep pattern fname"
if (s != NULL) {
@@ -4704,7 +4709,7 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags)
if (s != NULL) {
*s = p + 1;
}
- c = (uint8_t)(*p);
+ int c = (uint8_t)(*p);
p = skip_regexp(p + 1, c, true);
if (*p != c) {
return NULL;
@@ -4737,10 +4742,10 @@ char *skip_vimgrep_pat(char *p, char **s, int *flags)
void ex_oldfiles(exarg_T *eap)
{
list_T *l = get_vim_var_list(VV_OLDFILES);
- long nr = 0;
+ int nr = 0;
if (l == NULL) {
- msg(_("No old files"));
+ msg(_("No old files"), 0);
return;
}
@@ -4752,10 +4757,10 @@ void ex_oldfiles(exarg_T *eap)
}
nr++;
const char *fname = tv_get_string(TV_LIST_ITEM_TV(li));
- if (!message_filtered((char *)fname)) {
+ if (!message_filtered(fname)) {
msg_outnum(nr);
msg_puts(": ");
- msg_outtrans((char *)tv_get_string(TV_LIST_ITEM_TV(li)));
+ msg_outtrans(tv_get_string(TV_LIST_ITEM_TV(li)), 0);
msg_clr_eos();
msg_putchar('\n');
os_breakcheck();
@@ -4771,7 +4776,7 @@ void ex_oldfiles(exarg_T *eap)
nr = prompt_for_number(false);
msg_starthere();
if (nr > 0 && nr <= tv_list_len(l)) {
- const char *const p = tv_list_find_str(l, (int)nr - 1);
+ const char *const p = tv_list_find_str(l, nr - 1);
if (p == NULL) {
return;
}
@@ -4784,29 +4789,3 @@ void ex_oldfiles(exarg_T *eap)
}
}
}
-
-void ex_trust(exarg_T *eap)
-{
- const char *const p = skiptowhite(eap->arg);
- char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg));
- const char *action = "allow";
- const char *path = skipwhite(p);
-
- if (strcmp(arg1, "++deny") == 0) {
- action = "deny";
- } else if (strcmp(arg1, "++remove") == 0) {
- action = "remove";
- } else if (*arg1 != '\0') {
- semsg(e_invarg2, arg1);
- goto theend;
- }
-
- if (path[0] == '\0') {
- path = NULL;
- }
-
- nlua_trust(action, path);
-
-theend:
- xfree(arg1);
-}
diff --git a/src/nvim/ex_cmds.h b/src/nvim/ex_cmds.h
index 39bff3e35d..de13f03197 100644
--- a/src/nvim/ex_cmds.h
+++ b/src/nvim/ex_cmds.h
@@ -1,14 +1,12 @@
-#ifndef NVIM_EX_CMDS_H
-#define NVIM_EX_CMDS_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: export
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
// flags for do_ecmd()
#define ECMD_HIDE 0x01 // don't free the current buffer
@@ -21,9 +19,9 @@
#define ECMD_NOWINENTER 0x40 // do not trigger BufWinEnter
// for lnum argument in do_ecmd()
-#define ECMD_LASTL (linenr_T)0 // use last position in loaded file
-#define ECMD_LAST ((linenr_T)(-1)) // use last position in all files
-#define ECMD_ONE (linenr_T)1 // use first line
+#define ECMD_LASTL 0 // use last position in loaded file
+#define ECMD_LAST (-1) // use last position in all files
+#define ECMD_ONE 1 // use first line
/// Previous :substitute replacement string definition
typedef struct {
@@ -35,4 +33,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds.h.generated.h"
#endif
-#endif // NVIM_EX_CMDS_H
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index c8b6ceab69..4859a70553 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -197,12 +197,6 @@ module.cmds = {
func='ex_bunload',
},
{
- command='behave',
- flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
- addr_type='ADDR_NONE',
- func='ex_behave',
- },
- {
command='belowright',
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM),
addr_type='ADDR_NONE',
@@ -640,7 +634,7 @@ module.cmds = {
command='const',
flags=bit.bor(EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
- func='ex_const',
+ func='ex_let',
},
{
command='copen',
@@ -721,6 +715,12 @@ module.cmds = {
func='ex_debuggreedy',
},
{
+ command='defer',
+ flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, SBOXOK, CMDWIN, LOCK_OK),
+ addr_type='ADDR_NONE',
+ func='ex_call',
+ },
+ {
command='delcommand',
flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
@@ -1045,6 +1045,12 @@ module.cmds = {
func='ex_function',
},
{
+ command='fclose',
+ flags=bit.bor(BANG, RANGE),
+ addr_type='ADDR_OTHER',
+ func='ex_fclose',
+ },
+ {
command='global',
flags=bit.bor(RANGE, WHOLEFOLD, BANG, EXTRA, DFLALL, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_LINES',
@@ -2212,7 +2218,7 @@ module.cmds = {
},
{
command='registers',
- flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN, LOCK_OK),
+ flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, SBOXOK, CMDWIN, LOCK_OK),
addr_type='ADDR_NONE',
func='ex_display',
},
@@ -3319,7 +3325,7 @@ module.cmds = {
{
command='=',
enum='CMD_equal',
- flags=bit.bor(RANGE, TRLBAR, DFLALL, FLAGS, CMDWIN, LOCK_OK),
+ flags=bit.bor(RANGE, EXTRA, DFLALL, ARGOPT, CMDWIN, LOCK_OK),
addr_type='ADDR_LINES',
func='ex_equal',
},
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index d08823bc30..1722b7902b 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file ex_cmds2.c
///
/// Some more functions for command line commands
@@ -12,14 +9,14 @@
#include <string.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -29,28 +26,30 @@
#include "nvim/fileio.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.c.generated.h"
#endif
+static const char e_compiler_not_supported_str[]
+ = N_("E666: Compiler not supported: %s");
+
void ex_ruby(exarg_T *eap)
{
script_host_execute("ruby", eap);
@@ -102,7 +101,6 @@ void ex_perldo(exarg_T *eap)
/// @return FAIL for failure, OK otherwise
int autowrite(buf_T *buf, int forceit)
{
- int r;
bufref_T bufref;
if (!(p_aw || p_awa) || !p_write
@@ -112,7 +110,7 @@ int autowrite(buf_T *buf, int forceit)
return FAIL;
}
set_bufref(&bufref, buf);
- r = buf_write_all(buf, forceit);
+ int r = buf_write_all(buf, forceit);
// Writing may succeed but the buffer still changed, e.g., when there is a
// conversion error. We do want to return FAIL then.
@@ -201,7 +199,7 @@ void dialog_changed(buf_T *buf, bool checkall)
.forceit = false,
};
- dialog_msg((char *)buff, _("Save changes to \"%s\"?"), buf->b_fname);
+ dialog_msg(buff, _("Save changes to \"%s\"?"), buf->b_fname);
if (checkall) {
ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1);
} else {
@@ -274,9 +272,7 @@ bool can_abandon(buf_T *buf, int forceit)
/// Add a buffer number to "bufnrs", unless it's already there.
static void add_bufnum(int *bufnrs, int *bufnump, int nr)
{
- int i;
-
- for (i = 0; i < *bufnump; i++) {
+ for (int i = 0; i < *bufnump; i++) {
if (bufnrs[i] == nr) {
return;
}
@@ -297,11 +293,9 @@ static void add_bufnum(int *bufnrs, int *bufnump, int nr)
bool check_changed_any(bool hidden, bool unload)
{
bool ret = false;
- int save;
int i;
int bufnum = 0;
size_t bufcount = 0;
- int *bufnrs;
// Make a list of all buffers, with the most important ones first.
FOR_ALL_BUFFERS(buf) {
@@ -312,7 +306,7 @@ bool check_changed_any(bool hidden, bool unload)
return false;
}
- bufnrs = xmalloc(sizeof(*bufnrs) * bufcount);
+ int *bufnrs = xmalloc(sizeof(*bufnrs) * bufcount);
// curbuf
bufnrs[bufnum++] = curbuf->b_fnum;
@@ -380,7 +374,7 @@ bool check_changed_any(bool hidden, bool unload)
? semsg(_("E947: Job still running in buffer \"%s\""), buf->b_fname)
: semsg(_("E162: No write since last change for buffer \"%s\""),
buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname)) {
- save = no_wait_return;
+ int save = no_wait_return;
no_wait_return = false;
wait_return(false);
no_wait_return = save;
@@ -430,15 +424,14 @@ int check_fname(void)
/// @return FAIL for failure, OK otherwise
int buf_write_all(buf_T *buf, int forceit)
{
- int retval;
buf_T *old_curbuf = curbuf;
- retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
- (linenr_T)1, buf->b_ml.ml_line_count, NULL,
- false, forceit, true, false));
+ int retval = (buf_write(buf, buf->b_ffname, buf->b_fname,
+ 1, buf->b_ml.ml_line_count, NULL,
+ false, forceit, true, false));
if (curbuf != old_curbuf) {
msg_source(HL_ATTR(HLF_W));
- msg(_("Warning: Entered other buffer unexpectedly (check autocommands)"));
+ msg(_("Warning: Entered other buffer unexpectedly (check autocommands)"), 0);
}
return retval;
}
@@ -446,12 +439,11 @@ int buf_write_all(buf_T *buf, int forceit)
/// ":argdo", ":windo", ":bufdo", ":tabdo", ":cdo", ":ldo", ":cfdo" and ":lfdo"
void ex_listdo(exarg_T *eap)
{
- int i;
- win_T *wp;
- tabpage_T *tp;
- int next_fnum = 0;
char *save_ei = NULL;
- char *p_shm_save;
+
+ // Temporarily override SHM_OVER and SHM_OVERALL to avoid that file
+ // message overwrites output from the command.
+ msg_listdo_overwrite++;
if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) {
// Don't do syntax HL autocommands. Skipping the syntax file is a
@@ -469,10 +461,11 @@ void ex_listdo(exarg_T *eap)
|| !check_changed(curbuf, CCGD_AW
| (eap->forceit ? CCGD_FORCEIT : 0)
| CCGD_EXCMD)) {
- i = 0;
+ int next_fnum = 0;
+ int i = 0;
// start at the eap->line1 argument/window/buffer
- wp = firstwin;
- tp = first_tabpage;
+ win_T *wp = firstwin;
+ tabpage_T *tp = first_tabpage;
switch (eap->cmdidx) {
case CMD_windo:
for (; wp != NULL && i + 1 < eap->line1; wp = wp->w_next) {
@@ -542,11 +535,7 @@ void ex_listdo(exarg_T *eap)
if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) {
// Clear 'shm' to avoid that the file message overwrites
// any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
do_argfile(eap, i);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
}
if (curwin->w_arg_idx != i) {
break;
@@ -609,13 +598,8 @@ void ex_listdo(exarg_T *eap)
break;
}
- // Go to the next buffer. Clear 'shm' to avoid that the file
- // message overwrites any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
+ // Go to the next buffer.
goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
// If autocommands took us elsewhere, quit here.
if (curbuf->b_fnum != next_fnum) {
@@ -632,13 +616,7 @@ void ex_listdo(exarg_T *eap)
size_t qf_idx = qf_get_cur_idx(eap);
- // Clear 'shm' to avoid that the file message overwrites
- // any output from the command.
- p_shm_save = xstrdup(p_shm);
- set_option_value_give_err("shm", 0L, "", 0);
ex_cnext(eap);
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
- xfree(p_shm_save);
// If jumping to the next quickfix entry fails, quit here.
if (qf_get_cur_idx(eap) == qf_idx) {
@@ -665,6 +643,7 @@ void ex_listdo(exarg_T *eap)
listcmd_busy = false;
}
+ msg_listdo_overwrite--;
if (save_ei != NULL) {
buf_T *bnext;
aco_save_T aco;
@@ -697,9 +676,7 @@ void ex_listdo(exarg_T *eap)
/// ":compiler[!] {name}"
void ex_compiler(exarg_T *eap)
{
- char *buf;
char *old_cur_comp = NULL;
- char *p;
if (*eap->arg == NUL) {
// List all compiler scripts.
@@ -709,7 +686,7 @@ void ex_compiler(exarg_T *eap)
}
size_t bufsize = strlen(eap->arg) + 14;
- buf = xmalloc(bufsize);
+ char *buf = xmalloc(bufsize);
if (eap->forceit) {
// ":compiler! {name}" sets global options
@@ -730,20 +707,16 @@ void ex_compiler(exarg_T *eap)
do_unlet(S_LEN("g:current_compiler"), true);
do_unlet(S_LEN("b:current_compiler"), true);
- snprintf(buf, bufsize, "compiler/%s.vim", eap->arg);
- if (source_runtime(buf, DIP_ALL) == FAIL) {
- // Try lua compiler
- snprintf(buf, bufsize, "compiler/%s.lua", eap->arg);
- if (source_runtime(buf, DIP_ALL) == FAIL) {
- semsg(_("E666: compiler not supported: %s"), eap->arg);
- }
+ snprintf(buf, bufsize, "compiler/%s.*", eap->arg);
+ if (source_runtime_vim_lua(buf, DIP_ALL) == FAIL) {
+ semsg(_(e_compiler_not_supported_str), eap->arg);
}
xfree(buf);
do_cmdline_cmd(":delcommand CompilerSet");
// Set "b:current_compiler" from "current_compiler".
- p = get_var_value("g:current_compiler");
+ char *p = get_var_value("g:current_compiler");
if (p != NULL) {
set_internal_string_var("b:current_compiler", p);
}
@@ -762,14 +735,13 @@ void ex_compiler(exarg_T *eap)
/// ":checktime [buffer]"
void ex_checktime(exarg_T *eap)
{
- buf_T *buf;
int save_no_check_timestamps = no_check_timestamps;
no_check_timestamps = 0;
if (eap->addr_count == 0) { // default is all buffers
check_timestamps(false);
} else {
- buf = buflist_findnr((int)eap->line2);
+ buf_T *buf = buflist_findnr((int)eap->line2);
if (buf != NULL) { // cannot happen?
(void)buf_check_timestamp(buf);
}
@@ -816,7 +788,7 @@ static void script_host_do_range(char *name, exarg_T *eap)
list_T *args = tv_list_alloc(3);
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- tv_list_append_string(args, (const char *)eap->arg, -1);
+ tv_list_append_string(args, eap->arg, -1);
(void)eval_call_provider(name, "do_range", args, true);
}
}
@@ -827,7 +799,6 @@ static void script_host_do_range(char *name, exarg_T *eap)
void ex_drop(exarg_T *eap)
{
bool split = false;
- buf_T *buf;
// Check if the first argument is already being edited in a window. If
// so, jump to that window.
@@ -855,7 +826,7 @@ void ex_drop(exarg_T *eap)
// ":drop file ...": Edit the first argument. Jump to an existing
// window if possible, edit in current window if the current buffer
// can be abandoned, otherwise open a new window.
- buf = buflist_findnr(ARGLIST[0].ae_fnum);
+ buf_T *buf = buflist_findnr(ARGLIST[0].ae_fnum);
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
diff --git a/src/nvim/ex_cmds2.h b/src/nvim/ex_cmds2.h
index 3a41e105f3..4f41f2cc41 100644
--- a/src/nvim/ex_cmds2.h
+++ b/src/nvim/ex_cmds2.h
@@ -1,18 +1,17 @@
-#ifndef NVIM_EX_CMDS2_H
-#define NVIM_EX_CMDS2_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
-//
-// flags for check_changed()
-//
-#define CCGD_AW 1 // do autowrite if buffer was changed
-#define CCGD_MULTWIN 2 // check also when several wins for the buf
-#define CCGD_FORCEIT 4 // ! used
-#define CCGD_ALLBUF 8 // may write all buffers
-#define CCGD_EXCMD 16 // may suggest using !
+/// flags for check_changed()
+enum {
+ CCGD_AW = 1, ///< do autowrite if buffer was changed
+ CCGD_MULTWIN = 2, ///< check also when several wins for the buf
+ CCGD_FORCEIT = 4, ///< ! used
+ CCGD_ALLBUF = 8, ///< may write all buffers
+ CCGD_EXCMD = 16, ///< may suggest using !
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds2.h.generated.h"
#endif
-#endif // NVIM_EX_CMDS2_H
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 629aaf14cf..00363884ec 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_EX_CMDS_DEFS_H
-#define NVIM_EX_CMDS_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
-#include "nvim/normal.h"
-#include "nvim/pos.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_eval_defs.h"
+#include "nvim/normal_defs.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -69,20 +69,20 @@
#define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file
#define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed
-// values for cmd_addr_type
+/// values for cmd_addr_type
typedef enum {
- ADDR_LINES, // buffer line numbers
- ADDR_WINDOWS, // window number
- ADDR_ARGUMENTS, // argument number
- ADDR_LOADED_BUFFERS, // buffer number of loaded buffer
- ADDR_BUFFERS, // buffer number
- ADDR_TABS, // tab page number
- ADDR_TABS_RELATIVE, // Tab page that only relative
- ADDR_QUICKFIX_VALID, // quickfix list valid entry number
- ADDR_QUICKFIX, // quickfix list entry number
- ADDR_UNSIGNED, // positive count or zero, defaults to 1
- ADDR_OTHER, // something else, use line number for '$', '%', etc.
- ADDR_NONE, // no range used
+ ADDR_LINES, ///< buffer line numbers
+ ADDR_WINDOWS, ///< window number
+ ADDR_ARGUMENTS, ///< argument number
+ ADDR_LOADED_BUFFERS, ///< buffer number of loaded buffer
+ ADDR_BUFFERS, ///< buffer number
+ ADDR_TABS, ///< tab page number
+ ADDR_TABS_RELATIVE, ///< Tab page that only relative
+ ADDR_QUICKFIX_VALID, ///< quickfix list valid entry number
+ ADDR_QUICKFIX, ///< quickfix list entry number
+ ADDR_UNSIGNED, ///< positive count or zero, defaults to 1
+ ADDR_OTHER, ///< something else, use line number for '$', '%', etc.
+ ADDR_NONE, ///< no range used
} cmd_addr_T;
typedef struct exarg exarg_T;
@@ -93,7 +93,7 @@ typedef struct exarg exarg_T;
#define BAD_DROP (-2) // erase it
typedef void (*ex_func_T)(exarg_T *eap);
-typedef int (*ex_preview_func_T)(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr);
+typedef int (*ex_preview_func_T)(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr);
// NOTE: These possible could be removed and changed so that
// Callback could take a "command" style string, and simply
@@ -128,54 +128,13 @@ typedef char *(*LineGetter)(int, void *, int, bool);
/// Structure for command definition.
typedef struct cmdname {
- char *cmd_name; ///< Name of the command.
- ex_func_T cmd_func; ///< Function with implementation of this command.
- ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
- uint32_t cmd_argt; ///< Relevant flags from the declared above.
- cmd_addr_T cmd_addr_type; ///< Flag for address type.
+ char *cmd_name; ///< Name of the command.
+ ex_func_T cmd_func; ///< Function with implementation of this command.
+ ex_preview_func_T cmd_preview_func; ///< Preview callback function of this command.
+ uint32_t cmd_argt; ///< Relevant flags from the declared above.
+ cmd_addr_T cmd_addr_type; ///< Flag for address type.
} CommandDefinition;
-// A list used for saving values of "emsg_silent". Used by ex_try() to save the
-// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
-// flag below is set.
-typedef struct eslist_elem eslist_T;
-struct eslist_elem {
- int saved_emsg_silent; // saved value of "emsg_silent"
- eslist_T *next; // next element on the list
-};
-
-// For conditional commands a stack is kept of nested conditionals.
-// When cs_idx < 0, there is no conditional command.
-enum {
- CSTACK_LEN = 50,
-};
-
-typedef struct {
- int cs_flags[CSTACK_LEN]; // CSF_ flags
- char cs_pending[CSTACK_LEN]; // CSTP_: what's pending in ":finally"
- union {
- void *csp_rv[CSTACK_LEN]; // return typeval for pending return
- void *csp_ex[CSTACK_LEN]; // exception for pending throw
- } cs_pend;
- void *cs_forinfo[CSTACK_LEN]; // info used by ":for"
- int cs_line[CSTACK_LEN]; // line nr of ":while"/":for" line
- int cs_idx; // current entry, or -1 if none
- int cs_looplevel; // nr of nested ":while"s and ":for"s
- int cs_trylevel; // nr of nested ":try"s
- eslist_T *cs_emsg_silent_list; // saved values of "emsg_silent"
- int cs_lflags; // loop flags: CSL_ flags
-} cstack_T;
-#define cs_rettv cs_pend.csp_rv
-#define cs_exception cs_pend.csp_ex
-
-// Flags for the cs_lflags item in cstack_T.
-enum {
- CSL_HAD_LOOP = 1, // just found ":while" or ":for"
- CSL_HAD_ENDLOOP = 2, // just found ":endwhile" or ":endfor"
- CSL_HAD_CONT = 4, // just found ":continue"
- CSL_HAD_FINA = 8, // just found ":finally"
-};
-
/// Arguments used for Ex commands.
struct exarg {
char *arg; ///< argument of the command
@@ -185,6 +144,7 @@ struct exarg {
char *nextcmd; ///< next command (NULL if none)
char *cmd; ///< the name of the command (except for :make)
char **cmdlinep; ///< pointer to pointer of allocated cmdline
+ char *cmdline_tofree; ///< free later
cmdidx_T cmdidx; ///< the index for the command
uint32_t argt; ///< flags for the command
int skip; ///< don't execute the command, only parse it
@@ -221,32 +181,6 @@ struct exarg {
#define EXFLAG_NR 0x02 // '#': number
#define EXFLAG_PRINT 0x04 // 'p': print
-// used for completion on the command line
-struct expand {
- char *xp_pattern; // start of item to expand
- int xp_context; // type of expansion
- size_t xp_pattern_len; // bytes in xp_pattern before cursor
- char *xp_arg; // completion function
- LuaRef xp_luaref; // Ref to Lua completion function
- sctx_T xp_script_ctx; // SCTX for completion function
- int xp_backslash; // one of the XP_BS_ values
-#ifndef BACKSLASH_IN_FILENAME
- int xp_shell; // true for a shell command, more
- // characters need to be escaped
-#endif
- int xp_numfiles; // number of files found by file name completion
- int xp_col; // cursor position in line
- char **xp_files; // list of files
- char *xp_line; // text being completed
-#define EXPAND_BUF_LEN 256
- char xp_buf[EXPAND_BUF_LEN]; // buffer for returned match
-};
-
-// values for xp_backslash
-#define XP_BS_NONE 0 // nothing special for backslashes
-#define XP_BS_ONE 1 // uses one backslash before a space
-#define XP_BS_THREE 2 // uses three backslashes before a space
-
enum {
CMOD_SANDBOX = 0x0001, ///< ":sandbox"
CMOD_SILENT = 0x0002, ///< ":silent"
@@ -281,7 +215,7 @@ typedef struct {
// values for undo_cmdmod()
char *cmod_save_ei; ///< saved value of 'eventignore'
int cmod_did_sandbox; ///< set when "sandbox" was incremented
- long cmod_verbose_save; ///< if 'verbose' was set: value of p_verbose plus one
+ OptInt cmod_verbose_save; ///< if 'verbose' was set: value of p_verbose plus one
int cmod_save_msg_silent; ///< if non-zero: saved value of msg_silent + 1
int cmod_save_msg_scroll; ///< for restoring msg_scroll
int cmod_did_esilent; ///< incremented when emsg_silent is
@@ -295,5 +229,3 @@ typedef struct {
bool bar;
} magic;
} CmdParseInfo;
-
-#endif // NVIM_EX_CMDS_DEFS_H
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index a24e8458a6..0b466bbe4e 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_docmd.c: functions for executing an Ex command line.
#include <assert.h>
@@ -12,15 +9,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/debugger.h"
#include "nvim/digraph.h"
@@ -28,7 +28,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/ex_cmds.h"
@@ -40,19 +39,20 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
@@ -61,40 +61,52 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
-static char e_ambiguous_use_of_user_defined_command[]
+static const char e_ambiguous_use_of_user_defined_command[]
= N_("E464: Ambiguous use of user-defined command");
-static char e_not_an_editor_command[]
+static const char e_no_call_stack_to_substitute_for_stack[]
+ = N_("E489: No call stack to substitute for \"<stack>\"");
+static const char e_not_an_editor_command[]
= N_("E492: Not an editor command");
-static char e_no_source_file_name_to_substitute_for_sfile[]
- = N_("E498: no :source file name to substitute for \"<sfile>\"");
-static char e_no_call_stack_to_substitute_for_stack[]
- = N_("E489: no call stack to substitute for \"<stack>\"");
-static char e_no_script_file_name_to_substitute_for_script[]
+static const char e_no_autocommand_file_name_to_substitute_for_afile[]
+ = N_("E495: No autocommand file name to substitute for \"<afile>\"");
+static const char e_no_autocommand_buffer_number_to_substitute_for_abuf[]
+ = N_("E496: No autocommand buffer number to substitute for \"<abuf>\"");
+static const char e_no_autocommand_match_name_to_substitute_for_amatch[]
+ = N_("E497: No autocommand match name to substitute for \"<amatch>\"");
+static const char e_no_source_file_name_to_substitute_for_sfile[]
+ = N_("E498: No :source file name to substitute for \"<sfile>\"");
+static const char e_no_line_number_to_use_for_slnum[]
+ = N_("E842: No line number to use for \"<slnum>\"");
+static const char e_no_line_number_to_use_for_sflnum[]
+ = N_("E961: No line number to use for \"<sflnum>\"");
+static const char e_no_script_file_name_to_substitute_for_script[]
= N_("E1274: No script file name to substitute for \"<script>\"");
static int quitmore = 0;
@@ -139,10 +151,6 @@ struct dbg_stuff {
# include "ex_docmd.c.generated.h"
#endif
-#ifndef HAVE_WORKING_LIBINTL
-# define ex_language ex_ni
-#endif
-
// Declare cmdnames[].
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_cmds_defs.generated.h"
@@ -152,19 +160,28 @@ static char dollar_command[2] = { '$', 0 };
static void save_dbg_stuff(struct dbg_stuff *dsp)
{
- dsp->trylevel = trylevel; trylevel = 0;
- dsp->force_abort = force_abort; force_abort = false;
- dsp->caught_stack = caught_stack; caught_stack = NULL;
- dsp->vv_exception = v_exception(NULL);
- dsp->vv_throwpoint = v_throwpoint(NULL);
+ dsp->trylevel = trylevel;
+ trylevel = 0;
+ dsp->force_abort = force_abort;
+ force_abort = false;
+ dsp->caught_stack = caught_stack;
+ caught_stack = NULL;
+ dsp->vv_exception = v_exception(NULL);
+ dsp->vv_throwpoint = v_throwpoint(NULL);
// Necessary for debugging an inactive ":catch", ":finally", ":endtry".
- dsp->did_emsg = did_emsg; did_emsg = false;
- dsp->got_int = got_int; got_int = false;
- dsp->did_throw = did_throw; did_throw = false;
- dsp->need_rethrow = need_rethrow; need_rethrow = false;
- dsp->check_cstack = check_cstack; check_cstack = false;
- dsp->current_exception = current_exception; current_exception = NULL;
+ dsp->did_emsg = did_emsg;
+ did_emsg = false;
+ dsp->got_int = got_int;
+ got_int = false;
+ dsp->did_throw = did_throw;
+ did_throw = false;
+ dsp->need_rethrow = need_rethrow;
+ need_rethrow = false;
+ dsp->check_cstack = check_cstack;
+ check_cstack = false;
+ dsp->current_exception = current_exception;
+ current_exception = NULL;
}
static void restore_dbg_stuff(struct dbg_stuff *dsp)
@@ -200,7 +217,7 @@ void do_exmode(void)
RedrawingDisabled++; // don't redisplay the window
no_wait_return++; // don't wait for return
- msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."));
+ msg(_("Entering Ex mode. Type \"visual\" to go to Normal mode."), 0);
while (exmode_active) {
// Check for a ":normal" command and no more characters left.
if (ex_normal_busy > 0 && typebuf.tb_len == 0) {
@@ -221,7 +238,7 @@ void do_exmode(void)
if ((prev_line != curwin->w_cursor.lnum
|| changedtick != buf_get_changedtick(curbuf)) && !ex_no_reprint) {
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
if (ex_pressedreturn) {
// Make sure the message overwrites the right line and isn't throttled.
@@ -239,7 +256,7 @@ void do_exmode(void)
}
} else if (ex_pressedreturn && !ex_no_reprint) { // must be at EOF
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
emsg(_("E501: At end-of-file"));
}
@@ -264,9 +281,9 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd)
verbose_enter_scroll();
if (lnum == 0) {
- smsg(_("Executing: %s"), cmd);
+ smsg(0, _("Executing: %s"), cmd);
} else {
- smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd);
+ smsg(0, _("line %" PRIdLINENR ": %s"), lnum, cmd);
}
if (msg_silent == 0) {
msg_puts("\n"); // don't overwrite this
@@ -279,7 +296,7 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd)
/// Execute a simple command line. Used for translated commands like "*".
int do_cmdline_cmd(const char *cmd)
{
- return do_cmdline((char *)cmd, NULL, NULL, DOCMD_NOWAIT|DOCMD_KEYTYPED);
+ return do_cmdline((char *)cmd, NULL, NULL, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED);
}
/// do_cmdline(): execute one Ex command line
@@ -320,16 +337,12 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
linenr_T *breakpoint = NULL; // ptr to breakpoint field in cookie
int *dbg_tick = NULL; // ptr to dbg_tick field in cookie
struct dbg_stuff debug_saved; // saved things for debug mode
- int initial_trylevel;
- msglist_T **saved_msg_list = NULL;
msglist_T *private_msg_list;
// "fgetline" and "cookie" passed to do_one_cmd()
char *(*cmd_getline)(int, void *, int, bool);
void *cmd_cookie;
struct loop_cookie cmd_loop_cookie;
- void *real_cookie;
- int getline_is_func;
static int call_depth = 0; // recursiveness
// For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory
@@ -338,7 +351,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// combine the messages stored by an earlier invocation of do_one_cmd()
// with the command name of the later one. This would happen when
// BufWritePost autocommands are executed after a write error.
- saved_msg_list = msg_list;
+ msglist_T **saved_msg_list = msg_list;
msg_list = &private_msg_list;
private_msg_list = NULL;
@@ -358,10 +371,10 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
ga_init(&lines_ga, (int)sizeof(wcmd_T), 10);
- real_cookie = getline_cookie(fgetline, cookie);
+ void *real_cookie = getline_cookie(fgetline, cookie);
// Inside a function use a higher nesting level.
- getline_is_func = getline_equal(fgetline, cookie, get_func_line);
+ bool getline_is_func = getline_equal(fgetline, cookie, get_func_line);
if (getline_is_func && ex_nesting_level == func_level(real_cookie)) {
ex_nesting_level++;
}
@@ -393,7 +406,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
CLEAR_FIELD(debug_saved);
}
- initial_trylevel = trylevel;
+ int initial_trylevel = trylevel;
// "did_throw" will be set to true when an exception is being thrown.
did_throw = false;
@@ -485,24 +498,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
}
- if (cstack.cs_looplevel > 0) {
- // Inside a while/for loop we need to store the lines and use them
- // again. Pass a different "fgetline" function to do_one_cmd()
- // below, so that it stores lines in or reads them from
- // "lines_ga". Makes it possible to define a function inside a
- // while/for loop.
- cmd_getline = get_loop_line;
- cmd_cookie = (void *)&cmd_loop_cookie;
- cmd_loop_cookie.lines_gap = &lines_ga;
- cmd_loop_cookie.current_line = current_line;
- cmd_loop_cookie.getline = fgetline;
- cmd_loop_cookie.cookie = cookie;
- cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len);
- } else {
- cmd_getline = fgetline;
- cmd_cookie = cookie;
- }
-
// 2. If no line given, get an allocated line with fgetline().
if (next_cmdline == NULL) {
// Need to set msg_didout for the first line after an ":if",
@@ -541,15 +536,37 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
}
cmdline_copy = next_cmdline;
- // Save the current line when inside a ":while" or ":for", and when
- // the command looks like a ":while" or ":for", because we may need it
- // later. When there is a '|' and another command, it is stored
- // separately, because we need to be able to jump back to it from an
+ int current_line_before = 0;
+ // Inside a while/for loop, and when the command looks like a ":while"
+ // or ":for", the line is stored, because we may need it later when
+ // looping.
+ //
+ // When there is a '|' and another command, it is stored separately,
+ // because we need to be able to jump back to it from an
// :endwhile/:endfor.
- if (current_line == lines_ga.ga_len
- && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) {
- store_loop_line(&lines_ga, next_cmdline);
+ //
+ // Pass a different "fgetline" function to do_one_cmd() below,
+ // that it stores lines in or reads them from "lines_ga". Makes it
+ // possible to define a function inside a while/for loop.
+ if ((cstack.cs_looplevel > 0 || has_loop_cmd(next_cmdline))) {
+ cmd_getline = get_loop_line;
+ cmd_cookie = (void *)&cmd_loop_cookie;
+ cmd_loop_cookie.lines_gap = &lines_ga;
+ cmd_loop_cookie.current_line = current_line;
+ cmd_loop_cookie.getline = fgetline;
+ cmd_loop_cookie.cookie = cookie;
+ cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len);
+
+ // Save the current line when encountering it the first time.
+ if (current_line == lines_ga.ga_len) {
+ store_loop_line(&lines_ga, next_cmdline);
+ }
+ current_line_before = current_line;
+ } else {
+ cmd_getline = fgetline;
+ cmd_cookie = cookie;
}
+
did_endif = false;
if (count++ == 0) {
@@ -652,7 +669,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
} else if (cstack.cs_lflags & CSL_HAD_LOOP) {
// For a ":while" or ":for" we need to remember the line number.
cstack.cs_lflags &= ~CSL_HAD_LOOP;
- cstack.cs_line[cstack.cs_idx] = current_line - 1;
+ cstack.cs_line[cstack.cs_idx] = current_line_before;
}
}
@@ -819,8 +836,8 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
// Cleanup if "cs_emsg_silent_list" remains.
if (cstack.cs_emsg_silent_list != NULL) {
- eslist_T *elem, *temp;
- for (elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) {
+ eslist_T *temp;
+ for (eslist_T *elem = cstack.cs_emsg_silent_list; elem != NULL; elem = temp) {
temp = elem->next;
xfree(elem);
}
@@ -893,7 +910,7 @@ void handle_did_throw(void)
if (messages != NULL) {
do {
msglist_T *next = messages->next;
- emsg(messages->msg);
+ emsg_multiline(messages->msg, messages->multiline);
xfree(messages->msg);
xfree(messages->sfile);
xfree(messages);
@@ -919,7 +936,7 @@ static char *get_loop_line(int c, void *cookie, int indent, bool do_concat)
char *line;
// First time inside the ":while"/":for": get line normally.
if (cp->getline == NULL) {
- line = getcmdline(c, 0L, indent, do_concat);
+ line = getcmdline(c, 0, indent, do_concat);
} else {
line = cp->getline(c, cp->cookie, indent, do_concat);
}
@@ -986,10 +1003,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie)
/// ":bwipeout", etc.
///
/// @return the buffer number.
-static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, long offset)
+static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int offset)
{
buf_T *nextbuf;
- long count = offset;
+ int count = offset;
buf_T *buf = firstbuf;
while (buf->b_next != NULL && buf->b_fnum < lnum) {
@@ -1107,7 +1124,7 @@ static void get_wincmd_addr_type(const char *arg, exarg_T *eap)
case 'd':
case Ctrl_D:
// window size or any count
- eap->addr_type = ADDR_OTHER; // -V1037
+ eap->addr_type = ADDR_OTHER;
break;
case Ctrl_HAT:
@@ -1311,16 +1328,16 @@ static void parse_register(exarg_T *eap)
}
// Change line1 and line2 of Ex command to use count
-void set_cmd_count(exarg_T *eap, long count, bool validate)
+void set_cmd_count(exarg_T *eap, linenr_T count, bool validate)
{
if (eap->addr_type != ADDR_LINES) { // e.g. :buffer 2, :sleep 3
- eap->line2 = (linenr_T)count;
+ eap->line2 = count;
if (eap->addr_count == 0) {
eap->addr_count = 1;
}
} else {
eap->line1 = eap->line2;
- eap->line2 += (linenr_T)count - 1;
+ eap->line2 += count - 1;
eap->addr_count++;
// Be vi compatible: no error message for out of range.
if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
@@ -1329,7 +1346,7 @@ void set_cmd_count(exarg_T *eap, long count, bool validate)
}
}
-static int parse_count(exarg_T *eap, char **errormsg, bool validate)
+static int parse_count(exarg_T *eap, const char **errormsg, bool validate)
{
// Check for a count. When accepting a EX_BUFNAME, don't use "123foo" as a
// count, it's a buffer name.
@@ -1338,7 +1355,7 @@ static int parse_count(exarg_T *eap, char **errormsg, bool validate)
if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg)
&& (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL
|| ascii_iswhite(*p))) {
- long n = getdigits_long(&eap->arg, false, -1);
+ linenr_T n = getdigits_int32(&eap->arg, false, -1);
eap->arg = skipwhite(eap->arg);
if (eap->args != NULL) {
@@ -1383,7 +1400,7 @@ bool is_cmd_ni(cmdidx_T cmdidx)
/// @param[out] errormsg Error message, if any
///
/// @return Success or failure
-bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **errormsg)
+bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, const char **errormsg)
{
char *after_modifier = NULL;
bool retval = false;
@@ -1441,7 +1458,7 @@ bool parse_cmdline(char *cmdline, exarg_T *eap, CmdParseInfo *cmdinfo, char **er
}
// Fail if command is invalid
if (eap->cmdidx == CMD_SIZE) {
- STRCPY(IObuff, _(e_not_an_editor_command));
+ xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// If the modifier was parsed OK the error must be in the following command
char *cmdname = after_modifier ? after_modifier : cmdline;
append_command(cmdname);
@@ -1551,7 +1568,7 @@ static void shift_cmd_args(exarg_T *eap)
xfree(oldarglens);
}
-static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview)
+static int execute_cmd0(int *retv, exarg_T *eap, const char **errormsg, bool preview)
{
// If filename expansion is enabled, expand filenames
if (eap->argt & EX_XFILE) {
@@ -1636,7 +1653,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, char **errormsg, bool preview)
/// @param preview Execute command preview callback instead of actual command
int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
{
- char *errormsg = NULL;
+ const char *errormsg = NULL;
int retv = 0;
#undef ERROR
@@ -1677,7 +1694,7 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
&& !(eap->cmdidx == CMD_file && *eap->arg == NUL)
&& !IS_USER_CMDIDX(eap->cmdidx)
&& curbuf_locked()) {
- ERROR(_(e_cannot_edit_other_buf));
+ goto end;
}
correct_range(eap);
@@ -1849,7 +1866,8 @@ static bool skip_cmd(const exarg_T *eap)
/// Execute one Ex command.
///
-/// If 'sourcing' is true, the command will be included in the error message.
+/// If "flags" has DOCMD_VERBOSE, the command will be included in the error
+/// message.
///
/// 1. skip comment lines and leading space
/// 2. handle command modifiers
@@ -1867,7 +1885,7 @@ static bool skip_cmd(const exarg_T *eap)
static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter fgetline,
void *cookie)
{
- char *errormsg = NULL; // error message
+ const char *errormsg = NULL; // error message
const int save_reg_executing = reg_executing;
const bool save_pending_end_reg_executing = pending_end_reg_executing;
@@ -1882,8 +1900,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// avoid that a function call in 'statusline' does this
&& !getline_equal(fgetline, cookie, get_func_line)
// avoid that an autocommand, e.g. QuitPre, does this
- && !getline_equal(fgetline, cookie,
- getnextac)) {
+ && !getline_equal(fgetline, cookie, getnextac)) {
quitmore--;
}
@@ -2013,7 +2030,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
while (ASCII_ISALNUM(*p)) {
p++;
}
- p = xstrnsave(ea.cmd, (size_t)(p - ea.cmd));
+ p = xmemdupz(ea.cmd, (size_t)(p - ea.cmd));
int ret = apply_autocmds(EVENT_CMDUNDEFINED, p, p, true, NULL);
xfree(p);
// If the autocommands did something and didn't cause an error, try
@@ -2031,7 +2048,7 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
// Check for wrong commands.
if (ea.cmdidx == CMD_SIZE) {
if (!ea.skip) {
- STRCPY(IObuff, _(e_not_an_editor_command));
+ xstrlcpy(IObuff, _(e_not_an_editor_command), IOSIZE);
// If the modifier was parsed OK the error must be in the following
// command
char *cmdname = after_modifier ? after_modifier : *cmdlinep;
@@ -2308,7 +2325,7 @@ doend:
if (errormsg != NULL && *errormsg != NUL && !did_emsg) {
if (flags & DOCMD_VERBOSE) {
if (errormsg != IObuff) {
- STRCPY(IObuff, errormsg);
+ xstrlcpy(IObuff, errormsg, IOSIZE);
errormsg = IObuff;
}
append_command(*ea.cmdlinep);
@@ -2329,6 +2346,7 @@ doend:
}
ex_nesting_level--;
+ xfree(ea.cmdline_tofree);
return ea.nextcmd;
}
@@ -2362,12 +2380,12 @@ char *ex_errmsg(const char *const msg, const char *const arg)
/// - set 'eventignore' to "all" for ":noautocmd"
///
/// @return FAIL when the command is not to be executed.
-int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool skip_only)
+int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod, bool skip_only)
{
CLEAR_POINTER(cmod);
// Repeat until no more command modifiers are found.
- for (;;) {
+ while (true) {
while (*eap->cmd == ' '
|| *eap->cmd == '\t'
|| *eap->cmd == ':') {
@@ -2397,7 +2415,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool
char *p = skip_range(eap->cmd, NULL);
switch (*p) {
- // When adding an entry, also modify cmd_exists().
+ // When adding an entry, also modify cmdmods[]
case 'a':
if (!checkforcmd(&eap->cmd, "aboveleft", 3)) {
break;
@@ -2543,7 +2561,10 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, cmdmod_T *cmod, bool
if (checkforcmd(&p, "tab", 3)) {
if (!skip_only) {
int tabnr = (int)get_address(eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only,
- false, 1);
+ false, 1, errormsg);
+ if (eap->cmd == NULL) {
+ return false;
+ }
if (tabnr == MAXLNUM) {
cmod->cmod_tab = tabpage_index(curtab) + 1;
@@ -2687,7 +2708,7 @@ void undo_cmdmod(cmdmod_T *cmod)
/// May set the last search pattern, unless "silent" is true.
///
/// @return FAIL and set "errormsg" or return OK.
-int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
+int parse_cmd_address(exarg_T *eap, const char **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
@@ -2696,12 +2717,12 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
int ret = FAIL;
// Repeat for all ',' or ';' separated addresses.
- for (;;) {
+ while (true) {
eap->line1 = eap->line2;
eap->line2 = get_cmd_default_range(eap);
eap->cmd = skipwhite(eap->cmd);
lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
- eap->addr_count == 0, address_count++);
+ eap->addr_count == 0, address_count++, errormsg);
if (eap->cmd == NULL) { // error detected
goto theend;
}
@@ -2737,7 +2758,7 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
if (IS_USER_CMDIDX(eap->cmdidx)) {
eap->line1 = 1;
eap->line2 = eap->addr_type == ADDR_WINDOWS
- ? LAST_WIN_NR : LAST_TAB_NR;
+ ? LAST_WIN_NR : LAST_TAB_NR;
} else {
// there is no Vim command which uses '%' and
// ADDR_WINDOWS or ADDR_TABS
@@ -2780,13 +2801,13 @@ int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent)
eap->cmd++;
if (!eap->skip) {
fmark_T *fm = mark_get_visual(curbuf, '<');
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
eap->line1 = fm->mark.lnum;
fm = mark_get_visual(curbuf, '>');
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
goto theend;
}
assert(fm != NULL);
@@ -2862,10 +2883,10 @@ bool checkforcmd(char **pp, const char *cmd, int len)
/// Append "cmd" to the error message in IObuff.
/// Takes care of limiting the length and handling 0xa0, which would be
/// invisible otherwise.
-static void append_command(char *cmd)
+static void append_command(const char *cmd)
{
size_t len = strlen(IObuff);
- char *s = cmd;
+ const char *s = cmd;
char *d;
if (len > IOSIZE - 100) {
@@ -2874,7 +2895,7 @@ static void append_command(char *cmd)
d -= utf_head_off(IObuff, d);
STRCPY(d, "...");
}
- STRCAT(IObuff, ": ");
+ xstrlcat(IObuff, ": ", IOSIZE);
d = IObuff + strlen(IObuff);
while (*s != NUL && d - IObuff + 5 < IOSIZE) {
if ((uint8_t)s[0] == 0xc2 && (uint8_t)s[1] == 0xa0) {
@@ -2884,7 +2905,7 @@ static void append_command(char *cmd)
} else if (d - IObuff + utfc_ptr2len(s) + 1 >= IOSIZE) {
break;
} else {
- mb_copy_char((const char **)&s, &d);
+ mb_copy_char(&s, &d);
}
}
*d = NUL;
@@ -2988,6 +3009,11 @@ char *find_ex_command(exarg_T *eap, int *full)
}
assert(eap->cmdidx >= 0);
+ if (len == 3 && strncmp("def", eap->cmd, 3) == 0) {
+ // Make :def an unknown command to avoid confusing behavior. #23149
+ eap->cmdidx = CMD_SIZE;
+ }
+
for (; (int)eap->cmdidx < CMD_SIZE;
eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) {
if (strncmp(cmdnames[(int)eap->cmdidx].cmd_name, eap->cmd,
@@ -3029,6 +3055,7 @@ static struct cmdmod {
{ "confirm", 4, false },
{ "filter", 4, false },
{ "hide", 3, false },
+ { "horizontal", 3, false },
{ "keepalt", 5, false },
{ "keepjumps", 5, false },
{ "keepmarks", 3, false },
@@ -3094,7 +3121,7 @@ int cmd_exists(const char *const name)
// For ":2match" and ":3match" we need to skip the number.
exarg_T ea;
ea.cmd = (char *)((*name == '2' || *name == '3') ? name + 1 : name);
- ea.cmdidx = (cmdidx_T)0;
+ ea.cmdidx = 0;
ea.flags = 0;
int full = false;
char *p = find_ex_command(&ea, &full);
@@ -3113,13 +3140,10 @@ int cmd_exists(const char *const name)
/// "fullcommand" function
void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- char *name = argvars[0].vval.v_string;
+ char *name = (char *)tv_get_string(&argvars[0]);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- if (name == NULL) {
- return;
- }
while (*name == ':') {
name++;
@@ -3128,7 +3152,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
exarg_T ea;
ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name;
- ea.cmdidx = (cmdidx_T)0;
+ ea.cmdidx = 0;
ea.flags = 0;
char *p = find_ex_command(&ea, NULL);
if (p == NULL || ea.cmdidx == CMD_SIZE) {
@@ -3142,10 +3166,15 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
cmdidx_T excmd_get_cmdidx(const char *cmd, size_t len)
{
+ if (len == 3 && strncmp("def", cmd, 3) == 0) {
+ // Make :def an unknown command to avoid confusing behavior. #23149
+ return CMD_SIZE;
+ }
+
cmdidx_T idx;
if (!one_letter_cmd(cmd, &idx)) {
- for (idx = (cmdidx_T)0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) {
+ for (idx = 0; (int)idx < CMD_SIZE; idx = (cmdidx_T)((int)idx + 1)) {
if (strncmp(cmdnames[(int)idx].cmd_name, cmd, len) == 0) {
break;
}
@@ -3200,34 +3229,35 @@ char *skip_range(const char *cmd, int *ctx)
}
// Skip ":" and white space.
- cmd = skip_colon_white((char *)cmd, false);
+ cmd = skip_colon_white(cmd, false);
return (char *)cmd;
}
-static void addr_error(cmd_addr_T addr_type)
+static const char *addr_error(cmd_addr_T addr_type)
{
if (addr_type == ADDR_NONE) {
- emsg(_(e_norange));
+ return _(e_norange);
} else {
- emsg(_(e_invrange));
+ return _(e_invrange);
}
}
-/// Get a single EX address
+/// Gets a single EX address.
///
-/// Set ptr to the next character after the part that was interpreted.
-/// Set ptr to NULL when an error is encountered.
-/// This may set the last used search pattern.
+/// Sets ptr to the next character after the part that was interpreted.
+/// Sets ptr to NULL when an error is encountered (stored in `errormsg`).
+/// May set the last used search pattern.
///
/// @param skip only skip the address, don't use it
/// @param silent no errors or side effects
/// @param to_other_file flag: may jump to other file
/// @param address_count 1 for first, >1 after comma
+/// @param errormsg Error message, if any
///
/// @return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int skip, bool silent,
- int to_other_file, int address_count)
+ int to_other_file, int address_count, const char **errormsg)
FUNC_ATTR_NONNULL_ALL
{
int c;
@@ -3263,7 +3293,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
@@ -3308,7 +3338,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case ADDR_NONE:
case ADDR_TABS_RELATIVE:
case ADDR_UNSIGNED:
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
break;
@@ -3333,7 +3363,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
goto error;
}
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3346,10 +3376,11 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd);
cmd++;
if (fm != NULL && fm->fnum != curbuf->handle) {
+ (void)mark_move_to(fm, 0);
// Jumped to another file.
lnum = curwin->w_cursor.lnum;
} else {
- if (!mark_check(fm)) {
+ if (!mark_check(fm, errormsg)) {
cmd = NULL;
goto error;
}
@@ -3363,7 +3394,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case '?': // '/' or '?' - search
c = (uint8_t)(*cmd++);
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3397,7 +3428,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
}
searchcmdlen = 0;
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
- if (!do_search(NULL, c, c, cmd, 1L, flags, NULL)) {
+ if (!do_search(NULL, c, c, cmd, 1, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -3412,7 +3443,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
case '\\': // "\?", "\/" or "\&", repeat search
cmd++;
if (addr_type != ADDR_LINES) {
- addr_error(addr_type);
+ *errormsg = addr_error(addr_type);
cmd = NULL;
goto error;
}
@@ -3421,7 +3452,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
} else if (*cmd == '?' || *cmd == '/') {
i = RE_SEARCH;
} else {
- emsg(_(e_backslash));
+ *errormsg = _(e_backslash);
cmd = NULL;
goto error;
}
@@ -3434,7 +3465,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
pos.coladd = 0;
if (searchit(curwin, curbuf, &pos, NULL,
*cmd == '?' ? BACKWARD : FORWARD,
- "", 1L, SEARCH_MSG, i, NULL) != FAIL) {
+ "", 1, SEARCH_MSG, i, NULL) != FAIL) {
lnum = pos.lnum;
} else {
cmd = NULL;
@@ -3450,7 +3481,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
}
}
- for (;;) {
+ while (true) {
cmd = skipwhite(cmd);
if (*cmd != '-' && *cmd != '+' && !ascii_isdigit(*cmd)) {
break;
@@ -3503,13 +3534,13 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
// "number", "+number" or "-number"
n = getdigits_int32(&cmd, false, MAXLNUM);
if (n == MAXLNUM) {
- emsg(_(e_line_number_out_of_range));
+ *errormsg = _(e_line_number_out_of_range);
goto error;
}
}
if (addr_type == ADDR_TABS_RELATIVE) {
- emsg(_(e_invrange));
+ *errormsg = _(e_invrange);
cmd = NULL;
goto error;
} else if (addr_type == ADDR_LOADED_BUFFERS || addr_type == ADDR_BUFFERS) {
@@ -3524,8 +3555,8 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int
if (i == '-') {
lnum -= n;
} else {
- if (n >= INT32_MAX - lnum) {
- emsg(_(e_line_number_out_of_range));
+ if (lnum >= 0 && n >= INT32_MAX - lnum) {
+ *errormsg = _(e_line_number_out_of_range);
goto error;
}
lnum += n;
@@ -3738,7 +3769,7 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep)
/// When an error is detected, "errormsgp" is set to a non-NULL pointer.
///
/// @return FAIL for failure, OK otherwise.
-int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
+int expand_filename(exarg_T *eap, char **cmdlinep, const char **errormsgp)
{
// Skip a regexp pattern for ":vimgrep[add] pat file..."
char *p = skip_grep_pat(eap);
@@ -3751,7 +3782,7 @@ int expand_filename(exarg_T *eap, char **cmdlinep, char **errormsgp)
// Skip over `=expr`, wildcards in it are not expanded.
if (p[0] == '`' && p[1] == '=') {
p += 2;
- (void)skip_expr(&p);
+ (void)skip_expr(&p, NULL);
if (*p == '`') {
p++;
}
@@ -3970,7 +4001,7 @@ void separate_nextcmd(exarg_T *eap)
} else if (p[0] == '`' && p[1] == '=' && (eap->argt & EX_XFILE)) {
// Skip over `=expr` when wildcards are expanded.
p += 2;
- (void)skip_expr(&p);
+ (void)skip_expr(&p, NULL);
if (*p == NUL) { // stop at NUL after CTRL-V
break;
}
@@ -4011,7 +4042,7 @@ static char *getargcmd(char **argp)
if (*arg == '+') { // +[command]
arg++;
if (ascii_isspace(*arg) || *arg == '\0') {
- command = (char *)dollar_command;
+ command = dollar_command;
} else {
command = arg;
arg = skip_cmd_arg(command, true);
@@ -4059,6 +4090,22 @@ int get_bad_opt(const char *p, exarg_T *eap)
return OK;
}
+/// Function given to ExpandGeneric() to obtain the list of bad= names.
+static char *get_bad_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ // Note: Keep this in sync with get_bad_opt().
+ static char *(p_bad_values[]) = {
+ "?",
+ "keep",
+ "drop",
+ };
+
+ if (idx < (int)ARRAY_SIZE(p_bad_values)) {
+ return p_bad_values[idx];
+ }
+ return NULL;
+}
+
/// Get "++opt=arg" argument.
///
/// @return FAIL or OK.
@@ -4068,6 +4115,8 @@ static int getargopt(exarg_T *eap)
int *pp = NULL;
int bad_char_idx;
+ // Note: Keep this in sync with get_argopt_name.
+
// ":edit ++[no]bin[ary] file"
if (strncmp(arg, "bin", 3) == 0 || strncmp(arg, "nobin", 5) == 0) {
if (*arg == 'n') {
@@ -4146,6 +4195,71 @@ static int getargopt(exarg_T *eap)
return OK;
}
+/// Function given to ExpandGeneric() to obtain the list of ++opt names.
+static char *get_argopt_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ // Note: Keep this in sync with getargopt().
+ static char *(p_opt_values[]) = {
+ "fileformat=",
+ "encoding=",
+ "binary",
+ "nobinary",
+ "bad=",
+ "edit",
+ "p",
+ };
+
+ if (idx < (int)ARRAY_SIZE(p_opt_values)) {
+ return p_opt_values[idx];
+ }
+ return NULL;
+}
+
+/// Command-line expansion for ++opt=name.
+int expand_argopt(char *pat, expand_T *xp, regmatch_T *rmp, char ***matches, int *numMatches)
+{
+ if (xp->xp_pattern > xp->xp_line && *(xp->xp_pattern - 1) == '=') {
+ CompleteListItemGetter cb = NULL;
+
+ char *name_end = xp->xp_pattern - 1;
+ if (name_end - xp->xp_line >= 2
+ && strncmp(name_end - 2, "ff", 2) == 0) {
+ cb = get_fileformat_name;
+ } else if (name_end - xp->xp_line >= 10
+ && strncmp(name_end - 10, "fileformat", 10) == 0) {
+ cb = get_fileformat_name;
+ } else if (name_end - xp->xp_line >= 3
+ && strncmp(name_end - 3, "enc", 3) == 0) {
+ cb = get_encoding_name;
+ } else if (name_end - xp->xp_line >= 8
+ && strncmp(name_end - 8, "encoding", 8) == 0) {
+ cb = get_encoding_name;
+ } else if (name_end - xp->xp_line >= 3
+ && strncmp(name_end - 3, "bad", 3) == 0) {
+ cb = get_bad_name;
+ }
+
+ if (cb != NULL) {
+ ExpandGeneric(pat, xp, rmp, matches, numMatches, cb, false);
+ return OK;
+ }
+ return FAIL;
+ }
+
+ // Special handling of "ff" which acts as a short form of
+ // "fileformat", as "ff" is not a substring of it.
+ if (xp->xp_pattern_len == 2
+ && strncmp(xp->xp_pattern, "ff", xp->xp_pattern_len) == 0) {
+ *matches = xmalloc(sizeof(char *));
+ *numMatches = 1;
+ (*matches)[0] = xstrdup("fileformat=");
+ return OK;
+ }
+
+ ExpandGeneric(pat, xp, rmp, matches, numMatches, get_argopt_name, false);
+ return OK;
+}
+
/// Handle the argument for a tabpage related ex command.
/// When an error is encountered then eap->errmsg is set.
///
@@ -4401,7 +4515,7 @@ static int check_more(int message, bool forceit)
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && curbuf->b_fname != NULL) {
char buff[DIALOG_MSG_SIZE];
- vim_snprintf((char *)buff, DIALOG_MSG_SIZE,
+ vim_snprintf(buff, DIALOG_MSG_SIZE,
NGETTEXT("%d more file to edit. Quit anyway?",
"%d more files to edit. Quit anyway?", n), n);
if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) {
@@ -4433,15 +4547,15 @@ static void ex_colorscheme(exarg_T *eap)
char *expr = xstrdup("g:colors_name");
emsg_off++;
- char *p = eval_to_string(expr, NULL, false);
+ char *p = eval_to_string(expr, false);
emsg_off--;
xfree(expr);
if (p != NULL) {
- msg(p);
+ msg(p, 0);
xfree(p);
} else {
- msg("default");
+ msg("default", 0);
}
} else if (load_colors(eap->arg) == FAIL) {
semsg(_("E185: Cannot find color scheme '%s'"), eap->arg);
@@ -4451,9 +4565,9 @@ static void ex_colorscheme(exarg_T *eap)
static void ex_highlight(exarg_T *eap)
{
if (*eap->arg == NUL && eap->cmd[2] == '!') {
- msg(_("Greetings, Vim user!"));
+ msg(_("Greetings, Vim user!"), 0);
}
- do_highlight((const char *)eap->arg, eap->forceit, false);
+ do_highlight(eap->arg, eap->forceit, false);
}
/// Call this function if we thought we were going to exit, but we won't
@@ -4562,11 +4676,14 @@ static void ex_cquit(exarg_T *eap)
FUNC_ATTR_NORETURN
{
// this does not always pass on the exit code to the Manx compiler. why?
- getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE);
+ int status = eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE;
+ ui_call_error_exit(status);
+ getout(status);
}
-/// ":qall": try to quit all windows
-static void ex_quit_all(exarg_T *eap)
+/// Do preparations for "qall" and "wqall".
+/// Returns FAIL when quitting should be aborted.
+int before_quit_all(exarg_T *eap)
{
if (cmdwin_type != 0) {
if (eap->forceit) {
@@ -4574,19 +4691,28 @@ static void ex_quit_all(exarg_T *eap)
} else {
cmdwin_result = K_XF2;
}
- return;
+ return FAIL;
}
// Don't quit while editing the command line.
if (text_locked()) {
text_locked_msg();
- return;
+ return FAIL;
}
if (before_quit_autocmds(curwin, true, eap->forceit)) {
- return;
+ return FAIL;
}
+ return OK;
+}
+
+/// ":qall": try to quit all windows
+static void ex_quit_all(exarg_T *eap)
+{
+ if (before_quit_all(eap) == FAIL) {
+ return;
+ }
exiting = true;
if (eap->forceit || !check_changed_any(false, false)) {
getout(0);
@@ -4710,7 +4836,7 @@ static void ex_tabonly(exarg_T *eap)
}
if (first_tabpage->tp_next == NULL) {
- msg(_("Already only one tab page"));
+ msg(_("Already only one tab page"), 0);
return;
}
@@ -4769,7 +4895,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit)
// Limit to 1000 windows, autocommands may add a window while we close
// one. OK, so I'm paranoid...
while (++done < 1000) {
- snprintf((char *)prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
+ snprintf(prev_idx, sizeof(prev_idx), "%i", tabpage_index(tp));
win_T *wp = tp->tp_lastwin;
ex_win_close(forceit, wp, tp);
@@ -4836,19 +4962,9 @@ static void ex_stop(exarg_T *eap)
if (!eap->forceit) {
autowrite_all();
}
- apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
-
- // TODO(bfredl): the TUI should do this on suspend
- ui_cursor_goto(Rows - 1, 0);
- ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
- ui_flush();
- ui_call_suspend(); // call machine specific function
-
+ may_trigger_vim_suspend_resume(true);
+ ui_call_suspend();
ui_flush();
- maketitle();
- resettitle(); // force updating the title
- ui_refresh(); // may have resized window
- apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
/// ":exit", ":xit" and ":wq": Write file and quit the current window.
@@ -4891,7 +5007,7 @@ static void ex_exit(exarg_T *eap)
static void ex_print(exarg_T *eap)
{
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
- emsg(_(e_emptybuf));
+ emsg(_(e_empty_buffer));
} else {
for (; !got_int; os_breakcheck()) {
print_line(eap->line1,
@@ -4976,8 +5092,13 @@ void ex_splitview(exarg_T *eap)
}
if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
fname = find_file_in_path(eap->arg, strlen(eap->arg),
- FNAME_MESS, true, curbuf->b_ffname);
+ FNAME_MESS, true, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
goto theend;
}
@@ -5091,8 +5212,8 @@ static void ex_tabs(exarg_T *eap)
msg_scroll = true;
win_T *lastused_win = valid_tabpage(lastused_tabpage)
- ? lastused_tabpage->tp_curwin
- : NULL;
+ ? lastused_tabpage->tp_curwin
+ : NULL;
FOR_ALL_TABS(tp) {
if (got_int) {
@@ -5101,7 +5222,7 @@ static void ex_tabs(exarg_T *eap)
msg_putchar('\n');
vim_snprintf(IObuff, IOSIZE, _("Tab page %d"), tabcount++);
- msg_outtrans_attr(IObuff, HL_ATTR(HLF_T));
+ msg_outtrans(IObuff, HL_ATTR(HLF_T));
os_breakcheck();
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
@@ -5119,7 +5240,7 @@ static void ex_tabs(exarg_T *eap)
} else {
home_replace(wp->w_buffer, wp->w_buffer->b_fname, IObuff, IOSIZE, true);
}
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
os_breakcheck();
}
}
@@ -5169,17 +5290,23 @@ static void ex_resize(exarg_T *eap)
/// ":find [+command] <file>" command.
static void ex_find(exarg_T *eap)
{
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
char *fname = find_file_in_path(eap->arg, strlen(eap->arg),
- FNAME_MESS, true, curbuf->b_ffname);
+ FNAME_MESS, true, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
if (eap->addr_count > 0) {
- // Repeat finding the file "count" times. This matters when it
- // appears several times in the path.
+ // Repeat finding the file "count" times. This matters when it appears
+ // several times in the path.
linenr_T count = eap->line2;
while (fname != NULL && --count > 0) {
xfree(fname);
- fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname);
+ fname = find_file_in_path(NULL, 0, FNAME_MESS, false, curbuf->b_ffname,
+ &file_to_find, &search_ctx);
}
}
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (fname == NULL) {
return;
@@ -5201,8 +5328,6 @@ static void ex_edit(exarg_T *eap)
/// @param old_curwin curwin before doing a split or NULL
void do_exedit(exarg_T *eap, win_T *old_curwin)
{
- int n;
-
// ":vi" command ends Ex mode.
if (exmode_active && (eap->cmdidx == CMD_visual
|| eap->cmdidx == CMD_view)) {
@@ -5211,18 +5336,17 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (*eap->arg == NUL) {
// Special case: ":global/pat/visual\NLvi-commands"
if (global_busy) {
- int rd = RedrawingDisabled;
- int nwr = no_wait_return;
- int ms = msg_scroll;
-
if (eap->nextcmd != NULL) {
- stuffReadbuff((const char *)eap->nextcmd);
+ stuffReadbuff(eap->nextcmd);
eap->nextcmd = NULL;
}
+ const int save_rd = RedrawingDisabled;
RedrawingDisabled = 0;
+ const int save_nwr = no_wait_return;
no_wait_return = 0;
need_wait_return = false;
+ const int save_ms = msg_scroll;
msg_scroll = 0;
redraw_all_later(UPD_NOT_VALID);
pending_exmode_active = true;
@@ -5230,9 +5354,9 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
normal_enter(false, true);
pending_exmode_active = false;
- RedrawingDisabled = rd;
- no_wait_return = nwr;
- msg_scroll = ms;
+ RedrawingDisabled = save_rd;
+ no_wait_return = save_nwr;
+ msg_scroll = save_ms;
}
return;
}
@@ -5254,7 +5378,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (*eap->arg != NUL && text_or_buf_locked()) {
return;
}
- n = readonlymode;
+ int n = readonlymode;
if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) {
readonlymode = true;
} else if (eap->cmdidx == CMD_enew) {
@@ -5302,7 +5426,7 @@ void do_exedit(exarg_T *eap, win_T *old_curwin)
if (eap->do_ecmd_cmd != NULL) {
do_cmdline_cmd(eap->do_ecmd_cmd);
}
- n = curwin->w_arg_idx_invalid;
+ int n = curwin->w_arg_idx_invalid;
check_arg_idx(curwin);
if (n != curwin->w_arg_idx_invalid) {
maketitle();
@@ -5337,9 +5461,9 @@ static void ex_popup(exarg_T *eap)
static void ex_swapname(exarg_T *eap)
{
if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) {
- msg(_("No swap file"));
+ msg(_("No swap file"), 0);
} else {
- msg(curbuf->b_ml.ml_mfp->mf_fname);
+ msg(curbuf->b_ml.ml_mfp->mf_fname, 0);
}
}
@@ -5350,8 +5474,8 @@ static void ex_syncbind(exarg_T *eap)
{
win_T *save_curwin = curwin;
buf_T *save_curbuf = curbuf;
- long topline;
- long y;
+ linenr_T topline;
+ int y;
linenr_T old_linenr = curwin->w_cursor.lnum;
setpcmark();
@@ -5425,13 +5549,13 @@ static void ex_read(exarg_T *eap)
return;
}
i = readfile(curbuf->b_ffname, curbuf->b_fname,
- eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
+ eap->line2, 0, (linenr_T)MAXLNUM, eap, 0, false);
} else {
if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) {
- (void)setaltfname(eap->arg, eap->arg, (linenr_T)1);
+ (void)setaltfname(eap->arg, eap->arg, 1);
}
i = readfile(eap->arg, NULL,
- eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0, false);
+ eap->line2, 0, (linenr_T)MAXLNUM, eap, 0, false);
}
if (i != OK) {
if (!aborting()) {
@@ -5447,13 +5571,13 @@ static void ex_read(exarg_T *eap)
} else {
lnum = 1;
}
- if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) {
+ if (*ml_get(lnum) == NUL && u_savedel(lnum, 1) == OK) {
ml_delete(lnum, false);
if (curwin->w_cursor.lnum > 1
&& curwin->w_cursor.lnum >= lnum) {
curwin->w_cursor.lnum--;
}
- deleted_lines_mark(lnum, 1L);
+ deleted_lines_mark(lnum, 1);
}
}
redraw_curbuf_later(UPD_VALID);
@@ -5651,9 +5775,9 @@ static void ex_pwd(exarg_T *eap)
} else if (curtab->tp_localdir != NULL) {
context = "tabpage";
}
- smsg("[%s] %s", context, NameBuff);
+ smsg(0, "[%s] %s", context, NameBuff);
} else {
- msg(NameBuff);
+ msg(NameBuff, 0);
}
} else {
emsg(_("E187: Unknown"));
@@ -5663,25 +5787,27 @@ static void ex_pwd(exarg_T *eap)
/// ":=".
static void ex_equal(exarg_T *eap)
{
- smsg("%" PRId64, (int64_t)eap->line2);
- ex_may_print(eap);
+ if (*eap->arg != NUL && *eap->arg != '|') {
+ // equivalent to :lua= expr
+ ex_lua(eap);
+ } else {
+ eap->nextcmd = find_nextcmd(eap->arg);
+ smsg(0, "%" PRId64, (int64_t)eap->line2);
+ }
}
static void ex_sleep(exarg_T *eap)
{
if (cursor_valid()) {
- int n = curwin->w_winrow + curwin->w_wrow - msg_scrolled;
- if (n >= 0) {
- ui_cursor_goto(n, curwin->w_wincol + curwin->w_wcol);
- }
+ setcursor_mayforce(true);
}
- long len = eap->line2;
+ int64_t len = eap->line2;
switch (*eap->arg) {
case 'm':
break;
case NUL:
- len *= 1000L; break;
+ len *= 1000; break;
default:
semsg(_(e_invarg2), eap->arg); return;
}
@@ -5689,7 +5815,7 @@ static void ex_sleep(exarg_T *eap)
}
/// Sleep for "msec" milliseconds, but return early on CTRL-C.
-void do_sleep(long msec)
+void do_sleep(int64_t msec)
{
ui_flush(); // flush before waiting
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int);
@@ -5746,7 +5872,7 @@ static void ex_wincmd(exarg_T *eap)
// Pass flags on for ":vertical wincmd ]".
postponed_split_flags = cmdmod.cmod_split;
postponed_split_tab = cmdmod.cmod_tab;
- do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar);
+ do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0, xchar);
postponed_split_flags = 0;
postponed_split_tab = 0;
}
@@ -5816,8 +5942,12 @@ static void ex_put(exarg_T *eap)
/// Handle ":copy" and ":move".
static void ex_copymove(exarg_T *eap)
{
- long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1);
+ const char *errormsg = NULL;
+ linenr_T n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1, &errormsg);
if (eap->arg == NULL) { // error detected
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
eap->nextcmd = NULL;
return;
}
@@ -5830,13 +5960,13 @@ static void ex_copymove(exarg_T *eap)
}
if (eap->cmdidx == CMD_move) {
- if (do_move(eap->line1, eap->line2, (linenr_T)n) == FAIL) {
+ if (do_move(eap->line1, eap->line2, n) == FAIL) {
return;
}
} else {
- ex_copy(eap->line1, eap->line2, (linenr_T)n);
+ ex_copy(eap->line1, eap->line2, n);
}
- u_clearline();
+ u_clearline(curbuf);
beginline(BL_SOL | BL_FIX);
ex_may_print(eap);
}
@@ -5862,7 +5992,7 @@ static void ex_submagic(exarg_T *eap)
}
/// ":smagic" and ":snomagic" preview callback.
-static int ex_submagic_preview(exarg_T *eap, long cmdpreview_ns, handle_T cmdpreview_bufnr)
+static int ex_submagic_preview(exarg_T *eap, int cmdpreview_ns, handle_T cmdpreview_bufnr)
{
const optmagic_T saved = magic_overruled;
@@ -5944,7 +6074,7 @@ static void ex_undo(exarg_T *eap)
return;
}
- long step = eap->line2;
+ linenr_T step = eap->line2;
if (eap->forceit) { // undo! 123
// change number for "undo!" must be lesser than current change number
@@ -5995,7 +6125,7 @@ static void ex_redo(exarg_T *eap)
/// ":earlier" and ":later".
static void ex_later(exarg_T *eap)
{
- long count = 0;
+ int count = 0;
bool sec = false;
bool file = false;
char *p = eap->arg;
@@ -6003,7 +6133,7 @@ static void ex_later(exarg_T *eap)
if (*p == NUL) {
count = 1;
} else if (isdigit((uint8_t)(*p))) {
- count = getdigits_long(&p, false, 0);
+ count = getdigits_int(&p, false, 0);
switch (*p) {
case 's':
p++; sec = true; break;
@@ -6334,6 +6464,11 @@ void restore_current_state(save_state_T *sst)
ui_cursor_shape(); // may show different cursor shape
}
+bool expr_map_locked(void)
+{
+ return expr_map_lock > 0 && !(curbuf->b_flags & BF_DUMMY);
+}
+
/// ":normal[!] {commands}": Execute normal mode commands.
static void ex_normal(exarg_T *eap)
{
@@ -6343,10 +6478,11 @@ static void ex_normal(exarg_T *eap)
}
char *arg = NULL;
- if (ex_normal_lock > 0) {
+ if (expr_map_locked()) {
emsg(_(e_secure));
return;
}
+
if (ex_normal_busy >= p_mmd) {
emsg(_("E192: Recursive use of :normal too deep"));
return;
@@ -6360,8 +6496,7 @@ static void ex_normal(exarg_T *eap)
// Count the number of characters to be escaped.
int l;
- char *p;
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
if (*++p == (char)K_SPECIAL) { // trailbyte K_SPECIAL
len += 2;
@@ -6371,7 +6506,7 @@ static void ex_normal(exarg_T *eap)
if (len > 0) {
arg = xmalloc(strlen(eap->arg) + (size_t)len + 1);
len = 0;
- for (p = eap->arg; *p != NUL; p++) {
+ for (char *p = eap->arg; *p != NUL; p++) {
arg[len++] = *p;
for (l = utfc_ptr2len(p) - 1; l > 0; l--) {
arg[len++] = *++p;
@@ -6489,9 +6624,9 @@ void exec_normal(bool was_typed)
static void ex_checkpath(exarg_T *eap)
{
- find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1L,
+ find_pattern_in_path(NULL, 0, 0, false, false, CHECK_PATH, 1,
eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW,
- (linenr_T)1, (linenr_T)MAXLNUM);
+ 1, (linenr_T)MAXLNUM);
}
/// ":psearch"
@@ -6526,9 +6661,9 @@ static void ex_findpat(exarg_T *eap)
break;
}
- long n = 1;
+ int n = 1;
if (ascii_isdigit(*eap->arg)) { // get count
- n = getdigits_long(&eap->arg, false, 0);
+ n = getdigits_int(&eap->arg, false, 0);
eap->arg = skipwhite(eap->arg);
}
if (*eap->arg == '/') { // Match regexp, not just whole words
@@ -6549,7 +6684,7 @@ static void ex_findpat(exarg_T *eap)
}
if (!eap->skip) {
find_pattern_in_path(eap->arg, 0, strlen(eap->arg), whole, !eap->forceit,
- *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
+ *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY,
n, action, eap->line1, eap->line2);
}
}
@@ -6725,8 +6860,8 @@ ssize_t find_cmdline_var(const char *src, size_t *usedlen)
/// @return an allocated string if a valid match was found.
/// Returns NULL if no match was found. "usedlen" then still contains the
/// number of characters to skip.
-char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump, char **errormsg,
- int *escaped, bool empty_is_error)
+char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnump,
+ const char **errormsg, int *escaped, bool empty_is_error)
{
char *result;
char *resultbuf = NULL;
@@ -6752,7 +6887,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
// Note: In "\\%" the % is also not recognized!
if (src > srcstart && src[-1] == '\\') {
*usedlen = 0;
- STRMOVE(src - 1, (char *)src); // remove backslash
+ STRMOVE(src - 1, src); // remove backslash
return NULL;
}
@@ -6845,7 +6980,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
break;
case SPEC_CFILE: // file name under cursor
- result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL);
+ result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1, NULL);
if (result == NULL) {
*errormsg = "";
return NULL;
@@ -6854,12 +6989,10 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
break;
case SPEC_AFILE: // file name for autocommand
- if (autocmd_fname != NULL
- && !path_is_absolute(autocmd_fname)
- // For CmdlineEnter and related events, <afile> is not a path! #9348
- && !strequal("/", autocmd_fname)) {
+ if (autocmd_fname != NULL && !autocmd_fname_full) {
// Still need to turn the fname into a full path. It was
// postponed to avoid a delay when <afile> is not used.
+ autocmd_fname_full = true;
result = FullName_save(autocmd_fname, false);
// Copy into `autocmd_fname`, don't reassign it. #8165
xstrlcpy(autocmd_fname, result, MAXPATHL);
@@ -6867,7 +7000,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
}
result = autocmd_fname;
if (result == NULL) {
- *errormsg = _("E495: no autocommand file name to substitute for \"<afile>\"");
+ *errormsg = _(e_no_autocommand_file_name_to_substitute_for_afile);
return NULL;
}
result = path_try_shorten_fname(result);
@@ -6875,7 +7008,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_ABUF: // buffer number for autocommand
if (autocmd_bufnr <= 0) {
- *errormsg = _("E496: no autocommand buffer number to substitute for \"<abuf>\"");
+ *errormsg = _(e_no_autocommand_buffer_number_to_substitute_for_abuf);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%d", autocmd_bufnr);
@@ -6885,7 +7018,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_AMATCH: // match name for autocommand
result = autocmd_match;
if (result == NULL) {
- *errormsg = _("E497: no autocommand match name to substitute for \"<amatch>\"");
+ *errormsg = _(e_no_autocommand_match_name_to_substitute_for_amatch);
return NULL;
}
break;
@@ -6917,7 +7050,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_SLNUM: // line in file for ":so" command
if (SOURCING_NAME == NULL || SOURCING_LNUM == 0) {
- *errormsg = _("E842: no line number to use for \"<slnum>\"");
+ *errormsg = _(e_no_line_number_to_use_for_slnum);
return NULL;
}
snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR, SOURCING_LNUM);
@@ -6926,10 +7059,10 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
case SPEC_SFLNUM: // line in script file
if (current_sctx.sc_lnum + SOURCING_LNUM == 0) {
- *errormsg = _("E961: no line number to use for \"<sflnum>\"");
+ *errormsg = _(e_no_line_number_to_use_for_sflnum);
return NULL;
}
- snprintf((char *)strbuf, sizeof(strbuf), "%" PRIdLINENR,
+ snprintf(strbuf, sizeof(strbuf), "%" PRIdLINENR,
current_sctx.sc_lnum + SOURCING_LNUM);
result = strbuf;
break;
@@ -6982,7 +7115,7 @@ char *eval_vars(char *src, const char *srcstart, size_t *usedlen, linenr_T *lnum
}
result = NULL;
} else {
- result = xstrnsave(result, resultlen);
+ result = xmemdupz(result, resultlen);
}
xfree(resultbuf);
return result;
@@ -7001,7 +7134,7 @@ char *expand_sfile(char *arg)
} else {
// replace "<sfile>" with the sourced file name, and do ":" stuff
size_t srclen;
- char *errormsg;
+ const char *errormsg;
char *repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL, true);
if (errormsg != NULL) {
if (*errormsg) {
@@ -7055,24 +7188,6 @@ void dialog_msg(char *buff, char *format, char *fname)
vim_snprintf(buff, DIALOG_MSG_SIZE, format, fname);
}
-/// ":behave {mswin,xterm}"
-static void ex_behave(exarg_T *eap)
-{
- if (strcmp(eap->arg, "mswin") == 0) {
- set_option_value_give_err("selection", 0L, "exclusive", 0);
- set_option_value_give_err("selectmode", 0L, "mouse,key", 0);
- set_option_value_give_err("mousemodel", 0L, "popup", 0);
- set_option_value_give_err("keymodel", 0L, "startsel,stopsel", 0);
- } else if (strcmp(eap->arg, "xterm") == 0) {
- set_option_value_give_err("selection", 0L, "inclusive", 0);
- set_option_value_give_err("selectmode", 0L, "", 0);
- set_option_value_give_err("mousemodel", 0L, "extend", 0);
- set_option_value_give_err("keymodel", 0L, "", 0);
- } else {
- semsg(_(e_invarg2), eap->arg);
- }
-}
-
static TriState filetype_detect = kNone;
static TriState filetype_plugin = kNone;
static TriState filetype_indent = kNone;
@@ -7088,7 +7203,7 @@ static void ex_filetype(exarg_T *eap)
{
if (*eap->arg == NUL) {
// Print current status.
- smsg("filetype detection:%s plugin:%s indent:%s",
+ smsg(0, "filetype detection:%s plugin:%s indent:%s",
filetype_detect == kTrue ? "ON" : "OFF",
filetype_plugin == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF",
filetype_indent == kTrue ? (filetype_detect == kTrue ? "ON" : "(on)") : "OFF");
@@ -7100,7 +7215,7 @@ static void ex_filetype(exarg_T *eap)
bool indent = false;
// Accept "plugin" and "indent" in any order.
- for (;;) {
+ while (true) {
if (strncmp(arg, "plugin", 6) == 0) {
plugin = true;
arg = skipwhite(arg + 6);
@@ -7189,7 +7304,7 @@ static void ex_setfiletype(exarg_T *eap)
arg += 9;
}
- set_option_value_give_err("filetype", 0L, arg, OPT_LOCAL);
+ set_option_value_give_err("filetype", CSTR_AS_OPTVAL(arg), OPT_LOCAL);
if (arg != eap->arg) {
did_filetype = false;
}
@@ -7271,12 +7386,31 @@ void set_pressedreturn(bool val)
static void ex_terminal(exarg_T *eap)
{
char ex_cmd[1024];
+ size_t len = 0;
+
+ if (cmdmod.cmod_tab > 0 || cmdmod.cmod_split != 0) {
+ bool multi_mods = false;
+
+ // ex_cmd must be a null terminated string before passing to add_win_cmd_modifiers
+ ex_cmd[0] = '\0';
+
+ len = add_win_cmd_modifiers(ex_cmd, &cmdmod, &multi_mods);
+ assert(len < sizeof(ex_cmd));
+ int result = snprintf(ex_cmd + len, sizeof(ex_cmd) - len, " new");
+ assert(result > 0);
+ len += (size_t)result;
+ } else {
+ int result = snprintf(ex_cmd, sizeof(ex_cmd), "enew%s", eap->forceit ? "!" : "");
+ assert(result > 0);
+ len += (size_t)result;
+ }
+
+ assert(len < sizeof(ex_cmd));
if (*eap->arg != NUL) { // Run {cmd} in 'shell'.
char *name = vim_strsave_escaped(eap->arg, "\"\\");
- snprintf(ex_cmd, sizeof(ex_cmd),
- ":enew%s | call termopen(\"%s\")",
- eap->forceit ? "!" : "", name);
+ snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
+ " | call termopen(\"%s\")", name);
xfree(name);
} else { // No {cmd}: run the job with tokenized 'shell'.
if (*p_sh == NUL) {
@@ -7296,302 +7430,308 @@ static void ex_terminal(exarg_T *eap)
}
shell_free_argv(argv);
- snprintf(ex_cmd, sizeof(ex_cmd),
- ":enew%s | call termopen([%s])",
- eap->forceit ? "!" : "", shell_argv + 1);
+ snprintf(ex_cmd + len, sizeof(ex_cmd) - len,
+ " | call termopen([%s])", shell_argv + 1);
}
do_cmdline_cmd(ex_cmd);
}
+/// ":fclose"
+static void ex_fclose(exarg_T *eap)
+{
+ win_float_remove(eap->forceit, eap->line1);
+}
+
void verify_command(char *cmd)
{
if (strcmp("smile", cmd) != 0) {
return; // acceptable non-existing command
}
+ int a = HL_ATTR(HLF_E);
msg(" #xxn` #xnxx` ,+x@##@Mz;` .xxx"
- "xxxxxxnz+, znnnnnnnnnnnnnnnn.");
+ "xxxxxxnz+, znnnnnnnnnnnnnnnn.", a);
msg(" n###z x####` :x##########W+` ,###"
- "##########M; W################.");
+ "##########M; W################.", a);
msg(" n####; x####` `z##############W: ,###"
- "############# W################.");
+ "############# W################.", a);
msg(" n####W. x####` ,W#################+ ,###"
- "############## W################.");
+ "############## W################.", a);
msg(" n#####n x####` @################### ,###"
- "##############i W################.");
+ "##############i W################.", a);
msg(" n######i x####` .#########@W@########* ,###"
- "##############W`W################.");
+ "##############W`W################.", a);
msg(" n######@. x####` x######W*. `;n#######: ,###"
- "#x,,,,:*M######iW###@:,,,,,,,,,,,`");
+ "#x,,,,:*M######iW###@:,,,,,,,,,,,`", a);
msg(" n#######n x####` *######+` :M#####M ,###"
- "#n `x#####xW###@`");
+ "#n `x#####xW###@`", a);
msg(" n########* x####``@####@; `x#####i ,###"
- "#n ,#####@W###@`");
+ "#n ,#####@W###@`", a);
msg(" n########@ x####`*#####i `M####M ,###"
- "#n x#########@`");
+ "#n x#########@`", a);
msg(" n######### x####`M####z :#####:,###"
- "#n z#########@`");
+ "#n z#########@`", a);
msg(" n#########* x####,#####. n####+,###"
- "#n n#########@`");
+ "#n n#########@`", a);
msg(" n####@####@, x####i####x ;####x,###"
- "#n `W#####@####+++++++++++i");
+ "#n `W#####@####+++++++++++i", a);
msg(" n####*#####M` x#########* `####@,###"
- "#n i#####MW###############W");
+ "#n i#####MW###############W", a);
msg(" n####.######+ x####z####; W####,###"
- "#n i@######W###############W");
+ "#n i@######W###############W", a);
msg(" n####.`W#####: x####n####: M####:###"
- "#@nnnnnW#######,W###############W");
+ "#@nnnnnW#######,W###############W", a);
msg(" n####. :#####M`x####z####; W####,###"
- "##############z W###############W");
+ "##############z W###############W", a);
msg(" n####. #######x#########* `####W,###"
- "#############W` W###############W");
+ "#############W` W###############W", a);
msg(" n####. `M#####W####i####x ;####x,###"
- "############W, W####+**********i");
+ "############W, W####+**********i", a);
msg(" n####. ,##########,#####. n####+,###"
- "###########n. W###@`");
+ "###########n. W###@`", a);
msg(" n####. ##########`M####z :#####:,###"
- "########Wz: W###@`");
+ "########Wz: W###@`", a);
msg(" n####. x#########`*#####i `M####M ,###"
- "#x.....` W###@`");
+ "#x.....` W###@`", a);
msg(" n####. ,@########``@####@; `x#####i ,###"
- "#n W###@`");
+ "#n W###@`", a);
msg(" n####. *########` *#####@+` ,M#####M ,###"
- "#n W###@`");
+ "#n W###@`", a);
msg(" n####. x#######` x######W*. `;n######@: ,###"
- "#n W###@,,,,,,,,,,,,`");
+ "#n W###@,,,,,,,,,,,,`", a);
msg(" n####. .@######` .#########@W@########* ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. i######` @################### ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. n#####` ,W#################+ ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. .@####` .n##############W; ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" n####. i####` :x##########W+` ,###"
- "#n W################,");
+ "#n W################,", a);
msg(" +nnnn` +nnn` ,+x@##@Mz;` .nnn"
- "n+ zxxxxxxxxxxxxxxxx.");
- msg(" ");
+ "n+ zxxxxxxxxxxxxxxxx.", a);
+ msg(" ", a);
msg(" "
- " ,+M@#Mi");
+ " ,+M@#Mi", a);
msg(" "
- " .z########");
+ " .z########", a);
msg(" "
- " i@#########i");
+ " i@#########i", a);
msg(" "
- " `############W`");
+ " `############W`", a);
msg(" "
- " `n#############i");
+ " `n#############i", a);
msg(" "
- " `n##############n");
+ " `n##############n", a);
msg(" `` "
- " z###############@`");
+ " z###############@`", a);
msg(" `W@z, "
- " ##################,");
+ " ##################,", a);
msg(" *#####` "
- " i############@x@###i");
+ " i############@x@###i", a);
msg(" ######M. "
- " :#############n`,W##+");
+ " :#############n`,W##+", a);
msg(" +######@: "
- " .W#########M@##+ *##z");
+ " .W#########M@##+ *##z", a);
msg(" :#######@: "
- " `x########@#x###* ,##n");
+ " `x########@#x###* ,##n", a);
msg(" `@#######@; "
- " z#########M*@nW#i .##x");
+ " z#########M*@nW#i .##x", a);
msg(" z########@i "
- " *###########WM#@#, `##x");
+ " *###########WM#@#, `##x", a);
msg(" i##########+ "
- " ;###########*n###@ `##x");
+ " ;###########*n###@ `##x", a);
msg(" `@#MM#######x, "
- " ,@#########zM,`z##M `@#x");
+ " ,@#########zM,`z##M `@#x", a);
msg(" n##M#W#######n. "
- " `.:i*+#zzzz##+i:.` ,W#########Wii,`n@#@` n@##n");
+ " `.:i*+#zzzz##+i:.` ,W#########Wii,`n@#@` n@##n", a);
msg(" ;###@#x#######n `,i"
- "#nW@#####@@WWW@@####@Mzi. ,W##########@z.. ;zM#+i####z");
+ "#nW@#####@@WWW@@####@Mzi. ,W##########@z.. ;zM#+i####z", a);
msg(" x####nz######## .;#x@##"
- "@Wn#*;,.` ``,:*#x@##M+, ;@########xz@WM+#` `n@#######");
+ "@Wn#*;,.` ``,:*#x@##M+, ;@########xz@WM+#` `n@#######", a);
msg(" ,@####M########xi#@##@Mzi,"
- "` .+x###Mi:n##########Mz```.:i *@######*");
+ "` .+x###Mi:n##########Mz```.:i *@######*", a);
msg(" *#####W#########ix+:` "
- " :n#############z: `*.`M######i");
+ " :n#############z: `*.`M######i", a);
msg(" i#W##nW@+@##@#M@; "
- " ;W@@##########W, i`x@#####,");
+ " ;W@@##########W, i`x@#####,", a);
msg(" `@@n@Wn#@iMW*#*: "
- " `iz#z@######x. M######`");
+ " `iz#z@######x. M######`", a);
msg(" z##zM###x`*, .` "
- " `iW#####W;:` +#####M");
+ " `iW#####W;:` +#####M", a);
msg(" ,###nn##n` "
- " ,#####x;` ,;@######");
+ " ,#####x;` ,;@######", a);
msg(" x###xz#. "
- " in###+ `:######@.");
+ " in###+ `:######@.", a);
msg(" ;####n+ "
- " `Mnx##xi` , zM#######");
+ " `Mnx##xi` , zM#######", a);
msg(" `W####+ "
- "i. `.+x###@#. :n,z######:");
+ "i. `.+x###@#. :n,z######:", a);
msg(" z####@` ;"
- "#: .ii@###@;.*M*z####@`");
+ "#: .ii@###@;.*M*z####@`", a);
msg(" i####M ` `i@"
- "#, :: +#n##@+@##W####n");
+ "#, :: +#n##@+@##W####n", a);
msg(" :####x ,i. ##xzM###"
- "@` i. .@@, .z####x#######*");
+ "@` i. .@@, .z####x#######*", a);
msg(" ,###W; i##Wz########"
- "# :## z##n ,@########x###:");
+ "# :## z##n ,@########x###:", a);
msg(" n##n `W###########M"
- "`;n, i#x ,###@i *W########W#@`");
+ "`;n, i#x ,###@i *W########W#@`", a);
msg(" .@##+ `x###########@."
- " z#+ .M#W``x#####n` `;#######@z#x");
+ " z#+ .M#W``x#####n` `;#######@z#x", a);
msg(" n###z :W############@ "
- " z#* @##xM#######@n; `########nW+");
+ " z#* @##xM#######@n; `########nW+", a);
msg(" ;####nW##############W "
- ":@#* `@#############* :########z@i`");
+ ":@#* `@#############* :########z@i`", a);
msg(" M##################### "
- "M##: @#############@: *W########M#");
+ "M##: @#############@: *W########M#", a);
msg(" ;#####################i."
- "##x` W#############W, :n########zx");
+ "##x` W#############W, :n########zx", a);
msg(" x####################@.`"
- "x; @#############z. .@########W#");
+ "x; @#############z. .@########W#", a);
msg(" ,######################` "
- " W###############x*,` W######zM#i");
+ " W###############x*,` W######zM#i", a);
msg(" #######################: "
- " z##################@x+*#zzi `@#########.");
+ " z##################@x+*#zzi `@#########.", a);
msg(" W########W#z#M#########; "
- " *##########################z :@#######@`");
+ " *##########################z :@#######@`", a);
msg(" `@#######x`;#z ,x#######; "
- " z###########M###xnM@########* :M######@");
+ " z###########M###xnM@########* :M######@", a);
msg(" i########, x#@` z######; "
- " *##########i *#@` `+########+` n######.");
+ " *##########i *#@` `+########+` n######.", a);
msg(" n#######@` M##, `W#####. "
- " *#########z ###; z########M: :W####n");
+ " *#########z ###; z########M: :W####n", a);
msg(" M#######M n##. x####x "
- " `x########: z##+ M#########@; .n###+");
+ " `x########: z##+ M#########@; .n###+", a);
msg(" W#######@` :#W `@####: "
- " `@######W i### ;###########@. n##n");
+ " `@######W i### ;###########@. n##n", a);
msg(" W########z` ,, .x####z "
- " @######@` `W#; `W############* *###;");
+ " @######@` `W#; `W############* *###;", a);
msg(" `@#########Mi,:*n@####W` "
- " W#######* .. `n#############i i###x");
+ " W#######* .. `n#############i i###x", a);
msg(" .#####################z "
- " `@#######@*` .x############n:` ;####.");
+ " `@#######@*` .x############n:` ;####.", a);
msg(" :####################x`,,` "
- " `W#########@x#+#@#############i ,####:");
+ " `W#########@x#+#@#############i ,####:", a);
msg(" ;###################x#@###x"
- "i` *############################: `####i");
+ "i` *############################: `####i", a);
msg(" i##################+#######"
- "#M, x##########################@` W###i");
+ "#M, x##########################@` W###i", a);
msg(" *################@; @######"
- "##@, .W#########################@ x###:");
+ "##@, .W#########################@ x###:", a);
msg(" .+M#############z. M######"
- "###x ,W########################@` ####.");
+ "###x ,W########################@` ####.", a);
msg(" *M*;z@########x: :W#####"
- "##i .M########################i i###:");
+ "##i .M########################i i###:", a);
msg(" *##@z;#@####x: :z###"
- "@i `########################x .###;");
+ "@i `########################x .###;", a);
msg(" *#####n;#@## ;##"
- "* ,x#####################@` W##*");
+ "* ,x#####################@` W##*", a);
msg(" *#######n;* :M##"
- "W*, *W####################` n##z");
+ "W*, *W####################` n##z", a);
msg(" i########@. ,*n####"
- "###M*` `###################M *##M");
+ "###M*` `###################M *##M", a);
msg(" i########n `z#####@@"
- "#####Wi ,M################; ,##@`");
+ "#####Wi ,M################; ,##@`", a);
msg(" ;WMWW@###* .x##@ni.``"
- ".:+zW##z` `n##############z @##,");
+ ".:+zW##z` `n##############z @##,", a);
msg(" .*++*i;;;. .M#@+` "
- " .##n `x############x` n##i");
+ " .##n `x############x` n##i", a);
msg(" :########* x#W, "
- " *#+ *###########M` +##+");
+ " *#+ *###########M` +##+", a);
msg(" ,######### :#@: "
- " ##: #nzzzzzzzzzz. :##x");
+ " ##: #nzzzzzzzzzz. :##x", a);
msg(" .#####Wz+` ##+ "
- " `MM` .znnnnnnnnn. `@#@`");
+ " `MM` .znnnnnnnnn. `@#@`", a);
msg(" `@@ni;*nMz` @W` "
- " :#+ .x#######n x##,");
+ " :#+ .x#######n x##,", a);
msg(" i;z@#####, .#* "
- " z#: ;;;*zW##; ###i");
+ " z#: ;;;*zW##; ###i", a);
msg(" z########: :#; "
- " `Wx +###Wni;n. ;##z");
+ " `Wx +###Wni;n. ;##z", a);
msg(" n########W: .#* "
- " ,#, ;#######@+ `@#M");
+ " ,#, ;#######@+ `@#M", a);
msg(" .###########n;.MM "
- " n* ;iM#######* x#@`");
+ " n* ;iM#######* x#@`", a);
msg(" :#############@;; "
- " .n` ,#W*iW#####W` +##,");
+ " .n` ,#W*iW#####W` +##,", a);
msg(" ,##############. "
- " ix. `x###M;####### ,##i");
+ " ix. `x###M;####### ,##i", a);
msg(" .#############@` "
- " x@n**#W######z;M###@. W##");
+ " x@n**#W######z;M###@. W##", a);
msg(" .##############W: "
- " .x############@*;zW#; z#x");
+ " .x############@*;zW#; z#x", a);
msg(" ,###############@; "
- " `##############@n*;. i#@");
+ " `##############@n*;. i#@", a);
msg(" ,#################i "
- " :n##############W` .##,");
+ " :n##############W` .##,", a);
msg(" ,###################` "
- " .+W##########W, `##i");
+ " .+W##########W, `##i", a);
msg(" :###################@zi,` "
- " ;zM@@@WMn*` @#z");
+ " ;zM@@@WMn*` @#z", a);
msg(" :#######################@x+"
- "*i;;:i#M, `` M#W");
+ "*i;;:i#M, `` M#W", a);
msg(" ;##########################"
- "######@x. n##,");
+ "######@x. n##,", a);
msg(" i#####################@W@@@"
- "@Wxz*:` *##+");
+ "@Wxz*:` *##+", a);
msg(" *######################+```"
- " :##M");
+ " :##M", a);
msg(" ########################M; "
- " `@##,");
+ " `@##,", a);
msg(" z#########################x"
- ", z###");
+ ", z###", a);
msg(" n##########################"
- "#n: ;##W`");
+ "#n: ;##W`", a);
msg(" x##########################"
- "###Mz#++##* `W##i");
+ "###Mz#++##* `W##i", a);
msg(" M##########################"
- "##########@` ###x");
+ "##########@` ###x", a);
msg(" W##########################"
- "###########` .###,");
+ "###########` .###,", a);
msg(" @##########################"
- "##########M n##z");
+ "##########M n##z", a);
msg(" @##################z*i@WMMM"
- "x#x@#####,. :##@.");
+ "x#x@#####,. :##@.", a);
msg(" `#####################@xi` "
- " `::,* x##+");
+ " `::,* x##+", a);
msg(" .#####################@#M. "
- " ;##@`");
+ " ;##@`", a);
msg(" ,#####################:. "
- " M##i");
+ " M##i", a);
msg(" ;###################ni` "
- " i##M");
+ " i##M", a);
msg(" *#################W#` "
- " `W##,");
+ " `W##,", a);
msg(" z#################@Wx+. "
- " +###");
+ " +###", a);
msg(" x######################z. "
- " .@#@`");
+ " .@#@`", a);
msg(" `@#######################@; "
- " z##;");
+ " z##;", a);
msg(" :##########################: "
- " :##z");
+ " :##z", a);
msg(" +#########################W# "
- " M#W");
+ " M#W", a);
msg(" W################@n+*i;:,` "
- " +##,");
+ " +##,", a);
msg(" :##################WMxz+, "
- " ,##i");
+ " ,##i", a);
msg(" n#######################W.., "
- " W##");
+ " W##", a);
msg(" +#########################WW@+. .:. "
- " z#x");
+ " z#x", a);
msg(" `@#############################@@###: "
- " *#W");
+ " *#W", a);
msg(" #################################Wz: "
- " :#@");
+ " :#@", a);
msg(",@###############################i "
- " .##");
+ " .##", a);
msg("n@@@@@@@#########################+ "
- " `##");
+ " `##", a);
msg("` `.:.`.,:iii;;;;;;;;iii;;;:` `.`` "
- " `nW");
+ " `nW", a);
}
/// Get argt of command with id
diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h
index 19dd9e96ca..698153e8df 100644
--- a/src/nvim/ex_docmd.h
+++ b/src/nvim/ex_docmd.h
@@ -1,36 +1,44 @@
-#ifndef NVIM_EX_DOCMD_H
-#define NVIM_EX_DOCMD_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/getchar_defs.h"
#include "nvim/globals.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// flags for do_cmdline()
-#define DOCMD_VERBOSE 0x01 // included command in error message
-#define DOCMD_NOWAIT 0x02 // don't call wait_return() and friends
-#define DOCMD_REPEAT 0x04 // repeat exec. until getline() returns NULL
-#define DOCMD_KEYTYPED 0x08 // don't reset KeyTyped
-#define DOCMD_EXCRESET 0x10 // reset exception environment (for debugging
-#define DOCMD_KEEPLINE 0x20 // keep typed line for repeating with "."
+/// flags for do_cmdline()
+enum {
+ DOCMD_VERBOSE = 0x01, ///< included command in error message
+ DOCMD_NOWAIT = 0x02, ///< don't call wait_return() and friends
+ DOCMD_REPEAT = 0x04, ///< repeat exec. until getline() returns NULL
+ DOCMD_KEYTYPED = 0x08, ///< don't reset KeyTyped
+ DOCMD_EXCRESET = 0x10, ///< reset exception environment (for debugging
+ DOCMD_KEEPLINE = 0x20, ///< keep typed line for repeating with "."
+};
-// defines for eval_vars()
-#define VALID_PATH 1
-#define VALID_HEAD 2
+/// defines for eval_vars()
+enum {
+ VALID_PATH = 1,
+ VALID_HEAD = 2,
+};
// Whether a command index indicates a user command.
#define IS_USER_CMDIDX(idx) ((int)(idx) < 0)
-// Structure used to save the current state. Used when executing Normal mode
-// commands while in any other mode.
+enum { DIALOG_MSG_SIZE = 1000, }; ///< buffer size for dialog_msg()
+
+/// Structure used to save the current state. Used when executing Normal mode
+/// commands while in any other mode.
typedef struct {
int save_msg_scroll;
int save_restart_edit;
bool save_msg_didout;
int save_State;
bool save_finish_op;
- long save_opcount;
+ int save_opcount;
int save_reg_executing;
bool save_pending_end_reg_executing;
tasave_T tabuf;
@@ -39,4 +47,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.h.generated.h"
#endif
-#endif // NVIM_EX_DOCMD_H
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index f76e60f6c5..d2a1d53b78 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file ex_eval.c
///
/// Functions for Ex command line for the +eval feature.
@@ -11,33 +8,34 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_eval_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.c.generated.h"
#endif
+static const char e_multiple_else[] = N_("E583: Multiple :else");
+static const char e_multiple_finally[] = N_("E607: Multiple :finally");
+
// Exception handling terms:
//
// :try ":try" command ─┐
@@ -154,11 +152,10 @@ int aborted_in_try(void)
/// When several messages appear in the same command, the first is usually the
/// most specific one and used as the exception value. The "severe" flag can be
/// set to true, if a later but severer message should be used instead.
-bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
+bool cause_errthrow(const char *mesg, bool multiline, bool severe, bool *ignore)
FUNC_ATTR_NONNULL_ALL
{
msglist_T *elem;
- msglist_T **plist;
// Do nothing when displaying the interrupt message or reporting an
// uncaught exception (which has already been discarded then) at the top
@@ -239,21 +236,20 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
// returned. - Throw only the first of several errors in a row, except
// a severe error is following.
if (msg_list != NULL) {
- plist = msg_list;
+ msglist_T **plist = msg_list;
while (*plist != NULL) {
plist = &(*plist)->next;
}
elem = xmalloc(sizeof(msglist_T));
elem->msg = xstrdup(mesg);
+ elem->multiline = multiline;
elem->next = NULL;
elem->throw_msg = NULL;
*plist = elem;
if (plist == msg_list || severe) {
- char *tmsg;
-
// Skip the extra "Vim " prefix for message "E458".
- tmsg = elem->msg;
+ char *tmsg = elem->msg;
if (strncmp(tmsg, "Vim E", 5) == 0
&& ascii_isdigit(tmsg[5])
&& ascii_isdigit(tmsg[6])
@@ -278,11 +274,9 @@ bool cause_errthrow(const char *mesg, bool severe, bool *ignore)
/// Free a "msg_list" and the messages it contains.
static void free_msglist(msglist_T *l)
{
- msglist_T *messages, *next;
-
- messages = l;
+ msglist_T *messages = l;
while (messages != NULL) {
- next = messages->next;
+ msglist_T *next = messages->next;
xfree(messages->msg);
xfree(messages->sfile);
xfree(messages);
@@ -378,12 +372,12 @@ int do_intthrow(cstack_T *cstack)
/// Get an exception message that is to be stored in current_exception->value.
char *get_exception_string(void *value, except_type_T type, char *cmdname, int *should_free)
{
- char *ret, *mesg;
- char *p, *val;
+ char *ret;
if (type == ET_ERROR) {
+ char *val;
*should_free = true;
- mesg = ((msglist_T *)value)->throw_msg;
+ char *mesg = ((msglist_T *)value)->throw_msg;
if (cmdname != NULL && *cmdname != NUL) {
size_t cmdlen = strlen(cmdname);
ret = xstrnsave("Vim(", 4 + cmdlen + 2 + strlen(mesg));
@@ -398,7 +392,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int *
// msg_add_fname may have been used to prefix the message with a file
// name in quotes. In the exception value, put the file name in
// parentheses and move it to the end.
- for (p = mesg;; p++) {
+ for (char *p = mesg;; p++) {
if (*p == NUL
|| (*p == 'E'
&& ascii_isdigit(p[1])
@@ -441,9 +435,6 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, int *
/// exception.
static int throw_exception(void *value, except_type_T type, char *cmdname)
{
- except_T *excp;
- int should_free;
-
// Disallow faking Interrupt or error exceptions as user exceptions. They
// would be treated differently from real interrupt or error exceptions
// when no active try block is found, see do_cmdline().
@@ -456,7 +447,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
}
}
- excp = xmalloc(sizeof(except_T));
+ except_T *excp = xmalloc(sizeof(except_T));
if (type == ET_ERROR) {
// Store the original message and prefix the exception value with
@@ -464,6 +455,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
excp->messages = (msglist_T *)value;
}
+ int should_free;
excp->value = get_exception_string(value, type, cmdname, &should_free);
if (excp->value == NULL && should_free) {
goto nomem;
@@ -495,7 +487,7 @@ static int throw_exception(void *value, except_type_T type, char *cmdname)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(_("Exception thrown: %s"), excp->value);
+ smsg(0, _("Exception thrown: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
@@ -525,8 +517,6 @@ fail:
/// caught and the catch clause has been ended normally.
static void discard_exception(except_T *excp, bool was_finished)
{
- char *saved_IObuff;
-
if (current_exception == excp) {
current_exception = NULL;
}
@@ -538,7 +528,7 @@ static void discard_exception(except_T *excp, bool was_finished)
if (p_verbose >= 13 || debug_break_level > 0) {
int save_msg_silent = msg_silent;
- saved_IObuff = xstrdup(IObuff);
+ char *saved_IObuff = xstrdup(IObuff);
if (debug_break_level > 0) {
msg_silent = false; // display messages
} else {
@@ -548,7 +538,7 @@ static void discard_exception(except_T *excp, bool was_finished)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value);
+ smsg(0, was_finished ? _("Exception finished: %s") : _("Exception discarded: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
cmdline_row = msg_row;
@@ -615,7 +605,7 @@ static void catch_exception(except_T *excp)
if (debug_break_level > 0 || *p_vfile == NUL) {
msg_scroll = true; // always scroll up, don't overwrite
}
- smsg(_("Exception caught: %s"), excp->value);
+ smsg(0, _("Exception caught: %s"), excp->value);
msg_puts("\n"); // don't overwrite this either
if (debug_break_level > 0 || *p_vfile == NUL) {
@@ -663,6 +653,40 @@ static void finish_exception(except_T *excp)
discard_exception(excp, true);
}
+/// Save the current exception state in "estate"
+void exception_state_save(exception_state_T *estate)
+{
+ estate->estate_current_exception = current_exception;
+ estate->estate_did_throw = did_throw;
+ estate->estate_need_rethrow = need_rethrow;
+ estate->estate_trylevel = trylevel;
+ estate->estate_did_emsg = did_emsg;
+}
+
+/// Restore the current exception state from "estate"
+void exception_state_restore(exception_state_T *estate)
+{
+ // Handle any outstanding exceptions before restoring the state
+ if (did_throw) {
+ handle_did_throw();
+ }
+ current_exception = estate->estate_current_exception;
+ did_throw = estate->estate_did_throw;
+ need_rethrow = estate->estate_need_rethrow;
+ trylevel = estate->estate_trylevel;
+ did_emsg = estate->estate_did_emsg;
+}
+
+/// Clear the current exception state
+void exception_state_clear(void)
+{
+ current_exception = NULL;
+ did_throw = false;
+ need_rethrow = false;
+ trylevel = 0;
+ did_emsg = 0;
+}
+
// Flags specifying the message displayed by report_pending.
#define RP_MAKE 0
#define RP_RESUME 1
@@ -677,7 +701,6 @@ static void report_pending(int action, int pending, void *value)
{
char *mesg;
char *s;
- int save_msg_silent;
assert(value || !(pending & CSTP_THROW));
@@ -727,13 +750,13 @@ static void report_pending(int action, int pending, void *value)
}
}
- save_msg_silent = msg_silent;
+ int save_msg_silent = msg_silent;
if (debug_break_level > 0) {
msg_silent = false; // display messages
}
no_wait_return++;
msg_scroll = true; // always scroll up, don't overwrite
- smsg(mesg, s);
+ smsg(0, mesg, s);
msg_puts("\n"); // don't overwrite this either
cmdline_row = msg_row;
no_wait_return--;
@@ -797,17 +820,20 @@ void report_discard_pending(int pending, void *value)
void ex_eval(exarg_T *eap)
{
typval_T tv;
+ evalarg_T evalarg;
+
+ fill_evalarg_from_eap(&evalarg, eap, eap->skip);
- if (eval0(eap->arg, &tv, &eap->nextcmd, !eap->skip) == OK) {
+ if (eval0(eap->arg, &tv, eap, &evalarg) == OK) {
tv_clear(&tv);
}
+
+ clear_evalarg(&evalarg, eap);
}
/// Handle ":if".
void ex_if(exarg_T *eap)
{
- int skip;
- int result;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
@@ -816,10 +842,10 @@ void ex_if(exarg_T *eap)
cstack->cs_idx++;
cstack->cs_flags[cstack->cs_idx] = 0;
- skip = CHECK_SKIP;
+ int skip = CHECK_SKIP;
bool error;
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
+ int result = eval_to_bool(eap->arg, &error, eap, skip);
if (!skip && !error) {
if (result) {
@@ -860,7 +886,6 @@ void ex_endif(exarg_T *eap)
/// Handle ":else" and ":elseif".
void ex_else(exarg_T *eap)
{
- bool result = false;
cstack_T *const cstack = eap->cstack;
bool skip = CHECK_SKIP;
@@ -876,7 +901,7 @@ void ex_else(exarg_T *eap)
skip = true;
} else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) {
if (eap->cmdidx == CMD_else) {
- eap->errmsg = _("E583: multiple :else");
+ eap->errmsg = _(e_multiple_else);
return;
}
eap->errmsg = _("E584: :elseif after :else");
@@ -907,6 +932,7 @@ void ex_else(exarg_T *eap)
}
if (eap->cmdidx == CMD_elseif) {
+ bool result = false;
bool error;
// When skipping we ignore most errors, but a missing expression is
// wrong, perhaps it should have been "else".
@@ -914,7 +940,7 @@ void ex_else(exarg_T *eap)
if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) {
semsg(_(e_invexpr2), eap->arg);
} else {
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
+ result = eval_to_bool(eap->arg, &error, eap, skip);
}
// When throwing error exceptions, we want to throw always the first
@@ -941,13 +967,12 @@ void ex_else(exarg_T *eap)
void ex_while(exarg_T *eap)
{
bool error;
- int skip;
- int result;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
eap->errmsg = _("E585: :while/:for nesting too deep");
} else {
+ int result;
// The loop flag is set when we have jumped back from the matching
// ":endwhile" or ":endfor". When not set, need to initialise this
// cstack entry.
@@ -959,14 +984,13 @@ void ex_while(exarg_T *eap)
cstack->cs_flags[cstack->cs_idx] =
eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR;
- skip = CHECK_SKIP;
- if (eap->cmdidx == CMD_while) {
- // ":while bool-expr"
- result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip);
- } else {
+ int skip = CHECK_SKIP;
+ if (eap->cmdidx == CMD_while) { // ":while bool-expr"
+ result = eval_to_bool(eap->arg, &error, eap, skip);
+ } else { // ":for var in list-expr"
+ evalarg_T evalarg;
+ fill_evalarg_from_eap(&evalarg, eap, skip);
void *fi;
-
- // ":for var in list-expr"
if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0) {
// Jumping here from a ":continue" or ":endfor": use the
// previously evaluated list.
@@ -974,7 +998,7 @@ void ex_while(exarg_T *eap)
error = false;
} else {
// Evaluate the argument and get the info in a structure.
- fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip);
+ fi = eval_for_line(eap->arg, &error, eap, &evalarg);
cstack->cs_forinfo[cstack->cs_idx] = fi;
}
@@ -989,6 +1013,7 @@ void ex_while(exarg_T *eap)
free_for_info(fi);
cstack->cs_forinfo[cstack->cs_idx] = NULL;
}
+ clear_evalarg(&evalarg, eap);
}
// If this cstack entry was just initialised and is active, set the
@@ -1013,7 +1038,6 @@ void ex_while(exarg_T *eap)
/// Handle ":continue"
void ex_continue(exarg_T *eap)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
@@ -1023,7 +1047,7 @@ void ex_continue(exarg_T *eap)
// conditional not in its finally clause (which is then to be executed
// next). Therefore, deactivate all conditionals except the ":while"
// itself (if reached).
- idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false);
+ int idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, false);
assert(idx >= 0);
if (cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR)) {
rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel);
@@ -1043,7 +1067,6 @@ void ex_continue(exarg_T *eap)
/// Handle ":break"
void ex_break(exarg_T *eap)
{
- int idx;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
@@ -1053,7 +1076,7 @@ void ex_break(exarg_T *eap)
// conditional not in its finally clause (which is then to be
// executed next) is found. In the latter case, make the ":break"
// pending for execution at the ":endtry".
- idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, true);
+ int idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, true);
if (idx >= 0 && !(cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR))) {
cstack->cs_pending[idx] = CSTP_BREAK;
report_make_pending(CSTP_BREAK, NULL);
@@ -1065,10 +1088,8 @@ void ex_break(exarg_T *eap)
void ex_endwhile(exarg_T *eap)
{
cstack_T *const cstack = eap->cstack;
- int idx;
- char *err;
+ const char *err;
int csf;
- int fl;
if (eap->cmdidx == CMD_endwhile) {
err = e_while;
@@ -1081,7 +1102,7 @@ void ex_endwhile(exarg_T *eap)
if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) {
eap->errmsg = _(err);
} else {
- fl = cstack->cs_flags[cstack->cs_idx];
+ int fl = cstack->cs_flags[cstack->cs_idx];
if (!(fl & csf)) {
// If we are in a ":while" or ":for" but used the wrong endloop
// command, do not rewind to the next enclosing ":for"/":while".
@@ -1098,6 +1119,7 @@ void ex_endwhile(exarg_T *eap)
eap->errmsg = _(e_endtry);
}
// Try to find the matching ":while" and report what's missing.
+ int idx;
for (idx = cstack->cs_idx; idx > 0; idx--) {
fl = cstack->cs_flags[idx];
if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) {
@@ -1136,12 +1158,11 @@ void ex_endwhile(exarg_T *eap)
/// Handle ":throw expr"
void ex_throw(exarg_T *eap)
{
- const char *arg = (const char *)eap->arg;
+ char *arg = eap->arg;
char *value;
if (*arg != NUL && *arg != '|' && *arg != '\n') {
- value = eval_to_string_skip(arg, (const char **)&eap->nextcmd,
- (bool)eap->skip);
+ value = eval_to_string_skip(arg, eap, eap->skip);
} else {
emsg(_(e_argreq));
value = NULL;
@@ -1163,10 +1184,8 @@ void ex_throw(exarg_T *eap)
/// used for rethrowing an uncaught exception.
void do_throw(cstack_T *cstack)
{
- int idx;
int inactivate_try = false;
- //
// Cleanup and deactivate up to the next surrounding try conditional that
// is not in its finally clause. Normally, do not deactivate the try
// conditional itself, so that its ACTIVE flag can be tested below. But
@@ -1174,7 +1193,7 @@ void do_throw(cstack_T *cstack)
// deactivate the try conditional, too, as if the conversion had been done,
// and reset the did_emsg or got_int flag, so this won't happen again at
// the next surrounding try conditional.
- //
+
#ifndef THROW_ON_ERROR_TRUE
if (did_emsg && !THROW_ON_ERROR) {
inactivate_try = true;
@@ -1187,7 +1206,7 @@ void do_throw(cstack_T *cstack)
got_int = false;
}
#endif
- idx = cleanup_conditionals(cstack, 0, inactivate_try);
+ int idx = cleanup_conditionals(cstack, 0, inactivate_try);
if (idx >= 0) {
// If this try conditional is active and we are before its first
// ":catch", set THROWN so that the ":catch" commands will check
@@ -1220,7 +1239,6 @@ void do_throw(cstack_T *cstack)
/// Handle ":try"
void ex_try(exarg_T *eap)
{
- int skip;
cstack_T *const cstack = eap->cstack;
if (cstack->cs_idx == CSTACK_LEN - 1) {
@@ -1231,7 +1249,7 @@ void ex_try(exarg_T *eap)
cstack->cs_flags[cstack->cs_idx] = CSF_TRY;
cstack->cs_pending[cstack->cs_idx] = CSTP_NONE;
- skip = CHECK_SKIP;
+ int skip = CHECK_SKIP;
if (!skip) {
// Set ACTIVE and TRUE. TRUE means that the corresponding ":catch"
@@ -1271,12 +1289,9 @@ void ex_catch(exarg_T *eap)
int idx = 0;
bool give_up = false;
bool skip = false;
- bool caught = false;
char *end;
- char save_char = 0;
char *save_cpo;
regmatch_T regmatch;
- int prev_got_int;
cstack_T *const cstack = eap->cstack;
char *pat;
@@ -1319,6 +1334,7 @@ void ex_catch(exarg_T *eap)
}
if (!give_up) {
+ bool caught = false;
// Don't do something when no exception has been thrown or when the
// corresponding try block never got active (because of an inactive
// surrounding conditional or after an error or interrupt or throw).
@@ -1344,6 +1360,7 @@ void ex_catch(exarg_T *eap)
// the original exception, replace it by an interrupt exception,
// and don't catch it in this try block.
if (!dbg_check_skipped(eap) || !do_intthrow(cstack)) {
+ char save_char = 0;
// Terminate the pattern and avoid the 'l' flag in 'cpoptions'
// while compiling it.
if (end != NULL) {
@@ -1351,7 +1368,7 @@ void ex_catch(exarg_T *eap)
*end = NUL;
}
save_cpo = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
// Disable error messages, it will make current exception
// invalid
emsg_off++;
@@ -1365,14 +1382,13 @@ void ex_catch(exarg_T *eap)
if (regmatch.regprog == NULL) {
semsg(_(e_invarg2), pat);
} else {
- //
// Save the value of got_int and reset it. We don't want
// a previous interruption cancel matching, only hitting
// CTRL-C while matching should abort it.
- //
- prev_got_int = got_int;
+
+ int prev_got_int = got_int;
got_int = false;
- caught = vim_regexec_nl(&regmatch, current_exception->value, (colnr_T)0);
+ caught = vim_regexec_nl(&regmatch, current_exception->value, 0);
got_int |= prev_got_int;
vim_regfree(regmatch.regprog);
}
@@ -1415,7 +1431,6 @@ void ex_catch(exarg_T *eap)
void ex_finally(exarg_T *eap)
{
int idx;
- int skip = false;
int pending = CSTP_NONE;
cstack_T *const cstack = eap->cstack;
@@ -1439,7 +1454,7 @@ void ex_finally(exarg_T *eap)
if (cstack->cs_flags[idx] & CSF_FINALLY) {
// Give up for a multiple ":finally" and ignore it.
- eap->errmsg = _("E607: multiple :finally");
+ eap->errmsg = _(e_multiple_finally);
return;
}
rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR,
@@ -1451,7 +1466,7 @@ void ex_finally(exarg_T *eap)
// ":finally". After every other error (did_emsg or the conditional
// errors detected above) or after an interrupt (got_int) or an
// exception (did_throw), the finally clause must be executed.
- skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
+ int skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE);
if (!skip) {
// When debugging or a breakpoint was encountered, display the
@@ -1497,8 +1512,8 @@ void ex_finally(exarg_T *eap)
} else {
pending |= (did_throw ? CSTP_THROW : 0);
}
- pending |= did_emsg ? CSTP_ERROR : 0;
- pending |= got_int ? CSTP_INTERRUPT : 0;
+ pending |= did_emsg ? CSTP_ERROR : 0;
+ pending |= got_int ? CSTP_INTERRUPT : 0;
assert(pending >= CHAR_MIN && pending <= CHAR_MAX);
cstack->cs_pending[cstack->cs_idx] = (char)pending;
@@ -1644,8 +1659,9 @@ void ex_endtry(exarg_T *eap)
if (!skip) {
report_resume_pending(pending,
- (pending == CSTP_RETURN) ? rettv :
- (pending & CSTP_THROW) ? (void *)current_exception : NULL);
+ (pending == CSTP_RETURN)
+ ? rettv
+ : (pending & CSTP_THROW) ? (void *)current_exception : NULL);
switch (pending) {
case CSTP_NONE:
break;
@@ -1979,20 +1995,18 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev
/// Handle ":endfunction" when not after a ":function"
void ex_endfunction(exarg_T *eap)
{
- emsg(_("E193: :endfunction not inside a function"));
+ semsg(_(e_str_not_inside_function), ":endfunction");
}
/// @return true if the string "p" looks like a ":while" or ":for" command.
int has_loop_cmd(char *p)
{
- int len;
-
// skip modifiers, white space and ':'
- for (;;) {
+ while (true) {
while (*p == ' ' || *p == '\t' || *p == ':') {
p++;
}
- len = modifier_len(p);
+ int len = modifier_len(p);
if (len == 0) {
break;
}
diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h
index d3053ae0d4..0294acd109 100644
--- a/src/nvim/ex_eval.h
+++ b/src/nvim/ex_eval.h
@@ -1,10 +1,8 @@
-#ifndef NVIM_EX_EVAL_H
-#define NVIM_EX_EVAL_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_eval_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/ex_eval_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_eval.h.generated.h"
#endif
-#endif // NVIM_EX_EVAL_H
diff --git a/src/nvim/ex_eval_defs.h b/src/nvim/ex_eval_defs.h
index 6b3c426722..c7231bb315 100644
--- a/src/nvim/ex_eval_defs.h
+++ b/src/nvim/ex_eval_defs.h
@@ -1,7 +1,39 @@
-#ifndef NVIM_EX_EVAL_DEFS_H
-#define NVIM_EX_EVAL_DEFS_H
+#pragma once
-#include "nvim/pos.h"
+#include <stdbool.h>
+
+#include "nvim/pos_defs.h"
+
+/// A list used for saving values of "emsg_silent". Used by ex_try() to save the
+/// value of "emsg_silent" if it was non-zero. When this is done, the CSF_SILENT
+/// flag below is set.
+typedef struct eslist_elem eslist_T;
+struct eslist_elem {
+ int saved_emsg_silent; ///< saved value of "emsg_silent"
+ eslist_T *next; ///< next element on the list
+};
+
+/// For conditional commands a stack is kept of nested conditionals.
+/// When cs_idx < 0, there is no conditional command.
+enum { CSTACK_LEN = 50, };
+
+typedef struct {
+ int cs_flags[CSTACK_LEN]; ///< CSF_ flags
+ char cs_pending[CSTACK_LEN]; ///< CSTP_: what's pending in ":finally"
+ union {
+ void *csp_rv[CSTACK_LEN]; ///< return typeval for pending return
+ void *csp_ex[CSTACK_LEN]; ///< exception for pending throw
+ } cs_pend;
+ void *cs_forinfo[CSTACK_LEN]; ///< info used by ":for"
+ int cs_line[CSTACK_LEN]; ///< line nr of ":while"/":for" line
+ int cs_idx; ///< current entry, or -1 if none
+ int cs_looplevel; ///< nr of nested ":while"s and ":for"s
+ int cs_trylevel; ///< nr of nested ":try"s
+ eslist_T *cs_emsg_silent_list; ///< saved values of "emsg_silent"
+ int cs_lflags; ///< loop flags: CSL_ flags
+} cstack_T;
+#define cs_rettv cs_pend.csp_rv
+#define cs_exception cs_pend.csp_ex
/// There is no CSF_IF, the lack of CSF_WHILE, CSF_FOR and CSF_TRY means ":if"
/// was used.
@@ -35,17 +67,26 @@ enum {
CSTP_FINISH = 32, ///< ":finish" is pending
};
+/// Flags for the cs_lflags item in cstack_T.
+enum {
+ CSL_HAD_LOOP = 1, ///< just found ":while" or ":for"
+ CSL_HAD_ENDLOOP = 2, ///< just found ":endwhile" or ":endfor"
+ CSL_HAD_CONT = 4, ///< just found ":continue"
+ CSL_HAD_FINA = 8, ///< just found ":finally"
+};
+
/// A list of error messages that can be converted to an exception. "throw_msg"
/// is only set in the first element of the list. Usually, it points to the
/// original message stored in that element, but sometimes it points to a later
/// message in the list. See cause_errthrow().
typedef struct msglist msglist_T;
struct msglist {
+ msglist_T *next; ///< next of several messages in a row
char *msg; ///< original message, allocated
char *throw_msg; ///< msg to throw: usually original one
char *sfile; ///< value from estack_sfile(), allocated
linenr_T slnum; ///< line number for "sfile"
- msglist_T *next; ///< next of several messages in a row
+ bool multiline; ///< whether this is a multiline message
};
/// The exception types.
@@ -76,4 +117,13 @@ struct cleanup_stuff {
except_T *exception; ///< exception value
};
-#endif // NVIM_EX_EVAL_DEFS_H
+/// Exception state that is saved and restored when calling timer callback
+/// functions and deferred functions.
+typedef struct exception_state_S exception_state_T;
+struct exception_state_S {
+ except_T *estate_current_exception;
+ bool estate_did_throw;
+ bool estate_need_rethrow;
+ int estate_trylevel;
+ int estate_did_emsg;
+};
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 76c3680742..64ef17b157 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ex_getln.c: Functions for entering and editing an Ex command line.
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -17,7 +15,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -29,21 +27,23 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -55,22 +55,24 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
#include "nvim/window.h"
@@ -82,6 +84,7 @@ static unsigned last_prompt_id = 0;
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
+ colnr_T vs_skipcol;
linenr_T vs_topline;
int vs_topfill;
linenr_T vs_botline;
@@ -104,7 +107,7 @@ typedef struct {
typedef struct command_line_state {
VimState state;
int firstc;
- long count;
+ int count;
int indent;
int c;
int gotesc; // true when <ESC> just typed
@@ -126,9 +129,34 @@ typedef struct command_line_state {
int ignore_drag_release;
int break_ctrl_c;
expand_T xpc;
- long *b_im_ptr;
+ OptInt *b_im_ptr;
+ buf_T *b_im_ptr_buf; ///< buffer where b_im_ptr is valid
} CommandLineState;
+typedef struct cmdpreview_undo_info {
+ u_header_T *save_b_u_oldhead;
+ u_header_T *save_b_u_newhead;
+ u_header_T *save_b_u_curhead;
+ int save_b_u_numhead;
+ bool save_b_u_synced;
+ int save_b_u_seq_last;
+ int save_b_u_save_nr_last;
+ int save_b_u_seq_cur;
+ time_t save_b_u_time_cur;
+ int save_b_u_save_nr_cur;
+ char *save_b_u_line_ptr;
+ linenr_T save_b_u_line_lnum;
+ colnr_T save_b_u_line_colnr;
+} CpUndoInfo;
+
+typedef struct cmdpreview_buf_info {
+ buf_T *buf;
+ OptInt save_b_p_ul;
+ int save_b_changed;
+ varnumber_T save_changedtick;
+ CpUndoInfo undo_info;
+} CpBufInfo;
+
typedef struct cmdpreview_win_info {
win_T *win;
pos_T save_w_cursor;
@@ -137,17 +165,6 @@ typedef struct cmdpreview_win_info {
int save_w_p_cuc;
} CpWinInfo;
-typedef struct cmdpreview_buf_info {
- buf_T *buf;
- bool save_b_u_synced;
- time_t save_b_u_time_cur;
- long save_b_u_seq_cur;
- u_header_T *save_b_u_newhead;
- long save_b_p_ul;
- int save_b_changed;
- varnumber_T save_changedtick;
-} CpBufInfo;
-
typedef struct cmdpreview_info {
kvec_t(CpWinInfo) win_info;
kvec_t(CpBufInfo) buf_info;
@@ -187,15 +204,14 @@ static int cedit_key = -1; ///< key value of 'cedit' option
#endif
static handle_T cmdpreview_bufnr = 0;
-static long cmdpreview_ns = 0;
-
-static int cmd_hkmap = 0; // Hebrew mapping during command line
+static int cmdpreview_ns = 0;
static void save_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
vs->vs_curswant = wp->w_curswant;
vs->vs_leftcol = wp->w_leftcol;
+ vs->vs_skipcol = wp->w_skipcol;
vs->vs_topline = wp->w_topline;
vs->vs_topfill = wp->w_topfill;
vs->vs_botline = wp->w_botline;
@@ -207,6 +223,7 @@ static void restore_viewstate(win_T *wp, viewstate_T *vs)
{
wp->w_curswant = vs->vs_curswant;
wp->w_leftcol = vs->vs_leftcol;
+ wp->w_skipcol = vs->vs_skipcol;
wp->w_topline = vs->vs_topline;
wp->w_topfill = vs->vs_topfill;
wp->w_botline = vs->vs_botline;
@@ -232,14 +249,9 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
int *skiplen, int *patlen)
FUNC_ATTR_NONNULL_ALL
{
- char *cmd;
char *p;
bool delim_optional = false;
- int delim;
- char *end;
- char *dummy;
- pos_T save_cursor;
- bool use_last_pat;
+ const char *dummy;
bool retval = false;
magic_T magic = 0;
@@ -273,7 +285,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
cmdmod_T dummy_cmdmod;
parse_command_modifiers(&ea, &dummy, &dummy_cmdmod, true);
- cmd = skip_range(ea.cmd, NULL);
+ char *cmd = skip_range(ea.cmd, NULL);
if (vim_strchr("sgvl", (uint8_t)(*cmd)) == NULL) {
goto theend;
}
@@ -298,7 +310,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
if (*p == '!') {
p = skipwhite(p + 1);
}
- while (ASCII_ISALPHA(*(p = skipwhite((char *)p)))) {
+ while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
p++;
}
if (*p == NUL) {
@@ -324,11 +336,11 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
}
p = skipwhite(p);
- delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++;
+ int delim = (delim_optional && vim_isIDc((uint8_t)(*p))) ? ' ' : *p++;
*search_delim = delim;
- end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
+ char *end = skip_regexp_ex(p, delim, magic_isset(), NULL, NULL, &magic);
- use_last_pat = end == p && *end == delim;
+ bool use_last_pat = end == p && *end == delim;
if (end == p && !use_last_pat) {
goto theend;
}
@@ -349,7 +361,7 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
*patlen = (int)(end - p);
// parse the address range
- save_cursor = curwin->w_cursor;
+ pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor = s->search_start;
parse_cmd_address(&ea, &dummy, true);
if (ea.addr_count > 0) {
@@ -375,13 +387,10 @@ theend:
}
// May do 'incsearch' highlighting if desired.
-static void may_do_incsearch_highlighting(int firstc, long count, incsearch_state_T *s)
+static void may_do_incsearch_highlighting(int firstc, int count, incsearch_state_T *s)
{
pos_T end_pos;
- proftime_T tm;
int skiplen, patlen;
- char next_char;
- bool use_last_pat;
int search_delim;
// Parsing range may already set the last search pattern.
@@ -418,9 +427,9 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
int found; // do_search() result
// Use the previous pattern for ":s//".
- next_char = ccline.cmdbuff[skiplen + patlen];
- use_last_pat = patlen == 0 && skiplen > 0
- && ccline.cmdbuff[skiplen - 1] == next_char;
+ char next_char = ccline.cmdbuff[skiplen + patlen];
+ bool use_last_pat = patlen == 0 && skiplen > 0
+ && ccline.cmdbuff[skiplen - 1] == next_char;
// If there is no pattern, don't do anything.
if (patlen == 0 && !use_last_pat) {
@@ -433,7 +442,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
ui_flush();
emsg_off++; // So it doesn't beep if bad expr
// Set the time limit to half a second.
- tm = profile_setlimit(500L);
+ proftime_T tm = profile_setlimit(500);
if (!p_hls) {
search_flags += SEARCH_KEEP;
}
@@ -477,7 +486,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
// first restore the old curwin values, so the screen is
// positioned in the same way as the actual search command
restore_viewstate(curwin, &s->old_viewstate);
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
update_topline(curwin);
if (found != 0) {
@@ -506,6 +515,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat
}
validate_cursor();
+
// May redraw the status line to show the cursor position.
if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) {
curwin->w_redr_status = true;
@@ -588,7 +598,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
curwin->w_cursor = s->save_cursor;
setpcmark();
}
- curwin->w_cursor = s->search_start; // -V519
+ curwin->w_cursor = s->search_start;
}
restore_viewstate(curwin, &s->old_viewstate);
highlight_match = false;
@@ -600,6 +610,7 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool
magic_overruled = s->magic_overruled_save;
validate_cursor(); // needed for TAB
+ status_redraw_all();
redraw_all_later(UPD_SOME_VALID);
if (call_update_screen) {
update_screen();
@@ -641,7 +652,7 @@ static void init_ccline(int firstc, int indent)
/// @param count only used for incremental search
/// @param indent indent for inside conditionals
/// @param clear_ccline clear ccline first
-static uint8_t *command_line_enter(int firstc, long count, int indent, bool clear_ccline)
+static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear_ccline)
{
// can be invoked recursively, identify each level
static int cmdline_level = 0;
@@ -680,11 +691,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
s->break_ctrl_c = true;
}
- // start without Hebrew mapping for a command line
- if (s->firstc == ':' || s->firstc == '=' || s->firstc == '>') {
- cmd_hkmap = 0;
- }
-
init_ccline(s->firstc, s->indent);
ccline.prompt_id = last_prompt_id++;
ccline.level = cmdline_level;
@@ -698,12 +704,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
ExpandInit(&s->xpc);
ccline.xpc = &s->xpc;
- if (curwin->w_p_rl && *curwin->w_p_rlc == 's'
- && (s->firstc == '/' || s->firstc == '?')) {
- cmdmsg_rl = true;
- } else {
- cmdmsg_rl = false;
- }
+ cmdmsg_rl = (curwin->w_p_rl && *curwin->w_p_rlc == 's'
+ && (s->firstc == '/' || s->firstc == '?'));
msg_grid_validate();
@@ -741,7 +743,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
} else {
s->b_im_ptr = &curbuf->b_p_imsearch;
}
-
+ s->b_im_ptr_buf = curbuf;
if (*s->b_im_ptr == B_IMODE_LMAP) {
State |= MODE_LANGMAP;
}
@@ -795,7 +797,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
// Redraw the statusline in case it uses the current mode using the mode()
// function.
- if (!cmd_silent) {
+ if (!cmd_silent && !exmode_active) {
bool found_one = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
@@ -844,6 +846,16 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool clea
cmdmsg_rl = false;
+ // We could have reached here without having a chance to clean up wild menu
+ // if certain special keys like <Esc> or <C-\> were used as wildchar. Make
+ // sure to still clean up to avoid memory corruption.
+ if (cmdline_pum_active()) {
+ cmdline_pum_remove();
+ }
+ wildmenu_cleanup(&ccline);
+ s->did_wild_list = false;
+ s->wim_index = 0;
+
ExpandCleanup(&s->xpc);
ccline.xpc = NULL;
@@ -914,6 +926,9 @@ theend:
ui_call_cmdline_hide(ccline.level);
msg_ext_clear_later();
}
+ if (!cmd_silent) {
+ status_redraw_all(); // redraw to show mode change
+ }
cmdline_level--;
@@ -928,6 +943,8 @@ theend:
static int command_line_check(VimState *state)
{
+ CommandLineState *s = (CommandLineState *)state;
+
redir_off = true; // Don't redirect the typed command.
// Repeated, because a ":redir" inside
// completion may switch it on.
@@ -937,6 +954,9 @@ static int command_line_check(VimState *state)
// that occurs while typing a command should
// cause the command not to be executed.
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(s->xpc.xp_numfiles <= 0);
+
cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
return 1;
@@ -1142,7 +1162,7 @@ static int command_line_execute(VimState *state, int key)
} else if (s->c == K_COMMAND) {
do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT);
} else {
- map_execute_lua();
+ map_execute_lua(false);
}
// nvim_select_popupmenu_item() can be called from the handling of
@@ -1166,9 +1186,6 @@ static int command_line_execute(VimState *state, int key)
if (KeyTyped) {
s->some_key_typed = true;
- if (cmd_hkmap) {
- s->c = hkmap(s->c);
- }
if (cmdmsg_rl && !KeyStuffed) {
// Invert horizontal movements and operations. Only when
@@ -1225,13 +1242,14 @@ static int command_line_execute(VimState *state, int key)
s->c = wildmenu_translate_key(&ccline, s->c, &s->xpc, s->did_wild_list);
}
- if (cmdline_pum_active() || s->did_wild_list) {
+ int wild_type = 0;
+ const bool key_is_wc = (s->c == p_wc && KeyTyped) || s->c == p_wcm;
+ if ((cmdline_pum_active() || s->did_wild_list) && !key_is_wc) {
// Ctrl-Y: Accept the current selection and close the popup menu.
// Ctrl-E: cancel the cmdline popup menu and return the original text.
if (s->c == Ctrl_E || s->c == Ctrl_Y) {
- const int wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY;
+ wild_type = (s->c == Ctrl_E) ? WILD_CANCEL : WILD_APPLY;
(void)nextwild(&s->xpc, wild_type, WILD_NO_BEEP, s->firstc != '@');
- s->c = Ctrl_E;
}
}
@@ -1240,7 +1258,7 @@ static int command_line_execute(VimState *state, int key)
// 'wildcharm' or Ctrl-N or Ctrl-P or Ctrl-A or Ctrl-L).
// If the popup menu is displayed, then PageDown and PageUp keys are
// also used to navigate the menu.
- bool end_wildmenu = (!(s->c == p_wc && KeyTyped) && s->c != p_wcm && s->c != Ctrl_Z
+ bool end_wildmenu = (!key_is_wc && s->c != Ctrl_Z
&& s->c != Ctrl_N && s->c != Ctrl_P && s->c != Ctrl_A
&& s->c != Ctrl_L);
end_wildmenu = end_wildmenu && (!cmdline_pum_active()
@@ -1307,7 +1325,7 @@ static int command_line_execute(VimState *state, int key)
if (!cmd_silent) {
if (!ui_has(kUICmdline)) {
- cmd_cursor_goto(msg_row, 0);
+ msg_cursor_goto(msg_row, 0);
}
ui_flush();
}
@@ -1344,13 +1362,19 @@ static int command_line_execute(VimState *state, int key)
}
s->do_abbr = true; // default: check for abbreviation
+
+ // If already used to cancel/accept wildmenu, don't process the key further.
+ if (wild_type == WILD_CANCEL || wild_type == WILD_APPLY) {
+ return command_line_not_changed(s);
+ }
+
return command_line_handle_key(s);
}
// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
// or previous match.
// Returns FAIL when calling command_line_not_changed.
-static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_state_T *s,
+static int may_do_command_line_next_incsearch(int firstc, int count, incsearch_state_T *s,
bool next_match)
FUNC_ATTR_NONNULL_ALL
{
@@ -1376,7 +1400,6 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
pos_T t;
char *pat;
int search_flags = SEARCH_NOOF;
- char save;
if (search_delim == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
@@ -1405,7 +1428,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
search_flags += SEARCH_KEEP;
}
emsg_off++;
- save = pat[patlen];
+ char save = pat[patlen];
pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
@@ -1443,7 +1466,7 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_
set_search_match(&s->match_end);
curwin->w_cursor = s->match_start;
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
update_topline(curwin);
validate_cursor();
highlight_match = true;
@@ -1479,10 +1502,8 @@ static int command_line_erase_chars(CommandLineState *s)
}
if (ccline.cmdpos > 0) {
- char *p;
-
int j = ccline.cmdpos;
- p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
+ char *p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j);
if (s->c == Ctrl_W) {
while (p > ccline.cmdbuff && ascii_isspace(*p)) {
@@ -1525,11 +1546,7 @@ static int command_line_erase_chars(CommandLineState *s)
XFREE_CLEAR(ccline.cmdbuff); // no commandline to return
if (!cmd_silent && !ui_has(kUICmdline)) {
- if (cmdmsg_rl) {
- msg_col = Columns;
- } else {
- msg_col = 0;
- }
+ msg_col = 0;
msg_putchar(' '); // delete ':'
}
s->is_state.search_start = s->is_state.save_cursor;
@@ -1543,20 +1560,21 @@ static int command_line_erase_chars(CommandLineState *s)
/// language :lmap mappings and/or Input Method.
static void command_line_toggle_langmap(CommandLineState *s)
{
+ OptInt *b_im_ptr = buf_valid(s->b_im_ptr_buf) ? s->b_im_ptr : NULL;
if (map_to_exists_mode("", MODE_LANGMAP, false)) {
// ":lmap" mappings exists, toggle use of mappings.
State ^= MODE_LANGMAP;
- if (s->b_im_ptr != NULL) {
+ if (b_im_ptr != NULL) {
if (State & MODE_LANGMAP) {
- *s->b_im_ptr = B_IMODE_LMAP;
+ *b_im_ptr = B_IMODE_LMAP;
} else {
- *s->b_im_ptr = B_IMODE_NONE;
+ *b_im_ptr = B_IMODE_NONE;
}
}
}
- if (s->b_im_ptr != NULL) {
- if (s->b_im_ptr == &curbuf->b_p_iminsert) {
+ if (b_im_ptr != NULL) {
+ if (b_im_ptr == &curbuf->b_p_iminsert) {
set_iminsert_global(curbuf);
} else {
set_imsearch_global(curbuf);
@@ -1598,8 +1616,10 @@ static int command_line_insert_reg(CommandLineState *s)
}
}
+ bool literally = false;
if (s->c != ESC) { // use ESC to cancel inserting register
- cmdline_paste(s->c, i == Ctrl_R, false);
+ literally = i == Ctrl_R || is_literal_register(s->c);
+ cmdline_paste(s->c, literally, false);
// When there was a serious error abort getting the
// command line.
@@ -1624,8 +1644,9 @@ static int command_line_insert_reg(CommandLineState *s)
ccline.special_char = NUL;
redrawcmd();
- // The text has been stuffed, the command line didn't change yet.
- return CMDLINE_NOT_CHANGED;
+ // With "literally": the command line has already changed.
+ // Else: the text has been stuffed, but the command line didn't change yet.
+ return literally ? CMDLINE_CHANGED : CMDLINE_NOT_CHANGED;
}
/// Handle the Left and Right mouse clicks in the command-line mode.
@@ -1656,7 +1677,7 @@ static void command_line_left_right_mouse(CommandLineState *s)
static void command_line_next_histidx(CommandLineState *s, bool next_match)
{
int j = (int)strlen(s->lookfor);
- for (;;) {
+ while (true) {
// one step backwards
if (!next_match) {
if (s->hiscnt == get_hislen()) {
@@ -1729,7 +1750,6 @@ static int command_line_browse_history(CommandLineState *s)
if (s->hiscnt != s->save_hiscnt) {
// jumped to other entry
char *p;
- int len = 0;
int old_firstc;
XFREE_CLEAR(ccline.cmdbuff);
@@ -1743,6 +1763,7 @@ static int command_line_browse_history(CommandLineState *s)
if (s->histype == HIST_SEARCH
&& p != s->lookfor
&& (old_firstc = (uint8_t)p[strlen(p) + 1]) != s->firstc) {
+ int len = 0;
// Correct for the separator character used when
// adding the history entry vs the one used now.
// First loop: count length.
@@ -1814,8 +1835,10 @@ static int command_line_handle_key(CommandLineState *s)
case K_INS:
case K_KINS:
ccline.overstrike = !ccline.overstrike;
-
ui_cursor_shape(); // may show different cursor shape
+ may_trigger_modechanged();
+ status_redraw_curbuf();
+ redraw_statuslines();
return command_line_not_changed(s);
case Ctrl_HAT:
@@ -1857,12 +1880,12 @@ static int command_line_handle_key(CommandLineState *s)
case Ctrl_R: // insert register
switch (command_line_insert_reg(s)) {
- case CMDLINE_NOT_CHANGED:
- return command_line_not_changed(s);
case GOTO_NORMAL_MODE:
return 0; // back to cmd mode
- default:
+ case CMDLINE_CHANGED:
return command_line_changed(s);
+ default:
+ return command_line_not_changed(s);
}
case Ctrl_D:
@@ -2015,7 +2038,7 @@ static int command_line_handle_key(CommandLineState *s)
if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) {
break;
}
- return command_line_not_changed(s);
+ return command_line_changed(s);
}
FALLTHROUGH;
@@ -2037,7 +2060,7 @@ static int command_line_handle_key(CommandLineState *s)
if (nextwild(&s->xpc, wild_type, 0, s->firstc != '@') == FAIL) {
break;
}
- return command_line_not_changed(s);
+ return command_line_changed(s);
} else {
switch (command_line_browse_history(s)) {
case CMDLINE_CHANGED:
@@ -2098,7 +2121,6 @@ static int command_line_handle_key(CommandLineState *s)
if (!p_ari) {
break;
}
- cmd_hkmap = !cmd_hkmap;
return command_line_not_changed(s);
default:
@@ -2123,7 +2145,7 @@ static int command_line_handle_key(CommandLineState *s)
// put the character in the command line
if (IS_SPECIAL(s->c) || mod_mask != 0) {
- put_on_cmdline((char *)get_special_key_name(s->c, mod_mask), -1, true);
+ put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true);
} else {
int j = utf_char2bytes(s->c, IObuff);
IObuff[j] = NUL; // exclude composing chars
@@ -2183,7 +2205,7 @@ handle_T cmdpreview_get_bufnr(void)
return cmdpreview_bufnr;
}
-long cmdpreview_get_ns(void)
+int cmdpreview_get_ns(void)
{
return cmdpreview_ns;
}
@@ -2278,9 +2300,51 @@ static void cmdpreview_close_win(void)
}
}
+/// Save the undo state of a buffer for command preview.
+static void cmdpreview_save_undo(CpUndoInfo *cp_undoinfo, buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL
+{
+ cp_undoinfo->save_b_u_synced = buf->b_u_synced;
+ cp_undoinfo->save_b_u_oldhead = buf->b_u_oldhead;
+ cp_undoinfo->save_b_u_newhead = buf->b_u_newhead;
+ cp_undoinfo->save_b_u_curhead = buf->b_u_curhead;
+ cp_undoinfo->save_b_u_numhead = buf->b_u_numhead;
+ cp_undoinfo->save_b_u_seq_last = buf->b_u_seq_last;
+ cp_undoinfo->save_b_u_save_nr_last = buf->b_u_save_nr_last;
+ cp_undoinfo->save_b_u_seq_cur = buf->b_u_seq_cur;
+ cp_undoinfo->save_b_u_time_cur = buf->b_u_time_cur;
+ cp_undoinfo->save_b_u_save_nr_cur = buf->b_u_save_nr_cur;
+ cp_undoinfo->save_b_u_line_ptr = buf->b_u_line_ptr;
+ cp_undoinfo->save_b_u_line_lnum = buf->b_u_line_lnum;
+ cp_undoinfo->save_b_u_line_colnr = buf->b_u_line_colnr;
+}
+
+/// Restore the undo state of a buffer for command preview.
+static void cmdpreview_restore_undo(const CpUndoInfo *cp_undoinfo, buf_T *buf)
+{
+ buf->b_u_oldhead = cp_undoinfo->save_b_u_oldhead;
+ buf->b_u_newhead = cp_undoinfo->save_b_u_newhead;
+ buf->b_u_curhead = cp_undoinfo->save_b_u_curhead;
+ buf->b_u_numhead = cp_undoinfo->save_b_u_numhead;
+ buf->b_u_seq_last = cp_undoinfo->save_b_u_seq_last;
+ buf->b_u_save_nr_last = cp_undoinfo->save_b_u_save_nr_last;
+ buf->b_u_seq_cur = cp_undoinfo->save_b_u_seq_cur;
+ buf->b_u_time_cur = cp_undoinfo->save_b_u_time_cur;
+ buf->b_u_save_nr_cur = cp_undoinfo->save_b_u_save_nr_cur;
+ buf->b_u_line_ptr = cp_undoinfo->save_b_u_line_ptr;
+ buf->b_u_line_lnum = cp_undoinfo->save_b_u_line_lnum;
+ buf->b_u_line_colnr = cp_undoinfo->save_b_u_line_colnr;
+ if (buf->b_u_curhead == NULL) {
+ buf->b_u_synced = cp_undoinfo->save_b_u_synced;
+ }
+}
+
/// Save current state and prepare windows and buffers for command preview.
static void cmdpreview_prepare(CpInfo *cpinfo)
+ FUNC_ATTR_NONNULL_ALL
{
+ Set(ptr_t) saved_bufs = SET_INIT;
+
kv_init(cpinfo->buf_info);
kv_init(cpinfo->win_info);
@@ -2292,20 +2356,19 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
continue;
}
- CpBufInfo cp_bufinfo;
- cp_bufinfo.buf = buf;
+ if (!set_has(ptr_t, &saved_bufs, buf)) {
+ CpBufInfo cp_bufinfo;
+ cp_bufinfo.buf = buf;
+ cp_bufinfo.save_b_p_ul = buf->b_p_ul;
+ cp_bufinfo.save_b_changed = buf->b_changed;
+ cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
+ cmdpreview_save_undo(&cp_bufinfo.undo_info, buf);
+ kv_push(cpinfo->buf_info, cp_bufinfo);
+ set_put(ptr_t, &saved_bufs, buf);
- cp_bufinfo.save_b_u_synced = buf->b_u_synced;
- cp_bufinfo.save_b_u_time_cur = buf->b_u_time_cur;
- cp_bufinfo.save_b_u_seq_cur = buf->b_u_seq_cur;
- cp_bufinfo.save_b_u_newhead = buf->b_u_newhead;
- cp_bufinfo.save_b_p_ul = buf->b_p_ul;
- cp_bufinfo.save_b_changed = buf->b_changed;
- cp_bufinfo.save_changedtick = buf_get_changedtick(buf);
-
- kv_push(cpinfo->buf_info, cp_bufinfo);
-
- buf->b_p_ul = LONG_MAX; // Make sure we can undo all changes
+ u_clearall(buf);
+ buf->b_p_ul = INT_MAX; // Make sure we can undo all changes
+ }
CpWinInfo cp_wininfo;
cp_wininfo.win = win;
@@ -2324,6 +2387,8 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
win->w_p_cuc = false; // Disable 'cursorcolumn' so it doesn't mess up the highlights
}
+ set_destroy(ptr_t, &saved_bufs);
+
cpinfo->save_hls = p_hls;
cpinfo->save_cmdmod = cmdmod;
win_size_save(&cpinfo->save_view);
@@ -2337,8 +2402,9 @@ static void cmdpreview_prepare(CpInfo *cpinfo)
u_sync(true);
}
-// Restore the state of buffers and windows before command preview.
+/// Restore the state of buffers and windows for command preview.
static void cmdpreview_restore_state(CpInfo *cpinfo)
+ FUNC_ATTR_NONNULL_ALL
{
for (size_t i = 0; i < cpinfo->buf_info.size; i++) {
CpBufInfo cp_bufinfo = cpinfo->buf_info.items[i];
@@ -2346,41 +2412,40 @@ static void cmdpreview_restore_state(CpInfo *cpinfo)
buf->b_changed = cp_bufinfo.save_b_changed;
- if (buf->b_u_seq_cur != cp_bufinfo.save_b_u_seq_cur) {
+ // Clear preview highlights.
+ extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
+
+ if (buf->b_u_seq_cur != cp_bufinfo.undo_info.save_b_u_seq_cur) {
int count = 0;
// Calculate how many undo steps are necessary to restore earlier state.
for (u_header_T *uhp = buf->b_u_curhead ? buf->b_u_curhead : buf->b_u_newhead;
- uhp != NULL && uhp->uh_seq > cp_bufinfo.save_b_u_seq_cur;
+ uhp != NULL;
uhp = uhp->uh_next.ptr, ++count) {}
aco_save_T aco;
aucmd_prepbuf(&aco, buf);
+ // Ensure all the entries will be undone
+ if (curbuf->b_u_synced == false) {
+ u_sync(true);
+ }
// Undo invisibly. This also moves the cursor!
if (!u_undo_and_forget(count, false)) {
abort();
}
aucmd_restbuf(&aco);
-
- // Restore newhead. It is meaningless when curhead is valid, but we must
- // restore it so that undotree() is identical before/after the preview.
- buf->b_u_newhead = cp_bufinfo.save_b_u_newhead;
- buf->b_u_time_cur = cp_bufinfo.save_b_u_time_cur;
}
- if (buf->b_u_curhead == NULL) {
- buf->b_u_synced = cp_bufinfo.save_b_u_synced;
- }
+ u_blockfree(buf);
+ cmdpreview_restore_undo(&cp_bufinfo.undo_info, buf);
if (cp_bufinfo.save_changedtick != buf_get_changedtick(buf)) {
buf_set_changedtick(buf, cp_bufinfo.save_changedtick);
}
buf->b_p_ul = cp_bufinfo.save_b_p_ul; // Restore 'undolevels'
-
- // Clear preview highlights.
- extmark_clear(buf, (uint32_t)cmdpreview_ns, 0, 0, MAXLNUM, MAXCOL);
}
+
for (size_t i = 0; i < cpinfo->win_info.size; i++) {
CpWinInfo cp_wininfo = cpinfo->win_info.items[i];
win_T *win = cp_wininfo.win;
@@ -2429,7 +2494,7 @@ static bool cmdpreview_may_show(CommandLineState *s)
// Copy the command line so we can modify it.
int cmdpreview_type = 0;
char *cmdline = xstrdup(ccline.cmdbuff);
- char *errormsg = NULL;
+ const char *errormsg = NULL;
emsg_off++; // Block errors when parsing the command line, and don't update v:errmsg
if (!parse_cmdline(cmdline, &ea, &cmdinfo, &errormsg)) {
emsg_off--;
@@ -2452,8 +2517,8 @@ static bool cmdpreview_may_show(CommandLineState *s)
CpInfo cpinfo;
bool icm_split = *p_icm == 's'; // inccommand=split
- buf_T *cmdpreview_buf;
- win_T *cmdpreview_win;
+ buf_T *cmdpreview_buf = NULL;
+ win_T *cmdpreview_win = NULL;
emsg_silent++; // Block error reporting as the command may be incomplete,
// but still update v:errmsg
@@ -2579,7 +2644,7 @@ static int command_line_changed(CommandLineState *s)
}
}
- if (cmdmsg_rl || (p_arshape && !p_tbidi)) {
+ if (p_arshape && !p_tbidi) {
// Always redraw the whole command line to fix shaping and
// right-left typing. Not efficient, but it works.
// Do it only when there are no characters left to read
@@ -2601,7 +2666,7 @@ static void abandon_cmdline(void)
if (msg_scrolled == 0) {
compute_cmdrow();
}
- msg("");
+ msg("", 0);
redraw_cmdline = true;
}
@@ -2625,7 +2690,7 @@ static void abandon_cmdline(void)
///
/// @param count only used for incremental search
/// @param indent indent for inside conditionals
-char *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED)
+char *getcmdline(int firstc, int count, int indent, bool do_concat FUNC_ATTR_UNUSED)
{
return (char *)command_line_enter(firstc, count, indent, true);
}
@@ -2670,7 +2735,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at
int msg_silent_saved = msg_silent;
msg_silent = 0;
- char *const ret = (char *)command_line_enter(firstc, 1L, 0, false);
+ char *const ret = (char *)command_line_enter(firstc, 1, 0, false);
if (did_save_ccline) {
restore_cmdline(&save_ccline);
@@ -2690,7 +2755,7 @@ char *getcmdline_prompt(const int firstc, const char *const prompt, const int at
/// Read the 'wildmode' option, fill wim_flags[].
int check_opt_wim(void)
{
- char_u new_wim_flags[4];
+ uint8_t new_wim_flags[4];
int i;
int idx = 0;
@@ -2699,6 +2764,7 @@ int check_opt_wim(void)
}
for (char *p = p_wim; *p; p++) {
+ // Note: Keep this in sync with p_wim_values.
for (i = 0; ASCII_ISALPHA(p[i]); i++) {}
if (p[i] != NUL && p[i] != ',' && p[i] != ':') {
return FAIL;
@@ -2746,6 +2812,9 @@ bool text_locked(void)
if (cmdwin_type != 0) {
return true;
}
+ if (expr_map_locked()) {
+ return true;
+ }
return textlock != 0;
}
@@ -2756,7 +2825,7 @@ void text_locked_msg(void)
emsg(_(get_text_locked_msg()));
}
-char *get_text_locked_msg(void)
+const char *get_text_locked_msg(void)
{
if (cmdwin_type != 0) {
return e_cmdwin;
@@ -2866,7 +2935,7 @@ char *getexline(int c, void *cookie, int indent, bool do_concat)
(void)vgetc();
}
- return getcmdline(c, 1L, indent, do_concat);
+ return getcmdline(c, 1, indent, do_concat);
}
bool cmdline_overstrike(void)
@@ -2926,16 +2995,6 @@ void realloc_cmdbuff(int len)
}
}
-static char *arshape_buf = NULL;
-
-#if defined(EXITFREE)
-void free_arshape_buf(void)
-{
- xfree(arshape_buf);
-}
-
-#endif
-
enum { MAX_CB_ERRORS = 1, };
/// Color expression cmdline using built-in expressions parser
@@ -2950,7 +3009,7 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline,
{
ParserLine parser_lines[] = {
{
- .data = (const char *)colored_ccline->cmdbuff,
+ .data = colored_ccline->cmdbuff,
.size = strlen(colored_ccline->cmdbuff),
.allocated = false,
},
@@ -3054,7 +3113,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
bool can_free_cb = false;
TryState tstate;
Error err = ERROR_INIT;
- const char *err_errmsg = (const char *)e_intern2;
+ const char *err_errmsg = e_intern2;
bool dgc_ret = true;
bool tl_ret = true;
@@ -3087,8 +3146,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline)
}
if (colored_ccline->cmdbuff[colored_ccline->cmdlen] != NUL) {
arg_allocated = true;
- arg.vval.v_string = xmemdupz((const char *)colored_ccline->cmdbuff,
- (size_t)colored_ccline->cmdlen);
+ arg.vval.v_string = xmemdupz(colored_ccline->cmdbuff, (size_t)colored_ccline->cmdlen);
}
// msg_start() called by e.g. :echo may shift command-line to the first column
// even though msg_silent is here. Two ways to workaround this problem without
@@ -3207,8 +3265,7 @@ color_cmdline_end:
if (arg_allocated) {
ccline_colors->cmdbuff = arg.vval.v_string;
} else {
- ccline_colors->cmdbuff = xmemdupz((const char *)colored_ccline->cmdbuff,
- (size_t)colored_ccline->cmdlen);
+ ccline_colors->cmdbuff = xmemdupz(colored_ccline->cmdbuff, (size_t)colored_ccline->cmdlen);
}
tv_clear(&tv);
return ret;
@@ -3247,98 +3304,7 @@ static void draw_cmdline(int start, int len)
msg_putchar('*');
i += utfc_ptr2len(ccline.cmdbuff + start + i) - 1;
}
- } else if (p_arshape && !p_tbidi && len > 0) {
- bool do_arabicshape = false;
- int mb_l;
- for (int i = start; i < start + len; i += mb_l) {
- char *p = ccline.cmdbuff + i;
- int u8cc[MAX_MCO];
- int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
- mb_l = utfc_ptr2len_len(p, start + len - i);
- if (ARABIC_CHAR(u8c)) {
- do_arabicshape = true;
- break;
- }
- }
- if (!do_arabicshape) {
- goto draw_cmdline_no_arabicshape;
- }
-
- static size_t buflen = 0;
- assert(len >= 0);
-
- // Do arabic shaping into a temporary buffer. This is very
- // inefficient!
- if ((size_t)len * 2 + 2 > buflen) {
- // Re-allocate the buffer. We keep it around to avoid a lot of
- // alloc()/free() calls.
- xfree(arshape_buf);
- buflen = (size_t)len * 2 + 2;
- arshape_buf = xmalloc(buflen);
- }
-
- int newlen = 0;
- if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) {
- // Prepend a space to draw the leading composing char on.
- arshape_buf[0] = ' ';
- newlen = 1;
- }
-
- int prev_c = 0;
- int prev_c1 = 0;
- for (int i = start; i < start + len; i += mb_l) {
- char *p = ccline.cmdbuff + i;
- int u8cc[MAX_MCO];
- int u8c = utfc_ptr2char_len(p, u8cc, start + len - i);
- mb_l = utfc_ptr2len_len(p, start + len - i);
- if (ARABIC_CHAR(u8c)) {
- int pc;
- int pc1 = 0;
- int nc = 0;
- // Do Arabic shaping.
- if (cmdmsg_rl) {
- // Displaying from right to left.
- pc = prev_c;
- pc1 = prev_c1;
- prev_c1 = u8cc[0];
- if (i + mb_l >= start + len) {
- nc = NUL;
- } else {
- nc = utf_ptr2char(p + mb_l);
- }
- } else {
- // Displaying from left to right.
- if (i + mb_l >= start + len) {
- pc = NUL;
- } else {
- int pcc[MAX_MCO];
-
- pc = utfc_ptr2char_len(p + mb_l, pcc, start + len - i - mb_l);
- pc1 = pcc[0];
- }
- nc = prev_c;
- }
- prev_c = u8c;
-
- u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc);
-
- newlen += utf_char2bytes(u8c, arshape_buf + newlen);
- if (u8cc[0] != 0) {
- newlen += utf_char2bytes(u8cc[0], arshape_buf + newlen);
- if (u8cc[1] != 0) {
- newlen += utf_char2bytes(u8cc[1], arshape_buf + newlen);
- }
- }
- } else {
- prev_c = u8c;
- memmove(arshape_buf + newlen, p, (size_t)mb_l);
- newlen += mb_l;
- }
- }
-
- msg_outtrans_len(arshape_buf, newlen);
} else {
-draw_cmdline_no_arabicshape:
if (kv_size(ccline.last_colors.colors)) {
for (size_t i = 0; i < kv_size(ccline.last_colors.colors); i++) {
CmdlineColorChunk chunk = kv_A(ccline.last_colors.colors, i);
@@ -3346,12 +3312,10 @@ draw_cmdline_no_arabicshape:
continue;
}
const int chunk_start = MAX(chunk.start, start);
- msg_outtrans_len_attr(ccline.cmdbuff + chunk_start,
- chunk.end - chunk_start,
- chunk.attr);
+ msg_outtrans_len(ccline.cmdbuff + chunk_start, chunk.end - chunk_start, chunk.attr);
}
} else {
- msg_outtrans_len(ccline.cmdbuff + start, len);
+ msg_outtrans_len(ccline.cmdbuff + start, len, 0);
}
}
}
@@ -3380,14 +3344,14 @@ static void ui_ext_cmdline_show(CmdlineInfo *line)
ADD_C(item, INTEGER_OBJ(chunk.attr));
assert(chunk.end >= chunk.start);
- ADD_C(item, STRING_OBJ(cbuf_as_string((char *)line->cmdbuff + chunk.start,
+ ADD_C(item, STRING_OBJ(cbuf_as_string(line->cmdbuff + chunk.start,
(size_t)(chunk.end - chunk.start))));
ADD_C(content, ARRAY_OBJ(item));
}
} else {
Array item = arena_array(&arena, 2);
ADD_C(item, INTEGER_OBJ(0));
- ADD_C(item, STRING_OBJ(cstr_as_string((char *)(line->cmdbuff))));
+ ADD_C(item, CSTR_AS_OBJ(line->cmdbuff));
content = arena_array(&arena, 1);
ADD_C(content, ARRAY_OBJ(item));
}
@@ -3410,11 +3374,11 @@ void ui_ext_cmdline_block_append(size_t indent, const char *line)
{
char *buf = xmallocz(indent + strlen(line));
memset(buf, ' ', indent);
- memcpy(buf + indent, line, strlen(line)); // -V575
+ memcpy(buf + indent, line, strlen(line));
Array item = ARRAY_DICT_INIT;
ADD(item, INTEGER_OBJ(0));
- ADD(item, STRING_OBJ(cstr_as_string(buf)));
+ ADD(item, CSTR_AS_OBJ(buf));
Array content = ARRAY_DICT_INIT;
ADD(content, ARRAY_OBJ(item));
ADD(cmdline_block, ARRAY_OBJ(content));
@@ -3530,7 +3494,7 @@ void unputcmdline(void)
// part will be redrawn, otherwise it will not. If this function is called
// twice in a row, then 'redraw' should be false and redrawcmd() should be
// called afterwards.
-void put_on_cmdline(char *str, int len, int redraw)
+void put_on_cmdline(const char *str, int len, int redraw)
{
int i;
int m;
@@ -3676,7 +3640,6 @@ static void restore_cmdline(CmdlineInfo *ccp)
static bool cmdline_paste(int regname, bool literally, bool remcr)
{
char *arg;
- char *p;
bool allocated;
// check for valid regname; also accept special characters for CTRL-R in
@@ -3708,7 +3671,7 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
// When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate
// part of the word.
- p = arg;
+ char *p = arg;
if (p_is && regname == Ctrl_W) {
char *w;
int len;
@@ -3741,19 +3704,17 @@ static bool cmdline_paste(int regname, bool literally, bool remcr)
// When "literally" is true, insert literally.
// When "literally" is false, insert as typed, but don't leave the command
// line.
-void cmdline_paste_str(char *s, int literally)
+void cmdline_paste_str(const char *s, int literally)
{
- int c, cv;
-
if (literally) {
put_on_cmdline(s, -1, true);
} else {
while (*s != NUL) {
- cv = (uint8_t)(*s);
+ int cv = (uint8_t)(*s);
if (cv == Ctrl_V && s[1]) {
s++;
}
- c = mb_cptr2char_adv((const char **)&s);
+ int c = mb_cptr2char_adv(&s);
if (cv == Ctrl_V || c == ESC || c == Ctrl_C
|| c == CAR || c == NL || c == Ctrl_L
|| (c == Ctrl_BSL && *s == Ctrl_N)) {
@@ -3781,8 +3742,6 @@ void redrawcmdline(void)
static void redrawcmdprompt(void)
{
- int i;
-
if (cmd_silent) {
return;
}
@@ -3801,7 +3760,7 @@ static void redrawcmdprompt(void)
ccline.cmdindent--;
}
} else {
- for (i = ccline.cmdindent; i > 0; i--) {
+ for (int i = ccline.cmdindent; i > 0; i--) {
msg_putchar(' ');
}
}
@@ -3821,7 +3780,7 @@ void redrawcmd(void)
// when 'incsearch' is set there may be no command line while redrawing
if (ccline.cmdbuff == NULL) {
- cmd_cursor_goto(cmdline_row, 0);
+ msg_cursor_goto(cmdline_row, 0);
msg_clr_eos();
return;
}
@@ -3884,28 +3843,13 @@ void cursorcmd(void)
return;
}
- if (cmdmsg_rl) {
- msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1));
- msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1;
- if (msg_row <= 0) {
- msg_row = Rows - 1;
- }
- } else {
- msg_row = cmdline_row + (ccline.cmdspos / Columns);
- msg_col = ccline.cmdspos % Columns;
- if (msg_row >= Rows) {
- msg_row = Rows - 1;
- }
+ msg_row = cmdline_row + (ccline.cmdspos / Columns);
+ msg_col = ccline.cmdspos % Columns;
+ if (msg_row >= Rows) {
+ msg_row = Rows - 1;
}
- cmd_cursor_goto(msg_row, msg_col);
-}
-
-static void cmd_cursor_goto(int row, int col)
-{
- ScreenGrid *grid = &msg_grid_adj;
- grid_adjust(&grid, &row, &col);
- ui_grid_cursor_goto(grid->handle, row, col);
+ msg_cursor_goto(msg_row, msg_col);
}
void gotocmdline(bool clr)
@@ -3914,15 +3858,11 @@ void gotocmdline(bool clr)
return;
}
msg_start();
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- } else {
- msg_col = 0; // always start in column 0
- }
+ msg_col = 0; // always start in column 0
if (clr) { // clear the bottom line(s)
msg_clr_eos(); // will reset clear_cmdline
}
- cmd_cursor_goto(cmdline_row, 0);
+ msg_cursor_goto(cmdline_row, 0);
}
// Check the word in front of the cursor for an abbreviation.
@@ -4022,11 +3962,9 @@ void escape_fname(char **pp)
/// If 'orig_pat' starts with "~/", replace the home directory with "~".
void tilde_replace(char *orig_pat, int num_files, char **files)
{
- char *p;
-
if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) {
for (int i = 0; i < num_files; i++) {
- p = home_replace_save(NULL, files[i]);
+ char *p = home_replace_save(NULL, files[i]);
xfree(files[i]);
files[i] = p;
}
@@ -4106,12 +4044,24 @@ static char *get_cmdline_completion(void)
}
set_expand_context(p->xpc);
+ if (p->xpc->xp_context == EXPAND_UNSUCCESSFUL) {
+ return NULL;
+ }
+
char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context);
- if (cmd_compl != NULL) {
- return xstrdup(cmd_compl);
+ if (cmd_compl == NULL) {
+ return NULL;
}
- return NULL;
+ if (p->xpc->xp_context == EXPAND_USER_LIST
+ || p->xpc->xp_context == EXPAND_USER_DEFINED) {
+ size_t buflen = strlen(cmd_compl) + strlen(p->xpc->xp_arg) + 2;
+ char *buffer = xmalloc(buflen);
+ snprintf(buffer, buflen, "%s,%s", cmd_compl, p->xpc->xp_arg);
+ return buffer;
+ }
+
+ return xstrdup(cmd_compl);
}
/// "getcmdcompltype()" function
@@ -4219,7 +4169,8 @@ void f_setcmdline(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
- rettv->vval.v_number = set_cmdline_str(argvars[0].vval.v_string, pos);
+ // Use tv_get_string() to handle a NULL string like an empty string.
+ rettv->vval.v_number = set_cmdline_str(tv_get_string(&argvars[0]), pos);
}
/// "setcmdpos()" function
@@ -4254,18 +4205,28 @@ int get_list_range(char **str, int *num1, int *num2)
*str = skipwhite((*str));
if (**str == '-' || ascii_isdigit(**str)) { // parse "from" part of range
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false, NULL);
*str += len;
+ // overflow
+ if (num > INT_MAX) {
+ return FAIL;
+ }
+
*num1 = (int)num;
first = true;
}
*str = skipwhite((*str));
if (**str == ',') { // parse "to" part of range
*str = skipwhite((*str) + 1);
- vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false);
+ vim_str2nr(*str, NULL, &len, 0, &num, NULL, 0, false, NULL);
if (len > 0) {
- *num2 = (int)num;
*str = skipwhite((*str) + len);
+ // overflow
+ if (num > INT_MAX) {
+ return FAIL;
+ }
+
+ *num2 = (int)num;
} else if (!first) { // no number given at all
return FAIL;
}
@@ -4282,14 +4243,12 @@ void cmdline_init(void)
/// Check value of 'cedit' and set cedit_key.
/// Returns NULL if value is OK, error message otherwise.
-char *check_cedit(void)
+const char *did_set_cedit(optset_T *args)
{
- int n;
-
if (*p_cedit == NUL) {
cedit_key = -1;
} else {
- n = string_to_key(p_cedit);
+ int n = string_to_key(p_cedit);
if (vim_isprintc(n)) {
return e_invarg;
}
@@ -4309,9 +4268,7 @@ static int open_cmdwin(void)
bufref_T old_curbuf;
bufref_T bufref;
win_T *old_curwin = curwin;
- win_T *wp;
int i;
- linenr_T lnum;
garray_T winsizes;
char typestr[2];
int save_restart_edit = restart_edit;
@@ -4351,6 +4308,7 @@ static int open_cmdwin(void)
// Set "cmdwin_type" before any autocommands may mess things up.
cmdwin_type = get_cmdline_type();
cmdwin_level = ccline.level;
+ cmdwin_old_curwin = old_curwin;
// Create empty command-line buffer.
if (buf_open_scratch(0, _("[Command Line]")) == FAIL) {
@@ -4358,10 +4316,11 @@ static int open_cmdwin(void)
win_close(curwin, true, false);
ga_clear(&winsizes);
cmdwin_type = 0;
+ cmdwin_old_curwin = NULL;
return Ctrl_C;
}
// Command-line buffer has bufhidden=wipe, unlike a true "scratch" buffer.
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
curbuf->b_p_ma = true;
curwin->w_p_fen = false;
curwin->w_p_rl = cmdmsg_rl;
@@ -4379,7 +4338,7 @@ static int open_cmdwin(void)
add_map("<Tab>", "<C-X><C-V>", MODE_INSERT, true);
add_map("<Tab>", "a<C-X><C-V>", MODE_NORMAL, true);
}
- set_option_value_give_err("ft", 0L, "vim", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("vim"), OPT_LOCAL);
}
curbuf->b_ro_locked--;
@@ -4392,13 +4351,13 @@ static int open_cmdwin(void)
if (get_hislen() > 0 && histtype != HIST_INVALID) {
i = *get_hisidx(histtype);
if (i >= 0) {
- lnum = 0;
+ linenr_T lnum = 0;
do {
if (++i == get_hislen()) {
i = 0;
}
if (get_histentry(histtype)[i].hisstr != NULL) {
- ml_append(lnum++, get_histentry(histtype)[i].hisstr, (colnr_T)0, false);
+ ml_append(lnum++, get_histentry(histtype)[i].hisstr, 0, false);
}
} while (i != *get_hisidx(histtype));
}
@@ -4410,7 +4369,7 @@ static int open_cmdwin(void)
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
curwin->w_cursor.col = ccline.cmdpos;
changed_line_abv_curs();
- invalidate_botline();
+ invalidate_botline(curwin);
if (ui_has(kUICmdline)) {
ccline.redraw_state = kCmdRedrawNone;
ui_call_cmdline_hide(ccline.level);
@@ -4454,6 +4413,7 @@ static int open_cmdwin(void)
cmdwin_type = 0;
cmdwin_level = 0;
+ cmdwin_old_curwin = NULL;
exmode_active = save_exmode;
@@ -4463,6 +4423,7 @@ static int open_cmdwin(void)
cmdwin_result = Ctrl_C;
emsg(_("E199: Active window or buffer deleted"));
} else {
+ win_T *wp;
// autocmds may abort script processing
if (aborting() && cmdwin_result != K_IGNORE) {
cmdwin_result = Ctrl_C;
@@ -4500,7 +4461,8 @@ static int open_cmdwin(void)
ccline.cmdlen = (int)strlen(ccline.cmdbuff);
ccline.cmdbufflen = ccline.cmdlen + 1;
ccline.cmdpos = curwin->w_cursor.col;
- if (ccline.cmdpos > ccline.cmdlen) {
+ // If the cursor is on the last character, it probably should be after it.
+ if (ccline.cmdpos == ccline.cmdlen - 1 || ccline.cmdpos > ccline.cmdlen) {
ccline.cmdpos = ccline.cmdlen;
}
if (cmdwin_result == K_IGNORE) {
@@ -4569,39 +4531,37 @@ bool is_in_cmdwin(void)
char *script_get(exarg_T *const eap, size_t *const lenp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC
{
- const char *const cmd = (const char *)eap->arg;
+ char *cmd = eap->arg;
if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) {
*lenp = strlen(eap->arg);
return eap->skip ? NULL : xmemdupz(eap->arg, *lenp);
}
+ cmd += 2;
garray_T ga = { .ga_data = NULL, .ga_len = 0 };
+
+ list_T *const l = heredoc_get(eap, cmd, true);
+ if (l == NULL) {
+ return NULL;
+ }
+
if (!eap->skip) {
ga_init(&ga, 1, 0x400);
}
- const char *const end_pattern = (cmd[2] != NUL ? (const char *)skipwhite(cmd + 2) : ".");
- for (;;) {
- char *const theline = eap->getline(eap->cstack->cs_looplevel > 0 ? -1 : NUL, eap->cookie, 0,
- true);
-
- if (theline == NULL || strcmp(end_pattern, theline) == 0) {
- xfree(theline);
- break;
- }
-
+ TV_LIST_ITER_CONST(l, li, {
if (!eap->skip) {
- ga_concat(&ga, theline);
+ ga_concat(&ga, tv_get_string(TV_LIST_ITEM_TV(li)));
ga_append(&ga, '\n');
}
- xfree(theline);
- }
+ });
*lenp = (size_t)ga.ga_len; // Set length without trailing NUL.
if (!eap->skip) {
ga_append(&ga, NUL);
}
+ tv_list_free(l);
return (char *)ga.ga_data;
}
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 61ac4b69c5..93bdd2299f 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -1,15 +1,13 @@
-#ifndef NVIM_EX_GETLN_H
-#define NVIM_EX_GETLN_H
+#pragma once
#include <stdbool.h>
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-
-struct cmdline_info;
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Command-line colors: one chunk
///
@@ -83,4 +81,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif
-#endif // NVIM_EX_GETLN_H
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 3de5e1db52..71c01922bc 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -1,21 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Functions for creating a session file, i.e. implementing:
// :mkexrc
// :mkvimrc
// :mkview
// :mksession
-#include <assert.h>
#include <inttypes.h>
-#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds_defs.h"
@@ -25,20 +20,22 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
-#include "nvim/mark_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -65,11 +62,9 @@ static int put_view_curpos(FILE *fd, const win_T *wp, char *spaces)
static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
{
- int n = 0;
- win_T *wp;
-
if (restore_size && (ssop_flags & SSOP_WINSIZE)) {
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ int n = 0;
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (!ses_do_win(wp)) {
continue;
}
@@ -109,7 +104,6 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin)
/// @return FAIL when writing the commands to "fd" fails.
static int ses_win_rec(FILE *fd, frame_T *fr)
{
- frame_T *frc;
int count = 0;
if (fr->fr_layout == FR_LEAF) {
@@ -118,7 +112,7 @@ static int ses_win_rec(FILE *fd, frame_T *fr)
// Find first frame that's not skipped and then create a window for
// each following one (first frame is already there).
- frc = ses_skipframe(fr->fr_child);
+ frame_T *frc = ses_skipframe(fr->fr_child);
if (frc != NULL) {
while ((frc = ses_skipframe(frc->fr_next)) != NULL) {
// Make window as big as possible so that we have lots of room
@@ -218,14 +212,13 @@ static int ses_do_win(win_T *wp)
static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigned *flagp)
{
char *buf = NULL;
- char *s;
if (fprintf(fd, "%s\n%s\n", cmd, "%argdel") < 0) {
return FAIL;
}
for (int i = 0; i < gap->ga_len; i++) {
// NULL file names are skipped (only happens when out of memory).
- s = alist_name(&((aentry_T *)gap->ga_data)[i]);
+ char *s = alist_name(&((aentry_T *)gap->ga_data)[i]);
if (s != NULL) {
if (fullname) {
buf = xmalloc(MAXPATHL);
@@ -321,18 +314,14 @@ static int ses_put_fname(FILE *fd, char *name, unsigned *flagp)
/// @param current_arg_idx current argument index of the window, use -1 if unknown
static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int current_arg_idx)
{
- win_T *save_curwin;
int f;
- int do_cursor;
int did_next = false;
// Always restore cursor position for ":mksession". For ":mkview" only
// when 'viewoptions' contains "cursor".
- do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
+ int do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR);
- //
// Local argument list.
- //
if (wp->w_alist == &global_alist) {
PUTLINE_FAIL("argglobal");
} else {
@@ -425,22 +414,18 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Local mappings and abbreviations.
- //
if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS))
&& makemap(fd, wp->w_buffer) == FAIL) {
return FAIL;
}
- //
// Local options. Need to go to the window temporarily.
// Store only local values when using ":mkview" and when ":mksession" is
// used and 'sessionoptions' doesn't include "nvim/options".
// Some folding options are always stored when "folds" is included,
// otherwise the folds would not be restored correctly.
- //
- save_curwin = curwin;
+ win_T *save_curwin = curwin;
curwin = wp;
curbuf = curwin->w_buffer;
if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) {
@@ -457,9 +442,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
return FAIL;
}
- //
// Save Folds when 'buftype' is empty and for help files.
- //
if ((*flagp & SSOP_FOLDS)
&& wp->w_buffer->b_ffname != NULL
&& (bt_normal(wp->w_buffer)
@@ -469,9 +452,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Set the cursor after creating folds, since that moves the cursor.
- //
if (do_cursor) {
// Restore the cursor line in the file and relatively in the
// window. Don't use "G", it changes the jumplist.
@@ -522,10 +503,8 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
}
}
- //
// Local directory, if the current flag is not view options or the "curdir"
// option is included.
- //
if (wp->w_localdir != NULL
&& (flagp != &vop_flags || (*flagp & SSOP_CURDIR))) {
if (fputs("lcd ", fd) < 0
@@ -551,12 +530,8 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr
static int makeopens(FILE *fd, char *dirnow)
{
int only_save_windows = true;
- int nr;
int restore_size = true;
- win_T *wp;
- char *sname;
win_T *edited_win = NULL;
- int tabnr;
win_T *tab_firstwin;
frame_T *tab_topframe;
int cur_arg_idx = 0;
@@ -581,13 +556,11 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
// Now a :cd command to the session directory or the current directory
- //
if (ssop_flags & SSOP_SESDIR) {
PUTLINE_FAIL("exe \"cd \" . escape(expand(\"<sfile>:p:h\"), ' ')");
} else if (ssop_flags & SSOP_CURDIR) {
- sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
+ char *sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow);
char *fname_esc = ses_escape_fname(sname, &ssop_flags);
if (fprintf(fd, "cd %s\n", fname_esc) < 0) {
xfree(fname_esc);
@@ -633,7 +606,7 @@ static int makeopens(FILE *fd, char *dirnow)
&& buf->b_p_bl) {
if (fprintf(fd, "badd +%" PRId64 " ",
buf->b_wininfo == NULL
- ? (int64_t)1L
+ ? 1
: (int64_t)buf->b_wininfo->wi_mark.mark.lnum) < 0
|| ses_fname(fd, buf, &ssop_flags, true) == FAIL) {
return FAIL;
@@ -665,16 +638,10 @@ static int makeopens(FILE *fd, char *dirnow)
restore_stal = true;
}
- //
- // For each tab:
- // - Put windows for each tab, when "tabpages" is in 'sessionoptions'.
- // - Don't use goto_tabpage(), it may change CWD and trigger autocommands.
- //
- tab_firstwin = firstwin; // First window in tab page "tabnr".
- tab_topframe = topframe;
if ((ssop_flags & SSOP_TABPAGES)) {
- // Similar to ses_win_rec() below, populate the tab pages first so
- // later local options won't be copied to the new tabs.
+ // "tabpages" is in 'sessionoptions': Similar to ses_win_rec() below,
+ // populate the tab pages first so later local options won't be copied
+ // to the new tabs.
FOR_ALL_TABS(tp) {
// Use `bufhidden=wipe` to remove empty "placeholder" buffers once
// they are not needed. This prevents creating extra buffers (see
@@ -688,15 +655,17 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
}
- for (tabnr = 1;; tabnr++) {
- tabpage_T *tp = find_tabpage(tabnr);
- if (tp == NULL) {
- break; // done all tab pages
- }
+ // Assume "tabpages" is in 'sessionoptions'. If not then we only do
+ // "curtab" and bail out of the loop.
+ FOR_ALL_TABS(tp) {
bool need_tabnext = false;
int cnr = 1;
+ // May repeat putting Windows for each tab, when "tabpages" is in
+ // 'sessionoptions'.
+ // Don't use goto_tabpage(), it may change directory and trigger
+ // autocommands.
if ((ssop_flags & SSOP_TABPAGES)) {
if (tp == curtab) {
tab_firstwin = firstwin;
@@ -705,17 +674,19 @@ static int makeopens(FILE *fd, char *dirnow)
tab_firstwin = tp->tp_firstwin;
tab_topframe = tp->tp_topframe;
}
- if (tabnr > 1) {
+ if (tp != first_tabpage) {
need_tabnext = true;
}
+ } else {
+ tp = curtab;
+ tab_firstwin = firstwin;
+ tab_topframe = topframe;
}
- //
// Before creating the window layout, try loading one file. If this
// is aborted we don't end up with a number of useless windows.
// This may have side effects! (e.g., compressed or network file).
- //
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (ses_do_win(wp)
&& wp->w_buffer->b_ffname != NULL
&& !bt_help(wp->w_buffer)
@@ -753,12 +724,10 @@ static int makeopens(FILE *fd, char *dirnow)
PUTLINE_FAIL("let &splitright = s:save_splitright");
}
- //
// Check if window sizes can be restored (no windows omitted).
// Remember the window number of the current window after restoring.
- //
- nr = 0;
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ int nr = 0;
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (ses_do_win(wp)) {
nr++;
} else {
@@ -794,10 +763,20 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
+ // Restore the tab-local working directory if specified
+ // Do this before the windows, so that the window-local directory can
+ // override the tab-local directory.
+ if ((ssop_flags & SSOP_CURDIR) && tp->tp_localdir != NULL) {
+ if (fputs("tcd ", fd) < 0
+ || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
+ || put_eol(fd) == FAIL) {
+ return FAIL;
+ }
+ did_lcd = true;
+ }
+
// Restore the view of the window (options, file, cursor, etc.).
- //
- for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
+ for (win_T *wp = tab_firstwin; wp != NULL; wp = wp->w_next) {
if (!ses_do_win(wp)) {
continue;
}
@@ -816,31 +795,17 @@ static int makeopens(FILE *fd, char *dirnow)
// "tabedit".
cur_arg_idx = next_arg_idx;
- //
// Restore cursor to the current window if it's not the first one.
- //
if (cnr > 1 && (fprintf(fd, "%dwincmd w\n", cnr) < 0)) {
return FAIL;
}
- //
// Restore window sizes again after jumping around in windows, because
// the current window has a minimum size while others may not.
- //
if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) {
return FAIL;
}
- // Take care of tab-local working directories if applicable
- if (tp->tp_localdir) {
- if (fputs("if exists(':tcd') == 2 | tcd ", fd) < 0
- || ses_put_fname(fd, tp->tp_localdir, &ssop_flags) == FAIL
- || fputs(" | endif\n", fd) < 0) {
- return FAIL;
- }
- did_lcd = true;
- }
-
// Don't continue in another tab page when doing only the current one
// or when at the last tab page.
if (!(ssop_flags & SSOP_TABPAGES)) {
@@ -857,9 +822,7 @@ static int makeopens(FILE *fd, char *dirnow)
return FAIL;
}
- //
// Wipe out an empty unnamed buffer we started in.
- //
if (fprintf(fd, "%s",
"if exists('s:wipebuf') "
"&& len(win_findbuf(s:wipebuf)) == 0 "
@@ -891,9 +854,7 @@ static int makeopens(FILE *fd, char *dirnow)
PUTLINE_FAIL("let &winminwidth = s:save_winminwidth");
}
- //
// Lastly, execute the x.vim file if it exists.
- //
if (fprintf(fd, "%s",
"let s:sx = expand(\"<sfile>:p:r\").\"x.vim\"\n"
"if filereadable(s:sx)\n"
@@ -913,7 +874,7 @@ void ex_loadview(exarg_T *eap)
return;
}
- if (do_source(fname, false, DOSO_NONE) == FAIL) {
+ if (do_source(fname, false, DOSO_NONE, NULL) == FAIL) {
semsg(_(e_notopen), fname);
}
xfree(fname);
@@ -926,12 +887,9 @@ void ex_loadview(exarg_T *eap)
/// - SSOP_SLASH: filenames are written with "/" slash
void ex_mkrc(exarg_T *eap)
{
- FILE *fd;
- int failed = false;
int view_session = false; // :mkview, :mksession
int using_vdir = false; // using 'viewdir'?
char *viewFile = NULL;
- unsigned *flagp;
if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) {
view_session = true;
@@ -965,11 +923,13 @@ void ex_mkrc(exarg_T *eap)
// When using 'viewdir' may have to create the directory.
if (using_vdir && !os_isdir(p_vdir)) {
- vim_mkdir_emsg((const char *)p_vdir, 0755);
+ vim_mkdir_emsg(p_vdir, 0755);
}
- fd = open_exfile(fname, eap->forceit, WRITEBIN);
+ FILE *fd = open_exfile(fname, eap->forceit, WRITEBIN);
if (fd != NULL) {
+ int failed = false;
+ unsigned *flagp;
if (eap->cmdidx == CMD_mkview) {
flagp = &vop_flags;
} else {
@@ -1008,9 +968,8 @@ void ex_mkrc(exarg_T *eap)
char *dirnow; // current directory
dirnow = xmalloc(MAXPATHL);
- //
+
// Change to session file's dir.
- //
if (os_dirname(dirnow, MAXPATHL) == FAIL
|| os_chdir(dirnow) != 0) {
*dirnow = NUL;
@@ -1084,7 +1043,7 @@ void ex_mkrc(exarg_T *eap)
}
/// @return the name of the view file for the current buffer.
-static char *get_view_file(int c)
+static char *get_view_file(char c)
{
if (curbuf->b_ffname == NULL) {
emsg(_(e_noname));
@@ -1123,8 +1082,7 @@ static char *get_view_file(int c)
}
}
*s++ = '=';
- assert(c >= CHAR_MIN && c <= CHAR_MAX);
- *s++ = (char)c;
+ *s++ = c;
xstrlcpy(s, ".vim", 5);
xfree(sname);
diff --git a/src/nvim/ex_session.h b/src/nvim/ex_session.h
index 7ee2894c6e..275f20e4a8 100644
--- a/src/nvim/ex_session.h
+++ b/src/nvim/ex_session.h
@@ -1,12 +1,9 @@
-#ifndef NVIM_EX_SESSION_H
-#define NVIM_EX_SESSION_H
+#pragma once
-#include <stdio.h>
+#include <stdio.h> // IWYU pragma: keep
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_session.h.generated.h"
#endif
-
-#endif // NVIM_EX_SESSION_H
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 3e059bcc6c..d9c1993f32 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Implements extended marks for plugins. Marks sit in a MarkTree
// datastructure which provides both efficient mark insertations/lookups
// and adjustment to text changes. See marktree.c for more details.
@@ -29,91 +26,58 @@
// code for redrawing the line with the deleted decoration.
#include <assert.h>
-#include <sys/types.h>
+#include <stddef.h>
-#include "nvim/buffer.h"
+#include "nvim/api/private/defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/decoration.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/marktree.h"
#include "nvim/memline.h"
-#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/undo.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.c.generated.h"
#endif
-static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put)
-{
- return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put);
-}
-
/// Create or update an extmark
///
/// must not be used during iteration!
void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
- colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity,
- ExtmarkOp op)
+ colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity,
+ bool end_right_gravity, bool no_undo, bool invalidate, Error *err)
{
- uint32_t *ns = buf_ns_ref(buf, ns_id, true);
+ uint32_t *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL);
uint32_t id = idp ? *idp : 0;
- bool decor_full = false;
-
- uint8_t decor_level = kDecorLevelNone; // no decor
- if (decor) {
- if (kv_size(decor->virt_text)
- || kv_size(decor->virt_lines)
- || decor->conceal
- || decor_has_sign(decor)
- || decor->ui_watched
- || decor->spell != kNone) {
- decor_full = true;
- decor = xmemdup(decor, sizeof *decor);
- }
- decor_level = kDecorLevelVisible; // decor affects redraw
- if (kv_size(decor->virt_lines)) {
- decor_level = kDecorLevelVirtLine; // decor affects horizontal size
- }
- }
+ uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext) | decor_flags;
if (id == 0) {
id = ++*ns;
} else {
MarkTreeIter itr[1] = { 0 };
- mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ MTKey old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
if (old_mark.id) {
if (mt_paired(old_mark) || end_row > -1) {
- extmark_del(buf, ns_id, id);
+ extmark_del_id(buf, ns_id, id);
} else {
- // TODO(bfredl): we need to do more if "revising" a decoration mark.
- assert(itr->node);
+ assert(marktree_itr_valid(itr));
if (old_mark.pos.row == row && old_mark.pos.col == col) {
- if (marktree_decor_level(old_mark) > kDecorLevelNone) {
- decor_remove(buf, row, row, old_mark.decor_full);
- old_mark.decor_full = NULL;
- }
- old_mark.flags = 0;
- if (decor_full) {
- old_mark.decor_full = decor;
- } else if (decor) {
- old_mark.hl_id = decor->hl_id;
- // Workaround: the gcc compiler of functionaltest-lua build
- // apparently incapable of handling basic integer constants.
- // This can be underanged as soon as we bump minimal gcc version.
- old_mark.flags = (uint16_t)(old_mark.flags
- | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
- old_mark.priority = decor->priority;
+ if (mt_decor_any(old_mark)) {
+ buf_decor_remove(buf, row, row, mt_decor(old_mark), true);
}
- marktree_revise(buf->b_marktree, itr, decor_level, old_mark);
+
+ // not paired: we can revise in place
+ mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
+ mt_itr_rawkey(itr).flags |= flags;
+ mt_itr_rawkey(itr).decor_data = decor.data;
goto revised;
}
- decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.decor_full);
+ buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, mt_decor(old_mark), true);
marktree_del_itr(buf->b_marktree, itr, false);
}
} else {
@@ -121,40 +85,13 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
}
}
- mtkey_t mark = { { row, col }, ns_id, id, 0,
- mt_flags(right_gravity, decor_level), 0, NULL };
- if (decor_full) {
- mark.decor_full = decor;
- } else if (decor) {
- mark.hl_id = decor->hl_id;
- // workaround: see above
- mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0));
- mark.priority = decor->priority;
- }
+ MTKey mark = { { row, col }, ns_id, id, flags, decor.data };
marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity);
revised:
- if (op != kExtmarkNoUndo) {
- // TODO(bfredl): this doesn't cover all the cases and probably shouldn't
- // be done "prematurely". Any movement in undo history might necessitate
- // adding new marks to old undo headers. add a test case for this (doesn't
- // fail extmark_spec.lua, and it should)
- uint64_t mark_id = mt_lookup_id(ns_id, id, false);
- u_extmark_set(buf, mark_id, row, col);
- }
-
- if (decor) {
- if (kv_size(decor->virt_lines)) {
- buf->b_virt_line_blocks++;
- }
- if (decor_has_sign(decor)) {
- buf->b_signs++;
- }
- if (decor->sign_text) {
- // TODO(lewis6991): smarter invalidation
- buf_signcols_add_check(buf, NULL);
- }
+ if (decor_flags || decor.ext) {
+ buf_put_decor(buf, decor, row, end_row > -1 ? end_row : row);
decor_redraw(buf, row, end_row > -1 ? end_row : row, decor);
}
@@ -166,7 +103,7 @@ revised:
static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
{
MarkTreeIter itr[1] = { 0 };
- mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr);
+ MTKey key = marktree_lookup(buf->b_marktree, mark, itr);
if (key.pos.row == -1) {
return false;
}
@@ -179,33 +116,41 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col)
return true;
}
-/// Remove an extmark
+/// Remove an extmark in "ns_id" by "id"
///
-/// @return 0 on missing id
-bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id)
+/// @return false on missing id
+bool extmark_del_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
MarkTreeIter itr[1] = { 0 };
- mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
- if (!key.id) {
- return false;
+ MTKey key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr);
+ if (key.id) {
+ extmark_del(buf, itr, key, false);
}
- assert(key.pos.row >= 0);
- marktree_del_itr(buf->b_marktree, itr, false);
- mtkey_t key2 = key;
+ return key.id > 0;
+}
+
+/// Remove a (paired) extmark "key" pointed to by "itr"
+void extmark_del(buf_T *buf, MarkTreeIter *itr, MTKey key, bool restore)
+{
+ assert(key.pos.row >= 0);
- if (mt_paired(key)) {
- key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr);
+ MTKey key2 = key;
+ uint64_t other = marktree_del_itr(buf->b_marktree, itr, false);
+ if (other) {
+ key2 = marktree_lookup(buf->b_marktree, other, itr);
assert(key2.pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
+ if (restore) {
+ marktree_itr_get(buf->b_marktree, key.pos.row, key.pos.col, itr);
+ }
}
- if (marktree_decor_level(key) > kDecorLevelNone) {
- decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full);
+ if (mt_decor_any(key)) {
+ buf_decor_remove(buf, key.pos.row, key2.pos.row, mt_decor(key), true);
}
// TODO(bfredl): delete it from current undo header, opportunistically?
- return true;
}
/// Free extmarks in a ns between lines
@@ -216,79 +161,33 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
return false;
}
- bool marks_cleared = false;
-
bool all_ns = (ns_id == 0);
uint32_t *ns = NULL;
if (!all_ns) {
- ns = buf_ns_ref(buf, ns_id, false);
+ ns = map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL);
if (!ns) {
// nothing to do
return false;
}
}
- // the value is either zero or the lnum (row+1) if highlight was present.
- static Map(uint64_t, ssize_t) delete_set = MAP_INIT;
- typedef struct { int row1; } DecorItem;
- static kvec_t(DecorItem) decors;
-
+ bool marks_cleared = false;
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, l_row, l_col, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark),
- false);
- if (del_status) {
- marktree_del_itr(buf->b_marktree, itr, false);
- if (*del_status >= 0) { // we had a decor_id
- DecorItem it = kv_A(decors, *del_status);
- decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
- }
- map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark));
- continue;
- }
-
- assert(mark.ns > 0 && mark.id > 0);
if (mark.ns == ns_id || all_ns) {
marks_cleared = true;
- if (mt_paired(mark)) {
- uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark));
- ssize_t decor_id = -1;
- if (marktree_decor_level(mark) > kDecorLevelNone) {
- // Save the decoration and the first pos. Clear the decoration
- // later when we know the full range.
- decor_id = (ssize_t)kv_size(decors);
- kv_push(decors,
- ((DecorItem) { .row1 = mark.pos.row }));
- }
- map_put(uint64_t, ssize_t)(&delete_set, other, decor_id);
- } else if (mark.decor_full) {
- decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full);
- }
- marktree_del_itr(buf->b_marktree, itr, false);
+ extmark_del(buf, itr, mark, true);
} else {
marktree_itr_next(buf->b_marktree, itr);
}
}
- uint64_t id;
- ssize_t decor_id;
- map_foreach(&delete_set, id, decor_id, {
- mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr);
- assert(itr->node);
- marktree_del_itr(buf->b_marktree, itr, false);
- if (decor_id >= 0) {
- DecorItem it = kv_A(decors, decor_id);
- decor_remove(buf, it.row1, mark.pos.row, mark.decor_full);
- }
- });
- map_clear(uint64_t, ssize_t)(&delete_set);
- kv_size(decors) = 0;
return marks_cleared;
}
@@ -297,19 +196,34 @@ bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_r
///
/// if upper_lnum or upper_col are negative the buffer
/// will be searched to the start, or end
-/// dir can be set to control the order of the array
-/// amount = amount of marks to find or -1 for all
+/// reverse can be set to control the order of the array
+/// amount = amount of marks to find or INT64_MAX for all
ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row,
- colnr_T u_col, int64_t amount, bool reverse)
+ colnr_T u_col, int64_t amount, bool reverse, ExtmarkType type_filter,
+ bool overlap)
{
ExtmarkInfoArray array = KV_INITIAL_VALUE;
MarkTreeIter itr[1];
- // Find all the marks
- marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
- itr, reverse, false, NULL);
+
+ if (overlap) {
+ // Find all the marks overlapping the start position
+ if (!marktree_itr_get_overlap(buf->b_marktree, l_row, l_col, itr)) {
+ return array;
+ }
+
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ push_mark(&array, ns_id, type_filter, pair);
+ }
+ } else {
+ // Find all the marks beginning with the start position
+ marktree_itr_get_ext(buf->b_marktree, MTPos(l_row, l_col),
+ itr, reverse, false, NULL);
+ }
+
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| (mark.pos.row - u_row) * order > 0
|| (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) {
@@ -319,17 +233,8 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_co
goto next_mark;
}
- if (mark.ns == ns_id) {
- mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
- kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns,
- .mark_id = mark.id,
- .row = mark.pos.row, .col = mark.pos.col,
- .end_row = end.pos.row,
- .end_col = end.pos.col,
- .right_gravity = mt_right(mark),
- .end_right_gravity = mt_right(end),
- .decor = get_decor(mark) }));
- }
+ MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
+ push_mark(&array, ns_id, type_filter, mtpair_from(mark, end));
next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
@@ -340,28 +245,36 @@ next_mark:
return array;
}
+static void push_mark(ExtmarkInfoArray *array, uint32_t ns_id, ExtmarkType type_filter, MTPair mark)
+{
+ if (!(ns_id == UINT32_MAX || mark.start.ns == ns_id)) {
+ return;
+ }
+ if (type_filter != kExtmarkNone) {
+ if (!mt_decor_any(mark.start)) {
+ return;
+ }
+ uint16_t type_flags = decor_type_flags(mt_decor(mark.start));
+
+ if (!(type_flags & type_filter)) {
+ return;
+ }
+ }
+
+ kv_push(*array, mark);
+}
+
/// Lookup an extmark by id
-ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
+MTPair extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id)
{
- ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT };
- mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
+ MTKey mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL);
if (!mark.id) {
- return ret;
+ return mtpair_from(mark, mark); // invalid
}
assert(mark.pos.row >= 0);
- mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL);
-
- ret.ns_id = ns_id;
- ret.mark_id = id;
- ret.row = mark.pos.row;
- ret.col = mark.pos.col;
- ret.end_row = end.pos.row;
- ret.end_col = end.pos.col;
- ret.right_gravity = mt_right(mark);
- ret.end_right_gravity = mt_right(end);
- ret.decor = get_decor(mark);
-
- return ret;
+ MTKey end = marktree_get_alt(buf->b_marktree, mark, NULL);
+
+ return mtpair_from(mark, end);
}
/// free extmarks from the buffer
@@ -374,14 +287,14 @@ void extmark_free_all(buf_T *buf)
MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, 0, 0, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0) {
break;
}
- // don't free mark.decor_full twice for a paired mark.
+ // don't free mark.decor twice for a paired mark.
if (!(mt_paired(mark) && mt_end(mark))) {
- decor_free(mark.decor_full);
+ decor_free(mt_decor(mark));
}
marktree_itr_next(buf->b_marktree, itr);
@@ -389,63 +302,65 @@ void extmark_free_all(buf_T *buf)
marktree_clear(buf->b_marktree);
- map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns);
- map_init(uint32_t, uint32_t, buf->b_extmark_ns);
+ map_destroy(uint32_t, buf->b_extmark_ns);
+ *buf->b_extmark_ns = (Map(uint32_t, uint32_t)) MAP_INIT;
}
-/// Save info for undo/redo of set marks
-static void u_extmark_set(buf_T *buf, uint64_t mark, int row, colnr_T col)
-{
- u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
- }
-
- ExtmarkSavePos pos;
- pos.mark = mark;
- pos.old_row = -1;
- pos.old_col = -1;
- pos.row = row;
- pos.col = col;
-
- ExtmarkUndoObject undo = { .type = kExtmarkSavePos,
- .data.savepos = pos };
-
- kv_push(uhp->uh_extmark, undo);
-}
-
-/// copy extmarks data between range
+/// invalidate extmarks between range and copy to undo header
///
-/// useful when we cannot simply reverse the operation. This will do nothing on
-/// redo, enforces correct position when undo.
-void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_col)
+/// copying is useful when we cannot simply reverse the operation. This will do
+/// nothing on redo, enforces correct position when undo.
+void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_col,
+ ExtmarkOp op)
{
u_header_T *uhp = u_force_get_undo_header(buf);
- if (!uhp) {
- return;
- }
-
+ MarkTreeIter itr[1] = { 0 };
ExtmarkUndoObject undo;
- MarkTreeIter itr[1] = { 0 };
marktree_itr_get(buf->b_marktree, (int32_t)l_row, l_col, itr);
while (true) {
- mtkey_t mark = marktree_itr_current(itr);
+ MTKey mark = marktree_itr_current(itr);
if (mark.pos.row < 0
|| mark.pos.row > u_row
|| (mark.pos.row == u_row && mark.pos.col > u_col)) {
break;
}
- ExtmarkSavePos pos;
- pos.mark = mt_lookup_key(mark);
- pos.old_row = mark.pos.row;
- pos.old_col = mark.pos.col;
- pos.row = -1;
- pos.col = -1;
- undo.data.savepos = pos;
- undo.type = kExtmarkSavePos;
- kv_push(uhp->uh_extmark, undo);
+ bool invalidated = false;
+ // Invalidate/delete mark
+ if (!mt_invalid(mark) && mt_invalidate(mark) && !mt_end(mark)) {
+ MTPos endpos = marktree_get_altpos(buf->b_marktree, mark, NULL);
+ if (endpos.row < 0) {
+ endpos = mark.pos;
+ }
+ if ((endpos.col <= u_col || (!u_col && endpos.row == mark.pos.row))
+ && mark.pos.col >= l_col
+ && mark.pos.row >= l_row && endpos.row <= u_row - (u_col ? 0 : 1)) {
+ if (mt_no_undo(mark)) {
+ extmark_del(buf, itr, mark, true);
+ continue;
+ } else {
+ invalidated = true;
+ mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
+ buf_decor_remove(buf, mark.pos.row, endpos.row, mt_decor(mark), false);
+ }
+ }
+ }
+
+ // Push mark to undo header
+ if (uhp && op == kExtmarkUndo && !mt_no_undo(mark)) {
+ ExtmarkSavePos pos;
+ pos.mark = mt_lookup_key(mark);
+ pos.invalidated = invalidated;
+ pos.old_row = mark.pos.row;
+ pos.old_col = mark.pos.col;
+ pos.row = -1;
+ pos.col = -1;
+
+ undo.data.savepos = pos;
+ undo.type = kExtmarkSavePos;
+ kv_push(uhp->uh_extmark, undo);
+ }
marktree_itr_next(buf->b_marktree, itr);
}
@@ -475,6 +390,13 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
} else if (undo_info.type == kExtmarkSavePos) {
ExtmarkSavePos pos = undo_info.data.savepos;
if (undo) {
+ if (pos.invalidated) {
+ MarkTreeIter itr[1] = { 0 };
+ MTKey mark = marktree_lookup(curbuf->b_marktree, pos.mark, itr);
+ mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID;
+ MTPos end = marktree_get_altpos(curbuf->b_marktree, mark, itr);
+ buf_put_decor(curbuf, mt_decor(mark), mark.pos.row, end.row < 0 ? mark.pos.row : end.row);
+ }
if (pos.old_row >= 0) {
extmark_setraw(curbuf, pos.mark, pos.old_row, pos.old_col);
}
@@ -516,7 +438,6 @@ void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
old_row = line2 - line1 + 1;
// TODO(bfredl): ej kasta?
old_byte = (bcount_t)buf->deleted_bytes2;
-
new_row = amount_after + old_row;
} else {
// A region is either deleted (amount == MAXLNUM) or
@@ -557,7 +478,7 @@ void extmark_splice(buf_T *buf, int start_row, colnr_T start_col, int old_row, c
bcount_t old_byte, int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
- long offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
+ int offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
// On empty buffers, when editing the first line, the line is buffered,
// causing offset to be < 0. While the buffer is not actually empty, the
@@ -582,15 +503,29 @@ void extmark_splice_impl(buf_T *buf, int start_row, colnr_T start_col, bcount_t
old_row, old_col, old_byte,
new_row, new_col, new_byte);
- if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
- // Copy marks that would be effected by delete
+ if (old_row > 0 || old_col > 0) {
+ // Copy and invalidate marks that would be effected by delete
// TODO(bfredl): Be "smart" about gravity here, left-gravity at the
// beginning and right-gravity at the end need not be preserved.
// Also be smart about marks that already have been saved (important for
// merge!)
int end_row = start_row + old_row;
int end_col = (old_row ? 0 : start_col) + old_col;
- u_extmark_copy(buf, start_row, start_col, end_row, end_col);
+ extmark_splice_delete(buf, start_row, start_col, end_row, end_col, undo);
+ }
+
+ // Move the signcolumn sentinel line
+ if (buf->b_signs_with_text && buf->b_signcols.sentinel) {
+ linenr_T se_lnum = buf->b_signcols.sentinel;
+ if (se_lnum >= start_row) {
+ if (old_row != 0 && se_lnum > old_row + start_row) {
+ buf->b_signcols.sentinel += new_row - old_row;
+ } else if (new_row == 0) {
+ buf->b_signcols.sentinel = 0;
+ } else {
+ buf->b_signcols.sentinel += new_row;
+ }
+ }
}
marktree_splice(buf->b_marktree, (int32_t)start_row, start_col,
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index 657e99a938..061cd0ed5f 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_EXTMARK_H
-#define NVIM_EXTMARK_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -8,30 +7,15 @@
#include "klib/kvec.h"
#include "nvim/buffer_defs.h"
#include "nvim/decoration.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/macros.h"
+#include "nvim/extmark_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/marktree.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-EXTERN int extmark_splice_pending INIT(= 0);
+EXTERN int extmark_splice_pending INIT( = 0);
-typedef struct {
- uint64_t ns_id;
- uint64_t mark_id;
- int row;
- colnr_T col;
- int end_row;
- colnr_T end_col;
- bool right_gravity;
- bool end_right_gravity;
- Decoration decor; // TODO(bfredl): CHONKY
-} ExtmarkInfo;
-
-typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
-
-// TODO(bfredl): good enough name for now.
-typedef ptrdiff_t bcount_t;
+typedef kvec_t(MTPair) ExtmarkInfoArray;
// delete the columns between mincol and endcol
typedef struct {
@@ -66,6 +50,7 @@ typedef struct {
colnr_T old_col;
int row;
colnr_T col;
+ bool invalidated;
} ExtmarkSavePos;
typedef enum {
@@ -76,6 +61,17 @@ typedef enum {
kExtmarkClear,
} UndoObjectType;
+// TODO(bfredl): if possible unify these with marktree flags,
+// so it is possible to filter extmarks directly on top-level flags
+typedef enum {
+ kExtmarkNone = 0x1,
+ kExtmarkSign = 0x2,
+ kExtmarkSignHL = 0x4,
+ kExtmarkVirtText = 0x8,
+ kExtmarkVirtLines = 0x10,
+ kExtmarkHighlight = 0x20,
+} ExtmarkType;
+
// TODO(bfredl): reduce the number of undo action types
struct undo_object {
UndoObjectType type;
@@ -89,5 +85,3 @@ struct undo_object {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.h.generated.h"
#endif
-
-#endif // NVIM_EXTMARK_H
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index 51b60dcf6d..c5a8684545 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -1,15 +1,10 @@
-#ifndef NVIM_EXTMARK_DEFS_H
-#define NVIM_EXTMARK_DEFS_H
+#pragma once
#include "klib/kvec.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
-typedef struct {
- char *text;
- int hl_id;
-} VirtTextChunk;
-
-typedef kvec_t(VirtTextChunk) VirtText;
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
typedef struct undo_object ExtmarkUndoObject;
typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t;
@@ -22,11 +17,3 @@ typedef enum {
kExtmarkNoUndo, // Operation should not be reversible
kExtmarkUndoNoRedo, // Operation should be undoable, but not redoable
} ExtmarkOp;
-
-typedef enum {
- kDecorLevelNone = 0,
- kDecorLevelVisible = 1,
- kDecorLevelVirtLine = 2,
-} DecorLevel;
-
-#endif // NVIM_EXTMARK_DEFS_H
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index a0435afd65..460cd48fc5 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// File searching functions for 'path', 'tags' and 'cdpath' options.
//
// External visible functions:
@@ -47,31 +44,30 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/file_search.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static char *ff_expand_buffer = NULL; // used for expanding filenames
@@ -117,7 +113,7 @@ typedef struct ff_visited {
FileID file_id;
// The memory for this struct is allocated according to the length of
// ffv_fname.
- char ffv_fname[1]; // actually longer
+ char ffv_fname[];
} ff_visited_T;
// We might have to manage several visited lists during a search.
@@ -182,7 +178,8 @@ typedef struct ff_search_ctx_T {
# include "file_search.c.generated.h"
#endif
-static char e_pathtoolong[] = N_("E854: path too long for completion");
+static const char e_path_too_long_for_completion[]
+ = N_("E854: Path too long for completion");
/// Initialization routine for vim_findfile().
///
@@ -293,7 +290,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
xstrlcpy(ff_expand_buffer, rel_fname, len + 1);
search_ctx->ffsc_start_dir = FullName_save(ff_expand_buffer, false);
} else {
- search_ctx->ffsc_start_dir = xstrnsave(rel_fname, len);
+ search_ctx->ffsc_start_dir = xmemdupz(rel_fname, len);
}
if (*++path != NUL) {
path++;
@@ -395,7 +392,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
len = 0;
while (*wc_part != NUL) {
if (len + 5 >= MAXPATHL) {
- emsg(_(e_pathtoolong));
+ emsg(_(e_path_too_long_for_completion));
break;
}
if (strncmp(wc_part, "**", 2) == 0) {
@@ -438,7 +435,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
// create an absolute path
if (strlen(search_ctx->ffsc_start_dir)
+ strlen(search_ctx->ffsc_fix_path) + 3 >= MAXPATHL) {
- emsg(_(e_pathtoolong));
+ emsg(_(e_path_too_long_for_completion));
goto error_return;
}
STRCPY(ff_expand_buffer, search_ctx->ffsc_start_dir);
@@ -576,9 +573,9 @@ char *vim_findfile(void *search_ctx_arg)
}
// upward search loop
- for (;;) {
+ while (true) {
// downward search loop
- for (;;) {
+ while (true) {
// check if user wants to stop the search
os_breakcheck();
if (got_int) {
@@ -614,7 +611,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Already Searched: %s (%s)",
+ smsg(0, "Already Searched: %s (%s)",
stackp->ffs_fix_path, stackp->ffs_wc_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -625,7 +622,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
} else if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Searching: %s (%s)",
+ smsg(0, "Searching: %s (%s)",
stackp->ffs_fix_path, stackp->ffs_wc_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
@@ -780,7 +777,7 @@ char *vim_findfile(void *search_ctx_arg)
} else {
suf = curbuf->b_p_sua;
}
- for (;;) {
+ while (true) {
// if file exists and we didn't already find it
if ((path_with_url(file_path)
|| (os_path_exists(file_path)
@@ -794,10 +791,10 @@ char *vim_findfile(void *search_ctx_arg)
) {
#ifdef FF_VERBOSE
if (ff_check_visited(&search_ctx->ffsc_visited_list->ffvl_visited_list,
- file_path, (char_u *)"") == FAIL) {
+ file_path, "") == FAIL) {
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("Already: %s", file_path);
+ smsg(0, "Already: %s", file_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -822,7 +819,7 @@ char *vim_findfile(void *search_ctx_arg)
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("HIT: %s", file_path);
+ smsg(0, "HIT: %s", file_path);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -985,7 +982,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("ff_get_visited_list: FOUND list for %s", filename);
+ smsg(0, "ff_get_visited_list: FOUND list for %s", filename);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -999,7 +996,7 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
#ifdef FF_VERBOSE
if (p_verbose >= 5) {
verbose_enter_scroll();
- smsg("ff_get_visited_list: new list for %s", filename);
+ smsg(0, "ff_get_visited_list: new list for %s", filename);
msg_puts("\n"); // don't overwrite this either
verbose_leave_scroll();
}
@@ -1026,8 +1023,6 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char *filename,
static bool ff_wc_equal(char *s1, char *s2)
{
int i, j;
- int c1 = NUL;
- int c2 = NUL;
int prev1 = NUL;
int prev2 = NUL;
@@ -1040,8 +1035,8 @@ static bool ff_wc_equal(char *s1, char *s2)
}
for (i = 0, j = 0; s1[i] != NUL && s2[j] != NUL;) {
- c1 = utf_ptr2char(s1 + i);
- c2 = utf_ptr2char(s2 + j);
+ int c1 = utf_ptr2char(s1 + i);
+ int c2 = utf_ptr2char(s2 + j);
if ((p_fic ? mb_tolower(c1) != mb_tolower(c2) : c1 != c2)
&& (prev1 != '*' || prev2 != '*')) {
@@ -1119,28 +1114,28 @@ static int ff_check_visited(ff_visited_T **visited_list, char *fname, char *wc_p
static ff_stack_T *ff_create_stack_element(char *fix_part, char *wc_part, int level,
int star_star_empty)
{
- ff_stack_T *new = xmalloc(sizeof(ff_stack_T));
+ ff_stack_T *stack = xmalloc(sizeof(ff_stack_T));
- new->ffs_prev = NULL;
- new->ffs_filearray = NULL;
- new->ffs_filearray_size = 0;
- new->ffs_filearray_cur = 0;
- new->ffs_stage = 0;
- new->ffs_level = level;
- new->ffs_star_star_empty = star_star_empty;
+ stack->ffs_prev = NULL;
+ stack->ffs_filearray = NULL;
+ stack->ffs_filearray_size = 0;
+ stack->ffs_filearray_cur = 0;
+ stack->ffs_stage = 0;
+ stack->ffs_level = level;
+ stack->ffs_star_star_empty = star_star_empty;
// the following saves NULL pointer checks in vim_findfile
if (fix_part == NULL) {
fix_part = "";
}
- new->ffs_fix_path = xstrdup(fix_part);
+ stack->ffs_fix_path = xstrdup(fix_part);
if (wc_part == NULL) {
wc_part = "";
}
- new->ffs_wc_path = xstrdup(wc_part);
+ stack->ffs_wc_path = xstrdup(wc_part);
- return new;
+ return stack;
}
/// Push a dir on the directory stack.
@@ -1283,28 +1278,26 @@ static int ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
/// @param len length of file name
/// @param first use count'th matching file name
/// @param rel_fname file name searching relative to
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx state of the search
///
/// @return an allocated string for the file name. NULL for error.
-char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname)
+char *find_file_in_path(char *ptr, size_t len, int options, int first, char *rel_fname,
+ char **file_to_find, char **search_ctx)
{
return find_file_in_path_option(ptr, len, options, first,
(*curbuf->b_p_path == NUL
? p_path
: curbuf->b_p_path),
- FINDFILE_BOTH, rel_fname, curbuf->b_p_sua);
+ FINDFILE_BOTH, rel_fname, curbuf->b_p_sua,
+ file_to_find, search_ctx);
}
-static char *ff_file_to_find = NULL;
-static void *fdip_search_ctx = NULL;
-
#if defined(EXITFREE)
void free_findfile(void)
{
- xfree(ff_file_to_find);
- vim_findfile_cleanup(fdip_search_ctx);
- xfree(ff_expand_buffer);
+ XFREE_CLEAR(ff_expand_buffer);
}
-
#endif
/// Find the directory name "ptr[len]" in the path.
@@ -1318,12 +1311,16 @@ void free_findfile(void)
/// @param ptr file name
/// @param len length of file name
/// @param rel_fname file name searching relative to
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx state of the search
///
/// @return an allocated string for the file name. NULL for error.
-char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname)
+char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname,
+ char **file_to_find, char **search_ctx)
{
return find_file_in_path_option(ptr, len, options, true, p_cdpath,
- FINDFILE_DIR, rel_fname, "");
+ FINDFILE_DIR, rel_fname, "",
+ file_to_find, search_ctx);
}
/// @param ptr file name
@@ -1333,9 +1330,13 @@ char *find_directory_in_path(char *ptr, size_t len, int options, char *rel_fname
/// @param find_what FINDFILE_FILE, _DIR or _BOTH
/// @param rel_fname file name we are looking relative to.
/// @param suffixes list of suffixes, 'suffixesadd' option
+/// @param[in,out] file_to_find modified copy of file name
+/// @param[in,out] search_ctx_arg state of the search
char *find_file_in_path_option(char *ptr, size_t len, int options, int first, char *path_option,
- int find_what, char *rel_fname, char *suffixes)
+ int find_what, char *rel_fname, char *suffixes, char **file_to_find,
+ char **search_ctx_arg)
{
+ ff_search_ctx_T **search_ctx = (ff_search_ctx_T **)search_ctx_arg;
static char *dir;
static int did_findfile_init = false;
char save_char;
@@ -1343,7 +1344,7 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
char *buf = NULL;
int rel_to_curdir;
- if (rel_fname != NULL && path_with_url((const char *)rel_fname)) {
+ if (rel_fname != NULL && path_with_url(rel_fname)) {
// Do not attempt to search "relative" to a URL. #6009
rel_fname = NULL;
}
@@ -1359,11 +1360,11 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
expand_env_esc(ptr, NameBuff, MAXPATHL, false, true, NULL);
ptr[len] = save_char;
- xfree(ff_file_to_find);
- ff_file_to_find = xstrdup(NameBuff);
+ xfree(*file_to_find);
+ *file_to_find = xstrdup(NameBuff);
if (options & FNAME_UNESC) {
// Change all "\ " to " ".
- for (ptr = ff_file_to_find; *ptr != NUL; ptr++) {
+ for (ptr = *file_to_find; *ptr != NUL; ptr++) {
if (ptr[0] == '\\' && ptr[1] == ' ') {
memmove(ptr, ptr + 1, strlen(ptr));
}
@@ -1371,51 +1372,51 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
}
}
- rel_to_curdir = (ff_file_to_find[0] == '.'
- && (ff_file_to_find[1] == NUL
- || vim_ispathsep(ff_file_to_find[1])
- || (ff_file_to_find[1] == '.'
- && (ff_file_to_find[2] == NUL
- || vim_ispathsep(ff_file_to_find[2])))));
- if (vim_isAbsName(ff_file_to_find)
+ rel_to_curdir = ((*file_to_find)[0] == '.'
+ && ((*file_to_find)[1] == NUL
+ || vim_ispathsep((*file_to_find)[1])
+ || ((*file_to_find)[1] == '.'
+ && ((*file_to_find)[2] == NUL
+ || vim_ispathsep((*file_to_find)[2])))));
+ if (vim_isAbsName(*file_to_find)
// "..", "../path", "." and "./path": don't use the path_option
|| rel_to_curdir
#if defined(MSWIN)
// handle "\tmp" as absolute path
- || vim_ispathsep(ff_file_to_find[0])
+ || vim_ispathsep((*file_to_find)[0])
// handle "c:name" as absolute path
- || (ff_file_to_find[0] != NUL && ff_file_to_find[1] == ':')
+ || ((*file_to_find)[0] != NUL && (*file_to_find)[1] == ':')
#endif
) {
// Absolute path, no need to use "path_option".
// If this is not a first call, return NULL. We already returned a
// filename on the first call.
if (first == true) {
- if (path_with_url(ff_file_to_find)) {
- file_name = xstrdup(ff_file_to_find);
+ if (path_with_url(*file_to_find)) {
+ file_name = xstrdup(*file_to_find);
goto theend;
}
// When FNAME_REL flag given first use the directory of the file.
// Otherwise or when this fails use the current directory.
for (int run = 1; run <= 2; run++) {
- size_t l = strlen(ff_file_to_find);
+ size_t l = strlen(*file_to_find);
if (run == 1
&& rel_to_curdir
&& (options & FNAME_REL)
&& rel_fname != NULL
&& strlen(rel_fname) + l < MAXPATHL) {
STRCPY(NameBuff, rel_fname);
- STRCPY(path_tail(NameBuff), ff_file_to_find);
+ STRCPY(path_tail(NameBuff), *file_to_find);
l = strlen(NameBuff);
} else {
- STRCPY(NameBuff, ff_file_to_find);
+ STRCPY(NameBuff, *file_to_find);
run = 2;
}
// When the file doesn't exist, try adding parts of 'suffixesadd'.
buf = suffixes;
- for (;;) {
+ while (true) {
if ((os_path_exists(NameBuff)
&& (find_what == FINDFILE_BOTH
|| ((find_what == FINDFILE_DIR)
@@ -1437,14 +1438,14 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
// Otherwise continue to find the next match.
if (first == true) {
// vim_findfile_free_visited can handle a possible NULL pointer
- vim_findfile_free_visited(fdip_search_ctx);
+ vim_findfile_free_visited(*search_ctx);
dir = path_option;
did_findfile_init = false;
}
- for (;;) {
+ while (true) {
if (did_findfile_init) {
- file_name = vim_findfile(fdip_search_ctx);
+ file_name = vim_findfile(*search_ctx);
if (file_name != NULL) {
break;
}
@@ -1455,8 +1456,8 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
if (dir == NULL || *dir == NUL) {
// We searched all paths of the option, now we can free the search context.
- vim_findfile_cleanup(fdip_search_ctx);
- fdip_search_ctx = NULL;
+ vim_findfile_cleanup(*search_ctx);
+ *search_ctx = NULL;
break;
}
@@ -1468,10 +1469,10 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
// get the stopdir string
r_ptr = vim_findfile_stopdir(buf);
- fdip_search_ctx = vim_findfile_init(buf, ff_file_to_find,
- r_ptr, 100, false, find_what,
- fdip_search_ctx, false, rel_fname);
- if (fdip_search_ctx != NULL) {
+ *search_ctx = vim_findfile_init(buf, *file_to_find,
+ r_ptr, 100, false, find_what,
+ *search_ctx, false, rel_fname);
+ if (*search_ctx != NULL) {
did_findfile_init = true;
}
xfree(buf);
@@ -1481,19 +1482,15 @@ char *find_file_in_path_option(char *ptr, size_t len, int options, int first, ch
if (file_name == NULL && (options & FNAME_MESS)) {
if (first == true) {
if (find_what == FINDFILE_DIR) {
- semsg(_("E344: Can't find directory \"%s\" in cdpath"),
- ff_file_to_find);
+ semsg(_("E344: Can't find directory \"%s\" in cdpath"), *file_to_find);
} else {
- semsg(_("E345: Can't find file \"%s\" in path"),
- ff_file_to_find);
+ semsg(_("E345: Can't find file \"%s\" in path"), *file_to_find);
}
} else {
if (find_what == FINDFILE_DIR) {
- semsg(_("E346: No more directory \"%s\" found in cdpath"),
- ff_file_to_find);
+ semsg(_("E346: No more directory \"%s\" found in cdpath"), *file_to_find);
} else {
- semsg(_("E347: No more file \"%s\" found in path"),
- ff_file_to_find);
+ semsg(_("E347: No more file \"%s\" found in path"), *file_to_find);
}
}
}
@@ -1547,7 +1544,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre
} else {
tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
}
- tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
+ tv_dict_add_str(dict, S_LEN("scope"), buf);
tv_dict_add_bool(dict, S_LEN("changed_window"), cause == kCdCauseWindow);
tv_dict_set_keys_readonly(dict);
@@ -1608,8 +1605,12 @@ int vim_chdirfile(char *fname, CdCause cause)
/// Change directory to "new_dir". Search 'cdpath' for relative directory names.
int vim_chdir(char *new_dir)
{
- char *dir_name = find_directory_in_path(new_dir, strlen(new_dir),
- FNAME_MESS, curbuf->b_ffname);
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+ char *dir_name = find_directory_in_path(new_dir, strlen(new_dir), FNAME_MESS,
+ curbuf->b_ffname, &file_to_find, &search_ctx);
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
if (dir_name == NULL) {
return -1;
}
diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h
index b69a6fa154..d4b5c5d352 100644
--- a/src/nvim/file_search.h
+++ b/src/nvim/file_search.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_FILE_SEARCH_H
-#define NVIM_FILE_SEARCH_H
+#pragma once
#include <stdlib.h>
#include "nvim/globals.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Flags for find_file_*() functions.
#define FINDFILE_FILE 0 // only files
@@ -14,4 +13,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "file_search.h.generated.h"
#endif
-#endif // NVIM_FILE_SEARCH_H
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index fbb5c4f1fa..0bb664bcf5 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// fileio.c: read from and write to a file
#include <assert.h>
@@ -10,13 +7,16 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <time.h>
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,19 +27,20 @@
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds.h"
+#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/iconv.h"
-#include "nvim/input.h"
+#include "nvim/highlight.h"
+#include "nvim/iconv_defs.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
#include "nvim/memline.h"
@@ -47,24 +48,30 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/fs_defs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/sha256.h"
#include "nvim/shada.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/charset.h"
+#endif
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
# include <dirent.h>
# include <sys/file.h>
#endif
@@ -73,87 +80,48 @@
# include "nvim/charset.h"
#endif
-#define BUFSIZE 8192 // size of normal write buffer
-#define SMBUFSIZE 256 // size of emergency write buffer
-
// For compatibility with libuv < 1.20.0 (tested on 1.18.0)
#ifndef UV_FS_COPYFILE_FICLONE
# define UV_FS_COPYFILE_FICLONE 0
#endif
-#define HAS_BW_FLAGS
-enum {
- FIO_LATIN1 = 0x01, // convert Latin1
- FIO_UTF8 = 0x02, // convert UTF-8
- FIO_UCS2 = 0x04, // convert UCS-2
- FIO_UCS4 = 0x08, // convert UCS-4
- FIO_UTF16 = 0x10, // convert UTF-16
- FIO_ENDIAN_L = 0x80, // little endian
- FIO_NOCONVERT = 0x2000, // skip encoding conversion
- FIO_UCSBOM = 0x4000, // check for BOM at start of file
- FIO_ALL = -1, // allow all formats
-};
-
-// When converting, a read() or write() may leave some bytes to be converted
-// for the next call. The value is guessed...
-#define CONV_RESTLEN 30
-
-// We have to guess how much a sequence of bytes may expand when converting
-// with iconv() to be able to allocate a buffer.
-#define ICONV_MULT 8
-
-// Structure to pass arguments from buf_write() to buf_write_bytes().
-struct bw_info {
- int bw_fd; // file descriptor
- char *bw_buf; // buffer with data to be written
- int bw_len; // length of data
-#ifdef HAS_BW_FLAGS
- int bw_flags; // FIO_ flags
-#endif
- char_u bw_rest[CONV_RESTLEN]; // not converted bytes
- int bw_restlen; // nr of bytes in bw_rest[]
- int bw_first; // first write call
- char *bw_conv_buf; // buffer for writing converted chars
- size_t bw_conv_buflen; // size of bw_conv_buf
- int bw_conv_error; // set for conversion error
- linenr_T bw_conv_error_lnum; // first line with error or zero
- linenr_T bw_start_lnum; // line number at start of buffer
- iconv_t bw_iconv_fd; // descriptor for iconv() or -1
-};
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fileio.c.generated.h"
#endif
-static char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
-static char e_no_matching_autocommands_for_buftype_str_buffer[]
- = N_("E676: No matching autocommands for buftype=%s buffer");
+static const char *e_auchangedbuf = N_("E812: Autocommands changed buffer or buffer name");
void filemess(buf_T *buf, char *name, char *s, int attr)
{
- int msg_scroll_save;
+ int prev_msg_col = msg_col;
if (msg_silent != 0) {
return;
}
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)name);
+
+ add_quoted_fname(IObuff, IOSIZE - 100, buf, name);
+
// Avoid an over-long translation to cause trouble.
xstrlcat(IObuff, s, IOSIZE);
+
// For the first message may have to start a new line.
// For further ones overwrite the previous one, reset msg_scroll before
// calling filemess().
- msg_scroll_save = msg_scroll;
- if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) {
+ int msg_scroll_save = msg_scroll;
+ if (shortmess(SHM_OVERALL) && !msg_listdo_overwrite && !exiting && p_verbose == 0) {
msg_scroll = false;
}
if (!msg_scroll) { // wait a bit when overwriting an error msg
- check_for_delay(false);
+ msg_check_for_delay(false);
}
msg_start();
+ if (prev_msg_col != 0 && msg_col == 0) {
+ msg_putchar('\r'); // overwrite any previous message.
+ }
msg_scroll = msg_scroll_save;
msg_scrolled_ign = true;
// may truncate the message to avoid a hit-return prompt
- msg_outtrans_attr(msg_may_trunc(false, IObuff), attr);
+ msg_outtrans(msg_may_trunc(false, IObuff), attr);
msg_clr_eos();
ui_flush();
msg_scrolled_ign = false;
@@ -188,6 +156,7 @@ void filemess(buf_T *buf, char *name, char *s, int attr)
int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
linenr_T lines_to_read, exarg_T *eap, int flags, bool silent)
{
+ int retval = FAIL; // jump to "theend" instead of returning
int fd = stdin_fd >= 0 ? stdin_fd : 0;
int newfile = (flags & READ_NEW);
int check_readonly;
@@ -207,7 +176,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
char *line_start = NULL; // init to shut up gcc
int wasempty; // buffer was empty before reading
colnr_T len;
- long size = 0;
+ ptrdiff_t size = 0;
uint8_t *p = NULL;
off_T filesize = 0;
bool skip_read = false;
@@ -217,7 +186,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
linenr_T linecnt;
bool error = false; // errors encountered
int ff_error = EOL_UNKNOWN; // file format with errors
- long linerest = 0; // remaining chars in line
+ ptrdiff_t linerest = 0; // remaining chars in line
int perm = 0;
#ifdef UNIX
int swap_mode = -1; // protection bits for swap file
@@ -245,7 +214,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
bool fenc_alloced; // fenc_next is in allocated memory
char *fenc_next = NULL; // next item in 'fencs' or NULL
bool advance_fenc = false;
- long real_size = 0;
+ int real_size = 0;
iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1
bool did_iconv = false; // true when iconv() failed and trying
// 'charconvert' next
@@ -275,7 +244,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
&& vim_strchr(p_cpo, CPO_FNAMER) != NULL
&& !(flags & READ_DUMMY)) {
if (set_rw_fname(fname, sfname) == FAIL) {
- return FAIL;
+ goto theend;
}
}
@@ -319,10 +288,9 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (newfile) {
if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname,
false, curbuf, eap)) {
- int status = OK;
-
+ retval = OK;
if (aborting()) {
- status = FAIL;
+ retval = FAIL;
}
// The BufReadCmd code usually uses ":read" to get the text and
@@ -330,14 +298,15 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
// consider this to work like ":edit", thus reset the
// BF_NOTEDITED flag. Then ":write" will work to overwrite the
// same file.
- if (status == OK) {
+ if (retval == OK) {
curbuf->b_flags &= ~BF_NOTEDITED;
}
- return status;
+ goto theend;
}
} else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname,
false, NULL, eap)) {
- return aborting() ? FAIL : OK;
+ retval = aborting() ? FAIL : OK;
+ goto theend;
}
curbuf->b_op_start = orig_start;
@@ -345,11 +314,12 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (flags & READ_NOFILE) {
// Return NOTDONE instead of FAIL so that BufEnter can be triggered
// and other operations don't fail.
- return NOTDONE;
+ retval = NOTDONE;
+ goto theend;
}
}
- if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) {
+ if (((shortmess(SHM_OVER) && !msg_listdo_overwrite) || curbuf->b_help) && p_verbose == 0) {
msg_scroll = false; // overwrite previous file message
} else {
msg_scroll = true; // don't overwrite previous file message
@@ -363,7 +333,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
filemess(curbuf, fname, _("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
- return FAIL;
+ goto theend;
}
// If the name ends in a path separator, we can't open it. Check here,
@@ -375,7 +345,8 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
}
msg_end();
msg_scroll = msg_save;
- return NOTDONE;
+ retval = NOTDONE;
+ goto theend;
}
}
@@ -383,24 +354,30 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
perm = os_getperm(fname);
// On Unix it is possible to read a directory, so we have to
// check for it before os_open().
+
+#ifdef OPEN_CHR_FILES
+# define IS_CHR_DEV(perm, fname) S_ISCHR(perm) && is_dev_fd_file(fname)
+#else
+# define IS_CHR_DEV(perm, fname) false
+#endif
+
if (perm >= 0 && !S_ISREG(perm) // not a regular file ...
&& !S_ISFIFO(perm) // ... or fifo
&& !S_ISSOCK(perm) // ... or socket
-#ifdef OPEN_CHR_FILES
- && !(S_ISCHR(perm) && is_dev_fd_file(fname))
+ && !(IS_CHR_DEV(perm, fname))
// ... or a character special file named /dev/fd/<n>
-#endif
) {
if (S_ISDIR(perm)) {
if (!silent) {
filemess(curbuf, fname, _(msg_is_a_directory), 0);
}
+ retval = NOTDONE;
} else {
filemess(curbuf, fname, _("is not a file"), 0);
}
msg_end();
msg_scroll = msg_save;
- return S_ISDIR(perm) ? NOTDONE : FAIL;
+ goto theend;
}
}
@@ -461,7 +438,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (fd < 0) { // cannot open at all
msg_scroll = msg_save;
if (!newfile) {
- return FAIL;
+ goto theend;
}
if (perm == UV_ENOENT) { // check if the file exists
// Set the 'new-file' flag, so that when the file has
@@ -480,12 +457,12 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
|| (using_b_fname
&& (old_b_fname != curbuf->b_fname))) {
emsg(_(e_auchangedbuf));
- return FAIL;
+ goto theend;
}
}
if (!silent) {
if (dir_of_file_exists(fname)) {
- filemess(curbuf, sfname, new_file_message(), 0);
+ filemess(curbuf, sfname, _("[New]"), 0);
} else {
filemess(curbuf, sfname, _("[New DIRECTORY]"), 0);
}
@@ -502,23 +479,27 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
// remember the current fileformat
save_file_ff(curbuf);
- if (aborting()) { // autocmds may abort script processing
- return FAIL;
+ if (!aborting()) { // autocmds may abort script processing
+ retval = OK; // a new file is not an error
}
- return OK; // a new file is not an error
+ goto theend;
}
- filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]") :
#if defined(UNIX) && defined(EOVERFLOW)
+ filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]")
+ :
// libuv only returns -errno
// in Unix and in Windows
// open() does not set
// EOVERFLOW
- (fd == -EOVERFLOW) ? _("[File too big]") :
+ (fd == -EOVERFLOW) ? _("[File too big]")
+ : _("[Permission Denied]")), 0);
+#else
+ filemess(curbuf, sfname, ((fd == UV_EFBIG) ? _("[File too big]")
+ : _("[Permission Denied]")), 0);
#endif
- _("[Permission Denied]")), 0);
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
// Only set the 'ro' flag for readonly files the first time they are
@@ -553,13 +534,13 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (!read_buffer) {
close(fd);
}
- return FAIL;
+ goto theend;
}
#ifdef UNIX
// Set swap file protection bits after creating it.
if (swap_mode > 0 && curbuf->b_ml.ml_mfp != NULL
&& curbuf->b_ml.ml_mfp->mf_fname != NULL) {
- const char *swap_fname = (const char *)curbuf->b_ml.ml_mfp->mf_fname;
+ const char *swap_fname = curbuf->b_ml.ml_mfp->mf_fname;
// If the group-read bit is set but not the world-read bit, then
// the group must be equal to the group of the original file. If
@@ -588,7 +569,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
if (!read_buffer && !read_stdin) {
close(fd);
}
- return FAIL;
+ goto theend;
}
no_wait_return++; // don't wait for return yet
@@ -644,7 +625,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
no_wait_return--;
msg_scroll = msg_save;
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
// Don't allow the autocommands to change the current buffer.
// Try to re-open the file.
@@ -663,7 +644,7 @@ int readfile(char *fname, char *sfname, linenr_T from, linenr_T lines_to_skip,
emsg(_("E201: *ReadPre autocommands must not change current buffer"));
}
curbuf->b_p_ro = true; // must use "w!" now
- return FAIL;
+ goto theend;
}
}
@@ -740,7 +721,7 @@ retry:
if (read_buffer) {
read_buf_lnum = 1;
read_buf_col = 0;
- } else if (read_stdin || vim_lseek(fd, (off_T)0L, SEEK_SET) != 0) {
+ } else if (read_stdin || vim_lseek(fd, 0, SEEK_SET) != 0) {
// Can't rewind the file, give up.
error = true;
goto failed;
@@ -902,9 +883,9 @@ retry:
// Use buffer >= 64K. Add linerest to double the size if the
// line gets very long, to avoid a lot of copying. But don't
// read more than 1 Mbyte at a time, so we can be interrupted.
- size = 0x10000L + linerest;
- if (size > 0x100000L) {
- size = 0x100000L;
+ size = 0x10000 + linerest;
+ if (size > 0x100000) {
+ size = 0x100000;
}
}
@@ -957,7 +938,7 @@ retry:
if (conv_restlen > 0) {
// Insert unconverted bytes from previous line.
- memmove(ptr, conv_rest, (size_t)conv_restlen); // -V614
+ memmove(ptr, conv_rest, (size_t)conv_restlen);
ptr += conv_restlen;
size -= conv_restlen;
}
@@ -969,11 +950,11 @@ retry:
size = 0;
} else {
int ni;
- long tlen = 0;
- for (;;) {
- p = (char_u *)ml_get(read_buf_lnum) + read_buf_col;
+ int tlen = 0;
+ while (true) {
+ p = (uint8_t *)ml_get(read_buf_lnum) + read_buf_col;
int n = (int)strlen((char *)p);
- if ((int)tlen + n + 1 > size) {
+ if (tlen + n + 1 > size) {
// Filled up to "size", append partial line.
// Change NL to NUL to reverse the effect done
// below.
@@ -1013,7 +994,8 @@ retry:
}
} else {
// Read bytes from the file.
- size = read_eintr(fd, ptr, (size_t)size);
+ size_t read_size = (size_t)size;
+ size = read_eintr(fd, ptr, read_size);
}
if (size <= 0) {
@@ -1079,7 +1061,7 @@ retry:
if (size < 2 || curbuf->b_p_bin) {
ccname = NULL;
} else {
- ccname = check_for_bom(ptr, size, &blen,
+ ccname = check_for_bom(ptr, (int)size, &blen,
fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc));
}
if (ccname != NULL) {
@@ -1168,7 +1150,7 @@ retry:
}
if (fio_flags != 0) {
- unsigned int u8c;
+ unsigned u8c;
char *dest;
char *tail = NULL;
@@ -1347,7 +1329,6 @@ retry:
// Reading UTF-8: Check if the bytes are valid UTF-8.
for (p = (uint8_t *)ptr;; p++) {
int todo = (int)(((uint8_t *)ptr + size) - p);
- int l;
if (todo <= 0) {
break;
@@ -1357,7 +1338,7 @@ retry:
// an incomplete character at the end though, the next
// read() will get the next bytes, we'll check it
// then.
- l = utf_ptr2len_len((char *)p, todo);
+ int l = utf_ptr2len_len((char *)p, todo);
if (l > todo && !incomplete_tail) {
// Avoid retrying with a different encoding when
// a truncated file is more likely, or attempting
@@ -1552,7 +1533,7 @@ rewind_retry:
if (try_unix
&& !read_stdin
&& (read_buffer
- || vim_lseek(fd, (off_T)0L, SEEK_SET) == 0)) {
+ || vim_lseek(fd, 0, SEEK_SET) == 0)) {
fileformat = EOL_UNIX;
if (set_options) {
set_fileformat(EOL_UNIX, OPT_LOCAL);
@@ -1711,50 +1692,51 @@ failed:
}
msg_scroll = msg_save;
check_marks_read();
- return OK; // an interrupt isn't really an error
+ retval = OK; // an interrupt isn't really an error
+ goto theend;
}
if (!filtering && !(flags & READ_DUMMY) && !silent) {
- add_quoted_fname(IObuff, IOSIZE, curbuf, (const char *)sfname);
+ add_quoted_fname(IObuff, IOSIZE, curbuf, sfname);
c = false;
#ifdef UNIX
if (S_ISFIFO(perm)) { // fifo
- STRCAT(IObuff, _("[fifo]"));
+ xstrlcat(IObuff, _("[fifo]"), IOSIZE);
c = true;
}
if (S_ISSOCK(perm)) { // or socket
- STRCAT(IObuff, _("[socket]"));
+ xstrlcat(IObuff, _("[socket]"), IOSIZE);
c = true;
}
# ifdef OPEN_CHR_FILES
if (S_ISCHR(perm)) { // or character special
- STRCAT(IObuff, _("[character special]"));
+ xstrlcat(IObuff, _("[character special]"), IOSIZE);
c = true;
}
# endif
#endif
if (curbuf->b_p_ro) {
- STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"));
+ xstrlcat(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]"), IOSIZE);
c = true;
}
if (read_no_eol_lnum) {
- msg_add_eol();
+ xstrlcat(IObuff, _("[noeol]"), IOSIZE);
c = true;
}
if (ff_error == EOL_DOS) {
- STRCAT(IObuff, _("[CR missing]"));
+ xstrlcat(IObuff, _("[CR missing]"), IOSIZE);
c = true;
}
if (split) {
- STRCAT(IObuff, _("[long lines split]"));
+ xstrlcat(IObuff, _("[long lines split]"), IOSIZE);
c = true;
}
if (notconverted) {
- STRCAT(IObuff, _("[NOT converted]"));
+ xstrlcat(IObuff, _("[NOT converted]"), IOSIZE);
c = true;
} else if (converted) {
- STRCAT(IObuff, _("[converted]"));
+ xstrlcat(IObuff, _("[converted]"), IOSIZE);
c = true;
}
if (conv_error != 0) {
@@ -1766,21 +1748,24 @@ failed:
_("[ILLEGAL BYTE in line %" PRId64 "]"), (int64_t)illegal_byte);
c = true;
} else if (error) {
- STRCAT(IObuff, _("[READ ERRORS]"));
+ xstrlcat(IObuff, _("[READ ERRORS]"), IOSIZE);
c = true;
}
if (msg_add_fileformat(fileformat)) {
c = true;
}
- msg_add_lines(c, (long)linecnt, filesize);
+ msg_add_lines(c, linecnt, filesize);
XFREE_CLEAR(keep_msg);
p = NULL;
msg_scrolled_ign = true;
if (!read_stdin && !read_buffer) {
- p = (char_u *)msg_trunc_attr(IObuff, false, 0);
+ if (msg_col > 0) {
+ msg_putchar('\r'); // overwrite previous message
+ }
+ p = (uint8_t *)msg_trunc(IObuff, false, 0);
}
if (read_stdin || read_buffer || restart_edit != 0
@@ -1802,7 +1787,7 @@ failed:
curbuf->b_p_ro = true;
}
- u_clearline(); // cannot use "U" command after adding lines
+ u_clearline(curbuf); // cannot use "U" command after adding lines
// In Ex mode: cursor at last new line.
// Otherwise: cursor at first new line.
@@ -1811,7 +1796,7 @@ failed:
} else {
curwin->w_cursor.lnum = from + 1;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_WHITE | BL_FIX); // on first non-blank
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
@@ -1841,7 +1826,7 @@ failed:
// When opening a new file locate undo info and read it.
if (read_undo_file) {
- char_u hash[UNDO_HASH_SIZE];
+ uint8_t hash[UNDO_HASH_SIZE];
sha256_finish(&sha_ctx, hash);
u_read_undo(NULL, hash, fname);
@@ -1884,10 +1869,18 @@ failed:
}
}
- if (recoverymode && error) {
- return FAIL;
+ if (!(recoverymode && error)) {
+ retval = OK;
}
- return OK;
+
+theend:
+ if (curbuf->b_ml.ml_mfp != NULL
+ && curbuf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES_NOSYNC) {
+ // OK to sync the swap file now
+ curbuf->b_ml.ml_mfp->mf_dirty = MF_DIRTY_YES;
+ }
+
+ return retval;
}
#ifdef OPEN_CHR_FILES
@@ -1917,11 +1910,8 @@ bool is_dev_fd_file(char *fname)
/// @param endp end of more bytes read
static linenr_T readfile_linenr(linenr_T linecnt, char *p, const char *endp)
{
- char *s;
- linenr_T lnum;
-
- lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
- for (s = p; s < endp; s++) {
+ linenr_T lnum = curbuf->b_ml.ml_line_count - linecnt + 1;
+ for (char *s = p; s < endp; s++) {
if (*s == '\n') {
lnum++;
}
@@ -1989,7 +1979,6 @@ void set_forced_fenc(exarg_T *eap)
static char *next_fenc(char **pp, bool *alloced)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- char *p;
char *r;
*alloced = false;
@@ -1997,12 +1986,12 @@ static char *next_fenc(char **pp, bool *alloced)
*pp = NULL;
return "";
}
- p = vim_strchr(*pp, ',');
+ char *p = vim_strchr(*pp, ',');
if (p == NULL) {
r = enc_canonize(*pp);
*pp += strlen(*pp);
} else {
- r = xstrnsave(*pp, (size_t)(p - *pp));
+ r = xmemdupz(*pp, (size_t)(p - *pp));
*pp = p + 1;
p = enc_canonize(r);
xfree(r);
@@ -2024,10 +2013,9 @@ static char *next_fenc(char **pp, bool *alloced)
/// Returns NULL if the conversion failed ("*fdp" is not set) .
static char *readfile_charconvert(char *fname, char *fenc, int *fdp)
{
- char *tmpname;
char *errmsg = NULL;
- tmpname = vim_tempname();
+ char *tmpname = vim_tempname();
if (tmpname == NULL) {
errmsg = _("Can't find temp file for conversion");
} else {
@@ -2045,7 +2033,7 @@ static char *readfile_charconvert(char *fname, char *fenc, int *fdp)
if (errmsg != NULL) {
// Don't use emsg(), it breaks mappings, the retry with
// another type of conversion might still work.
- msg(errmsg);
+ msg(errmsg, 0);
if (tmpname != NULL) {
os_remove(tmpname); // delete converted file
XFREE_CLEAR(tmpname);
@@ -2074,1515 +2062,9 @@ static void check_marks_read(void)
curbuf->b_marks_read = true;
}
-char *new_file_message(void)
-{
- return shortmess(SHM_NEW) ? _("[New]") : _("[New File]");
-}
-
-/// buf_write() - write to file "fname" lines "start" through "end"
-///
-/// We do our own buffering here because fwrite() is so slow.
-///
-/// If "forceit" is true, we don't care for errors when attempting backups.
-/// In case of an error everything possible is done to restore the original
-/// file. But when "forceit" is true, we risk losing it.
-///
-/// When "reset_changed" is true and "append" == false and "start" == 1 and
-/// "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed.
-///
-/// This function must NOT use NameBuff (because it's called by autowrite()).
-///
-///
-/// @param eap for forced 'ff' and 'fenc', can be NULL!
-/// @param append append to the file
-///
-/// @return FAIL for failure, OK otherwise
-int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T end, exarg_T *eap,
- int append, int forceit, int reset_changed, int filtering)
-{
- int fd;
- char *backup = NULL;
- int backup_copy = false; // copy the original file?
- int dobackup;
- char *ffname;
- char *wfname = NULL; // name of file to write to
- char *s;
- char *ptr;
- char c;
- int len;
- linenr_T lnum;
- long nchars;
-#define SET_ERRMSG_NUM(num, msg) \
- errnum = (num), errmsg = (msg), errmsgarg = 0
-#define SET_ERRMSG_ARG(msg, error) \
- errnum = NULL, errmsg = (msg), errmsgarg = error
-#define SET_ERRMSG(msg) \
- errnum = NULL, errmsg = (msg), errmsgarg = 0
- const char *errnum = NULL;
- char *errmsg = NULL;
- int errmsgarg = 0;
- bool errmsg_allocated = false;
- char *buffer;
- char smallbuf[SMBUFSIZE];
- int bufsize;
- long perm; // file permissions
- int retval = OK;
- int newfile = false; // true if file doesn't exist yet
- int msg_save = msg_scroll;
- int overwriting; // true if writing over original
- int no_eol = false; // no end-of-line written
- int device = false; // writing to a device
- int prev_got_int = got_int;
- int checking_conversion;
- bool file_readonly = false; // overwritten file is read-only
- static char *err_readonly =
- "is read-only (cannot override: \"W\" in 'cpoptions')";
-#if defined(UNIX)
- int made_writable = false; // 'w' bit has been set
-#endif
- // writing everything
- int whole = (start == 1 && end == buf->b_ml.ml_line_count);
- linenr_T old_line_count = buf->b_ml.ml_line_count;
- int fileformat;
- int write_bin;
- struct bw_info write_info; // info for buf_write_bytes()
- int converted = false;
- int notconverted = false;
- char *fenc; // effective 'fileencoding'
- char *fenc_tofree = NULL; // allocated "fenc"
-#ifdef HAS_BW_FLAGS
- int wb_flags = 0;
-#endif
-#ifdef HAVE_ACL
- vim_acl_T acl = NULL; // ACL copied from original file to
- // backup or new file
-#endif
- int write_undo_file = false;
- context_sha256_T sha_ctx;
- unsigned int bkc = get_bkc_value(buf);
- const pos_T orig_start = buf->b_op_start;
- const pos_T orig_end = buf->b_op_end;
-
- if (fname == NULL || *fname == NUL) { // safety check
- return FAIL;
- }
- if (buf->b_ml.ml_mfp == NULL) {
- // This can happen during startup when there is a stray "w" in the
- // vimrc file.
- emsg(_(e_emptybuf));
- return FAIL;
- }
-
- // Disallow writing in secure mode.
- if (check_secure()) {
- return FAIL;
- }
-
- // Avoid a crash for a long name.
- if (strlen(fname) >= MAXPATHL) {
- emsg(_(e_longname));
- return FAIL;
- }
-
- // must init bw_conv_buf and bw_iconv_fd before jumping to "fail"
- write_info.bw_conv_buf = NULL;
- write_info.bw_conv_error = false;
- write_info.bw_conv_error_lnum = 0;
- write_info.bw_restlen = 0;
- write_info.bw_iconv_fd = (iconv_t)-1;
-
- // After writing a file changedtick changes but we don't want to display
- // the line.
- ex_no_reprint = true;
-
- // If there is no file name yet, use the one for the written file.
- // BF_NOTEDITED is set to reflect this (in case the write fails).
- // Don't do this when the write is for a filter command.
- // Don't do this when appending.
- // Only do this when 'cpoptions' contains the 'F' flag.
- if (buf->b_ffname == NULL
- && reset_changed
- && whole
- && buf == curbuf
- && !bt_nofilename(buf)
- && !filtering
- && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL)
- && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) {
- if (set_rw_fname(fname, sfname) == FAIL) {
- return FAIL;
- }
- buf = curbuf; // just in case autocmds made "buf" invalid
- }
-
- if (sfname == NULL) {
- sfname = fname;
- }
-
- // For Unix: Use the short file name whenever possible.
- // Avoids problems with networks and when directory names are changed.
- // Don't do this for Windows, a "cd" in a sub-shell may have moved us to
- // another directory, which we don't detect.
- ffname = fname; // remember full fname
-#ifdef UNIX
- fname = sfname;
-#endif
-
- if (buf->b_ffname != NULL && path_fnamecmp(ffname, buf->b_ffname) == 0) {
- overwriting = true;
- } else {
- overwriting = false;
- }
-
- no_wait_return++; // don't wait for return yet
-
- // Set '[ and '] marks to the lines to be written.
- buf->b_op_start.lnum = start;
- buf->b_op_start.col = 0;
- buf->b_op_end.lnum = end;
- buf->b_op_end.col = 0;
-
- {
- aco_save_T aco;
- int buf_ffname = false;
- int buf_sfname = false;
- int buf_fname_f = false;
- int buf_fname_s = false;
- int did_cmd = false;
- int nofile_err = false;
- int empty_memline = (buf->b_ml.ml_mfp == NULL);
- bufref_T bufref;
-
- // Apply PRE autocommands.
- // Set curbuf to the buffer to be written.
- // Careful: The autocommands may call buf_write() recursively!
- if (ffname == buf->b_ffname) {
- buf_ffname = true;
- }
- if (sfname == buf->b_sfname) {
- buf_sfname = true;
- }
- if (fname == buf->b_ffname) {
- buf_fname_f = true;
- }
- if (fname == buf->b_sfname) {
- buf_fname_s = true;
- }
-
- // Set curwin/curbuf to buf and save a few things.
- aucmd_prepbuf(&aco, buf);
- set_bufref(&bufref, buf);
-
- if (append) {
- if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD,
- sfname, sfname, false, curbuf, eap))) {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_FILEAPPENDPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- } else if (filtering) {
- apply_autocmds_exarg(EVENT_FILTERWRITEPRE,
- NULL, sfname, false, curbuf, eap);
- } else if (reset_changed && whole) {
- int was_changed = curbufIsChanged();
-
- did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD,
- sfname, sfname, false, curbuf, eap);
- if (did_cmd) {
- if (was_changed && !curbufIsChanged()) {
- // Written everything correctly and BufWriteCmd has reset
- // 'modified': Correct the undo information so that an
- // undo now sets 'modified'.
- u_unchanged(curbuf);
- u_update_save_nr(curbuf);
- }
- } else {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_BUFWRITEPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- } else {
- if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD,
- sfname, sfname, false, curbuf, eap))) {
- if (overwriting && bt_nofilename(curbuf)) {
- nofile_err = true;
- } else {
- apply_autocmds_exarg(EVENT_FILEWRITEPRE,
- sfname, sfname, false, curbuf, eap);
- }
- }
- }
-
- // restore curwin/curbuf and a few other things
- aucmd_restbuf(&aco);
-
- // In three situations we return here and don't write the file:
- // 1. the autocommands deleted or unloaded the buffer.
- // 2. The autocommands abort script processing.
- // 3. If one of the "Cmd" autocommands was executed.
- if (!bufref_valid(&bufref)) {
- buf = NULL;
- }
- if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline)
- || did_cmd || nofile_err
- || aborting()) {
- if (buf != NULL && (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
- // restore the original '[ and '] positions
- buf->b_op_start = orig_start;
- buf->b_op_end = orig_end;
- }
-
- no_wait_return--;
- msg_scroll = msg_save;
- if (nofile_err) {
- semsg(_(e_no_matching_autocommands_for_buftype_str_buffer), curbuf->b_p_bt);
- }
-
- if (nofile_err
- || aborting()) {
- // An aborting error, interrupt or exception in the
- // autocommands.
- return FAIL;
- }
- if (did_cmd) {
- if (buf == NULL) {
- // The buffer was deleted. We assume it was written
- // (can't retry anyway).
- return OK;
- }
- if (overwriting) {
- // Assume the buffer was written, update the timestamp.
- ml_timestamp(buf);
- if (append) {
- buf->b_flags &= ~BF_NEW;
- } else {
- buf->b_flags &= ~BF_WRITE_MASK;
- }
- }
- if (reset_changed && buf->b_changed && !append
- && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
- // Buffer still changed, the autocommands didn't work properly.
- return FAIL;
- }
- return OK;
- }
- if (!aborting()) {
- emsg(_("E203: Autocommands deleted or unloaded buffer to be written"));
- }
- return FAIL;
- }
-
- // The autocommands may have changed the number of lines in the file.
- // When writing the whole file, adjust the end.
- // When writing part of the file, assume that the autocommands only
- // changed the number of lines that are to be written (tricky!).
- if (buf->b_ml.ml_line_count != old_line_count) {
- if (whole) { // write all
- end = buf->b_ml.ml_line_count;
- } else if (buf->b_ml.ml_line_count > old_line_count) { // more lines
- end += buf->b_ml.ml_line_count - old_line_count;
- } else { // less lines
- end -= old_line_count - buf->b_ml.ml_line_count;
- if (end < start) {
- no_wait_return--;
- msg_scroll = msg_save;
- emsg(_("E204: Autocommand changed number of lines in unexpected way"));
- return FAIL;
- }
- }
- }
-
- // The autocommands may have changed the name of the buffer, which may
- // be kept in fname, ffname and sfname.
- if (buf_ffname) {
- ffname = buf->b_ffname;
- }
- if (buf_sfname) {
- sfname = buf->b_sfname;
- }
- if (buf_fname_f) {
- fname = buf->b_ffname;
- }
- if (buf_fname_s) {
- fname = buf->b_sfname;
- }
- }
-
- if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
- // restore the original '[ and '] positions
- buf->b_op_start = orig_start;
- buf->b_op_end = orig_end;
- }
-
- if (shortmess(SHM_OVER) && !exiting) {
- msg_scroll = false; // overwrite previous file message
- } else {
- msg_scroll = true; // don't overwrite previous file message
- }
- if (!filtering) {
- filemess(buf,
-#ifndef UNIX
- sfname,
-#else
- fname,
-#endif
- "", 0); // show that we are busy
- }
- msg_scroll = false; // always overwrite the file message now
-
- buffer = verbose_try_malloc(BUFSIZE);
- // can't allocate big buffer, use small one (to be able to write when out of
- // memory)
- if (buffer == NULL) {
- buffer = smallbuf;
- bufsize = SMBUFSIZE;
- } else {
- bufsize = BUFSIZE;
- }
-
- // Get information about original file (if there is one).
- FileInfo file_info_old;
-#if defined(UNIX)
- perm = -1;
- if (!os_fileinfo(fname, &file_info_old)) {
- newfile = true;
- } else {
- perm = (long)file_info_old.stat.st_mode;
- if (!S_ISREG(file_info_old.stat.st_mode)) { // not a file
- if (S_ISDIR(file_info_old.stat.st_mode)) {
- SET_ERRMSG_NUM("E502", _("is a directory"));
- goto fail;
- }
- if (os_nodetype(fname) != NODE_WRITABLE) {
- SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
- goto fail;
- }
- // It's a device of some kind (or a fifo) which we can write to
- // but for which we can't make a backup.
- device = true;
- newfile = true;
- perm = -1;
- }
- }
-#else // win32
- // Check for a writable device name.
- c = fname == NULL ? NODE_OTHER : os_nodetype(fname);
- if (c == NODE_OTHER) {
- SET_ERRMSG_NUM("E503", _("is not a file or writable device"));
- goto fail;
- }
- if (c == NODE_WRITABLE) {
- device = true;
- newfile = true;
- perm = -1;
- } else {
- perm = os_getperm((const char *)fname);
- if (perm < 0) {
- newfile = true;
- } else if (os_isdir(fname)) {
- SET_ERRMSG_NUM("E502", _("is a directory"));
- goto fail;
- }
- if (overwriting) {
- os_fileinfo(fname, &file_info_old);
- }
- }
-#endif // !UNIX
-
- if (!device && !newfile) {
- // Check if the file is really writable (when renaming the file to
- // make a backup we won't discover it later).
- file_readonly = !os_file_is_writable(fname);
-
- if (!forceit && file_readonly) {
- if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
- SET_ERRMSG_NUM("E504", _(err_readonly));
- } else {
- SET_ERRMSG_NUM("E505", _("is read-only (add ! to override)"));
- }
- goto fail;
- }
-
- // If 'forceit' is false, check if the timestamp hasn't changed since reading the file.
- if (overwriting && !forceit) {
- retval = check_mtime(buf, &file_info_old);
- if (retval == FAIL) {
- goto fail;
- }
- }
- }
-
-#ifdef HAVE_ACL
- // For systems that support ACL: get the ACL from the original file.
- if (!newfile) {
- acl = os_get_acl(fname);
- }
-#endif
-
- // If 'backupskip' is not empty, don't make a backup for some files.
- dobackup = (p_wb || p_bk || *p_pm != NUL);
- if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) {
- dobackup = false;
- }
-
- // Save the value of got_int and reset it. We don't want a previous
- // interruption cancel writing, only hitting CTRL-C while writing should
- // abort it.
- prev_got_int = got_int;
- got_int = false;
-
- // Mark the buffer as 'being saved' to prevent changed buffer warnings
- buf->b_saving = true;
-
- // If we are not appending or filtering, the file exists, and the
- // 'writebackup', 'backup' or 'patchmode' option is set, need a backup.
- // When 'patchmode' is set also make a backup when appending.
- //
- // Do not make any backup, if 'writebackup' and 'backup' are both switched
- // off. This helps when editing large files on almost-full disks.
- if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) {
- FileInfo file_info;
- const bool no_prepend_dot = false;
-
- if ((bkc & BKC_YES) || append) { // "yes"
- backup_copy = true;
- } else if ((bkc & BKC_AUTO)) { // "auto"
- // Don't rename the file when:
- // - it's a hard link
- // - it's a symbolic link
- // - we don't have write permission in the directory
- if (os_fileinfo_hardlinks(&file_info_old) > 1
- || !os_fileinfo_link(fname, &file_info)
- || !os_fileinfo_id_equal(&file_info, &file_info_old)) {
- backup_copy = true;
- } else {
- // Check if we can create a file and set the owner/group to
- // the ones from the original file.
- // First find a file name that doesn't exist yet (use some
- // arbitrary numbers).
- STRCPY(IObuff, fname);
- for (int i = 4913;; i += 123) {
- char *tail = path_tail(IObuff);
- size_t size = (size_t)(tail - IObuff);
- snprintf(tail, IOSIZE - size, "%d", i);
- if (!os_fileinfo_link(IObuff, &file_info)) {
- break;
- }
- }
- fd = os_open(IObuff,
- O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, (int)perm);
- if (fd < 0) { // can't write in directory
- backup_copy = true;
- } else {
-#ifdef UNIX
- os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
- if (!os_fileinfo(IObuff, &file_info)
- || file_info.stat.st_uid != file_info_old.stat.st_uid
- || file_info.stat.st_gid != file_info_old.stat.st_gid
- || (long)file_info.stat.st_mode != perm) {
- backup_copy = true;
- }
-#endif
- // Close the file before removing it, on MS-Windows we
- // can't delete an open file.
- close(fd);
- os_remove(IObuff);
- }
- }
- }
-
- // Break symlinks and/or hardlinks if we've been asked to.
- if ((bkc & BKC_BREAKSYMLINK) || (bkc & BKC_BREAKHARDLINK)) {
-#ifdef UNIX
- bool file_info_link_ok = os_fileinfo_link(fname, &file_info);
-
- // Symlinks.
- if ((bkc & BKC_BREAKSYMLINK)
- && file_info_link_ok
- && !os_fileinfo_id_equal(&file_info, &file_info_old)) {
- backup_copy = false;
- }
-
- // Hardlinks.
- if ((bkc & BKC_BREAKHARDLINK)
- && os_fileinfo_hardlinks(&file_info_old) > 1
- && (!file_info_link_ok
- || os_fileinfo_id_equal(&file_info, &file_info_old))) {
- backup_copy = false;
- }
-#endif
- }
-
- // make sure we have a valid backup extension to use
- char *backup_ext = *p_bex == NUL ? ".bak" : p_bex;
-
- if (backup_copy) {
- int some_error = false;
-
- // Try to make the backup in each directory in the 'bdir' option.
- //
- // Unix semantics has it, that we may have a writable file,
- // that cannot be recreated with a simple open(..., O_CREAT, ) e.g:
- // - the directory is not writable,
- // - the file may be a symbolic link,
- // - the file may belong to another user/group, etc.
- //
- // For these reasons, the existing writable file must be truncated
- // and reused. Creation of a backup COPY will be attempted.
- char *dirp = p_bdir;
- while (*dirp) {
- // Isolate one directory name, using an entry in 'bdir'.
- size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
- char *p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
- if (*dirp == NUL && !os_isdir(IObuff)) {
- int ret;
- char *failed_dir;
- if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
- semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
- failed_dir, os_strerror(ret));
- xfree(failed_dir);
- }
- }
- if (trailing_pathseps) {
- // Ends with '//', Use Full path
- if ((p = make_percent_swname(IObuff, fname))
- != NULL) {
- backup = modname(p, backup_ext, no_prepend_dot);
- xfree(p);
- }
- }
-
- char *rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL) {
- some_error = true; // out of memory
- goto nobackup;
- }
-
- FileInfo file_info_new;
- {
- //
- // Make the backup file name.
- //
- if (backup == NULL) {
- backup = modname(rootname, backup_ext, no_prepend_dot);
- }
-
- if (backup == NULL) {
- xfree(rootname);
- some_error = true; // out of memory
- goto nobackup;
- }
-
- // Check if backup file already exists.
- if (os_fileinfo(backup, &file_info_new)) {
- if (os_fileinfo_id_equal(&file_info_new, &file_info_old)) {
- //
- // Backup file is same as original file.
- // May happen when modname() gave the same file back (e.g. silly
- // link). If we don't check here, we either ruin the file when
- // copying or erase it after writing.
- //
- XFREE_CLEAR(backup); // no backup file to delete
- } else if (!p_bk) {
- // We are not going to keep the backup file, so don't
- // delete an existing one, and try to use another name instead.
- // Change one character, just before the extension.
- //
- char *wp = backup + strlen(backup) - 1 - strlen(backup_ext);
- if (wp < backup) { // empty file name ???
- wp = backup;
- }
- *wp = 'z';
- while (*wp > 'a' && os_fileinfo(backup, &file_info_new)) {
- (*wp)--;
- }
- // They all exist??? Must be something wrong.
- if (*wp == 'a') {
- XFREE_CLEAR(backup);
- }
- }
- }
- }
- xfree(rootname);
-
- // Try to create the backup file
- if (backup != NULL) {
- // remove old backup, if present
- os_remove(backup);
-
- // set file protection same as original file, but
- // strip s-bit.
- (void)os_setperm((const char *)backup, perm & 0777);
-
-#ifdef UNIX
- //
- // Try to set the group of the backup same as the original file. If
- // this fails, set the protection bits for the group same as the
- // protection bits for others.
- //
- if (file_info_new.stat.st_gid != file_info_old.stat.st_gid
- && os_chown(backup, (uv_uid_t)-1, (uv_gid_t)file_info_old.stat.st_gid) != 0) {
- os_setperm((const char *)backup,
- ((int)perm & 0707) | (((int)perm & 07) << 3));
- }
-#endif
-
- // copy the file
- if (os_copy(fname, backup, UV_FS_COPYFILE_FICLONE) != 0) {
- SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)"));
- XFREE_CLEAR(backup);
- backup = NULL;
- continue;
- }
-
-#ifdef UNIX
- os_file_settime(backup,
- (double)file_info_old.stat.st_atim.tv_sec,
- (double)file_info_old.stat.st_mtim.tv_sec);
-#endif
-#ifdef HAVE_ACL
- os_set_acl(backup, acl);
-#endif
- SET_ERRMSG(NULL);
- break;
- }
- }
-
-nobackup:
- if (backup == NULL && errmsg == NULL) {
- SET_ERRMSG(_("E509: Cannot create backup file (add ! to override)"));
- }
- // Ignore errors when forceit is true.
- if ((some_error || errmsg != NULL) && !forceit) {
- retval = FAIL;
- goto fail;
- }
- SET_ERRMSG(NULL);
- } else {
- // Make a backup by renaming the original file.
-
- // If 'cpoptions' includes the "W" flag, we don't want to
- // overwrite a read-only file. But rename may be possible
- // anyway, thus we need an extra check here.
- if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) {
- SET_ERRMSG_NUM("E504", _(err_readonly));
- goto fail;
- }
-
- // Form the backup file name - change path/fo.o.h to
- // path/fo.o.h.bak Try all directories in 'backupdir', first one
- // that works is used.
- char *dirp = p_bdir;
- while (*dirp) {
- // Isolate one directory name and make the backup file name.
- size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
- char *p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
- if (*dirp == NUL && !os_isdir(IObuff)) {
- int ret;
- char *failed_dir;
- if ((ret = os_mkdir_recurse(IObuff, 0755, &failed_dir)) != 0) {
- semsg(_("E303: Unable to create directory \"%s\" for backup file: %s"),
- failed_dir, os_strerror(ret));
- xfree(failed_dir);
- }
- }
- if (trailing_pathseps) {
- // path ends with '//', use full path
- if ((p = make_percent_swname(IObuff, fname))
- != NULL) {
- backup = modname(p, backup_ext, no_prepend_dot);
- xfree(p);
- }
- }
-
- if (backup == NULL) {
- char *rootname = get_file_in_dir(fname, IObuff);
- if (rootname == NULL) {
- backup = NULL;
- } else {
- backup = modname(rootname, backup_ext, no_prepend_dot);
- xfree(rootname);
- }
- }
-
- if (backup != NULL) {
- // If we are not going to keep the backup file, don't
- // delete an existing one, try to use another name.
- // Change one character, just before the extension.
- if (!p_bk && os_path_exists(backup)) {
- p = backup + strlen(backup) - 1 - strlen(backup_ext);
- if (p < backup) { // empty file name ???
- p = backup;
- }
- *p = 'z';
- while (*p > 'a' && os_path_exists(backup)) {
- (*p)--;
- }
- // They all exist??? Must be something wrong!
- if (*p == 'a') {
- XFREE_CLEAR(backup);
- }
- }
- }
- if (backup != NULL) {
- // Delete any existing backup and move the current version
- // to the backup. For safety, we don't remove the backup
- // until the write has finished successfully. And if the
- // 'backup' option is set, leave it around.
-
- // If the renaming of the original file to the backup file
- // works, quit here.
- ///
- if (vim_rename(fname, backup) == 0) {
- break;
- }
-
- XFREE_CLEAR(backup); // don't do the rename below
- }
- }
- if (backup == NULL && !forceit) {
- SET_ERRMSG(_("E510: Can't make backup file (add ! to override)"));
- goto fail;
- }
- }
- }
-
-#if defined(UNIX)
- // When using ":w!" and the file was read-only: make it writable
- if (forceit && perm >= 0 && !(perm & 0200)
- && file_info_old.stat.st_uid == getuid()
- && vim_strchr(p_cpo, CPO_FWRITE) == NULL) {
- perm |= 0200;
- (void)os_setperm((const char *)fname, (int)perm);
- made_writable = true;
- }
-#endif
-
- // When using ":w!" and writing to the current file, 'readonly' makes no
- // sense, reset it, unless 'Z' appears in 'cpoptions'.
- if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) {
- buf->b_p_ro = false;
- need_maketitle = true; // set window title later
- status_redraw_all(); // redraw status lines later
- }
-
- if (end > buf->b_ml.ml_line_count) {
- end = buf->b_ml.ml_line_count;
- }
- if (buf->b_ml.ml_flags & ML_EMPTY) {
- start = end + 1;
- }
-
- // If the original file is being overwritten, there is a small chance that
- // we crash in the middle of writing. Therefore the file is preserved now.
- // This makes all block numbers positive so that recovery does not need
- // the original file.
- // Don't do this if there is a backup file and we are exiting.
- if (reset_changed && !newfile && overwriting
- && !(exiting && backup != NULL)) {
- ml_preserve(buf, false, !!p_fs);
- if (got_int) {
- SET_ERRMSG(_(e_interr));
- goto restore_backup;
- }
- }
-
- // Default: write the file directly. May write to a temp file for
- // multi-byte conversion.
- wfname = fname;
-
- // Check for forced 'fileencoding' from "++opt=val" argument.
- if (eap != NULL && eap->force_enc != 0) {
- fenc = eap->cmd + eap->force_enc;
- fenc = enc_canonize(fenc);
- fenc_tofree = fenc;
- } else {
- fenc = buf->b_p_fenc;
- }
-
- // Check if the file needs to be converted.
- converted = need_conversion(fenc);
-
- // Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or
- // Latin1 to Unicode conversion. This is handled in buf_write_bytes().
- // Prepare the flags for it and allocate bw_conv_buf when needed.
- if (converted) {
- wb_flags = get_fio_flags(fenc);
- if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) {
- // Need to allocate a buffer to translate into.
- if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) {
- write_info.bw_conv_buflen = (size_t)bufsize * 2;
- } else { // FIO_UCS4
- write_info.bw_conv_buflen = (size_t)bufsize * 4;
- }
- write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
- if (!write_info.bw_conv_buf) {
- end = 0;
- }
- }
- }
-
- if (converted && wb_flags == 0) {
- // Use iconv() conversion when conversion is needed and it's not done
- // internally.
- write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, "utf-8");
- if (write_info.bw_iconv_fd != (iconv_t)-1) {
- // We're going to use iconv(), allocate a buffer to convert in.
- write_info.bw_conv_buflen = (size_t)bufsize * ICONV_MULT;
- write_info.bw_conv_buf = verbose_try_malloc(write_info.bw_conv_buflen);
- if (!write_info.bw_conv_buf) {
- end = 0;
- }
- write_info.bw_first = true;
- } else {
- // When the file needs to be converted with 'charconvert' after
- // writing, write to a temp file instead and let the conversion
- // overwrite the original file.
- if (*p_ccv != NUL) {
- wfname = vim_tempname();
- if (wfname == NULL) { // Can't write without a tempfile!
- SET_ERRMSG(_("E214: Can't find temp file for writing"));
- goto restore_backup;
- }
- }
- }
- }
-
- if (converted && wb_flags == 0
- && write_info.bw_iconv_fd == (iconv_t)-1
- && wfname == fname) {
- if (!forceit) {
- SET_ERRMSG(_("E213: Cannot convert (add ! to write without conversion)"));
- goto restore_backup;
- }
- notconverted = true;
- }
-
- // If conversion is taking place, we may first pretend to write and check
- // for conversion errors. Then loop again to write for real.
- // When not doing conversion this writes for real right away.
- for (checking_conversion = true;; checking_conversion = false) {
- // There is no need to check conversion when:
- // - there is no conversion
- // - we make a backup file, that can be restored in case of conversion
- // failure.
- if (!converted || dobackup) {
- checking_conversion = false;
- }
-
- if (checking_conversion) {
- // Make sure we don't write anything.
- fd = -1;
- write_info.bw_fd = fd;
- } else {
- // Open the file "wfname" for writing.
- // We may try to open the file twice: If we can't write to the file
- // and forceit is true we delete the existing file and try to
- // create a new one. If this still fails we may have lost the
- // original file! (this may happen when the user reached his
- // quotum for number of files).
- // Appending will fail if the file does not exist and forceit is
- // false.
- while ((fd = os_open(wfname,
- O_WRONLY |
- (append
- ? (forceit
- ? (O_APPEND | O_CREAT)
- : O_APPEND)
- : (O_CREAT | O_TRUNC)),
- perm < 0 ? 0666 : (perm & 0777))) < 0) {
- // A forced write will try to create a new file if the old one
- // is still readonly. This may also happen when the directory
- // is read-only. In that case the mch_remove() will fail.
- if (errmsg == NULL) {
-#ifdef UNIX
- FileInfo file_info;
-
- // Don't delete the file when it's a hard or symbolic link.
- if ((!newfile && os_fileinfo_hardlinks(&file_info_old) > 1)
- || (os_fileinfo_link(fname, &file_info)
- && !os_fileinfo_id_equal(&file_info, &file_info_old))) {
- SET_ERRMSG(_("E166: Can't open linked file for writing"));
- } else {
-#endif
- SET_ERRMSG_ARG(_("E212: Can't open file for writing: %s"), fd);
- if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL
- && perm >= 0) {
-#ifdef UNIX
- // we write to the file, thus it should be marked
- // writable after all
- if (!(perm & 0200)) {
- made_writable = true;
- }
- perm |= 0200;
- if (file_info_old.stat.st_uid != getuid()
- || file_info_old.stat.st_gid != getgid()) {
- perm &= 0777;
- }
-#endif
- if (!append) { // don't remove when appending
- os_remove(wfname);
- }
- continue;
- }
-#ifdef UNIX
- }
-#endif
- }
-
-restore_backup:
- {
- // If we failed to open the file, we don't need a backup. Throw it
- // away. If we moved or removed the original file try to put the
- // backup in its place.
- if (backup != NULL && wfname == fname) {
- if (backup_copy) {
- // There is a small chance that we removed the original,
- // try to move the copy in its place.
- // This may not work if the vim_rename() fails.
- // In that case we leave the copy around.
- // If file does not exist, put the copy in its place
- if (!os_path_exists(fname)) {
- vim_rename(backup, fname);
- }
- // if original file does exist throw away the copy
- if (os_path_exists(fname)) {
- os_remove(backup);
- }
- } else {
- // try to put the original file back
- vim_rename(backup, fname);
- }
- }
-
- // if original file no longer exists give an extra warning
- if (!newfile && !os_path_exists(fname)) {
- end = 0;
- }
- }
-
- if (wfname != fname) {
- xfree(wfname);
- }
- goto fail;
- }
- write_info.bw_fd = fd;
- }
- SET_ERRMSG(NULL);
-
- write_info.bw_buf = buffer;
- nchars = 0;
-
- // use "++bin", "++nobin" or 'binary'
- if (eap != NULL && eap->force_bin != 0) {
- write_bin = (eap->force_bin == FORCE_BIN);
- } else {
- write_bin = buf->b_p_bin;
- }
-
- // Skip the BOM when appending and the file already existed, the BOM
- // only makes sense at the start of the file.
- if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) {
- write_info.bw_len = make_bom((char_u *)buffer, fenc);
- if (write_info.bw_len > 0) {
- // don't convert
- write_info.bw_flags = FIO_NOCONVERT | wb_flags;
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0;
- } else {
- nchars += write_info.bw_len;
- }
- }
- }
- write_info.bw_start_lnum = start;
-
- write_undo_file = (buf->b_p_udf && overwriting && !append
- && !filtering && reset_changed && !checking_conversion);
- if (write_undo_file) {
- // Prepare for computing the hash value of the text.
- sha256_start(&sha_ctx);
- }
-
- write_info.bw_len = bufsize;
-#ifdef HAS_BW_FLAGS
- write_info.bw_flags = wb_flags;
-#endif
- fileformat = get_fileformat_force(buf, eap);
- s = buffer;
- len = 0;
- for (lnum = start; lnum <= end; lnum++) {
- // The next while loop is done once for each character written.
- // Keep it fast!
- ptr = ml_get_buf(buf, lnum, false) - 1;
- if (write_undo_file) {
- sha256_update(&sha_ctx, (uint8_t *)ptr + 1, (uint32_t)(strlen(ptr + 1) + 1));
- }
- while ((c = *++ptr) != NUL) {
- if (c == NL) {
- *s = NUL; // replace newlines with NULs
- } else if (c == CAR && fileformat == EOL_MAC) {
- *s = NL; // Mac: replace CRs with NLs
- } else {
- *s = c;
- }
- s++;
- if (++len != bufsize) {
- continue;
- }
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error: break loop
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
- write_info.bw_start_lnum = lnum;
- }
- // write failed or last line has no EOL: stop here
- if (end == 0
- || (lnum == end
- && (write_bin || !buf->b_p_fixeol)
- && ((write_bin && lnum == buf->b_no_eol_lnum)
- || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) {
- lnum++; // written the line, count it
- no_eol = true;
- break;
- }
- if (fileformat == EOL_UNIX) {
- *s++ = NL;
- } else {
- *s++ = CAR; // EOL_MAC or EOL_DOS: write CR
- if (fileformat == EOL_DOS) { // write CR-NL
- if (++len == bufsize) {
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error: break loop
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
- }
- *s++ = NL;
- }
- }
- if (++len == bufsize) {
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // Write error: break loop.
- break;
- }
- nchars += bufsize;
- s = buffer;
- len = 0;
-
- os_breakcheck();
- if (got_int) {
- end = 0; // Interrupted, break loop.
- break;
- }
- }
- }
- if (len > 0 && end > 0) {
- write_info.bw_len = len;
- if (buf_write_bytes(&write_info) == FAIL) {
- end = 0; // write error
- }
- nchars += len;
- }
-
- if (!buf->b_p_fixeol && buf->b_p_eof) {
- // write trailing CTRL-Z
- (void)write_eintr(write_info.bw_fd, "\x1a", 1);
- }
-
- // Stop when writing done or an error was encountered.
- if (!checking_conversion || end == 0) {
- break;
- }
-
- // If no error happened until now, writing should be ok, so loop to
- // really write the buffer.
- }
-
- // If we started writing, finish writing. Also when an error was
- // encountered.
- if (!checking_conversion) {
- // On many journalling file systems there is a bug that causes both the
- // original and the backup file to be lost when halting the system right
- // after writing the file. That's because only the meta-data is
- // journalled. Syncing the file slows down the system, but assures it has
- // been written to disk and we don't lose it.
- // For a device do try the fsync() but don't complain if it does not work
- // (could be a pipe).
- // If the 'fsync' option is false, don't fsync(). Useful for laptops.
- int error;
- if (p_fs && (error = os_fsync(fd)) != 0 && !device
- // fsync not supported on this storage.
- && error != UV_ENOTSUP) {
- SET_ERRMSG_ARG(e_fsync, error);
- end = 0;
- }
-
-#ifdef UNIX
- // When creating a new file, set its owner/group to that of the original
- // file. Get the new device and inode number.
- if (backup != NULL && !backup_copy) {
- // don't change the owner when it's already OK, some systems remove
- // permission or ACL stuff
- FileInfo file_info;
- if (!os_fileinfo(wfname, &file_info)
- || file_info.stat.st_uid != file_info_old.stat.st_uid
- || file_info.stat.st_gid != file_info_old.stat.st_gid) {
- os_fchown(fd, (uv_uid_t)file_info_old.stat.st_uid, (uv_gid_t)file_info_old.stat.st_gid);
- if (perm >= 0) { // Set permission again, may have changed.
- (void)os_setperm(wfname, (int)perm);
- }
- }
- buf_set_file_id(buf);
- } else if (!buf->file_id_valid) {
- // Set the file_id when creating a new file.
- buf_set_file_id(buf);
- }
-#endif
-
- if ((error = os_close(fd)) != 0) {
- SET_ERRMSG_ARG(_("E512: Close failed: %s"), error);
- end = 0;
- }
-
-#ifdef UNIX
- if (made_writable) {
- perm &= ~0200; // reset 'w' bit for security reasons
- }
-#endif
- if (perm >= 0) { // Set perm. of new file same as old file.
- (void)os_setperm((const char *)wfname, (int)perm);
- }
-#ifdef HAVE_ACL
- // Probably need to set the ACL before changing the user (can't set the
- // ACL on a file the user doesn't own).
- if (!backup_copy) {
- os_set_acl(wfname, acl);
- }
-#endif
-
- if (wfname != fname) {
- // The file was written to a temp file, now it needs to be converted
- // with 'charconvert' to (overwrite) the output file.
- if (end != 0) {
- if (eval_charconvert("utf-8", fenc, wfname, fname) == FAIL) {
- write_info.bw_conv_error = true;
- end = 0;
- }
- }
- os_remove(wfname);
- xfree(wfname);
- }
- }
-
- if (end == 0) {
- // Error encountered.
- if (errmsg == NULL) {
- if (write_info.bw_conv_error) {
- if (write_info.bw_conv_error_lnum == 0) {
- SET_ERRMSG(_("E513: write error, conversion failed "
- "(make 'fenc' empty to override)"));
- } else {
- errmsg_allocated = true;
- SET_ERRMSG(xmalloc(300));
- vim_snprintf(errmsg, 300, // NOLINT(runtime/printf)
- _("E513: write error, conversion failed in line %" PRIdLINENR
- " (make 'fenc' empty to override)"),
- write_info.bw_conv_error_lnum);
- }
- } else if (got_int) {
- SET_ERRMSG(_(e_interr));
- } else {
- SET_ERRMSG(_("E514: write error (file system full?)"));
- }
- }
-
- // If we have a backup file, try to put it in place of the new file,
- // because the new file is probably corrupt. This avoids losing the
- // original file when trying to make a backup when writing the file a
- // second time.
- // When "backup_copy" is set we need to copy the backup over the new
- // file. Otherwise rename the backup file.
- // If this is OK, don't give the extra warning message.
- if (backup != NULL) {
- if (backup_copy) {
- // This may take a while, if we were interrupted let the user
- // know we got the message.
- if (got_int) {
- msg(_(e_interr));
- ui_flush();
- }
-
- // copy the file.
- if (os_copy(backup, fname, UV_FS_COPYFILE_FICLONE)
- == 0) {
- end = 1; // success
- }
- } else {
- if (vim_rename(backup, fname) == 0) {
- end = 1;
- }
- }
- }
- goto fail;
- }
-
- lnum -= start; // compute number of written lines
- no_wait_return--; // may wait for return now
-
-#if !defined(UNIX)
- fname = sfname; // use shortname now, for the messages
-#endif
- if (!filtering) {
- add_quoted_fname(IObuff, IOSIZE, buf, (const char *)fname);
- c = false;
- if (write_info.bw_conv_error) {
- STRCAT(IObuff, _(" CONVERSION ERROR"));
- c = true;
- if (write_info.bw_conv_error_lnum != 0) {
- vim_snprintf_add(IObuff, IOSIZE, _(" in line %" PRId64 ";"),
- (int64_t)write_info.bw_conv_error_lnum);
- }
- } else if (notconverted) {
- STRCAT(IObuff, _("[NOT converted]"));
- c = true;
- } else if (converted) {
- STRCAT(IObuff, _("[converted]"));
- c = true;
- }
- if (device) {
- STRCAT(IObuff, _("[Device]"));
- c = true;
- } else if (newfile) {
- STRCAT(IObuff, new_file_message());
- c = true;
- }
- if (no_eol) {
- msg_add_eol();
- c = true;
- }
- // may add [unix/dos/mac]
- if (msg_add_fileformat(fileformat)) {
- c = true;
- }
- msg_add_lines(c, (long)lnum, nchars); // add line/char count
- if (!shortmess(SHM_WRITE)) {
- if (append) {
- STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended"));
- } else {
- STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written"));
- }
- }
-
- set_keep_msg(msg_trunc_attr(IObuff, false, 0), 0);
- }
-
- // When written everything correctly: reset 'modified'. Unless not
- // writing to the original file and '+' is not in 'cpoptions'.
- if (reset_changed && whole && !append
- && !write_info.bw_conv_error
- && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) {
- unchanged(buf, true, false);
- const varnumber_T changedtick = buf_get_changedtick(buf);
- if (buf->b_last_changedtick + 1 == changedtick) {
- // b:changedtick may be incremented in unchanged() but that
- // should not trigger a TextChanged event.
- buf->b_last_changedtick = changedtick;
- }
- u_unchanged(buf);
- u_update_save_nr(buf);
- }
-
- // If written to the current file, update the timestamp of the swap file
- // and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime.
- if (overwriting) {
- ml_timestamp(buf);
- if (append) {
- buf->b_flags &= ~BF_NEW;
- } else {
- buf->b_flags &= ~BF_WRITE_MASK;
- }
- }
-
- // If we kept a backup until now, and we are in patch mode, then we make
- // the backup file our 'original' file.
- if (*p_pm && dobackup) {
- char *const org = modname(fname, p_pm, false);
-
- if (backup != NULL) {
- // If the original file does not exist yet
- // the current backup file becomes the original file
- if (org == NULL) {
- emsg(_("E205: Patchmode: can't save original file"));
- } else if (!os_path_exists(org)) {
- vim_rename(backup, org);
- XFREE_CLEAR(backup); // don't delete the file
-#ifdef UNIX
- os_file_settime(org,
- (double)file_info_old.stat.st_atim.tv_sec,
- (double)file_info_old.stat.st_mtim.tv_sec);
-#endif
- }
- } else {
- // If there is no backup file, remember that a (new) file was
- // created.
- int empty_fd;
-
- if (org == NULL
- || (empty_fd = os_open(org,
- O_CREAT | O_EXCL | O_NOFOLLOW,
- perm < 0 ? 0666 : (perm & 0777))) < 0) {
- emsg(_("E206: patchmode: can't touch empty original file"));
- } else {
- close(empty_fd);
- }
- }
- if (org != NULL) {
- os_setperm(org, os_getperm((const char *)fname) & 0777);
- xfree(org);
- }
- }
-
- // Remove the backup unless 'backup' option is set
- if (!p_bk && backup != NULL
- && !write_info.bw_conv_error
- && os_remove(backup) != 0) {
- emsg(_("E207: Can't delete backup file"));
- }
-
- goto nofail;
-
- // Finish up. We get here either after failure or success.
-fail:
- no_wait_return--; // may wait for return now
-nofail:
-
- // Done saving, we accept changed buffer warnings again
- buf->b_saving = false;
-
- xfree(backup);
- if (buffer != smallbuf) {
- xfree(buffer);
- }
- xfree(fenc_tofree);
- xfree(write_info.bw_conv_buf);
- if (write_info.bw_iconv_fd != (iconv_t)-1) {
- iconv_close(write_info.bw_iconv_fd);
- write_info.bw_iconv_fd = (iconv_t)-1;
- }
-#ifdef HAVE_ACL
- os_free_acl(acl);
-#endif
-
- if (errmsg != NULL) {
- // - 100 to save some space for further error message
-#ifndef UNIX
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)sfname);
-#else
- add_quoted_fname(IObuff, IOSIZE - 100, buf, (const char *)fname);
-#endif
- if (errnum != NULL) {
- if (errmsgarg != 0) {
- semsg("%s: %s%s: %s", errnum, IObuff, errmsg, os_strerror(errmsgarg));
- } else {
- semsg("%s: %s%s", errnum, IObuff, errmsg);
- }
- } else if (errmsgarg != 0) {
- semsg(errmsg, os_strerror(errmsgarg));
- } else {
- emsg(errmsg);
- }
- if (errmsg_allocated) {
- xfree(errmsg);
- }
-
- retval = FAIL;
- if (end == 0) {
- const int attr = HL_ATTR(HLF_E); // Set highlight for error messages.
- msg_puts_attr(_("\nWARNING: Original file may be lost or damaged\n"),
- attr | MSG_HIST);
- msg_puts_attr(_("don't quit the editor until the file is successfully written!"),
- attr | MSG_HIST);
-
- // Update the timestamp to avoid an "overwrite changed file"
- // prompt when writing again.
- if (os_fileinfo(fname, &file_info_old)) {
- buf_store_file_info(buf, &file_info_old);
- buf->b_mtime_read = buf->b_mtime;
- buf->b_mtime_read_ns = buf->b_mtime_ns;
- }
- }
- }
- msg_scroll = msg_save;
-
- // When writing the whole file and 'undofile' is set, also write the undo
- // file.
- if (retval == OK && write_undo_file) {
- uint8_t hash[UNDO_HASH_SIZE];
-
- sha256_finish(&sha_ctx, hash);
- u_write_undo(NULL, false, buf, hash);
- }
-
- if (!should_abort(retval)) {
- aco_save_T aco;
-
- curbuf->b_no_eol_lnum = 0; // in case it was set by the previous read
-
- // Apply POST autocommands.
- // Careful: The autocommands may call buf_write() recursively!
- aucmd_prepbuf(&aco, buf);
-
- if (append) {
- apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname,
- false, curbuf, eap);
- } else if (filtering) {
- apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname,
- false, curbuf, eap);
- } else if (reset_changed && whole) {
- apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname,
- false, curbuf, eap);
- } else {
- apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname,
- false, curbuf, eap);
- }
-
- // restore curwin/curbuf and a few other things
- aucmd_restbuf(&aco);
-
- if (aborting()) { // autocmds may abort script processing
- retval = false;
- }
- }
-
- got_int |= prev_got_int;
-
- return retval;
-#undef SET_ERRMSG
-#undef SET_ERRMSG_ARG
-#undef SET_ERRMSG_NUM
-}
-
/// Set the name of the current buffer. Use when the buffer doesn't have a
/// name and a ":r" or ":w" command with a file name is used.
-static int set_rw_fname(char *fname, char *sfname)
+int set_rw_fname(char *fname, char *sfname)
{
buf_T *buf = curbuf;
@@ -3632,8 +2114,8 @@ static int set_rw_fname(char *fname, char *sfname)
/// @param[in] buf_len ret_buf length.
/// @param[in] buf buf_T file name is coming from.
/// @param[in] fname File name to write.
-static void add_quoted_fname(char *const ret_buf, const size_t buf_len, const buf_T *const buf,
- const char *fname)
+void add_quoted_fname(char *const ret_buf, const size_t buf_len, const buf_T *const buf,
+ const char *fname)
FUNC_ATTR_NONNULL_ARG(1)
{
if (fname == NULL) {
@@ -3649,21 +2131,21 @@ static void add_quoted_fname(char *const ret_buf, const size_t buf_len, const bu
/// @param eol_type line ending type
///
/// @return true if something was appended.
-static bool msg_add_fileformat(int eol_type)
+bool msg_add_fileformat(int eol_type)
{
#ifndef USE_CRNL
if (eol_type == EOL_DOS) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]"));
+ xstrlcat(IObuff, _("[dos]"), IOSIZE);
return true;
}
#endif
if (eol_type == EOL_MAC) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]"));
+ xstrlcat(IObuff, _("[mac]"), IOSIZE);
return true;
}
#ifdef USE_CRNL
if (eol_type == EOL_UNIX) {
- STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]"));
+ xstrlcat(IObuff, _("[unix]"), IOSIZE);
return true;
}
#endif
@@ -3671,7 +2153,7 @@ static bool msg_add_fileformat(int eol_type)
}
/// Append line and character count to IObuff.
-void msg_add_lines(int insert_space, long lnum, off_T nchars)
+void msg_add_lines(int insert_space, linenr_T lnum, off_T nchars)
{
char *p = IObuff + strlen(IObuff);
@@ -3692,283 +2174,29 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars)
}
}
-/// Append message for missing line separator to IObuff.
-static void msg_add_eol(void)
-{
- STRCAT(IObuff,
- shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]"));
-}
-
-/// Check modification time of file, before writing to it.
-/// The size isn't checked, because using a tool like "gzip" takes care of
-/// using the same timestamp but can't set the size.
-static int check_mtime(buf_T *buf, FileInfo *file_info)
+bool time_differs(const FileInfo *file_info, int64_t mtime, int64_t mtime_ns)
+ FUNC_ATTR_CONST
{
- if (buf->b_mtime_read != 0
- && time_differs(file_info, buf->b_mtime_read, buf->b_mtime_read_ns)) {
- msg_scroll = true; // Don't overwrite messages here.
- msg_silent = 0; // Must give this prompt.
- // Don't use emsg() here, don't want to flush the buffers.
- msg_attr(_("WARNING: The file has been changed since reading it!!!"),
- HL_ATTR(HLF_E));
- if (ask_yesno(_("Do you really want to write to it"), true) == 'n') {
- return FAIL;
- }
- msg_scroll = false; // Always overwrite the file message now.
- }
- return OK;
-}
-
-static bool time_differs(const FileInfo *file_info, long mtime, long mtime_ns) FUNC_ATTR_CONST
-{
- return file_info->stat.st_mtim.tv_nsec != mtime_ns
#if defined(__linux__) || defined(MSWIN)
+ return file_info->stat.st_mtim.tv_nsec != mtime_ns
// On a FAT filesystem, esp. under Linux, there are only 5 bits to store
// the seconds. Since the roundoff is done when flushing the inode, the
// time may change unexpectedly by one second!!!
|| file_info->stat.st_mtim.tv_sec - mtime > 1
|| mtime - file_info->stat.st_mtim.tv_sec > 1;
#else
+ return file_info->stat.st_mtim.tv_nsec != mtime_ns
|| file_info->stat.st_mtim.tv_sec != mtime;
#endif
}
-/// Call write() to write a number of bytes to the file.
-/// Handles 'encoding' conversion.
-///
-/// @return FAIL for failure, OK otherwise.
-static int buf_write_bytes(struct bw_info *ip)
-{
- char *buf = ip->bw_buf; // data to write
- int len = ip->bw_len; // length of data
-#ifdef HAS_BW_FLAGS
- int flags = ip->bw_flags; // extra flags
-#endif
-
- // Skip conversion when writing the BOM.
- if (!(flags & FIO_NOCONVERT)) {
- if (flags & FIO_UTF8) {
- // Convert latin1 in the buffer to UTF-8 in the file.
- char *p = ip->bw_conv_buf; // translate to buffer
- for (int wlen = 0; wlen < len; wlen++) {
- p += utf_char2bytes((uint8_t)buf[wlen], p);
- }
- buf = ip->bw_conv_buf;
- len = (int)(p - ip->bw_conv_buf);
- } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) {
- unsigned c;
- int n = 0;
- // Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or
- // Latin1 chars in the file.
- // translate in-place (can only get shorter) or to buffer
- char *p = flags & FIO_LATIN1 ? buf : ip->bw_conv_buf;
- for (int wlen = 0; wlen < len; wlen += n) {
- if (wlen == 0 && ip->bw_restlen != 0) {
- // Use remainder of previous call. Append the start of
- // buf[] to get a full sequence. Might still be too
- // short!
- int l = MIN(len, CONV_RESTLEN - ip->bw_restlen);
- memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l);
- n = utf_ptr2len_len((char *)ip->bw_rest, ip->bw_restlen + l);
- if (n > ip->bw_restlen + len) {
- // We have an incomplete byte sequence at the end to
- // be written. We can't convert it without the
- // remaining bytes. Keep them for the next call.
- if (ip->bw_restlen + len > CONV_RESTLEN) {
- return FAIL;
- }
- ip->bw_restlen += len;
- break;
- }
- if (n > 1) {
- c = (unsigned)utf_ptr2char((char *)ip->bw_rest);
- } else {
- c = ip->bw_rest[0];
- }
- if (n >= ip->bw_restlen) {
- n -= ip->bw_restlen;
- ip->bw_restlen = 0;
- } else {
- ip->bw_restlen -= n;
- memmove(ip->bw_rest, ip->bw_rest + n,
- (size_t)ip->bw_restlen);
- n = 0;
- }
- } else {
- n = utf_ptr2len_len(buf + wlen, len - wlen);
- if (n > len - wlen) {
- // We have an incomplete byte sequence at the end to
- // be written. We can't convert it without the
- // remaining bytes. Keep them for the next call.
- if (len - wlen > CONV_RESTLEN) {
- return FAIL;
- }
- ip->bw_restlen = len - wlen;
- memmove(ip->bw_rest, buf + wlen,
- (size_t)ip->bw_restlen);
- break;
- }
- if (n > 1) {
- c = (unsigned)utf_ptr2char(buf + wlen);
- } else {
- c = (uint8_t)buf[wlen];
- }
- }
-
- if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) {
- ip->bw_conv_error = true;
- ip->bw_conv_error_lnum = ip->bw_start_lnum;
- }
- if (c == NL) {
- ip->bw_start_lnum++;
- }
- }
- if (flags & FIO_LATIN1) {
- len = (int)(p - buf);
- } else {
- buf = ip->bw_conv_buf;
- len = (int)(p - ip->bw_conv_buf);
- }
- }
-
- if (ip->bw_iconv_fd != (iconv_t)-1) {
- const char *from;
- size_t fromlen;
- size_t tolen;
-
- // Convert with iconv().
- if (ip->bw_restlen > 0) {
- // Need to concatenate the remainder of the previous call and
- // the bytes of the current call. Use the end of the
- // conversion buffer for this.
- fromlen = (size_t)len + (size_t)ip->bw_restlen;
- char *fp = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen;
- memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen);
- memmove(fp + ip->bw_restlen, buf, (size_t)len);
- from = fp;
- tolen = ip->bw_conv_buflen - fromlen;
- } else {
- from = buf;
- fromlen = (size_t)len;
- tolen = ip->bw_conv_buflen;
- }
- char *to = ip->bw_conv_buf;
-
- if (ip->bw_first) {
- size_t save_len = tolen;
-
- // output the initial shift state sequence
- (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen);
-
- // There is a bug in iconv() on Linux (which appears to be
- // wide-spread) which sets "to" to NULL and messes up "tolen".
- if (to == NULL) {
- to = ip->bw_conv_buf;
- tolen = save_len;
- }
- ip->bw_first = false;
- }
-
- // If iconv() has an error or there is not enough room, fail.
- if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen)
- == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL)
- || fromlen > CONV_RESTLEN) {
- ip->bw_conv_error = true;
- return FAIL;
- }
-
- // copy remainder to ip->bw_rest[] to be used for the next call.
- if (fromlen > 0) {
- memmove(ip->bw_rest, (void *)from, fromlen);
- }
- ip->bw_restlen = (int)fromlen;
-
- buf = ip->bw_conv_buf;
- len = (int)(to - ip->bw_conv_buf);
- }
- }
-
- if (ip->bw_fd < 0) {
- // Only checking conversion, which is OK if we get here.
- return OK;
- }
- int wlen = (int)write_eintr(ip->bw_fd, buf, (size_t)len);
- return (wlen < len) ? FAIL : OK;
-}
-
-/// Convert a Unicode character to bytes.
-///
-/// @param c character to convert
-/// @param[in,out] pp pointer to store the result at
-/// @param flags FIO_ flags that specify which encoding to use
-///
-/// @return true for an error, false when it's OK.
-static bool ucs2bytes(unsigned c, char **pp, int flags) FUNC_ATTR_NONNULL_ALL
-{
- char_u *p = (char_u *)(*pp);
- bool error = false;
-
- if (flags & FIO_UCS4) {
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)c;
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)(c >> 16);
- *p++ = (uint8_t)(c >> 24);
- } else {
- *p++ = (uint8_t)(c >> 24);
- *p++ = (uint8_t)(c >> 16);
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)c;
- }
- } else if (flags & (FIO_UCS2 | FIO_UTF16)) {
- if (c >= 0x10000) {
- if (flags & FIO_UTF16) {
- // Make two words, ten bits of the character in each. First
- // word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff
- c -= 0x10000;
- if (c >= 0x100000) {
- error = true;
- }
- int cc = (int)(((c >> 10) & 0x3ff) + 0xd800);
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)cc;
- *p++ = (uint8_t)(cc >> 8);
- } else {
- *p++ = (uint8_t)(cc >> 8);
- *p++ = (uint8_t)cc;
- }
- c = (c & 0x3ff) + 0xdc00;
- } else {
- error = true;
- }
- }
- if (flags & FIO_ENDIAN_L) {
- *p++ = (uint8_t)c;
- *p++ = (uint8_t)(c >> 8);
- } else {
- *p++ = (uint8_t)(c >> 8);
- *p++ = (uint8_t)c;
- }
- } else { // Latin1
- if (c >= 0x100) {
- error = true;
- *p++ = 0xBF;
- } else {
- *p++ = (uint8_t)c;
- }
- }
-
- *pp = (char *)p;
- return error;
-}
-
/// Return true if file encoding "fenc" requires conversion from or to
/// 'encoding'.
///
/// @param fenc file encoding to check
///
/// @return true if conversion is required
-static bool need_conversion(const char *fenc)
+bool need_conversion(const char *fenc)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
int same_encoding;
@@ -3999,7 +2227,7 @@ static bool need_conversion(const char *fenc)
/// use 'encoding'.
///
/// @param name string to check for encoding
-static int get_fio_flags(const char *name)
+int get_fio_flags(const char *name)
{
if (*name == NUL) {
name = p_enc;
@@ -4038,7 +2266,7 @@ static int get_fio_flags(const char *name)
///
/// @return the name of the encoding and set "*lenp" to the length or,
/// NULL when no BOM found.
-static char *check_for_bom(const char *p_in, long size, int *lenp, int flags)
+static char *check_for_bom(const char *p_in, int size, int *lenp, int flags)
{
const uint8_t *p = (const uint8_t *)p_in;
char *name = NULL;
@@ -4079,29 +2307,6 @@ static char *check_for_bom(const char *p_in, long size, int *lenp, int flags)
return name;
}
-/// Generate a BOM in "buf[4]" for encoding "name".
-///
-/// @return the length of the BOM (zero when no BOM).
-static int make_bom(char_u *buf, char *name)
-{
- int flags = get_fio_flags(name);
-
- // Can't put a BOM in a non-Unicode file.
- if (flags == FIO_LATIN1 || flags == 0) {
- return 0;
- }
-
- if (flags == FIO_UTF8) { // UTF-8
- buf[0] = 0xef;
- buf[1] = 0xbb;
- buf[2] = 0xbf;
- return 3;
- }
- char *p = (char *)buf;
- (void)ucs2bytes(0xfeff, &p, flags);
- return (int)((char_u *)p - buf);
-}
-
/// Shorten filename of a buffer.
///
/// @param force when true: Use full path from now on for files currently being
@@ -4140,7 +2345,7 @@ void shorten_fnames(int force)
os_dirname(dirname, MAXPATHL);
FOR_ALL_BUFFERS(buf) {
- shorten_buf_fname(buf, (char *)dirname, force);
+ shorten_buf_fname(buf, dirname, force);
// Always make the swap file name a full path, a "nofile" buffer may
// also have a swap file.
@@ -4274,7 +2479,7 @@ bool vim_fgets(char *buf, int size, FILE *fp)
do {
tbuf[sizeof(tbuf) - 2] = NUL;
errno = 0;
- retval = fgets((char *)tbuf, sizeof(tbuf), fp);
+ retval = fgets(tbuf, sizeof(tbuf), fp);
if (retval == NULL && (feof(fp) || errno != EINTR)) {
break;
}
@@ -4410,6 +2615,38 @@ int put_time(FILE *fd, time_t time_)
return fwrite(buf, sizeof(uint8_t), ARRAY_SIZE(buf), fd) == 1 ? OK : FAIL;
}
+static int rename_with_tmp(const char *const from, const char *const to)
+{
+ // Find a name that doesn't exist and is in the same directory.
+ // Rename "from" to "tempname" and then rename "tempname" to "to".
+ if (strlen(from) >= MAXPATHL - 5) {
+ return -1;
+ }
+
+ char tempname[MAXPATHL + 1];
+ STRCPY(tempname, from);
+ for (int n = 123; n < 99999; n++) {
+ char *tail = path_tail(tempname);
+ snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n);
+
+ if (!os_path_exists(tempname)) {
+ if (os_rename(from, tempname) == OK) {
+ if (os_rename(tempname, to) == OK) {
+ return 0;
+ }
+ // Strange, the second step failed. Try moving the
+ // file back and return failure.
+ (void)os_rename(tempname, from);
+ return -1;
+ }
+ // If it fails for one temp name it will most likely fail
+ // for any temp name, give up.
+ return -1;
+ }
+ }
+ return -1;
+}
+
/// os_rename() only works if both files are on the same file system, this
/// function will (attempts to?) copy the file across if rename fails -- webb
///
@@ -4417,20 +2654,14 @@ int put_time(FILE *fd, time_t time_)
int vim_rename(const char *from, const char *to)
FUNC_ATTR_NONNULL_ALL
{
- int fd_in;
- int fd_out;
char *errmsg = NULL;
-#ifdef HAVE_ACL
- vim_acl_T acl; // ACL from original file
-#endif
bool use_tmp_file = false;
// When the names are identical, there is nothing to do. When they refer
// to the same file (ignoring case and slash/backslash differences) but
// the file name differs we need to go through a temp file.
if (path_fnamecmp(from, to) == 0) {
- if (p_fic && (strcmp(path_tail((char *)from), path_tail((char *)to))
- != 0)) {
+ if (p_fic && (strcmp(path_tail(from), path_tail(to)) != 0)) {
use_tmp_file = true;
} else {
return 0;
@@ -4439,7 +2670,7 @@ int vim_rename(const char *from, const char *to)
// Fail if the "from" file doesn't exist. Avoids that "to" is deleted.
FileInfo from_info;
- if (!os_fileinfo((char *)from, &from_info)) {
+ if (!os_fileinfo(from, &from_info)) {
return -1;
}
@@ -4447,47 +2678,19 @@ int vim_rename(const char *from, const char *to)
// This happens when "from" and "to" differ in case and are on a FAT32
// filesystem. In that case go through a temp file name.
FileInfo to_info;
- if (os_fileinfo((char *)to, &to_info)
- && os_fileinfo_id_equal(&from_info, &to_info)) {
+ if (os_fileinfo(to, &to_info) && os_fileinfo_id_equal(&from_info, &to_info)) {
use_tmp_file = true;
}
if (use_tmp_file) {
- char tempname[MAXPATHL + 1];
-
- // Find a name that doesn't exist and is in the same directory.
- // Rename "from" to "tempname" and then rename "tempname" to "to".
- if (strlen(from) >= MAXPATHL - 5) {
- return -1;
- }
- STRCPY(tempname, from);
- for (int n = 123; n < 99999; n++) {
- char *tail = path_tail(tempname);
- snprintf(tail, (size_t)((MAXPATHL + 1) - (tail - tempname - 1)), "%d", n);
-
- if (!os_path_exists(tempname)) {
- if (os_rename(from, tempname) == OK) {
- if (os_rename(tempname, to) == OK) {
- return 0;
- }
- // Strange, the second step failed. Try moving the
- // file back and return failure.
- (void)os_rename(tempname, from);
- return -1;
- }
- // If it fails for one temp name it will most likely fail
- // for any temp name, give up.
- return -1;
- }
- }
- return -1;
+ return rename_with_tmp(from, to);
}
// Delete the "to" file, this is required on some systems to make the
// os_rename() work, on other systems it makes sure that we don't have
// two files when the os_rename() fails.
- os_remove((char *)to);
+ os_remove(to);
// First try a normal rename, return if it works.
if (os_rename(from, to) == OK) {
@@ -4495,44 +2698,35 @@ int vim_rename(const char *from, const char *to)
}
// Rename() failed, try copying the file.
- long perm = os_getperm(from);
-#ifdef HAVE_ACL
+ int perm = os_getperm(from);
// For systems that support ACL: get the ACL from the original file.
- acl = os_get_acl(from);
-#endif
- fd_in = os_open((char *)from, O_RDONLY, 0);
+ vim_acl_T acl = os_get_acl(from);
+ int fd_in = os_open(from, O_RDONLY, 0);
if (fd_in < 0) {
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
// Create the new file with same permissions as the original.
- fd_out = os_open((char *)to,
- O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, (int)perm);
+ int fd_out = os_open(to, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, perm);
if (fd_out < 0) {
close(fd_in);
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
// Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim
// is `preserve_exit()`ing.
- char *buffer = try_malloc(BUFSIZE);
+ char *buffer = try_malloc(WRITEBUFSIZE);
if (buffer == NULL) {
close(fd_out);
close(fd_in);
-#ifdef HAVE_ACL
os_free_acl(acl);
-#endif
return -1;
}
int n;
- while ((n = (int)read_eintr(fd_in, buffer, BUFSIZE)) > 0) {
+ while ((n = read_eintr(fd_in, buffer, WRITEBUFSIZE)) > 0) {
if (write_eintr(fd_out, buffer, (size_t)n) != n) {
errmsg = _("E208: Error writing to \"%s\"");
break;
@@ -4551,15 +2745,13 @@ int vim_rename(const char *from, const char *to)
#ifndef UNIX // For Unix os_open() already set the permission.
os_setperm(to, perm);
#endif
-#ifdef HAVE_ACL
os_set_acl(to, acl);
os_free_acl(acl);
-#endif
if (errmsg != NULL) {
semsg(errmsg, to);
return -1;
}
- os_remove((char *)from);
+ os_remove(from);
return 0;
}
@@ -4638,7 +2830,7 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf)
// Copy the lines in "frombuf" to "tobuf".
curbuf = tobuf;
for (linenr_T lnum = 1; lnum <= frombuf->b_ml.ml_line_count; lnum++) {
- char *p = xstrdup(ml_get_buf(frombuf, lnum, false));
+ char *p = xstrdup(ml_get_buf(frombuf, lnum));
if (ml_append(lnum - 1, p, 0, false) == FAIL) {
xfree(p);
retval = FAIL;
@@ -4711,7 +2903,7 @@ int buf_check_timestamp(buf_T *buf)
&& (!(file_info_ok = os_fileinfo(buf->b_ffname, &file_info))
|| time_differs(&file_info, buf->b_mtime, buf->b_mtime_ns)
|| (int)file_info.stat.st_mode != buf->b_orig_mode)) {
- const long prev_b_mtime = buf->b_mtime;
+ const int64_t prev_b_mtime = buf->b_mtime;
retval = 1;
@@ -4788,8 +2980,8 @@ int buf_check_timestamp(buf_T *buf)
// checked out of CVS). Always warn when the buffer was
// changed.
if (reason[2] == 'n') {
- mesg = _(
- "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
+ mesg =
+ _("W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well");
mesg2 = _("See \":help W12\" for more info.");
} else if (reason[1] == 'h') {
mesg = _("W11: Warning: File \"%s\" has changed since editing started");
@@ -4859,7 +3051,7 @@ int buf_check_timestamp(buf_T *buf)
if (emsg_silent == 0 && !in_assert_fails) {
ui_flush();
// give the user some time to think about it
- os_delay(1004L, true);
+ os_delay(1004, true);
// don't redraw and erase the message
redraw_cmdline = false;
@@ -4876,7 +3068,7 @@ int buf_check_timestamp(buf_T *buf)
// Reload the buffer.
buf_reload(buf, orig_mode, reload == RELOAD_DETECT);
if (buf->b_p_udf && buf->b_ffname != NULL) {
- char_u hash[UNDO_HASH_SIZE];
+ uint8_t hash[UNDO_HASH_SIZE];
// Any existing undo file is unusable, write it now.
u_compute_hash(buf, hash);
@@ -4937,7 +3129,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
savebuf = NULL;
} else {
// Allocate a buffer without putting it in the buffer list.
- savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ savebuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
set_bufref(&bufref, savebuf);
if (savebuf != NULL && buf == curbuf) {
// Open the memline.
@@ -4958,7 +3150,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
if (saved == OK) {
curbuf->b_flags |= BF_CHECK_RO; // check for RO again
keep_filetype = true; // don't detect 'filetype'
- if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, (linenr_T)0,
+ if (readfile(buf->b_ffname, buf->b_fname, 0, 0,
(linenr_T)MAXLNUM, &ea, flags, false) != OK) {
if (!aborting()) {
semsg(_("E321: Could not reload \"%s\""), buf->b_fname);
@@ -5054,12 +3246,10 @@ void write_lnum_adjust(linenr_T offset)
/// unless when it looks like a URL.
void forward_slash(char *fname)
{
- char *p;
-
if (path_with_url(fname)) {
return;
}
- for (p = fname; *p != NUL; p++) {
+ for (char *p = fname; *p != NUL; p++) {
if (*p == '\\') {
*p = '/';
}
@@ -5069,7 +3259,7 @@ void forward_slash(char *fname)
/// Path to Nvim's own temp dir. Ends in a slash.
static char *vim_tempdir = NULL;
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
DIR *vim_tempdir_dp = NULL; ///< File descriptor of temp dir
#endif
@@ -5086,12 +3276,18 @@ static void vim_mktempdir(void)
char tmp[TEMP_FILE_PATH_MAXLEN];
char path[TEMP_FILE_PATH_MAXLEN];
char user[40] = { 0 };
+ char appname[40] = { 0 };
(void)os_get_username(user, sizeof(user));
// Usernames may contain slashes! #19240
memchrsub(user, '/', '_', sizeof(user));
memchrsub(user, '\\', '_', sizeof(user));
+ // Appname may be a relative path, replace slashes to make it name-like.
+ xstrlcpy(appname, get_appname(), sizeof(appname));
+ memchrsub(appname, '/', '%', sizeof(appname));
+ memchrsub(appname, '\\', '%', sizeof(appname));
+
// Make sure the umask doesn't remove the executable bit.
// "repl" has been reported to use "0177".
mode_t umask_save = umask(0077);
@@ -5104,7 +3300,9 @@ static void vim_mktempdir(void)
// "/tmp/" exists, now try to create "/tmp/nvim.<user>/".
add_pathsep(tmp);
- xstrlcat(tmp, "nvim.", sizeof(tmp));
+
+ xstrlcat(tmp, appname, sizeof(tmp));
+ xstrlcat(tmp, ".", sizeof(tmp));
xstrlcat(tmp, user, sizeof(tmp));
(void)os_mkdir(tmp, 0700); // Always create, to avoid a race.
bool owned = os_file_owned(tmp);
@@ -5162,11 +3360,11 @@ int readdir_core(garray_T *gap, const char *path, void *context, CheckItem check
Directory dir;
if (!os_scandir(&dir, path)) {
- smsg(_(e_notopen), path);
+ smsg(0, _(e_notopen), path);
return FAIL;
}
- for (;;) {
+ while (true) {
const char *p = os_scandir_next(&dir);
if (p == NULL) {
break;
@@ -5214,7 +3412,7 @@ int delete_recursive(const char *name)
if (readdir_core(&ga, exp, NULL, NULL) == OK) {
for (int i = 0; i < ga.ga_len; i++) {
vim_snprintf(NameBuff, MAXPATHL, "%s/%s", exp, ((char **)ga.ga_data)[i]);
- if (delete_recursive((const char *)NameBuff) != 0) {
+ if (delete_recursive(NameBuff) != 0) {
// Remember the failure but continue deleting any further
// entries.
result = -1;
@@ -5236,7 +3434,7 @@ int delete_recursive(const char *name)
return result;
}
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
/// Open temporary directory and take file lock to prevent
/// to be auto-cleaned.
static void vim_opentempdir(void)
@@ -5273,7 +3471,7 @@ void vim_deltempdir(void)
return;
}
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
vim_closetempdir();
#endif
// remove the trailing path separator
@@ -5287,10 +3485,20 @@ void vim_deltempdir(void)
/// Creates the directory on the first call.
char *vim_gettempdir(void)
{
- if (vim_tempdir == NULL) {
+ static int notfound = 0;
+ if (vim_tempdir == NULL || !os_isdir(vim_tempdir)) {
+ if (vim_tempdir != NULL) {
+ notfound++;
+ if (notfound == 1) {
+ ELOG("tempdir disappeared (antivirus or broken cleanup job?): %s", vim_tempdir);
+ }
+ if (notfound > 1) {
+ msg_schedule_semsg("E5431: tempdir disappeared (%d times)", notfound);
+ }
+ XFREE_CLEAR(vim_tempdir);
+ }
vim_mktempdir();
}
-
return vim_tempdir;
}
@@ -5311,7 +3519,7 @@ static bool vim_settempdir(char *tempdir)
vim_FullName(tempdir, buf, MAXPATHL, false);
add_pathsep(buf);
vim_tempdir = xstrdup(buf);
-#if defined(HAVE_FLOCK) && defined(HAVE_DIRFD)
+#ifdef HAVE_DIRFD_AND_FLOCK
vim_opentempdir();
#endif
xfree(buf);
@@ -5336,10 +3544,9 @@ char *vim_tempname(void)
// There is no need to check if the file exists, because we own the directory
// and nobody else creates a file in it.
- char template[TEMP_FILE_PATH_MAXLEN];
- snprintf(template, TEMP_FILE_PATH_MAXLEN,
- "%s%" PRIu64, tempdir, temp_count++);
- return xstrdup(template);
+ char templ[TEMP_FILE_PATH_MAXLEN];
+ snprintf(templ, TEMP_FILE_PATH_MAXLEN, "%s%" PRIu64, tempdir, temp_count++);
+ return xstrdup(templ);
}
/// Tries matching a filename with a "pattern" ("prog" is NULL), or use the
@@ -5363,13 +3570,7 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname,
bool result = false;
regmatch.rm_ic = p_fic; // ignore case if 'fileignorecase' is set
- {
- if (prog != NULL) {
- regmatch.regprog = *prog;
- } else {
- regmatch.regprog = vim_regcomp(pattern, RE_MAGIC);
- }
- }
+ regmatch.regprog = prog != NULL ? *prog : vim_regcomp(pattern, RE_MAGIC);
// Try for a match with the pattern with:
// 1. the full file name, when the pattern has a '/'.
@@ -5377,10 +3578,10 @@ bool match_file_pat(char *pattern, regprog_T **prog, char *fname, char *sfname,
// 3. the tail of the file name, when the pattern has no '/'.
if (regmatch.regprog != NULL
&& ((allow_dirs
- && (vim_regexec(&regmatch, fname, (colnr_T)0)
+ && (vim_regexec(&regmatch, fname, 0)
|| (sfname != NULL
- && vim_regexec(&regmatch, sfname, (colnr_T)0))))
- || (!allow_dirs && vim_regexec(&regmatch, tail, (colnr_T)0)))) {
+ && vim_regexec(&regmatch, sfname, 0))))
+ || (!allow_dirs && vim_regexec(&regmatch, tail, 0)))) {
result = true;
}
@@ -5409,7 +3610,7 @@ bool match_file_list(char *list, char *sfname, char *ffname)
// try all patterns in 'wildignore'
char *p = list;
while (*p) {
- char buf[100];
+ char buf[MAXPATHL];
copy_option_part(&p, buf, ARRAY_SIZE(buf), ",");
char allow_dirs;
char *regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, false);
@@ -5544,11 +3745,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
// regexp.
// An escaped { must be unescaped since we use magic not
// verymagic. Use "\\\{n,m\}"" to get "\{n,m}".
- if (*++p == '?'
-#ifdef BACKSLASH_IN_FILENAME
- && no_bslash
-#endif
- ) {
+ if (*++p == '?' && (!BACKSLASH_IN_FILENAME_BOOL || no_bslash)) {
reg_pat[i++] = '?';
} else if (*p == ',' || *p == '%' || *p == '#'
|| ascii_isspace(*p) || *p == '{' || *p == '}') {
@@ -5559,10 +3756,7 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
p += 2;
} else {
if (allow_dirs != NULL && vim_ispathsep(*p)
-#ifdef BACKSLASH_IN_FILENAME
- && (!no_bslash || *p != '\\')
-#endif
- ) {
+ && (!BACKSLASH_IN_FILENAME_BOOL || (!no_bslash || *p != '\\'))) {
*allow_dirs = true;
}
reg_pat[i++] = '\\';
@@ -5625,35 +3819,35 @@ char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs
/// Version of read() that retries when interrupted by EINTR (possibly
/// by a SIGWINCH).
-long read_eintr(int fd, void *buf, size_t bufsize)
+int read_eintr(int fd, void *buf, size_t bufsize)
{
- long ret;
+ ssize_t ret;
- for (;;) {
- ret = read(fd, buf, bufsize);
+ while (true) {
+ ret = read(fd, buf, (unsigned)bufsize);
if (ret >= 0 || errno != EINTR) {
break;
}
}
- return ret;
+ return (int)ret;
}
/// Version of write() that retries when interrupted by EINTR (possibly
/// by a SIGWINCH).
-long write_eintr(int fd, void *buf, size_t bufsize)
+int write_eintr(int fd, void *buf, size_t bufsize)
{
- long ret = 0;
+ int ret = 0;
// Repeat the write() so long it didn't fail, other than being interrupted
// by a signal.
- while (ret < (long)bufsize) {
- long wlen = write(fd, (char *)buf + ret, bufsize - (size_t)ret);
+ while (ret < (int)bufsize) {
+ ssize_t wlen = write(fd, (char *)buf + ret, (unsigned)(bufsize - (size_t)ret));
if (wlen < 0) {
if (errno != EINTR) {
break;
}
} else {
- ret += wlen;
+ ret += (int)wlen;
}
}
return ret;
diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h
index dabcda5bf2..d1f6561507 100644
--- a/src/nvim/fileio.h
+++ b/src/nvim/fileio.h
@@ -1,11 +1,16 @@
-#ifndef NVIM_FILEIO_H
-#define NVIM_FILEIO_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/os/os.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/globals.h"
+#include "nvim/os/fs_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h"
// Values for readfile() flags
#define READ_NEW 0x01 // read a file into a new buffer
@@ -18,12 +23,30 @@
#define READ_NOWINENTER 0x80 // do not trigger BufWinEnter
#define READ_NOFILE 0x100 // do not read a file, do trigger BufReadCmd
-#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
-
typedef varnumber_T (*CheckItem)(void *expr, const char *name);
+enum {
+ FIO_LATIN1 = 0x01, // convert Latin1
+ FIO_UTF8 = 0x02, // convert UTF-8
+ FIO_UCS2 = 0x04, // convert UCS-2
+ FIO_UCS4 = 0x08, // convert UCS-4
+ FIO_UTF16 = 0x10, // convert UTF-16
+ FIO_ENDIAN_L = 0x80, // little endian
+ FIO_NOCONVERT = 0x2000, // skip encoding conversion
+ FIO_UCSBOM = 0x4000, // check for BOM at start of file
+ FIO_ALL = -1, // allow all formats
+};
+
+// When converting, a read() or write() may leave some bytes to be converted
+// for the next call. The value is guessed...
+#define CONV_RESTLEN 30
+
+#define WRITEBUFSIZE 8192 // size of normal write buffer
+
+// We have to guess how much a sequence of bytes may expand when converting
+// with iconv() to be able to allocate a buffer.
+#define ICONV_MULT 8
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
-// Events for autocommands
# include "fileio.h.generated.h"
#endif
-#endif // NVIM_FILEIO_H
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 7306131574..c905b2d3ed 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// vim: set fdm=marker fdl=1 fdc=3
// fold.c: code for folding
@@ -12,21 +9,27 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_session.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
@@ -37,15 +40,17 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/ops.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// local declarations. {{{1
// typedef fold_T {{{2
@@ -101,12 +106,12 @@ typedef void (*LevelGetter)(fline_T *);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fold.c.generated.h"
#endif
-static char *e_nofold = N_("E490: No fold found");
+static const char *e_nofold = N_("E490: No fold found");
// While updating the folds lines between invalid_top and invalid_bot have an
// undefined fold level. Only used for the window currently being updated.
-static linenr_T invalid_top = (linenr_T)0;
-static linenr_T invalid_bot = (linenr_T)0;
+static linenr_T invalid_top = 0;
+static linenr_T invalid_bot = 0;
// When using 'foldexpr' we sometimes get the level of the next line, which
// calls foldlevel() to get the level of the current line, which hasn't been
@@ -201,7 +206,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
if (first == 0) {
// Recursively search for a fold that contains "lnum".
garray_T *gap = &win->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum_rel, &fp)) {
break;
}
@@ -263,7 +268,7 @@ static int foldLevel(linenr_T lnum)
{
// While updating the folds lines between invalid_top and invalid_bot have
// an undefined fold level. Otherwise update the folds first.
- if (invalid_top == (linenr_T)0) {
+ if (invalid_top == 0) {
checkupdate(curwin);
} else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {
return prev_lnum_lvl;
@@ -358,7 +363,7 @@ int foldmethodIsDiff(win_T *wp)
// closeFold() {{{2
/// Close fold for current window at position "pos".
/// Repeat "count" times.
-void closeFold(pos_T pos, long count)
+void closeFold(pos_T pos, int count)
{
setFoldRepeat(pos, count, false);
}
@@ -412,7 +417,7 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha
// openFold() {{{2
/// Open fold for current window at position "pos".
/// Repeat "count" times.
-void openFold(pos_T pos, long count)
+void openFold(pos_T pos, int count)
{
setFoldRepeat(pos, count, true);
}
@@ -430,7 +435,7 @@ void foldOpenCursor(void)
{
checkupdate(curwin);
if (hasAnyFolding(curwin)) {
- for (;;) {
+ while (true) {
int done = DONE_NOTHING;
(void)setManualFold(curwin->w_cursor, true, false, &done);
if (!(done & DONE_ACTION)) {
@@ -562,7 +567,7 @@ void foldCreate(win_T *wp, pos_T start, pos_T end)
i = 0;
} else {
fold_T *fp;
- for (;;) {
+ while (true) {
if (!foldFind(gap, start_rel.lnum, &fp)) {
break;
}
@@ -645,7 +650,7 @@ void foldCreate(win_T *wp, pos_T start, pos_T end)
// We want the new fold to be closed. If it would remain open because
// of using 'foldlevel', need to adjust fd_flags of containing folds.
if (use_level && !closed && level < wp->w_p_fdl) {
- closeFold(start, 1L);
+ closeFold(start, 1);
}
if (!use_level) {
wp->w_fold_manual = true;
@@ -683,7 +688,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
garray_T *found_ga = NULL;
linenr_T lnum_off = 0;
bool use_level = false;
- for (;;) {
+ while (true) {
fold_T *fp;
if (!foldFind(gap, lnum - lnum_off, &fp)) {
break;
@@ -743,8 +748,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
}
if (last_lnum > 0) {
- // TODO(teto): pass the buffer
- changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L, false);
+ changed_lines(wp->w_buffer, first_lnum, 0, last_lnum, 0, false);
// send one nvim_buf_lines_event at the end
// last_lnum is the line *after* the last line of the outermost fold
@@ -771,7 +775,7 @@ void clearFolding(win_T *win)
/// The changes in lines from top to bot (inclusive).
void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
{
- if (disable_fold_update || State & MODE_INSERT) {
+ if (disable_fold_update || (State & MODE_INSERT && !foldmethodIsIndent(wp))) {
return;
}
@@ -843,7 +847,7 @@ void foldUpdateAll(win_T *win)
/// @return FAIL if not moved.
///
/// @param dir FORWARD or BACKWARD
-int foldMoveTo(const bool updown, const int dir, const long count)
+int foldMoveTo(const bool updown, const int dir, const int count)
{
int retval = FAIL;
linenr_T lnum;
@@ -852,7 +856,7 @@ int foldMoveTo(const bool updown, const int dir, const long count)
checkupdate(curwin);
// Repeat "count" times.
- for (long n = 0; n < count; n++) {
+ for (int n = 0; n < count; n++) {
// Find nested folds. Stop when a fold is closed. The deepest fold
// that moves the cursor is used.
linenr_T lnum_off = 0;
@@ -865,7 +869,7 @@ int foldMoveTo(const bool updown, const int dir, const long count)
linenr_T lnum_found = curwin->w_cursor.lnum;
int level = 0;
bool last = false;
- for (;;) {
+ while (true) {
if (!foldFind(gap, curwin->w_cursor.lnum - lnum_off, &fp)) {
if (!updown || gap->ga_len == 0) {
break;
@@ -1104,7 +1108,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum)
// Recursively search for a fold that contains "lnum".
garray_T *gap = &wp->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum_rel, &fp)) {
break;
}
@@ -1125,14 +1129,14 @@ static void checkupdate(win_T *wp)
return;
}
- foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); // will update all
+ foldUpdate(wp, 1, (linenr_T)MAXLNUM); // will update all
wp->w_foldinvalid = false;
}
// setFoldRepeat() {{{2
/// Open or close fold for current window at position `pos`.
/// Repeat "count" times.
-static void setFoldRepeat(pos_T pos, long count, int do_open)
+static void setFoldRepeat(pos_T pos, int count, int do_open)
{
for (int n = 0; n < count; n++) {
int done = DONE_NOTHING;
@@ -1201,7 +1205,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu
// Find the fold, open or close it.
garray_T *gap = &wp->w_folds;
- for (;;) {
+ while (true) {
if (!foldFind(gap, lnum, &fp)) {
// If there is a following fold, continue there next time.
if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) {
@@ -1360,7 +1364,7 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
}
// If appending a line in Insert mode, it should be included in the fold
// just above the line.
- if ((State & MODE_INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
line1--;
}
foldMarkAdjustRecurse(wp, &wp->w_folds, line1, line2, amount, amount_after);
@@ -1378,7 +1382,7 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line
// In Insert mode an inserted line at the top of a fold is considered part
// of the fold, otherwise it isn't.
- if ((State & MODE_INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) {
+ if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
top = line1 + 1;
} else {
top = line1;
@@ -1580,8 +1584,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
// Update both changes here, to avoid all folds after the start are
// changed when the start marker is inserted and the end isn't.
- // TODO(teto): pass the buffer
- changed_lines(start.lnum, (colnr_T)0, end.lnum, 0L, false);
+ changed_lines(buf, start.lnum, 0, end.lnum, 0, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
@@ -1601,7 +1604,7 @@ static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t mark
linenr_T lnum = pos.lnum;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
- char *line = ml_get_buf(buf, lnum, false);
+ char *line = ml_get_buf(buf, lnum);
size_t line_len = strlen(line);
size_t added = 0;
@@ -1661,7 +1664,7 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker
}
char *cms = buf->b_p_cms;
- char *line = ml_get_buf(buf, lnum, false);
+ char *line = ml_get_buf(buf, lnum);
for (char *p = line; *p != NUL; p++) {
if (strncmp(p, marker, markerlen) != 0) {
continue;
@@ -1704,8 +1707,9 @@ static void foldDelMarker(buf_T *buf, linenr_T lnum, char *marker, size_t marker
/// @return the text for a closed fold
///
/// Otherwise the result is in allocated memory.
-char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf)
- FUNC_ATTR_NONNULL_ARG(1)
+char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo, char *buf,
+ VirtText *vt)
+ FUNC_ATTR_NONNULL_ALL
{
char *text = NULL;
// an error occurred when evaluating 'fdt' setting
@@ -1742,15 +1746,33 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
set_vim_var_string(VV_FOLDDASHES, dashes, -1);
set_vim_var_nr(VV_FOLDLEVEL, (varnumber_T)level);
- // skip evaluating foldtext on errors
+ // skip evaluating 'foldtext' on errors
if (!got_fdt_error) {
- win_T *save_curwin = curwin;
+ win_T *const save_curwin = curwin;
+ const sctx_T saved_sctx = current_sctx;
+
curwin = wp;
curbuf = wp->w_buffer;
+ current_sctx = wp->w_p_script_ctx[WV_FDT].script_ctx;
+
+ emsg_off++; // handle exceptions, but don't display errors
+
+ Object obj = eval_foldtext(wp);
+ if (obj.type == kObjectTypeArray) {
+ Error err = ERROR_INIT;
+ *vt = parse_virt_text(obj.data.array, &err, NULL);
+ if (!ERROR_SET(&err)) {
+ *buf = NUL;
+ text = buf;
+ }
+ api_clear_error(&err);
+ } else if (obj.type == kObjectTypeString) {
+ text = obj.data.string.data;
+ obj = NIL;
+ }
+ api_free_object(obj);
- emsg_silent++; // handle exceptions, but don't display errors
- text = eval_to_string_safe(wp->w_p_fdt, NULL, was_set_insecurely(wp, "foldtext", OPT_LOCAL));
- emsg_silent--;
+ emsg_off--;
if (text == NULL || did_emsg) {
got_fdt_error = true;
@@ -1758,9 +1780,10 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
curwin = save_curwin;
curbuf = curwin->w_buffer;
+ current_sctx = saved_sctx;
}
last_lnum = lnum;
- last_wp = wp;
+ last_wp = wp;
set_vim_var_string(VV_FOLDDASHES, NULL, -1);
if (!did_emsg && save_did_emsg) {
@@ -1786,18 +1809,18 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
}
}
if (*p != NUL) {
- p = transstr((const char *)text, true);
+ p = transstr(text, true);
xfree(text);
text = p;
}
}
}
if (text == NULL) {
- long count = lnume - lnum + 1;
+ int count = lnume - lnum + 1;
vim_snprintf(buf, FOLD_TEXT_LEN,
- NGETTEXT("+--%3ld line folded",
- "+--%3ld lines folded ", count),
+ NGETTEXT("+--%3d line folded",
+ "+--%3d lines folded ", count),
count);
text = buf;
}
@@ -1888,7 +1911,7 @@ static void foldtext_cleanup(char *str)
static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
{
// Avoid problems when being called recursively.
- if (invalid_top != (linenr_T)0) {
+ if (invalid_top != 0) {
return;
}
@@ -2109,7 +2132,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
}
}
- invalid_top = (linenr_T)0;
+ invalid_top = 0;
}
// foldUpdateIEMSRecurse() {{{2
@@ -2271,12 +2294,12 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
// We will move the start of this fold up, hence we move all
// nested folds (with relative line numbers) down.
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
- (linenr_T)0, (linenr_T)MAXLNUM,
- (fp->fd_top - firstlnum), 0L);
+ 0, (linenr_T)MAXLNUM,
+ (fp->fd_top - firstlnum), 0);
} else {
// Will move fold down, move nested folds relatively up.
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
- (linenr_T)0,
+ 0,
(firstlnum - fp->fd_top - 1),
(linenr_T)MAXLNUM,
(fp->fd_top - firstlnum));
@@ -2344,7 +2367,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
fp->fd_len = startlnum - fp->fd_top;
foldMarkAdjustRecurse(flp->wp, &fp->fd_nested,
fp->fd_len, (linenr_T)MAXLNUM,
- (linenr_T)MAXLNUM, 0L);
+ (linenr_T)MAXLNUM, 0);
fold_changed = true;
}
} else {
@@ -2504,7 +2527,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
}
// delete following folds that end before the current line
- for (;;) {
+ while (true) {
fp2 = fp + 1;
if (fp2 >= (fold_T *)gap->ga_data + gap->ga_len
|| fp2->fd_top > flp->lnum) {
@@ -2514,7 +2537,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
if (fp2->fd_top < flp->lnum) {
// Make fold that includes lnum start at lnum.
foldMarkAdjustRecurse(flp->wp, &fp2->fd_nested,
- (linenr_T)0, (flp->lnum - fp2->fd_top - 1),
+ 0, (flp->lnum - fp2->fd_top - 1),
(linenr_T)MAXLNUM, (fp2->fd_top - flp->lnum));
fp2->fd_len -= flp->lnum - fp2->fd_top;
fp2->fd_top = flp->lnum;
@@ -2652,7 +2675,7 @@ static void foldRemove(win_T *const wp, garray_T *gap, linenr_T top, linenr_T bo
if (fp->fd_top + fp->fd_len - 1 > bot) {
// 5: Make fold that includes bot start below bot.
foldMarkAdjustRecurse(wp, &fp->fd_nested,
- (linenr_T)0, (bot - fp->fd_top),
+ 0, (bot - fp->fd_top),
(linenr_T)MAXLNUM, (fp->fd_top - bot - 1));
fp->fd_len -= bot - fp->fd_top + 1;
fp->fd_top = bot + 1;
@@ -2718,7 +2741,6 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
-// -V:VALID_FOLD:V560
#define VALID_FOLD(fp, gap) \
((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)((fp) - ((fold_T *)(gap)->ga_data)))
@@ -2838,7 +2860,7 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2)
// If the last nested fold in fp1 touches the first nested fold in fp2,
// merge them recursively.
- if (foldFind(gap1, fp1->fd_len - 1, &fp3) && foldFind(gap2, 0L, &fp4)) {
+ if (foldFind(gap1, fp1->fd_len - 1, &fp3) && foldFind(gap2, 0, &fp4)) {
foldMerge(wp, fp3, gap2, fp4);
}
@@ -2869,7 +2891,7 @@ static void foldlevelIndent(fline_T *flp)
linenr_T lnum = flp->lnum + flp->off;
buf_T *buf = flp->wp->w_buffer;
- char *s = skipwhite(ml_get_buf(buf, lnum, false));
+ char *s = skipwhite(ml_get_buf(buf, lnum));
// empty line or lines starting with a character in 'foldignore': level
// depends on surrounding lines
@@ -2926,7 +2948,7 @@ static void foldlevelExpr(fline_T *flp)
const bool save_keytyped = KeyTyped;
int c;
- const int n = eval_foldexpr(flp->wp->w_p_fde, &c);
+ const int n = eval_foldexpr(flp->wp, &c);
KeyTyped = save_keytyped;
switch (c) {
@@ -3031,7 +3053,7 @@ static void foldlevelMarker(fline_T *flp)
flp->start = 0;
flp->lvl_next = flp->lvl;
- char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, false);
+ char *s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off);
while (*s) {
if (*s == cstart
&& strncmp(s + 1, startmarker, foldstartmarkerlen - 1) == 0) {
@@ -3109,7 +3131,7 @@ int put_folds(FILE *fd, win_T *wp)
{
if (foldmethodIsManual(wp)) {
if (put_line(fd, "silent! normal! zE") == FAIL
- || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL
+ || put_folds_recurse(fd, &wp->w_folds, 0) == FAIL
|| put_line(fd, "let &fdl = &fdl") == FAIL) {
return FAIL;
}
@@ -3117,7 +3139,7 @@ int put_folds(FILE *fd, win_T *wp)
// If some folds are manually opened/closed, need to restore that.
if (wp->w_fold_manual) {
- return put_foldopen_recurse(fd, wp, &wp->w_folds, (linenr_T)0);
+ return put_foldopen_recurse(fd, wp, &wp->w_folds, 0);
}
return OK;
@@ -3281,8 +3303,8 @@ void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
}
- long count = foldend - foldstart + 1;
- char *txt = NGETTEXT("+-%s%3ld line: ", "+-%s%3ld lines: ", count);
+ int count = foldend - foldstart + 1;
+ char *txt = NGETTEXT("+-%s%3d line: ", "+-%s%3d lines: ", count);
size_t len = strlen(txt)
+ strlen(dashes) // for %s
+ 20 // for %3ld
@@ -3317,10 +3339,25 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
foldinfo_T info = fold_info(curwin, lnum);
if (info.fi_lines > 0) {
- char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf);
+ VirtText vt = VIRTTEXT_EMPTY;
+ char *text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf, &vt);
if (text == buf) {
text = xstrdup(text);
}
+ if (kv_size(vt) > 0) {
+ assert(*text == NUL);
+ for (size_t i = 0; i < kv_size(vt);) {
+ int attr = 0;
+ char *new_text = next_virt_text_chunk(vt, &i, &attr);
+ if (new_text == NULL) {
+ break;
+ }
+ new_text = concat_str(text, new_text);
+ xfree(text);
+ text = new_text;
+ }
+ }
+ clear_virttext(&vt);
rettv->vval.v_string = text;
}
diff --git a/src/nvim/fold.h b/src/nvim/fold.h
index ac1e8c9419..3a70c11792 100644
--- a/src/nvim/fold.h
+++ b/src/nvim/fold.h
@@ -1,28 +1,16 @@
-#ifndef NVIM_FOLD_H
-#define NVIM_FOLD_H
+#pragma once
#include <stdio.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/garray.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/fold_defs.h" // IWYU pragma: export
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-// Info used to pass info about a fold from the fold-detection code to the
-// code that displays the foldcolumn.
-typedef struct foldinfo {
- linenr_T fi_lnum; // line number where fold starts
- int fi_level; // level of the fold; when this is zero the
- // other fields are invalid
- int fi_low_level; // lowest fold level that starts in the same
- // line
- linenr_T fi_lines;
-} foldinfo_T;
-
-EXTERN int disable_fold_update INIT(= 0);
+EXTERN int disable_fold_update INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "fold.h.generated.h"
#endif
-#endif // NVIM_FOLD_H
diff --git a/src/nvim/fold_defs.h b/src/nvim/fold_defs.h
new file mode 100644
index 0000000000..68ecd9cc7e
--- /dev/null
+++ b/src/nvim/fold_defs.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "nvim/pos_defs.h"
+
+/// Info used to pass info about a fold from the fold-detection code to the
+/// code that displays the foldcolumn.
+typedef struct foldinfo {
+ linenr_T fi_lnum; ///< line number where fold starts
+ int fi_level; ///< level of the fold; when this is zero the
+ ///< other fields are invalid
+ int fi_low_level; ///< lowest fold level that starts in the same line
+ linenr_T fi_lines;
+} foldinfo_T;
+
+enum { FOLD_TEXT_LEN = 51, }; ///< buffer size for get_foldtext()
diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h
index 6c049df6ff..15370dcb3e 100644
--- a/src/nvim/func_attr.h
+++ b/src/nvim/func_attr.h
@@ -41,7 +41,7 @@
// $ gcc -E -dM - </dev/null
// $ echo | clang -dM -E -
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#ifdef FUNC_ATTR_MALLOC
# undef FUNC_ATTR_MALLOC
@@ -99,6 +99,10 @@
# undef FUNC_ATTR_NO_SANITIZE_UNDEFINED
#endif
+#ifdef FUNC_ATTR_NO_SANITIZE_ADDRESS
+# undef FUNC_ATTR_NO_SANITIZE_ADDRESS
+#endif
+
#ifdef FUNC_ATTR_PRINTF
# undef FUNC_ATTR_PRINTF
#endif
@@ -121,7 +125,7 @@
# define REAL_FATTR_NONNULL_ALL __attribute__((nonnull))
# define REAL_FATTR_NONNULL_ARG(...) __attribute__((nonnull(__VA_ARGS__)))
# define REAL_FATTR_NORETURN __attribute__((noreturn))
-# define REAL_FATTR_PRINTF(x, y) __attribute__((format (printf, x, y)))
+# define REAL_FATTR_PRINTF(x, y) __attribute__((format(printf, x, y)))
# if NVIM_HAS_ATTRIBUTE(returns_nonnull)
# define REAL_FATTR_NONNULL_RET __attribute__((returns_nonnull))
@@ -139,6 +143,11 @@
# define REAL_FATTR_NO_SANITIZE_UNDEFINED \
__attribute__((no_sanitize("undefined")))
# endif
+
+# if NVIM_HAS_ATTRIBUTE(no_sanitize_address)
+# define REAL_FATTR_NO_SANITIZE_ADDRESS \
+ __attribute__((no_sanitize_address))
+# endif
# endif
// Define attributes that are not defined for this compiler.
@@ -199,6 +208,10 @@
# define REAL_FATTR_NO_SANITIZE_UNDEFINED
# endif
+# ifndef REAL_FATTR_NO_SANITIZE_ADDRESS
+# define REAL_FATTR_NO_SANITIZE_ADDRESS
+# endif
+
# ifndef REAL_FATTR_PRINTF
# define REAL_FATTR_PRINTF(x, y)
# endif
@@ -209,12 +222,14 @@
# define FUNC_API_FAST
/// Internal C function not exposed in the RPC API.
# define FUNC_API_NOEXPORT
-/// API function not exposed in VimL/eval.
+/// API function not exposed in Vimscript/eval.
# define FUNC_API_REMOTE_ONLY
-/// API function not exposed in VimL/remote.
+/// API function not exposed in Vimscript/remote.
# define FUNC_API_LUA_ONLY
-/// API function checked textlock.
-# define FUNC_API_CHECK_TEXTLOCK
+/// API function fails during textlock.
+# define FUNC_API_TEXTLOCK
+/// API function fails during textlock, but allows cmdwin.
+# define FUNC_API_TEXTLOCK_ALLOW_CMDWIN
/// API function introduced at the given API level.
# define FUNC_API_SINCE(X)
/// API function deprecated since the given API level.
@@ -233,6 +248,7 @@
# define FUNC_ATTR_NONNULL_RET REAL_FATTR_NONNULL_RET
# define FUNC_ATTR_NORETURN REAL_FATTR_NORETURN
# define FUNC_ATTR_NO_SANITIZE_UNDEFINED REAL_FATTR_NO_SANITIZE_UNDEFINED
+# define FUNC_ATTR_NO_SANITIZE_ADDRESS REAL_FATTR_NO_SANITIZE_ADDRESS
# define FUNC_ATTR_PRINTF(x, y) REAL_FATTR_PRINTF(x, y)
#elif !defined(DO_NOT_DEFINE_EMPTY_ATTRIBUTES)
# define FUNC_ATTR_MALLOC
@@ -249,5 +265,6 @@
# define FUNC_ATTR_NONNULL_RET
# define FUNC_ATTR_NORETURN
# define FUNC_ATTR_NO_SANITIZE_UNDEFINED
+# define FUNC_ATTR_NO_SANITIZE_ADDRESS
# define FUNC_ATTR_PRINTF(x, y)
#endif
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index aa9a44d410..24b6fb0007 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -1,18 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file garray.c
///
/// Functions for handling growing arrays.
+#include <stdint.h>
#include <string.h>
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "garray.c.generated.h" // IWYU pragma: export
diff --git a/src/nvim/garray.h b/src/nvim/garray.h
index 1623c4db7b..a96deda759 100644
--- a/src/nvim/garray.h
+++ b/src/nvim/garray.h
@@ -1,26 +1,12 @@
-#ifndef NVIM_GARRAY_H
-#define NVIM_GARRAY_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
+#include "nvim/garray_defs.h" // IWYU pragma: export
#include "nvim/log.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-
-/// Structure used for growing arrays.
-/// This is used to store information that only grows, is deleted all at
-/// once, and needs to be accessed by index. See ga_clear() and ga_grow().
-typedef struct growarray {
- int ga_len; // current number of items used
- int ga_maxlen; // maximum number of items possible
- int ga_itemsize; // sizeof(item)
- int ga_growsize; // number of items to grow each time
- void *ga_data; // pointer to the first item
-} garray_T;
-
-#define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL }
-#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL }
+#include "nvim/types_defs.h"
#define GA_EMPTY(ga_ptr) ((ga_ptr)->ga_len <= 0)
@@ -52,7 +38,7 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size)
///
/// @param gap the garray to be freed
/// @param item_type type of the item in the garray
-/// @param free_item_fn free function that takes (*item_type) as parameter
+/// @param free_item_fn free function that takes (item_type *) as parameter
#define GA_DEEP_CLEAR(gap, item_type, free_item_fn) \
do { \
garray_T *_gap = (gap); \
@@ -72,5 +58,3 @@ static inline void *ga_append_via_ptr(garray_T *gap, size_t item_size)
///
/// @param gap the garray to be freed
#define GA_DEEP_CLEAR_PTR(gap) GA_DEEP_CLEAR(gap, void *, FREE_PTR_PTR)
-
-#endif // NVIM_GARRAY_H
diff --git a/src/nvim/garray_defs.h b/src/nvim/garray_defs.h
new file mode 100644
index 0000000000..5f4032884e
--- /dev/null
+++ b/src/nvim/garray_defs.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <stddef.h>
+
+/// Structure used for growing arrays.
+/// This is used to store information that only grows, is deleted all at
+/// once, and needs to be accessed by index. See ga_clear() and ga_grow().
+typedef struct growarray {
+ int ga_len; // current number of items used
+ int ga_maxlen; // maximum number of items possible
+ int ga_itemsize; // sizeof(item)
+ int ga_growsize; // number of items to grow each time
+ void *ga_data; // pointer to the first item
+} garray_T;
+
+#define GA_EMPTY_INIT_VALUE { 0, 0, 0, 1, NULL }
+#define GA_INIT(itemsize, growsize) { 0, 0, (itemsize), (growsize), NULL }
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 3e89b60b4a..f33da452ff 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -1,4 +1,4 @@
-local lpeg = require('lpeg')
+local lpeg = vim.lpeg
-- lpeg grammar for building api metadata from a set of header files. It
-- ignores comments and preprocessor commands and parses a very small subset
@@ -47,7 +47,8 @@ local c_proto = Ct(
(fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) *
(fill * Cg((P('FUNC_API_LUA_ONLY') * Cc(true)), 'lua_only') ^ -1) *
- (fill * Cg((P('FUNC_API_CHECK_TEXTLOCK') * Cc(true)), 'check_textlock') ^ -1) *
+ (fill * (Cg(P('FUNC_API_TEXTLOCK_ALLOW_CMDWIN') * Cc(true), 'textlock_allow_cmdwin') +
+ Cg(P('FUNC_API_TEXTLOCK') * Cc(true), 'textlock')) ^ -1) *
(fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) *
(fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) *
(fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) *
@@ -55,5 +56,11 @@ local c_proto = Ct(
fill * P(';')
)
-local grammar = Ct((c_proto + c_comment + c_preproc + ws) ^ 1)
+local c_field = Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * P(';') * fill)
+local c_keyset = Ct(
+ P('typedef') * ws * P('struct') * fill * P('{') * fill *
+ Cg(Ct(c_field ^ 1), 'fields') *
+ P('}') * fill * P('Dict') * fill * P('(') * Cg(c_id, 'keyset_name') * fill * P(')') * P(';'))
+
+local grammar = Ct((c_proto + c_comment + c_preproc + ws + c_keyset) ^ 1)
return {grammar=grammar, typed_container=typed_container}
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 35f6bf8455..9720cca477 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -1,38 +1,23 @@
-local mpack = require('mpack')
-
--- we need at least 4 arguments since the last two are output files
-if arg[1] == '--help' then
- print('Usage: genmsgpack.lua args')
- print('Args: 1: source directory')
- print(' 2: dispatch output file (dispatch_wrappers.generated.h)')
- print(' 3: functions metadata output file (funcs_metadata.generated.h)')
- print(' 4: API metadata output file (api_metadata.mpack)')
- print(' 5: lua C bindings output file (lua_api_c_bindings.generated.c)')
- print(' rest: C files where API functions are defined')
-end
-assert(#arg >= 4)
-local functions = {}
+local mpack = vim.mpack
-local nvimdir = arg[1]
-package.path = nvimdir .. '/?.lua;' .. package.path
+local hashy = require'generators.hashy'
-_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
-_G.vim.inspect = loadfile(nvimdir..'/../../runtime/lua/vim/inspect.lua')()
+assert(#arg >= 5)
+-- output h file with generated dispatch functions (dispatch_wrappers.generated.h)
+local dispatch_outputf = arg[1]
+-- output h file with packed metadata (funcs_metadata.generated.h)
+local funcs_metadata_outputf = arg[2]
+-- output metadata mpack file, for use by other build scripts (api_metadata.mpack)
+local mpack_outputf = arg[3]
+local lua_c_bindings_outputf = arg[4] -- lua_api_c_bindings.generated.c
+local keysets_outputf = arg[5] -- keysets_defs.generated.h
-local hashy = require'generators.hashy'
+local functions = {}
-- names of all headers relative to the source root (for inclusion in the
-- generated file)
local headers = {}
--- output h file with generated dispatch functions
-local dispatch_outputf = arg[2]
--- output h file with packed metadata
-local funcs_metadata_outputf = arg[3]
--- output metadata mpack file, for use by other build scripts
-local mpack_outputf = arg[4]
-local lua_c_bindings_outputf = arg[5]
-
-- set of function names, used to detect duplicates
local function_names = {}
@@ -42,6 +27,69 @@ local function startswith(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
+local function add_function(fn)
+ local public = startswith(fn.name, "nvim_") or fn.deprecated_since
+ if public and not fn.noexport then
+ functions[#functions + 1] = fn
+ function_names[fn.name] = true
+ if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
+ -- function receives the "args" as a parameter
+ fn.receives_array_args = true
+ -- remove the args parameter
+ table.remove(fn.parameters, 2)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
+ -- this function should receive the channel id
+ fn.receives_channel_id = true
+ -- remove the parameter since it won't be passed by the api client
+ table.remove(fn.parameters, 1)
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
+ -- function can fail if the last parameter type is 'Error'
+ fn.can_fail = true
+ -- remove the error parameter, msgpack has it's own special field
+ -- for specifying errors
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
+ -- return value is allocated in an arena
+ fn.arena_return = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
+ fn.has_lua_imp = true
+ fn.parameters[#fn.parameters] = nil
+ end
+ end
+end
+
+local keysets = {}
+
+local function add_keyset(val)
+ local keys = {}
+ local types = {}
+ local is_set_name = 'is_set__' .. val.keyset_name .. '_'
+ local has_optional = false
+ for i,field in ipairs(val.fields) do
+ if field.type ~= 'Object' then
+ types[field.name] = field.type
+ end
+ if field.name ~= is_set_name and field.type ~= 'OptionalKeys' then
+ table.insert(keys, field.name)
+ else
+ if i > 1 then
+ error("'is_set__{type}_' must be first if present")
+ elseif field.name ~= is_set_name then
+ error(val.keyset_name..": name of first key should be "..is_set_name)
+ elseif field.type ~= 'OptionalKeys' then
+ error("'"..is_set_name.."' must have type 'OptionalKeys'")
+ end
+ has_optional = true
+ end
+ end
+ table.insert(keysets, {name=val.keyset_name, keys=keys, types=types, has_optional=has_optional})
+end
+
-- read each input file, parse and append to the api metadata
for i = 6, #arg do
local full_path = arg[i]
@@ -55,39 +103,11 @@ for i = 6, #arg do
local tmp = c_grammar.grammar:match(input:read('*all'))
for j = 1, #tmp do
- local fn = tmp[j]
- local public = startswith(fn.name, "nvim_") or fn.deprecated_since
- if public and not fn.noexport then
- functions[#functions + 1] = tmp[j]
- function_names[fn.name] = true
- if #fn.parameters >= 2 and fn.parameters[2][1] == 'Array' and fn.parameters[2][2] == 'uidata' then
- -- function receives the "args" as a parameter
- fn.receives_array_args = true
- -- remove the args parameter
- table.remove(fn.parameters, 2)
- end
- if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then
- -- this function should receive the channel id
- fn.receives_channel_id = true
- -- remove the parameter since it won't be passed by the api client
- table.remove(fn.parameters, 1)
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'error' then
- -- function can fail if the last parameter type is 'Error'
- fn.can_fail = true
- -- remove the error parameter, msgpack has it's own special field
- -- for specifying errors
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'arena' then
- -- return value is allocated in an arena
- fn.arena_return = true
- fn.parameters[#fn.parameters] = nil
- end
- if #fn.parameters ~= 0 and fn.parameters[#fn.parameters][1] == 'lstate' then
- fn.has_lua_imp = true
- fn.parameters[#fn.parameters] = nil
- end
+ local val = tmp[j]
+ if val.keyset_name then
+ add_keyset(val)
+ else
+ add_function(val)
end
end
input:close()
@@ -187,7 +207,7 @@ end
-- serialize the API metadata using msgpack and embed into the resulting
-- binary for easy querying by clients
local funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb')
-local packed = mpack.pack(exported_functions)
+local packed = mpack.encode(exported_functions)
local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed)
funcs_metadata_output:close()
@@ -195,6 +215,7 @@ funcs_metadata_output:close()
-- start building the dispatch wrapper output
local output = io.open(dispatch_outputf, 'wb')
+local keysets_defs = io.open(keysets_outputf, 'wb')
-- ===========================================================================
-- NEW API FILES MUST GO HERE.
@@ -203,10 +224,11 @@ local output = io.open(dispatch_outputf, 'wb')
-- so that the dispatcher can find the C functions that you are creating!
-- ===========================================================================
output:write([[
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/log.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/vim.h"
#include "nvim/api/autocmd.h"
#include "nvim/api/buffer.h"
@@ -224,6 +246,62 @@ output:write([[
]])
+for _,k in ipairs(keysets) do
+ local c_name = {}
+
+ for i = 1,#k.keys do
+ -- some keys, like "register" are c keywords and get
+ -- escaped with a trailing _ in the struct.
+ if vim.endswith(k.keys[i], "_") then
+ local orig = k.keys[i]
+ k.keys[i] = string.sub(k.keys[i],1, #(k.keys[i]) - 1)
+ c_name[k.keys[i]] = orig
+ k.types[k.keys[i]] = k.types[orig]
+ end
+ end
+
+ local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function (idx)
+ return k.name.."_table["..idx.."].str"
+ end)
+
+ keysets_defs:write("extern KeySetLink "..k.name.."_table[];\n")
+
+ local function typename(type)
+ if type ~= nil then
+ return "kObjectType"..type
+ else
+ return "kObjectTypeNil"
+ end
+ end
+
+ output:write("KeySetLink "..k.name.."_table[] = {\n")
+ for i, key in ipairs(neworder) do
+ local ind = -1
+ if k.has_optional then
+ ind = i
+ keysets_defs:write("#define KEYSET_OPTIDX_"..k.name.."__"..key.." "..ind.."\n")
+ end
+ output:write(' {"'..key..'", offsetof(KeyDict_'..k.name..", "..(c_name[key] or key).."), "..typename(k.types[key])..", "..ind.."},\n")
+ end
+ output:write(' {NULL, 0, kObjectTypeNil, -1},\n')
+ output:write("};\n\n")
+
+ output:write(hashfun)
+
+ output:write([[
+KeySetLink *KeyDict_]]..k.name..[[_get_field(const char *str, size_t len)
+{
+ int hash = ]]..k.name..[[_hash(str, len);
+ if (hash == -1) {
+ return NULL;
+ }
+ return &]]..k.name..[[_table[hash];
+}
+
+]])
+ keysets_defs:write("#define api_free_keydict_"..k.name.."(x) api_free_keydict(x, "..k.name.."_table)\n")
+end
+
local function real_type(type)
local rv = type
local rmatch = string.match(type, "Dict%(([_%w]+)%)")
@@ -257,9 +335,8 @@ for i = 1, #functions do
output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Arena* arena, Error *error)')
output:write('\n{')
- output:write('\n#if MIN_LOG_LEVEL <= LOGLVL_DBG')
- output:write('\n logmsg(LOGLVL_DBG, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke '
- ..fn.name..'", channel_id);')
+ output:write('\n#ifdef NVIM_LOG_DEBUG')
+ output:write('\n DLOG("RPC: ch %" PRIu64 ": invoke '..fn.name..'", channel_id);')
output:write('\n#endif')
output:write('\n Object ret = NIL;')
-- Declare/initialize variables that will hold converted arguments
@@ -337,8 +414,13 @@ for i = 1, #functions do
args[#args + 1] = converted
end
- if fn.check_textlock then
- output:write('\n if (textlock != 0) {')
+ if fn.textlock then
+ output:write('\n if (text_locked()) {')
+ output:write('\n api_set_error(error, kErrorTypeException, "%s", get_text_locked_msg());')
+ output:write('\n goto cleanup;')
+ output:write('\n }\n')
+ elseif fn.textlock_allow_cmdwin then
+ output:write('\n if (textlock != 0 || expr_map_locked()) {')
output:write('\n api_set_error(error, kErrorTypeException, "%s", e_textlock);')
output:write('\n goto cleanup;')
output:write('\n }\n')
@@ -444,8 +526,9 @@ output:write(hashfun)
output:close()
+functions.keysets = keysets
local mpack_output = io.open(mpack_outputf, 'wb')
-mpack_output:write(mpack.pack(functions))
+mpack_output:write(mpack.encode(functions))
mpack_output:close()
local function include_headers(output_handle, headers_to_include)
@@ -467,15 +550,16 @@ end
output = io.open(lua_c_bindings_outputf, 'wb')
output:write([[
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
+#include "nvim/ex_docmd.h"
+#include "nvim/ex_getln.h"
#include "nvim/func_attr.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/memory.h"
@@ -493,6 +577,7 @@ local function process_function(fn)
static int %s(lua_State *lstate)
{
Error err = ERROR_INIT;
+ char *err_param = 0;
if (lua_gettop(lstate) != %i) {
api_set_error(&err, kErrorTypeValidation, "Expected %i argument%s");
goto exit_0;
@@ -512,9 +597,16 @@ local function process_function(fn)
]], fn.name))
end
- if fn.check_textlock then
+ if fn.textlock then
write_shifted_output(output, [[
- if (textlock != 0) {
+ if (text_locked()) {
+ api_set_error(&err, kErrorTypeException, "%s", get_text_locked_msg());
+ goto exit_0;
+ }
+ ]])
+ elseif fn.textlock_allow_cmdwin then
+ write_shifted_output(output, [[
+ if (textlock != 0 || expr_map_locked()) {
api_set_error(&err, kErrorTypeException, "%s", e_textlock);
goto exit_0;
}
@@ -533,19 +625,22 @@ local function process_function(fn)
extra = "true, "
end
local errshift = 0
+ local seterr = ''
if string.match(param_type, '^KeyDict_') then
write_shifted_output(output, string.format([[
- %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, %s&err);]], param_type, cparam, cparam, param_type, extra))
+ %s %s = { 0 }; nlua_pop_keydict(lstate, &%s, %s_get_field, &err_param, &err);]], param_type, cparam, cparam, param_type))
cparam = '&'..cparam
errshift = 1 -- free incomplete dict on error
else
write_shifted_output(output, string.format([[
const %s %s = nlua_pop_%s(lstate, %s&err);]], param[1], cparam, param_type, extra))
+ seterr = [[
+ err_param = "]]..param[2]..[[";]]
end
write_shifted_output(output, string.format([[
- if (ERROR_SET(&err)) {
+ if (ERROR_SET(&err)) {]]..seterr..[[
goto exit_%u;
}
@@ -589,9 +684,14 @@ local function process_function(fn)
exit_0:
if (ERROR_SET(&err)) {
luaL_where(lstate, 1);
+ if (err_param) {
+ lua_pushstring(lstate, "Invalid '");
+ lua_pushstring(lstate, err_param);
+ lua_pushstring(lstate, "': ");
+ }
lua_pushstring(lstate, err.msg);
api_clear_error(&err);
- lua_concat(lstate, 2);
+ lua_concat(lstate, err_param ? 5 : 2);
return lua_error(lstate);
}
]]
@@ -620,9 +720,10 @@ local function process_function(fn)
}
]], return_type))
else
+ local special = (fn.since ~= nil and fn.since < 11)
write_shifted_output(output, string.format([[
- nlua_push_%s(lstate, ret, true);
- ]], return_type))
+ nlua_push_%s(lstate, ret, %s);
+ ]], return_type, tostring(special)))
end
write_shifted_output(output, string.format([[
@@ -670,3 +771,4 @@ output:write([[
]])
output:close()
+keysets_defs:close()
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 827097f69d..e2af5f8d44 100755..100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -1,19 +1,15 @@
-local mpack = require('mpack')
+local mpack = vim.mpack
-local nvimdir = arg[1]
-package.path = nvimdir .. '/?.lua;' .. package.path
-
-assert(#arg == 6)
-local input = io.open(arg[2], 'rb')
-local call_output = io.open(arg[3], 'wb')
-local remote_output = io.open(arg[4], 'wb')
-local metadata_output = io.open(arg[5], 'wb')
-local client_output = io.open(arg[6], 'wb')
+assert(#arg == 5)
+local input = io.open(arg[1], 'rb')
+local call_output = io.open(arg[2], 'wb')
+local remote_output = io.open(arg[3], 'wb')
+local metadata_output = io.open(arg[4], 'wb')
+local client_output = io.open(arg[5], 'wb')
local c_grammar = require('generators.c_grammar')
local events = c_grammar.grammar:match(input:read('*all'))
-_G.vim = loadfile(nvimdir..'/../../runtime/lua/vim/shared.lua')()
local hashy = require'generators.hashy'
local function write_signature(output, ev, prefix, notype)
@@ -120,7 +116,6 @@ for i = 1, #events do
if ev.remote_only then
call_output:write(' Array args = call_buf;\n')
write_arglist(call_output, ev)
- call_output:write(' UI_LOG('..ev.name..');\n')
call_output:write(' ui_call_event("'..ev.name..'", args);\n')
elseif ev.compositor_impl then
call_output:write(' ui_comp_'..ev.name)
@@ -195,7 +190,7 @@ for _,ev in ipairs(events) do
end
end
-local packed = mpack.pack(exported_events)
+local packed = mpack.encode(exported_events)
local dump_bin_array = require("generators.dump_bin_array")
dump_bin_array(metadata_output, 'ui_events_metadata', packed)
metadata_output:close()
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index 4097ff7dc5..f9e9c6b0a8 100755..100644
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -1,12 +1,9 @@
-#!/usr/bin/lua
-
local fname = arg[1]
local static_fname = arg[2]
local non_static_fname = arg[3]
local preproc_fname = arg[4]
-
-local lpeg = require('lpeg')
+local lpeg = vim.lpeg
local fold = function (func, ...)
local result = nil
@@ -174,7 +171,7 @@ static functions into static.h and declarations for non-static functions into
non-static.h. File `definitions.i' should contain an already preprocessed
version of definitions.c and it is the only one which is actually parsed,
definitions.c is needed only to determine functions from which file out of all
-functions found in definitions.i are needed.
+functions found in definitions.i are needed and to generate an IWYU comment.
Additionally uses the following environment variables:
@@ -227,6 +224,18 @@ local non_static = header .. [[
local static = header
+if fname:find('.*/src/nvim/.*%.c$') then
+ -- Add an IWYU pragma comment if the corresponding .h file exists.
+ local header_fname = fname:sub(1, -3) .. '.h'
+ local header_f = io.open(header_fname, 'r')
+ if header_f ~= nil then
+ header_f:close()
+ non_static = ([[
+// IWYU pragma: private, include "%s"
+]]):format(header_fname:gsub('.*/src/nvim/', 'nvim/')) .. non_static
+ end
+end
+
local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"'
local init = 1
@@ -244,12 +253,7 @@ while init ~= nil do
curfile = file
is_needed_file = (curfile == neededfile)
declline = tonumber(line) - 1
- local curdir_start = dir:find('src/nvim/')
- if curdir_start ~= nil then
- curdir = dir:sub(curdir_start + #('src/nvim/'))
- else
- curdir = dir
- end
+ curdir = dir:gsub('.*/src/nvim/', '')
else
declline = declline - 1
end
@@ -311,7 +315,7 @@ F = io.open(static_fname, 'w')
F:write(static)
F:close()
--- Before generating the non-static headers, check if the current file(if
+-- Before generating the non-static headers, check if the current file (if
-- exists) is different from the new one. If they are the same, we won't touch
-- the current version to avoid triggering an unnecessary rebuilds of modules
-- that depend on this one
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index baed6a74c2..7b272c337e 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -1,37 +1,23 @@
-local mpack = require('mpack')
+local mpack = vim.mpack
-local nvimsrcdir = arg[1]
-local shared_file = arg[2]
-local autodir = arg[3]
-local metadata_file = arg[4]
-local funcs_file = arg[5]
-
-_G.vim = loadfile(shared_file)()
-
-if nvimsrcdir == '--help' then
- print([[
-Usage:
- lua gen_eval.lua src/nvim build/src/nvim/auto
-
-Will generate build/src/nvim/auto/funcs.generated.h with definition of functions
-static const array.
-]])
- os.exit(0)
-end
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local autodir = arg[1]
+local metadata_file = arg[2]
+local funcs_file = arg[3]
local funcsfname = autodir .. '/funcs.generated.h'
+--Will generate funcs.generated.h with definition of functions static const array.
+
local hashy = require'generators.hashy'
-local hashpipe = io.open(funcsfname, 'wb')
+local hashpipe = assert(io.open(funcsfname, 'wb'))
hashpipe:write([[
#include "nvim/arglist.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdhist.h"
#include "nvim/digraph.h"
+#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
@@ -46,11 +32,16 @@ hashpipe:write([[
#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/menu.h"
+#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/quickfix.h"
+#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state.h"
+#include "nvim/strings.h"
#include "nvim/sign.h"
#include "nvim/testing.h"
+#include "nvim/undo.h"
]])
@@ -62,7 +53,7 @@ for _, func in pairs(funcs) do
end
end
-local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all"))
+local metadata = mpack.decode(io.open(metadata_file, 'rb'):read("*all"))
for _,fun in ipairs(metadata) do
if fun.eval then
funcs[fun.name] = {
@@ -73,16 +64,22 @@ for _,fun in ipairs(metadata) do
end
end
-local func_names = vim.tbl_keys(funcs)
+local func_names = vim.tbl_filter(function(name)
+ return name:match('__%d*$') == nil
+end, vim.tbl_keys(funcs))
+
table.sort(func_names)
-local funcsdata = io.open(funcs_file, 'w')
-funcsdata:write(mpack.pack(func_names))
+
+local funcsdata = assert(io.open(funcs_file, 'w'))
+funcsdata:write(mpack.encode(func_names))
funcsdata:close()
local neworder, hashfun = hashy.hashy_hash("find_internal_func", func_names, function (idx)
return "functions["..idx.."].name"
end)
+
hashpipe:write("static const EvalFuncDef functions[] = {\n")
+
for _, name in ipairs(neworder) do
local def = funcs[name]
local args = def.args or 0
@@ -93,12 +90,12 @@ for _, name in ipairs(neworder) do
end
local base = def.base or "BASE_NONE"
local func = def.func or ('f_' .. name)
- local data = def.data or "{ .nullptr = NULL }"
+ local data = def.data or "{ .null = NULL }"
local fast = def.fast and 'true' or 'false'
hashpipe:write((' { "%s", %s, %s, %s, %s, &%s, %s },\n')
:format(name, args[1], args[2], base, fast, func, data))
end
-hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .nullptr = NULL } },\n')
+hashpipe:write(' { NULL, 0, 0, BASE_NONE, false, NULL, { .null = NULL } },\n')
hashpipe:write("};\n\n")
hashpipe:write(hashfun)
hashpipe:close()
diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua
index 8db7f22452..4763a2f463 100644
--- a/src/nvim/generators/gen_events.lua
+++ b/src/nvim/generators/gen_events.lua
@@ -1,13 +1,5 @@
-if arg[1] == '--help' then
- print('Usage: gen_events.lua src/nvim enum_file event_names_file')
- os.exit(0)
-end
-
-local nvimsrcdir = arg[1]
-local fileio_enum_file = arg[2]
-local names_file = arg[3]
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local fileio_enum_file = arg[1]
+local names_file = arg[2]
local auevents = require('auevents')
local events = auevents.events
@@ -16,7 +8,10 @@ local aliases = auevents.aliases
local enum_tgt = io.open(fileio_enum_file, 'w')
local names_tgt = io.open(names_file, 'w')
-enum_tgt:write('typedef enum auto_event {')
+enum_tgt:write([[
+// IWYU pragma: private, include "nvim/autocmd_defs.h"
+
+typedef enum auto_event {]])
names_tgt:write([[
static const struct event_name {
size_t len;
@@ -43,27 +38,24 @@ names_tgt:write('\n {0, NULL, (event_T)0},')
enum_tgt:write('\n} event_T;\n')
names_tgt:write('\n};\n')
-local gen_autopat_events = function(name)
- names_tgt:write(string.format('\nstatic AutoPat *%s[NUM_EVENTS] = {\n ', name))
+do
+ names_tgt:write('\nstatic AutoCmdVec autocmds[NUM_EVENTS] = {\n ')
local line_len = 1
for _ = 1,((#events) - 1) do
- line_len = line_len + #(' NULL,')
+ line_len = line_len + #(' KV_INITIAL_VALUE,')
if line_len > 80 then
names_tgt:write('\n ')
- line_len = 1 + #(' NULL,')
+ line_len = 1 + #(' KV_INITIAL_VALUE,')
end
- names_tgt:write(' NULL,')
+ names_tgt:write(' KV_INITIAL_VALUE,')
end
- if line_len + #(' NULL') > 80 then
- names_tgt:write('\n NULL')
+ if line_len + #(' KV_INITIAL_VALUE') > 80 then
+ names_tgt:write('\n KV_INITIAL_VALUE')
else
- names_tgt:write(' NULL')
+ names_tgt:write(' KV_INITIAL_VALUE')
end
names_tgt:write('\n};\n')
end
-gen_autopat_events("first_autopat")
-gen_autopat_events("last_autopat")
-
enum_tgt:close()
names_tgt:close()
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 0c1051b04e..ae8c952648 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -1,20 +1,8 @@
-local nvimsrcdir = arg[1]
-local includedir = arg[2]
-local autodir = arg[3]
+local includedir = arg[1]
+local autodir = arg[2]
-if nvimsrcdir == '--help' then
- print ([[
-Usage:
- lua genex_cmds.lua src/nvim build/include build/src/nvim/auto
-
-Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T
-enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands
-definitions.
-]])
- os.exit(0)
-end
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+-- Will generate files ex_cmds_enum.generated.h with cmdidx_T enum
+-- and ex_cmds_defs.generated.h with main Ex commands definitions.
local enumfname = includedir .. '/ex_cmds_enum.generated.h'
local defsfname = autodir .. '/ex_cmds_defs.generated.h'
@@ -41,11 +29,13 @@ static const uint16_t cmdidxs1[%u] = {
-- Values in cmdidxs2[c1][c2] are relative to cmdidxs1[c1] so that they
-- fit in a byte.
local cmdidxs2_out = string.format([[
-static const char_u cmdidxs2[%u][%u] = {
+static const uint8_t cmdidxs2[%u][%u] = {
/* a b c d e f g h i j k l m n o p q r s t u v w x y z */
]], a_to_z, a_to_z)
enumfile:write([[
+// IWYU pragma: private, include "nvim/ex_cmds_defs.h"
+
typedef enum CMD_index {
]])
defsfile:write(string.format([[
@@ -66,8 +56,8 @@ defsfile:write(string.format([[
#include "nvim/ex_session.h"
#include "nvim/help.h"
#include "nvim/indent.h"
-#include "nvim/locale.h"
#include "nvim/lua/executor.h"
+#include "nvim/lua/secure.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/match.h"
@@ -75,6 +65,7 @@ defsfile:write(string.format([[
#include "nvim/message.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/os/lang.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
@@ -113,7 +104,7 @@ for _, cmd in ipairs(defs) do
end
local preview_func
if cmd.preview_func then
- preview_func = string.format("(ex_preview_func_T)&%s", cmd.preview_func)
+ preview_func = string.format("&%s", cmd.preview_func)
else
preview_func = "NULL"
end
diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua
deleted file mode 100644
index b1c1f3e2d8..0000000000
--- a/src/nvim/generators/gen_keysets.lua
+++ /dev/null
@@ -1,80 +0,0 @@
-local nvimsrcdir = arg[1]
-local shared_file = arg[2]
-local funcs_file = arg[3]
-local defs_file = arg[4]
-
-_G.vim = loadfile(shared_file)()
-
-if nvimsrcdir == '--help' then
- print([[
-Usage:
- lua gen_keyset.lua TODOFIXUPDATETHIS
-
-Will generate build/src/nvim/auto/keyset.generated.h with definition of functions
-static const array.
-]])
- os.exit(0)
-end
-
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
-local hashy = require'generators.hashy'
-
-local funcspipe = io.open(funcs_file, 'wb')
-local defspipe = io.open(defs_file, 'wb')
-
-local keysets = require'api.keysets'
-
-local keywords = {
- register = true;
- default = true;
-}
-
-local function sanitize(key)
- if keywords[key] then
- return key .. "_"
- end
- return key
-end
-
-for _, v in ipairs(keysets) do
- local name = v[1]
- local keys = v[2]
- local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx)
- return name.."_table["..idx.."].str"
- end)
-
- defspipe:write("typedef struct {\n")
- for _, key in ipairs(neworder) do
- defspipe:write(" Object "..sanitize(key)..";\n")
- end
- defspipe:write("} KeyDict_"..name..";\n\n")
-
- defspipe:write("extern KeySetLink "..name.."_table[];\n")
-
- funcspipe:write("KeySetLink "..name.."_table[] = {\n")
- for _, key in ipairs(neworder) do
- funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n")
- end
- funcspipe:write(' {NULL, 0},\n')
- funcspipe:write("};\n\n")
-
- funcspipe:write(hashfun)
-
- funcspipe:write([[
-Object *KeyDict_]]..name..[[_get_field(void *retval, const char *str, size_t len)
-{
- int hash = ]]..name..[[_hash(str, len);
- if (hash == -1) {
- return NULL;
- }
-
- return (Object *)((char *)retval + ]]..name..[[_table[hash].ptr_off);
-}
-
-]])
- defspipe:write("#define api_free_keydict_"..name.."(x) api_free_keydict(x, "..name.."_table)\n")
-end
-
-funcspipe:close()
-defspipe:close()
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index edb7dae159..26ade2745d 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -1,14 +1,6 @@
-if arg[1] == '--help' then
- print('Usage: genoptions.lua src/nvim options_file')
- os.exit(0)
-end
-
-local nvimsrcdir = arg[1]
-local options_file = arg[2]
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
+local options_file = arg[1]
-local opt_fd = io.open(options_file, 'w')
+local opt_fd = assert(io.open(options_file, 'w'))
local w = function(s)
if s:match('^ %.') then
@@ -18,6 +10,7 @@ local w = function(s)
end
end
+--- @module 'nvim.options'
local options = require('options')
local cstr = options.cstr
@@ -42,11 +35,16 @@ local redraw_flags={
local list_flags={
comma='P_COMMA',
onecomma='P_ONECOMMA',
+ commacolon='P_COMMA|P_COLON',
+ onecommacolon='P_ONECOMMA|P_COLON',
flags='P_FLAGLIST',
flagscomma='P_COMMA|P_FLAGLIST',
}
-local get_flags = function(o)
+--- @param o vim.option_meta
+--- @return string
+local function get_flags(o)
+ --- @type string[]
local ret = {type_flags[o.type]}
local add_flag = function(f)
ret[1] = ret[1] .. '|' .. f
@@ -89,8 +87,10 @@ local get_flags = function(o)
return ret[1]
end
-local get_cond
-get_cond = function(c, base_string)
+--- @param c string|string[]
+--- @param base_string? string
+--- @return string
+local function get_cond(c, base_string)
local cond_string = base_string or '#if '
if type(c) == 'table' then
cond_string = cond_string .. get_cond(c[1], '')
@@ -112,11 +112,11 @@ local value_dumpers = {
string=cstr,
boolean=function(v) return v and 'true' or 'false' end,
number=function(v) return ('%iL'):format(v) end,
- ['nil']=function(_) return '0L' end,
+ ['nil']=function(_) return '0' end,
}
local get_value = function(v)
- return '(char *) ' .. value_dumpers[type(v)](v)
+ return '(void *) ' .. value_dumpers[type(v)](v)
end
local get_defaults = function(d,n)
@@ -126,9 +126,12 @@ local get_defaults = function(d,n)
return get_value(d)
end
+--- @type {[1]:string,[2]:string}[]
local defines = {}
-local dump_option = function(i, o)
+--- @param i integer
+--- @param o vim.option_meta
+local function dump_option(i, o)
w(' [' .. ('%u'):format(i - 1) .. ']={')
w(' .fullname=' .. cstr(o.full_name))
if o.abbreviation then
@@ -139,10 +142,14 @@ local dump_option = function(i, o)
w(get_cond(o.enable_if))
end
if o.varname then
- w(' .var=(char_u *)&' .. o.varname)
+ w(' .var=&' .. o.varname)
+ -- Immutable options should directly point to the default value
+ elseif o.immutable then
+ w((' .var=&options[%u].def_val'):format(i - 1))
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
end
+ w(' .immutable=' .. (o.immutable and 'true' or 'false'))
if #o.scope == 1 and o.scope[1] == 'global' then
w(' .indir=PV_NONE')
else
@@ -162,6 +169,12 @@ local dump_option = function(i, o)
table.insert(defines, { 'PV_' .. varname:sub(3):upper() , pv_name})
w(' .indir=' .. pv_name)
end
+ if o.cb then
+ w(' .opt_did_set_cb=' .. o.cb)
+ end
+ if o.expand_cb then
+ w(' .opt_expand_cb=' .. o.expand_cb)
+ end
if o.enable_if then
w('#else')
w(' .var=NULL')
@@ -184,7 +197,19 @@ local dump_option = function(i, o)
w(' },')
end
-w('static vimoption_T options[] = {')
+w([[
+#include "nvim/ex_getln.h"
+#include "nvim/insexpand.h"
+#include "nvim/mapping.h"
+#include "nvim/ops.h"
+#include "nvim/option.h"
+#include "nvim/optionstr.h"
+#include "nvim/quickfix.h"
+#include "nvim/runtime.h"
+#include "nvim/tag.h"
+#include "nvim/window.h"
+
+static vimoption_T options[] = {]])
for i, o in ipairs(options.options) do
dump_option(i, o)
end
diff --git a/scripts/genvimvim.lua b/src/nvim/generators/gen_vimvim.lua
index 3e9e7077be..29355d3cda 100644
--- a/scripts/genvimvim.lua
+++ b/src/nvim/generators/gen_vimvim.lua
@@ -1,17 +1,7 @@
-local mpack = require('mpack')
+local mpack = vim.mpack
-if arg[1] == '--help' then
- print('Usage: lua genvimvim.lua src/nvim runtime/syntax/vim/generated.vim')
- os.exit(0)
-end
-
-local nvimsrcdir = arg[1]
-local syntax_file = arg[2]
-local funcs_file = arg[3]
-
-package.path = nvimsrcdir .. '/?.lua;' .. package.path
-
-_G.vim = loadfile(nvimsrcdir..'/../../runtime/lua/vim/shared.lua')()
+local syntax_file = arg[1]
+local funcs_file = arg[2]
local lld = {}
local syn_fd = io.open(syntax_file, 'w')
@@ -37,6 +27,9 @@ local function cmd_kw(prev_cmd, cmd)
while cmd:sub(shift, shift) == prev_cmd:sub(shift, shift) do
shift = shift + 1
end
+ if cmd:sub(1, shift) == 'def' then
+ shift = shift + 1
+ end
if shift >= #cmd then
return cmd
else
@@ -67,6 +60,20 @@ for _, cmd_desc in ipairs(ex_cmds.cmds) do
if cmd:match('%w') and cmd ~= 'z' and not is_special_cased_cmd(cmd) then
w(' ' .. cmd_kw(prev_cmd, cmd))
end
+ if cmd == 'delete' then
+ -- Add special abbreviations of :delete
+ w(' ' .. cmd_kw('d', 'dl'))
+ w(' ' .. cmd_kw('del', 'dell'))
+ w(' ' .. cmd_kw('dele', 'delel'))
+ w(' ' .. cmd_kw('delet', 'deletl'))
+ w(' ' .. cmd_kw('delete', 'deletel'))
+ w(' ' .. cmd_kw('d', 'dp'))
+ w(' ' .. cmd_kw('de', 'dep'))
+ w(' ' .. cmd_kw('del', 'delp'))
+ w(' ' .. cmd_kw('dele', 'delep'))
+ w(' ' .. cmd_kw('delet', 'deletp'))
+ w(' ' .. cmd_kw('delete', 'deletep'))
+ end
prev_cmd = cmd
end
@@ -105,13 +112,12 @@ for _, au in ipairs(auevents.events) do
w(' ' .. au)
end
end
-for au, _ in pairs(auevents.aliases) do
- if not auevents.nvim_specific[au] then
- if lld.line_length > 850 then
- w('\n' .. vimau_start)
- end
- w(' ' .. au)
+for _, au in pairs(auevents.aliases) do
+ if lld.line_length > 850 then
+ w('\n' .. vimau_start)
end
+ -- au[1] is aliased to au[2]
+ w(' ' .. au[1])
end
local nvimau_start = 'syn keyword nvimAutoEvent contained '
@@ -127,7 +133,7 @@ end
w('\n\nsyn case match')
local vimfun_start = 'syn keyword vimFuncName contained '
w('\n\n' .. vimfun_start)
-local funcs = mpack.unpack(io.open(funcs_file, 'rb'):read("*all"))
+local funcs = mpack.decode(io.open(funcs_file, 'rb'):read("*all"))
for _, name in ipairs(funcs) do
if name then
if lld.line_length > 850 then
diff --git a/src/nvim/generators/preload.lua b/src/nvim/generators/preload.lua
new file mode 100644
index 0000000000..4b7fde2c39
--- /dev/null
+++ b/src/nvim/generators/preload.lua
@@ -0,0 +1,10 @@
+local srcdir = table.remove(arg, 1)
+local nlualib = table.remove(arg, 1)
+package.path = srcdir .. '/src/nvim/?.lua;' ..srcdir .. '/runtime/lua/?.lua;' .. package.path
+_G.vim = require'vim.shared'
+_G.vim.inspect = require 'vim.inspect'
+package.cpath = package.cpath .. ';' .. nlualib
+require 'nlua0'
+
+arg[0] = table.remove(arg, 1)
+return loadfile(arg[0])()
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 8ed9381bca..73af78d3e2 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1,21 +1,19 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// getchar.c: Code related to getting a character from the user or a script
// file, manipulations with redo buffer and stuff buffer.
#include <assert.h>
-#include <inttypes.h>
+#include <lauxlib.h>
+#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
@@ -24,11 +22,11 @@
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -37,7 +35,7 @@
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
@@ -48,19 +46,18 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
-#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// Index in scriptin
static int curscript = 0;
@@ -85,61 +82,73 @@ static buffheader_T redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 };
-// First read ahead buffer. Used for translated commands.
+/// First read ahead buffer. Used for translated commands.
static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 };
-// Second read ahead buffer. Used for redo.
+/// Second read ahead buffer. Used for redo.
static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 };
-static int typeahead_char = 0; // typeahead char that's not flushed
+static int typeahead_char = 0; ///< typeahead char that's not flushed
-// when block_redo is true redo buffer will not be changed
-// used by edit() to repeat insertions and 'V' command for redoing
+/// When block_redo is true the redo buffer will not be changed.
+/// Used by edit() to repeat insertions.
static int block_redo = false;
-static int KeyNoremap = 0; // remapping flags
+static int KeyNoremap = 0; ///< remapping flags
-// Variables used by vgetorpeek() and flush_buffers()
-//
-// typebuf.tb_buf[] contains all characters that are not consumed yet.
-// typebuf.tb_buf[typebuf.tb_off] is the first valid character.
-// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char.
-// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL.
-// The head of the buffer may contain the result of mappings, abbreviations
-// and @a commands. The length of this part is typebuf.tb_maplen.
-// typebuf.tb_silent is the part where <silent> applies.
-// After the head are characters that come from the terminal.
-// typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that
-// should not be considered for abbreviations.
-// Some parts of typebuf.tb_buf may not be mapped. These parts are remembered
-// in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and
-// contains RM_NONE for the characters that are not to be remapped.
-// typebuf.tb_noremap[typebuf.tb_off] is the first valid flag.
-// (typebuf has been put in globals.h, because check_termcode() needs it).
-#define RM_YES 0 // tb_noremap: remap
-#define RM_NONE 1 // tb_noremap: don't remap
-#define RM_SCRIPT 2 // tb_noremap: remap local script mappings
-#define RM_ABBR 4 // tb_noremap: don't remap, do abbrev.
+/// Variables used by vgetorpeek() and flush_buffers()
+///
+/// typebuf.tb_buf[] contains all characters that are not consumed yet.
+/// typebuf.tb_buf[typebuf.tb_off] is the first valid character.
+/// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char.
+/// typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL.
+/// The head of the buffer may contain the result of mappings, abbreviations
+/// and @a commands. The length of this part is typebuf.tb_maplen.
+/// typebuf.tb_silent is the part where <silent> applies.
+/// After the head are characters that come from the terminal.
+/// typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that
+/// should not be considered for abbreviations.
+/// Some parts of typebuf.tb_buf may not be mapped. These parts are remembered
+/// in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and
+/// contains RM_NONE for the characters that are not to be remapped.
+/// typebuf.tb_noremap[typebuf.tb_off] is the first valid flag.
+enum {
+ RM_YES = 0, ///< tb_noremap: remap
+ RM_NONE = 1, ///< tb_noremap: don't remap
+ RM_SCRIPT = 2, ///< tb_noremap: remap local script mappings
+ RM_ABBR = 4, ///< tb_noremap: don't remap, do abbrev.
+};
// typebuf.tb_buf has three parts: room in front (for result of mappings), the
// middle for typeahead and room for new characters (which needs to be 3 *
// MAXMAPLEN for the Amiga).
#define TYPELEN_INIT (5 * (MAXMAPLEN + 3))
-static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf
-static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
+static uint8_t typebuf_init[TYPELEN_INIT]; ///< initial typebuf.tb_buf
+static uint8_t noremapbuf_init[TYPELEN_INIT]; ///< initial typebuf.tb_noremap
-static size_t last_recorded_len = 0; // number of last recorded chars
+static size_t last_recorded_len = 0; ///< number of last recorded chars
+
+enum {
+ KEYLEN_PART_KEY = -1, ///< keylen value for incomplete key-code
+ KEYLEN_PART_MAP = -2, ///< keylen value for incomplete mapping
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.c.generated.h"
#endif
-// Free and clear a buffer.
-void free_buff(buffheader_T *buf)
+static const char e_recursive_mapping[] = N_("E223: Recursive mapping");
+static const char e_cmd_mapping_must_end_with_cr[]
+ = N_("E1255: <Cmd> mapping must end with <CR>");
+static const char e_cmd_mapping_must_end_with_cr_before_second_cmd[]
+ = N_("E1136: <Cmd> mapping must end with <CR> before second <Cmd>");
+
+/// Free and clear a buffer.
+static void free_buff(buffheader_T *buf)
{
- buffblock_T *p, *np;
+ buffblock_T *np;
- for (p = buf->bh_first.b_next; p != NULL; p = np) {
+ for (buffblock_T *p = buf->bh_first.b_next; p != NULL; p = np) {
np = p->b_next;
xfree(p);
}
@@ -155,7 +164,6 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
{
size_t count = 0;
char *p = NULL;
- char *p2;
// compute the total length of the string
for (const buffblock_T *bp = buffer->bh_first.b_next;
@@ -163,9 +171,9 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
count += strlen(bp->b_str);
}
- if (count || dozero) {
+ if (count > 0 || dozero) {
p = xmalloc(count + 1);
- p2 = p;
+ char *p2 = p;
for (const buffblock_T *bp = buffer->bh_first.b_next;
bp != NULL; bp = bp->b_next) {
for (const char *str = bp->b_str; *str;) {
@@ -180,7 +188,7 @@ static char *get_buffcont(buffheader_T *buffer, int dozero)
/// Return the contents of the record buffer as a single string
/// and clear the record buffer.
/// K_SPECIAL in the returned string is escaped.
-char_u *get_recorded(void)
+char *get_recorded(void)
{
char *p;
size_t len;
@@ -202,7 +210,7 @@ char_u *get_recorded(void)
p[len - 1] = NUL;
}
- return (char_u *)p;
+ return p;
}
/// Return the contents of the redo buffer as a single string.
@@ -281,11 +289,11 @@ static void delete_buff_tail(buffheader_T *buf, int slen)
}
/// Add number "n" to buffer "buf".
-static void add_num_buff(buffheader_T *buf, long n)
+static void add_num_buff(buffheader_T *buf, int n)
{
char number[32];
- snprintf(number, sizeof(number), "%ld", n);
- add_buff(buf, number, -1L);
+ snprintf(number, sizeof(number), "%d", n);
+ add_buff(buf, number, -1);
}
/// Add character 'c' to buffer "buf".
@@ -317,7 +325,7 @@ static void add_char_buff(buffheader_T *buf, int c)
temp[0] = (char)c;
temp[1] = NUL;
}
- add_buff(buf, temp, -1L);
+ add_buff(buf, temp, -1);
}
}
@@ -338,14 +346,12 @@ static int read_readbuffers(int advance)
static int read_readbuf(buffheader_T *buf, int advance)
{
- char_u c;
-
if (buf->bh_first.b_next == NULL) { // buffer is empty
return NUL;
}
buffblock_T *const curr = buf->bh_first.b_next;
- c = (char_u)curr->b_str[buf->bh_index];
+ uint8_t c = (uint8_t)curr->b_str[buf->bh_index];
if (advance) {
if (curr->b_str[++buf->bh_index] == NUL) {
@@ -357,7 +363,7 @@ static int read_readbuf(buffheader_T *buf, int advance)
return c;
}
-// Prepare the read buffers for reading (if they contain something).
+/// Prepare the read buffers for reading (if they contain something).
static void start_stuff(void)
{
if (readbuf1.bh_first.b_next != NULL) {
@@ -385,15 +391,15 @@ int readbuf1_empty(void)
return (readbuf1.bh_first.b_next == NULL);
}
-// Set a typeahead character that won't be flushed.
+/// Set a typeahead character that won't be flushed.
void typeahead_noflush(int c)
{
typeahead_char = c;
}
-// Remove the contents of the stuff buffer and the mapped characters in the
-// typeahead buffer (used in case of an error). If "flush_typeahead" is true,
-// flush all typeahead characters (used when interrupted by a CTRL-C).
+/// Remove the contents of the stuff buffer and the mapped characters in the
+/// typeahead buffer (used in case of an error). If "flush_typeahead" is true,
+/// flush all typeahead characters (used when interrupted by a CTRL-C).
void flush_buffers(flush_buffers_T flush_typeahead)
{
init_typebuf();
@@ -411,7 +417,7 @@ void flush_buffers(flush_buffers_T flush_typeahead)
// We have to get all characters, because we may delete the first
// part of an escape sequence. In an xterm we get one char at a
// time and we have to get them all.
- while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L) != 0) {}
+ while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10) != 0) {}
}
typebuf.tb_off = MAXMAPLEN;
typebuf.tb_len = 0;
@@ -437,8 +443,8 @@ void beep_flush(void)
}
}
-// The previous contents of the redo buffer is kept in old_redobuffer.
-// This is used for the CTRL-O <.> command in insert mode.
+/// The previous contents of the redo buffer is kept in old_redobuffer.
+/// This is used for the CTRL-O <.> command in insert mode.
void ResetRedobuff(void)
{
if (block_redo) {
@@ -450,8 +456,8 @@ void ResetRedobuff(void)
redobuff.bh_first.b_next = NULL;
}
-// Discard the contents of the redo buffer and restore the previous redo
-// buffer.
+/// Discard the contents of the redo buffer and restore the previous redo
+/// buffer.
void CancelRedo(void)
{
if (block_redo) {
@@ -480,7 +486,7 @@ void saveRedobuff(save_redo_T *save_redo)
return;
}
- add_buff(&redobuff, s, -1L);
+ add_buff(&redobuff, s, -1);
xfree(s);
}
@@ -499,7 +505,7 @@ void restoreRedobuff(save_redo_T *save_redo)
void AppendToRedobuff(const char *s)
{
if (!block_redo) {
- add_buff(&redobuff, s, -1L);
+ add_buff(&redobuff, s, -1);
}
}
@@ -545,13 +551,32 @@ void AppendToRedobuffLit(const char *str, int len)
// CTRL-V '0' must be inserted as CTRL-V 048.
if (*s == NUL && c == '0') {
- add_buff(&redobuff, "048", 3L);
+ add_buff(&redobuff, "048", 3);
} else {
add_char_buff(&redobuff, c);
}
}
}
+/// Append "s" to the redo buffer, leaving 3-byte special key codes unmodified
+/// and escaping other K_SPECIAL bytes.
+void AppendToRedobuffSpec(const char *s)
+{
+ if (block_redo) {
+ return;
+ }
+
+ while (*s != NUL) {
+ if ((uint8_t)(*s) == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
+ // Insert special key literally.
+ add_buff(&redobuff, s, 3);
+ s += 3;
+ } else {
+ add_char_buff(&redobuff, mb_cptr2char_adv(&s));
+ }
+ }
+}
+
/// Append a character to the redo buffer.
/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
void AppendCharToRedobuff(int c)
@@ -562,7 +587,7 @@ void AppendCharToRedobuff(int c)
}
// Append a number to the redo buffer.
-void AppendNumberToRedobuff(long n)
+void AppendNumberToRedobuff(int n)
{
if (!block_redo) {
add_num_buff(&redobuff, n);
@@ -573,17 +598,17 @@ void AppendNumberToRedobuff(long n)
/// K_SPECIAL must already have been escaped.
void stuffReadbuff(const char *s)
{
- add_buff(&readbuf1, s, -1L);
+ add_buff(&readbuf1, s, -1);
}
/// Append string "s" to the redo stuff buffer.
/// @remark K_SPECIAL must already have been escaped.
void stuffRedoReadbuff(const char *s)
{
- add_buff(&readbuf2, s, -1L);
+ add_buff(&readbuf2, s, -1);
}
-void stuffReadbuffLen(const char *s, long len)
+void stuffReadbuffLen(const char *s, ptrdiff_t len)
{
add_buff(&readbuf1, s, len);
}
@@ -616,7 +641,7 @@ void stuffcharReadbuff(int c)
}
// Append a number to the stuff buffer.
-void stuffnumReadbuff(long n)
+void stuffnumReadbuff(int n)
{
add_num_buff(&readbuf1, n);
}
@@ -635,7 +660,7 @@ void stuffescaped(const char *arg, bool literally)
arg++;
}
if (arg > start) {
- stuffReadbuffLen(start, (arg - start));
+ stuffReadbuffLen(start, arg - start);
}
// stuff a single special character
@@ -658,18 +683,17 @@ void stuffescaped(const char *arg, bool literally)
static int read_redo(bool init, bool old_redo)
{
static buffblock_T *bp;
- static char_u *p;
+ static uint8_t *p;
int c;
int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
+ uint8_t buf[MB_MAXBYTES + 1];
if (init) {
bp = old_redo ? old_redobuff.bh_first.b_next : redobuff.bh_first.b_next;
if (bp == NULL) {
return FAIL;
}
- p = (char_u *)bp->b_str;
+ p = (uint8_t *)bp->b_str;
return OK;
}
if ((c = *p) == NUL) {
@@ -683,16 +707,16 @@ static int read_redo(bool init, bool old_redo)
} else {
n = 1;
}
- for (i = 0;; i++) {
+ for (int i = 0;; i++) {
if (c == K_SPECIAL) { // special key or escaped K_SPECIAL
c = TO_SPECIAL(p[1], p[2]);
p += 2;
}
if (*++p == NUL && bp->b_next != NULL) {
bp = bp->b_next;
- p = (char_u *)bp->b_str;
+ p = (uint8_t *)bp->b_str;
}
- buf[i] = (char_u)c;
+ buf[i] = (uint8_t)c;
if (i == n - 1) { // last byte of a character
if (n != 1) {
c = utf_ptr2char((char *)buf);
@@ -720,27 +744,25 @@ static void copy_redo(bool old_redo)
}
}
-// Stuff the redo buffer into readbuf2.
-// Insert the redo count into the command.
-// If "old_redo" is true, the last but one command is repeated
-// instead of the last command (inserting text). This is used for
-// CTRL-O <.> in insert mode
-//
-// return FAIL for failure, OK otherwise
-int start_redo(long count, bool old_redo)
+/// Stuff the redo buffer into readbuf2.
+/// Insert the redo count into the command.
+/// If "old_redo" is true, the last but one command is repeated
+/// instead of the last command (inserting text). This is used for
+/// CTRL-O <.> in insert mode
+///
+/// @return FAIL for failure, OK otherwise
+int start_redo(int count, bool old_redo)
{
- int c;
-
// init the pointers; return if nothing to redo
if (read_redo(true, old_redo) == FAIL) {
return FAIL;
}
- c = read_redo(false, old_redo);
+ int c = read_redo(false, old_redo);
// copy the buffer name, if present
if (c == '"') {
- add_buff(&readbuf2, "\"", 1L);
+ add_buff(&readbuf2, "\"", 1);
c = read_redo(false, old_redo);
// if a numbered buffer is used, increment the number
@@ -781,9 +803,10 @@ int start_redo(long count, bool old_redo)
return OK;
}
-// Repeat the last insert (R, o, O, a, A, i or I command) by stuffing
-// the redo buffer into readbuf2.
-// return FAIL for failure, OK otherwise
+/// Repeat the last insert (R, o, O, a, A, i or I command) by stuffing
+/// the redo buffer into readbuf2.
+///
+/// @return FAIL for failure, OK otherwise
int start_redo_ins(void)
{
int c;
@@ -797,7 +820,7 @@ int start_redo_ins(void)
while ((c = read_redo(false, false)) != NUL) {
if (vim_strchr("AaIiRrOo", c) != NULL) {
if (c == 'O' || c == 'o') {
- add_buff(&readbuf2, NL_STR, -1L);
+ add_buff(&readbuf2, NL_STR, -1);
}
break;
}
@@ -814,9 +837,9 @@ void stop_redo_ins(void)
block_redo = false;
}
-// Initialize typebuf.tb_buf to point to typebuf_init.
-// alloc() cannot be used here: In out-of-memory situations it would
-// be impossible to type anything.
+/// Initialize typebuf.tb_buf to point to typebuf_init.
+/// alloc() cannot be used here: In out-of-memory situations it would
+/// be impossible to type anything.
static void init_typebuf(void)
{
if (typebuf.tb_buf != NULL) {
@@ -837,28 +860,24 @@ bool noremap_keys(void)
return KeyNoremap & (RM_NONE|RM_SCRIPT);
}
-// Insert a string in position 'offset' in the typeahead buffer (for "@r"
-// and ":normal" command, vgetorpeek() and check_termcode())
-//
-// If noremap is REMAP_YES, new string can be mapped again.
-// If noremap is REMAP_NONE, new string cannot be mapped again.
-// If noremap is REMAP_SKIP, first char of new string cannot be mapped again,
-// but abbreviations are allowed.
-// If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for
-// script-local mappings.
-// If noremap is > 0, that many characters of the new string cannot be mapped.
-//
-// If nottyped is true, the string does not return KeyTyped (don't use when
-// offset is non-zero!).
-//
-// If silent is true, cmd_silent is set when the characters are obtained.
-//
-// return FAIL for failure, OK otherwise
+/// Insert a string in position "offset" in the typeahead buffer.
+///
+/// If "noremap" is REMAP_YES, new string can be mapped again.
+/// If "noremap" is REMAP_NONE, new string cannot be mapped again.
+/// If "noremap" is REMAP_SKIP, first char of new string cannot be mapped again,
+/// but abbreviations are allowed.
+/// If "noremap" is REMAP_SCRIPT, new string cannot be mapped again, except for
+/// script-local mappings.
+/// If "noremap" is > 0, that many characters of the new string cannot be mapped.
+///
+/// If "nottyped" is true, the string does not return KeyTyped (don't use when
+/// "offset" is non-zero!).
+///
+/// If "silent" is true, cmd_silent is set when the characters are obtained.
+///
+/// @return FAIL for failure, OK otherwise
int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
{
- char_u *s1, *s2;
- int addlen;
- int i;
int val;
int nrm;
@@ -866,8 +885,9 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
if (++typebuf.tb_change_cnt == 0) {
typebuf.tb_change_cnt = 1;
}
+ state_no_longer_safe("ins_typebuf()");
- addlen = (int)strlen(str);
+ int addlen = (int)strlen(str);
if (offset == 0 && addlen <= typebuf.tb_off) {
// Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off]
@@ -893,8 +913,8 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
return FAIL;
}
int newlen = typebuf.tb_len + extra;
- s1 = xmalloc((size_t)newlen);
- s2 = xmalloc((size_t)newlen);
+ uint8_t *s1 = xmalloc((size_t)newlen);
+ uint8_t *s2 = xmalloc((size_t)newlen);
typebuf.tb_buflen = newlen;
// copy the old chars, before the insertion point
@@ -948,7 +968,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
} else {
nrm = noremap;
}
- for (i = 0; i < addlen; i++) {
+ for (int i = 0; i < addlen; i++) {
typebuf.tb_noremap[typebuf.tb_off + i + offset] =
(uint8_t)((--nrm >= 0) ? val : RM_YES);
}
@@ -978,11 +998,11 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
/// @return the length of what was inserted
int ins_char_typebuf(int c, int modifiers)
{
- char_u buf[MB_MAXBYTES * 3 + 4];
- unsigned int len = special_to_buf(c, modifiers, true, buf);
+ char buf[MB_MAXBYTES * 3 + 4];
+ unsigned len = special_to_buf(c, modifiers, true, buf);
assert(len < sizeof(buf));
buf[len] = NUL;
- (void)ins_typebuf((char *)buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
+ (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
return (int)len;
}
@@ -1010,18 +1030,16 @@ int typebuf_typed(void)
return typebuf.tb_maplen == 0;
}
-// Return the number of characters that are mapped (or not typed).
+/// Get the number of characters that are mapped (or not typed).
int typebuf_maplen(void)
FUNC_ATTR_PURE
{
return typebuf.tb_maplen;
}
-// remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset]
+/// Remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset]
void del_typebuf(int len, int offset)
{
- int i;
-
if (len == 0) {
return; // nothing to do
}
@@ -1034,7 +1052,7 @@ void del_typebuf(int len, int offset)
typebuf.tb_off += len;
} else {
// Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[]
- i = typebuf.tb_off + offset;
+ int i = typebuf.tb_off + offset;
// Leave some extra room at the end to avoid reallocation.
if (typebuf.tb_off > MAXMAPLEN) {
memmove(typebuf.tb_buf + MAXMAPLEN,
@@ -1084,13 +1102,13 @@ void del_typebuf(int len, int offset)
}
}
-// Write typed characters to script file.
-// If recording is on put the character in the recordbuffer.
-static void gotchars(const char_u *chars, size_t len)
+/// Write typed characters to script file.
+/// If recording is on put the character in the recordbuffer.
+static void gotchars(const uint8_t *chars, size_t len)
FUNC_ATTR_NONNULL_ALL
{
- const char_u *s = chars;
- static char_u buf[4] = { 0 };
+ const uint8_t *s = chars;
+ static uint8_t buf[4] = { 0 };
static size_t buflen = 0;
size_t todo = len;
@@ -1130,6 +1148,13 @@ static void gotchars(const char_u *chars, size_t len)
maptick++;
}
+/// Record a <Nop> key.
+void gotchars_nop(void)
+{
+ uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_NOP };
+ gotchars(nop_buf, 3);
+}
+
/// Undo the last gotchars() for "len" bytes. To be used when putting a typed
/// character back into the typeahead buffer, thus gotchars() will be called
/// again.
@@ -1144,12 +1169,12 @@ void ungetchars(int len)
last_recorded_len -= (size_t)len;
}
-// Sync undo. Called when typed characters are obtained from the typeahead
-// buffer, or when a menu is used.
-// Do not sync:
-// - In Insert mode, unless cursor key has been used.
-// - While reading a script file.
-// - When no_u_sync is non-zero.
+/// Sync undo. Called when typed characters are obtained from the typeahead
+/// buffer, or when a menu is used.
+/// Do not sync:
+/// - In Insert mode, unless cursor key has been used.
+/// - While reading a script file.
+/// - When no_u_sync is non-zero.
void may_sync_undo(void)
{
if ((!(State & (MODE_INSERT | MODE_CMDLINE)) || arrow_used)
@@ -1158,7 +1183,7 @@ void may_sync_undo(void)
}
}
-// Make "typebuf" empty and allocate new buffers.
+/// Make "typebuf" empty and allocate new buffers.
void alloc_typebuf(void)
{
typebuf.tb_buf = xmalloc(TYPELEN_INIT);
@@ -1174,7 +1199,7 @@ void alloc_typebuf(void)
}
}
-// Free the buffers of "typebuf".
+/// Free the buffers of "typebuf".
void free_typebuf(void)
{
if (typebuf.tb_buf == typebuf_init) {
@@ -1189,8 +1214,8 @@ void free_typebuf(void)
}
}
-// When doing ":so! file", the current typeahead needs to be saved, and
-// restored when "file" has been read completely.
+/// When doing ":so! file", the current typeahead needs to be saved, and
+/// restored when "file" has been read completely.
static typebuf_T saved_typebuf[NSCRIPT];
void save_typebuf(void)
@@ -1200,12 +1225,12 @@ void save_typebuf(void)
alloc_typebuf();
}
-static int old_char = -1; // character put back by vungetc()
-static int old_mod_mask; // mod_mask for ungotten character
-static int old_mouse_grid; // mouse_grid related to old_char
-static int old_mouse_row; // mouse_row related to old_char
-static int old_mouse_col; // mouse_col related to old_char
-static int old_KeyStuffed; // whether old_char was stuffed
+static int old_char = -1; ///< character put back by vungetc()
+static int old_mod_mask; ///< mod_mask for ungotten character
+static int old_mouse_grid; ///< mouse_grid related to old_char
+static int old_mouse_row; ///< mouse_row related to old_char
+static int old_mouse_col; ///< mouse_col related to old_char
+static int old_KeyStuffed; ///< whether old_char was stuffed
static bool can_get_old_char(void)
{
@@ -1214,7 +1239,7 @@ static bool can_get_old_char(void)
return old_char != -1 && (old_KeyStuffed || stuff_empty());
}
-// Save all three kinds of typeahead, so that the user must type at a prompt.
+/// Save all three kinds of typeahead, so that the user must type at a prompt.
void save_typeahead(tasave_T *tp)
{
tp->save_typebuf = typebuf;
@@ -1230,8 +1255,8 @@ void save_typeahead(tasave_T *tp)
readbuf2.bh_first.b_next = NULL;
}
-// Restore the typeahead to what it was before calling save_typeahead().
-// The allocated memory is freed, can only be called once!
+/// Restore the typeahead to what it was before calling save_typeahead().
+/// The allocated memory is freed, can only be called once!
void restore_typeahead(tasave_T *tp)
{
if (tp->typebuf_valid) {
@@ -1317,7 +1342,7 @@ void openscript(char *name, bool directly)
}
}
-// Close the currently active input script.
+/// Close the currently active input script.
static void closescript(void)
{
free_typebuf();
@@ -1357,8 +1382,8 @@ void before_blocking(void)
}
}
-/// updatescript() is called when a character can be written to the script file
-/// or when we have waited some time for a character (c == 0).
+/// updatescript() is called when a character can be written to the script
+/// file or when we have waited some time for a character (c == 0).
///
/// All the changed memfiles are synced if c == 0 or when the number of typed
/// characters reaches 'updatecount' and 'updatecount' is non-zero.
@@ -1408,10 +1433,8 @@ int merge_modifiers(int c_arg, int *modifiers)
/// Returns the modifiers in the global "mod_mask".
int vgetc(void)
{
- int c, c2;
- int n;
- char_u buf[MB_MAXBYTES + 1];
- int i;
+ int c;
+ uint8_t buf[MB_MAXBYTES + 1];
// Do garbage collection when garbagecollect() was called previously and
// we are now at the toplevel.
@@ -1429,6 +1452,8 @@ int vgetc(void)
mouse_row = old_mouse_row;
mouse_col = old_mouse_col;
} else {
+ int c2;
+ int n;
// number of characters recorded from the last vgetc() call
static size_t last_vgetc_recorded_len = 0;
@@ -1440,7 +1465,7 @@ int vgetc(void)
// if peeking records more
last_recorded_len -= last_vgetc_recorded_len;
- for (;;) { // this is done twice if there are modifiers
+ while (true) { // this is done twice if there are modifiers
bool did_inc = false;
if (mod_mask) { // no mapping after modifier has been read
no_mapping++;
@@ -1553,9 +1578,9 @@ int vgetc(void)
// Note: This will loop until enough bytes are received!
if ((n = MB_BYTE2LEN_CHECK(c)) > 1) {
no_mapping++;
- buf[0] = (char_u)c;
- for (i = 1; i < n; i++) {
- buf[i] = (char_u)vgetorpeek(true);
+ buf[0] = (uint8_t)c;
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)vgetorpeek(true);
if (buf[i] == K_SPECIAL) {
// Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
// which represents a K_SPECIAL (0x80).
@@ -1601,11 +1626,17 @@ int vgetc(void)
// Execute Lua on_key callbacks.
nlua_execute_on_key(c);
+ // Need to process the character before we know it's safe to do something
+ // else.
+ if (c != K_IGNORE) {
+ state_no_longer_safe("key typed");
+ }
+
return c;
}
-// Like vgetc(), but never return a NUL when called recursively, get a key
-// directly from the user (ignoring typeahead).
+/// Like vgetc(), but never return a NUL when called recursively, get a key
+/// directly from the user (ignoring typeahead).
int safe_vgetc(void)
{
int c;
@@ -1617,8 +1648,8 @@ int safe_vgetc(void)
return c;
}
-// Like safe_vgetc(), but loop to handle K_IGNORE.
-// Also ignore scrollbar events.
+/// Like safe_vgetc(), but loop to handle K_IGNORE.
+/// Also ignore scrollbar events.
int plain_vgetc(void)
{
int c;
@@ -1631,10 +1662,10 @@ int plain_vgetc(void)
return c;
}
-// Check if a character is available, such that vgetc() will not block.
-// If the next character is a special character or multi-byte, the returned
-// character is not valid!.
-// Returns NUL if no character is available.
+/// Check if a character is available, such that vgetc() will not block.
+/// If the next character is a special character or multi-byte, the returned
+/// character is not valid!.
+/// Returns NUL if no character is available.
int vpeekc(void)
{
if (can_get_old_char()) {
@@ -1643,9 +1674,9 @@ int vpeekc(void)
return vgetorpeek(false);
}
-// Check if any character is available, also half an escape sequence.
-// Trick: when no typeahead found, but there is something in the typeahead
-// buffer, it must be an ESC that is recognized as the start of a key code.
+/// Check if any character is available, also half an escape sequence.
+/// Trick: when no typeahead found, but there is something in the typeahead
+/// buffer, it must be an ESC that is recognized as the start of a key code.
int vpeekc_any(void)
{
int c;
@@ -1657,9 +1688,9 @@ int vpeekc_any(void)
return c;
}
-// Call vpeekc() without causing anything to be mapped.
-// Return true if a character is available, false otherwise.
-int char_avail(void)
+/// Call vpeekc() without causing anything to be mapped.
+/// @return true if a character is available, false otherwise.
+bool char_avail(void)
{
int retval;
@@ -1678,7 +1709,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
no_mapping++;
allow_keys++;
- for (;;) {
+ while (true) {
if (msg_col > 0) {
// Position the cursor. Needed after a message that ends in a space.
ui_cursor_goto(msg_row, msg_col);
@@ -1688,7 +1719,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
// getchar(): blocking wait.
// TODO(bfredl): deduplicate shared logic with state_enter ?
if (!char_avail()) {
- // flush output before waiting
+ // Flush screen updates before blocking.
ui_flush();
(void)os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
@@ -1754,9 +1785,9 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
int grid = mouse_grid;
linenr_T lnum;
win_T *wp;
- int winnr = 1;
if (row >= 0 && col >= 0) {
+ int winnr = 1;
// Find the window at the mouse coordinates and compute the
// text position.
win_T *const win = mouse_find_win(&grid, &row, &col);
@@ -1796,7 +1827,7 @@ void f_getcharstr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
int i = 0;
if (n != 0) {
- i += utf_char2bytes((int)n, (char *)temp);
+ i += utf_char2bytes((int)n, temp);
}
assert(i < 7);
temp[i++] = NUL;
@@ -1820,7 +1851,7 @@ typedef enum {
/// Put "string[new_slen]" in typebuf.
/// Remove "slen" bytes.
/// @return FAIL for error, OK otherwise.
-static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_slen)
+static int put_string_in_typebuf(int offset, int slen, uint8_t *string, int new_slen)
{
int extra = new_slen - slen;
string[new_slen] = NUL;
@@ -1843,7 +1874,7 @@ static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_s
/// in Insert mode completion. This includes the form with a CTRL modifier.
static bool at_ins_compl_key(void)
{
- char_u *p = typebuf.tb_buf + typebuf.tb_off;
+ uint8_t *p = typebuf.tb_buf + typebuf.tb_off;
int c = *p;
if (typebuf.tb_len > 3 && c == K_SPECIAL && p[1] == KS_MODIFIER && (p[2] & MOD_MASK_CTRL)) {
@@ -1863,7 +1894,7 @@ static int check_simplify_modifier(int max_offset)
if (offset + 3 >= typebuf.tb_len) {
break;
}
- char_u *tp = typebuf.tb_buf + typebuf.tb_off + offset;
+ uint8_t *tp = typebuf.tb_buf + typebuf.tb_off + offset;
if (tp[0] == K_SPECIAL && tp[1] == KS_MODIFIER) {
// A modifier was not used for a mapping, apply it to ASCII
// keys. Shift would already have been applied.
@@ -1879,12 +1910,12 @@ static int check_simplify_modifier(int max_offset)
vgetc_char = c;
vgetc_mod_mask = tp[2];
}
- char_u new_string[MB_MAXBYTES];
+ uint8_t new_string[MB_MAXBYTES];
int len;
if (IS_SPECIAL(new_c)) {
new_string[0] = K_SPECIAL;
- new_string[1] = (char_u)K_SECOND(new_c);
- new_string[2] = (char_u)K_THIRD(new_c);
+ new_string[1] = (uint8_t)K_SECOND(new_c);
+ new_string[2] = (uint8_t)K_THIRD(new_c);
len = 3;
} else {
len = utf_char2bytes(new_c, (char *)new_string);
@@ -1894,7 +1925,7 @@ static int check_simplify_modifier(int max_offset)
return -1;
}
} else {
- tp[2] = (char_u)modifier;
+ tp[2] = (uint8_t)modifier;
if (put_string_in_typebuf(offset + 3, 1, new_string, len) == FAIL) {
return -1;
}
@@ -1920,10 +1951,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
int mp_match_len = 0;
int max_mlen = 0;
int tb_c1;
- int mlen;
- int nolmaplen;
int keylen = *keylenp;
- int i;
int local_State = get_real_state();
bool is_plug_map = false;
@@ -1956,6 +1984,8 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
&& State != MODE_ASKMORE
&& State != MODE_CONFIRM
&& !at_ins_compl_key()) {
+ int mlen;
+ int nolmaplen;
if (tb_c1 == K_SPECIAL) {
nolmaplen = 2;
} else {
@@ -2016,10 +2046,10 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// Don't allow mapping the first byte(s) of a multi-byte char.
// Happens when mapping <M-a> and then changing 'encoding'.
// Beware that 0x80 is escaped.
- char_u *p1 = (char_u *)mp->m_keys;
- char_u *p2 = (char_u *)mb_unescape((const char **)&p1);
+ const char *p1 = mp->m_keys;
+ const char *p2 = mb_unescape(&p1);
- if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len((char *)p2)) {
+ if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len(p2)) {
mlen = 0;
}
@@ -2081,39 +2111,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
}
- // Check for match with 'pastetoggle'
- if (*p_pt != NUL && mp == NULL && (State & (MODE_INSERT | MODE_NORMAL))) {
- bool match = typebuf_match_len((char_u *)p_pt, &mlen);
- if (match) {
- // write chars to script file(s)
- if (mlen > typebuf.tb_maplen) {
- gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen,
- (size_t)(mlen - typebuf.tb_maplen));
- }
-
- del_typebuf(mlen, 0); // remove the chars
- set_option_value_give_err("paste", !p_paste, NULL, 0);
- if (!(State & MODE_INSERT)) {
- msg_col = 0;
- msg_row = Rows - 1;
- msg_clr_eos(); // clear ruler
- }
- status_redraw_all();
- redraw_statuslines();
- showmode();
- setcursor();
- *keylenp = keylen;
- return map_result_retry;
- }
- // Need more chars for partly match.
- if (mlen == typebuf.tb_len) {
- keylen = KEYLEN_PART_KEY;
- } else if (max_mlen < mlen) {
- // no match, may have to check for termcode at next character
- max_mlen = mlen + 1;
- }
- }
-
if ((mp == NULL || max_mlen > mp_match_len) && keylen != KEYLEN_PART_MAP) {
// When no matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched:
@@ -2124,13 +2121,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
|| (typebuf.tb_buf[typebuf.tb_off + 1] == KS_MODIFIER && typebuf.tb_len < 4))) {
// Incomplete modifier sequence: cannot decide whether to simplify yet.
keylen = KEYLEN_PART_KEY;
- } else if (keylen == KEYLEN_PART_KEY && !*timedout) {
- // If 'pastetoggle' matched partially, don't simplify.
- // When the last characters were not typed, don't wait for a typed character to
- // complete 'pastetoggle'.
- if (typebuf.tb_len == typebuf.tb_maplen) {
- keylen = 0;
- }
} else {
// Try to include the modifier into the key.
keylen = check_simplify_modifier(max_mlen + 1);
@@ -2168,6 +2158,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// complete match
if (keylen >= 0 && keylen <= typebuf.tb_len) {
+ int i;
char *map_str = NULL;
// Write chars to script file(s).
@@ -2183,7 +2174,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// Put the replacement string in front of mapstr.
// The depth check catches ":map x y" and ":map y x".
if (++*mapdepth >= p_mmd) {
- emsg(_("E223: recursive mapping"));
+ emsg(_(e_recursive_mapping));
if (State & MODE_CMDLINE) {
redrawcmdline();
} else {
@@ -2199,7 +2190,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// mode temporarily. Append K_SELECT to switch back to Select mode.
if (VIsual_active && VIsual_select && (mp->m_mode & MODE_VISUAL)) {
VIsual_select = false;
- (void)ins_typebuf((char *)K_SELECT_STRING, REMAP_NONE, 0, true, false);
+ (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false);
}
// Copy the values from *mp that are used, because evaluating the
@@ -2218,9 +2209,6 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
if (mp->m_expr) {
const int save_vgetc_busy = vgetc_busy;
const bool save_may_garbage_collect = may_garbage_collect;
- const int save_cursor_row = ui_current_row();
- const int save_cursor_col = ui_current_col();
- const handle_T save_cursor_grid = ui_cursor_grid();
const int prev_did_emsg = did_emsg;
vgetc_busy = 0;
@@ -2232,28 +2220,28 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
map_str = eval_map_expr(mp, NUL);
- // The mapping may do anything, but we expect it to take care of
- // redrawing. Do put the cursor back where it was.
- ui_grid_cursor_goto(save_cursor_grid, save_cursor_row, save_cursor_col);
- ui_flush();
-
- // If an error was displayed and the expression returns an empty
- // string, generate a <Nop> to allow for a redraw.
- if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) {
- char buf[4];
- xfree(map_str);
- buf[0] = (char)K_SPECIAL;
- buf[1] = (char)KS_EXTRA;
- buf[2] = KE_IGNORE;
- buf[3] = NUL;
- map_str = xstrdup(buf);
- if (State & MODE_CMDLINE) {
- // redraw the command below the error
- msg_didout = true;
- if (msg_row < cmdline_row) {
- msg_row = cmdline_row;
+ if ((map_str == NULL || *map_str == NUL)) {
+ // If an error was displayed and the expression returns an empty
+ // string, generate a <Nop> to allow for a redraw.
+ if (prev_did_emsg != did_emsg) {
+ char buf[4];
+ xfree(map_str);
+ buf[0] = (char)K_SPECIAL;
+ buf[1] = (char)KS_EXTRA;
+ buf[2] = KE_IGNORE;
+ buf[3] = NUL;
+ map_str = xstrdup(buf);
+ if (State & MODE_CMDLINE) {
+ // redraw the command below the error
+ msg_didout = true;
+ if (msg_row < cmdline_row) {
+ msg_row = cmdline_row;
+ }
+ redrawcmd();
}
- redrawcmd();
+ } else if (State & (MODE_NORMAL | MODE_INSERT)) {
+ // otherwise, just put back the cursor
+ setcursor();
}
}
@@ -2275,7 +2263,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
// If this is a LANGMAP mapping, then we didn't record the keys
// at the start of the function and have to record them now.
if (keylen > typebuf.tb_maplen && (mp->m_mode & MODE_LANGMAP) != 0) {
- gotchars((char_u *)map_str, strlen(map_str));
+ gotchars((uint8_t *)map_str, strlen(map_str));
}
if (save_m_noremap != REMAP_YES) {
@@ -2355,16 +2343,12 @@ void check_end_reg_executing(bool advance)
/// K_SPECIAL may be escaped, need to get two more bytes then.
static int vgetorpeek(bool advance)
{
- int c, c1;
+ int c;
bool timedout = false; // waited for more than 'timeoutlen'
// for mapping to complete or
// 'ttimeoutlen' for complete key code
int mapdepth = 0; // check for recursive mapping
bool mode_deleted = false; // set when mode has been deleted
- int new_wcol, new_wrow;
- int n;
- int old_wcol, old_wrow;
- int wait_tb_len;
// This function doesn't work very well when called recursively. This may
// happen though, because of:
@@ -2414,7 +2398,7 @@ static int vgetorpeek(bool advance)
// are sure that it is not a mapped key.
// If a mapped key sequence is found we go back to the start to
// try re-mapping.
- for (;;) {
+ while (true) {
check_end_reg_executing(advance);
// os_breakcheck() is slow, don't use it too often when
// inside a mapping. But call it each time for typed
@@ -2432,7 +2416,7 @@ static int vgetorpeek(bool advance)
int keylen = 0;
if (got_int) {
// flush all input
- c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L);
+ c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0);
// If inchar() returns true (script file was active) or we
// are inside a mapping, get out of Insert mode.
@@ -2450,7 +2434,7 @@ static int vgetorpeek(bool advance)
if (advance) {
// Also record this character, it might be needed to
// get out of Insert mode.
- *typebuf.tb_buf = (char_u)c;
+ *typebuf.tb_buf = (uint8_t)c;
gotchars(typebuf.tb_buf, 1);
}
cmd_silent = false;
@@ -2501,8 +2485,8 @@ static int vgetorpeek(bool advance)
// have to redisplay the mode. That the cursor is in the wrong
// place does not matter.
c = 0;
- new_wcol = curwin->w_wcol;
- new_wrow = curwin->w_wrow;
+ int new_wcol = curwin->w_wcol;
+ int new_wrow = curwin->w_wrow;
if (advance
&& typebuf.tb_len == 1
&& typebuf.tb_buf[typebuf.tb_off] == ESC
@@ -2511,20 +2495,19 @@ static int vgetorpeek(bool advance)
&& typebuf.tb_maplen == 0
&& (State & MODE_INSERT)
&& (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout))
- && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) {
- colnr_T col = 0;
- char_u *ptr;
-
+ && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25)) == 0) {
if (mode_displayed) {
unshowmode(true);
mode_deleted = true;
}
validate_cursor();
- old_wcol = curwin->w_wcol;
- old_wrow = curwin->w_wrow;
+ int old_wcol = curwin->w_wcol;
+ int old_wrow = curwin->w_wrow;
// move cursor left, if possible
if (curwin->w_cursor.col != 0) {
+ colnr_T col = 0;
+ char *ptr;
if (curwin->w_wcol > 0) {
// After auto-indenting and no text is following,
// we are expecting to truncate the trailing
@@ -2533,11 +2516,10 @@ static int vgetorpeek(bool advance)
if (did_ai
&& *skipwhite(get_cursor_line_ptr() + curwin->w_cursor.col) == NUL) {
curwin->w_wcol = 0;
- ptr = (char_u *)get_cursor_line_ptr();
+ ptr = get_cursor_line_ptr();
chartabsize_T cts;
- init_chartabsize_arg(&cts, curwin,
- curwin->w_cursor.lnum, 0, (char *)ptr, (char *)ptr);
- while ((char_u *)cts.cts_ptr < ptr + curwin->w_cursor.col) {
+ init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, ptr, ptr);
+ while (cts.cts_ptr < ptr + curwin->w_cursor.col) {
if (!ascii_iswhite(*cts.cts_ptr)) {
curwin->w_wcol = cts.cts_vcol;
}
@@ -2563,9 +2545,9 @@ static int vgetorpeek(bool advance)
if (col > 0 && curwin->w_wcol > 0) {
// Correct when the cursor is on the right halve
// of a double-wide character.
- ptr = (char_u *)get_cursor_line_ptr();
- col -= utf_head_off((char *)ptr, (char *)ptr + col);
- if (utf_ptr2cells((char *)ptr + col) > 1) {
+ ptr = get_cursor_line_ptr();
+ col -= utf_head_off(ptr, ptr + col);
+ if (utf_ptr2cells(ptr + col) > 1) {
curwin->w_wcol--;
}
}
@@ -2583,7 +2565,7 @@ static int vgetorpeek(bool advance)
// Allow mapping for just typed characters. When we get here c
// is the number of extra bytes and typebuf.tb_len is 1.
- for (n = 1; n <= c; n++) {
+ for (int n = 1; n <= c; n++) {
typebuf.tb_noremap[typebuf.tb_off + n] = RM_YES;
}
typebuf.tb_len += c;
@@ -2651,7 +2633,7 @@ static int vgetorpeek(bool advance)
// input from the user), show the partially matched characters
// to the user with showcmd.
int showcmd_idx = 0;
- c1 = 0;
+ bool showing_partial = false;
if (typebuf.tb_len > 0 && advance && !exmode_active) {
if (((State & (MODE_NORMAL | MODE_INSERT)) || State == MODE_LANGMAP)
&& State != MODE_HITRETURN) {
@@ -2660,11 +2642,11 @@ static int vgetorpeek(bool advance)
&& ptr2cells((char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1) == 1) {
edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], false);
setcursor(); // put cursor back where it belongs
- c1 = 1;
+ showing_partial = true;
}
// need to use the col and row from above here
- old_wcol = curwin->w_wcol;
- old_wrow = curwin->w_wrow;
+ int old_wcol = curwin->w_wcol;
+ int old_wrow = curwin->w_wrow;
curwin->w_wcol = new_wcol;
curwin->w_wrow = new_wrow;
push_showcmd();
@@ -2678,12 +2660,15 @@ static int vgetorpeek(bool advance)
curwin->w_wrow = old_wrow;
}
- // this looks nice when typing a dead character map
- if ((State & MODE_CMDLINE) && cmdline_star == 0) {
+ // This looks nice when typing a dead character map.
+ // There is no actual command line for get_number().
+ if ((State & MODE_CMDLINE)
+ && get_cmdline_info()->cmdbuff != NULL
+ && cmdline_star == 0) {
char *p = (char *)typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1;
if (ptr2cells(p) == 1 && (uint8_t)(*p) < 128) {
putcmdline(*p, false);
- c1 = 1;
+ showing_partial = true;
}
}
}
@@ -2695,20 +2680,20 @@ static int vgetorpeek(bool advance)
timedout = false;
}
- long wait_time = 0;
+ int wait_time = 0;
if (advance) {
if (typebuf.tb_len == 0 || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) {
// blocking wait
- wait_time = -1L;
+ wait_time = -1;
} else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) {
- wait_time = p_ttm;
+ wait_time = (int)p_ttm;
} else {
- wait_time = p_tm;
+ wait_time = (int)p_tm;
}
}
- wait_tb_len = typebuf.tb_len;
+ int wait_tb_len = typebuf.tb_len;
c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len,
typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1,
wait_time);
@@ -2716,11 +2701,12 @@ static int vgetorpeek(bool advance)
if (showcmd_idx != 0) {
pop_showcmd();
}
- if (c1 == 1) {
+ if (showing_partial == 1) {
if (State & MODE_INSERT) {
edit_unputchar();
}
- if (State & MODE_CMDLINE) {
+ if ((State & MODE_CMDLINE)
+ && get_cmdline_info()->cmdbuff != NULL) {
unputcmdline();
} else {
setcursor(); // put cursor back where it belongs
@@ -2743,7 +2729,7 @@ static int vgetorpeek(bool advance)
typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES;
}
}
- } // for (;;)
+ } // while (true)
} // if (!character from stuffbuf)
// if advance is false don't loop on NULs
@@ -2769,14 +2755,9 @@ static int vgetorpeek(bool advance)
}
if (timedout && c == ESC) {
- char_u nop_buf[3];
-
// When recording there will be no timeout. Add a <Nop> after the ESC
// to avoid that it forms a key code with following characters.
- nop_buf[0] = K_SPECIAL;
- nop_buf[1] = KS_EXTRA;
- nop_buf[2] = KE_NOP;
- gotchars(nop_buf, 3);
+ gotchars_nop();
}
vgetc_busy--;
@@ -2807,13 +2788,13 @@ static int vgetorpeek(bool advance)
/// Return -1 when end of input script reached.
///
/// @param wait_time milliseconds
-int inchar(char_u *buf, int maxlen, long wait_time)
+int inchar(uint8_t *buf, int maxlen, long wait_time)
{
int len = 0; // Init for GCC.
int retesc = false; // Return ESC with gotint.
const int tb_change_cnt = typebuf.tb_change_cnt;
- if (wait_time == -1L || wait_time > 100L) {
+ if (wait_time == -1 || wait_time > 100) {
// flush output before waiting
ui_flush();
}
@@ -2846,7 +2827,7 @@ int inchar(char_u *buf, int maxlen, long wait_time)
return -1;
}
} else {
- buf[0] = (char_u)script_char;
+ buf[0] = (uint8_t)script_char;
len = 1;
}
}
@@ -2860,10 +2841,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
// and buf may be pointing inside typebuf.tb_buf[].
if (got_int) {
#define DUM_LEN (MAXMAPLEN * 3 + 3)
- char_u dum[DUM_LEN + 1];
+ uint8_t dum[DUM_LEN + 1];
- for (;;) {
- len = os_inchar(dum, DUM_LEN, 0L, 0, NULL);
+ while (true) {
+ len = os_inchar(dum, DUM_LEN, 0, 0, NULL);
if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) {
break;
}
@@ -2872,8 +2853,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
}
// Always flush the output characters when getting input characters
- // from the user.
- ui_flush();
+ // from the user and not just peeking.
+ if (wait_time == -1 || wait_time > 10) {
+ ui_flush();
+ }
// Fill up to a third of the buffer, because each character may be
// tripled below.
@@ -2896,10 +2879,10 @@ int inchar(char_u *buf, int maxlen, long wait_time)
return fix_input_buffer(buf, len);
}
-// Fix typed characters for use by vgetc() and check_termcode().
-// "buf[]" must have room to triple the number of bytes!
-// Returns the new length.
-int fix_input_buffer(char_u *buf, int len)
+/// Fix typed characters for use by vgetc().
+/// "buf[]" must have room to triple the number of bytes!
+/// Returns the new length.
+int fix_input_buffer(uint8_t *buf, int len)
FUNC_ATTR_NONNULL_ALL
{
if (!using_script()) {
@@ -2910,19 +2893,18 @@ int fix_input_buffer(char_u *buf, int len)
}
// Reading from script, need to process special bytes
- int i;
- char_u *p = buf;
+ uint8_t *p = buf;
// Two characters are special: NUL and K_SPECIAL.
// Replace NUL by K_SPECIAL KS_ZERO KE_FILLER
// Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER
- for (i = len; --i >= 0; p++) {
+ for (int i = len; --i >= 0; p++) {
if (p[0] == NUL
|| (p[0] == K_SPECIAL
&& (i < 2 || p[1] != KS_EXTRA))) {
memmove(p + 3, p + 1, (size_t)i);
- p[2] = (char_u)K_THIRD(p[0]);
- p[1] = (char_u)K_SECOND(p[0]);
+ p[2] = (uint8_t)K_THIRD(p[0]);
+ p[1] = (uint8_t)K_SECOND(p[0]);
p[0] = K_SPECIAL;
p += 2;
len += 2;
@@ -2932,28 +2914,19 @@ int fix_input_buffer(char_u *buf, int len)
return len;
}
-static bool typebuf_match_len(const uint8_t *str, int *mlen)
-{
- int i;
- for (i = 0; i < typebuf.tb_len && str[i]; i++) {
- if (str[i] != typebuf.tb_buf[typebuf.tb_off + i]) {
- break;
- }
- }
- *mlen = i;
- return str[i] == NUL; // matched the whole string
-}
-
-/// Get command argument for <Cmd> key
+/// Function passed to do_cmdline() to get the command after a <Cmd> key from
+/// typeahead.
char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
{
garray_T line_ga;
- int c1 = -1, c2;
+ int c1 = -1;
+ int c2;
int cmod = 0;
bool aborted = false;
ga_init(&line_ga, 1, 32);
+ // no mapping for these characters
no_mapping++;
got_int = false;
@@ -2963,16 +2936,17 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
if (vgetorpeek(false) == NUL) {
// incomplete <Cmd> is an error, because there is not much the user
// could do in this state.
- emsg(e_cmdmap_err);
+ emsg(_(e_cmd_mapping_must_end_with_cr));
aborted = true;
break;
}
// Get one character at a time.
c1 = vgetorpeek(true);
+
// Get two extra bytes for special keys
if (c1 == K_SPECIAL) {
- c1 = vgetorpeek(true); // no mapping for these chars
+ c1 = vgetorpeek(true);
c2 = vgetorpeek(true);
if (c1 == KS_MODIFIER) {
cmod = c2;
@@ -2988,8 +2962,8 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
} else if (c1 == ESC) {
aborted = true;
} else if (c1 == K_COMMAND) {
- // special case to give nicer error message
- emsg(e_cmdmap_repeated);
+ // give a nicer error message for this special case
+ emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
aborted = true;
} else if (c1 == K_SNR) {
ga_concat(&line_ga, "<SNR>");
@@ -3020,7 +2994,12 @@ char *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat)
return line_ga.ga_data;
}
-bool map_execute_lua(void)
+/// Handle a Lua mapping: get its LuaRef from typeahead and execute it.
+///
+/// @param may_repeat save the LuaRef for redoing with "." later
+///
+/// @return false if getting the LuaRef was aborted, true otherwise
+bool map_execute_lua(bool may_repeat)
{
garray_T line_ga;
int c1 = -1;
@@ -3052,6 +3031,10 @@ bool map_execute_lua(void)
}
LuaRef ref = (LuaRef)atoi(line_ga.ga_data);
+ if (may_repeat) {
+ repeat_luaref = ref;
+ }
+
Error err = ERROR_INIT;
Array args = ARRAY_DICT_INIT;
nlua_call_ref(ref, NULL, args, false, &err);
diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h
index 6996b00c6e..177a021706 100644
--- a/src/nvim/getchar.h
+++ b/src/nvim/getchar.h
@@ -1,29 +1,20 @@
-#ifndef NVIM_GETCHAR_H
-#define NVIM_GETCHAR_H
+#pragma once
-#include "nvim/os/fileio.h"
-#include "nvim/vim.h"
+#include <stdbool.h>
+#include <stdint.h>
-/// Values for "noremap" argument of ins_typebuf()
-///
-/// Also used for map->m_noremap and menu->noremap[].
-enum RemapValues {
- REMAP_YES = 0, ///< Allow remapping.
- REMAP_NONE = -1, ///< No remapping.
- REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
- REMAP_SKIP = -3, ///< No remapping for first char.
-};
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/getchar_defs.h" // IWYU pragma: export
+#include "nvim/os/fileio.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// Argument for flush_buffers().
+/// Argument for flush_buffers().
typedef enum {
FLUSH_MINIMAL,
- FLUSH_TYPEAHEAD, // flush current typebuf contents
- FLUSH_INPUT, // flush typebuf and inchar() input
+ FLUSH_TYPEAHEAD, ///< flush current typebuf contents
+ FLUSH_INPUT, ///< flush typebuf and inchar() input
} flush_buffers_T;
-#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code
-#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping
-
/// Maximum number of streams to read script from
enum { NSCRIPT = 15, };
@@ -33,4 +24,3 @@ extern FileDescriptor *scriptin[NSCRIPT];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "getchar.h.generated.h"
#endif
-#endif // NVIM_GETCHAR_H
diff --git a/src/nvim/getchar_defs.h b/src/nvim/getchar_defs.h
new file mode 100644
index 0000000000..5e6db7d9f3
--- /dev/null
+++ b/src/nvim/getchar_defs.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "nvim/api/private/defs.h"
+
+typedef struct buffblock buffblock_T;
+typedef struct buffheader buffheader_T;
+
+/// structure used to store one block of the stuff/redo/recording buffers
+struct buffblock {
+ buffblock_T *b_next; ///< pointer to next buffblock
+ char b_str[1]; ///< contents (actually longer)
+};
+
+/// header used for the stuff buffer and the redo buffer
+struct buffheader {
+ buffblock_T bh_first; ///< first (dummy) block of list
+ buffblock_T *bh_curr; ///< buffblock for appending
+ size_t bh_index; ///< index for reading
+ size_t bh_space; ///< space in bh_curr for appending
+};
+
+typedef struct {
+ buffheader_T sr_redobuff;
+ buffheader_T sr_old_redobuff;
+} save_redo_T;
+
+/// Used for the typeahead buffer: typebuf.
+typedef struct {
+ uint8_t *tb_buf; ///< buffer for typed characters
+ uint8_t *tb_noremap; ///< mapping flags for characters in tb_buf[]
+ int tb_buflen; ///< size of tb_buf[]
+ int tb_off; ///< current position in tb_buf[]
+ int tb_len; ///< number of valid bytes in tb_buf[]
+ int tb_maplen; ///< nr of mapped bytes in tb_buf[]
+ int tb_silent; ///< nr of silently mapped bytes in tb_buf[]
+ int tb_no_abbr_cnt; ///< nr of bytes without abbrev. in tb_buf[]
+ int tb_change_cnt; ///< nr of time tb_buf was changed; never zero
+} typebuf_T;
+
+/// Struct to hold the saved typeahead for save_typeahead().
+typedef struct {
+ typebuf_T save_typebuf;
+ bool typebuf_valid; ///< true when save_typebuf valid
+ int old_char;
+ int old_mod_mask;
+ buffheader_T save_readbuf1;
+ buffheader_T save_readbuf2;
+ String save_inputbuf;
+} tasave_T;
+
+/// Values for "noremap" argument of ins_typebuf()
+///
+/// Also used for map->m_noremap and menu->noremap[].
+enum RemapValues {
+ REMAP_YES = 0, ///< Allow remapping.
+ REMAP_NONE = -1, ///< No remapping.
+ REMAP_SCRIPT = -2, ///< Remap script-local mappings only.
+ REMAP_SKIP = -3, ///< No remapping for first char.
+};
diff --git a/src/nvim/gettext.h b/src/nvim/gettext.h
index 6d2a55124e..2c7b5626d2 100644
--- a/src/nvim/gettext.h
+++ b/src/nvim/gettext.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_GETTEXT_H
-#define NVIM_GETTEXT_H
+#pragma once
#ifdef HAVE_WORKING_LIBINTL
-# include <libintl.h>
-# define _(x) gettext((char *)(x))
+# include <libintl.h> // IWYU pragma: export
+# define _(x) gettext(x) // NOLINT(bugprone-reserved-identifier)
// XXX do we actually need this?
# ifdef gettext_noop
# define N_(x) gettext_noop(x)
@@ -17,12 +16,10 @@
# undef setlocale
# endif
#else
-# define _(x) ((char *)(x))
+# define _(x) ((char *)(x)) // NOLINT(bugprone-reserved-identifier)
# define N_(x) x
# define NGETTEXT(x, xs, n) ((n) == 1 ? (x) : (xs))
# define bindtextdomain(x, y) // empty
# define bind_textdomain_codeset(x, y) // empty
# define textdomain(x) // empty
#endif
-
-#endif // NVIM_GETTEXT_H
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index df4ae05522..270ffe4fa0 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -1,21 +1,23 @@
-#ifndef NVIM_GLOBALS_H
-#define NVIM_GLOBALS_H
+#pragma once
#include <inttypes.h>
#include <stdbool.h>
-#include "nvim/ascii.h"
+#include "nvim/arglist_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/event/loop.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval_defs.h"
-#include "nvim/iconv.h"
-#include "nvim/macros.h"
+#include "nvim/getchar_defs.h"
+#include "nvim/iconv_defs.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/menu_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/runtime.h"
+#include "nvim/state_defs.h"
#include "nvim/syntax_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#define IOSIZE (1024 + 1) // file I/O and sprintf buffer size
@@ -23,14 +25,15 @@
#define MSG_BUF_CLEN (MSG_BUF_LEN / 6) // cell length (worst case: utf-8
// takes 6 bytes for one cell)
-#ifdef MSWIN
-# define _PATHSEPSTR "\\"
-#else
-# define _PATHSEPSTR "/"
-#endif
+// FILETYPE_FILE used for file type detection
+// FTPLUGIN_FILE used for loading filetype plugin files
+// INDENT_FILE used for loading indent files
+// FTOFF_FILE used for file type detection
+// FTPLUGOF_FILE used for loading settings files
+// INDOFF_FILE used for loading indent files
#ifndef FILETYPE_FILE
-# define FILETYPE_FILE "filetype.lua filetype.vim"
+# define FILETYPE_FILE "filetype.lua filetype.vim"
#endif
#ifndef FTPLUGIN_FILE
@@ -56,15 +59,15 @@
#define DFLT_ERRORFILE "errors.err"
#ifndef SYS_VIMRC_FILE
-# define SYS_VIMRC_FILE "$VIM" _PATHSEPSTR "sysinit.vim"
+# define SYS_VIMRC_FILE "$VIM/sysinit.vim"
#endif
#ifndef DFLT_HELPFILE
-# define DFLT_HELPFILE "$VIMRUNTIME" _PATHSEPSTR "doc" _PATHSEPSTR "help.txt"
+# define DFLT_HELPFILE "$VIMRUNTIME/doc/help.txt"
#endif
#ifndef SYNTAX_FNAME
-# define SYNTAX_FNAME "$VIMRUNTIME" _PATHSEPSTR "syntax" _PATHSEPSTR "%s.vim"
+# define SYNTAX_FNAME "$VIMRUNTIME/syntax/%s.vim"
#endif
#ifndef EXRC_FILE
@@ -83,7 +86,7 @@ EXTERN struct nvim_stats_s {
int64_t fsync;
int64_t redraw;
int16_t log_skip; // How many logs were tried and skipped before log_init.
-} g_stats INIT(= { 0, 0, 0 });
+} g_stats INIT( = { 0, 0, 0 });
// Values for "starting".
#define NO_SCREEN 2 // no screen updating yet
@@ -97,8 +100,8 @@ EXTERN struct nvim_stats_s {
// up).
#define DFLT_COLS 80 // default value for 'columns'
#define DFLT_ROWS 24 // default value for 'lines'
-EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen
-EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen
+EXTERN int Rows INIT( = DFLT_ROWS); // nr of rows in the screen
+EXTERN int Columns INIT( = DFLT_COLS); // nr of columns in the screen
// We use 64-bit file functions here, if available. E.g. ftello() returns
// off_t instead of long, which helps if long is 32 bit and off_t is 64 bit.
@@ -130,11 +133,11 @@ typedef off_t off_T;
// When vgetc() is called, it sets mod_mask to the set of modifiers that are
// held down based on the MOD_MASK_* symbols that are read first.
-EXTERN int mod_mask INIT(= 0); // current key modifiers
+EXTERN int mod_mask INIT( = 0); // current key modifiers
// The value of "mod_mask" and the unmodified character before calling merge_modifiers().
-EXTERN int vgetc_mod_mask INIT(= 0);
-EXTERN int vgetc_char INIT(= 0);
+EXTERN int vgetc_mod_mask INIT( = 0);
+EXTERN int vgetc_char INIT( = 0);
// Cmdline_row is the row where the command line starts, just below the
// last window.
@@ -145,65 +148,65 @@ EXTERN int vgetc_char INIT(= 0);
// update_screen().
EXTERN int cmdline_row;
-EXTERN bool redraw_cmdline INIT(= false); // cmdline must be redrawn
-EXTERN bool redraw_mode INIT(= false); // mode must be redrawn
-EXTERN bool clear_cmdline INIT(= false); // cmdline must be cleared
-EXTERN bool mode_displayed INIT(= false); // mode is being displayed
-EXTERN int cmdline_star INIT(= false); // cmdline is encrypted
-EXTERN bool redrawing_cmdline INIT(= false); // cmdline is being redrawn
-EXTERN bool cmdline_was_last_drawn INIT(= false); // cmdline was last drawn
+EXTERN bool redraw_cmdline INIT( = false); // cmdline must be redrawn
+EXTERN bool redraw_mode INIT( = false); // mode must be redrawn
+EXTERN bool clear_cmdline INIT( = false); // cmdline must be cleared
+EXTERN bool mode_displayed INIT( = false); // mode is being displayed
+EXTERN int cmdline_star INIT( = false); // cmdline is encrypted
+EXTERN bool redrawing_cmdline INIT( = false); // cmdline is being redrawn
+EXTERN bool cmdline_was_last_drawn INIT( = false); // cmdline was last drawn
-EXTERN bool exec_from_reg INIT(= false); // executing register
+EXTERN bool exec_from_reg INIT( = false); // executing register
// When '$' is included in 'cpoptions' option set:
// When a change command is given that deletes only part of a line, a dollar
// is put at the end of the changed text. dollar_vcol is set to the virtual
// column of this '$'. -1 is used to indicate no $ is being displayed.
-EXTERN colnr_T dollar_vcol INIT(= -1);
+EXTERN colnr_T dollar_vcol INIT( = -1);
// Variables for Insert mode completion.
-EXTERN char *edit_submode INIT(= NULL); // msg for CTRL-X submode
-EXTERN char *edit_submode_pre INIT(= NULL); // prepended to edit_submode
-EXTERN char *edit_submode_extra INIT(= NULL); // appended to edit_submode
+EXTERN char *edit_submode INIT( = NULL); // msg for CTRL-X submode
+EXTERN char *edit_submode_pre INIT( = NULL); // prepended to edit_submode
+EXTERN char *edit_submode_extra INIT( = NULL); // appended to edit_submode
EXTERN hlf_T edit_submode_highl; // highl. method for extra info
// state for putting characters in the message area
-EXTERN int cmdmsg_rl INIT(= false); // cmdline is drawn right to left
+EXTERN bool cmdmsg_rl INIT( = false); // cmdline is drawn right to left
EXTERN int msg_col;
EXTERN int msg_row;
EXTERN int msg_scrolled; // Number of screen lines that windows have
// scrolled because of printing messages.
// when true don't set need_wait_return in msg_puts_attr()
// when msg_scrolled is non-zero
-EXTERN bool msg_scrolled_ign INIT(= false);
+EXTERN bool msg_scrolled_ign INIT( = false);
// Whether the screen is damaged due to scrolling. Sometimes msg_scrolled
// is reset before the screen is redrawn, so we need to keep track of this.
-EXTERN bool msg_did_scroll INIT(= false);
-
-EXTERN char *keep_msg INIT(= NULL); // msg to be shown after redraw
-EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg
-EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw
-EXTERN int msg_scroll INIT(= false); // msg_start() will scroll
-EXTERN bool msg_didout INIT(= false); // msg_outstr() was used in line
-EXTERN bool msg_didany INIT(= false); // msg_outstr() was used at all
-EXTERN bool msg_nowait INIT(= false); // don't wait for this msg
-EXTERN int emsg_off INIT(= 0); // don't display errors for now,
- // unless 'debug' is set.
-EXTERN bool info_message INIT(= false); // printing informative message
-EXTERN bool msg_hist_off INIT(= false); // don't add messages to history
-EXTERN bool need_clr_eos INIT(= false); // need to clear text before
- // displaying a message.
-EXTERN int emsg_skip INIT(= 0); // don't display errors for
- // expression that is skipped
-EXTERN bool emsg_severe INIT(= false); // use message of next of several
- // emsg() calls for throw
+EXTERN bool msg_did_scroll INIT( = false);
+
+EXTERN char *keep_msg INIT( = NULL); // msg to be shown after redraw
+EXTERN int keep_msg_attr INIT( = 0); // highlight attr for keep_msg
+EXTERN bool need_fileinfo INIT( = false); // do fileinfo() after redraw
+EXTERN int msg_scroll INIT( = false); // msg_start() will scroll
+EXTERN bool msg_didout INIT( = false); // msg_outstr() was used in line
+EXTERN bool msg_didany INIT( = false); // msg_outstr() was used at all
+EXTERN bool msg_nowait INIT( = false); // don't wait for this msg
+EXTERN int emsg_off INIT( = 0); // don't display errors for now,
+ // unless 'debug' is set.
+EXTERN bool info_message INIT( = false); // printing informative message
+EXTERN bool msg_hist_off INIT( = false); // don't add messages to history
+EXTERN bool need_clr_eos INIT( = false); // need to clear text before
+ // displaying a message.
+EXTERN int emsg_skip INIT( = 0); // don't display errors for
+ // expression that is skipped
+EXTERN bool emsg_severe INIT( = false); // use message of next of several
+ // emsg() calls for throw
// used by assert_fails()
-EXTERN char *emsg_assert_fails_msg INIT(= NULL);
-EXTERN long emsg_assert_fails_lnum INIT(= 0);
-EXTERN char *emsg_assert_fails_context INIT(= NULL);
+EXTERN char *emsg_assert_fails_msg INIT( = NULL);
+EXTERN long emsg_assert_fails_lnum INIT( = 0);
+EXTERN char *emsg_assert_fails_context INIT( = NULL);
-EXTERN bool did_endif INIT(= false); // just had ":endif"
+EXTERN bool did_endif INIT( = false); // just had ":endif"
EXTERN dict_T vimvardict; // Dictionary with v: variables
EXTERN dict_T globvardict; // Dictionary with g: variables
/// g: value
@@ -214,39 +217,39 @@ EXTERN bool called_vim_beep; // set if vim_beep() is called
EXTERN bool did_emsg_syntax; // did_emsg set because of a
// syntax error
EXTERN int called_emsg; // always incremented by emsg()
-EXTERN int ex_exitval INIT(= 0); // exit value for ex mode
-EXTERN bool emsg_on_display INIT(= false); // there is an error message
-EXTERN bool rc_did_emsg INIT(= false); // vim_regcomp() called emsg()
+EXTERN int ex_exitval INIT( = 0); // exit value for ex mode
+EXTERN bool emsg_on_display INIT( = false); // there is an error message
+EXTERN bool rc_did_emsg INIT( = false); // vim_regcomp() called emsg()
-EXTERN int no_wait_return INIT(= 0); // don't wait for return for now
-EXTERN bool need_wait_return INIT(= false); // need to wait for return later
-EXTERN bool did_wait_return INIT(= false); // wait_return() was used and
- // nothing written since then
-EXTERN bool need_maketitle INIT(= true); // call maketitle() soon
+EXTERN int no_wait_return INIT( = 0); // don't wait for return for now
+EXTERN bool need_wait_return INIT( = false); // need to wait for return later
+EXTERN bool did_wait_return INIT( = false); // wait_return() was used and
+ // nothing written since then
+EXTERN bool need_maketitle INIT( = true); // call maketitle() soon
-EXTERN int quit_more INIT(= false); // 'q' hit at "--more--" msg
-EXTERN int vgetc_busy INIT(= 0); // when inside vgetc() then > 0
+EXTERN bool quit_more INIT( = false); // 'q' hit at "--more--" msg
+EXTERN int vgetc_busy INIT( = 0); // when inside vgetc() then > 0
-EXTERN bool didset_vim INIT(= false); // did set $VIM ourselves
-EXTERN bool didset_vimruntime INIT(= false); // idem for $VIMRUNTIME
+EXTERN bool didset_vim INIT( = false); // did set $VIM ourselves
+EXTERN bool didset_vimruntime INIT( = false); // idem for $VIMRUNTIME
/// Lines left before a "more" message. Ex mode needs to be able to reset this
/// after you type something.
-EXTERN int lines_left INIT(= -1); // lines left for listing
-EXTERN int msg_no_more INIT(= false); // don't use more prompt, truncate
- // messages
+EXTERN int lines_left INIT( = -1); // lines left for listing
+EXTERN bool msg_no_more INIT( = false); // don't use more prompt, truncate
+ // messages
-EXTERN int ex_nesting_level INIT(= 0); // nesting level
-EXTERN int debug_break_level INIT(= -1); // break below this level
-EXTERN bool debug_did_msg INIT(= false); // did "debug mode" message
-EXTERN int debug_tick INIT(= 0); // breakpoint change count
-EXTERN int debug_backtrace_level INIT(= 0); // breakpoint backtrace level
+EXTERN int ex_nesting_level INIT( = 0); // nesting level
+EXTERN int debug_break_level INIT( = -1); // break below this level
+EXTERN bool debug_did_msg INIT( = false); // did "debug mode" message
+EXTERN int debug_tick INIT( = 0); // breakpoint change count
+EXTERN int debug_backtrace_level INIT( = 0); // breakpoint backtrace level
// Values for "do_profiling".
#define PROF_NONE 0 ///< profiling not started
#define PROF_YES 1 ///< profiling busy
#define PROF_PAUSED 2 ///< profiling paused
-EXTERN int do_profiling INIT(= PROF_NONE); ///< PROF_ values
+EXTERN int do_profiling INIT( = PROF_NONE); ///< PROF_ values
/// Exception currently being thrown. Used to pass an exception to a different
/// cstack. Also used for discarding an exception before it is caught or made
@@ -255,19 +258,19 @@ EXTERN except_T *current_exception;
/// An exception is being thrown. Reset when the exception is caught or as
/// long as it is pending in a finally clause.
-EXTERN bool did_throw INIT(= false);
+EXTERN bool did_throw INIT( = false);
/// Set when a throw that cannot be handled in do_cmdline() must be propagated
/// to the cstack of the previously called do_cmdline().
-EXTERN bool need_rethrow INIT(= false);
+EXTERN bool need_rethrow INIT( = false);
/// Set when a ":finish" or ":return" that cannot be handled in do_cmdline()
/// must be propagated to the cstack of the previously called do_cmdline().
-EXTERN bool check_cstack INIT(= false);
+EXTERN bool check_cstack INIT( = false);
/// Number of nested try conditionals (across function calls and ":source"
/// commands).
-EXTERN int trylevel INIT(= 0);
+EXTERN int trylevel INIT( = 0);
/// When "force_abort" is true, always skip commands after an error message,
/// even after the outermost ":endif", ":endwhile" or ":endfor" or for a
@@ -275,7 +278,7 @@ EXTERN int trylevel INIT(= 0);
/// non-zero (and ":silent!" was not used) or an exception is being thrown at
/// the time an error is detected. It is set to false when "trylevel" gets
/// zero again and there was no error or interrupt or throw.
-EXTERN int force_abort INIT(= false);
+EXTERN bool force_abort INIT( = false);
/// "msg_list" points to a variable in the stack of do_cmdline() which keeps
/// the list of arguments of several emsg() calls, one of which is to be
@@ -285,19 +288,19 @@ EXTERN int force_abort INIT(= false);
/// same as the "msg" field of that element, but can be identical to the "msg"
/// field of a later list element, when the "emsg_severe" flag was set when the
/// emsg() call was made.
-EXTERN msglist_T **msg_list INIT(= NULL);
+EXTERN msglist_T **msg_list INIT( = NULL);
/// When set, don't convert an error to an exception. Used when displaying the
/// interrupt message or reporting an exception that is still uncaught at the
/// top level (which has already been discarded then). Also used for the error
/// message when no exception can be thrown.
-EXTERN bool suppress_errthrow INIT(= false);
+EXTERN bool suppress_errthrow INIT( = false);
/// The stack of all caught and not finished exceptions. The exception on the
/// top of the stack is the one got by evaluation of v:exception. The complete
/// stack of all caught and pending exceptions is embedded in the various
/// cstacks; the pending exceptions, however, are not on the caught stack.
-EXTERN except_T *caught_stack INIT(= NULL);
+EXTERN except_T *caught_stack INIT( = NULL);
///
/// Garbage collection can only take place when we are sure there are no Lists
@@ -307,9 +310,9 @@ EXTERN except_T *caught_stack INIT(= NULL);
/// we do garbage collection before waiting for a char at the toplevel.
/// "garbage_collect_at_exit" indicates garbagecollect(1) was called.
///
-EXTERN bool may_garbage_collect INIT(= false);
-EXTERN int want_garbage_collect INIT(= false);
-EXTERN int garbage_collect_at_exit INIT(= false);
+EXTERN bool may_garbage_collect INIT( = false);
+EXTERN bool want_garbage_collect INIT( = false);
+EXTERN bool garbage_collect_at_exit INIT( = false);
// Special values for current_SID.
#define SID_MODELINE (-1) // when using a modeline
@@ -324,11 +327,11 @@ EXTERN int garbage_collect_at_exit INIT(= false);
#define SID_STR (-10) // for sourcing a string with no script item
// Script CTX being sourced or was sourced to define the current function.
-EXTERN sctx_T current_sctx INIT(= { 0, 0, 0 });
+EXTERN sctx_T current_sctx INIT( = { 0, 0, 0 });
// ID of the current channel making a client API call
-EXTERN uint64_t current_channel_id INIT(= 0);
+EXTERN uint64_t current_channel_id INIT( = 0);
-EXTERN bool did_source_packages INIT(= false);
+EXTERN bool did_source_packages INIT( = false);
// Scope information for the code that indirectly triggered the current
// provider function call
@@ -336,76 +339,77 @@ EXTERN struct caller_scope {
sctx_T script_ctx;
estack_T es_entry;
char *autocmd_fname, *autocmd_match;
+ bool autocmd_fname_full;
int autocmd_bufnr;
void *funccalp;
} provider_caller_scope;
-EXTERN int provider_call_nesting INIT(= 0);
+EXTERN int provider_call_nesting INIT( = 0);
-EXTERN int t_colors INIT(= 256); // int value of T_CCO
+EXTERN int t_colors INIT( = 256); // int value of T_CCO
// Flags to indicate an additional string for highlight name completion.
-EXTERN int include_none INIT(= 0); // when 1 include "None"
-EXTERN int include_default INIT(= 0); // when 1 include "default"
-EXTERN int include_link INIT(= 0); // when 2 include "link" and "clear"
+EXTERN int include_none INIT( = 0); // when 1 include "None"
+EXTERN int include_default INIT( = 0); // when 1 include "default"
+EXTERN int include_link INIT( = 0); // when 2 include "link" and "clear"
// When highlight_match is true, highlight a match, starting at the cursor
// position. Search_match_lines is the number of lines after the match (0 for
// a match within one line), search_match_endcol the column number of the
// character just after the match in the last line.
-EXTERN bool highlight_match INIT(= false); // show search match pos
+EXTERN bool highlight_match INIT( = false); // show search match pos
EXTERN linenr_T search_match_lines; // lines of matched string
EXTERN colnr_T search_match_endcol; // col nr of match end
-EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat
-EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat
+EXTERN linenr_T search_first_line INIT( = 0); // for :{FIRST},{last}s/pat
+EXTERN linenr_T search_last_line INIT( = MAXLNUM); // for :{first},{LAST}s/pat
-EXTERN bool no_smartcase INIT(= false); // don't use 'smartcase' once
+EXTERN bool no_smartcase INIT( = false); // don't use 'smartcase' once
-EXTERN int need_check_timestamps INIT(= false); // need to check file
- // timestamps asap
-EXTERN int did_check_timestamps INIT(= false); // did check timestamps
- // recently
-EXTERN int no_check_timestamps INIT(= 0); // Don't check timestamps
+EXTERN bool need_check_timestamps INIT( = false); // need to check file
+ // timestamps asap
+EXTERN bool did_check_timestamps INIT( = false); // did check timestamps
+ // recently
+EXTERN int no_check_timestamps INIT( = 0); // Don't check timestamps
-EXTERN int autocmd_busy INIT(= false); // Is apply_autocmds() busy?
-EXTERN int autocmd_no_enter INIT(= false); // *Enter autocmds disabled
-EXTERN int autocmd_no_leave INIT(= false); // *Leave autocmds disabled
+EXTERN bool autocmd_busy INIT( = false); // Is apply_autocmds() busy?
+EXTERN int autocmd_no_enter INIT( = false); // *Enter autocmds disabled
+EXTERN int autocmd_no_leave INIT( = false); // *Leave autocmds disabled
EXTERN int modified_was_set; // did ":set modified"
-EXTERN int did_filetype INIT(= false); // FileType event found
+EXTERN bool did_filetype INIT( = false); // FileType event found
// value for did_filetype when starting to execute autocommands
-EXTERN int keep_filetype INIT(= false);
+EXTERN bool keep_filetype INIT( = false);
// When deleting the current buffer, another one must be loaded.
// If we know which one is preferred, au_new_curbuf is set to it.
-EXTERN bufref_T au_new_curbuf INIT(= { NULL, 0, 0 });
+EXTERN bufref_T au_new_curbuf INIT( = { NULL, 0, 0 });
// When deleting a buffer/window and autocmd_busy is true, do not free the
// buffer/window. but link it in the list starting with
// au_pending_free_buf/ap_pending_free_win, using b_next/w_next.
// Free the buffer/window when autocmd_busy is being set to false.
-EXTERN buf_T *au_pending_free_buf INIT(= NULL);
-EXTERN win_T *au_pending_free_win INIT(= NULL);
+EXTERN buf_T *au_pending_free_buf INIT( = NULL);
+EXTERN win_T *au_pending_free_win INIT( = NULL);
// Mouse coordinates, set by handle_mouse_event()
EXTERN int mouse_grid;
EXTERN int mouse_row;
EXTERN int mouse_col;
-EXTERN bool mouse_past_bottom INIT(= false); // mouse below last line
-EXTERN bool mouse_past_eol INIT(= false); // mouse right of line
-EXTERN int mouse_dragging INIT(= 0); // extending Visual area with
- // mouse dragging
+EXTERN bool mouse_past_bottom INIT( = false); // mouse below last line
+EXTERN bool mouse_past_eol INIT( = false); // mouse right of line
+EXTERN int mouse_dragging INIT( = 0); // extending Visual area with
+ // mouse dragging
// The root of the menu hierarchy.
-EXTERN vimmenu_T *root_menu INIT(= NULL);
+EXTERN vimmenu_T *root_menu INIT( = NULL);
// While defining the system menu, sys_menu is true. This avoids
// overruling of menus that the user already defined.
-EXTERN int sys_menu INIT(= false);
+EXTERN bool sys_menu INIT( = false);
// All windows are linked in a list. firstwin points to the first entry,
// lastwin to the last entry (can be the same as firstwin) and curwin to the
// currently active window.
EXTERN win_T *firstwin; // first window
EXTERN win_T *lastwin; // last window
-EXTERN win_T *prevwin INIT(= NULL); // previous window
+EXTERN win_T *prevwin INIT( = NULL); // previous window
#define ONE_WINDOW (firstwin == lastwin)
#define FOR_ALL_FRAMES(frp, first_frame) \
for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) // NOLINT
@@ -416,10 +420,9 @@ EXTERN win_T *prevwin INIT(= NULL); // previous window
FOR_ALL_TABS(tp) \
FOR_ALL_WINDOWS_IN_TAB(wp, tp)
-// -V:FOR_ALL_WINDOWS_IN_TAB:501
#define FOR_ALL_WINDOWS_IN_TAB(wp, tp) \
- for (win_T *wp = ((tp) == curtab) \
- ? firstwin : (tp)->tp_firstwin; wp != NULL; wp = wp->w_next)
+ for (win_T *wp = ((tp) == curtab) ? firstwin : (tp)->tp_firstwin; \
+ wp != NULL; wp = wp->w_next)
EXTERN win_T *curwin; // currently active window
@@ -432,7 +435,7 @@ typedef struct {
/// When executing autocommands for a buffer that is not in any window, a
/// special window is created to handle the side effects. When autocommands
/// nest we may need more than one.
-EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT(= KV_INITIAL_VALUE);
+EXTERN kvec_t(aucmdwin_T) aucmd_win_vec INIT( = KV_INITIAL_VALUE);
#define aucmd_win (aucmd_win_vec.items)
#define AUCMD_WIN_COUNT ((int)aucmd_win_vec.size)
@@ -446,16 +449,16 @@ EXTERN frame_T *topframe; // top of the window frame tree
EXTERN tabpage_T *first_tabpage;
EXTERN tabpage_T *curtab;
EXTERN tabpage_T *lastused_tabpage;
-EXTERN bool redraw_tabline INIT(= false); // need to redraw tabline
+EXTERN bool redraw_tabline INIT( = false); // need to redraw tabline
// Iterates over all tabs in the tab list
#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next)
// All buffers are linked in a list. 'firstbuf' points to the first entry,
// 'lastbuf' to the last entry and 'curbuf' to the currently active buffer.
-EXTERN buf_T *firstbuf INIT(= NULL); // first buffer
-EXTERN buf_T *lastbuf INIT(= NULL); // last buffer
-EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
+EXTERN buf_T *firstbuf INIT( = NULL); // first buffer
+EXTERN buf_T *lastbuf INIT( = NULL); // last buffer
+EXTERN buf_T *curbuf INIT( = NULL); // currently active buffer
// Iterates over all buffers in the buffer list.
#define FOR_ALL_BUFFERS(buf) \
@@ -466,16 +469,12 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer
#define FOR_ALL_BUF_WININFO(buf, wip) \
for ((wip) = (buf)->b_wininfo; (wip) != NULL; (wip) = (wip)->wi_next) // NOLINT
-// Iterate through all the signs placed in a buffer
-#define FOR_ALL_SIGNS_IN_BUF(buf, sign) \
- for ((sign) = (buf)->b_signlist; (sign) != NULL; (sign) = (sign)->se_next) // NOLINT
-
// List of files being edited (global argument list). curwin->w_alist points
// to this when the window is using the global argument list.
EXTERN alist_T global_alist; // global argument list
-EXTERN int max_alist_id INIT(= 0); ///< the previous argument list id
-EXTERN bool arg_had_last INIT(= false); // accessed last file in
- // global_alist
+EXTERN int max_alist_id INIT( = 0); ///< the previous argument list id
+EXTERN bool arg_had_last INIT( = false); // accessed last file in
+ // global_alist
EXTERN int ru_col; // column for ruler
EXTERN int ru_wid; // 'rulerfmt' width of ruler when non-zero
@@ -485,61 +484,61 @@ EXTERN int sc_col; // column for shown command
// updating).
// First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished.
-EXTERN int starting INIT(= NO_SCREEN);
+EXTERN int starting INIT( = NO_SCREEN);
// true when planning to exit. Might keep running if there is a changed buffer.
-EXTERN bool exiting INIT(= false);
+EXTERN bool exiting INIT( = false);
// internal value of v:dying
-EXTERN int v_dying INIT(= 0);
+EXTERN int v_dying INIT( = 0);
// is stdin a terminal?
-EXTERN bool stdin_isatty INIT(= true);
+EXTERN bool stdin_isatty INIT( = true);
// is stdout a terminal?
-EXTERN bool stdout_isatty INIT(= true);
+EXTERN bool stdout_isatty INIT( = true);
// is stderr a terminal?
-EXTERN bool stderr_isatty INIT(= true);
+EXTERN bool stderr_isatty INIT( = true);
/// filedesc set by embedder for reading first buffer like `cmd | nvim -`
-EXTERN int stdin_fd INIT(= -1);
+EXTERN int stdin_fd INIT( = -1);
// true when doing full-screen output, otherwise only writing some messages.
-EXTERN int full_screen INIT(= false);
+EXTERN bool full_screen INIT( = false);
/// Non-zero when only "safe" commands are allowed
-EXTERN int secure INIT(= 0);
+EXTERN int secure INIT( = 0);
/// Non-zero when changing text and jumping to another window or editing another buffer is not
/// allowed.
-EXTERN int textlock INIT(= 0);
+EXTERN int textlock INIT( = 0);
/// Non-zero when no buffer name can be changed, no buffer can be deleted and
/// current directory can't be changed. Used for SwapExists et al.
-EXTERN int allbuf_lock INIT(= 0);
+EXTERN int allbuf_lock INIT( = 0);
/// Non-zero when evaluating an expression in a "sandbox". Several things are
/// not allowed then.
-EXTERN int sandbox INIT(= 0);
+EXTERN int sandbox INIT( = 0);
/// Batch-mode: "-es", "-Es", "-l" commandline argument was given.
-EXTERN int silent_mode INIT(= false);
+EXTERN bool silent_mode INIT( = false);
/// Start position of active Visual selection.
EXTERN pos_T VIsual;
/// Whether Visual mode is active.
-EXTERN int VIsual_active INIT(= false);
+EXTERN bool VIsual_active INIT( = false);
/// Whether Select mode is active.
-EXTERN int VIsual_select INIT(= false);
+EXTERN bool VIsual_select INIT( = false);
/// Register name for Select mode
-EXTERN int VIsual_select_reg INIT(= 0);
+EXTERN int VIsual_select_reg INIT( = 0);
/// Restart Select mode when next cmd finished
-EXTERN int restart_VIsual_select INIT(= 0);
+EXTERN int restart_VIsual_select INIT( = 0);
/// Whether to restart the selection after a Select-mode mapping or menu.
EXTERN int VIsual_reselect;
/// Type of Visual mode.
-EXTERN int VIsual_mode INIT(= 'v');
+EXTERN int VIsual_mode INIT( = 'v');
/// true when redoing Visual.
-EXTERN int redo_VIsual_busy INIT(= false);
+EXTERN bool redo_VIsual_busy INIT( = false);
// The Visual area is remembered for reselection.
-EXTERN int resel_VIsual_mode INIT(= NUL); // 'v', 'V', or Ctrl-V
+EXTERN int resel_VIsual_mode INIT( = NUL); // 'v', 'V', or Ctrl-V
EXTERN linenr_T resel_VIsual_line_count; // number of lines
EXTERN colnr_T resel_VIsual_vcol; // nr of cols or end col
@@ -551,40 +550,40 @@ EXTERN pos_T where_paste_started;
// <RETURN> or <ESC> is typed. It is set when an auto-indent is done, and
// reset when any other editing is done on the line. If an <ESC> or <RETURN>
// is received, and did_ai is true, the line is truncated.
-EXTERN bool did_ai INIT(= false);
+EXTERN bool did_ai INIT( = false);
// Column of first char after autoindent. 0 when no autoindent done. Used
// when 'backspace' is 0, to avoid backspacing over autoindent.
-EXTERN colnr_T ai_col INIT(= 0);
+EXTERN colnr_T ai_col INIT( = 0);
// This is a character which will end a start-middle-end comment when typed as
// the first character on a new line. It is taken from the last character of
// the "end" comment leader when the COM_AUTO_END flag is given for that
// comment end in 'comments'. It is only valid when did_ai is true.
-EXTERN int end_comment_pending INIT(= NUL);
+EXTERN int end_comment_pending INIT( = NUL);
// This flag is set after a ":syncbind" to let the check_scrollbind() function
// know that it should not attempt to perform scrollbinding due to the scroll
// that was a result of the ":syncbind." (Otherwise, check_scrollbind() will
// undo some of the work done by ":syncbind.") -ralston
-EXTERN bool did_syncbind INIT(= false);
+EXTERN bool did_syncbind INIT( = false);
// This flag is set when a smart indent has been performed. When the next typed
// character is a '{' the inserted tab will be deleted again.
-EXTERN bool did_si INIT(= false);
+EXTERN bool did_si INIT( = false);
// This flag is set after an auto indent. If the next typed character is a '}'
// one indent will be removed.
-EXTERN bool can_si INIT(= false);
+EXTERN bool can_si INIT( = false);
// This flag is set after an "O" command. If the next typed character is a '{'
// one indent will be removed.
-EXTERN bool can_si_back INIT(= false);
+EXTERN bool can_si_back INIT( = false);
-EXTERN int old_indent INIT(= 0); ///< for ^^D command in insert mode
+EXTERN int old_indent INIT( = 0); ///< for ^^D command in insert mode
// w_cursor before formatting text.
-EXTERN pos_T saved_cursor INIT(= { 0, 0, 0 });
+EXTERN pos_T saved_cursor INIT( = { 0, 0, 0 });
// Stuff for insert mode.
EXTERN pos_T Insstart; // This is where the latest
@@ -596,11 +595,11 @@ EXTERN pos_T Insstart; // This is where the latest
EXTERN pos_T Insstart_orig;
// Stuff for MODE_VREPLACE state.
-EXTERN linenr_T orig_line_count INIT(= 0); // Line count when "gR" started
-EXTERN int vr_lines_changed INIT(= 0); // #Lines changed by "gR" so far
+EXTERN linenr_T orig_line_count INIT( = 0); // Line count when "gR" started
+EXTERN int vr_lines_changed INIT( = 0); // #Lines changed by "gR" so far
// increase around internal delete/replace
-EXTERN int inhibit_delete_count INIT(= 0);
+EXTERN int inhibit_delete_count INIT( = 0);
// These flags are set based upon 'fileencoding'.
// The characters are internally stored as UTF-8
@@ -617,7 +616,7 @@ EXTERN int inhibit_delete_count INIT(= 0);
#define DBCS_DEBUG (-1)
/// Encoding used when 'fencs' is set to "default"
-EXTERN char *fenc_default INIT(= NULL);
+EXTERN char *fenc_default INIT( = NULL);
/// "State" is the main state of Vim.
/// There are other variables that modify the state:
@@ -626,69 +625,70 @@ EXTERN char *fenc_default INIT(= NULL);
/// before typing the motion command.
/// motion_force: Last motion_force from do_pending_operator()
/// debug_mode: Debug mode
-EXTERN int State INIT(= MODE_NORMAL);
+EXTERN int State INIT( = MODE_NORMAL);
-EXTERN bool debug_mode INIT(= false);
-EXTERN bool finish_op INIT(= false); // true while an operator is pending
-EXTERN long opcount INIT(= 0); // count for pending operator
-EXTERN int motion_force INIT(= 0); // motion force for pending operator
+EXTERN bool debug_mode INIT( = false);
+EXTERN bool finish_op INIT( = false); // true while an operator is pending
+EXTERN int opcount INIT( = 0); // count for pending operator
+EXTERN int motion_force INIT( = 0); // motion force for pending operator
// Ex Mode (Q) state
-EXTERN bool exmode_active INIT(= false); // true if Ex mode is active
+EXTERN bool exmode_active INIT( = false); // true if Ex mode is active
/// Flag set when normal_check() should return 0 when entering Ex mode.
-EXTERN bool pending_exmode_active INIT(= false);
+EXTERN bool pending_exmode_active INIT( = false);
-EXTERN bool ex_no_reprint INIT(= false); // No need to print after z or p.
+EXTERN bool ex_no_reprint INIT( = false); // No need to print after z or p.
// 'inccommand' command preview state
-EXTERN bool cmdpreview INIT(= false);
+EXTERN bool cmdpreview INIT( = false);
-EXTERN int reg_recording INIT(= 0); // register for recording or zero
-EXTERN int reg_executing INIT(= 0); // register being executed or zero
+EXTERN int reg_recording INIT( = 0); // register for recording or zero
+EXTERN int reg_executing INIT( = 0); // register being executed or zero
// Flag set when peeking a character and found the end of executed register
-EXTERN bool pending_end_reg_executing INIT(= false);
-EXTERN int reg_recorded INIT(= 0); // last recorded register or zero
-
-EXTERN int no_mapping INIT(= false); // currently no mapping allowed
-EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed
-EXTERN int allow_keys INIT(= false); // allow key codes when no_mapping is set
-EXTERN int no_u_sync INIT(= 0); // Don't call u_sync()
-EXTERN int u_sync_once INIT(= 0); // Call u_sync() once when evaluating
- // an expression.
-
-EXTERN bool force_restart_edit INIT(= false); // force restart_edit after
- // ex_normal returns
-EXTERN int restart_edit INIT(= 0); // call edit when next cmd finished
+EXTERN bool pending_end_reg_executing INIT( = false);
+EXTERN int reg_recorded INIT( = 0); // last recorded register or zero
+
+EXTERN int no_mapping INIT( = false); // currently no mapping allowed
+EXTERN int no_zero_mapping INIT( = 0); // mapping zero not allowed
+EXTERN int allow_keys INIT( = false); // allow key codes when no_mapping is set
+EXTERN int no_u_sync INIT( = 0); // Don't call u_sync()
+EXTERN int u_sync_once INIT( = 0); // Call u_sync() once when evaluating
+ // an expression.
+
+EXTERN bool force_restart_edit INIT( = false); // force restart_edit after
+ // ex_normal returns
+EXTERN int restart_edit INIT( = 0); // call edit when next cmd finished
EXTERN int arrow_used; // Normally false, set to true after
// hitting cursor key in insert mode.
// Used by vgetorpeek() to decide when
// to call u_sync()
-EXTERN bool ins_at_eol INIT(= false); // put cursor after eol when
- // restarting edit after CTRL-O
+EXTERN bool ins_at_eol INIT( = false); // put cursor after eol when
+ // restarting edit after CTRL-O
-EXTERN bool no_abbr INIT(= true); // true when no abbreviations loaded
+EXTERN bool no_abbr INIT( = true); // true when no abbreviations loaded
-EXTERN int mapped_ctrl_c INIT(= 0); // Modes where CTRL-C is mapped.
-EXTERN bool ctrl_c_interrupts INIT(= true); // CTRL-C sets got_int
+EXTERN int mapped_ctrl_c INIT( = 0); // Modes where CTRL-C is mapped.
+EXTERN bool ctrl_c_interrupts INIT( = true); // CTRL-C sets got_int
EXTERN cmdmod_T cmdmod; // Ex command modifiers
-EXTERN int msg_silent INIT(= 0); // don't print messages
-EXTERN int emsg_silent INIT(= 0); // don't print error messages
-EXTERN bool emsg_noredir INIT(= false); // don't redirect error messages
-EXTERN bool cmd_silent INIT(= false); // don't echo the command line
+EXTERN int msg_silent INIT( = 0); // don't print messages
+EXTERN int emsg_silent INIT( = 0); // don't print error messages
+EXTERN bool emsg_noredir INIT( = false); // don't redirect error messages
+EXTERN bool cmd_silent INIT( = false); // don't echo the command line
-EXTERN bool in_assert_fails INIT(= false); // assert_fails() active
+EXTERN bool in_assert_fails INIT( = false); // assert_fails() active
// Values for swap_exists_action: what to do when swap file already exists
#define SEA_NONE 0 // don't use dialog
#define SEA_DIALOG 1 // use dialog when possible
#define SEA_QUIT 2 // quit editing the file
#define SEA_RECOVER 3 // recover the file
+#define SEA_READONLY 4 // no dialog, mark buffer as read-only
-EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file already exists.
-EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog.
+EXTERN int swap_exists_action INIT( = SEA_NONE); ///< For dialog when swap file already exists.
+EXTERN bool swap_exists_did_quit INIT( = false); ///< Selected "quit" at the dialog.
EXTERN char IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc.
EXTERN char NameBuff[MAXPATHL]; ///< Buffer for expanding file names
@@ -702,338 +702,340 @@ EXTERN char os_buf[ ///< Buffer for the os/ layer
];
// When non-zero, postpone redrawing.
-EXTERN int RedrawingDisabled INIT(= 0);
+EXTERN int RedrawingDisabled INIT( = 0);
-EXTERN int readonlymode INIT(= false); // Set to true for "view"
-EXTERN int recoverymode INIT(= false); // Set to true for "-r" option
+EXTERN bool readonlymode INIT( = false); // Set to true for "view"
+EXTERN bool recoverymode INIT( = false); // Set to true for "-r" option
// typeahead buffer
-EXTERN typebuf_T typebuf INIT(= { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
+EXTERN typebuf_T typebuf INIT( = { NULL, NULL, 0, 0, 0, 0, 0, 0, 0 });
/// Flag used to indicate that vgetorpeek() returned a char like Esc when the
/// :normal argument was exhausted.
-EXTERN bool typebuf_was_empty INIT(= false);
+EXTERN bool typebuf_was_empty INIT( = false);
-EXTERN int ex_normal_busy INIT(= 0); // recursiveness of ex_normal()
-EXTERN int ex_normal_lock INIT(= 0); // forbid use of ex_normal()
-EXTERN int ignore_script INIT(= false); // ignore script input
-EXTERN int stop_insert_mode; // for ":stopinsert"
-EXTERN bool KeyTyped; // true if user typed current char
-EXTERN int KeyStuffed; // true if current char from stuffbuf
-EXTERN int maptick INIT(= 0); // tick for each non-mapped char
+EXTERN int ex_normal_busy INIT( = 0); // recursiveness of ex_normal()
+EXTERN int expr_map_lock INIT( = 0); // running expr mapping, prevent use of ex_normal() and text changes
+EXTERN bool ignore_script INIT( = false); // ignore script input
+EXTERN int stop_insert_mode; // for ":stopinsert"
+EXTERN bool KeyTyped; // true if user typed current char
+EXTERN int KeyStuffed; // true if current char from stuffbuf
+EXTERN int maptick INIT( = 0); // tick for each non-mapped char
-EXTERN int must_redraw INIT(= 0); // type of redraw necessary
-EXTERN bool skip_redraw INIT(= false); // skip redraw once
-EXTERN bool do_redraw INIT(= false); // extra redraw once
-EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw
- // should also be set.
+EXTERN int must_redraw INIT( = 0); // type of redraw necessary
+EXTERN bool skip_redraw INIT( = false); // skip redraw once
+EXTERN bool do_redraw INIT( = false); // extra redraw once
+EXTERN bool must_redraw_pum INIT( = false); // redraw pum. NB: must_redraw
+ // should also be set.
-EXTERN bool need_highlight_changed INIT(= true);
+EXTERN bool need_highlight_changed INIT( = true);
-EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to.
+EXTERN FILE *scriptout INIT( = NULL); ///< Stream to write script to.
// Note that even when handling SIGINT, volatile is not necessary because the
// callback is not called directly from the signal handlers.
-EXTERN bool got_int INIT(= false); // set to true when interrupt signal occurred
-EXTERN bool bangredo INIT(= false); // set to true with ! command
+EXTERN bool got_int INIT( = false); // set to true when interrupt signal occurred
+EXTERN bool bangredo INIT( = false); // set to true with ! command
EXTERN int searchcmdlen; // length of previous search cmd
-EXTERN int reg_do_extmatch INIT(= 0); // Used when compiling regexp:
- // REX_SET to allow \z\(...\),
- // REX_USE to allow \z\1 et al.
+EXTERN int reg_do_extmatch INIT( = 0); // Used when compiling regexp:
+ // REX_SET to allow \z\(...\),
+ // REX_USE to allow \z\1 et al.
// Used by vim_regexec(): strings for \z\1...\z\9
-EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL);
+EXTERN reg_extmatch_T *re_extmatch_in INIT( = NULL);
// Set by vim_regexec() to store \z\(...\) matches
-EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL);
+EXTERN reg_extmatch_T *re_extmatch_out INIT( = NULL);
-EXTERN bool did_outofmem_msg INIT(= false); ///< set after out of memory msg
-EXTERN bool did_swapwrite_msg INIT(= false); ///< set after swap write error msg
-EXTERN int global_busy INIT(= 0); ///< set when :global is executing
-EXTERN bool listcmd_busy INIT(= false); ///< set when :argdo, :windo or :bufdo is executing
-EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon
+EXTERN bool did_outofmem_msg INIT( = false); ///< set after out of memory msg
+EXTERN bool did_swapwrite_msg INIT( = false); ///< set after swap write error msg
+EXTERN int global_busy INIT( = 0); ///< set when :global is executing
+EXTERN bool listcmd_busy INIT( = false); ///< set when :argdo, :windo or :bufdo is executing
+EXTERN bool need_start_insertmode INIT( = false); ///< start insert mode soon
#define MODE_MAX_LENGTH 4 // max mode length returned in get_mode(),
// including the terminating NUL
-EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n");
-EXTERN char *last_cmdline INIT(= NULL); // last command line (for ":)
-EXTERN char *repeat_cmdline INIT(= NULL); // command line for "."
-EXTERN char *new_last_cmdline INIT(= NULL); // new value for last_cmdline
-EXTERN char *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline
-EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline
-EXTERN char *autocmd_match INIT(= NULL); // name for <amatch> on cmdline
-EXTERN bool did_cursorhold INIT(= false); // set when CursorHold t'gerd
-
-EXTERN int postponed_split INIT(= 0); // for CTRL-W CTRL-] command
-EXTERN int postponed_split_flags INIT(= 0); // args for win_split()
-EXTERN int postponed_split_tab INIT(= 0); // cmdmod.cmod_tab
-EXTERN int g_do_tagpreview INIT(= 0); // for tag preview commands:
- // height of preview window
-EXTERN bool g_tag_at_cursor INIT(= false); // whether the tag command comes
- // from the command line (0) or was
- // invoked as a normal command (1)
-
-EXTERN int replace_offset INIT(= 0); // offset for replace_push()
-
-EXTERN char *escape_chars INIT(= " \t\\\"|"); // need backslash in cmd line
-
-EXTERN int keep_help_flag INIT(= false); // doing :ta from help file
-
-// When a string option is NULL (which only happens in out-of-memory
-// situations), it is set to empty_option, to avoid having to check for NULL
-// everywhere.
-EXTERN char *empty_option INIT(= "");
-
-EXTERN bool redir_off INIT(= false); // no redirection for a moment
-EXTERN FILE *redir_fd INIT(= NULL); // message redirection file
-EXTERN int redir_reg INIT(= 0); // message redirection register
-EXTERN int redir_vname INIT(= 0); // message redirection variable
-EXTERN garray_T *capture_ga INIT(= NULL); // captured output for execute()
-
-EXTERN char_u langmap_mapchar[256]; // mapping for language keys
-
-EXTERN int save_p_ls INIT(= -1); // Save 'laststatus' setting
-EXTERN int save_p_wmh INIT(= -1); // Save 'winminheight' setting
-EXTERN int wild_menu_showing INIT(= 0);
+EXTERN char last_mode[MODE_MAX_LENGTH] INIT( = "n");
+EXTERN char *last_cmdline INIT( = NULL); // last command line (for ":)
+EXTERN char *repeat_cmdline INIT( = NULL); // command line for "."
+EXTERN char *new_last_cmdline INIT( = NULL); // new value for last_cmdline
+EXTERN char *autocmd_fname INIT( = NULL); // fname for <afile> on cmdline
+EXTERN bool autocmd_fname_full INIT( = false); // autocmd_fname is full path
+EXTERN int autocmd_bufnr INIT( = 0); // fnum for <abuf> on cmdline
+EXTERN char *autocmd_match INIT( = NULL); // name for <amatch> on cmdline
+EXTERN bool did_cursorhold INIT( = false); // set when CursorHold t'gerd
+
+EXTERN int postponed_split INIT( = 0); // for CTRL-W CTRL-] command
+EXTERN int postponed_split_flags INIT( = 0); // args for win_split()
+EXTERN int postponed_split_tab INIT( = 0); // cmdmod.cmod_tab
+EXTERN int g_do_tagpreview INIT( = 0); // for tag preview commands:
+ // height of preview window
+EXTERN bool g_tag_at_cursor INIT( = false); // whether the tag command comes
+ // from the command line (0) or was
+ // invoked as a normal command (1)
+
+EXTERN int replace_offset INIT( = 0); // offset for replace_push()
+
+EXTERN char *escape_chars INIT( = " \t\\\"|"); // need backslash in cmd line
+
+EXTERN bool keep_help_flag INIT( = false); // doing :ta from help file
+
+// When a string option is NULL (which only happens in out-of-memory situations), it is set to
+// empty_string_option, to avoid having to check for NULL everywhere.
+//
+// TODO(famiu): Remove this when refcounted strings are used for string options.
+EXTERN char *empty_string_option INIT( = "");
+
+EXTERN bool redir_off INIT( = false); // no redirection for a moment
+EXTERN FILE *redir_fd INIT( = NULL); // message redirection file
+EXTERN int redir_reg INIT( = 0); // message redirection register
+EXTERN int redir_vname INIT( = 0); // message redirection variable
+EXTERN garray_T *capture_ga INIT( = NULL); // captured output for execute()
+
+EXTERN uint8_t langmap_mapchar[256]; // mapping for language keys
+
+EXTERN int save_p_ls INIT( = -1); // Save 'laststatus' setting
+EXTERN int save_p_wmh INIT( = -1); // Save 'winminheight' setting
+EXTERN int wild_menu_showing INIT( = 0);
enum {
WM_SHOWN = 1, ///< wildmenu showing
WM_SCROLLED = 2, ///< wildmenu showing with scroll
WM_LIST = 3, ///< cmdline CTRL-D
};
-// Some file names are stored in pathdef.c, which is generated from the
-// Makefile to make their value depend on the Makefile.
-#ifdef HAVE_PATHDEF
-extern char *default_vim_dir;
-extern char *default_vimruntime_dir;
-extern char *default_lib_dir;
-extern char *compiled_user;
-extern char *compiled_sys;
-#endif
-
// When a window has a local directory, the absolute path of the global
// current directory is stored here (in allocated memory). If the current
// directory is not a local directory, globaldir is NULL.
-EXTERN char *globaldir INIT(= NULL);
+EXTERN char *globaldir INIT( = NULL);
-EXTERN char *last_chdir_reason INIT(= NULL);
+EXTERN char *last_chdir_reason INIT( = NULL);
// Whether 'keymodel' contains "stopsel" and "startsel".
-EXTERN bool km_stopsel INIT(= false);
-EXTERN bool km_startsel INIT(= false);
+EXTERN bool km_stopsel INIT( = false);
+EXTERN bool km_startsel INIT( = false);
-EXTERN int cmdwin_type INIT(= 0); ///< type of cmdline window or 0
-EXTERN int cmdwin_result INIT(= 0); ///< result of cmdline window or 0
-EXTERN int cmdwin_level INIT(= 0); ///< cmdline recursion level
+EXTERN int cmdwin_type INIT( = 0); ///< type of cmdline window or 0
+EXTERN int cmdwin_result INIT( = 0); ///< result of cmdline window or 0
+EXTERN int cmdwin_level INIT( = 0); ///< cmdline recursion level
+EXTERN win_T *cmdwin_old_curwin INIT( = NULL); ///< curwin before opening cmdline window or NULL
-EXTERN char no_lines_msg[] INIT(= N_("--No lines in buffer--"));
+EXTERN char no_lines_msg[] INIT( = N_("--No lines in buffer--"));
// When ":global" is used to number of substitutions and changed lines is
// accumulated until it's finished.
// Also used for ":spellrepall".
-EXTERN long sub_nsubs; // total number of substitutions
+EXTERN int sub_nsubs; // total number of substitutions
EXTERN linenr_T sub_nlines; // total number of lines changed
// table to store parsed 'wildmode'
-EXTERN char_u wim_flags[4];
+EXTERN uint8_t wim_flags[4];
// whether titlestring and iconstring contains statusline syntax
#define STL_IN_ICON 1
#define STL_IN_TITLE 2
-EXTERN int stl_syntax INIT(= 0);
+EXTERN int stl_syntax INIT( = 0);
// don't use 'hlsearch' temporarily
-EXTERN bool no_hlsearch INIT(= false);
+EXTERN bool no_hlsearch INIT( = false);
-EXTERN bool typebuf_was_filled INIT(= false); // received text from client
- // or from feedkeys()
+EXTERN bool typebuf_was_filled INIT( = false); // received text from client
+ // or from feedkeys()
#ifdef BACKSLASH_IN_FILENAME
-EXTERN char psepc INIT(= '\\'); // normal path separator character
-EXTERN char psepcN INIT(= '/'); // abnormal path separator character
-EXTERN char pseps[2] INIT(= { '\\', 0 }); // normal path separator string
+EXTERN char psepc INIT( = '\\'); // normal path separator character
+EXTERN char psepcN INIT( = '/'); // abnormal path separator character
+EXTERN char pseps[2] INIT( = { '\\', 0 }); // normal path separator string
#endif
// Set to kTrue when an operator is being executed with virtual editing
// kNone when no operator is being executed, kFalse otherwise.
-EXTERN TriState virtual_op INIT(= kNone);
+EXTERN TriState virtual_op INIT( = kNone);
// Display tick, incremented for each call to update_screen()
-EXTERN disptick_T display_tick INIT(= 0);
+EXTERN disptick_T display_tick INIT( = 0);
// Line in which spell checking wasn't highlighted because it touched the
// cursor position in Insert mode.
-EXTERN linenr_T spell_redraw_lnum INIT(= 0);
+EXTERN linenr_T spell_redraw_lnum INIT( = 0);
// uncrustify:off
// The error messages that can be shared are included here.
// Excluded are errors that are only used once and debugging messages.
-EXTERN char e_abort[] INIT(= N_("E470: Command aborted"));
-EXTERN char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
-EXTERN char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
-EXTERN char e_argreq[] INIT(= N_("E471: Argument required"));
-EXTERN char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
-EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
-EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
-EXTERN char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
-EXTERN char e_endif[] INIT(= N_("E171: Missing :endif"));
-EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry"));
-EXTERN char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
-EXTERN char e_endfor[] INIT(= N_("E170: Missing :endfor"));
-EXTERN char e_while[] INIT(= N_("E588: :endwhile without :while"));
-EXTERN char e_for[] INIT(= N_("E588: :endfor without :for"));
-EXTERN char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
-EXTERN char e_failed[] INIT(= N_("E472: Command failed"));
-EXTERN char e_internal[] INIT(= N_("E473: Internal error"));
-EXTERN char e_intern2[] INIT(= N_("E685: Internal error: %s"));
-EXTERN char e_interr[] INIT(= N_("Interrupted"));
-EXTERN char e_invarg[] INIT(= N_("E474: Invalid argument"));
-EXTERN char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
-EXTERN char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
-EXTERN char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
-EXTERN char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
-EXTERN char e_invexpr2[] INIT(= N_("E15: Invalid expression: %s"));
-EXTERN char e_invrange[] INIT(= N_("E16: Invalid range"));
-EXTERN char e_invcmd[] INIT(= N_("E476: Invalid command"));
-EXTERN char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
-EXTERN char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
-EXTERN char e_invchan[] INIT(= N_("E900: Invalid channel id"));
-EXTERN char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
-EXTERN char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
-EXTERN char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
-EXTERN char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
-EXTERN char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
-EXTERN char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
-EXTERN char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
-EXTERN char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
-EXTERN char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
-EXTERN char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
-EXTERN char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
-EXTERN char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
-EXTERN char e_marknotset[] INIT(= N_("E20: Mark not set"));
-EXTERN char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
-EXTERN char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
-EXTERN char e_noalt[] INIT(= N_("E23: No alternate file"));
-EXTERN char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
-EXTERN char e_nobang[] INIT(= N_("E477: No ! allowed"));
-EXTERN char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
-EXTERN char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
-EXTERN char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
-EXTERN char e_nomap[] INIT(= N_("E31: No such mapping"));
-EXTERN char e_nomatch[] INIT(= N_("E479: No match"));
-EXTERN char e_nomatch2[] INIT(= N_("E480: No match: %s"));
-EXTERN char e_noname[] INIT(= N_("E32: No file name"));
-EXTERN char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
-EXTERN char e_noprev[] INIT(= N_("E34: No previous command"));
-EXTERN char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
-EXTERN char e_norange[] INIT(= N_("E481: No range allowed"));
-EXTERN char e_noroom[] INIT(= N_("E36: Not enough room"));
-EXTERN char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
-EXTERN char e_notopen[] INIT(= N_("E484: Can't open file %s"));
-EXTERN char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
-EXTERN char e_notread[] INIT(= N_("E485: Can't read file %s"));
-EXTERN char e_null[] INIT(= N_("E38: Null argument"));
-EXTERN char e_number_exp[] INIT(= N_("E39: Number expected"));
-EXTERN char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
-EXTERN char e_outofmem[] INIT(= N_("E41: Out of memory!"));
-EXTERN char e_patnotf[] INIT(= N_("Pattern not found"));
-EXTERN char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
-EXTERN char e_positive[] INIT(= N_("E487: Argument must be positive"));
-EXTERN char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
-
-EXTERN char e_no_errors[] INIT(= N_("E42: No Errors"));
-EXTERN char e_loclist[] INIT(= N_("E776: No location list"));
-EXTERN char e_re_damg[] INIT(= N_("E43: Damaged match string"));
-EXTERN char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
-EXTERN char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
-EXTERN char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
-EXTERN char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
-EXTERN char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
-EXTERN char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
-EXTERN char e_stringreq[] INIT(= N_("E928: String required"));
-EXTERN char e_dictreq[] INIT(= N_("E715: Dictionary required"));
-EXTERN char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
-EXTERN char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
-EXTERN char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
-EXTERN char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
-EXTERN char e_listreq[] INIT(= N_("E714: List required"));
-EXTERN char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
-EXTERN char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
-EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
-EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
-EXTERN char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
-EXTERN char e_secure[] INIT(= N_("E523: Not allowed here"));
-EXTERN char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
-EXTERN char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
-EXTERN char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
-EXTERN char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
-EXTERN char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
-EXTERN char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
-EXTERN char e_tagstack[] INIT(= N_("E73: tag stack empty"));
-EXTERN char e_toocompl[] INIT(= N_("E74: Command too complex"));
-EXTERN char e_longname[] INIT(= N_("E75: Name too long"));
-EXTERN char e_toomsbra[] INIT(= N_("E76: Too many ["));
-EXTERN char e_toomany[] INIT(= N_("E77: Too many file names"));
-EXTERN char e_trailing[] INIT(= N_("E488: Trailing characters"));
-EXTERN char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
-EXTERN char e_umark[] INIT(= N_("E78: Unknown mark"));
-EXTERN char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
-EXTERN char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
-EXTERN char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
-EXTERN char e_write[] INIT(= N_("E80: Error while writing"));
-EXTERN char e_zerocount[] INIT(= N_("E939: Positive count required"));
-EXTERN char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
-EXTERN char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
-EXTERN char e_maxmempat[] INIT(= N_("E363: pattern uses more memory than 'maxmempattern'"));
-EXTERN char e_emptybuf[] INIT(= N_("E749: empty buffer"));
-EXTERN char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
-
-EXTERN char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
-EXTERN char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
-EXTERN char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
-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_menuothermode[] INIT(= N_("E328: Menu only exists in another mode"));
-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"));
-EXTERN char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
-
-EXTERN char e_cmdmap_err[] INIT(= N_("E5520: <Cmd> mapping must end with <CR>"));
-EXTERN char e_cmdmap_repeated[]
-INIT(= N_("E5521: <Cmd> mapping must end with <CR> before second <Cmd>"));
-
-EXTERN char e_api_error[] INIT(= N_("E5555: API call: %s"));
-
-EXTERN char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
-
-EXTERN char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
-EXTERN char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
-
-EXTERN char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
-
-EXTERN char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
-
-EXTERN char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
-
-EXTERN char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
-
-EXTERN char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
-
-EXTERN char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
-
-EXTERN char e_undobang_cannot_redo_or_move_branch[]
+EXTERN const char e_abort[] INIT(= N_("E470: Command aborted"));
+EXTERN const char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
+EXTERN const char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
+EXTERN const char e_argreq[] INIT(= N_("E471: Argument required"));
+EXTERN const char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
+EXTERN const char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
+EXTERN const char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
+EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
+EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
+EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
+EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
+EXTERN const char e_endfor[] INIT(= N_("E170: Missing :endfor"));
+EXTERN const char e_while[] INIT(= N_("E588: :endwhile without :while"));
+EXTERN const char e_for[] INIT(= N_("E588: :endfor without :for"));
+EXTERN const char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
+EXTERN const char e_failed[] INIT(= N_("E472: Command failed"));
+EXTERN const char e_internal[] INIT(= N_("E473: Internal error"));
+EXTERN const char e_intern2[] INIT(= N_("E685: Internal error: %s"));
+EXTERN const char e_interr[] INIT(= N_("Interrupted"));
+EXTERN const char e_invarg[] INIT(= N_("E474: Invalid argument"));
+EXTERN const char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
+EXTERN const char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
+EXTERN const char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
+EXTERN const char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
+EXTERN const char e_invexpr2[] INIT(= N_("E15: Invalid expression: \"%s\""));
+EXTERN const char e_invrange[] INIT(= N_("E16: Invalid range"));
+EXTERN const char e_invcmd[] INIT(= N_("E476: Invalid command"));
+EXTERN const char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
+EXTERN const char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
+EXTERN const char e_invchan[] INIT(= N_("E900: Invalid channel id"));
+EXTERN const char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
+EXTERN const char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
+EXTERN const char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
+EXTERN const char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
+EXTERN const char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
+EXTERN const char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
+EXTERN const char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
+EXTERN const char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
+EXTERN const char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
+EXTERN const char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
+EXTERN const char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
+EXTERN const char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
+EXTERN const char e_marknotset[] INIT(= N_("E20: Mark not set"));
+EXTERN const char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
+EXTERN const char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
+EXTERN const char e_noalt[] INIT(= N_("E23: No alternate file"));
+EXTERN const char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
+EXTERN const char e_nobang[] INIT(= N_("E477: No ! allowed"));
+EXTERN const char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
+EXTERN const char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
+EXTERN const char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
+EXTERN const char e_nomap[] INIT(= N_("E31: No such mapping"));
+EXTERN const char e_nomatch[] INIT(= N_("E479: No match"));
+EXTERN const char e_nomatch2[] INIT(= N_("E480: No match: %s"));
+EXTERN const char e_noname[] INIT(= N_("E32: No file name"));
+EXTERN const char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
+EXTERN const char e_noprev[] INIT(= N_("E34: No previous command"));
+EXTERN const char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
+EXTERN const char e_norange[] INIT(= N_("E481: No range allowed"));
+EXTERN const char e_noroom[] INIT(= N_("E36: Not enough room"));
+EXTERN const char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
+EXTERN const char e_notopen[] INIT(= N_("E484: Can't open file %s"));
+EXTERN const char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
+EXTERN const char e_notread[] INIT(= N_("E485: Can't read file %s"));
+EXTERN const char e_null[] INIT(= N_("E38: Null argument"));
+EXTERN const char e_number_exp[] INIT(= N_("E39: Number expected"));
+EXTERN const char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
+EXTERN const char e_outofmem[] INIT(= N_("E41: Out of memory!"));
+EXTERN const char e_patnotf[] INIT(= N_("Pattern not found"));
+EXTERN const char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
+EXTERN const char e_positive[] INIT(= N_("E487: Argument must be positive"));
+EXTERN const char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
+
+EXTERN const char e_no_errors[] INIT(= N_("E42: No Errors"));
+EXTERN const char e_loclist[] INIT(= N_("E776: No location list"));
+EXTERN const char e_re_damg[] INIT(= N_("E43: Damaged match string"));
+EXTERN const char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
+EXTERN const char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
+EXTERN const char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
+EXTERN const char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
+EXTERN const char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
+EXTERN const char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
+EXTERN const char e_stringreq[] INIT(= N_("E928: String required"));
+EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
+EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
+EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s"));
+EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
+EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
+EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
+EXTERN const char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
+EXTERN const char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
+EXTERN const char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
+EXTERN const char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
+EXTERN const char e_secure[] INIT(= N_("E523: Not allowed here"));
+EXTERN const char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
+EXTERN const char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
+EXTERN const char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
+EXTERN const char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
+EXTERN const char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
+EXTERN const char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
+EXTERN const char e_toocompl[] INIT(= N_("E74: Command too complex"));
+EXTERN const char e_longname[] INIT(= N_("E75: Name too long"));
+EXTERN const char e_toomsbra[] INIT(= N_("E76: Too many ["));
+EXTERN const char e_toomany[] INIT(= N_("E77: Too many file names"));
+EXTERN const char e_trailing[] INIT(= N_("E488: Trailing characters"));
+EXTERN const char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
+EXTERN const char e_umark[] INIT(= N_("E78: Unknown mark"));
+EXTERN const char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
+EXTERN const char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
+EXTERN const char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
+EXTERN const char e_write[] INIT(= N_("E80: Error while writing"));
+EXTERN const char e_zerocount[] INIT(= N_("E939: Positive count required"));
+EXTERN const char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
+EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
+EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer"));
+EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
+
+EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
+
+EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
+EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
+EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
+EXTERN const char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
+EXTERN const char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
+EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
+EXTERN const char e_menu_only_exists_in_another_mode[]
+INIT(= N_("E328: Menu only exists in another mode"));
+EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
+EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
+EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
+EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a String"));
+EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
+EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
+EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
+
+EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
+
+EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
+
+EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
+EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
+
+EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
+
+EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
+
+EXTERN const char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
+
+EXTERN const char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
+
+EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
+
+EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
+
+EXTERN const char e_stray_closing_curly_str[]
+INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
+EXTERN const char e_missing_close_curly_str[]
+INIT(= N_("E1279: Missing '}': %s"));
+
+EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
+
+EXTERN const char e_undobang_cannot_redo_or_move_branch[]
INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
-EXTERN char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+
+EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
-EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
-EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
+EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
+EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
-EXTERN char line_msg[] INIT(= N_(" line "));
+EXTERN const char line_msg[] INIT(= N_(" line "));
EXTERN FILE *time_fd INIT(= NULL); // where to write startup timing
@@ -1082,17 +1084,15 @@ typedef enum {
} CdCause;
// Only filled for Win32.
-EXTERN char windowsVersion[20] INIT(= { 0 });
+EXTERN char windowsVersion[20] INIT( = { 0 });
/// While executing a regexp and set to OPTION_MAGIC_ON or OPTION_MAGIC_OFF this
/// overrules p_magic. Otherwise set to OPTION_MAGIC_NOT_SET.
-EXTERN optmagic_T magic_overruled INIT(= OPTION_MAGIC_NOT_SET);
+EXTERN optmagic_T magic_overruled INIT( = OPTION_MAGIC_NOT_SET);
/// Skip win_fix_cursor() call for 'splitkeep' when cmdwin is closed.
-EXTERN bool skip_win_fix_cursor INIT(= false);
+EXTERN bool skip_win_fix_cursor INIT( = false);
/// Skip win_fix_scroll() call for 'splitkeep' when closing tab page.
-EXTERN bool skip_win_fix_scroll INIT(= false);
+EXTERN bool skip_win_fix_scroll INIT( = false);
/// Skip update_topline() call while executing win_fix_scroll().
-EXTERN bool skip_update_topline INIT(= false);
-
-#endif // NVIM_GLOBALS_H
+EXTERN bool skip_update_topline INIT( = false);
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index 46f8a59710..2ef89b778e 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Most of the routines in this file perform screen (grid) manipulations. The
// given operation is performed physically on the screen. The corresponding
// change is also made to the internal screen image. In this way, the editor
@@ -13,19 +10,25 @@
#include <assert.h>
#include <limits.h>
+#include <stdint.h>
#include <stdlib.h>
+#include <string.h>
+#include "nvim/api/private/defs.h"
#include "nvim/arabic.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/types.h"
+#include "nvim/option_vars.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "grid.c.generated.h"
@@ -36,6 +39,15 @@
// Per-cell attributes
static size_t linebuf_size = 0;
+// Used to cache glyphs which doesn't fit an a sizeof(schar_T) length UTF-8 string.
+// Then it instead stores an index into glyph_cache.keys[] which is a flat char array.
+// The hash part is used by schar_from_buf() to quickly lookup glyphs which already
+// has been interned. schar_get() should used to convert a schar_T value
+// back to a string buffer.
+//
+// The maximum byte size of a glyph is MAX_SCHAR_SIZE (including the final NUL).
+static Set(glyph) glyph_cache = SET_INIT;
+
/// Determine if dedicated window grid should be used or the default_grid
///
/// If UI did not request multigrid support, draw all windows on the
@@ -55,320 +67,469 @@ void grid_adjust(ScreenGrid **grid, int *row_off, int *col_off)
}
}
-/// Put a unicode char, and up to MAX_MCO composing chars, in a screen cell.
-int schar_from_cc(char *p, int c, int u8cc[MAX_MCO])
+schar_T schar_from_str(char *str)
{
- int len = utf_char2bytes(c, p);
- for (int i = 0; i < MAX_MCO; i++) {
- if (u8cc[i] == 0) {
- break;
- }
- len += utf_char2bytes(u8cc[i], p + len);
+ if (str == NULL) {
+ return 0;
}
- p[len] = 0;
- return len;
+ return schar_from_buf(str, strlen(str));
}
-/// clear a line in the grid starting at "off" until "width" characters
-/// are cleared.
-void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
+/// @param buf need not be NUL terminated, but may not contain embedded NULs.
+///
+/// caller must ensure len < MAX_SCHAR_SIZE (not =, as NUL needs a byte)
+schar_T schar_from_buf(const char *buf, size_t len)
{
- for (int col = 0; col < width; col++) {
- schar_from_ascii(grid->chars[off + (size_t)col], ' ');
+ assert(len < MAX_SCHAR_SIZE);
+ if (len <= 4) {
+ schar_T sc = 0;
+ memcpy((char *)&sc, buf, len);
+ return sc;
+ } else {
+ String str = { .data = (char *)buf, .size = len };
+
+ MHPutStatus status;
+ uint32_t idx = set_put_idx(glyph, &glyph_cache, str, &status);
+ assert(idx < 0xFFFFFF);
+#ifdef ORDER_BIG_ENDIAN
+ return idx + ((uint32_t)0xFF << 24);
+#else
+ return 0xFF + (idx << 8);
+#endif
}
- int fill = valid ? 0 : -1;
- (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
}
-void grid_invalidate(ScreenGrid *grid)
+/// Check if cache is full, and if it is, clear it.
+///
+/// This should normally only be called in update_screen()
+///
+/// @return true if cache was clered, and all your screen buffers now are hosed
+/// and you need to use UPD_CLEAR
+bool schar_cache_clear_if_full(void)
{
- (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
+ // note: critical max is really (1<<24)-1. This gives us some marginal
+ // until next time update_screen() is called
+ if (glyph_cache.h.n_keys > (1<<21)) {
+ schar_cache_clear();
+ return true;
+ }
+ return false;
}
-bool grid_invalid_row(ScreenGrid *grid, int row)
+void schar_cache_clear(void)
{
- return grid->attrs[grid->line_offset[row]] < 0;
+ decor_check_invalid_glyphs();
+ set_clear(glyph, &glyph_cache);
}
-static int line_off2cells(schar_T *line, size_t off, size_t max_off)
+bool schar_high(schar_T sc)
{
- return (off + 1 < max_off && line[off + 1][0] == 0) ? 2 : 1;
+#ifdef ORDER_BIG_ENDIAN
+ return ((sc & 0xFF000000) == 0xFF000000);
+#else
+ return ((sc & 0xFF) == 0xFF);
+#endif
}
-/// Return number of display cells for char at grid->chars[off].
-/// We make sure that the offset used is less than "max_off".
-static int grid_off2cells(ScreenGrid *grid, size_t off, size_t max_off)
+#ifdef ORDER_BIG_ENDIAN
+# define schar_idx(sc) (sc & (0x00FFFFFF))
+#else
+# define schar_idx(sc) (sc >> 8)
+#endif
+
+void schar_get(char *buf_out, schar_T sc)
{
- return line_off2cells(grid->chars, off, max_off);
+ if (schar_high(sc)) {
+ uint32_t idx = schar_idx(sc);
+ assert(idx < glyph_cache.h.n_keys);
+ xstrlcpy(buf_out, &glyph_cache.keys[idx], 32);
+ } else {
+ memcpy(buf_out, (char *)&sc, 4);
+ buf_out[4] = NUL;
+ }
}
-/// Return true if the character at "row"/"col" on the screen is the left side
-/// of a double-width character.
-///
-/// Caller must make sure "row" and "col" are not invalid!
-bool grid_lefthalve(ScreenGrid *grid, int row, int col)
+/// gets first raw UTF-8 byte of an schar
+static char schar_get_first_byte(schar_T sc)
{
- grid_adjust(&grid, &row, &col);
+ assert(!(schar_high(sc) && schar_idx(sc) >= glyph_cache.h.n_keys));
+ return schar_high(sc) ? glyph_cache.keys[schar_idx(sc)] : *(char *)&sc;
+}
- return grid_off2cells(grid, grid->line_offset[row] + (size_t)col,
- grid->line_offset[row] + (size_t)grid->cols) > 1;
+int schar_get_first_codepoint(schar_T sc)
+{
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, sc);
+ return utf_ptr2char(sc_buf);
}
-/// Correct a position on the screen, if it's the right half of a double-wide
-/// char move it to the left half. Returns the corrected column.
-int grid_fix_col(ScreenGrid *grid, int col, int row)
+/// @return ascii char or NUL if not ascii
+char schar_get_ascii(schar_T sc)
{
- int coloff = 0;
- grid_adjust(&grid, &row, &coloff);
+#ifdef ORDER_BIG_ENDIAN
+ return (!(sc & 0x80FFFFFF)) ? *(char *)&sc : NUL;
+#else
+ return (sc < 0x80) ? (char)sc : NUL;
+#endif
+}
- col += coloff;
- if (grid->chars != NULL && col > 0
- && grid->chars[grid->line_offset[row] + (size_t)col][0] == 0) {
- return col - 1 - coloff;
- }
- return col - coloff;
+static bool schar_in_arabic_block(schar_T sc)
+{
+ char first_byte = schar_get_first_byte(sc);
+ return ((uint8_t)first_byte & 0xFE) == 0xD8;
}
-/// output a single character directly to the grid
-void grid_putchar(ScreenGrid *grid, int c, int row, int col, int attr)
+/// Get the first two codepoints of an schar, or NUL when not available
+static void schar_get_first_two_codepoints(schar_T sc, int *c0, int *c1)
{
- char buf[MB_MAXBYTES + 1];
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, sc);
- buf[utf_char2bytes(c, buf)] = NUL;
- grid_puts(grid, buf, row, col, attr);
+ *c0 = utf_ptr2char(sc_buf);
+ int len = utf_ptr2len(sc_buf);
+ if (*c0 == NUL) {
+ *c1 = NUL;
+ } else {
+ *c1 = utf_ptr2char(sc_buf + len);
+ }
}
-/// get a single character directly from grid.chars into "bytes[]".
-/// Also return its attribute in *attrp;
-void grid_getbytes(ScreenGrid *grid, int row, int col, char *bytes, int *attrp)
+void line_do_arabic_shape(schar_T *buf, int cols)
{
- grid_adjust(&grid, &row, &col);
+ int i = 0;
- // safety check
- if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) {
+ for (i = 0; i < cols; i++) {
+ // quickly skip over non-arabic text
+ if (schar_in_arabic_block(buf[i])) {
+ break;
+ }
+ }
+
+ if (i == cols) {
return;
}
- size_t off = grid->line_offset[row] + (size_t)col;
- *attrp = grid->attrs[off];
- schar_copy(bytes, grid->chars[off]);
+ int c0prev = 0;
+ int c0, c1;
+ schar_get_first_two_codepoints(buf[i], &c0, &c1);
+
+ for (; i < cols; i++) {
+ int c0next, c1next;
+ schar_get_first_two_codepoints(i + 1 < cols ? buf[i + 1] : 0, &c0next, &c1next);
+
+ if (!ARABIC_CHAR(c0)) {
+ goto next;
+ }
+
+ int c1new = c1;
+ int c0new = arabic_shape(c0, &c1new, c0next, c1next, c0prev);
+
+ if (c0new == c0 && c1new == c1) {
+ goto next; // unchanged
+ }
+
+ char scbuf[MAX_SCHAR_SIZE];
+ schar_get(scbuf, buf[i]);
+
+ char scbuf_new[MAX_SCHAR_SIZE];
+ size_t len = (size_t)utf_char2bytes(c0new, scbuf_new);
+ if (c1new) {
+ len += (size_t)utf_char2bytes(c1new, scbuf_new + len);
+ }
+
+ int off = utf_char2len(c0) + (c1 ? utf_char2len(c1) : 0);
+ size_t rest = strlen(scbuf + off);
+ if (rest + len + 1 > MAX_SCHAR_SIZE) {
+ // Too bigly, discard one code-point.
+ // This should be enough as c0 cannot grow more than from 2 to 4 bytes
+ // (base arabic to extended arabic)
+ rest -= (size_t)utf_cp_head_off(scbuf + off, scbuf + off + rest - 1) + 1;
+ }
+ memcpy(scbuf_new + len, scbuf + off, rest);
+ buf[i] = schar_from_buf(scbuf_new, len + rest);
+
+next:
+ c0prev = c0;
+ c0 = c0next;
+ c1 = c1next;
+ }
}
-/// put string '*text' on the window grid at position 'row' and 'col', with
-/// attributes 'attr', and update chars[] and attrs[].
-/// Note: only outputs within one row, message is truncated at grid boundary!
-/// Note: if grid, row and/or col is invalid, nothing is done.
-void grid_puts(ScreenGrid *grid, char *text, int row, int col, int attr)
+/// clear a line in the grid starting at "off" until "width" characters
+/// are cleared.
+void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid)
{
- grid_puts_len(grid, text, -1, row, col, attr);
+ for (int col = 0; col < width; col++) {
+ grid->chars[off + (size_t)col] = schar_from_ascii(' ');
+ }
+ int fill = valid ? 0 : -1;
+ (void)memset(grid->attrs + off, fill, (size_t)width * sizeof(sattr_T));
+ (void)memset(grid->vcols + off, -1, (size_t)width * sizeof(colnr_T));
}
-static ScreenGrid *put_dirty_grid = NULL;
-static int put_dirty_row = -1;
-static int put_dirty_first = INT_MAX;
-static int put_dirty_last = 0;
+void grid_invalidate(ScreenGrid *grid)
+{
+ (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->rows * (size_t)grid->cols);
+}
-/// Start a group of grid_puts_len calls that builds a single grid line.
-///
-/// Must be matched with a grid_puts_line_flush call before moving to
-/// another line.
-void grid_puts_line_start(ScreenGrid *grid, int row)
+static bool grid_invalid_row(ScreenGrid *grid, int row)
{
- int col = 0; // unused
- grid_adjust(&grid, &row, &col);
- assert(put_dirty_row == -1);
- put_dirty_row = row;
- put_dirty_grid = grid;
+ return grid->attrs[grid->line_offset[row]] < 0;
}
-void grid_put_schar(ScreenGrid *grid, int row, int col, char *schar, int attr)
+/// Get a single character directly from grid.chars
+///
+/// @param[out] attrp set to the character's attribute (optional)
+schar_T grid_getchar(ScreenGrid *grid, int row, int col, int *attrp)
{
- assert(put_dirty_row == row);
- size_t off = grid->line_offset[row] + (size_t)col;
- if (grid->attrs[off] != attr || schar_cmp(grid->chars[off], schar) || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], schar);
- grid->attrs[off] = attr;
+ grid_adjust(&grid, &row, &col);
+
+ // safety check
+ if (grid->chars == NULL || row >= grid->rows || col >= grid->cols) {
+ return NUL;
+ }
- put_dirty_first = MIN(put_dirty_first, col);
- // TODO(bfredl): Y U NO DOUBLEWIDTH?
- put_dirty_last = MAX(put_dirty_last, col + 1);
+ size_t off = grid->line_offset[row] + (size_t)col;
+ if (attrp != NULL) {
+ *attrp = grid->attrs[off];
}
+ return grid->chars[off];
}
-/// like grid_puts(), but output "text[len]". When "len" is -1 output up to
-/// a NUL.
-void grid_puts_len(ScreenGrid *grid, char *text, int textlen, int row, int col, int attr)
-{
- size_t off;
- char *ptr = text;
- int len = textlen;
- int c;
- size_t max_off;
- int mbyte_blen = 1;
- int mbyte_cells = 1;
- int u8c = 0;
- int u8cc[MAX_MCO];
- bool clear_next_cell = false;
- int prev_c = 0; // previous Arabic character
- int pc, nc, nc1;
- int pcc[MAX_MCO];
- int need_redraw;
- bool do_flush = false;
+static ScreenGrid *grid_line_grid = NULL;
+static int grid_line_row = -1;
+static int grid_line_coloff = 0;
+static int grid_line_maxcol = 0;
+static int grid_line_first = INT_MAX;
+static int grid_line_last = 0;
+/// Start a group of grid_line_puts calls that builds a single grid line.
+///
+/// Must be matched with a grid_line_flush call before moving to
+/// another line.
+void grid_line_start(ScreenGrid *grid, int row)
+{
+ int col = 0;
grid_adjust(&grid, &row, &col);
-
- // Safety check. The check for negative row and column is to fix issue
- // vim/vim#4102. TODO(neovim): find out why row/col could be negative.
- if (grid->chars == NULL
- || row >= grid->rows || row < 0
- || col >= grid->cols || col < 0) {
- return;
+ assert(grid_line_grid == NULL);
+ grid_line_row = row;
+ grid_line_grid = grid;
+ grid_line_coloff = col;
+ grid_line_first = (int)linebuf_size;
+ grid_line_maxcol = grid->cols - grid_line_coloff;
+ grid_line_last = 0;
+
+ assert((size_t)grid_line_maxcol <= linebuf_size);
+
+ if (rdb_flags & RDB_INVALID) {
+ // Current batch must not depend on previous contents of linebuf_char.
+ // Set invalid values which will cause assertion failures later if they are used.
+ memset(linebuf_char, 0xFF, sizeof(schar_T) * linebuf_size);
+ memset(linebuf_attr, 0xFF, sizeof(sattr_T) * linebuf_size);
}
+}
- if (put_dirty_row == -1) {
- grid_puts_line_start(grid, row);
- do_flush = true;
- } else {
- if (grid != put_dirty_grid || row != put_dirty_row) {
- abort();
+/// Get present char from current rendered screen line
+///
+/// This indicates what already is on screen, not the pending render buffer.
+///
+/// @return char or space if out of bounds
+schar_T grid_line_getchar(int col, int *attr)
+{
+ if (col < grid_line_maxcol) {
+ col += grid_line_coloff;
+ size_t off = grid_line_grid->line_offset[grid_line_row] + (size_t)col;
+ if (attr != NULL) {
+ *attr = grid_line_grid->attrs[off];
}
+ return grid_line_grid->chars[off];
+ } else {
+ // NUL is a very special value (right-half of double width), space is True Neutral™
+ return schar_from_ascii(' ');
}
- off = grid->line_offset[row] + (size_t)col;
+}
- // When drawing over the right half of a double-wide char clear out the
- // left half. Only needed in a terminal.
- if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) {
- // redraw the previous cell, make it empty
- put_dirty_first = -1;
- put_dirty_last = MAX(put_dirty_last, 1);
+void grid_line_put_schar(int col, schar_T schar, int attr)
+{
+ assert(grid_line_grid);
+ if (col >= grid_line_maxcol) {
+ return;
}
- max_off = grid->line_offset[row] + (size_t)grid->cols;
- while (col < grid->cols
- && (len < 0 || (int)(ptr - text) < len)
- && *ptr != NUL) {
- c = (unsigned char)(*ptr);
+ linebuf_char[col] = schar;
+ linebuf_attr[col] = attr;
+
+ grid_line_first = MIN(grid_line_first, col);
+ // TODO(bfredl): Y U NO DOUBLEWIDTH?
+ grid_line_last = MAX(grid_line_last, col + 1);
+ linebuf_vcol[col] = -1;
+}
+
+/// Put string "text" at "col" position relative to the grid line from the
+/// recent grid_line_start() call.
+///
+/// @param textlen length of string or -1 to use strlen(text)
+/// Note: only outputs within one row!
+///
+/// @return number of grid cells used
+int grid_line_puts(int col, const char *text, int textlen, int attr)
+{
+ const char *ptr = text;
+ int len = textlen;
+
+ assert(grid_line_grid);
+
+ int start_col = col;
+
+ const int max_col = grid_line_maxcol;
+ while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) {
// check if this is the first byte of a multibyte
- mbyte_blen = len > 0
- ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
- : utfc_ptr2len(ptr);
- u8c = len >= 0
- ? utfc_ptr2char_len(ptr, u8cc, (int)((text + len) - ptr))
- : utfc_ptr2char(ptr, u8cc);
- mbyte_cells = utf_char2cells(u8c);
- if (p_arshape && !p_tbidi && ARABIC_CHAR(u8c)) {
- // Do Arabic shaping.
- if (len >= 0 && (int)(ptr - text) + mbyte_blen >= len) {
- // Past end of string to be displayed.
- nc = NUL;
- nc1 = NUL;
- } else {
- nc = len >= 0
- ? utfc_ptr2char_len(ptr + mbyte_blen, pcc,
- (int)((text + len) - ptr - mbyte_blen))
- : utfc_ptr2char(ptr + mbyte_blen, pcc);
- nc1 = pcc[0];
- }
- pc = prev_c;
- prev_c = u8c;
- u8c = arabic_shape(u8c, &c, &u8cc[0], nc, nc1, pc);
- } else {
- prev_c = u8c;
+ int mbyte_blen = len > 0
+ ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
+ : utfc_ptr2len(ptr);
+ int firstc;
+ schar_T schar = len >= 0
+ ? utfc_ptr2schar_len(ptr, (int)((text + len) - ptr), &firstc)
+ : utfc_ptr2schar(ptr, &firstc);
+ int mbyte_cells = utf_char2cells(firstc);
+ if (mbyte_cells > 2) {
+ mbyte_cells = 1;
+
+ schar = schar_from_char(0xFFFD);
}
- if (col + mbyte_cells > grid->cols) {
+
+ if (col + mbyte_cells > max_col) {
// Only 1 cell left, but character requires 2 cells:
// display a '>' in the last column to avoid wrapping. */
- c = '>';
- u8c = '>';
- u8cc[0] = 0;
+ schar = schar_from_ascii('>');
mbyte_cells = 1;
}
- schar_T buf;
- schar_from_cc(buf, u8c, u8cc);
-
- need_redraw = schar_cmp(grid->chars[off], buf)
- || (mbyte_cells == 2 && grid->chars[off + 1][0] != 0)
- || grid->attrs[off] != attr
- || exmode_active
- || rdb_flags & RDB_NODELTA;
-
- if (need_redraw) {
- // When at the end of the text and overwriting a two-cell
- // character with a one-cell character, need to clear the next
- // cell. Also when overwriting the left half of a two-cell char
- // with the right half of a two-cell char. Do this only once
- // (utf8_off2cells() may return 2 on the right half).
- if (clear_next_cell) {
- clear_next_cell = false;
- } else if ((len < 0 ? ptr[mbyte_blen] == NUL : ptr + mbyte_blen >= text + len)
- && ((mbyte_cells == 1
- && grid_off2cells(grid, off, max_off) > 1)
- || (mbyte_cells == 2
- && grid_off2cells(grid, off, max_off) == 1
- && grid_off2cells(grid, off + 1, max_off) > 1))) {
- clear_next_cell = true;
- }
-
- // When at the start of the text and overwriting the right half of a
- // two-cell character in the same grid, truncate that into a '>'.
- if (ptr == text && col > 0 && grid->chars[off][0] == 0) {
- grid->chars[off - 1][0] = '>';
- grid->chars[off - 1][1] = 0;
- }
+ // When at the start of the text and overwriting the right half of a
+ // two-cell character in the same grid, truncate that into a '>'.
+ if (ptr == text && col > grid_line_first && col < grid_line_last
+ && linebuf_char[col] == 0) {
+ linebuf_char[col - 1] = schar_from_ascii('>');
+ }
- schar_copy(grid->chars[off], buf);
- grid->attrs[off] = attr;
- if (mbyte_cells == 2) {
- grid->chars[off + 1][0] = 0;
- grid->attrs[off + 1] = attr;
- }
- put_dirty_first = MIN(put_dirty_first, col);
- put_dirty_last = MAX(put_dirty_last, col + mbyte_cells);
+ linebuf_char[col] = schar;
+ linebuf_attr[col] = attr;
+ linebuf_vcol[col] = -1;
+ if (mbyte_cells == 2) {
+ linebuf_char[col + 1] = 0;
+ linebuf_attr[col + 1] = attr;
+ linebuf_vcol[col + 1] = -1;
}
- off += (size_t)mbyte_cells;
col += mbyte_cells;
ptr += mbyte_blen;
- if (clear_next_cell) {
- // This only happens at the end, display one space next.
- ptr = " ";
- len = -1;
+ }
+
+ if (col > start_col) {
+ grid_line_first = MIN(grid_line_first, start_col);
+ grid_line_last = MAX(grid_line_last, col);
+ }
+
+ return col - start_col;
+}
+
+void grid_line_fill(int start_col, int end_col, int c, int attr)
+{
+ end_col = MIN(end_col, grid_line_maxcol);
+ if (start_col >= end_col) {
+ return;
+ }
+
+ schar_T sc = schar_from_char(c);
+ for (int col = start_col; col < end_col; col++) {
+ linebuf_char[col] = sc;
+ linebuf_attr[col] = attr;
+ linebuf_vcol[col] = -1;
+ }
+
+ grid_line_first = MIN(grid_line_first, start_col);
+ grid_line_last = MAX(grid_line_last, end_col);
+}
+
+/// move the cursor to a position in a currently rendered line.
+void grid_line_cursor_goto(int col)
+{
+ ui_grid_cursor_goto(grid_line_grid->handle, grid_line_row, col);
+}
+
+void grid_line_mirror(void)
+{
+ if (grid_line_first >= grid_line_last) {
+ return;
+ }
+ linebuf_mirror(&grid_line_first, &grid_line_last, grid_line_maxcol);
+}
+
+void linebuf_mirror(int *firstp, int *lastp, int maxcol)
+{
+ int first = *firstp;
+ int last = *lastp;
+
+ size_t n = (size_t)(last - first);
+ int mirror = maxcol - 1; // Mirrors are more fun than television.
+ schar_T *scratch_char = (schar_T *)linebuf_scratch;
+ memcpy(scratch_char + first, linebuf_char + first, n * sizeof(schar_T));
+ for (int col = first; col < last; col++) {
+ int rev = mirror - col;
+ if (col + 1 < last && scratch_char[col + 1] == 0) {
+ linebuf_char[rev - 1] = scratch_char[col];
+ linebuf_char[rev] = 0;
+ col++;
+ } else {
+ linebuf_char[rev] = scratch_char[col];
}
}
- if (do_flush) {
- grid_puts_line_flush(true);
+ // for attr and vcol: assumes doublewidth chars are self-consistent
+ sattr_T *scratch_attr = (sattr_T *)linebuf_scratch;
+ memcpy(scratch_attr + first, linebuf_attr + first, n * sizeof(sattr_T));
+ for (int col = first; col < last; col++) {
+ linebuf_attr[mirror - col] = scratch_attr[col];
+ }
+
+ colnr_T *scratch_vcol = (colnr_T *)linebuf_scratch;
+ memcpy(scratch_vcol + first, linebuf_vcol + first, n * sizeof(colnr_T));
+ for (int col = first; col < last; col++) {
+ linebuf_vcol[mirror - col] = scratch_vcol[col];
+ }
+
+ *lastp = maxcol - first;
+ *firstp = maxcol - last;
+}
+
+/// End a group of grid_line_puts calls and send the screen buffer to the UI layer.
+void grid_line_flush(void)
+{
+ ScreenGrid *grid = grid_line_grid;
+ grid_line_grid = NULL;
+ assert(grid_line_last <= grid_line_maxcol);
+ if (grid_line_first >= grid_line_last) {
+ return;
}
+
+ grid_put_linebuf(grid, grid_line_row, grid_line_coloff, grid_line_first, grid_line_last,
+ grid_line_last, false, 0, false);
}
-/// End a group of grid_puts_len calls and send the screen buffer to the UI
-/// layer.
+/// flush grid line but only if on a valid row
///
-/// @param set_cursor Move the visible cursor to the end of the changed region.
-/// This is a workaround for not yet refactored code paths
-/// and shouldn't be used in new code.
-void grid_puts_line_flush(bool set_cursor)
-{
- assert(put_dirty_row != -1);
- if (put_dirty_first < put_dirty_last) {
- if (set_cursor) {
- ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row,
- MIN(put_dirty_last, put_dirty_grid->cols - 1));
- }
- if (!put_dirty_grid->throttled) {
- ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last,
- put_dirty_last, 0, false);
- } else if (put_dirty_grid->dirty_col) {
- if (put_dirty_last > put_dirty_grid->dirty_col[put_dirty_row]) {
- put_dirty_grid->dirty_col[put_dirty_row] = put_dirty_last;
- }
+/// This is a stopgap until message.c has been refactored to behave
+void grid_line_flush_if_valid_row(void)
+{
+ if (grid_line_row < 0 || grid_line_row >= grid_line_grid->rows) {
+ if (rdb_flags & RDB_INVALID) {
+ abort();
+ } else {
+ grid_line_grid = NULL;
+ return;
}
- put_dirty_first = INT_MAX;
- put_dirty_last = 0;
}
- put_dirty_row = -1;
- put_dirty_grid = NULL;
+ grid_line_flush();
}
/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col"
@@ -377,8 +538,6 @@ void grid_puts_line_flush(bool set_cursor)
void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1,
int c2, int attr)
{
- schar_T sc;
-
int row_off = 0, col_off = 0;
grid_adjust(&grid, &row_off, &col_off);
start_row += row_off;
@@ -400,47 +559,46 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
}
for (int row = start_row; row < end_row; row++) {
+ int dirty_first = INT_MAX;
+ int dirty_last = 0;
+ size_t lineoff = grid->line_offset[row];
+
// When drawing over the right half of a double-wide char clear
// out the left half. When drawing over the left half of a
// double wide-char clear out the right half. Only needed in a
// terminal.
- if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) {
- grid_puts_len(grid, " ", 1, row, start_col - 1, 0);
+ if (start_col > 0 && grid->chars[lineoff + (size_t)start_col] == NUL) {
+ size_t off = lineoff + (size_t)start_col - 1;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = attr;
+ dirty_first = start_col - 1;
}
- if (end_col < grid->cols
- && grid_fix_col(grid, end_col, row) != end_col) {
- grid_puts_len(grid, " ", 1, row, end_col, 0);
+ if (end_col < grid->cols && grid->chars[lineoff + (size_t)end_col] == NUL) {
+ size_t off = lineoff + (size_t)end_col;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = attr;
+ dirty_last = end_col + 1;
}
- // if grid was resized (in ext_multigrid mode), the UI has no redraw updates
- // for the newly resized grid. It is better mark everything as dirty and
- // send all the updates.
- int dirty_first = INT_MAX;
- int dirty_last = 0;
-
int col = start_col;
- schar_from_char(sc, c1);
- size_t lineoff = grid->line_offset[row];
+ schar_T sc = schar_from_char(c1);
for (col = start_col; col < end_col; col++) {
size_t off = lineoff + (size_t)col;
- if (schar_cmp(grid->chars[off], sc) || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
- schar_copy(grid->chars[off], sc);
+ if (grid->chars[off] != sc || grid->attrs[off] != attr || rdb_flags & RDB_NODELTA) {
+ grid->chars[off] = sc;
grid->attrs[off] = attr;
if (dirty_first == INT_MAX) {
dirty_first = col;
}
dirty_last = col + 1;
}
+ grid->vcols[off] = -1;
if (col == start_col) {
- schar_from_char(sc, c2);
+ sc = schar_from_char(c2);
}
}
if (dirty_last > dirty_first) {
- // TODO(bfredl): support a cleared suffix even with a batched line?
- if (put_dirty_row == row) {
- put_dirty_first = MIN(put_dirty_first, dirty_first);
- put_dirty_last = MAX(put_dirty_last, dirty_last);
- } else if (grid->throttled) {
+ if (grid->throttled) {
// Note: assumes msg_grid is the only throttled grid
assert(grid == &msg_grid);
int dirty = 0;
@@ -457,10 +615,6 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
ui_line(grid, row, dirty_first, last, dirty_last, attr, false);
}
}
-
- if (end_col == grid->cols) {
- grid->line_wraps[row] = false;
- }
}
}
@@ -469,14 +623,14 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int
/// - the attributes are different
/// - the character is multi-byte and the next byte is different
/// - the character is two cells wide and the second cell differs.
-static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_to, int cols)
+static int grid_char_needs_redraw(ScreenGrid *grid, int col, size_t off_to, int cols)
{
return (cols > 0
- && ((schar_cmp(linebuf_char[off_from], grid->chars[off_to])
- || linebuf_attr[off_from] != grid->attrs[off_to]
- || (line_off2cells(linebuf_char, off_from, off_from + (size_t)cols) > 1
- && schar_cmp(linebuf_char[off_from + 1],
- grid->chars[off_to + 1])))
+ && ((linebuf_char[col] != grid->chars[off_to]
+ || linebuf_attr[col] != grid->attrs[off_to]
+ || (cols > 1 && linebuf_char[col + 1] == 0
+ && linebuf_char[col + 1] != grid->chars[off_to + 1]))
+ || exmode_active // TODO(bfredl): what in the actual fuck
|| rdb_flags & RDB_NODELTA));
}
@@ -486,53 +640,47 @@ static int grid_char_needs_redraw(ScreenGrid *grid, size_t off_from, size_t off_
/// "endcol" gives the columns where valid characters are.
/// "clear_width" is the width of the window. It's > 0 if the rest of the line
/// needs to be cleared, negative otherwise.
-/// "rlflag" is true in a rightleft window:
+/// "rl" is true for rightleft text, like a window with 'rightleft' option set
/// When true and "clear_width" > 0, clear columns 0 to "endcol"
/// When false and "clear_width" > 0, clear columns "endcol" to "clear_width"
/// If "wrap" is true, then hint to the UI that "row" contains a line
/// which has wrapped into the next row.
-void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int clear_width,
- int rlflag, win_T *wp, int bg_attr, bool wrap)
+void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int col, int endcol, int clear_width,
+ bool rl, int bg_attr, bool wrap)
{
- size_t max_off_from;
- size_t max_off_to;
- int col = 0;
- bool redraw_this; // Does character need redraw?
bool redraw_next; // redraw_this for next character
bool clear_next = false;
int char_cells; // 1: normal char
// 2: occupies two display cells
- int start_dirty = -1, end_dirty = 0;
-
+ assert(0 <= row && row < grid->rows);
// TODO(bfredl): check all callsites and eliminate
- // Check for illegal row and col, just in case
- if (row >= grid->rows) {
- row = grid->rows - 1;
- }
+ // Check for illegal col, just in case
if (endcol > grid->cols) {
endcol = grid->cols;
}
- grid_adjust(&grid, &row, &coloff);
-
// Safety check. Avoids clang warnings down the call stack.
if (grid->chars == NULL || row >= grid->rows || coloff >= grid->cols) {
DLOG("invalid state, skipped");
return;
}
- size_t off_from = 0;
+ bool invalid_row = grid != &default_grid && grid_invalid_row(grid, row) && col == 0;
size_t off_to = grid->line_offset[row] + (size_t)coloff;
- max_off_from = linebuf_size;
- max_off_to = grid->line_offset[row] + (size_t)grid->cols;
+ const size_t max_off_to = grid->line_offset[row] + (size_t)grid->cols;
+
+ // When at the start of the text and overwriting the right half of a
+ // two-cell character in the same grid, truncate that into a '>'.
+ if (col > 0 && grid->chars[off_to + (size_t)col] == 0) {
+ linebuf_char[col - 1] = schar_from_ascii('>');
+ col--;
+ }
- if (rlflag) {
+ if (rl) {
// Clear rest first, because it's left of the text.
if (clear_width > 0) {
- while (col <= endcol && grid->chars[off_to][0] == ' '
- && grid->chars[off_to][1] == NUL
- && grid->attrs[off_to] == bg_attr) {
- off_to++;
+ while (col <= endcol && grid->chars[off_to + (size_t)col] == schar_from_ascii(' ')
+ && grid->attrs[off_to + (size_t)col] == bg_attr) {
col++;
}
if (col <= endcol) {
@@ -540,28 +688,32 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
}
}
col = endcol + 1;
- off_to = grid->line_offset[row] + (size_t)col + (size_t)coloff;
- off_from += (size_t)col;
- endcol = (clear_width > 0 ? clear_width : -clear_width);
+ endcol = clear_width;
+ }
+
+ if (p_arshape && !p_tbidi && endcol > col) {
+ line_do_arabic_shape(linebuf_char + col, endcol - col);
}
if (bg_attr) {
for (int c = col; c < endcol; c++) {
- linebuf_attr[off_from + (size_t)c] =
- hl_combine_attr(bg_attr, linebuf_attr[off_from + (size_t)c]);
+ linebuf_attr[c] = hl_combine_attr(bg_attr, linebuf_attr[c]);
}
}
- redraw_next = grid_char_needs_redraw(grid, off_from, off_to, endcol - col);
+ redraw_next = grid_char_needs_redraw(grid, col, (size_t)col + off_to, endcol - col);
+
+ int start_dirty = -1, end_dirty = 0;
while (col < endcol) {
char_cells = 1;
- if (col + 1 < endcol) {
- char_cells = line_off2cells(linebuf_char, off_from, max_off_from);
+ if (col + 1 < endcol && linebuf_char[col + 1] == 0) {
+ char_cells = 2;
}
- redraw_this = redraw_next;
- redraw_next = grid_char_needs_redraw(grid, off_from + (size_t)char_cells,
- off_to + (size_t)char_cells,
+ bool redraw_this = redraw_next; // Does character need redraw?
+ size_t off = (size_t)col + off_to;
+ redraw_next = grid_char_needs_redraw(grid, col + char_cells,
+ off + (size_t)char_cells,
endcol - col - char_cells);
if (redraw_this) {
@@ -574,52 +726,50 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
// the right half of the old character.
// Also required when writing the right half of a double-width
// char over the left half of an existing one
- if (col + char_cells == endcol
- && ((char_cells == 1
- && grid_off2cells(grid, off_to, max_off_to) > 1)
- || (char_cells == 2
- && grid_off2cells(grid, off_to, max_off_to) == 1
- && grid_off2cells(grid, off_to + 1, max_off_to) > 1))) {
+ if (col + char_cells == endcol && off + (size_t)char_cells < max_off_to
+ && grid->chars[off + (size_t)char_cells] == NUL) {
clear_next = true;
}
- schar_copy(grid->chars[off_to], linebuf_char[off_from]);
+ grid->chars[off] = linebuf_char[col];
if (char_cells == 2) {
- schar_copy(grid->chars[off_to + 1], linebuf_char[off_from + 1]);
+ grid->chars[off + 1] = linebuf_char[col + 1];
}
- grid->attrs[off_to] = linebuf_attr[off_from];
+ grid->attrs[off] = linebuf_attr[col];
// For simplicity set the attributes of second half of a
// double-wide character equal to the first half.
if (char_cells == 2) {
- grid->attrs[off_to + 1] = linebuf_attr[off_from];
+ grid->attrs[off + 1] = linebuf_attr[col];
}
}
- off_to += (size_t)char_cells;
- off_from += (size_t)char_cells;
+ grid->vcols[off] = linebuf_vcol[col];
+ if (char_cells == 2) {
+ grid->vcols[off + 1] = linebuf_vcol[col + 1];
+ }
+
col += char_cells;
}
if (clear_next) {
// Clear the second half of a double-wide character of which the left
// half was overwritten with a single-wide character.
- schar_from_ascii(grid->chars[off_to], ' ');
+ grid->chars[(size_t)col + off_to] = schar_from_ascii(' ');
end_dirty++;
}
int clear_end = -1;
- if (clear_width > 0 && !rlflag) {
+ if (clear_width > 0 && !rl) {
// blank out the rest of the line
// TODO(bfredl): we could cache winline widths
while (col < clear_width) {
- if (grid->chars[off_to][0] != ' '
- || grid->chars[off_to][1] != NUL
- || grid->attrs[off_to] != bg_attr
+ size_t off = (size_t)col + off_to;
+ if (grid->chars[off] != schar_from_ascii(' ')
+ || grid->attrs[off] != bg_attr
|| rdb_flags & RDB_NODELTA) {
- grid->chars[off_to][0] = ' ';
- grid->chars[off_to][1] = NUL;
- grid->attrs[off_to] = bg_attr;
+ grid->chars[off] = schar_from_ascii(' ');
+ grid->attrs[off] = bg_attr;
if (start_dirty == -1) {
start_dirty = col;
end_dirty = col;
@@ -628,17 +778,11 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
}
clear_end = col + 1;
}
+ grid->vcols[off] = MAXCOL;
col++;
- off_to++;
}
}
- if (clear_width > 0 || wp->w_width != grid->cols) {
- // If we cleared after the end of the line, it did not wrap.
- // For vsplit, line wrapping is not possible.
- grid->line_wraps[row] = false;
- }
-
if (clear_end < end_dirty) {
clear_end = end_dirty;
}
@@ -646,30 +790,44 @@ void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, int cle
start_dirty = end_dirty;
}
if (clear_end > start_dirty) {
- ui_line(grid, row, coloff + start_dirty, coloff + end_dirty, coloff + clear_end,
- bg_attr, wrap);
+ if (!grid->throttled) {
+ int start_pos = coloff + start_dirty;
+ // When drawing over the right half of a double-wide char clear out the
+ // left half. Only needed in a terminal.
+ if (invalid_row && start_pos == 0) {
+ start_pos = -1;
+ }
+ ui_line(grid, row, start_pos, coloff + end_dirty, coloff + clear_end,
+ bg_attr, wrap);
+ } else if (grid->dirty_col) {
+ // TODO(bfredl): really get rid of the extra pseudo terminal in message.c
+ // by using a linebuf_char copy for "throttled message line"
+ if (clear_end > grid->dirty_col[row]) {
+ grid->dirty_col[row] = clear_end;
+ }
+ }
}
}
void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
{
int new_row;
- ScreenGrid new = *grid;
+ ScreenGrid ngrid = *grid;
assert(rows >= 0 && columns >= 0);
size_t ncells = (size_t)rows * (size_t)columns;
- new.chars = xmalloc(ncells * sizeof(schar_T));
- new.attrs = xmalloc(ncells * sizeof(sattr_T));
- new.line_offset = xmalloc((size_t)rows * sizeof(*new.line_offset));
- new.line_wraps = xmalloc((size_t)rows * sizeof(*new.line_wraps));
+ ngrid.chars = xmalloc(ncells * sizeof(schar_T));
+ ngrid.attrs = xmalloc(ncells * sizeof(sattr_T));
+ ngrid.vcols = xmalloc(ncells * sizeof(colnr_T));
+ memset(ngrid.vcols, -1, ncells * sizeof(colnr_T));
+ ngrid.line_offset = xmalloc((size_t)rows * sizeof(*ngrid.line_offset));
- new.rows = rows;
- new.cols = columns;
+ ngrid.rows = rows;
+ ngrid.cols = columns;
- for (new_row = 0; new_row < new.rows; new_row++) {
- new.line_offset[new_row] = (size_t)new_row * (size_t)new.cols;
- new.line_wraps[new_row] = false;
+ for (new_row = 0; new_row < ngrid.rows; new_row++) {
+ ngrid.line_offset[new_row] = (size_t)new_row * (size_t)ngrid.cols;
- grid_clear_line(&new, new.line_offset[new_row], columns, valid);
+ grid_clear_line(&ngrid, ngrid.line_offset[new_row], columns, valid);
if (copy) {
// If the screen is not going to be cleared, copy as much as
@@ -677,26 +835,33 @@ void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid)
// (used when resizing the window at the "--more--" prompt or when
// executing an external command, for the GUI).
if (new_row < grid->rows && grid->chars != NULL) {
- int len = MIN(grid->cols, new.cols);
- memmove(new.chars + new.line_offset[new_row],
+ int len = MIN(grid->cols, ngrid.cols);
+ memmove(ngrid.chars + ngrid.line_offset[new_row],
grid->chars + grid->line_offset[new_row],
(size_t)len * sizeof(schar_T));
- memmove(new.attrs + new.line_offset[new_row],
+ memmove(ngrid.attrs + ngrid.line_offset[new_row],
grid->attrs + grid->line_offset[new_row],
(size_t)len * sizeof(sattr_T));
+ memmove(ngrid.vcols + ngrid.line_offset[new_row],
+ grid->vcols + grid->line_offset[new_row],
+ (size_t)len * sizeof(colnr_T));
}
}
}
grid_free(grid);
- *grid = new;
+ *grid = ngrid;
// Share a single scratch buffer for all grids, by
// ensuring it is as wide as the widest grid.
if (linebuf_size < (size_t)columns) {
xfree(linebuf_char);
xfree(linebuf_attr);
+ xfree(linebuf_vcol);
+ xfree(linebuf_scratch);
linebuf_char = xmalloc((size_t)columns * sizeof(schar_T));
linebuf_attr = xmalloc((size_t)columns * sizeof(sattr_T));
+ linebuf_vcol = xmalloc((size_t)columns * sizeof(colnr_T));
+ linebuf_scratch = xmalloc((size_t)columns * sizeof(sscratch_T));
linebuf_size = (size_t)columns;
}
}
@@ -705,13 +870,13 @@ void grid_free(ScreenGrid *grid)
{
xfree(grid->chars);
xfree(grid->attrs);
+ xfree(grid->vcols);
xfree(grid->line_offset);
- xfree(grid->line_wraps);
grid->chars = NULL;
grid->attrs = NULL;
+ grid->vcols = NULL;
grid->line_offset = NULL;
- grid->line_wraps = NULL;
}
/// Doesn't allow reinit, so must only be called by free_all_mem!
@@ -720,6 +885,8 @@ void grid_free_all_mem(void)
grid_free(&default_grid);
xfree(linebuf_char);
xfree(linebuf_attr);
+ xfree(linebuf_vcol);
+ xfree(linebuf_scratch);
}
/// (Re)allocates a window grid if size changed while in ext_multigrid mode.
@@ -788,6 +955,7 @@ void win_grid_alloc(win_T *wp)
if ((resizing_screen || was_resized) && want_allocation) {
ui_call_grid_resize(grid_allocated->handle,
grid_allocated->cols, grid_allocated->rows);
+ ui_check_cursor_grid(grid_allocated->handle);
}
}
@@ -810,7 +978,6 @@ void grid_assign_handle(ScreenGrid *grid)
/// 'row', 'col' and 'end' are relative to the start of the region.
void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
{
- int i;
int j;
unsigned temp;
@@ -825,7 +992,7 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
// Shift line_offset[] line_count down to reflect the inserted lines.
// Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
+ for (int i = 0; i < line_count; i++) {
if (width != grid->cols) {
// need to copy part of a line
j = end - 1 - i;
@@ -834,16 +1001,13 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
j += line_count;
grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
} else {
j = end - 1 - i;
temp = (unsigned)grid->line_offset[j];
while ((j -= line_count) >= row) {
grid->line_offset[j + line_count] = grid->line_offset[j];
- grid->line_wraps[j + line_count] = grid->line_wraps[j];
}
grid->line_offset[j + line_count] = temp;
- grid->line_wraps[j + line_count] = false;
grid_clear_line(grid, temp, grid->cols, false);
}
}
@@ -860,7 +1024,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, int width)
{
int j;
- int i;
unsigned temp;
int row_off = 0;
@@ -874,7 +1037,7 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
// Now shift line_offset[] line_count up to reflect the deleted lines.
// Clear the inserted lines.
- for (i = 0; i < line_count; i++) {
+ for (int i = 0; i < line_count; i++) {
if (width != grid->cols) {
// need to copy part of a line
j = row + i;
@@ -883,17 +1046,14 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col,
}
j -= line_count;
grid_clear_line(grid, grid->line_offset[j] + (size_t)col, width, false);
- grid->line_wraps[j] = false;
} else {
// whole width, moving the line pointers is faster
j = row + i;
temp = (unsigned)grid->line_offset[j];
while ((j += line_count) <= end - 1) {
grid->line_offset[j - line_count] = grid->line_offset[j];
- grid->line_wraps[j - line_count] = grid->line_wraps[j];
}
grid->line_offset[j - line_count] = temp;
- grid->line_wraps[j - line_count] = false;
grid_clear_line(grid, temp, grid->cols, false);
}
}
@@ -910,6 +1070,7 @@ static void linecopy(ScreenGrid *grid, int to, int from, int col, int width)
memmove(grid->chars + off_to, grid->chars + off_from, (size_t)width * sizeof(schar_T));
memmove(grid->attrs + off_to, grid->attrs + off_from, (size_t)width * sizeof(sattr_T));
+ memmove(grid->vcols + off_to, grid->vcols + off_from, (size_t)width * sizeof(colnr_T));
}
win_T *get_win_by_grid_handle(handle_T handle)
diff --git a/src/nvim/grid.h b/src/nvim/grid.h
index deb3d3785d..9d8e395dae 100644
--- a/src/nvim/grid.h
+++ b/src/nvim/grid.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_GRID_H
-#define NVIM_GRID_H
+#pragma once
#include <stdbool.h>
+#include <stddef.h> // IWYU pragma: keep
#include <string.h>
-#include "nvim/ascii.h"
#include "nvim/buffer_defs.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
+#include "nvim/grid_defs.h" // IWYU pragma: export
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
-#include "nvim/memory.h"
+#include "nvim/pos_defs.h"
/// By default, all windows are drawn on a single rectangular grid, represented by
/// this ScreenGrid instance. In multigrid mode each window will have its own
@@ -18,47 +17,43 @@
///
/// Note: before the screen is initialized and when out of memory these can be
/// NULL.
-EXTERN ScreenGrid default_grid INIT(= SCREEN_GRID_INIT);
+EXTERN ScreenGrid default_grid INIT( = SCREEN_GRID_INIT);
#define DEFAULT_GRID_HANDLE 1 // handle for the default_grid
/// While resizing the screen this flag is set.
-EXTERN bool resizing_screen INIT(= 0);
+EXTERN bool resizing_screen INIT( = 0);
-EXTERN schar_T *linebuf_char INIT(= NULL);
-EXTERN sattr_T *linebuf_attr INIT(= NULL);
+EXTERN schar_T *linebuf_char INIT( = NULL);
+EXTERN sattr_T *linebuf_attr INIT( = NULL);
+EXTERN colnr_T *linebuf_vcol INIT( = NULL);
+EXTERN char *linebuf_scratch INIT( = NULL);
// Low-level functions to manipulate individual character cells on the
// screen grid.
/// Put a ASCII character in a screen cell.
-static inline void schar_from_ascii(char *p, const char c)
-{
- p[0] = c;
- p[1] = 0;
-}
+///
+/// If `x` is a compile time constant, schar_from_ascii(x) will also be.
+/// But the specific value varies per platform.
+#ifdef ORDER_BIG_ENDIAN
+# define schar_from_ascii(x) ((schar_T)((x) << 24))
+#else
+# define schar_from_ascii(x) ((schar_T)(x))
+#endif
/// Put a unicode character in a screen cell.
-static inline int schar_from_char(char *p, int c)
-{
- int len = utf_char2bytes(c, p);
- p[len] = NUL;
- return len;
-}
-
-/// compare the contents of two screen cells.
-static inline int schar_cmp(char *sc1, char *sc2)
-{
- return strncmp(sc1, sc2, sizeof(schar_T));
-}
-
-/// copy the contents of screen cell `sc2` into cell `sc1`
-static inline void schar_copy(char *sc1, char *sc2)
+static inline schar_T schar_from_char(int c)
{
- xstrlcpy(sc1, sc2, sizeof(schar_T));
+ schar_T sc = 0;
+ if (c >= 0x200000) {
+ // TODO(bfredl): this must NEVER happen, even if the file contained overlong sequences
+ c = 0xFFFD;
+ }
+ utf_char2bytes(c, (char *)&sc);
+ return sc;
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "grid.h.generated.h"
#endif
-#endif // NVIM_GRID_H
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index db31f7b984..10a6161171 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -1,17 +1,15 @@
-#ifndef NVIM_GRID_DEFS_H
-#define NVIM_GRID_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
-#define MAX_MCO 6 // fixed value for 'maxcombine'
-
-// The characters and attributes drawn on grids.
-typedef char schar_T[(MAX_MCO + 1) * 4 + 1];
-typedef int sattr_T;
+// Includes final NUL. MAX_MCO is no longer used, but at least 4*(MAX_MCO+1)+1=29
+// ensures we can fit all composed chars which did fit before.
+#define MAX_SCHAR_SIZE 32
enum {
kZIndexDefaultGrid = 0,
@@ -29,7 +27,7 @@ enum {
/// we can avoid sending bigger updates than necessary to the Ul layer.
///
/// Screen cells are stored as NUL-terminated UTF-8 strings, and a cell can
-/// contain up to MAX_MCO composing characters after the base character.
+/// contain composing characters as many as fits in MAX_SCHAR_SIZE-1 bytes
/// The composing characters are to be drawn on top of the original character.
/// The content after the NUL is not defined (so comparison must be done a
/// single cell at a time). Double-width characters are stored in the left cell,
@@ -37,20 +35,24 @@ enum {
/// screen is cleared, the cells should be filled with a single whitespace char.
///
/// attrs[] contains the highlighting attribute for each cell.
-/// line_offset[n] is the offset from chars[] and attrs[] for the
-/// start of line 'n'. These offsets are in general not linear, as full screen
-/// scrolling is implemented by rotating the offsets in the line_offset array.
-/// line_wraps[] is an array of boolean flags indicating if the screen line
-/// wraps to the next line. It can only be true if a window occupies the entire
-/// screen width.
+///
+/// vcols[] contains the virtual columns in the line. -1 means not available
+/// or before buffer text, MAXCOL means after the end of the line.
+/// -2 or -3 means in fold column and a mouse click should:
+/// -2: open a fold
+/// -3: close a fold
+///
+/// line_offset[n] is the offset from chars[], attrs[] and vcols[] for the start
+/// of line 'n'. These offsets are in general not linear, as full screen scrolling
+/// is implemented by rotating the offsets in the line_offset array.
typedef struct ScreenGrid ScreenGrid;
struct ScreenGrid {
handle_T handle;
schar_T *chars;
sattr_T *attrs;
+ colnr_T *vcols;
size_t *line_offset;
- char_u *line_wraps;
// last column that was drawn (not cleared with the default background).
// only used when "throttled" is set. Not allocated by grid_alloc!
@@ -117,6 +119,5 @@ typedef struct {
int coloff;
int cur_attr;
int clear_width;
+ bool wrap;
} GridLineEvent;
-
-#endif // NVIM_GRID_DEFS_H
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index 851e70caca..475666be5e 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file hashtab.c
///
/// Handling of a hashtable with Vim-specific properties.
@@ -26,12 +23,13 @@
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/func_attr.h"
+#include "nvim/gettext.h"
#include "nvim/hashtab.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Magic value for algorithm that walks through the array.
#define PERTURB_SHIFT 5
@@ -65,7 +63,7 @@ void hash_clear(hashtab_T *ht)
/// Free the array of a hash table and all contained values.
///
/// @param off the offset from start of value to start of key (@see hashitem_T).
-void hash_clear_all(hashtab_T *ht, unsigned int off)
+void hash_clear_all(hashtab_T *ht, unsigned off)
{
size_t todo = ht->ht_used;
for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
@@ -207,7 +205,7 @@ int hash_add(hashtab_T *ht, char *key)
hash_T hash = hash_hash(key);
hashitem_T *hi = hash_lookup(ht, key, strlen(key), hash);
if (!HASHITEM_EMPTY(hi)) {
- internal_error("hash_add()");
+ siemsg(_("E685: Internal error: hash_add(): duplicate key \"%s\""), key);
return FAIL;
}
hash_add_item(ht, hi, key, hash);
@@ -350,15 +348,15 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems)
// so that copying is possible.
hashitem_T temparray[HT_INIT_SIZE];
hashitem_T *oldarray = keep_smallarray
- ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray))
- : ht->ht_array;
+ ? memcpy(temparray, ht->ht_smallarray, sizeof(temparray))
+ : ht->ht_array;
if (newarray_is_small) {
CLEAR_FIELD(ht->ht_smallarray);
}
hashitem_T *newarray = newarray_is_small
- ? ht->ht_smallarray
- : xcalloc(newsize, sizeof(hashitem_T));
+ ? ht->ht_smallarray
+ : xcalloc(newsize, sizeof(hashitem_T));
// Move all the items from the old array to the new one, placing them in
// the right spot. The new array won't have any removed items, thus this
@@ -411,7 +409,7 @@ hash_T hash_hash(const char *key)
hash_T hash = (uint8_t)(*key);
if (hash == 0) {
- return (hash_T)0;
+ return 0;
}
// A simplistic algorithm that appears to do very well.
@@ -457,8 +455,8 @@ hash_T hash_hash_len(const char *key, const size_t len)
///
/// Used for testing because luajit ffi does not allow getting addresses of
/// globals.
-const char_u *_hash_key_removed(void)
+const char *_hash_key_removed(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- return (char_u *)HI_KEY_REMOVED;
+ return HI_KEY_REMOVED;
}
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index 0a50fb2ef8..d14eb6944e 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -1,76 +1,18 @@
-#ifndef NVIM_HASHTAB_H
-#define NVIM_HASHTAB_H
+#pragma once
#include <stddef.h>
-#include "nvim/types.h"
+#include "nvim/hashtab_defs.h" // IWYU pragma: export
/// Magic number used for hashitem "hi_key" value indicating a deleted item
///
/// Only the address is used.
extern char hash_removed;
-/// Type for hash number (hash calculation result).
-typedef size_t hash_T;
-
/// The address of "hash_removed" is used as a magic number
/// for hi_key to indicate a removed item.
#define HI_KEY_REMOVED (&hash_removed)
-#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL \
- || (hi)->hi_key == &hash_removed)
-
-/// Hashtable item.
-///
-/// Each item has a NUL terminated string key.
-/// A key can appear only once in the table.
-///
-/// A hash number is computed from the key for quick lookup. When the hashes
-/// of two different keys point to the same entry an algorithm is used to
-/// iterate over other entries in the table until the right one is found.
-/// To make the iteration work removed keys are different from entries where a
-/// key was never present.
-///
-/// Note that this does not contain a pointer to the key and another pointer to
-/// the value. Instead, it is assumed that the key is contained within the
-/// value, so that you can get a pointer to the value subtracting an offset from
-/// the pointer to the key.
-/// This reduces the size of this item by 1/3.
-typedef struct hashitem_S {
- /// Cached hash number for hi_key.
- hash_T hi_hash;
-
- /// Item key.
- ///
- /// Possible values mean the following:
- /// NULL : Item was never used.
- /// HI_KEY_REMOVED : Item was removed.
- /// (Any other pointer value) : Item is currently being used.
- char *hi_key;
-} hashitem_T;
-
-/// Initial size for a hashtable.
-/// Our items are relatively small and growing is expensive, thus start with 16.
-/// Must be a power of 2.
-/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
-#define HT_INIT_SIZE 16
-
-/// An array-based hashtable.
-///
-/// Keys are NUL terminated strings. They cannot be repeated within a table.
-/// Values are of any type.
-///
-/// The hashtable grows to accommodate more entries when needed.
-typedef struct hashtable_S {
- hash_T ht_mask; ///< mask used for hash value
- ///< (nr of items in array is "ht_mask" + 1)
- size_t ht_used; ///< number of items used
- size_t ht_filled; ///< number of items used or removed
- int ht_changed; ///< incremented when adding or removing an item
- int ht_locked; ///< counter for hash_lock()
- hashitem_T *ht_array; ///< points to the array, allocated when it's
- ///< not "ht_smallarray"
- hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array
-} hashtab_T;
+#define HASHITEM_EMPTY(hi) ((hi)->hi_key == NULL || (hi)->hi_key == &hash_removed)
/// Iterate over a hashtab
///
@@ -94,5 +36,3 @@ typedef struct hashtable_S {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "hashtab.h.generated.h"
#endif
-
-#endif // NVIM_HASHTAB_H
diff --git a/src/nvim/hashtab_defs.h b/src/nvim/hashtab_defs.h
new file mode 100644
index 0000000000..089838fcae
--- /dev/null
+++ b/src/nvim/hashtab_defs.h
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <stddef.h>
+
+/// Type for hash number (hash calculation result).
+typedef size_t hash_T;
+
+/// Hashtable item.
+///
+/// Each item has a NUL terminated string key.
+/// A key can appear only once in the table.
+///
+/// A hash number is computed from the key for quick lookup. When the hashes
+/// of two different keys point to the same entry an algorithm is used to
+/// iterate over other entries in the table until the right one is found.
+/// To make the iteration work removed keys are different from entries where a
+/// key was never present.
+///
+/// Note that this does not contain a pointer to the key and another pointer to
+/// the value. Instead, it is assumed that the key is contained within the
+/// value, so that you can get a pointer to the value subtracting an offset from
+/// the pointer to the key.
+/// This reduces the size of this item by 1/3.
+typedef struct hashitem_S {
+ /// Cached hash number for hi_key.
+ hash_T hi_hash;
+
+ /// Item key.
+ ///
+ /// Possible values mean the following:
+ /// NULL : Item was never used.
+ /// HI_KEY_REMOVED : Item was removed.
+ /// (Any other pointer value) : Item is currently being used.
+ char *hi_key;
+} hashitem_T;
+
+/// Initial size for a hashtable.
+/// Our items are relatively small and growing is expensive, thus start with 16.
+/// Must be a power of 2.
+/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
+enum { HT_INIT_SIZE = 16, };
+
+/// An array-based hashtable.
+///
+/// Keys are NUL terminated strings. They cannot be repeated within a table.
+/// Values are of any type.
+///
+/// The hashtable grows to accommodate more entries when needed.
+typedef struct hashtable_S {
+ hash_T ht_mask; ///< mask used for hash value
+ ///< (nr of items in array is "ht_mask" + 1)
+ size_t ht_used; ///< number of items used
+ size_t ht_filled; ///< number of items used or removed
+ int ht_changed; ///< incremented when adding or removing an item
+ int ht_locked; ///< counter for hash_lock()
+ hashitem_T *ht_array; ///< points to the array, allocated when it's
+ ///< not "ht_smallarray"
+ hashitem_T ht_smallarray[HT_INIT_SIZE]; ///< initial array
+} hashtab_T;
diff --git a/src/nvim/help.c b/src/nvim/help.c
index bbc552fa4c..c23dc7fd9d 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -1,42 +1,46 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// help.c: functions for Vim help
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
+#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/help.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -47,19 +51,12 @@
void ex_help(exarg_T *eap)
{
char *arg;
- char *tag;
FILE *helpfd; // file descriptor of help file
- int n;
- int i;
win_T *wp;
int num_matches;
char **matches;
- char *p;
int empty_fnum = 0;
int alt_fnum = 0;
- buf_T *buf;
- int len;
- char *lang;
const bool old_KeyTyped = KeyTyped;
if (eap != NULL) {
@@ -88,13 +85,13 @@ void ex_help(exarg_T *eap)
}
// remove trailing blanks
- p = arg + strlen(arg) - 1;
+ char *p = arg + strlen(arg) - 1;
while (p > arg && ascii_iswhite(*p) && p[-1] != '\\') {
*p-- = NUL;
}
// Check for a specified language
- lang = check_help_lang(arg);
+ char *lang = check_help_lang(arg);
// When no argument given go to the index.
if (*arg == NUL) {
@@ -102,13 +99,13 @@ void ex_help(exarg_T *eap)
}
// Check if there is a match for the argument.
- n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
+ int n = find_help_tags(arg, &num_matches, &matches, eap != NULL && eap->forceit);
- i = 0;
+ int i = 0;
if (n != FAIL && lang != NULL) {
// Find first item with the requested language.
for (i = 0; i < num_matches; i++) {
- len = (int)strlen(matches[i]);
+ int len = (int)strlen(matches[i]);
if (len > 3 && matches[i][len - 3] == '@'
&& STRICMP(matches[i] + len - 2, lang) == 0) {
break;
@@ -128,7 +125,7 @@ void ex_help(exarg_T *eap)
}
// The first match (in the requested language) is the best match.
- tag = xstrdup(matches[i]);
+ char *tag = xstrdup(matches[i]);
FreeWild(num_matches, matches);
// Re-use an existing help window or open a new one.
@@ -151,7 +148,7 @@ void ex_help(exarg_T *eap)
// There is no help window yet.
// Try to open the file specified by the "helpfile" option.
if ((helpfd = os_fopen(p_hf, READBIN)) == NULL) {
- smsg(_("Sorry, help file \"%s\" not found"), p_hf);
+ smsg(0, _("Sorry, help file \"%s\" not found"), p_hf);
goto erret;
}
fclose(helpfd);
@@ -199,7 +196,7 @@ void ex_help(exarg_T *eap)
// may have jumped to another window, check that the buffer is not in a
// window.
if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) {
- buf = buflist_findnr(empty_fnum);
+ buf_T *buf = buflist_findnr(empty_fnum);
if (buf != NULL && buf->b_nwindows == 0) {
wipe_buffer(buf, true);
}
@@ -259,11 +256,8 @@ char *check_help_lang(char *arg)
int help_heuristic(char *matched_string, int offset, int wrong_case)
FUNC_ATTR_PURE
{
- int num_letters;
- char *p;
-
- num_letters = 0;
- for (p = matched_string; *p; p++) {
+ int num_letters = 0;
+ for (char *p = matched_string; *p; p++) {
if (ASCII_ISALNUM(*p)) {
num_letters++;
}
@@ -298,11 +292,8 @@ int help_heuristic(char *matched_string, int offset, int wrong_case)
/// that has been put after the tagname by find_tags().
static int help_compare(const void *s1, const void *s2)
{
- char *p1;
- char *p2;
-
- p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
- p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
+ char *p1 = *(char **)s1 + strlen(*(char **)s1) + 1;
+ char *p2 = *(char **)s2 + strlen(*(char **)s2) + 1;
// Compare by help heuristic number first.
int cmp = strcmp(p1, p2);
@@ -320,8 +311,6 @@ static int help_compare(const void *s1, const void *s2)
/// When "keep_lang" is true try keeping the language of the current buffer.
int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep_lang)
{
- int i;
-
// Specific tags that either have a specific replacement or won't go
// through the generic rules.
static char *(except_tbl[][2]) = {
@@ -379,7 +368,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
// When the string starting with "expr-" and containing '?' and matches
// the table, it is taken literally (but ~ is escaped). Otherwise '?'
// is recognized as a wildcard.
- for (i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
+ for (int i = (int)ARRAY_SIZE(expr_table); --i >= 0;) {
if (strcmp(arg + 5, expr_table[i]) == 0) {
for (int si = 0, di = 0;; si++) {
if (arg[si] == '~') {
@@ -396,7 +385,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
} else {
// Recognize a few exceptions to the rule. Some strings that contain
// '*'are changed to "star", otherwise '*' is recognized as a wildcard.
- for (i = 0; except_tbl[i][0] != NULL; i++) {
+ for (int i = 0; except_tbl[i][0] != NULL; i++) {
if (strcmp(arg, except_tbl[i][0]) == 0) {
STRCPY(d, except_tbl[i][1]);
break;
@@ -469,7 +458,7 @@ int find_help_tags(const char *arg, int *num_matches, char ***matches, bool keep
// Replace "^x" by "CTRL-X". Don't do this for "^_" to make
// ":help i_^_CTRL-D" work.
// Insert '-' before and after "CTRL-X" when applicable.
- if (*s < ' '
+ if ((uint8_t)(*s) < ' '
|| (*s == '^' && s[1]
&& (ASCII_ISALPHA(s[1]) || vim_strchr("?@[\\]^", (uint8_t)s[1]) != NULL))) {
if (d > IObuff && d[-1] != '_' && d[-1] != '\\') {
@@ -658,26 +647,23 @@ void prepare_help_buffer(void)
/// highlighting is not used.
void fix_help_buffer(void)
{
- linenr_T lnum;
- char *line;
- bool in_example = false;
-
// Set filetype to "help".
if (strcmp(curbuf->b_p_ft, "help") != 0) {
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "help", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("help"), OPT_LOCAL);
curbuf->b_ro_locked--;
}
if (!syntax_present(curwin)) {
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
- line = ml_get_buf(curbuf, lnum, false);
+ bool in_example = false;
+ for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
+ char *line = ml_get_buf(curbuf, lnum);
const size_t len = strlen(line);
if (in_example && len > 0 && !ascii_iswhite(line[0])) {
// End of example: non-white or '<' in first column.
if (line[0] == '<') {
// blank-out a '<' in the first column
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[0] = ' ';
}
in_example = false;
@@ -685,12 +671,12 @@ void fix_help_buffer(void)
if (!in_example && len > 0) {
if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) {
// blank-out a '>' in the last column (start of example)
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[len - 1] = ' ';
in_example = true;
} else if (line[len - 1] == '~') {
// blank-out a '~' at the end of line (header marker)
- line = ml_get_buf(curbuf, lnum, true);
+ line = ml_get_buf_mut(curbuf, lnum);
line[len - 1] = ' ';
}
}
@@ -706,12 +692,14 @@ void fix_help_buffer(void)
&& ASCII_ISALPHA(fname[6])
&& TOLOWER_ASC(fname[7]) == 'x'
&& fname[8] == NUL)) {
- for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
- line = ml_get_buf(curbuf, lnum, false);
+ for (linenr_T lnum = 1; lnum < curbuf->b_ml.ml_line_count; lnum++) {
+ char *line = ml_get_buf(curbuf, lnum);
if (strstr(line, "*local-additions*") == NULL) {
continue;
}
+ int lnum_start = lnum;
+
// Go through all directories in 'runtimepath', skipping
// $VIMRUNTIME.
char *p = p_rtp;
@@ -722,9 +710,7 @@ void fix_help_buffer(void)
&& path_full_compare(rt, NameBuff, false, true) != kEqualFiles) {
int fcount;
char **fnames;
- char *s;
vimconv_T vc;
- char *cp;
// Find all "doc/ *.txt" files in this directory.
if (!add_pathsep(NameBuff)
@@ -740,6 +726,8 @@ void fix_help_buffer(void)
if (gen_expand_wildcards(1, buff_list, &fcount,
&fnames, EW_FILE|EW_SILENT) == OK
&& fcount > 0) {
+ char *s;
+ char *cp;
// If foo.abx is found use it instead of foo.txt in
// the same directory.
for (int i1 = 0; i1 < fcount; i1++) {
@@ -829,7 +817,7 @@ void fix_help_buffer(void)
}
convert_setup(&vc, NULL, NULL);
- ml_append(lnum, cp, (colnr_T)0, false);
+ ml_append(lnum, cp, 0, false);
if (cp != IObuff) {
xfree(cp);
}
@@ -842,6 +830,11 @@ void fix_help_buffer(void)
}
xfree(rt);
}
+ linenr_T appended = lnum - lnum_start;
+ if (appended) {
+ mark_adjust(lnum_start + 1, (linenr_T)MAXLNUM, appended, 0, kExtmarkUndo);
+ buf_redraw_changed_lines_later(curbuf, lnum_start + 1, lnum_start + 1, appended);
+ }
break;
}
}
@@ -874,7 +867,6 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
garray_T ga;
int filecount;
char **files;
- char *p1, *p2;
char *s;
TriState utf8 = kNone;
bool mix = false; // detected mixed encodings
@@ -979,9 +971,9 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
}
in_example = false;
}
- p1 = vim_strchr(IObuff, '*'); // find first '*'
+ char *p1 = vim_strchr(IObuff, '*'); // find first '*'
while (p1 != NULL) {
- p2 = strchr((const char *)p1 + 1, '*'); // Find second '*'.
+ char *p2 = strchr(p1 + 1, '*'); // Find second '*'.
if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**".
for (s = p1 + 1; s < p2; s++) {
if (*s == ' ' || *s == '\t' || *s == '|') {
@@ -1028,14 +1020,14 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
// Check for duplicates.
for (int i = 1; i < ga.ga_len; i++) {
- p1 = ((char **)ga.ga_data)[i - 1];
- p2 = ((char **)ga.ga_data)[i];
+ char *p1 = ((char **)ga.ga_data)[i - 1];
+ char *p2 = ((char **)ga.ga_data)[i];
while (*p1 == *p2) {
if (*p2 == '\t') {
*p2 = NUL;
vim_snprintf(NameBuff, MAXPATHL,
_("E154: Duplicate tag \"%s\" in file %s/%s"),
- ((char_u **)ga.ga_data)[i], dir, p2 + 1);
+ ((char **)ga.ga_data)[i], dir, p2 + 1);
emsg(NameBuff);
*p2 = '\t';
break;
@@ -1057,7 +1049,7 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
fputs(s, fd_tags);
} else {
fprintf(fd_tags, "%s\t/" "*", s);
- for (p1 = s; *p1 != '\t'; p1++) {
+ for (char *p1 = s; *p1 != '\t'; p1++) {
// insert backslash before '\\' and '/'
if (*p1 == '\\' || *p1 == '/') {
putc('\\', fd_tags);
@@ -1080,7 +1072,6 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
FUNC_ATTR_NONNULL_ALL
{
- int len;
garray_T ga;
char lang[2];
char ext[5];
@@ -1090,7 +1081,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
// Get a list of all files in the help directory and in subdirectories.
xstrlcpy(NameBuff, dirname, sizeof(NameBuff));
- if (!add_pathsep((char *)NameBuff)
+ if (!add_pathsep(NameBuff)
|| xstrlcat(NameBuff, "**", sizeof(NameBuff)) >= MAXPATHL) {
emsg(_(e_fnametoolong));
return;
@@ -1111,7 +1102,7 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
int j;
ga_init(&ga, 1, 10);
for (int i = 0; i < filecount; i++) {
- len = (int)strlen(files[i]);
+ int len = (int)strlen(files[i]);
if (len <= 4) {
continue;
}
@@ -1160,24 +1151,30 @@ static void do_helptags(char *dirname, bool add_help_tags, bool ignore_writeerr)
ext[1] = fname[5];
ext[2] = fname[6];
}
- helptags_one(dirname, (char *)ext, (char *)fname, add_help_tags, ignore_writeerr);
+ helptags_one(dirname, ext, fname, add_help_tags, ignore_writeerr);
}
ga_clear(&ga);
FreeWild(filecount, files);
}
-static void helptags_cb(char *fname, void *cookie)
+static bool helptags_cb(int num_fnames, char **fnames, bool all, void *cookie)
FUNC_ATTR_NONNULL_ALL
{
- do_helptags(fname, *(bool *)cookie, true);
+ for (int i = 0; i < num_fnames; i++) {
+ do_helptags(fnames[i], *(bool *)cookie, true);
+ if (!all) {
+ return true;
+ }
+ }
+
+ return num_fnames > 0;
}
/// ":helptags"
void ex_helptags(exarg_T *eap)
{
expand_T xpc;
- char *dirname;
bool add_help_tags = false;
// Check for ":helptags ++t {dir}".
@@ -1187,11 +1184,12 @@ void ex_helptags(exarg_T *eap)
}
if (strcmp(eap->arg, "ALL") == 0) {
- do_in_path(p_rtp, "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
+ do_in_path(p_rtp, "", "doc", DIP_ALL + DIP_DIR, helptags_cb, &add_help_tags);
} else {
ExpandInit(&xpc);
xpc.xp_context = EXPAND_DIRECTORIES;
- dirname = ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
+ char *dirname =
+ ExpandOne(&xpc, eap->arg, NULL, WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE);
if (dirname == NULL || !os_isdir(dirname)) {
semsg(_("E150: Not a directory: %s"), eap->arg);
} else {
diff --git a/src/nvim/help.h b/src/nvim/help.h
index 21e11392ee..f60f14c748 100644
--- a/src/nvim/help.h
+++ b/src/nvim/help.h
@@ -1,11 +1,7 @@
-#ifndef NVIM_HELP_H
-#define NVIM_HELP_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "help.h.generated.h"
#endif
-#endif // NVIM_HELP_H
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index c20eac3c28..141761c52e 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -1,17 +1,15 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// highlight.c: low level code for UI and syntax highlighting
#include <assert.h>
#include <inttypes.h>
-#include <limits.h>
+#include <lauxlib.h>
#include <string.h>
-#include "klib/kvec.h"
-#include "lauxlib.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
@@ -20,17 +18,17 @@
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
-#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
#include "nvim/popupmenu.h"
-#include "nvim/types.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight.c.generated.h"
@@ -38,23 +36,23 @@
static bool hlstate_active = false;
-static kvec_t(HlEntry) attr_entries = KV_INITIAL_VALUE;
-
-static Map(HlEntry, int) attr_entry_ids = MAP_INIT;
+static Set(HlEntry) attr_entries = SET_INIT;
static Map(int, int) combine_attr_entries = MAP_INIT;
static Map(int, int) blend_attr_entries = MAP_INIT;
static Map(int, int) blendthrough_attr_entries = MAP_INIT;
+#define attr_entry(i) attr_entries.keys[i]
+
/// highlight entries private to a namespace
static Map(ColorKey, ColorItem) ns_hls;
typedef int NSHlAttr[HLF_COUNT + 1];
-static PMap(handle_T) ns_hl_attr;
+static PMap(int) ns_hl_attr;
void highlight_init(void)
{
// index 0 is no attribute, add dummy entry:
- kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown,
- .id1 = 0, .id2 = 0 }));
+ set_put(HlEntry, &attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlInvalid,
+ .id1 = 0, .id2 = 0 }));
}
/// @return true if hl table was reset
@@ -75,6 +73,7 @@ bool highlight_use_hlstate(void)
/// @return 0 for error.
static int get_attr_entry(HlEntry entry)
{
+ bool retried = false;
if (!hlstate_active) {
// This information will not be used, erase it and reduce the table size.
entry.kind = kHlUnknown;
@@ -82,17 +81,19 @@ static int get_attr_entry(HlEntry entry)
entry.id2 = 0;
}
- int id = map_get(HlEntry, int)(&attr_entry_ids, entry);
- if (id > 0) {
- return id;
+retry: {}
+ MHPutStatus status;
+ uint32_t k = set_put_idx(HlEntry, &attr_entries, entry, &status);
+ if (status == kMHExisting) {
+ return (int)k;
}
static bool recursive = false;
- if (kv_size(attr_entries) > MAX_TYPENR) {
+ if (set_size(&attr_entries) > MAX_TYPENR) {
// Running out of attribute entries! remove all attributes, and
// compute new ones for all groups.
// When called recursively, we are really out of numbers.
- if (recursive) {
+ if (recursive || retried) {
emsg(_("E424: Too many different highlighting attributes in use"));
return 0;
}
@@ -105,17 +106,12 @@ static int get_attr_entry(HlEntry entry)
// This entry is now invalid, don't put it
return 0;
}
+ retried = true;
+ goto retry;
}
- size_t next_id = kv_size(attr_entries);
- if (next_id > INT_MAX) {
- ELOG("The index on attr_entries has overflowed");
- return 0;
- }
- id = (int)next_id;
- kv_push(attr_entries, entry);
-
- map_put(HlEntry, int)(&attr_entry_ids, entry, id);
+ // new attr id, send event to remote ui:s
+ int id = (int)k;
Array inspect = hl_inspect(id);
@@ -129,10 +125,10 @@ static int get_attr_entry(HlEntry entry)
/// When a UI connects, we need to send it the table of highlights used so far.
void ui_send_all_hls(UI *ui)
{
- for (size_t i = 1; i < kv_size(attr_entries); i++) {
+ for (size_t i = 1; i < set_size(&attr_entries); i++) {
Array inspect = hl_inspect((int)i);
- remote_ui_hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr,
- kv_A(attr_entries, i).attr, inspect);
+ HlAttrs attr = attr_entry(i).attr;
+ remote_ui_hl_attr_define(ui, (Integer)i, attr, attr, inspect);
api_free_array(inspect);
}
for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) {
@@ -165,7 +161,7 @@ void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight)
return;
}
if ((attrs.rgb_ae_attr & HL_DEFAULT)
- && map_has(ColorKey, ColorItem)(&ns_hls, ColorKey(ns_id, hl_id))) {
+ && map_has(ColorKey, &ns_hls, (ColorKey(ns_id, hl_id)))) {
return;
}
DecorProvider *p = get_decor_provider(ns_id, true);
@@ -205,7 +201,7 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
if (!valid_item && p->hl_def != LUA_NOREF && !recursive) {
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ((Integer)ns_id));
- ADD_C(args, STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id))));
+ ADD_C(args, CSTR_TO_OBJ(syn_id2name(hl_id)));
ADD_C(args, BOOLEAN_OBJ(link));
// TODO(bfredl): preload the "global" attr dict?
@@ -224,8 +220,8 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
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);
+ fallback = GET_BOOL_OR_TRUE(&dict, highlight, fallback);
+ tmp = dict.fallback; // or false
if (it.link_id >= 0) {
fallback = true;
}
@@ -275,7 +271,7 @@ bool hl_check_ns(void)
hl_attr_active = highlight_attr;
if (ns > 0) {
update_ns_hl(ns);
- NSHlAttr *hl_def = (NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns);
+ NSHlAttr *hl_def = (NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns);
if (hl_def) {
hl_attr_active = *hl_def;
}
@@ -302,7 +298,7 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
bool available = false;
if (final_id > 0) {
- int syn_attr = syn_ns_id2attr(ns_id, final_id, optional);
+ int syn_attr = syn_ns_id2attr(ns_id, final_id, &optional);
if (syn_attr > 0) {
attrs = syn_attr2entry(syn_attr);
available = true;
@@ -325,6 +321,23 @@ int hl_get_ui_attr(int ns_id, int idx, int final_id, bool optional)
.id1 = idx, .id2 = final_id });
}
+/// Apply 'winblend' to highlight attributes.
+///
+/// @param wp The window to get 'winblend' value from.
+/// @param attr The original attribute code.
+///
+/// @return The attribute code with 'winblend' applied.
+int hl_apply_winblend(win_T *wp, int attr)
+{
+ HlEntry entry = attr_entry(attr);
+ // if blend= attribute is not set, 'winblend' value overrides it.
+ if (entry.attr.hl_blend == -1 && wp->w_p_winbl > 0) {
+ entry.attr.hl_blend = (int)wp->w_p_winbl;
+ attr = get_attr_entry(entry);
+ }
+ return attr;
+}
+
void update_window_hl(win_T *wp, bool invalid)
{
int ns_id = wp->w_ns_hl;
@@ -333,7 +346,7 @@ void update_window_hl(win_T *wp, bool invalid)
if (ns_id != wp->w_ns_hl_active || wp->w_ns_hl_attr == NULL) {
wp->w_ns_hl_active = ns_id;
- wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(handle_T)(&ns_hl_attr, ns_id);
+ wp->w_ns_hl_attr = *(NSHlAttr *)pmap_get(int)(&ns_hl_attr, ns_id);
if (!wp->w_ns_hl_attr) {
// No specific highlights, use the defaults.
wp->w_ns_hl_attr = highlight_attr;
@@ -360,13 +373,8 @@ void update_window_hl(win_T *wp, bool invalid)
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
}
- // if blend= attribute is not set, 'winblend' value overrides it.
- if (wp->w_floating && wp->w_p_winbl > 0) {
- HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
- if (entry.attr.hl_blend == -1) {
- entry.attr.hl_blend = (int)wp->w_p_winbl;
- wp->w_hl_attr_normal = get_attr_entry(entry);
- }
+ if (wp->w_floating) {
+ wp->w_hl_attr_normal = hl_apply_winblend(wp, wp->w_hl_attr_normal);
}
wp->w_float_config.shadow = false;
@@ -376,10 +384,10 @@ void update_window_hl(win_T *wp, bool invalid)
if (wp->w_float_config.border_hl_ids[i]) {
attr = hl_get_ui_attr(ns_id, HLF_BORDER,
wp->w_float_config.border_hl_ids[i], false);
- HlAttrs a = syn_attr2entry(attr);
- if (a.hl_blend) {
- wp->w_float_config.shadow = true;
- }
+ }
+ attr = hl_apply_winblend(wp, attr);
+ if (syn_attr2entry(attr).hl_blend > 0) {
+ wp->w_float_config.shadow = true;
}
wp->w_float_config.border_attr[i] = attr;
}
@@ -396,6 +404,10 @@ void update_window_hl(win_T *wp, bool invalid)
} else {
wp->w_hl_attr_normalnc = hl_def[HLF_INACTIVE];
}
+
+ if (wp->w_floating) {
+ wp->w_hl_attr_normalnc = hl_apply_winblend(wp, wp->w_hl_attr_normalnc);
+ }
}
void update_ns_hl(int ns_id)
@@ -408,7 +420,7 @@ void update_ns_hl(int ns_id)
return;
}
- NSHlAttr **alloc = (NSHlAttr **)pmap_ref(handle_T)(&ns_hl_attr, ns_id, true);
+ NSHlAttr **alloc = (NSHlAttr **)pmap_put_ref(int)(&ns_hl_attr, ns_id, NULL, NULL);
if (*alloc == NULL) {
*alloc = xmalloc(sizeof(**alloc));
}
@@ -471,7 +483,7 @@ int hl_get_underline(void)
/// Get attribute code for forwarded :terminal highlights.
int hl_get_term_attr(HlAttrs *aep)
{
- return get_attr_entry((HlEntry){ .attr= *aep, .kind = kHlTerminal,
+ return get_attr_entry((HlEntry){ .attr = *aep, .kind = kHlTerminal,
.id1 = 0, .id2 = 0 });
}
@@ -479,33 +491,42 @@ int hl_get_term_attr(HlAttrs *aep)
void clear_hl_tables(bool reinit)
{
if (reinit) {
- kv_size(attr_entries) = 1;
- map_clear(HlEntry, int)(&attr_entry_ids);
- map_clear(int, int)(&combine_attr_entries);
- map_clear(int, int)(&blend_attr_entries);
- map_clear(int, int)(&blendthrough_attr_entries);
+ set_clear(HlEntry, &attr_entries);
+ highlight_init();
+ map_clear(int, &combine_attr_entries);
+ map_clear(int, &blend_attr_entries);
+ map_clear(int, &blendthrough_attr_entries);
memset(highlight_attr_last, -1, sizeof(highlight_attr_last));
highlight_attr_set_all();
highlight_changed();
screen_invalidate_highlights();
} else {
- kv_destroy(attr_entries);
- map_destroy(HlEntry, int)(&attr_entry_ids);
- map_destroy(int, int)(&combine_attr_entries);
- map_destroy(int, int)(&blend_attr_entries);
- map_destroy(int, int)(&blendthrough_attr_entries);
- map_destroy(ColorKey, ColorItem)(&ns_hls);
+ set_destroy(HlEntry, &attr_entries);
+ map_destroy(int, &combine_attr_entries);
+ map_destroy(int, &blend_attr_entries);
+ map_destroy(int, &blendthrough_attr_entries);
+ map_destroy(ColorKey, &ns_hls);
}
}
void hl_invalidate_blends(void)
{
- map_clear(int, int)(&blend_attr_entries);
- map_clear(int, int)(&blendthrough_attr_entries);
+ map_clear(int, &blend_attr_entries);
+ map_clear(int, &blendthrough_attr_entries);
highlight_changed();
update_window_hl(curwin, true);
}
+/// Combine HlAttrFlags.
+/// The underline attribute in "prim_ae" overrules the one in "char_ae" if both are present.
+static int16_t hl_combine_ae(int16_t char_ae, int16_t prim_ae)
+{
+ int16_t char_ul = char_ae & HL_UNDERLINE_MASK;
+ int16_t prim_ul = prim_ae & HL_UNDERLINE_MASK;
+ int16_t new_ul = prim_ul ? prim_ul : char_ul;
+ return (char_ae & ~HL_UNDERLINE_MASK) | (prim_ae & ~HL_UNDERLINE_MASK) | new_ul;
+}
+
// Combine special attributes (e.g., for spelling) with other attributes
// (e.g., for syntax highlighting).
// "prim_attr" overrules "char_attr".
@@ -536,12 +557,12 @@ int hl_combine_attr(int char_attr, int prim_attr)
if (prim_aep.cterm_ae_attr & HL_NOCOMBINE) {
new_en.cterm_ae_attr = prim_aep.cterm_ae_attr;
} else {
- new_en.cterm_ae_attr |= prim_aep.cterm_ae_attr;
+ new_en.cterm_ae_attr = hl_combine_ae(new_en.cterm_ae_attr, prim_aep.cterm_ae_attr);
}
if (prim_aep.rgb_ae_attr & HL_NOCOMBINE) {
new_en.rgb_ae_attr = prim_aep.rgb_ae_attr;
} else {
- new_en.rgb_ae_attr |= prim_aep.rgb_ae_attr;
+ new_en.rgb_ae_attr = hl_combine_ae(new_en.rgb_ae_attr, prim_aep.rgb_ae_attr);
}
if (prim_aep.cterm_fg_color > 0) {
@@ -663,7 +684,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through)
} else {
cattrs = fattrs;
if (ratio >= 50) {
- cattrs.rgb_ae_attr |= battrs.rgb_ae_attr;
+ cattrs.rgb_ae_attr = hl_combine_ae(battrs.rgb_ae_attr, cattrs.rgb_ae_attr);
}
cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color,
fattrs.rgb_fg_color);
@@ -788,11 +809,11 @@ static int hl_cterm2rgb_color(int nr)
/// Get highlight attributes for a attribute code
HlAttrs syn_attr2entry(int attr)
{
- if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
+ if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
// invalid attribute code, or the tables were cleared
return HLATTRS_INIT;
}
- return kv_A(attr_entries, attr).attr;
+ return attr_entry(attr).attr;
}
/// Gets highlight description for id `attr_id` as a map.
@@ -804,13 +825,13 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *
return dic;
}
- if (attr_id <= 0 || attr_id >= (int)kv_size(attr_entries)) {
+ if (attr_id <= 0 || attr_id >= (int)set_size(&attr_entries)) {
api_set_error(err, kErrorTypeException,
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
Dictionary retval = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&retval, syn_attr2entry((int)attr_id), rgb);
+ hlattrs2dict(&retval, NULL, syn_attr2entry((int)attr_id), rgb, false);
return retval;
}
@@ -819,101 +840,105 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *
/// @param[in/out] hl Dictionary with pre-allocated space for HLATTRS_DICT_SIZE elements
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
-void hlattrs2dict(Dictionary *dict, HlAttrs ae, bool use_rgb)
+/// @param short_keys change (foreground, background, special) to (fg, bg, sp) for 'gui*' settings
+/// (foreground, background) to (ctermfg, ctermbg) for 'cterm*' settings
+void hlattrs2dict(Dictionary *hl, Dictionary *hl_attrs, HlAttrs ae, bool use_rgb, bool short_keys)
{
- assert(dict->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
- Dictionary hl = *dict;
- int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
+ hl_attrs = hl_attrs ? hl_attrs : hl;
+ assert(hl->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
+ assert(hl_attrs->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
+ int mask = use_rgb ? ae.rgb_ae_attr : ae.cterm_ae_attr;
if (mask & HL_INVERSE) {
- PUT_C(hl, "reverse", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "reverse", BOOLEAN_OBJ(true));
}
if (mask & HL_BOLD) {
- PUT_C(hl, "bold", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "bold", BOOLEAN_OBJ(true));
}
if (mask & HL_ITALIC) {
- PUT_C(hl, "italic", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "italic", BOOLEAN_OBJ(true));
}
switch (mask & HL_UNDERLINE_MASK) {
case HL_UNDERLINE:
- PUT_C(hl, "underline", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underline", BOOLEAN_OBJ(true));
break;
case HL_UNDERCURL:
- PUT_C(hl, "undercurl", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "undercurl", BOOLEAN_OBJ(true));
break;
case HL_UNDERDOUBLE:
- PUT_C(hl, "underdouble", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underdouble", BOOLEAN_OBJ(true));
break;
case HL_UNDERDOTTED:
- PUT_C(hl, "underdotted", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underdotted", BOOLEAN_OBJ(true));
break;
case HL_UNDERDASHED:
- PUT_C(hl, "underdashed", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "underdashed", BOOLEAN_OBJ(true));
break;
}
if (mask & HL_STANDOUT) {
- PUT_C(hl, "standout", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "standout", BOOLEAN_OBJ(true));
}
if (mask & HL_STRIKETHROUGH) {
- PUT_C(hl, "strikethrough", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "strikethrough", BOOLEAN_OBJ(true));
}
if (mask & HL_ALTFONT) {
- PUT_C(hl, "altfont", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "altfont", BOOLEAN_OBJ(true));
}
if (mask & HL_NOCOMBINE) {
- PUT_C(hl, "nocombine", BOOLEAN_OBJ(true));
+ PUT_C(*hl_attrs, "nocombine", BOOLEAN_OBJ(true));
}
if (use_rgb) {
- if (mask & HL_FG_INDEXED) {
- PUT_C(hl, "fg_indexed", BOOLEAN_OBJ(true));
- }
-
- if (mask & HL_BG_INDEXED) {
- PUT_C(hl, "bg_indexed", BOOLEAN_OBJ(true));
- }
-
if (ae.rgb_fg_color != -1) {
- PUT_C(hl, "foreground", INTEGER_OBJ(ae.rgb_fg_color));
+ PUT_C(*hl, short_keys ? "fg" : "foreground", INTEGER_OBJ(ae.rgb_fg_color));
}
if (ae.rgb_bg_color != -1) {
- PUT_C(hl, "background", INTEGER_OBJ(ae.rgb_bg_color));
+ PUT_C(*hl, short_keys ? "bg" : "background", INTEGER_OBJ(ae.rgb_bg_color));
}
if (ae.rgb_sp_color != -1) {
- PUT_C(hl, "special", INTEGER_OBJ(ae.rgb_sp_color));
+ PUT_C(*hl, short_keys ? "sp" : "special", INTEGER_OBJ(ae.rgb_sp_color));
+ }
+
+ if (!short_keys) {
+ if (mask & HL_FG_INDEXED) {
+ PUT_C(*hl, "fg_indexed", BOOLEAN_OBJ(true));
+ }
+
+ if (mask & HL_BG_INDEXED) {
+ PUT_C(*hl, "bg_indexed", BOOLEAN_OBJ(true));
+ }
}
} else {
if (ae.cterm_fg_color != 0) {
- PUT_C(hl, "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
+ PUT_C(*hl, short_keys ? "ctermfg" : "foreground", INTEGER_OBJ(ae.cterm_fg_color - 1));
}
if (ae.cterm_bg_color != 0) {
- PUT_C(hl, "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
+ PUT_C(*hl, short_keys ? "ctermbg" : "background", INTEGER_OBJ(ae.cterm_bg_color - 1));
}
}
- if (ae.hl_blend > -1) {
- PUT_C(hl, "blend", INTEGER_OBJ(ae.hl_blend));
+ if (ae.hl_blend > -1 && (use_rgb || !short_keys)) {
+ PUT_C(*hl, "blend", INTEGER_OBJ(ae.hl_blend));
}
-
- *dict = hl;
}
HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err)
{
+#define HAS_KEY_X(d, key) HAS_KEY(d, highlight, key)
HlAttrs hlattrs = HLATTRS_INIT;
int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1;
int blend = -1;
@@ -922,8 +947,11 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
bool cterm_mask_provided = false;
#define CHECK_FLAG(d, m, name, extra, flag) \
- if (api_object_to_bool(d->name##extra, #name, false, err)) { \
- m = m | flag; \
+ if (d->name##extra) { \
+ if (flag & HL_UNDERLINE_MASK) { \
+ m &= ~HL_UNDERLINE_MASK; \
+ } \
+ m |= flag; \
}
CHECK_FLAG(dict, mask, reverse, , HL_INVERSE);
@@ -944,62 +972,56 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
CHECK_FLAG(dict, mask, nocombine, , HL_NOCOMBINE);
CHECK_FLAG(dict, mask, default, _, HL_DEFAULT);
- if (HAS_KEY(dict->fg)) {
+ if (HAS_KEY_X(dict, fg)) {
fg = object_to_color(dict->fg, "fg", use_rgb, err);
- } else if (HAS_KEY(dict->foreground)) {
+ } else if (HAS_KEY_X(dict, foreground)) {
fg = object_to_color(dict->foreground, "foreground", use_rgb, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (HAS_KEY(dict->bg)) {
+ if (HAS_KEY_X(dict, bg)) {
bg = object_to_color(dict->bg, "bg", use_rgb, err);
- } else if (HAS_KEY(dict->background)) {
+ } else if (HAS_KEY_X(dict, background)) {
bg = object_to_color(dict->background, "background", use_rgb, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (HAS_KEY(dict->sp)) {
+ if (HAS_KEY_X(dict, sp)) {
sp = object_to_color(dict->sp, "sp", true, err);
- } else if (HAS_KEY(dict->special)) {
+ } else if (HAS_KEY_X(dict, special)) {
sp = object_to_color(dict->special, "special", true, err);
}
if (ERROR_SET(err)) {
return hlattrs;
}
- if (dict->blend.type == kObjectTypeInteger) {
- Integer blend0 = dict->blend.data.integer;
- if (blend0 < 0 || blend0 > 100) {
- api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100");
- } else {
- blend = (int)blend0;
- }
- } else if (HAS_KEY(dict->blend)) {
- api_set_error(err, kErrorTypeValidation, "'blend' must be an integer");
- }
- if (ERROR_SET(err)) {
- return hlattrs;
+ if (HAS_KEY_X(dict, blend)) {
+ Integer blend0 = dict->blend;
+ VALIDATE_RANGE((blend0 >= 0 && blend0 <= 100), "blend", {
+ return hlattrs;
+ });
+ blend = (int)blend0;
}
- if (HAS_KEY(dict->link) || HAS_KEY(dict->global_link)) {
- if (link_id) {
- if (HAS_KEY(dict->global_link)) {
- *link_id = object_to_hl_id(dict->global_link, "link", err);
- mask |= HL_GLOBAL;
- } else {
- *link_id = object_to_hl_id(dict->link, "link", err);
- }
-
- if (ERROR_SET(err)) {
- return hlattrs;
- }
- } else {
+ if (HAS_KEY_X(dict, link) || HAS_KEY_X(dict, global_link)) {
+ if (!link_id) {
api_set_error(err, kErrorTypeValidation, "Invalid Key: '%s'",
- HAS_KEY(dict->global_link) ? "global_link" : "link");
+ HAS_KEY_X(dict, global_link) ? "global_link" : "link");
+ return hlattrs;
+ }
+ if (HAS_KEY_X(dict, global_link)) {
+ *link_id = object_to_hl_id(dict->global_link, "link", err);
+ mask |= HL_GLOBAL;
+ } else {
+ *link_id = object_to_hl_id(dict->link, "link", err);
+ }
+
+ if (ERROR_SET(err)) {
+ return hlattrs;
}
}
@@ -1025,19 +1047,21 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
// empty list from Lua API should clear all cterm attributes
// TODO(clason): handle via gen_api_dispatch
cterm_mask_provided = true;
- } else if (HAS_KEY(dict->cterm)) {
- api_set_error(err, kErrorTypeValidation, "'cterm' must be a Dictionary.");
+ } else if (HAS_KEY_X(dict, cterm)) {
+ VALIDATE_EXP(false, "cterm", "Dict", api_typename(dict->cterm.type), {
+ return hlattrs;
+ });
}
#undef CHECK_FLAG
- if (HAS_KEY(dict->ctermfg)) {
+ if (HAS_KEY_X(dict, ctermfg)) {
ctermfg = object_to_color(dict->ctermfg, "ctermfg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
}
}
- if (HAS_KEY(dict->ctermbg)) {
+ if (HAS_KEY_X(dict, ctermbg)) {
ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err);
if (ERROR_SET(err)) {
return hlattrs;
@@ -1064,6 +1088,7 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
}
return hlattrs;
+#undef HAS_KEY_X
}
int object_to_color(Object val, char *key, bool rgb, Error *err)
@@ -1083,13 +1108,14 @@ int object_to_color(Object val, char *key, bool rgb, Error *err)
} else {
color = name_to_ctermcolor(str.data);
}
- if (color < 0) {
- api_set_error(err, kErrorTypeValidation, "'%s' is not a valid color", str.data);
- }
+ VALIDATE_S((color >= 0), "highlight color", str.data, {
+ return color;
+ });
return color;
} else {
- api_set_error(err, kErrorTypeValidation, "'%s' must be string or integer", key);
- return 0;
+ VALIDATE_EXP(false, key, "String or Integer", NULL, {
+ return 0;
+ });
}
}
@@ -1106,28 +1132,28 @@ Array hl_inspect(int attr)
static void hl_inspect_impl(Array *arr, int attr)
{
Dictionary item = ARRAY_DICT_INIT;
- if (attr <= 0 || attr >= (int)kv_size(attr_entries)) {
+ if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
return;
}
- HlEntry e = kv_A(attr_entries, attr);
+ HlEntry e = attr_entry(attr);
switch (e.kind) {
case kHlSyntax:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("syntax")));
+ PUT(item, "kind", CSTR_TO_OBJ("syntax"));
PUT(item, "hi_name",
- STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id1))));
+ CSTR_TO_OBJ(syn_id2name(e.id1)));
break;
case kHlUI:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("ui")));
+ PUT(item, "kind", CSTR_TO_OBJ("ui"));
const char *ui_name = (e.id1 == -1) ? "Normal" : hlf_names[e.id1];
- PUT(item, "ui_name", STRING_OBJ(cstr_to_string(ui_name)));
+ PUT(item, "ui_name", CSTR_TO_OBJ(ui_name));
PUT(item, "hi_name",
- STRING_OBJ(cstr_to_string((char *)syn_id2name(e.id2))));
+ CSTR_TO_OBJ(syn_id2name(e.id2)));
break;
case kHlTerminal:
- PUT(item, "kind", STRING_OBJ(cstr_to_string("term")));
+ PUT(item, "kind", CSTR_TO_OBJ("term"));
break;
case kHlCombine:
@@ -1139,6 +1165,7 @@ static void hl_inspect_impl(Array *arr, int attr)
return;
case kHlUnknown:
+ case kHlInvalid:
return;
}
PUT(item, "id", INTEGER_OBJ(attr));
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index 4da7dd65bb..e5d3f3d1ca 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_HIGHLIGHT_H
-#define NVIM_HIGHLIGHT_H
+#pragma once
#include <stdbool.h>
-#include "nvim/api/private/defs.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/option_defs.h"
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: export
+#include "nvim/option_vars.h"
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -30,4 +30,5 @@ static inline int win_hl_attr(win_T *wp, int hlf)
rgb_sp = rgb_sp != -1 ? rgb_sp : 0xFF0000; \
} while (0);
-#endif // NVIM_HIGHLIGHT_H
+// Enums need a typecast to be used as array index.
+#define HL_ATTR(n) hl_attr_active[(int)(n)]
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 95c81ac9db..24070199ee 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_HIGHLIGHT_DEFS_H
-#define NVIM_HIGHLIGHT_DEFS_H
+#pragma once
#include <inttypes.h>
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
typedef int32_t RgbValue;
@@ -100,6 +99,10 @@ typedef enum {
HLF_SPL, // SpellLocal
HLF_PNI, // popup menu normal item
HLF_PSI, // popup menu selected item
+ HLF_PNK, // popup menu normal item "kind"
+ HLF_PSK, // popup menu selected item "kind"
+ HLF_PNX, // popup menu normal item "menu" (extra text)
+ HLF_PSX, // popup menu selected item "menu" (extra text)
HLF_PSB, // popup menu scrollbar
HLF_PST, // popup menu scrollbar thumb
HLF_TP, // tabpage line
@@ -119,10 +122,11 @@ typedef enum {
HLF_WBRNC, // Window bars of not-current windows
HLF_CU, // Cursor
HLF_BTITLE, // Float Border Title
+ HLF_BFOOTER, // Float Border Footer
HLF_COUNT, // MUST be the last one
} hlf_T;
-EXTERN const char *hlf_names[] INIT(= {
+EXTERN const char *hlf_names[] INIT( = {
[HLF_8] = "SpecialKey",
[HLF_EOB] = "EndOfBuffer",
[HLF_TERM] = "TermCursor",
@@ -165,6 +169,10 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_SPL] = "SpellLocal",
[HLF_PNI] = "Pmenu",
[HLF_PSI] = "PmenuSel",
+ [HLF_PNK] = "PmenuKind",
+ [HLF_PSK] = "PmenuKindSel",
+ [HLF_PNX] = "PmenuExtra",
+ [HLF_PSX] = "PmenuExtraSel",
[HLF_PSB] = "PmenuSbar",
[HLF_PST] = "PmenuThumb",
[HLF_TP] = "TabLine",
@@ -184,24 +192,25 @@ EXTERN const char *hlf_names[] INIT(= {
[HLF_WBRNC] = "WinBarNC",
[HLF_CU] = "Cursor",
[HLF_BTITLE] = "FloatTitle",
+ [HLF_BFOOTER] = "FloatFooter",
});
EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context.
EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups
EXTERN int highlight_user[9]; // User[1-9] attributes
EXTERN int highlight_stlnc[9]; // On top of user
-EXTERN int cterm_normal_fg_color INIT(= 0);
-EXTERN int cterm_normal_bg_color INIT(= 0);
-EXTERN RgbValue normal_fg INIT(= -1);
-EXTERN RgbValue normal_bg INIT(= -1);
-EXTERN RgbValue normal_sp INIT(= -1);
+EXTERN int cterm_normal_fg_color INIT( = 0);
+EXTERN int cterm_normal_bg_color INIT( = 0);
+EXTERN RgbValue normal_fg INIT( = -1);
+EXTERN RgbValue normal_bg INIT( = -1);
+EXTERN RgbValue normal_sp INIT( = -1);
-EXTERN NS ns_hl_global INIT(= 0); // global highlight namespace
-EXTERN NS ns_hl_win INIT(= -1); // highlight namespace for the current window
-EXTERN NS ns_hl_fast INIT(= -1); // highlight namespace specified in a fast callback
-EXTERN NS ns_hl_active INIT(= 0); // currently active/cached namespace
+EXTERN NS ns_hl_global INIT( = 0); // global highlight namespace
+EXTERN NS ns_hl_win INIT( = -1); // highlight namespace for the current window
+EXTERN NS ns_hl_fast INIT( = -1); // highlight namespace specified in a fast callback
+EXTERN NS ns_hl_active INIT( = 0); // currently active/cached namespace
-EXTERN int *hl_attr_active INIT(= highlight_attr);
+EXTERN int *hl_attr_active INIT( = highlight_attr);
typedef enum {
kHlUnknown,
@@ -211,6 +220,7 @@ typedef enum {
kHlCombine,
kHlBlend,
kHlBlendThrough,
+ kHlInvalid,
} HlKind;
typedef struct {
@@ -236,11 +246,3 @@ typedef struct {
} ColorItem;
#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1, \
.is_default = false, .link_global = false }
-
-/// highlight attributes with associated priorities
-typedef struct {
- int attr_id;
- int priority;
-} HlPriAttr;
-
-#endif // NVIM_HIGHLIGHT_DEFS_H
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index 3d91335f55..3f1758894e 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// highlight_group.c: code for managing highlight groups
#include <ctype.h>
@@ -10,38 +7,42 @@
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
-#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// \addtogroup SG_SET
/// @{
@@ -118,6 +119,17 @@ enum {
# include "highlight_group.c.generated.h"
#endif
+static const char e_highlight_group_name_not_found_str[]
+ = N_("E411: Highlight group not found: %s");
+static const char e_group_has_settings_highlight_link_ignored[]
+ = N_("E414: Group has settings, highlight link ignored");
+static const char e_unexpected_equal_sign_str[]
+ = N_("E415: Unexpected equal sign: %s");
+static const char e_missing_equal_sign_str_2[]
+ = N_("E416: Missing equal sign: %s");
+static const char e_missing_argument_str[]
+ = N_("E417: Missing argument: %s");
+
#define hl_table ((HlGroup *)((highlight_ga.ga_data)))
// The default highlight groups. These are compiled-in for fast startup and
@@ -150,12 +162,18 @@ static const char *highlight_init_both[] = {
"default link QuickFixLine Search",
"default link CursorLineSign SignColumn",
"default link CursorLineFold FoldColumn",
+ "default link CurSearch Search",
+ "default link PmenuKind Pmenu",
+ "default link PmenuKindSel PmenuSel",
+ "default link PmenuExtra Pmenu",
+ "default link PmenuExtraSel PmenuSel",
"default link Substitute Search",
"default link Whitespace NonText",
"default link MsgSeparator StatusLine",
"default link NormalFloat Pmenu",
"default link FloatBorder WinSeparator",
"default link FloatTitle Title",
+ "default link FloatFooter Title",
"default FloatShadow blend=80 guibg=Black",
"default FloatShadowThrough blend=100 guibg=Black",
"RedrawDebugNormal cterm=reverse gui=reverse",
@@ -213,6 +231,10 @@ static const char *highlight_init_both[] = {
"default link DiagnosticSignInfo DiagnosticInfo",
"default link DiagnosticSignHint DiagnosticHint",
"default link DiagnosticSignOk DiagnosticOk",
+ "default DiagnosticDeprecated cterm=strikethrough gui=strikethrough guisp=Red",
+ "default link DiagnosticUnnecessary Comment",
+ "default link LspInlayHint NonText",
+ "default link SnippetTabstop Visual",
// Text
"default link @text.literal Comment",
@@ -270,16 +292,23 @@ static const char *highlight_init_both[] = {
"default link @tag Tag",
// LSP semantic tokens
- "default link @class Structure",
- "default link @struct Structure",
- "default link @enum Type",
- "default link @enumMember Constant",
- "default link @event Identifier",
- "default link @interface Identifier",
- "default link @modifier Identifier",
- "default link @regexp SpecialChar",
- "default link @typeParameter Type",
- "default link @decorator Identifier",
+ "default link @lsp.type.class Structure",
+ "default link @lsp.type.comment Comment",
+ "default link @lsp.type.decorator Function",
+ "default link @lsp.type.enum Structure",
+ "default link @lsp.type.enumMember Constant",
+ "default link @lsp.type.function Function",
+ "default link @lsp.type.interface Structure",
+ "default link @lsp.type.macro Macro",
+ "default link @lsp.type.method Function",
+ "default link @lsp.type.namespace Structure",
+ "default link @lsp.type.parameter Identifier",
+ "default link @lsp.type.property Identifier",
+ "default link @lsp.type.struct Structure",
+ "default link @lsp.type.type Type",
+ "default link @lsp.type.typeParameter TypeDef",
+ "default link @lsp.type.variable Identifier",
+
NULL
};
@@ -671,12 +700,8 @@ int load_colors(char *name)
size_t buflen = strlen(name) + 12;
char *buf = xmalloc(buflen);
apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf);
- snprintf(buf, buflen, "colors/%s.vim", name);
- int retval = source_runtime(buf, DIP_START + DIP_OPT);
- if (retval == FAIL) {
- snprintf(buf, buflen, "colors/%s.lua", name);
- retval = source_runtime(buf, DIP_START + DIP_OPT);
- }
+ snprintf(buf, buflen, "colors/%s.*", name);
+ int retval = source_runtime_vim_lua(buf, DIP_START + DIP_OPT);
xfree(buf);
if (retval == OK) {
apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf);
@@ -775,18 +800,17 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp)
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)) {
+ if (is_default && hl_has_settings(idx, true) && !dict->force) {
return;
}
HlGroup *g = &hl_table[idx];
+ g->sg_cleared = false;
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;
@@ -796,12 +820,11 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
g->sg_deflink_sctx = current_sctx;
g->sg_deflink_sctx.sc_lnum += SOURCING_LNUM;
}
- goto update;
+ } else {
+ g->sg_link = 0;
}
- g->sg_cleared = false;
- g->sg_link = 0;
- g->sg_gui = attrs.rgb_ae_attr;
+ g->sg_gui = attrs.rgb_ae_attr &~HL_DEFAULT;
g->sg_rgb_fg = attrs.rgb_fg_color;
g->sg_rgb_bg = attrs.rgb_bg_color;
@@ -810,9 +833,11 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
struct {
int *dest; RgbValue val; Object name;
} cattrs[] = {
- { &g->sg_rgb_fg_idx, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground },
- { &g->sg_rgb_bg_idx, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background },
- { &g->sg_rgb_sp_idx, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special },
+ { &g->sg_rgb_fg_idx, g->sg_rgb_fg,
+ HAS_KEY(dict, highlight, fg) ? dict->fg : dict->foreground },
+ { &g->sg_rgb_bg_idx, g->sg_rgb_bg,
+ HAS_KEY(dict, highlight, bg) ? dict->bg : dict->background },
+ { &g->sg_rgb_sp_idx, g->sg_rgb_sp, HAS_KEY(dict, highlight, sp) ? dict->sp : dict->special },
{ NULL, -1, NIL },
};
@@ -826,7 +851,7 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
}
}
- g->sg_cterm = attrs.cterm_ae_attr;
+ g->sg_cterm = attrs.cterm_ae_attr &~HL_DEFAULT;
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;
@@ -841,9 +866,17 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
if (strcmp(g->sg_name_u, "NORMAL") == 0) {
cterm_normal_fg_color = g->sg_cterm_fg;
cterm_normal_bg_color = g->sg_cterm_bg;
+ bool did_changed = false;
+ if (normal_bg != g->sg_rgb_bg || normal_fg != g->sg_rgb_fg || normal_sp != g->sg_rgb_sp) {
+ did_changed = true;
+ }
normal_fg = g->sg_rgb_fg;
normal_bg = g->sg_rgb_bg;
normal_sp = g->sg_rgb_sp;
+
+ if (did_changed) {
+ highlight_attr_set_all();
+ }
ui_default_colors_set();
} else {
// a cursor style uses this syn_id, make sure its attribute is updated.
@@ -852,7 +885,6 @@ void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id)
}
}
-update:
if (!updating_screen) {
redraw_all_later(UPD_NOT_VALID);
}
@@ -883,15 +915,15 @@ void do_highlight(const char *line, const bool forceit, const bool init)
bool dodefault = false;
// Isolate the name.
- const char *name_end = (const char *)skiptowhite(line);
- const char *linep = (const char *)skipwhite(name_end);
+ const char *name_end = skiptowhite(line);
+ const char *linep = skipwhite(name_end);
// Check for "default" argument.
if (strncmp(line, "default", (size_t)(name_end - line)) == 0) {
dodefault = true;
line = linep;
- name_end = (const char *)skiptowhite(line);
- linep = (const char *)skipwhite(name_end);
+ name_end = skiptowhite(line);
+ linep = skipwhite(name_end);
}
bool doclear = false;
@@ -908,7 +940,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) {
int id = syn_name2id_len(line, (size_t)(name_end - line));
if (id == 0) {
- semsg(_("E411: highlight group not found: %s"), line);
+ semsg(_(e_highlight_group_name_not_found_str), line);
} else {
highlight_list_one(id);
}
@@ -925,9 +957,9 @@ void do_highlight(const char *line, const bool forceit, const bool init)
int to_id;
HlGroup *hlgroup = NULL;
- from_end = (const char *)skiptowhite(from_start);
- to_start = (const char *)skipwhite(from_end);
- to_end = (const char *)skiptowhite(to_start);
+ from_end = skiptowhite(from_start);
+ to_start = skipwhite(from_end);
+ to_end = skiptowhite(to_start);
if (ends_excmd((uint8_t)(*from_start))
|| ends_excmd((uint8_t)(*to_start))) {
@@ -964,7 +996,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (to_id > 0 && !forceit && !init
&& hl_has_settings(from_id - 1, dodefault)) {
if (SOURCING_NAME == NULL && !dodefault) {
- emsg(_("E414: group has settings, highlight link ignored"));
+ emsg(_(e_group_has_settings_highlight_link_ignored));
}
} else if (hlgroup->sg_link != to_id
|| hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid
@@ -991,7 +1023,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// ":highlight clear [group]" command.
line = linep;
if (ends_excmd((uint8_t)(*line))) {
- do_unlet(S_LEN("colors_name"), true);
+ do_unlet(S_LEN("g:colors_name"), true);
restore_cterm_colors();
// Clear all default highlight groups and load the defaults.
@@ -1003,8 +1035,8 @@ void do_highlight(const char *line, const bool forceit, const bool init)
redraw_all_later(UPD_NOT_VALID);
return;
}
- name_end = (const char *)skiptowhite(line);
- linep = (const char *)skipwhite(name_end);
+ name_end = skiptowhite(line);
+ linep = skipwhite(name_end);
}
// Find the group name in the table. If it does not exist yet, add it.
@@ -1042,7 +1074,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
while (!ends_excmd((uint8_t)(*linep))) {
const char *key_start = linep;
if (*linep == '=') {
- semsg(_("E415: unexpected equal sign: %s"), key_start);
+ semsg(_(e_unexpected_equal_sign_str), key_start);
error = true;
break;
}
@@ -1061,7 +1093,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
memcpy(key, key_start, key_len);
key[key_len] = NUL;
vim_strup(key);
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
if (strcmp(key, "NONE") == 0) {
if (!init || hl_table[idx].sg_set == 0) {
@@ -1075,14 +1107,14 @@ void do_highlight(const char *line, const bool forceit, const bool init)
// Check for the equal sign.
if (*linep != '=') {
- semsg(_("E416: missing equal sign: %s"), key_start);
+ semsg(_(e_missing_equal_sign_str_2), key_start);
error = true;
break;
}
linep++;
// Isolate the argument.
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
if (*linep == '\'') { // guifg='color name'
arg_start = ++linep;
linep = strchr(linep, '\'');
@@ -1093,10 +1125,10 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
} else {
arg_start = linep;
- linep = (const char *)skiptowhite(linep);
+ linep = skiptowhite(linep);
}
if (linep == arg_start) {
- semsg(_("E417: missing argument: %s"), key_start);
+ semsg(_(e_missing_argument_str), key_start);
error = true;
break;
}
@@ -1124,6 +1156,9 @@ void do_highlight(const char *line, const bool forceit, const bool init)
for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) {
int len = (int)strlen(hl_name_table[i]);
if (STRNICMP(arg + off, hl_name_table[i], len) == 0) {
+ if (hl_attr_table[i] & HL_UNDERLINE_MASK) {
+ attr &= ~HL_UNDERLINE_MASK;
+ }
attr |= hl_attr_table[i];
off += len;
break;
@@ -1246,7 +1281,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (dark != -1
&& dark != (*p_bg == 'd')
&& !option_was_set("bg")) {
- set_option_value_give_err("bg", 0L, (dark ? "dark" : "light"), 0);
+ set_option_value_give_err("bg", CSTR_AS_OPTVAL(dark ? "dark" : "light"), 0);
reset_option_was_set("bg");
}
}
@@ -1346,7 +1381,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
// Continue with next argument.
- linep = (const char *)skipwhite(linep);
+ linep = skipwhite(linep);
}
}
@@ -1394,7 +1429,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
void free_highlight(void)
{
ga_clear(&highlight_ga);
- map_destroy(cstr_t, int)(&highlight_unames);
+ map_destroy(cstr_t, &highlight_unames);
arena_mem_free(arena_finish(&highlight_arena));
}
@@ -1414,7 +1449,7 @@ void restore_cterm_colors(void)
/// @param check_link if true also check for an existing link.
///
/// @return true if highlight group "idx" has any settings.
-static int hl_has_settings(int idx, bool check_link)
+static bool hl_has_settings(int idx, bool check_link)
{
return hl_table[idx].sg_cleared == 0
&& (hl_table[idx].sg_attr != 0
@@ -1497,7 +1532,7 @@ static void highlight_list_one(const int id)
didh = true;
msg_puts_attr("links to", HL_ATTR(HLF_D));
msg_putchar(' ');
- msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name);
+ msg_outtrans(hl_table[hl_table[id - 1].sg_link - 1].sg_name, 0);
}
if (!didh) {
@@ -1508,24 +1543,79 @@ static void highlight_list_one(const int id)
}
}
-Dictionary get_global_hl_defs(Arena *arena)
+static bool hlgroup2dict(Dictionary *hl, NS ns_id, int hl_id, Arena *arena)
{
+ HlGroup *sgp = &hl_table[hl_id - 1];
+ int link = ns_id == 0 ? sgp->sg_link : ns_get_hl(&ns_id, hl_id, true, sgp->sg_set);
+ if (link == -1) {
+ return false;
+ }
+ if (ns_id == 0 && sgp->sg_cleared && sgp->sg_set == 0) {
+ // table entry was created but not ever set
+ return false;
+ }
+ HlAttrs attr =
+ syn_attr2entry(ns_id == 0 ? sgp->sg_attr : ns_get_hl(&ns_id, hl_id, false, sgp->sg_set));
+ *hl = arena_dict(arena, HLATTRS_DICT_SIZE + 1);
+ if (attr.rgb_ae_attr & HL_DEFAULT) {
+ PUT_C(*hl, "default", BOOLEAN_OBJ(true));
+ }
+ if (link > 0) {
+ PUT_C(*hl, "link", CSTR_AS_OBJ(hl_table[link - 1].sg_name));
+ }
+ Dictionary hl_cterm = arena_dict(arena, HLATTRS_DICT_SIZE);
+ hlattrs2dict(hl, NULL, attr, true, true);
+ hlattrs2dict(hl, &hl_cterm, attr, false, true);
+ if (kv_size(hl_cterm)) {
+ PUT_C(*hl, "cterm", DICTIONARY_OBJ(hl_cterm));
+ }
+ return true;
+}
+
+Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+{
+ Boolean link = GET_BOOL_OR_TRUE(opts, get_highlight, link);
+ int id = -1;
+ if (HAS_KEY(opts, get_highlight, name)) {
+ Boolean create = GET_BOOL_OR_TRUE(opts, get_highlight, create);
+ id = create ? syn_check_group(opts->name.data, opts->name.size)
+ : syn_name2id_len(opts->name.data, opts->name.size);
+ if (id == 0 && !create) {
+ Dictionary attrs = ARRAY_DICT_INIT;
+ return attrs;
+ }
+ } else if (HAS_KEY(opts, get_highlight, id)) {
+ id = (int)opts->id;
+ }
+
+ if (id != -1) {
+ VALIDATE(1 <= id && id <= highlight_ga.ga_len, "%s", "Highlight id out of bounds", {
+ goto cleanup;
+ });
+ Dictionary attrs = ARRAY_DICT_INIT;
+ hlgroup2dict(&attrs, ns_id, link ? id : syn_get_final_id(id), arena);
+ return attrs;
+ }
+ if (ERROR_SET(err)) {
+ goto cleanup;
+ }
+
Dictionary rv = arena_dict(arena, (size_t)highlight_ga.ga_len);
for (int i = 1; i <= highlight_ga.ga_len; i++) {
Dictionary attrs = ARRAY_DICT_INIT;
- HlGroup *h = &hl_table[i - 1];
- if (h->sg_attr > 0) {
- attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
- hlattrs2dict(&attrs, syn_attr2entry(h->sg_attr), true);
- } else if (h->sg_link > 0) {
- attrs = arena_dict(arena, 1);
- char *link = hl_table[h->sg_link - 1].sg_name;
- PUT_C(attrs, "link", STRING_OBJ(cstr_as_string(link)));
+ if (!hlgroup2dict(&attrs, ns_id, i, arena)) {
+ continue;
}
- PUT_C(rv, (char *)h->sg_name, DICTIONARY_OBJ(attrs));
+ PUT_C(rv, hl_table[(link ? i : syn_get_final_id(i)) - 1].sg_name, DICTIONARY_OBJ(attrs));
}
return rv;
+
+cleanup:
+ api_free_integer(id);
+ api_free_boolean(link);
+ Dictionary empty = ARRAY_DICT_INIT;
+ return empty;
}
/// Outputs a highlight when doing ":hi MyHighlight"
@@ -1547,7 +1637,7 @@ static bool highlight_list_arg(const int id, bool didh, const int type, int iarg
char buf[100];
const char *ts = buf;
if (type == LIST_INT) {
- snprintf((char *)buf, sizeof(buf), "%d", iarg - 1);
+ snprintf(buf, sizeof(buf), "%d", iarg - 1);
} else if (type == LIST_STRING) {
ts = sarg;
} else { // type == LIST_ATTR
@@ -1568,14 +1658,14 @@ static bool highlight_list_arg(const int id, bool didh, const int type, int iarg
}
}
- (void)syn_list_header(didh, vim_strsize((char *)ts) + (int)strlen(name) + 1, id, false);
+ (void)syn_list_header(didh, vim_strsize(ts) + (int)strlen(name) + 1, id, false);
didh = true;
if (!got_int) {
if (*name != NUL) {
msg_puts_attr(name, HL_ATTR(HLF_D));
msg_puts_attr("=", HL_ATTR(HLF_D));
}
- msg_outtrans((char *)ts);
+ msg_outtrans(ts, 0);
}
return didh;
}
@@ -1705,7 +1795,7 @@ bool syn_list_header(const bool did_header, const int outlen, const int id, bool
if (got_int) {
return true;
}
- msg_outtrans(hl_table[id - 1].sg_name);
+ msg_outtrans(hl_table[id - 1].sg_name, 0);
name_col = msg_col;
endcol = 15;
} else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) {
@@ -1861,13 +1951,13 @@ int syn_check_group(const char *name, size_t len)
/// @see syn_check_group
static int syn_add_group(const char *name, size_t len)
{
- // Check that the name is ASCII letters, digits and underscore.
+ // Check that the name is valid (ASCII letters, digits, '_', '.', '@', '-').
for (size_t i = 0; i < len; i++) {
int c = (uint8_t)name[i];
if (!vim_isprintc(c)) {
emsg(_("E669: Unprintable character in group name"));
return 0;
- } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@') {
+ } else if (!ASCII_ISALNUM(c) && c != '_' && c != '.' && c != '@' && c != '-') {
// '.' and '@' are allowed characters for use with treesitter capture names.
msg_source(HL_ATTR(HLF_W));
emsg(_(e_highlight_group_name_invalid_char));
@@ -1924,18 +2014,23 @@ static int syn_add_group(const char *name, size_t len)
/// @see syn_attr2entry
int syn_id2attr(int hl_id)
{
- return syn_ns_id2attr(-1, hl_id, false);
+ bool optional = false;
+ return syn_ns_id2attr(-1, hl_id, &optional);
}
-int syn_ns_id2attr(int ns_id, int hl_id, bool optional)
+int syn_ns_id2attr(int ns_id, int hl_id, bool *optional)
+ FUNC_ATTR_NONNULL_ALL
{
- hl_id = syn_ns_get_final_id(&ns_id, hl_id);
+ if (syn_ns_get_final_id(&ns_id, &hl_id)) {
+ // If the namespace explicitly defines a group to be empty, it is not optional
+ *optional = false;
+ }
HlGroup *sgp = &hl_table[hl_id - 1]; // index is ID minus one
int attr = ns_get_hl(&ns_id, hl_id, false, sgp->sg_set);
// if a highlight group is optional, don't use the global value
- if (attr >= 0 || (optional && ns_id > 0)) {
+ if (attr >= 0 || (*optional && ns_id > 0)) {
return attr;
}
return sgp->sg_attr;
@@ -1944,16 +2039,20 @@ int syn_ns_id2attr(int ns_id, int hl_id, bool optional)
/// Translate a group ID to the final group ID (following links).
int syn_get_final_id(int hl_id)
{
- int id = curwin->w_ns_hl_active;
- return syn_ns_get_final_id(&id, hl_id);
+ int ns_id = curwin->w_ns_hl_active;
+ syn_ns_get_final_id(&ns_id, &hl_id);
+ return hl_id;
}
-int syn_ns_get_final_id(int *ns_id, int hl_id)
+bool syn_ns_get_final_id(int *ns_id, int *hl_idp)
{
int count;
+ int hl_id = *hl_idp;
+ bool used = false;
if (hl_id > highlight_ga.ga_len || hl_id < 1) {
- return 0; // Can be called from eval!!
+ *hl_idp = 0;
+ return false; // Can be called from eval!!
}
// Follow links until there is no more.
@@ -1966,8 +2065,10 @@ int syn_ns_get_final_id(int *ns_id, int hl_id)
// syn_id2attr time
int check = ns_get_hl(ns_id, hl_id, true, sgp->sg_set);
if (check == 0) {
- return hl_id; // how dare! it broke the link!
+ *hl_idp = hl_id;
+ return true; // how dare! it broke the link!
} else if (check > 0) {
+ used = true;
hl_id = check;
continue;
}
@@ -1981,7 +2082,8 @@ int syn_ns_get_final_id(int *ns_id, int hl_id)
}
}
- return hl_id;
+ *hl_idp = hl_id;
+ return used;
}
/// Refresh the color attributes of all highlight groups.
@@ -2064,15 +2166,15 @@ void highlight_changed(void)
abort();
}
int ns_id = -1;
- int final_id = syn_ns_get_final_id(&ns_id, id);
+ int final_id = id;
+ syn_ns_get_final_id(&ns_id, &final_id);
if (hlf == HLF_SNC) {
id_SNC = final_id;
} else if (hlf == HLF_S) {
id_S = final_id;
}
- highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id,
- (hlf == HLF_INACTIVE || hlf == HLF_LC));
+ highlight_attr[hlf] = hl_get_ui_attr(ns_id, hlf, final_id, hlf == HLF_INACTIVE);
if (highlight_attr[hlf] != highlight_attr_last[hlf]) {
if (hlf == HLF_MSG) {
@@ -2134,7 +2236,7 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
}
// (part of) subcommand already typed
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
return;
}
@@ -2142,9 +2244,9 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
// past "default" or group name
include_default = 0;
if (strncmp("default", arg, (unsigned)(p - arg)) == 0) {
- arg = (const char *)skipwhite(p);
+ arg = skipwhite(p);
xp->xp_pattern = (char *)arg;
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
}
if (*p == NUL) {
return;
@@ -2158,10 +2260,10 @@ void set_context_in_highlight_cmd(expand_T *xp, const char *arg)
if (strncmp("link", arg, (unsigned)(p - arg)) == 0
|| strncmp("clear", arg, (unsigned)(p - arg)) == 0) {
xp->xp_pattern = skipwhite(p);
- p = (const char *)skiptowhite(xp->xp_pattern);
+ p = skiptowhite(xp->xp_pattern);
if (*p != NUL) { // past first group name
xp->xp_pattern = skipwhite(p);
- p = (const char *)skiptowhite(xp->xp_pattern);
+ p = skiptowhite(xp->xp_pattern);
}
}
if (*p != NUL) { // past group name(s)
@@ -2185,14 +2287,14 @@ static void highlight_list_two(int cnt, int attr)
msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr);
msg_clr_eos();
ui_flush();
- os_delay(cnt == 99 ? 40L : (uint64_t)cnt * 50L, false);
+ os_delay(cnt == 99 ? 40 : (uint64_t)cnt * 50, false);
}
/// Function given to ExpandGeneric() to obtain the list of group names.
-const char *get_highlight_name(expand_T *const xp, int idx)
+char *get_highlight_name(expand_T *const xp, int idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- return get_highlight_name_ext(xp, idx, true);
+ return (char *)get_highlight_name_ext(xp, idx, true);
}
/// Obtain a highlight group name.
@@ -2224,7 +2326,7 @@ const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared)
} else if (idx >= highlight_ga.ga_len) {
return NULL;
}
- return (const char *)hl_table[idx].sg_name;
+ return hl_table[idx].sg_name;
}
color_name_table_T color_name_table[] = {
@@ -2925,7 +3027,7 @@ RgbValue name_to_color(const char *name, int *idx)
&& isxdigit((uint8_t)name[6]) && name[7] == NUL) {
// rgb hex string
*idx = kColorIdxHex;
- return (RgbValue)strtol((char *)(name + 1), NULL, 16);
+ return (RgbValue)strtol(name + 1, NULL, 16);
} else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) {
*idx = kColorIdxBg;
return normal_bg;
diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h
index bf6bad1a86..ca7bd36271 100644
--- a/src/nvim/highlight_group.h
+++ b/src/nvim/highlight_group.h
@@ -1,9 +1,11 @@
-#ifndef NVIM_HIGHLIGHT_GROUP_H
-#define NVIM_HIGHLIGHT_GROUP_H
+#pragma once
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/api/private/helpers.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/highlight_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
#define MAX_HL_ID 20000 // maximum value for a highlight ID.
@@ -16,5 +18,3 @@ extern color_name_table_T color_name_table[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight_group.h.generated.h"
#endif
-
-#endif // NVIM_HIGHLIGHT_GROUP_H
diff --git a/src/nvim/iconv.h b/src/nvim/iconv_defs.h
index f5f3f25786..b6456b94ba 100644
--- a/src/nvim/iconv.h
+++ b/src/nvim/iconv_defs.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_ICONV_H
-#define NVIM_ICONV_H
+#pragma once
#include <errno.h>
#include <iconv.h>
-#include "auto/config.h"
-
// define some missing constants if necessary
#ifndef EILSEQ
# define EILSEQ 123
@@ -14,5 +11,3 @@
#define ICONV_E2BIG E2BIG
#define ICONV_EINVAL EINVAL
#define ICONV_EILSEQ EILSEQ
-
-#endif // NVIM_ICONV_H
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index ec6c72da6d..348f3a6528 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -1,14 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -20,6 +17,7 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/extmark.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
@@ -31,18 +29,19 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.c.generated.h"
@@ -52,9 +51,9 @@
/// "array" will be set, caller must free it if needed.
///
/// @return false for an error.
-bool tabstop_set(char *var, long **array)
+bool tabstop_set(char *var, colnr_T **array)
{
- long valcount = 1;
+ int valcount = 1;
int t;
char *cp;
@@ -88,8 +87,8 @@ bool tabstop_set(char *var, long **array)
return false;
}
- *array = (long *)xmalloc((unsigned)(valcount + 1) * sizeof(long));
- (*array)[0] = valcount;
+ *array = (colnr_T *)xmalloc((unsigned)(valcount + 1) * sizeof(int));
+ (*array)[0] = (colnr_T)valcount;
t = 1;
for (cp = var; *cp != NUL;) {
@@ -116,21 +115,21 @@ bool tabstop_set(char *var, long **array)
/// Calculate the number of screen spaces a tab will occupy.
/// If "vts" is set then the tab widths are taken from that array,
/// otherwise the value of ts is used.
-int tabstop_padding(colnr_T col, long ts_arg, const long *vts)
+int tabstop_padding(colnr_T col, OptInt ts_arg, const colnr_T *vts)
{
- long ts = ts_arg == 0 ? 8 : ts_arg;
+ OptInt ts = ts_arg == 0 ? 8 : ts_arg;
colnr_T tabcol = 0;
int t;
- long padding = 0;
+ int padding = 0;
if (vts == NULL || vts[0] == 0) {
return (int)(ts - (col % ts));
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
padding = tabcol - col;
break;
@@ -140,23 +139,23 @@ int tabstop_padding(colnr_T col, long ts_arg, const long *vts)
padding = vts[tabcount] - ((col - tabcol) % vts[tabcount]);
}
- return (int)padding;
+ return padding;
}
/// Find the size of the tab that covers a particular column.
-int tabstop_at(colnr_T col, long ts, const long *vts)
+int tabstop_at(colnr_T col, OptInt ts, const colnr_T *vts)
{
colnr_T tabcol = 0;
int t;
- long tab_size = 0;
+ int tab_size = 0;
if (vts == NULL || vts[0] == 0) {
return (int)ts;
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
tab_size = vts[t];
break;
@@ -166,53 +165,53 @@ int tabstop_at(colnr_T col, long ts, const long *vts)
tab_size = vts[tabcount];
}
- return (int)tab_size;
+ return tab_size;
}
/// Find the column on which a tab starts.
-colnr_T tabstop_start(colnr_T col, long ts, long *vts)
+colnr_T tabstop_start(colnr_T col, int ts, colnr_T *vts)
{
colnr_T tabcol = 0;
int t;
if (vts == NULL || vts[0] == 0) {
- return (int)((col / ts) * ts);
+ return ((col / ts) * ts);
}
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > col) {
- return (int)(tabcol - vts[t]);
+ return (tabcol - vts[t]);
}
}
- const int excess = (int)(tabcol % vts[tabcount]);
- return (int)(excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
+ const int excess = (tabcol % vts[tabcount]);
+ return (excess + ((col - excess) / vts[tabcount]) * vts[tabcount]);
}
/// Find the number of tabs and spaces necessary to get from one column
/// to another.
-void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long *vts, int *ntabs,
+void tabstop_fromto(colnr_T start_col, colnr_T end_col, int ts_arg, const colnr_T *vts, int *ntabs,
int *nspcs)
{
int spaces = end_col - start_col;
colnr_T tabcol = 0;
- long padding = 0;
+ int padding = 0;
int t;
- long ts = ts_arg == 0 ? curbuf->b_p_ts : ts_arg;
+ int ts = ts_arg == 0 ? (int)curbuf->b_p_ts : ts_arg;
assert(ts != 0); // suppress clang "Division by zero"
if (vts == NULL || vts[0] == 0) {
int tabs = 0;
- const int initspc = (int)(ts - (start_col % ts));
+ const int initspc = (ts - (start_col % ts));
if (spaces >= initspc) {
spaces -= initspc;
tabs++;
}
- tabs += (int)(spaces / ts);
- spaces -= (int)((spaces / ts) * ts);
+ tabs += (spaces / ts);
+ spaces -= ((spaces / ts) * ts);
*ntabs = tabs;
*nspcs = spaces;
@@ -220,9 +219,9 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
// Find the padding needed to reach the next tabstop.
- const long tabcount = vts[0];
+ const int tabcount = vts[0];
for (t = 1; t <= tabcount; t++) {
- tabcol += (colnr_T)vts[t];
+ tabcol += vts[t];
if (tabcol > start_col) {
padding = tabcol - start_col;
break;
@@ -240,7 +239,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
*ntabs = 1;
- spaces -= (int)padding;
+ spaces -= padding;
// At least one tab has been used. See if any more will fit.
while (spaces != 0 && ++t <= tabcount) {
@@ -250,7 +249,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
return;
}
*ntabs += 1;
- spaces -= (int)padding;
+ spaces -= padding;
}
*ntabs += spaces / (int)vts[tabcount];
@@ -258,7 +257,7 @@ void tabstop_fromto(colnr_T start_col, colnr_T end_col, long ts_arg, const long
}
/// See if two tabstop arrays contain the same values.
-bool tabstop_eq(const long *ts1, const long *ts2)
+bool tabstop_eq(const colnr_T *ts1, const colnr_T *ts2)
{
int t;
@@ -282,31 +281,31 @@ bool tabstop_eq(const long *ts1, const long *ts2)
}
/// Copy a tabstop array, allocating space for the new array.
-int *tabstop_copy(const long *oldts)
+int *tabstop_copy(const int *oldts)
{
- long *newts;
+ int *newts;
int t;
if (oldts == 0) {
return 0;
}
- newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(long));
+ newts = xmalloc((unsigned)(oldts[0] + 1) * sizeof(int));
for (t = 0; t <= oldts[0]; t++) {
newts[t] = oldts[t];
}
- return (int *)newts;
+ return newts;
}
/// Return a count of the number of tabstops.
-int tabstop_count(long *ts)
+int tabstop_count(colnr_T *ts)
{
return ts != NULL ? (int)ts[0] : 0;
}
/// Return the first tabstop, or 8 if there are no tabstops defined.
-int tabstop_first(long *ts)
+int tabstop_first(colnr_T *ts)
{
return ts != NULL ? (int)ts[1] : 8;
}
@@ -315,25 +314,23 @@ int tabstop_first(long *ts)
/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
- long result = get_sw_value_col(buf, 0);
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
+ int result = get_sw_value_col(buf, 0);
+ return result;
}
/// Idem, using "pos".
-long get_sw_value_pos(buf_T *buf, pos_T *pos)
+int get_sw_value_pos(buf_T *buf, pos_T *pos)
{
pos_T save_cursor = curwin->w_cursor;
- long sw_value;
curwin->w_cursor = *pos;
- sw_value = get_sw_value_col(buf, get_nolist_virtcol());
+ int sw_value = get_sw_value_col(buf, get_nolist_virtcol());
curwin->w_cursor = save_cursor;
return sw_value;
}
/// Idem, using the first non-black in the current line.
-long get_sw_value_indent(buf_T *buf)
+int get_sw_value_indent(buf_T *buf)
{
pos_T pos = curwin->w_cursor;
@@ -342,9 +339,9 @@ long get_sw_value_indent(buf_T *buf)
}
/// Idem, using virtual column "col".
-long get_sw_value_col(buf_T *buf, colnr_T col)
+int get_sw_value_col(buf_T *buf, colnr_T col)
{
- return buf->b_p_sw ? buf->b_p_sw
+ return buf->b_p_sw ? (int)buf->b_p_sw
: tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
}
@@ -352,9 +349,8 @@ long get_sw_value_col(buf_T *buf, colnr_T col)
/// using the shiftwidth value when 'softtabstop' is negative.
int get_sts_value(void)
{
- long result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : curbuf->b_p_sts;
- assert(result >= 0 && result <= INT_MAX);
- return (int)result;
+ int result = curbuf->b_p_sts < 0 ? get_sw_value(curbuf) : (int)curbuf->b_p_sts;
+ return result;
}
// Count the size (in window cells) of the indent in the current line.
@@ -379,10 +375,7 @@ int get_indent_lnum(linenr_T lnum)
// "buf".
int get_indent_buf(buf_T *buf, linenr_T lnum)
{
- return get_indent_str_vtab(ml_get_buf(buf, lnum, false),
- buf->b_p_ts,
- buf->b_p_vts_array,
- false);
+ return get_indent_str_vtab(ml_get_buf(buf, lnum), buf->b_p_ts, buf->b_p_vts_array, false);
}
/// Count the size (in window cells) of the indent in line "ptr", with
@@ -417,7 +410,7 @@ int get_indent_str(const char *ptr, int ts, bool list)
/// Count the size (in window cells) of the indent in line "ptr", using
/// variable tabstops.
/// if "list" is true, count only screen size for tabs.
-int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list)
+int get_indent_str_vtab(const char *ptr, OptInt ts, colnr_T *vts, bool list)
{
int count = 0;
@@ -428,7 +421,7 @@ int get_indent_str_vtab(const char *ptr, long ts, long *vts, bool list)
} else {
// In list mode, when tab is not set, count screen char width
// for Tab, displays: ^I
- count += ptr2cells((char *)ptr);
+ count += ptr2cells(ptr);
}
} else if (*ptr == ' ') {
count++; // count a space for one
@@ -460,7 +453,6 @@ int set_indent(int size, int flags)
int line_len;
int doit = false;
int ind_done = 0; // Measured in spaces.
- int ind_col = 0;
int tab_pad;
int retval = false;
@@ -479,6 +471,7 @@ int set_indent(int size, int flags)
// 'preserveindent' are set count the number of characters at the
// beginning of the line to be copied.
if (!curbuf->b_p_et || (!(flags & SIN_INSERT) && curbuf->b_p_pi)) {
+ int ind_col = 0;
// If 'preserveindent' is set then reuse as much as possible of
// the existing indent structure for the new indent.
if (!(flags & SIN_INSERT) && curbuf->b_p_pi) {
@@ -528,7 +521,7 @@ int set_indent(int size, int flags)
}
// Count tabs required for indent.
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_col, curbuf->b_p_ts, curbuf->b_p_vts_array);
if (todo < tab_pad) {
break;
@@ -651,7 +644,7 @@ int set_indent(int size, int flags)
p = skipwhite(p);
}
- for (;;) {
+ while (true) {
tab_pad = tabstop_padding(ind_done,
curbuf->b_p_ts,
curbuf->b_p_vts_array);
@@ -736,7 +729,7 @@ int get_number_indent(linenr_T lnum)
// vim_regexec() expects a pointer to a line. This lets us
// start matching for the flp beyond any comment leader...
- if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, (colnr_T)0)) {
+ if (vim_regexec(&regmatch, ml_get(lnum) + lead_len, 0)) {
pos.lnum = lnum;
pos.col = (colnr_T)(*regmatch.endp - ml_get(lnum));
pos.coladd = 0;
@@ -763,6 +756,7 @@ bool briopt_check(win_T *wp)
char *p = wp->w_p_briopt;
while (*p != NUL) {
+ // Note: Keep this in sync with p_briopt_values
if (strncmp(p, "shift:", 6) == 0
&& ((p[6] == '-' && ascii_isdigit(p[7])) || ascii_isdigit(p[6]))) {
p += 6;
@@ -803,11 +797,12 @@ bool briopt_check(win_T *wp)
int get_breakindent_win(win_T *wp, char *line)
FUNC_ATTR_NONNULL_ALL
{
- static int prev_indent = 0; // Cached indent value.
- static long prev_ts = 0L; // Cached tabstop value.
- static const char *prev_line = NULL; // cached pointer to line.
- static varnumber_T prev_tick = 0; // Changedtick of cached value.
- static long *prev_vts = NULL; // Cached vartabs values.
+ static int prev_indent = 0; // cached indent value
+ static OptInt prev_ts = 0; // cached tabstop value
+ static int prev_fnum = 0; // cached buffer number
+ static char *prev_line = NULL; // cached copy of "line"
+ static varnumber_T prev_tick = 0; // changedtick of cached value
+ static colnr_T *prev_vts = NULL; // cached vartabs values
static int prev_list = 0; // cached list value
static int prev_listopt = 0; // cached w_p_briopt_list value
static char *prev_flp = NULL; // cached formatlistpat value
@@ -818,16 +813,24 @@ int get_breakindent_win(win_T *wp, char *line)
&& (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0);
// used cached indent, unless
- // - line pointer changed
+ // - buffer changed
// - 'tabstop' changed
+ // - buffer was changed
// - 'briopt_list changed' changed or
// - 'formatlistpattern' changed
- if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts
+ // - line changed
+ // - 'vartabs' changed
+ if (prev_fnum != wp->w_buffer->b_fnum
+ || prev_ts != wp->w_buffer->b_p_ts
|| prev_tick != buf_get_changedtick(wp->w_buffer)
|| prev_listopt != wp->w_briopt_list
- || (prev_flp == NULL || (strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0))
+ || prev_flp == NULL
+ || strcmp(prev_flp, get_flp_value(wp->w_buffer)) != 0
+ || prev_line == NULL || strcmp(prev_line, line) != 0
|| prev_vts != wp->w_buffer->b_p_vts_array) {
- prev_line = line;
+ prev_fnum = wp->w_buffer->b_fnum;
+ xfree(prev_line);
+ prev_line = xstrdup(line);
prev_ts = wp->w_buffer->b_p_ts;
prev_tick = buf_get_changedtick(wp->w_buffer);
prev_vts = wp->w_buffer->b_p_vts_array;
@@ -887,7 +890,7 @@ int get_breakindent_win(win_T *wp, char *line)
// always leave at least bri_min characters on the left,
// if text width is sufficient
bri = (eff_wwidth - wp->w_briopt_min < 0)
- ? 0 : eff_wwidth - wp->w_briopt_min;
+ ? 0 : eff_wwidth - wp->w_briopt_min;
}
return bri;
@@ -933,18 +936,14 @@ void ex_retab(exarg_T *eap)
{
linenr_T lnum;
bool got_tab = false;
- long num_spaces = 0;
- long num_tabs;
- long len;
- long col;
- long vcol;
- long start_col = 0; // For start of white-space string
- long start_vcol = 0; // For start of white-space string
- long old_len;
- char *ptr;
+ int num_spaces = 0;
+ int num_tabs;
+ int len;
+ int start_col = 0; // For start of white-space string
+ int64_t start_vcol = 0; // For start of white-space string
+ int old_len;
char *new_line = (char *)1; // init to non-NULL
- bool did_undo; // called u_save for current line
- long *new_vts_array = NULL;
+ colnr_T *new_vts_array = NULL;
char *new_ts_str; // string value of tab argument
int save_list;
@@ -959,7 +958,7 @@ void ex_retab(exarg_T *eap)
return;
}
while (ascii_isdigit(*(eap->arg)) || *(eap->arg) == ',') {
- (eap->arg)++;
+ eap->arg++;
}
// This ensures that either new_vts_array and new_ts_str are freshly
@@ -969,14 +968,14 @@ void ex_retab(exarg_T *eap)
new_vts_array = curbuf->b_p_vts_array;
new_ts_str = NULL;
} else {
- new_ts_str = xstrnsave(new_ts_str, (size_t)(eap->arg - new_ts_str));
+ new_ts_str = xmemdupz(new_ts_str, (size_t)(eap->arg - new_ts_str));
}
for (lnum = eap->line1; !got_int && lnum <= eap->line2; lnum++) {
- ptr = ml_get(lnum);
- col = 0;
- vcol = 0;
- did_undo = false;
- for (;;) {
+ char *ptr = ml_get(lnum);
+ int col = 0;
+ int64_t vcol = 0;
+ bool did_undo = false; // called u_save for current line
+ while (true) {
if (ascii_iswhite(ptr[col])) {
if (!got_tab && num_spaces == 0) {
// First consecutive white-space
@@ -993,13 +992,13 @@ void ex_retab(exarg_T *eap)
// Retabulate this string of white-space
// len is virtual length of white string
- len = num_spaces = vcol - start_vcol;
+ len = num_spaces = (int)(vcol - start_vcol);
num_tabs = 0;
if (!curbuf->b_p_et) {
int t, s;
tabstop_fromto((colnr_T)start_vcol, (colnr_T)vcol,
- curbuf->b_p_ts, new_vts_array, &t, &s);
+ (int)curbuf->b_p_ts, new_vts_array, &t, &s);
num_tabs = t;
num_spaces = s;
}
@@ -1016,8 +1015,8 @@ void ex_retab(exarg_T *eap)
// len is actual number of white characters used
len = num_spaces + num_tabs;
- old_len = (long)strlen(ptr);
- const long new_len = old_len - col + start_col + len + 1;
+ old_len = (int)strlen(ptr);
+ const int new_len = old_len - col + start_col + len + 1;
if (new_len <= 0 || new_len >= MAXCOL) {
emsg_text_too_long();
break;
@@ -1028,7 +1027,7 @@ void ex_retab(exarg_T *eap)
memmove(new_line, ptr, (size_t)start_col);
}
memmove(new_line + start_col + len,
- ptr + col, (size_t)(old_len - col + 1));
+ ptr + col, (size_t)old_len - (size_t)col + 1);
ptr = new_line + start_col;
for (col = 0; col < len; col++) {
ptr[col] = (col < num_tabs) ? '\t' : ' ';
@@ -1082,7 +1081,7 @@ void ex_retab(exarg_T *eap)
redraw_curbuf_later(UPD_NOT_VALID);
}
if (first_line != 0) {
- changed_lines(first_line, 0, last_line + 1, 0L, true);
+ changed_lines(curbuf, first_line, 0, last_line + 1, 0, true);
}
curwin->w_p_list = save_list; // restore 'list'
@@ -1090,7 +1089,7 @@ void ex_retab(exarg_T *eap)
if (new_ts_str != NULL) { // set the new tabstop
// If 'vartabstop' is in use or if the value given to retab has more
// than one tabstop then update 'vartabstop'.
- long *old_vts_ary = curbuf->b_p_vts_array;
+ colnr_T *old_vts_ary = curbuf->b_p_vts_array;
if (tabstop_count(old_vts_ary) > 0 || tabstop_count(new_vts_array) > 1) {
set_string_option_direct("vts", -1, new_ts_str, OPT_FREE | OPT_LOCAL, 0);
@@ -1106,7 +1105,7 @@ void ex_retab(exarg_T *eap)
}
coladvance(curwin->w_curswant);
- u_clearline();
+ u_clearline(curbuf);
}
/// Get indent level from 'indentexpr'.
@@ -1118,6 +1117,7 @@ int get_expr_indent(void)
int save_set_curswant;
int save_State;
int use_sandbox = was_set_insecurely(curwin, "indentexpr", OPT_LOCAL);
+ const sctx_T save_sctx = current_sctx;
// Save and restore cursor position and curswant, in case it was changed
// * via :normal commands.
@@ -1130,6 +1130,7 @@ int get_expr_indent(void)
sandbox++;
}
textlock++;
+ current_sctx = curbuf->b_p_script_ctx[BV_INDE].script_ctx;
// Need to make a copy, the 'indentexpr' option could be changed while
// evaluating it.
@@ -1141,6 +1142,7 @@ int get_expr_indent(void)
sandbox--;
}
textlock--;
+ current_sctx = save_sctx;
// Restore the cursor position so that 'indentexpr' doesn't need to.
// Pretend to be in Insert mode, allow cursor past end of line for "o"
@@ -1183,19 +1185,15 @@ int get_expr_indent(void)
// I tried to fix the first two issues.
int get_lisp_indent(void)
{
- pos_T *pos, realpos, paren;
+ pos_T *pos;
+ pos_T paren;
int amount;
char *that;
- colnr_T col;
- colnr_T firsttry;
- int parencount;
- int quotecount;
- int vi_lisp;
// Set vi_lisp to use the vi-compatible method.
- vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
+ int vi_lisp = (vim_strchr(p_cpo, CPO_LISP) != NULL);
- realpos = curwin->w_cursor;
+ pos_T realpos = curwin->w_cursor;
curwin->w_cursor.col = 0;
if ((pos = findmatch(NULL, '(')) == NULL) {
@@ -1213,7 +1211,7 @@ int get_lisp_indent(void)
// Extra trick: Take the indent of the first previous non-white
// line that is at the same () level.
amount = -1;
- parencount = 0;
+ int parencount = 0;
while (--curwin->w_cursor.lnum >= pos->lnum) {
if (linewhite(curwin->w_cursor.lnum)) {
@@ -1268,7 +1266,7 @@ int get_lisp_indent(void)
if (amount == -1) {
curwin->w_cursor.lnum = pos->lnum;
curwin->w_cursor.col = pos->col;
- col = pos->col;
+ colnr_T col = pos->col;
that = get_cursor_line_ptr();
@@ -1298,7 +1296,7 @@ int get_lisp_indent(void)
that++;
amount++;
}
- firsttry = amount;
+ colnr_T firsttry = amount;
init_chartabsize_arg(&cts, curwin, (colnr_T)(that - line),
amount, line, that);
@@ -1319,13 +1317,13 @@ int get_lisp_indent(void)
}
parencount = 0;
- quotecount = 0;
init_chartabsize_arg(&cts, curwin,
(colnr_T)(that - line), amount, line, that);
if (vi_lisp || ((*that != '"') && (*that != '\'')
&& (*that != '#')
&& (((uint8_t)(*that) < '0') || ((uint8_t)(*that) > '9')))) {
+ int quotecount = 0;
while (*cts.cts_ptr
&& (!ascii_iswhite(*cts.cts_ptr) || quotecount || parencount)
&& (!((*cts.cts_ptr == '(' || *cts.cts_ptr == '[')
@@ -1372,13 +1370,12 @@ int get_lisp_indent(void)
static int lisp_match(char *p)
{
- char buf[LSIZE];
- int len;
+ char buf[512];
char *word = *curbuf->b_p_lw != NUL ? curbuf->b_p_lw : p_lispwords;
while (*word != NUL) {
- (void)copy_option_part(&word, buf, LSIZE, ",");
- len = (int)strlen(buf);
+ (void)copy_option_part(&word, buf, sizeof(buf), ",");
+ int len = (int)strlen(buf);
if ((strncmp(buf, p, (size_t)len) == 0) && ascii_iswhite_or_nul(p[len])) {
return true;
diff --git a/src/nvim/indent.h b/src/nvim/indent.h
index f807bbb42b..b64015958c 100644
--- a/src/nvim/indent.h
+++ b/src/nvim/indent.h
@@ -1,17 +1,20 @@
-#ifndef NVIM_INDENT_H
-#define NVIM_INDENT_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
typedef int (*IndentGetter)(void);
-// flags for set_indent()
-#define SIN_CHANGED 1 // call changed_bytes() when line changed
-#define SIN_INSERT 2 // insert indent before existing text
-#define SIN_UNDO 4 // save line for undo before changing it
-#define SIN_NOMARK 8 // don't adjust extmarks
+/// flags for set_indent()
+enum {
+ SIN_CHANGED = 1, ///< call changed_bytes() when line changed
+ SIN_INSERT = 2, ///< insert indent before existing text
+ SIN_UNDO = 4, ///< save line for undo before changing it
+ SIN_NOMARK = 8, ///< don't adjust extmarks
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent.h.generated.h"
#endif
-#endif // NVIM_INDENT_H
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 1c771073b2..c140d468d8 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -1,29 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Find result cache for cpp_baseclass
typedef struct {
@@ -47,7 +48,7 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX
pos_T *pos;
int64_t cur_maxcomment = ind_maxcomment;
- for (;;) {
+ while (true) {
pos = findmatchlimit(NULL, '*', FM_BACKWARD, cur_maxcomment);
if (pos == NULL) {
break;
@@ -106,9 +107,9 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw)
static pos_T *find_start_rawstring(int ind_maxcomment) // XXX
{
pos_T *pos;
- long cur_maxcomment = ind_maxcomment;
+ int cur_maxcomment = ind_maxcomment;
- for (;;) {
+ while (true) {
pos = findmatchlimit(NULL, 'R', FM_BACKWARD, cur_maxcomment);
if (pos == NULL) {
break;
@@ -412,7 +413,7 @@ static int cin_isinit(void)
s = cin_skipcomment(s + 7);
}
- for (;;) {
+ while (true) {
int i, l;
for (i = 0; i < (int)ARRAY_SIZE(skip); i++) {
@@ -525,7 +526,9 @@ static bool cin_is_cpp_namespace(const char *s)
s = cin_skipcomment(s);
- if (strncmp(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) {
+ // skip over "inline" and "export" in any order
+ while ((strncmp(s, "inline", 6) == 0 || strncmp(s, "export", 6) == 0)
+ && (s[6] == NUL || !vim_iswordc((uint8_t)s[6]))) {
s = cin_skipcomment(skipwhite(s + 6));
}
@@ -754,7 +757,7 @@ static int cin_ispreproc_cont(const char **pp, linenr_T *lnump, int *amount)
candidate_amount = get_indent_lnum(lnum);
}
- for (;;) {
+ while (true) {
if (cin_ispreproc(line)) {
retval = true;
*lnump = lnum;
@@ -927,7 +930,7 @@ static int cin_isfuncdecl(const char **sp, linenr_T first_lnum, linenr_T min_lnu
// At the end: check for ',' in the next line, for this style:
// func(arg1
// , arg2)
- for (;;) {
+ while (true) {
if (lnum >= curbuf->b_ml.ml_line_count) {
break;
}
@@ -1184,7 +1187,7 @@ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached)
pos->lnum = lnum;
line = ml_get(lnum);
s = line;
- for (;;) {
+ while (true) {
if (*s == NUL) {
if (lnum == curwin->w_cursor.lnum) {
break;
@@ -1504,10 +1507,10 @@ static pos_T *find_match_paren_after_brace(int ind_maxparen)
// looking a few lines further.
static int corr_ind_maxparen(pos_T *startpos)
{
- long n = (long)startpos->lnum - (long)curwin->w_cursor.lnum;
+ int n = startpos->lnum - curwin->w_cursor.lnum;
if (n > 0 && n < curbuf->b_ind_maxparen / 2) {
- return curbuf->b_ind_maxparen - (int)n;
+ return curbuf->b_ind_maxparen - n;
}
return curbuf->b_ind_maxparen;
}
@@ -2499,7 +2502,7 @@ int get_c_indent(void)
// the usual amount relative to the conditional
// that opens the block.
curwin->w_cursor = cur_curpos;
- for (;;) {
+ while (true) {
curwin->w_cursor.lnum--;
curwin->w_cursor.col = 0;
@@ -2529,8 +2532,6 @@ int get_c_indent(void)
break;
}
- l = get_cursor_line_ptr();
-
// If we're in a comment or raw string now, skip to
// the start of it.
trypos = ind_find_start_CORS(NULL);
@@ -2540,9 +2541,9 @@ int get_c_indent(void)
continue;
}
- //
+ l = get_cursor_line_ptr();
+
// Skip preprocessor directives and blank lines.
- //
if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {
continue;
}
@@ -2640,8 +2641,6 @@ int get_c_indent(void)
break;
}
- l = get_cursor_line_ptr();
-
// If we're in a comment or raw string now, skip
// to the start of it.
trypos = ind_find_start_CORS(NULL);
@@ -2651,6 +2650,8 @@ int get_c_indent(void)
continue;
}
+ l = get_cursor_line_ptr();
+
// Skip preprocessor directives and blank lines.
if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {
continue;
@@ -2916,11 +2917,15 @@ int get_c_indent(void)
trypos = NULL;
}
+ l = get_cursor_line_ptr();
+
// If we are looking for ',', we also look for matching
// braces.
- if (trypos == NULL && terminated == ','
- && find_last_paren(l, '{', '}')) {
- trypos = find_start_brace();
+ if (trypos == NULL && terminated == ',') {
+ if (find_last_paren(l, '{', '}')) {
+ trypos = find_start_brace();
+ }
+ l = get_cursor_line_ptr();
}
if (trypos != NULL) {
@@ -2951,6 +2956,7 @@ int get_c_indent(void)
curwin->w_cursor.lnum--;
curwin->w_cursor.col = 0;
}
+ l = get_cursor_line_ptr();
}
// Get indent and pointer to text for current line,
diff --git a/src/nvim/indent_c.h b/src/nvim/indent_c.h
index bd6eeed67d..64ba67a42b 100644
--- a/src/nvim/indent_c.h
+++ b/src/nvim/indent_c.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_INDENT_C_H
-#define NVIM_INDENT_C_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "indent_c.h.generated.h"
#endif
-#endif // NVIM_INDENT_C_H
diff --git a/src/nvim/input.c b/src/nvim/input.c
index 96214d45c2..fb25968071 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -1,19 +1,17 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// input.c: high level functions for prompting the user or input
// like yes/no or number prompts.
+#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
#include "nvim/keycodes.h"
#include "nvim/mbyte.h"
@@ -21,9 +19,8 @@
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/os/input.h"
-#include "nvim/types.h"
+#include "nvim/state_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "input.c.generated.h" // IWYU pragma: export
@@ -56,7 +53,7 @@ int ask_yesno(const char *const str, const bool direct)
int r = ' ';
while (r != 'y' && r != 'n') {
// same highlighting as for wait_return()
- smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str);
+ smsg(HL_ATTR(HLF_R), "%s (y/n)?", str);
if (direct) {
r = get_keystroke(NULL);
} else {
@@ -86,21 +83,20 @@ int ask_yesno(const char *const str, const bool direct)
/// Translates the interrupt character for unix to ESC.
int get_keystroke(MultiQueue *events)
{
- char_u *buf = NULL;
+ uint8_t *buf = NULL;
int buflen = 150;
- int maxlen;
int len = 0;
int n;
int save_mapped_ctrl_c = mapped_ctrl_c;
mapped_ctrl_c = 0; // mappings are not used here
- for (;;) {
+ while (true) {
// flush output before waiting
ui_flush();
// Leave some room for check_termcode() to insert a key code into (max
// 5 chars plus NUL). And fix_input_buffer() can triple the number of
// bytes.
- maxlen = (buflen - 6 - len) / 3;
+ int maxlen = (buflen - 6 - len) / 3;
if (buf == NULL) {
buf = xmalloc((size_t)buflen);
} else if (maxlen < 10) {
@@ -113,7 +109,7 @@ int get_keystroke(MultiQueue *events)
// First time: blocking wait. Second time: wait up to 100ms for a
// terminal code to complete.
- n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events);
+ n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
if (n > 0) {
// Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);
@@ -166,7 +162,6 @@ int get_keystroke(MultiQueue *events)
int get_number(int colon, int *mouse_used)
{
int n = 0;
- int c;
int typed = 0;
if (mouse_used != NULL) {
@@ -181,10 +176,13 @@ int get_number(int colon, int *mouse_used)
no_mapping++;
allow_keys++; // no mapping here, but recognize keys
- for (;;) {
+ while (true) {
ui_cursor_goto(msg_row, msg_col);
- c = safe_vgetc();
+ int c = safe_vgetc();
if (ascii_isdigit(c)) {
+ if (n > INT_MAX / 10) {
+ return 0;
+ }
n = n * 10 + c - '0';
msg_putchar(c);
typed++;
diff --git a/src/nvim/input.h b/src/nvim/input.h
index 7975f21215..3d948fa4ca 100644
--- a/src/nvim/input.h
+++ b/src/nvim/input.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_INPUT_H
-#define NVIM_INPUT_H
+#pragma once
-#include "nvim/vim.h"
+#include "nvim/event/multiqueue.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "input.h.generated.h"
#endif
-#endif // NVIM_INPUT_H
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index 6de3b0a9d0..12543a2d42 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// insexpand.c: functions for Insert mode completion
#include <assert.h>
@@ -12,7 +9,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -23,23 +20,21 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -47,24 +42,26 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+#include "nvim/window.h"
// Definitions used for CTRL-X submode.
// Note: If you change CTRL-X submode, you must also maintain ctrl_x_msgs[]
@@ -91,6 +88,7 @@ enum {
CTRL_X_LOCAL_MSG = 15, ///< only used in "ctrl_x_msgs"
CTRL_X_EVAL = 16, ///< for builtin function complete()
CTRL_X_CMDLINE_CTRL_X = 17, ///< CTRL-X typed in CTRL_X_CMDLINE
+ CTRL_X_BUFNAMES = 18,
};
#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT]
@@ -162,7 +160,8 @@ struct compl_S {
/// state information used for getting the next set of insert completion
/// matches.
typedef struct {
- char *e_cpt; ///< current entry in 'complete'
+ char *e_cpt_copy; ///< copy of 'complete'
+ char *e_cpt; ///< current entry in "e_cpt_copy"
buf_T *ins_buf; ///< buffer being scanned
pos_T *cur_match_pos; ///< current match position
pos_T prev_match_pos; ///< previous match position
@@ -188,8 +187,8 @@ typedef enum {
CP_FAST = 32, ///< use fast_breakcheck instead of os_breakcheck
} cp_flags_T;
-static char e_hitend[] = N_("Hit end of paragraph");
-static char e_compldel[] = N_("E840: Completion function deleted text");
+static const char e_hitend[] = N_("Hit end of paragraph");
+static const char e_compldel[] = N_("E840: Completion function deleted text");
// All the current matches are stored in a list.
// "compl_first_match" points to the start of the list.
@@ -466,14 +465,13 @@ bool check_compl_option(bool dict_opt)
&& *curbuf->b_p_tsrfu == NUL && *p_tsrfu == NUL)) {
ctrl_x_mode = CTRL_X_NORMAL;
edit_submode = NULL;
- msg_attr((dict_opt
- ? _("'dictionary' option is empty")
- : _("'thesaurus' option is empty")), HL_ATTR(HLF_E));
+ msg((dict_opt ? _("'dictionary' option is empty") : _("'thesaurus' option is empty")),
+ HL_ATTR(HLF_E));
if (emsg_silent == 0 && !in_assert_fails) {
vim_beep(BO_COMPL);
setcursor();
ui_flush();
- os_delay(2004L, false);
+ os_delay(2004, false);
}
return false;
}
@@ -536,6 +534,8 @@ bool vim_is_ctrl_x_key(int c)
return c == Ctrl_S || c == Ctrl_P || c == Ctrl_N;
case CTRL_X_EVAL:
return (c == Ctrl_P || c == Ctrl_N);
+ case CTRL_X_BUFNAMES:
+ return (c == Ctrl_P || c == Ctrl_N);
}
internal_error("vim_is_ctrl_x_key()");
return false;
@@ -909,8 +909,6 @@ static bool ins_compl_equal(compl_T *match, char *str, size_t len)
/// Reduce the longest common string for match "match".
static void ins_compl_longest_match(compl_T *match)
{
- char *p, *s;
- int c1, c2;
int had_match;
if (compl_leader == NULL) {
@@ -933,11 +931,11 @@ static void ins_compl_longest_match(compl_T *match)
}
// Reduce the text if this match differs from compl_leader.
- p = compl_leader;
- s = match->cp_str;
+ char *p = compl_leader;
+ char *s = match->cp_str;
while (*p != NUL) {
- c1 = utf_ptr2char(p);
- c2 = utf_ptr2char(s);
+ int c1 = utf_ptr2char(p);
+ int c2 = utf_ptr2char(s);
if ((match->cp_flags & CP_ICASE)
? (mb_tolower(c1) != mb_tolower(c2))
@@ -1068,14 +1066,14 @@ static bool pum_enough_matches(void)
{
// Don't display the popup menu if there are no matches or there is only
// one (ignoring the original text).
- compl_T *compl = compl_first_match;
+ compl_T *comp = compl_first_match;
int i = 0;
do {
- if (compl == NULL || (!match_at_original_text(compl) && ++i == 2)) {
+ if (comp == NULL || (!match_at_original_text(comp) && ++i == 2)) {
break;
}
- compl = compl->cp_next;
- } while (!is_first_match(compl));
+ comp = comp->cp_next;
+ } while (!is_first_match(comp));
if (strstr(p_cot, "menuone") != NULL) {
return i >= 1;
@@ -1139,7 +1137,7 @@ static int ins_compl_build_pum(void)
{
// Need to build the popup menu list.
compl_match_arraysize = 0;
- compl_T *compl = compl_first_match;
+ compl_T *comp = compl_first_match;
// If it's user complete function and refresh_always,
// do not use "compl_leader" as prefix filter.
@@ -1150,13 +1148,13 @@ static int ins_compl_build_pum(void)
const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0;
do {
- if (!match_at_original_text(compl)
+ if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
compl_match_arraysize++;
}
- compl = compl->cp_next;
- } while (compl != NULL && !is_first_match(compl));
+ comp = comp->cp_next;
+ } while (comp != NULL && !is_first_match(comp));
if (compl_match_arraysize == 0) {
return -1;
@@ -1173,46 +1171,46 @@ static int ins_compl_build_pum(void)
bool did_find_shown_match = false;
int cur = -1;
int i = 0;
- compl = compl_first_match;
+ comp = compl_first_match;
do {
- if (!match_at_original_text(compl)
+ if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(compl, compl_leader, (size_t)lead_len))) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
if (!shown_match_ok) {
- if (compl == compl_shown_match || did_find_shown_match) {
+ if (comp == compl_shown_match || did_find_shown_match) {
// This item is the shown match or this is the
// first displayed item after the shown match.
- compl_shown_match = compl;
+ compl_shown_match = comp;
did_find_shown_match = true;
shown_match_ok = true;
} else {
// Remember this displayed match for when the
// shown match is just below it.
- shown_compl = compl;
+ shown_compl = comp;
}
cur = i;
}
- if (compl->cp_text[CPT_ABBR] != NULL) {
- compl_match_array[i].pum_text = compl->cp_text[CPT_ABBR];
+ if (comp->cp_text[CPT_ABBR] != NULL) {
+ compl_match_array[i].pum_text = comp->cp_text[CPT_ABBR];
} else {
- compl_match_array[i].pum_text = compl->cp_str;
+ compl_match_array[i].pum_text = comp->cp_str;
}
- compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND];
- compl_match_array[i].pum_info = compl->cp_text[CPT_INFO];
- if (compl->cp_text[CPT_MENU] != NULL) {
- compl_match_array[i++].pum_extra = compl->cp_text[CPT_MENU];
+ compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
+ compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
+ if (comp->cp_text[CPT_MENU] != NULL) {
+ compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
} else {
- compl_match_array[i++].pum_extra = compl->cp_fname;
+ compl_match_array[i++].pum_extra = comp->cp_fname;
}
}
- if (compl == compl_shown_match) {
+ if (comp == compl_shown_match) {
did_find_shown_match = true;
// When the original text is the shown match don't set
// compl_shown_match.
- if (match_at_original_text(compl)) {
+ if (match_at_original_text(comp)) {
shown_match_ok = true;
}
@@ -1223,8 +1221,8 @@ static int ins_compl_build_pum(void)
shown_match_ok = true;
}
}
- compl = compl->cp_next;
- } while (compl != NULL && !is_first_match(compl));
+ comp = comp->cp_next;
+ } while (comp != NULL && !is_first_match(comp));
if (!shown_match_ok) { // no displayed match at all
cur = -1;
@@ -1241,9 +1239,6 @@ void ins_compl_show_pum(void)
return;
}
- // Dirty hard-coded hack: remove any matchparen highlighting.
- do_cmdline_cmd("if exists('g:loaded_matchparen')|3match none|endif");
-
// Update the screen before drawing the popup menu over it.
update_screen();
@@ -1298,11 +1293,9 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int t
{
char *dict = dict_start;
char *ptr;
- char *buf;
regmatch_T regmatch;
char **files;
int count;
- int save_p_scs;
Direction dir = compl_direction;
if (*dict == NUL) {
@@ -1315,11 +1308,11 @@ static void ins_compl_dictionaries(char *dict_start, char *pat, int flags, int t
}
}
- buf = xmalloc(LSIZE);
+ char *buf = xmalloc(LSIZE);
regmatch.regprog = NULL; // so that we can goto theend
// If 'infercase' is set, don't use 'smartcase' here
- save_p_scs = p_scs;
+ int save_p_scs = p_scs;
if (curbuf->b_p_inf) {
p_scs = false;
}
@@ -1415,7 +1408,7 @@ static int thesaurus_add_words_in_line(char *fname, char **buf_arg, int dir, con
// different classes, only separate words
// with single-byte non-word characters.
while (*ptr != NUL) {
- const int l = utfc_ptr2len((const char *)ptr);
+ const int l = utfc_ptr2len(ptr);
if (l < 2 && !vim_iswordc((uint8_t)(*ptr))) {
break;
@@ -1443,18 +1436,13 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
char *buf, Direction *dir)
FUNC_ATTR_NONNULL_ARG(2, 7)
{
- char *ptr;
- int i;
- FILE *fp;
- int add_r;
-
- for (i = 0; i < count && !got_int && !compl_interrupted; i++) {
- fp = os_fopen(files[i], "r"); // open dictionary file
+ for (int i = 0; i < count && !got_int && !compl_interrupted; i++) {
+ FILE *fp = os_fopen(files[i], "r"); // open dictionary file
if (flags != DICT_EXACT && !shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE,
_("Scanning dictionary: %s"), files[i]);
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
if (fp == NULL) {
@@ -1464,7 +1452,7 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
// Read dictionary file line by line.
// Check each line for a match.
while (!got_int && !compl_interrupted && !vim_fgets(buf, LSIZE, fp)) {
- ptr = buf;
+ char *ptr = buf;
while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) {
ptr = regmatch->startp[0];
if (ctrl_x_mode_line_or_eval()) {
@@ -1472,9 +1460,9 @@ static void ins_compl_files(int count, char **files, int thesaurus, int flags, r
} else {
ptr = find_word_end(ptr);
}
- add_r = ins_compl_add_infercase(regmatch->startp[0],
- (int)(ptr - regmatch->startp[0]),
- p_ic, files[i], *dir, false);
+ int add_r = ins_compl_add_infercase(regmatch->startp[0],
+ (int)(ptr - regmatch->startp[0]),
+ p_ic, files[i], *dir, false);
if (thesaurus) {
// For a thesaurus, add all the words in the line
ptr = buf;
@@ -1532,9 +1520,7 @@ char *find_word_end(char *ptr)
/// @return a pointer to just after the line.
static char *find_line_end(char *ptr)
{
- char *s;
-
- s = ptr + strlen(ptr);
+ char *s = ptr + strlen(ptr);
while (s > ptr && (s[-1] == CAR || s[-1] == NL)) {
s--;
}
@@ -1544,8 +1530,6 @@ static char *find_line_end(char *ptr)
/// Free the list of completions
static void ins_compl_free(void)
{
- compl_T *match;
-
XFREE_CLEAR(compl_pattern);
XFREE_CLEAR(compl_leader);
@@ -1558,7 +1542,7 @@ static void ins_compl_free(void)
compl_curr_match = compl_first_match;
do {
- match = compl_curr_match;
+ compl_T *match = compl_curr_match;
compl_curr_match = compl_curr_match->cp_next;
xfree(match->cp_str);
// several entries may use the same fname, free it just once.
@@ -1749,9 +1733,9 @@ void ins_compl_addleader(int c)
return;
}
if ((cc = utf_char2len(c)) > 1) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
- utf_char2bytes(c, (char *)buf);
+ utf_char2bytes(c, buf);
buf[cc] = NUL;
ins_char_bytes(buf, (size_t)cc);
} else {
@@ -1805,12 +1789,9 @@ static void ins_compl_set_original_text(char *str)
/// matches.
void ins_compl_addfrommatch(void)
{
- char *p;
int len = (int)curwin->w_cursor.col - (int)compl_col;
- int c;
- compl_T *cp;
assert(compl_shown_match != NULL);
- p = compl_shown_match->cp_str;
+ char *p = compl_shown_match->cp_str;
if ((int)strlen(p) <= len) { // the match is too short
// When still at the original match use the first entry that matches
// the leader.
@@ -1819,7 +1800,7 @@ void ins_compl_addfrommatch(void)
}
p = NULL;
- for (cp = compl_shown_match->cp_next; cp != NULL
+ for (compl_T *cp = compl_shown_match->cp_next; cp != NULL
&& !is_first_match(cp); cp = cp->cp_next) {
if (compl_leader == NULL
|| ins_compl_equal(cp, compl_leader, strlen(compl_leader))) {
@@ -1832,7 +1813,7 @@ void ins_compl_addfrommatch(void)
}
}
p += len;
- c = utf_ptr2char(p);
+ int c = utf_ptr2char(p);
ins_compl_addleader(c);
}
@@ -2096,10 +2077,10 @@ bool ins_compl_prep(int c)
edit_submode_extra = NULL;
}
- // Ignore end of Select mode mapping and mouse scroll buttons.
+ // Ignore end of Select mode mapping and mouse scroll/movement.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
- || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT
- || c == K_COMMAND || c == K_LUA) {
+ || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_MOUSEMOVE
+ || c == K_EVENT || c == K_COMMAND || c == K_LUA) {
return retval;
}
@@ -2184,7 +2165,6 @@ bool ins_compl_prep(int c)
static void ins_compl_fixRedoBufForLeader(char *ptr_arg)
{
int len;
- char *p;
char *ptr = ptr_arg;
if (ptr == NULL) {
@@ -2195,7 +2175,7 @@ static void ins_compl_fixRedoBufForLeader(char *ptr_arg)
}
}
if (compl_orig_text != NULL) {
- p = compl_orig_text;
+ char *p = compl_orig_text;
for (len = 0; p[len] != NUL && p[len] == ptr[len]; len++) {}
if (len > 0) {
len -= utf_head_off(p, p + len);
@@ -2220,7 +2200,8 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag)
static win_T *wp = NULL;
if (flag == 'w') { // just windows
- if (buf == curbuf || wp == NULL) { // first call for this flag/expansion
+ if (buf == curbuf || !win_valid(wp)) {
+ // first call for this flag/expansion or window was closed
wp = curwin;
}
assert(wp);
@@ -2258,14 +2239,14 @@ static void copy_global_to_buflocal_cb(Callback *globcb, Callback *bufcb)
/// Invoked when the 'completefunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_completefunc_option(char **errmsg)
+const char *did_set_completefunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(curbuf->b_p_cfu, &cfu_cb) == FAIL) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
set_buflocal_cfu_callback(curbuf);
+ return NULL;
}
/// Copy the global 'completefunc' callback function to the buffer-local
@@ -2279,13 +2260,14 @@ void set_buflocal_cfu_callback(buf_T *buf)
/// Invoked when the 'omnifunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_omnifunc_option(buf_T *buf, char **errmsg)
+const char *did_set_omnifunc(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
if (option_set_callback_func(buf->b_p_ofu, &ofu_cb) == FAIL) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
set_buflocal_ofu_callback(buf);
+ return NULL;
}
/// Copy the global 'omnifunc' callback function to the buffer-local 'omnifunc'
@@ -2299,7 +2281,7 @@ void set_buflocal_ofu_callback(buf_T *buf)
/// Invoked when the 'thesaurusfunc' option is set. The option value can be a
/// name of a function (string), or function(<name>) or funcref(<name>) or a
/// lambda expression.
-void set_thesaurusfunc_option(char **errmsg)
+const char *did_set_thesaurusfunc(optset_T *args FUNC_ATTR_UNUSED)
{
int retval;
@@ -2311,9 +2293,7 @@ void set_thesaurusfunc_option(char **errmsg)
retval = option_set_callback_func(p_tsrfu, &tsrfu_cb);
}
- if (retval == FAIL) {
- *errmsg = e_invarg;
- }
+ return retval == FAIL ? e_invarg : NULL;
}
/// Mark the global 'completefunc' 'omnifunc' and 'thesaurusfunc' callbacks with
@@ -2363,13 +2343,11 @@ static void expand_by_function(int type, char *base)
{
list_T *matchlist = NULL;
dict_T *matchdict = NULL;
- char *funcname;
- pos_T pos;
typval_T rettv;
const int save_State = State;
assert(curbuf != NULL);
- funcname = get_complete_funcname(type);
+ char *funcname = get_complete_funcname(type);
if (*funcname == NUL) {
return;
}
@@ -2382,7 +2360,7 @@ static void expand_by_function(int type, char *base)
args[0].vval.v_number = 0;
args[1].vval.v_string = base != NULL ? base : "";
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
// Lock the text to avoid weird things from happening. Also disallow
// switching to another window, it should not be needed and may end up in
// Insert mode in another buffer.
@@ -2434,7 +2412,7 @@ theend:
}
}
-/// Add a match to the list of matches from VimL object
+/// Add a match to the list of matches from Vimscript object
///
/// @param[in] tv Object to get matches from.
/// @param[in] dir Completion direction.
@@ -2509,14 +2487,11 @@ static void ins_compl_add_list(list_T *const list)
/// Add completions from a dict.
static void ins_compl_add_dict(dict_T *dict)
{
- dictitem_T *di_refresh;
- dictitem_T *di_words;
-
// Check for optional "refresh" item.
compl_opt_refresh_always = false;
- di_refresh = tv_dict_find(dict, S_LEN("refresh"));
+ dictitem_T *di_refresh = tv_dict_find(dict, S_LEN("refresh"));
if (di_refresh != NULL && di_refresh->di_tv.v_type == VAR_STRING) {
- const char *v = (const char *)di_refresh->di_tv.vval.v_string;
+ const char *v = di_refresh->di_tv.vval.v_string;
if (v != NULL && strcmp(v, "always") == 0) {
compl_opt_refresh_always = true;
@@ -2524,7 +2499,7 @@ static void ins_compl_add_dict(dict_T *dict)
}
// Add completions from a "words" list.
- di_words = tv_dict_find(dict, S_LEN("words"));
+ dictitem_T *di_words = tv_dict_find(dict, S_LEN("words"));
if (di_words != NULL && di_words->di_tv.v_type == VAR_LIST) {
ins_compl_add_list(di_words->di_tv.vval.v_list);
}
@@ -2652,7 +2627,7 @@ static void ins_compl_update_sequence_numbers(void)
compl_T *match;
if (compl_dir_forward()) {
- // search backwards for the first valid (!= -1) number.
+ // Search backwards for the first valid (!= -1) number.
// This should normally succeed already at the first loop
// cycle, so it's fast!
for (match = compl_curr_match->cp_prev;
@@ -2672,7 +2647,7 @@ static void ins_compl_update_sequence_numbers(void)
}
} else { // BACKWARD
assert(compl_direction == BACKWARD);
- // search forwards (upwards) for the first valid (!= -1)
+ // Search forwards (upwards) for the first valid (!= -1)
// number. This should normally succeed already at the
// first loop cycle, so it's fast!
for (match = compl_curr_match->cp_next;
@@ -2683,8 +2658,7 @@ static void ins_compl_update_sequence_numbers(void)
}
}
if (match != NULL) {
- // go down and assign all numbers which are not
- // assigned yet
+ // go down and assign all numbers which are not assigned yet
for (match = match->cp_prev;
match && match->cp_number == -1;
match = match->cp_prev) {
@@ -2694,6 +2668,70 @@ static void ins_compl_update_sequence_numbers(void)
}
}
+static int info_add_completion_info(list_T *li)
+{
+ if (compl_first_match == NULL) {
+ return OK;
+ }
+
+ bool forward = compl_dir_forward();
+ compl_T *match = compl_first_match;
+ // There are four cases to consider here:
+ // 1) when just going forward through the menu,
+ // compl_first_match should point to the initial entry with
+ // number zero and CP_ORIGINAL_TEXT flag set
+ // 2) when just going backwards,
+ // compl-first_match should point to the last entry before
+ // the entry with the CP_ORIGINAL_TEXT flag set
+ // 3) when first going forwards and then backwards, e.g.
+ // pressing C-N, C-P, compl_first_match points to the
+ // last entry before the entry with the CP_ORIGINAL_TEXT
+ // flag set and next-entry moves opposite through the list
+ // compared to case 2, so pretend the direction is forward again
+ // 4) when first going backwards and then forwards, e.g.
+ // pressing C-P, C-N, compl_first_match points to the
+ // first entry with the CP_ORIGINAL_TEXT
+ // flag set and next-entry moves in opposite direction through the list
+ // compared to case 1, so pretend the direction is backwards again
+ //
+ // But only do this when the 'noselect' option is not active!
+
+ if (!compl_no_select) {
+ if (forward && !match_at_original_text(match)) {
+ forward = false;
+ } else if (!forward && match_at_original_text(match)) {
+ forward = true;
+ }
+ }
+
+ // Skip the element with the CP_ORIGINAL_TEXT flag at the beginning, in case of
+ // forward completion, or at the end, in case of backward completion.
+ match = forward ? match->cp_next
+ : (compl_no_select && match_at_original_text(match)
+ ? match->cp_prev : match->cp_prev->cp_prev);
+
+ while (match != NULL && !match_at_original_text(match)) {
+ dict_T *di = tv_dict_alloc();
+
+ tv_list_append_dict(li, di);
+ tv_dict_add_str(di, S_LEN("word"), EMPTY_IF_NULL(match->cp_str));
+ tv_dict_add_str(di, S_LEN("abbr"), EMPTY_IF_NULL(match->cp_text[CPT_ABBR]));
+ tv_dict_add_str(di, S_LEN("menu"), EMPTY_IF_NULL(match->cp_text[CPT_MENU]));
+ tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
+ tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
+ if (match->cp_user_data.v_type == VAR_UNKNOWN) {
+ // Add an empty string for backwards compatibility
+ tv_dict_add_str(di, S_LEN("user_data"), "");
+ } else {
+ tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
+ }
+
+ match = forward ? match->cp_next : match->cp_prev;
+ }
+
+ return OK;
+}
+
/// Get complete information
static void get_complete_info(list_T *what_list, dict_T *retdict)
{
@@ -2739,29 +2777,9 @@ static void get_complete_info(list_T *what_list, dict_T *retdict)
if (ret == OK && (what_flag & CI_WHAT_ITEMS)) {
list_T *li = tv_list_alloc(get_compl_len());
-
ret = tv_dict_add_list(retdict, S_LEN("items"), li);
- if (ret == OK && compl_first_match != NULL) {
- compl_T *match = compl_first_match;
- do {
- if (!match_at_original_text(match)) {
- dict_T *di = tv_dict_alloc();
-
- tv_list_append_dict(li, di);
- tv_dict_add_str(di, S_LEN("word"), EMPTY_IF_NULL(match->cp_str));
- tv_dict_add_str(di, S_LEN("abbr"), EMPTY_IF_NULL(match->cp_text[CPT_ABBR]));
- tv_dict_add_str(di, S_LEN("menu"), EMPTY_IF_NULL(match->cp_text[CPT_MENU]));
- tv_dict_add_str(di, S_LEN("kind"), EMPTY_IF_NULL(match->cp_text[CPT_KIND]));
- tv_dict_add_str(di, S_LEN("info"), EMPTY_IF_NULL(match->cp_text[CPT_INFO]));
- if (match->cp_user_data.v_type == VAR_UNKNOWN) {
- // Add an empty string for backwards compatibility
- tv_dict_add_str(di, S_LEN("user_data"), "");
- } else {
- tv_dict_add_tv(di, S_LEN("user_data"), &match->cp_user_data);
- }
- }
- match = match->cp_next;
- } while (match != NULL && !is_first_match(match));
+ if (ret == OK) {
+ ret = info_add_completion_info(li);
}
}
@@ -2878,20 +2896,20 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
st->dict_f = DICT_EXACT;
}
if (!shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE, _("Scanning: %s"),
st->ins_buf->b_fname == NULL
? buf_spname(st->ins_buf)
: st->ins_buf->b_sfname == NULL
? st->ins_buf->b_fname
: st->ins_buf->b_sfname);
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
} else if (*st->e_cpt == NUL) {
status = INS_COMPL_CPT_END;
} else {
if (ctrl_x_mode_line_or_eval()) {
- compl_type = -1;
+ // compl_type = -1;
} else if (*st->e_cpt == 'k' || *st->e_cpt == 's') {
if (*st->e_cpt == 'k') {
compl_type = CTRL_X_DICTIONARY;
@@ -2906,15 +2924,15 @@ static int process_next_cpt_value(ins_compl_next_state_T *st, int *compl_type_ar
compl_type = CTRL_X_PATH_PATTERNS;
} else if (*st->e_cpt == 'd') {
compl_type = CTRL_X_PATH_DEFINES;
+ } else if (*st->e_cpt == 'f') {
+ compl_type = CTRL_X_BUFNAMES;
} else if (*st->e_cpt == ']' || *st->e_cpt == 't') {
compl_type = CTRL_X_TAGS;
if (!shortmess(SHM_COMPLETIONSCAN)) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE, "%s", _("Scanning tags."));
- (void)msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ (void)msg_trunc(IObuff, true, HL_ATTR(HLF_R));
}
- } else {
- compl_type = -1;
}
// in any case e_cpt is advanced to the next entry
@@ -2940,7 +2958,7 @@ static void get_next_include_file_completion(int compl_type)
((compl_type == CTRL_X_PATH_DEFINES
&& !(compl_cont_status & CONT_SOL))
? FIND_DEFINE : FIND_ANY),
- 1L, ACTION_EXPAND, 1, MAXLNUM);
+ 1, ACTION_EXPAND, 1, MAXLNUM);
}
/// Get the next set of words matching "compl_pattern" in dictionary or
@@ -2950,11 +2968,11 @@ static void get_next_dict_tsr_completion(int compl_type, char *dict, int dict_f)
if (thesaurus_func_complete(compl_type)) {
expand_by_function(compl_type, compl_pattern);
} else {
- ins_compl_dictionaries(dict != NULL ? dict
+ ins_compl_dictionaries(dict != NULL
+ ? dict
: (compl_type == CTRL_X_THESAURUS
? (*curbuf->b_p_tsr == NUL ? p_tsr : curbuf->b_p_tsr)
- : (*curbuf->b_p_dict ==
- NUL ? p_dict : curbuf->b_p_dict)),
+ : (*curbuf->b_p_dict == NUL ? p_dict : curbuf->b_p_dict)),
compl_pattern,
dict != NULL ? dict_f : 0,
compl_type == CTRL_X_THESAURUS);
@@ -3043,18 +3061,18 @@ static void get_next_spell_completion(linenr_T lnum)
/// @param cur_match_pos current match position
/// @param match_len
/// @param cont_s_ipos next ^X<> will set initial_pos
-static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len,
- bool *cont_s_ipos)
+static char *ins_compl_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos, int *match_len,
+ bool *cont_s_ipos)
{
*match_len = 0;
- char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum, false) + cur_match_pos->col;
+ char *ptr = ml_get_buf(ins_buf, cur_match_pos->lnum) + cur_match_pos->col;
int len;
if (ctrl_x_mode_line_or_eval()) {
if (compl_status_adding()) {
if (cur_match_pos->lnum >= ins_buf->b_ml.ml_line_count) {
return NULL;
}
- ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false);
+ ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1);
if (!p_paste) {
ptr = skipwhite(ptr);
}
@@ -3082,7 +3100,7 @@ static char *ins_comp_get_next_word_or_line(buf_T *ins_buf, pos_T *cur_match_pos
// normal command "J" was used. IOSIZE is always greater than
// compl_length, so the next strncpy always works -- Acevedo
strncpy(IObuff, ptr, (size_t)len); // NOLINT(runtime/printf)
- ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1, false);
+ ptr = ml_get_buf(ins_buf, cur_match_pos->lnum + 1);
tmp_ptr = ptr = skipwhite(ptr);
// Find start of next word.
tmp_ptr = find_word_start(tmp_ptr);
@@ -3151,7 +3169,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
}
bool looped_around = false;
int found_new_match = FAIL;
- for (;;) {
+ while (true) {
bool cont_s_ipos = false;
msg_silent++; // Don't want messages for wrapscan.
@@ -3162,7 +3180,7 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
compl_direction, compl_pattern);
} else {
found_new_match = searchit(NULL, st->ins_buf, st->cur_match_pos,
- NULL, compl_direction, compl_pattern, 1L,
+ NULL, compl_direction, compl_pattern, 1,
SEARCH_KEEP + SEARCH_NFMSG, RE_LAST, NULL);
}
msg_silent--;
@@ -3206,8 +3224,8 @@ static int get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_
continue;
}
int len;
- char *ptr = ins_comp_get_next_word_or_line(st->ins_buf, st->cur_match_pos,
- &len, &cont_s_ipos);
+ char *ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos,
+ &len, &cont_s_ipos);
if (ptr == NULL) {
continue;
}
@@ -3265,6 +3283,9 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
case CTRL_X_SPELL:
get_next_spell_completion(st->first_match_pos.lnum);
break;
+ case CTRL_X_BUFNAMES:
+ get_next_bufname_token();
+ break;
default: // normal ^P/^N and ^X^L
found_new_match = get_next_default_completion(st, ini);
@@ -3282,6 +3303,19 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
return found_new_match;
}
+static void get_next_bufname_token(void)
+{
+ FOR_ALL_BUFFERS(b) {
+ if (b->b_p_bl && b->b_sfname != NULL) {
+ char *tail = path_tail(b->b_sfname);
+ if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) {
+ ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
+ p_ic ? CP_ICASE : 0, false);
+ }
+ }
+ }
+}
+
/// Get the next expansion(s), using "compl_pattern".
/// The search starts at position "ini" in curbuf and in the direction
/// compl_direction.
@@ -3292,7 +3326,7 @@ static bool get_next_completion_match(int type, ins_compl_next_state_T *st, pos_
static int ins_compl_get_exp(pos_T *ini)
{
static ins_compl_next_state_T st;
- int i;
+ static bool st_cleared = false;
int found_new_match;
int type = ctrl_x_mode;
@@ -3302,9 +3336,16 @@ static int ins_compl_get_exp(pos_T *ini)
FOR_ALL_BUFFERS(buf) {
buf->b_scanned = false;
}
+ if (!st_cleared) {
+ CLEAR_FIELD(st);
+ st_cleared = true;
+ }
st.found_all = false;
st.ins_buf = curbuf;
- st.e_cpt = (compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt;
+ xfree(st.e_cpt_copy);
+ // Make a copy of 'complete', in case the buffer is wiped out.
+ st.e_cpt_copy = xstrdup((compl_cont_status & CONT_LOCAL) ? "." : curbuf->b_p_cpt);
+ st.e_cpt = st.e_cpt_copy;
st.last_match_pos = st.first_match_pos = *ini;
} else if (st.ins_buf != curbuf && !buf_valid(st.ins_buf)) {
st.ins_buf = curbuf; // In case the buffer was wiped out.
@@ -3315,7 +3356,7 @@ static int ins_compl_get_exp(pos_T *ini)
st.cur_match_pos = compl_dir_forward() ? &st.last_match_pos : &st.first_match_pos;
// For ^N/^P loop over all the flags/windows/buffers in 'complete'
- for (;;) {
+ while (true) {
found_new_match = FAIL;
st.set_match_pos = false;
@@ -3361,7 +3402,7 @@ static int ins_compl_get_exp(pos_T *ini)
compl_started = true;
} else {
// Mark a buffer scanned when it has been scanned completely
- if (type == 0 || type == CTRL_X_PATH_PATTERNS) {
+ if (buf_valid(st.ins_buf) && (type == 0 || type == CTRL_X_PATH_PATTERNS)) {
assert(st.ins_buf);
st.ins_buf->b_scanned = true;
}
@@ -3376,7 +3417,7 @@ static int ins_compl_get_exp(pos_T *ini)
found_new_match = FAIL;
}
- i = -1; // total of matches, unknown
+ int i = -1; // total of matches, unknown
if (found_new_match == FAIL
|| (ctrl_x_mode_not_default() && !ctrl_x_mode_line_or_eval())) {
i = ins_compl_make_cyclic();
@@ -3387,8 +3428,8 @@ static int ins_compl_get_exp(pos_T *ini)
// just been made cyclic then we have to move compl_curr_match to the
// next or previous entry (if any) -- Acevedo
compl_curr_match = compl_dir_forward()
- ? compl_old_match->cp_next
- : compl_old_match->cp_prev;
+ ? compl_old_match->cp_next
+ : compl_old_match->cp_prev;
if (compl_curr_match == NULL) {
compl_curr_match = compl_old_match;
}
@@ -3426,11 +3467,9 @@ static void ins_compl_update_shown_match(void)
/// Delete the old text being completed.
void ins_compl_delete(void)
{
- int col;
-
// In insert mode: Delete the typed part.
// In replace mode: Put the old characters back, if any.
- col = compl_col + (compl_status_adding() ? compl_length : 0);
+ int col = compl_col + (compl_status_adding() ? compl_length : 0);
if ((int)curwin->w_cursor.col > col) {
if (stop_arrow() == FAIL) {
return;
@@ -3440,7 +3479,7 @@ void ins_compl_delete(void)
// TODO(vim): is this sufficient for redrawing? Redrawing everything
// causes flicker, thus we can't do that.
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
// clear v:completed_item
set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED));
}
@@ -3484,7 +3523,7 @@ static void ins_compl_show_filename(void)
msg_hist_off = true;
vim_snprintf(IObuff, IOSIZE, "%s %s%s", lead,
s > compl_shown_match->cp_fname ? "<" : "", s);
- msg(IObuff);
+ msg(IObuff, 0);
msg_hist_off = false;
redraw_cmdline = false; // don't overwrite!
}
@@ -3604,6 +3643,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
int num_matches = -1;
int todo = count;
const bool started = compl_started;
+ buf_T *const orig_curbuf = curbuf;
// When user complete function return -1 for findstart which is next
// time of 'always', compl_shown_match become NULL.
@@ -3617,7 +3657,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
}
if (allow_get_expansion && insert_match
- && (!(compl_get_longest || compl_restarting) || compl_used_match)) {
+ && (!compl_get_longest || compl_used_match)) {
// Delete old text to be replaced
ins_compl_delete();
}
@@ -3639,6 +3679,12 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
return -1;
}
+ if (curbuf != orig_curbuf) {
+ // In case some completion function switched buffer, don't want to
+ // insert the completion elsewhere.
+ return -1;
+ }
+
// Insert the text of the new completion, or the compl_leader.
if (compl_no_insert && !started) {
ins_bytes(compl_orig_text + get_compl_len());
@@ -3768,15 +3814,13 @@ static bool ins_compl_pum_key(int c)
/// Returns 1 for most keys, height of the popup menu for page-up/down keys.
static int ins_compl_key2count(int c)
{
- int h;
-
if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
int offset = pum_want.item - pum_selected_item;
return abs(offset);
}
if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) {
- h = pum_get_height();
+ int h = pum_get_height();
if (h > 3) {
h -= 2; // keep some context
}
@@ -4304,9 +4348,8 @@ static void ins_compl_show_statusmsg(void)
if (edit_submode_extra != NULL) {
if (!p_smd) {
msg_hist_off = true;
- msg_attr((const char *)edit_submode_extra,
- (edit_submode_highl < HLF_COUNT
- ? HL_ATTR(edit_submode_highl) : 0));
+ msg(edit_submode_extra, (edit_submode_highl < HLF_COUNT
+ ? HL_ATTR(edit_submode_highl) : 0));
msg_hist_off = false;
}
} else {
@@ -4320,13 +4363,8 @@ static void ins_compl_show_statusmsg(void)
/// Returns OK if completion was done, FAIL if something failed.
int ins_complete(int c, bool enable_pum)
{
- int n;
- int save_w_wrow;
- int save_w_leftcol;
- int insert_match;
-
compl_direction = ins_compl_key2dir(c);
- insert_match = ins_compl_use_match(c);
+ int insert_match = ins_compl_use_match(c);
if (!compl_started) {
if (ins_compl_start() == FAIL) {
@@ -4340,9 +4378,9 @@ int ins_complete(int c, bool enable_pum)
compl_shows_dir = compl_direction;
// Find next match (and following matches).
- save_w_wrow = curwin->w_wrow;
- save_w_leftcol = curwin->w_leftcol;
- n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
+ int save_w_wrow = curwin->w_wrow;
+ int save_w_leftcol = curwin->w_leftcol;
+ int n = ins_compl_next(true, ins_compl_key2count(c), insert_match, false);
if (n > 1) { // all matches have been found
compl_matches = n;
diff --git a/src/nvim/insexpand.h b/src/nvim/insexpand.h
index 83ba14e0d2..121d5568ff 100644
--- a/src/nvim/insexpand.h
+++ b/src/nvim/insexpand.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_INSEXPAND_H
-#define NVIM_INSEXPAND_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/macros.h"
-#include "nvim/vim.h"
+#include "nvim/macros_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "insexpand.h.generated.h"
#endif
-#endif // NVIM_INSEXPAND_H
diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c
index e19806e464..745500fe39 100644
--- a/src/nvim/keycodes.c
+++ b/src/nvim/keycodes.c
@@ -1,29 +1,25 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "keycodes.c.generated.h"
@@ -34,18 +30,18 @@
static const struct modmasktable {
uint16_t mod_mask; ///< Bit-mask for particular key modifier.
uint16_t mod_flag; ///< Bit(s) for particular key modifier.
- char_u name; ///< Single letter name of modifier.
+ char name; ///< Single letter name of modifier.
} mod_mask_table[] = {
- { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' },
- { MOD_MASK_META, MOD_MASK_META, (char_u)'T' },
- { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' },
- { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' },
- { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' },
- { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' },
+ { MOD_MASK_ALT, MOD_MASK_ALT, 'M' },
+ { MOD_MASK_META, MOD_MASK_META, 'T' },
+ { MOD_MASK_CTRL, MOD_MASK_CTRL, 'C' },
+ { MOD_MASK_SHIFT, MOD_MASK_SHIFT, 'S' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, '2' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, '3' },
+ { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, '4' },
+ { MOD_MASK_CMD, MOD_MASK_CMD, 'D' },
// 'A' must be the last one
- { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' },
+ { MOD_MASK_ALT, MOD_MASK_ALT, 'A' },
{ 0, 0, NUL }
// NOTE: when adding an entry, update MAX_KEY_NAME_LEN!
};
@@ -386,7 +382,7 @@ int name_to_mod_mask(int c)
{
c = TOUPPER_ASC(c);
for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) {
- if (c == mod_mask_table[i].name) {
+ if (c == (uint8_t)mod_mask_table[i].name) {
return mod_mask_table[i].mod_flag;
}
}
@@ -468,16 +464,12 @@ int handle_x_keys(const int key)
}
/// @return a string which contains the name of the given key when the given modifiers are down.
-char_u *get_special_key_name(int c, int modifiers)
+char *get_special_key_name(int c, int modifiers)
{
- static char_u string[MAX_KEY_NAME_LEN + 1];
-
- int i, idx;
- int table_idx;
- char *s;
+ static char string[MAX_KEY_NAME_LEN + 1];
string[0] = '<';
- idx = 1;
+ int idx = 1;
// Key that stands for a normal character.
if (IS_SPECIAL(c) && KEY2TERMCAP0(c) == KS_KEY) {
@@ -487,7 +479,7 @@ char_u *get_special_key_name(int c, int modifiers)
// Translate shifted special keys into unshifted keys and set modifier.
// Same for CTRL and ALT modifiers.
if (IS_SPECIAL(c)) {
- for (i = 0; modifier_keys_table[i] != 0; i += MOD_KEYS_ENTRY_SIZE) {
+ for (int i = 0; modifier_keys_table[i] != 0; i += MOD_KEYS_ENTRY_SIZE) {
if (KEY2TERMCAP0(c) == (int)modifier_keys_table[i + 1]
&& (int)KEY2TERMCAP1(c) == (int)modifier_keys_table[i + 2]) {
modifiers |= modifier_keys_table[i];
@@ -499,7 +491,7 @@ char_u *get_special_key_name(int c, int modifiers)
}
// try to find the key in the special key table
- table_idx = find_special_key_in_table(c);
+ int table_idx = find_special_key_in_table(c);
// When not a known special key, and not a printable character, try to
// extract modifiers.
@@ -520,11 +512,11 @@ char_u *get_special_key_name(int c, int modifiers)
}
// translate the modifier into a string
- for (i = 0; mod_mask_table[i].name != 'A'; i++) {
+ for (int i = 0; mod_mask_table[i].name != 'A'; i++) {
if ((modifiers & mod_mask_table[i].mod_mask)
== mod_mask_table[i].mod_flag) {
string[idx++] = mod_mask_table[i].name;
- string[idx++] = (char_u)'-';
+ string[idx++] = '-';
}
}
@@ -532,18 +524,18 @@ char_u *get_special_key_name(int c, int modifiers)
if (IS_SPECIAL(c)) {
string[idx++] = 't';
string[idx++] = '_';
- string[idx++] = (char_u)KEY2TERMCAP0(c);
- string[idx++] = KEY2TERMCAP1(c);
+ string[idx++] = (char)(uint8_t)KEY2TERMCAP0(c);
+ string[idx++] = (char)(uint8_t)KEY2TERMCAP1(c);
} else {
// Not a special key, only modifiers, output directly.
if (utf_char2len(c) > 1) {
- idx += utf_char2bytes(c, (char *)string + idx);
+ idx += utf_char2bytes(c, string + idx);
} else if (vim_isprintc(c)) {
- string[idx++] = (char_u)c;
+ string[idx++] = (char)(uint8_t)c;
} else {
- s = transchar(c);
+ char *s = transchar(c);
while (*s) {
- string[idx++] = (uint8_t)(*s++);
+ string[idx++] = *s++;
}
}
}
@@ -572,8 +564,8 @@ char_u *get_special_key_name(int c, int modifiers)
/// @param[out] did_simplify found <C-H>, etc.
///
/// @return Number of characters added to dst, zero for no match.
-unsigned int trans_special(const char **const srcp, const size_t src_len, char *const dst,
- const int flags, const bool escape_ks, bool *const did_simplify)
+unsigned trans_special(const char **const srcp, const size_t src_len, char *const dst,
+ const int flags, const bool escape_ks, bool *const did_simplify)
FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
int modifiers = 0;
@@ -582,7 +574,7 @@ unsigned int trans_special(const char **const srcp, const size_t src_len, char *
return 0;
}
- return special_to_buf(key, modifiers, escape_ks, (char_u *)dst);
+ return special_to_buf(key, modifiers, escape_ks, dst);
}
/// Put the character sequence for "key" with "modifiers" into "dst" and return
@@ -590,27 +582,27 @@ unsigned int trans_special(const char **const srcp, const size_t src_len, char *
/// When "escape_ks" is true escape K_SPECIAL bytes in the character.
/// The sequence is not NUL terminated.
/// This is how characters in a string are encoded.
-unsigned int special_to_buf(int key, int modifiers, bool escape_ks, char_u *dst)
+unsigned special_to_buf(int key, int modifiers, bool escape_ks, char *dst)
{
- unsigned int dlen = 0;
+ unsigned dlen = 0;
// Put the appropriate modifier in a string.
if (modifiers != 0) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = KS_MODIFIER;
- dst[dlen++] = (char_u)modifiers;
+ dst[dlen++] = (char)(uint8_t)K_SPECIAL;
+ dst[dlen++] = (char)(uint8_t)KS_MODIFIER;
+ dst[dlen++] = (char)(uint8_t)modifiers;
}
if (IS_SPECIAL(key)) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = (char_u)KEY2TERMCAP0(key);
- dst[dlen++] = KEY2TERMCAP1(key);
+ dst[dlen++] = (char)(uint8_t)K_SPECIAL;
+ dst[dlen++] = (char)(uint8_t)KEY2TERMCAP0(key);
+ dst[dlen++] = (char)(uint8_t)KEY2TERMCAP1(key);
} else if (escape_ks) {
- char_u *after = add_char2buf(key, dst + dlen);
+ char *after = add_char2buf(key, dst + dlen);
assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX);
- dlen = (unsigned int)(after - dst);
+ dlen = (unsigned)(after - dst);
} else {
- dlen += (unsigned int)utf_char2bytes(key, (char *)dst + dlen);
+ dlen += (unsigned)utf_char2bytes(key, dst + dlen);
}
return dlen;
@@ -629,15 +621,9 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
const int flags, bool *const did_simplify)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3)
{
- const char *last_dash;
- const char *end_of_name;
- const char *src;
const char *bp;
const char *const end = *srcp + src_len - 1;
const bool in_string = flags & FSK_IN_STRING;
- int modifiers;
- int bit;
- int key;
uvarnumber_T n;
int l;
@@ -645,7 +631,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
return 0;
}
- src = *srcp;
+ const char *src = *srcp;
if (src[0] != '<') {
return 0;
}
@@ -654,7 +640,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
}
// Find end of modifier list
- last_dash = src;
+ const char *last_dash = src;
for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) {
if (*bp == '-') {
last_dash = bp;
@@ -674,7 +660,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') {
bp += 3; // skip t_xx, xx may be '-' or '>'
} else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) {
- vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true);
+ vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0, true, NULL);
if (l == 0) {
emsg(_(e_invarg));
return 0;
@@ -685,13 +671,14 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
}
if (bp <= end && *bp == '>') { // found matching '>'
- end_of_name = bp + 1;
+ int key;
+ const char *end_of_name = bp + 1;
// Which modifiers are given?
- modifiers = 0x0;
+ int modifiers = 0x0;
for (bp = src + 1; bp < last_dash; bp++) {
if (*bp != '-') {
- bit = name_to_mod_mask((uint8_t)(*bp));
+ int bit = name_to_mod_mask((uint8_t)(*bp));
if (bit == 0x0) {
break; // Illegal modifier name
}
@@ -704,7 +691,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (STRNICMP(last_dash + 1, "char-", 5) == 0
&& ascii_isdigit(last_dash[6])) {
// <Char-123> or <Char-033> or <Char-0x33>
- vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true);
+ vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, true, NULL);
if (l == 0) {
emsg(_(e_invarg));
return 0;
@@ -723,7 +710,7 @@ int find_special_key(const char **const srcp, const size_t src_len, int *const m
if (modifiers != 0 && last_dash[l + 1] == '>') {
key = utf_ptr2char(last_dash + off);
} else {
- key = get_special_key_code((char_u *)last_dash + off);
+ key = get_special_key_code(last_dash + off);
if (!(flags & FSK_KEEP_X_KEY)) {
key = handle_x_keys(key);
}
@@ -822,18 +809,22 @@ int find_special_key_in_table(int c)
/// a termcap name.
///
/// @return Key code or 0 if not found.
-int get_special_key_code(const char_u *name)
+int get_special_key_code(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
+ if (name[0] == 't' && name[1] == '_' && name[2] != NUL && name[3] != NUL) {
+ return TERMCAP2KEY((uint8_t)name[2], (uint8_t)name[3]);
+ }
+
for (int i = 0; key_names_table[i].name != NULL; i++) {
const char *const table_name = key_names_table[i].name;
int j;
- for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
- if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
+ for (j = 0; ascii_isident((uint8_t)name[j]) && table_name[j] != NUL; j++) {
+ if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC((uint8_t)name[j])) {
break;
}
}
- if (!ascii_isident(name[j]) && table_name[j] == NUL) {
+ if (!ascii_isident((uint8_t)name[j]) && table_name[j] == NUL) {
return key_names_table[i].key;
}
}
@@ -845,9 +836,7 @@ int get_special_key_code(const char_u *name)
/// @return which button is down or was released.
int get_mouse_button(int code, bool *is_click, bool *is_drag)
{
- int i;
-
- for (i = 0; mouse_table[i].pseudo_code; i++) {
+ for (int i = 0; mouse_table[i].pseudo_code; i++) {
if (code == mouse_table[i].pseudo_code) {
*is_click = mouse_table[i].is_click;
*is_drag = mouse_table[i].is_drag;
@@ -873,25 +862,24 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
/// If `*bufp` is non-NULL, it will be used directly,
/// and is assumed to be 128 bytes long (enough for transcoding LHS of mapping),
/// and will be set to NULL in case of failure.
+/// @param[in] sid_arg Script ID to use for <SID>, or 0 to use current_sctx
/// @param[in] flags REPTERM_FROM_PART see above
/// REPTERM_DO_LT also translate <lt>
/// REPTERM_NO_SPECIAL do not accept <key> notation
/// REPTERM_NO_SIMPLIFY do not simplify <C-H> into 0x08, etc.
-/// @param[out] did_simplify set when some <C-H> code was simplied, unless it is NULL.
+/// @param[out] did_simplify set when some <C-H> code was simplified, unless it is NULL.
/// @param[in] cpo_flags Relevant flags derived from p_cpo, see CPO_TO_CPO_FLAGS.
///
/// @return The same as what `*bufp` is set to.
char *replace_termcodes(const char *const from, const size_t from_len, char **const bufp,
- const int flags, bool *const did_simplify, const int cpo_flags)
+ const scid_T sid_arg, const int flags, bool *const did_simplify,
+ const int cpo_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
- ssize_t i;
- size_t slen;
char key;
size_t dlen = 0;
const char *src;
const char *const end = from + from_len - 1;
- char *result; // buffer for resulting string
const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character
const bool do_special = !(flags & REPTERM_NO_SPECIAL);
@@ -901,23 +889,10 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
// Allocate space for the translation. Worst case a single character is
// replaced by 6 bytes (shifted special key), plus a NUL at the end.
const size_t buf_len = allocated ? from_len * 6 + 1 : 128;
- result = allocated ? xmalloc(buf_len) : *bufp;
+ char *result = allocated ? xmalloc(buf_len) : *bufp; // buffer for resulting string
src = from;
- // Check for #n at start only: function key n
- if ((flags & REPTERM_FROM_PART) && from_len > 1 && src[0] == '#'
- && ascii_isdigit(src[1])) { // function key
- result[dlen++] = (char)K_SPECIAL;
- result[dlen++] = 'k';
- if (src[1] == '0') {
- result[dlen++] = ';'; // #0 is F10 is "k;"
- } else {
- result[dlen++] = src[1]; // #3 is F3 is "k3"
- }
- src += 2;
- }
-
// Copy each byte from *from to result[dlen]
while (src <= end) {
if (!allocated && dlen + 64 > buf_len) {
@@ -926,27 +901,28 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
// Check for special <> keycodes, like "<C-S-LeftMouse>"
if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3
&& strncmp(src, "<lt>", 4) != 0))) {
- // Replace <SID> by K_SNR <script-nr> _.
+ // Change <SID>Func to K_SNR <script-nr> _Func. This name is used
+ // for script-local user functions.
// (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14)
if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) {
- if (current_sctx.sc_sid <= 0) {
+ if (sid_arg < 0 || (sid_arg == 0 && current_sctx.sc_sid <= 0)) {
emsg(_(e_usingsid));
} else {
+ const scid_T sid = sid_arg != 0 ? sid_arg : current_sctx.sc_sid;
src += 5;
result[dlen++] = (char)K_SPECIAL;
result[dlen++] = (char)KS_EXTRA;
result[dlen++] = KE_SNR;
- snprintf(result + dlen, buf_len - dlen, "%" PRId64,
- (int64_t)current_sctx.sc_sid);
+ snprintf(result + dlen, buf_len - dlen, "%" PRId64, (int64_t)sid);
dlen += strlen(result + dlen);
result[dlen++] = '_';
continue;
}
}
- slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen,
- FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY),
- true, did_simplify);
+ size_t slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen,
+ FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY),
+ true, did_simplify);
if (slen) {
dlen += slen;
continue;
@@ -1002,7 +978,7 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
}
// skip multibyte char correctly
- for (i = utfc_ptr2len_len((char *)src, (int)(end - src) + 1); i > 0; i--) {
+ for (ssize_t i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) {
// If the character is K_SPECIAL, replace it with K_SPECIAL
// KS_SPECIAL KE_FILLER.
if (*src == (char)K_SPECIAL) {
@@ -1033,20 +1009,20 @@ char *replace_termcodes(const char *const from, const size_t from_len, char **co
/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes.
///
/// @return Pointer to after the added bytes.
-char_u *add_char2buf(int c, char_u *s)
+char *add_char2buf(int c, char *s)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- char_u temp[MB_MAXBYTES + 1];
- const int len = utf_char2bytes(c, (char *)temp);
+ char temp[MB_MAXBYTES + 1];
+ const int len = utf_char2bytes(c, temp);
for (int i = 0; i < len; i++) {
c = (uint8_t)temp[i];
// Need to escape K_SPECIAL like in the typeahead buffer.
if (c == K_SPECIAL) {
- *s++ = K_SPECIAL;
- *s++ = KS_SPECIAL;
+ *s++ = (char)(uint8_t)K_SPECIAL;
+ *s++ = (char)(uint8_t)KS_SPECIAL;
*s++ = KE_FILLER;
} else {
- *s++ = (char_u)c;
+ *s++ = (char)(uint8_t)c;
}
}
return s;
@@ -1060,9 +1036,9 @@ char *vim_strsave_escape_ks(char *p)
// illegal utf-8 byte:
// 0xc0 -> 0xc3 - 0x80 -> 0xc3 K_SPECIAL KS_SPECIAL KE_FILLER
char *res = xmalloc(strlen(p) * 4 + 1);
- char_u *d = (char_u *)res;
- for (char_u *s = (char_u *)p; *s != NUL;) {
- if (s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
+ char *d = res;
+ for (char *s = p; *s != NUL;) {
+ if ((uint8_t)s[0] == K_SPECIAL && s[1] != NUL && s[2] != NUL) {
// Copy special key unmodified.
*d++ = *s++;
*d++ = *s++;
@@ -1070,8 +1046,8 @@ char *vim_strsave_escape_ks(char *p)
} else {
// Add character, possibly multi-byte to destination, escaping
// K_SPECIAL. Be careful, it can be an illegal byte!
- d = add_char2buf(utf_ptr2char((char *)s), d);
- s += utf_ptr2len((char *)s);
+ d = add_char2buf(utf_ptr2char(s), d);
+ s += utf_ptr2len(s);
}
}
*d = NUL;
@@ -1081,9 +1057,9 @@ char *vim_strsave_escape_ks(char *p)
/// Remove escaping from K_SPECIAL characters. Reverse of
/// vim_strsave_escape_ks(). Works in-place.
-void vim_unescape_ks(char_u *p)
+void vim_unescape_ks(char *p)
{
- char_u *s = p, *d = p;
+ uint8_t *s = (uint8_t *)p, *d = (uint8_t *)p;
while (*s != NUL) {
if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) {
@@ -1095,15 +1071,3 @@ void vim_unescape_ks(char_u *p)
}
*d = NUL;
}
-
-/// Logs a single key as a human-readable keycode.
-void log_key(int log_level, int key)
-{
- if (log_level < MIN_LOG_LEVEL) {
- return;
- }
- char *keyname = key == K_EVENT
- ? "K_EVENT"
- : (char *)get_special_key_name(key, mod_mask);
- LOG(log_level, "input: %s", keyname);
-}
diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h
index 7842dee92c..db9ef38cc3 100644
--- a/src/nvim/keycodes.h
+++ b/src/nvim/keycodes.h
@@ -1,10 +1,10 @@
-#ifndef NVIM_KEYCODES_H
-#define NVIM_KEYCODES_H
+#pragma once
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/strings.h"
// Keycode definitions for special keys.
@@ -59,7 +59,7 @@
// Used for switching Select mode back on after a mapping or menu.
#define KS_SELECT 245
-#define K_SELECT_STRING (char_u *)"\200\365X"
+#define K_SELECT_STRING "\200\365X"
/// Used a termcap entry that produces a normal character.
#define KS_KEY 242
@@ -500,4 +500,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "keycodes.h.generated.h"
#endif
-#endif // NVIM_KEYCODES_H
diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h
index c6d3f74ec1..40769e44b5 100644
--- a/src/nvim/lib/queue.h
+++ b/src/nvim/lib/queue.h
@@ -17,8 +17,7 @@
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-#ifndef NVIM_LIB_QUEUE_H
-#define NVIM_LIB_QUEUE_H
+#pragma once
#include <stddef.h>
@@ -91,5 +90,3 @@ static inline void QUEUE_REMOVE(QUEUE *const q) FUNC_ATTR_ALWAYS_INLINE
q->prev->next = q->next;
q->next->prev = q->prev;
}
-
-#endif // NVIM_LIB_QUEUE_H
diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h
index 907018a06e..c8abccfeb4 100644
--- a/src/nvim/lib/ringbuf.h
+++ b/src/nvim/lib/ringbuf.h
@@ -12,8 +12,8 @@
/// - idx_p: get pointer to the element at given index.
/// - insert: insert element at given position.
/// - remove: remove element from given position.
-#ifndef NVIM_LIB_RINGBUF_H
-#define NVIM_LIB_RINGBUF_H
+
+#pragma once
#include <assert.h>
#include <stddef.h>
@@ -288,5 +288,3 @@
rb->first = _RINGBUF_NEXT(rb, rb->first); \
} \
}
-
-#endif // NVIM_LIB_RINGBUF_H
diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c
index a9dac40731..c34b303193 100644
--- a/src/nvim/linematch.c
+++ b/src/nvim/linematch.c
@@ -1,23 +1,28 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
+#include <math.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <string.h>
#include "nvim/linematch.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
+#include "nvim/pos_defs.h"
+
+#define LN_MAX_BUFS 8
+#define LN_DECISION_MAX 255 // pow(2, LN_MAX_BUFS(8)) - 1 = 255
// struct for running the diff linematch algorithm
-typedef struct {
- int *df_decision; // to keep track of this path traveled
+typedef struct diffcmppath_S diffcmppath_T;
+struct diffcmppath_S {
int df_lev_score; // to keep track of the total score of this path
- size_t df_path_idx; // current index of this path
-} diffcmppath_T;
-
-#define LN_MAX_BUFS 8
+ size_t df_path_n; // current index of this path
+ int df_choice_mem[LN_DECISION_MAX + 1];
+ int df_choice[LN_DECISION_MAX];
+ diffcmppath_T *df_decision[LN_DECISION_MAX]; // to keep track of this path traveled
+ size_t df_optimal_choice;
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "linematch.c.generated.h"
@@ -64,26 +69,6 @@ static int matching_chars_iwhite(const char *s1, const char *s2)
return matching;
}
-/// update the path of a point in the diff linematch algorithm
-/// @param diffcmppath
-/// @param score
-/// @param to
-/// @param from
-/// @param choice
-static void update_path_flat(diffcmppath_T *diffcmppath, int score, size_t to, size_t from,
- const int choice)
-{
- size_t path_idx = diffcmppath[from].df_path_idx;
-
- for (size_t k = 0; k < path_idx; k++) {
- diffcmppath[to].df_decision[k] = diffcmppath[from].df_decision[k];
- }
-
- diffcmppath[to].df_decision[path_idx] = choice;
- diffcmppath[to].df_lev_score = score;
- diffcmppath[to].df_path_idx = path_idx + 1;
-}
-
#define MATCH_CHAR_MAX_LEN 800
/// Return matching characters between "s1" and "s2" whilst respecting sequence order.
@@ -157,10 +142,13 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
return matched_chars;
}
-void fastforward_buf_to_lnum(const char **s, long lnum)
+void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
{
- for (long i = 0; i < lnum - 1; i++) {
+ for (int i = 0; i < lnum - 1; i++) {
*s = strchr(*s, '\n');
+ if (!*s) {
+ return;
+ }
(*s)++;
}
}
@@ -183,7 +171,7 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
{
if (path_idx == npaths) {
if ((*choice) > 0) {
- int from_vals[LN_MAX_BUFS];
+ int from_vals[LN_MAX_BUFS] = { 0 };
const int *to_vals = df_iters;
const char *current_lines[LN_MAX_BUFS];
for (size_t k = 0; k < ndiffs; k++) {
@@ -203,17 +191,14 @@ static void try_possible_paths(const int *df_iters, const size_t *paths, const i
int matched_chars = count_n_matched_chars(current_lines, ndiffs, iwhite);
int score = diffcmppath[unwrapped_idx_from].df_lev_score + matched_chars;
if (score > diffcmppath[unwrapped_idx_to].df_lev_score) {
- update_path_flat(diffcmppath, score, unwrapped_idx_to, unwrapped_idx_from, *choice);
- }
- } else {
- // initialize the 0, 0, 0 ... choice
- size_t i = 0;
- while (i < ndiffs && df_iters[i] == 0) {
- i++;
- if (i == ndiffs) {
- diffcmppath[0].df_lev_score = 0;
- diffcmppath[0].df_path_idx = 0;
- }
+ diffcmppath[unwrapped_idx_to].df_path_n = 1;
+ diffcmppath[unwrapped_idx_to].df_decision[0] = &diffcmppath[unwrapped_idx_from];
+ diffcmppath[unwrapped_idx_to].df_choice[0] = *choice;
+ diffcmppath[unwrapped_idx_to].df_lev_score = score;
+ } else if (score == diffcmppath[unwrapped_idx_to].df_lev_score) {
+ size_t k = diffcmppath[unwrapped_idx_to].df_path_n++;
+ diffcmppath[unwrapped_idx_to].df_decision[k] = &diffcmppath[unwrapped_idx_from];
+ diffcmppath[unwrapped_idx_to].df_choice[k] = *choice;
}
}
return;
@@ -242,8 +227,7 @@ static size_t unwrap_indexes(const int *values, const int *diff_len, const size_
for (size_t k = 0; k < ndiffs; k++) {
num_unwrap_scalar /= (size_t)diff_len[k] + 1;
- // (k == 0) space optimization
- int n = k == 0 ? values[k] % 2 : values[k];
+ int n = values[k];
path_idx += num_unwrap_scalar * (size_t)n;
}
return path_idx;
@@ -351,7 +335,7 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
size_t memsize_decisions = 0;
for (size_t i = 0; i < ndiffs; i++) {
assert(diff_len[i] >= 0);
- memsize *= i == 0 ? 2 : (size_t)(diff_len[i] + 1);
+ memsize *= (size_t)(diff_len[i] + 1);
memsize_decisions += (size_t)diff_len[i];
}
@@ -359,7 +343,11 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
diffcmppath_T *diffcmppath = xmalloc(sizeof(diffcmppath_T) * memsize);
// allocate memory here
for (size_t i = 0; i < memsize; i++) {
- diffcmppath[i].df_decision = xmalloc(memsize_decisions * sizeof(int));
+ diffcmppath[i].df_lev_score = 0;
+ diffcmppath[i].df_path_n = 0;
+ for (size_t j = 0; j < (size_t)pow(2, (double)ndiffs); j++) {
+ diffcmppath[i].df_choice_mem[j] = -1;
+ }
}
// memory for avoiding repetitive calculations of score
@@ -367,18 +355,49 @@ size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size
populate_tensor(df_iters, 0, diffcmppath, diff_len, ndiffs, diff_blk, iwhite);
const size_t u = unwrap_indexes(diff_len, diff_len, ndiffs);
- const size_t best_path_idx = diffcmppath[u].df_path_idx;
- const int *best_path_decisions = diffcmppath[u].df_decision;
+ diffcmppath_T *startNode = &diffcmppath[u];
- *decisions = xmalloc(sizeof(int) * best_path_idx);
- for (size_t i = 0; i < best_path_idx; i++) {
- (*decisions)[i] = best_path_decisions[i];
+ *decisions = xmalloc(sizeof(int) * memsize_decisions);
+ size_t n_optimal = 0;
+ test_charmatch_paths(startNode, 0);
+ while (startNode->df_path_n > 0) {
+ size_t j = startNode->df_optimal_choice;
+ (*decisions)[n_optimal++] = startNode->df_choice[j];
+ startNode = startNode->df_decision[j];
}
-
- for (size_t i = 0; i < memsize; i++) {
- xfree(diffcmppath[i].df_decision);
+ // reverse array
+ for (size_t i = 0; i < (n_optimal / 2); i++) {
+ int tmp = (*decisions)[i];
+ (*decisions)[i] = (*decisions)[n_optimal - 1 - i];
+ (*decisions)[n_optimal - 1 - i] = tmp;
}
+
xfree(diffcmppath);
- return best_path_idx;
+ return n_optimal;
+}
+
+// returns the minimum amount of path changes from start to end
+static size_t test_charmatch_paths(diffcmppath_T *node, int lastdecision)
+{
+ // memoization
+ if (node->df_choice_mem[lastdecision] == -1) {
+ if (node->df_path_n == 0) {
+ // we have reached the end of the tree
+ node->df_choice_mem[lastdecision] = 0;
+ } else {
+ size_t minimum_turns = SIZE_MAX; // the minimum amount of turns required to reach the end
+ for (size_t i = 0; i < node->df_path_n; i++) {
+ // recurse
+ size_t t = test_charmatch_paths(node->df_decision[i], node->df_choice[i]) +
+ (lastdecision != node->df_choice[i] ? 1 : 0);
+ if (t < minimum_turns) {
+ node->df_optimal_choice = i;
+ minimum_turns = t;
+ }
+ }
+ node->df_choice_mem[lastdecision] = (int)minimum_turns;
+ }
+ }
+ return (size_t)node->df_choice_mem[lastdecision];
}
diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h
index 052d438617..eaf0d54bec 100644
--- a/src/nvim/linematch.h
+++ b/src/nvim/linematch.h
@@ -1,10 +1,9 @@
-#ifndef NVIM_LINEMATCH_H
-#define NVIM_LINEMATCH_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
+
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "linematch.h.generated.h"
#endif
-
-#endif // NVIM_LINEMATCH_H
diff --git a/src/nvim/locale.c b/src/nvim/locale.c
deleted file mode 100644
index c3cfd3bedb..0000000000
--- a/src/nvim/locale.c
+++ /dev/null
@@ -1,377 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// locale.c: functions for language/locale configuration
-
-#include <stdbool.h>
-#include <stdio.h>
-
-#include "auto/config.h"
-#ifdef HAVE_LOCALE_H
-# include <locale.h>
-#endif
-
-#include "nvim/ascii.h"
-#include "nvim/buffer.h"
-#include "nvim/charset.h"
-#include "nvim/eval.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/gettext.h"
-#include "nvim/locale.h"
-#include "nvim/macros.h"
-#include "nvim/memory.h"
-#include "nvim/message.h"
-#include "nvim/option.h"
-#include "nvim/os/os.h"
-#include "nvim/os/shell.h"
-#include "nvim/path.h"
-#include "nvim/profile.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "locale.c.generated.h"
-#endif
-
-#if defined(HAVE_LOCALE_H)
-# define HAVE_GET_LOCALE_VAL
-
-static char *get_locale_val(int what)
-{
- // Obtain the locale value from the libraries.
- char *loc = setlocale(what, NULL);
-
- return loc;
-}
-#endif
-
-/// @return true when "lang" starts with a valid language name.
-/// Rejects NULL, empty string, "C", "C.UTF-8" and others.
-static bool is_valid_mess_lang(const char *lang)
-{
- return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
-}
-
-/// Obtain the current messages language. Used to set the default for
-/// 'helplang'. May return NULL or an empty string.
-char *get_mess_lang(void)
-{
- char *p;
-
-#ifdef HAVE_GET_LOCALE_VAL
-# if defined(LC_MESSAGES)
- p = get_locale_val(LC_MESSAGES);
-# else
- // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
- // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
- // and LC_MONETARY may be set differently for a Japanese working in the
- // US.
- p = get_locale_val(LC_COLLATE);
-# endif
-#else
- p = os_getenv("LC_ALL");
- if (!is_valid_mess_lang(p)) {
- p = os_getenv("LC_MESSAGES");
- if (!is_valid_mess_lang(p)) {
- p = os_getenv("LANG");
- }
- }
-#endif
- return is_valid_mess_lang(p) ? p : NULL;
-}
-
-// Complicated #if; matches with where get_mess_env() is used below.
-#ifdef HAVE_WORKING_LIBINTL
-/// Get the language used for messages from the environment.
-static char *get_mess_env(void)
-{
- char *p;
-
- p = (char *)os_getenv("LC_ALL");
- if (p == NULL) {
- p = (char *)os_getenv("LC_MESSAGES");
- if (p == NULL) {
- p = (char *)os_getenv("LANG");
- if (p != NULL && ascii_isdigit(*p)) {
- p = NULL; // ignore something like "1043"
- }
-# ifdef HAVE_GET_LOCALE_VAL
- if (p == NULL) {
- p = get_locale_val(LC_CTYPE);
- }
-# endif
- }
- }
- return p;
-}
-#endif
-
-/// Set the "v:lang" variable according to the current locale setting.
-/// Also do "v:lc_time"and "v:ctype".
-void set_lang_var(void)
-{
- const char *loc;
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_CTYPE);
-#else
- // setlocale() not supported: use the default value
- loc = "C";
-#endif
- set_vim_var_string(VV_CTYPE, loc, -1);
-
- // When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall
- // back to LC_CTYPE if it's empty.
-#ifdef HAVE_WORKING_LIBINTL
- loc = get_mess_env();
-#elif defined(LC_MESSAGES)
- loc = get_locale_val(LC_MESSAGES);
-#else
- // In Windows LC_MESSAGES is not defined fallback to LC_CTYPE
- loc = get_locale_val(LC_CTYPE);
-#endif
- set_vim_var_string(VV_LANG, loc, -1);
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_TIME);
-#endif
- set_vim_var_string(VV_LC_TIME, loc, -1);
-
-#ifdef HAVE_GET_LOCALE_VAL
- loc = get_locale_val(LC_COLLATE);
-#else
- // setlocale() not supported: use the default value
- loc = "C";
-#endif
- set_vim_var_string(VV_COLLATE, loc, -1);
-}
-
-#if defined(HAVE_LOCALE_H)
-/// Setup to use the current locale (for ctype() and many other things).
-void init_locale(void)
-{
- setlocale(LC_ALL, "");
-
-# ifdef LC_NUMERIC
- // Make sure strtod() uses a decimal point, not a comma.
- setlocale(LC_NUMERIC, "C");
-# endif
-
- char localepath[MAXPATHL] = { 0 };
- snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH));
- char *tail = path_tail_with_sep(localepath);
- *tail = NUL;
- tail = path_tail(localepath);
- xstrlcpy(tail, "share/locale",
- sizeof(localepath) - (size_t)(tail - localepath));
- bindtextdomain(PROJECT_NAME, localepath);
- textdomain(PROJECT_NAME);
- TIME_MSG("locale set");
-}
-#endif
-
-#ifdef HAVE_WORKING_LIBINTL
-
-/// ":language": Set the language (locale).
-///
-/// @param eap
-void ex_language(exarg_T *eap)
-{
- char *loc;
- char *p;
- char *name;
- int what = LC_ALL;
- char *whatstr = "";
-# ifdef LC_MESSAGES
-# define VIM_LC_MESSAGES LC_MESSAGES
-# else
-# define VIM_LC_MESSAGES 6789
-# endif
-
- name = eap->arg;
-
- // Check for "messages {name}", "ctype {name}" or "time {name}" argument.
- // Allow abbreviation, but require at least 3 characters to avoid
- // confusion with a two letter language name "me" or "ct".
- p = skiptowhite(eap->arg);
- if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) {
- if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) {
- what = VIM_LC_MESSAGES;
- name = skipwhite(p);
- whatstr = "messages ";
- } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) {
- what = LC_CTYPE;
- name = skipwhite(p);
- whatstr = "ctype ";
- } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) {
- what = LC_TIME;
- name = skipwhite(p);
- whatstr = "time ";
- } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
- what = LC_COLLATE;
- name = skipwhite(p);
- whatstr = "collate ";
- }
- }
-
- if (*name == NUL) {
- if (what == VIM_LC_MESSAGES) {
- p = get_mess_env();
- } else {
- p = setlocale(what, NULL);
- }
- if (p == NULL || *p == NUL) {
- p = "Unknown";
- }
- smsg(_("Current %slanguage: \"%s\""), whatstr, p);
- } else {
-# ifndef LC_MESSAGES
- if (what == VIM_LC_MESSAGES) {
- loc = "";
- } else {
-# endif
- loc = setlocale(what, name);
-# ifdef LC_NUMERIC
- // Make sure strtod() uses a decimal point, not a comma.
- setlocale(LC_NUMERIC, "C");
-# endif
-# ifndef LC_MESSAGES
- }
-# endif
- if (loc == NULL) {
- semsg(_("E197: Cannot set language to \"%s\""), name);
- } else {
-# ifdef HAVE_NL_MSG_CAT_CNTR
- // Need to do this for GNU gettext, otherwise cached translations
- // will be used again.
- extern int _nl_msg_cat_cntr;
-
- _nl_msg_cat_cntr++;
-# endif
- // Reset $LC_ALL, otherwise it would overrule everything.
- os_setenv("LC_ALL", "", 1);
-
- if (what != LC_TIME && what != LC_COLLATE) {
- // Tell gettext() what to translate to. It apparently doesn't
- // use the currently effective locale.
- if (what == LC_ALL) {
- os_setenv("LANG", name, 1);
-
- // Clear $LANGUAGE because GNU gettext uses it.
- os_setenv("LANGUAGE", "", 1);
- }
- if (what != LC_CTYPE) {
- os_setenv("LC_MESSAGES", name, 1);
- set_helplang_default(name);
- }
- }
-
- // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
- set_lang_var();
- maketitle();
- }
- }
-}
-
-static char **locales = NULL; // Array of all available locales
-
-# ifndef MSWIN
-static bool did_init_locales = false;
-
-/// @return an array of strings for all available locales + NULL for the
-/// last element or,
-/// NULL in case of error.
-static char **find_locales(void)
-{
- garray_T locales_ga;
- char *loc;
- char *saveptr = NULL;
-
- // Find all available locales by running command "locale -a". If this
- // doesn't work we won't have completion.
- char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL);
- if (locale_a == NULL) {
- return NULL;
- }
- ga_init(&locales_ga, sizeof(char *), 20);
-
- // Transform locale_a string where each locale is separated by "\n"
- // into an array of locale strings.
- loc = os_strtok(locale_a, "\n", &saveptr);
-
- while (loc != NULL) {
- loc = xstrdup(loc);
- GA_APPEND(char *, &locales_ga, loc);
- loc = os_strtok(NULL, "\n", &saveptr);
- }
- xfree(locale_a);
- // Guarantee that .ga_data is NULL terminated
- ga_grow(&locales_ga, 1);
- ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
- return locales_ga.ga_data;
-}
-# endif
-
-/// Lazy initialization of all available locales.
-static void init_locales(void)
-{
-# ifndef MSWIN
- if (did_init_locales) {
- return;
- }
-
- did_init_locales = true;
- locales = find_locales();
-# endif
-}
-
-# if defined(EXITFREE)
-void free_locales(void)
-{
- if (locales == NULL) {
- return;
- }
-
- for (int i = 0; locales[i] != NULL; i++) {
- xfree(locales[i]);
- }
- XFREE_CLEAR(locales);
-}
-# endif
-
-/// Function given to ExpandGeneric() to obtain the possible arguments of the
-/// ":language" command.
-char *get_lang_arg(expand_T *xp, int idx)
-{
- if (idx == 0) {
- return "messages";
- }
- if (idx == 1) {
- return "ctype";
- }
- if (idx == 2) {
- return "time";
- }
- if (idx == 3) {
- return "collate";
- }
-
- init_locales();
- if (locales == NULL) {
- return NULL;
- }
- return locales[idx - 4];
-}
-
-/// Function given to ExpandGeneric() to obtain the available locales.
-char *get_locales(expand_T *xp, int idx)
-{
- init_locales();
- if (locales == NULL) {
- return NULL;
- }
- return locales[idx];
-}
-
-#endif
diff --git a/src/nvim/locale.h b/src/nvim/locale.h
deleted file mode 100644
index 39735d371f..0000000000
--- a/src/nvim/locale.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#ifndef NVIM_LOCALE_H
-#define NVIM_LOCALE_H
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "locale.h.generated.h"
-#endif
-#endif // NVIM_LOCALE_H
diff --git a/src/nvim/log.c b/src/nvim/log.c
index 2c214aa32d..aeee088cd3 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// Log module
//
@@ -20,12 +17,14 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
@@ -76,7 +75,7 @@ static void log_path_init(void)
char *failed_dir = NULL;
bool log_dir_failure = false;
if (!os_isdir(loghome)) {
- log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir) != 0);
+ log_dir_failure = (os_mkdir_recurse(loghome, 0700, &failed_dir, NULL) != 0);
}
XFREE_CLEAR(loghome);
// Invalid $NVIM_LOG_FILE or failed to expand; fall back to default.
@@ -131,7 +130,7 @@ void log_unlock(void)
/// @return true if log was emitted normally, false if failed or recursive
bool logmsg(int log_level, const char *context, const char *func_name, int line_num, bool eol,
const char *fmt, ...)
- FUNC_ATTR_UNUSED FUNC_ATTR_PRINTF(6, 7)
+ FUNC_ATTR_PRINTF(6, 7)
{
static bool recursive = false;
static bool did_msg = false; // Showed recursion message?
@@ -141,14 +140,28 @@ bool logmsg(int log_level, const char *context, const char *func_name, int line_
return false;
}
- if (log_level < MIN_LOG_LEVEL) {
+#ifndef NVIM_LOG_DEBUG
+ // This should rarely happen (callsites are compiled out), but to be sure.
+ // TODO(bfredl): allow log levels to be configured at runtime
+ if (log_level < LOGLVL_WRN) {
return false;
}
+#endif
#ifdef EXITFREE
// Logging after we've already started freeing all our memory will only cause
// pain. We need access to VV_PROGPATH, homedir, etc.
- assert(!entered_free_all_mem);
+ if (entered_free_all_mem) {
+ fprintf(stderr, "FATAL: error in free_all_mem\n %s %s %d: ", context, func_name, line_num);
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+ if (eol) {
+ fprintf(stderr, "\n");
+ }
+ abort();
+ }
#endif
log_lock();
@@ -328,13 +341,13 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
// Print the log message.
int rv = (line_num == -1 || func_name == NULL)
- ? fprintf(log_file, "%s %s.%03d %-10s %s",
- log_levels[log_level], date_time, millis, name,
- (context == NULL ? "?:" : context))
- : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ",
- log_levels[log_level], date_time, millis, name,
- (context == NULL ? "" : context),
- func_name, line_num);
+ ? fprintf(log_file, "%s %s.%03d %-10s %s",
+ log_levels[log_level], date_time, millis, name,
+ (context == NULL ? "?:" : context))
+ : fprintf(log_file, "%s %s.%03d %-10s %s%s:%d: ",
+ log_levels[log_level], date_time, millis, name,
+ (context == NULL ? "" : context),
+ func_name, line_num);
if (name[0] == '?') {
// No v:servername yet. Clear `name` so that the next log can try again.
name[0] = '\0';
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 14d46c2ea7..cac074d146 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_LOG_H
-#define NVIM_LOG_H
+#pragma once
#include <stdbool.h>
#include <stdio.h>
#include "auto/config.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
// USDT probes. Example invocation:
// NVIM_PROBE(nvim_foo_bar, 1, string.data);
@@ -17,54 +16,29 @@
# define NVIM_PROBE(name, n, ...)
#endif
-#define LOGLVL_TRC 0
#define LOGLVL_DBG 1
#define LOGLVL_INF 2
#define LOGLVL_WRN 3
#define LOGLVL_ERR 4
-#define DLOG(...)
-#define DLOGN(...)
-#define ILOG(...)
-#define ILOGN(...)
-#define WLOG(...)
-#define WLOGN(...)
-#define ELOG(...)
-#define ELOGN(...)
-
-#ifndef MIN_LOG_LEVEL
-# define MIN_LOG_LEVEL LOGLVL_INF
-#endif
-
#define LOG(level, ...) logmsg((level), NULL, __func__, __LINE__, true, __VA_ARGS__)
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
-# undef DLOG
-# undef DLOGN
+#ifdef NVIM_LOG_DEBUG
# define DLOG(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, true, __VA_ARGS__)
# define DLOGN(...) logmsg(LOGLVL_DBG, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
-
-#if MIN_LOG_LEVEL <= LOGLVL_INF
-# undef ILOG
-# undef ILOGN
# define ILOG(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, true, __VA_ARGS__)
# define ILOGN(...) logmsg(LOGLVL_INF, NULL, __func__, __LINE__, false, __VA_ARGS__)
+#else
+# define DLOG(...)
+# define DLOGN(...)
+# define ILOG(...)
+# define ILOGN(...)
#endif
-#if MIN_LOG_LEVEL <= LOGLVL_WRN
-# undef WLOG
-# undef WLOGN
-# define WLOG(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, true, __VA_ARGS__)
-# define WLOGN(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
-
-#if MIN_LOG_LEVEL <= LOGLVL_ERR
-# undef ELOG
-# undef ELOGN
-# define ELOG(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, true, __VA_ARGS__)
-# define ELOGN(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, false, __VA_ARGS__)
-#endif
+#define WLOG(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, true, __VA_ARGS__)
+#define WLOGN(...) logmsg(LOGLVL_WRN, NULL, __func__, __LINE__, false, __VA_ARGS__)
+#define ELOG(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, true, __VA_ARGS__)
+#define ELOGN(...) logmsg(LOGLVL_ERR, NULL, __func__, __LINE__, false, __VA_ARGS__)
#ifdef HAVE_EXECINFO_BACKTRACE
# define LOG_CALLSTACK() log_callstack(__func__, __LINE__)
@@ -78,4 +52,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.h.generated.h"
#endif
-#endif // NVIM_LOG_H
diff --git a/src/nvim/lua/base64.c b/src/nvim/lua/base64.c
new file mode 100644
index 0000000000..c1f43a37d7
--- /dev/null
+++ b/src/nvim/lua/base64.c
@@ -0,0 +1,70 @@
+#include <assert.h>
+#include <lauxlib.h>
+#include <lua.h>
+#include <stddef.h>
+
+#include "nvim/base64.h"
+#include "nvim/lua/base64.h"
+#include "nvim/memory.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/base64.c.generated.h"
+#endif
+
+static int nlua_base64_encode(lua_State *L)
+{
+ if (lua_gettop(L) < 1) {
+ return luaL_error(L, "Expected 1 argument");
+ }
+
+ if (lua_type(L, 1) != LUA_TSTRING) {
+ luaL_argerror(L, 1, "expected string");
+ }
+
+ size_t src_len = 0;
+ const char *src = lua_tolstring(L, 1, &src_len);
+
+ const char *ret = base64_encode(src, src_len);
+ assert(ret != NULL);
+ lua_pushstring(L, ret);
+ xfree((void *)ret);
+
+ return 1;
+}
+
+static int nlua_base64_decode(lua_State *L)
+{
+ if (lua_gettop(L) < 1) {
+ return luaL_error(L, "Expected 1 argument");
+ }
+
+ if (lua_type(L, 1) != LUA_TSTRING) {
+ luaL_argerror(L, 1, "expected string");
+ }
+
+ size_t src_len = 0;
+ const char *src = lua_tolstring(L, 1, &src_len);
+
+ const char *ret = base64_decode(src, src_len);
+ if (ret == NULL) {
+ return luaL_error(L, "Invalid input");
+ }
+
+ lua_pushstring(L, ret);
+ xfree((void *)ret);
+
+ return 1;
+}
+
+static const luaL_Reg base64_functions[] = {
+ { "encode", nlua_base64_encode },
+ { "decode", nlua_base64_decode },
+ { NULL, NULL },
+};
+
+int luaopen_base64(lua_State *L)
+{
+ lua_newtable(L);
+ luaL_register(L, NULL, base64_functions);
+ return 1;
+}
diff --git a/src/nvim/lua/base64.h b/src/nvim/lua/base64.h
new file mode 100644
index 0000000000..3c95968cda
--- /dev/null
+++ b/src/nvim/lua/base64.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include <lua.h> // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/base64.h.generated.h"
+#endif
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 6160b84485..4598d48c4a 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
@@ -10,26 +7,24 @@
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/memory.h"
-// FIXME: vim.h is not actually needed, but otherwise it states MAXPATHL is
-// redefined
-#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/eval/userfunc.h"
-#include "nvim/garray.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
/// Determine, which keys lua table contains
typedef struct {
@@ -183,7 +178,7 @@ typedef struct {
int idx; ///< Container index (used to detect self-referencing structures).
} TVPopStackItem;
-/// Convert lua object to VimL typval_T
+/// Convert lua object to Vimscript typval_T
///
/// Should pop exactly one value from lua stack.
///
@@ -459,7 +454,7 @@ static bool typval_conv_special = false;
TYPVAL_ENCODE_CONV_NUMBER(tv, flt)
#define TYPVAL_ENCODE_CONV_STRING(tv, str, len) \
- lua_pushlstring(lstate, (const char *)(str), (len))
+ lua_pushlstring(lstate, (str), (len))
#define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING
@@ -469,9 +464,7 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
do { \
const blob_T *const blob_ = (blob); \
- lua_pushlstring(lstate, \
- blob_ != NULL ? (const char *)blob_->bv_ga.ga_data : "", \
- (size_t)(len)); \
+ lua_pushlstring(lstate, blob_ != NULL ? blob_->bv_ga.ga_data : "", (size_t)(len)); \
} while (0)
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
@@ -603,7 +596,7 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_RECURSE
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
-/// Convert VimL typval_T to lua value
+/// Convert Vimscript typval_T to lua value
///
/// Should leave single value in lua stack. May only fail if lua failed to grow
/// stack.
@@ -630,8 +623,7 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special)
/// Push value which is a type index
///
-/// Used for all “typed” tables: i.e. for all tables which represent VimL
-/// values.
+/// Used for all “typed” tables: i.e. for all tables which represent Vimscript values.
static inline void nlua_push_type_idx(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
@@ -659,7 +651,7 @@ static inline void nlua_push_type(lua_State *lstate, ObjectType type)
lua_pushnumber(lstate, (lua_Number)type);
}
-/// Create lua table which has an entry that determines its VimL type
+/// Create Lua table which has an entry that determines its Vimscript type
///
/// @param[out] lstate Lua state.
/// @param[in] narr Number of “array” entries to be populated later.
@@ -816,7 +808,7 @@ String nlua_pop_String(lua_State *lstate, Error *err)
{
if (lua_type(lstate, -1) != LUA_TSTRING) {
lua_pop(lstate, 1);
- api_set_error(err, kErrorTypeValidation, "Expected lua string");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua string");
return (String) { .size = 0, .data = NULL };
}
String ret;
@@ -837,7 +829,7 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err)
{
if (lua_type(lstate, -1) != LUA_TNUMBER) {
lua_pop(lstate, 1);
- api_set_error(err, kErrorTypeValidation, "Expected lua number");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua number");
return 0;
}
const lua_Number n = lua_tonumber(lstate, -1);
@@ -852,6 +844,9 @@ Integer nlua_pop_Integer(lua_State *lstate, Error *err)
/// Convert lua value to boolean
///
+/// Despite the name of the function, this uses lua semantics for booleans.
+/// thus `err` is never set as any lua value can be co-erced into a lua bool
+///
/// Always pops one value from the stack.
Boolean nlua_pop_Boolean(lua_State *lstate, Error *err)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
@@ -861,6 +856,36 @@ Boolean nlua_pop_Boolean(lua_State *lstate, Error *err)
return ret;
}
+/// Convert lua value to boolean
+///
+/// This follows API conventions for a Boolean value, compare api_object_to_bool
+///
+/// Always pops one value from the stack.
+Boolean nlua_pop_Boolean_strict(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ Boolean ret = false;
+ switch (lua_type(lstate, -1)) {
+ case LUA_TBOOLEAN:
+ ret = lua_toboolean(lstate, -1);
+ break;
+
+ case LUA_TNUMBER:
+ ret = (lua_tonumber(lstate, -1) != 0);
+ break;
+
+ case LUA_TNIL:
+ ret = false;
+ break;
+
+ default:
+ api_set_error(err, kErrorTypeValidation, "not a boolean");
+ }
+
+ lua_pop(lstate, 1);
+ return ret;
+}
+
/// Check whether typed table on top of the stack has given type
///
/// @param[in] lstate Lua state.
@@ -874,7 +899,8 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
{
if (lua_type(lstate, -1) != LUA_TTABLE) {
if (err) {
- api_set_error(err, kErrorTypeValidation, "Expected lua table");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua %s",
+ (type == kObjectTypeFloat) ? "number" : "table");
}
return (LuaTableProps) { .type = kObjectTypeNil };
}
@@ -887,7 +913,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
if (table_props.type != type) {
if (err) {
- api_set_error(err, kErrorTypeValidation, "Unexpected type");
+ api_set_error(err, kErrorTypeValidation, "Expected %s-like Lua table", api_typename(type));
}
}
@@ -1171,7 +1197,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err)
break;
case kObjectTypeNil:
api_set_error(err, kErrorTypeValidation,
- "Cannot convert given lua table");
+ "Cannot convert given Lua table");
break;
default:
abort();
@@ -1227,26 +1253,19 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
return rv;
}
-#define GENERATE_INDEX_FUNCTION(type) \
- type nlua_pop_##type(lua_State *lstate, Error *err) \
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
- { \
- type ret; \
- if (lua_type(lstate, -1) != LUA_TNUMBER) { \
- api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \
- ret = (type) - 1; \
- } else { \
- ret = (type)lua_tonumber(lstate, -1); \
- } \
- lua_pop(lstate, 1); \
- return ret; \
+handle_T nlua_pop_handle(lua_State *lstate, Error *err)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ handle_T ret;
+ if (lua_type(lstate, -1) != LUA_TNUMBER) {
+ api_set_error(err, kErrorTypeValidation, "Expected Lua number");
+ ret = (handle_T) - 1;
+ } else {
+ ret = (handle_T)lua_tonumber(lstate, -1);
}
-
-GENERATE_INDEX_FUNCTION(Buffer)
-GENERATE_INDEX_FUNCTION(Window)
-GENERATE_INDEX_FUNCTION(Tabpage)
-
-#undef GENERATE_INDEX_FUNCTION
+ lua_pop(lstate, 1);
+ return ret;
+}
/// Record some auxiliary values in vim module
///
@@ -1295,10 +1314,11 @@ void nlua_init_types(lua_State *const lstate)
lua_rawset(lstate, -3);
}
-void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err)
+// lua specific variant of api_dict_to_keydict
+void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Error *err)
{
if (!lua_istable(L, -1)) {
- api_set_error(err, kErrorTypeValidation, "Expected lua table");
+ api_set_error(err, kErrorTypeValidation, "Expected Lua table");
lua_pop(L, -1);
return;
}
@@ -1308,14 +1328,45 @@ void nlua_pop_keydict(lua_State *L, void *retval, field_hash hashy, Error *err)
// [dict, key, value]
size_t len;
const char *s = lua_tolstring(L, -2, &len);
- Object *field = hashy(retval, s, len);
+ KeySetLink *field = hashy(s, len);
if (!field) {
api_set_error(err, kErrorTypeValidation, "invalid key: %.*s", (int)len, s);
lua_pop(L, 3); // []
return;
}
- *field = nlua_pop_Object(L, true, err);
+ if (field->opt_index >= 0) {
+ OptKeySet *ks = (OptKeySet *)retval;
+ ks->is_set_ |= (1ULL << field->opt_index);
+ }
+ char *mem = ((char *)retval + field->ptr_off);
+
+ if (field->type == kObjectTypeNil) {
+ *(Object *)mem = nlua_pop_Object(L, true, err);
+ } else if (field->type == kObjectTypeInteger) {
+ *(Integer *)mem = nlua_pop_Integer(L, err);
+ } else if (field->type == kObjectTypeBoolean) {
+ *(Boolean *)mem = nlua_pop_Boolean_strict(L, err);
+ } else if (field->type == kObjectTypeString) {
+ *(String *)mem = nlua_pop_String(L, err);
+ } else if (field->type == kObjectTypeFloat) {
+ *(Float *)mem = nlua_pop_Float(L, err);
+ } else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
+ || field->type == kObjectTypeTabpage) {
+ *(handle_T *)mem = nlua_pop_handle(L, err);
+ } else if (field->type == kObjectTypeArray) {
+ *(Array *)mem = nlua_pop_Array(L, err);
+ } else if (field->type == kObjectTypeDictionary) {
+ *(Dictionary *)mem = nlua_pop_Dictionary(L, false, err);
+ } else if (field->type == kObjectTypeLuaRef) {
+ *(LuaRef *)mem = nlua_pop_LuaRef(L, err);
+ } else {
+ abort();
+ }
+ if (ERROR_SET(err)) {
+ *err_opt = field->str;
+ break;
+ }
}
// [dict]
lua_pop(L, 1);
diff --git a/src/nvim/lua/converter.h b/src/nvim/lua/converter.h
index ddc0acfbfa..a502df80d9 100644
--- a/src/nvim/lua/converter.h
+++ b/src/nvim/lua/converter.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_LUA_CONVERTER_H
-#define NVIM_LUA_CONVERTER_H
+#pragma once
-#include <lua.h>
-#include <stdbool.h>
-#include <stdint.h>
+#include <lua.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
-#include "nvim/eval/typval.h"
-#include "nvim/func_attr.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+
+#define nlua_pop_Buffer nlua_pop_handle
+#define nlua_pop_Window nlua_pop_handle
+#define nlua_pop_Tabpage nlua_pop_handle
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/converter.h.generated.h"
#endif
-#endif // NVIM_LUA_CONVERTER_H
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 1415ceeaed..06d16efb05 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -1,12 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <tree_sitter/api.h>
#include <uv.h>
@@ -16,9 +15,9 @@
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/change.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
@@ -42,25 +41,24 @@
#include "nvim/lua/executor.h"
#include "nvim/lua/stdlib.h"
#include "nvim/lua/treesitter.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/usercmd.h"
-#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static int in_fast_callback = 0;
@@ -109,11 +107,16 @@ typedef enum luv_err_type {
kThreadCallback,
} luv_err_t;
+lua_State *get_global_lstate(void)
+{
+ return global_lstate;
+}
+
/// Convert lua error into a Vim error message
///
/// @param lstate Lua interpreter state.
-/// @param[in] msg Message base, must contain one `%s`.
-static void nlua_error(lua_State *const lstate, const char *const msg)
+/// @param[in] msg Message base, must contain one `%*s`.
+void nlua_error(lua_State *const lstate, const char *const msg)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
@@ -135,8 +138,8 @@ static void nlua_error(lua_State *const lstate, const char *const msg)
}
if (in_script) {
- os_errmsg(str);
- os_errmsg("\n");
+ fprintf(stderr, msg, (int)len, str);
+ fprintf(stderr, "\n");
} else {
msg_ext_set_kind("lua_error");
semsg_multiline(msg, (int)len, str);
@@ -150,7 +153,7 @@ static void nlua_error(lua_State *const lstate, const char *const msg)
/// @param lstate Lua interpreter state
/// @param[in] nargs Number of arguments expected by the function being called.
/// @param[in] nresults Number of results the function returns.
-static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
+int nlua_pcall(lua_State *lstate, int nargs, int nresults)
{
lua_getglobal(lstate, "debug");
lua_getfield(lstate, -1, "traceback");
@@ -165,17 +168,6 @@ static int nlua_pcall(lua_State *lstate, int nargs, int nresults)
return status;
}
-/// Gets the version of the current Nvim build.
-///
-/// @param lstate Lua interpreter state.
-static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
-{
- Dictionary version = version_dict();
- nlua_push_Dictionary(lstate, version, true);
- api_free_dictionary(version);
- return 1;
-}
-
static void nlua_luv_error_event(void **argv)
{
char *error = (char *)argv[0];
@@ -211,9 +203,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags
if (status) {
if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) {
// consider out of memory errors unrecoverable, just like xmalloc()
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
const char *error = lua_tostring(lstate, -1);
@@ -349,7 +339,7 @@ static int nlua_init_argv(lua_State *const L, char **argv, int argc, int lua_arg
lua_pushstring(L, argv[lua_arg0 - 1]);
lua_rawseti(L, -2, 0); // _G.arg[0] = "foo.lua"
- for (; lua_arg0 >= 0 && i + lua_arg0 < argc; i++) {
+ for (; i + lua_arg0 < argc; i++) {
lua_pushstring(L, argv[i + lua_arg0]);
lua_rawseti(L, -2, i + 1); // _G.arg[i+1] = "--foo"
}
@@ -381,6 +371,12 @@ static int nlua_schedule(lua_State *const lstate)
return lua_error(lstate);
}
+ // If main_loop is closing don't schedule tasks to run in the future,
+ // otherwise any refs allocated here will not be cleaned up.
+ if (main_loop.closing) {
+ return 0;
+ }
+
LuaRef cb = nlua_ref_global(lstate, 1);
multiqueue_put(main_loop.events, nlua_schedule_event,
@@ -390,7 +386,8 @@ static int nlua_schedule(lua_State *const lstate)
// Dummy timer callback. Used by f_wait().
static void dummy_timer_due_cb(TimeWatcher *tw, void *data)
-{}
+{
+}
// Dummy timer close callback. Used by f_wait().
static void dummy_timer_close_cb(TimeWatcher *tw, void *data)
@@ -414,6 +411,10 @@ static bool nlua_wait_condition(lua_State *lstate, int *status, bool *callback_r
static int nlua_wait(lua_State *lstate)
FUNC_ATTR_NONNULL_ALL
{
+ if (in_fast_callback) {
+ return luaL_error(lstate, e_luv_api_disabled, "vim.wait");
+ }
+
intptr_t timeout = luaL_checkinteger(lstate, 1);
if (timeout < 0) {
return luaL_error(lstate, "timeout must be >= 0");
@@ -452,8 +453,7 @@ static int nlua_wait(lua_State *lstate)
fast_only = lua_toboolean(lstate, 4);
}
- MultiQueue *loop_events = fast_only || in_fast_callback > 0
- ? main_loop.fast_events : main_loop.events;
+ MultiQueue *loop_events = fast_only ? main_loop.fast_events : main_loop.events;
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
@@ -469,12 +469,16 @@ static int nlua_wait(lua_State *lstate)
int pcall_status = 0;
bool callback_result = false;
+ // Flush screen updates before blocking.
+ ui_flush();
+
LOOP_PROCESS_EVENTS_UNTIL(&main_loop,
loop_events,
(int)timeout,
got_int || (is_function ? nlua_wait_condition(lstate,
&pcall_status,
- &callback_result) : false));
+ &callback_result)
+ : false));
// Stop dummy timer
time_watcher_stop(tw);
@@ -572,7 +576,7 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan
lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict");
lua_setfield(lstate, -2, "_empty_dict_mt");
- // vim.loop
+ // vim.uv
if (is_standalone) {
// do nothing, use libluv like in a standalone interpreter
} else if (is_thread) {
@@ -585,9 +589,12 @@ static void nlua_common_vim_init(lua_State *lstate, bool is_thread, bool is_stan
}
luaopen_luv(lstate);
lua_pushvalue(lstate, -1);
- lua_setfield(lstate, -3, "loop");
+ lua_setfield(lstate, -3, "uv");
+
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -3, "loop"); // deprecated
- // package.loaded.luv = vim.loop
+ // package.loaded.luv = vim.uv
// otherwise luv will be reinitialized when require'luv'
lua_getglobal(lstate, "package");
lua_getfield(lstate, -1, "loaded");
@@ -622,7 +629,7 @@ static bool nlua_init_packages(lua_State *lstate, bool is_standalone)
lua_getfield(lstate, -1, "preload"); // [package, preload]
for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) {
ModuleDef def = builtin_modules[i];
- lua_pushinteger(lstate, (long)i); // [package, preload, i]
+ lua_pushinteger(lstate, (lua_Integer)i); // [package, preload, i]
lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure]
lua_setfield(lstate, -2, def.name); // [package, preload]
@@ -636,7 +643,7 @@ static bool nlua_init_packages(lua_State *lstate, bool is_standalone)
lua_getglobal(lstate, "require");
lua_pushstring(lstate, "vim._init_packages");
if (nlua_pcall(lstate, 1, 0)) {
- os_errmsg((char *)lua_tostring(lstate, -1));
+ os_errmsg(lua_tostring(lstate, -1));
os_errmsg("\n");
return false;
}
@@ -741,10 +748,6 @@ static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
// vim.types, vim.type_idx, vim.val_idx
nlua_init_types(lstate);
- // neovim version
- lua_pushcfunction(lstate, &nlua_nvim_version);
- lua_setfield(lstate, -2, "version");
-
// schedule
lua_pushcfunction(lstate, &nlua_schedule);
lua_setfield(lstate, -2, "schedule");
@@ -852,7 +855,7 @@ void nlua_run_script(char **argv, int argc, int lua_arg0)
exit(lua_ok ? 0 : 1);
}
-lua_State *nlua_init_state(bool thread)
+static lua_State *nlua_init_state(bool thread)
{
// If it is called from the main thread, it will attempt to rebuild the cache.
const uv_thread_t self = uv_thread_self();
@@ -926,12 +929,13 @@ static void nlua_common_free_all_mem(lua_State *lstate)
if (nlua_track_refs) {
// in case there are leaked luarefs, leak the associated memory
// to get LeakSanitizer stacktraces on exit
- pmap_destroy(handle_T)(&ref_state->ref_markers);
+ map_destroy(int, &ref_state->ref_markers);
}
#endif
lua_close(lstate);
}
+
static void nlua_print_event(void **argv)
{
char *str = argv[0];
@@ -960,10 +964,13 @@ static void nlua_print_event(void **argv)
}
break;
}
- msg(str + start);
+ msg(str + start, 0);
+ if (msg_silent == 0) {
+ msg_didout = true; // Make blank lines work properly
+ }
}
if (len && str[len - 1] == NUL) { // Last was newline
- msg("");
+ msg("", 0);
}
xfree(str);
}
@@ -1110,7 +1117,7 @@ static int nlua_debug(lua_State *lstate)
.v_type = VAR_UNKNOWN,
},
};
- for (;;) {
+ while (true) {
lua_settop(lstate, 0);
typval_T input;
get_user_input(input_args, &input, false, false);
@@ -1122,7 +1129,7 @@ static int nlua_debug(lua_State *lstate)
tv_clear(&input);
return 0;
}
- if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string,
+ if (luaL_loadbuffer(lstate, input.vval.v_string,
strlen(input.vval.v_string), "=(debug command)")) {
nlua_error(lstate, _("E5115: Error while loading debug string: %.*s"));
} else if (nlua_pcall(lstate, 0, 0)) {
@@ -1145,7 +1152,7 @@ static bool viml_func_is_fast(const char *name)
if (fdef) {
return fdef->fast;
}
- // Not a vimL function
+ // Not a Vimscript function
return false;
}
@@ -1155,7 +1162,7 @@ int nlua_call(lua_State *lstate)
size_t name_len;
const char *name = luaL_checklstring(lstate, 1, &name_len);
if (!nlua_is_deferred_safe() && !viml_func_is_fast(name)) {
- return luaL_error(lstate, e_luv_api_disabled, "vimL function");
+ return luaL_error(lstate, e_luv_api_disabled, "Vimscript function");
}
int nargs = lua_gettop(lstate) - 1;
@@ -1174,28 +1181,29 @@ int nlua_call(lua_State *lstate)
}
}
- TRY_WRAP({
- // TODO(bfredl): this should be simplified in error handling refactor
- force_abort = false;
- suppress_errthrow = false;
- did_throw = false;
- did_emsg = false;
+ // TODO(bfredl): this should be simplified in error handling refactor
+ force_abort = false;
+ suppress_errthrow = false;
+ did_throw = false;
+ did_emsg = false;
- try_start();
- typval_T rettv;
- funcexe_T funcexe = FUNCEXE_INIT;
- funcexe.fe_firstline = curwin->w_cursor.lnum;
- funcexe.fe_lastline = curwin->w_cursor.lnum;
- funcexe.fe_evaluate = true;
+ typval_T rettv;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_firstline = curwin->w_cursor.lnum;
+ funcexe.fe_lastline = curwin->w_cursor.lnum;
+ funcexe.fe_evaluate = true;
+
+ TRY_WRAP(&err, {
// call_func() retval is deceptive, ignore it. Instead we set `msg_list`
// (TRY_WRAP) to capture abort-causing non-exception errors.
- (void)call_func((char *)name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
- if (!try_end(&err)) {
- nlua_push_typval(lstate, &rettv, false);
- }
- tv_clear(&rettv);
+ (void)call_func(name, (int)name_len, &rettv, nargs, vim_args, &funcexe);
});
+ if (!ERROR_SET(&err)) {
+ nlua_push_typval(lstate, &rettv, false);
+ }
+ tv_clear(&rettv);
+
free_vim_args:
while (i > 0) {
tv_clear(&vim_args[--i]);
@@ -1300,13 +1308,16 @@ LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index)
#ifdef NLUA_TRACK_REFS
if (nlua_track_refs) {
// dummy allocation to make LeakSanitizer track our luarefs
- pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3));
+ pmap_put(int)(&ref_state->ref_markers, ref, xmalloc(3));
}
#endif
}
return ref;
}
+// TODO(lewis6991): Currently cannot be run in __gc metamethods as they are
+// invoked in lua_close() which can be invoked after the ref_markers map is
+// destroyed in nlua_common_free_all_mem.
LuaRef nlua_ref_global(lua_State *lstate, int index)
{
return nlua_ref(lstate, nlua_global_refs, index);
@@ -1320,7 +1331,7 @@ void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref)
#ifdef NLUA_TRACK_REFS
// NB: don't remove entry from map to track double-unref
if (nlua_track_refs) {
- xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref));
+ xfree(pmap_get(int)(&ref_state->ref_markers, ref));
}
#endif
luaL_unref(lstate, LUA_REGISTRYINDEX, ref);
@@ -1493,7 +1504,7 @@ int nlua_source_using_linegetter(LineGetter fgetline, void *cookie, char *name)
/// Call a LuaCallable given some typvals
///
-/// Used to call any lua callable passed from Lua into VimL
+/// Used to call any Lua callable passed from Lua into Vimscript.
///
/// @param[in] lstate Lua State
/// @param[in] lua_cb Lua Callable
@@ -1628,24 +1639,25 @@ bool nlua_is_deferred_safe(void)
///
/// Used for :lua.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_lua(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
size_t len;
char *code = script_get(eap, &len);
- if (eap->skip) {
+ if (eap->skip || code == NULL) {
xfree(code);
return;
}
- // When =expr is used transform it to print(vim.inspect(expr))
- if (code[0] == '=') {
- len += sizeof("vim.pretty_print()") - sizeof("=");
+ // When =expr is used transform it to vim.print(expr)
+ if (eap->cmdidx == CMD_equal || code[0] == '=') {
+ size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1;
+ len += sizeof("vim.print()") - 1 - off;
// code_buf needs to be 1 char larger then len for null byte in the end.
// lua nlua_typval_exec doesn't expect null terminated string so len
// needs to end before null byte.
char *code_buf = xmallocz(len);
- vim_snprintf(code_buf, len + 1, "vim.pretty_print(%s)", code + 1);
+ vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off);
xfree(code);
code = code_buf;
}
@@ -1659,7 +1671,7 @@ void ex_lua(exarg_T *const eap)
///
/// Used for :luado.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_luado(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
@@ -1667,7 +1679,7 @@ void ex_luado(exarg_T *const eap)
emsg(_("cannot save undo information"));
return;
}
- const char *const cmd = (const char *)eap->arg;
+ const char *const cmd = eap->arg;
const size_t cmd_len = strlen(cmd);
lua_State *const lstate = global_lstate;
@@ -1708,7 +1720,9 @@ void ex_luado(exarg_T *const eap)
break;
}
lua_pushvalue(lstate, -1);
- const char *old_line = (const char *)ml_get_buf(curbuf, l, false);
+ const char *const old_line = ml_get_buf(curbuf, l);
+ // Get length of old_line here as calling Lua code may free it.
+ const size_t old_line_len = strlen(old_line);
lua_pushstring(lstate, old_line);
lua_pushnumber(lstate, (lua_Number)l);
if (nlua_pcall(lstate, 2, 1)) {
@@ -1716,8 +1730,6 @@ void ex_luado(exarg_T *const eap)
break;
}
if (lua_isstring(lstate, -1)) {
- size_t old_line_len = strlen(old_line);
-
size_t new_line_len;
const char *const new_line = lua_tolstring(lstate, -1, &new_line_len);
char *const new_line_transformed = xmemdupz(new_line, new_line_len);
@@ -1740,11 +1752,11 @@ void ex_luado(exarg_T *const eap)
///
/// Used for :luafile.
///
-/// @param eap VimL command being run.
+/// @param eap Vimscript command being run.
void ex_luafile(exarg_T *const eap)
FUNC_ATTR_NONNULL_ALL
{
- nlua_exec_file((const char *)eap->arg);
+ nlua_exec_file(eap->arg);
}
/// Executes Lua code from a file or "-" (stdin).
@@ -1768,13 +1780,12 @@ bool nlua_exec_file(const char *path)
StringBuilder sb = KV_INITIAL_VALUE;
kv_resize(sb, 64);
- ptrdiff_t read_size = -1;
// Read all input from stdin, unless interrupted (ctrl-c).
while (true) {
if (got_int) { // User canceled.
return false;
}
- read_size = file_read(stdin_dup, IObuff, 64);
+ ptrdiff_t read_size = file_read(stdin_dup, IObuff, 64);
if (read_size < 0) { // Error.
return false;
}
@@ -1876,7 +1887,7 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
luaL_checktype(lstate, -1, LUA_TFUNCTION);
// [ vim, vim._expand_pat, buf ]
- lua_pushlstring(lstate, (const char *)pat, strlen(pat));
+ lua_pushlstring(lstate, pat, strlen(pat));
if (nlua_pcall(lstate, 1, 2) != 0) {
nlua_error(lstate,
@@ -1995,7 +2006,7 @@ char *nlua_register_table_as_callable(const typval_T *const arg)
void nlua_execute_on_key(int c)
{
char buf[NUMBUFLEN];
- size_t buf_len = special_to_buf(c, mod_mask, false, (char_u *)buf);
+ size_t buf_len = special_to_buf(c, mod_mask, false, buf);
lua_State *const lstate = global_lstate;
@@ -2041,9 +2052,9 @@ void nlua_set_sctx(sctx_T *current)
lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug));
// Files where internal wrappers are defined so we can ignore them
- // like vim.o/opt etc are defined in _meta.lua
+ // like vim.o/opt etc are defined in _options.lua
char *ignorelist[] = {
- "vim/_meta.lua",
+ "vim/_options.lua",
"vim/keymap.lua",
};
int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]);
@@ -2073,10 +2084,15 @@ void nlua_set_sctx(sctx_T *current)
break;
}
char *source_path = fix_fname(info->source + 1);
- get_current_script_id(&source_path, current);
- xfree(source_path);
- current->sc_lnum = info->currentline;
+ int sid = find_script_by_name(source_path);
+ if (sid > 0) {
+ xfree(source_path);
+ } else {
+ new_script_item(source_path, &sid);
+ }
+ current->sc_sid = sid;
current->sc_seq = -1;
+ current->sc_lnum = info->currentline;
cleanup:
xfree(info);
@@ -2103,7 +2119,7 @@ int nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap, bool preview)
lua_setfield(lstate, -2, "line2");
lua_newtable(lstate); // f-args table
- lua_pushstring(lstate, (const char *)eap->arg);
+ lua_pushstring(lstate, eap->arg);
lua_pushvalue(lstate, -1); // Reference for potential use on f-args
lua_setfield(lstate, -4, "args");
@@ -2286,79 +2302,16 @@ plain:
return str.items;
}
-char *nlua_read_secure(const char *path)
-{
- lua_State *const lstate = global_lstate;
- const int top = lua_gettop(lstate);
-
- lua_getglobal(lstate, "vim");
- lua_getfield(lstate, -1, "secure");
- lua_getfield(lstate, -1, "read");
- lua_pushstring(lstate, path);
- if (nlua_pcall(lstate, 1, 1)) {
- nlua_error(lstate, _("Error executing vim.secure.read: %.*s"));
- lua_settop(lstate, top);
- return NULL;
- }
-
- size_t len = 0;
- const char *contents = lua_tolstring(lstate, -1, &len);
- char *buf = NULL;
- if (contents != NULL) {
- // Add one to include trailing null byte
- buf = xcalloc(len + 1, sizeof(char));
- memcpy(buf, contents, len + 1);
- }
-
- lua_settop(lstate, top);
- return buf;
-}
-
-bool nlua_trust(const char *action, const char *path)
+/// Execute the vim._defaults module to set up default mappings and autocommands
+void nlua_init_defaults(void)
{
- lua_State *const lstate = global_lstate;
- const int top = lua_gettop(lstate);
+ lua_State *const L = global_lstate;
+ assert(L);
- lua_getglobal(lstate, "vim");
- lua_getfield(lstate, -1, "secure");
- lua_getfield(lstate, -1, "trust");
-
- lua_newtable(lstate);
- lua_pushstring(lstate, "action");
- lua_pushstring(lstate, action);
- lua_settable(lstate, -3);
- if (path == NULL) {
- lua_pushstring(lstate, "bufnr");
- lua_pushnumber(lstate, 0);
- lua_settable(lstate, -3);
- } else {
- lua_pushstring(lstate, "path");
- lua_pushstring(lstate, path);
- lua_settable(lstate, -3);
- }
-
- if (nlua_pcall(lstate, 1, 2)) {
- nlua_error(lstate, _("Error executing vim.secure.trust: %.*s"));
- lua_settop(lstate, top);
- return false;
- }
-
- bool success = lua_toboolean(lstate, -2);
- const char *msg = lua_tostring(lstate, -1);
- if (msg != NULL) {
- if (success) {
- if (strcmp(action, "allow") == 0) {
- smsg("Allowed \"%s\" in trust database.", msg);
- } else if (strcmp(action, "deny") == 0) {
- smsg("Denied \"%s\" in trust database.", msg);
- } else if (strcmp(action, "remove") == 0) {
- smsg("Removed \"%s\" from trust database.", msg);
- }
- } else {
- semsg(e_trustfile, msg);
- }
+ lua_getglobal(L, "require");
+ lua_pushstring(L, "vim._defaults");
+ if (nlua_pcall(L, 1, 0)) {
+ os_errmsg(lua_tostring(L, -1));
+ os_errmsg("\n");
}
-
- lua_settop(lstate, top);
- return success;
}
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index c6747833e5..b38faddbb3 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_LUA_EXECUTOR_H
-#define NVIM_LUA_EXECUTOR_H
+#pragma once
#include <lauxlib.h>
#include <lua.h>
@@ -7,13 +6,15 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
-#include "nvim/eval/typval.h"
+#include "nvim/assert_defs.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/func_attr.h"
#include "nvim/lua/converter.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
// Generated by msgpack-gen.lua
@@ -24,7 +25,7 @@ typedef struct {
LuaRef empty_dict_ref;
int ref_count;
#if __has_feature(address_sanitizer)
- PMap(handle_T) ref_markers;
+ PMap(int) ref_markers;
#endif
} nlua_ref_state_t;
@@ -43,7 +44,5 @@ typedef struct {
# include "lua/executor.h.generated.h"
#endif
-EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL);
-EXTERN bool nlua_disable_preload INIT(= false);
-
-#endif // NVIM_LUA_EXECUTOR_H
+EXTERN nlua_ref_state_t *nlua_global_refs INIT( = NULL);
+EXTERN bool nlua_disable_preload INIT( = false);
diff --git a/src/nvim/lua/secure.c b/src/nvim/lua/secure.c
new file mode 100644
index 0000000000..65c13f8872
--- /dev/null
+++ b/src/nvim/lua/secure.c
@@ -0,0 +1,119 @@
+#include <lua.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "nvim/charset.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/lua/executor.h"
+#include "nvim/lua/secure.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/secure.c.generated.h"
+#endif
+
+char *nlua_read_secure(const char *path)
+{
+ lua_State *const lstate = get_global_lstate();
+ const int top = lua_gettop(lstate);
+
+ lua_getglobal(lstate, "vim");
+ lua_getfield(lstate, -1, "secure");
+ lua_getfield(lstate, -1, "read");
+ lua_pushstring(lstate, path);
+ if (nlua_pcall(lstate, 1, 1)) {
+ nlua_error(lstate, _("Error executing vim.secure.read: %.*s"));
+ lua_settop(lstate, top);
+ return NULL;
+ }
+
+ size_t len = 0;
+ const char *contents = lua_tolstring(lstate, -1, &len);
+ char *buf = NULL;
+ if (contents != NULL) {
+ // Add one to include trailing null byte
+ buf = xcalloc(len + 1, sizeof(char));
+ memcpy(buf, contents, len + 1);
+ }
+
+ lua_settop(lstate, top);
+ return buf;
+}
+
+static bool nlua_trust(const char *action, const char *path)
+{
+ lua_State *const lstate = get_global_lstate();
+ const int top = lua_gettop(lstate);
+
+ lua_getglobal(lstate, "vim");
+ lua_getfield(lstate, -1, "secure");
+ lua_getfield(lstate, -1, "trust");
+
+ lua_newtable(lstate);
+ lua_pushstring(lstate, "action");
+ lua_pushstring(lstate, action);
+ lua_settable(lstate, -3);
+ if (path == NULL) {
+ lua_pushstring(lstate, "bufnr");
+ lua_pushnumber(lstate, 0);
+ lua_settable(lstate, -3);
+ } else {
+ lua_pushstring(lstate, "path");
+ lua_pushstring(lstate, path);
+ lua_settable(lstate, -3);
+ }
+
+ if (nlua_pcall(lstate, 1, 2)) {
+ nlua_error(lstate, _("Error executing vim.secure.trust: %.*s"));
+ lua_settop(lstate, top);
+ return false;
+ }
+
+ bool success = lua_toboolean(lstate, -2);
+ const char *msg = lua_tostring(lstate, -1);
+ if (msg != NULL) {
+ if (success) {
+ if (strcmp(action, "allow") == 0) {
+ smsg(0, "Allowed \"%s\" in trust database.", msg);
+ } else if (strcmp(action, "deny") == 0) {
+ smsg(0, "Denied \"%s\" in trust database.", msg);
+ } else if (strcmp(action, "remove") == 0) {
+ smsg(0, "Removed \"%s\" from trust database.", msg);
+ }
+ } else {
+ semsg(e_trustfile, msg);
+ }
+ }
+
+ lua_settop(lstate, top);
+ return success;
+}
+
+void ex_trust(exarg_T *eap)
+{
+ const char *const p = skiptowhite(eap->arg);
+ char *arg1 = xmemdupz(eap->arg, (size_t)(p - eap->arg));
+ const char *action = "allow";
+ const char *path = skipwhite(p);
+
+ if (strcmp(arg1, "++deny") == 0) {
+ action = "deny";
+ } else if (strcmp(arg1, "++remove") == 0) {
+ action = "remove";
+ } else if (*arg1 != '\0') {
+ semsg(e_invarg2, arg1);
+ goto theend;
+ }
+
+ if (path[0] == '\0') {
+ path = NULL;
+ }
+
+ nlua_trust(action, path);
+
+theend:
+ xfree(arg1);
+}
diff --git a/src/nvim/lua/secure.h b/src/nvim/lua/secure.h
new file mode 100644
index 0000000000..c69c0d4f8f
--- /dev/null
+++ b/src/nvim/lua/secure.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lua/secure.h.generated.h"
+#endif
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index d510d25e90..c261c5105e 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <limits.h>
@@ -8,7 +5,7 @@
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -16,7 +13,6 @@
#include "nvim/lua/spell.h"
#include "nvim/message.h"
#include "nvim/spell.h"
-#include "nvim/types.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/spell.c.generated.h" // IWYU pragma: export
@@ -39,7 +35,7 @@ int nlua_spell_check(lua_State *lstate)
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -51,7 +47,6 @@ int nlua_spell_check(lua_State *lstate)
}
hlf_T attr = HLF_COUNT;
- size_t len = 0;
size_t pos = 0;
int capcol = -1;
int no_res = 0;
@@ -61,7 +56,7 @@ int nlua_spell_check(lua_State *lstate)
while (*str != NUL) {
attr = HLF_COUNT;
- len = spell_check(curwin, (char *)str, &attr, &capcol, false);
+ size_t len = spell_check(curwin, (char *)str, &attr, &capcol, false);
assert(len <= INT_MAX);
if (attr != HLF_COUNT) {
@@ -70,11 +65,11 @@ int nlua_spell_check(lua_State *lstate)
lua_pushlstring(lstate, str, len);
lua_rawseti(lstate, -2, 1);
- result = attr == HLF_SPB ? "bad" :
- attr == HLF_SPR ? "rare" :
- attr == HLF_SPL ? "local" :
- attr == HLF_SPC ? "caps" :
- NULL;
+ result = attr == HLF_SPB
+ ? "bad" : (attr == HLF_SPR
+ ? "rare" : (attr == HLF_SPL
+ ? "local" : (attr == HLF_SPC
+ ? "caps" : NULL)));
assert(result != NULL);
@@ -82,7 +77,7 @@ int nlua_spell_check(lua_State *lstate)
lua_rawseti(lstate, -2, 2);
// +1 for 1-indexing
- lua_pushinteger(lstate, (long)pos + 1);
+ lua_pushinteger(lstate, (lua_Integer)pos + 1);
lua_rawseti(lstate, -2, 3);
lua_rawseti(lstate, -2, ++no_res);
diff --git a/src/nvim/lua/spell.h b/src/nvim/lua/spell.h
index 8f798a5191..6f1b322e5b 100644
--- a/src/nvim/lua/spell.h
+++ b/src/nvim/lua/spell.h
@@ -1,12 +1,7 @@
-#ifndef NVIM_LUA_SPELL_H
-#define NVIM_LUA_SPELL_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/spell.h.generated.h"
#endif
-
-#endif // NVIM_LUA_SPELL_H
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 6ebca6d97e..33770b2e62 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <lauxlib.h>
#include <lua.h>
@@ -11,30 +8,36 @@
#include <string.h>
#include <sys/types.h>
-#include "auto/config.h"
+#ifdef NVIM_VENDOR_BIT
+# include "bit.h"
+#endif
+
#include "cjson/lua_cjson.h"
#include "mpack/lmpack.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_eval.h"
+#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
+#include "nvim/lua/base64.h"
#include "nvim/lua/converter.h"
#include "nvim/lua/spell.h"
#include "nvim/lua/stdlib.h"
#include "nvim/lua/xdiff.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/runtime.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/stdlib.c.generated.h"
@@ -78,20 +81,20 @@ static int regex_match_line(lua_State *lstate)
return luaL_error(lstate, "not enough args");
}
- long bufnr = luaL_checkinteger(lstate, 2);
+ handle_T bufnr = (handle_T)luaL_checkinteger(lstate, 2);
linenr_T rownr = (linenr_T)luaL_checkinteger(lstate, 3);
- long start = 0, end = -1;
+ int start = 0, end = -1;
if (narg >= 4) {
- start = luaL_checkinteger(lstate, 4);
+ start = (int)luaL_checkinteger(lstate, 4);
}
if (narg >= 5) {
- end = luaL_checkinteger(lstate, 5);
+ end = (int)luaL_checkinteger(lstate, 5);
if (end < 0) {
return luaL_error(lstate, "invalid end");
}
}
- buf_T *buf = bufnr ? handle_get_buffer((int)bufnr) : curbuf;
+ buf_T *buf = bufnr ? handle_get_buffer(bufnr) : curbuf;
if (!buf || buf->b_ml.ml_mfp == NULL) {
return luaL_error(lstate, "invalid buffer");
}
@@ -100,7 +103,7 @@ static int regex_match_line(lua_State *lstate)
return luaL_error(lstate, "invalid row");
}
- char *line = ml_get_buf(buf, rownr + 1, false);
+ char *line = ml_get_buf(buf, rownr + 1);
size_t len = strlen(line);
if (start < 0 || (size_t)start > len) {
@@ -178,8 +181,8 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
size_t codepoints = 0, codeunits = 0;
mb_utflen(s1, (size_t)idx, &codepoints, &codeunits);
- lua_pushinteger(lstate, (long)codepoints);
- lua_pushinteger(lstate, (long)codeunits);
+ lua_pushinteger(lstate, (lua_Integer)codepoints);
+ lua_pushinteger(lstate, (lua_Integer)codeunits);
return 2;
}
@@ -199,7 +202,7 @@ static int nlua_str_utf_pos(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
size_t clen;
for (size_t i = 0; i < s1_len && s1[i] != NUL; i += clen) {
clen = (size_t)utf_ptr2len_len(s1 + i, (int)(s1_len - i));
- lua_pushinteger(lstate, (long)i + 1);
+ lua_pushinteger(lstate, (lua_Integer)i + 1);
lua_rawseti(lstate, -2, (int)idx);
idx++;
}
@@ -218,11 +221,11 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
size_t s1_len;
const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
- long offset = luaL_checkinteger(lstate, 2);
+ ptrdiff_t offset = luaL_checkinteger(lstate, 2);
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
- int head_offset = utf_cp_head_off((char_u *)s1, (char_u *)s1 + offset - 1);
+ int head_offset = -utf_cp_head_off(s1, s1 + offset - 1);
lua_pushinteger(lstate, head_offset);
return 1;
}
@@ -238,7 +241,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
size_t s1_len;
const char *s1 = luaL_checklstring(lstate, 1, &s1_len);
- long offset = luaL_checkinteger(lstate, 2);
+ ptrdiff_t offset = luaL_checkinteger(lstate, 2);
if (offset < 0 || offset > (intptr_t)s1_len) {
return luaL_error(lstate, "index out of range");
}
@@ -271,7 +274,7 @@ int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
return luaL_error(lstate, "index out of range");
}
- lua_pushinteger(lstate, (long)byteidx);
+ lua_pushinteger(lstate, (lua_Integer)byteidx);
return 1;
}
@@ -282,10 +285,8 @@ int nlua_regex(lua_State *lstate)
const char *text = luaL_checkstring(lstate, 1);
regprog_T *prog = NULL;
- TRY_WRAP({
- try_start();
- prog = vim_regcomp((char *)text, RE_AUTO | RE_MAGIC | RE_STRICT);
- try_end(&err);
+ TRY_WRAP(&err, {
+ prog = vim_regcomp(text, RE_AUTO | RE_MAGIC | RE_STRICT);
});
if (ERROR_SET(&err)) {
@@ -356,6 +357,9 @@ int nlua_setvar(lua_State *lstate)
Error err = ERROR_INIT;
dictitem_T *di = dict_check_writable(dict, key, del, &err);
if (ERROR_SET(&err)) {
+ nlua_push_errstr(lstate, "%s", err.msg);
+ api_clear_error(&err);
+ lua_error(lstate);
return 0;
}
@@ -391,6 +395,15 @@ int nlua_setvar(lua_State *lstate)
di = tv_dict_item_alloc_len(key.data, key.size);
tv_dict_add(dict, di);
} else {
+ bool type_error = false;
+ if (dict == &vimvardict
+ && !before_set_vvar(key.data, di, &tv, true, watched, &type_error)) {
+ tv_clear(&tv);
+ if (type_error) {
+ return luaL_error(lstate, "Setting v:%s to value with wrong type", key.data);
+ }
+ return 0;
+ }
if (watched) {
tv_copy(&di->di_tv, &oldtv);
}
@@ -452,7 +465,7 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
int ret = 0;
assert(s1[s1_len] == NUL);
assert(s2[s2_len] == NUL);
- do {
+ while (true) {
nul1 = memchr(s1, NUL, s1_len);
nul2 = memchr(s2, NUL, s2_len);
ret = STRICMP(s1, s2);
@@ -476,7 +489,7 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
} else {
break;
}
- } while (true);
+ }
lua_pop(lstate, 2);
lua_pushnumber(lstate, (lua_Number)((ret > 0) - (ret < 0)));
return 1;
@@ -501,7 +514,7 @@ static int nlua_iconv(lua_State *lstate)
const char *str = lua_tolstring(lstate, 1, &str_len);
char *from = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 2, NULL)));
- char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL)));
+ char *to = enc_canonize(enc_skip((char *)lua_tolstring(lstate, 3, NULL)));
vimconv_T vimconv;
vimconv.vc_type = CONV_NONE;
@@ -524,6 +537,31 @@ static int nlua_iconv(lua_State *lstate)
return 1;
}
+// Like 'zx' but don't call newFoldLevel()
+static int nlua_foldupdate(lua_State *lstate)
+{
+ curwin->w_foldinvalid = true; // recompute folds
+ foldOpenCursor();
+
+ return 0;
+}
+
+// Access to internal functions. For use in runtime/
+static void nlua_state_add_internal(lua_State *const lstate)
+{
+ // _getvar
+ lua_pushcfunction(lstate, &nlua_getvar);
+ lua_setfield(lstate, -2, "_getvar");
+
+ // _setvar
+ lua_pushcfunction(lstate, &nlua_setvar);
+ lua_setfield(lstate, -2, "_setvar");
+
+ // _updatefolds
+ lua_pushcfunction(lstate, &nlua_foldupdate);
+ lua_setfield(lstate, -2, "_foldupdate");
+}
+
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
{
if (!is_thread) {
@@ -558,14 +596,6 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
lua_setfield(lstate, -2, "__index"); // [meta]
lua_pop(lstate, 1); // don't use metatable now
- // _getvar
- lua_pushcfunction(lstate, &nlua_getvar);
- lua_setfield(lstate, -2, "_getvar");
-
- // _setvar
- lua_pushcfunction(lstate, &nlua_setvar);
- lua_setfield(lstate, -2, "_setvar");
-
// vim.spell
luaopen_spell(lstate);
lua_setfield(lstate, -2, "spell");
@@ -574,6 +604,12 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
// depends on p_ambw, p_emoji
lua_pushcfunction(lstate, &nlua_iconv);
lua_setfield(lstate, -2, "iconv");
+
+ // vim.base64
+ luaopen_base64(lstate);
+ lua_setfield(lstate, -2, "base64");
+
+ nlua_state_add_internal(lstate);
}
// vim.mpack
@@ -589,6 +625,19 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
lua_setfield(lstate, -2, "mpack");
lua_pop(lstate, 3);
+ // vim.lpeg
+ int luaopen_lpeg(lua_State *);
+ luaopen_lpeg(lstate);
+ lua_pushvalue(lstate, -1);
+ lua_setfield(lstate, -4, "lpeg");
+
+ // package.loaded.lpeg = vim.lpeg
+ lua_getglobal(lstate, "package");
+ lua_getfield(lstate, -1, "loaded");
+ lua_pushvalue(lstate, -3);
+ lua_setfield(lstate, -2, "lpeg");
+ lua_pop(lstate, 4);
+
// vim.diff
lua_pushcfunction(lstate, &nlua_xdl_diff);
lua_setfield(lstate, -2, "diff");
@@ -596,6 +645,13 @@ void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
// vim.json
lua_cjson_new(lstate);
lua_setfield(lstate, -2, "json");
+
+#ifdef NVIM_VENDOR_BIT
+ // if building with puc lua, use internal fallback for require'bit'
+ int top = lua_gettop(lstate);
+ luaopen_bit(lstate);
+ lua_settop(lstate, top);
+#endif
}
/// like luaL_error, but allow cleanup
diff --git a/src/nvim/lua/stdlib.h b/src/nvim/lua/stdlib.h
index 17aec6714d..26e96055ae 100644
--- a/src/nvim/lua/stdlib.h
+++ b/src/nvim/lua/stdlib.h
@@ -1,10 +1,7 @@
-#ifndef NVIM_LUA_STDLIB_H
-#define NVIM_LUA_STDLIB_H
+#pragma once
-#include <lua.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/stdlib.h.generated.h"
#endif
-
-#endif // NVIM_LUA_STDLIB_H
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 56f4daed1a..008b3f2e95 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -1,11 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// lua bindings for tree-sitter.
// NB: this file mostly contains a generic lua interface for tree-sitter
// trees and nodes, and could be broken out as a reusable lua package
#include <assert.h>
+#include <ctype.h>
#include <lauxlib.h>
#include <limits.h>
#include <lua.h>
@@ -14,6 +12,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <tree_sitter/api.h>
#include <uv.h>
#include "klib/kvec.h"
@@ -21,14 +20,13 @@
#include "nvim/buffer_defs.h"
#include "nvim/globals.h"
#include "nvim/lua/treesitter.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "tree_sitter/api.h"
+#include "nvim/types_defs.h"
#define TS_META_PARSER "treesitter_parser"
#define TS_META_TREE "treesitter_tree"
@@ -43,6 +41,17 @@ typedef struct {
int max_match_id;
} TSLua_cursor;
+typedef struct {
+ LuaRef cb;
+ lua_State *lstate;
+ bool lex;
+ bool parse;
+} TSLuaLoggerOpts;
+
+typedef struct {
+ TSTree *tree;
+} TSLuaTree;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/treesitter.c.generated.h"
#endif
@@ -51,8 +60,13 @@ static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
{ "parse", parser_parse },
+ { "reset", parser_reset },
{ "set_included_ranges", parser_set_ranges },
{ "included_ranges", parser_get_ranges },
+ { "set_timeout", parser_set_timeout },
+ { "timeout", parser_get_timeout },
+ { "_set_logger", parser_set_logger },
+ { "_logger", parser_get_logger },
{ NULL, NULL }
};
@@ -61,6 +75,7 @@ static struct luaL_Reg tree_meta[] = {
{ "__tostring", tree_tostring },
{ "root", tree_root },
{ "edit", tree_edit },
+ { "included_ranges", tree_get_ranges },
{ "copy", tree_copy },
{ NULL, NULL }
};
@@ -78,6 +93,8 @@ static struct luaL_Reg node_meta[] = {
{ "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
+ { "extra", node_extra },
+ { "has_changes", node_has_changes },
{ "has_error", node_has_error },
{ "sexpr", node_sexpr },
{ "child_count", node_child_count },
@@ -95,7 +112,9 @@ static struct luaL_Reg node_meta[] = {
{ "prev_named_sibling", node_prev_named_sibling },
{ "named_children", node_named_children },
{ "root", node_root },
+ { "tree", node_tree },
{ "byte_length", node_byte_length },
+ { "equal", node_equal },
{ NULL, NULL }
};
@@ -132,9 +151,9 @@ static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
lua_pop(L, 1); // [] (don't use it now)
}
-/// init the tslua library
+/// Init the tslua library.
///
-/// all global state is stored in the regirstry of the lua_State
+/// All global state is stored in the registry of the lua_State.
void tslua_init(lua_State *L)
{
// type metatables
@@ -145,15 +164,13 @@ void tslua_init(lua_State *L)
build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);
build_meta(L, TS_META_TREECURSOR, treecursor_meta);
-#ifdef NVIM_TS_HAS_SET_ALLOCATOR
ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
-#endif
}
int tslua_has_language(lua_State *L)
{
const char *lang_name = luaL_checkstring(L, 1);
- lua_pushboolean(L, pmap_has(cstr_t)(&langs, lang_name));
+ lua_pushboolean(L, map_has(cstr_t, &langs, lang_name));
return 1;
}
@@ -170,7 +187,7 @@ int tslua_add_language(lua_State *L)
symbol_name = luaL_checkstring(L, 3);
}
- if (pmap_has(cstr_t)(&langs, lang_name)) {
+ if (map_has(cstr_t, &langs, lang_name)) {
lua_pushboolean(L, true);
return 1;
}
@@ -223,11 +240,11 @@ int tslua_add_language(lua_State *L)
int tslua_remove_lang(lua_State *L)
{
const char *lang_name = luaL_checkstring(L, 1);
- bool present = pmap_has(cstr_t)(&langs, lang_name);
+ bool present = map_has(cstr_t, &langs, lang_name);
if (present) {
- char *key = (char *)pmap_key(cstr_t)(&langs, lang_name);
- pmap_del(cstr_t)(&langs, lang_name);
- xfree(key);
+ cstr_t key;
+ pmap_del(cstr_t)(&langs, lang_name, &key);
+ xfree((void *)key);
}
lua_pushboolean(L, present);
return 1;
@@ -309,6 +326,17 @@ static TSParser **parser_check(lua_State *L, uint16_t index)
return luaL_checkudata(L, index, TS_META_PARSER);
}
+static void logger_gc(TSLogger logger)
+{
+ if (!logger.log) {
+ return;
+ }
+
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload;
+ luaL_unref(opts->lstate, LUA_REGISTRYINDEX, opts->cb);
+ xfree(opts);
+}
+
static int parser_gc(lua_State *L)
{
TSParser **p = parser_check(L, 1);
@@ -316,6 +344,7 @@ static int parser_gc(lua_State *L)
return 0;
}
+ logger_gc(ts_parser_logger(*p));
ts_parser_delete(*p);
return 0;
}
@@ -329,7 +358,7 @@ static int parser_tostring(lua_State *L)
static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position,
uint32_t *bytes_read)
{
- buf_T *bp = payload;
+ buf_T *bp = payload;
#define BUFSIZE 256
static char buf[BUFSIZE];
@@ -337,7 +366,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
*bytes_read = 0;
return "";
}
- char *line = ml_get_buf(bp, (linenr_T)position.row + 1, false);
+ char *line = ml_get_buf(bp, (linenr_T)position.row + 1);
size_t len = strlen(line);
if (position.column > len) {
*bytes_read = 0;
@@ -359,19 +388,29 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
#undef BUFSIZE
}
-static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length)
+static void push_ranges(lua_State *L, const TSRange *ranges, const size_t length,
+ bool include_bytes)
{
lua_createtable(L, (int)length, 0);
for (size_t i = 0; i < length; i++) {
- lua_createtable(L, 4, 0);
+ lua_createtable(L, include_bytes ? 6 : 4, 0);
+ int j = 1;
lua_pushinteger(L, ranges[i].start_point.row);
- lua_rawseti(L, -2, 1);
+ lua_rawseti(L, -2, j++);
lua_pushinteger(L, ranges[i].start_point.column);
- lua_rawseti(L, -2, 2);
+ lua_rawseti(L, -2, j++);
+ if (include_bytes) {
+ lua_pushinteger(L, ranges[i].start_byte);
+ lua_rawseti(L, -2, j++);
+ }
lua_pushinteger(L, ranges[i].end_point.row);
- lua_rawseti(L, -2, 3);
+ lua_rawseti(L, -2, j++);
lua_pushinteger(L, ranges[i].end_point.column);
- lua_rawseti(L, -2, 4);
+ lua_rawseti(L, -2, j++);
+ if (include_bytes) {
+ lua_pushinteger(L, ranges[i].end_byte);
+ lua_rawseti(L, -2, j++);
+ }
lua_rawseti(L, -2, (int)(i + 1));
}
@@ -386,14 +425,14 @@ static int parser_parse(lua_State *L)
TSTree *old_tree = NULL;
if (!lua_isnil(L, 2)) {
- TSTree **tmp = tree_check(L, 2);
- old_tree = tmp ? *tmp : NULL;
+ TSLuaTree *ud = tree_check(L, 2);
+ old_tree = ud ? ud->tree : NULL;
}
TSTree *new_tree = NULL;
size_t len;
const char *str;
- long bufnr;
+ handle_T bufnr;
buf_T *buf;
TSInput input;
@@ -406,13 +445,13 @@ static int parser_parse(lua_State *L)
break;
case LUA_TNUMBER:
- bufnr = lua_tointeger(L, 3);
- buf = handle_get_buffer((handle_T)bufnr);
+ bufnr = (handle_T)lua_tointeger(L, 3);
+ buf = handle_get_buffer(bufnr);
if (!buf) {
#define BUFSIZE 256
char ebuf[BUFSIZE] = { 0 };
- vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %ld", bufnr);
+ vim_snprintf(ebuf, BUFSIZE, "invalid buffer handle: %d", bufnr);
return luaL_argerror(L, 3, ebuf);
#undef BUFSIZE
}
@@ -426,34 +465,46 @@ static int parser_parse(lua_State *L)
return luaL_argerror(L, 3, "expected either string or buffer handle");
}
+ bool include_bytes = (lua_gettop(L) >= 4) && lua_toboolean(L, 4);
+
// Sometimes parsing fails (timeout, or wrong parser ABI)
// In those case, just return an error.
if (!new_tree) {
return luaL_error(L, "An error occurred when parsing.");
}
- // The new tree will be pushed to the stack, without copy, ownership is now to
- // the lua GC.
- // Old tree is still owned by the lua GC.
+ // The new tree will be pushed to the stack, without copy, ownership is now to the lua GC.
+ // Old tree is owned by lua GC since before
uint32_t n_ranges = 0;
- TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL;
+ TSRange *changed = old_tree ? ts_tree_get_changed_ranges(old_tree, new_tree, &n_ranges) : NULL;
- push_tree(L, new_tree, false); // [tree]
+ push_tree(L, new_tree); // [tree]
- push_ranges(L, changed, n_ranges); // [tree, ranges]
+ push_ranges(L, changed, n_ranges, include_bytes); // [tree, ranges]
xfree(changed);
return 2;
}
+static int parser_reset(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (p && *p) {
+ ts_parser_reset(*p);
+ }
+
+ return 0;
+}
+
static int tree_copy(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!(*tree)) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
- push_tree(L, *tree, true); // [tree]
+ TSTree *copy = ts_tree_copy(ud->tree);
+ push_tree(L, copy); // [tree]
return 1;
}
@@ -465,8 +516,8 @@ static int tree_edit(lua_State *L)
return lua_error(L);
}
- TSTree **tree = tree_check(L, 1);
- if (!(*tree)) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
@@ -480,11 +531,29 @@ static int tree_edit(lua_State *L)
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
start_point, old_end_point, new_end_point };
- ts_tree_edit(*tree, &edit);
+ ts_tree_edit(ud->tree, &edit);
return 0;
}
+static int tree_get_ranges(lua_State *L)
+{
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
+ return 0;
+ }
+
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
+ uint32_t len;
+ TSRange *ranges = ts_tree_included_ranges(ud->tree, &len);
+
+ push_ranges(L, ranges, len, include_bytes);
+
+ xfree(ranges);
+ return 1;
+}
+
// Use the top of the stack (without popping it) to create a TSRange, it can be
// either a lua table or a TSNode
static void range_from_lua(lua_State *L, TSRange *range)
@@ -590,58 +659,159 @@ static int parser_get_ranges(lua_State *L)
return 0;
}
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
uint32_t len;
const TSRange *ranges = ts_parser_included_ranges(*p, &len);
- push_ranges(L, ranges, len);
+ push_ranges(L, ranges, len, include_bytes);
+ return 1;
+}
+
+static int parser_set_timeout(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ if (lua_gettop(L) < 2) {
+ luaL_error(L, "integer expected");
+ }
+
+ uint32_t timeout = (uint32_t)luaL_checkinteger(L, 2);
+ ts_parser_set_timeout_micros(*p, timeout);
+ return 0;
+}
+
+static int parser_get_timeout(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ lua_pushinteger(L, (lua_Integer)ts_parser_timeout_micros(*p));
+ return 1;
+}
+
+static void logger_cb(void *payload, TSLogType logtype, const char *s)
+{
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)payload;
+ if ((!opts->lex && logtype == TSLogTypeLex)
+ || (!opts->parse && logtype == TSLogTypeParse)) {
+ return;
+ }
+
+ lua_State *lstate = opts->lstate;
+
+ lua_rawgeti(lstate, LUA_REGISTRYINDEX, opts->cb);
+ lua_pushstring(lstate, logtype == TSLogTypeParse ? "parse" : "lex");
+ lua_pushstring(lstate, s);
+ if (lua_pcall(lstate, 2, 0, 0)) {
+ luaL_error(lstate, "Error executing treesitter logger callback");
+ }
+}
+
+static int parser_set_logger(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ if (!lua_isboolean(L, 2)) {
+ return luaL_argerror(L, 2, "boolean expected");
+ }
+
+ if (!lua_isboolean(L, 3)) {
+ return luaL_argerror(L, 3, "boolean expected");
+ }
+
+ if (!lua_isfunction(L, 4)) {
+ return luaL_argerror(L, 4, "function expected");
+ }
+
+ TSLuaLoggerOpts *opts = xmalloc(sizeof(TSLuaLoggerOpts));
+ lua_pushvalue(L, 4);
+ LuaRef ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+ *opts = (TSLuaLoggerOpts){
+ .lex = lua_toboolean(L, 2),
+ .parse = lua_toboolean(L, 3),
+ .cb = ref,
+ .lstate = L
+ };
+
+ TSLogger logger = {
+ .payload = (void *)opts,
+ .log = logger_cb
+ };
+
+ ts_parser_set_logger(*p, logger);
+ return 0;
+}
+
+static int parser_get_logger(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ TSLogger logger = ts_parser_logger(*p);
+ if (logger.log) {
+ TSLuaLoggerOpts *opts = (TSLuaLoggerOpts *)logger.payload;
+ lua_rawgeti(L, LUA_REGISTRYINDEX, opts->cb);
+ } else {
+ lua_pushnil(L);
+ }
+
return 1;
}
// Tree methods
-/// push tree interface on lua stack.
+/// Push tree interface on to the lua stack.
///
-/// This makes a copy of the tree, so ownership of the argument is unaffected.
-void push_tree(lua_State *L, TSTree *tree, bool do_copy)
+/// The tree is not copied. Ownership of the tree is transferred from C to
+/// Lua. If needed use ts_tree_copy() in the caller
+static void push_tree(lua_State *L, TSTree *tree)
{
if (tree == NULL) {
lua_pushnil(L);
return;
}
- TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
- if (do_copy) {
- *ud = ts_tree_copy(tree);
- } else {
- *ud = tree;
- }
+ TSLuaTree *ud = lua_newuserdata(L, sizeof(TSLuaTree)); // [udata]
+
+ ud->tree = tree;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREE); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
- // table used for node wrappers to keep a reference to tree wrapper
- // NB: in lua 5.3 the uservalue for the node could just be the tree, but
- // in lua 5.1 the uservalue (fenv) must be a table.
+ // To prevent the tree from being garbage collected, create a reference to it
+ // in the fenv which will be passed to userdata nodes of the tree.
+ // Note: environments (fenvs) associated with userdata have no meaning in Lua
+ // and are only used to associate a table.
lua_createtable(L, 1, 0); // [udata, reftable]
lua_pushvalue(L, -2); // [udata, reftable, udata]
lua_rawseti(L, -2, 1); // [udata, reftable]
lua_setfenv(L, -2); // [udata]
}
-static TSTree **tree_check(lua_State *L, int index)
+static TSLuaTree *tree_check(lua_State *L, int index)
{
- TSTree **ud = luaL_checkudata(L, index, TS_META_TREE);
+ TSLuaTree *ud = luaL_checkudata(L, index, TS_META_TREE);
return ud;
}
static int tree_gc(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!tree) {
- return 0;
+ TSLuaTree *ud = tree_check(L, 1);
+ if (ud) {
+ ts_tree_delete(ud->tree);
}
-
- ts_tree_delete(*tree);
return 0;
}
@@ -653,20 +823,20 @@ static int tree_tostring(lua_State *L)
static int tree_root(lua_State *L)
{
- TSTree **tree = tree_check(L, 1);
- if (!tree) {
+ TSLuaTree *ud = tree_check(L, 1);
+ if (!ud) {
return 0;
}
- TSNode root = ts_tree_root_node(*tree);
+ TSNode root = ts_tree_root_node(ud->tree);
push_node(L, root, 1);
return 1;
}
// Node methods
-/// push node interface on lua stack
+/// Push node interface on to the Lua stack
///
-/// top of stack must either be the tree this node belongs to or another node
+/// Top of stack must either be the tree this node belongs to or another node
/// of the same tree! This value is not popped. Can only be called inside a
/// cfunction with the tslua environment.
static void push_node(lua_State *L, TSNode node, int uindex)
@@ -680,6 +850,8 @@ static void push_node(lua_State *L, TSNode node, int uindex)
*ud = node;
lua_getfield(L, LUA_REGISTRYINDEX, TS_META_NODE); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
+
+ // Copy the fenv which contains the nodes tree.
lua_getfenv(L, uindex); // [udata, reftable]
lua_setfenv(L, -2); // [udata]
}
@@ -740,12 +912,26 @@ static int node_range(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
+
+ bool include_bytes = (lua_gettop(L) >= 2) && lua_toboolean(L, 2);
+
TSPoint start = ts_node_start_point(node);
TSPoint end = ts_node_end_point(node);
- lua_pushnumber(L, start.row);
- lua_pushnumber(L, start.column);
- lua_pushnumber(L, end.row);
- lua_pushnumber(L, end.column);
+
+ if (include_bytes) {
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, ts_node_start_byte(node));
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
+ lua_pushinteger(L, ts_node_end_byte(node));
+ return 6;
+ }
+
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
return 4;
}
@@ -757,9 +943,9 @@ static int node_start(lua_State *L)
}
TSPoint start = ts_node_start_point(node);
uint32_t start_byte = ts_node_start_byte(node);
- lua_pushnumber(L, start.row);
- lua_pushnumber(L, start.column);
- lua_pushnumber(L, start_byte);
+ lua_pushinteger(L, start.row);
+ lua_pushinteger(L, start.column);
+ lua_pushinteger(L, start_byte);
return 3;
}
@@ -771,9 +957,9 @@ static int node_end(lua_State *L)
}
TSPoint end = ts_node_end_point(node);
uint32_t end_byte = ts_node_end_byte(node);
- lua_pushnumber(L, end.row);
- lua_pushnumber(L, end.column);
- lua_pushnumber(L, end_byte);
+ lua_pushinteger(L, end.row);
+ lua_pushinteger(L, end.column);
+ lua_pushinteger(L, end_byte);
return 3;
}
@@ -784,7 +970,7 @@ static int node_child_count(lua_State *L)
return 0;
}
uint32_t count = ts_node_child_count(node);
- lua_pushnumber(L, count);
+ lua_pushinteger(L, count);
return 1;
}
@@ -795,7 +981,7 @@ static int node_named_child_count(lua_State *L)
return 0;
}
uint32_t count = ts_node_named_child_count(node);
- lua_pushnumber(L, count);
+ lua_pushinteger(L, count);
return 1;
}
@@ -816,7 +1002,7 @@ static int node_symbol(lua_State *L)
return 0;
}
TSSymbol symbol = ts_node_symbol(node);
- lua_pushnumber(L, symbol);
+ lua_pushinteger(L, symbol);
return 1;
}
@@ -882,6 +1068,26 @@ static int node_missing(lua_State *L)
return 1;
}
+static int node_extra(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_is_extra(node));
+ return 1;
+}
+
+static int node_has_changes(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+ lua_pushboolean(L, ts_node_has_changes(node));
+ return 1;
+}
+
static int node_has_error(lua_State *L)
{
TSNode node;
@@ -898,8 +1104,8 @@ static int node_child(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
- long num = lua_tointeger(L, 2);
- TSNode child = ts_node_child(node, (uint32_t)num);
+ uint32_t num = (uint32_t)lua_tointeger(L, 2);
+ TSNode child = ts_node_child(node, num);
push_node(L, child, 1);
return 1;
@@ -911,8 +1117,8 @@ static int node_named_child(lua_State *L)
if (!node_check(L, 1, &node)) {
return 0;
}
- long num = lua_tointeger(L, 2);
- TSNode child = ts_node_named_child(node, (uint32_t)num);
+ uint32_t num = (uint32_t)lua_tointeger(L, 2);
+ TSNode child = ts_node_named_child(node, num);
push_node(L, child, 1);
return 1;
@@ -1108,6 +1314,19 @@ static int node_root(lua_State *L)
return 1;
}
+static int node_tree(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ lua_getfenv(L, 1); // [udata, reftable]
+ lua_rawgeti(L, -1, 1); // [udata, reftable, tree_udata]
+
+ return 1;
+}
+
static int node_byte_length(lua_State *L)
{
TSNode node;
@@ -1118,7 +1337,23 @@ static int node_byte_length(lua_State *L)
uint32_t start_byte = ts_node_start_byte(node);
uint32_t end_byte = ts_node_end_byte(node);
- lua_pushnumber(L, end_byte - start_byte);
+ lua_pushinteger(L, end_byte - start_byte);
+ return 1;
+}
+
+static int node_equal(lua_State *L)
+{
+ TSNode node1;
+ if (!node_check(L, 1, &node1)) {
+ return 0;
+ }
+
+ TSNode node2;
+ if (!node_check(L, 2, &node2)) {
+ return luaL_error(L, "TSNode expected");
+ }
+
+ lua_pushboolean(L, ts_node_eq(node1, node2));
return 1;
}
@@ -1213,11 +1448,12 @@ static int node_rawquery(lua_State *L)
} else {
cursor = ts_query_cursor_new();
}
- // TODO(clason): API introduced after tree-sitter release 0.19.5
- // remove guard when minimum ts version is bumped to 0.19.6+
-#ifdef NVIM_TS_HAS_SET_MATCH_LIMIT
- ts_query_cursor_set_match_limit(cursor, 64);
+
+#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH
+ // reset the start depth
+ ts_query_cursor_set_max_start_depth(cursor, UINT32_MAX);
#endif
+ ts_query_cursor_set_match_limit(cursor, 256);
ts_query_cursor_exec(cursor, query, node);
bool captures = lua_toboolean(L, 3);
@@ -1228,6 +1464,29 @@ static int node_rawquery(lua_State *L)
ts_query_cursor_set_point_range(cursor, (TSPoint){ start, 0 }, (TSPoint){ end, 0 });
}
+ if (lua_gettop(L) >= 6 && !lua_isnil(L, 6)) {
+ if (!lua_istable(L, 6)) {
+ return luaL_error(L, "table expected");
+ }
+ lua_pushnil(L);
+ // stack: [dict, ..., nil]
+ while (lua_next(L, 6)) {
+ // stack: [dict, ..., key, value]
+ if (lua_type(L, -2) == LUA_TSTRING) {
+ char *k = (char *)lua_tostring(L, -2);
+ if (strequal("max_start_depth", k)) {
+ // TODO(lewis6991): remove ifdef when min TS version is 0.20.9
+#ifdef NVIM_TS_HAS_SET_MAX_START_DEPTH
+ uint32_t max_start_depth = (uint32_t)lua_tointeger(L, -1);
+ ts_query_cursor_set_max_start_depth(cursor, max_start_depth);
+#endif
+ }
+ }
+ lua_pop(L, 1); // pop the value; lua_next will pop the key.
+ // stack: [dict, ..., key]
+ }
+ }
+
TSLua_cursor *ud = lua_newuserdata(L, sizeof(*ud)); // [udata]
ud->cursor = cursor;
ud->predicated_match = -1;
@@ -1281,8 +1540,9 @@ int tslua_parse_query(lua_State *L)
TSQuery *query = ts_query_new(lang, src, (uint32_t)len, &error_offset, &error_type);
if (!query) {
- return luaL_error(L, "query: %s at position %d for language %s",
- query_err_string(error_type), (int)error_offset, lang_name);
+ char err_msg[IOSIZE];
+ query_err_string(src, (int)error_offset, error_type, err_msg, sizeof(err_msg));
+ return luaL_error(L, "%s", err_msg);
}
TSQuery **ud = lua_newuserdata(L, sizeof(TSQuery *)); // [udata]
@@ -1292,24 +1552,79 @@ int tslua_parse_query(lua_State *L)
return 1;
}
-static const char *query_err_string(TSQueryError err)
+static const char *query_err_to_string(TSQueryError error_type)
{
- switch (err) {
+ switch (error_type) {
case TSQueryErrorSyntax:
- return "invalid syntax";
+ return "Invalid syntax:\n";
case TSQueryErrorNodeType:
- return "invalid node type";
+ return "Invalid node type ";
case TSQueryErrorField:
- return "invalid field";
+ return "Invalid field name ";
case TSQueryErrorCapture:
- return "invalid capture";
+ return "Invalid capture name ";
case TSQueryErrorStructure:
- return "invalid structure";
+ return "Impossible pattern:\n";
default:
return "error";
}
}
+static void query_err_string(const char *src, int error_offset, TSQueryError error_type, char *err,
+ size_t errlen)
+{
+ int line_start = 0;
+ int row = 0;
+ const char *error_line = NULL;
+ int error_line_len = 0;
+
+ const char *end_str;
+ do {
+ const char *src_tmp = src + line_start;
+ end_str = strchr(src_tmp, '\n');
+ int line_length = end_str != NULL ? (int)(end_str - src_tmp) : (int)strlen(src_tmp);
+ int line_end = line_start + line_length;
+ if (line_end > error_offset) {
+ error_line = src_tmp;
+ error_line_len = line_length;
+ break;
+ }
+ line_start = line_end + 1;
+ row++;
+ } while (end_str != NULL);
+
+ int column = error_offset - line_start;
+
+ const char *type_msg = query_err_to_string(error_type);
+ snprintf(err, errlen, "Query error at %d:%d. %s", row + 1, column + 1, type_msg);
+ size_t offset = strlen(err);
+ errlen = errlen - offset;
+ err = err + offset;
+
+ // Error types that report names
+ if (error_type == TSQueryErrorNodeType
+ || error_type == TSQueryErrorField
+ || error_type == TSQueryErrorCapture) {
+ const char *suffix = src + error_offset;
+ int suffix_len = 0;
+ char c = suffix[suffix_len];
+ while (isalnum(c) || c == '_' || c == '-' || c == '.') {
+ c = suffix[++suffix_len];
+ }
+ snprintf(err, errlen, "\"%.*s\":\n", suffix_len, suffix);
+ offset = strlen(err);
+ errlen = errlen - offset;
+ err = err + offset;
+ }
+
+ if (!error_line) {
+ snprintf(err, errlen, "Unexpected EOF\n");
+ return;
+ }
+
+ snprintf(err, errlen, "%.*s\n%*s^\n", error_line_len, error_line, column, "");
+}
+
static TSQuery *query_check(lua_State *L, int index)
{
TSQuery **ud = luaL_checkudata(L, index, TS_META_QUERY);
@@ -1367,7 +1682,7 @@ static int query_inspect(lua_State *L)
&strlen);
lua_pushlstring(L, str, strlen); // [retval, patterns, pat, pred, item]
} else if (step[k].type == TSQueryPredicateStepTypeCapture) {
- lua_pushnumber(L, step[k].value_id + 1); // [..., pat, pred, item]
+ lua_pushinteger(L, step[k].value_id + 1); // [..., pat, pred, item]
} else {
abort();
}
diff --git a/src/nvim/lua/treesitter.h b/src/nvim/lua/treesitter.h
index b69fb9dfae..4ef9a10602 100644
--- a/src/nvim/lua/treesitter.h
+++ b/src/nvim/lua/treesitter.h
@@ -1,14 +1,7 @@
-#ifndef NVIM_LUA_TREESITTER_H
-#define NVIM_LUA_TREESITTER_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
-
-#include "tree_sitter/api.h"
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/treesitter.h.generated.h"
#endif
-
-#endif // NVIM_LUA_TREESITTER_H
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index 857b159af5..16c3aa5e11 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -1,9 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <lauxlib.h>
#include <lua.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
#include "luaconf.h"
@@ -13,9 +11,9 @@
#include "nvim/lua/converter.h"
#include "nvim/lua/executor.h"
#include "nvim/lua/xdiff.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
#include "xdiff/xdiff.h"
#define COMPARED_BUFFER0 (1 << 0)
@@ -32,7 +30,7 @@ typedef struct {
Error *err;
mmfile_t *ma;
mmfile_t *mb;
- bool linematch;
+ int64_t linematch;
bool iwhite;
} hunkpriv_t;
@@ -63,25 +61,25 @@ static void lua_pushhunk(lua_State *lstate, long start_a, long count_a, long sta
lua_rawseti(lstate, -2, (signed)lua_objlen(lstate, -2) + 1);
}
-static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, long start_a,
- long count_a, long start_b, long count_b, bool iwhite)
+static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb, int start_a,
+ int count_a, int start_b, int count_b, bool iwhite)
{
// get the pointer to char of the start of the diff to pass it to linematch algorithm
const char *diff_begin[2] = { ma->ptr, mb->ptr };
- int diff_length[2] = { (int)count_a, (int)count_b };
+ int diff_length[2] = { count_a, count_b };
- fastforward_buf_to_lnum(&diff_begin[0], start_a + 1);
- fastforward_buf_to_lnum(&diff_begin[1], start_b + 1);
+ fastforward_buf_to_lnum(&diff_begin[0], (linenr_T)start_a + 1);
+ fastforward_buf_to_lnum(&diff_begin[1], (linenr_T)start_b + 1);
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
- long lnuma = start_a, lnumb = start_b;
+ int lnuma = start_a, lnumb = start_b;
- long hunkstarta = lnuma;
- long hunkstartb = lnumb;
- long hunkcounta = 0;
- long hunkcountb = 0;
+ int hunkstarta = lnuma;
+ int hunkstartb = lnumb;
+ int hunkcounta = 0;
+ int hunkcountb = 0;
for (size_t i = 0; i < decisions_length; i++) {
if (i && (decisions[i - 1] != decisions[i])) {
lua_pushhunk(lstate, hunkstarta, hunkcounta, hunkstartb, hunkcountb);
@@ -109,8 +107,8 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
{
luaL_Buffer *buf = (luaL_Buffer *)priv;
for (int i = 0; i < nbuf; i++) {
- const long size = mb[i].size;
- for (long total = 0; total < size; total += LUAL_BUFFERSIZE) {
+ const int size = mb[i].size;
+ for (int total = 0; total < size; total += LUAL_BUFFERSIZE) {
const int tocopy = MIN((int)(size - total), LUAL_BUFFERSIZE);
char *p = luaL_prepbuffer(buf);
if (!p) {
@@ -124,11 +122,11 @@ static int write_string(void *priv, mmbuffer_t *mb, int nbuf)
}
// hunk_func callback used when opts.hunk_lines = true
-static int hunk_locations_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data)
+static int hunk_locations_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
{
hunkpriv_t *priv = (hunkpriv_t *)cb_data;
lua_State *lstate = priv->lstate;
- if (priv->linematch) {
+ if (priv->linematch > 0 && count_a + count_b <= priv->linematch) {
get_linematch_results(lstate, priv->ma, priv->mb, start_a, count_a, start_b, count_b,
priv->iwhite);
} else {
@@ -139,7 +137,7 @@ static int hunk_locations_cb(long start_a, long count_a, long start_b, long coun
}
// hunk_func callback used when opts.on_hunk is given
-static int call_on_hunk_cb(long start_a, long count_a, long start_b, long count_b, void *cb_data)
+static int call_on_hunk_cb(int start_a, int count_a, int start_b, int count_b, void *cb_data)
{
// Mimic extra offsets done by xdiff, see:
// src/xdiff/xemit.c:284
@@ -193,11 +191,11 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char *
{
if (actType != expType) {
const char *type_str =
- expType == kObjectTypeString ? "string" :
- expType == kObjectTypeInteger ? "integer" :
- expType == kObjectTypeBoolean ? "boolean" :
- expType == kObjectTypeLuaRef ? "function" :
- "NA";
+ expType == kObjectTypeString
+ ? "string" : (expType == kObjectTypeInteger
+ ? "integer" : (expType == kObjectTypeBoolean
+ ? "boolean" : (expType == kObjectTypeLuaRef
+ ? "function" : "NA")));
api_set_error(err, kErrorTypeValidation, "%s is not a %s", name,
type_str);
@@ -208,7 +206,7 @@ static bool check_xdiff_opt(ObjectType actType, ObjectType expType, const char *
}
static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, xpparam_t *params,
- bool *linematch, Error *err)
+ int64_t *linematch, Error *err)
{
const DictionaryOf(LuaRef) opts = nlua_pop_Dictionary(lstate, true, err);
@@ -257,16 +255,20 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg,
if (check_xdiff_opt(v->type, kObjectTypeInteger, "ctxlen", err)) {
goto exit_1;
}
- cfg->ctxlen = v->data.integer;
+ cfg->ctxlen = (long)v->data.integer;
} else if (strequal("interhunkctxlen", k.data)) {
if (check_xdiff_opt(v->type, kObjectTypeInteger, "interhunkctxlen",
err)) {
goto exit_1;
}
- cfg->interhunkctxlen = v->data.integer;
+ cfg->interhunkctxlen = (long)v->data.integer;
} else if (strequal("linematch", k.data)) {
- *linematch = api_object_to_bool(*v, "linematch", false, err);
- if (ERROR_SET(err)) {
+ if (v->type == kObjectTypeBoolean) {
+ *linematch = v->data.boolean ? INT64_MAX : 0;
+ } else if (v->type == kObjectTypeInteger) {
+ *linematch = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "linematch must be a boolean or integer");
goto exit_1;
}
} else {
@@ -330,7 +332,7 @@ int nlua_xdl_diff(lua_State *lstate)
xdemitconf_t cfg;
xpparam_t params;
xdemitcb_t ecb;
- bool linematch = false;
+ int64_t linematch = 0;
CLEAR_FIELD(cfg);
CLEAR_FIELD(params);
@@ -362,18 +364,18 @@ int nlua_xdl_diff(lua_State *lstate)
cfg.hunk_func = call_on_hunk_cb;
priv = (hunkpriv_t) {
.lstate = lstate,
- .err = &err,
+ .err = &err,
};
ecb.priv = &priv;
break;
case kNluaXdiffModeLocations:
cfg.hunk_func = hunk_locations_cb;
priv = (hunkpriv_t) {
- .lstate = lstate,
- .ma = &ma,
- .mb = &mb,
+ .lstate = lstate,
+ .ma = &ma,
+ .mb = &mb,
.linematch = linematch,
- .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0
+ .iwhite = (params.flags & XDF_IGNORE_WHITESPACE) > 0
};
ecb.priv = &priv;
lua_createtable(lstate, 0, 0);
diff --git a/src/nvim/lua/xdiff.h b/src/nvim/lua/xdiff.h
index b172d2f922..2ea74a79e8 100644
--- a/src/nvim/lua/xdiff.h
+++ b/src/nvim/lua/xdiff.h
@@ -1,12 +1,7 @@
-#ifndef NVIM_LUA_XDIFF_H
-#define NVIM_LUA_XDIFF_H
+#pragma once
-#include <lauxlib.h>
-#include <lua.h>
-#include <lualib.h>
+#include <lua.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/xdiff.h.generated.h"
#endif
-
-#endif // NVIM_LUA_XDIFF_H
diff --git a/src/nvim/macros.h b/src/nvim/macros_defs.h
index 242e3c381a..a7af2f91c3 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MACROS_H
-#define NVIM_MACROS_H
+#pragma once
#include "auto/config.h"
@@ -61,8 +60,6 @@
/// Don't apply 'langmap' if the character comes from the Stuff buffer or from a
/// mapping and the langnoremap option was set.
/// The do-while is just to ignore a ';' after the macro.
-///
-/// -V:LANGMAP_ADJUST:560
#define LANGMAP_ADJUST(c, condition) \
do { \
if (*p_langmap \
@@ -82,16 +79,7 @@
#define READBIN "rb"
#define APPENDBIN "ab"
-// mch_open_rw(): invoke os_open() with third argument for user R/W.
-#if defined(UNIX) // open in rw------- mode
-# define MCH_OPEN_RW(n, f) os_open((n), (f), (mode_t)0600)
-#elif defined(MSWIN)
-# define MCH_OPEN_RW(n, f) os_open((n), (f), S_IREAD | S_IWRITE)
-#else
-# define MCH_OPEN_RW(n, f) os_open((n), (f), 0)
-#endif
-
-#define REPLACE_NORMAL(s) (((s) & REPLACE_FLAG) && !((s) & VREPLACE_FLAG))
+#define REPLACE_NORMAL(s) (((s)& REPLACE_FLAG) && !((s)& VREPLACE_FLAG))
// MB_PTR_ADV(): advance a pointer to the next character, taking care of
// multi-byte characters if needed. Skip over composing chars.
@@ -120,8 +108,6 @@
/// error. A mechanism to detect many (though not all) of those errors at
/// compile time is implemented. It works by the second division producing
/// a division by zero in those cases (-Wdiv-by-zero in GCC).
-///
-/// -V:ARRAY_SIZE:1063
#define ARRAY_SIZE(arr) \
((sizeof(arr)/sizeof((arr)[0])) \
/ ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
@@ -163,17 +149,13 @@
# define FALLTHROUGH
#endif
-// -V:STRUCT_CAST:641
-
-/// Change type of structure pointers: cast `struct a *` to `struct b *`
-///
-/// Used to silence PVS errors.
-///
-/// @param Type Structure to cast to.
-/// @param obj Object to cast.
-///
-/// @return ((Type *)obj).
-#define STRUCT_CAST(Type, obj) ((Type *)(obj))
+#if defined(__clang__) || defined(__GNUC__)
+# define UNREACHABLE __builtin_unreachable()
+#elif defined(_MSVC_VER)
+# define UNREACHABLE __assume(false)
+#else
+# define UNREACHABLE
+#endif
// Type of uv_buf_t.len is platform-dependent.
// Related: https://github.com/libuv/libuv/pull/1236
@@ -228,5 +210,3 @@
#endif
#define EMPTY_POS(a) ((a).lnum == 0 && (a).col == 0 && (a).coladd == 0)
-
-#endif // NVIM_MACROS_H
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e26922bf8e..6585bd1df7 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -1,7 +1,9 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-#define EXTERN
+// Make sure extern symbols are exported on Windows
+#ifdef WIN32
+# define EXTERN __declspec(dllexport)
+#else
+# define EXTERN
+#endif
#include <assert.h>
#include <limits.h>
#include <msgpack/pack.h>
@@ -10,10 +12,18 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#ifdef ENABLE_ASAN_UBSAN
+# include <sanitizer/asan_interface.h>
+# include <sanitizer/ubsan_interface.h>
+#endif
-#include "auto/config.h"
+#include "auto/config.h" // IWYU pragma: keep
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/ui.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -24,14 +34,17 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/event/process.h"
#include "nvim/event/stream.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -41,60 +54,52 @@
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/lua/secure.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mark.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
+#include "nvim/msgpack_rpc/channel.h"
+#include "nvim/msgpack_rpc/helpers.h"
+#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/fileio.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
+#include "nvim/os/signal.h"
#include "nvim/os/stdpaths_defs.h"
-#include "nvim/os/time.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/shada.h"
-#include "nvim/sign.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
#include "nvim/ui_compositor.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
#endif
-#include "nvim/api/extmark.h"
-#include "nvim/api/private/defs.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/api/ui.h"
-#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
-#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/msgpack_rpc/server.h"
-#include "nvim/os/signal.h"
// values for "window_layout"
enum {
@@ -167,10 +172,9 @@ bool event_teardown(void)
/// Performs early initialization.
///
-/// Needed for unit tests. Must be called after `time_init()`.
+/// Needed for unit tests.
void early_init(mparm_T *paramp)
{
- env_init();
estack_init();
cmdline_init();
eval_init(); // init global variables
@@ -182,19 +186,24 @@ void early_init(mparm_T *paramp)
#ifdef MSWIN
OSVERSIONINFO ovi;
ovi.dwOSVersionInfoSize = sizeof(ovi);
+ // Disable warning about GetVersionExA being deprecated. There doesn't seem to be a convenient
+ // replacement that doesn't add a ton of extra code as of writing this.
+# ifdef _MSC_VER
+# pragma warning(suppress : 4996)
GetVersionEx(&ovi);
+# else
+ GetVersionEx(&ovi);
+# endif
snprintf(windowsVersion, sizeof(windowsVersion), "%d.%d",
(int)ovi.dwMajorVersion, (int)ovi.dwMinorVersion);
#endif
TIME_MSG("early init");
-#if defined(HAVE_LOCALE_H)
// Setup to use the current locale (for ctype() and many other things).
// NOTE: Translated messages with encodings other than latin1 will not
// work until set_init_1() has been called!
init_locale();
-#endif
// tabpage local options (p_ch) must be set before allocating first tabpage.
set_init_tablocal();
@@ -214,8 +223,6 @@ void early_init(mparm_T *paramp)
TIME_MSG("inits 1");
set_lang_var(); // set v:lang and v:ctype
-
- init_signs();
}
#ifdef MAKE_LIB
@@ -239,6 +246,11 @@ int main(int argc, char **argv)
argv0 = argv[0];
+ if (!appname_is_valid()) {
+ os_errmsg("$NVIM_APPNAME must be a name or relative path.\n");
+ exit(1);
+ }
+
if (argc > 1 && STRICMP(argv[1], "-ll") == 0) {
if (argc == 2) {
print_mainerr(err_arg_missing, argv[1]);
@@ -251,16 +263,11 @@ int main(int argc, char **argv)
mparm_T params; // various parameters passed between
// main() and other functions.
char *cwd = NULL; // current working dir on startup
- time_init();
// Many variables are in `params` so that we can pass them around easily.
// `argc` and `argv` are also copied, so that they can be changed.
init_params(&params, argc, argv);
- // Since os_open is called during the init_startuptime, we need to call
- // fs_init before it.
- fs_init();
-
init_startuptime(&params);
// Need to find "--clean" before actually parsing arguments.
@@ -294,6 +301,16 @@ int main(int argc, char **argv)
}
}
+ if (GARGCOUNT > 0) {
+ fname = get_fname(&params, cwd);
+ }
+
+ // Recovery mode without a file name: List swap files.
+ // In this case, no UI is needed.
+ if (recoverymode && fname == NULL) {
+ headless_mode = true;
+ }
+
#ifdef MSWIN
// on windows we use CONIN special file, thus we don't know this yet.
bool has_term = true;
@@ -320,21 +337,11 @@ int main(int argc, char **argv)
uint64_t rv = ui_client_start_server(params.argc, params.argv);
if (!rv) {
os_errmsg("Failed to start Nvim server!\n");
- getout(1);
+ os_exit(1);
}
ui_client_channel_id = rv;
}
- if (GARGCOUNT > 0) {
- fname = get_fname(&params, cwd);
- }
-
- // Recovery mode without a file name: List swap files.
- // In this case, no UI is needed.
- if (recoverymode && fname == NULL) {
- headless_mode = true;
- }
-
TIME_MSG("expanding arguments");
if (params.diff_mode && params.window_count == -1) {
@@ -343,7 +350,7 @@ int main(int argc, char **argv)
// Don't redraw until much later.
RedrawingDisabled++;
- setbuf(stdout, NULL);
+ setbuf(stdout, NULL); // NOLINT(bugprone-unsafe-functions)
full_screen = !silent_mode;
@@ -356,7 +363,7 @@ int main(int argc, char **argv)
}
assert(p_ch >= 0 && Rows >= p_ch && Rows - p_ch <= INT_MAX);
- cmdline_row = (int)(Rows - p_ch);
+ cmdline_row = Rows - (int)p_ch;
msg_row = cmdline_row;
default_grid_alloc(); // allocate screen buffers
set_init_2(headless_mode);
@@ -380,13 +387,15 @@ int main(int argc, char **argv)
if (ui_client_channel_id) {
ui_client_run(remote_ui); // NORETURN
}
+ assert(!ui_client_channel_id && !use_builtin_ui);
// Wait for UIs to set up Nvim or show early messages
// and prompts (--cmd, swapfile dialog, …).
bool use_remote_ui = (embedded_mode && !headless_mode);
+ bool listen_and_embed = params.listen_addr != NULL;
if (use_remote_ui) {
TIME_MSG("waiting for UI");
- remote_ui_wait_for_attach();
+ remote_ui_wait_for_attach(!listen_and_embed);
TIME_MSG("done waiting for UI");
firstwin->w_prev_height = firstwin->w_height; // may have changed
}
@@ -404,19 +413,9 @@ int main(int argc, char **argv)
open_script_files(&params);
- // Default mappings (incl. menus)
- Error err = ERROR_INIT;
- Object o = NLUA_EXEC_STATIC("return vim._init_default_mappings()",
- (Array)ARRAY_DICT_INIT, &err);
- assert(!ERROR_SET(&err));
- api_clear_error(&err);
- assert(o.type == kObjectTypeNil);
- api_free_object(o);
-
- TIME_MSG("init default mappings");
+ nlua_init_defaults();
- init_default_autocmds();
- TIME_MSG("init default autocommands");
+ TIME_MSG("init default mappings & autocommands");
bool vimrc_none = strequal(params.use_vimrc, "NONE");
@@ -440,8 +439,7 @@ int main(int argc, char **argv)
// If using the runtime (-u is not NONE), enable syntax & filetype plugins.
if (!vimrc_none || params.clean) {
- // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype
- // off.
+ // Sources filetype.lua unless the user explicitly disabled it with :filetype off.
filetype_maybe_enable();
// Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can
// disable syntax highlighting with `:syntax off` if they wish.
@@ -457,7 +455,7 @@ int main(int argc, char **argv)
// Recovery mode without a file name: List swap files.
// Uses the 'dir' option, therefore it must be after the initializations.
if (recoverymode && fname == NULL) {
- recover_names(NULL, true, 0, NULL);
+ recover_names(NULL, true, NULL, 0, NULL);
os_exit(0);
}
@@ -580,16 +578,16 @@ int main(int argc, char **argv)
// 'autochdir' has been postponed.
do_autochdir();
- set_vim_var_nr(VV_VIM_DID_ENTER, 1L);
+ set_vim_var_nr(VV_VIM_DID_ENTER, 1);
apply_autocmds(EVENT_VIMENTER, NULL, NULL, false, curbuf);
TIME_MSG("VimEnter autocommands");
- if (use_remote_ui || use_builtin_ui) {
- do_autocmd_uienter(use_remote_ui ? CHAN_STDIO : 0, true);
+ if (use_remote_ui) {
+ do_autocmd_uienter_all();
TIME_MSG("UIEnter autocommands");
}
#ifdef MSWIN
- if (use_builtin_ui) {
+ if (use_remote_ui) {
os_icon_init();
}
os_title_save();
@@ -605,7 +603,7 @@ int main(int argc, char **argv)
// scrollbind, sync the scrollbind now.
if (curwin->w_p_diff && curwin->w_p_scb) {
update_topline(curwin);
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
TIME_MSG("diff scrollbinding");
}
@@ -621,8 +619,14 @@ int main(int argc, char **argv)
}
if (params.luaf != NULL) {
+ // Like "--cmd", "+", "-c" and "-S", don't truncate messages.
+ msg_scroll = true;
bool lua_ok = nlua_exec_file(params.luaf);
TIME_MSG("executing Lua -l script");
+ if (msg_didout) {
+ msg_putchar('\n');
+ msg_didout = false;
+ }
getout(lua_ok ? 0 : 1);
}
@@ -645,6 +649,9 @@ void os_exit(int r)
if (ui_client_channel_id) {
ui_client_stop();
+ if (r == 0) {
+ r = ui_client_exit_status;
+ }
} else {
ui_flush();
ui_call_stop();
@@ -671,6 +678,7 @@ void os_exit(int r)
void getout(int exitval)
FUNC_ATTR_NORETURN
{
+ assert(!ui_client_channel_id);
exiting = true;
// On error during Ex mode, exit with a non-zero code.
@@ -681,8 +689,8 @@ void getout(int exitval)
set_vim_var_nr(VV_EXITING, exitval);
- // Position the cursor on the last screen line, below all the text
- ui_cursor_goto(Rows - 1, 0);
+ // Invoked all deferred functions in the function stack.
+ invoke_all_defer();
// Optionally print hashtable efficiency.
hash_debug_results();
@@ -694,7 +702,7 @@ void getout(int exitval)
for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) {
next_tp = tp->tp_next;
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
- if (wp->w_buffer == NULL) {
+ if (wp->w_buffer == NULL || !buf_valid(wp->w_buffer)) {
// Autocmd must have close the buffer already, skip.
continue;
}
@@ -769,9 +777,6 @@ void getout(int exitval)
wait_return(false);
}
- // Position the cursor again, the autocommands may have moved it
- ui_cursor_goto(Rows - 1, 0);
-
// Apply 'titleold'.
if (p_title && *p_titleold != NUL) {
ui_call_set_title(cstr_as_string(p_titleold));
@@ -790,10 +795,11 @@ void getout(int exitval)
os_exit(exitval);
}
-/// Preserve files, print contents of `IObuff`, and exit 1.
+/// Preserve files, print contents of `errmsg`, and exit 1.
+/// @param errmsg If NULL, this function will not print anything.
///
/// May be called from deadly_signal().
-void preserve_exit(void)
+void preserve_exit(const char *errmsg)
FUNC_ATTR_NORETURN
{
// 'true' when we are sure to exit, e.g., after a deadly signal
@@ -813,19 +819,24 @@ void preserve_exit(void)
signal_reject_deadly();
if (ui_client_channel_id) {
+ // For TUI: exit alternate screen so that the error messages can be seen.
+ ui_client_stop();
+ }
+ if (errmsg != NULL) {
+ os_errmsg(errmsg);
+ os_errmsg("\n");
+ }
+ if (ui_client_channel_id) {
os_exit(1);
}
- os_errmsg(IObuff);
- os_errmsg("\n");
- ui_flush();
-
ml_close_notmod(); // close all not-modified buffers
FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
- os_errmsg("Vim: preserving files...\r\n");
- ui_flush();
+ if (errmsg != NULL) {
+ os_errmsg("Vim: preserving files...\r\n");
+ }
ml_sync_all(false, false, true); // preserve all swap files
break;
}
@@ -833,7 +844,9 @@ void preserve_exit(void)
ml_close_all(false); // close all memfiles, without deleting
- os_errmsg("Vim: Finished.\r\n");
+ if (errmsg != NULL) {
+ os_errmsg("Vim: Finished.\r\n");
+ }
getout(1);
}
@@ -853,7 +866,7 @@ void preserve_exit(void)
static int get_number_arg(const char *p, int *idx, int def)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- if (ascii_isdigit(p[*idx])) { // -V522
+ if (ascii_isdigit(p[*idx])) {
def = atoi(&(p[*idx]));
while (ascii_isdigit(p[*idx])) {
*idx = *idx + 1;
@@ -901,6 +914,11 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
os_errmsg(connect_error);
os_errmsg("\n");
os_exit(1);
+ } else if (strequal(server_addr, os_getenv("NVIM"))) {
+ os_errmsg("Cannot attach UI of :terminal child to its parent. ");
+ os_errmsg("(Unset $NVIM to skip this check)");
+ os_errmsg("\n");
+ os_exit(1);
}
ui_client_channel_id = chan;
@@ -908,9 +926,8 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
}
Array args = ARRAY_DICT_INIT;
- String arg_s;
for (int t_argc = remote_args; t_argc < argc; t_argc++) {
- arg_s = cstr_to_string(argv[t_argc]);
+ String arg_s = cstr_to_string(argv[t_argc]);
ADD(args, STRING_OBJ(arg_s));
}
@@ -940,7 +957,7 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
TriState tabbed = kNone;
for (size_t i = 0; i < rvobj.data.dictionary.size; i++) {
- if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) {
+ if (strequal(rvobj.data.dictionary.items[i].key.data, "errmsg")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
os_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n");
os_exit(2);
@@ -948,13 +965,19 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
os_errmsg(rvobj.data.dictionary.items[i].value.data.string.data);
os_errmsg("\n");
os_exit(2);
- } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) {
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "result")) {
+ if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
+ os_errmsg("vim._cs_remote returned an unexpected type for 'result'\n");
+ os_exit(2);
+ }
+ os_msg(rvobj.data.dictionary.items[i].value.data.string.data);
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "tabbed")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
os_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n");
os_exit(2);
}
tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse;
- } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) {
+ } else if (strequal(rvobj.data.dictionary.items[i].key.data, "should_exit")) {
if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
os_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n");
os_exit(2);
@@ -997,7 +1020,7 @@ static void command_line_scan(mparm_T *parmp)
int argv_idx; // index in argv[n][]
bool had_minmin = false; // found "--" argument
int want_argument; // option argument with argument
- long n;
+ int n;
argc--;
argv++;
@@ -1048,6 +1071,10 @@ static void command_line_scan(mparm_T *parmp)
version();
os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) {
+#ifdef MSWIN
+ // set stdout to binary to avoid crlf in --api-info output
+ _setmode(STDOUT_FILENO, _O_BINARY);
+#endif
FileDescriptor fp;
const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
kFileWriteOnly);
@@ -1095,7 +1122,7 @@ static void command_line_scan(mparm_T *parmp)
} else if (STRNICMP(argv[0] + argv_idx, "clean", 5) == 0) {
parmp->use_vimrc = "NONE";
parmp->clean = true;
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
} else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) {
nlua_disable_preload = true;
} else {
@@ -1109,7 +1136,7 @@ static void command_line_scan(mparm_T *parmp)
}
break;
case 'A': // "-A" start in Arabic mode.
- set_option_value_give_err("arabic", 1L, NULL, 0);
+ set_option_value_give_err("arabic", BOOLEAN_OPTVAL(true), 0);
break;
case 'b': // "-b" binary mode.
// Needs to be effective before expanding file names, because
@@ -1138,9 +1165,9 @@ static void command_line_scan(mparm_T *parmp)
case 'h': // "-h" give help message
usage();
os_exit(0);
- case 'H': // "-H" start in Hebrew mode: rl + hkmap set.
- p_hkmap = true;
- set_option_value_give_err("rl", 1L, NULL, 0);
+ case 'H': // "-H" start in Hebrew mode: rl + keymap=hebrew set.
+ set_option_value_give_err("keymap", STATIC_CSTR_AS_OPTVAL("hebrew"), 0);
+ set_option_value_give_err("rl", BOOLEAN_OPTVAL(true), 0);
break;
case 'M': // "-M" no changes or writing of files
reset_modifiable();
@@ -1220,7 +1247,7 @@ static void command_line_scan(mparm_T *parmp)
// default is 10: a little bit verbose
p_verbose = get_number_arg(argv[0], &argv_idx, 10);
if (argv[0][argv_idx] != NUL) {
- set_option_value_give_err("verbosefile", 0L, argv[0] + argv_idx, 0);
+ set_option_value_give_err("verbosefile", CSTR_AS_OPTVAL(argv[0] + argv_idx), 0);
argv_idx = (int)strlen(argv[0]);
}
break;
@@ -1228,7 +1255,7 @@ static void command_line_scan(mparm_T *parmp)
// "-w {scriptout}" write to script
if (ascii_isdigit((argv[0])[argv_idx])) {
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL((OptInt)n), 0);
break;
}
want_argument = true;
@@ -1324,7 +1351,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 'i': // "-i {shada}" use for shada
- set_option_value_give_err("shadafile", 0L, argv[0], 0);
+ set_option_value_give_err("shadafile", CSTR_AS_OPTVAL(argv[0]), 0);
break;
case 'l': // "-l" Lua script: args after "-l".
@@ -1334,11 +1361,11 @@ static void command_line_scan(mparm_T *parmp)
parmp->no_swap_file = true;
parmp->use_vimrc = parmp->use_vimrc ? parmp->use_vimrc : "NONE";
if (p_shadafile == NULL || *p_shadafile == NUL) {
- set_option_value_give_err("shadafile", 0L, "NONE", 0);
+ set_option_value_give_err("shadafile", STATIC_CSTR_AS_OPTVAL("NONE"), 0);
}
parmp->luaf = argv[0];
argc--;
- if (argc > 0) { // Lua args after "-l <file>".
+ if (argc >= 0) { // Lua args after "-l <file>".
parmp->lua_arg0 = parmp->argc - argc;
argc = 0;
}
@@ -1370,7 +1397,7 @@ scripterror:
if (ascii_isdigit(*(argv[0]))) {
argv_idx = 0;
n = get_number_arg(argv[0], &argv_idx, 10);
- set_option_value_give_err("window", n, NULL, 0);
+ set_option_value_give_err("window", NUMBER_OPTVAL((OptInt)n), 0);
argv_idx = -1;
break;
}
@@ -1461,7 +1488,7 @@ static void init_startuptime(mparm_T *paramp)
{
for (int i = 1; i < paramp->argc - 1; i++) {
if (STRICMP(paramp->argv[i], "--startuptime") == 0) {
- time_fd = os_fopen(paramp->argv[i + 1], "a");
+ time_fd = fopen(paramp->argv[i + 1], "a");
time_start("--- NVIM STARTING ---");
break;
}
@@ -1545,6 +1572,7 @@ static void handle_tag(char *tagname)
// If the user doesn't want to edit the file then we quit here.
if (swap_exists_did_quit) {
+ ui_call_error_exit(1);
getout(1);
}
}
@@ -1583,7 +1611,7 @@ static void open_script_files(mparm_T *parmp)
scriptin[0] = file_open_new(&error, parmp->scriptin,
kFileReadOnly|kFileNonBlocking, 0);
if (scriptin[0] == NULL) {
- vim_snprintf((char *)IObuff, IOSIZE,
+ vim_snprintf(IObuff, IOSIZE,
_("Cannot open for reading: \"%s\": %s\n"),
parmp->scriptin, os_strerror(error));
os_errmsg(IObuff);
@@ -1608,9 +1636,6 @@ static void open_script_files(mparm_T *parmp)
// Also does recovery if "recoverymode" set.
static void create_windows(mparm_T *parmp)
{
- int dorewind;
- int done = 0;
-
// Create the number of windows that was requested.
if (parmp->window_count == -1) { // was not set
parmp->window_count = 1;
@@ -1646,6 +1671,7 @@ static void create_windows(mparm_T *parmp)
}
do_modelines(0); // do modelines
} else {
+ int done = 0;
// Open a buffer for windows that don't have one yet.
// Commands in the vimrc might have loaded a file or split the window.
// Watch out for autocommands that delete a window.
@@ -1653,7 +1679,7 @@ static void create_windows(mparm_T *parmp)
// Don't execute Win/Buf Enter/Leave autocommands here
autocmd_no_enter++;
autocmd_no_leave++;
- dorewind = true;
+ int dorewind = true;
while (done++ < 1000) {
if (dorewind) {
if (parmp->window_layout == WIN_TABS) {
@@ -1690,6 +1716,7 @@ static void create_windows(mparm_T *parmp)
if (got_int || only_one_window()) {
// abort selected or quit and only one window
did_emsg = false; // avoid hit-enter prompt
+ ui_call_error_exit(1);
getout(1);
}
// We can't close the window, it would disturb what
@@ -1725,7 +1752,6 @@ static void create_windows(mparm_T *parmp)
static void edit_buffers(mparm_T *parmp, char *cwd)
{
int arg_idx; // index in argument list
- int i;
bool advance = true;
win_T *win;
char *p_shm_save = NULL;
@@ -1741,7 +1767,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
}
arg_idx = 1;
- for (i = 1; i < parmp->window_count; i++) {
+ for (int i = 1; i < parmp->window_count; i++) {
if (cwd != NULL) {
os_chdir(cwd);
}
@@ -1767,7 +1793,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
p_shm_save = xstrdup(p_shm);
snprintf(buf, sizeof(buf), "F%s", p_shm);
- set_option_value_give_err("shm", 0L, buf, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(buf), 0);
}
} else {
if (curwin->w_next == NULL) { // just checking
@@ -1793,6 +1819,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
if (got_int || only_one_window()) {
// abort selected and only one window
did_emsg = false; // avoid hit-enter prompt
+ ui_call_error_exit(1);
getout(1);
}
win_close(curwin, true, false);
@@ -1811,7 +1838,7 @@ static void edit_buffers(mparm_T *parmp, char *cwd)
}
if (p_shm_save != NULL) {
- set_option_value_give_err("shm", 0L, p_shm_save, 0);
+ set_option_value_give_err("shm", CSTR_AS_OPTVAL(p_shm_save), 0);
xfree(p_shm_save);
}
@@ -1844,7 +1871,6 @@ static void exe_pre_commands(mparm_T *parmp)
{
char **cmds = parmp->pre_commands;
int cnt = parmp->n_pre_commands;
- int i;
if (cnt <= 0) {
return;
@@ -1853,7 +1879,7 @@ static void exe_pre_commands(mparm_T *parmp)
curwin->w_cursor.lnum = 0; // just in case..
estack_push(ETYPE_ARGS, _("pre-vimrc command line"), 0);
current_sctx.sc_sid = SID_CMDARG;
- for (i = 0; i < cnt; i++) {
+ for (int i = 0; i < cnt; i++) {
do_cmdline_cmd(cmds[i]);
}
estack_pop();
@@ -1864,8 +1890,6 @@ static void exe_pre_commands(mparm_T *parmp)
// Execute "+", "-c" and "-S" arguments.
static void exe_commands(mparm_T *parmp)
{
- int i;
-
// We start commands on line 0, make "vim +/pat file" match a
// pattern on line 1. But don't move the cursor when an autocommand
// with g`" was used.
@@ -1876,7 +1900,7 @@ static void exe_commands(mparm_T *parmp)
estack_push(ETYPE_ARGS, "command line", 0);
current_sctx.sc_sid = SID_CARG;
current_sctx.sc_seq = 0;
- for (i = 0; i < parmp->n_commands; i++) {
+ for (int i = 0; i < parmp->n_commands; i++) {
do_cmdline_cmd(parmp->commands[i]);
if (parmp->cmds_tofree[i]) {
xfree(parmp->commands[i]);
@@ -1928,7 +1952,7 @@ static void do_system_initialization(void)
dir_len += 1;
}
memcpy(vimrc + dir_len, path_tail, sizeof(path_tail));
- if (do_source(vimrc, false, DOSO_NONE) != FAIL) {
+ if (do_source(vimrc, false, DOSO_NONE, NULL) != FAIL) {
xfree(vimrc);
xfree(config_dirs);
return;
@@ -1940,7 +1964,7 @@ static void do_system_initialization(void)
#ifdef SYS_VIMRC_FILE
// Get system wide defaults, if the file name is defined.
- (void)do_source(SYS_VIMRC_FILE, false, DOSO_NONE);
+ (void)do_source(SYS_VIMRC_FILE, false, DOSO_NONE, NULL);
#endif
}
@@ -1969,7 +1993,7 @@ static bool do_user_initialization(void)
// init.lua
if (os_path_exists(init_lua_path)
- && do_source(init_lua_path, true, DOSO_VIMRC)) {
+ && do_source(init_lua_path, true, DOSO_VIMRC, NULL)) {
if (os_path_exists(user_vimrc)) {
semsg(_("E5422: Conflicting configs: \"%s\" \"%s\""), init_lua_path,
user_vimrc);
@@ -1983,7 +2007,7 @@ static bool do_user_initialization(void)
xfree(init_lua_path);
// init.vim
- if (do_source(user_vimrc, true, DOSO_VIMRC) != FAIL) {
+ if (do_source(user_vimrc, true, DOSO_VIMRC, NULL) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare(VIMRC_FILE, user_vimrc, false, true) != kEqualFiles);
@@ -2009,7 +2033,7 @@ static bool do_user_initialization(void)
memmove(vimrc, dir, dir_len);
vimrc[dir_len] = PATHSEP;
memmove(vimrc + dir_len + 1, path_tail, sizeof(path_tail));
- if (do_source(vimrc, true, DOSO_VIMRC) != FAIL) {
+ if (do_source(vimrc, true, DOSO_VIMRC, NULL) != FAIL) {
do_exrc = p_exrc;
if (do_exrc) {
do_exrc = (path_full_compare(VIMRC_FILE, vimrc, false, true) != kEqualFiles);
@@ -2075,7 +2099,7 @@ static void source_startup_scripts(const mparm_T *const parmp)
|| strequal(parmp->use_vimrc, "NORC")) {
// Do nothing.
} else {
- if (do_source(parmp->use_vimrc, false, DOSO_NONE) != OK) {
+ if (do_source(parmp->use_vimrc, false, DOSO_NONE, NULL) != OK) {
semsg(_("E282: Cannot read from \"%s\""), parmp->use_vimrc);
}
}
@@ -2108,7 +2132,7 @@ static int execute_env(char *env)
current_sctx.sc_sid = SID_ENV;
current_sctx.sc_seq = 0;
current_sctx.sc_lnum = 0;
- do_cmdline_cmd((char *)initstr);
+ do_cmdline_cmd(initstr);
estack_pop();
current_sctx = save_current_sctx;
@@ -2139,7 +2163,7 @@ static void print_mainerr(const char *errstr, const char *str)
os_errmsg(_(errstr));
if (str != NULL) {
os_errmsg(": \"");
- os_errmsg((char *)str);
+ os_errmsg(str);
os_errmsg("\"");
}
os_errmsg(_("\nMore info with \""));
@@ -2164,43 +2188,33 @@ static void usage(void)
signal_stop(); // kill us with CTRL-C here, if you like
os_msg(_("Usage:\n"));
- os_msg(_(" nvim [options] [file ...] Edit file(s)\n"));
- os_msg(_(" nvim [options] -t <tag> Edit file where tag is defined\n"));
- os_msg(_(" nvim [options] -q [errorfile] Edit file with first error\n"));
+ os_msg(_(" nvim [options] [file ...]\n"));
os_msg(_("\nOptions:\n"));
- os_msg(_(" -- Only file names after this\n"));
- os_msg(_(" + Start at end of file\n"));
os_msg(_(" --cmd <cmd> Execute <cmd> before any config\n"));
os_msg(_(" +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"));
os_msg(_(" -l <script> [args...] Execute Lua <script> (with optional args)\n"));
+ os_msg(_(" -S <session> Source <session> after loading the first file\n"));
+ os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
+ os_msg(_(" -u <config> Use this config file\n"));
os_msg("\n");
- os_msg(_(" -b Binary mode\n"));
os_msg(_(" -d Diff mode\n"));
- os_msg(_(" -e, -E Ex mode\n"));
os_msg(_(" -es, -Es Silent (batch) mode\n"));
os_msg(_(" -h, --help Print this help message\n"));
os_msg(_(" -i <shada> Use this shada file\n"));
- os_msg(_(" -m Modifications (writing files) not allowed\n"));
- os_msg(_(" -M Modifications in text not allowed\n"));
os_msg(_(" -n No swap file, use memory only\n"));
os_msg(_(" -o[N] Open N windows (default: one per file)\n"));
os_msg(_(" -O[N] Open N vertical windows (default: one per file)\n"));
os_msg(_(" -p[N] Open N tab pages (default: one per file)\n"));
- os_msg(_(" -r, -L List swap files\n"));
- os_msg(_(" -r <file> Recover edit state for this file\n"));
- os_msg(_(" -R Read-only mode\n"));
- os_msg(_(" -S <session> Source <session> after loading the first file\n"));
- os_msg(_(" -s <scriptin> Read Normal mode commands from <scriptin>\n"));
- os_msg(_(" -u <config> Use this config file\n"));
+ os_msg(_(" -R Read-only (view) mode\n"));
os_msg(_(" -v, --version Print version information\n"));
os_msg(_(" -V[N][file] Verbose [level][file]\n"));
os_msg("\n");
+ os_msg(_(" -- Only file names after this\n"));
os_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n"));
os_msg(_(" --clean \"Factory defaults\" (skip user config and plugins, shada)\n"));
os_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n"));
os_msg(_(" --headless Don't start a user interface\n"));
os_msg(_(" --listen <address> Serve RPC API from this address\n"));
- os_msg(_(" --noplugin Don't load plugins\n"));
os_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
os_msg(_(" --server <address> Specify RPC server to send commands to\n"));
os_msg(_(" --startuptime <file> Write startup timing messages to <file>\n"));
@@ -2213,7 +2227,20 @@ static void usage(void)
static void check_swap_exists_action(void)
{
if (swap_exists_action == SEA_QUIT) {
+ ui_call_error_exit(1);
getout(1);
}
handle_swap_exists(NULL);
}
+
+#ifdef ENABLE_ASAN_UBSAN
+const char *__ubsan_default_options(void)
+{
+ return "print_stacktrace=1";
+}
+
+const char *__asan_default_options(void)
+{
+ return "handle_abort=1,handle_sigill=1";
+}
+#endif
diff --git a/src/nvim/main.h b/src/nvim/main.h
index 2d54837872..6aeb62712a 100644
--- a/src/nvim/main.h
+++ b/src/nvim/main.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MAIN_H
-#define NVIM_MAIN_H
+#pragma once
#include <stdbool.h>
@@ -51,4 +50,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "main.h.generated.h"
#endif
-#endif // NVIM_MAIN_H
diff --git a/src/nvim/map.c b/src/nvim/map.c
index 191a459863..be6bf58daa 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -1,197 +1,193 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
+// map.c: Hash maps and sets
//
-// map.c: khash.h wrapper
+// parts of the implementation derived from khash.h as part of klib (MIT license)
//
// NOTE: Callers must manage memory (allocate) for keys and values.
-// khash.h does not make its own copy of the key or value.
-//
+// Map and Set does not make its own copy of the key or value.
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include "auto/config.h"
-#include "klib/khash.h"
-#include "nvim/gettext.h"
-#include "nvim/map.h"
#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#define cstr_t_hash kh_str_hash_func
-#define cstr_t_eq kh_str_hash_equal
-#define uint64_t_hash kh_int64_hash_func
-#define uint64_t_eq kh_int64_hash_equal
-#define uint32_t_hash kh_int_hash_func
-#define uint32_t_eq kh_int_hash_equal
-#define int_hash kh_int_hash_func
-#define int_eq kh_int_hash_equal
-#define handle_T_hash kh_int_hash_func
-#define handle_T_eq kh_int_hash_equal
-#define KittyKey_hash kh_int_hash_func
-#define KittyKey_eq kh_int_hash_equal
+#define equal_simple(x, y) ((x) == (y))
+
+#define hash_uint64_t(key) (uint32_t)((key) >> 33^(key)^(key) << 11)
+#define equal_uint64_t equal_simple
+#define hash_uint32_t(x) (x)
+#define equal_uint32_t equal_simple
+#define hash_int(x) hash_uint32_t((uint32_t)(x))
+#define equal_int equal_simple
+#define hash_int64_t(key) hash_uint64_t((uint64_t)key)
+#define equal_int64_t equal_simple
#if defined(ARCH_64)
-# define ptr_t_hash(key) uint64_t_hash((uint64_t)(key))
-# define ptr_t_eq(a, b) uint64_t_eq((uint64_t)(a), (uint64_t)(b))
+# define hash_ptr_t(key) hash_uint64_t((uint64_t)(key))
+# define equal_ptr_t(a, b) equal_uint64_t((uint64_t)(a), (uint64_t)(b))
#elif defined(ARCH_32)
-# define ptr_t_hash(key) uint32_t_hash((uint32_t)(key))
-# define ptr_t_eq(a, b) uint32_t_eq((uint32_t)(a), (uint32_t)(b))
+# define hash_ptr_t(key) hash_uint32_t((uint32_t)(key))
+# define equal_ptr_t(a, b) equal_uint32_t((uint32_t)(a), (uint32_t)(b))
#endif
-#define INITIALIZER(T, U) T##_##U##_initializer
-#define INITIALIZER_DECLARE(T, U, ...) const U INITIALIZER(T, U) = __VA_ARGS__
-#define DEFAULT_INITIALIZER { 0 }
-#define SSIZE_INITIALIZER { -1 }
-
-#define MAP_IMPL(T, U, ...) \
- INITIALIZER_DECLARE(T, U, __VA_ARGS__); \
- __KHASH_IMPL(T##_##U##_map, , T, U, 1, T##_hash, T##_eq) \
- void map_##T##_##U##_destroy(Map(T, U) *map) \
- { \
- kh_dealloc(T##_##U##_map, &map->table); \
- } \
- U map_##T##_##U##_get(Map(T, U) *map, T key) \
- { \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) == kh_end(&map->table)) { \
- return INITIALIZER(T, U); \
- } \
- return kh_val(&map->table, k); \
- } \
- bool map_##T##_##U##_has(Map(T, U) *map, T key) \
- { \
- return kh_get(T##_##U##_map, &map->table, key) != kh_end(&map->table); \
- } \
- T map_##T##_##U##_key(Map(T, U) *map, T key) \
- { \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) == kh_end(&map->table)) { \
- abort(); /* Caller must check map_has(). */ \
- } \
- return kh_key(&map->table, k); \
- } \
- U map_##T##_##U##_put(Map(T, U) *map, T key, U value) \
- { \
- int ret; \
- U rv = INITIALIZER(T, U); \
- khiter_t k = kh_put(T##_##U##_map, &map->table, key, &ret); \
- if (!ret) { \
- rv = kh_val(&map->table, k); \
- } \
- kh_val(&map->table, k) = value; \
- return rv; \
- } \
- U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put) \
- { \
- int ret; \
- khiter_t k; \
- if (put) { \
- k = kh_put(T##_##U##_map, &map->table, key, &ret); \
- if (ret) { \
- kh_val(&map->table, k) = INITIALIZER(T, U); \
- } \
- } else { \
- k = kh_get(T##_##U##_map, &map->table, key); \
- if (k == kh_end(&map->table)) { \
- return NULL; \
- } \
- } \
- return &kh_val(&map->table, k); \
- } \
- U map_##T##_##U##_del(Map(T, U) *map, T key) \
- { \
- U rv = INITIALIZER(T, U); \
- khiter_t k; \
- if ((k = kh_get(T##_##U##_map, &map->table, key)) != kh_end(&map->table)) { \
- rv = kh_val(&map->table, k); \
- kh_del(T##_##U##_map, &map->table, k); \
- } \
- return rv; \
- } \
- void map_##T##_##U##_clear(Map(T, U) *map) \
- { \
- kh_clear(T##_##U##_map, &map->table); \
- }
-
-static inline khint_t String_hash(String s)
+static inline uint32_t hash_cstr_t(const char *s)
{
- khint_t h = 0;
- for (size_t i = 0; i < s.size && s.data[i]; i++) {
- h = (h << 5) - h + (uint8_t)s.data[i];
+ uint32_t h = 0;
+ for (size_t i = 0; s[i]; i++) {
+ h = (h << 5) - h + (uint8_t)s[i];
}
return h;
}
-static inline bool String_eq(String a, String b)
-{
- if (a.size != b.size) {
- return false;
- }
- return memcmp(a.data, b.data, a.size) == 0;
-}
+#define equal_cstr_t strequal
-static inline khint_t HlEntry_hash(HlEntry ae)
+static inline uint32_t hash_HlEntry(HlEntry ae)
{
const uint8_t *data = (const uint8_t *)&ae;
- khint_t h = 0;
+ uint32_t h = 0;
for (size_t i = 0; i < sizeof(ae); i++) {
h = (h << 5) - h + data[i];
}
return h;
}
-static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2)
+static inline bool equal_HlEntry(HlEntry ae1, HlEntry ae2)
{
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
-static inline khint_t ColorKey_hash(ColorKey ae)
+static inline uint32_t hash_ColorKey(ColorKey ae)
{
const uint8_t *data = (const uint8_t *)&ae;
- khint_t h = 0;
+ uint32_t h = 0;
for (size_t i = 0; i < sizeof(ae); i++) {
h = (h << 5) - h + data[i];
}
return h;
}
-static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2)
+static inline bool equal_ColorKey(ColorKey ae1, ColorKey ae2)
{
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
-MAP_IMPL(int, int, DEFAULT_INITIALIZER)
-MAP_IMPL(int, cstr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(cstr_t, int, DEFAULT_INITIALIZER)
-MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint32_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
-MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
-MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER)
-MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
-MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
-MAP_IMPL(String, handle_T, 0)
-MAP_IMPL(String, int, DEFAULT_INITIALIZER)
-MAP_IMPL(int, String, DEFAULT_INITIALIZER)
-
-MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
-
-MAP_IMPL(KittyKey, cstr_t, DEFAULT_INITIALIZER)
+// TODO(bfredl): this could be _less_ for the h->hash part as this is now small (4 bytes per value)
+#define UPPER_FILL 0.77
+
+#define roundup32(x) (--(x), (x) |= (x)>>1, (x) |= (x)>>2, (x) |= (x)>>4, (x) |= (x)>>8, \
+ (x) |= (x)>>16, ++(x))
+
+// h->hash must either be NULL or an already valid pointer
+void mh_realloc(MapHash *h, uint32_t n_min_buckets)
+{
+ xfree(h->hash);
+ uint32_t n_buckets = n_min_buckets < 16 ? 16 : n_min_buckets;
+ roundup32(n_buckets);
+ // sets all buckets to EMPTY
+ h->hash = xcalloc(n_buckets, sizeof *h->hash);
+ h->n_occupied = h->size = 0;
+ h->n_buckets = n_buckets;
+ h->upper_bound = (uint32_t)(h->n_buckets * UPPER_FILL + 0.5);
+}
+
+void mh_clear(MapHash *h)
+{
+ if (h->hash) {
+ memset(h->hash, 0, h->n_buckets * sizeof(*h->hash));
+ h->size = h->n_occupied = 0;
+ h->n_keys = 0;
+ }
+}
+
+#define KEY_NAME(x) x##int
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, String)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##ptr_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##cstr_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##String
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, int)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##uint32_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, uint32_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##uint64_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, ssize_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, uint64_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##int64_t
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ptr_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#define VAL_NAME(x) quasiquote(x, int64_t)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##HlEntry
+#include "nvim/map_key_impl.c.h"
+#undef KEY_NAME
+
+#define KEY_NAME(x) x##ColorKey
+#include "nvim/map_key_impl.c.h"
+#define VAL_NAME(x) quasiquote(x, ColorItem)
+#include "nvim/map_value_impl.c.h"
+#undef VAL_NAME
+#undef KEY_NAME
/// Deletes a key:value pair from a string:pointer map, and frees the
/// storage of both key and value.
///
void pmap_del2(PMap(cstr_t) *map, const char *key)
{
- if (pmap_has(cstr_t)(map, key)) {
- void *k = (void *)pmap_key(cstr_t)(map, key);
- void *v = pmap_get(cstr_t)(map, key);
- pmap_del(cstr_t)(map, key);
- xfree(k);
- xfree(v);
- }
+ cstr_t key_alloc = NULL;
+ ptr_t val = pmap_del(cstr_t)(map, key, &key_alloc);
+ xfree((void *)key_alloc);
+ xfree(val);
}
diff --git a/src/nvim/map.h b/src/nvim/map.h
deleted file mode 100644
index 92f0b32255..0000000000
--- a/src/nvim/map.h
+++ /dev/null
@@ -1,96 +0,0 @@
-#ifndef NVIM_MAP_H
-#define NVIM_MAP_H
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-
-#include "klib/khash.h"
-#include "nvim/api/private/defs.h"
-#include "nvim/extmark_defs.h"
-#include "nvim/gettext.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/map_defs.h"
-#include "nvim/tui/input_defs.h"
-#include "nvim/types.h"
-#include "nvim/ui_client.h"
-
-#if defined(__NetBSD__)
-# undef uint64_t
-# define uint64_t uint64_t
-#endif
-
-#define MAP_DECLS(T, U) \
- KHASH_DECLARE(T##_##U##_map, T, U) \
- typedef struct { \
- khash_t(T##_##U##_map) table; \
- } Map(T, U); \
- Map(T, U) *map_##T##_##U##_new(void); \
- void map_##T##_##U##_free(Map(T, U) *map); \
- void map_##T##_##U##_destroy(Map(T, U) *map); \
- U map_##T##_##U##_get(Map(T, U) *map, T key); \
- bool map_##T##_##U##_has(Map(T, U) *map, T key); \
- T map_##T##_##U##_key(Map(T, U) *map, T key); \
- U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
- U *map_##T##_##U##_ref(Map(T, U) *map, T key, bool put); \
- U map_##T##_##U##_del(Map(T, U) *map, T key); \
- void map_##T##_##U##_clear(Map(T, U) *map);
-
-//
-// NOTE: Keys AND values must be allocated! khash.h does not make a copy.
-//
-MAP_DECLS(int, int)
-MAP_DECLS(int, cstr_t)
-MAP_DECLS(cstr_t, ptr_t)
-MAP_DECLS(cstr_t, int)
-MAP_DECLS(ptr_t, ptr_t)
-MAP_DECLS(uint32_t, ptr_t)
-MAP_DECLS(uint64_t, ptr_t)
-MAP_DECLS(uint64_t, ssize_t)
-MAP_DECLS(uint64_t, uint64_t)
-MAP_DECLS(uint32_t, uint32_t)
-
-MAP_DECLS(handle_T, ptr_t)
-MAP_DECLS(HlEntry, int)
-MAP_DECLS(String, handle_T)
-MAP_DECLS(String, int)
-MAP_DECLS(int, String)
-
-MAP_DECLS(ColorKey, ColorItem)
-
-MAP_DECLS(KittyKey, cstr_t)
-
-#define MAP_INIT { { 0, 0, 0, 0, NULL, NULL, NULL } }
-#define map_init(k, v, map) do { *(map) = (Map(k, v)) MAP_INIT; } while (false)
-
-#define map_destroy(T, U) map_##T##_##U##_destroy
-#define map_get(T, U) map_##T##_##U##_get
-#define map_has(T, U) map_##T##_##U##_has
-#define map_key(T, U) map_##T##_##U##_key
-#define map_put(T, U) map_##T##_##U##_put
-#define map_ref(T, U) map_##T##_##U##_ref
-#define map_del(T, U) map_##T##_##U##_del
-#define map_clear(T, U) map_##T##_##U##_clear
-
-#define map_size(map) ((map)->table.size)
-
-#define pmap_destroy(T) map_destroy(T, ptr_t)
-#define pmap_get(T) map_get(T, ptr_t)
-#define pmap_has(T) map_has(T, ptr_t)
-#define pmap_key(T) map_key(T, ptr_t)
-#define pmap_put(T) map_put(T, ptr_t)
-#define pmap_ref(T) map_ref(T, ptr_t)
-/// @see pmap_del2
-#define pmap_del(T) map_del(T, ptr_t)
-#define pmap_clear(T) map_clear(T, ptr_t)
-#define pmap_init(k, map) map_init(k, ptr_t, map)
-
-#define map_foreach(map, key, value, block) \
- kh_foreach(&(map)->table, key, value, block)
-
-#define map_foreach_value(map, value, block) \
- kh_foreach_value(&(map)->table, value, block)
-
-void pmap_del2(PMap(cstr_t) *map, const char *key);
-
-#endif // NVIM_MAP_H
diff --git a/src/nvim/map_defs.h b/src/nvim/map_defs.h
index 61afedbe50..147c03327a 100644
--- a/src/nvim/map_defs.h
+++ b/src/nvim/map_defs.h
@@ -1,12 +1,226 @@
-#ifndef NVIM_MAP_DEFS_H
-#define NVIM_MAP_DEFS_H
+#pragma once
-#include "klib/khash.h"
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/assert_defs.h"
+#include "nvim/highlight_defs.h"
+#include "nvim/types_defs.h"
+
+#if defined(__NetBSD__)
+# undef uint64_t
+# define uint64_t uint64_t
+#endif
typedef const char *cstr_t;
typedef void *ptr_t;
-#define Map(T, U) Map_##T##_##U
+// when used as a key, String doesn't need to be NUL terminated,
+// and can also contain embedded NUL:s as part of the data.
+static inline uint32_t hash_String(String s)
+{
+ uint32_t h = 0;
+ for (size_t i = 0; i < s.size; i++) {
+ h = (h << 5) - h + (uint8_t)s.data[i];
+ }
+ return h;
+}
+
+static inline bool equal_String(String a, String b)
+{
+ if (a.size != b.size) {
+ return false;
+ }
+ return memcmp(a.data, b.data, a.size) == 0;
+}
+
+#define Set(type) Set_##type
+#define Map(T, U) Map_##T##U
#define PMap(T) Map(T, ptr_t)
-#endif // NVIM_MAP_DEFS_H
+static const int value_init_int = 0;
+static const ptr_t value_init_ptr_t = NULL;
+static const ssize_t value_init_ssize_t = -1;
+static const uint32_t value_init_uint32_t = 0;
+static const uint64_t value_init_uint64_t = 0;
+static const int64_t value_init_int64_t = 0;
+static const String value_init_String = STRING_INIT;
+static const ColorItem value_init_ColorItem = COLOR_ITEM_INITIALIZER;
+
+// layer 0: type non-specific code
+
+typedef struct {
+ uint32_t n_buckets;
+ uint32_t size;
+ uint32_t n_occupied;
+ uint32_t upper_bound;
+ uint32_t n_keys; // this is almost always "size", but keys[] could contain ded items..
+ uint32_t keys_capacity;
+ uint32_t *hash;
+} MapHash;
+
+#define MAPHASH_INIT { 0, 0, 0, 0, 0, 0, NULL }
+#define SET_INIT { MAPHASH_INIT, NULL }
+#define MAP_INIT { SET_INIT, NULL }
+
+#define MH_TOMBSTONE UINT32_MAX
+
+#define mh_is_empty(h, i) ((h)->hash[i] == 0)
+#define mh_is_del(h, i) ((h)->hash[i] == MH_TOMBSTONE)
+#define mh_is_either(h, i) ((uint32_t)((h)->hash[i] + 1U) <= 1U)
+
+typedef enum {
+ kMHExisting = 0,
+ kMHNewKeyDidFit,
+ kMHNewKeyRealloc,
+} MHPutStatus;
+
+void mh_clear(MapHash *h);
+void mh_realloc(MapHash *h, uint32_t n_min_buckets);
+
+// layer 1: key type specific defs
+// This is all need for sets.
+
+#define MH_DECLS(T, K, K_query) \
+ typedef struct { \
+ MapHash h; \
+ K *keys; \
+ } Set(T); \
+ \
+ uint32_t mh_find_bucket_##T(Set(T) *set, K_query key, bool put); \
+ uint32_t mh_get_##T(Set(T) *set, K_query key); \
+ void mh_rehash_##T(Set(T) *set); \
+ uint32_t mh_put_##T(Set(T) *set, K_query key, MHPutStatus *new); \
+
+#define KEY_DECLS(T) \
+ MH_DECLS(T, T, T) \
+ uint32_t mh_delete_##T(Set(T) *set, T *key); \
+ static inline bool set_put_##T(Set(T) *set, T key, T **key_alloc) { \
+ MHPutStatus status; \
+ uint32_t k = mh_put_##T(set, key, &status); \
+ if (key_alloc) { \
+ *key_alloc = &set->keys[k]; \
+ } \
+ return status != kMHExisting; \
+ } \
+ static inline T set_del_##T(Set(T) *set, T key) \
+ { \
+ mh_delete_##T(set, &key); \
+ return key; \
+ } \
+ static inline bool set_has_##T(Set(T) *set, T key) { \
+ return mh_get_##T(set, key) != MH_TOMBSTONE; \
+ } \
+
+// layer 2: key+value specific defs
+// now we finally get Maps
+
+#define MAP_DECLS(T, U) \
+ typedef struct { \
+ Set(T) set; \
+ U *values; \
+ } Map(T, U); \
+ static inline U map_get_##T##U(Map(T, U) *map, T key) \
+ { \
+ uint32_t k = mh_get_##T(&map->set, key); \
+ return k == MH_TOMBSTONE ? value_init_##U : map->values[k]; \
+ } \
+ U *map_ref_##T##U(Map(T, U) *map, T key, T **key_alloc); \
+ U *map_put_ref_##T##U(Map(T, U) *map, T key, T **key_alloc, bool *new_item); \
+ static inline void map_put_##T##U(Map(T, U) *map, T key, U value) \
+ { \
+ U *val = map_put_ref_##T##U(map, key, NULL, NULL); \
+ *val = value; \
+ } \
+ U map_del_##T##U(Map(T, U) *map, T key, T *key_alloc); \
+
+// NOTE: Keys AND values must be allocated! Map and Set does not make a copy.
+
+#define quasiquote(x, y) x##y
+
+MH_DECLS(glyph, char, String)
+KEY_DECLS(int)
+KEY_DECLS(cstr_t)
+KEY_DECLS(ptr_t)
+KEY_DECLS(uint64_t)
+KEY_DECLS(int64_t)
+KEY_DECLS(uint32_t)
+KEY_DECLS(String)
+KEY_DECLS(HlEntry)
+KEY_DECLS(ColorKey)
+
+MAP_DECLS(int, int)
+MAP_DECLS(int, ptr_t)
+MAP_DECLS(cstr_t, ptr_t)
+MAP_DECLS(cstr_t, int)
+MAP_DECLS(ptr_t, ptr_t)
+MAP_DECLS(uint32_t, ptr_t)
+MAP_DECLS(uint64_t, ptr_t)
+MAP_DECLS(uint64_t, ssize_t)
+MAP_DECLS(uint64_t, uint64_t)
+MAP_DECLS(int64_t, int64_t)
+MAP_DECLS(int64_t, ptr_t)
+MAP_DECLS(uint32_t, uint32_t)
+MAP_DECLS(String, int)
+MAP_DECLS(int, String)
+MAP_DECLS(ColorKey, ColorItem)
+
+#define set_has(T, set, key) set_has_##T(set, key)
+#define set_put(T, set, key) set_put_##T(set, key, NULL)
+#define set_put_ref(T, set, key, key_alloc) set_put_##T(set, key, key_alloc)
+#define set_put_idx(T, set, key, status) mh_put_##T(set, key, status)
+#define set_del(T, set, key) set_del_##T(set, key)
+#define set_destroy(T, set) (xfree((set)->keys), xfree((set)->h.hash))
+#define set_clear(T, set) mh_clear(&(set)->h)
+#define set_size(set) ((set)->h.size)
+
+#define map_get(T, U) map_get_##T##U
+#define map_has(T, map, key) set_has(T, &(map)->set, key)
+#define map_put(T, U) map_put_##T##U
+#define map_ref(T, U) map_ref_##T##U
+#define map_put_ref(T, U) map_put_ref_##T##U
+#define map_del(T, U) map_del_##T##U
+#define map_destroy(T, map) (set_destroy(T, &(map)->set), xfree((map)->values))
+#define map_clear(T, map) set_clear(T, &(map)->set)
+#define map_size(map) set_size(&(map)->set)
+
+#define pmap_get(T) map_get(T, ptr_t)
+#define pmap_put(T) map_put(T, ptr_t)
+#define pmap_ref(T) map_ref(T, ptr_t)
+#define pmap_put_ref(T) map_put_ref(T, ptr_t)
+/// @see pmap_del2
+#define pmap_del(T) map_del(T, ptr_t)
+
+#define set_foreach(set, key, block) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (set)->h.n_keys; __i++) { \
+ (key) = (set)->keys[__i]; \
+ block; \
+ } \
+ }
+
+#define map_foreach_key(map, key, block) set_foreach(&(map)->set, key, block)
+
+#define map_foreach(map, key, value, code) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (map)->set.h.n_keys; __i++) { \
+ (key) = (map)->set.keys[__i]; \
+ (value) = (map)->values[__i]; \
+ code; \
+ } \
+ }
+
+#define map_foreach_value(map, value, code) \
+ { \
+ uint32_t __i; \
+ for (__i = 0; __i < (map)->set.h.n_keys; __i++) { \
+ (value) = (map)->values[__i]; \
+ code; \
+ } \
+ }
+
+void pmap_del2(PMap(cstr_t) *map, const char *key);
diff --git a/src/nvim/map_glyph_cache.c b/src/nvim/map_glyph_cache.c
new file mode 100644
index 0000000000..5efa87b960
--- /dev/null
+++ b/src/nvim/map_glyph_cache.c
@@ -0,0 +1,109 @@
+// Specialized version of Set() where interned strings is stored in a compact,
+// NUL-separated char array.
+// `String key` lookup keys don't need to be NULL terminated, but they
+// must not contain embedded NUL:s. When reading a key from set->keys, they
+// are always NUL terminated, though. Thus, it is enough to store an index into
+// this array, and use strlen(), to retrieve an interned key.
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
+
+uint32_t mh_find_bucket_glyph(Set(glyph) *set, String key, bool put)
+{
+ MapHash *h = &set->h;
+ uint32_t step = 0;
+ uint32_t mask = h->n_buckets - 1;
+ uint32_t k = hash_String(key);
+ uint32_t i = k & mask;
+ uint32_t last = i;
+ uint32_t site = put ? last : MH_TOMBSTONE;
+ while (!mh_is_empty(h, i)) {
+ if (mh_is_del(h, i)) {
+ if (site == last) {
+ site = i;
+ }
+ } else if (equal_String(cstr_as_string(&set->keys[h->hash[i] - 1]), key)) {
+ return i;
+ }
+ i = (i + (++step)) & mask;
+ if (i == last) {
+ abort();
+ }
+ }
+ if (site == last) {
+ site = i;
+ }
+ return site;
+}
+
+/// @return index into set->keys if found, MH_TOMBSTONE otherwise
+uint32_t mh_get_glyph(Set(glyph) *set, String key)
+{
+ if (set->h.n_buckets == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = mh_find_bucket_glyph(set, key, false);
+ return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
+}
+
+void mh_rehash_glyph(Set(glyph) *set)
+{
+ // assume the format of set->keys, i e NUL terminated strings
+ for (uint32_t k = 0; k < set->h.n_keys; k += (uint32_t)strlen(&set->keys[k]) + 1) {
+ uint32_t idx = mh_find_bucket_glyph(set, cstr_as_string(&set->keys[k]), true);
+ // there must be tombstones when we do a rehash
+ if (!mh_is_empty((&set->h), idx)) {
+ abort();
+ }
+ set->h.hash[idx] = k + 1;
+ }
+ set->h.n_occupied = set->h.size = set->h.n_keys;
+}
+
+uint32_t mh_put_glyph(Set(glyph) *set, String key, MHPutStatus *new)
+{
+ MapHash *h = &set->h;
+ // Might rehash ahead of time if "key" already existed. But it was
+ // going to happen soon anyway.
+ if (h->n_occupied >= h->upper_bound) {
+ mh_realloc(h, h->n_buckets + 1);
+ mh_rehash_glyph(set);
+ }
+
+ uint32_t idx = mh_find_bucket_glyph(set, key, true);
+
+ if (mh_is_either(h, idx)) {
+ h->size++;
+ h->n_occupied++;
+
+ uint32_t size = (uint32_t)key.size + 1; // NUL takes space
+ uint32_t pos = h->n_keys;
+ h->n_keys += size;
+ if (h->n_keys > h->keys_capacity) {
+ h->keys_capacity = MAX(h->keys_capacity * 2, 64);
+ set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(char));
+ *new = kMHNewKeyRealloc;
+ } else {
+ *new = kMHNewKeyDidFit;
+ }
+ memcpy(&set->keys[pos], key.data, key.size);
+ set->keys[pos + key.size] = NUL;
+ h->hash[idx] = pos + 1;
+ return pos;
+ } else {
+ *new = kMHExisting;
+ uint32_t pos = h->hash[idx] - 1;
+ assert(equal_String(cstr_as_string(&set->keys[pos]), key));
+ return pos;
+ }
+}
diff --git a/src/nvim/map_key_impl.c.h b/src/nvim/map_key_impl.c.h
new file mode 100644
index 0000000000..5568c049ab
--- /dev/null
+++ b/src/nvim/map_key_impl.c.h
@@ -0,0 +1,159 @@
+#include "nvim/map_defs.h"
+#include "nvim/memory.h"
+
+#ifndef KEY_NAME
+// Don't error out. it is nice to type-check the file in isolation, in clangd or otherwise
+# define KEY_NAME(x) x##int
+# define hash_int(x) ((uint32_t)x)
+# define equal_int(x, y) ((x) == (y))
+#endif
+
+#define SET_TYPE KEY_NAME(Set_)
+#define KEY_TYPE KEY_NAME()
+
+/// find bucket to get or put "key"
+///
+/// set->h.hash assumed already allocated!
+///
+/// @return bucket index, or MH_TOMBSTONE if not found and `put` was false
+/// mh_is_either(hash[rv]) : not found, but this is the place to put
+/// otherwise: hash[rv]-1 is index into key/value arrays
+uint32_t KEY_NAME(mh_find_bucket_)(SET_TYPE *set, KEY_TYPE key, bool put)
+{
+ MapHash *h = &set->h;
+ uint32_t step = 0;
+ uint32_t mask = h->n_buckets - 1;
+ uint32_t k = KEY_NAME(hash_)(key);
+ uint32_t i = k & mask;
+ uint32_t last = i;
+ uint32_t site = put ? last : MH_TOMBSTONE;
+ while (!mh_is_empty(h, i)) {
+ if (mh_is_del(h, i)) {
+ if (site == last) {
+ site = i;
+ }
+ } else if (KEY_NAME(equal_)(set->keys[h->hash[i] - 1], key)) {
+ return i;
+ }
+ i = (i + (++step)) & mask;
+ if (i == last) {
+ abort();
+ }
+ }
+ if (site == last) {
+ site = i;
+ }
+ return site;
+}
+
+/// @return index into set->keys if found, MH_TOMBSTONE otherwise
+uint32_t KEY_NAME(mh_get_)(SET_TYPE *set, KEY_TYPE key)
+{
+ if (set->h.n_buckets == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, key, false);
+ return (idx != MH_TOMBSTONE) ? set->h.hash[idx] - 1 : MH_TOMBSTONE;
+}
+
+/// Rebuild hash from keys[] array
+///
+/// set->h.hash must be allocated and empty before&alling!
+void KEY_NAME(mh_rehash_)(SET_TYPE *set)
+{
+ for (uint32_t k = 0; k < set->h.n_keys; k++) {
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, set->keys[k], true);
+ // there must be tombstones when we do a rehash
+ if (!mh_is_empty((&set->h), idx)) {
+ abort();
+ }
+ set->h.hash[idx] = k + 1;
+ }
+ set->h.n_occupied = set->h.size = set->h.n_keys;
+}
+
+/// Put a key. Return the existing item if found
+///
+/// Allocates/resizes the hash table and/or keys[] table if needed.
+///
+/// @param[out] new mandatory. Reveals if an existing key was found. In addition,
+/// if new item, indicates if keys[] was resized.
+///
+/// @return keys index
+uint32_t KEY_NAME(mh_put_)(SET_TYPE *set, KEY_TYPE key, MHPutStatus *new)
+{
+ MapHash *h = &set->h;
+ // Might rehash ahead of time if "key" already existed. But it was
+ // going to happen soon anyway.
+ if (h->n_occupied >= h->upper_bound) {
+ // If we likely were to resize soon, do it now to avoid extra rehash
+ // TODO(bfredl): we never shrink. but maybe that's fine
+ if (h->size >= h->upper_bound * 0.9) {
+ mh_realloc(h, h->n_buckets + 1);
+ } else {
+ // Just a lot of tombstones from deleted items, start all over again
+ memset(h->hash, 0, h->n_buckets * sizeof(*h->hash));
+ h->size = h->n_occupied = 0;
+ }
+ KEY_NAME(mh_rehash_)(set);
+ }
+
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, key, true);
+
+ if (mh_is_either(h, idx)) {
+ h->size++;
+ if (mh_is_empty(h, idx)) {
+ h->n_occupied++;
+ }
+
+ uint32_t pos = h->n_keys++;
+ if (pos >= h->keys_capacity) {
+ h->keys_capacity = MAX(h->keys_capacity * 2, 8);
+ set->keys = xrealloc(set->keys, h->keys_capacity * sizeof(KEY_TYPE));
+ *new = kMHNewKeyRealloc;
+ } else {
+ *new = kMHNewKeyDidFit;
+ }
+ set->keys[pos] = key;
+ h->hash[idx] = pos + 1;
+ return pos;
+ } else {
+ *new = kMHExisting;
+ uint32_t pos = h->hash[idx] - 1;
+ if (!KEY_NAME(equal_)(set->keys[pos], key)) {
+ abort();
+ }
+ return pos;
+ }
+}
+
+/// Deletes `*key` if found, do nothing otherwise
+///
+/// @param[in, out] key modified to the value contained in the set
+/// @return the index the item used to have in keys[]
+/// MH_TOMBSTONE if key was not found
+uint32_t KEY_NAME(mh_delete_)(SET_TYPE *set, KEY_TYPE *key)
+{
+ if (set->h.size == 0) {
+ return MH_TOMBSTONE;
+ }
+ uint32_t idx = KEY_NAME(mh_find_bucket_)(set, *key, false);
+ if (idx != MH_TOMBSTONE) {
+ uint32_t k = set->h.hash[idx] - 1;
+ set->h.hash[idx] = MH_TOMBSTONE;
+
+ uint32_t last = --set->h.n_keys;
+ *key = set->keys[k];
+ set->h.size--;
+ if (last != k) {
+ uint32_t idx2 = KEY_NAME(mh_find_bucket_)(set, set->keys[last], false);
+ if (set->h.hash[idx2] != last + 1) {
+ abort();
+ }
+ set->h.hash[idx2] = k + 1;
+ set->keys[k] = set->keys[last];
+ }
+ return k;
+ }
+ return MH_TOMBSTONE;
+}
diff --git a/src/nvim/map_value_impl.c.h b/src/nvim/map_value_impl.c.h
new file mode 100644
index 0000000000..d93856c25d
--- /dev/null
+++ b/src/nvim/map_value_impl.c.h
@@ -0,0 +1,64 @@
+#include "nvim/assert_defs.h"
+#include "nvim/map_defs.h"
+
+#if !defined(KEY_NAME) || !defined(VAL_NAME)
+// Don't error out. it is nice to type-check the file in isolation, in clangd or otherwise
+# define KEY_NAME(x) x##int
+# define VAL_NAME(x) quasiquote(x, ptr_t)
+#endif
+
+#define MAP_NAME(x) VAL_NAME(KEY_NAME(x))
+#define MAP_TYPE MAP_NAME(Map_)
+#define KEY_TYPE KEY_NAME()
+#define VALUE_TYPE VAL_NAME()
+#define INITIALIZER VAL_NAME(value_init_)
+
+VALUE_TYPE *MAP_NAME(map_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc)
+{
+ uint32_t k = KEY_NAME(mh_get_)(&map->set, key);
+ if (k == MH_TOMBSTONE) {
+ return NULL;
+ }
+ if (key_alloc) {
+ *key_alloc = &map->set.keys[k];
+ }
+ return &map->values[k];
+}
+
+VALUE_TYPE *MAP_NAME(map_put_ref_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE **key_alloc,
+ bool *new_item)
+{
+ MHPutStatus status;
+ uint32_t k = KEY_NAME(mh_put_)(&map->set, key, &status);
+ if (status != kMHExisting) {
+ if (status == kMHNewKeyRealloc) {
+ map->values = xrealloc(map->values, map->set.h.keys_capacity * sizeof(VALUE_TYPE));
+ }
+ map->values[k] = INITIALIZER;
+ }
+ if (new_item) {
+ *new_item = (status != kMHExisting);
+ }
+ if (key_alloc) {
+ *key_alloc = &map->set.keys[k];
+ }
+ return &map->values[k];
+}
+
+VALUE_TYPE MAP_NAME(map_del_)(MAP_TYPE *map, KEY_TYPE key, KEY_TYPE *key_alloc)
+{
+ VALUE_TYPE rv = INITIALIZER;
+ uint32_t k = KEY_NAME(mh_delete_)(&map->set, &key);
+ if (k == MH_TOMBSTONE) {
+ return rv;
+ }
+
+ if (key_alloc) {
+ *key_alloc = key;
+ }
+ rv = map->values[k];
+ if (k != map->set.h.n_keys) {
+ map->values[k] = map->values[map->set.h.n_keys];
+ }
+ return rv;
+}
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index 831d1299a8..17593a9121 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -1,48 +1,54 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// mapping.c: Code for mappings and abbreviations.
#include <assert.h>
-#include <inttypes.h>
#include <lauxlib.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_session.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
+#include "nvim/regexp_defs.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/ui.h"
+#include "nvim/vim_defs.h"
/// List used for abbreviations.
static mapblock_T *first_abbr = NULL; // first entry in abbrlist
@@ -61,10 +67,65 @@ static mapblock_T *(maphash[MAX_MAPHASH]) = { 0 };
(MODE_NORMAL | MODE_VISUAL | MODE_SELECT | \
MODE_OP_PENDING | MODE_TERMINAL)) ? (c1) : ((c1) ^ 0x80))
+/// All possible |:map-arguments| usable in a |:map| command.
+///
+/// The <special> argument has no effect on mappings and is excluded from this
+/// struct declaration. |:noremap| is included, since it behaves like a map
+/// argument when used in a mapping.
+///
+/// @see mapblock_T
+struct map_arguments {
+ bool buffer;
+ bool expr;
+ bool noremap;
+ bool nowait;
+ bool script;
+ bool silent;
+ bool unique;
+ bool replace_keycodes;
+
+ /// The {lhs} of the mapping.
+ ///
+ /// vim limits this to MAXMAPLEN characters, allowing us to use a static
+ /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
+ /// that {lhs} was too long and truncated.
+ char lhs[MAXMAPLEN + 1];
+ size_t lhs_len;
+
+ /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
+ char alt_lhs[MAXMAPLEN + 1];
+ size_t alt_lhs_len;
+
+ char *rhs; /// The {rhs} of the mapping.
+ size_t rhs_len;
+ LuaRef rhs_lua; /// lua function as {rhs}
+ bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
+
+ char *orig_rhs; /// The original text of the {rhs}.
+ size_t orig_rhs_len;
+ char *desc; /// map description
+};
+typedef struct map_arguments MapArguments;
+#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, false, \
+ { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mapping.c.generated.h"
#endif
+static const char e_global_abbreviation_already_exists_for_str[]
+ = N_("E224: Global abbreviation already exists for %s");
+static const char e_global_mapping_already_exists_for_str[]
+ = N_("E225: Global mapping already exists for %s");
+static const char e_abbreviation_already_exists_for_str[]
+ = N_("E226: Abbreviation already exists for %s");
+static const char e_mapping_already_exists_for_str[]
+ = N_("E227: Mapping already exists for %s");
+static const char e_entries_missing_in_mapset_dict_argument[]
+ = N_("E460: Entries missing in mapset() dict argument");
+static const char e_illegal_map_mode_string_str[]
+ = N_("E1276: Illegal map mode string: '%s'");
+
/// Get the start of the hashed map list for "state" and first character "c".
mapblock_T *get_maphash_list(int state, int c)
{
@@ -95,9 +156,7 @@ mapblock_T *get_maphash(int index, buf_T *buf)
/// "mpp" is a pointer to the m_next field of the PREVIOUS entry!
static void mapblock_free(mapblock_T **mpp)
{
- mapblock_T *mp;
-
- mp = *mpp;
+ mapblock_T *mp = *mpp;
xfree(mp->m_keys);
if (!mp->m_simplified) {
NLUA_CLEAR_REF(mp->m_luaref);
@@ -159,26 +218,23 @@ static char *map_mode_to_chars(int mode)
/// @param local true for buffer-local map
static void showmap(mapblock_T *mp, bool local)
{
- size_t len = 1;
-
if (message_filtered(mp->m_keys) && message_filtered(mp->m_str)
&& (mp->m_desc == NULL || message_filtered(mp->m_desc))) {
return;
}
- if (msg_didout || msg_silent != 0) {
+ // When ext_messages is active, msg_didout is never set.
+ if (msg_didout || msg_silent != 0 || ui_has(kUIMessages)) {
msg_putchar('\n');
if (got_int) { // 'q' typed at MORE prompt
return;
}
}
- {
- char *const mapchars = map_mode_to_chars(mp->m_mode);
- msg_puts(mapchars);
- len = strlen(mapchars);
- xfree(mapchars);
- }
+ char *const mapchars = map_mode_to_chars(mp->m_mode);
+ msg_puts(mapchars);
+ size_t len = strlen(mapchars);
+ xfree(mapchars);
while (++len <= 3) {
msg_putchar(' ');
@@ -268,16 +324,16 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
bool did_simplify = false;
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
char *bufarg = lhs_buf;
- char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags, &did_simplify,
- cpo_flags);
+ char *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0,
+ flags, &did_simplify, cpo_flags);
if (replaced == NULL) {
return false;
}
mapargs->lhs_len = strlen(replaced);
xstrlcpy(mapargs->lhs, replaced, sizeof(mapargs->lhs));
if (did_simplify) {
- replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, flags | REPTERM_NO_SIMPLIFY,
- NULL, cpo_flags);
+ replaced = replace_termcodes(orig_lhs, orig_lhs_len, &bufarg, 0,
+ flags | REPTERM_NO_SIMPLIFY, NULL, cpo_flags);
if (replaced == NULL) {
return false;
}
@@ -287,14 +343,15 @@ static bool set_maparg_lhs_rhs(const char *const orig_lhs, const size_t orig_lhs
mapargs->alt_lhs_len = 0;
}
- set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, cpo_flags, mapargs);
+ set_maparg_rhs(orig_rhs, orig_rhs_len, rhs_lua, 0, cpo_flags, mapargs);
return true;
}
/// @see set_maparg_lhs_rhs
static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len,
- const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs)
+ const LuaRef rhs_lua, const scid_T sid, const int cpo_flags,
+ MapArguments *const mapargs)
{
mapargs->rhs_lua = rhs_lua;
@@ -308,8 +365,8 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len
mapargs->rhs_is_noop = true;
} else {
char *rhs_buf = NULL;
- char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
- cpo_flags);
+ char *replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, sid,
+ REPTERM_DO_LT, NULL, cpo_flags);
mapargs->rhs_len = strlen(replaced);
// NB: replace_termcodes may produce an empty string even if orig_rhs is non-empty
// (e.g. a single ^V, see :h map-empty-rhs)
@@ -323,7 +380,7 @@ static void set_maparg_rhs(const char *const orig_rhs, const size_t orig_rhs_len
mapargs->orig_rhs_len = 0;
// stores <lua>ref_no<cr> in map_str
mapargs->rhs_len = (size_t)vim_snprintf(S_LEN(tmp_buf), "%c%c%c%d\r", K_SPECIAL,
- (char_u)KS_EXTRA, KE_LUA, rhs_lua);
+ KS_EXTRA, KE_LUA, rhs_lua);
mapargs->rhs = xstrdup(tmp_buf);
}
}
@@ -422,7 +479,7 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
// {lhs_end} is a pointer to the "terminating whitespace" after {lhs}.
// Use that to initialize {rhs_start}.
- const char *rhs_start = skipwhite((char *)lhs_end);
+ const char *rhs_start = skipwhite(lhs_end);
// Given {lhs} might be larger than MAXMAPLEN before replace_termcodes
// (e.g. "<Space>" is longer than ' '), so first copy into a buffer.
@@ -449,7 +506,7 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
/// @param args "rhs", "rhs_lua", "orig_rhs", "expr", "silent", "nowait", "replace_keycodes" and
/// and "desc" fields are used.
/// "rhs", "rhs_lua", "orig_rhs" fields are cleared if "simplified" is false.
-/// @param sid -1 to use current_sctx
+/// @param sid 0 to use current_sctx
static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, const char *keys,
MapArguments *args, int noremap, int mode, bool is_abbr, scid_T sid,
linenr_T lnum, bool simplified)
@@ -482,7 +539,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_simplified = simplified;
mp->m_expr = args->expr;
mp->m_replace_keycodes = args->replace_keycodes;
- if (sid >= 0) {
+ if (sid != 0) {
mp->m_script_ctx.sc_sid = sid;
mp->m_script_ctx.sc_lnum = lnum;
} else {
@@ -518,33 +575,16 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
/// @param buf Target Buffer
static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf)
{
- mapblock_T *mp, **mpp;
- const char *p;
- int n;
int retval = 0;
- mapblock_T **abbr_table;
- mapblock_T **map_table;
- int noremap;
- map_table = maphash;
- abbr_table = &first_abbr;
+ // If <buffer> was given, we'll be searching through the buffer's
+ // mappings/abbreviations, not the globals.
+ mapblock_T **map_table = args->buffer ? buf->b_maphash : maphash;
+ mapblock_T **abbr_table = args->buffer ? &buf->b_first_abbr : &first_abbr;
// For ":noremap" don't remap, otherwise do remap.
- if (maptype == MAPTYPE_NOREMAP) {
- noremap = REMAP_NONE;
- } else {
- noremap = REMAP_YES;
- }
-
- if (args->buffer) {
- // If <buffer> was given, we'll be searching through the buffer's
- // mappings/abbreviations, not the globals.
- map_table = buf->b_maphash;
- abbr_table = &buf->b_first_abbr;
- }
- if (args->script) {
- noremap = REMAP_SCRIPT;
- }
+ int noremap = args->script ? REMAP_SCRIPT
+ : maptype == MAPTYPE_NOREMAP ? REMAP_NONE : REMAP_YES;
const bool has_lhs = (args->lhs[0] != NUL);
const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
@@ -594,15 +634,15 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
const int first = vim_iswordp(lhs);
int last = first;
- p = (char *)lhs + utfc_ptr2len((char *)lhs);
- n = 1;
- while (p < (char *)lhs + len) {
+ const char *p = lhs + utfc_ptr2len(lhs);
+ int n = 1;
+ while (p < lhs + len) {
n++; // nr of (multi-byte) chars
last = vim_iswordp(p); // type of last char
if (same == -1 && last != first) {
same = n - 1; // count of same char type
}
- p += utfc_ptr2len((char *)p);
+ p += utfc_ptr2len(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
@@ -631,6 +671,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
&& maptype != MAPTYPE_UNMAP) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
+ mapblock_T *mp;
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
@@ -645,10 +686,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
&& mp->m_keylen == len
&& strncmp(mp->m_keys, lhs, (size_t)len) == 0) {
if (is_abbrev) {
- semsg(_("E224: global abbreviation already exists for %s"),
- mp->m_keys);
+ semsg(_(e_global_abbreviation_already_exists_for_str), mp->m_keys);
} else {
- semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
+ semsg(_(e_global_mapping_already_exists_for_str), mp->m_keys);
}
retval = 5;
goto theend;
@@ -661,6 +701,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
if (map_table != buf->b_maphash && !has_rhs && maptype != MAPTYPE_UNMAP) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
+ mapblock_T *mp;
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
@@ -676,7 +717,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
showmap(mp, true);
did_local = true;
} else {
- n = mp->m_keylen;
+ int n = mp->m_keylen;
if (strncmp(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
showmap(mp, true);
did_local = true;
@@ -706,8 +747,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
hash_end = 256;
}
for (int hash = hash_start; hash < hash_end && !got_int; hash++) {
- mpp = is_abbrev ? abbr_table : &(map_table[hash]);
- for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
+ mapblock_T **mpp = is_abbrev ? abbr_table : &(map_table[hash]);
+ for (mapblock_T *mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
if ((mp->m_mode & mode) == 0) {
// skip entries with wrong mode
mpp = &(mp->m_next);
@@ -719,6 +760,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
did_it = true;
}
} else { // do we have a match?
+ int n;
+ const char *p;
if (round) { // second round: Try unmap "rhs" string
n = (int)strlen(mp->m_str);
p = mp->m_str;
@@ -733,8 +776,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// we ignore trailing space when matching with
// the "lhs", since an abbreviation can't have
// trailing space.
- if (n != len && (!is_abbrev || round || n > len
- || *skipwhite((char *)lhs + n) != NUL)) {
+ if (n != len && (!is_abbrev || round || n > len || *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
@@ -762,9 +804,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
break;
} else if (args->unique) {
if (is_abbrev) {
- semsg(_("E226: abbreviation already exists for %s"), p);
+ semsg(_(e_abbreviation_already_exists_for_str), p);
} else {
- semsg(_("E227: mapping already exists for %s"), p);
+ semsg(_(e_mapping_already_exists_for_str), p);
}
retval = 5;
goto theend;
@@ -844,9 +886,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// print entries
if (!did_it && !did_local) {
if (is_abbrev) {
- msg(_("No abbreviation found"));
+ msg(_("No abbreviation found"), 0);
} else {
- msg(_("No mapping found"));
+ msg(_("No mapping found"), 0);
}
}
goto theend; // listing finished
@@ -857,9 +899,9 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
}
// Get here when adding a new entry to the maphash[] list or abbrlist.
- map_add(buf, map_table, abbr_table, (char *)lhs, args, noremap, mode, is_abbrev,
- -1, // sid
- 0, // lnum
+ map_add(buf, map_table, abbr_table, lhs, args, noremap, mode, is_abbrev,
+ 0, // sid
+ 0, // lnum
keyround1_simplified);
}
@@ -942,12 +984,10 @@ free_and_return:
/// Get the mapping mode from the command name.
static int get_map_mode(char **cmdp, bool forceit)
{
- char *p;
- int modec;
int mode;
- p = *cmdp;
- modec = (uint8_t)(*p++);
+ char *p = *cmdp;
+ int modec = (uint8_t)(*p++);
if (modec == 'i') {
mode = MODE_INSERT; // :imap
} else if (modec == 'l') {
@@ -984,16 +1024,13 @@ static int get_map_mode(char **cmdp, bool forceit)
/// This function used to be called map_clear().
static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr)
{
- int mode;
- int local;
-
- local = (strcmp(arg, "<buffer>") == 0);
+ bool local = strcmp(arg, "<buffer>") == 0;
if (!local && *arg != NUL) {
emsg(_(e_invarg));
return;
}
- mode = get_map_mode(&cmdp, forceit);
+ int mode = get_map_mode(&cmdp, forceit);
map_clear_mode(curbuf, mode, local, abbr);
}
@@ -1005,11 +1042,8 @@ static void do_mapclear(char *cmdp, char *arg, int forceit, int abbr)
/// @param abbr true for abbreviations
void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
{
- mapblock_T *mp, **mpp;
- int hash;
- int new_hash;
-
- for (hash = 0; hash < 256; hash++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T **mpp;
if (abbr) {
if (hash > 0) { // there is only one abbrlist
break;
@@ -1027,7 +1061,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
}
}
while (*mpp != NULL) {
- mp = *mpp;
+ mapblock_T *mp = *mpp;
if (mp->m_mode & mode) {
mp->m_mode &= ~mode;
if (mp->m_mode == 0) { // entry can be deleted
@@ -1035,7 +1069,7 @@ void map_clear_mode(buf_T *buf, int mode, bool local, bool abbr)
continue;
}
// May need to put this entry into another hash list.
- new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]);
+ int new_hash = MAP_HASH(mp->m_mode, (uint8_t)mp->m_keys[0]);
if (!abbr && new_hash != hash) {
*mpp = mp->m_next;
if (local) {
@@ -1067,12 +1101,10 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
int mode = 0;
- int retval;
char *buf = NULL;
- const char *const rhs = replace_termcodes(str, strlen(str),
- &buf, REPTERM_DO_LT,
- NULL, CPO_TO_CPO_FLAGS);
+ const char *const rhs = replace_termcodes(str, strlen(str), &buf, 0,
+ REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
#define MAPMODE(mode, modechars, chr, modeflags) \
do { \
@@ -1090,7 +1122,7 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
MAPMODE(mode, modechars, 'c', MODE_CMDLINE);
#undef MAPMODE
- retval = map_to_exists_mode(rhs, mode, abbr);
+ int retval = map_to_exists_mode(rhs, mode, abbr);
xfree(buf);
return retval;
@@ -1108,13 +1140,12 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
/// @return true if there is at least one mapping with given parameters.
int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr)
{
- mapblock_T *mp;
- int hash;
bool exp_buffer = false;
// Do it twice: once for global maps and once for local maps.
- for (;;) {
- for (hash = 0; hash < 256; hash++) {
+ while (true) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // There is only one abbr list.
break;
@@ -1162,12 +1193,13 @@ static bool expand_buffer = false;
/// @param cpo_flags Value of various flags present in &cpo
///
/// @return NULL when there is a problem.
-static char_u *translate_mapping(char_u *str, int cpo_flags)
+static char *translate_mapping(char *str_in, int cpo_flags)
{
+ uint8_t *str = (uint8_t *)str_in;
garray_T ga;
ga_init(&ga, 1, 40);
- bool cpo_bslash = !(cpo_flags&FLAG_CPO_BSLASH);
+ bool cpo_bslash = cpo_flags & FLAG_CPO_BSLASH;
for (; *str; str++) {
int c = *str;
@@ -1188,13 +1220,13 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
str += 2;
}
if (IS_SPECIAL(c) || modifiers) { // special key
- ga_concat(&ga, (char *)get_special_key_name(c, modifiers));
+ ga_concat(&ga, get_special_key_name(c, modifiers));
continue; // for (str)
}
}
if (c == ' ' || c == '\t' || c == Ctrl_J || c == Ctrl_V
- || (c == '\\' && !cpo_bslash)) {
+ || c == '<' || (c == '\\' && !cpo_bslash)) {
ga_append(&ga, cpo_bslash ? Ctrl_V : '\\');
}
@@ -1203,7 +1235,7 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
}
}
ga_append(&ga, NUL);
- return (char_u *)(ga.ga_data);
+ return (char *)ga.ga_data;
}
/// Work out what to complete when doing command line completion of mapping
@@ -1212,8 +1244,8 @@ static char_u *translate_mapping(char_u *str, int cpo_flags)
/// @param forceit true if '!' given
/// @param isabbrev true if abbreviation
/// @param isunmap true if unmap/unabbrev command
-char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev,
- bool isunmap, cmdidx_T cmdidx)
+char *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit, bool isabbrev,
+ bool isunmap, cmdidx_T cmdidx)
{
if (forceit && cmdidx != CMD_map && cmdidx != CMD_unmap) {
xp->xp_context = EXPAND_NOTHING;
@@ -1229,7 +1261,7 @@ char_u *set_context_in_map_cmd(expand_T *xp, char *cmd, char *arg, bool forceit,
expand_isabbrev = isabbrev;
xp->xp_context = EXPAND_MAPPINGS;
expand_buffer = false;
- for (;;) {
+ while (true) {
if (strncmp(arg, "<buffer>", 8) == 0) {
expand_buffer = true;
arg = skipwhite(arg + 8);
@@ -1308,7 +1340,7 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
bool match;
int score = 0;
if (!fuzzy) {
- match = vim_regexec(regmatch, p, (colnr_T)0);
+ match = vim_regexec(regmatch, p, 0);
} else {
score = fuzzy_match_str(p, pat);
match = (score != 0);
@@ -1342,11 +1374,11 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
mp = maphash[hash];
}
for (; mp; mp = mp->m_next) {
- if (!(mp->m_mode & expand_mapmodes)) {
+ if (mp->m_simplified || !(mp->m_mode & expand_mapmodes)) {
continue;
}
- char *p = (char *)translate_mapping((char_u *)mp->m_keys, CPO_TO_CPO_FLAGS);
+ char *p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS);
if (p == NULL) {
continue;
}
@@ -1354,7 +1386,7 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
bool match;
int score = 0;
if (!fuzzy) {
- match = vim_regexec(regmatch, p, (colnr_T)0);
+ match = vim_regexec(regmatch, p, 0);
} else {
score = fuzzy_match_str(p, pat);
match = (score != 0);
@@ -1434,15 +1466,8 @@ int ExpandMappings(char *pat, regmatch_T *regmatch, int *numMatches, char ***mat
// Return true if there is an abbreviation, false if not.
bool check_abbr(int c, char *ptr, int col, int mincol)
{
- int len;
- int scol; // starting column of the abbr.
- int j;
- char *s;
- char_u tb[MB_MAXBYTES + 4];
- mapblock_T *mp;
- mapblock_T *mp2;
+ uint8_t tb[MB_MAXBYTES + 4];
int clen = 0; // length in characters
- bool is_id = true;
if (typebuf.tb_no_abbr_cnt) { // abbrev. are not recursive
return false;
@@ -1461,7 +1486,10 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
return false;
}
+ int scol; // starting column of the abbr.
+
{
+ bool is_id = true;
bool vim_abbr;
char *p = mb_prevptr(ptr, ptr + col);
if (!vim_iswordp(p)) {
@@ -1489,24 +1517,24 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
}
if (scol < col) { // there is a word in front of the cursor
ptr += scol;
- len = col - scol;
- mp = curbuf->b_first_abbr;
- mp2 = first_abbr;
+ int len = col - scol;
+ mapblock_T *mp = curbuf->b_first_abbr;
+ mapblock_T *mp2 = first_abbr;
if (mp == NULL) {
mp = mp2;
mp2 = NULL;
}
for (; mp;
- mp->m_next == NULL ? (mp = mp2, mp2 = NULL) :
- (mp = mp->m_next)) {
+ mp->m_next == NULL ? (mp = mp2, mp2 = NULL)
+ : (mp = mp->m_next)) {
int qlen = mp->m_keylen;
char *q = mp->m_keys;
int match;
- if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) {
+ if (strchr(mp->m_keys, K_SPECIAL) != NULL) {
// Might have K_SPECIAL escaped mp->m_keys.
q = xstrdup(mp->m_keys);
- vim_unescape_ks((char_u *)q);
+ vim_unescape_ks(q);
qlen = (int)strlen(q);
}
// find entries with right mode and keys
@@ -1532,13 +1560,13 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
//
// Character CTRL-] is treated specially - it completes the
// abbreviation, but is not inserted into the input stream.
- j = 0;
+ int j = 0;
if (c != Ctrl_RSB) {
// special key code, split up
if (IS_SPECIAL(c) || c == K_SPECIAL) {
tb[j++] = K_SPECIAL;
- tb[j++] = (char_u)K_SECOND(c);
- tb[j++] = (char_u)K_THIRD(c);
+ tb[j++] = (uint8_t)K_SECOND(c);
+ tb[j++] = (uint8_t)K_THIRD(c);
} else {
if (c < ABBR_OFF && (c < ' ' || c > '~')) {
tb[j++] = Ctrl_V; // special char needs CTRL-V
@@ -1568,6 +1596,7 @@ bool check_abbr(int c, char *ptr, int col, int mincol)
const bool silent = mp->m_silent;
const bool expr = mp->m_expr;
+ char *s;
if (expr) {
s = eval_map_expr(mp, c);
} else {
@@ -1609,15 +1638,14 @@ char *eval_map_expr(mapblock_T *mp, int c)
// typeahead.
if (mp->m_luaref == LUA_NOREF) {
expr = xstrdup(mp->m_str);
- vim_unescape_ks((char_u *)expr);
+ vim_unescape_ks(expr);
}
const bool replace_keycodes = mp->m_replace_keycodes;
// Forbid changing text or using ":normal" to avoid most of the bad side
// effects. Also restore the cursor position.
- textlock++;
- ex_normal_lock++;
+ expr_map_lock++;
set_vim_var_char(c); // set v:char to the typed character
const pos_T save_cursor = curwin->w_cursor;
const int save_msg_col = msg_col;
@@ -1627,7 +1655,7 @@ char *eval_map_expr(mapblock_T *mp, int c)
Array args = ARRAY_DICT_INIT;
Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err);
if (ret.type == kObjectTypeString) {
- p = xstrndup(ret.data.string.data, ret.data.string.size);
+ p = string_to_cstr(ret.data.string);
}
api_free_object(ret);
if (err.type != kErrorTypeNone) {
@@ -1635,11 +1663,10 @@ char *eval_map_expr(mapblock_T *mp, int c)
api_clear_error(&err);
}
} else {
- p = eval_to_string(expr, NULL, false);
+ p = eval_to_string(expr, false);
xfree(expr);
}
- textlock--;
- ex_normal_lock--;
+ expr_map_lock--;
curwin->w_cursor = save_cursor;
msg_col = save_msg_col;
msg_row = save_msg_row;
@@ -1651,7 +1678,7 @@ char *eval_map_expr(mapblock_T *mp, int c)
char *res = NULL;
if (replace_keycodes) {
- replace_termcodes(p, strlen(p), &res, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(p, strlen(p), &res, 0, REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
} else {
// Escape K_SPECIAL in the result to be able to use the string as typeahead.
res = vim_strsave_escape_ks(p);
@@ -1667,18 +1694,13 @@ char *eval_map_expr(mapblock_T *mp, int c)
/// @param buf buffer for local mappings or NULL
int makemap(FILE *fd, buf_T *buf)
{
- mapblock_T *mp;
- char c1, c2, c3;
- char *p;
- char *cmd;
- int abbr;
- int hash;
bool did_cpo = false;
// Do the loop twice: Once for mappings, once for abbreviations.
// Then loop over all map hash lists.
- for (abbr = 0; abbr < 2; abbr++) {
- for (hash = 0; hash < 256; hash++) {
+ for (int abbr = 0; abbr < 2; abbr++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // there is only one abbr list
break;
@@ -1707,6 +1729,7 @@ int makemap(FILE *fd, buf_T *buf)
if (mp->m_luaref != LUA_NOREF) {
continue;
}
+ char *p;
for (p = mp->m_str; *p != NUL; p++) {
if ((uint8_t)p[0] == K_SPECIAL && (uint8_t)p[1] == KS_EXTRA
&& p[2] == KE_SNR) {
@@ -1720,14 +1743,10 @@ int makemap(FILE *fd, buf_T *buf)
// It's possible to create a mapping and then ":unmap" certain
// modes. We recreate this here by mapping the individual
// modes, which requires up to three of them.
- c1 = NUL;
- c2 = NUL;
- c3 = NUL;
- if (abbr) {
- cmd = "abbr";
- } else {
- cmd = "map";
- }
+ char c1 = NUL;
+ char c2 = NUL;
+ char c3 = NUL;
+ char *cmd = abbr ? "abbr" : "map";
switch (mp->m_mode) {
case MODE_NORMAL | MODE_VISUAL | MODE_SELECT | MODE_OP_PENDING:
break;
@@ -1814,8 +1833,7 @@ int makemap(FILE *fd, buf_T *buf)
did_cpo = true;
} else {
const char specials[] = { (char)(uint8_t)K_SPECIAL, NL, NUL };
- if (strpbrk((const char *)mp->m_str, specials) != NULL
- || strpbrk((const char *)mp->m_keys, specials) != NULL) {
+ if (strpbrk(mp->m_str, specials) != NULL || strpbrk(mp->m_keys, specials) != NULL) {
did_cpo = true;
}
}
@@ -1881,8 +1899,7 @@ int makemap(FILE *fd, buf_T *buf)
// return FAIL for failure, OK otherwise
int put_escstr(FILE *fd, char *strstart, int what)
{
- char_u *str = (char_u *)strstart;
- int c;
+ uint8_t *str = (uint8_t *)strstart;
// :map xx <Nop>
if (*str == NUL && what == 1) {
@@ -1906,7 +1923,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
continue;
}
- c = *str;
+ int c = *str;
// Special key codes have to be translated to be able to make sense
// when they are read back.
if (c == K_SPECIAL && what != 2) {
@@ -1921,7 +1938,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
str += 2;
}
if (IS_SPECIAL(c) || modifiers) { // special key
- if (fputs((char *)get_special_key_name(c, modifiers), fd) < 0) {
+ if (fputs(get_special_key_name(c, modifiers), fd) < 0) {
return FAIL;
}
continue;
@@ -1958,7 +1975,7 @@ int put_escstr(FILE *fd, char *strstart, int what)
}
} else if (c < ' ' || c > '~' || c == '|'
|| (what == 0 && c == ' ')
- || (what == 1 && str == (char_u *)strstart && c == ' ')
+ || (what == 1 && str == (uint8_t *)strstart && c == ' ')
|| (what != 2 && c == '<')) {
if (putc(Ctrl_V, fd) < 0) {
return FAIL;
@@ -1983,14 +2000,13 @@ int put_escstr(FILE *fd, char *strstart, int what)
char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr,
int *local_ptr, int *rhs_lua)
{
- int len, minlen;
- mapblock_T *mp;
*rhs_lua = LUA_NOREF;
- len = (int)strlen(keys);
+ int len = (int)strlen(keys);
for (int local = 1; local >= 0; local--) {
// loop over all hash lists
for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
if (abbr) {
if (hash > 0) { // there is only one list.
break;
@@ -2015,7 +2031,7 @@ char *check_map(char *keys, int mode, int exact, int ign_mod, int abbr, mapblock
s += 3;
keylen -= 3;
}
- minlen = keylen < len ? keylen : len;
+ int minlen = keylen < len ? keylen : len;
if (strncmp(s, keys, (size_t)minlen) == 0) {
if (mp_ptr != NULL) {
*mp_ptr = mp;
@@ -2050,28 +2066,25 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
- if (map_to_exists(name, mode, abbr)) {
- rettv->vval.v_number = true;
- } else {
- rettv->vval.v_number = false;
- }
+ rettv->vval.v_number = map_to_exists(name, mode, abbr);
}
/// Fill a Dictionary with all applicable maparg() like dictionaries
///
/// @param mp The maphash that contains the mapping information
/// @param buffer_value The "buffer" value
+/// @param abbr True if abbreviation
/// @param compatible True for compatible with old maparg() dict
///
/// @return A Dictionary.
static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt,
- const long buffer_value, const bool compatible)
+ const int buffer_value, const bool abbr, const bool compatible)
FUNC_ATTR_NONNULL_ARG(1)
{
Dictionary dict = ARRAY_DICT_INIT;
char *const lhs = str2special_save(mp->m_keys, compatible, !compatible);
char *const mapmode = map_mode_to_chars(mp->m_mode);
- varnumber_T noremap_value;
+ int noremap_value;
if (compatible) {
// Keep old compatible behavior
@@ -2091,26 +2104,29 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs
: cstr_as_string(str2special_save(mp->m_str, false, true))));
}
if (mp->m_desc != NULL) {
- PUT(dict, "desc", STRING_OBJ(cstr_to_string(mp->m_desc)));
+ PUT(dict, "desc", CSTR_TO_OBJ(mp->m_desc));
}
- PUT(dict, "lhs", STRING_OBJ(cstr_as_string(lhs)));
- PUT(dict, "lhsraw", STRING_OBJ(cstr_to_string((const char *)mp->m_keys)));
+ PUT(dict, "lhs", CSTR_AS_OBJ(lhs));
+ PUT(dict, "lhsraw", CSTR_TO_OBJ(mp->m_keys));
if (lhsrawalt != NULL) {
// Also add the value for the simplified entry.
- PUT(dict, "lhsrawalt", STRING_OBJ(cstr_to_string(lhsrawalt)));
+ PUT(dict, "lhsrawalt", CSTR_TO_OBJ(lhsrawalt));
}
PUT(dict, "noremap", INTEGER_OBJ(noremap_value));
PUT(dict, "script", INTEGER_OBJ(mp->m_noremap == REMAP_SCRIPT ? 1 : 0));
PUT(dict, "expr", INTEGER_OBJ(mp->m_expr ? 1 : 0));
PUT(dict, "silent", INTEGER_OBJ(mp->m_silent ? 1 : 0));
- PUT(dict, "sid", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_sid));
- PUT(dict, "lnum", INTEGER_OBJ((varnumber_T)mp->m_script_ctx.sc_lnum));
- PUT(dict, "buffer", INTEGER_OBJ((varnumber_T)buffer_value));
+ PUT(dict, "sid", INTEGER_OBJ(mp->m_script_ctx.sc_sid));
+ PUT(dict, "scriptversion", INTEGER_OBJ(1));
+ PUT(dict, "lnum", INTEGER_OBJ(mp->m_script_ctx.sc_lnum));
+ PUT(dict, "buffer", INTEGER_OBJ(buffer_value));
PUT(dict, "nowait", INTEGER_OBJ(mp->m_nowait ? 1 : 0));
if (mp->m_replace_keycodes) {
PUT(dict, "replace_keycodes", INTEGER_OBJ(1));
}
- PUT(dict, "mode", STRING_OBJ(cstr_as_string(mapmode)));
+ PUT(dict, "mode", CSTR_AS_OBJ(mapmode));
+ PUT(dict, "abbr", INTEGER_OBJ(abbr ? 1 : 0));
+ PUT(dict, "mode_bits", INTEGER_OBJ(mp->m_mode));
return dict;
}
@@ -2152,8 +2168,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
const int mode = get_map_mode((char **)&which, 0);
- char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, flags, &did_simplify,
- CPO_TO_CPO_FLAGS);
+ char *keys_simplified = replace_termcodes(keys, strlen(keys), &keys_buf, 0,
+ flags, &did_simplify, CPO_TO_CPO_FLAGS);
mapblock_T *mp = NULL;
int buffer_local;
LuaRef rhs_lua;
@@ -2162,10 +2178,8 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
if (did_simplify) {
// When the lhs is being simplified the not-simplified keys are
// preferred for printing, like in do_map().
- (void)replace_termcodes(keys,
- strlen(keys),
- &alt_keys_buf, flags | REPTERM_NO_SIMPLIFY, NULL,
- CPO_TO_CPO_FLAGS);
+ (void)replace_termcodes(keys, strlen(keys), &alt_keys_buf, 0,
+ flags | REPTERM_NO_SIMPLIFY, NULL, CPO_TO_CPO_FLAGS);
rhs = check_map(alt_keys_buf, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
}
@@ -2185,7 +2199,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
Dictionary dict = mapblock_fill_dict(mp,
did_simplify ? keys_simplified : NULL,
- buffer_local, true);
+ buffer_local, abbr, true);
(void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL);
api_free_dictionary(dict);
} else {
@@ -2198,22 +2212,99 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
xfree(alt_keys_buf);
}
+/// Get the mapping mode from the mode string.
+/// It may contain multiple characters, eg "nox", or "!", or ' '
+/// Return 0 if there is an error.
+static int get_map_mode_string(const char *const mode_string, const bool abbr)
+{
+ const char *p = mode_string;
+ const int MASK_V = MODE_VISUAL | MODE_SELECT;
+ const int MASK_MAP = MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING;
+ const int MASK_BANG = MODE_INSERT | MODE_CMDLINE;
+
+ if (*p == NUL) {
+ p = " "; // compatibility
+ }
+ int mode = 0;
+ int modec;
+ while ((modec = (uint8_t)(*p++))) {
+ int tmode;
+ switch (modec) {
+ case 'i':
+ tmode = MODE_INSERT; break;
+ case 'l':
+ tmode = MODE_LANGMAP; break;
+ case 'c':
+ tmode = MODE_CMDLINE; break;
+ case 'n':
+ tmode = MODE_NORMAL; break;
+ case 'x':
+ tmode = MODE_VISUAL; break;
+ case 's':
+ tmode = MODE_SELECT; break;
+ case 'o':
+ tmode = MODE_OP_PENDING; break;
+ case 't':
+ tmode = MODE_TERMINAL; break;
+ case 'v':
+ tmode = MASK_V; break;
+ case '!':
+ tmode = MASK_BANG; break;
+ case ' ':
+ tmode = MASK_MAP; break;
+ default:
+ return 0; // error, unknown mode character
+ }
+ mode |= tmode;
+ }
+ if ((abbr && (mode & ~MASK_BANG) != 0)
+ || (!abbr && (mode & (mode - 1)) != 0 // more than one bit set
+ && (
+ // false if multiple bits set in mode and mode is fully
+ // contained in one mask
+ !(((mode & MASK_BANG) != 0 && (mode & ~MASK_BANG) == 0)
+ || ((mode & MASK_MAP) != 0 && (mode & ~MASK_MAP) == 0))))) {
+ return 0;
+ }
+
+ return mode;
+}
+
/// "mapset()" function
void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ const char *which;
char buf[NUMBUFLEN];
- const char *which = tv_get_string_buf_chk(&argvars[0], buf);
- if (which == NULL) {
- return;
+ int is_abbr;
+ dict_T *d;
+
+ // If first arg is a dict, then that's the only arg permitted.
+ const bool dict_only = argvars[0].v_type == VAR_DICT;
+
+ if (dict_only) {
+ d = argvars[0].vval.v_dict;
+ which = tv_dict_get_string(d, "mode", false);
+ is_abbr = (int)tv_dict_get_bool(d, "abbr", -1);
+ if (which == NULL || is_abbr < 0) {
+ emsg(_(e_entries_missing_in_mapset_dict_argument));
+ return;
+ }
+ } else {
+ which = tv_get_string_buf_chk(&argvars[0], buf);
+ if (which == NULL) {
+ return;
+ }
+ is_abbr = (int)tv_get_bool(&argvars[1]);
+ if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
+ return;
+ }
+ d = argvars[2].vval.v_dict;
}
- const int mode = get_map_mode((char **)&which, 0);
- const bool is_abbr = tv_get_number(&argvars[1]) != 0;
-
- if (argvars[2].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ const int mode = get_map_mode_string(which, is_abbr);
+ if (mode == 0) {
+ semsg(_(e_illegal_map_mode_string_str), which);
return;
}
- dict_T *d = argvars[2].vval.v_dict;
// Get the values in the same order as above in get_maparg().
char *lhs = tv_dict_get_string(d, "lhs", false);
@@ -2232,7 +2323,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
api_free_object(callback_obj);
}
if (lhs == NULL || lhsraw == NULL || orig_rhs == NULL) {
- emsg(_("E460: entries missing in mapset() dict argument"));
+ emsg(_(e_entries_missing_in_mapset_dict_argument));
api_free_luaref(rhs_lua);
return;
}
@@ -2248,12 +2339,13 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
.replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0,
.desc = tv_dict_get_string(d, "desc", false),
};
- set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, CPO_TO_CPO_FLAGS, &args);
scid_T sid = (scid_T)tv_dict_get_number(d, "sid");
linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum");
bool buffer = tv_dict_get_number(d, "buffer") != 0;
// mode from the dict is not used
+ set_maparg_rhs(orig_rhs, strlen(orig_rhs), rhs_lua, sid, CPO_TO_CPO_FLAGS, &args);
+
mapblock_T **map_table = buffer ? curbuf->b_maphash : maphash;
mapblock_T **abbr_table = buffer ? &curbuf->b_first_abbr : &first_abbr;
@@ -2273,6 +2365,59 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
sid, lnum, false);
}
+/// "maplist()" function
+void f_maplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
+ const bool abbr = argvars[0].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[0]);
+
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ // Do it twice: once for global maps and once for local maps.
+ for (int buffer_local = 0; buffer_local <= 1; buffer_local++) {
+ for (int hash = 0; hash < 256; hash++) {
+ mapblock_T *mp;
+ if (abbr) {
+ if (hash > 0) { // there is only one abbr list
+ break;
+ }
+ if (buffer_local) {
+ mp = curbuf->b_first_abbr;
+ } else {
+ mp = first_abbr;
+ }
+ } else if (buffer_local) {
+ mp = curbuf->b_maphash[hash];
+ } else {
+ mp = maphash[hash];
+ }
+ for (; mp; mp = mp->m_next) {
+ if (mp->m_simplified) {
+ continue;
+ }
+
+ char *keys_buf = NULL;
+ bool did_simplify = false;
+
+ char *lhs = str2special_save(mp->m_keys, true, false);
+ (void)replace_termcodes(lhs, strlen(lhs), &keys_buf, 0, flags, &did_simplify,
+ CPO_TO_CPO_FLAGS);
+ xfree(lhs);
+
+ Dictionary dict = mapblock_fill_dict(mp,
+ did_simplify ? keys_buf : NULL,
+ buffer_local, abbr, true);
+ typval_T d = TV_INITIAL_VALUE;
+ (void)object_to_vim(DICTIONARY_OBJ(dict), &d, NULL);
+ assert(d.v_type == VAR_DICT);
+ tv_list_append_dict(rettv->vval.v_list, d.vval.v_dict);
+ api_free_dictionary(dict);
+ xfree(keys_buf);
+ }
+ }
+ }
+}
+
/// "maparg()" function
void f_maparg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2328,13 +2473,13 @@ static garray_T langmap_mapga = GA_EMPTY_INIT_VALUE;
static void langmap_set_entry(int from, int to)
{
langmap_entry_T *entries = (langmap_entry_T *)(langmap_mapga.ga_data);
- unsigned int a = 0;
+ unsigned a = 0;
assert(langmap_mapga.ga_len >= 0);
- unsigned int b = (unsigned int)langmap_mapga.ga_len;
+ unsigned b = (unsigned)langmap_mapga.ga_len;
// Do a binary search for an existing entry.
while (a != b) {
- unsigned int i = (a + b) / 2;
+ unsigned i = (a + b) / 2;
int d = entries[i].from - from;
if (d == 0) {
@@ -2353,7 +2498,7 @@ static void langmap_set_entry(int from, int to)
// insert new entry at position "a"
entries = (langmap_entry_T *)(langmap_mapga.ga_data) + a;
memmove(entries + 1, entries,
- ((unsigned int)langmap_mapga.ga_len - a) * sizeof(langmap_entry_T));
+ ((unsigned)langmap_mapga.ga_len - a) * sizeof(langmap_entry_T));
langmap_mapga.ga_len++;
entries[0].from = from;
entries[0].to = to;
@@ -2385,23 +2530,20 @@ int langmap_adjust_mb(int c)
void langmap_init(void)
{
for (int i = 0; i < 256; i++) {
- langmap_mapchar[i] = (char_u)i; // we init with a one-to-one map
+ langmap_mapchar[i] = (uint8_t)i; // we init with a one-to-one map
}
ga_init(&langmap_mapga, sizeof(langmap_entry_T), 8);
}
/// Called when langmap option is set; the language map can be
/// changed at any time!
-void langmap_set(void)
+const char *did_set_langmap(optset_T *args)
{
- char *p;
- char *p2;
- int from, to;
-
- ga_clear(&langmap_mapga); // clear the previous map first
- langmap_init(); // back to one-to-one map
+ ga_clear(&langmap_mapga); // clear the previous map first
+ langmap_init(); // back to one-to-one map
- for (p = p_langmap; p[0] != NUL;) {
+ for (char *p = p_langmap; p[0] != NUL;) {
+ char *p2;
for (p2 = p; p2[0] != NUL && p2[0] != ',' && p2[0] != ';';
MB_PTR_ADV(p2)) {
if (p2[0] == '\\' && p2[1] != NUL) {
@@ -2421,8 +2563,8 @@ void langmap_set(void)
if (p[0] == '\\' && p[1] != NUL) {
p++;
}
- from = utf_ptr2char(p);
- to = NUL;
+ int from = utf_ptr2char(p);
+ int to = NUL;
if (p2 == NULL) {
MB_PTR_ADV(p);
if (p[0] != ',') {
@@ -2440,16 +2582,17 @@ void langmap_set(void)
}
}
if (to == NUL) {
- semsg(_("E357: 'langmap': Matching character missing for %s"),
- transchar(from));
- return;
+ snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E357: 'langmap': Matching character missing for %s"),
+ transchar(from));
+ return args->os_errbuf;
}
if (from >= 256) {
langmap_set_entry(from, to);
} else {
assert(to <= UCHAR_MAX);
- langmap_mapchar[from & 255] = (char_u)to;
+ langmap_mapchar[from & 255] = (uint8_t)to;
}
// Advance to next pair
@@ -2460,8 +2603,10 @@ void langmap_set(void)
p = p2;
if (p[0] != NUL) {
if (p[0] != ',') {
- semsg(_("E358: 'langmap': Extra characters after semicolon: %s"), p);
- return;
+ snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E358: 'langmap': Extra characters after semicolon: %s"),
+ p);
+ return args->os_errbuf;
}
p++;
}
@@ -2470,16 +2615,17 @@ void langmap_set(void)
}
}
}
+
+ return NULL;
}
static void do_exmap(exarg_T *eap, int isabbrev)
{
- int mode;
char *cmdp = eap->cmd;
- mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
+ int mode = get_map_mode(&cmdp, eap->forceit || isabbrev);
switch (do_map((*cmdp == 'n') ? MAPTYPE_NOREMAP
- : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP,
+ : (*cmdp == 'u') ? MAPTYPE_UNMAP : MAPTYPE_MAP,
eap->arg, mode, isabbrev)) {
case 1:
emsg(_(e_invarg));
@@ -2502,7 +2648,7 @@ void ex_map(exarg_T *eap)
// If we are in a secure mode we print the mappings for security reasons.
if (secure) {
secure = 2;
- msg_outtrans(eap->cmd);
+ msg_outtrans(eap->cmd, 0);
msg_putchar('\n');
}
do_exmap(eap, false);
@@ -2549,26 +2695,22 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
const sctx_T save_current_sctx = api_set_sctx(channel_id);
- if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) {
- lua_funcref = opts->callback.data.luaref;
- opts->callback.data.luaref = LUA_NOREF;
- }
MapArguments parsed_args = MAP_ARGUMENTS_INIT;
if (opts) {
-#define KEY_TO_BOOL(name) \
- parsed_args.name = api_object_to_bool(opts->name, #name, false, err); \
- if (ERROR_SET(err)) { \
- goto fail_and_free; \
- }
-
- KEY_TO_BOOL(nowait);
- KEY_TO_BOOL(noremap);
- KEY_TO_BOOL(silent);
- KEY_TO_BOOL(script);
- KEY_TO_BOOL(expr);
- KEY_TO_BOOL(unique);
- KEY_TO_BOOL(replace_keycodes);
-#undef KEY_TO_BOOL
+ parsed_args.nowait = opts->nowait;
+ parsed_args.noremap = opts->noremap;
+ parsed_args.silent = opts->silent;
+ parsed_args.script = opts->script;
+ parsed_args.expr = opts->expr;
+ parsed_args.unique = opts->unique;
+ parsed_args.replace_keycodes = opts->replace_keycodes;
+ if (HAS_KEY(opts, keymap, callback)) {
+ lua_funcref = opts->callback;
+ opts->callback = LUA_NOREF;
+ }
+ if (HAS_KEY(opts, keymap, desc)) {
+ parsed_args.desc = string_to_cstr(opts->desc);
+ }
}
parsed_args.buffer = !global;
@@ -2584,32 +2726,26 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
goto fail_and_free;
}
- if (opts != NULL && opts->desc.type == kObjectTypeString) {
- parsed_args.desc = string_to_cstr(opts->desc.data.string);
- } else {
- parsed_args.desc = NULL;
- }
if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
goto fail_and_free;
}
- if (mode.size > 1) {
- api_set_error(err, kErrorTypeValidation, "Shortname is too long: %s", mode.data);
- goto fail_and_free;
+ char *p = mode.size > 0 ? mode.data : "m";
+ bool forceit = *p == '!';
+ // integer value of the mapping mode, to be passed to do_map()
+ int mode_val = get_map_mode(&p, forceit);
+ if (forceit) {
+ assert(p == mode.data);
+ p++;
}
- int mode_val; // integer value of the mapping mode, to be passed to do_map()
- char *p = (mode.size) ? mode.data : "m";
- if (strncmp(p, "!", 2) == 0) {
- mode_val = get_map_mode(&p, true); // mapmode-ic
- } else {
- mode_val = get_map_mode(&p, false);
- if (mode_val == (MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING) && mode.size > 0) {
- // get_map_mode() treats unrecognized mode shortnames as ":map".
- // This is an error unless the given shortname was empty string "".
- api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", p);
- goto fail_and_free;
- }
+ bool is_abbrev = (mode_val & (MODE_INSERT | MODE_CMDLINE)) != 0 && *p == 'a';
+ if (is_abbrev) {
+ p++;
+ }
+ if (mode.size > 0 && (size_t)(p - mode.data) != mode.size) {
+ api_set_error(err, kErrorTypeValidation, "Invalid mode shortname: \"%s\"", mode.data);
+ goto fail_and_free;
}
if (parsed_args.lhs_len == 0) {
@@ -2645,14 +2781,14 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
maptype_val = MAPTYPE_NOREMAP;
}
- switch (buf_do_map(maptype_val, &parsed_args, mode_val, 0, target_buf)) {
+ switch (buf_do_map(maptype_val, &parsed_args, mode_val, is_abbrev, target_buf)) {
case 0:
break;
case 1:
- api_set_error(err, kErrorTypeException, (char *)e_invarg, 0);
+ api_set_error(err, kErrorTypeException, e_invarg, 0);
goto fail_and_free;
case 2:
- api_set_error(err, kErrorTypeException, (char *)e_nomap, 0);
+ api_set_error(err, kErrorTypeException, e_nomap, 0);
goto fail_and_free;
case 5:
api_set_error(err, kErrorTypeException,
@@ -2687,7 +2823,7 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
int int_mode = get_map_mode(&p, 0);
// Determine the desired buffer value
- long buffer_value = (buf == NULL) ? 0 : buf->handle;
+ int buffer_value = (buf == NULL) ? 0 : buf->handle;
for (int i = 0; i < MAX_MAPHASH; i++) {
for (const mapblock_T *current_maphash = get_maphash(i, buf);
@@ -2699,7 +2835,8 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
ADD(mappings,
- DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value, false)));
+ DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL,
+ buffer_value, false, false)));
}
}
}
diff --git a/src/nvim/mapping.h b/src/nvim/mapping.h
index 58e28810bc..ffe7ab4290 100644
--- a/src/nvim/mapping.h
+++ b/src/nvim/mapping.h
@@ -1,63 +1,25 @@
-#ifndef NVIM_MAPPING_H
-#define NVIM_MAPPING_H
-
-#include <stdbool.h>
-#include <stddef.h>
-
-#include "lauxlib.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-/// All possible |:map-arguments| usable in a |:map| command.
-///
-/// The <special> argument has no effect on mappings and is excluded from this
-/// struct declaration. |:noremap| is included, since it behaves like a map
-/// argument when used in a mapping.
-///
-/// @see mapblock_T
-struct map_arguments {
- bool buffer;
- bool expr;
- bool noremap;
- bool nowait;
- bool script;
- bool silent;
- bool unique;
- bool replace_keycodes;
-
- /// The {lhs} of the mapping.
- ///
- /// vim limits this to MAXMAPLEN characters, allowing us to use a static
- /// buffer. Setting lhs_len to a value larger than MAXMAPLEN can signal
- /// that {lhs} was too long and truncated.
- char lhs[MAXMAPLEN + 1];
- size_t lhs_len;
-
- /// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
- char alt_lhs[MAXMAPLEN + 1];
- size_t alt_lhs_len;
-
- char *rhs; /// The {rhs} of the mapping.
- size_t rhs_len;
- LuaRef rhs_lua; /// lua function as {rhs}
- bool rhs_is_noop; /// True when the {rhs} should be <Nop>.
-
- char *orig_rhs; /// The original text of the {rhs}.
- size_t orig_rhs_len;
- char *desc; /// map description
+#pragma once
+
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+
+#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/mapping_defs.h" // IWYU pragma: export
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/regexp_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// Used for the first argument of do_map()
+enum {
+ MAPTYPE_MAP = 0,
+ MAPTYPE_UNMAP = 1,
+ MAPTYPE_NOREMAP = 2,
};
-typedef struct map_arguments MapArguments;
-#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, false, \
- { 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
-
-// Used for the first argument of do_map()
-#define MAPTYPE_MAP 0
-#define MAPTYPE_UNMAP 1
-#define MAPTYPE_NOREMAP 2
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mapping.h.generated.h"
#endif
-#endif // NVIM_MAPPING_H
diff --git a/src/nvim/mapping_defs.h b/src/nvim/mapping_defs.h
new file mode 100644
index 0000000000..6691c5ac3b
--- /dev/null
+++ b/src/nvim/mapping_defs.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/eval/typval_defs.h"
+#include "nvim/types_defs.h"
+
+/// Maximum length of key sequence to be mapped.
+enum { MAXMAPLEN = 50, };
+
+/// Structure used for mappings and abbreviations.
+typedef struct mapblock mapblock_T;
+struct mapblock {
+ mapblock_T *m_next; ///< next mapblock in list
+ char *m_keys; ///< mapped from, lhs
+ char *m_str; ///< mapped to, rhs
+ char *m_orig_str; ///< rhs as entered by the user
+ LuaRef m_luaref; ///< lua function reference as rhs
+ int m_keylen; ///< strlen(m_keys)
+ int m_mode; ///< valid mode
+ int m_simplified; ///< m_keys was simplified
+ int m_noremap; ///< if non-zero no re-mapping for m_str
+ char m_silent; ///< <silent> used, don't echo commands
+ char m_nowait; ///< <nowait> used
+ char m_expr; ///< <expr> used, m_str is an expression
+ sctx_T m_script_ctx; ///< SCTX where map was defined
+ char *m_desc; ///< description of mapping
+ bool m_replace_keycodes; ///< replace keycodes in result of expression
+};
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index f1a1f25e6c..5839cf7a2e 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// mark.c: functions for setting marks and jumping to them
#include <assert.h>
@@ -9,7 +6,7 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
@@ -17,14 +14,13 @@
#include "nvim/diff.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
#include "nvim/fold.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -32,16 +28,15 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/quickfix.h"
-#include "nvim/sign.h"
#include "nvim/strings.h"
#include "nvim/textobject.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// This file contains routines to maintain and manipulate marks.
@@ -51,9 +46,6 @@
// There are marks 'A - 'Z (set by user) and '0 to '9 (set when writing
// shada).
-/// Global marks (marks with file number or name)
-static xfmark_T namedfm[NGLOBALMARKS];
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.c.generated.h"
#endif
@@ -80,11 +72,12 @@ void free_xfmark(xfmark_T fm)
}
/// Free and clear fmark_T item
-void clear_fmark(fmark_T *fm)
+void clear_fmark(fmark_T *const fm, const Timestamp timestamp)
FUNC_ATTR_NONNULL_ALL
{
free_fmark(*fm);
- CLEAR_POINTER(fm);
+ *fm = (fmark_T)INIT_FMARK;
+ fm->timestamp = timestamp;
}
// Set named mark "c" to position "pos".
@@ -239,7 +232,7 @@ fmark_T *get_jumplist(win_T *win, int count)
return NULL;
}
- for (;;) {
+ while (true) {
if (win->w_jumplistidx + count < 0
|| win->w_jumplistidx + count >= win->w_jumplistlen) {
return NULL;
@@ -445,11 +438,11 @@ fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name)
listcmd_busy = true; // avoid that '' is changed
if (name == '{' || name == '}') { // to previous/next paragraph
oparg_T oa;
- if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) {
+ if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1, NUL, false)) {
mark = pos_to_mark(buf, NULL, win->w_cursor);
}
} else if (name == '(' || name == ')') { // to previous/next sentence
- if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) {
+ if (findsent(name == ')' ? FORWARD : BACKWARD, 1)) {
mark = pos_to_mark(buf, NULL, win->w_cursor);
}
}
@@ -521,11 +514,10 @@ fmark_T *pos_to_mark(buf_T *buf, fmark_T *fmp, pos_T pos)
/// @return whether the buffer was switched or not.
static MarkMoveRes switch_to_mark_buf(fmark_T *fm, bool pcmark_on_switch)
{
- bool res;
if (fm->fnum != curbuf->b_fnum) {
// Switch to another file.
int getfile_flag = pcmark_on_switch ? GETF_SETMARK : 0;
- res = buflist_getfile(fm->fnum, (linenr_T)1, getfile_flag, false) == OK;
+ bool res = buflist_getfile(fm->fnum, fm->mark.lnum, getfile_flag, false) == OK;
return res == true ? kMarkSwitchedBuf : kMarkMoveFailed;
}
return 0;
@@ -542,7 +534,11 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags)
{
static fmark_T fm_copy = INIT_FMARK;
MarkMoveRes res = kMarkMoveSuccess;
- if (!mark_check(fm)) {
+ const char *errormsg = NULL;
+ if (!mark_check(fm, &errormsg)) {
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
res = kMarkMoveFailed;
goto end;
}
@@ -558,7 +554,10 @@ MarkMoveRes mark_move_to(fmark_T *fm, MarkMove flags)
goto end;
}
// Check line count now that the **destination buffer is loaded**.
- if (!mark_check_line_bounds(curbuf, fm)) {
+ if (!mark_check_line_bounds(curbuf, fm, &errormsg)) {
+ if (errormsg != NULL) {
+ emsg(errormsg);
+ }
res |= kMarkMoveFailed;
goto end;
}
@@ -617,11 +616,8 @@ fmarkv_T mark_view_make(linenr_T topline, pos_T pos)
/// @return next mark or NULL if no mark is found.
fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
{
- int i;
fmark_T *result = NULL;
- pos_T pos;
-
- pos = *startpos;
+ pos_T pos = *startpos;
if (dir == BACKWARD && begin_line) {
pos.col = 0;
@@ -629,7 +625,7 @@ fmark_T *getnextmark(pos_T *startpos, int dir, int begin_line)
pos.col = MAXCOL;
}
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
if (curbuf->b_namedm[i].mark.lnum > 0) {
if (dir == FORWARD) {
if ((result == NULL || lt(curbuf->b_namedm[i].mark, result->mark))
@@ -677,7 +673,7 @@ static void fname2fnum(xfmark_T *fm)
char *p = path_shorten_fname(NameBuff, IObuff);
// buflist_new() will call fmarks_check_names()
- (void)buflist_new(NameBuff, p, (linenr_T)1, 0);
+ (void)buflist_new(NameBuff, p, 1, 0);
}
// Check all file marks for a name that matches the file name in buf.
@@ -686,18 +682,17 @@ static void fname2fnum(xfmark_T *fm)
void fmarks_check_names(buf_T *buf)
{
char *name = buf->b_ffname;
- int i;
if (buf->b_ffname == NULL) {
return;
}
- for (i = 0; i < NGLOBALMARKS; i++) {
+ for (int i = 0; i < NGLOBALMARKS; i++) {
fmarks_check_one(&namedfm[i], name, buf);
}
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- for (i = 0; i < wp->w_jumplistlen; i++) {
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
fmarks_check_one(&wp->w_jumplist[i], name, buf);
}
}
@@ -715,43 +710,45 @@ static void fmarks_check_one(xfmark_T *fm, char *name, buf_T *buf)
/// Check the position in @a fm is valid.
///
-/// Emit error message and return accordingly.
-///
/// Checks for:
/// - NULL raising unknown mark error.
/// - Line number <= 0 raising mark not set.
/// - Line number > buffer line count, raising invalid mark.
+///
/// @param fm[in] File mark to check.
+/// @param errormsg[out] Error message, if any.
///
/// @return true if the mark passes all the above checks, else false.
-bool mark_check(fmark_T *fm)
+bool mark_check(fmark_T *fm, const char **errormsg)
{
if (fm == NULL) {
- emsg(_(e_umark));
+ *errormsg = _(e_umark);
return false;
} else if (fm->mark.lnum <= 0) {
// In both cases it's an error but only raise when equals to 0
if (fm->mark.lnum == 0) {
- emsg(_(e_marknotset));
+ *errormsg = _(e_marknotset);
}
return false;
}
// Only check for valid line number if the buffer is loaded.
- if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm)) {
+ if (fm->fnum == curbuf->handle && !mark_check_line_bounds(curbuf, fm, errormsg)) {
return false;
}
return true;
}
/// Check if a mark line number is greater than the buffer line count, and set e_markinval.
+///
/// @note Should be done after the buffer is loaded into memory.
/// @param buf Buffer where the mark is set.
/// @param fm Mark to check.
+/// @param errormsg[out] Error message, if any.
/// @return true if below line count else false.
-bool mark_check_line_bounds(buf_T *buf, fmark_T *fm)
+bool mark_check_line_bounds(buf_T *buf, fmark_T *fm, const char **errormsg)
{
if (buf != NULL && fm->mark.lnum > buf->b_ml.ml_line_count) {
- emsg(_(e_markinval));
+ *errormsg = _(e_markinval);
return false;
}
return true;
@@ -762,20 +759,20 @@ bool mark_check_line_bounds(buf_T *buf, fmark_T *fm)
/// Used mainly when trashing the entire buffer during ":e" type commands.
///
/// @param[out] buf Buffer to clear marks in.
-void clrallmarks(buf_T *const buf)
+void clrallmarks(buf_T *const buf, const Timestamp timestamp)
FUNC_ATTR_NONNULL_ALL
{
for (size_t i = 0; i < NMARKS; i++) {
- clear_fmark(&buf->b_namedm[i]);
+ clear_fmark(&buf->b_namedm[i], timestamp);
}
- clear_fmark(&buf->b_last_cursor);
+ clear_fmark(&buf->b_last_cursor, timestamp);
buf->b_last_cursor.mark.lnum = 1;
- clear_fmark(&buf->b_last_insert);
- clear_fmark(&buf->b_last_change);
+ clear_fmark(&buf->b_last_insert, timestamp);
+ clear_fmark(&buf->b_last_change, timestamp);
buf->b_op_start.lnum = 0; // start/end op mark cleared
buf->b_op_end.lnum = 0;
for (int i = 0; i < buf->b_changelistlen; i++) {
- clear_fmark(&buf->b_changelist[i]);
+ clear_fmark(&buf->b_changelist[i], timestamp);
}
buf->b_changelistlen = 0;
}
@@ -795,18 +792,17 @@ char *fm_getname(fmark_T *fmark, int lead_len)
/// The returned string has been allocated.
static char *mark_line(pos_T *mp, int lead_len)
{
- char *s, *p;
- int len;
+ char *p;
if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) {
return xstrdup("-invalid-");
}
assert(Columns >= 0);
// Allow for up to 5 bytes per character.
- s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5);
+ char *s = xstrnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5);
// Truncate the line to fit it in the window
- len = 0;
+ int len = 0;
for (p = s; *p != NUL; MB_PTR_ADV(p)) {
len += ptr2cells(p);
if (len >= Columns - lead_len) {
@@ -821,19 +817,18 @@ static char *mark_line(pos_T *mp, int lead_len)
void ex_marks(exarg_T *eap)
{
char *arg = eap->arg;
- int i;
char *name;
- pos_T *posp, *startp, *endp;
+ pos_T *posp;
if (arg != NULL && *arg == NUL) {
arg = NULL;
}
show_one_mark('\'', arg, &curwin->w_pcmark, NULL, true);
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
show_one_mark(i + 'a', arg, &curbuf->b_namedm[i].mark, NULL, true);
}
- for (i = 0; i < NGLOBALMARKS; i++) {
+ for (int i = 0; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum != 0) {
name = fm_getname(&namedfm[i].fmark, 15);
} else {
@@ -855,8 +850,8 @@ void ex_marks(exarg_T *eap)
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true);
// Show the marks as where they will jump to.
- startp = &curbuf->b_visual.vi_start;
- endp = &curbuf->b_visual.vi_end;
+ pos_T *startp = &curbuf->b_visual.vi_start;
+ pos_T *endp = &curbuf->b_visual.vi_end;
if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) {
posp = startp;
} else {
@@ -880,7 +875,7 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
did_title = false;
} else {
if (arg == NULL) {
- msg(_("No marks set"));
+ msg(_("No marks set"), 0);
} else {
semsg(_("E283: No marks matching \"%s\""), arg);
}
@@ -902,9 +897,9 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
msg_putchar('\n');
if (!got_int) {
snprintf(IObuff, IOSIZE, " %c %6" PRIdLINENR " %4d ", c, p->lnum, p->col);
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
if (name != NULL) {
- msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(name, current ? HL_ATTR(HLF_D) : 0);
}
}
}
@@ -917,33 +912,30 @@ static void show_one_mark(int c, char *arg, pos_T *p, char *name_arg, int curren
// ":delmarks[!] [marks]"
void ex_delmarks(exarg_T *eap)
{
- char *p;
int from, to;
- int i;
- int lower;
- int digit;
int n;
if (*eap->arg == NUL && eap->forceit) {
// clear all marks
- clrallmarks(curbuf);
+ clrallmarks(curbuf, os_time());
} else if (eap->forceit) {
emsg(_(e_invarg));
} else if (*eap->arg == NUL) {
emsg(_(e_argreq));
} else {
// clear specified marks only
- for (p = eap->arg; *p != NUL; p++) {
- lower = ASCII_ISLOWER(*p);
- digit = ascii_isdigit(*p);
+ const Timestamp timestamp = os_time();
+ for (char *p = eap->arg; *p != NUL; p++) {
+ int lower = ASCII_ISLOWER(*p);
+ int digit = ascii_isdigit(*p);
if (lower || digit || ASCII_ISUPPER(*p)) {
if (p[1] == '-') {
// clear range of marks
from = (uint8_t)(*p);
to = (uint8_t)p[2];
if (!(lower ? ASCII_ISLOWER(p[2])
- : (digit ? ascii_isdigit(p[2])
- : ASCII_ISUPPER(p[2])))
+ : (digit ? ascii_isdigit(p[2])
+ : ASCII_ISUPPER(p[2])))
|| to < from) {
semsg(_(e_invarg2), p);
return;
@@ -954,9 +946,10 @@ void ex_delmarks(exarg_T *eap)
from = to = (uint8_t)(*p);
}
- for (i = from; i <= to; i++) {
+ for (int i = from; i <= to; i++) {
if (lower) {
curbuf->b_namedm[i - 'a'].mark.lnum = 0;
+ curbuf->b_namedm[i - 'a'].timestamp = timestamp;
} else {
if (digit) {
n = i - '0' + NMARKS;
@@ -965,25 +958,29 @@ void ex_delmarks(exarg_T *eap)
}
namedfm[n].fmark.mark.lnum = 0;
namedfm[n].fmark.fnum = 0;
+ namedfm[n].fmark.timestamp = timestamp;
XFREE_CLEAR(namedfm[n].fname);
}
}
} else {
switch (*p) {
case '"':
- CLEAR_FMARK(&curbuf->b_last_cursor); break;
+ clear_fmark(&curbuf->b_last_cursor, timestamp);
+ break;
case '^':
- CLEAR_FMARK(&curbuf->b_last_insert); break;
+ clear_fmark(&curbuf->b_last_insert, timestamp);
+ break;
case '.':
- CLEAR_FMARK(&curbuf->b_last_change); break;
+ clear_fmark(&curbuf->b_last_change, timestamp);
+ break;
case '[':
- curbuf->b_op_start.lnum = 0; break;
+ curbuf->b_op_start.lnum = 0; break;
case ']':
- curbuf->b_op_end.lnum = 0; break;
+ curbuf->b_op_end.lnum = 0; break;
case '<':
curbuf->b_visual.vi_start.lnum = 0; break;
case '>':
- curbuf->b_visual.vi_end.lnum = 0; break;
+ curbuf->b_visual.vi_end.lnum = 0; break;
case ' ':
break;
default:
@@ -998,15 +995,12 @@ void ex_delmarks(exarg_T *eap)
// print the jumplist
void ex_jumps(exarg_T *eap)
{
- int i;
- char *name;
-
cleanup_jumplist(curwin, true);
// Highlight title
msg_puts_title(_("\n jump line col file/text"));
- for (i = 0; i < curwin->w_jumplistlen && !got_int; i++) {
+ for (int i = 0; i < curwin->w_jumplistlen && !got_int; i++) {
if (curwin->w_jumplist[i].fmark.mark.lnum != 0) {
- name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
+ char *name = fm_getname(&curwin->w_jumplist[i].fmark, 16);
// Make sure to output the current indicator, even when on an wiped
// out buffer. ":filter" may still skip it.
@@ -1028,10 +1022,9 @@ void ex_jumps(exarg_T *eap)
i == curwin->w_jumplistidx ? '>' : ' ',
i > curwin->w_jumplistidx ? i - curwin->w_jumplistidx : curwin->w_jumplistidx - i,
curwin->w_jumplist[i].fmark.mark.lnum, curwin->w_jumplist[i].fmark.mark.col);
- msg_outtrans(IObuff);
- msg_outtrans_attr(name,
- curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum
- ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(IObuff, 0);
+ msg_outtrans(name,
+ curwin->w_jumplist[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0);
xfree(name);
os_breakcheck();
}
@@ -1051,26 +1044,24 @@ void ex_clearjumps(exarg_T *eap)
// print the changelist
void ex_changes(exarg_T *eap)
{
- int i;
- char *name;
-
// Highlight title
msg_puts_title(_("\nchange line col text"));
- for (i = 0; i < curbuf->b_changelistlen && !got_int; i++) {
+ for (int i = 0; i < curbuf->b_changelistlen && !got_int; i++) {
if (curbuf->b_changelist[i].mark.lnum != 0) {
msg_putchar('\n');
if (got_int) {
break;
}
- snprintf(IObuff, IOSIZE, "%c %3d %5ld %4d ",
+ snprintf(IObuff, IOSIZE, "%c %3d %5" PRIdLINENR " %4d ",
i == curwin->w_changelistidx ? '>' : ' ',
- i > curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i,
- (long)curbuf->b_changelist[i].mark.lnum,
+ i >
+ curwin->w_changelistidx ? i - curwin->w_changelistidx : curwin->w_changelistidx - i,
+ curbuf->b_changelist[i].mark.lnum,
curbuf->b_changelist[i].mark.col);
- msg_outtrans(IObuff);
- name = mark_line(&curbuf->b_changelist[i].mark, 17);
- msg_outtrans_attr(name, HL_ATTR(HLF_D));
+ msg_outtrans(IObuff, 0);
+ char *name = mark_line(&curbuf->b_changelist[i].mark, 17);
+ msg_outtrans(name, HL_ATTR(HLF_D));
xfree(name);
os_breakcheck();
}
@@ -1109,19 +1100,19 @@ void ex_changes(exarg_T *eap)
} \
}
-// Adjust marks between line1 and line2 (inclusive) to move 'amount' lines.
+// Adjust marks between "line1" and "line2" (inclusive) to move "amount" lines.
// Must be called before changed_*(), appended_lines() or deleted_lines().
// May be called before or after changing the text.
-// When deleting lines line1 to line2, use an 'amount' of MAXLNUM: The marks
-// within this range are made invalid.
-// If 'amount_after' is non-zero adjust marks after line2.
+// When deleting lines "line1" to "line2", use an "amount" of MAXLNUM: The
+// marks within this range are made invalid.
+// If "amount_after" is non-zero adjust marks after "line2".
// Example: Delete lines 34 and 35: mark_adjust(34, 35, MAXLNUM, -2);
// Example: Insert two lines below 55: mark_adjust(56, MAXLNUM, 2, 0);
// or: mark_adjust(56, 55, MAXLNUM, 2);
void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, true, op);
+ mark_adjust_buf(curbuf, line1, line2, amount, amount_after, true, false, op);
}
// mark_adjust_nofold() does the same as mark_adjust() but without adjusting
@@ -1132,84 +1123,83 @@ void mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amoun
void mark_adjust_nofold(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after,
ExtmarkOp op)
{
- mark_adjust_internal(line1, line2, amount, amount_after, false, op);
+ mark_adjust_buf(curbuf, line1, line2, amount, amount_after, false, false, op);
}
-static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount,
- linenr_T amount_after, bool adjust_folds, ExtmarkOp op)
+void mark_adjust_buf(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amount,
+ linenr_T amount_after, bool adjust_folds, bool by_api, ExtmarkOp op)
{
- int i;
- int fnum = curbuf->b_fnum;
+ int fnum = buf->b_fnum;
linenr_T *lp;
static pos_T initpos = { 1, 0, 0 };
- if (line2 < line1 && amount_after == 0L) { // nothing to do
+ if (line2 < line1 && amount_after == 0) { // nothing to do
return;
}
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// named marks, lower case and upper case
- for (i = 0; i < NMARKS; i++) {
- ONE_ADJUST(&(curbuf->b_namedm[i].mark.lnum));
+ for (int i = 0; i < NMARKS; i++) {
+ ONE_ADJUST(&(buf->b_namedm[i].mark.lnum));
if (namedfm[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum));
}
}
- for (i = NMARKS; i < NGLOBALMARKS; i++) {
+ for (int i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(namedfm[i].fmark.mark.lnum));
}
}
// last Insert position
- ONE_ADJUST(&(curbuf->b_last_insert.mark.lnum));
+ ONE_ADJUST(&(buf->b_last_insert.mark.lnum));
// last change position
- ONE_ADJUST(&(curbuf->b_last_change.mark.lnum));
+ ONE_ADJUST(&(buf->b_last_change.mark.lnum));
// last cursor position, if it was set
- if (!equalpos(curbuf->b_last_cursor.mark, initpos)) {
- ONE_ADJUST(&(curbuf->b_last_cursor.mark.lnum));
+ if (!equalpos(buf->b_last_cursor.mark, initpos)) {
+ ONE_ADJUST(&(buf->b_last_cursor.mark.lnum));
}
// list of change positions
- for (i = 0; i < curbuf->b_changelistlen; i++) {
- ONE_ADJUST_NODEL(&(curbuf->b_changelist[i].mark.lnum));
+ for (int i = 0; i < buf->b_changelistlen; i++) {
+ ONE_ADJUST_NODEL(&(buf->b_changelist[i].mark.lnum));
}
// Visual area
- ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_start.lnum));
- ONE_ADJUST_NODEL(&(curbuf->b_visual.vi_end.lnum));
+ ONE_ADJUST_NODEL(&(buf->b_visual.vi_start.lnum));
+ ONE_ADJUST_NODEL(&(buf->b_visual.vi_end.lnum));
// quickfix marks
- if (!qf_mark_adjust(NULL, line1, line2, amount, amount_after)) {
- curbuf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY;
+ if (!qf_mark_adjust(buf, NULL, line1, line2, amount, amount_after)) {
+ buf->b_has_qf_entry &= ~BUF_HAS_QF_ENTRY;
}
// location lists
bool found_one = false;
FOR_ALL_TAB_WINDOWS(tab, win) {
- found_one |= qf_mark_adjust(win, line1, line2, amount, amount_after);
+ found_one |= qf_mark_adjust(buf, win, line1, line2, amount, amount_after);
}
if (!found_one) {
- curbuf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY;
+ buf->b_has_qf_entry &= ~BUF_HAS_LL_ENTRY;
}
-
- sign_mark_adjust(line1, line2, amount, amount_after);
}
if (op != kExtmarkNOOP) {
- extmark_adjust(curbuf, line1, line2, amount, amount_after, op);
+ extmark_adjust(buf, line1, line2, amount, amount_after, op);
}
- // previous context mark
- ONE_ADJUST(&(curwin->w_pcmark.lnum));
+ if (curwin->w_buffer == buf) {
+ // previous context mark
+ ONE_ADJUST(&(curwin->w_pcmark.lnum));
- // previous pcmark
- ONE_ADJUST(&(curwin->w_prev_pcmark.lnum));
+ // previous pcpmark
+ ONE_ADJUST(&(curwin->w_prev_pcmark.lnum));
- // saved cursor for formatting
- if (saved_cursor.lnum != 0) {
- ONE_ADJUST_NODEL(&(saved_cursor.lnum));
+ // saved cursor for formatting
+ if (saved_cursor.lnum != 0) {
+ ONE_ADJUST_NODEL(&(saved_cursor.lnum));
+ }
}
// Adjust items in all windows related to the current buffer.
@@ -1217,17 +1207,17 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Marks in the jumplist. When deleting lines, this may create
// duplicate marks in the jumplist, they will be removed later.
- for (i = 0; i < win->w_jumplistlen; i++) {
+ for (int i = 0; i < win->w_jumplistlen; i++) {
if (win->w_jumplist[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(win->w_jumplist[i].fmark.mark.lnum));
}
}
}
- if (win->w_buffer == curbuf) {
+ if (win->w_buffer == buf) {
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// marks in the tag stack
- for (i = 0; i < win->w_tagstacklen; i++) {
+ for (int i = 0; i < win->w_tagstacklen; i++) {
if (win->w_tagstack[i].fmark.fnum == fnum) {
ONE_ADJUST_NODEL(&(win->w_tagstack[i].fmark.mark.lnum));
}
@@ -1242,22 +1232,29 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
// topline and cursor position for windows with the same buffer
// other than the current window
- if (win != curwin) {
+ if (win != curwin || by_api) {
if (win->w_topline >= line1 && win->w_topline <= line2) {
if (amount == MAXLNUM) { // topline is deleted
if (line1 <= 1) {
win->w_topline = 1;
} else {
- win->w_topline = line1 - 1;
+ // api: if the deleted region was replaced with new contents, display that
+ win->w_topline = (by_api && amount_after > line1 - line2 - 1) ? line1 : line1 - 1;
}
- } else { // keep topline on the same line
+ } else if (win->w_topline > line1) {
+ // keep topline on the same line, unless inserting just
+ // above it (we probably want to see that line then)
win->w_topline += amount;
}
win->w_topfill = 0;
- } else if (amount_after && win->w_topline > line2) {
+ // api: display new line if inserted right at topline
+ // TODO(bfredl): maybe always?
+ } else if (amount_after && win->w_topline > line2 + (by_api ? 1 : 0)) {
win->w_topline += amount_after;
win->w_topfill = 0;
}
+ }
+ if (win != curwin && !by_api) {
if (win->w_cursor.lnum >= line1 && win->w_cursor.lnum <= line2) {
if (amount == MAXLNUM) { // line with cursor is deleted
if (line1 <= 1) {
@@ -1281,7 +1278,7 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
}
// adjust diffs
- diff_mark_adjust(line1, line2, amount, amount_after);
+ diff_mark_adjust(buf, line1, line2, amount, amount_after);
}
// This code is used often, needs to be fast.
@@ -1306,24 +1303,23 @@ static void mark_adjust_internal(linenr_T line1, linenr_T line2, linenr_T amount
// position.
// "spaces_removed" is the number of spaces that were removed, matters when the
// cursor is inside them.
-void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long col_amount,
+void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, colnr_T col_amount,
int spaces_removed)
{
- int i;
int fnum = curbuf->b_fnum;
pos_T *posp;
- if ((col_amount == 0L && lnum_amount == 0L) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
+ if ((col_amount == 0 && lnum_amount == 0) || (cmdmod.cmod_flags & CMOD_LOCKMARKS)) {
return; // nothing to do
}
// named marks, lower case and upper case
- for (i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
COL_ADJUST(&(curbuf->b_namedm[i].mark));
if (namedfm[i].fmark.fnum == fnum) {
COL_ADJUST(&(namedfm[i].fmark.mark));
}
}
- for (i = NMARKS; i < NGLOBALMARKS; i++) {
+ for (int i = NMARKS; i < NGLOBALMARKS; i++) {
if (namedfm[i].fmark.fnum == fnum) {
COL_ADJUST(&(namedfm[i].fmark.mark));
}
@@ -1336,7 +1332,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
COL_ADJUST(&(curbuf->b_last_change.mark));
// list of change positions
- for (i = 0; i < curbuf->b_changelistlen; i++) {
+ for (int i = 0; i < curbuf->b_changelistlen; i++) {
COL_ADJUST(&(curbuf->b_changelist[i].mark));
}
@@ -1356,7 +1352,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
// Adjust items in all windows related to the current buffer.
FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
// marks in the jumplist
- for (i = 0; i < win->w_jumplistlen; i++) {
+ for (int i = 0; i < win->w_jumplistlen; i++) {
if (win->w_jumplist[i].fmark.fnum == fnum) {
COL_ADJUST(&(win->w_jumplist[i].fmark.mark));
}
@@ -1364,7 +1360,7 @@ void mark_col_adjust(linenr_T lnum, colnr_T mincol, linenr_T lnum_amount, long c
if (win->w_buffer == curbuf) {
// marks in the tag stack
- for (i = 0; i < win->w_tagstacklen; i++) {
+ for (int i = 0; i < win->w_tagstacklen; i++) {
if (win->w_tagstack[i].fmark.fnum == fnum) {
COL_ADJUST(&(win->w_tagstack[i].fmark.mark));
}
@@ -1457,9 +1453,7 @@ void cleanup_jumplist(win_T *wp, bool loadfiles)
// Copy the jumplist from window "from" to window "to".
void copy_jumplist(win_T *from, win_T *to)
{
- int i;
-
- for (i = 0; i < from->w_jumplistlen; i++) {
+ for (int i = 0; i < from->w_jumplistlen; i++) {
to->w_jumplist[i] = from->w_jumplist[i];
if (from->w_jumplist[i].fname != NULL) {
to->w_jumplist[i].fname = xstrdup(from->w_jumplist[i].fname);
@@ -1523,9 +1517,9 @@ const void *mark_global_iter(const void *const iter, char *const name, xfmark_T
return NULL;
}
size_t iter_off = (size_t)(iter_mark - &(namedfm[0]));
- *name = (char)(iter_off < NMARKS ?
- 'A' + (char)iter_off :
- '0' + (char)(iter_off - NMARKS));
+ *name = (char)(iter_off < NMARKS
+ ? 'A' + (char)iter_off
+ : '0' + (char)(iter_off - NMARKS));
*fm = *iter_mark;
while ((size_t)(++iter_mark - &(namedfm[0])) < ARRAY_SIZE(namedfm)) {
if (iter_mark->fmark.mark.lnum) {
@@ -1587,11 +1581,15 @@ const void *mark_buffer_iter(const void *const iter, const buf_T *const buf, cha
FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
*name = NUL;
- char mark_name = (char)(iter == NULL ? NUL :
- iter == &(buf->b_last_cursor) ? '"' :
- iter == &(buf->b_last_insert) ? '^' :
- iter == &(buf->b_last_change) ? '.' :
- 'a' + (char)((const fmark_T *)iter - &(buf->b_namedm[0])));
+ char mark_name = (char)(iter == NULL
+ ? NUL
+ : (iter == &(buf->b_last_cursor)
+ ? '"'
+ : (iter == &(buf->b_last_insert)
+ ? '^'
+ : (iter == &(buf->b_last_change)
+ ? '.'
+ : 'a' + (const fmark_T *)iter - &(buf->b_namedm[0])))));
const fmark_T *iter_mark = next_buffer_mark(buf, &mark_name);
while (iter_mark != NULL && iter_mark->mark.lnum == 0) {
iter_mark = next_buffer_mark(buf, &mark_name);
@@ -1671,9 +1669,7 @@ bool mark_set_local(const char name, buf_T *const buf, const fmark_T fm, const b
// Free items in the jumplist of window "wp".
void free_jumplist(win_T *wp)
{
- int i;
-
- for (i = 0; i < wp->w_jumplistlen; i++) {
+ for (int i = 0; i < wp->w_jumplistlen; i++) {
free_xfmark(wp->w_jumplist[i]);
}
wp->w_jumplistlen = 0;
@@ -1710,7 +1706,7 @@ void mark_mb_adjustpos(buf_T *buf, pos_T *lp)
FUNC_ATTR_NONNULL_ALL
{
if (lp->col > 0 || lp->coladd > 1) {
- const char *const p = ml_get_buf(buf, lp->lnum, false);
+ const char *const p = ml_get_buf(buf, lp->lnum);
if (*p == NUL || (int)strlen(p) < lp->col) {
lp->col = 0;
} else {
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index af0abba864..3237ae541e 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -1,19 +1,18 @@
-#ifndef NVIM_MARK_H
-#define NVIM_MARK_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark_defs.h"
#include "nvim/func_attr.h"
-#include "nvim/macros.h"
-#include "nvim/mark_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark_defs.h" // IWYU pragma: export
#include "nvim/memory.h"
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
/// Set fmark using given value
#define SET_FMARK(fmarkp_, mark_, fnum_, view_) \
@@ -34,10 +33,6 @@
SET_FMARK(fmarkp___, mark_, fnum_, view_); \
} while (0)
-/// Clear given fmark
-#define CLEAR_FMARK(fmarkp_) \
- RESET_FMARK(fmarkp_, ((pos_T) { 0, 0, 0 }), 0, ((fmarkv_T) { 0 }))
-
/// Set given extended mark (regular mark + file name)
#define SET_XFMARK(xfmarkp_, mark_, fnum_, view_, fname_) \
do { \
@@ -125,4 +120,3 @@ static inline void clearpos(pos_T *a)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.h.generated.h"
#endif
-#endif // NVIM_MARK_H
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index f9df0028db..67532d030f 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_MARK_DEFS_H
-#define NVIM_MARK_DEFS_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
// marks: positions in a file
// (a normal mark is a lnum/col pair, the same as a file position)
@@ -86,4 +85,5 @@ typedef struct xfilemark {
#define INIT_XFMARK { INIT_FMARK, NULL }
-#endif // NVIM_MARK_DEFS_H
+/// Global marks (marks with file number or name)
+EXTERN xfmark_T namedfm[NGLOBALMARKS] INIT( = { 0 });
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 77ba6e6fa4..b555b3b4ae 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Tree data structure for storing marks at (row, col) positions and updating
// them to arbitrary text changes. Derivative work of kbtree in klib, whose
// copyright notice is reproduced below. Also inspired by the design of the
@@ -14,8 +11,6 @@
// Use marktree_itr_current and marktree_itr_next/prev to read marks in a loop.
// marktree_del_itr deletes the current mark of the iterator and implicitly
// moves the iterator to the next mark.
-//
-// Work is ongoing to fully support ranges (mark pairs).
// Copyright notice for kbtree (included in heavily modified form):
//
@@ -48,29 +43,41 @@
// at the repo root.
#include <assert.h>
+#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "klib/kvec.h"
#include "nvim/garray.h"
#include "nvim/marktree.h"
#include "nvim/memory.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+// only for debug functions
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/garray_defs.h"
+#include "nvim/macros_defs.h"
#define T MT_BRANCH_FACTOR
-#define ILEN (sizeof(mtnode_t) + (2 * T) * sizeof(void *))
+#define ILEN (sizeof(MTNode) + (2 * T) * sizeof(void *))
#define ID_INCR (((uint64_t)1) << 2)
-#define rawkey(itr) ((itr)->node->key[(itr)->i])
+#define rawkey(itr) ((itr)->x->key[(itr)->i])
-static bool pos_leq(mtpos_t a, mtpos_t b)
+static bool pos_leq(MTPos a, MTPos b)
{
return a.row < b.row || (a.row == b.row && a.col <= b.col);
}
-static void relative(mtpos_t base, mtpos_t *val)
+static bool pos_less(MTPos a, MTPos b)
+{
+ return !pos_leq(b, a);
+}
+
+static void relative(MTPos base, MTPos *val)
{
assert(pos_leq(base, *val));
if (val->row == base.row) {
@@ -81,7 +88,7 @@ static void relative(mtpos_t base, mtpos_t *val)
}
}
-static void unrelative(mtpos_t base, mtpos_t *val)
+static void unrelative(MTPos base, MTPos *val)
{
if (val->row == 0) {
val->row = base.row;
@@ -91,7 +98,7 @@ static void unrelative(mtpos_t base, mtpos_t *val)
}
}
-static void compose(mtpos_t *base, mtpos_t val)
+static void compose(MTPos *base, MTPos val)
{
if (val.row == 0) {
base->col += val.col;
@@ -101,12 +108,21 @@ static void compose(mtpos_t *base, mtpos_t val)
}
}
+// Used by `marktree_splice`. Need to keep track of marks which moved
+// in order to repair intersections.
+typedef struct {
+ uint64_t id;
+ MTNode *old, *new;
+ int old_i, new_i;
+} Damage;
+typedef kvec_withinit_t(Damage, 8) DamageList;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "marktree.c.generated.h"
#endif
#define mt_generic_cmp(a, b) (((b) < (a)) - ((a) < (b)))
-static int key_cmp(mtkey_t a, mtkey_t b)
+static int key_cmp(MTKey a, MTKey b)
{
int cmp = mt_generic_cmp(a.pos.row, b.pos.row);
if (cmp != 0) {
@@ -116,18 +132,25 @@ static int key_cmp(mtkey_t a, mtkey_t b)
if (cmp != 0) {
return cmp;
}
- // NB: keeping the events at the same pos sorted by id is actually not
- // necessary only make sure that START is before END etc.
- return mt_generic_cmp(a.flags, b.flags);
+
+ // TODO(bfredl): MT_FLAG_REAL could go away if we fix marktree_getp_aux for real
+ const uint16_t cmp_mask = MT_FLAG_RIGHT_GRAVITY | MT_FLAG_END | MT_FLAG_REAL | MT_FLAG_LAST;
+ return mt_generic_cmp(a.flags & cmp_mask, b.flags & cmp_mask);
}
-static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
+/// @return position of k if it exists in the node, otherwise the position
+/// it should be inserted, which ranges from 0 to x->n _inclusively_
+/// @param match (optional) set to TRUE if match (pos, gravity) was found
+static inline int marktree_getp_aux(const MTNode *x, MTKey k, bool *match)
{
- int tr, *rr, begin = 0, end = x->n;
+ bool dummy_match;
+ bool *m = match ? match : &dummy_match;
+
+ int begin = 0, end = x->n;
if (x->n == 0) {
+ *m = false;
return -1;
}
- rr = r? r : &tr;
while (begin < end) {
int mid = (begin + end) >> 1;
if (key_cmp(x->key[mid], k) < 0) {
@@ -137,47 +160,84 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r)
}
}
if (begin == x->n) {
- *rr = 1; return x->n - 1;
+ *m = false;
+ return x->n - 1;
}
- if ((*rr = key_cmp(k, x->key[begin])) < 0) {
+ if (!(*m = (key_cmp(k, x->key[begin]) == 0))) {
begin--;
}
return begin;
}
-static inline void refkey(MarkTree *b, mtnode_t *x, int i)
+static inline void refkey(MarkTree *b, MTNode *x, int i)
{
pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x);
}
+static MTNode *id2node(MarkTree *b, uint64_t id)
+{
+ return pmap_get(uint64_t)(b->id2node, id);
+}
+
// put functions
// x must be an internal node, which is not full
// x->ptr[i] should be a full node, i e x->ptr[i]->n == 2*T-1
-static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
+static inline void split_node(MarkTree *b, MTNode *x, const int i, MTKey next)
{
- mtnode_t *y = x->ptr[i];
- mtnode_t *z;
- z = (mtnode_t *)xcalloc(1, y->level ? ILEN : sizeof(mtnode_t));
- b->n_nodes++;
+ MTNode *y = x->ptr[i];
+ MTNode *z = marktree_alloc_node(b, y->level);
z->level = y->level;
z->n = T - 1;
- memcpy(z->key, &y->key[T], sizeof(mtkey_t) * (T - 1));
+
+ // tricky: we might split a node in between inserting the start node and the end
+ // node of the same pair. Then we must not intersect this id yet (done later
+ // in marktree_intersect_pair).
+ uint64_t last_start = mt_end(next) ? mt_lookup_id(next.ns, next.id, false) : MARKTREE_END_FLAG;
+
+ // no alloc in the common case (less than 4 intersects)
+ kvi_copy(z->intersect, y->intersect);
+
+ if (!y->level) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+ for (int j = 0; j < T; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_end = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, true), true);
+ if (mt_start(k) && pi_end > pi && mt_lookup_key(k) != last_start) {
+ intersect_node(b, z, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+
+ // note: y->key[T-1] is moved up and thus checked for both
+ for (int j = T - 1; j < (T * 2) - 1; j++) {
+ MTKey k = y->key[j];
+ uint64_t pi_start = pseudo_index_for_id(b, mt_lookup_id(k.ns, k.id, false), true);
+ if (mt_end(k) && pi_start > 0 && pi_start < pi) {
+ intersect_node(b, y, mt_lookup_id(k.ns, k.id, false));
+ }
+ }
+ }
+
+ memcpy(z->key, &y->key[T], sizeof(MTKey) * (T - 1));
for (int j = 0; j < T - 1; j++) {
refkey(b, z, j);
}
if (y->level) {
- memcpy(z->ptr, &y->ptr[T], sizeof(mtnode_t *) * T);
+ memcpy(z->ptr, &y->ptr[T], sizeof(MTNode *) * T);
for (int j = 0; j < T; j++) {
z->ptr[j]->parent = z;
+ z->ptr[j]->p_idx = (int16_t)j;
}
}
y->n = T - 1;
memmove(&x->ptr[i + 2], &x->ptr[i + 1],
- sizeof(mtnode_t *) * (size_t)(x->n - i));
+ sizeof(MTNode *) * (size_t)(x->n - i));
x->ptr[i + 1] = z;
z->parent = x; // == y->parent
- memmove(&x->key[i + 1], &x->key[i], sizeof(mtkey_t) * (size_t)(x->n - i));
+ for (int j = i + 1; j < x->n + 2; j++) {
+ x->ptr[j]->p_idx = (int16_t)j;
+ }
+ memmove(&x->key[i + 1], &x->key[i], sizeof(MTKey) * (size_t)(x->n - i));
// move key to internal layer:
x->key[i] = y->key[T - 1];
@@ -190,25 +250,32 @@ static inline void split_node(MarkTree *b, mtnode_t *x, const int i)
if (i > 0) {
unrelative(x->key[i - 1].pos, &x->key[i].pos);
}
+
+ if (y->level) {
+ bubble_up(y);
+ bubble_up(z);
+ } else {
+ // code above goose here
+ }
}
// x must not be a full node (even if there might be internal space)
-static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
+static inline void marktree_putp_aux(MarkTree *b, MTNode *x, MTKey k)
{
- int i;
+ // TODO(bfredl): ugh, make sure this is the _last_ valid (pos, gravity) position,
+ // to minimize movement
+ int i = marktree_getp_aux(x, k, NULL) + 1;
if (x->level == 0) {
- i = marktree_getp_aux(x, k, 0);
- if (i != x->n - 1) {
- memmove(&x->key[i + 2], &x->key[i + 1],
- (size_t)(x->n - i - 1) * sizeof(mtkey_t));
+ if (i != x->n) {
+ memmove(&x->key[i + 1], &x->key[i],
+ (size_t)(x->n - i) * sizeof(MTKey));
}
- x->key[i + 1] = k;
- refkey(b, x, i + 1);
+ x->key[i] = k;
+ refkey(b, x, i);
x->n++;
} else {
- i = marktree_getp_aux(x, k, 0) + 1;
if (x->ptr[i]->n == 2 * T - 1) {
- split_node(b, x, i);
+ split_node(b, x, i, k);
if (key_cmp(k, x->key[i]) > 0) {
i++;
}
@@ -220,9 +287,9 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k)
}
}
-void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right)
+void marktree_put(MarkTree *b, MTKey key, int end_row, int end_col, bool end_right)
{
- assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK));
+ assert(!(key.flags & ~(MT_FLAG_EXTERNAL_MASK | MT_FLAG_RIGHT_GRAVITY)));
if (end_row >= 0) {
key.flags |= MT_FLAG_PAIRED;
}
@@ -230,32 +297,150 @@ void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_r
marktree_put_key(b, key);
if (end_row >= 0) {
- mtkey_t end_key = key;
+ MTKey end_key = key;
end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY)
|(uint16_t)MT_FLAG_END
|(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0));
- end_key.pos = (mtpos_t){ end_row, end_col };
+ end_key.pos = (MTPos){ end_row, end_col };
marktree_put_key(b, end_key);
+ MarkTreeIter itr[1] = { 0 }, end_itr[1] = { 0 };
+ marktree_lookup(b, mt_lookup_key(key), itr);
+ marktree_lookup(b, mt_lookup_key(end_key), end_itr);
+
+ marktree_intersect_pair(b, mt_lookup_key(key), itr, end_itr, false);
+ }
+}
+
+// this is currently not used very often, but if it was it should use binary search
+static bool intersection_has(Intersection *x, uint64_t id)
+{
+ for (size_t i = 0; i < kv_size(*x); i++) {
+ if (kv_A(*x, i) == id) {
+ return true;
+ } else if (kv_A(*x, i) >= id) {
+ return false;
+ }
+ }
+ return false;
+}
+
+static void intersect_node(MarkTree *b, MTNode *x, uint64_t id)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ kvi_pushp(x->intersect);
+ // optimized for the common case: new key is always in the end
+ for (ssize_t i = (ssize_t)kv_size(x->intersect) - 1; i >= 0; i--) {
+ if (i > 0 && kv_A(x->intersect, i - 1) > id) {
+ kv_A(x->intersect, i) = kv_A(x->intersect, i - 1);
+ } else {
+ kv_A(x->intersect, i) = id;
+ break;
+ }
+ }
+}
+
+static void unintersect_node(MarkTree *b, MTNode *x, uint64_t id, bool strict)
+{
+ assert(!(id & MARKTREE_END_FLAG));
+ bool seen = false;
+ size_t i;
+ for (i = 0; i < kv_size(x->intersect); i++) {
+ if (kv_A(x->intersect, i) < id) {
+ continue;
+ } else if (kv_A(x->intersect, i) == id) {
+ seen = true;
+ break;
+ } else { // (kv_A(x->intersect, i) > id)
+ break;
+ }
+ }
+ if (strict) {
+ assert(seen);
+ }
+
+ if (seen) {
+ if (i < kv_size(x->intersect) - 1) {
+ memmove(&kv_A(x->intersect, i), &kv_A(x->intersect, i + 1), (kv_size(x->intersect) - i - 1) *
+ sizeof(kv_A(x->intersect, i)));
+ }
+ kv_size(x->intersect)--;
}
}
-void marktree_put_key(MarkTree *b, mtkey_t k)
+/// @param itr mutated
+/// @param end_itr not mutated
+void marktree_intersect_pair(MarkTree *b, uint64_t id, MarkTreeIter *itr, MarkTreeIter *end_itr,
+ bool delete)
+{
+ int lvl = 0, maxlvl = MIN(itr->lvl, end_itr->lvl);
+#define iat(itr, l, q) ((l == itr->lvl) ? itr->i + q : itr->s[l].i)
+ for (; lvl < maxlvl; lvl++) {
+ if (itr->s[lvl].i > end_itr->s[lvl].i) {
+ return; // empty range
+ } else if (itr->s[lvl].i < end_itr->s[lvl].i) {
+ break; // work to do
+ }
+ }
+ if (lvl == maxlvl && iat(itr, lvl, 1) > iat(end_itr, lvl, 0)) {
+ return; // empty range
+ }
+
+ while (itr->x) {
+ bool skip = false;
+ if (itr->x == end_itr->x) {
+ if (itr->x->level == 0 || itr->i >= end_itr->i) {
+ break;
+ } else {
+ skip = true;
+ }
+ } else if (itr->lvl > lvl) {
+ skip = true;
+ } else {
+ if (iat(itr, lvl, 1) < iat(end_itr, lvl, 1)) {
+ skip = true;
+ } else {
+ lvl++;
+ }
+ }
+
+ if (skip) {
+ if (itr->x->level) {
+ MTNode *x = itr->x->ptr[itr->i + 1];
+ if (delete) {
+ unintersect_node(b, x, id, true);
+ } else {
+ intersect_node(b, x, id);
+ }
+ }
+ }
+ marktree_itr_next_skip(b, itr, skip, true, NULL);
+ }
+#undef iat
+}
+
+static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
+{
+ MTNode *x = xcalloc(1, internal ? ILEN : sizeof(MTNode));
+ kvi_init(x->intersect);
+ b->n_nodes++;
+ return x;
+}
+
+void marktree_put_key(MarkTree *b, MTKey k)
{
k.flags |= MT_FLAG_REAL; // let's be real.
if (!b->root) {
- b->root = (mtnode_t *)xcalloc(1, ILEN);
- b->n_nodes++;
+ b->root = marktree_alloc_node(b, true);
}
- mtnode_t *r, *s;
b->n_keys++;
- r = b->root;
+ MTNode *r = b->root;
if (r->n == 2 * T - 1) {
- b->n_nodes++;
- s = (mtnode_t *)xcalloc(1, ILEN);
+ MTNode *s = marktree_alloc_node(b, true);
b->root = s; s->level = r->level + 1; s->n = 0;
s->ptr[0] = r;
r->parent = s;
- split_node(b, s, 0);
+ r->p_idx = 0;
+ split_node(b, s, 0, k);
r = s;
}
marktree_putp_aux(b, r, k);
@@ -279,26 +464,41 @@ void marktree_put_key(MarkTree *b, mtkey_t k)
/// 6. If 4 went all the way to the root node. The root node
/// might have ended up with size 0. Delete it then.
///
-/// NB: ideally keeps the iterator valid. Like point to the key after this
-/// if present.
+/// The iterator remains valid, and now points at the key _after_ the deleted
+/// one.
///
/// @param rev should be true if we plan to iterate _backwards_ and delete
/// stuff before this key. Most of the time this is false (the
/// recommended strategy is to always iterate forward)
-void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
+uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
{
int adjustment = 0;
- mtnode_t *cur = itr->node;
+ MTNode *cur = itr->x;
int curi = itr->i;
uint64_t id = mt_lookup_key(cur->key[curi]);
- // fprintf(stderr, "\nDELET %lu\n", id);
- if (itr->node->level) {
+ MTKey raw = rawkey(itr);
+ uint64_t other = 0;
+ if (mt_paired(raw) && !(raw.flags & MT_FLAG_ORPHANED)) {
+ other = mt_lookup_key_side(raw, !mt_end(raw));
+
+ MarkTreeIter other_itr[1];
+ marktree_lookup(b, other, other_itr);
+ rawkey(other_itr).flags |= MT_FLAG_ORPHANED;
+ // Remove intersect markers. NB: must match exactly!
+ if (mt_start(raw)) {
+ MarkTreeIter this_itr[1] = { *itr }; // mutated copy
+ marktree_intersect_pair(b, id, this_itr, other_itr, true);
+ } else {
+ marktree_intersect_pair(b, other, other_itr, itr, true);
+ }
+ }
+
+ if (itr->x->level) {
if (rev) {
abort();
} else {
- // fprintf(stderr, "INTERNAL %d\n", cur->level);
// steal previous node
marktree_itr_prev(b, itr);
adjustment = -1;
@@ -306,41 +506,72 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
}
// 3.
- mtnode_t *x = itr->node;
+ MTNode *x = itr->x;
assert(x->level == 0);
- mtkey_t intkey = x->key[itr->i];
+ MTKey intkey = x->key[itr->i];
if (x->n > itr->i + 1) {
memmove(&x->key[itr->i], &x->key[itr->i + 1],
- sizeof(mtkey_t) * (size_t)(x->n - itr->i - 1));
+ sizeof(MTKey) * (size_t)(x->n - itr->i - 1));
}
x->n--;
+ b->n_keys--;
+ pmap_del(uint64_t)(b->id2node, id, NULL);
+
// 4.
// if (adjustment == 1) {
// abort();
// }
if (adjustment == -1) {
int ilvl = itr->lvl - 1;
- const mtnode_t *lnode = x;
+ MTNode *lnode = x;
+ uint64_t start_id = 0;
+ bool did_bubble = false;
+ if (mt_end(intkey)) {
+ start_id = mt_lookup_key_side(intkey, false);
+ }
do {
- const mtnode_t *const p = lnode->parent;
+ MTNode *p = lnode->parent;
if (ilvl < 0) {
abort();
}
- const int i = itr->s[ilvl].i;
+ int i = itr->s[ilvl].i;
assert(p->ptr[i] == lnode);
if (i > 0) {
unrelative(p->key[i - 1].pos, &intkey.pos);
}
+
+ if (p != cur && start_id) {
+ if (intersection_has(&p->ptr[0]->intersect, start_id)) {
+ // if not the first time, we need to undo the addition in the
+ // previous step (`intersect_node` just below)
+ int last = (lnode != x) ? 1 : 0;
+ for (int k = 0; k < p->n + last; k++) { // one less as p->ptr[n] is the last
+ unintersect_node(b, p->ptr[k], start_id, true);
+ }
+ intersect_node(b, p, start_id);
+ did_bubble = true;
+ }
+ }
+
lnode = p;
ilvl--;
} while (lnode != cur);
- mtkey_t deleted = cur->key[curi];
+ MTKey deleted = cur->key[curi];
cur->key[curi] = intkey;
refkey(b, cur, curi);
+ // if `did_bubble` then we already added `start_id` to some parent
+ if (mt_end(cur->key[curi]) && !did_bubble) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
relative(intkey.pos, &deleted.pos);
- mtnode_t *y = cur->ptr[curi + 1];
+ MTNode *y = cur->ptr[curi + 1];
if (deleted.pos.row || deleted.pos.col) {
while (y) {
for (int k = 0; k < y->n; k++) {
@@ -352,46 +583,48 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->i--;
}
- b->n_keys--;
- pmap_del(uint64_t)(b->id2node, id);
-
// 5.
bool itr_dirty = false;
int rlvl = itr->lvl - 1;
int *lasti = &itr->i;
+ MTPos ppos = itr->pos;
while (x != b->root) {
assert(rlvl >= 0);
- mtnode_t *p = x->parent;
+ MTNode *p = x->parent;
if (x->n >= T - 1) {
// we are done, if this node is fine the rest of the tree will be
break;
}
int pi = itr->s[rlvl].i;
assert(p->ptr[pi] == x);
+ if (pi > 0) {
+ ppos.row -= p->key[pi - 1].pos.row;
+ ppos.col = itr->s[rlvl].oldcol;
+ }
+ // ppos is now the pos of p
+
if (pi > 0 && p->ptr[pi - 1]->n > T - 1) {
*lasti += 1;
itr_dirty = true;
// steal one key from the left neighbour
- pivot_right(b, p, pi - 1);
+ pivot_right(b, ppos, p, pi - 1);
break;
} else if (pi < p->n && p->ptr[pi + 1]->n > T - 1) {
// steal one key from right neighbour
- pivot_left(b, p, pi);
+ pivot_left(b, ppos, p, pi);
break;
} else if (pi > 0) {
- // fprintf(stderr, "LEFT ");
assert(p->ptr[pi - 1]->n == T - 1);
// merge with left neighbour
*lasti += T;
x = merge_node(b, p, pi - 1);
if (lasti == &itr->i) {
// TRICKY: we merged the node the iterator was on
- itr->node = x;
+ itr->x = x;
}
itr->s[rlvl].i--;
itr_dirty = true;
} else {
- // fprintf(stderr, "RIGHT ");
assert(pi < p->n && p->ptr[pi + 1]->n == T - 1);
merge_node(b, p, pi);
// no iter adjustment needed
@@ -408,18 +641,18 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
itr->lvl--;
}
if (b->root->level) {
- mtnode_t *oldroot = b->root;
+ MTNode *oldroot = b->root;
b->root = b->root->ptr[0];
b->root->parent = NULL;
- xfree(oldroot);
+ marktree_free_node(b, oldroot);
} else {
// no items, nothing for iterator to point to
// not strictly needed, should handle delete right-most mark anyway
- itr->node = NULL;
+ itr->x = NULL;
}
}
- if (itr->node && itr_dirty) {
+ if (itr->x && itr_dirty) {
marktree_itr_fix_pos(b, itr);
}
@@ -435,18 +668,241 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
marktree_itr_next(b, itr);
marktree_itr_next(b, itr);
} else {
- if (itr->node && itr->i >= itr->node->n) {
+ if (itr->x && itr->i >= itr->x->n) {
// we deleted the last key of a leaf node
// go to the inner key after that.
- assert(itr->node->level == 0);
+ assert(itr->x->level == 0);
marktree_itr_next(b, itr);
}
}
+
+ return other;
+}
+
+/// similar to intersect_common but modify x and y in place to retain
+/// only the items which are NOT in common
+static void intersect_merge(Intersection *restrict m, Intersection *restrict x,
+ Intersection *restrict y)
+{
+ size_t xi = 0, yi = 0;
+ size_t xn = 0, yn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ // TODO(bfredl): kvi_pushp is actually quite complex, break out kvi_resize() to a function?
+ kvi_push(*m, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ }
+ }
+
+ if (xi < kv_size(*x)) {
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), sizeof(kv_A(*x, xn)) * (kv_size(*x) - xi));
+ xn += kv_size(*x) - xi;
+ }
+ if (yi < kv_size(*y)) {
+ memmove(&kv_A(*y, yn), &kv_A(*y, yi), sizeof(kv_A(*y, yn)) * (kv_size(*y) - yi));
+ yn += kv_size(*y) - yi;
+ }
+
+ kv_size(*x) = xn;
+ kv_size(*y) = yn;
+}
+
+// w used to be a child of x but it is now a child of y, adjust intersections accordingly
+// @param[out] d are intersections which should be added to the old children of y
+static void intersect_mov(Intersection *restrict x, Intersection *restrict y,
+ Intersection *restrict w, Intersection *restrict d)
+{
+ size_t wi = 0, yi = 0;
+ size_t wn = 0, yn = 0;
+ size_t xi = 0;
+ while (wi < kv_size(*w) || xi < kv_size(*x)) {
+ if (wi < kv_size(*w) && (xi >= kv_size(*x) || kv_A(*x, xi) >= kv_A(*w, wi))) {
+ if (xi < kv_size(*x) && kv_A(*x, xi) == kv_A(*w, wi)) {
+ xi++;
+ }
+ // now w < x strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*w, wi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*w, wi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ wi++;
+ } else {
+ kv_A(*w, wn++) = kv_A(*w, wi++);
+ }
+ } else {
+ // x < w strictly
+ while (yi < kv_size(*y) && kv_A(*y, yi) < kv_A(*x, xi)) {
+ kvi_push(*d, kv_A(*y, yi));
+ yi++;
+ }
+ if (yi < kv_size(*y) && kv_A(*y, yi) == kv_A(*x, xi)) {
+ kv_A(*y, yn++) = kv_A(*y, yi++);
+ xi++;
+ } else {
+ // add kv_A(x, xi) at kv_A(w, wn), pushing up wi if wi == wn
+ if (wi == wn) {
+ size_t n = kv_size(*w) - wn;
+ kvi_pushp(*w);
+ if (n > 0) {
+ memmove(&kv_A(*w, wn + 1), &kv_A(*w, wn), n * sizeof(kv_A(*w, 0)));
+ }
+ kv_A(*w, wi) = kv_A(*x, xi);
+ wn++;
+ wi++; // no need to consider the added element again
+ } else {
+ assert(wn < wi);
+ kv_A(*w, wn++) = kv_A(*x, xi);
+ }
+ xi++;
+ }
+ }
+ }
+ if (yi < kv_size(*y)) {
+ // move remaining items to d
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*d, n);
+ memcpy(&kv_A(*d, kv_size(*d)), &kv_A(*y, yi), n * sizeof(kv_A(*d, 0)));
+ kv_size(*d) += n;
+ }
+ kv_size(*w) = wn;
+ kv_size(*y) = yn;
+}
+
+bool intersect_mov_test(const uint64_t *x, size_t nx, const uint64_t *y, size_t ny,
+ const uint64_t *win, size_t nwin, uint64_t *wout, size_t *nwout,
+ uint64_t *dout, size_t *ndout)
+{
+ // x is immutable in the context of intersect_mov. y might shrink, but we
+ // don't care about it (we get it the deleted ones in d)
+ Intersection xi = { .items = (uint64_t *)x, .size = nx };
+ Intersection yi = { .items = (uint64_t *)y, .size = ny };
+
+ Intersection w;
+ kvi_init(w);
+ for (size_t i = 0; i < nwin; i++) {
+ kvi_push(w, win[i]);
+ }
+ Intersection d;
+ kvi_init(d);
+
+ intersect_mov(&xi, &yi, &w, &d);
+
+ if (w.size > *nwout || d.size > *ndout) {
+ return false;
+ }
+
+ memcpy(wout, w.items, sizeof(w.items[0]) * w.size);
+ *nwout = w.size;
+
+ memcpy(dout, d.items, sizeof(d.items[0]) * d.size);
+ *ndout = d.size;
+
+ return true;
+}
+
+/// intersection: i = x & y
+static void intersect_common(Intersection *i, Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ kvi_push(*i, kv_A(*x, xi));
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ xi++;
+ } else {
+ yi++;
+ }
+ }
+}
+
+// inplace union: x |= y
+static void intersect_add(Intersection *x, Intersection *y)
+{
+ size_t xi = 0, yi = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*y, yi) < kv_A(*x, xi)) {
+ size_t n = kv_size(*x) - xi; // at least one
+ kvi_pushp(*x);
+ memmove(&kv_A(*x, xi + 1), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ kv_A(*x, xi) = kv_A(*y, yi);
+ xi++; // newly added element
+ yi++;
+ } else {
+ xi++;
+ }
+ }
+ if (yi < kv_size(*y)) {
+ size_t n = kv_size(*y) - yi; // at least one
+ kvi_ensure_more_space(*x, n);
+ memcpy(&kv_A(*x, kv_size(*x)), &kv_A(*y, yi), n * sizeof(kv_A(*x, 0)));
+ kv_size(*x) += n;
+ }
+}
+
+// inplace asymmetric difference: x &= ~y
+static void intersect_sub(Intersection *restrict x, Intersection *restrict y)
+{
+ size_t xi = 0, yi = 0;
+ size_t xn = 0;
+ while (xi < kv_size(*x) && yi < kv_size(*y)) {
+ if (kv_A(*x, xi) == kv_A(*y, yi)) {
+ xi++;
+ yi++;
+ } else if (kv_A(*x, xi) < kv_A(*y, yi)) {
+ kv_A(*x, xn++) = kv_A(*x, xi++);
+ } else {
+ yi++;
+ }
+ }
+ if (xi < kv_size(*x)) {
+ size_t n = kv_size(*x) - xi;
+ if (xn < xi) { // otherwise xn == xi
+ memmove(&kv_A(*x, xn), &kv_A(*x, xi), n * sizeof(kv_A(*x, 0)));
+ }
+ xn += n;
+ }
+ kv_size(*x) = xn;
}
-static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
+/// x is a node which shrunk, or the half of a split
+///
+/// this means that intervals which previously intersected all the (current)
+/// child nodes, now instead intersects `x` itself.
+static void bubble_up(MTNode *x)
+{
+ Intersection xi;
+ kvi_init(xi);
+ // due to invariants, the largest subset of _all_ subnodes is the intersection
+ // between the first and the last
+ intersect_common(&xi, &x->ptr[0]->intersect, &x->ptr[x->n]->intersect);
+ if (kv_size(xi)) {
+ for (int i = 0; i < x->n + 1; i++) {
+ intersect_sub(&x->ptr[i]->intersect, &xi);
+ }
+ intersect_add(&x->intersect, &xi);
+ }
+ kvi_destroy(xi);
+}
+
+static MTNode *merge_node(MarkTree *b, MTNode *p, int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ Intersection m;
+ kvi_init(m);
+
+ intersect_merge(&m, &x->intersect, &y->intersect);
x->key[x->n] = p->key[i];
refkey(b, x, x->n);
@@ -454,35 +910,78 @@ static mtnode_t *merge_node(MarkTree *b, mtnode_t *p, int i)
relative(p->key[i - 1].pos, &x->key[x->n].pos);
}
- memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ memmove(&x->key[x->n + 1], y->key, (size_t)y->n * sizeof(MTKey));
for (int k = 0; k < y->n; k++) {
refkey(b, x, x->n + 1 + k);
unrelative(x->key[x->n].pos, &x->key[x->n + 1 + k].pos);
}
if (x->level) {
- memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
- for (int k = 0; k < y->n + 1; k++) {
- x->ptr[x->n + k + 1]->parent = x;
+ // bubble down: ranges that intersected old-x but not old-y or vice versa
+ // must be moved to their respective children
+ memmove(&x->ptr[x->n + 1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int k = 0; k < x->n + 1; k++) {
+ // TODO(bfredl): dedicated impl for "Z |= Y"
+ for (size_t idx = 0; idx < kv_size(x->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(x->intersect, idx));
+ }
+ }
+ for (int ky = 0; ky < y->n + 1; ky++) {
+ int k = x->n + ky + 1;
+ // nodes that used to be in y, now the second half of x
+ x->ptr[k]->parent = x;
+ x->ptr[k]->p_idx = (int16_t)k;
+ // TODO(bfredl): dedicated impl for "Z |= X"
+ for (size_t idx = 0; idx < kv_size(y->intersect); idx++) {
+ intersect_node(b, x->ptr[k], kv_A(y->intersect, idx));
+ }
}
}
x->n += y->n + 1;
- memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(mtkey_t));
+ memmove(&p->key[i], &p->key[i + 1], (size_t)(p->n - i - 1) * sizeof(MTKey));
memmove(&p->ptr[i + 1], &p->ptr[i + 2],
- (size_t)(p->n - i - 1) * sizeof(mtkey_t *));
+ (size_t)(p->n - i - 1) * sizeof(MTKey *));
+ for (int j = i + 1; j < p->n; j++) { // note: one has been deleted
+ p->ptr[j]->p_idx = (int16_t)j;
+ }
p->n--;
- xfree(y);
- b->n_nodes--;
+ marktree_free_node(b, y);
+
+ kvi_destroy(x->intersect);
+
+ // move of a kvec_withinit_t, messy!
+ // TODO(bfredl): special case version of intersect_merge(x_out, x_in_m_out, y) to avoid this
+ kvi_move(&x->intersect, &m);
+
return x;
}
+/// @param dest is overwritten (assumed to already been freed/moved)
+/// @param src consumed (don't free or use)
+void kvi_move(Intersection *dest, Intersection *src)
+{
+ dest->size = src->size;
+ dest->capacity = src->capacity;
+ if (src->items == src->init_array) {
+ memcpy(dest->init_array, src->init_array, src->size * sizeof(*src->init_array));
+ dest->items = dest->init_array;
+ } else {
+ dest->items = src->items;
+ }
+}
+
// TODO(bfredl): as a potential "micro" optimization, pivoting should balance
// the two nodes instead of stealing just one key
-static void pivot_right(MarkTree *b, mtnode_t *p, int i)
+// x_pos is the absolute position of the key just before x (or a dummy key strictly less than any
+// key inside x, if x is the first leaf)
+static void pivot_right(MarkTree *b, MTPos p_pos, MTNode *p, const int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
- memmove(&y->key[1], y->key, (size_t)y->n * sizeof(mtkey_t));
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
+ memmove(&y->key[1], y->key, (size_t)y->n * sizeof(MTKey));
if (y->level) {
- memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(mtnode_t *));
+ memmove(&y->ptr[1], y->ptr, ((size_t)y->n + 1) * sizeof(MTNode *));
+ for (int j = 1; j < y->n + 2; j++) {
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
y->key[0] = p->key[i];
refkey(b, y, 0);
@@ -491,6 +990,7 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
y->ptr[0] = x->ptr[x->n];
y->ptr[0]->parent = y;
+ y->ptr[0]->p_idx = 0;
}
x->n--;
y->n++;
@@ -501,11 +1001,46 @@ static void pivot_right(MarkTree *b, mtnode_t *p, int i)
for (int k = 1; k < y->n; k++) {
unrelative(y->key[0].pos, &y->key[k].pos);
}
+
+ // repair intersections of x
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // y->ptr[0] was moved from x to y
+ // adjust y->ptr[0] for a difference between the parents
+ // in addition, this might cause some intersection of the old y
+ // to bubble down to the old children of y (if y->ptr[0] wasn't intersected)
+ intersect_mov(&x->intersect, &y->intersect, &y->ptr[0]->intersect, &d);
+ if (kv_size(d)) {
+ for (int yi = 1; yi < y->n + 1; yi++) {
+ intersect_add(&y->ptr[yi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(x);
+ } else {
+ // if the last element of x used to be an end node, check if it now covers all of x
+ if (mt_end(p->key[i])) {
+ uint64_t pi = pseudo_index(x, 0); // note: sloppy pseudo-index
+ uint64_t start_id = mt_lookup_key_side(p->key[i], false);
+ uint64_t pi_start = pseudo_index_for_id(b, start_id, true);
+ if (pi_start > 0 && pi_start < pi) {
+ intersect_node(b, x, start_id);
+ }
+ }
+
+ if (mt_start(y->key[0])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, y, mt_lookup_key(y->key[0]), false);
+ }
+ }
}
-static void pivot_left(MarkTree *b, mtnode_t *p, int i)
+static void pivot_left(MarkTree *b, MTPos p_pos, MTNode *p, int i)
{
- mtnode_t *x = p->ptr[i], *y = p->ptr[i + 1];
+ MTNode *x = p->ptr[i], *y = p->ptr[i + 1];
// reverse from how we "always" do it. but pivot_left
// is just the inverse of pivot_right, so reverse it literally.
@@ -524,97 +1059,191 @@ static void pivot_left(MarkTree *b, mtnode_t *p, int i)
if (x->level) {
x->ptr[x->n + 1] = y->ptr[0];
x->ptr[x->n + 1]->parent = x;
+ x->ptr[x->n + 1]->p_idx = (int16_t)(x->n + 1);
}
- memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(mtkey_t));
+ memmove(y->key, &y->key[1], (size_t)(y->n - 1) * sizeof(MTKey));
if (y->level) {
- memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(mtnode_t *));
+ memmove(y->ptr, &y->ptr[1], (size_t)y->n * sizeof(MTNode *));
+ for (int j = 0; j < y->n; j++) { // note: last item deleted
+ y->ptr[j]->p_idx = (int16_t)j;
+ }
}
x->n++;
y->n--;
+
+ // repair intersections of x,y
+ if (x->level) {
+ // handle y and first new y->ptr[0]
+ Intersection d;
+ kvi_init(d);
+ // x->ptr[x->n] was moved from y to x
+ // adjust x->ptr[x->n] for a difference between the parents
+ // in addition, this might cause some intersection of the old x
+ // to bubble down to the old children of x (if x->ptr[n] wasn't intersected)
+ intersect_mov(&y->intersect, &x->intersect, &x->ptr[x->n]->intersect, &d);
+ if (kv_size(d)) {
+ for (int xi = 0; xi < x->n; xi++) { // ptr[x->n| deliberately skipped
+ intersect_add(&x->ptr[xi]->intersect, &d);
+ }
+ }
+ kvi_destroy(d);
+
+ bubble_up(y);
+ } else {
+ // if the first element of y used to be an start node, check if it now covers all of y
+ if (mt_start(p->key[i])) {
+ uint64_t pi = pseudo_index(y, 0); // note: sloppy pseudo-index
+
+ uint64_t end_id = mt_lookup_key_side(p->key[i], true);
+ uint64_t pi_end = pseudo_index_for_id(b, end_id, true);
+
+ if (pi_end > pi) {
+ intersect_node(b, y, mt_lookup_key(p->key[i]));
+ }
+ }
+
+ if (mt_end(x->key[x->n - 1])) {
+ // no need for a check, just delet it if it was there
+ unintersect_node(b, x, mt_lookup_key_side(x->key[x->n - 1], false), false);
+ }
+ }
}
/// frees all mem, resets tree to valid empty state
void marktree_clear(MarkTree *b)
{
if (b->root) {
- marktree_free_node(b->root);
+ marktree_free_subtree(b, b->root);
b->root = NULL;
}
- if (b->id2node->table.keys) {
- pmap_destroy(uint64_t)(b->id2node);
- pmap_init(uint64_t, b->id2node);
- }
+ map_destroy(uint64_t, b->id2node);
+ *b->id2node = (PMap(uint64_t)) MAP_INIT;
b->n_keys = 0;
- b->n_nodes = 0;
+ assert(b->n_nodes == 0);
}
-void marktree_free_node(mtnode_t *x)
+void marktree_free_subtree(MarkTree *b, MTNode *x)
{
if (x->level) {
for (int i = 0; i < x->n + 1; i++) {
- marktree_free_node(x->ptr[i]);
+ marktree_free_subtree(b, x->ptr[i]);
}
}
- xfree(x);
+ marktree_free_node(b, x);
}
-/// NB: caller must check not pair!
-void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key)
+static void marktree_free_node(MarkTree *b, MTNode *x)
{
- // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms
- // once we upgrade to a non-broken version of gcc in functionaltest-lua CI
- rawkey(itr).flags = (uint16_t)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_DECOR_MASK);
- rawkey(itr).flags = (uint16_t)(rawkey(itr).flags
- | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET)
- | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK));
- rawkey(itr).decor_full = key.decor_full;
- rawkey(itr).hl_id = key.hl_id;
- rawkey(itr).priority = key.priority;
+ kvi_destroy(x->intersect);
+ xfree(x);
+ b->n_nodes--;
}
+/// @param itr iterator is invalid after call
void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col)
{
- mtkey_t key = rawkey(itr);
- // TODO(bfredl): optimize when moving a mark within a leaf without moving it
- // across neighbours!
- marktree_del_itr(b, itr, false);
- key.pos = (mtpos_t){ row, col };
+ MTKey key = rawkey(itr);
+ MTNode *x = itr->x;
+ if (!x->level) {
+ bool internal = false;
+ MTPos newpos = MTPos(row, col);
+ if (x->parent != NULL) {
+ // strictly _after_ key before `x`
+ // (not optimal when x is very first leaf of the entire tree, but that's fine)
+ if (pos_less(itr->pos, newpos)) {
+ relative(itr->pos, &newpos);
+
+ // strictly before the end of x. (this could be made sharper by
+ // finding the internal key just after x, but meh)
+ if (pos_less(newpos, x->key[x->n - 1].pos)) {
+ internal = true;
+ }
+ }
+ } else {
+ // tree is one node. newpos thus is already "relative" itr->pos
+ internal = true;
+ }
+
+ if (internal) {
+ if (key.pos.row == newpos.row && key.pos.col == newpos.col) {
+ return;
+ }
+ key.pos = newpos;
+ bool match;
+ // tricky: could minimize movement in either direction better
+ int new_i = marktree_getp_aux(x, key, &match);
+ if (!match) {
+ new_i++;
+ }
+ if (new_i == itr->i) {
+ x->key[itr->i].pos = newpos;
+ } else if (new_i < itr->i) {
+ memmove(&x->key[new_i + 1], &x->key[new_i], sizeof(MTKey) * (size_t)(itr->i - new_i));
+ x->key[new_i] = key;
+ } else if (new_i > itr->i) {
+ memmove(&x->key[itr->i], &x->key[itr->i + 1], sizeof(MTKey) * (size_t)(new_i - itr->i - 1));
+ x->key[new_i - 1] = key;
+ }
+ return;
+ }
+ }
+ uint64_t other = marktree_del_itr(b, itr, false);
+ key.pos = (MTPos){ row, col };
marktree_put_key(b, key);
- itr->node = NULL; // itr might become invalid by put
+
+ if (other) {
+ marktree_restore_pair(b, key);
+ }
+ itr->x = NULL; // itr might become invalid by put
+}
+
+void marktree_restore_pair(MarkTree *b, MTKey key)
+{
+ MarkTreeIter itr[1];
+ MarkTreeIter end_itr[1];
+ marktree_lookup(b, mt_lookup_key_side(key, false), itr);
+ marktree_lookup(b, mt_lookup_key_side(key, true), end_itr);
+ if (!itr->x || !end_itr->x) {
+ // this could happen if the other end is waiting to be restored later
+ // this function will be called again for the other end.
+ return;
+ }
+ rawkey(itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+ rawkey(end_itr).flags &= (uint16_t) ~MT_FLAG_ORPHANED;
+
+ marktree_intersect_pair(b, mt_lookup_key_side(key, false), itr, end_itr, false);
}
// itr functions
-// TODO(bfredl): static inline?
bool marktree_itr_get(MarkTree *b, int32_t row, int col, MarkTreeIter *itr)
{
- return marktree_itr_get_ext(b, (mtpos_t){ row, col },
- itr, false, false, NULL);
+ return marktree_itr_get_ext(b, MTPos(row, col), itr, false, false, NULL);
}
-bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity,
- mtpos_t *oldbase)
+bool marktree_itr_get_ext(MarkTree *b, MTPos p, MarkTreeIter *itr, bool last, bool gravity,
+ MTPos *oldbase)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
+ MTKey k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 };
if (last && !gravity) {
k.flags = MT_FLAG_LAST;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ itr->x = b->root;
itr->lvl = 0;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
}
while (true) {
- itr->i = marktree_getp_aux(itr->node, k, 0) + 1;
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -622,10 +1251,10 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
itr->s[itr->lvl].oldcol = itr->pos.col;
if (itr->i > 0) {
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
- relative(itr->node->key[itr->i - 1].pos, &k.pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &k.pos);
}
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
if (oldbase) {
oldbase[itr->lvl] = itr->pos;
@@ -634,7 +1263,7 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
if (last) {
return marktree_itr_prev(b, itr);
- } else if (itr->i >= itr->node->n) {
+ } else if (itr->i >= itr->x->n) {
return marktree_itr_next(b, itr);
}
return true;
@@ -642,19 +1271,20 @@ bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last,
bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
{
- itr->node = b->root;
if (b->n_keys == 0) {
+ itr->x = NULL;
return false;
}
+ itr->x = b->root;
itr->i = 0;
itr->lvl = 0;
- itr->pos = (mtpos_t){ 0, 0 };
- while (itr->node->level > 0) {
+ itr->pos = MTPos(0, 0);
+ while (itr->x->level > 0) {
itr->s[itr->lvl].i = 0;
itr->s[itr->lvl].oldcol = 0;
itr->lvl++;
- itr->node = itr->node->ptr[0];
+ itr->x = itr->x->ptr[0];
}
return true;
}
@@ -663,16 +1293,16 @@ bool marktree_itr_first(MarkTree *b, MarkTreeIter *itr)
int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
{
if (b->n_keys == 0) {
- itr->node = NULL;
+ itr->x = NULL;
return false;
}
- itr->pos = (mtpos_t){ 0, 0 };
- itr->node = b->root;
+ itr->pos = MTPos(0, 0);
+ itr->x = b->root;
itr->lvl = 0;
while (true) {
- itr->i = itr->node->n;
+ itr->i = itr->x->n;
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
break;
}
@@ -680,63 +1310,71 @@ int marktree_itr_last(MarkTree *b, MarkTreeIter *itr)
itr->s[itr->lvl].oldcol = itr->pos.col;
assert(itr->i > 0);
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
- itr->node = itr->node->ptr[itr->i];
+ itr->x = itr->x->ptr[itr->i];
itr->lvl++;
}
itr->i--;
return true;
}
-// TODO(bfredl): static inline
bool marktree_itr_next(MarkTree *b, MarkTreeIter *itr)
{
- return marktree_itr_next_skip(b, itr, false, NULL);
+ return marktree_itr_next_skip(b, itr, false, false, NULL);
}
-static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mtpos_t oldbase[])
+static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, bool preload,
+ MTPos oldbase[])
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
itr->i++;
- if (itr->node->level == 0 || skip) {
- if (itr->i < itr->node->n) {
+ if (itr->x->level == 0 || skip) {
+ if (preload && itr->x->level == 0 && skip) {
+ // skip rest of this leaf node
+ itr->i = itr->x->n;
+ } else if (itr->i < itr->x->n) {
// TODO(bfredl): this is the common case,
// and could be handled by inline wrapper
return true;
}
// we ran out of non-internal keys. Go up until we find an internal key
- while (itr->i >= itr->node->n) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ while (itr->i >= itr->x->n) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i;
if (itr->i > 0) {
- itr->pos.row -= itr->node->key[itr->i - 1].pos.row;
+ itr->pos.row -= itr->x->key[itr->i - 1].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the first non-internal
// key after it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child after
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
if (oldbase && itr->i == 0) {
oldbase[itr->lvl + 1] = oldbase[itr->lvl];
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = 0;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
itr->lvl++;
+ itr->x = itr->x->ptr[itr->i];
+ if (preload && itr->x->level) {
+ itr->i = -1;
+ break;
+ } else {
+ itr->i = 0;
+ }
}
}
return true;
@@ -744,10 +1382,10 @@ static bool marktree_itr_next_skip(MarkTree *b, MarkTreeIter *itr, bool skip, mt
bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
{
- if (!itr->node) {
+ if (!itr->x) {
return false;
}
- if (itr->node->level == 0) {
+ if (itr->x->level == 0) {
itr->i--;
if (itr->i >= 0) {
// TODO(bfredl): this is the common case,
@@ -756,30 +1394,30 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
}
// we ran out of non-internal keys. Go up until we find a non-internal key
while (itr->i < 0) {
- itr->node = itr->node->parent;
- if (itr->node == NULL) {
+ itr->x = itr->x->parent;
+ if (itr->x == NULL) {
return false;
}
itr->lvl--;
itr->i = itr->s[itr->lvl].i - 1;
if (itr->i >= 0) {
- itr->pos.row -= itr->node->key[itr->i].pos.row;
+ itr->pos.row -= itr->x->key[itr->i].pos.row;
itr->pos.col = itr->s[itr->lvl].oldcol;
}
}
} else {
// we stood at an "internal" key. Go down to the last non-internal
// key before it.
- while (itr->node->level > 0) {
+ while (itr->x->level > 0) {
// internal key, there is always a child before
if (itr->i > 0) {
itr->s[itr->lvl].oldcol = itr->pos.col;
- compose(&itr->pos, itr->node->key[itr->i - 1].pos);
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
}
itr->s[itr->lvl].i = itr->i;
- assert(itr->node->ptr[itr->i]->parent == itr->node);
- itr->node = itr->node->ptr[itr->i];
- itr->i = itr->node->n;
+ assert(itr->x->ptr[itr->i]->parent == itr->x);
+ itr->x = itr->x->ptr[itr->i];
+ itr->i = itr->x->n;
itr->lvl++;
}
itr->i--;
@@ -787,33 +1425,22 @@ bool marktree_itr_prev(MarkTree *b, MarkTreeIter *itr)
return true;
}
-void marktree_itr_rewind(MarkTree *b, MarkTreeIter *itr)
-{
- if (!itr->node) {
- return;
- }
- if (itr->node->level) {
- marktree_itr_prev(b, itr);
- }
- itr->i = 0;
-}
-
bool marktree_itr_node_done(MarkTreeIter *itr)
{
- return !itr->node || itr->i == itr->node->n - 1;
+ return !itr->x || itr->i == itr->x->n - 1;
}
-mtpos_t marktree_itr_pos(MarkTreeIter *itr)
+MTPos marktree_itr_pos(MarkTreeIter *itr)
{
- mtpos_t pos = rawkey(itr).pos;
+ MTPos pos = rawkey(itr).pos;
unrelative(itr->pos, &pos);
return pos;
}
-mtkey_t marktree_itr_current(MarkTreeIter *itr)
+MTKey marktree_itr_current(MarkTreeIter *itr)
{
- if (itr->node) {
- mtkey_t key = rawkey(itr);
+ if (itr->x) {
+ MTKey key = rawkey(itr);
key.pos = marktree_itr_pos(itr);
return key;
}
@@ -825,47 +1452,193 @@ static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2)
return (&rawkey(itr1) == &rawkey(itr2));
}
-static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2)
+/// Get all marks which overlaps the position (row,col)
+///
+/// After calling this function, use marktree_itr_step_overlap to step through
+/// one overlapping mark at a time, until it returns false
+///
+/// NOTE: It's possible to get all marks which overlaps a region (row,col) to (row_end,col_end)
+/// To do this, first call marktree_itr_get_overlap with the start position and
+/// keep calling marktree_itr_step_overlap until it returns false.
+/// After this, as a second loop, keep calling the marktree_itr_next() until
+/// the iterator is invalid or reaches past (row_end, col_end). In this loop,
+/// consider all "start" marks (and unpaired marks if relevant), but skip over
+/// all "end" marks, using mt_end(mark).
+///
+/// @return false if we already know no marks can be found
+/// even if "true" the first call to marktree_itr_step_overlap
+/// could return false
+bool marktree_itr_get_overlap(MarkTree *b, int row, int col, MarkTreeIter *itr)
+{
+ if (b->n_keys == 0) {
+ itr->x = NULL;
+ return false;
+ }
+
+ itr->x = b->root;
+ itr->i = -1;
+ itr->lvl = 0;
+ itr->pos = MTPos(0, 0);
+ itr->intersect_pos = MTPos(row, col);
+ // intersect_pos but will be adjusted relative itr->x
+ itr->intersect_pos_x = MTPos(row, col);
+ itr->intersect_idx = 0;
+ return true;
+}
+
+/// Step through all overlapping pairs at a position.
+///
+/// This function must only be used with an iterator from |marktree_itr_step_overlap|
+///
+/// @return true if a valid pair was found (returned as `pair`)
+/// When all overlapping mark pairs have been found, false will be returned. `itr`
+/// is then valid as an ordinary iterator at the (row, col) position specified in
+/// marktree_itr_step_overlap
+bool marktree_itr_step_overlap(MarkTree *b, MarkTreeIter *itr, MTPair *pair)
+{
+ // phase one: we start at the root node and step inwards towards itr->intersect_pos
+ // (the position queried in marktree_itr_get_overlap)
+ //
+ // For each node (ancestor node to the node containing the sought position)
+ // we return all intersecting intervals, one at a time
+ while (itr->i == -1) {
+ if (itr->intersect_idx < kv_size(itr->x->intersect)) {
+ uint64_t id = kv_A(itr->x->intersect, itr->intersect_idx++);
+ *pair = mtpair_from(marktree_lookup(b, id, NULL),
+ marktree_lookup(b, id|MARKTREE_END_FLAG, NULL));
+ return true;
+ }
+
+ if (itr->x->level == 0) {
+ itr->s[itr->lvl].i = itr->i = 0;
+ break;
+ }
+
+ MTKey k = { .pos = itr->intersect_pos_x, .flags = 0 };
+ itr->i = marktree_getp_aux(itr->x, k, 0) + 1;
+
+ itr->s[itr->lvl].i = itr->i;
+ itr->s[itr->lvl].oldcol = itr->pos.col;
+
+ if (itr->i > 0) {
+ compose(&itr->pos, itr->x->key[itr->i - 1].pos);
+ relative(itr->x->key[itr->i - 1].pos, &itr->intersect_pos_x);
+ }
+ itr->x = itr->x->ptr[itr->i];
+ itr->lvl++;
+ itr->i = -1;
+ itr->intersect_idx = 0;
+ }
+
+ // phase two: we now need to handle the node found at itr->intersect_pos
+ // first consider all start nodes in the node before this position.
+ while (itr->i < itr->x->n && pos_less(rawkey(itr).pos, itr->intersect_pos_x)) {
+ MTKey k = itr->x->key[itr->i++];
+ itr->s[itr->lvl].i = itr->i;
+ if (mt_start(k)) {
+ MTKey end = marktree_lookup(b, mt_lookup_id(k.ns, k.id, true), NULL);
+ if (pos_less(end.pos, itr->intersect_pos)) {
+ continue;
+ }
+
+ unrelative(itr->pos, &k.pos);
+ *pair = mtpair_from(k, end);
+ return true; // it's a start!
+ }
+ }
+
+ // phase 2B: We also need to step to the end of this node and consider all end marks, which
+ // might end an interval overlapping itr->intersect_pos
+ while (itr->i < itr->x->n) {
+ MTKey k = itr->x->key[itr->i++];
+ if (mt_end(k)) {
+ uint64_t id = mt_lookup_id(k.ns, k.id, false);
+ if (id2node(b, id) == itr->x) {
+ continue;
+ }
+ unrelative(itr->pos, &k.pos);
+ MTKey start = marktree_lookup(b, id, NULL);
+ if (pos_less(itr->intersect_pos, start.pos)) {
+ continue;
+ }
+ *pair = mtpair_from(start, k);
+ return true; // end of a range which began before us!
+ }
+ }
+
+ // when returning false, get back to the queried position, to ensure the caller
+ // can keep using it as an ordinary iterator at the queried position. The docstring
+ // for marktree_itr_get_overlap explains how this is useful.
+ itr->i = itr->s[itr->lvl].i;
+ assert(itr->i >= 0);
+ if (itr->i >= itr->x->n) {
+ marktree_itr_next(b, itr);
+ }
+
+ // either on or after the intersected position, bail out
+ return false;
+}
+
+static void swap_keys(MarkTree *b, MarkTreeIter *itr1, MarkTreeIter *itr2, DamageList *damage)
{
- mtkey_t key1 = rawkey(itr1);
- mtkey_t key2 = rawkey(itr2);
+ if (itr1->x != itr2->x) {
+ if (mt_paired(rawkey(itr1))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr1)), itr1->x, itr2->x,
+ itr1->i, itr2->i }));
+ }
+ if (mt_paired(rawkey(itr2))) {
+ kvi_push(*damage, ((Damage){ mt_lookup_key(rawkey(itr2)), itr2->x, itr1->x,
+ itr2->i, itr1->i }));
+ }
+ }
+
+ MTKey key1 = rawkey(itr1);
+ MTKey key2 = rawkey(itr2);
rawkey(itr1) = key2;
rawkey(itr1).pos = key1.pos;
rawkey(itr2) = key1;
rawkey(itr2).pos = key2.pos;
+ refkey(b, itr1->x, itr1->i);
+ refkey(b, itr2->x, itr2->i);
+}
+
+static int damage_cmp(const void *s1, const void *s2)
+{
+ Damage *d1 = (Damage *)s1, *d2 = (Damage *)s2;
+ assert(d1->id != d2->id);
+ return d1->id > d2->id ? 1 : -1;
}
bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_extent_line,
int old_extent_col, int new_extent_line, int new_extent_col)
{
- mtpos_t start = { start_line, start_col };
- mtpos_t old_extent = { old_extent_line, old_extent_col };
- mtpos_t new_extent = { new_extent_line, new_extent_col };
+ MTPos start = { start_line, start_col };
+ MTPos old_extent = { old_extent_line, old_extent_col };
+ MTPos new_extent = { new_extent_line, new_extent_col };
bool may_delete = (old_extent.row != 0 || old_extent.col != 0);
bool same_line = old_extent.row == 0 && new_extent.row == 0;
unrelative(start, &old_extent);
unrelative(start, &new_extent);
- MarkTreeIter itr[1] = { 0 };
- MarkTreeIter enditr[1] = { 0 };
+ MarkTreeIter itr[1] = { 0 }, enditr[1] = { 0 };
- mtpos_t oldbase[MT_MAX_DEPTH] = { 0 };
+ MTPos oldbase[MT_MAX_DEPTH] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, oldbase);
- if (!itr->node) {
+ if (!itr->x) {
// den e FÄRDIG
return false;
}
- mtpos_t delta = { new_extent.row - old_extent.row,
- new_extent.col - old_extent.col };
+ MTPos delta = { new_extent.row - old_extent.row,
+ new_extent.col - old_extent.col };
if (may_delete) {
- mtpos_t ipos = marktree_itr_pos(itr);
+ MTPos ipos = marktree_itr_pos(itr);
if (!pos_leq(old_extent, ipos)
|| (old_extent.row == ipos.row && old_extent.col == ipos.col
&& !mt_right(rawkey(itr)))) {
marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL);
- assert(enditr->node);
+ assert(enditr->x);
// "assert" (itr <= enditr)
} else {
may_delete = false;
@@ -874,14 +1647,16 @@ bool marktree_splice(MarkTree *b, int32_t start_line, int start_col, int old_ext
bool past_right = false;
bool moved = false;
+ DamageList damage;
+ kvi_init(damage);
// Follow the general strategy of messing things up and fix them later
// "oldbase" carries the information needed to calculate old position of
// children.
if (may_delete) {
- while (itr->node && !past_right) {
- mtpos_t loc_start = start;
- mtpos_t loc_old = old_extent;
+ while (itr->x && !past_right) {
+ MTPos loc_start = start;
+ MTPos loc_old = old_extent;
relative(itr->pos, &loc_start);
relative(oldbase[itr->lvl], &loc_old);
@@ -899,9 +1674,7 @@ continue_same_node:
marktree_itr_prev(b, enditr);
}
if (!mt_right(rawkey(enditr))) {
- itr_swap(itr, enditr);
- refkey(b, itr->node, itr->i);
- refkey(b, enditr->node, enditr->i);
+ swap_keys(b, itr, enditr, &damage);
} else {
past_right = true; // NOLINT
(void)past_right;
@@ -915,14 +1688,14 @@ continue_same_node:
}
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = rawkey(itr).pos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
rawkey(itr).pos = loc_start;
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
rawkey(itr).pos = loc_start;
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
if (!past_right) {
goto continue_same_node;
@@ -932,10 +1705,10 @@ continue_same_node:
}
}
}
- while (itr->node) {
- mtpos_t loc_new = new_extent;
+ while (itr->x) {
+ MTPos loc_new = new_extent;
relative(itr->pos, &loc_new);
- mtpos_t limit = old_extent;
+ MTPos limit = old_extent;
relative(oldbase[itr->lvl], &limit);
@@ -945,16 +1718,16 @@ past_continue_same_node:
break;
}
- mtpos_t oldpos = rawkey(itr).pos;
+ MTPos oldpos = rawkey(itr).pos;
rawkey(itr).pos = loc_new;
moved = true;
- if (itr->node->level) {
+ if (itr->x->level) {
oldbase[itr->lvl + 1] = oldpos;
unrelative(oldbase[itr->lvl], &oldbase[itr->lvl + 1]);
- marktree_itr_next_skip(b, itr, false, oldbase);
+ marktree_itr_next_skip(b, itr, false, false, oldbase);
} else {
- if (itr->i < itr->node->n - 1) {
+ if (itr->i < itr->x->n - 1) {
itr->i++;
goto past_continue_same_node;
} else {
@@ -964,7 +1737,7 @@ past_continue_same_node:
}
}
- while (itr->node) {
+ while (itr->x) {
unrelative(oldbase[itr->lvl], &rawkey(itr).pos);
int realrow = rawkey(itr).pos.row;
assert(realrow >= old_extent.row);
@@ -972,7 +1745,6 @@ past_continue_same_node:
if (realrow == old_extent.row) {
if (delta.col) {
rawkey(itr).pos.col += delta.col;
- moved = true;
}
} else {
if (same_line) {
@@ -988,22 +1760,79 @@ past_continue_same_node:
if (done) {
break;
}
- marktree_itr_next_skip(b, itr, true, NULL);
+ marktree_itr_next_skip(b, itr, true, false, NULL);
+ }
+
+ if (kv_size(damage)) {
+ // TODO(bfredl): a full sort is not really needed. we just need a "start" node to find
+ // its corresponding "end" node. Set up some dedicated hash for this later (c.f. the
+ // "grow only" variant of khash_t branch)
+ qsort((void *)&kv_A(damage, 0), kv_size(damage), sizeof(kv_A(damage, 0)),
+ damage_cmp);
+
+ for (size_t i = 0; i < kv_size(damage); i++) {
+ Damage d = kv_A(damage, i);
+ assert(i == 0 || d.id > kv_A(damage, i - 1).id);
+ if (!(d.id & MARKTREE_END_FLAG)) { // start
+ if (i + 1 < kv_size(damage) && kv_A(damage, i + 1).id == (d.id | MARKTREE_END_FLAG)) {
+ Damage d2 = kv_A(damage, i + 1);
+
+ // pair
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ marktree_itr_set_node(b, enditr, d2.old, d2.old_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ marktree_itr_set_node(b, enditr, d2.new, d2.new_i);
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+
+ i++; // consume two items
+ continue;
+ }
+
+ // d is lone start, end didn't move
+ MarkTreeIter endpos[1];
+ marktree_lookup(b, d.id | MARKTREE_END_FLAG, endpos);
+ if (endpos->x) {
+ marktree_itr_set_node(b, itr, d.old, d.old_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, true);
+ marktree_itr_set_node(b, itr, d.new, d.new_i);
+ *enditr = *endpos;
+ marktree_intersect_pair(b, d.id, itr, enditr, false);
+ }
+ } else {
+ // d is lone end, start didn't move
+ MarkTreeIter startpos[1];
+ uint64_t start_id = d.id & ~MARKTREE_END_FLAG;
+
+ marktree_lookup(b, start_id, startpos);
+ if (startpos->x) {
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.old, d.old_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, true);
+ *itr = *startpos;
+ marktree_itr_set_node(b, enditr, d.new, d.new_i);
+ marktree_intersect_pair(b, start_id, itr, enditr, false);
+ }
+ }
+ }
}
+ kvi_destroy(damage);
+
return moved;
}
void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int extent_row,
colnr_T extent_col, int new_row, colnr_T new_col)
{
- mtpos_t start = { start_row, start_col }, size = { extent_row, extent_col };
- mtpos_t end = size;
+ MTPos start = { start_row, start_col }, size = { extent_row, extent_col };
+ MTPos end = size;
unrelative(start, &end);
MarkTreeIter itr[1] = { 0 };
marktree_itr_get_ext(b, start, itr, false, true, NULL);
- kvec_t(mtkey_t) saved = KV_INITIAL_VALUE;
- while (itr->node) {
- mtkey_t k = marktree_itr_current(itr);
+ kvec_t(MTKey) saved = KV_INITIAL_VALUE;
+ while (itr->x) {
+ MTKey k = marktree_itr_current(itr);
if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col
&& mt_right(k))) {
break;
@@ -1014,57 +1843,101 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext
}
marktree_splice(b, start.row, start.col, size.row, size.col, 0, 0);
- mtpos_t new = { new_row, new_col };
+ MTPos new = { new_row, new_col };
marktree_splice(b, new.row, new.col,
0, 0, size.row, size.col);
for (size_t i = 0; i < kv_size(saved); i++) {
- mtkey_t item = kv_A(saved, i);
+ MTKey item = kv_A(saved, i);
unrelative(new, &item.pos);
marktree_put_key(b, item);
+ if (mt_paired(item)) {
+ // other end might be later in `saved`, this will safely bail out then
+ marktree_restore_pair(b, item);
+ }
}
kv_destroy(saved);
}
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
+MTKey marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr)
{
return marktree_lookup(b, mt_lookup_id(ns, id, end), itr);
}
+static uint64_t pseudo_index(MTNode *x, int i)
+{
+ int off = MT_LOG2_BRANCH * x->level;
+ uint64_t index = 0;
+
+ while (x) {
+ index |= (uint64_t)(i + 1) << off;
+ off += MT_LOG2_BRANCH;
+ i = x->p_idx;
+ x = x->parent;
+ }
+
+ return index;
+}
+
+/// @param itr OPTIONAL. set itr to pos.
+/// if sloppy, two keys at the same _leaf_ node has the same index
+static uint64_t pseudo_index_for_id(MarkTree *b, uint64_t id, bool sloppy)
+{
+ MTNode *n = id2node(b, id);
+ if (n == NULL) {
+ return 0; // a valid pseudo-index is never zero!
+ }
+
+ int i = 0;
+ if (n->level || !sloppy) {
+ for (i = 0; i < n->n; i++) {
+ if (mt_lookup_key(n->key[i]) == id) {
+ break;
+ }
+ }
+ assert(i < n->n);
+ if (n->level) {
+ i += 1; // internal key i comes after ptr[i]
+ }
+ }
+
+ return pseudo_index(n, i);
+}
+
/// @param itr OPTIONAL. set itr to pos.
-mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
+MTKey marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr)
{
- mtnode_t *n = pmap_get(uint64_t)(b->id2node, id);
+ MTNode *n = id2node(b, id);
if (n == NULL) {
if (itr) {
- itr->node = NULL;
+ itr->x = NULL;
}
return MT_INVALID_KEY;
}
int i = 0;
for (i = 0; i < n->n; i++) {
if (mt_lookup_key(n->key[i]) == id) {
- goto found;
+ return marktree_itr_set_node(b, itr, n, i);
}
}
+
abort();
-found: {}
- mtkey_t key = n->key[i];
+}
+
+MTKey marktree_itr_set_node(MarkTree *b, MarkTreeIter *itr, MTNode *n, int i)
+{
+ MTKey key = n->key[i];
if (itr) {
itr->i = i;
- itr->node = n;
+ itr->x = n;
itr->lvl = b->root->level - n->level;
}
while (n->parent != NULL) {
- mtnode_t *p = n->parent;
- for (i = 0; i < p->n + 1; i++) {
- if (p->ptr[i] == n) {
- goto found_node;
- }
- }
- abort();
-found_node:
+ MTNode *p = n->parent;
+ i = n->p_idx;
+ assert(p->ptr[i] == n);
+
if (itr) {
itr->s[b->root->level - p->level].i = i;
}
@@ -1079,14 +1952,14 @@ found_node:
return key;
}
-mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTPos marktree_get_altpos(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
return marktree_get_alt(b, mark, itr).pos;
}
-mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
+MTKey marktree_get_alt(MarkTree *b, MTKey mark, MarkTreeIter *itr)
{
- mtkey_t end = MT_INVALID_KEY;
+ MTKey end = MT_INVALID_KEY;
if (mt_paired(mark)) {
end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr);
}
@@ -1095,8 +1968,8 @@ mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr)
static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
{
- itr->pos = (mtpos_t){ 0, 0 };
- mtnode_t *x = b->root;
+ itr->pos = (MTPos){ 0, 0 };
+ MTNode *x = b->root;
for (int lvl = 0; lvl < itr->lvl; lvl++) {
itr->s[lvl].oldcol = itr->pos.col;
int i = itr->s[lvl].i;
@@ -1106,23 +1979,36 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
assert(x->level);
x = x->ptr[i];
}
- assert(x == itr->node);
+ assert(x == itr->x);
}
// for unit test
-void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity)
+void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity,
+ int end_row, int end_col, bool end_right)
{
- mtkey_t key = { { row, col }, UINT32_MAX, id, 0,
- mt_flags(right_gravity, 0), 0, NULL };
- marktree_put(b, key, -1, -1, false);
+ uint16_t flags = mt_flags(right_gravity, false, false, false);
+ MTKey key = { { row, col }, ns, id, flags, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } };
+ marktree_put(b, key, end_row, end_col, end_right);
}
// for unit test
-bool mt_right_test(mtkey_t key)
+bool mt_right_test(MTKey key)
{
return mt_right(key);
}
+// for unit test
+void marktree_del_pair_test(MarkTree *b, uint32_t ns, uint32_t id)
+{
+ MarkTreeIter itr[1];
+ marktree_lookup_ns(b, ns, id, false, itr);
+
+ uint64_t other = marktree_del_itr(b, itr, false);
+ assert(other);
+ marktree_lookup(b, other, itr);
+ marktree_del_itr(b, itr, false);
+}
+
void marktree_check(MarkTree *b)
{
#ifndef NDEBUG
@@ -1133,9 +2019,9 @@ void marktree_check(MarkTree *b)
return;
}
- mtpos_t dummy;
+ MTPos dummy;
bool last_right = false;
- size_t nkeys = check_node(b, b->root, &dummy, &last_right);
+ size_t nkeys = marktree_check_node(b, b->root, &dummy, &last_right);
assert(b->n_keys == nkeys);
assert(b->n_keys == map_size(b->id2node));
#else
@@ -1145,7 +2031,7 @@ void marktree_check(MarkTree *b)
}
#ifndef NDEBUG
-static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_right)
+size_t marktree_check_node(MarkTree *b, MTNode *x, MTPos *last, bool *last_right)
{
assert(x->n <= 2 * T - 1);
// TODO(bfredl): too strict if checking "in repair" post-delete tree.
@@ -1154,9 +2040,9 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
for (int i = 0; i < x->n; i++) {
if (x->level) {
- n_keys += check_node(b, x->ptr[i], last, last_right);
+ n_keys += marktree_check_node(b, x->ptr[i], last, last_right);
} else {
- *last = (mtpos_t) { 0, 0 };
+ *last = (MTPos) { 0, 0 };
}
if (i > 0) {
unrelative(x->key[i - 1].pos, last);
@@ -1171,11 +2057,12 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
}
if (x->level) {
- n_keys += check_node(b, x->ptr[x->n], last, last_right);
+ n_keys += marktree_check_node(b, x->ptr[x->n], last, last_right);
unrelative(x->key[x->n - 1].pos, last);
for (int i = 0; i < x->n + 1; i++) {
assert(x->ptr[i]->parent == x);
+ assert(x->ptr[i]->p_idx == i);
assert(x->ptr[i]->level == x->level - 1);
// PARANOIA: check no double node ref
for (int j = 0; j < i; j++) {
@@ -1187,34 +2074,221 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig
}
return n_keys;
}
+
+bool marktree_check_intersections(MarkTree *b)
+{
+ if (!b->root) {
+ return true;
+ }
+ PMap(ptr_t) checked = MAP_INIT;
+
+ // 1. move x->intersect to checked[x] and reinit x->intersect
+ mt_recurse_nodes(b->root, &checked);
+
+ // 2. iterate over all marks. for each START mark of a pair,
+ // intersect the nodes between the pair
+ MarkTreeIter itr[1];
+ marktree_itr_first(b, itr);
+ while (true) {
+ MTKey mark = marktree_itr_current(itr);
+ if (mark.pos.row < 0) {
+ break;
+ }
+
+ if (mt_start(mark)) {
+ MarkTreeIter start_itr[1];
+ MarkTreeIter end_itr[1];
+ uint64_t end_id = mt_lookup_id(mark.ns, mark.id, true);
+ MTKey k = marktree_lookup(b, end_id, end_itr);
+ if (k.pos.row >= 0) {
+ *start_itr = *itr;
+ marktree_intersect_pair(b, mt_lookup_key(mark), start_itr, end_itr, false);
+ }
+ }
+
+ marktree_itr_next(b, itr);
+ }
+
+ // 3. for each node check if the recreated intersection
+ // matches the old checked[x] intersection.
+ bool status = mt_recurse_nodes_compare(b->root, &checked);
+
+ uint64_t *val;
+ map_foreach_value(&checked, val, {
+ xfree(val);
+ });
+ map_destroy(ptr_t, &checked);
+
+ return status;
+}
+
+void mt_recurse_nodes(MTNode *x, PMap(ptr_t) *checked)
+{
+ if (kv_size(x->intersect)) {
+ kvi_push(x->intersect, (uint64_t)-1); // sentinel
+ uint64_t *val;
+ if (x->intersect.items == x->intersect.init_array) {
+ val = xmemdup(x->intersect.items, x->intersect.size * sizeof(*x->intersect.items));
+ } else {
+ val = x->intersect.items;
+ }
+ pmap_put(ptr_t)(checked, x, val);
+ kvi_init(x->intersect);
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ mt_recurse_nodes(x->ptr[i], checked);
+ }
+ }
+}
+
+bool mt_recurse_nodes_compare(MTNode *x, PMap(ptr_t) *checked)
+{
+ uint64_t *ref = pmap_get(ptr_t)(checked, x);
+ if (ref != NULL) {
+ for (size_t i = 0;; i++) {
+ if (ref[i] == (uint64_t)-1) {
+ if (i != kv_size(x->intersect)) {
+ return false;
+ }
+
+ break;
+ } else {
+ if (kv_size(x->intersect) <= i || ref[i] != kv_A(x->intersect, i)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ if (kv_size(x->intersect)) {
+ return false;
+ }
+ }
+
+ if (x->level) {
+ for (int i = 0; i < x->n + 1; i++) {
+ if (!mt_recurse_nodes_compare(x->ptr[i], checked)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
#endif
-char *mt_inspect_rec(MarkTree *b)
+// TODO(bfredl): kv_print
+#define GA_PUT(x) ga_concat(ga, (char *)(x))
+#define GA_PRINT(fmt, ...) snprintf(buf, sizeof(buf), fmt, __VA_ARGS__); \
+ GA_PUT(buf);
+
+String mt_inspect(MarkTree *b, bool keys, bool dot)
{
- garray_T ga;
- ga_init(&ga, (int)sizeof(char), 80);
- mtpos_t p = { 0, 0 };
- mt_inspect_node(b, &ga, b->root, p);
- return ga.ga_data;
+ garray_T ga[1];
+ ga_init(ga, (int)sizeof(char), 80);
+ MTPos p = { 0, 0 };
+ if (b->root) {
+ if (dot) {
+ GA_PUT("digraph D {\n\n");
+ mt_inspect_dotfile_node(b, ga, b->root, p, NULL);
+ GA_PUT("\n}");
+ } else {
+ mt_inspect_node(b, ga, keys, b->root, p);
+ }
+ }
+ return ga_take_string(ga);
}
-void mt_inspect_node(MarkTree *b, garray_T *ga, mtnode_t *n, mtpos_t off)
+void mt_inspect_node(MarkTree *b, garray_T *ga, bool keys, MTNode *n, MTPos off)
{
static char buf[1024];
- ga_concat(ga, "[");
+ GA_PUT("[");
+ if (keys && kv_size(n->intersect)) {
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ GA_PUT(i == 0 ? "{" : ";");
+ // GA_PRINT("%"PRIu64, kv_A(n->intersect, i));
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("},");
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[0], off);
+ mt_inspect_node(b, ga, keys, n->ptr[0], off);
}
for (int i = 0; i < n->n; i++) {
- mtpos_t p = n->key[i].pos;
+ MTPos p = n->key[i].pos;
unrelative(off, &p);
- snprintf((char *)buf, sizeof(buf), "%d/%d", p.row, p.col);
- ga_concat(ga, buf);
+ GA_PRINT("%d/%d", p.row, p.col);
+ if (keys) {
+ MTKey key = n->key[i];
+ GA_PUT(":");
+ if (mt_start(key)) {
+ GA_PUT("<");
+ }
+ // GA_PRINT("%"PRIu64, mt_lookup_id(key.ns, key.id, false));
+ GA_PRINT("%" PRIu32, key.id);
+ if (mt_end(key)) {
+ GA_PUT(">");
+ }
+ }
if (n->level) {
- mt_inspect_node(b, ga, n->ptr[i + 1], p);
+ mt_inspect_node(b, ga, keys, n->ptr[i + 1], p);
} else {
ga_concat(ga, ",");
}
}
ga_concat(ga, "]");
}
+
+void mt_inspect_dotfile_node(MarkTree *b, garray_T *ga, MTNode *n, MTPos off, char *parent)
+{
+ static char buf[1024];
+ char namebuf[64];
+ if (parent != NULL) {
+ snprintf(namebuf, sizeof namebuf, "%s_%c%d", parent, 'a' + n->level, n->p_idx);
+ } else {
+ snprintf(namebuf, sizeof namebuf, "MTNode");
+ }
+
+ GA_PRINT(" %s[shape=plaintext, label=<\n", namebuf);
+ GA_PUT(" <table border='0' cellborder='1' cellspacing='0'>\n");
+ if (kv_size(n->intersect)) {
+ GA_PUT(" <tr><td>");
+ for (size_t i = 0; i < kv_size(n->intersect); i++) {
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%" PRIu64, mt_dbg_id(kv_A(n->intersect, i)));
+ }
+ GA_PUT("</td></tr>\n");
+ }
+
+ GA_PUT(" <tr><td>");
+ for (int i = 0; i < n->n; i++) {
+ MTKey k = n->key[i];
+ if (i > 0) {
+ GA_PUT(", ");
+ }
+ GA_PRINT("%d", k.id);
+ if (mt_paired(k)) {
+ GA_PUT(mt_end(k) ? "e" : "s");
+ }
+ }
+ GA_PUT("</td></tr>\n");
+ GA_PUT(" </table>\n");
+ GA_PUT(">];\n");
+ if (parent) {
+ GA_PRINT(" %s -> %s\n", parent, namebuf);
+ }
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[0], off, namebuf);
+ }
+ for (int i = 0; i < n->n; i++) {
+ MTPos p = n->key[i].pos;
+ unrelative(off, &p);
+ if (n->level) {
+ mt_inspect_dotfile_node(b, ga, n->ptr[i + 1], p, namebuf);
+ }
+ }
+}
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 5ce4b2cd24..c76359d3f9 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -1,140 +1,209 @@
-#ifndef NVIM_MARKTREE_H
-#define NVIM_MARKTREE_H
+#pragma once
-#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/assert.h"
-#include "nvim/garray.h"
-#include "nvim/map.h"
+#include "klib/kvec.h"
+#include "nvim/decoration_defs.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
#include "nvim/map_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-struct mtnode_s;
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+// only for debug functions:
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#define MT_MAX_DEPTH 20
#define MT_BRANCH_FACTOR 10
+// note max branch is actually 2*MT_BRANCH_FACTOR
+// and strictly this is ceil(log2(2*MT_BRANCH_FACTOR + 1))
+// as we need a pseudo-index for "right before this node"
+#define MT_LOG2_BRANCH 5
typedef struct {
int32_t row;
int32_t col;
-} mtpos_t;
+} MTPos;
+#define MTPos(r, c) ((MTPos){ .row = (r), .col = (c) })
-typedef struct mtnode_s mtnode_t;
-typedef struct {
- int oldcol;
- int i;
-} iterstate_t;
+typedef struct mtnode_s MTNode;
typedef struct {
- mtpos_t pos;
+ MTPos pos;
int lvl;
- mtnode_t *node;
+ MTNode *x;
int i;
- iterstate_t s[MT_MAX_DEPTH];
+ struct {
+ int oldcol;
+ int i;
+ } s[MT_MAX_DEPTH];
+
+ size_t intersect_idx;
+ MTPos intersect_pos;
+ MTPos intersect_pos_x;
} MarkTreeIter;
+#define marktree_itr_valid(itr) ((itr)->x != NULL)
+// access raw key: flags in MT_FLAG_EXTERNAL_MASK and decor_data are safe to modify.
+#define mt_itr_rawkey(itr) ((itr)->x->key[(itr)->i])
+
// Internal storage
//
// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for
// "space before (row,col)"
typedef struct {
- mtpos_t pos;
+ MTPos pos;
uint32_t ns;
uint32_t id;
- int32_t hl_id;
uint16_t flags;
- uint16_t priority;
- Decoration *decor_full;
-} mtkey_t;
-#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL }
+ DecorInlineData decor_data; // "ext" tag in flags
+} MTKey;
+
+typedef struct {
+ MTKey start;
+ MTPos end_pos;
+ bool end_right_gravity;
+} MTPair;
+
+#define MT_INVALID_KEY (MTKey) { { -1, -1 }, 0, 0, 0, { .hl = DECOR_HIGHLIGHT_INLINE_INIT } }
#define MT_FLAG_REAL (((uint16_t)1) << 0)
#define MT_FLAG_END (((uint16_t)1) << 1)
#define MT_FLAG_PAIRED (((uint16_t)1) << 2)
-#define MT_FLAG_HL_EOL (((uint16_t)1) << 3)
-
-#define DECOR_LEVELS 4
-#define MT_FLAG_DECOR_OFFSET 4
-#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS - 1)) << MT_FLAG_DECOR_OFFSET)
-
-// next flag is (((uint16_t)1) << 6)
+// orphaned: the other side of this paired mark was deleted. this mark must be deleted very soon!
+#define MT_FLAG_ORPHANED (((uint16_t)1) << 3)
+#define MT_FLAG_NO_UNDO (((uint16_t)1) << 4)
+#define MT_FLAG_INVALIDATE (((uint16_t)1) << 5)
+#define MT_FLAG_INVALID (((uint16_t)1) << 6)
+// discriminant for union
+#define MT_FLAG_DECOR_EXT (((uint16_t)1) << 7)
+
+// TODO(bfredl): flags for decorations. These cover the cases where we quickly needs
+// to skip over irrelevant marks internally. When we refactor this more, also make all info
+// for ExtmarkType included here
+#define MT_FLAG_DECOR_HL (((uint16_t)1) << 8)
+#define MT_FLAG_DECOR_SIGNTEXT (((uint16_t)1) << 9)
+// TODO(bfredl): for now this means specifically number_hl, line_hl, cursorline_hl
+// needs to clean up the name.
+#define MT_FLAG_DECOR_SIGNHL (((uint16_t)1) << 10)
+#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11)
+#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12)
// These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
#define MT_FLAG_LAST (((uint16_t)1) << 15)
-#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL)
+#define MT_FLAG_DECOR_MASK (MT_FLAG_DECOR_EXT| MT_FLAG_DECOR_HL | MT_FLAG_DECOR_SIGNTEXT \
+ | MT_FLAG_DECOR_SIGNHL | MT_FLAG_DECOR_VIRT_LINES \
+ | MT_FLAG_DECOR_VIRT_TEXT_INLINE)
-#define MARKTREE_END_FLAG (((uint64_t)1) << 63)
+#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \
+ | MT_FLAG_INVALIDATE | MT_FLAG_INVALID)
+
+// this is defined so that start and end of the same range have adjacent ids
+#define MARKTREE_END_FLAG ((uint64_t)1)
static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda)
{
- return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0);
+ return (uint64_t)ns << 33 | (id <<1) | (enda ? MARKTREE_END_FLAG : 0);
}
-#undef MARKTREE_END_FLAG
-static inline uint64_t mt_lookup_key(mtkey_t key)
+static inline uint64_t mt_lookup_key_side(MTKey key, bool end)
+{
+ return mt_lookup_id(key.ns, key.id, end);
+}
+
+static inline uint64_t mt_lookup_key(MTKey key)
{
return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END);
}
-static inline bool mt_paired(mtkey_t key)
+static inline bool mt_paired(MTKey key)
{
return key.flags & MT_FLAG_PAIRED;
}
-static inline bool mt_end(mtkey_t key)
+static inline bool mt_end(MTKey key)
{
return key.flags & MT_FLAG_END;
}
-static inline bool mt_start(mtkey_t key)
+static inline bool mt_start(MTKey key)
{
return mt_paired(key) && !mt_end(key);
}
-static inline bool mt_right(mtkey_t key)
+static inline bool mt_right(MTKey key)
{
return key.flags & MT_FLAG_RIGHT_GRAVITY;
}
-static inline uint8_t marktree_decor_level(mtkey_t key)
+static inline bool mt_no_undo(MTKey key)
+{
+ return key.flags & MT_FLAG_NO_UNDO;
+}
+
+static inline bool mt_invalidate(MTKey key)
+{
+ return key.flags & MT_FLAG_INVALIDATE;
+}
+
+static inline bool mt_invalid(MTKey key)
{
- return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET);
+ return key.flags & MT_FLAG_INVALID;
}
-static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level)
+static inline bool mt_decor_any(MTKey key)
+{
+ return key.flags & MT_FLAG_DECOR_MASK;
+}
+
+static inline bool mt_decor_sign(MTKey key)
+{
+ return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
+}
+
+static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext)
{
- assert(decor_level < DECOR_LEVELS);
return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
- | (decor_level << MT_FLAG_DECOR_OFFSET));
+ | (no_undo ? MT_FLAG_NO_UNDO : 0)
+ | (invalidate ? MT_FLAG_INVALIDATE : 0)
+ | (decor_ext ? MT_FLAG_DECOR_EXT : 0));
+}
+
+static inline MTPair mtpair_from(MTKey start, MTKey end)
+{
+ return (MTPair){ .start = start, .end_pos = end.pos, .end_right_gravity = mt_right(end) };
}
+static inline DecorInline mt_decor(MTKey key)
+{
+ return (DecorInline){ .ext = key.flags & MT_FLAG_DECOR_EXT, .data = key.decor_data };
+}
+
+typedef kvec_withinit_t(uint64_t, 4) Intersection;
+
struct mtnode_s {
int32_t n;
- int32_t level;
+ int16_t level;
+ int16_t p_idx; // index in parent
+ Intersection intersect;
// TODO(bfredl): we could consider having a only-sometimes-valid
// index into parent for faster "cached" lookup.
- mtnode_t *parent;
- mtkey_t key[2 * MT_BRANCH_FACTOR - 1];
- mtnode_t *ptr[];
+ MTNode *parent;
+ MTKey key[2 * MT_BRANCH_FACTOR - 1];
+ MTNode *ptr[];
};
-// TODO(bfredl): the iterator is pretty much everpresent, make it part of the
-// tree struct itself?
+static inline uint64_t mt_dbg_id(uint64_t id)
+{
+ return (id>>1)&0xffffffff;
+}
+
typedef struct {
- mtnode_t *root;
+ MTNode *root;
size_t n_keys, n_nodes;
- // TODO(bfredl): the pointer to node could be part of the larger
- // Map(uint64_t, ExtmarkItem) essentially;
PMap(uint64_t) id2node[1];
} MarkTree;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "marktree.h.generated.h"
#endif
-
-#endif // NVIM_MARKTREE_H
diff --git a/src/nvim/match.c b/src/nvim/match.c
index 6663dfd7ec..0a7c264d4f 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// match.c: functions for highlighting matches
#include <assert.h>
@@ -9,40 +6,40 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/match.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "match.c.generated.h"
#endif
-static char *e_invalwindow = N_("E957: Invalid window number");
+static const char *e_invalwindow = N_("E957: Invalid window number");
#define SEARCH_HL_PRIORITY 0
@@ -59,9 +56,6 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
list_T *pos_list, const char *const conceal_char)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- matchitem_T *cur;
- matchitem_T *prev;
- matchitem_T *m;
int hlg_id;
regprog_T *regprog = NULL;
int rtype = UPD_SOME_VALID;
@@ -80,7 +74,7 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
id = wp->w_next_match_id++;
} else {
// check the given ID is not already in use
- for (cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) {
+ for (matchitem_T *cur = wp->w_match_head; cur != NULL; cur = cur->mit_next) {
if (cur->mit_id == id) {
semsg(_("E801: ID already taken: %" PRId64), (int64_t)id);
return -1;
@@ -98,20 +92,20 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) {
return -1;
}
- if (pat != NULL && (regprog = vim_regcomp((char *)pat, RE_MAGIC)) == NULL) {
+ if (pat != NULL && (regprog = vim_regcomp(pat, RE_MAGIC)) == NULL) {
semsg(_(e_invarg2), pat);
return -1;
}
// Build new match.
- m = xcalloc(1, sizeof(matchitem_T));
+ matchitem_T *m = xcalloc(1, sizeof(matchitem_T));
if (pos_list != NULL) {
m->mit_pos_array = xcalloc((size_t)tv_list_len(pos_list), sizeof(llpos_T));
m->mit_pos_count = tv_list_len(pos_list);
}
m->mit_id = id;
m->mit_priority = prio;
- m->mit_pattern = pat == NULL ? NULL: xstrdup(pat);
+ m->mit_pattern = pat == NULL ? NULL : xstrdup(pat);
m->mit_hlg_id = hlg_id;
m->mit_match.regprog = regprog;
m->mit_match.rmm_ic = false;
@@ -215,8 +209,8 @@ static int match_add(win_T *wp, const char *const grp, const char *const pat, in
// Insert new match. The match list is in ascending order with regard to
// the match priorities.
- cur = wp->w_match_head;
- prev = cur;
+ matchitem_T *cur = wp->w_match_head;
+ matchitem_T *prev = cur;
while (cur != NULL && prio >= cur->mit_priority) {
prev = cur;
cur = cur->mit_next;
@@ -296,10 +290,8 @@ static int match_delete(win_T *wp, int id, bool perr)
/// Delete all matches in the match list of window 'wp'.
void clear_matches(win_T *wp)
{
- matchitem_T *m;
-
while (wp->w_match_head != NULL) {
- m = wp->w_match_head->mit_next;
+ matchitem_T *m = wp->w_match_head->mit_next;
vim_regfree(wp->w_match_head->mit_match.regprog);
xfree(wp->w_match_head->mit_pattern);
xfree(wp->w_match_head->mit_pos_array);
@@ -358,11 +350,10 @@ void init_search_hl(win_T *wp, match_T *search_hl)
static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, colnr_T mincol)
FUNC_ATTR_NONNULL_ALL
{
- int i;
int found = -1;
shl->lnum = 0;
- for (i = match->mit_pos_cur; i < match->mit_pos_count; i++) {
+ for (int i = match->mit_pos_cur; i < match->mit_pos_count; i++) {
llpos_T *pos = &match->mit_pos_array[i];
if (pos->lnum == 0) {
@@ -388,7 +379,7 @@ static int next_search_hl_pos(match_T *shl, linenr_T lnum, matchitem_T *match, c
match->mit_pos_cur = 0;
if (found >= 0) {
colnr_T start = match->mit_pos_array[found].col == 0
- ? 0: match->mit_pos_array[found].col - 1;
+ ? 0 : match->mit_pos_array[found].col - 1;
colnr_T end = match->mit_pos_array[found].col == 0
? MAXCOL : start + match->mit_pos_array[found].len;
@@ -419,9 +410,8 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
colnr_T mincol, matchitem_T *cur)
FUNC_ATTR_NONNULL_ARG(2)
{
- linenr_T l;
colnr_T matchcol;
- long nmatched = 0;
+ int nmatched = 0;
const int called_emsg_before = called_emsg;
// for :{range}s/pat only highlight inside the range
@@ -435,7 +425,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
// 1. If the "lnum" is below a previous match, start a new search.
// 2. If the previous match includes "mincol", use it.
// 3. Continue after the previous match.
- l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
+ linenr_T l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum;
if (lnum > l) {
shl->lnum = 0;
} else if (lnum < l || shl->rm.endpos[0].col > mincol) {
@@ -445,7 +435,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
// Repeat searching for a match until one is found that includes "mincol"
// or none is found in this line.
- for (;;) {
+ while (true) {
// Stop searching after passing the time limit.
if (profile_passed_limit(shl->tm)) {
shl->lnum = 0; // no match found in time
@@ -464,7 +454,7 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
char *ml;
matchcol = shl->rm.startpos[0].col;
- ml = ml_get_buf(shl->buf, lnum, false) + matchcol;
+ ml = ml_get_buf(shl->buf, lnum) + matchcol;
if (*ml == NUL) {
matchcol++;
shl->lnum = 0;
@@ -523,22 +513,19 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_
void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum)
FUNC_ATTR_NONNULL_ALL
{
- matchitem_T *cur; // points to the match list
+ matchitem_T *cur = wp->w_match_head; // points to the match list
match_T *shl; // points to search_hl or a match
- bool shl_flag; // flag to indicate whether search_hl
- // has been processed or not
+ bool shl_flag = false; // flag to indicate whether search_hl has been processed or not
// When using a multi-line pattern, start searching at the top
// of the window or just after a closed fold.
// Do this both for search_hl and the match list.
- cur = wp->w_match_head;
- shl_flag = false;
while (cur != NULL || shl_flag == false) {
if (shl_flag == false) {
shl = search_hl;
shl_flag = true;
} else {
- shl = &cur->mit_hl; // -V595
+ shl = &cur->mit_hl;
}
if (shl->rm.regprog != NULL
&& shl->lnum == 0
@@ -616,7 +603,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
shl = search_hl;
shl_flag = true;
} else {
- shl = &cur->mit_hl; // -V595
+ shl = &cur->mit_hl;
}
shl->startcol = MAXCOL;
shl->endcol = MAXCOL;
@@ -631,7 +618,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
// Need to get the line again, a multi-line regexp may have made it
// invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
+ *line = ml_get_buf(wp->w_buffer, lnum);
if (shl->lnum != 0 && shl->lnum <= lnum) {
if (shl->lnum == lnum) {
@@ -659,7 +646,7 @@ bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char **lin
shl->endcol++;
}
}
- if ((long)shl->startcol < mincol) { // match at leftcol
+ if (shl->startcol < mincol) { // match at leftcol
shl->attr_cur = shl->attr;
*search_attr = shl->attr;
*search_attr_from_match = shl != search_hl;
@@ -717,8 +704,8 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
}
// Highlight the match were the cursor is using the CurSearch
// group.
- if (shl == search_hl && shl->has_cursor && (HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC))) {
- shl->attr_cur = win_hl_attr(wp, HLF_LC) ? win_hl_attr(wp, HLF_LC) : HL_ATTR(HLF_LC);
+ if (shl == search_hl && shl->has_cursor) {
+ shl->attr_cur = win_hl_attr(wp, HLF_LC);
} else {
shl->attr_cur = shl->attr;
}
@@ -741,7 +728,7 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
// Need to get the line again, a multi-line regexp
// may have made it invalid.
- *line = ml_get_buf(wp->w_buffer, lnum, false);
+ *line = ml_get_buf(wp->w_buffer, lnum);
if (shl->lnum == lnum) {
shl->startcol = shl->rm.startpos[0].col;
@@ -809,28 +796,27 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
return search_attr;
}
-bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
+bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, colnr_T curcol)
{
- long prevcol = curcol;
- matchitem_T *cur; // points to the match list
+ colnr_T prevcol = curcol;
// we're not really at that column when skipping some text
- if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
+ if ((wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) {
prevcol++;
}
// Highlight a character after the end of the line if the match started
// at the end of the line or when the match continues in the next line
// (match includes the line break).
- if (!search_hl->is_addpos && (prevcol == (long)search_hl->startcol
- || (prevcol > (long)search_hl->startcol
+ if (!search_hl->is_addpos && (prevcol == search_hl->startcol
+ || (prevcol > search_hl->startcol
&& search_hl->endcol == MAXCOL))) {
return true;
}
- cur = wp->w_match_head;
+ matchitem_T *cur = wp->w_match_head; // points to the match list
while (cur != NULL) {
- if (!cur->mit_hl.is_addpos && (prevcol == (long)cur->mit_hl.startcol
- || (prevcol > (long)cur->mit_hl.startcol
+ if (!cur->mit_hl.is_addpos && (prevcol == cur->mit_hl.startcol
+ || (prevcol > cur->mit_hl.startcol
&& cur->mit_hl.endcol == MAXCOL))) {
return true;
}
@@ -842,7 +828,7 @@ bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol)
/// Get highlighting for the char after the text in "char_attr" from 'hlsearch'
/// or match highlighting.
-void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr)
+void get_search_match_hl(win_T *wp, match_T *search_hl, colnr_T col, int *char_attr)
{
matchitem_T *cur = wp->w_match_head; // points to the match list
match_T *shl; // points to search_hl or a match
@@ -857,7 +843,7 @@ void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr
} else {
shl = &cur->mit_hl;
}
- if (col - 1 == (long)shl->startcol
+ if (col - 1 == shl->startcol
&& (shl == search_hl || !shl->is_addpos)) {
*char_attr = shl->attr;
}
@@ -906,8 +892,6 @@ void f_clearmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "getmatches()" function
void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- matchitem_T *cur;
- int i;
win_T *win = get_optional_window(argvars, 0);
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -915,12 +899,12 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- cur = win->w_match_head;
+ matchitem_T *cur = win->w_match_head;
while (cur != NULL) {
dict_T *dict = tv_dict_alloc();
if (cur->mit_match.regprog == NULL) {
// match added with matchaddpos()
- for (i = 0; i < cur->mit_pos_count; i++) {
+ for (int i = 0; i < cur->mit_pos_count; i++) {
llpos_T *llpos;
char buf[30]; // use 30 to avoid compiler warning
@@ -939,15 +923,14 @@ void f_getmatches(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_list(dict, buf, (size_t)len, l);
}
} else {
- tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->mit_pattern);
+ tv_dict_add_str(dict, S_LEN("pattern"), cur->mit_pattern);
}
- tv_dict_add_str(dict, S_LEN("group"),
- (const char *)syn_id2name(cur->mit_hlg_id));
+ tv_dict_add_str(dict, S_LEN("group"), syn_id2name(cur->mit_hlg_id));
tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->mit_priority);
tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->mit_id);
if (cur->mit_conceal_char) {
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
buf[utf_char2bytes(cur->mit_conceal_char, buf)] = NUL;
tv_dict_add_str(dict, S_LEN("conceal"), buf);
@@ -1167,9 +1150,8 @@ void f_matcharg(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
matchitem_T *const m = get_match(curwin, id);
if (m != NULL) {
- tv_list_append_string(rettv->vval.v_list,
- (const char *)syn_id2name(m->mit_hlg_id), -1);
- tv_list_append_string(rettv->vval.v_list, (const char *)m->mit_pattern, -1);
+ tv_list_append_string(rettv->vval.v_list, syn_id2name(m->mit_hlg_id), -1);
+ tv_list_append_string(rettv->vval.v_list, m->mit_pattern, -1);
} else {
tv_list_append_string(rettv->vval.v_list, NULL, 0);
tv_list_append_string(rettv->vval.v_list, NULL, 0);
@@ -1194,10 +1176,8 @@ void f_matchdelete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// skipping commands to find the next command.
void ex_match(exarg_T *eap)
{
- char *p;
char *g = NULL;
char *end;
- int c;
int id;
if (eap->line2 <= 3) {
@@ -1218,9 +1198,9 @@ void ex_match(exarg_T *eap)
&& (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) {
end = eap->arg + 4;
} else {
- p = skiptowhite(eap->arg);
+ char *p = skiptowhite(eap->arg);
if (!eap->skip) {
- g = xstrnsave(eap->arg, (size_t)(p - eap->arg));
+ g = xmemdupz(eap->arg, (size_t)(p - eap->arg));
}
p = skipwhite(p);
if (*p == NUL) {
@@ -1233,7 +1213,7 @@ void ex_match(exarg_T *eap)
if (!eap->skip) {
if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) {
xfree(g);
- eap->errmsg = ex_errmsg(e_trailing_arg, (const char *)end);
+ eap->errmsg = ex_errmsg(e_trailing_arg, end);
return;
}
if (*end != *p) {
@@ -1242,10 +1222,9 @@ void ex_match(exarg_T *eap)
return;
}
- c = (uint8_t)(*end);
+ int c = (uint8_t)(*end);
*end = NUL;
- match_add(curwin, (const char *)g, (const char *)p + 1, 10, id,
- NULL, NULL);
+ match_add(curwin, g, p + 1, 10, id, NULL, NULL);
xfree(g);
*end = (char)c;
}
diff --git a/src/nvim/match.h b/src/nvim/match.h
index 22a848bfdf..8dcf4fa470 100644
--- a/src/nvim/match.h
+++ b/src/nvim/match.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_MATCH_H
-#define NVIM_MATCH_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "match.h.generated.h"
#endif
-
-#endif // NVIM_MATCH_H
diff --git a/src/nvim/math.c b/src/nvim/math.c
index 31c6b5af69..96ff1bef10 100644
--- a/src/nvim/math.c
+++ b/src/nvim/math.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// uncrustify:off
#include <math.h>
// uncrustify:on
@@ -16,10 +13,9 @@
int xfpclassify(double d)
{
uint64_t m;
- int e;
memcpy(&m, &d, sizeof(m));
- e = 0x7ff & (m >> 52);
+ int e = 0x7ff & (m >> 52);
m = 0xfffffffffffffULL & m;
switch (e) {
diff --git a/src/nvim/math.h b/src/nvim/math.h
index 7969323905..c88ce1e03d 100644
--- a/src/nvim/math.h
+++ b/src/nvim/math.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_MATH_H
-#define NVIM_MATH_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "math.h.generated.h"
#endif
-#endif // NVIM_MATH_H
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index 8b50ba719a..f2883cc5c7 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// mbyte.c: Code specifically for handling multi-byte characters.
/// Multibyte extensions partly by Sung-Hoon Baek
///
@@ -29,18 +26,21 @@
#include <ctype.h>
#include <errno.h>
#include <iconv.h>
+#include <locale.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <wchar.h>
+#include <sys/types.h>
#include <wctype.h>
#include "auto/config.h"
#include "nvim/arabic.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/typval.h"
@@ -48,28 +48,23 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/iconv.h"
+#include "nvim/grid.h"
+#include "nvim/iconv_defs.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/mbyte_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/optionstr.h"
#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-
-#ifdef HAVE_LOCALE_H
-# include <locale.h>
-#endif
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
typedef struct {
int rangeStart;
@@ -79,8 +74,8 @@ typedef struct {
} convertStruct;
struct interval {
- long first;
- long last;
+ int first;
+ int last;
};
// uncrustify:off
@@ -90,17 +85,17 @@ struct interval {
#endif
// uncrustify:on
-static char e_list_item_nr_is_not_list[]
+static const char e_list_item_nr_is_not_list[]
= N_("E1109: List item %d is not a List");
-static char e_list_item_nr_does_not_contain_3_numbers[]
+static const char e_list_item_nr_does_not_contain_3_numbers[]
= N_("E1110: List item %d does not contain 3 numbers");
-static char e_list_item_nr_range_invalid[]
+static const char e_list_item_nr_range_invalid[]
= N_("E1111: List item %d range invalid");
-static char e_list_item_nr_cell_width_invalid[]
+static const char e_list_item_nr_cell_width_invalid[]
= N_("E1112: List item %d cell width invalid");
-static char e_overlapping_ranges_for_nr[]
+static const char e_overlapping_ranges_for_nr[]
= N_("E1113: Overlapping ranges for 0x%lx");
-static char e_only_values_of_0x80_and_higher_supported[]
+static const char e_only_values_of_0x80_and_higher_supported[]
= N_("E1114: Only values of 0x80 and higher supported");
// To speed up BYTELEN(); keep a lookup table to quickly get the length in
@@ -370,7 +365,7 @@ static int enc_canon_search(const char *name)
int enc_canon_props(const char *name)
FUNC_ATTR_PURE
{
- int i = enc_canon_search((char *)name);
+ int i = enc_canon_search(name);
if (i >= 0) {
return enc_canon_table[i].prop;
} else if (strncmp(name, "2byte-", 6) == 0) {
@@ -449,18 +444,16 @@ int mb_get_class_tab(const char *p, const uint64_t *const chartab)
static bool intable(const struct interval *table, size_t n_items, int c)
FUNC_ATTR_PURE
{
- int mid, bot, top;
-
// first quick check for Latin1 etc. characters
if (c < table[0].first) {
return false;
}
// binary search in table
- bot = 0;
- top = (int)(n_items - 1);
+ int bot = 0;
+ int top = (int)(n_items - 1);
while (top >= bot) {
- mid = (bot + top) / 2;
+ int mid = (bot + top) / 2;
if (table[mid].last < c) {
bot = mid + 1;
} else if (table[mid].first > c) {
@@ -518,11 +511,9 @@ int utf_char2cells(int c)
/// This doesn't take care of unprintable characters, use ptr2cells() for that.
int utf_ptr2cells(const char *p)
{
- int c;
-
// Need to convert to a character number.
if ((uint8_t)(*p) >= 0x80) {
- c = utf_ptr2char(p);
+ int c = utf_ptr2char(p);
// An illegal byte is displayed as <xx>.
if (utf_ptr2len(p) == 1 || c == NUL) {
return 4;
@@ -540,16 +531,14 @@ int utf_ptr2cells(const char *p)
/// For an empty string or truncated character returns 1.
int utf_ptr2cells_len(const char *p, int size)
{
- int c;
-
// Need to convert to a wide character.
if (size > 0 && (uint8_t)(*p) >= 0x80) {
if (utf_ptr2len_len(p, size) < utf8len_tab[(uint8_t)(*p)]) {
return 1; // truncated
}
- c = utf_ptr2char((char *)p);
+ int c = utf_ptr2char(p);
// An illegal byte is displayed as <xx>.
- if (utf_ptr2len((char *)p) == 1 || c == NUL) {
+ if (utf_ptr2len(p) == 1 || c == NUL) {
return 4;
}
// If the char is ASCII it must be an overlong sequence.
@@ -662,34 +651,32 @@ int utf_ptr2char(const char *const p_in)
//
// If byte sequence is illegal or incomplete, returns -1 and does not advance
// "s".
-static int utf_safe_read_char_adv(const char_u **s, size_t *n)
+static int utf_safe_read_char_adv(const char **s, size_t *n)
{
- int c;
-
if (*n == 0) { // end of buffer
return 0;
}
- uint8_t k = utf8len_tab_zero[**s];
+ uint8_t k = utf8len_tab_zero[(uint8_t)(**s)];
if (k == 1) {
// ASCII character or NUL
(*n)--;
- return *(*s)++;
+ return (uint8_t)(*(*s)++);
}
if (k <= *n) {
// We have a multibyte sequence and it isn't truncated by buffer
// limits so utf_ptr2char() is safe to use. Or the first byte is
// illegal (k=0), and it's also safe to use utf_ptr2char().
- c = utf_ptr2char((char *)(*s));
+ int c = utf_ptr2char(*s);
// On failure, utf_ptr2char() returns the first byte, so here we
// check equality with the first byte. The only non-ASCII character
// which equals the first byte of its own UTF-8 representation is
// U+00C3 (UTF-8: 0xC3 0x83), so need to check that special case too.
// It's safe even if n=1, else we would have k=2 > n.
- if (c != (int)(**s) || (c == 0xC3 && (*s)[1] == 0x83)) {
+ if (c != (int)((uint8_t)(**s)) || (c == 0xC3 && (uint8_t)(*s)[1] == 0x83)) {
// byte sequence was successfully decoded
*s += k;
*n -= k;
@@ -705,9 +692,7 @@ static int utf_safe_read_char_adv(const char_u **s, size_t *n)
// Note: composing characters are skipped!
int mb_ptr2char_adv(const char **const pp)
{
- int c;
-
- c = utf_ptr2char(*pp);
+ int c = utf_ptr2char(*pp);
*pp += utfc_ptr2len(*pp);
return c;
}
@@ -716,9 +701,7 @@ int mb_ptr2char_adv(const char **const pp)
// Note: composing characters are returned as separate characters.
int mb_cptr2char_adv(const char **pp)
{
- int c;
-
- c = utf_ptr2char(*pp);
+ int c = utf_ptr2char(*pp);
*pp += utf_ptr2len(*pp);
return c;
}
@@ -728,92 +711,78 @@ int mb_cptr2char_adv(const char **pp)
/// behaves like a composing character.
bool utf_composinglike(const char *p1, const char *p2)
{
- int c2;
-
- c2 = utf_ptr2char((char *)p2);
+ int c2 = utf_ptr2char(p2);
if (utf_iscomposing(c2)) {
return true;
}
if (!arabic_maycombine(c2)) {
return false;
}
- return arabic_combine(utf_ptr2char((char *)p1), c2);
+ return arabic_combine(utf_ptr2char(p1), c2);
}
-/// Convert a UTF-8 string to a wide character
+/// Get the screen char at the beginning of a string
+///
+/// Caller is expected to check for things like unprintable chars etc
+/// If first char in string is a composing char, prepend a space to display it correctly.
///
-/// Also gets up to #MAX_MCO composing characters.
+/// If "p" starts with an invalid sequence, zero is returned.
///
-/// @param[out] pcc Location where to store composing characters. Must have
-/// space at least for #MAX_MCO + 1 elements.
+/// @param[out] firstc (required) The first codepoint of the screen char,
+/// or the first byte of an invalid sequence
///
-/// @return leading character.
-int utfc_ptr2char(const char *p, int *pcc)
+/// @return the char
+schar_T utfc_ptr2schar(const char *p, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
- int i = 0;
-
int c = utf_ptr2char(p);
- int len = utf_ptr2len(p);
+ *firstc = c; // NOT optional, you are gonna need it
+ bool first_compose = utf_iscomposing(c);
+ size_t maxlen = MAX_SCHAR_SIZE - 1 - first_compose;
+ size_t len = (size_t)utfc_ptr2len_len(p, (int)maxlen);
- // Only accept a composing char when the first char isn't illegal.
- if ((len > 1 || (uint8_t)(*p) < 0x80)
- && (uint8_t)p[len] >= 0x80
- && utf_composinglike(p, p + len)) {
- int cc = utf_ptr2char(p + len);
- for (;;) {
- pcc[i++] = cc;
- if (i == MAX_MCO) {
- break;
- }
- len += utf_ptr2len(p + len);
- if ((uint8_t)p[len] < 0x80 || !utf_iscomposing(cc = utf_ptr2char(p + len))) {
- break;
- }
- }
- }
-
- if (i < MAX_MCO) { // last composing char must be 0
- pcc[i] = 0;
+ if (len == 1 && (uint8_t)(*p) >= 0x80) {
+ return 0; // invalid sequence
}
- return c;
+ return schar_from_buf_first(p, len, first_compose);
}
-// Convert a UTF-8 byte string to a wide character. Also get up to MAX_MCO
-// composing characters. Use no more than p[maxlen].
-//
-// @param [out] pcc: composing chars, last one is 0
-int utfc_ptr2char_len(const char *p, int *pcc, int maxlen)
+/// Get the screen char at the beginning of a string with length
+///
+/// Like utfc_ptr2schar but use no more than p[maxlen].
+schar_T utfc_ptr2schar_len(const char *p, int maxlen, int *firstc)
+ FUNC_ATTR_NONNULL_ALL
{
assert(maxlen > 0);
- int i = 0;
+ size_t len = (size_t)utf_ptr2len_len(p, maxlen);
+ if (len > (size_t)maxlen || (len == 1 && (uint8_t)(*p) >= 0x80) || len == 0) {
+ // invalid or truncated sequence
+ *firstc = (uint8_t)(*p);
+ return 0;
+ }
- int len = utf_ptr2len_len(p, maxlen);
- // Is it safe to use utf_ptr2char()?
- bool safe = len > 1 && len <= maxlen;
- int c = safe ? utf_ptr2char(p) : (uint8_t)(*p);
+ int c = utf_ptr2char(p);
+ *firstc = c;
+ bool first_compose = utf_iscomposing(c);
+ maxlen = MIN(maxlen, MAX_SCHAR_SIZE - 1 - first_compose);
+ len = (size_t)utfc_ptr2len_len(p, maxlen);
- // Only accept a composing char when the first char isn't illegal.
- if ((safe || c < 0x80) && len < maxlen && (uint8_t)p[len] >= 0x80) {
- for (; i < MAX_MCO; i++) {
- int len_cc = utf_ptr2len_len(p + len, maxlen - len);
- safe = len_cc > 1 && len_cc <= maxlen - len;
- if (!safe || (pcc[i] = utf_ptr2char(p + len)) < 0x80
- || !(i == 0 ? utf_composinglike(p, p + len) : utf_iscomposing(pcc[i]))) {
- break;
- }
- len += len_cc;
- }
- }
+ return schar_from_buf_first(p, len, first_compose);
+}
- if (i < MAX_MCO) {
- // last composing char must be 0
- pcc[i] = 0;
+/// Caller must ensure there is space for `first_compose`
+static schar_T schar_from_buf_first(const char *buf, size_t len, bool first_compose)
+{
+ if (first_compose) {
+ char cbuf[MAX_SCHAR_SIZE];
+ cbuf[0] = ' ';
+ memcpy(cbuf + 1, buf, len);
+ return schar_from_buf(cbuf, len + 1);
+ } else {
+ return schar_from_buf(buf, len);
}
-
- return c;
-#undef ISCOMPOSING
}
/// Get the length of a UTF-8 byte sequence representing a single codepoint
@@ -854,11 +823,9 @@ int utf_byte2len(int b)
// Never returns zero.
int utf_ptr2len_len(const char *p, int size)
{
- int len;
- int i;
int m;
- len = utf8len_tab[(uint8_t)(*p)];
+ int len = utf8len_tab[(uint8_t)(*p)];
if (len == 1) {
return 1; // NUL, ascii or illegal lead byte
}
@@ -867,7 +834,7 @@ int utf_ptr2len_len(const char *p, int size)
} else {
m = len;
}
- for (i = 1; i < m; i++) {
+ for (int i = 1; i < m; i++) {
if ((p[i] & 0xc0) != 0x80) {
return 1;
}
@@ -898,10 +865,9 @@ int utfc_ptr2len(const char *const p)
return 1;
}
- // Check for composing characters. We can handle only the first six, but
- // skip all of them (otherwise the cursor would get stuck).
+ // Check for composing characters.
int prevlen = 0;
- for (;;) {
+ while (true) {
if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) {
return len;
}
@@ -918,9 +884,6 @@ int utfc_ptr2len(const char *const p)
/// Returns 1 for an illegal char or an incomplete byte sequence.
int utfc_ptr2len_len(const char *p, int size)
{
- int len;
- int prevlen;
-
if (size < 1 || *p == NUL) {
return 0;
}
@@ -929,7 +892,7 @@ int utfc_ptr2len_len(const char *p, int size)
}
// Skip over first UTF-8 char, stopping at a NUL byte.
- len = utf_ptr2len_len(p, size);
+ int len = utf_ptr2len_len(p, size);
// Check for illegal byte and incomplete byte sequence.
if ((len == 1 && (uint8_t)p[0] >= 0x80) || len > size) {
@@ -938,17 +901,15 @@ int utfc_ptr2len_len(const char *p, int size)
// Check for composing characters. We can handle only the first six, but
// skip all of them (otherwise the cursor would get stuck).
- prevlen = 0;
+ int prevlen = 0;
while (len < size) {
- int len_next_char;
-
if ((uint8_t)p[len] < 0x80) {
break;
}
// Next character length should not go beyond size to ensure that
// utf_composinglike(...) does not read beyond size.
- len_next_char = utf_ptr2len_len(p + len, size - len);
+ int len_next_char = utf_ptr2len_len(p + len, size - len);
if (len_next_char > size - len) {
break;
}
@@ -1063,9 +1024,9 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
{
// sorted list of non-overlapping intervals
static struct clinterval {
- unsigned int first;
- unsigned int last;
- unsigned int class;
+ unsigned first;
+ unsigned last;
+ unsigned cls;
} classes[] = {
{ 0x037e, 0x037e, 1 }, // Greek question mark
{ 0x0387, 0x0387, 1 }, // Greek ano teleia
@@ -1141,7 +1102,6 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
};
int bot = 0;
int top = ARRAY_SIZE(classes) - 1;
- int mid;
// First quick check for Latin1 characters, use 'iskeyword'.
if (c < 0x100) {
@@ -1161,13 +1121,13 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
// binary search in table
while (top >= bot) {
- mid = (bot + top) / 2;
- if (classes[mid].last < (unsigned int)c) {
+ int mid = (bot + top) / 2;
+ if (classes[mid].last < (unsigned)c) {
bot = mid + 1;
- } else if (classes[mid].first > (unsigned int)c) {
+ } else if (classes[mid].first > (unsigned)c) {
top = mid - 1;
} else {
- return (int)classes[mid].class;
+ return (int)classes[mid].cls;
}
}
@@ -1186,13 +1146,12 @@ bool utf_ambiguous_width(int c)
// the given conversion "table". Uses binary search on "table".
static int utf_convert(int a, const convertStruct *const table, size_t n_items)
{
- size_t start, mid, end; // indices into table
-
- start = 0;
- end = n_items;
+ // indices into table
+ size_t start = 0;
+ size_t end = n_items;
while (start < end) {
// need to search further
- mid = (end + start) / 2;
+ size_t mid = (end + start) / 2;
if (table[mid].rangeEnd < a) {
start = mid + 1;
} else {
@@ -1285,12 +1244,12 @@ bool mb_isalpha(int a)
return mb_islower(a) || mb_isupper(a);
}
-static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2)
+static int utf_strnicmp(const char *s1, const char *s2, size_t n1, size_t n2)
{
- int c1, c2, cdiff;
+ int c1, c2;
char buffer[6];
- for (;;) {
+ while (true) {
c1 = utf_safe_read_char_adv(&s1, &n1);
c2 = utf_safe_read_char_adv(&s2, &n2);
@@ -1302,7 +1261,7 @@ static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2
continue;
}
- cdiff = utf_fold(c1) - utf_fold(c2);
+ int cdiff = utf_fold(c1) - utf_fold(c2);
if (cdiff != 0) {
return cdiff;
}
@@ -1326,15 +1285,15 @@ static int utf_strnicmp(const char_u *s1, const char_u *s2, size_t n1, size_t n2
// to fold just one character to determine the result of comparison.
if (c1 != -1 && c2 == -1) {
- n1 = (size_t)utf_char2bytes(utf_fold(c1), (char *)buffer);
- s1 = (char_u *)buffer;
+ n1 = (size_t)utf_char2bytes(utf_fold(c1), buffer);
+ s1 = buffer;
} else if (c2 != -1 && c1 == -1) {
- n2 = (size_t)utf_char2bytes(utf_fold(c2), (char *)buffer);
- s2 = (char_u *)buffer;
+ n2 = (size_t)utf_char2bytes(utf_fold(c2), buffer);
+ s2 = buffer;
}
while (n1 > 0 && n2 > 0 && *s1 != NUL && *s2 != NUL) {
- cdiff = (int)(*s1) - (int)(*s2);
+ int cdiff = (int)((uint8_t)(*s1)) - (int)((uint8_t)(*s2));
if (cdiff != 0) {
return cdiff;
}
@@ -1483,11 +1442,11 @@ ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_
FUNC_ATTR_NONNULL_ALL
{
size_t count = 0;
- size_t clen, i;
+ size_t clen;
if (index == 0) {
return 0;
}
- for (i = 0; i < len; i += clen) {
+ for (size_t i = 0; i < len; i += clen) {
clen = (size_t)utf_ptr2len_len(s + i, (int)(len - i));
// NB: gets the byte value of invalid sequence bytes.
// we only care whether the char fits in the BMP or not
@@ -1512,7 +1471,7 @@ ssize_t mb_utf_index_to_bytes(const char *s, size_t len, size_t index, bool use_
/// two characters otherwise.
int mb_strnicmp(const char *s1, const char *s2, const size_t nn)
{
- return utf_strnicmp((char_u *)s1, (char_u *)s2, nn, nn);
+ return utf_strnicmp(s1, s2, nn, nn);
}
/// Compare strings case-insensitively
@@ -1536,23 +1495,18 @@ int mb_stricmp(const char *s1, const char *s2)
// 'encoding' has been set to.
void show_utf8(void)
{
- int len;
- int rlen = 0;
- char *line;
- int clen;
- int i;
-
// Get the byte length of the char under the cursor, including composing
// characters.
- line = get_cursor_pos_ptr();
- len = utfc_ptr2len(line);
+ char *line = get_cursor_pos_ptr();
+ int len = utfc_ptr2len(line);
if (len == 0) {
- msg("NUL");
+ msg("NUL", 0);
return;
}
- clen = 0;
- for (i = 0; i < len; i++) {
+ size_t rlen = 0;
+ int clen = 0;
+ for (int i = 0; i < len; i++) {
if (clen == 0) {
// start of (composing) character, get its length
if (i > 0) {
@@ -1561,16 +1515,17 @@ void show_utf8(void)
}
clen = utf_ptr2len(line + i);
}
- sprintf(IObuff + rlen, "%02x ", // NOLINT(runtime/printf)
- (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL
+ assert(IOSIZE > rlen);
+ snprintf(IObuff + rlen, IOSIZE - rlen, "%02x ",
+ (line[i] == NL) ? NUL : (uint8_t)line[i]); // NUL is stored as NL
clen--;
- rlen += (int)strlen(IObuff + rlen);
+ rlen += strlen(IObuff + rlen);
if (rlen > IOSIZE - 20) {
break;
}
}
- msg(IObuff);
+ msg(IObuff, 0);
}
/// Return offset from "p" to the start of a character, including composing characters.
@@ -1579,9 +1534,6 @@ void show_utf8(void)
/// Returns 0 when already at the first byte of a character.
int utf_head_off(const char *base_in, const char *p_in)
{
- int c;
- int len;
-
if ((uint8_t)(*p_in) < 0x80) { // be quick for ASCII
return 0;
}
@@ -1603,7 +1555,7 @@ int utf_head_off(const char *base_in, const char *p_in)
}
// Check for illegal sequence. Do allow an illegal byte after where we
// started.
- len = utf8len_tab[*q];
+ int len = utf8len_tab[*q];
if (len != (int)(s - q + 1) && len != (int)(p - q + 1)) {
return 0;
}
@@ -1612,7 +1564,7 @@ int utf_head_off(const char *base_in, const char *p_in)
break;
}
- c = utf_ptr2char((char *)q);
+ int c = utf_ptr2char((char *)q);
if (utf_iscomposing(c)) {
continue;
}
@@ -1669,7 +1621,7 @@ bool utf_allow_break_before(int cc)
0x2021, // ‡ double dagger
0x2026, // … horizontal ellipsis
0x2030, // ‰ per mille sign
- 0x2031, // ‱ per then thousand sign
+ 0x2031, // ‱ per the thousand sign
0x203c, // ‼ double exclamation mark
0x2047, // ⁇ double question mark
0x2048, // ⁈ question exclamation mark
@@ -1795,7 +1747,6 @@ int mb_off_next(const char *base, const char *p_in)
{
const uint8_t *p = (uint8_t *)p_in;
int i;
- int j;
if (*p < 0x80) { // be quick for ASCII
return 0;
@@ -1804,6 +1755,7 @@ int mb_off_next(const char *base, const char *p_in)
// Find the next character that isn't 10xx.xxxx
for (i = 0; (p[i] & 0xc0) == 0x80; i++) {}
if (i > 0) {
+ int j;
// Check for illegal sequence.
for (j = 0; p - j > (uint8_t *)base; j++) {
if ((p[-j] & 0xc0) != 0x80) {
@@ -1849,33 +1801,35 @@ int utf_cp_tail_off(const char *base, const char *p_in)
/// Return the offset from "p" to the first byte of the codepoint it points
/// to. Can start anywhere in a stream of bytes.
/// Note: Unlike `utf_head_off`, this counts individual codepoints of composed characters
-/// separately and returns a negative offset.
+/// separately.
///
/// @param[in] base Pointer to start of string
/// @param[in] p Pointer to byte for which to return the offset to the previous codepoint
//
-/// @return 0 if invalid sequence, else offset to previous codepoint
-int utf_cp_head_off(const char_u *base, const char_u *p)
+/// @return 0 if invalid sequence, else number of bytes to previous codepoint
+int utf_cp_head_off(const char *base, const char *p)
{
int i;
- int j;
if (*p == NUL) {
return 0;
}
// Find the first character that is not 10xx.xxxx
- for (i = 0; p - i > base; i--) {
- if ((p[i] & 0xc0) != 0x80) {
+ for (i = 0; p - i >= base; i++) {
+ if (((uint8_t)p[-i] & 0xc0) != 0x80) {
break;
}
}
- // Find the last character that is 10xx.xxxx
- for (j = 0; (p[j + 1] & 0xc0) == 0x80; j++) {}
+ // Find the last character that is 10xx.xxxx (condition terminates on NUL)
+ int j = 1;
+ while (((uint8_t)p[j] & 0xc0) == 0x80) {
+ j++;
+ }
// Check for illegal sequence.
- if (utf8len_tab[p[i]] == 1) {
+ if (utf8len_tab[(uint8_t)p[-i]] != j + i) {
return 0;
}
return i;
@@ -1885,8 +1839,6 @@ int utf_cp_head_off(const char_u *base, const char_u *p)
void utf_find_illegal(void)
{
pos_T pos = curwin->w_cursor;
- char *p;
- int len;
vimconv_T vimconv;
char *tofree = NULL;
@@ -1899,8 +1851,8 @@ void utf_find_illegal(void)
}
curwin->w_cursor.coladd = 0;
- for (;;) {
- p = get_cursor_pos_ptr();
+ while (true) {
+ char *p = get_cursor_pos_ptr();
if (vimconv.vc_type != CONV_NONE) {
xfree(tofree);
tofree = string_convert(&vimconv, p, NULL);
@@ -1913,7 +1865,7 @@ void utf_find_illegal(void)
while (*p != NUL) {
// Illegal means that there are not enough trail bytes (checked by
// utf_ptr2len()) or too many of them (overlong sequence).
- len = utf_ptr2len(p);
+ int len = utf_ptr2len(p);
if ((uint8_t)(*p) >= 0x80 && (len == 1 || utf_char2len(utf_ptr2char(p)) != len)) {
if (vimconv.vc_type == CONV_NONE) {
curwin->w_cursor.col += (colnr_T)(p - get_cursor_pos_ptr());
@@ -1948,16 +1900,16 @@ theend:
/// @return true if string "s" is a valid utf-8 string.
/// When "end" is NULL stop at the first NUL. Otherwise stop at "end".
-bool utf_valid_string(const char_u *s, const char_u *end)
+bool utf_valid_string(const char *s, const char *end)
{
- const char_u *p = s;
+ const uint8_t *p = (uint8_t *)s;
- while (end == NULL ? *p != NUL : p < end) {
+ while (end == NULL ? *p != NUL : p < (uint8_t *)end) {
int l = utf8len_tab_zero[*p];
if (l == 0) {
return false; // invalid lead byte
}
- if (end != NULL && p + l > end) {
+ if (end != NULL && p + l > (uint8_t *)end) {
return false; // incomplete byte sequence
}
p++;
@@ -1988,7 +1940,7 @@ void mb_check_adjust_col(void *win_)
// Column 0 is always valid.
if (oldcol != 0) {
- char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum, false);
+ char *p = ml_get_buf(win->w_buffer, win->w_cursor.lnum);
colnr_T len = (colnr_T)strlen(p);
// Empty line or invalid column?
@@ -2042,6 +1994,24 @@ int mb_charlen(const char *str)
return count;
}
+int mb_charlen2bytelen(const char *str, int charlen)
+{
+ const char *p = str;
+ int count = 0;
+
+ if (p == NULL) {
+ return 0;
+ }
+
+ for (int i = 0; *p != NUL && i < charlen; i++) {
+ int b = utfc_ptr2len(p);
+ p += b;
+ count += b;
+ }
+
+ return count;
+}
+
/// Like mb_charlen() but for a string with specified length.
int mb_charlen_len(const char *str, int len)
{
@@ -2122,7 +2092,6 @@ char *enc_skip(char *p)
char *enc_canonize(char *enc)
FUNC_ATTR_NONNULL_RET
{
- char *p, *s;
if (strcmp(enc, "default") == 0) {
// Use the default encoding as found by set_init_1().
return xstrdup(fenc_default);
@@ -2131,8 +2100,8 @@ char *enc_canonize(char *enc)
// copy "enc" to allocated memory, with room for two '-'
char *r = xmalloc(strlen(enc) + 3);
// Make it all lower case and replace '_' with '-'.
- p = r;
- for (s = enc; *s != NUL; s++) {
+ char *p = r;
+ for (char *s = enc; *s != NUL; s++) {
if (*s == '_') {
*p++ = '-';
} else {
@@ -2184,9 +2153,7 @@ char *enc_canonize(char *enc)
/// Returns -1 when not found.
static int enc_alias_search(const char *name)
{
- int i;
-
- for (i = 0; enc_alias_table[i].name != NULL; i++) {
+ for (int i = 0; enc_alias_table[i].name != NULL; i++) {
if (strcmp(name, enc_alias_table[i].name) == 0) {
return enc_alias_table[i].canon;
}
@@ -2210,10 +2177,7 @@ char *enc_locale(void)
if (!(s = nl_langinfo(CODESET)) || *s == NUL)
#endif
{
-#if defined(HAVE_LOCALE_H)
- if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL)
-#endif
- {
+ if (!(s = setlocale(LC_CTYPE, NULL)) || *s == NUL) {
if ((s = os_getenv("LC_ALL"))) {
if ((s = os_getenv("LC_CTYPE"))) {
s = os_getenv("LANG");
@@ -2269,17 +2233,14 @@ enc_locale_copy_enc:
// (should return iconv_t, but that causes problems with prototypes).
void *my_iconv_open(char *to, char *from)
{
- iconv_t fd;
#define ICONV_TESTLEN 400
char tobuf[ICONV_TESTLEN];
- char *p;
- size_t tolen;
static WorkingStatus iconv_working = kUnknown;
if (iconv_working == kBroken) {
return (void *)-1; // detected a broken iconv() previously
}
- fd = iconv_open(enc_skip(to), enc_skip(from));
+ iconv_t fd = iconv_open(enc_skip(to), enc_skip(from));
if (fd != (iconv_t)-1 && iconv_working == kUnknown) {
// Do a dummy iconv() call to check if it actually works. There is a
@@ -2287,8 +2248,8 @@ void *my_iconv_open(char *to, char *from)
// because it's wide-spread. The symptoms are that after outputting
// the initial shift state the "to" pointer is NULL and conversion
// stops for no apparent reason after about 8160 characters.
- p = tobuf;
- tolen = ICONV_TESTLEN;
+ char *p = tobuf;
+ size_t tolen = ICONV_TESTLEN;
(void)iconv(fd, NULL, NULL, &p, &tolen);
if (p == NULL) {
iconv_working = kBroken;
@@ -2310,24 +2271,19 @@ void *my_iconv_open(char *to, char *from)
static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t slen,
size_t *unconvlenp, size_t *resultlenp)
{
- const char *from;
- size_t fromlen;
char *to;
- size_t tolen;
size_t len = 0;
size_t done = 0;
char *result = NULL;
- char *p;
- int l;
- from = str;
- fromlen = slen;
- for (;;) {
+ const char *from = str;
+ size_t fromlen = slen;
+ while (true) {
if (len == 0 || ICONV_ERRNO == ICONV_E2BIG) {
// Allocate enough room for most conversions. When re-allocating
// increase the buffer size.
len = len + fromlen * 2 + 40;
- p = xmalloc(len);
+ char *p = xmalloc(len);
if (done > 0) {
memmove(p, result, done);
}
@@ -2336,7 +2292,7 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
}
to = result + done;
- tolen = len - done - 2;
+ size_t tolen = len - done - 2;
// Avoid a warning for systems with a wrong iconv() prototype by
// casting the second argument to void *.
if (iconv(vcp->vc_fd, (void *)&from, &fromlen, &to, &tolen) != SIZE_MAX) {
@@ -2366,7 +2322,7 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
if (utf_ptr2cells(from) > 1) {
*to++ = '?';
}
- l = utfc_ptr2len_len(from, (int)fromlen);
+ int l = utfc_ptr2len_len(from, (int)fromlen);
from += l;
fromlen -= (size_t)l;
} else if (ICONV_ERRNO != ICONV_E2BIG) {
@@ -2384,6 +2340,34 @@ static char *iconv_string(const vimconv_T *const vcp, const char *str, size_t sl
return result;
}
+/// iconv() function
+void f_iconv(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ vimconv_T vimconv;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *const str = tv_get_string(&argvars[0]);
+ char buf1[NUMBUFLEN];
+ char *const from = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[1], buf1)));
+ char buf2[NUMBUFLEN];
+ char *const to = enc_canonize(enc_skip((char *)tv_get_string_buf(&argvars[2], buf2)));
+ vimconv.vc_type = CONV_NONE;
+ convert_setup(&vimconv, from, to);
+
+ // If the encodings are equal, no conversion needed.
+ if (vimconv.vc_type == CONV_NONE) {
+ rettv->vval.v_string = xstrdup(str);
+ } else {
+ rettv->vval.v_string = string_convert(&vimconv, (char *)str, NULL);
+ }
+
+ convert_setup(&vimconv, NULL, NULL);
+ xfree(from);
+ xfree(to);
+}
+
/// Setup "vcp" for conversion from "from" to "to".
/// The names must have been made canonical with enc_canonize().
/// vcp->vc_type must have been initialized to CONV_NONE.
@@ -2402,8 +2386,6 @@ int convert_setup(vimconv_T *vcp, char *from, char *to)
int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, char *to,
bool to_unicode_is_utf8)
{
- int from_prop;
- int to_prop;
int from_is_utf8;
int to_is_utf8;
@@ -2419,8 +2401,8 @@ int convert_setup_ext(vimconv_T *vcp, char *from, bool from_unicode_is_utf8, cha
return OK;
}
- from_prop = enc_canon_props(from);
- to_prop = enc_canon_props(to);
+ int from_prop = enc_canon_props(from);
+ int to_prop = enc_canon_props(to);
if (from_unicode_is_utf8) {
from_is_utf8 = from_prop & ENC_UNICODE;
} else {
@@ -2477,9 +2459,8 @@ char *string_convert(const vimconv_T *const vcp, char *ptr, size_t *lenp)
// set to the number of remaining bytes.
char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, size_t *unconvlenp)
{
- char_u *retval = NULL;
- char_u *d;
- int l;
+ uint8_t *retval = NULL;
+ uint8_t *d;
int c;
size_t len;
@@ -2499,10 +2480,10 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
for (size_t i = 0; i < len; i++) {
c = (uint8_t)ptr[i];
if (c < 0x80) {
- *d++ = (char_u)c;
+ *d++ = (uint8_t)c;
} else {
- *d++ = (char_u)(0xc0 + (char_u)((unsigned)c >> 6));
- *d++ = (char_u)(0x80 + (c & 0x3f));
+ *d++ = (uint8_t)(0xc0 + (uint8_t)((unsigned)c >> 6));
+ *d++ = (uint8_t)(0x80 + (c & 0x3f));
}
}
*d = NUL;
@@ -2547,7 +2528,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
retval = xmalloc(len + 1);
d = retval;
for (size_t i = 0; i < len; i++) {
- l = utf_ptr2len_len(ptr + i, (int)(len - i));
+ int l = utf_ptr2len_len(ptr + i, (int)(len - i));
if (l == 0) {
*d++ = NUL;
} else if (l == 1) {
@@ -2597,7 +2578,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
}
if (!utf_iscomposing(c)) { // skip composing chars
if (c < 0x100) {
- *d++ = (char_u)c;
+ *d++ = (uint8_t)c;
} else if (vcp->vc_fail) {
xfree(retval);
return NULL;
@@ -2618,7 +2599,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
break;
case CONV_ICONV: // conversion with vcp->vc_fd
- retval = (char_u *)iconv_string(vcp, ptr, len, unconvlenp, lenp);
+ retval = (uint8_t *)iconv_string(vcp, ptr, len, unconvlenp, lenp);
break;
}
@@ -2627,8 +2608,8 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
/// Table set by setcellwidths().
typedef struct {
- long first;
- long last;
+ int64_t first;
+ int64_t last;
char width;
} cw_interval_T;
@@ -2753,7 +2734,7 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
const listitem_T *lili = tv_list_first(li_l);
const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number;
if (item > 0 && n1 <= table[item - 1].last) {
- semsg(_(e_overlapping_ranges_for_nr), (long)n1);
+ semsg(_(e_overlapping_ranges_for_nr), (size_t)n1);
xfree((void *)ptrs);
xfree(table);
return;
@@ -2810,3 +2791,14 @@ void f_charclass(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
rettv->vval.v_number = mb_get_class(argvars[0].vval.v_string);
}
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// encoding options.
+char *get_encoding_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(enc_canon_table)) {
+ return NULL;
+ }
+
+ return (char *)enc_canon_table[idx].name;
+}
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 780f33e05b..49c323282d 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -1,15 +1,15 @@
-#ifndef NVIM_MBYTE_H
-#define NVIM_MBYTE_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/eval/typval.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/func_attr.h"
-#include "nvim/mbyte_defs.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/types.h"
+#include "nvim/mbyte_defs.h" // IWYU pragma: export
+#include "nvim/os/os_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Return byte length of character that starts with byte "b".
// Returns 1 for a single-byte character.
@@ -38,4 +38,3 @@ static inline int mb_strcmp_ic(bool ic, const char *s1, const char *s2)
{
return (ic ? mb_stricmp(s1, s2) : strcmp(s1, s2));
}
-#endif // NVIM_MBYTE_H
diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h
index e913e20f9f..2904047223 100644
--- a/src/nvim/mbyte_defs.h
+++ b/src/nvim/mbyte_defs.h
@@ -1,9 +1,13 @@
-#ifndef NVIM_MBYTE_DEFS_H
-#define NVIM_MBYTE_DEFS_H
+#pragma once
#include <stdbool.h>
-#include "nvim/iconv.h"
+#include "nvim/iconv_defs.h"
+
+/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
+/// character of up to 6 bytes, or one 16-bit character of up to three bytes
+/// plus six following composing characters of three bytes each.
+enum { MB_MAXBYTES = 21, };
/// max length of an unicode char
enum { MB_MAXCHAR = 6, };
@@ -50,5 +54,3 @@ typedef struct {
bool vc_fail; ///< What to do with invalid characters: if true, fail,
///< otherwise use '?'.
} vimconv_T;
-
-#endif // NVIM_MBYTE_DEFS_H
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index 46be9ccea5..d989600d45 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// An abstraction to handle blocks of memory which can be stored in a file.
/// This is the implementation of a sort of virtual memory.
///
@@ -45,24 +42,25 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
-#include "nvim/assert.h"
+#include "nvim/assert_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/fileio.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/map_defs.h"
#include "nvim/memfile.h"
#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/vim_defs.h"
#define MEMFILE_PAGE_SIZE 4096 /// default page size
@@ -70,6 +68,8 @@
# include "memfile.c.generated.h"
#endif
+static const char e_block_was_not_locked[] = N_("E293: Block was not locked");
+
/// Open a new or existing memory block file.
///
/// @param fname Name of file to use.
@@ -99,11 +99,9 @@ memfile_T *mf_open(char *fname, int flags)
}
mfp->mf_free_first = NULL; // free list is empty
- mfp->mf_used_first = NULL; // used list is empty
- mfp->mf_used_last = NULL;
- mfp->mf_dirty = false;
- mf_hash_init(&mfp->mf_hash);
- mf_hash_init(&mfp->mf_trans);
+ mfp->mf_dirty = MF_DIRTY_NO;
+ mfp->mf_hash = (PMap(int64_t)) MAP_INIT;
+ mfp->mf_trans = (Map(int64_t, int64_t)) MAP_INIT;
mfp->mf_page_size = MEMFILE_PAGE_SIZE;
// Try to set the page size equal to device's block size. Speeds up I/O a lot.
@@ -124,7 +122,7 @@ memfile_T *mf_open(char *fname, int flags)
// must be rounded up.
if (mfp->mf_fd < 0
|| (flags & (O_TRUNC|O_EXCL))
- || (size = vim_lseek(mfp->mf_fd, 0L, SEEK_END)) <= 0) {
+ || (size = vim_lseek(mfp->mf_fd, 0, SEEK_END)) <= 0) {
// no file or empty file
mfp->mf_blocknr_max = 0;
} else {
@@ -157,7 +155,7 @@ memfile_T *mf_open(char *fname, int flags)
int mf_open_file(memfile_T *mfp, char *fname)
{
if (mf_do_open(mfp, fname, O_RDWR | O_CREAT | O_EXCL)) {
- mfp->mf_dirty = true;
+ mfp->mf_dirty = MF_DIRTY_YES;
return OK;
}
@@ -180,15 +178,15 @@ void mf_close(memfile_T *mfp, bool del_file)
}
// free entries in used list
- for (bhdr_T *hp = mfp->mf_used_first, *nextp; hp != NULL; hp = nextp) {
- nextp = hp->bh_next;
+ bhdr_T *hp;
+ map_foreach_value(&mfp->mf_hash, hp, {
mf_free_bhdr(hp);
- }
+ })
while (mfp->mf_free_first != NULL) { // free entries in free list
xfree(mf_rem_free(mfp));
}
- mf_hash_free(&mfp->mf_hash);
- mf_hash_free_all(&mfp->mf_trans); // free hashtable and its items
+ map_destroy(int64_t, &mfp->mf_hash);
+ map_destroy(int64_t, &mfp->mf_trans); // free hashtable and its items
mf_free_fnames(mfp);
xfree(mfp);
}
@@ -206,7 +204,7 @@ void mf_close_file(buf_T *buf, bool getlines)
if (getlines) {
// get all blocks in memory by accessing all lines (clumsy!)
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- (void)ml_get_buf(buf, lnum, false);
+ (void)ml_get_buf(buf, lnum);
}
}
@@ -267,10 +265,9 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count)
}
}
hp->bh_flags = BH_LOCKED | BH_DIRTY; // new block is always dirty
- mfp->mf_dirty = true;
+ mfp->mf_dirty = MF_DIRTY_YES;
hp->bh_page_count = page_count;
- mf_ins_used(mfp, hp);
- mf_ins_hash(mfp, hp);
+ pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp);
// Init the data to all zero, to avoid reading uninitialized data.
// This also avoids that the passwd file ends up in the swap file!
@@ -292,7 +289,7 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
}
// see if it is in the cache
- bhdr_T *hp = mf_find_hash(mfp, nr);
+ bhdr_T *hp = pmap_get(int64_t)(&mfp->mf_hash, nr);
if (hp == NULL) { // not in the hash list
if (nr < 0 || nr >= mfp->mf_infile_count) { // can't be in the file
return NULL;
@@ -300,7 +297,12 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
// could check here if the block is in the free list
- hp = mf_alloc_bhdr(mfp, page_count);
+ if (page_count > 0) {
+ hp = mf_alloc_bhdr(mfp, page_count);
+ }
+ if (hp == NULL) {
+ return NULL;
+ }
hp->bh_bnum = nr;
hp->bh_flags = 0;
@@ -310,13 +312,11 @@ bhdr_T *mf_get(memfile_T *mfp, blocknr_T nr, unsigned page_count)
return NULL;
}
} else {
- mf_rem_used(mfp, hp); // remove from list, insert in front below
- mf_rem_hash(mfp, hp);
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
}
hp->bh_flags |= BH_LOCKED;
- mf_ins_used(mfp, hp); // put in front of used list
- mf_ins_hash(mfp, hp); // put in front of hash list
+ pmap_put(int64_t)(&mfp->mf_hash, hp->bh_bnum, hp); // put in front of hash table
return hp;
}
@@ -330,12 +330,14 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile)
unsigned flags = hp->bh_flags;
if ((flags & BH_LOCKED) == 0) {
- iemsg(_("E293: block was not locked"));
+ iemsg(_(e_block_was_not_locked));
}
flags &= ~BH_LOCKED;
if (dirty) {
flags |= BH_DIRTY;
- mfp->mf_dirty = true;
+ if (mfp->mf_dirty != MF_DIRTY_YES_NOSYNC) {
+ mfp->mf_dirty = MF_DIRTY_YES;
+ }
}
hp->bh_flags = flags;
if (infile) {
@@ -347,8 +349,7 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile)
void mf_free(memfile_T *mfp, bhdr_T *hp)
{
xfree(hp->bh_data); // free data
- mf_rem_hash(mfp, hp); // get *hp out of the hash list
- mf_rem_used(mfp, hp); // get *hp out of the used list
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL); // get *hp out of the hash table
if (hp->bh_bnum < 0) {
xfree(hp); // don't want negative numbers in free list
mfp->mf_neg_count--;
@@ -375,8 +376,9 @@ int mf_sync(memfile_T *mfp, int flags)
{
int got_int_save = got_int;
- if (mfp->mf_fd < 0) { // there is no file, nothing to do
- mfp->mf_dirty = false;
+ if (mfp->mf_fd < 0) {
+ // there is no file, nothing to do
+ mfp->mf_dirty = MF_DIRTY_NO;
return FAIL;
}
@@ -389,7 +391,8 @@ int mf_sync(memfile_T *mfp, int flags)
// fails then we give up.
int status = OK;
bhdr_T *hp;
- for (hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
+ // note, "last" block is typically earlier in the hash list
+ map_foreach_value(&mfp->mf_hash, hp, {
if (((flags & MFS_ALL) || hp->bh_bnum >= 0)
&& (hp->bh_flags & BH_DIRTY)
&& (status == OK || (hp->bh_bnum >= 0
@@ -414,12 +417,12 @@ int mf_sync(memfile_T *mfp, int flags)
break;
}
}
- }
+ })
// If the whole list is flushed, the memfile is not dirty anymore.
// In case of an error, dirty flag is also set, to avoid trying all the time.
if (hp == NULL || status == FAIL) {
- mfp->mf_dirty = false;
+ mfp->mf_dirty = MF_DIRTY_NO;
}
if (flags & MFS_FLUSH) {
@@ -437,59 +440,13 @@ int mf_sync(memfile_T *mfp, int flags)
/// These are blocks that need to be written to a newly created swapfile.
void mf_set_dirty(memfile_T *mfp)
{
- for (bhdr_T *hp = mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
+ bhdr_T *hp;
+ map_foreach_value(&mfp->mf_hash, hp, {
if (hp->bh_bnum > 0) {
hp->bh_flags |= BH_DIRTY;
}
- }
- mfp->mf_dirty = true;
-}
-
-/// Insert block in front of memfile's hash list.
-static void mf_ins_hash(memfile_T *mfp, bhdr_T *hp)
-{
- mf_hash_add_item(&mfp->mf_hash, (mf_hashitem_T *)hp);
-}
-
-/// Remove block from memfile's hash list.
-static void mf_rem_hash(memfile_T *mfp, bhdr_T *hp)
-{
- mf_hash_rem_item(&mfp->mf_hash, (mf_hashitem_T *)hp);
-}
-
-/// Lookup block with number "nr" in memfile's hash list.
-static bhdr_T *mf_find_hash(memfile_T *mfp, blocknr_T nr)
-{
- return (bhdr_T *)mf_hash_find(&mfp->mf_hash, nr);
-}
-
-/// Insert block at the front of memfile's used list.
-static void mf_ins_used(memfile_T *mfp, bhdr_T *hp)
-{
- hp->bh_next = mfp->mf_used_first;
- mfp->mf_used_first = hp;
- hp->bh_prev = NULL;
- if (hp->bh_next == NULL) { // list was empty, adjust last pointer
- mfp->mf_used_last = hp;
- } else {
- hp->bh_next->bh_prev = hp;
- }
-}
-
-/// Remove block from memfile's used list.
-static void mf_rem_used(memfile_T *mfp, bhdr_T *hp)
-{
- if (hp->bh_next == NULL) { // last block in used list
- mfp->mf_used_last = hp->bh_prev;
- } else {
- hp->bh_next->bh_prev = hp->bh_prev;
- }
-
- if (hp->bh_prev == NULL) { // first block in used list
- mfp->mf_used_first = hp->bh_next;
- } else {
- hp->bh_prev->bh_next = hp->bh_next;
- }
+ })
+ mfp->mf_dirty = MF_DIRTY_YES;
}
/// Release as many blocks as possible.
@@ -510,17 +467,18 @@ bool mf_release_all(void)
// Flush as many blocks as possible, only if there is a swapfile.
if (mfp->mf_fd >= 0) {
- for (bhdr_T *hp = mfp->mf_used_last; hp != NULL;) {
+ for (int i = 0; i < (int)map_size(&mfp->mf_hash);) {
+ bhdr_T *hp = mfp->mf_hash.values[i];
if (!(hp->bh_flags & BH_LOCKED)
&& (!(hp->bh_flags & BH_DIRTY)
|| mf_write(mfp, hp) != FAIL)) {
- mf_rem_used(mfp, hp);
- mf_rem_hash(mfp, hp);
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
mf_free_bhdr(hp);
- hp = mfp->mf_used_last; // restart, list was changed
retval = true;
+ // Rerun with the same value of i. another item will have taken
+ // its place (or it was the last)
} else {
- hp = hp->bh_prev;
+ i++;
}
}
}
@@ -548,7 +506,7 @@ static void mf_free_bhdr(bhdr_T *hp)
/// Insert a block in the free list.
static void mf_ins_free(memfile_T *mfp, bhdr_T *hp)
{
- hp->bh_next = mfp->mf_free_first;
+ hp->bh_data = mfp->mf_free_first;
mfp->mf_free_first = hp;
}
@@ -558,7 +516,7 @@ static void mf_ins_free(memfile_T *mfp, bhdr_T *hp)
static bhdr_T *mf_rem_free(memfile_T *mfp)
{
bhdr_T *hp = mfp->mf_free_first;
- mfp->mf_free_first = hp->bh_next;
+ mfp->mf_free_first = hp->bh_data;
return hp;
}
@@ -602,12 +560,8 @@ static int mf_read(memfile_T *mfp, bhdr_T *hp)
/// - Write error in swap file.
static int mf_write(memfile_T *mfp, bhdr_T *hp)
{
- off_T offset; // offset in the file
- blocknr_T nr; // block nr which is being written
bhdr_T *hp2;
- unsigned page_size; // number of bytes in a page
unsigned page_count; // number of pages written
- unsigned size; // number of bytes written
if (mfp->mf_fd < 0) { // there is no file, can't write
return FAIL;
@@ -619,23 +573,23 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
}
}
- page_size = mfp->mf_page_size;
+ unsigned page_size = mfp->mf_page_size; // number of bytes in a page
/// We don't want gaps in the file. Write the blocks in front of *hp
/// to extend the file.
/// If block 'mf_infile_count' is not in the hash list, it has been
/// freed. Fill the space in the file with data from the current block.
- for (;;) {
- nr = hp->bh_bnum;
+ while (true) {
+ blocknr_T nr = hp->bh_bnum; // block nr which is being written
if (nr > mfp->mf_infile_count) { // beyond end of file
nr = mfp->mf_infile_count;
- hp2 = mf_find_hash(mfp, nr); // NULL caught below
+ hp2 = pmap_get(int64_t)(&mfp->mf_hash, nr); // NULL caught below
} else {
hp2 = hp;
}
// TODO(elmart): Check (page_size * nr) within off_T bounds.
- offset = (off_T)(page_size * nr);
+ off_T offset = (off_T)(page_size * nr); // offset in the file
if (vim_lseek(mfp->mf_fd, offset, SEEK_SET) != offset) {
PERROR(_("E296: Seek error in swap file write"));
return FAIL;
@@ -645,7 +599,7 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
} else {
page_count = hp2->bh_page_count;
}
- size = page_size * page_count;
+ unsigned size = page_size * page_count; // number of bytes written
void *data = (hp2 == NULL) ? hp->bh_data : hp2->bh_data;
if ((unsigned)write_eintr(mfp->mf_fd, data, size) != size) {
/// Avoid repeating the error message, this mostly happens when the
@@ -682,8 +636,6 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
return OK;
}
- mf_blocknr_trans_item_T *np = xmalloc(sizeof(mf_blocknr_trans_item_T));
-
// Get a new number for the block.
// If the first item in the free list has sufficient pages, use its number.
// Otherwise use mf_blocknr_max.
@@ -706,15 +658,13 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
mfp->mf_blocknr_max += page_count;
}
- np->nt_old_bnum = hp->bh_bnum; // adjust number
- np->nt_new_bnum = new_bnum;
-
- mf_rem_hash(mfp, hp); // remove from old hash list
+ blocknr_T old_bnum = hp->bh_bnum; // adjust number
+ pmap_del(int64_t)(&mfp->mf_hash, hp->bh_bnum, NULL);
hp->bh_bnum = new_bnum;
- mf_ins_hash(mfp, hp); // insert in new hash list
+ pmap_put(int64_t)(&mfp->mf_hash, new_bnum, hp);
// Insert "np" into "mf_trans" hashtable with key "np->nt_old_bnum".
- mf_hash_add_item(&mfp->mf_trans, (mf_hashitem_T *)np);
+ map_put(int64_t, int64_t)(&mfp->mf_trans, old_bnum, new_bnum);
return OK;
}
@@ -725,20 +675,16 @@ static int mf_trans_add(memfile_T *mfp, bhdr_T *hp)
/// The old number When not found.
blocknr_T mf_trans_del(memfile_T *mfp, blocknr_T old_nr)
{
- mf_blocknr_trans_item_T *np =
- (mf_blocknr_trans_item_T *)mf_hash_find(&mfp->mf_trans, old_nr);
-
- if (np == NULL) { // not found
+ blocknr_T *num = map_ref(int64_t, int64_t)(&mfp->mf_trans, old_nr, false);
+ if (num == NULL) { // not found
return old_nr;
}
mfp->mf_neg_count--;
- blocknr_T new_bnum = np->nt_new_bnum;
+ blocknr_T new_bnum = *num;
// remove entry from the trans list
- mf_hash_rem_item(&mfp->mf_trans, (mf_hashitem_T *)np);
-
- xfree(np);
+ map_del(int64_t, int64_t)(&mfp->mf_trans, old_nr, NULL);
return new_bnum;
}
@@ -802,7 +748,7 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags)
emsg(_("E300: Swap file already exists (symlink attack?)"));
} else {
// try to open the file
- mfp->mf_fd = MCH_OPEN_RW((char *)mfp->mf_fname, flags | O_NOFOLLOW);
+ mfp->mf_fd = os_open(mfp->mf_fname, flags | O_NOFOLLOW, S_IREAD | S_IWRITE);
}
// If the file cannot be opened, use memory only
@@ -815,152 +761,3 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags)
return true;
}
-
-//
-// Implementation of mf_hashtab_T.
-//
-
-/// The number of buckets in the hashtable is increased by a factor of
-/// MHT_GROWTH_FACTOR when the average number of items per bucket
-/// exceeds 2 ^ MHT_LOG_LOAD_FACTOR.
-enum {
- MHT_LOG_LOAD_FACTOR = 6,
- MHT_GROWTH_FACTOR = 2, // must be a power of two
-};
-
-/// Initialize an empty hash table.
-static void mf_hash_init(mf_hashtab_T *mht)
-{
- CLEAR_POINTER(mht);
- mht->mht_buckets = mht->mht_small_buckets;
- mht->mht_mask = MHT_INIT_SIZE - 1;
-}
-
-/// Free the array of a hash table. Does not free the items it contains!
-/// The hash table must not be used again without another mf_hash_init() call.
-static void mf_hash_free(mf_hashtab_T *mht)
-{
- if (mht->mht_buckets != mht->mht_small_buckets) {
- xfree(mht->mht_buckets);
- }
-}
-
-/// Free the array of a hash table and all the items it contains.
-static void mf_hash_free_all(mf_hashtab_T *mht)
-{
- for (size_t idx = 0; idx <= mht->mht_mask; idx++) {
- mf_hashitem_T *next;
- for (mf_hashitem_T *mhi = mht->mht_buckets[idx]; mhi != NULL; mhi = next) {
- next = mhi->mhi_next;
- xfree(mhi);
- }
- }
-
- mf_hash_free(mht);
-}
-
-/// Find by key.
-///
-/// @return A pointer to a mf_hashitem_T or NULL if the item was not found.
-static mf_hashitem_T *mf_hash_find(mf_hashtab_T *mht, blocknr_T key)
-{
- mf_hashitem_T *mhi = mht->mht_buckets[(size_t)key & mht->mht_mask];
- while (mhi != NULL && mhi->mhi_key != key) {
- mhi = mhi->mhi_next;
- }
- return mhi;
-}
-
-/// Add item to hashtable. Item must not be NULL.
-static void mf_hash_add_item(mf_hashtab_T *mht, mf_hashitem_T *mhi)
-{
- size_t idx = (size_t)mhi->mhi_key & mht->mht_mask;
- mhi->mhi_next = mht->mht_buckets[idx];
- mhi->mhi_prev = NULL;
- if (mhi->mhi_next != NULL) {
- mhi->mhi_next->mhi_prev = mhi;
- }
- mht->mht_buckets[idx] = mhi;
-
- mht->mht_count++;
-
- /// Grow hashtable when we have more thank 2^MHT_LOG_LOAD_FACTOR
- /// items per bucket on average.
- if ((mht->mht_count >> MHT_LOG_LOAD_FACTOR) > mht->mht_mask) {
- mf_hash_grow(mht);
- }
-}
-
-/// Remove item from hashtable. Item must be non NULL and within hashtable.
-static void mf_hash_rem_item(mf_hashtab_T *mht, mf_hashitem_T *mhi)
-{
- if (mhi->mhi_prev == NULL) {
- mht->mht_buckets[(size_t)mhi->mhi_key & mht->mht_mask] =
- mhi->mhi_next;
- } else {
- mhi->mhi_prev->mhi_next = mhi->mhi_next;
- }
-
- if (mhi->mhi_next != NULL) {
- mhi->mhi_next->mhi_prev = mhi->mhi_prev;
- }
-
- mht->mht_count--;
-
- // We could shrink the table here, but it typically takes little memory,
- // so why bother?
-}
-
-/// Increase number of buckets in the hashtable by MHT_GROWTH_FACTOR and
-/// rehash items.
-static void mf_hash_grow(mf_hashtab_T *mht)
-{
- size_t size = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR * sizeof(void *);
- mf_hashitem_T **buckets = xcalloc(1, size);
-
- int shift = 0;
- while ((mht->mht_mask >> shift) != 0) {
- shift++;
- }
-
- for (size_t i = 0; i <= mht->mht_mask; i++) {
- /// Traverse the items in the i-th original bucket and move them into
- /// MHT_GROWTH_FACTOR new buckets, preserving their relative order
- /// within each new bucket. Preserving the order is important because
- /// mf_get() tries to keep most recently used items at the front of
- /// each bucket.
- ///
- /// Here we strongly rely on the fact that hashes are computed modulo
- /// a power of two.
-
- mf_hashitem_T *tails[MHT_GROWTH_FACTOR];
- CLEAR_FIELD(tails);
-
- for (mf_hashitem_T *mhi = mht->mht_buckets[i];
- mhi != NULL; mhi = mhi->mhi_next) {
- size_t j = (mhi->mhi_key >> shift) & (MHT_GROWTH_FACTOR - 1);
- if (tails[j] == NULL) {
- buckets[i + (j << shift)] = mhi;
- tails[j] = mhi;
- mhi->mhi_prev = NULL;
- } else {
- tails[j]->mhi_next = mhi;
- mhi->mhi_prev = tails[j];
- tails[j] = mhi;
- }
- }
-
- for (size_t j = 0; j < MHT_GROWTH_FACTOR; j++) {
- if (tails[j] != NULL) {
- tails[j]->mhi_next = NULL;
- }
- }
- }
-
- if (mht->mht_buckets != mht->mht_small_buckets) {
- xfree(mht->mht_buckets);
- }
-
- mht->mht_buckets = buckets;
- mht->mht_mask = (mht->mht_mask + 1) * MHT_GROWTH_FACTOR - 1;
-}
diff --git a/src/nvim/memfile.h b/src/nvim/memfile.h
index 085fa22f12..687ac042a4 100644
--- a/src/nvim/memfile.h
+++ b/src/nvim/memfile.h
@@ -1,16 +1,25 @@
-#ifndef NVIM_MEMFILE_H
-#define NVIM_MEMFILE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/memfile_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/memfile_defs.h" // IWYU pragma: export
/// flags for mf_sync()
-#define MFS_ALL 1 /// also sync blocks with negative numbers
-#define MFS_STOP 2 /// stop syncing when a character is available
-#define MFS_FLUSH 4 /// flushed file to disk
-#define MFS_ZERO 8 /// only write block 0
+enum {
+ MFS_ALL = 1, ///< also sync blocks with negative numbers
+ MFS_STOP = 2, ///< stop syncing when a character is available
+ MFS_FLUSH = 4, ///< flushed file to disk
+ MFS_ZERO = 8, ///< only write block 0
+};
+
+enum {
+ /// Minimal size for block 0 of a swap file.
+ /// NOTE: This depends on size of struct block0! It's not done with a sizeof(),
+ /// because struct block0 is defined in memline.c (Sorry).
+ /// The maximal block size is arbitrary.
+ MIN_SWAP_PAGE_SIZE = 1048,
+ MAX_SWAP_PAGE_SIZE = 50000,
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memfile.h.generated.h"
#endif
-#endif // NVIM_MEMFILE_H
diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h
index 53152c28f8..db68ecf039 100644
--- a/src/nvim/memfile_defs.h
+++ b/src/nvim/memfile_defs.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_MEMFILE_DEFS_H
-#define NVIM_MEMFILE_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/map_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
/// A block number.
///
@@ -15,93 +15,54 @@
/// with negative numbers are currently in memory only.
typedef int64_t blocknr_T;
-/// A hash item.
-///
-/// Items' keys are block numbers.
-/// Items in the same bucket are organized into a doubly-linked list.
-///
-/// Therefore, items can be arbitrary data structures beginning with pointers
-/// for the list and and a block number key.
-typedef struct mf_hashitem {
- struct mf_hashitem *mhi_next;
- struct mf_hashitem *mhi_prev;
- blocknr_T mhi_key;
-} mf_hashitem_T;
-
-/// Initial size for a hashtable.
-#define MHT_INIT_SIZE 64
-
-/// A chained hashtable with block numbers as keys and arbitrary data structures
-/// as items.
-///
-/// This is an intrusive data structure: we require that items begin with
-/// mf_hashitem_T which contains the key and linked list pointers. List of items
-/// in each bucket is doubly-linked.
-typedef struct mf_hashtab {
- size_t mht_mask; /// mask used to mod hash value to array index
- /// (nr of items in array is 'mht_mask + 1')
- size_t mht_count; /// number of items inserted
- mf_hashitem_T **mht_buckets; /// points to the array of buckets (can be
- /// mht_small_buckets or a newly allocated array
- /// when mht_small_buckets becomes too small)
- mf_hashitem_T *mht_small_buckets[MHT_INIT_SIZE]; /// initial buckets
-} mf_hashtab_T;
-
/// A block header.
///
/// There is a block header for each previously used block in the memfile.
///
/// The block may be linked in the used list OR in the free list.
-/// The used blocks are also kept in hash lists.
///
/// The used list is a doubly linked list, most recently used block first.
/// The blocks in the used list have a block of memory allocated.
-/// The hash lists are used to quickly find a block in the used list.
/// The free list is a single linked list, not sorted.
/// The blocks in the free list have no block of memory allocated and
/// the contents of the block in the file (if any) is irrelevant.
typedef struct bhdr {
- mf_hashitem_T bh_hashitem; /// header for hash table and key
-#define bh_bnum bh_hashitem.mhi_key /// block number, part of bh_hashitem
+ blocknr_T bh_bnum; ///< key used in hash table
- struct bhdr *bh_next; /// next block header in free or used list
- struct bhdr *bh_prev; /// previous block header in used list
- void *bh_data; /// pointer to memory (for used block)
- unsigned bh_page_count; /// number of pages in this block
+ void *bh_data; ///< pointer to memory (for used block)
+ unsigned bh_page_count; ///< number of pages in this block
#define BH_DIRTY 1U
#define BH_LOCKED 2U
- unsigned bh_flags; // BH_DIRTY or BH_LOCKED
+ unsigned bh_flags; ///< BH_DIRTY or BH_LOCKED
} bhdr_T;
-/// A block number translation list item.
-///
-/// When a block with a negative number is flushed to the file, it gets
-/// a positive number. Because the reference to the block is still the negative
-/// number, we remember the translation to the new positive number in the
-/// double linked trans lists. The structure is the same as the hash lists.
-typedef struct mf_blocknr_trans_item {
- mf_hashitem_T nt_hashitem; /// header for hash table and key
-#define nt_old_bnum nt_hashitem.mhi_key /// old, negative, number
- blocknr_T nt_new_bnum; /// new, positive, number
-} mf_blocknr_trans_item_T;
+typedef enum {
+ MF_DIRTY_NO = 0, ///< no dirty blocks
+ MF_DIRTY_YES, ///< there are dirty blocks
+ MF_DIRTY_YES_NOSYNC, ///< there are dirty blocks, do not sync yet
+} mfdirty_T;
/// A memory file.
typedef struct memfile {
- char *mf_fname; /// name of the file
- char *mf_ffname; /// idem, full path
- int mf_fd; /// file descriptor
- bhdr_T *mf_free_first; /// first block header in free list
- bhdr_T *mf_used_first; /// mru block header in used list
- bhdr_T *mf_used_last; /// lru block header in used list
- mf_hashtab_T mf_hash; /// hash lists
- mf_hashtab_T mf_trans; /// trans lists
- blocknr_T mf_blocknr_max; /// highest positive block number + 1
- blocknr_T mf_blocknr_min; /// lowest negative block number - 1
- blocknr_T mf_neg_count; /// number of negative blocks numbers
- blocknr_T mf_infile_count; /// number of pages in the file
- unsigned mf_page_size; /// number of bytes in a page
- bool mf_dirty; /// true if there are dirty blocks
-} memfile_T;
+ char *mf_fname; ///< name of the file
+ char *mf_ffname; ///< idem, full path
+ int mf_fd; ///< file descriptor
+ bhdr_T *mf_free_first; ///< first block header in free list
+
+ /// The used blocks are kept in mf_hash.
+ /// mf_hash are used to quickly find a block in the used list.
+ PMap(int64_t) mf_hash;
-#endif // NVIM_MEMFILE_DEFS_H
+ /// When a block with a negative number is flushed to the file, it gets
+ /// a positive number. Because the reference to the block is still the negative
+ /// number, we remember the translation to the new positive number.
+ Map(int64_t, int64_t) mf_trans;
+
+ blocknr_T mf_blocknr_max; ///< highest positive block number + 1
+ blocknr_T mf_blocknr_min; ///< lowest negative block number - 1
+ blocknr_T mf_neg_count; ///< number of negative blocks numbers
+ blocknr_T mf_infile_count; ///< number of pages in the file
+ unsigned mf_page_size; ///< number of bytes in a page
+ mfdirty_T mf_dirty;
+} memfile_T;
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index b3fc64a68c..3c671121b7 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// for debugging
// #define CHECK(c, s) do { if (c) emsg(s); } while (0)
#define CHECK(c, s) do {} while (0)
@@ -39,14 +36,16 @@
#include <fcntl.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -55,16 +54,17 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
@@ -72,31 +72,26 @@
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/process.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/spell.h"
+#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifndef UNIX // it's in os/unix_defs.h for Unix
# include <time.h>
#endif
-typedef struct block0 ZERO_BL; // contents of the first block
-typedef struct pointer_block PTR_BL; // contents of a pointer block
-typedef struct data_block DATA_BL; // contents of a data block
-typedef struct pointer_entry PTR_EN; // block/line-count pair
-
enum {
DATA_ID = (('d' << 8) + 'a'), // data block id
PTR_ID = (('p' << 8) + 't'), // pointer block id
@@ -105,39 +100,43 @@ enum {
};
// pointer to a block, used in a pointer block
-struct pointer_entry {
+typedef struct {
blocknr_T pe_bnum; // block number
linenr_T pe_line_count; // number of lines in this branch
linenr_T pe_old_lnum; // lnum for this block (for recovery)
int pe_page_count; // number of pages in block pe_bnum
-};
+} PointerEntry;
// A pointer block contains a list of branches in the tree.
-struct pointer_block {
+typedef struct {
uint16_t pb_id; // ID for pointer block: PTR_ID
uint16_t pb_count; // number of pointers in this block
uint16_t pb_count_max; // maximum value for pb_count
- PTR_EN pb_pointer[1]; // list of pointers to blocks (actually longer)
+ PointerEntry pb_pointer[]; // list of pointers to blocks
// followed by empty space until end of page
-};
+} PointerBlock;
+
+// Value for pb_count_max.
+#define PB_COUNT_MAX(mfp) \
+ (uint16_t)((mfp->mf_page_size - offsetof(PointerBlock, pb_pointer)) / sizeof(PointerEntry))
// A data block is a leaf in the tree.
//
// The text of the lines is at the end of the block. The text of the first line
// in the block is put at the end, the text of the second line in front of it,
// etc. Thus the order of the lines is the opposite of the line number.
-struct data_block {
+typedef struct {
uint16_t db_id; // ID for data block: DATA_ID
unsigned db_free; // free space available
unsigned db_txt_start; // byte where text starts
unsigned db_txt_end; // byte just after data block
// linenr_T db_line_count;
long db_line_count; // number of lines in this block
- unsigned db_index[1]; // index for start of line (actually bigger)
+ unsigned db_index[]; // index for start of line
// followed by empty space up to db_txt_start
// followed by the text in the lines until
// end of page
-};
+} DataBlock;
// The low bits of db_index hold the actual index. The topmost bit is
// used for the global command to be able to mark a line.
@@ -149,7 +148,7 @@ struct data_block {
#define DB_INDEX_MASK (~DB_MARKED)
#define INDEX_SIZE (sizeof(unsigned)) // size of one db_index entry
-#define HEADER_SIZE (offsetof(DATA_BL, db_index)) // size of data block header
+#define HEADER_SIZE (offsetof(DataBlock, db_index)) // size of data block header
enum {
B0_FNAME_SIZE_ORG = 900, // what it was in older versions
@@ -162,37 +161,36 @@ enum {
// This won't detect a 64 bit machine that only swaps a byte in the top 32
// bits, but that is crazy anyway.
enum {
- B0_MAGIC_LONG = 0x30313233L,
- B0_MAGIC_INT = 0x20212223L,
- B0_MAGIC_SHORT = 0x10111213L,
+ B0_MAGIC_LONG = 0x30313233,
+ B0_MAGIC_INT = 0x20212223,
+ B0_MAGIC_SHORT = 0x10111213,
B0_MAGIC_CHAR = 0x55,
};
-// Block zero holds all info about the swap file.
-//
-// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing
-// swap files unusable!
-//
-// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in vim.h!!
-//
-// This block is built up of single bytes, to make it portable across
-// different machines. b0_magic_* is used to check the byte order and size of
-// variables, because the rest of the swap file is not portable.
-struct block0 {
- char_u b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1.
- char b0_version[10]; // Vim version string
- char_u b0_page_size[4]; // number of bytes per page
- char_u b0_mtime[4]; // last modification time of file
- char_u b0_ino[4]; // inode of b0_fname
- char_u b0_pid[4]; // process id of creator (or 0)
- char_u b0_uname[B0_UNAME_SIZE]; // name of user (uid if no name)
- char_u b0_hname[B0_HNAME_SIZE]; // host name (if it has a name)
- char b0_fname[B0_FNAME_SIZE_ORG]; // name of file being edited
- long b0_magic_long; // check for byte order of long
- int b0_magic_int; // check for byte order of int
- int16_t b0_magic_short; // check for byte order of short
- char_u b0_magic_char; // check for last char
-};
+/// Block zero holds all info about the swapfile. This is the first block in the file.
+///
+/// NOTE: DEFINITION OF BLOCK 0 SHOULD NOT CHANGE! It would make all existing swapfiles unusable!
+///
+/// If size of block0 changes anyway, adjust MIN_SWAP_PAGE_SIZE in memfile.h!!
+///
+/// This block is built up of single bytes, to make it portable across
+/// different machines. b0_magic_* is used to check the byte order and size of
+/// variables, because the rest of the swapfile is not portable.
+typedef struct {
+ char b0_id[2]; ///< ID for block 0: BLOCK0_ID0 and BLOCK0_ID1.
+ char b0_version[10]; ///< Vim version string
+ char b0_page_size[4]; ///< number of bytes per page
+ char b0_mtime[4]; ///< last modification time of file
+ char b0_ino[4]; ///< inode of b0_fname
+ char b0_pid[4]; ///< process id of creator (or 0)
+ char b0_uname[B0_UNAME_SIZE]; ///< name of user (uid if no name)
+ char b0_hname[B0_HNAME_SIZE]; ///< host name (if it has a name)
+ char b0_fname[B0_FNAME_SIZE_ORG]; ///< name of file being edited
+ long b0_magic_long; ///< check for byte order of long
+ int b0_magic_int; ///< check for byte order of int
+ int16_t b0_magic_short; ///< check for byte order of short
+ char b0_magic_char; ///< check for last char
+} ZeroBlock;
// Note: b0_dirty and b0_flags are put at the end of the file name. For very
// long file names in older versions of Vim they are invalid.
@@ -209,8 +207,7 @@ struct block0 {
// EOL_MAC + 1.
#define B0_FF_MASK 3
-// Swap file is in directory of edited file. Used to find the file from
-// different mount points.
+// Swapfile is in directory of edited file. Used to find the file from different mount points.
#define B0_SAME_DIR 4
// The 'fileencoding' is at the end of b0_fname[], with a NUL in front of it.
@@ -240,17 +237,48 @@ typedef enum {
UB_SAME_DIR, // update the B0_SAME_DIR flag
} upd_block0_T;
+typedef enum {
+ SEA_CHOICE_NONE = 0,
+ SEA_CHOICE_READONLY = 1,
+ SEA_CHOICE_EDIT = 2,
+ SEA_CHOICE_RECOVER = 3,
+ SEA_CHOICE_DELETE = 4,
+ SEA_CHOICE_QUIT = 5,
+ SEA_CHOICE_ABORT = 6,
+} sea_choice_T;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memline.c.generated.h"
#endif
+static const char e_ml_get_invalid_lnum_nr[]
+ = N_("E315: ml_get: Invalid lnum: %" PRId64);
+static const char e_ml_get_cannot_find_line_nr_in_buffer_nr_str[]
+ = N_("E316: ml_get: Cannot find line %" PRId64 "in buffer %d %s");
+static const char e_pointer_block_id_wrong[]
+ = N_("E317: Pointer block id wrong");
+static const char e_pointer_block_id_wrong_two[]
+ = N_("E317: Pointer block id wrong 2");
+static const char e_pointer_block_id_wrong_three[]
+ = N_("E317: Pointer block id wrong 3");
+static const char e_pointer_block_id_wrong_four[]
+ = N_("E317: Pointer block id wrong 4");
+static const char e_line_number_out_of_range_nr_past_the_end[]
+ = N_("E322: Line number out of range: %" PRId64 " past the end");
+static const char e_line_count_wrong_in_block_nr[]
+ = N_("E323: Line count wrong in block %" PRId64);
+static const char e_warning_pointer_block_corrupted[]
+ = N_("E1364: Warning: Pointer block corrupted");
+
+#if __has_feature(address_sanitizer)
+# define ML_GET_ALLOC_LINES
+#endif
+
/// Open a new memline for "buf".
///
/// @return FAIL for failure, OK otherwise.
int ml_open(buf_T *buf)
{
- bhdr_T *hp = NULL;
-
// init fields in memline struct
buf->b_ml.ml_stack_size = 0; // no stack yet
buf->b_ml.ml_stack = NULL; // no stack yet
@@ -265,14 +293,14 @@ int ml_open(buf_T *buf)
buf->b_p_swf = false;
}
- // When 'updatecount' is non-zero swap file may be opened later.
+ // When 'updatecount' is non-zero swapfile may be opened later.
if (!buf->terminal && p_uc && buf->b_p_swf) {
buf->b_may_swap = true;
} else {
buf->b_may_swap = false;
}
- // Open the memfile. No swap file is created yet.
+ // Open the memfile. No swapfile is created yet.
memfile_T *mfp = mf_open(NULL, 0);
if (mfp == NULL) {
goto error;
@@ -283,12 +311,12 @@ int ml_open(buf_T *buf)
buf->b_ml.ml_line_count = 1;
// fill block0 struct and write page 0
- hp = mf_new(mfp, false, 1);
+ bhdr_T *hp = mf_new(mfp, false, 1);
if (hp->bh_bnum != 0) {
iemsg(_("E298: Didn't get block nr 0?"));
goto error;
}
- ZERO_BL *b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
b0p->b0_id[0] = BLOCK0_ID0;
b0p->b0_id[1] = BLOCK0_ID1;
@@ -303,32 +331,31 @@ int ml_open(buf_T *buf)
b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
b0p->b0_flags = (char)(get_fileformat(buf) + 1);
set_b0_fname(b0p, buf);
- (void)os_get_username((char *)b0p->b0_uname, B0_UNAME_SIZE);
+ (void)os_get_username(b0p->b0_uname, B0_UNAME_SIZE);
b0p->b0_uname[B0_UNAME_SIZE - 1] = NUL;
- os_get_hostname((char *)b0p->b0_hname, B0_HNAME_SIZE);
+ os_get_hostname(b0p->b0_hname, B0_HNAME_SIZE);
b0p->b0_hname[B0_HNAME_SIZE - 1] = NUL;
- long_to_char(os_get_pid(), b0p->b0_pid);
+ long_to_char((long)os_get_pid(), b0p->b0_pid);
}
// Always sync block number 0 to disk, so we can check the file name in
- // the swap file in findswapname(). Don't do this for a help files or
+ // the swapfile in findswapname(). Don't do this for a help files or
// a spell buffer though.
// Only works when there's a swapfile, otherwise it's done when the file
// is created.
mf_put(mfp, hp, true, false);
- if (!buf->b_help && !B_SPELL(buf)) {
+ if (!buf->b_help && !buf->b_spell) {
(void)mf_sync(mfp, 0);
}
// Fill in root pointer block and write page 1.
- if ((hp = ml_new_ptr(mfp)) == NULL) {
- goto error;
- }
+ hp = ml_new_ptr(mfp);
+ assert(hp != NULL);
if (hp->bh_bnum != 1) {
iemsg(_("E298: Didn't get block nr 1?"));
goto error;
}
- PTR_BL *pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
pp->pb_count = 1;
pp->pb_pointer[0].pe_bnum = 2;
pp->pb_pointer[0].pe_page_count = 1;
@@ -343,11 +370,11 @@ int ml_open(buf_T *buf)
goto error;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_index[0] = --dp->db_txt_start; // at end of block
dp->db_free -= 1 + (unsigned)INDEX_SIZE;
dp->db_line_count = 1;
- *((char_u *)dp + dp->db_txt_start) = NUL; // empty line
+ *((char *)dp + dp->db_txt_start) = NUL; // empty line
return OK;
@@ -363,17 +390,17 @@ error:
}
/// ml_setname() is called when the file name of "buf" has been changed.
-/// It may rename the swap file.
+/// It may rename the swapfile.
void ml_setname(buf_T *buf)
{
bool success = false;
memfile_T *mfp = buf->b_ml.ml_mfp;
- if (mfp->mf_fd < 0) { // there is no swap file yet
- // When 'updatecount' is 0 and 'noswapfile' there is no swap file.
- // For help files we will make a swap file now.
+ if (mfp->mf_fd < 0) { // there is no swapfile yet
+ // When 'updatecount' is 0 and 'noswapfile' there is no swapfile.
+ // For help files we will make a swapfile now.
if (p_uc != 0 && (cmdmod.cmod_flags & CMOD_NOSWAPFILE) == 0) {
- ml_open_file(buf); // create a swap file
+ ml_open_file(buf); // create a swapfile
}
return;
}
@@ -381,7 +408,7 @@ void ml_setname(buf_T *buf)
// Try all directories in the 'directory' option.
char *dirp = p_dir;
bool found_existing_dir = false;
- for (;;) {
+ while (true) {
if (*dirp == NUL) { // tried all directories, fail
break;
}
@@ -400,13 +427,13 @@ void ml_setname(buf_T *buf)
success = true;
break;
}
- // need to close the swap file before renaming
+ // need to close the swapfile before renaming
if (mfp->mf_fd >= 0) {
close(mfp->mf_fd);
mfp->mf_fd = -1;
}
- // try to rename the swap file
+ // try to rename the swapfile
if (vim_rename(mfp->mf_fname, fname) == 0) {
success = true;
mf_free_fnames(mfp);
@@ -417,10 +444,10 @@ void ml_setname(buf_T *buf)
xfree(fname); // this fname didn't work, try another
}
- if (mfp->mf_fd == -1) { // need to (re)open the swap file
+ if (mfp->mf_fd == -1) { // need to (re)open the swapfile
mfp->mf_fd = os_open(mfp->mf_fname, O_RDWR, 0);
if (mfp->mf_fd < 0) {
- // could not (re)open the swap file, what can we do????
+ // could not (re)open the swapfile, what can we do????
emsg(_("E301: Oops, lost the swap file!!!"));
return;
}
@@ -443,7 +470,7 @@ void ml_open_files(void)
}
}
-/// Open a swap file for an existing memfile, if there is no swap file yet.
+/// Open a swapfile for an existing memfile, if there is no swapfile yet.
/// If we are unable to find a file name, mf_fname will be NULL
/// and the memfile will be in memory only (no recovery possible).
void ml_open_file(buf_T *buf)
@@ -468,11 +495,11 @@ void ml_open_file(buf_T *buf)
// Try all directories in 'directory' option.
char *dirp = p_dir;
bool found_existing_dir = false;
- for (;;) {
+ while (true) {
if (*dirp == NUL) {
break;
}
- // There is a small chance that between choosing the swap file name
+ // There is a small chance that between choosing the swapfile name
// and creating it, another Vim creates the file. In that case the
// creation will fail and we will use another directory.
char *fname = findswapname(buf, &dirp, NULL, &found_existing_dir);
@@ -483,13 +510,15 @@ void ml_open_file(buf_T *buf)
continue;
}
if (mf_open_file(mfp, fname) == OK) { // consumes fname!
+ // don't sync yet in ml_sync_all()
+ mfp->mf_dirty = MF_DIRTY_YES_NOSYNC;
ml_upd_block0(buf, UB_SAME_DIR);
// Flush block zero, so others can read it
if (mf_sync(mfp, MFS_ZERO) == OK) {
// Mark all blocks that should be in the swapfile as dirty.
// Needed for when the 'swapfile' option was reset, so that
- // the swap file was deleted, and then on again.
+ // the swapfile was deleted, and then on again.
mf_set_dirty(mfp);
break;
}
@@ -506,12 +535,12 @@ void ml_open_file(buf_T *buf)
no_wait_return--;
}
- // don't try to open a swap file again
+ // don't try to open a swapfile again
buf->b_may_swap = false;
}
-/// If still need to create a swap file, and starting to edit a not-readonly
-/// file, or reading into an existing buffer, create a swap file now.
+/// If still need to create a swapfile, and starting to edit a not-readonly
+/// file, or reading into an existing buffer, create a swapfile now.
///
/// @param newfile reading file into new buffer
void check_need_swap(bool newfile)
@@ -528,14 +557,15 @@ void check_need_swap(bool newfile)
/// Close memline for buffer 'buf'.
///
-/// @param del_file if true, delete the swap file
+/// @param del_file if true, delete the swapfile
void ml_close(buf_T *buf, int del_file)
{
if (buf->b_ml.ml_mfp == NULL) { // not open
return;
}
mf_close(buf->b_ml.ml_mfp, del_file); // close the .swp file
- if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY)) {
+ if (buf->b_ml.ml_line_lnum != 0
+ && (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED))) {
xfree(buf->b_ml.ml_line_ptr);
}
xfree(buf->b_ml.ml_stack);
@@ -579,20 +609,20 @@ void ml_timestamp(buf_T *buf)
}
/// Checks whether the IDs in b0 are valid.
-static bool ml_check_b0_id(ZERO_BL *b0p)
+static bool ml_check_b0_id(ZeroBlock *b0p)
FUNC_ATTR_NONNULL_ALL
{
return b0p->b0_id[0] == BLOCK0_ID0 && b0p->b0_id[1] == BLOCK0_ID1;
}
/// Checks whether all strings in b0 are valid (i.e. nul-terminated).
-static bool ml_check_b0_strings(ZERO_BL *b0p)
+static bool ml_check_b0_strings(ZeroBlock *b0p)
FUNC_ATTR_NONNULL_ALL
{
return (memchr(b0p->b0_version, NUL, 10)
&& memchr(b0p->b0_uname, NUL, B0_UNAME_SIZE)
&& memchr(b0p->b0_hname, NUL, B0_HNAME_SIZE)
- && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V1086
+ && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT));
}
/// Update the timestamp or the B0_SAME_DIR flag of the .swp file.
@@ -604,7 +634,7 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) {
return;
}
- ZERO_BL *b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
if (ml_check_b0_id(b0p) == FAIL) {
iemsg(_("E304: ml_upd_block0(): Didn't get block 0??"));
} else {
@@ -617,10 +647,10 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what)
mf_put(mfp, hp, true, false);
}
-/// Write file name and timestamp into block 0 of a swap file.
+/// Write file name and timestamp into block 0 of a swapfile.
/// Also set buf->b_mtime.
/// Don't use NameBuff[]!!!
-static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
+static void set_b0_fname(ZeroBlock *b0p, buf_T *buf)
{
if (buf->b_ffname == NULL) {
b0p->b0_fname[0] = NUL;
@@ -632,7 +662,7 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
// editing the same file on different machines over a network.
// First replace home dir path with "~/" with home_replace().
// Then insert the user name to get "~user/".
- home_replace(NULL, buf->b_ffname, (char *)b0p->b0_fname,
+ home_replace(NULL, buf->b_ffname, b0p->b0_fname,
B0_FNAME_SIZE_CRYPT, true);
if (b0p->b0_fname[0] == '~') {
// If there is no user name or it is too long, don't use "~/"
@@ -654,8 +684,8 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
buf->b_mtime_read = buf->b_mtime;
buf->b_mtime_read_ns = buf->b_mtime_ns;
} else {
- long_to_char(0L, b0p->b0_mtime);
- long_to_char(0L, b0p->b0_ino);
+ long_to_char(0, b0p->b0_mtime);
+ long_to_char(0, b0p->b0_ino);
buf->b_mtime = 0;
buf->b_mtime_ns = 0;
buf->b_mtime_read = 0;
@@ -669,11 +699,11 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf)
add_b0_fenc(b0p, curbuf);
}
-/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the
+/// Update the B0_SAME_DIR flag of the swapfile. It's set if the file and the
/// swapfile for "buf" are in the same directory.
/// This is fail safe: if we are not sure the directories are equal the flag is
/// not set.
-static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
+static void set_b0_dir_flag(ZeroBlock *b0p, buf_T *buf)
{
if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) {
b0p->b0_flags |= B0_SAME_DIR;
@@ -683,7 +713,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf)
}
/// When there is room, add the 'fileencoding' to block zero.
-static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
+static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
{
const int size = B0_FNAME_SIZE_NOCRYPT;
@@ -691,48 +721,46 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf)
if ((int)strlen(b0p->b0_fname) + n + 1 > size) {
b0p->b0_flags = (char)(b0p->b0_flags & ~B0_HAS_FENC);
} else {
- memmove((char *)b0p->b0_fname + size - n,
+ memmove(b0p->b0_fname + size - n,
buf->b_p_fenc, (size_t)n);
*(b0p->b0_fname + size - n - 1) = NUL;
b0p->b0_flags |= B0_HAS_FENC;
}
}
-/// Return true if the process with number "b0p->b0_pid" is still running.
-/// "swap_fname" is the name of the swap file, if it's from before a reboot then
-/// the result is false;
-static bool swapfile_process_running(const ZERO_BL *b0p, const char *swap_fname)
+/// Returns the PID of the process that owns the swapfile, if it is running.
+///
+/// @param b0p swapfile data
+/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
+///
+/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
+static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
- // If the system rebooted after when the swap file was written then the
+ // If the system rebooted after when the swapfile was written then the
// process can't be running now.
if (os_fileinfo(swap_fname, &st)
&& uv_uptime(&uptime) == 0
&& (Timestamp)st.stat.st_mtim.tv_sec < os_time() - (Timestamp)uptime) {
- return false;
+ return 0;
}
- return os_proc_running((int)char_to_long(b0p->b0_pid));
+ int pid = (int)char_to_long(b0p->b0_pid);
+ return os_proc_running(pid) ? pid : 0;
}
/// Try to recover curbuf from the .swp file.
///
-/// @param checkext if true, check the extension and detect whether it is a
-/// swap file.
+/// @param checkext if true, check the extension and detect whether it is a swapfile.
void ml_recover(bool checkext)
{
buf_T *buf = NULL;
memfile_T *mfp = NULL;
char *fname_used = NULL;
bhdr_T *hp = NULL;
- ZERO_BL *b0p;
- int b0_ff;
char *b0_fenc = NULL;
- PTR_BL *pp;
- DATA_BL *dp;
infoptr_T *ip;
bool directly;
- char *p;
bool serious_error = true;
int orig_file_status = NOTDONE;
@@ -740,8 +768,8 @@ void ml_recover(bool checkext)
int called_from_main = (curbuf->b_ml.ml_mfp == NULL);
int attr = HL_ATTR(HLF_E);
- // If the file name ends in ".s[a-w][a-z]" we assume this is the swap file.
- // Otherwise a search is done to find the swap file(s).
+ // If the file name ends in ".s[a-w][a-z]" we assume this is the swapfile.
+ // Otherwise a search is done to find the swapfile(s).
char *fname = curbuf->b_fname;
if (fname == NULL) { // When there is no file name
fname = "";
@@ -756,18 +784,18 @@ void ml_recover(bool checkext)
} else {
directly = false;
- // count the number of matching swap files
- len = recover_names(fname, false, 0, NULL);
- if (len == 0) { // no swap files found
+ // count the number of matching swapfiles
+ len = recover_names(fname, false, NULL, 0, NULL);
+ if (len == 0) { // no swapfiles found
semsg(_("E305: No swap file found for %s"), fname);
goto theend;
}
int i;
- if (len == 1) { // one swap file found, use it
+ if (len == 1) { // one swapfile found, use it
i = 1;
- } else { // several swap files found, choose
- // list the names of the swap files
- (void)recover_names(fname, true, 0, NULL);
+ } else { // several swapfiles found, choose
+ // list the names of the swapfiles
+ (void)recover_names(fname, true, NULL, 0, NULL);
msg_putchar('\n');
msg_puts(_("Enter number of swap file to use (0 to quit): "));
i = get_number(false, NULL);
@@ -775,8 +803,8 @@ void ml_recover(bool checkext)
goto theend;
}
}
- // get the swap file name that will be used
- (void)recover_names(fname, false, i, &fname_used);
+ // get the swapfile name that will be used
+ (void)recover_names(fname, false, NULL, i, &fname_used);
}
if (fname_used == NULL) {
goto theend; // user chose invalid number.
@@ -786,7 +814,7 @@ void ml_recover(bool checkext)
getout(1);
}
- // Allocate a buffer structure for the swap file that is used for recovery.
+ // Allocate a buffer structure for the swapfile that is used for recovery.
// Only the memline in it is really used.
buf = xmalloc(sizeof(buf_T));
@@ -799,8 +827,8 @@ void ml_recover(bool checkext)
buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
- // open the memfile from the old swap file
- p = xstrdup(fname_used); // save "fname_used" for the message:
+ // open the memfile from the old swapfile
+ char *p = xstrdup(fname_used); // save "fname_used" for the message:
// mf_open() will consume "fname_used"!
mfp = mf_open(fname_used, O_RDONLY);
fname_used = p;
@@ -811,7 +839,7 @@ void ml_recover(bool checkext)
buf->b_ml.ml_mfp = mfp;
// The page size set in mf_open() might be different from the page size
- // used in the swap file, we must get it from block 0. But to read block
+ // used in the swapfile, we must get it from block 0. But to read block
// 0 we need a page size. Use the minimal size for block 0 here, it will
// be set to the real value below.
mfp->mf_page_size = MIN_SWAP_PAGE_SIZE;
@@ -820,16 +848,16 @@ void ml_recover(bool checkext)
if ((hp = mf_get(mfp, 0, 1)) == NULL) {
msg_start();
msg_puts_attr(_("Unable to read block 0 from "), attr | MSG_HIST);
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_("\nMaybe no changes were made or Vim did not update the swap file."),
attr | MSG_HIST);
msg_end();
goto theend;
}
- b0p = hp->bh_data;
+ ZeroBlock *b0p = hp->bh_data;
if (strncmp(b0p->b0_version, "VIM 3.0", 7) == 0) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, MSG_HIST);
+ msg_outtrans(mfp->mf_fname, MSG_HIST);
msg_puts_attr(_(" cannot be used with this version of Vim.\n"),
MSG_HIST);
msg_puts_attr(_("Use Vim version 3.0.\n"), MSG_HIST);
@@ -842,13 +870,13 @@ void ml_recover(bool checkext)
}
if (b0_magic_wrong(b0p)) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_(" cannot be used on this computer.\n"),
attr | MSG_HIST);
msg_puts_attr(_("The file was created on "), attr | MSG_HIST);
// avoid going past the end of a corrupted hostname
b0p->b0_fname[0] = NUL;
- msg_puts_attr((char *)b0p->b0_hname, attr | MSG_HIST);
+ msg_puts_attr(b0p->b0_hname, attr | MSG_HIST);
msg_puts_attr(_(",\nor the file has been damaged."), attr | MSG_HIST);
msg_end();
goto theend;
@@ -862,14 +890,14 @@ void ml_recover(bool checkext)
mf_new_page_size(mfp, (unsigned)char_to_long(b0p->b0_page_size));
if (mfp->mf_page_size < previous_page_size) {
msg_start();
- msg_outtrans_attr(mfp->mf_fname, attr | MSG_HIST);
+ msg_outtrans(mfp->mf_fname, attr | MSG_HIST);
msg_puts_attr(_(" has been damaged (page size is smaller than minimum value).\n"),
attr | MSG_HIST);
msg_end();
goto theend;
}
off_T size;
- if ((size = vim_lseek(mfp->mf_fd, (off_T)0L, SEEK_END)) <= 0) {
+ if ((size = vim_lseek(mfp->mf_fd, 0, SEEK_END)) <= 0) {
mfp->mf_blocknr_max = 0; // no file or empty file
} else {
mfp->mf_blocknr_max = size / mfp->mf_page_size;
@@ -884,29 +912,29 @@ void ml_recover(bool checkext)
b0p = hp->bh_data;
}
- // If .swp file name given directly, use name from swap file for buffer.
+ // If .swp file name given directly, use name from swapfile for buffer.
if (directly) {
- expand_env((char *)b0p->b0_fname, NameBuff, MAXPATHL);
+ expand_env(b0p->b0_fname, NameBuff, MAXPATHL);
if (setfname(curbuf, NameBuff, NULL, true) == FAIL) {
goto theend;
}
}
home_replace(NULL, mfp->mf_fname, NameBuff, MAXPATHL, true);
- smsg(_("Using swap file \"%s\""), NameBuff);
+ smsg(0, _("Using swap file \"%s\""), NameBuff);
if (buf_spname(curbuf) != NULL) {
xstrlcpy(NameBuff, buf_spname(curbuf), MAXPATHL);
} else {
home_replace(NULL, curbuf->b_ffname, NameBuff, MAXPATHL, true);
}
- smsg(_("Original file \"%s\""), NameBuff);
+ smsg(0, _("Original file \"%s\""), NameBuff);
msg_putchar('\n');
- // check date of swap file and original file
+ // check date of swapfile and original file
FileInfo org_file_info;
FileInfo swp_file_info;
- long mtime = char_to_long(b0p->b0_mtime);
+ int mtime = (int)char_to_long(b0p->b0_mtime);
if (curbuf->b_ffname != NULL
&& os_fileinfo(curbuf->b_ffname, &org_file_info)
&& ((os_fileinfo(mfp->mf_fname, &swp_file_info)
@@ -918,11 +946,11 @@ void ml_recover(bool checkext)
ui_flush();
// Get the 'fileformat' and 'fileencoding' from block zero.
- b0_ff = (b0p->b0_flags & B0_FF_MASK);
+ int b0_ff = (b0p->b0_flags & B0_FF_MASK);
if (b0p->b0_flags & B0_HAS_FENC) {
int fnsize = B0_FNAME_SIZE_NOCRYPT;
- for (p = (char *)b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {}
+ for (p = b0p->b0_fname + fnsize; p > b0p->b0_fname && p[-1] != NUL; p--) {}
b0_fenc = xstrnsave(p, (size_t)(b0p->b0_fname + fnsize - p));
}
@@ -932,22 +960,22 @@ void ml_recover(bool checkext)
// Now that we are sure that the file is going to be recovered, clear the
// contents of the current buffer.
while (!(curbuf->b_ml.ml_flags & ML_EMPTY)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
// Try reading the original file to obtain the values of 'fileformat',
// 'fileencoding', etc. Ignore errors. The text itself is not used.
if (curbuf->b_ffname != NULL) {
- orig_file_status = readfile(curbuf->b_ffname, NULL, (linenr_T)0,
- (linenr_T)0, (linenr_T)MAXLNUM, NULL, READ_NEW, false);
+ orig_file_status = readfile(curbuf->b_ffname, NULL, 0,
+ 0, MAXLNUM, NULL, READ_NEW, false);
}
- // Use the 'fileformat' and 'fileencoding' as stored in the swap file.
+ // Use the 'fileformat' and 'fileencoding' as stored in the swapfile.
if (b0_ff != 0) {
set_fileformat(b0_ff - 1, OPT_LOCAL);
}
if (b0_fenc != NULL) {
- set_option_value_give_err("fenc", 0L, b0_fenc, OPT_LOCAL);
+ set_option_value_give_err("fenc", CSTR_AS_OPTVAL(b0_fenc), OPT_LOCAL);
xfree(b0_fenc);
}
unchanged(curbuf, true, true);
@@ -957,10 +985,10 @@ void ml_recover(bool checkext)
linenr_T lnum = 0; // append after line 0 in curbuf
linenr_T line_count = 0;
int idx = 0; // start with first index in block 1
- long error = 0;
- buf->b_ml.ml_stack_top = 0; // -V1048
+ int error = 0;
+ buf->b_ml.ml_stack_top = 0;
buf->b_ml.ml_stack = NULL;
- buf->b_ml.ml_stack_size = 0; // -V1048
+ buf->b_ml.ml_stack_size = 0;
bool cannot_open = (curbuf->b_ffname == NULL);
@@ -976,11 +1004,23 @@ void ml_recover(bool checkext)
goto theend;
}
error++;
- ml_append(lnum++, _("???MANY LINES MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???MANY LINES MISSING"), 0, true);
} else { // there is a block
- pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
if (pp->pb_id == PTR_ID) { // it is a pointer block
+ bool ptr_block_error = false;
+ if (pp->pb_count_max != PB_COUNT_MAX(mfp)) {
+ ptr_block_error = true;
+ pp->pb_count_max = PB_COUNT_MAX(mfp);
+ }
+ if (pp->pb_count > pp->pb_count_max) {
+ ptr_block_error = true;
+ pp->pb_count = pp->pb_count_max;
+ }
+ if (ptr_block_error) {
+ emsg(_(e_warning_pointer_block_corrupted));
+ }
+
// check line count when using pointer block first time
if (idx == 0 && line_count != 0) {
for (int i = 0; i < (int)pp->pb_count; i++) {
@@ -988,14 +1028,12 @@ void ml_recover(bool checkext)
}
if (line_count != 0) {
error++;
- ml_append(lnum++, _("???LINE COUNT WRONG"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???LINE COUNT WRONG"), 0, true);
}
}
if (pp->pb_count == 0) {
- ml_append(lnum++, _("???EMPTY BLOCK"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???EMPTY BLOCK"), 0, true);
error++;
} else if (idx < (int)pp->pb_count) { // go a block deeper
if (pp->pb_pointer[idx].pe_bnum < 0) {
@@ -1014,8 +1052,7 @@ void ml_recover(bool checkext)
}
if (cannot_open) {
error++;
- ml_append(lnum++, _("???LINES MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???LINES MISSING"), 0, true);
}
idx++; // get same block again for next index
continue;
@@ -1034,7 +1071,7 @@ void ml_recover(bool checkext)
continue;
}
} else { // not a pointer block
- dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
if (dp->db_id != DATA_ID) { // block id wrong
if (bnum == 1) {
semsg(_("E310: Block 1 ID wrong (%s not a .swp file?)"),
@@ -1042,50 +1079,65 @@ void ml_recover(bool checkext)
goto theend;
}
error++;
- ml_append(lnum++, _("???BLOCK MISSING"),
- (colnr_T)0, true);
+ ml_append(lnum++, _("???BLOCK MISSING"), 0, true);
} else {
- // it is a data block
- // Append all the lines in this block
+ // It is a data block.
+ // Append all the lines in this block.
bool has_error = false;
- // check length of block
- // if wrong, use length in pointer block
+
+ // Check the length of the block.
+ // If wrong, use the length given in the pointer block.
if (page_count * mfp->mf_page_size != dp->db_txt_end) {
ml_append(lnum++,
_("??? from here until ???END lines" " may be messed up"),
- (colnr_T)0, true);
+ 0, true);
error++;
has_error = true;
dp->db_txt_end = page_count * mfp->mf_page_size;
}
- // make sure there is a NUL at the end of the block
- *((char_u *)dp + dp->db_txt_end - 1) = NUL;
+ // Make sure there is a NUL at the end of the block so we
+ // don't go over the end when copying text.
+ *((char *)dp + dp->db_txt_end - 1) = NUL;
- // check number of lines in block
- // if wrong, use count in data block
+ // Check the number of lines in the block.
+ // If wrong, use the count in the data block.
if (line_count != dp->db_line_count) {
ml_append(lnum++,
_("??? from here until ???END lines"
" may have been inserted/deleted"),
- (colnr_T)0, true);
+ 0, true);
error++;
has_error = true;
}
+ bool did_questions = false;
for (int i = 0; i < dp->db_line_count; i++) {
+ if ((char *)&(dp->db_index[i]) >= (char *)dp + dp->db_txt_start) {
+ // line count must be wrong
+ error++;
+ ml_append(lnum++, _("??? lines may be missing"), 0, true);
+ break;
+ }
+
int txt_start = (dp->db_index[i] & DB_INDEX_MASK);
if (txt_start <= (int)HEADER_SIZE
|| txt_start >= (int)dp->db_txt_end) {
- p = "???";
error++;
+ // avoid lots of lines with "???"
+ if (did_questions) {
+ continue;
+ }
+ did_questions = true;
+ p = "???";
} else {
+ did_questions = false;
p = (char *)dp + txt_start;
}
- ml_append(lnum++, p, (colnr_T)0, true);
+ ml_append(lnum++, p, 0, true);
}
if (has_error) {
- ml_append(lnum++, _("???END"), (colnr_T)0, true);
+ ml_append(lnum++, _("???END"), 0, true);
}
}
}
@@ -1111,7 +1163,7 @@ void ml_recover(bool checkext)
// Recovering an empty file results in two lines and the first line is
// empty. Don't set the modified flag then.
if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) {
- changed_internal();
+ changed_internal(curbuf);
buf_inc_changedtick(curbuf);
}
} else {
@@ -1121,7 +1173,7 @@ void ml_recover(bool checkext)
int i = strcmp(p, ml_get(idx + lnum));
xfree(p);
if (i != 0) {
- changed_internal();
+ changed_internal(curbuf);
buf_inc_changedtick(curbuf);
break;
}
@@ -1142,25 +1194,25 @@ void ml_recover(bool checkext)
emsg(_("E311: Recovery Interrupted"));
} else if (error) {
no_wait_return++;
- msg(">>>>>>>>>>>>>");
+ msg(">>>>>>>>>>>>>", 0);
emsg(_("E312: Errors detected while recovering; look for lines starting with ???"));
no_wait_return--;
- msg(_("See \":help E312\" for more information."));
- msg(">>>>>>>>>>>>>");
+ msg(_("See \":help E312\" for more information."), 0);
+ msg(">>>>>>>>>>>>>", 0);
} else {
if (curbuf->b_changed) {
- msg(_("Recovery completed. You should check if everything is OK."));
+ msg(_("Recovery completed. You should check if everything is OK."), 0);
msg_puts(_("\n(You might want to write out this file under another name\n"));
msg_puts(_("and run diff with the original file to check for changes)"));
} else {
- msg(_("Recovery completed. Buffer contents equals file contents."));
+ msg(_("Recovery completed. Buffer contents equals file contents."), 0);
}
msg_puts(_("\nYou may want to delete the .swp file now."));
if (swapfile_process_running(b0p, fname_used)) {
// Warn there could be an active Vim on the same file, the user may
// want to kill it.
msg_puts(_("\nNote: process STILL RUNNING: "));
- msg_outnum(char_to_long(b0p->b0_pid));
+ msg_outnum((int)char_to_long(b0p->b0_pid));
}
msg_puts("\n\n");
cmdline_row = msg_row;
@@ -1176,7 +1228,7 @@ theend:
}
mf_close(mfp, false); // will also xfree(mfp->mf_fname)
}
- if (buf != NULL) { // may be NULL if swap file not found.
+ if (buf != NULL) { // may be NULL if swapfile not found.
xfree(buf->b_ml.ml_stack);
xfree(buf);
}
@@ -1188,20 +1240,22 @@ theend:
}
}
-/// Find the names of swap files in current directory and the directory given
+/// Find the names of swapfiles in current directory and the directory given
/// with the 'directory' option.
///
/// Used to:
-/// - list the swap files for "vim -r"
-/// - count the number of swap files when recovering
-/// - list the swap files when recovering
-/// - find the name of the n'th swap file when recovering
-///
-/// @param fname base for swap file name
-/// @param list when true, list the swap file names
-/// @param nr when non-zero, return nr'th swap file name
+/// - list the swapfiles for "vim -r"
+/// - count the number of swapfiles when recovering
+/// - list the swapfiles when recovering
+/// - list the swapfiles for swapfilelist()
+/// - find the name of the n'th swapfile when recovering
+///
+/// @param fname base for swapfile name
+/// @param do_list when true, list the swapfile names
+/// @param ret_list when not NULL add file names to it
+/// @param nr when non-zero, return nr'th swapfile name
/// @param fname_out result when "nr" > 0
-int recover_names(char *fname, int list, int nr, char **fname_out)
+int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fname_out)
{
int num_names;
char *(names[6]);
@@ -1216,18 +1270,17 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
if (fname != NULL) {
#ifdef HAVE_READLINK
- // Expand symlink in the file name, because the swap file is created
+ // Expand symlink in the file name, because the swapfile is created
// with the actual file instead of with the symlink.
- if (resolve_symlink(fname, fname_buf) == OK) {
- fname_res = fname_buf;
- } else
-#endif
+ fname_res = (resolve_symlink(fname, fname_buf) == OK) ? fname_buf : fname;
+#else
fname_res = fname;
+#endif
}
- if (list) {
+ if (do_list) {
// use msg() to start the scrolling properly
- msg(_("Swap files found:"));
+ msg(_("Swap files found:"), 0);
msg_putchar('\n');
}
@@ -1285,9 +1338,9 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
num_files = 0;
}
- // When no swap file found, wildcard expansion might have failed (e.g.
+ // When no swapfile found, wildcard expansion might have failed (e.g.
// not able to execute the shell).
- // Try finding a swap file by simply adding ".swp" to the file name.
+ // Try finding a swapfile by simply adding ".swp" to the file name.
if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) {
char *swapname = modname(fname_res, ".swp", true);
if (swapname != NULL) {
@@ -1301,9 +1354,11 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
}
}
- // remove swapfile name of the current buffer, it must be ignored
+ // Remove swapfile name of the current buffer, it must be ignored.
+ // But keep it for swapfilelist().
if (curbuf->b_ml.ml_mfp != NULL
- && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL) {
+ && (p = curbuf->b_ml.ml_mfp->mf_fname) != NULL
+ && ret_list == NULL) {
for (int i = 0; i < num_files; i++) {
// Do not expand wildcards, on Windows would try to expand
// "%tmp%" in "%tmp%file"
@@ -1328,7 +1383,7 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
*fname_out = xstrdup(files[nr - 1 + num_files - file_count]);
dirp = ""; // stop searching
}
- } else if (list) {
+ } else if (do_list) {
if (dir_name[0] == '.' && dir_name[1] == NUL) {
if (fname == NULL) {
msg_puts(_(" In current directory:\n"));
@@ -1343,10 +1398,10 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
if (num_files) {
for (int i = 0; i < num_files; i++) {
- // print the swap file name
- msg_outnum((long)++file_count);
+ // print the swapfile name
+ msg_outnum(++file_count);
msg_puts(". ");
- msg_puts((const char *)path_tail(files[i]));
+ msg_puts(path_tail(files[i]));
msg_putchar('\n');
(void)swapfile_info(files[i]);
}
@@ -1354,6 +1409,11 @@ int recover_names(char *fname, int list, int nr, char **fname_out)
msg_puts(_(" -- none --\n"));
}
ui_flush();
+ } else if (ret_list != NULL) {
+ for (int i = 0; i < num_files; i++) {
+ char *name = concat_fnames(dir_name, files[i], true);
+ tv_list_append_allocated_string(ret_list, name);
+ }
} else {
file_count += num_files;
}
@@ -1392,15 +1452,16 @@ char *make_percent_swname(const char *dir, const char *name)
return d;
}
-static bool process_still_running;
+// PID of swapfile owner, or zero if not running.
+static int process_running;
-/// This is used by the swapinfo() function.
+/// For Vimscript "swapinfo()".
///
/// @return information found in swapfile "fname" in dictionary "d".
-void get_b0_dict(const char *fname, dict_T *d)
+void swapfile_dict(const char *fname, dict_T *d)
{
int fd;
- struct block0 b0;
+ ZeroBlock b0;
if ((fd = os_open(fname, O_RDONLY, 0)) >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
@@ -1411,14 +1472,14 @@ void get_b0_dict(const char *fname, dict_T *d)
} else {
// We have swap information.
tv_dict_add_str_len(d, S_LEN("version"), b0.b0_version, 10);
- tv_dict_add_str_len(d, S_LEN("user"), (char *)b0.b0_uname,
+ tv_dict_add_str_len(d, S_LEN("user"), b0.b0_uname,
B0_UNAME_SIZE);
- tv_dict_add_str_len(d, S_LEN("host"), (char *)b0.b0_hname,
+ tv_dict_add_str_len(d, S_LEN("host"), b0.b0_hname,
B0_HNAME_SIZE);
- tv_dict_add_str_len(d, S_LEN("fname"), (char *)b0.b0_fname,
+ tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
- tv_dict_add_nr(d, S_LEN("pid"), char_to_long(b0.b0_pid));
+ tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@@ -1432,27 +1493,26 @@ void get_b0_dict(const char *fname, dict_T *d)
}
}
-/// Give information about an existing swap file.
+/// Loads info from swapfile `fname`, and displays it to the user.
///
/// @return timestamp (0 when unknown).
static time_t swapfile_info(char *fname)
{
assert(fname != NULL);
- int fd;
- struct block0 b0;
+ ZeroBlock b0;
time_t x = (time_t)0;
#ifdef UNIX
char uname[B0_UNAME_SIZE];
#endif
- // print the swap file date
+ // print the swapfile date
FileInfo file_info;
if (os_fileinfo(fname, &file_info)) {
#ifdef UNIX
// print name of owner of the file
if (os_get_uname((uv_uid_t)file_info.stat.st_uid, uname, B0_UNAME_SIZE) == OK) {
msg_puts(_(" owned by: "));
- msg_outtrans(uname);
+ msg_outtrans(uname, 0);
msg_puts(_(" dated: "));
} else {
msg_puts(_(" dated: "));
@@ -1466,7 +1526,7 @@ static time_t swapfile_info(char *fname)
}
// print the original file name
- fd = os_open(fname, O_RDONLY, 0);
+ int fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
if (strncmp(b0.b0_version, "VIM 3.0", 7) == 0) {
@@ -1480,7 +1540,7 @@ static time_t swapfile_info(char *fname)
if (b0.b0_fname[0] == NUL) {
msg_puts(_("[No Name]"));
} else {
- msg_outtrans((char *)b0.b0_fname);
+ msg_outtrans(b0.b0_fname, 0);
}
msg_puts(_("\n modified: "));
@@ -1488,7 +1548,7 @@ static time_t swapfile_info(char *fname)
if (*(b0.b0_uname) != NUL) {
msg_puts(_("\n user name: "));
- msg_outtrans((char *)b0.b0_uname);
+ msg_outtrans(b0.b0_uname, 0);
}
if (*(b0.b0_hname) != NUL) {
@@ -1497,15 +1557,14 @@ static time_t swapfile_info(char *fname)
} else {
msg_puts(_("\n host name: "));
}
- msg_outtrans((char *)b0.b0_hname);
+ msg_outtrans(b0.b0_hname, 0);
}
- if (char_to_long(b0.b0_pid) != 0L) {
+ if (char_to_long(b0.b0_pid) != 0) {
msg_puts(_("\n process ID: "));
- msg_outnum(char_to_long(b0.b0_pid));
- if (swapfile_process_running(&b0, (const char *)fname)) {
+ msg_outnum((int)char_to_long(b0.b0_pid));
+ if ((process_running = swapfile_process_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
- process_still_running = true;
}
}
@@ -1525,13 +1584,12 @@ static time_t swapfile_info(char *fname)
return x;
}
-/// @return true if the swap file looks OK and there are no changes, thus it
-/// can be safely deleted.
+/// @return true if the swapfile looks OK and there are no changes, thus it can be safely deleted.
static bool swapfile_unchanged(char *fname)
{
- struct block0 b0;
+ ZeroBlock b0;
- // Swap file must exist.
+ // Swapfile must exist.
if (!os_path_exists(fname)) {
return false;
}
@@ -1573,7 +1631,7 @@ static bool swapfile_unchanged(char *fname)
}
// process must be known and not running.
- if (char_to_long(b0.b0_pid) == 0L || swapfile_process_running(&b0, fname)) {
+ if (char_to_long(b0.b0_pid) == 0 || swapfile_process_running(&b0, fname)) {
ret = false;
}
@@ -1589,7 +1647,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
{
int num_names = 0;
- // May also add the file name with a dot prepended, for swap file in same
+ // May also add the file name with a dot prepended, for swapfile in same
// dir as original file.
if (prepend_dot) {
names[num_names] = modname(path, ".sw?", true);
@@ -1599,7 +1657,7 @@ static int recov_file_names(char **names, char *path, int prepend_dot)
num_names++;
}
- // Form the normal swap file name pattern by appending ".sw?".
+ // Form the normal swapfile name pattern by appending ".sw?".
names[num_names] = concat_fnames(path, ".sw?", false);
if (num_names >= 1) { // check if we have the same name twice
char *p = names[num_names - 1];
@@ -1633,7 +1691,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
}
ml_flush_line(buf); // flush buffered line
// flush locked block
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH);
+ (void)ml_find_line(buf, 0, ML_FLUSH);
if (bufIsChanged(buf) && check_file && mf_need_trans(buf->b_ml.ml_mfp)
&& buf->b_ffname != NULL) {
// If the original file does not exist anymore or has been changed
@@ -1648,7 +1706,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
need_check_timestamps = true; // give message later
}
}
- if (buf->b_ml.ml_mfp->mf_dirty) {
+ if (buf->b_ml.ml_mfp->mf_dirty == MF_DIRTY_YES) {
(void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0)
| (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0));
if (check_char && os_char_avail()) { // character available now
@@ -1660,7 +1718,7 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync)
/// sync one buffer, including negative blocks
///
-/// after this all the blocks are in the swap file
+/// after this all the blocks are in the swapfile
///
/// Used for the :preserve command and when the original file has been
/// changed or deleted.
@@ -1683,7 +1741,7 @@ void ml_preserve(buf_T *buf, int message, bool do_fsync)
got_int = false;
ml_flush_line(buf); // flush buffered line
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush locked block
int status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0));
// stack is invalid after mf_sync(.., MFS_ALL)
@@ -1710,7 +1768,7 @@ void ml_preserve(buf_T *buf, int message, bool do_fsync)
CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum");
lnum = buf->b_ml.ml_locked_high + 1;
}
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush locked block
// sync the updated pointer blocks
if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) {
status = FAIL;
@@ -1722,7 +1780,7 @@ theend:
if (message) {
if (status == OK) {
- msg(_("File preserved"));
+ msg(_("File preserved"), 0);
} else {
emsg(_("E314: Preserve failed"));
}
@@ -1735,20 +1793,40 @@ theend:
// line2 = ml_get(2); // line1 is now invalid!
// Make a copy of the line if necessary.
-/// @return a pointer to a (read-only copy of a) line.
+/// @return a pointer to a (read-only copy of a) line in curbuf.
///
/// On failure an error message is given and IObuff is returned (to avoid
/// having to check for error everywhere).
char *ml_get(linenr_T lnum)
{
- return ml_get_buf(curbuf, lnum, false);
+ return ml_get_buf_impl(curbuf, lnum, false);
+}
+
+/// @return a pointer to a (read-only copy of a) line.
+///
+/// This is the same as ml_get(), but taking in the buffer
+/// as an argument.
+char *ml_get_buf(buf_T *buf, linenr_T lnum)
+{
+ return ml_get_buf_impl(buf, lnum, false);
+}
+
+/// Like `ml_get_buf`, but allow the line to be mutated in place.
+///
+/// This is very limited. Generally ml_replace_buf()
+/// should be used to modify a line.
+///
+/// @return a pointer to a line in the buffer
+char *ml_get_buf_mut(buf_T *buf, linenr_T lnum)
+{
+ return ml_get_buf_impl(buf, lnum, true);
}
/// @return pointer to position "pos".
char *ml_get_pos(const pos_T *pos)
FUNC_ATTR_NONNULL_ALL
{
- return ml_get_buf(curbuf, pos->lnum, false) + pos->col;
+ return ml_get_buf(curbuf, pos->lnum) + pos->col;
}
/// @return codepoint at pos. pos must be either valid or have col set to MAXCOL!
@@ -1765,7 +1843,7 @@ int gchar_pos(pos_T *pos)
/// @param will_change true mark the buffer dirty (chars in the line will be changed)
///
/// @return a pointer to a line in a specific buffer
-char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change)
+static char *ml_get_buf_impl(buf_T *buf, linenr_T lnum, bool will_change)
FUNC_ATTR_NONNULL_ALL
{
static int recursive = 0;
@@ -1776,11 +1854,10 @@ char *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change)
// Avoid giving this message for a recursive call, may happen when
// the GUI redraws part of the text.
recursive++;
- siemsg(_("E315: ml_get: invalid lnum: %" PRId64), (int64_t)lnum);
+ siemsg(_(e_ml_get_invalid_lnum_nr), (int64_t)lnum);
recursive--;
}
ml_flush_line(buf);
- buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
errorret:
STRCPY(questions, "???");
buf->b_ml.ml_line_lnum = lnum;
@@ -1812,30 +1889,48 @@ errorret:
recursive++;
get_trans_bufname(buf);
shorten_dir(NameBuff);
- siemsg(_("E316: ml_get: cannot find line %" PRId64 " in buffer %d %s"),
+ siemsg(_(e_ml_get_cannot_find_line_nr_in_buffer_nr_str),
(int64_t)lnum, buf->b_fnum, NameBuff);
recursive--;
}
goto errorret;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
char *ptr = (char *)dp + (dp->db_index[lnum - buf->b_ml.ml_locked_low] & DB_INDEX_MASK);
buf->b_ml.ml_line_ptr = ptr;
buf->b_ml.ml_line_lnum = lnum;
- buf->b_ml.ml_flags &= ~ML_LINE_DIRTY;
+ buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED);
}
if (will_change) {
buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
+#ifdef ML_GET_ALLOC_LINES
+ if (buf->b_ml.ml_flags & ML_ALLOCATED) {
+ // can't make the change in the data block
+ buf->b_ml.ml_flags |= ML_LINE_DIRTY;
+ }
+#endif
ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
}
+#ifdef ML_GET_ALLOC_LINES
+ if ((buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) == 0) {
+ // make sure the text is in allocated memory
+ buf->b_ml.ml_line_ptr = xstrdup(buf->b_ml.ml_line_ptr);
+ buf->b_ml.ml_flags |= ML_ALLOCATED;
+ if (will_change) {
+ // can't make the change in the data block
+ buf->b_ml.ml_flags |= ML_LINE_DIRTY;
+ }
+ }
+#endif
return buf->b_ml.ml_line_ptr;
}
/// Check if a line that was just obtained by a call to ml_get
/// is in allocated memory.
+/// This ignores ML_ALLOCATED to get the same behavior as without ML_GET_ALLOC_LINES.
int ml_line_alloced(void)
{
return curbuf->b_ml.ml_flags & ML_LINE_DIRTY;
@@ -1917,7 +2012,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// This also fills the stack with the blocks from the root to the data block
// This also releases any locked block.
bhdr_T *hp;
- if ((hp = ml_find_line(buf, lnum == 0 ? (linenr_T)1 : lnum,
+ if ((hp = ml_find_line(buf, lnum == 0 ? 1 : lnum,
ML_INSERT)) == NULL) {
return FAIL;
}
@@ -1933,7 +2028,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// get line count (number of indexes in current block) before the insertion
int line_count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low;
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
// If
// - there is not enough room in the current block
@@ -2016,14 +2111,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
int lines_moved;
int data_moved = 0; // init to shut up gcc
int total_moved = 0; // init to shut up gcc
- DATA_BL *dp_right, *dp_left;
int stack_idx;
bool in_left;
- int lineadd;
- blocknr_T bnum_left, bnum_right;
linenr_T lnum_left, lnum_right;
- int pb_idx;
- PTR_BL *pp_new;
+ PointerBlock *pp_new;
// We are going to allocate a new data block. Depending on the
// situation it will be put to the left or right of the existing
@@ -2067,10 +2158,10 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
line_count_left = line_count;
line_count_right = 0;
}
- dp_right = hp_right->bh_data;
- dp_left = hp_left->bh_data;
- bnum_left = hp_left->bh_bnum;
- bnum_right = hp_right->bh_bnum;
+ DataBlock *dp_right = hp_right->bh_data;
+ DataBlock *dp_left = hp_left->bh_data;
+ blocknr_T bnum_left = hp_left->bh_bnum;
+ blocknr_T bnum_right = hp_right->bh_bnum;
page_count_left = (int)hp_left->bh_page_count;
page_count_right = (int)hp_right->bh_page_count;
@@ -2149,20 +2240,20 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// flush the old data block
// set ml_locked_lineadd to 0, because the updating of the
// pointer blocks is done below
- lineadd = buf->b_ml.ml_locked_lineadd;
+ int lineadd = buf->b_ml.ml_locked_lineadd;
buf->b_ml.ml_locked_lineadd = 0;
- (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush data block
+ (void)ml_find_line(buf, 0, ML_FLUSH); // flush data block
// update pointer blocks for the new data block
for (stack_idx = buf->b_ml.ml_stack_top - 1; stack_idx >= 0; stack_idx--) {
infoptr_T *ip = &(buf->b_ml.ml_stack[stack_idx]);
- pb_idx = ip->ip_index;
+ int pb_idx = ip->ip_index;
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
return FAIL;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong 3"));
+ iemsg(_(e_pointer_block_id_wrong_three));
mf_put(mfp, hp, false, false);
return FAIL;
}
@@ -2173,7 +2264,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (pb_idx + 1 < (int)pp->pb_count) {
memmove(&pp->pb_pointer[pb_idx + 2],
&pp->pb_pointer[pb_idx + 1],
- (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PTR_EN));
+ (size_t)(pp->pb_count - pb_idx - 1) * sizeof(PointerEntry));
}
pp->pb_count++;
pp->pb_pointer[pb_idx].pe_line_count = line_count_left;
@@ -2212,7 +2303,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
// allocate a new pointer block
// move some of the pointer into the new block
// prepare for updating the parent block
- for (;;) { // do this twice when splitting block 1
+ while (true) { // do this twice when splitting block 1
hp_new = ml_new_ptr(mfp);
if (hp_new == NULL) { // TODO(vim): try to fix tree
return FAIL;
@@ -2246,7 +2337,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (total_moved) {
memmove(&pp_new->pb_pointer[0],
&pp->pb_pointer[pb_idx + 1],
- (size_t)(total_moved) * sizeof(PTR_EN));
+ (size_t)(total_moved) * sizeof(PointerEntry));
pp_new->pb_count = (uint16_t)total_moved;
pp->pb_count = (uint16_t)(pp->pb_count - (total_moved - 1));
pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right;
@@ -2297,7 +2388,7 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
}
// The line was inserted below 'lnum'
- ml_updatechunk(buf, lnum + 1, (long)len, ML_CHNK_ADDLINE);
+ ml_updatechunk(buf, lnum + 1, len, ML_CHNK_ADDLINE);
return OK;
}
@@ -2315,13 +2406,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char *ptr, ssize_t len)
if (len == -1 || len > maxlen) {
len = maxlen;
}
- curbuf->deleted_bytes += (size_t)len + 1;
- curbuf->deleted_bytes2 += (size_t)len + 1;
- if (curbuf->update_need_codepoints) {
- mb_utflen(ptr, (size_t)len, &curbuf->deleted_codepoints,
- &curbuf->deleted_codeunits);
- curbuf->deleted_codepoints++; // NL char
- curbuf->deleted_codeunits++;
+ buf->deleted_bytes += (size_t)len + 1;
+ buf->deleted_bytes2 += (size_t)len + 1;
+ if (buf->update_need_codepoints) {
+ mb_utflen(ptr, (size_t)len, &buf->deleted_codepoints,
+ &buf->deleted_codeunits);
+ buf->deleted_codepoints++; // NL char
+ buf->deleted_codeunits++;
}
}
@@ -2353,22 +2444,21 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char *line, bool copy)
return FAIL;
}
- bool readlen = true;
-
if (copy) {
line = xstrdup(line);
}
- if (buf->b_ml.ml_line_lnum != lnum) { // other line buffered
- ml_flush_line(buf); // flush it
- } else if (buf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated
- ml_add_deleted_len_buf(buf, buf->b_ml.ml_line_ptr, -1);
- readlen = false; // already added the length
- xfree(buf->b_ml.ml_line_ptr); // free it
+ if (buf->b_ml.ml_line_lnum != lnum) {
+ // another line is buffered, flush it
+ ml_flush_line(buf);
+ }
+
+ if (kv_size(buf->update_callbacks)) {
+ ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum), -1);
}
- if (readlen && kv_size(buf->update_callbacks)) {
- ml_add_deleted_len_buf(buf, ml_get_buf(buf, lnum, false), -1);
+ if (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) {
+ xfree(buf->b_ml.ml_line_ptr); // free allocated line
}
buf->b_ml.ml_line_ptr = line;
@@ -2392,6 +2482,19 @@ int ml_delete(linenr_T lnum, bool message)
return ml_delete_int(curbuf, lnum, message);
}
+/// Delete line `lnum` in buffer
+///
+/// @note The caller of this function should probably also call changed_lines() after this.
+///
+/// @param message Show "--No lines in buffer--" message.
+///
+/// @return FAIL for failure, OK otherwise
+int ml_delete_buf(buf_T *buf, linenr_T lnum, bool message)
+{
+ ml_flush_line(buf);
+ return ml_delete_int(buf, lnum, message);
+}
+
static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
{
if (lnum < 1 || lnum > buf->b_ml.ml_line_count) {
@@ -2408,7 +2511,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
set_keep_msg(_(no_lines_msg), 0);
}
- int i = ml_replace((linenr_T)1, "", true);
+ int i = ml_replace_buf(buf, 1, "", true);
buf->b_ml.ml_flags |= ML_EMPTY;
return i;
@@ -2427,7 +2530,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
return FAIL;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
// compute line count (number of entries in block) before the delete
int count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 2;
int idx = lnum - buf->b_ml.ml_locked_low;
@@ -2435,11 +2538,11 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
buf->b_ml.ml_line_count--;
int line_start = ((dp->db_index[idx]) & DB_INDEX_MASK);
- long line_size;
+ int line_size;
if (idx == 0) { // first line in block, text at the end
- line_size = dp->db_txt_end - (unsigned)line_start;
+ line_size = (int)(dp->db_txt_end - (unsigned)line_start);
} else {
- line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - (unsigned)line_start;
+ line_size = (int)(((dp->db_index[idx - 1]) & DB_INDEX_MASK) - (unsigned)line_start);
}
// Line should always have an NL char internally (represented as NUL),
@@ -2464,9 +2567,9 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
return FAIL;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong 4"));
+ iemsg(_(e_pointer_block_id_wrong_four));
mf_put(mfp, hp, false, false);
return FAIL;
}
@@ -2476,7 +2579,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message)
} else {
if (count != idx) { // move entries after the deleted one
memmove(&pp->pb_pointer[idx], &pp->pb_pointer[idx + 1],
- (size_t)(count - idx) * sizeof(PTR_EN));
+ (size_t)(count - idx) * sizeof(PointerEntry));
}
mf_put(mfp, hp, true, false);
@@ -2536,7 +2639,7 @@ void ml_setmarked(linenr_T lnum)
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
return; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_index[lnum - curbuf->b_ml.ml_locked_low] |= DB_MARKED;
curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY;
}
@@ -2545,7 +2648,7 @@ void ml_setmarked(linenr_T lnum)
linenr_T ml_firstmarked(void)
{
if (curbuf->b_ml.ml_mfp == NULL) {
- return (linenr_T)0;
+ return 0;
}
// The search starts with lowest_marked line. This is the last line where
@@ -2556,9 +2659,9 @@ linenr_T ml_firstmarked(void)
// block This also releases any locked block.
bhdr_T *hp;
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
- return (linenr_T)0; // give error message?
+ return 0; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
for (int i = lnum - curbuf->b_ml.ml_locked_low;
lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) {
@@ -2571,7 +2674,7 @@ linenr_T ml_firstmarked(void)
}
}
- return (linenr_T)0;
+ return 0;
}
/// clear all DB_MARKED flags
@@ -2590,7 +2693,7 @@ void ml_clearmarked(void)
if ((hp = ml_find_line(curbuf, lnum, ML_FIND)) == NULL) {
return; // give error message?
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
for (int i = lnum - curbuf->b_ml.ml_locked_low;
lnum <= curbuf->b_ml.ml_locked_high; i++, lnum++) {
@@ -2639,7 +2742,7 @@ static void ml_flush_line(buf_T *buf)
if (hp == NULL) {
siemsg(_("E320: Cannot find line %" PRId64), (int64_t)lnum);
} else {
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
int idx = lnum - buf->b_ml.ml_locked_low;
int start = ((dp->db_index[idx]) & DB_INDEX_MASK);
char *old_line = (char *)dp + start;
@@ -2677,7 +2780,7 @@ static void ml_flush_line(buf_T *buf)
memmove(old_line - extra, new_line, (size_t)new_len);
buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS);
// The else case is already covered by the insert and delete
- ml_updatechunk(buf, lnum, (long)extra, ML_CHNK_UPDLINE);
+ ml_updatechunk(buf, lnum, extra, ML_CHNK_UPDLINE);
} else {
// Cannot do it in one data block: Delete and append.
// Append first, because ml_delete_int() cannot delete the
@@ -2693,8 +2796,11 @@ static void ml_flush_line(buf_T *buf)
xfree(new_line);
entered = false;
+ } else if (buf->b_ml.ml_flags & ML_ALLOCATED) {
+ xfree(buf->b_ml.ml_line_ptr);
}
+ buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED);
buf->b_ml.ml_line_lnum = 0;
buf->b_ml.ml_line_offset = 0;
}
@@ -2704,7 +2810,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
{
assert(page_count >= 0);
bhdr_T *hp = mf_new(mfp, negative, (unsigned)page_count);
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
dp->db_id = DATA_ID;
dp->db_txt_start = dp->db_txt_end = (unsigned)page_count * mfp->mf_page_size;
dp->db_free = dp->db_txt_start - (unsigned)HEADER_SIZE;
@@ -2717,11 +2823,10 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count)
static bhdr_T *ml_new_ptr(memfile_T *mfp)
{
bhdr_T *hp = mf_new(mfp, false, 1);
- PTR_BL *pp = hp->bh_data;
+ PointerBlock *pp = hp->bh_data;
pp->pb_id = PTR_ID;
pp->pb_count = 0;
- pp->pb_count_max
- = (uint16_t)((mfp->mf_page_size - offsetof(PTR_BL, pb_pointer)) / sizeof(PTR_EN));
+ pp->pb_count_max = PB_COUNT_MAX(mfp);
return hp;
}
@@ -2741,7 +2846,6 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp)
/// @return NULL for failure, pointer to block header otherwise
static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
{
- PTR_BL *pp;
bhdr_T *hp;
int top;
@@ -2806,7 +2910,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
buf->b_ml.ml_stack_top = 0; // start at the root
}
// search downwards in the tree until a data block is found
- for (;;) {
+ while (true) {
if ((hp = mf_get(mfp, bnum, (unsigned)page_count)) == NULL) {
goto error_noblock;
}
@@ -2818,7 +2922,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
high--;
}
- DATA_BL *dp = hp->bh_data;
+ DataBlock *dp = hp->bh_data;
if (dp->db_id == DATA_ID) { // data block
buf->b_ml.ml_locked = hp;
buf->b_ml.ml_locked_low = low;
@@ -2828,9 +2932,9 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
return hp;
}
- pp = (PTR_BL *)(dp); // must be pointer block
+ PointerBlock *pp = (PointerBlock *)(dp); // must be pointer block
if (pp->pb_id != PTR_ID) {
- iemsg(_("E317: pointer block id wrong"));
+ iemsg(_(e_pointer_block_id_wrong));
goto error_block;
}
@@ -2868,10 +2972,10 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action)
}
if (idx >= (int)pp->pb_count) { // past the end: something wrong!
if (lnum > buf->b_ml.ml_line_count) {
- siemsg(_("E322: line number out of range: %" PRId64 " past the end"),
+ siemsg(_(e_line_number_out_of_range_nr_past_the_end),
(int64_t)lnum - buf->b_ml.ml_line_count);
} else {
- siemsg(_("E323: line count wrong in block %" PRId64), bnum);
+ siemsg(_(e_line_count_wrong_in_block_nr), bnum);
}
goto error_block;
}
@@ -2938,10 +3042,10 @@ static void ml_lineadd(buf_T *buf, int count)
if ((hp = mf_get(mfp, ip->ip_bnum, 1)) == NULL) {
break;
}
- PTR_BL *pp = hp->bh_data; // must be pointer block
+ PointerBlock *pp = hp->bh_data; // must be pointer block
if (pp->pb_id != PTR_ID) {
mf_put(mfp, hp, false, false);
- iemsg(_("E317: pointer block id wrong 2"));
+ iemsg(_(e_pointer_block_id_wrong_two));
break;
}
pp->pb_pointer[ip->ip_index].pe_line_count += count;
@@ -2970,7 +3074,7 @@ int resolve_symlink(const char *fname, char *buf)
// Put the result so far in tmp[], starting with the original name.
xstrlcpy(tmp, fname, MAXPATHL);
- for (;;) {
+ while (true) {
// Limit symlink depth to 100, catch recursive loops.
if (++depth == 100) {
semsg(_("E773: Symlink loop for \"%s\""), fname);
@@ -3018,7 +3122,7 @@ int resolve_symlink(const char *fname, char *buf)
}
#endif
-/// Make swap file name out of the file name and a directory name.
+/// Make swapfile name out of the file name and a directory name.
///
/// @return pointer to allocated memory or NULL.
char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
@@ -3027,9 +3131,9 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
#ifdef HAVE_READLINK
char fname_buf[MAXPATHL];
- // Expand symlink in the file name, so that we put the swap file with the
+ // Expand symlink in the file name, so that we put the swapfile with the
// actual file instead of with the symlink.
- if (resolve_symlink(fname, (char *)fname_buf) == OK) {
+ if (resolve_symlink(fname, fname_buf) == OK) {
fname_res = fname_buf;
}
#endif
@@ -3048,7 +3152,7 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return r;
}
- // Prepend a '.' to the swap file name for the current directory.
+ // Prepend a '.' to the swapfile name for the current directory.
char *r = modname(fname_res, ".swp",
dir_name[0] == '.' && dir_name[1] == NUL);
if (r == NULL) { // out of memory
@@ -3060,14 +3164,11 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
return s;
}
-/// Get file name to use for swap file or backup file.
-/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir'
-/// option "dname".
-/// - If "dname" is ".", return "fname" (swap file in dir of file).
-/// - If "dname" starts with "./", insert "dname" in "fname" (swap file
-/// relative to dir of file).
-/// - Otherwise, prepend "dname" to the tail of "fname" (swap file in specific
-/// dir).
+/// Get file name to use for swapfile or backup file.
+/// Use the name of the edited file "fname" and an entry in the 'dir' or 'bdir' option "dname".
+/// - If "dname" is ".", return "fname" (swapfile in dir of file).
+/// - If "dname" starts with "./", insert "dname" in "fname" (swapfile relative to dir of file).
+/// - Otherwise, prepend "dname" to the tail of "fname" (swapfile in specific dir).
///
/// The return value is an allocated string and can be NULL.
///
@@ -3098,10 +3199,10 @@ char *get_file_in_dir(char *fname, char *dname)
return retval;
}
-/// Print the ATTENTION message: info about an existing swap file.
+/// Print the ATTENTION message: info about an existing swapfile.
///
/// @param buf buffer being edited
-/// @param fname swap file name
+/// @param fname swapfile name
static void attention_message(buf_T *buf, char *fname)
{
assert(buf->b_fname != NULL);
@@ -3113,7 +3214,7 @@ static void attention_message(buf_T *buf, char *fname)
msg_puts("\"\n");
const time_t swap_mtime = swapfile_info(fname);
msg_puts(_("While opening file \""));
- msg_outtrans(buf->b_fname);
+ msg_outtrans(buf->b_fname, 0);
msg_puts("\"\n");
FileInfo file_info;
if (!os_fileinfo(buf->b_fname, &file_info)) {
@@ -3135,10 +3236,10 @@ static void attention_message(buf_T *buf, char *fname)
" Quit, or continue with caution.\n"));
msg_puts(_("(2) An edit session for this file crashed.\n"));
msg_puts(_(" If this is the case, use \":recover\" or \"vim -r "));
- msg_outtrans(buf->b_fname);
+ msg_outtrans(buf->b_fname, 0);
msg_puts(_("\"\n to recover the changes (see \":help recovery\").\n"));
msg_puts(_(" If you did this already, delete the swap file \""));
- msg_outtrans(fname);
+ msg_outtrans(fname, 0);
msg_puts(_("\"\n to avoid this message.\n"));
cmdline_row = msg_row;
no_wait_return--;
@@ -3146,15 +3247,8 @@ static void attention_message(buf_T *buf, char *fname)
/// Trigger the SwapExists autocommands.
///
-/// @return a value for equivalent to do_dialog() (see below):
-/// 0: still need to ask for a choice
-/// 1: open read-only
-/// 2: edit anyway
-/// 3: recover
-/// 4: delete it
-/// 5: quit
-/// 6: abort
-static int do_swapexists(buf_T *buf, char *fname)
+/// @return a value for equivalent to do_dialog().
+static sea_choice_T do_swapexists(buf_T *buf, char *fname)
{
set_vim_var_string(VV_SWAPNAME, fname, -1);
set_vim_var_string(VV_SWAPCHOICE, NULL, -1);
@@ -3169,23 +3263,23 @@ static int do_swapexists(buf_T *buf, char *fname)
switch (*get_vim_var_str(VV_SWAPCHOICE)) {
case 'o':
- return 1;
+ return SEA_CHOICE_READONLY;
case 'e':
- return 2;
+ return SEA_CHOICE_EDIT;
case 'r':
- return 3;
+ return SEA_CHOICE_RECOVER;
case 'd':
- return 4;
+ return SEA_CHOICE_DELETE;
case 'q':
- return 5;
+ return SEA_CHOICE_QUIT;
case 'a':
- return 6;
+ return SEA_CHOICE_ABORT;
}
- return 0;
+ return SEA_CHOICE_NONE;
}
-/// Find out what name to use for the swap file for buffer 'buf'.
+/// Find out what name to use for the swapfile for buffer 'buf'.
///
/// Several names are tried to find one that does not exist. Last directory in
/// option is automatically created.
@@ -3194,24 +3288,23 @@ static int do_swapexists(buf_T *buf, char *fname)
/// not being able to open the swap or undo file.
/// @note May trigger SwapExists autocmd, pointers may change!
///
-/// @param[in] buf Buffer for which swap file names needs to be found.
+/// @param[in] buf Buffer for which swapfile names needs to be found.
/// @param[in,out] dirp Pointer to a list of directories. When out of memory,
/// is set to NULL. Is advanced to the next directory in
/// the list otherwise.
-/// @param[in] old_fname Allowed existing swap file name. Except for this
+/// @param[in] old_fname Allowed existing swapfile name. Except for this
/// case, name of the non-existing file is used.
/// @param[in,out] found_existing_dir If points to true, then new directory
-/// for swap file is not created. At first
+/// for swapfile is not created. At first
/// findswapname() call this argument must
/// point to false. This parameter may only
/// be set to true by this function, it is
/// never set to false.
///
-/// @return [allocated] Name of the swap file.
+/// @return [allocated] Name of the swapfile.
static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_existing_dir)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- size_t n;
char *buf_fname = buf->b_fname;
// Isolate a directory name from *dirp and put it in dir_name.
@@ -3220,10 +3313,11 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
char *dir_name = xmalloc(dir_len);
(void)copy_option_part(dirp, dir_name, dir_len, ",");
- // we try different names until we find one that does not exist yet
+ // We try different swapfile names until we find one that does not exist yet.
char *fname = makeswapname(buf_fname, buf->b_ffname, buf, dir_name);
- for (;;) {
+ while (true) {
+ size_t n;
if (fname == NULL) { // must be out of memory
break;
}
@@ -3232,7 +3326,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
break;
}
// check if the swapfile already exists
- // Extra security check: When a swap file is a symbolic link, this
+ // Extra security check: When a swapfile is a symbolic link, this
// is most likely a symlink attack.
FileInfo file_info;
bool file_or_link_found = os_fileinfo_link(fname, &file_info);
@@ -3251,37 +3345,36 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// Give an error message, unless recovering, no file name, we are
// viewing a help file or when the path of the file is different
// (happens when all .swp files are in one directory).
- if (!recoverymode && buf_fname != NULL
- && !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
+ if (!recoverymode && buf_fname != NULL && !buf->b_help && !(buf->b_flags & BF_DUMMY)) {
int fd;
- struct block0 b0;
+ ZeroBlock b0;
int differ = false;
- // Try to read block 0 from the swap file to get the original
- // file name (and inode number).
+ // Try to read block 0 from the swapfile to get the original file name (and inode number).
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
+ process_running = swapfile_process_running(&b0, fname);
+
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
// have a different mountpoint.
if (b0.b0_flags & B0_SAME_DIR) {
if (path_fnamecmp(path_tail(buf->b_ffname),
- path_tail((char *)b0.b0_fname)) != 0
+ path_tail(b0.b0_fname)) != 0
|| !same_directory(fname, buf->b_ffname)) {
// Symlinks may point to the same file even
// when the name differs, need to check the
// inode too.
- expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL);
+ expand_env(b0.b0_fname, NameBuff, MAXPATHL);
if (fnamecmp_ino(buf->b_ffname, NameBuff,
char_to_long(b0.b0_ino))) {
differ = true;
}
}
} else {
- // The name in the swap file may be
- // "~user/path/file". Expand it first.
- expand_env((char *)b0.b0_fname, NameBuff, MAXPATHL);
+ // The name in the swapfile may be "~user/path/file". Expand it first.
+ expand_env(b0.b0_fname, NameBuff, MAXPATHL);
if (fnamecmp_ino(buf->b_ffname, NameBuff,
char_to_long(b0.b0_ino))) {
differ = true;
@@ -3291,18 +3384,18 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
close(fd);
}
- // give the ATTENTION message when there is an old swap file
- // for the current file, and the buffer was not recovered.
+ // Show the ATTENTION message when:
+ // - there is an old swapfile for the current file
+ // - the buffer was not recovered
if (differ == false && !(curbuf->b_flags & BF_RECOVERED)
&& vim_strchr(p_shm, SHM_ATTENTION) == NULL) {
- int choice = 0;
+ sea_choice_T choice = SEA_CHOICE_NONE;
- process_still_running = false;
- // It's safe to delete the swap file if all these are true:
+ // It's safe to delete the swapfile if all these are true:
// - the edited file exists
- // - the swap file has no changes and looks OK
+ // - the swapfile has no changes and looks OK
if (os_path_exists(buf->b_fname) && swapfile_unchanged(fname)) {
- choice = 4;
+ choice = SEA_CHOICE_DELETE;
if (p_verbose > 0) {
verb_msg(_("Found a swap file that is not useful, deleting it"));
}
@@ -3310,14 +3403,20 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
// If there is a SwapExists autocommand and we can handle the
// response, trigger it. It may return 0 to ask the user anyway.
- if (choice == 0
+ if (choice == SEA_CHOICE_NONE
&& swap_exists_action != SEA_NONE
&& has_autocmd(EVENT_SWAPEXISTS, buf_fname, buf)) {
choice = do_swapexists(buf, fname);
}
- if (choice == 0) {
- // Show info about the existing swap file.
+ if (choice == SEA_CHOICE_NONE && swap_exists_action == SEA_READONLY) {
+ // always open readonly.
+ choice = SEA_CHOICE_READONLY;
+ }
+
+ process_running = 0; // Set by attention_message..swapfile_info.
+ if (choice == SEA_CHOICE_NONE) {
+ // Show info about the existing swapfile.
attention_message(buf, fname);
// We don't want a 'q' typed at the more-prompt
@@ -3329,7 +3428,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
flush_buffers(FLUSH_TYPEAHEAD);
}
- if (swap_exists_action != SEA_NONE && choice == 0) {
+ if (swap_exists_action != SEA_NONE && choice == SEA_CHOICE_NONE) {
const char *const sw_msg_1 = _("Swap file \"");
const char *const sw_msg_2 = _("\" already exists!");
@@ -3343,65 +3442,66 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
memcpy(name, sw_msg_1, sw_msg_1_len + 1);
home_replace(NULL, fname, &name[sw_msg_1_len], fname_len, true);
xstrlcat(name, sw_msg_2, name_len);
- choice = do_dialog(VIM_WARNING, _("VIM - ATTENTION"),
- name,
- process_still_running
- ? _("&Open Read-Only\n&Edit anyway\n&Recover"
- "\n&Quit\n&Abort") :
- _("&Open Read-Only\n&Edit anyway\n&Recover"
- "\n&Delete it\n&Quit\n&Abort"),
- 1, NULL, false);
-
- if (process_still_running && choice >= 4) {
- choice++; // Skip missing "Delete it" button.
+ int dialog_result
+ = do_dialog(VIM_WARNING,
+ _("VIM - ATTENTION"),
+ name,
+ process_running
+ ? _("&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort")
+ : _("&Open Read-Only\n&Edit anyway\n&Recover\n&Delete it\n&Quit\n&Abort"),
+ 1, NULL, false);
+
+ if (process_running && dialog_result >= 4) {
+ // compensate for missing "Delete it" button
+ dialog_result++;
}
+ choice = (sea_choice_T)dialog_result;
xfree(name);
// pretend screen didn't scroll, need redraw anyway
msg_reset_scroll();
}
- if (choice > 0) {
- switch (choice) {
- case 1:
- buf->b_p_ro = true;
- break;
- case 2:
- break;
- case 3:
- swap_exists_action = SEA_RECOVER;
- break;
- case 4:
- os_remove(fname);
- break;
- case 5:
- swap_exists_action = SEA_QUIT;
- break;
- case 6:
- swap_exists_action = SEA_QUIT;
- got_int = true;
- break;
- }
-
- // If the file was deleted this fname can be used.
- if (!os_path_exists(fname)) {
- break;
- }
- } else {
+ switch (choice) {
+ case SEA_CHOICE_READONLY: // "Open Read-Only"
+ buf->b_p_ro = true;
+ break;
+ case SEA_CHOICE_EDIT: // "Edit anyway"
+ break;
+ case SEA_CHOICE_RECOVER: // "Recover"
+ swap_exists_action = SEA_RECOVER;
+ break;
+ case SEA_CHOICE_DELETE: // "Delete it"
+ os_remove(fname);
+ break;
+ case SEA_CHOICE_QUIT: // "Quit"
+ swap_exists_action = SEA_QUIT;
+ break;
+ case SEA_CHOICE_ABORT: // "Abort"
+ swap_exists_action = SEA_QUIT;
+ got_int = true;
+ break;
+ case SEA_CHOICE_NONE:
msg_puts("\n");
if (msg_silent == 0) {
// call wait_return() later
need_wait_return = true;
}
+ break;
+ }
+
+ // If the swapfile was deleted this `fname` can be used.
+ if (choice != SEA_CHOICE_NONE && !os_path_exists(fname)) {
+ break;
}
}
}
}
- // Change the ".swp" extension to find another file that can be used.
+ // Permute the ".swp" extension to find a unique swapfile name.
// First decrement the last char: ".swo", ".swn", etc.
// If that still isn't enough decrement the last but one char: ".svz"
- // Can happen when editing many "No Name" buffers.
+ // Can happen when many Nvim instances are editing the same file (including "No Name" buffers).
if (fname[n - 1] == 'a') { // ".s?a"
if (fname[n - 2] == 'a') { // ".saa": tried enough, give up
emsg(_("E326: Too many swap files found"));
@@ -3419,7 +3519,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
} else if (!*found_existing_dir && **dirp == NUL) {
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E303: Unable to create directory \"%s\" for swap file, "
"recovery impossible: %s"),
failed_dir, os_strerror(ret));
@@ -3431,7 +3531,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
return fname;
}
-static int b0_magic_wrong(ZERO_BL *b0p)
+static int b0_magic_wrong(ZeroBlock *b0p)
{
return b0p->b0_magic_long != B0_MAGIC_LONG
|| b0p->b0_magic_int != B0_MAGIC_INT
@@ -3439,7 +3539,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
|| b0p->b0_magic_char != B0_MAGIC_CHAR;
}
-/// Compare current file name with file name from swap file.
+/// Compare current file name with file name from swapfile.
/// Try to use inode numbers when possible.
/// Return non-zero when files are different.
///
@@ -3449,7 +3549,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// because the device number cannot be used over a network.
/// - When a file does not exist yet (editing a new file) there is no inode
/// number.
-/// - The file name in a swap file may not be valid on the current host. The
+/// - The file name in a swapfile may not be valid on the current host. The
/// "~user" form is used whenever possible to avoid this.
///
/// This is getting complicated, let's make a table:
@@ -3463,7 +3563,7 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// == 0 X OK OK fname_c != fname_s
/// X == 0 OK OK fname_c != fname_s
///
-/// current file doesn't exist, file for swap file exist, file name(s) not
+/// current file doesn't exist, file for swapfile exist, file name(s) not
/// available -> probably different
/// == 0 != 0 FAIL X true
/// == 0 != 0 X FAIL true
@@ -3486,11 +3586,11 @@ static int b0_magic_wrong(ZERO_BL *b0p)
/// without making the block 0 incompatible with 32 bit versions.
///
/// @param fname_c current file name
-/// @param fname_s file name from swap file
+/// @param fname_s file name from swapfile
static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
{
uint64_t ino_c = 0; // ino of current file
- uint64_t ino_s; // ino of file from swap file
+ uint64_t ino_s; // ino of file from swapfile
char buf_c[MAXPATHL]; // full path of fname_c
char buf_s[MAXPATHL]; // full path of fname_s
int retval_c; // flag: buf_c valid
@@ -3502,7 +3602,7 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
}
// First we try to get the inode from the file name, because the inode in
- // the swap file may be outdated. If that fails (e.g. this path is not
+ // the swapfile may be outdated. If that fails (e.g. this path is not
// valid on this machine), use the inode from block 0.
if (os_fileinfo(fname_s, &file_info)) {
ino_s = os_fileinfo_inode(&file_info);
@@ -3516,15 +3616,15 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
// One of the inode numbers is unknown, try a forced vim_FullName() and
// compare the file names.
- retval_c = vim_FullName(fname_c, (char *)buf_c, MAXPATHL, true);
- retval_s = vim_FullName(fname_s, (char *)buf_s, MAXPATHL, true);
+ retval_c = vim_FullName(fname_c, buf_c, MAXPATHL, true);
+ retval_s = vim_FullName(fname_s, buf_s, MAXPATHL, true);
if (retval_c == OK && retval_s == OK) {
return strcmp(buf_c, buf_s) != 0;
}
// Can't compare inodes or file names, guess that the files are different,
// unless both appear not to exist at all, then compare with the file name
- // in the swap file.
+ // in the swapfile.
if (ino_s == 0 && ino_c == 0 && retval_c == FAIL && retval_s == FAIL) {
return strcmp(fname_c, fname_s) != 0;
}
@@ -3533,22 +3633,23 @@ static bool fnamecmp_ino(char *fname_c, char *fname_s, long ino_block0)
/// Move a long integer into a four byte character array.
/// Used for machine independency in block zero.
-static void long_to_char(long n, char_u *s)
+static void long_to_char(long n, char *s_in)
{
- s[0] = (char_u)(n & 0xff);
+ uint8_t *s = (uint8_t *)s_in;
+ s[0] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[1] = (char_u)(n & 0xff);
+ s[1] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[2] = (char_u)(n & 0xff);
+ s[2] = (uint8_t)(n & 0xff);
n = (unsigned)n >> 8;
- s[3] = (char_u)(n & 0xff);
+ s[3] = (uint8_t)(n & 0xff);
}
-static long char_to_long(const char_u *s)
+static long char_to_long(const char *s_in)
{
- long retval;
+ const uint8_t *s = (uint8_t *)s_in;
- retval = s[3];
+ long retval = s[3];
retval <<= 8;
retval |= s[2];
retval <<= 8;
@@ -3559,28 +3660,23 @@ static long char_to_long(const char_u *s)
return retval;
}
-/// Set the flags in the first block of the swap file:
+/// Set the flags in the first block of the swapfile:
/// - file is modified or not: buf->b_changed
/// - 'fileformat'
/// - 'fileencoding'
void ml_setflags(buf_T *buf)
{
- bhdr_T *hp;
- ZERO_BL *b0p;
-
if (!buf->b_ml.ml_mfp) {
return;
}
- for (hp = buf->b_ml.ml_mfp->mf_used_last; hp != NULL; hp = hp->bh_prev) {
- if (hp->bh_bnum == 0) {
- b0p = hp->bh_data;
- b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
- b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1));
- add_b0_fenc(b0p, buf);
- hp->bh_flags |= BH_DIRTY;
- mf_sync(buf->b_ml.ml_mfp, MFS_ZERO);
- break;
- }
+ bhdr_T *hp = pmap_get(int64_t)(&buf->b_ml.ml_mfp->mf_hash, 0);
+ if (hp) {
+ ZeroBlock *b0p = hp->bh_data;
+ b0p->b0_dirty = buf->b_changed ? B0_DIRTY : 0;
+ b0p->b0_flags = (char)((b0p->b0_flags & ~B0_FF_MASK) | (uint8_t)(get_fileformat(buf) + 1));
+ add_b0_fenc(b0p, buf);
+ hp->bh_flags |= BH_DIRTY;
+ mf_sync(buf->b_ml.ml_mfp, MFS_ZERO);
}
}
@@ -3596,7 +3692,7 @@ enum {
/// Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called.
/// ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it
/// ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity.
-static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
+static void ml_updatechunk(buf_T *buf, linenr_T line, int len, int updtype)
{
static buf_T *ml_upd_lastbuf = NULL;
static linenr_T ml_upd_lastline;
@@ -3605,11 +3701,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
linenr_T curline = ml_upd_lastcurline;
int curix = ml_upd_lastcurix;
- long size;
- chunksize_T *curchnk;
- int rest;
bhdr_T *hp;
- DATA_BL *dp;
if (buf->b_ml.ml_usedchunks == -1 || len == 0) {
return;
@@ -3626,8 +3718,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
// First line in empty buffer from ml_flush_line() -- reset
buf->b_ml.ml_usedchunks = 1;
buf->b_ml.ml_chunksize[0].mlcs_numlines = 1;
- buf->b_ml.ml_chunksize[0].mlcs_totalsize =
- (long)strlen(buf->b_ml.ml_line_ptr) + 1;
+ buf->b_ml.ml_chunksize[0].mlcs_totalsize = (int)strlen(buf->b_ml.ml_line_ptr) + 1;
return;
}
@@ -3648,13 +3739,15 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
curix++;
}
- curchnk = buf->b_ml.ml_chunksize + curix;
+ chunksize_T *curchnk = buf->b_ml.ml_chunksize + curix;
if (updtype == ML_CHNK_DELLINE) {
len = -len;
}
curchnk->mlcs_totalsize += len;
if (updtype == ML_CHNK_ADDLINE) {
+ int rest;
+ DataBlock *dp;
curchnk->mlcs_numlines++;
// May resize here so we don't have to do it in both cases below
@@ -3665,17 +3758,14 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
}
if (buf->b_ml.ml_chunksize[curix].mlcs_numlines >= MLCS_MAXL) {
- int count; // number of entries in block
- int idx;
int text_end;
- int linecnt;
memmove(buf->b_ml.ml_chunksize + curix + 1,
buf->b_ml.ml_chunksize + curix,
(size_t)(buf->b_ml.ml_usedchunks - curix) * sizeof(chunksize_T));
// Compute length of first half of lines in the split chunk
- size = 0;
- linecnt = 0;
+ int size = 0;
+ int linecnt = 0;
while (curline < buf->b_ml.ml_line_count
&& linecnt < MLCS_MINL) {
if ((hp = ml_find_line(buf, curline, ML_FIND)) == NULL) {
@@ -3683,8 +3773,9 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
return;
}
dp = hp->bh_data;
- count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1;
- idx = curline - buf->b_ml.ml_locked_low;
+ int count
+ = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; // number of entries in block
+ int idx = curline - buf->b_ml.ml_locked_low;
curline = buf->b_ml.ml_locked_high + 1;
if (idx == 0) { // first line in block, text at the end
text_end = (int)dp->db_txt_end;
@@ -3787,19 +3878,11 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
/// @return -1 if information is not available
-long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
+int ml_find_line_or_offset(buf_T *buf, linenr_T lnum, int *offp, bool no_ff)
{
- linenr_T curline;
- int curix;
- long size;
bhdr_T *hp;
- DATA_BL *dp;
- int count; // number of entries in block
- int idx;
- int start_idx;
int text_end;
- long offset;
- int len;
+ int offset;
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
@@ -3817,12 +3900,17 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
ml_flush_line(curbuf);
} else if (can_cache && buf->b_ml.ml_line_offset > 0) {
- return (long)buf->b_ml.ml_line_offset;
+ return (int)buf->b_ml.ml_line_offset;
}
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
|| lnum < 0) {
+ // memline is currently empty. Although if it is loaded,
+ // it behaves like there is one empty line.
+ if (!ffdos && buf->b_ml.ml_mfp && (lnum == 1 || lnum == 2)) {
+ return lnum - 1;
+ }
return -1;
}
@@ -3836,16 +3924,16 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
// Find the last chunk before the one containing our line. Last chunk is
// special because it will never qualify
- curline = 1;
- curix = 0;
- size = 0;
+ linenr_T curline = 1;
+ int curix = 0;
+ int size = 0;
while (curix < buf->b_ml.ml_usedchunks - 1
&& ((lnum != 0
&& lnum >= curline + buf->b_ml.ml_chunksize[curix].mlcs_numlines)
|| (offset != 0
&& offset > size +
buf->b_ml.ml_chunksize[curix].mlcs_totalsize
- + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
+ + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) {
curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines;
size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize;
if (offset && ffdos) {
@@ -3859,9 +3947,11 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
|| (hp = ml_find_line(buf, curline, ML_FIND)) == NULL) {
return -1;
}
- dp = hp->bh_data;
- count = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1;
- start_idx = idx = curline - buf->b_ml.ml_locked_low;
+ DataBlock *dp = hp->bh_data;
+ int count
+ = buf->b_ml.ml_locked_high - buf->b_ml.ml_locked_low + 1; // number of entries in block
+ int idx;
+ int start_idx = idx = curline - buf->b_ml.ml_locked_low;
if (idx == 0) { // first line in block, text at the end
text_end = (int)dp->db_txt_end;
} else {
@@ -3889,7 +3979,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
idx++;
}
}
- len = text_end - (int)((dp->db_index[idx]) & DB_INDEX_MASK);
+ int len = text_end - (int)((dp->db_index[idx]) & DB_INDEX_MASK);
size += len;
if (offset != 0 && size >= offset) {
if (size + ffdos == offset) {
@@ -3931,16 +4021,16 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
/// Goto byte in buffer with offset 'cnt'.
-void goto_byte(long cnt)
+void goto_byte(int cnt)
{
- long boff = cnt;
+ int boff = cnt;
ml_flush_line(curbuf); // cached line may be dirty
setpcmark();
if (boff) {
boff--;
}
- linenr_T lnum = (linenr_T)ml_find_line_or_offset(curbuf, (linenr_T)0, &boff, false);
+ linenr_T lnum = (linenr_T)ml_find_line_or_offset(curbuf, 0, &boff, false);
if (lnum < 1) { // past the end
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
curwin->w_curswant = MAXCOL;
@@ -3969,7 +4059,7 @@ int inc(pos_T *lp)
if (lp->col != MAXCOL) {
const char *const p = ml_get_pos(lp);
if (*p != NUL) { // still within line, move to next char (may be NUL)
- const int l = utfc_ptr2len((char *)p);
+ const int l = utfc_ptr2len(p);
lp->col += l;
return ((p[l] != NUL) ? 0 : 2);
diff --git a/src/nvim/memline.h b/src/nvim/memline.h
index f4190f0210..e70a8e423e 100644
--- a/src/nvim/memline.h
+++ b/src/nvim/memline.h
@@ -1,11 +1,10 @@
-#ifndef NVIM_MEMLINE_H
-#define NVIM_MEMLINE_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/memline_defs.h" // IWYU pragma: export
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memline.h.generated.h"
#endif
-#endif // NVIM_MEMLINE_H
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index 6bb9255909..f95dc7a2e5 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MEMLINE_DEFS_H
-#define NVIM_MEMLINE_DEFS_H
+#pragma once
#include "nvim/memfile_defs.h"
@@ -17,7 +16,7 @@ typedef struct info_pointer {
typedef struct ml_chunksize {
int mlcs_numlines;
- long mlcs_totalsize;
+ int mlcs_totalsize;
} chunksize_T;
// Flags when calling ml_updatechunk()
@@ -49,10 +48,11 @@ typedef struct memline {
int ml_stack_top; // current top of ml_stack
int ml_stack_size; // total number of entries in ml_stack
-#define ML_EMPTY 1 // empty buffer
-#define ML_LINE_DIRTY 2 // cached line was changed and allocated
-#define ML_LOCKED_DIRTY 4 // ml_locked was changed
-#define ML_LOCKED_POS 8 // ml_locked needs positive block number
+#define ML_EMPTY 0x01 // empty buffer
+#define ML_LINE_DIRTY 0x02 // cached line was changed and allocated
+#define ML_LOCKED_DIRTY 0x04 // ml_locked was changed
+#define ML_LOCKED_POS 0x08 // ml_locked needs positive block number
+#define ML_ALLOCATED 0x10 // ml_line_ptr is an allocated copy
int ml_flags;
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
@@ -68,5 +68,3 @@ typedef struct memline {
int ml_numchunks;
int ml_usedchunks;
} memline_T;
-
-#endif // NVIM_MEMLINE_DEFS_H
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 5356300382..df6c81fe0d 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Various routines dealing with allocation and deallocation of memory.
#include <assert.h>
@@ -13,11 +10,13 @@
#include "nvim/api/extmark.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_updates.h"
#include "nvim/context.h"
#include "nvim/decoration_provider.h"
+#include "nvim/drawline.h"
#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
@@ -29,10 +28,12 @@
#include "nvim/memfile.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/option_vars.h"
#include "nvim/sign.h"
+#include "nvim/state_defs.h"
#include "nvim/ui.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef UNIT_TESTING
# define malloc(size) mem_malloc(size)
@@ -121,9 +122,7 @@ void *xmalloc(size_t size)
{
void *ret = try_malloc(size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
return ret;
}
@@ -152,9 +151,7 @@ void *xcalloc(size_t count, size_t size)
try_to_free_memory();
ret = calloc(allocated_count, allocated_size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
}
return ret;
@@ -174,9 +171,7 @@ void *xrealloc(void *ptr, size_t size)
try_to_free_memory();
ret = realloc(ptr, allocated_size);
if (!ret) {
- os_errmsg(e_outofmem);
- os_errmsg("\n");
- preserve_exit();
+ preserve_exit(e_outofmem);
}
}
return ret;
@@ -194,8 +189,7 @@ void *xmallocz(size_t size)
{
size_t total_size = size + 1;
if (total_size < size) {
- os_errmsg(_("Vim: Data too large to fit into virtual memory space\n"));
- preserve_exit();
+ preserve_exit(_("Vim: Data too large to fit into virtual memory space\n"));
}
void *ret = xmalloc(total_size);
@@ -219,6 +213,18 @@ void *xmemdupz(const void *data, size_t len)
return memcpy(xmallocz(len), data, len);
}
+#ifndef HAVE_STRNLEN
+size_t xstrnlen(const char *s, size_t n)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
+{
+ const char *end = memchr(s, '\0', n);
+ if (end == NULL) {
+ return n;
+ }
+ return (size_t)(end - s);
+}
+#endif
+
/// A version of strchr() that returns a pointer to the terminating NUL if it
/// doesn't find `c`.
///
@@ -502,13 +508,6 @@ bool strequal(const char *a, const char *b)
return (a == NULL && b == NULL) || (a && b && strcmp(a, b) == 0);
}
-/// Case-insensitive `strequal`.
-bool striequal(const char *a, const char *b)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0);
-}
-
// Avoid repeating the error message many times (they take 1 second each).
// Did_outofmem_msg is reset when a character is read.
void do_outofmem_msg(size_t size)
@@ -585,7 +584,9 @@ void alloc_block(Arena *arena)
static size_t arena_align_offset(uint64_t off)
{
+#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double))
return ((off + (ARENA_ALIGN - 1)) & ~(ARENA_ALIGN - 1));
+#undef ARENA_ALIGN
}
/// @param arena if NULL, do a global allocation. caller must then free the value!
@@ -663,14 +664,13 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/cmdhist.h"
# include "nvim/diff.h"
# include "nvim/edit.h"
-# include "nvim/eval/typval.h"
# include "nvim/ex_cmds.h"
# include "nvim/ex_docmd.h"
-# include "nvim/ex_getln.h"
# include "nvim/file_search.h"
# include "nvim/getchar.h"
# include "nvim/grid.h"
# include "nvim/mark.h"
+# include "nvim/msgpack_rpc/channel.h"
# include "nvim/ops.h"
# include "nvim/option.h"
# include "nvim/os/os.h"
@@ -762,11 +762,7 @@ void free_all_mem(void)
p_hi = 0;
init_history();
- qf_free_all(NULL);
- // Free all location lists
- FOR_ALL_TAB_WINDOWS(tab, win) {
- qf_free_all(win);
- }
+ free_quickfix();
// Close all script inputs.
close_all_scripts();
@@ -777,8 +773,6 @@ void free_all_mem(void)
// Free all option values. Must come after closing windows.
free_all_options();
- free_arshape_buf();
-
// Clear registers.
clear_registers();
ResetRedobuff();
@@ -793,7 +787,7 @@ void free_all_mem(void)
first_tabpage = NULL;
// message history
- for (;;) {
+ while (true) {
if (delete_first_msg() == FAIL) {
break;
}
@@ -826,14 +820,15 @@ void free_all_mem(void)
grid_free_all_mem();
clear_hl_tables(false);
- list_free_log();
check_quickfix_busy();
decor_free_all_mem();
+ drawline_free_all_mem();
ui_free_all_mem();
nlua_free_all_mem();
+ rpc_free_all_mem();
// should be last, in case earlier free functions deallocates arenas
arena_free_reuse_blks();
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index 5b9798dc0d..ffdc4c7366 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -1,12 +1,12 @@
-#ifndef NVIM_MEMORY_H
-#define NVIM_MEMORY_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-#include <time.h>
+#include <stdint.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
-#include "nvim/macros.h"
+#include "auto/config.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory_defs.h" // IWYU pragma: export
/// `malloc()` function signature
typedef void *(*MemMalloc)(size_t);
@@ -39,21 +39,7 @@ extern MemRealloc mem_realloc;
extern bool entered_free_all_mem;
#endif
-EXTERN size_t arena_alloc_count INIT(= 0);
-
-typedef struct consumed_blk {
- struct consumed_blk *prev;
-} *ArenaMem;
-
-#define ARENA_ALIGN MAX(sizeof(void *), sizeof(double))
-
-typedef struct {
- char *cur_blk;
- size_t pos, size;
-} Arena;
-
-// inits an empty arena.
-#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 }
+EXTERN size_t arena_alloc_count INIT( = 0);
#define kv_fixsize_arena(a, v, s) \
((v).capacity = (s), \
@@ -73,4 +59,16 @@ typedef struct {
(void)(*ptr_); \
} while (0)
-#endif // NVIM_MEMORY_H
+#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field))
+#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
+
+#ifndef HAVE_STRNLEN
+# define strnlen xstrnlen // Older versions of SunOS may not have strnlen
+#endif
+
+#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
+
+// Like strcpy() but allows overlapped source and destination.
+#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1)
+
+#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
diff --git a/src/nvim/memory_defs.h b/src/nvim/memory_defs.h
new file mode 100644
index 0000000000..bde0e54f54
--- /dev/null
+++ b/src/nvim/memory_defs.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <stddef.h>
+
+typedef struct consumed_blk {
+ struct consumed_blk *prev;
+} *ArenaMem;
+
+typedef struct {
+ char *cur_blk;
+ size_t pos, size;
+} Arena;
+
+// inits an empty arena.
+#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 }
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index 2a18b08d8d..3252a73970 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Code for menus. Used for the GUI and 'wildmenu'.
// GUI/Motif support by Robert Webb
@@ -9,37 +6,36 @@
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/menu_defs.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#define MENUDEPTH 10 // maximum depth of menus
@@ -50,8 +46,8 @@
/// The character for each menu mode
static char *menu_mode_chars[] = { "n", "v", "s", "o", "i", "c", "tl", "t" };
-static char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
-static char e_nomenu[] = N_("E329: No menu \"%s\"");
+static const char e_notsubmenu[] = N_("E327: Part of menu-item path is not sub-menu");
+static const char e_nomenu[] = N_("E329: No menu \"%s\"");
// Return true if "name" is a window toolbar menu name.
static bool menu_is_winbar(const char *const name)
@@ -70,25 +66,22 @@ static vimmenu_T **get_root_menu(const char *const name)
/// @param eap Ex command arguments
void ex_menu(exarg_T *eap)
{
- char *menu_path;
- int modes;
char *map_to; // command mapped to the menu entry
int noremap;
bool silent = false;
int unmenu;
char *map_buf;
- char *arg;
char *p;
int i;
- long pri_tab[MENUDEPTH + 1];
+ int pri_tab[MENUDEPTH + 1];
TriState enable = kNone; // kTrue for "menu enable",
// kFalse for "menu disable
vimmenu_T menuarg;
- modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
- arg = eap->arg;
+ int modes = get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
+ char *arg = eap->arg;
- for (;;) {
+ while (true) {
if (strncmp(arg, "<script>", 8) == 0) {
noremap = REMAP_SCRIPT;
arg = skipwhite(arg + 8);
@@ -131,7 +124,7 @@ void ex_menu(exarg_T *eap)
}
if (ascii_iswhite(*p)) {
for (i = 0; i < MENUDEPTH && !ascii_iswhite(*arg); i++) {
- pri_tab[i] = getdigits_long(&arg, false, 0);
+ pri_tab[i] = getdigits_int(&arg, false, 0);
if (pri_tab[i] == 0) {
pri_tab[i] = 500;
}
@@ -166,7 +159,7 @@ void ex_menu(exarg_T *eap)
return;
}
- menu_path = arg;
+ char *menu_path = arg;
if (*menu_path == '.') {
semsg(_(e_invarg2), menu_path);
goto theend;
@@ -232,7 +225,7 @@ void ex_menu(exarg_T *eap)
map_buf = NULL; // Menu tips are plain text.
} else {
map_buf = NULL;
- map_to = replace_termcodes(map_to, strlen(map_to), &map_buf,
+ map_to = replace_termcodes(map_to, strlen(map_to), &map_buf, 0,
REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
}
menuarg.modes = modes;
@@ -267,38 +260,28 @@ theend:
/// @param[out] menuarg menu entry
/// @param[] pri_tab priority table
/// @param[in] call_data Right hand side command
-static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const long *const pri_tab,
+static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const int *const pri_tab,
const char *const call_data)
{
- char *path_name;
int modes = menuarg->modes;
vimmenu_T *menu = NULL;
- vimmenu_T *parent;
vimmenu_T **lower_pri;
- char *p;
- char *name;
char *dname;
- char *next_name;
- char c;
- char d;
- int i;
int pri_idx = 0;
int old_modes = 0;
- int amenu;
char *en_name;
- char *map_to = NULL;
// Make a copy so we can stuff around with it, since it could be const
- path_name = xstrdup(menu_path);
+ char *path_name = xstrdup(menu_path);
vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
vimmenu_T **menup = root_menu_ptr;
- parent = NULL;
- name = path_name;
+ vimmenu_T *parent = NULL;
+ char *name = path_name;
while (*name) {
// Get name of this element in the menu hierarchy, and the simplified
// name (without mnemonic and accelerator text).
- next_name = menu_name_skip(name);
- map_to = menutrans_lookup(name, (int)strlen(name));
+ char *next_name = menu_name_skip(name);
+ char *map_to = menutrans_lookup(name, (int)strlen(name));
if (map_to != NULL) {
en_name = name;
name = map_to;
@@ -401,25 +384,25 @@ static int add_menu_path(const char *const menu_path, vimmenu_T *menuarg, const
// Only add system menu items which have not been defined yet.
// First check if this was an ":amenu".
- amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
- (MENU_NORMAL_MODE | MENU_INSERT_MODE));
+ int amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
+ (MENU_NORMAL_MODE | MENU_INSERT_MODE));
if (sys_menu) {
modes &= ~old_modes;
}
if (menu != NULL && modes) {
- p = (call_data == NULL) ? NULL : xstrdup(call_data);
+ char *p = (call_data == NULL) ? NULL : xstrdup(call_data);
// loop over all modes, may add more than one
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
if (modes & (1 << i)) {
// free any old menu
free_menu_string(menu, i);
// For "amenu", may insert an extra character.
// Don't do this for "<Nop>".
- c = 0;
- d = 0;
+ char c = 0;
+ char d = 0;
if (amenu && call_data != NULL && *call_data != NUL) {
switch (1 << i) {
case MENU_VISUAL_MODE:
@@ -487,13 +470,11 @@ erret:
// Called recursively.
static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enable)
{
- char *p;
-
if (menu == NULL) {
return OK; // Got to bottom of hierarchy
}
// Get name of this element in the menu hierarchy
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
// Find the menu
while (menu != NULL) {
@@ -536,14 +517,12 @@ static int menu_enable_recurse(vimmenu_T *menu, char *name, int modes, int enabl
static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
{
vimmenu_T *menu;
- vimmenu_T *child;
- char *p;
if (*menup == NULL) {
return OK; // Got to bottom of hierarchy
}
// Get name of this element in the menu hierarchy
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
// Find the menu
while ((menu = *menup) != NULL) {
@@ -560,7 +539,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
}
} else if (*name != NUL) {
if (!silent) {
- emsg(_(e_menuothermode));
+ emsg(_(e_menu_only_exists_in_another_mode));
}
return FAIL;
}
@@ -597,7 +576,7 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
// Recalculate modes for menu based on the new updated children
menu->modes &= ~modes;
- child = menu->children;
+ vimmenu_T *child = menu->children;
for (; child != NULL; child = child->next) {
menu->modes |= child->modes;
}
@@ -617,20 +596,15 @@ static int remove_menu(vimmenu_T **menup, char *name, int modes, bool silent)
// Free the given menu structure and remove it from the linked list.
static void free_menu(vimmenu_T **menup)
{
- int i;
- vimmenu_T *menu;
-
- menu = *menup;
+ vimmenu_T *menu = *menup;
- // Don't change *menup until after calling gui_mch_destroy_menu(). The
- // MacOS code needs the original structure to properly delete the menu.
*menup = menu->next;
xfree(menu->name);
xfree(menu->dname);
xfree(menu->en_name);
xfree(menu->en_dname);
xfree(menu->actext);
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
free_menu_string(menu, i);
}
xfree(menu);
@@ -640,9 +614,8 @@ static void free_menu(vimmenu_T **menup)
static void free_menu_string(vimmenu_T *menu, int idx)
{
int count = 0;
- int i;
- for (i = 0; i < MENU_MODES; i++) {
+ for (int i = 0; i < MENU_MODES; i++) {
if (menu->strings[i] == menu->strings[idx]) {
count++;
}
@@ -662,15 +635,13 @@ static void free_menu_string(vimmenu_T *menu, int idx)
/// @see menu_get
static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes)
{
- dict_T *dict;
-
if (!menu || (menu->modes & modes) == 0x0) {
return NULL;
}
- dict = tv_dict_alloc();
+ dict_T *dict = tv_dict_alloc();
tv_dict_add_str(dict, S_LEN("name"), menu->dname);
- tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority);
+ tv_dict_add_nr(dict, S_LEN("priority"), menu->priority);
tv_dict_add_nr(dict, S_LEN("hidden"), menu_is_hidden(menu->dname));
if (menu->mnemonic) {
@@ -755,11 +726,9 @@ bool menu_get(char *const path_name, int modes, list_T *list)
/// @return menu if \p name is null, found menu or NULL
static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes)
{
- char *p;
-
while (*name) {
// find the end of one dot-separated name and put a NUL at the dot
- p = menu_name_skip(name);
+ char *p = menu_name_skip(name);
while (menu != NULL) {
if (menu_name_equal(name, menu)) {
// Found menu
@@ -767,7 +736,7 @@ static vimmenu_T *find_menu(vimmenu_T *menu, char *name, int modes)
emsg(_(e_notsubmenu));
return NULL;
} else if ((menu->modes & modes) == 0x0) {
- emsg(_(e_menuothermode));
+ emsg(_(e_menu_only_exists_in_another_mode));
return NULL;
} else if (*p == NUL) { // found a full match
return menu;
@@ -814,9 +783,6 @@ static int show_menus(char *const path_name, int modes)
/// Recursively show the mappings associated with the menus under the given one
static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
{
- int i;
- int bit;
-
if (menu != NULL && (menu->modes & modes) == 0x0) {
return;
}
@@ -826,7 +792,7 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
if (got_int) { // "q" hit for "--more--"
return;
}
- for (i = 0; i < depth; i++) {
+ for (int i = 0; i < depth; i++) {
msg_puts(" ");
}
if (menu->priority) {
@@ -834,17 +800,17 @@ static void show_menus_recursive(vimmenu_T *menu, int modes, int depth)
msg_puts(" ");
}
// Same highlighting as for directories!?
- msg_outtrans_attr(menu->name, HL_ATTR(HLF_D));
+ msg_outtrans(menu->name, HL_ATTR(HLF_D));
}
if (menu != NULL && menu->children == NULL) {
- for (bit = 0; bit < MENU_MODES; bit++) {
+ for (int bit = 0; bit < MENU_MODES; bit++) {
if ((menu->modes & modes & (1 << bit)) != 0) {
msg_putchar('\n');
if (got_int) { // "q" hit for "--more--"
return;
}
- for (i = 0; i < depth + 2; i++) {
+ for (int i = 0; i < depth + 2; i++) {
msg_puts(" ");
}
msg_puts(menu_mode_chars[bit]);
@@ -902,10 +868,8 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
char *after_dot;
char *p;
char *path_name = NULL;
- char *name;
int unmenu;
vimmenu_T *menu;
- int expand_menus;
xp->xp_context = EXPAND_UNSUCCESSFUL;
@@ -943,7 +907,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
}
// ":popup" only uses menus, not entries
- expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
+ int expand_menus = !((*cmd == 't' && cmd[1] == 'e') || *cmd == 'p');
expand_emenu = (*cmd == 'e');
if (expand_menus && ascii_iswhite(*p)) {
return NULL; // TODO(vim): check for next command?
@@ -963,7 +927,7 @@ char *set_context_in_menu_cmd(expand_T *xp, const char *cmd, char *arg, bool for
path_name = xmalloc(path_len);
xstrlcpy(path_name, arg, path_len);
}
- name = path_name;
+ char *name = path_name;
while (name != NULL && *name) {
p = menu_name_skip(name);
while (menu != NULL) {
@@ -1316,17 +1280,16 @@ static char *menu_text(const char *str, int *mnemonic, char **actext)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
FUNC_ATTR_NONNULL_ARG(1)
{
- char *p;
char *text;
// Locate accelerator text, after the first TAB
- p = vim_strchr(str, TAB);
+ char *p = vim_strchr(str, TAB);
if (p != NULL) {
if (actext != NULL) {
*actext = xstrdup(p + 1);
}
assert(p >= str);
- text = xstrnsave(str, (size_t)(p - str));
+ text = xmemdupz(str, (size_t)(p - str));
} else {
text = xstrdup(str);
}
@@ -1352,7 +1315,7 @@ static char *menu_text(const char *str, int *mnemonic, char **actext)
bool menu_is_menubar(const char *const name)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- return !menu_is_popup((char *)name)
+ return !menu_is_popup(name)
&& !menu_is_toolbar(name)
&& !menu_is_winbar(name)
&& *name != MNU_HIDDEN_CHAR;
@@ -1459,7 +1422,8 @@ void show_popupmenu(void)
/// Execute "menu". Use by ":emenu" and the window toolbar.
/// @param eap NULL for the window toolbar.
-/// @param mode_idx specify a MENU_INDEX_ value, use -1 to depend on the current state
+/// @param mode_idx specify a MENU_INDEX_ value,
+/// use MENU_INDEX_INVALID to depend on the current state
void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx)
FUNC_ATTR_NONNULL_ARG(2)
{
@@ -1467,7 +1431,7 @@ void execute_menu(const exarg_T *eap, vimmenu_T *menu, int mode_idx)
if (idx < 0) {
// Use the Insert mode entry when returning to Insert mode.
- if (((State & MODE_INSERT) || restart_edit) && !current_sctx.sc_sid) {
+ if (((State & MODE_INSERT) || restart_edit) && current_sctx.sc_sid == 0) {
idx = MENU_INDEX_INSERT;
} else if (State & MODE_CMDLINE) {
idx = MENU_INDEX_CMDLINE;
@@ -1621,7 +1585,7 @@ static vimmenu_T *menu_getbyname(char *name_arg)
void ex_emenu(exarg_T *eap)
{
char *arg = eap->arg;
- int mode_idx = -1;
+ int mode_idx = MENU_INDEX_INVALID;
if (arg[0] && ascii_iswhite(arg[1])) {
switch (arg[0]) {
@@ -1723,7 +1687,6 @@ static garray_T menutrans_ga = GA_EMPTY_INIT_VALUE;
void ex_menutranslate(exarg_T *eap)
{
char *arg = eap->arg;
- char *from, *from_noamp, *to;
if (menutrans_ga.ga_itemsize == 0) {
ga_init(&menutrans_ga, (int)sizeof(menutrans_T), 5);
@@ -1737,18 +1700,18 @@ void ex_menutranslate(exarg_T *eap)
del_menutrans_vars();
} else {
// ":menutrans from to": add translation
- from = arg;
+ char *from = arg;
arg = menu_skip_part(arg);
- to = skipwhite(arg);
+ char *to = skipwhite(arg);
*arg = NUL;
arg = menu_skip_part(to);
if (arg == to) {
emsg(_(e_invarg));
} else {
from = xstrdup(from);
- from_noamp = menu_text(from, NULL, NULL);
+ char *from_noamp = menu_text(from, NULL, NULL);
assert(arg >= to);
- to = xstrnsave(to, (size_t)(arg - to));
+ to = xmemdupz(to, (size_t)(arg - to));
menu_translate_tab_and_shift(from);
menu_translate_tab_and_shift(to);
menu_unescape_name(from);
@@ -1778,7 +1741,6 @@ static char *menu_skip_part(char *p)
static char *menutrans_lookup(char *name, int len)
{
menutrans_T *tp = (menutrans_T *)menutrans_ga.ga_data;
- char *dname;
for (int i = 0; i < menutrans_ga.ga_len; i++) {
if (STRNICMP(name, tp[i].from, len) == 0 && tp[i].from[len] == NUL) {
@@ -1789,7 +1751,7 @@ static char *menutrans_lookup(char *name, int len)
// Now try again while ignoring '&' characters.
char c = name[len];
name[len] = NUL;
- dname = menu_text(name, NULL, NULL);
+ char *dname = menu_text(name, NULL, NULL);
name[len] = c;
for (int i = 0; i < menutrans_ga.ga_len; i++) {
if (STRICMP(dname, tp[i].from_noamp) == 0) {
@@ -1805,9 +1767,7 @@ static char *menutrans_lookup(char *name, int len)
// Unescape the name in the translate dictionary table.
static void menu_unescape_name(char *name)
{
- char *p;
-
- for (p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
+ for (char *p = name; *p && *p != '.'; MB_PTR_ADV(p)) {
if (*p == '\\') {
STRMOVE(p, p + 1);
}
@@ -1859,7 +1819,7 @@ static void menuitem_getinfo(const char *menu_name, const vimmenu_T *menu, int m
if (menu->actext != NULL) {
tv_dict_add_str(dict, S_LEN("accel"), menu->actext);
}
- tv_dict_add_nr(dict, S_LEN("priority"), (int)menu->priority);
+ tv_dict_add_nr(dict, S_LEN("priority"), menu->priority);
tv_dict_add_str(dict, S_LEN("modes"), get_menu_mode_str(menu->modes));
char buf[NUMBUFLEN];
diff --git a/src/nvim/menu.h b/src/nvim/menu.h
index 32959cf35f..9644386003 100644
--- a/src/nvim/menu.h
+++ b/src/nvim/menu.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_MENU_H
-#define NVIM_MENU_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/menu_defs.h"
-#include "nvim/types.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/menu_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "menu.h.generated.h"
#endif
-#endif // NVIM_MENU_H
diff --git a/src/nvim/menu_defs.h b/src/nvim/menu_defs.h
index 79b267ae49..b870055238 100644
--- a/src/nvim/menu_defs.h
+++ b/src/nvim/menu_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MENU_DEFS_H
-#define NVIM_MENU_DEFS_H
+#pragma once
#include <stdbool.h>
@@ -52,7 +51,7 @@ struct VimMenu {
char *en_dname; ///< NULL when "dname" untranslated
int mnemonic; ///< mnemonic key (after '&')
char *actext; ///< accelerator text (after TAB)
- long priority; ///< Menu order priority
+ int priority; ///< Menu order priority
char *strings[MENU_MODES]; ///< Mapped string for each mode
int noremap[MENU_MODES]; ///< A \ref REMAP_VALUES flag for each mode
bool silent[MENU_MODES]; ///< A silent flag for each mode
@@ -60,5 +59,3 @@ struct VimMenu {
vimmenu_T *parent; ///< Parent of menu
vimmenu_T *next; ///< Next item in menu
};
-
-#endif // NVIM_MENU_DEFS_H
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 3b3dfcd5b6..3268ff389a 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// message.c: functions for displaying messages on the command line
#include <assert.h>
@@ -11,22 +8,23 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -44,16 +42,20 @@
#include "nvim/mouse.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
-#include "nvim/pos.h"
+#include "nvim/os/time.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// To be able to scroll back at the "more" and "hit-enter" prompts we need to
// store the displayed text and remember where screen lines start.
@@ -64,7 +66,7 @@ struct msgchunk_S {
char sb_eol; // true when line ends after this text
int sb_msg_col; // column in which text starts
int sb_attr; // text attributes
- char sb_text[1]; // text to be displayed, actually longer
+ char sb_text[]; // text to be displayed
};
// Magic chars used in confirm dialog strings
@@ -142,7 +144,7 @@ static int msg_grid_pos_at_flush = 0;
static void ui_ext_msg_set_pos(int row, bool scrolled)
{
- char buf[MAX_MCO + 1];
+ char buf[MB_MAXCHAR + 1];
size_t size = (size_t)utf_char2bytes(curwin->w_p_fcs_chars.msgsep, buf);
buf[size] = '\0';
ui_call_msg_set_pos(msg_grid.handle, row, scrolled,
@@ -182,7 +184,7 @@ void msg_grid_validate(void)
msg_grid.dirty_col = xcalloc((size_t)Rows, sizeof(*msg_grid.dirty_col));
// Tricky: allow resize while pager or ex mode is active
- int pos = MAX(max_rows - msg_scrolled, 0);
+ int pos = (State & MODE_ASKMORE) ? 0 : MAX(max_rows - msg_scrolled, 0);
msg_grid.throttled = false; // don't throttle in 'cmdheight' area
msg_grid_set_pos(pos, msg_scrolled);
ui_comp_put_grid(&msg_grid, pos, 0, msg_grid.rows, msg_grid.cols,
@@ -213,17 +215,8 @@ void msg_grid_validate(void)
}
}
-/// Displays the string 's' on the status line
-/// When terminal not initialized (yet) os_errmsg(..) is used.
-///
-/// @return true if wait_return() not called
-int msg(char *s)
-{
- return msg_attr_keep(s, 0, false, false);
-}
-
/// Like msg() but keep it silent when 'verbosefile' is set.
-int verb_msg(char *s)
+int verb_msg(const char *s)
{
verbose_enter();
int n = msg_attr_keep(s, 0, false, false);
@@ -232,14 +225,18 @@ int verb_msg(char *s)
return n;
}
-int msg_attr(const char *s, const int attr)
+/// Displays the string 's' on the status line
+/// When terminal not initialized (yet) os_errmsg(..) is used.
+///
+/// @return true if wait_return() not called
+int msg(const char *s, const int attr)
FUNC_ATTR_NONNULL_ARG(1)
{
return msg_attr_keep(s, attr, false, false);
}
-/// Similar to msg_outtrans_attr, but support newlines and tabs.
-void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clear)
+/// Similar to msg_outtrans, but support newlines and tabs.
+void msg_multiline(const char *s, int attr, bool check_int, bool *need_clear)
FUNC_ATTR_NONNULL_ALL
{
const char *next_spec = s;
@@ -252,7 +249,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
if (next_spec != NULL) {
// Printing all char that are before the char found by strpbrk
- msg_outtrans_len_attr(s, (int)(next_spec - s), attr);
+ msg_outtrans_len(s, (int)(next_spec - s), attr);
if (*next_spec != TAB && *need_clear) {
msg_clr_eos();
@@ -266,7 +263,7 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea
// Print the rest of the message. We know there is no special
// character because strpbrk returned NULL
if (*s != NUL) {
- msg_outtrans_attr(s, attr);
+ msg_outtrans(s, attr);
}
}
@@ -279,8 +276,7 @@ void msg_multiattr(HlMessage hl_msg, const char *kind, bool history)
msg_ext_set_kind(kind);
for (uint32_t i = 0; i < kv_size(hl_msg); i++) {
HlMessageChunk chunk = kv_A(hl_msg, i);
- msg_multiline_attr((const char *)chunk.text.data, chunk.attr,
- true, &need_clear);
+ msg_multiline(chunk.text.data, chunk.attr, true, &need_clear);
}
if (history && kv_size(hl_msg)) {
add_msg_hist_multiattr(NULL, 0, 0, true, hl_msg);
@@ -294,7 +290,6 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
FUNC_ATTR_NONNULL_ALL
{
static int entered = 0;
- int retval;
char *buf = NULL;
if (keep && multiline) {
@@ -306,7 +301,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
// Skip messages not match ":filter pattern".
// Don't filter when there is an error.
- if (!emsg_on_display && message_filtered((char *)s)) {
+ if (!emsg_on_display && message_filtered(s)) {
return true;
}
@@ -334,24 +329,24 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
// Truncate the message if needed.
msg_start();
- buf = msg_strtrunc((char *)s, false);
+ buf = msg_strtrunc(s, false);
if (buf != NULL) {
s = buf;
}
bool need_clear = true;
if (multiline) {
- msg_multiline_attr(s, attr, false, &need_clear);
+ msg_multiline(s, attr, false, &need_clear);
} else {
- msg_outtrans_attr(s, attr);
+ msg_outtrans(s, attr);
}
if (need_clear) {
msg_clr_eos();
}
- retval = msg_end();
+ int retval = msg_end();
- if (keep && retval && vim_strsize((char *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
- set_keep_msg((char *)s, 0);
+ if (keep && retval && vim_strsize(s) < (Rows - cmdline_row - 1) * Columns + sc_col) {
+ set_keep_msg(s, 0);
}
need_fileinfo = false;
@@ -366,17 +361,16 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline)
/// @return an allocated string or NULL when no truncating is done.
///
/// @param force always truncate
-char *msg_strtrunc(char *s, int force)
+char *msg_strtrunc(const char *s, int force)
{
char *buf = NULL;
- int len;
- int room;
// May truncate message to avoid a hit-return prompt
if ((!msg_scroll && !need_wait_return && shortmess(SHM_TRUNCALL)
&& !exmode_active && msg_silent == 0 && !ui_has(kUIMessages))
|| force) {
- len = vim_strsize(s);
+ int room;
+ int len = vim_strsize(s);
if (msg_scrolled != 0) {
// Use all the columns.
room = (Rows - msg_row) * Columns - 1;
@@ -397,10 +391,9 @@ char *msg_strtrunc(char *s, int force)
/// Truncate a string "s" to "buf" with cell width "room".
/// "s" and "buf" may be equal.
-void trunc_string(char *s, char *buf, int room_in, int buflen)
+void trunc_string(const char *s, char *buf, int room_in, int buflen)
{
int room = room_in - 3; // "..." takes 3 chars
- int half;
int len = 0;
int e;
int i;
@@ -416,7 +409,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
if (room_in < 3) {
room = 0;
}
- half = room / 2;
+ int half = room / 2;
// First part: Start of the string.
for (e = 0; len < half && e < buflen; e++) {
@@ -441,7 +434,7 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
// Last part: End of the string.
half = i = (int)strlen(s);
- for (;;) {
+ while (true) {
do {
half = half - utf_head_off(s, s + half - 1) - 1;
} while (half > 0 && utf_iscomposing(utf_ptr2char(s + half)));
@@ -478,26 +471,19 @@ void trunc_string(char *s, char *buf, int room_in, int buflen)
buf[e + 3 + len - 1] = NUL;
} else {
// can't fit in the "...", just truncate it
- buf[e - 1] = NUL;
+ buf[buflen - 1] = NUL;
}
}
-// Note: Caller of smsg() and smsg_attr() must check the resulting string is
-// shorter than IOSIZE!!!
-
-int smsg(const char *s, ...)
- FUNC_ATTR_PRINTF(1, 2)
-{
- va_list arglist;
-
- va_start(arglist, s);
- vim_vsnprintf(IObuff, IOSIZE, s, arglist);
- va_end(arglist);
-
- return msg(IObuff);
-}
-
-int smsg_attr(int attr, const char *s, ...)
+/// Shows a printf-style message with attributes.
+///
+/// Note: Caller must check the resulting string is shorter than IOSIZE!!!
+///
+/// @see semsg
+/// @see swmsg
+///
+/// @param s printf-style format message
+int smsg(int attr, const char *s, ...)
FUNC_ATTR_PRINTF(2, 3)
{
va_list arglist;
@@ -505,7 +491,7 @@ int smsg_attr(int attr, const char *s, ...)
va_start(arglist, s);
vim_vsnprintf(IObuff, IOSIZE, s, arglist);
va_end(arglist);
- return msg_attr((const char *)IObuff, attr);
+ return msg(IObuff, attr);
}
int smsg_attr_keep(int attr, const char *s, ...)
@@ -516,7 +502,7 @@ int smsg_attr_keep(int attr, const char *s, ...)
va_start(arglist, s);
vim_vsnprintf(IObuff, IOSIZE, s, arglist);
va_end(arglist);
- return msg_attr_keep((const char *)IObuff, attr, true, false);
+ return msg_attr_keep(IObuff, attr, true, false);
}
// Remember the last sourcing name/lnum used in an error message, so that it
@@ -581,10 +567,10 @@ static char *get_emsg_lnum(void)
if (SOURCING_NAME != NULL
&& (other_sourcing_name() || SOURCING_LNUM != last_sourcing_lnum)
&& SOURCING_LNUM != 0) {
- const char *const p = _("line %4ld:");
+ const char *const p = _("line %4" PRIdLINENR ":");
const size_t buf_len = 20 + strlen(p);
char *const buf = xmalloc(buf_len);
- snprintf(buf, buf_len, p, (long)SOURCING_LNUM);
+ snprintf(buf, buf_len, p, SOURCING_LNUM);
return buf;
}
return NULL;
@@ -607,12 +593,12 @@ void msg_source(int attr)
char *p = get_emsg_source();
if (p != NULL) {
msg_scroll = true; // this will take more than one line
- msg_attr(p, attr);
+ msg(p, attr);
xfree(p);
}
p = get_emsg_lnum();
if (p != NULL) {
- msg_attr(p, HL_ATTR(HLF_N));
+ msg(p, HL_ATTR(HLF_N));
xfree(p);
last_sourcing_lnum = SOURCING_LNUM; // only once for each line
}
@@ -643,9 +629,8 @@ int emsg_not_now(void)
return false;
}
-static bool emsg_multiline(const char *s, bool multiline)
+bool emsg_multiline(const char *s, bool multiline)
{
- int attr;
bool ignore = false;
// Skip this if not giving error messages at the moment.
@@ -666,7 +651,7 @@ static bool emsg_multiline(const char *s, bool multiline)
// be found, the message will be displayed later on.) "ignore" is set
// when the message should be ignored completely (used for the
// interrupt message).
- if (cause_errthrow(s, severe, &ignore)) {
+ if (cause_errthrow(s, multiline, severe, &ignore)) {
if (!ignore) {
did_emsg++;
}
@@ -707,8 +692,8 @@ static bool emsg_multiline(const char *s, bool multiline)
// Log (silent) errors as debug messages.
if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
- DLOG("(:silent) %s (%s (line %ld))",
- s, SOURCING_NAME, (long)SOURCING_LNUM);
+ DLOG("(:silent) %s (%s (line %" PRIdLINENR "))",
+ s, SOURCING_NAME, SOURCING_LNUM);
} else {
DLOG("(:silent) %s", s);
}
@@ -718,7 +703,7 @@ static bool emsg_multiline(const char *s, bool multiline)
// Log editor errors as INFO.
if (SOURCING_NAME != NULL && SOURCING_LNUM != 0) {
- ILOG("%s (%s (line %ld))", s, SOURCING_NAME, (long)SOURCING_LNUM);
+ ILOG("%s (%s (line %" PRIdLINENR "))", s, SOURCING_NAME, SOURCING_LNUM);
} else {
ILOG("%s", s);
}
@@ -742,7 +727,7 @@ static bool emsg_multiline(const char *s, bool multiline)
}
emsg_on_display = true; // remember there is an error message
- attr = HL_ATTR(HLF_E); // set highlight mode for error messages
+ int attr = HL_ATTR(HLF_E); // set highlight mode for error messages
if (msg_scrolled != 0) {
need_wait_return = true; // needed in case emsg() is called after
} // wait_return() has reset need_wait_return
@@ -774,10 +759,12 @@ bool emsg(const char *s)
void emsg_invreg(int name)
{
- semsg(_("E354: Invalid register name: '%s'"), transchar(name));
+ semsg(_("E354: Invalid register name: '%s'"), transchar_buf(NULL, name));
}
/// Print an error message with unknown number of arguments
+///
+/// @return whether the message was displayed
bool semsg(const char *const fmt, ...)
FUNC_ATTR_PRINTF(1, 2)
{
@@ -864,7 +851,7 @@ void siemsg(const char *s, ...)
}
/// Give an "Internal error" message.
-void internal_error(char *where)
+void internal_error(const char *where)
{
siemsg(_(e_intern2), where);
}
@@ -888,22 +875,38 @@ void msg_schedule_semsg(const char *const fmt, ...)
loop_schedule_deferred(&main_loop, event_create(msg_semsg_event, 1, s));
}
+static void msg_semsg_multiline_event(void **argv)
+{
+ char *s = argv[0];
+ (void)emsg_multiline(s, true);
+ xfree(s);
+}
+
+void msg_schedule_semsg_multiline(const char *const fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vim_vsnprintf(IObuff, IOSIZE, fmt, ap);
+ va_end(ap);
+
+ char *s = xstrdup(IObuff);
+ loop_schedule_deferred(&main_loop, event_create(msg_semsg_multiline_event, 1, s));
+}
+
/// Like msg(), but truncate to a single line if p_shm contains 't', or when
/// "force" is true. This truncates in another way as for normal messages.
/// Careful: The string may be changed by msg_may_trunc()!
///
/// @return a pointer to the printed message, if wait_return() not called.
-char *msg_trunc_attr(char *s, bool force, int attr)
+char *msg_trunc(char *s, bool force, int attr)
{
- int n;
-
// Add message to history before truncating.
add_msg_hist(s, -1, attr, false);
char *ts = msg_may_trunc(force, s);
msg_hist_off = true;
- n = msg_attr(ts, attr);
+ int n = msg(ts, attr);
msg_hist_off = false;
if (n) {
@@ -1009,12 +1012,10 @@ static void add_msg_hist_multiattr(const char *s, int len, int attr, bool multil
/// @return FAIL if there are no messages.
int delete_first_msg(void)
{
- struct msg_hist *p;
-
if (msg_hist_len <= 0) {
return FAIL;
}
- p = first_msg_hist;
+ struct msg_hist *p = first_msg_hist;
first_msg_hist = p->next;
if (first_msg_hist == NULL) { // history is becoming empty
assert(msg_hist_len == 1);
@@ -1028,13 +1029,9 @@ int delete_first_msg(void)
}
/// :messages command implementation
-void ex_messages(void *const eap_p)
+void ex_messages(exarg_T *eap)
FUNC_ATTR_NONNULL_ALL
{
- const exarg_T *const eap = (const exarg_T *)eap_p;
- struct msg_hist *p;
- int c = 0;
-
if (strcmp(eap->arg, "clear") == 0) {
int keep = eap->addr_count == 0 ? 0 : eap->line2;
@@ -1049,9 +1046,10 @@ void ex_messages(void *const eap_p)
return;
}
- p = first_msg_hist;
+ struct msg_hist *p = first_msg_hist;
if (eap->addr_count != 0) {
+ int c = 0;
// Count total messages
for (; p != NULL && !got_int; p = p->next) {
c++;
@@ -1072,7 +1070,7 @@ void ex_messages(void *const eap_p)
for (; p != NULL; p = p->next) {
if (kv_size(p->multiattr) || (p->msg && p->msg[0])) {
Array entry = ARRAY_DICT_INIT;
- ADD(entry, STRING_OBJ(cstr_to_string(p->kind)));
+ ADD(entry, CSTR_TO_OBJ(p->kind));
Array content = ARRAY_DICT_INIT;
if (kv_size(p->multiattr)) {
for (uint32_t i = 0; i < kv_size(p->multiattr); i++) {
@@ -1085,7 +1083,7 @@ void ex_messages(void *const eap_p)
} else if (p->msg && p->msg[0]) {
Array content_entry = ARRAY_DICT_INIT;
ADD(content_entry, INTEGER_OBJ(p->attr));
- ADD(content_entry, STRING_OBJ(cstr_to_string((char *)(p->msg))));
+ ADD(content_entry, CSTR_TO_OBJ(p->msg));
ADD(content, ARRAY_OBJ(content_entry));
}
ADD(entry, ARRAY_OBJ(content));
@@ -1130,8 +1128,6 @@ void msg_end_prompt(void)
void wait_return(int redraw)
{
int c;
- int oldState;
- int tmpState;
int had_got_int;
FILE *save_scriptout;
@@ -1165,7 +1161,7 @@ void wait_return(int redraw)
}
redir_off = true; // don't redirect this message
- oldState = State;
+ int oldState = State;
if (quit_more) {
c = CAR; // just pretend CR was hit
quit_more = false;
@@ -1226,9 +1222,7 @@ void wait_return(int redraw)
} else {
msg_didout = false;
c = K_IGNORE;
- msg_col =
- cmdmsg_rl ? Columns - 1 :
- 0;
+ msg_col = 0;
}
if (quit_more) {
c = CAR; // just pretend CR was hit
@@ -1253,6 +1247,7 @@ void wait_return(int redraw)
|| c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSEMOVE);
os_breakcheck();
+
// Avoid that the mouse-up event causes visual mode to start.
if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE
|| c == K_X1MOUSE || c == K_X2MOUSE) {
@@ -1281,7 +1276,7 @@ void wait_return(int redraw)
// If the screen size changed screen_resize() will redraw the screen.
// Otherwise the screen is only redrawn if 'redraw' is set and no ':'
// typed.
- tmpState = State;
+ int tmpState = State;
State = oldState; // restore State before screen_resize()
setmouse();
msg_check();
@@ -1329,7 +1324,7 @@ static void hit_return_msg(void)
}
/// Set "keep_msg" to "s". Free the old value and check for NULL pointer.
-void set_keep_msg(char *s, int attr)
+void set_keep_msg(const char *s, int attr)
{
xfree(keep_msg);
if (s != NULL && msg_silent == 0) {
@@ -1341,9 +1336,17 @@ void set_keep_msg(char *s, int attr)
keep_msg_attr = attr;
}
-void msgmore(long n)
+/// Return true if printing messages should currently be done.
+bool messaging(void)
{
- long pn;
+ // TODO(bfredl): with general support for "async" messages with p_ch,
+ // this should be re-enabled.
+ return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages));
+}
+
+void msgmore(int n)
+{
+ int pn;
if (global_busy // no messages now, wait until global is finished
|| !messaging()) { // 'lazyredraw' set, don't do messages now
@@ -1366,17 +1369,17 @@ void msgmore(long n)
if (pn > p_report) {
if (n > 0) {
vim_snprintf(msg_buf, MSG_BUF_LEN,
- NGETTEXT("%ld more line", "%ld more lines", pn),
+ NGETTEXT("%d more line", "%d more lines", pn),
pn);
} else {
vim_snprintf(msg_buf, MSG_BUF_LEN,
- NGETTEXT("%ld line less", "%ld fewer lines", pn),
+ NGETTEXT("%d line less", "%d fewer lines", pn),
pn);
}
if (got_int) {
xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN);
}
- if (msg(msg_buf)) {
+ if (msg(msg_buf, 0)) {
set_keep_msg(msg_buf, 0);
keep_msg_more = true;
}
@@ -1397,7 +1400,11 @@ void msg_ext_set_kind(const char *msg_kind)
/// Prepare for outputting characters in the command line.
void msg_start(void)
{
- int did_return = false;
+ bool did_return = false;
+
+ if (msg_row < cmdline_row) {
+ msg_row = cmdline_row;
+ }
if (!msg_silent) {
XFREE_CLEAR(keep_msg); // don't display old message now
@@ -1421,7 +1428,7 @@ void msg_start(void)
if (!msg_scroll && full_screen) { // overwrite last message
msg_row = cmdline_row;
- msg_col = cmdmsg_rl ? Columns - 1 : 0;
+ msg_col = 0;
} else if (msg_didout || (p_ch == 0 && !ui_has(kUIMessages))) { // start message on next line
msg_putchar('\n');
did_return = true;
@@ -1462,7 +1469,7 @@ void msg_putchar(int c)
void msg_putchar_attr(int c, int attr)
{
- char buf[MB_MAXBYTES + 1];
+ char buf[MB_MAXCHAR + 1];
if (IS_SPECIAL(c)) {
buf[0] = (char)K_SPECIAL;
@@ -1470,33 +1477,33 @@ void msg_putchar_attr(int c, int attr)
buf[2] = (char)K_THIRD(c);
buf[3] = NUL;
} else {
- buf[utf_char2bytes(c, (char *)buf)] = NUL;
+ buf[utf_char2bytes(c, buf)] = NUL;
}
- msg_puts_attr((const char *)buf, attr);
+ msg_puts_attr(buf, attr);
}
-void msg_outnum(long n)
+void msg_outnum(int n)
{
char buf[20];
- snprintf(buf, sizeof(buf), "%ld", n);
+ snprintf(buf, sizeof(buf), "%d", n);
msg_puts(buf);
}
-void msg_home_replace(char *fname)
+void msg_home_replace(const char *fname)
{
msg_home_replace_attr(fname, 0);
}
-void msg_home_replace_hl(char *fname)
+void msg_home_replace_hl(const char *fname)
{
msg_home_replace_attr(fname, HL_ATTR(HLF_D));
}
-static void msg_home_replace_attr(char *fname, int attr)
+static void msg_home_replace_attr(const char *fname, int attr)
{
char *name = home_replace_save(NULL, fname);
- msg_outtrans_attr(name, attr);
+ msg_outtrans(name, attr);
xfree(name);
}
@@ -1505,44 +1512,33 @@ static void msg_home_replace_attr(char *fname, int attr)
/// Use attributes 'attr'.
///
/// @return the number of characters it takes on the screen.
-int msg_outtrans(char *str)
-{
- return msg_outtrans_attr(str, 0);
-}
-
-int msg_outtrans_attr(const char *str, int attr)
-{
- return msg_outtrans_len_attr(str, (int)strlen(str), attr);
-}
-
-int msg_outtrans_len(const char *str, int len)
+int msg_outtrans(const char *str, int attr)
{
- return msg_outtrans_len_attr(str, len, 0);
+ return msg_outtrans_len(str, (int)strlen(str), attr);
}
/// Output one character at "p".
/// Handles multi-byte characters.
///
/// @return pointer to the next character.
-char *msg_outtrans_one(char *p, int attr)
+const char *msg_outtrans_one(const char *p, int attr)
{
int l;
if ((l = utfc_ptr2len(p)) > 1) {
- msg_outtrans_len_attr(p, l, attr);
+ msg_outtrans_len(p, l, attr);
return p + l;
}
- msg_puts_attr((const char *)transchar_byte((uint8_t)(*p)), attr);
+ msg_puts_attr(transchar_byte_buf(NULL, (uint8_t)(*p)), attr);
return p + 1;
}
-int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
+int msg_outtrans_len(const char *msgstr, int len, int attr)
{
int retval = 0;
const char *str = msgstr;
const char *plain_start = msgstr;
char *s;
- int mb_l;
int c;
int save_got_int = got_int;
@@ -1555,17 +1551,18 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
attr &= ~MSG_HIST;
}
- // If the string starts with a composing character first draw a space on
- // which the composing char can be drawn.
- if (utf_iscomposing(utf_ptr2char((char *)msgstr))) {
- msg_puts_attr(" ", attr);
+ // When drawing over the command line no need to clear it later or remove
+ // the mode message.
+ if (msg_row >= cmdline_row && msg_col == 0) {
+ clear_cmdline = false;
+ mode_displayed = false;
}
// Go over the string. Special characters are translated and printed.
// Normal characters are printed several at a time.
while (--len >= 0 && !got_int) {
// Don't include composing chars after the end.
- mb_l = utfc_ptr2len_len(str, len + 1);
+ int mb_l = utfc_ptr2len_len(str, len + 1);
if (mb_l > 1) {
c = utf_ptr2char(str);
if (vim_isprintc(c)) {
@@ -1575,25 +1572,24 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
// Unprintable multi-byte char: print the printable chars so
// far and the translation of the unprintable char.
if (str > plain_start) {
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
plain_start = str + mb_l;
- msg_puts_attr((const char *)transchar(c),
- (attr == 0 ? HL_ATTR(HLF_8) : attr));
+ msg_puts_attr(transchar_buf(NULL, c), attr == 0 ? HL_ATTR(HLF_8) : attr);
retval += char2cells(c);
}
len -= mb_l - 1;
str += mb_l;
} else {
- s = (char *)transchar_byte((uint8_t)(*str));
+ s = transchar_byte_buf(NULL, (uint8_t)(*str));
if (s[1] != NUL) {
// Unprintable char: print the printable chars so far and the
// translation of the unprintable char.
if (str > plain_start) {
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
plain_start = str + 1;
- msg_puts_attr((const char *)s, attr == 0 ? HL_ATTR(HLF_8) : attr);
+ msg_puts_attr(s, attr == 0 ? HL_ATTR(HLF_8) : attr);
retval += (int)strlen(s);
} else {
retval++;
@@ -1604,7 +1600,7 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
if (str > plain_start && !got_int) {
// Print the printable chars at the end.
- msg_puts_attr_len(plain_start, str - plain_start, attr);
+ msg_puts_len(plain_start, str - plain_start, attr);
}
got_int |= save_got_int;
@@ -1612,11 +1608,11 @@ int msg_outtrans_len_attr(const char *msgstr, int len, int attr)
return retval;
}
-void msg_make(char *arg)
+void msg_make(const char *arg)
{
int i;
- static char *str = "eeffoc";
- static char *rs = "Plon#dqg#vxjduB";
+ static const char *str = "eeffoc";
+ static const char *rs = "Plon#dqg#vxjduB";
arg = skipwhite(arg);
for (i = 5; *arg && i >= 0; i--) {
@@ -1667,9 +1663,9 @@ int msg_outtrans_special(const char *strstart, bool from, int maxlen)
}
if (text[0] != NUL && text[1] == NUL) {
// single-byte character or illegal byte
- text = (char *)transchar_byte((uint8_t)text[0]);
+ text = transchar_byte_buf(NULL, (uint8_t)text[0]);
}
- const int len = vim_strsize((char *)text);
+ const int len = vim_strsize(text);
if (maxlen > 0 && retval + len >= maxlen) {
break;
}
@@ -1773,7 +1769,7 @@ const char *str2special(const char **const sp, const bool replace_spaces, const
|| c < ' '
|| (replace_spaces && c == ' ')
|| (replace_lt && c == '<')) {
- return (const char *)get_special_key_name(c, modifiers);
+ return get_special_key_name(c, modifiers);
}
buf[0] = (char)c;
buf[1] = NUL;
@@ -1802,20 +1798,20 @@ void str2specialbuf(const char *sp, char *buf, size_t len)
}
/// print line for :print or :list command
-void msg_prt_line(char *s, int list)
+void msg_prt_line(const char *s, int list)
{
int c;
int col = 0;
int n_extra = 0;
int c_extra = 0;
int c_final = 0;
- char *p_extra = NULL; // init to make SASC shut up
+ const char *p_extra = NULL; // init to make SASC shut up
int n;
int attr = 0;
- char *lead = NULL;
+ const char *lead = NULL;
bool in_multispace = false;
int multispace_pos = 0;
- char *trail = NULL;
+ const char *trail = NULL;
int l;
if (curwin->w_p_list) {
@@ -1878,10 +1874,13 @@ void msg_prt_line(char *s, int list)
continue;
} else {
attr = 0;
- c = (unsigned char)(*s++);
- in_multispace = c == ' ' && ((col > 0 && s[-2] == ' ') || *s == ' ');
- if (!in_multispace) {
- multispace_pos = 0;
+ c = (uint8_t)(*s++);
+ if (list) {
+ in_multispace = c == ' ' && (*s == ' '
+ || (col > 0 && s[-2] == ' '));
+ if (!in_multispace) {
+ multispace_pos = 0;
+ }
}
if (c == TAB && (!list || curwin->w_p_lcs_chars.tab1)) {
// tab amount depends on current column
@@ -1913,7 +1912,7 @@ void msg_prt_line(char *s, int list)
s--;
} else if (c != NUL && (n = byte2cells(c)) > 1) {
n_extra = n - 1;
- p_extra = (char *)transchar_byte(c);
+ p_extra = transchar_byte_buf(NULL, c);
c_extra = NUL;
c_final = NUL;
c = (unsigned char)(*p_extra++);
@@ -1921,7 +1920,7 @@ void msg_prt_line(char *s, int list)
// the same in plain text.
attr = HL_ATTR(HLF_0);
} else if (c == ' ') {
- if (list && lead != NULL && s <= lead && in_multispace
+ if (lead != NULL && s <= lead && in_multispace
&& curwin->w_p_lcs_chars.leadmultispace != NULL) {
c = curwin->w_p_lcs_chars.leadmultispace[multispace_pos++];
if (curwin->w_p_lcs_chars.leadmultispace[multispace_pos] == NUL) {
@@ -1934,7 +1933,7 @@ void msg_prt_line(char *s, int list)
} else if (trail != NULL && s > trail) {
c = curwin->w_p_lcs_chars.trail;
attr = HL_ATTR(HLF_0);
- } else if (list && in_multispace
+ } else if (in_multispace
&& curwin->w_p_lcs_chars.multispace != NULL) {
c = curwin->w_p_lcs_chars.multispace[multispace_pos++];
if (curwin->w_p_lcs_chars.multispace[multispace_pos] == NUL) {
@@ -1958,40 +1957,6 @@ void msg_prt_line(char *s, int list)
msg_clr_eos();
}
-/// Use grid_puts() to output one multi-byte character.
-///
-/// @return the pointer "s" advanced to the next character.
-static char *screen_puts_mbyte(char *s, int l, int attr)
-{
- int cw;
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
-
- msg_didout = true; // remember that line is not empty
- cw = utf_ptr2cells(s);
- if (cw > 1
- && (cmdmsg_rl ? msg_col <= 1 : msg_col == Columns - 1)) {
- // Doesn't fit, print a highlighted '>' to fill it up.
- msg_screen_putchar('>', HL_ATTR(HLF_AT));
- return s;
- }
-
- grid_puts_len(&msg_grid_adj, s, l, msg_row, msg_col, attr);
- if (cmdmsg_rl) {
- msg_col -= cw;
- if (msg_col == 0) {
- msg_col = Columns;
- msg_row++;
- }
- } else {
- msg_col += cw;
- if (msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
- }
- return s + l;
-}
-
/// Output a string to the screen at position msg_row, msg_col.
/// Update msg_row and msg_col for the next message.
void msg_puts(const char *s)
@@ -2007,29 +1972,23 @@ void msg_puts_title(const char *s)
/// Show a message in such a way that it always fits in the line. Cut out a
/// part in the middle and replace it with "..." when necessary.
/// Does not handle multi-byte characters!
-void msg_outtrans_long_attr(char *longstr, int attr)
-{
- msg_outtrans_long_len_attr(longstr, (int)strlen(longstr), attr);
-}
-
-void msg_outtrans_long_len_attr(char *longstr, int len, int attr)
+void msg_outtrans_long(const char *longstr, int attr)
{
+ int len = (int)strlen(longstr);
int slen = len;
- int room;
-
- room = Columns - msg_col;
+ int room = Columns - msg_col;
if (len > room && room >= 20) {
slen = (room - 3) / 2;
- msg_outtrans_len_attr(longstr, slen, attr);
+ msg_outtrans_len(longstr, slen, attr);
msg_puts_attr("...", HL_ATTR(HLF_8));
}
- msg_outtrans_len_attr(longstr + len - slen, slen, attr);
+ msg_outtrans_len(longstr + len - slen, slen, attr);
}
/// Basic function for writing a message with highlight attributes.
void msg_puts_attr(const char *const s, const int attr)
{
- msg_puts_attr_len(s, -1, attr);
+ msg_puts_len(s, -1, attr);
}
/// Write a message with highlight attributes
@@ -2037,7 +1996,7 @@ void msg_puts_attr(const char *const s, const int attr)
/// @param[in] str NUL-terminated message string.
/// @param[in] len Length of the string or -1.
/// @param[in] attr Highlight attribute.
-void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
+void msg_puts_len(const char *const str, const ptrdiff_t len, int attr)
FUNC_ATTR_NONNULL_ALL
{
assert(len < 0 || memchr(str, 0, (size_t)len) == NULL);
@@ -2113,7 +2072,7 @@ void msg_printf_attr(const int attr, const char *const fmt, ...)
va_end(ap);
msg_scroll = true;
- msg_puts_attr_len(msgbuf, (ptrdiff_t)len, attr);
+ msg_puts_len(msgbuf, (ptrdiff_t)len, attr);
}
static void msg_ext_emit_chunk(void)
@@ -2130,19 +2089,13 @@ static void msg_ext_emit_chunk(void)
ADD(msg_ext_chunks, ARRAY_OBJ(chunk));
}
-/// The display part of msg_puts_attr_len().
+/// The display part of msg_puts_len().
/// May be called recursively to display scroll-back text.
static void msg_puts_display(const char *str, int maxlen, int attr, int recurse)
{
const char *s = str;
- const char *t_s = str; // String from "t_s" to "s" is still todo.
- int t_col = 0; // Screen cells todo, 0 when "t_s" not used.
- int l;
- int cw;
- const char *sb_str = (char *)str;
+ const char *sb_str = str;
int sb_col = msg_col;
- int wrap;
- int did_last_char;
did_wait_return = false;
@@ -2152,197 +2105,175 @@ static void msg_puts_display(const char *str, int maxlen, int attr, int recurse)
msg_ext_last_attr = attr;
}
// Concat pieces with the same highlight
- size_t len = strnlen(str, (size_t)maxlen); // -V781
- ga_concat_len(&msg_ext_last_chunk, (char *)str, len);
+ size_t len = strnlen(str, (size_t)maxlen);
+ ga_concat_len(&msg_ext_last_chunk, str, len);
msg_ext_cur_len += len;
return;
}
+ int print_attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
msg_grid_validate();
cmdline_was_last_drawn = redrawing_cmdline;
- while ((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL) {
- // We are at the end of the screen line when:
- // - When outputting a newline.
- // - When outputting a character in the last column.
- if (!recurse && msg_row >= Rows - 1
- && (*s == '\n' || (cmdmsg_rl
- ? (msg_col <= 1
- || (*s == TAB && msg_col <= 7)
- || (utf_ptr2cells(s) > 1
- && msg_col <= 2))
- : ((*s != '\r' && msg_col + t_col >= Columns - 1)
- || (*s == TAB
- && msg_col + t_col >= ((Columns - 1) & ~7))
- || (utf_ptr2cells(s) > 1
- && msg_col + t_col >= Columns - 2))))) {
- // The screen is scrolled up when at the last row (some terminals
- // scroll automatically, some don't. To avoid problems we scroll
- // ourselves).
- if (t_col > 0) {
- // output postponed text
- t_puts(&t_col, t_s, s, attr);
- }
+ int msg_row_pending = -1;
- // When no more prompt and no more room, truncate here
+ while (true) {
+ if (msg_col >= Columns) {
+ if (p_more && !recurse) {
+ // Store text for scrolling back.
+ store_sb_text(&sb_str, s, attr, &sb_col, true);
+ }
if (msg_no_more && lines_left == 0) {
break;
}
- // Scroll the screen up one line.
- bool has_last_char = ((uint8_t)(*s) >= ' ' && !cmdmsg_rl);
- msg_scroll_up(!has_last_char, false);
+ msg_col = 0;
+ msg_row++;
+ msg_didout = false;
+ }
- msg_row = Rows - 2;
- if (msg_col >= Columns) { // can happen after screen resize
- msg_col = Columns - 1;
- }
+ if (msg_row >= Rows) {
+ msg_row = Rows - 1;
- // Display char in last column before showing more-prompt.
- if (has_last_char) {
- if (maxlen >= 0) {
- // Avoid including composing chars after the end.
- l = utfc_ptr2len_len(s, (int)((str + maxlen) - s));
- } else {
- l = utfc_ptr2len(s);
- }
- s = screen_puts_mbyte((char *)s, l, attr);
- did_last_char = true;
- } else {
- did_last_char = false;
+ // When no more prompt and no more room, truncate here
+ if (msg_no_more && lines_left == 0) {
+ break;
}
- // Tricky: if last cell will be written, delay the throttle until
- // after the first scroll. Otherwise we would need to keep track of it.
- if (has_last_char && msg_do_throttle()) {
- if (!msg_grid.throttled) {
- msg_grid_scroll_discount++;
+ if (!recurse) {
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
+ msg_row_pending = -1;
}
- msg_grid.throttled = true;
- }
-
- if (p_more) {
- // Store text for scrolling back.
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
- }
- inc_msg_scrolled();
- need_wait_return = true; // may need wait_return() in main()
- redraw_cmdline = true;
- if (cmdline_row > 0 && !exmode_active) {
- cmdline_row--;
- }
+ // Scroll the screen up one line.
+ msg_scroll_up(true, false);
- // If screen is completely filled and 'more' is set then wait
- // for a character.
- if (lines_left > 0) {
- lines_left--;
- }
- if (p_more && lines_left == 0 && State != MODE_HITRETURN
- && !msg_no_more && !exmode_active) {
- if (do_more_prompt(NUL)) {
- s = confirm_msg_tail;
+ inc_msg_scrolled();
+ need_wait_return = true; // may need wait_return() in main()
+ redraw_cmdline = true;
+ if (cmdline_row > 0 && !exmode_active) {
+ cmdline_row--;
}
- if (quit_more) {
- return;
+
+ // If screen is completely filled and 'more' is set then wait
+ // for a character.
+ if (lines_left > 0) {
+ lines_left--;
}
- }
- // When we displayed a char in last column need to check if there
- // is still more.
- if (did_last_char) {
- continue;
+ if (p_more && lines_left == 0 && State != MODE_HITRETURN
+ && !msg_no_more && !exmode_active) {
+ if (do_more_prompt(NUL)) {
+ s = confirm_msg_tail;
+ }
+ if (quit_more) {
+ return;
+ }
+ }
}
}
- wrap = *s == '\n'
- || msg_col + t_col >= Columns
- || (utf_ptr2cells(s) > 1
- && msg_col + t_col >= Columns - 1)
- ;
- if (t_col > 0 && (wrap || *s == '\r' || *s == '\b'
- || *s == '\t' || *s == BELL)) {
- // Output any postponed text.
- t_puts(&t_col, t_s, s, attr);
+ if (!((maxlen < 0 || (int)(s - str) < maxlen) && *s != NUL)) {
+ break;
}
- if (wrap && p_more && !recurse) {
- // Store text for scrolling back.
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, true);
+ if (msg_row != msg_row_pending && ((uint8_t)(*s) >= 0x20 || *s == TAB)) {
+ // TODO(bfredl): this logic is messier that it has to be. What
+ // messages really want is its own private linebuf_char buffer.
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
+ }
+ grid_line_start(&msg_grid_adj, msg_row);
+ msg_row_pending = msg_row;
}
- if (*s == '\n') { // go to next line
- msg_didout = false; // remember that line is empty
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- } else {
- msg_col = 0;
- }
- if (++msg_row >= Rows) { // safety check
- msg_row = Rows - 1;
- }
- } else if (*s == '\r') { // go to column 0
- msg_col = 0;
- } else if (*s == '\b') { // go to previous char
- if (msg_col) {
- msg_col--;
- }
- } else if (*s == TAB) { // translate Tab into spaces
- do {
- msg_screen_putchar(' ', attr);
- } while (msg_col & 7);
- } else if (*s == BELL) { // beep (from ":sh")
- vim_beep(BO_SH);
- } else if ((uint8_t)(*s) >= 0x20) { // printable char
- cw = utf_ptr2cells(s);
- if (maxlen >= 0) {
- // avoid including composing chars after the end
- l = utfc_ptr2len_len(s, (int)((str + maxlen) - s));
+ if ((uint8_t)(*s) >= 0x20) { // printable char
+ int cw = utf_ptr2cells(s);
+ // avoid including composing chars after the end
+ int l = (maxlen >= 0) ? utfc_ptr2len_len(s, (int)((str + maxlen) - s)) : utfc_ptr2len(s);
+
+ if (cw > 1 && (msg_col == Columns - 1)) {
+ // Doesn't fit, print a highlighted '>' to fill it up.
+ grid_line_puts(msg_col, ">", 1, HL_ATTR(HLF_AT));
+ cw = 1;
} else {
- l = utfc_ptr2len(s);
+ grid_line_puts(msg_col, s, l, print_attr);
+ s += l;
}
- // When drawing from right to left or when a double-wide character
- // doesn't fit, draw a single character here. Otherwise collect
- // characters and draw them all at once later.
- if (cmdmsg_rl || (cw > 1 && msg_col + t_col >= Columns - 1)) {
- if (l > 1) {
- s = screen_puts_mbyte((char *)s, l, attr) - 1;
- } else {
- msg_screen_putchar(*s, attr);
+ msg_didout = true; // remember that line is not empty
+ msg_col += cw;
+ } else {
+ char c = *s++;
+ if (c == '\n') { // go to next line
+ msg_didout = false; // remember that line is empty
+ msg_col = 0;
+ msg_row++;
+ if (p_more && !recurse) {
+ // Store text for scrolling back.
+ store_sb_text(&sb_str, s, attr, &sb_col, true);
}
- } else {
- // postpone this character until later
- if (t_col == 0) {
- t_s = s;
+ } else if (c == '\r') { // go to column 0
+ msg_col = 0;
+ } else if (c == '\b') { // go to previous char
+ if (msg_col) {
+ msg_col--;
}
- t_col += cw;
- s += l - 1;
+ } else if (c == TAB) { // translate Tab into spaces
+ do {
+ grid_line_puts(msg_col, " ", 1, print_attr);
+ msg_col += 1;
+
+ if (msg_col == Columns) {
+ break;
+ }
+ } while (msg_col & 7);
+ } else if (c == BELL) { // beep (from ":sh")
+ vim_beep(BO_SH);
}
}
- s++;
}
- // Output any postponed text.
- if (t_col > 0) {
- t_puts(&t_col, t_s, s, attr);
+ if (msg_row_pending >= 0) {
+ msg_line_flush();
}
+ msg_cursor_goto(msg_row, msg_col);
+
if (p_more && !recurse && !(s == sb_str + 1 && *sb_str == '\n')) {
- store_sb_text((char **)&sb_str, (char *)s, attr, &sb_col, false);
+ store_sb_text(&sb_str, s, attr, &sb_col, false);
}
msg_check();
}
+void msg_line_flush(void)
+{
+ if (cmdmsg_rl) {
+ grid_line_mirror();
+ }
+ grid_line_flush_if_valid_row();
+}
+
+void msg_cursor_goto(int row, int col)
+{
+ ScreenGrid *grid = &msg_grid_adj;
+ if (cmdmsg_rl) {
+ col = Columns - 1 - col;
+ }
+ grid_adjust(&grid, &row, &col);
+ ui_grid_cursor_goto(grid->handle, row, col);
+}
+
/// @return true when ":filter pattern" was used and "msg" does not match
/// "pattern".
-bool message_filtered(char *msg)
+bool message_filtered(const char *msg)
{
if (cmdmod.cmod_filter_regmatch.regprog == NULL) {
return false;
}
- bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, (colnr_T)0);
+ bool match = vim_regexec(&cmdmod.cmod_filter_regmatch, msg, 0);
return cmdmod.cmod_filter_force ? match : !match;
}
@@ -2502,7 +2433,7 @@ static sb_clear_T do_clear_sb_text = SB_CLEAR_NONE;
/// @param sb_str start of string
/// @param s just after string
/// @param finish line ends
-static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int finish)
+static void store_sb_text(const char **sb_str, const char *s, int attr, int *sb_col, int finish)
{
msgchunk_T *mp;
@@ -2510,6 +2441,9 @@ static void store_sb_text(char **sb_str, char *s, int attr, int *sb_col, int fin
|| do_clear_sb_text == SB_CLEAR_CMDLINE_DONE) {
clear_sb_text(do_clear_sb_text == SB_CLEAR_ALL);
msg_sb_eol(); // prevent messages from overlapping
+ if (do_clear_sb_text == SB_CLEAR_CMDLINE_DONE && s > *sb_str && **sb_str == '\n') {
+ (*sb_str)++;
+ }
do_clear_sb_text = SB_CLEAR_NONE;
}
@@ -2652,15 +2586,11 @@ void msg_sb_eol(void)
static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
{
msgchunk_T *mp = smp;
- char *p;
- for (;;) {
+ while (true) {
msg_row = row;
msg_col = mp->sb_msg_col;
- p = mp->sb_text;
- if (*p == '\n') { // don't display the line break
- p++;
- }
+ char *p = mp->sb_text;
msg_puts_display(p, -1, mp->sb_attr, true);
if (mp->sb_eol || mp->sb_next == NULL) {
break;
@@ -2671,26 +2601,6 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp)
return mp->sb_next;
}
-/// Output any postponed text for msg_puts_attr_len().
-static void t_puts(int *t_col, const char *t_s, const char *s, int attr)
-{
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
- // Output postponed text.
- msg_didout = true; // Remember that line is not empty.
- grid_puts_len(&msg_grid_adj, (char *)t_s, (int)(s - t_s), msg_row, msg_col, attr);
- msg_col += *t_col;
- *t_col = 0;
- // If the string starts with a composing character don't increment the
- // column position for it.
- if (utf_iscomposing(utf_ptr2char(t_s))) {
- msg_col--;
- }
- if (msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
-}
-
/// @return true when messages should be printed to stdout/stderr:
/// - "batch mode" ("silent mode", -es/-Es)
/// - no UI and not embedded
@@ -2736,18 +2646,10 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
int cw = utf_char2cells(utf_ptr2char(s));
// primitive way to compute the current column
- if (cmdmsg_rl) {
- if (*s == '\r' || *s == '\n') {
- msg_col = Columns - 1;
- } else {
- msg_col -= cw;
- }
+ if (*s == '\r' || *s == '\n') {
+ msg_col = 0;
} else {
- if (*s == '\r' || *s == '\n') {
- msg_col = 0;
- } else {
- msg_col += cw;
- }
+ msg_col += cw;
}
s += len;
}
@@ -2767,11 +2669,9 @@ static int do_more_prompt(int typed_char)
int oldState = State;
int c;
int retval = false;
- int toscroll;
bool to_redraw = false;
msgchunk_T *mp_last = NULL;
msgchunk_T *mp;
- int i;
// If headless mode is enabled and no input is required, this variable
// will be true. However If server mode is enabled, the message "--more--"
@@ -2789,7 +2689,7 @@ static int do_more_prompt(int typed_char)
if (typed_char == 'G') {
// "g<": Find first line on the last page.
mp_last = msg_sb_start(last_msgchunk);
- for (i = 0; i < Rows - 2 && mp_last != NULL
+ for (int i = 0; i < Rows - 2 && mp_last != NULL
&& mp_last->sb_prev != NULL; i++) {
mp_last = msg_sb_start(mp_last->sb_prev);
}
@@ -2800,7 +2700,7 @@ static int do_more_prompt(int typed_char)
if (typed_char == NUL) {
msg_moremsg(false);
}
- for (;;) {
+ while (true) {
// Get a typed character directly from the user.
if (used_typed_char != NUL) {
c = used_typed_char; // was typed at hit-enter prompt
@@ -2809,7 +2709,7 @@ static int do_more_prompt(int typed_char)
c = get_keystroke(resize_events);
}
- toscroll = 0;
+ int toscroll = 0;
switch (c) {
case BS: // scroll one line back
case K_BS:
@@ -2907,13 +2807,13 @@ static int do_more_prompt(int typed_char)
}
// go to start of line at top of the screen
- for (i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) {
+ for (int i = 0; i < Rows - 2 && mp != NULL && mp->sb_prev != NULL; i++) {
mp = msg_sb_start(mp->sb_prev);
}
if (mp != NULL && (mp->sb_prev != NULL || to_redraw)) {
// Find line to be displayed at top
- for (i = 0; i > toscroll; i--) {
+ for (int i = 0; i > toscroll; i--) {
if (mp == NULL || mp->sb_prev == NULL) {
break;
}
@@ -2937,7 +2837,7 @@ static int do_more_prompt(int typed_char)
// event fragmentization, not unnecessary scroll events).
grid_fill(&msg_grid_adj, 0, Rows, 0, Columns, ' ', ' ',
HL_ATTR(HLF_MSG));
- for (i = 0; mp != NULL && i < Rows - 1; i++) {
+ for (int i = 0; mp != NULL && i < Rows - 1; i++) {
mp = disp_sb_line(i, mp);
msg_scrolled++;
}
@@ -2996,8 +2896,6 @@ static int do_more_prompt(int typed_char)
if (quit_more) {
msg_row = Rows - 1;
msg_col = 0;
- } else if (cmdmsg_rl) {
- msg_col = Columns - 1;
}
entered = false;
@@ -3038,37 +2936,17 @@ void os_msg(const char *str)
}
#endif // MSWIN
-/// Put a character on the screen at the current message position and advance
-/// to the next position. Only for printable ASCII!
-static void msg_screen_putchar(int c, int attr)
-{
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr);
- msg_didout = true; // remember that line is not empty
- grid_putchar(&msg_grid_adj, c, msg_row, msg_col, attr);
- if (cmdmsg_rl) {
- if (--msg_col == 0) {
- msg_col = Columns;
- msg_row++;
- }
- } else {
- if (++msg_col >= Columns) {
- msg_col = 0;
- msg_row++;
- }
- }
-}
-
void msg_moremsg(int full)
{
- int attr;
- char *s = _("-- More --");
-
- attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M));
- grid_puts(&msg_grid_adj, s, Rows - 1, 0, attr);
+ int attr = hl_combine_attr(HL_ATTR(HLF_MSG), HL_ATTR(HLF_M));
+ grid_line_start(&msg_grid_adj, Rows - 1);
+ int len = grid_line_puts(0, _("-- More --"), -1, attr);
if (full) {
- grid_puts(&msg_grid_adj, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
- Rows - 1, vim_strsize(s), attr);
+ len += grid_line_puts(len, _(" SPACE/d/j: screen/page/line down, b/u/k: up, q: quit "),
+ -1, attr);
}
+ grid_line_cursor_goto(len);
+ grid_line_flush();
}
/// Repeat the message for the current mode: MODE_ASKMORE, MODE_EXTERNCMD,
@@ -3115,12 +2993,15 @@ void msg_clr_eos_force(void)
return;
}
int msg_startcol = (cmdmsg_rl) ? 0 : msg_col;
- int msg_endcol = (cmdmsg_rl) ? msg_col + 1 : Columns;
+ int msg_endcol = (cmdmsg_rl) ? Columns - msg_col : Columns;
+ // TODO(bfredl): ugly, this state should already been validated at this
+ // point. But msg_clr_eos() is called in a lot of places.
if (msg_grid.chars && msg_row < msg_grid_pos) {
- // TODO(bfredl): ugly, this state should already been validated at this
- // point. But msg_clr_eos() is called in a lot of places.
- msg_row = msg_grid_pos;
+ msg_grid_validate();
+ if (msg_row < msg_grid_pos) {
+ msg_row = msg_grid_pos;
+ }
}
grid_fill(&msg_grid_adj, msg_row, msg_row + 1, msg_startcol, msg_endcol,
@@ -3129,7 +3010,7 @@ void msg_clr_eos_force(void)
' ', ' ', HL_ATTR(HLF_MSG));
redraw_cmdline = true; // overwritten the command line
- if (msg_row < Rows - 1 || msg_col == (cmdmsg_rl ? Columns : 0)) {
+ if (msg_row < Rows - 1 || msg_col == 0) {
clear_cmdline = false; // command line has been cleared
mode_displayed = false; // mode cleared or overwritten
}
@@ -3305,7 +3186,7 @@ static void redir_write(const char *const str, const ptrdiff_t maxlen)
write_reg_contents(redir_reg, s, (ssize_t)len, true);
}
if (redir_vname) {
- var_redir_str((char *)s, (int)maxlen);
+ var_redir_str(s, (int)maxlen);
}
// Write and adjust the current column.
@@ -3414,7 +3295,7 @@ int verbose_open(void)
/// Give a warning message (for searching).
/// Use 'w' highlighting and may repeat the message after redrawing
-void give_warning(char *message, bool hl)
+void give_warning(const char *message, bool hl)
FUNC_ATTR_NONNULL_ARG(1)
{
// Don't do this for ":silent".
@@ -3437,7 +3318,7 @@ void give_warning(char *message, bool hl)
msg_ext_set_kind("wmsg");
}
- if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) {
+ if (msg(message, keep_msg_attr) && msg_scrolled == 0) {
set_keep_msg(message, keep_msg_attr);
}
msg_didout = false; // Overwrite this message.
@@ -3447,9 +3328,22 @@ void give_warning(char *message, bool hl)
no_wait_return--;
}
-void give_warning2(char *const message, char *const a1, bool hl)
+/// Shows a warning, with optional highlighting.
+///
+/// @param hl enable highlighting
+/// @param fmt printf-style format message
+///
+/// @see smsg
+/// @see semsg
+void swmsg(bool hl, const char *const fmt, ...)
+ FUNC_ATTR_PRINTF(2, 3)
{
- vim_snprintf(IObuff, IOSIZE, message, a1);
+ va_list args;
+
+ va_start(args, fmt);
+ vim_vsnprintf(IObuff, IOSIZE, fmt, args);
+ va_end(args);
+
give_warning(IObuff, hl);
}
@@ -3471,14 +3365,8 @@ void msg_advance(int col)
if (col >= Columns) { // not enough room
col = Columns - 1;
}
- if (cmdmsg_rl) {
- while (msg_col > Columns - col) {
- msg_putchar(' ');
- }
- } else {
- while (msg_col < col) {
- msg_putchar(' ');
- }
+ while (msg_col < col) {
+ msg_putchar(' ');
}
}
@@ -3502,12 +3390,11 @@ void msg_advance(int col)
/// @param textfiel IObuff for inputdialog(), NULL otherwise
/// @param ex_cmd when true pressing : accepts default and starts Ex command
/// @returns 0 if cancelled, otherwise the nth button (1-indexed).
-int do_dialog(int type, char *title, char *message, char *buttons, int dfltbutton, char *textfield,
- int ex_cmd)
+int do_dialog(int type, const char *title, const char *message, const char *buttons, int dfltbutton,
+ const char *textfield, int ex_cmd)
{
int retval = 0;
char *hotkeys;
- int c;
int i;
if (silent_mode // No dialogs in silent mode ("ex -s")
@@ -3528,9 +3415,9 @@ int do_dialog(int type, char *title, char *message, char *buttons, int dfltbutto
no_wait_return++;
hotkeys = msg_show_console_dialog(message, buttons, dfltbutton);
- for (;;) {
+ while (true) {
// Get a typed character directly from the user.
- c = get_keystroke(NULL);
+ int c = get_keystroke(NULL);
switch (c) {
case CAR: // User accepts default option
case NL:
@@ -3611,7 +3498,7 @@ static int copy_char(const char *from, char *to, bool lowercase)
/// corresponding button has a hotkey
///
/// @return Pointer to memory allocated for storing hotkeys
-static char *console_dialog_alloc(const char *message, char *buttons, bool has_hotkey[])
+static char *console_dialog_alloc(const char *message, const char *buttons, bool has_hotkey[])
{
int lenhotkey = HOTK_LEN; // count first button
has_hotkey[0] = false;
@@ -3619,7 +3506,7 @@ static char *console_dialog_alloc(const char *message, char *buttons, bool has_h
// Compute the size of memory to allocate.
int len = 0;
int idx = 0;
- char *r = buttons;
+ const char *r = buttons;
while (*r) {
if (*r == DLG_BUTTON_SEP) {
len += 3; // '\n' -> ', '; 'x' -> '(x)'
@@ -3665,7 +3552,7 @@ static char *console_dialog_alloc(const char *message, char *buttons, bool has_h
/// The hotkeys can be multi-byte characters, but without combining chars.
///
/// @return an allocated string with hotkeys.
-static char *msg_show_console_dialog(char *message, char *buttons, int dfltbutton)
+static char *msg_show_console_dialog(const char *message, const char *buttons, int dfltbutton)
FUNC_ATTR_NONNULL_RET
{
bool has_hotkey[HAS_HOTKEY_LEN] = { false };
@@ -3685,7 +3572,7 @@ static char *msg_show_console_dialog(char *message, char *buttons, int dfltbutto
/// @param has_hotkey An element in this array is true if corresponding button
/// has a hotkey
/// @param[out] hotkeys_ptr Pointer to the memory location where hotkeys will be copied
-static void copy_hotkeys_and_msg(const char *message, char *buttons, int default_button_idx,
+static void copy_hotkeys_and_msg(const char *message, const char *buttons, int default_button_idx,
const bool has_hotkey[], char *hotkeys_ptr)
{
*confirm_msg = '\n';
@@ -3708,7 +3595,7 @@ static void copy_hotkeys_and_msg(const char *message, char *buttons, int default
}
int idx = 0;
- char *r = buttons;
+ const char *r = buttons;
while (*r) {
if (*r == DLG_BUTTON_SEP) {
*msgp++ = ',';
@@ -3812,3 +3699,21 @@ int vim_dialog_yesnoallcancel(int type, char *title, char *message, int dflt)
}
return VIM_CANCEL;
}
+
+/// Check if there should be a delay to allow the user to see a message.
+///
+/// Used before clearing or redrawing the screen or the command line.
+void msg_check_for_delay(bool check_msg_scroll)
+{
+ if ((emsg_on_display || (check_msg_scroll && msg_scroll))
+ && !did_wait_return
+ && emsg_silent == 0
+ && !in_assert_fails) {
+ ui_flush();
+ os_delay(1006, true);
+ emsg_on_display = false;
+ if (check_msg_scroll) {
+ msg_scroll = false;
+ }
+ }
+}
diff --git a/src/nvim/message.h b/src/nvim/message.h
index 191d3b8da7..adbb40277b 100644
--- a/src/nvim/message.h
+++ b/src/nvim/message.h
@@ -1,30 +1,37 @@
-#ifndef NVIM_MESSAGE_H
-#define NVIM_MESSAGE_H
+#pragma once
-#include <stdarg.h>
+#include <errno.h>
#include <stdbool.h>
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <stdio.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
-
-// Types of dialogs passed to do_dialog().
-#define VIM_GENERIC 0
-#define VIM_ERROR 1
-#define VIM_WARNING 2
-#define VIM_INFO 3
-#define VIM_QUESTION 4
-#define VIM_LAST_TYPE 4 // sentinel value
-
-// Return values for functions like vim_dialogyesno()
-#define VIM_YES 2
-#define VIM_NO 3
-#define VIM_CANCEL 4
-#define VIM_ALL 5
-#define VIM_DISCARDALL 6
+#include "nvim/macros_defs.h"
+
+/// Types of dialogs passed to do_dialog().
+enum {
+ VIM_GENERIC = 0,
+ VIM_ERROR = 1,
+ VIM_WARNING = 2,
+ VIM_INFO = 3,
+ VIM_QUESTION = 4,
+ VIM_LAST_TYPE = 4, ///< sentinel value
+};
+
+/// Return values for functions like vim_dialogyesno()
+enum {
+ VIM_YES = 2,
+ VIM_NO = 3,
+ VIM_CANCEL = 4,
+ VIM_ALL = 5,
+ VIM_DISCARDALL = 6,
+};
+
+/// special attribute addition: Put message in history
+enum { MSG_HIST = 0x1000, };
typedef struct {
String text;
@@ -36,8 +43,8 @@ typedef kvec_t(HlMessageChunk) HlMessage;
/// Message history for `:messages`
typedef struct msg_hist {
struct msg_hist *next; ///< Next message.
- char *msg; ///< Message text.
- const char *kind; ///< Message kind (for msg_ext)
+ char *msg; ///< Message text.
+ const char *kind; ///< Message kind (for msg_ext)
int attr; ///< Message highlighting.
bool multiline; ///< Multiline message.
HlMessage multiattr; ///< multiattr message.
@@ -48,27 +55,39 @@ extern MessageHistoryEntry *first_msg_hist;
/// Last message
extern MessageHistoryEntry *last_msg_hist;
-EXTERN bool msg_ext_need_clear INIT(= false);
+EXTERN bool msg_ext_need_clear INIT( = false);
+
+/// allocated grid for messages. Used when display+=msgsep is set, or
+/// ext_multigrid is active. See also the description at msg_scroll_flush()
+EXTERN ScreenGrid msg_grid INIT( = SCREEN_GRID_INIT);
+EXTERN int msg_grid_pos INIT( = 0);
-// allocated grid for messages. Used when display+=msgsep is set, or
-// ext_multigrid is active. See also the description at msg_scroll_flush()
-EXTERN ScreenGrid msg_grid INIT(= SCREEN_GRID_INIT);
-EXTERN int msg_grid_pos INIT(= 0);
+/// "adjusted" message grid. This grid accepts positions relative to
+/// default_grid. Internally it will be translated to a position on msg_grid
+/// relative to the start of the message area, or directly mapped to default_grid
+/// for legacy (display-=msgsep) message scroll behavior.
+/// TODO(bfredl): refactor "internal" message logic, msg_row etc
+/// to use the correct positions already.
+EXTERN ScreenGrid msg_grid_adj INIT( = SCREEN_GRID_INIT);
-// "adjusted" message grid. This grid accepts positions relative to
-// default_grid. Internally it will be translated to a position on msg_grid
-// relative to the start of the message area, or directly mapped to default_grid
-// for legacy (display-=msgsep) message scroll behavior.
-// // TODO(bfredl): refactor "internal" message logic, msg_row etc
-// to use the correct positions already.
-EXTERN ScreenGrid msg_grid_adj INIT(= SCREEN_GRID_INIT);
+/// value of msg_scrolled at latest msg_scroll_flush.
+EXTERN int msg_scrolled_at_flush INIT( = 0);
-// value of msg_scrolled at latest msg_scroll_flush.
-EXTERN int msg_scrolled_at_flush INIT(= 0);
+EXTERN int msg_grid_scroll_discount INIT( = 0);
-EXTERN int msg_grid_scroll_discount INIT(= 0);
+EXTERN int msg_listdo_overwrite INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "message.h.generated.h"
#endif
-#endif // NVIM_MESSAGE_H
+
+// Prefer using semsg(), because perror() may send the output to the wrong
+// destination and mess up the screen.
+#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno))
+
+#ifndef MSWIN
+/// Headless (no UI) error message handler.
+# define os_errmsg(str) fprintf(stderr, "%s", (str))
+/// Headless (no UI) message handler.
+# define os_msg(str) printf("%s", (str))
+#endif
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index 950b025e53..8fe3864424 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -1,31 +1,30 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
+#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
@@ -33,18 +32,18 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/popupmenu.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -86,15 +85,11 @@ static int get_mouse_class(char *p)
/// Move "pos" back to the start of the word it's in.
static void find_start_of_word(pos_T *pos)
{
- char *line;
- int cclass;
- int col;
-
- line = ml_get(pos->lnum);
- cclass = get_mouse_class(line + pos->col);
+ char *line = ml_get(pos->lnum);
+ int cclass = get_mouse_class(line + pos->col);
while (pos->col > 0) {
- col = pos->col - 1;
+ int col = pos->col - 1;
col -= utf_head_off(line, line + col);
if (get_mouse_class(line + col) != cclass) {
break;
@@ -107,18 +102,14 @@ static void find_start_of_word(pos_T *pos)
/// When 'selection' is "exclusive", the position is just after the word.
static void find_end_of_word(pos_T *pos)
{
- char *line;
- int cclass;
- int col;
-
- line = ml_get(pos->lnum);
+ char *line = ml_get(pos->lnum);
if (*p_sel == 'e' && pos->col > 0) {
pos->col--;
pos->col -= utf_head_off(line, line + pos->col);
}
- cclass = get_mouse_class(line + pos->col);
+ int cclass = get_mouse_class(line + pos->col);
while (line[pos->col] != NUL) {
- col = pos->col + utfc_ptr2len(line + pos->col);
+ int col = pos->col + utfc_ptr2len(line + pos->col);
if (get_mouse_class(line + col) != cclass) {
if (*p_sel == 'e') {
pos->col = col;
@@ -143,6 +134,8 @@ static void move_tab_to_mouse(void)
}
}
+static bool got_click = false; // got a click some time back
+
/// Call click definition function for column "col" in the "click_defs" array for button
/// "which_button".
static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button)
@@ -198,6 +191,8 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi
typval_T rettv;
(void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv);
tv_clear(&rettv);
+ // Make sure next click does not register as drag when callback absorbs the release event.
+ got_click = false;
}
/// Translate window coordinates to buffer position without any side effects.
@@ -218,27 +213,37 @@ static int get_fpos_of_mouse(pos_T *mpos)
if (wp == NULL) {
return IN_UNKNOWN;
}
+ int winrow = row;
+ int wincol = col;
+
+ // compute the position in the buffer line from the posn on the screen
+ bool below_buffer = mouse_comp_pos(wp, &row, &col, &mpos->lnum);
+
+ if (!below_buffer && *wp->w_p_stc != NUL
+ && (wp->w_p_rl
+ ? wincol >= wp->w_width_inner - win_col_off(wp)
+ : wincol < win_col_off(wp))) {
+ return MOUSE_STATUSCOL;
+ }
// winpos and height may change in win_enter()!
- if (row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line
+ if (winrow >= wp->w_height_inner) { // In (or below) status line
return IN_STATUS_LINE;
}
- if (col >= wp->w_width) { // In vertical separator line
- return IN_SEP_LINE;
- }
- if (wp != curwin) {
- return IN_UNKNOWN;
+ if (winrow == -1 && wp->w_winbar_height != 0) {
+ return MOUSE_WINBAR;
}
- // compute the position in the buffer line from the posn on the screen
- if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) {
- return IN_STATUS_LINE; // past bottom
+ if (wincol >= wp->w_width_inner) { // In vertical separator line
+ return IN_SEP_LINE;
}
- mpos->col = vcol2col(wp, mpos->lnum, col);
+ if (wp != curwin || below_buffer) {
+ return IN_UNKNOWN;
+ }
- mpos->coladd = 0;
+ mpos->col = vcol2col(wp, mpos->lnum, col, &mpos->coladd);
return IN_BUFFER;
}
@@ -281,10 +286,8 @@ static int get_fpos_of_mouse(pos_T *mpos)
/// @param fixindent PUT_FIXINDENT if fixing indent necessary
///
/// @return true if start_arrow() should be called for edit mode.
-bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
+bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent)
{
- static bool got_click = false; // got a click some time back
-
int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT
bool is_click; // If false it's a drag or release event
bool is_drag; // If true it's a drag event
@@ -296,20 +299,18 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
bool in_status_line; // mouse in status line
static bool in_tab_line = false; // mouse clicked in tab line
bool in_sep_line; // mouse in vertical separator line
- int c1, c2;
- pos_T save_cursor;
+ int c1;
win_T *old_curwin = curwin;
static pos_T orig_cursor;
colnr_T leftcol, rightcol;
pos_T end_visual;
- long diff;
int old_active = VIsual_active;
int old_mode = VIsual_mode;
int regname;
- save_cursor = curwin->w_cursor;
+ pos_T save_cursor = curwin->w_cursor;
- for (;;) {
+ while (true) {
which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag);
if (is_drag) {
// If the next character is the same mouse event then use that
@@ -367,7 +368,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
stuffnumReadbuff(count);
}
stuffcharReadbuff(Ctrl_T);
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
return false;
}
@@ -449,7 +450,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) {
insert_reg(regname, true);
} else {
- do_put(regname, NULL, BACKWARD, 1L,
+ do_put(regname, NULL, BACKWARD, 1,
(fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND);
// Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r
@@ -538,6 +539,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// shift-left button -> right button
// alt-left button -> alt-right button
if (mouse_model_popup()) {
+ pos_T m_pos;
+ int m_pos_flag = get_fpos_of_mouse(&m_pos);
+ if (m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) {
+ goto popupexit;
+ }
if (which_button == MOUSE_RIGHT
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) {
if (!is_click) {
@@ -547,17 +553,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
}
jump_flags = 0;
if (strcmp(p_mousem, "popup_setpos") == 0) {
- // First set the cursor position before showing the popup
- // menu.
+ // First set the cursor position before showing the popup menu.
if (VIsual_active) {
- pos_T m_pos;
- // set MOUSE_MAY_STOP_VIS if we are outside the
- // selection or the current window (might have false
- // negative here)
- if (mouse_row < curwin->w_winrow
- || mouse_row > (curwin->w_winrow + curwin->w_height)) {
- jump_flags = MOUSE_MAY_STOP_VIS;
- } else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER) {
+ // set MOUSE_MAY_STOP_VIS if we are outside the selection
+ // or the current window (might have false negative here)
+ if (m_pos_flag != IN_BUFFER) {
jump_flags = MOUSE_MAY_STOP_VIS;
} else {
if (VIsual_mode == 'V') {
@@ -601,6 +601,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
mod_mask &= ~MOD_MASK_SHIFT;
}
}
+popupexit:
if ((State & (MODE_NORMAL | MODE_INSERT))
&& !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) {
@@ -652,7 +653,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
in_sep_line = (jump_flags & IN_SEP_LINE);
if ((in_winbar || in_status_line || in_statuscol) && is_click) {
- // Handle click event on window bar or status lin
+ // Handle click event on window bar, status line or status column
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
@@ -672,6 +673,10 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
click_col = mouse_col;
}
+ if (in_statuscol && wp->w_p_rl) {
+ click_col = wp->w_width_inner - click_col - 1;
+ }
+
if (click_defs != NULL) {
switch (click_defs[click_col].type) {
case kStlClickDisabled:
@@ -680,7 +685,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
call_click_def_func(click_defs, click_col, which_button);
break;
default:
- assert(false && "winbar and statusline only support %@ for clicks");
+ assert(false && "winbar, statusline and statuscolumn only support %@ for clicks");
break;
}
}
@@ -703,9 +708,9 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
&& which_button == MOUSE_LEFT) {
// open or close a fold at this line
if (jump_flags & MOUSE_FOLD_OPEN) {
- openFold(curwin->w_cursor, 1L);
+ openFold(curwin->w_cursor, 1);
} else {
- closeFold(curwin->w_cursor, 1L);
+ closeFold(curwin->w_cursor, 1);
}
// don't move the cursor if still in the same window
if (curwin == old_curwin) {
@@ -726,11 +731,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// When dragging the mouse above the window, scroll down.
if (is_drag && mouse_row < 0 && !in_status_line) {
- scroll_redraw(false, 1L);
+ scroll_redraw(false, 1);
mouse_row = 0;
}
if (start_visual.lnum) { // right click in visual mode
+ linenr_T diff;
// When ALT is pressed make Visual mode blockwise.
if (mod_mask & MOD_MASK_ALT) {
VIsual_mode = Ctrl_V;
@@ -800,6 +806,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// Middle mouse click: Put text before cursor.
if (which_button == MOUSE_MIDDLE) {
+ int c2;
if (regname == 0 && eval_has_provider("clipboard")) {
regname = '*';
}
@@ -835,7 +842,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
} else { // location list window
do_cmdline_cmd(".ll");
}
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
} else if ((mod_mask & MOD_MASK_CTRL)
|| (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) {
// Ctrl-Mouse click (or double click in a help window) jumps to the tag
@@ -844,7 +851,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
stuffcharReadbuff(Ctrl_O);
}
stuffcharReadbuff(Ctrl_RSB);
- got_click = false; // ignore drag&release now
+ got_click = false; // ignore drag&release now
} else if ((mod_mask & MOD_MASK_SHIFT)) {
// Shift-Mouse click searches for the next occurrence of the word under
// the mouse pointer
@@ -888,13 +895,13 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
// A double click selects a word or a block.
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) {
pos_T *pos = NULL;
- int gc;
if (is_click) {
// If the character under the cursor (skipping white space) is
// not a word character, try finding a match and select a (),
// {}, [], #if/#endif, etc. block.
end_visual = curwin->w_cursor;
+ int gc;
while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) {
inc(&end_visual);
}
@@ -956,6 +963,146 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
return moved;
}
+void ins_mouse(int c)
+{
+ win_T *old_curwin = curwin;
+
+ undisplay_dollar();
+ pos_T tpos = curwin->w_cursor;
+ if (do_mouse(NULL, c, BACKWARD, 1, 0)) {
+ win_T *new_curwin = curwin;
+
+ if (curwin != old_curwin && win_valid(old_curwin)) {
+ // Mouse took us to another window. We need to go back to the
+ // previous one to stop insert there properly.
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+ if (bt_prompt(curbuf)) {
+ // Restart Insert mode when re-entering the prompt buffer.
+ curbuf->b_prompt_insert = 'A';
+ }
+ }
+ start_arrow(curwin == old_curwin ? &tpos : NULL);
+ if (curwin != new_curwin && win_valid(new_curwin)) {
+ curwin = new_curwin;
+ curbuf = curwin->w_buffer;
+ }
+ set_can_cindent(true);
+ }
+
+ // redraw status lines (in case another window became active)
+ redraw_statuslines();
+}
+
+/// Common mouse wheel scrolling, shared between Insert mode and NV modes.
+/// Default action is to scroll mouse_vert_step lines (or mouse_hor_step columns
+/// depending on the scroll direction) or one page when Shift or Ctrl is used.
+/// Direction is indicated by "cap->arg":
+/// K_MOUSEUP - MSCR_UP
+/// K_MOUSEDOWN - MSCR_DOWN
+/// K_MOUSELEFT - MSCR_LEFT
+/// K_MOUSERIGHT - MSCR_RIGHT
+/// "curwin" may have been changed to the window that should be scrolled and
+/// differ from the window that actually has focus.
+void do_mousescroll(cmdarg_T *cap)
+{
+ bool shift_or_ctrl = mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL);
+
+ if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) {
+ // Vertical scrolling
+ if ((State & MODE_NORMAL) && shift_or_ctrl) {
+ // whole page up or down
+ (void)onepage(cap->arg ? FORWARD : BACKWARD, 1);
+ } else {
+ if (shift_or_ctrl) {
+ // whole page up or down
+ cap->count1 = curwin->w_botline - curwin->w_topline;
+ } else {
+ cap->count1 = (int)p_mousescroll_vert;
+ }
+ if (cap->count1 > 0) {
+ cap->count0 = cap->count1;
+ nv_scroll_line(cap);
+ }
+ }
+ } else {
+ // Horizontal scrolling
+ int step = shift_or_ctrl ? curwin->w_width_inner : (int)p_mousescroll_hor;
+ colnr_T leftcol = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step);
+ if (leftcol < 0) {
+ leftcol = 0;
+ }
+ (void)do_mousescroll_horiz(leftcol);
+ }
+}
+
+/// Implementation for scrolling in Insert mode in direction "dir", which is one
+/// of the MSCR_ values.
+void ins_mousescroll(int dir)
+{
+ cmdarg_T cap;
+ oparg_T oa;
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+ cap.arg = dir;
+
+ switch (dir) {
+ case MSCR_UP:
+ cap.cmdchar = K_MOUSEUP;
+ break;
+ case MSCR_DOWN:
+ cap.cmdchar = K_MOUSEDOWN;
+ break;
+ case MSCR_LEFT:
+ cap.cmdchar = K_MOUSELEFT;
+ break;
+ case MSCR_RIGHT:
+ cap.cmdchar = K_MOUSERIGHT;
+ break;
+ default:
+ siemsg("Invalid ins_mousescroll() argument: %d", dir);
+ }
+
+ win_T *old_curwin = curwin;
+ if (mouse_row >= 0 && mouse_col >= 0) {
+ // Find the window at the mouse pointer coordinates.
+ // NOTE: Must restore "curwin" to "old_curwin" before returning!
+ int grid = mouse_grid;
+ int row = mouse_row;
+ int col = mouse_col;
+ curwin = mouse_find_win(&grid, &row, &col);
+ if (curwin == NULL) {
+ curwin = old_curwin;
+ return;
+ }
+ curbuf = curwin->w_buffer;
+ }
+
+ if (curwin == old_curwin) {
+ // Don't scroll the current window if the popup menu is visible.
+ if (pum_visible()) {
+ return;
+ }
+
+ undisplay_dollar();
+ }
+
+ pos_T orig_cursor = curwin->w_cursor;
+
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(&cap);
+
+ curwin->w_redr_status = true;
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+
+ if (!equalpos(curwin->w_cursor, orig_cursor)) {
+ start_arrow(&orig_cursor);
+ set_can_cindent(true);
+ }
+}
+
/// Return true if "c" is a mouse key.
bool is_mouse_key(int c)
{
@@ -1036,8 +1183,6 @@ int jump_to_mouse(int flags, bool *inclusive, int which_button)
static int prev_col = -1;
static int did_drag = false; // drag was noticed
- win_T *wp, *old_curwin;
- pos_T old_cursor;
int count;
bool first;
int row = mouse_row;
@@ -1090,34 +1235,28 @@ retnomove:
if (flags & MOUSE_SETPOS) {
goto retnomove; // ugly goto...
}
- old_curwin = curwin;
- old_cursor = curwin->w_cursor;
+ win_T *old_curwin = curwin;
+ pos_T old_cursor = curwin->w_cursor;
if (row < 0 || col < 0) { // check if it makes sense
return IN_UNKNOWN;
}
// find the window where the row is in
- wp = mouse_find_win(&grid, &row, &col);
+ win_T *wp = mouse_find_win(&grid, &row, &col);
if (wp == NULL) {
return IN_UNKNOWN;
}
- on_status_line = (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height)
- ? row + wp->w_winbar_height - wp->w_height + 1 == 1
- : false;
-
- on_winbar = (row == -1)
- ? wp->w_winbar_height != 0
- : false;
-
- on_statuscol = !on_status_line && !on_winbar && col < win_col_off(wp)
- ? *wp->w_p_stc != NUL
- : false;
-
- on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width
- ? col - wp->w_width + 1 == 1
- : false;
+ bool below_window = grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height;
+ on_status_line = below_window && row + wp->w_winbar_height - wp->w_height + 1 == 1;
+ on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width && col - wp->w_width + 1 == 1;
+ on_winbar = row == -1 && wp->w_winbar_height != 0;
+ on_statuscol = !below_window && !on_status_line && !on_sep_line && !on_winbar
+ && *wp->w_p_stc != NUL
+ && (wp->w_p_rl
+ ? col >= wp->w_width_inner - win_col_off(wp)
+ : col < win_col_off(wp));
// The rightmost character of the status line might be a vertical
// separator character if there is no connecting window to the right.
@@ -1150,7 +1289,7 @@ retnomove:
dragwin = NULL;
// winpos and height may change in win_enter()!
- if (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height) {
+ if (below_window) {
// In (or below) status line
status_line_offset = row + wp->w_winbar_height - wp->w_height + 1;
dragwin = wp;
@@ -1306,14 +1445,13 @@ retnomove:
}
first = false;
- if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
- && curwin->w_topline == curbuf->b_ml.ml_line_count) {
- break;
- }
-
if (curwin->w_topfill > 0) {
curwin->w_topfill--;
} else {
+ if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
+ && curwin->w_topline == curbuf->b_ml.ml_line_count) {
+ break;
+ }
curwin->w_topline++;
curwin->w_topfill = win_get_fill(curwin, curwin->w_topline);
}
@@ -1336,15 +1474,15 @@ retnomove:
}
}
+ colnr_T col_from_screen = -1;
+ int mouse_fold_flags = 0;
+ mouse_check_grid(&col_from_screen, &mouse_fold_flags);
+
// compute the position in the buffer line from the posn on the screen
if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) {
mouse_past_bottom = true;
}
- if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) {
- col = mouse_adjust_click(curwin, row, col);
- }
-
// Start Visual mode before coladvance(), for when 'sel' != "old"
if ((flags & MOUSE_MAY_VIS) && !VIsual_active) {
VIsual = old_cursor;
@@ -1359,6 +1497,10 @@ retnomove:
}
}
+ if (col_from_screen >= 0) {
+ col = col_from_screen;
+ }
+
curwin->w_curswant = col;
curwin->w_set_curswant = false; // May still have been true
if (coladvance(col) == FAIL) { // Mouse click beyond end of line
@@ -1376,41 +1518,110 @@ retnomove:
count |= CURSOR_MOVED; // Cursor has moved
}
- count |= mouse_check_fold();
+ count |= mouse_fold_flags;
return count;
}
-// Compute the position in the buffer line from the posn on the screen in
-// window "win".
-// Returns true if the position is below the last line.
+/// Make a horizontal scroll to "leftcol".
+/// @return true if the cursor moved, false otherwise.
+static bool do_mousescroll_horiz(colnr_T leftcol)
+{
+ if (curwin->w_p_wrap) {
+ return false; // no horizontal scrolling when wrapping
+ }
+ if (curwin->w_leftcol == leftcol) {
+ return false; // already there
+ }
+
+ // When the line of the cursor is too short, move the cursor to the
+ // longest visible line.
+ if (!virtual_active()
+ && leftcol > scroll_line_len(curwin->w_cursor.lnum)) {
+ curwin->w_cursor.lnum = find_longest_lnum();
+ curwin->w_cursor.col = 0;
+ }
+
+ return set_leftcol(leftcol);
+}
+
+/// Normal and Visual modes implementation for scrolling in direction
+/// "cap->arg", which is one of the MSCR_ values.
+void nv_mousescroll(cmdarg_T *cap)
+{
+ win_T *const old_curwin = curwin;
+
+ if (mouse_row >= 0 && mouse_col >= 0) {
+ // Find the window at the mouse pointer coordinates.
+ // NOTE: Must restore "curwin" to "old_curwin" before returning!
+ int grid = mouse_grid;
+ int row = mouse_row;
+ int col = mouse_col;
+ curwin = mouse_find_win(&grid, &row, &col);
+ if (curwin == NULL) {
+ curwin = old_curwin;
+ return;
+ }
+ curbuf = curwin->w_buffer;
+ }
+
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(cap);
+
+ if (curwin != old_curwin && curwin->w_p_cul) {
+ redraw_for_cursorline(curwin);
+ }
+ curwin->w_redr_status = true;
+ curwin = old_curwin;
+ curbuf = curwin->w_buffer;
+}
+
+/// Mouse clicks and drags.
+void nv_mouse(cmdarg_T *cap)
+{
+ (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0);
+}
+
+/// Compute the position in the buffer line from the posn on the screen in
+/// window "win".
+/// Returns true if the position is below the last line.
bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
{
int col = *colp;
int row = *rowp;
- linenr_T lnum;
bool retval = false;
- int off;
int count;
if (win->w_p_rl) {
col = win->w_width_inner - 1 - col;
}
- lnum = win->w_topline;
+ linenr_T lnum = win->w_topline;
while (row > 0) {
// Don't include filler lines in "count"
- if (win_may_fill(win)
- && !hasFoldingWin(win, lnum, NULL, NULL, true, NULL)) {
+ if (win_may_fill(win)) {
if (lnum == win->w_topline) {
row -= win->w_topfill;
} else {
row -= win_get_fill(win, lnum);
}
- count = plines_win_nofill(win, lnum, true);
+ count = plines_win_nofill(win, lnum, false);
} else {
- count = plines_win(win, lnum, true);
+ count = plines_win(win, lnum, false);
+ }
+
+ if (win->w_skipcol > 0 && lnum == win->w_topline) {
+ // Adjust for 'smoothscroll' clipping the top screen lines.
+ // A similar formula is used in curs_columns().
+ int width1 = win->w_width_inner - win_col_off(win);
+ int skip_lines = 0;
+ if (win->w_skipcol > width1) {
+ skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1;
+ } else if (win->w_skipcol > 0) {
+ skip_lines = 1;
+ }
+ count -= skip_lines;
}
if (count > row) {
@@ -1429,13 +1640,16 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
if (!retval) {
// Compute the column without wrapping.
- off = win_col_off(win) - win_col_off2(win);
+ int off = win_col_off(win) - win_col_off2(win);
if (col < off) {
col = off;
}
col += row * (win->w_width_inner - off);
- // add skip column (for long wrapping line)
- col += win->w_skipcol;
+
+ // Add skip column for the topline.
+ if (lnum == win->w_topline) {
+ col += win->w_skipcol;
+ }
}
if (!win->w_p_wrap) {
@@ -1467,11 +1681,9 @@ win_T *mouse_find_win(int *gridp, int *rowp, int *colp)
return NULL;
}
- frame_T *fp;
-
- fp = topframe;
+ frame_T *fp = topframe;
*rowp -= firstwin->w_winrow;
- for (;;) {
+ while (true) {
if (fp->fr_layout == FR_LEAF) {
break;
}
@@ -1535,20 +1747,27 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp)
}
/// Convert a virtual (screen) column to a character column.
-/// The first column is one.
-colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+/// The first column is zero.
+colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
{
// try to advance to the specified column
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
+ int size = win_lbr_chartabsize(&cts, NULL);
+ if (cts.cts_vcol + size > vcol) {
+ break;
+ }
+ cts.cts_vcol += size;
MB_PTR_ADV(cts.cts_ptr);
}
clear_chartabsize_arg(&cts);
+ if (coladdp != NULL) {
+ *coladdp = vcol - cts.cts_vcol;
+ }
return (colnr_T)(cts.cts_ptr - line);
}
@@ -1569,15 +1788,13 @@ static void set_mouse_topline(win_T *wp)
orig_topfill = wp->w_topfill;
}
-///
/// Return length of line "lnum" for horizontal scrolling.
-///
static colnr_T scroll_line_len(linenr_T lnum)
{
colnr_T col = 0;
char *line = ml_get(lnum);
if (*line != NUL) {
- for (;;) {
+ while (true) {
int numchar = win_chartabsize(curwin, line, col);
MB_PTR_ADV(line);
if (*line == NUL) { // don't count the last character
@@ -1589,9 +1806,7 @@ static colnr_T scroll_line_len(linenr_T lnum)
return col;
}
-///
/// Find longest visible line number.
-///
static linenr_T find_longest_lnum(void)
{
linenr_T ret = 0;
@@ -1602,17 +1817,17 @@ static linenr_T find_longest_lnum(void)
if (curwin->w_topline <= curwin->w_cursor.lnum
&& curwin->w_botline > curwin->w_cursor.lnum
&& curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) {
- long max = 0;
+ colnr_T max = 0;
// Use maximum of all visible lines. Remember the lnum of the
// longest line, closest to the cursor line. Used when scrolling
// below.
for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) {
colnr_T len = scroll_line_len(lnum);
- if (len > (colnr_T)max) {
+ if (len > max) {
max = len;
ret = lnum;
- } else if (len == (colnr_T)max
+ } else if (len == max
&& abs(lnum - curwin->w_cursor.lnum)
< abs(ret - curwin->w_cursor.lnum)) {
ret = lnum;
@@ -1626,194 +1841,116 @@ static linenr_T find_longest_lnum(void)
return ret;
}
-/// Do a horizontal scroll.
-/// @return true if the cursor moved, false otherwise.
-bool mouse_scroll_horiz(int dir)
-{
- if (curwin->w_p_wrap) {
- return false;
- }
-
- int step = (int)p_mousescroll_hor;
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- step = curwin->w_width_inner;
- }
-
- int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step);
- if (leftcol < 0) {
- leftcol = 0;
- }
-
- if (curwin->w_leftcol == leftcol) {
- return false;
- }
-
- curwin->w_leftcol = (colnr_T)leftcol;
-
- // When the line of the cursor is too short, move the cursor to the
- // longest visible line.
- if (!virtual_active()
- && (colnr_T)leftcol > scroll_line_len(curwin->w_cursor.lnum)) {
- curwin->w_cursor.lnum = find_longest_lnum();
- curwin->w_cursor.col = 0;
- }
-
- return leftcol_changed();
-}
-
-/// Adjusts the clicked column position when 'conceallevel' > 0
-static int mouse_adjust_click(win_T *wp, int row, int col)
+/// Check clicked cell on its grid
+static void mouse_check_grid(colnr_T *vcolp, int *flagsp)
+ FUNC_ATTR_NONNULL_ALL
{
- if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0
- && wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) {
- return col;
- }
-
- // `col` is the position within the current line that is highlighted by the
- // cursor without consideration for concealed characters. The current line is
- // scanned *up to* `col`, nudging it left or right when concealed characters
- // are encountered.
- //
- // win_chartabsize() is used to keep track of the virtual column position
- // relative to the line's bytes. For example: if col == 9 and the line
- // starts with a tab that's 8 columns wide, we would want the cursor to be
- // highlighting the second byte, not the ninth.
-
- linenr_T lnum = wp->w_cursor.lnum;
- char *line = ml_get(lnum);
- char *ptr = line;
- char *ptr_end;
- char *ptr_row_offset = line; // Where we begin adjusting `ptr_end`
-
- // Find the offset where scanning should begin.
- int offset = wp->w_leftcol;
- if (row > 0) {
- offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) -
- wp->w_leftcol + wp->w_skipcol);
- }
-
- int vcol;
-
- if (offset) {
- // Skip everything up to an offset since nvim takes care of displaying the
- // correct portion of the line when horizontally scrolling.
- // When 'wrap' is enabled, only the row (of the wrapped line) needs to be
- // checked for concealed characters.
- vcol = 0;
- while (vcol < offset && *ptr != NUL) {
- vcol += win_chartabsize(curwin, ptr, vcol);
- ptr += utfc_ptr2len(ptr);
- }
-
- ptr_row_offset = ptr;
- }
-
- // Align `ptr_end` with `col`
- vcol = offset;
- ptr_end = ptr_row_offset;
- while (vcol < col && *ptr_end != NUL) {
- vcol += win_chartabsize(curwin, ptr_end, vcol);
- ptr_end += utfc_ptr2len(ptr_end);
- }
-
- int matchid;
- int prev_matchid;
- int nudge = 0;
- int cwidth = 0;
-
- vcol = offset;
-
-#define INCR() nudge++; ptr_end += utfc_ptr2len((char *)ptr_end)
-#define DECR() nudge--; ptr_end -= utfc_ptr2len((char *)ptr_end)
-
- while (ptr < ptr_end && *ptr != NUL) {
- cwidth = win_chartabsize(curwin, ptr, vcol);
- vcol += cwidth;
- if (cwidth > 1 && *ptr == '\t' && nudge > 0) {
- // A tab will "absorb" any previous adjustments.
- cwidth = MIN(cwidth, nudge);
- while (cwidth > 0) {
- DECR();
- cwidth--;
- }
- }
-
- matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
- if (matchid != 0) {
- if (wp->w_p_cole == 3) {
- INCR();
- } else {
- if (!(row > 0 && ptr == ptr_row_offset)
- && (wp->w_p_cole == 1 || (wp->w_p_cole == 2
- && (wp->w_p_lcs_chars.conceal != NUL
- || syn_get_sub_char() != NUL)))) {
- // At least one placeholder character will be displayed.
- DECR();
- }
-
- prev_matchid = matchid;
+ int click_grid = mouse_grid;
+ int click_row = mouse_row;
+ int click_col = mouse_col;
- while (prev_matchid == matchid && *ptr != NUL) {
- INCR();
- ptr += utfc_ptr2len(ptr);
- matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
+ // XXX: this doesn't change click_grid if it is 1, even with multigrid
+ win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col);
+ // Only use vcols[] after the window was redrawn. Mainly matters
+ // for tests, a user would not click before redrawing.
+ if (wp == NULL || wp->w_redr_type != 0) {
+ return;
+ }
+ ScreenGrid *gp = &wp->w_grid;
+ int start_row = 0;
+ int start_col = 0;
+ grid_adjust(&gp, &start_row, &start_col);
+ if (gp->handle != click_grid || gp->chars == NULL) {
+ return;
+ }
+ click_row += start_row;
+ click_col += start_col;
+ if (click_row < 0 || click_row >= gp->rows
+ || click_col < 0 || click_col >= gp->cols) {
+ return;
+ }
+
+ const size_t off = gp->line_offset[click_row] + (size_t)click_col;
+ colnr_T col_from_screen = gp->vcols[off];
+
+ if (col_from_screen == MAXCOL) {
+ // When clicking after end of line, still need to set correct curswant
+ size_t off_l = gp->line_offset[click_row] + (size_t)start_col;
+ if (gp->vcols[off_l] < MAXCOL) {
+ // Binary search to find last char in line
+ size_t off_r = off;
+ while (off_l < off_r) {
+ size_t off_m = (off_l + off_r + 1) / 2;
+ if (gp->vcols[off_m] < MAXCOL) {
+ off_l = off_m;
+ } else {
+ off_r = off_m - 1;
}
-
- continue;
}
+ colnr_T eol_vcol = gp->vcols[off_r];
+ assert(eol_vcol < MAXCOL);
+ if (eol_vcol < 0) {
+ // Empty line or whole line before w_leftcol,
+ // with columns before buffer text
+ eol_vcol = wp->w_leftcol - 1;
+ }
+ *vcolp = eol_vcol + (int)(off - off_r);
+ } else {
+ // Empty line or whole line before w_leftcol
+ *vcolp = click_col - start_col + wp->w_leftcol;
}
-
- ptr += utfc_ptr2len(ptr);
+ } else if (col_from_screen >= 0) {
+ // Use the virtual column from vcols[], it is accurate also after
+ // concealed characters.
+ *vcolp = col_from_screen;
}
- return col + nudge;
+ if (col_from_screen == -2) {
+ *flagsp |= MOUSE_FOLD_OPEN;
+ } else if (col_from_screen == -3) {
+ *flagsp |= MOUSE_FOLD_CLOSE;
+ }
}
-// Check clicked cell is foldcolumn
-int mouse_check_fold(void)
+/// "getmousepos()" function
+void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int click_grid = mouse_grid;
- int click_row = mouse_row;
- int click_col = mouse_col;
- int mouse_char = ' ';
- int max_row = Rows;
- int max_col = Columns;
- int multigrid = ui_has(kUIMultigrid);
-
- win_T *wp;
-
- wp = mouse_find_win(&click_grid, &click_row, &click_col);
- if (wp && multigrid) {
- max_row = wp->w_grid_alloc.rows;
- max_col = wp->w_grid_alloc.cols;
- }
+ int row = mouse_row;
+ int col = mouse_col;
+ int grid = mouse_grid;
+ varnumber_T winid = 0;
+ varnumber_T winrow = 0;
+ varnumber_T wincol = 0;
+ linenr_T lnum = 0;
+ varnumber_T column = 0;
+ colnr_T coladd = 0;
- if (wp && mouse_row >= 0 && mouse_row < max_row
- && mouse_col >= 0 && mouse_col < max_col) {
- ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
- int fdc = win_fdccol_count(wp);
- int row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
- int col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
+ tv_dict_alloc_ret(rettv);
+ dict_T *d = rettv->vval.v_dict;
- // Remember the character under the mouse, might be one of foldclose or
- // foldopen fillchars in the fold column.
- if (gp->chars != NULL) {
- mouse_char = utf_ptr2char((char *)gp->chars[gp->line_offset[row]
- + (unsigned)col]);
- }
+ tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1);
+ tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1);
- // Check for position outside of the fold column.
- if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
- click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
- mouse_char = ' ';
+ win_T *wp = mouse_find_win(&grid, &row, &col);
+ if (wp != NULL) {
+ int height = wp->w_height + wp->w_hsep_height + wp->w_status_height;
+ // The height is adjusted by 1 when there is a bottom border. This is not
+ // necessary for a top border since `row` starts at -1 in that case.
+ if (row < height + wp->w_border_adj[2]) {
+ winid = wp->handle;
+ winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border
+ wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border
+ if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) {
+ (void)mouse_comp_pos(wp, &row, &col, &lnum);
+ col = vcol2col(wp, lnum, col, &coladd);
+ column = col + 1;
+ }
}
}
-
- if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
- return MOUSE_FOLD_OPEN;
- } else if (mouse_char != ' ') {
- return MOUSE_FOLD_CLOSE;
- }
-
- return 0;
+ tv_dict_add_nr(d, S_LEN("winid"), winid);
+ tv_dict_add_nr(d, S_LEN("winrow"), winrow);
+ tv_dict_add_nr(d, S_LEN("wincol"), wincol);
+ tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum);
+ tv_dict_add_nr(d, S_LEN("column"), column);
+ tv_dict_add_nr(d, S_LEN("coladd"), coladd);
}
diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h
index 8b2d7e0acd..928b3e360b 100644
--- a/src/nvim/mouse.h
+++ b/src/nvim/mouse.h
@@ -1,12 +1,10 @@
-#ifndef NVIM_MOUSE_H
-#define NVIM_MOUSE_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/normal.h"
-#include "nvim/vim.h"
-#include "nvim/window.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
/// jump_to_mouse() returns one of first five these values, possibly with
/// some of the other five added.
@@ -56,5 +54,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mouse.h.generated.h"
#endif
-
-#endif // NVIM_MOUSE_H
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 3af26b910e..9ed3978490 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// move.c: Functions for moving the cursor and scrolling text.
//
// There are two ways to move the cursor:
@@ -14,39 +11,42 @@
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
-#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
+#include "nvim/memline.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/sign_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
typedef struct {
linenr_T lnum; // line number
@@ -58,6 +58,37 @@ typedef struct {
# include "move.c.generated.h"
#endif
+/// Get the number of screen lines skipped with "wp->w_skipcol".
+int adjust_plines_for_skipcol(win_T *wp)
+{
+ if (wp->w_skipcol == 0) {
+ return 0;
+ }
+
+ int width = wp->w_width_inner - win_col_off(wp);
+ int w2 = width + win_col_off2(wp);
+ if (wp->w_skipcol >= width && w2 > 0) {
+ return (wp->w_skipcol - width) / w2 + 1;
+ }
+
+ return 0;
+}
+
+/// Return how many lines "lnum" will take on the screen, taking into account
+/// whether it is the first line, whether w_skipcol is non-zero and limiting to
+/// the window height.
+static int plines_correct_topline(win_T *wp, linenr_T lnum, linenr_T *nextp, bool *foldedp)
+{
+ int n = plines_win_full(wp, lnum, nextp, foldedp, true, false);
+ if (lnum == wp->w_topline) {
+ n -= adjust_plines_for_skipcol(wp);
+ }
+ if (n > wp->w_height_inner) {
+ return wp->w_height_inner;
+ }
+ return n;
+}
+
// Compute wp->w_botline for the current wp->w_topline. Can be called after
// wp->w_topline changed.
static void comp_botline(win_T *wp)
@@ -79,7 +110,7 @@ static void comp_botline(win_T *wp)
for (; lnum <= wp->w_buffer->b_ml.ml_line_count; lnum++) {
linenr_T last = lnum;
bool folded;
- int n = plines_win_full(wp, lnum, &last, &folded, true);
+ int n = plines_correct_topline(wp, lnum, &last, &folded);
if (lnum <= wp->w_cursor.lnum && last >= wp->w_cursor.lnum) {
wp->w_cline_row = done;
wp->w_cline_height = n;
@@ -104,18 +135,6 @@ static void comp_botline(win_T *wp)
win_check_anchored_floats(wp);
}
-/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set.
-/// Also when concealing is on and 'concealcursor' is not active.
-void redraw_for_cursorline(win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible()
- && (wp->w_p_rnu || win_cursorline_standout(wp))) {
- // win_line() will redraw the number column and cursorline only.
- redraw_later(wp, UPD_VALID);
- }
-}
-
/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt'
/// contains "screenline" or when the "CurSearch" highlight is in use.
/// Also when concealing is on and 'concealcursor' is active.
@@ -123,7 +142,8 @@ static void redraw_for_cursorcolumn(win_T *wp)
FUNC_ATTR_NONNULL_ALL
{
if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) {
- if (wp->w_p_cuc || ((HL_ATTR(HLF_LC) || win_hl_attr(wp, HLF_LC)) && using_hlsearch())) {
+ if (wp->w_p_cuc
+ || (win_hl_attr(wp, HLF_LC) != win_hl_attr(wp, HLF_L) && using_hlsearch())) {
// When 'cursorcolumn' is set or "CurSearch" is in use
// need to redraw with UPD_SOME_VALID.
redraw_later(wp, UPD_SOME_VALID);
@@ -140,15 +160,63 @@ static void redraw_for_cursorcolumn(win_T *wp)
}
}
+/// Calculates how much the 'listchars' "precedes" or 'smoothscroll' "<<<"
+/// marker overlaps with buffer text for window "wp".
+/// Parameter "extra2" should be the padding on the 2nd line, not the first
+/// line.
+/// Returns the number of columns of overlap with buffer text, excluding the
+/// extra padding on the ledge.
+int sms_marker_overlap(win_T *wp, int extra2)
+{
+ // There is no marker overlap when in showbreak mode, thus no need to
+ // account for it. See grid_put_linebuf().
+ if (*get_showbreak_value(wp) != NUL) {
+ return 0;
+ }
+
+ // Overlap when 'list' and 'listchars' "precedes" are set is 1.
+ if (wp->w_p_list && wp->w_p_lcs_chars.prec) {
+ return 1;
+ }
+
+ return extra2 > 3 ? 0 : 3 - extra2;
+}
+
+/// Calculates the skipcol offset for window "wp" given how many
+/// physical lines we want to scroll down.
+static int skipcol_from_plines(win_T *wp, int plines_off)
+{
+ int width1 = wp->w_width_inner - win_col_off(wp);
+
+ int skipcol = 0;
+ if (plines_off > 0) {
+ skipcol += width1;
+ }
+ if (plines_off > 1) {
+ skipcol += (width1 + win_col_off2(wp)) * (plines_off - 1);
+ }
+ return skipcol;
+}
+
+/// Set wp->w_skipcol to zero and redraw later if needed.
+static void reset_skipcol(win_T *wp)
+{
+ if (wp->w_skipcol != 0) {
+ wp->w_skipcol = 0;
+
+ // Should use the least expensive way that displays all that changed.
+ // UPD_NOT_VALID is too expensive, UPD_REDRAW_TOP does not redraw
+ // enough when the top line gets another screen line.
+ redraw_later(wp, UPD_SOME_VALID);
+ }
+}
+
// Update curwin->w_topline to move the cursor onto the screen.
void update_topline(win_T *wp)
{
- linenr_T old_topline;
- int old_topfill;
- bool check_topline = false;
bool check_botline = false;
- long *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
- long save_so = *so_ptr;
+ OptInt *so_ptr = wp->w_p_so >= 0 ? &wp->w_p_so : &p_so;
+ OptInt save_so = *so_ptr;
// Cursor is updated instead when this is true for 'splitkeep'.
if (skip_update_topline) {
@@ -175,11 +243,11 @@ void update_topline(win_T *wp)
*so_ptr = mouse_dragging - 1;
}
- old_topline = wp->w_topline;
- old_topfill = wp->w_topfill;
+ linenr_T old_topline = wp->w_topline;
+ int old_topfill = wp->w_topfill;
// If the buffer is empty, always set topline to 1.
- if (buf_is_empty(curbuf)) { // special case - file is empty
+ if (buf_is_empty(wp->w_buffer)) { // special case - file is empty
if (wp->w_topline != 1) {
redraw_later(wp, UPD_NOT_VALID);
}
@@ -189,9 +257,10 @@ void update_topline(win_T *wp)
wp->w_viewport_invalid = true;
wp->w_scbind_pos = 1;
} else {
+ bool check_topline = false;
// If the cursor is above or near the top of the window, scroll the window
// to show the line the cursor is in, with 'scrolloff' context.
- if (wp->w_topline > 1) {
+ if (wp->w_topline > 1 || wp->w_skipcol > 0) {
// If the cursor is above topline, scrolling is always needed.
// If the cursor is far below topline and there is no folding,
// scrolling down is never needed.
@@ -199,6 +268,16 @@ void update_topline(win_T *wp)
check_topline = true;
} else if (check_top_offset()) {
check_topline = true;
+ } else if (wp->w_skipcol > 0 && wp->w_cursor.lnum == wp->w_topline) {
+ colnr_T vcol;
+
+ // Check that the cursor position is visible. Add columns for
+ // the marker displayed in the top-left if needed.
+ getvvcol(wp, &wp->w_cursor, &vcol, NULL, NULL);
+ int overlap = sms_marker_overlap(wp, win_col_off(wp) - win_col_off2(wp));
+ if (wp->w_skipcol + overlap > vcol) {
+ check_topline = true;
+ }
}
}
// Check if there are more filler lines than allowed.
@@ -211,7 +290,7 @@ void update_topline(win_T *wp)
if (halfheight < 2) {
halfheight = 2;
}
- long n;
+ int64_t n;
if (hasAnyFolding(wp)) {
// Count the number of logical lines between the cursor and
// topline + p_so (approximation of how much will be
@@ -235,7 +314,7 @@ void update_topline(win_T *wp)
// cursor in the middle of the window. Otherwise put the cursor
// near the top of the window.
if (n >= halfheight) {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
} else {
scroll_cursor_top(scrolljump_value(), false);
check_botline = true;
@@ -261,9 +340,7 @@ void update_topline(win_T *wp)
assert(wp->w_buffer != 0);
if (wp->w_botline <= wp->w_buffer->b_ml.ml_line_count) {
if (wp->w_cursor.lnum < wp->w_botline) {
- if (((long)wp->w_cursor.lnum
- >= (long)wp->w_botline - *so_ptr
- || hasAnyFolding(wp))) {
+ if ((wp->w_cursor.lnum >= wp->w_botline - *so_ptr || hasAnyFolding(wp))) {
lineoff_T loff;
// Cursor is (a few lines) above botline, check if there are
@@ -294,7 +371,7 @@ void update_topline(win_T *wp)
}
}
if (check_botline) {
- long line_count = 0;
+ int line_count = 0;
if (hasAnyFolding(wp)) {
// Count the number of logical lines between the cursor and
// botline - p_so (approximation of how much will be
@@ -309,12 +386,12 @@ void update_topline(win_T *wp)
(void)hasFolding(lnum, &lnum, NULL);
}
} else {
- line_count = wp->w_cursor.lnum - wp->w_botline + 1 + *so_ptr;
+ line_count = wp->w_cursor.lnum - wp->w_botline + 1 + (int)(*so_ptr);
}
if (line_count <= wp->w_height_inner + 1) {
scroll_cursor_bot(scrolljump_value(), false);
} else {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
}
}
}
@@ -327,12 +404,15 @@ void update_topline(win_T *wp)
if (wp->w_topline != old_topline
|| wp->w_topfill != old_topfill) {
dollar_vcol = -1;
- if (wp->w_skipcol != 0) {
- wp->w_skipcol = 0;
- redraw_later(wp, UPD_NOT_VALID);
- } else {
- redraw_later(wp, UPD_VALID);
+ redraw_later(wp, UPD_VALID);
+
+ // When 'smoothscroll' is not set, should reset w_skipcol.
+ if (!wp->w_p_sms) {
+ reset_skipcol(wp);
+ } else if (wp->w_skipcol != 0) {
+ redraw_later(wp, UPD_SOME_VALID);
}
+
// May need to set w_skipcol when cursor in w_topline.
if (wp->w_cursor.lnum == wp->w_topline) {
validate_cursor();
@@ -347,16 +427,15 @@ void update_topline(win_T *wp)
// When 'scrolljump' is negative use it as a percentage of the window height.
static int scrolljump_value(void)
{
- long result = p_sj >= 0 ? p_sj : (curwin->w_height_inner * -p_sj) / 100;
- assert(result <= INT_MAX);
- return (int)result;
+ int result = p_sj >= 0 ? (int)p_sj : (curwin->w_height_inner * (int)(-p_sj)) / 100;
+ return result;
}
// Return true when there are not 'scrolloff' lines above the cursor for the
// current window.
static bool check_top_offset(void)
{
- long so = get_scrolloff_value(curwin);
+ int so = get_scrolloff_value(curwin);
if (curwin->w_cursor.lnum < curwin->w_topline + so
|| hasAnyFolding(curwin)) {
lineoff_T loff;
@@ -405,7 +484,15 @@ void check_cursor_moved(win_T *wp)
|VALID_CHEIGHT|VALID_CROW|VALID_TOPLINE);
wp->w_valid_cursor = wp->w_cursor;
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
wp->w_viewport_invalid = true;
+ } else if (wp->w_skipcol != wp->w_valid_skipcol) {
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ |VALID_CHEIGHT|VALID_CROW
+ |VALID_BOTLINE|VALID_BOTLINE_AP);
+ wp->w_valid_cursor = wp->w_cursor;
+ wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
} else if (wp->w_cursor.col != wp->w_valid_cursor.col
|| wp->w_leftcol != wp->w_valid_leftcol
|| wp->w_cursor.coladd !=
@@ -454,18 +541,14 @@ void set_topline(win_T *wp, linenr_T lnum)
redraw_later(wp, UPD_VALID);
}
-// Call this function when the length of the cursor line (in screen
-// characters) has changed, and the change is before the cursor.
-// Need to take care of w_botline separately!
-void changed_cline_bef_curs(void)
+/// Call this function when the length of the cursor line (in screen
+/// characters) has changed, and the change is before the cursor.
+/// If the line length changed the number of screen lines might change,
+/// requiring updating w_topline. That may also invalidate w_crow.
+/// Need to take care of w_botline separately!
+void changed_cline_bef_curs(win_T *wp)
{
- curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
- |VALID_CHEIGHT|VALID_TOPLINE);
-}
-
-void changed_cline_bef_curs_win(win_T *wp)
-{
- wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL|VALID_CROW
|VALID_CHEIGHT|VALID_TOPLINE);
}
@@ -484,6 +567,19 @@ void changed_line_abv_curs_win(win_T *wp)
|VALID_CHEIGHT|VALID_TOPLINE);
}
+/// Display of line has changed for "buf", invalidate cursor position and
+/// w_botline.
+void changed_line_display_buf(buf_T *buf)
+{
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_buffer == buf) {
+ wp->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL
+ |VALID_CROW|VALID_CHEIGHT
+ |VALID_TOPLINE|VALID_BOTLINE|VALID_BOTLINE_AP);
+ }
+ }
+}
+
// Make sure the value of curwin->w_botline is valid.
void validate_botline(win_T *wp)
{
@@ -492,13 +588,8 @@ void validate_botline(win_T *wp)
}
}
-// Mark curwin->w_botline as invalid (because of some change in the buffer).
-void invalidate_botline(void)
-{
- curwin->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
-}
-
-void invalidate_botline_win(win_T *wp)
+// Mark wp->w_botline as invalid (because of some change in the buffer).
+void invalidate_botline(win_T *wp)
{
wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP);
}
@@ -512,14 +603,14 @@ void approximate_botline_win(win_T *wp)
int cursor_valid(void)
{
check_cursor_moved(curwin);
- return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) ==
- (VALID_WROW|VALID_WCOL);
+ return (curwin->w_valid & (VALID_WROW|VALID_WCOL)) == (VALID_WROW|VALID_WCOL);
}
// Validate cursor position. Makes sure w_wrow and w_wcol are valid.
// w_topline must be valid, you may need to call update_topline() first!
void validate_cursor(void)
{
+ check_cursor();
check_cursor_moved(curwin);
if ((curwin->w_valid & (VALID_WCOL|VALID_WROW)) != (VALID_WCOL|VALID_WROW)) {
curs_columns(curwin, true);
@@ -555,7 +646,7 @@ static void curs_rows(win_T *wp)
i--; // hold at inserted lines
}
}
- if (valid && (lnum != wp->w_topline || !win_may_fill(wp))) {
+ if (valid && (lnum != wp->w_topline || (wp->w_skipcol == 0 && !win_may_fill(wp)))) {
lnum = wp->w_lines[i].wl_lastlnum + 1;
// Cursor inside folded lines, don't count this row
if (lnum > wp->w_cursor.lnum) {
@@ -565,7 +656,7 @@ static void curs_rows(win_T *wp)
} else {
linenr_T last = lnum;
bool folded;
- int n = plines_win_full(wp, lnum, &last, &folded, false);
+ int n = plines_correct_topline(wp, lnum, &last, &folded);
lnum = last + 1;
if (folded && lnum > wp->w_cursor.lnum) {
break;
@@ -582,7 +673,7 @@ static void curs_rows(win_T *wp)
&& (!wp->w_lines[i].wl_valid
|| wp->w_lines[i].wl_lnum != wp->w_cursor.lnum))) {
wp->w_cline_height = plines_win_full(wp, wp->w_cursor.lnum, NULL,
- &wp->w_cline_folded, true);
+ &wp->w_cline_folded, true, true);
} else if (i > wp->w_lines_valid) {
// a line that is too long to fit on the last screen line
wp->w_cline_height = 0;
@@ -629,7 +720,7 @@ void validate_cheight(void)
curwin->w_cline_height = plines_win_full(curwin, curwin->w_cursor.lnum,
NULL, &curwin->w_cline_folded,
- true);
+ true, true);
curwin->w_valid |= VALID_CHEIGHT;
}
@@ -667,11 +758,10 @@ void validate_cursor_col(void)
// fold column and sign column (these don't move when scrolling horizontally).
int win_col_off(win_T *wp)
{
- return ((wp->w_p_nu || wp->w_p_rnu || (*wp->w_p_stc != NUL)) ?
- (number_width(wp) + (*wp->w_p_stc == NUL)) : 0)
- + (cmdwin_type == 0 || wp != curwin ? 0 : 1)
- + win_fdccol_count(wp)
- + (win_signcol_count(wp) * win_signcol_width(wp));
+ return ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
+ ? (number_width(wp) + (*wp->w_p_stc == NUL)) : 0)
+ + ((cmdwin_type == 0 || wp != curwin) ? 0 : 1)
+ + win_fdccol_count(wp) + (win_signcol_count(wp) * SIGN_WIDTH);
}
int curwin_col_off(void)
@@ -680,12 +770,13 @@ int curwin_col_off(void)
}
// Return the difference in column offset for the second screen line of a
-// wrapped line. It's 8 if 'number' or 'relativenumber' is on and 'n' is in
-// 'cpoptions'.
+// wrapped line. It's positive if 'number' or 'relativenumber' is on and 'n'
+// is in 'cpoptions'.
int win_col_off2(win_T *wp)
{
- if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
- return number_width(wp) + 1;
+ if ((wp->w_p_nu || wp->w_p_rnu || *wp->w_p_stc != NUL)
+ && vim_strchr(p_cpo, CPO_NUMCOL) != NULL) {
+ return number_width(wp) + (*wp->w_p_stc == NUL);
}
return 0;
}
@@ -695,19 +786,14 @@ int curwin_col_off2(void)
return win_col_off2(curwin);
}
-// Compute curwin->w_wcol and curwin->w_virtcol.
-// Also updates curwin->w_wrow and curwin->w_cline_row.
-// Also updates curwin->w_leftcol.
+// Compute wp->w_wcol and wp->w_virtcol.
+// Also updates wp->w_wrow and wp->w_cline_row.
+// Also updates wp->w_leftcol.
// @param may_scroll when true, may scroll horizontally
void curs_columns(win_T *wp, int may_scroll)
{
- int n;
- int width = 0;
colnr_T startcol;
colnr_T endcol;
- colnr_T prev_skipcol;
- long so = get_scrolloff_value(wp);
- long siso = get_sidescrolloff_value(wp);
// First make sure that w_topline is valid (after moving the cursor).
update_topline(wp);
@@ -737,8 +823,11 @@ void curs_columns(win_T *wp, int may_scroll)
// Now compute w_wrow, counting screen lines from w_cline_row.
wp->w_wrow = wp->w_cline_row;
- int textwidth = wp->w_width_inner - extra;
- if (textwidth <= 0) {
+ int n;
+ int width1 = wp->w_width_inner - extra; // text width for first screen line
+ int width2 = 0; // text width for second and later screen line
+ bool did_sub_skipcol = false;
+ if (width1 <= 0) {
// No room for text, put cursor in last char of window.
// If not wrapping, the last non-empty line.
wp->w_wcol = wp->w_width_inner - 1;
@@ -748,23 +837,29 @@ void curs_columns(win_T *wp, int may_scroll)
wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows;
}
} else if (wp->w_p_wrap && wp->w_width_inner != 0) {
- width = textwidth + win_col_off2(wp);
+ width2 = width1 + win_col_off2(wp);
+
+ // skip columns that are not visible
+ if (wp->w_cursor.lnum == wp->w_topline
+ && wp->w_skipcol > 0
+ && wp->w_wcol >= wp->w_skipcol) {
+ // Deduct by multiples of width2. This allows the long line wrapping
+ // formula below to correctly calculate the w_wcol value when wrapping.
+ if (wp->w_skipcol <= width1) {
+ wp->w_wcol -= width2;
+ } else {
+ wp->w_wcol -= width2 * (((wp->w_skipcol - width1) / width2) + 1);
+ }
+
+ did_sub_skipcol = true;
+ }
// long line wrapping, adjust wp->w_wrow
if (wp->w_wcol >= wp->w_width_inner) {
// this same formula is used in validate_cursor_col()
- n = (wp->w_wcol - wp->w_width_inner) / width + 1;
- wp->w_wcol -= n * width;
+ n = (wp->w_wcol - wp->w_width_inner) / width2 + 1;
+ wp->w_wcol -= n * width2;
wp->w_wrow += n;
-
- // When cursor wraps to first char of next line in Insert
- // mode, the 'showbreak' string isn't shown, backup to first
- // column
- char *const sbr = get_showbreak_value(wp);
- if (*sbr && *get_cursor_pos_ptr() == NUL
- && wp->w_wcol == vim_strsize(sbr)) {
- wp->w_wcol = 0;
- }
}
} else if (may_scroll
&& !wp->w_cline_folded) {
@@ -776,18 +871,17 @@ void curs_columns(win_T *wp, int may_scroll)
// If Cursor is right of the screen, scroll leftwards
// If we get closer to the edge than 'sidescrolloff', scroll a little
// extra
- assert(siso <= INT_MAX);
- int off_left = startcol - wp->w_leftcol - (int)siso;
- int off_right =
- endcol - wp->w_leftcol - wp->w_width_inner + (int)siso + 1;
+ int siso = get_sidescrolloff_value(wp);
+ int off_left = startcol - wp->w_leftcol - siso;
+ int off_right = endcol - wp->w_leftcol - wp->w_width_inner + siso + 1;
if (off_left < 0 || off_right > 0) {
- int diff = (off_left < 0) ? -off_left: off_right;
+ int diff = (off_left < 0) ? -off_left : off_right;
// When far off or not enough room on either side, put cursor in
// middle of window.
int new_leftcol;
- if (p_ss == 0 || diff >= textwidth / 2 || off_right >= off_left) {
- new_leftcol = wp->w_wcol - extra - textwidth / 2;
+ if (p_ss == 0 || diff >= width1 / 2 || off_right >= off_left) {
+ new_leftcol = curwin->w_wcol - extra - width1 / 2;
} else {
if (diff < p_ss) {
assert(p_ss <= INT_MAX);
@@ -824,9 +918,9 @@ void curs_columns(win_T *wp, int may_scroll)
wp->w_wrow += win_get_fill(wp, wp->w_cursor.lnum);
}
- prev_skipcol = wp->w_skipcol;
-
int plines = 0;
+ int so = get_scrolloff_value(wp);
+ colnr_T prev_skipcol = wp->w_skipcol;
if ((wp->w_wrow >= wp->w_height_inner
|| ((prev_skipcol > 0
|| wp->w_wrow + so >= wp->w_height_inner)
@@ -834,7 +928,7 @@ void curs_columns(win_T *wp, int may_scroll)
>= wp->w_height_inner))
&& wp->w_height_inner != 0
&& wp->w_cursor.lnum == wp->w_topline
- && width > 0
+ && width2 > 0
&& wp->w_width_inner != 0) {
// Cursor past end of screen. Happens with a single line that does
// not fit on screen. Find a skipcol to show the text around the
@@ -843,7 +937,7 @@ void curs_columns(win_T *wp, int may_scroll)
// 2: Less than "p_so" lines below
// 3: both of them
extra = 0;
- if (wp->w_skipcol + so * width > wp->w_virtcol) {
+ if (wp->w_skipcol + so * width2 > wp->w_virtcol) {
extra = 1;
}
// Compute last display line of the buffer line that we want at the
@@ -854,17 +948,17 @@ void curs_columns(win_T *wp, int may_scroll)
plines--;
if (plines > wp->w_wrow + so) {
assert(so <= INT_MAX);
- n = wp->w_wrow + (int)so;
+ n = wp->w_wrow + so;
} else {
n = plines;
}
- if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width - so) {
+ if ((colnr_T)n >= wp->w_height_inner + wp->w_skipcol / width2 - so) {
extra += 2;
}
- if (extra == 3 || plines <= so * 2) {
+ if (extra == 3 || wp->w_height_inner <= so * 2) {
// not enough room for 'scrolloff', put cursor in the middle
- n = wp->w_virtcol / width;
+ n = wp->w_virtcol / width2;
if (n > wp->w_height_inner / 2) {
n -= wp->w_height_inner / 2;
} else {
@@ -874,51 +968,62 @@ void curs_columns(win_T *wp, int may_scroll)
if (n > plines - wp->w_height_inner + 1) {
n = plines - wp->w_height_inner + 1;
}
- wp->w_skipcol = n * width;
+ if (n > 0) {
+ curwin->w_skipcol = width1 + (n - 1) * width2;
+ } else {
+ curwin->w_skipcol = 0;
+ }
} else if (extra == 1) {
// less than 'scrolloff' lines above, decrease skipcol
assert(so <= INT_MAX);
- extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol
- + width - 1) / width;
+ extra = (wp->w_skipcol + so * width2 - wp->w_virtcol + width2 - 1) / width2;
if (extra > 0) {
- if ((colnr_T)(extra * width) > wp->w_skipcol) {
- extra = wp->w_skipcol / width;
+ if ((colnr_T)(extra * width2) > wp->w_skipcol) {
+ extra = wp->w_skipcol / width2;
}
- wp->w_skipcol -= extra * width;
+ wp->w_skipcol -= extra * width2;
}
} else if (extra == 2) {
// less than 'scrolloff' lines below, increase skipcol
- endcol = (n - wp->w_height_inner + 1) * width;
+ endcol = (n - wp->w_height_inner + 1) * width2;
while (endcol > wp->w_virtcol) {
- endcol -= width;
+ endcol -= width2;
}
if (endcol > wp->w_skipcol) {
wp->w_skipcol = endcol;
}
}
- wp->w_wrow -= wp->w_skipcol / width;
+ // adjust w_wrow for the changed w_skipcol
+ if (did_sub_skipcol) {
+ wp->w_wrow -= (wp->w_skipcol - prev_skipcol) / width2;
+ } else {
+ wp->w_wrow -= wp->w_skipcol / width2;
+ }
+
if (wp->w_wrow >= wp->w_height_inner) {
// small window, make sure cursor is in it
extra = wp->w_wrow - wp->w_height_inner + 1;
- wp->w_skipcol += extra * width;
+ wp->w_skipcol += extra * width2;
wp->w_wrow -= extra;
}
// extra could be either positive or negative
- extra = ((int)prev_skipcol - (int)wp->w_skipcol) / width;
+ extra = (prev_skipcol - wp->w_skipcol) / width2;
win_scroll_lines(wp, 0, extra);
- } else {
+ } else if (!wp->w_p_sms) {
wp->w_skipcol = 0;
}
if (prev_skipcol != wp->w_skipcol) {
- redraw_later(wp, UPD_NOT_VALID);
+ redraw_later(wp, UPD_SOME_VALID);
}
- redraw_for_cursorcolumn(curwin);
+ redraw_for_cursorcolumn(wp);
- // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise
+ // now w_leftcol and w_skipcol are valid, avoid check_cursor_moved()
+ // thinking otherwise
wp->w_valid_leftcol = wp->w_leftcol;
+ wp->w_valid_skipcol = wp->w_skipcol;
wp->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
@@ -935,64 +1040,67 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp,
{
colnr_T scol = 0, ccol = 0, ecol = 0;
int row = 0;
- int rowoff = 0;
colnr_T coloff = 0;
bool visible_row = false;
bool is_folded = false;
- if (pos->lnum >= wp->w_topline && pos->lnum <= wp->w_botline) {
- linenr_T lnum = pos->lnum;
+ linenr_T lnum = pos->lnum;
+ if (lnum >= wp->w_topline && lnum <= wp->w_botline) {
is_folded = hasFoldingWin(wp, lnum, &lnum, NULL, true, NULL);
- row = plines_m_win(wp, wp->w_topline, lnum - 1) + 1;
+ row = plines_m_win(wp, wp->w_topline, lnum - 1, false);
+ // "row" should be the screen line where line "lnum" begins, which can
+ // be negative if "lnum" is "w_topline" and "w_skipcol" is non-zero.
+ row -= adjust_plines_for_skipcol(wp);
// Add filler lines above this buffer line.
- row += win_get_fill(wp, lnum);
+ row += lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum);
visible_row = true;
- } else if (!local || pos->lnum < wp->w_topline) {
+ } else if (!local || lnum < wp->w_topline) {
row = 0;
} else {
- row = wp->w_height_inner;
+ row = wp->w_height_inner - 1;
}
- bool existing_row = (pos->lnum > 0
- && pos->lnum <= wp->w_buffer->b_ml.ml_line_count);
+ bool existing_row = (lnum > 0 && lnum <= wp->w_buffer->b_ml.ml_line_count);
if ((local || visible_row) && existing_row) {
const colnr_T off = win_col_off(wp);
if (is_folded) {
- row += local ? 0 : wp->w_winrow + wp->w_winrow_off;
+ row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
coloff = (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1 + off;
} else {
+ assert(lnum == pos->lnum);
getvcol(wp, pos, &scol, &ccol, &ecol);
// similar to what is done in validate_cursor_col()
colnr_T col = scol;
col += off;
- int width = wp->w_width - off + win_col_off2(wp);
+ int width = wp->w_width_inner - off + win_col_off2(wp);
// long line wrapping, adjust row
- if (wp->w_p_wrap && col >= (colnr_T)wp->w_width && width > 0) {
+ if (wp->w_p_wrap && col >= (colnr_T)wp->w_width_inner && width > 0) {
// use same formula as what is used in curs_columns()
- rowoff = visible_row ? ((col - wp->w_width) / width + 1) : 0;
+ int rowoff = visible_row ? ((col - wp->w_width_inner) / width + 1) : 0;
col -= rowoff * width;
+ row += rowoff;
}
col -= wp->w_leftcol;
- if (col >= 0 && col < wp->w_width && row + rowoff <= wp->w_height) {
+ if (col >= 0 && col < wp->w_width_inner && row >= 0 && row < wp->w_height_inner) {
coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_wincol_off) + 1;
- row += local ? 0 : wp->w_winrow + wp->w_winrow_off;
+ row += (local ? 0 : wp->w_winrow + wp->w_winrow_off) + 1;
} else {
// character is left, right or below of the window
scol = ccol = ecol = 0;
if (local) {
coloff = col < 0 ? -1 : wp->w_width_inner + 1;
} else {
- row = rowoff = 0;
+ row = 0;
}
}
}
}
- *rowp = row + rowoff;
+ *rowp = row;
*scolp = scol + coloff;
*ccolp = ccol + coloff;
*ecolp = ecol + coloff;
@@ -1010,8 +1118,8 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
pos_T pos = {
- .lnum = (linenr_T)tv_get_number(&argvars[1]),
- .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
+ .lnum = (linenr_T)tv_get_number(&argvars[1]),
+ .col = (colnr_T)tv_get_number(&argvars[2]) - 1,
.coladd = 0
};
if (pos.lnum > wp->w_buffer->b_ml.ml_line_count) {
@@ -1028,6 +1136,25 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
tv_dict_add_nr(dict, S_LEN("endcol"), ecol);
}
+/// Convert a virtual (screen) column to a character column. The first column
+/// is one. For a multibyte character, the column number of the first byte is
+/// returned.
+static int virtcol2col(win_T *wp, linenr_T lnum, int vcol)
+{
+ int offset = vcol2col(wp, lnum, vcol - 1, NULL);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
+ char *p = line + offset;
+
+ if (*p == NUL) {
+ if (p == line) { // empty line
+ return 0;
+ }
+ // Move to the first byte of the last char.
+ MB_PTR_BACK(line, p);
+ }
+ return (int)(p - line + 1);
+}
+
/// "virtcol2col({winid}, {lnum}, {col})" function
void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -1055,46 +1182,83 @@ void f_virtcol2col(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- rettv->vval.v_number = vcol2col(wp, lnum, screencol);
+ rettv->vval.v_number = virtcol2col(wp, lnum, screencol);
}
/// Scroll the current window down by "line_count" logical lines. "CTRL-Y"
///
/// @param line_count number of lines to scroll
/// @param byfold if true, count a closed fold as one line
-bool scrolldown(long line_count, int byfold)
+bool scrolldown(linenr_T line_count, int byfold)
{
int done = 0; // total # of physical lines done
+ int width1 = 0;
+ int width2 = 0;
+ bool do_sms = curwin->w_p_wrap && curwin->w_p_sms;
+
+ if (do_sms) {
+ width1 = curwin->w_width_inner - curwin_col_off();
+ width2 = width1 + curwin_col_off2();
+ }
// Make sure w_topline is at the first of a sequence of folded lines.
(void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
validate_cursor(); // w_wrow needs to be valid
- while (line_count-- > 0) {
+ for (int todo = line_count; todo > 0; todo--) {
if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)
&& curwin->w_topfill < curwin->w_height_inner - 1) {
curwin->w_topfill++;
done++;
} else {
- if (curwin->w_topline == 1) {
+ // break when at the very top
+ if (curwin->w_topline == 1 && (!do_sms || curwin->w_skipcol < width1)) {
break;
}
- curwin->w_topline--;
- curwin->w_topfill = 0;
- // A sequence of folded lines only counts for one logical line
- linenr_T first;
- if (hasFolding(curwin->w_topline, &first, NULL)) {
- done++;
- if (!byfold) {
- line_count -= curwin->w_topline - first - 1;
+ if (do_sms && curwin->w_skipcol >= width1) {
+ // scroll a screen line down
+ if (curwin->w_skipcol >= width1 + width2) {
+ curwin->w_skipcol -= width2;
+ } else {
+ curwin->w_skipcol -= width1;
}
- curwin->w_botline -= curwin->w_topline - first;
- curwin->w_topline = first;
+ redraw_later(curwin, UPD_NOT_VALID);
+ done++;
} else {
- done += plines_win_nofill(curwin, curwin->w_topline, true);
+ // scroll a text line down
+ curwin->w_topline--;
+ curwin->w_skipcol = 0;
+ curwin->w_topfill = 0;
+ // A sequence of folded lines only counts for one logical line
+ linenr_T first;
+ if (hasFolding(curwin->w_topline, &first, NULL)) {
+ done++;
+ if (!byfold) {
+ todo -= curwin->w_topline - first - 1;
+ }
+ curwin->w_botline -= curwin->w_topline - first;
+ curwin->w_topline = first;
+ } else {
+ if (do_sms) {
+ int size = win_linetabsize(curwin, curwin->w_topline,
+ ml_get(curwin->w_topline), MAXCOL);
+ if (size > width1) {
+ curwin->w_skipcol = width1;
+ size -= width1;
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
+ while (size > width2) {
+ curwin->w_skipcol += width2;
+ size -= width2;
+ }
+ done++;
+ } else {
+ done += plines_win_nofill(curwin, curwin->w_topline, true);
+ }
+ }
}
}
curwin->w_botline--; // approximate w_botline
- invalidate_botline();
+ invalidate_botline(curwin);
}
curwin->w_wrow += done; // keep w_wrow updated
curwin->w_cline_row += done; // keep w_cline_row updated
@@ -1135,6 +1299,27 @@ bool scrolldown(long line_count, int byfold)
foldAdjustCursor();
coladvance(curwin->w_curswant);
}
+
+ if (curwin->w_cursor.lnum == curwin->w_topline && do_sms) {
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+
+ // make sure the cursor is in the visible text
+ validate_virtcol();
+ colnr_T col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols;
+ int row = 0;
+ if (col >= width1) {
+ col -= width1;
+ row++;
+ }
+ if (col > width2 && width2 > 0) {
+ row += (int)col / width2;
+ }
+ if (row >= curwin->w_height_inner) {
+ curwin->w_curswant = curwin->w_virtcol - (row - curwin->w_height_inner + 1) * width2;
+ coladvance(curwin->w_curswant);
+ }
+ }
return moved;
}
@@ -1142,35 +1327,76 @@ bool scrolldown(long line_count, int byfold)
///
/// @param line_count number of lines to scroll
/// @param byfold if true, count a closed fold as one line
-bool scrollup(long line_count, int byfold)
+bool scrollup(linenr_T line_count, int byfold)
{
linenr_T topline = curwin->w_topline;
linenr_T botline = curwin->w_botline;
+ int do_sms = curwin->w_p_wrap && curwin->w_p_sms;
- if ((byfold && hasAnyFolding(curwin))
- || win_may_fill(curwin)) {
- // count each sequence of folded lines as one logical line
- linenr_T lnum = curwin->w_topline;
- while (line_count--) {
+ if (do_sms || (byfold && hasAnyFolding(curwin)) || win_may_fill(curwin)) {
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ int width2 = width1 + curwin_col_off2();
+ int size = 0;
+ const colnr_T prev_skipcol = curwin->w_skipcol;
+
+ if (do_sms) {
+ size = linetabsize(curwin, curwin->w_topline);
+ }
+
+ // diff mode: first consume "topfill"
+ // 'smoothscroll': increase "w_skipcol" until it goes over the end of
+ // the line, then advance to the next line.
+ // folding: count each sequence of folded lines as one logical line.
+ for (int todo = line_count; todo > 0; todo--) {
if (curwin->w_topfill > 0) {
curwin->w_topfill--;
} else {
+ linenr_T lnum = curwin->w_topline;
if (byfold) {
+ // for a closed fold: go to the last line in the fold
(void)hasFolding(lnum, NULL, &lnum);
}
- if (lnum >= curbuf->b_ml.ml_line_count) {
- break;
+ if (lnum == curwin->w_topline && do_sms) {
+ // 'smoothscroll': increase "w_skipcol" until it goes over
+ // the end of the line, then advance to the next line.
+ int add = curwin->w_skipcol > 0 ? width2 : width1;
+ curwin->w_skipcol += add;
+ if (curwin->w_skipcol >= size) {
+ if (lnum == curbuf->b_ml.ml_line_count) {
+ // at the last screen line, can't scroll further
+ curwin->w_skipcol -= add;
+ break;
+ }
+ lnum++;
+ }
+ } else {
+ if (lnum >= curbuf->b_ml.ml_line_count) {
+ break;
+ }
+ lnum++;
+ }
+
+ if (lnum > curwin->w_topline) {
+ // approximate w_botline
+ curwin->w_botline += lnum - curwin->w_topline;
+ curwin->w_topline = lnum;
+ curwin->w_topfill = win_get_fill(curwin, lnum);
+ curwin->w_skipcol = 0;
+ if (todo > 1 && do_sms) {
+ size = linetabsize(curwin, curwin->w_topline);
+ }
}
- lnum++;
- curwin->w_topfill = win_get_fill(curwin, lnum);
}
}
- // approximate w_botline
- curwin->w_botline += lnum - curwin->w_topline;
- curwin->w_topline = lnum;
+
+ if (prev_skipcol > 0 || curwin->w_skipcol > 0) {
+ // need to redraw more, because wl_size of the (new) topline may
+ // now be invalid
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
} else {
- curwin->w_topline += (linenr_T)line_count;
- curwin->w_botline += (linenr_T)line_count; // approximate w_botline
+ curwin->w_topline += line_count;
+ curwin->w_botline += line_count; // approximate w_botline
}
if (curwin->w_topline > curbuf->b_ml.ml_line_count) {
@@ -1195,12 +1421,116 @@ bool scrollup(long line_count, int byfold)
coladvance(curwin->w_curswant);
}
- bool moved = topline != curwin->w_topline
- || botline != curwin->w_botline;
+ if (curwin->w_cursor.lnum == curwin->w_topline && do_sms && curwin->w_skipcol > 0) {
+ int col_off = curwin_col_off();
+ int col_off2 = curwin_col_off2();
+
+ int width1 = curwin->w_width_inner - col_off;
+ int width2 = width1 + col_off2;
+ int extra2 = col_off - col_off2;
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+ int space_cols = (curwin->w_height_inner - 1) * width2;
+
+ // If we have non-zero scrolloff, just ignore the marker as we are
+ // going past it anyway.
+ int overlap = scrolloff_cols != 0 ? 0 : sms_marker_overlap(curwin, extra2);
+
+ // Make sure the cursor is in a visible part of the line, taking
+ // 'scrolloff' into account, but using screen lines.
+ // If there are not enough screen lines put the cursor in the middle.
+ if (scrolloff_cols > space_cols / 2) {
+ scrolloff_cols = space_cols / 2;
+ }
+ validate_virtcol();
+ if (curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
+ colnr_T col = curwin->w_virtcol;
+
+ if (col < width1) {
+ col += width1;
+ }
+ while (col < curwin->w_skipcol + overlap + scrolloff_cols) {
+ col += width2;
+ }
+ curwin->w_curswant = col;
+ coladvance(curwin->w_curswant);
+
+ // validate_virtcol() marked various things as valid, but after
+ // moving the cursor they need to be recomputed
+ curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
+ }
+ }
+
+ bool moved = topline != curwin->w_topline || botline != curwin->w_botline;
return moved;
}
+/// Called after changing the cursor column: make sure that curwin->w_skipcol is
+/// valid for 'smoothscroll'.
+void adjust_skipcol(void)
+{
+ if (!curwin->w_p_wrap || !curwin->w_p_sms || curwin->w_cursor.lnum != curwin->w_topline) {
+ return;
+ }
+
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ if (width1 <= 0) {
+ return; // no text will be displayed
+ }
+ int width2 = width1 + curwin_col_off2();
+ int so = get_scrolloff_value(curwin);
+ colnr_T scrolloff_cols = so == 0 ? 0 : width1 + (so - 1) * width2;
+ bool scrolled = false;
+
+ validate_cheight();
+ if (curwin->w_cline_height == curwin->w_height_inner
+ // w_cline_height may be capped at w_height_inner, check there aren't
+ // actually more lines.
+ && plines_win(curwin, curwin->w_cursor.lnum, false) <= curwin->w_height_inner) {
+ // the line just fits in the window, don't scroll
+ reset_skipcol(curwin);
+ return;
+ }
+
+ validate_virtcol();
+ int overlap = sms_marker_overlap(curwin, curwin_col_off() - curwin_col_off2());
+ while (curwin->w_skipcol > 0
+ && curwin->w_virtcol < curwin->w_skipcol + overlap + scrolloff_cols) {
+ // scroll a screen line down
+ if (curwin->w_skipcol >= width1 + width2) {
+ curwin->w_skipcol -= width2;
+ } else {
+ curwin->w_skipcol -= width1;
+ }
+ scrolled = true;
+ }
+ if (scrolled) {
+ validate_virtcol();
+ redraw_later(curwin, UPD_NOT_VALID);
+ return; // don't scroll in the other direction now
+ }
+ colnr_T col = curwin->w_virtcol - curwin->w_skipcol + scrolloff_cols;
+ int row = 0;
+ if (col >= width1) {
+ col -= width1;
+ row++;
+ }
+ if (col > width2) {
+ row += (int)col / width2;
+ }
+ if (row >= curwin->w_height_inner) {
+ if (curwin->w_skipcol == 0) {
+ curwin->w_skipcol += width1;
+ row--;
+ }
+ if (row >= curwin->w_height_inner) {
+ curwin->w_skipcol += (row - curwin->w_height_inner) * width2;
+ }
+ redraw_later(curwin, UPD_NOT_VALID);
+ }
+}
+
/// Don't end up with too many filler lines in the window.
///
/// @param down when true scroll down when not enough space
@@ -1317,7 +1647,8 @@ void scrollup_clamp(void)
// a (wrapped) text line. Uses and sets "lp->fill".
// Returns the height of the added line in "lp->height".
// Lines above the first one are incredibly high: MAXCOL.
-static void topline_back(win_T *wp, lineoff_T *lp)
+// When "winheight" is true limit to window height.
+static void topline_back_winheight(win_T *wp, lineoff_T *lp, int winheight)
{
if (lp->fill < win_get_fill(wp, lp->lnum)) {
// Add a filler line
@@ -1332,11 +1663,16 @@ static void topline_back(win_T *wp, lineoff_T *lp)
// Add a closed fold
lp->height = 1;
} else {
- lp->height = plines_win_nofill(wp, lp->lnum, true);
+ lp->height = plines_win_nofill(wp, lp->lnum, winheight);
}
}
}
+static void topline_back(win_T *wp, lineoff_T *lp)
+{
+ topline_back_winheight(wp, lp, true);
+}
+
// Add one line below "lp->lnum". This can be a filler line, a closed fold or
// a (wrapped) text line. Uses and sets "lp->fill".
// Returns the height of the added line in "lp->height".
@@ -1389,13 +1725,10 @@ static void topline_botline(lineoff_T *lp)
// If "always" is true, always set topline (for "zt").
void scroll_cursor_top(int min_scroll, int always)
{
- int scrolled = 0;
- linenr_T top; // just above displayed lines
- linenr_T bot; // just below displayed lines
linenr_T old_topline = curwin->w_topline;
+ int old_skipcol = curwin->w_skipcol;
linenr_T old_topfill = curwin->w_topfill;
- linenr_T new_topline;
- int off = (int)get_scrolloff_value(curwin);
+ int off = get_scrolloff_value(curwin);
if (mouse_dragging > 0) {
off = mouse_dragging - 1;
@@ -1407,11 +1740,14 @@ void scroll_cursor_top(int min_scroll, int always)
// - moved at least 'scrolljump' lines and
// - at least 'scrolloff' lines above and below the cursor
validate_cheight();
+ int scrolled = 0;
int used = curwin->w_cline_height; // includes filler lines above
if (curwin->w_cursor.lnum < curwin->w_topline) {
scrolled = used;
}
+ linenr_T top; // just above displayed lines
+ linenr_T bot; // just below displayed lines
if (hasFolding(curwin->w_cursor.lnum, &top, &bot)) {
top--;
bot++;
@@ -1419,7 +1755,7 @@ void scroll_cursor_top(int min_scroll, int always)
top = curwin->w_cursor.lnum - 1;
bot = curwin->w_cursor.lnum + 1;
}
- new_topline = top + 1;
+ linenr_T new_topline = top + 1;
// "used" already contains the number of filler lines above, don't add it
// again.
@@ -1432,6 +1768,15 @@ void scroll_cursor_top(int min_scroll, int always)
int i = hasFolding(top, &top, NULL)
? 1 // count one logical line for a sequence of folded lines
: plines_win_nofill(curwin, top, true);
+ if (top < curwin->w_topline) {
+ scrolled += i;
+ }
+
+ // If scrolling is needed, scroll at least 'sj' lines.
+ if ((new_topline >= curwin->w_topline || scrolled > min_scroll) && extra >= off) {
+ break;
+ }
+
used += i;
if (extra + i <= off && bot < curbuf->b_ml.ml_line_count) {
if (hasFolding(bot, NULL, &bot)) {
@@ -1444,15 +1789,6 @@ void scroll_cursor_top(int min_scroll, int always)
if (used > curwin->w_height_inner) {
break;
}
- if (top < curwin->w_topline) {
- scrolled += i;
- }
-
- // If scrolling is needed, scroll at least 'sj' lines.
- if ((new_topline >= curwin->w_topline || scrolled > min_scroll)
- && extra >= off) {
- break;
- }
extra += i;
new_topline = top;
@@ -1464,10 +1800,10 @@ void scroll_cursor_top(int min_scroll, int always)
// This makes sure we get the same position when using "k" and "j"
// in a small window.
if (used > curwin->w_height_inner) {
- scroll_cursor_halfway(false);
+ scroll_cursor_halfway(false, false);
} else {
// If "always" is false, only adjust topline to a lower value, higher
- // value may happen with wrapping lines
+ // value may happen with wrapping lines.
if (new_topline < curwin->w_topline || always) {
curwin->w_topline = new_topline;
}
@@ -1482,7 +1818,18 @@ void scroll_cursor_top(int min_scroll, int always)
}
}
check_topfill(curwin, false);
+ if (curwin->w_topline != old_topline) {
+ reset_skipcol(curwin);
+ } else if (curwin->w_topline == curwin->w_cursor.lnum) {
+ validate_virtcol();
+ if (curwin->w_skipcol >= curwin->w_virtcol) {
+ // TODO(vim): if the line doesn't fit may optimize w_skipcol instead
+ // of making it zero
+ reset_skipcol(curwin);
+ }
+ }
if (curwin->w_topline != old_topline
+ || curwin->w_skipcol != old_skipcol
|| curwin->w_topfill != old_topfill) {
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
@@ -1519,31 +1866,44 @@ void set_empty_rows(win_T *wp, int used)
/// This is messy stuff!!!
void scroll_cursor_bot(int min_scroll, int set_topbot)
{
- int used;
- int scrolled = 0;
- int extra = 0;
lineoff_T loff;
- lineoff_T boff;
- int fill_below_window;
- linenr_T old_topline = curwin->w_topline;
- int old_topfill = curwin->w_topfill;
- linenr_T old_botline = curwin->w_botline;
- int old_valid = curwin->w_valid;
+ linenr_T old_topline = curwin->w_topline;
+ int old_skipcol = curwin->w_skipcol;
+ int old_topfill = curwin->w_topfill;
+ linenr_T old_botline = curwin->w_botline;
+ int old_valid = curwin->w_valid;
int old_empty_rows = curwin->w_empty_rows;
- linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
- long so = get_scrolloff_value(curwin);
+ linenr_T cln = curwin->w_cursor.lnum; // Cursor Line Number
+ int do_sms = curwin->w_p_wrap && curwin->w_p_sms;
if (set_topbot) {
- used = 0;
+ bool set_skipcol = false;
+
+ int used = 0;
curwin->w_botline = cln + 1;
loff.fill = 0;
for (curwin->w_topline = curwin->w_botline;
curwin->w_topline > 1;
curwin->w_topline = loff.lnum) {
loff.lnum = curwin->w_topline;
- topline_back(curwin, &loff);
- if (loff.height == MAXCOL
- || used + loff.height > curwin->w_height_inner) {
+ topline_back_winheight(curwin, &loff, false);
+ if (loff.height == MAXCOL) {
+ break;
+ }
+ if (used + loff.height > curwin->w_height_inner) {
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set. The above line is
+ // too long to show in its entirety, so we show just a part
+ // of it.
+ if (used < curwin->w_height_inner) {
+ int plines_offset = used + loff.height - curwin->w_height_inner;
+ used = curwin->w_height_inner;
+ curwin->w_topfill = loff.fill;
+ curwin->w_topline = loff.lnum;
+ curwin->w_skipcol = skipcol_from_plines(curwin, plines_offset);
+ set_skipcol = true;
+ }
+ }
break;
}
used += loff.height;
@@ -1552,26 +1912,59 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
set_empty_rows(curwin, used);
curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP;
if (curwin->w_topline != old_topline
- || curwin->w_topfill != old_topfill) {
+ || curwin->w_topfill != old_topfill
+ || set_skipcol
+ || curwin->w_skipcol != 0) {
curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
+ if (set_skipcol) {
+ redraw_later(curwin, UPD_NOT_VALID);
+ } else {
+ reset_skipcol(curwin);
+ }
}
} else {
validate_botline(curwin);
}
// The lines of the cursor line itself are always used.
- used = plines_win_nofill(curwin, cln, true);
+ int used = plines_win_nofill(curwin, cln, true);
- // If the cursor is below botline, we will at least scroll by the height
- // of the cursor line. Correct for empty lines, which are really part of
- // botline.
+ int scrolled = 0;
+ // If the cursor is on or below botline, we will at least scroll by the
+ // height of the cursor line, which is "used". Correct for empty lines,
+ // which are really part of botline.
if (cln >= curwin->w_botline) {
scrolled = used;
if (cln == curwin->w_botline) {
scrolled -= curwin->w_empty_rows;
}
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set.
+ // Calculate how many screen lines the current top line of window
+ // occupies. If it is occupying more than the entire window, we
+ // need to scroll the additional clipped lines to scroll past the
+ // top line before we can move on to the other lines.
+ int top_plines = plines_win_nofill(curwin, curwin->w_topline, false);
+ int skip_lines = 0;
+ int width1 = curwin->w_width_inner - curwin_col_off();
+ if (width1 > 0) {
+ int width2 = width1 + curwin_col_off2();
+ // similar formula is used in curs_columns()
+ if (curwin->w_skipcol > width1) {
+ skip_lines += (curwin->w_skipcol - width1) / width2 + 1;
+ } else if (curwin->w_skipcol > 0) {
+ skip_lines = 1;
+ }
+
+ top_plines -= skip_lines;
+ if (top_plines > curwin->w_height_inner) {
+ scrolled += (top_plines - curwin->w_height_inner);
+ }
+ }
+ }
}
+ lineoff_T boff;
// Stop counting lines to scroll when
// - hitting start of the file
// - scrolled nothing or at least 'sj' lines
@@ -1583,9 +1976,10 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
}
loff.fill = 0;
boff.fill = 0;
- fill_below_window = win_get_fill(curwin, curwin->w_botline)
- - curwin->w_filler_rows;
+ int fill_below_window = win_get_fill(curwin, curwin->w_botline) - curwin->w_filler_rows;
+ int extra = 0;
+ int so = get_scrolloff_value(curwin);
while (loff.lnum > 1) {
// Stop when scrolled nothing or at least "min_scroll", found "extra"
// context for 'scrolloff' and counted all lines below the window.
@@ -1669,15 +2063,19 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
// Scroll up if the cursor is off the bottom of the screen a bit.
// Otherwise put it at 1/2 of the screen.
if (line_count >= curwin->w_height_inner && line_count > min_scroll) {
- scroll_cursor_halfway(false);
- } else {
- scrollup(line_count, true);
+ scroll_cursor_halfway(false, true);
+ } else if (line_count > 0) {
+ if (do_sms) {
+ scrollup(scrolled, true); // TODO(vim):
+ } else {
+ scrollup(line_count, true);
+ }
}
// If topline didn't change we need to restore w_botline and w_empty_rows
// (we changed them).
// If topline did change, update_screen() will set botline.
- if (curwin->w_topline == old_topline && set_topbot) {
+ if (curwin->w_topline == old_topline && curwin->w_skipcol == old_skipcol && set_topbot) {
curwin->w_botline = old_botline;
curwin->w_empty_rows = old_empty_rows;
curwin->w_valid = old_valid;
@@ -1690,55 +2088,121 @@ void scroll_cursor_bot(int min_scroll, int set_topbot)
///
/// @param atend if true, also put the cursor halfway to the end of the file.
///
-void scroll_cursor_halfway(int atend)
+void scroll_cursor_halfway(bool atend, bool prefer_above)
{
- int above = 0;
- int topfill = 0;
- int below = 0;
- lineoff_T loff;
- lineoff_T boff;
linenr_T old_topline = curwin->w_topline;
-
- loff.lnum = boff.lnum = curwin->w_cursor.lnum;
+ lineoff_T loff = { .lnum = curwin->w_cursor.lnum };
+ lineoff_T boff = { .lnum = curwin->w_cursor.lnum };
(void)hasFolding(loff.lnum, &loff.lnum, &boff.lnum);
int used = plines_win_nofill(curwin, loff.lnum, true);
loff.fill = 0;
boff.fill = 0;
linenr_T topline = loff.lnum;
- while (topline > 1) {
- if (below <= above) { // add a line below the cursor first
- if (boff.lnum < curbuf->b_ml.ml_line_count) {
- botline_forw(curwin, &boff);
- used += boff.height;
- if (used > curwin->w_height_inner) {
- break;
- }
- below += boff.height;
- } else {
- below++; // count a "~" line
- if (atend) {
- used++;
- }
- }
+ colnr_T skipcol = 0;
+
+ int want_height;
+ bool do_sms = curwin->w_p_wrap && curwin->w_p_sms;
+ if (do_sms) {
+ // 'smoothscroll' and 'wrap' are set
+ if (atend) {
+ want_height = (curwin->w_height_inner - used) / 2;
+ used = 0;
+ } else {
+ want_height = curwin->w_height_inner;
}
+ }
- if (below > above) { // add a line above the cursor
- topline_back(curwin, &loff);
+ int topfill = 0;
+ while (topline > 1) {
+ // If using smoothscroll, we can precisely scroll to the
+ // exact point where the cursor is halfway down the screen.
+ if (do_sms) {
+ topline_back_winheight(curwin, &loff, false);
if (loff.height == MAXCOL) {
- used = MAXCOL;
- } else {
- used += loff.height;
+ break;
}
- if (used > curwin->w_height_inner) {
+ used += loff.height;
+ if (!atend && boff.lnum < curbuf->b_ml.ml_line_count) {
+ botline_forw(curwin, &boff);
+ used += boff.height;
+ }
+ if (used > want_height) {
+ if (used - loff.height < want_height) {
+ topline = loff.lnum;
+ topfill = loff.fill;
+ skipcol = skipcol_from_plines(curwin, used - want_height);
+ }
break;
}
- above += loff.height;
topline = loff.lnum;
topfill = loff.fill;
+ continue;
+ }
+
+ // If not using smoothscroll, we have to iteratively find how many
+ // lines to scroll down to roughly fit the cursor.
+ // This may not be right in the middle if the lines'
+ // physical height > 1 (e.g. 'wrap' is on).
+
+ // Depending on "prefer_above" we add a line above or below first.
+ // Loop twice to avoid duplicating code.
+ bool done = false;
+ int above = 0;
+ int below = 0;
+ for (int round = 1; round <= 2; round++) {
+ if (prefer_above
+ ? (round == 2 && below < above)
+ : (round == 1 && below <= above)) {
+ // add a line below the cursor
+ if (boff.lnum < curbuf->b_ml.ml_line_count) {
+ botline_forw(curwin, &boff);
+ used += boff.height;
+ if (used > curwin->w_height_inner) {
+ done = true;
+ break;
+ }
+ below += boff.height;
+ } else {
+ below++; // count a "~" line
+ if (atend) {
+ used++;
+ }
+ }
+ }
+
+ if (prefer_above
+ ? (round == 1 && below >= above)
+ : (round == 1 && below > above)) {
+ // add a line above the cursor
+ topline_back(curwin, &loff);
+ if (loff.height == MAXCOL) {
+ used = MAXCOL;
+ } else {
+ used += loff.height;
+ }
+ if (used > curwin->w_height_inner) {
+ done = true;
+ break;
+ }
+ above += loff.height;
+ topline = loff.lnum;
+ topfill = loff.fill;
+ }
+ }
+ if (done) {
+ break;
}
}
- if (!hasFolding(topline, &curwin->w_topline, NULL)) {
+
+ if (!hasFolding(topline, &curwin->w_topline, NULL)
+ && (curwin->w_topline != topline || skipcol != 0 || curwin->w_skipcol != 0)) {
curwin->w_topline = topline;
+ if (skipcol != 0) {
+ curwin->w_skipcol = skipcol;
+ redraw_later(curwin, UPD_NOT_VALID);
+ } else if (do_sms) {
+ reset_skipcol(curwin);
+ }
}
curwin->w_topfill = topfill;
if (old_topline > curwin->w_topline + curwin->w_height_inner) {
@@ -1757,8 +2221,8 @@ void cursor_correct(void)
{
// How many lines we would like to have above/below the cursor depends on
// whether the first/last line of the file is on screen.
- int above_wanted = (int)get_scrolloff_value(curwin);
- int below_wanted = (int)get_scrolloff_value(curwin);
+ int above_wanted = get_scrolloff_value(curwin);
+ int below_wanted = get_scrolloff_value(curwin);
if (mouse_dragging > 0) {
above_wanted = mouse_dragging - 1;
below_wanted = mouse_dragging - 1;
@@ -1789,6 +2253,16 @@ void cursor_correct(void)
return;
}
+ if (curwin->w_p_sms && !curwin->w_p_wrap) {
+ // 'smoothscroll' is active
+ if (curwin->w_cline_height == curwin->w_height_inner) {
+ // The cursor line just fits in the window, don't scroll.
+ reset_skipcol(curwin);
+ return;
+ }
+ // TODO(vim): If the cursor line doesn't fit in the window then only adjust w_skipcol.
+ }
+
// Narrow down the area where the cursor can be put by taking lines from
// the top and the bottom until:
// - the desired context lines are found
@@ -1841,16 +2315,16 @@ void cursor_correct(void)
curwin->w_viewport_invalid = true;
}
-// move screen 'count' pages up or down and update screen
-//
-// return FAIL for failure, OK otherwise
-int onepage(Direction dir, long count)
+/// Move screen "count" pages up ("dir" is BACKWARD) or down ("dir" is FORWARD)
+/// and update the screen.
+///
+/// @return FAIL for failure, OK otherwise.
+int onepage(Direction dir, int count)
{
- long n;
int retval = OK;
lineoff_T loff;
linenr_T old_topline = curwin->w_topline;
- long so = get_scrolloff_value(curwin);
+ int so = get_scrolloff_value(curwin);
if (curbuf->b_ml.ml_line_count == 1) { // nothing to do
beep_flush();
@@ -1945,7 +2419,7 @@ int onepage(Direction dir, long count)
// Find the line just above the new topline to get the right line
// at the bottom of the window.
- n = 0;
+ int n = 0;
while (n <= curwin->w_height_inner && loff.lnum >= 1) {
topline_back(curwin, &loff);
if (loff.height == MAXCOL) {
@@ -1981,11 +2455,11 @@ int onepage(Direction dir, long count)
if (curwin->w_topfill == loff.fill) {
curwin->w_topline--;
curwin->w_topfill = 0;
+ curwin->w_valid &= ~(VALID_WROW|VALID_CROW);
}
comp_botline(curwin);
curwin->w_cursor.lnum = curwin->w_botline - 1;
- curwin->w_valid &=
- ~(VALID_WCOL | VALID_CHEIGHT | VALID_WROW | VALID_CROW);
+ curwin->w_valid &= ~(VALID_WCOL|VALID_CHEIGHT|VALID_WROW|VALID_CROW);
} else {
curwin->w_topline = loff.lnum;
curwin->w_topfill = loff.fill;
@@ -2087,7 +2561,7 @@ static void get_scroll_overlap(lineoff_T *lp, int dir)
// Scroll 'scroll' lines up or down.
void halfpage(bool flag, linenr_T Prenum)
{
- long scrolled = 0;
+ int scrolled = 0;
int i;
if (Prenum) {
@@ -2157,7 +2631,7 @@ void halfpage(bool flag, linenr_T Prenum)
} else {
curwin->w_cursor.lnum += n;
}
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
}
} else {
// scroll the text down
@@ -2210,9 +2684,18 @@ void halfpage(bool flag, linenr_T Prenum)
void do_check_cursorbind(void)
{
- linenr_T line = curwin->w_cursor.lnum;
- colnr_T col = curwin->w_cursor.col;
- colnr_T coladd = curwin->w_cursor.coladd;
+ static win_T *prev_curwin = NULL;
+ static pos_T prev_cursor = { 0, 0, 0 };
+
+ if (curwin == prev_curwin && equalpos(curwin->w_cursor, prev_cursor)) {
+ return;
+ }
+ prev_curwin = curwin;
+ prev_cursor = curwin->w_cursor;
+
+ linenr_T line = curwin->w_cursor.lnum;
+ colnr_T col = curwin->w_cursor.col;
+ colnr_T coladd = curwin->w_cursor.coladd;
colnr_T curswant = curwin->w_curswant;
int set_curswant = curwin->w_set_curswant;
win_T *old_curwin = curwin;
@@ -2221,11 +2704,11 @@ void do_check_cursorbind(void)
int old_VIsual_active = VIsual_active;
// loop through the cursorbound windows
- VIsual_select = VIsual_active = 0;
+ VIsual_select = VIsual_active = false;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
curwin = wp;
curbuf = curwin->w_buffer;
- // skip original window and windows with 'noscrollbind'
+ // skip original window and windows with 'nocursorbind'
if (curwin != old_curwin && curwin->w_p_crb) {
if (curwin->w_p_diff) {
curwin->w_cursor.lnum =
diff --git a/src/nvim/move.h b/src/nvim/move.h
index dd944e39ee..ab8fb2b386 100644
--- a/src/nvim/move.h
+++ b/src/nvim/move.h
@@ -1,11 +1,12 @@
-#ifndef NVIM_MOVE_H
-#define NVIM_MOVE_H
+#pragma once
#include <stdbool.h>
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "move.h.generated.h"
#endif
-#endif // NVIM_MOVE_H
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index d60e18590f..0fb1ebf931 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/object.h>
@@ -10,7 +7,6 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
-#include <uv.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
@@ -24,9 +20,11 @@
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -35,16 +33,82 @@
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/os/input.h"
#include "nvim/rbuffer.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
-#if MIN_LOG_LEVEL > LOGLVL_DBG
+#ifdef NVIM_LOG_DEBUG
+# define REQ "[request] "
+# define RES "[response] "
+# define NOT "[notify] "
+# define ERR "[error] "
+
+// Cannot define array with negative offsets, so this one is needed to be added
+// to MSGPACK_UNPACK_\* values.
+# define MUR_OFF 2
+
+static const char *const msgpack_error_messages[] = {
+ [MSGPACK_UNPACK_EXTRA_BYTES + MUR_OFF] = "extra bytes found",
+ [MSGPACK_UNPACK_CONTINUE + MUR_OFF] = "incomplete string",
+ [MSGPACK_UNPACK_PARSE_ERROR + MUR_OFF] = "parse error",
+ [MSGPACK_UNPACK_NOMEM_ERROR + MUR_OFF] = "not enough memory",
+};
+
+static void log_close(FILE *f)
+{
+ fputc('\n', f);
+ fflush(f);
+ fclose(f);
+ log_unlock();
+}
+
+static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
+{
+ msgpack_unpacked unpacked;
+ msgpack_unpacked_init(&unpacked);
+ DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
+ const msgpack_unpack_return result =
+ msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
+ switch (result) {
+ case MSGPACK_UNPACK_SUCCESS: {
+ uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
+ msgpack_object_print(f, unpacked.data);
+ log_close(f);
+ msgpack_unpacked_destroy(&unpacked);
+ break;
+ }
+ case MSGPACK_UNPACK_EXTRA_BYTES:
+ case MSGPACK_UNPACK_CONTINUE:
+ case MSGPACK_UNPACK_PARSE_ERROR:
+ case MSGPACK_UNPACK_NOMEM_ERROR: {
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, ERR);
+ fprintf(f, "%s", msgpack_error_messages[result + MUR_OFF]);
+ log_close(f);
+ break;
+ }
+ }
+}
+
+static void log_client_msg(uint64_t channel_id, bool is_request, const char *name)
+{
+ DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
+ log_lock();
+ FILE *f = open_log_file();
+ fprintf(f, "%s: %s", is_request ? REQ : RES, name);
+ log_close(f);
+}
+
+#else
# define log_client_msg(...)
# define log_server_msg(...)
#endif
-static PMap(cstr_t) event_strings = MAP_INIT;
+static Set(cstr_t) event_strings = SET_INIT;
static msgpack_sbuffer out_buffer;
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -71,7 +135,7 @@ void rpc_start(Channel *channel)
if (channel->streamtype != kChannelStreamInternal) {
Stream *out = channel_outstream(channel);
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
+#ifdef NVIM_LOG_DEBUG
Stream *in = channel_instream(channel);
DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id,
(void *)in, (void *)out);
@@ -141,9 +205,15 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem
// Push the frame
ChannelCallFrame frame = { request_id, false, false, NIL, NULL };
kv_push(rpc->call_stack, &frame);
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned);
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, channel->events, -1, frame.returned || rpc->closed);
(void)kv_pop(rpc->call_stack);
+ if (rpc->closed) {
+ api_set_error(err, kErrorTypeException, "Invalid channel: %" PRIu64, id);
+ channel_decref(channel);
+ return NIL;
+ }
+
if (frame.errored) {
if (frame.result.type == kObjectTypeString) {
api_set_error(err, kErrorTypeException, "%s",
@@ -188,14 +258,12 @@ void rpc_subscribe(uint64_t id, char *event)
abort();
}
- char *event_string = pmap_get(cstr_t)(&event_strings, event);
-
- if (!event_string) {
- event_string = xstrdup(event);
- pmap_put(cstr_t)(&event_strings, event_string, event_string);
+ const char **key_alloc = NULL;
+ if (set_put_ref(cstr_t, &event_strings, event, &key_alloc)) {
+ *key_alloc = xstrdup(event);
}
- pmap_put(cstr_t)(channel->rpc.subscribed_events, event_string, event_string);
+ set_put(cstr_t, channel->rpc.subscribed_events, *key_alloc);
}
/// Unsubscribes to event broadcasts
@@ -242,26 +310,43 @@ end:
channel_decref(channel);
}
+static ChannelCallFrame *find_call_frame(RpcState *rpc, uint32_t request_id)
+{
+ for (size_t i = 0; i < kv_size(rpc->call_stack); i++) {
+ ChannelCallFrame *frame = kv_Z(rpc->call_stack, i);
+ if (frame->request_id == request_id) {
+ return frame;
+ }
+ }
+ return NULL;
+}
+
static void parse_msgpack(Channel *channel)
{
Unpacker *p = channel->rpc.unpacker;
while (unpacker_advance(p)) {
if (p->type == kMessageTypeRedrawEvent) {
- if (p->grid_line_event) {
- ui_client_event_raw_line(p->grid_line_event);
- } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) {
- p->ui_handler.fn(p->result.data.array);
+ // When exiting, ui_client_stop() has already been called, so don't handle UI events.
+ if (ui_client_channel_id && !exiting) {
+ if (p->grid_line_event) {
+ ui_client_event_raw_line(p->grid_line_event);
+ } else if (p->ui_handler.fn != NULL && p->result.type == kObjectTypeArray) {
+ p->ui_handler.fn(p->result.data.array);
+ }
}
arena_mem_free(arena_finish(&p->arena));
} else if (p->type == kMessageTypeResponse) {
- ChannelCallFrame *frame = kv_last(channel->rpc.call_stack);
- if (p->request_id != frame->request_id) {
+ ChannelCallFrame *frame = channel->rpc.client_type == kClientTypeMsgpackRpc
+ ? find_call_frame(&channel->rpc, p->request_id)
+ : kv_last(channel->rpc.call_stack);
+ if (frame == NULL || p->request_id != frame->request_id) {
char buf[256];
snprintf(buf, sizeof(buf),
- "ch %" PRIu64 " returned a response with an unknown request "
- "id. Ensure the client is properly synchronized",
- channel->id);
+ "ch %" PRIu64 " (type=%" PRIu32 ") returned a response with an unknown request "
+ "id %" PRIu32 ". Ensure the client is properly synchronized",
+ channel->id, (unsigned)channel->rpc.client_type, p->request_id);
chan_close_with_error(channel, buf, LOGLVL_ERR);
+ return;
}
frame->returned = true;
frame->errored = (p->error.type != kObjectTypeNil);
@@ -486,7 +571,7 @@ static void broadcast_event(const char *name, Array args)
map_foreach_value(&channels, channel, {
if (channel->is_rpc
- && pmap_has(cstr_t)(channel->rpc.subscribed_events, name)) {
+ && set_has(cstr_t, channel->rpc.subscribed_events, name)) {
kv_push(subscribed, channel);
}
});
@@ -514,24 +599,12 @@ end:
static void unsubscribe(Channel *channel, char *event)
{
- char *event_string = pmap_get(cstr_t)(&event_strings, event);
- if (!event_string) {
+ if (!set_has(cstr_t, &event_strings, event)) {
WLOG("RPC: ch %" PRIu64 ": tried to unsubscribe unknown event '%s'",
channel->id, event);
return;
}
- pmap_del(cstr_t)(channel->rpc.subscribed_events, event_string);
-
- map_foreach_value(&channels, channel, {
- if (channel->is_rpc
- && pmap_has(cstr_t)(channel->rpc.subscribed_events, event_string)) {
- return;
- }
- });
-
- // Since the string is no longer used by other channels, release it's memory
- pmap_del(cstr_t)(&event_strings, event_string);
- xfree(event_string);
+ set_del(cstr_t, channel->rpc.subscribed_events, event);
}
/// Mark rpc state as closed, and release its reference to the channel.
@@ -547,6 +620,10 @@ void rpc_close(Channel *channel)
if (channel->streamtype == kChannelStreamStdio
|| (channel->id == ui_client_channel_id && channel->streamtype != kChannelStreamProc)) {
+ if (channel->streamtype == kChannelStreamStdio) {
+ // Avoid hanging when there are no other UIs and a prompt is triggered on exit.
+ remote_ui_disconnect(channel->id);
+ }
exit_from_channel(0);
}
}
@@ -557,13 +634,7 @@ void rpc_free(Channel *channel)
unpacker_teardown(channel->rpc.unpacker);
xfree(channel->rpc.unpacker);
- // Unsubscribe from all events
- char *event_string;
- map_foreach_value(channel->rpc.subscribed_events, event_string, {
- unsubscribe(channel, event_string);
- });
-
- pmap_destroy(cstr_t)(channel->rpc.subscribed_events);
+ set_destroy(cstr_t, channel->rpc.subscribed_events);
kv_destroy(channel->rpc.call_stack);
api_free_dictionary(channel->rpc.info);
}
@@ -575,7 +646,7 @@ static void chan_close_with_error(Channel *channel, char *msg, int loglevel)
ChannelCallFrame *frame = kv_A(channel->rpc.call_stack, i);
frame->returned = true;
frame->errored = true;
- frame->result = STRING_OBJ(cstr_to_string(msg));
+ frame->result = CSTR_TO_OBJ(msg);
}
channel_close(channel->id, kChannelPartRpc, NULL);
@@ -612,7 +683,7 @@ static WBuffer *serialize_response(uint64_t channel_id, MsgpackRpcRequestHandler
} else {
Array args = ARRAY_DICT_INIT;
ADD(args, INTEGER_OBJ(err->type));
- ADD(args, STRING_OBJ(cstr_to_string(err->msg)));
+ ADD(args, CSTR_TO_OBJ(err->msg));
msgpack_rpc_serialize_request(0, cstr_as_string("nvim_error_event"),
args, &pac);
api_free_array(args);
@@ -638,6 +709,25 @@ void rpc_set_client_info(uint64_t id, Dictionary info)
api_free_dictionary(chan->rpc.info);
chan->rpc.info = info;
+
+ // Parse "type" on "info" and set "client_type"
+ const char *type = get_client_info(chan, "type");
+ if (type == NULL || strequal(type, "remote")) {
+ chan->rpc.client_type = kClientTypeRemote;
+ } else if (strequal(type, "msgpack-rpc")) {
+ chan->rpc.client_type = kClientTypeMsgpackRpc;
+ } else if (strequal(type, "ui")) {
+ chan->rpc.client_type = kClientTypeUi;
+ } else if (strequal(type, "embedder")) {
+ chan->rpc.client_type = kClientTypeEmbedder;
+ } else if (strequal(type, "host")) {
+ chan->rpc.client_type = kClientTypeHost;
+ } else if (strequal(type, "plugin")) {
+ chan->rpc.client_type = kClientTypePlugin;
+ } else {
+ chan->rpc.client_type = kClientTypeUnknown;
+ }
+
channel_info_changed(chan, false);
}
@@ -646,14 +736,15 @@ Dictionary rpc_client_info(Channel *chan)
return copy_dictionary(chan->rpc.info, NULL);
}
-const char *rpc_client_name(Channel *chan)
+const char *get_client_info(Channel *chan, const char *key)
+ FUNC_ATTR_NONNULL_ALL
{
if (!chan->is_rpc) {
return NULL;
}
Dictionary info = chan->rpc.info;
for (size_t i = 0; i < info.size; i++) {
- if (strequal("name", info.items[i].key.data)
+ if (strequal(key, info.items[i].key.data)
&& info.items[i].value.type == kObjectTypeString) {
return info.items[i].value.data.string.data;
}
@@ -662,69 +753,11 @@ const char *rpc_client_name(Channel *chan)
return NULL;
}
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
-# define REQ "[request] "
-# define RES "[response] "
-# define NOT "[notify] "
-# define ERR "[error] "
-
-// Cannot define array with negative offsets, so this one is needed to be added
-// to MSGPACK_UNPACK_\* values.
-# define MUR_OFF 2
-
-static const char *const msgpack_error_messages[] = {
- [MSGPACK_UNPACK_EXTRA_BYTES + MUR_OFF] = "extra bytes found",
- [MSGPACK_UNPACK_CONTINUE + MUR_OFF] = "incomplete string",
- [MSGPACK_UNPACK_PARSE_ERROR + MUR_OFF] = "parse error",
- [MSGPACK_UNPACK_NOMEM_ERROR + MUR_OFF] = "not enough memory",
-};
-
-static void log_server_msg(uint64_t channel_id, msgpack_sbuffer *packed)
-{
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- DLOGN("RPC ->ch %" PRIu64 ": ", channel_id);
- const msgpack_unpack_return result =
- msgpack_unpack_next(&unpacked, packed->data, packed->size, NULL);
- switch (result) {
- case MSGPACK_UNPACK_SUCCESS: {
- uint64_t type = unpacked.data.via.array.ptr[0].via.u64;
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, type ? (type == 1 ? RES : NOT) : REQ);
- msgpack_object_print(f, unpacked.data);
- log_close(f);
- msgpack_unpacked_destroy(&unpacked);
- break;
- }
- case MSGPACK_UNPACK_EXTRA_BYTES:
- case MSGPACK_UNPACK_CONTINUE:
- case MSGPACK_UNPACK_PARSE_ERROR:
- case MSGPACK_UNPACK_NOMEM_ERROR: {
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, ERR);
- fprintf(f, "%s", msgpack_error_messages[result + MUR_OFF]);
- log_close(f);
- break;
- }
- }
-}
-
-static void log_client_msg(uint64_t channel_id, bool is_request, const char *name)
+void rpc_free_all_mem(void)
{
- DLOGN("RPC <-ch %" PRIu64 ": ", channel_id);
- log_lock();
- FILE *f = open_log_file();
- fprintf(f, "%s: %s", is_request ? REQ : RES, name);
- log_close(f);
-}
-
-static void log_close(FILE *f)
-{
- fputc('\n', f);
- fflush(f);
- fclose(f);
- log_unlock();
+ cstr_t key;
+ set_foreach(&event_strings, key, {
+ xfree((void *)key);
+ });
+ set_destroy(cstr_t, &event_strings);
}
-#endif
diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h
index ce5806930c..818bee8318 100644
--- a/src/nvim/msgpack_rpc/channel.h
+++ b/src/nvim/msgpack_rpc/channel.h
@@ -1,25 +1,24 @@
-#ifndef NVIM_MSGPACK_RPC_CHANNEL_H
-#define NVIM_MSGPACK_RPC_CHANNEL_H
+#pragma once
-#include <stdbool.h>
-#include <uv.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/channel.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
-#include "nvim/macros.h"
-#include "nvim/vim.h"
+#include "nvim/event/wstream.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory_defs.h" // IWYU pragma: keep
+#include "nvim/msgpack_rpc/channel_defs.h" // IWYU pragma: export
#define METHOD_MAXLEN 512
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
/// of os_inchar(), so they are processed "just-in-time".
-EXTERN MultiQueue *ch_before_blocking_events INIT(= NULL);
+EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/channel.h.generated.h"
#endif
-#endif // NVIM_MSGPACK_RPC_CHANNEL_H
diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h
index 404e68329a..20b8a89afb 100644
--- a/src/nvim/msgpack_rpc/channel_defs.h
+++ b/src/nvim/msgpack_rpc/channel_defs.h
@@ -1,19 +1,29 @@
-#ifndef NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
-#define NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
+#pragma once
#include <msgpack.h>
#include <stdbool.h>
#include <uv.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/event/process.h"
#include "nvim/event/socket.h"
-#include "nvim/vim.h"
+#include "nvim/map_defs.h"
typedef struct Channel Channel;
typedef struct Unpacker Unpacker;
+typedef enum {
+ kClientTypeUnknown = -1,
+ kClientTypeRemote = 0,
+ kClientTypeMsgpackRpc = 5,
+ kClientTypeUi = 1,
+ kClientTypeEmbedder = 2,
+ kClientTypeHost = 3,
+ kClientTypePlugin = 4,
+} ClientType;
+
typedef struct {
uint32_t request_id;
bool returned, errored;
@@ -31,12 +41,11 @@ typedef struct {
} RequestEvent;
typedef struct {
- PMap(cstr_t) subscribed_events[1];
+ Set(cstr_t) subscribed_events[1];
bool closed;
Unpacker *unpacker;
uint32_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
Dictionary info;
+ ClientType client_type;
} RpcState;
-
-#endif // NVIM_MSGPACK_RPC_CHANNEL_DEFS_H
diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c
index 5f0f03dd69..1fdfc9e536 100644
--- a/src/nvim/msgpack_rpc/helpers.c
+++ b/src/nvim/msgpack_rpc/helpers.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <msgpack/object.h>
#include <msgpack/sbuffer.h>
#include <msgpack/unpack.h>
@@ -12,14 +9,13 @@
#include "klib/kvec.h"
#include "msgpack/pack.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/assert.h"
-#include "nvim/event/wstream.h"
+#include "nvim/assert_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/helpers.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "keysets.generated.h" // IWYU pragma: export
# include "msgpack_rpc/helpers.c.generated.h"
#endif
@@ -84,12 +80,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
*cur.aobj = INTEGER_OBJ((Integer)cur.mobj->via.u64);
}
break;
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
{
STATIC_ASSERT(sizeof(Float) == sizeof(cur.mobj->via.f64),
"Msgpack floating-point size does not match API integer");
@@ -156,12 +148,8 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg)
case MSGPACK_OBJECT_BOOLEAN:
case MSGPACK_OBJECT_POSITIVE_INTEGER:
case MSGPACK_OBJECT_NEGATIVE_INTEGER:
-#ifdef NVIM_MSGPACK_HAS_FLOAT32
case MSGPACK_OBJECT_FLOAT32:
case MSGPACK_OBJECT_FLOAT64:
-#else
- case MSGPACK_OBJECT_FLOAT:
-#endif
case MSGPACK_OBJECT_EXT:
case MSGPACK_OBJECT_MAP:
case MSGPACK_OBJECT_ARRAY:
@@ -484,8 +472,8 @@ msgpack_object *msgpack_rpc_method(msgpack_object *req)
{
msgpack_object *obj = req->via.array.ptr
+ (msgpack_rpc_is_notification(req) ? 1 : 2);
- return obj->type == MSGPACK_OBJECT_STR || obj->type == MSGPACK_OBJECT_BIN ?
- obj : NULL;
+ return obj->type == MSGPACK_OBJECT_STR || obj->type == MSGPACK_OBJECT_BIN
+ ? obj : NULL;
}
msgpack_object *msgpack_rpc_args(msgpack_object *req)
diff --git a/src/nvim/msgpack_rpc/helpers.h b/src/nvim/msgpack_rpc/helpers.h
index dab8a16b6b..dd2096f305 100644
--- a/src/nvim/msgpack_rpc/helpers.h
+++ b/src/nvim/msgpack_rpc/helpers.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MSGPACK_RPC_HELPERS_H
-#define NVIM_MSGPACK_RPC_HELPERS_H
+#pragma once
#include <msgpack.h>
#include <stdbool.h>
@@ -19,5 +18,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/helpers.h.generated.h"
#endif
-
-#endif // NVIM_MSGPACK_RPC_HELPERS_H
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 1d75c208be..f3627eaa61 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
@@ -10,6 +7,7 @@
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/event/socket.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/log.h"
#include "nvim/main.h"
@@ -71,8 +69,8 @@ static void close_socket_watcher(SocketWatcher **watcher)
static void set_vservername(garray_T *srvs)
{
char *default_server = (srvs->ga_len > 0)
- ? ((SocketWatcher **)srvs->ga_data)[0]->addr
- : NULL;
+ ? ((SocketWatcher **)srvs->ga_data)[0]->addr
+ : NULL;
set_vim_var_string(VV_SEND_SERVER, default_server, -1);
}
@@ -91,13 +89,14 @@ char *server_address_new(const char *name)
{
static uint32_t count = 0;
char fmt[ADDRESS_MAX_SIZE];
+ const char *appname = get_appname();
#ifdef MSWIN
int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
- name ? name : "nvim", os_get_pid(), count++);
+ name ? name : appname, os_get_pid(), count++);
#else
char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
- dir, name ? name : "nvim", os_get_pid(), count++);
+ dir, name ? name : appname, os_get_pid(), count++);
xfree(dir);
#endif
if ((size_t)r >= sizeof(fmt)) {
diff --git a/src/nvim/msgpack_rpc/server.h b/src/nvim/msgpack_rpc/server.h
index 5446e40e0b..71b578a14b 100644
--- a/src/nvim/msgpack_rpc/server.h
+++ b/src/nvim/msgpack_rpc/server.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_MSGPACK_RPC_SERVER_H
-#define NVIM_MSGPACK_RPC_SERVER_H
+#pragma once
-#include <stdio.h>
+#include <stddef.h> // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/server.h.generated.h"
#endif
-#endif // NVIM_MSGPACK_RPC_SERVER_H
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
index 44a16beb48..38263381bf 100644
--- a/src/nvim/msgpack_rpc/unpacker.c
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
@@ -8,8 +5,9 @@
#include "klib/kvec.h"
#include "mpack/conv.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/macros.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/grid.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/helpers.h"
@@ -87,7 +85,7 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
*result = NIL;
break;
case MPACK_TOKEN_BOOLEAN:
- *result = BOOL(mpack_unpack_boolean(node->tok));
+ *result = BOOLEAN_OBJ(mpack_unpack_boolean(node->tok));
break;
case MPACK_TOKEN_SINT:
*result = INTEGER_OBJ(mpack_unpack_sint(node->tok));
@@ -172,14 +170,12 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
node->data[0].p = result;
break;
}
-
- default:
- abort();
}
}
static void api_parse_exit(mpack_parser_t *parser, mpack_node_t *node)
-{}
+{
+}
void unpacker_init(Unpacker *p)
{
@@ -292,13 +288,13 @@ error:
// objects. For the moment "redraw/grid_line" uses a hand-rolled decoder,
// to avoid a blizzard of small objects for each screen cell.
//
-// <0>[2, "redraw", <10>[{11}["method", <12>[args], <12>[args], ...], <11>[...], ...]]
+// <0>[2, "redraw", <10>[<11>["method", <12>[args], <12>[args], ...], <11>[...], ...]]
//
// Where [args] gets unpacked as an Array. Note: first {11} is not saved as a state.
//
// When method is "grid_line", we furthermore decode a cell at a time like:
//
-// <0>[2, "redraw", <10>[{11}["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...]], ...], <11>[...], ...]]
+// <0>[2, "redraw", <10>[<11>["grid_line", <14>[g, r, c, [<15>[cell], <15>[cell], ...], <16>wrap]], <11>[...], ...]]
//
// where [cell] is [char, repeat, attr], where 'repeat' and 'attr' is optional
@@ -323,7 +319,7 @@ bool unpacker_advance(Unpacker *p)
return false;
}
- if (p->state == 15) {
+ if (p->state == 16) {
// grid_line event already unpacked
goto done;
} else {
@@ -358,10 +354,10 @@ done:
p->state = 0;
return true;
case 13:
- case 15:
+ case 16:
p->ncalls--;
if (p->ncalls > 0) {
- p->state = (p->state == 15) ? 14 : 12;
+ p->state = (p->state == 16) ? 14 : 12;
} else if (p->nevents > 0) {
p->state = 11;
} else {
@@ -382,7 +378,6 @@ bool unpacker_parse_redraw(Unpacker *p)
size_t size = p->read_size;
GridLineEvent *g = p->grid_line_event;
-// -V:NEXT_TYPE:501
#define NEXT_TYPE(tok, typ) \
result = mpack_rtoken(&data, &size, &tok); \
if (result == MPACK_EOF) { \
@@ -394,7 +389,6 @@ bool unpacker_parse_redraw(Unpacker *p)
return false; \
}
-redo:
switch (p->state) {
case 10:
NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
@@ -440,7 +434,7 @@ redo:
case 14:
NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
int eventarrsize = (int)tok.length;
- if (eventarrsize != 4) {
+ if (eventarrsize != 5) {
p->state = -1;
return false;
}
@@ -462,58 +456,64 @@ redo:
FALLTHROUGH;
case 15:
- assert(g->icell < g->ncells);
-
- NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
- int cellarrsize = (int)tok.length;
- if (cellarrsize < 1 || cellarrsize > 3) {
- p->state = -1;
- return false;
- }
+ for (; g->icell != g->ncells; g->icell++) {
+ assert(g->icell < g->ncells);
+
+ NEXT_TYPE(tok, MPACK_TOKEN_ARRAY);
+ int cellarrsize = (int)tok.length;
+ if (cellarrsize < 1 || cellarrsize > 3) {
+ p->state = -1;
+ return false;
+ }
- NEXT_TYPE(tok, MPACK_TOKEN_STR);
- if (tok.length > size) {
- return false;
- }
+ NEXT_TYPE(tok, MPACK_TOKEN_STR);
+ if (tok.length > size) {
+ return false;
+ }
- const char *cellbuf = data;
- size_t cellsize = tok.length;
- data += cellsize;
- size -= cellsize;
+ const char *cellbuf = data;
+ size_t cellsize = tok.length;
+ data += cellsize;
+ size -= cellsize;
- if (cellarrsize >= 2) {
- NEXT_TYPE(tok, MPACK_TOKEN_SINT);
- g->cur_attr = (int)tok.data.value.lo;
- }
+ if (cellarrsize >= 2) {
+ NEXT_TYPE(tok, MPACK_TOKEN_SINT);
+ g->cur_attr = (int)tok.data.value.lo;
+ }
- int repeat = 1;
- if (cellarrsize >= 3) {
- NEXT_TYPE(tok, MPACK_TOKEN_UINT);
- repeat = (int)tok.data.value.lo;
- }
+ int repeat = 1;
+ if (cellarrsize >= 3) {
+ NEXT_TYPE(tok, MPACK_TOKEN_UINT);
+ repeat = (int)tok.data.value.lo;
+ }
- g->clear_width = 0;
- if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
- g->clear_width = repeat;
- } else {
- for (int r = 0; r < repeat; r++) {
- if (g->coloff >= (int)grid_line_buf_size) {
- p->state = -1;
- return false;
+ g->clear_width = 0;
+ if (g->icell == g->ncells - 1 && cellsize == 1 && cellbuf[0] == ' ' && repeat > 1) {
+ g->clear_width = repeat;
+ } else {
+ schar_T sc = schar_from_buf(cellbuf, cellsize);
+ for (int r = 0; r < repeat; r++) {
+ if (g->coloff >= (int)grid_line_buf_size) {
+ p->state = -1;
+ return false;
+ }
+ grid_line_buf_char[g->coloff] = sc;
+ grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
- memcpy(grid_line_buf_char[g->coloff], cellbuf, cellsize);
- grid_line_buf_char[g->coloff][cellsize] = NUL;
- grid_line_buf_attr[g->coloff++] = g->cur_attr;
}
+
+ p->read_ptr = data;
+ p->read_size = size;
}
+ p->state = 16;
+ FALLTHROUGH;
- g->icell++;
+ case 16:
+ NEXT_TYPE(tok, MPACK_TOKEN_BOOLEAN);
+ g->wrap = mpack_unpack_boolean(tok);
p->read_ptr = data;
p->read_size = size;
- if (g->icell == g->ncells) {
- return true;
- }
- goto redo;
+ return true;
case 12:
return true;
diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h
index b8b2d38d3b..53af29761e 100644
--- a/src/nvim/msgpack_rpc/unpacker.h
+++ b/src/nvim/msgpack_rpc/unpacker.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_MSGPACK_RPC_UNPACKER_H
-#define NVIM_MSGPACK_RPC_UNPACKER_H
+#pragma once
#include <inttypes.h>
#include <stdbool.h>
@@ -11,9 +10,9 @@
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/grid_defs.h"
-#include "nvim/memory.h"
+#include "nvim/memory_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui_client.h"
struct Unpacker {
@@ -49,5 +48,3 @@ struct Unpacker {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/unpacker.h.generated.h"
#endif
-
-#endif // NVIM_MSGPACK_RPC_UNPACKER_H
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index b88cfb8926..1f789dc153 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// normal.c: Contains the main routine for processing characters in command
// mode. Communicates closely with the code in ops.c to handle
@@ -17,9 +14,8 @@
#include <string.h>
#include <time.h>
-#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -38,14 +34,15 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
@@ -57,12 +54,12 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
#include "nvim/profile.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
@@ -74,10 +71,9 @@
#include "nvim/tag.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
typedef struct normal_state {
@@ -107,6 +103,10 @@ static int VIsual_mode_orig = NUL; // saved Visual mode
# include "normal.c.generated.h"
#endif
+static const char e_changelist_is_empty[] = N_("E664: Changelist is empty");
+static const char e_cmdline_window_already_open[]
+ = N_("E1292: Command-line window is already open");
+
static inline void normal_state_init(NormalState *s)
{
memset(s, 0, sizeof(NormalState));
@@ -118,7 +118,7 @@ static inline void normal_state_init(NormalState *s)
// n_*(): functions called to handle Normal mode commands.
// v_*(): functions called to handle Visual mode commands.
-static char *e_noident = N_("E349: No identifier under cursor");
+static const char *e_noident = N_("E349: No identifier under cursor");
/// Function to be called for a Normal or Visual mode command.
/// The argument is a cmdarg_T.
@@ -359,11 +359,9 @@ static int nv_max_linear;
/// through the index in nv_cmd_idx[].
static int nv_compare(const void *s1, const void *s2)
{
- int c1, c2;
-
// The commands are sorted on absolute value.
- c1 = nv_cmds[*(const int16_t *)s1].cmd_char;
- c2 = nv_cmds[*(const int16_t *)s2].cmd_char;
+ int c1 = nv_cmds[*(const int16_t *)s1].cmd_char;
+ int c2 = nv_cmds[*(const int16_t *)s2].cmd_char;
if (c1 < 0) {
c1 = -c1;
}
@@ -401,11 +399,6 @@ void init_normal_cmds(void)
/// @return -1 for invalid command.
static int find_command(int cmdchar)
{
- int i;
- int idx;
- int top, bot;
- int c;
-
// A multi-byte character is never a command.
if (cmdchar >= 0x100) {
return -1;
@@ -425,12 +418,12 @@ static int find_command(int cmdchar)
}
// Perform a binary search.
- bot = nv_max_linear + 1;
- top = NV_CMDS_SIZE - 1;
- idx = -1;
+ int bot = nv_max_linear + 1;
+ int top = NV_CMDS_SIZE - 1;
+ int idx = -1;
while (bot <= top) {
- i = (top + bot) / 2;
- c = nv_cmds[nv_cmd_idx[i]].cmd_char;
+ int i = (top + bot) / 2;
+ int c = nv_cmds[nv_cmd_idx[i]].cmd_char;
if (c < 0) {
c = -c;
}
@@ -465,7 +458,7 @@ static bool check_text_locked(oparg_T *oap)
/// If text is locked, "curbuf->b_ro_locked" or "allbuf_lock" is set:
/// Give an error message, possibly beep and return true.
/// "oap" may be NULL.
-static bool check_text_or_curbuf_locked(oparg_T *oap)
+bool check_text_or_curbuf_locked(oparg_T *oap)
{
if (check_text_locked(oap)) {
return true;
@@ -481,6 +474,20 @@ static bool check_text_or_curbuf_locked(oparg_T *oap)
return true;
}
+static oparg_T *current_oap = NULL;
+
+/// Check if an operator was started but not finished yet.
+/// Includes typing a count or a register name.
+bool op_pending(void)
+{
+ return !(current_oap != NULL
+ && !finish_op
+ && current_oap->prev_opcount == 0
+ && current_oap->prev_count0 == 0
+ && current_oap->op_type == OP_NOP
+ && current_oap->regname == NUL);
+}
+
/// Normal state entry point. This is called on:
///
/// - Startup, In this case the function never returns.
@@ -489,15 +496,18 @@ static bool check_text_or_curbuf_locked(oparg_T *oap)
/// for example. Returns when re-entering ex mode(because ex mode recursion is
/// not allowed)
///
-/// This used to be called main_loop on main.c
+/// This used to be called main_loop() on main.c
void normal_enter(bool cmdwin, bool noexmode)
{
NormalState state;
normal_state_init(&state);
+ oparg_T *prev_oap = current_oap;
+ current_oap = &state.oa;
state.cmdwin = cmdwin;
state.noexmode = noexmode;
state.toplevel = (!cmdwin || cmdwin_result == 0) && !noexmode;
state_enter(&state.state);
+ current_oap = prev_oap;
}
static void normal_prepare(NormalState *s)
@@ -671,16 +681,16 @@ static void normal_redraw_mode_message(NormalState *s)
keep_msg = kmsg;
kmsg = xstrdup(keep_msg);
- msg_attr((const char *)kmsg, keep_msg_attr);
+ msg(kmsg, keep_msg_attr);
xfree(kmsg);
}
setcursor();
ui_cursor_shape(); // show different cursor shape
ui_flush();
if (msg_scroll || emsg_on_display) {
- os_delay(1003L, true); // wait at least one second
+ os_delay(1003, true); // wait at least one second
}
- os_delay(3003L, false); // wait up to three seconds
+ os_delay(3003, false); // wait up to three seconds
State = save_State;
msg_scroll = false;
@@ -693,7 +703,6 @@ static void normal_get_additional_char(NormalState *s)
int *cp;
bool repl = false; // get character for replace mode
bool lit = false; // get extra character literally
- bool langmap_active = false; // using :lmap mappings
int lang; // getting a text character
no_mapping++;
@@ -729,6 +738,7 @@ static void normal_get_additional_char(NormalState *s)
// Get a second or third character.
if (cp != NULL) {
+ bool langmap_active = false; // using :lmap mappings
if (repl) {
State = MODE_REPLACE; // pretend Replace mode
ui_cursor_shape(); // show different cursor shape
@@ -771,10 +781,6 @@ static void normal_get_additional_char(NormalState *s)
// adjust chars > 127, except after "tTfFr" commands
LANGMAP_ADJUST(*cp, !lang);
- // adjust Hebrew mapped char
- if (p_hkmap && lang && KeyTyped) {
- *cp = hkmap(*cp);
- }
}
// When the next character is CTRL-\ a following CTRL-N means the
@@ -789,13 +795,13 @@ static void normal_get_additional_char(NormalState *s)
&& s->ca.cmdchar == 'g') {
s->ca.oap->op_type = get_op_type(*cp, NUL);
} else if (*cp == Ctrl_BSL) {
- long towait = (p_ttm >= 0 ? p_ttm : p_tm);
+ int towait = (p_ttm >= 0 ? (int)p_ttm : (int)p_tm);
// There is a busy wait here when typing "f<C-\>" and then
// something different from CTRL-N. Can't be avoided.
- while ((s->c = vpeekc()) <= 0 && towait > 0L) {
- do_sleep(towait > 50L ? 50L : towait);
- towait -= 50L;
+ while ((s->c = vpeekc()) <= 0 && towait > 0) {
+ do_sleep(towait > 50 ? 50 : towait);
+ towait -= 50;
}
if (s->c > 0) {
s->c = plain_vgetc();
@@ -810,25 +816,34 @@ static void normal_get_additional_char(NormalState *s)
}
}
- // When getting a text character and the next character is a
- // multi-byte character, it could be a composing character.
- // However, don't wait for it to arrive. Also, do enable mapping,
- // because if it's put back with vungetc() it's too late to apply
- // mapping.
- no_mapping--;
- while (lang && (s->c = vpeekc()) > 0
- && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
- s->c = plain_vgetc();
- if (!utf_iscomposing(s->c)) {
- vungetc(s->c); // it wasn't, put it back
- break;
- } else if (s->ca.ncharC1 == 0) {
- s->ca.ncharC1 = s->c;
- } else {
- s->ca.ncharC2 = s->c;
+ if (lang) {
+ // When getting a text character and the next character is a
+ // multi-byte character, it could be a composing character.
+ // However, don't wait for it to arrive. Also, do enable mapping,
+ // because if it's put back with vungetc() it's too late to apply
+ // mapping.
+ no_mapping--;
+ while ((s->c = vpeekc()) > 0
+ && (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
+ s->c = plain_vgetc();
+ if (!utf_iscomposing(s->c)) {
+ vungetc(s->c); // it wasn't, put it back
+ break;
+ } else if (s->ca.ncharC1 == 0) {
+ s->ca.ncharC1 = s->c;
+ } else {
+ s->ca.ncharC2 = s->c;
+ }
}
+ no_mapping++;
+ // Vim may be in a different mode when the user types the next key,
+ // but when replaying a recording the next key is already in the
+ // typeahead buffer, so record a <Nop> before that to prevent the
+ // vpeekc() above from applying wrong mappings when replaying.
+ no_u_sync++;
+ gotchars_nop();
+ no_u_sync--;
}
- no_mapping++;
}
no_mapping--;
allow_keys--;
@@ -874,8 +889,8 @@ static bool normal_get_command_count(NormalState *s)
if (s->c == K_DEL || s->c == K_KDEL) {
s->ca.count0 /= 10;
del_from_showcmd(4); // delete the digit and ~@%
- } else if (s->ca.count0 > 99999999L) {
- s->ca.count0 = 999999999L;
+ } else if (s->ca.count0 > 99999999) {
+ s->ca.count0 = 999999999;
} else {
s->ca.count0 = s->ca.count0 * 10 + (s->c - '0');
}
@@ -963,13 +978,16 @@ normal_end:
set_reg_var(get_default_register_name());
}
- // Reset finish_op, in case it was set
- s->c = finish_op;
- finish_op = false;
- may_trigger_modechanged();
+ const bool prev_finish_op = finish_op;
+ if (s->oa.op_type == OP_NOP) {
+ // Reset finish_op, in case it was set
+ finish_op = false;
+ may_trigger_modechanged();
+ }
// Redraw the cursor with another shape, if we were in Operator-pending
// mode or did a replace command.
- if (s->c || s->ca.cmdchar == 'r') {
+ if (prev_finish_op || s->ca.cmdchar == 'r'
+ || (s->ca.cmdchar == 'g' && s->ca.nchar == 'r')) {
ui_cursor_shape(); // may show different cursor shape
}
@@ -1010,7 +1028,7 @@ normal_end:
restart_VIsual_select = 0;
}
if (restart_edit != 0 && !VIsual_active && s->old_mapped_len == 0) {
- (void)edit(restart_edit, false, 1L);
+ (void)edit(restart_edit, false, 1);
}
}
@@ -1088,8 +1106,8 @@ static int normal_execute(VimState *state, int key)
// If you give a count before AND after the operator, they are
// multiplied.
if (s->ca.count0) {
- if (s->ca.opcount >= 999999999L / s->ca.count0) {
- s->ca.count0 = 999999999L;
+ if (s->ca.opcount >= 999999999 / s->ca.count0) {
+ s->ca.count0 = 999999999;
} else {
s->ca.count0 *= s->ca.opcount;
}
@@ -1168,7 +1186,7 @@ static int normal_execute(VimState *state, int key)
State = MODE_NORMAL;
- if (s->ca.nchar == ESC) {
+ if (s->ca.nchar == ESC || s->ca.extra_char == ESC) {
clearop(&s->oa);
s->command_finished = true;
goto finish;
@@ -1179,7 +1197,7 @@ static int normal_execute(VimState *state, int key)
msg_col = 0;
}
- s->old_pos = curwin->w_cursor; // remember where cursor was
+ s->old_pos = curwin->w_cursor; // remember where the cursor was
// When 'keymodel' contains "startsel" some keys start Select/Visual
// mode.
@@ -1260,9 +1278,11 @@ static void normal_check_cursor_moved(NormalState *s)
{
// Trigger CursorMoved if the cursor moved.
if (!finish_op && has_event(EVENT_CURSORMOVED)
- && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) {
+ && (last_cursormoved_win != curwin
+ || !equalpos(last_cursormoved, curwin->w_cursor))) {
apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
- curwin->w_last_cursormoved = curwin->w_cursor;
+ last_cursormoved_win = curwin;
+ last_cursormoved = curwin->w_cursor;
}
}
@@ -1286,6 +1306,13 @@ static void normal_check_buffer_modified(NormalState *s)
}
}
+/// If nothing is pending and we are going to wait for the user to
+/// type a character, trigger SafeState.
+static void normal_check_safe_state(NormalState *s)
+{
+ may_trigger_safestate(!op_pending() && restart_edit == 0);
+}
+
static void normal_check_folds(NormalState *s)
{
// Include a closed fold completely in the Visual area.
@@ -1311,16 +1338,20 @@ static void normal_redraw(NormalState *s)
update_topline(curwin);
validate_cursor();
+ show_cursor_info_later(false);
+
if (VIsual_active) {
redraw_curbuf_later(UPD_INVERTED); // update inverted part
- update_screen();
- } else if (must_redraw) {
- update_screen();
- } else if (redraw_cmdline || clear_cmdline || redraw_mode) {
- showmode();
}
- redraw_statuslines();
+ if (must_redraw) {
+ update_screen();
+ } else {
+ redraw_statuslines();
+ if (redraw_cmdline || clear_cmdline || redraw_mode) {
+ showmode();
+ }
+ }
if (need_maketitle) {
maketitle();
@@ -1338,7 +1369,7 @@ static void normal_redraw(NormalState *s)
// check for duplicates. Never put this message in
// history.
msg_hist_off = true;
- msg_attr((const char *)p, keep_msg_attr);
+ msg(p, keep_msg_attr);
msg_hist_off = false;
xfree(p);
}
@@ -1353,7 +1384,6 @@ static void normal_redraw(NormalState *s)
did_emsg = false;
msg_didany = false; // reset lines_left in msg_start()
may_clear_sb_text(); // clear scroll-back text on next msg
- show_cursor_info(false);
setcursor();
}
@@ -1375,11 +1405,14 @@ static int normal_check(VimState *state)
}
quit_more = false;
+ state_no_longer_safe(NULL);
+
// If skip redraw is set (for ":" in wait_return()), don't redraw now.
// If there is nothing in the stuff_buffer or do_redraw is true,
// update cursor and redraw.
if (skip_redraw || exmode_active) {
skip_redraw = false;
+ setcursor();
} else if (do_redraw || stuff_empty()) {
// Ensure curwin->w_topline and curwin->w_leftcol are up to date
// before triggering a WinScrolled autocommand.
@@ -1390,6 +1423,7 @@ static int normal_check(VimState *state)
normal_check_text_changed(s);
normal_check_window_scrolled(s);
normal_check_buffer_modified(s);
+ normal_check_safe_state(s);
// Updating diffs from changed() does not always work properly,
// esp. updating folds. Do an update just before redrawing if
@@ -1402,7 +1436,7 @@ static int normal_check(VimState *state)
// Scroll-binding for diff mode may have been postponed until
// here. Avoids doing it for every change.
if (diff_need_scrollbind) {
- check_scrollbind((linenr_T)0, 0L);
+ check_scrollbind(0, 0);
diff_need_scrollbind = false;
}
@@ -1455,7 +1489,7 @@ static int normal_check(VimState *state)
/// Set v:prevcount only when "set_prevcount" is true.
static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount)
{
- long count = cap->count0;
+ int64_t count = cap->count0;
// multiply with cap->opcount the same way as above
if (cap->opcount != 0) {
@@ -1597,7 +1631,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
// if i == 0: try to find an identifier
// if i == 1: try to find any non-white text
- char *ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ char *ptr = ml_get_buf(wp->w_buffer, lnum);
for (i = (find_type & FIND_IDENT) ? 0 : 1; i < 2; i++) {
// 1. skip to start of identifier/text
col = startcol;
@@ -1671,7 +1705,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
col = 0;
// Search for point of changing multibyte character class.
this_class = mb_get_class(ptr);
- while (ptr[col] != NUL // -V781
+ while (ptr[col] != NUL
&& ((i == 0
? mb_get_class(ptr + col) == this_class
: mb_get_class(ptr + col) != 0)
@@ -1694,13 +1728,13 @@ static void prep_redo_cmd(cmdarg_T *cap)
/// Prepare for redo of any command.
/// Note that only the last argument can be a multi-byte char.
-void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5)
+void prep_redo(int regname, int num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5)
{
- prep_redo_num2(regname, num, cmd1, cmd2, 0L, cmd3, cmd4, cmd5);
+ prep_redo_num2(regname, num, cmd1, cmd2, 0, cmd3, cmd4, cmd5);
}
/// Prepare for redo of any command with extra count after "cmd2".
-void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int cmd3, int cmd4,
+void prep_redo_num2(int regname, int num1, int cmd1, int cmd2, int num2, int cmd3, int cmd4,
int cmd5)
{
ResetRedobuff();
@@ -1731,9 +1765,9 @@ void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int c
}
}
-/// check for operator active and clear it
+/// Check for operator active and clear it.
///
-/// @return true if operator was active
+/// Beep and return true if an operator was active.
static bool checkclearop(oparg_T *oap)
{
if (oap->op_type == OP_NOP) {
@@ -1745,7 +1779,7 @@ static bool checkclearop(oparg_T *oap)
/// Check for operator or Visual active. Clear active operator.
///
-/// @return true if operator or Visual was active.
+/// Beep and return true if an operator or Visual was active.
static bool checkclearopq(oparg_T *oap)
{
if (oap->op_type == OP_NOP && !VIsual_active) {
@@ -1815,7 +1849,7 @@ void clear_showcmd(void)
if (VIsual_active && !char_avail()) {
int cursor_bot = lt(VIsual, curwin->w_cursor);
- long lines;
+ int lines;
colnr_T leftcol, rightcol;
linenr_T top, bot;
@@ -1837,8 +1871,8 @@ void clear_showcmd(void)
char *const saved_w_sbr = curwin->w_p_sbr;
// Make 'sbr' empty for a moment to get the correct size.
- p_sbr = empty_option;
- curwin->w_p_sbr = empty_option;
+ p_sbr = empty_string_option;
+ curwin->w_p_sbr = empty_string_option;
getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol);
p_sbr = saved_sbr;
curwin->w_p_sbr = saved_w_sbr;
@@ -1848,7 +1882,6 @@ void clear_showcmd(void)
snprintf(showcmd_buf, SHOWCMD_BUFLEN, "%" PRId64, (int64_t)lines);
} else {
char *s, *e;
- int l;
int bytes = 0;
int chars = 0;
@@ -1860,7 +1893,7 @@ void clear_showcmd(void)
e = ml_get_pos(&VIsual);
}
while ((*p_sel != 'e') ? s <= e : s < e) {
- l = utfc_ptr2len(s);
+ int l = utfc_ptr2len(s);
if (l == 0) {
bytes++;
chars++;
@@ -1957,13 +1990,11 @@ void add_to_showcmd_c(int c)
/// Delete 'len' characters from the end of the shown command.
static void del_from_showcmd(int len)
{
- int old_len;
-
if (!p_sc) {
return;
}
- old_len = (int)strlen(showcmd_buf);
+ int old_len = (int)strlen(showcmd_buf);
if (len > old_len) {
len = old_len;
}
@@ -2000,13 +2031,21 @@ static void display_showcmd(void)
showcmd_is_clear = (len == 0);
if (*p_sloc == 's') {
- win_redr_status(curwin);
- setcursor(); // put cursor back where it belongs
+ if (showcmd_is_clear) {
+ curwin->w_redr_status = true;
+ } else {
+ win_redr_status(curwin);
+ setcursor(); // put cursor back where it belongs
+ }
return;
}
if (*p_sloc == 't') {
- draw_tabline();
- setcursor(); // put cursor back where it belongs
+ if (showcmd_is_clear) {
+ redraw_tabline = true;
+ } else {
+ draw_tabline();
+ setcursor(); // put cursor back where it belongs
+ }
return;
}
// 'showcmdloc' is "last" or empty
@@ -2020,7 +2059,7 @@ static void display_showcmd(void)
if (len > 0) {
// placeholder for future highlight support
ADD_C(chunk, INTEGER_OBJ(0));
- ADD_C(chunk, STRING_OBJ(cstr_as_string(showcmd_buf)));
+ ADD_C(chunk, CSTR_AS_OBJ(showcmd_buf));
ADD_C(content, ARRAY_OBJ(chunk));
}
ui_call_msg_showcmd(content);
@@ -2029,18 +2068,16 @@ static void display_showcmd(void)
msg_grid_validate();
int showcmd_row = Rows - 1;
- grid_puts_line_start(&msg_grid_adj, showcmd_row);
+ grid_line_start(&msg_grid_adj, showcmd_row);
if (!showcmd_is_clear) {
- grid_puts(&msg_grid_adj, showcmd_buf, showcmd_row, sc_col,
- HL_ATTR(HLF_MSG));
+ grid_line_puts(sc_col, showcmd_buf, -1, HL_ATTR(HLF_MSG));
}
// clear the rest of an old message by outputting up to SHOWCMD_COLS spaces
- grid_puts(&msg_grid_adj, (char *)" " + len, showcmd_row,
- sc_col + len, HL_ATTR(HLF_MSG));
+ grid_line_puts(sc_col + len, (char *)" " + len, -1, HL_ATTR(HLF_MSG));
- grid_puts_line_flush(false);
+ grid_line_flush();
}
/// When "check" is false, prepare for commands that scroll the window.
@@ -2069,8 +2106,7 @@ void do_check_scrollbind(bool check)
&& (curwin->w_topline != old_topline
|| curwin->w_topfill != old_topfill
|| curwin->w_leftcol != old_leftcol)) {
- check_scrollbind(curwin->w_topline - old_topline,
- (long)(curwin->w_leftcol - old_leftcol));
+ check_scrollbind(curwin->w_topline - old_topline, curwin->w_leftcol - old_leftcol);
}
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
// When switching between windows, make sure that the relative
@@ -2081,7 +2117,7 @@ void do_check_scrollbind(bool check)
// resync is performed, some of the other 'scrollbind' windows may
// need to jump so that the current window's relative position is
// visible on-screen.
- check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0L);
+ check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0);
}
curwin->w_scbind_pos = curwin->w_topline;
}
@@ -2096,22 +2132,20 @@ void do_check_scrollbind(bool check)
/// Synchronize any windows that have "scrollbind" set, based on the
/// number of rows by which the current window has changed
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
-void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
+void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
{
- bool want_ver;
- bool want_hor;
win_T *old_curwin = curwin;
buf_T *old_curbuf = curbuf;
int old_VIsual_select = VIsual_select;
int old_VIsual_active = VIsual_active;
colnr_T tgt_leftcol = curwin->w_leftcol;
- long topline;
- long y;
+ linenr_T topline;
+ linenr_T y;
// check 'scrollopt' string for vertical and horizontal scroll options
- want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
+ bool want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
want_ver |= old_curwin->w_p_diff;
- want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
+ bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
// loop through the scrollbound windows and scroll accordingly
VIsual_select = VIsual_active = 0;
@@ -2129,7 +2163,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
diff_set_topline(old_curwin, curwin);
} else {
curwin->w_scbind_pos += topline_diff;
- topline = curwin->w_scbind_pos;
+ topline = (linenr_T)curwin->w_scbind_pos;
if (topline > curbuf->b_ml.ml_line_count) {
topline = curbuf->b_ml.ml_line_count;
}
@@ -2151,9 +2185,8 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
}
// do the horizontal scroll
- if (want_hor && curwin->w_leftcol != tgt_leftcol) {
- curwin->w_leftcol = tgt_leftcol;
- leftcol_changed();
+ if (want_hor) {
+ (void)set_leftcol(tgt_leftcol);
}
}
@@ -2175,7 +2208,8 @@ static void nv_ignore(cmdarg_T *cap)
/// Command character that doesn't do anything, but unlike nv_ignore() does
/// start edit(). Used for "startinsert" executed while starting up.
static void nv_nop(cmdarg_T *cap)
-{}
+{
+}
/// Command character doesn't exist.
static void nv_error(cmdarg_T *cap)
@@ -2199,7 +2233,7 @@ static void nv_addsub(cmdarg_T *cap)
} else if (!VIsual_active && cap->oap->op_type == OP_NOP) {
prep_redo_cmd(cap);
cap->oap->op_type = cap->cmdchar == Ctrl_A ? OP_NR_ADD : OP_NR_SUB;
- op_addsub(cap->oap, (linenr_T)cap->count1, cap->arg);
+ op_addsub(cap->oap, cap->count1, cap->arg);
cap->oap->op_type = OP_NOP;
} else if (VIsual_active) {
nv_operator(cap);
@@ -2218,9 +2252,9 @@ static void nv_page(cmdarg_T *cap)
if (mod_mask & MOD_MASK_CTRL) {
// <C-PageUp>: tab page back; <C-PageDown>: tab page forward
if (cap->arg == BACKWARD) {
- goto_tabpage(-(int)cap->count1);
+ goto_tabpage(-cap->count1);
} else {
- goto_tabpage((int)cap->count0);
+ goto_tabpage(cap->count0);
}
} else {
(void)onepage(cap->arg, cap->count1);
@@ -2292,34 +2326,30 @@ static bool is_ident(const char *line, int offset)
/// @return fail when not found.
bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_arg)
{
- char *pat;
- pos_T old_pos;
pos_T par_pos;
pos_T found_pos;
bool t;
- bool save_p_ws;
- bool save_p_scs;
bool retval = true;
bool incll;
int searchflags = flags_arg;
- pat = xmalloc(len + 7);
+ size_t patlen = len + 7;
+ char *pat = xmalloc(patlen);
// Put "\V" before the pattern to avoid that the special meaning of "."
// and "~" causes trouble.
- assert(len <= INT_MAX);
- sprintf(pat, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", // NOLINT(runtime/printf)
- (int)len, ptr);
- old_pos = curwin->w_cursor;
- save_p_ws = p_ws;
- save_p_scs = p_scs;
+ assert(patlen <= INT_MAX);
+ snprintf(pat, patlen, vim_iswordp(ptr) ? "\\V\\<%.*s\\>" : "\\V%.*s", (int)len, ptr);
+ pos_T old_pos = curwin->w_cursor;
+ bool save_p_ws = p_ws;
+ bool save_p_scs = p_scs;
p_ws = false; // don't wrap around end of file now
p_scs = false; // don't switch ignorecase off now
// With "gD" go to line 1.
// With "gd" Search back for the start of the current function, then go
// back until a blank line. If this fails go to line 1.
- if (!locally || !findpar(&incll, BACKWARD, 1L, '{', false)) {
+ if (!locally || !findpar(&incll, BACKWARD, 1, '{', false)) {
setpcmark(); // Set in findpar() otherwise
curwin->w_cursor.lnum = 1;
par_pos = curwin->w_cursor;
@@ -2334,9 +2364,9 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
// Search forward for the identifier, ignore comment lines.
clearpos(&found_pos);
- for (;;) {
+ while (true) {
t = searchit(curwin, curbuf, &curwin->w_cursor, NULL, FORWARD,
- pat, 1L, searchflags, RE_LAST, NULL);
+ pat, 1, searchflags, RE_LAST, NULL);
if (curwin->w_cursor.lnum >= old_pos.lnum) {
t = false; // match after start is failure too
}
@@ -2422,12 +2452,11 @@ bool find_decl(char *ptr, size_t len, bool locally, bool thisblock, int flags_ar
/// 'dist' must be positive.
///
/// @return true if able to move cursor, false otherwise.
-static bool nv_screengo(oparg_T *oap, int dir, long dist)
+static bool nv_screengo(oparg_T *oap, int dir, int dist)
{
- int linelen = linetabsize(get_cursor_line_ptr());
+ int linelen = linetabsize(curwin, curwin->w_cursor.lnum);
bool retval = true;
bool atend = false;
- int n;
int col_off1; // margin offset for first screen line
int col_off2; // margin offset for wrapped screen line
int width1; // text width for first screen line
@@ -2446,6 +2475,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
}
if (curwin->w_width_inner != 0) {
+ int n;
// Instead of sticking at the last character of the buffer line we
// try to stick in the last column of the screen.
if (curwin->w_curswant == MAXCOL) {
@@ -2482,20 +2512,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
curwin->w_curswant -= width2;
} else {
// to previous line
-
- // Move to the start of a closed fold. Don't do that when
- // 'foldopen' contains "all": it will open in a moment.
- if (!(fdo_flags & FDO_ALL)) {
- (void)hasFolding(curwin->w_cursor.lnum,
- &curwin->w_cursor.lnum, NULL);
- }
- if (curwin->w_cursor.lnum == 1) {
+ if (curwin->w_cursor.lnum <= 1) {
retval = false;
break;
}
- curwin->w_cursor.lnum--;
+ cursor_up_inner(curwin, 1);
- linelen = linetabsize(get_cursor_line_ptr());
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
if (linelen > width1) {
int w = (((linelen - width1 - 1) / width2) + 1) * width2;
assert(curwin->w_curswant <= INT_MAX - w);
@@ -2514,16 +2537,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
curwin->w_curswant += width2;
} else {
// to next line
-
- // Move to the end of a closed fold.
- (void)hasFolding(curwin->w_cursor.lnum, NULL,
- &curwin->w_cursor.lnum);
- if (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
+ if (curwin->w_cursor.lnum >= curwin->w_buffer->b_ml.ml_line_count) {
retval = false;
break;
}
- curwin->w_cursor.lnum++;
+ cursor_down_inner(curwin, 1);
curwin->w_curswant %= width2;
+
// Check if the cursor has moved below the number display
// when width1 < width2 (with cpoptions+=n). Subtract width2
// to get a negative value for w_curswant, which will get
@@ -2531,7 +2551,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
if (curwin->w_curswant >= width1) {
curwin->w_curswant -= width2;
}
- linelen = linetabsize(get_cursor_line_ptr());
+ linelen = linetabsize(curwin, curwin->w_cursor.lnum);
}
}
}
@@ -2572,63 +2592,14 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist)
if (atend) {
curwin->w_curswant = MAXCOL; // stick in the last column
}
- return retval;
-}
-
-/// Mouse scroll wheel: Default action is to scroll three lines, or one page
-/// when Shift or Ctrl is used.
-/// K_MOUSEUP (cap->arg == 1) or K_MOUSEDOWN (cap->arg == 0) or
-/// K_MOUSELEFT (cap->arg == -1) or K_MOUSERIGHT (cap->arg == -2)
-static void nv_mousescroll(cmdarg_T *cap)
-{
- win_T *old_curwin = curwin;
-
- if (mouse_row >= 0 && mouse_col >= 0) {
- int grid, row, col;
-
- grid = mouse_grid;
- row = mouse_row;
- col = mouse_col;
-
- // find the window at the pointer coordinates
- win_T *wp = mouse_find_win(&grid, &row, &col);
- if (wp == NULL) {
- return;
- }
- curwin = wp;
- curbuf = curwin->w_buffer;
- }
+ adjust_skipcol();
- if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) {
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L);
- } else if (p_mousescroll_vert > 0) {
- cap->count1 = p_mousescroll_vert;
- cap->count0 = p_mousescroll_vert;
- nv_scroll_line(cap);
- }
- } else {
- mouse_scroll_horiz(cap->arg);
- }
- if (curwin != old_curwin && curwin->w_p_cul) {
- redraw_for_cursorline(curwin);
- }
-
- curwin->w_redr_status = true;
-
- curwin = old_curwin;
- curbuf = curwin->w_buffer;
-}
-
-/// Mouse clicks and drags.
-static void nv_mouse(cmdarg_T *cap)
-{
- (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0);
+ return retval;
}
/// Handle CTRL-E and CTRL-Y commands: scroll a line up or down.
/// cap->arg must be true for CTRL-E.
-static void nv_scroll_line(cmdarg_T *cap)
+void nv_scroll_line(cmdarg_T *cap)
{
if (!checkclearop(cap->oap)) {
scroll_redraw(cap->arg, cap->count1);
@@ -2636,17 +2607,18 @@ static void nv_scroll_line(cmdarg_T *cap)
}
/// Scroll "count" lines up or down, and redraw.
-void scroll_redraw(int up, long count)
+void scroll_redraw(int up, linenr_T count)
{
linenr_T prev_topline = curwin->w_topline;
+ int prev_skipcol = curwin->w_skipcol;
int prev_topfill = curwin->w_topfill;
linenr_T prev_lnum = curwin->w_cursor.lnum;
- bool moved = up ?
- scrollup(count, true) :
- scrolldown(count, true);
+ bool moved = up
+ ? scrollup(count, true)
+ : scrolldown(count, true);
- if (get_scrolloff_value(curwin)) {
+ if (get_scrolloff_value(curwin) > 0) {
// Adjust the cursor position for 'scrolloff'. Mark w_topline as
// valid, otherwise the screen jumps back at the end of the file.
cursor_correct();
@@ -2657,16 +2629,17 @@ void scroll_redraw(int up, long count)
// we get stuck at one position. Don't move the cursor up if the
// first line of the buffer is already on the screen
while (curwin->w_topline == prev_topline
+ && curwin->w_skipcol == prev_skipcol
&& curwin->w_topfill == prev_topfill) {
if (up) {
if (curwin->w_cursor.lnum > prev_lnum
- || cursor_down(1L, false) == false) {
+ || cursor_down(1, false) == false) {
break;
}
} else {
if (curwin->w_cursor.lnum < prev_lnum
- || prev_topline == 1L
- || cursor_up(1L, false) == false) {
+ || prev_topline == 1
+ || cursor_up(1, false) == false) {
break;
}
}
@@ -2696,9 +2669,9 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg)
if (checkclearop(cap->oap)) {
return false;
}
- long n = nchar - '0';
+ int n = nchar - '0';
- for (;;) {
+ while (true) {
no_mapping++;
allow_keys++; // no mapping for nchar, but allow key codes
nchar = plain_vgetc();
@@ -2710,9 +2683,13 @@ static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg)
if (nchar == K_DEL || nchar == K_KDEL) {
n /= 10;
} else if (ascii_isdigit(nchar)) {
+ if (n > INT_MAX / 10) {
+ clearopbeep(cap->oap);
+ break;
+ }
n = n * 10 + (nchar - '0');
} else if (nchar == CAR) {
- win_setheight((int)n);
+ win_setheight(n);
break;
} else if (nchar == 'l'
|| nchar == 'h'
@@ -2784,7 +2761,7 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
assert(len <= INT_MAX);
spell_add_word(ptr, (int)len,
nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD,
- (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1,
+ (nchar == 'G' || nchar == 'W') ? 0 : cap->count1,
undo);
return OK;
@@ -2793,13 +2770,12 @@ static int nv_zg_zw(cmdarg_T *cap, int nchar)
/// Commands that start with "z".
static void nv_zet(cmdarg_T *cap)
{
- int n;
colnr_T col;
int nchar = cap->nchar;
- long old_fdl = curwin->w_p_fdl;
+ int old_fdl = (int)curwin->w_p_fdl;
int old_fen = curwin->w_p_fen;
- int siso = (int)get_sidescrolloff_value(curwin);
+ int siso = get_sidescrolloff_value(curwin);
if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) {
return;
@@ -2824,7 +2800,7 @@ static void nv_zet(cmdarg_T *cap)
if (cap->count0 > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
} else {
- curwin->w_cursor.lnum = (linenr_T)cap->count0;
+ curwin->w_cursor.lnum = cap->count0;
}
check_cursor_col();
}
@@ -2860,7 +2836,7 @@ static void nv_zet(cmdarg_T *cap)
FALLTHROUGH;
case 'z':
- scroll_cursor_halfway(true);
+ scroll_cursor_halfway(true, false);
redraw_later(curwin, UPD_VALID);
set_fraction(curwin);
break;
@@ -2897,27 +2873,21 @@ static void nv_zet(cmdarg_T *cap)
case 'h':
case K_LEFT:
if (!curwin->w_p_wrap) {
- if ((colnr_T)cap->count1 > curwin->w_leftcol) {
- curwin->w_leftcol = 0;
- } else {
- curwin->w_leftcol -= (colnr_T)cap->count1;
- }
- leftcol_changed();
+ (void)set_leftcol((colnr_T)cap->count1 > curwin->w_leftcol
+ ? 0 : curwin->w_leftcol - (colnr_T)cap->count1);
}
break;
- // "zL" - scroll screen left half-page
+ // "zL" - scroll window left half-page
case 'L':
cap->count1 *= curwin->w_width_inner / 2;
FALLTHROUGH;
- // "zl" - scroll screen to the left
+ // "zl" - scroll window to the left if not wrapping
case 'l':
case K_RIGHT:
if (!curwin->w_p_wrap) {
- // scroll the window left
- curwin->w_leftcol += (colnr_T)cap->count1;
- leftcol_changed();
+ (void)set_leftcol(curwin->w_leftcol + (colnr_T)cap->count1);
}
break;
@@ -2949,7 +2919,7 @@ static void nv_zet(cmdarg_T *cap)
} else {
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col);
}
- n = curwin->w_width_inner - curwin_col_off();
+ int n = curwin->w_width_inner - curwin_col_off();
if (col + siso < n) {
col = 0;
} else {
@@ -3011,7 +2981,7 @@ static void nv_zet(cmdarg_T *cap)
clearFolding(curwin);
changed_window_setting();
} else if (foldmethodIsMarker(curwin)) {
- deleteFold(curwin, (linenr_T)1, curbuf->b_ml.ml_line_count, true, false);
+ deleteFold(curwin, 1, curbuf->b_ml.ml_line_count, true, false);
} else {
emsg(_("E352: Cannot erase folds with current 'foldmethod'"));
}
@@ -3165,7 +3135,7 @@ static void nv_zet(cmdarg_T *cap)
case '=': // "z=": suggestions for a badly spelled word
if (!checkclearop(cap->oap)) {
- spell_suggest((int)cap->count0);
+ spell_suggest(cap->count0);
}
break;
@@ -3230,7 +3200,7 @@ static void nv_colon(cmdarg_T *cap)
stuffcharReadbuff('.');
if (cap->count0 > 1) {
stuffReadbuff(",.+");
- stuffnumReadbuff(cap->count0 - 1L);
+ stuffnumReadbuff(cap->count0 - 1);
}
}
@@ -3240,7 +3210,7 @@ static void nv_colon(cmdarg_T *cap)
}
if (is_lua) {
- cmd_result = map_execute_lua();
+ cmd_result = map_execute_lua(true);
} else {
// get a command line and execute it
cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
@@ -3269,7 +3239,7 @@ static void nv_ctrlg(cmdarg_T *cap)
showmode();
} else if (!checkclearop(cap->oap)) {
// print full name if count given or :cd used
- fileinfo((int)cap->count0, false, true);
+ fileinfo(cap->count0, false, true);
}
}
@@ -3319,7 +3289,7 @@ static void nv_ctrlo(cmdarg_T *cap)
static void nv_hat(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
- (void)buflist_getfile((int)cap->count0, (linenr_T)0,
+ (void)buflist_getfile(cap->count0, 0,
GETF_SETMARK|GETF_ALT, false);
}
}
@@ -3438,7 +3408,6 @@ static void nv_ident(cmdarg_T *cap)
int cmdchar;
bool g_cmd; // "g" command
bool tag_cmd = false;
- char *aux_ptr;
if (cap->cmdchar == 'g') { // "g*", "g#", "g]" and "gCTRL-]"
cmdchar = cap->nchar;
@@ -3531,7 +3500,7 @@ static void nv_ident(cmdarg_T *cap)
ptr = xstrnsave(ptr, n);
if (kp_ex) {
// Escape the argument properly for an Ex command
- p = vim_strsave_fnameescape((const char *)ptr, VSE_NONE);
+ p = vim_strsave_fnameescape(ptr, VSE_NONE);
} else {
// Escape the argument properly for a shell command
p = vim_strsave_shellescape(ptr, true, true);
@@ -3542,6 +3511,7 @@ static void nv_ident(cmdarg_T *cap)
STRCAT(buf, p);
xfree(p);
} else {
+ char *aux_ptr;
if (cmdchar == '*') {
aux_ptr = (magic_isset() ? "/.*~[^$\\" : "/^$\\");
} else if (cmdchar == '#') {
@@ -3646,17 +3616,15 @@ bool get_visual_text(cmdarg_T *cap, char **pp, size_t *lenp)
static void nv_tagpop(cmdarg_T *cap)
{
if (!checkclearopq(cap->oap)) {
- do_tag("", DT_POP, (int)cap->count1, false, true);
+ do_tag("", DT_POP, cap->count1, false, true);
}
}
/// Handle scrolling command 'H', 'L' and 'M'.
static void nv_scroll(cmdarg_T *cap)
{
- int used = 0;
- long n;
+ int n;
linenr_T lnum;
- int half;
cap->oap->motion_type = kMTLineWise;
setpcmark();
@@ -3678,28 +3646,29 @@ static void nv_scroll(cmdarg_T *cap)
}
}
} else {
- curwin->w_cursor.lnum -= (linenr_T)cap->count1 - 1;
+ curwin->w_cursor.lnum -= cap->count1 - 1;
}
}
} else {
if (cap->cmdchar == 'M') {
+ int used = 0;
// Don't count filler lines above the window.
used -= win_get_fill(curwin, curwin->w_topline)
- curwin->w_topfill;
validate_botline(curwin); // make sure w_empty_rows is valid
- half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
+ int half = (curwin->w_height_inner - curwin->w_empty_rows + 1) / 2;
for (n = 0; curwin->w_topline + n < curbuf->b_ml.ml_line_count; n++) {
// Count half the number of filler lines to be "below this
// line" and half to be "above the next line".
- if (n > 0 && used + win_get_fill(curwin, curwin->w_topline + (linenr_T)n) / 2 >= half) {
+ if (n > 0 && used + win_get_fill(curwin, curwin->w_topline + n) / 2 >= half) {
n--;
break;
}
- used += plines_win(curwin, curwin->w_topline + (linenr_T)n, true);
+ used += plines_win(curwin, curwin->w_topline + n, true);
if (used >= half) {
break;
}
- if (hasFolding(curwin->w_topline + (linenr_T)n, NULL, &lnum)) {
+ if (hasFolding(curwin->w_topline + n, NULL, &lnum)) {
n = lnum - curwin->w_topline;
}
}
@@ -3718,7 +3687,7 @@ static void nv_scroll(cmdarg_T *cap)
n = lnum - curwin->w_topline;
}
}
- curwin->w_cursor.lnum = curwin->w_topline + (linenr_T)n;
+ curwin->w_cursor.lnum = curwin->w_topline + n;
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
}
@@ -3734,7 +3703,7 @@ static void nv_scroll(cmdarg_T *cap)
/// Cursor right commands.
static void nv_right(cmdarg_T *cap)
{
- long n;
+ int n;
if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
// <C-Right> and <S-Right> move a word or WORD right
@@ -3812,7 +3781,7 @@ static void nv_right(cmdarg_T *cap)
/// @return true when operator end should not be adjusted.
static void nv_left(cmdarg_T *cap)
{
- long n;
+ int n;
if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
// <C-Left> and <S-Left> move a word or WORD left
@@ -3921,14 +3890,13 @@ static void nv_down(cmdarg_T *cap)
/// Grab the file name under the cursor and edit it.
static void nv_gotofile(cmdarg_T *cap)
{
- char *ptr;
linenr_T lnum = -1;
if (check_text_or_curbuf_locked(cap->oap)) {
return;
}
- ptr = grab_file_name(cap->count1, &lnum);
+ char *ptr = grab_file_name(cap->count1, &lnum);
if (ptr != NULL) {
// do autowrite if necessary
@@ -3940,7 +3908,7 @@ static void nv_gotofile(cmdarg_T *cap)
buf_hide(curbuf) ? ECMD_HIDE : 0, curwin) == OK
&& cap->nchar == 'F' && lnum >= 0) {
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_SOL | BL_FIX);
}
xfree(ptr);
@@ -4113,9 +4081,8 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
pos_T new_pos = { 0, 0, 0 };
pos_T *pos = NULL; // init for GCC
pos_T prev_pos;
- long n;
+ int n;
int findc;
- int c;
if (cap->nchar == '*') {
cap->nchar = '/';
@@ -4155,6 +4122,7 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
// Try finding the '{' or '}' we want to be at.
// Also repeat for the given count.
if (cap->nchar == 'm' || cap->nchar == 'M') {
+ int c;
// norm is true for "]M" and "[m"
int norm = ((findc == '{') == (cap->nchar == 'm'));
@@ -4170,7 +4138,7 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
pos = NULL;
}
while (n > 0) {
- for (;;) {
+ while (true) {
if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) {
// if not found anything, that's an error
if (pos == NULL) {
@@ -4225,13 +4193,12 @@ static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos)
/// cap->arg is BACKWARD for "[" and FORWARD for "]".
static void nv_brackets(cmdarg_T *cap)
{
- pos_T old_pos; // cursor position before command
int flag;
- long n;
+ int n;
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
- old_pos = curwin->w_cursor;
+ pos_T old_pos = curwin->w_cursor; // cursor position before command
curwin->w_cursor.coladd = 0; // TODO(Unknown): don't do this for an error.
// "[f" or "]f" : Edit file under the cursor (same as "gf")
@@ -4252,19 +4219,19 @@ static void nv_brackets(cmdarg_T *cap)
clearop(cap->oap);
} else {
// Make a copy, if the line was changed it will be freed.
- ptr = xstrnsave(ptr, len);
+ ptr = xmemdupz(ptr, len);
find_pattern_in_path(ptr, 0, len, true,
cap->count0 == 0 ? !isupper(cap->nchar) : false,
(((cap->nchar & 0xf) == ('d' & 0xf))
? FIND_DEFINE
: FIND_ANY),
cap->count1,
- (isupper(cap->nchar) ? ACTION_SHOW_ALL :
- islower(cap->nchar) ? ACTION_SHOW :
- ACTION_GOTO),
+ (isupper(cap->nchar) ? ACTION_SHOW_ALL
+ : islower(cap->nchar) ? ACTION_SHOW
+ : ACTION_GOTO),
(cap->cmdchar == ']'
? curwin->w_cursor.lnum + 1
- : (linenr_T)1),
+ : 1),
MAXLNUM);
xfree(ptr);
curwin->w_set_curswant = true;
@@ -4318,7 +4285,7 @@ static void nv_brackets(cmdarg_T *cap)
fm = prev_fm;
}
MarkMove flags = kMarkContext;
- flags |= cap->nchar == '\'' ? kMarkBeginLine: 0;
+ flags |= cap->nchar == '\'' ? kMarkBeginLine : 0;
nv_mark_move_to(cap, flags, fm);
} else if (cap->nchar >= K_RIGHTRELEASE && cap->nchar <= K_LEFTMOUSE) {
// [ or ] followed by a middle mouse click: put selected text with
@@ -4361,7 +4328,6 @@ static void nv_brackets(cmdarg_T *cap)
/// Handle Normal mode "%" command.
static void nv_percent(cmdarg_T *cap)
{
- pos_T *pos;
linenr_T lnum = curwin->w_cursor.lnum;
cap->oap->inclusive = true;
@@ -4377,10 +4343,10 @@ static void nv_percent(cmdarg_T *cap)
// to avoid overflows.
if (curbuf->b_ml.ml_line_count >= 21474836) {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count + 99)
- / 100 * (linenr_T)cap->count0;
+ / 100 * cap->count0;
} else {
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count *
- (linenr_T)cap->count0 + 99) / 100;
+ cap->count0 + 99) / 100;
}
if (curwin->w_cursor.lnum < 1) {
curwin->w_cursor.lnum = 1;
@@ -4391,6 +4357,7 @@ static void nv_percent(cmdarg_T *cap)
beginline(BL_SOL | BL_FIX);
}
} else { // "%" : go to matching paren
+ pos_T *pos;
cap->oap->motion_type = kMTCharWise;
cap->oap->use_reg_one = true;
if ((pos = findmatch(cap->oap, NUL)) == NULL) {
@@ -4490,14 +4457,13 @@ static void nv_kundo(cmdarg_T *cap)
clearopbeep(cap->oap);
return;
}
- u_undo((int)cap->count1);
+ u_undo(cap->count1);
curwin->w_set_curswant = true;
}
/// Handle the "r" command.
static void nv_replace(cmdarg_T *cap)
{
- char *ptr;
int had_ctrl_v;
if (checkclearop(cap->oap)) {
@@ -4509,7 +4475,7 @@ static void nv_replace(cmdarg_T *cap)
}
// get another character
- if (cap->nchar == Ctrl_V) {
+ if (cap->nchar == Ctrl_V || cap->nchar == Ctrl_Q) {
had_ctrl_v = Ctrl_V;
cap->nchar = get_literal(false);
// Don't redo a multibyte character with CTRL-V.
@@ -4529,7 +4495,7 @@ static void nv_replace(cmdarg_T *cap)
// Visual mode "r"
if (VIsual_active) {
if (got_int) {
- reset_VIsual();
+ got_int = false;
}
if (had_ctrl_v) {
// Use a special (negative) number to make a difference between a
@@ -4560,7 +4526,7 @@ static void nv_replace(cmdarg_T *cap)
}
// Abort if not enough characters to replace.
- ptr = get_cursor_pos_ptr();
+ char *ptr = get_cursor_pos_ptr();
if (strlen(ptr) < (unsigned)cap->count1
|| (mb_charlen(ptr) < cap->count1)) {
clearopbeep(cap->oap);
@@ -4614,7 +4580,7 @@ static void nv_replace(cmdarg_T *cap)
// This is slow, but it handles replacing a single-byte with a
// multi-byte and the other way around. Also handles adding
// composing characters for utf-8.
- for (long n = cap->count1; n > 0; n--) {
+ for (int n = cap->count1; n > 0; n--) {
State = MODE_REPLACE;
if (cap->nchar == Ctrl_E || cap->nchar == Ctrl_Y) {
int c = ins_copychar(curwin->w_cursor.lnum
@@ -4652,11 +4618,10 @@ static void nv_replace(cmdarg_T *cap)
/// 'O': same, but in block mode exchange left and right corners.
static void v_swap_corners(int cmdchar)
{
- pos_T old_cursor;
colnr_T left, right;
if (cmdchar == 'O' && VIsual_mode == Ctrl_V) {
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
getvcols(curwin, &old_cursor, &VIsual, &left, &right);
curwin->w_cursor.lnum = VIsual.lnum;
coladvance(left);
@@ -4686,7 +4651,7 @@ static void v_swap_corners(int cmdchar)
curwin->w_curswant = left;
}
} else {
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
curwin->w_cursor = VIsual;
VIsual = old_cursor;
curwin->w_set_curswant = true;
@@ -4736,9 +4701,15 @@ static void nv_vreplace(cmdarg_T *cap)
if (!MODIFIABLE(curbuf)) {
emsg(_(e_modifiable));
} else {
- if (cap->extra_char == Ctrl_V) { // get another character
+ if (cap->extra_char == Ctrl_V || cap->extra_char == Ctrl_Q) {
+ // get another character
cap->extra_char = get_literal(false);
}
+ if (cap->extra_char < ' ') {
+ // Prefix a control character with CTRL-V to avoid it being used as
+ // a command.
+ stuffcharReadbuff(Ctrl_V);
+ }
stuffcharReadbuff(cap->extra_char);
stuffcharReadbuff(ESC);
if (virtual_active()) {
@@ -4751,8 +4722,6 @@ static void nv_vreplace(cmdarg_T *cap)
/// Swap case for "~" command, when it does not work like an operator.
static void n_swapchar(cmdarg_T *cap)
{
- long n;
- pos_T startpos;
int did_change = 0;
if (checkclearopq(cap->oap)) {
@@ -4770,8 +4739,8 @@ static void n_swapchar(cmdarg_T *cap)
return;
}
- startpos = curwin->w_cursor;
- for (n = cap->count1; n > 0; n--) {
+ pos_T startpos = curwin->w_cursor;
+ for (int n = cap->count1; n > 0; n--) {
did_change |= swapchar(cap->oap->op_type, &curwin->w_cursor);
inc_cursor();
if (gchar_cursor() == NUL) {
@@ -4783,7 +4752,7 @@ static void n_swapchar(cmdarg_T *cap)
if (u_savesub(curwin->w_cursor.lnum) == false) {
break;
}
- u_clearline();
+ u_clearline(curbuf);
}
} else {
break;
@@ -4794,8 +4763,8 @@ static void n_swapchar(cmdarg_T *cap)
check_cursor();
curwin->w_set_curswant = true;
if (did_change) {
- changed_lines(startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1,
- 0L, true);
+ changed_lines(curbuf, startpos.lnum, startpos.col, curwin->w_cursor.lnum + 1,
+ 0, true);
curbuf->b_op_start = startpos;
curbuf->b_op_end = curwin->w_cursor;
if (curbuf->b_op_end.col > 0) {
@@ -4957,9 +4926,9 @@ static void nv_pcmark(cmdarg_T *cap)
}
if (cap->cmdchar == 'g') {
- fm = get_changelist(curbuf, curwin, (int)cap->count1);
+ fm = get_changelist(curbuf, curwin, cap->count1);
} else {
- fm = get_jumplist(curwin, (int)cap->count1);
+ fm = get_jumplist(curwin, cap->count1);
flags |= KMarkNoContext | kMarkJumpList;
}
// Changelist and jumplist have their own error messages. Therefore avoid
@@ -4969,7 +4938,7 @@ static void nv_pcmark(cmdarg_T *cap)
move_res = nv_mark_move_to(cap, flags, fm);
} else if (cap->cmdchar == 'g') {
if (curbuf->b_changelistlen == 0) {
- emsg(_("E664: changelist is empty"));
+ emsg(_(e_changelist_is_empty));
} else if (cap->count1 < 0) {
emsg(_("E662: At start of changelist"));
} else {
@@ -5051,7 +5020,7 @@ static void nv_visual(cmdarg_T *cap)
// For V and ^V, we multiply the number of lines even if there
// was only one -- webb
if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) {
- curwin->w_cursor.lnum += resel_VIsual_line_count * (linenr_T)cap->count0 - 1;
+ curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1;
check_cursor();
}
VIsual_mode = resel_VIsual_mode;
@@ -5059,7 +5028,7 @@ static void nv_visual(cmdarg_T *cap)
if (resel_VIsual_line_count <= 1) {
update_curswant_force();
assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
- curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0;
+ curwin->w_curswant += resel_VIsual_vcol * cap->count0;
if (*p_sel != 'e') {
curwin->w_curswant--;
}
@@ -5077,7 +5046,7 @@ static void nv_visual(cmdarg_T *cap)
curwin->w_cursor.lnum = VIsual.lnum;
update_curswant_force();
assert(cap->count0 >= INT_MIN && cap->count0 <= INT_MAX);
- curwin->w_curswant += resel_VIsual_vcol * (int)cap->count0 - 1;
+ curwin->w_curswant += resel_VIsual_vcol * cap->count0 - 1;
curwin->w_cursor.lnum = lnum;
coladvance(curwin->w_curswant);
} else {
@@ -5272,10 +5241,11 @@ static void nv_g_home_m_cmd(cmdarg_T *cap)
if (flag) {
do {
i = gchar_cursor();
- } while (ascii_iswhite(i) && oneright());
+ } while (ascii_iswhite(i) && oneright() == OK);
curwin->w_valid &= ~VALID_WCOL;
}
curwin->w_set_curswant = true;
+ adjust_skipcol();
}
/// "g_": to the last non-blank character in the line or <count> lines downward.
@@ -5310,6 +5280,7 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
oparg_T *oap = cap->oap;
int i;
int col_off = curwin_col_off();
+ const bool flag = cap->nchar == K_END || cap->nchar == K_KEND;
oap->motion_type = kMTCharWise;
oap->inclusive = true;
@@ -5348,11 +5319,11 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
coladvance((colnr_T)i);
// if the character doesn't fit move one back
- if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) {
+ if (curwin->w_cursor.col > 0 && utf_ptr2cells(get_cursor_pos_ptr()) > 1) {
colnr_T vcol;
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) {
+ if (vcol >= curwin->w_leftcol + curwin->w_width_inner - col_off) {
curwin->w_cursor.col--;
}
}
@@ -5360,6 +5331,12 @@ static void nv_g_dollar_cmd(cmdarg_T *cap)
// Make sure we stick in this column.
update_curswant_force();
}
+ if (flag) {
+ do {
+ i = gchar_cursor();
+ } while (ascii_iswhite(i) && oneleft() == OK);
+ curwin->w_valid &= ~VALID_WCOL;
+ }
}
/// "gi": start Insert at the last position.
@@ -5367,7 +5344,7 @@ static void nv_gi_cmd(cmdarg_T *cap)
{
if (curbuf->b_last_insert.mark.lnum != 0) {
curwin->w_cursor = curbuf->b_last_insert.mark;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
int i = (int)strlen(get_cursor_line_ptr());
if (curwin->w_cursor.col > (colnr_T)i) {
if (virtual_active()) {
@@ -5496,7 +5473,7 @@ static void nv_g_cmd(cmdarg_T *cap)
case 'M':
oap->motion_type = kMTCharWise;
oap->inclusive = false;
- i = linetabsize(get_cursor_line_ptr());
+ i = linetabsize(curwin, curwin->w_cursor.lnum);
if (cap->count0 > 0 && cap->count0 <= 100) {
coladvance((colnr_T)(i * cap->count0 / 100));
} else {
@@ -5574,7 +5551,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// "gs": Goto sleep.
case 's':
- do_sleep(cap->count1 * 1000L);
+ do_sleep(cap->count1 * 1000);
break;
// "ga": Display the ascii value of the character under the
@@ -5629,7 +5606,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// "gD": idem, but in the current file.
case 'd':
case 'D':
- nv_gd(oap, cap->nchar, (int)cap->count0);
+ nv_gd(oap, cap->nchar, cap->count0);
break;
// g<*Mouse> : <C-*mouse>
@@ -5685,12 +5662,12 @@ static void nv_g_cmd(cmdarg_T *cap)
case 't':
if (!checkclearop(oap)) {
- goto_tabpage((int)cap->count0);
+ goto_tabpage(cap->count0);
}
break;
case 'T':
if (!checkclearop(oap)) {
- goto_tabpage(-(int)cap->count1);
+ goto_tabpage(-cap->count1);
}
break;
@@ -5730,10 +5707,10 @@ static void n_opencmd(cmdarg_T *cap)
(void)hasFolding(curwin->w_cursor.lnum,
NULL, &curwin->w_cursor.lnum);
}
- if (u_save((linenr_T)(curwin->w_cursor.lnum -
- (cap->cmdchar == 'O' ? 1 : 0)),
- (linenr_T)(curwin->w_cursor.lnum +
- (cap->cmdchar == 'o' ? 1 : 0)))
+ // trigger TextChangedI for the 'o/O' command
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+ if (u_save(curwin->w_cursor.lnum - (cap->cmdchar == 'O' ? 1 : 0),
+ curwin->w_cursor.lnum + (cap->cmdchar == 'o' ? 1 : 0))
&& open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD,
has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0,
0, NULL)) {
@@ -5764,10 +5741,9 @@ static void nv_dot(cmdarg_T *cap)
static void nv_redo_or_register(cmdarg_T *cap)
{
if (VIsual_select && VIsual_active) {
- int reg;
// Get register name
no_mapping++;
- reg = plain_vgetc();
+ int reg = plain_vgetc();
LANGMAP_ADJUST(reg, true);
no_mapping--;
@@ -5784,7 +5760,7 @@ static void nv_redo_or_register(cmdarg_T *cap)
return;
}
- u_redo((int)cap->count1);
+ u_redo(cap->count1);
curwin->w_set_curswant = true;
}
@@ -5827,9 +5803,7 @@ static void nv_tilde(cmdarg_T *cap)
/// The actual work is done by do_pending_operator().
static void nv_operator(cmdarg_T *cap)
{
- int op_type;
-
- op_type = get_op_type(cap->cmdchar, cap->nchar);
+ int op_type = get_op_type(cap->cmdchar, cap->nchar);
if (bt_prompt(curbuf) && op_is_change(op_type)
&& !prompt_curpos_editable()) {
@@ -5876,7 +5850,7 @@ static void set_op_var(int optype)
static void nv_lineop(cmdarg_T *cap)
{
cap->oap->motion_type = kMTLineWise;
- if (cursor_down(cap->count1 - 1L, cap->oap->op_type == OP_NOP) == false) {
+ if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) {
clearopbeep(cap->oap);
} else if ((cap->oap->op_type == OP_DELETE
// only with linewise motions
@@ -6046,9 +6020,8 @@ static void adjust_for_sel(cmdarg_T *cap)
/// @return true when backed up to the previous line.
bool unadjust_for_sel(void)
{
- pos_T *pp;
-
if (*p_sel == 'e' && !equalpos(VIsual, curwin->w_cursor)) {
+ pos_T *pp;
if (lt(VIsual, curwin->w_cursor)) {
pp = &curwin->w_cursor;
} else {
@@ -6090,17 +6063,17 @@ static void nv_goto(cmdarg_T *cap)
if (cap->arg) {
lnum = curbuf->b_ml.ml_line_count;
} else {
- lnum = 1L;
+ lnum = 1;
}
cap->oap->motion_type = kMTLineWise;
setpcmark();
// When a count is given, use it instead of the default lnum
if (cap->count0 != 0) {
- lnum = (linenr_T)cap->count0;
+ lnum = cap->count0;
}
- if (lnum < 1L) {
- lnum = 1L;
+ if (lnum < 1) {
+ lnum = 1;
} else if (lnum > curbuf->b_ml.ml_line_count) {
lnum = curbuf->b_ml.ml_line_count;
}
@@ -6136,20 +6109,18 @@ static void nv_normal(cmdarg_T *cap)
/// Don't even beep if we are canceling a command.
static void nv_esc(cmdarg_T *cap)
{
- int no_reason;
-
- no_reason = (cap->oap->op_type == OP_NOP
- && cap->opcount == 0
- && cap->count0 == 0
- && cap->oap->regname == 0);
+ int no_reason = (cap->oap->op_type == OP_NOP
+ && cap->opcount == 0
+ && cap->count0 == 0
+ && cap->oap->regname == 0);
if (cap->arg) { // true for CTRL-C
if (restart_edit == 0 && cmdwin_type == 0 && !VIsual_active && no_reason) {
if (anyBufIsChanged()) {
msg(_("Type :qa! and press <Enter> to abandon all changes"
- " and exit Nvim"));
+ " and exit Nvim"), 0);
} else {
- msg(_("Type :qa and press <Enter> to exit Nvim"));
+ msg(_("Type :qa and press <Enter> to exit Nvim"), 0);
}
}
@@ -6274,6 +6245,11 @@ static void invoke_edit(cmdarg_T *cap, int repl, int cmd, int startln)
// Always reset "restart_edit", this is not a restarted edit.
restart_edit = 0;
+ // Reset Changedtick_i, so that TextChangedI will only be triggered for stuff
+ // from insert mode, for 'o/O' this has already been done in n_opencmd
+ if (cap->cmdchar != 'O' && cap->cmdchar != 'o') {
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+ }
if (edit(cmd, startln, cap->count1)) {
cap->retval |= CA_COMMAND_BUSY;
}
@@ -6288,7 +6264,6 @@ static void nv_object(cmdarg_T *cap)
{
bool flag;
bool include;
- char *mps_save;
if (cap->cmdchar == 'i') {
include = false; // "ix" = inner object: exclude white space
@@ -6296,7 +6271,7 @@ static void nv_object(cmdarg_T *cap)
include = true; // "ax" = an object: include white space
}
// Make sure (), [], {} and <> are in 'matchpairs'
- mps_save = curbuf->b_p_mps;
+ char *mps_save = curbuf->b_p_mps;
curbuf->b_p_mps = "(:),{:},[:],<:>";
switch (cap->nchar) {
@@ -6376,6 +6351,10 @@ static void nv_record(cmdarg_T *cap)
}
if (cap->nchar == ':' || cap->nchar == '/' || cap->nchar == '?') {
+ if (cmdwin_type != 0) {
+ emsg(_(e_cmdline_window_already_open));
+ return;
+ }
stuffcharReadbuff(cap->nchar);
stuffcharReadbuff(K_CMDWIN);
} else {
@@ -6415,7 +6394,7 @@ static void nv_halfpage(cmdarg_T *cap)
&& curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count)) {
clearopbeep(cap->oap);
} else if (!checkclearop(cap->oap)) {
- halfpage(cap->cmdchar == Ctrl_D, (linenr_T)cap->count0);
+ halfpage(cap->cmdchar == Ctrl_D, cap->count0);
}
}
@@ -6460,7 +6439,6 @@ static void nv_put(cmdarg_T *cap)
/// @param fix_indent true for "[p", "[P", "]p" and "]P".
static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
{
- int regname = 0;
yankreg_T *savereg = NULL;
bool empty = false;
bool was_visual = false;
@@ -6486,7 +6464,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
if (fix_indent) {
dir = (cap->cmdchar == ']' && cap->nchar == 'p')
- ? FORWARD : BACKWARD;
+ ? FORWARD : BACKWARD;
flags |= PUT_FIXINDENT;
} else {
dir = (cap->cmdchar == 'P'
@@ -6506,7 +6484,7 @@ static void nv_put_opt(cmdarg_T *cap, bool fix_indent)
// Need to save and restore the registers that the delete
// overwrites if the old contents is being put.
was_visual = true;
- regname = cap->oap->regname;
+ int regname = cap->oap->regname;
bool keep_registers = cap->cmdchar == 'P';
// '+' and '*' could be the same selection
bool clipoverwrite = (regname == '+' || regname == '*') && (cb_flags & CB_UNNAMEDMASK);
diff --git a/src/nvim/normal.h b/src/nvim/normal.h
index bed1a40b97..dbe74712fc 100644
--- a/src/nvim/normal.h
+++ b/src/nvim/normal.h
@@ -1,85 +1,20 @@
-#ifndef NVIM_NORMAL_H
-#define NVIM_NORMAL_H
+#pragma once
-#include <stdbool.h>
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/normal_defs.h" // IWYU pragma: export
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/pos.h"
+/// Values for find_ident_under_cursor()
+enum {
+ FIND_IDENT = 1, ///< find identifier (word)
+ FIND_STRING = 2, ///< find any string (WORD)
+ FIND_EVAL = 4, ///< include "->", "[]" and "."
+};
-// Values for find_ident_under_cursor()
-#define FIND_IDENT 1 // find identifier (word)
-#define FIND_STRING 2 // find any string (WORD)
-#define FIND_EVAL 4 // include "->", "[]" and "."
-
-/// Motion types, used for operators and for yank/delete registers.
-///
-/// The three valid numerical values must not be changed, as they
-/// are used in external communication and serialization.
-typedef enum {
- kMTCharWise = 0, ///< character-wise movement/register
- kMTLineWise = 1, ///< line-wise movement/register
- kMTBlockWise = 2, ///< block-wise movement/register
- kMTUnknown = -1, ///< Unknown or invalid motion type
-} MotionType;
-
-// Arguments for operators.
-typedef struct oparg_S {
- int op_type; // current pending operator type
- int regname; // register to use for the operator
- MotionType motion_type; // type of the current cursor motion
- int motion_force; // force motion type: 'v', 'V' or CTRL-V
- bool use_reg_one; // true if delete uses reg 1 even when not
- // linewise
- bool inclusive; // true if char motion is inclusive (only
- // valid when motion_type is kMTCharWise)
- bool end_adjusted; // backuped b_op_end one char (only used by
- // do_format())
- pos_T start; // start of the operator
- pos_T end; // end of the operator
- pos_T cursor_start; // cursor position before motion for "gw"
-
- long line_count; // number of lines from op_start to op_end
- // (inclusive)
- bool empty; // op_start and op_end the same (only used by
- // op_change())
- bool is_VIsual; // operator on Visual area
- colnr_T start_vcol; // start col for block mode operator
- colnr_T end_vcol; // end col for block mode operator
- long prev_opcount; // ca.opcount saved for K_EVENT
- long prev_count0; // ca.count0 saved for K_EVENT
- bool excl_tr_ws; // exclude trailing whitespace for yank of a
- // block
-} oparg_T;
-
-// Arguments for Normal mode commands.
-typedef struct cmdarg_S {
- oparg_T *oap; // Operator arguments
- int prechar; // prefix character (optional, always 'g')
- int cmdchar; // command character
- int nchar; // next command character (optional)
- int ncharC1; // first composing character (optional)
- int ncharC2; // second composing character (optional)
- int extra_char; // yet another character (optional)
- long opcount; // count before an operator
- long count0; // count before command, default 0
- long count1; // count before command, default 1
- int arg; // extra argument from nv_cmds[]
- int retval; // return: CA_* values
- char *searchbuf; // return: pointer to search pattern or NULL
-} cmdarg_T;
-
-// values for retval:
-#define CA_COMMAND_BUSY 1 // skip restarting edit() once
-#define CA_NO_ADJ_OP_END 2 // don't adjust operator end
-
-// columns needed by shown command
-#define SHOWCMD_COLS 10
-// 'showcmd' buffer shared between normal.c and statusline.c
-#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30)
+/// 'showcmd' buffer shared between normal.c and statusline.c
EXTERN char showcmd_buf[SHOWCMD_BUFLEN];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "normal.h.generated.h"
#endif
-#endif // NVIM_NORMAL_H
diff --git a/src/nvim/normal_defs.h b/src/nvim/normal_defs.h
new file mode 100644
index 0000000000..060c1057f9
--- /dev/null
+++ b/src/nvim/normal_defs.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/pos_defs.h"
+
+/// Motion types, used for operators and for yank/delete registers.
+///
+/// The three valid numerical values must not be changed, as they
+/// are used in external communication and serialization.
+typedef enum {
+ kMTCharWise = 0, ///< character-wise movement/register
+ kMTLineWise = 1, ///< line-wise movement/register
+ kMTBlockWise = 2, ///< block-wise movement/register
+ kMTUnknown = -1, ///< Unknown or invalid motion type
+} MotionType;
+
+/// Arguments for operators.
+typedef struct oparg_S {
+ int op_type; ///< current pending operator type
+ int regname; ///< register to use for the operator
+ MotionType motion_type; ///< type of the current cursor motion
+ int motion_force; ///< force motion type: 'v', 'V' or CTRL-V
+ bool use_reg_one; ///< true if delete uses reg 1 even when not
+ ///< linewise
+ bool inclusive; ///< true if char motion is inclusive (only
+ ///< valid when motion_type is kMTCharWise)
+ bool end_adjusted; ///< backuped b_op_end one char (only used by
+ ///< do_format())
+ pos_T start; ///< start of the operator
+ pos_T end; ///< end of the operator
+ pos_T cursor_start; ///< cursor position before motion for "gw"
+
+ linenr_T line_count; ///< number of lines from op_start to op_end (inclusive)
+ bool empty; ///< op_start and op_end the same (only used by op_change())
+ bool is_VIsual; ///< operator on Visual area
+ colnr_T start_vcol; ///< start col for block mode operator
+ colnr_T end_vcol; ///< end col for block mode operator
+ int prev_opcount; ///< ca.opcount saved for K_EVENT
+ int prev_count0; ///< ca.count0 saved for K_EVENT
+ bool excl_tr_ws; ///< exclude trailing whitespace for yank of a block
+} oparg_T;
+
+/// Arguments for Normal mode commands.
+typedef struct cmdarg_S {
+ oparg_T *oap; ///< Operator arguments
+ int prechar; ///< prefix character (optional, always 'g')
+ int cmdchar; ///< command character
+ int nchar; ///< next command character (optional)
+ int ncharC1; ///< first composing character (optional)
+ int ncharC2; ///< second composing character (optional)
+ int extra_char; ///< yet another character (optional)
+ int opcount; ///< count before an operator
+ int count0; ///< count before command, default 0
+ int count1; ///< count before command, default 1
+ int arg; ///< extra argument from nv_cmds[]
+ int retval; ///< return: CA_* values
+ char *searchbuf; ///< return: pointer to search pattern or NULL
+} cmdarg_T;
+
+/// values for retval:
+enum {
+ CA_COMMAND_BUSY = 1, ///< skip restarting edit() once
+ CA_NO_ADJ_OP_END = 2, ///< don't adjust operator end
+};
+
+/// Replacement for nchar used by nv_replace().
+enum {
+ REPLACE_CR_NCHAR = -1,
+ REPLACE_NL_NCHAR = -2,
+};
+
+/// columns needed by shown command
+enum { SHOWCMD_COLS = 10, };
+enum { SHOWCMD_BUFLEN = SHOWCMD_COLS + 1 + 30, };
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 17a0d6e543..9d0b8e01cd 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// ops.c: implementation of various operators: op_shift, op_delete, op_tilde,
// op_change, op_yank, do_put, do_join
@@ -12,10 +9,11 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -34,11 +32,11 @@
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -49,19 +47,20 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/plines.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/terminal.h"
#include "nvim/textformat.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static yankreg_T y_regs[NUM_REGISTERS] = { 0 };
@@ -97,13 +96,16 @@ struct block_def {
# include "ops.c.generated.h"
#endif
+static const char e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines[]
+ = N_("E883: Search pattern and expression register may not contain two or more lines");
+
// Flags for third item in "opchars".
#define OPF_LINES 1 // operator always works on lines
#define OPF_CHANGE 2 // operator changes text
-// The names of operators.
-// IMPORTANT: Index must correspond with defines in vim.h!!!
-// The third field indicates whether the operator always works on lines.
+/// The names of operators.
+/// IMPORTANT: Index must correspond with defines in ops.h!!!
+/// The third field indicates whether the operator always works on lines.
static char opchars[][3] = {
{ NUL, NUL, 0 }, // OP_NOP
{ 'd', NUL, OPF_CHANGE }, // OP_DELETE
@@ -213,8 +215,7 @@ int get_extra_op_char(int optype)
/// handle a shift operation
void op_shift(oparg_T *oap, int curs_top, int amount)
{
- long i;
- int first_char;
+ int i;
int block_col = 0;
if (u_save((linenr_T)(oap->start.lnum - 1),
@@ -227,7 +228,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
}
for (i = oap->line_count - 1; i >= 0; i--) {
- first_char = (uint8_t)(*get_cursor_line_ptr());
+ int first_char = (uint8_t)(*get_cursor_line_ptr());
if (first_char == NUL) { // empty line
curwin->w_cursor.col = 0;
} else if (oap->motion_type == kMTBlockWise) {
@@ -280,7 +281,7 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
}
}
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
/// Shift the current line one shiftwidth left (if left != 0) or right
@@ -289,15 +290,13 @@ void op_shift(oparg_T *oap, int curs_top, int amount)
/// @param call_changed_bytes call changed_bytes()
void shift_line(int left, int round, int amount, int call_changed_bytes)
{
- int count;
- int i, j;
- const int sw_val = (int)get_sw_value_indent(curbuf);
+ const int sw_val = get_sw_value_indent(curbuf);
- count = get_indent(); // get current indent
+ int count = get_indent(); // get current indent
if (round) { // round off indent
- i = count / sw_val; // number of 'shiftwidth' rounded down
- j = count % sw_val; // extra spaces
+ int i = count / sw_val; // number of 'shiftwidth' rounded down
+ int j = count % sw_val; // extra spaces
if (j && left) { // first remove extra spaces
amount--;
}
@@ -337,11 +336,10 @@ static void shift_block(oparg_T *oap, int amount)
const int oldstate = State;
char *newp;
const int oldcol = curwin->w_cursor.col;
- const int sw_val = (int)get_sw_value_indent(curbuf);
+ const int sw_val = get_sw_value_indent(curbuf);
const int ts_val = (int)curbuf->b_p_ts;
struct block_def bd;
int incr;
- int i = 0, j = 0;
const int old_p_ri = p_ri;
p_ri = 0; // don't want revins in indent
@@ -392,40 +390,36 @@ static void shift_block(oparg_T *oap, int amount)
bd.start_vcol = cts.cts_vcol;
clear_chartabsize_arg(&cts);
+ int tabs = 0, spaces = 0;
// OK, now total=all the VWS reqd, and textstart points at the 1st
// non-ws char in the block.
if (!curbuf->b_p_et) {
- tabstop_fromto(ws_vcol, ws_vcol + total, ts_val, curbuf->b_p_vts_array, &i, &j);
+ tabstop_fromto(ws_vcol, ws_vcol + total,
+ ts_val, curbuf->b_p_vts_array, &tabs, &spaces);
} else {
- j = total;
+ spaces = total;
}
// if we're splitting a TAB, allow for it
- int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
+ const int col_pre = bd.pre_whitesp_c - (bd.startspaces != 0);
bd.textcol -= col_pre;
- const int len = (int)strlen(bd.textstart) + 1;
- int col = bd.textcol + i + j + len;
- assert(col >= 0);
- newp = xmalloc((size_t)col);
- memset(newp, NUL, (size_t)col);
+
+ const size_t new_line_len // the length of the line after the block shift
+ = (size_t)bd.textcol + (size_t)tabs + (size_t)spaces + strlen(bd.textstart);
+ newp = xmalloc(new_line_len + 1);
memmove(newp, oldp, (size_t)bd.textcol);
startcol = bd.textcol;
oldlen = (int)(bd.textstart - old_textstart) + col_pre;
- newlen = i + j;
- memset(newp + bd.textcol, TAB, (size_t)i);
- memset(newp + bd.textcol + i, ' ', (size_t)j);
- // the end
- memmove(newp + bd.textcol + i + j, bd.textstart, (size_t)len);
+ newlen = tabs + spaces;
+ memset(newp + bd.textcol, TAB, (size_t)tabs);
+ memset(newp + bd.textcol + tabs, ' ', (size_t)spaces);
+ // Note that STRMOVE() copies the trailing NUL.
+ STRMOVE(newp + bd.textcol + tabs + spaces, bd.textstart);
} else { // left
- colnr_T destination_col; // column to which text in block will
- // be shifted
char *verbatim_copy_end; // end of the part of the line which is
// copied verbatim
colnr_T verbatim_copy_width; // the (displayed) width of this part
// of line
- size_t fill; // nr of spaces that replace a TAB
- size_t new_line_len; // the length of the line after the
- // block shift
char *non_white = bd.textstart;
// Firstly, let's find the first non-whitespace character that is
@@ -458,10 +452,10 @@ static void shift_block(oparg_T *oap, int amount)
const colnr_T block_space_width = non_white_col - oap->start_vcol;
// We will shift by "total" or "block_space_width", whichever is less.
const colnr_T shift_amount = block_space_width < total
- ? block_space_width
- : total;
+ ? block_space_width
+ : total;
// The column to which we will shift the text.
- destination_col = non_white_col - shift_amount;
+ const colnr_T destination_col = non_white_col - shift_amount;
// Now let's find out how much of the beginning of the line we can
// reuse without modification.
@@ -492,7 +486,8 @@ static void shift_block(oparg_T *oap, int amount)
// part of the line that will be copied, it means we encountered a tab
// character, which we will have to partly replace with spaces.
assert(destination_col - verbatim_copy_width >= 0);
- fill = (size_t)(destination_col - verbatim_copy_width);
+ const size_t fill // nr of spaces that replace a TAB
+ = (size_t)(destination_col - verbatim_copy_width);
assert(verbatim_copy_end - oldp >= 0);
const size_t verbatim_diff = (size_t)(verbatim_copy_end - oldp);
@@ -500,14 +495,16 @@ static void shift_block(oparg_T *oap, int amount)
// - the beginning of the original line up to "verbatim_copy_end",
// - "fill" number of spaces,
// - the rest of the line, pointed to by non_white.
- new_line_len = verbatim_diff + fill + strlen(non_white) + 1;
+ const size_t new_line_len // the length of the line after the block shift
+ = verbatim_diff + fill + strlen(non_white);
- newp = xmalloc(new_line_len);
+ newp = xmalloc(new_line_len + 1);
startcol = (int)verbatim_diff;
oldlen = bd.textcol + (int)(non_white - bd.textstart) - (int)verbatim_diff;
newlen = (int)fill;
memmove(newp, oldp, verbatim_diff);
memset(newp + verbatim_diff, ' ', fill);
+ // Note that STRMOVE() copies the trailing NUL.
STRMOVE(newp + verbatim_diff + fill, non_white);
}
// replace the line
@@ -531,11 +528,10 @@ static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *
colnr_T offset; // pointer along new line
size_t s_len = strlen(s);
char *newp, *oldp; // new, old lines
- linenr_T lnum; // loop var
int oldstate = State;
State = MODE_INSERT; // don't want MODE_REPLACE for State
- for (lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
+ for (linenr_T lnum = oap->start.lnum + 1; lnum <= oap->end.lnum; lnum++) {
block_prep(oap, bdp, lnum, true);
if (bdp->is_short && b_insert) {
continue; // OP_INSERT, line ends before block start
@@ -627,17 +623,15 @@ static void block_insert(oparg_T *oap, char *s, int b_insert, struct block_def *
}
} // for all lnum
- changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true);
-
State = oldstate;
+
+ changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
}
/// Handle reindenting a block of lines.
void op_reindent(oparg_T *oap, Indenter how)
{
- long i = 0;
- char *l;
- int amount;
+ int i = 0;
linenr_T first_changed = 0;
linenr_T last_changed = 0;
linenr_T start_lnum = curwin->w_cursor.lnum;
@@ -650,8 +644,9 @@ void op_reindent(oparg_T *oap, Indenter how)
// Save for undo. Do this once for all lines, much faster than doing this
// for each line separately, especially when undoing.
- if (u_savecommon(curbuf, start_lnum - 1, start_lnum + (linenr_T)oap->line_count,
- start_lnum + (linenr_T)oap->line_count, false) == OK) {
+ if (u_savecommon(curbuf, start_lnum - 1, start_lnum + oap->line_count,
+ start_lnum + oap->line_count, false) == OK) {
+ int amount;
for (i = oap->line_count - 1; i >= 0 && !got_int; i--) {
// it's a slow thing to do, so give feedback so there's no worry
// that the computer's just hung.
@@ -659,14 +654,14 @@ void op_reindent(oparg_T *oap, Indenter how)
if (i > 1
&& (i % 50 == 0 || i == oap->line_count - 1)
&& oap->line_count > p_report) {
- smsg(_("%" PRId64 " lines to indent... "), (int64_t)i);
+ smsg(0, _("%" PRId64 " lines to indent... "), (int64_t)i);
}
// Be vi-compatible: For lisp indenting the first line is not
// indented, unless there is only one line.
if (i != oap->line_count - 1 || oap->line_count == 1
|| how != get_lisp_indent) {
- l = skipwhite(get_cursor_line_ptr());
+ char *l = skipwhite(get_cursor_line_ptr());
if (*l == NUL) { // empty or blank line
amount = 0;
} else {
@@ -693,18 +688,16 @@ void op_reindent(oparg_T *oap, Indenter how)
// highlighting was present, need to continue until the last line. When
// there is no change still need to remove the Visual highlighting.
if (last_changed != 0) {
- changed_lines(first_changed, 0,
- oap->is_VIsual ? start_lnum + (linenr_T)oap->line_count :
- last_changed + 1, 0L, true);
+ changed_lines(curbuf, first_changed, 0,
+ oap->is_VIsual ? start_lnum + oap->line_count
+ : last_changed + 1, 0, true);
} else if (oap->is_VIsual) {
redraw_curbuf_later(UPD_INVERTED);
}
if (oap->line_count > p_report) {
i = oap->line_count - (i + 1);
- smsg(NGETTEXT("%" PRId64 " line indented ",
- "%" PRId64 " lines indented ", i),
- (int64_t)i);
+ smsg(0, NGETTEXT("%" PRId64 " line indented ", "%" PRId64 " lines indented ", i), (int64_t)i);
}
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// set '[ and '] marks
@@ -721,9 +714,7 @@ static char *expr_line = NULL;
/// @return '=' when OK, NUL otherwise.
int get_expr_register(void)
{
- char *new_line;
-
- new_line = getcmdline('=', 0L, 0, true);
+ char *new_line = getcmdline('=', 0, 0, true);
if (new_line == NULL) {
return NUL;
}
@@ -748,8 +739,6 @@ void set_expr_line(char *new_line)
/// @return a pointer to allocated memory, or NULL for failure.
char *get_expr_line(void)
{
- char *expr_copy;
- char *rv;
static int nested = 0;
if (expr_line == NULL) {
@@ -758,7 +747,7 @@ char *get_expr_line(void)
// Make a copy of the expression, because evaluating it may cause it to be
// changed.
- expr_copy = xstrdup(expr_line);
+ char *expr_copy = xstrdup(expr_line);
// When we are invoked recursively limit the evaluation to 10 levels.
// Then return the string as-is.
@@ -767,7 +756,7 @@ char *get_expr_line(void)
}
nested++;
- rv = eval_to_string(expr_copy, NULL, true);
+ char *rv = eval_to_string(expr_copy, true);
nested--;
xfree(expr_copy);
return rv;
@@ -855,15 +844,6 @@ static bool is_append_register(int regname)
return ASCII_ISUPPER(regname);
}
-/// @see get_yank_register
-/// @returns true when register should be inserted literally
-/// (selection or clipboard)
-static inline bool is_literal_register(int regname)
- FUNC_ATTR_CONST
-{
- return regname == '*' || regname == '+';
-}
-
/// @return a copy of contents in register `name` for use in do_put. Should be
/// freed by caller.
yankreg_T *copy_register(int name)
@@ -902,9 +882,7 @@ bool yank_register_mline(int regname)
/// @return FAIL for failure, OK otherwise.
int do_record(int c)
{
- char *p;
static int regname;
- yankreg_T *old_y_previous;
int retval;
if (reg_recording == 0) {
@@ -927,11 +905,11 @@ int do_record(int c)
dict_T *dict = get_v_event(&save_v_event);
// The recorded text contents.
- p = (char *)get_recorded();
+ char *p = get_recorded();
if (p != NULL) {
// Remove escaping for K_SPECIAL in multi-byte chars.
- vim_unescape_ks((char_u *)p);
- (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p);
+ vim_unescape_ks(p);
+ (void)tv_dict_add_str(dict, S_LEN("regcontents"), p);
}
// Name of requested register, or empty string for unnamed operation.
@@ -951,14 +929,14 @@ int do_record(int c)
if (p_ch == 0 || ui_has(kUIMessages)) {
showmode();
} else {
- msg("");
+ msg("", 0);
}
if (p == NULL) {
retval = FAIL;
} else {
// We don't want to change the default register here, so save and
// restore the current register name.
- old_y_previous = y_previous;
+ yankreg_T *old_y_previous = y_previous;
retval = stuff_yank(regname, p);
@@ -996,10 +974,12 @@ static int stuff_yank(int regname, char *p)
yankreg_T *reg = get_yank_register(regname, YREG_YANK);
if (is_append_register(regname) && reg->y_array != NULL) {
char **pp = &(reg->y_array[reg->y_size - 1]);
- char *lp = xmalloc(strlen(*pp) + strlen(p) + 1);
- STRCPY(lp, *pp);
- // TODO(philix): use xstpcpy() in stuff_yank()
- STRCAT(lp, p);
+ const size_t ppl = strlen(*pp);
+ const size_t pl = strlen(p);
+ char *lp = xmalloc(ppl + pl + 1);
+ memcpy(lp, *pp, ppl);
+ memcpy(lp + ppl, p, pl);
+ *(lp + ppl + pl) = NUL;
xfree(p);
xfree(*pp);
*pp = lp;
@@ -1037,13 +1017,11 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
garray_T ga;
ga_init(&ga, (int)sizeof(char), 400);
- char *p;
-
// search backwards to find the first line of this command.
// Any line not starting with \ or "\ is the start of the
// command.
while (--i > 0) {
- p = skipwhite(lines[i]);
+ char *p = skipwhite(lines[i]);
if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) {
break;
}
@@ -1053,7 +1031,7 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
// join all the lines
ga_concat(&ga, lines[cmd_start]);
for (size_t j = cmd_start + 1; j <= cmd_end; j++) {
- p = skipwhite(lines[j]);
+ char *p = skipwhite(lines[j]);
if (*p == '\\') {
// Adjust the growsize to the current length to
// speed up concatenating many lines.
@@ -1080,7 +1058,6 @@ static char *execreg_line_continuation(char **lines, size_t *idx)
/// @return FAIL for failure, OK otherwise
int do_execreg(int regname, int colon, int addcr, int silent)
{
- char *p;
int retval = OK;
if (regname == '@') { // repeat previous one
@@ -1109,12 +1086,12 @@ int do_execreg(int regname, int colon, int addcr, int silent)
// don't keep the cmdline containing @:
XFREE_CLEAR(new_last_cmdline);
// Escape all control characters with a CTRL-V
- p = vim_strsave_escaped_ext(last_cmdline,
- "\001\002\003\004\005\006\007"
- "\010\011\012\013\014\015\016\017"
- "\020\021\022\023\024\025\026\027"
- "\030\031\032\033\034\035\036\037",
- Ctrl_V, false);
+ char *p = vim_strsave_escaped_ext(last_cmdline,
+ "\001\002\003\004\005\006\007"
+ "\010\011\012\013\014\015\016\017"
+ "\020\021\022\023\024\025\026\027"
+ "\030\031\032\033\034\035\036\037",
+ Ctrl_V, false);
// When in Visual mode "'<,'>" will be prepended to the command.
// Remove it when it's already there.
if (VIsual_active && strncmp(p, "'<,'>", 5) == 0) {
@@ -1124,14 +1101,14 @@ int do_execreg(int regname, int colon, int addcr, int silent)
}
xfree(p);
} else if (regname == '=') {
- p = get_expr_line();
+ char *p = get_expr_line();
if (p == NULL) {
return FAIL;
}
retval = put_in_typebuf(p, true, colon, silent);
xfree(p);
} else if (regname == '.') { // use last inserted text
- p = get_last_insert_save();
+ char *p = get_last_insert_save();
if (p == NULL) {
emsg(_(e_noinstext));
return FAIL;
@@ -1149,7 +1126,6 @@ int do_execreg(int regname, int colon, int addcr, int silent)
// Insert lines into typeahead buffer, from last one to first one.
put_reedit_in_typebuf(silent);
- char *escaped;
for (size_t i = reg->y_size; i-- > 0;) { // from y_size - 1 to 0 included
// insert NL between lines and after last line if type is kMTLineWise
if (reg->y_type == kMTLineWise || i < reg->y_size - 1 || addcr) {
@@ -1162,13 +1138,13 @@ int do_execreg(int regname, int colon, int addcr, int silent)
char *str = reg->y_array[i];
bool free_str = false;
if (colon && i > 0) {
- p = skipwhite(str);
+ char *p = skipwhite(str);
if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) {
str = execreg_line_continuation(reg->y_array, &i);
free_str = true;
}
}
- escaped = vim_strsave_escape_ks(str);
+ char *escaped = vim_strsave_escape_ks(str);
if (free_str) {
xfree(str);
}
@@ -1191,7 +1167,7 @@ int do_execreg(int regname, int colon, int addcr, int silent)
/// used only after other typeahead has been processed.
static void put_reedit_in_typebuf(int silent)
{
- char_u buf[3];
+ uint8_t buf[3];
if (restart_edit == NUL) {
return;
@@ -1202,7 +1178,7 @@ static void put_reedit_in_typebuf(int silent)
buf[1] = 'R';
buf[2] = NUL;
} else {
- buf[0] = (char_u)(restart_edit == 'I' ? 'i' : restart_edit);
+ buf[0] = (uint8_t)(restart_edit == 'I' ? 'i' : restart_edit);
buf[1] = NUL;
}
if (ins_typebuf((char *)buf, REMAP_NONE, 0, true, silent) == OK) {
@@ -1274,12 +1250,12 @@ int insert_reg(int regname, bool literally_arg)
char *arg;
if (regname == '.') { // Insert last inserted text.
- retval = stuff_inserted(NUL, 1L, true);
+ retval = stuff_inserted(NUL, 1, true);
} else if (get_spec_reg(regname, &arg, &allocated, true)) {
if (arg == NULL) {
return FAIL;
}
- stuffescaped((const char *)arg, literally);
+ stuffescaped(arg, literally);
if (allocated) {
xfree(arg);
}
@@ -1292,9 +1268,9 @@ int insert_reg(int regname, bool literally_arg)
if (regname == '-') {
AppendCharToRedobuff(Ctrl_R);
AppendCharToRedobuff(regname);
- do_put(regname, NULL, BACKWARD, 1L, PUT_CURSEND);
+ do_put(regname, NULL, BACKWARD, 1, PUT_CURSEND);
} else {
- stuffescaped((const char *)reg->y_array[i], literally);
+ stuffescaped(reg->y_array[i], literally);
}
// Insert a newline between lines and after last line if
// y_type is kMTLineWise.
@@ -1317,8 +1293,6 @@ int insert_reg(int regname, bool literally_arg)
/// @return true if "regname" is a special register,
bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
{
- size_t cnt;
-
*argp = NULL;
*allocated = false;
switch (regname) {
@@ -1366,7 +1340,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
return false;
}
*argp = file_name_at_cursor(FNAME_MESS | FNAME_HYP | (regname == Ctrl_P ? FNAME_EXP : 0),
- 1L, NULL);
+ 1, NULL);
*allocated = true;
return true;
@@ -1375,10 +1349,10 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
if (!errmsg) {
return false;
}
- cnt = find_ident_under_cursor(argp, (regname == Ctrl_W
- ? (FIND_IDENT|FIND_STRING)
- : FIND_STRING));
- *argp = cnt ? xstrnsave(*argp, cnt) : NULL;
+ size_t cnt = find_ident_under_cursor(argp, (regname == Ctrl_W
+ ? (FIND_IDENT|FIND_STRING)
+ : FIND_STRING));
+ *argp = cnt ? xmemdupz(*argp, cnt) : NULL;
*allocated = true;
return true;
@@ -1387,7 +1361,7 @@ bool get_spec_reg(int regname, char **argp, bool *allocated, bool errmsg)
return false;
}
- *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum, false);
+ *argp = ml_get_buf(curwin->w_buffer, curwin->w_cursor.lnum);
return true;
case '_': // black hole: always empty
@@ -1454,10 +1428,7 @@ static void shift_delete_registers(bool y_append)
/// @return FAIL if undo failed, OK otherwise.
int op_delete(oparg_T *oap)
{
- int n;
linenr_T lnum;
- char *ptr;
- char *newp, *oldp;
struct block_def bd = { 0 };
linenr_T old_lcount = curbuf->b_ml.ml_line_count;
@@ -1490,7 +1461,7 @@ int op_delete(oparg_T *oap)
&& oap->line_count > 1
&& oap->motion_force == NUL
&& oap->op_type == OP_DELETE) {
- ptr = ml_get(oap->end.lnum) + oap->end.col;
+ char *ptr = ml_get(oap->end.lnum) + oap->end.col;
if (*ptr != NUL) {
ptr += oap->inclusive;
}
@@ -1584,12 +1555,12 @@ int op_delete(oparg_T *oap)
curwin->w_cursor.coladd = 0;
}
- // n == number of chars deleted
+ // "n" == number of chars deleted
// If we delete a TAB, it may be replaced by several characters.
// Thus the number of characters may increase!
- n = bd.textlen - bd.startspaces - bd.endspaces;
- oldp = ml_get(lnum);
- newp = xmalloc(strlen(oldp) - (size_t)n + 1);
+ int n = bd.textlen - bd.startspaces - bd.endspaces;
+ char *oldp = ml_get(lnum);
+ char *newp = xmalloc(strlen(oldp) - (size_t)n + 1);
// copy up to deleted part
memmove(newp, oldp, (size_t)bd.textcol);
// insert spaces
@@ -1607,8 +1578,8 @@ int op_delete(oparg_T *oap)
}
check_cursor_col();
- changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
- oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, curwin->w_cursor.col,
+ oap->end.lnum + 1, 0, true);
oap->line_count = 0; // no lines deleted
} else if (oap->motion_type == kMTLineWise) {
if (oap->op_type == OP_CHANGE) {
@@ -1642,19 +1613,18 @@ int op_delete(oparg_T *oap)
// leave cursor past last char in line
if (oap->line_count > 1) {
- u_clearline(); // "U" command not possible after "2cc"
+ u_clearline(curbuf); // "U" command not possible after "2cc"
}
} else {
del_lines(oap->line_count, true);
beginline(BL_WHITE | BL_FIX);
- u_clearline(); // "U" command not possible after "dd"
+ u_clearline(curbuf); // "U" command not possible after "dd"
}
} else {
if (virtual_op) {
- int endcol = 0;
-
// For virtualedit: break the tabs that are partly included.
if (gchar_pos(&oap->start) == '\t') {
+ int endcol = 0;
if (u_save_cursor() == FAIL) { // save first line for undo
return FAIL;
}
@@ -1701,7 +1671,7 @@ int op_delete(oparg_T *oap)
display_dollar(oap->end.col - !oap->inclusive);
}
- n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
+ int n = oap->end.col - oap->start.col + 1 - !oap->inclusive;
if (virtual_op) {
// fix up things for virtualedit-delete:
@@ -1732,8 +1702,8 @@ int op_delete(oparg_T *oap)
pos_T curpos;
// save deleted and changed lines for undo
- if (u_save((linenr_T)(curwin->w_cursor.lnum - 1),
- (linenr_T)(curwin->w_cursor.lnum + oap->line_count)) == FAIL) {
+ if (u_save(curwin->w_cursor.lnum - 1,
+ curwin->w_cursor.lnum + oap->line_count) == FAIL) {
return FAIL;
}
@@ -1750,7 +1720,7 @@ int op_delete(oparg_T *oap)
del_lines(oap->line_count - 2, false);
// delete from start of line until op_end
- n = (oap->end.col + 1 - !oap->inclusive);
+ int n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
@@ -1798,7 +1768,7 @@ static void mb_adjust_opend(oparg_T *oap)
static inline void pbyte(pos_T lp, int c)
{
assert(c <= UCHAR_MAX);
- *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char)c;
+ *(ml_get_buf_mut(curbuf, lp.lnum) + lp.col) = (char)c;
if (!curbuf_splice_pending) {
extmark_splice_cols(curbuf, (int)lp.lnum - 1, lp.col, 1, 1, kExtmarkUndo);
}
@@ -1820,10 +1790,7 @@ static void replace_character(int c)
/// Replace a whole area with one character.
static int op_replace(oparg_T *oap, int c)
{
- int n, numc;
- int num_chars;
- char *newp, *oldp;
- colnr_T oldlen;
+ int n;
struct block_def bd;
char *after_p = NULL;
int had_ctrl_v_cr = false;
@@ -1848,6 +1815,11 @@ static int op_replace(oparg_T *oap, int c)
// block mode replace
if (oap->motion_type == kMTBlockWise) {
+ int numc;
+ int num_chars;
+ char *newp;
+ char *oldp;
+ colnr_T oldlen;
bd.is_MAX = (curwin->w_curswant == MAXCOL);
for (; curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
curwin->w_cursor.col = 0; // make sure cursor position is valid
@@ -1947,7 +1919,7 @@ static int op_replace(oparg_T *oap, int c)
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
- appended_lines_mark(curwin->w_cursor.lnum, 1L);
+ appended_lines_mark(curwin->w_cursor.lnum, 1);
oap->end.lnum++;
xfree(after_p);
}
@@ -2043,7 +2015,7 @@ static int op_replace(oparg_T *oap, int c)
curwin->w_cursor = oap->start;
check_cursor();
- changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0, true);
if ((cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Set "'[" and "']" marks.
@@ -2057,7 +2029,6 @@ static int op_replace(oparg_T *oap, int c)
/// Handle the (non-standard vi) tilde operator. Also for "gu", "gU" and "g?".
void op_tilde(oparg_T *oap)
{
- pos_T pos;
struct block_def bd;
int did_change = false;
@@ -2066,7 +2037,7 @@ void op_tilde(oparg_T *oap)
return;
}
- pos = oap->start;
+ pos_T pos = oap->start;
if (oap->motion_type == kMTBlockWise) { // Visual block mode
for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
int one_change;
@@ -2077,7 +2048,7 @@ void op_tilde(oparg_T *oap)
did_change |= one_change;
}
if (did_change) {
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
} else { // not block mode
if (oap->motion_type == kMTLineWise) {
@@ -2095,18 +2066,18 @@ void op_tilde(oparg_T *oap)
did_change = swapchars(oap->op_type, &pos,
oap->end.col - pos.col + 1);
} else {
- for (;;) {
+ while (true) {
did_change |= swapchars(oap->op_type, &pos,
- pos.lnum == oap->end.lnum ? oap->end.col + 1 :
- (int)strlen(ml_get_pos(&pos)));
+ pos.lnum == oap->end.lnum ? oap->end.col + 1
+ : (int)strlen(ml_get_pos(&pos)));
if (ltoreq(oap->end, pos) || inc(&pos) == -1) {
break;
}
}
}
if (did_change) {
- changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1,
- 0L, true);
+ changed_lines(curbuf, oap->start.lnum, oap->start.col, oap->end.lnum + 1,
+ 0, true);
}
}
@@ -2122,8 +2093,7 @@ void op_tilde(oparg_T *oap)
}
if (oap->line_count > p_report) {
- smsg(NGETTEXT("%" PRId64 " line changed",
- "%" PRId64 " lines changed", oap->line_count),
+ smsg(0, NGETTEXT("%" PRId64 " line changed", "%" PRId64 " lines changed", oap->line_count),
(int64_t)oap->line_count);
}
}
@@ -2216,15 +2186,12 @@ bool swapchar(int op_type, pos_T *pos)
}
/// Insert and append operators for Visual mode.
-void op_insert(oparg_T *oap, long count1)
+void op_insert(oparg_T *oap, int count1)
{
- long ins_len, pre_textlen = 0;
- char *firstline, *ins_text;
- colnr_T ind_pre_col = 0, ind_post_col;
- int ind_pre_vcol = 0, ind_post_vcol = 0;
+ int pre_textlen = 0;
+ colnr_T ind_pre_col = 0;
+ int ind_pre_vcol = 0;
struct block_def bd;
- int i;
- pos_T t1;
// edit() changes this - record it for OP_APPEND
bd.is_MAX = (curwin->w_curswant == MAXCOL);
@@ -2261,12 +2228,12 @@ void op_insert(oparg_T *oap, long count1)
// Get indent information
ind_pre_col = (colnr_T)getwhitecols_curline();
ind_pre_vcol = get_indent();
- firstline = ml_get(oap->start.lnum) + bd.textcol;
+ char *firstline = ml_get(oap->start.lnum) + bd.textcol;
if (oap->op_type == OP_APPEND) {
firstline += bd.textlen;
}
- pre_textlen = (long)strlen(firstline);
+ pre_textlen = (int)strlen(firstline);
}
if (oap->op_type == OP_APPEND) {
@@ -2284,7 +2251,7 @@ void op_insert(oparg_T *oap, long count1)
if (u_save_cursor() == FAIL) {
return;
}
- for (i = 0; i < bd.endspaces; i++) {
+ for (int i = 0; i < bd.endspaces; i++) {
ins_char(' ');
}
bd.textlen += bd.endspaces;
@@ -2301,7 +2268,7 @@ void op_insert(oparg_T *oap, long count1)
}
}
- t1 = oap->start;
+ pos_T t1 = oap->start;
const pos_T start_insert = curwin->w_cursor;
(void)edit(NUL, false, (linenr_T)count1);
@@ -2321,12 +2288,13 @@ void op_insert(oparg_T *oap, long count1)
}
if (oap->motion_type == kMTBlockWise) {
+ int ind_post_vcol = 0;
struct block_def bd2;
bool did_indent = false;
// if indent kicked in, the firstline might have changed
// but only do that, if the indent actually increased
- ind_post_col = (colnr_T)getwhitecols_curline();
+ colnr_T ind_post_col = (colnr_T)getwhitecols_curline();
if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) {
bd.textcol += ind_post_col - ind_pre_col;
ind_post_vcol = get_indent();
@@ -2391,7 +2359,7 @@ void op_insert(oparg_T *oap, long count1)
// Subsequent calls to ml_get() flush the firstline data - take a
// copy of the required string.
- firstline = ml_get(oap->start.lnum);
+ char *firstline = ml_get(oap->start.lnum);
const size_t len = strlen(firstline);
colnr_T add = bd.textcol;
colnr_T offset = 0; // offset when cursor was moved in insert mode
@@ -2414,9 +2382,9 @@ void op_insert(oparg_T *oap, long count1)
} else {
firstline += add;
}
- ins_len = (long)strlen(firstline) - pre_textlen - offset;
+ int ins_len = (int)strlen(firstline) - pre_textlen - offset;
if (pre_textlen >= 0 && ins_len > 0) {
- ins_text = xstrnsave(firstline, (size_t)ins_len);
+ char *ins_text = xmemdupz(firstline, (size_t)ins_len);
// block handled here
if (u_save(oap->start.lnum, (linenr_T)(oap->end.lnum + 1)) == OK) {
block_insert(oap, ins_text, (oap->op_type == OP_INSERT), &bd);
@@ -2434,20 +2402,12 @@ void op_insert(oparg_T *oap, long count1)
/// @return true if edit() returns because of a CTRL-O command
int op_change(oparg_T *oap)
{
- colnr_T l;
- int retval;
- long offset;
- linenr_T linenr;
- long ins_len;
- long pre_textlen = 0;
- long pre_indent = 0;
- char *newp;
+ int pre_textlen = 0;
+ int pre_indent = 0;
char *firstline;
- char *ins_text;
- char *oldp;
struct block_def bd;
- l = oap->start.col;
+ colnr_T l = oap->start.col;
if (oap->motion_type == kMTLineWise) {
l = 0;
can_si = may_do_si(); // Like opening a new line, do smart indent
@@ -2477,8 +2437,8 @@ int op_change(oparg_T *oap)
coladvance_force(getviscol());
}
firstline = ml_get(oap->start.lnum);
- pre_textlen = (long)strlen(firstline);
- pre_indent = (long)getwhitecols(firstline);
+ pre_textlen = (int)strlen(firstline);
+ pre_indent = (int)getwhitecols(firstline);
bd.textcol = curwin->w_cursor.col;
}
@@ -2490,7 +2450,7 @@ int op_change(oparg_T *oap)
const bool save_finish_op = finish_op;
finish_op = false;
- retval = edit(NUL, false, (linenr_T)1);
+ int retval = edit(NUL, false, 1);
finish_op = save_finish_op;
@@ -2499,23 +2459,27 @@ int op_change(oparg_T *oap)
// Don't repeat the insert when Insert mode ended with CTRL-C.
if (oap->motion_type == kMTBlockWise
&& oap->start.lnum != oap->end.lnum && !got_int) {
+ int ins_len;
// Auto-indenting may have changed the indent. If the cursor was past
// the indent, exclude that indent change from the inserted text.
firstline = ml_get(oap->start.lnum);
if (bd.textcol > (colnr_T)pre_indent) {
- long new_indent = (long)getwhitecols(firstline);
+ int new_indent = (int)getwhitecols(firstline);
pre_textlen += new_indent - pre_indent;
bd.textcol += (colnr_T)(new_indent - pre_indent);
}
- ins_len = (long)strlen(firstline) - pre_textlen;
+ ins_len = (int)strlen(firstline) - pre_textlen;
if (ins_len > 0) {
+ int offset;
+ char *newp;
+ char *oldp;
// Subsequent calls to ml_get() flush the firstline data - take a
// copy of the inserted text.
- ins_text = xmalloc((size_t)(ins_len + 1));
+ char *ins_text = xmalloc((size_t)ins_len + 1);
xstrlcpy(ins_text, firstline + bd.textcol, (size_t)ins_len + 1);
- for (linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
+ for (linenr_T linenr = oap->start.lnum + 1; linenr <= oap->end.lnum;
linenr++) {
block_prep(oap, &bd, linenr, true);
if (!bd.is_short || virtual_op) {
@@ -2543,11 +2507,11 @@ int op_change(oparg_T *oap)
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
extmark_splice_cols(curbuf, (int)linenr - 1, bd.textcol,
- 0, vpos.coladd + (int)ins_len, kExtmarkUndo);
+ 0, vpos.coladd + ins_len, kExtmarkUndo);
}
}
check_cursor();
- changed_lines(oap->start.lnum + 1, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum + 1, 0, oap->end.lnum + 1, 0, true);
xfree(ins_text);
}
}
@@ -2559,9 +2523,7 @@ int op_change(oparg_T *oap)
#if defined(EXITFREE)
void clear_registers(void)
{
- int i;
-
- for (i = 0; i < NUM_REGISTERS; i++) {
+ for (int i = 0; i < NUM_REGISTERS; i++) {
free_register(&y_regs[i]);
}
}
@@ -2617,14 +2579,9 @@ bool op_yank(oparg_T *oap, bool message)
static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
{
yankreg_T newreg; // new yank register when appending
- char **new_ptr;
- linenr_T lnum; // current line number
- size_t j;
MotionType yank_type = oap->motion_type;
size_t yanklines = (size_t)oap->line_count;
linenr_T yankendlnum = oap->end.lnum;
- char *p;
- char *pnew;
struct block_def bd;
yankreg_T *curr = reg; // copy of current register
@@ -2656,7 +2613,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
reg->timestamp = os_time();
size_t y_idx = 0; // index in y_array[]
- lnum = oap->start.lnum;
+ linenr_T lnum = oap->start.lnum; // current line number
if (yank_type == kMTBlockWise) {
// Visual block mode
@@ -2682,7 +2639,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
colnr_T startcol = 0, endcol = MAXCOL;
int is_oneChar = false;
colnr_T cs, ce;
- p = ml_get(lnum);
+ char *p = ml_get(lnum);
bd.startspaces = 0;
bd.endspaces = 0;
@@ -2692,8 +2649,10 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
getvcol(curwin, &oap->start, &cs, NULL, &ce);
if (ce != cs && oap->start.coladd > 0) {
// Part of a tab selected -- but don't double-count it.
- bd.startspaces = (ce - cs + 1)
- - oap->start.coladd;
+ bd.startspaces = (ce - cs + 1) - oap->start.coladd;
+ if (bd.startspaces < 0) {
+ bd.startspaces = 0;
+ }
startcol++;
}
}
@@ -2743,7 +2702,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
}
if (curr != reg) { // append the new block to the old block
- new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size));
+ size_t j;
+ char **new_ptr = xmalloc(sizeof(char *) * (curr->y_size + reg->y_size));
for (j = 0; j < curr->y_size; j++) {
new_ptr[j] = curr->y_array[j];
}
@@ -2759,8 +2719,8 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
// the new block, unless being Vi compatible.
if (curr->y_type == kMTCharWise
&& vim_strchr(p_cpo, CPO_REGAPPEND) == NULL) {
- pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1])
- + strlen(reg->y_array[0]) + 1);
+ char *pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1])
+ + strlen(reg->y_array[0]) + 1);
STRCPY(pnew, curr->y_array[--j]);
STRCAT(pnew, reg->y_array[0]);
xfree(curr->y_array[j]);
@@ -2797,12 +2757,12 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
update_screen();
}
if (yank_type == kMTBlockWise) {
- smsg(NGETTEXT("block of %" PRId64 " line yanked%s",
- "block of %" PRId64 " lines yanked%s", yanklines),
+ smsg(0, NGETTEXT("block of %" PRId64 " line yanked%s",
+ "block of %" PRId64 " lines yanked%s", yanklines),
(int64_t)yanklines, namebuf);
} else {
- smsg(NGETTEXT("%" PRId64 " line yanked%s",
- "%" PRId64 " lines yanked%s", yanklines),
+ smsg(0, NGETTEXT("%" PRId64 " line yanked%s",
+ "%" PRId64 " lines yanked%s", yanklines),
(int64_t)yanklines, namebuf);
}
}
@@ -2873,7 +2833,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
// The yanked text contents.
list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size);
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(list, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(list, reg->y_array[i], -1);
}
tv_list_set_lock(list, VAR_FIXED);
(void)tv_dict_add_list(dict, S_LEN("regcontents"), list);
@@ -2968,39 +2928,28 @@ static void do_autocmd_textputpost(int regname, yankreg_T *reg)
/// PUT_LINE force linewise put (":put")
/// PUT_BLOCK_INNER in block mode, do not add trailing spaces
/// @param dir BACKWARD for 'P', FORWARD for 'p'
-void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
+void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
{
- char *ptr;
- char *newp;
- char *oldp;
- int yanklen;
size_t totlen = 0; // init for gcc
linenr_T lnum = 0;
- colnr_T col = 0;
- size_t i; // index in y_array[]
MotionType y_type;
size_t y_size;
- size_t oldlen;
int y_width = 0;
colnr_T vcol = 0;
- int delcount;
int incr = 0;
struct block_def bd;
char **y_array = NULL;
linenr_T nr_lines = 0;
- pos_T new_cursor;
int indent;
int orig_indent = 0; // init for gcc
int indent_diff = 0; // init for gcc
bool first_indent = true;
int lendiff = 0;
- pos_T old_pos;
char *insert_string = NULL;
bool allocated = false;
- long cnt;
const pos_T orig_start = curbuf->b_op_start;
const pos_T orig_end = curbuf->b_op_end;
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
if (flags & PUT_FIXINDENT) {
orig_indent = get_indent();
@@ -3015,8 +2964,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
bool non_linewise_vis = (VIsual_active && VIsual_mode != 'V');
// PUT_LINE has special handling below which means we use 'i' to start.
- char command_start_char = non_linewise_vis ? 'c' :
- (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i'));
+ char command_start_char = non_linewise_vis
+ ? 'c'
+ : (flags & PUT_LINE ? 'i' : (dir == FORWARD ? 'a' : 'i'));
// To avoid 'autoindent' on linewise puts, create a new line with `:put _`.
if (flags & PUT_LINE) {
@@ -3112,9 +3062,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// For the = register we need to split the string at NL
// characters.
// Loop twice: count the number of lines and save them.
- for (;;) {
+ while (true) {
y_size = 0;
- ptr = insert_string;
+ char *ptr = insert_string;
while (ptr != NULL) {
if (y_array != NULL) {
y_array[y_size] = ptr;
@@ -3172,16 +3122,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (dir == FORWARD && *p != NUL) {
MB_PTR_ADV(p);
}
- ptr = xstrdup(p);
- ml_append(curwin->w_cursor.lnum, ptr, (colnr_T)0, false);
+ char *ptr = xstrdup(p);
+ ml_append(curwin->w_cursor.lnum, ptr, 0, false);
xfree(ptr);
- oldp = get_cursor_line_ptr();
+ char *oldp = get_cursor_line_ptr();
p = oldp + curwin->w_cursor.col;
if (dir == FORWARD && *p != NUL) {
MB_PTR_ADV(p);
}
- ptr = xstrnsave(oldp, (size_t)(p - oldp));
+ ptr = xmemdupz(oldp, (size_t)(p - oldp));
ml_replace(curwin->w_cursor.lnum, ptr, false);
nr_lines++;
dir = FORWARD;
@@ -3227,8 +3177,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
// In an empty buffer the empty line is going to be replaced, include
// it in the saved lines.
- if ((buf_is_empty(curbuf) ?
- u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) {
+ if ((buf_is_empty(curbuf)
+ ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) {
goto end;
}
if (dir == FORWARD) {
@@ -3241,12 +3191,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
goto end;
}
- yanklen = (int)strlen(y_array[0]);
+ int yanklen = (int)strlen(y_array[0]);
if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) {
if (gchar_cursor() == TAB) {
int viscol = getviscol();
- long ts = curbuf->b_p_ts;
+ OptInt ts = curbuf->b_p_ts;
// Don't need to insert spaces when "p" on the last position of a
// tab or "P" on the first position.
if (dir == FORWARD
@@ -3262,7 +3212,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
lnum = curwin->w_cursor.lnum;
- col = curwin->w_cursor.col;
+ colnr_T col = curwin->w_cursor.col;
// Block mode
if (y_type == kMTBlockWise) {
@@ -3303,7 +3253,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
curwin->w_cursor.coladd = 0;
bd.textcol = 0;
- for (i = 0; i < y_size; i++) {
+ for (size_t i = 0; i < y_size; i++) {
int spaces = 0;
char shortline;
// can just be 0 or 1, needed for blockwise paste beyond the current
@@ -3313,20 +3263,19 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
bd.startspaces = 0;
bd.endspaces = 0;
vcol = 0;
- delcount = 0;
+ int delcount = 0;
// add a new line
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- if (ml_append(curbuf->b_ml.ml_line_count, "",
- (colnr_T)1, false) == FAIL) {
+ if (ml_append(curbuf->b_ml.ml_line_count, "", 1, false) == FAIL) {
break;
}
nr_lines++;
lines_appended = 1;
}
// get the old line and advance to the position to insert at
- oldp = get_cursor_line_ptr();
- oldlen = strlen(oldp);
+ char *oldp = get_cursor_line_ptr();
+ size_t oldlen = strlen(oldp);
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, curwin->w_cursor.lnum, 0, oldp, oldp);
@@ -3336,7 +3285,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
cts.cts_vcol += incr;
}
vcol = cts.cts_vcol;
- ptr = cts.cts_ptr;
+ char *ptr = cts.cts_ptr;
bd.textcol = (colnr_T)(ptr - oldp);
clear_chartabsize_arg(&cts);
@@ -3385,8 +3334,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
break;
}
- totlen = (size_t)(count * (yanklen + spaces) + bd.startspaces + bd.endspaces);
- newp = xmalloc(totlen + oldlen + 1);
+ totlen = (size_t)count * (size_t)(yanklen + spaces) + (size_t)bd.startspaces +
+ (size_t)bd.endspaces;
+ char *newp = xmalloc(totlen + oldlen + 1);
// copy part up to cursor to new line
ptr = newp;
@@ -3398,12 +3348,12 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
ptr += bd.startspaces;
// insert the new text
- for (long j = 0; j < count; j++) {
+ for (int j = 0; j < count; j++) {
memmove(ptr, y_array[i], (size_t)yanklen);
ptr += yanklen;
// insert block's trailing spaces only if there's text behind
- if ((j < count - 1 || !shortline) && spaces) {
+ if ((j < count - 1 || !shortline) && spaces > 0) {
memset(ptr, ' ', (size_t)spaces);
ptr += spaces;
} else {
@@ -3429,7 +3379,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
- changed_lines(lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
+ changed_lines(curbuf, lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
- nr_lines, nr_lines, true);
// Set '[ mark.
@@ -3477,7 +3427,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// Line mode: BACKWARD is the same as FORWARD on the previous line
lnum--;
}
- new_cursor = curwin->w_cursor;
+ pos_T new_cursor = curwin->w_cursor;
// simple case: insert into one line at a time
if (y_type == kMTCharWise && y_size == 1) {
@@ -3511,10 +3461,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// multiplication overflow
emsg(_(e_resulting_text_too_long));
} else {
- totlen = (size_t)(count * yanklen);
+ totlen = (size_t)count * (size_t)yanklen;
do {
- oldp = ml_get(lnum);
- oldlen = strlen(oldp);
+ char *oldp = ml_get(lnum);
+ size_t oldlen = strlen(oldp);
if (lnum > start_lnum) {
pos_T pos = {
.lnum = lnum,
@@ -3529,10 +3479,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
lnum++;
continue;
}
- newp = xmalloc(totlen + oldlen + 1);
+ char *newp = xmalloc(totlen + oldlen + 1);
memmove(newp, oldp, (size_t)col);
- ptr = newp + col;
- for (i = 0; i < (size_t)count; i++) {
+ char *ptr = newp + col;
+ for (size_t i = 0; i < (size_t)count; i++) {
memmove(ptr, y_array[0], (size_t)yanklen);
ptr += yanklen;
}
@@ -3545,7 +3495,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
// Place cursor on last putted char.
if (lnum == curwin->w_cursor.lnum) {
// make sure curwin->w_virtcol is updated
- changed_cline_bef_curs();
+ changed_cline_bef_curs(curwin);
+ invalidate_botline(curwin);
curwin->w_cursor.col += (colnr_T)(totlen - 1);
}
changed_bytes(lnum, col);
@@ -3573,28 +3524,27 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
} 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++) {
- i = 0;
+ for (int cnt = 1; cnt <= count; cnt++) {
+ size_t i = 0;
if (y_type == kMTCharWise) {
// Split the current line in two at the insert position.
// First insert y_array[size - 1] in front of second line.
// Then append y_array[0] to first line.
lnum = new_cursor.lnum;
- ptr = ml_get(lnum) + col;
+ char *ptr = ml_get(lnum) + col;
totlen = strlen(y_array[y_size - 1]);
- newp = xmalloc((size_t)(strlen(ptr) + totlen + 1));
+ char *newp = xmalloc((size_t)(strlen(ptr) + totlen + 1));
STRCPY(newp, y_array[y_size - 1]);
STRCAT(newp, ptr);
// insert second line
- ml_append(lnum, newp, (colnr_T)0, false);
+ ml_append(lnum, newp, 0, false);
new_lnum++;
xfree(newp);
- oldp = ml_get(lnum);
+ char *oldp = ml_get(lnum);
newp = xmalloc((size_t)col + (size_t)yanklen + 1);
// copy first part of line
memmove(newp, oldp, (size_t)col);
@@ -3608,7 +3558,7 @@ 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)) {
- if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) {
+ if (ml_append(lnum, y_array[i], 0, false) == FAIL) {
goto error;
}
new_lnum++;
@@ -3616,9 +3566,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
lnum++;
nr_lines++;
if (flags & PUT_FIXINDENT) {
- old_pos = curwin->w_cursor;
+ pos_T old_pos = curwin->w_cursor;
curwin->w_cursor.lnum = lnum;
- ptr = ml_get(lnum);
+ char *ptr = ml_get(lnum);
if (cnt == count && i == y_size - 1) {
lendiff = (int)strlen(ptr);
}
@@ -3679,21 +3629,21 @@ error:
ExtmarkOp kind = (y_type == kMTLineWise && !(flags & PUT_LINE_SPLIT))
? kExtmarkUndo : kExtmarkNOOP;
mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise),
- (linenr_T)MAXLNUM, nr_lines, 0L, kind);
+ (linenr_T)MAXLNUM, nr_lines, 0, kind);
// note changed text for displaying and folding
if (y_type == kMTCharWise) {
- changed_lines(curwin->w_cursor.lnum, col,
+ changed_lines(curbuf, curwin->w_cursor.lnum, col,
curwin->w_cursor.lnum + 1, nr_lines, true);
} else {
- changed_lines(curbuf->b_op_start.lnum, 0,
+ changed_lines(curbuf, curbuf->b_op_start.lnum, 0,
curbuf->b_op_start.lnum, nr_lines, true);
}
// Put the '] mark on the first byte of the last inserted character.
// Correct the length for change in indent.
curbuf->b_op_end.lnum = new_lnum;
- len = strlen(y_array[y_size - 1]);
+ size_t len = strlen(y_array[y_size - 1]);
col = (colnr_T)len - lendiff;
if (col > 1) {
curbuf->b_op_end.col = col - 1;
@@ -3742,6 +3692,15 @@ error:
msgmore(nr_lines);
curwin->w_set_curswant = true;
+ // Make sure the cursor is not after the NUL.
+ int len = (int)strlen(get_cursor_line_ptr());
+ if (curwin->w_cursor.col > len) {
+ if (cur_ve_flags == VE_ALL) {
+ curwin->w_cursor.coladd = curwin->w_cursor.col - len;
+ }
+ curwin->w_cursor.col = len;
+ }
+
end:
if (reg) {
do_autocmd_textputpost(regname, reg);
@@ -3768,7 +3727,7 @@ end:
/// there move it left.
void adjust_cursor_eol(void)
{
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
const bool adj_cursor = (curwin->w_cursor.col > 0
&& gchar_cursor() == NUL
@@ -3827,9 +3786,7 @@ void ex_display(exarg_T *eap)
{
char *p;
yankreg_T *yb;
- int name;
char *arg = eap->arg;
- int clen;
int type;
if (arg != NULL && *arg == NUL) {
@@ -3840,7 +3797,7 @@ void ex_display(exarg_T *eap)
// Highlight title
msg_puts_title(_("\nType Name Content"));
for (int i = -1; i < NUM_REGISTERS && !got_int; i++) {
- name = get_register_name(i);
+ int name = get_register_name(i);
switch (get_reg_type(name, NULL)) {
case kMTLineWise:
type = 'l'; break;
@@ -3895,9 +3852,9 @@ void ex_display(exarg_T *eap)
n -= 2;
}
for (p = yb->y_array[j];
- *p != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019
- clen = utfc_ptr2len(p);
- msg_outtrans_len(p, clen);
+ *p != NUL && (n -= ptr2cells(p)) >= 0; p++) {
+ int clen = utfc_ptr2len(p);
+ msg_outtrans_len(p, clen, 0);
p += clen - 1;
}
}
@@ -3910,7 +3867,7 @@ void ex_display(exarg_T *eap)
}
// display last inserted text
- if ((p = (char *)get_last_insert()) != NULL
+ if ((p = get_last_insert()) != NULL
&& (arg == NULL || vim_strchr(arg, '.') != NULL) && !got_int
&& !message_filtered(p)) {
msg_puts("\n c \". ");
@@ -3966,18 +3923,16 @@ void ex_display(exarg_T *eap)
static void dis_msg(const char *p, bool skip_esc)
FUNC_ATTR_NONNULL_ALL
{
- int n;
- int l;
-
- n = Columns - 6;
+ int n = Columns - 6;
while (*p != NUL
&& !(*p == ESC && skip_esc && *(p + 1) == NUL)
&& (n -= ptr2cells(p)) >= 0) {
+ int l;
if ((l = utfc_ptr2len(p)) > 1) {
- msg_outtrans_len(p, l);
+ msg_outtrans_len(p, l, 0);
p += l;
} else {
- msg_outtrans_len(p++, 1);
+ msg_outtrans_len(p++, 1, 0);
}
}
os_breakcheck();
@@ -3997,7 +3952,6 @@ static void dis_msg(const char *p, bool skip_esc)
char *skip_comment(char *line, bool process, bool include_space, bool *is_comment)
{
char *comment_flags = NULL;
- int lead_len;
int leader_offset = get_last_leader_offset(line, &comment_flags);
*is_comment = false;
@@ -4020,7 +3974,7 @@ char *skip_comment(char *line, bool process, bool include_space, bool *is_commen
return line;
}
- lead_len = get_leader_len(line, &comment_flags, false, include_space);
+ int lead_len = get_leader_len(line, &comment_flags, false, include_space);
if (lead_len == 0) {
return line;
@@ -4061,14 +4015,10 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
char *curr = NULL;
char *curr_start = NULL;
char *cend;
- char *newp;
- char *spaces; // number of spaces inserted before a line
int endcurr1 = NUL;
int endcurr2 = NUL;
int currsize = 0; // size of the current line
int sumsize = 0; // size of the long new line
- linenr_T t;
- colnr_T col = 0;
int ret = OK;
int *comments = NULL;
int remove_comments = (use_formatoptions == true)
@@ -4083,15 +4033,16 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// Allocate an array to store the number of spaces inserted before each
// line. We will use it to pre-compute the length of the new line and the
// proper placement of each original line in the new one.
- spaces = xcalloc(count, 1);
+ char *spaces = xcalloc(count, 1); // number of spaces inserted before a line
if (remove_comments) {
comments = xcalloc(count, sizeof(*comments));
}
- // Don't move anything, just compute the final line length
+ // Don't move anything yet, just compute the final line length
// and setup the array of space strings lengths
- for (t = 0; t < (linenr_T)count; t++) {
- curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t));
+ // This loops forward over joined lines.
+ for (linenr_T t = 0; t < (linenr_T)count; t++) {
+ curr_start = ml_get(curwin->w_cursor.lnum + t);
curr = curr_start;
if (t == 0 && setmark && (cmdmod.cmod_flags & CMOD_LOCKMARKS) == 0) {
// Set the '[ mark.
@@ -4162,14 +4113,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
}
// store the column position before last line
- col = sumsize - currsize - spaces[count - 1];
+ colnr_T col = sumsize - currsize - spaces[count - 1];
// allocate the space for the new line
- newp = xmalloc((size_t)sumsize + 1);
+ char *newp = xmalloc((size_t)sumsize + 1);
cend = newp + sumsize;
*cend = 0;
// Move affected lines to the new long one.
+ // This loops backwards over the joined lines, including the original line.
//
// Move marks from each deleted line to the joined line, adjusting the
// column. This is not Vi compatible, but Vi deletes the marks, thus that
@@ -4177,7 +4129,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
curbuf_splice_pending++;
- for (t = (linenr_T)count - 1;; t--) {
+ for (linenr_T t = (linenr_T)count - 1;; t--) {
cend -= currsize;
memmove(cend, curr, (size_t)currsize);
if (spaces[t] > 0) {
@@ -4189,9 +4141,9 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// what is added if it is inside these spaces.
const int spaces_removed = (int)((curr - curr_start) - spaces[t]);
linenr_T lnum = curwin->w_cursor.lnum + t;
- colnr_T mincol = (colnr_T)0;
+ colnr_T mincol = 0;
linenr_T lnum_amount = -t;
- long col_amount = (cend - newp - spaces_removed);
+ colnr_T col_amount = (colnr_T)(cend - newp - spaces_removed);
mark_col_adjust(lnum, mincol, lnum_amount, col_amount, spaces_removed);
@@ -4220,15 +4172,15 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions
// Only report the change in the first line here, del_lines() will report
// the deleted line.
- changed_lines(curwin->w_cursor.lnum, currsize,
- curwin->w_cursor.lnum + 1, 0L, true);
+ changed_lines(curbuf, curwin->w_cursor.lnum, currsize,
+ curwin->w_cursor.lnum + 1, 0, true);
// Delete following lines. To do this we move the cursor there
// briefly, and then move it back. After del_lines() the cursor may
// have moved up (last line deleted), so the current lnum is kept in t.
- t = curwin->w_cursor.lnum;
+ linenr_T t = curwin->w_cursor.lnum;
curwin->w_cursor.lnum++;
- del_lines((long)count - 1, false);
+ del_lines((int)count - 1, false);
curwin->w_cursor.lnum = t;
curbuf_splice_pending--;
curbuf->deleted_bytes2 = 0;
@@ -4290,11 +4242,6 @@ static void restore_lbr(bool lbr_saved)
static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool is_del)
{
int incr = 0;
- char *pend;
- char *pstart;
- char *line;
- char *prev_pstart;
- char *prev_pend;
// Avoid a problem with unwanted linebreaks in block mode.
const bool lbr_saved = reset_lbr();
@@ -4310,8 +4257,8 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
bdp->end_char_vcols = 0;
bdp->start_char_vcols = 0;
- line = ml_get(lnum);
- prev_pstart = line;
+ char *line = ml_get(lnum);
+ char *prev_pstart = line;
chartabsize_T cts;
init_chartabsize_arg(&cts, curwin, lnum, bdp->start_vcol, line, line);
@@ -4330,7 +4277,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
MB_PTR_ADV(cts.cts_ptr);
}
bdp->start_vcol = cts.cts_vcol;
- pstart = cts.cts_ptr;
+ char *pstart = cts.cts_ptr;
clear_chartabsize_arg(&cts);
bdp->start_char_vcols = incr;
@@ -4347,7 +4294,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
if (is_del && bdp->startspaces) {
bdp->startspaces = bdp->start_char_vcols - bdp->startspaces;
}
- pend = pstart;
+ char *pend = pstart;
bdp->end_vcol = bdp->start_vcol;
if (bdp->end_vcol > oap->end_vcol) { // it's all in one character
bdp->is_oneChar = true;
@@ -4369,7 +4316,7 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
}
} else {
init_chartabsize_arg(&cts, curwin, lnum, bdp->end_vcol, line, pend);
- prev_pend = pend;
+ char *prev_pend = pend;
while (cts.cts_vcol <= oap->end_vcol && *cts.cts_ptr != NUL) {
// Count a tab for what it's worth (if list mode not on)
prev_pend = cts.cts_ptr;
@@ -4421,7 +4368,6 @@ static void block_prep(oparg_T *oap, struct block_def *bdp, linenr_T lnum, bool
/// @param[in] g_cmd Prefixed with `g`.
void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
{
- pos_T pos;
struct block_def bd;
ssize_t change_cnt = 0;
linenr_T amount = Prenum1;
@@ -4432,7 +4378,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
disable_fold_update++;
if (!VIsual_active) {
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
if (u_save_cursor() == FAIL) {
disable_fold_update--;
return;
@@ -4440,10 +4386,9 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
change_cnt = do_addsub(oap->op_type, &pos, 0, amount);
disable_fold_update--;
if (change_cnt) {
- changed_lines(pos.lnum, 0, pos.lnum + 1, 0L, true);
+ changed_lines(curbuf, pos.lnum, 0, pos.lnum + 1, 0, true);
}
} else {
- int one_change;
int length;
pos_T startpos;
@@ -4453,7 +4398,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
return;
}
- pos = oap->start;
+ pos_T pos = oap->start;
for (; pos.lnum <= oap->end.lnum; pos.lnum++) {
if (oap->motion_type == kMTBlockWise) {
// Visual block mode
@@ -4483,7 +4428,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
length = oap->end.col - pos.col + 1;
}
}
- one_change = do_addsub(oap->op_type, &pos, length, amount);
+ int one_change = do_addsub(oap->op_type, &pos, length, amount);
if (one_change) {
// Remember the start position of the first change.
if (change_cnt == 0) {
@@ -4499,7 +4444,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
disable_fold_update--;
if (change_cnt) {
- changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true);
+ changed_lines(curbuf, oap->start.lnum, 0, oap->end.lnum + 1, 0, true);
}
if (!change_cnt && oap->is_VIsual) {
@@ -4514,8 +4459,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
}
if (change_cnt > p_report) {
- smsg(NGETTEXT("%" PRId64 " lines changed",
- "%" PRId64 " lines changed", change_cnt),
+ smsg(0, NGETTEXT("%" PRId64 " lines changed", "%" PRId64 " lines changed", change_cnt),
(int64_t)change_cnt);
}
}
@@ -4531,17 +4475,11 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
/// @return true if some character was changed.
int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
{
- int col;
char *buf1 = NULL;
char buf2[NUMBUFLEN];
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
uvarnumber_T n;
- uvarnumber_T oldn;
- char *ptr;
- int c;
- int todel;
- int firstdigit;
bool negative = false;
bool was_positive = true;
bool visual = VIsual_active;
@@ -4565,8 +4503,8 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
curwin->w_cursor = *pos;
- ptr = ml_get(pos->lnum);
- col = pos->col;
+ char *ptr = ml_get(pos->lnum);
+ int col = pos->col;
if (*ptr == NUL || col + !!save_coladd >= (int)strlen(ptr)) {
goto theend;
@@ -4659,7 +4597,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
// If a number was found, and saving for undo works, replace the number.
- firstdigit = (uint8_t)ptr[col];
+ int firstdigit = (uint8_t)ptr[col];
if (!ascii_isdigit(firstdigit) && !(do_alpha && ASCII_ISALPHA(firstdigit))) {
beep_flush();
goto theend;
@@ -4712,11 +4650,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
: length);
}
+ bool overflow = false;
vim_str2nr(ptr + col, &pre, &length,
0 + (do_bin ? STR2NR_BIN : 0)
+ (do_oct ? STR2NR_OCT : 0)
+ (do_hex ? STR2NR_HEX : 0),
- NULL, &n, maxlen, false);
+ NULL, &n, maxlen, false, &overflow);
// ignore leading '-' for hex, octal and bin numbers
if (pre && negative) {
@@ -4734,10 +4673,12 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
subtract ^= true;
}
- oldn = n;
+ uvarnumber_T oldn = n;
- n = subtract ? n - (uvarnumber_T)Prenum1
- : n + (uvarnumber_T)Prenum1;
+ if (!overflow) { // if number is too big don't add/subtract
+ n = subtract ? n - (uvarnumber_T)Prenum1
+ : n + (uvarnumber_T)Prenum1;
+ }
// handle wraparound for decimal numbers
if (!pre) {
@@ -4761,7 +4702,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
if (do_unsigned && negative) {
if (subtract) {
// sticking at zero.
- n = (uvarnumber_T)0;
+ n = 0;
} else {
// sticking at 2^64 - 1.
n = (uvarnumber_T)(-1);
@@ -4779,8 +4720,8 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
curwin->w_cursor.col = col;
startpos = curwin->w_cursor;
did_change = true;
- todel = length;
- c = gchar_cursor();
+ int todel = length;
+ int c = gchar_cursor();
// Don't include the '-' in the length, only the length of the part
// after it is kept the same.
@@ -4829,7 +4770,7 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
- while (bits > 0) {
+ while (bits > 0 && i < NUMBUFLEN - 1) {
buf2[i++] = ((n >> --bits) & 0x1) ? '1' : '0';
}
@@ -5015,7 +4956,7 @@ void *get_reg_contents(int regname, int flags)
if (flags & kGRegList) {
list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size);
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(list, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(list, reg->y_array[i], -1);
}
return list;
@@ -5084,7 +5025,7 @@ static void finish_write_reg(int name, yankreg_T *reg, yankreg_T *old_y_previous
/// @see write_reg_contents_ex
void write_reg_contents(int name, const char *str, ssize_t len, int must_append)
{
- write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0L);
+ write_reg_contents_ex(name, str, len, must_append, kMTUnknown, 0);
}
void write_reg_contents_lst(int name, char **strings, bool must_append, MotionType yank_type,
@@ -5095,8 +5036,7 @@ void write_reg_contents_lst(int name, char **strings, bool must_append, MotionTy
if (strings[0] == NULL) {
s = "";
} else if (strings[1] != NULL) {
- emsg(_("E883: search pattern and expression register may not "
- "contain two or more lines"));
+ emsg(_(e_search_pattern_and_expression_register_may_not_contain_two_or_more_lines));
return;
}
write_reg_contents_ex(name, s, -1, must_append, yank_type, block_len);
@@ -5214,7 +5154,7 @@ void write_reg_contents_ex(int name, const char *str, ssize_t len, bool must_app
/// @param str string or list of strings to put in register
/// @param len length of the string (Ignored when str_list=true.)
/// @param blocklen width of visual block, or -1 for "I don't know."
-/// @param str_list True if str is `char_u **`.
+/// @param str_list True if str is `char **`.
static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str, size_t len,
colnr_T blocklen, bool str_list)
FUNC_ATTR_NONNULL_ALL
@@ -5374,10 +5314,8 @@ static varnumber_T line_count_info(char *line, varnumber_T *wc, varnumber_T *cc,
/// @param dict when not NULL, store the info there instead of showing it.
void cursor_pos_info(dict_T *dict)
{
- char *p;
char buf1[50];
char buf2[40];
- linenr_T lnum;
varnumber_T byte_count = 0;
varnumber_T bom_count = 0;
varnumber_T byte_count_cursor = 0;
@@ -5385,9 +5323,6 @@ void cursor_pos_info(dict_T *dict)
varnumber_T char_count_cursor = 0;
varnumber_T word_count = 0;
varnumber_T word_count_cursor = 0;
- int eol_size;
- varnumber_T last_check = 100000L;
- long line_count_selected = 0;
pos_T min_pos, max_pos;
oparg_T oparg;
struct block_def bd;
@@ -5397,10 +5332,13 @@ void cursor_pos_info(dict_T *dict)
// Compute the length of the file in characters.
if (curbuf->b_ml.ml_flags & ML_EMPTY) {
if (dict == NULL) {
- msg(_(no_lines_msg));
+ msg(_(no_lines_msg), 0);
return;
}
} else {
+ int eol_size;
+ varnumber_T last_check = 100000;
+ int line_count_selected = 0;
if (get_fileformat(curbuf) == EOL_DOS) {
eol_size = 2;
} else {
@@ -5424,8 +5362,8 @@ void cursor_pos_info(dict_T *dict)
char *const saved_w_sbr = curwin->w_p_sbr;
// Make 'sbr' empty for a moment to get the correct size.
- p_sbr = empty_option;
- curwin->w_p_sbr = empty_option;
+ p_sbr = empty_string_option;
+ curwin->w_p_sbr = empty_string_option;
oparg.is_VIsual = true;
oparg.motion_type = kMTBlockWise;
oparg.op_type = OP_NOP;
@@ -5445,21 +5383,21 @@ void cursor_pos_info(dict_T *dict)
line_count_selected = max_pos.lnum - min_pos.lnum + 1;
}
- for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
+ for (linenr_T lnum = 1; lnum <= curbuf->b_ml.ml_line_count; lnum++) {
// Check for a CTRL-C every 100000 characters.
if (byte_count > last_check) {
os_breakcheck();
if (got_int) {
return;
}
- last_check = byte_count + 100000L;
+ last_check = byte_count + 100000;
}
// Do extra processing for VIsual mode.
if (l_VIsual_active
&& lnum >= min_pos.lnum && lnum <= max_pos.lnum) {
char *s = NULL;
- long len = 0L;
+ int len = 0;
switch (l_VIsual_mode) {
case Ctrl_V:
@@ -5467,7 +5405,7 @@ void cursor_pos_info(dict_T *dict)
block_prep(&oparg, &bd, lnum, false);
virtual_op = kNone;
s = bd.textstart;
- len = (long)bd.textlen;
+ len = bd.textlen;
break;
case 'V':
s = ml_get(lnum);
@@ -5490,7 +5428,7 @@ void cursor_pos_info(dict_T *dict)
if (lnum == curbuf->b_ml.ml_line_count
&& !curbuf->b_p_eol
&& (curbuf->b_p_bin || !curbuf->b_p_fixeol)
- && (long)strlen(s) < len) {
+ && (int)strlen(s) < len) {
byte_count_cursor -= eol_size;
}
}
@@ -5551,11 +5489,11 @@ void cursor_pos_info(dict_T *dict)
(int64_t)byte_count_cursor, (int64_t)byte_count);
}
} else {
- p = get_cursor_line_ptr();
+ char *p = get_cursor_line_ptr();
validate_virtcol();
col_print(buf1, sizeof(buf1), (int)curwin->w_cursor.col + 1,
(int)curwin->w_virtcol + 1);
- col_print((char *)buf2, sizeof(buf2), (int)strlen(p), linetabsize(p));
+ col_print(buf2, sizeof(buf2), (int)strlen(p), linetabsize_str(p));
if (char_count_cursor == byte_count_cursor
&& char_count == byte_count) {
@@ -5592,13 +5530,13 @@ void cursor_pos_info(dict_T *dict)
}
if (dict == NULL) {
// Don't shorten this message, the user asked for it.
- p = p_shm;
+ char *p = p_shm;
p_shm = "";
if (p_ch < 1) {
msg_start();
msg_scroll = true;
}
- msg(IObuff);
+ msg(IObuff, 0);
p_shm = p;
}
}
@@ -5607,7 +5545,7 @@ void cursor_pos_info(dict_T *dict)
// Don't shorten this message, the user asked for it.
tv_dict_add_nr(dict, S_LEN("words"), word_count);
tv_dict_add_nr(dict, S_LEN("chars"), char_count);
- tv_dict_add_nr(dict, S_LEN("bytes"), (varnumber_T)(byte_count + bom_count));
+ tv_dict_add_nr(dict, S_LEN("bytes"), byte_count + bom_count);
STATIC_ASSERT(sizeof("visual") == sizeof("cursor"),
"key_len argument in tv_dict_add_nr is wrong");
@@ -5631,7 +5569,7 @@ static void op_colon(oparg_T *oap)
if (oap->start.lnum == curwin->w_cursor.lnum) {
stuffcharReadbuff('.');
} else {
- stuffnumReadbuff((long)oap->start.lnum);
+ stuffnumReadbuff(oap->start.lnum);
}
// When using !! on a closed fold the range ".!" works best to operate
@@ -5652,7 +5590,7 @@ static void op_colon(oparg_T *oap)
stuffReadbuff(".+");
stuffnumReadbuff(oap->line_count - 1);
} else {
- stuffnumReadbuff((long)oap->end.lnum);
+ stuffnumReadbuff(oap->end.lnum);
}
}
}
@@ -5660,13 +5598,13 @@ static void op_colon(oparg_T *oap)
stuffReadbuff("!");
}
if (oap->op_type == OP_INDENT) {
- stuffReadbuff((const char *)get_equalprg());
+ stuffReadbuff(get_equalprg());
stuffReadbuff("\n");
} else if (oap->op_type == OP_FORMAT) {
if (*curbuf->b_p_fp != NUL) {
- stuffReadbuff((const char *)curbuf->b_p_fp);
+ stuffReadbuff(curbuf->b_p_fp);
} else if (*p_fp != NUL) {
- stuffReadbuff((const char *)p_fp);
+ stuffReadbuff(p_fp);
} else {
stuffReadbuff("fmt");
}
@@ -5680,11 +5618,12 @@ static void op_colon(oparg_T *oap)
static Callback opfunc_cb;
/// Process the 'operatorfunc' option value.
-void set_operatorfunc_option(char **errmsg)
+const char *did_set_operatorfunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(p_opfunc, &opfunc_cb) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
#if defined(EXITFREE)
@@ -5822,31 +5761,34 @@ typedef struct {
int rv_mode; ///< 'v', 'V', or Ctrl-V
linenr_T rv_line_count; ///< number of lines
colnr_T rv_vcol; ///< number of cols or end column
- long rv_count; ///< count for Visual operator
+ int rv_count; ///< count for Visual operator
int rv_arg; ///< extra argument
} redo_VIsual_T;
+static bool is_ex_cmdchar(cmdarg_T *cap)
+{
+ return cap->cmdchar == ':' || cap->cmdchar == K_COMMAND;
+}
+
/// Handle an operator after Visual mode or when the movement is finished.
/// "gui_yank" is true when yanking text for the clipboard.
void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
{
oparg_T *oap = cap->oap;
- pos_T old_cursor;
- bool empty_region_error;
- int restart_edit_save;
int lbr_saved = curwin->w_p_lbr;
// The visual area is remembered for redo
static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 };
- bool include_line_break = false;
-
- old_cursor = curwin->w_cursor;
+ pos_T old_cursor = curwin->w_cursor;
// If an operation is pending, handle it...
if ((finish_op
|| VIsual_active)
&& oap->op_type != OP_NOP) {
+ bool empty_region_error;
+ int restart_edit_save;
+ bool include_line_break = false;
// Yank can be redone when 'y' is in 'cpoptions', but not when yanking
// for the clipboard.
const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank;
@@ -5882,7 +5824,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if ((redo_yank || oap->op_type != OP_YANK)
&& ((!VIsual_active || oap->motion_force)
// Also redo Operator-pending Visual mode mappings.
- || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
+ || ((is_ex_cmdchar(cap) || cap->cmdchar == K_LUA)
&& oap->op_type != OP_COLON))
&& cap->cmdchar != 'D'
&& oap->op_type != OP_FOLD
@@ -5902,17 +5844,24 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
AppendToRedobuffLit(cap->searchbuf, -1);
}
AppendToRedobuff(NL_STR);
- } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) {
+ } else if (is_ex_cmdchar(cap)) {
// do_cmdline() has stored the first typed line in
// "repeat_cmdline". When several lines are typed repeating
// won't be possible.
if (repeat_cmdline == NULL) {
ResetRedobuff();
} else {
- AppendToRedobuffLit(repeat_cmdline, -1);
+ if (cap->cmdchar == ':') {
+ AppendToRedobuffLit(repeat_cmdline, -1);
+ } else {
+ AppendToRedobuffSpec(repeat_cmdline);
+ }
AppendToRedobuff(NL_STR);
XFREE_CLEAR(repeat_cmdline);
}
+ } else if (cap->cmdchar == K_LUA) {
+ AppendNumberToRedobuff(repeat_luaref);
+ AppendToRedobuff(NL_STR);
}
}
@@ -5968,8 +5917,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
} else if (VIsual_mode == 'v') {
// If 'selection' is "exclusive", backup one character for
// charwise selections.
- include_line_break =
- unadjust_for_sel();
+ include_line_break = unadjust_for_sel();
}
oap->start = VIsual;
@@ -6048,7 +5996,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
resel_VIsual_vcol = oap->end_vcol;
}
}
- resel_VIsual_line_count = (linenr_T)oap->line_count;
+ resel_VIsual_line_count = oap->line_count;
}
// can't redo yank (unless 'y' is in 'cpoptions') and ":"
@@ -6069,7 +6017,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
prep_redo(oap->regname, cap->count0,
get_op_char(oap->op_type), get_extra_op_char(oap->op_type),
oap->motion_force, cap->cmdchar, cap->nchar);
- } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) {
+ } else if (!is_ex_cmdchar(cap) && cap->cmdchar != K_LUA) {
int opchar = get_op_char(oap->op_type);
int extra_opchar = get_extra_op_char(oap->op_type);
int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL;
@@ -6083,9 +6031,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
if (opchar == 'g' && extra_opchar == '@') {
// also repeat the count for 'operatorfunc'
- prep_redo_num2(oap->regname, 0L, NUL, 'v', cap->count0, opchar, extra_opchar, nchar);
+ prep_redo_num2(oap->regname, 0, NUL, 'v', cap->count0, opchar, extra_opchar, nchar);
} else {
- prep_redo(oap->regname, 0L, NUL, 'v', opchar, extra_opchar, nchar);
+ prep_redo(oap->regname, 0, NUL, 'v', opchar, extra_opchar, nchar);
}
}
if (!redo_VIsual_busy) {
@@ -6207,7 +6155,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
switch (oap->op_type) {
case OP_LSHIFT:
case OP_RSHIFT:
- op_shift(oap, true, oap->is_VIsual ? (int)cap->count1 : 1);
+ op_shift(oap, true, oap->is_VIsual ? cap->count1 : 1);
auto_format(false, true);
break;
@@ -6275,6 +6223,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// Restore linebreak, so that when the user edits it looks as before.
restore_lbr(lbr_saved);
+ // trigger TextChangedI
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+
if (op_change(oap)) { // will call edit()
cap->retval |= CA_COMMAND_BUSY;
}
@@ -6306,8 +6257,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
break;
}
op_reindent(oap,
- *curbuf->b_p_inde != NUL ? get_expr_indent :
- get_c_indent);
+ *curbuf->b_p_inde != NUL ? get_expr_indent
+ : get_c_indent);
break;
}
@@ -6373,6 +6324,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// Restore linebreak, so that when the user edits it looks as before.
restore_lbr(lbr_saved);
+ // trigger TextChangedI
+ curbuf->b_last_changedtick_i = buf_get_changedtick(curbuf);
+
op_insert(oap, cap->count1);
// Reset linebreak, so that formatting works correctly.
@@ -6502,7 +6456,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
clipboard_didwarn = true;
// Do NOT error (emsg()) here--if it interrupts :redir we get into
// a weird state, stuck in "redirect mode".
- msg(MSG_NO_CLIP);
+ msg(MSG_NO_CLIP, 0);
}
// ... else, be silent (don't flood during :while, :redir, etc.).
goto end;
@@ -6525,7 +6479,7 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet, bool writing)
}
if (cb_flags & CB_UNNAMEDPLUS) {
- *name = (cb_flags & CB_UNNAMED && writing) ? '"': '+';
+ *name = (cb_flags & CB_UNNAMED && writing) ? '"' : '+';
target = &y_regs[PLUS_REGISTER];
} else {
*name = '*';
@@ -6694,7 +6648,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) {
goto err;
}
- reg->y_array[tv_idx++] = xstrdupnul((const char *)TV_LIST_ITEM_TV(li)->vval.v_string);
+ reg->y_array[tv_idx++] = xstrdupnul(TV_LIST_ITEM_TV(li)->vval.v_string);
});
if (reg->y_size > 0 && strlen(reg->y_array[reg->y_size - 1]) == 0) {
@@ -6755,7 +6709,7 @@ static void set_clipboard(int name, yankreg_T *reg)
list_T *const lines = tv_list_alloc((ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise));
for (size_t i = 0; i < reg->y_size; i++) {
- tv_list_append_string(lines, (const char *)reg->y_array[i], -1);
+ tv_list_append_string(lines, reg->y_array[i], -1);
}
char regtype;
@@ -6777,7 +6731,7 @@ static void set_clipboard(int name, yankreg_T *reg)
list_T *args = tv_list_alloc(3);
tv_list_append_list(args, lines);
- tv_list_append_string(args, &regtype, 1); // -V614
+ tv_list_append_string(args, &regtype, 1);
tv_list_append_string(args, ((char[]) { (char)name }), 1);
(void)eval_call_provider("clipboard", "set", args, true);
@@ -6965,14 +6919,14 @@ bcount_t get_region_bytecount(buf_T *buf, linenr_T start_lnum, linenr_T end_lnum
if (start_lnum == end_lnum) {
return end_col - start_col;
}
- const char *first = (const char *)ml_get_buf(buf, start_lnum, false);
+ const char *first = ml_get_buf(buf, start_lnum);
bcount_t deleted_bytes = (bcount_t)strlen(first) - start_col + 1;
for (linenr_T i = 1; i <= end_lnum - start_lnum - 1; i++) {
if (start_lnum + i > max_lnum) {
return deleted_bytes;
}
- deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i, false)) + 1;
+ deleted_bytes += (bcount_t)strlen(ml_get_buf(buf, start_lnum + i)) + 1;
}
if (end_lnum > max_lnum) {
return deleted_bytes;
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 75ea1853a0..67a613cbca 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -1,78 +1,82 @@
-#ifndef NVIM_OPS_H
-#define NVIM_OPS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
-#include "nvim/ascii.h"
-#include "nvim/eval/typval.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/extmark.h"
-#include "nvim/macros.h"
-#include "nvim/normal.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/extmark_defs.h" // IWYU pragma: keep
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
+#include "nvim/normal_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
typedef int (*Indenter)(void);
-// flags for do_put()
-#define PUT_FIXINDENT 1 // make indent look nice
-#define PUT_CURSEND 2 // leave cursor after end of new text
-#define PUT_CURSLINE 4 // leave cursor on last line of new text
-#define PUT_LINE 8 // put register as lines
-#define PUT_LINE_SPLIT 16 // split line for linewise register
-#define PUT_LINE_FORWARD 32 // put linewise register below Visual sel.
-#define PUT_BLOCK_INNER 64 // in block mode, do not add trailing spaces
+/// flags for do_put()
+enum {
+ PUT_FIXINDENT = 1, ///< make indent look nice
+ PUT_CURSEND = 2, ///< leave cursor after end of new text
+ PUT_CURSLINE = 4, ///< leave cursor on last line of new text
+ PUT_LINE = 8, ///< put register as lines
+ PUT_LINE_SPLIT = 16, ///< split line for linewise register
+ PUT_LINE_FORWARD = 32, ///< put linewise register below Visual sel.
+ PUT_BLOCK_INNER = 64, ///< in block mode, do not add trailing spaces
+};
-// Registers:
-// 0 = register for latest (unnamed) yank
-// 1..9 = registers '1' to '9', for deletes
-// 10..35 = registers 'a' to 'z'
-// 36 = delete register '-'
-// 37 = selection register '*'
-// 38 = clipboard register '+'
-#define DELETION_REGISTER 36
-#define NUM_SAVED_REGISTERS 37
-// The following registers should not be saved in ShaDa file:
-#define STAR_REGISTER 37
-#define PLUS_REGISTER 38
-#define NUM_REGISTERS 39
+/// Registers:
+/// 0 = register for latest (unnamed) yank
+/// 1..9 = registers '1' to '9', for deletes
+/// 10..35 = registers 'a' to 'z'
+/// 36 = delete register '-'
+/// 37 = selection register '*'
+/// 38 = clipboard register '+'
+enum {
+ DELETION_REGISTER = 36,
+ NUM_SAVED_REGISTERS = 37,
+ // The following registers should not be saved in ShaDa file:
+ STAR_REGISTER = 37,
+ PLUS_REGISTER = 38,
+ NUM_REGISTERS = 39,
+};
-// Operator IDs; The order must correspond to opchars[] in ops.c!
-#define OP_NOP 0 // no pending operation
-#define OP_DELETE 1 // "d" delete operator
-#define OP_YANK 2 // "y" yank operator
-#define OP_CHANGE 3 // "c" change operator
-#define OP_LSHIFT 4 // "<" left shift operator
-#define OP_RSHIFT 5 // ">" right shift operator
-#define OP_FILTER 6 // "!" filter operator
-#define OP_TILDE 7 // "g~" switch case operator
-#define OP_INDENT 8 // "=" indent operator
-#define OP_FORMAT 9 // "gq" format operator
-#define OP_COLON 10 // ":" colon operator
-#define OP_UPPER 11 // "gU" make upper case operator
-#define OP_LOWER 12 // "gu" make lower case operator
-#define OP_JOIN 13 // "J" join operator, only for Visual mode
-#define OP_JOIN_NS 14 // "gJ" join operator, only for Visual mode
-#define OP_ROT13 15 // "g?" rot-13 encoding
-#define OP_REPLACE 16 // "r" replace chars, only for Visual mode
-#define OP_INSERT 17 // "I" Insert column, only for Visual mode
-#define OP_APPEND 18 // "A" Append column, only for Visual mode
-#define OP_FOLD 19 // "zf" define a fold
-#define OP_FOLDOPEN 20 // "zo" open folds
-#define OP_FOLDOPENREC 21 // "zO" open folds recursively
-#define OP_FOLDCLOSE 22 // "zc" close folds
-#define OP_FOLDCLOSEREC 23 // "zC" close folds recursively
-#define OP_FOLDDEL 24 // "zd" delete folds
-#define OP_FOLDDELREC 25 // "zD" delete folds recursively
-#define OP_FORMAT2 26 // "gw" format operator, keeps cursor pos
-#define OP_FUNCTION 27 // "g@" call 'operatorfunc'
-#define OP_NR_ADD 28 // "<C-A>" Add to the number or alphabetic
- // character (OP_ADD conflicts with Perl)
-#define OP_NR_SUB 29 // "<C-X>" Subtract from the number or
- // alphabetic character
+/// Operator IDs; The order must correspond to opchars[] in ops.c!
+enum {
+ OP_NOP = 0, ///< no pending operation
+ OP_DELETE = 1, ///< "d" delete operator
+ OP_YANK = 2, ///< "y" yank operator
+ OP_CHANGE = 3, ///< "c" change operator
+ OP_LSHIFT = 4, ///< "<" left shift operator
+ OP_RSHIFT = 5, ///< ">" right shift operator
+ OP_FILTER = 6, ///< "!" filter operator
+ OP_TILDE = 7, ///< "g~" switch case operator
+ OP_INDENT = 8, ///< "=" indent operator
+ OP_FORMAT = 9, ///< "gq" format operator
+ OP_COLON = 10, ///< ":" colon operator
+ OP_UPPER = 11, ///< "gU" make upper case operator
+ OP_LOWER = 12, ///< "gu" make lower case operator
+ OP_JOIN = 13, ///< "J" join operator, only for Visual mode
+ OP_JOIN_NS = 14, ///< "gJ" join operator, only for Visual mode
+ OP_ROT13 = 15, ///< "g?" rot-13 encoding
+ OP_REPLACE = 16, ///< "r" replace chars, only for Visual mode
+ OP_INSERT = 17, ///< "I" Insert column, only for Visual mode
+ OP_APPEND = 18, ///< "A" Append column, only for Visual mode
+ OP_FOLD = 19, ///< "zf" define a fold
+ OP_FOLDOPEN = 20, ///< "zo" open folds
+ OP_FOLDOPENREC = 21, ///< "zO" open folds recursively
+ OP_FOLDCLOSE = 22, ///< "zc" close folds
+ OP_FOLDCLOSEREC = 23, ///< "zC" close folds recursively
+ OP_FOLDDEL = 24, ///< "zd" delete folds
+ OP_FOLDDELREC = 25, ///< "zD" delete folds recursively
+ OP_FORMAT2 = 26, ///< "gw" format operator, keeps cursor pos
+ OP_FUNCTION = 27, ///< "g@" call 'operatorfunc'
+ OP_NR_ADD = 28, ///< "<C-A>" Add to the number or alphabetic character
+ OP_NR_SUB = 29, ///< "<C-X>" Subtract from the number or alphabetic character
+};
/// Flags for get_reg_contents().
enum GRegFlags {
@@ -123,7 +127,17 @@ static inline int op_reg_index(const int regname)
}
}
+/// @see get_yank_register
+/// @return true when register should be inserted literally
+/// (selection or clipboard)
+static inline bool is_literal_register(const int regname)
+ FUNC_ATTR_CONST
+{
+ return regname == '*' || regname == '+';
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ops.h.generated.h"
#endif
-#endif // NVIM_OPS_H
+
+EXTERN LuaRef repeat_luaref INIT( = LUA_NOREF); ///< LuaRef for "."
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 387b94533c..96d6d8e01e 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// User-settable options. Checklist for adding a new option:
// - Put it in options.lua
// - For a global option: Add a variable for it in option_defs.h.
@@ -13,15 +10,13 @@
// add some code to didset_window_options().
// - For a buffer option, add some code to buf_copy_options().
// - For a buffer string option, add code to check_buf_options().
-// - If it's a numeric option, add any necessary bounds checks to
-// set_num_option().
+// - If it's a numeric option, add any necessary bounds checks to check_num_option_bounds().
// - If it's a list of flags, add some code in do_set(), search for WW_ALL.
// - Add documentation! doc/options.txt, and any other related places.
// - Add an entry in runtime/optwin.vim.
#define IN_OPTION_C
#include <assert.h>
-#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
@@ -30,38 +25,43 @@
#include <string.h>
#include "auto/config.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
+#include "nvim/eval/vars.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
-#include "nvim/locale.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/lua/executor.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
@@ -74,47 +74,46 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/input.h"
+#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/sign_defs.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-#ifdef MSWIN
-# include "nvim/os/pty_conpty_win.h"
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/arglist.h"
#endif
-#include "nvim/api/extmark.h"
-#include "nvim/api/private/helpers.h"
-#include "nvim/lua/executor.h"
-#include "nvim/os/input.h"
-#include "nvim/os/lang.h"
-static char e_unknown_option[]
+static const char e_unknown_option[]
= N_("E518: Unknown option");
-static char e_not_allowed_in_modeline[]
+static const char e_not_allowed_in_modeline[]
= N_("E520: Not allowed in a modeline");
-static char e_not_allowed_in_modeline_when_modelineexpr_is_off[]
+static const char e_not_allowed_in_modeline_when_modelineexpr_is_off[]
= N_("E992: Not allowed in a modeline when 'modelineexpr' is off");
-static char e_key_code_not_set[]
+static const char e_key_code_not_set[]
= N_("E846: Key code not set");
-static char e_number_required_after_equal[]
+static const char e_number_required_after_equal[]
= N_("E521: Number required after =");
-static char e_preview_window_already_exists[]
+static const char e_preview_window_already_exists[]
= N_("E590: A preview window already exists");
static char *p_term = NULL;
@@ -123,17 +122,30 @@ static char *p_ttytype = NULL;
// Saved values for when 'bin' is set.
static int p_et_nobin;
static int p_ml_nobin;
-static long p_tw_nobin;
-static long p_wm_nobin;
+static OptInt p_tw_nobin;
+static OptInt p_wm_nobin;
// Saved values for when 'paste' is set.
static int p_ai_nopaste;
static int p_et_nopaste;
-static long p_sts_nopaste;
-static long p_tw_nopaste;
-static long p_wm_nopaste;
+static OptInt p_sts_nopaste;
+static OptInt p_tw_nopaste;
+static OptInt p_wm_nopaste;
static char *p_vsts_nopaste;
+#define OPTION_COUNT ARRAY_SIZE(options)
+
+/// :set boolean option prefix
+typedef enum {
+ PREFIX_NO = 0, ///< "no" prefix
+ PREFIX_NONE, ///< no prefix
+ PREFIX_INV, ///< "inv" prefix
+} set_prefix_T;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "option.c.generated.h"
+#endif
+
// options[] is initialized here.
// The order of the options MUST be alphabetic for ":set all" and findoption().
// All option names MUST start with a lowercase letter (for findoption()).
@@ -145,141 +157,190 @@ static char *p_vsts_nopaste;
# include "options.generated.h"
#endif
-#define OPTION_COUNT ARRAY_SIZE(options)
-
-typedef enum {
- OP_NONE = 0,
- OP_ADDING, ///< "opt+=arg"
- OP_PREPENDING, ///< "opt^=arg"
- OP_REMOVING, ///< "opt-=arg"
-} set_op_T;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "option.c.generated.h"
-#endif
+static char *(p_bin_dep_opts[]) = {
+ "textwidth", "wrapmargin", "modeline", "expandtab", NULL
+};
+static char *(p_paste_dep_opts[]) = {
+ "autoindent", "expandtab", "ruler", "showmatch", "smarttab",
+ "softtabstop", "textwidth", "wrapmargin", "revins", "varsofttabstop", NULL
+};
void set_init_tablocal(void)
{
// susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal!
int ch_idx = findoption("cmdheight");
- p_ch = (long)options[ch_idx].def_val;
+ p_ch = (OptInt)(intptr_t)options[ch_idx].def_val;
}
-/// Initialize the options, first part.
-///
-/// Called only once from main(), just after creating the first buffer.
-/// If "clean_arg" is true, Nvim was started with --clean.
-///
-/// NOTE: ELOG() etc calls are not allowed here, as log location depends on
-/// env var expansion which depends on expression evaluation and other
-/// editor state initialized here. Do logging in set_init_2 or later.
-void set_init_1(bool clean_arg)
+/// Initialize the 'shell' option to a default value.
+static void set_init_default_shell(void)
{
- langmap_init();
-
// Find default value for 'shell' option.
// Don't use it if it is empty.
- {
- const char *shell = os_getenv("SHELL");
- if (shell != NULL) {
- if (vim_strchr(shell, ' ') != NULL) {
- const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL
- char *const cmd = xmalloc(len);
- snprintf(cmd, len, "\"%s\"", shell);
- set_string_default("sh", cmd, true);
- } else {
- set_string_default("sh", (char *)shell, false);
- }
+ const char *shell = os_getenv("SHELL");
+ if (shell != NULL) {
+ if (vim_strchr(shell, ' ') != NULL) {
+ const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL
+ char *const cmd = xmalloc(len);
+ snprintf(cmd, len, "\"%s\"", shell);
+ set_string_default("sh", cmd, true);
+ } else {
+ set_string_default("sh", (char *)shell, false);
}
}
+}
- // Set the default for 'backupskip' to include environment variables for
- // temp files.
- {
+/// Set the default for 'backupskip' to include environment variables for
+/// temp files.
+static void set_init_default_backupskip(void)
+{
#ifdef UNIX
- static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" };
+ static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" };
#else
- static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" };
+ static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" };
#endif
- garray_T ga;
- int opt_idx = findoption("backupskip");
+ garray_T ga;
+ int opt_idx = findoption("backupskip");
- ga_init(&ga, 1, 100);
- for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
- bool mustfree = true;
- char *p;
+ ga_init(&ga, 1, 100);
+ for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
+ bool mustfree = true;
+ char *p;
#ifdef UNIX
- if (*names[n] == NUL) {
+ if (*names[n] == NUL) {
# ifdef __APPLE__
- p = "/private/tmp";
+ p = "/private/tmp";
# else
- p = "/tmp";
+ p = "/tmp";
# endif
- mustfree = false;
- } else // NOLINT(readability/braces)
+ mustfree = false;
+ } else // NOLINT(readability/braces)
#endif
- {
- p = vim_getenv(names[n]);
- }
- if (p != NULL && *p != NUL) {
- // First time count the NUL, otherwise count the ','.
- const size_t len = strlen(p) + 3;
- char *item = xmalloc(len);
- xstrlcpy(item, p, len);
- add_pathsep(item);
- xstrlcat(item, "*", len);
- if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
- == NULL) {
- ga_grow(&ga, (int)len);
- if (!GA_EMPTY(&ga)) {
- STRCAT(ga.ga_data, ",");
- }
- STRCAT(ga.ga_data, p);
- add_pathsep(ga.ga_data);
- STRCAT(ga.ga_data, "*");
- ga.ga_len += (int)len;
+ {
+ p = vim_getenv(names[n]);
+ }
+ if (p != NULL && *p != NUL) {
+ // First time count the NUL, otherwise count the ','.
+ const size_t len = strlen(p) + 3;
+ char *item = xmalloc(len);
+ xstrlcpy(item, p, len);
+ add_pathsep(item);
+ xstrlcat(item, "*", len);
+ if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
+ == NULL) {
+ ga_grow(&ga, (int)len);
+ if (!GA_EMPTY(&ga)) {
+ STRCAT(ga.ga_data, ",");
}
- xfree(item);
- }
- if (mustfree) {
- xfree(p);
+ STRCAT(ga.ga_data, p);
+ add_pathsep(ga.ga_data);
+ STRCAT(ga.ga_data, "*");
+ ga.ga_len += (int)len;
}
+ xfree(item);
}
- if (ga.ga_data != NULL) {
- set_string_default("bsk", ga.ga_data, true);
+ if (mustfree) {
+ xfree(p);
}
}
+ if (ga.ga_data != NULL) {
+ set_string_default("bsk", ga.ga_data, true);
+ }
+}
- {
- // Initialize the 'cdpath' option's default value.
- char *cdpath = vim_getenv("CDPATH");
- if (cdpath != NULL) {
- char *buf = xmalloc(2 * strlen(cdpath) + 2);
- {
- buf[0] = ','; // start with ",", current dir first
- int j = 1;
- for (int i = 0; cdpath[i] != NUL; i++) {
- if (vim_ispathlistsep(cdpath[i])) {
- buf[j++] = ',';
- } else {
- if (cdpath[i] == ' ' || cdpath[i] == ',') {
- buf[j++] = '\\';
- }
- buf[j++] = cdpath[i];
- }
- }
- buf[j] = NUL;
- int opt_idx = findoption("cdpath");
- if (opt_idx >= 0) {
- options[opt_idx].def_val = buf;
- options[opt_idx].flags |= P_DEF_ALLOCED;
- } else {
- xfree(buf); // cannot happen
- }
+/// Initialize the 'cdpath' option to a default value.
+static void set_init_default_cdpath(void)
+{
+ char *cdpath = vim_getenv("CDPATH");
+ if (cdpath == NULL) {
+ return;
+ }
+
+ char *buf = xmalloc(2 * strlen(cdpath) + 2);
+ buf[0] = ','; // start with ",", current dir first
+ int j = 1;
+ for (int i = 0; cdpath[i] != NUL; i++) {
+ if (vim_ispathlistsep(cdpath[i])) {
+ buf[j++] = ',';
+ } else {
+ if (cdpath[i] == ' ' || cdpath[i] == ',') {
+ buf[j++] = '\\';
}
- xfree(cdpath);
+ buf[j++] = cdpath[i];
}
}
+ buf[j] = NUL;
+ int opt_idx = findoption("cdpath");
+ if (opt_idx >= 0) {
+ options[opt_idx].def_val = buf;
+ options[opt_idx].flags |= P_DEF_ALLOCED;
+ } else {
+ xfree(buf); // cannot happen
+ }
+ xfree(cdpath);
+}
+
+/// Expand environment variables and things like "~" for the defaults.
+/// If option_expand() returns non-NULL the variable is expanded. This can
+/// only happen for non-indirect options.
+/// Also set the default to the expanded value, so ":set" does not list
+/// them.
+/// Don't set the P_ALLOCED flag, because we don't want to free the
+/// default.
+static void set_init_expand_env(void)
+{
+ for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
+ vimoption_T *opt = &options[opt_idx];
+ if (opt->flags & P_NO_DEF_EXP) {
+ continue;
+ }
+ char *p;
+ if ((opt->flags & P_GETTEXT) && opt->var != NULL) {
+ p = _(*(char **)opt->var);
+ } else {
+ p = option_expand(opt_idx, NULL);
+ }
+ if (p != NULL) {
+ p = xstrdup(p);
+ *(char **)opt->var = p;
+ if (opt->flags & P_DEF_ALLOCED) {
+ xfree(opt->def_val);
+ }
+ opt->def_val = p;
+ opt->flags |= P_DEF_ALLOCED;
+ }
+ }
+}
+
+/// Initialize the encoding used for "default" in 'fileencodings'.
+static void set_init_fenc_default(void)
+{
+ // enc_locale() will try to find the encoding of the current locale.
+ // This will be used when "default" is used as encoding specifier
+ // in 'fileencodings'.
+ char *p = enc_locale();
+ if (p == NULL) {
+ // Use utf-8 as "default" if locale encoding can't be detected.
+ p = xmemdupz(S_LEN("utf-8"));
+ }
+ fenc_default = p;
+}
+
+/// Initialize the options, first part.
+///
+/// Called only once from main(), just after creating the first buffer.
+/// If "clean_arg" is true, Nvim was started with --clean.
+///
+/// NOTE: ELOG() etc calls are not allowed here, as log location depends on
+/// env var expansion which depends on expression evaluation and other
+/// editor state initialized here. Do logging in set_init_2 or later.
+void set_init_1(bool clean_arg)
+{
+ langmap_init();
+
+ set_init_default_shell();
+ set_init_default_backupskip();
+ set_init_default_cdpath();
char *backupdir = stdpaths_user_state_subpath("backup", 2, true);
const size_t backupdir_len = strlen(backupdir);
@@ -328,33 +389,7 @@ void set_init_1(bool clean_arg)
init_spell_chartab();
// Expand environment variables and things like "~" for the defaults.
- // If option_expand() returns non-NULL the variable is expanded. This can
- // only happen for non-indirect options.
- // Also set the default to the expanded value, so ":set" does not list
- // them.
- // Don't set the P_ALLOCED flag, because we don't want to free the
- // default.
- for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) {
- vimoption_T *opt = &options[opt_idx];
- if (opt->flags & P_NO_DEF_EXP) {
- continue;
- }
- char *p;
- if ((opt->flags & P_GETTEXT) && opt->var != NULL) {
- p = _(*(char **)opt->var);
- } else {
- p = option_expand(opt_idx, NULL);
- }
- if (p != NULL) {
- p = xstrdup(p);
- *(char **)opt->var = p;
- if (opt->flags & P_DEF_ALLOCED) {
- xfree(opt->def_val);
- }
- opt->def_val = p;
- opt->flags |= P_DEF_ALLOCED;
- }
- }
+ set_init_expand_env();
save_file_ff(curbuf); // Buffer is unchanged
@@ -364,22 +399,13 @@ void set_init_1(bool clean_arg)
// NOTE: mlterm's author is being asked to 'set' a variable
// instead of an environment variable due to inheritance.
if (os_env_exists("MLTERM")) {
- set_option_value_give_err("tbidi", 1L, NULL, 0);
+ set_option_value_give_err("tbidi", BOOLEAN_OPTVAL(true), 0);
}
didset_options2();
lang_init();
-
- // enc_locale() will try to find the encoding of the current locale.
- // This will be used when 'default' is used as encoding specifier
- // in 'fileencodings'
- char *p = enc_locale();
- if (p == NULL) {
- // use utf-8 as 'default' if locale encoding can't be detected.
- p = xmemdupz(S_LEN("utf-8"));
- }
- fenc_default = p;
+ set_init_fenc_default();
#ifdef HAVE_WORKING_LIBINTL
// GNU gettext 0.10.37 supports this feature: set the codeset used for
@@ -401,7 +427,7 @@ static void set_option_default(const int opt_idx, int opt_flags)
// pointer to variable for current option
vimoption_T *opt = &options[opt_idx];
- char_u *varp = (char_u *)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
+ void *varp = get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
uint32_t flags = opt->flags;
if (varp != NULL) { // skip hidden option, nothing to do for it
if (flags & P_STRING) {
@@ -420,19 +446,18 @@ static void set_option_default(const int opt_idx, int opt_flags)
if (opt->indir == PV_SCROLL) {
win_comp_scroll(curwin);
} else {
- long def_val = (long)opt->def_val;
- if ((long *)varp == &curwin->w_p_so
- || (long *)varp == &curwin->w_p_siso) {
+ OptInt def_val = (OptInt)(intptr_t)opt->def_val;
+ if ((OptInt *)varp == &curwin->w_p_so
+ || (OptInt *)varp == &curwin->w_p_siso) {
// 'scrolloff' and 'sidescrolloff' local values have a
// different default value than the global default.
- *(long *)varp = -1;
+ *(OptInt *)varp = -1;
} else {
- *(long *)varp = def_val;
+ *(OptInt *)varp = def_val;
}
// May also set global value for local option.
if (both) {
- *(long *)get_varp_scope(opt, OPT_GLOBAL) =
- def_val;
+ *(OptInt *)get_varp_scope(opt, OPT_GLOBAL) = def_val;
}
}
} else { // P_BOOL
@@ -531,11 +556,11 @@ static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
/// Set the Vi-default value of a number option.
/// Used for 'lines' and 'columns'.
-void set_number_default(char *name, long val)
+void set_number_default(char *name, OptInt val)
{
int opt_idx = findoption(name);
if (opt_idx >= 0) {
- options[opt_idx].def_val = (char *)(intptr_t)val;
+ options[opt_idx].def_val = (void *)(intptr_t)val;
}
}
@@ -547,14 +572,14 @@ void free_all_options(void)
if (options[i].indir == PV_NONE) {
// global option: free value and default value.
if ((options[i].flags & P_ALLOCED) && options[i].var != NULL) {
- free_string_option(*(char **)options[i].var);
+ optval_free(optval_from_varp(i, options[i].var));
}
if (options[i].flags & P_DEF_ALLOCED) {
- free_string_option(options[i].def_val);
+ optval_free(optval_from_varp(i, &options[i].def_val));
}
- } else if (options[i].var != VAR_WIN && (options[i].flags & P_STRING)) {
+ } else if (options[i].var != VAR_WIN) {
// buffer-local option: free global value
- clear_string_option((char **)options[i].var);
+ optval_free(optval_from_varp(i, options[i].var));
}
}
free_operatorfunc_option();
@@ -594,16 +619,16 @@ void set_init_3(void)
// set, but only if they have not been set before.
int idx_srr = findoption("srr");
int do_srr = (idx_srr < 0)
- ? false
- : !(options[idx_srr].flags & P_WAS_SET);
+ ? false
+ : !(options[idx_srr].flags & P_WAS_SET);
int idx_sp = findoption("sp");
int do_sp = (idx_sp < 0)
- ? false
- : !(options[idx_sp].flags & P_WAS_SET);
+ ? false
+ : !(options[idx_sp].flags & P_WAS_SET);
size_t len = 0;
char *p = (char *)invocation_path_tail(p_sh, &len);
- p = xstrnsave(p, len);
+ p = xmemdupz(p, len);
{
//
@@ -726,293 +751,206 @@ void ex_set(exarg_T *eap)
(void)do_set(eap->arg, flags);
}
-static void do_set_bool(int opt_idx, int opt_flags, int prefix, int nextchar, const char *varp,
- char **errmsg)
+/// Get the default value for a string option.
+static char *stropt_get_default_val(int opt_idx, uint64_t flags)
{
- varnumber_T value;
-
- // ":set opt!": invert
- // ":set opt&": reset to default value
- // ":set opt<": reset to global value
- if (nextchar == '!') {
- value = *(int *)(varp) ^ 1;
- } else if (nextchar == '&') {
- value = (int)(intptr_t)options[opt_idx].def_val;
- } else if (nextchar == '<') {
- // For 'autoread' -1 means to use global value.
- if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) {
- value = -1;
- } else {
- value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
- }
+ char *newval = options[opt_idx].def_val;
+ // expand environment variables and ~ since the default value was
+ // already expanded, only required when an environment variable was set
+ // later
+ if (newval == NULL) {
+ newval = empty_string_option;
+ } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) {
+ char *s = option_expand(opt_idx, newval);
+ if (s == NULL) {
+ s = newval;
+ }
+ newval = xstrdup(s);
} else {
- if (prefix == 2) {
- value = *(int *)varp ^ 1; // ":set invopt": invert
- } else {
- value = prefix; // ":set opt" or ":set noopt": set or reset
- }
+ newval = xstrdup(newval);
}
-
- *errmsg = set_bool_option(opt_idx, (char_u *)varp, (int)value, opt_flags);
+ return newval;
}
-static void do_set_num(int opt_idx, int opt_flags, char **argp, int nextchar, const set_op_T op,
- const char *varp, char *errbuf, size_t errbuflen, char **errmsg)
+/// Copy the new string value into allocated memory for the option.
+/// Can't use set_string_option_direct(), because we need to remove the
+/// backslashes.
+static char *stropt_copy_value(char *origval, char **argp, set_op_T op,
+ uint32_t flags FUNC_ATTR_UNUSED)
{
- varnumber_T value;
char *arg = *argp;
- // Different ways to set a number option:
- // & set to default value
- // < set to global value
- // <xx> accept special key codes for 'wildchar'
- // c accept any non-digit for 'wildchar'
- // [-]0-9 set number
- // other error
- arg++;
- if (nextchar == '&') {
- value = (long)(intptr_t)options[opt_idx].def_val;
- } else if (nextchar == '<') {
- // For 'undolevels' NO_LOCAL_UNDOLEVEL means to
- // use the global value.
- if ((long *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) {
- value = NO_LOCAL_UNDOLEVEL;
+ // get a bit too much
+ size_t newlen = strlen(arg) + 1;
+ if (op != OP_NONE) {
+ newlen += strlen(origval) + 1;
+ }
+ char *newval = xmalloc(newlen);
+ char *s = newval;
+
+ // Copy the string, skip over escaped chars.
+ // For MS-Windows backslashes before normal file name characters
+ // are not removed, and keep backslash at start, for "\\machine\path",
+ // but do remove it for "\\\\machine\\path".
+ // The reverse is found in escape_option_str_cmdline().
+ while (*arg != NUL && !ascii_iswhite(*arg)) {
+ if (*arg == '\\' && arg[1] != NUL
+#ifdef BACKSLASH_IN_FILENAME
+ && !((flags & P_EXPAND)
+ && vim_isfilec((uint8_t)arg[1])
+ && !ascii_iswhite(arg[1])
+ && (arg[1] != '\\'
+ || (s == newval && arg[2] != '\\')))
+#endif
+ ) {
+ arg++; // remove backslash
+ }
+ int i = utfc_ptr2len(arg);
+ if (i > 1) {
+ // copy multibyte char
+ memmove(s, arg, (size_t)i);
+ arg += i;
+ s += i;
} else {
- value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
- }
- } else if (((long *)varp == &p_wc
- || (long *)varp == &p_wcm)
- && (*arg == '<'
- || *arg == '^'
- || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1]))
- && !ascii_isdigit(*arg)))) {
- value = string_to_key(arg);
- if (value == 0 && (long *)varp != &p_wcm) {
- *errmsg = e_invarg;
- return;
- }
- } else if (*arg == '-' || ascii_isdigit(*arg)) {
- int i;
- // Allow negative, octal and hex numbers.
- vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0, true);
- if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) {
- *errmsg = e_number_required_after_equal;
- return;
+ *s++ = *arg++;
}
- } else {
- *errmsg = e_number_required_after_equal;
- return;
}
+ *s = NUL;
- if (op == OP_ADDING) {
- value = *(long *)varp + value;
- }
- if (op == OP_PREPENDING) {
- value = *(long *)varp * value;
- }
- if (op == OP_REMOVING) {
- value = *(long *)varp - value;
- }
- *errmsg = set_num_option(opt_idx, (char_u *)varp, (long)value,
- errbuf, errbuflen, opt_flags);
-}
-
-// Handle some special cases with string option values
-static void munge_string_opt_val(char **varp, char **oldval, char **const origval,
- char_u **const origval_l, char_u **const origval_g,
- char **const argp, char *const whichwrap, size_t whichwraplen,
- char **const save_argp)
-{
- // Set 'keywordprg' to ":help" if an empty
- // value was passed to :set by the user.
- if (varp == &p_kp && (**argp == NUL || **argp == ' ')) {
- *save_argp = *argp;
- *argp = ":help";
- } else if (varp == &p_bs && ascii_isdigit(**(char_u **)varp)) {
- // Convert 'backspace' number to string, for
- // adding, prepending and removing string.
- const int i = getdigits_int(varp, true, 0);
- switch (i) {
- case 0:
- *varp = empty_option;
- break;
- case 1:
- *varp = xstrdup("indent,eol");
- break;
- case 2:
- *varp = xstrdup("indent,eol,start");
- break;
- case 3:
- *varp = xstrdup("indent,eol,nostop");
- break;
- }
- xfree(*oldval);
- if (*origval == *oldval) {
- *origval = *varp;
- }
- if (*origval_l == (char_u *)(*oldval)) {
- *origval_l = *(char_u **)varp;
- }
- if (*origval_g == (char_u *)(*oldval)) {
- *origval_g = *(char_u **)varp;
- }
- *oldval = *varp;
- } else if (varp == &p_ww && ascii_isdigit(**argp)) {
- // Convert 'whichwrap' number to string, for backwards compatibility
- // with Vim 3.0.
- *whichwrap = NUL;
- int i = getdigits_int(argp, true, 0);
- if (i & 1) {
- xstrlcat(whichwrap, "b,", whichwraplen);
- }
- if (i & 2) {
- xstrlcat(whichwrap, "s,", whichwraplen);
- }
- if (i & 4) {
- xstrlcat(whichwrap, "h,l,", whichwraplen);
- }
- if (i & 8) {
- xstrlcat(whichwrap, "<,>,", whichwraplen);
- }
- if (i & 16) {
- xstrlcat(whichwrap, "[,],", whichwraplen);
- }
- if (*whichwrap != NUL) { // remove trailing ,
- whichwrap[strlen(whichwrap) - 1] = NUL;
- }
- *save_argp = *argp;
- *argp = whichwrap;
- } else if (**argp == '>' && (varp == &p_dir || varp == &p_bdir)) {
- // Remove '>' before 'dir' and 'bdir', for backwards compatibility with
- // version 3.0
- (*argp)++;
- }
+ *argp = arg;
+ return newval;
}
-/// Part of do_set() for string options.
-static void do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar, set_op_T op_arg,
- uint32_t flags, char *varp_arg, char *errbuf, size_t errbuflen,
- int *value_checked, char **errmsg)
+/// Expand environment variables and ~ in string option value 'newval'.
+static char *stropt_expand_envvar(int opt_idx, char *origval, char *newval, set_op_T op)
{
- char *arg = *argp;
- set_op_T op = op_arg;
- char *varp = varp_arg;
- char *save_arg = NULL;
- char *s = NULL;
- char_u *origval_l = NULL;
- char_u *origval_g = NULL;
- char whichwrap[80];
+ char *s = option_expand(opt_idx, newval);
+ if (s == NULL) {
+ return newval;
+ }
- // When using ":set opt=val" for a global option
- // with a local value the local value will be
- // reset, use the global value here.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- && ((int)options[opt_idx].indir & PV_BOTH)) {
- varp = (char *)options[opt_idx].var;
+ xfree(newval);
+ uint32_t newlen = (unsigned)strlen(s) + 1;
+ if (op != OP_NONE) {
+ newlen += (unsigned)strlen(origval) + 1;
}
+ newval = xmalloc(newlen);
+ STRCPY(newval, s);
- // The old value is kept until we are sure that the new value is valid.
- char *oldval = *(char **)varp;
+ return newval;
+}
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL);
- origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+/// Concatenate the original and new values of a string option, adding a "," if
+/// needed.
+static void stropt_concat_with_comma(char *origval, char *newval, set_op_T op, uint32_t flags)
+{
+ int len = 0;
+ int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL);
+ if (op == OP_ADDING) {
+ len = (int)strlen(origval);
+ // Strip a trailing comma, would get 2.
+ if (comma && len > 1
+ && (flags & P_ONECOMMA) == P_ONECOMMA
+ && origval[len - 1] == ','
+ && origval[len - 2] != '\\') {
+ len--;
+ }
+ memmove(newval + len + comma, newval, strlen(newval) + 1);
+ memmove(newval, origval, (size_t)len);
+ } else {
+ len = (int)strlen(newval);
+ STRMOVE(newval + len + comma, origval);
+ }
+ if (comma) {
+ newval[len] = ',';
+ }
+}
- // A global-local string option might have an empty option as value to
- // indicate that the global value should be used.
- if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == (char_u *)empty_option) {
- origval_l = origval_g;
+/// Remove a value from a string option. Copy string option value in "origval"
+/// to "newval" and then remove the string "strval" of length "len".
+static void stropt_remove_val(char *origval, char *newval, uint32_t flags, char *strval, int len)
+{
+ // Remove newval[] from origval[]. (Note: "len" has been set above
+ // and is used here).
+ STRCPY(newval, origval);
+ if (*strval) {
+ // may need to remove a comma
+ if (flags & P_COMMA) {
+ if (strval == origval) {
+ // include comma after string
+ if (strval[len] == ',') {
+ len++;
+ }
+ } else {
+ // include comma before string
+ strval--;
+ len++;
+ }
}
+ STRMOVE(newval + (strval - origval), strval + len);
}
+}
- char *origval;
- // When setting the local value of a global option, the old value may be
- // the global value.
- if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) {
- origval = *(char **)get_varp(&options[opt_idx]);
- } else {
- origval = oldval;
+/// Remove flags that appear twice in the string option value 'newval'.
+static void stropt_remove_dupflags(char *newval, uint32_t flags)
+{
+ char *s = newval;
+ // Remove flags that appear twice.
+ for (s = newval; *s;) {
+ // if options have P_FLAGLIST and P_ONECOMMA such as 'whichwrap'
+ if (flags & P_ONECOMMA) {
+ if (*s != ',' && *(s + 1) == ','
+ && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) {
+ // Remove the duplicated value and the next comma.
+ STRMOVE(s, s + 2);
+ continue;
+ }
+ } else {
+ if ((!(flags & P_COMMA) || *s != ',')
+ && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) {
+ STRMOVE(s, s + 1);
+ continue;
+ }
+ }
+ s++;
}
+}
+/// Get the string value specified for a ":set" command. The following set
+/// options are supported:
+/// set {opt}&
+/// set {opt}<
+/// set {opt}={val}
+/// set {opt}:{val}
+static char *stropt_get_newval(int nextchar, int opt_idx, char **argp, void *varp, char *origval,
+ set_op_T *op_arg, uint32_t flags)
+{
+ char *arg = *argp;
+ set_op_T op = *op_arg;
+ char *save_arg = NULL;
char *newval;
+ char *s = NULL;
if (nextchar == '&') { // set to default val
- newval = options[opt_idx].def_val;
- // expand environment variables and ~ since the default value was
- // already expanded, only required when an environment variable was set
- // later
- if (newval == NULL) {
- newval = empty_option;
- } else if (!(options[opt_idx].flags & P_NO_DEF_EXP)) {
- s = option_expand(opt_idx, newval);
- if (s == NULL) {
- s = newval;
- }
- newval = xstrdup(s);
- } else {
- newval = xstrdup(newval);
- }
+ newval = stropt_get_default_val(opt_idx, flags);
} else if (nextchar == '<') { // set to global val
newval = xstrdup(*(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL));
} else {
arg++; // jump to after the '=' or ':'
- munge_string_opt_val((char **)varp, &oldval, &origval, &origval_l, &origval_g, &arg,
- whichwrap, sizeof(whichwrap), &save_arg);
+ // Set 'keywordprg' to ":help" if an empty
+ // value was passed to :set by the user.
+ if (varp == &p_kp && (*arg == NUL || *arg == ' ')) {
+ save_arg = arg;
+ arg = ":help";
+ }
// Copy the new string into allocated memory.
- // Can't use set_string_option_direct(), because we need to remove the
- // backslashes.
-
- // get a bit too much
- size_t newlen = strlen(arg) + 1;
- if (op != OP_NONE) {
- newlen += strlen(origval) + 1;
- }
- newval = xmalloc(newlen);
- s = newval;
-
- // Copy the string, skip over escaped chars.
- // For MS-Windows backslashes before normal file name characters
- // are not removed, and keep backslash at start, for "\\machine\path",
- // but do remove it for "\\\\machine\\path".
- // The reverse is found in ExpandOldSetting().
- while (*arg != NUL && !ascii_iswhite(*arg)) {
- if (*arg == '\\' && arg[1] != NUL
-#ifdef BACKSLASH_IN_FILENAME
- && !((flags & P_EXPAND)
- && vim_isfilec((uint8_t)arg[1])
- && !ascii_iswhite(arg[1])
- && (arg[1] != '\\'
- || (s == newval && arg[2] != '\\')))
-#endif
- ) {
- arg++; // remove backslash
- }
- int i = utfc_ptr2len(arg);
- if (i > 1) {
- // copy multibyte char
- memmove(s, arg, (size_t)i);
- arg += i;
- s += i;
- } else {
- *s++ = *arg++;
- }
- }
- *s = NUL;
+ newval = stropt_copy_value(origval, &arg, op, flags);
// Expand environment variables and ~.
// Don't do it when adding without inserting a comma.
if (op == OP_NONE || (flags & P_COMMA)) {
- s = option_expand(opt_idx, newval);
- if (s != NULL) {
- xfree(newval);
- newlen = (unsigned)strlen(s) + 1;
- if (op != OP_NONE) {
- newlen += (unsigned)strlen(origval) + 1;
- }
- newval = xmalloc(newlen);
- STRCPY(newval, s);
- }
+ newval = stropt_expand_envvar(opt_idx, origval, newval, op);
}
// locate newval[] in origval[] when removing it
@@ -1036,129 +974,26 @@ static void do_set_string(int opt_idx, int opt_flags, char **argp, int nextchar,
// concatenate the two strings; add a ',' if needed
if (op == OP_ADDING || op == OP_PREPENDING) {
- int comma = ((flags & P_COMMA) && *origval != NUL && *newval != NUL);
- if (op == OP_ADDING) {
- len = (int)strlen(origval);
- // Strip a trailing comma, would get 2.
- if (comma && len > 1
- && (flags & P_ONECOMMA) == P_ONECOMMA
- && origval[len - 1] == ','
- && origval[len - 2] != '\\') {
- len--;
- }
- memmove(newval + len + comma, newval, strlen(newval) + 1);
- memmove(newval, origval, (size_t)len);
- } else {
- len = (int)strlen(newval);
- STRMOVE(newval + len + comma, origval);
- }
- if (comma) {
- newval[len] = ',';
- }
- }
-
- // Remove newval[] from origval[]. (Note: "len" has been set above and
- // is used here).
- if (op == OP_REMOVING) {
- STRCPY(newval, origval);
- if (*s) {
- // may need to remove a comma
- if (flags & P_COMMA) {
- if (s == origval) {
- // include comma after string
- if (s[len] == ',') {
- len++;
- }
- } else {
- // include comma before string
- s--;
- len++;
- }
- }
- STRMOVE(newval + (s - origval), s + len);
- }
+ stropt_concat_with_comma(origval, newval, op, flags);
+ } else if (op == OP_REMOVING) {
+ // Remove newval[] from origval[]. (Note: "len" has been set above
+ // and is used here).
+ stropt_remove_val(origval, newval, flags, s, len);
}
if (flags & P_FLAGLIST) {
// Remove flags that appear twice.
- for (s = newval; *s;) {
- // if options have P_FLAGLIST and P_ONECOMMA such as
- // 'whichwrap'
- if (flags & P_ONECOMMA) {
- if (*s != ',' && *(s + 1) == ','
- && vim_strchr(s + 2, (uint8_t)(*s)) != NULL) {
- // Remove the duplicated value and the next comma.
- STRMOVE(s, s + 2);
- continue;
- }
- } else {
- if ((!(flags & P_COMMA) || *s != ',')
- && vim_strchr(s + 1, (uint8_t)(*s)) != NULL) {
- STRMOVE(s, s + 1);
- continue;
- }
- }
- s++;
- }
- }
-
- if (save_arg != NULL) {
- arg = save_arg; // arg was temporarily changed, restore it
- }
- }
-
- // Set the new value.
- *(char_u **)(varp) = (char_u *)newval;
-
- // origval may be freed by did_set_string_option(), make a copy.
- char *saved_origval = (origval != NULL) ? xstrdup(origval) : NULL;
- char *saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : NULL;
- char *saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : NULL;
-
- // newval (and varp) may become invalid if the buffer is closed by
- // autocommands.
- char *saved_newval = (newval != NULL) ? xstrdup(newval) : NULL;
-
- {
- uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
- const int secure_saved = secure;
-
- // When an option is set in the sandbox, from a modeline or in secure
- // mode, then deal with side effects in secure mode. Also when the
- // value was set with the P_INSECURE flag and is not completely
- // replaced.
- if ((opt_flags & OPT_MODELINE)
- || sandbox != 0
- || (op != OP_NONE && (*p & P_INSECURE))) {
- secure = 1;
+ stropt_remove_dupflags(newval, flags);
}
-
- // Handle side effects, and set the global value for ":set" on local
- // options. Note: when setting 'syntax' or 'filetype' autocommands may
- // be triggered that can cause havoc.
- *errmsg = did_set_string_option(opt_idx, (char **)varp, oldval,
- errbuf, errbuflen,
- opt_flags, value_checked);
-
- secure = secure_saved;
}
- if (*errmsg == NULL) {
- if (!starting) {
- trigger_optionset_string(opt_idx, opt_flags, saved_origval, saved_origval_l,
- saved_origval_g, saved_newval);
- }
- if (options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- STRING_OBJ(cstr_as_string(saved_newval)));
- }
+ if (save_arg != NULL) {
+ arg = save_arg; // arg was temporarily changed, restore it
}
- xfree(saved_origval);
- xfree(saved_origval_l);
- xfree(saved_origval_g);
- xfree(saved_newval);
-
*argp = arg;
+ *op_arg = op;
+
+ return newval;
}
static set_op_T get_op(const char *arg)
@@ -1176,17 +1011,17 @@ static set_op_T get_op(const char *arg)
return op;
}
-static int get_option_prefix(char **argp)
+static set_prefix_T get_option_prefix(char **argp)
{
if (strncmp(*argp, "no", 2) == 0) {
*argp += 2;
- return 0;
+ return PREFIX_NO;
} else if (strncmp(*argp, "inv", 3) == 0) {
*argp += 3;
- return 2;
+ return PREFIX_INV;
}
- return 1;
+ return PREFIX_NONE;
}
/// @param[in] arg Pointer to start option name
@@ -1216,7 +1051,7 @@ static int parse_option_name(char *arg, int *keyp, int *lenp, int *opt_idxp)
return FAIL;
}
if (arg[1] == 't' && arg[2] == '_') { // could be term code
- opt_idx = findoption_len((const char *)arg + 1, (size_t)(len - 1));
+ opt_idx = findoption_len(arg + 1, (size_t)(len - 1));
}
len++;
if (opt_idx == -1) {
@@ -1232,7 +1067,7 @@ static int parse_option_name(char *arg, int *keyp, int *lenp, int *opt_idxp)
len++;
}
}
- opt_idx = findoption_len((const char *)arg, (size_t)len);
+ opt_idx = findoption_len(arg, (size_t)len);
if (opt_idx == -1) {
key = find_key_option(arg, false);
}
@@ -1245,11 +1080,11 @@ static int parse_option_name(char *arg, int *keyp, int *lenp, int *opt_idxp)
return OK;
}
-static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t flags, int prefix,
- char **errmsg)
+static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t flags,
+ set_prefix_T prefix, const char **errmsg)
{
// Only bools can have a prefix of 'inv' or 'no'
- if (!(flags & P_BOOL) && prefix != 1) {
+ if (!(flags & P_BOOL) && prefix != PREFIX_NONE) {
*errmsg = e_invarg;
return FAIL;
}
@@ -1297,37 +1132,141 @@ static int validate_opt_idx(win_T *win, int opt_idx, int opt_flags, uint32_t fla
return OK;
}
-static void do_set_option_value(int opt_idx, int opt_flags, char **argp, int prefix, int nextchar,
- set_op_T op, uint32_t flags, char *varp, char *errbuf,
- size_t errbuflen, char **errmsg)
+/// Get new option value from argp. Allocated OptVal must be freed by caller.
+static OptVal get_option_newval(int opt_idx, int opt_flags, set_prefix_T prefix, char **argp,
+ int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf,
+ const size_t errbuflen, const char **errmsg)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- int value_checked = false;
- if (flags & P_BOOL) { // boolean
- do_set_bool(opt_idx, opt_flags, prefix, nextchar, varp, errmsg);
- } else if (flags & P_NUM) { // numeric
- do_set_num(opt_idx, opt_flags, argp, nextchar, op, varp, errbuf, errbuflen, errmsg);
- } else if (opt_idx >= 0) { // string.
- do_set_string(opt_idx, opt_flags, argp, nextchar, op, flags, varp, errbuf,
- errbuflen, &value_checked, errmsg);
- } else {
- // key code option(FIXME(tarruda): Show a warning or something
- // similar)
- }
+ assert(varp != NULL);
- if (*errmsg != NULL) {
- return;
+ vimoption_T *opt = &options[opt_idx];
+ char *arg = *argp;
+ // When setting the local value of a global option, the old value may be the global value.
+ const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL);
+ OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
+ OptVal newval = NIL_OPTVAL;
+
+ switch (oldval.type) {
+ case kOptValTypeNil:
+ abort();
+ case kOptValTypeBoolean: {
+ TriState newval_bool;
+
+ // ":set opt!": invert
+ // ":set opt&": reset to default value
+ // ":set opt<": reset to global value
+ if (nextchar == '!') {
+ switch (oldval.data.boolean) {
+ case kNone:
+ newval_bool = kNone;
+ break;
+ case kTrue:
+ newval_bool = kFalse;
+ break;
+ case kFalse:
+ newval_bool = kTrue;
+ break;
+ }
+ } else if (nextchar == '&') {
+ newval_bool = TRISTATE_FROM_INT((int)(intptr_t)options[opt_idx].def_val);
+ } else if (nextchar == '<') {
+ // For 'autoread', kNone means to use global value.
+ if ((int *)varp == &curbuf->b_p_ar && opt_flags == OPT_LOCAL) {
+ newval_bool = kNone;
+ } else {
+ newval_bool = TRISTATE_FROM_INT(*(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL));
+ }
+ } else {
+ // ":set invopt": invert
+ // ":set opt" or ":set noopt": set or reset
+ if (prefix == PREFIX_INV) {
+ newval_bool = *(int *)varp ^ 1;
+ } else {
+ newval_bool = prefix == PREFIX_NO ? 0 : 1;
+ }
+ }
+
+ newval = BOOLEAN_OPTVAL(newval_bool);
+ break;
}
+ case kOptValTypeNumber: {
+ OptInt oldval_num = oldval.data.number;
+ OptInt newval_num;
+
+ // Different ways to set a number option:
+ // & set to default value
+ // < set to global value
+ // <xx> accept special key codes for 'wildchar'
+ // c accept any non-digit for 'wildchar'
+ // [-]0-9 set number
+ // other error
+ arg++;
+ if (nextchar == '&') {
+ newval_num = (OptInt)(intptr_t)options[opt_idx].def_val;
+ } else if (nextchar == '<') {
+ if ((OptInt *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) {
+ // for 'undolevels' NO_LOCAL_UNDOLEVEL means using the global newval_num
+ newval_num = NO_LOCAL_UNDOLEVEL;
+ } else if (opt_flags == OPT_LOCAL
+ && ((OptInt *)varp == &curwin->w_p_siso || (OptInt *)varp == &curwin->w_p_so)) {
+ // for 'scrolloff'/'sidescrolloff' -1 means using the global newval_num
+ newval_num = -1;
+ } else {
+ newval_num = *(OptInt *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ }
+ } else if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm)
+ && (*arg == '<' || *arg == '^'
+ || (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1]))
+ && !ascii_isdigit(*arg)))) {
+ newval_num = string_to_key(arg);
+ if (newval_num == 0 && (OptInt *)varp != &p_wcm) {
+ *errmsg = e_invarg;
+ return newval;
+ }
+ } else if (*arg == '-' || ascii_isdigit(*arg)) {
+ int i;
+ // Allow negative, octal and hex numbers.
+ vim_str2nr(arg, NULL, &i, STR2NR_ALL, &newval_num, NULL, 0, true, NULL);
+ if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) {
+ *errmsg = e_number_required_after_equal;
+ return newval;
+ }
+ } else {
+ *errmsg = e_number_required_after_equal;
+ return newval;
+ }
- if (opt_idx >= 0) {
- did_set_option(opt_idx, opt_flags, op == OP_NONE, value_checked);
+ if (op == OP_ADDING) {
+ newval_num = oldval_num + newval_num;
+ }
+ if (op == OP_PREPENDING) {
+ newval_num = oldval_num * newval_num;
+ }
+ if (op == OP_REMOVING) {
+ newval_num = oldval_num - newval_num;
+ }
+
+ newval = NUMBER_OPTVAL(newval_num);
+ break;
+ }
+ case kOptValTypeString: {
+ char *oldval_str = oldval.data.string.data;
+ // Get the new value for the option
+ char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op, flags);
+ newval = CSTR_AS_OPTVAL(newval_str);
+ break;
}
+ }
+
+ return newval;
}
-static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf,
- size_t errbuflen, char **errmsg)
+static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf,
+ size_t errbuflen, const char **errmsg)
{
// 1: nothing, 0: "no", 2: "inv" in front of name
- int prefix = get_option_prefix(argp);
+ set_prefix_T prefix = get_option_prefix(argp);
char *arg = *argp;
@@ -1361,7 +1300,7 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb
}
uint32_t flags; // flags for current option
- char *varp = NULL; // pointer to variable for current option
+ void *varp = NULL; // pointer to variable for current option
if (opt_idx >= 0) {
if (options[opt_idx].var == NULL) { // hidden option: skip
@@ -1406,7 +1345,7 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb
// '=' character per "set" command line. grrr. (jw)
//
if (nextchar == '?'
- || (prefix == 1
+ || (prefix == PREFIX_NONE
&& vim_strchr("=:&<", nextchar) == NULL
&& !(flags & P_BOOL))) {
// print value
@@ -1420,7 +1359,7 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb
showoneopt(&options[opt_idx], opt_flags);
if (p_verbose > 0) {
// Mention where the option was last set.
- if (varp == (char *)options[opt_idx].var) {
+ if (varp == options[opt_idx].var) {
option_last_set_msg(options[opt_idx].last_set);
} else if ((int)options[opt_idx].indir & PV_WIN) {
option_last_set_msg(curwin->w_p_script_ctx[(int)options[opt_idx].indir & PV_MASK]);
@@ -1455,8 +1394,19 @@ static void do_set_option(int opt_flags, char **argp, bool *did_show, char *errb
}
}
- do_set_option_value(opt_idx, opt_flags, argp, prefix, nextchar, op, flags, varp,
- errbuf, errbuflen, errmsg);
+ // Don't try to change hidden option.
+ if (varp == NULL) {
+ return;
+ }
+
+ OptVal newval = get_option_newval(opt_idx, opt_flags, prefix, argp, nextchar, op, flags, varp,
+ errbuf, errbuflen, errmsg);
+
+ if (newval.type == kOptValTypeNil || *errmsg != NULL) {
+ return;
+ }
+
+ *errmsg = set_option(opt_idx, varp, newval, opt_flags, op == OP_NONE, errbuf, errbuflen);
}
/// Parse 'arg' for option settings.
@@ -1502,10 +1452,10 @@ int do_set(char *arg, int opt_flags)
}
} else {
char *startarg = arg; // remember for error message
- char *errmsg = NULL;
+ const char *errmsg = NULL;
char errbuf[80];
- do_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg);
+ do_one_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg);
// Advance to next argument.
// - skip until a blank found, taking care of backslashes
@@ -1524,7 +1474,7 @@ int do_set(char *arg, int opt_flags)
int i = (int)strlen(IObuff) + 2;
if (i + (arg - startarg) < IOSIZE) {
// append the argument with the error
- STRCAT(IObuff, ": ");
+ xstrlcat(IObuff, ": ", IOSIZE);
assert(arg >= startarg);
memmove(IObuff + i, startarg, (size_t)(arg - startarg));
IObuff[i + (arg - startarg)] = NUL;
@@ -1549,7 +1499,6 @@ int do_set(char *arg, int opt_flags)
silent_mode = false;
info_message = true; // use os_msg(), not os_errmsg()
msg_putchar('\n');
- ui_flush();
silent_mode = true;
info_message = false; // use os_msg(), not os_errmsg()
}
@@ -1557,29 +1506,6 @@ int do_set(char *arg, int opt_flags)
return OK;
}
-/// Call this when an option has been given a new value through a user command.
-/// Sets the P_WAS_SET flag and takes care of the P_INSECURE flag.
-///
-/// @param opt_flags possibly with OPT_MODELINE
-/// @param new_value value was replaced completely
-/// @param value_checked value was checked to be safe, no need to set P_INSECURE
-void did_set_option(int opt_idx, int opt_flags, int new_value, int value_checked)
-{
- options[opt_idx].flags |= P_WAS_SET;
-
- // When an option is set in the sandbox, from a modeline or in secure mode
- // set the P_INSECURE flag. Otherwise, if a new value is stored reset the
- // flag.
- uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
- if (!value_checked && (secure
- || sandbox != 0
- || (opt_flags & OPT_MODELINE))) {
- *p = *p | P_INSECURE;
- } else if (new_value) {
- *p = *p & ~P_INSECURE;
- }
-}
-
/// Convert a key name or string into a key value.
/// Used for 'wildchar' and 'cedit' options.
int string_to_key(char *arg)
@@ -1654,6 +1580,9 @@ void set_options_bin(int oldval, int newval, int opt_flags)
p_et = p_et_nobin;
}
}
+
+ // Remember where the dependent option were reset
+ didset_options_sctx(opt_flags, p_bin_dep_opts);
}
/// Find the parameter represented by the given character (eg ', :, ", or /),
@@ -1717,8 +1646,8 @@ static char *option_expand(int opt_idx, char *val)
// For 'spellsuggest' expand after "file:".
expand_env_esc(val, NameBuff, MAXPATHL,
(char **)options[opt_idx].var == &p_tags, false,
- (char_u **)options[opt_idx].var == (char_u **)&p_sps ? "file:" :
- NULL);
+ (char **)options[opt_idx].var == &p_sps ? "file:"
+ : NULL);
if (strcmp(NameBuff, val) == 0) { // they are the same
return NULL;
}
@@ -1740,9 +1669,9 @@ static void didset_options(void)
(void)compile_cap_prog(curwin->w_s);
(void)did_set_spell_option(true);
// set cedit_key
- (void)check_cedit();
+ (void)did_set_cedit(NULL);
// initialize the table for 'breakat'.
- fill_breakat_flags();
+ (void)did_set_breakat(NULL);
didset_window_options(curwin, true);
}
@@ -1753,10 +1682,10 @@ static void didset_options2(void)
highlight_changed();
// Parse default for 'fillchars'.
- (void)set_chars_option(curwin, &curwin->w_p_fcs, true);
+ (void)set_fillchars_option(curwin, curwin->w_p_fcs, true);
// Parse default for 'listchars'.
- (void)set_chars_option(curwin, &curwin->w_p_lcs, true);
+ (void)set_listchars_option(curwin, curwin->w_p_lcs, true);
// Parse default for 'wildmode'.
check_opt_wim();
@@ -1795,7 +1724,7 @@ int was_set_insecurely(win_T *const wp, char *opt, int opt_flags)
/// "opt_idx". For some local options a local flags field is used.
/// NOTE: Caller must make sure that "wp" is set to the window from which
/// the option is used.
-static uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
+uint32_t *insecure_flag(win_T *const wp, int opt_idx, int opt_flags)
{
if (opt_flags & OPT_LOCAL) {
assert(wp != NULL);
@@ -1851,7 +1780,7 @@ void check_blending(win_T *wp)
/// Handle setting `winhighlight' in window "wp"
bool parse_winhl_opt(win_T *wp)
{
- const char *p = (const char *)wp->w_p_winhl;
+ const char *p = wp->w_p_winhl;
if (!*p) {
if (wp->w_ns_hl_winhl && wp->w_ns_hl == wp->w_ns_hl_winhl) {
@@ -1882,6 +1811,9 @@ bool parse_winhl_opt(win_T *wp)
char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
+ if (hl_id == 0) {
+ return false;
+ }
int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
HlAttrs attrs = HLATTRS_INIT;
@@ -1895,6 +1827,18 @@ bool parse_winhl_opt(win_T *wp)
return true;
}
+/// Get the script context of global option "name".
+sctx_T *get_option_sctx(const char *const name)
+{
+ int idx = findoption(name);
+
+ if (idx >= 0) {
+ return &options[idx].last_set.script_ctx;
+ }
+ siemsg("no such option: %s", name);
+ return NULL;
+}
+
/// Set the script_ctx for an option, taking care of setting the buffer- or
/// window-local value.
void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
@@ -1922,737 +1866,1127 @@ void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx)
curbuf->b_p_script_ctx[indir & PV_MASK] = last_set;
} else if (indir & PV_WIN) {
curwin->w_p_script_ctx[indir & PV_MASK] = last_set;
+ if (both) {
+ // also setting the "all buffers" value
+ curwin->w_allbuf_opt.wo_script_ctx[indir & PV_MASK] = last_set;
+ }
}
}
}
/// Apply the OptionSet autocommand.
-static void apply_optionset_autocmd(int opt_idx, long opt_flags, long oldval, long oldval_g,
- long newval, const char *errmsg)
+static void apply_optionset_autocmd(int opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g,
+ OptVal oldval_l, OptVal newval, const char *errmsg)
{
// Don't do this while starting up, failure or recursively.
if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
return;
}
- char buf_old[12], buf_old_global[12], buf_new[12], buf_type[12];
+ char buf_type[7];
+ typval_T oldval_tv = optval_as_tv(oldval);
+ typval_T oldval_g_tv = optval_as_tv(oldval_g);
+ typval_T oldval_l_tv = optval_as_tv(oldval_l);
+ typval_T newval_tv = optval_as_tv(newval);
- vim_snprintf(buf_old, sizeof(buf_old), "%ld", oldval);
- vim_snprintf(buf_old_global, sizeof(buf_old_global), "%ld", oldval_g);
- vim_snprintf(buf_new, sizeof(buf_new), "%ld", newval);
- vim_snprintf(buf_type, sizeof(buf_type), "%s",
- (opt_flags & OPT_LOCAL) ? "local" : "global");
- set_vim_var_string(VV_OPTION_NEW, buf_new, -1);
- set_vim_var_string(VV_OPTION_OLD, buf_old, -1);
+ vim_snprintf(buf_type, sizeof(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_tv(VV_OPTION_NEW, &newval_tv);
+ set_vim_var_tv(VV_OPTION_OLD, &oldval_tv);
set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
if (opt_flags & OPT_LOCAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
if (opt_flags & OPT_GLOBAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_tv);
}
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
set_vim_var_string(VV_OPTION_COMMAND, "set", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_l_tv);
+ set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_g_tv);
}
if (opt_flags & OPT_MODELINE) {
set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1);
+ set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
reset_v_option_vars();
}
-/// Set the value of a boolean option, taking care of side effects
-///
-/// @param[in] opt_idx Option index in options[] table.
-/// @param[out] varp Pointer to the option variable.
-/// @param[in] value New value.
-/// @param[in] opt_flags OPT_LOCAL and/or OPT_GLOBAL.
-///
-/// @return NULL on success, error message on error.
-static char *set_bool_option(const int opt_idx, char_u *const varp, const int value,
- const int opt_flags)
+/// Process the updated 'arabic' option value.
+static const char *did_set_arabic(optset_T *args)
{
- int old_value = *(int *)varp;
- int old_global_value = 0;
- char *errmsg = NULL;
+ win_T *win = (win_T *)args->os_win;
+ const char *errmsg = NULL;
+
+ if (win->w_p_arab) {
+ // 'arabic' is set, handle various sub-settings.
+ if (!p_tbidi) {
+ // set rightleft mode
+ if (!win->w_p_rl) {
+ win->w_p_rl = true;
+ changed_window_setting();
+ }
+
+ // Enable Arabic shaping (major part of what Arabic requires)
+ if (!p_arshape) {
+ p_arshape = true;
+ redraw_all_later(UPD_NOT_VALID);
+ }
+ }
+
+ // Arabic requires a utf-8 encoding, inform the user if it's not
+ // set.
+ if (strcmp(p_enc, "utf-8") != 0) {
+ static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
+
+ msg_source(HL_ATTR(HLF_W));
+ msg(_(w_arabic), HL_ATTR(HLF_W));
+ set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);
+ }
- // Disallow changing some options from secure mode
- if ((secure || sandbox != 0)
- && (options[opt_idx].flags & P_SECURE)) {
- return (char *)e_secure;
+ // set 'delcombine'
+ p_deco = true;
+
+ // Force-set the necessary keymap for arabic.
+ errmsg = set_option_value("keymap", STATIC_CSTR_AS_OPTVAL("arabic"), OPT_LOCAL);
+ } else {
+ // 'arabic' is reset, handle various sub-settings.
+ if (!p_tbidi) {
+ // reset rightleft mode
+ if (win->w_p_rl) {
+ win->w_p_rl = false;
+ changed_window_setting();
+ }
+
+ // 'arabicshape' isn't reset, it is a global option and
+ // another window may still need it "on".
+ }
+
+ // 'delcombine' isn't reset, it is a global option and another
+ // window may still want it "on".
+
+ // Revert to the default keymap
+ curbuf->b_p_iminsert = B_IMODE_NONE;
+ curbuf->b_p_imsearch = B_IMODE_USE_INSERT;
}
- // Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" in fact sets the global
- // value (since there is only one value).
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- old_global_value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ return errmsg;
+}
+
+/// Process the updated 'autochdir' option value.
+static const char *did_set_autochdir(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // Change directories when the 'acd' option is set now.
+ do_autochdir();
+ return NULL;
+}
+
+/// Process the updated 'binary' option value.
+static const char *did_set_binary(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+
+ // when 'bin' is set also set some other options
+ set_options_bin((int)args->os_oldval.boolean, buf->b_p_bin, args->os_flags);
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Called when the 'breakat' option changes value.
+static const char *did_set_breakat(optset_T *args FUNC_ATTR_UNUSED)
+{
+ for (int i = 0; i < 256; i++) {
+ breakat_flags[i] = false;
}
- *(int *)varp = value; // set the new value
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
+ if (p_breakat != NULL) {
+ for (char *p = p_breakat; *p; p++) {
+ breakat_flags[(uint8_t)(*p)] = true;
+ }
+ }
- // May set global value for local option.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = value;
- }
-
- // Ensure that options set to p_force_on cannot be disabled.
- if ((int *)varp == &p_force_on && p_force_on == false) {
- p_force_on = true;
- return e_unsupportedoption;
- // Ensure that options set to p_force_off cannot be enabled.
- } else if ((int *)varp == &p_force_off && p_force_off == true) {
- p_force_off = false;
- return (char *)e_unsupportedoption;
- } else if ((int *)varp == &p_lrm) {
- // 'langremap' -> !'langnoremap'
- p_lnr = !p_lrm;
- } else if ((int *)varp == &p_lnr) {
- // 'langnoremap' -> !'langremap'
- p_lrm = !p_lnr;
- } else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) {
- // 'undofile'
- // Only take action when the option was set. When reset we do not
- // delete the undo file, the option may be set again without making
- // any changes in between.
- if (curbuf->b_p_udf || p_udf) {
- char_u hash[UNDO_HASH_SIZE];
-
- FOR_ALL_BUFFERS(bp) {
- // When 'undofile' is set globally: for every buffer, otherwise
- // only for the current buffer: Try to read in the undofile,
- // if one exists, the buffer wasn't changed and the buffer was
- // loaded
- if ((curbuf == bp
- || (opt_flags & OPT_GLOBAL) || opt_flags == 0)
- && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) {
- u_compute_hash(bp, hash);
- u_read_undo(NULL, hash, bp->b_fname);
- }
- }
+ return NULL;
+}
+
+/// Process the updated 'buflisted' option value.
+static const char *did_set_buflisted(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+
+ // when 'buflisted' changes, trigger autocommands
+ if (args->os_oldval.boolean != buf->b_p_bl) {
+ apply_autocmds(buf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE,
+ NULL, NULL, true, buf);
+ }
+ return NULL;
+}
+
+/// Process the new 'cmdheight' option value.
+static const char *did_set_cmdheight(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ if (ui_has(kUIMessages)) {
+ p_ch = 0;
+ }
+ if (p_ch > Rows - min_rows() + 1) {
+ p_ch = Rows - min_rows() + 1;
+ }
+
+ // if p_ch changed value, change the command line height
+ // Only compute the new window layout when startup has been
+ // completed. Otherwise the frame sizes may be wrong.
+ if ((p_ch != old_value
+ || tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch)
+ && full_screen) {
+ command_height();
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'diff' option value.
+static const char *did_set_diff(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ // May add or remove the buffer from the list of diff buffers.
+ diff_buf_adjust(win);
+ if (foldmethodIsDiff(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// Process the updated 'endoffile' or 'endofline' or 'fixendofline' or 'bomb'
+/// option value.
+static const char *did_set_eof_eol_fixeol_bomb(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // redraw the window title and tab page text
+ redraw_titles();
+ return NULL;
+}
+
+/// Process the updated 'equalalways' option value.
+static const char *did_set_equalalways(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (p_ea && !args->os_oldval.boolean) {
+ win_equal(win, false, 0);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'foldlevel' option value.
+static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED)
+{
+ newFoldLevel();
+ return NULL;
+}
+
+/// Process the new 'foldminlines' option value.
+static const char *did_set_foldminlines(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ foldUpdateAll(win);
+ return NULL;
+}
+
+/// Process the new 'foldnestmax' option value.
+static const char *did_set_foldnestmax(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (foldmethodIsSyntax(win) || foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// Process the new 'helpheight' option value.
+static const char *did_set_helpheight(optset_T *args)
+{
+ // Change window height NOW
+ if (!ONE_WINDOW) {
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ if (buf->b_help && win->w_height < p_hh) {
+ win_setheight((int)p_hh);
}
- } else if ((int *)varp == &curbuf->b_p_ro) {
- // when 'readonly' is reset globally, also reset readonlymode
- if (!curbuf->b_p_ro && (opt_flags & OPT_LOCAL) == 0) {
- readonlymode = false;
- }
-
- // when 'readonly' is set may give W10 again
- if (curbuf->b_p_ro) {
- curbuf->b_did_warn = false;
- }
-
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_ma) {
- // when 'modifiable' is changed, redraw the window title
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_eof
- || (int *)varp == &curbuf->b_p_eol
- || (int *)varp == &curbuf->b_p_fixeol
- || (int *)varp == &curbuf->b_p_bomb) {
- // redraw the window title and tab page text when 'endoffile', 'endofline',
- // 'fixeol' or 'bomb' is changed
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_bin) {
- // when 'bin' is set also set some other options
- set_options_bin(old_value, curbuf->b_p_bin, opt_flags);
- redraw_titles();
- } else if ((int *)varp == &curbuf->b_p_bl && old_value != curbuf->b_p_bl) {
- // when 'buflisted' changes, trigger autocommands
- apply_autocmds(curbuf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE,
- NULL, NULL, true, curbuf);
- } else if ((int *)varp == &curbuf->b_p_swf) {
- // when 'swf' is set, create swapfile, when reset remove swapfile
- if (curbuf->b_p_swf && p_uc) {
- ml_open_file(curbuf); // create the swap file
- } else {
- // no need to reset curbuf->b_may_swap, ml_open_file() will check
- // buf->b_p_swf
- mf_close_file(curbuf, true); // remove the swap file
- }
- } else if ((int *)varp == &p_paste) {
- // when 'paste' is set or reset also change other options
- paste_option_changed();
- } else if ((int *)varp == &p_ic && p_hls) {
- // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'hlsearch' option value.
+static const char *did_set_hlsearch(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'hlsearch' is set or reset: reset no_hlsearch
+ set_no_hlsearch(false);
+ return NULL;
+}
+
+/// Process the updated 'ignorecase' option value.
+static const char *did_set_ignorecase(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'ignorecase' is set or reset and 'hlsearch' is set, redraw
+ if (p_hls) {
redraw_all_later(UPD_SOME_VALID);
- } else if ((int *)varp == &p_hls) {
- // when 'hlsearch' is set or reset: reset no_hlsearch
- set_no_hlsearch(false);
- } else if ((int *)varp == &curwin->w_p_scb) {
- // when 'scrollbind' is set: snapshot the current position to avoid a jump
- // at the end of normal_cmd()
- if (curwin->w_p_scb) {
- do_check_scrollbind(false);
- curwin->w_scbind_pos = curwin->w_topline;
- }
- } else if ((int *)varp == &curwin->w_p_pvw) {
- // There can be only one window with 'previewwindow' set.
- if (curwin->w_p_pvw) {
- FOR_ALL_WINDOWS_IN_TAB(win, curtab) {
- if (win->w_p_pvw && win != curwin) {
- curwin->w_p_pvw = false;
- return e_preview_window_already_exists;
+ }
+ return NULL;
+}
+
+/// Process the new 'iminset' option value.
+static const char *did_set_iminsert(optset_T *args FUNC_ATTR_UNUSED)
+{
+ showmode();
+ // Show/unshow value of 'keymap' in status lines.
+ status_redraw_curbuf();
+
+ return NULL;
+}
+
+/// Process the updated 'langnoremap' option value.
+static const char *did_set_langnoremap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // 'langnoremap' -> !'langremap'
+ p_lrm = !p_lnr;
+ return NULL;
+}
+
+/// Process the updated 'langremap' option value.
+static const char *did_set_langremap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // 'langremap' -> !'langnoremap'
+ p_lnr = !p_lrm;
+ return NULL;
+}
+
+/// Process the new 'laststatus' option value.
+static const char *did_set_laststatus(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ // When switching to global statusline, decrease topframe height
+ // Also clear the cmdline to remove the ruler if there is one
+ if (value == 3 && old_value != 3) {
+ frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false);
+ (void)win_comp_pos();
+ clear_cmdline = true;
+ }
+ // When switching from global statusline, increase height of topframe by STATUS_HEIGHT
+ // in order to to re-add the space that was previously taken by the global statusline
+ if (old_value == 3 && value != 3) {
+ frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false);
+ (void)win_comp_pos();
+ }
+
+ last_status(false); // (re)set last window status line.
+ return NULL;
+}
+
+/// Process the updated 'lisp' option value.
+static const char *did_set_lisp(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // When 'lisp' option changes include/exclude '-' in keyword characters.
+ (void)buf_init_chartab(buf, false); // ignore errors
+ return NULL;
+}
+
+/// Process the updated 'modifiable' option value.
+static const char *did_set_modifiable(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'modifiable' is changed, redraw the window title
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Process the updated 'modified' option value.
+static const char *did_set_modified(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ if (!args->os_newval.boolean) {
+ save_file_ff(buf); // Buffer is unchanged
+ }
+ redraw_titles();
+ modified_was_set = (int)args->os_newval.boolean;
+ return NULL;
+}
+
+/// Process the updated 'number' or 'relativenumber' option value.
+static const char *did_set_number_relativenumber(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (*win->w_p_stc != NUL) {
+ // When 'relativenumber'/'number' is changed and 'statuscolumn' is set, reset width.
+ win->w_nrwidth_line_count = 0;
+ }
+ check_signcolumn(win);
+ return NULL;
+}
+
+/// Process the new 'numberwidth' option value.
+static const char *did_set_numberwidth(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ win->w_nrwidth_line_count = 0; // trigger a redraw
+
+ return NULL;
+}
+
+/// Process the updated 'paste' option value.
+static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED)
+{
+ static int old_p_paste = false;
+ static int save_sm = 0;
+ static int save_sta = 0;
+ static int save_ru = 0;
+ static int save_ri = 0;
+
+ if (p_paste) {
+ // Paste switched from off to on.
+ // Save the current values, so they can be restored later.
+ if (!old_p_paste) {
+ // save options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw_nopaste = buf->b_p_tw;
+ buf->b_p_wm_nopaste = buf->b_p_wm;
+ buf->b_p_sts_nopaste = buf->b_p_sts;
+ buf->b_p_ai_nopaste = buf->b_p_ai;
+ buf->b_p_et_nopaste = buf->b_p_et;
+ if (buf->b_p_vsts_nopaste) {
+ xfree(buf->b_p_vsts_nopaste);
}
+ buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_string_option
+ ? xstrdup(buf->b_p_vsts)
+ : NULL;
+ }
+
+ // save global options
+ save_sm = p_sm;
+ save_sta = p_sta;
+ save_ru = p_ru;
+ save_ri = p_ri;
+ // save global values for local buffer options
+ p_ai_nopaste = p_ai;
+ p_et_nopaste = p_et;
+ p_sts_nopaste = p_sts;
+ p_tw_nopaste = p_tw;
+ p_wm_nopaste = p_wm;
+ if (p_vsts_nopaste) {
+ xfree(p_vsts_nopaste);
}
+ p_vsts_nopaste = p_vsts && p_vsts != empty_string_option ? xstrdup(p_vsts) : NULL;
}
- } else if (varp == (char_u *)&(curbuf->b_p_lisp)) {
- // When 'lisp' option changes include/exclude '-' in
- // keyword characters.
- (void)buf_init_chartab(curbuf, false); // ignore errors
- } else if ((int *)varp == &p_title) {
- // when 'title' changed, may need to change the title; same for 'icon'
- did_set_title();
- } else if ((int *)varp == &p_icon) {
- did_set_title();
- } else if ((int *)varp == &curbuf->b_changed) {
- if (!value) {
- save_file_ff(curbuf); // Buffer is unchanged
- }
- redraw_titles();
- modified_was_set = value;
-#ifdef BACKSLASH_IN_FILENAME
- } else if ((int *)varp == &p_ssl) {
- if (p_ssl) {
- psepc = '/';
- psepcN = '\\';
- pseps[0] = '/';
- } else {
- psepc = '\\';
- psepcN = '/';
- pseps[0] = '\\';
+ // Always set the option values, also when 'paste' is set when it is
+ // already on.
+ // set options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw = 0; // textwidth is 0
+ buf->b_p_wm = 0; // wrapmargin is 0
+ buf->b_p_sts = 0; // softtabstop is 0
+ buf->b_p_ai = 0; // no auto-indent
+ buf->b_p_et = 0; // no expandtab
+ if (buf->b_p_vsts) {
+ free_string_option(buf->b_p_vsts);
+ }
+ buf->b_p_vsts = empty_string_option;
+ XFREE_CLEAR(buf->b_p_vsts_array);
}
- // need to adjust the file name arguments and buffer names.
- buflist_slash_adjust();
- alist_slash_adjust();
- scriptnames_slash_adjust();
-#endif
- } else if ((int *)varp == &curwin->w_p_wrap) {
- // If 'wrap' is set, set w_leftcol to zero.
- if (curwin->w_p_wrap) {
- curwin->w_leftcol = 0;
- }
- } else if ((int *)varp == &p_ea) {
- if (p_ea && !old_value) {
- win_equal(curwin, false, 0);
- }
- } else if ((int *)varp == &p_acd) {
- // Change directories when the 'acd' option is set now.
- do_autochdir();
- } else if ((int *)varp == &curwin->w_p_diff) { // 'diff'
- // May add or remove the buffer from the list of diff buffers.
- diff_buf_adjust(curwin);
- if (foldmethodIsDiff(curwin)) {
- foldUpdateAll(curwin);
- }
- } else if ((int *)varp == &curwin->w_p_spell) { // 'spell'
- if (curwin->w_p_spell) {
- errmsg = did_set_spelllang(curwin);
- }
- } else if (((int *)varp == &curwin->w_p_nu || (int *)varp == &curwin->w_p_rnu)
- && *curwin->w_p_stc != NUL) { // '(relative)number' + 'statuscolumn'
- curwin->w_nrwidth_line_count = 0;
- }
-
- if ((int *)varp == &curwin->w_p_arab) {
- if (curwin->w_p_arab) {
- // 'arabic' is set, handle various sub-settings.
- if (!p_tbidi) {
- // set rightleft mode
- if (!curwin->w_p_rl) {
- curwin->w_p_rl = true;
- changed_window_setting();
- }
+ // set global options
+ p_sm = 0; // no showmatch
+ p_sta = 0; // no smarttab
+ if (p_ru) {
+ status_redraw_all(); // redraw to remove the ruler
+ }
+ p_ru = 0; // no ruler
+ p_ri = 0; // no reverse insert
+ // set global values for local buffer options
+ p_tw = 0;
+ p_wm = 0;
+ p_sts = 0;
+ p_ai = 0;
+ p_et = 0;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = empty_string_option;
+ } else if (old_p_paste) {
+ // Paste switched from on to off: Restore saved values.
- // Enable Arabic shaping (major part of what Arabic requires)
- if (!p_arshape) {
- p_arshape = true;
- redraw_all_later(UPD_NOT_VALID);
- }
+ // restore options for each buffer
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_p_tw = buf->b_p_tw_nopaste;
+ buf->b_p_wm = buf->b_p_wm_nopaste;
+ buf->b_p_sts = buf->b_p_sts_nopaste;
+ buf->b_p_ai = buf->b_p_ai_nopaste;
+ buf->b_p_et = buf->b_p_et_nopaste;
+ if (buf->b_p_vsts) {
+ free_string_option(buf->b_p_vsts);
}
+ buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_string_option;
+ xfree(buf->b_p_vsts_array);
+ if (buf->b_p_vsts && buf->b_p_vsts != empty_string_option) {
+ (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
+ } else {
+ buf->b_p_vsts_array = NULL;
+ }
+ }
+
+ // restore global options
+ p_sm = save_sm;
+ p_sta = save_sta;
+ if (p_ru != save_ru) {
+ status_redraw_all(); // redraw to draw the ruler
+ }
+ p_ru = save_ru;
+ p_ri = save_ri;
+ // set global values for local buffer options
+ p_ai = p_ai_nopaste;
+ p_et = p_et_nopaste;
+ p_sts = p_sts_nopaste;
+ p_tw = p_tw_nopaste;
+ p_wm = p_wm_nopaste;
+ if (p_vsts) {
+ free_string_option(p_vsts);
+ }
+ p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_string_option;
+ }
- // Arabic requires a utf-8 encoding, inform the user if it's not
- // set.
- if (strcmp(p_enc, "utf-8") != 0) {
- static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
+ old_p_paste = p_paste;
- msg_source(HL_ATTR(HLF_W));
- msg_attr(_(w_arabic), HL_ATTR(HLF_W));
- set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);
- }
+ // Remember where the dependent options were reset
+ didset_options_sctx((OPT_LOCAL | OPT_GLOBAL), p_paste_dep_opts);
- // set 'delcombine'
- p_deco = true;
+ return NULL;
+}
- // Force-set the necessary keymap for arabic.
- errmsg = set_option_value("keymap", 0L, "arabic", OPT_LOCAL);
- } else {
- // 'arabic' is reset, handle various sub-settings.
- if (!p_tbidi) {
- // reset rightleft mode
- if (curwin->w_p_rl) {
- curwin->w_p_rl = false;
- changed_window_setting();
- }
+/// Process the updated 'previewwindow' option value.
+static const char *did_set_previewwindow(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
- // 'arabicshape' isn't reset, it is a global option and
- // another window may still need it "on".
- }
+ if (!win->w_p_pvw) {
+ return NULL;
+ }
+
+ // There can be only one window with 'previewwindow' set.
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp->w_p_pvw && wp != win) {
+ win->w_p_pvw = false;
+ return e_preview_window_already_exists;
+ }
+ }
+
+ return NULL;
+}
+
+/// Process the new 'pumblend' option value.
+static const char *did_set_pumblend(optset_T *args FUNC_ATTR_UNUSED)
+{
+ p_pb = MAX(MIN(p_pb, 100), 0);
+ hl_invalidate_blends();
+ pum_grid.blending = (p_pb > 0);
+ if (pum_drawn()) {
+ pum_redraw();
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'readonly' option value.
+static const char *did_set_readonly(optset_T *args)
+{
+ // when 'readonly' is reset globally, also reset readonlymode
+ if (!curbuf->b_p_ro && (args->os_flags & OPT_LOCAL) == 0) {
+ readonlymode = false;
+ }
+
+ // when 'readonly' is set may give W10 again
+ if (curbuf->b_p_ro) {
+ curbuf->b_did_warn = false;
+ }
+
+ redraw_titles();
+
+ return NULL;
+}
+
+/// Process the new 'scrollback' option value.
+static const char *did_set_scrollback(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ if (buf->terminal && value < old_value) {
+ // Force the scrollback to take immediate effect only when decreasing it.
+ on_scrollback_option_changed(buf->terminal);
+ }
+ return NULL;
+}
+
+/// Process the updated 'scrollbind' option value.
+static const char *did_set_scrollbind(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+
+ // when 'scrollbind' is set: snapshot the current position to avoid a jump
+ // at the end of normal_cmd()
+ if (!win->w_p_scb) {
+ return NULL;
+ }
+ do_check_scrollbind(false);
+ win->w_scbind_pos = win->w_topline;
+ return NULL;
+}
+
+#ifdef BACKSLASH_IN_FILENAME
+/// Process the updated 'shellslash' option value.
+static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (p_ssl) {
+ psepc = '/';
+ psepcN = '\\';
+ pseps[0] = '/';
+ } else {
+ psepc = '\\';
+ psepcN = '/';
+ pseps[0] = '\\';
+ }
+
+ // need to adjust the file name arguments and buffer names.
+ buflist_slash_adjust();
+ alist_slash_adjust();
+ scriptnames_slash_adjust();
+ return NULL;
+}
+#endif
+
+/// Process the new 'shiftwidth' or the 'tabstop' option value.
+static const char *did_set_shiftwidth_tabstop(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ OptInt *pp = (OptInt *)args->os_varp;
+
+ if (foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ // When 'shiftwidth' changes, or it's zero and 'tabstop' changes:
+ // parse 'cinoptions'.
+ if (pp == &buf->b_p_sw || buf->b_p_sw == 0) {
+ parse_cino(buf);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'showtabline' option value.
+static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // (re)set tab page line
+ win_new_screen_rows(); // recompute window positions and heights
+ return NULL;
+}
+
+/// Process the updated 'smoothscroll' option value.
+static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (!win->w_p_sms) {
+ win->w_skipcol = 0;
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'spell' option value.
+static const char *did_set_spell(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (win->w_p_spell) {
+ return parse_spelllang(win);
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'swapfile' option value.
+static const char *did_set_swapfile(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // when 'swf' is set, create swapfile, when reset remove swapfile
+ if (buf->b_p_swf && p_uc) {
+ ml_open_file(buf); // create the swap file
+ } else {
+ // no need to reset curbuf->b_may_swap, ml_open_file() will check
+ // buf->b_p_swf
+ mf_close_file(buf, true); // remove the swap file
+ }
+ return NULL;
+}
+
+/// Process the new 'textwidth' option value.
+static const char *did_set_textwidth(optset_T *args FUNC_ATTR_UNUSED)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ check_colorcolumn(wp);
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'title' or the 'icon' option value.
+static const char *did_set_title_icon(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // when 'title' changed, may need to change the title; same for 'icon'
+ did_set_title();
+ return NULL;
+}
+
+/// Process the new 'titlelen' option value.
+static const char *did_set_titlelen(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ // if 'titlelen' has changed, redraw the title
+ if (starting != NO_SCREEN && old_value != p_titlelen) {
+ need_maketitle = true;
+ }
+
+ return NULL;
+}
+
+/// Process the updated 'undofile' option value.
+static const char *did_set_undofile(optset_T *args)
+{
+ // Only take action when the option was set.
+ if (!curbuf->b_p_udf && !p_udf) {
+ return NULL;
+ }
- // 'delcombine' isn't reset, it is a global option and another
- // window may still want it "on".
+ // When reset we do not delete the undo file, the option may be set again
+ // without making any changes in between.
+ uint8_t hash[UNDO_HASH_SIZE];
- // Revert to the default keymap
- curbuf->b_p_iminsert = B_IMODE_NONE;
- curbuf->b_p_imsearch = B_IMODE_USE_INSERT;
+ FOR_ALL_BUFFERS(bp) {
+ // When 'undofile' is set globally: for every buffer, otherwise
+ // only for the current buffer: Try to read in the undofile,
+ // if one exists, the buffer wasn't changed and the buffer was
+ // loaded
+ if ((curbuf == bp
+ || (args->os_flags & OPT_GLOBAL) || args->os_flags == 0)
+ && !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) {
+ u_compute_hash(bp, hash);
+ u_read_undo(NULL, hash, bp->b_fname);
}
}
- // End of handling side effects for bool options.
+ return NULL;
+}
- // after handling side effects, call autocommand
+/// Process the new global 'undolevels' option value.
+const char *did_set_global_undolevels(OptInt value, OptInt old_value)
+{
+ // sync undo before 'undolevels' changes
+ // use the old value, otherwise u_sync() may not work properly
+ p_ul = old_value;
+ u_sync(true);
+ p_ul = value;
+ return NULL;
+}
- options[opt_idx].flags |= P_WAS_SET;
+/// Process the new buffer local 'undolevels' option value.
+const char *did_set_buflocal_undolevels(buf_T *buf, OptInt value, OptInt old_value)
+{
+ // use the old value, otherwise u_sync() may not work properly
+ buf->b_p_ul = old_value;
+ u_sync(true);
+ buf->b_p_ul = value;
+ return NULL;
+}
- apply_optionset_autocmd(opt_idx, opt_flags,
- (long)(old_value ? true : false),
- (long)(old_global_value ? true : false),
- (long)(value ? true : false), NULL);
+/// Process the new 'undolevels' option value.
+static const char *did_set_undolevels(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ OptInt *pp = (OptInt *)args->os_varp;
- if (options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- BOOLEAN_OBJ(*varp));
+ if (pp == &p_ul) { // global 'undolevels'
+ did_set_global_undolevels(args->os_newval.number, args->os_oldval.number);
+ } else if (pp == &curbuf->b_p_ul) { // buffer local 'undolevels'
+ did_set_buflocal_undolevels(buf, args->os_newval.number, args->os_oldval.number);
}
- comp_col(); // in case 'ruler' or 'showcmd' changed
- if (curwin->w_curswant != MAXCOL
- && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) {
- curwin->w_set_curswant = true;
+ return NULL;
+}
+
+/// Process the new 'updatecount' option value.
+static const char *did_set_updatecount(optset_T *args)
+{
+ OptInt old_value = args->os_oldval.number;
+
+ // when 'updatecount' changes from zero to non-zero, open swap files
+ if (p_uc && !old_value) {
+ ml_open_files();
}
- check_redraw(options[opt_idx].flags);
- return errmsg;
+ return NULL;
}
-/// Set the value of a number option, taking care of side effects
-///
-/// @param[in] opt_idx Option index in options[] table.
-/// @param[out] varp Pointer to the option variable.
-/// @param[in] value New value.
-/// @param errbuf Buffer for error messages.
-/// @param[in] errbuflen Length of `errbuf`.
-/// @param[in] opt_flags OPT_LOCAL, OPT_GLOBAL or OPT_MODELINE.
-///
-/// @return NULL on success, error message on error.
-static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, size_t errbuflen,
- int opt_flags)
+/// Process the new 'wildchar' / 'wildcharm' option value.
+static const char *did_set_wildchar(optset_T *args)
{
- char *errmsg = NULL;
- long old_value = *(long *)varp;
- long old_global_value = 0; // only used when setting a local and global option
- long old_Rows = Rows; // remember old Rows
- long *pp = (long *)varp;
+ OptInt c = *(OptInt *)args->os_varp;
- // Disallow changing some options from secure mode.
- if ((secure || sandbox != 0)
- && (options[opt_idx].flags & P_SECURE)) {
- return e_secure;
+ // Don't allow key values that wouldn't work as wildchar.
+ if (c == Ctrl_C || c == '\n' || c == '\r' || c == K_KENTER) {
+ return e_invarg;
}
- // Save the global value before changing anything. This is needed as for
- // a global-only option setting the "local value" in fact sets the global
- // value (since there is only one value).
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL);
+ return NULL;
+}
+
+/// Process the new 'winblend' option value.
+static const char *did_set_winblend(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ OptInt old_value = args->os_oldval.number;
+ OptInt value = args->os_newval.number;
+
+ if (value != old_value) {
+ win->w_p_winbl = MAX(MIN(win->w_p_winbl, 100), 0);
+ win->w_hl_needs_update = true;
+ check_blending(win);
+ }
+
+ return NULL;
+}
+
+/// Process the new 'window' option value.
+static const char *did_set_window(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (p_window < 1) {
+ p_window = Rows - 1;
+ } else if (p_window >= Rows) {
+ p_window = Rows - 1;
+ }
+ return NULL;
+}
+
+/// Process the new 'winheight' value.
+static const char *did_set_winheight(optset_T *args)
+{
+ // Change window height NOW
+ if (!ONE_WINDOW) {
+ win_T *win = (win_T *)args->os_win;
+ if (win->w_height < p_wh) {
+ win_setheight((int)p_wh);
+ }
+ }
+
+ return NULL;
+}
+
+/// Process the new 'winwidth' option value.
+static const char *did_set_winwidth(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+
+ if (!ONE_WINDOW && win->w_width < p_wiw) {
+ win_setwidth((int)p_wiw);
+ }
+ return NULL;
+}
+
+/// Process the updated 'wrap' option value.
+static const char *did_set_wrap(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ // Set w_leftcol or w_skipcol to zero.
+ if (win->w_p_wrap) {
+ win->w_leftcol = 0;
+ } else {
+ win->w_skipcol = 0;
+ }
+
+ return NULL;
+}
+
+// When 'syntax' is set, load the syntax of that name
+static void do_syntax_autocmd(buf_T *buf, bool value_changed)
+{
+ static int syn_recursive = 0;
+
+ syn_recursive++;
+ // Only pass true for "force" when the value changed or not used
+ // recursively, to avoid endless recurrence.
+ apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname,
+ value_changed || syn_recursive == 1, buf);
+ buf->b_flags |= BF_SYN_SET;
+ syn_recursive--;
+}
+
+static void do_spelllang_source(win_T *win)
+{
+ char fname[200];
+ char *q = win->w_s->b_p_spl;
+
+ // Skip the first name if it is "cjk".
+ if (strncmp(q, "cjk,", 4) == 0) {
+ q += 4;
+ }
+
+ // Source the spell/LANG.{vim,lua} in 'runtimepath'.
+ // They could set 'spellcapcheck' depending on the language.
+ // Use the first name in 'spelllang' up to '_region' or
+ // '.encoding'.
+ char *p;
+ for (p = q; *p != NUL; p++) {
+ if (!ASCII_ISALNUM(*p) && *p != '-') {
+ break;
+ }
+ }
+ if (p > q) {
+ vim_snprintf(fname, sizeof(fname), "spell/%.*s.*", (int)(p - q), q);
+ source_runtime_vim_lua(fname, DIP_ALL);
+ }
+}
+
+/// Check the bounds of numeric options.
+static const char *check_num_option_bounds(OptInt *pp, OptInt old_value, char *errbuf,
+ size_t errbuflen, const char *errmsg)
+{
+ int old_Rows = Rows; // remember old Rows
+ // Check the (new) bounds for Rows and Columns here.
+ if (p_lines < min_rows() && full_screen) {
+ if (errbuf != NULL) {
+ vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows());
+ errmsg = errbuf;
+ }
+ p_lines = min_rows();
+ }
+ if (p_columns < MIN_COLUMNS && full_screen) {
+ if (errbuf != NULL) {
+ vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS);
+ errmsg = errbuf;
+ }
+ p_columns = MIN_COLUMNS;
+ }
+
+ // True max size is defined by check_screensize()
+ p_lines = MIN(p_lines, INT_MAX);
+ p_columns = MIN(p_columns, INT_MAX);
+
+ // If the screen (shell) height has been changed, assume it is the
+ // physical screenheight.
+ if (p_lines != Rows || p_columns != Columns) {
+ // Changing the screen size is not allowed while updating the screen.
+ if (updating_screen) {
+ *pp = old_value;
+ } else if (full_screen) {
+ screen_resize((int)p_columns, (int)p_lines);
+ } else {
+ // TODO(bfredl): is this branch ever needed?
+ // Postpone the resizing; check the size and cmdline position for
+ // messages.
+ Rows = (int)p_lines;
+ Columns = (int)p_columns;
+ check_screensize();
+ int new_row = (int)(Rows - MAX(p_ch, 1));
+ if (cmdline_row > new_row && Rows > p_ch) {
+ assert(p_ch >= 0 && new_row <= INT_MAX);
+ cmdline_row = new_row;
+ }
+ }
+ if (p_window >= Rows || !option_was_set("window")) {
+ p_window = Rows - 1;
+ }
}
+ if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0))
+ && full_screen) {
+ if (pp == &(curwin->w_p_scr)) {
+ if (curwin->w_p_scr != 0) {
+ errmsg = e_scroll;
+ }
+ win_comp_scroll(curwin);
+ } else if (curwin->w_p_scr <= 0) {
+ // If 'scroll' became invalid because of a side effect silently adjust it.
+ curwin->w_p_scr = 1;
+ } else { // curwin->w_p_scr > curwin->w_height
+ curwin->w_p_scr = curwin->w_height;
+ }
+ }
+ if ((p_sj < -100 || p_sj >= Rows) && full_screen) {
+ if (Rows != old_Rows) { // Rows changed, just adjust p_sj
+ p_sj = Rows / 2;
+ } else {
+ errmsg = e_scroll;
+ p_sj = 1;
+ }
+ }
+
+ return errmsg;
+}
+
+/// Options that need some validation.
+static const char *validate_num_option(const OptInt *pp, OptInt *valuep)
+{
+ OptInt value = *valuep;
+
// Many number options assume their value is in the signed int range.
if (value < INT_MIN || value > INT_MAX) {
return e_invarg;
}
- // Options that need some validation.
if (pp == &p_wh) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (p_wmh > value) {
- errmsg = e_winheight;
+ return e_winheight;
}
} else if (pp == &p_hh) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_wmh) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > p_wh) {
- errmsg = e_winheight;
+ return e_winheight;
}
} else if (pp == &p_wiw) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (p_wmw > value) {
- errmsg = e_winwidth;
+ return e_winwidth;
}
} else if (pp == &p_wmw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > p_wiw) {
- errmsg = e_winwidth;
+ return e_winwidth;
}
} else if (pp == &p_mco) {
- value = MAX_MCO;
+ *valuep = MAX_MCO;
} else if (pp == &p_titlelen) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_uc) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ch) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else {
p_ch_was_zero = value == 0;
}
} else if (pp == &p_tm) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_hi) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > 10000) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_pyx) {
if (value == 0) {
- value = 3;
+ *valuep = 3;
} else if (value != 3) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_re) {
if (value < 0 || value > 2) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &p_report) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_so) {
if (value < 0 && full_screen) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_siso) {
if (value < 0 && full_screen) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_cwh) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ut) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_ss) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curwin->w_p_fdl || pp == &curwin->w_allbuf_opt.wo_fdl) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curwin->w_p_cole || pp == &curwin->w_allbuf_opt.wo_cole) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > 3) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > MAX_NUMBERWIDTH) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) {
if (value < 0 || value > B_IMODE_LAST) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) {
if (value < -1 || value > B_IMODE_LAST) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_channel || pp == &p_channel) {
- errmsg = e_invarg;
+ return e_invarg;
} else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) {
if (value < -1 || value > SB_MAX) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_sw || pp == &p_sw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &curbuf->b_p_ts || pp == &p_ts) {
if (value < 1) {
- errmsg = e_positive;
+ return e_positive;
} else if (value > TABSTOP_MAX) {
- errmsg = e_invarg;
+ return e_invarg;
}
} else if (pp == &curbuf->b_p_tw || pp == &p_tw) {
if (value < 0) {
- errmsg = e_positive;
+ return e_positive;
}
} else if (pp == &p_wd) {
if (value < 0) {
- errmsg = e_positive;
- }
- }
-
- // Don't change the value and return early if validation failed.
- if (errmsg != NULL) {
- return errmsg;
- }
-
- *pp = value;
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
-
- // For these options we want to fix some invalid values.
- if (pp == &p_window) {
- if (p_window < 1) {
- p_window = Rows - 1;
- } else if (p_window >= Rows) {
- p_window = Rows - 1;
- }
- } else if (pp == &p_ch) {
- if (ui_has(kUIMessages)) {
- p_ch = 0;
- }
- if (p_ch > Rows - min_rows() + 1) {
- p_ch = Rows - min_rows() + 1;
- }
- }
-
- // Number options that need some action when changed
- if (pp == &p_wh) {
- // 'winheight'
- if (!ONE_WINDOW && curwin->w_height < p_wh) {
- win_setheight((int)p_wh);
- }
- } else if (pp == &p_hh) {
- // 'helpheight'
- if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) {
- win_setheight((int)p_hh);
- }
- } else if (pp == &p_wmh) {
- // 'winminheight'
- win_setminheight();
- } else if (pp == &p_wiw) {
- // 'winwidth'
- if (!ONE_WINDOW && curwin->w_width < p_wiw) {
- win_setwidth((int)p_wiw);
- }
- } else if (pp == &p_wmw) {
- // 'winminwidth'
- win_setminwidth();
- } else if (pp == &p_ls) {
- // When switching to global statusline, decrease topframe height
- // Also clear the cmdline to remove the ruler if there is one
- if (value == 3 && old_value != 3) {
- frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false);
- (void)win_comp_pos();
- clear_cmdline = true;
- }
- // When switching from global statusline, increase height of topframe by STATUS_HEIGHT
- // in order to to re-add the space that was previously taken by the global statusline
- if (old_value == 3 && value != 3) {
- frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false);
- (void)win_comp_pos();
- }
-
- last_status(false); // (re)set last window status line.
- } else if (pp == &p_stal) {
- // (re)set tab page line
- win_new_screen_rows(); // recompute window positions and heights
- } else if (pp == &curwin->w_p_fdl) {
- newFoldLevel();
- } else if (pp == &curwin->w_p_fml) {
- foldUpdateAll(curwin);
- } else if (pp == &curwin->w_p_fdn) {
- if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) {
- foldUpdateAll(curwin);
- }
- } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) {
- // 'shiftwidth' or 'tabstop'
- if (foldmethodIsIndent(curwin)) {
- foldUpdateAll(curwin);
- }
- // When 'shiftwidth' changes, or it's zero and 'tabstop' changes:
- // parse 'cinoptions'.
- if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) {
- parse_cino(curbuf);
- }
- } else if (pp == &curbuf->b_p_iminsert) {
- showmode();
- // Show/unshow value of 'keymap' in status lines.
- status_redraw_curbuf();
- } else if (pp == &p_titlelen) {
- // if 'titlelen' has changed, redraw the title
- if (starting != NO_SCREEN && old_value != p_titlelen) {
- need_maketitle = true;
- }
- } else if (pp == &p_ch) {
- // if p_ch changed value, change the command line height
- // Only compute the new window layout when startup has been
- // completed. Otherwise the frame sizes may be wrong.
- if ((p_ch != old_value
- || tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch)
- && full_screen) {
- command_height();
- }
- } else if (pp == &p_uc) {
- // when 'updatecount' changes from zero to non-zero, open swap files
- if (p_uc && !old_value) {
- ml_open_files();
- }
- } else if (pp == &p_pb) {
- p_pb = MAX(MIN(p_pb, 100), 0);
- hl_invalidate_blends();
- pum_grid.blending = (p_pb > 0);
- if (pum_drawn()) {
- pum_redraw();
- }
- } else if (pp == &p_ul || pp == &curbuf->b_p_ul) {
- // sync undo before 'undolevels' changes
- // use the old value, otherwise u_sync() may not work properly
- *pp = old_value;
- u_sync(true);
- *pp = value;
- } else if (pp == &curbuf->b_p_tw) {
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- check_colorcolumn(wp);
- }
- } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) {
- if (curbuf->terminal && value < old_value) {
- // Force the scrollback to take immediate effect only when decreasing it.
- on_scrollback_option_changed(curbuf->terminal);
- }
- } else if (pp == &curwin->w_p_nuw) {
- curwin->w_nrwidth_line_count = 0;
- } else if (pp == &curwin->w_p_winbl && value != old_value) {
- // 'winblend'
- curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0);
- curwin->w_hl_needs_update = true;
- check_blending(curwin);
- }
-
- // Check the (new) bounds for Rows and Columns here.
- if (p_lines < min_rows() && full_screen) {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E593: Need at least %d lines"), min_rows());
- errmsg = errbuf;
- }
- p_lines = min_rows();
- }
- if (p_columns < MIN_COLUMNS && full_screen) {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E594: Need at least %d columns"), MIN_COLUMNS);
- errmsg = errbuf;
+ return e_positive;
}
- p_columns = MIN_COLUMNS;
}
- // True max size is defined by check_screensize()
- p_lines = MIN(p_lines, INT_MAX);
- p_columns = MIN(p_columns, INT_MAX);
-
- // If the screen (shell) height has been changed, assume it is the
- // physical screenheight.
- if (p_lines != Rows || p_columns != Columns) {
- // Changing the screen size is not allowed while updating the screen.
- if (updating_screen) {
- *pp = old_value;
- } else if (full_screen) {
- screen_resize((int)p_columns, (int)p_lines);
- } else {
- // TODO(bfredl): is this branch ever needed?
- // Postpone the resizing; check the size and cmdline position for
- // messages.
- Rows = (int)p_lines;
- Columns = (int)p_columns;
- check_screensize();
- int new_row = (int)(Rows - MAX(p_ch, 1));
- if (cmdline_row > new_row && Rows > p_ch) {
- assert(p_ch >= 0 && new_row <= INT_MAX);
- cmdline_row = new_row;
- }
- }
- if (p_window >= Rows || !option_was_set("window")) {
- p_window = Rows - 1;
- }
- }
-
- if ((curwin->w_p_scr <= 0
- || (curwin->w_p_scr > curwin->w_height
- && curwin->w_height > 0))
- && full_screen) {
- if (pp == &(curwin->w_p_scr)) {
- if (curwin->w_p_scr != 0) {
- errmsg = e_scroll;
- }
- win_comp_scroll(curwin);
- } else if (curwin->w_p_scr <= 0) {
- // If 'scroll' became invalid because of a side effect silently adjust it.
- curwin->w_p_scr = 1;
- } else { // curwin->w_p_scr > curwin->w_height
- curwin->w_p_scr = curwin->w_height;
- }
- }
- if ((p_sj < -100 || p_sj >= Rows) && full_screen) {
- if (Rows != old_Rows) { // Rows changed, just adjust p_sj
- p_sj = Rows / 2;
- } else {
- errmsg = e_scroll;
- p_sj = 1;
- }
- }
-
- // May set global value for local option.
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp;
- }
-
- options[opt_idx].flags |= P_WAS_SET;
-
- apply_optionset_autocmd(opt_idx, opt_flags, old_value, old_global_value,
- value, errmsg);
-
- if (errmsg == NULL && options[opt_idx].flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(options[opt_idx].fullname),
- INTEGER_OBJ(*pp));
- }
-
- comp_col(); // in case 'columns' or 'ls' changed
- if (curwin->w_curswant != MAXCOL
- && (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0) {
- curwin->w_set_curswant = true;
- }
- check_redraw(options[opt_idx].flags);
-
- return errmsg;
+ return NULL;
}
/// Called after an option changed: check if something needs to be redrawn.
@@ -2831,23 +3165,6 @@ bool set_tty_option(const char *name, char *value)
return false;
}
-void set_tty_background(const char *value)
-{
- if (option_was_set("bg") || strequal(p_bg, value)) {
- // background is already set... ignore
- return;
- }
- if (starting) {
- // Wait until after startup, so OptionSet is triggered.
- do_cmdline_cmd((value[0] == 'l')
- ? "autocmd VimEnter * ++once ++nested set bg=light"
- : "autocmd VimEnter * ++once ++nested set bg=dark");
- } else {
- set_option_value_give_err("bg", 0L, value, 0);
- reset_option_was_set("bg");
- }
-}
-
/// Find index for an option
///
/// @param[in] arg Option name.
@@ -2859,283 +3176,702 @@ int findoption(const char *const arg)
return findoption_len(arg, strlen(arg));
}
-/// Gets the value for an option.
+/// Free an allocated OptVal.
+void optval_free(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ break;
+ case kOptValTypeString:
+ // Don't free empty string option
+ if (o.data.string.data != empty_string_option) {
+ api_free_string(o.data.string);
+ }
+ break;
+ }
+}
+
+/// Copy an OptVal.
+OptVal optval_copy(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ case kOptValTypeBoolean:
+ case kOptValTypeNumber:
+ return o;
+ case kOptValTypeString:
+ return STRING_OPTVAL(copy_string(o.data.string, NULL));
+ }
+ UNREACHABLE;
+}
+
+/// Check if two option values are equal.
+bool optval_equal(OptVal o1, OptVal o2)
+{
+ if (o1.type != o2.type) {
+ return false;
+ }
+
+ switch (o1.type) {
+ case kOptValTypeNil:
+ return true;
+ case kOptValTypeBoolean:
+ return o1.data.boolean == o2.data.boolean;
+ case kOptValTypeNumber:
+ return o1.data.number == o2.data.number;
+ case kOptValTypeString:
+ return o1.data.string.size == o2.data.string.size
+ && strequal(o1.data.string.data, o2.data.string.data);
+ }
+ UNREACHABLE;
+}
+
+/// Match type of OptVal with the type of the target option. Returns true if the types match and
+/// false otherwise.
+static bool optval_match_type(OptVal o, int opt_idx)
+{
+ assert(opt_idx >= 0);
+ uint32_t flags = options[opt_idx].flags;
+
+ switch (o.type) {
+ case kOptValTypeNil:
+ return false;
+ case kOptValTypeBoolean:
+ return flags & P_BOOL;
+ case kOptValTypeNumber:
+ return flags & P_NUM;
+ case kOptValTypeString:
+ return flags & P_STRING;
+ }
+ UNREACHABLE;
+}
+
+/// Create OptVal from var pointer.
///
-/// @param stringval NULL when only checking existence
-/// @param flagsp set to the option flags (P_xxxx) (if not NULL)
+/// @param opt_idx Option index in options[] table.
+/// @param[out] varp Pointer to option variable.
+OptVal optval_from_varp(int opt_idx, void *varp)
+{
+ // Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc'
+ // changed.
+ if ((int *)varp == &curbuf->b_changed) {
+ return BOOLEAN_OPTVAL(curbufIsChanged());
+ }
+
+ uint32_t flags = options[opt_idx].flags;
+
+ OptValType type = kOptValTypeNil;
+ if (flags & P_BOOL) {
+ type = kOptValTypeBoolean;
+ } else if (flags & P_NUM) {
+ type = kOptValTypeNumber;
+ } else if (flags & P_STRING) {
+ type = kOptValTypeString;
+ } else {
+ abort();
+ }
+
+ switch (type) {
+ case kOptValTypeNil:
+ return NIL_OPTVAL;
+ case kOptValTypeBoolean:
+ return BOOLEAN_OPTVAL(varp == NULL ? false : TRISTATE_FROM_INT(*(int *)varp));
+ case kOptValTypeNumber:
+ return NUMBER_OPTVAL(varp == NULL ? 0 : *(OptInt *)varp);
+ case kOptValTypeString:
+ return STRING_OPTVAL(varp == NULL ? (String)STRING_INIT : cstr_as_string(*(char **)varp));
+ }
+ UNREACHABLE;
+}
+
+/// Set option var pointer value from Optval.
///
-/// @returns:
-/// Number option: gov_number, *numval gets value.
-/// Tottle option: gov_bool, *numval gets value.
-/// String option: gov_string, *stringval gets allocated string.
-/// Hidden Number option: gov_hidden_number.
-/// Hidden Toggle option: gov_hidden_bool.
-/// Hidden String option: gov_hidden_string.
-/// Unknown option: gov_unknown.
-getoption_T get_option_value(const char *name, long *numval, char **stringval, uint32_t *flagsp,
- int scope)
+/// @param opt_idx Option index in options[] table.
+/// @param[out] varp Pointer to option variable.
+/// @param[in] value New option value.
+/// @param free_oldval Free old value.
+static void set_option_varp(int opt_idx, void *varp, OptVal value, bool free_oldval)
+ FUNC_ATTR_NONNULL_ARG(2)
{
- if (get_tty_option(name, stringval)) {
- return gov_string;
+ assert(optval_match_type(value, opt_idx));
+
+ if (free_oldval) {
+ optval_free(optval_from_varp(opt_idx, varp));
}
- int opt_idx = findoption(name);
- if (opt_idx < 0) { // option not in the table
- return gov_unknown;
+ switch (value.type) {
+ case kOptValTypeNil:
+ return;
+ case kOptValTypeBoolean:
+ *(int *)varp = value.data.boolean;
+ return;
+ case kOptValTypeNumber:
+ *(OptInt *)varp = value.data.number;
+ return;
+ case kOptValTypeString:
+ *(char **)varp = value.data.string.data;
+ return;
}
+ UNREACHABLE;
+}
- char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), scope);
+/// Return C-string representation of OptVal. Caller must free the returned C-string.
+static char *optval_to_cstr(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return xstrdup("");
+ case kOptValTypeBoolean:
+ return xstrdup(o.data.boolean ? "true" : "false");
+ case kOptValTypeNumber: {
+ char *buf = xmalloc(NUMBUFLEN);
+ snprintf(buf, NUMBUFLEN, "%" PRId64, o.data.number);
+ return buf;
+ }
+ case kOptValTypeString: {
+ char *buf = xmalloc(o.data.string.size + 3);
+ snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
+ return buf;
+ }
+ }
+ UNREACHABLE;
+}
- if (flagsp != NULL) {
- // Return the P_xxxx option flags.
- *flagsp = options[opt_idx].flags;
+/// Convert an OptVal to an API Object.
+Object optval_as_object(OptVal o)
+{
+ switch (o.type) {
+ case kOptValTypeNil:
+ return NIL;
+ case kOptValTypeBoolean:
+ switch (o.data.boolean) {
+ case kFalse:
+ case kTrue:
+ return BOOLEAN_OBJ(o.data.boolean);
+ case kNone:
+ return NIL;
+ }
+ UNREACHABLE;
+ case kOptValTypeNumber:
+ return INTEGER_OBJ(o.data.number);
+ case kOptValTypeString:
+ return STRING_OBJ(o.data.string);
+ }
+ UNREACHABLE;
+}
+
+/// Convert an API Object to an OptVal.
+OptVal object_as_optval(Object o, bool *error)
+{
+ switch (o.type) {
+ case kObjectTypeNil:
+ return NIL_OPTVAL;
+ case kObjectTypeBoolean:
+ return BOOLEAN_OPTVAL(o.data.boolean);
+ case kObjectTypeInteger:
+ return NUMBER_OPTVAL((OptInt)o.data.integer);
+ case kObjectTypeString:
+ return STRING_OPTVAL(o.data.string);
+ default:
+ *error = true;
+ return NIL_OPTVAL;
}
+ UNREACHABLE;
+}
- if (options[opt_idx].flags & P_STRING) {
- if (varp == NULL) { // hidden option
- return gov_hidden_string;
+/// Unset the local value of an option. The exact semantics of this depend on the option.
+/// TODO(famiu): Remove this once we have a dedicated OptVal type for unset local options.
+///
+/// @param opt_idx Option index in options[] table.
+/// @param[in] varp Pointer to option variable.
+///
+/// @return [allocated] Option value equal to the unset value for the option.
+static OptVal optval_unset_local(int opt_idx, void *varp)
+{
+ vimoption_T *opt = &options[opt_idx];
+ // For global-local options, use the unset value of the local value.
+ if (opt->indir & PV_BOTH) {
+ // String global-local options always use an empty string for the unset value.
+ if (opt->flags & P_STRING) {
+ return STATIC_CSTR_TO_OPTVAL("");
}
- if (stringval != NULL) {
- if ((char **)varp == &p_pt) { // 'pastetoggle'
- *stringval = str2special_save(*(char **)(varp), false, false);
- } else {
- *stringval = xstrdup(*(char **)(varp));
- }
+
+ if ((int *)varp == &curbuf->b_p_ar) {
+ return BOOLEAN_OPTVAL(kNone);
+ } else if ((OptInt *)varp == &curwin->w_p_so || (OptInt *)varp == &curwin->w_p_siso) {
+ return NUMBER_OPTVAL(-1);
+ } else if ((OptInt *)varp == &curbuf->b_p_ul) {
+ return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL);
+ } else {
+ // This should never happen.
+ abort();
}
- return gov_string;
}
+ // For options that aren't global-local, just set the local value to the global value.
+ return get_option_value(opt->fullname, NULL, OPT_GLOBAL, NULL);
+}
+
+/// Get an allocated string containing a list of valid types for an option.
+/// For options with a singular type, it returns the name of the type. For options with multiple
+/// possible types, it returns a slash separated list of types. For example, if an option can be a
+/// number, boolean or string, the function returns "Number/Boolean/String"
+static char *option_get_valid_types(int opt_idx)
+{
+ uint32_t flags = options[opt_idx].flags;
+ uint32_t type_count = 0;
+
+ StringBuilder str = KV_INITIAL_VALUE;
+ kv_resize(str, 32);
+
+#define OPTION_ADD_TYPE(typename) \
+ do { \
+ if (type_count == 0) { \
+ kv_concat(str, typename); \
+ } else { \
+ kv_printf(str, "/%s", typename); \
+ } \
+ type_count++; \
+ } while (0);
- if (varp == NULL) { // hidden option
- return (options[opt_idx].flags & P_NUM) ? gov_hidden_number : gov_hidden_bool;
+ if (flags & P_NUM) {
+ OPTION_ADD_TYPE("Number");
}
- if (options[opt_idx].flags & P_NUM) {
- *numval = *(long *)varp;
- } else {
- // Special case: 'modified' is b_changed, but we also want to consider
- // it set when 'ff' or 'fenc' changed.
- if ((int *)varp == &curbuf->b_changed) {
- *numval = curbufIsChanged();
- } else {
- *numval = (long)(*(int *)varp);
- }
+ if (flags & P_BOOL) {
+ OPTION_ADD_TYPE("Boolean");
+ }
+ if (flags & P_STRING) {
+ OPTION_ADD_TYPE("String");
+ }
+
+ if (type_count == 0) {
+ abort();
}
- return (options[opt_idx].flags & P_NUM) ? gov_number : gov_bool;
+
+ // Ensure that the string is NUL-terminated.
+ kv_push(str, NUL);
+ return str.items;
+
+#undef OPTION_ADD_TYPE
}
-// Returns the option attributes and its value. Unlike the above function it
-// will return either global value or local value of the option depending on
-// what was requested, but it will never return global value if it was
-// requested to return local one and vice versa. Neither it will return
-// buffer-local value if it was requested to return window-local one.
-//
-// Pretends that option is absent if it is not present in the requested scope
-// (i.e. has no global, window-local or buffer-local value depending on
-// opt_type).
-//
-// Returned flags:
-// 0 hidden or unknown option, also option that does not have requested
-// type (see SREQ_* in option_defs.h)
-// see SOPT_* in option_defs.h for other flags
-//
-// Possible opt_type values: see SREQ_* in option_defs.h
-int get_option_value_strict(char *name, int64_t *numval, char **stringval, int opt_type, void *from)
+/// Gets the value for an option.
+///
+/// @param[in] name Option name.
+/// @param[out] flagsp Set to the option flags (P_xxxx) (if not NULL).
+/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
+/// @param[out] hidden Whether option is hidden.
+///
+/// @return [allocated] Option value. Returns NIL_OPTVAL for invalid options.
+OptVal get_option_value(const char *name, uint32_t *flagsp, int scope, bool *hidden)
{
- if (get_tty_option(name, stringval)) {
- return SOPT_STRING | SOPT_GLOBAL;
+ // Make sure that hidden and flagsp are never returned uninitialized
+ if (hidden != NULL) {
+ *hidden = false;
+ }
+ if (flagsp != NULL) {
+ *flagsp = 0;
+ }
+
+ char *str;
+ if (get_tty_option(name, &str)) {
+ return CSTR_AS_OPTVAL(str);
}
- int rv = 0;
int opt_idx = findoption(name);
- if (opt_idx < 0) {
- return 0;
+ if (opt_idx < 0) { // option not in the table
+ return NIL_OPTVAL;
}
- vimoption_T *p = &options[opt_idx];
+ vimoption_T *opt = &options[opt_idx];
+ void *varp = get_varp_scope(opt, scope);
- // Hidden option
- if (p->var == NULL) {
- return 0;
+ if (hidden != NULL) {
+ *hidden = varp == NULL;
}
- if (p->flags & P_BOOL) {
- rv |= SOPT_BOOL;
- } else if (p->flags & P_NUM) {
- rv |= SOPT_NUM;
- } else if (p->flags & P_STRING) {
- rv |= SOPT_STRING;
+ if (flagsp != NULL) {
+ // Return the P_xxxx option flags.
+ *flagsp = opt->flags;
}
- if (p->indir == PV_NONE) {
- if (opt_type == SREQ_GLOBAL) {
- rv |= SOPT_GLOBAL;
- } else {
- return 0; // Did not request global-only option
- }
- } else {
- if (p->indir & PV_BOTH) {
- rv |= SOPT_GLOBAL;
- }
+ return optval_copy(optval_from_varp(opt_idx, varp));
+}
- if (p->indir & PV_WIN) {
- if (opt_type == SREQ_BUF) {
- return 0; // Requested buffer-local, not window-local option
- }
- rv |= SOPT_WIN;
- } else if (p->indir & PV_BUF) {
- if (opt_type == SREQ_WIN) {
- return 0; // Requested window-local, not buffer-local option
- }
- rv |= SOPT_BUF;
+/// Return information for option at 'opt_idx'
+vimoption_T *get_option(int opt_idx)
+{
+ return &options[opt_idx];
+}
+
+/// Check if local value of global-local option is unset for current buffer / window.
+/// Always returns false for options that aren't global-local.
+///
+/// TODO(famiu): Remove this once we have an OptVal type to indicate an unset local value.
+static bool is_option_local_value_unset(vimoption_T *opt, buf_T *buf, win_T *win)
+{
+ // Local value of option that isn't global-local is always considered set.
+ if (!((int)opt->indir & PV_BOTH)) {
+ return false;
+ }
+
+ // Get pointer to local value in varp_local, and a pointer to the currently used value in varp.
+ // If the local value is the one currently being used, that indicates that it's set.
+ // Otherwise it indicates the local value is unset.
+ void *varp = get_varp_from(opt, buf, win);
+ void *varp_local = get_varp_scope_from(opt, OPT_LOCAL, buf, win);
+
+ return varp != varp_local;
+}
+
+/// Handle side-effects of setting an option.
+///
+/// @param opt_idx Index in options[] table. Must be >= 0.
+/// @param[in] varp Option variable pointer, cannot be NULL.
+/// @param old_value Old option value.
+/// @param new_value New option value.
+/// @param opt_flags Option flags.
+/// @param[out] value_checked Value was checked to be safe, no need to set P_INSECURE.
+/// @param value_replaced Value was replaced completely.
+/// @param[out] errbuf Buffer for error message.
+/// @param errbuflen Length of error buffer.
+///
+/// @return NULL on success, an untranslated error message on error.
+static const char *did_set_option(int opt_idx, void *varp, OptVal old_value, OptVal new_value,
+ int opt_flags, bool *value_checked, bool value_replaced,
+ char *errbuf, size_t errbuflen)
+{
+ vimoption_T *opt = &options[opt_idx];
+ const char *errmsg = NULL;
+ bool restore_chartab = false;
+ bool free_oldval = (opt->flags & P_ALLOCED);
+ bool value_changed = false;
+
+ optset_T did_set_cb_args = {
+ .os_varp = varp,
+ .os_idx = opt_idx,
+ .os_flags = opt_flags,
+ .os_oldval = old_value.data,
+ .os_newval = new_value.data,
+ .os_value_checked = false,
+ .os_value_changed = false,
+ .os_restore_chartab = false,
+ .os_errbuf = errbuf,
+ .os_errbuflen = errbuflen,
+ .os_buf = curbuf,
+ .os_win = curwin
+ };
+
+ // Disallow changing immutable options.
+ if (opt->immutable && !optval_equal(old_value, new_value)) {
+ errmsg = e_unsupportedoption;
+ }
+ // Disallow changing some options from secure mode.
+ else if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) {
+ errmsg = e_secure;
+ }
+ // Check for a "normal" directory or file name in some string options.
+ else if (new_value.type == kOptValTypeString
+ && check_illegal_path_names(*(char **)varp, opt->flags)) {
+ errmsg = e_invarg;
+ } else if (opt->opt_did_set_cb != NULL) {
+ // Invoke the option specific callback function to validate and apply the new value.
+ errmsg = opt->opt_did_set_cb(&did_set_cb_args);
+ // The 'filetype' and 'syntax' option callback functions may change the os_value_changed field.
+ value_changed = did_set_cb_args.os_value_changed;
+ // The 'keymap', 'filetype' and 'syntax' option callback functions may change the
+ // os_value_checked field.
+ *value_checked = did_set_cb_args.os_value_checked;
+ // The 'isident', 'iskeyword', 'isprint' and 'isfname' options may change the character table.
+ // On failure, this needs to be restored.
+ restore_chartab = did_set_cb_args.os_restore_chartab;
+ }
+
+ // If an error is detected, restore the previous value and don't do any further processing.
+ if (errmsg != NULL) {
+ set_option_varp(opt_idx, varp, old_value, true);
+ // When resetting some values, need to act on it.
+ if (restore_chartab) {
+ (void)buf_init_chartab(curbuf, true);
}
+
+ // Unset new_value as it is no longer valid.
+ new_value = NIL_OPTVAL; // NOLINT(clang-analyzer-deadcode.DeadStores)
+ return errmsg;
+ }
+
+ // Re-assign the new value as its value may get freed or modified by the option callback.
+ new_value = optval_from_varp(opt_idx, varp);
+
+ // Remember where the option was set.
+ set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
+ // Free options that are in allocated memory.
+ // Use "free_oldval", because recursiveness may change the flags (esp. init_highlight()).
+ if (free_oldval) {
+ optval_free(old_value);
+ }
+ opt->flags |= P_ALLOCED;
+
+ // Check the bound for num options.
+ if (new_value.type == kOptValTypeNumber) {
+ errmsg = check_num_option_bounds((OptInt *)varp, old_value.data.number, errbuf, errbuflen,
+ errmsg);
+ // Re-assign new_value because the new value was modified by the bound check.
+ new_value = optval_from_varp(opt_idx, varp);
+ }
+
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && (opt->indir & PV_BOTH)) {
+ // Global option with local value set to use global value.
+ // Free the local value and clear it.
+ void *varp_local = get_varp_scope(opt, OPT_LOCAL);
+ OptVal local_unset_value = optval_unset_local(opt_idx, varp_local);
+ set_option_varp(opt_idx, varp_local, local_unset_value, true);
+ } else if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
+ // May set global value for local option.
+ void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
+ set_option_varp(opt_idx, varp_global, optval_copy(new_value), true);
+ }
+
+ // Trigger the autocommand only after setting the flags.
+ if (varp == &curbuf->b_p_syn) {
+ do_syntax_autocmd(curbuf, value_changed);
+ } else if (varp == &curbuf->b_p_ft) {
+ // 'filetype' is set, trigger the FileType autocommand
+ // Skip this when called from a modeline
+ // Force autocmd when the filetype was changed
+ if (!(opt_flags & OPT_MODELINE) || value_changed) {
+ do_filetype_autocmd(curbuf, value_changed);
+ }
+ } else if (varp == &curwin->w_s->b_p_spl) {
+ do_spelllang_source(curwin);
+ }
+
+ // In case 'columns' or 'ls' changed.
+ comp_col();
+
+ if (varp == &p_mouse) {
+ setmouse(); // in case 'mouse' changed
+ } else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) {
+ // Changing Formatlistpattern when briopt includes the list setting:
+ // redraw
+ redraw_all_later(UPD_NOT_VALID);
+ } else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) {
+ // add / remove window bars for 'winbar'
+ set_winbar(true);
}
- if (stringval == NULL) {
- return rv;
+ if (curwin->w_curswant != MAXCOL && (opt->flags & (P_CURSWANT | P_RALL)) != 0) {
+ curwin->w_set_curswant = true;
}
- char_u *varp = NULL;
+ check_redraw(opt->flags);
- if (opt_type == SREQ_GLOBAL) {
- if (p->var == VAR_WIN) {
- return 0;
+ if (errmsg == NULL) {
+ uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+ opt->flags |= P_WAS_SET;
+
+ // When an option is set in the sandbox, from a modeline or in secure mode set the P_INSECURE
+ // flag. Otherwise, if a new value is stored reset the flag.
+ if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) {
+ *p |= P_INSECURE;
+ } else if (value_replaced) {
+ *p &= ~P_INSECURE;
}
- varp = p->var;
- } else {
- if (opt_type == SREQ_BUF) {
- // Special case: 'modified' is b_changed, but we also want to
- // consider it set when 'ff' or 'fenc' changed.
- if (p->indir == PV_MOD) {
- *numval = bufIsChanged((buf_T *)from);
- varp = NULL;
- } else {
- buf_T *save_curbuf = curbuf;
-
- // only getting a pointer, no need to use aucmd_prepbuf()
- curbuf = (buf_T *)from;
- curwin->w_buffer = curbuf;
- varp = (char_u *)get_varp_scope(p, OPT_LOCAL);
- curbuf = save_curbuf;
- curwin->w_buffer = curbuf;
- }
- } else if (opt_type == SREQ_WIN) {
- win_T *save_curwin = curwin;
- curwin = (win_T *)from;
- curbuf = curwin->w_buffer;
- varp = (char_u *)get_varp_scope(p, OPT_LOCAL);
- curwin = save_curwin;
- curbuf = curwin->w_buffer;
+ }
+
+ return errmsg;
+}
+
+/// Set the value of an option using an OptVal.
+///
+/// @param opt_idx Index in options[] table. Must be >= 0.
+/// @param[in] varp Option variable pointer, cannot be NULL.
+/// @param value New option value. Might get freed.
+/// @param opt_flags Option flags.
+/// @param value_replaced Value was replaced completely.
+/// @param[out] errbuf Buffer for error message.
+/// @param errbuflen Length of error buffer.
+///
+/// @return NULL on success, an untranslated error message on error.
+static const char *set_option(const int opt_idx, void *varp, OptVal value, int opt_flags,
+ const bool value_replaced, char *errbuf, size_t errbuflen)
+{
+ assert(opt_idx >= 0 && varp != NULL);
+
+ const char *errmsg = NULL;
+ bool value_checked = false;
+
+ vimoption_T *opt = &options[opt_idx];
+
+ static const char *optval_type_names[] = {
+ [kOptValTypeNil] = "Nil",
+ [kOptValTypeBoolean] = "Boolean",
+ [kOptValTypeNumber] = "Number",
+ [kOptValTypeString] = "String"
+ };
+
+ if (value.type == kOptValTypeNil) {
+ // Don't try to unset local value if scope is global.
+ // TODO(famiu): Change this to forbid changing all non-local scopes when the API scope bug is
+ // fixed.
+ if (opt_flags == OPT_GLOBAL) {
+ errmsg = _("Cannot unset global option value");
+ } else {
+ optval_free(value);
+ value = optval_unset_local(opt_idx, varp);
}
+ } else if (!optval_match_type(value, opt_idx)) {
+ char *rep = optval_to_cstr(value);
+ char *valid_types = option_get_valid_types(opt_idx);
+ snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"),
+ opt->fullname, valid_types, optval_type_names[value.type], rep);
+ xfree(rep);
+ xfree(valid_types);
+ errmsg = errbuf;
+ }
+
+ if (errmsg != NULL) {
+ goto err;
+ }
+
+ // When using ":set opt=val" for a global option with a local value the local value will be reset,
+ // use the global value here.
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0 && ((int)opt->indir & PV_BOTH)) {
+ varp = opt->var;
+ }
+
+ OptVal old_value = optval_from_varp(opt_idx, varp);
+ OptVal old_global_value = NIL_OPTVAL;
+ OptVal old_local_value = NIL_OPTVAL;
- if (varp == p->var) {
- return (rv | SOPT_UNSET);
+ // Save the local and global values before changing anything. This is needed as for a global-only
+ // option setting the "local value" in fact sets the global value (since there is only one value).
+ //
+ // TODO(famiu): This needs to be changed to use the current type of the old value instead of
+ // value.type, when multi-type options are added.
+ if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
+ old_global_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_GLOBAL));
+ old_local_value = optval_from_varp(opt_idx, get_varp_scope(opt, OPT_LOCAL));
+
+ // If local value of global-local option is unset, use global value as local value.
+ if (is_option_local_value_unset(opt, curbuf, curwin)) {
+ old_local_value = old_global_value;
}
}
- if (varp != NULL) {
- if (p->flags & P_STRING) {
- *stringval = *(char **)(varp);
- } else if (p->flags & P_NUM) {
- *numval = *(long *)varp;
- } else {
- *numval = *(int *)varp;
+ // Value that's actually being used.
+ // For local scope of a global-local option, it is equal to the global value.
+ // In every other case, it is the same as old_value.
+ const bool oldval_is_global = ((int)opt->indir & PV_BOTH) && (opt_flags & OPT_LOCAL);
+ OptVal used_old_value = oldval_is_global ? optval_from_varp(opt_idx, get_varp(opt)) : old_value;
+
+ if (value.type == kOptValTypeNumber) {
+ errmsg = validate_num_option((OptInt *)varp, &value.data.number);
+
+ // Don't change the value and return early if validation failed.
+ if (errmsg != NULL) {
+ goto err;
}
}
- return rv;
-}
+ set_option_varp(opt_idx, varp, value, false);
-// Return information for option at 'opt_idx'
-vimoption_T *get_option(int opt_idx)
-{
- return &options[opt_idx];
+ OptVal saved_used_value = optval_copy(used_old_value);
+ OptVal saved_old_global_value = optval_copy(old_global_value);
+ OptVal saved_old_local_value = optval_copy(old_local_value);
+ // New value (and varp) may become invalid if the buffer is closed by autocommands.
+ OptVal saved_new_value = optval_copy(value);
+
+ uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
+ const int secure_saved = secure;
+
+ // When an option is set in the sandbox, from a modeline or in secure mode, then deal with side
+ // effects in secure mode. Also when the value was set with the P_INSECURE flag and is not
+ // completely replaced.
+ if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & P_INSECURE))) {
+ secure = 1;
+ }
+
+ errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, &value_checked,
+ value_replaced, errbuf, errbuflen);
+
+ secure = secure_saved;
+
+ if (errmsg == NULL) {
+ if (!starting) {
+ apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value,
+ saved_old_local_value, saved_new_value, errmsg);
+ }
+ if (opt->flags & P_UI_OPTION) {
+ // Calculate saved_new_value again as its value might be changed by bound checks.
+ // NOTE: Currently there are no buffer/window local UI options, but if there ever are buffer
+ // or window local UI options added in the future, varp might become invalid if the buffer or
+ // window is closed during an autocommand, and a check would have to be added for it.
+ optval_free(saved_new_value);
+ saved_new_value = optval_copy(optval_from_varp(opt_idx, varp));
+ ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value));
+ }
+ }
+
+ // Free copied values as they are not needed anymore
+ optval_free(saved_used_value);
+ optval_free(saved_old_local_value);
+ optval_free(saved_old_global_value);
+ optval_free(saved_new_value);
+ return errmsg;
+err:
+ optval_free(value);
+ return errmsg;
}
/// Set the value of an option
///
-/// @param[in] name Option name.
-/// @param[in] number New value for the number or boolean option.
-/// @param[in] string New value for string option.
+/// @param[in] name Option name.
+/// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
-/// If OPT_CLEAR is set, the value of the option
-/// is cleared (the exact semantics of this depend
-/// on the option).
///
-/// @return NULL on success, an untranslated error message on error.
-char *set_option_value(const char *const name, const long number, const char *const string,
- const int opt_flags)
+/// @return NULL on success, an untranslated error message on error.
+const char *set_option_value(const char *const name, const OptVal value, int opt_flags)
FUNC_ATTR_NONNULL_ARG(1)
{
+ static char errbuf[IOSIZE];
+
if (is_tty_option(name)) {
return NULL; // Fail silently; many old vimrcs set t_xx options.
}
int opt_idx = findoption(name);
if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return NULL;
+ snprintf(errbuf, IOSIZE, _(e_unknown_option2), name);
+ return errbuf;
}
uint32_t flags = options[opt_idx].flags;
// Disallow changing some options in the sandbox
if (sandbox > 0 && (flags & P_SECURE)) {
- emsg(_(e_sandbox));
- return NULL;
- }
-
- if (flags & P_STRING) {
- const char *s = string;
- if (s == NULL || opt_flags & OPT_CLEAR) {
- s = "";
- }
- return set_string_option(opt_idx, s, opt_flags);
+ return _(e_sandbox);
}
- char_u *varp = (char_u *)get_varp_scope(&(options[opt_idx]), opt_flags);
+ void *varp = get_varp_scope(&(options[opt_idx]), opt_flags);
if (varp == NULL) {
// hidden option is not changed
return NULL;
}
- if (number == 0 && string != NULL) {
- int idx;
+ const char *errmsg = NULL;
- // Either we are given a string or we are setting option
- // to zero.
- for (idx = 0; string[idx] == '0'; idx++) {}
- if (string[idx] != NUL || idx == 0) {
- // There's another character after zeros or the string
- // is empty. In both cases, we are trying to set a
- // num option using a string.
- semsg(_("E521: Number required: &%s = '%s'"),
- name, string);
- return NULL; // do nothing as we hit an error
- }
- }
- long numval = number;
- if (opt_flags & OPT_CLEAR) {
- if ((int *)varp == &curbuf->b_p_ar) {
- numval = -1;
- } else if ((long *)varp == &curbuf->b_p_ul) {
- numval = NO_LOCAL_UNDOLEVEL;
- } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) {
- numval = -1;
- } else {
- char *s = NULL;
- (void)get_option_value(name, &numval, &s, NULL, OPT_GLOBAL);
- }
- }
- if (flags & P_NUM) {
- return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags);
- }
- return set_bool_option(opt_idx, varp, (int)numval, opt_flags);
+ errmsg = set_option(opt_idx, varp, optval_copy(value), opt_flags, true, errbuf, sizeof(errbuf));
+
+ return errmsg;
}
/// Call set_option_value() and when an error is returned report it.
///
/// @param opt_flags OPT_LOCAL or 0 (both)
-void set_option_value_give_err(const char *name, long number, const char *string, int opt_flags)
+void set_option_value_give_err(const char *name, OptVal value, int opt_flags)
{
- char *errmsg = set_option_value(name, number, string, opt_flags);
+ const char *errmsg = set_option_value(name, value, opt_flags);
if (errmsg != NULL) {
emsg(_(errmsg));
@@ -3148,14 +3884,6 @@ bool is_option_allocated(const char *name)
return idx >= 0 && (options[idx].flags & P_ALLOCED);
}
-/// Return true if "name" is a string option.
-/// Returns false if option "name" does not exist.
-bool is_string_option(const char *name)
-{
- int idx = findoption(name);
- return idx >= 0 && (options[idx].flags & P_STRING);
-}
-
// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
// When "has_lt" is true there is a '<' before "*arg_arg".
// Returns 0 when the key is not recognized.
@@ -3218,16 +3946,15 @@ static void showoptions(bool all, int opt_flags)
continue;
}
- char_u *varp = NULL;
+ void *varp = NULL;
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) {
if (p->indir != PV_NONE) {
- varp = (char_u *)get_varp_scope(p, opt_flags);
+ varp = get_varp_scope(p, opt_flags);
}
} else {
varp = get_varp(p);
}
- if (varp != NULL
- && (all == 1 || (all == 0 && !optval_default(p, varp)))) {
+ if (varp != NULL && (all || !optval_default(p, varp))) {
int len;
if (opt_flags & OPT_ONECOLUMN) {
len = Columns;
@@ -3278,13 +4005,13 @@ static void showoptions(bool all, int opt_flags)
}
/// Return true if option "p" has its default value.
-static int optval_default(vimoption_T *p, const char_u *varp)
+static int optval_default(vimoption_T *p, const void *varp)
{
if (varp == NULL) {
return true; // hidden option is always at default
}
if (p->flags & P_NUM) {
- return *(long *)varp == (long)(intptr_t)p->def_val;
+ return *(OptInt *)varp == (OptInt)(intptr_t)p->def_val;
}
if (p->flags & P_BOOL) {
return *(int *)varp == (int)(intptr_t)p->def_val;
@@ -3307,10 +4034,10 @@ void ui_refresh_options(void)
if (flags & P_BOOL) {
value = BOOLEAN_OBJ(*(int *)varp);
} else if (flags & P_NUM) {
- value = INTEGER_OBJ(*(long *)varp);
+ value = INTEGER_OBJ(*(OptInt *)varp);
} else if (flags & P_STRING) {
// cstr_as_string handles NULL string
- value = STRING_OBJ(cstr_as_string(*(char **)varp));
+ value = CSTR_AS_OBJ(*(char **)varp);
}
ui_call_option_set(name, value);
}
@@ -3330,7 +4057,7 @@ static void showoneopt(vimoption_T *p, int opt_flags)
silent_mode = false;
info_message = true; // use os_msg(), not os_errmsg()
- char_u *varp = (char_u *)get_varp_scope(p, opt_flags);
+ void *varp = get_varp_scope(p, opt_flags);
// for 'modified' we also need to check if 'ff' or 'fenc' changed.
if ((p->flags & P_BOOL) && ((int *)varp == &curbuf->b_changed
@@ -3346,7 +4073,7 @@ static void showoneopt(vimoption_T *p, int opt_flags)
msg_putchar('=');
// put value string in NameBuff
option_value2string(p, opt_flags);
- msg_outtrans(NameBuff);
+ msg_outtrans(NameBuff, 0);
}
silent_mode = save_silent;
@@ -3397,23 +4124,23 @@ int makeset(FILE *fd, int opt_flags, int local_only)
continue;
}
- char *varp = get_varp_scope(p, opt_flags); // currently used value
+ void *varp = get_varp_scope(p, opt_flags); // currently used value
// Hidden options are never written.
if (!varp) {
continue;
}
// Global values are only written when not at the default value.
- if ((opt_flags & OPT_GLOBAL) && optval_default(p, (char_u *)varp)) {
+ if ((opt_flags & OPT_GLOBAL) && optval_default(p, varp)) {
continue;
}
if ((opt_flags & OPT_SKIPRTP)
- && (p->var == (char_u *)&p_rtp || p->var == (char_u *)&p_pp)) {
+ && (p->var == &p_rtp || p->var == &p_pp)) {
continue;
}
int round = 2;
- char_u *varp_local = NULL; // fresh value
+ void *varp_local = NULL; // fresh value
if (p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
// skip window-local option when only doing globals
@@ -3423,11 +4150,11 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// When fresh value of window-local option is not at the
// default, need to write it too.
if (!(opt_flags & OPT_GLOBAL) && !local_only) {
- char_u *varp_fresh = (char_u *)get_varp_scope(p, OPT_GLOBAL); // local value
+ void *varp_fresh = get_varp_scope(p, OPT_GLOBAL); // local value
if (!optval_default(p, varp_fresh)) {
round = 1;
- varp_local = (char_u *)varp;
- varp = (char *)varp_fresh;
+ varp_local = varp;
+ varp = varp_fresh;
}
}
}
@@ -3435,7 +4162,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// Round 1: fresh value for window-local options.
// Round 2: other values
- for (; round <= 2; varp = (char *)varp_local, round++) {
+ for (; round <= 2; varp = varp_local, round++) {
char *cmd;
if (round == 1 || (opt_flags & OPT_GLOBAL)) {
cmd = "set";
@@ -3448,7 +4175,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
return FAIL;
}
} else if (p->flags & P_NUM) {
- if (put_setnum(fd, cmd, p->fullname, (long *)varp) == FAIL) {
+ if (put_setnum(fd, cmd, p->fullname, (OptInt *)varp) == FAIL) {
return FAIL;
}
} else { // P_STRING
@@ -3458,7 +4185,7 @@ int makeset(FILE *fd, int opt_flags, int local_only)
// already right, avoids reloading the syntax file.
if (p->indir == PV_SYN || p->indir == PV_FT) {
if (fprintf(fd, "if &%s != '%s'", p->fullname,
- *(char_u **)(varp)) < 0
+ *(char **)(varp)) < 0
|| put_eol(fd) < 0) {
return FAIL;
}
@@ -3505,20 +4232,10 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_
}
char *buf = NULL;
- char_u *part = NULL;
+ char *part = NULL;
if (*valuep != NULL) {
- // Output 'pastetoggle' as key names. For other
- // options some characters have to be escaped with
- // CTRL-V or backslash
- if (valuep == &p_pt) {
- char_u *s = (char_u *)(*valuep);
- while (*s != NUL) {
- if (put_escstr(fd, (char *)str2special((const char **)&s, false, false), 2) == FAIL) {
- return FAIL;
- }
- }
- } else if ((flags & P_EXPAND) != 0) {
+ if ((flags & P_EXPAND) != 0) {
size_t size = (size_t)strlen(*valuep) + 1;
// replace home directory in the whole option value into "buf"
@@ -3543,8 +4260,8 @@ static int put_setstring(FILE *fd, char *cmd, char *name, char **valuep, uint64_
if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
goto fail;
}
- (void)copy_option_part(&p, (char *)part, size, ",");
- if (put_escstr(fd, (char *)part, 2) == FAIL || put_eol(fd) == FAIL) {
+ (void)copy_option_part(&p, part, size, ",");
+ if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
goto fail;
}
}
@@ -3571,15 +4288,15 @@ fail:
return FAIL;
}
-static int put_setnum(FILE *fd, char *cmd, char *name, long *valuep)
+static int put_setnum(FILE *fd, char *cmd, char *name, OptInt *valuep)
{
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
}
- long wc;
- if (wc_use_keyname((char_u *)valuep, &wc)) {
+ OptInt wc;
+ if (wc_use_keyname(valuep, &wc)) {
// print 'wildchar' and 'wildcharm' as a key name
- if (fputs((char *)get_special_key_name((int)wc, 0), fd) < 0) {
+ if (fputs(get_special_key_name((int)wc, 0), fd) < 0) {
return FAIL;
}
} else if (fprintf(fd, "%" PRId64, (int64_t)(*valuep)) < 0) {
@@ -3603,196 +4320,94 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value)
return OK;
}
-// Unset local option value, similar to ":set opt<".
-void unset_global_local_option(char *name, void *from)
-{
- vimoption_T *p;
- buf_T *buf = (buf_T *)from;
-
- int opt_idx = findoption(name);
- if (opt_idx < 0) {
- semsg(_("E355: Unknown option: %s"), name);
- return;
- }
- p = &(options[opt_idx]);
-
- switch ((int)p->indir) {
- // global option with local value: use local value if it's been set
- case PV_EP:
- clear_string_option(&buf->b_p_ep);
- break;
- case PV_KP:
- clear_string_option(&buf->b_p_kp);
- break;
- case PV_PATH:
- clear_string_option(&buf->b_p_path);
- break;
- case PV_AR:
- buf->b_p_ar = -1;
- break;
- case PV_BKC:
- clear_string_option(&buf->b_p_bkc);
- buf->b_bkc_flags = 0;
- break;
- case PV_TAGS:
- clear_string_option(&buf->b_p_tags);
- break;
- case PV_TC:
- clear_string_option(&buf->b_p_tc);
- buf->b_tc_flags = 0;
- break;
- case PV_SISO:
- curwin->w_p_siso = -1;
- break;
- case PV_SO:
- curwin->w_p_so = -1;
- break;
- case PV_DEF:
- clear_string_option(&buf->b_p_def);
- break;
- case PV_INC:
- clear_string_option(&buf->b_p_inc);
- break;
- case PV_DICT:
- clear_string_option(&buf->b_p_dict);
- break;
- case PV_TSR:
- clear_string_option(&buf->b_p_tsr);
- break;
- case PV_TSRFU:
- clear_string_option(&buf->b_p_tsrfu);
- break;
- case PV_FP:
- clear_string_option(&buf->b_p_fp);
- break;
- case PV_EFM:
- clear_string_option(&buf->b_p_efm);
- break;
- case PV_GP:
- clear_string_option(&buf->b_p_gp);
- break;
- case PV_MP:
- clear_string_option(&buf->b_p_mp);
- break;
- case PV_SBR:
- clear_string_option(&((win_T *)from)->w_p_sbr);
- break;
- case PV_STL:
- clear_string_option(&((win_T *)from)->w_p_stl);
- break;
- case PV_WBR:
- clear_string_option(&((win_T *)from)->w_p_wbr);
- break;
- case PV_UL:
- buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
- break;
- case PV_LW:
- clear_string_option(&buf->b_p_lw);
- break;
- case PV_MENC:
- clear_string_option(&buf->b_p_menc);
- break;
- case PV_LCS:
- clear_string_option(&((win_T *)from)->w_p_lcs);
- set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true);
- redraw_later((win_T *)from, UPD_NOT_VALID);
- break;
- case PV_FCS:
- clear_string_option(&((win_T *)from)->w_p_fcs);
- set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
- redraw_later((win_T *)from, UPD_NOT_VALID);
- break;
- case PV_VE:
- clear_string_option(&((win_T *)from)->w_p_ve);
- ((win_T *)from)->w_ve_flags = 0;
- break;
- case PV_STC:
- clear_string_option(&((win_T *)from)->w_p_stc);
- break;
- }
-}
-
-char *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
+void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
{
if ((scope & OPT_GLOBAL) && p->indir != PV_NONE) {
if (p->var == VAR_WIN) {
return GLOBAL_WO(get_varp_from(p, buf, win));
}
- return (char *)p->var;
+ return p->var;
}
if ((scope & OPT_LOCAL) && ((int)p->indir & PV_BOTH)) {
switch ((int)p->indir) {
case PV_FP:
- return (char *)&(buf->b_p_fp);
+ return &(buf->b_p_fp);
case PV_EFM:
- return (char *)&(buf->b_p_efm);
+ return &(buf->b_p_efm);
case PV_GP:
- return (char *)&(buf->b_p_gp);
+ return &(buf->b_p_gp);
case PV_MP:
- return (char *)&(buf->b_p_mp);
+ return &(buf->b_p_mp);
case PV_EP:
- return (char *)&(buf->b_p_ep);
+ return &(buf->b_p_ep);
case PV_KP:
- return (char *)&(buf->b_p_kp);
+ return &(buf->b_p_kp);
case PV_PATH:
- return (char *)&(buf->b_p_path);
+ return &(buf->b_p_path);
case PV_AR:
- return (char *)&(buf->b_p_ar);
+ return &(buf->b_p_ar);
case PV_TAGS:
- return (char *)&(buf->b_p_tags);
+ return &(buf->b_p_tags);
case PV_TC:
- return (char *)&(buf->b_p_tc);
+ return &(buf->b_p_tc);
case PV_SISO:
- return (char *)&(win->w_p_siso);
+ return &(win->w_p_siso);
case PV_SO:
- return (char *)&(win->w_p_so);
+ return &(win->w_p_so);
case PV_DEF:
- return (char *)&(buf->b_p_def);
+ return &(buf->b_p_def);
case PV_INC:
- return (char *)&(buf->b_p_inc);
+ return &(buf->b_p_inc);
case PV_DICT:
- return (char *)&(buf->b_p_dict);
+ return &(buf->b_p_dict);
case PV_TSR:
- return (char *)&(buf->b_p_tsr);
+ return &(buf->b_p_tsr);
case PV_TSRFU:
- return (char *)&(buf->b_p_tsrfu);
+ return &(buf->b_p_tsrfu);
case PV_TFU:
- return (char *)&(buf->b_p_tfu);
+ return &(buf->b_p_tfu);
case PV_SBR:
- return (char *)&(win->w_p_sbr);
+ return &(win->w_p_sbr);
case PV_STL:
- return (char *)&(win->w_p_stl);
+ return &(win->w_p_stl);
case PV_WBR:
- return (char *)&(win->w_p_wbr);
+ return &(win->w_p_wbr);
case PV_UL:
- return (char *)&(buf->b_p_ul);
+ return &(buf->b_p_ul);
case PV_LW:
- return (char *)&(buf->b_p_lw);
+ return &(buf->b_p_lw);
case PV_BKC:
- return (char *)&(buf->b_p_bkc);
+ return &(buf->b_p_bkc);
case PV_MENC:
- return (char *)&(buf->b_p_menc);
+ return &(buf->b_p_menc);
case PV_FCS:
- return (char *)&(win->w_p_fcs);
+ return &(win->w_p_fcs);
case PV_LCS:
- return (char *)&(win->w_p_lcs);
+ return &(win->w_p_lcs);
case PV_VE:
- return (char *)&(win->w_p_ve);
+ return &(win->w_p_ve);
}
return NULL; // "cannot happen"
}
- return (char *)get_varp_from(p, buf, win);
+ return get_varp_from(p, buf, win);
}
/// Get pointer to option variable, depending on local or global scope.
///
/// @param scope can be OPT_LOCAL, OPT_GLOBAL or a combination.
-char *get_varp_scope(vimoption_T *p, int scope)
+void *get_varp_scope(vimoption_T *p, int scope)
{
return get_varp_scope_from(p, scope, curbuf, curwin);
}
-static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
+/// Get pointer to option variable at 'opt_idx', depending on local or global
+/// scope.
+void *get_option_varp_scope_from(int opt_idx, int scope, buf_T *buf, win_T *win)
+{
+ return get_varp_scope_from(&(options[opt_idx]), scope, buf, win);
+}
+
+void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
{
// hidden option, always return NULL
if (p->var == NULL) {
@@ -3805,309 +4420,284 @@ static char_u *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
// global option with local value: use local value if it's been set
case PV_EP:
- return *buf->b_p_ep != NUL
- ? (char_u *)&buf->b_p_ep : p->var;
+ return *buf->b_p_ep != NUL ? &buf->b_p_ep : p->var;
case PV_KP:
- return *buf->b_p_kp != NUL
- ? (char_u *)&buf->b_p_kp : p->var;
+ return *buf->b_p_kp != NUL ? &buf->b_p_kp : p->var;
case PV_PATH:
- return *buf->b_p_path != NUL
- ? (char_u *)&(buf->b_p_path) : p->var;
+ return *buf->b_p_path != NUL ? &(buf->b_p_path) : p->var;
case PV_AR:
- return buf->b_p_ar >= 0
- ? (char_u *)&(buf->b_p_ar) : p->var;
+ return buf->b_p_ar >= 0 ? &(buf->b_p_ar) : p->var;
case PV_TAGS:
- return *buf->b_p_tags != NUL
- ? (char_u *)&(buf->b_p_tags) : p->var;
+ return *buf->b_p_tags != NUL ? &(buf->b_p_tags) : p->var;
case PV_TC:
- return *buf->b_p_tc != NUL
- ? (char_u *)&(buf->b_p_tc) : p->var;
+ return *buf->b_p_tc != NUL ? &(buf->b_p_tc) : p->var;
case PV_SISO:
- return win->w_p_siso >= 0
- ? (char_u *)&(win->w_p_siso) : p->var;
+ return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var;
case PV_SO:
- return win->w_p_so >= 0
- ? (char_u *)&(win->w_p_so) : p->var;
+ return win->w_p_so >= 0 ? &(win->w_p_so) : p->var;
case PV_BKC:
- return *buf->b_p_bkc != NUL
- ? (char_u *)&(buf->b_p_bkc) : p->var;
+ return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var;
case PV_DEF:
- return *buf->b_p_def != NUL
- ? (char_u *)&(buf->b_p_def) : p->var;
+ return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var;
case PV_INC:
- return *buf->b_p_inc != NUL
- ? (char_u *)&(buf->b_p_inc) : p->var;
+ return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var;
case PV_DICT:
- return *buf->b_p_dict != NUL
- ? (char_u *)&(buf->b_p_dict) : p->var;
+ return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var;
case PV_TSR:
- return *buf->b_p_tsr != NUL
- ? (char_u *)&(buf->b_p_tsr) : p->var;
+ return *buf->b_p_tsr != NUL ? &(buf->b_p_tsr) : p->var;
case PV_TSRFU:
- return *buf->b_p_tsrfu != NUL
- ? (char_u *)&(buf->b_p_tsrfu) : p->var;
+ return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
case PV_FP:
- return *buf->b_p_fp != NUL
- ? (char_u *)&(buf->b_p_fp) : p->var;
+ return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
case PV_EFM:
- return *buf->b_p_efm != NUL
- ? (char_u *)&(buf->b_p_efm) : p->var;
+ return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
case PV_GP:
- return *buf->b_p_gp != NUL
- ? (char_u *)&(buf->b_p_gp) : p->var;
+ return *buf->b_p_gp != NUL ? &(buf->b_p_gp) : p->var;
case PV_MP:
- return *buf->b_p_mp != NUL
- ? (char_u *)&(buf->b_p_mp) : p->var;
+ return *buf->b_p_mp != NUL ? &(buf->b_p_mp) : p->var;
case PV_SBR:
- return *win->w_p_sbr != NUL
- ? (char_u *)&(win->w_p_sbr) : p->var;
+ return *win->w_p_sbr != NUL ? &(win->w_p_sbr) : p->var;
case PV_STL:
- return *win->w_p_stl != NUL
- ? (char_u *)&(win->w_p_stl) : p->var;
+ return *win->w_p_stl != NUL ? &(win->w_p_stl) : p->var;
case PV_WBR:
- return *win->w_p_wbr != NUL
- ? (char_u *)&(win->w_p_wbr) : p->var;
+ return *win->w_p_wbr != NUL ? &(win->w_p_wbr) : p->var;
case PV_UL:
- return buf->b_p_ul != NO_LOCAL_UNDOLEVEL
- ? (char_u *)&(buf->b_p_ul) : p->var;
+ return buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(buf->b_p_ul) : p->var;
case PV_LW:
- return *buf->b_p_lw != NUL
- ? (char_u *)&(buf->b_p_lw) : p->var;
+ return *buf->b_p_lw != NUL ? &(buf->b_p_lw) : p->var;
case PV_MENC:
- return *buf->b_p_menc != NUL
- ? (char_u *)&(buf->b_p_menc) : p->var;
+ return *buf->b_p_menc != NUL ? &(buf->b_p_menc) : p->var;
case PV_FCS:
- return *win->w_p_fcs != NUL
- ? (char_u *)&(win->w_p_fcs) : p->var;
+ return *win->w_p_fcs != NUL ? &(win->w_p_fcs) : p->var;
case PV_LCS:
- return *win->w_p_lcs != NUL
- ? (char_u *)&(win->w_p_lcs) : p->var;
+ return *win->w_p_lcs != NUL ? &(win->w_p_lcs) : p->var;
case PV_VE:
- return *win->w_p_ve != NUL
- ? (char_u *)&win->w_p_ve : p->var;
+ return *win->w_p_ve != NUL ? &win->w_p_ve : p->var;
case PV_ARAB:
- return (char_u *)&(win->w_p_arab);
+ return &(win->w_p_arab);
case PV_LIST:
- return (char_u *)&(win->w_p_list);
+ return &(win->w_p_list);
case PV_SPELL:
- return (char_u *)&(win->w_p_spell);
+ return &(win->w_p_spell);
case PV_CUC:
- return (char_u *)&(win->w_p_cuc);
+ return &(win->w_p_cuc);
case PV_CUL:
- return (char_u *)&(win->w_p_cul);
+ return &(win->w_p_cul);
case PV_CULOPT:
- return (char_u *)&(win->w_p_culopt);
+ return &(win->w_p_culopt);
case PV_CC:
- return (char_u *)&(win->w_p_cc);
+ return &(win->w_p_cc);
case PV_DIFF:
- return (char_u *)&(win->w_p_diff);
+ return &(win->w_p_diff);
case PV_FDC:
- return (char_u *)&(win->w_p_fdc);
+ return &(win->w_p_fdc);
case PV_FEN:
- return (char_u *)&(win->w_p_fen);
+ return &(win->w_p_fen);
case PV_FDI:
- return (char_u *)&(win->w_p_fdi);
+ return &(win->w_p_fdi);
case PV_FDL:
- return (char_u *)&(win->w_p_fdl);
+ return &(win->w_p_fdl);
case PV_FDM:
- return (char_u *)&(win->w_p_fdm);
+ return &(win->w_p_fdm);
case PV_FML:
- return (char_u *)&(win->w_p_fml);
+ return &(win->w_p_fml);
case PV_FDN:
- return (char_u *)&(win->w_p_fdn);
+ return &(win->w_p_fdn);
case PV_FDE:
- return (char_u *)&(win->w_p_fde);
+ return &(win->w_p_fde);
case PV_FDT:
- return (char_u *)&(win->w_p_fdt);
+ return &(win->w_p_fdt);
case PV_FMR:
- return (char_u *)&(win->w_p_fmr);
+ return &(win->w_p_fmr);
case PV_NU:
- return (char_u *)&(win->w_p_nu);
+ return &(win->w_p_nu);
case PV_RNU:
- return (char_u *)&(win->w_p_rnu);
+ return &(win->w_p_rnu);
case PV_NUW:
- return (char_u *)&(win->w_p_nuw);
+ return &(win->w_p_nuw);
case PV_WFH:
- return (char_u *)&(win->w_p_wfh);
+ return &(win->w_p_wfh);
case PV_WFW:
- return (char_u *)&(win->w_p_wfw);
+ return &(win->w_p_wfw);
case PV_PVW:
- return (char_u *)&(win->w_p_pvw);
+ return &(win->w_p_pvw);
case PV_RL:
- return (char_u *)&(win->w_p_rl);
+ return &(win->w_p_rl);
case PV_RLC:
- return (char_u *)&(win->w_p_rlc);
+ return &(win->w_p_rlc);
case PV_SCROLL:
- return (char_u *)&(win->w_p_scr);
+ return &(win->w_p_scr);
+ case PV_SMS:
+ return &(win->w_p_sms);
case PV_WRAP:
- return (char_u *)&(win->w_p_wrap);
+ return &(win->w_p_wrap);
case PV_LBR:
- return (char_u *)&(win->w_p_lbr);
+ return &(win->w_p_lbr);
case PV_BRI:
- return (char_u *)&(win->w_p_bri);
+ return &(win->w_p_bri);
case PV_BRIOPT:
- return (char_u *)&(win->w_p_briopt);
+ return &(win->w_p_briopt);
case PV_SCBIND:
- return (char_u *)&(win->w_p_scb);
+ return &(win->w_p_scb);
case PV_CRBIND:
- return (char_u *)&(win->w_p_crb);
+ return &(win->w_p_crb);
case PV_COCU:
- return (char_u *)&(win->w_p_cocu);
+ return &(win->w_p_cocu);
case PV_COLE:
- return (char_u *)&(win->w_p_cole);
+ return &(win->w_p_cole);
case PV_AI:
- return (char_u *)&(buf->b_p_ai);
+ return &(buf->b_p_ai);
case PV_BIN:
- return (char_u *)&(buf->b_p_bin);
+ return &(buf->b_p_bin);
case PV_BOMB:
- return (char_u *)&(buf->b_p_bomb);
+ return &(buf->b_p_bomb);
case PV_BH:
- return (char_u *)&(buf->b_p_bh);
+ return &(buf->b_p_bh);
case PV_BT:
- return (char_u *)&(buf->b_p_bt);
+ return &(buf->b_p_bt);
case PV_BL:
- return (char_u *)&(buf->b_p_bl);
+ return &(buf->b_p_bl);
case PV_CHANNEL:
- return (char_u *)&(buf->b_p_channel);
+ return &(buf->b_p_channel);
case PV_CI:
- return (char_u *)&(buf->b_p_ci);
+ return &(buf->b_p_ci);
case PV_CIN:
- return (char_u *)&(buf->b_p_cin);
+ return &(buf->b_p_cin);
case PV_CINK:
- return (char_u *)&(buf->b_p_cink);
+ return &(buf->b_p_cink);
case PV_CINO:
- return (char_u *)&(buf->b_p_cino);
+ return &(buf->b_p_cino);
case PV_CINSD:
- return (char_u *)&(buf->b_p_cinsd);
+ return &(buf->b_p_cinsd);
case PV_CINW:
- return (char_u *)&(buf->b_p_cinw);
+ return &(buf->b_p_cinw);
case PV_COM:
- return (char_u *)&(buf->b_p_com);
+ return &(buf->b_p_com);
case PV_CMS:
- return (char_u *)&(buf->b_p_cms);
+ return &(buf->b_p_cms);
case PV_CPT:
- return (char_u *)&(buf->b_p_cpt);
+ return &(buf->b_p_cpt);
#ifdef BACKSLASH_IN_FILENAME
case PV_CSL:
- return (char_u *)&(buf->b_p_csl);
+ return &(buf->b_p_csl);
#endif
case PV_CFU:
- return (char_u *)&(buf->b_p_cfu);
+ return &(buf->b_p_cfu);
case PV_OFU:
- return (char_u *)&(buf->b_p_ofu);
+ return &(buf->b_p_ofu);
case PV_EOF:
- return (char_u *)&(buf->b_p_eof);
+ return &(buf->b_p_eof);
case PV_EOL:
- return (char_u *)&(buf->b_p_eol);
+ return &(buf->b_p_eol);
case PV_FIXEOL:
- return (char_u *)&(buf->b_p_fixeol);
+ return &(buf->b_p_fixeol);
case PV_ET:
- return (char_u *)&(buf->b_p_et);
+ return &(buf->b_p_et);
case PV_FENC:
- return (char_u *)&(buf->b_p_fenc);
+ return &(buf->b_p_fenc);
case PV_FF:
- return (char_u *)&(buf->b_p_ff);
+ return &(buf->b_p_ff);
case PV_FT:
- return (char_u *)&(buf->b_p_ft);
+ return &(buf->b_p_ft);
case PV_FO:
- return (char_u *)&(buf->b_p_fo);
+ return &(buf->b_p_fo);
case PV_FLP:
- return (char_u *)&(buf->b_p_flp);
+ return &(buf->b_p_flp);
case PV_IMI:
- return (char_u *)&(buf->b_p_iminsert);
+ return &(buf->b_p_iminsert);
case PV_IMS:
- return (char_u *)&(buf->b_p_imsearch);
+ return &(buf->b_p_imsearch);
case PV_INF:
- return (char_u *)&(buf->b_p_inf);
+ return &(buf->b_p_inf);
case PV_ISK:
- return (char_u *)&(buf->b_p_isk);
+ return &(buf->b_p_isk);
case PV_INEX:
- return (char_u *)&(buf->b_p_inex);
+ return &(buf->b_p_inex);
case PV_INDE:
- return (char_u *)&(buf->b_p_inde);
+ return &(buf->b_p_inde);
case PV_INDK:
- return (char_u *)&(buf->b_p_indk);
+ return &(buf->b_p_indk);
case PV_FEX:
- return (char_u *)&(buf->b_p_fex);
+ return &(buf->b_p_fex);
case PV_LISP:
- return (char_u *)&(buf->b_p_lisp);
+ return &(buf->b_p_lisp);
case PV_LOP:
- return (char_u *)&(buf->b_p_lop);
+ return &(buf->b_p_lop);
case PV_ML:
- return (char_u *)&(buf->b_p_ml);
+ return &(buf->b_p_ml);
case PV_MPS:
- return (char_u *)&(buf->b_p_mps);
+ return &(buf->b_p_mps);
case PV_MA:
- return (char_u *)&(buf->b_p_ma);
+ return &(buf->b_p_ma);
case PV_MOD:
- return (char_u *)&(buf->b_changed);
+ return &(buf->b_changed);
case PV_NF:
- return (char_u *)&(buf->b_p_nf);
+ return &(buf->b_p_nf);
case PV_PI:
- return (char_u *)&(buf->b_p_pi);
+ return &(buf->b_p_pi);
case PV_QE:
- return (char_u *)&(buf->b_p_qe);
+ return &(buf->b_p_qe);
case PV_RO:
- return (char_u *)&(buf->b_p_ro);
+ return &(buf->b_p_ro);
case PV_SCBK:
- return (char_u *)&(buf->b_p_scbk);
+ return &(buf->b_p_scbk);
case PV_SI:
- return (char_u *)&(buf->b_p_si);
+ return &(buf->b_p_si);
case PV_STS:
- return (char_u *)&(buf->b_p_sts);
+ return &(buf->b_p_sts);
case PV_SUA:
- return (char_u *)&(buf->b_p_sua);
+ return &(buf->b_p_sua);
case PV_SWF:
- return (char_u *)&(buf->b_p_swf);
+ return &(buf->b_p_swf);
case PV_SMC:
- return (char_u *)&(buf->b_p_smc);
+ return &(buf->b_p_smc);
case PV_SYN:
- return (char_u *)&(buf->b_p_syn);
+ return &(buf->b_p_syn);
case PV_SPC:
- return (char_u *)&(win->w_s->b_p_spc);
+ return &(win->w_s->b_p_spc);
case PV_SPF:
- return (char_u *)&(win->w_s->b_p_spf);
+ return &(win->w_s->b_p_spf);
case PV_SPL:
- return (char_u *)&(win->w_s->b_p_spl);
+ return &(win->w_s->b_p_spl);
case PV_SPO:
- return (char_u *)&(win->w_s->b_p_spo);
+ return &(win->w_s->b_p_spo);
case PV_SW:
- return (char_u *)&(buf->b_p_sw);
+ return &(buf->b_p_sw);
case PV_TFU:
- return (char_u *)&(buf->b_p_tfu);
+ return &(buf->b_p_tfu);
case PV_TS:
- return (char_u *)&(buf->b_p_ts);
+ return &(buf->b_p_ts);
case PV_TW:
- return (char_u *)&(buf->b_p_tw);
+ return &(buf->b_p_tw);
case PV_UDF:
- return (char_u *)&(buf->b_p_udf);
+ return &(buf->b_p_udf);
case PV_WM:
- return (char_u *)&(buf->b_p_wm);
+ return &(buf->b_p_wm);
case PV_VSTS:
- return (char_u *)&(buf->b_p_vsts);
+ return &(buf->b_p_vsts);
case PV_VTS:
- return (char_u *)&(buf->b_p_vts);
+ return &(buf->b_p_vts);
case PV_KMAP:
- return (char_u *)&(buf->b_p_keymap);
+ return &(buf->b_p_keymap);
case PV_SCL:
- return (char_u *)&(win->w_p_scl);
+ return &(win->w_p_scl);
case PV_WINHL:
- return (char_u *)&(win->w_p_winhl);
+ return &(win->w_p_winhl);
case PV_WINBL:
- return (char_u *)&(win->w_p_winbl);
+ return &(win->w_p_winbl);
case PV_STC:
- return (char_u *)&(win->w_p_stc);
+ return &(win->w_p_stc);
default:
iemsg(_("E356: get_varp ERROR"));
}
// always return a valid pointer to avoid a crash!
- return (char_u *)&(buf->b_p_wm);
+ return &(buf->b_p_wm);
}
/// Get pointer to option variable.
-static inline char_u *get_varp(vimoption_T *p)
+static inline void *get_varp(vimoption_T *p)
{
return get_varp_from(p, curbuf, curwin);
}
@@ -4132,8 +4722,8 @@ void win_copy_options(win_T *wp_from, win_T *wp_to)
static char *copy_option_val(const char *val)
{
- if (val == empty_option) {
- return empty_option; // no need to allocate memory
+ if (val == empty_string_option) {
+ return empty_string_option; // no need to allocate memory
}
return xstrdup(val);
}
@@ -4153,7 +4743,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_ve = copy_option_val(from->wo_ve);
to->wo_ve_flags = from->wo_ve_flags;
to->wo_nuw = from->wo_nuw;
- to->wo_rl = from->wo_rl;
+ to->wo_rl = from->wo_rl;
to->wo_rlc = copy_option_val(from->wo_rlc);
to->wo_sbr = copy_option_val(from->wo_sbr);
to->wo_stl = copy_option_val(from->wo_stl);
@@ -4165,8 +4755,11 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_briopt = copy_option_val(from->wo_briopt);
to->wo_scb = from->wo_scb;
to->wo_scb_save = from->wo_scb_save;
+ to->wo_sms = from->wo_sms;
to->wo_crb = from->wo_crb;
to->wo_crb_save = from->wo_crb_save;
+ to->wo_siso = from->wo_siso;
+ to->wo_so = from->wo_so;
to->wo_spell = from->wo_spell;
to->wo_cuc = from->wo_cuc;
to->wo_cul = from->wo_cul;
@@ -4177,7 +4770,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_cocu = copy_option_val(from->wo_cocu);
to->wo_cole = from->wo_cole;
to->wo_fdc = copy_option_val(from->wo_fdc);
- to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_option;
+ to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_string_option;
to->wo_fen = from->wo_fen;
to->wo_fen_save = from->wo_fen_save;
to->wo_fdi = copy_option_val(from->wo_fdi);
@@ -4185,7 +4778,7 @@ void copy_winopt(winopt_T *from, winopt_T *to)
to->wo_fdl = from->wo_fdl;
to->wo_fdl_save = from->wo_fdl_save;
to->wo_fdm = copy_option_val(from->wo_fdm);
- to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_option;
+ to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_string_option;
to->wo_fdn = from->wo_fdn;
to->wo_fde = copy_option_val(from->wo_fde);
to->wo_fdt = copy_option_val(from->wo_fdt);
@@ -4207,7 +4800,7 @@ void check_win_options(win_T *win)
check_winopt(&win->w_allbuf_opt);
}
-/// Check for NULL pointers in a winopt_T and replace them with empty_option.
+/// Check for NULL pointers in a winopt_T and replace them with empty_string_option.
static void check_winopt(winopt_T *wop)
{
check_string_option(&wop->wo_fdc);
@@ -4266,11 +4859,12 @@ void didset_window_options(win_T *wp, bool valid_cursor)
check_colorcolumn(wp);
briopt_check(wp);
fill_culopt_flags(NULL, wp);
- set_chars_option(wp, &wp->w_p_fcs, true);
- set_chars_option(wp, &wp->w_p_lcs, true);
+ set_fillchars_option(wp, wp->w_p_fcs, true);
+ set_listchars_option(wp, wp->w_p_lcs, true);
parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl
check_blending(wp);
set_winbar_win(wp, false, valid_cursor);
+ check_signcolumn(wp);
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
@@ -4360,8 +4954,8 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_ff = xstrdup(p_ff);
break;
}
- buf->b_p_bh = empty_option;
- buf->b_p_bt = empty_option;
+ buf->b_p_bh = empty_string_option;
+ buf->b_p_bt = empty_string_option;
} else {
free_buf_options(buf, false);
}
@@ -4422,7 +5016,7 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = xstrdup(p_vsts);
COPY_OPT_SCTX(buf, BV_VSTS);
- if (p_vsts && p_vsts != empty_option) {
+ if (p_vsts && p_vsts != empty_string_option) {
(void)tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
buf->b_p_vsts_array = NULL;
@@ -4458,7 +5052,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_LOP);
// Don't copy 'filetype', it must be detected
- buf->b_p_ft = empty_option;
+ buf->b_p_ft = empty_string_option;
buf->b_p_pi = p_pi;
COPY_OPT_SCTX(buf, BV_PI);
buf->b_p_cinw = xstrdup(p_cinw);
@@ -4466,10 +5060,10 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_lisp = p_lisp;
COPY_OPT_SCTX(buf, BV_LISP);
// Don't copy 'syntax', it must be set
- buf->b_p_syn = empty_option;
+ buf->b_p_syn = empty_string_option;
buf->b_p_smc = p_smc;
COPY_OPT_SCTX(buf, BV_SMC);
- buf->b_s.b_syn_isk = empty_option;
+ buf->b_s.b_syn_isk = empty_string_option;
buf->b_s.b_p_spc = xstrdup(p_spc);
COPY_OPT_SCTX(buf, BV_SPC);
(void)compile_cap_prog(&buf->b_s);
@@ -4483,7 +5077,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_INDE);
buf->b_p_indk = xstrdup(p_indk);
COPY_OPT_SCTX(buf, BV_INDK);
- buf->b_p_fp = empty_option;
+ buf->b_p_fp = empty_string_option;
buf->b_p_fex = xstrdup(p_fex);
COPY_OPT_SCTX(buf, BV_FEX);
buf->b_p_sua = xstrdup(p_sua);
@@ -4502,30 +5096,30 @@ void buf_copy_options(buf_T *buf, int flags)
// are not copied, start using the global value
buf->b_p_ar = -1;
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
- buf->b_p_bkc = empty_option;
+ buf->b_p_bkc = empty_string_option;
buf->b_bkc_flags = 0;
- buf->b_p_gp = empty_option;
- buf->b_p_mp = empty_option;
- buf->b_p_efm = empty_option;
- buf->b_p_ep = empty_option;
- buf->b_p_kp = empty_option;
- buf->b_p_path = empty_option;
- buf->b_p_tags = empty_option;
- buf->b_p_tc = empty_option;
+ buf->b_p_gp = empty_string_option;
+ buf->b_p_mp = empty_string_option;
+ buf->b_p_efm = empty_string_option;
+ buf->b_p_ep = empty_string_option;
+ buf->b_p_kp = empty_string_option;
+ buf->b_p_path = empty_string_option;
+ buf->b_p_tags = empty_string_option;
+ buf->b_p_tc = empty_string_option;
buf->b_tc_flags = 0;
- buf->b_p_def = empty_option;
- buf->b_p_inc = empty_option;
+ buf->b_p_def = empty_string_option;
+ buf->b_p_inc = empty_string_option;
buf->b_p_inex = xstrdup(p_inex);
COPY_OPT_SCTX(buf, BV_INEX);
- buf->b_p_dict = empty_option;
- buf->b_p_tsr = empty_option;
- buf->b_p_tsrfu = empty_option;
+ buf->b_p_dict = empty_string_option;
+ buf->b_p_tsr = empty_string_option;
+ buf->b_p_tsrfu = empty_string_option;
buf->b_p_qe = xstrdup(p_qe);
COPY_OPT_SCTX(buf, BV_QE);
buf->b_p_udf = p_udf;
COPY_OPT_SCTX(buf, BV_UDF);
- buf->b_p_lw = empty_option;
- buf->b_p_menc = empty_option;
+ buf->b_p_lw = empty_string_option;
+ buf->b_p_menc = empty_string_option;
// Don't copy the options set by ex_help(), use the saved values,
// when going from a help buffer to a non-help buffer.
@@ -4533,7 +5127,7 @@ void buf_copy_options(buf_T *buf, int flags)
// or to a help buffer.
if (dont_do_help) {
buf->b_p_isk = save_p_isk;
- if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
+ if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
(void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
@@ -4546,7 +5140,7 @@ void buf_copy_options(buf_T *buf, int flags)
COPY_OPT_SCTX(buf, BV_TS);
buf->b_p_vts = xstrdup(p_vts);
COPY_OPT_SCTX(buf, BV_VTS);
- if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) {
+ if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
(void)tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
@@ -4599,8 +5193,10 @@ void set_imsearch_global(buf_T *buf)
}
static int expand_option_idx = -1;
-static char_u expand_option_name[5] = { 't', '_', NUL, NUL, NUL };
+static int expand_option_start_col = 0;
+static char expand_option_name[5] = { 't', '_', NUL, NUL, NUL };
static int expand_option_flags = 0;
+static bool expand_option_append = false;
/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL
void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
@@ -4634,10 +5230,11 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
if (strncmp(p, "no", 2) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
+ xp->xp_prefix = XP_PREFIX_NO;
p += 2;
- }
- if (strncmp(p, "inv", 3) == 0) {
+ } else if (strncmp(p, "inv", 3) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
+ xp->xp_prefix = XP_PREFIX_INV;
p += 3;
}
xp->xp_pattern = p;
@@ -4654,15 +5251,15 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
return;
}
}
- int key = get_special_key_code((char_u *)arg + 1);
+ int key = get_special_key_code(arg + 1);
if (key == 0) { // unknown name
xp->xp_context = EXPAND_NOTHING;
return;
}
nextchar = *++p;
is_term_option = true;
- expand_option_name[2] = (char_u)KEY2TERMCAP0(key);
- expand_option_name[3] = KEY2TERMCAP1(key);
+ expand_option_name[2] = (char)(uint8_t)KEY2TERMCAP0(key);
+ expand_option_name[3] = (char)(uint8_t)KEY2TERMCAP1(key);
} else {
if (p[0] == 't' && p[1] == '_') {
p += 2;
@@ -4674,8 +5271,8 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
nextchar = *++p;
is_term_option = true;
- expand_option_name[2] = (char_u)p[-2];
- expand_option_name[3] = (char_u)p[-1];
+ expand_option_name[2] = p[-2];
+ expand_option_name[3] = p[-1];
} else {
// Allow * wildcard.
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '*') {
@@ -4685,7 +5282,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
return;
}
nextchar = *p;
- opt_idx = findoption_len((const char *)arg, (size_t)(p - arg));
+ opt_idx = findoption_len(arg, (size_t)(p - arg));
if (opt_idx == -1 || options[opt_idx].var == NULL) {
xp->xp_context = EXPAND_NOTHING;
return;
@@ -4698,7 +5295,15 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
}
}
// handle "-=" and "+="
+ expand_option_append = false;
+ bool expand_option_subtract = false;
if ((nextchar == '-' || nextchar == '+' || nextchar == '^') && p[1] == '=') {
+ if (nextchar == '-') {
+ expand_option_subtract = true;
+ }
+ if (nextchar == '+' || nextchar == '^') {
+ expand_option_append = true;
+ }
p++;
nextchar = '=';
}
@@ -4707,25 +5312,53 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_context = EXPAND_UNSUCCESSFUL;
return;
}
- if (p[1] == NUL) {
+
+ // Below are for handling expanding a specific option's value after the '=' or ':'
+
+ if (is_term_option) {
+ expand_option_idx = -1;
+ } else {
+ expand_option_idx = opt_idx;
+ }
+
+ xp->xp_pattern = p + 1;
+ expand_option_start_col = (int)(p + 1 - xp->xp_line);
+
+ // Certain options currently have special case handling to reuse the
+ // expansion logic with other commands.
+ if (options[opt_idx].var == &p_syn) {
+ xp->xp_context = EXPAND_OWNSYNTAX;
+ return;
+ }
+ if (options[opt_idx].var == &p_ft) {
+ xp->xp_context = EXPAND_FILETYPE;
+ return;
+ }
+
+ // Now pick. If the option has a custom expander, use that. Otherwise, just
+ // fill with the existing option value.
+ if (expand_option_subtract) {
+ xp->xp_context = EXPAND_SETTING_SUBTRACT;
+ return;
+ } else if (expand_option_idx >= 0
+ && options[expand_option_idx].opt_expand_cb != NULL) {
+ xp->xp_context = EXPAND_STRING_SETTING;
+ } else if (*xp->xp_pattern == NUL) {
xp->xp_context = EXPAND_OLD_SETTING;
- if (is_term_option) {
- expand_option_idx = -1;
- } else {
- expand_option_idx = opt_idx;
- }
- xp->xp_pattern = p + 1;
return;
+ } else {
+ xp->xp_context = EXPAND_NOTHING;
}
- xp->xp_context = EXPAND_NOTHING;
+
if (is_term_option || (flags & P_NUM)) {
return;
}
- xp->xp_pattern = p + 1;
+ // Only string options below
+ // Options that have P_EXPAND are considered to all use file/dir expansion.
if (flags & P_EXPAND) {
- p = (char *)options[opt_idx].var;
+ p = options[opt_idx].var;
if (p == (char *)&p_bdir
|| p == (char *)&p_dir
|| p == (char *)&p_path
@@ -4739,8 +5372,6 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
} else {
xp->xp_backslash = XP_BS_ONE;
}
- } else if (p == (char *)&p_ft) {
- xp->xp_context = EXPAND_FILETYPE;
} else {
xp->xp_context = EXPAND_FILES;
// for 'tags' need three backslashes for a space
@@ -4750,29 +5381,54 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_backslash = XP_BS_ONE;
}
}
+ if (flags & P_COMMA) {
+ xp->xp_backslash |= XP_BS_COMMA;
+ }
}
- // For an option that is a list of file names, find the start of the
- // last file name.
- for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
- // count number of backslashes before ' ' or ','
- if (*p == ' ' || *p == ',') {
- char *s = p;
- while (s > xp->xp_pattern && *(s - 1) == '\\') {
- s--;
- }
- if ((*p == ' ' && (xp->xp_backslash == XP_BS_THREE && (p - s) < 3))
- || (*p == ',' && (flags & P_COMMA) && ((p - s) & 1) == 0)) {
- xp->xp_pattern = p + 1;
- break;
+ // For an option that is a list of file names, or comma/colon-separated
+ // values, split it by the delimiter and find the start of the current
+ // pattern, while accounting for backslash-escaped space/commas/colons.
+ // Triple-backslashed escaped file names (e.g. 'path') can also be
+ // delimited by space.
+ if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) {
+ for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
+ // count number of backslashes before ' ' or ','
+ if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) {
+ char *s = p;
+ while (s > xp->xp_pattern && *(s - 1) == '\\') {
+ s--;
+ }
+ if ((*p == ' ' && ((xp->xp_backslash & XP_BS_THREE) && (p - s) < 3))
+#if defined(BACKSLASH_IN_FILENAME)
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 1)
+#else
+ || (*p == ',' && (flags & P_COMMA) && (p - s) < 2)
+#endif
+ || (*p == ':' && (flags & P_COLON))) {
+ xp->xp_pattern = p + 1;
+ break;
+ }
}
}
+ }
- // for 'spellsuggest' start at "file:"
- if (options[opt_idx].var == (char_u *)&p_sps
- && strncmp(p, "file:", 5) == 0) {
- xp->xp_pattern = p + 5;
- break;
+ // An option that is a list of single-character flags should always start
+ // at the end as we don't complete words.
+ if (flags & P_FLAGLIST) {
+ xp->xp_pattern = arg + strlen(arg);
+ }
+
+ // Some options can either be using file/dir expansions, or custom value
+ // expansion depending on what the user typed. Unfortunately we have to
+ // manually handle it here to make sure we have the correct xp_context set.
+ // for 'spellsuggest' start at "file:"
+ if (options[opt_idx].var == &p_sps) {
+ if (strncmp(xp->xp_pattern, "file:", 5) == 0) {
+ xp->xp_pattern += 5;
+ return;
+ } else if (options[expand_option_idx].opt_expand_cb != NULL) {
+ xp->xp_context = EXPAND_STRING_SETTING;
}
}
}
@@ -4796,7 +5452,7 @@ static bool match_str(char *const str, regmatch_T *const regmatch, char **const
const char *const fuzzystr, fuzmatch_str_T *const fuzmatch)
{
if (!fuzzy) {
- if (vim_regexec(regmatch, str, (colnr_T)0)) {
+ if (vim_regexec(regmatch, str, 0)) {
if (!test_only) {
matches[idx] = xstrdup(str);
}
@@ -4831,10 +5487,9 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
// loop == 0: count the number of matching options
// loop == 1: copy the matching options into allocated memory
for (int loop = 0; loop <= 1; loop++) {
- int match;
regmatch->rm_ic = ic;
if (xp->xp_context != EXPAND_BOOL_SETTINGS) {
- for (match = 0; match < (int)ARRAY_SIZE(names);
+ for (int match = 0; match < (int)ARRAY_SIZE(names);
match++) {
if (match_str(names[match], regmatch, *matches,
count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) {
@@ -4865,7 +5520,7 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
count++;
}
} else if (!fuzzy && options[opt_idx].shortname != NULL
- && vim_regexec(regmatch, options[opt_idx].shortname, (colnr_T)0)) {
+ && vim_regexec(regmatch, options[opt_idx].shortname, 0)) {
// Compare against the abbreviated option name (for regular
// expression match). Fuzzy matching (previous if) already
// matches against both the expanded and abbreviated names.
@@ -4898,7 +5553,33 @@ int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numM
return OK;
}
-void ExpandOldSetting(int *numMatches, char ***matches)
+/// Escape an option value that can be used on the command-line with :set.
+/// Caller needs to free the returned string, unless NULL is returned.
+static char *escape_option_str_cmdline(char *var)
+{
+ // A backslash is required before some characters. This is the reverse of
+ // what happens in do_set().
+ char *buf = vim_strsave_escaped(var, escape_chars);
+
+#ifdef BACKSLASH_IN_FILENAME
+ // For MS-Windows et al. we don't double backslashes at the start and
+ // before a file name character.
+ // The reverse is found at stropt_copy_value().
+ for (var = buf; *var != NUL; MB_PTR_ADV(var)) {
+ if (var[0] == '\\' && var[1] == '\\'
+ && expand_option_idx >= 0
+ && (options[expand_option_idx].flags & P_EXPAND)
+ && vim_isfilec((uint8_t)var[2])
+ && (var[2] != '\\' || (var == buf && var[4] != '\\'))) {
+ STRMOVE(var, var + 1);
+ }
+ }
+#endif
+ return buf;
+}
+
+/// Expansion handler for :set= when we just want to fill in with the existing value.
+int ExpandOldSetting(int *numMatches, char ***matches)
{
char *var = NULL;
@@ -4907,7 +5588,7 @@ void ExpandOldSetting(int *numMatches, char ***matches)
// For a terminal key code expand_option_idx is < 0.
if (expand_option_idx < 0) {
- expand_option_idx = findoption((const char *)expand_option_name);
+ expand_option_idx = findoption(expand_option_name);
}
if (expand_option_idx >= 0) {
@@ -4918,26 +5599,149 @@ void ExpandOldSetting(int *numMatches, char ***matches)
var = "";
}
- // A backslash is required before some characters. This is the reverse of
- // what happens in do_set().
- char *buf = vim_strsave_escaped(var, escape_chars);
+ char *buf = escape_option_str_cmdline(var);
-#ifdef BACKSLASH_IN_FILENAME
- // For MS-Windows et al. we don't double backslashes at the start and
- // before a file name character.
- for (var = buf; *var != NUL; MB_PTR_ADV(var)) {
- if (var[0] == '\\' && var[1] == '\\'
- && expand_option_idx >= 0
- && (options[expand_option_idx].flags & P_EXPAND)
- && vim_isfilec((uint8_t)var[2])
- && (var[2] != '\\' || (var == buf && var[4] != '\\'))) {
- STRMOVE(var, var + 1);
+ (*matches)[0] = buf;
+ *numMatches = 1;
+ return OK;
+}
+
+/// Expansion handler for :set=/:set+= when the option has a custom expansion handler.
+int ExpandStringSetting(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
+{
+ if (expand_option_idx < 0
+ || options[expand_option_idx].opt_expand_cb == NULL) {
+ // Not supposed to reach this. This function is only for options with
+ // custom expansion callbacks.
+ return FAIL;
+ }
+
+ optexpand_T args = {
+ .oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags),
+ .oe_append = expand_option_append,
+ .oe_regmatch = regmatch,
+ .oe_xp = xp,
+ .oe_set_arg = xp->xp_line + expand_option_start_col,
+ };
+ args.oe_include_orig_val = !expand_option_append && (*args.oe_set_arg == NUL);
+
+ // Retrieve the existing value, but escape it as a reverse of setting it.
+ // We technically only need to do this when oe_append or
+ // oe_include_orig_val is true.
+ option_value2string(&options[expand_option_idx], expand_option_flags);
+ char *var = NameBuff;
+ char *buf = escape_option_str_cmdline(var);
+ args.oe_opt_value = buf;
+
+ int num_ret = options[expand_option_idx].opt_expand_cb(&args, numMatches, matches);
+
+ xfree(buf);
+ return num_ret;
+}
+
+/// Expansion handler for :set-=
+int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
+{
+ if (expand_option_idx < 0) {
+ // term option
+ return ExpandOldSetting(numMatches, matches);
+ }
+
+ char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx,
+ expand_option_flags,
+ curbuf, curwin);
+
+ uint32_t option_flags = options[expand_option_idx].flags;
+
+ if (option_flags & P_NUM) {
+ return ExpandOldSetting(numMatches, matches);
+ } else if (option_flags & P_COMMA) {
+ // Split the option by comma, then present each option to the user if
+ // it matches the pattern.
+ // This condition needs to go first, because 'whichwrap' has both
+ // P_COMMA and P_FLAGLIST.
+
+ if (*option_val == NUL) {
+ return FAIL;
+ }
+
+ // Make a copy as we need to inject null characters destructively.
+ char *option_copy = xstrdup(option_val);
+ char *next_val = option_copy;
+
+ garray_T ga;
+ ga_init(&ga, sizeof(char *), 10);
+
+ do {
+ char *item = next_val;
+ char *comma = vim_strchr(next_val, ',');
+ while (comma != NULL && comma != next_val && *(comma - 1) == '\\') {
+ // "\," is interpreted as a literal comma rather than option
+ // separator when reading options in copy_option_part(). Skip
+ // it.
+ comma = vim_strchr(comma + 1, ',');
+ }
+ if (comma != NULL) {
+ *comma = NUL; // null-terminate this value, required by later functions
+ next_val = comma + 1;
+ } else {
+ next_val = NULL;
+ }
+
+ if (*item == NUL) {
+ // empty value, don't add to list
+ continue;
+ }
+
+ if (!vim_regexec(regmatch, item, 0)) {
+ continue;
+ }
+
+ char *buf = escape_option_str_cmdline(item);
+ GA_APPEND(char *, &ga, buf);
+ } while (next_val != NULL);
+
+ xfree(option_copy);
+
+ *matches = ga.ga_data;
+ *numMatches = ga.ga_len;
+ return OK;
+ } else if (option_flags & P_FLAGLIST) {
+ // Only present the flags that are set on the option as the other flags
+ // are not meaningful to do set-= on.
+
+ if (*xp->xp_pattern != NUL) {
+ // Don't suggest anything if cmdline is non-empty. Vim's set-=
+ // behavior requires consecutive strings and it's usually
+ // unintuitive to users if they try to subtract multiple flags at
+ // once.
+ return FAIL;
+ }
+
+ size_t num_flags = strlen(option_val);
+ if (num_flags == 0) {
+ return FAIL;
+ }
+
+ *matches = xmalloc(sizeof(char *) * (num_flags + 1));
+
+ int count = 0;
+
+ (*matches)[count++] = xstrdup(option_val);
+
+ if (num_flags > 1) {
+ // If more than one flags, split the flags up and expose each
+ // character as individual choice.
+ for (char *flag = option_val; *flag != NUL; flag++) {
+ (*matches)[count++] = xmemdupz(flag, 1);
+ }
}
+
+ *numMatches = count;
+ return OK;
}
-#endif
- *matches[0] = buf;
- *numMatches = 1;
+ return ExpandOldSetting(numMatches, matches);
}
/// Get the value for the numeric or string option///opp in a nice format into
@@ -4946,30 +5750,27 @@ void ExpandOldSetting(int *numMatches, char ***matches)
/// @param opt_flags OPT_GLOBAL and/or OPT_LOCAL
static void option_value2string(vimoption_T *opp, int scope)
{
- char *varp = get_varp_scope(opp, scope);
+ void *varp = get_varp_scope(opp, scope);
if (opp->flags & P_NUM) {
- long wc = 0;
+ OptInt wc = 0;
- if (wc_use_keyname((char_u *)varp, &wc)) {
- xstrlcpy(NameBuff, (char *)get_special_key_name((int)wc, 0), sizeof(NameBuff));
+ if (wc_use_keyname(varp, &wc)) {
+ xstrlcpy(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff));
} else if (wc != 0) {
xstrlcpy(NameBuff, transchar((int)wc), sizeof(NameBuff));
} else {
snprintf(NameBuff,
sizeof(NameBuff),
"%" PRId64,
- (int64_t)(*(long *)varp));
+ (int64_t)(*(OptInt *)varp));
}
} else { // P_STRING
varp = *(char **)(varp);
if (varp == NULL) { // Just in case.
NameBuff[0] = NUL;
} else if (opp->flags & P_EXPAND) {
- home_replace(NULL, varp, (char *)NameBuff, MAXPATHL, false);
- // Translate 'pastetoggle' into special key names.
- } else if ((char **)opp->var == &p_pt) {
- str2specialbuf((const char *)p_pt, NameBuff, MAXPATHL);
+ home_replace(NULL, varp, NameBuff, MAXPATHL, false);
} else {
xstrlcpy(NameBuff, varp, MAXPATHL);
}
@@ -4979,10 +5780,10 @@ static void option_value2string(vimoption_T *opp, int scope)
/// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be
/// printed as a keyname.
/// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'.
-static int wc_use_keyname(const char_u *varp, long *wcp)
+static int wc_use_keyname(const void *varp, OptInt *wcp)
{
- if (((long *)varp == &p_wc) || ((long *)varp == &p_wcm)) {
- *wcp = *(long *)varp;
+ if (((OptInt *)varp == &p_wc) || ((OptInt *)varp == &p_wcm)) {
+ *wcp = *(OptInt *)varp;
if (IS_SPECIAL(*wcp) || find_special_key_in_table((int)(*wcp)) >= 0) {
return true;
}
@@ -5000,133 +5801,6 @@ bool shortmess(int x)
&& vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL)));
}
-/// paste_option_changed() - Called after p_paste was set or reset.
-static void paste_option_changed(void)
-{
- static int old_p_paste = false;
- static int save_sm = 0;
- static int save_sta = 0;
- static int save_ru = 0;
- static int save_ri = 0;
- static int save_hkmap = 0;
-
- if (p_paste) {
- // Paste switched from off to on.
- // Save the current values, so they can be restored later.
- if (!old_p_paste) {
- // save options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw_nopaste = buf->b_p_tw;
- buf->b_p_wm_nopaste = buf->b_p_wm;
- buf->b_p_sts_nopaste = buf->b_p_sts;
- buf->b_p_ai_nopaste = buf->b_p_ai;
- buf->b_p_et_nopaste = buf->b_p_et;
- if (buf->b_p_vsts_nopaste) {
- xfree(buf->b_p_vsts_nopaste);
- }
- buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_option
- ? xstrdup(buf->b_p_vsts)
- : NULL;
- }
-
- // save global options
- save_sm = p_sm;
- save_sta = p_sta;
- save_ru = p_ru;
- save_ri = p_ri;
- save_hkmap = p_hkmap;
- // save global values for local buffer options
- p_ai_nopaste = p_ai;
- p_et_nopaste = p_et;
- p_sts_nopaste = p_sts;
- p_tw_nopaste = p_tw;
- p_wm_nopaste = p_wm;
- if (p_vsts_nopaste) {
- xfree(p_vsts_nopaste);
- }
- p_vsts_nopaste = p_vsts && p_vsts != empty_option ? xstrdup(p_vsts) : NULL;
- }
-
- // Always set the option values, also when 'paste' is set when it is
- // already on.
- // set options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw = 0; // textwidth is 0
- buf->b_p_wm = 0; // wrapmargin is 0
- buf->b_p_sts = 0; // softtabstop is 0
- buf->b_p_ai = 0; // no auto-indent
- buf->b_p_et = 0; // no expandtab
- if (buf->b_p_vsts) {
- free_string_option(buf->b_p_vsts);
- }
- buf->b_p_vsts = empty_option;
- XFREE_CLEAR(buf->b_p_vsts_array);
- }
-
- // set global options
- p_sm = 0; // no showmatch
- p_sta = 0; // no smarttab
- if (p_ru) {
- status_redraw_all(); // redraw to remove the ruler
- }
- p_ru = 0; // no ruler
- p_ri = 0; // no reverse insert
- p_hkmap = 0; // no Hebrew keyboard
- // set global values for local buffer options
- p_tw = 0;
- p_wm = 0;
- p_sts = 0;
- p_ai = 0;
- if (p_vsts) {
- free_string_option(p_vsts);
- }
- p_vsts = empty_option;
- } else if (old_p_paste) {
- // Paste switched from on to off: Restore saved values.
-
- // restore options for each buffer
- FOR_ALL_BUFFERS(buf) {
- buf->b_p_tw = buf->b_p_tw_nopaste;
- buf->b_p_wm = buf->b_p_wm_nopaste;
- buf->b_p_sts = buf->b_p_sts_nopaste;
- buf->b_p_ai = buf->b_p_ai_nopaste;
- buf->b_p_et = buf->b_p_et_nopaste;
- if (buf->b_p_vsts) {
- free_string_option(buf->b_p_vsts);
- }
- buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_option;
- 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 = NULL;
- }
- }
-
- // restore global options
- p_sm = save_sm;
- p_sta = save_sta;
- if (p_ru != save_ru) {
- status_redraw_all(); // redraw to draw the ruler
- }
- p_ru = save_ru;
- p_ri = save_ri;
- p_hkmap = save_hkmap;
- // set global values for local buffer options
- p_ai = p_ai_nopaste;
- p_et = p_et_nopaste;
- p_sts = p_sts_nopaste;
- p_tw = p_tw_nopaste;
- p_wm = p_wm_nopaste;
- if (p_vsts) {
- free_string_option(p_vsts);
- }
- p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_option;
- }
-
- old_p_paste = p_paste;
-}
-
/// vimrc_found() - Called when a vimrc or "VIMINIT" has been found.
///
/// Set the values for options that didn't get set yet to the defaults.
@@ -5179,25 +5853,11 @@ void reset_option_was_set(const char *name)
options[idx].flags &= ~P_WAS_SET;
}
-/// fill_breakat_flags() -- called when 'breakat' changes value.
-void fill_breakat_flags(void)
-{
- for (int i = 0; i < 256; i++) {
- breakat_flags[i] = false;
- }
-
- if (p_breakat != NULL) {
- for (char_u *p = (char_u *)p_breakat; *p; p++) {
- breakat_flags[*p] = true;
- }
- }
-}
-
/// fill_culopt_flags() -- called when 'culopt' changes value
int fill_culopt_flags(char *val, win_T *wp)
{
char *p;
- char_u culopt_flags_new = 0;
+ uint8_t culopt_flags_new = 0;
if (val == NULL) {
p = wp->w_p_culopt;
@@ -5205,6 +5865,7 @@ int fill_culopt_flags(char *val, win_T *wp)
p = val;
}
while (*p != NUL) {
+ // Note: Keep this in sync with p_culopt_values.
if (strncmp(p, "line", 4) == 0) {
p += 4;
culopt_flags_new |= CULOPT_LINE;
@@ -5265,7 +5926,7 @@ int option_set_callback_func(char *optval, Callback *optcb)
|| (strncmp(optval, "function(", 9) == 0)
|| (strncmp(optval, "funcref(", 8) == 0)) {
// Lambda expression or a funcref
- tv = eval_expr(optval);
+ tv = eval_expr(optval, NULL);
if (tv == NULL) {
return FAIL;
}
@@ -5288,6 +5949,20 @@ int option_set_callback_func(char *optval, Callback *optcb)
return OK;
}
+static void didset_options_sctx(int opt_flags, char **buf)
+{
+ for (int i = 0;; i++) {
+ if (buf[i] == NULL) {
+ break;
+ }
+
+ int idx = findoption(buf[i]);
+ if (idx >= 0) {
+ set_option_sctx_idx(idx, opt_flags, current_sctx);
+ }
+ }
+}
+
/// Check if backspacing over something is allowed.
/// @param what BS_INDENT, BS_EOL, BS_START, or BS_NOSTOP
bool can_bs(int what)
@@ -5295,23 +5970,20 @@ bool can_bs(int what)
if (what == BS_START && bt_prompt(curbuf)) {
return false;
}
- switch (*p_bs) {
- case '3':
- return true;
- case '2':
+
+ // support for number values was removed but we keep '2' since it is used in
+ // legacy tests
+ if (*p_bs == '2') {
return what != BS_NOSTOP;
- case '1':
- return what != BS_START;
- case '0':
- return false;
}
+
return vim_strchr(p_bs, what) != NULL;
}
/// Get the local or global value of 'backupcopy'.
///
/// @param buf The buffer.
-unsigned int get_bkc_value(buf_T *buf)
+unsigned get_bkc_value(buf_T *buf)
{
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
@@ -5328,7 +6000,7 @@ char *get_flp_value(buf_T *buf)
}
/// Get the local or global value of the 'virtualedit' flags.
-unsigned int get_ve_flags(void)
+unsigned get_ve_flags(void)
{
return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU);
}
@@ -5344,7 +6016,7 @@ char *get_showbreak_value(win_T *const win)
return p_sbr;
}
if (strcmp(win->w_p_sbr, "NONE") == 0) {
- return empty_option;
+ return empty_string_option;
}
return win->w_p_sbr;
}
@@ -5502,53 +6174,12 @@ bool fish_like_shell(void)
/// buffer signs and on user configuration.
int win_signcol_count(win_T *wp)
{
- return win_signcol_configured(wp, NULL);
-}
-
-/// Return the number of requested sign columns, based on user / configuration.
-int win_signcol_configured(win_T *wp, int *is_fixed)
-{
- const char *scl = (const char *)wp->w_p_scl;
-
- if (is_fixed) {
- *is_fixed = 1;
- }
-
- // Note: It checks "no" or "number" in 'signcolumn' option
- if (*scl == 'n'
- && (*(scl + 1) == 'o' || (*(scl + 1) == 'u'
- && (wp->w_p_nu || wp->w_p_rnu)))) {
+ if (wp->w_minscwidth <= SCL_NO) {
return 0;
}
- // yes or yes
- if (!strncmp(scl, "yes:", 4)) {
- // Fixed amount of columns
- return scl[4] - '0';
- }
- if (*scl == 'y') {
- return 1;
- }
-
- if (is_fixed) {
- // auto or auto:<NUM>
- *is_fixed = 0;
- }
-
- int minimum = 0, maximum = 1;
-
- if (!strncmp(scl, "auto:", 5)) {
- // Variable depending on a configuration
- maximum = scl[5] - '0';
- // auto:<NUM>-<NUM>
- if (strlen(scl) == 8 && *(scl + 6) == '-') {
- minimum = maximum;
- maximum = scl[7] - '0';
- }
- }
-
- int needed_signcols = buf_signcols(wp->w_buffer, maximum);
- int ret = MAX(minimum, MIN(maximum, needed_signcols));
+ int needed_signcols = buf_signcols(wp->w_buffer, wp->w_maxscwidth);
+ int ret = MAX(wp->w_minscwidth, MIN(wp->w_maxscwidth, needed_signcols));
assert(ret <= SIGN_SHOW_MAX);
return ret;
}
@@ -5564,7 +6195,7 @@ dict_T *get_winbuf_options(const int bufopt)
if ((bufopt && (opt->indir & PV_BUF))
|| (!bufopt && (opt->indir & PV_WIN))) {
- char_u *varp = get_varp(opt);
+ void *varp = get_varp(opt);
if (varp != NULL) {
if (opt->flags & P_STRING) {
@@ -5572,7 +6203,7 @@ dict_T *get_winbuf_options(const int bufopt)
*(const char **)varp);
} else if (opt->flags & P_NUM) {
tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname),
- *(long *)varp);
+ *(OptInt *)varp);
} else {
tv_dict_add_nr(d, opt->fullname, strlen(opt->fullname), *(int *)varp);
}
@@ -5585,43 +6216,43 @@ dict_T *get_winbuf_options(const int bufopt)
/// Return the effective 'scrolloff' value for the current window, using the
/// global value when appropriate.
-long get_scrolloff_value(win_T *wp)
+int get_scrolloff_value(win_T *wp)
{
// Disallow scrolloff in terminal-mode. #11915
if (State & MODE_TERMINAL) {
return 0;
}
- return wp->w_p_so < 0 ? p_so : wp->w_p_so;
+ return (int)(wp->w_p_so < 0 ? p_so : wp->w_p_so);
}
/// Return the effective 'sidescrolloff' value for the current window, using the
/// global value when appropriate.
-long get_sidescrolloff_value(win_T *wp)
+int get_sidescrolloff_value(win_T *wp)
{
- return wp->w_p_siso < 0 ? p_siso : wp->w_p_siso;
+ return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso);
}
-Dictionary get_vimoption(String name, Error *err)
+Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Error *err)
{
- int opt_idx = findoption_len((const char *)name.data, name.size);
- if (opt_idx < 0) {
- api_set_error(err, kErrorTypeValidation, "no such option: '%s'", name.data);
+ int opt_idx = findoption_len(name.data, name.size);
+ VALIDATE_S(opt_idx >= 0, "option (not found)", name.data, {
return (Dictionary)ARRAY_DICT_INIT;
- }
- return vimoption2dict(&options[opt_idx]);
+ });
+
+ return vimoption2dict(&options[opt_idx], scope, buf, win);
}
Dictionary get_all_vimoptions(void)
{
Dictionary retval = ARRAY_DICT_INIT;
for (size_t i = 0; options[i].fullname != NULL; i++) {
- Dictionary opt_dict = vimoption2dict(&options[i]);
+ Dictionary opt_dict = vimoption2dict(&options[i], OPT_GLOBAL, curbuf, curwin);
PUT(retval, options[i].fullname, DICTIONARY_OBJ(opt_dict));
}
return retval;
}
-static Dictionary vimoption2dict(vimoption_T *opt)
+static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win)
{
Dictionary dict = ARRAY_DICT_INIT;
@@ -5640,35 +6271,51 @@ static Dictionary vimoption2dict(vimoption_T *opt)
PUT(dict, "scope", CSTR_TO_OBJ(scope));
// welcome to the jungle
- PUT(dict, "global_local", BOOL(opt->indir & PV_BOTH));
- PUT(dict, "commalist", BOOL(opt->flags & P_COMMA));
- PUT(dict, "flaglist", BOOL(opt->flags & P_FLAGLIST));
+ PUT(dict, "global_local", BOOLEAN_OBJ(opt->indir & PV_BOTH));
+ PUT(dict, "commalist", BOOLEAN_OBJ(opt->flags & P_COMMA));
+ PUT(dict, "flaglist", BOOLEAN_OBJ(opt->flags & P_FLAGLIST));
+
+ PUT(dict, "was_set", BOOLEAN_OBJ(opt->flags & P_WAS_SET));
- PUT(dict, "was_set", BOOL(opt->flags & P_WAS_SET));
+ LastSet last_set = { .channel_id = 0 };
+ if (req_scope == OPT_GLOBAL) {
+ last_set = opt->last_set;
+ } else {
+ // Scope is either OPT_LOCAL or a fallback mode was requested.
+ if (opt->indir & PV_BUF) {
+ last_set = buf->b_p_script_ctx[opt->indir & PV_MASK];
+ }
+ if (opt->indir & PV_WIN) {
+ last_set = win->w_p_script_ctx[opt->indir & PV_MASK];
+ }
+ if (req_scope != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) {
+ last_set = opt->last_set;
+ }
+ }
- PUT(dict, "last_set_sid", INTEGER_OBJ(opt->last_set.script_ctx.sc_sid));
- PUT(dict, "last_set_linenr", INTEGER_OBJ(opt->last_set.script_ctx.sc_lnum));
- PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)opt->last_set.channel_id));
+ PUT(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid));
+ PUT(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum));
+ PUT(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id));
const char *type;
Object def;
// TODO(bfredl): do you even nocp?
- char_u *def_val = (char_u *)opt->def_val;
+ char *def_val = opt->def_val;
if (opt->flags & P_STRING) {
type = "string";
- def = CSTR_TO_OBJ(def_val ? (char *)def_val : "");
+ def = CSTR_TO_OBJ(def_val ? def_val : "");
} else if (opt->flags & P_NUM) {
type = "number";
def = INTEGER_OBJ((Integer)(intptr_t)def_val);
} else if (opt->flags & P_BOOL) {
type = "boolean";
- def = BOOL((intptr_t)def_val);
+ def = BOOLEAN_OBJ((intptr_t)def_val);
} else {
type = ""; def = NIL;
}
PUT(dict, "type", CSTR_TO_OBJ(type));
PUT(dict, "default", def);
- PUT(dict, "allows_duplicates", BOOL(!(opt->flags & P_NODUP)));
+ PUT(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & P_NODUP)));
return dict;
}
diff --git a/src/nvim/option.h b/src/nvim/option.h
index 636257bfe8..ebf8e0417d 100644
--- a/src/nvim/option.h
+++ b/src/nvim/option.h
@@ -1,27 +1,113 @@
-#ifndef NVIM_OPTION_H
-#define NVIM_OPTION_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
+#include <stdint.h>
+#include <stdio.h> // IWYU pragma: keep
-/// Returned by get_option_value().
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/api/private/helpers.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// The options that are local to a window or buffer have "indir" set to one of
+/// these values. Special values:
+/// PV_NONE: global option.
+/// PV_WIN is added: window-local option
+/// PV_BUF is added: buffer-local option
+/// PV_BOTH is added: global option which also has a local value.
+enum {
+ PV_BOTH = 0x1000,
+ PV_WIN = 0x2000,
+ PV_BUF = 0x4000,
+ PV_MASK = 0x0fff,
+};
+#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x))
+#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x))
+#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x))
+
+/// WV_ and BV_ values get typecasted to this for the "indir" field
typedef enum {
- gov_unknown,
- gov_bool,
- gov_number,
- gov_string,
- gov_hidden_bool,
- gov_hidden_number,
- gov_hidden_string,
-} getoption_T;
-
-// flags for buf_copy_options()
-#define BCO_ENTER 1 // going to enter the buffer
-#define BCO_ALWAYS 2 // always copy the options
-#define BCO_NOHELP 4 // don't touch the help related options
-
-#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn'
+ PV_NONE = 0,
+ PV_MAXVAL = 0xffff, ///< to avoid warnings for value out of range
+} idopt_T;
+
+// Options local to a window have a value local to a buffer and global to all
+// buffers. Indicate this by setting "var" to VAR_WIN.
+#define VAR_WIN ((char *)-1)
+
+typedef struct vimoption {
+ char *fullname; ///< full option name
+ char *shortname; ///< permissible abbreviation
+ uint32_t flags; ///< see above
+ void *var; ///< global option: pointer to variable;
+ ///< window-local option: VAR_WIN;
+ ///< buffer-local option: global value
+ idopt_T indir; ///< global option: PV_NONE;
+ ///< local option: indirect option index
+ ///< callback function to invoke after an option is modified to validate and
+ ///< apply the new value.
+ bool immutable; ///< option value cannot be changed from the default value.
+
+ /// callback function to invoke after an option is modified to validate and
+ /// apply the new value.
+ opt_did_set_cb_T opt_did_set_cb;
+
+ /// callback function to invoke when expanding possible values on the
+ /// cmdline. Only useful for string options.
+ opt_expand_cb_T opt_expand_cb;
+
+ // TODO(famiu): Use OptVal for def_val.
+ void *def_val; ///< default values for variable (neovim!!)
+ LastSet last_set; ///< script in which the option was last set
+} vimoption_T;
+
+/// flags for buf_copy_options()
+enum {
+ BCO_ENTER = 1, ///< going to enter the buffer
+ BCO_ALWAYS = 2, ///< always copy the options
+ BCO_NOHELP = 4, ///< don't touch the help related options
+};
+
+/// Flags for option-setting functions
+///
+/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
+/// values, get local value.
+typedef enum {
+ // TODO(famiu): See if `OPT_FREE` is really necessary and remove it if not.
+ OPT_FREE = 0x01, ///< Free old value if it was allocated.
+ OPT_GLOBAL = 0x02, ///< Use global value.
+ OPT_LOCAL = 0x04, ///< Use local value.
+ OPT_MODELINE = 0x08, ///< Option in modeline.
+ OPT_WINONLY = 0x10, ///< Only set window-local options.
+ OPT_NOWIN = 0x20, ///< Don’t set window-local options.
+ OPT_ONECOLUMN = 0x40, ///< list options one per line
+ OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
+ OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
+} OptionFlags;
+
+/// Return value from get_option_value_strict
+enum {
+ SOPT_BOOL = 0x01, ///< Boolean option
+ SOPT_NUM = 0x02, ///< Number option
+ SOPT_STRING = 0x04, ///< String option
+ SOPT_GLOBAL = 0x08, ///< Option has global value
+ SOPT_WIN = 0x10, ///< Option has window-local value
+ SOPT_BUF = 0x20, ///< Option has buffer-local value
+};
+
+// OptVal helper macros.
+#define NIL_OPTVAL ((OptVal) { .type = kOptValTypeNil })
+#define BOOLEAN_OPTVAL(b) ((OptVal) { .type = kOptValTypeBoolean, .data.boolean = b })
+#define NUMBER_OPTVAL(n) ((OptVal) { .type = kOptValTypeNumber, .data.number = n })
+#define STRING_OPTVAL(s) ((OptVal) { .type = kOptValTypeString, .data.string = s })
+
+#define CSTR_AS_OPTVAL(s) STRING_OPTVAL(cstr_as_string(s))
+#define CSTR_TO_OPTVAL(s) STRING_OPTVAL(cstr_to_string(s))
+#define STATIC_CSTR_AS_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_AS_STRING(s))
+#define STATIC_CSTR_TO_OPTVAL(s) STRING_OPTVAL(STATIC_CSTR_TO_STRING(s))
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "option.h.generated.h"
#endif
-#endif // NVIM_OPTION_H
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index d190fc5999..b2e8081a08 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -1,1024 +1,123 @@
-#ifndef NVIM_OPTION_DEFS_H
-#define NVIM_OPTION_DEFS_H
+#pragma once
-#include "nvim/eval/typval.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include <stdbool.h>
+#include <stddef.h>
-// option_defs.h: definition of global variables for settable options
+#include "nvim/api/private/defs.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/regexp_defs.h"
+#include "nvim/types_defs.h"
-// Flags
-#define P_BOOL 0x01U ///< the option is boolean
-#define P_NUM 0x02U ///< the option is numeric
-#define P_STRING 0x04U ///< the option is a string
-#define P_ALLOCED 0x08U ///< the string option is in allocated memory,
- ///< must use free_string_option() when
- ///< assigning new value. Not set if default is
- ///< the same.
-#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can
- ///< never be used for local or hidden options
-#define P_NODEFAULT 0x40U ///< don't set to default value
-#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must
- ///< use free() when assigning new value
-#define P_WAS_SET 0x100U ///< option has been set/reset
-#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output
-
-// when option changed, what to display:
-#define P_UI_OPTION 0x400U ///< send option to remote UI
-#define P_RTABL 0x800U ///< redraw tabline
-#define P_RSTAT 0x1000U ///< redraw status lines
-#define P_RWIN 0x2000U ///< redraw current window and recompute text
-#define P_RBUF 0x4000U ///< redraw current buffer and recompute text
-#define P_RALL 0x6000U ///< redraw all windows
-#define P_RCLR 0x7000U ///< clear and redraw all
-
-#define P_COMMA 0x8000U ///< comma separated list
-#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive
- ///< commas
-#define P_NODUP 0x20000U ///< don't allow duplicate strings
-#define P_FLAGLIST 0x40000U ///< list of single-char flags
-
-#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode
-#define P_GETTEXT 0x100000U ///< expand default value with _()
-#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc
-#define P_NFNAME 0x400000U ///< only normal file name chars allowed
-#define P_INSECURE 0x800000U ///< option was set from a modeline
-#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option
- ///< has side effects)
-#define P_NO_ML 0x2000000U ///< not allowed in modeline
-#define P_CURSWANT 0x4000000U ///< update curswant required; not needed
- ///< when there is a redraw flag
-#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed
-#define P_RWINONLY 0x10000000U ///< only redraw current window
-#define P_MLE 0x20000000U ///< under control of 'modelineexpr'
-#define P_FUNC 0x40000000U ///< accept a function reference or a lambda
-
-#define P_NO_DEF_EXP 0x80000000U ///< Do not expand default value.
-
-/// Flags for option-setting functions
-///
-/// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global
-/// values, get local value.
+/// Option value type
typedef enum {
- OPT_FREE = 0x01, ///< Free old value if it was allocated.
- OPT_GLOBAL = 0x02, ///< Use global value.
- OPT_LOCAL = 0x04, ///< Use local value.
- OPT_MODELINE = 0x08, ///< Option in modeline.
- OPT_WINONLY = 0x10, ///< Only set window-local options.
- OPT_NOWIN = 0x20, ///< Don’t set window-local options.
- OPT_ONECOLUMN = 0x40, ///< list options one per line
- OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option
- OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions'
- OPT_CLEAR = 0x200, ///< Clear local value of an option.
-} OptionFlags;
-
-// Return value from get_option_value_strict
-#define SOPT_BOOL 0x01 // Boolean option
-#define SOPT_NUM 0x02 // Number option
-#define SOPT_STRING 0x04 // String option
-#define SOPT_GLOBAL 0x08 // Option has global value
-#define SOPT_WIN 0x10 // Option has window-local value
-#define SOPT_BUF 0x20 // Option has buffer-local value
-#define SOPT_UNSET 0x40 // Option does not have local value set
-
-// Option types for various functions in option.c
-#define SREQ_GLOBAL 0 // Request global option value
-#define SREQ_WIN 1 // Request window-local option value
-#define SREQ_BUF 2 // Request buffer-local option value
-
-#define HIGHLIGHT_INIT \
- "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
- "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
- "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \
- "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \
- "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \
- "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \
- "X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \
- "q:QuickFixLine,0:Whitespace,I:NormalNC"
-
-// Default values for 'errorformat'.
-// The "%f|%l| %m" one is used for when the contents of the quickfix window is
-// written to a file.
-#ifdef MSWIN
-# define DFLT_EFM \
- "%f(%l) \\=: %t%*\\D%n: %m,%*[^\"]\"%f\"%*\\D%l: %m,%f(%l) \\=: %m,%*[^ ] %f %l: %m,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,%f|%l| %m"
-#else
-# define DFLT_EFM \
- "%*[^\"]\"%f\"%*\\D%l: %m,\"%f\"%*\\D%l: %m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,\"%f\"\\, line %l%*\\D%c%*[^ ] %m,%D%*\\a[%*\\d]: Entering directory %*[`']%f',%X%*\\a[%*\\d]: Leaving directory %*[`']%f',%D%*\\a: Entering directory %*[`']%f',%X%*\\a: Leaving directory %*[`']%f',%DMaking %*\\a in %f,%f|%l| %m"
-#endif
-
-#define DFLT_GREPFORMAT "%f:%l:%m,%f:%l%m,%f %l%m"
-
-// default values for b_p_ff 'fileformat' and p_ffs 'fileformats'
-#define FF_DOS "dos"
-#define FF_MAC "mac"
-#define FF_UNIX "unix"
-
-#ifdef USE_CRNL
-# define DFLT_FF "dos"
-# define DFLT_FFS_VIM "dos,unix"
-# define DFLT_FFS_VI "dos,unix" // also autodetect in compatible mode
-#else
-# define DFLT_FF "unix"
-# define DFLT_FFS_VIM "unix,dos"
-# define DFLT_FFS_VI ""
-#endif
-
-// Possible values for 'encoding'
-#define ENC_UCSBOM "ucs-bom" // check for BOM at start of file
-
-// default value for 'encoding'
-#define ENC_DFLT "utf-8"
-
-// end-of-line style
-#define EOL_UNKNOWN (-1) // not defined yet
-#define EOL_UNIX 0 // NL
-#define EOL_DOS 1 // CR NL
-#define EOL_MAC 2 // CR
-
-// Formatting options for p_fo 'formatoptions'
-#define FO_WRAP 't'
-#define FO_WRAP_COMS 'c'
-#define FO_RET_COMS 'r'
-#define FO_OPEN_COMS 'o'
-#define FO_NO_OPEN_COMS '/'
-#define FO_Q_COMS 'q'
-#define FO_Q_NUMBER 'n'
-#define FO_Q_SECOND '2'
-#define FO_INS_VI 'v'
-#define FO_INS_LONG 'l'
-#define FO_INS_BLANK 'b'
-#define FO_MBYTE_BREAK 'm' // break before/after multi-byte char
-#define FO_MBYTE_JOIN 'M' // no space before/after multi-byte char
-#define FO_MBYTE_JOIN2 'B' // no space between multi-byte chars
-#define FO_ONE_LETTER '1'
-#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
-#define FO_AUTO 'a' // automatic formatting
-#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
-#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
-#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
-
-#define DFLT_FO_VI "vt"
-#define DFLT_FO_VIM "tcqj"
-#define FO_ALL "tcro/q2vlb1mMBn,aw]jp" // for do_set()
-
-// characters for the p_cpo option:
-#define CPO_ALTREAD 'a' // ":read" sets alternate file name
-#define CPO_ALTWRITE 'A' // ":write" sets alternate file name
-#define CPO_BAR 'b' // "\|" ends a mapping
-#define CPO_BSLASH 'B' // backslash in mapping is not special
-#define CPO_SEARCH 'c'
-#define CPO_CONCAT 'C' // Don't concatenate sourced lines
-#define CPO_DOTTAG 'd' // "./tags" in 'tags' is in current dir
-#define CPO_DIGRAPH 'D' // No digraph after "r", "f", etc.
-#define CPO_EXECBUF 'e'
-#define CPO_EMPTYREGION 'E' // operating on empty region is an error
-#define CPO_FNAMER 'f' // set file name for ":r file"
-#define CPO_FNAMEW 'F' // set file name for ":w file"
-#define CPO_INTMOD 'i' // interrupt a read makes buffer modified
-#define CPO_INDENT 'I' // remove auto-indent more often
-#define CPO_ENDOFSENT 'J' // need two spaces to detect end of sentence
-#define CPO_KOFFSET 'K' // don't wait for key code in mappings
-#define CPO_LITERAL 'l' // take char after backslash in [] literal
-#define CPO_LISTWM 'L' // 'list' changes wrapmargin
-#define CPO_SHOWMATCH 'm'
-#define CPO_MATCHBSL 'M' // "%" ignores use of backslashes
-#define CPO_NUMCOL 'n' // 'number' column also used for text
-#define CPO_LINEOFF 'o'
-#define CPO_OVERNEW 'O' // silently overwrite new file
-#define CPO_LISP 'p' // 'lisp' indenting
-#define CPO_FNAMEAPP 'P' // set file name for ":w >>file"
-#define CPO_JOINCOL 'q' // with "3J" use column after first join
-#define CPO_REDO 'r'
-#define CPO_REMMARK 'R' // remove marks when filtering
-#define CPO_BUFOPT 's'
-#define CPO_BUFOPTGLOB 'S'
-#define CPO_TAGPAT 't' // tag pattern is used for "n"
-#define CPO_UNDO 'u' // "u" undoes itself
-#define CPO_BACKSPACE 'v' // "v" keep deleted text
-#define CPO_FWRITE 'W' // "w!" doesn't overwrite readonly files
-#define CPO_ESC 'x'
-#define CPO_REPLCNT 'X' // "R" with a count only deletes chars once
-#define CPO_YANK 'y'
-#define CPO_KEEPRO 'Z' // don't reset 'readonly' on ":w!"
-#define CPO_DOLLAR '$'
-#define CPO_FILTER '!'
-#define CPO_MATCH '%'
-#define CPO_PLUS '+' // ":write file" resets 'modified'
-#define CPO_REGAPPEND '>' // insert NL when appending to a register
-#define CPO_SCOLON ';' // using "," and ";" will skip over char if
- // cursor would not move
-#define CPO_CHANGEW '_' // "cw" special-case
-// default values for Vim and Vi
-#define CPO_VIM "aABceFs_"
-#define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;_"
-
-// characters for p_ww option:
-#define WW_ALL "bshl<>[],~"
-
-// characters for p_mouse option:
-#define MOUSE_NORMAL 'n' // use mouse in Normal mode
-#define MOUSE_VISUAL 'v' // use mouse in Visual/Select mode
-#define MOUSE_INSERT 'i' // use mouse in Insert mode
-#define MOUSE_COMMAND 'c' // use mouse in Command-line mode
-#define MOUSE_HELP 'h' // use mouse in help buffers
-#define MOUSE_RETURN 'r' // use mouse for hit-return message
-#define MOUSE_A "nvich" // used for 'a' flag
-#define MOUSE_ALL "anvichr" // all possible characters
-#define MOUSE_NONE ' ' // don't use Visual selection
-#define MOUSE_NONEF 'x' // forced modeless selection
-
-// default vertical and horizontal mouse scroll values.
-// Note: This should be in sync with the default mousescroll option.
-#define MOUSESCROLL_VERT_DFLT 3
-#define MOUSESCROLL_HOR_DFLT 6
-
-#define COCU_ALL "nvic" // flags for 'concealcursor'
-
-/// characters for p_shm option:
-enum {
- SHM_RO = 'r', ///< Readonly.
- SHM_MOD = 'm', ///< Modified.
- SHM_FILE = 'f', ///< (file 1 of 2)
- SHM_LAST = 'i', ///< Last line incomplete.
- SHM_TEXT = 'x', ///< tx instead of textmode.
- SHM_LINES = 'l', ///< "L" instead of "lines".
- SHM_NEW = 'n', ///< "[New]" instead of "[New file]".
- SHM_WRI = 'w', ///< "[w]" instead of "written".
- SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
- SHM_WRITE = 'W', ///< Don't use "written" at all.
- SHM_TRUNC = 't', ///< Truncate file messages.
- SHM_TRUNCALL = 'T', ///< Truncate all messages.
- SHM_OVER = 'o', ///< Overwrite file messages.
- SHM_OVERALL = 'O', ///< Overwrite more messages.
- SHM_SEARCH = 's', ///< No search hit bottom messages.
- SHM_ATTENTION = 'A', ///< No ATTENTION messages.
- SHM_INTRO = 'I', ///< Intro messages.
- SHM_COMPLETIONMENU = 'c', ///< Completion menu messages.
- SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages.
- SHM_RECORDING = 'q', ///< Short recording message.
- SHM_FILEINFO = 'F', ///< No file info messages.
- SHM_SEARCHCOUNT = 'S', ///< Search stats: '[1/10]'
-};
-/// Represented by 'a' flag.
-#define SHM_ALL_ABBREVIATIONS ((char[]) { \
- SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW, SHM_WRI, \
- 0 })
-
-// characters for p_go:
-#define GO_ASEL 'a' // autoselect
-#define GO_ASELML 'A' // autoselect modeless selection
-#define GO_BOT 'b' // use bottom scrollbar
-#define GO_CONDIALOG 'c' // use console dialog
-#define GO_DARKTHEME 'd' // use dark theme variant
-#define GO_TABLINE 'e' // may show tabline
-#define GO_FORG 'f' // start GUI in foreground
-#define GO_GREY 'g' // use grey menu items
-#define GO_HORSCROLL 'h' // flexible horizontal scrolling
-#define GO_ICON 'i' // use Vim icon
-#define GO_LEFT 'l' // use left scrollbar
-#define GO_VLEFT 'L' // left scrollbar with vert split
-#define GO_MENUS 'm' // use menu bar
-#define GO_NOSYSMENU 'M' // don't source system menu
-#define GO_POINTER 'p' // pointer enter/leave callbacks
-#define GO_ASELPLUS 'P' // autoselectPlus
-#define GO_RIGHT 'r' // use right scrollbar
-#define GO_VRIGHT 'R' // right scrollbar with vert split
-#define GO_TOOLBAR 'T' // add toolbar
-#define GO_FOOTER 'F' // add footer
-#define GO_VERTICAL 'v' // arrange dialog buttons vertically
-#define GO_KEEPWINSIZE 'k' // keep GUI window size
-#define GO_ALL "aAbcdefFghilmMprTvk" // all possible flags for 'go'
-
-// flags for 'comments' option
-#define COM_NEST 'n' // comments strings nest
-#define COM_BLANK 'b' // needs blank after string
-#define COM_START 's' // start of comment
-#define COM_MIDDLE 'm' // middle of comment
-#define COM_END 'e' // end of comment
-#define COM_AUTO_END 'x' // last char of end closes comment
-#define COM_FIRST 'f' // first line comment only
-#define COM_LEFT 'l' // left adjusted
-#define COM_RIGHT 'r' // right adjusted
-#define COM_NOBACK 'O' // don't use for "O" command
-#define COM_ALL "nbsmexflrO" // all flags for 'comments' option
-#define COM_MAX_LEN 50 // maximum length of a part
-
-/// 'statusline' option flags
-enum {
- STL_FILEPATH = 'f', ///< Path of file in buffer.
- STL_FULLPATH = 'F', ///< Full path of file in buffer.
- STL_FILENAME = 't', ///< Last part (tail) of file path.
- STL_COLUMN = 'c', ///< Column og cursor.
- STL_VIRTCOL = 'v', ///< Virtual column.
- STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display.
- STL_LINE = 'l', ///< Line number of cursor.
- STL_NUMLINES = 'L', ///< Number of lines in buffer.
- STL_BUFNO = 'n', ///< Current buffer number.
- STL_KEYMAP = 'k', ///< 'keymap' when active.
- STL_OFFSET = 'o', ///< Offset of character under cursor.
- STL_OFFSET_X = 'O', ///< - in hexadecimal.
- STL_BYTEVAL = 'b', ///< Byte value of character.
- STL_BYTEVAL_X = 'B', ///< - in hexadecimal.
- STL_ROFLAG = 'r', ///< Readonly flag.
- STL_ROFLAG_ALT = 'R', ///< - other display.
- STL_HELPFLAG = 'h', ///< Window is showing a help file.
- STL_HELPFLAG_ALT = 'H', ///< - other display.
- STL_FILETYPE = 'y', ///< 'filetype'.
- STL_FILETYPE_ALT = 'Y', ///< - other display.
- STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf.
- STL_PREVIEWFLAG_ALT = 'W', ///< - other display.
- STL_MODIFIED = 'm', ///< Modified flag.
- STL_MODIFIED_ALT = 'M', ///< - other display.
- STL_QUICKFIX = 'q', ///< Quickfix window description.
- STL_PERCENTAGE = 'p', ///< Percentage through file.
- STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%.
- STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y).
- STL_PAGENUM = 'N', ///< Page number (when printing).
- STL_SHOWCMD = 'S', ///< 'showcmd' buffer
- STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn'
- STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn'
- STL_VIM_EXPR = '{', ///< Start of expression to substitute.
- STL_SEPARATE = '=', ///< Separation between alignment sections.
- STL_TRUNCMARK = '<', ///< Truncation mark if line is too long.
- STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0.
- STL_HIGHLIGHT = '#', ///< Highlight name.
- STL_TABPAGENR = 'T', ///< Tab page label nr.
- STL_TABCLOSENR = 'X', ///< Tab page close nr.
- STL_CLICK_FUNC = '@', ///< Click region start.
-};
-/// C string containing all 'statusline' option flags
-#define STL_ALL ((char[]) { \
- STL_FILEPATH, STL_FULLPATH, STL_FILENAME, STL_COLUMN, STL_VIRTCOL, \
- STL_VIRTCOL_ALT, STL_LINE, STL_NUMLINES, STL_BUFNO, STL_KEYMAP, STL_OFFSET, \
- STL_OFFSET_X, STL_BYTEVAL, STL_BYTEVAL_X, STL_ROFLAG, STL_ROFLAG_ALT, \
- STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \
- STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \
- STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \
- STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \
- STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \
- STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \
- 0, })
-
-// flags used for parsed 'wildmode'
-#define WIM_FULL 0x01
-#define WIM_LONGEST 0x02
-#define WIM_LIST 0x04
-#define WIM_BUFLASTUSED 0x08
-
-// arguments for can_bs()
-// each defined char should be unique over all values
-// except for BS_START, that intentionally also matches BS_NOSTOP
-// because BS_NOSTOP behaves exactly the same except it
-// does not stop at the start of the insert point
-#define BS_INDENT 'i' // "Indent"
-#define BS_EOL 'l' // "eoL"
-#define BS_START 's' // "Start"
-#define BS_NOSTOP 'p' // "nostoP
-
-// flags for the 'culopt' option
-#define CULOPT_LINE 0x01 // Highlight complete line
-#define CULOPT_SCRLINE 0x02 // Highlight screen line
-#define CULOPT_NBR 0x04 // Highlight Number column
-
-#define LISPWORD_VALUE \
- "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object"
-
-// The following are actual variables for the options
-
-EXTERN long p_aleph; // 'aleph'
-EXTERN char *p_ambw; ///< 'ambiwidth'
-EXTERN int p_acd; ///< 'autochdir'
-EXTERN int p_ai; ///< 'autoindent'
-EXTERN int p_bin; ///< 'binary'
-EXTERN int p_bomb; ///< 'bomb'
-EXTERN int p_bl; ///< 'buflisted'
-EXTERN int p_cin; ///< 'cindent'
-EXTERN long p_channel; ///< 'channel'
-EXTERN char *p_cink; ///< 'cinkeys'
-EXTERN char *p_cinsd; ///< 'cinscopedecls'
-EXTERN char *p_cinw; ///< 'cinwords'
-EXTERN char *p_cfu; ///< 'completefunc'
-EXTERN char *p_ofu; ///< 'omnifunc'
-EXTERN char *p_tsrfu; ///< 'thesaurusfunc'
-EXTERN int p_ci; ///< 'copyindent'
-EXTERN int p_ar; // 'autoread'
-EXTERN int p_aw; // 'autowrite'
-EXTERN int p_awa; // 'autowriteall'
-EXTERN char *p_bs; // 'backspace'
-EXTERN char *p_bg; // 'background'
-EXTERN int p_bk; // 'backup'
-EXTERN char *p_bkc; // 'backupcopy'
-EXTERN unsigned int bkc_flags; ///< flags from 'backupcopy'
-#define BKC_YES 0x001
-#define BKC_AUTO 0x002
-#define BKC_NO 0x004
-#define BKC_BREAKSYMLINK 0x008
-#define BKC_BREAKHARDLINK 0x010
-EXTERN char *p_bdir; // 'backupdir'
-EXTERN char *p_bex; // 'backupext'
-EXTERN char *p_bo; // 'belloff'
-EXTERN char breakat_flags[256]; // which characters are in 'breakat'
-EXTERN unsigned bo_flags;
-
-// values for the 'belloff' option
-#define BO_ALL 0x0001
-#define BO_BS 0x0002
-#define BO_CRSR 0x0004
-#define BO_COMPL 0x0008
-#define BO_COPY 0x0010
-#define BO_CTRLG 0x0020
-#define BO_ERROR 0x0040
-#define BO_ESC 0x0080
-#define BO_EX 0x0100
-#define BO_HANGUL 0x0200
-#define BO_IM 0x0400
-#define BO_LANG 0x0800
-#define BO_MESS 0x1000
-#define BO_MATCH 0x2000
-#define BO_OPER 0x4000
-#define BO_REG 0x8000
-#define BO_SH 0x10000
-#define BO_SPELL 0x20000
-#define BO_WILD 0x40000
-
-EXTERN char *p_bsk; // 'backupskip'
-EXTERN char *p_breakat; // 'breakat'
-EXTERN char *p_bh; ///< 'bufhidden'
-EXTERN char *p_bt; ///< 'buftype'
-EXTERN char *p_cmp; // 'casemap'
-EXTERN unsigned cmp_flags;
-#define CMP_INTERNAL 0x001
-#define CMP_KEEPASCII 0x002
-EXTERN char *p_enc; // 'encoding'
-EXTERN int p_deco; // 'delcombine'
-EXTERN char *p_ccv; // 'charconvert'
-EXTERN char *p_cino; ///< 'cinoptions'
-EXTERN char *p_cedit; // 'cedit'
-EXTERN char *p_cb; // 'clipboard'
-EXTERN unsigned cb_flags;
-#define CB_UNNAMED 0x001
-#define CB_UNNAMEDPLUS 0x002
-#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
-EXTERN long p_cwh; // 'cmdwinheight'
-EXTERN long p_ch; // 'cmdheight'
-EXTERN char *p_cms; ///< 'commentstring'
-EXTERN char *p_cpt; ///< 'complete'
-EXTERN long p_columns; // 'columns'
-EXTERN int p_confirm; // 'confirm'
-EXTERN char *p_cot; // 'completeopt'
-#ifdef BACKSLASH_IN_FILENAME
-EXTERN char *p_csl; // 'completeslash'
-#endif
-EXTERN long p_pb; // 'pumblend'
-EXTERN long p_ph; // 'pumheight'
-EXTERN long p_pw; // 'pumwidth'
-EXTERN char *p_com; ///< 'comments'
-EXTERN char *p_cpo; // 'cpoptions'
-EXTERN char *p_debug; // 'debug'
-EXTERN char *p_def; // 'define'
-EXTERN char *p_inc;
-EXTERN char *p_dip; // 'diffopt'
-EXTERN char *p_dex; // 'diffexpr'
-EXTERN char *p_dict; // 'dictionary'
-EXTERN int p_dg; // 'digraph'
-EXTERN char *p_dir; // 'directory'
-EXTERN char *p_dy; // 'display'
-EXTERN unsigned dy_flags;
-#define DY_LASTLINE 0x001
-#define DY_TRUNCATE 0x002
-#define DY_UHEX 0x004
-// legacy flag, not used
-#define DY_MSGSEP 0x008
-EXTERN int p_ed; // 'edcompatible'
-EXTERN char *p_ead; // 'eadirection'
-EXTERN int p_emoji; // 'emoji'
-EXTERN int p_ea; // 'equalalways'
-EXTERN char *p_ep; // 'equalprg'
-EXTERN int p_eb; // 'errorbells'
-EXTERN char *p_ef; // 'errorfile'
-EXTERN char *p_efm; // 'errorformat'
-EXTERN char *p_gefm; // 'grepformat'
-EXTERN char *p_gp; // 'grepprg'
-EXTERN int p_eof; ///< 'endoffile'
-EXTERN int p_eol; ///< 'endofline'
-EXTERN char *p_ei; // 'eventignore'
-EXTERN int p_et; ///< 'expandtab'
-EXTERN int p_exrc; // 'exrc'
-EXTERN char *p_fenc; ///< 'fileencoding'
-EXTERN char *p_fencs; // 'fileencodings'
-EXTERN char *p_ff; ///< 'fileformat'
-EXTERN char *p_ffs; // 'fileformats'
-EXTERN int p_fic; // 'fileignorecase'
-EXTERN char *p_ft; ///< 'filetype'
-EXTERN char *p_fcs; ///< 'fillchar'
-EXTERN int p_fixeol; ///< 'fixendofline'
-EXTERN char *p_fcl; // 'foldclose'
-EXTERN long p_fdls; // 'foldlevelstart'
-EXTERN char *p_fdo; // 'foldopen'
-EXTERN unsigned fdo_flags;
-#define FDO_ALL 0x001
-#define FDO_BLOCK 0x002
-#define FDO_HOR 0x004
-#define FDO_MARK 0x008
-#define FDO_PERCENT 0x010
-#define FDO_QUICKFIX 0x020
-#define FDO_SEARCH 0x040
-#define FDO_TAG 0x080
-#define FDO_INSERT 0x100
-#define FDO_UNDO 0x200
-#define FDO_JUMP 0x400
-EXTERN char *p_fex; ///< 'formatexpr'
-EXTERN char *p_flp; ///< 'formatlistpat'
-EXTERN char *p_fo; ///< 'formatoptions'
-EXTERN char *p_fp; // 'formatprg'
-EXTERN int p_fs; // 'fsync'
-EXTERN int p_gd; // 'gdefault'
-EXTERN char *p_guicursor; // 'guicursor'
-EXTERN char *p_guifont; // 'guifont'
-EXTERN char *p_guifontwide; // 'guifontwide'
-EXTERN char *p_hf; // 'helpfile'
-EXTERN long p_hh; // 'helpheight'
-EXTERN char *p_hlg; // 'helplang'
-EXTERN int p_hid; // 'hidden'
-EXTERN char *p_hl; // 'highlight'
-EXTERN int p_hls; // 'hlsearch'
-EXTERN long p_hi; // 'history'
-EXTERN int p_hkmap; // 'hkmap'
-EXTERN int p_hkmapp; // 'hkmapp'
-EXTERN int p_arshape; // 'arabicshape'
-EXTERN int p_icon; // 'icon'
-EXTERN char *p_iconstring; // 'iconstring'
-EXTERN int p_ic; // 'ignorecase'
-EXTERN long p_iminsert; ///< 'iminsert'
-EXTERN long p_imsearch; ///< 'imsearch'
-EXTERN int p_inf; ///< 'infercase'
-EXTERN char *p_inex; ///< 'includeexpr'
-EXTERN int p_is; // 'incsearch'
-EXTERN char *p_inde; ///< 'indentexpr'
-EXTERN char *p_indk; ///< 'indentkeys'
-EXTERN char *p_icm; // 'inccommand'
-EXTERN char *p_isf; // 'isfname'
-EXTERN char *p_isi; // 'isident'
-EXTERN char *p_isk; ///< 'iskeyword'
-EXTERN char *p_isp; // 'isprint'
-EXTERN int p_js; // 'joinspaces'
-EXTERN char *p_jop; // 'jumpooptions'
-EXTERN unsigned jop_flags;
-#define JOP_STACK 0x01
-#define JOP_VIEW 0x02
-EXTERN char *p_keymap; ///< 'keymap'
-EXTERN char *p_kp; // 'keywordprg'
-EXTERN char *p_km; // 'keymodel'
-EXTERN char *p_langmap; // 'langmap'
-EXTERN int p_lnr; // 'langnoremap'
-EXTERN int p_lrm; // 'langremap'
-EXTERN char *p_lm; // 'langmenu'
-EXTERN long p_lines; // 'lines'
-EXTERN long p_linespace; // 'linespace'
-EXTERN int p_lisp; ///< 'lisp'
-EXTERN char *p_lop; ///< 'lispoptions'
-EXTERN char *p_lispwords; // 'lispwords'
-EXTERN long p_ls; // 'laststatus'
-EXTERN long p_stal; // 'showtabline'
-EXTERN char *p_lcs; // 'listchars'
-
-EXTERN int p_lz; // 'lazyredraw'
-EXTERN int p_lpl; // 'loadplugins'
-EXTERN int p_magic; // 'magic'
-EXTERN char *p_menc; // 'makeencoding'
-EXTERN char *p_mef; // 'makeef'
-EXTERN char *p_mp; // 'makeprg'
-EXTERN char *p_mps; ///< 'matchpairs'
-EXTERN long p_mat; // 'matchtime'
-EXTERN long p_mco; // 'maxcombine'
-EXTERN long p_mfd; // 'maxfuncdepth'
-EXTERN long p_mmd; // 'maxmapdepth'
-EXTERN long p_mmp; // 'maxmempattern'
-EXTERN long p_mis; // 'menuitems'
-EXTERN char *p_msm; // 'mkspellmem'
-EXTERN int p_ml; ///< 'modeline'
-EXTERN int p_mle; // 'modelineexpr'
-EXTERN long p_mls; // 'modelines'
-EXTERN int p_ma; ///< 'modifiable'
-EXTERN int p_mod; ///< 'modified'
-EXTERN char *p_mouse; // 'mouse'
-EXTERN char *p_mousem; // 'mousemodel'
-EXTERN int p_mousemev; ///< 'mousemoveevent'
-EXTERN int p_mousef; // 'mousefocus'
-EXTERN char *p_mousescroll; // 'mousescroll'
-EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT);
-EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT);
-EXTERN long p_mouset; // 'mousetime'
-EXTERN int p_more; // 'more'
-EXTERN char *p_nf; ///< 'nrformats'
-EXTERN char *p_opfunc; // 'operatorfunc'
-EXTERN char *p_para; // 'paragraphs'
-EXTERN int p_paste; // 'paste'
-EXTERN char *p_pt; // 'pastetoggle'
-EXTERN char *p_pex; // 'patchexpr'
-EXTERN char *p_pm; // 'patchmode'
-EXTERN char *p_path; // 'path'
-EXTERN char *p_cdpath; // 'cdpath'
-EXTERN int p_pi; ///< 'preserveindent'
-EXTERN long p_pyx; // 'pyxversion'
-EXTERN char *p_qe; ///< 'quoteescape'
-EXTERN int p_ro; ///< 'readonly'
-EXTERN char *p_rdb; // 'redrawdebug'
-EXTERN unsigned rdb_flags;
-#define RDB_COMPOSITOR 0x001
-#define RDB_NOTHROTTLE 0x002
-#define RDB_INVALID 0x004
-#define RDB_NODELTA 0x008
-
-EXTERN long p_rdt; // 'redrawtime'
-EXTERN long p_re; // 'regexpengine'
-EXTERN long p_report; // 'report'
-EXTERN long p_pvh; // 'previewheight'
-EXTERN int p_ari; // 'allowrevins'
-EXTERN int p_ri; // 'revins'
-EXTERN int p_ru; // 'ruler'
-EXTERN char *p_ruf; // 'rulerformat'
-EXTERN char *p_pp; // 'packpath'
-EXTERN char *p_qftf; // 'quickfixtextfunc'
-EXTERN char *p_rtp; // 'runtimepath'
-EXTERN long p_scbk; // 'scrollback'
-EXTERN long p_sj; // 'scrolljump'
-EXTERN long p_so; // 'scrolloff'
-EXTERN char *p_sbo; // 'scrollopt'
-EXTERN char *p_sections; // 'sections'
-EXTERN int p_secure; // 'secure'
-EXTERN char *p_sel; // 'selection'
-EXTERN char *p_slm; // 'selectmode'
-EXTERN char *p_ssop; // 'sessionoptions'
-EXTERN unsigned ssop_flags;
-
-#define SSOP_BUFFERS 0x001
-#define SSOP_WINPOS 0x002
-#define SSOP_RESIZE 0x004
-#define SSOP_WINSIZE 0x008
-#define SSOP_LOCALOPTIONS 0x010
-#define SSOP_OPTIONS 0x020
-#define SSOP_HELP 0x040
-#define SSOP_BLANK 0x080
-#define SSOP_GLOBALS 0x100
-#define SSOP_SLASH 0x200 // Deprecated, always set.
-#define SSOP_UNIX 0x400 // Deprecated, always set.
-#define SSOP_SESDIR 0x800
-#define SSOP_CURDIR 0x1000
-#define SSOP_FOLDS 0x2000
-#define SSOP_CURSOR 0x4000
-#define SSOP_TABPAGES 0x8000
-#define SSOP_TERMINAL 0x10000
-#define SSOP_SKIP_RTP 0x20000
-
-EXTERN char *p_sh; // 'shell'
-EXTERN char *p_shcf; // 'shellcmdflag'
-EXTERN char *p_sp; // 'shellpipe'
-EXTERN char *p_shq; // 'shellquote'
-EXTERN char *p_sxq; // 'shellxquote'
-EXTERN char *p_sxe; // 'shellxescape'
-EXTERN char *p_srr; // 'shellredir'
-EXTERN int p_stmp; // 'shelltemp'
-#ifdef BACKSLASH_IN_FILENAME
-EXTERN int p_ssl; // 'shellslash'
-#endif
-EXTERN char *p_stl; // 'statusline'
-EXTERN char *p_wbr; // 'winbar'
-EXTERN int p_sr; // 'shiftround'
-EXTERN long p_sw; ///< 'shiftwidth'
-EXTERN char *p_shm; // 'shortmess'
-EXTERN char *p_sbr; // 'showbreak'
-EXTERN int p_sc; // 'showcmd'
-EXTERN char *p_sloc; // 'showcmdloc'
-EXTERN int p_sft; // 'showfulltag'
-EXTERN int p_sm; // 'showmatch'
-EXTERN int p_smd; // 'showmode'
-EXTERN long p_ss; // 'sidescroll'
-EXTERN long p_siso; // 'sidescrolloff'
-EXTERN int p_scs; // 'smartcase'
-EXTERN int p_si; ///< 'smartindent'
-EXTERN int p_sta; // 'smarttab'
-EXTERN long p_sts; ///< 'softtabstop'
-EXTERN int p_sb; // 'splitbelow'
-EXTERN char *p_sua; ///< 'suffixesadd'
-EXTERN int p_swf; ///< 'swapfile'
-EXTERN long p_smc; ///< 'synmaxcol'
-EXTERN long p_tpm; // 'tabpagemax'
-EXTERN char *p_tal; // 'tabline'
-EXTERN char *p_tpf; // 'termpastefilter'
-EXTERN unsigned int tpf_flags; ///< flags from 'termpastefilter'
-#define TPF_BS 0x001
-#define TPF_HT 0x002
-#define TPF_FF 0x004
-#define TPF_ESC 0x008
-#define TPF_DEL 0x010
-#define TPF_C0 0x020
-#define TPF_C1 0x040
-EXTERN char *p_tfu; ///< 'tagfunc'
-EXTERN char *p_spc; ///< 'spellcapcheck'
-EXTERN char *p_spf; ///< 'spellfile'
-EXTERN char *p_spk; ///< 'splitkeep'
-EXTERN char *p_spl; ///< 'spelllang'
-EXTERN char *p_spo; // 'spelloptions'
-EXTERN unsigned int spo_flags;
-EXTERN char *p_sps; // 'spellsuggest'
-EXTERN int p_spr; // 'splitright'
-EXTERN int p_sol; // 'startofline'
-EXTERN char *p_su; // 'suffixes'
-EXTERN char *p_swb; // 'switchbuf'
-EXTERN unsigned swb_flags;
-// Keep in sync with p_swb_values in optionstr.c
-#define SWB_USEOPEN 0x001
-#define SWB_USETAB 0x002
-#define SWB_SPLIT 0x004
-#define SWB_NEWTAB 0x008
-#define SWB_VSPLIT 0x010
-#define SWB_USELAST 0x020
-EXTERN char *p_syn; ///< 'syntax'
-EXTERN long p_ts; ///< 'tabstop'
-EXTERN int p_tbs; ///< 'tagbsearch'
-EXTERN char *p_tc; ///< 'tagcase'
-EXTERN unsigned tc_flags; ///< flags from 'tagcase'
-#define TC_FOLLOWIC 0x01
-#define TC_IGNORE 0x02
-#define TC_MATCH 0x04
-#define TC_FOLLOWSCS 0x08
-#define TC_SMART 0x10
-EXTERN long p_tl; ///< 'taglength'
-EXTERN int p_tr; ///< 'tagrelative'
-EXTERN char *p_tags; ///< 'tags'
-EXTERN int p_tgst; ///< 'tagstack'
-EXTERN int p_tbidi; ///< 'termbidi'
-EXTERN long p_tw; ///< 'textwidth'
-EXTERN int p_to; ///< 'tildeop'
-EXTERN int p_timeout; ///< 'timeout'
-EXTERN long p_tm; ///< 'timeoutlen'
-EXTERN int p_title; ///< 'title'
-EXTERN long p_titlelen; ///< 'titlelen'
-EXTERN char *p_titleold; ///< 'titleold'
-EXTERN char *p_titlestring; ///< 'titlestring'
-EXTERN char *p_tsr; ///< 'thesaurus'
-EXTERN int p_tgc; ///< 'termguicolors'
-EXTERN int p_ttimeout; ///< 'ttimeout'
-EXTERN long p_ttm; ///< 'ttimeoutlen'
-EXTERN char *p_udir; ///< 'undodir'
-EXTERN int p_udf; ///< 'undofile'
-EXTERN long p_ul; ///< 'undolevels'
-EXTERN long p_ur; ///< 'undoreload'
-EXTERN long p_uc; ///< 'updatecount'
-EXTERN long p_ut; ///< 'updatetime'
-EXTERN char *p_shada; ///< 'shada'
-EXTERN char *p_shadafile; ///< 'shadafile'
-EXTERN char *p_vsts; ///< 'varsofttabstop'
-EXTERN char *p_vts; ///< 'vartabstop'
-EXTERN char *p_vdir; ///< 'viewdir'
-EXTERN char *p_vop; ///< 'viewoptions'
-EXTERN unsigned vop_flags; ///< uses SSOP_ flags
-EXTERN int p_vb; ///< 'visualbell'
-EXTERN char *p_ve; ///< 'virtualedit'
-EXTERN unsigned ve_flags;
-#define VE_BLOCK 5U // includes "all"
-#define VE_INSERT 6U // includes "all"
-#define VE_ALL 4U
-#define VE_ONEMORE 8U
-#define VE_NONE 16U // "none"
-#define VE_NONEU 32U // "NONE"
-EXTERN long p_verbose; // 'verbose'
-#ifdef IN_OPTION_C
-char *p_vfile = ""; // used before options are initialized
-#else
-extern char *p_vfile; // 'verbosefile'
-#endif
-EXTERN int p_warn; // 'warn'
-EXTERN char *p_wop; // 'wildoptions'
-EXTERN unsigned wop_flags;
-#define WOP_TAGFILE 0x01
-#define WOP_PUM 0x02
-#define WOP_FUZZY 0x04
-EXTERN long p_window; // 'window'
-EXTERN char *p_wak; // 'winaltkeys'
-EXTERN char *p_wig; // 'wildignore'
-EXTERN char *p_ww; // 'whichwrap'
-EXTERN long p_wc; // 'wildchar'
-EXTERN long p_wcm; // 'wildcharm'
-EXTERN int p_wic; // 'wildignorecase'
-EXTERN char *p_wim; // 'wildmode'
-EXTERN int p_wmnu; // 'wildmenu'
-EXTERN long p_wh; // 'winheight'
-EXTERN long p_wmh; // 'winminheight'
-EXTERN long p_wmw; // 'winminwidth'
-EXTERN long p_wiw; // 'winwidth'
-EXTERN long p_wm; ///< 'wrapmargin'
-EXTERN int p_ws; // 'wrapscan'
-EXTERN int p_write; // 'write'
-EXTERN int p_wa; // 'writeany'
-EXTERN int p_wb; // 'writebackup'
-EXTERN long p_wd; // 'writedelay'
-EXTERN int p_cdh; // 'cdhome'
-
-EXTERN int p_force_on; ///< options that cannot be turned off.
-EXTERN int p_force_off; ///< options that cannot be turned on.
-
-//
-// "indir" values for buffer-local options.
-// These need to be defined globally, so that the BV_COUNT can be used with
-// b_p_scriptID[].
-//
-enum {
- BV_AI = 0,
- BV_AR,
- BV_BH,
- BV_BKC,
- BV_BT,
- BV_EFM,
- BV_GP,
- BV_MP,
- BV_BIN,
- BV_BL,
- BV_BOMB,
- BV_CHANNEL,
- BV_CI,
- BV_CIN,
- BV_CINK,
- BV_CINO,
- BV_CINW,
- BV_CINSD,
- BV_CM,
- BV_CMS,
- BV_COM,
- BV_CPT,
- BV_DICT,
- BV_TSR,
- BV_CSL,
- BV_CFU,
- BV_DEF,
- BV_INC,
- BV_EOF,
- BV_EOL,
- BV_FIXEOL,
- BV_EP,
- BV_ET,
- BV_FENC,
- BV_FP,
- BV_BEXPR,
- BV_FEX,
- BV_FF,
- BV_FLP,
- BV_FO,
- BV_FT,
- BV_IMI,
- BV_IMS,
- BV_INDE,
- BV_INDK,
- BV_INEX,
- BV_INF,
- BV_ISK,
- BV_KMAP,
- BV_KP,
- BV_LISP,
- BV_LOP,
- BV_LW,
- BV_MENC,
- BV_MA,
- BV_ML,
- BV_MOD,
- BV_MPS,
- BV_NF,
- BV_OFU,
- BV_PATH,
- BV_PI,
- BV_QE,
- BV_RO,
- BV_SCBK,
- BV_SI,
- BV_SMC,
- BV_SYN,
- BV_SPC,
- BV_SPF,
- BV_SPL,
- BV_SPO,
- BV_STS,
- BV_SUA,
- BV_SW,
- BV_SWF,
- BV_TFU,
- BV_TSRFU,
- BV_TAGS,
- BV_TC,
- BV_TS,
- BV_TW,
- BV_TX,
- BV_UDF,
- BV_UL,
- BV_WM,
- BV_VSTS,
- BV_VTS,
- BV_COUNT, // must be the last one
-};
-
-// "indir" values for window-local options.
-// These need to be defined globally, so that the WV_COUNT can be used in the
-// window structure.
-enum {
- WV_LIST = 0,
- WV_ARAB,
- WV_COCU,
- WV_COLE,
- WV_CRBIND,
- WV_BRI,
- WV_BRIOPT,
- WV_DIFF,
- WV_FDC,
- WV_FEN,
- WV_FDI,
- WV_FDL,
- WV_FDM,
- WV_FML,
- WV_FDN,
- WV_FDE,
- WV_FDT,
- WV_FMR,
- WV_LBR,
- WV_NU,
- WV_RNU,
- WV_VE,
- WV_NUW,
- WV_PVW,
- WV_RL,
- WV_RLC,
- WV_SCBIND,
- WV_SCROLL,
- WV_SISO,
- WV_SO,
- WV_SPELL,
- WV_CUC,
- WV_CUL,
- WV_CULOPT,
- WV_CC,
- WV_SBR,
- WV_STC,
- WV_STL,
- WV_WFH,
- WV_WFW,
- WV_WRAP,
- WV_SCL,
- WV_WINHL,
- WV_LCS,
- WV_FCS,
- WV_WINBL,
- WV_WBR,
- WV_COUNT, // must be the last one
-};
-
-// Value for b_p_ul indicating the global value must be used.
-#define NO_LOCAL_UNDOLEVEL (-123456)
-
-#define SB_MAX 100000 // Maximum 'scrollback' value.
-
-#define TABSTOP_MAX 9999
-
-/// Stores an identifier of a script or channel that last set an option.
+ kOptValTypeNil = 0,
+ kOptValTypeBoolean,
+ kOptValTypeNumber,
+ kOptValTypeString,
+} OptValType;
+
+typedef union {
+ // boolean options are actually tri-states because they have a third "None" value.
+ TriState boolean;
+ OptInt number;
+ String string;
+} OptValData;
+
+/// Option value
typedef struct {
- sctx_T script_ctx; /// script context where the option was last set
- uint64_t channel_id; /// Only used when script_id is SID_API_CLIENT.
-} LastSet;
+ OptValType type;
+ OptValData data;
+} OptVal;
-// WV_ and BV_ values get typecasted to this for the "indir" field
+/// :set operator types
typedef enum {
- PV_NONE = 0,
- PV_MAXVAL = 0xffff, // to avoid warnings for value out of range
-} idopt_T;
-
-typedef struct vimoption {
- char *fullname; // full option name
- char *shortname; // permissible abbreviation
- uint32_t flags; // see below
- char_u *var; // global option: pointer to variable;
- // window-local option: VAR_WIN;
- // buffer-local option: global value
- idopt_T indir; // global option: PV_NONE;
- // local option: indirect option index
- char *def_val; // default values for variable (neovim!!)
- LastSet last_set; // script in which the option was last set
-} vimoption_T;
-
-// The options that are local to a window or buffer have "indir" set to one of
-// these values. Special values:
-// PV_NONE: global option.
-// PV_WIN is added: window-local option
-// PV_BUF is added: buffer-local option
-// PV_BOTH is added: global option which also has a local value.
-#define PV_BOTH 0x1000
-#define PV_WIN 0x2000
-#define PV_BUF 0x4000
-#define PV_MASK 0x0fff
-#define OPT_WIN(x) (idopt_T)(PV_WIN + (int)(x))
-#define OPT_BUF(x) (idopt_T)(PV_BUF + (int)(x))
-#define OPT_BOTH(x) (idopt_T)(PV_BOTH + (int)(x))
+ OP_NONE = 0,
+ OP_ADDING, ///< "opt+=arg"
+ OP_PREPENDING, ///< "opt^=arg"
+ OP_REMOVING, ///< "opt-=arg"
+} set_op_T;
+
+/// Argument for the callback function (opt_did_set_cb_T) invoked after an
+/// option value is modified.
+typedef struct {
+ /// Pointer to the option variable. The variable can be an OptInt (numeric
+ /// option), an int (boolean option) or a char pointer (string option).
+ void *os_varp;
+ int os_idx;
+ int os_flags;
+
+ /// Old value of the option.
+ OptValData os_oldval;
+ /// New value of the option.
+ OptValData os_newval;
+
+ /// Option value was checked to be safe, no need to set P_INSECURE
+ /// Used for the 'keymap', 'filetype' and 'syntax' options.
+ bool os_value_checked;
+ /// Option value changed. Used for the 'filetype' and 'syntax' options.
+ bool os_value_changed;
+
+ /// Used by the 'isident', 'iskeyword', 'isprint' and 'isfname' options.
+ /// Set to true if the character table is modified when processing the
+ /// option and need to be restored because of a failure.
+ bool os_restore_chartab;
+
+ /// If the value specified for an option is not valid and the error message
+ /// is parameterized, then the "os_errbuf" buffer is used to store the error
+ /// message (when it is not NULL).
+ char *os_errbuf;
+ size_t os_errbuflen;
+
+ void *os_win;
+ void *os_buf;
+} optset_T;
+
+/// Type for the callback function that is invoked after an option value is
+/// changed to validate and apply the new value.
+///
+/// Returns NULL if the option value is valid and successfully applied.
+/// Otherwise returns an error message.
+typedef const char *(*opt_did_set_cb_T)(optset_T *args);
-// Options local to a window have a value local to a buffer and global to all
-// buffers. Indicate this by setting "var" to VAR_WIN.
-#define VAR_WIN ((char_u *)-1)
+/// Argument for the callback function (opt_expand_cb_T) invoked after a string
+/// option value is expanded for cmdline completion.
+typedef struct {
+ /// Pointer to the option variable. It's always a string.
+ char *oe_varp;
+ /// The original option value, escaped.
+ char *oe_opt_value;
+
+ /// true if using set+= instead of set=
+ bool oe_append;
+ /// true if we would like to add the original option value as the first choice.
+ bool oe_include_orig_val;
+
+ /// Regex from the cmdline, for matching potential options against.
+ regmatch_T *oe_regmatch;
+ /// The expansion context.
+ expand_T *oe_xp;
+
+ /// The full argument passed to :set. For example, if the user inputs
+ /// ":set dip=icase,algorithm:my<Tab>", oe_xp->xp_pattern will only have
+ /// "my", but oe_set_arg will contain the whole "icase,algorithm:my".
+ char *oe_set_arg;
+} optexpand_T;
+
+/// Type for the callback function that is invoked when expanding possible
+/// string option values during cmdline completion.
+///
+/// Strings in returned matches will be managed and freed by caller.
+///
+/// Returns OK if the expansion succeeded (numMatches and matches have to be
+/// set). Otherwise returns FAIL.
+///
+/// Note: If returned FAIL or *numMatches is 0, *matches will NOT be freed by
+/// caller.
+typedef int (*opt_expand_cb_T)(optexpand_T *args, int *numMatches, char ***matches);
-#endif // NVIM_OPTION_DEFS_H
+/// Requested option scopes for various functions in option.c
+typedef enum {
+ kOptReqGlobal = 0, ///< Request global option value
+ kOptReqWin = 1, ///< Request window-local option value
+ kOptReqBuf = 2, ///< Request buffer-local option value
+} OptReqScope;
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
new file mode 100644
index 0000000000..b0e9ff9434
--- /dev/null
+++ b/src/nvim/option_vars.h
@@ -0,0 +1,948 @@
+#pragma once
+
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
+
+// option_vars.h: definition of global variables for settable options
+
+// Option Flags
+#define P_BOOL 0x01U ///< the option is boolean
+#define P_NUM 0x02U ///< the option is numeric
+#define P_STRING 0x04U ///< the option is a string
+#define P_ALLOCED 0x08U ///< the string option is in allocated memory,
+ ///< must use free_string_option() when
+ ///< assigning new value. Not set if default is
+ ///< the same.
+#define P_EXPAND 0x10U ///< environment expansion. NOTE: P_EXPAND can
+ ///< never be used for local or hidden options
+#define P_NO_DEF_EXP 0x20U ///< do not expand default value
+#define P_NODEFAULT 0x40U ///< don't set to default value
+#define P_DEF_ALLOCED 0x80U ///< default value is in allocated memory, must
+ ///< use free() when assigning new value
+#define P_WAS_SET 0x100U ///< option has been set/reset
+#define P_NO_MKRC 0x200U ///< don't include in :mkvimrc output
+
+// when option changed, what to display:
+#define P_UI_OPTION 0x400U ///< send option to remote UI
+#define P_RTABL 0x800U ///< redraw tabline
+#define P_RSTAT 0x1000U ///< redraw status lines
+#define P_RWIN 0x2000U ///< redraw current window and recompute text
+#define P_RBUF 0x4000U ///< redraw current buffer and recompute text
+#define P_RALL 0x6000U ///< redraw all windows
+#define P_RCLR 0x7000U ///< clear and redraw all
+
+#define P_COMMA 0x8000U ///< comma separated list
+#define P_ONECOMMA 0x18000U ///< P_COMMA and cannot have two consecutive
+ ///< commas
+#define P_NODUP 0x20000U ///< don't allow duplicate strings
+#define P_FLAGLIST 0x40000U ///< list of single-char flags
+
+#define P_SECURE 0x80000U ///< cannot change in modeline or secure mode
+#define P_GETTEXT 0x100000U ///< expand default value with _()
+#define P_NOGLOB 0x200000U ///< do not use local value for global vimrc
+#define P_NFNAME 0x400000U ///< only normal file name chars allowed
+#define P_INSECURE 0x800000U ///< option was set from a modeline
+#define P_PRI_MKRC 0x1000000U ///< priority for :mkvimrc (setting option
+ ///< has side effects)
+#define P_NO_ML 0x2000000U ///< not allowed in modeline
+#define P_CURSWANT 0x4000000U ///< update curswant required; not needed
+ ///< when there is a redraw flag
+#define P_NDNAME 0x8000000U ///< only normal dir name chars allowed
+#define P_RWINONLY 0x10000000U ///< only redraw current window
+#define P_MLE 0x20000000U ///< under control of 'modelineexpr'
+#define P_FUNC 0x40000000U ///< accept a function reference or a lambda
+#define P_COLON 0x80000000U ///< values use colons to create sublists
+// Warning: Currently we have used all 32 bits for option flags, and adding more
+// flags will overflow it. Adding another flag will need to change how
+// it's stored first.
+
+#define HIGHLIGHT_INIT \
+ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
+ "i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
+ "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \
+ "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \
+ "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \
+ "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel," \
+ "[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb," \
+ "*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \
+ "q:QuickFixLine,0:Whitespace,I:NormalNC"
+
+// Default values for 'errorformat'.
+// The "%f|%l| %m" one is used for when the contents of the quickfix window is
+// written to a file.
+#ifdef MSWIN
+# define DFLT_EFM \
+ "%f(%l): %t%*\\D%n: %m,%f(%l\\,%c): %t%*\\D%n: %m,%f(%l) \\=: %t%*\\D%n: %m,%*[^\"]\"%f\"%*\\D%l: %m,%f(%l) \\=: %m,%*[^ ] %f %l: %m,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,%f|%l| %m"
+#else
+# define DFLT_EFM \
+ "%*[^\"]\"%f\"%*\\D%l: %m,\"%f\"%*\\D%l: %m,%-Gg%\\?make[%*\\d]: *** [%f:%l:%m,%-Gg%\\?make: *** [%f:%l:%m,%-G%f:%l: (Each undeclared identifier is reported only once,%-G%f:%l: for each function it appears in.),%-GIn file included from %f:%l:%c:,%-GIn file included from %f:%l:%c\\,,%-GIn file included from %f:%l:%c,%-GIn file included from %f:%l,%-G%*[ ]from %f:%l:%c,%-G%*[ ]from %f:%l:,%-G%*[ ]from %f:%l\\,,%-G%*[ ]from %f:%l,%f:%l:%c:%m,%f(%l):%m,%f:%l:%m,\"%f\"\\, line %l%*\\D%c%*[^ ] %m,%D%*\\a[%*\\d]: Entering directory %*[`']%f',%X%*\\a[%*\\d]: Leaving directory %*[`']%f',%D%*\\a: Entering directory %*[`']%f',%X%*\\a: Leaving directory %*[`']%f',%DMaking %*\\a in %f,%f|%l| %m"
+#endif
+
+#define DFLT_GREPFORMAT "%f:%l:%m,%f:%l%m,%f %l%m"
+
+// default values for b_p_ff 'fileformat' and p_ffs 'fileformats'
+#define FF_DOS "dos"
+#define FF_MAC "mac"
+#define FF_UNIX "unix"
+
+#ifdef USE_CRNL
+# define DFLT_FF "dos"
+# define DFLT_FFS_VIM "dos,unix"
+# define DFLT_FFS_VI "dos,unix" // also autodetect in compatible mode
+#else
+# define DFLT_FF "unix"
+# define DFLT_FFS_VIM "unix,dos"
+# define DFLT_FFS_VI ""
+#endif
+
+// Possible values for 'encoding'
+#define ENC_UCSBOM "ucs-bom" // check for BOM at start of file
+
+// default value for 'encoding'
+#define ENC_DFLT "utf-8"
+
+// end-of-line style
+#define EOL_UNKNOWN (-1) // not defined yet
+#define EOL_UNIX 0 // NL
+#define EOL_DOS 1 // CR NL
+#define EOL_MAC 2 // CR
+
+// Formatting options for p_fo 'formatoptions'
+#define FO_WRAP 't'
+#define FO_WRAP_COMS 'c'
+#define FO_RET_COMS 'r'
+#define FO_OPEN_COMS 'o'
+#define FO_NO_OPEN_COMS '/'
+#define FO_Q_COMS 'q'
+#define FO_Q_NUMBER 'n'
+#define FO_Q_SECOND '2'
+#define FO_INS_VI 'v'
+#define FO_INS_LONG 'l'
+#define FO_INS_BLANK 'b'
+#define FO_MBYTE_BREAK 'm' // break before/after multi-byte char
+#define FO_MBYTE_JOIN 'M' // no space before/after multi-byte char
+#define FO_MBYTE_JOIN2 'B' // no space between multi-byte chars
+#define FO_ONE_LETTER '1'
+#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
+#define FO_AUTO 'a' // automatic formatting
+#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
+#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
+#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
+
+#define DFLT_FO_VI "vt"
+#define DFLT_FO_VIM "tcqj"
+#define FO_ALL "tcro/q2vlb1mMBn,aw]jp" // for do_set()
+
+// characters for the p_cpo option:
+#define CPO_ALTREAD 'a' // ":read" sets alternate file name
+#define CPO_ALTWRITE 'A' // ":write" sets alternate file name
+#define CPO_BAR 'b' // "\|" ends a mapping
+#define CPO_BSLASH 'B' // backslash in mapping is not special
+#define CPO_SEARCH 'c'
+#define CPO_CONCAT 'C' // Don't concatenate sourced lines
+#define CPO_DOTTAG 'd' // "./tags" in 'tags' is in current dir
+#define CPO_DIGRAPH 'D' // No digraph after "r", "f", etc.
+#define CPO_EXECBUF 'e'
+#define CPO_EMPTYREGION 'E' // operating on empty region is an error
+#define CPO_FNAMER 'f' // set file name for ":r file"
+#define CPO_FNAMEW 'F' // set file name for ":w file"
+#define CPO_INTMOD 'i' // interrupt a read makes buffer modified
+#define CPO_INDENT 'I' // remove auto-indent more often
+#define CPO_ENDOFSENT 'J' // need two spaces to detect end of sentence
+#define CPO_KOFFSET 'K' // don't wait for key code in mappings
+#define CPO_LITERAL 'l' // take char after backslash in [] literal
+#define CPO_LISTWM 'L' // 'list' changes wrapmargin
+#define CPO_SHOWMATCH 'm'
+#define CPO_MATCHBSL 'M' // "%" ignores use of backslashes
+#define CPO_NUMCOL 'n' // 'number' column also used for text
+#define CPO_LINEOFF 'o'
+#define CPO_OVERNEW 'O' // silently overwrite new file
+#define CPO_LISP 'p' // 'lisp' indenting
+#define CPO_FNAMEAPP 'P' // set file name for ":w >>file"
+#define CPO_JOINCOL 'q' // with "3J" use column after first join
+#define CPO_REDO 'r'
+#define CPO_REMMARK 'R' // remove marks when filtering
+#define CPO_BUFOPT 's'
+#define CPO_BUFOPTGLOB 'S'
+#define CPO_TAGPAT 't' // tag pattern is used for "n"
+#define CPO_UNDO 'u' // "u" undoes itself
+#define CPO_BACKSPACE 'v' // "v" keep deleted text
+#define CPO_FWRITE 'W' // "w!" doesn't overwrite readonly files
+#define CPO_ESC 'x'
+#define CPO_REPLCNT 'X' // "R" with a count only deletes chars once
+#define CPO_YANK 'y'
+#define CPO_KEEPRO 'Z' // don't reset 'readonly' on ":w!"
+#define CPO_DOLLAR '$'
+#define CPO_FILTER '!'
+#define CPO_MATCH '%'
+#define CPO_PLUS '+' // ":write file" resets 'modified'
+#define CPO_REGAPPEND '>' // insert NL when appending to a register
+#define CPO_SCOLON ';' // using "," and ";" will skip over char if
+ // cursor would not move
+#define CPO_CHANGEW '_' // "cw" special-case
+// default values for Vim and Vi
+#define CPO_VIM "aABceFs_"
+#define CPO_VI "aAbBcCdDeEfFiIJKlLmMnoOpPqrRsStuvWxXyZ$!%+>;_"
+
+// characters for p_ww option:
+#define WW_ALL "bshl<>[]~"
+
+// characters for p_mouse option:
+#define MOUSE_NORMAL 'n' // use mouse in Normal mode
+#define MOUSE_VISUAL 'v' // use mouse in Visual/Select mode
+#define MOUSE_INSERT 'i' // use mouse in Insert mode
+#define MOUSE_COMMAND 'c' // use mouse in Command-line mode
+#define MOUSE_HELP 'h' // use mouse in help buffers
+#define MOUSE_RETURN 'r' // use mouse for hit-return message
+#define MOUSE_A "nvich" // used for 'a' flag
+#define MOUSE_ALL "anvichr" // all possible characters
+#define MOUSE_NONE ' ' // don't use Visual selection
+#define MOUSE_NONEF 'x' // forced modeless selection
+
+// default vertical and horizontal mouse scroll values.
+// Note: This should be in sync with the default mousescroll option.
+#define MOUSESCROLL_VERT_DFLT 3
+#define MOUSESCROLL_HOR_DFLT 6
+
+#define COCU_ALL "nvic" // flags for 'concealcursor'
+
+/// characters for p_shm option:
+enum {
+ SHM_RO = 'r', ///< Readonly.
+ SHM_MOD = 'm', ///< Modified.
+ SHM_LINES = 'l', ///< "L" instead of "lines".
+ SHM_WRI = 'w', ///< "[w]" instead of "written".
+ SHM_ABBREVIATIONS = 'a', ///< Use abbreviations from #SHM_ALL_ABBREVIATIONS.
+ SHM_WRITE = 'W', ///< Don't use "written" at all.
+ SHM_TRUNC = 't', ///< Truncate file messages.
+ SHM_TRUNCALL = 'T', ///< Truncate all messages.
+ SHM_OVER = 'o', ///< Overwrite file messages.
+ SHM_OVERALL = 'O', ///< Overwrite more messages.
+ SHM_SEARCH = 's', ///< No search hit bottom messages.
+ SHM_ATTENTION = 'A', ///< No ATTENTION messages.
+ SHM_INTRO = 'I', ///< Intro messages.
+ SHM_COMPLETIONMENU = 'c', ///< Completion menu messages.
+ SHM_COMPLETIONSCAN = 'C', ///< Completion scanning messages.
+ SHM_RECORDING = 'q', ///< Short recording message.
+ SHM_FILEINFO = 'F', ///< No file info messages.
+ SHM_SEARCHCOUNT = 'S', ///< No search stats: '[1/10]'
+};
+/// Represented by 'a' flag.
+#define SHM_ALL_ABBREVIATIONS ((char[]) { \
+ SHM_RO, SHM_MOD, SHM_LINES, SHM_WRI, \
+ 0 })
+
+// characters for p_go:
+#define GO_ASEL 'a' // autoselect
+#define GO_ASELML 'A' // autoselect modeless selection
+#define GO_BOT 'b' // use bottom scrollbar
+#define GO_CONDIALOG 'c' // use console dialog
+#define GO_DARKTHEME 'd' // use dark theme variant
+#define GO_TABLINE 'e' // may show tabline
+#define GO_FORG 'f' // start GUI in foreground
+#define GO_GREY 'g' // use grey menu items
+#define GO_HORSCROLL 'h' // flexible horizontal scrolling
+#define GO_ICON 'i' // use Vim icon
+#define GO_LEFT 'l' // use left scrollbar
+#define GO_VLEFT 'L' // left scrollbar with vert split
+#define GO_MENUS 'm' // use menu bar
+#define GO_NOSYSMENU 'M' // don't source system menu
+#define GO_POINTER 'p' // pointer enter/leave callbacks
+#define GO_ASELPLUS 'P' // autoselectPlus
+#define GO_RIGHT 'r' // use right scrollbar
+#define GO_VRIGHT 'R' // right scrollbar with vert split
+#define GO_TOOLBAR 'T' // add toolbar
+#define GO_FOOTER 'F' // add footer
+#define GO_VERTICAL 'v' // arrange dialog buttons vertically
+#define GO_KEEPWINSIZE 'k' // keep GUI window size
+#define GO_ALL "!aAbcdefFghilLmMpPrRtTvk" // all possible flags for 'go'
+
+// flags for 'comments' option
+#define COM_NEST 'n' // comments strings nest
+#define COM_BLANK 'b' // needs blank after string
+#define COM_START 's' // start of comment
+#define COM_MIDDLE 'm' // middle of comment
+#define COM_END 'e' // end of comment
+#define COM_AUTO_END 'x' // last char of end closes comment
+#define COM_FIRST 'f' // first line comment only
+#define COM_LEFT 'l' // left adjusted
+#define COM_RIGHT 'r' // right adjusted
+#define COM_NOBACK 'O' // don't use for "O" command
+#define COM_ALL "nbsmexflrO" // all flags for 'comments' option
+#define COM_MAX_LEN 50 // maximum length of a part
+
+/// 'statusline' option flags
+enum {
+ STL_FILEPATH = 'f', ///< Path of file in buffer.
+ STL_FULLPATH = 'F', ///< Full path of file in buffer.
+ STL_FILENAME = 't', ///< Last part (tail) of file path.
+ STL_COLUMN = 'c', ///< Column og cursor.
+ STL_VIRTCOL = 'v', ///< Virtual column.
+ STL_VIRTCOL_ALT = 'V', ///< - with 'if different' display.
+ STL_LINE = 'l', ///< Line number of cursor.
+ STL_NUMLINES = 'L', ///< Number of lines in buffer.
+ STL_BUFNO = 'n', ///< Current buffer number.
+ STL_KEYMAP = 'k', ///< 'keymap' when active.
+ STL_OFFSET = 'o', ///< Offset of character under cursor.
+ STL_OFFSET_X = 'O', ///< - in hexadecimal.
+ STL_BYTEVAL = 'b', ///< Byte value of character.
+ STL_BYTEVAL_X = 'B', ///< - in hexadecimal.
+ STL_ROFLAG = 'r', ///< Readonly flag.
+ STL_ROFLAG_ALT = 'R', ///< - other display.
+ STL_HELPFLAG = 'h', ///< Window is showing a help file.
+ STL_HELPFLAG_ALT = 'H', ///< - other display.
+ STL_FILETYPE = 'y', ///< 'filetype'.
+ STL_FILETYPE_ALT = 'Y', ///< - other display.
+ STL_PREVIEWFLAG = 'w', ///< Window is showing the preview buf.
+ STL_PREVIEWFLAG_ALT = 'W', ///< - other display.
+ STL_MODIFIED = 'm', ///< Modified flag.
+ STL_MODIFIED_ALT = 'M', ///< - other display.
+ STL_QUICKFIX = 'q', ///< Quickfix window description.
+ STL_PERCENTAGE = 'p', ///< Percentage through file.
+ STL_ALTPERCENT = 'P', ///< Percentage as TOP BOT ALL or NN%.
+ STL_ARGLISTSTAT = 'a', ///< Argument list status as (x of y).
+ STL_PAGENUM = 'N', ///< Page number (when printing).
+ STL_SHOWCMD = 'S', ///< 'showcmd' buffer
+ STL_FOLDCOL = 'C', ///< Fold column for 'statuscolumn'
+ STL_SIGNCOL = 's', ///< Sign column for 'statuscolumn'
+ STL_VIM_EXPR = '{', ///< Start of expression to substitute.
+ STL_SEPARATE = '=', ///< Separation between alignment sections.
+ STL_TRUNCMARK = '<', ///< Truncation mark if line is too long.
+ STL_USER_HL = '*', ///< Highlight from (User)1..9 or 0.
+ STL_HIGHLIGHT = '#', ///< Highlight name.
+ STL_TABPAGENR = 'T', ///< Tab page label nr.
+ STL_TABCLOSENR = 'X', ///< Tab page close nr.
+ STL_CLICK_FUNC = '@', ///< Click region start.
+};
+/// C string containing all 'statusline' option flags
+#define STL_ALL ((char[]) { \
+ STL_FILEPATH, STL_FULLPATH, STL_FILENAME, STL_COLUMN, STL_VIRTCOL, \
+ STL_VIRTCOL_ALT, STL_LINE, STL_NUMLINES, STL_BUFNO, STL_KEYMAP, STL_OFFSET, \
+ STL_OFFSET_X, STL_BYTEVAL, STL_BYTEVAL_X, STL_ROFLAG, STL_ROFLAG_ALT, \
+ STL_HELPFLAG, STL_HELPFLAG_ALT, STL_FILETYPE, STL_FILETYPE_ALT, \
+ STL_PREVIEWFLAG, STL_PREVIEWFLAG_ALT, STL_MODIFIED, STL_MODIFIED_ALT, \
+ STL_QUICKFIX, STL_PERCENTAGE, STL_ALTPERCENT, STL_ARGLISTSTAT, STL_PAGENUM, \
+ STL_SHOWCMD, STL_FOLDCOL, STL_SIGNCOL, STL_VIM_EXPR, STL_SEPARATE, \
+ STL_TRUNCMARK, STL_USER_HL, STL_HIGHLIGHT, STL_TABPAGENR, STL_TABCLOSENR, \
+ STL_CLICK_FUNC, STL_TABPAGENR, STL_TABCLOSENR, STL_CLICK_FUNC, \
+ 0, })
+
+// flags used for parsed 'wildmode'
+#define WIM_FULL 0x01
+#define WIM_LONGEST 0x02
+#define WIM_LIST 0x04
+#define WIM_BUFLASTUSED 0x08
+
+// arguments for can_bs()
+// each defined char should be unique over all values
+// except for BS_START, that intentionally also matches BS_NOSTOP
+// because BS_NOSTOP behaves exactly the same except it
+// does not stop at the start of the insert point
+#define BS_INDENT 'i' // "Indent"
+#define BS_EOL 'l' // "eoL"
+#define BS_START 's' // "Start"
+#define BS_NOSTOP 'p' // "nostoP
+
+// flags for the 'culopt' option
+#define CULOPT_LINE 0x01 // Highlight complete line
+#define CULOPT_SCRLINE 0x02 // Highlight screen line
+#define CULOPT_NBR 0x04 // Highlight Number column
+
+#define LISPWORD_VALUE \
+ "defun,define,defmacro,set!,lambda,if,case,let,flet,let*,letrec,do,do*,define-syntax,let-syntax,letrec-syntax,destructuring-bind,defpackage,defparameter,defstruct,deftype,defvar,do-all-symbols,do-external-symbols,do-symbols,dolist,dotimes,ecase,etypecase,eval-when,labels,macrolet,multiple-value-bind,multiple-value-call,multiple-value-prog1,multiple-value-setq,prog1,progv,typecase,unless,unwind-protect,when,with-input-from-string,with-open-file,with-open-stream,with-output-to-string,with-package-iterator,define-condition,handler-bind,handler-case,restart-bind,restart-case,with-simple-restart,store-value,use-value,muffle-warning,abort,continue,with-slots,with-slots*,with-accessors,with-accessors*,defclass,defmethod,print-unreadable-object"
+
+// The following are actual variables for the options
+
+EXTERN char *p_ambw; ///< 'ambiwidth'
+EXTERN int p_acd; ///< 'autochdir'
+EXTERN int p_ai; ///< 'autoindent'
+EXTERN int p_bin; ///< 'binary'
+EXTERN int p_bomb; ///< 'bomb'
+EXTERN int p_bl; ///< 'buflisted'
+EXTERN int p_cin; ///< 'cindent'
+EXTERN OptInt p_channel; ///< 'channel'
+EXTERN char *p_cink; ///< 'cinkeys'
+EXTERN char *p_cinsd; ///< 'cinscopedecls'
+EXTERN char *p_cinw; ///< 'cinwords'
+EXTERN char *p_cfu; ///< 'completefunc'
+EXTERN char *p_ofu; ///< 'omnifunc'
+EXTERN char *p_tsrfu; ///< 'thesaurusfunc'
+EXTERN int p_ci; ///< 'copyindent'
+EXTERN int p_ar; ///< 'autoread'
+EXTERN int p_aw; ///< 'autowrite'
+EXTERN int p_awa; ///< 'autowriteall'
+EXTERN char *p_bs; ///< 'backspace'
+EXTERN char *p_bg; ///< 'background'
+EXTERN int p_bk; ///< 'backup'
+EXTERN char *p_bkc; ///< 'backupcopy'
+EXTERN unsigned bkc_flags; ///< flags from 'backupcopy'
+#define BKC_YES 0x001
+#define BKC_AUTO 0x002
+#define BKC_NO 0x004
+#define BKC_BREAKSYMLINK 0x008
+#define BKC_BREAKHARDLINK 0x010
+EXTERN char *p_bdir; ///< 'backupdir'
+EXTERN char *p_bex; ///< 'backupext'
+EXTERN char *p_bo; ///< 'belloff'
+EXTERN char breakat_flags[256]; ///< which characters are in 'breakat'
+EXTERN unsigned bo_flags;
+
+// values for the 'belloff' option
+#define BO_ALL 0x0001
+#define BO_BS 0x0002
+#define BO_CRSR 0x0004
+#define BO_COMPL 0x0008
+#define BO_COPY 0x0010
+#define BO_CTRLG 0x0020
+#define BO_ERROR 0x0040
+#define BO_ESC 0x0080
+#define BO_EX 0x0100
+#define BO_HANGUL 0x0200
+#define BO_IM 0x0400
+#define BO_LANG 0x0800
+#define BO_MESS 0x1000
+#define BO_MATCH 0x2000
+#define BO_OPER 0x4000
+#define BO_REG 0x8000
+#define BO_SH 0x10000
+#define BO_SPELL 0x20000
+#define BO_WILD 0x40000
+
+EXTERN char *p_bsk; ///< 'backupskip'
+EXTERN char *p_breakat; ///< 'breakat'
+EXTERN char *p_bh; ///< 'bufhidden'
+EXTERN char *p_bt; ///< 'buftype'
+EXTERN char *p_cmp; ///< 'casemap'
+EXTERN unsigned cmp_flags;
+#define CMP_INTERNAL 0x001
+#define CMP_KEEPASCII 0x002
+EXTERN char *p_enc; ///< 'encoding'
+EXTERN int p_deco; ///< 'delcombine'
+EXTERN char *p_ccv; ///< 'charconvert'
+EXTERN char *p_cino; ///< 'cinoptions'
+EXTERN char *p_cedit; ///< 'cedit'
+EXTERN char *p_cb; ///< 'clipboard'
+EXTERN unsigned cb_flags;
+#define CB_UNNAMED 0x001
+#define CB_UNNAMEDPLUS 0x002
+#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
+EXTERN OptInt p_cwh; ///< 'cmdwinheight'
+EXTERN OptInt p_ch; ///< 'cmdheight'
+EXTERN char *p_cms; ///< 'commentstring'
+EXTERN char *p_cpt; ///< 'complete'
+EXTERN OptInt p_columns; ///< 'columns'
+EXTERN int p_confirm; ///< 'confirm'
+EXTERN char *p_cot; ///< 'completeopt'
+#ifdef BACKSLASH_IN_FILENAME
+EXTERN char *p_csl; ///< 'completeslash'
+#endif
+EXTERN OptInt p_pb; ///< 'pumblend'
+EXTERN OptInt p_ph; ///< 'pumheight'
+EXTERN OptInt p_pw; ///< 'pumwidth'
+EXTERN char *p_com; ///< 'comments'
+EXTERN char *p_cpo; ///< 'cpoptions'
+EXTERN char *p_debug; ///< 'debug'
+EXTERN char *p_def; ///< 'define'
+EXTERN char *p_inc;
+EXTERN char *p_dip; ///< 'diffopt'
+EXTERN char *p_dex; ///< 'diffexpr'
+EXTERN char *p_dict; ///< 'dictionary'
+EXTERN int p_dg; ///< 'digraph'
+EXTERN char *p_dir; ///< 'directory'
+EXTERN char *p_dy; ///< 'display'
+EXTERN unsigned dy_flags;
+#define DY_LASTLINE 0x001
+#define DY_TRUNCATE 0x002
+#define DY_UHEX 0x004
+// legacy flag, not used
+#define DY_MSGSEP 0x008
+EXTERN char *p_ead; ///< 'eadirection'
+EXTERN int p_emoji; ///< 'emoji'
+EXTERN int p_ea; ///< 'equalalways'
+EXTERN char *p_ep; ///< 'equalprg'
+EXTERN int p_eb; ///< 'errorbells'
+EXTERN char *p_ef; ///< 'errorfile'
+EXTERN char *p_efm; ///< 'errorformat'
+EXTERN char *p_gefm; ///< 'grepformat'
+EXTERN char *p_gp; ///< 'grepprg'
+EXTERN int p_eof; ///< 'endoffile'
+EXTERN int p_eol; ///< 'endofline'
+EXTERN char *p_ei; ///< 'eventignore'
+EXTERN int p_et; ///< 'expandtab'
+EXTERN int p_exrc; ///< 'exrc'
+EXTERN char *p_fenc; ///< 'fileencoding'
+EXTERN char *p_fencs; ///< 'fileencodings'
+EXTERN char *p_ff; ///< 'fileformat'
+EXTERN char *p_ffs; ///< 'fileformats'
+EXTERN int p_fic; ///< 'fileignorecase'
+EXTERN char *p_ft; ///< 'filetype'
+EXTERN char *p_fcs; ///< 'fillchar'
+EXTERN int p_fixeol; ///< 'fixendofline'
+EXTERN char *p_fcl; ///< 'foldclose'
+EXTERN OptInt p_fdls; ///< 'foldlevelstart'
+EXTERN char *p_fdo; ///< 'foldopen'
+EXTERN unsigned fdo_flags;
+#define FDO_ALL 0x001
+#define FDO_BLOCK 0x002
+#define FDO_HOR 0x004
+#define FDO_MARK 0x008
+#define FDO_PERCENT 0x010
+#define FDO_QUICKFIX 0x020
+#define FDO_SEARCH 0x040
+#define FDO_TAG 0x080
+#define FDO_INSERT 0x100
+#define FDO_UNDO 0x200
+#define FDO_JUMP 0x400
+EXTERN char *p_fex; ///< 'formatexpr'
+EXTERN char *p_flp; ///< 'formatlistpat'
+EXTERN char *p_fo; ///< 'formatoptions'
+EXTERN char *p_fp; ///< 'formatprg'
+EXTERN int p_fs; ///< 'fsync'
+EXTERN int p_gd; ///< 'gdefault'
+EXTERN char *p_guicursor; ///< 'guicursor'
+EXTERN char *p_guifont; ///< 'guifont'
+EXTERN char *p_guifontwide; ///< 'guifontwide'
+EXTERN char *p_hf; ///< 'helpfile'
+EXTERN OptInt p_hh; ///< 'helpheight'
+EXTERN char *p_hlg; ///< 'helplang'
+EXTERN int p_hid; ///< 'hidden'
+EXTERN char *p_hl; ///< 'highlight'
+EXTERN int p_hls; ///< 'hlsearch'
+EXTERN OptInt p_hi; ///< 'history'
+EXTERN int p_arshape; ///< 'arabicshape'
+EXTERN int p_icon; ///< 'icon'
+EXTERN char *p_iconstring; ///< 'iconstring'
+EXTERN int p_ic; ///< 'ignorecase'
+EXTERN OptInt p_iminsert; ///< 'iminsert'
+EXTERN OptInt p_imsearch; ///< 'imsearch'
+EXTERN int p_inf; ///< 'infercase'
+EXTERN char *p_inex; ///< 'includeexpr'
+EXTERN int p_is; ///< 'incsearch'
+EXTERN char *p_inde; ///< 'indentexpr'
+EXTERN char *p_indk; ///< 'indentkeys'
+EXTERN char *p_icm; ///< 'inccommand'
+EXTERN char *p_isf; ///< 'isfname'
+EXTERN char *p_isi; ///< 'isident'
+EXTERN char *p_isk; ///< 'iskeyword'
+EXTERN char *p_isp; ///< 'isprint'
+EXTERN int p_js; ///< 'joinspaces'
+EXTERN char *p_jop; ///< 'jumpooptions'
+EXTERN unsigned jop_flags;
+#define JOP_STACK 0x01
+#define JOP_VIEW 0x02
+EXTERN char *p_keymap; ///< 'keymap'
+EXTERN char *p_kp; ///< 'keywordprg'
+EXTERN char *p_km; ///< 'keymodel'
+EXTERN char *p_langmap; ///< 'langmap'
+EXTERN int p_lnr; ///< 'langnoremap'
+EXTERN int p_lrm; ///< 'langremap'
+EXTERN char *p_lm; ///< 'langmenu'
+EXTERN OptInt p_lines; ///< 'lines'
+EXTERN OptInt p_linespace; ///< 'linespace'
+EXTERN int p_lisp; ///< 'lisp'
+EXTERN char *p_lop; ///< 'lispoptions'
+EXTERN char *p_lispwords; ///< 'lispwords'
+EXTERN OptInt p_ls; ///< 'laststatus'
+EXTERN OptInt p_stal; ///< 'showtabline'
+EXTERN char *p_lcs; ///< 'listchars'
+
+EXTERN int p_lz; ///< 'lazyredraw'
+EXTERN int p_lpl; ///< 'loadplugins'
+EXTERN int p_magic; ///< 'magic'
+EXTERN char *p_menc; ///< 'makeencoding'
+EXTERN char *p_mef; ///< 'makeef'
+EXTERN char *p_mp; ///< 'makeprg'
+EXTERN char *p_mps; ///< 'matchpairs'
+EXTERN OptInt p_mat; ///< 'matchtime'
+EXTERN OptInt p_mco; ///< 'maxcombine'
+#define MAX_MCO 6 // fixed value for 'maxcombine'
+EXTERN OptInt p_mfd; ///< 'maxfuncdepth'
+EXTERN OptInt p_mmd; ///< 'maxmapdepth'
+EXTERN OptInt p_mmp; ///< 'maxmempattern'
+EXTERN OptInt p_mis; ///< 'menuitems'
+EXTERN char *p_msm; ///< 'mkspellmem'
+EXTERN int p_ml; ///< 'modeline'
+EXTERN int p_mle; ///< 'modelineexpr'
+EXTERN OptInt p_mls; ///< 'modelines'
+EXTERN int p_ma; ///< 'modifiable'
+EXTERN int p_mod; ///< 'modified'
+EXTERN char *p_mouse; ///< 'mouse'
+EXTERN char *p_mousem; ///< 'mousemodel'
+EXTERN int p_mousemev; ///< 'mousemoveevent'
+EXTERN int p_mousef; ///< 'mousefocus'
+EXTERN int p_mh; ///< 'mousehide'
+EXTERN char *p_mousescroll; ///< 'mousescroll'
+EXTERN OptInt p_mousescroll_vert INIT( = MOUSESCROLL_VERT_DFLT);
+EXTERN OptInt p_mousescroll_hor INIT( = MOUSESCROLL_HOR_DFLT);
+EXTERN OptInt p_mouset; ///< 'mousetime'
+EXTERN int p_more; ///< 'more'
+EXTERN char *p_nf; ///< 'nrformats'
+EXTERN char *p_opfunc; ///< 'operatorfunc'
+EXTERN char *p_para; ///< 'paragraphs'
+EXTERN int p_paste; ///< 'paste'
+EXTERN char *p_pex; ///< 'patchexpr'
+EXTERN char *p_pm; ///< 'patchmode'
+EXTERN char *p_path; ///< 'path'
+EXTERN char *p_cdpath; ///< 'cdpath'
+EXTERN int p_pi; ///< 'preserveindent'
+EXTERN OptInt p_pyx; ///< 'pyxversion'
+EXTERN char *p_qe; ///< 'quoteescape'
+EXTERN int p_ro; ///< 'readonly'
+EXTERN char *p_rdb; ///< 'redrawdebug'
+EXTERN unsigned rdb_flags;
+#define RDB_COMPOSITOR 0x001
+#define RDB_NOTHROTTLE 0x002
+#define RDB_INVALID 0x004
+#define RDB_NODELTA 0x008
+#define RDB_LINE 0x010
+#define RDB_FLUSH 0x020
+#define RDB_INTERSECT 0x040
+
+EXTERN OptInt p_rdt; ///< 'redrawtime'
+EXTERN OptInt p_re; ///< 'regexpengine'
+EXTERN OptInt p_report; ///< 'report'
+EXTERN OptInt p_pvh; ///< 'previewheight'
+EXTERN int p_ari; ///< 'allowrevins'
+EXTERN int p_ri; ///< 'revins'
+EXTERN int p_ru; ///< 'ruler'
+EXTERN char *p_ruf; ///< 'rulerformat'
+EXTERN char *p_pp; ///< 'packpath'
+EXTERN char *p_qftf; ///< 'quickfixtextfunc'
+EXTERN char *p_rtp; ///< 'runtimepath'
+EXTERN OptInt p_scbk; ///< 'scrollback'
+EXTERN OptInt p_sj; ///< 'scrolljump'
+EXTERN OptInt p_so; ///< 'scrolloff'
+EXTERN char *p_sbo; ///< 'scrollopt'
+EXTERN char *p_sections; ///< 'sections'
+EXTERN int p_secure; ///< 'secure'
+EXTERN char *p_sel; ///< 'selection'
+EXTERN char *p_slm; ///< 'selectmode'
+EXTERN char *p_ssop; ///< 'sessionoptions'
+EXTERN unsigned ssop_flags;
+
+#define SSOP_BUFFERS 0x001
+#define SSOP_WINPOS 0x002
+#define SSOP_RESIZE 0x004
+#define SSOP_WINSIZE 0x008
+#define SSOP_LOCALOPTIONS 0x010
+#define SSOP_OPTIONS 0x020
+#define SSOP_HELP 0x040
+#define SSOP_BLANK 0x080
+#define SSOP_GLOBALS 0x100
+#define SSOP_SLASH 0x200 // Deprecated, always set.
+#define SSOP_UNIX 0x400 // Deprecated, always set.
+#define SSOP_SESDIR 0x800
+#define SSOP_CURDIR 0x1000
+#define SSOP_FOLDS 0x2000
+#define SSOP_CURSOR 0x4000
+#define SSOP_TABPAGES 0x8000
+#define SSOP_TERMINAL 0x10000
+#define SSOP_SKIP_RTP 0x20000
+
+EXTERN char *p_sh; ///< 'shell'
+EXTERN char *p_shcf; ///< 'shellcmdflag'
+EXTERN char *p_sp; ///< 'shellpipe'
+EXTERN char *p_shq; ///< 'shellquote'
+EXTERN char *p_sxq; ///< 'shellxquote'
+EXTERN char *p_sxe; ///< 'shellxescape'
+EXTERN char *p_srr; ///< 'shellredir'
+EXTERN int p_stmp; ///< 'shelltemp'
+#ifdef BACKSLASH_IN_FILENAME
+EXTERN int p_ssl; ///< 'shellslash'
+#endif
+EXTERN char *p_stl; ///< 'statusline'
+EXTERN char *p_wbr; ///< 'winbar'
+EXTERN int p_sr; ///< 'shiftround'
+EXTERN OptInt p_sw; ///< 'shiftwidth'
+EXTERN char *p_shm; ///< 'shortmess'
+EXTERN char *p_sbr; ///< 'showbreak'
+EXTERN int p_sc; ///< 'showcmd'
+EXTERN char *p_sloc; ///< 'showcmdloc'
+EXTERN int p_sft; ///< 'showfulltag'
+EXTERN int p_sm; ///< 'showmatch'
+EXTERN int p_smd; ///< 'showmode'
+EXTERN OptInt p_ss; ///< 'sidescroll'
+EXTERN OptInt p_siso; ///< 'sidescrolloff'
+EXTERN int p_scs; ///< 'smartcase'
+EXTERN int p_si; ///< 'smartindent'
+EXTERN int p_sta; ///< 'smarttab'
+EXTERN OptInt p_sts; ///< 'softtabstop'
+EXTERN int p_sb; ///< 'splitbelow'
+EXTERN char *p_sua; ///< 'suffixesadd'
+EXTERN int p_swf; ///< 'swapfile'
+EXTERN OptInt p_smc; ///< 'synmaxcol'
+EXTERN OptInt p_tpm; ///< 'tabpagemax'
+EXTERN char *p_tal; ///< 'tabline'
+EXTERN char *p_tpf; ///< 'termpastefilter'
+EXTERN unsigned tpf_flags; ///< flags from 'termpastefilter'
+#define TPF_BS 0x001
+#define TPF_HT 0x002
+#define TPF_FF 0x004
+#define TPF_ESC 0x008
+#define TPF_DEL 0x010
+#define TPF_C0 0x020
+#define TPF_C1 0x040
+EXTERN char *p_tfu; ///< 'tagfunc'
+EXTERN char *p_spc; ///< 'spellcapcheck'
+EXTERN char *p_spf; ///< 'spellfile'
+EXTERN char *p_spk; ///< 'splitkeep'
+EXTERN char *p_spl; ///< 'spelllang'
+EXTERN char *p_spo; ///< 'spelloptions'
+EXTERN unsigned spo_flags;
+EXTERN char *p_sps; ///< 'spellsuggest'
+EXTERN int p_spr; ///< 'splitright'
+EXTERN int p_sol; ///< 'startofline'
+EXTERN char *p_su; ///< 'suffixes'
+EXTERN char *p_swb; ///< 'switchbuf'
+EXTERN unsigned swb_flags;
+// Keep in sync with p_swb_values in optionstr.c
+#define SWB_USEOPEN 0x001
+#define SWB_USETAB 0x002
+#define SWB_SPLIT 0x004
+#define SWB_NEWTAB 0x008
+#define SWB_VSPLIT 0x010
+#define SWB_USELAST 0x020
+EXTERN char *p_syn; ///< 'syntax'
+EXTERN OptInt p_ts; ///< 'tabstop'
+EXTERN int p_tbs; ///< 'tagbsearch'
+EXTERN char *p_tc; ///< 'tagcase'
+EXTERN unsigned tc_flags; ///< flags from 'tagcase'
+#define TC_FOLLOWIC 0x01
+#define TC_IGNORE 0x02
+#define TC_MATCH 0x04
+#define TC_FOLLOWSCS 0x08
+#define TC_SMART 0x10
+EXTERN OptInt p_tl; ///< 'taglength'
+EXTERN int p_tr; ///< 'tagrelative'
+EXTERN char *p_tags; ///< 'tags'
+EXTERN int p_tgst; ///< 'tagstack'
+EXTERN int p_tbidi; ///< 'termbidi'
+EXTERN OptInt p_tw; ///< 'textwidth'
+EXTERN int p_to; ///< 'tildeop'
+EXTERN int p_timeout; ///< 'timeout'
+EXTERN OptInt p_tm; ///< 'timeoutlen'
+EXTERN int p_title; ///< 'title'
+EXTERN OptInt p_titlelen; ///< 'titlelen'
+EXTERN char *p_titleold; ///< 'titleold'
+EXTERN char *p_titlestring; ///< 'titlestring'
+EXTERN char *p_tsr; ///< 'thesaurus'
+EXTERN int p_tgc; ///< 'termguicolors'
+EXTERN int p_ttimeout; ///< 'ttimeout'
+EXTERN OptInt p_ttm; ///< 'ttimeoutlen'
+EXTERN char *p_udir; ///< 'undodir'
+EXTERN int p_udf; ///< 'undofile'
+EXTERN OptInt p_ul; ///< 'undolevels'
+EXTERN OptInt p_ur; ///< 'undoreload'
+EXTERN OptInt p_uc; ///< 'updatecount'
+EXTERN OptInt p_ut; ///< 'updatetime'
+EXTERN char *p_shada; ///< 'shada'
+EXTERN char *p_shadafile; ///< 'shadafile'
+EXTERN int p_termsync; ///< 'termsync'
+EXTERN char *p_vsts; ///< 'varsofttabstop'
+EXTERN char *p_vts; ///< 'vartabstop'
+EXTERN char *p_vdir; ///< 'viewdir'
+EXTERN char *p_vop; ///< 'viewoptions'
+EXTERN unsigned vop_flags; ///< uses SSOP_ flags
+EXTERN int p_vb; ///< 'visualbell'
+EXTERN char *p_ve; ///< 'virtualedit'
+EXTERN unsigned ve_flags;
+#define VE_BLOCK 5U // includes "all"
+#define VE_INSERT 6U // includes "all"
+#define VE_ALL 4U
+#define VE_ONEMORE 8U
+#define VE_NONE 16U // "none"
+#define VE_NONEU 32U // "NONE"
+EXTERN OptInt p_verbose; ///< 'verbose'
+#ifdef IN_OPTION_C
+char *p_vfile = ""; ///< used before options are initialized
+#else
+extern char *p_vfile; ///< 'verbosefile'
+#endif
+EXTERN int p_warn; ///< 'warn'
+EXTERN char *p_wop; ///< 'wildoptions'
+EXTERN unsigned wop_flags;
+#define WOP_FUZZY 0x01
+#define WOP_TAGFILE 0x02
+#define WOP_PUM 0x04
+EXTERN OptInt p_window; ///< 'window'
+EXTERN char *p_wak; ///< 'winaltkeys'
+EXTERN char *p_wig; ///< 'wildignore'
+EXTERN char *p_ww; ///< 'whichwrap'
+EXTERN OptInt p_wc; ///< 'wildchar'
+EXTERN OptInt p_wcm; ///< 'wildcharm'
+EXTERN int p_wic; ///< 'wildignorecase'
+EXTERN char *p_wim; ///< 'wildmode'
+EXTERN int p_wmnu; ///< 'wildmenu'
+EXTERN OptInt p_wh; ///< 'winheight'
+EXTERN OptInt p_wmh; ///< 'winminheight'
+EXTERN OptInt p_wmw; ///< 'winminwidth'
+EXTERN OptInt p_wiw; ///< 'winwidth'
+EXTERN OptInt p_wm; ///< 'wrapmargin'
+EXTERN int p_ws; ///< 'wrapscan'
+EXTERN int p_write; ///< 'write'
+EXTERN int p_wa; ///< 'writeany'
+EXTERN int p_wb; ///< 'writebackup'
+EXTERN OptInt p_wd; ///< 'writedelay'
+EXTERN int p_cdh; ///< 'cdhome'
+
+/// "indir" values for buffer-local options.
+/// These need to be defined globally, so that the BV_COUNT can be used with
+/// b_p_script_stx[].
+enum {
+ BV_AI = 0,
+ BV_AR,
+ BV_BH,
+ BV_BKC,
+ BV_BT,
+ BV_EFM,
+ BV_GP,
+ BV_MP,
+ BV_BIN,
+ BV_BL,
+ BV_BOMB,
+ BV_CHANNEL,
+ BV_CI,
+ BV_CIN,
+ BV_CINK,
+ BV_CINO,
+ BV_CINW,
+ BV_CINSD,
+ BV_CM,
+ BV_CMS,
+ BV_COM,
+ BV_CPT,
+ BV_DICT,
+ BV_TSR,
+ BV_CSL,
+ BV_CFU,
+ BV_DEF,
+ BV_INC,
+ BV_EOF,
+ BV_EOL,
+ BV_FIXEOL,
+ BV_EP,
+ BV_ET,
+ BV_FENC,
+ BV_FP,
+ BV_BEXPR,
+ BV_FEX,
+ BV_FF,
+ BV_FLP,
+ BV_FO,
+ BV_FT,
+ BV_IMI,
+ BV_IMS,
+ BV_INDE,
+ BV_INDK,
+ BV_INEX,
+ BV_INF,
+ BV_ISK,
+ BV_KMAP,
+ BV_KP,
+ BV_LISP,
+ BV_LOP,
+ BV_LW,
+ BV_MENC,
+ BV_MA,
+ BV_ML,
+ BV_MOD,
+ BV_MPS,
+ BV_NF,
+ BV_OFU,
+ BV_PATH,
+ BV_PI,
+ BV_QE,
+ BV_RO,
+ BV_SCBK,
+ BV_SI,
+ BV_SMC,
+ BV_SYN,
+ BV_SPC,
+ BV_SPF,
+ BV_SPL,
+ BV_SPO,
+ BV_STS,
+ BV_SUA,
+ BV_SW,
+ BV_SWF,
+ BV_TFU,
+ BV_TSRFU,
+ BV_TAGS,
+ BV_TC,
+ BV_TS,
+ BV_TW,
+ BV_TX,
+ BV_UDF,
+ BV_UL,
+ BV_WM,
+ BV_VSTS,
+ BV_VTS,
+ BV_COUNT, // must be the last one
+};
+
+/// "indir" values for window-local options.
+/// These need to be defined globally, so that the WV_COUNT can be used in the
+/// window structure.
+enum {
+ WV_LIST = 0,
+ WV_ARAB,
+ WV_COCU,
+ WV_COLE,
+ WV_CRBIND,
+ WV_BRI,
+ WV_BRIOPT,
+ WV_DIFF,
+ WV_FDC,
+ WV_FEN,
+ WV_FDI,
+ WV_FDL,
+ WV_FDM,
+ WV_FML,
+ WV_FDN,
+ WV_FDE,
+ WV_FDT,
+ WV_FMR,
+ WV_LBR,
+ WV_NU,
+ WV_RNU,
+ WV_VE,
+ WV_NUW,
+ WV_PVW,
+ WV_RL,
+ WV_RLC,
+ WV_SCBIND,
+ WV_SCROLL,
+ WV_SMS,
+ WV_SISO,
+ WV_SO,
+ WV_SPELL,
+ WV_CUC,
+ WV_CUL,
+ WV_CULOPT,
+ WV_CC,
+ WV_SBR,
+ WV_STC,
+ WV_STL,
+ WV_WFH,
+ WV_WFW,
+ WV_WRAP,
+ WV_SCL,
+ WV_WINHL,
+ WV_LCS,
+ WV_FCS,
+ WV_WINBL,
+ WV_WBR,
+ WV_COUNT, // must be the last one
+};
+
+// Value for b_p_ul indicating the global value must be used.
+#define NO_LOCAL_UNDOLEVEL (-123456)
+
+#define SB_MAX 100000 // Maximum 'scrollback' value.
+
+#define MAX_NUMBERWIDTH 20 // used for 'numberwidth' and 'statuscolumn'
+
+#define TABSTOP_MAX 9999
+
+#define SCL_NO -1 // 'signcolumn' set to "no"
+#define SCL_NUM -2 // 'signcolumn' set to "number"
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 387ccd0888..daaf73d241 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1,2905 +1,10093 @@
--- {
--- {
--- full_name='aleph', abbreviation='al',
--- short_desc="ASCII code of the letter Aleph (Hebrew)",
--- varname='p_aleph', pv_name=nil,
--- type='number', list=nil, scope={'global'},
--- deny_duplicates=nil,
--- enable_if=nil,
--- defaults={condition=nil, if_true=224, if_false=nil},
--- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil,
--- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil,
--- modelineexpr=nil,
--- func=nil,
--- expand=nil, nodefault=nil, no_mkrc=nil,
--- alloced=nil,
--- save_pv_indir=nil,
--- redraw={'curswant'},
--- }
--- }
--- types: bool, number, string
--- lists: (nil), comma, onecomma, flags, flagscomma
--- scopes: global, buffer, window
--- redraw options: statuslines, tabline, current_window, current_window_only,
--- current_buffer, all_windows, curswant
--- defaults: {condition=#if condition, if_true=default, if_false=default}
--- #if condition:
--- string: #ifdef string
--- !string: #ifndef string
--- {string, string}: #if defined(string) && defined(string)
--- {!string, !string}: #if !defined(string) && !defined(string)
-local cstr = function(s)
+--- @class vim.option_meta
+--- @field full_name string
+--- @field desc? string
+--- @field abbreviation? string
+--- @field short_desc? string|fun(): string
+--- @field varname? string
+--- @field pv_name? string
+--- @field type 'bool'|'number'|'string'
+--- @field immutable? boolean
+--- @field list? 'comma'|'onecomma'|'commacolon'|'onecommacolon'|'flags'|'flagscomma'
+--- @field scope vim.option_scope[]
+--- @field deny_duplicates? boolean
+--- @field enable_if? string|false
+--- @field defaults? vim.option_defaults
+--- @field secure? true
+--- @field noglob? true
+--- @field normal_fname_chars? true
+--- @field pri_mkrc? true
+--- @field deny_in_modelines? true
+--- @field normal_dname_chars? true
+--- @field modelineexpr? true
+--- @field func? true
+--- @field expand? string|true
+--- @field nodefault? true
+--- @field no_mkrc? true
+--- @field alloced? true
+--- @field redraw? vim.option_redraw[]
+--- @field cb? string
+--- @field expand_cb? string
+--- @field tags? string[]
+
+--- @class vim.option_defaults
+--- @field condition? string
+--- string: #ifdef string
+--- !string: #ifndef string
+--- @field if_true integer|boolean|string|fun(): string
+--- @field if_false? integer|boolean|string
+--- @field doc? string Default to show in options.txt
+--- @field meta? integer|boolean|string Default to use in Lua meta files
+
+--- @alias vim.option_scope 'global'|'buffer'|'window'
+
+--- @alias vim.option_redraw
+--- |'statuslines'
+--- |'tabline'
+--- |'current_window'
+--- |'current_window_only'
+--- |'current_buffer'
+--- |'all_windows'
+--- |'curswant'
+--- |'ui_option'
+
+--- @param s string
+--- @return string
+local function cstr(s)
return '"' .. s:gsub('["\\]', '\\%0'):gsub('\t', '\\t') .. '"'
end
-local macros=function(s)
+
+--- @param s string
+--- @return fun(): string
+local function macros(s)
return function()
return s
end
end
-local imacros=function(s)
+
+--- @param s string
+--- @return fun(): string
+local function imacros(s)
return function()
return '(intptr_t)' .. s
end
end
-local N_=function(s) -- luacheck: ignore 211 (currently unused)
+
+--- @param s string
+--- @return fun(): string
+local function N_(s) -- luacheck: ignore 211 (currently unused)
return function()
return 'N_(' .. cstr(s) .. ')'
end
end
--- used for 'cinkeys' and 'indentkeys'
-local indentkeys_default = '0{,0},0),0],:,0#,!^F,o,O,e';
+
+-- luacheck: ignore 621
return {
- cstr=cstr,
- options={
- {
- full_name='aleph', abbreviation='al',
- short_desc=N_("ASCII code of the letter Aleph (Hebrew)"),
- type='number', scope={'global'},
- redraw={'curswant'},
- varname='p_aleph',
- defaults={if_true=224}
- },
- {
- full_name='arabic', abbreviation='arab',
- short_desc=N_("Arabic as a default second language"),
- type='bool', scope={'window'},
- redraw={'curswant'},
- defaults={if_true=false}
- },
- {
- full_name='arabicshape', abbreviation='arshape',
- short_desc=N_("do shaping for Arabic characters"),
- type='bool', scope={'global'},
- redraw={'all_windows', 'ui_option'},
+ cstr = cstr,
+ --- @type vim.option_meta[]
+ options = {
+ {
+ abbreviation = 'al',
+ defaults = { if_true = 224 },
+ full_name = 'aleph',
+ scope = { 'global' },
+ short_desc = N_('ASCII code of the letter Aleph (Hebrew)'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'arab',
+ cb = 'did_set_arabic',
+ defaults = { if_true = false },
+ desc = [=[
+ This option can be set to start editing Arabic text.
+ Setting this option will:
+ - Set the 'rightleft' option, unless 'termbidi' is set.
+ - Set the 'arabicshape' option, unless 'termbidi' is set.
+ - Set the 'keymap' option to "arabic"; in Insert mode CTRL-^ toggles
+ between typing English and Arabic key mapping.
+ - Set the 'delcombine' option
- varname='p_arshape',
- defaults={if_true=true}
- },
- {
- full_name='allowrevins', abbreviation='ari',
- short_desc=N_("allow CTRL-_ in Insert and Command-line mode"),
- type='bool', scope={'global'},
- varname='p_ari',
- defaults={if_true=false}
- },
- {
- full_name='ambiwidth', abbreviation='ambw',
- short_desc=N_("what to do with Unicode chars of ambiguous width"),
- type='string', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_ambw',
- defaults={if_true="single"}
- },
- {
- full_name='autochdir', abbreviation='acd',
- short_desc=N_("change directory to the file in the current window"),
- type='bool', scope={'global'},
- varname='p_acd',
- defaults={if_true=false}
- },
- {
- full_name='autoindent', abbreviation='ai',
- short_desc=N_("take indent for new line from previous line"),
- type='bool', scope={'buffer'},
- varname='p_ai',
- defaults={if_true=true}
- },
- {
- full_name='autoread', abbreviation='ar',
- short_desc=N_("autom. read file when changed outside of Vim"),
- type='bool', scope={'global', 'buffer'},
- varname='p_ar',
- defaults={if_true=true}
- },
- {
- full_name='autowrite', abbreviation='aw',
- short_desc=N_("automatically write file if changed"),
- type='bool', scope={'global'},
- varname='p_aw',
- defaults={if_true=false}
- },
- {
- full_name='autowriteall', abbreviation='awa',
- short_desc=N_("as 'autowrite', but works with more commands"),
- type='bool', scope={'global'},
- varname='p_awa',
- defaults={if_true=false}
- },
- {
- full_name='background', abbreviation='bg',
- short_desc=N_("\"dark\" or \"light\", used for highlight colors"),
- type='string', scope={'global'},
- varname='p_bg',
- defaults={if_true="dark"}
- },
- {
- full_name='backspace', abbreviation='bs',
- short_desc=N_("how backspace works at start of line"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_bs',
- defaults={if_true="indent,eol,start"}
- },
- {
- full_name='backup', abbreviation='bk',
- short_desc=N_("keep backup file after overwriting a file"),
- type='bool', scope={'global'},
- varname='p_bk',
- defaults={if_true=false}
- },
- {
- full_name='backupcopy', abbreviation='bkc',
- short_desc=N_("make backup as a copy, don't rename the file"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_bkc',
- defaults={
- condition='UNIX',
- if_true="auto",
- if_false="auto"
+ Resetting this option will:
+ - Reset the 'rightleft' option.
+ - Disable the use of 'keymap' (without changing its value).
+ Note that 'arabicshape' and 'delcombine' are not reset (it is a global
+ option).
+ Also see |arabic.txt|.
+ ]=],
+ full_name = 'arabic',
+ redraw = { 'curswant' },
+ scope = { 'window' },
+ short_desc = N_('Arabic as a default second language'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'arshape',
+ defaults = { if_true = true },
+ desc = [=[
+ When on and 'termbidi' is off, the required visual character
+ corrections that need to take place for displaying the Arabic language
+ take effect. Shaping, in essence, gets enabled; the term is a broad
+ one which encompasses:
+ a) the changing/morphing of characters based on their location
+ within a word (initial, medial, final and stand-alone).
+ b) the enabling of the ability to compose characters
+ c) the enabling of the required combining of some characters
+ When disabled the display shows each character's true stand-alone
+ form.
+ Arabic is a complex language which requires other settings, for
+ further details see |arabic.txt|.
+ ]=],
+ full_name = 'arabicshape',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('do shaping for Arabic characters'),
+ type = 'bool',
+ varname = 'p_arshape',
+ },
+ {
+ abbreviation = 'ari',
+ defaults = { if_true = false },
+ desc = [=[
+ Allow CTRL-_ in Insert and Command-line mode. This is default off, to
+ avoid that users that accidentally type CTRL-_ instead of SHIFT-_ get
+ into reverse Insert mode, and don't know how to get out. See
+ 'revins'.
+ ]=],
+ full_name = 'allowrevins',
+ scope = { 'global' },
+ short_desc = N_('allow CTRL-_ in Insert and Command-line mode'),
+ type = 'bool',
+ varname = 'p_ari',
+ },
+ {
+ abbreviation = 'ambw',
+ cb = 'did_set_ambiwidth',
+ defaults = { if_true = 'single' },
+ desc = [=[
+ Tells Vim what to do with characters with East Asian Width Class
+ Ambiguous (such as Euro, Registered Sign, Copyright Sign, Greek
+ letters, Cyrillic letters).
+
+ There are currently two possible values:
+ "single": Use the same width as characters in US-ASCII. This is
+ expected by most users.
+ "double": Use twice the width of ASCII characters.
+ *E834* *E835*
+ The value "double" cannot be used if 'listchars' or 'fillchars'
+ contains a character that would be double width. These errors may
+ also be given when calling setcellwidths().
+
+ The values are overruled for characters specified with
+ |setcellwidths()|.
+
+ There are a number of CJK fonts for which the width of glyphs for
+ those characters are solely based on how many octets they take in
+ legacy/traditional CJK encodings. In those encodings, Euro,
+ Registered sign, Greek/Cyrillic letters are represented by two octets,
+ therefore those fonts have "wide" glyphs for them. This is also
+ true of some line drawing characters used to make tables in text
+ file. Therefore, when a CJK font is used for GUI Vim or
+ Vim is running inside a terminal (emulators) that uses a CJK font
+ (or Vim is run inside an xterm invoked with "-cjkwidth" option.),
+ this option should be set to "double" to match the width perceived
+ by Vim with the width of glyphs in the font. Perhaps it also has
+ to be set to "double" under CJK MS-Windows when the system locale is
+ set to one of CJK locales. See Unicode Standard Annex #11
+ (https://www.unicode.org/reports/tr11).
+ ]=],
+ expand_cb = 'expand_set_ambiwidth',
+ full_name = 'ambiwidth',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('what to do with Unicode chars of ambiguous width'),
+ type = 'string',
+ varname = 'p_ambw',
+ },
+ {
+ abbreviation = 'acd',
+ cb = 'did_set_autochdir',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, Vim will change the current working directory whenever you
+ open a file, switch buffers, delete a buffer or open/close a window.
+ It will change to the directory containing the file which was opened
+ or selected. When a buffer has no name it also has no directory, thus
+ the current directory won't change when navigating to it.
+ Note: When this option is on some plugins may not work.
+ ]=],
+ full_name = 'autochdir',
+ scope = { 'global' },
+ short_desc = N_('change directory to the file in the current window'),
+ type = 'bool',
+ varname = 'p_acd',
+ },
+ {
+ abbreviation = 'ai',
+ defaults = { if_true = true },
+ desc = [=[
+ Copy indent from current line when starting a new line (typing <CR>
+ in Insert mode or when using the "o" or "O" command). If you do not
+ type anything on the new line except <BS> or CTRL-D and then type
+ <Esc>, CTRL-O or <CR>, the indent is deleted again. Moving the cursor
+ to another line has the same effect, unless the 'I' flag is included
+ in 'cpoptions'.
+ When autoindent is on, formatting (with the "gq" command or when you
+ reach 'textwidth' in Insert mode) uses the indentation of the first
+ line.
+ When 'smartindent' or 'cindent' is on the indent is changed in
+ a different way.
+ ]=],
+ full_name = 'autoindent',
+ scope = { 'buffer' },
+ short_desc = N_('take indent for new line from previous line'),
+ type = 'bool',
+ varname = 'p_ai',
+ },
+ {
+ abbreviation = 'ar',
+ defaults = { if_true = true },
+ desc = [=[
+ When a file has been detected to have been changed outside of Vim and
+ it has not been changed inside of Vim, automatically read it again.
+ When the file has been deleted this is not done, so you have the text
+ from before it was deleted. When it appears again then it is read.
+ |timestamp|
+ If this option has a local value, use this command to switch back to
+ using the global value: >
+ :set autoread<
+ <
+ ]=],
+ full_name = 'autoread',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('autom. read file when changed outside of Vim'),
+ type = 'bool',
+ varname = 'p_ar',
+ },
+ {
+ abbreviation = 'aw',
+ defaults = { if_true = false },
+ desc = [=[
+ Write the contents of the file, if it has been modified, on each
+ `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`,
+ `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when
+ a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one
+ to another file.
+ A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is
+ set to "hide" and `:next` is used.
+ Note that for some commands the 'autowrite' option is not used, see
+ 'autowriteall' for that.
+ Some buffers will not be written, specifically when 'buftype' is
+ "nowrite", "nofile", "terminal" or "prompt".
+ USE WITH CARE: If you make temporary changes to a buffer that you
+ don't want to be saved this option may cause it to be saved anyway.
+ Renaming the buffer with ":file {name}" may help avoid this.
+ ]=],
+ full_name = 'autowrite',
+ scope = { 'global' },
+ short_desc = N_('automatically write file if changed'),
+ type = 'bool',
+ varname = 'p_aw',
+ },
+ {
+ abbreviation = 'awa',
+ defaults = { if_true = false },
+ desc = [=[
+ Like 'autowrite', but also used for commands ":edit", ":enew", ":quit",
+ ":qall", ":exit", ":xit", ":recover" and closing the Vim window.
+ Setting this option also implies that Vim behaves like 'autowrite' has
+ been set.
+ ]=],
+ full_name = 'autowriteall',
+ scope = { 'global' },
+ short_desc = N_("as 'autowrite', but works with more commands"),
+ type = 'bool',
+ varname = 'p_awa',
+ },
+ {
+ abbreviation = 'bg',
+ cb = 'did_set_background',
+ defaults = { if_true = 'dark' },
+ desc = [=[
+ When set to "dark" or "light", adjusts the default color groups for
+ that background type. The |TUI| or other UI sets this on startup
+ (triggering |OptionSet|) if it can detect the background color.
+
+ This option does NOT change the background color, it tells Nvim what
+ the "inherited" (terminal/GUI) background looks like.
+ See |:hi-normal| if you want to set the background color explicitly.
+ *g:colors_name*
+ When a color scheme is loaded (the "g:colors_name" variable is set)
+ setting 'background' will cause the color scheme to be reloaded. If
+ the color scheme adjusts to the value of 'background' this will work.
+ However, if the color scheme sets 'background' itself the effect may
+ be undone. First delete the "g:colors_name" variable when needed.
+
+ Normally this option would be set in the vimrc file. Possibly
+ depending on the terminal name. Example: >
+ :if $TERM ==# "xterm"
+ : set background=dark
+ :endif
+ < When this option is set, the default settings for the highlight groups
+ will change. To use other settings, place ":highlight" commands AFTER
+ the setting of the 'background' option.
+ This option is also used in the "$VIMRUNTIME/syntax/syntax.vim" file
+ to select the colors for syntax highlighting. After changing this
+ option, you must load syntax.vim again to see the result. This can be
+ done with ":syntax on".
+ ]=],
+ expand_cb = 'expand_set_background',
+ full_name = 'background',
+ scope = { 'global' },
+ short_desc = N_('"dark" or "light", used for highlight colors'),
+ type = 'string',
+ varname = 'p_bg',
+ },
+ {
+ abbreviation = 'bs',
+ cb = 'did_set_backspace',
+ defaults = { if_true = 'indent,eol,start' },
+ deny_duplicates = true,
+ desc = [=[
+ Influences the working of <BS>, <Del>, CTRL-W and CTRL-U in Insert
+ mode. This is a list of items, separated by commas. Each item allows
+ a way to backspace over something:
+ value effect ~
+ indent allow backspacing over autoindent
+ eol allow backspacing over line breaks (join lines)
+ start allow backspacing over the start of insert; CTRL-W and CTRL-U
+ stop once at the start of insert.
+ nostop like start, except CTRL-W and CTRL-U do not stop at the start of
+ insert.
+
+ When the value is empty, Vi compatible backspacing is used, none of
+ the ways mentioned for the items above are possible.
+ ]=],
+ expand_cb = 'expand_set_backspace',
+ full_name = 'backspace',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('how backspace works at start of line'),
+ type = 'string',
+ varname = 'p_bs',
+ },
+ {
+ abbreviation = 'bk',
+ defaults = { if_true = false },
+ desc = [=[
+ Make a backup before overwriting a file. Leave it around after the
+ file has been successfully written. If you do not want to keep the
+ backup file, but you do want a backup while the file is being
+ written, reset this option and set the 'writebackup' option (this is
+ the default). If you do not want a backup file at all reset both
+ options (use this if your file system is almost full). See the
+ |backup-table| for more explanations.
+ When the 'backupskip' pattern matches, a backup is not made anyway.
+ When 'patchmode' is set, the backup may be renamed to become the
+ oldest version of a file.
+ ]=],
+ full_name = 'backup',
+ scope = { 'global' },
+ short_desc = N_('keep backup file after overwriting a file'),
+ type = 'bool',
+ varname = 'p_bk',
+ },
+ {
+ abbreviation = 'bkc',
+ cb = 'did_set_backupcopy',
+ defaults = { condition = 'UNIX', if_false = 'auto', if_true = 'auto' },
+ deny_duplicates = true,
+ desc = [=[
+ When writing a file and a backup is made, this option tells how it's
+ done. This is a comma-separated list of words.
+
+ The main values are:
+ "yes" make a copy of the file and overwrite the original one
+ "no" rename the file and write a new one
+ "auto" one of the previous, what works best
+
+ Extra values that can be combined with the ones above are:
+ "breaksymlink" always break symlinks when writing
+ "breakhardlink" always break hardlinks when writing
+
+ Making a copy and overwriting the original file:
+ - Takes extra time to copy the file.
+ + When the file has special attributes, is a (hard/symbolic) link or
+ has a resource fork, all this is preserved.
+ - When the file is a link the backup will have the name of the link,
+ not of the real file.
+
+ Renaming the file and writing a new one:
+ + It's fast.
+ - Sometimes not all attributes of the file can be copied to the new
+ file.
+ - When the file is a link the new file will not be a link.
+
+ The "auto" value is the middle way: When Vim sees that renaming the
+ file is possible without side effects (the attributes can be passed on
+ and the file is not a link) that is used. When problems are expected,
+ a copy will be made.
+
+ The "breaksymlink" and "breakhardlink" values can be used in
+ combination with any of "yes", "no" and "auto". When included, they
+ force Vim to always break either symbolic or hard links by doing
+ exactly what the "no" option does, renaming the original file to
+ become the backup and writing a new file in its place. This can be
+ useful for example in source trees where all the files are symbolic or
+ hard links and any changes should stay in the local source tree, not
+ be propagated back to the original source.
+ *crontab*
+ One situation where "no" and "auto" will cause problems: A program
+ that opens a file, invokes Vim to edit that file, and then tests if
+ the open file was changed (through the file descriptor) will check the
+ backup file instead of the newly created file. "crontab -e" is an
+ example.
+
+ When a copy is made, the original file is truncated and then filled
+ with the new text. This means that protection bits, owner and
+ symbolic links of the original file are unmodified. The backup file,
+ however, is a new file, owned by the user who edited the file. The
+ group of the backup is set to the group of the original file. If this
+ fails, the protection bits for the group are made the same as for
+ others.
+
+ When the file is renamed, this is the other way around: The backup has
+ the same attributes of the original file, and the newly written file
+ is owned by the current user. When the file was a (hard/symbolic)
+ link, the new file will not! That's why the "auto" value doesn't
+ rename when the file is a link. The owner and group of the newly
+ written file will be set to the same ones as the original file, but
+ the system may refuse to do this. In that case the "auto" value will
+ again not rename the file.
+ ]=],
+ expand_cb = 'expand_set_backupcopy',
+ full_name = 'backupcopy',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_("make backup as a copy, don't rename the file"),
+ type = 'string',
+ varname = 'p_bkc',
+ },
+ {
+ abbreviation = 'bdir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directories for the backup file, separated with commas.
+ - The backup file will be created in the first directory in the list
+ where this is possible. If none of the directories exist Nvim will
+ attempt to create the last directory in the list.
+ - Empty means that no backup file will be created ('patchmode' is
+ impossible!). Writing may fail because of this.
+ - A directory "." means to put the backup file in the same directory
+ as the edited file.
+ - A directory starting with "./" (or ".\" for MS-Windows) means to put
+ the backup file relative to where the edited file is. The leading
+ "." is replaced with the path name of the edited file.
+ ("." inside a directory name has no special meaning).
+ - Spaces after the comma are ignored, other spaces are considered part
+ of the directory name. To have a space at the start of a directory
+ name, precede it with a backslash.
+ - To include a comma in a directory name precede it with a backslash.
+ - A directory name may end in an '/'.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators changed to percent '%' signs. This will
+ ensure file name uniqueness in the backup directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
+ - Environment variables are expanded |:set_env|.
+ - Careful with '\' characters, type one before a space, type two to
+ get one in the option (see |option-backslash|), for example: >
+ :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+ <
+ See also 'backup' and 'writebackup' options.
+ If you want to hide your backup files on Unix, consider this value: >
+ :set backupdir=./.backup,~/.backup,.,/tmp
+ < You must create a ".backup" directory in each directory and in your
+ home directory for this to work properly.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'backupdir',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories for the backup file'),
+ type = 'string',
+ varname = 'p_bdir',
+ },
+ {
+ abbreviation = 'bex',
+ cb = 'did_set_backupext_or_patchmode',
+ defaults = { if_true = '~' },
+ desc = [=[
+ String which is appended to a file name to make the name of the
+ backup file. The default is quite unusual, because this avoids
+ accidentally overwriting existing files with a backup file. You might
+ prefer using ".bak", but make sure that you don't have files with
+ ".bak" that you want to keep.
+ Only normal file name characters can be used; `/\*?[|<>` are illegal.
+
+ If you like to keep a lot of backups, you could use a BufWritePre
+ autocommand to change 'backupext' just before writing the file to
+ include a timestamp. >
+ :au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~'
+ < Use 'backupdir' to put the backup in a different directory.
+ ]=],
+ full_name = 'backupext',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('extension used for the backup file'),
+ tags = { 'E589' },
+ type = 'string',
+ varname = 'p_bex',
+ },
+ {
+ abbreviation = 'bsk',
+ defaults = {
+ if_true = '',
+ doc = [["$TMPDIR/*,$TMP/*,$TEMP/*"
+ Unix: "/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"
+ Mac: "/private/tmp/*,$TMPDIR/*,$TMP/*,$TEMP/*"]],
+ meta = '/tmp/*',
},
- },
- {
- full_name='backupdir', abbreviation='bdir',
- short_desc=N_("list of directories for the backup file"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_bdir',
- defaults={if_true=''}
- },
- {
- full_name='backupext', abbreviation='bex',
- short_desc=N_("extension used for the backup file"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_bex',
- defaults={if_true="~"}
- },
- {
- full_name='backupskip', abbreviation='bsk',
- short_desc=N_("no backup for files that match these patterns"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_bsk',
- defaults={if_true=""}
- },
- {
- full_name='belloff', abbreviation='bo',
- short_desc=N_("do not ring the bell for these reasons"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_bo',
- defaults={if_true="all"}
- },
- {
- full_name='binary', abbreviation='bin',
- short_desc=N_("read/write/edit file in binary mode"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_bin',
- defaults={if_true=false}
- },
- {
- full_name='bomb',
- short_desc=N_("a Byte Order Mark to the file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_bomb',
- defaults={if_true=false}
- },
- {
- full_name='breakat', abbreviation='brk',
- short_desc=N_("characters that may cause a line break"),
- type='string', list='flags', scope={'global'},
- redraw={'all_windows'},
- varname='p_breakat',
- defaults={if_true=" \t!@*-+;:,./?"}
- },
- {
- full_name='breakindent', abbreviation='bri',
- short_desc=N_("wrapped line repeats indent"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='breakindentopt', abbreviation='briopt',
- short_desc=N_("settings for 'breakindent'"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_buffer'},
- defaults={if_true=""},
- },
- {
- full_name='browsedir', abbreviation='bsdir',
- short_desc=N_("which directory to start browsing in"),
- type='string', scope={'global'},
- enable_if=false,
- },
- {
- full_name='bufhidden', abbreviation='bh',
- short_desc=N_("what to do when buffer is no longer in window"),
- type='string', scope={'buffer'},
- noglob=true,
- alloced=true,
- varname='p_bh',
- defaults={if_true=""}
- },
- {
- full_name='buflisted', abbreviation='bl',
- short_desc=N_("whether the buffer shows up in the buffer list"),
- type='bool', scope={'buffer'},
- noglob=true,
- varname='p_bl',
- defaults={if_true=1}
- },
- {
- full_name='buftype', abbreviation='bt',
- short_desc=N_("special type of buffer"),
- type='string', scope={'buffer'},
- noglob=true,
- alloced=true,
- varname='p_bt',
- defaults={if_true=""}
- },
- {
- full_name='casemap', abbreviation='cmp',
- short_desc=N_("specifies how case of letters is changed"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cmp',
- defaults={if_true="internal,keepascii"}
- },
- {
- full_name='cdhome', abbreviation='cdh',
- short_desc=N_(":cd without argument goes to the home directory"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_cdh',
- defaults={if_true=false}
- },
- {
- full_name='cdpath', abbreviation='cd',
- short_desc=N_("list of directories searched with \":cd\""),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- expand=true,
- secure=true,
- varname='p_cdpath',
- defaults={if_true=",,"}
- },
- {
- full_name='cedit',
- short_desc=N_("used to open the command-line window"),
- type='string', scope={'global'},
- varname='p_cedit',
- defaults={if_true=macros('CTRL_F_STR')}
- },
- {
- full_name='channel',
- short_desc=N_("Channel connected to the buffer"),
- type='number', scope={'buffer'},
- no_mkrc=true,
- nodefault=true,
- varname='p_channel',
- defaults={if_true=0}
- },
- {
- full_name='charconvert', abbreviation='ccv',
- short_desc=N_("expression for character encoding conversion"),
- type='string', scope={'global'},
- secure=true,
- varname='p_ccv',
- defaults={if_true=""}
- },
- {
- full_name='cindent', abbreviation='cin',
- short_desc=N_("do C program indenting"),
- type='bool', scope={'buffer'},
- varname='p_cin',
- defaults={if_true=false}
- },
- {
- full_name='cinkeys', abbreviation='cink',
- short_desc=N_("keys that trigger indent when 'cindent' is set"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cink',
- defaults={if_true=indentkeys_default}
- },
- {
- full_name='cinoptions', abbreviation='cino',
- short_desc=N_("how to do indenting when 'cindent' is set"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cino',
- defaults={if_true=""}
- },
- {
- full_name='cinwords', abbreviation='cinw',
- short_desc=N_("words where 'si' and 'cin' add an indent"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cinw',
- defaults={if_true="if,else,while,do,for,switch"}
- },
- {
- full_name='cinscopedecls', abbreviation='cinsd',
- short_desc=N_("words that are recognized by 'cino-g'"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cinsd',
- defaults={if_true="public,protected,private"}
- },
- {
- full_name='clipboard', abbreviation='cb',
- short_desc=N_("use the clipboard as the unnamed register"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cb',
- defaults={if_true=""}
- },
- {
- full_name='cmdheight', abbreviation='ch',
- short_desc=N_("number of lines to use for the command-line"),
- type='number', scope={'global'},
- redraw={'all_windows'},
- varname='p_ch',
- defaults={if_true=1}
- },
- {
- full_name='cmdwinheight', abbreviation='cwh',
- short_desc=N_("height of the command-line window"),
- type='number', scope={'global'},
- varname='p_cwh',
- defaults={if_true=7}
- },
- {
- full_name='colorcolumn', abbreviation='cc',
- short_desc=N_("columns to highlight"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='columns', abbreviation='co',
- short_desc=N_("number of columns in the display"),
- type='number', scope={'global'},
- no_mkrc=true,
- varname='p_columns',
- defaults={if_true=macros('DFLT_COLS')}
- },
- {
- full_name='comments', abbreviation='com',
- short_desc=N_("patterns that can start a comment line"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- redraw={'curswant'},
- varname='p_com',
- defaults={if_true="s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-"}
- },
- {
- full_name='commentstring', abbreviation='cms',
- short_desc=N_("template for comments; used for fold marker"),
- type='string', scope={'buffer'},
- alloced=true,
- redraw={'curswant'},
- varname='p_cms',
- defaults={if_true="/*%s*/"}
- },
- {
- full_name='compatible', abbreviation='cp',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- -- pri_mkrc isn't needed here, optval_default()
- -- always returns TRUE for 'compatible'
- defaults={if_true=false}
- },
- {
- full_name='complete', abbreviation='cpt',
- short_desc=N_("specify how Insert mode completion works"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_cpt',
- defaults={if_true=".,w,b,u,t"}
- },
- {
- full_name='concealcursor', abbreviation='cocu',
- short_desc=N_("whether concealable text is hidden in cursor line"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='conceallevel', abbreviation='cole',
- short_desc=N_("whether concealable text is shown or hidden"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='completefunc', abbreviation='cfu',
- short_desc=N_("function to be used for Insert mode completion"),
- type='string', scope={'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_cfu',
- defaults={if_true=""}
- },
- {
- full_name='completeopt', abbreviation='cot',
- short_desc=N_("options for Insert mode completion"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_cot',
- defaults={if_true="menu,preview"}
- },
- {
- full_name='completeslash', abbreviation='csl',
- type='string', scope={'buffer'},
- varname='p_csl',
- enable_if='BACKSLASH_IN_FILENAME',
- defaults={if_true=""}
- },
- {
- full_name='confirm', abbreviation='cf',
- short_desc=N_("ask what to do about unsaved/read-only files"),
- type='bool', scope={'global'},
- varname='p_confirm',
- defaults={if_true=false}
- },
- {
- full_name='copyindent', abbreviation='ci',
- short_desc=N_("make 'autoindent' use existing indent structure"),
- type='bool', scope={'buffer'},
- varname='p_ci',
- defaults={if_true=false}
- },
- {
- full_name='cpoptions', abbreviation='cpo',
- short_desc=N_("flags for Vi-compatible behavior"),
- type='string', list='flags', scope={'global'},
- redraw={'all_windows'},
- varname='p_cpo',
- defaults={if_true=macros('CPO_VIM')}
- },
- {
- full_name='cursorbind', abbreviation='crb',
- short_desc=N_("move cursor in window as it moves in other windows"),
- type='bool', scope={'window'},
- pv_name='p_crbind',
- defaults={if_true=false}
- },
- {
- full_name='cursorcolumn', abbreviation='cuc',
- short_desc=N_("highlight the screen column of the cursor"),
- type='bool', scope={'window'},
- redraw={'current_window_only'},
- defaults={if_true=false}
- },
- {
- full_name='cursorline', abbreviation='cul',
- short_desc=N_("highlight the screen line of the cursor"),
- type='bool', scope={'window'},
- redraw={'current_window_only'},
- defaults={if_true=false}
- },
- {
- full_name='cursorlineopt', abbreviation='culopt',
- short_desc=N_("settings for 'cursorline'"),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- redraw={'current_window_only'},
- defaults={if_true="both"}
- },
- {
- full_name='debug',
- short_desc=N_("to \"msg\" to see all error messages"),
- type='string', scope={'global'},
- varname='p_debug',
- defaults={if_true=""}
- },
- {
- full_name='define', abbreviation='def',
- short_desc=N_("pattern to be used to find a macro definition"),
- type='string', scope={'global', 'buffer'},
- alloced=true,
- redraw={'curswant'},
- varname='p_def',
- defaults={if_true="^\\s*#\\s*define"}
- },
- {
- full_name='delcombine', abbreviation='deco',
- short_desc=N_("delete combining characters on their own"),
- type='bool', scope={'global'},
- varname='p_deco',
- defaults={if_true=false}
- },
- {
- full_name='dictionary', abbreviation='dict',
- short_desc=N_("list of file names used for keyword completion"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- normal_dname_chars=true,
- expand=true,
- varname='p_dict',
- defaults={if_true=""}
- },
- {
- full_name='diff',
- short_desc=N_("diff mode for the current window"),
- type='bool', scope={'window'},
- noglob=true,
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='diffexpr', abbreviation='dex',
- short_desc=N_("expression used to obtain a diff file"),
- type='string', scope={'global'},
- secure=true,
- redraw={'curswant'},
- varname='p_dex',
- defaults={if_true=""}
- },
- {
- full_name='diffopt', abbreviation='dip',
- short_desc=N_("options for using diff mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_dip',
- defaults={if_true="internal,filler,closeoff"}
- },
- {
- full_name='digraph', abbreviation='dg',
- short_desc=N_("enable the entering of digraphs in Insert mode"),
- type='bool', scope={'global'},
- varname='p_dg',
- defaults={if_true=false}
- },
- {
- full_name='directory', abbreviation='dir',
- short_desc=N_("list of directory names for the swap file"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_dir',
- defaults={if_true=''}
- },
- {
- full_name='display', abbreviation='dy',
- short_desc=N_("list of flags for how to display text"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'all_windows'},
- varname='p_dy',
- defaults={if_true="lastline"}
- },
- {
- full_name='eadirection', abbreviation='ead',
- short_desc=N_("in which direction 'equalalways' works"),
- type='string', scope={'global'},
- varname='p_ead',
- defaults={if_true="both"}
- },
- {
- full_name='edcompatible', abbreviation='ed',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='emoji', abbreviation='emo',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_emoji',
- defaults={if_true=true}
- },
- {
- full_name='encoding', abbreviation='enc',
- short_desc=N_("encoding used internally"),
- type='string', scope={'global'},
- deny_in_modelines=true,
- varname='p_enc',
- defaults={if_true=macros('ENC_DFLT')}
- },
- {
- full_name='endoffile', abbreviation='eof',
- short_desc=N_("write CTRL-Z for last line in file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_eof',
- defaults={if_true=false}
- },
- {
- full_name='endofline', abbreviation='eol',
- short_desc=N_("write <EOL> for last line in file"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_eol',
- defaults={if_true=true}
- },
- {
- full_name='equalalways', abbreviation='ea',
- short_desc=N_("windows are automatically made the same size"),
- type='bool', scope={'global'},
- varname='p_ea',
- defaults={if_true=true}
- },
- {
- full_name='equalprg', abbreviation='ep',
- short_desc=N_("external program to use for \"=\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_ep',
- defaults={if_true=""}
- },
- {
- full_name='errorbells', abbreviation='eb',
- short_desc=N_("ring the bell for error messages"),
- type='bool', scope={'global'},
- varname='p_eb',
- defaults={if_true=false}
- },
- {
- full_name='errorfile', abbreviation='ef',
- short_desc=N_("name of the errorfile for the QuickFix mode"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_ef',
- defaults={if_true=macros('DFLT_ERRORFILE')}
- },
- {
- full_name='errorformat', abbreviation='efm',
- short_desc=N_("description of the lines in the error file"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_efm',
- defaults={if_true=macros('DFLT_EFM')}
- },
- {
- full_name='eventignore', abbreviation='ei',
- short_desc=N_("autocommand events that are ignored"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ei',
- defaults={if_true=""}
- },
- {
- full_name='expandtab', abbreviation='et',
- short_desc=N_("use spaces when <Tab> is inserted"),
- type='bool', scope={'buffer'},
- varname='p_et',
- defaults={if_true=false}
- },
- {
- full_name='exrc', abbreviation='ex',
- short_desc=N_("read .nvimrc and .exrc in the current directory"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_exrc',
- defaults={if_true=false}
- },
- {
- full_name='fileencoding', abbreviation='fenc',
- short_desc=N_("file encoding for multi-byte text"),
- type='string', scope={'buffer'},
- no_mkrc=true,
- alloced=true,
- redraw={'statuslines', 'current_buffer'},
- varname='p_fenc',
- defaults={if_true=""}
- },
- {
- full_name='fileencodings', abbreviation='fencs',
- short_desc=N_("automatically detected character encodings"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_fencs',
- defaults={if_true="ucs-bom,utf-8,default,latin1"}
- },
- {
- full_name='fileformat', abbreviation='ff',
- short_desc=N_("file format used for file I/O"),
- type='string', scope={'buffer'},
- no_mkrc=true,
- alloced=true,
- redraw={'curswant', 'statuslines'},
- varname='p_ff',
- defaults={if_true=macros('DFLT_FF')}
- },
- {
- full_name='fileformats', abbreviation='ffs',
- short_desc=N_("automatically detected values for 'fileformat'"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ffs',
- defaults={if_true=macros('DFLT_FFS_VIM')}
- },
- {
- full_name='fileignorecase', abbreviation='fic',
- short_desc=N_("ignore case when using file names"),
- type='bool', scope={'global'},
- varname='p_fic',
- defaults={
- condition='CASE_INSENSITIVE_FILENAME',
- if_true=true,
- if_false=false,
- }
- },
- {
- full_name='filetype', abbreviation='ft',
- short_desc=N_("type of file, used for autocommands"),
- type='string', scope={'buffer'},
- noglob=true,
- normal_fname_chars=true,
- alloced=true,
- expand=true,
- varname='p_ft',
- defaults={if_true=""}
- },
- {
- full_name='fillchars', abbreviation='fcs',
- short_desc=N_("characters to use for displaying special items"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_fcs',
- defaults={if_true=''}
- },
- {
- full_name='fixendofline', abbreviation='fixeol',
- short_desc=N_("make sure last line in file has <EOL>"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_fixeol',
- defaults={if_true=true}
- },
- {
- full_name='foldclose', abbreviation='fcl',
- short_desc=N_("close a fold when the cursor leaves it"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'current_window'},
- varname='p_fcl',
- defaults={if_true=""}
- },
- {
- full_name='foldcolumn', abbreviation='fdc',
- short_desc=N_("width of the column used to indicate folds"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="0"}
- },
- {
- full_name='foldenable', abbreviation='fen',
- short_desc=N_("set to display all folds open"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=true}
- },
- {
- full_name='foldexpr', abbreviation='fde',
- short_desc=N_("expression used when 'foldmethod' is \"expr\""),
- type='string', scope={'window'},
- modelineexpr=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="0"}
- },
- {
- full_name='foldignore', abbreviation='fdi',
- short_desc=N_("ignore lines when 'foldmethod' is \"indent\""),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="#"}
- },
- {
- full_name='foldlevel', abbreviation='fdl',
- short_desc=N_("close folds with a level higher than this"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='foldlevelstart', abbreviation='fdls',
- short_desc=N_("'foldlevel' when starting to edit a file"),
- type='number', scope={'global'},
- redraw={'curswant'},
- varname='p_fdls',
- defaults={if_true=-1}
- },
- {
- full_name='foldmarker', abbreviation='fmr',
- short_desc=N_("markers used when 'foldmethod' is \"marker\""),
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="{{{,}}}"}
- },
- {
- full_name='foldmethod', abbreviation='fdm',
- short_desc=N_("folding type"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="manual"}
- },
- {
- full_name='foldminlines', abbreviation='fml',
- short_desc=N_("minimum number of lines for a fold to be closed"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=1}
- },
- {
- full_name='foldnestmax', abbreviation='fdn',
- short_desc=N_("maximum fold depth"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=20}
- },
- {
- full_name='foldopen', abbreviation='fdo',
- short_desc=N_("for which commands a fold will be opened"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'curswant'},
- varname='p_fdo',
- defaults={if_true="block,hor,mark,percent,quickfix,search,tag,undo"}
- },
- {
- full_name='foldtext', abbreviation='fdt',
- short_desc=N_("expression used to display for a closed fold"),
- type='string', scope={'window'},
- modelineexpr=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="foldtext()"}
- },
- {
- full_name='formatexpr', abbreviation='fex',
- short_desc=N_("expression used with \"gq\" command"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_fex',
- defaults={if_true=""}
- },
- {
- full_name='formatoptions', abbreviation='fo',
- short_desc=N_("how automatic formatting is to be done"),
- type='string', list='flags', scope={'buffer'},
- alloced=true,
- varname='p_fo',
- defaults={if_true=macros('DFLT_FO_VIM')}
- },
- {
- full_name='formatlistpat', abbreviation='flp',
- short_desc=N_("pattern used to recognize a list header"),
- type='string', scope={'buffer'},
- alloced=true,
- varname='p_flp',
- defaults={if_true="^\\s*\\d\\+[\\]:.)}\\t ]\\s*"}
- },
- {
- full_name='formatprg', abbreviation='fp',
- short_desc=N_("name of external program used with \"gq\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_fp',
- defaults={if_true=""}
- },
- {
- full_name='fsync', abbreviation='fs',
- short_desc=N_("whether to invoke fsync() after file write"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_fs',
- defaults={if_true=false}
- },
- {
- full_name='gdefault', abbreviation='gd',
- short_desc=N_("the \":substitute\" flag 'g' is default on"),
- type='bool', scope={'global'},
- varname='p_gd',
- defaults={if_true=false}
- },
- {
- full_name='grepformat', abbreviation='gfm',
- short_desc=N_("format of 'grepprg' output"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_gefm',
- defaults={if_true=macros('DFLT_GREPFORMAT')}
- },
- {
- full_name='grepprg', abbreviation='gp',
- short_desc=N_("program to use for \":grep\""),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_gp',
- defaults={
- condition='MSWIN',
- -- Add an extra file name so that grep will always
- -- insert a file name in the match line. */
- if_true="findstr /n $* nul",
- if_false="grep -n $* /dev/null"
- }
- },
- {
- full_name='guicursor', abbreviation='gcr',
- short_desc=N_("GUI: settings for cursor shape and blinking"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_guicursor',
- defaults={if_true="n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20"}
- },
- {
- full_name='guifont', abbreviation='gfn',
- short_desc=N_("GUI: Name(s) of font(s) to be used"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_guifont',
- redraw={'ui_option'},
- defaults={if_true=""}
- },
- {
- full_name='guifontwide', abbreviation='gfw',
- short_desc=N_("list of font names for double-wide characters"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- redraw={'ui_option'},
- varname='p_guifontwide',
- defaults={if_true=""}
- },
- {
- full_name='guioptions', abbreviation='go',
- short_desc=N_("GUI: Which components and options are used"),
- type='string', list='flags', scope={'global'},
- enable_if=false,
- },
- {
- full_name='guitablabel', abbreviation='gtl',
- short_desc=N_("GUI: custom label for a tab page"),
- type='string', scope={'global'},
- modelineexpr=true,
- redraw={'current_window'},
- enable_if=false,
- },
- {
- full_name='guitabtooltip', abbreviation='gtt',
- short_desc=N_("GUI: custom tooltip for a tab page"),
- type='string', scope={'global'},
- redraw={'current_window'},
- enable_if=false,
- },
- {
- full_name='helpfile', abbreviation='hf',
- short_desc=N_("full path name of the main help file"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_hf',
- defaults={if_true=macros('DFLT_HELPFILE')}
- },
- {
- full_name='helpheight', abbreviation='hh',
- short_desc=N_("minimum height of a new help window"),
- type='number', scope={'global'},
- varname='p_hh',
- defaults={if_true=20}
- },
- {
- full_name='helplang', abbreviation='hlg',
- short_desc=N_("preferred help languages"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_hlg',
- defaults={if_true=""}
- },
- {
- full_name='hidden', abbreviation='hid',
- short_desc=N_("don't unload buffer when it is |abandon|ed"),
- type='bool', scope={'global'},
- varname='p_hid',
- defaults={if_true=true}
- },
- {
- full_name='highlight', abbreviation='hl',
- short_desc=N_("sets highlighting mode for various occasions"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_hl',
- defaults={if_true=macros('HIGHLIGHT_INIT')}
- },
- {
- full_name='history', abbreviation='hi',
- short_desc=N_("number of command-lines that are remembered"),
- type='number', scope={'global'},
- varname='p_hi',
- defaults={if_true=10000}
- },
- {
- full_name='hkmap', abbreviation='hk',
- short_desc=N_("Hebrew keyboard mapping"),
- type='bool', scope={'global'},
- varname='p_hkmap',
- defaults={if_true=false}
- },
- {
- full_name='hkmapp', abbreviation='hkp',
- short_desc=N_("phonetic Hebrew keyboard mapping"),
- type='bool', scope={'global'},
- varname='p_hkmapp',
- defaults={if_true=false}
- },
- {
- full_name='hlsearch', abbreviation='hls',
- short_desc=N_("highlight matches with last search pattern"),
- type='bool', scope={'global'},
- redraw={'all_windows'},
- varname='p_hls',
- defaults={if_true=true}
- },
- {
- full_name='icon',
- short_desc=N_("Vim set the text of the window icon"),
- type='bool', scope={'global'},
- varname='p_icon',
- defaults={if_true=false}
- },
- {
- full_name='iconstring',
- short_desc=N_("to use for the Vim icon text"),
- type='string', scope={'global'},
- modelineexpr=true,
- varname='p_iconstring',
- defaults={if_true=""}
- },
- {
- full_name='ignorecase', abbreviation='ic',
- short_desc=N_("ignore case in search patterns"),
- type='bool', scope={'global'},
- varname='p_ic',
- defaults={if_true=false}
- },
- {
- full_name='imcmdline', abbreviation='imc',
- short_desc=N_("use IM when starting to edit a command line"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='imdisable', abbreviation='imd',
- short_desc=N_("do not use the IM in any mode"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='iminsert', abbreviation='imi',
- short_desc=N_("use :lmap or IM in Insert mode"),
- type='number', scope={'buffer'},
- varname='p_iminsert', pv_name='p_imi',
- defaults={
- if_true=macros('B_IMODE_NONE'),
- }
- },
- {
- full_name='imsearch', abbreviation='ims',
- short_desc=N_("use :lmap or IM when typing a search pattern"),
- type='number', scope={'buffer'},
- varname='p_imsearch', pv_name='p_ims',
- defaults={
- if_true=macros('B_IMODE_USE_INSERT'),
- }
- },
- {
- full_name='inccommand', abbreviation='icm',
- short_desc=N_("Live preview of substitution"),
- type='string', scope={'global'},
- varname='p_icm',
- defaults={if_true="nosplit"}
- },
- {
- full_name='include', abbreviation='inc',
- short_desc=N_("pattern to be used to find an include file"),
- type='string', scope={'global', 'buffer'},
- alloced=true,
- varname='p_inc',
- defaults={if_true="^\\s*#\\s*include"}
- },
- {
- full_name='includeexpr', abbreviation='inex',
- short_desc=N_("expression used to process an include line"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_inex',
- defaults={if_true=""}
- },
- {
- full_name='incsearch', abbreviation='is',
- short_desc=N_("highlight match while typing search pattern"),
- type='bool', scope={'global'},
- varname='p_is',
- defaults={if_true=true}
- },
- {
- full_name='indentexpr', abbreviation='inde',
- short_desc=N_("expression used to obtain the indent of a line"),
- type='string', scope={'buffer'},
- modelineexpr=true,
- alloced=true,
- varname='p_inde',
- defaults={if_true=""}
- },
- {
- full_name='indentkeys', abbreviation='indk',
- short_desc=N_("keys that trigger indenting with 'indentexpr'"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_indk',
- defaults={if_true=indentkeys_default}
- },
- {
- full_name='infercase', abbreviation='inf',
- short_desc=N_("adjust case of match for keyword completion"),
- type='bool', scope={'buffer'},
- varname='p_inf',
- defaults={if_true=false}
- },
- {
- full_name='insertmode', abbreviation='im',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='isfname', abbreviation='isf',
- short_desc=N_("characters included in file names and pathnames"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_isf',
- defaults={
- condition='BACKSLASH_IN_FILENAME',
- -- Excluded are: & and ^ are special in cmd.exe
- -- ( and ) are used in text separating fnames */
- if_true="@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],:,@-@,!,~,=",
- if_false="@,48-57,/,.,-,_,+,,,#,$,%,~,="
- }
- },
- {
- full_name='isident', abbreviation='isi',
- short_desc=N_("characters included in identifiers"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- varname='p_isi',
- defaults={
- condition='MSWIN',
- if_true="@,48-57,_,128-167,224-235",
- if_false="@,48-57,_,192-255"
- }
- },
- {
- full_name='iskeyword', abbreviation='isk',
- short_desc=N_("characters included in keywords"),
- type='string', list='comma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_isk',
- defaults={if_true="@,48-57,_,192-255"}
- },
- {
- full_name='isprint', abbreviation='isp',
- short_desc=N_("printable characters"),
- type='string', list='comma', scope={'global'},
- deny_duplicates=true,
- redraw={'all_windows'},
- varname='p_isp',
- defaults={if_true="@,161-255"
- }
- },
- {
- full_name='joinspaces', abbreviation='js',
- short_desc=N_("two spaces after a period with a join command"),
- type='bool', scope={'global'},
- varname='p_js',
- defaults={if_true=false}
- },
- {
- full_name='jumpoptions', abbreviation='jop',
- short_desc=N_("Controls the behavior of the jumplist"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_jop',
- defaults={if_true=''}
- },
- {
- full_name='keymap', abbreviation='kmp',
- short_desc=N_("name of a keyboard mapping"),
- type='string', scope={'buffer'},
- normal_fname_chars=true,
- pri_mkrc=true,
- alloced=true,
- redraw={'statuslines', 'current_buffer'},
- varname='p_keymap', pv_name='p_kmap',
- defaults={if_true=""}
- },
- {
- full_name='keymodel', abbreviation='km',
- short_desc=N_("enable starting/stopping selection with keys"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_km',
- defaults={if_true=""}
- },
- {
- full_name='keywordprg', abbreviation='kp',
- short_desc=N_("program to use for the \"K\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_kp',
- defaults={
- if_true=":Man",
- }
- },
- {
- full_name='langmap', abbreviation='lmap',
- short_desc=N_("alphabetic characters for other language mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- varname='p_langmap',
- defaults={if_true=""}
- },
- {
- full_name='langmenu', abbreviation='lm',
- short_desc=N_("language to be used for the menus"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_lm',
- defaults={if_true=""}
- },
- {
- full_name='langnoremap', abbreviation='lnr',
- short_desc=N_("do not apply 'langmap' to mapped characters"),
- type='bool', scope={'global'},
- varname='p_lnr',
- defaults={if_true=true}
- },
- {
- full_name='langremap', abbreviation='lrm',
- short_desc=N_('No description'),
- type='bool', scope={'global'},
- varname='p_lrm',
- defaults={if_true=false}
- },
- {
- full_name='laststatus', abbreviation='ls',
- short_desc=N_("tells when last window has status lines"),
- type='number', scope={'global'},
- redraw={'all_windows'},
- varname='p_ls',
- defaults={if_true=2}
- },
- {
- full_name='lazyredraw', abbreviation='lz',
- short_desc=N_("don't redraw while executing macros"),
- type='bool', scope={'global'},
- varname='p_lz',
- defaults={if_true=false}
- },
- {
- full_name='linebreak', abbreviation='lbr',
- short_desc=N_("wrap long lines at a blank"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='lines',
- short_desc=N_("of lines in the display"),
- type='number', scope={'global'},
- no_mkrc=true,
- varname='p_lines',
- defaults={if_true=macros('DFLT_ROWS')}
- },
- {
- full_name='linespace', abbreviation='lsp',
- short_desc=N_("number of pixel lines to use between characters"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_linespace',
- defaults={if_true=0}
- },
- {
- full_name='lisp',
- short_desc=N_("indenting for Lisp"),
- type='bool', scope={'buffer'},
- varname='p_lisp',
- defaults={if_true=false}
- },
- {
- full_name='lispoptions', abbreviation='lop',
- short_desc=N_("options for lisp indenting"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- varname='p_lop', pv_name='p_lop',
- defaults={if_true=''}
- },
- {
- full_name='lispwords', abbreviation='lw',
- short_desc=N_("words that change how lisp indenting works"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- varname='p_lispwords', pv_name='p_lw',
- defaults={if_true=macros('LISPWORD_VALUE')}
- },
- {
- full_name='list',
- short_desc=N_("<Tab> and <EOL>"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='listchars', abbreviation='lcs',
- short_desc=N_("characters for displaying in list mode"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- varname='p_lcs',
- defaults={if_true="tab:> ,trail:-,nbsp:+"}
- },
- {
- full_name='loadplugins', abbreviation='lpl',
- short_desc=N_("load plugin scripts when starting up"),
- type='bool', scope={'global'},
- varname='p_lpl',
- defaults={if_true=true}
- },
- {
- full_name='magic',
- short_desc=N_("special characters in search patterns"),
- type='bool', scope={'global'},
- varname='p_magic',
- defaults={if_true=true}
- },
- {
- full_name='makeef', abbreviation='mef',
- short_desc=N_("name of the errorfile for \":make\""),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_mef',
- defaults={if_true=""}
- },
- {
- full_name='makeencoding', abbreviation='menc',
- short_desc=N_("Converts the output of external commands"),
- type='string', scope={'global', 'buffer'},
- varname='p_menc',
- defaults={if_true=""}
- },
- {
- full_name='makeprg', abbreviation='mp',
- short_desc=N_("program to use for the \":make\" command"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- expand=true,
- varname='p_mp',
- defaults={if_true="make"}
- },
- {
- full_name='matchpairs', abbreviation='mps',
- short_desc=N_("pairs of characters that \"%\" can match"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_mps',
- defaults={if_true="(:),{:},[:]"}
- },
- {
- full_name='matchtime', abbreviation='mat',
- short_desc=N_("tenths of a second to show matching paren"),
- type='number', scope={'global'},
- varname='p_mat',
- defaults={if_true=5}
- },
- {
- full_name='maxcombine', abbreviation='mco',
- short_desc=N_("maximum nr of combining characters displayed"),
- type='number', scope={'global'},
- varname='p_mco',
- defaults={if_true=6}
- },
- {
- full_name='maxfuncdepth', abbreviation='mfd',
- short_desc=N_("maximum recursive depth for user functions"),
- type='number', scope={'global'},
- varname='p_mfd',
- defaults={if_true=100}
- },
- {
- full_name='maxmapdepth', abbreviation='mmd',
- short_desc=N_("maximum recursive depth for mapping"),
- type='number', scope={'global'},
- varname='p_mmd',
- defaults={if_true=1000}
- },
- {
- full_name='maxmempattern', abbreviation='mmp',
- short_desc=N_("maximum memory (in Kbyte) used for pattern search"),
- type='number', scope={'global'},
- varname='p_mmp',
- defaults={if_true=1000}
- },
- {
- full_name='menuitems', abbreviation='mis',
- short_desc=N_("maximum number of items in a menu"),
- type='number', scope={'global'},
- varname='p_mis',
- defaults={if_true=25}
- },
- {
- full_name='mkspellmem', abbreviation='msm',
- short_desc=N_("memory used before |:mkspell| compresses the tree"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_msm',
- defaults={if_true="460000,2000,500"}
- },
- {
- full_name='modeline', abbreviation='ml',
- short_desc=N_("recognize modelines at start or end of file"),
- type='bool', scope={'buffer'},
- varname='p_ml',
- defaults={if_true=true}
- },
- {
- full_name='modelineexpr', abbreviation='mle',
- short_desc=N_("allow some options to be set in modeline"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_mle',
- defaults={if_true=false}
- },
- {
- full_name='modelines', abbreviation='mls',
- short_desc=N_("number of lines checked for modelines"),
- type='number', scope={'global'},
- varname='p_mls',
- defaults={if_true=5}
- },
- {
- full_name='modifiable', abbreviation='ma',
- short_desc=N_("changes to the text are not possible"),
- type='bool', scope={'buffer'},
- noglob=true,
- varname='p_ma',
- defaults={if_true=true}
- },
- {
- full_name='modified', abbreviation='mod',
- short_desc=N_("buffer has been modified"),
- type='bool', scope={'buffer'},
- no_mkrc=true,
- redraw={'statuslines'},
- varname='p_mod',
- defaults={if_true=false}
- },
- {
- full_name='more',
- short_desc=N_("listings when the whole screen is filled"),
- type='bool', scope={'global'},
- varname='p_more',
- defaults={if_true=true}
- },
- {
- full_name='mouse',
- short_desc=N_("the use of mouse clicks"),
- type='string', list='flags', scope={'global'},
- varname='p_mouse',
- defaults={if_true="nvi"}
- },
- {
- full_name='mousefocus', abbreviation='mousef',
- short_desc=N_("keyboard focus follows the mouse"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_mousef',
- defaults={if_true=false}
- },
- {
- full_name='mousehide', abbreviation='mh',
- short_desc=N_("hide mouse pointer while typing"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=true}
- },
- {
- full_name='mousemodel', abbreviation='mousem',
- short_desc=N_("changes meaning of mouse buttons"),
- type='string', scope={'global'},
- varname='p_mousem',
- defaults={if_true="popup_setpos"}
- },
- {
- full_name='mousemoveevent', abbreviation='mousemev',
- short_desc=N_("deliver mouse move events to input queue"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_mousemev',
- defaults={if_true=false}
- },
- {
- full_name='mousescroll',
- short_desc=N_("amount to scroll by when scrolling with a mouse"),
- type='string', list='comma', scope={'global'},
- vi_def=true,
- varname='p_mousescroll',
- defaults={if_true="ver:3,hor:6"}
- },
- {
- full_name='mouseshape', abbreviation='mouses',
- short_desc=N_("shape of the mouse pointer in different modes"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- enable_if=false,
- },
- {
- full_name='mousetime', abbreviation='mouset',
- short_desc=N_("max time between mouse double-click"),
- type='number', scope={'global'},
- varname='p_mouset',
- defaults={if_true=500}
- },
- {
- full_name='nrformats', abbreviation='nf',
- short_desc=N_("number formats recognized for CTRL-A command"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_nf',
- defaults={if_true="bin,hex"}
- },
- {
- full_name='number', abbreviation='nu',
- short_desc=N_("print the line number in front of each line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='numberwidth', abbreviation='nuw',
- short_desc=N_("number of columns used for the line number"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=4}
- },
- {
- full_name='omnifunc', abbreviation='ofu',
- short_desc=N_("function for filetype-specific completion"),
- type='string', scope={'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_ofu',
- defaults={if_true=""}
- },
- {
- full_name='opendevice', abbreviation='odev',
- short_desc=N_("allow reading/writing devices on MS-Windows"),
- type='bool', scope={'global'},
- enable_if=false,
- defaults={if_true=false}
- },
- {
- full_name='operatorfunc', abbreviation='opfunc',
- short_desc=N_("function to be called for |g@| operator"),
- type='string', scope={'global'},
- secure=true,
- func=true,
- varname='p_opfunc',
- defaults={if_true=""}
- },
- {
- full_name='packpath', abbreviation='pp',
- short_desc=N_("list of directories used for packages"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_pp',
- defaults={if_true=''}
- },
- {
- full_name='paragraphs', abbreviation='para',
- short_desc=N_("nroff macros that separate paragraphs"),
- type='string', scope={'global'},
- varname='p_para',
- defaults={if_true="IPLPPPQPP TPHPLIPpLpItpplpipbp"}
- },
- {
- full_name='paste',
- short_desc=N_("pasting text"),
- type='bool', scope={'global'},
- pri_mkrc=true,
- varname='p_paste',
- defaults={if_true=false}
- },
- {
- full_name='pastetoggle', abbreviation='pt',
- short_desc=N_("key code that causes 'paste' to toggle"),
- type='string', scope={'global'},
- varname='p_pt',
- defaults={if_true=""}
- },
- {
- full_name='patchexpr', abbreviation='pex',
- short_desc=N_("expression used to patch a file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_pex',
- defaults={if_true=""}
- },
- {
- full_name='patchmode', abbreviation='pm',
- short_desc=N_("keep the oldest version of a file"),
- type='string', scope={'global'},
- normal_fname_chars=true,
- varname='p_pm',
- defaults={if_true=""}
- },
- {
- full_name='path', abbreviation='pa',
- short_desc=N_("list of directories searched with \"gf\" et.al."),
- type='string', list='comma', scope={'global', 'buffer'},
- deny_duplicates=true,
- expand=true,
- varname='p_path',
- defaults={if_true=".,/usr/include,,"}
- },
- {
- full_name='preserveindent', abbreviation='pi',
- short_desc=N_("preserve the indent structure when reindenting"),
- type='bool', scope={'buffer'},
- varname='p_pi',
- defaults={if_true=false}
- },
- {
- full_name='previewheight', abbreviation='pvh',
- short_desc=N_("height of the preview window"),
- type='number', scope={'global'},
- varname='p_pvh',
- defaults={if_true=12}
- },
- {
- full_name='previewwindow', abbreviation='pvw',
- short_desc=N_("identifies the preview window"),
- type='bool', scope={'window'},
- noglob=true,
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='prompt',
- short_desc=N_("enable prompt in Ex mode"),
- type='bool', scope={'global'},
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='pumblend', abbreviation='pb',
- short_desc=N_("Controls transparency level of popup menu"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_pb',
- defaults={if_true=0}
- },
- {
- full_name='pumheight', abbreviation='ph',
- short_desc=N_("maximum height of the popup menu"),
- type='number', scope={'global'},
- varname='p_ph',
- defaults={if_true=0}
- },
- {
- full_name='pumwidth', abbreviation='pw',
- short_desc=N_("minimum width of the popup menu"),
- type='number', scope={'global'},
- varname='p_pw',
- defaults={if_true=15}
- },
- {
- full_name='pyxversion', abbreviation='pyx',
- short_desc=N_("selects default python version to use"),
- type='number', scope={'global'},
- secure=true,
- varname='p_pyx',
- defaults={if_true=3}
- },
- {
- full_name='quickfixtextfunc', abbreviation='qftf',
- short_desc=N_("customize the quickfix window"),
- type='string', scope={'global'},
- secure=true,
- func=true,
- varname='p_qftf',
- defaults={if_true=""}
- },
- {
- full_name='quoteescape', abbreviation='qe',
- short_desc=N_("escape characters used in a string"),
- type='string', scope={'buffer'},
- alloced=true,
- varname='p_qe',
- defaults={if_true="\\"}
- },
- {
- full_name='readonly', abbreviation='ro',
- short_desc=N_("disallow writing the buffer"),
- type='bool', scope={'buffer'},
- noglob=true,
- redraw={'statuslines'},
- varname='p_ro',
- defaults={if_true=false}
- },
- {
- full_name='redrawdebug', abbreviation='rdb',
- short_desc=N_("Changes the way redrawing works (debug)"),
- type='string', list='onecomma', scope={'global'},
- varname='p_rdb',
- defaults={if_true=''}
- },
- {
- full_name='redrawtime', abbreviation='rdt',
- short_desc=N_("timeout for 'hlsearch' and |:match| highlighting"),
- type='number', scope={'global'},
- varname='p_rdt',
- defaults={if_true=2000}
- },
- {
- full_name='regexpengine', abbreviation='re',
- short_desc=N_("default regexp engine to use"),
- type='number', scope={'global'},
- varname='p_re',
- defaults={if_true=0}
- },
- {
- full_name='relativenumber', abbreviation='rnu',
- short_desc=N_("show relative line number in front of each line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='remap',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='report',
- short_desc=N_("for reporting nr. of lines changed"),
- type='number', scope={'global'},
- varname='p_report',
- defaults={if_true=2}
- },
- {
- full_name='revins', abbreviation='ri',
- short_desc=N_("inserting characters will work backwards"),
- type='bool', scope={'global'},
- varname='p_ri',
- defaults={if_true=false}
- },
- {
- full_name='rightleft', abbreviation='rl',
- short_desc=N_("window is right-to-left oriented"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='rightleftcmd', abbreviation='rlc',
- short_desc=N_("commands for which editing works right-to-left"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="search"}
- },
- {
- full_name='ruler', abbreviation='ru',
- short_desc=N_("show cursor line and column in the status line"),
- type='bool', scope={'global'},
- redraw={'statuslines'},
- varname='p_ru',
- defaults={if_true=true}
- },
- {
- full_name='rulerformat', abbreviation='ruf',
- short_desc=N_("custom format for the ruler"),
- type='string', scope={'global'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_ruf',
- defaults={if_true=""}
- },
- {
- full_name='runtimepath', abbreviation='rtp',
- short_desc=N_("list of directories used for runtime files"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_rtp',
- defaults={if_true=''}
- },
- {
- full_name='scroll', abbreviation='scr',
- short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
- type='number', scope={'window'},
- no_mkrc=true,
- pv_name='p_scroll',
- defaults={if_true=0}
- },
- {
- full_name='scrollback', abbreviation='scbk',
- short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
- type='number', scope={'buffer'},
- varname='p_scbk',
- redraw={'current_buffer'},
- defaults={if_true=-1}
- },
- {
- full_name='scrollbind', abbreviation='scb',
- short_desc=N_("scroll in window as other windows scroll"),
- type='bool', scope={'window'},
- pv_name='p_scbind',
- defaults={if_true=false}
- },
- {
- full_name='scrolljump', abbreviation='sj',
- short_desc=N_("minimum number of lines to scroll"),
- type='number', scope={'global'},
- varname='p_sj',
- defaults={if_true=1}
- },
- {
- full_name='scrolloff', abbreviation='so',
- short_desc=N_("minimum nr. of lines above and below cursor"),
- type='number', scope={'global', 'window'},
- varname='p_so',
- defaults={if_true=0}
- },
- {
- full_name='scrollopt', abbreviation='sbo',
- short_desc=N_("how 'scrollbind' should behave"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_sbo',
- defaults={if_true="ver,jump"}
- },
- {
- full_name='sections', abbreviation='sect',
- short_desc=N_("nroff macros that separate sections"),
- type='string', scope={'global'},
- varname='p_sections',
- defaults={if_true="SHNHH HUnhsh"}
- },
- {
- full_name='secure',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- secure=true,
- varname='p_secure',
- defaults={if_true=false}
- },
- {
- full_name='selection', abbreviation='sel',
- short_desc=N_("what type of selection to use"),
- type='string', scope={'global'},
- varname='p_sel',
- defaults={if_true="inclusive"}
- },
- {
- full_name='selectmode', abbreviation='slm',
- short_desc=N_("when to use Select mode instead of Visual mode"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_slm',
- defaults={if_true=""}
- },
- {
- full_name='sessionoptions', abbreviation='ssop',
- short_desc=N_("options for |:mksession|"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_ssop',
- defaults={if_true="blank,buffers,curdir,folds,help,tabpages,winsize,terminal"}
- },
- {
- full_name='shada', abbreviation='sd',
- short_desc=N_("use .shada file upon startup and exiting"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- varname='p_shada',
- defaults={if_true="!,'100,<50,s10,h"}
- },
- {
- full_name='shadafile', abbreviation='sdf',
- short_desc=N_("overrides the filename used for shada"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_shadafile',
- defaults={if_true=""}
- },
- {
- full_name='shell', abbreviation='sh',
- short_desc=N_("name of shell to use for external commands"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_sh',
- defaults={
- condition='MSWIN',
- if_true="cmd.exe",
- if_false="sh"
- }
- },
- {
- full_name='shellcmdflag', abbreviation='shcf',
- short_desc=N_("flag to shell to execute one command"),
- type='string', scope={'global'},
- secure=true,
- varname='p_shcf',
- defaults={
- condition='MSWIN',
- if_true="/s /c",
- if_false="-c"
- }
- },
- {
- full_name='shellpipe', abbreviation='sp',
- short_desc=N_("string to put output of \":make\" in error file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_sp',
- defaults={
- condition='MSWIN',
- if_true=">%s 2>&1",
- if_false="| tee",
- }
- },
- {
- full_name='shellquote', abbreviation='shq',
- short_desc=N_("quote character(s) for around shell command"),
- type='string', scope={'global'},
- secure=true,
- varname='p_shq',
- defaults={if_true=""}
- },
- {
- full_name='shellredir', abbreviation='srr',
- short_desc=N_("string to put output of filter in a temp file"),
- type='string', scope={'global'},
- secure=true,
- varname='p_srr',
- defaults={
- condition='MSWIN',
- if_true=">%s 2>&1",
- if_false=">"
- }
- },
- {
- full_name='shellslash', abbreviation='ssl',
- short_desc=N_("use forward slash for shell file names"),
- type='bool', scope={'global'},
- varname='p_ssl',
- enable_if='BACKSLASH_IN_FILENAME',
- defaults={if_true=false}
- },
- {
- full_name='shelltemp', abbreviation='stmp',
- short_desc=N_("whether to use a temp file for shell commands"),
- type='bool', scope={'global'},
- varname='p_stmp',
- defaults={if_true=true}
- },
- {
- full_name='shellxquote', abbreviation='sxq',
- short_desc=N_("like 'shellquote', but include redirection"),
- type='string', scope={'global'},
- secure=true,
- varname='p_sxq',
- defaults={
- condition='MSWIN',
- if_true="\"",
- if_false="",
- }
- },
- {
- full_name='shellxescape', abbreviation='sxe',
- short_desc=N_("characters to escape when 'shellxquote' is ("),
- type='string', scope={'global'},
- secure=true,
- varname='p_sxe',
- defaults={if_true=""}
- },
- {
- full_name='shiftround', abbreviation='sr',
- short_desc=N_("round indent to multiple of shiftwidth"),
- type='bool', scope={'global'},
- varname='p_sr',
- defaults={if_true=false}
- },
- {
- full_name='shiftwidth', abbreviation='sw',
- short_desc=N_("number of spaces to use for (auto)indent step"),
- type='number', scope={'buffer'},
- varname='p_sw',
- defaults={if_true=8}
- },
- {
- full_name='shortmess', abbreviation='shm',
- short_desc=N_("list of flags, reduce length of messages"),
- type='string', list='flags', scope={'global'},
- varname='p_shm',
- defaults={if_true="filnxtToOF"}
- },
- {
- full_name='showbreak', abbreviation='sbr',
- short_desc=N_("string to use at the start of wrapped lines"),
- type='string', scope={'global', 'window'},
- redraw={'all_windows'},
- varname='p_sbr',
- defaults={if_true=""}
- },
- {
- full_name='showcmd', abbreviation='sc',
- short_desc=N_("show (partial) command in status line"),
- type='bool', scope={'global'},
- varname='p_sc',
- defaults={if_true=true}
- },
- {
- full_name='showcmdloc', abbreviation='sloc',
- short_desc=N_("change location of partial command"),
- type='string', scope={'global'},
- varname='p_sloc',
- defaults={if_true="last"}
- },
- {
- full_name='showfulltag', abbreviation='sft',
- short_desc=N_("show full tag pattern when completing tag"),
- type='bool', scope={'global'},
- varname='p_sft',
- defaults={if_true=false}
- },
- {
- full_name='showmatch', abbreviation='sm',
- short_desc=N_("briefly jump to matching bracket if insert one"),
- type='bool', scope={'global'},
- varname='p_sm',
- defaults={if_true=false}
- },
- {
- full_name='showmode', abbreviation='smd',
- short_desc=N_("message on status line to show current mode"),
- type='bool', scope={'global'},
- varname='p_smd',
- defaults={if_true=true}
- },
- {
- full_name='showtabline', abbreviation='stal',
- short_desc=N_("tells when the tab pages line is displayed"),
- type='number', scope={'global'},
- redraw={'all_windows', 'ui_option'},
- varname='p_stal',
- defaults={if_true=1}
- },
- {
- full_name='sidescroll', abbreviation='ss',
- short_desc=N_("minimum number of columns to scroll horizontal"),
- type='number', scope={'global'},
- varname='p_ss',
- defaults={if_true=1}
- },
- {
- full_name='sidescrolloff', abbreviation='siso',
- short_desc=N_("min. nr. of columns to left and right of cursor"),
- type='number', scope={'global', 'window'},
- varname='p_siso',
- defaults={if_true=0}
- },
- {
- full_name='signcolumn', abbreviation='scl',
- short_desc=N_("when to display the sign column"),
- type='string', scope={'window'},
- alloced=true,
- redraw={'current_window'},
- defaults={if_true="auto"}
- },
- {
- full_name='smartcase', abbreviation='scs',
- short_desc=N_("no ignore case when pattern has uppercase"),
- type='bool', scope={'global'},
- varname='p_scs',
- defaults={if_true=false}
- },
- {
- full_name='smartindent', abbreviation='si',
- short_desc=N_("smart autoindenting for C programs"),
- type='bool', scope={'buffer'},
- varname='p_si',
- defaults={if_true=false}
- },
- {
- full_name='smarttab', abbreviation='sta',
- short_desc=N_("use 'shiftwidth' when inserting <Tab>"),
- type='bool', scope={'global'},
- varname='p_sta',
- defaults={if_true=true}
- },
- {
- full_name='softtabstop', abbreviation='sts',
- short_desc=N_("number of spaces that <Tab> uses while editing"),
- type='number', scope={'buffer'},
- varname='p_sts',
- defaults={if_true=0}
- },
- {
- full_name='spell',
- short_desc=N_("spell checking"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=false}
- },
- {
- full_name='spellcapcheck', abbreviation='spc',
- short_desc=N_("pattern to locate end of a sentence"),
- type='string', scope={'buffer'},
- alloced=true,
- redraw={'current_buffer'},
- varname='p_spc',
- defaults={if_true="[.?!]\\_[\\])'\" ]\\+"}
- },
- {
- full_name='spellfile', abbreviation='spf',
- short_desc=N_("files where |zg| and |zw| store words"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- secure=true,
- alloced=true,
- expand=true,
- varname='p_spf',
- defaults={if_true=""}
- },
- {
- full_name='spelllang', abbreviation='spl',
- short_desc=N_("language(s) to do spell checking for"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- expand=true,
- redraw={'current_buffer'},
- varname='p_spl',
- defaults={if_true="en"}
- },
- {
- full_name='spellsuggest', abbreviation='sps',
- short_desc=N_("method(s) used to suggest spelling corrections"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_sps',
- defaults={if_true="best"}
- },
- {
- full_name='spelloptions', abbreviation='spo',
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- secure=true,
- expand=true,
- varname='p_spo',
- redraw={'current_buffer'},
- defaults={if_true=""}
- },
- {
- full_name='splitbelow', abbreviation='sb',
- short_desc=N_("new window from split is below the current one"),
- type='bool', scope={'global'},
- varname='p_sb',
- defaults={if_true=false}
- },
- {
- full_name='splitkeep', abbreviation='spk',
- short_desc=N_("determines scroll behavior for split windows"),
- type='string', scope={'global'},
- varname='p_spk',
- defaults={if_true='cursor'}
- },
- {
- full_name='splitright', abbreviation='spr',
- short_desc=N_("new window is put right of the current one"),
- type='bool', scope={'global'},
- varname='p_spr',
- defaults={if_true=false}
- },
- {
- full_name='startofline', abbreviation='sol',
- short_desc=N_("commands move cursor to first non-blank in line"),
- type='bool', scope={'global'},
- vim=false,
- varname='p_sol',
- defaults={if_true=false}
- },
- {
- full_name='statuscolumn', abbreviation='stc',
- short_desc=N_("custom format for the status column"),
- type='string', scope={'window'},
- redraw={'current_window'},
- secure=true,
- alloced=true,
- defaults={if_true=""}
- },
- {
- full_name='statusline', abbreviation='stl',
- short_desc=N_("custom format for the status line"),
- type='string', scope={'global', 'window'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_stl',
- defaults={if_true=""}
- },
- {
- full_name='suffixes', abbreviation='su',
- short_desc=N_("suffixes that are ignored with multiple match"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_su',
- defaults={if_true=".bak,~,.o,.h,.info,.swp,.obj"}
- },
- {
- full_name='suffixesadd', abbreviation='sua',
- short_desc=N_("suffixes added when searching for a file"),
- type='string', list='onecomma', scope={'buffer'},
- deny_duplicates=true,
- alloced=true,
- varname='p_sua',
- defaults={if_true=""}
- },
- {
- full_name='swapfile', abbreviation='swf',
- short_desc=N_("whether to use a swapfile for a buffer"),
- type='bool', scope={'buffer'},
- redraw={'statuslines'},
- varname='p_swf',
- defaults={if_true=true}
- },
- {
- full_name='switchbuf', abbreviation='swb',
- short_desc=N_("sets behavior when switching to another buffer"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_swb',
- defaults={if_true="uselast"}
- },
- {
- full_name='synmaxcol', abbreviation='smc',
- short_desc=N_("maximum column to find syntax items"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_smc',
- defaults={if_true=3000}
- },
- {
- full_name='syntax', abbreviation='syn',
- short_desc=N_("syntax to be loaded for current buffer"),
- type='string', scope={'buffer'},
- noglob=true,
- normal_fname_chars=true,
- alloced=true,
- varname='p_syn',
- defaults={if_true=""}
- },
- {
- full_name='tagfunc', abbreviation='tfu',
- short_desc=N_("function used to perform tag searches"),
- type='string', scope={'buffer'},
- secure=true,
- func=true,
- varname='p_tfu',
- defaults={if_true=""}
- },
- {
- full_name='tabline', abbreviation='tal',
- short_desc=N_("custom format for the console tab pages line"),
- type='string', scope={'global'},
- modelineexpr=true,
- redraw={'tabline'},
- varname='p_tal',
- defaults={if_true=""}
- },
- {
- full_name='tabpagemax', abbreviation='tpm',
- short_desc=N_("maximum number of tab pages for |-p| and \"tab all\""),
- type='number', scope={'global'},
- varname='p_tpm',
- defaults={if_true=50}
- },
- {
- full_name='tabstop', abbreviation='ts',
- short_desc=N_("number of spaces that <Tab> in file uses"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_ts',
- defaults={if_true=8}
- },
- {
- full_name='tagbsearch', abbreviation='tbs',
- short_desc=N_("use binary searching in tags files"),
- type='bool', scope={'global'},
- varname='p_tbs',
- defaults={if_true=true}
- },
- {
- full_name='tagcase', abbreviation='tc',
- short_desc=N_("how to handle case when searching in tags files"),
- type='string', scope={'global', 'buffer'},
- varname='p_tc',
- defaults={if_true="followic"}
- },
- {
- full_name='taglength', abbreviation='tl',
- short_desc=N_("number of significant characters for a tag"),
- type='number', scope={'global'},
- varname='p_tl',
- defaults={if_true=0}
- },
- {
- full_name='tagrelative', abbreviation='tr',
- short_desc=N_("file names in tag file are relative"),
- type='bool', scope={'global'},
- varname='p_tr',
- defaults={if_true=true}
- },
- {
- full_name='tags', abbreviation='tag',
- short_desc=N_("list of file names used by the tag command"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- expand=true,
- varname='p_tags',
- defaults={if_true="./tags;,tags"}
- },
- {
- full_name='tagstack', abbreviation='tgst',
- short_desc=N_("push tags onto the tag stack"),
- type='bool', scope={'global'},
- varname='p_tgst',
- defaults={if_true=true}
- },
- {
- full_name='termbidi', abbreviation='tbidi',
- short_desc=N_("terminal takes care of bi-directionality"),
- type='bool', scope={'global'},
- varname='p_tbidi',
- defaults={if_true=false}
- },
- {
- full_name='termencoding', abbreviation='tenc',
- short_desc=N_("Terminal encoding"),
- type='string', scope={'global'},
- defaults={if_true=""}
- },
- {
- full_name='termguicolors', abbreviation='tgc',
- short_desc=N_("Terminal true color support"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_tgc',
- defaults={if_true=false}
- },
- {
- full_name='termpastefilter', abbreviation='tpf',
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_tpf',
- defaults={if_true="BS,HT,ESC,DEL"}
- },
- {
- full_name='terse',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- varname='p_force_off',
- defaults={if_true=false}
- },
- {
- full_name='textwidth', abbreviation='tw',
- short_desc=N_("maximum width of text that is being inserted"),
- type='number', scope={'buffer'},
- redraw={'current_buffer'},
- varname='p_tw',
- defaults={if_true=0}
- },
- {
- full_name='thesaurus', abbreviation='tsr',
- short_desc=N_("list of thesaurus files for keyword completion"),
- type='string', list='onecomma', scope={'global', 'buffer'},
- deny_duplicates=true,
- normal_dname_chars=true,
- expand=true,
- varname='p_tsr',
- defaults={if_true=""}
- },
- {
- full_name='thesaurusfunc', abbreviation='tsrfu',
- short_desc=N_("function used for thesaurus completion"),
- type='string', scope={'global', 'buffer'},
- secure=true,
- alloced=true,
- func=true,
- varname='p_tsrfu',
- defaults={if_true=""}
- },
- {
- full_name='tildeop', abbreviation='top',
- short_desc=N_("tilde command \"~\" behaves like an operator"),
- type='bool', scope={'global'},
- varname='p_to',
- defaults={if_true=false}
- },
- {
- full_name='timeout', abbreviation='to',
- short_desc=N_("time out on mappings and key codes"),
- type='bool', scope={'global'},
- varname='p_timeout',
- defaults={if_true=true}
- },
- {
- full_name='timeoutlen', abbreviation='tm',
- short_desc=N_("time out time in milliseconds"),
- type='number', scope={'global'},
- varname='p_tm',
- defaults={if_true=1000}
- },
- {
- full_name='title',
- short_desc=N_("Vim set the title of the window"),
- type='bool', scope={'global'},
- varname='p_title',
- defaults={if_true=false}
- },
- {
- full_name='titlelen',
- short_desc=N_("of 'columns' used for window title"),
- type='number', scope={'global'},
- varname='p_titlelen',
- defaults={if_true=85}
- },
- {
- full_name='titleold',
- short_desc=N_("title, restored when exiting"),
- type='string', scope={'global'},
- secure=true,
- no_mkrc=true,
- varname='p_titleold',
- defaults={if_true=""}
- },
- {
- full_name='titlestring',
- short_desc=N_("to use for the Vim window title"),
- type='string', scope={'global'},
- modelineexpr=true,
- varname='p_titlestring',
- defaults={if_true=""}
- },
- {
- full_name='ttimeout',
- short_desc=N_("out on mappings"),
- type='bool', scope={'global'},
- redraw={'ui_option'},
- varname='p_ttimeout',
- defaults={if_true=true}
- },
- {
- full_name='ttimeoutlen', abbreviation='ttm',
- short_desc=N_("time out time for key codes in milliseconds"),
- type='number', scope={'global'},
- redraw={'ui_option'},
- varname='p_ttm',
- defaults={if_true=50}
- },
- {
- full_name='ttyfast', abbreviation='tf',
- short_desc=N_("No description"),
- type='bool', scope={'global'},
- no_mkrc=true,
- varname='p_force_on',
- defaults={if_true=true}
- },
- {
- full_name='undodir', abbreviation='udir',
- short_desc=N_("where to store undo files"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- secure=true,
- expand='nodefault',
- varname='p_udir',
- defaults={if_true=''}
- },
- {
- full_name='undofile', abbreviation='udf',
- short_desc=N_("save undo information in a file"),
- type='bool', scope={'buffer'},
- varname='p_udf',
- defaults={if_true=false}
- },
- {
- full_name='undolevels', abbreviation='ul',
- short_desc=N_("maximum number of changes that can be undone"),
- type='number', scope={'global', 'buffer'},
- varname='p_ul',
- defaults={if_true=1000}
- },
- {
- full_name='undoreload', abbreviation='ur',
- short_desc=N_("max nr of lines to save for undo on a buffer reload"),
- type='number', scope={'global'},
- varname='p_ur',
- defaults={if_true=10000}
- },
- {
- full_name='updatecount', abbreviation='uc',
- short_desc=N_("after this many characters flush swap file"),
- type='number', scope={'global'},
- varname='p_uc',
- defaults={if_true=200}
- },
- {
- full_name='updatetime', abbreviation='ut',
- short_desc=N_("after this many milliseconds flush swap file"),
- type='number', scope={'global'},
- varname='p_ut',
- defaults={if_true=4000}
- },
- {
- full_name='varsofttabstop', abbreviation='vsts',
- short_desc=N_("list of numbers of spaces that <Tab> uses while editing"),
- type='string', list='comma', scope={'buffer'},
- varname='p_vsts',
- defaults={if_true=""}
- },
- {
- full_name='vartabstop', abbreviation='vts',
- short_desc=N_("list of numbers of spaces that <Tab> in file uses"),
- type='string', list='comma', scope={'buffer'},
- varname='p_vts',
- redraw={'current_buffer'},
- defaults={if_true=""}
- },
- {
- full_name='verbose', abbreviation='vbs',
- short_desc=N_("give informative messages"),
- type='number', scope={'global'},
- varname='p_verbose', redraw={'ui_option'},
- defaults={if_true=0}
- },
- {
- full_name='verbosefile', abbreviation='vfile',
- short_desc=N_("file to write messages in"),
- type='string', scope={'global'},
- secure=true,
- expand=true,
- varname='p_vfile',
- defaults={if_true=""}
- },
- {
- full_name='viewdir', abbreviation='vdir',
- short_desc=N_("directory where to store files with :mkview"),
- type='string', scope={'global'},
- secure=true,
- expand='nodefault',
- varname='p_vdir',
- defaults={if_true=''}
- },
- {
- full_name='viewoptions', abbreviation='vop',
- short_desc=N_("specifies what to save for :mkview"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_vop',
- defaults={if_true="folds,cursor,curdir"}
- },
- {
- -- Alias for "shada".
- full_name='viminfo', abbreviation='vi',
- short_desc=N_("Alias for shada"),
- type='string', scope={'global'}, nodefault=true,
- },
- {
- -- Alias for "shadafile".
- full_name='viminfofile', abbreviation='vif',
- short_desc=N_("Alias for shadafile instead"),
- type='string', scope={'global'}, nodefault=true,
- },
- {
- full_name='virtualedit', abbreviation='ve',
- short_desc=N_("when to use virtual editing"),
- type='string', list='onecomma', scope={'global', 'window'},
- deny_duplicates=true,
- redraw={'curswant'},
- varname='p_ve',
- defaults={if_true=""}
- },
- {
- full_name='visualbell', abbreviation='vb',
- short_desc=N_("use visual bell instead of beeping"),
- type='bool', scope={'global'},
- varname='p_vb',
- defaults={if_true=false}
- },
- {
- full_name='warn',
- short_desc=N_("for shell command when buffer was changed"),
- type='bool', scope={'global'},
- varname='p_warn',
- defaults={if_true=true}
- },
- {
- full_name='whichwrap', abbreviation='ww',
- short_desc=N_("allow specified keys to cross line boundaries"),
- type='string', list='flagscomma', scope={'global'},
- varname='p_ww',
- defaults={if_true="b,s"}
- },
- {
- full_name='wildchar', abbreviation='wc',
- short_desc=N_("command-line character for wildcard expansion"),
- type='number', scope={'global'},
- varname='p_wc',
- defaults={if_true=imacros('TAB')}
- },
- {
- full_name='wildcharm', abbreviation='wcm',
- short_desc=N_("like 'wildchar' but also works when mapped"),
- type='number', scope={'global'},
- varname='p_wcm',
- defaults={if_true=0}
- },
- {
- full_name='wildignore', abbreviation='wig',
- short_desc=N_("files matching these patterns are not completed"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_wig',
- defaults={if_true=""}
- },
- {
- full_name='wildignorecase', abbreviation='wic',
- short_desc=N_("ignore case when completing file names"),
- type='bool', scope={'global'},
- varname='p_wic',
- defaults={if_true=false}
- },
- {
- full_name='wildmenu', abbreviation='wmnu',
- short_desc=N_("use menu for command line completion"),
- type='bool', scope={'global'},
- varname='p_wmnu',
- defaults={if_true=true}
- },
- {
- full_name='wildmode', abbreviation='wim',
- short_desc=N_("mode for 'wildchar' command-line expansion"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=false,
- varname='p_wim',
- defaults={if_true="full"}
- },
- {
- full_name='wildoptions', abbreviation='wop',
- short_desc=N_("specifies how command line completion is done"),
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- varname='p_wop',
- defaults={if_true='pum,tagfile'}
- },
- {
- full_name='winaltkeys', abbreviation='wak',
- short_desc=N_("when the windows system handles ALT keys"),
- type='string', scope={'global'},
- varname='p_wak',
- defaults={if_true="menu"}
- },
- {
- full_name='winbar', abbreviation='wbr',
- short_desc=N_("custom format for the window bar"),
- type='string', scope={'global', 'window'},
- alloced=true,
- modelineexpr=true,
- redraw={'statuslines'},
- varname='p_wbr',
- defaults={if_true=""}
- },
- {
- full_name='winblend', abbreviation='winbl',
- short_desc=N_("Controls transparency level for floating windows"),
- type='number', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=0}
- },
- {
- full_name='winhighlight', abbreviation='winhl',
- short_desc=N_("Setup window-local highlights");
- type='string', list='onecomma', scope={'window'},
- deny_duplicates=true,
- alloced=true,
- redraw={'current_window'},
- defaults={if_true=""}
- },
- {
- full_name='window', abbreviation='wi',
- short_desc=N_("nr of lines to scroll for CTRL-F and CTRL-B"),
- type='number', scope={'global'},
- varname='p_window',
- defaults={if_true=0}
- },
- {
- full_name='winheight', abbreviation='wh',
- short_desc=N_("minimum number of lines for the current window"),
- type='number', scope={'global'},
- varname='p_wh',
- defaults={if_true=1}
- },
- {
- full_name='winfixheight', abbreviation='wfh',
- short_desc=N_("keep window height when opening/closing windows"),
- type='bool', scope={'window'},
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='winfixwidth', abbreviation='wfw',
- short_desc=N_("keep window width when opening/closing windows"),
- type='bool', scope={'window'},
- redraw={'statuslines'},
- defaults={if_true=false}
- },
- {
- full_name='winminheight', abbreviation='wmh',
- short_desc=N_("minimum number of lines for any window"),
- type='number', scope={'global'},
- varname='p_wmh',
- defaults={if_true=1}
- },
- {
- full_name='winminwidth', abbreviation='wmw',
- short_desc=N_("minimal number of columns for any window"),
- type='number', scope={'global'},
- varname='p_wmw',
- defaults={if_true=1}
- },
- {
- full_name='winwidth', abbreviation='wiw',
- short_desc=N_("minimal number of columns for current window"),
- type='number', scope={'global'},
- varname='p_wiw',
- defaults={if_true=20}
- },
- {
- full_name='wrap',
- short_desc=N_("lines wrap and continue on the next line"),
- type='bool', scope={'window'},
- redraw={'current_window'},
- defaults={if_true=true}
- },
- {
- full_name='wrapmargin', abbreviation='wm',
- short_desc=N_("chars from the right where wrapping starts"),
- type='number', scope={'buffer'},
- varname='p_wm',
- defaults={if_true=0}
- },
- {
- full_name='wrapscan', abbreviation='ws',
- short_desc=N_("searches wrap around the end of the file"),
- type='bool', scope={'global'},
- varname='p_ws',
- defaults={if_true=true}
- },
- {
- full_name='write',
- short_desc=N_("to a file is allowed"),
- type='bool', scope={'global'},
- varname='p_write',
- defaults={if_true=true}
- },
- {
- full_name='writeany', abbreviation='wa',
- short_desc=N_("write to file with no need for \"!\" override"),
- type='bool', scope={'global'},
- varname='p_wa',
- defaults={if_true=false}
- },
- {
- full_name='writebackup', abbreviation='wb',
- short_desc=N_("make a backup before overwriting a file"),
- type='bool', scope={'global'},
- varname='p_wb',
- defaults={if_true=true}
- },
- {
- full_name='writedelay', abbreviation='wd',
- short_desc=N_("delay this many msec for each char (for debug)"),
- type='number', scope={'global'},
- varname='p_wd',
- defaults={if_true=0}
- },
- }
+ deny_duplicates = true,
+ desc = [=[
+ A list of file patterns. When one of the patterns matches with the
+ name of the file which is written, no backup file is created. Both
+ the specified file name and the full path name of the file are used.
+ The pattern is used like with |:autocmd|, see |autocmd-pattern|.
+ Watch out for special characters, see |option-backslash|.
+ When $TMPDIR, $TMP or $TEMP is not defined, it is not used for the
+ default value. "/tmp/*" is only used for Unix.
+
+ WARNING: Not having a backup file means that when Vim fails to write
+ your buffer correctly and then, for whatever reason, Vim exits, you
+ lose both the original file and what you were writing. Only disable
+ backups if you don't care about losing the file.
+
+ Note that environment variables are not expanded. If you want to use
+ $HOME you must expand it explicitly, e.g.: >vim
+ :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
+
+ < Note that the default also makes sure that "crontab -e" works (when a
+ backup would be made by renaming the original file crontab won't see
+ the newly created file). Also see 'backupcopy' and |crontab|.
+ ]=],
+ full_name = 'backupskip',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('no backup for files that match these patterns'),
+ type = 'string',
+ varname = 'p_bsk',
+ },
+ {
+ abbreviation = 'bo',
+ cb = 'did_set_belloff',
+ defaults = { if_true = 'all' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies for which events the bell will not be rung. It is a comma-
+ separated list of items. For each item that is present, the bell
+ will be silenced. This is most useful to specify specific events in
+ insert mode to be silenced.
+
+ item meaning when present ~
+ all All events.
+ backspace When hitting <BS> or <Del> and deleting results in an
+ error.
+ cursor Fail to move around using the cursor keys or
+ <PageUp>/<PageDown> in |Insert-mode|.
+ complete Error occurred when using |i_CTRL-X_CTRL-K| or
+ |i_CTRL-X_CTRL-T|.
+ copy Cannot copy char from insert mode using |i_CTRL-Y| or
+ |i_CTRL-E|.
+ ctrlg Unknown Char after <C-G> in Insert mode.
+ error Other Error occurred (e.g. try to join last line)
+ (mostly used in |Normal-mode| or |Cmdline-mode|).
+ esc hitting <Esc> in |Normal-mode|.
+ hangul Ignored.
+ lang Calling the beep module for Lua/Mzscheme/TCL.
+ mess No output available for |g<|.
+ showmatch Error occurred for 'showmatch' function.
+ operator Empty region error |cpo-E|.
+ register Unknown register after <C-R> in |Insert-mode|.
+ shell Bell from shell output |:!|.
+ spell Error happened on spell suggest.
+ wildmode More matches in |cmdline-completion| available
+ (depends on the 'wildmode' setting).
+
+ This is most useful to fine tune when in Insert mode the bell should
+ be rung. For Normal mode and Ex commands, the bell is often rung to
+ indicate that an error occurred. It can be silenced by adding the
+ "error" keyword.
+ ]=],
+ expand_cb = 'expand_set_belloff',
+ full_name = 'belloff',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('do not ring the bell for these reasons'),
+ type = 'string',
+ varname = 'p_bo',
+ },
+ {
+ abbreviation = 'bin',
+ cb = 'did_set_binary',
+ defaults = { if_true = false },
+ desc = [=[
+ This option should be set before editing a binary file. You can also
+ use the |-b| Vim argument. When this option is switched on a few
+ options will be changed (also when it already was on):
+ 'textwidth' will be set to 0
+ 'wrapmargin' will be set to 0
+ 'modeline' will be off
+ 'expandtab' will be off
+ Also, 'fileformat' and 'fileformats' options will not be used, the
+ file is read and written like 'fileformat' was "unix" (a single <NL>
+ separates lines).
+ The 'fileencoding' and 'fileencodings' options will not be used, the
+ file is read without conversion.
+ NOTE: When you start editing a(nother) file while the 'bin' option is
+ on, settings from autocommands may change the settings again (e.g.,
+ 'textwidth'), causing trouble when editing. You might want to set
+ 'bin' again when the file has been loaded.
+ The previous values of these options are remembered and restored when
+ 'bin' is switched from on to off. Each buffer has its own set of
+ saved option values.
+ To edit a file with 'binary' set you can use the |++bin| argument.
+ This avoids you have to do ":set bin", which would have effect for all
+ files you edit.
+ When writing a file the <EOL> for the last line is only written if
+ there was one in the original file (normally Vim appends an <EOL> to
+ the last line if there is none; this would make the file longer). See
+ the 'endofline' option.
+ ]=],
+ full_name = 'binary',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('read/write/edit file in binary mode'),
+ type = 'bool',
+ varname = 'p_bin',
+ },
+ {
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = false },
+ desc = [=[
+ When writing a file and the following conditions are met, a BOM (Byte
+ Order Mark) is prepended to the file:
+ - this option is on
+ - the 'binary' option is off
+ - 'fileencoding' is "utf-8", "ucs-2", "ucs-4" or one of the little/big
+ endian variants.
+ Some applications use the BOM to recognize the encoding of the file.
+ Often used for UCS-2 files on MS-Windows. For other applications it
+ causes trouble, for example: "cat file1 file2" makes the BOM of file2
+ appear halfway through the resulting file. Gcc doesn't accept a BOM.
+ When Vim reads a file and 'fileencodings' starts with "ucs-bom", a
+ check for the presence of the BOM is done and 'bomb' set accordingly.
+ Unless 'binary' is set, it is removed from the first line, so that you
+ don't see it when editing. When you don't change the options, the BOM
+ will be restored when writing the file.
+ ]=],
+ full_name = 'bomb',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('a Byte Order Mark to the file'),
+ type = 'bool',
+ varname = 'p_bomb',
+ },
+ {
+ abbreviation = 'brk',
+ cb = 'did_set_breakat',
+ defaults = {
+ if_true = ' \t!@*-+;:,./?',
+ doc = '" ^I!@*-+;:,./?"',
+ },
+ desc = [=[
+ This option lets you choose which characters might cause a line
+ break if 'linebreak' is on. Only works for ASCII characters.
+ ]=],
+ full_name = 'breakat',
+ list = 'flags',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('characters that may cause a line break'),
+ type = 'string',
+ varname = 'p_breakat',
+ },
+ {
+ abbreviation = 'bri',
+ defaults = { if_true = false },
+ desc = [=[
+ Every wrapped line will continue visually indented (same amount of
+ space as the beginning of that line), thus preserving horizontal blocks
+ of text.
+ ]=],
+ full_name = 'breakindent',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('wrapped line repeats indent'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'briopt',
+ alloced = true,
+ cb = 'did_set_breakindentopt',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Settings for 'breakindent'. It can consist of the following optional
+ items and must be separated by a comma:
+ min:{n} Minimum text width that will be kept after
+ applying 'breakindent', even if the resulting
+ text should normally be narrower. This prevents
+ text indented almost to the right window border
+ occupying lot of vertical space when broken.
+ (default: 20)
+ shift:{n} After applying 'breakindent', the wrapped line's
+ beginning will be shifted by the given number of
+ characters. It permits dynamic French paragraph
+ indentation (negative) or emphasizing the line
+ continuation (positive).
+ (default: 0)
+ sbr Display the 'showbreak' value before applying the
+ additional indent.
+ (default: off)
+ list:{n} Adds an additional indent for lines that match a
+ numbered or bulleted list (using the
+ 'formatlistpat' setting).
+ list:-1 Uses the length of a match with 'formatlistpat'
+ for indentation.
+ (default: 0)
+ column:{n} Indent at column {n}. Will overrule the other
+ sub-options. Note: an additional indent may be
+ added for the 'showbreak' setting.
+ (default: off)
+ ]=],
+ expand_cb = 'expand_set_breakindentopt',
+ full_name = 'breakindentopt',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'window' },
+ short_desc = N_("settings for 'breakindent'"),
+ type = 'string',
+ },
+ {
+ abbreviation = 'bsdir',
+ defaults = {
+ if_true = '',
+ doc = '"last"',
+ },
+ desc = [=[
+ Which directory to use for the file browser:
+ last Use same directory as with last file browser, where a
+ file was opened or saved.
+ buffer Use the directory of the related buffer.
+ current Use the current directory.
+ {path} Use the specified directory
+ ]=],
+ enable_if = false,
+ full_name = 'browsedir',
+ scope = { 'global' },
+ short_desc = N_('which directory to start browsing in'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'bh',
+ alloced = true,
+ cb = 'did_set_bufhidden',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies what happens when a buffer is no longer
+ displayed in a window:
+ <empty> follow the global 'hidden' option
+ hide hide the buffer (don't unload it), even if 'hidden' is
+ not set
+ unload unload the buffer, even if 'hidden' is set; the
+ |:hide| command will also unload the buffer
+ delete delete the buffer from the buffer list, even if
+ 'hidden' is set; the |:hide| command will also delete
+ the buffer, making it behave like |:bdelete|
+ wipe wipe the buffer from the buffer list, even if
+ 'hidden' is set; the |:hide| command will also wipe
+ out the buffer, making it behave like |:bwipeout|
+
+ CAREFUL: when "unload", "delete" or "wipe" is used changes in a buffer
+ are lost without a warning. Also, these values may break autocommands
+ that switch between buffers temporarily.
+ This option is used together with 'buftype' and 'swapfile' to specify
+ special kinds of buffers. See |special-buffers|.
+ ]=],
+ expand_cb = 'expand_set_bufhidden',
+ full_name = 'bufhidden',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('what to do when buffer is no longer in window'),
+ type = 'string',
+ varname = 'p_bh',
+ },
+ {
+ abbreviation = 'bl',
+ cb = 'did_set_buflisted',
+ defaults = { if_true = true },
+ desc = [=[
+ When this option is set, the buffer shows up in the buffer list. If
+ it is reset it is not used for ":bnext", "ls", the Buffers menu, etc.
+ This option is reset by Vim for buffers that are only used to remember
+ a file name or marks. Vim sets it when starting to edit a buffer.
+ But not when moving to a buffer with ":buffer".
+ ]=],
+ full_name = 'buflisted',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('whether the buffer shows up in the buffer list'),
+ tags = { 'E85' },
+ type = 'bool',
+ varname = 'p_bl',
+ },
+ {
+ abbreviation = 'bt',
+ alloced = true,
+ cb = 'did_set_buftype',
+ defaults = { if_true = '' },
+ desc = [=[
+ The value of this option specifies the type of a buffer:
+ <empty> normal buffer
+ acwrite buffer will always be written with |BufWriteCmd|s
+ help help buffer (do not set this manually)
+ nofile buffer is not related to a file, will not be written
+ nowrite buffer will not be written
+ quickfix list of errors |:cwindow| or locations |:lwindow|
+ terminal |terminal-emulator| buffer
+ prompt buffer where only the last line can be edited, meant
+ to be used by a plugin, see |prompt-buffer|
+
+ This option is used together with 'bufhidden' and 'swapfile' to
+ specify special kinds of buffers. See |special-buffers|.
+ Also see |win_gettype()|, which returns the type of the window.
+
+ Be careful with changing this option, it can have many side effects!
+ One such effect is that Vim will not check the timestamp of the file,
+ if the file is changed by another program this will not be noticed.
+
+ A "quickfix" buffer is only used for the error list and the location
+ list. This value is set by the |:cwindow| and |:lwindow| commands and
+ you are not supposed to change it.
+
+ "nofile" and "nowrite" buffers are similar:
+ both: The buffer is not to be written to disk, ":w" doesn't
+ work (":w filename" does work though).
+ both: The buffer is never considered to be |'modified'|.
+ There is no warning when the changes will be lost, for
+ example when you quit Vim.
+ both: A swap file is only created when using too much memory
+ (when 'swapfile' has been reset there is never a swap
+ file).
+ nofile only: The buffer name is fixed, it is not handled like a
+ file name. It is not modified in response to a |:cd|
+ command.
+ both: When using ":e bufname" and already editing "bufname"
+ the buffer is made empty and autocommands are
+ triggered as usual for |:edit|.
+ *E676*
+ "acwrite" implies that the buffer name is not related to a file, like
+ "nofile", but it will be written. Thus, in contrast to "nofile" and
+ "nowrite", ":w" does work and a modified buffer can't be abandoned
+ without saving. For writing there must be matching |BufWriteCmd|,
+ |FileWriteCmd| or |FileAppendCmd| autocommands.
+ ]=],
+ expand_cb = 'expand_set_buftype',
+ full_name = 'buftype',
+ noglob = true,
+ scope = { 'buffer' },
+ tags = { 'E382' },
+ short_desc = N_('special type of buffer'),
+ type = 'string',
+ varname = 'p_bt',
+ },
+ {
+ abbreviation = 'cmp',
+ cb = 'did_set_casemap',
+ defaults = { if_true = 'internal,keepascii' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies details about changing the case of letters. It may contain
+ these words, separated by a comma:
+ internal Use internal case mapping functions, the current
+ locale does not change the case mapping. When
+ "internal" is omitted, the towupper() and towlower()
+ system library functions are used when available.
+ keepascii For the ASCII characters (0x00 to 0x7f) use the US
+ case mapping, the current locale is not effective.
+ This probably only matters for Turkish.
+ ]=],
+ expand_cb = 'expand_set_casemap',
+ full_name = 'casemap',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies how case of letters is changed'),
+ type = 'string',
+ varname = 'p_cmp',
+ },
+ {
+ abbreviation = 'cdh',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, |:cd|, |:tcd| and |:lcd| without an argument changes the
+ current working directory to the |$HOME| directory like in Unix.
+ When off, those commands just print the current directory name.
+ On Unix this option has no effect.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'cdhome',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_(':cd without argument goes to the home directory'),
+ type = 'bool',
+ varname = 'p_cdh',
+ },
+ {
+ abbreviation = 'cd',
+ defaults = {
+ if_true = ',,',
+ doc = 'equivalent to $CDPATH or ",,"',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of directories which will be searched when using the
+ |:cd|, |:tcd| and |:lcd| commands, provided that the directory being
+ searched for has a relative path, not an absolute part starting with
+ "/", "./" or "../", the 'cdpath' option is not used then.
+ The 'cdpath' option's value has the same form and semantics as
+ |'path'|. Also see |file-searching|.
+ The default value is taken from $CDPATH, with a "," prepended to look
+ in the current directory first.
+ If the default value taken from $CDPATH is not what you want, include
+ a modified version of the following command in your vimrc file to
+ override it: >
+ :let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g')
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ (parts of 'cdpath' can be passed to the shell to expand file names).
+ ]=],
+ expand = true,
+ full_name = 'cdpath',
+ list = 'comma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories searched with ":cd"'),
+ tags = { 'E344', 'E346' },
+ type = 'string',
+ varname = 'p_cdpath',
+ },
+ {
+ cb = 'did_set_cedit',
+ defaults = {
+ if_true = macros('CTRL_F_STR'),
+ doc = 'CTRL-F',
+ },
+ desc = [=[
+ The key used in Command-line Mode to open the command-line window.
+ Only non-printable keys are allowed.
+ The key can be specified as a single character, but it is difficult to
+ type. The preferred way is to use the <> notation. Examples: >
+ :exe "set cedit=\\<C-Y>"
+ :exe "set cedit=\\<Esc>"
+ < |Nvi| also has this option, but it only uses the first character.
+ See |cmdwin|.
+ ]=],
+ full_name = 'cedit',
+ scope = { 'global' },
+ short_desc = N_('used to open the command-line window'),
+ type = 'string',
+ varname = 'p_cedit',
+ },
+ {
+ defaults = { if_true = 0 },
+ desc = [=[
+ |channel| connected to the buffer, or 0 if no channel is connected.
+ In a |:terminal| buffer this is the terminal channel.
+ Read-only.
+ ]=],
+ full_name = 'channel',
+ no_mkrc = true,
+ nodefault = true,
+ scope = { 'buffer' },
+ short_desc = N_('Channel connected to the buffer'),
+ type = 'number',
+ varname = 'p_channel',
+ },
+ {
+ abbreviation = 'ccv',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ An expression that is used for character encoding conversion. It is
+ evaluated when a file that is to be read or has been written has a
+ different encoding from what is desired.
+ 'charconvert' is not used when the internal iconv() function is
+ supported and is able to do the conversion. Using iconv() is
+ 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, 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.
+ Conversion between "latin1", "unicode", "ucs-2", "ucs-4" and "utf-8"
+ is done internally by Vim, 'charconvert' is not used for this.
+ Also used for Unicode conversion.
+ Example: >
+ set charconvert=CharConvert()
+ fun CharConvert()
+ system("recode "
+ \ .. v:charconvert_from .. ".." .. v:charconvert_to
+ \ .. " <" .. v:fname_in .. " >" .. v:fname_out)
+ return v:shell_error
+ endfun
+ < The related Vim variables are:
+ v:charconvert_from name of the current encoding
+ v:charconvert_to name of the desired encoding
+ v:fname_in name of the input file
+ v:fname_out name of the output file
+ Note that v:fname_in and v:fname_out will never be the same.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'charconvert',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression for character encoding conversion'),
+ type = 'string',
+ tags = { 'E202', 'E214', 'E513' },
+ varname = 'p_ccv',
+ },
+ {
+ abbreviation = 'cin',
+ defaults = { if_true = false },
+ desc = [=[
+ Enables automatic C program indenting. See 'cinkeys' to set the keys
+ that trigger reindenting in insert mode and 'cinoptions' to set your
+ preferred indent style.
+ If 'indentexpr' is not empty, it overrules 'cindent'.
+ If 'lisp' is not on and both 'indentexpr' and 'equalprg' are empty,
+ the "=" operator indents using this algorithm rather than calling an
+ external program.
+ See |C-indenting|.
+ When you don't like the way 'cindent' works, try the 'smartindent'
+ option or 'indentexpr'.
+ ]=],
+ full_name = 'cindent',
+ scope = { 'buffer' },
+ short_desc = N_('do C program indenting'),
+ type = 'bool',
+ varname = 'p_cin',
+ },
+ {
+ abbreviation = 'cink',
+ alloced = true,
+ defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of keys that, when typed in Insert mode, cause reindenting of
+ the current line. Only used if 'cindent' is on and 'indentexpr' is
+ empty.
+ For the format of this option see |cinkeys-format|.
+ See |C-indenting|.
+ ]=],
+ full_name = 'cinkeys',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("keys that trigger indent when 'cindent' is set"),
+ type = 'string',
+ varname = 'p_cink',
+ },
+ {
+ abbreviation = 'cino',
+ alloced = true,
+ cb = 'did_set_cinoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ The 'cinoptions' affect the way 'cindent' reindents lines in a C
+ program. See |cinoptions-values| for the values of this option, and
+ |C-indenting| for info on C indenting in general.
+ ]=],
+ full_name = 'cinoptions',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("how to do indenting when 'cindent' is set"),
+ type = 'string',
+ varname = 'p_cino',
+ },
+ {
+ abbreviation = 'cinw',
+ alloced = true,
+ defaults = { if_true = 'if,else,while,do,for,switch' },
+ deny_duplicates = true,
+ desc = [=[
+ These keywords start an extra indent in the next line when
+ 'smartindent' or 'cindent' is set. For 'cindent' this is only done at
+ an appropriate place (inside {}).
+ Note that 'ignorecase' isn't used for 'cinwords'. If case doesn't
+ matter, include the keyword both the uppercase and lowercase:
+ "if,If,IF".
+ ]=],
+ full_name = 'cinwords',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("words where 'si' and 'cin' add an indent"),
+ type = 'string',
+ varname = 'p_cinw',
+ },
+ {
+ abbreviation = 'cinsd',
+ alloced = true,
+ defaults = { if_true = 'public,protected,private' },
+ deny_duplicates = true,
+ desc = [=[
+ Keywords that are interpreted as a C++ scope declaration by |cino-g|.
+ Useful e.g. for working with the Qt framework that defines additional
+ scope declarations "signals", "public slots" and "private slots": >
+ set cinscopedecls+=signals,public\ slots,private\ slots
+ <
+ ]=],
+ full_name = 'cinscopedecls',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("words that are recognized by 'cino-g'"),
+ type = 'string',
+ varname = 'p_cinsd',
+ },
+ {
+ abbreviation = 'cb',
+ cb = 'did_set_clipboard',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option is a list of comma-separated names.
+ These names are recognized:
+
+ *clipboard-unnamed*
+ unnamed When included, Vim will use the clipboard register "*"
+ for all yank, delete, change and put operations which
+ would normally go to the unnamed register. When a
+ register is explicitly specified, it will always be
+ used regardless of whether "unnamed" is in 'clipboard'
+ or not. The clipboard register can always be
+ explicitly accessed using the "* notation. Also see
+ |clipboard|.
+
+ *clipboard-unnamedplus*
+ unnamedplus A variant of the "unnamed" flag which uses the
+ clipboard register "+" (|quoteplus|) instead of
+ register "*" for all yank, delete, change and put
+ operations which would normally go to the unnamed
+ register. When "unnamed" is also included to the
+ option, yank and delete operations (but not put)
+ will additionally copy the text into register
+ "*". See |clipboard|.
+ ]=],
+ deny_duplicates = true,
+ expand_cb = 'expand_set_clipboard',
+ full_name = 'clipboard',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('use the clipboard as the unnamed register'),
+ type = 'string',
+ varname = 'p_cb',
+ },
+ {
+ abbreviation = 'ch',
+ cb = 'did_set_cmdheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Number of screen lines to use for the command-line. Helps avoiding
+ |hit-enter| prompts.
+ The value of this option is stored with the tab page, so that each tab
+ page can have a different value.
+
+ When 'cmdheight' is zero, there is no command-line unless it is being
+ used. The command-line will cover the last line of the screen when
+ shown.
+
+ WARNING: `cmdheight=0` is considered experimental. Expect some
+ unwanted behaviour. Some 'shortmess' flags and similar
+ mechanism might fail to take effect, causing unwanted hit-enter
+ prompts. Some informative messages, both from Nvim itself and
+ plugins, will not be displayed.
+ ]=],
+ full_name = 'cmdheight',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('number of lines to use for the command-line'),
+ type = 'number',
+ varname = 'p_ch',
+ },
+ {
+ abbreviation = 'cwh',
+ defaults = { if_true = 7 },
+ desc = [=[
+ Number of screen lines to use for the command-line window. |cmdwin|
+ ]=],
+ full_name = 'cmdwinheight',
+ scope = { 'global' },
+ short_desc = N_('height of the command-line window'),
+ type = 'number',
+ varname = 'p_cwh',
+ },
+ {
+ abbreviation = 'cc',
+ cb = 'did_set_colorcolumn',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ 'colorcolumn' is a comma-separated list of screen columns that are
+ highlighted with ColorColumn |hl-ColorColumn|. Useful to align
+ text. Will make screen redrawing slower.
+ The screen column can be an absolute number, or a number preceded with
+ '+' or '-', which is added to or subtracted from 'textwidth'. >
+
+ :set cc=+1 " highlight column after 'textwidth'
+ :set cc=+1,+2,+3 " highlight three columns after 'textwidth'
+ :hi ColorColumn ctermbg=lightgrey guibg=lightgrey
+ <
+ When 'textwidth' is zero then the items with '-' and '+' are not used.
+ A maximum of 256 columns are highlighted.
+ ]=],
+ full_name = 'colorcolumn',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('columns to highlight'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'co',
+ defaults = {
+ if_true = macros('DFLT_COLS'),
+ doc = '80 or terminal width',
+ },
+ desc = [=[
+ Number of columns of the screen. Normally this is set by the terminal
+ initialization and does not have to be set by hand.
+ When Vim is running in the GUI or in a resizable window, setting this
+ option will cause the window size to be changed. When you only want
+ to use the size for the GUI, put the command in your |ginit.vim| file.
+ When you set this option and Vim is unable to change the physical
+ number of columns of the display, the display may be messed up. For
+ the GUI it is always possible and Vim limits the number of columns to
+ what fits on the screen. You can use this command to get the widest
+ window possible: >
+ :set columns=9999
+ < Minimum value is 12, maximum value is 10000.
+ ]=],
+ full_name = 'columns',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('number of columns in the display'),
+ tags = { 'E594' },
+ type = 'number',
+ varname = 'p_columns',
+ },
+ {
+ abbreviation = 'com',
+ alloced = true,
+ cb = 'did_set_comments',
+ defaults = { if_true = 's1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-,fb:•' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of strings that can start a comment line. See
+ |format-comments|. See |option-backslash| about using backslashes to
+ insert a space.
+ ]=],
+ full_name = 'comments',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'buffer' },
+ short_desc = N_('patterns that can start a comment line'),
+ tags = { 'E524', 'E525' },
+ type = 'string',
+ varname = 'p_com',
+ },
+ {
+ abbreviation = 'cms',
+ alloced = true,
+ cb = 'did_set_commentstring',
+ defaults = { if_true = '' },
+ desc = [=[
+ A template for a comment. The "%s" in the value is replaced with the
+ comment text. For example, C uses "/*%s*/". Currently only used to
+ add markers for folding, see |fold-marker|.
+ ]=],
+ full_name = 'commentstring',
+ redraw = { 'curswant' },
+ scope = { 'buffer' },
+ short_desc = N_('template for comments; used for fold marker'),
+ tags = { 'E537' },
+ type = 'string',
+ varname = 'p_cms',
+ },
+ {
+ abbreviation = 'cp',
+ defaults = { if_true = false },
+ full_name = 'compatible',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'cpt',
+ alloced = true,
+ cb = 'did_set_complete',
+ defaults = { if_true = '.,w,b,u,t' },
+ deny_duplicates = true,
+ desc = [=[
+ This option specifies how keyword completion |ins-completion| works
+ when CTRL-P or CTRL-N are used. It is also used for whole-line
+ completion |i_CTRL-X_CTRL-L|. It indicates the type of completion
+ and the places to scan. It is a comma-separated list of flags:
+ . scan the current buffer ('wrapscan' is ignored)
+ w scan buffers from other windows
+ b scan other loaded buffers that are in the buffer list
+ u scan the unloaded buffers that are in the buffer list
+ U scan the buffers that are not in the buffer list
+ k scan the files given with the 'dictionary' option
+ kspell use the currently active spell checking |spell|
+ k{dict} scan the file {dict}. Several "k" flags can be given,
+ patterns are valid too. For example: >
+ :set cpt=k/usr/dict/*,k~/spanish
+ < s scan the files given with the 'thesaurus' option
+ s{tsr} scan the file {tsr}. Several "s" flags can be given, patterns
+ are valid too.
+ i scan current and included files
+ d scan current and included files for defined name or macro
+ |i_CTRL-X_CTRL-D|
+ ] tag completion
+ t same as "]"
+ f scan the buffer names (as opposed to buffer contents)
+
+ Unloaded buffers are not loaded, thus their autocmds |:autocmd| are
+ not executed, this may lead to unexpected completions from some files
+ (gzipped files for example). Unloaded buffers are not scanned for
+ whole-line completion.
+
+ As you can see, CTRL-N and CTRL-P can be used to do any 'iskeyword'-
+ based expansion (e.g., dictionary |i_CTRL-X_CTRL-K|, included patterns
+ |i_CTRL-X_CTRL-I|, tags |i_CTRL-X_CTRL-]| and normal expansions).
+ ]=],
+ expand_cb = 'expand_set_complete',
+ full_name = 'complete',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('specify how Insert mode completion works'),
+ tags = { 'E535' },
+ type = 'string',
+ varname = 'p_cpt',
+ },
+ {
+ abbreviation = 'cocu',
+ alloced = true,
+ cb = 'did_set_concealcursor',
+ defaults = { if_true = '' },
+ desc = [=[
+ Sets the modes in which text in the cursor line can also be concealed.
+ When the current mode is listed then concealing happens just like in
+ other lines.
+ n Normal mode
+ v Visual mode
+ i Insert mode
+ c Command line editing, for 'incsearch'
+
+ 'v' applies to all lines in the Visual area, not only the cursor.
+ A useful value is "nc". This is used in help files. So long as you
+ are moving around text is concealed, but when starting to insert text
+ or selecting a Visual area the concealed text is displayed, so that
+ you can see what you are doing.
+ Keep in mind that the cursor position is not always where it's
+ displayed. E.g., when moving vertically it may change column.
+ ]=],
+ expand_cb = 'expand_set_concealcursor',
+ full_name = 'concealcursor',
+ list = 'flags',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('whether concealable text is hidden in cursor line'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'cole',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Determine how text with the "conceal" syntax attribute |:syn-conceal|
+ is shown:
+
+ Value Effect ~
+ 0 Text is shown normally
+ 1 Each block of concealed text is replaced with one
+ character. If the syntax item does not have a custom
+ replacement character defined (see |:syn-cchar|) the
+ character defined in 'listchars' is used.
+ It is highlighted with the "Conceal" highlight group.
+ 2 Concealed text is completely hidden unless it has a
+ custom replacement character defined (see
+ |:syn-cchar|).
+ 3 Concealed text is completely hidden.
+
+ Note: in the cursor line concealed text is not hidden, so that you can
+ edit and copy the text. This can be changed with the 'concealcursor'
+ option.
+ ]=],
+ full_name = 'conceallevel',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('whether concealable text is shown or hidden'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'cfu',
+ alloced = true,
+ cb = 'did_set_completefunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for Insert mode completion
+ with CTRL-X CTRL-U. |i_CTRL-X_CTRL-U|
+ See |complete-functions| for an explanation of how the function is
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'completefunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function to be used for Insert mode completion'),
+ type = 'string',
+ varname = 'p_cfu',
+ },
+ {
+ abbreviation = 'cot',
+ cb = 'did_set_completeopt',
+ defaults = { if_true = 'menu,preview' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for Insert mode completion
+ |ins-completion|. The supported values are:
+
+ menu Use a popup menu to show the possible completions. The
+ menu is only shown when there is more than one match and
+ sufficient colors are available. |ins-completion-menu|
+
+ menuone Use the popup menu also when there is only one match.
+ Useful when there is additional information about the
+ match, e.g., what file it comes from.
+
+ longest Only insert the longest common text of the matches. If
+ the menu is displayed you can use CTRL-L to add more
+ characters. Whether case is ignored depends on the kind
+ of completion. For buffer text the 'ignorecase' option is
+ used.
+
+ preview Show extra information about the currently selected
+ completion in the preview window. Only works in
+ combination with "menu" or "menuone".
+
+ noinsert Do not insert any text for a match until the user selects
+ a match from the menu. Only works in combination with
+ "menu" or "menuone". No effect if "longest" is present.
+
+ noselect Do not select a match in the menu, force the user to
+ select one from the menu. Only works in combination with
+ "menu" or "menuone".
+ ]=],
+ expand_cb = 'expand_set_completeopt',
+ full_name = 'completeopt',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('options for Insert mode completion'),
+ type = 'string',
+ varname = 'p_cot',
+ },
+ {
+ abbreviation = 'csl',
+ cb = 'did_set_completeslash',
+ defaults = { if_true = '' },
+ desc = [=[
+ only for MS-Windows
+ When this option is set it overrules 'shellslash' for completion:
+ - When this option is set to "slash", a forward slash is used for path
+ completion in insert mode. This is useful when editing HTML tag, or
+ Makefile with 'noshellslash' on MS-Windows.
+ - When this option is set to "backslash", backslash is used. This is
+ useful when editing a batch file with 'shellslash' set on MS-Windows.
+ - When this option is empty, same character is used as for
+ 'shellslash'.
+ For Insert mode completion the buffer-local value is used. For
+ command line completion the global value is used.
+ ]=],
+ enable_if = 'BACKSLASH_IN_FILENAME',
+ expand_cb = 'expand_set_completeslash',
+ full_name = 'completeslash',
+ scope = { 'buffer' },
+ type = 'string',
+ varname = 'p_csl',
+ },
+ {
+ abbreviation = 'cf',
+ defaults = { if_true = false },
+ desc = [=[
+ When 'confirm' is on, certain operations that would normally
+ fail because of unsaved changes to a buffer, e.g. ":q" and ":e",
+ instead raise a dialog asking if you wish to save the current
+ file(s). You can still use a ! to unconditionally |abandon| a buffer.
+ If 'confirm' is off you can still activate confirmation for one
+ command only (this is most useful in mappings) with the |:confirm|
+ command.
+ Also see the |confirm()| function and the 'v' flag in 'guioptions'.
+ ]=],
+ full_name = 'confirm',
+ scope = { 'global' },
+ short_desc = N_('ask what to do about unsaved/read-only files'),
+ type = 'bool',
+ varname = 'p_confirm',
+ },
+ {
+ abbreviation = 'ci',
+ defaults = { if_true = false },
+ desc = [=[
+ Copy the structure of the existing lines indent when autoindenting a
+ new line. Normally the new indent is reconstructed by a series of
+ tabs followed by spaces as required (unless |'expandtab'| is enabled,
+ in which case only spaces are used). Enabling this option makes the
+ new line copy whatever characters were used for indenting on the
+ existing line. 'expandtab' has no effect on these characters, a Tab
+ remains a Tab. If the new indent is greater than on the existing
+ line, the remaining space is filled in the normal manner.
+ See 'preserveindent'.
+ ]=],
+ full_name = 'copyindent',
+ scope = { 'buffer' },
+ short_desc = N_("make 'autoindent' use existing indent structure"),
+ type = 'bool',
+ varname = 'p_ci',
+ },
+ {
+ abbreviation = 'cpo',
+ cb = 'did_set_cpoptions',
+ defaults = { if_true = macros('CPO_VIM') },
+ desc = [=[
+ A sequence of single character flags. When a character is present
+ this indicates Vi-compatible behavior. This is used for things where
+ not being Vi-compatible is mostly or sometimes preferred.
+ 'cpoptions' stands for "compatible-options".
+ Commas can be added for readability.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+
+ contains behavior ~
+ *cpo-a*
+ a When included, a ":read" command with a file name
+ argument will set the alternate file name for the
+ current window.
+ *cpo-A*
+ A When included, a ":write" command with a file name
+ argument will set the alternate file name for the
+ current window.
+ *cpo-b*
+ b "\|" in a ":map" command is recognized as the end of
+ the map command. The '\' is included in the mapping,
+ the text after the '|' is interpreted as the next
+ command. Use a CTRL-V instead of a backslash to
+ include the '|' in the mapping. Applies to all
+ mapping, abbreviation, menu and autocmd commands.
+ See also |map_bar|.
+ *cpo-B*
+ B A backslash has no special meaning in mappings,
+ abbreviations, user commands and the "to" part of the
+ menu commands. Remove this flag to be able to use a
+ backslash like a CTRL-V. For example, the command
+ ":map X \\<Esc>" results in X being mapped to:
+ 'B' included: "\^[" (^[ is a real <Esc>)
+ 'B' excluded: "<Esc>" (5 characters)
+ *cpo-c*
+ c Searching continues at the end of any match at the
+ cursor position, but not further than the start of the
+ next line. When not present searching continues
+ one character from the cursor position. With 'c'
+ "abababababab" only gets three matches when repeating
+ "/abab", without 'c' there are five matches.
+ *cpo-C*
+ C Do not concatenate sourced lines that start with a
+ backslash. See |line-continuation|.
+ *cpo-d*
+ d Using "./" in the 'tags' option doesn't mean to use
+ the tags file relative to the current file, but the
+ tags file in the current directory.
+ *cpo-D*
+ D Can't use CTRL-K to enter a digraph after Normal mode
+ commands with a character argument, like |r|, |f| and
+ |t|.
+ *cpo-e*
+ e When executing a register with ":@r", always add a
+ <CR> to the last line, also when the register is not
+ linewise. If this flag is not present, the register
+ is not linewise and the last line does not end in a
+ <CR>, then the last line is put on the command-line
+ and can be edited before hitting <CR>.
+ *cpo-E*
+ E It is an error when using "y", "d", "c", "g~", "gu" or
+ "gU" on an Empty region. The operators only work when
+ at least one character is to be operated on. Example:
+ This makes "y0" fail in the first column.
+ *cpo-f*
+ f When included, a ":read" command with a file name
+ argument will set the file name for the current buffer,
+ if the current buffer doesn't have a file name yet.
+ *cpo-F*
+ F When included, a ":write" command with a file name
+ argument will set the file name for the current
+ buffer, if the current buffer doesn't have a file name
+ yet. Also see |cpo-P|.
+ *cpo-i*
+ i When included, interrupting the reading of a file will
+ leave it modified.
+ *cpo-I*
+ I When moving the cursor up or down just after inserting
+ indent for 'autoindent', do not delete the indent.
+ *cpo-J*
+ J A |sentence| has to be followed by two spaces after
+ the '.', '!' or '?'. A <Tab> is not recognized as
+ white space.
+ *cpo-K*
+ K Don't wait for a key code to complete when it is
+ halfway through a mapping. This breaks mapping
+ <F1><F1> when only part of the second <F1> has been
+ read. It enables cancelling the mapping by typing
+ <F1><Esc>.
+ *cpo-l*
+ l Backslash in a [] range in a search pattern is taken
+ literally, only "\]", "\^", "\-" and "\\" are special.
+ See |/[]|
+ 'l' included: "/[ \t]" finds <Space>, '\' and 't'
+ 'l' excluded: "/[ \t]" finds <Space> and <Tab>
+ *cpo-L*
+ L When the 'list' option is set, 'wrapmargin',
+ 'textwidth', 'softtabstop' and Virtual Replace mode
+ (see |gR|) count a <Tab> as two characters, instead of
+ the normal behavior of a <Tab>.
+ *cpo-m*
+ m When included, a showmatch will always wait half a
+ second. When not included, a showmatch will wait half
+ a second or until a character is typed. |'showmatch'|
+ *cpo-M*
+ M When excluded, "%" matching will take backslashes into
+ account. Thus in "( \( )" and "\( ( \)" the outer
+ parenthesis match. When included "%" ignores
+ backslashes, which is Vi compatible.
+ *cpo-n*
+ n When included, the column used for 'number' and
+ 'relativenumber' will also be used for text of wrapped
+ lines.
+ *cpo-o*
+ o Line offset to search command is not remembered for
+ next search.
+ *cpo-O*
+ O Don't complain if a file is being overwritten, even
+ when it didn't exist when editing it. This is a
+ protection against a file unexpectedly created by
+ someone else. Vi didn't complain about this.
+ *cpo-p*
+ p Vi compatible Lisp indenting. When not present, a
+ slightly better algorithm is used.
+ *cpo-P*
+ P When included, a ":write" command that appends to a
+ file will set the file name for the current buffer, if
+ the current buffer doesn't have a file name yet and
+ the 'F' flag is also included |cpo-F|.
+ *cpo-q*
+ q When joining multiple lines leave the cursor at the
+ position where it would be when joining two lines.
+ *cpo-r*
+ r Redo ("." command) uses "/" to repeat a search
+ command, instead of the actually used search string.
+ *cpo-R*
+ R Remove marks from filtered lines. Without this flag
+ marks are kept like |:keepmarks| was used.
+ *cpo-s*
+ s Set buffer options when entering the buffer for the
+ first time. This is like it is in Vim version 3.0.
+ And it is the default. If not present the options are
+ set when the buffer is created.
+ *cpo-S*
+ S Set buffer options always when entering a buffer
+ (except 'readonly', 'fileformat', 'filetype' and
+ 'syntax'). This is the (most) Vi compatible setting.
+ The options are set to the values in the current
+ buffer. When you change an option and go to another
+ buffer, the value is copied. Effectively makes the
+ buffer options global to all buffers.
+
+ 's' 'S' copy buffer options
+ no no when buffer created
+ yes no when buffer first entered (default)
+ X yes each time when buffer entered (vi comp.)
+ *cpo-t*
+ t Search pattern for the tag command is remembered for
+ "n" command. Otherwise Vim only puts the pattern in
+ the history for search pattern, but doesn't change the
+ last used search pattern.
+ *cpo-u*
+ u Undo is Vi compatible. See |undo-two-ways|.
+ *cpo-v*
+ v Backspaced characters remain visible on the screen in
+ Insert mode. Without this flag the characters are
+ erased from the screen right away. With this flag the
+ screen newly typed text overwrites backspaced
+ characters.
+ *cpo-W*
+ W Don't overwrite a readonly file. When omitted, ":w!"
+ overwrites a readonly file, if possible.
+ *cpo-x*
+ x <Esc> on the command-line executes the command-line.
+ The default in Vim is to abandon the command-line,
+ because <Esc> normally aborts a command. |c_<Esc>|
+ *cpo-X*
+ X When using a count with "R" the replaced text is
+ deleted only once. Also when repeating "R" with "."
+ and a count.
+ *cpo-y*
+ y A yank command can be redone with ".". Think twice if
+ you really want to use this, it may break some
+ plugins, since most people expect "." to only repeat a
+ change.
+ *cpo-Z*
+ Z When using "w!" while the 'readonly' option is set,
+ don't reset 'readonly'.
+ *cpo-!*
+ ! When redoing a filter command, use the last used
+ external command, whatever it was. Otherwise the last
+ used -filter- command is used.
+ *cpo-$*
+ $ When making a change to one line, don't redisplay the
+ line, but put a '$' at the end of the changed text.
+ The changed text will be overwritten when you type the
+ new text. The line is redisplayed if you type any
+ command that moves the cursor from the insertion
+ point.
+ *cpo-%*
+ % Vi-compatible matching is done for the "%" command.
+ Does not recognize "#if", "#endif", etc.
+ Does not recognize "/*" and "*/".
+ Parens inside single and double quotes are also
+ counted, causing a string that contains a paren to
+ disturb the matching. For example, in a line like
+ "if (strcmp("foo(", s))" the first paren does not
+ match the last one. When this flag is not included,
+ parens inside single and double quotes are treated
+ specially. When matching a paren outside of quotes,
+ everything inside quotes is ignored. When matching a
+ paren inside quotes, it will find the matching one (if
+ there is one). This works very well for C programs.
+ This flag is also used for other features, such as
+ C-indenting.
+ *cpo-+*
+ + When included, a ":write file" command will reset the
+ 'modified' flag of the buffer, even though the buffer
+ itself may still be different from its file.
+ *cpo->*
+ > When appending to a register, put a line break before
+ the appended text.
+ *cpo-;*
+ ; When using |,| or |;| to repeat the last |t| search
+ and the cursor is right in front of the searched
+ character, the cursor won't move. When not included,
+ the cursor would skip over it and jump to the
+ following occurrence.
+ *cpo-_*
+ _ When using |cw| on a word, do not include the
+ whitespace following the word in the motion.
+ ]=],
+ expand_cb = 'expand_set_cpoptions',
+ full_name = 'cpoptions',
+ list = 'flags',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('flags for Vi-compatible behavior'),
+ tags = { 'cpo' },
+ type = 'string',
+ varname = 'p_cpo',
+ },
+ {
+ abbreviation = 'crb',
+ defaults = { if_true = false },
+ desc = [=[
+ When this option is set, as the cursor in the current
+ window moves other cursorbound windows (windows that also have
+ this option set) move their cursors to the corresponding line and
+ column. This option is useful for viewing the
+ differences between two versions of a file (see 'diff'); in diff mode,
+ inserted and deleted lines (though not characters within a line) are
+ taken into account.
+ ]=],
+ full_name = 'cursorbind',
+ pv_name = 'p_crbind',
+ scope = { 'window' },
+ short_desc = N_('move cursor in window as it moves in other windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'cuc',
+ defaults = { if_true = false },
+ desc = [=[
+ Highlight the screen column of the cursor with CursorColumn
+ |hl-CursorColumn|. Useful to align text. Will make screen redrawing
+ slower.
+ If you only want the highlighting in the current window you can use
+ these autocommands: >
+ au WinLeave * set nocursorline nocursorcolumn
+ au WinEnter * set cursorline cursorcolumn
+ <
+ ]=],
+ full_name = 'cursorcolumn',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_('highlight the screen column of the cursor'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'cul',
+ defaults = { if_true = false },
+ desc = [=[
+ Highlight the text line of the cursor with CursorLine |hl-CursorLine|.
+ Useful to easily spot the cursor. Will make screen redrawing slower.
+ When Visual mode is active the highlighting isn't used to make it
+ easier to see the selected text.
+ ]=],
+ full_name = 'cursorline',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_('highlight the screen line of the cursor'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'culopt',
+ cb = 'did_set_cursorlineopt',
+ defaults = { if_true = 'both' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of settings for how 'cursorline' is displayed.
+ Valid values:
+ "line" Highlight the text line of the cursor with
+ CursorLine |hl-CursorLine|.
+ "screenline" Highlight only the screen line of the cursor with
+ CursorLine |hl-CursorLine|.
+ "number" Highlight the line number of the cursor with
+ CursorLineNr |hl-CursorLineNr|.
+
+ Special value:
+ "both" Alias for the values "line,number".
+
+ "line" and "screenline" cannot be used together.
+ ]=],
+ expand_cb = 'expand_set_cursorlineopt',
+ full_name = 'cursorlineopt',
+ list = 'onecomma',
+ redraw = { 'current_window_only' },
+ scope = { 'window' },
+ short_desc = N_("settings for 'cursorline'"),
+ type = 'string',
+ },
+ {
+ cb = 'did_set_debug',
+ defaults = { if_true = '' },
+ desc = [=[
+ These values can be used:
+ msg Error messages that would otherwise be omitted will be given
+ anyway.
+ throw Error messages that would otherwise be omitted will be given
+ anyway and also throw an exception and set |v:errmsg|.
+ beep A message will be given when otherwise only a beep would be
+ produced.
+ The values can be combined, separated by a comma.
+ "msg" and "throw" are useful for debugging 'foldexpr', 'formatexpr' or
+ 'indentexpr'.
+ ]=],
+ expand_cb = 'expand_set_debug',
+ full_name = 'debug',
+ scope = { 'global' },
+ short_desc = N_('to "msg" to see all error messages'),
+ type = 'string',
+ varname = 'p_debug',
+ },
+ {
+ abbreviation = 'def',
+ alloced = true,
+ defaults = { if_true = '' },
+ desc = [=[
+ Pattern to be used to find a macro definition. It is a search
+ pattern, just like for the "/" command. This option is used for the
+ commands like "[i" and "[d" |include-search|. The 'isident' option is
+ used to recognize the defined name after the match: >
+ {match with 'define'}{non-ID chars}{defined name}{non-ID char}
+ < See |option-backslash| about inserting backslashes to include a space
+ or backslash.
+ For C++ this value would be useful, to include const type declarations: >
+ ^\(#\s*define\|[a-z]*\s*const\s*[a-z]*\)
+ < You can also use "\ze" just before the name and continue the pattern
+ to check what is following. E.g. for Javascript, if a function is
+ defined with `func_name = function(args)`: >
+ ^\s*\ze\i\+\s*=\s*function(
+ < If the function is defined with `func_name : function() {...`: >
+ ^\s*\ze\i\+\s*[:]\s*(*function\s*(
+ < When using the ":set" command, you need to double the backslashes!
+ To avoid that use `:let` with a single quote string: >
+ let &l:define = '^\s*\ze\k\+\s*=\s*function('
+ <
+ ]=],
+ full_name = 'define',
+ redraw = { 'curswant' },
+ scope = { 'global', 'buffer' },
+ short_desc = N_('pattern to be used to find a macro definition'),
+ type = 'string',
+ varname = 'p_def',
+ },
+ {
+ abbreviation = 'deco',
+ defaults = { if_true = false },
+ desc = [=[
+ If editing Unicode and this option is set, backspace and Normal mode
+ "x" delete each combining character on its own. When it is off (the
+ default) the character along with its combining characters are
+ deleted.
+ Note: When 'delcombine' is set "xx" may work differently from "2x"!
+
+ This is useful for Arabic, Hebrew and many other languages where one
+ may have combining characters overtop of base characters, and want
+ to remove only the combining ones.
+ ]=],
+ full_name = 'delcombine',
+ scope = { 'global' },
+ short_desc = N_('delete combining characters on their own'),
+ type = 'bool',
+ varname = 'p_deco',
+ },
+ {
+ abbreviation = 'dict',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of file names, separated by commas, that are used to lookup words
+ for keyword completion commands |i_CTRL-X_CTRL-K|. Each file should
+ contain a list of words. This can be one word per line, or several
+ words per line, separated by non-keyword characters (white space is
+ preferred). Maximum line length is 510 bytes.
+
+ When this option is empty or an entry "spell" is present, and spell
+ checking is enabled, words in the word lists for the currently active
+ 'spelllang' are used. See |spell|.
+
+ To include a comma in a file name precede it with a backslash. Spaces
+ after a comma are ignored, otherwise spaces are included in the file
+ name. See |option-backslash| about using backslashes.
+ This has nothing to do with the |Dictionary| variable type.
+ Where to find a list of words?
+ - BSD/macOS include the "/usr/share/dict/words" file.
+ - Try "apt install spell" to get the "/usr/share/dict/words" file on
+ apt-managed systems (Debian/Ubuntu).
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default.
+ Backticks cannot be used in this option for security reasons.
+ ]=],
+ expand = true,
+ full_name = 'dictionary',
+ list = 'onecomma',
+ normal_dname_chars = true,
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of file names used for keyword completion'),
+ type = 'string',
+ varname = 'p_dict',
+ },
+ {
+ cb = 'did_set_diff',
+ defaults = { if_true = false },
+ desc = [=[
+ Join the current window in the group of windows that shows differences
+ between files. See |diff-mode|.
+ ]=],
+ full_name = 'diff',
+ noglob = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('diff mode for the current window'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'dex',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to obtain a diff file (either ed-style
+ or unified-style) from two versions of a file. See |diff-diffexpr|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'diffexpr',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression used to obtain a diff file'),
+ type = 'string',
+ varname = 'p_dex',
+ },
+ {
+ abbreviation = 'dip',
+ alloced = true,
+ cb = 'did_set_diffopt',
+ defaults = { if_true = 'internal,filler,closeoff' },
+ deny_duplicates = true,
+ desc = [=[
+ Option settings for diff mode. It can consist of the following items.
+ All are optional. Items must be separated by a comma.
+
+ filler Show filler lines, to keep the text
+ synchronized with a window that has inserted
+ lines at the same position. Mostly useful
+ when windows are side-by-side and 'scrollbind'
+ is set.
+
+ context:{n} Use a context of {n} lines between a change
+ and a fold that contains unchanged lines.
+ When omitted a context of six lines is used.
+ When using zero the context is actually one,
+ since folds require a line in between, also
+ for a deleted line. Set it to a very large
+ value (999999) to disable folding completely.
+ See |fold-diff|.
+
+ iblank Ignore changes where lines are all blank. Adds
+ the "-B" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+ NOTE: the diff windows will get out of sync,
+ because no differences between blank lines are
+ taken into account.
+
+ icase Ignore changes in case of text. "a" and "A"
+ are considered the same. Adds the "-i" flag
+ to the "diff" command if 'diffexpr' is empty.
+
+ iwhite Ignore changes in amount of white space. Adds
+ the "-b" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly. It should ignore adding trailing
+ white space, but not leading white space.
+
+ iwhiteall Ignore all white space changes. Adds
+ the "-w" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+
+ iwhiteeol Ignore white space changes at end of line.
+ Adds the "-Z" flag to the "diff" command if
+ 'diffexpr' is empty. Check the documentation
+ of the "diff" command for what this does
+ exactly.
+
+ horizontal Start diff mode with horizontal splits (unless
+ explicitly specified otherwise).
+
+ vertical Start diff mode with vertical splits (unless
+ explicitly specified otherwise).
+
+ closeoff When a window is closed where 'diff' is set
+ and there is only one window remaining in the
+ same tab page with 'diff' set, execute
+ `:diffoff` in that window. This undoes a
+ `:diffsplit` command.
+
+ hiddenoff Do not use diff mode for a buffer when it
+ becomes hidden.
+
+ foldcolumn:{n} Set the 'foldcolumn' option to {n} when
+ starting diff mode. Without this 2 is used.
+
+ followwrap Follow the 'wrap' option and leave as it is.
+
+ internal Use the internal diff library. This is
+ ignored when 'diffexpr' is set. *E960*
+ When running out of memory when writing a
+ buffer this item will be ignored for diffs
+ involving that buffer. Set the 'verbose'
+ option to see when this happens.
+
+ indent-heuristic
+ Use the indent heuristic for the internal
+ diff library.
+
+ linematch:{n} Enable a second stage diff on each generated
+ hunk in order to align lines. When the total
+ number of lines in a hunk exceeds {n}, the
+ second stage diff will not be performed as
+ very large hunks can cause noticeable lag. A
+ recommended setting is "linematch:60", as this
+ will enable alignment for a 2 buffer diff with
+ hunks of up to 30 lines each, or a 3 buffer
+ diff with hunks of up to 20 lines each.
+
+ algorithm:{text} Use the specified diff algorithm with the
+ internal diff engine. Currently supported
+ algorithms are:
+ myers the default algorithm
+ minimal spend extra time to generate the
+ smallest possible diff
+ patience patience diff algorithm
+ histogram histogram diff algorithm
+
+ Examples: >
+ :set diffopt=internal,filler,context:4
+ :set diffopt=
+ :set diffopt=internal,filler,foldcolumn:3
+ :set diffopt-=internal " do NOT use the internal diff parser
+ <
+ ]=],
+ expand_cb = 'expand_set_diffopt',
+ full_name = 'diffopt',
+ list = 'onecommacolon',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('options for using diff mode'),
+ type = 'string',
+ varname = 'p_dip',
+ },
+ {
+ abbreviation = 'dg',
+ defaults = { if_true = false },
+ desc = [=[
+ Enable the entering of digraphs in Insert mode with {char1} <BS>
+ {char2}. See |digraphs|.
+ ]=],
+ full_name = 'digraph',
+ scope = { 'global' },
+ short_desc = N_('enable the entering of digraphs in Insert mode'),
+ type = 'bool',
+ varname = 'p_dg',
+ },
+ {
+ abbreviation = 'dir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directory names for the swap file, separated with commas.
+
+ Possible items:
+ - The swap file will be created in the first directory where this is
+ possible. If it is not possible in any directory, but last
+ directory listed in the option does not exist, it is created.
+ - Empty means that no swap file will be used (recovery is
+ impossible!) and no |E303| error will be given.
+ - A directory "." means to put the swap file in the same directory as
+ the edited file. On Unix, a dot is prepended to the file name, so
+ it doesn't show in a directory listing. On MS-Windows the "hidden"
+ attribute is set and a dot prepended if possible.
+ - A directory starting with "./" (or ".\" for MS-Windows) means to put
+ the swap file relative to where the edited file is. The leading "."
+ is replaced with the path name of the edited file.
+ - For Unix and Win32, if a directory ends in two path separators "//",
+ the swap file name will be built from the complete path to the file
+ with all path separators replaced by percent '%' signs (including
+ the colon following the drive letter on Win32). This will ensure
+ file name uniqueness in the preserve directory.
+ On Win32, it is also possible to end with "\\". However, When a
+ separating comma is following, you must use "//", since "\\" will
+ include the comma in the file name. Therefore it is recommended to
+ use '//', instead of '\\'.
+ - Spaces after the comma are ignored, other spaces are considered part
+ of the directory name. To have a space at the start of a directory
+ name, precede it with a backslash.
+ - To include a comma in a directory name precede it with a backslash.
+ - A directory name may end in an ':' or '/'.
+ - Environment variables are expanded |:set_env|.
+ - Careful with '\' characters, type one before a space, type two to
+ get one in the option (see |option-backslash|), for example: >
+ :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+ <
+ Editing the same file twice will result in a warning. Using "/tmp" on
+ is discouraged: if the system crashes you lose the swap file. And
+ others on the computer may be able to see the files.
+ Use |:set+=| and |:set-=| when adding or removing directories from the
+ list, this avoids problems if the Nvim default is changed.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'directory',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directory names for the swap file'),
+ type = 'string',
+ varname = 'p_dir',
+ },
+ {
+ abbreviation = 'dy',
+ cb = 'did_set_display',
+ defaults = { if_true = 'lastline' },
+ deny_duplicates = true,
+ desc = [=[
+ Change the way text is displayed. This is a comma-separated list of
+ flags:
+ lastline When included, as much as possible of the last line
+ in a window will be displayed. "@@@" is put in the
+ last columns of the last screen line to indicate the
+ rest of the line is not displayed.
+ truncate Like "lastline", but "@@@" is displayed in the first
+ column of the last screen line. Overrules "lastline".
+ uhex Show unprintable characters hexadecimal as <xx>
+ instead of using ^C and ~C.
+ msgsep Obsolete flag. Allowed but takes no effect. |msgsep|
+
+ When neither "lastline" nor "truncate" is included, a last line that
+ doesn't fit is replaced with "@" lines.
+
+ The "@" character can be changed by setting the "lastline" item in
+ 'fillchars'. The character is highlighted with |hl-NonText|.
+ ]=],
+ expand_cb = 'expand_set_display',
+ full_name = 'display',
+ list = 'onecomma',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('list of flags for how to display text'),
+ type = 'string',
+ varname = 'p_dy',
+ },
+ {
+ abbreviation = 'ead',
+ cb = 'did_set_eadirection',
+ defaults = { if_true = 'both' },
+ desc = [=[
+ Tells when the 'equalalways' option applies:
+ ver vertically, width of windows is not affected
+ hor horizontally, height of windows is not affected
+ both width and height of windows is affected
+ ]=],
+ expand_cb = 'expand_set_eadirection',
+ full_name = 'eadirection',
+ scope = { 'global' },
+ short_desc = N_("in which direction 'equalalways' works"),
+ type = 'string',
+ varname = 'p_ead',
+ },
+ {
+ abbreviation = 'ed',
+ defaults = { if_true = false },
+ full_name = 'edcompatible',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'emo',
+ cb = 'did_set_ambiwidth',
+ defaults = { if_true = true },
+ desc = [=[
+ When on all Unicode emoji characters are considered to be full width.
+ This excludes "text emoji" characters, which are normally displayed as
+ single width. Unfortunately there is no good specification for this
+ and it has been determined on trial-and-error basis. Use the
+ |setcellwidths()| function to change the behavior.
+ ]=],
+ full_name = 'emoji',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_emoji',
+ },
+ {
+ abbreviation = 'enc',
+ cb = 'did_set_encoding',
+ defaults = { if_true = macros('ENC_DFLT') },
+ deny_in_modelines = true,
+ desc = [=[
+ String-encoding used internally and for |RPC| communication.
+ Always UTF-8.
+
+ See 'fileencoding' to control file-content encoding.
+ ]=],
+ full_name = 'encoding',
+ scope = { 'global' },
+ short_desc = N_('encoding used internally'),
+ type = 'string',
+ varname = 'p_enc',
+ },
+ {
+ abbreviation = 'eof',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = false },
+ desc = [=[
+ Indicates that a CTRL-Z character was found at the end of the file
+ when reading it. Normally only happens when 'fileformat' is "dos".
+ When writing a file and this option is off and the 'binary' option
+ is on, or 'fixeol' option is off, no CTRL-Z will be written at the
+ end of the file.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'endoffile',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('write CTRL-Z for last line in file'),
+ type = 'bool',
+ varname = 'p_eof',
+ },
+ {
+ abbreviation = 'eol',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = true },
+ desc = [=[
+ When writing a file and this option is off and the 'binary' option
+ is on, or 'fixeol' option is off, no <EOL> will be written for the
+ last line in the file. This option is automatically set or reset when
+ starting to edit a new file, depending on whether file has an <EOL>
+ for the last line in the file. Normally you don't have to set or
+ reset this option.
+ When 'binary' is off and 'fixeol' is on the value is not used when
+ writing the file. When 'binary' is on or 'fixeol' is off it is used
+ to remember the presence of a <EOL> for the last line in the file, so
+ that when you write the file the situation from the original file can
+ be kept. But you can change it if you want to.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'endofline',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('write <EOL> for last line in file'),
+ type = 'bool',
+ varname = 'p_eol',
+ },
+ {
+ abbreviation = 'ea',
+ cb = 'did_set_equalalways',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, all the windows are automatically made the same size after
+ splitting or closing a window. This also happens the moment the
+ option is switched on. When off, splitting a window will reduce the
+ size of the current window and leave the other windows the same. When
+ closing a window the extra lines are given to the window next to it
+ (depending on 'splitbelow' and 'splitright').
+ When mixing vertically and horizontally split windows, a minimal size
+ is computed and some windows may be larger if there is room. The
+ 'eadirection' option tells in which direction the size is affected.
+ Changing the height and width of a window can be avoided by setting
+ 'winfixheight' and 'winfixwidth', respectively.
+ If a window size is specified when creating a new window sizes are
+ currently not equalized (it's complicated, but may be implemented in
+ the future).
+ ]=],
+ full_name = 'equalalways',
+ scope = { 'global' },
+ short_desc = N_('windows are automatically made the same size'),
+ type = 'bool',
+ varname = 'p_ea',
+ },
+ {
+ abbreviation = 'ep',
+ defaults = { if_true = '' },
+ desc = [=[
+ External program to use for "=" command. When this option is empty
+ the internal formatting functions are used; either 'lisp', 'cindent'
+ or 'indentexpr'.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'equalprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('external program to use for "=" command'),
+ type = 'string',
+ varname = 'p_ep',
+ },
+ {
+ abbreviation = 'eb',
+ defaults = { if_true = false },
+ desc = [=[
+ Ring the bell (beep or screen flash) for error messages. This only
+ makes a difference for error messages, the bell will be used always
+ for a lot of errors without a message (e.g., hitting <Esc> in Normal
+ mode). See 'visualbell' to make the bell behave like a screen flash
+ or do nothing. See 'belloff' to finetune when to ring the bell.
+ ]=],
+ full_name = 'errorbells',
+ scope = { 'global' },
+ short_desc = N_('ring the bell for error messages'),
+ type = 'bool',
+ varname = 'p_eb',
+ },
+ {
+ abbreviation = 'ef',
+ defaults = { if_true = macros('DFLT_ERRORFILE') },
+ desc = [=[
+ Name of the errorfile for the QuickFix mode (see |:cf|).
+ When the "-q" command-line argument is used, 'errorfile' is set to the
+ following argument. See |-q|.
+ NOT used for the ":make" command. See 'makeef' for that.
+ Environment variables are expanded |:set_env|.
+ See |option-backslash| about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'errorfile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of the errorfile for the QuickFix mode'),
+ type = 'string',
+ varname = 'p_ef',
+ },
+ {
+ abbreviation = 'efm',
+ defaults = {
+ if_true = macros('DFLT_EFM'),
+ doc = 'is very long',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Scanf-like description of the format for the lines in the error file
+ (see |errorformat|).
+ ]=],
+ full_name = 'errorformat',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('description of the lines in the error file'),
+ type = 'string',
+ varname = 'p_efm',
+ },
+ {
+ abbreviation = 'ei',
+ cb = 'did_set_eventignore',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of autocommand event names, which are to be ignored.
+ When set to "all" or when "all" is one of the items, all autocommand
+ events are ignored, autocommands will not be executed.
+ Otherwise this is a comma-separated list of event names. Example: >
+ :set ei=WinEnter,WinLeave
+ <
+ ]=],
+ expand_cb = 'expand_set_eventignore',
+ full_name = 'eventignore',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('autocommand events that are ignored'),
+ type = 'string',
+ varname = 'p_ei',
+ },
+ {
+ abbreviation = 'et',
+ defaults = { if_true = false },
+ desc = [=[
+ In Insert mode: Use the appropriate number of spaces to insert a
+ <Tab>. Spaces are used in indents with the '>' and '<' commands and
+ when 'autoindent' is on. To insert a real tab when 'expandtab' is
+ on, use CTRL-V<Tab>. See also |:retab| and |ins-expandtab|.
+ ]=],
+ full_name = 'expandtab',
+ scope = { 'buffer' },
+ short_desc = N_('use spaces when <Tab> is inserted'),
+ type = 'bool',
+ varname = 'p_et',
+ },
+ {
+ abbreviation = 'ex',
+ defaults = { if_true = false },
+ desc = [=[
+ Automatically execute .nvim.lua, .nvimrc, and .exrc files in the
+ current directory, if the file is in the |trust| list. Use |:trust| to
+ manage trusted files. See also |vim.secure.read()|.
+
+ Compare 'exrc' to |editorconfig|:
+ - 'exrc' can execute any code; editorconfig only specifies settings.
+ - 'exrc' is Nvim-specific; editorconfig works in other editors.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'exrc',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('read .nvimrc and .exrc in the current directory'),
+ type = 'bool',
+ varname = 'p_exrc',
+ },
+ {
+ abbreviation = 'fenc',
+ alloced = true,
+ cb = 'did_set_encoding',
+ defaults = { if_true = '' },
+ desc = [=[
+ File-content encoding for the current buffer. Conversion is done with
+ iconv() or as specified with 'charconvert'.
+
+ When 'fileencoding' is not UTF-8, conversion will be done when
+ writing the file. For reading see below.
+ When 'fileencoding' is empty, the file will be saved with UTF-8
+ encoding (no conversion when reading or writing a file).
+
+ WARNING: Conversion to a non-Unicode encoding can cause loss of
+ information!
+
+ See |encoding-names| for the possible values. Additionally, values may be
+ specified that can be handled by the converter, see
+ |mbyte-conversion|.
+
+ When reading a file 'fileencoding' will be set from 'fileencodings'.
+ To read a file in a certain encoding it won't work by setting
+ 'fileencoding', use the |++enc| argument. One exception: when
+ 'fileencodings' is empty the value of 'fileencoding' is used.
+ For a new file the global value of 'fileencoding' is used.
+
+ Prepending "8bit-" and "2byte-" has no meaning here, they are ignored.
+ When the option is set, the value is converted to lowercase. Thus
+ you can set it with uppercase values too. '_' characters are
+ replaced with '-'. If a name is recognized from the list at
+ |encoding-names|, it is replaced by the standard name. For example
+ "ISO8859-2" becomes "iso-8859-2".
+
+ When this option is set, after starting to edit a file, the 'modified'
+ option is set, because the file would be different when written.
+
+ Keep in mind that changing 'fenc' from a modeline happens
+ AFTER the text has been read, thus it applies to when the file will be
+ written. If you do set 'fenc' in a modeline, you might want to set
+ 'nomodified' to avoid not being able to ":q".
+
+ This option cannot be changed when 'modifiable' is off.
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'fileencoding',
+ no_mkrc = true,
+ redraw = { 'statuslines', 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('file encoding for multi-byte text'),
+ tags = { 'E213' },
+ type = 'string',
+ varname = 'p_fenc',
+ },
+ {
+ abbreviation = 'fencs',
+ defaults = { if_true = 'ucs-bom,utf-8,default,latin1' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of character encodings considered when starting to edit
+ an existing file. When a file is read, Vim tries to use the first
+ mentioned character encoding. If an error is detected, the next one
+ in the list is tried. When an encoding is found that works,
+ 'fileencoding' is set to it. If all fail, 'fileencoding' is set to
+ an empty string, which means that UTF-8 is used.
+ WARNING: Conversion can cause loss of information! You can use
+ the |++bad| argument to specify what is done with characters
+ that can't be converted.
+ For an empty file or a file with only ASCII characters most encodings
+ will work and the first entry of 'fileencodings' will be used (except
+ "ucs-bom", which requires the BOM to be present). If you prefer
+ another encoding use an BufReadPost autocommand event to test if your
+ preferred encoding is to be used. Example: >
+ au BufReadPost * if search('\S', 'w') == 0 |
+ \ set fenc=iso-2022-jp | endif
+ < This sets 'fileencoding' to "iso-2022-jp" if the file does not contain
+ non-blank characters.
+ When the |++enc| argument is used then the value of 'fileencodings' is
+ not used.
+ Note that 'fileencodings' is not used for a new file, the global value
+ of 'fileencoding' is used instead. You can set it with: >
+ :setglobal fenc=iso-8859-2
+ < This means that a non-existing file may get a different encoding than
+ an empty file.
+ The special value "ucs-bom" can be used to check for a Unicode BOM
+ (Byte Order Mark) at the start of the file. It must not be preceded
+ by "utf-8" or another Unicode encoding for this to work properly.
+ An entry for an 8-bit encoding (e.g., "latin1") should be the last,
+ because Vim cannot detect an error, thus the encoding is always
+ accepted.
+ The special value "default" can be used for the encoding from the
+ environment. It is useful when your environment uses a non-latin1
+ encoding, such as Russian.
+ When a file contains an illegal UTF-8 byte sequence it won't be
+ recognized as "utf-8". You can use the |8g8| command to find the
+ illegal byte sequence.
+ WRONG VALUES: WHAT'S WRONG:
+ latin1,utf-8 "latin1" will always be used
+ utf-8,ucs-bom,latin1 BOM won't be recognized in an utf-8
+ file
+ cp1250,latin1 "cp1250" will always be used
+ If 'fileencodings' is empty, 'fileencoding' is not modified.
+ See 'fileencoding' for the possible values.
+ Setting this option does not have an effect until the next time a file
+ is read.
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'fileencodings',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('automatically detected character encodings'),
+ type = 'string',
+ varname = 'p_fencs',
+ },
+ {
+ abbreviation = 'ff',
+ alloced = true,
+ cb = 'did_set_fileformat',
+ defaults = {
+ if_true = macros('DFLT_FF'),
+ doc = 'Windows: "dos", Unix: "unix"',
+ },
+ desc = [=[
+ This gives the <EOL> of the current buffer, which is used for
+ reading/writing the buffer from/to a file:
+ dos <CR><NL>
+ unix <NL>
+ mac <CR>
+ When "dos" is used, CTRL-Z at the end of a file is ignored.
+ See |file-formats| and |file-read|.
+ For the character encoding of the file see 'fileencoding'.
+ When 'binary' is set, the value of 'fileformat' is ignored, file I/O
+ works like it was set to "unix".
+ This option is set automatically when starting to edit a file and
+ 'fileformats' is not empty and 'binary' is off.
+ When this option is set, after starting to edit a file, the 'modified'
+ option is set, because the file would be different when written.
+ This option cannot be changed when 'modifiable' is off.
+ ]=],
+ expand_cb = 'expand_set_fileformat',
+ full_name = 'fileformat',
+ no_mkrc = true,
+ redraw = { 'curswant', 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('file format used for file I/O'),
+ type = 'string',
+ varname = 'p_ff',
+ },
+ {
+ abbreviation = 'ffs',
+ cb = 'did_set_fileformats',
+ defaults = {
+ if_true = macros('DFLT_FFS_VIM'),
+ doc = 'Windows: "dos,unix", Unix: "unix,dos"',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ This gives the end-of-line (<EOL>) formats that will be tried when
+ starting to edit a new buffer and when reading a file into an existing
+ buffer:
+ - When empty, the format defined with 'fileformat' will be used
+ always. It is not set automatically.
+ - When set to one name, that format will be used whenever a new buffer
+ is opened. 'fileformat' is set accordingly for that buffer. The
+ 'fileformats' name will be used when a file is read into an existing
+ buffer, no matter what 'fileformat' for that buffer is set to.
+ - When more than one name is present, separated by commas, automatic
+ <EOL> detection will be done when reading a file. When starting to
+ edit a file, a check is done for the <EOL>:
+ 1. If all lines end in <CR><NL>, and 'fileformats' includes "dos",
+ 'fileformat' is set to "dos".
+ 2. If a <NL> is found and 'fileformats' includes "unix", 'fileformat'
+ is set to "unix". Note that when a <NL> is found without a
+ preceding <CR>, "unix" is preferred over "dos".
+ 3. If 'fileformat' has not yet been set, and if a <CR> is found, and
+ if 'fileformats' includes "mac", 'fileformat' is set to "mac".
+ This means that "mac" is only chosen when:
+ "unix" is not present or no <NL> is found in the file, and
+ "dos" is not present or no <CR><NL> is found in the file.
+ Except: if "unix" was chosen, but there is a <CR> before
+ the first <NL>, and there appear to be more <CR>s than <NL>s in
+ the first few lines, "mac" is used.
+ 4. If 'fileformat' is still not set, the first name from
+ 'fileformats' is used.
+ When reading a file into an existing buffer, the same is done, but
+ this happens like 'fileformat' has been set appropriately for that
+ file only, the option is not changed.
+ When 'binary' is set, the value of 'fileformats' is not used.
+
+ When Vim starts up with an empty buffer the first item is used. You
+ can overrule this by setting 'fileformat' in your .vimrc.
+
+ For systems with a Dos-like <EOL> (<CR><NL>), when reading files that
+ are ":source"ed and for vimrc files, automatic <EOL> detection may be
+ done:
+ - When 'fileformats' is empty, there is no automatic detection. Dos
+ format will be used.
+ - When 'fileformats' is set to one or more names, automatic detection
+ is done. This is based on the first <NL> in the file: If there is a
+ <CR> in front of it, Dos format is used, otherwise Unix format is
+ used.
+ Also see |file-formats|.
+ ]=],
+ expand_cb = 'expand_set_fileformat',
+ full_name = 'fileformats',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("automatically detected values for 'fileformat'"),
+ type = 'string',
+ varname = 'p_ffs',
+ },
+ {
+ abbreviation = 'fic',
+ defaults = {
+ condition = 'CASE_INSENSITIVE_FILENAME',
+ if_false = false,
+ if_true = true,
+ doc = [[on for systems where case in file
+ names is normally ignored]],
+ },
+ desc = [=[
+ When set case is ignored when using file names and directories.
+ See 'wildignorecase' for only ignoring case when doing completion.
+ ]=],
+ full_name = 'fileignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case when using file names'),
+ type = 'bool',
+ varname = 'p_fic',
+ },
+ {
+ abbreviation = 'ft',
+ alloced = true,
+ cb = 'did_set_filetype_or_syntax',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is set, the FileType autocommand event is triggered.
+ All autocommands that match with the value of this option will be
+ executed. Thus the value of 'filetype' is used in place of the file
+ name.
+ Otherwise this option does not always reflect the current file type.
+ This option is normally set when the file type is detected. To enable
+ this use the ":filetype on" command. |:filetype|
+ Setting this option to a different value is most useful in a modeline,
+ for a file for which the file type is not automatically recognized.
+ Example, for in an IDL file: >
+ /* vim: set filetype=idl : */
+ < |FileType| |filetypes|
+ When a dot appears in the value then this separates two filetype
+ names. Example: >
+ /* vim: set filetype=c.doxygen : */
+ < This will use the "c" filetype first, then the "doxygen" filetype.
+ This works both for filetype plugins and for syntax files. More than
+ one dot may appear.
+ This option is not copied to another buffer, independent of the 's' or
+ 'S' flag in 'cpoptions'.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'filetype',
+ noglob = true,
+ normal_fname_chars = true,
+ scope = { 'buffer' },
+ short_desc = N_('type of file, used for autocommands'),
+ type = 'string',
+ varname = 'p_ft',
+ },
+ {
+ abbreviation = 'fcs',
+ alloced = true,
+ cb = 'did_set_chars_option',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Characters to fill the statuslines, vertical separators and special
+ lines in the window.
+ It is a comma-separated list of items. Each item has a name, a colon
+ and the value of that item:
+
+ item default Used for ~
+ stl ' ' statusline of the current window
+ stlnc ' ' statusline of the non-current windows
+ wbr ' ' window bar
+ horiz '─' or '-' horizontal separators |:split|
+ horizup '┴' or '-' upwards facing horizontal separator
+ horizdown '┬' or '-' downwards facing horizontal separator
+ vert '│' or '|' vertical separators |:vsplit|
+ vertleft '┤' or '|' left facing vertical separator
+ vertright '├' or '|' right facing vertical separator
+ verthoriz '┼' or '+' overlapping vertical and horizontal
+ separator
+ fold '·' or '-' filling 'foldtext'
+ foldopen '-' mark the beginning of a fold
+ foldclose '+' show a closed fold
+ foldsep '│' or '|' open fold middle marker
+ diff '-' deleted lines of the 'diff' option
+ msgsep ' ' message separator 'display'
+ eob '~' empty lines at the end of a buffer
+ lastline '@' 'display' contains lastline/truncate
+
+ Any one that is omitted will fall back to the default.
+
+ Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and
+ "verthoriz" are only used when 'laststatus' is 3, since only vertical
+ window separators are used otherwise.
+
+ If 'ambiwidth' is "double" then "horiz", "horizup", "horizdown",
+ "vert", "vertleft", "vertright", "verthoriz", "foldsep" and "fold"
+ default to single-byte alternatives.
+
+ Example: >
+ :set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
+ <
+ For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
+ single-byte and multibyte characters are supported. But double-width
+ characters are not supported.
+
+ The highlighting used for these items:
+ item highlight group ~
+ stl StatusLine |hl-StatusLine|
+ stlnc StatusLineNC |hl-StatusLineNC|
+ wbr WinBar |hl-WinBar| or |hl-WinBarNC|
+ horiz WinSeparator |hl-WinSeparator|
+ horizup WinSeparator |hl-WinSeparator|
+ horizdown WinSeparator |hl-WinSeparator|
+ vert WinSeparator |hl-WinSeparator|
+ vertleft WinSeparator |hl-WinSeparator|
+ vertright WinSeparator |hl-WinSeparator|
+ verthoriz WinSeparator |hl-WinSeparator|
+ fold Folded |hl-Folded|
+ diff DiffDelete |hl-DiffDelete|
+ eob EndOfBuffer |hl-EndOfBuffer|
+ lastline NonText |hl-NonText|
+ ]=],
+ expand_cb = 'expand_set_chars_option',
+ full_name = 'fillchars',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global', 'window' },
+ short_desc = N_('characters to use for displaying special items'),
+ type = 'string',
+ varname = 'p_fcs',
+ },
+ {
+ abbreviation = 'fixeol',
+ cb = 'did_set_eof_eol_fixeol_bomb',
+ defaults = { if_true = true },
+ desc = [=[
+ When writing a file and this option is on, <EOL> at the end of file
+ will be restored if missing. Turn this option off if you want to
+ preserve the situation from the original file.
+ When the 'binary' option is set the value of this option doesn't
+ matter.
+ See the 'endofline' option.
+ See |eol-and-eof| for example settings.
+ ]=],
+ full_name = 'fixendofline',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('make sure last line in file has <EOL>'),
+ type = 'bool',
+ varname = 'p_fixeol',
+ },
+ {
+ abbreviation = 'fcl',
+ cb = 'did_set_foldclose',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ When set to "all", a fold is closed when the cursor isn't in it and
+ its level is higher than 'foldlevel'. Useful if you want folds to
+ automatically close when moving out of them.
+ ]=],
+ expand_cb = 'expand_set_foldclose',
+ full_name = 'foldclose',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('close a fold when the cursor leaves it'),
+ type = 'string',
+ varname = 'p_fcl',
+ },
+ {
+ abbreviation = 'fdc',
+ alloced = true,
+ cb = 'did_set_foldcolumn',
+ defaults = { if_true = '0' },
+ desc = [=[
+ When and how to draw the foldcolumn. Valid values are:
+ "auto": resize to the minimum amount of folds to display.
+ "auto:[1-9]": resize to accommodate multiple folds up to the
+ selected level
+ "0": to disable foldcolumn
+ "[1-9]": to display a fixed number of columns
+ See |folding|.
+ ]=],
+ expand_cb = 'expand_set_foldcolumn',
+ full_name = 'foldcolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('width of the column used to indicate folds'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fen',
+ defaults = { if_true = true },
+ desc = [=[
+ When off, all folds are open. This option can be used to quickly
+ switch between showing all text unfolded and viewing the text with
+ folds (including manually opened or closed folds). It can be toggled
+ with the |zi| command. The 'foldcolumn' will remain blank when
+ 'foldenable' is off.
+ This option is set by commands that create a new fold or close a fold.
+ See |folding|.
+ ]=],
+ full_name = 'foldenable',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('set to display all folds open'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'fde',
+ alloced = true,
+ cb = 'did_set_foldexpr',
+ defaults = { if_true = '0' },
+ desc = [=[
+ The expression used for when 'foldmethod' is "expr". It is evaluated
+ for each line to obtain its fold level. The context is set to the
+ script where 'foldexpr' was set, script-local items can be accessed.
+ See |fold-expr| for the usage.
+
+ The expression will be evaluated in the |sandbox| if set from a
+ modeline, see |sandbox-option|.
+ This option can't be set from a |modeline| when the 'diff' option is
+ on or the 'modelineexpr' option is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'foldexpr' |textlock|.
+ ]=],
+ full_name = 'foldexpr',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('expression used when \'foldmethod\' is "expr"'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdi',
+ alloced = true,
+ cb = 'did_set_foldignore',
+ defaults = { if_true = '#' },
+ desc = [=[
+ Used only when 'foldmethod' is "indent". Lines starting with
+ characters in 'foldignore' will get their fold level from surrounding
+ lines. White space is skipped before checking for this character.
+ The default "#" works well for C programs. See |fold-indent|.
+ ]=],
+ full_name = 'foldignore',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('ignore lines when \'foldmethod\' is "indent"'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdl',
+ cb = 'did_set_foldlevel',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Sets the fold level: Folds with a higher level will be closed.
+ Setting this option to zero will close all folds. Higher numbers will
+ close fewer folds.
+ This option is set by commands like |zm|, |zM| and |zR|.
+ See |fold-foldlevel|.
+ ]=],
+ full_name = 'foldlevel',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('close folds with a level higher than this'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdls',
+ defaults = { if_true = -1 },
+ desc = [=[
+ Sets 'foldlevel' when starting to edit another buffer in a window.
+ Useful to always start editing with all folds closed (value zero),
+ some folds closed (one) or no folds closed (99).
+ This is done before reading any modeline, thus a setting in a modeline
+ overrules this option. Starting to edit a file for |diff-mode| also
+ ignores this option and closes all folds.
+ It is also done before BufReadPre autocommands, to allow an autocmd to
+ overrule the 'foldlevel' value for specific files.
+ When the value is negative, it is not used.
+ ]=],
+ full_name = 'foldlevelstart',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ short_desc = N_("'foldlevel' when starting to edit a file"),
+ type = 'number',
+ varname = 'p_fdls',
+ },
+ {
+ abbreviation = 'fmr',
+ alloced = true,
+ cb = 'did_set_foldmarker',
+ defaults = { if_true = '{{{,}}}' },
+ deny_duplicates = true,
+ desc = [=[
+ The start and end marker used when 'foldmethod' is "marker". There
+ must be one comma, which separates the start and end marker. The
+ marker is a literal string (a regular expression would be too slow).
+ See |fold-marker|.
+ ]=],
+ full_name = 'foldmarker',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('markers used when \'foldmethod\' is "marker"'),
+ tags = { 'E536' },
+ type = 'string',
+ },
+ {
+ abbreviation = 'fdm',
+ alloced = true,
+ cb = 'did_set_foldmethod',
+ defaults = { if_true = 'manual' },
+ desc = [=[
+ The kind of folding used for the current window. Possible values:
+ |fold-manual| manual Folds are created manually.
+ |fold-indent| indent Lines with equal indent form a fold.
+ |fold-expr| expr 'foldexpr' gives the fold level of a line.
+ |fold-marker| marker Markers are used to specify folds.
+ |fold-syntax| syntax Syntax highlighting items specify folds.
+ |fold-diff| diff Fold text that is not changed.
+ ]=],
+ expand_cb = 'expand_set_foldmethod',
+ full_name = 'foldmethod',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('folding type'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fml',
+ cb = 'did_set_foldminlines',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Sets the number of screen lines above which a fold can be displayed
+ closed. Also for manually closed folds. With the default value of
+ one a fold can only be closed if it takes up two or more screen lines.
+ Set to zero to be able to close folds of just one screen line.
+ Note that this only has an effect on what is displayed. After using
+ "zc" to close a fold, which is displayed open because it's smaller
+ than 'foldminlines', a following "zc" may close a containing fold.
+ ]=],
+ full_name = 'foldminlines',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('minimum number of lines for a fold to be closed'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdn',
+ cb = 'did_set_foldnestmax',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Sets the maximum nesting of folds for the "indent" and "syntax"
+ methods. This avoids that too many folds will be created. Using more
+ than 20 doesn't work, because the internal limit is 20.
+ ]=],
+ full_name = 'foldnestmax',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('maximum fold depth'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'fdo',
+ cb = 'did_set_foldopen',
+ defaults = { if_true = 'block,hor,mark,percent,quickfix,search,tag,undo' },
+ deny_duplicates = true,
+ desc = [=[
+ Specifies for which type of commands folds will be opened, if the
+ command moves the cursor into a closed fold. It is a comma-separated
+ list of items.
+ NOTE: When the command is part of a mapping this option is not used.
+ Add the |zv| command to the mapping to get the same effect.
+ (rationale: the mapping may want to control opening folds itself)
+
+ item commands ~
+ all any
+ block (, {, [[, [{, etc.
+ hor horizontal movements: "l", "w", "fx", etc.
+ insert any command in Insert mode
+ jump far jumps: "G", "gg", etc.
+ mark jumping to a mark: "'m", CTRL-O, etc.
+ percent "%"
+ quickfix ":cn", ":crew", ":make", etc.
+ search search for a pattern: "/", "n", "*", "gd", etc.
+ (not for a search pattern in a ":" command)
+ Also for |[s| and |]s|.
+ tag jumping to a tag: ":ta", CTRL-T, etc.
+ undo undo or redo: "u" and CTRL-R
+ When a movement command is used for an operator (e.g., "dl" or "y%")
+ this option is not used. This means the operator will include the
+ whole closed fold.
+ Note that vertical movements are not here, because it would make it
+ very difficult to move onto a closed fold.
+ In insert mode the folds containing the cursor will always be open
+ when text is inserted.
+ To close folds you can re-apply 'foldlevel' with the |zx| command or
+ set the 'foldclose' option to "all".
+ ]=],
+ expand_cb = 'expand_set_foldopen',
+ full_name = 'foldopen',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'global' },
+ short_desc = N_('for which commands a fold will be opened'),
+ type = 'string',
+ varname = 'p_fdo',
+ },
+ {
+ abbreviation = 'fdt',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = 'foldtext()' },
+ desc = [=[
+ An expression which is used to specify the text displayed for a closed
+ fold. The context is set to the script where 'foldexpr' was set,
+ script-local items can be accessed. See |fold-foldtext| for the
+ usage.
+
+ The expression will be evaluated in the |sandbox| if set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'foldtext' |textlock|.
+ ]=],
+ full_name = 'foldtext',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('expression used to display for a closed fold'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'fex',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to format a range of lines for the |gq|
+ operator or automatic formatting (see 'formatoptions'). When this
+ option is empty 'formatprg' is used.
+
+ The |v:lnum| variable holds the first line to be formatted.
+ The |v:count| variable holds the number of lines to be formatted.
+ The |v:char| variable holds the character that is going to be
+ inserted if the expression is being evaluated due to
+ automatic formatting. This can be empty. Don't insert
+ it yet!
+
+ Example: >
+ :set formatexpr=mylang#Format()
+ < This will invoke the mylang#Format() function in the
+ autoload/mylang.vim file in 'runtimepath'. |autoload|
+
+ The expression is also evaluated when 'textwidth' is set and adding
+ text beyond that limit. This happens under the same conditions as
+ when internal formatting is used. Make sure the cursor is kept in the
+ same spot relative to the text then! The |mode()| function will
+ return "i" or "R" in this situation.
+
+ When the expression evaluates to non-zero Vim will fall back to using
+ the internal format mechanism.
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ set formatexpr=s:MyFormatExpr()
+ set formatexpr=<SID>SomeFormatExpr()
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|. That stops the option from working,
+ since changing the buffer text is not allowed.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ NOTE: This option is set to "" when 'compatible' is set.
+ ]=],
+ full_name = 'formatexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used with "gq" command'),
+ type = 'string',
+ varname = 'p_fex',
+ },
+ {
+ abbreviation = 'fo',
+ alloced = true,
+ cb = 'did_set_formatoptions',
+ defaults = { if_true = macros('DFLT_FO_VIM') },
+ desc = [=[
+ This is a sequence of letters which describes how automatic
+ formatting is to be done.
+ See |fo-table| for possible values and |gq| for how to format text.
+ Commas can be inserted for readability.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+ ]=],
+ expand_cb = 'expand_set_formatoptions',
+ full_name = 'formatoptions',
+ list = 'flags',
+ scope = { 'buffer' },
+ short_desc = N_('how automatic formatting is to be done'),
+ type = 'string',
+ varname = 'p_fo',
+ },
+ {
+ abbreviation = 'flp',
+ alloced = true,
+ defaults = { if_true = '^\\s*\\d\\+[\\]:.)}\\t ]\\s*' },
+ desc = [=[
+ A pattern that is used to recognize a list header. This is used for
+ the "n" flag in 'formatoptions'.
+ The pattern must match exactly the text that will be the indent for
+ the line below it. You can use |/\ze| to mark the end of the match
+ while still checking more characters. There must be a character
+ following the pattern, when it matches the whole line it is handled
+ like there is no match.
+ The default recognizes a number, followed by an optional punctuation
+ character and white space.
+ ]=],
+ full_name = 'formatlistpat',
+ scope = { 'buffer' },
+ short_desc = N_('pattern used to recognize a list header'),
+ type = 'string',
+ varname = 'p_flp',
+ },
+ {
+ abbreviation = 'fp',
+ defaults = { if_true = '' },
+ desc = [=[
+ The name of an external program that will be used to format the lines
+ selected with the |gq| operator. The program must take the input on
+ stdin and produce the output on stdout. The Unix program "fmt" is
+ such a program.
+ If the 'formatexpr' option is not empty it will be used instead.
+ Otherwise, if 'formatprg' option is an empty string, the internal
+ format function will be used |C-indenting|.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'formatprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('name of external program used with "gq" command'),
+ type = 'string',
+ varname = 'p_fp',
+ },
+ {
+ abbreviation = 'fs',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, the OS function fsync() will be called after saving a file
+ (|:write|, |writefile()|, …), |swap-file|, |undo-persistence| and |shada-file|.
+ This flushes the file to disk, ensuring that it is safely written.
+ Slow on some systems: writing buffers, quitting Nvim, and other
+ operations may sometimes take a few seconds.
+
+ Files are ALWAYS flushed ('fsync' is ignored) when:
+ - |CursorHold| event is triggered
+ - |:preserve| is called
+ - system signals low battery life
+ - Nvim exits abnormally
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'fsync',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('whether to invoke fsync() after file write'),
+ type = 'bool',
+ varname = 'p_fs',
+ },
+ {
+ abbreviation = 'gd',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the ":substitute" flag 'g' is default on. This means that
+ all matches in a line are substituted instead of one. When a 'g' flag
+ is given to a ":substitute" command, this will toggle the substitution
+ of all or one match. See |complex-change|.
+
+ command 'gdefault' on 'gdefault' off ~
+ :s/// subst. all subst. one
+ :s///g subst. one subst. all
+ :s///gg subst. all subst. one
+
+ DEPRECATED: Setting this option may break plugins that are not aware
+ of this option. Also, many users get confused that adding the /g flag
+ has the opposite effect of that it normally does.
+ ]=],
+ full_name = 'gdefault',
+ scope = { 'global' },
+ short_desc = N_('the ":substitute" flag \'g\' is default on'),
+ type = 'bool',
+ varname = 'p_gd',
+ },
+ {
+ abbreviation = 'gfm',
+ defaults = { if_true = macros('DFLT_GREPFORMAT') },
+ deny_duplicates = true,
+ desc = [=[
+ Format to recognize for the ":grep" command output.
+ This is a scanf-like string that uses the same format as the
+ 'errorformat' option: see |errorformat|.
+ ]=],
+ full_name = 'grepformat',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("format of 'grepprg' output"),
+ type = 'string',
+ varname = 'p_gefm',
+ },
+ {
+ abbreviation = 'gp',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = 'grep -n $* /dev/null',
+ if_true = 'findstr /n $* nul',
+ doc = [["grep -n ",
+ Unix: "grep -n $* /dev/null"]],
+ },
+ desc = [=[
+ Program to use for the |:grep| command. This option may contain '%'
+ and '#' characters, which are expanded like when used in a command-
+ line. The placeholder "$*" is allowed to specify where the arguments
+ will be included. Environment variables are expanded |:set_env|. See
+ |option-backslash| about including spaces and backslashes.
+ When your "grep" accepts the "-H" argument, use this to make ":grep"
+ also work well with a single file: >
+ :set grepprg=grep\ -nH
+ < Special value: When 'grepprg' is set to "internal" the |:grep| command
+ works like |:vimgrep|, |:lgrep| like |:lvimgrep|, |:grepadd| like
+ |:vimgrepadd| and |:lgrepadd| like |:lvimgrepadd|.
+ See also the section |:make_makeprg|, since most of the comments there
+ apply equally to 'grepprg'.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'grepprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for ":grep"'),
+ type = 'string',
+ varname = 'p_gp',
+ },
+ {
+ abbreviation = 'gcr',
+ cb = 'did_set_guicursor',
+ defaults = { if_true = 'n-v-c-sm:block,i-ci-ve:ver25,r-cr-o:hor20' },
+ deny_duplicates = true,
+ desc = [=[
+ Configures the cursor style for each mode. Works in the GUI and many
+ terminals. See |tui-cursor-shape|.
+
+ To disable cursor-styling, reset the option: >
+ :set guicursor=
+
+ < To enable mode shapes, "Cursor" highlight, and blinking: >
+ :set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
+ \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor
+ \,sm:block-blinkwait175-blinkoff150-blinkon175
+
+ < The option is a comma-separated list of parts. Each part consists of a
+ mode-list and an argument-list:
+ mode-list:argument-list,mode-list:argument-list,..
+ The mode-list is a dash separated list of these modes:
+ n Normal mode
+ v Visual mode
+ ve Visual mode with 'selection' "exclusive" (same as 'v',
+ if not specified)
+ o Operator-pending mode
+ i Insert mode
+ r Replace mode
+ c Command-line Normal (append) mode
+ ci Command-line Insert mode
+ cr Command-line Replace mode
+ sm showmatch in Insert mode
+ a all modes
+ The argument-list is a dash separated list of these arguments:
+ hor{N} horizontal bar, {N} percent of the character height
+ ver{N} vertical bar, {N} percent of the character width
+ block block cursor, fills the whole character
+ - Only one of the above three should be present.
+ - Default is "block" for each mode.
+ blinkwait{N} *cursor-blinking*
+ blinkon{N}
+ blinkoff{N}
+ blink times for cursor: blinkwait is the delay before
+ the cursor starts blinking, blinkon is the time that
+ the cursor is shown and blinkoff is the time that the
+ cursor is not shown. Times are in msec. When one of
+ the numbers is zero, there is no blinking. E.g.: >
+ :set guicursor=n:blinkon0
+ < - Default is "blinkon0" for each mode.
+ {group-name}
+ Highlight group that decides the color and font of the
+ cursor.
+ In the |TUI|:
+ - |inverse|/reverse and no group-name are interpreted
+ as "host-terminal default cursor colors" which
+ typically means "inverted bg and fg colors".
+ - |ctermfg| and |guifg| are ignored.
+ {group-name}/{group-name}
+ Two highlight group names, the first is used when
+ no language mappings are used, the other when they
+ are. |language-mapping|
+
+ Examples of parts:
+ n-c-v:block-nCursor In Normal, Command-line and Visual mode, use a
+ block cursor with colors from the "nCursor"
+ highlight group
+ n-v-c-sm:block,i-ci-ve:ver25-Cursor,r-cr-o:hor20
+ In Normal et al. modes, use a block cursor
+ with the default colors defined by the host
+ terminal. In Insert-like modes, use
+ a vertical bar cursor with colors from
+ "Cursor" highlight group. In Replace-like
+ modes, use an underline cursor with
+ default colors.
+ i-ci:ver30-iCursor-blinkwait300-blinkon200-blinkoff150
+ In Insert and Command-line Insert mode, use a
+ 30% vertical bar cursor with colors from the
+ "iCursor" highlight group. Blink a bit
+ faster.
+
+ The 'a' mode is different. It will set the given argument-list for
+ all modes. It does not reset anything to defaults. This can be used
+ to do a common setting for all modes. For example, to switch off
+ blinking: "a:blinkon0"
+
+ Examples of cursor highlighting: >
+ :highlight Cursor gui=reverse guifg=NONE guibg=NONE
+ :highlight Cursor gui=NONE guifg=bg guibg=fg
+ <
+ ]=],
+ full_name = 'guicursor',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('GUI: settings for cursor shape and blinking'),
+ tags = { 'E545', 'E546', 'E548', 'E549' },
+ type = 'string',
+ varname = 'p_guicursor',
+ },
+ {
+ abbreviation = 'gfn',
+ defaults = { if_true = '' },
+ desc = [=[
+ This is a list of fonts which will be used for the GUI version of Vim.
+ In its simplest form the value is just one font name. When
+ the font cannot be found you will get an error message. To try other
+ font names a list can be specified, font names separated with commas.
+ The first valid font is used.
+
+ Spaces after a comma are ignored. To include a comma in a font name
+ precede it with a backslash. Setting an option requires an extra
+ backslash before a space and a backslash. See also
+ |option-backslash|. For example: >
+ :set guifont=Screen15,\ 7x13,font\\,with\\,commas
+ < will make Vim try to use the font "Screen15" first, and if it fails it
+ will try to use "7x13" and then "font,with,commas" instead.
+
+ If none of the fonts can be loaded, Vim will keep the current setting.
+ If an empty font list is given, Vim will try using other resource
+ settings (for X, it will use the Vim.font resource), and finally it
+ will try some builtin default which should always be there ("7x13" in
+ the case of X). The font names given should be "normal" fonts. Vim
+ will try to find the related bold and italic fonts.
+
+ For Win32 and Mac OS: >
+ :set guifont=*
+ < will bring up a font requester, where you can pick the font you want.
+
+ The font name depends on the GUI used.
+
+ For Mac OSX you can use something like this: >
+ :set guifont=Monaco:h10
+ < *E236*
+ Note that the fonts must be mono-spaced (all characters have the same
+ width).
+
+ To preview a font on X11, you might be able to use the "xfontsel"
+ program. The "xlsfonts" program gives a list of all available fonts.
+
+ For the Win32 GUI *E244* *E245*
+ - takes these options in the font name:
+ hXX - height is XX (points, can be floating-point)
+ wXX - width is XX (points, can be floating-point)
+ b - bold
+ i - italic
+ u - underline
+ s - strikeout
+ cXX - character set XX. Valid charsets are: ANSI, ARABIC,
+ BALTIC, CHINESEBIG5, DEFAULT, EASTEUROPE, GB2312, GREEK,
+ HANGEUL, HEBREW, JOHAB, MAC, OEM, RUSSIAN, SHIFTJIS,
+ SYMBOL, THAI, TURKISH, VIETNAMESE ANSI and BALTIC.
+ Normally you would use "cDEFAULT".
+
+ Use a ':' to separate the options.
+ - A '_' can be used in the place of a space, so you don't need to use
+ backslashes to escape the spaces.
+ - Examples: >
+ :set guifont=courier_new:h12:w5:b:cRUSSIAN
+ :set guifont=Andale_Mono:h7.5:w4.5
+ <
+ ]=],
+ deny_duplicates = true,
+ full_name = 'guifont',
+ list = 'onecomma',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('GUI: Name(s) of font(s) to be used'),
+ tags = { 'E235', 'E596' },
+ type = 'string',
+ varname = 'p_guifont',
+ },
+ {
+ abbreviation = 'gfw',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of fonts to be used for double-width characters.
+ The first font that can be loaded is used.
+ Note: The size of these fonts must be exactly twice as wide as the one
+ specified with 'guifont' and the same height.
+
+ When 'guifont' has a valid font and 'guifontwide' is empty Vim will
+ attempt to set 'guifontwide' to a matching double-width font.
+ ]=],
+ full_name = 'guifontwide',
+ list = 'onecomma',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('list of font names for double-wide characters'),
+ tags = { 'E231', 'E533', 'E534' },
+ type = 'string',
+ varname = 'p_guifontwide',
+ },
+ {
+ abbreviation = 'go',
+ defaults = {
+ if_true = '',
+ doc = '"egmrLT" (MS-Windows)',
+ },
+ desc = [=[
+ This option only has an effect in the GUI version of Vim. It is a
+ sequence of letters which describes what components and options of the
+ GUI should be used.
+ To avoid problems with flags that are added in the future, use the
+ "+=" and "-=" feature of ":set" |add-option-flags|.
+
+ Valid letters are as follows:
+ *guioptions_a* *'go-a'*
+ 'a' Autoselect: If present, then whenever VISUAL mode is started,
+ or the Visual area extended, Vim tries to become the owner of
+ the windowing system's global selection. This means that the
+ Visually highlighted text is available for pasting into other
+ applications as well as into Vim itself. When the Visual mode
+ ends, possibly due to an operation on the text, or when an
+ application wants to paste the selection, the highlighted text
+ is automatically yanked into the "* selection register.
+ Thus the selection is still available for pasting into other
+ applications after the VISUAL mode has ended.
+ If not present, then Vim won't become the owner of the
+ windowing system's global selection unless explicitly told to
+ by a yank or delete operation for the "* register.
+ The same applies to the modeless selection.
+ *'go-P'*
+ 'P' Like autoselect but using the "+ register instead of the "*
+ register.
+ *'go-A'*
+ 'A' Autoselect for the modeless selection. Like 'a', but only
+ applies to the modeless selection.
+
+ 'guioptions' autoselect Visual autoselect modeless ~
+ "" - -
+ "a" yes yes
+ "A" - yes
+ "aA" yes yes
+
+ *'go-c'*
+ 'c' Use console dialogs instead of popup dialogs for simple
+ choices.
+ *'go-d'*
+ 'd' Use dark theme variant if available.
+ *'go-e'*
+ 'e' Add tab pages when indicated with 'showtabline'.
+ 'guitablabel' can be used to change the text in the labels.
+ When 'e' is missing a non-GUI tab pages line may be used.
+ The GUI tabs are only supported on some systems, currently
+ Mac OS/X and MS-Windows.
+ *'go-i'*
+ 'i' Use a Vim icon.
+ *'go-m'*
+ 'm' Menu bar is present.
+ *'go-M'*
+ 'M' The system menu "$VIMRUNTIME/menu.vim" is not sourced. Note
+ that this flag must be added in the vimrc file, before
+ switching on syntax or filetype recognition (when the |gvimrc|
+ file is sourced the system menu has already been loaded; the
+ `:syntax on` and `:filetype on` commands load the menu too).
+ *'go-g'*
+ 'g' Grey menu items: Make menu items that are not active grey. If
+ 'g' is not included inactive menu items are not shown at all.
+ *'go-T'*
+ 'T' Include Toolbar. Currently only in Win32 GUI.
+ *'go-r'*
+ 'r' Right-hand scrollbar is always present.
+ *'go-R'*
+ 'R' Right-hand scrollbar is present when there is a vertically
+ split window.
+ *'go-l'*
+ 'l' Left-hand scrollbar is always present.
+ *'go-L'*
+ 'L' Left-hand scrollbar is present when there is a vertically
+ split window.
+ *'go-b'*
+ 'b' Bottom (horizontal) scrollbar is present. Its size depends on
+ the longest visible line, or on the cursor line if the 'h'
+ flag is included. |gui-horiz-scroll|
+ *'go-h'*
+ 'h' Limit horizontal scrollbar size to the length of the cursor
+ line. Reduces computations. |gui-horiz-scroll|
+
+ And yes, you may even have scrollbars on the left AND the right if
+ you really want to :-). See |gui-scrollbars| for more information.
+
+ *'go-v'*
+ 'v' Use a vertical button layout for dialogs. When not included,
+ a horizontal layout is preferred, but when it doesn't fit a
+ vertical layout is used anyway. Not supported in GTK 3.
+ *'go-p'*
+ 'p' Use Pointer callbacks for X11 GUI. This is required for some
+ window managers. If the cursor is not blinking or hollow at
+ the right moment, try adding this flag. This must be done
+ before starting the GUI. Set it in your |gvimrc|. Adding or
+ removing it after the GUI has started has no effect.
+ *'go-k'*
+ 'k' Keep the GUI window size when adding/removing a scrollbar, or
+ toolbar, tabline, etc. Instead, the behavior is similar to
+ when the window is maximized and will adjust 'lines' and
+ 'columns' to fit to the window. Without the 'k' flag Vim will
+ try to keep 'lines' and 'columns' the same when adding and
+ removing GUI components.
+ ]=],
+ enable_if = false,
+ full_name = 'guioptions',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('GUI: Which components and options are used'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'gtl',
+ desc = [=[
+ When non-empty describes the text to use in a label of the GUI tab
+ pages line. When empty and when the result is empty Vim will use a
+ default label. See |setting-guitablabel| for more info.
+
+ The format of this option is like that of 'statusline'.
+ 'guitabtooltip' is used for the tooltip, see below.
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Only used when the GUI tab pages line is displayed. 'e' must be
+ present in 'guioptions'. For the non-GUI tab pages line 'tabline' is
+ used.
+ ]=],
+ enable_if = false,
+ full_name = 'guitablabel',
+ modelineexpr = true,
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('GUI: custom label for a tab page'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'gtt',
+ desc = [=[
+ When non-empty describes the text to use in a tooltip for the GUI tab
+ pages line. When empty Vim will use a default tooltip.
+ This option is otherwise just like 'guitablabel' above.
+ You can include a line break. Simplest method is to use |:let|: >
+ :let &guitabtooltip = "line one\nline two"
+ <
+ ]=],
+ enable_if = false,
+ full_name = 'guitabtooltip',
+ redraw = { 'current_window' },
+ scope = { 'global' },
+ short_desc = N_('GUI: custom tooltip for a tab page'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'hf',
+ cb = 'did_set_helpfile',
+ defaults = {
+ if_true = macros('DFLT_HELPFILE'),
+ doc = [[(MS-Windows) "$VIMRUNTIME\doc\help.txt"
+ (others) "$VIMRUNTIME/doc/help.txt"]],
+ },
+ desc = [=[
+ Name of the main help file. All distributed help files should be
+ placed together in one directory. Additionally, all "doc" directories
+ in 'runtimepath' will be used.
+ Environment variables are expanded |:set_env|. For example:
+ "$VIMRUNTIME/doc/help.txt". If $VIMRUNTIME is not set, $VIM is also
+ tried. Also see |$VIMRUNTIME| and |option-backslash| about including
+ spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'helpfile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('full path name of the main help file'),
+ type = 'string',
+ varname = 'p_hf',
+ },
+ {
+ abbreviation = 'hh',
+ cb = 'did_set_helpheight',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Minimal initial height of the help window when it is opened with the
+ ":help" command. The initial height of the help window is half of the
+ current window, or (when the 'ea' option is on) the same as other
+ windows. When the height is less than 'helpheight', the height is
+ set to 'helpheight'. Set to zero to disable.
+ ]=],
+ full_name = 'helpheight',
+ scope = { 'global' },
+ short_desc = N_('minimum height of a new help window'),
+ type = 'number',
+ varname = 'p_hh',
+ },
+ {
+ abbreviation = 'hlg',
+ cb = 'did_set_helplang',
+ defaults = {
+ if_true = '',
+ doc = 'messages language or empty',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of languages. Vim will use the first language
+ for which the desired help can be found. The English help will always
+ be used as a last resort. You can add "en" to prefer English over
+ another language, but that will only find tags that exist in that
+ language and not in the English help.
+ Example: >
+ :set helplang=de,it
+ < This will first search German, then Italian and finally English help
+ files.
+ When using |CTRL-]| and ":help!" in a non-English help file Vim will
+ try to find the tag in the current language before using this option.
+ See |help-translated|.
+ ]=],
+ full_name = 'helplang',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('preferred help languages'),
+ type = 'string',
+ varname = 'p_hlg',
+ },
+ {
+ abbreviation = 'hid',
+ defaults = { if_true = true },
+ desc = [=[
+ When off a buffer is unloaded (including loss of undo information)
+ when it is |abandon|ed. When on a buffer becomes hidden when it is
+ |abandon|ed. A buffer displayed in another window does not become
+ hidden, of course.
+
+ Commands that move through the buffer list sometimes hide a buffer
+ although the 'hidden' option is off when these three are true:
+ - the buffer is modified
+ - 'autowrite' is off or writing is not possible
+ - the '!' flag was used
+ Also see |windows|.
+
+ To hide a specific buffer use the 'bufhidden' option.
+ 'hidden' is set for one command with ":hide {command}" |:hide|.
+ ]=],
+ full_name = 'hidden',
+ scope = { 'global' },
+ short_desc = N_("don't unload buffer when it is |abandon|ed"),
+ type = 'bool',
+ varname = 'p_hid',
+ },
+ {
+ abbreviation = 'hl',
+ cb = 'did_set_highlight',
+ defaults = { if_true = macros('HIGHLIGHT_INIT') },
+ deny_duplicates = true,
+ full_name = 'highlight',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('sets highlighting mode for various occasions'),
+ type = 'string',
+ varname = 'p_hl',
+ },
+ {
+ abbreviation = 'hi',
+ defaults = { if_true = 10000 },
+ desc = [=[
+ A history of ":" commands, and a history of previous search patterns
+ is remembered. This option decides how many entries may be stored in
+ each of these histories (see |cmdline-editing|).
+ The maximum value is 10000.
+ ]=],
+ full_name = 'history',
+ scope = { 'global' },
+ short_desc = N_('number of command-lines that are remembered'),
+ type = 'number',
+ varname = 'p_hi',
+ },
+ {
+ abbreviation = 'hk',
+ defaults = { if_true = false },
+ full_name = 'hkmap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'hkp',
+ defaults = { if_true = false },
+ full_name = 'hkmapp',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'hls',
+ cb = 'did_set_hlsearch',
+ defaults = { if_true = true },
+ desc = [=[
+ When there is a previous search pattern, highlight all its matches.
+ The |hl-Search| highlight group determines the highlighting for all
+ matches not under the cursor while the |hl-CurSearch| highlight group
+ (if defined) determines the highlighting for the match under the
+ cursor. If |hl-CurSearch| is not defined, then |hl-Search| is used for
+ both. Note that only the matching text is highlighted, any offsets
+ are not applied.
+ See also: 'incsearch' and |:match|.
+ When you get bored looking at the highlighted matches, you can turn it
+ off with |:nohlsearch|. This does not change the option value, as
+ soon as you use a search command, the highlighting comes back.
+ 'redrawtime' specifies the maximum time spent on finding matches.
+ When the search pattern can match an end-of-line, Vim will try to
+ highlight all of the matched text. However, this depends on where the
+ search starts. This will be the first line in the window or the first
+ line below a closed fold. A match in a previous line which is not
+ drawn may not continue in a newly drawn line.
+ You can specify whether the highlight status is restored on startup
+ with the 'h' flag in 'shada' |shada-h|.
+ ]=],
+ full_name = 'hlsearch',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('highlight matches with last search pattern'),
+ type = 'bool',
+ varname = 'p_hls',
+ },
+ {
+ cb = 'did_set_title_icon',
+ defaults = {
+ if_true = false,
+ doc = 'off, on when title can be restored',
+ },
+ desc = [=[
+ When on, the icon text of the window will be set to the value of
+ 'iconstring' (if it is not empty), or to the name of the file
+ currently being edited. Only the last part of the name is used.
+ Overridden by the 'iconstring' option.
+ Only works if the terminal supports setting window icons.
+ ]=],
+ full_name = 'icon',
+ scope = { 'global' },
+ short_desc = N_('Vim set the text of the window icon'),
+ type = 'bool',
+ varname = 'p_icon',
+ },
+ {
+ cb = 'did_set_iconstring',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it will be used for the icon text of
+ the window. This happens only when the 'icon' option is on.
+ Only works if the terminal supports setting window icon text
+ When this option contains printf-style '%' items, they will be
+ expanded according to the rules used for 'statusline'. See
+ 'titlestring' for example settings.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ ]=],
+ full_name = 'iconstring',
+ modelineexpr = true,
+ scope = { 'global' },
+ short_desc = N_('to use for the Vim icon text'),
+ type = 'string',
+ varname = 'p_iconstring',
+ },
+ {
+ abbreviation = 'ic',
+ cb = 'did_set_ignorecase',
+ defaults = { if_true = false },
+ desc = [=[
+ Ignore case in search patterns, |cmdline-completion|, when
+ searching in the tags file, and |expr-==|.
+ Also see 'smartcase' and 'tagcase'.
+ Can be overruled by using "\c" or "\C" in the pattern, see
+ |/ignorecase|.
+ ]=],
+ full_name = 'ignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case in search patterns'),
+ type = 'bool',
+ varname = 'p_ic',
+ },
+ {
+ abbreviation = 'imc',
+ defaults = { if_true = false },
+ desc = [=[
+ When set the Input Method is always on when starting to edit a command
+ line, unless entering a search pattern (see 'imsearch' for that).
+ Setting this option is useful when your input method allows entering
+ English characters directly, e.g., when it's used to type accented
+ characters with dead keys.
+ ]=],
+ enable_if = false,
+ full_name = 'imcmdline',
+ scope = { 'global' },
+ short_desc = N_('use IM when starting to edit a command line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'imd',
+ defaults = {
+ if_true = false,
+ doc = 'off, on for some systems (SGI)',
+ },
+ desc = [=[
+ When set the Input Method is never used. This is useful to disable
+ the IM when it doesn't work properly.
+ Currently this option is on by default for SGI/IRIX machines. This
+ may change in later releases.
+ ]=],
+ enable_if = false,
+ full_name = 'imdisable',
+ scope = { 'global' },
+ short_desc = N_('do not use the IM in any mode'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'imi',
+ cb = 'did_set_iminsert',
+ defaults = { if_true = macros('B_IMODE_NONE') },
+ desc = [=[
+ Specifies whether :lmap or an Input Method (IM) is to be used in
+ Insert mode. Valid values:
+ 0 :lmap is off and IM is off
+ 1 :lmap is ON and IM is off
+ 2 :lmap is off and IM is ON
+ To always reset the option to zero when leaving Insert mode with <Esc>
+ this can be used: >
+ :inoremap <ESC> <ESC>:set iminsert=0<CR>
+ < This makes :lmap and IM turn off automatically when leaving Insert
+ mode.
+ Note that this option changes when using CTRL-^ in Insert mode
+ |i_CTRL-^|.
+ The value is set to 1 when setting 'keymap' to a valid keymap name.
+ It is also used for the argument of commands like "r" and "f".
+ ]=],
+ full_name = 'iminsert',
+ pv_name = 'p_imi',
+ scope = { 'buffer' },
+ short_desc = N_('use :lmap or IM in Insert mode'),
+ type = 'number',
+ varname = 'p_iminsert',
+ },
+ {
+ abbreviation = 'ims',
+ defaults = { if_true = macros('B_IMODE_USE_INSERT') },
+ desc = [=[
+ Specifies whether :lmap or an Input Method (IM) is to be used when
+ entering a search pattern. Valid values:
+ -1 the value of 'iminsert' is used, makes it look like
+ 'iminsert' is also used when typing a search pattern
+ 0 :lmap is off and IM is off
+ 1 :lmap is ON and IM is off
+ 2 :lmap is off and IM is ON
+ Note that this option changes when using CTRL-^ in Command-line mode
+ |c_CTRL-^|.
+ The value is set to 1 when it is not -1 and setting the 'keymap'
+ option to a valid keymap name.
+ ]=],
+ full_name = 'imsearch',
+ pv_name = 'p_ims',
+ scope = { 'buffer' },
+ short_desc = N_('use :lmap or IM when typing a search pattern'),
+ type = 'number',
+ varname = 'p_imsearch',
+ },
+ {
+ abbreviation = 'icm',
+ cb = 'did_set_inccommand',
+ defaults = { if_true = 'nosplit' },
+ desc = [=[
+ When nonempty, shows the effects of |:substitute|, |:smagic|,
+ |:snomagic| and user commands with the |:command-preview| flag as you
+ type.
+
+ Possible values:
+ nosplit Shows the effects of a command incrementally in the
+ buffer.
+ split Like "nosplit", but also shows partial off-screen
+ results in a preview window.
+
+ If the preview for built-in commands is too slow (exceeds
+ 'redrawtime') then 'inccommand' is automatically disabled until
+ |Command-line-mode| is done.
+ ]=],
+ expand_cb = 'expand_set_inccommand',
+ full_name = 'inccommand',
+ scope = { 'global' },
+ short_desc = N_('Live preview of substitution'),
+ type = 'string',
+ varname = 'p_icm',
+ },
+ {
+ abbreviation = 'inc',
+ alloced = true,
+ defaults = { if_true = '' },
+ desc = [=[
+ Pattern to be used to find an include command. It is a search
+ pattern, just like for the "/" command (See |pattern|). This option
+ is used for the commands "[i", "]I", "[d", etc.
+ Normally the 'isfname' option is used to recognize the file name that
+ comes after the matched pattern. But if "\zs" appears in the pattern
+ then the text matched from "\zs" to the end, or until "\ze" if it
+ appears, is used as the file name. Use this to include characters
+ that are not in 'isfname', such as a space. You can then use
+ 'includeexpr' to process the matched text.
+ See |option-backslash| about including spaces and backslashes.
+ ]=],
+ full_name = 'include',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('pattern to be used to find an include file'),
+ type = 'string',
+ varname = 'p_inc',
+ },
+ {
+ abbreviation = 'inex',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression to be used to transform the string found with the 'include'
+ option to a file name. Mostly useful to change "." to "/" for Java: >
+ :setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+ < The "v:fname" variable will be set to the file name that was detected.
+ Note the double backslash: the `:set` command first halves them, then
+ one remains in the value, where "\." matches a dot literally. For
+ simple character replacements `tr()` avoids the need for escaping: >
+ :setlocal includeexpr=tr(v:fname,'.','/')
+ <
+ Also used for the |gf| command if an unmodified file name can't be
+ found. Allows doing "gf" on the name after an 'include' statement.
+ Also used for |<cfile>|.
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ setlocal includeexpr=s:MyIncludeExpr(v:fname)
+ setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'includeexpr' |textlock|.
+ ]=],
+ full_name = 'includeexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used to process an include line'),
+ type = 'string',
+ varname = 'p_inex',
+ },
+ {
+ abbreviation = 'is',
+ defaults = { if_true = true },
+ desc = [=[
+ While typing a search command, show where the pattern, as it was typed
+ so far, matches. The matched string is highlighted. If the pattern
+ is invalid or not found, nothing is shown. The screen will be updated
+ often, this is only useful on fast terminals.
+ Note that the match will be shown, but the cursor will return to its
+ original position when no match is found and when pressing <Esc>. You
+ still need to finish the search command with <Enter> to move the
+ cursor to the match.
+ You can use the CTRL-G and CTRL-T keys to move to the next and
+ previous match. |c_CTRL-G| |c_CTRL-T|
+ Vim only searches for about half a second. With a complicated
+ pattern and/or a lot of text the match may not be found. This is to
+ avoid that Vim hangs while you are typing the pattern.
+ The |hl-IncSearch| highlight group determines the highlighting.
+ When 'hlsearch' is on, all matched strings are highlighted too while
+ typing a search command. See also: 'hlsearch'.
+ If you don't want to turn 'hlsearch' on, but want to highlight all
+ matches while searching, you can turn on and off 'hlsearch' with
+ autocmd. Example: >
+ augroup vimrc-incsearch-highlight
+ autocmd!
+ autocmd CmdlineEnter /,\? :set hlsearch
+ autocmd CmdlineLeave /,\? :set nohlsearch
+ augroup END
+ <
+ CTRL-L can be used to add one character from after the current match
+ to the command line. If 'ignorecase' and 'smartcase' are set and the
+ command line has no uppercase characters, the added character is
+ converted to lowercase.
+ CTRL-R CTRL-W can be used to add the word at the end of the current
+ match, excluding the characters that were already typed.
+ ]=],
+ full_name = 'incsearch',
+ scope = { 'global' },
+ short_desc = N_('highlight match while typing search pattern'),
+ type = 'bool',
+ varname = 'p_is',
+ },
+ {
+ abbreviation = 'inde',
+ alloced = true,
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to obtain the proper indent for a line.
+ It is used when a new line is created, for the |=| operator and
+ in Insert mode as specified with the 'indentkeys' option.
+ When this option is not empty, it overrules the 'cindent' and
+ 'smartindent' indenting. When 'lisp' is set, this option is
+ is only used when 'lispoptions' contains "expr:1".
+ The expression is evaluated with |v:lnum| set to the line number for
+ which the indent is to be computed. The cursor is also in this line
+ when the expression is evaluated (but it may be moved around).
+
+ If the expression starts with s: or |<SID>|, then it is replaced with
+ the script ID (|local-function|). Example: >
+ set indentexpr=s:MyIndentExpr()
+ set indentexpr=<SID>SomeIndentExpr()
+ < Otherwise, the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
+ The expression must return the number of spaces worth of indent. It
+ can return "-1" to keep the current indent (this means 'autoindent' is
+ used for the indent).
+ Functions useful for computing the indent are |indent()|, |cindent()|
+ and |lispindent()|.
+ The evaluation of the expression must not have side effects! It must
+ not change the text, jump to another window, etc. Afterwards the
+ cursor position is always restored, thus the cursor may be moved.
+ Normally this option would be set to call a function: >
+ :set indentexpr=GetMyIndent()
+ < Error messages will be suppressed, unless the 'debug' option contains
+ "msg".
+ See |indent-expression|.
+
+ The expression will be evaluated in the |sandbox| when set from a
+ modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'indentexpr' |textlock|.
+ ]=],
+ full_name = 'indentexpr',
+ modelineexpr = true,
+ scope = { 'buffer' },
+ short_desc = N_('expression used to obtain the indent of a line'),
+ type = 'string',
+ varname = 'p_inde',
+ },
+ {
+ abbreviation = 'indk',
+ alloced = true,
+ defaults = { if_true = '0{,0},0),0],:,0#,!^F,o,O,e' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of keys that, when typed in Insert mode, cause reindenting of
+ the current line. Only happens if 'indentexpr' isn't empty.
+ The format is identical to 'cinkeys', see |indentkeys-format|.
+ See |C-indenting| and |indent-expression|.
+ ]=],
+ full_name = 'indentkeys',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_("keys that trigger indenting with 'indentexpr'"),
+ type = 'string',
+ varname = 'p_indk',
+ },
+ {
+ abbreviation = 'inf',
+ defaults = { if_true = false },
+ desc = [=[
+ When doing keyword completion in insert mode |ins-completion|, and
+ 'ignorecase' is also on, the case of the match is adjusted depending
+ on the typed text. If the typed text contains a lowercase letter
+ where the match has an upper case letter, the completed part is made
+ lowercase. If the typed text has no lowercase letters and the match
+ has a lowercase letter where the typed text has an uppercase letter,
+ and there is a letter before it, the completed part is made uppercase.
+ With 'noinfercase' the match is used as-is.
+ ]=],
+ full_name = 'infercase',
+ scope = { 'buffer' },
+ short_desc = N_('adjust case of match for keyword completion'),
+ type = 'bool',
+ varname = 'p_inf',
+ },
+ {
+ abbreviation = 'im',
+ defaults = { if_true = false },
+ full_name = 'insertmode',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'isf',
+ cb = 'did_set_isopt',
+ defaults = {
+ condition = 'BACKSLASH_IN_FILENAME',
+ if_false = '@,48-57,/,.,-,_,+,,,#,$,%,~,=',
+ if_true = '@,48-57,/,\\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,=',
+ doc = [[for Windows:
+ "@,48-57,/,\,.,-,_,+,,,#,$,%,{,},[,],@-@,!,~,="
+ otherwise: "@,48-57,/,.,-,_,+,,,#,$,%,~,="]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ The characters specified by this option are included in file names and
+ path names. Filenames are used for commands like "gf", "[i" and in
+ the tags file. It is also used for "\f" in a |pattern|.
+ Multi-byte characters 256 and above are always included, only the
+ characters up to 255 are specified with this option.
+ For UTF-8 the characters 0xa0 to 0xff are included as well.
+ Think twice before adding white space to this option. Although a
+ space may appear inside a file name, the effect will be that Vim
+ doesn't know where a file name starts or ends when doing completion.
+ It most likely works better without a space in 'isfname'.
+
+ Note that on systems using a backslash as path separator, Vim tries to
+ do its best to make it work as you would expect. That is a bit
+ tricky, since Vi originally used the backslash to escape special
+ characters. Vim will not remove a backslash in front of a normal file
+ name character on these systems, but it will on Unix and alikes. The
+ '&' and '^' are not included by default, because these are special for
+ cmd.exe.
+
+ The format of this option is a list of parts, separated with commas.
+ Each part can be a single character number or a range. A range is two
+ character numbers with '-' in between. A character number can be a
+ decimal number between 0 and 255 or the ASCII character itself (does
+ not work for digits). Example:
+ "_,-,128-140,#-43" (include '_' and '-' and the range
+ 128 to 140 and '#' to 43)
+ If a part starts with '^', the following character number or range
+ will be excluded from the option. The option is interpreted from left
+ to right. Put the excluded character after the range where it is
+ included. To include '^' itself use it as the last character of the
+ option or the end of a range. Example:
+ "^a-z,#,^" (exclude 'a' to 'z', include '#' and '^')
+ If the character is '@', all characters where isalpha() returns TRUE
+ are included. Normally these are the characters a to z and A to Z,
+ plus accented characters. To include '@' itself use "@-@". Examples:
+ "@,^a-z" All alphabetic characters, excluding lower
+ case ASCII letters.
+ "a-z,A-Z,@-@" All letters plus the '@' character.
+ A comma can be included by using it where a character number is
+ expected. Example:
+ "48-57,,,_" Digits, comma and underscore.
+ A comma can be excluded by prepending a '^'. Example:
+ " -~,^,,9" All characters from space to '~', excluding
+ comma, plus <Tab>.
+ See |option-backslash| about including spaces and backslashes.
+ ]=],
+ full_name = 'isfname',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('characters included in file names and pathnames'),
+ type = 'string',
+ varname = 'p_isf',
+ },
+ {
+ abbreviation = 'isi',
+ cb = 'did_set_isopt',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '@,48-57,_,192-255',
+ if_true = '@,48-57,_,128-167,224-235',
+ doc = [[for Windows:
+ "@,48-57,_,128-167,224-235"
+ otherwise: "@,48-57,_,192-255"]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ The characters given by this option are included in identifiers.
+ Identifiers are used in recognizing environment variables and after a
+ match of the 'define' option. It is also used for "\i" in a
+ |pattern|. See 'isfname' for a description of the format of this
+ option. For '@' only characters up to 255 are used.
+ Careful: If you change this option, it might break expanding
+ environment variables. E.g., when '/' is included and Vim tries to
+ expand "$HOME/.local/state/nvim/shada/main.shada". Maybe you should
+ change 'iskeyword' instead.
+ ]=],
+ full_name = 'isident',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('characters included in identifiers'),
+ type = 'string',
+ varname = 'p_isi',
+ },
+ {
+ abbreviation = 'isk',
+ alloced = true,
+ cb = 'did_set_isopt',
+ defaults = { if_true = '@,48-57,_,192-255' },
+ deny_duplicates = true,
+ desc = [=[
+ Keywords are used in searching and recognizing with many commands:
+ "w", "*", "[i", etc. It is also used for "\k" in a |pattern|. See
+ 'isfname' for a description of the format of this option. For '@'
+ characters above 255 check the "word" character class (any character
+ that is not white space or punctuation).
+ For C programs you could use "a-z,A-Z,48-57,_,.,-,>".
+ For a help file it is set to all non-blank printable characters except
+ "*", '"' and '|' (so that CTRL-] on a command finds the help for that
+ command).
+ When the 'lisp' option is on the '-' character is always included.
+ This option also influences syntax highlighting, unless the syntax
+ uses |:syn-iskeyword|.
+ ]=],
+ full_name = 'iskeyword',
+ list = 'comma',
+ scope = { 'buffer' },
+ short_desc = N_('characters included in keywords'),
+ type = 'string',
+ varname = 'p_isk',
+ },
+ {
+ abbreviation = 'isp',
+ cb = 'did_set_isopt',
+ defaults = { if_true = '@,161-255' },
+ deny_duplicates = true,
+ desc = [=[
+ The characters given by this option are displayed directly on the
+ screen. It is also used for "\p" in a |pattern|. The characters from
+ space (ASCII 32) to '~' (ASCII 126) are always displayed directly,
+ even when they are not included in 'isprint' or excluded. See
+ 'isfname' for a description of the format of this option.
+
+ Non-printable characters are displayed with two characters:
+ 0 - 31 "^@" - "^_"
+ 32 - 126 always single characters
+ 127 "^?"
+ 128 - 159 "~@" - "~_"
+ 160 - 254 "| " - "|~"
+ 255 "~?"
+ Illegal bytes from 128 to 255 (invalid UTF-8) are
+ displayed as <xx>, with the hexadecimal value of the byte.
+ When 'display' contains "uhex" all unprintable characters are
+ displayed as <xx>.
+ The SpecialKey highlighting will be used for unprintable characters.
+ |hl-SpecialKey|
+
+ Multi-byte characters 256 and above are always included, only the
+ characters up to 255 are specified with this option. When a character
+ is printable but it is not available in the current font, a
+ replacement character will be shown.
+ Unprintable and zero-width Unicode characters are displayed as <xxxx>.
+ There is no option to specify these characters.
+ ]=],
+ full_name = 'isprint',
+ list = 'comma',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('printable characters'),
+ type = 'string',
+ varname = 'p_isp',
+ },
+ {
+ abbreviation = 'js',
+ defaults = { if_true = false },
+ desc = [=[
+ Insert two spaces after a '.', '?' and '!' with a join command.
+ Otherwise only one space is inserted.
+ ]=],
+ full_name = 'joinspaces',
+ scope = { 'global' },
+ short_desc = N_('two spaces after a period with a join command'),
+ type = 'bool',
+ varname = 'p_js',
+ },
+ {
+ abbreviation = 'jop',
+ cb = 'did_set_jumpoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of words that change the behavior of the |jumplist|.
+ stack Make the jumplist behave like the tagstack.
+ Relative location of entries in the jumplist is
+ preserved at the cost of discarding subsequent entries
+ when navigating backwards in the jumplist and then
+ jumping to a location. |jumplist-stack|
+
+ view When moving through the jumplist, |changelist|,
+ |alternate-file| or using |mark-motions| try to
+ restore the |mark-view| in which the action occurred.
+ ]=],
+ expand_cb = 'expand_set_jumpoptions',
+ full_name = 'jumpoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('Controls the behavior of the jumplist'),
+ type = 'string',
+ varname = 'p_jop',
+ },
+ {
+ abbreviation = 'kmp',
+ alloced = true,
+ cb = 'did_set_keymap',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of a keyboard mapping. See |mbyte-keymap|.
+ Setting this option to a valid keymap name has the side effect of
+ setting 'iminsert' to one, so that the keymap becomes effective.
+ 'imsearch' is also set to one, unless it was -1
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'keymap',
+ normal_fname_chars = true,
+ pri_mkrc = true,
+ pv_name = 'p_kmap',
+ redraw = { 'statuslines', 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('name of a keyboard mapping'),
+ type = 'string',
+ varname = 'p_keymap',
+ },
+ {
+ abbreviation = 'km',
+ cb = 'did_set_keymodel',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of comma-separated words, which enable special things that keys
+ can do. These values can be used:
+ startsel Using a shifted special key starts selection (either
+ Select mode or Visual mode, depending on "key" being
+ present in 'selectmode').
+ stopsel Using a not-shifted special key stops selection.
+ Special keys in this context are the cursor keys, <End>, <Home>,
+ <PageUp> and <PageDown>.
+ ]=],
+ expand_cb = 'expand_set_keymodel',
+ full_name = 'keymodel',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('enable starting/stopping selection with keys'),
+ type = 'string',
+ varname = 'p_km',
+ },
+ {
+ abbreviation = 'kp',
+ defaults = {
+ if_true = ':Man',
+ doc = '":Man", Windows: ":help"',
+ },
+ desc = [=[
+ Program to use for the |K| command. Environment variables are
+ expanded |:set_env|. ":help" may be used to access the Vim internal
+ help. (Note that previously setting the global option to the empty
+ value did this, which is now deprecated.)
+ When the first character is ":", the command is invoked as a Vim
+ Ex command prefixed with [count].
+ When "man" or "man -s" is used, Vim will automatically translate
+ a [count] for the "K" command to a section number.
+ See |option-backslash| about including spaces and backslashes.
+ Example: >
+ :set keywordprg=man\ -s
+ :set keywordprg=:Man
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'keywordprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for the "K" command'),
+ type = 'string',
+ varname = 'p_kp',
+ },
+ {
+ abbreviation = 'lmap',
+ cb = 'did_set_langmap',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ This option allows switching your keyboard into a special language
+ mode. When you are typing text in Insert mode the characters are
+ inserted directly. When in Normal mode the 'langmap' option takes
+ care of translating these special characters to the original meaning
+ of the key. This means you don't have to change the keyboard mode to
+ be able to execute Normal mode commands.
+ This is the opposite of the 'keymap' option, where characters are
+ mapped in Insert mode.
+ Also consider setting 'langremap' to off, to prevent 'langmap' from
+ applying to characters resulting from a mapping.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
+ Example (for Greek, in UTF-8): *greek* >
+ :set langmap=ΑA,ΒB,ΨC,ΔD,ΕE,ΦF,ΓG,ΗH,ΙI,ΞJ,ΚK,ΛL,ΜM,ΝN,ΟO,ΠP,QQ,ΡR,ΣS,ΤT,ΘU,ΩV,WW,ΧX,ΥY,ΖZ,αa,βb,ψc,δd,εe,φf,γg,ηh,ιi,ξj,κk,λl,μm,νn,οo,πp,qq,ρr,σs,τt,θu,ωv,ςw,χx,υy,ζz
+ < Example (exchanges meaning of z and y for commands): >
+ :set langmap=zy,yz,ZY,YZ
+ <
+ The 'langmap' option is a list of parts, separated with commas. Each
+ part can be in one of two forms:
+ 1. A list of pairs. Each pair is a "from" character immediately
+ followed by the "to" character. Examples: "aA", "aAbBcC".
+ 2. A list of "from" characters, a semi-colon and a list of "to"
+ characters. Example: "abc;ABC"
+ Example: "aA,fgh;FGH,cCdDeE"
+ Special characters need to be preceded with a backslash. These are
+ ";", ',', '"', '|' and backslash itself.
+
+ This will allow you to activate vim actions without having to switch
+ back and forth between the languages. Your language characters will
+ be understood as normal vim English characters (according to the
+ langmap mappings) in the following cases:
+ o Normal/Visual mode (commands, buffer/register names, user mappings)
+ o Insert/Replace Mode: Register names after CTRL-R
+ o Insert/Replace Mode: Mappings
+ Characters entered in Command-line mode will NOT be affected by
+ this option. Note that this option can be changed at any time
+ allowing to switch between mappings for different languages/encodings.
+ Use a mapping to avoid having to type it each time!
+ ]=],
+ full_name = 'langmap',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('alphabetic characters for other language mode'),
+ tags = { 'E357', 'E358' },
+ type = 'string',
+ varname = 'p_langmap',
+ },
+ {
+ abbreviation = 'lm',
+ defaults = { if_true = '' },
+ desc = [=[
+ Language to use for menu translation. Tells which file is loaded
+ from the "lang" directory in 'runtimepath': >
+ "lang/menu_" .. &langmenu .. ".vim"
+ < (without the spaces). For example, to always use the Dutch menus, no
+ matter what $LANG is set to: >
+ :set langmenu=nl_NL.ISO_8859-1
+ < When 'langmenu' is empty, |v:lang| is used.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ If your $LANG is set to a non-English language but you do want to use
+ the English menus: >
+ :set langmenu=none
+ < This option must be set before loading menus, switching on filetype
+ detection or syntax highlighting. Once the menus are defined setting
+ this option has no effect. But you could do this: >
+ :source $VIMRUNTIME/delmenu.vim
+ :set langmenu=de_DE.ISO_8859-1
+ :source $VIMRUNTIME/menu.vim
+ < Warning: This deletes all menus that you defined yourself!
+ ]=],
+ full_name = 'langmenu',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('language to be used for the menus'),
+ type = 'string',
+ varname = 'p_lm',
+ },
+ {
+ abbreviation = 'lnr',
+ cb = 'did_set_langnoremap',
+ defaults = { if_true = true },
+ full_name = 'langnoremap',
+ scope = { 'global' },
+ short_desc = N_("do not apply 'langmap' to mapped characters"),
+ type = 'bool',
+ varname = 'p_lnr',
+ },
+ {
+ abbreviation = 'lrm',
+ cb = 'did_set_langremap',
+ defaults = { if_true = false },
+ desc = [=[
+ When off, setting 'langmap' does not apply to characters resulting from
+ a mapping. If setting 'langmap' disables some of your mappings, make
+ sure this option is off.
+ ]=],
+ full_name = 'langremap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_lrm',
+ },
+ {
+ abbreviation = 'ls',
+ cb = 'did_set_laststatus',
+ defaults = { if_true = 2 },
+ desc = [=[
+ The value of this option influences when the last window will have a
+ status line:
+ 0: never
+ 1: only if there are at least two windows
+ 2: always
+ 3: always and ONLY the last window
+ The screen looks nicer with a status line if you have several
+ windows, but it takes another screen line. |status-line|
+ ]=],
+ full_name = 'laststatus',
+ redraw = { 'all_windows' },
+ scope = { 'global' },
+ short_desc = N_('tells when last window has status lines'),
+ type = 'number',
+ varname = 'p_ls',
+ },
+ {
+ abbreviation = 'lz',
+ defaults = { if_true = false },
+ desc = [=[
+ When this option is set, the screen will not be redrawn while
+ executing macros, registers and other commands that have not been
+ typed. Also, updating the window title is postponed. To force an
+ update use |:redraw|.
+ This may occasionally cause display errors. It is only meant to be set
+ temporarily when performing an operation where redrawing may cause
+ flickering or cause a slow down.
+ ]=],
+ full_name = 'lazyredraw',
+ scope = { 'global' },
+ short_desc = N_("don't redraw while executing macros"),
+ type = 'bool',
+ varname = 'p_lz',
+ },
+ {
+ abbreviation = 'lbr',
+ defaults = { if_true = false },
+ desc = [=[
+ If on, Vim will wrap long lines at a character in 'breakat' rather
+ than at the last character that fits on the screen. Unlike
+ 'wrapmargin' and 'textwidth', this does not insert <EOL>s in the file,
+ it only affects the way the file is displayed, not its contents.
+ If 'breakindent' is set, line is visually indented. Then, the value
+ of 'showbreak' is used to put in front of wrapped lines. This option
+ is not used when the 'wrap' option is off.
+ Note that <Tab> characters after an <EOL> are mostly not displayed
+ with the right amount of white space.
+ ]=],
+ full_name = 'linebreak',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('wrap long lines at a blank'),
+ type = 'bool',
+ },
+ {
+ defaults = {
+ if_true = macros('DFLT_ROWS'),
+ doc = '24 or terminal height',
+ },
+ desc = [=[
+ Number of lines of the Vim window.
+ Normally you don't need to set this. It is done automatically by the
+ terminal initialization code.
+ When Vim is running in the GUI or in a resizable window, setting this
+ option will cause the window size to be changed. When you only want
+ to use the size for the GUI, put the command in your |gvimrc| file.
+ Vim limits the number of lines to what fits on the screen. You can
+ use this command to get the tallest window possible: >
+ :set lines=999
+ < Minimum value is 2, maximum value is 1000.
+ ]=],
+ full_name = 'lines',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('of lines in the display'),
+ tags = { 'E593' },
+ type = 'number',
+ varname = 'p_lines',
+ },
+ {
+ abbreviation = 'lsp',
+ defaults = { if_true = 0 },
+ desc = [=[
+ only in the GUI
+ Number of pixel lines inserted between characters. Useful if the font
+ uses the full character cell height, making lines touch each other.
+ When non-zero there is room for underlining.
+ With some fonts there can be too much room between lines (to have
+ space for ascents and descents). Then it makes sense to set
+ 'linespace' to a negative value. This may cause display problems
+ though!
+ ]=],
+ full_name = 'linespace',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('number of pixel lines to use between characters'),
+ type = 'number',
+ varname = 'p_linespace',
+ },
+ {
+ cb = 'did_set_lisp',
+ defaults = { if_true = false },
+ desc = [=[
+ Lisp mode: When <Enter> is typed in insert mode set the indent for
+ the next line to Lisp standards (well, sort of). Also happens with
+ "cc" or "S". 'autoindent' must also be on for this to work. The 'p'
+ flag in 'cpoptions' changes the method of indenting: Vi compatible or
+ better. Also see 'lispwords'.
+ The '-' character is included in keyword characters. Redefines the
+ "=" operator to use this same indentation algorithm rather than
+ calling an external program if 'equalprg' is empty.
+ ]=],
+ full_name = 'lisp',
+ scope = { 'buffer' },
+ short_desc = N_('indenting for Lisp'),
+ type = 'bool',
+ varname = 'p_lisp',
+ },
+ {
+ abbreviation = 'lop',
+ cb = 'did_set_lispoptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of items that influence the Lisp indenting when
+ enabled with the |'lisp'| option. Currently only one item is
+ supported:
+ expr:1 use 'indentexpr' for Lisp indenting when it is set
+ expr:0 do not use 'indentexpr' for Lisp indenting (default)
+ Note that when using 'indentexpr' the `=` operator indents all the
+ lines, otherwise the first line is not indented (Vi-compatible).
+ ]=],
+ expand_cb = 'expand_set_lispoptions',
+ full_name = 'lispoptions',
+ list = 'onecomma',
+ pv_name = 'p_lop',
+ scope = { 'buffer' },
+ short_desc = N_('options for lisp indenting'),
+ type = 'string',
+ varname = 'p_lop',
+ },
+ {
+ abbreviation = 'lw',
+ defaults = {
+ if_true = macros('LISPWORD_VALUE'),
+ doc = 'is very long',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of words that influence the Lisp indenting when
+ enabled with the |'lisp'| option.
+ ]=],
+ full_name = 'lispwords',
+ list = 'onecomma',
+ pv_name = 'p_lw',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('words that change how lisp indenting works'),
+ type = 'string',
+ varname = 'p_lispwords',
+ },
+ {
+ defaults = { if_true = false },
+ desc = [=[
+ List mode: By default, show tabs as ">", trailing spaces as "-", and
+ non-breakable space characters as "+". Useful to see the difference
+ between tabs and spaces and for trailing blanks. Further changed by
+ the 'listchars' option.
+
+ The cursor is displayed at the start of the space a Tab character
+ occupies, not at the end as usual in Normal mode. To get this cursor
+ position while displaying Tabs with spaces, use: >
+ :set list lcs=tab:\ \
+ <
+ Note that list mode will also affect formatting (set with 'textwidth'
+ or 'wrapmargin') when 'cpoptions' includes 'L'. See 'listchars' for
+ changing the way tabs are displayed.
+ ]=],
+ full_name = 'list',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('<Tab> and <EOL>'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'lcs',
+ alloced = true,
+ cb = 'did_set_chars_option',
+ defaults = { if_true = 'tab:> ,trail:-,nbsp:+' },
+ deny_duplicates = true,
+ desc = [=[
+ Strings to use in 'list' mode and for the |:list| command. It is a
+ comma-separated list of string settings.
+
+ *lcs-eol*
+ eol:c Character to show at the end of each line. When
+ omitted, there is no extra character at the end of the
+ line.
+ *lcs-tab*
+ tab:xy[z] Two or three characters to be used to show a tab.
+ The third character is optional.
+
+ tab:xy The 'x' is always used, then 'y' as many times as will
+ fit. Thus "tab:>-" displays: >
+ >
+ >-
+ >--
+ etc.
+ <
+ tab:xyz The 'z' is always used, then 'x' is prepended, and
+ then 'y' is used as many times as will fit. Thus
+ "tab:<->" displays: >
+ >
+ <>
+ <->
+ <-->
+ etc.
+ <
+ When "tab:" is omitted, a tab is shown as ^I.
+ *lcs-space*
+ space:c Character to show for a space. When omitted, spaces
+ are left blank.
+ *lcs-multispace*
+ multispace:c...
+ One or more characters to use cyclically to show for
+ multiple consecutive spaces. Overrides the "space"
+ setting, except for single spaces. When omitted, the
+ "space" setting is used. For example,
+ `:set listchars=multispace:---+` shows ten consecutive
+ spaces as: >
+ ---+---+--
+ <
+ *lcs-lead*
+ lead:c Character to show for leading spaces. When omitted,
+ leading spaces are blank. Overrides the "space" and
+ "multispace" settings for leading spaces. You can
+ combine it with "tab:", for example: >
+ :set listchars+=tab:>-,lead:.
+ <
+ *lcs-leadmultispace*
+ leadmultispace:c...
+ Like the |lcs-multispace| value, but for leading
+ spaces only. Also overrides |lcs-lead| for leading
+ multiple spaces.
+ `:set listchars=leadmultispace:---+` shows ten
+ consecutive leading spaces as: >
+ ---+---+--XXX
+ <
+ Where "XXX" denotes the first non-blank characters in
+ the line.
+ *lcs-trail*
+ trail:c Character to show for trailing spaces. When omitted,
+ trailing spaces are blank. Overrides the "space" and
+ "multispace" settings for trailing spaces.
+ *lcs-extends*
+ extends:c Character to show in the last column, when 'wrap' is
+ off and the line continues beyond the right of the
+ screen.
+ *lcs-precedes*
+ precedes:c Character to show in the first visible column of the
+ physical line, when there is text preceding the
+ character visible in the first column.
+ *lcs-conceal*
+ conceal:c Character to show in place of concealed text, when
+ 'conceallevel' is set to 1. A space when omitted.
+ *lcs-nbsp*
+ nbsp:c Character to show for a non-breakable space character
+ (0xA0 (160 decimal) and U+202F). Left blank when
+ omitted.
+
+ The characters ':' and ',' should not be used. UTF-8 characters can
+ be used. All characters must be single width.
+
+ Each character can be specified as hex: >
+ set listchars=eol:\\x24
+ set listchars=eol:\\u21b5
+ set listchars=eol:\\U000021b5
+ < Note that a double backslash is used. The number of hex characters
+ must be exactly 2 for \\x, 4 for \\u and 8 for \\U.
+
+ Examples: >
+ :set lcs=tab:>-,trail:-
+ :set lcs=tab:>-,eol:<,nbsp:%
+ :set lcs=extends:>,precedes:<
+ < |hl-NonText| highlighting will be used for "eol", "extends" and
+ "precedes". |hl-Whitespace| for "nbsp", "space", "tab", "multispace",
+ "lead" and "trail".
+ ]=],
+ expand_cb = 'expand_set_chars_option',
+ full_name = 'listchars',
+ list = 'onecomma',
+ redraw = { 'current_window' },
+ scope = { 'global', 'window' },
+ short_desc = N_('characters for displaying in list mode'),
+ type = 'string',
+ varname = 'p_lcs',
+ },
+ {
+ abbreviation = 'lpl',
+ defaults = { if_true = true },
+ desc = [=[
+ When on the plugin scripts are loaded when starting up |load-plugins|.
+ This option can be reset in your |vimrc| file to disable the loading
+ of plugins.
+ Note that using the "-u NONE" and "--noplugin" command line arguments
+ reset this option. |-u| |--noplugin|
+ ]=],
+ full_name = 'loadplugins',
+ scope = { 'global' },
+ short_desc = N_('load plugin scripts when starting up'),
+ type = 'bool',
+ varname = 'p_lpl',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Changes the special characters that can be used in search patterns.
+ See |pattern|.
+ WARNING: Switching this option off most likely breaks plugins! That
+ is because many patterns assume it's on and will fail when it's off.
+ Only switch it off when working with old Vi scripts. In any other
+ situation write patterns that work when 'magic' is on. Include "\M"
+ when you want to |/\M|.
+ ]=],
+ full_name = 'magic',
+ scope = { 'global' },
+ short_desc = N_('special characters in search patterns'),
+ type = 'bool',
+ varname = 'p_magic',
+ },
+ {
+ abbreviation = 'mef',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of the errorfile for the |:make| command (see |:make_makeprg|)
+ and the |:grep| command.
+ When it is empty, an internally generated temp file will be used.
+ When "##" is included, it is replaced by a number to make the name
+ unique. This makes sure that the ":make" command doesn't overwrite an
+ existing file.
+ NOT used for the ":cf" command. See 'errorfile' for that.
+ Environment variables are expanded |:set_env|.
+ See |option-backslash| about including spaces and backslashes.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'makeef',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of the errorfile for ":make"'),
+ type = 'string',
+ varname = 'p_mef',
+ },
+ {
+ abbreviation = 'menc',
+ cb = 'did_set_encoding',
+ defaults = { if_true = '' },
+ desc = [=[
+ Encoding used for reading the output of external commands. When empty,
+ encoding is not converted.
+ This is used for `:make`, `:lmake`, `:grep`, `:lgrep`, `:grepadd`,
+ `:lgrepadd`, `:cfile`, `:cgetfile`, `:caddfile`, `:lfile`, `:lgetfile`,
+ and `:laddfile`.
+
+ This would be mostly useful when you use MS-Windows. If iconv is
+ enabled, setting 'makeencoding' to "char" has the same effect as
+ setting to the system locale encoding. Example: >
+ :set makeencoding=char " system locale is used
+ <
+ ]=],
+ expand_cb = 'expand_set_encoding',
+ full_name = 'makeencoding',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('Converts the output of external commands'),
+ type = 'string',
+ varname = 'p_menc',
+ },
+ {
+ abbreviation = 'mp',
+ defaults = { if_true = 'make' },
+ desc = [=[
+ Program to use for the ":make" command. See |:make_makeprg|.
+ This option may contain '%' and '#' characters (see |:_%| and |:_#|),
+ which are expanded to the current and alternate file name. Use |::S|
+ to escape file names in case they contain special characters.
+ Environment variables are expanded |:set_env|. See |option-backslash|
+ about including spaces and backslashes.
+ Note that a '|' must be escaped twice: once for ":set" and once for
+ the interpretation of a command. When you use a filter called
+ "myfilter" do it like this: >
+ :set makeprg=gmake\ \\\|\ myfilter
+ < The placeholder "$*" can be given (even multiple times) to specify
+ where the arguments will be included, for example: >
+ :set makeprg=latex\ \\\\nonstopmode\ \\\\input\\{$*}
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'makeprg',
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('program to use for the ":make" command'),
+ type = 'string',
+ varname = 'p_mp',
+ },
+ {
+ abbreviation = 'mps',
+ alloced = true,
+ cb = 'did_set_matchpairs',
+ defaults = { if_true = '(:),{:},[:]' },
+ deny_duplicates = true,
+ desc = [=[
+ Characters that form pairs. The |%| command jumps from one to the
+ other.
+ Only character pairs are allowed that are different, thus you cannot
+ jump between two double quotes.
+ The characters must be separated by a colon.
+ The pairs must be separated by a comma. Example for including '<' and
+ '>' (for HTML): >
+ :set mps+=<:>
+
+ < A more exotic example, to jump between the '=' and ';' in an
+ assignment, useful for languages like C and Java: >
+ :au FileType c,cpp,java set mps+==:;
+
+ < For a more advanced way of using "%", see the matchit.vim plugin in
+ the $VIMRUNTIME/plugin directory. |add-local-help|
+ ]=],
+ full_name = 'matchpairs',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('pairs of characters that "%" can match'),
+ type = 'string',
+ varname = 'p_mps',
+ },
+ {
+ abbreviation = 'mat',
+ defaults = { if_true = 5 },
+ desc = [=[
+ Tenths of a second to show the matching paren, when 'showmatch' is
+ set. Note that this is not in milliseconds, like other options that
+ set a time. This is to be compatible with Nvi.
+ ]=],
+ full_name = 'matchtime',
+ scope = { 'global' },
+ short_desc = N_('tenths of a second to show matching paren'),
+ type = 'number',
+ varname = 'p_mat',
+ },
+ {
+ abbreviation = 'mco',
+ defaults = { if_true = 6 },
+ full_name = 'maxcombine',
+ scope = { 'global' },
+ short_desc = N_('maximum nr of combining characters displayed'),
+ type = 'number',
+ varname = 'p_mco',
+ },
+ {
+ abbreviation = 'mfd',
+ defaults = { if_true = 100 },
+ desc = [=[
+ Maximum depth of function calls for user functions. This normally
+ catches endless recursion. When using a recursive function with
+ more depth, set 'maxfuncdepth' to a bigger number. But this will use
+ more memory, there is the danger of failing when memory is exhausted.
+ Increasing this limit above 200 also changes the maximum for Ex
+ command recursion, see |E169|.
+ See also |:function|.
+ ]=],
+ full_name = 'maxfuncdepth',
+ scope = { 'global' },
+ short_desc = N_('maximum recursive depth for user functions'),
+ type = 'number',
+ varname = 'p_mfd',
+ },
+ {
+ abbreviation = 'mmd',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum number of times a mapping is done without resulting in a
+ character to be used. This normally catches endless mappings, like
+ ":map x y" with ":map y x". It still does not catch ":map g wg",
+ because the 'w' is used before the next mapping is done. See also
+ |key-mapping|.
+ ]=],
+ full_name = 'maxmapdepth',
+ scope = { 'global' },
+ short_desc = N_('maximum recursive depth for mapping'),
+ tags = { 'E223' },
+ type = 'number',
+ varname = 'p_mmd',
+ },
+ {
+ abbreviation = 'mmp',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum amount of memory (in Kbyte) to use for pattern matching.
+ The maximum value is about 2000000. Use this to work without a limit.
+ *E363*
+ When Vim runs into the limit it gives an error message and mostly
+ behaves like CTRL-C was typed.
+ Running into the limit often means that the pattern is very
+ inefficient or too complex. This may already happen with the pattern
+ "\(.\)*" on a very long line. ".*" works much better.
+ Might also happen on redraw, when syntax rules try to match a complex
+ text structure.
+ Vim may run out of memory before hitting the 'maxmempattern' limit, in
+ which case you get an "Out of memory" error instead.
+ ]=],
+ full_name = 'maxmempattern',
+ scope = { 'global' },
+ short_desc = N_('maximum memory (in Kbyte) used for pattern search'),
+ type = 'number',
+ varname = 'p_mmp',
+ },
+ {
+ abbreviation = 'mis',
+ defaults = { if_true = 25 },
+ desc = [=[
+ Maximum number of items to use in a menu. Used for menus that are
+ generated from a list of items, e.g., the Buffers menu. Changing this
+ option has no direct effect, the menu must be refreshed first.
+ ]=],
+ full_name = 'menuitems',
+ scope = { 'global' },
+ short_desc = N_('maximum number of items in a menu'),
+ type = 'number',
+ varname = 'p_mis',
+ },
+ {
+ abbreviation = 'msm',
+ cb = 'did_set_mkspellmem',
+ defaults = { if_true = '460000,2000,500' },
+ desc = [=[
+ Parameters for |:mkspell|. This tunes when to start compressing the
+ word tree. Compression can be slow when there are many words, but
+ it's needed to avoid running out of memory. The amount of memory used
+ per word depends very much on how similar the words are, that's why
+ this tuning is complicated.
+
+ There are three numbers, separated by commas: >
+ {start},{inc},{added}
+ <
+ For most languages the uncompressed word tree fits in memory. {start}
+ gives the amount of memory in Kbyte that can be used before any
+ compression is done. It should be a bit smaller than the amount of
+ memory that is available to Vim.
+
+ When going over the {start} limit the {inc} number specifies the
+ amount of memory in Kbyte that can be allocated before another
+ compression is done. A low number means compression is done after
+ less words are added, which is slow. A high number means more memory
+ will be allocated.
+
+ After doing compression, {added} times 1024 words can be added before
+ the {inc} limit is ignored and compression is done when any extra
+ amount of memory is needed. A low number means there is a smaller
+ chance of hitting the {inc} limit, less memory is used but it's
+ slower.
+
+ The languages for which these numbers are important are Italian and
+ Hungarian. The default works for when you have about 512 Mbyte. If
+ you have 1 Gbyte you could use: >
+ :set mkspellmem=900000,3000,800
+ < If you have less than 512 Mbyte |:mkspell| may fail for some
+ languages, no matter what you set 'mkspellmem' to.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'mkspellmem',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('memory used before |:mkspell| compresses the tree'),
+ type = 'string',
+ varname = 'p_msm',
+ },
+ {
+ abbreviation = 'ml',
+ defaults = {
+ if_true = true,
+ doc = 'on (off for root)',
+ },
+ desc = [=[
+ If 'modeline' is on 'modelines' gives the number of lines that is
+ checked for set commands. If 'modeline' is off or 'modelines' is zero
+ no lines are checked. See |modeline|.
+ ]=],
+ full_name = 'modeline',
+ scope = { 'buffer' },
+ short_desc = N_('recognize modelines at start or end of file'),
+ type = 'bool',
+ varname = 'p_ml',
+ },
+ {
+ abbreviation = 'mle',
+ defaults = { if_true = false },
+ desc = [=[
+ When on allow some options that are an expression to be set in the
+ modeline. Check the option for whether it is affected by
+ 'modelineexpr'. Also see |modeline|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'modelineexpr',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('allow some options to be set in modeline'),
+ type = 'bool',
+ varname = 'p_mle',
+ },
+ {
+ abbreviation = 'mls',
+ defaults = { if_true = 5 },
+ desc = [=[
+ If 'modeline' is on 'modelines' gives the number of lines that is
+ checked for set commands. If 'modeline' is off or 'modelines' is zero
+ no lines are checked. See |modeline|.
+
+ ]=],
+ full_name = 'modelines',
+ scope = { 'global' },
+ short_desc = N_('number of lines checked for modelines'),
+ type = 'number',
+ varname = 'p_mls',
+ },
+ {
+ abbreviation = 'ma',
+ cb = 'did_set_modifiable',
+ defaults = { if_true = true },
+ desc = [=[
+ When off the buffer contents cannot be changed. The 'fileformat' and
+ 'fileencoding' options also can't be changed.
+ Can be reset on startup with the |-M| command line argument.
+ ]=],
+ full_name = 'modifiable',
+ noglob = true,
+ scope = { 'buffer' },
+ short_desc = N_('changes to the text are not possible'),
+ tags = { 'E21' },
+ type = 'bool',
+ varname = 'p_ma',
+ },
+ {
+ abbreviation = 'mod',
+ cb = 'did_set_modified',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the buffer is considered to be modified. This option is set
+ when:
+ 1. A change was made to the text since it was last written. Using the
+ |undo| command to go back to the original text will reset the
+ option. But undoing changes that were made before writing the
+ buffer will set the option again, since the text is different from
+ when it was written.
+ 2. 'fileformat' or 'fileencoding' is different from its original
+ value. The original value is set when the buffer is read or
+ written. A ":set nomodified" command also resets the original
+ values to the current values and the 'modified' option will be
+ reset.
+ Similarly for 'eol' and 'bomb'.
+ This option is not set when a change is made to the buffer as the
+ result of a BufNewFile, BufRead/BufReadPost, BufWritePost,
+ FileAppendPost or VimLeave autocommand event. See |gzip-example| for
+ an explanation.
+ When 'buftype' is "nowrite" or "nofile" this option may be set, but
+ will be ignored.
+ Note that the text may actually be the same, e.g. 'modified' is set
+ when using "rA" on an "A".
+ ]=],
+ full_name = 'modified',
+ no_mkrc = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('buffer has been modified'),
+ type = 'bool',
+ varname = 'p_mod',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ When on, listings pause when the whole screen is filled. You will get
+ the |more-prompt|. When this option is off there are no pauses, the
+ listing continues until finished.
+ ]=],
+ full_name = 'more',
+ scope = { 'global' },
+ short_desc = N_('listings when the whole screen is filled'),
+ type = 'bool',
+ varname = 'p_more',
+ },
+ {
+ cb = 'did_set_mouse',
+ defaults = { if_true = 'nvi' },
+ desc = [=[
+ Enables mouse support. For example, to enable the mouse in Normal mode
+ and Visual mode: >
+ :set mouse=nv
+ <
+ To temporarily disable mouse support, hold the shift key while using
+ the mouse.
+
+ Mouse support can be enabled for different modes:
+ n Normal mode
+ v Visual mode
+ i Insert mode
+ c Command-line mode
+ h all previous modes when editing a help file
+ a all previous modes
+ r for |hit-enter| and |more-prompt| prompt
+
+ Left-click anywhere in a text buffer to place the cursor there. This
+ works with operators too, e.g. type |d| then left-click to delete text
+ from the current cursor position to the position where you clicked.
+
+ Drag the |status-line| or vertical separator of a window to resize it.
+
+ If enabled for "v" (Visual mode) then double-click selects word-wise,
+ triple-click makes it line-wise, and quadruple-click makes it
+ rectangular block-wise.
+
+ For scrolling with a mouse wheel see |scroll-mouse-wheel|.
+
+ Note: When enabling the mouse in a terminal, copy/paste will use the
+ "* register if possible. See also 'clipboard'.
+
+ Related options:
+ 'mousefocus' window focus follows mouse pointer
+ 'mousemodel' what mouse button does which action
+ 'mousehide' hide mouse pointer while typing text
+ 'selectmode' whether to start Select mode or Visual mode
+ ]=],
+ expand_cb = 'expand_set_mouse',
+ full_name = 'mouse',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('the use of mouse clicks'),
+ type = 'string',
+ varname = 'p_mouse',
+ },
+ {
+ abbreviation = 'mousef',
+ defaults = { if_true = false },
+ desc = [=[
+ The window that the mouse pointer is on is automatically activated.
+ When changing the window layout or window focus in another way, the
+ mouse pointer is moved to the window with keyboard focus. Off is the
+ default because it makes using the pull down menus a little goofy, as
+ a pointer transit may activate a window unintentionally.
+ ]=],
+ full_name = 'mousefocus',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('keyboard focus follows the mouse'),
+ type = 'bool',
+ varname = 'p_mousef',
+ },
+ {
+ abbreviation = 'mh',
+ defaults = { if_true = true },
+ desc = [=[
+ only in the GUI
+ When on, the mouse pointer is hidden when characters are typed.
+ The mouse pointer is restored when the mouse is moved.
+ ]=],
+ enable_if = false,
+ full_name = 'mousehide',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('hide mouse pointer while typing'),
+ type = 'bool',
+ varname = 'p_mh',
+ },
+ {
+ abbreviation = 'mousem',
+ cb = 'did_set_mousemodel',
+ defaults = { if_true = 'popup_setpos' },
+ desc = [=[
+ Sets the model to use for the mouse. The name mostly specifies what
+ the right mouse button is used for:
+ extend Right mouse button extends a selection. This works
+ like in an xterm.
+ popup Right mouse button pops up a menu. The shifted left
+ mouse button extends a selection. This works like
+ with Microsoft Windows.
+ popup_setpos Like "popup", but the cursor will be moved to the
+ position where the mouse was clicked, and thus the
+ selected operation will act upon the clicked object.
+ If clicking inside a selection, that selection will
+ be acted upon, i.e. no cursor move. This implies of
+ course, that right clicking outside a selection will
+ end Visual mode.
+ Overview of what button does what for each model:
+ mouse extend popup(_setpos) ~
+ left click place cursor place cursor
+ left drag start selection start selection
+ shift-left search word extend selection
+ right click extend selection popup menu (place cursor)
+ right drag extend selection -
+ middle click paste paste
+
+ In the "popup" model the right mouse button produces a pop-up menu.
+ Nvim creates a default |popup-menu| but you can redefine it.
+
+ Note that you can further refine the meaning of buttons with mappings.
+ See |mouse-overview|. But mappings are NOT used for modeless selection.
+
+ Example: >
+ :map <S-LeftMouse> <RightMouse>
+ :map <S-LeftDrag> <RightDrag>
+ :map <S-LeftRelease> <RightRelease>
+ :map <2-S-LeftMouse> <2-RightMouse>
+ :map <2-S-LeftDrag> <2-RightDrag>
+ :map <2-S-LeftRelease> <2-RightRelease>
+ :map <3-S-LeftMouse> <3-RightMouse>
+ :map <3-S-LeftDrag> <3-RightDrag>
+ :map <3-S-LeftRelease> <3-RightRelease>
+ :map <4-S-LeftMouse> <4-RightMouse>
+ :map <4-S-LeftDrag> <4-RightDrag>
+ :map <4-S-LeftRelease> <4-RightRelease>
+ <
+ Mouse commands requiring the CTRL modifier can be simulated by typing
+ the "g" key before using the mouse:
+ "g<LeftMouse>" is "<C-LeftMouse> (jump to tag under mouse click)
+ "g<RightMouse>" is "<C-RightMouse> ("CTRL-T")
+ ]=],
+ expand_cb = 'expand_set_mousemodel',
+ full_name = 'mousemodel',
+ scope = { 'global' },
+ short_desc = N_('changes meaning of mouse buttons'),
+ type = 'string',
+ varname = 'p_mousem',
+ },
+ {
+ abbreviation = 'mousemev',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, mouse move events are delivered to the input queue and are
+ available for mapping. The default, off, avoids the mouse movement
+ overhead except when needed.
+ Warning: Setting this option can make pending mappings to be aborted
+ when the mouse is moved.
+ ]=],
+ full_name = 'mousemoveevent',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('deliver mouse move events to input queue'),
+ type = 'bool',
+ varname = 'p_mousemev',
+ },
+ {
+ cb = 'did_set_mousescroll',
+ defaults = { if_true = 'ver:3,hor:6' },
+ desc = [=[
+ This option controls the number of lines / columns to scroll by when
+ scrolling with a mouse wheel (|scroll-mouse-wheel|). The option is
+ a comma-separated list. Each part consists of a direction and a count
+ as follows:
+ direction:count,direction:count
+ Direction is one of either "hor" or "ver". "hor" controls horizontal
+ scrolling and "ver" controls vertical scrolling. Count sets the amount
+ to scroll by for the given direction, it should be a non negative
+ integer. Each direction should be set at most once. If a direction
+ is omitted, a default value is used (6 for horizontal scrolling and 3
+ for vertical scrolling). You can disable mouse scrolling by using
+ a count of 0.
+
+ Example: >
+ :set mousescroll=ver:5,hor:2
+ < Will make Nvim scroll 5 lines at a time when scrolling vertically, and
+ scroll 2 columns at a time when scrolling horizontally.
+ ]=],
+ expand_cb = 'expand_set_mousescroll',
+ full_name = 'mousescroll',
+ list = 'comma',
+ scope = { 'global' },
+ short_desc = N_('amount to scroll by when scrolling with a mouse'),
+ tags = { 'E5080' },
+ type = 'string',
+ varname = 'p_mousescroll',
+ vi_def = true,
+ },
+ {
+ abbreviation = 'mouses',
+ deny_duplicates = true,
+ defaults = {
+ if_true = '',
+ doc = [["i:beam,r:beam,s:updown,sd:cross,
+ m:no,ml:up-arrow,v:rightup-arrow"]],
+ },
+ desc = [=[
+ This option tells Vim what the mouse pointer should look like in
+ different modes. The option is a comma-separated list of parts, much
+ like used for 'guicursor'. Each part consist of a mode/location-list
+ and an argument-list:
+ mode-list:shape,mode-list:shape,..
+ The mode-list is a dash separated list of these modes/locations:
+ In a normal window: ~
+ n Normal mode
+ v Visual mode
+ ve Visual mode with 'selection' "exclusive" (same as 'v',
+ if not specified)
+ o Operator-pending mode
+ i Insert mode
+ r Replace mode
+
+ Others: ~
+ c appending to the command-line
+ ci inserting in the command-line
+ cr replacing in the command-line
+ m at the 'Hit ENTER' or 'More' prompts
+ ml idem, but cursor in the last line
+ e any mode, pointer below last window
+ s any mode, pointer on a status line
+ sd any mode, while dragging a status line
+ vs any mode, pointer on a vertical separator line
+ vd any mode, while dragging a vertical separator line
+ a everywhere
+
+ The shape is one of the following:
+ avail name looks like ~
+ w x arrow Normal mouse pointer
+ w x blank no pointer at all (use with care!)
+ w x beam I-beam
+ w x updown up-down sizing arrows
+ w x leftright left-right sizing arrows
+ w x busy The system's usual busy pointer
+ w x no The system's usual "no input" pointer
+ x udsizing indicates up-down resizing
+ x lrsizing indicates left-right resizing
+ x crosshair like a big thin +
+ x hand1 black hand
+ x hand2 white hand
+ x pencil what you write with
+ x question big ?
+ x rightup-arrow arrow pointing right-up
+ w x up-arrow arrow pointing up
+ x <number> any X11 pointer number (see X11/cursorfont.h)
+
+ The "avail" column contains a 'w' if the shape is available for Win32,
+ x for X11.
+ Any modes not specified or shapes not available use the normal mouse
+ pointer.
+
+ Example: >
+ :set mouseshape=s:udsizing,m:no
+ < will make the mouse turn to a sizing arrow over the status lines and
+ indicate no input when the hit-enter prompt is displayed (since
+ clicking the mouse has no effect in this state.)
+ ]=],
+ enable_if = false,
+ full_name = 'mouseshape',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('shape of the mouse pointer in different modes'),
+ tags = { 'E547' },
+ type = 'string',
+ },
+ {
+ abbreviation = 'mouset',
+ defaults = { if_true = 500 },
+ desc = [=[
+ Defines the maximum time in msec between two mouse clicks for the
+ second click to be recognized as a multi click.
+ ]=],
+ full_name = 'mousetime',
+ scope = { 'global' },
+ short_desc = N_('max time between mouse double-click'),
+ type = 'number',
+ varname = 'p_mouset',
+ },
+ {
+ abbreviation = 'nf',
+ alloced = true,
+ cb = 'did_set_nrformats',
+ defaults = { if_true = 'bin,hex' },
+ deny_duplicates = true,
+ desc = [=[
+ This defines what bases Vim will consider for numbers when using the
+ CTRL-A and CTRL-X commands for adding to and subtracting from a number
+ respectively; see |CTRL-A| for more info on these commands.
+ alpha If included, single alphabetical characters will be
+ incremented or decremented. This is useful for a list with a
+ letter index a), b), etc. *octal-nrformats*
+ octal If included, numbers that start with a zero will be considered
+ to be octal. Example: Using CTRL-A on "007" results in "010".
+ hex If included, numbers starting with "0x" or "0X" will be
+ considered to be hexadecimal. Example: Using CTRL-X on
+ "0x100" results in "0x0ff".
+ bin If included, numbers starting with "0b" or "0B" will be
+ considered to be binary. Example: Using CTRL-X on
+ "0b1000" subtracts one, resulting in "0b0111".
+ unsigned If included, numbers are recognized as unsigned. Thus a
+ leading dash or negative sign won't be considered as part of
+ the number. Examples:
+ Using CTRL-X on "2020" in "9-2020" results in "9-2019"
+ (without "unsigned" it would become "9-2021").
+ Using CTRL-A on "2020" in "9-2020" results in "9-2021"
+ (without "unsigned" it would become "9-2019").
+ Using CTRL-X on "0" or CTRL-A on "18446744073709551615"
+ (2^64 - 1) has no effect, overflow is prevented.
+ Numbers which simply begin with a digit in the range 1-9 are always
+ considered decimal. This also happens for numbers that are not
+ recognized as octal or hex.
+ ]=],
+ expand_cb = 'expand_set_nrformats',
+ full_name = 'nrformats',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('number formats recognized for CTRL-A command'),
+ type = 'string',
+ varname = 'p_nf',
+ },
+ {
+ abbreviation = 'nu',
+ cb = 'did_set_number_relativenumber',
+ defaults = { if_true = false },
+ desc = [=[
+ Print the line number in front of each line. When the 'n' option is
+ excluded from 'cpoptions' a wrapped line will not use the column of
+ line numbers.
+ Use the 'numberwidth' option to adjust the room for the line number.
+ When a long, wrapped line doesn't start with the first character, '-'
+ characters are put before the number.
+ For highlighting see |hl-LineNr|, |hl-CursorLineNr|, and the
+ |:sign-define| "numhl" argument.
+ *number_relativenumber*
+ The 'relativenumber' option changes the displayed number to be
+ relative to the cursor. Together with 'number' there are these
+ four combinations (cursor in line 3):
+
+ 'nonu' 'nu' 'nonu' 'nu'
+ 'nornu' 'nornu' 'rnu' 'rnu'
+ >
+ |apple | 1 apple | 2 apple | 2 apple
+ |pear | 2 pear | 1 pear | 1 pear
+ |nobody | 3 nobody | 0 nobody |3 nobody
+ |there | 4 there | 1 there | 1 there
+ <
+ ]=],
+ full_name = 'number',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('print the line number in front of each line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'nuw',
+ cb = 'did_set_numberwidth',
+ defaults = { if_true = 4 },
+ desc = [=[
+ Minimal number of columns to use for the line number. Only relevant
+ when the 'number' or 'relativenumber' option is set or printing lines
+ with a line number. Since one space is always between the number and
+ the text, there is one less character for the number itself.
+ The value is the minimum width. A bigger width is used when needed to
+ fit the highest line number in the buffer respectively the number of
+ rows in the window, depending on whether 'number' or 'relativenumber'
+ is set. Thus with the Vim default of 4 there is room for a line number
+ up to 999. When the buffer has 1000 lines five columns will be used.
+ The minimum value is 1, the maximum value is 20.
+ ]=],
+ full_name = 'numberwidth',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('number of columns used for the line number'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'ofu',
+ alloced = true,
+ cb = 'did_set_omnifunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for Insert mode omni
+ completion with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O|
+ See |complete-functions| for an explanation of how the function is
+ invoked and what it should return. The value can be the name of a
+ function, a |lambda| or a |Funcref|. See |option-value-function| for
+ more information.
+ This option is usually set by a filetype plugin:
+ |:filetype-plugin-on|
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'omnifunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function for filetype-specific completion'),
+ type = 'string',
+ varname = 'p_ofu',
+ },
+ {
+ abbreviation = 'odev',
+ defaults = { if_true = false },
+ desc = [=[
+ only for Windows
+ Enable reading and writing from devices. This may get Vim stuck on a
+ device that can be opened but doesn't actually do the I/O. Therefore
+ it is off by default.
+ Note that on Windows editing "aux.h", "lpt1.txt" and the like also
+ result in editing a device.
+ ]=],
+ enable_if = false,
+ full_name = 'opendevice',
+ scope = { 'global' },
+ short_desc = N_('allow reading/writing devices on MS-Windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'opfunc',
+ cb = 'did_set_operatorfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be called by the |g@| operator.
+ See |:map-operator| for more info and an example. The value can be
+ the name of a function, a |lambda| or a |Funcref|. See
+ |option-value-function| for more information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'operatorfunc',
+ func = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('function to be called for |g@| operator'),
+ type = 'string',
+ varname = 'p_opfunc',
+ },
+ {
+ abbreviation = 'pp',
+ cb = 'did_set_runtimepackpath',
+ defaults = {
+ if_true = '',
+ doc = "see 'runtimepath'",
+ meta = '...',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ Directories used to find packages.
+ See |packages| and |packages-runtimepath|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'packpath',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories used for packages'),
+ type = 'string',
+ varname = 'p_pp',
+ },
+ {
+ abbreviation = 'para',
+ defaults = { if_true = 'IPLPPPQPP TPHPLIPpLpItpplpipbp' },
+ desc = [=[
+ Specifies the nroff macros that separate paragraphs. These are pairs
+ of two letters (see |object-motions|).
+ ]=],
+ full_name = 'paragraphs',
+ scope = { 'global' },
+ short_desc = N_('nroff macros that separate paragraphs'),
+ type = 'string',
+ varname = 'p_para',
+ },
+ {
+ cb = 'did_set_paste',
+ defaults = { if_true = false },
+ full_name = 'paste',
+ pri_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('pasting text'),
+ type = 'bool',
+ varname = 'p_paste',
+ },
+ {
+ abbreviation = 'pt',
+ defaults = { if_true = '' },
+ full_name = 'pastetoggle',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'pex',
+ cb = 'did_set_optexpr',
+ defaults = { if_true = '' },
+ desc = [=[
+ Expression which is evaluated to apply a patch to a file and generate
+ the resulting new version of the file. See |diff-patchexpr|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'patchexpr',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('expression used to patch a file'),
+ type = 'string',
+ varname = 'p_pex',
+ },
+ {
+ abbreviation = 'pm',
+ cb = 'did_set_backupext_or_patchmode',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty the oldest version of a file is kept. This can be used
+ to keep the original version of a file if you are changing files in a
+ source distribution. Only the first time that a file is written a
+ copy of the original file will be kept. The name of the copy is the
+ name of the original file with the string in the 'patchmode' option
+ appended. This option should start with a dot. Use a string like
+ ".orig" or ".org". 'backupdir' must not be empty for this to work
+ (Detail: The backup file is renamed to the patchmode file after the
+ new file has been successfully written, that's why it must be possible
+ to write a backup file). If there was no file to be backed up, an
+ empty file is created.
+ When the 'backupskip' pattern matches, a patchmode file is not made.
+ Using 'patchmode' for compressed files appends the extension at the
+ end (e.g., "file.gz.orig"), thus the resulting name isn't always
+ recognized as a compressed file.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'patchmode',
+ normal_fname_chars = true,
+ scope = { 'global' },
+ short_desc = N_('keep the oldest version of a file'),
+ tags = { 'E205', 'E206' },
+ type = 'string',
+ varname = 'p_pm',
+ },
+ {
+ abbreviation = 'pa',
+ defaults = { if_true = '.,,' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a list of directories which will be searched when using the
+ |gf|, [f, ]f, ^Wf, |:find|, |:sfind|, |:tabfind| and other commands,
+ provided that the file being searched for has a relative path (not
+ starting with "/", "./" or "../"). The directories in the 'path'
+ option may be relative or absolute.
+ - Use commas to separate directory names: >
+ :set path=.,/usr/local/include,/usr/include
+ < - Spaces can also be used to separate directory names. To have a
+ space in a directory name, precede it with an extra backslash, and
+ escape the space: >
+ :set path=.,/dir/with\\\ space
+ < - To include a comma in a directory name precede it with an extra
+ backslash: >
+ :set path=.,/dir/with\\,comma
+ < - To search relative to the directory of the current file, use: >
+ :set path=.
+ < - To search in the current directory use an empty string between two
+ commas: >
+ :set path=,,
+ < - A directory name may end in a ':' or '/'.
+ - Environment variables are expanded |:set_env|.
+ - When using |netrw.vim| URLs can be used. For example, adding
+ "https://www.vim.org" will make ":find index.html" work.
+ - Search upwards and downwards in a directory tree using "*", "**" and
+ ";". See |file-searching| for info and syntax.
+ - Careful with '\' characters, type two to get one in the option: >
+ :set path=.,c:\\include
+ < Or just use '/' instead: >
+ :set path=.,c:/include
+ < Don't forget "." or files won't even be found in the same directory as
+ the file!
+ The maximum length is limited. How much depends on the system, mostly
+ it is something like 256 or 1024 characters.
+ You can check if all the include files are found, using the value of
+ 'path', see |:checkpath|.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ directories from the list. This avoids problems when a future version
+ uses another default. To remove the current directory use: >
+ :set path-=
+ < To add the current directory use: >
+ :set path+=
+ < To use an environment variable, you probably need to replace the
+ separator. Here is an example to append $INCL, in which directory
+ names are separated with a semi-colon: >
+ :let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
+ < Replace the ';' with a ':' or whatever separator is used. Note that
+ this doesn't work when $INCL contains a comma or white space.
+ ]=],
+ expand = true,
+ full_name = 'path',
+ list = 'comma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of directories searched with "gf" et.al.'),
+ tags = { 'E343', 'E345', 'E347', 'E854' },
+ type = 'string',
+ varname = 'p_path',
+ },
+ {
+ abbreviation = 'pi',
+ defaults = { if_true = false },
+ desc = [=[
+ When changing the indent of the current line, preserve as much of the
+ indent structure as possible. Normally the indent is replaced by a
+ series of tabs followed by spaces as required (unless |'expandtab'| is
+ enabled, in which case only spaces are used). Enabling this option
+ means the indent will preserve as many existing characters as possible
+ for indenting, and only add additional tabs or spaces as required.
+ 'expandtab' does not apply to the preserved white space, a Tab remains
+ a Tab.
+ NOTE: When using ">>" multiple times the resulting indent is a mix of
+ tabs and spaces. You might not like this.
+ Also see 'copyindent'.
+ Use |:retab| to clean up white space.
+ ]=],
+ full_name = 'preserveindent',
+ scope = { 'buffer' },
+ short_desc = N_('preserve the indent structure when reindenting'),
+ type = 'bool',
+ varname = 'p_pi',
+ },
+ {
+ abbreviation = 'pvh',
+ defaults = { if_true = 12 },
+ desc = [=[
+ Default height for a preview window. Used for |:ptag| and associated
+ commands. Used for |CTRL-W_}| when no count is given.
+ ]=],
+ full_name = 'previewheight',
+ scope = { 'global' },
+ short_desc = N_('height of the preview window'),
+ type = 'number',
+ varname = 'p_pvh',
+ },
+ {
+ abbreviation = 'pvw',
+ cb = 'did_set_previewwindow',
+ defaults = { if_true = false },
+ desc = [=[
+ Identifies the preview window. Only one window can have this option
+ set. It's normally not set directly, but by using one of the commands
+ |:ptag|, |:pedit|, etc.
+ ]=],
+ full_name = 'previewwindow',
+ noglob = true,
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('identifies the preview window'),
+ tags = { 'E590' },
+ type = 'bool',
+ },
+ {
+ defaults = { if_true = true },
+ full_name = 'prompt',
+ scope = { 'global' },
+ short_desc = N_('enable prompt in Ex mode'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'pb',
+ cb = 'did_set_pumblend',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Enables pseudo-transparency for the |popup-menu|. Valid values are in
+ the range of 0 for fully opaque popupmenu (disabled) to 100 for fully
+ transparent background. Values between 0-30 are typically most useful.
+
+ It is possible to override the level for individual highlights within
+ the popupmenu using |highlight-blend|. For instance, to enable
+ transparency but force the current selected element to be fully opaque: >
+
+ :set pumblend=15
+ :hi PmenuSel blend=0
+ <
+ UI-dependent. Works best with RGB colors. 'termguicolors'
+ ]=],
+ full_name = 'pumblend',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('Controls transparency level of popup menu'),
+ type = 'number',
+ varname = 'p_pb',
+ },
+ {
+ abbreviation = 'ph',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Maximum number of items to show in the popup menu
+ (|ins-completion-menu|). Zero means "use available screen space".
+ ]=],
+ full_name = 'pumheight',
+ scope = { 'global' },
+ short_desc = N_('maximum height of the popup menu'),
+ type = 'number',
+ varname = 'p_ph',
+ },
+ {
+ abbreviation = 'pw',
+ defaults = { if_true = 15 },
+ desc = [=[
+ Minimum width for the popup menu (|ins-completion-menu|). If the
+ cursor column + 'pumwidth' exceeds screen width, the popup menu is
+ nudged to fit on the screen.
+ ]=],
+ full_name = 'pumwidth',
+ scope = { 'global' },
+ short_desc = N_('minimum width of the popup menu'),
+ type = 'number',
+ varname = 'p_pw',
+ },
+ {
+ abbreviation = 'pyx',
+ defaults = { if_true = 3 },
+ desc = [=[
+ Specifies the python version used for pyx* functions and commands
+ |python_x|. As only Python 3 is supported, this always has the value
+ `3`. Setting any other value is an error.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'pyxversion',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('selects default python version to use'),
+ type = 'number',
+ varname = 'p_pyx',
+ },
+ {
+ abbreviation = 'qftf',
+ cb = 'did_set_quickfixtextfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used to get the text to display
+ in the quickfix and location list windows. This can be used to
+ customize the information displayed in the quickfix or location window
+ for each entry in the corresponding quickfix or location list. See
+ |quickfix-window-function| for an explanation of how to write the
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'quickfixtextfunc',
+ func = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('customize the quickfix window'),
+ type = 'string',
+ varname = 'p_qftf',
+ },
+ {
+ abbreviation = 'qe',
+ alloced = true,
+ defaults = { if_true = '\\' },
+ desc = [=[
+ The characters that are used to escape quotes in a string. Used for
+ objects like a', a" and a` |a'|.
+ When one of the characters in this option is found inside a string,
+ the following character will be skipped. The default value makes the
+ text "foo\"bar\\" considered to be one string.
+ ]=],
+ full_name = 'quoteescape',
+ scope = { 'buffer' },
+ short_desc = N_('escape characters used in a string'),
+ type = 'string',
+ varname = 'p_qe',
+ },
+ {
+ abbreviation = 'ro',
+ cb = 'did_set_readonly',
+ defaults = { if_true = false },
+ desc = [=[
+ If on, writes fail unless you use a '!'. Protects you from
+ accidentally overwriting a file. Default on when Vim is started
+ in read-only mode ("vim -R") or when the executable is called "view".
+ When using ":w!" the 'readonly' option is reset for the current
+ buffer, unless the 'Z' flag is in 'cpoptions'.
+ When using the ":view" command the 'readonly' option is set for the
+ newly edited buffer.
+ See 'modifiable' for disallowing changes to the buffer.
+ ]=],
+ full_name = 'readonly',
+ noglob = true,
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('disallow writing the buffer'),
+ type = 'bool',
+ varname = 'p_ro',
+ },
+ {
+ abbreviation = 'rdb',
+ cb = 'did_set_redrawdebug',
+ defaults = { if_true = '' },
+ desc = [=[
+ Flags to change the way redrawing works, for debugging purposes.
+ Most useful with 'writedelay' set to some reasonable value.
+ Supports the following flags:
+ compositor Indicate each redraw event handled by the compositor
+ by briefly flashing the redrawn regions in colors
+ indicating the redraw type. These are the highlight
+ groups used (and their default colors):
+ RedrawDebugNormal gui=reverse normal redraw passed through
+ RedrawDebugClear guibg=Yellow clear event passed through
+ RedrawDebugComposed guibg=Green redraw event modified by the
+ compositor (due to
+ overlapping grids, etc)
+ RedrawDebugRecompose guibg=Red redraw generated by the
+ compositor itself, due to a
+ grid being moved or deleted.
+ line introduce a delay after each line drawn on the screen.
+ When using the TUI or another single-grid UI, "compositor"
+ gives more information and should be preferred (every
+ line is processed as a separate event by the compositor)
+ flush introduce a delay after each "flush" event.
+ nothrottle Turn off throttling of the message grid. This is an
+ optimization that joins many small scrolls to one
+ larger scroll when drawing the message area (with
+ 'display' msgsep flag active).
+ invalid Enable stricter checking (abort) of inconsistencies
+ of the internal screen state. This is mostly
+ useful when running nvim inside a debugger (and
+ the test suite).
+ nodelta Send all internally redrawn cells to the UI, even if
+ they are unchanged from the already displayed state.
+ ]=],
+ full_name = 'redrawdebug',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('Changes the way redrawing works (debug)'),
+ type = 'string',
+ varname = 'p_rdb',
+ },
+ {
+ abbreviation = 'rdt',
+ defaults = { if_true = 2000 },
+ desc = [=[
+ Time in milliseconds for redrawing the display. Applies to
+ 'hlsearch', 'inccommand', |:match| highlighting and syntax
+ highlighting.
+ When redrawing takes more than this many milliseconds no further
+ matches will be highlighted.
+ For syntax highlighting the time applies per window. When over the
+ limit syntax highlighting is disabled until |CTRL-L| is used.
+ This is used to avoid that Vim hangs when using a very complicated
+ pattern.
+ ]=],
+ full_name = 'redrawtime',
+ scope = { 'global' },
+ short_desc = N_("timeout for 'hlsearch' and |:match| highlighting"),
+ type = 'number',
+ varname = 'p_rdt',
+ },
+ {
+ abbreviation = 're',
+ defaults = { if_true = 0 },
+ desc = [=[
+ This selects the default regexp engine. |two-engines|
+ The possible values are:
+ 0 automatic selection
+ 1 old engine
+ 2 NFA engine
+ Note that when using the NFA engine and the pattern contains something
+ that is not supported the pattern will not match. This is only useful
+ for debugging the regexp engine.
+ Using automatic selection enables Vim to switch the engine, if the
+ default engine becomes too costly. E.g., when the NFA engine uses too
+ many states. This should prevent Vim from hanging on a combination of
+ a complex pattern with long text.
+ ]=],
+ full_name = 'regexpengine',
+ scope = { 'global' },
+ short_desc = N_('default regexp engine to use'),
+ type = 'number',
+ varname = 'p_re',
+ },
+ {
+ abbreviation = 'rnu',
+ cb = 'did_set_number_relativenumber',
+ defaults = { if_true = false },
+ desc = [=[
+ Show the line number relative to the line with the cursor in front of
+ each line. Relative line numbers help you use the |count| you can
+ precede some vertical motion commands (e.g. j k + -) with, without
+ having to calculate it yourself. Especially useful in combination with
+ other commands (e.g. y d c < > gq gw =).
+ When the 'n' option is excluded from 'cpoptions' a wrapped
+ line will not use the column of line numbers.
+ The 'numberwidth' option can be used to set the room used for the line
+ number.
+ When a long, wrapped line doesn't start with the first character, '-'
+ characters are put before the number.
+ See |hl-LineNr| and |hl-CursorLineNr| for the highlighting used for
+ the number.
+
+ The number in front of the cursor line also depends on the value of
+ 'number', see |number_relativenumber| for all combinations of the two
+ options.
+ ]=],
+ full_name = 'relativenumber',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('show relative line number in front of each line'),
+ type = 'bool',
+ },
+ {
+ defaults = { if_true = true },
+ full_name = 'remap',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ defaults = { if_true = 2 },
+ desc = [=[
+ Threshold for reporting number of lines changed. When the number of
+ changed lines is more than 'report' a message will be given for most
+ ":" commands. If you want it always, set 'report' to 0.
+ For the ":substitute" command the number of substitutions is used
+ instead of the number of lines.
+ ]=],
+ full_name = 'report',
+ scope = { 'global' },
+ short_desc = N_('for reporting nr. of lines changed'),
+ type = 'number',
+ varname = 'p_report',
+ },
+ {
+ abbreviation = 'ri',
+ defaults = { if_true = false },
+ desc = [=[
+ Inserting characters in Insert mode will work backwards. See "typing
+ backwards" |ins-reverse|. This option can be toggled with the CTRL-_
+ command in Insert mode, when 'allowrevins' is set.
+ ]=],
+ full_name = 'revins',
+ scope = { 'global' },
+ short_desc = N_('inserting characters will work backwards'),
+ type = 'bool',
+ varname = 'p_ri',
+ },
+ {
+ abbreviation = 'rl',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, display orientation becomes right-to-left, i.e., characters
+ that are stored in the file appear from the right to the left.
+ Using this option, it is possible to edit files for languages that
+ are written from the right to the left such as Hebrew and Arabic.
+ This option is per window, so it is possible to edit mixed files
+ simultaneously, or to view the same file in both ways (this is
+ useful whenever you have a mixed text file with both right-to-left
+ and left-to-right strings so that both sets are displayed properly
+ in different windows). Also see |rileft.txt|.
+ ]=],
+ full_name = 'rightleft',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('window is right-to-left oriented'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'rlc',
+ alloced = true,
+ cb = 'did_set_rightleftcmd',
+ defaults = { if_true = 'search' },
+ desc = [=[
+ Each word in this option enables the command line editing to work in
+ right-to-left mode for a group of commands:
+
+ search "/" and "?" commands
+
+ This is useful for languages such as Hebrew, Arabic and Farsi.
+ The 'rightleft' option must be set for 'rightleftcmd' to take effect.
+ ]=],
+ expand_cb = 'expand_set_rightleftcmd',
+ full_name = 'rightleftcmd',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('commands for which editing works right-to-left'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'ru',
+ defaults = { if_true = true },
+ desc = [=[
+ Show the line and column number of the cursor position, separated by a
+ comma. When there is room, the relative position of the displayed
+ text in the file is shown on the far right:
+ Top first line is visible
+ Bot last line is visible
+ All first and last line are visible
+ 45% relative position in the file
+ If 'rulerformat' is set, it will determine the contents of the ruler.
+ Each window has its own ruler. If a window has a status line, the
+ ruler is shown there. If a window doesn't have a status line and
+ 'cmdheight' is zero, the ruler is not shown. Otherwise it is shown in
+ the last line of the screen. If the statusline is given by
+ 'statusline' (i.e. not empty), this option takes precedence over
+ 'ruler' and 'rulerformat'.
+ If the number of characters displayed is different from the number of
+ bytes in the text (e.g., for a TAB or a multibyte character), both
+ the text column (byte number) and the screen column are shown,
+ separated with a dash.
+ For an empty line "0-1" is shown.
+ For an empty buffer the line number will also be zero: "0,0-1".
+ If you don't want to see the ruler all the time but want to know where
+ you are, use "g CTRL-G" |g_CTRL-G|.
+ ]=],
+ full_name = 'ruler',
+ redraw = { 'statuslines' },
+ scope = { 'global' },
+ short_desc = N_('show cursor line and column in the status line'),
+ type = 'bool',
+ varname = 'p_ru',
+ },
+ {
+ abbreviation = 'ruf',
+ alloced = true,
+ cb = 'did_set_rulerformat',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it determines the content of the ruler
+ string, as displayed for the 'ruler' option.
+ The format of this option is like that of 'statusline'.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ The default ruler width is 17 characters. To make the ruler 15
+ characters wide, put "%15(" at the start and "%)" at the end.
+ Example: >
+ :set rulerformat=%15(%c%V\ %p%%%)
+ <
+ ]=],
+ full_name = 'rulerformat',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global' },
+ short_desc = N_('custom format for the ruler'),
+ type = 'string',
+ varname = 'p_ruf',
+ },
+ {
+ abbreviation = 'rtp',
+ cb = 'did_set_runtimepackpath',
+ defaults = {
+ if_true = '',
+ doc = [["$XDG_CONFIG_HOME/nvim,
+ $XDG_CONFIG_DIRS[1]/nvim,
+ $XDG_CONFIG_DIRS[2]/nvim,
+ …
+ $XDG_DATA_HOME/nvim[-data]/site,
+ $XDG_DATA_DIRS[1]/nvim/site,
+ $XDG_DATA_DIRS[2]/nvim/site,
+ …
+ $VIMRUNTIME,
+ …
+ $XDG_DATA_DIRS[2]/nvim/site/after,
+ $XDG_DATA_DIRS[1]/nvim/site/after,
+ $XDG_DATA_HOME/nvim[-data]/site/after,
+ …
+ $XDG_CONFIG_DIRS[2]/nvim/after,
+ $XDG_CONFIG_DIRS[1]/nvim/after,
+ $XDG_CONFIG_HOME/nvim/after"]],
+ meta = '...',
+ },
+ deny_duplicates = true,
+ desc = [=[
+ List of directories to be searched for these runtime files:
+ filetype.lua filetypes |new-filetype|
+ autoload/ automatically loaded scripts |autoload-functions|
+ colors/ color scheme files |:colorscheme|
+ compiler/ compiler files |:compiler|
+ doc/ documentation |write-local-help|
+ ftplugin/ filetype plugins |write-filetype-plugin|
+ indent/ indent scripts |indent-expression|
+ keymap/ key mapping files |mbyte-keymap|
+ lang/ menu translations |:menutrans|
+ lua/ |Lua| plugins
+ menu.vim GUI menus |menu.vim|
+ pack/ packages |:packadd|
+ parser/ |treesitter| syntax parsers
+ plugin/ plugin scripts |write-plugin|
+ queries/ |treesitter| queries
+ rplugin/ |remote-plugin| scripts
+ spell/ spell checking files |spell|
+ syntax/ syntax files |mysyntaxfile|
+ tutor/ tutorial files |:Tutor|
+
+ And any other file searched for with the |:runtime| command.
+
+ Defaults are setup to search these locations:
+ 1. Your home directory, for personal preferences.
+ Given by `stdpath("config")`. |$XDG_CONFIG_HOME|
+ 2. Directories which must contain configuration files according to
+ |xdg| ($XDG_CONFIG_DIRS, defaults to /etc/xdg). This also contains
+ preferences from system administrator.
+ 3. Data home directory, for plugins installed by user.
+ Given by `stdpath("data")/site`. |$XDG_DATA_HOME|
+ 4. nvim/site subdirectories for each directory in $XDG_DATA_DIRS.
+ This is for plugins which were installed by system administrator,
+ but are not part of the Nvim distribution. XDG_DATA_DIRS defaults
+ to /usr/local/share/:/usr/share/, so system administrators are
+ expected to install site plugins to /usr/share/nvim/site.
+ 5. Session state directory, for state data such as swap, backupdir,
+ viewdir, undodir, etc.
+ Given by `stdpath("state")`. |$XDG_STATE_HOME|
+ 6. $VIMRUNTIME, for files distributed with Nvim.
+ *after-directory*
+ 7, 8, 9, 10. In after/ subdirectories of 1, 2, 3 and 4, with reverse
+ ordering. This is for preferences to overrule or add to the
+ distributed defaults or system-wide settings (rarely needed).
+
+ *packages-runtimepath*
+ "start" packages will also be searched (|runtime-search-path|) for
+ runtime files after these, though such packages are not explicitly
+ reported in &runtimepath. But "opt" packages are explicitly added to
+ &runtimepath by |:packadd|.
+
+ Note that, unlike 'path', no wildcards like "**" are allowed. Normal
+ wildcards are allowed, but can significantly slow down searching for
+ runtime files. For speed, use as few items as possible and avoid
+ wildcards.
+ See |:runtime|.
+ Example: >
+ :set runtimepath=~/vimruntime,/mygroup/vim,$VIMRUNTIME
+ < This will use the directory "~/vimruntime" first (containing your
+ personal Nvim runtime files), then "/mygroup/vim", and finally
+ "$VIMRUNTIME" (the default runtime files).
+ You can put a directory before $VIMRUNTIME to find files which replace
+ distributed runtime files. You can put a directory after $VIMRUNTIME
+ to find files which add to distributed runtime files.
+
+ With |--clean| the home directory entries are not included.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'runtimepath',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('list of directories used for runtime files'),
+ tags = { 'vimfiles' },
+ type = 'string',
+ varname = 'p_rtp',
+ },
+ {
+ abbreviation = 'scr',
+ defaults = {
+ if_true = 0,
+ doc = 'half the window height',
+ },
+ desc = [=[
+ Number of lines to scroll with CTRL-U and CTRL-D commands. Will be
+ set to half the number of lines in the window when the window size
+ changes. This may happen when enabling the |status-line| or
+ 'tabline' option after setting the 'scroll' option.
+ If you give a count to the CTRL-U or CTRL-D command it will
+ be used as the new value for 'scroll'. Reset to half the window
+ height with ":set scroll=0".
+ ]=],
+ full_name = 'scroll',
+ no_mkrc = true,
+ pv_name = 'p_scroll',
+ scope = { 'window' },
+ short_desc = N_('lines to scroll with CTRL-U and CTRL-D'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'sms',
+ cb = 'did_set_smoothscroll',
+ defaults = { if_true = false },
+ desc = [=[
+ Scrolling works with screen lines. When 'wrap' is set and the first
+ line in the window wraps part of it may not be visible, as if it is
+ above the window. "<<<" is displayed at the start of the first line,
+ highlighted with |hl-NonText|.
+ You may also want to add "lastline" to the 'display' option to show as
+ much of the last line as possible.
+ NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y
+ and scrolling with the mouse.
+ ]=],
+ full_name = 'smoothscroll',
+ pv_name = 'p_sms',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_("scroll by screen lines when 'wrap' is set"),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'scbk',
+ cb = 'did_set_scrollback',
+ defaults = {
+ if_true = -1,
+ doc = '10000',
+ },
+ desc = [=[
+ Maximum number of lines kept beyond the visible screen. Lines at the
+ top are deleted if new lines exceed this limit.
+ Minimum is 1, maximum is 100000.
+ Only in |terminal| buffers.
+
+ Note: Lines that are not visible and kept in scrollback are not
+ reflown when the terminal buffer is resized horizontally.
+ ]=],
+ full_name = 'scrollback',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('lines to scroll with CTRL-U and CTRL-D'),
+ type = 'number',
+ varname = 'p_scbk',
+ },
+ {
+ abbreviation = 'scb',
+ cb = 'did_set_scrollbind',
+ defaults = { if_true = false },
+ desc = [=[
+ See also |scroll-binding|. When this option is set, scrolling the
+ current window also scrolls other scrollbind windows (windows that
+ also have this option set). This option is useful for viewing the
+ differences between two versions of a file, see 'diff'.
+ See |'scrollopt'| for options that determine how this option should be
+ interpreted.
+ This option is mostly reset when splitting a window to edit another
+ file. This means that ":split | edit file" results in two windows
+ with scroll-binding, but ":split file" does not.
+ ]=],
+ full_name = 'scrollbind',
+ pv_name = 'p_scbind',
+ scope = { 'window' },
+ short_desc = N_('scroll in window as other windows scroll'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'sj',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Minimal number of lines to scroll when the cursor gets off the
+ screen (e.g., with "j"). Not used for scroll commands (e.g., CTRL-E,
+ CTRL-D). Useful if your terminal scrolls very slowly.
+ When set to a negative number from -1 to -100 this is used as the
+ percentage of the window height. Thus -50 scrolls half the window
+ height.
+ ]=],
+ full_name = 'scrolljump',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines to scroll'),
+ type = 'number',
+ varname = 'p_sj',
+ },
+ {
+ abbreviation = 'so',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Minimal number of screen lines to keep above and below the cursor.
+ This will make some context visible around where you are working. If
+ you set it to a very large value (999) the cursor line will always be
+ in the middle of the window (except at the start or end of the file or
+ when long lines wrap).
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal scrolloff<
+ setlocal scrolloff=-1
+ < For scrolling horizontally see 'sidescrolloff'.
+ ]=],
+ full_name = 'scrolloff',
+ scope = { 'global', 'window' },
+ short_desc = N_('minimum nr. of lines above and below cursor'),
+ type = 'number',
+ varname = 'p_so',
+ },
+ {
+ abbreviation = 'sbo',
+ cb = 'did_set_scrollopt',
+ defaults = { if_true = 'ver,jump' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a comma-separated list of words that specifies how
+ 'scrollbind' windows should behave. 'sbo' stands for ScrollBind
+ Options.
+ The following words are available:
+ ver Bind vertical scrolling for 'scrollbind' windows
+ hor Bind horizontal scrolling for 'scrollbind' windows
+ jump Applies to the offset between two windows for vertical
+ scrolling. This offset is the difference in the first
+ displayed line of the bound windows. When moving
+ around in a window, another 'scrollbind' window may
+ reach a position before the start or after the end of
+ the buffer. The offset is not changed though, when
+ moving back the 'scrollbind' window will try to scroll
+ to the desired position when possible.
+ When now making that window the current one, two
+ things can be done with the relative offset:
+ 1. When "jump" is not included, the relative offset is
+ adjusted for the scroll position in the new current
+ window. When going back to the other window, the
+ new relative offset will be used.
+ 2. When "jump" is included, the other windows are
+ scrolled to keep the same relative offset. When
+ going back to the other window, it still uses the
+ same relative offset.
+ Also see |scroll-binding|.
+ When 'diff' mode is active there always is vertical scroll binding,
+ even when "ver" isn't there.
+ ]=],
+ expand_cb = 'expand_set_scrollopt',
+ full_name = 'scrollopt',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_("how 'scrollbind' should behave"),
+ type = 'string',
+ varname = 'p_sbo',
+ },
+ {
+ abbreviation = 'sect',
+ defaults = { if_true = 'SHNHH HUnhsh' },
+ desc = [=[
+ Specifies the nroff macros that separate sections. These are pairs of
+ two letters (See |object-motions|). The default makes a section start
+ at the nroff macros ".SH", ".NH", ".H", ".HU", ".nh" and ".sh".
+ ]=],
+ full_name = 'sections',
+ scope = { 'global' },
+ short_desc = N_('nroff macros that separate sections'),
+ type = 'string',
+ varname = 'p_sections',
+ },
+ {
+ defaults = { if_true = false },
+ full_name = 'secure',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('No description'),
+ type = 'bool',
+ varname = 'p_secure',
+ },
+ {
+ abbreviation = 'sel',
+ cb = 'did_set_selection',
+ defaults = { if_true = 'inclusive' },
+ desc = [=[
+ This option defines the behavior of the selection. It is only used
+ in Visual and Select mode.
+ Possible values:
+ value past line inclusive ~
+ old no yes
+ inclusive yes yes
+ exclusive yes no
+ "past line" means that the cursor is allowed to be positioned one
+ character past the line.
+ "inclusive" means that the last character of the selection is included
+ in an operation. For example, when "x" is used to delete the
+ selection.
+ When "old" is used and 'virtualedit' allows the cursor to move past
+ the end of line the line break still isn't included.
+ Note that when "exclusive" is used and selecting from the end
+ backwards, you cannot include the last character of a line, when
+ starting in Normal mode and 'virtualedit' empty.
+ ]=],
+ expand_cb = 'expand_set_selection',
+ full_name = 'selection',
+ scope = { 'global' },
+ short_desc = N_('what type of selection to use'),
+ type = 'string',
+ varname = 'p_sel',
+ },
+ {
+ abbreviation = 'slm',
+ cb = 'did_set_selectmode',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ This is a comma-separated list of words, which specifies when to start
+ Select mode instead of Visual mode, when a selection is started.
+ Possible values:
+ mouse when using the mouse
+ key when using shifted special keys
+ cmd when using "v", "V" or CTRL-V
+ See |Select-mode|.
+ ]=],
+ expand_cb = 'expand_set_selectmode',
+ full_name = 'selectmode',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('when to use Select mode instead of Visual mode'),
+ type = 'string',
+ varname = 'p_slm',
+ },
+ {
+ abbreviation = 'ssop',
+ cb = 'did_set_sessionoptions',
+ defaults = { if_true = 'blank,buffers,curdir,folds,help,tabpages,winsize,terminal' },
+ deny_duplicates = true,
+ desc = [=[
+ Changes the effect of the |:mksession| command. It is a comma-
+ separated list of words. Each word enables saving and restoring
+ something:
+ word save and restore ~
+ blank empty windows
+ buffers hidden and unloaded buffers, not just those in windows
+ curdir the current directory
+ folds manually created folds, opened/closed folds and local
+ fold options
+ globals global variables that start with an uppercase letter
+ and contain at least one lowercase letter. Only
+ String and Number types are stored.
+ help the help window
+ localoptions options and mappings local to a window or buffer (not
+ global values for local options)
+ options all options and mappings (also global values for local
+ options)
+ skiprtp exclude 'runtimepath' and 'packpath' from the options
+ resize size of the Vim window: 'lines' and 'columns'
+ sesdir the directory in which the session file is located
+ will become the current directory (useful with
+ projects accessed over a network from different
+ systems)
+ tabpages all tab pages; without this only the current tab page
+ is restored, so that you can make a session for each
+ tab page separately
+ terminal include terminal windows where the command can be
+ restored
+ winpos position of the whole Vim window
+ winsize window sizes
+ slash |deprecated| Always enabled. Uses "/" in filenames.
+ unix |deprecated| Always enabled. Uses "\n" line endings.
+
+ Don't include both "curdir" and "sesdir". When neither is included
+ filenames are stored as absolute paths.
+ If you leave out "options" many things won't work well after restoring
+ the session.
+ ]=],
+ expand_cb = 'expand_set_sessionoptions',
+ full_name = 'sessionoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('options for |:mksession|'),
+ type = 'string',
+ varname = 'p_ssop',
+ },
+ {
+ abbreviation = 'sd',
+ cb = 'did_set_shada',
+ defaults = {
+ if_true = "!,'100,<50,s10,h",
+ doc = [[for
+ Win32: !,'100,<50,s10,h,rA:,rB:
+ others: !,'100,<50,s10,h]],
+ },
+ deny_duplicates = true,
+ desc = [=[
+ When non-empty, the shada file is read upon startup and written
+ when exiting Vim (see |shada-file|). The string should be a comma-
+ separated list of parameters, each consisting of a single character
+ identifying the particular parameter, followed by a number or string
+ which specifies the value of that parameter. If a particular
+ character is left out, then the default value is used for that
+ parameter. The following is a list of the identifying characters and
+ the effect of their value.
+ CHAR VALUE ~
+ *shada-!*
+ ! When included, save and restore global variables that start
+ with an uppercase letter, and don't contain a lowercase
+ letter. Thus "KEEPTHIS and "K_L_M" are stored, but "KeepThis"
+ and "_K_L_M" are not. Nested List and Dict items may not be
+ read back correctly, you end up with an empty item.
+ *shada-quote*
+ " Maximum number of lines saved for each register. Old name of
+ the '<' item, with the disadvantage that you need to put a
+ backslash before the ", otherwise it will be recognized as the
+ start of a comment!
+ *shada-%*
+ % When included, save and restore the buffer list. If Vim is
+ started with a file name argument, the buffer list is not
+ restored. If Vim is started without a file name argument, the
+ buffer list is restored from the shada file. Quickfix
+ ('buftype'), unlisted ('buflisted'), unnamed and buffers on
+ removable media (|shada-r|) are not saved.
+ When followed by a number, the number specifies the maximum
+ number of buffers that are stored. Without a number all
+ buffers are stored.
+ *shada-'*
+ ' Maximum number of previously edited files for which the marks
+ are remembered. This parameter must always be included when
+ 'shada' is non-empty.
+ Including this item also means that the |jumplist| and the
+ |changelist| are stored in the shada file.
+ *shada-/*
+ / Maximum number of items in the search pattern history to be
+ saved. If non-zero, then the previous search and substitute
+ patterns are also saved. When not included, the value of
+ 'history' is used.
+ *shada-:*
+ : Maximum number of items in the command-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-<*
+ \< Maximum number of lines saved for each register. If zero then
+ registers are not saved. When not included, all lines are
+ saved. '"' is the old name for this item.
+ Also see the 's' item below: limit specified in KiB.
+ *shada-@*
+ @ Maximum number of items in the input-line history to be
+ saved. When not included, the value of 'history' is used.
+ *shada-c*
+ c Dummy option, kept for compatibility reasons. Has no actual
+ effect: ShaDa always uses UTF-8 and 'encoding' value is fixed
+ to UTF-8 as well.
+ *shada-f*
+ f Whether file marks need to be stored. If zero, file marks ('0
+ to '9, 'A to 'Z) are not stored. When not present or when
+ non-zero, they are all stored. '0 is used for the current
+ cursor position (when exiting or when doing |:wshada|).
+ *shada-h*
+ h Disable the effect of 'hlsearch' when loading the shada
+ file. When not included, it depends on whether ":nohlsearch"
+ has been used since the last search command.
+ *shada-n*
+ n Name of the shada file. The name must immediately follow
+ the 'n'. Must be at the end of the option! If the
+ 'shadafile' option is set, that file name overrides the one
+ given here with 'shada'. Environment variables are
+ expanded when opening the file, not when setting the option.
+ *shada-r*
+ r Removable media. The argument is a string (up to the next
+ ','). This parameter can be given several times. Each
+ specifies the start of a path for which no marks will be
+ stored. This is to avoid removable media. For Windows you
+ could use "ra:,rb:". You can also use it for temp files,
+ e.g., for Unix: "r/tmp". Case is ignored.
+ *shada-s*
+ s Maximum size of an item contents in KiB. If zero then nothing
+ is saved. Unlike Vim this applies to all items, except for
+ the buffer list and header. Full item size is off by three
+ unsigned integers: with `s10` maximum item size may be 1 byte
+ (type: 7-bit integer) + 9 bytes (timestamp: up to 64-bit
+ integer) + 3 bytes (item size: up to 16-bit integer because
+ 2^8 < 10240 < 2^16) + 10240 bytes (requested maximum item
+ contents size) = 10253 bytes.
+
+ Example: >
+ :set shada='50,<1000,s100,:0,n~/nvim/shada
+ <
+ '50 Marks will be remembered for the last 50 files you
+ edited.
+ <1000 Contents of registers (up to 1000 lines each) will be
+ remembered.
+ s100 Items with contents occupying more then 100 KiB are
+ skipped.
+ :0 Command-line history will not be saved.
+ n~/nvim/shada The name of the file to use is "~/nvim/shada".
+ no / Since '/' is not specified, the default will be used,
+ that is, save all of the search history, and also the
+ previous search and substitute patterns.
+ no % The buffer list will not be saved nor read back.
+ no h 'hlsearch' highlighting will be restored.
+
+ When setting 'shada' from an empty value you can use |:rshada| to
+ load the contents of the file, this is not done automatically.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shada',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('use .shada file upon startup and exiting'),
+ tags = { 'E526', 'E527', 'E528' },
+ type = 'string',
+ varname = 'p_shada',
+ },
+ {
+ abbreviation = 'sdf',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ When non-empty, overrides the file name used for |shada| (viminfo).
+ When equal to "NONE" no shada file will be read or written.
+ This option can be set with the |-i| command line flag. The |--clean|
+ command line flag sets it to "NONE".
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'shadafile',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('overrides the filename used for shada'),
+ type = 'string',
+ varname = 'p_shadafile',
+ },
+ {
+ abbreviation = 'sh',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = 'sh',
+ if_true = 'cmd.exe',
+ doc = '$SHELL or "sh", Win32: "cmd.exe"',
+ meta = 'sh',
+ },
+ desc = [=[
+ Name of the shell to use for ! and :! commands. When changing the
+ value also check these options: 'shellpipe', 'shellslash'
+ 'shellredir', 'shellquote', 'shellxquote' and 'shellcmdflag'.
+ It is allowed to give an argument to the command, e.g. "csh -f".
+ See |option-backslash| about including spaces and backslashes.
+ Environment variables are expanded |:set_env|.
+
+ If the name of the shell contains a space, you need to enclose it in
+ quotes. Example with quotes: >
+ :set shell=\"c:\program\ files\unix\sh.exe\"\ -f
+ < Note the backslash before each quote (to avoid starting a comment) and
+ each space (to avoid ending the option value), so better use |:let-&|
+ like this: >
+ :let &shell='"C:\Program Files\unix\sh.exe" -f'
+ < Also note that the "-f" is not inside the quotes, because it is not
+ part of the command name.
+ *shell-unquoting*
+ Rules regarding quotes:
+ 1. Option is split on space and tab characters that are not inside
+ quotes: "abc def" runs shell named "abc" with additional argument
+ "def", '"abc def"' runs shell named "abc def" with no additional
+ arguments (here and below: additional means “additional to
+ 'shellcmdflag'”).
+ 2. Quotes in option may be present in any position and any number:
+ '"abc"', '"a"bc', 'a"b"c', 'ab"c"' and '"a"b"c"' are all equivalent
+ to just "abc".
+ 3. Inside quotes backslash preceding backslash means one backslash.
+ Backslash preceding quote means one quote. Backslash preceding
+ anything else means backslash and next character literally:
+ '"a\\b"' is the same as "a\b", '"a\\"b"' runs shell named literally
+ 'a"b', '"a\b"' is the same as "a\b" again.
+ 4. Outside of quotes backslash always means itself, it cannot be used
+ to escape quote: 'a\"b"' is the same as "a\b".
+ Note that such processing is done after |:set| did its own round of
+ unescaping, so to keep yourself sane use |:let-&| like shown above.
+ *shell-powershell*
+ To use PowerShell: >
+ let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
+ let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
+ let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
+ let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
+ set shellquote= shellxquote=
+
+ < This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'shell',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('name of shell to use for external commands'),
+ tags = { 'E91' },
+ type = 'string',
+ varname = 'p_sh',
+ },
+ {
+ abbreviation = 'shcf',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '-c',
+ if_true = '/s /c',
+ doc = '"-c"; Windows: "/s /c"',
+ },
+ desc = [=[
+ Flag passed to the shell to execute "!" and ":!" commands; e.g.,
+ `bash.exe -c ls` or `cmd.exe /s /c "dir"`. For MS-Windows, the
+ default is set according to the value of 'shell', to reduce the need
+ to set this option by the user.
+ On Unix it can have more than one flag. Each white space separated
+ part is passed as an argument to the shell command.
+ See |option-backslash| about including spaces and backslashes.
+ See |shell-unquoting| which talks about separating this option into
+ multiple arguments.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellcmdflag',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('flag to shell to execute one command'),
+ type = 'string',
+ varname = 'p_shcf',
+ },
+ {
+ abbreviation = 'sp',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '| tee',
+ if_true = '2>&1| tee',
+ doc = '">", "| tee", "|& tee" or "2>&1| tee"',
+ },
+ desc = [=[
+ String to be used to put the output of the ":make" command in the
+ error file. See also |:make_makeprg|. See |option-backslash| about
+ including spaces and backslashes.
+ The name of the temporary file can be represented by "%s" if necessary
+ (the file name is appended automatically if no %s appears in the value
+ of this option).
+ For MS-Windows the default is "2>&1| tee". The stdout and stderr are
+ saved in a file and echoed to the screen.
+ For Unix the default is "| tee". The stdout of the compiler is saved
+ in a file and echoed to the screen. If the 'shell' option is "csh" or
+ "tcsh" after initializations, the default becomes "|& tee". If the
+ 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+ "bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This
+ means that stderr is also included. Before using the 'shell' option a
+ path is removed, thus "/bin/sh" uses "sh".
+ The initialization of this option is done after reading the vimrc
+ and the other initializations, so that when the 'shell' option is set
+ there, the 'shellpipe' option changes automatically, unless it was
+ explicitly set before.
+ When 'shellpipe' is set to an empty string, no redirection of the
+ ":make" output will be done. This is useful if you use a 'makeprg'
+ that writes to 'makeef' by itself. If you want no piping, but do
+ want to include the 'makeef', set 'shellpipe' to a single space.
+ Don't forget to precede the space with a backslash: ":set sp=\ ".
+ In the future pipes may be used for filtering and this option will
+ become obsolete (at least for Unix).
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellpipe',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('string to put output of ":make" in error file'),
+ type = 'string',
+ varname = 'p_sp',
+ },
+ {
+ abbreviation = 'shq',
+ defaults = {
+ if_true = '',
+ doc = [[""; Windows, when 'shell'
+ contains "sh" somewhere: "\""]],
+ },
+ desc = [=[
+ Quoting character(s), put around the command passed to the shell, for
+ the "!" and ":!" commands. The redirection is kept outside of the
+ quoting. See 'shellxquote' to include the redirection. It's
+ probably not useful to set both options.
+ This is an empty string by default. Only known to be useful for
+ third-party shells on Windows systems, such as the MKS Korn Shell
+ or bash, where it should be "\"". The default is adjusted according
+ the value of 'shell', to reduce the need to set this option by the
+ user.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellquote',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('quote character(s) for around shell command'),
+ type = 'string',
+ varname = 'p_shq',
+ },
+ {
+ abbreviation = 'srr',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '>',
+ if_true = '>%s 2>&1',
+ doc = '">", ">&" or ">%s 2>&1"',
+ },
+ desc = [=[
+ String to be used to put the output of a filter command in a temporary
+ file. See also |:!|. See |option-backslash| about including spaces
+ and backslashes.
+ The name of the temporary file can be represented by "%s" if necessary
+ (the file name is appended automatically if no %s appears in the value
+ of this option).
+ The default is ">". For Unix, if the 'shell' option is "csh" or
+ "tcsh" during initializations, the default becomes ">&". If the
+ 'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
+ "bash" or "fish", the default becomes ">%s 2>&1". This means that
+ stderr is also included. For Win32, the Unix checks are done and
+ additionally "cmd" is checked for, which makes the default ">%s 2>&1".
+ Also, the same names with ".exe" appended are checked for.
+ The initialization of this option is done after reading the vimrc
+ and the other initializations, so that when the 'shell' option is set
+ there, the 'shellredir' option changes automatically unless it was
+ explicitly set before.
+ In the future pipes may be used for filtering and this option will
+ become obsolete (at least for Unix).
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellredir',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('string to put output of filter in a temp file'),
+ type = 'string',
+ varname = 'p_srr',
+ },
+ {
+ abbreviation = 'ssl',
+ cb = 'did_set_shellslash',
+ defaults = { if_true = false },
+ desc = [=[
+ only for MS-Windows
+ When set, a forward slash is used when expanding file names. This is
+ useful when a Unix-like shell is used instead of cmd.exe. Backward
+ slashes can still be typed, but they are changed to forward slashes by
+ Vim.
+ Note that setting or resetting this option has no effect for some
+ existing file names, thus this option needs to be set before opening
+ any file for best results. This might change in the future.
+ 'shellslash' only works when a backslash can be used as a path
+ separator. To test if this is so use: >
+ if exists('+shellslash')
+ < Also see 'completeslash'.
+ ]=],
+ enable_if = 'BACKSLASH_IN_FILENAME',
+ full_name = 'shellslash',
+ scope = { 'global' },
+ short_desc = N_('use forward slash for shell file names'),
+ type = 'bool',
+ varname = 'p_ssl',
+ },
+ {
+ abbreviation = 'stmp',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, use temp files for shell commands. When off use a pipe.
+ When using a pipe is not possible temp files are used anyway.
+ The advantage of using a pipe is that nobody can read the temp file
+ and the 'shell' command does not need to support redirection.
+ The advantage of using a temp file is that the file type and encoding
+ can be detected.
+ The |FilterReadPre|, |FilterReadPost| and |FilterWritePre|,
+ |FilterWritePost| autocommands event are not triggered when
+ 'shelltemp' is off.
+ |system()| does not respect this option, it always uses pipes.
+ ]=],
+ full_name = 'shelltemp',
+ scope = { 'global' },
+ short_desc = N_('whether to use a temp file for shell commands'),
+ type = 'bool',
+ varname = 'p_stmp',
+ },
+ {
+ abbreviation = 'sxq',
+ defaults = {
+ condition = 'MSWIN',
+ if_false = '',
+ if_true = '"',
+ doc = '"", Windows: "\\""',
+ },
+ desc = [=[
+ Quoting character(s), put around the command passed to the shell, for
+ the "!" and ":!" commands. Includes the redirection. See
+ 'shellquote' to exclude the redirection. It's probably not useful
+ to set both options.
+ When the value is '(' then ')' is appended. When the value is '"('
+ then ')"' is appended.
+ When the value is '(' then also see 'shellxescape'.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellxquote',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_("like 'shellquote', but include redirection"),
+ type = 'string',
+ varname = 'p_sxq',
+ },
+ {
+ abbreviation = 'sxe',
+ defaults = { if_true = '' },
+ desc = [=[
+ When 'shellxquote' is set to "(" then the characters listed in this
+ option will be escaped with a '^' character. This makes it possible
+ to execute most external commands with cmd.exe.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'shellxescape',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_("characters to escape when 'shellxquote' is ("),
+ type = 'string',
+ varname = 'p_sxe',
+ },
+ {
+ abbreviation = 'sr',
+ defaults = { if_true = false },
+ desc = [=[
+ Round indent to multiple of 'shiftwidth'. Applies to > and <
+ commands. CTRL-T and CTRL-D in Insert mode always round the indent to
+ a multiple of 'shiftwidth' (this is Vi compatible).
+ ]=],
+ full_name = 'shiftround',
+ scope = { 'global' },
+ short_desc = N_('round indent to multiple of shiftwidth'),
+ type = 'bool',
+ varname = 'p_sr',
+ },
+ {
+ abbreviation = 'sw',
+ cb = 'did_set_shiftwidth_tabstop',
+ defaults = { if_true = 8 },
+ desc = [=[
+ Number of spaces to use for each step of (auto)indent. Used for
+ |'cindent'|, |>>|, |<<|, etc.
+ When zero the 'tabstop' value will be used. Use the |shiftwidth()|
+ function to get the effective shiftwidth value.
+ ]=],
+ full_name = 'shiftwidth',
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces to use for (auto)indent step'),
+ type = 'number',
+ varname = 'p_sw',
+ },
+ {
+ abbreviation = 'shm',
+ cb = 'did_set_shortmess',
+ defaults = { if_true = 'ltToOCF' },
+ desc = [=[
+ This option helps to avoid all the |hit-enter| prompts caused by file
+ messages, for example with CTRL-G, and to avoid some other messages.
+ It is a list of flags:
+ flag meaning when present ~
+ l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l*
+ m use "[+]" instead of "[Modified]" *shm-m*
+ r use "[RO]" instead of "[readonly]" *shm-r*
+ w use "[w]" instead of "written" for file write message *shm-w*
+ and "[a]" instead of "appended" for ':w >> file' command
+ a all of the above abbreviations *shm-a*
+
+ o overwrite message for writing a file with subsequent *shm-o*
+ message for reading a file (useful for ":wn" or when
+ 'autowrite' on)
+ O message for reading a file overwrites any previous *shm-O*
+ message; also for quickfix message (e.g., ":cn")
+ s don't give "search hit BOTTOM, continuing at TOP" or *shm-s*
+ "search hit TOP, continuing at BOTTOM" messages; when using
+ the search count do not show "W" after the count message (see
+ S below)
+ t truncate file message at the start if it is too long *shm-t*
+ to fit on the command-line, "<" will appear in the left most
+ column; ignored in Ex mode
+ T truncate other messages in the middle if they are too *shm-T*
+ long to fit on the command line; "..." will appear in the
+ middle; ignored in Ex mode
+ W don't give "written" or "[w]" when writing a file *shm-W*
+ A don't give the "ATTENTION" message when an existing *shm-A*
+ swap file is found
+ I don't give the intro message when starting Vim, *shm-I*
+ see |:intro|
+ c don't give |ins-completion-menu| messages; for *shm-c*
+ example, "-- XXX completion (YYY)", "match 1 of 2", "The only
+ match", "Pattern not found", "Back at original", etc.
+ C don't give messages while scanning for ins-completion *shm-C*
+ items, for instance "scanning tags"
+ q use "recording" instead of "recording @a" *shm-q*
+ F don't give the file info when editing a file, like *shm-F*
+ `:silent` was used for the command
+ S do not show search count message when searching, e.g. *shm-S*
+ "[1/5]"
+
+ This gives you the opportunity to avoid that a change between buffers
+ requires you to hit <Enter>, but still gives as useful a message as
+ possible for the space available. To get the whole message that you
+ would have got with 'shm' empty, use ":file!"
+ Useful values:
+ shm= No abbreviation of message.
+ shm=a Abbreviation, but no loss of information.
+ shm=at Abbreviation, and truncate message when necessary.
+ ]=],
+ expand_cb = 'expand_set_shortmess',
+ full_name = 'shortmess',
+ list = 'flags',
+ scope = { 'global' },
+ short_desc = N_('list of flags, reduce length of messages'),
+ tags = { 'E1336' },
+ type = 'string',
+ varname = 'p_shm',
+ },
+ {
+ abbreviation = 'sbr',
+ cb = 'did_set_showbreak',
+ defaults = { if_true = '' },
+ desc = [=[
+ String to put at the start of lines that have been wrapped. Useful
+ values are "> " or "+++ ": >
+ :let &showbreak = "> "
+ :let &showbreak = '+++ '
+ < Only printable single-cell characters are allowed, excluding <Tab> and
+ comma (in a future version the comma might be used to separate the
+ part that is shown at the end and at the start of a line).
+ The |hl-NonText| highlight group determines the highlighting.
+ Note that tabs after the showbreak will be displayed differently.
+ If you want the 'showbreak' to appear in between line numbers, add the
+ "n" flag to 'cpoptions'.
+ A window-local value overrules a global value. If the global value is
+ set and you want no value in the current window use NONE: >
+ :setlocal showbreak=NONE
+ <
+ ]=],
+ full_name = 'showbreak',
+ redraw = { 'all_windows' },
+ scope = { 'global', 'window' },
+ short_desc = N_('string to use at the start of wrapped lines'),
+ tags = { 'E595' },
+ type = 'string',
+ varname = 'p_sbr',
+ },
+ {
+ abbreviation = 'sc',
+ defaults = { if_true = true },
+ desc = [=[
+ Show (partial) command in the last line of the screen. Set this
+ option off if your terminal is slow.
+ In Visual mode the size of the selected area is shown:
+ - When selecting characters within a line, the number of characters.
+ If the number of bytes is different it is also displayed: "2-6"
+ means two characters and six bytes.
+ - When selecting more than one line, the number of lines.
+ - When selecting a block, the size in screen characters:
+ {lines}x{columns}.
+ This information can be displayed in an alternative location using the
+ 'showcmdloc' option, useful when 'cmdheight' is 0.
+ ]=],
+ full_name = 'showcmd',
+ scope = { 'global' },
+ short_desc = N_('show (partial) command in status line'),
+ type = 'bool',
+ varname = 'p_sc',
+ },
+ {
+ abbreviation = 'sloc',
+ cb = 'did_set_showcmdloc',
+ defaults = { if_true = 'last' },
+ desc = [=[
+ This option can be used to display the (partially) entered command in
+ another location. Possible values are:
+ last Last line of the screen (default).
+ statusline Status line of the current window.
+ tabline First line of the screen if 'showtabline' is enabled.
+ Setting this option to "statusline" or "tabline" means that these will
+ be redrawn whenever the command changes, which can be on every key
+ pressed.
+ The %S 'statusline' item can be used in 'statusline' or 'tabline' to
+ place the text. Without a custom 'statusline' or 'tabline' it will be
+ displayed in a convenient location.
+ ]=],
+ expand_cb = 'expand_set_showcmdloc',
+ full_name = 'showcmdloc',
+ scope = { 'global' },
+ short_desc = N_('change location of partial command'),
+ type = 'string',
+ varname = 'p_sloc',
+ },
+ {
+ abbreviation = 'sft',
+ defaults = { if_true = false },
+ desc = [=[
+ When completing a word in insert mode (see |ins-completion|) from the
+ tags file, show both the tag name and a tidied-up form of the search
+ pattern (if there is one) as possible matches. Thus, if you have
+ matched a C function, you can see a template for what arguments are
+ required (coding style permitting).
+ Note that this doesn't work well together with having "longest" in
+ 'completeopt', because the completion from the search pattern may not
+ match the typed text.
+ ]=],
+ full_name = 'showfulltag',
+ scope = { 'global' },
+ short_desc = N_('show full tag pattern when completing tag'),
+ type = 'bool',
+ varname = 'p_sft',
+ },
+ {
+ abbreviation = 'sm',
+ defaults = { if_true = false },
+ desc = [=[
+ When a bracket is inserted, briefly jump to the matching one. The
+ jump is only done if the match can be seen on the screen. The time to
+ show the match can be set with 'matchtime'.
+ A Beep is given if there is no match (no matter if the match can be
+ seen or not).
+ When the 'm' flag is not included in 'cpoptions', typing a character
+ will immediately move the cursor back to where it belongs.
+ See the "sm" field in 'guicursor' for setting the cursor shape and
+ blinking when showing the match.
+ The 'matchpairs' option can be used to specify the characters to show
+ matches for. 'rightleft' and 'revins' are used to look for opposite
+ matches.
+ Also see the matchparen plugin for highlighting the match when moving
+ around |pi_paren.txt|.
+ Note: Use of the short form is rated PG.
+ ]=],
+ full_name = 'showmatch',
+ scope = { 'global' },
+ short_desc = N_('briefly jump to matching bracket if insert one'),
+ type = 'bool',
+ varname = 'p_sm',
+ },
+ {
+ abbreviation = 'smd',
+ defaults = { if_true = true },
+ desc = [=[
+ If in Insert, Replace or Visual mode put a message on the last line.
+ The |hl-ModeMsg| highlight group determines the highlighting.
+ The option has no effect when 'cmdheight' is zero.
+ ]=],
+ full_name = 'showmode',
+ scope = { 'global' },
+ short_desc = N_('message on status line to show current mode'),
+ type = 'bool',
+ varname = 'p_smd',
+ },
+ {
+ abbreviation = 'stal',
+ cb = 'did_set_showtabline',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The value of this option specifies when the line with tab page labels
+ will be displayed:
+ 0: never
+ 1: only if there are at least two tab pages
+ 2: always
+ This is both for the GUI and non-GUI implementation of the tab pages
+ line.
+ See |tab-page| for more information about tab pages.
+ ]=],
+ full_name = 'showtabline',
+ redraw = { 'all_windows', 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('tells when the tab pages line is displayed'),
+ type = 'number',
+ varname = 'p_stal',
+ },
+ {
+ abbreviation = 'ss',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal number of columns to scroll horizontally. Used only when
+ the 'wrap' option is off and the cursor is moved off of the screen.
+ When it is zero the cursor will be put in the middle of the screen.
+ When using a slow terminal set it to a large number or 0. Not used
+ for "zh" and "zl" commands.
+ ]=],
+ full_name = 'sidescroll',
+ scope = { 'global' },
+ short_desc = N_('minimum number of columns to scroll horizontal'),
+ type = 'number',
+ varname = 'p_ss',
+ },
+ {
+ abbreviation = 'siso',
+ defaults = { if_true = 0 },
+ desc = [=[
+ The minimal number of screen columns to keep to the left and to the
+ right of the cursor if 'nowrap' is set. Setting this option to a
+ value greater than 0 while having |'sidescroll'| also at a non-zero
+ value makes some context visible in the line you are scrolling in
+ horizontally (except at beginning of the line). Setting this option
+ to a large value (like 999) has the effect of keeping the cursor
+ horizontally centered in the window, as long as one does not come too
+ close to the beginning of the line.
+ After using the local value, go back the global value with one of
+ these two: >
+ setlocal sidescrolloff<
+ setlocal sidescrolloff=-1
+ <
+ Example: Try this together with 'sidescroll' and 'listchars' as
+ in the following example to never allow the cursor to move
+ onto the "extends" character: >
+
+ :set nowrap sidescroll=1 listchars=extends:>,precedes:<
+ :set sidescrolloff=1
+ <
+ ]=],
+ full_name = 'sidescrolloff',
+ scope = { 'global', 'window' },
+ short_desc = N_('min. nr. of columns to left and right of cursor'),
+ type = 'number',
+ varname = 'p_siso',
+ },
+ {
+ abbreviation = 'scl',
+ alloced = true,
+ cb = 'did_set_signcolumn',
+ defaults = { if_true = 'auto' },
+ desc = [=[
+ When and how to draw the signcolumn. Valid values are:
+ "auto" only when there is a sign to display
+ "auto:[1-9]" resize to accommodate multiple signs up to the
+ given number (maximum 9), e.g. "auto:4"
+ "auto:[1-8]-[2-9]"
+ resize to accommodate multiple signs up to the
+ given maximum number (maximum 9) while keeping
+ at least the given minimum (maximum 8) fixed
+ space. The minimum number should always be less
+ than the maximum number, e.g. "auto:2-5"
+ "no" never
+ "yes" always
+ "yes:[1-9]" always, with fixed space for signs up to the given
+ number (maximum 9), e.g. "yes:3"
+ "number" display signs in the 'number' column. If the number
+ column is not present, then behaves like "auto".
+ ]=],
+ expand_cb = 'expand_set_signcolumn',
+ full_name = 'signcolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('when to display the sign column'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'scs',
+ defaults = { if_true = false },
+ desc = [=[
+ Override the 'ignorecase' option if the search pattern contains upper
+ case characters. Only used when the search pattern is typed and
+ 'ignorecase' option is on. Used for the commands "/", "?", "n", "N",
+ ":g" and ":s". Not used for "*", "#", "gd", tag search, etc. After
+ "*" and "#" you can make 'smartcase' used by doing a "/" command,
+ recalling the search pattern from history and hitting <Enter>.
+ ]=],
+ full_name = 'smartcase',
+ scope = { 'global' },
+ short_desc = N_('no ignore case when pattern has uppercase'),
+ type = 'bool',
+ varname = 'p_scs',
+ },
+ {
+ abbreviation = 'si',
+ defaults = { if_true = false },
+ desc = [=[
+ Do smart autoindenting when starting a new line. Works for C-like
+ programs, but can also be used for other languages. 'cindent' does
+ something like this, works better in most cases, but is more strict,
+ see |C-indenting|. When 'cindent' is on or 'indentexpr' is set,
+ setting 'si' has no effect. 'indentexpr' is a more advanced
+ alternative.
+ Normally 'autoindent' should also be on when using 'smartindent'.
+ An indent is automatically inserted:
+ - After a line ending in "{".
+ - After a line starting with a keyword from 'cinwords'.
+ - Before a line starting with "}" (only with the "O" command).
+ When typing '}' as the first character in a new line, that line is
+ given the same indent as the matching "{".
+ When typing '#' as the first character in a new line, the indent for
+ that line is removed, the '#' is put in the first column. The indent
+ is restored for the next line. If you don't want this, use this
+ mapping: ":inoremap # X^H#", where ^H is entered with CTRL-V CTRL-H.
+ When using the ">>" command, lines starting with '#' are not shifted
+ right.
+ ]=],
+ full_name = 'smartindent',
+ scope = { 'buffer' },
+ short_desc = N_('smart autoindenting for C programs'),
+ type = 'bool',
+ varname = 'p_si',
+ },
+ {
+ abbreviation = 'sta',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, a <Tab> in front of a line inserts blanks according to
+ 'shiftwidth'. 'tabstop' or 'softtabstop' is used in other places. A
+ <BS> will delete a 'shiftwidth' worth of space at the start of the
+ line.
+ When off, a <Tab> always inserts blanks according to 'tabstop' or
+ 'softtabstop'. 'shiftwidth' is only used for shifting text left or
+ right |shift-left-right|.
+ What gets inserted (a <Tab> or spaces) depends on the 'expandtab'
+ option. Also see |ins-expandtab|. When 'expandtab' is not set, the
+ number of spaces is minimized by using <Tab>s.
+ ]=],
+ full_name = 'smarttab',
+ scope = { 'global' },
+ short_desc = N_("use 'shiftwidth' when inserting <Tab>"),
+ type = 'bool',
+ varname = 'p_sta',
+ },
+ {
+ abbreviation = 'sts',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Number of spaces that a <Tab> counts for while performing editing
+ operations, like inserting a <Tab> or using <BS>. It "feels" like
+ <Tab>s are being inserted, while in fact a mix of spaces and <Tab>s is
+ used. This is useful to keep the 'ts' setting at its standard value
+ of 8, while being able to edit like it is set to 'sts'. However,
+ commands like "x" still work on the actual characters.
+ When 'sts' is zero, this feature is off.
+ When 'sts' is negative, the value of 'shiftwidth' is used.
+ See also |ins-expandtab|. When 'expandtab' is not set, the number of
+ spaces is minimized by using <Tab>s.
+ The 'L' flag in 'cpoptions' changes how tabs are used when 'list' is
+ set.
+
+ The value of 'softtabstop' will be ignored if |'varsofttabstop'| is set
+ to anything other than an empty string.
+ ]=],
+ full_name = 'softtabstop',
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces that <Tab> uses while editing'),
+ type = 'number',
+ varname = 'p_sts',
+ },
+ {
+ cb = 'did_set_spell',
+ defaults = { if_true = false },
+ desc = [=[
+ When on spell checking will be done. See |spell|.
+ The languages are specified with 'spelllang'.
+ ]=],
+ full_name = 'spell',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('spell checking'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'spc',
+ alloced = true,
+ cb = 'did_set_spellcapcheck',
+ defaults = { if_true = '[.?!]\\_[\\])\'"\\t ]\\+' },
+ desc = [=[
+ Pattern to locate the end of a sentence. The following word will be
+ checked to start with a capital letter. If not then it is highlighted
+ with SpellCap |hl-SpellCap| (unless the word is also badly spelled).
+ When this check is not wanted make this option empty.
+ Only used when 'spell' is set.
+ Be careful with special characters, see |option-backslash| about
+ including spaces and backslashes.
+ To set this option automatically depending on the language, see
+ |set-spc-auto|.
+ ]=],
+ full_name = 'spellcapcheck',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('pattern to locate end of a sentence'),
+ type = 'string',
+ varname = 'p_spc',
+ },
+ {
+ abbreviation = 'spf',
+ alloced = true,
+ cb = 'did_set_spellfile',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Name of the word list file where words are added for the |zg| and |zw|
+ commands. It must end in ".{encoding}.add". You need to include the
+ path, otherwise the file is placed in the current directory.
+ The path may include characters from 'isfname', space, comma and '@'.
+ *E765*
+ It may also be a comma-separated list of names. A count before the
+ |zg| and |zw| commands can be used to access each. This allows using
+ a personal word list file and a project word list file.
+ When a word is added while this option is empty Vim will set it for
+ you: Using the first directory in 'runtimepath' that is writable. If
+ there is no "spell" directory yet it will be created. For the file
+ name the first language name that appears in 'spelllang' is used,
+ ignoring the region.
+ The resulting ".spl" file will be used for spell checking, it does not
+ have to appear in 'spelllang'.
+ Normally one file is used for all regions, but you can add the region
+ name if you want to. However, it will then only be used when
+ 'spellfile' is set to it, for entries in 'spelllang' only files
+ without region name will be found.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'spellfile',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('files where |zg| and |zw| store words'),
+ type = 'string',
+ varname = 'p_spf',
+ },
+ {
+ abbreviation = 'spl',
+ alloced = true,
+ cb = 'did_set_spelllang',
+ defaults = { if_true = 'en' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of word list names. When the 'spell' option is
+ on spellchecking will be done for these languages. Example: >
+ set spelllang=en_us,nl,medical
+ < This means US English, Dutch and medical words are recognized. Words
+ that are not recognized will be highlighted.
+ The word list name must consist of alphanumeric characters, a dash or
+ an underscore. It should not include a comma or dot. Using a dash is
+ recommended to separate the two letter language name from a
+ specification. Thus "en-rare" is used for rare English words.
+ A region name must come last and have the form "_xx", where "xx" is
+ the two-letter, lower case region name. You can use more than one
+ region by listing them: "en_us,en_ca" supports both US and Canadian
+ English, but not words specific for Australia, New Zealand or Great
+ Britain. (Note: currently en_au and en_nz dictionaries are older than
+ en_ca, en_gb and en_us).
+ If the name "cjk" is included East Asian characters are excluded from
+ spell checking. This is useful when editing text that also has Asian
+ words.
+ Note that the "medical" dictionary does not exist, it is just an
+ example of a longer name.
+ *E757*
+ As a special case the name of a .spl file can be given as-is. The
+ first "_xx" in the name is removed and used as the region name
+ (_xx is an underscore, two letters and followed by a non-letter).
+ This is mainly for testing purposes. You must make sure the correct
+ encoding is used, Vim doesn't check it.
+ How the related spell files are found is explained here: |spell-load|.
+
+ If the |spellfile.vim| plugin is active and you use a language name
+ for which Vim cannot find the .spl file in 'runtimepath' the plugin
+ will ask you if you want to download the file.
+
+ After this option has been set successfully, Vim will source the files
+ "spell/LANG.vim" in 'runtimepath'. "LANG" is the value of 'spelllang'
+ up to the first character that is not an ASCII letter or number and
+ not a dash. Also see |set-spc-auto|.
+ ]=],
+ expand = true,
+ full_name = 'spelllang',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('language(s) to do spell checking for'),
+ type = 'string',
+ varname = 'p_spl',
+ },
+ {
+ abbreviation = 'sps',
+ cb = 'did_set_spellsuggest',
+ defaults = { if_true = 'best' },
+ deny_duplicates = true,
+ desc = [=[
+ Methods used for spelling suggestions. Both for the |z=| command and
+ the |spellsuggest()| function. This is a comma-separated list of
+ items:
+
+ best Internal method that works best for English. Finds
+ changes like "fast" and uses a bit of sound-a-like
+ scoring to improve the ordering.
+
+ double Internal method that uses two methods and mixes the
+ results. The first method is "fast", the other method
+ computes how much the suggestion sounds like the bad
+ word. That only works when the language specifies
+ sound folding. Can be slow and doesn't always give
+ better results.
+
+ fast Internal method that only checks for simple changes:
+ character inserts/deletes/swaps. Works well for
+ simple typing mistakes.
+
+ {number} The maximum number of suggestions listed for |z=|.
+ Not used for |spellsuggest()|. The number of
+ suggestions is never more than the value of 'lines'
+ minus two.
+
+ timeout:{millisec} Limit the time searching for suggestions to
+ {millisec} milli seconds. Applies to the following
+ methods. When omitted the limit is 5000. When
+ negative there is no limit.
+
+ file:{filename} Read file {filename}, which must have two columns,
+ separated by a slash. The first column contains the
+ bad word, the second column the suggested good word.
+ Example:
+ theribal/terrible ~
+ Use this for common mistakes that do not appear at the
+ top of the suggestion list with the internal methods.
+ Lines without a slash are ignored, use this for
+ comments.
+ The word in the second column must be correct,
+ otherwise it will not be used. Add the word to an
+ ".add" file if it is currently flagged as a spelling
+ mistake.
+ The file is used for all languages.
+
+ expr:{expr} Evaluate expression {expr}. Use a function to avoid
+ trouble with spaces. |v:val| holds the badly spelled
+ word. The expression must evaluate to a List of
+ Lists, each with a suggestion and a score.
+ Example:
+ [['the', 33], ['that', 44]] ~
+ Set 'verbose' and use |z=| to see the scores that the
+ internal methods use. A lower score is better.
+ This may invoke |spellsuggest()| if you temporarily
+ set 'spellsuggest' to exclude the "expr:" part.
+ Errors are silently ignored, unless you set the
+ 'verbose' option to a non-zero value.
+
+ Only one of "best", "double" or "fast" may be used. The others may
+ appear several times in any order. Example: >
+ :set sps=file:~/.config/nvim/sugg,best,expr:MySuggest()
+ <
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ expand_cb = 'expand_set_spellsuggest',
+ full_name = 'spellsuggest',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('method(s) used to suggest spelling corrections'),
+ type = 'string',
+ varname = 'p_sps',
+ },
+ {
+ abbreviation = 'spo',
+ cb = 'did_set_spelloptions',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for spell checking:
+ camel When a word is CamelCased, assume "Cased" is a
+ separate word: every upper-case character in a word
+ that comes after a lower case character indicates the
+ start of a new word.
+ noplainbuffer Only spellcheck a buffer when 'syntax' is enabled,
+ or when extmarks are set within the buffer. Only
+ designated regions of the buffer are spellchecked in
+ this case.
+ ]=],
+ expand_cb = 'expand_set_spelloptions',
+ full_name = 'spelloptions',
+ list = 'onecomma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ secure = true,
+ type = 'string',
+ varname = 'p_spo',
+ },
+ {
+ abbreviation = 'sb',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, splitting a window will put the new window below the current
+ one. |:split|
+ ]=],
+ full_name = 'splitbelow',
+ scope = { 'global' },
+ short_desc = N_('new window from split is below the current one'),
+ type = 'bool',
+ varname = 'p_sb',
+ },
+ {
+ abbreviation = 'spk',
+ cb = 'did_set_splitkeep',
+ defaults = { if_true = 'cursor' },
+ desc = [=[
+ The value of this option determines the scroll behavior when opening,
+ closing or resizing horizontal splits.
+
+ Possible values are:
+ cursor Keep the same relative cursor position.
+ screen Keep the text on the same screen line.
+ topline Keep the topline the same.
+
+ For the "screen" and "topline" values, the cursor position will be
+ changed when necessary. In this case, the jumplist will be populated
+ with the previous cursor position. For "screen", the text cannot always
+ be kept on the same screen line when 'wrap' is enabled.
+ ]=],
+ expand_cb = 'expand_set_splitkeep',
+ full_name = 'splitkeep',
+ scope = { 'global' },
+ short_desc = N_('determines scroll behavior for split windows'),
+ type = 'string',
+ varname = 'p_spk',
+ },
+ {
+ abbreviation = 'spr',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, splitting a window will put the new window right of the
+ current one. |:vsplit|
+ ]=],
+ full_name = 'splitright',
+ scope = { 'global' },
+ short_desc = N_('new window is put right of the current one'),
+ type = 'bool',
+ varname = 'p_spr',
+ },
+ {
+ abbreviation = 'sol',
+ defaults = { if_true = false },
+ desc = [=[
+ When "on" the commands listed below move the cursor to the first
+ non-blank of the line. When off the cursor is kept in the same column
+ (if possible). This applies to the commands:
+ - CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg"
+ - "d", "<<" and ">>" with a linewise operator
+ - "%" with a count
+ - buffer changing commands (CTRL-^, :bnext, :bNext, etc.)
+ - Ex commands that only have a line number, e.g., ":25" or ":+".
+ In case of buffer changing commands the cursor is placed at the column
+ where it was the last time the buffer was edited.
+ ]=],
+ full_name = 'startofline',
+ scope = { 'global' },
+ short_desc = N_('commands move cursor to first non-blank in line'),
+ type = 'bool',
+ varname = 'p_sol',
+ vim = false,
+ },
+ {
+ abbreviation = 'stc',
+ alloced = true,
+ cb = 'did_set_statuscolumn',
+ defaults = { if_true = '' },
+ desc = [=[
+ EXPERIMENTAL
+ When non-empty, this option determines the content of the area to the
+ side of a window, normally containing the fold, sign and number columns.
+ The format of this option is like that of 'statusline'.
+
+ Some of the items from the 'statusline' format are different for
+ 'statuscolumn':
+
+ %l line number of currently drawn line
+ %r relative line number of currently drawn line
+ %s sign column for currently drawn line
+ %C fold column for currently drawn line
+
+ NOTE: To draw the sign and fold columns, their items must be included in
+ 'statuscolumn'. Even when they are not included, the status column width
+ will adapt to the 'signcolumn' and 'foldcolumn' width.
+
+ The |v:lnum| variable holds the line number to be drawn.
+ The |v:relnum| variable holds the relative line number to be drawn.
+ The |v:virtnum| variable is negative when drawing virtual lines, zero
+ when drawing the actual buffer line, and positive when
+ drawing the wrapped part of a buffer line.
+
+ NOTE: The %@ click execute function item is supported as well but the
+ specified function will be the same for each row in the same column.
+ It cannot be switched out through a dynamic 'statuscolumn' format, the
+ handler should be written with this in mind.
+
+ Examples: >vim
+ " Relative number with bar separator and click handlers:
+ :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
+
+ " Right aligned relative cursor line number:
+ :let &stc='%=%{v:relnum?v:relnum:v:lnum} '
+
+ " Line numbers in hexadecimal for non wrapped part of lines:
+ :let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
+
+ " Human readable line numbers with thousands separator:
+ :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\'
+ . '%(\\d\\d\\d\\)\\+$",",","g")}'
+
+ " Both relative and absolute line numbers with different
+ " highlighting for odd and even relative numbers:
+ :let &stc='%#NonText#%{&nu?v:lnum:""}' .
+ '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' .
+ '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}'
+
+ < WARNING: this expression is evaluated for each screen line so defining
+ an expensive expression can negatively affect render performance.
+ ]=],
+ full_name = 'statuscolumn',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ secure = true,
+ short_desc = N_('custom format for the status column'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'stl',
+ alloced = true,
+ cb = 'did_set_statusline',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option determines the content of the status line.
+ Also see |status-line|.
+
+ The option consists of printf style '%' items interspersed with
+ normal text. Each status line item is of the form:
+ %-0{minwid}.{maxwid}{item}
+ All fields except the {item} are optional. A single percent sign can
+ be given as "%%".
+
+ When the option starts with "%!" then it is used as an expression,
+ evaluated and the result is used as the option value. Example: >
+ :set statusline=%!MyStatusLine()
+ < The *g:statusline_winid* variable will be set to the |window-ID| of the
+ window that the status line belongs to.
+ The result can contain %{} items that will be evaluated too.
+ Note that the "%!" expression is evaluated in the context of the
+ current window and buffer, while %{} items are evaluated in the
+ context of the window that the statusline belongs to.
+
+ When there is error while evaluating the option then it will be made
+ empty to avoid further errors. Otherwise screen updating would loop.
+ When the result contains unprintable characters the result is
+ unpredictable.
+
+ Note that the only effect of 'ruler' when this option is set (and
+ 'laststatus' is 2 or 3) is controlling the output of |CTRL-G|.
+
+ field meaning ~
+ - Left justify the item. The default is right justified
+ when minwid is larger than the length of the item.
+ 0 Leading zeroes in numeric items. Overridden by "-".
+ minwid Minimum width of the item, padding as set by "-" & "0".
+ Value must be 50 or less.
+ maxwid Maximum width of the item. Truncation occurs with a "<"
+ on the left for text items. Numeric items will be
+ shifted down to maxwid-2 digits followed by ">"number
+ where number is the amount of missing digits, much like
+ an exponential notation.
+ item A one letter code as described below.
+
+ Following is a description of the possible statusline items. The
+ second character in "item" is the type:
+ N for number
+ S for string
+ F for flags as described below
+ - not applicable
+
+ item meaning ~
+ f S Path to the file in the buffer, as typed or relative to current
+ directory.
+ F S Full path to the file in the buffer.
+ t S File name (tail) of file in the buffer.
+ m F Modified flag, text is "[+]"; "[-]" if 'modifiable' is off.
+ M F Modified flag, text is ",+" or ",-".
+ r F Readonly flag, text is "[RO]".
+ R F Readonly flag, text is ",RO".
+ h F Help buffer flag, text is "[help]".
+ H F Help buffer flag, text is ",HLP".
+ w F Preview window flag, text is "[Preview]".
+ W F Preview window flag, text is ",PRV".
+ y F Type of file in the buffer, e.g., "[vim]". See 'filetype'.
+ Y F Type of file in the buffer, e.g., ",VIM". See 'filetype'.
+ q S "[Quickfix List]", "[Location List]" or empty.
+ k S Value of "b:keymap_name" or 'keymap' when |:lmap| mappings are
+ being used: "<keymap>"
+ n N Buffer number.
+ b N Value of character under cursor.
+ B N As above, in hexadecimal.
+ o N Byte number in file of byte under cursor, first byte is 1.
+ Mnemonic: Offset from start of file (with one added)
+ O N As above, in hexadecimal.
+ l N Line number.
+ L N Number of lines in buffer.
+ c N Column number (byte index).
+ v N Virtual column number (screen column).
+ V N Virtual column number as -{num}. Not displayed if equal to 'c'.
+ p N Percentage through file in lines as in |CTRL-G|.
+ P S Percentage through file of displayed window. This is like the
+ percentage described for 'ruler'. Always 3 in length, unless
+ translated.
+ S S 'showcmd' content, see 'showcmdloc'.
+ a S Argument list status as in default title. ({current} of {max})
+ Empty if the argument file count is zero or one.
+ { NF Evaluate expression between "%{" and "}" and substitute result.
+ Note that there is no "%" before the closing "}". The
+ expression cannot contain a "}" character, call a function to
+ work around that. See |stl-%{| below.
+ `{%` - This is almost same as "{" except the result of the expression is
+ re-evaluated as a statusline format string. Thus if the
+ return value of expr contains "%" items they will get expanded.
+ The expression can contain the "}" character, the end of
+ expression is denoted by "%}".
+ For example: >
+ func! Stl_filename() abort
+ return "%t"
+ endfunc
+ < `stl=%{Stl_filename()}` results in `"%t"`
+ `stl=%{%Stl_filename()%}` results in `"Name of current file"`
+ %} - End of "{%" expression
+ ( - Start of item group. Can be used for setting the width and
+ alignment of a section. Must be followed by %) somewhere.
+ ) - End of item group. No width fields allowed.
+ T N For 'tabline': start of tab page N label. Use %T or %X to end
+ the label. Clicking this label with left mouse button switches
+ to the specified tab page.
+ X N For 'tabline': start of close tab N label. Use %X or %T to end
+ the label, e.g.: %3Xclose%X. Use %999X for a "close current
+ tab" label. Clicking this label with left mouse button closes
+ specified tab page.
+ @ N Start of execute function label. Use %X or %T to
+ end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
+ label runs specified function: in the example when clicking once
+ using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
+ ' ')" expression will be run. Function receives the
+ following arguments in order:
+ 1. minwid field value or zero if no N was specified
+ 2. number of mouse clicks to detect multiple clicks
+ 3. mouse button used: "l", "r" or "m" for left, right or middle
+ button respectively; one should not rely on third argument
+ being only "l", "r" or "m": any other non-empty string value
+ that contains only ASCII lower case letters may be expected
+ for other mouse buttons
+ 4. modifiers pressed: string which contains "s" if shift
+ modifier was pressed, "c" for control, "a" for alt and "m"
+ for meta; currently if modifier is not pressed string
+ contains space instead, but one should not rely on presence
+ of spaces or specific order of modifiers: use |stridx()| to
+ test whether some modifier is present; string is guaranteed
+ to contain only ASCII letters and spaces, one letter per
+ modifier; "?" modifier may also be present, but its presence
+ is a bug that denotes that new mouse button recognition was
+ added without modifying code that reacts on mouse clicks on
+ this label.
+ Use |getmousepos()|.winid in the specified function to get the
+ corresponding window id of the clicked item.
+ \< - Where to truncate line if too long. Default is at the start.
+ No width fields allowed.
+ = - Separation point between alignment sections. Each section will
+ be separated by an equal number of spaces. With one %= what
+ comes after it will be right-aligned. With two %= there is a
+ middle part, with white space left and right of it.
+ No width fields allowed.
+ # - Set highlight group. The name must follow and then a # again.
+ Thus use %#HLname# for highlight group HLname. The same
+ highlighting is used, also for the statusline of non-current
+ windows.
+ * - Set highlight group to User{N}, where {N} is taken from the
+ minwid field, e.g. %1*. Restore normal highlight with %* or %0*.
+ The difference between User{N} and StatusLine will be applied to
+ StatusLineNC for the statusline of non-current windows.
+ The number N must be between 1 and 9. See |hl-User1..9|
+
+ When displaying a flag, Vim removes the leading comma, if any, when
+ that flag comes right after plaintext. This will make a nice display
+ when flags are used like in the examples below.
+
+ When all items in a group becomes an empty string (i.e. flags that are
+ not set) and a minwid is not set for the group, the whole group will
+ become empty. This will make a group like the following disappear
+ completely from the statusline when none of the flags are set. >
+ :set statusline=...%(\ [%M%R%H]%)...
+ < Beware that an expression is evaluated each and every time the status
+ line is displayed.
+ *stl-%{* *g:actual_curbuf* *g:actual_curwin*
+ While evaluating %{} the current buffer and current window will be set
+ temporarily to that of the window (and buffer) whose statusline is
+ currently being drawn. The expression will evaluate in this context.
+ The variable "g:actual_curbuf" is set to the `bufnr()` number of the
+ real current buffer and "g:actual_curwin" to the |window-ID| of the
+ real current window. These values are strings.
+
+ The 'statusline' option will be evaluated in the |sandbox| if set from
+ a modeline, see |sandbox-option|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ It is not allowed to change text or jump to another window while
+ evaluating 'statusline' |textlock|.
+
+ If the statusline is not updated when you want it (e.g., after setting
+ a variable that's used in an expression), you can force an update by
+ using `:redrawstatus`.
+
+ A result of all digits is regarded a number for display purposes.
+ Otherwise the result is taken as flag text and applied to the rules
+ described above.
+
+ Watch out for errors in expressions. They may render Vim unusable!
+ If you are stuck, hold down ':' or 'Q' to get a prompt, then quit and
+ edit your vimrc or whatever with "vim --clean" to get it right.
+
+ Examples:
+ Emulate standard status line with 'ruler' set >
+ :set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+ < Similar, but add ASCII value of char under the cursor (like "ga") >
+ :set statusline=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
+ < Display byte count and byte value, modified flag in red. >
+ :set statusline=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
+ :hi User1 term=inverse,bold cterm=inverse,bold ctermfg=red
+ < Display a ,GZ flag if a compressed file is loaded >
+ :set statusline=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
+ < In the |:autocmd|'s: >
+ :let b:gzflag = 1
+ < And: >
+ :unlet b:gzflag
+ < And define this function: >
+ :function VarExists(var, val)
+ : if exists(a:var) | return a:val | else | return '' | endif
+ :endfunction
+ <
+ ]=],
+ full_name = 'statusline',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global', 'window' },
+ short_desc = N_('custom format for the status line'),
+ tags = { 'E540', 'E542' },
+ type = 'string',
+ varname = 'p_stl',
+ },
+ {
+ abbreviation = 'su',
+ defaults = { if_true = '.bak,~,.o,.h,.info,.swp,.obj' },
+ deny_duplicates = true,
+ desc = [=[
+ Files with these suffixes get a lower priority when multiple files
+ match a wildcard. See |suffixes|. Commas can be used to separate the
+ suffixes. Spaces after the comma are ignored. A dot is also seen as
+ the start of a suffix. To avoid a dot or comma being recognized as a
+ separator, precede it with a backslash (see |option-backslash| about
+ including spaces and backslashes).
+ See 'wildignore' for completely ignoring files.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ suffixes from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ full_name = 'suffixes',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('suffixes that are ignored with multiple match'),
+ type = 'string',
+ varname = 'p_su',
+ },
+ {
+ abbreviation = 'sua',
+ alloced = true,
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Comma-separated list of suffixes, which are used when searching for a
+ file for the "gf", "[I", etc. commands. Example: >
+ :set suffixesadd=.java
+ <
+ ]=],
+ full_name = 'suffixesadd',
+ list = 'onecomma',
+ scope = { 'buffer' },
+ short_desc = N_('suffixes added when searching for a file'),
+ type = 'string',
+ varname = 'p_sua',
+ },
+ {
+ abbreviation = 'swf',
+ cb = 'did_set_swapfile',
+ defaults = { if_true = true },
+ desc = [=[
+ Use a swapfile for the buffer. This option can be reset when a
+ swapfile is not wanted for a specific buffer. For example, with
+ confidential information that even root must not be able to access.
+ Careful: All text will be in memory:
+ - Don't use this for big files.
+ - Recovery will be impossible!
+ A swapfile will only be present when |'updatecount'| is non-zero and
+ 'swapfile' is set.
+ When 'swapfile' is reset, the swap file for the current buffer is
+ immediately deleted. When 'swapfile' is set, and 'updatecount' is
+ non-zero, a swap file is immediately created.
+ Also see |swap-file|.
+ If you want to open a new buffer without creating a swap file for it,
+ use the |:noswapfile| modifier.
+ See 'directory' for where the swap file is created.
+
+ This option is used together with 'bufhidden' and 'buftype' to
+ specify special kinds of buffers. See |special-buffers|.
+ ]=],
+ full_name = 'swapfile',
+ redraw = { 'statuslines' },
+ scope = { 'buffer' },
+ short_desc = N_('whether to use a swapfile for a buffer'),
+ type = 'bool',
+ varname = 'p_swf',
+ },
+ {
+ abbreviation = 'swb',
+ cb = 'did_set_switchbuf',
+ defaults = { if_true = 'uselast' },
+ deny_duplicates = true,
+ desc = [=[
+ This option controls the behavior when switching between buffers.
+ This option is checked, when
+ - jumping to errors with the |quickfix| commands (|:cc|, |:cn|, |:cp|,
+ etc.).
+ - jumping to a tag using the |:stag| command.
+ - opening a file using the |CTRL-W_f| or |CTRL-W_F| command.
+ - jumping to a buffer using a buffer split command (e.g. |:sbuffer|,
+ |:sbnext|, or |:sbrewind|).
+ Possible values (comma-separated list):
+ useopen If included, jump to the first open window in the
+ current tab page that contains the specified buffer
+ (if there is one). Otherwise: Do not examine other
+ windows.
+ usetab Like "useopen", but also consider windows in other tab
+ pages.
+ split If included, split the current window before loading
+ a buffer for a |quickfix| command that display errors.
+ Otherwise: do not split, use current window (when used
+ in the quickfix window: the previously used window or
+ split if there is no other window).
+ vsplit Just like "split" but split vertically.
+ newtab Like "split", but open a new tab page. Overrules
+ "split" when both are present.
+ uselast If included, jump to the previously used window when
+ jumping to errors with |quickfix| commands.
+ ]=],
+ expand_cb = 'expand_set_switchbuf',
+ full_name = 'switchbuf',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('sets behavior when switching to another buffer'),
+ type = 'string',
+ varname = 'p_swb',
+ },
+ {
+ abbreviation = 'smc',
+ defaults = { if_true = 3000 },
+ desc = [=[
+ Maximum column in which to search for syntax items. In long lines the
+ text after this column is not highlighted and following lines may not
+ be highlighted correctly, because the syntax state is cleared.
+ This helps to avoid very slow redrawing for an XML file that is one
+ long line.
+ Set to zero to remove the limit.
+ ]=],
+ full_name = 'synmaxcol',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('maximum column to find syntax items'),
+ type = 'number',
+ varname = 'p_smc',
+ },
+ {
+ abbreviation = 'syn',
+ alloced = true,
+ cb = 'did_set_filetype_or_syntax',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is set, the syntax with this name is loaded, unless
+ syntax highlighting has been switched off with ":syntax off".
+ Otherwise this option does not always reflect the current syntax (the
+ b:current_syntax variable does).
+ This option is most useful in a modeline, for a file which syntax is
+ not automatically recognized. Example, in an IDL file: >
+ /* vim: set syntax=idl : */
+ < When a dot appears in the value then this separates two filetype
+ names. Example: >
+ /* vim: set syntax=c.doxygen : */
+ < This will use the "c" syntax first, then the "doxygen" syntax.
+ Note that the second one must be prepared to be loaded as an addition,
+ otherwise it will be skipped. More than one dot may appear.
+ To switch off syntax highlighting for the current file, use: >
+ :set syntax=OFF
+ < To switch syntax highlighting on according to the current value of the
+ 'filetype' option: >
+ :set syntax=ON
+ < What actually happens when setting the 'syntax' option is that the
+ Syntax autocommand event is triggered with the value as argument.
+ This option is not copied to another buffer, independent of the 's' or
+ 'S' flag in 'cpoptions'.
+ Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ ]=],
+ full_name = 'syntax',
+ noglob = true,
+ normal_fname_chars = true,
+ scope = { 'buffer' },
+ short_desc = N_('syntax to be loaded for current buffer'),
+ type = 'string',
+ varname = 'p_syn',
+ },
+ {
+ abbreviation = 'tfu',
+ cb = 'did_set_tagfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used to perform tag searches.
+ The function gets the tag pattern and should return a List of matching
+ tags. See |tag-function| for an explanation of how to write the
+ function and an example. The value can be the name of a function, a
+ |lambda| or a |Funcref|. See |option-value-function| for more
+ information.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'tagfunc',
+ func = true,
+ scope = { 'buffer' },
+ secure = true,
+ short_desc = N_('function used to perform tag searches'),
+ type = 'string',
+ varname = 'p_tfu',
+ },
+ {
+ abbreviation = 'tal',
+ cb = 'did_set_tabline',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option determines the content of the tab pages
+ line at the top of the Vim window. When empty Vim will use a default
+ tab pages line. See |setting-tabline| for more info.
+
+ The tab pages line only appears as specified with the 'showtabline'
+ option and only when there is no GUI tab line. When 'e' is in
+ 'guioptions' and the GUI supports a tab line 'guitablabel' is used
+ instead. Note that the two tab pages lines are very different.
+
+ The value is evaluated like with 'statusline'. You can use
+ |tabpagenr()|, |tabpagewinnr()| and |tabpagebuflist()| to figure out
+ the text to be displayed. Use "%1T" for the first label, "%2T" for
+ the second one, etc. Use "%X" items for closing labels.
+
+ When changing something that is used in 'tabline' that does not
+ trigger it to be updated, use |:redrawtabline|.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Keep in mind that only one of the tab pages is the current one, others
+ are invisible and you can't jump to their windows.
+ ]=],
+ full_name = 'tabline',
+ modelineexpr = true,
+ redraw = { 'tabline' },
+ scope = { 'global' },
+ short_desc = N_('custom format for the console tab pages line'),
+ type = 'string',
+ varname = 'p_tal',
+ },
+ {
+ abbreviation = 'tpm',
+ defaults = { if_true = 50 },
+ desc = [=[
+ Maximum number of tab pages to be opened by the |-p| command line
+ argument or the ":tab all" command. |tabpage|
+ ]=],
+ full_name = 'tabpagemax',
+ scope = { 'global' },
+ short_desc = N_('maximum number of tab pages for |-p| and "tab all"'),
+ type = 'number',
+ varname = 'p_tpm',
+ },
+ {
+ abbreviation = 'ts',
+ cb = 'did_set_shiftwidth_tabstop',
+ defaults = { if_true = 8 },
+ desc = [=[
+ Number of spaces that a <Tab> in the file counts for. Also see
+ 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.
+ 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
+ (or 3 or whatever you prefer) and use 'noexpandtab'. Then Vim
+ will use a mix of tabs and spaces, but typing <Tab> and <BS> will
+ behave like a tab appears every 4 (or 3) characters.
+ This is the recommended way, the file will look the same with other
+ tools and when listing it in a terminal.
+ 2. Set 'softtabstop' and 'shiftwidth' to whatever you prefer and use
+ 'expandtab'. This way you will always insert spaces. The
+ formatting will never be messed up when 'tabstop' is changed (leave
+ it at 8 just in case). The file will be a bit larger.
+ You do need to check if no Tabs exist in the file. You can get rid
+ of them by first setting 'expandtab' and using `%retab!`, making
+ sure the value of 'tabstop' is set correctly.
+ 3. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use
+ 'expandtab'. This way you will always insert spaces. The
+ formatting will never be messed up when 'tabstop' is changed.
+ You do need to check if no Tabs exist in the file, just like in the
+ item just above.
+ 4. Set 'tabstop' and 'shiftwidth' to whatever you prefer and use a
+ |modeline| to set these values when editing the file again. Only
+ works when using Vim to edit the file, other tools assume a tabstop
+ is worth 8 spaces.
+ 5. Always set 'tabstop' and 'shiftwidth' to the same value, and
+ 'noexpandtab'. This should then work (for initial indents only)
+ for any tabstop setting that people use. It might be nice to have
+ tabs after the first non-blank inserted as spaces if you do this
+ though. Otherwise aligned comments will be wrong when 'tabstop' is
+ changed.
+
+ The value of 'tabstop' will be ignored if |'vartabstop'| is set to
+ anything other than an empty string.
+ ]=],
+ full_name = 'tabstop',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('number of spaces that <Tab> in file uses'),
+ type = 'number',
+ varname = 'p_ts',
+ },
+ {
+ abbreviation = 'tbs',
+ defaults = { if_true = true },
+ desc = [=[
+ When searching for a tag (e.g., for the |:ta| command), Vim can either
+ use a binary search or a linear search in a tags file. Binary
+ searching makes searching for a tag a LOT faster, but a linear search
+ will find more tags if the tags file wasn't properly sorted.
+ Vim normally assumes that your tags files are sorted, or indicate that
+ they are not sorted. Only when this is not the case does the
+ 'tagbsearch' option need to be switched off.
+
+ When 'tagbsearch' is on, binary searching is first used in the tags
+ files. In certain situations, Vim will do a linear search instead for
+ certain files, or retry all files with a linear search. When
+ 'tagbsearch' is off, only a linear search is done.
+
+ Linear searching is done anyway, for one file, when Vim finds a line
+ at the start of the file indicating that it's not sorted: >
+ !_TAG_FILE_SORTED 0 /some comment/
+ < [The whitespace before and after the '0' must be a single <Tab>]
+
+ When a binary search was done and no match was found in any of the
+ files listed in 'tags', and case is ignored or a pattern is used
+ instead of a normal tag name, a retry is done with a linear search.
+ Tags in unsorted tags files, and matches with different case will only
+ be found in the retry.
+
+ If a tag file indicates that it is case-fold sorted, the second,
+ linear search can be avoided when case is ignored. Use a value of '2'
+ in the "!_TAG_FILE_SORTED" line for this. A tag file can be case-fold
+ sorted with the -f switch to "sort" in most unices, as in the command:
+ "sort -f -o tags tags". For Universal ctags and Exuberant ctags
+ version 5.x or higher (at least 5.5) the --sort=foldcase switch can be
+ used for this as well. Note that case must be folded to uppercase for
+ this to work.
+
+ By default, tag searches are case-sensitive. Case is ignored when
+ 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is
+ "ignore".
+ Also when 'tagcase' is "followscs" and 'smartcase' is set, or
+ 'tagcase' is "smart", and the pattern contains only lowercase
+ characters.
+
+ When 'tagbsearch' is off, tags searching is slower when a full match
+ exists, but faster when no full match exists. Tags in unsorted tags
+ files may only be found with 'tagbsearch' off.
+ When the tags file is not sorted, or sorted in a wrong way (not on
+ ASCII byte value), 'tagbsearch' should be off, or the line given above
+ must be included in the tags file.
+ This option doesn't affect commands that find all matching tags (e.g.,
+ command-line completion and ":help").
+ ]=],
+ full_name = 'tagbsearch',
+ scope = { 'global' },
+ short_desc = N_('use binary searching in tags files'),
+ type = 'bool',
+ varname = 'p_tbs',
+ },
+ {
+ abbreviation = 'tc',
+ cb = 'did_set_tagcase',
+ defaults = { if_true = 'followic' },
+ desc = [=[
+ This option specifies how case is handled when searching the tags
+ file:
+ followic Follow the 'ignorecase' option
+ followscs Follow the 'smartcase' and 'ignorecase' options
+ ignore Ignore case
+ match Match case
+ smart Ignore case unless an upper case letter is used
+ ]=],
+ expand_cb = 'expand_set_tagcase',
+ full_name = 'tagcase',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('how to handle case when searching in tags files'),
+ type = 'string',
+ varname = 'p_tc',
+ },
+ {
+ abbreviation = 'tl',
+ defaults = { if_true = 0 },
+ desc = [=[
+ If non-zero, tags are significant up to this number of characters.
+ ]=],
+ full_name = 'taglength',
+ scope = { 'global' },
+ short_desc = N_('number of significant characters for a tag'),
+ type = 'number',
+ varname = 'p_tl',
+ },
+ {
+ abbreviation = 'tr',
+ defaults = { if_true = true },
+ desc = [=[
+ If on and using a tags file in another directory, file names in that
+ tags file are relative to the directory where the tags file is.
+ ]=],
+ full_name = 'tagrelative',
+ scope = { 'global' },
+ short_desc = N_('file names in tag file are relative'),
+ type = 'bool',
+ varname = 'p_tr',
+ },
+ {
+ abbreviation = 'tag',
+ defaults = { if_true = './tags;,tags' },
+ deny_duplicates = true,
+ desc = [=[
+ Filenames for the tag command, separated by spaces or commas. To
+ include a space or comma in a file name, precede it with backslashes
+ (see |option-backslash| about including spaces/commas and backslashes).
+ When a file name starts with "./", the '.' is replaced with the path
+ of the current file. But only when the 'd' flag is not included in
+ 'cpoptions'. Environment variables are expanded |:set_env|. Also see
+ |tags-option|.
+ "*", "**" and other wildcards can be used to search for tags files in
+ a directory tree. See |file-searching|. E.g., "/lib/**/tags" will
+ find all files named "tags" below "/lib". The filename itself cannot
+ contain wildcards, it is used as-is. E.g., "/lib/**/tags?" will find
+ files called "tags?".
+ The |tagfiles()| function can be used to get a list of the file names
+ actually used.
+ The use of |:set+=| and |:set-=| is preferred when adding or removing
+ file names from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ expand = true,
+ full_name = 'tags',
+ list = 'onecomma',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of file names used by the tag command'),
+ tags = { 'E433' },
+ type = 'string',
+ varname = 'p_tags',
+ },
+ {
+ abbreviation = 'tgst',
+ defaults = { if_true = true },
+ desc = [=[
+ When on, the |tagstack| is used normally. When off, a ":tag" or
+ ":tselect" command with an argument will not push the tag onto the
+ tagstack. A following ":tag" without an argument, a ":pop" command or
+ any other command that uses the tagstack will use the unmodified
+ tagstack, but does change the pointer to the active entry.
+ Resetting this option is useful when using a ":tag" command in a
+ mapping which should not change the tagstack.
+ ]=],
+ full_name = 'tagstack',
+ scope = { 'global' },
+ short_desc = N_('push tags onto the tag stack'),
+ type = 'bool',
+ varname = 'p_tgst',
+ },
+ {
+ abbreviation = 'tbidi',
+ defaults = { if_true = false },
+ desc = [=[
+ The terminal is in charge of Bi-directionality of text (as specified
+ by Unicode). The terminal is also expected to do the required shaping
+ that some languages (such as Arabic) require.
+ Setting this option implies that 'rightleft' will not be set when
+ 'arabic' is set and the value of 'arabicshape' will be ignored.
+ Note that setting 'termbidi' has the immediate effect that
+ 'arabicshape' is ignored, but 'rightleft' isn't changed automatically.
+ For further details see |arabic.txt|.
+ ]=],
+ full_name = 'termbidi',
+ scope = { 'global' },
+ short_desc = N_('terminal takes care of bi-directionality'),
+ type = 'bool',
+ varname = 'p_tbidi',
+ },
+ {
+ abbreviation = 'tenc',
+ defaults = { if_true = '' },
+ full_name = 'termencoding',
+ scope = { 'global' },
+ short_desc = N_('Terminal encoding'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'tgc',
+ defaults = { if_true = false },
+ desc = [=[
+ Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight|
+ attributes instead of "cterm" attributes. |guifg|
+ Requires an ISO-8613-3 compatible terminal.
+ ]=],
+ full_name = 'termguicolors',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('Terminal true color support'),
+ type = 'bool',
+ varname = 'p_tgc',
+ },
+ {
+ abbreviation = 'tpf',
+ cb = 'did_set_termpastefilter',
+ defaults = { if_true = 'BS,HT,ESC,DEL' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of options for specifying control characters
+ to be removed from the text pasted into the terminal window. The
+ supported values are:
+
+ BS Backspace
+
+ HT TAB
+
+ FF Form feed
+
+ ESC Escape
+
+ DEL DEL
+
+ C0 Other control characters, excluding Line feed and
+ Carriage return < ' '
+
+ C1 Control characters 0x80...0x9F
+ ]=],
+ expand_cb = 'expand_set_termpastefilter',
+ full_name = 'termpastefilter',
+ list = 'onecomma',
+ scope = { 'global' },
+ type = 'string',
+ varname = 'p_tpf',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ If the host terminal supports it, buffer all screen updates
+ made during a redraw cycle so that each screen is displayed in
+ the terminal all at once. This can prevent tearing or flickering
+ when the terminal updates faster than Nvim can redraw.
+ ]=],
+ full_name = 'termsync',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('synchronize redraw output with the host terminal'),
+ type = 'bool',
+ varname = 'p_termsync',
+ },
+ {
+ defaults = { if_true = false },
+ full_name = 'terse',
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'tw',
+ cb = 'did_set_textwidth',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Maximum width of text that is being inserted. A longer line will be
+ broken after white space to get this width. A zero value disables
+ this.
+ When 'textwidth' is zero, 'wrapmargin' may be used. See also
+ 'formatoptions' and |ins-textwidth|.
+ When 'formatexpr' is set it will be used to break the line.
+ ]=],
+ full_name = 'textwidth',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('maximum width of text that is being inserted'),
+ type = 'number',
+ varname = 'p_tw',
+ },
+ {
+ abbreviation = 'tsr',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of file names, separated by commas, that are used to lookup words
+ for thesaurus completion commands |i_CTRL-X_CTRL-T|. See
+ |compl-thesaurus|.
+
+ This option is not used if 'thesaurusfunc' is set, either for the
+ buffer or globally.
+
+ To include a comma in a file name precede it with a backslash. Spaces
+ after a comma are ignored, otherwise spaces are included in the file
+ name. See |option-backslash| about using backslashes. The use of
+ |:set+=| and |:set-=| is preferred when adding or removing directories
+ from the list. This avoids problems when a future version uses
+ another default. Backticks cannot be used in this option for security
+ reasons.
+ ]=],
+ expand = true,
+ full_name = 'thesaurus',
+ list = 'onecomma',
+ normal_dname_chars = true,
+ scope = { 'global', 'buffer' },
+ short_desc = N_('list of thesaurus files for keyword completion'),
+ type = 'string',
+ varname = 'p_tsr',
+ },
+ {
+ abbreviation = 'tsrfu',
+ alloced = true,
+ cb = 'did_set_thesaurusfunc',
+ defaults = { if_true = '' },
+ desc = [=[
+ This option specifies a function to be used for thesaurus completion
+ with CTRL-X CTRL-T. |i_CTRL-X_CTRL-T| See |compl-thesaurusfunc|.
+ The value can be the name of a function, a |lambda| or a |Funcref|.
+ See |option-value-function| for more information.
+
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'thesaurusfunc',
+ func = true,
+ scope = { 'global', 'buffer' },
+ secure = true,
+ short_desc = N_('function used for thesaurus completion'),
+ type = 'string',
+ varname = 'p_tsrfu',
+ },
+ {
+ abbreviation = 'top',
+ defaults = { if_true = false },
+ desc = [=[
+ When on: The tilde command "~" behaves like an operator.
+ ]=],
+ full_name = 'tildeop',
+ scope = { 'global' },
+ short_desc = N_('tilde command "~" behaves like an operator'),
+ type = 'bool',
+ varname = 'p_to',
+ },
+ {
+ abbreviation = 'to',
+ defaults = { if_true = true },
+ desc = [=[
+ This option and 'timeoutlen' determine the behavior when part of a
+ mapped key sequence has been received. For example, if <c-f> is
+ pressed and 'timeout' is set, Nvim will wait 'timeoutlen' milliseconds
+ for any key that can follow <c-f> in a mapping.
+ ]=],
+ full_name = 'timeout',
+ scope = { 'global' },
+ short_desc = N_('time out on mappings and key codes'),
+ type = 'bool',
+ varname = 'p_timeout',
+ },
+ {
+ abbreviation = 'tm',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Time in milliseconds to wait for a mapped sequence to complete.
+ ]=],
+ full_name = 'timeoutlen',
+ scope = { 'global' },
+ short_desc = N_('time out time in milliseconds'),
+ type = 'number',
+ varname = 'p_tm',
+ },
+ {
+ cb = 'did_set_title_icon',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, the title of the window will be set to the value of
+ 'titlestring' (if it is not empty), or to:
+ filename [+=-] (path) - NVIM
+ Where:
+ filename the name of the file being edited
+ - indicates the file cannot be modified, 'ma' off
+ + indicates the file was modified
+ = indicates the file is read-only
+ =+ indicates the file is read-only and modified
+ (path) is the path of the file being edited
+ - NVIM the server name |v:servername| or "NVIM"
+ ]=],
+ full_name = 'title',
+ scope = { 'global' },
+ short_desc = N_('Vim set the title of the window'),
+ type = 'bool',
+ varname = 'p_title',
+ },
+ {
+ cb = 'did_set_titlelen',
+ defaults = { if_true = 85 },
+ desc = [=[
+ Gives the percentage of 'columns' to use for the length of the window
+ title. When the title is longer, only the end of the path name is
+ shown. A '<' character before the path name is used to indicate this.
+ Using a percentage makes this adapt to the width of the window. But
+ it won't work perfectly, because the actual number of characters
+ available also depends on the font used and other things in the title
+ bar. When 'titlelen' is zero the full path is used. Otherwise,
+ values from 1 to 30000 percent can be used.
+ 'titlelen' is also used for the 'titlestring' option.
+ ]=],
+ full_name = 'titlelen',
+ scope = { 'global' },
+ short_desc = N_("of 'columns' used for window title"),
+ type = 'number',
+ varname = 'p_titlelen',
+ },
+ {
+ defaults = { if_true = '' },
+ desc = [=[
+ If not empty, this option will be used to set the window title when
+ exiting. Only if 'title' is enabled.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ full_name = 'titleold',
+ no_mkrc = true,
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('title, restored when exiting'),
+ type = 'string',
+ varname = 'p_titleold',
+ },
+ {
+ cb = 'did_set_titlestring',
+ defaults = { if_true = '' },
+ desc = [=[
+ When this option is not empty, it will be used for the title of the
+ window. This happens only when the 'title' option is on.
+
+ When this option contains printf-style '%' items, they will be
+ expanded according to the rules used for 'statusline'.
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+
+ Example: >
+ :auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p")
+ :set title titlestring=%<%F%=%l/%L-%P titlelen=70
+ < The value of 'titlelen' is used to align items in the middle or right
+ of the available space.
+ Some people prefer to have the file name first: >
+ :set titlestring=%t%(\ %M%)%(\ (%{expand(\"%:~:.:h\")})%)%(\ %a%)
+ < Note the use of "%{ }" and an expression to get the path of the file,
+ without the file name. The "%( %)" constructs are used to add a
+ separating space only when needed.
+ NOTE: Use of special characters in 'titlestring' may cause the display
+ to be garbled (e.g., when it contains a CR or NL character).
+ ]=],
+ full_name = 'titlestring',
+ modelineexpr = true,
+ scope = { 'global' },
+ short_desc = N_('to use for the Vim window title'),
+ type = 'string',
+ varname = 'p_titlestring',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ This option and 'ttimeoutlen' determine the behavior when part of a
+ key code sequence has been received by the |TUI|.
+
+ For example if <Esc> (the \x1b byte) is received and 'ttimeout' is
+ set, Nvim waits 'ttimeoutlen' milliseconds for the terminal to
+ complete a key code sequence. If no input arrives before the timeout,
+ a single <Esc> is assumed. Many TUI cursor key codes start with <Esc>.
+
+ On very slow systems this may fail, causing cursor keys not to work
+ sometimes. If you discover this problem you can ":set ttimeoutlen=9999".
+ Nvim will wait for the next character to arrive after an <Esc>.
+ ]=],
+ full_name = 'ttimeout',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('out on mappings'),
+ type = 'bool',
+ varname = 'p_ttimeout',
+ },
+ {
+ abbreviation = 'ttm',
+ defaults = { if_true = 50 },
+ desc = [=[
+ Time in milliseconds to wait for a key code sequence to complete. Also
+ used for CTRL-\ CTRL-N and CTRL-\ CTRL-G when part of a command has
+ been typed.
+ ]=],
+ full_name = 'ttimeoutlen',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('time out time for key codes in milliseconds'),
+ type = 'number',
+ varname = 'p_ttm',
+ },
+ {
+ abbreviation = 'tf',
+ defaults = { if_true = true },
+ full_name = 'ttyfast',
+ no_mkrc = true,
+ scope = { 'global' },
+ short_desc = N_('No description'),
+ type = 'bool',
+ immutable = true,
+ },
+ {
+ abbreviation = 'udir',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ List of directory names for undo files, separated with commas.
+ See 'backupdir' for details of the format.
+ "." means using the directory of the file. The undo file name for
+ "file.txt" is ".file.txt.un~".
+ For other directories the file name is the full path of the edited
+ file, with path separators replaced with "%".
+ When writing: The first directory that exists is used. "." always
+ works, no directories after "." will be used for writing. If none of
+ the directories exist Nvim will attempt to create the last directory in
+ the list.
+ When reading all entries are tried to find an undo file. The first
+ undo file that exists is used. When it cannot be read an error is
+ given, no further entry is used.
+ See |undo-persistence|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+
+ Note that unlike 'directory' and 'backupdir', 'undodir' always acts as
+ though the trailing slashes are present (see 'backupdir' for what this
+ means).
+ ]=],
+ expand = 'nodefault',
+ full_name = 'undodir',
+ list = 'onecomma',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('where to store undo files'),
+ tags = { 'E5003' },
+ type = 'string',
+ varname = 'p_udir',
+ },
+ {
+ abbreviation = 'udf',
+ cb = 'did_set_undofile',
+ defaults = { if_true = false },
+ desc = [=[
+ When on, Vim automatically saves undo history to an undo file when
+ writing a buffer to a file, and restores undo history from the same
+ file on buffer read.
+ The directory where the undo file is stored is specified by 'undodir'.
+ For more information about this feature see |undo-persistence|.
+ The undo file is not read when 'undoreload' causes the buffer from
+ before a reload to be saved for undo.
+ When 'undofile' is turned off the undo file is NOT deleted.
+ ]=],
+ full_name = 'undofile',
+ scope = { 'buffer' },
+ short_desc = N_('save undo information in a file'),
+ type = 'bool',
+ varname = 'p_udf',
+ },
+ {
+ abbreviation = 'ul',
+ cb = 'did_set_undolevels',
+ defaults = { if_true = 1000 },
+ desc = [=[
+ Maximum number of changes that can be undone. Since undo information
+ is kept in memory, higher numbers will cause more memory to be used.
+ Nevertheless, a single change can already use a large amount of memory.
+ Set to 0 for Vi compatibility: One level of undo and "u" undoes
+ itself: >
+ set ul=0
+ < But you can also get Vi compatibility by including the 'u' flag in
+ 'cpoptions', and still be able to use CTRL-R to repeat undo.
+ Also see |undo-two-ways|.
+ Set to -1 for no undo at all. You might want to do this only for the
+ current buffer: >
+ setlocal ul=-1
+ < This helps when you run out of memory for a single change.
+
+ The local value is set to -123456 when the global value is to be used.
+
+ Also see |clear-undo|.
+ ]=],
+ full_name = 'undolevels',
+ scope = { 'global', 'buffer' },
+ short_desc = N_('maximum number of changes that can be undone'),
+ type = 'number',
+ varname = 'p_ul',
+ },
+ {
+ abbreviation = 'ur',
+ defaults = { if_true = 10000 },
+ desc = [=[
+ Save the whole buffer for undo when reloading it. This applies to the
+ ":e!" command and reloading for when the buffer changed outside of
+ Vim. |FileChangedShell|
+ The save only happens when this option is negative or when the number
+ of lines is smaller than the value of this option.
+ Set this option to zero to disable undo for a reload.
+
+ When saving undo for a reload, any undo file is not read.
+
+ Note that this causes the whole buffer to be stored in memory. Set
+ this option to a lower value if you run out of memory.
+ ]=],
+ full_name = 'undoreload',
+ scope = { 'global' },
+ short_desc = N_('max nr of lines to save for undo on a buffer reload'),
+ type = 'number',
+ varname = 'p_ur',
+ },
+ {
+ abbreviation = 'uc',
+ cb = 'did_set_updatecount',
+ defaults = { if_true = 200 },
+ desc = [=[
+ After typing this many characters the swap file will be written to
+ disk. When zero, no swap file will be created at all (see chapter on
+ recovery |crash-recovery|). 'updatecount' is set to zero by starting
+ Vim with the "-n" option, see |startup|. When editing in readonly
+ mode this option will be initialized to 10000.
+ The swapfile can be disabled per buffer with |'swapfile'|.
+ When 'updatecount' is set from zero to non-zero, swap files are
+ created for all buffers that have 'swapfile' set. When 'updatecount'
+ is set to zero, existing swap files are not deleted.
+ This option has no meaning in buffers where |'buftype'| is "nofile"
+ or "nowrite".
+ ]=],
+ full_name = 'updatecount',
+ scope = { 'global' },
+ short_desc = N_('after this many characters flush swap file'),
+ type = 'number',
+ varname = 'p_uc',
+ },
+ {
+ abbreviation = 'ut',
+ defaults = { if_true = 4000 },
+ desc = [=[
+ If this many milliseconds nothing is typed the swap file will be
+ written to disk (see |crash-recovery|). Also used for the
+ |CursorHold| autocommand event.
+ ]=],
+ full_name = 'updatetime',
+ scope = { 'global' },
+ short_desc = N_('after this many milliseconds flush swap file'),
+ type = 'number',
+ varname = 'p_ut',
+ },
+ {
+ abbreviation = 'vsts',
+ cb = 'did_set_varsofttabstop',
+ defaults = { if_true = '' },
+ desc = [=[
+ A list of the number of spaces that a <Tab> counts for while editing,
+ such as inserting a <Tab> or using <BS>. It "feels" like variable-
+ width <Tab>s are being inserted, while in fact a mixture of spaces
+ and <Tab>s is used. Tab widths are separated with commas, with the
+ final value applying to all subsequent tabs.
+
+ For example, when editing assembly language files where statements
+ start in the 9th column and comments in the 41st, it may be useful
+ to use the following: >
+ :set varsofttabstop=8,32,8
+ < This will set soft tabstops with 8 and 8 + 32 spaces, and 8 more
+ for every column thereafter.
+
+ Note that the value of |'softtabstop'| will be ignored while
+ 'varsofttabstop' is set.
+ ]=],
+ full_name = 'varsofttabstop',
+ list = 'comma',
+ scope = { 'buffer' },
+ short_desc = N_('list of numbers of spaces that <Tab> uses while editing'),
+ type = 'string',
+ varname = 'p_vsts',
+ },
+ {
+ abbreviation = 'vts',
+ cb = 'did_set_vartabstop',
+ defaults = { if_true = '' },
+ desc = [=[
+ A list of the number of spaces that a <Tab> in the file counts for,
+ separated by commas. Each value corresponds to one tab, with the
+ final value applying to all subsequent tabs. For example: >
+ :set vartabstop=4,20,10,8
+ < This will make the first tab 4 spaces wide, the second 20 spaces,
+ the third 10 spaces, and all following tabs 8 spaces.
+
+ Note that the value of |'tabstop'| will be ignored while 'vartabstop'
+ is set.
+ ]=],
+ full_name = 'vartabstop',
+ list = 'comma',
+ redraw = { 'current_buffer' },
+ scope = { 'buffer' },
+ short_desc = N_('list of numbers of spaces that <Tab> in file uses'),
+ type = 'string',
+ varname = 'p_vts',
+ },
+ {
+ abbreviation = 'vbs',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Sets the verbosity level. Also set by |-V| and |:verbose|.
+
+ Tracing of options in Lua scripts is activated at level 1; Lua scripts
+ are not traced with verbose=0, for performance.
+
+ If greater than or equal to a given level, Nvim produces the following
+ messages:
+
+ Level Messages ~
+ ----------------------------------------------------------------------
+ 1 Lua assignments to options, mappings, etc.
+ 2 When a file is ":source"'ed, or |shada| file is read or written.
+ 3 UI info, terminal capabilities.
+ 4 Shell commands.
+ 5 Every searched tags file and include file.
+ 8 Files for which a group of autocommands is executed.
+ 9 Executed autocommands.
+ 11 Finding items in a path.
+ 12 Vimscript function calls.
+ 13 When an exception is thrown, caught, finished, or discarded.
+ 14 Anything pending in a ":finally" clause.
+ 15 Ex commands from a script (truncated at 200 characters).
+ 16 Ex commands.
+
+ If 'verbosefile' is set then the verbose messages are not displayed.
+ ]=],
+ full_name = 'verbose',
+ redraw = { 'ui_option' },
+ scope = { 'global' },
+ short_desc = N_('give informative messages'),
+ type = 'number',
+ varname = 'p_verbose',
+ },
+ {
+ abbreviation = 'vfile',
+ cb = 'did_set_verbosefile',
+ defaults = { if_true = '' },
+ desc = [=[
+ When not empty all messages are written in a file with this name.
+ When the file exists messages are appended.
+ Writing to the file ends when Vim exits or when 'verbosefile' is made
+ empty. Writes are buffered, thus may not show up for some time.
+ Setting 'verbosefile' to a new value is like making it empty first.
+ The difference with |:redir| is that verbose messages are not
+ displayed when 'verbosefile' is set.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = true,
+ full_name = 'verbosefile',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('file to write messages in'),
+ type = 'string',
+ varname = 'p_vfile',
+ },
+ {
+ abbreviation = 'vdir',
+ defaults = { if_true = '' },
+ desc = [=[
+ Name of the directory where to store files for |:mkview|.
+ This option cannot be set from a |modeline| or in the |sandbox|, for
+ security reasons.
+ ]=],
+ expand = 'nodefault',
+ full_name = 'viewdir',
+ scope = { 'global' },
+ secure = true,
+ short_desc = N_('directory where to store files with :mkview'),
+ type = 'string',
+ varname = 'p_vdir',
+ },
+ {
+ abbreviation = 'vop',
+ cb = 'did_set_viewoptions',
+ defaults = { if_true = 'folds,cursor,curdir' },
+ deny_duplicates = true,
+ desc = [=[
+ Changes the effect of the |:mkview| command. It is a comma-separated
+ list of words. Each word enables saving and restoring something:
+ word save and restore ~
+ cursor cursor position in file and in window
+ curdir local current directory, if set with |:lcd|
+ folds manually created folds, opened/closed folds and local
+ fold options
+ options options and mappings local to a window or buffer (not
+ global values for local options)
+ localoptions same as "options"
+ slash |deprecated| Always enabled. Uses "/" in filenames.
+ unix |deprecated| Always enabled. Uses "\n" line endings.
+ ]=],
+ expand_cb = 'expand_set_sessionoptions',
+ full_name = 'viewoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies what to save for :mkview'),
+ type = 'string',
+ varname = 'p_vop',
+ },
+ {
+ abbreviation = 'vi',
+ full_name = 'viminfo',
+ nodefault = true,
+ scope = { 'global' },
+ short_desc = N_('Alias for shada'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'vif',
+ full_name = 'viminfofile',
+ nodefault = true,
+ scope = { 'global' },
+ short_desc = N_('Alias for shadafile instead'),
+ type = 'string',
+ },
+ {
+ abbreviation = 've',
+ cb = 'did_set_virtualedit',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of these words:
+ block Allow virtual editing in Visual block mode.
+ insert Allow virtual editing in Insert mode.
+ all Allow virtual editing in all modes.
+ onemore Allow the cursor to move just past the end of the line
+ none When used as the local value, do not allow virtual
+ editing even when the global value is set. When used
+ as the global value, "none" is the same as "".
+ NONE Alternative spelling of "none".
+
+ Virtual editing means that the cursor can be positioned where there is
+ no actual character. This can be halfway into a tab or beyond the end
+ of the line. Useful for selecting a rectangle in Visual mode and
+ editing a table.
+ "onemore" is not the same, it will only allow moving the cursor just
+ after the last character of the line. This makes some commands more
+ consistent. Previously the cursor was always past the end of the line
+ if the line was empty. But it is far from Vi compatible. It may also
+ break some plugins or Vim scripts. For example because |l| can move
+ the cursor after the last character. Use with care!
+ Using the `$` command will move to the last character in the line, not
+ past it. This may actually move the cursor to the left!
+ The `g$` command will move to the end of the screen line.
+ It doesn't make sense to combine "all" with "onemore", but you will
+ not get a warning for it.
+ When combined with other words, "none" is ignored.
+ ]=],
+ expand_cb = 'expand_set_virtualedit',
+ full_name = 'virtualedit',
+ list = 'onecomma',
+ redraw = { 'curswant' },
+ scope = { 'global', 'window' },
+ short_desc = N_('when to use virtual editing'),
+ type = 'string',
+ varname = 'p_ve',
+ },
+ {
+ abbreviation = 'vb',
+ defaults = { if_true = false },
+ desc = [=[
+ Use visual bell instead of beeping. Also see 'errorbells'.
+ ]=],
+ full_name = 'visualbell',
+ scope = { 'global' },
+ short_desc = N_('use visual bell instead of beeping'),
+ type = 'bool',
+ varname = 'p_vb',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Give a warning message when a shell command is used while the buffer
+ has been changed.
+ ]=],
+ full_name = 'warn',
+ scope = { 'global' },
+ short_desc = N_('for shell command when buffer was changed'),
+ type = 'bool',
+ varname = 'p_warn',
+ },
+ {
+ abbreviation = 'ww',
+ cb = 'did_set_whichwrap',
+ defaults = { if_true = 'b,s' },
+ desc = [=[
+ Allow specified keys that move the cursor left/right to move to the
+ previous/next line when the cursor is on the first/last character in
+ the line. Concatenate characters to allow this for these keys:
+ char key mode ~
+ b <BS> Normal and Visual
+ s <Space> Normal and Visual
+ h "h" Normal and Visual (not recommended)
+ l "l" Normal and Visual (not recommended)
+ < <Left> Normal and Visual
+ > <Right> Normal and Visual
+ ~ "~" Normal
+ [ <Left> Insert and Replace
+ ] <Right> Insert and Replace
+ For example: >
+ :set ww=<,>,[,]
+ < allows wrap only when cursor keys are used.
+ When the movement keys are used in combination with a delete or change
+ operator, the <EOL> also counts for a character. This makes "3h"
+ different from "3dh" when the cursor crosses the end of a line. This
+ is also true for "x" and "X", because they do the same as "dl" and
+ "dh". If you use this, you may also want to use the mapping
+ ":map <BS> X" to make backspace delete the character in front of the
+ cursor.
+ When 'l' is included and it is used after an operator at the end of a
+ line (not an empty line) then it will not move to the next line. This
+ makes "dl", "cl", "yl" etc. work normally.
+ ]=],
+ expand_cb = 'expand_set_whichwrap',
+ full_name = 'whichwrap',
+ list = 'flagscomma',
+ scope = { 'global' },
+ short_desc = N_('allow specified keys to cross line boundaries'),
+ type = 'string',
+ varname = 'p_ww',
+ },
+ {
+ abbreviation = 'wc',
+ cb = 'did_set_wildchar',
+ defaults = {
+ if_true = imacros('TAB'),
+ doc = '<Tab>',
+ },
+ desc = [=[
+ Character you have to type to start wildcard expansion in the
+ command-line, as specified with 'wildmode'.
+ More info here: |cmdline-completion|.
+ The character is not recognized when used inside a macro. See
+ 'wildcharm' for that.
+ Some keys will not work, such as CTRL-C, <CR> and Enter.
+ <Esc> can be used, but hitting it twice in a row will still exit
+ command-line as a failsafe measure.
+ Although 'wc' is a number option, you can set it to a special key: >
+ :set wc=<Tab>
+ <
+ ]=],
+ full_name = 'wildchar',
+ scope = { 'global' },
+ short_desc = N_('command-line character for wildcard expansion'),
+ type = 'number',
+ varname = 'p_wc',
+ },
+ {
+ abbreviation = 'wcm',
+ cb = 'did_set_wildchar',
+ defaults = { if_true = 0 },
+ desc = [=[
+ 'wildcharm' works exactly like 'wildchar', except that it is
+ recognized when used inside a macro. You can find "spare" command-line
+ keys suitable for this option by looking at |ex-edit-index|. Normally
+ you'll never actually type 'wildcharm', just use it in mappings that
+ automatically invoke completion mode, e.g.: >
+ :set wcm=<C-Z>
+ :cnoremap ss so $vim/sessions/*.vim<C-Z>
+ < Then after typing :ss you can use CTRL-P & CTRL-N.
+ ]=],
+ full_name = 'wildcharm',
+ scope = { 'global' },
+ short_desc = N_("like 'wildchar' but also works when mapped"),
+ type = 'number',
+ varname = 'p_wcm',
+ },
+ {
+ abbreviation = 'wig',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of file patterns. A file that matches with one of these
+ patterns is ignored when expanding |wildcards|, completing file or
+ directory names, and influences the result of |expand()|, |glob()| and
+ |globpath()| unless a flag is passed to disable this.
+ The pattern is used like with |:autocmd|, see |autocmd-pattern|.
+ Also see 'suffixes'.
+ Example: >
+ :set wildignore=*.o,*.obj
+ < The use of |:set+=| and |:set-=| is preferred when adding or removing
+ a pattern from the list. This avoids problems when a future version
+ uses another default.
+ ]=],
+ full_name = 'wildignore',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('files matching these patterns are not completed'),
+ type = 'string',
+ varname = 'p_wig',
+ },
+ {
+ abbreviation = 'wic',
+ defaults = { if_true = false },
+ desc = [=[
+ When set case is ignored when completing file names and directories.
+ Has no effect when 'fileignorecase' is set.
+ Does not apply when the shell is used to expand wildcards, which
+ happens when there are special characters.
+ ]=],
+ full_name = 'wildignorecase',
+ scope = { 'global' },
+ short_desc = N_('ignore case when completing file names'),
+ type = 'bool',
+ varname = 'p_wic',
+ },
+ {
+ abbreviation = 'wmnu',
+ defaults = { if_true = true },
+ desc = [=[
+ When 'wildmenu' is on, command-line completion operates in an enhanced
+ mode. On pressing 'wildchar' (usually <Tab>) to invoke completion,
+ the possible matches are shown.
+ When 'wildoptions' contains "pum", then the completion matches are
+ shown in a popup menu. Otherwise they are displayed just above the
+ command line, with the first match highlighted (overwriting the status
+ line, if there is one).
+ Keys that show the previous/next match, such as <Tab> or
+ CTRL-P/CTRL-N, cause the highlight to move to the appropriate match.
+ 'wildmode' must specify "full": "longest" and "list" do not start
+ 'wildmenu' mode. You can check the current mode with |wildmenumode()|.
+ The menu is cancelled when a key is hit that is not used for selecting
+ a completion.
+
+ While the menu is active these keys have special meanings:
+ CTRL-P - go to the previous entry
+ CTRL-N - go to the next entry
+ <Left> <Right> - select previous/next match (like CTRL-P/CTRL-N)
+ <PageUp> - select a match several entries back
+ <PageDown> - select a match several entries further
+ <Up> - in filename/menu name completion: move up into
+ parent directory or parent menu.
+ <Down> - in filename/menu name completion: move into a
+ subdirectory or submenu.
+ <CR> - in menu completion, when the cursor is just after a
+ dot: move into a submenu.
+ CTRL-E - end completion, go back to what was there before
+ selecting a match.
+ CTRL-Y - accept the currently selected match and stop
+ completion.
+
+ If you want <Left> and <Right> to move the cursor instead of selecting
+ a different match, use this: >
+ :cnoremap <Left> <Space><BS><Left>
+ :cnoremap <Right> <Space><BS><Right>
+ <
+ |hl-WildMenu| highlights the current match.
+ ]=],
+ full_name = 'wildmenu',
+ scope = { 'global' },
+ short_desc = N_('use menu for command line completion'),
+ type = 'bool',
+ varname = 'p_wmnu',
+ },
+ {
+ abbreviation = 'wim',
+ cb = 'did_set_wildmode',
+ defaults = { if_true = 'full' },
+ deny_duplicates = false,
+ desc = [=[
+ Completion mode that is used for the character specified with
+ 'wildchar'. It is a comma-separated list of up to four parts. Each
+ part specifies what to do for each consecutive use of 'wildchar'. The
+ first part specifies the behavior for the first use of 'wildchar',
+ The second part for the second use, etc.
+
+ Each part consists of a colon separated list consisting of the
+ following possible values:
+ "" Complete only the first match.
+ "full" Complete the next full match. After the last match,
+ the original string is used and then the first match
+ again. Will also start 'wildmenu' if it is enabled.
+ "longest" Complete till longest common string. If this doesn't
+ result in a longer string, use the next part.
+ "list" When more than one match, list all matches.
+ "lastused" When completing buffer names and more than one buffer
+ matches, sort buffers by time last used (other than
+ the current buffer).
+ When there is only a single match, it is fully completed in all cases.
+
+ Examples of useful colon-separated values:
+ "longest:full" Like "longest", but also start 'wildmenu' if it is
+ enabled. Will not complete to the next full match.
+ "list:full" When more than one match, list all matches and
+ complete first match.
+ "list:longest" When more than one match, list all matches and
+ complete till longest common string.
+ "list:lastused" When more than one buffer matches, list all matches
+ and sort buffers by time last used (other than the
+ current buffer).
+
+ Examples: >
+ :set wildmode=full
+ < Complete first full match, next match, etc. (the default) >
+ :set wildmode=longest,full
+ < Complete longest common string, then each full match >
+ :set wildmode=list:full
+ < List all matches and complete each full match >
+ :set wildmode=list,full
+ < List all matches without completing, then each full match >
+ :set wildmode=longest,list
+ < Complete longest common string, then list alternatives.
+ More info here: |cmdline-completion|.
+ ]=],
+ expand_cb = 'expand_set_wildmode',
+ full_name = 'wildmode',
+ list = 'onecommacolon',
+ scope = { 'global' },
+ short_desc = N_("mode for 'wildchar' command-line expansion"),
+ type = 'string',
+ varname = 'p_wim',
+ },
+ {
+ abbreviation = 'wop',
+ cb = 'did_set_wildoptions',
+ defaults = { if_true = 'pum,tagfile' },
+ deny_duplicates = true,
+ desc = [=[
+ A list of words that change how |cmdline-completion| is done.
+ The following values are supported:
+ fuzzy Use |fuzzy-matching| to find completion matches. When
+ this value is specified, wildcard expansion will not
+ be used for completion. The matches will be sorted by
+ the "best match" rather than alphabetically sorted.
+ This will find more matches than the wildcard
+ expansion. Currently fuzzy matching based completion
+ is not supported for file and directory names and
+ instead wildcard expansion is used.
+ pum Display the completion matches using the popup menu
+ in the same style as the |ins-completion-menu|.
+ tagfile When using CTRL-D to list matching tags, the kind of
+ tag and the file of the tag is listed. Only one match
+ is displayed per line. Often used tag kinds are:
+ d #define
+ f function
+ ]=],
+ expand_cb = 'expand_set_wildoptions',
+ full_name = 'wildoptions',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('specifies how command line completion is done'),
+ type = 'string',
+ varname = 'p_wop',
+ },
+ {
+ abbreviation = 'wak',
+ cb = 'did_set_winaltkeys',
+ defaults = { if_true = 'menu' },
+ desc = [=[
+ only used in Win32
+ Some GUI versions allow the access to menu entries by using the ALT
+ key in combination with a character that appears underlined in the
+ menu. This conflicts with the use of the ALT key for mappings and
+ entering special characters. This option tells what to do:
+ no Don't use ALT keys for menus. ALT key combinations can be
+ mapped, but there is no automatic handling.
+ yes ALT key handling is done by the windowing system. ALT key
+ combinations cannot be mapped.
+ menu Using ALT in combination with a character that is a menu
+ shortcut key, will be handled by the windowing system. Other
+ keys can be mapped.
+ If the menu is disabled by excluding 'm' from 'guioptions', the ALT
+ key is never used for the menu.
+ This option is not used for <F10>; on Win32.
+ ]=],
+ expand_cb = 'expand_set_winaltkeys',
+ full_name = 'winaltkeys',
+ scope = { 'global' },
+ short_desc = N_('when the windows system handles ALT keys'),
+ type = 'string',
+ varname = 'p_wak',
+ },
+ {
+ abbreviation = 'wbr',
+ alloced = true,
+ cb = 'did_set_winbar',
+ defaults = { if_true = '' },
+ desc = [=[
+ When non-empty, this option enables the window bar and determines its
+ contents. The window bar is a bar that's shown at the top of every
+ window with it enabled. The value of 'winbar' is evaluated like with
+ 'statusline'.
+
+ When changing something that is used in 'winbar' that does not trigger
+ it to be updated, use |:redrawstatus|.
+
+ Floating windows do not use the global value of 'winbar'. The
+ window-local value of 'winbar' must be set for a floating window to
+ have a window bar.
+
+ This option cannot be set in a modeline when 'modelineexpr' is off.
+ ]=],
+ full_name = 'winbar',
+ modelineexpr = true,
+ redraw = { 'statuslines' },
+ scope = { 'global', 'window' },
+ short_desc = N_('custom format for the window bar'),
+ type = 'string',
+ varname = 'p_wbr',
+ },
+ {
+ abbreviation = 'winbl',
+ cb = 'did_set_winblend',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Enables pseudo-transparency for a floating window. Valid values are in
+ the range of 0 for fully opaque window (disabled) to 100 for fully
+ transparent background. Values between 0-30 are typically most useful.
+
+ UI-dependent. Works best with RGB colors. 'termguicolors'
+ ]=],
+ full_name = 'winblend',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('Controls transparency level for floating windows'),
+ type = 'number',
+ },
+ {
+ abbreviation = 'winhl',
+ alloced = true,
+ cb = 'did_set_winhighlight',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ Window-local highlights. Comma-delimited list of highlight
+ |group-name| pairs "{hl-from}:{hl-to},..." where each {hl-from} is
+ a |highlight-groups| item to be overridden by {hl-to} group in
+ the window.
+
+ Note: highlight namespaces take precedence over 'winhighlight'.
+ See |nvim_win_set_hl_ns()| and |nvim_set_hl()|.
+
+ Highlights of vertical separators are determined by the window to the
+ left of the separator. The 'tabline' highlight of a tabpage is
+ decided by the last-focused window of the tabpage. Highlights of
+ the popupmenu are determined by the current window. Highlights in the
+ message area cannot be overridden.
+
+ Example: show a different color for non-current windows: >
+ set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
+ <
+ ]=],
+ expand_cb = 'expand_set_winhighlight',
+ full_name = 'winhighlight',
+ list = 'onecommacolon',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('Setup window-local highlights'),
+ type = 'string',
+ },
+ {
+ abbreviation = 'wi',
+ cb = 'did_set_window',
+ defaults = {
+ if_true = 0,
+ doc = 'screen height - 1',
+ },
+ desc = [=[
+ Window height used for |CTRL-F| and |CTRL-B| when there is only one
+ window and the value is smaller than 'lines' minus one. The screen
+ will scroll 'window' minus two lines, with a minimum of one.
+ When 'window' is equal to 'lines' minus one CTRL-F and CTRL-B scroll
+ in a much smarter way, taking care of wrapping lines.
+ When resizing the Vim window, the value is smaller than 1 or more than
+ or equal to 'lines' it will be set to 'lines' minus 1.
+ Note: Do not confuse this with the height of the Vim window, use
+ 'lines' for that.
+ ]=],
+ full_name = 'window',
+ scope = { 'global' },
+ short_desc = N_('nr of lines to scroll for CTRL-F and CTRL-B'),
+ type = 'number',
+ varname = 'p_window',
+ },
+ {
+ abbreviation = 'wh',
+ cb = 'did_set_winheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ Minimal number of lines for the current window. This is not a hard
+ minimum, Vim will use fewer lines if there is not enough room. If the
+ focus goes to a window that is smaller, its size is increased, at the
+ cost of the height of other windows.
+ Set 'winheight' to a small number for normal editing.
+ Set it to 999 to make the current window fill most of the screen.
+ Other windows will be only 'winminheight' high. This has the drawback
+ that ":all" will create only two windows. To avoid "vim -o 1 2 3 4"
+ to create only two windows, set the option after startup is done,
+ using the |VimEnter| event: >
+ au VimEnter * set winheight=999
+ < Minimum value is 1.
+ The height is not adjusted after one of the commands that change the
+ height of the current window.
+ 'winheight' applies to the current window. Use 'winminheight' to set
+ the minimal height for other windows.
+ ]=],
+ full_name = 'winheight',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines for the current window'),
+ tags = { 'E591' },
+ type = 'number',
+ varname = 'p_wh',
+ },
+ {
+ abbreviation = 'wfh',
+ defaults = { if_true = false },
+ desc = [=[
+ Keep the window height when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|. Set by default for the
+ |preview-window| and |quickfix-window|.
+ The height may be changed anyway when running out of room.
+ ]=],
+ full_name = 'winfixheight',
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('keep window height when opening/closing windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wfw',
+ defaults = { if_true = false },
+ desc = [=[
+ Keep the window width when windows are opened or closed and
+ 'equalalways' is set. Also for |CTRL-W_=|.
+ The width may be changed anyway when running out of room.
+ ]=],
+ full_name = 'winfixwidth',
+ redraw = { 'statuslines' },
+ scope = { 'window' },
+ short_desc = N_('keep window width when opening/closing windows'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wmh',
+ cb = 'did_set_winminheight',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal height of a window, when it's not the current window.
+ This is a hard minimum, windows will never become smaller.
+ When set to zero, windows may be "squashed" to zero lines (i.e. just a
+ status bar) if necessary. They will return to at least one line when
+ they become active (since the cursor has to have somewhere to go.)
+ Use 'winheight' to set the minimal height of the current window.
+ This option is only checked when making a window smaller. Don't use a
+ large number, it will cause errors when opening more than a few
+ windows. A value of 0 to 3 is reasonable.
+ ]=],
+ full_name = 'winminheight',
+ scope = { 'global' },
+ short_desc = N_('minimum number of lines for any window'),
+ type = 'number',
+ varname = 'p_wmh',
+ },
+ {
+ abbreviation = 'wmw',
+ cb = 'did_set_winminwidth',
+ defaults = { if_true = 1 },
+ desc = [=[
+ The minimal width of a window, when it's not the current window.
+ This is a hard minimum, windows will never become smaller.
+ When set to zero, windows may be "squashed" to zero columns (i.e. just
+ a vertical separator) if necessary. They will return to at least one
+ line when they become active (since the cursor has to have somewhere
+ to go.)
+ Use 'winwidth' to set the minimal width of the current window.
+ This option is only checked when making a window smaller. Don't use a
+ large number, it will cause errors when opening more than a few
+ windows. A value of 0 to 12 is reasonable.
+ ]=],
+ full_name = 'winminwidth',
+ scope = { 'global' },
+ short_desc = N_('minimal number of columns for any window'),
+ type = 'number',
+ varname = 'p_wmw',
+ },
+ {
+ abbreviation = 'wiw',
+ cb = 'did_set_winwidth',
+ defaults = { if_true = 20 },
+ desc = [=[
+ Minimal number of columns for the current window. This is not a hard
+ minimum, Vim will use fewer columns if there is not enough room. If
+ the current window is smaller, its size is increased, at the cost of
+ the width of other windows. Set it to 999 to make the current window
+ always fill the screen. Set it to a small number for normal editing.
+ The width is not adjusted after one of the commands to change the
+ width of the current window.
+ 'winwidth' applies to the current window. Use 'winminwidth' to set
+ the minimal width for other windows.
+ ]=],
+ full_name = 'winwidth',
+ scope = { 'global' },
+ short_desc = N_('minimal number of columns for current window'),
+ tags = { 'E592' },
+ type = 'number',
+ varname = 'p_wiw',
+ },
+ {
+ cb = 'did_set_wrap',
+ defaults = { if_true = true },
+ desc = [=[
+ This option changes how text is displayed. It doesn't change the text
+ in the buffer, see 'textwidth' for that.
+ When on, lines longer than the width of the window will wrap and
+ displaying continues on the next line. When off lines will not wrap
+ and only part of long lines will be displayed. When the cursor is
+ moved to a part that is not shown, the screen will scroll
+ horizontally.
+ The line will be broken in the middle of a word if necessary. See
+ 'linebreak' to get the break at a word boundary.
+ To make scrolling horizontally a bit more useful, try this: >
+ :set sidescroll=5
+ :set listchars+=precedes:<,extends:>
+ < See 'sidescroll', 'listchars' and |wrap-off|.
+ This option can't be set from a |modeline| when the 'diff' option is
+ on.
+ ]=],
+ full_name = 'wrap',
+ redraw = { 'current_window' },
+ scope = { 'window' },
+ short_desc = N_('lines wrap and continue on the next line'),
+ type = 'bool',
+ },
+ {
+ abbreviation = 'wm',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Number of characters from the right window border where wrapping
+ starts. When typing text beyond this limit, an <EOL> will be inserted
+ and inserting continues on the next line.
+ Options that add a margin, such as 'number' and 'foldcolumn', cause
+ the text width to be further reduced.
+ When 'textwidth' is non-zero, this option is not used.
+ See also 'formatoptions' and |ins-textwidth|.
+ ]=],
+ full_name = 'wrapmargin',
+ scope = { 'buffer' },
+ short_desc = N_('chars from the right where wrapping starts'),
+ type = 'number',
+ varname = 'p_wm',
+ },
+ {
+ abbreviation = 'ws',
+ defaults = { if_true = true },
+ desc = [=[
+ Searches wrap around the end of the file. Also applies to |]s| and
+ |[s|, searching for spelling mistakes.
+ ]=],
+ full_name = 'wrapscan',
+ scope = { 'global' },
+ short_desc = N_('searches wrap around the end of the file'),
+ tags = { 'E384', 'E385' },
+ type = 'bool',
+ varname = 'p_ws',
+ },
+ {
+ defaults = { if_true = true },
+ desc = [=[
+ Allows writing files. When not set, writing a file is not allowed.
+ Can be used for a view-only mode, where modifications to the text are
+ still allowed. Can be reset with the |-m| or |-M| command line
+ argument. Filtering text is still possible, even though this requires
+ writing a temporary file.
+ ]=],
+ full_name = 'write',
+ scope = { 'global' },
+ short_desc = N_('to a file is allowed'),
+ type = 'bool',
+ varname = 'p_write',
+ },
+ {
+ abbreviation = 'wa',
+ defaults = { if_true = false },
+ desc = [=[
+ Allows writing to any file with no need for "!" override.
+ ]=],
+ full_name = 'writeany',
+ scope = { 'global' },
+ short_desc = N_('write to file with no need for "!" override'),
+ type = 'bool',
+ varname = 'p_wa',
+ },
+ {
+ abbreviation = 'wb',
+ defaults = { if_true = true },
+ desc = [=[
+ Make a backup before overwriting a file. The backup is removed after
+ the file was successfully written, unless the 'backup' option is
+ also on.
+ WARNING: Switching this option off means that when Vim fails to write
+ your buffer correctly and then, for whatever reason, Vim exits, you
+ lose both the original file and what you were writing. Only reset
+ this option if your file system is almost full and it makes the write
+ fail (and make sure not to exit Vim until the write was successful).
+ See |backup-table| for another explanation.
+ When the 'backupskip' pattern matches, a backup is not made anyway.
+ Depending on 'backupcopy' the backup is a new file or the original
+ file renamed (and a new file is written).
+ ]=],
+ full_name = 'writebackup',
+ scope = { 'global' },
+ short_desc = N_('make a backup before overwriting a file'),
+ type = 'bool',
+ varname = 'p_wb',
+ },
+ {
+ abbreviation = 'wd',
+ defaults = { if_true = 0 },
+ desc = [=[
+ Only takes effect together with 'redrawdebug'.
+ The number of milliseconds to wait after each line or each flush
+ ]=],
+ full_name = 'writedelay',
+ scope = { 'global' },
+ short_desc = N_('delay this many msec for each char (for debug)'),
+ type = 'number',
+ varname = 'p_wd',
+ },
+ },
}
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index ca50c3ab00..281ec86171 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -1,71 +1,65 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
-#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
#include "nvim/ex_getln.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-#include "nvim/mapping.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/mouse.h"
#include "nvim/move.h"
-#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
-#include "nvim/pos.h"
-#include "nvim/quickfix.h"
-#include "nvim/runtime.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
+#include "nvim/regexp.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
-#include "nvim/tag.h"
-#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "optionstr.c.generated.h"
#endif
-static char e_unclosed_expression_sequence[]
+static const char e_unclosed_expression_sequence[]
= N_("E540: Unclosed expression sequence");
-static char e_unbalanced_groups[]
- = N_("E542: unbalanced groups");
-static char e_backupext_and_patchmode_are_equal[]
+static const char e_comma_required[]
+ = N_("E536: Comma required");
+static const char e_unbalanced_groups[]
+ = N_("E542: Unbalanced groups");
+static const char e_backupext_and_patchmode_are_equal[]
= N_("E589: 'backupext' and 'patchmode' are equal");
-static char e_showbreak_contains_unprintable_or_wide_character[]
+static const char e_showbreak_contains_unprintable_or_wide_character[]
= N_("E595: 'showbreak' contains unprintable or wide character");
static char *(p_ambw_values[]) = { "single", "double", NULL };
@@ -74,12 +68,25 @@ static char *(p_bkc_values[]) = { "yes", "auto", "no", "breaksymlink", "breakhar
static char *(p_bo_values[]) = { "all", "backspace", "cursor", "complete", "copy", "ctrlg", "error",
"esc", "ex", "hangul", "lang", "mess", "showmatch", "operator",
"register", "shell", "spell", "wildmode", NULL };
+// Note: Keep this in sync with briopt_check()
+static char *(p_briopt_values[]) = { "shift:", "min:", "sbr", "list:", "column:", NULL };
+// Note: Keep this in sync with diffopt_changed()
+static char *(p_dip_values[]) = { "filler", "context:", "iblank", "icase",
+ "iwhite", "iwhiteall", "iwhiteeol", "horizontal", "vertical",
+ "closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal",
+ "indent-heuristic", "linematch:", "algorithm:", NULL };
+static char *(p_dip_algorithm_values[]) = { "myers", "minimal", "patience", "histogram", NULL };
static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL };
static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL };
+static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
static char *(p_cmp_values[]) = { "internal", "keepascii", NULL };
+// Note: Keep this in sync with fill_culopt_flags()
+static char *(p_culopt_values[]) = { "line", "screenline", "number", "both", NULL };
static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", NULL };
static char *(p_fdo_values[]) = { "all", "block", "hor", "mark", "percent", "quickfix", "search",
"tag", "insert", "undo", "jump", NULL };
+// Note: Keep this in sync with spell_check_sps()
+static char *(p_sps_values[]) = { "best", "fast", "double", "expr:", "file:", "timeout:", NULL };
/// Also used for 'viewoptions'! Keep in sync with SSOP_ flags.
static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "localoptions",
"options", "help", "blank", "globals", "slash", "unix", "sesdir",
@@ -91,7 +98,9 @@ static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vspli
static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL };
static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL };
static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
-static char *(p_wop_values[]) = { "tagfile", "pum", "fuzzy", NULL };
+// Note: Keep this in sync with check_opt_wim()
+static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL };
+static char *(p_wop_values[]) = { "fuzzy", "tagfile", "pum", NULL };
static char *(p_wak_values[]) = { "yes", "menu", "no", NULL };
static char *(p_mousem_values[]) = { "extend", "popup", "popup_setpos", "mac", NULL };
static char *(p_sel_values[]) = { "inclusive", "exclusive", "old", NULL };
@@ -120,20 +129,21 @@ static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2", "auto
static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4", "auto:5",
"auto:6", "auto:7", "auto:8", "auto:9", "0", "1", "2", "3", "4",
"5", "6", "7", "8", "9", NULL };
-static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL };
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_jop_values[]) = { "stack", "view", NULL };
static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL };
-static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", NULL };
+static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line",
+ "flush", NULL };
static char *(p_sloc_values[]) = { "last", "statusline", "tabline", NULL };
/// All possible flags for 'shm'.
-static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_FILE, SHM_LAST, SHM_TEXT, SHM_LINES, SHM_NEW,
+/// the literal chars before 0 are removed flags. these are safely ignored
+static char SHM_ALL[] = { SHM_RO, SHM_MOD, SHM_LINES,
SHM_WRI, SHM_ABBREVIATIONS, SHM_WRITE, SHM_TRUNC, SHM_TRUNCALL,
SHM_OVER, SHM_OVERALL, SHM_SEARCH, SHM_ATTENTION, SHM_INTRO,
SHM_COMPLETIONMENU, SHM_COMPLETIONSCAN, SHM_RECORDING, SHM_FILEINFO,
- SHM_SEARCHCOUNT, 0, };
+ SHM_SEARCHCOUNT, 'n', 'f', 'x', 'i', 0, };
/// After setting various option values: recompute variables that depend on
/// option values.
@@ -146,61 +156,17 @@ void didset_string_options(void)
(void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true);
(void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true);
(void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true);
+ (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true);
(void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false);
(void)opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true);
(void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
(void)opt_strings_flags(p_swb, p_swb_values, &swb_flags, true);
(void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
- (void)opt_strings_flags(p_jop, p_jop_values, &jop_flags, true);
(void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
}
-/// Trigger the OptionSet autocommand.
-/// "opt_idx" is the index of the option being set.
-/// "opt_flags" can be OPT_LOCAL etc.
-/// "oldval" the old value
-/// "oldval_l" the old local value (only non-NULL if global and local value are set)
-/// "oldval_g" the old global value (only non-NULL if global and local value are set)
-/// "newval" the new value
-void trigger_optionset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l,
- char *oldval_g, char *newval)
-{
- // Don't do this recursively.
- if (oldval == NULL || newval == NULL
- || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
- return;
- }
-
- char buf_type[7];
-
- vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",
- (opt_flags & OPT_LOCAL) ? "local" : "global");
- set_vim_var_string(VV_OPTION_OLD, oldval, -1);
- set_vim_var_string(VV_OPTION_NEW, newval, -1);
- set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
- if (opt_flags & OPT_LOCAL) {
- set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1);
- }
- if (opt_flags & OPT_GLOBAL) {
- set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1);
- }
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- set_vim_var_string(VV_OPTION_COMMAND, "set", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1);
- set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1);
- }
- if (opt_flags & OPT_MODELINE) {
- set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
- set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1);
- }
- apply_autocmds(EVENT_OPTIONSET, get_option(opt_idx)->fullname, NULL, false, NULL);
- reset_v_option_vars();
-}
-
-static char *illegal_char(char *errbuf, size_t errbuflen, int c)
+char *illegal_char(char *errbuf, size_t errbuflen, int c)
{
if (errbuf == NULL) {
return "";
@@ -270,29 +236,28 @@ void check_buf_options(buf_T *buf)
}
/// Free the string allocated for an option.
-/// Checks for the string being empty_option. This may happen if we're out of
-/// memory, xstrdup() returned NULL, which was replaced by empty_option by
-/// check_options().
+/// Checks for the string being empty_string_option. This may happen if we're out of memory,
+/// xstrdup() returned NULL, which was replaced by empty_string_option by check_options().
/// Does NOT check for P_ALLOCED flag!
void free_string_option(char *p)
{
- if (p != empty_option) {
+ if (p != empty_string_option) {
xfree(p);
}
}
void clear_string_option(char **pp)
{
- if (*pp != empty_option) {
+ if (*pp != empty_string_option) {
xfree(*pp);
}
- *pp = empty_option;
+ *pp = empty_string_option;
}
void check_string_option(char **pp)
{
if (*pp == NULL) {
- *pp = empty_option;
+ *pp = empty_string_option;
}
}
@@ -324,12 +289,12 @@ static void set_string_option_global(vimoption_T *opt, char **varp)
/// "set_sid" is SID_NONE don't set the scriptID. Otherwise set the scriptID to
/// "set_sid".
///
-/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL
+/// @param opt_flags OPT_FREE, OPT_LOCAL and/or OPT_GLOBAL.
+///
+/// TODO(famiu): Remove this and its win/buf variants.
void set_string_option_direct(const char *name, int opt_idx, const char *val, int opt_flags,
int set_sid)
{
- char *s;
- char **varp;
int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
int idx = opt_idx;
@@ -348,11 +313,11 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in
return;
}
- assert((void *)opt->var != (void *)&p_shada);
+ assert(opt->var != &p_shada);
- s = xstrdup(val);
+ char *s = xstrdup(val);
{
- varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
+ char **varp = (char **)get_varp_scope(opt, both ? OPT_LOCAL : opt_flags);
if ((opt_flags & OPT_FREE) && (opt->flags & P_ALLOCED)) {
free_string_option(*varp);
}
@@ -369,7 +334,7 @@ void set_string_option_direct(const char *name, int opt_idx, const char *val, in
// make the local value empty, so that the global value is used.
if ((opt->indir & PV_BOTH) && both) {
free_string_option(*varp);
- *varp = empty_option;
+ *varp = empty_string_option;
}
if (set_sid != SID_NONE) {
sctx_T script_ctx;
@@ -402,69 +367,18 @@ void set_string_option_direct_in_win(win_T *wp, const char *name, int opt_idx, c
unblock_autocmds();
}
-/// Set a string option to a new value, handling the effects
-///
-/// @param[in] opt_idx Option to set.
-/// @param[in] value New value.
-/// @param[in] opt_flags Option flags: expected to contain #OPT_LOCAL and/or
-/// #OPT_GLOBAL.
-///
-/// @return NULL on success, an untranslated error message on error.
-char *set_string_option(const int opt_idx, const char *const value, const int opt_flags)
- FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_WARN_UNUSED_RESULT
+/// Like set_string_option_direct(), but for a buffer-local option in "buf".
+/// Blocks autocommands to avoid the old curwin becoming invalid.
+void set_string_option_direct_in_buf(buf_T *buf, const char *name, int opt_idx, const char *val,
+ int opt_flags, int set_sid)
{
- vimoption_T *opt = get_option(opt_idx);
-
- if (opt->var == NULL) { // don't set hidden option
- return NULL;
- }
-
- char *const s = xstrdup(value);
- char **const varp
- = (char **)get_varp_scope(opt, ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- ? ((opt->indir & PV_BOTH) ? OPT_GLOBAL : OPT_LOCAL)
- : opt_flags));
- char *const oldval = *varp;
- char *oldval_l = NULL;
- char *oldval_g = NULL;
-
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
- oldval_l = *(char **)get_varp_scope(opt, OPT_LOCAL);
- oldval_g = *(char **)get_varp_scope(opt, OPT_GLOBAL);
- }
+ buf_T *save_curbuf = curbuf;
- *varp = s;
-
- char *const saved_oldval = xstrdup(oldval);
- 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 errmsg = did_set_string_option(opt_idx, varp, oldval,
- NULL, 0,
- opt_flags, &value_checked);
- if (errmsg == NULL) {
- did_set_option(opt_idx, opt_flags, true, value_checked);
- }
-
- // call autocommand after handling side effects
- if (errmsg == NULL) {
- if (!starting) {
- trigger_optionset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g,
- saved_newval);
- }
- if (opt->flags & P_UI_OPTION) {
- ui_call_option_set(cstr_as_string(opt->fullname),
- STRING_OBJ(cstr_as_string(saved_newval)));
- }
- }
- xfree(saved_oldval);
- xfree(saved_oldval_l);
- xfree(saved_oldval_g);
- xfree(saved_newval);
-
- return errmsg;
+ block_autocmds();
+ curbuf = buf;
+ set_string_option_direct(name, opt_idx, val, opt_flags, set_sid);
+ curbuf = save_curbuf;
+ unblock_autocmds();
}
/// Return true if "val" is a valid 'filetype' name.
@@ -475,102 +389,58 @@ static bool valid_filetype(const char *val)
return valid_name(val, ".-_");
}
-/// Handle setting 'mousescroll'.
-/// @return error message, NULL if it's OK.
-static char *check_mousescroll(char *string)
-{
- long vertical = -1;
- long horizontal = -1;
-
- for (;;) {
- char *end = vim_strchr(string, ',');
- size_t length = end ? (size_t)(end - string) : strlen(string);
-
- // Both "ver:" and "hor:" are 4 bytes long.
- // They should be followed by at least one digit.
- if (length <= 4) {
- return e_invarg;
- }
-
- long *direction;
-
- if (memcmp(string, "ver:", 4) == 0) {
- direction = &vertical;
- } else if (memcmp(string, "hor:", 4) == 0) {
- direction = &horizontal;
- } else {
- return e_invarg;
- }
-
- // If the direction has already been set, this is a duplicate.
- if (*direction != -1) {
- return e_invarg;
- }
-
- // Verify that only digits follow the colon.
- for (size_t i = 4; i < length; i++) {
- if (!ascii_isdigit(string[i])) {
- return N_("E548: digit expected");
- }
- }
-
- string += 4;
- *direction = getdigits_int(&string, false, -1);
-
- // Num options are generally kept within the signed int range.
- // We know this number won't be negative because we've already checked for
- // a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
- if (*direction == -1) {
- return e_invarg;
- }
-
- if (!end) {
- break;
- }
-
- string = end + 1;
- }
-
- // If a direction wasn't set, fallback to the default value.
- p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
- p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
-
- return NULL;
-}
-
-/// Handle setting 'signcolumn' for value 'val'
+/// Handle setting 'signcolumn' for value 'val'. Store minimum and maximum width.
///
/// @return OK when the value is valid, FAIL otherwise
-static int check_signcolumn(char *val)
+int check_signcolumn(win_T *wp)
{
+ char *val = wp->w_p_scl;
if (*val == NUL) {
return FAIL;
}
- // check for basic match
+
if (check_opt_strings(val, p_scl_values, false) == OK) {
+ if (!strncmp(val, "no", 2)) { // no
+ wp->w_minscwidth = wp->w_maxscwidth = SCL_NO;
+ } else if (!strncmp(val, "nu", 2) && (wp->w_p_nu || wp->w_p_rnu)) { // number
+ wp->w_minscwidth = wp->w_maxscwidth = SCL_NUM;
+ } else if (!strncmp(val, "yes:", 4)) { // yes:<NUM>
+ wp->w_minscwidth = wp->w_maxscwidth = val[4] - '0';
+ } else if (*val == 'y') { // yes
+ wp->w_minscwidth = wp->w_maxscwidth = 1;
+ } else if (!strncmp(val, "auto:", 5)) { // auto:<NUM>
+ wp->w_minscwidth = 0;
+ wp->w_maxscwidth = val[5] - '0';
+ } else { // auto
+ wp->w_minscwidth = 0;
+ wp->w_maxscwidth = 1;
+ }
return OK;
}
- // check for 'auto:<NUMBER>-<NUMBER>'
- if (strlen(val) == 8
- && !strncmp(val, "auto:", 5)
- && ascii_isdigit(val[5])
- && val[6] == '-'
- && ascii_isdigit(val[7])) {
- int min = val[5] - '0';
- int max = val[7] - '0';
- if (min < 1 || max < 2 || min > 8 || max > 9 || min >= max) {
- return FAIL;
- }
- return OK;
+ if (strncmp(val, "auto:", 5) != 0
+ || strlen(val) != 8
+ || !ascii_isdigit(val[5])
+ || val[6] != '-'
+ || !ascii_isdigit(val[7])) {
+ return FAIL;
}
- return FAIL;
+ // auto:<NUM>-<NUM>
+ int min = val[5] - '0';
+ int max = val[7] - '0';
+ if (min < 1 || max < 2 || min > 8 || min >= max) {
+ return FAIL;
+ }
+
+ wp->w_minscwidth = min;
+ wp->w_maxscwidth = max;
+ return OK;
}
/// Check validity of options with the 'statusline' format.
/// Return an untranslated error message or NULL.
-char *check_stl_option(char *s)
+const char *check_stl_option(char *s)
{
int groupdepth = 0;
static char errbuf[80];
@@ -641,7 +511,7 @@ char *check_stl_option(char *s)
/// Check for a "normal" directory or file name in some options. Disallow a
/// path separator (slash and/or backslash), wildcards and characters that are
/// often illegal in a file name. Be more permissive if "secure" is off.
-static bool check_illegal_path_names(char *val, uint32_t flags)
+bool check_illegal_path_names(char *val, uint32_t flags)
{
return (((flags & P_NFNAME)
&& strpbrk(val, (secure ? "/\\*?[|;&<>\r\n" : "/\\*?[<>\r\n")) != NULL)
@@ -649,10 +519,226 @@ static bool check_illegal_path_names(char *val, uint32_t flags)
&& strpbrk(val, "*?[|;&<>\r\n") != NULL));
}
-static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **errmsg)
+/// An option that accepts a list of flags is changed.
+/// e.g. 'viewoptions', 'switchbuf', 'casemap', etc.
+static const char *did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list)
+{
+ if (opt_strings_flags(val, values, flagp, list) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+/// An option that accepts a list of string values is changed.
+/// e.g. 'nrformats', 'scrollopt', 'wildoptions', etc.
+static const char *did_set_opt_strings(char *val, char **values, bool list)
+{
+ return did_set_opt_flags(val, values, NULL, list);
+}
+
+/// An option which is a list of flags is set. Valid values are in "flags".
+static const char *did_set_option_listflag(char *val, char *flags, char *errbuf, size_t errbuflen)
+{
+ for (char *s = val; *s; s++) {
+ if (vim_strchr(flags, (uint8_t)(*s)) == NULL) {
+ return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
+ }
+ }
+
+ return NULL;
+}
+
+/// Expand an option that accepts a list of string values.
+static int expand_set_opt_string(optexpand_T *args, char **values, size_t numValues,
+ int *numMatches, char ***matches)
+{
+ regmatch_T *regmatch = args->oe_regmatch;
+ bool include_orig_val = args->oe_include_orig_val;
+ char *option_val = args->oe_opt_value;
+
+ // Assume numValues is small since they are fixed enums, so just allocate
+ // upfront instead of needing two passes to calculate output size.
+ *matches = xmalloc(sizeof(char *) * (numValues + 1));
+
+ int count = 0;
+
+ if (include_orig_val && *option_val != NUL) {
+ (*matches)[count++] = xstrdup(option_val);
+ }
+
+ for (char **val = values; *val != NULL; val++) {
+ if (include_orig_val && *option_val != NUL) {
+ if (strcmp(*val, option_val) == 0) {
+ continue;
+ }
+ }
+ if (vim_regexec(regmatch, *val, 0)) {
+ (*matches)[count++] = xstrdup(*val);
+ }
+ }
+ if (count == 0) {
+ XFREE_CLEAR(*matches);
+ return FAIL;
+ }
+ *numMatches = count;
+ return OK;
+}
+
+static char *set_opt_callback_orig_option = NULL;
+static char *((*set_opt_callback_func)(expand_T *, int));
+
+/// Callback used by expand_set_opt_generic to also include the original value.
+static char *expand_set_opt_callback(expand_T *xp, int idx)
+{
+ if (idx == 0) {
+ if (set_opt_callback_orig_option != NULL) {
+ return set_opt_callback_orig_option;
+ } else {
+ return ""; // empty strings are ignored
+ }
+ }
+ return set_opt_callback_func(xp, idx - 1);
+}
+
+/// Expand an option with a callback that iterates through a list of possible names.
+static int expand_set_opt_generic(optexpand_T *args, CompleteListItemGetter func, int *numMatches,
+ char ***matches)
+{
+ set_opt_callback_orig_option = args->oe_include_orig_val ? args->oe_opt_value : NULL;
+ set_opt_callback_func = func;
+
+ // not using fuzzy as currently EXPAND_STRING_SETTING doesn't use it
+ ExpandGeneric("", args->oe_xp, args->oe_regmatch, matches, numMatches,
+ expand_set_opt_callback, false);
+
+ set_opt_callback_orig_option = NULL;
+ set_opt_callback_func = NULL;
+ return OK;
+}
+
+/// Expand an option which is a list of flags.
+static int expand_set_opt_listflag(optexpand_T *args, char *flags, int *numMatches, char ***matches)
{
+ char *option_val = args->oe_opt_value;
+ char *cmdline_val = args->oe_set_arg;
+ bool append = args->oe_append;
+ bool include_orig_val = args->oe_include_orig_val && (*option_val != NUL);
+
+ size_t num_flags = strlen(flags);
+
+ // Assume we only have small number of flags, so just allocate max size.
+ *matches = xmalloc(sizeof(char *) * (num_flags + 1));
+
+ int count = 0;
+
+ if (include_orig_val) {
+ (*matches)[count++] = xstrdup(option_val);
+ }
+
+ for (char *flag = flags; *flag != NUL; flag++) {
+ if (append && vim_strchr(option_val, *flag) != NULL) {
+ continue;
+ }
+
+ if (vim_strchr(cmdline_val, *flag) == NULL) {
+ if (include_orig_val && option_val[1] == NUL && *flag == option_val[0]) {
+ // This value is already used as the first choice as it's the
+ // existing flag. Just skip it to avoid duplicate.
+ continue;
+ }
+ (*matches)[count++] = xmemdupz(flag, 1);
+ }
+ }
+
+ if (count == 0) {
+ XFREE_CLEAR(*matches);
+ return FAIL;
+ }
+ *numMatches = count;
+ return OK;
+}
+
+/// The 'ambiwidth' option is changed.
+const char *did_set_ambiwidth(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
+ return e_invarg;
+ }
+ return check_chars_options();
+}
+
+int expand_set_ambiwidth(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ambw_values,
+ ARRAY_SIZE(p_ambw_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'background' option is changed.
+const char *did_set_background(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_strings(p_bg, p_bg_values, false) != OK) {
+ return e_invarg;
+ }
+
+ int dark = (*p_bg == 'd');
+
+ init_highlight(false, false);
+
+ if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) {
+ // The color scheme must have set 'background' back to another
+ // value, that's not what we want here. Disable the color
+ // scheme and set the colors again.
+ do_unlet(S_LEN("g:colors_name"), true);
+ free_string_option(p_bg);
+ p_bg = xstrdup((dark ? "dark" : "light"));
+ check_string_option(&p_bg);
+ init_highlight(false, false);
+ }
+ return NULL;
+}
+
+int expand_set_background(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bg_values,
+ ARRAY_SIZE(p_bg_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backspace' option is changed.
+const char *did_set_backspace(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (ascii_isdigit(*p_bs)) {
+ if (*p_bs != '2') {
+ return e_invarg;
+ }
+ } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_backspace(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bs_values,
+ ARRAY_SIZE(p_bs_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backupcopy' option is changed.
+const char *did_set_backupcopy(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ const char *oldval = args->os_oldval.string.data;
+ int opt_flags = args->os_flags;
char *bkc = p_bkc;
- unsigned int *flags = &bkc_flags;
+ unsigned *flags = &bkc_flags;
if (opt_flags & OPT_LOCAL) {
bkc = buf->b_p_bkc;
@@ -664,7 +750,7 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e
*flags = 0;
} else {
if (opt_strings_flags(bkc, p_bkc_values, flags, true) != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
if (((*flags & BKC_AUTO) != 0)
@@ -672,171 +758,526 @@ static void did_set_backupcopy(buf_T *buf, char *oldval, int opt_flags, char **e
+ ((*flags & BKC_NO) != 0) != 1) {
// Must have exactly one of "auto", "yes" and "no".
(void)opt_strings_flags(oldval, p_bkc_values, flags, true);
- *errmsg = e_invarg;
+ return e_invarg;
}
}
+
+ return NULL;
}
-static void did_set_backupext_or_patchmode(char **errmsg)
+int expand_set_backupcopy(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bkc_values,
+ ARRAY_SIZE(p_bkc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'backupext' or the 'patchmode' option is changed.
+const char *did_set_backupext_or_patchmode(optset_T *args FUNC_ATTR_UNUSED)
{
if (strcmp(*p_bex == '.' ? p_bex + 1 : p_bex,
*p_pm == '.' ? p_pm + 1 : p_pm) == 0) {
- *errmsg = e_backupext_and_patchmode_are_equal;
+ return e_backupext_and_patchmode_are_equal;
}
+
+ return NULL;
}
-static void did_set_breakindentopt(win_T *win, char **errmsg)
+/// The 'belloff' option is changed.
+const char *did_set_belloff(optset_T *args FUNC_ATTR_UNUSED)
{
+ return did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true);
+}
+
+int expand_set_belloff(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bo_values,
+ ARRAY_SIZE(p_bo_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'breakindentopt' option is changed.
+const char *did_set_breakindentopt(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
if (briopt_check(win) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
// list setting requires a redraw
if (win == curwin && win->w_briopt_list) {
redraw_all_later(UPD_NOT_VALID);
}
+
+ return NULL;
}
-static void did_set_isopt(buf_T *buf, bool *did_chartab, char **errmsg)
+int expand_set_breakindentopt(optexpand_T *args, int *numMatches, char ***matches)
{
- // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[]
- // If the new option is invalid, use old value.
- // 'lisp' option: refill g_chartab[] for '-' char
- if (buf_init_chartab(buf, true) == FAIL) {
- *did_chartab = true; // need to restore it below
- *errmsg = e_invarg; // error in value
+ return expand_set_opt_string(args,
+ p_briopt_values,
+ ARRAY_SIZE(p_briopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'bufhidden' option is changed.
+const char *did_set_bufhidden(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ return did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false);
+}
+
+int expand_set_bufhidden(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_bufhidden_values,
+ ARRAY_SIZE(p_bufhidden_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'buftype' option is changed.
+const char *did_set_buftype(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ // When 'buftype' is set, check for valid value.
+ if ((buf->terminal && buf->b_p_bt[0] != 't')
+ || (!buf->terminal && buf->b_p_bt[0] == 't')
+ || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) {
+ return e_invarg;
}
+ if (win->w_status_height || global_stl_height()) {
+ win->w_redr_status = true;
+ redraw_later(win, UPD_VALID);
+ }
+ buf->b_help = (buf->b_p_bt[0] == 'h');
+ redraw_titles();
+ return NULL;
}
-static void did_set_helpfile(void)
+int expand_set_buftype(optexpand_T *args, int *numMatches, char ***matches)
{
- // May compute new values for $VIM and $VIMRUNTIME
- if (didset_vim) {
- vim_unsetenv_ext("VIM");
+ return expand_set_opt_string(args,
+ p_buftype_values,
+ ARRAY_SIZE(p_buftype_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'casemap' option is changed.
+const char *did_set_casemap(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true);
+}
+
+int expand_set_casemap(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_cmp_values,
+ ARRAY_SIZE(p_cmp_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The global 'listchars' or 'fillchars' option is changed.
+static const char *did_set_global_listfillchars(win_T *win, char *val, bool opt_lcs, int opt_flags)
+{
+ const char *errmsg = NULL;
+ char **local_ptr = opt_lcs ? &win->w_p_lcs : &win->w_p_fcs;
+
+ // only apply the global value to "win" when it does not have a
+ // local value
+ if (opt_lcs) {
+ errmsg = set_listchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
+ } else {
+ errmsg = set_fillchars_option(win, val, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
}
- if (didset_vimruntime) {
- vim_unsetenv_ext("VIMRUNTIME");
+ if (errmsg != NULL) {
+ return errmsg;
+ }
+
+ // If the current window is set to use the global
+ // 'listchars'/'fillchars' value, clear the window-local value.
+ if (!(opt_flags & OPT_GLOBAL)) {
+ clear_string_option(local_ptr);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ // If the current window has a local value need to apply it
+ // again, it was changed when setting the global value.
+ // If no error was returned above, we don't expect an error
+ // here, so ignore the return value.
+ if (opt_lcs) {
+ if (*wp->w_p_lcs == NUL) {
+ (void)set_listchars_option(wp, wp->w_p_lcs, true);
+ }
+ } else {
+ if (*wp->w_p_fcs == NUL) {
+ (void)set_fillchars_option(wp, wp->w_p_fcs, true);
+ }
+ }
}
+
+ redraw_all_later(UPD_NOT_VALID);
+
+ return NULL;
}
-static void did_set_cursorlineopt(win_T *win, char **varp, char **errmsg)
+/// The 'fillchars' option or the 'listchars' option is changed.
+const char *did_set_chars_option(optset_T *args)
{
- if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) {
- *errmsg = e_invarg;
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ const char *errmsg = NULL;
+
+ if (varp == &p_lcs // global 'listchars'
+ || varp == &p_fcs) { // global 'fillchars'
+ errmsg = did_set_global_listfillchars(win, *varp, varp == &p_lcs, args->os_flags);
+ } else if (varp == &win->w_p_lcs) { // local 'listchars'
+ errmsg = set_listchars_option(win, *varp, true);
+ } else if (varp == &win->w_p_fcs) { // local 'fillchars'
+ errmsg = set_fillchars_option(win, *varp, true);
}
+
+ return errmsg;
}
-static void did_set_helplang(char **errmsg)
+/// Expand 'fillchars' or 'listchars' option value.
+int expand_set_chars_option(optexpand_T *args, int *numMatches, char ***matches)
{
- // Check for "", "ab", "ab,cd", etc.
- for (char *s = p_hlg; *s != NUL; s += 3) {
- if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
- *errmsg = e_invarg;
- break;
+ char **varp = (char **)args->oe_varp;
+ bool is_lcs = (varp == &p_lcs || varp == &curwin->w_p_lcs);
+ return expand_set_opt_generic(args,
+ is_lcs ? get_listchars_name : get_fillchars_name,
+ numMatches,
+ matches);
+}
+
+/// The 'cinoptions' option is changed.
+const char *did_set_cinoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // TODO(vim): recognize errors
+ parse_cino(curbuf);
+
+ return NULL;
+}
+
+/// The 'clipboard' option is changed.
+const char *did_set_clipboard(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true);
+}
+
+int expand_set_clipboard(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_cb_values,
+ ARRAY_SIZE(p_cb_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'colorcolumn' option is changed.
+const char *did_set_colorcolumn(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ return check_colorcolumn(win);
+}
+
+/// The 'comments' option is changed.
+const char *did_set_comments(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+ char *errmsg = NULL;
+ for (char *s = *varp; *s;) {
+ while (*s && *s != ':') {
+ if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL
+ && !ascii_isdigit(*s) && *s != '-') {
+ errmsg = illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
+ break;
+ }
+ s++;
}
- if (s[2] == NUL) {
+ if (*s++ == NUL) {
+ errmsg = N_("E524: Missing colon");
+ } else if (*s == ',' || *s == NUL) {
+ errmsg = N_("E525: Zero length string");
+ }
+ if (errmsg != NULL) {
break;
}
+ while (*s && *s != ',') {
+ if (*s == '\\' && s[1] != NUL) {
+ s++;
+ }
+ s++;
+ }
+ s = skip_to_option_part(s);
}
+ return errmsg;
}
-static void did_set_highlight(char **varp, char **errmsg)
+/// The 'commentstring' option is changed.
+const char *did_set_commentstring(optset_T *args)
{
- if (strcmp(*varp, HIGHLIGHT_INIT) != 0) {
- *errmsg = e_unsupportedoption;
+ char **varp = (char **)args->os_varp;
+
+ if (**varp != NUL && strstr(*varp, "%s") == NULL) {
+ return N_("E537: 'commentstring' must be empty or contain %s");
}
+ return NULL;
}
-static void did_set_opt_flags(char *val, char **values, unsigned *flagp, bool list, char **errmsg)
+/// The 'complete' option is changed.
+const char *did_set_complete(optset_T *args)
{
- if (opt_strings_flags(val, values, flagp, list) != OK) {
- *errmsg = e_invarg;
+ char **varp = (char **)args->os_varp;
+
+ // check if it is a valid value for 'complete' -- Acevedo
+ for (char *s = *varp; *s;) {
+ while (*s == ',' || *s == ' ') {
+ s++;
+ }
+ if (!*s) {
+ break;
+ }
+ if (vim_strchr(".wbuksid]tUf", (uint8_t)(*s)) == NULL) {
+ return illegal_char(args->os_errbuf, args->os_errbuflen, (uint8_t)(*s));
+ }
+ if (*++s != NUL && *s != ',' && *s != ' ') {
+ if (s[-1] == 'k' || s[-1] == 's') {
+ // skip optional filename after 'k' and 's'
+ while (*s && *s != ',' && *s != ' ') {
+ if (*s == '\\' && s[1] != NUL) {
+ s++;
+ }
+ s++;
+ }
+ } else {
+ if (args->os_errbuf != NULL) {
+ vim_snprintf(args->os_errbuf, args->os_errbuflen,
+ _("E535: Illegal character after <%c>"),
+ *--s);
+ return args->os_errbuf;
+ }
+ return "";
+ }
+ }
}
+ return NULL;
}
-static void did_set_opt_strings(char *val, char **values, bool list, char **errmsg)
+int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches)
{
- did_set_opt_flags(val, values, NULL, list, errmsg);
+ static char *(p_cpt_values[]) = {
+ ".", "w", "b", "u", "k", "kspell", "s", "i", "d", "]", "t", "U", "f", NULL
+ };
+ return expand_set_opt_string(args,
+ p_cpt_values,
+ ARRAY_SIZE(p_cpt_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_sessionoptions(char *oldval, char **errmsg)
+/// The 'completeopt' option is changed.
+const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
{
- if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) {
- *errmsg = e_invarg;
- }
- if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) {
- // Don't allow both "sesdir" and "curdir".
- (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true);
- *errmsg = e_invarg;
+ if (check_opt_strings(p_cot, p_cot_values, true) != OK) {
+ return e_invarg;
}
+ completeopt_was_set();
+ return NULL;
}
-static void did_set_ambiwidth(char **errmsg)
+int expand_set_completeopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_strings(p_ambw, p_ambw_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- *errmsg = check_chars_options();
- }
+ return expand_set_opt_string(args,
+ p_cot_values,
+ ARRAY_SIZE(p_cot_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_background(char **errmsg)
+#ifdef BACKSLASH_IN_FILENAME
+/// The 'completeslash' option is changed.
+const char *did_set_completeslash(optset_T *args)
{
- if (check_opt_strings(p_bg, p_bg_values, false) != OK) {
- *errmsg = e_invarg;
- return;
+ buf_T *buf = (buf_T *)args->os_buf;
+ if (check_opt_strings(p_csl, p_csl_values, false) != OK
+ || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) {
+ return e_invarg;
}
+ return NULL;
+}
- int dark = (*p_bg == 'd');
+int expand_set_completeslash(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_csl_values,
+ ARRAY_SIZE(p_csl_values) - 1,
+ numMatches,
+ matches);
+}
+#endif
- init_highlight(false, false);
+/// The 'concealcursor' option is changed.
+const char *did_set_concealcursor(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
- if (dark != (*p_bg == 'd') && get_var_value("g:colors_name") != NULL) {
- // The color scheme must have set 'background' back to another
- // value, that's not what we want here. Disable the color
- // scheme and set the colors again.
- do_unlet(S_LEN("g:colors_name"), true);
- free_string_option(p_bg);
- p_bg = xstrdup((dark ? "dark" : "light"));
- check_string_option(&p_bg);
- init_highlight(false, false);
+ return did_set_option_listflag(*varp, COCU_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_concealcursor(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, COCU_ALL, numMatches, matches);
+}
+
+/// The 'cpoptions' option is changed.
+const char *did_set_cpoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, CPO_VI, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_cpoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, CPO_VI, numMatches, matches);
+}
+
+/// The 'cursorlineopt' option is changed.
+const char *did_set_cursorlineopt(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+
+ // This could be changed to use opt_strings_flags() instead.
+ if (**varp == NUL || fill_culopt_flags(*varp, win) != OK) {
+ return e_invarg;
}
+
+ return NULL;
}
-static void did_set_wildmode(char **errmsg)
+int expand_set_cursorlineopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_wim() == FAIL) {
- *errmsg = e_invarg;
+ return expand_set_opt_string(args,
+ p_culopt_values,
+ ARRAY_SIZE(p_culopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'debug' option is changed.
+const char *did_set_debug(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_debug, p_debug_values, false);
+}
+
+int expand_set_debug(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_debug_values,
+ ARRAY_SIZE(p_debug_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'diffopt' option is changed.
+const char *did_set_diffopt(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (diffopt_changed() == FAIL) {
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_winaltkeys(char **errmsg)
+int expand_set_diffopt(optexpand_T *args, int *numMatches, char ***matches)
{
- if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) {
- *errmsg = e_invarg;
+ expand_T *xp = args->oe_xp;
+
+ if (xp->xp_pattern > args->oe_set_arg && *(xp->xp_pattern - 1) == ':') {
+ // Within "algorithm:", we have a subgroup of possible options.
+ const size_t algo_len = strlen("algorithm:");
+ if (xp->xp_pattern - args->oe_set_arg >= (int)algo_len
+ && strncmp(xp->xp_pattern - algo_len, "algorithm:", algo_len) == 0) {
+ return expand_set_opt_string(args,
+ p_dip_algorithm_values,
+ ARRAY_SIZE(p_dip_algorithm_values) - 1,
+ numMatches,
+ matches);
+ }
+ return FAIL;
}
+
+ return expand_set_opt_string(args,
+ p_dip_values,
+ ARRAY_SIZE(p_dip_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_eventignore(char **errmsg)
+/// The 'display' option is changed.
+const char *did_set_display(optset_T *args FUNC_ATTR_UNUSED)
{
- if (check_ei() == FAIL) {
- *errmsg = e_invarg;
+ if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) {
+ return e_invarg;
}
+ (void)init_chartab();
+ msg_grid_validate();
+ return NULL;
+}
+
+int expand_set_display(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_dy_values,
+ ARRAY_SIZE(p_dy_values) - 1,
+ numMatches,
+ matches);
}
-// 'encoding', 'fileencoding' and 'makeencoding'
-static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flags, char **errmsg)
+/// The 'eadirection' option is changed.
+const char *did_set_eadirection(optset_T *args FUNC_ATTR_UNUSED)
{
+ return did_set_opt_strings(p_ead, p_ead_values, false);
+}
+
+int expand_set_eadirection(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ead_values,
+ ARRAY_SIZE(p_ead_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// One of the 'encoding', 'fileencoding' or 'makeencoding'
+/// options is changed.
+const char *did_set_encoding(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ int opt_flags = args->os_flags;
+ // Get the global option to compare with, otherwise we would have to check
+ // two values for all local options.
+ char **gvarp = (char **)get_option_varp_scope_from(args->os_idx, OPT_GLOBAL, buf, NULL);
+
if (gvarp == &p_fenc) {
if (!MODIFIABLE(buf) && opt_flags != OPT_GLOBAL) {
- *errmsg = e_modifiable;
- return;
+ return e_modifiable;
}
if (vim_strchr(*varp, ',') != NULL) {
// No comma allowed in 'fileencoding'; catches confusing it
// with 'fileencodings'.
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
// May show a "+" in the title now.
@@ -852,19 +1293,347 @@ static void did_set_encoding(buf_T *buf, char **varp, char **gvarp, int opt_flag
if (varp == &p_enc) {
// only encoding=utf-8 allowed
if (strcmp(p_enc, "utf-8") != 0) {
- *errmsg = e_unsupportedoption;
- return;
+ return e_unsupportedoption;
}
spell_reload();
}
+ return NULL;
+}
+
+int expand_set_encoding(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_generic(args, get_encoding_name, numMatches, matches);
+}
+
+/// The 'eventignore' option is changed.
+const char *did_set_eventignore(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_ei() == FAIL) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+static char *get_eventignore_name(expand_T *xp, int idx)
+{
+ // 'eventignore' allows special keyword "all" in addition to
+ // all event names.
+ if (idx == 0) {
+ return "all";
+ }
+ return get_event_name_no_group(xp, idx - 1);
}
-static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_checked,
- char **errmsg)
+int expand_set_eventignore(optexpand_T *args, int *numMatches, char ***matches)
{
+ return expand_set_opt_generic(args, get_eventignore_name, numMatches, matches);
+}
+
+/// The 'fileformat' option is changed.
+const char *did_set_fileformat(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ const char *oldval = args->os_oldval.string.data;
+ int opt_flags = args->os_flags;
+ if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) {
+ return e_modifiable;
+ } else if (check_opt_strings(*varp, p_ff_values, false) != OK) {
+ return e_invarg;
+ }
+ redraw_titles();
+ // update flag in swap file
+ ml_setflags(buf);
+ // Redraw needed when switching to/from "mac": a CR in the text
+ // will be displayed differently.
+ if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') {
+ redraw_buf_later(buf, UPD_NOT_VALID);
+ }
+ return NULL;
+}
+
+int expand_set_fileformat(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ff_values,
+ ARRAY_SIZE(p_ff_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// fileformat options.
+char *get_fileformat_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(p_ff_values)) {
+ return NULL;
+ }
+
+ return p_ff_values[idx];
+}
+
+/// The 'fileformats' option is changed.
+const char *did_set_fileformats(optset_T *args)
+{
+ return did_set_opt_strings(p_ffs, p_ff_values, true);
+}
+
+/// The 'filetype' or the 'syntax' option is changed.
+const char *did_set_filetype_or_syntax(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
if (!valid_filetype(*varp)) {
- *errmsg = e_invarg;
- return;
+ return e_invarg;
+ }
+
+ args->os_value_changed = strcmp(args->os_oldval.string.data, *varp) != 0;
+
+ // Since we check the value, there is no need to set P_INSECURE,
+ // even when the value comes from a modeline.
+ args->os_value_checked = true;
+
+ return NULL;
+}
+
+/// The 'foldclose' option is changed.
+const char *did_set_foldclose(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_fcl, p_fcl_values, true);
+}
+
+int expand_set_foldclose(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fcl_values,
+ ARRAY_SIZE(p_fcl_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldcolumn' option is changed.
+const char *did_set_foldcolumn(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+ if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_foldcolumn(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdc_values,
+ ARRAY_SIZE(p_fdc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldexpr' option is changed.
+const char *did_set_foldexpr(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ (void)did_set_optexpr(args);
+ if (foldmethodIsExpr(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// The 'foldignore' option is changed.
+const char *did_set_foldignore(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (foldmethodIsIndent(win)) {
+ foldUpdateAll(win);
+ }
+ return NULL;
+}
+
+/// The 'foldmarker' option is changed.
+const char *did_set_foldmarker(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ char *p = vim_strchr(*varp, ',');
+
+ if (p == NULL) {
+ return e_comma_required;
+ }
+
+ if (p == *varp || p[1] == NUL) {
+ return e_invarg;
+ }
+
+ if (foldmethodIsMarker(win)) {
+ foldUpdateAll(win);
+ }
+
+ return NULL;
+}
+
+/// The 'foldmethod' option is changed.
+const char *did_set_foldmethod(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ if (check_opt_strings(*varp, p_fdm_values, false) != OK
+ || *win->w_p_fdm == NUL) {
+ return e_invarg;
+ }
+ foldUpdateAll(win);
+ if (foldmethodIsDiff(win)) {
+ newFoldLevel();
+ }
+ return NULL;
+}
+
+int expand_set_foldmethod(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdm_values,
+ ARRAY_SIZE(p_fdm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'foldopen' option is changed.
+const char *did_set_foldopen(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true);
+}
+
+int expand_set_foldopen(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_fdo_values,
+ ARRAY_SIZE(p_fdo_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'formatoptions' option is changed.
+const char *did_set_formatoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, FO_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_formatoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, FO_ALL, numMatches, matches);
+}
+
+/// The 'guicursor' option is changed.
+const char *did_set_guicursor(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return parse_shape_opt(SHAPE_CURSOR);
+}
+
+/// The 'helpfile' option is changed.
+const char *did_set_helpfile(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // May compute new values for $VIM and $VIMRUNTIME
+ if (didset_vim) {
+ vim_unsetenv_ext("VIM");
+ }
+ if (didset_vimruntime) {
+ vim_unsetenv_ext("VIMRUNTIME");
+ }
+ return NULL;
+}
+
+/// The 'helplang' option is changed.
+const char *did_set_helplang(optset_T *args FUNC_ATTR_UNUSED)
+{
+ // Check for "", "ab", "ab,cd", etc.
+ for (char *s = p_hlg; *s != NUL; s += 3) {
+ if (s[1] == NUL || ((s[2] != ',' || s[3] == NUL) && s[2] != NUL)) {
+ return e_invarg;
+ }
+ if (s[2] == NUL) {
+ break;
+ }
+ }
+ return NULL;
+}
+
+/// The 'highlight' option is changed.
+const char *did_set_highlight(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ if (strcmp(*varp, HIGHLIGHT_INIT) != 0) {
+ return e_unsupportedoption;
+ }
+ return NULL;
+}
+
+/// The 'iconstring' option is changed.
+const char *did_set_iconstring(optset_T *args)
+{
+ return did_set_titleiconstring(args, STL_IN_ICON);
+}
+
+/// The 'inccommand' option is changed.
+const char *did_set_inccommand(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (cmdpreview) {
+ return e_invarg;
+ }
+ return did_set_opt_strings(p_icm, p_icm_values, false);
+}
+
+int expand_set_inccommand(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_icm_values,
+ ARRAY_SIZE(p_icm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'isident' or the 'iskeyword' or the 'isprint' or the 'isfname' option is
+/// changed.
+const char *did_set_isopt(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ // 'isident', 'iskeyword', 'isprint or 'isfname' option: refill g_chartab[]
+ // If the new option is invalid, use old value.
+ // 'lisp' option: refill g_chartab[] for '-' char
+ if (buf_init_chartab(buf, true) == FAIL) {
+ args->os_restore_chartab = true; // need to restore it below
+ return e_invarg; // error in value
+ }
+ return NULL;
+}
+
+/// The 'jumpoptions' option is changed.
+const char *did_set_jumpoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true);
+}
+
+int expand_set_jumpoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_jop_values,
+ ARRAY_SIZE(p_jop_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'keymap' option has changed.
+const char *did_set_keymap(optset_T *args)
+{
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
+ int opt_flags = args->os_flags;
+
+ if (!valid_filetype(*varp)) {
+ return e_invarg;
}
int secure_save = secure;
@@ -874,15 +1643,15 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch
secure = 0;
// load or unload key mapping tables
- *errmsg = keymap_init();
+ const char *errmsg = keymap_init();
secure = secure_save;
// Since we check the value, there is no need to set P_INSECURE,
// even when the value comes from a modeline.
- *value_checked = true;
+ args->os_value_checked = true;
- if (*errmsg == NULL) {
+ if (errmsg == NULL) {
if (*buf->b_p_keymap != NUL) {
// Installed a new keymap, switch on using it.
buf->b_p_iminsert = B_IMODE_LMAP;
@@ -904,29 +1673,56 @@ static void did_set_keymap(buf_T *buf, char **varp, int opt_flags, int *value_ch
}
status_redraw_buf(buf);
}
+
+ return errmsg;
}
-static void did_set_fileformat(buf_T *buf, char **varp, const char *oldval, int opt_flags,
- char **errmsg)
+/// The 'keymodel' option is changed.
+const char *did_set_keymodel(optset_T *args FUNC_ATTR_UNUSED)
{
- if (!MODIFIABLE(buf) && !(opt_flags & OPT_GLOBAL)) {
- *errmsg = e_modifiable;
- } else if (check_opt_strings(*varp, p_ff_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- redraw_titles();
- // update flag in swap file
- ml_setflags(buf);
- // Redraw needed when switching to/from "mac": a CR in the text
- // will be displayed differently.
- if (get_fileformat(buf) == EOL_MAC || *oldval == 'm') {
- redraw_buf_later(buf, UPD_NOT_VALID);
- }
+ if (check_opt_strings(p_km, p_km_values, true) != OK) {
+ return e_invarg;
+ }
+ km_stopsel = (vim_strchr(p_km, 'o') != NULL);
+ km_startsel = (vim_strchr(p_km, 'a') != NULL);
+ return NULL;
+}
+
+int expand_set_keymodel(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_km_values,
+ ARRAY_SIZE(p_km_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'lispoptions' option is changed.
+const char *did_set_lispoptions(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) {
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_matchpairs(char **varp, char **errmsg)
+int expand_set_lispoptions(optexpand_T *args, int *numMatches, char ***matches)
{
+ static char *(p_lop_values[]) = { "expr:0", "expr:1", NULL };
+ return expand_set_opt_string(args,
+ p_lop_values,
+ ARRAY_SIZE(p_lop_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'matchpairs' option is changed.
+const char *did_set_matchpairs(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
for (char *p = *varp; *p != NUL; p++) {
int x2 = -1;
int x3 = -1;
@@ -940,97 +1736,276 @@ static void did_set_matchpairs(char **varp, char **errmsg)
p += utfc_ptr2len(p);
}
if (x2 != ':' || x3 == -1 || (*p != NUL && *p != ',')) {
- *errmsg = e_invarg;
- break;
+ return e_invarg;
}
if (*p == NUL) {
break;
}
}
+ return NULL;
}
-static void did_set_comments(char **varp, char *errbuf, size_t errbuflen, char **errmsg)
+/// The 'mkspellmem' option is changed.
+const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED)
{
- for (char *s = *varp; *s;) {
- while (*s && *s != ':') {
- if (vim_strchr(COM_ALL, (uint8_t)(*s)) == NULL
- && !ascii_isdigit(*s) && *s != '-') {
- *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s));
- break;
- }
- s++;
+ if (spell_check_msm() != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+/// The 'mouse' option is changed.
+const char *did_set_mouse(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, MOUSE_ALL, args->os_errbuf, args->os_errbuflen);
+}
+
+int expand_set_mouse(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_listflag(args, MOUSE_ALL, numMatches, matches);
+}
+
+/// The 'mousemodel' option is changed.
+const char *did_set_mousemodel(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_mousem, p_mousem_values, false);
+}
+
+int expand_set_mousemodel(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_mousem_values,
+ ARRAY_SIZE(p_mousem_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// Handle setting 'mousescroll'.
+/// @return error message, NULL if it's OK.
+const char *did_set_mousescroll(optset_T *args FUNC_ATTR_UNUSED)
+{
+ OptInt vertical = -1;
+ OptInt horizontal = -1;
+
+ char *string = p_mousescroll;
+
+ while (true) {
+ char *end = vim_strchr(string, ',');
+ size_t length = end ? (size_t)(end - string) : strlen(string);
+
+ // Both "ver:" and "hor:" are 4 bytes long.
+ // They should be followed by at least one digit.
+ if (length <= 4) {
+ return e_invarg;
}
- if (*s++ == NUL) {
- *errmsg = N_("E524: Missing colon");
- } else if (*s == ',' || *s == NUL) {
- *errmsg = N_("E525: Zero length string");
+
+ OptInt *direction;
+
+ if (memcmp(string, "ver:", 4) == 0) {
+ direction = &vertical;
+ } else if (memcmp(string, "hor:", 4) == 0) {
+ direction = &horizontal;
+ } else {
+ return e_invarg;
}
- if (*errmsg != NULL) {
- break;
+
+ // If the direction has already been set, this is a duplicate.
+ if (*direction != -1) {
+ return e_invarg;
}
- while (*s && *s != ',') {
- if (*s == '\\' && s[1] != NUL) {
- s++;
+
+ // Verify that only digits follow the colon.
+ for (size_t i = 4; i < length; i++) {
+ if (!ascii_isdigit(string[i])) {
+ return N_("E5080: Digit expected");
}
- s++;
}
- s = skip_to_option_part(s);
+
+ string += 4;
+ *direction = getdigits_int(&string, false, -1);
+
+ // Num options are generally kept within the signed int range.
+ // We know this number won't be negative because we've already checked for
+ // a minus sign. We'll allow 0 as a means of disabling mouse scrolling.
+ if (*direction == -1) {
+ return e_invarg;
+ }
+
+ if (!end) {
+ break;
+ }
+
+ string = end + 1;
+ }
+
+ // If a direction wasn't set, fallback to the default value.
+ p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical;
+ p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal;
+
+ return NULL;
+}
+
+int expand_set_mousescroll(optexpand_T *args, int *numMatches, char ***matches)
+{
+ static char *(p_mousescroll_values[]) = { "hor:", "ver:", NULL };
+ return expand_set_opt_string(args,
+ p_mousescroll_values,
+ ARRAY_SIZE(p_mousescroll_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'nrformats' option is changed.
+const char *did_set_nrformats(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_opt_strings(*varp, p_nf_values, true);
+}
+
+int expand_set_nrformats(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_nf_values,
+ ARRAY_SIZE(p_nf_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// One of the '*expr' options is changed:, 'diffexpr', 'foldexpr', 'foldtext',
+/// 'formatexpr', 'includeexpr', 'indentexpr', 'patchexpr' and 'charconvert'.
+const char *did_set_optexpr(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ // If the option value starts with <SID> or s:, then replace that with
+ // the script identifier.
+ char *name = get_scriptlocal_funcname(*varp);
+ if (name != NULL) {
+ free_string_option(*varp);
+ *varp = name;
}
+ return NULL;
}
-static void did_set_global_listfillchars(win_T *win, char **varp, int opt_flags, char **errmsg)
+/// The 'redrawdebug' option is changed.
+const char *did_set_redrawdebug(optset_T *args FUNC_ATTR_UNUSED)
{
- char **local_ptr = varp == &p_lcs ? &win->w_p_lcs : &win->w_p_fcs;
- // only apply the global value to "win" when it does not have a local value
- *errmsg = set_chars_option(win, varp, **local_ptr == NUL || !(opt_flags & OPT_GLOBAL));
- if (*errmsg == NULL) {
- // If the current window is set to use the global
- // 'listchars'/'fillchars' value, clear the window-local value.
- if (!(opt_flags & OPT_GLOBAL)) {
- clear_string_option(local_ptr);
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- // If the current window has a local value need to apply it
- // again, it was changed when setting the global value.
- // If no error was returned above, we don't expect an error
- // here, so ignore the return value.
- local_ptr = varp == &p_lcs ? &wp->w_p_lcs : &wp->w_p_fcs;
- if (**local_ptr == NUL) {
- (void)set_chars_option(wp, local_ptr, true);
- }
- }
- redraw_all_later(UPD_NOT_VALID);
+ return did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true);
+}
+
+/// The 'rightleftcmd' option is changed.
+const char *did_set_rightleftcmd(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ // Currently only "search" is a supported value.
+ if (**varp != NUL && strcmp(*varp, "search") != 0) {
+ return e_invarg;
}
+
+ return NULL;
}
-static void did_set_verbosefile(char **errmsg)
+int expand_set_rightleftcmd(optexpand_T *args, int *numMatches, char ***matches)
{
- verbose_stop();
- if (*p_vfile != NUL && verbose_open() == FAIL) {
- *errmsg = e_invarg;
+ static char *(p_rlc_values[]) = { "search", NULL };
+ return expand_set_opt_string(args,
+ p_rlc_values,
+ ARRAY_SIZE(p_rlc_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'rulerformat' option is changed.
+const char *did_set_rulerformat(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, true, false);
+}
+
+/// The 'scrollopt' option is changed.
+const char *did_set_scrollopt(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_sbo, p_scbopt_values, true);
+}
+
+int expand_set_scrollopt(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_scbopt_values,
+ ARRAY_SIZE(p_scbopt_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'selection' option is changed.
+const char *did_set_selection(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) {
+ return e_invarg;
+ }
+ return NULL;
+}
+
+int expand_set_selection(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_sel_values,
+ ARRAY_SIZE(p_sel_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'selectmode' option is changed.
+const char *did_set_selectmode(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_strings(p_slm, p_slm_values, true);
+}
+
+int expand_set_selectmode(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_slm_values,
+ ARRAY_SIZE(p_slm_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'sessionoptions' option is changed.
+const char *did_set_sessionoptions(optset_T *args)
+{
+ if (opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true) != OK) {
+ return e_invarg;
+ }
+ if ((ssop_flags & SSOP_CURDIR) && (ssop_flags & SSOP_SESDIR)) {
+ // Don't allow both "sesdir" and "curdir".
+ const char *oldval = args->os_oldval.string.data;
+ (void)opt_strings_flags(oldval, p_ssop_values, &ssop_flags, true);
+ return e_invarg;
}
+ return NULL;
}
-static int shada_idx = -1;
+int expand_set_sessionoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_ssop_values,
+ ARRAY_SIZE(p_ssop_values) - 1,
+ numMatches,
+ matches);
+}
-static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, char *errbuf,
- size_t errbuflen, char **errmsg)
+const char *did_set_shada(optset_T *args)
{
- // TODO(ZyX-I): Remove this code in the future, alongside with &viminfo
- // option.
- *opt_idx = (((*opt)->fullname[0] == 'v')
- ? (shada_idx == -1 ? ((shada_idx = findoption("shada"))) : shada_idx)
- : *opt_idx);
- *opt = get_option(*opt_idx);
- // Update free_oldval now that we have the opt_idx for 'shada', otherwise
- // there would be a disconnect between the check for P_ALLOCED at the start
- // of the function and the set of P_ALLOCED at the end of the function.
- *free_oldval = ((*opt)->flags & P_ALLOCED);
+ char *errbuf = args->os_errbuf;
+ size_t errbuflen = args->os_errbuflen;
+
for (char *s = p_shada; *s;) {
// Check it's a valid character
if (vim_strchr("!\"%'/:<@cfhnrs", (uint8_t)(*s)) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s));
- break;
+ return illegal_char(errbuf, errbuflen, (uint8_t)(*s));
}
if (*s == 'n') { // name is always last one
break;
@@ -1049,290 +2024,261 @@ static void did_set_shada(vimoption_T **opt, int *opt_idx, bool *free_oldval, ch
vim_snprintf(errbuf, errbuflen,
_("E526: Missing number after <%s>"),
transchar_byte((uint8_t)(*(s - 1))));
- *errmsg = errbuf;
+ return errbuf;
} else {
- *errmsg = "";
+ return "";
}
- break;
}
}
if (*s == ',') {
s++;
} else if (*s) {
if (errbuf != NULL) {
- *errmsg = N_("E527: Missing comma");
+ return N_("E527: Missing comma");
} else {
- *errmsg = "";
+ return "";
}
- break;
}
}
- if (*p_shada && *errmsg == NULL && get_shada_parameter('\'') < 0) {
- *errmsg = N_("E528: Must specify a ' value");
+ if (*p_shada && get_shada_parameter('\'') < 0) {
+ return N_("E528: Must specify a ' value");
}
+ return NULL;
+}
+
+/// The 'shortmess' option is changed.
+const char *did_set_shortmess(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
+ return did_set_option_listflag(*varp, SHM_ALL, args->os_errbuf, args->os_errbuflen);
}
-static void did_set_showbreak(char **varp, char **errmsg)
+int expand_set_shortmess(optexpand_T *args, int *numMatches, char ***matches)
{
+ return expand_set_opt_listflag(args, SHM_ALL, numMatches, matches);
+}
+
+/// The 'showbreak' option is changed.
+const char *did_set_showbreak(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
+
for (char *s = *varp; *s;) {
if (ptr2cells(s) != 1) {
- *errmsg = e_showbreak_contains_unprintable_or_wide_character;
+ return e_showbreak_contains_unprintable_or_wide_character;
}
MB_PTR_ADV(s);
}
+ return NULL;
}
-static void did_set_titleiconstring(char **varp)
+/// The 'showcmdloc' option is changed.
+const char *did_set_showcmdloc(optset_T *args FUNC_ATTR_UNUSED)
{
- // 'titlestring' and 'iconstring'
- int flagval = (varp == &p_titlestring) ? STL_IN_TITLE : STL_IN_ICON;
+ return did_set_opt_strings(p_sloc, p_sloc_values, true);
+}
- // NULL => statusline syntax
- if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) {
- stl_syntax |= flagval;
- } else {
- stl_syntax &= ~flagval;
- }
- did_set_title();
+int expand_set_showcmdloc(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_sloc_values,
+ ARRAY_SIZE(p_sloc_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_selection(char **errmsg)
+/// The 'signcolumn' option is changed.
+const char *did_set_signcolumn(optset_T *args)
{
- if (*p_sel == NUL || check_opt_strings(p_sel, p_sel_values, false) != OK) {
- *errmsg = e_invarg;
+ win_T *win = (win_T *)args->os_win;
+ const char *oldval = args->os_oldval.string.data;
+ if (check_signcolumn(win) != OK) {
+ return e_invarg;
}
+ // When changing the 'signcolumn' to or from 'number', recompute the
+ // width of the number column if 'number' or 'relativenumber' is set.
+ if ((*oldval == 'n' && *(oldval + 1) == 'u') || win->w_minscwidth == SCL_NUM) {
+ win->w_nrwidth_line_count = 0;
+ }
+ return NULL;
}
-static void did_set_keymodel(char **errmsg)
+int expand_set_signcolumn(optexpand_T *args, int *numMatches, char ***matches)
{
- if (check_opt_strings(p_km, p_km_values, true) != OK) {
- *errmsg = e_invarg;
- return;
- }
- km_stopsel = (vim_strchr(p_km, 'o') != NULL);
- km_startsel = (vim_strchr(p_km, 'a') != NULL);
+ return expand_set_opt_string(args,
+ p_scl_values,
+ ARRAY_SIZE(p_scl_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_display(char **errmsg)
+/// The 'spellcapcheck' option is changed.
+const char *did_set_spellcapcheck(optset_T *args)
{
- if (opt_strings_flags(p_dy, p_dy_values, &dy_flags, true) != OK) {
- *errmsg = e_invarg;
- return;
- }
- (void)init_chartab();
- msg_grid_validate();
+ win_T *win = (win_T *)args->os_win;
+ // When 'spellcapcheck' is set compile the regexp program.
+ return compile_cap_prog(win->w_s);
}
-static void did_set_spellfile(char **varp, char **errmsg)
+/// The 'spellfile' option is changed.
+const char *did_set_spellfile(optset_T *args)
{
+ char **varp = (char **)args->os_varp;
+
// When there is a window for this buffer in which 'spell'
// is set load the wordlists.
-
if ((!valid_spellfile(*varp))) {
- *errmsg = e_invarg;
- } else {
- *errmsg = did_set_spell_option(true);
+ return e_invarg;
}
+ return did_set_spell_option(true);
}
-static void did_set_spell(char **varp, char **errmsg)
+/// The 'spelllang' option is changed.
+const char *did_set_spelllang(optset_T *args)
{
+ char **varp = (char **)args->os_varp;
+
// When there is a window for this buffer in which 'spell'
// is set load the wordlists.
if (!valid_spelllang(*varp)) {
- *errmsg = e_invarg;
- } else {
- *errmsg = did_set_spell_option(false);
+ return e_invarg;
}
+ return did_set_spell_option(false);
}
-static void did_set_spellcapcheck(win_T *win, char **errmsg)
-{
- // When 'spellcapcheck' is set compile the regexp program.
- *errmsg = compile_cap_prog(win->w_s);
-}
-
-static void did_set_spelloptions(win_T *win, char **errmsg)
+/// The 'spelloptions' option is changed.
+const char *did_set_spelloptions(optset_T *args)
{
+ win_T *win = (win_T *)args->os_win;
if (opt_strings_flags(win->w_s->b_p_spo, p_spo_values, &(win->w_s->b_p_spo_flags),
true) != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_spellsuggest(char **errmsg)
+int expand_set_spelloptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_spo_values,
+ ARRAY_SIZE(p_spo_values) - 1,
+ numMatches,
+ matches);
+}
+
+/// The 'spellsuggest' option is changed.
+const char *did_set_spellsuggest(optset_T *args FUNC_ATTR_UNUSED)
{
if (spell_check_sps() != OK) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_mkspellmem(char **errmsg)
+int expand_set_spellsuggest(optexpand_T *args, int *numMatches, char ***matches)
{
- if (spell_check_msm() != OK) {
- *errmsg = e_invarg;
- }
+ return expand_set_opt_string(args,
+ p_sps_values,
+ ARRAY_SIZE(p_sps_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_buftype(buf_T *buf, win_T *win, char **errmsg)
+/// The 'splitkeep' option is changed.
+const char *did_set_splitkeep(optset_T *args FUNC_ATTR_UNUSED)
{
- // When 'buftype' is set, check for valid value.
- if ((buf->terminal && buf->b_p_bt[0] != 't')
- || (!buf->terminal && buf->b_p_bt[0] == 't')
- || check_opt_strings(buf->b_p_bt, p_buftype_values, false) != OK) {
- *errmsg = e_invarg;
- } else {
- if (win->w_status_height || global_stl_height()) {
- win->w_redr_status = true;
- redraw_later(win, UPD_VALID);
- }
- buf->b_help = (buf->b_p_bt[0] == 'h');
- redraw_titles();
- }
+ return did_set_opt_strings(p_spk, p_spk_values, false);
+}
+
+int expand_set_splitkeep(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_spk_values,
+ ARRAY_SIZE(p_spk_values) - 1,
+ numMatches,
+ matches);
}
-// 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn'
-static void did_set_statusline(win_T *win, char **varp, char **gvarp, char **errmsg)
+/// The 'statuscolumn' option is changed.
+const char *did_set_statuscolumn(optset_T *args)
{
- if (varp == &p_ruf) { // reset ru_wid first
+ return did_set_statustabline_rulerformat(args, false, true);
+}
+
+/// The 'statusline' option is changed.
+const char *did_set_statusline(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, false, false);
+}
+
+/// The 'statusline', 'winbar', 'tabline', 'rulerformat' or 'statuscolumn' option is changed.
+///
+/// @param rulerformat true if the 'rulerformat' option is changed
+/// @param statuscolumn true if the 'statuscolumn' option is changed
+static const char *did_set_statustabline_rulerformat(optset_T *args, bool rulerformat,
+ bool statuscolumn)
+{
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+ if (rulerformat) { // reset ru_wid first
ru_wid = 0;
- } else if (varp == &win->w_p_stc) {
+ } else if (statuscolumn) {
+ // reset 'statuscolumn' width
win->w_nrwidth_line_count = 0;
}
+ const char *errmsg = NULL;
char *s = *varp;
- if (varp == &p_ruf && *s == '%') {
+ if (rulerformat && *s == '%') {
// set ru_wid if 'ruf' starts with "%99("
if (*++s == '-') { // ignore a '-'
s++;
}
int wid = getdigits_int(&s, true, 0);
- if (wid && *s == '(' && (*errmsg = check_stl_option(p_ruf)) == NULL) {
+ if (wid && *s == '(' && (errmsg = check_stl_option(p_ruf)) == NULL) {
ru_wid = wid;
} else {
- *errmsg = check_stl_option(p_ruf);
+ errmsg = check_stl_option(p_ruf);
}
- } else if (varp == &p_ruf || s[0] != '%' || s[1] != '!') {
+ } else if (rulerformat || s[0] != '%' || s[1] != '!') {
// check 'statusline', 'winbar', 'tabline' or 'statuscolumn'
// only if it doesn't start with "%!"
- *errmsg = check_stl_option(s);
+ errmsg = check_stl_option(s);
}
- if (varp == &p_ruf && *errmsg == NULL) {
+ if (rulerformat && errmsg == NULL) {
comp_col();
}
- // add / remove window bars for 'winbar'
- if (gvarp == &p_wbr) {
- set_winbar(true);
- }
-}
-
-static void did_set_complete(char **varp, char *errbuf, size_t errbuflen, char **errmsg)
-{
- // check if it is a valid value for 'complete' -- Acevedo
- for (char *s = *varp; *s;) {
- while (*s == ',' || *s == ' ') {
- s++;
- }
- if (!*s) {
- break;
- }
- if (vim_strchr(".wbuksid]tU", (uint8_t)(*s)) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s));
- break;
- }
- if (*++s != NUL && *s != ',' && *s != ' ') {
- if (s[-1] == 'k' || s[-1] == 's') {
- // skip optional filename after 'k' and 's'
- while (*s && *s != ',' && *s != ' ') {
- if (*s == '\\' && s[1] != NUL) {
- s++;
- }
- s++;
- }
- } else {
- if (errbuf != NULL) {
- vim_snprintf(errbuf, errbuflen,
- _("E535: Illegal character after <%c>"),
- *--s);
- *errmsg = errbuf;
- } else {
- *errmsg = "";
- }
- break;
- }
- }
- }
-}
-
-static void did_set_completeopt(char **errmsg)
-{
- if (check_opt_strings(p_cot, p_cot_values, true) != OK) {
- *errmsg = e_invarg;
- } else {
- completeopt_was_set();
- }
-}
-
-#ifdef BACKSLASH_IN_FILENAME
-static void did_set_completeslash(buf_T *buf, char **errmsg)
-{
- if (check_opt_strings(p_csl, p_csl_values, false) != OK
- || check_opt_strings(buf->b_p_csl, p_csl_values, false) != OK) {
- *errmsg = e_invarg;
- }
+ return errmsg;
}
-#endif
-static void did_set_signcolumn(win_T *win, char **varp, const char *oldval, char **errmsg)
+/// The 'switchbuf' option is changed.
+const char *did_set_switchbuf(optset_T *args FUNC_ATTR_UNUSED)
{
- if (check_signcolumn(*varp) != OK) {
- *errmsg = e_invarg;
- }
- // When changing the 'signcolumn' to or from 'number', recompute the
- // width of the number column if 'number' or 'relativenumber' is set.
- if (((*oldval == 'n' && *(oldval + 1) == 'u')
- || (*win->w_p_scl == 'n' && *(win->w_p_scl + 1) == 'u'))
- && (win->w_p_nu || win->w_p_rnu)) {
- win->w_nrwidth_line_count = 0;
- }
+ return did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true);
}
-static void did_set_foldcolumn(char **varp, char **errmsg)
+int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches)
{
- if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) {
- *errmsg = e_invarg;
- }
+ return expand_set_opt_string(args,
+ p_swb_values,
+ ARRAY_SIZE(p_swb_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_pastetoggle(void)
+/// The 'tabline' option is changed.
+const char *did_set_tabline(optset_T *args)
{
- // 'pastetoggle': translate key codes like in a mapping
- if (*p_pt) {
- char *p = NULL;
- (void)replace_termcodes(p_pt,
- strlen(p_pt),
- &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
- CPO_TO_CPO_FLAGS);
- if (p != NULL) {
- free_string_option(p_pt);
- p_pt = p;
- }
- }
+ return did_set_statustabline_rulerformat(args, false, false);
}
-static void did_set_backspace(char **errmsg)
+/// The 'tagcase' option is changed.
+const char *did_set_tagcase(optset_T *args)
{
- if (ascii_isdigit(*p_bs)) {
- if (*p_bs > '3' || p_bs[1] != NUL) {
- *errmsg = e_invarg;
- }
- } else if (check_opt_strings(p_bs, p_bs_values, true) != OK) {
- *errmsg = e_invarg;
- }
-}
+ buf_T *buf = (buf_T *)args->os_buf;
+ int opt_flags = args->os_flags;
-static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg)
-{
- unsigned int *flags;
+ unsigned *flags;
char *p;
if (opt_flags & OPT_LOCAL) {
@@ -1348,116 +2294,66 @@ static void did_set_tagcase(buf_T *buf, int opt_flags, char **errmsg)
*flags = 0;
} else if (*p == NUL
|| opt_strings_flags(p, p_tc_values, flags, false) != OK) {
- *errmsg = e_invarg;
- }
-}
-
-static void did_set_diffopt(char **errmsg)
-{
- if (diffopt_changed() == FAIL) {
- *errmsg = e_invarg;
- }
-}
-
-static void did_set_foldmethod(win_T *win, char **varp, char **errmsg)
-{
- if (check_opt_strings(*varp, p_fdm_values, false) != OK
- || *win->w_p_fdm == NUL) {
- *errmsg = e_invarg;
- } else {
- foldUpdateAll(win);
- if (foldmethodIsDiff(win)) {
- newFoldLevel();
- }
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_foldmarker(win_T *win, char **varp, char **errmsg)
+int expand_set_tagcase(optexpand_T *args, int *numMatches, char ***matches)
{
- char *p = vim_strchr(*varp, ',');
- if (p == NULL) {
- *errmsg = N_("E536: comma required");
- } else if (p == *varp || p[1] == NUL) {
- *errmsg = e_invarg;
- } else if (foldmethodIsMarker(win)) {
- foldUpdateAll(win);
- }
+ return expand_set_opt_string(args,
+ p_tc_values,
+ ARRAY_SIZE(p_tc_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_commentstring(char **varp, char **errmsg)
+/// The 'termpastefilter' option is changed.
+const char *did_set_termpastefilter(optset_T *args FUNC_ATTR_UNUSED)
{
- if (**varp != NUL && strstr(*varp, "%s") == NULL) {
- *errmsg = N_("E537: 'commentstring' must be empty or contain %s");
- }
+ return did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true);
}
-static void did_set_foldignore(win_T *win)
+int expand_set_termpastefilter(optexpand_T *args, int *numMatches, char ***matches)
{
- if (foldmethodIsIndent(win)) {
- foldUpdateAll(win);
- }
+ return expand_set_opt_string(args,
+ p_tpf_values,
+ ARRAY_SIZE(p_tpf_values) - 1,
+ numMatches,
+ matches);
}
-static void did_set_virtualedit(win_T *win, int opt_flags, char *oldval, char **errmsg)
+/// The 'titlestring' or the 'iconstring' option is changed.
+static const char *did_set_titleiconstring(optset_T *args, int flagval)
{
- char *ve = p_ve;
- unsigned int *flags = &ve_flags;
+ char **varp = (char **)args->os_varp;
- if (opt_flags & OPT_LOCAL) {
- ve = win->w_p_ve;
- flags = &win->w_ve_flags;
- }
-
- if ((opt_flags & OPT_LOCAL) && *ve == NUL) {
- // make the local value empty: use the global value
- *flags = 0;
+ // NULL => statusline syntax
+ if (vim_strchr(*varp, '%') && check_stl_option(*varp) == NULL) {
+ stl_syntax |= flagval;
} else {
- if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
- *errmsg = e_invarg;
- } else if (strcmp(ve, oldval) != 0) {
- // Recompute cursor position in case the new 've' setting
- // changes something.
- validate_virtcol_win(win);
- // XXX: this only works when win == curwin
- coladvance(win->w_virtcol);
- }
+ stl_syntax &= ~flagval;
}
-}
+ did_set_title();
-static void did_set_lispoptions(char **varp, char **errmsg)
-{
- if (**varp != NUL && strcmp(*varp, "expr:0") != 0 && strcmp(*varp, "expr:1") != 0) {
- *errmsg = e_invarg;
- }
+ return NULL;
}
-static void did_set_filetype_or_syntax(char **varp, char *oldval, int *value_checked,
- bool *value_changed, char **errmsg)
+/// The 'titlestring' option is changed.
+const char *did_set_titlestring(optset_T *args)
{
- if (!valid_filetype(*varp)) {
- *errmsg = e_invarg;
- return;
- }
-
- *value_changed = strcmp(oldval, *varp) != 0;
-
- // Since we check the value, there is no need to set P_INSECURE,
- // even when the value comes from a modeline.
- *value_checked = true;
+ return did_set_titleiconstring(args, STL_IN_TITLE);
}
-static void did_set_winhl(win_T *win, char **errmsg)
+/// The 'varsofttabstop' option is changed.
+const char *did_set_varsofttabstop(optset_T *args)
{
- if (!parse_winhl_opt(win)) {
- *errmsg = e_invarg;
- }
-}
+ buf_T *buf = (buf_T *)args->os_buf;
+ char **varp = (char **)args->os_varp;
-static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg)
-{
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
XFREE_CLEAR(buf->b_p_vsts_array);
- return;
+ return NULL;
}
for (char *cp = *varp; *cp; cp++) {
@@ -1467,23 +2363,28 @@ static void did_set_varsoftabstop(buf_T *buf, char **varp, char **errmsg)
if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
continue;
}
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
- long *oldarray = buf->b_p_vsts_array;
+ colnr_T *oldarray = buf->b_p_vsts_array;
if (tabstop_set(*varp, &(buf->b_p_vsts_array))) {
xfree(oldarray);
} else {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errmsg)
+/// The 'varstabstop' option is changed.
+const char *did_set_vartabstop(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
+ win_T *win = (win_T *)args->os_win;
+ char **varp = (char **)args->os_varp;
+
if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) {
XFREE_CLEAR(buf->b_p_vts_array);
- return;
+ return NULL;
}
for (char *cp = *varp; *cp; cp++) {
@@ -1493,436 +2394,161 @@ static void did_set_vartabstop(buf_T *buf, win_T *win, char **varp, char **errms
if (*cp == ',' && cp > *varp && *(cp - 1) != ',') {
continue;
}
- *errmsg = e_invarg;
- return;
+ return e_invarg;
}
- long *oldarray = buf->b_p_vts_array;
+ colnr_T *oldarray = buf->b_p_vts_array;
if (tabstop_set(*varp, &(buf->b_p_vts_array))) {
xfree(oldarray);
if (foldmethodIsIndent(win)) {
foldUpdateAll(win);
}
} else {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
-static void did_set_optexpr(char **varp)
+/// The 'verbosefile' option is changed.
+const char *did_set_verbosefile(optset_T *args)
{
- char *name = get_scriptlocal_funcname(*varp);
- if (name != NULL) {
- free_string_option(*varp);
- *varp = name;
+ verbose_stop();
+ if (*p_vfile != NUL && verbose_open() == FAIL) {
+ return (char *)e_invarg;
}
+ return NULL;
}
-// handle option that is a list of flags.
-static void did_set_option_listflag(char **varp, char *flags, char *errbuf, size_t errbuflen,
- char **errmsg)
+/// The 'viewoptions' option is changed.
+const char *did_set_viewoptions(optset_T *args FUNC_ATTR_UNUSED)
{
- for (char *s = *varp; *s; s++) {
- if (vim_strchr(flags, (uint8_t)(*s)) == NULL) {
- *errmsg = illegal_char(errbuf, errbuflen, (uint8_t)(*s));
- break;
- }
- }
+ return did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true);
}
-// When 'syntax' is set, load the syntax of that name
-static void do_syntax_autocmd(buf_T *buf, bool value_changed)
+/// The 'virtualedit' option is changed.
+const char *did_set_virtualedit(optset_T *args)
{
- static int syn_recursive = 0;
+ win_T *win = (win_T *)args->os_win;
- syn_recursive++;
- // Only pass true for "force" when the value changed or not used
- // recursively, to avoid endless recurrence.
- apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname,
- value_changed || syn_recursive == 1, buf);
- buf->b_flags |= BF_SYN_SET;
- syn_recursive--;
-}
-
-static void do_filetype_autocmd(buf_T *buf, char **varp, int opt_flags, bool value_changed)
-{
- // 'filetype' is set, trigger the FileType autocommand
- // Skip this when called from a modeline and the filetype was
- // already set to this value.
- if (!(opt_flags & OPT_MODELINE) || value_changed) {
- static int ft_recursive = 0;
- int secure_save = secure;
+ char *ve = p_ve;
+ unsigned *flags = &ve_flags;
- // Reset the secure flag, since the value of 'filetype' has
- // been checked to be safe.
- secure = 0;
+ if (args->os_flags & OPT_LOCAL) {
+ ve = win->w_p_ve;
+ flags = &win->w_ve_flags;
+ }
- ft_recursive++;
- did_filetype = true;
- // Only pass true for "force" when the value changed or not
- // used recursively, to avoid endless recurrence.
- apply_autocmds(EVENT_FILETYPE, buf->b_p_ft, buf->b_fname,
- value_changed || ft_recursive == 1, buf);
- ft_recursive--;
- // Just in case the old "buf" is now invalid
- if (varp != &(buf->b_p_ft)) {
- varp = NULL;
+ if ((args->os_flags & OPT_LOCAL) && *ve == NUL) {
+ // make the local value empty: use the global value
+ *flags = 0;
+ } else {
+ if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) {
+ return e_invarg;
+ } else if (strcmp(ve, args->os_oldval.string.data) != 0) {
+ // Recompute cursor position in case the new 've' setting
+ // changes something.
+ validate_virtcol_win(win);
+ // XXX: this only works when win == curwin
+ coladvance(win->w_virtcol);
}
- secure = secure_save;
}
+ return NULL;
}
-static void do_spelllang_source(win_T *win)
+int expand_set_virtualedit(optexpand_T *args, int *numMatches, char ***matches)
{
- char fname[200];
- char *q = win->w_s->b_p_spl;
+ return expand_set_opt_string(args,
+ p_ve_values,
+ ARRAY_SIZE(p_ve_values) - 1,
+ numMatches,
+ matches);
+}
- // Skip the first name if it is "cjk".
- if (strncmp(q, "cjk,", 4) == 0) {
- q += 4;
- }
+/// The 'whichwrap' option is changed.
+const char *did_set_whichwrap(optset_T *args)
+{
+ char **varp = (char **)args->os_varp;
- // Source the spell/LANG.vim in 'runtimepath'.
- // They could set 'spellcapcheck' depending on the language.
- // Use the first name in 'spelllang' up to '_region' or
- // '.encoding'.
- char *p;
- for (p = q; *p != NUL; p++) {
- if (!ASCII_ISALNUM(*p) && *p != '-') {
- break;
- }
- }
- if (p > q) {
- vim_snprintf(fname, sizeof(fname), "spell/%.*s.vim", (int)(p - q), q);
- source_runtime(fname, DIP_ALL);
- }
+ // Add ',' to the list flags because 'whichwrap' is a flag
+ // list that is comma-separated.
+ return did_set_option_listflag(*varp, WW_ALL ",", args->os_errbuf, args->os_errbuflen);
}
-/// Handle string options that need some action to perform when changed.
-/// The new value must be allocated.
-///
-/// @param opt_idx index in options[] table
-/// @param varp pointer to the option variable
-/// @param oldval previous value of the option
-/// @param errbuf buffer for errors, or NULL
-/// @param errbuflen length of errors buffer
-/// @param opt_flags OPT_LOCAL and/or OPT_GLOBAL
-/// @param value_checked value was checked to be safe, no need to set P_INSECURE
-///
-/// @return NULL for success, or an untranslated error message for an error
-static char *did_set_string_option_for(buf_T *buf, win_T *win, int opt_idx, char **varp,
- char *oldval, char *errbuf, size_t errbuflen, int opt_flags,
- int *value_checked)
+int expand_set_whichwrap(optexpand_T *args, int *numMatches, char ***matches)
{
- char *errmsg = NULL;
- bool did_chartab = false;
- vimoption_T *opt = get_option(opt_idx);
- bool free_oldval = (opt->flags & P_ALLOCED);
- bool value_changed = false;
+ return expand_set_opt_listflag(args, WW_ALL, numMatches, matches);
+}
- // Get the global option to compare with, otherwise we would have to check
- // two values for all local options.
- char **gvarp = (char **)get_varp_scope(opt, OPT_GLOBAL);
-
- // Disallow changing some options from secure mode
- if ((secure || sandbox != 0) && (opt->flags & P_SECURE)) {
- errmsg = e_secure;
- // Check for a "normal" directory or file name in some options.
- } else if (check_illegal_path_names(*varp, opt->flags)) {
- errmsg = e_invarg;
- } else if (gvarp == &p_bkc) { // 'backupcopy'
- did_set_backupcopy(buf, oldval, opt_flags, &errmsg);
- } else if (varp == &p_bex // 'backupext'
- || varp == &p_pm) { // 'patchmode'
- did_set_backupext_or_patchmode(&errmsg);
- } else if (varp == &win->w_p_briopt) { // 'breakindentopt'
- did_set_breakindentopt(win, &errmsg);
- } else if (varp == &p_isi // 'isident'
- || varp == &buf->b_p_isk // 'iskeyword'
- || varp == &p_isp // 'isprint'
- || varp == &p_isf) { // 'isfname'
- did_set_isopt(buf, &did_chartab, &errmsg);
- } else if (varp == &p_hf) { // 'helpfile'
- did_set_helpfile();
- } else if (varp == &p_rtp // 'runtimepath'
- || varp == &p_pp) { // 'packpath'
- runtime_search_path_invalidate();
- } else if (gvarp == &win->w_allbuf_opt.wo_culopt) { // 'cursorlineopt'
- did_set_cursorlineopt(win, varp, &errmsg);
- } else if (varp == &win->w_p_cc) { // 'colorcolumn'
- errmsg = check_colorcolumn(win);
- } else if (varp == &p_hlg) { // 'helplang'
- did_set_helplang(&errmsg);
- } else if (varp == &p_hl) { // 'highlight'
- did_set_highlight(varp, &errmsg);
- } else if (varp == &p_jop) { // 'jumpoptions'
- did_set_opt_flags(p_jop, p_jop_values, &jop_flags, true, &errmsg);
- } else if (gvarp == &p_nf) { // 'nrformats'
- did_set_opt_strings(*varp, p_nf_values, true, &errmsg);
- } else if (varp == &p_ssop) { // 'sessionoptions'
- did_set_sessionoptions(oldval, &errmsg);
- } else if (varp == &p_vop) { // 'viewoptions'
- did_set_opt_flags(p_vop, p_ssop_values, &vop_flags, true, &errmsg);
- } else if (varp == &p_rdb) { // 'redrawdebug'
- did_set_opt_flags(p_rdb, p_rdb_values, &rdb_flags, true, &errmsg);
- } else if (varp == &p_sbo) { // 'scrollopt'
- did_set_opt_strings(p_sbo, p_scbopt_values, true, &errmsg);
- } else if (varp == &p_ambw // 'ambiwidth'
- || (int *)varp == &p_emoji) { // 'emoji'
- did_set_ambiwidth(&errmsg);
- } else if (varp == &p_bg) { // 'background'
- did_set_background(&errmsg);
- } else if (varp == &p_wim) { // 'wildmode'
- did_set_wildmode(&errmsg);
- } else if (varp == &p_wop) { // 'wildoptions'
- did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true, &errmsg);
- } else if (varp == &p_wak) { // 'winaltkeys'
- did_set_winaltkeys(&errmsg);
- } else if (varp == &p_ei) { // 'eventignore'
- did_set_eventignore(&errmsg);
- } else if (varp == &p_enc // 'encoding'
- || gvarp == &p_fenc // 'fileencoding'
- || gvarp == &p_menc) { // 'makeencoding'
- did_set_encoding(buf, varp, gvarp, opt_flags, &errmsg);
- } else if (varp == &buf->b_p_keymap) { // 'keymap'
- did_set_keymap(buf, varp, opt_flags, value_checked, &errmsg);
- } else if (gvarp == &p_ff) { // 'fileformat'
- did_set_fileformat(buf, varp, oldval, opt_flags, &errmsg);
- } else if (varp == &p_ffs) { // 'fileformats'
- did_set_opt_strings(p_ffs, p_ff_values, true, &errmsg);
- } else if (gvarp == &p_mps) { // 'matchpairs'
- did_set_matchpairs(varp, &errmsg);
- } else if (gvarp == &p_com) { // 'comments'
- did_set_comments(varp, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_lcs // global 'listchars'
- || varp == &p_fcs) { // global 'fillchars'
- did_set_global_listfillchars(win, varp, opt_flags, &errmsg);
- } else if (varp == &win->w_p_lcs) { // local 'listchars'
- errmsg = set_chars_option(win, varp, true);
- } else if (varp == &win->w_p_fcs) { // local 'fillchars'
- errmsg = set_chars_option(win, varp, true);
- } else if (varp == &p_cedit) { // 'cedit'
- errmsg = check_cedit();
- } else if (varp == &p_vfile) { // 'verbosefile'
- did_set_verbosefile(&errmsg);
- } else if (varp == &p_shada) { // 'shada'
- did_set_shada(&opt, &opt_idx, &free_oldval, errbuf, errbuflen, &errmsg);
- } else if (gvarp == &p_sbr) { // 'showbreak'
- did_set_showbreak(varp, &errmsg);
- } else if (varp == &p_guicursor) { // 'guicursor'
- errmsg = parse_shape_opt(SHAPE_CURSOR);
- } else if (varp == &p_langmap) { // 'langmap'
- langmap_set();
- } else if (varp == &p_breakat) { // 'breakat'
- fill_breakat_flags();
- } else if (varp == &p_titlestring // 'titlestring'
- || varp == &p_iconstring) { // 'iconstring'
- did_set_titleiconstring(varp);
- } else if (varp == &p_sel) { // 'selection'
- did_set_selection(&errmsg);
- } else if (varp == &p_slm) { // 'selectmode'
- did_set_opt_strings(p_slm, p_slm_values, true, &errmsg);
- } else if (varp == &p_km) { // 'keymodel'
- did_set_keymodel(&errmsg);
- } else if (varp == &p_mousem) { // 'mousemodel'
- did_set_opt_strings(p_mousem, p_mousem_values, false, &errmsg);
- } else if (varp == &p_mousescroll) { // 'mousescroll'
- errmsg = check_mousescroll(p_mousescroll);
- } else if (varp == &p_swb) { // 'switchbuf'
- did_set_opt_flags(p_swb, p_swb_values, &swb_flags, true, &errmsg);
- } else if (varp == &p_spk) { // 'splitkeep'
- did_set_opt_strings(p_spk, p_spk_values, false, &errmsg);
- } else if (varp == &p_debug) { // 'debug'
- did_set_opt_strings(p_debug, p_debug_values, true, &errmsg);
- } else if (varp == &p_dy) { // 'display'
- did_set_display(&errmsg);
- } else if (varp == &p_ead) { // 'eadirection'
- did_set_opt_strings(p_ead, p_ead_values, false, &errmsg);
- } else if (varp == &p_cb) { // 'clipboard'
- did_set_opt_flags(p_cb, p_cb_values, &cb_flags, true, &errmsg);
- } else if (varp == &win->w_s->b_p_spf) { // 'spellfile'
- did_set_spellfile(varp, &errmsg);
- } else if (varp == &win->w_s->b_p_spl) { // 'spell'
- did_set_spell(varp, &errmsg);
- } else if (varp == &win->w_s->b_p_spc) { // 'spellcapcheck'
- did_set_spellcapcheck(win, &errmsg);
- } else if (varp == &win->w_s->b_p_spo) { // 'spelloptions'
- did_set_spelloptions(win, &errmsg);
- } else if (varp == &p_sps) { // 'spellsuggest'
- did_set_spellsuggest(&errmsg);
- } else if (varp == &p_msm) { // 'mkspellmem'
- did_set_mkspellmem(&errmsg);
- } else if (gvarp == &p_bh) { // 'bufhidden'
- did_set_opt_strings(buf->b_p_bh, p_bufhidden_values, false, &errmsg);
- } else if (gvarp == &p_bt) { // 'buftype'
- did_set_buftype(buf, win, &errmsg);
- } else if (gvarp == &p_stl // 'statusline'
- || gvarp == &p_wbr // 'winbar'
- || varp == &p_tal // 'tabline'
- || varp == &p_ruf // 'rulerformat'
- || varp == &win->w_p_stc) { // 'statuscolumn'
- did_set_statusline(win, varp, gvarp, &errmsg);
- } else if (gvarp == &p_cpt) { // 'complete'
- did_set_complete(varp, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_cot) { // 'completeopt'
- did_set_completeopt(&errmsg);
-#ifdef BACKSLASH_IN_FILENAME
- } else if (gvarp == &p_csl) { // 'completeslash'
- did_set_completeslash(buf, &errmsg);
-#endif
- } else if (varp == &win->w_p_scl) { // 'signcolumn'
- did_set_signcolumn(win, varp, oldval, &errmsg);
- } else if (varp == &p_sloc) { // 'showcmdloc'
- did_set_opt_strings(*varp, p_sloc_values, false, &errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fdc) { // 'foldcolumn'
- did_set_foldcolumn(varp, &errmsg);
- } else if (varp == &p_pt) { // 'pastetoggle'
- did_set_pastetoggle();
- } else if (varp == &p_bs) { // 'backspace'
- did_set_backspace(&errmsg);
- } else if (varp == &p_bo) {
- did_set_opt_flags(p_bo, p_bo_values, &bo_flags, true, &errmsg);
- } else if (gvarp == &p_tc) { // 'tagcase'
- did_set_tagcase(buf, opt_flags, &errmsg);
- } else if (varp == &p_cmp) { // 'casemap'
- did_set_opt_flags(p_cmp, p_cmp_values, &cmp_flags, true, &errmsg);
- } else if (varp == &p_dip) { // 'diffopt'
- did_set_diffopt(&errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fdm) { // 'foldmethod'
- did_set_foldmethod(win, varp, &errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fmr) { // 'foldmarker'
- did_set_foldmarker(win, varp, &errmsg);
- } else if (gvarp == &p_cms) { // 'commentstring'
- did_set_commentstring(varp, &errmsg);
- } else if (varp == &p_fdo) { // 'foldopen'
- did_set_opt_flags(p_fdo, p_fdo_values, &fdo_flags, true, &errmsg);
- } else if (varp == &p_fcl) { // 'foldclose'
- did_set_opt_strings(*varp, p_fcl_values, true, &errmsg);
- } else if (gvarp == &win->w_allbuf_opt.wo_fdi) { // 'foldignore'
- did_set_foldignore(win);
- } else if (gvarp == &p_ve) { // 'virtualedit'
- did_set_virtualedit(win, opt_flags, oldval, &errmsg);
- } else if (gvarp == &p_cino) { // 'cinoptions'
- // TODO(vim): recognize errors
- parse_cino(buf);
- } else if (gvarp == &p_lop) { // 'lispoptions'
- did_set_lispoptions(varp, &errmsg);
- } else if (varp == &p_icm) { // 'inccommand'
- did_set_opt_strings(*varp, p_icm_values, false, &errmsg);
- } else if (gvarp == &p_ft // 'filetype'
- || gvarp == &p_syn) { // 'syntax'
- did_set_filetype_or_syntax(varp, oldval, value_checked, &value_changed, &errmsg);
- } else if (varp == &win->w_p_winhl) { // 'winhighlight'
- did_set_winhl(win, &errmsg);
- } else if (varp == &p_tpf) {
- did_set_opt_flags(p_tpf, p_tpf_values, &tpf_flags, true, &errmsg);
- } else if (varp == &buf->b_p_vsts) { // 'varsofttabstop'
- did_set_varsoftabstop(buf, varp, &errmsg);
- } else if (varp == &buf->b_p_vts) { // 'vartabstop'
- did_set_vartabstop(buf, win, varp, &errmsg);
- } else if (varp == &p_dex // 'diffexpr'
- || gvarp == &win->w_allbuf_opt.wo_fde // 'foldexpr'
- || gvarp == &win->w_allbuf_opt.wo_fdt // 'foldtext'
- || gvarp == &p_fex // 'formatexpr'
- || gvarp == &p_inex // 'includeexpr'
- || gvarp == &p_inde // 'indentexpr'
- || varp == &p_pex // 'patchexpr'
- || varp == &p_ccv) { // 'charconvert'
- did_set_optexpr(varp);
- if (varp == &win->w_p_fde && foldmethodIsExpr(win)) {
- foldUpdateAll(win);
- }
- } else if (gvarp == &p_cfu) { // 'completefunc'
- set_completefunc_option(&errmsg);
- } else if (gvarp == &p_ofu) { // 'omnifunc'
- set_omnifunc_option(buf, &errmsg);
- } else if (gvarp == &p_tsrfu) { // 'thesaurusfunc'
- set_thesaurusfunc_option(&errmsg);
- } else if (varp == &p_opfunc) { // 'operatorfunc'
- set_operatorfunc_option(&errmsg);
- } else if (varp == &p_qftf) { // 'quickfixtextfunc'
- qf_process_qftf_option(&errmsg);
- } else if (gvarp == &p_tfu) { // 'tagfunc'
- set_tagfunc_option(&errmsg);
- } else if (varp == &p_ww) { // 'whichwrap'
- did_set_option_listflag(varp, WW_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_shm) { // 'shortmess'
- did_set_option_listflag(varp, SHM_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_cpo) { // 'cpoptions'
- did_set_option_listflag(varp, CPO_VI, errbuf, errbuflen, &errmsg);
- } else if (varp == &buf->b_p_fo) { // 'formatoptions'
- did_set_option_listflag(varp, FO_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &win->w_p_cocu) { // 'concealcursor'
- did_set_option_listflag(varp, COCU_ALL, errbuf, errbuflen, &errmsg);
- } else if (varp == &p_mouse) { // 'mouse'
- did_set_option_listflag(varp, MOUSE_ALL, errbuf, errbuflen, &errmsg);
- } else if (gvarp == &p_flp) { // 'formatlistpat'
- if (win->w_briopt_list) {
- // Changing Formatlistpattern when briopt includes the list setting:
- // redraw
- redraw_all_later(UPD_NOT_VALID);
- }
- }
-
- // If an error is detected, restore the previous value.
- if (errmsg != NULL) {
- free_string_option(*varp);
- *varp = oldval;
- // When resetting some values, need to act on it.
- if (did_chartab) {
- (void)buf_init_chartab(buf, true);
- }
- } else {
- // Remember where the option was set.
- set_option_sctx_idx(opt_idx, opt_flags, current_sctx);
- // Free string options that are in allocated memory.
- // Use "free_oldval", because recursiveness may change the flags under
- // our fingers (esp. init_highlight()).
- if (free_oldval) {
- free_string_option(oldval);
- }
- opt->flags |= P_ALLOCED;
+/// The 'wildmode' option is changed.
+const char *did_set_wildmode(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (check_opt_wim() == FAIL) {
+ return e_invarg;
+ }
+ return NULL;
+}
- if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0
- && (opt->indir & PV_BOTH)) {
- // global option with local value set to use global value; free
- // the local value and make it empty
- char *p = get_varp_scope(opt, OPT_LOCAL);
- free_string_option(*(char **)p);
- *(char **)p = empty_option;
- } else if (!(opt_flags & OPT_LOCAL) && opt_flags != OPT_GLOBAL) {
- // May set global value for local option.
- set_string_option_global(opt, varp);
- }
+int expand_set_wildmode(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wim_values,
+ ARRAY_SIZE(p_wim_values) - 1,
+ numMatches,
+ matches);
+}
- // Trigger the autocommand only after setting the flags.
- if (varp == &buf->b_p_syn) {
- do_syntax_autocmd(buf, value_changed);
- } else if (varp == &buf->b_p_ft) {
- do_filetype_autocmd(buf, varp, opt_flags, value_changed);
- } else if (varp == &win->w_s->b_p_spl) {
- do_spelllang_source(win);
- }
- }
+/// The 'wildoptions' option is changed.
+const char *did_set_wildoptions(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_wop, p_wop_values, &wop_flags, true);
+}
- if (varp == &p_mouse) {
- setmouse(); // in case 'mouse' changed
- }
+int expand_set_wildoptions(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wop_values,
+ ARRAY_SIZE(p_wop_values) - 1,
+ numMatches,
+ matches);
+}
- if (win->w_curswant != MAXCOL
- && (opt->flags & (P_CURSWANT | P_RALL)) != 0) {
- win->w_set_curswant = true;
+/// The 'winaltkeys' option is changed.
+const char *did_set_winaltkeys(optset_T *args FUNC_ATTR_UNUSED)
+{
+ if (*p_wak == NUL || check_opt_strings(p_wak, p_wak_values, false) != OK) {
+ return e_invarg;
}
+ return NULL;
+}
- check_redraw_for(buf, win, opt->flags);
+int expand_set_winaltkeys(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_wak_values,
+ ARRAY_SIZE(p_wak_values) - 1,
+ numMatches,
+ matches);
+}
- return errmsg;
+/// The 'winbar' option is changed.
+const char *did_set_winbar(optset_T *args)
+{
+ return did_set_statustabline_rulerformat(args, false, false);
+}
+
+/// The 'winhighlight' option is changed.
+const char *did_set_winhighlight(optset_T *args)
+{
+ win_T *win = (win_T *)args->os_win;
+ if (!parse_winhl_opt(win)) {
+ return e_invarg;
+ }
+ return NULL;
}
-char *did_set_string_option(int opt_idx, char **varp, char *oldval, char *errbuf, size_t errbuflen,
- int opt_flags, int *value_checked)
+int expand_set_winhighlight(optexpand_T *args, int *numMatches, char ***matches)
{
- return did_set_string_option_for(curbuf, curwin, opt_idx, varp, oldval, errbuf, errbuflen,
- opt_flags, value_checked);
+ return expand_set_opt_generic(args, get_highlight_name, numMatches, matches);
}
/// Check an option that can be a range of string values.
@@ -1943,12 +2569,12 @@ static int check_opt_strings(char *val, char **values, int list)
/// @param list when true: accept a list of values
///
/// @return OK for correct value, FAIL otherwise. Empty is always OK.
-static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool list)
+static int opt_strings_flags(const char *val, char **values, unsigned *flagp, bool list)
{
- unsigned int new_flags = 0;
+ unsigned new_flags = 0;
while (*val) {
- for (unsigned int i = 0;; i++) {
+ for (unsigned i = 0;; i++) {
if (values[i] == NULL) { // val not found in values[]
return FAIL;
}
@@ -1957,7 +2583,7 @@ static int opt_strings_flags(char *val, char **values, unsigned *flagp, bool lis
if (strncmp(values[i], val, len) == 0
&& ((list && val[len] == ',') || val[len] == NUL)) {
val += len + (val[len] == ',');
- assert(i < sizeof(1U) * 8);
+ assert(i < sizeof(new_flags) * 8);
new_flags |= (1U << i);
break; // check next item in val list
}
@@ -1975,3 +2601,335 @@ int check_ff_value(char *p)
{
return check_opt_strings(p, p_ff_values, false);
}
+
+static const char e_conflicts_with_value_of_listchars[]
+ = N_("E834: Conflicts with value of 'listchars'");
+static const char e_conflicts_with_value_of_fillchars[]
+ = N_("E835: Conflicts with value of 'fillchars'");
+
+/// Calls mb_cptr2char_adv(p) and returns the character.
+/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
+/// Returns 0 for invalid hex or invalid UTF-8 byte.
+static int get_encoded_char_adv(const char **p)
+{
+ const char *s = *p;
+
+ if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
+ int64_t num = 0;
+ for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
+ *p += 2;
+ int n = hexhex2nr(*p);
+ if (n < 0) {
+ return 0;
+ }
+ num = num * 256 + n;
+ }
+ *p += 2;
+ return (int)num;
+ }
+
+ // TODO(bfredl): use schar_T representation and utfc_ptr2len
+ int clen = utf_ptr2len(s);
+ int c = mb_cptr2char_adv(p);
+ if (clen == 1 && c > 127) { // Invalid UTF-8 byte
+ return 0;
+ }
+ return c;
+}
+
+struct chars_tab {
+ int *cp; ///< char value
+ const char *name; ///< char id
+ int def; ///< default value
+ int fallback; ///< default value when "def" isn't single-width
+};
+
+static fcs_chars_T fcs_chars;
+static const struct chars_tab fcs_tab[] = {
+ { &fcs_chars.stl, "stl", ' ', NUL },
+ { &fcs_chars.stlnc, "stlnc", ' ', NUL },
+ { &fcs_chars.wbr, "wbr", ' ', NUL },
+ { &fcs_chars.horiz, "horiz", 0x2500, '-' }, // ─
+ { &fcs_chars.horizup, "horizup", 0x2534, '-' }, // ┴
+ { &fcs_chars.horizdown, "horizdown", 0x252c, '-' }, // ┬
+ { &fcs_chars.vert, "vert", 0x2502, '|' }, // │
+ { &fcs_chars.vertleft, "vertleft", 0x2524, '|' }, // ┤
+ { &fcs_chars.vertright, "vertright", 0x251c, '|' }, // ├
+ { &fcs_chars.verthoriz, "verthoriz", 0x253c, '+' }, // ┼
+ { &fcs_chars.fold, "fold", 0x00b7, '-' }, // ·
+ { &fcs_chars.foldopen, "foldopen", '-', NUL },
+ { &fcs_chars.foldclosed, "foldclose", '+', NUL },
+ { &fcs_chars.foldsep, "foldsep", 0x2502, '|' }, // │
+ { &fcs_chars.diff, "diff", '-', NUL },
+ { &fcs_chars.msgsep, "msgsep", ' ', NUL },
+ { &fcs_chars.eob, "eob", '~', NUL },
+ { &fcs_chars.lastline, "lastline", '@', NUL },
+};
+
+static lcs_chars_T lcs_chars;
+static const struct chars_tab lcs_tab[] = {
+ { &lcs_chars.eol, "eol", NUL, NUL },
+ { &lcs_chars.ext, "extends", NUL, NUL },
+ { &lcs_chars.nbsp, "nbsp", NUL, NUL },
+ { &lcs_chars.prec, "precedes", NUL, NUL },
+ { &lcs_chars.space, "space", NUL, NUL },
+ { &lcs_chars.tab2, "tab", NUL, NUL },
+ { &lcs_chars.lead, "lead", NUL, NUL },
+ { &lcs_chars.trail, "trail", NUL, NUL },
+ { &lcs_chars.conceal, "conceal", NUL, NUL },
+ { NULL, "multispace", NUL, NUL },
+ { NULL, "leadmultispace", NUL, NUL },
+};
+
+/// Handle setting 'listchars' or 'fillchars'.
+/// Assume monocell characters
+///
+/// @param value points to either the global or the window-local value.
+/// @param is_listchars is true for "listchars" and false for "fillchars".
+/// @param apply if false, do not store the flags, only check for errors.
+/// @return error message, NULL if it's OK.
+static const char *set_chars_option(win_T *wp, const char *value, const bool is_listchars,
+ const bool apply)
+{
+ const char *last_multispace = NULL; // Last occurrence of "multispace:"
+ const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
+ int multispace_len = 0; // Length of lcs-multispace string
+ int lead_multispace_len = 0; // Length of lcs-leadmultispace string
+
+ const struct chars_tab *tab;
+ int entries;
+ if (is_listchars) {
+ tab = lcs_tab;
+ entries = ARRAY_SIZE(lcs_tab);
+ if (wp->w_p_lcs[0] == NUL) {
+ value = p_lcs; // local value is empty, use the global value
+ }
+ } else {
+ tab = fcs_tab;
+ entries = ARRAY_SIZE(fcs_tab);
+ if (wp->w_p_fcs[0] == NUL) {
+ value = p_fcs; // local value is empty, use the global value
+ }
+ }
+
+ // first round: check for valid value, second round: assign values
+ for (int round = 0; round <= (apply ? 1 : 0); round++) {
+ if (round > 0) {
+ // After checking that the value is valid: set defaults
+ for (int i = 0; i < entries; i++) {
+ if (tab[i].cp != NULL) {
+ // XXX: Characters taking 2 columns is forbidden (TUI limitation?).
+ // Set old defaults in this case.
+ *(tab[i].cp) = char2cells(tab[i].def) == 1 ? tab[i].def : tab[i].fallback;
+ }
+ }
+
+ if (is_listchars) {
+ lcs_chars.tab1 = NUL;
+ lcs_chars.tab3 = NUL;
+
+ if (multispace_len > 0) {
+ lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
+ lcs_chars.multispace[multispace_len] = NUL;
+ } else {
+ lcs_chars.multispace = NULL;
+ }
+
+ if (lead_multispace_len > 0) {
+ lcs_chars.leadmultispace = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
+ lcs_chars.leadmultispace[lead_multispace_len] = NUL;
+ } else {
+ lcs_chars.leadmultispace = NULL;
+ }
+ }
+ }
+
+ const char *p = value;
+ while (*p) {
+ int i;
+ for (i = 0; i < entries; i++) {
+ const size_t len = strlen(tab[i].name);
+ if (!(strncmp(p, tab[i].name, len) == 0
+ && p[len] == ':'
+ && p[len + 1] != NUL)) {
+ continue;
+ }
+
+ if (is_listchars && strcmp(tab[i].name, "multispace") == 0) {
+ const char *s = p + len + 1;
+ if (round == 0) {
+ // Get length of lcs-multispace string in the first round
+ last_multispace = p;
+ multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ multispace_len++;
+ }
+ if (multispace_len == 0) {
+ // lcs-multispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (p == last_multispace) {
+ lcs_chars.multispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ break;
+ }
+
+ if (is_listchars && strcmp(tab[i].name, "leadmultispace") == 0) {
+ const char *s = p + len + 1;
+ if (round == 0) {
+ // get length of lcs-leadmultispace string in first round
+ last_lmultispace = p;
+ lead_multispace_len = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ lead_multispace_len++;
+ }
+ if (lead_multispace_len == 0) {
+ // lcs-leadmultispace cannot be an empty string
+ return e_invarg;
+ }
+ p = s;
+ } else {
+ int multispace_pos = 0;
+ while (*s != NUL && *s != ',') {
+ int c1 = get_encoded_char_adv(&s);
+ if (p == last_lmultispace) {
+ lcs_chars.leadmultispace[multispace_pos++] = c1;
+ }
+ }
+ p = s;
+ }
+ break;
+ }
+
+ const char *s = p + len + 1;
+ int c1 = get_encoded_char_adv(&s);
+ if (c1 == 0 || char2cells(c1) > 1) {
+ return e_invarg;
+ }
+ int c2 = 0, c3 = 0;
+ if (tab[i].cp == &lcs_chars.tab2) {
+ if (*s == NUL) {
+ return e_invarg;
+ }
+ c2 = get_encoded_char_adv(&s);
+ if (c2 == 0 || char2cells(c2) > 1) {
+ return e_invarg;
+ }
+ if (!(*s == ',' || *s == NUL)) {
+ c3 = get_encoded_char_adv(&s);
+ if (c3 == 0 || char2cells(c3) > 1) {
+ return e_invarg;
+ }
+ }
+ }
+
+ if (*s == ',' || *s == NUL) {
+ if (round > 0) {
+ if (tab[i].cp == &lcs_chars.tab2) {
+ lcs_chars.tab1 = c1;
+ lcs_chars.tab2 = c2;
+ lcs_chars.tab3 = c3;
+ } else if (tab[i].cp != NULL) {
+ *(tab[i].cp) = c1;
+ }
+ }
+ p = s;
+ break;
+ }
+ }
+
+ if (i == entries) {
+ return e_invarg;
+ }
+
+ if (*p == ',') {
+ p++;
+ }
+ }
+ }
+
+ if (apply) {
+ if (is_listchars) {
+ xfree(wp->w_p_lcs_chars.multispace);
+ xfree(wp->w_p_lcs_chars.leadmultispace);
+ wp->w_p_lcs_chars = lcs_chars;
+ } else {
+ wp->w_p_fcs_chars = fcs_chars;
+ }
+ }
+
+ return NULL; // no error
+}
+
+/// Handle the new value of 'fillchars'.
+const char *set_fillchars_option(win_T *wp, char *val, bool apply)
+{
+ return set_chars_option(wp, val, false, apply);
+}
+
+/// Handle the new value of 'listchars'.
+const char *set_listchars_option(win_T *wp, char *val, bool apply)
+{
+ return set_chars_option(wp, val, true, apply);
+}
+
+/// Function given to ExpandGeneric() to obtain possible arguments of the
+/// 'fillchars' option.
+char *get_fillchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(fcs_tab)) {
+ return NULL;
+ }
+
+ return (char *)fcs_tab[idx].name;
+}
+
+/// Function given to ExpandGeneric() to obtain possible arguments of the
+/// 'listchars' option.
+char *get_listchars_name(expand_T *xp FUNC_ATTR_UNUSED, int idx)
+{
+ if (idx >= (int)ARRAY_SIZE(lcs_tab)) {
+ return NULL;
+ }
+
+ return (char *)lcs_tab[idx].name;
+}
+
+/// Check all global and local values of 'listchars' and 'fillchars'.
+/// May set different defaults in case character widths change.
+///
+/// @return an untranslated error message if any of them is invalid, NULL otherwise.
+const char *check_chars_options(void)
+{
+ if (set_listchars_option(curwin, p_lcs, false) != NULL) {
+ return e_conflicts_with_value_of_listchars;
+ }
+ if (set_fillchars_option(curwin, p_fcs, false) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
+ }
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_listchars_option(wp, wp->w_p_lcs, true) != NULL) {
+ return e_conflicts_with_value_of_listchars;
+ }
+ if (set_fillchars_option(wp, wp->w_p_fcs, true) != NULL) {
+ return e_conflicts_with_value_of_fillchars;
+ }
+ }
+ return NULL;
+}
diff --git a/src/nvim/optionstr.h b/src/nvim/optionstr.h
index 3520cc2061..b317e55b7e 100644
--- a/src/nvim/optionstr.h
+++ b/src/nvim/optionstr.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_OPTIONSTR_H
-#define NVIM_OPTIONSTR_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/option_defs.h"
+#include <stdint.h> // IWYU pragma: keep
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "optionstr.h.generated.h"
#endif
-#endif // NVIM_OPTIONSTR_H
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index 519cef7876..1a8d847f79 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// Functions for using external native libraries
#include <stdbool.h>
@@ -75,7 +72,7 @@ bool os_libcall(const char *libname, const char *funcname, const char *argv, int
// assume that ptr values of NULL, 1 or -1 are illegal
*str_out = (res && (intptr_t)res != 1 && (intptr_t)res != -1)
- ? xstrdup(res) : NULL;
+ ? xstrdup(res) : NULL;
} else {
str_int_fn sfn = (str_int_fn)fn;
int_int_fn ifn = (int_int_fn)fn;
diff --git a/src/nvim/os/dl.h b/src/nvim/os/dl.h
index 302e4e6678..0787c7fe46 100644
--- a/src/nvim/os/dl.h
+++ b/src/nvim/os/dl.h
@@ -1,10 +1,5 @@
-#ifndef NVIM_OS_DL_H
-#define NVIM_OS_DL_H
-
-#include <stdbool.h>
-#include <stdint.h>
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/dl.h.generated.h"
#endif
-#endif // NVIM_OS_DL_H
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 0611de14aa..8620c79069 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Environment inspection
#include <assert.h>
@@ -12,29 +9,33 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef MSWIN
-# include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+# include "nvim/mbyte.h"
+#endif
+
+#ifdef BACKSLASH_IN_FILENAME
+# include "nvim/fileio.h"
#endif
#ifdef HAVE__NSGETENVIRON
@@ -48,22 +49,6 @@
// Because `uv_os_getenv` requires allocating, we must manage a map to maintain
// the behavior of `os_getenv`.
static PMap(cstr_t) envmap = MAP_INIT;
-static uv_mutex_t mutex;
-
-void env_init(void)
-{
- uv_mutex_init(&mutex);
-}
-
-void os_env_var_lock(void)
-{
- uv_mutex_lock(&mutex);
-}
-
-void os_env_var_unlock(void)
-{
- uv_mutex_unlock(&mutex);
-}
/// Like getenv(), but returns NULL if the variable is empty.
/// @see os_env_exists
@@ -75,9 +60,8 @@ const char *os_getenv(const char *name)
if (name[0] == '\0') {
return NULL;
}
- uv_mutex_lock(&mutex);
int r = 0;
- if (pmap_has(cstr_t)(&envmap, name)
+ if (map_has(cstr_t, &envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) {
if (e[0] != '\0') {
// Found non-empty cached env var.
@@ -101,8 +85,6 @@ const char *os_getenv(const char *name)
}
pmap_put(cstr_t)(&envmap, xstrdup(name), e);
end:
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0 && r != UV_ENOENT && r != UV_UNKNOWN) {
ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -154,7 +136,6 @@ int os_setenv(const char *name, const char *value, int overwrite)
return 0;
}
#endif
- uv_mutex_lock(&mutex);
int r;
#ifdef MSWIN
// libintl uses getenv() for LC_ALL/LANG/etc., so we must use _putenv_s().
@@ -169,8 +150,6 @@ int os_setenv(const char *name, const char *value, int overwrite)
// Destroy the old map item. Do this AFTER uv_os_setenv(), because `value`
// could be a previous os_getenv() result.
pmap_del2(&envmap, name);
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -184,11 +163,8 @@ int os_unsetenv(const char *name)
if (name[0] == '\0') {
return -1;
}
- uv_mutex_lock(&mutex);
pmap_del2(&envmap, name);
int r = uv_os_unsetenv(name);
- // Must do this before ELOG, log.c may call os_setenv.
- uv_mutex_unlock(&mutex);
if (r != 0) {
ELOG("uv_os_unsetenv(%s) failed: %d %s", name, r, uv_err_name(r));
}
@@ -315,12 +291,11 @@ char *os_getenvname_at_index(size_t index)
// Some Windows env vars start with =, so skip over that to find the
// separator between name/value
- const char * const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0),
- '=');
+ const char *const end = strchr(utf8_str + (utf8_str[0] == '=' ? 1 : 0), '=');
assert(end != NULL);
ptrdiff_t len = end - utf8_str;
assert(len > 0);
- name = xstrndup(utf8_str, (size_t)len);
+ name = xmemdupz(utf8_str, (size_t)len);
xfree(utf8_str);
break;
}
@@ -351,7 +326,7 @@ char *os_getenvname_at_index(size_t index)
assert(end != NULL);
ptrdiff_t len = end - str;
assert(len > 0);
- return xstrndup(str, (size_t)len);
+ return xmemdupz(str, (size_t)len);
#endif
}
@@ -490,7 +465,7 @@ void init_homedir(void)
// links. Don't do it when we can't return.
if (os_dirname(os_buf, MAXPATHL) == OK && os_chdir(os_buf) == 0) {
if (!os_chdir(var) && os_dirname(IObuff, IOSIZE) == OK) {
- var = (char *)IObuff;
+ var = IObuff;
}
if (os_chdir(os_buf) != 0) {
emsg(_(e_prev_dir));
@@ -515,10 +490,8 @@ static char *os_homedir(void)
{
homedir_buf[0] = NUL;
size_t homedir_size = MAXPATHL;
- uv_mutex_lock(&mutex);
// http://docs.libuv.org/en/v1.x/misc.html#c.uv_os_homedir
int ret_value = uv_os_homedir(homedir_buf, &homedir_size);
- uv_mutex_unlock(&mutex);
if (ret_value == 0 && homedir_size < MAXPATHL) {
return homedir_buf;
}
@@ -599,7 +572,7 @@ void expand_env_esc(char *restrict srcp, char *restrict dst, int dstlen, bool es
if (src[0] == '`' && src[1] == '=') {
var = src;
src += 2;
- (void)skip_expr(&src);
+ (void)skip_expr(&src, NULL);
if (*src == '`') {
src++;
}
@@ -841,7 +814,7 @@ const void *vim_env_iter(const char delim, const char *const val, const void *co
const char **const dir, size_t *const len)
FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
- const char *varval = (const char *)iter;
+ const char *varval = iter;
if (varval == NULL) {
varval = val;
}
@@ -872,7 +845,7 @@ const void *vim_env_iter_rev(const char delim, const char *const val, const void
const char **const dir, size_t *const len)
FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
- const char *varend = (const char *)iter;
+ const char *varend = iter;
if (varend == NULL) {
varend = val + strlen(val) - 1;
}
@@ -956,10 +929,8 @@ char *vim_getenv(const char *name)
// Find runtime path relative to the nvim binary: ../share/nvim/runtime
if (vim_path == NULL) {
vim_get_prefix_from_exepath(exe_name);
- if (append_path(exe_name,
- "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR,
- MAXPATHL) == OK) {
- vim_path = exe_name; // -V507
+ if (append_path(exe_name, "share/nvim/runtime/", MAXPATHL) == OK) {
+ vim_path = exe_name;
}
}
@@ -985,7 +956,7 @@ char *vim_getenv(const char *name)
// check that the result is a directory name
assert(vim_path_end >= vim_path);
- vim_path = xstrndup(vim_path, (size_t)(vim_path_end - vim_path));
+ vim_path = xmemdupz(vim_path, (size_t)(vim_path_end - vim_path));
if (!os_isdir(vim_path)) {
xfree(vim_path);
@@ -1056,7 +1027,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
}
if (buf != NULL && buf->b_help) {
- const size_t dlen = xstrlcpy(dst, path_tail((char *)src), dstlen);
+ const size_t dlen = xstrlcpy(dst, path_tail(src), dstlen);
return MIN(dlen, dstlen - 1);
}
@@ -1094,7 +1065,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
}
if (!one) {
- src = skipwhite((char *)src);
+ src = skipwhite(src);
}
char *dst_p = dst;
while (*src && dstlen > 0) {
@@ -1107,7 +1078,7 @@ size_t home_replace(const buf_T *const buf, const char *src, char *const dst, si
// er's home directory)).
char *p = homedir;
size_t len = dirlen;
- for (;;) {
+ while (true) {
if (len
&& path_fnamencmp(src, p, len) == 0
&& (vim_ispathsep(src[len])
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index 5af39555c9..79d6ac08e7 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file fileio.c
///
/// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with
@@ -18,14 +15,22 @@
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
#include "nvim/rbuffer.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
+
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+#endif
+
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.c.generated.h"
@@ -49,7 +54,6 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, const int f
{
int os_open_flags = 0;
TriState wr = kNone;
- // -V:FLAG:501
#define FLAG(flags, flag, fcntl_flags, wrval, cond) \
do { \
if (flags & flag) { \
@@ -278,9 +282,10 @@ static char writebuf[kRWBufferSize];
///
/// @param[in,out] rv RBuffer instance used.
/// @param[in,out] fp File to work with.
-static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp)
+static void file_rb_write_full_cb(RBuffer *const rv, void *const fp_in)
FUNC_ATTR_NONNULL_ALL
{
+ FileDescriptor *const fp = fp_in;
assert(fp->wr);
assert(rv->data == (void *)fp);
if (rbuffer_size(rv) == 0) {
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
index 5e47bbf921..72e7984c8a 100644
--- a/src/nvim/os/fileio.h
+++ b/src/nvim/os/fileio.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_FILEIO_H
-#define NVIM_OS_FILEIO_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -75,4 +74,3 @@ enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.h.generated.h"
#endif
-#endif // NVIM_OS_FILEIO_H
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 6157341ec9..8f939c3b40 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// fs.c -- filesystem access
#include <assert.h>
#include <errno.h>
@@ -12,79 +9,75 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/types.h>
+#include <uv.h>
+
+#ifdef MSWIN
+# include <shlobj.h>
+#endif
#include "auto/config.h"
-#include "nvim/gettext.h"
-#include "nvim/globals.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
-#include "nvim/option_defs.h"
-#include "nvim/os/fs_defs.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/func_attr.h"
+#include "nvim/os/fs.h"
-#ifdef HAVE_SYS_UIO_H
-# include <sys/uio.h>
+#if defined(HAVE_ACL)
+# ifdef HAVE_SYS_ACL_H
+# include <sys/acl.h>
+# endif
+# ifdef HAVE_SYS_ACCESS_H
+# include <sys/access.h>
+# endif
#endif
-#include <uv.h>
+#ifdef HAVE_XATTR
+# include <sys/xattr.h>
+#endif
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/gettext.h"
+#include "nvim/globals.h"
+#include "nvim/log.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
-struct iovec;
+#ifdef HAVE_SYS_UIO_H
+# include <sys/uio.h>
+#endif
#ifdef MSWIN
-# include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+# include "nvim/mbyte.h"
+# include "nvim/option.h"
+# include "nvim/strings.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fs.c.generated.h"
#endif
+#ifdef HAVE_XATTR
+static const char e_xattr_erange[]
+ = N_("E1506: Buffer too small to copy xattr value or key");
+static const char e_xattr_e2big[]
+ = N_("E1508: Size of the extended attribute value is larger than the maximum size allowed");
+static const char e_xattr_other[]
+ = N_("E1509: Error occurred when reading or writing extended attribute");
+#endif
+
#define RUN_UV_FS_FUNC(ret, func, ...) \
do { \
- bool did_try_to_free = false; \
-uv_call_start: {} \
uv_fs_t req; \
- fs_loop_lock(); \
- ret = func(&fs_loop, &req, __VA_ARGS__); \
+ ret = func(NULL, &req, __VA_ARGS__); \
uv_fs_req_cleanup(&req); \
- fs_loop_unlock(); \
- if (ret == UV_ENOMEM && !did_try_to_free) { \
- try_to_free_memory(); \
- did_try_to_free = true; \
- goto uv_call_start; \
- } \
} while (0)
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
-static uv_loop_t fs_loop;
-static uv_mutex_t fs_loop_mutex;
-
-// Initialize the fs module
-void fs_init(void)
-{
- uv_loop_init(&fs_loop);
- uv_mutex_init_recursive(&fs_loop_mutex);
-}
-
-/// TODO(bfredl): some of these operations should
-/// be possible to do the private libuv loop of the
-/// thread, instead of contending the global fs loop
-void fs_loop_lock(void)
-{
- uv_mutex_lock(&fs_loop_mutex);
-}
-
-void fs_loop_unlock(void)
-{
- uv_mutex_unlock(&fs_loop_mutex);
-}
/// Changes the current directory to `path`.
///
@@ -94,7 +87,7 @@ int os_chdir(const char *path)
{
if (p_verbose >= 5) {
verbose_enter();
- smsg("chdir(%s)", path);
+ smsg(0, "chdir(%s)", path);
verbose_leave();
}
return uv_chdir(path);
@@ -123,12 +116,9 @@ bool os_isrealdir(const char *name)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- fs_loop_lock();
- if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) {
- fs_loop_unlock();
+ if (uv_fs_lstat(NULL, &request, name, NULL) != kLibuvSuccess) {
return false;
}
- fs_loop_unlock();
if (S_ISLNK(request.statbuf.st_mode)) {
return false;
}
@@ -373,7 +363,7 @@ static bool is_executable_in_path(const char *name, char **abspath)
// is an executable file.
char *p = path;
bool rv = false;
- for (;;) {
+ while (true) {
char *e = xstrchrnul(p, ENV_SEPCHAR);
// Combine the $PATH segment with `name`.
@@ -567,7 +557,6 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, const
return 0;
}
size_t read_bytes = 0;
- bool did_try_to_free = false;
while (read_bytes != size) {
assert(size >= read_bytes);
const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes,
@@ -582,10 +571,6 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, const
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
- } else if (error == UV_ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- continue;
} else {
return (ptrdiff_t)error;
}
@@ -619,7 +604,6 @@ ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, size_t
{
*ret_eof = false;
size_t read_bytes = 0;
- bool did_try_to_free = false;
size_t toread = 0;
for (size_t i = 0; i < iov_size; i++) {
// Overflow, trying to read too much data
@@ -651,10 +635,6 @@ ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, size_t
break;
} else if (error == UV_EINTR || error == UV_EAGAIN) {
continue;
- } else if (error == UV_ENOMEM && !did_try_to_free) {
- try_to_free_memory();
- did_try_to_free = true;
- continue;
} else {
return (ptrdiff_t)error;
}
@@ -743,9 +723,7 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
return UV_EINVAL;
}
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_stat(&fs_loop, &request, name, NULL);
- fs_loop_unlock();
+ int result = uv_fs_stat(NULL, &request, name, NULL);
if (result == kLibuvSuccess) {
*statbuf = request.statbuf;
}
@@ -777,13 +755,84 @@ int os_setperm(const char *const name, int perm)
return (r == kLibuvSuccess ? OK : FAIL);
}
-#if defined(HAVE_ACL)
-# ifdef HAVE_SYS_ACL_H
-# include <sys/acl.h>
-# endif
-# ifdef HAVE_SYS_ACCESS_H
-# include <sys/access.h>
-# endif
+#ifdef HAVE_XATTR
+/// Copy extended attributes from_file to to_file
+void os_copy_xattr(const char *from_file, const char *to_file)
+{
+ if (from_file == NULL) {
+ return;
+ }
+
+ // get the length of the extended attributes
+ ssize_t size = listxattr((char *)from_file, NULL, 0);
+ // not supported or no attributes to copy
+ if (errno == ENOTSUP || size <= 0) {
+ return;
+ }
+ char *xattr_buf = xmalloc((size_t)size);
+ size = listxattr(from_file, xattr_buf, (size_t)size);
+ ssize_t tsize = size;
+
+ errno = 0;
+
+ ssize_t max_vallen = 0;
+ char *val = NULL;
+ const char *errmsg = NULL;
+
+ for (int round = 0; round < 2; round++) {
+ char *key = xattr_buf;
+ if (round == 1) {
+ size = tsize;
+ }
+
+ while (size > 0) {
+ ssize_t vallen = getxattr(from_file, key, val, round ? (size_t)max_vallen : 0);
+ // only set the attribute in the second round
+ if (vallen >= 0 && round
+ && setxattr(to_file, key, val, (size_t)vallen, 0) == 0) {
+ //
+ } else if (errno) {
+ switch (errno) {
+ case E2BIG:
+ errmsg = e_xattr_e2big;
+ goto error_exit;
+ case ENOTSUP:
+ case EACCES:
+ case EPERM:
+ break;
+ case ERANGE:
+ errmsg = e_xattr_erange;
+ goto error_exit;
+ default:
+ errmsg = e_xattr_other;
+ goto error_exit;
+ }
+ }
+
+ if (round == 0 && vallen > max_vallen) {
+ max_vallen = vallen;
+ }
+
+ // add one for terminating null
+ ssize_t keylen = (ssize_t)strlen(key) + 1;
+ size -= keylen;
+ key += keylen;
+ }
+ if (round) {
+ break;
+ }
+
+ val = xmalloc((size_t)max_vallen + 1);
+ }
+error_exit:
+ xfree(xattr_buf);
+ xfree(val);
+
+ if (errmsg != NULL) {
+ emsg(_(errmsg));
+ }
+}
+#endif
// Return a pointer to the ACL of file "fname" in allocated memory.
// Return NULL if the ACL is not available for whatever reason.
@@ -807,7 +856,6 @@ void os_free_acl(vim_acl_T aclent)
return;
}
}
-#endif
#ifdef UNIX
/// Checks if the current user owns a file.
@@ -938,10 +986,13 @@ int os_mkdir(const char *path, int32_t mode)
/// the name of the directory which os_mkdir_recurse
/// failed to create. I.e. it will contain dir or any
/// of the higher level directories.
+/// @param[out] created Set to the full name of the first created directory.
+/// It will be NULL until that happens.
///
/// @return `0` for success, libuv error code for failure.
-int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_dir,
+ char **const created)
+ FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
// Get end of directory name in "dir".
// We're done when it's "/" or "c:/".
@@ -976,6 +1027,8 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, char **const failed_di
if ((ret = os_mkdir(curdir, mode)) != 0) {
*failed_dir = curdir;
return ret;
+ } else if (created != NULL && *created == NULL) {
+ *created = FullName_save(curdir, false);
}
}
xfree(curdir);
@@ -1003,7 +1056,7 @@ int os_file_mkdir(char *fname, int32_t mode)
*tail = NUL;
int r;
char *failed_dir;
- if (((r = os_mkdir_recurse(fname, mode, &failed_dir)) < 0)) {
+ if (((r = os_mkdir_recurse(fname, mode, &failed_dir, NULL)) < 0)) {
semsg(_(e_mkdir), failed_dir, os_strerror(r));
xfree(failed_dir);
}
@@ -1015,18 +1068,16 @@ int os_file_mkdir(char *fname, int32_t mode)
/// Create a unique temporary directory.
///
-/// @param[in] template Template of the path to the directory with XXXXXX
-/// which would be replaced by random chars.
+/// @param[in] templ Template of the path to the directory with XXXXXX
+/// which would be replaced by random chars.
/// @param[out] path Path to created directory for success, undefined for
/// failure.
/// @return `0` for success, non-zero for failure.
-int os_mkdtemp(const char *template, char *path)
+int os_mkdtemp(const char *templ, char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
- fs_loop_unlock();
+ int result = uv_fs_mkdtemp(NULL, &request, templ, NULL);
if (result == kLibuvSuccess) {
xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN);
}
@@ -1053,9 +1104,7 @@ int os_rmdir(const char *path)
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
- fs_loop_lock();
- int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
- fs_loop_unlock();
+ int r = uv_fs_scandir(NULL, &dir->request, path, 0, NULL);
if (r < 0) {
os_closedir(dir);
}
@@ -1116,9 +1165,7 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
return false;
}
uv_fs_t request;
- fs_loop_lock();
- bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
- fs_loop_unlock();
+ bool ok = uv_fs_lstat(NULL, &request, path, NULL) == kLibuvSuccess;
if (ok) {
file_info->stat = request.statbuf;
}
@@ -1136,8 +1183,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
{
uv_fs_t request;
CLEAR_POINTER(file_info);
- fs_loop_lock();
- bool ok = uv_fs_fstat(&fs_loop,
+ bool ok = uv_fs_fstat(NULL,
&request,
file_descriptor,
NULL) == kLibuvSuccess;
@@ -1145,7 +1191,6 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
file_info->stat = request.statbuf;
}
uv_fs_req_cleanup(&request);
- fs_loop_unlock();
return ok;
}
@@ -1262,8 +1307,7 @@ char *os_realpath(const char *name, char *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_fs_t request;
- fs_loop_lock();
- int result = uv_fs_realpath(&fs_loop, &request, name, NULL);
+ int result = uv_fs_realpath(NULL, &request, name, NULL);
if (result == kLibuvSuccess) {
if (buf == NULL) {
buf = xmallocz(MAXPATHL);
@@ -1271,13 +1315,10 @@ char *os_realpath(const char *name, char *buf)
xstrlcpy(buf, request.ptr, MAXPATHL + 1);
}
uv_fs_req_cleanup(&request);
- fs_loop_unlock();
return result == kLibuvSuccess ? buf : NULL;
}
#ifdef MSWIN
-# include <shlobj.h>
-
/// When "fname" is the name of a shortcut (*.lnk) resolve the file it points
/// to and return that name in allocated memory.
/// Otherwise NULL is returned.
diff --git a/src/nvim/os/fs.h b/src/nvim/os/fs.h
index 75c24b8db2..56dd657f70 100644
--- a/src/nvim/os/fs.h
+++ b/src/nvim/os/fs.h
@@ -1,10 +1,13 @@
-#ifndef NVIM_OS_FS_H
-#define NVIM_OS_FS_H
+#pragma once
-#include "nvim/os/fs_defs.h"
-#include "nvim/types.h"
+#include <stddef.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
+#include <stdio.h> // IWYU pragma: keep
+#include <uv.h> // IWYU pragma: keep
+
+#include "nvim/os/fs_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fs.h.generated.h"
#endif
-#endif // NVIM_OS_FS_H
diff --git a/src/nvim/os/fs_defs.h b/src/nvim/os/fs_defs.h
index f4929b12b1..e5355ddefd 100644
--- a/src/nvim/os/fs_defs.h
+++ b/src/nvim/os/fs_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_FS_DEFS_H
-#define NVIM_OS_FS_DEFS_H
+#pragma once
#include <uv.h>
@@ -26,5 +25,3 @@ typedef struct {
#define NODE_WRITABLE 1 // something we can write to (character
// device, fifo, socket, ..)
#define NODE_OTHER 2 // non-writable thing (e.g., block device)
-
-#endif // NVIM_OS_FS_DEFS_H
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 759b3cf83c..f3bd1c7ed9 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
@@ -9,28 +6,28 @@
#include <uv.h>
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
+#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/profile.h"
#include "nvim/rbuffer.h"
#include "nvim/state.h"
-#include "nvim/vim.h"
#define READ_BUFFER_SIZE 0xfff
#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
@@ -242,8 +239,8 @@ bool os_isatty(int fd)
size_t input_enqueue(String keys)
{
- char *ptr = keys.data;
- char *end = ptr + keys.size;
+ const char *ptr = keys.data;
+ const char *end = ptr + keys.size;
while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
// A "<x>" form occupies at least 1 characters, and produces up
@@ -253,9 +250,8 @@ size_t input_enqueue(String keys)
// K_SPECIAL(0x80).
uint8_t buf[19] = { 0 };
// Do not simplify the keys here. Simplification will be done later.
- unsigned int new_size
- = trans_special((const char **)&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true,
- NULL);
+ unsigned new_size
+ = trans_special(&ptr, (size_t)(end - ptr), (char *)buf, FSK_KEYCODE, true, NULL);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
@@ -264,7 +260,7 @@ size_t input_enqueue(String keys)
}
if (*ptr == '<') {
- char *old_ptr = ptr;
+ const char *old_ptr = ptr;
// Invalid or incomplete key sequence, skip until the next '>' or *end.
do {
ptr++;
@@ -346,7 +342,7 @@ static uint8_t check_multiclick(int code, int grid, int row, int col)
// Mouse event handling code(Extract row/col if available and detect multiple
// clicks)
-static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, unsigned int bufsize)
+static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufsize)
{
int mouse_code = 0;
int type = 0;
@@ -441,7 +437,7 @@ bool input_blocking(void)
// This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
{
- if (input_ready(events)) {
+ if (os_input_ready(events)) {
return kInputAvail;
}
@@ -457,24 +453,19 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
events && !multiqueue_empty(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
- input_ready(events) || input_eof);
+ os_input_ready(events) || input_eof);
blocking = false;
if (do_profiling == PROF_YES && ms) {
prof_inchar_exit();
}
- if (input_ready(events)) {
+ if (os_input_ready(events)) {
return kInputAvail;
}
return input_eof ? kInputEof : kInputNone;
}
-void input_done(void)
-{
- input_eof = true;
-}
-
bool input_available(void)
{
return rbuffer_size(input_buffer) != 0;
@@ -483,7 +474,7 @@ bool input_available(void)
static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bool at_eof)
{
if (at_eof) {
- input_done();
+ input_eof = true;
}
assert(rbuffer_space(input_buffer) >= rbuffer_size(buf));
@@ -535,8 +526,8 @@ static int push_event_key(uint8_t *buf, int maxlen)
return buf_idx;
}
-// Check if there's pending input
-static bool input_ready(MultiQueue *events)
+/// Check if there's pending input already in typebuf or `events`
+bool os_input_ready(MultiQueue *events)
{
return (typebuf_was_filled // API call filled typeahead
|| rbuffer_size(input_buffer) // Input buffer filled
@@ -550,8 +541,7 @@ static void read_error_exit(void)
if (silent_mode) { // Normal way to exit for "nvim -es".
getout(0);
}
- STRCPY(IObuff, _("Vim: Error reading input, exiting...\n"));
- preserve_exit();
+ preserve_exit(_("Vim: Error reading input, exiting...\n"));
}
static bool pending_events(MultiQueue *events)
diff --git a/src/nvim/os/input.h b/src/nvim/os/input.h
index 6f25efdc7b..4b104b0b50 100644
--- a/src/nvim/os/input.h
+++ b/src/nvim/os/input.h
@@ -1,15 +1,14 @@
-#ifndef NVIM_OS_INPUT_H
-#define NVIM_OS_INPUT_H
+#pragma once
#include <stdbool.h>
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/event/multiqueue.h"
+#include "nvim/macros_defs.h"
-EXTERN bool used_stdin INIT(= false);
+EXTERN bool used_stdin INIT( = false);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/input.h.generated.h"
#endif
-#endif // NVIM_OS_INPUT_H
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c
index 57c82bba86..17d179a56a 100644
--- a/src/nvim/os/lang.c
+++ b/src/nvim/os/lang.c
@@ -1,22 +1,341 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#ifdef __APPLE__
# define Boolean CFBoolean // Avoid conflict with API's Boolean
# define FileInfo CSFileInfo // Avoid conflict with API's Fileinfo
# include <CoreServices/CoreServices.h>
+
# undef Boolean
# undef FileInfo
+#endif
-# include "auto/config.h"
-# ifdef HAVE_LOCALE_H
-# include <locale.h>
-# endif
-# include "nvim/os/os.h"
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include "auto/config.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/buffer.h"
+#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/eval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/garray.h"
+#include "nvim/gettext.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option.h"
+#include "nvim/os/lang.h"
+#include "nvim/os/os.h"
+#include "nvim/os/shell.h"
+#include "nvim/path.h"
+#include "nvim/profile.h"
+#include "nvim/vim_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/lang.c.generated.h"
#endif
-#include "nvim/os/lang.h"
+static char *get_locale_val(int what)
+{
+ // Obtain the locale value from the libraries.
+ char *loc = setlocale(what, NULL);
+
+ return loc;
+}
+
+/// @return true when "lang" starts with a valid language name.
+/// Rejects NULL, empty string, "C", "C.UTF-8" and others.
+static bool is_valid_mess_lang(const char *lang)
+{
+ return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]);
+}
+
+/// Obtain the current messages language. Used to set the default for
+/// 'helplang'. May return NULL or an empty string.
+char *get_mess_lang(void)
+{
+ char *p;
+
+#if defined(LC_MESSAGES)
+ p = get_locale_val(LC_MESSAGES);
+#else
+ // This is necessary for Win32, where LC_MESSAGES is not defined and $LANG
+ // may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME
+ // and LC_MONETARY may be set differently for a Japanese working in the
+ // US.
+ p = get_locale_val(LC_COLLATE);
+#endif
+ return is_valid_mess_lang(p) ? p : NULL;
+}
+
+/// Get the language used for messages from the environment.
+///
+/// This uses LC_MESSAGES when available, which it is for most systems we build for
+/// except for windows. Then fallback to get the value from the environment
+/// ourselves, and use LC_CTYPE as a last resort.
+static char *get_mess_env(void)
+{
+#ifdef LC_MESSAGES
+ return get_locale_val(LC_MESSAGES);
+#else
+ char *p = (char *)os_getenv("LC_ALL");
+ if (p == NULL) {
+ p = (char *)os_getenv("LC_MESSAGES");
+ if (p == NULL) {
+ p = (char *)os_getenv("LANG");
+ if (p != NULL && ascii_isdigit(*p)) {
+ p = NULL; // ignore something like "1043"
+ }
+ if (p == NULL) {
+ p = get_locale_val(LC_CTYPE);
+ }
+ }
+ }
+ return p;
+#endif
+}
+
+/// Set the "v:lang" variable according to the current locale setting.
+/// Also do "v:lc_time"and "v:ctype".
+void set_lang_var(void)
+{
+ const char *loc;
+
+ loc = get_locale_val(LC_CTYPE);
+ set_vim_var_string(VV_CTYPE, loc, -1);
+
+ loc = get_mess_env();
+ set_vim_var_string(VV_LANG, loc, -1);
+
+ loc = get_locale_val(LC_TIME);
+ set_vim_var_string(VV_LC_TIME, loc, -1);
+
+ loc = get_locale_val(LC_COLLATE);
+ set_vim_var_string(VV_COLLATE, loc, -1);
+}
+
+/// Setup to use the current locale (for ctype() and many other things).
+void init_locale(void)
+{
+ setlocale(LC_ALL, "");
+
+#ifdef LC_NUMERIC
+ // Make sure strtod() uses a decimal point, not a comma.
+ setlocale(LC_NUMERIC, "C");
+#endif
+
+ char localepath[MAXPATHL] = { 0 };
+ snprintf(localepath, sizeof(localepath), "%s", get_vim_var_str(VV_PROGPATH));
+ char *tail = path_tail_with_sep(localepath);
+ *tail = NUL;
+ tail = path_tail(localepath);
+ xstrlcpy(tail, "share/locale",
+ sizeof(localepath) - (size_t)(tail - localepath));
+ bindtextdomain(PROJECT_NAME, localepath);
+ textdomain(PROJECT_NAME);
+ TIME_MSG("locale set");
+}
+
+/// ":language": Set the language (locale).
+///
+/// @param eap
+void ex_language(exarg_T *eap)
+{
+ char *loc;
+ char *p;
+ char *name;
+ int what = LC_ALL;
+ char *whatstr = "";
+#ifdef LC_MESSAGES
+# define VIM_LC_MESSAGES LC_MESSAGES
+#else
+# define VIM_LC_MESSAGES 6789
+#endif
+
+ name = eap->arg;
+
+ // Check for "messages {name}", "ctype {name}" or "time {name}" argument.
+ // Allow abbreviation, but require at least 3 characters to avoid
+ // confusion with a two letter language name "me" or "ct".
+ p = skiptowhite(eap->arg);
+ if ((*p == NUL || ascii_iswhite(*p)) && p - eap->arg >= 3) {
+ if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) {
+ what = VIM_LC_MESSAGES;
+ name = skipwhite(p);
+ whatstr = "messages ";
+ } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) {
+ what = LC_CTYPE;
+ name = skipwhite(p);
+ whatstr = "ctype ";
+ } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) {
+ what = LC_TIME;
+ name = skipwhite(p);
+ whatstr = "time ";
+ } else if (STRNICMP(eap->arg, "collate", p - eap->arg) == 0) {
+ what = LC_COLLATE;
+ name = skipwhite(p);
+ whatstr = "collate ";
+ }
+ }
+
+ if (*name == NUL) {
+ if (what == VIM_LC_MESSAGES) {
+ p = get_mess_env();
+ } else {
+ p = setlocale(what, NULL);
+ }
+ if (p == NULL || *p == NUL) {
+ p = "Unknown";
+ }
+ smsg(0, _("Current %slanguage: \"%s\""), whatstr, p);
+ } else {
+#ifndef LC_MESSAGES
+ if (what == VIM_LC_MESSAGES) {
+ loc = "";
+ } else {
+#endif
+ loc = setlocale(what, name);
+#ifdef LC_NUMERIC
+ // Make sure strtod() uses a decimal point, not a comma.
+ setlocale(LC_NUMERIC, "C");
+#endif
+#ifndef LC_MESSAGES
+ }
+#endif
+ if (loc == NULL) {
+ semsg(_("E197: Cannot set language to \"%s\""), name);
+ } else {
+#ifdef HAVE_NL_MSG_CAT_CNTR
+ // Need to do this for GNU gettext, otherwise cached translations
+ // will be used again.
+ extern int _nl_msg_cat_cntr; // NOLINT(bugprone-reserved-identifier)
+
+ _nl_msg_cat_cntr++;
+#endif
+ // Reset $LC_ALL, otherwise it would overrule everything.
+ os_setenv("LC_ALL", "", 1);
+
+ if (what != LC_TIME && what != LC_COLLATE) {
+ // Tell gettext() what to translate to. It apparently doesn't
+ // use the currently effective locale.
+ if (what == LC_ALL) {
+ os_setenv("LANG", name, 1);
+
+ // Clear $LANGUAGE because GNU gettext uses it.
+ os_setenv("LANGUAGE", "", 1);
+ }
+ if (what != LC_CTYPE) {
+ os_setenv("LC_MESSAGES", name, 1);
+ set_helplang_default(name);
+ }
+ }
+
+ // Set v:lang, v:lc_time, v:collate and v:ctype to the final result.
+ set_lang_var();
+ maketitle();
+ }
+ }
+}
+
+static char **locales = NULL; // Array of all available locales
+
+#ifndef MSWIN
+static bool did_init_locales = false;
+
+/// @return an array of strings for all available locales + NULL for the
+/// last element or,
+/// NULL in case of error.
+static char **find_locales(void)
+{
+ garray_T locales_ga;
+ char *loc;
+ char *saveptr = NULL;
+
+ // Find all available locales by running command "locale -a". If this
+ // doesn't work we won't have completion.
+ char *locale_a = get_cmd_output("locale -a", NULL, kShellOptSilent, NULL);
+ if (locale_a == NULL) {
+ return NULL;
+ }
+ ga_init(&locales_ga, sizeof(char *), 20);
+
+ // Transform locale_a string where each locale is separated by "\n"
+ // into an array of locale strings.
+ loc = os_strtok(locale_a, "\n", &saveptr);
+
+ while (loc != NULL) {
+ loc = xstrdup(loc);
+ GA_APPEND(char *, &locales_ga, loc);
+ loc = os_strtok(NULL, "\n", &saveptr);
+ }
+ xfree(locale_a);
+ // Guarantee that .ga_data is NULL terminated
+ ga_grow(&locales_ga, 1);
+ ((char **)locales_ga.ga_data)[locales_ga.ga_len] = NULL;
+ return locales_ga.ga_data;
+}
+#endif
+
+/// Lazy initialization of all available locales.
+static void init_locales(void)
+{
+#ifndef MSWIN
+ if (did_init_locales) {
+ return;
+ }
+
+ did_init_locales = true;
+ locales = find_locales();
+#endif
+}
+
+#if defined(EXITFREE)
+void free_locales(void)
+{
+ if (locales == NULL) {
+ return;
+ }
+
+ for (int i = 0; locales[i] != NULL; i++) {
+ xfree(locales[i]);
+ }
+ XFREE_CLEAR(locales);
+}
+#endif
+
+/// Function given to ExpandGeneric() to obtain the possible arguments of the
+/// ":language" command.
+char *get_lang_arg(expand_T *xp, int idx)
+{
+ if (idx == 0) {
+ return "messages";
+ }
+ if (idx == 1) {
+ return "ctype";
+ }
+ if (idx == 2) {
+ return "time";
+ }
+ if (idx == 3) {
+ return "collate";
+ }
+
+ init_locales();
+ if (locales == NULL) {
+ return NULL;
+ }
+ return locales[idx - 4];
+}
+
+/// Function given to ExpandGeneric() to obtain the available locales.
+char *get_locales(expand_T *xp, int idx)
+{
+ init_locales();
+ if (locales == NULL) {
+ return NULL;
+ }
+ return locales[idx];
+}
void lang_init(void)
{
diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h
index f60e064f57..4e7bf82195 100644
--- a/src/nvim/os/lang.h
+++ b/src/nvim/os/lang.h
@@ -1,7 +1,9 @@
-#ifndef NVIM_OS_LANG_H
-#define NVIM_OS_LANG_H
+#pragma once
+
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/lang.h.generated.h"
#endif
-#endif // NVIM_OS_LANG_H
diff --git a/src/nvim/os/mem.c b/src/nvim/os/mem.c
index 0b7e8065ef..3e6264c691 100644
--- a/src/nvim/os/mem.c
+++ b/src/nvim/os/mem.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// Functions for accessing system memory information.
#include <stdint.h>
diff --git a/src/nvim/os/nvim.manifest b/src/nvim/os/nvim.manifest
index 8878822a5d..571b7f4580 100644
--- a/src/nvim/os/nvim.manifest
+++ b/src/nvim/os/nvim.manifest
@@ -17,4 +17,9 @@
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
</application>
</compatibility>
+ <asmv3:application>
+ <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
+ <activeCodePage>UTF-8</activeCodePage>
+ </asmv3:windowsSettings>
+ </asmv3:application>
</assembly>
diff --git a/src/nvim/os/os.h b/src/nvim/os/os.h
index a7496130cc..302d84d066 100644
--- a/src/nvim/os/os.h
+++ b/src/nvim/os/os.h
@@ -1,22 +1,33 @@
-#ifndef NVIM_OS_OS_H
-#define NVIM_OS_OS_H
+#pragma once
-#include <stdbool.h>
-#include <uv.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <stdint.h> // IWYU pragma: keep
+#include <uv.h> // IWYU pragma: keep
-#include "nvim/os/fs_defs.h"
-#include "nvim/os/stdpaths_defs.h"
-#include "nvim/vim.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/os/os_defs.h" // IWYU pragma: export
+#include "nvim/os/stdpaths_defs.h" // IWYU pragma: keep
+
+#define HAVE_PATHDEF
+
+// Some file names are stored in pathdef.c, which is generated from the
+// Makefile to make their value depend on the Makefile.
+#ifdef HAVE_PATHDEF
+extern char *default_vim_dir;
+extern char *default_vimruntime_dir;
+extern char *default_lib_dir;
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
+// IWYU pragma: begin_exports
# include "os/env.h.generated.h"
-# include "os/fs.h.generated.h"
# include "os/mem.h.generated.h"
# include "os/stdpaths.h.generated.h"
# include "os/users.h.generated.h"
+// IWYU pragma: end_exports
#endif
#define ENV_LOGFILE "NVIM_LOG_FILE"
#define ENV_NVIM "NVIM"
-
-#endif // NVIM_OS_OS_H
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index a30e16eeba..12de55a227 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_OS_DEFS_H
-#define NVIM_OS_OS_DEFS_H
+#pragma once
#include <ctype.h>
#include <stdio.h>
@@ -7,12 +6,26 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include "auto/config.h"
+
+// Note: Some systems need both string.h and strings.h (Savage).
+#include <string.h>
+#ifdef HAVE_STRINGS_H
+# include <strings.h> // IWYU pragma: export
+#endif
+
#ifdef MSWIN
# include "nvim/os/win_defs.h"
#else
# include "nvim/os/unix_defs.h"
#endif
+#ifdef BACKSLASH_IN_FILENAME
+# define BACKSLASH_IN_FILENAME_BOOL true
+#else
+# define BACKSLASH_IN_FILENAME_BOOL false
+#endif
+
#if !defined(NAME_MAX) && defined(_XOPEN_NAME_MAX)
# define NAME_MAX _XOPEN_NAME_MAX
#endif
@@ -30,12 +43,7 @@
// Command-processing buffer. Use large buffers for all platforms.
#define CMDBUFFSIZE 1024
-// Note: Some systems need both string.h and strings.h (Savage). However,
-// some systems can't handle both, only use string.h in that case.
-#include <string.h>
-#if defined(HAVE_STRINGS_H) && !defined(NO_STRINGS_WITH_STRING_H)
-# include <strings.h>
-#endif
+#define ROOT_UID 0
/// Converts libuv error (negative int) to error description string.
#define os_strerror uv_strerror
@@ -52,52 +60,56 @@
// stat macros
#ifndef S_ISDIR
# ifdef S_IFDIR
-# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+# define S_ISDIR(m) (((m)& S_IFMT) == S_IFDIR)
# else
# define S_ISDIR(m) 0
# endif
#endif
#ifndef S_ISREG
# ifdef S_IFREG
-# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+# define S_ISREG(m) (((m)& S_IFMT) == S_IFREG)
# else
# define S_ISREG(m) 0
# endif
#endif
#ifndef S_ISBLK
# ifdef S_IFBLK
-# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+# define S_ISBLK(m) (((m)& S_IFMT) == S_IFBLK)
# else
# define S_ISBLK(m) 0
# endif
#endif
#ifndef S_ISSOCK
# ifdef S_IFSOCK
-# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+# define S_ISSOCK(m) (((m)& S_IFMT) == S_IFSOCK)
# else
# define S_ISSOCK(m) 0
# endif
#endif
#ifndef S_ISFIFO
# ifdef S_IFIFO
-# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+# define S_ISFIFO(m) (((m)& S_IFMT) == S_IFIFO)
# else
# define S_ISFIFO(m) 0
# endif
#endif
#ifndef S_ISCHR
# ifdef S_IFCHR
-# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+# define S_ISCHR(m) (((m)& S_IFMT) == S_IFCHR)
# else
# define S_ISCHR(m) 0
# endif
#endif
#ifndef S_ISLNK
# ifdef S_IFLNK
-# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+# define S_ISLNK(m) (((m)& S_IFMT) == S_IFLNK)
# else
# define S_ISLNK(m) 0
# endif
#endif
-#endif // NVIM_OS_OS_DEFS_H
+// BSD is supposed to cover FreeBSD and similar systems.
+#if (defined(BSD) || defined(__FreeBSD_kernel__)) \
+ && (defined(S_ISCHR) || defined(S_IFCHR))
+# define OPEN_CHR_FILES
+#endif
diff --git a/src/nvim/os/os_win_console.c b/src/nvim/os/os_win_console.c
index 006e27d28f..816e81e997 100644
--- a/src/nvim/os/os_win_console.c
+++ b/src/nvim/os/os_win_console.c
@@ -1,10 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+#include <string.h>
+#include "nvim/globals.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_win_console.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/os_win_console.c.generated.h"
@@ -83,7 +83,7 @@ void os_icon_init(void)
const char *vimruntime = os_getenv("VIMRUNTIME");
if (vimruntime != NULL) {
- snprintf(NameBuff, MAXPATHL, "%s" _PATHSEPSTR "neovim.ico", vimruntime);
+ snprintf(NameBuff, MAXPATHL, "%s/neovim.ico", vimruntime);
if (!os_path_exists(NameBuff)) {
WLOG("neovim.ico not found: %s", NameBuff);
} else {
diff --git a/src/nvim/os/os_win_console.h b/src/nvim/os/os_win_console.h
index 7b5800afa8..098267312a 100644
--- a/src/nvim/os/os_win_console.h
+++ b/src/nvim/os/os_win_console.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_OS_WIN_CONSOLE_H
-#define NVIM_OS_OS_WIN_CONSOLE_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/os_win_console.h.generated.h"
@@ -8,5 +7,3 @@
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
# define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
#endif
-
-#endif // NVIM_OS_OS_WIN_CONSOLE_H
diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c
index f4d95e141b..d9ec3a7a8a 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/process.c
@@ -1,29 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// OS process functions
///
/// psutil is a good reference for cross-platform syscall voodoo:
/// https://github.com/giampaolo/psutil/tree/master/psutil/arch
+// IWYU pragma: no_include <sys/param.h>
+
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
-#include <stdio.h>
#include <uv.h>
-#include "nvim/log.h"
-#include "nvim/memory.h"
-#include "nvim/os/process.h"
-
#ifdef MSWIN
# include <tlhelp32.h>
-
-# include "nvim/api/private/helpers.h"
#endif
-#if defined(__FreeBSD__) // XXX: OpenBSD ?
+#if defined(__FreeBSD__)
# include <string.h>
# include <sys/types.h>
# include <sys/user.h>
@@ -34,8 +26,21 @@
#endif
#if defined(__APPLE__) || defined(BSD)
-# include <pwd.h>
# include <sys/sysctl.h>
+
+# include "nvim/macros_defs.h"
+#endif
+
+#if defined(__linux__)
+# include <stdio.h>
+#endif
+
+#include "nvim/log.h"
+#include "nvim/memory.h"
+#include "nvim/os/process.h"
+
+#ifdef MSWIN
+# include "nvim/api/private/helpers.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -72,7 +77,7 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig)
}
theend:
- return (bool)TerminateProcess(process, (unsigned int)sig);
+ return (bool)TerminateProcess(process, (unsigned)sig);
}
/// Kills process `pid` and its descendants recursively.
bool os_proc_tree_kill(int pid, int sig)
@@ -254,7 +259,7 @@ Dictionary os_proc_info(int pid)
if (pe.th32ProcessID == (DWORD)pid) {
PUT(pinfo, "pid", INTEGER_OBJ(pid));
PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID));
- PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile)));
+ PUT(pinfo, "name", CSTR_TO_OBJ(pe.szExeFile));
}
return pinfo;
diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h
index faa4762cf1..3b116b4bad 100644
--- a/src/nvim/os/process.h
+++ b/src/nvim/os/process.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_OS_PROCESS_H
-#define NVIM_OS_PROCESS_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#ifdef MSWIN
+# include "nvim/api/private/defs.h" // IWYU pragma: keep
+#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/process.h.generated.h"
#endif
-
-#endif // NVIM_OS_PROCESS_H
diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c
index 43c89f8865..53169c0ef8 100644
--- a/src/nvim/os/pty_conpty_win.c
+++ b/src/nvim/os/pty_conpty_win.c
@@ -1,11 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <uv.h>
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifndef EXTENDED_STARTUPINFO_PRESENT
# define EXTENDED_STARTUPINFO_PRESENT 0x00080000
@@ -14,9 +11,9 @@
# define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016
#endif
-HRESULT (WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
-HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
-void (WINAPI *pClosePseudoConsole)(HPCON);
+HRESULT(WINAPI *pCreatePseudoConsole)(COORD, HANDLE, HANDLE, DWORD, HPCON *);
+HRESULT(WINAPI *pResizePseudoConsole)(HPCON, COORD);
+void(WINAPI *pClosePseudoConsole)(HPCON);
bool os_has_conpty_working(void)
{
diff --git a/src/nvim/os/pty_conpty_win.h b/src/nvim/os/pty_conpty_win.h
index 0c25a5970e..aa04cd1e84 100644
--- a/src/nvim/os/pty_conpty_win.h
+++ b/src/nvim/os/pty_conpty_win.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_PTY_CONPTY_WIN_H
-#define NVIM_OS_PTY_CONPTY_WIN_H
+#pragma once
#include "klib/kvec.h"
#include "nvim/os/input.h"
@@ -8,10 +7,10 @@
# define HPCON VOID *
#endif
-extern HRESULT (WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens)
+extern HRESULT(WINAPI *pCreatePseudoConsole) // NOLINT(whitespace/parens)
(COORD, HANDLE, HANDLE, DWORD, HPCON *);
-extern HRESULT (WINAPI *pResizePseudoConsole)(HPCON, COORD);
-extern void (WINAPI *pClosePseudoConsole)(HPCON);
+extern HRESULT(WINAPI *pResizePseudoConsole)(HPCON, COORD);
+extern void(WINAPI *pClosePseudoConsole)(HPCON);
typedef struct conpty {
HPCON pty;
@@ -21,5 +20,3 @@ typedef struct conpty {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_conpty_win.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_CONPTY_WIN_H
diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h
index 07d346be22..2c7a5f66bd 100644
--- a/src/nvim/os/pty_process.h
+++ b/src/nvim/os/pty_process.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_OS_PTY_PROCESS_H
-#define NVIM_OS_PTY_PROCESS_H
+#pragma once
#ifdef MSWIN
# include "nvim/os/pty_process_win.h"
#else
# include "nvim/os/pty_process_unix.h"
#endif
-#endif // NVIM_OS_PTY_PROCESS_H
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c
index 2413f0339b..f801646967 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_process_unix.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Some of the code came from pangoterm and libuv
#include <assert.h>
@@ -11,6 +8,7 @@
#include <string.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
+#include <uv.h>
// forkpty is not in POSIX, so headers are platform-specific
#if defined(__FreeBSD__) || defined(__DragonFly__)
@@ -31,16 +29,15 @@
# include <crt_externs.h>
#endif
-#include <uv.h>
-
#include "auto/config.h"
#include "klib/klist.h"
#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/process.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/log.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/pty_process.h"
#include "nvim/os/pty_process_unix.h"
@@ -286,7 +283,7 @@ static void init_child(PtyProcess *ptyproc)
return;
}
- char *prog = ptyproc->process.argv[0];
+ const char *prog = process_get_exepath(proc);
assert(proc->env);
environ = tv_dict_to_env(proc->env);
@@ -336,21 +333,21 @@ static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL
termios->c_lflag |= ECHOKE;
#endif
- termios->c_cc[VINTR] = 0x1f & 'C';
- termios->c_cc[VQUIT] = 0x1f & '\\';
- termios->c_cc[VERASE] = 0x7f;
- termios->c_cc[VKILL] = 0x1f & 'U';
- termios->c_cc[VEOF] = 0x1f & 'D';
- termios->c_cc[VEOL] = _POSIX_VDISABLE;
- termios->c_cc[VEOL2] = _POSIX_VDISABLE;
- termios->c_cc[VSTART] = 0x1f & 'Q';
- termios->c_cc[VSTOP] = 0x1f & 'S';
- termios->c_cc[VSUSP] = 0x1f & 'Z';
+ termios->c_cc[VINTR] = 0x1f & 'C';
+ termios->c_cc[VQUIT] = 0x1f & '\\';
+ termios->c_cc[VERASE] = 0x7f;
+ termios->c_cc[VKILL] = 0x1f & 'U';
+ termios->c_cc[VEOF] = 0x1f & 'D';
+ termios->c_cc[VEOL] = _POSIX_VDISABLE;
+ termios->c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios->c_cc[VSTART] = 0x1f & 'Q';
+ termios->c_cc[VSTOP] = 0x1f & 'S';
+ termios->c_cc[VSUSP] = 0x1f & 'Z';
termios->c_cc[VREPRINT] = 0x1f & 'R';
- termios->c_cc[VWERASE] = 0x1f & 'W';
- termios->c_cc[VLNEXT] = 0x1f & 'V';
- termios->c_cc[VMIN] = 1;
- termios->c_cc[VTIME] = 0;
+ termios->c_cc[VWERASE] = 0x1f & 'W';
+ termios->c_cc[VLNEXT] = 0x1f & 'V';
+ termios->c_cc[VMIN] = 1;
+ termios->c_cc[VTIME] = 0;
}
static int set_duplicating_descriptor(int fd, uv_pipe_t *pipe)
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_process_unix.h
index 0cc68cf3e9..92cc582832 100644
--- a/src/nvim/os/pty_process_unix.h
+++ b/src/nvim/os/pty_process_unix.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_PTY_PROCESS_UNIX_H
-#define NVIM_OS_PTY_PROCESS_UNIX_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_process.h"
#include <stdint.h>
#include <sys/ioctl.h>
@@ -27,5 +27,3 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_unix.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_PROCESS_UNIX_H
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c
index 6233a90638..ca2dce36ea 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_process_win.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
-#include "nvim/ascii.h"
-#include "nvim/mbyte.h" // for utf8_to_utf16, utf16_to_utf8
+#include "nvim/ascii_defs.h"
+#include "nvim/eval/typval.h"
+#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
@@ -172,11 +170,13 @@ void pty_process_close(PtyProcess *ptyproc)
void pty_process_close_master(PtyProcess *ptyproc)
FUNC_ATTR_NONNULL_ALL
-{}
+{
+}
void pty_process_teardown(Loop *loop)
FUNC_ATTR_NONNULL_ALL
-{}
+{
+}
static void pty_process_connect_cb(uv_connect_t *req, int status)
FUNC_ATTR_NONNULL_ALL
@@ -253,9 +253,9 @@ static int build_cmd_line(char **argv, wchar_t **cmd_line, bool is_cmdexe)
QUEUE_FOREACH(q, &args_q, {
ArgNode *arg_node = QUEUE_DATA(q, ArgNode, node);
xstrlcat(utf8_cmd_line, arg_node->arg, utf8_cmd_line_len);
+ QUEUE_REMOVE(q);
xfree(arg_node->arg);
xfree(arg_node);
- QUEUE_REMOVE(q);
if (!QUEUE_EMPTY(&args_q)) {
xstrlcat(utf8_cmd_line, " ", utf8_cmd_line_len);
}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_process_win.h
index ed7d765ac7..26cf387e54 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_process_win.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_PTY_PROCESS_WIN_H
-#define NVIM_OS_PTY_PROCESS_WIN_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/pty_process.h"
#include <uv.h>
@@ -37,5 +37,3 @@ static inline PtyProcess pty_process_init(Loop *loop, void *data)
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/pty_process_win.h.generated.h"
#endif
-
-#endif // NVIM_OS_PTY_PROCESS_WIN_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index f1e2c5440f..191be784e8 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
@@ -10,8 +7,7 @@
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -24,29 +20,30 @@
#include "nvim/event/wstream.h"
#include "nvim/ex_cmds.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/os/signal.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/rbuffer.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
@@ -134,6 +131,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
#define STYLE_VIMGLOB 2 // use "vimglob", for Posix sh
#define STYLE_PRINT 3 // use "print -N", for zsh
#define STYLE_BT 4 // `cmd` expansion, execute the pattern directly
+#define STYLE_GLOBSTAR 5 // use extended shell glob for bash (this uses extended
+ // globbing functionality with globstar, needs bash > 4)
int shell_style = STYLE_ECHO;
int check_spaces;
static bool did_find_nul = false;
@@ -141,10 +140,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// vimglob() function to define for Posix shell
static char *sh_vimglob_func =
"vimglob() { while [ $# -ge 1 ]; do echo \"$1\"; shift; done }; vimglob >";
+ // vimglob() function with globstar setting enabled, only for bash >= 4.X
+ static char *sh_globstar_opt =
+ "[[ ${BASH_VERSINFO[0]} -ge 4 ]] && shopt -s globstar; ";
bool is_fish_shell =
#if defined(UNIX)
- strncmp((char *)invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
+ strncmp(invocation_path_tail(p_sh, NULL), "fish", 4) == 0;
#else
false;
#endif
@@ -190,6 +192,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// If we use *zsh, "print -N" will work better than "glob".
// STYLE_VIMGLOB: NL separated
// If we use *sh*, we define "vimglob()".
+ // STYLE_GLOBSTAR: NL separated
+ // If we use *bash*, we define "vimglob() and enable globstar option".
// STYLE_ECHO: space separated.
// A shell we don't know, stay safe and use "echo".
if (num_pat == 1 && *pat[0] == '`'
@@ -203,9 +207,12 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
shell_style = STYLE_PRINT;
}
}
- if (shell_style == STYLE_ECHO
- && strstr(path_tail(p_sh), "sh") != NULL) {
- shell_style = STYLE_VIMGLOB;
+ if (shell_style == STYLE_ECHO) {
+ if (strstr(path_tail(p_sh), "bash") != NULL) {
+ shell_style = STYLE_GLOBSTAR;
+ } else if (strstr(path_tail(p_sh), "sh") != NULL) {
+ shell_style = STYLE_VIMGLOB;
+ }
}
// Compute the length of the command. We need 2 extra bytes: for the
@@ -214,6 +221,8 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
len = strlen(tempname) + 29;
if (shell_style == STYLE_VIMGLOB) {
len += strlen(sh_vimglob_func);
+ } else if (shell_style == STYLE_GLOBSTAR) {
+ len += strlen(sh_vimglob_func) + strlen(sh_globstar_opt);
}
for (i = 0; i < num_pat; i++) {
@@ -281,6 +290,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
STRCAT(command, "print -N >");
} else if (shell_style == STYLE_VIMGLOB) {
STRCAT(command, sh_vimglob_func);
+ } else if (shell_style == STYLE_GLOBSTAR) {
+ STRCAT(command, sh_globstar_opt);
+ STRCAT(command, sh_vimglob_func);
} else {
STRCAT(command, "echo >");
}
@@ -352,7 +364,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// When running in the background, give it some time to create the temp
// file, but don't wait for it to finish.
if (ampersand) {
- os_delay(10L, true);
+ os_delay(10, true);
}
xfree(command);
@@ -364,7 +376,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
if (!(flags & EW_SILENT)) {
msg_putchar('\n'); // clear bottom line quickly
cmdline_row = Rows - 1; // continue on last line
- msg(_(e_wildexpand));
+ msg(_(e_wildexpand), 0);
msg_start(); // don't overwrite this message
}
@@ -381,13 +393,13 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
if (fd == NULL) {
// Something went wrong, perhaps a file name with a special char.
if (!(flags & EW_SILENT)) {
- msg(_(e_wildexpand));
+ msg(_(e_wildexpand), 0);
msg_start(); // don't overwrite this message
}
xfree(tempname);
goto notfound;
}
- int fseek_res = fseek(fd, 0L, SEEK_END);
+ int fseek_res = fseek(fd, 0, SEEK_END);
if (fseek_res < 0) {
xfree(tempname);
fclose(fd);
@@ -399,11 +411,11 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
fclose(fd);
return FAIL;
}
-#if SIZEOF_LONG_LONG > SIZEOF_SIZE_T
- assert(templen <= (long long)SIZE_MAX); // NOLINT(runtime/int)
+#if 8 > SIZEOF_SIZE_T
+ assert(templen <= SIZE_MAX); // NOLINT(runtime/int)
#endif
len = (size_t)templen;
- fseek(fd, 0L, SEEK_SET);
+ fseek(fd, 0, SEEK_SET);
buffer = xmalloc(len + 1);
// fread() doesn't terminate buffer with NUL;
// appropriate termination (not always NUL) is done below.
@@ -430,7 +442,9 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
p = skipwhite(p); // skip to next entry
}
// file names are separated with NL
- } else if (shell_style == STYLE_BT || shell_style == STYLE_VIMGLOB) {
+ } else if (shell_style == STYLE_BT
+ || shell_style == STYLE_VIMGLOB
+ || shell_style == STYLE_GLOBSTAR) {
buffer[len] = NUL; // make sure the buffer ends in NUL
p = buffer;
for (i = 0; *p != NUL; i++) { // count number of entries
@@ -496,7 +510,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
(*file)[i] = p;
// Space or NL separates
if (shell_style == STYLE_ECHO || shell_style == STYLE_BT
- || shell_style == STYLE_VIMGLOB) {
+ || shell_style == STYLE_VIMGLOB || shell_style == STYLE_GLOBSTAR) {
while (!(shell_style == STYLE_ECHO && *p == ' ')
&& *p != '\n' && *p != NUL) {
p++;
@@ -715,7 +729,7 @@ int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg)
if (p_verbose > 3) {
verbose_enter();
- smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd);
+ smsg(0, _("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd);
msg_putchar('\n');
verbose_leave();
}
@@ -786,9 +800,9 @@ char *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len)
goto done;
}
- fseek(fd, 0L, SEEK_END);
+ fseek(fd, 0, SEEK_END);
size_t len = (size_t)ftell(fd); // get size of temp file
- fseek(fd, 0L, SEEK_SET);
+ fseek(fd, 0, SEEK_SET);
buffer = xmalloc(len + 1);
size_t i = fread(buffer, 1, len, fd);
@@ -876,9 +890,9 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
// Failed, probably 'shell' is not executable.
if (!silent) {
msg_puts(_("\nshell failed to start: "));
- msg_outtrans((char *)os_strerror(status));
+ msg_outtrans(os_strerror(status), 0);
msg_puts(": ");
- msg_outtrans(prog);
+ msg_outtrans(prog, 0);
msg_putchar('\n');
}
multiqueue_free(events);
@@ -1006,9 +1020,9 @@ static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, void *dat
/// Returns the previous decision if size=0.
static bool out_data_decide_throttle(size_t size)
{
- static uint64_t started = 0; // Start time of the current throttle.
- static size_t received = 0; // Bytes observed since last throttle.
- static size_t visit = 0; // "Pulse" count of the current throttle.
+ static uint64_t started = 0; // Start time of the current throttle.
+ static size_t received = 0; // Bytes observed since last throttle.
+ static size_t visit = 0; // "Pulse" count of the current throttle.
static char pulse_msg[] = { ' ', ' ', ' ', '\0' };
if (!size) {
@@ -1026,7 +1040,7 @@ static bool out_data_decide_throttle(size_t size)
started = os_hrtime();
} else {
uint64_t since = os_hrtime() - started;
- if (since < (visit * 0.1L * NS_1_SECOND)) {
+ if (since < (visit * (NS_1_SECOND / 10))) {
return true;
}
if (since > (3 * NS_1_SECOND)) {
@@ -1088,7 +1102,7 @@ static void out_data_ring(char *output, size_t size)
last_skipped_len = MAX_CHUNK_SIZE;
} else if (size > 0) {
// Length of the old data that can be kept.
- size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
+ size_t keep_len = MIN(last_skipped_len, MAX_CHUNK_SIZE - size);
size_t keep_start = last_skipped_len - keep_len;
// Shift the kept part of the old data to the start.
if (keep_start) {
@@ -1127,7 +1141,7 @@ static void out_data_append_to_screen(char *output, size_t *count, bool eof)
goto end;
}
- (void)msg_outtrans_len_attr(p, i, 0);
+ (void)msg_outtrans_len(p, i, 0);
p += i;
}
}
@@ -1180,7 +1194,7 @@ static size_t tokenize(const char *const str, char **const argv)
}
argc++;
- p = (const char *)skipwhite((p + len));
+ p = skipwhite((p + len));
}
return argc;
@@ -1221,12 +1235,12 @@ static size_t word_length(const char *str)
/// before we finish writing.
static void read_input(DynamicBuffer *buf)
{
- size_t written = 0, l = 0, len = 0;
+ size_t written = 0, len = 0;
linenr_T lnum = curbuf->b_op_start.lnum;
char *lp = ml_get(lnum);
- for (;;) {
- l = strlen(lp + written);
+ while (true) {
+ size_t l = strlen(lp + written);
if (l == 0) {
len = 0;
} else if (lp[written] == NL) {
diff --git a/src/nvim/os/shell.h b/src/nvim/os/shell.h
index 48503f2601..82c83543af 100644
--- a/src/nvim/os/shell.h
+++ b/src/nvim/os/shell.h
@@ -1,9 +1,6 @@
-#ifndef NVIM_OS_SHELL_H
-#define NVIM_OS_SHELL_H
+#pragma once
-#include <stdio.h>
-
-#include "nvim/types.h"
+#include <stddef.h> // IWYU pragma: keep
// Flags for os_call_shell() second argument
typedef enum {
@@ -19,4 +16,3 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/shell.h.generated.h"
#endif
-#endif // NVIM_OS_SHELL_H
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index b8daaabba2..c920cb655e 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -1,23 +1,24 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
+
#ifndef MSWIN
-# include <signal.h> // for sigset_t
+# include <signal.h>
#endif
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/eval.h"
#include "nvim/event/signal.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
-#include "nvim/memline.h"
#include "nvim/os/signal.h"
+#ifdef SIGPWR
+# include "nvim/memline.h"
+#endif
+
static SignalWatcher spipe, shup, squit, sterm, susr1, swinch;
#ifdef SIGPWR
static SignalWatcher spwr;
@@ -172,11 +173,10 @@ static void deadly_signal(int signum)
ILOG("got signal %d (%s)", signum, signal_name(signum));
- snprintf(IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n",
- signal_name(signum));
+ snprintf(IObuff, IOSIZE, "Vim: Caught deadly signal '%s'\r\n", signal_name(signum));
// Preserve files and exit.
- preserve_exit();
+ preserve_exit(IObuff);
}
static void on_signal(SignalWatcher *handle, int signum, void *data)
diff --git a/src/nvim/os/signal.h b/src/nvim/os/signal.h
index 5d8cc6f661..83a0a9c91b 100644
--- a/src/nvim/os/signal.h
+++ b/src/nvim/os/signal.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_OS_SIGNAL_H
-#define NVIM_OS_SIGNAL_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/signal.h.generated.h"
#endif
-#endif // NVIM_OS_SIGNAL_H
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index 6b07b6ef70..7691aa5122 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -1,11 +1,11 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
+#include <assert.h>
#include <stdbool.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
@@ -57,6 +57,39 @@ static const char *const xdg_defaults[] = {
#endif
};
+/// Get the value of $NVIM_APPNAME or "nvim" if not set.
+///
+/// @return $NVIM_APPNAME value
+const char *get_appname(void)
+{
+ const char *env_val = os_getenv("NVIM_APPNAME");
+ if (env_val == NULL || *env_val == '\0') {
+ env_val = "nvim";
+ }
+ return env_val;
+}
+
+/// Ensure that APPNAME is valid. Must be a name or relative path.
+bool appname_is_valid(void)
+{
+ const char *appname = get_appname();
+ if (path_is_absolute(appname)
+ // TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it?
+ || strequal(appname, "/")
+ || strequal(appname, "\\")
+ || strequal(appname, ".")
+ || strequal(appname, "..")
+#ifdef BACKSLASH_IN_FILENAME
+ || strstr(appname, "\\..") != NULL
+ || strstr(appname, "..\\") != NULL
+#endif
+ || strstr(appname, "/..") != NULL
+ || strstr(appname, "../") != NULL) {
+ return false;
+ }
+ return true;
+}
+
/// Return XDG variable value
///
/// @param[in] idx XDG variable to use.
@@ -92,7 +125,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
ret = "/tmp/";
}
size_t len = strlen(ret);
- ret = xstrndup(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
+ ret = xmemdupz(ret, len >= 2 ? len - 1 : 0); // Trim trailing slash.
}
return ret;
@@ -100,25 +133,28 @@ char *stdpaths_get_xdg_var(const XDGVarType idx)
/// Return Nvim-specific XDG directory subpath.
///
-/// Windows: Uses "…/nvim-data" for kXDGDataHome to avoid storing
+/// Windows: Uses "…/$NVIM_APPNAME-data" for kXDGDataHome to avoid storing
/// configuration and data files in the same path. #4403
///
/// @param[in] idx XDG directory to use.
///
-/// @return [allocated] "{xdg_directory}/nvim"
+/// @return [allocated] "{xdg_directory}/$NVIM_APPNAME"
char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
char *dir = stdpaths_get_xdg_var(idx);
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
+ assert(appname_len < (IOSIZE - sizeof("-data")));
+
if (dir) {
+ xstrlcpy(IObuff, appname, appname_len + 1);
#if defined(MSWIN)
- dir = concat_fnames_realloc(dir,
- ((idx == kXDGDataHome
- || idx == kXDGStateHome) ? "nvim-data" : "nvim"),
- true);
-#else
- dir = concat_fnames_realloc(dir, "nvim", true);
+ if (idx == kXDGDataHome || idx == kXDGStateHome) {
+ xstrlcat(IObuff, "-data", IOSIZE);
+ }
#endif
+ dir = concat_fnames_realloc(dir, IObuff, true);
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(dir);
@@ -131,7 +167,7 @@ char *get_xdg_home(const XDGVarType idx)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_CACHE_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_CACHE_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_cache_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -142,7 +178,7 @@ char *stdpaths_user_cache_subpath(const char *fname)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_CONFIG_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_CONFIG_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_conf_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -153,7 +189,7 @@ char *stdpaths_user_conf_subpath(const char *fname)
///
/// @param[in] fname New component of the path.
///
-/// @return [allocated] `$XDG_DATA_HOME/nvim/{fname}`
+/// @return [allocated] `$XDG_DATA_HOME/$NVIM_APPNAME/{fname}`
char *stdpaths_user_data_subpath(const char *fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
@@ -166,7 +202,7 @@ char *stdpaths_user_data_subpath(const char *fname)
/// @param[in] trailing_pathseps Amount of trailing path separators to add.
/// @param[in] escape_commas If true, all commas will be escaped.
///
-/// @return [allocated] `$XDG_STATE_HOME/nvim/{fname}`.
+/// @return [allocated] `$XDG_STATE_HOME/$NVIM_APPNAME/{fname}`.
char *stdpaths_user_state_subpath(const char *fname, const size_t trailing_pathseps,
const bool escape_commas)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
diff --git a/src/nvim/os/stdpaths_defs.h b/src/nvim/os/stdpaths_defs.h
index f94c511fe7..985688390d 100644
--- a/src/nvim/os/stdpaths_defs.h
+++ b/src/nvim/os/stdpaths_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_OS_STDPATHS_DEFS_H
-#define NVIM_OS_STDPATHS_DEFS_H
+#pragma once
/// List of possible XDG variables
typedef enum {
@@ -12,5 +11,3 @@ typedef enum {
kXDGConfigDirs, ///< XDG_CONFIG_DIRS
kXDGDataDirs, ///< XDG_DATA_DIRS
} XDGVarType;
-
-#endif // NVIM_OS_STDPATHS_DEFS_H
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 873302a27d..49b43af6c0 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -1,10 +1,6 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stdlib.h>
#include <string.h>
#include <time.h>
@@ -12,32 +8,20 @@
#include "auto/config.h"
#include "nvim/event/loop.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
-struct tm;
-
-static uv_mutex_t delay_mutex;
-static uv_cond_t delay_cond;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h" // IWYU pragma: export
#endif
-/// Initializes the time module
-void time_init(void)
-{
- uv_mutex_init(&delay_mutex);
- uv_cond_init(&delay_cond);
-}
-
/// Gets a high-resolution (nanosecond), monotonically-increasing time relative
/// to an arbitrary time in the past.
///
@@ -73,55 +57,28 @@ uint64_t os_now(void)
void os_delay(uint64_t ms, bool ignoreinput)
{
DLOG("%" PRIu64 " ms", ms);
- if (ignoreinput) {
- if (ms > INT_MAX) {
- ms = INT_MAX;
- }
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
- } else {
- os_microdelay(ms * 1000U, ignoreinput);
+ if (ms > INT_MAX) {
+ ms = INT_MAX;
}
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms,
+ ignoreinput ? got_int : os_input_ready(NULL));
}
-/// Sleeps for `us` microseconds.
+/// Sleeps for `ms` milliseconds without checking for events or interrupts.
+///
+/// This blocks even "fast" events which is quite disruptive. This should only
+/// be used in debug code. Prefer os_delay() and decide if the delay should be
+/// interrupted by input or only a CTRL-C.
///
/// @see uv_sleep() (libuv v1.34.0)
///
/// @param us Number of microseconds to sleep.
-/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
-/// If false, waiting is aborted on any input.
-void os_microdelay(uint64_t us, bool ignoreinput)
+void os_sleep(uint64_t ms)
{
- uint64_t elapsed = 0U;
- uint64_t base = uv_hrtime();
- // Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
- const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX;
-
- uv_mutex_lock(&delay_mutex);
-
- while (elapsed < ns) {
- // If ignoring input, we simply wait the full delay.
- // Else we check for input in ~100ms intervals.
- const uint64_t ns_delta = ignoreinput
- ? ns - elapsed
- : MIN(ns - elapsed, 100000000U); // 100ms
-
- const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
- if (0 != rv && UV_ETIMEDOUT != rv) {
- abort();
- break;
- } // Else: Timeout proceeded normally.
-
- if (!ignoreinput && os_char_avail()) {
- break;
- }
-
- const uint64_t now = uv_hrtime();
- elapsed += now - base;
- base = now;
+ if (ms > UINT_MAX) {
+ ms = UINT_MAX;
}
-
- uv_mutex_unlock(&delay_mutex);
+ uv_sleep((unsigned)ms);
}
// Cache of the current timezone name as retrieved from TZ, or an empty string
diff --git a/src/nvim/os/time.h b/src/nvim/os/time.h
index 1b6c667dbb..2748ba6953 100644
--- a/src/nvim/os/time.h
+++ b/src/nvim/os/time.h
@@ -1,13 +1,10 @@
-#ifndef NVIM_OS_TIME_H
-#define NVIM_OS_TIME_H
+#pragma once
-#include <stdbool.h>
-#include <stdint.h>
-#include <time.h>
+#include <stddef.h> // IWYU pragma: keep
+#include <time.h> // IWYU pragma: keep
-typedef uint64_t Timestamp;
+#include "nvim/os/time_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.h.generated.h"
#endif
-#endif // NVIM_OS_TIME_H
diff --git a/src/nvim/os/time_defs.h b/src/nvim/os/time_defs.h
new file mode 100644
index 0000000000..9b71a6764d
--- /dev/null
+++ b/src/nvim/os/time_defs.h
@@ -0,0 +1,4 @@
+#pragma once
+
+#include <stdint.h>
+typedef uint64_t Timestamp;
diff --git a/src/nvim/os/tty.c b/src/nvim/os/tty.c
index b5124bd83a..e683b9383f 100644
--- a/src/nvim/os/tty.c
+++ b/src/nvim/os/tty.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
//
// Terminal/console utils
//
diff --git a/src/nvim/os/tty.h b/src/nvim/os/tty.h
index d771e63768..a24d875c05 100644
--- a/src/nvim/os/tty.h
+++ b/src/nvim/os/tty.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_OS_TTY_H
-#define NVIM_OS_TTY_H
+#pragma once
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/tty.h.generated.h"
+# include "os/tty.h.generated.h" // IWYU pragma: export
#endif
-#endif // NVIM_OS_TTY_H
diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h
index 8d002fc5e9..d2bec7b361 100644
--- a/src/nvim/os/unix_defs.h
+++ b/src/nvim/os/unix_defs.h
@@ -1,14 +1,18 @@
-#ifndef NVIM_OS_UNIX_DEFS_H
-#define NVIM_OS_UNIX_DEFS_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/os_defs.h"
+// IWYU pragma: begin_exports
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
#include <sys/param.h>
+#include <sys/socket.h>
#include <unistd.h>
#if defined(HAVE_TERMIOS_H)
# include <termios.h>
#endif
-
-// POSIX.1-2008 says that NAME_MAX should be in here
-#include <limits.h>
+// IWYU pragma: end_exports
#define TEMP_DIR_NAMES { "$TMPDIR", "/tmp", ".", "~" }
#define TEMP_FILE_PATH_MAXLEN 256
@@ -21,5 +25,3 @@
// Character that separates entries in $PATH.
#define ENV_SEPCHAR ':'
#define ENV_SEPSTR ":"
-
-#endif // NVIM_OS_UNIX_DEFS_H
diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c
index ef2986246b..ae0994a73f 100644
--- a/src/nvim/os/users.c
+++ b/src/nvim/os/users.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// users.c -- operating system user information
#include <stdbool.h>
@@ -9,17 +6,20 @@
#include <uv.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/garray.h"
#include "nvim/memory.h"
#include "nvim/os/os.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
-#ifdef HAVE_PWD_H
+#include "nvim/vim_defs.h"
+#ifdef HAVE_PWD_FUNCS
# include <pwd.h>
#endif
#ifdef MSWIN
# include <lm.h>
+
+# include "nvim/mbyte.h"
+# include "nvim/message.h"
#endif
// All user names (for ~user completion as done by shell).
@@ -30,7 +30,7 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE;
static void add_user(garray_T *users, char *user, bool need_copy)
{
char *user_copy = (user != NULL && need_copy)
- ? xstrdup(user) : user;
+ ? xstrdup(user) : user;
if (user_copy == NULL || *user_copy == NUL) {
if (need_copy) {
@@ -50,7 +50,7 @@ int os_get_usernames(garray_T *users)
}
ga_init(users, sizeof(char *), 20);
-#if defined(HAVE_GETPWENT) && defined(HAVE_PWD_H)
+#ifdef HAVE_PWD_FUNCS
{
struct passwd *pw;
@@ -81,7 +81,7 @@ int os_get_usernames(garray_T *users)
}
}
#endif
-#if defined(HAVE_GETPWNAM)
+#ifdef HAVE_PWD_FUNCS
{
const char *user_env = os_getenv("USER");
@@ -141,7 +141,7 @@ int os_get_username(char *s, size_t len)
/// @return OK if a username was found, else FAIL.
int os_get_uname(uv_uid_t uid, char *s, size_t len)
{
-#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
+#ifdef HAVE_PWD_FUNCS
struct passwd *pw;
if ((pw = getpwuid(uid)) != NULL // NOLINT(runtime/threadsafe_fn)
@@ -159,7 +159,7 @@ int os_get_uname(uv_uid_t uid, char *s, size_t len)
/// Caller must free() the returned string.
char *os_get_userdir(const char *name)
{
-#if defined(HAVE_GETPWNAM) && defined(HAVE_PWD_H)
+#ifdef HAVE_PWD_FUNCS
if (name == NULL || *name == NUL) {
return NULL;
}
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 4f8a242a51..852059f78b 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -1,5 +1,5 @@
-#ifndef NVIM_OS_WIN_DEFS_H
-#define NVIM_OS_WIN_DEFS_H
+#pragma once
+// IWYU pragma: private, include "nvim/os/os_defs.h"
#ifndef MSWIN
# error Header must be included only when compiling for Windows.
@@ -86,5 +86,3 @@ typedef int mode_t;
#ifndef STDERR_FILENO
# define STDERR_FILENO 2
#endif
-
-#endif // NVIM_OS_WIN_DEFS_H
diff --git a/src/nvim/path.c b/src/nvim/path.c
index e4c2253357..c7212c7ade 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -1,8 +1,4 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
-#include <ctype.h>
#include <limits.h>
#include <stdbool.h>
#include <stddef.h>
@@ -11,32 +7,33 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
#include "nvim/regexp.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
enum {
@@ -620,7 +617,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
static int stardepth = 0; // depth for "**" expansion
// Expanding "**" may take a long time, check for CTRL-C.
- if (stardepth > 0) {
+ if (stardepth > 0 && !(flags & EW_NOBREAK)) {
os_breakcheck();
if (got_int) {
return 0;
@@ -641,7 +638,7 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
while (*path_end != NUL) {
// May ignore a wildcard that has a backslash before it; it will
// be removed by rem_backslash() or file_pat_to_reg_pat() below.
- if (path_end >= path + wildoff && rem_backslash((char *)path_end)) {
+ if (path_end >= path + wildoff && rem_backslash(path_end)) {
*p++ = *path_end++;
} else if (vim_ispathsep_nocolon(*path_end)) {
if (e != NULL) {
@@ -649,14 +646,16 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
}
s = p + 1;
} else if (path_end >= path + wildoff
+#ifdef MSWIN
+ && vim_strchr("*?[~", (uint8_t)(*path_end)) != NULL
+#else
&& (vim_strchr("*?[{~$", (uint8_t)(*path_end)) != NULL
-#ifndef MSWIN
- || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char((char *)path_end)))
+ || (!p_fic && (flags & EW_ICASE) && mb_isalpha(utf_ptr2char(path_end))))
#endif
- )) { // NOLINT(whitespace/parens)
+ ) { // NOLINT(whitespace/parens)
e = p;
}
- len = (size_t)(utfc_ptr2len((char *)path_end));
+ len = (size_t)(utfc_ptr2len(path_end));
memcpy(p, path_end, len);
p += len;
path_end += len;
@@ -701,7 +700,8 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
if (flags & (EW_NOERROR | EW_NOTWILD)) {
emsg_silent++;
}
- regmatch.regprog = vim_regcomp(pat, RE_MAGIC);
+ bool nobreak = (flags & EW_NOBREAK);
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC | (nobreak ? RE_NOBREAK : 0));
if (flags & (EW_NOERROR | EW_NOTWILD)) {
emsg_silent--;
}
@@ -727,14 +727,14 @@ static size_t do_path_expand(garray_T *gap, const char *path, size_t wildoff, in
char *dirpath = (*buf == NUL ? "." : buf);
if (os_file_is_readable(dirpath) && os_scandir(&dir, dirpath)) {
// Find all matching entries.
- char *name;
+ const char *name;
scandir_next_with_dots(NULL); // initialize
- while (!got_int && (name = (char *)scandir_next_with_dots(&dir)) != NULL) {
+ while (!got_int && (name = scandir_next_with_dots(&dir)) != NULL) {
if ((name[0] != '.'
|| starts_with_dot
|| ((flags & EW_DODOT)
&& name[1] != NUL
- && (name[1] != '.' || name[2] != NUL))) // -V557
+ && (name[1] != '.' || name[2] != NUL)))
&& ((regmatch.regprog != NULL && vim_regexec(&regmatch, name, 0))
|| ((flags & EW_NOTWILD)
&& path_fnamencmp(path + (s - buf), name, (size_t)(e - s)) == 0))) {
@@ -810,7 +810,7 @@ static int find_previous_pathsep(char *path, char **psep)
}
/// Returns true if "maybe_unique" is unique wrt other_paths in "gap".
-/// "maybe_unique" is the end portion of "((char_u **)gap->ga_data)[i]".
+/// "maybe_unique" is the end portion of "((char **)gap->ga_data)[i]".
static bool is_unique(char *maybe_unique, garray_T *gap, int i)
{
char **other_paths = gap->ga_data;
@@ -879,7 +879,7 @@ static void expand_path_option(char *curdir, garray_T *gap)
}
STRMOVE(buf + len + 1, buf);
STRCPY(buf, curdir);
- buf[len] = (char_u)PATHSEP;
+ buf[len] = PATHSEP;
simplify_filename(buf);
}
@@ -973,7 +973,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
for (int i = 0; i < gap->ga_len && !got_int; i++) {
char *path = fnames[i];
int is_in_curdir;
- char *dir_end = (char *)gettail_dir((const char *)path);
+ const char *dir_end = gettail_dir(path);
char *pathsep_p;
char *path_cutoff;
@@ -993,7 +993,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
if (pattern[0] == '*' && pattern[1] == '*'
&& vim_ispathsep_nocolon(pattern[2])
&& path_cutoff != NULL
- && vim_regexec(&regmatch, path_cutoff, (colnr_T)0)
+ && vim_regexec(&regmatch, path_cutoff, 0)
&& is_unique(path_cutoff, gap, i)) {
sort_again = true;
memmove(path, path_cutoff, strlen(path_cutoff) + 1);
@@ -1002,7 +1002,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
// unique path. We start at the end of the path. */
pathsep_p = path + len - 1;
while (find_previous_pathsep(path, &pathsep_p)) {
- if (vim_regexec(&regmatch, pathsep_p + 1, (colnr_T)0)
+ if (vim_regexec(&regmatch, pathsep_p + 1, 0)
&& is_unique(pathsep_p + 1, gap, i)
&& path_cutoff != NULL && pathsep_p + 1 >= path_cutoff) {
sort_again = true;
@@ -1162,7 +1162,7 @@ static bool has_env_var(char *p)
// Return true if "p" contains a special wildcard character, one that Vim
// cannot expand, requires using a shell.
-static bool has_special_wildchar(char *p)
+static bool has_special_wildchar(char *p, int flags)
{
for (; *p; MB_PTR_ADV(p)) {
// Disallow line break characters.
@@ -1173,6 +1173,10 @@ static bool has_special_wildchar(char *p)
if (*p == '\\' && p[1] != NUL && p[1] != '\r' && p[1] != '\n') {
p++;
} else if (vim_strchr(SPECIAL_WILDCHAR, (uint8_t)(*p)) != NULL) {
+ // Need a shell for curly braces only when including non-existing files.
+ if (*p == '{' && !(flags & EW_NOTFOUND)) {
+ continue;
+ }
// A { must be followed by a matching }.
if (*p == '{' && vim_strchr(p, '}') == NULL) {
continue;
@@ -1232,7 +1236,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i
// avoids starting the shell for each argument separately.
// For `=expr` do use the internal function.
for (int i = 0; i < num_pat; i++) {
- if (has_special_wildchar(pat[i])
+ if (has_special_wildchar(pat[i], flags)
&& !(vim_backtick(pat[i]) && pat[i][1] == '=')) {
return os_expand_wildcards(num_pat, pat, num_file, file, flags);
}
@@ -1368,10 +1372,10 @@ static int expand_backtick(garray_T *gap, char *pat, int flags)
int cnt = 0;
// Create the command: lop off the backticks.
- char *cmd = xstrnsave(pat + 1, strlen(pat) - 2);
+ char *cmd = xmemdupz(pat + 1, strlen(pat) - 2);
if (*cmd == '=') { // `={expr}`: Expand expression
- buffer = eval_to_string(cmd + 1, &p, true);
+ buffer = eval_to_string(cmd + 1, true);
} else {
buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL);
}
@@ -1498,11 +1502,10 @@ void addfile(garray_T *gap, char *f, int flags)
void simplify_filename(char *filename)
{
int components = 0;
- char *p, *tail, *start;
bool stripping_disabled = false;
bool relative = true;
- p = filename;
+ char *p = filename;
#ifdef BACKSLASH_IN_FILENAME
if (p[0] != NUL && p[1] == ':') { // skip "x:"
p += 2;
@@ -1515,7 +1518,7 @@ void simplify_filename(char *filename)
p++;
} while (vim_ispathsep(*p));
}
- start = p; // remember start after "c:/" or "/" or "///"
+ char *start = p; // remember start after "c:/" or "/" or "///"
do {
// At this point "p" is pointing to the char following a single "/"
@@ -1531,7 +1534,7 @@ void simplify_filename(char *filename)
// and there is no trailing path separator, either strip "/." if
// we are after "start", or strip "." if we are at the beginning
// of an absolute path name.
- tail = p + 1;
+ char *tail = p + 1;
if (p[1] != NUL) {
while (vim_ispathsep(*tail)) {
MB_PTR_ADV(tail);
@@ -1544,21 +1547,20 @@ void simplify_filename(char *filename)
} else if (p[0] == '.' && p[1] == '.'
&& (vim_ispathsep(p[2]) || p[2] == NUL)) {
// Skip to after ".." or "../" or "..///".
- tail = p + 2;
+ char *tail = p + 2;
while (vim_ispathsep(*tail)) {
MB_PTR_ADV(tail);
}
if (components > 0) { // strip one preceding component
bool do_strip = false;
- char saved_char;
// Don't strip for an erroneous file name.
if (!stripping_disabled) {
// If the preceding component does not exist in the file
// system, we strip it. On Unix, we don't accept a symbolic
// link that refers to a non-existent file.
- saved_char = p[-1];
+ char saved_char = p[-1];
p[-1] = NUL;
FileInfo file_info;
if (!os_fileinfo_link(filename, &file_info)) {
@@ -1661,10 +1663,15 @@ void simplify_filename(char *filename)
static char *eval_includeexpr(const char *const ptr, const size_t len)
{
+ const sctx_T save_sctx = current_sctx;
set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
- char *res = eval_to_string_safe(curbuf->b_p_inex, NULL,
+ current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx;
+
+ char *res = eval_to_string_safe(curbuf->b_p_inex,
was_set_insecurely(curwin, "includeexpr", OPT_LOCAL));
+
set_vim_var_string(VV_FNAME, NULL, 0);
+ current_sctx = save_sctx;
return res;
}
@@ -1690,8 +1697,11 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
}
if (options & FNAME_EXP) {
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS, true,
- rel_fname);
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ true, rel_fname, &file_to_find, &search_ctx);
// If the file could not be found in a normal way, try applying
// 'includeexpr' (unless done already).
@@ -1702,7 +1712,7 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
ptr = tofree;
len = strlen(ptr);
file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- true, rel_fname);
+ true, rel_fname, &file_to_find, &search_ctx);
}
}
if (file_name == NULL && (options & FNAME_MESS)) {
@@ -1716,9 +1726,12 @@ char *find_file_name_in_path(char *ptr, size_t len, int options, long count, cha
// appears several times in the path.
while (file_name != NULL && --count > 0) {
xfree(file_name);
- file_name =
- find_file_in_path(ptr, len, options, false, rel_fname);
+ file_name = find_file_in_path(ptr, len, options, false, rel_fname,
+ &file_to_find, &search_ctx);
}
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
} else {
file_name = xstrnsave(ptr, len);
}
@@ -1794,11 +1807,11 @@ bool path_with_extension(const char *path, const char *extension)
if (!last_dot) {
return false;
}
- return strcmp(last_dot + 1, extension) == 0;
+ return mb_strcmp_ic((bool)p_fic, last_dot + 1, extension) == 0;
}
/// Return true if "name" is a full (absolute) path name or URL.
-bool vim_isAbsName(char *name)
+bool vim_isAbsName(const char *name)
{
return path_with_url(name) != 0 || path_is_absolute(name);
}
@@ -1859,7 +1872,7 @@ char *fix_fname(const char *fname)
#ifdef UNIX
return FullName_save(fname, true);
#else
- if (!vim_isAbsName((char *)fname)
+ if (!vim_isAbsName(fname)
|| strstr(fname, "..") != NULL
|| strstr(fname, "//") != NULL
# ifdef BACKSLASH_IN_FILENAME
@@ -1911,8 +1924,8 @@ void path_fix_case(char *name)
return;
}
- char *entry;
- while ((entry = (char *)os_scandir_next(&dir))) {
+ const char *entry;
+ while ((entry = os_scandir_next(&dir))) {
// Only accept names that differ in case and are the same byte
// length. TODO: accept different length name.
if (STRICMP(tail, entry) == 0 && strlen(tail) == strlen(entry)) {
@@ -1923,7 +1936,7 @@ void path_fix_case(char *name)
xstrlcpy(newname + (tail - name), entry,
(size_t)(MAXPATHL - (tail - name) + 1));
FileInfo file_info_new;
- if (os_fileinfo_link((char *)newname, &file_info_new)
+ if (os_fileinfo_link(newname, &file_info_new)
&& os_fileinfo_id_equal(&file_info, &file_info_new)) {
STRCPY(tail, entry);
break;
@@ -1956,11 +1969,11 @@ bool same_directory(char *f1, char *f2)
return false;
}
- (void)vim_FullName(f1, (char *)ffname, MAXPATHL, false);
+ (void)vim_FullName(f1, ffname, MAXPATHL, false);
t1 = path_tail_with_sep(ffname);
t2 = path_tail_with_sep(f2);
return t1 - ffname == t2 - f2
- && pathcmp((char *)ffname, f2, (int)(t1 - ffname)) == 0;
+ && pathcmp(ffname, f2, (int)(t1 - ffname)) == 0;
}
// Compare path "p[]" to "q[]".
@@ -1969,12 +1982,11 @@ bool same_directory(char *f1, char *f2)
int pathcmp(const char *p, const char *q, int maxlen)
{
int i, j;
- int c1, c2;
const char *s = NULL;
for (i = 0, j = 0; maxlen < 0 || (i < maxlen && j < maxlen);) {
- c1 = utf_ptr2char(p + i);
- c2 = utf_ptr2char(q + j);
+ int c1 = utf_ptr2char(p + i);
+ int c2 = utf_ptr2char(q + j);
// End of "p": check if "q" also ends or just has a slash.
if (c1 == NUL) {
@@ -2016,12 +2028,12 @@ int pathcmp(const char *p, const char *q, int maxlen)
return 0;
}
- c1 = utf_ptr2char(s + i);
- c2 = utf_ptr2char(s + i + utfc_ptr2len(s + i));
+ int c1 = utf_ptr2char(s + i);
+ int c2 = utf_ptr2char(s + i + utfc_ptr2len(s + i));
// ignore a trailing slash, but not "//" or ":/"
if (c2 == NUL
&& i > 0
- && !after_pathsep((char *)s, (char *)s + i)
+ && !after_pathsep(s, s + i)
#ifdef BACKSLASH_IN_FILENAME
&& (c1 == '/' || c1 == '\\')
#else
@@ -2075,17 +2087,17 @@ char *path_shorten_fname(char *full_path, char *dir_name)
assert(dir_name != NULL);
size_t len = strlen(dir_name);
- // If dir_name is a path head, full_path can always be made relative.
- if (len == (size_t)path_head_length() && is_path_head(dir_name)) {
- return full_path + len;
- }
-
// If full_path and dir_name do not match, it's impossible to make one
// relative to the other.
if (path_fnamencmp(dir_name, full_path, len) != 0) {
return NULL;
}
+ // If dir_name is a path head, full_path can always be made relative.
+ if (len == (size_t)path_head_length() && is_path_head(dir_name)) {
+ return full_path + len;
+ }
+
char *p = full_path + len;
// If *p is not pointing to a path separator, this means that full_path's
@@ -2117,7 +2129,7 @@ int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags)
int ret = FAIL;
char *eval_pat = NULL;
char *exp_pat = *pat;
- char *ignored_msg;
+ const char *ignored_msg;
size_t usedlen;
const bool is_cur_alt_file = *exp_pat == '%' || *exp_pat == '#';
bool star_follows = false;
@@ -2172,12 +2184,7 @@ int expand_wildcards_eval(char **pat, int *num_file, char ***file, int flags)
/// NULL or points to "".
int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int flags)
{
- int retval;
- int i, j;
- char *p;
- int non_suf_match; // number without matching suffix
-
- retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
+ int retval = gen_expand_wildcards(num_pat, pat, num_files, files, flags);
// When keeping all matches, return here
if ((flags & EW_KEEPALL) || retval == FAIL) {
@@ -2186,18 +2193,16 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int
// Remove names that match 'wildignore'.
if (*p_wig) {
- char *ffname;
-
// check all files in (*files)[]
assert(*num_files == 0 || *files != NULL);
- for (i = 0; i < *num_files; i++) {
- ffname = FullName_save((*files)[i], false);
+ for (int i = 0; i < *num_files; i++) {
+ char *ffname = FullName_save((*files)[i], false);
assert((*files)[i] != NULL);
assert(ffname != NULL);
if (match_file_list(p_wig, (*files)[i], ffname)) {
// remove this matching file from the list
xfree((*files)[i]);
- for (j = i; j + 1 < *num_files; j++) {
+ for (int j = i; j + 1 < *num_files; j++) {
(*files)[j] = (*files)[j + 1];
}
(*num_files)--;
@@ -2211,12 +2216,12 @@ int expand_wildcards(int num_pat, char **pat, int *num_files, char ***files, int
// Skip when interrupted, the result probably won't be used.
assert(*num_files == 0 || *files != NULL);
if (*num_files > 1 && !got_int) {
- non_suf_match = 0;
- for (i = 0; i < *num_files; i++) {
+ int non_suf_match = 0; // number without matching suffix
+ for (int i = 0; i < *num_files; i++) {
if (!match_suffix((*files)[i])) {
// Move the name without matching suffix to the front of the list.
- p = (*files)[i];
- for (j = i; j > non_suf_match; j--) {
+ char *p = (*files)[i];
+ for (int j = i; j > non_suf_match; j--) {
(*files)[j] = (*files)[j - 1];
}
(*files)[non_suf_match++] = p;
@@ -2354,11 +2359,11 @@ int append_path(char *path, const char *to_append, size_t max_len)
/// @return FAIL for failure, OK for success.
static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
{
- char *p;
+ const char *p;
*buf = NUL;
char *relative_directory = xmalloc(len);
- char *end_of_path = (char *)fname;
+ const char *end_of_path = fname;
// expand it if forced or not an absolute path
if (force || !path_is_absolute(fname)) {
@@ -2375,7 +2380,6 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
end_of_path = p + 1;
} else {
relative_directory[0] = NUL;
- end_of_path = (char *)fname;
}
if (FAIL == path_full_dir_name(relative_directory, buf, len)) {
diff --git a/src/nvim/path.h b/src/nvim/path.h
index c8d192dffe..89f939dd02 100644
--- a/src/nvim/path.h
+++ b/src/nvim/path.h
@@ -1,9 +1,10 @@
-#ifndef NVIM_PATH_H
-#define NVIM_PATH_H
+#pragma once
+
+#include <stddef.h> // IWYU pragma: keep
#include "nvim/func_attr.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Flags for expand_wildcards()
#define EW_DIR 0x01 // include directory names
@@ -26,6 +27,7 @@
#define EW_DODOT 0x4000 // also files starting with a dot
#define EW_EMPTYOK 0x8000 // no matches is not an error
#define EW_NOTENV 0x10000 // do not expand environment variables
+#define EW_NOBREAK 0x20000 // do not invoke breakcheck
/// Return value for the comparison of two files. Also @see path_full_compare.
typedef enum file_comparison {
@@ -39,4 +41,3 @@ typedef enum file_comparison {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "path.h.generated.h"
#endif
-#endif // NVIM_PATH_H
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 5469d94800..6e9f92c193 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -1,223 +1,34 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// plines.c: calculate the vertical and horizontal size of text in a window
-#include <assert.h>
-#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.c.generated.h"
#endif
-/// Functions calculating vertical size of text when displayed inside a window.
-/// Calls horizontal size functions defined below.
-
-/// Return the number of window lines occupied by buffer line "lnum".
-/// Includes any filler lines.
-///
-/// @param winheight when true limit to window height
-int plines_win(win_T *wp, linenr_T lnum, bool winheight)
-{
- // Check for filler lines above this buffer line. When folded the result
- // is one line anyway.
- return plines_win_nofill(wp, lnum, winheight) + win_get_fill(wp, lnum);
-}
-
-/// Return the number of filler lines above "lnum".
-///
-/// @param wp
-/// @param lnum
-///
-/// @return Number of filler lines above lnum
-int win_get_fill(win_T *wp, linenr_T lnum)
-{
- int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone);
-
- // be quick when there are no filler lines
- if (diffopt_filler()) {
- int n = diff_check(wp, lnum);
-
- if (n > 0) {
- return virt_lines + n;
- }
- }
- return virt_lines;
-}
-
-bool win_may_fill(win_T *wp)
-{
- return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks;
-}
-
-/// Return the number of window lines occupied by buffer line "lnum".
-/// Does not include filler lines.
-///
-/// @param winheight when true limit to window height
-int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight)
-{
- if (!wp->w_p_wrap) {
- return 1;
- }
-
- if (wp->w_width_inner == 0) {
- return 1;
- }
-
- // Folded lines are handled just like an empty line.
- if (lineFolded(wp, lnum)) {
- return 1;
- }
-
- const int lines = plines_win_nofold(wp, lnum);
- if (winheight && lines > wp->w_height_inner) {
- return wp->w_height_inner;
- }
- return lines;
-}
-
-/// @Return number of window lines physical line "lnum" will occupy in window
-/// "wp". Does not care about folding, 'wrap' or 'diff'.
-int plines_win_nofold(win_T *wp, linenr_T lnum)
-{
- char *s;
- unsigned int col;
- int width;
-
- s = ml_get_buf(wp->w_buffer, lnum, false);
- if (*s == NUL) { // empty line
- return 1;
- }
- col = win_linetabsize(wp, lnum, s, MAXCOL);
-
- // If list mode is on, then the '$' at the end of the line may take up one
- // extra column.
- if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
- col += 1;
- }
-
- // Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
- width = wp->w_width_inner - win_col_off(wp);
- if (width <= 0 || col > 32000) {
- return 32000; // bigger than the number of screen columns
- }
- if (col <= (unsigned int)width) {
- return 1;
- }
- col -= (unsigned int)width;
- width += win_col_off2(wp);
- assert(col <= INT_MAX && (int)col < INT_MAX - (width - 1));
- return ((int)col + (width - 1)) / width + 1;
-}
-
-/// Like plines_win(), but only reports the number of physical screen lines
-/// used from the start of the line to the given column number.
-int plines_win_col(win_T *wp, linenr_T lnum, long column)
-{
- // Check for filler lines above this buffer line. When folded the result
- // is one line anyway.
- int lines = win_get_fill(wp, lnum);
-
- if (!wp->w_p_wrap) {
- return lines + 1;
- }
-
- if (wp->w_width_inner == 0) {
- return lines + 1;
- }
-
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
-
- colnr_T col = 0;
- chartabsize_T cts;
-
- init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
- while (*cts.cts_ptr != NUL && --column >= 0) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
- MB_PTR_ADV(cts.cts_ptr);
- }
-
- // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not
- // in MODE_INSERT state, then col must be adjusted so that it represents the
- // last screen position of the TAB. This only fixes an error when the TAB
- // wraps from one screen line to the next (when 'columns' is not a multiple
- // of 'ts') -- webb.
- col = cts.cts_vcol;
- if (*cts.cts_ptr == TAB && (State & MODE_NORMAL)
- && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
- col += win_lbr_chartabsize(&cts, NULL) - 1;
- }
- clear_chartabsize_arg(&cts);
-
- // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
- int width = wp->w_width_inner - win_col_off(wp);
- if (width <= 0) {
- return 9999;
- }
-
- lines += 1;
- if (col > width) {
- lines += (col - width) / (width + win_col_off2(wp)) + 1;
- }
- return lines;
-}
-
-/// Get the number of screen lines lnum takes up. This takes care of
-/// both folds and topfill, and limits to the current window height.
-///
-/// @param[in] wp window line is in
-/// @param[in] lnum line number
-/// @param[out] nextp if not NULL, the line after a fold
-/// @param[out] foldedp if not NULL, whether lnum is on a fold
-/// @param[in] cache whether to use the window's cache for folds
-///
-/// @return the total number of screen lines
-int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp,
- const bool cache)
-{
- bool folded = hasFoldingWin(wp, lnum, NULL, nextp, cache, NULL);
- if (foldedp) {
- *foldedp = folded;
- }
- if (folded) {
- return 1;
- } else if (lnum == wp->w_topline) {
- return plines_win_nofill(wp, lnum, true) + wp->w_topfill;
- }
- return plines_win(wp, lnum, true);
-}
-
-int plines_m_win(win_T *wp, linenr_T first, linenr_T last)
-{
- int count = 0;
-
- while (first <= last) {
- linenr_T next = first;
- count += plines_win_full(wp, first, &next, NULL, false);
- first = next + 1;
- }
- return count;
-}
-
/// Functions calculating horizontal size of text, when displayed in a window.
/// Return the number of characters 'c' will take on the screen, taking
@@ -243,12 +54,12 @@ int win_chartabsize(win_T *wp, char *p, colnr_T col)
/// @param s
///
/// @return Number of characters the string will take on the screen.
-int linetabsize(char *s)
+int linetabsize_str(char *s)
{
return linetabsize_col(0, s);
}
-/// Like linetabsize(), but "s" starts at column "startcol".
+/// Like linetabsize_str(), but "s" starts at column "startcol".
///
/// @param startcol
/// @param s
@@ -265,50 +76,76 @@ int linetabsize_col(int startcol, char *s)
return cts.cts_vcol;
}
-/// Like linetabsize(), but for a given window instead of the current one.
+/// Like linetabsize_str(), but for a given window instead of the current one.
///
/// @param wp
/// @param line
/// @param len
///
/// @return Number of characters the string will take on the screen.
-unsigned int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
+int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
{
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
- for (; *cts.cts_ptr != NUL && (len == MAXCOL || cts.cts_ptr < line + len);
- MB_PTR_ADV(cts.cts_ptr)) {
- cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
- }
+ win_linetabsize_cts(&cts, len);
clear_chartabsize_arg(&cts);
- return (unsigned int)cts.cts_vcol;
+ return cts.cts_vcol;
+}
+
+/// Return the number of cells line "lnum" of window "wp" will take on the
+/// screen, taking into account the size of a tab and inline virtual text.
+int linetabsize(win_T *wp, linenr_T lnum)
+{
+ return win_linetabsize(wp, lnum, ml_get_buf(wp->w_buffer, lnum), (colnr_T)MAXCOL);
+}
+
+void win_linetabsize_cts(chartabsize_T *cts, colnr_T len)
+{
+ for (; *cts->cts_ptr != NUL && (len == MAXCOL || cts->cts_ptr < cts->cts_line + len);
+ MB_PTR_ADV(cts->cts_ptr)) {
+ cts->cts_vcol += win_lbr_chartabsize(cts, NULL);
+ }
+ // check for inline virtual text after the end of the line
+ if (len == MAXCOL && cts->cts_has_virt_text && *cts->cts_ptr == NUL) {
+ (void)win_lbr_chartabsize(cts, NULL);
+ cts->cts_vcol += cts->cts_cur_text_width_left + cts->cts_cur_text_width_right;
+ }
}
/// Prepare the structure passed to chartabsize functions.
///
/// "line" is the start of the line, "ptr" is the first relevant character.
-/// When "lnum" is zero do not use text properties that insert text.
-void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum FUNC_ATTR_UNUSED,
- colnr_T col, char *line, char *ptr)
+/// When "lnum" is zero do not use inline virtual text.
+void init_chartabsize_arg(chartabsize_T *cts, win_T *wp, linenr_T lnum, colnr_T col, char *line,
+ char *ptr)
{
cts->cts_win = wp;
cts->cts_vcol = col;
cts->cts_line = line;
cts->cts_ptr = ptr;
- cts->cts_cur_text_width = 0;
- // TODO(bfredl): actually lookup inline virtual text here
+ cts->cts_max_head_vcol = 0;
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
cts->cts_has_virt_text = false;
+ cts->cts_row = lnum - 1;
+
+ if (cts->cts_row >= 0 && wp->w_buffer->b_virt_text_inline > 0) {
+ marktree_itr_get(wp->w_buffer->b_marktree, cts->cts_row, 0, cts->cts_iter);
+ MTKey mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row == cts->cts_row) {
+ cts->cts_has_virt_text = true;
+ }
+ }
}
/// Free any allocated item in "cts".
void clear_chartabsize_arg(chartabsize_T *cts)
-{}
+{
+}
/// like win_chartabsize(), but also check for line breaks on the screen
///
-/// @param line
-/// @param s
-/// @param col
+/// @param cts
///
/// @return The number of characters taken up on the screen.
int lbr_chartabsize(chartabsize_T *cts)
@@ -325,47 +162,39 @@ int lbr_chartabsize(chartabsize_T *cts)
/// Call lbr_chartabsize() and advance the pointer.
///
-/// @param line
-/// @param s
-/// @param col
+/// @param cts
///
/// @return The number of characters take up on the screen.
int lbr_chartabsize_adv(chartabsize_T *cts)
{
- int retval;
-
- retval = lbr_chartabsize(cts);
+ int retval = lbr_chartabsize(cts);
MB_PTR_ADV(cts->cts_ptr);
return retval;
}
+/// Get the number of characters taken up on the screen indicated by "cts".
+/// "cts->cts_cur_text_width_left" and "cts->cts_cur_text_width_right" are set
+/// to the extra size for inline virtual text.
/// This function is used very often, keep it fast!!!!
///
-/// If "headp" not NULL, set *headp to the size of what we for 'showbreak'
-/// string at start of line. Warning: *headp is only set if it's a non-zero
-/// value, init to 0 before calling.
+/// If "headp" not NULL, set "*headp" to the size of 'showbreak'/'breakindent'
+/// included in the return value.
+/// When "cts->cts_max_head_vcol" is positive, only count in "*headp" the size
+/// of 'showbreak'/'breakindent' before "cts->cts_max_head_vcol".
+/// When "cts->cts_max_head_vcol" is negative, only count in "*headp" the size
+/// of 'showbreak'/'breakindent' before where cursor should be placed.
///
-/// @param cts
-/// @param headp
-///
-/// @return The number of characters taken up on the screen.
+/// Warning: "*headp" may not be set if it's 0, init to 0 before calling.
int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
{
win_T *wp = cts->cts_win;
char *line = cts->cts_line; // start of the line
char *s = cts->cts_ptr;
colnr_T vcol = cts->cts_vcol;
-
- colnr_T col2;
- colnr_T col_adj = 0; // vcol + screen size of tab
- colnr_T colmax;
- int added;
int mb_added = 0;
- int numberextra;
- char *ps;
- int n;
- cts->cts_cur_text_width = 0;
+ cts->cts_cur_text_width_left = 0;
+ cts->cts_cur_text_width_right = 0;
// No 'linebreak', 'showbreak' and 'breakindent': return quickly.
if (!wp->w_p_lbr && !wp->w_p_bri && *get_showbreak_value(wp) == NUL
@@ -376,134 +205,186 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
return win_chartabsize(wp, s, vcol);
}
- // First get normal size, without 'linebreak' or virtual text
+ bool has_lcs_eol = wp->w_p_list && wp->w_p_lcs_chars.eol != NUL;
+
+ // First get normal size, without 'linebreak' or inline virtual text
int size = win_chartabsize(wp, s, vcol);
+ if (*s == NUL && !has_lcs_eol) {
+ size = 0; // NUL is not displayed
+ }
+ bool is_doublewidth = size == 2 && MB_BYTE2LEN((uint8_t)(*s)) > 1;
+
if (cts->cts_has_virt_text) {
- // TODO(bfredl): inline virtual text
+ int tab_size = size;
+ int col = (int)(s - line);
+ while (true) {
+ MTKey mark = marktree_itr_current(cts->cts_iter);
+ if (mark.pos.row != cts->cts_row || mark.pos.col > col) {
+ break;
+ } else if (mark.pos.col == col) {
+ if (!mt_end(mark) && mark.flags & (MT_FLAG_DECOR_VIRT_TEXT_INLINE)) {
+ DecorInline decor = mt_decor(mark);
+ DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL;
+ while (vt) {
+ if (!(vt->flags & kVTIsLines) && vt->pos == kVPosInline) {
+ if (mt_right(mark)) {
+ cts->cts_cur_text_width_right += vt->width;
+ } else {
+ cts->cts_cur_text_width_left += vt->width;
+ }
+ size += vt->width;
+ if (*s == TAB) {
+ // tab size changes because of the inserted text
+ size -= tab_size;
+ tab_size = win_chartabsize(wp, s, vcol + size);
+ size += tab_size;
+ }
+ }
+ vt = vt->next;
+ }
+ }
+ }
+ marktree_itr_next(wp->w_buffer->b_marktree, cts->cts_iter);
+ }
+ }
+
+ if (is_doublewidth && wp->w_p_wrap && in_win_border(wp, vcol + size - 2)) {
+ // Count the ">" in the last column.
+ size++;
+ mb_added = 1;
+ }
+
+ // May have to add something for 'breakindent' and/or 'showbreak'
+ // string at the start of a screen line.
+ int head = mb_added;
+ char *const sbr = get_showbreak_value(wp);
+ // When "size" is 0, no new screen line is started.
+ if (size > 0 && wp->w_p_wrap && (*sbr != NUL || wp->w_p_bri)) {
+ int col_off_prev = win_col_off(wp);
+ int width2 = wp->w_width_inner - col_off_prev + win_col_off2(wp);
+ colnr_T wcol = vcol + col_off_prev;
+ colnr_T max_head_vcol = cts->cts_max_head_vcol;
+ int added = 0;
+
+ // cells taken by 'showbreak'/'breakindent' before current char
+ int head_prev = 0;
+ if (wcol >= wp->w_width_inner) {
+ wcol -= wp->w_width_inner;
+ col_off_prev = wp->w_width_inner - width2;
+ if (wcol >= width2 && width2 > 0) {
+ wcol %= width2;
+ }
+ if (*sbr != NUL) {
+ head_prev += vim_strsize(sbr);
+ }
+ if (wp->w_p_bri) {
+ head_prev += get_breakindent_win(wp, line);
+ }
+ if (wcol < head_prev) {
+ head_prev -= wcol;
+ wcol += head_prev;
+ added += head_prev;
+ if (max_head_vcol <= 0 || vcol < max_head_vcol) {
+ head += head_prev;
+ }
+ } else {
+ head_prev = 0;
+ }
+ wcol += col_off_prev;
+ }
+
+ if (wcol + size > wp->w_width) {
+ // cells taken by 'showbreak'/'breakindent' halfway current char
+ int head_mid = 0;
+ if (*sbr != NUL) {
+ head_mid += vim_strsize(sbr);
+ }
+ if (wp->w_p_bri) {
+ head_mid += get_breakindent_win(wp, line);
+ }
+ if (head_mid > 0 && wcol + size > wp->w_width_inner) {
+ // Calculate effective window width.
+ int prev_rem = wp->w_width_inner - wcol;
+ int width = width2 - head_mid;
+
+ if (width <= 0) {
+ width = 1;
+ }
+ // Divide "size - prev_rem" by "width", rounding up.
+ int cnt = (size - prev_rem + width - 1) / width;
+ added += cnt * head_mid;
+
+ if (max_head_vcol == 0 || vcol + size + added < max_head_vcol) {
+ head += cnt * head_mid;
+ } else if (max_head_vcol > vcol + head_prev + prev_rem) {
+ head += (max_head_vcol - (vcol + head_prev + prev_rem)
+ + width2 - 1) / width2 * head_mid;
+ } else if (max_head_vcol < 0) {
+ int off = virt_text_cursor_off(cts, *s == NUL);
+ if (off >= prev_rem) {
+ if (size > off) {
+ head += (1 + (off - prev_rem) / width) * head_mid;
+ } else {
+ head += (off - prev_rem + width - 1) / width * head_mid;
+ }
+ }
+ }
+ }
+ }
+
+ size += added;
}
- int c = (uint8_t)(*s);
- if (*s == TAB) {
- col_adj = size - 1;
+ if (headp != NULL) {
+ *headp = head;
}
+ colnr_T vcol_start = 0; // start from where to consider linebreak
// If 'linebreak' set check at a blank before a non-blank if the line
// needs a break here
- if (wp->w_p_lbr
- && vim_isbreak(c)
+ if (wp->w_p_lbr && wp->w_p_wrap && wp->w_width_inner != 0) {
+ char *t = cts->cts_line;
+ while (vim_isbreak((uint8_t)t[0])) {
+ t++;
+ }
+ vcol_start = (colnr_T)(t - cts->cts_line);
+ }
+ if (wp->w_p_lbr && vcol_start <= vcol
+ && vim_isbreak((uint8_t)s[0])
&& !vim_isbreak((uint8_t)s[1])
&& wp->w_p_wrap
- && (wp->w_width_inner != 0)) {
+ && wp->w_width_inner != 0) {
// Count all characters from first non-blank after a blank up to next
// non-blank after a blank.
- numberextra = win_col_off(wp);
- col2 = vcol;
- colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj);
-
+ int numberextra = win_col_off(wp);
+ colnr_T col_adj = size - 1;
+ colnr_T colmax = (colnr_T)(wp->w_width_inner - numberextra - col_adj);
if (vcol >= colmax) {
colmax += col_adj;
- n = colmax + win_col_off2(wp);
-
+ int n = colmax + win_col_off2(wp);
if (n > 0) {
colmax += (((vcol - colmax) / n) + 1) * n - col_adj;
}
}
- for (;;) {
- ps = s;
+ colnr_T vcol2 = vcol;
+ while (true) {
+ char *ps = s;
MB_PTR_ADV(s);
- c = (uint8_t)(*s);
-
+ int c = (uint8_t)(*s);
if (!(c != NUL
- && (vim_isbreak(c) || col2 == vcol || !vim_isbreak((uint8_t)(*ps))))) {
+ && (vim_isbreak(c) || vcol2 == vcol || !vim_isbreak((uint8_t)(*ps))))) {
break;
}
- col2 += win_chartabsize(wp, s, col2);
-
- if (col2 >= colmax) { // doesn't fit
+ vcol2 += win_chartabsize(wp, s, vcol2);
+ if (vcol2 >= colmax) { // doesn't fit
size = colmax - vcol + col_adj;
break;
}
}
- } else if ((size == 2)
- && (MB_BYTE2LEN((uint8_t)(*s)) > 1)
- && wp->w_p_wrap
- && in_win_border(wp, vcol)) {
- // Count the ">" in the last column.
- size++;
- mb_added = 1;
- }
-
- // May have to add something for 'breakindent' and/or 'showbreak'
- // string at start of line.
- // Set *headp to the size of what we add.
- // Do not use 'showbreak' at the NUL after the text.
- added = 0;
- char *const sbr = c == NUL ? empty_option : get_showbreak_value(wp);
- if ((*sbr != NUL || wp->w_p_bri) && wp->w_p_wrap && vcol != 0) {
- colnr_T sbrlen = 0;
- int numberwidth = win_col_off(wp);
-
- numberextra = numberwidth;
- vcol += numberextra + mb_added;
-
- if (vcol >= (colnr_T)wp->w_width_inner) {
- vcol -= wp->w_width_inner;
- numberextra = wp->w_width_inner - (numberextra - win_col_off2(wp));
- if (vcol >= numberextra && numberextra > 0) {
- vcol %= numberextra;
- }
- if (*sbr != NUL) {
- sbrlen = (colnr_T)mb_charlen(sbr);
- if (vcol >= sbrlen) {
- vcol -= sbrlen;
- }
- }
- if (vcol >= numberextra && numberextra > 0) {
- vcol %= numberextra;
- } else if (vcol > 0 && numberextra > 0) {
- vcol += numberwidth - win_col_off2(wp);
- }
-
- numberwidth -= win_col_off2(wp);
- }
-
- if (vcol == 0 || (vcol + size + sbrlen > (colnr_T)wp->w_width_inner)) {
- if (*sbr != NUL) {
- if (size + sbrlen + numberwidth > (colnr_T)wp->w_width_inner) {
- // Calculate effective window width.
- int width = (colnr_T)wp->w_width_inner - sbrlen - numberwidth;
- int prev_width = vcol ? ((colnr_T)wp->w_width_inner - (sbrlen + vcol))
- : 0;
-
- if (width <= 0) {
- width = 1;
- }
- added += ((size - prev_width) / width) * vim_strsize(sbr);
- if ((size - prev_width) % width) {
- // Wrapped, add another length of 'sbr'.
- added += vim_strsize(sbr);
- }
- } else {
- added += vim_strsize(sbr);
- }
- }
-
- if (wp->w_p_bri) {
- added += get_breakindent_win(wp, line);
- }
-
- size += added;
- if (vcol != 0) {
- added = 0;
- }
- }
}
- if (headp != NULL) {
- *headp = added + mb_added;
- }
return size;
}
@@ -511,9 +392,7 @@ int win_lbr_chartabsize(chartabsize_T *cts, int *headp)
/// 'wrap' is on. This means we need to check for a double-byte character that
/// doesn't fit at the end of the screen line.
///
-/// @param wp
-/// @param s
-/// @param col
+/// @param cts
/// @param headp
///
/// @return The number of characters take up on the screen.
@@ -522,14 +401,13 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp)
win_T *wp = cts->cts_win;
char *s = cts->cts_ptr;
colnr_T col = cts->cts_vcol;
- int n;
if ((*s == TAB) && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
return tabstop_padding(col,
wp->w_buffer->b_p_ts,
wp->w_buffer->b_p_vts_array);
}
- n = ptr2cells(s);
+ int n = ptr2cells(s);
// Add one cell for a double-width character in the last column of the
// window, displayed with a ">".
@@ -541,3 +419,569 @@ static int win_nolbr_chartabsize(chartabsize_T *cts, int *headp)
}
return n;
}
+
+/// Check that virtual column "vcol" is in the rightmost column of window "wp".
+///
+/// @param wp window
+/// @param vcol column number
+static bool in_win_border(win_T *wp, colnr_T vcol)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (wp->w_width_inner == 0) {
+ // there is no border
+ return false;
+ }
+ int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number)
+
+ if ((int)vcol < width1 - 1) {
+ return false;
+ }
+
+ if ((int)vcol == width1 - 1) {
+ return true;
+ }
+ int width2 = width1 + win_col_off2(wp); // width of further lines
+
+ if (width2 <= 0) {
+ return false;
+ }
+ return (vcol - width1) % width2 == width2 - 1;
+}
+
+/// Get how many virtual columns inline virtual text should offset the cursor.
+///
+/// @param cts should contain information stored by win_lbr_chartabsize()
+/// about widths of left and right gravity virtual text
+/// @param on_NUL whether this is the end of the line
+static int virt_text_cursor_off(chartabsize_T *cts, bool on_NUL)
+{
+ int off = 0;
+ if (!on_NUL || !(State & MODE_NORMAL)) {
+ off += cts->cts_cur_text_width_left;
+ }
+ if (!on_NUL && (State & MODE_NORMAL)) {
+ off += cts->cts_cur_text_width_right;
+ }
+ return off;
+}
+
+/// Get virtual column number of pos.
+/// start: on the first position of this character (TAB, ctrl)
+/// cursor: where the cursor is on this character (first char, except for TAB)
+/// end: on the last position of this character (TAB, ctrl)
+///
+/// This is used very often, keep it fast!
+///
+/// @param wp
+/// @param pos
+/// @param start
+/// @param cursor
+/// @param end
+void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
+{
+ char *ptr; // points to current char
+ char *posptr; // points to char at pos->col
+ int incr;
+ int head;
+ colnr_T *vts = wp->w_buffer->b_p_vts_array;
+ int ts = (int)wp->w_buffer->b_p_ts;
+
+ colnr_T vcol = 0;
+ char *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line
+
+ if (pos->col == MAXCOL) {
+ // continue until the NUL
+ posptr = NULL;
+ } else {
+ // In a few cases the position can be beyond the end of the line.
+ for (colnr_T i = 0; i < pos->col; i++) {
+ if (ptr[i] == NUL) {
+ pos->col = i;
+ break;
+ }
+ }
+ posptr = ptr + pos->col;
+ posptr -= utf_head_off(line, posptr);
+ }
+
+ chartabsize_T cts;
+ bool on_NUL = false;
+ init_chartabsize_arg(&cts, wp, pos->lnum, 0, line, line);
+ cts.cts_max_head_vcol = -1;
+
+ // This function is used very often, do some speed optimizations.
+ // When 'list', 'linebreak', 'showbreak' and 'breakindent' are not set
+ // and there are no virtual text use a simple loop.
+ // Also use this when 'list' is set but tabs take their normal size.
+ if ((!wp->w_p_list || (wp->w_p_lcs_chars.tab1 != NUL))
+ && !wp->w_p_lbr
+ && *get_showbreak_value(wp) == NUL
+ && !wp->w_p_bri
+ && !cts.cts_has_virt_text) {
+ while (true) {
+ head = 0;
+ int c = (uint8_t)(*ptr);
+
+ // make sure we don't go past the end of the line
+ if (c == NUL) {
+ // NUL at end of line only takes one column
+ incr = 1;
+ break;
+ }
+
+ // A tab gets expanded, depending on the current column
+ if (c == TAB) {
+ incr = tabstop_padding(vcol, ts, vts);
+ } else {
+ // For utf-8, if the byte is >= 0x80, need to look at
+ // further bytes to find the cell width.
+ if (c >= 0x80) {
+ incr = utf_ptr2cells(ptr);
+ } else {
+ incr = byte2cells(c);
+ }
+
+ // If a double-cell char doesn't fit at the end of a line
+ // it wraps to the next line, it's like this char is three
+ // cells wide.
+ if ((incr == 2)
+ && wp->w_p_wrap
+ && (MB_BYTE2LEN((uint8_t)(*ptr)) > 1)
+ && in_win_border(wp, vcol)) {
+ incr++;
+ head = 1;
+ }
+ }
+
+ if ((posptr != NULL) && (ptr >= posptr)) {
+ // character at pos->col
+ break;
+ }
+
+ vcol += incr;
+ MB_PTR_ADV(ptr);
+ }
+ } else {
+ while (true) {
+ // A tab gets expanded, depending on the current column
+ // Other things also take up space.
+ head = 0;
+ incr = win_lbr_chartabsize(&cts, &head);
+
+ // make sure we don't go past the end of the line
+ if (*cts.cts_ptr == NUL) {
+ // NUL at end of line only takes one column, unless there is virtual text
+ incr = MAX(1, cts.cts_cur_text_width_left + cts.cts_cur_text_width_right);
+ on_NUL = true;
+ break;
+ }
+
+ if ((posptr != NULL) && (cts.cts_ptr >= posptr)) {
+ // character at pos->col
+ break;
+ }
+
+ cts.cts_vcol += incr;
+ MB_PTR_ADV(cts.cts_ptr);
+ }
+ vcol = cts.cts_vcol;
+ ptr = cts.cts_ptr;
+ }
+ clear_chartabsize_arg(&cts);
+
+ if (start != NULL) {
+ *start = vcol + head;
+ }
+
+ if (end != NULL) {
+ *end = vcol + incr - 1;
+ }
+
+ if (cursor != NULL) {
+ if ((*ptr == TAB)
+ && (State & MODE_NORMAL)
+ && !wp->w_p_list
+ && !virtual_active()
+ && !(VIsual_active && ((*p_sel == 'e') || ltoreq(*pos, VIsual)))) {
+ // cursor at end
+ *cursor = vcol + incr - 1;
+ } else {
+ vcol += virt_text_cursor_off(&cts, on_NUL);
+ // cursor at start
+ *cursor = vcol + head;
+ }
+ }
+}
+
+/// Get virtual cursor column in the current window, pretending 'list' is off.
+///
+/// @param posp
+///
+/// @retujrn The virtual cursor column.
+colnr_T getvcol_nolist(pos_T *posp)
+{
+ int list_save = curwin->w_p_list;
+ colnr_T vcol;
+
+ curwin->w_p_list = false;
+ if (posp->coladd) {
+ getvvcol(curwin, posp, NULL, &vcol, NULL);
+ } else {
+ getvcol(curwin, posp, NULL, &vcol, NULL);
+ }
+ curwin->w_p_list = list_save;
+ return vcol;
+}
+
+/// Get virtual column in virtual mode.
+///
+/// @param wp
+/// @param pos
+/// @param start
+/// @param cursor
+/// @param end
+void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
+{
+ colnr_T col;
+
+ if (virtual_active()) {
+ // For virtual mode, only want one value
+ getvcol(wp, pos, &col, NULL, NULL);
+
+ colnr_T coladd = pos->coladd;
+ colnr_T endadd = 0;
+
+ // Cannot put the cursor on part of a wide character.
+ char *ptr = ml_get_buf(wp->w_buffer, pos->lnum);
+
+ if (pos->col < (colnr_T)strlen(ptr)) {
+ int c = utf_ptr2char(ptr + pos->col);
+ if ((c != TAB) && vim_isprintc(c)) {
+ endadd = (colnr_T)(char2cells(c) - 1);
+ if (coladd > endadd) {
+ // past end of line
+ endadd = 0;
+ } else {
+ coladd = 0;
+ }
+ }
+ }
+ col += coladd;
+
+ if (start != NULL) {
+ *start = col;
+ }
+
+ if (cursor != NULL) {
+ *cursor = col;
+ }
+
+ if (end != NULL) {
+ *end = col + endadd;
+ }
+ } else {
+ getvcol(wp, pos, start, cursor, end);
+ }
+}
+
+/// Get the leftmost and rightmost virtual column of pos1 and pos2.
+/// Used for Visual block mode.
+///
+/// @param wp
+/// @param pos1
+/// @param pos2
+/// @param left
+/// @param right
+void getvcols(win_T *wp, pos_T *pos1, pos_T *pos2, colnr_T *left, colnr_T *right)
+{
+ colnr_T from1;
+ colnr_T from2;
+ colnr_T to1;
+ colnr_T to2;
+
+ if (lt(*pos1, *pos2)) {
+ getvvcol(wp, pos1, &from1, NULL, &to1);
+ getvvcol(wp, pos2, &from2, NULL, &to2);
+ } else {
+ getvvcol(wp, pos2, &from1, NULL, &to1);
+ getvvcol(wp, pos1, &from2, NULL, &to2);
+ }
+
+ if (from2 < from1) {
+ *left = from2;
+ } else {
+ *left = from1;
+ }
+
+ if (to2 > to1) {
+ if ((*p_sel == 'e') && (from2 - 1 >= to1)) {
+ *right = from2 - 1;
+ } else {
+ *right = to2;
+ }
+ } else {
+ *right = to1;
+ }
+}
+
+/// Functions calculating vertical size of text when displayed inside a window.
+/// Calls horizontal size functions defined above.
+
+/// Check if there may be filler lines anywhere in window "wp".
+bool win_may_fill(win_T *wp)
+{
+ return (wp->w_p_diff && diffopt_filler()) || wp->w_buffer->b_virt_line_blocks;
+}
+
+/// Return the number of filler lines above "lnum".
+///
+/// @param wp
+/// @param lnum
+///
+/// @return Number of filler lines above lnum
+int win_get_fill(win_T *wp, linenr_T lnum)
+{
+ int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone);
+
+ // be quick when there are no filler lines
+ if (diffopt_filler()) {
+ int n = diff_check(wp, lnum);
+
+ if (n > 0) {
+ return virt_lines + n;
+ }
+ }
+ return virt_lines;
+}
+
+/// Return the number of window lines occupied by buffer line "lnum".
+/// Includes any filler lines.
+///
+/// @param limit_winheight when true limit to window height
+int plines_win(win_T *wp, linenr_T lnum, bool limit_winheight)
+{
+ // Check for filler lines above this buffer line.
+ return plines_win_nofill(wp, lnum, limit_winheight) + win_get_fill(wp, lnum);
+}
+
+/// Return the number of window lines occupied by buffer line "lnum".
+/// Does not include filler lines.
+///
+/// @param limit_winheight when true limit to window height
+int plines_win_nofill(win_T *wp, linenr_T lnum, bool limit_winheight)
+{
+ if (!wp->w_p_wrap) {
+ return 1;
+ }
+
+ if (wp->w_width_inner == 0) {
+ return 1;
+ }
+
+ // Folded lines are handled just like an empty line.
+ if (lineFolded(wp, lnum)) {
+ return 1;
+ }
+
+ const int lines = plines_win_nofold(wp, lnum);
+ if (limit_winheight && lines > wp->w_height_inner) {
+ return wp->w_height_inner;
+ }
+ return lines;
+}
+
+/// Get number of window lines physical line "lnum" will occupy in window "wp".
+/// Does not care about folding, 'wrap' or filler lines.
+int plines_win_nofold(win_T *wp, linenr_T lnum)
+{
+ char *s = ml_get_buf(wp->w_buffer, lnum);
+ chartabsize_T cts;
+ init_chartabsize_arg(&cts, wp, lnum, 0, s, s);
+ if (*s == NUL && !cts.cts_has_virt_text) {
+ return 1; // be quick for an empty line
+ }
+ win_linetabsize_cts(&cts, (colnr_T)MAXCOL);
+ clear_chartabsize_arg(&cts);
+ int64_t col = cts.cts_vcol;
+
+ // If list mode is on, then the '$' at the end of the line may take up one
+ // extra column.
+ if (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL) {
+ col += 1;
+ }
+
+ // Add column offset for 'number', 'relativenumber' and 'foldcolumn'.
+ int width = wp->w_width_inner - win_col_off(wp);
+ if (width <= 0) {
+ return 32000; // bigger than the number of screen lines
+ }
+ if (col <= width) {
+ return 1;
+ }
+ col -= width;
+ width += win_col_off2(wp);
+ const int64_t lines = (col + (width - 1)) / width + 1;
+ return (lines > 0 && lines <= INT_MAX) ? (int)lines : INT_MAX;
+}
+
+/// Like plines_win(), but only reports the number of physical screen lines
+/// used from the start of the line to the given column number.
+int plines_win_col(win_T *wp, linenr_T lnum, long column)
+{
+ // Check for filler lines above this buffer line.
+ int lines = win_get_fill(wp, lnum);
+
+ if (!wp->w_p_wrap) {
+ return lines + 1;
+ }
+
+ if (wp->w_width_inner == 0) {
+ return lines + 1;
+ }
+
+ char *line = ml_get_buf(wp->w_buffer, lnum);
+
+ colnr_T col = 0;
+ chartabsize_T cts;
+
+ init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
+ while (*cts.cts_ptr != NUL && --column >= 0) {
+ cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
+ MB_PTR_ADV(cts.cts_ptr);
+ }
+
+ // If *cts.cts_ptr is a TAB, and the TAB is not displayed as ^I, and we're not
+ // in MODE_INSERT state, then col must be adjusted so that it represents the
+ // last screen position of the TAB. This only fixes an error when the TAB
+ // wraps from one screen line to the next (when 'columns' is not a multiple
+ // of 'ts') -- webb.
+ col = cts.cts_vcol;
+ if (*cts.cts_ptr == TAB && (State & MODE_NORMAL)
+ && (!wp->w_p_list || wp->w_p_lcs_chars.tab1)) {
+ col += win_lbr_chartabsize(&cts, NULL) - 1;
+ }
+ clear_chartabsize_arg(&cts);
+
+ // Add column offset for 'number', 'relativenumber', 'foldcolumn', etc.
+ int width = wp->w_width_inner - win_col_off(wp);
+ if (width <= 0) {
+ return 9999;
+ }
+
+ lines += 1;
+ if (col > width) {
+ lines += (col - width) / (width + win_col_off2(wp)) + 1;
+ }
+ return lines;
+}
+
+/// Get the number of screen lines buffer line "lnum" will take in window "wp".
+/// This takes care of both folds and topfill.
+///
+/// XXX: Because of topfill, this only makes sense when lnum >= wp->w_topline.
+///
+/// @param[in] wp window the line is in
+/// @param[in] lnum line number
+/// @param[out] nextp if not NULL, the line after a fold
+/// @param[out] foldedp if not NULL, whether lnum is on a fold
+/// @param[in] cache whether to use the window's cache for folds
+/// @param[in] limit_winheight when true limit to window height
+///
+/// @return the total number of screen lines
+int plines_win_full(win_T *wp, linenr_T lnum, linenr_T *const nextp, bool *const foldedp,
+ const bool cache, const bool limit_winheight)
+{
+ bool folded = hasFoldingWin(wp, lnum, &lnum, nextp, cache, NULL);
+ if (foldedp != NULL) {
+ *foldedp = folded;
+ }
+ return ((folded ? 1 : plines_win_nofill(wp, lnum, limit_winheight)) +
+ (lnum == wp->w_topline ? wp->w_topfill : win_get_fill(wp, lnum)));
+}
+
+/// Get the number of screen lines a range of buffer lines will take in window "wp".
+/// This takes care of both folds and topfill.
+///
+/// XXX: Because of topfill, this only makes sense when first >= wp->w_topline.
+///
+/// @param first first line number
+/// @param last last line number
+/// @param limit_winheight when true limit each line to window height
+///
+/// @see win_text_height
+int plines_m_win(win_T *wp, linenr_T first, linenr_T last, bool limit_winheight)
+{
+ int count = 0;
+
+ while (first <= last) {
+ linenr_T next = first;
+ count += plines_win_full(wp, first, &next, NULL, false, limit_winheight);
+ first = next + 1;
+ }
+ return count;
+}
+
+/// Get the number of screen lines a range of text will take in window "wp".
+///
+/// @param[in] start_lnum Starting line number, 1-based inclusive.
+/// @param[in] start_vcol >= 0: Starting virtual column index on "start_lnum",
+/// 0-based inclusive, rounded down to full screen lines.
+/// < 0: Count a full "start_lnum", including filler lines above.
+/// @param[in] end_lnum Ending line number, 1-based inclusive.
+/// @param[in] end_vcol >= 0: Ending virtual column index on "end_lnum",
+/// 0-based exclusive, rounded up to full screen lines.
+/// < 0: Count a full "end_lnum", not including filler lines below.
+/// @param[out] fill If not NULL, set to the number of filler lines in the range.
+int64_t win_text_height(win_T *const wp, const linenr_T start_lnum, const int64_t start_vcol,
+ const linenr_T end_lnum, const int64_t end_vcol, int64_t *const fill)
+{
+ int width1 = 0;
+ int width2 = 0;
+ if (start_vcol >= 0 || end_vcol >= 0) {
+ width1 = wp->w_width_inner - win_col_off(wp);
+ width2 = width1 + win_col_off2(wp);
+ width1 = MAX(width1, 0);
+ width2 = MAX(width2, 0);
+ }
+
+ int64_t height_sum_fill = 0;
+ int64_t height_cur_nofill = 0;
+ int64_t height_sum_nofill = 0;
+ linenr_T lnum = start_lnum;
+
+ if (start_vcol >= 0) {
+ linenr_T lnum_next = lnum;
+ const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL);
+ height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false);
+ height_sum_nofill += height_cur_nofill;
+ const int64_t row_off = (start_vcol < width1 || width2 <= 0)
+ ? 0
+ : 1 + (start_vcol - width1) / width2;
+ height_sum_nofill -= MIN(row_off, height_cur_nofill);
+ lnum = lnum_next + 1;
+ }
+
+ while (lnum <= end_lnum) {
+ linenr_T lnum_next = lnum;
+ const bool folded = hasFoldingWin(wp, lnum, &lnum, &lnum_next, true, NULL);
+ height_sum_fill += win_get_fill(wp, lnum);
+ height_cur_nofill = folded ? 1 : plines_win_nofill(wp, lnum, false);
+ height_sum_nofill += height_cur_nofill;
+ lnum = lnum_next + 1;
+ }
+
+ if (end_vcol >= 0) {
+ height_sum_nofill -= height_cur_nofill;
+ const int64_t row_off = end_vcol == 0
+ ? 0
+ : (end_vcol <= width1 || width2 <= 0)
+ ? 1
+ : 1 + (end_vcol - width1 + width2 - 1) / width2;
+ height_sum_nofill += MIN(row_off, height_cur_nofill);
+ }
+
+ if (fill != NULL) {
+ *fill = height_sum_fill;
+ }
+ return height_sum_fill + height_sum_nofill;
+}
diff --git a/src/nvim/plines.h b/src/nvim/plines.h
index 808f6d284e..6aede88c8b 100644
--- a/src/nvim/plines.h
+++ b/src/nvim/plines.h
@@ -1,25 +1,28 @@
-#ifndef NVIM_PLINES_H
-#define NVIM_PLINES_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/vim.h"
+#include "nvim/marktree.h"
+#include "nvim/pos_defs.h" // IWYU pragma: keep
-// Argument for lbr_chartabsize().
+/// Argument for lbr_chartabsize().
typedef struct {
win_T *cts_win;
- char *cts_line; // start of the line
- char *cts_ptr; // current position in line
+ char *cts_line; ///< start of the line
+ char *cts_ptr; ///< current position in line
+ int cts_row;
- bool cts_has_virt_text; // true if if a property inserts text
- int cts_cur_text_width; // width of current inserted text
- // TODO(bfredl): iterator in to the marktree for scanning virt text
+ bool cts_has_virt_text; ///< true if if there is inline virtual text
+ int cts_cur_text_width_left; ///< width of virtual text left of cursor
+ int cts_cur_text_width_right; ///< width of virtual text right of cursor
+ MarkTreeIter cts_iter[1];
- int cts_vcol; // virtual column at current position
+ int cts_vcol; ///< virtual column at current position
+ int cts_max_head_vcol; ///< see win_lbr_chartabsize()
} chartabsize_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.h.generated.h"
#endif
-#endif // NVIM_PLINES_H
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index 1db21880bb..68e572911c 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -1,9 +1,14 @@
find_package(Gettext REQUIRED)
find_program(XGETTEXT_PRG xgettext)
find_program(ICONV_PRG iconv)
-
-option(LANGUAGES "Localizations to build")
-if(NOT LANGUAGES)
+mark_as_advanced(
+ GETTEXT_MSGFMT_EXECUTABLE
+ GETTEXT_MSGMERGE_EXECUTABLE
+ ICONV_PRG
+ XGETTEXT_PRG)
+
+option(ENABLE_LANGUAGES "Localizations to build" ON)
+if(ENABLE_LANGUAGES)
set(LANGUAGES
af
ca
@@ -90,7 +95,6 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
COMMENT "Checking ${name}.po"
VERBATIM
DEPENDS ${poFile})
- set_target_properties(check-po-${name} PROPERTIES FOLDER po/check)
endmacro()
macro(BuildPoIconvGenericWithCharset
@@ -100,12 +104,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
add_custom_target(update-po-${lang}
COMMAND ${CMAKE_COMMAND}
- -DICONV_PRG=${ICONV_PRG}
- -DINPUT_FILE=${inputFile}
- -DOUTPUT_FILE=${outputFile}
- -DINPUT_ENC=${inputEnc}
- -DOUTPUT_ENC=${outputEnc}
- -DOUTPUT_CHARSET=${outputCharSet}
+ -D ICONV_PRG=${ICONV_PRG}
+ -D INPUT_FILE=${inputFile}
+ -D OUTPUT_FILE=${outputFile}
+ -D INPUT_ENC=${inputEnc}
+ -D OUTPUT_ENC=${outputEnc}
+ -D OUTPUT_CHARSET=${outputCharSet}
-P ${PROJECT_SOURCE_DIR}/cmake/ConvertPo.cmake
COMMENT "Updating ${outputName}.po"
DEPENDS ${inputFile})
@@ -177,9 +181,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
BuildMo(${LANGUAGE})
endforeach()
- set_target_properties(${UPDATE_PO_TARGETS} PROPERTIES FOLDER po/update)
add_custom_target(translations ALL DEPENDS ${LANGUAGE_MO_FILES})
add_custom_target(update-po DEPENDS ${UPDATE_PO_TARGETS})
- set_target_properties(translations update-po PROPERTIES FOLDER po)
endif()
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index 7705ba8577..e67cb8c149 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -6,6 +6,9 @@
if 1 " Only execute this if the eval feature is available.
+" using line continuation
+set cpo&vim
+
let filename = "check-" . expand("%:t:r") . ".log"
exe 'redir! > ' . filename
@@ -30,8 +33,15 @@ func! GetMline()
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
+ " remove duplicate positional format arguments
+ let idline2 = ""
+ while idline2 != idline
+ let idline2 = idline
+ let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g')
+ endwhile
+
" remove everything but % items.
- return substitute(idline, '[^%]*\(%[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
+ return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g')
endfunc
" This only works when 'wrapscan' is not set.
@@ -62,12 +72,18 @@ while 1
if getline(line('.') - 1) !~ "no-c-format"
" go over the "msgid" and "msgid_plural" lines
let prevfromline = 'foobar'
+ let plural = 0
while 1
+ if getline('.') =~ 'msgid_plural'
+ let plural += 1
+ endif
let fromline = GetMline()
if prevfromline != 'foobar' && prevfromline != fromline
+ \ && (plural != 1
+ \ || count(prevfromline, '%') + 1 != count(fromline, '%'))
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . prevfromline
- echomsg 'msgid ' . fromline
+ echomsg 'msgid: ' . fromline
if error == 0
let error = line('.')
endif
@@ -89,6 +105,7 @@ while 1
while getline('.') =~ '^msgstr'
let toline = GetMline()
if fromline != toline
+ \ && (plural == 0 || count(fromline, '%') != count(toline, '%') + 1)
echomsg 'Mismatching % in line ' . (line('.') - 1)
echomsg 'msgid: ' . fromline
echomsg 'msgstr: ' . toline
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index c55918abc9..0345d3e243 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -3787,7 +3787,7 @@ msgstr "linje %4ld:"
msgid "E354: Invalid register name: '%s'"
msgstr "E354: Ugyldigt registernavn: '%s'"
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Oversætter: scootergrisen"
msgid "Interrupt: "
diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po
index aaa39d6041..f619c4c408 100644
--- a/src/nvim/po/eo.po
+++ b/src/nvim/po/eo.po
@@ -3657,7 +3657,7 @@ msgstr "linio %4ld:"
msgid "E354: Invalid register name: '%s'"
msgstr "E354: Nevalida nomo de reĝistro: '%s'"
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Flegado de mesaĝoj: Dominique PELLÉ <dominique.pelle@gmail.com>"
msgid "Interrupt: "
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index c8db9a64b5..ab6acf685f 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -5540,7 +5540,7 @@ msgstr "tekijät Bram Moolenaar et al."
#~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten "
#, fuzzy
-#~ msgid "type :CheckHealth<Enter> to optimize Nvim"
+#~ msgid "type :checkhealth<Enter> to optimize Nvim"
#~ msgstr "kirjoita :help iccf<Enter> lisätietoa varten "
msgid "type :q<Enter> to exit "
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index f4fce68ac5..d9058326d5 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -3375,7 +3375,7 @@ msgid "E354: Invalid register name: '%s'"
msgstr "E354: Nom de registre invalide : '%s'"
# DB - todo : mettre jour ?
-msgid "Messages maintainer: Bram Moolenaar <Bram@vim.org>"
+msgid "Messages maintainer: The Vim Project"
msgstr "Maintenance des messages : Dominique Pell <dominique.pelle@gmail.com>"
msgid "Interrupt: "
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index eb7879efc4..f3c55fe9ab 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Neovim Turkish Localization Project\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-09-30 20:46+0300\n"
-"PO-Revision-Date: 2022-07-22 23:00+0300\n"
+"POT-Creation-Date: 2023-06-14 17:27+0300\n"
+"PO-Revision-Date: 2023-06-14 23:00+0300\n"
"Last-Translator: Emir SARI <emir_sari@icloud.com>\n"
"Language-Team: Turkish <https://github.com/bitigchi/neovim>\n"
"Language: tr\n"
@@ -18,6 +18,92 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+msgid "(local to window)"
+msgstr "(pencereye yerel)"
+
+msgid "(local to buffer)"
+msgstr "(arabelleğe yerel)"
+
+msgid "(global or local to buffer)"
+msgstr "(arabelleğe global veya yerel)"
+
+msgid ""
+"\" Each \"set\" line shows the current value of an option (on the left)."
+msgstr ""
+"Her bir \"set\" satırı, bir seçeneğin geçerli değerini gösterir (solda)."
+
+msgid "\" Hit <Enter> on a \"set\" line to execute it."
+msgstr "Bir satırı yürütmek için \"set\" üzerinde <Enter>'a basın."
+
+msgid "\" A boolean option will be toggled."
+msgstr "\" Bir Boole değeri açılır/kapatılır."
+
+msgid "pattern for an include-file line"
+msgstr "bir include dosyası satırı için dizgi"
+
+msgid "use binary searching in tags files"
+msgstr "etiket dosyalarında ikili arama kullan"
+
+msgid "long lines wrap"
+msgstr "uzun satırları kaydırılır"
+
+msgid "show the line number for each line"
+msgstr "her bir satır için satır numarasını göster"
+
+msgid "name of syntax highlighting used"
+msgstr "kullanılan sözdizim vurgulamanın adı"
+
+msgid "default height for the preview window"
+msgstr "önizleme penceresinin öntanımlı yüksekliği"
+
+msgid "identifies the preview window"
+msgstr "önizleme penceresini tanımlar"
+
+msgid "terminal"
+msgstr "uçbirim"
+
+msgid "file to write messages in"
+msgstr "iletilerin içine yazılacağı dosya"
+
+msgid "list of directories for undo files"
+msgstr "geri al dosyaları için dizinler listesi"
+
+msgid "list of pairs that match for the \"%\" command"
+msgstr "\"%\" komutu için eşleşen eşler listesi"
+
+msgid "folding"
+msgstr "kıvırma"
+
+msgid "mapping"
+msgstr "eşlemleme"
+
+msgid "reading and writing files"
+msgstr "dosyaları okuma ve yazma"
+
+msgid "automatically write a file when leaving a modified buffer"
+msgstr "değiştirilmiş bir arabellekten ayrılırken kendiliğinden bir dosya yaz"
+
+msgid "the swap file"
+msgstr "takas dosyası"
+
+msgid "use a swap file for this buffer"
+msgstr "bu arabellek için bir takas dosyası kullan"
+
+msgid "specifies how command line completion works"
+msgstr "komut satırı tamamlamasının nasıl çalıştığını belirtir"
+
+msgid "executing external commands"
+msgstr "dış komutları yürütme"
+
+msgid "specifies the characters in a file name"
+msgstr "bir dosya adındaki karakterleri belirtir"
+
+msgid "E249: Window layout changed unexpectedly"
+msgstr "E249: Pencere yerleşimi beklenmedik bir biçimde değişti"
+
+msgid "E1156: Cannot change the argument list recursively"
+msgstr "E1156: Argüman listesi özyineli olarak değiştirilemiyor"
+
msgid "E163: There is only one file to edit"
msgstr "E163: Düzenlenecek yalnızca bir dosya var"
@@ -30,8 +116,8 @@ msgstr "E165: Son dosyadan öteye gidilemez"
msgid "E610: No argument to delete"
msgstr "E610: Silinecek bir argüman yok"
-msgid "E249: window layout changed unexpectedly"
-msgstr "E249: Pencere yerleşimi beklenmedik bir biçimde değişti"
+msgid "E218: Autocommand nesting too deep"
+msgstr "E218: Çok fazla iç içe geçmiş otokomut"
msgid "--Deleted--"
msgstr "--Silindi--"
@@ -68,9 +154,6 @@ msgstr "E217: Otokomutlar TÜM olaylar için çalıştırılamıyor"
msgid "No matching autocommands: %s"
msgstr "Eşleşen otokomut yok: %s"
-msgid "E218: autocommand nesting too deep"
-msgstr "E218: Çok fazla iç içe geçmiş otokomut"
-
#, c-format
msgid "%s Autocommands for \"%s\""
msgstr "\"%s\" için %s otokomutlar"
@@ -98,8 +181,9 @@ msgstr "E216: Böyle bir grup veya olay yok: %s"
msgid "E855: Autocommands caused command to abort"
msgstr "E855: Otokomutlar komutun durmasına neden oldu"
-msgid "E937: Attempt to delete a buffer that is in use"
-msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor"
+#, c-format
+msgid "E937: Attempt to delete a buffer that is in use: %s"
+msgstr "E937: Kullanımda olan bir arabellek silinmeye çalışılıyor: %s"
msgid "E82: Cannot allocate any buffer, exiting..."
msgstr "E82: Arabellek ayrılamadı, çıkılıyor..."
@@ -116,6 +200,24 @@ msgstr "E516: Hiçbir arabellek silinmedi"
msgid "E517: No buffers were wiped out"
msgstr "E517: Hiçbir arabellek yok edilmedi"
+#, c-format
+msgid "%d buffer unloaded"
+msgid_plural "%d buffers unloaded"
+msgstr[0] "%d arabellek bellekten kaldırıldı"
+msgstr[1] "%d arabellek bellekten kaldırıldı"
+
+#, c-format
+msgid "%d buffer deleted"
+msgid_plural "%d buffers deleted"
+msgstr[0] "%d arabellek silindi"
+msgstr[1] "%d arabellek silindi"
+
+#, c-format
+msgid "%d buffer wiped out"
+msgid_plural "%d buffers wiped out"
+msgstr[0] "%d arabellek yok edildi"
+msgstr[1] "%d arabellek yok edildi"
+
msgid "E90: Cannot unload last buffer"
msgstr "E90: Son arabellek bellekten kaldırılamıyor"
@@ -193,8 +295,14 @@ msgid "[readonly]"
msgstr "[saltokunur]"
#, c-format
+msgid "%<PRId64> line --%d%%--"
+msgid_plural "%<PRId64> lines --%d%%--"
+msgstr[0] "%<PRId64>. satır --%%%d--"
+msgstr[1] "%<PRId64>. satır --%%%d--"
+
+#, c-format
msgid "line %<PRId64> of %<PRId64> --%d%%-- col "
-msgstr "satır %<PRId64>/%<PRId64> --%d%%-- sütun "
+msgstr "satır %<PRId64>/%<PRId64> --%%%d-- sütun "
msgid "[No Name]"
msgstr "[Adsız]"
@@ -211,6 +319,26 @@ msgstr "Son"
msgid "Top"
msgstr "Baş"
+#, c-format
+msgid "%d%%"
+msgstr "%%%d"
+
+#, c-format
+msgid " (%d of %d)"
+msgstr " (%d/%d)"
+
+#, c-format
+msgid " ((%d) of %d)"
+msgstr " ((%d)/%d)"
+
+#, c-format
+msgid " (file %d of %d)"
+msgstr " (dosya %d/%d)"
+
+#, c-format
+msgid " (file (%d) of %d)"
+msgstr " (dosya (%d)/%d)"
+
msgid "E382: Cannot write, 'buftype' option is set"
msgstr "E382: Yazılamıyor, 'buftype' seçeneği ayarlanmamış"
@@ -226,6 +354,116 @@ msgstr "[Konum Listesi]"
msgid "[Quickfix List]"
msgstr "[Hızlı Düzelt Listesi]"
+msgid "E206: Patchmode: can't touch empty original file"
+msgstr "E206: Yama kipi: Özgün boş dosyaya dokunulamıyor"
+
+msgid "E513: Write error, conversion failed (make 'fenc' empty to override)"
+msgstr ""
+"E513: Yazma hatası, dönüştürme başarısız (geçersiz kılmak için 'fenc'i boş "
+"bırak)"
+
+msgid "E513: Write error, conversion failed in line %"
+msgstr "E513: Yazma hatası, şu satırda dönüştürme başarısız: %"
+
+msgid "E514: Write error (file system full?)"
+msgstr "E514: Yazma hatası (dosya sistemi dolu mu?)"
+
+#, c-format
+msgid "E676: No matching autocommands for buftype=%s buffer"
+msgstr "E676: buftype=%s arabelleği için eşleşen otokomut yok"
+
+msgid "WARNING: The file has been changed since reading it!!!"
+msgstr "UYARI: Bu dosya açıldıktan sonra başkası tarafından değiştirilmiş!!!"
+
+msgid "Do you really want to write to it"
+msgstr "Yine de yazmak istiyor musunuz?"
+
+msgid "E203: Autocommands deleted or unloaded buffer to be written"
+msgstr "E203: Otokomutlar arabelleği silmiş veya yazılması için kaldırmışlar"
+
+msgid "E204: Autocommand changed number of lines in unexpected way"
+msgstr "E204: Otokomut satır sayısını beklenmedik biçimde değiştirdi"
+
+msgid "is a directory"
+msgstr "bir dizin"
+
+msgid "is not a file or writable device"
+msgstr "bir dosya veya yazılabilir aygıt değil"
+
+msgid "is read-only (add ! to override)"
+msgstr "saltokunur (geçersiz kılmak için ! ekleyin)"
+
+#, c-format
+msgid "E303: Unable to create directory \"%s\" for backup file: %s"
+msgstr "E303: Yedek dosyası için \"%s\" dizini oluşturulamadı: %s"
+
+msgid "E509: Cannot create backup file (add ! to override)"
+msgstr "E509: Yedek dosyası oluşturulamıyor (geçersiz kılmak için ! ekleyin)"
+
+msgid "E510: Can't make backup file (add ! to override)"
+msgstr "E510: Yedek dosyası yapılamıyor (geçersiz kılmak için ! ekleyin)"
+
+msgid "E214: Can't find temp file for writing"
+msgstr "E214: Yazma için geçici dosya bulunamıyor"
+
+msgid "E213: Cannot convert (add ! to write without conversion)"
+msgstr "E213: Dönüştürülemiyor (dönüştürmeden yazmak için ! ekleyin)"
+
+msgid "E166: Can't open linked file for writing"
+msgstr "E166: Bağlı dosya yazma için açılamıyor"
+
+#, c-format
+msgid "E212: Can't open file for writing: %s"
+msgstr "E212: Dosya yazma için açılamıyor: %s"
+
+#, c-format
+msgid "E512: Close failed: %s"
+msgstr "E512: Kapatma başarısız oldu: %s"
+
+msgid " CONVERSION ERROR"
+msgstr " DÖNÜŞTÜRME HATASI"
+
+#, c-format
+msgid " in line %<PRId64>;"
+msgstr " %<PRId64>. satırda;"
+
+msgid "[NOT converted]"
+msgstr "[dönüştürülmedi]"
+
+msgid "[converted]"
+msgstr "[dönüştürüldü]"
+
+msgid "[Device]"
+msgstr "[Aygıt]"
+
+msgid " [a]"
+msgstr " [i]"
+
+msgid " appended"
+msgstr " iliştirildi"
+
+msgid " [w]"
+msgstr " [y]"
+
+msgid " written"
+msgstr " yazıldı"
+
+msgid "E205: Patchmode: can't save original file"
+msgstr "E205: Yama kipi: Orijinal dosya kaydedilemiyor"
+
+msgid "E207: Can't delete backup file"
+msgstr "E207: Yedek dosyası silinemiyor"
+
+msgid ""
+"\n"
+"WARNING: Original file may be lost or damaged\n"
+msgstr ""
+"\n"
+"UYARI: Orijinal dosya kaybolmuş veya hasar görmüş olabilir\n"
+
+msgid "don't quit the editor until the file is successfully written!"
+msgstr "dosya başarılı bir biçimde yazılana kadar düzenleyiciden çıkmayın!"
+
msgid "W10: Warning: Changing a readonly file"
msgstr "W10: Uyarı: Saltokunur bir dosya değiştiriliyor"
@@ -253,15 +491,15 @@ msgstr "'history' seçeneği sıfır"
msgid "E474: Failed to convert list to msgpack string buffer"
msgstr "E474: Liste, msgpack dizi arabelleğine dönüştürülemedi"
+msgid "E548: Digit expected"
+msgstr "E548: Basamak bekleniyordu"
+
msgid "E545: Missing colon"
msgstr "E545: İki nokta eksik"
msgid "E546: Illegal mode"
msgstr "E546: İzin verilmeyen kip"
-msgid "E548: digit expected"
-msgstr "E548: Basamak bekleniyordu"
-
msgid "E549: Illegal percentage"
msgstr "E549: İzin verilmeyen yüzde"
@@ -460,11 +698,72 @@ msgstr "E105: :loadkeymap kaynak alınmayan bir dosyada kullanılıyor"
msgid "E791: Empty keymap entry"
msgstr "E791: Boş düğme eşlem girdisi"
+msgid " TERMINAL"
+msgstr " UÇBİRİM"
+
+msgid " VREPLACE"
+msgstr " SANAL DEĞİŞTİR"
+
+msgid " REPLACE"
+msgstr " DEĞİŞTİR"
+
+msgid " REVERSE"
+msgstr " GERİ AL"
+
+msgid " INSERT"
+msgstr " EKLE"
+
+msgid " (terminal)"
+msgstr " (uçbirim)"
+
+msgid " (insert)"
+msgstr " (ekle)"
+
+msgid " (replace)"
+msgstr " (değiştir)"
+
+msgid " (vreplace)"
+msgstr " (sanal değiştir)"
+
+msgid " Arabic"
+msgstr " Arapça"
+
+msgid " (paste)"
+msgstr " (yapıştır)"
+
+msgid " VISUAL"
+msgstr " GÖRSEL"
+
+msgid " VISUAL LINE"
+msgstr " GÖRSEL SATIR"
+
+msgid " VISUAL BLOCK"
+msgstr " GÖRSEL BLOK"
+
+msgid " SELECT"
+msgstr " SEÇ"
+
+msgid " SELECT LINE"
+msgstr " SATIR SEÇ"
+
+msgid " SELECT BLOCK"
+msgstr " BLOK SEÇ"
+
+msgid "recording"
+msgstr "kaydediliyor"
+
msgid "E111: Missing ']'"
msgstr "E111: ']' eksik"
-msgid "E719: Cannot use [:] with a Dictionary"
-msgstr "E719: [:], bir Sözlük ile kullanılamaz"
+#, c-format
+msgid "E697: Missing end of List ']': %s"
+msgstr "E697: Liste sonunda ']' eksik: %s"
+
+msgid "E719: Cannot slice a Dictionary"
+msgstr "E719: Bir sözlük dilimlenemiyor"
+
+msgid "E909: Cannot index a special variable"
+msgstr "E909: Özel bir değişken dizinlenemiyor"
msgid "E274: No white space allowed before parenthesis"
msgstr "E274: Ayraçtan önce boşluğa izin verilmiyor"
@@ -473,9 +772,26 @@ msgstr "E274: Ayraçtan önce boşluğa izin verilmiyor"
msgid "E80: Error while writing: %s"
msgstr "E80: Yazma sırasında hata: %s"
+msgid "E695: Cannot index a Funcref"
+msgstr "E695: Bir Funcref dizinlenemiyor"
+
+msgid "E698: Variable nested too deep for making a copy"
+msgstr "E698: Değişken kopyalama için çok iç içe geçmiş"
+
msgid "E1098: String, List or Blob required"
msgstr "E1098: Dizi, Liste veya İkili Nesne gerekiyor"
+#, c-format
+msgid "E1169: Expression too recursive: %s"
+msgstr "E1169: Komut çok özyineli: %s"
+
+#, c-format
+msgid "E1203: Dot can only be used on a dictionary: %s"
+msgstr "E1203: İki nokta yalnızca bir sözlük üzerinde kullanılabilir: %s"
+
+msgid "E1192: Empty function name"
+msgstr "E1192: İşlev adı boş"
+
msgid ""
"E5700: Expression from 'spellsuggest' must yield lists with exactly two "
"values"
@@ -500,18 +816,9 @@ msgstr "E713: . sonrası boş anahtar kullanılamaz"
msgid "E709: [:] requires a List or Blob value"
msgstr "E709: [:] bir liste veya ikili geniş nesne değeri gerektirir"
-msgid "E972: Blob value does not have the right number of bytes"
-msgstr "E972: İkili geniş nesne değeri doğru bayt sayısına sahip değil"
-
msgid "E996: Cannot lock a range"
msgstr "E996: Erim kilitlenemiyor"
-msgid "E710: List value has more items than target"
-msgstr "E710: Liste değeri hedeften daha fazla ögeye sahip"
-
-msgid "E711: List value has not enough items"
-msgstr "E711: Liste değeri yeterli ögeye sahip değil"
-
msgid "E996: Cannot lock a list or dict"
msgstr "E996: Bir liste veya sözlük kilitlenemiyor"
@@ -524,22 +831,12 @@ msgstr "E109: '?' sonrası ':' eksik"
msgid "E804: Cannot use '%' with Float"
msgstr "E804: Bir kayan noktalı değer ile '%' kullanılamaz"
-msgid "E973: Blob literal should have an even number of hex characters"
-msgstr ""
-"E973: İkili geniş nesne hazır bilgisi çift onalt. karakterlere iye olmalıdır"
-
msgid "E110: Missing ')'"
msgstr "E110: ')' eksik"
msgid "E260: Missing name after ->"
msgstr "E260: -> sonrası ad eksik"
-msgid "E695: Cannot index a Funcref"
-msgstr "E695: Bir Funcref dizinlenemiyor"
-
-msgid "E909: Cannot index a special variable"
-msgstr "E909: Özel bir değişken dizinlenemiyor"
-
#, c-format
msgid "E112: Option name missing: %s"
msgstr "E112: Seçenek adı eksik: %s"
@@ -548,6 +845,10 @@ msgstr "E112: Seçenek adı eksik: %s"
msgid "E113: Unknown option: %s"
msgstr "E113: Bilinmeyen seçenek: %s"
+msgid "E973: Blob literal should have an even number of hex characters"
+msgstr ""
+"E973: İkili geniş nesne hazır bilgisi çift onalt. karakterlere iye olmalıdır"
+
#, c-format
msgid "E114: Missing quote: %s"
msgstr "E114: Tırnak eksik: %s"
@@ -560,10 +861,6 @@ msgstr "E115: Tırnak eksik: %s"
msgid "E696: Missing comma in List: %s"
msgstr "E696: Listede virgül eksik: %s"
-#, c-format
-msgid "E697: Missing end of List ']': %s"
-msgstr "E697: Liste sonunda ']' eksik: %s"
-
msgid "Not enough memory to set references, garbage collection aborted!"
msgstr "Referansları ayarlamak için yetersiz bellek, atık toplama durduruldu"
@@ -593,9 +890,6 @@ msgstr "filter() argümanı"
msgid "E700: Unknown function: %s"
msgstr "E700: Bilinmeyen işlev: %s"
-msgid "E922: expected a dict"
-msgstr "E922: Bir sözlük bekleniyordu"
-
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: function() ikinci argümanı bir liste veya sözlük olmalıdır"
@@ -609,9 +903,6 @@ msgstr "Komut çalıştırılıyor: \"%s\""
msgid "E921: Invalid callback argument"
msgstr "E921: Geçersiz geri çağırma argümanı"
-msgid "E698: variable nested too deep for making a copy"
-msgstr "E698: Değişken kopyalama için çok iç içe geçmiş"
-
msgid ""
"\n"
"\tLast set from "
@@ -899,7 +1190,7 @@ msgid "E5005: Unable to dump %s: container references itself in %s"
msgstr "E5005: %s dökülemiyor: Kapsayıcı, %s içinde özüne başvuruyor"
#, c-format
-msgid "E684: list index out of range: %<PRId64>"
+msgid "E684: List index out of range: %<PRId64>"
msgstr "E684: Liste indeksi erim dışında: %<PRId64>"
#, c-format
@@ -910,9 +1201,16 @@ msgid "E957: Invalid window number"
msgstr "E957: Geçersiz pencere numarası"
#, c-format
+msgid "E935: Invalid submatch number: %d"
+msgstr "E935: Geçersiz alteşleşme numarası: %d"
+
+#, c-format
msgid "E998: Reduce of an empty %s with no initial value"
msgstr "E998: Başlangıç değeri olmayan boş bir %s için reduce() yapılamıyor"
+msgid "E1132: Missing function argument"
+msgstr "E1132: İşlev argümanı eksik"
+
#, c-format
msgid "Error converting the call result: %s"
msgstr "Çağrı sonuçlarını dönüştürürken hata: %s"
@@ -946,6 +1244,9 @@ msgstr "flatten() argümanı"
msgid "extend() argument"
msgstr "extend() argümanı"
+msgid "extendnew() argument"
+msgstr "extendnew() argümanı"
+
msgid "E5000: Cannot find tab number."
msgstr "E5000: Sekme numarası bulunamıyor."
@@ -1022,10 +1323,6 @@ msgstr "E6100: \"%s\", geçerli bir stdpath değil"
msgid "(Invalid)"
msgstr "(Geçersiz)"
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: Geçersiz alteşleşme numarası: %d"
-
msgid "Can only call this function in an unmodified buffer"
msgstr "Bu işlev yalnızca değiştirilmemiş bir arabellekte çağrılabilir"
@@ -1047,29 +1344,73 @@ msgstr "E482: %s dosyası yazma için açılamıyor: %s"
msgid "E80: Error when closing file %s: %s"
msgstr "E80: %s dosyası kapatılırken hata: %s"
+msgid "E743: Variable nested too deep for (un)lock"
+msgstr "E743: Değişken kilitlenemez/kilidi açılamaz, çok iç içe geçmiş"
+
+msgid "E908: Using an invalid value as a String"
+msgstr "E908: Bir dizi olarak geçersiz bir değer kullanılıyor"
+
#, c-format
msgid "E1174: String required for argument %d"
msgstr "E1174: %d argümanı için dizi gerekiyor"
#, c-format
-msgid "E1142: Non-empty string required for argument %d"
-msgstr "E1142: %d argümanı için boş olmayan dizi gerekiyor"
+msgid "E1175: Non-empty string required for argument %d"
+msgstr "E1175: %d argümanı için boş olmayan dizi gerekiyor"
+
+#, c-format
+msgid "E1206: Dictionary required for argument %d"
+msgstr "E1206: %d argümanı için sözlük gerekiyor"
#, c-format
msgid "E1210: Number required for argument %d"
msgstr "E1210: %d argümanı için sayı gerekiyor"
#, c-format
-msgid "E5142: Failed to open file %s: %s"
-msgstr "E5142: %s dosyası açılamadı: %s"
+msgid "E1211: List required for argument %d"
+msgstr "E1211: %d argümanı için liste gerekiyor"
+
+#, c-format
+msgid "E1212: Bool required for argument %d"
+msgstr "E1212: %d argümanı için Boole gerekiyor"
+
+#, c-format
+msgid "E1219: Float or Number required for argument %d"
+msgstr "E1219: %d argümanı için sayı veya kayan noktalı değer gerekiyor"
#, c-format
-msgid "E5143: Failed to write to file %s: %s"
-msgstr "E5143: %s dosyası yazılamadı: %s"
+msgid "E1220: String or Number required for argument %d"
+msgstr "E1220: %d argümanı için sayı veya dizi gerekiyor"
#, c-format
-msgid "E5144: Failed to close file %s: %s"
-msgstr "E5144: %s dosyası kapatılamadı: %s"
+msgid "E1222: String or List required for argument %d"
+msgstr "E1222: %d argümanı için dizi veya liste gerekiyor"
+
+#, c-format
+msgid "E1226: List or Blob required for argument %d"
+msgstr "E1226: %d argümanı için liste veya ikili nesne gerekiyor"
+
+#, c-format
+msgid "E1238: Blob required for argument %d"
+msgstr "E1238: %d argümanı için ikili nesne gerekiyor"
+
+#, c-format
+msgid "E1239: Invalid value for blob: %d"
+msgstr "E1239: İkili nesne için geçersiz değer: %d"
+
+#, c-format
+msgid "E1256: String or function required for argument %d"
+msgstr "E1256: %d argümanı için dizi veya işlev gerekiyor"
+
+#, c-format
+msgid "E1297: Non-NULL Dictionary required for argument %d"
+msgstr "E1297: %d argümanı için boş olmayan sözlük gerekiyor"
+
+msgid "E710: List value has more items than target"
+msgstr "E710: Liste değeri hedeften daha fazla ögeye sahip"
+
+msgid "E711: List value has not enough items"
+msgstr "E711: Liste değeri yeterli ögeye sahip değil"
msgid "sort() argument"
msgstr "sort() argümanı"
@@ -1090,8 +1431,8 @@ msgstr "E6000: Argüman bir işlev veya işlev adı değil"
msgid "E737: Key already exists: %s"
msgstr "E737: Anahtar hâlihazırda var: %s"
-msgid "E743: variable nested too deep for (un)lock"
-msgstr "E743: Değişken kilitlenemez/kilidi açılamaz, çok iç içe geçmiş"
+msgid "E972: Blob value does not have the right number of bytes"
+msgstr "E972: İkili geniş nesne değeri doğru bayt sayısına sahip değil"
#, c-format
msgid "E741: Value is locked: %.*s"
@@ -1140,18 +1481,15 @@ msgstr "E974: Bir Sayı olarak ikili geniş nesne kullanılıyor"
msgid "E685: using an invalid value as a Number"
msgstr "E685: Bir sayı olarak geçersiz bir değer kullanılıyor"
-msgid "E730: using List as a String"
+msgid "E730: Using a List as a String"
msgstr "E730: Bir dizi olarak liste kullanılıyor"
-msgid "E731: using Dictionary as a String"
+msgid "E731: Using a Dictionary as a String"
msgstr "E731: Bir dizi olarak sözlük kullanılıyor"
-msgid "E976: using Blob as a String"
+msgid "E976: Using a Blob as a String"
msgstr "E976: Bir dizi olarak ikili geniş nesne kullanılıyor"
-msgid "E908: using an invalid value as a String"
-msgstr "E908: Bir dizi olarak geçersiz bir değer kullanılıyor"
-
msgid "E891: Using a Funcref as a Float"
msgstr "E891: Bir kayan noktalı değer olarak bir Funcref kullanılıyor"
@@ -1178,6 +1516,10 @@ msgid "E808: Number or Float required"
msgstr "E808: Sayı veya kayan noktalı değer gerekiyor"
#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Bilinmeyen işlev: %s"
+
+#, c-format
msgid "E122: Function %s already exists, add ! to replace it"
msgstr "E122: %s işlevi hâlihazırda mevcut, değiştirmek için ! ekleyin"
@@ -1191,6 +1533,23 @@ msgstr "E718: Funcref gerekiyor"
msgid "E130: Unknown function: %s"
msgstr "E130: Bilinmeyen işlev: %s"
+msgid "E454: Function list was modified"
+msgstr "E454: İşlev istesi değiştirildi"
+
+msgid "E1058: Function nesting too deep"
+msgstr "E1058: Pek çok iç içe geçmiş işlev"
+
+#, c-format
+msgid "E1068: No white space allowed before '%s': %s"
+msgstr "E1068: '%s' öncesi boşluğa izin verilmiyor: %s"
+
+#, c-format
+msgid "E1145: Missing heredoc end marker: %s"
+msgstr "E1145: heredoc bitiş imleyicisi eksik: '%s'"
+
+msgid "E1300: Cannot use a partial with dictionary for :defer"
+msgstr "E1300: :defer için sözlükle bir kısımsal kullanılamıyor"
+
#, c-format
msgid "E125: Illegal argument: %s"
msgstr "E125: İzin verilmeyen argüman: %s"
@@ -1203,6 +1562,10 @@ msgid "E989: Non-default argument follows default argument"
msgstr "E989: Öntanımlı olmayan argüman öntanımlı argümandan sonra"
#, c-format
+msgid "E451: Expected }: %s"
+msgstr "E451: } bekleniyordu: %s"
+
+#, c-format
msgid "E740: Too many arguments for function %s"
msgstr "E740: %s işlevi için pek fazla argüman"
@@ -1237,10 +1600,6 @@ msgid "E699: Too many arguments"
msgstr "E699: Çok fazla argüman"
#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Bilinmeyen işlev: %s"
-
-#, c-format
msgid "E276: Cannot use function as a method: %s"
msgstr "E276: İşlev bir yöntem olarak kullanılamaz: %s"
@@ -1249,10 +1608,6 @@ msgid "E933: Function was deleted: %s"
msgstr "E933: İşlev silinmiş: %s"
#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Şu işlev için yetersiz sayıda argüman: %s"
-
-#, c-format
msgid "E120: Using <SID> not in a script context: %s"
msgstr "E120: <SID> bir betik bağlamında kullanılmıyor: %s"
@@ -1293,9 +1648,6 @@ msgstr "E126: :endfunction eksik"
msgid "W22: Text found after :endfunction: %s"
msgstr "W22: :endfunction sonrası metin bulundu: %s"
-msgid "E1058: function nesting too deep"
-msgstr "E1058: Pek çok iç içe geçmiş işlev"
-
#, c-format
msgid "E707: Function name conflicts with variable: %s"
msgstr "E707: İşlev adı şu değişken ile çakışıyor: %s"
@@ -1326,8 +1678,12 @@ msgstr "E18: :let içinde beklenmedik karakter"
msgid "E940: Cannot lock or unlock variable %s"
msgstr "E940: Değişken %s kilitlenemiyor veya açılamıyor"
-msgid "E991: cannot use =<< here"
-msgstr "E991: Burada =<< kullanılamaz"
+#, c-format
+msgid "E963: Setting %s to value with wrong type"
+msgstr "E963: %s yanlış türe sahip değere ayarlanıyor"
+
+msgid "E991: Cannot use =<< here"
+msgstr "E991: Burada =<< kullanılamıyor"
msgid "E221: Marker cannot start with lower case letter"
msgstr "E221: İmleyici küçük harfle başlayamaz"
@@ -1366,14 +1722,14 @@ msgid "E108: No such variable: \"%s\""
msgstr "E108: Böyle bir değişken yok: \"%s\""
#, c-format
-msgid "E963: setting %s to value with wrong type"
-msgstr "E963: %s yanlış türe sahip değere ayarlanıyor"
-
-#, c-format
msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
msgstr "E794: Kum havuzunda değişken ayarlanamaz: \"%.*s\""
#, c-format
+msgid "E1122: Variable is locked: %*s"
+msgstr "E1122: Değer kilitli: %*s"
+
+#, c-format
msgid "E795: Cannot delete variable %.*s"
msgstr "E795: %.*s değişkeni silinemiyor"
@@ -1385,6 +1741,14 @@ msgstr "E704: Funcref değişkeni BÜYÜK harf ile başlamalıdır: %s"
msgid "E705: Variable name conflicts with existing function: %s"
msgstr "E705: Değişken adı mevcut işlevle çakışıyor: %s"
+#, c-format
+msgid "E521: Number required: &%s = '%s'"
+msgstr "E521: Sayı gerekiyor: &%s = '%s'"
+
+msgid "E1308: Cannot resize a window in another tab page"
+msgstr ""
+"E1308: Başka bir sekme sayfasında bir pencere yeniden boyutlandırılamıyor"
+
msgid "tcp address must be host:port"
msgstr "tcp adresi makine:kapı olmalı"
@@ -1394,6 +1758,9 @@ msgstr "makine veya kapı bulunamadı"
msgid "connection refused"
msgstr "bağlantı reddedildi"
+msgid "E144: Non-numeric argument to :z"
+msgstr "E144: :z için sayısal olmayan argüman"
+
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"
msgstr "<%s>%s%s %d, Onalt. %02x, Sek. %03o, Digr %s"
@@ -1422,6 +1789,12 @@ msgid "E134: Cannot move a range of lines into itself"
msgstr "E134: Satırlardan oluşan erim kendi içine taşınamaz"
#, c-format
+msgid "%<PRId64> line moved"
+msgid_plural "%<PRId64> lines moved"
+msgstr[0] "%<PRId64> satır taşındı"
+msgstr[1] "%<PRId64> satır taşındı"
+
+#, c-format
msgid "E482: Can't create file %s"
msgstr "E482: %s dosyası oluşturulamıyor"
@@ -1435,6 +1808,10 @@ msgstr "E135: *Süzgeç* otokomutları şu anki arabelleği değiştirmemelidir"
msgid "[No write since last change]\n"
msgstr "[Son değişiklikten sonra yazılmadı]\n"
+#, c-format
+msgid "E503: \"%s\" is not a file or writable device"
+msgstr "E503: \"%s\", bir dosya veya yazılabilir aygıt değil"
+
msgid "Write partial file?"
msgstr "Dosyanın bir kısmı yazılsın mı?"
@@ -1486,9 +1863,6 @@ msgstr "E505: \"%s\" saltokunur (geçersiz kılmak için ! ekleyin)"
msgid "E143: Autocommands unexpectedly deleted new buffer %s"
msgstr "E143: yeni %s arabelleğini otokomutlar beklenmedik bir biçimde sildi"
-msgid "E144: non-numeric argument to :z"
-msgstr "E144: :z için sayısal olmayan argüman"
-
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: Düzenli ifadeler harflerle sınırlandırılamaz"
@@ -1499,6 +1873,30 @@ msgstr "%s ile değiştir (y/n/a/q/l/^E/^Y)?"
msgid "(Interrupted) "
msgstr "(Yarıda kesildi) "
+#, c-format
+msgid "%<PRId64> match on %<PRId64> line"
+msgid_plural "%<PRId64> matches on %<PRId64> line"
+msgstr[0] "%<PRId64> eşleşme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> eşleşme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> substitution on %<PRId64> line"
+msgid_plural "%<PRId64> substitutions on %<PRId64> line"
+msgstr[0] "%<PRId64> değiştirme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> değiştirme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> match on %<PRId64> lines"
+msgid_plural "%<PRId64> matches on %<PRId64> lines"
+msgstr[0] "%<PRId64> eşleşme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> eşleşme, %<PRId64> satırda"
+
+#, c-format
+msgid "%<PRId64> substitution on %<PRId64> lines"
+msgid_plural "%<PRId64> substitutions on %<PRId64> lines"
+msgstr[0] "%<PRId64> değiştirme, %<PRId64> satırda"
+msgstr[1] "%<PRId64> değiştirme, %<PRId64> satırda"
+
msgid "E147: Cannot do :global recursive with a range"
msgstr "E147: :global özyineli olarak bir erim ile yapılamaz"
@@ -1517,6 +1915,10 @@ msgid "No old files"
msgstr "Eski dosya yok"
#, c-format
+msgid "E666: Compiler not supported: %s"
+msgstr "E666: Derleyici desteklenmiyor: %s"
+
+#, c-format
msgid "Save changes to \"%s\"?"
msgstr "Değişiklikler şuraya kaydedilsin mi: \"%s\"?"
@@ -1535,21 +1937,33 @@ msgstr "E162: \"%s\" arabelleği son değişiklikten sonra yazılmadı"
msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
msgstr "Uyarı: Diğer arabelleğe aniden girildi (otokomutları denetleyin)"
-#, c-format
-msgid "E666: compiler not supported: %s"
-msgstr "E666: Derleyici desteklenmiyor: %s"
-
msgid "E464: Ambiguous use of user-defined command"
msgstr "E464: Kullanıcı tanımlı komutun belirsiz kullanımı"
+msgid "E489: No call stack to substitute for \"<stack>\""
+msgstr "E489: \"<yığın>\" yerine koymak için çağrı yığını yok"
+
msgid "E492: Not an editor command"
msgstr "E492: Bir düzenleyici komutu değil"
-msgid "E498: no :source file name to substitute for \"<sfile>\""
+msgid "E495: No autocommand file name to substitute for \"<afile>\""
+msgstr "E495: \"<odosyası>\" yerine koymak için otokomut dosya adı yok"
+
+msgid "E496: No autocommand buffer number to substitute for \"<abuf>\""
+msgstr ""
+"E496: \"<oarabelleği>\" yerine koymak için otokomut arabellek numarası yok"
+
+msgid "E497: No autocommand match name to substitute for \"<amatch>\""
+msgstr "E497: \"<oeşi>\" yerine koymak için otokomut eşleşme adı yok"
+
+msgid "E498: No :source file name to substitute for \"<sfile>\""
msgstr "E498: \"<kdosyası>\" yerine koymak için :source dosya adı yok"
-msgid "E489: no call stack to substitute for \"<stack>\""
-msgstr "E489: \"<yığın>\" yerine koymak için çağrı yığını yok"
+msgid "E842: No line number to use for \"<slnum>\""
+msgstr "E842: \"<slnum>\" kullanımı için satır numarası yok"
+
+msgid "E961: No line number to use for \"<sflnum>\""
+msgstr "E961: \"<sflnum>\" kullanımı için satır numarası yok"
msgid "E1274: No script file name to substitute for \"<script>\""
msgstr "E1274: \"<betik>\" yerine koymak için betik dosyası adı yok"
@@ -1567,16 +1981,16 @@ msgstr "Çalıştırılıyor: %s"
msgid "line %"
msgstr "satır %"
-#, c-format
-msgid "E605: Exception not caught: %s"
-msgstr "E605: Kural dışı durum yakalanmadı: %s"
-
msgid "End of sourced file"
msgstr "Kaynak alınan dosyanın sonu"
msgid "End of function"
msgstr "İşlevin sonu"
+#, c-format
+msgid "E605: Exception not caught: %s"
+msgstr "E605: Kural dışı durum yakalanmadı: %s"
+
msgid ""
"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
msgstr ""
@@ -1599,6 +2013,18 @@ msgid "E319: The command is not available in this version"
msgstr "E319: Üzgünüm, komut bu sürümde mevcut değil"
#, c-format
+msgid "%d more file to edit. Quit anyway?"
+msgid_plural "%d more files to edit. Quit anyway?"
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "E173: %<PRId64> more file to edit"
+msgid_plural "E173: %<PRId64> more files to edit"
+msgstr[0] "E173: Düzenlenecek %<PRId64> dosya daha var"
+msgstr[1] "E173: Düzenlenecek %<PRId64> dosya daha var"
+
+#, c-format
msgid "E185: Cannot find color scheme '%s'"
msgstr "E185: '%s' renk düzeni bulunamadı"
@@ -1647,22 +2073,6 @@ msgstr "E192: :normal'in özyineli kullanımı çok derinde"
msgid "E194: No alternate file name to substitute for '#'"
msgstr "E194: '#' yerine koymak için başka dosya adı yok"
-msgid "E495: no autocommand file name to substitute for \"<afile>\""
-msgstr "E495: \"<odosyası>\" yerine koymak için otokomut dosya adı yok"
-
-msgid "E496: no autocommand buffer number to substitute for \"<abuf>\""
-msgstr ""
-"E496: \"<oarabelleği>\" yerine koymak için otokomut arabellek numarası yok"
-
-msgid "E497: no autocommand match name to substitute for \"<amatch>\""
-msgstr "E497: \"<oeşi>\" yerine koymak için otokomut eşleşme adı yok"
-
-msgid "E842: no line number to use for \"<slnum>\""
-msgstr "E842: \"<slnum>\" kullanımı için satır numarası yok"
-
-msgid "E961: no line number to use for \"<sflnum>\""
-msgstr "E961: \"<sflnum>\" kullanımı için satır numarası yok"
-
#, no-c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: '%' veya '#' için boş dosya adı, yalnızca \":p:h\" ile çalışır"
@@ -1673,6 +2083,12 @@ msgstr "E500: Boş bir satır olarak değer biçer"
msgid "Untitled"
msgstr "Adsız"
+msgid "E583: Multiple :else"
+msgstr "E583: Birden çok :else"
+
+msgid "E607: Multiple :finally"
+msgstr "E607: Birden çok :finally"
+
msgid "E608: Cannot :throw exceptions with 'Vim' prefix"
msgstr "E608: 'Vim' öneki ile kural dışı durumlar :throw edilemez"
@@ -1681,14 +2097,14 @@ msgid "Exception thrown: %s"
msgstr "Kural dışı durum verdi: %s"
#, c-format
-msgid "Exception finished: %s"
-msgstr "Kural dışı durum bitti: %s"
-
-#, c-format
msgid "Exception discarded: %s"
msgstr "Kural dışı durum kenara atıldı: %s"
#, c-format
+msgid "Exception finished: %s"
+msgstr "Kural dışı durum bitti: %s"
+
+#, c-format
msgid "%s, line %<PRId64>"
msgstr "%s, %<PRId64>. satır"
@@ -1732,9 +2148,6 @@ msgstr "E581: :if olmadan :else"
msgid "E582: :elseif without :if"
msgstr "E582: :if olmadan :elseif"
-msgid "E583: multiple :else"
-msgstr "E583: Birden fazla :else"
-
msgid "E584: :elseif after :else"
msgstr "E584: :else sonrası :elseif"
@@ -1765,15 +2178,9 @@ msgstr "E604: :finally sonrası :catch"
msgid "E606: :finally without :try"
msgstr "E606: :try olmadan :finally"
-msgid "E607: multiple :finally"
-msgstr "E607: Birden fazla :finally"
-
msgid "E602: :endtry without :try"
msgstr "E602: :try olmadan :endtry"
-msgid "E193: :endfunction not inside a function"
-msgstr "E193: :endfunction, bir işlev içinde değil"
-
msgid "E811: Not allowed to change buffer information now"
msgstr "E811: Şu anda arabellek bilgisi değiştirilemez"
@@ -1814,8 +2221,8 @@ msgstr "[Komut Satırı]"
msgid "E199: Active window or buffer deleted"
msgstr "E199: Etkin pencere veya arabellek silinmiş"
-msgid "E854: path too long for completion"
-msgstr "E854: Yol tamamlama için çok uzun"
+msgid "E854: Path too long for completion"
+msgstr "E854: Yol tamamlama için pek uzun"
#, c-format
msgid ""
@@ -1844,13 +2251,6 @@ msgstr "E347: Başka bir \"%s\" dosyası yol içinde bulunamadı"
msgid "E812: Autocommands changed buffer or buffer name"
msgstr "E812: Otokomutlar arabelleği veya arabellek adını değiştirdi"
-#, c-format
-msgid "E676: No matching autocommands for buftype=%s buffer"
-msgstr "E676: buftype=%s arabelleği için eşleşen otokomut yok"
-
-msgid "is a directory"
-msgstr "bir dizin"
-
msgid "Illegal file name"
msgstr "İzin verilmeyen dosya adı"
@@ -1890,12 +2290,6 @@ msgstr "[Eksik CR]"
msgid "[long lines split]"
msgstr "[uzun satırlar bölünmüş]"
-msgid "[NOT converted]"
-msgstr "[dönüştürülmedi]"
-
-msgid "[converted]"
-msgstr "[dönüştürüldü]"
-
#, c-format
msgid "[CONVERSION ERROR in line %<PRId64>]"
msgstr "[%<PRId64>. satırda DÖNÜŞTÜRME HATASI]"
@@ -1922,100 +2316,6 @@ msgstr "[Yeni Dosya]"
msgid "[New]"
msgstr "[Yeni]"
-msgid "E203: Autocommands deleted or unloaded buffer to be written"
-msgstr "E203: Otokomutlar arabelleği silmiş veya yazılması için kaldırmışlar"
-
-msgid "E204: Autocommand changed number of lines in unexpected way"
-msgstr "E204: Otokomut satır sayısını beklenmedik biçimde değiştirdi"
-
-msgid "is not a file or writable device"
-msgstr "bir dosya veya yazılabilir aygıt değil"
-
-msgid "is read-only (add ! to override)"
-msgstr "saltokunur (geçersiz kılmak için ! ekleyin)"
-
-#, c-format
-msgid "E303: Unable to create directory \"%s\" for backup file: %s"
-msgstr "E303: Yedek dosyası için \"%s\" dizini oluşturulamadı: %s"
-
-msgid "E506: Can't write to backup file (add ! to override)"
-msgstr "E506: Yedek dosyasına yazılamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E509: Cannot create backup file (add ! to override)"
-msgstr "E509: Yedek dosyası oluşturulamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E510: Can't make backup file (add ! to override)"
-msgstr "E510: Yedek dosyası yapılamıyor (geçersiz kılmak için ! ekleyin)"
-
-msgid "E214: Can't find temp file for writing"
-msgstr "E214: Yazma için geçici dosya bulunamıyor"
-
-msgid "E213: Cannot convert (add ! to write without conversion)"
-msgstr "E213: Dönüştürülemiyor (dönüştürmeden yazmak için ! ekleyin)"
-
-msgid "E166: Can't open linked file for writing"
-msgstr "E166: Bağlı dosya yazma için açılamıyor"
-
-#, c-format
-msgid "E212: Can't open file for writing: %s"
-msgstr "E212: Dosya yazma için açılamıyor: %s"
-
-#, c-format
-msgid "E512: Close failed: %s"
-msgstr "E512: Kapatma başarısız oldu: %s"
-
-msgid "E513: write error, conversion failed (make 'fenc' empty to override)"
-msgstr ""
-"E513: Yazma hatası, dönüştürme başarısız (geçersiz kılmak için 'fenc'i boş "
-"bırakın)"
-
-msgid "E513: write error, conversion failed in line %"
-msgstr "E513: Yazma hatası, şu satırda dönüştürme başarısız: %"
-
-msgid "E514: write error (file system full?)"
-msgstr "E514: Yazma hatası (dosya sistemi dolu mu?)"
-
-msgid " CONVERSION ERROR"
-msgstr " DÖNÜŞTÜRME HATASI"
-
-#, c-format
-msgid " in line %<PRId64>;"
-msgstr " %<PRId64>. satırda;"
-
-msgid "[Device]"
-msgstr "[Aygıt]"
-
-msgid " [a]"
-msgstr " [i]"
-
-msgid " appended"
-msgstr " iliştirildi"
-
-msgid " [w]"
-msgstr " [y]"
-
-msgid " written"
-msgstr " yazıldı"
-
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: Yama kipi: Orijinal dosya kaydedilemiyor"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: Yama kipi: Orijinal boş dosyaya dokunulamıyor"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Yedek dosyası silinemiyor"
-
-msgid ""
-"\n"
-"WARNING: Original file may be lost or damaged\n"
-msgstr ""
-"\n"
-"UYARI: Orijinal dosya kaybolmuş veya hasar görmüş olabilir\n"
-
-msgid "don't quit the editor until the file is successfully written!"
-msgstr "dosya başarılı bir biçimde yazılana kadar düzenleyiciden çıkmayın!"
-
msgid "[dos format]"
msgstr "[dos biçimi]"
@@ -2034,18 +2334,24 @@ msgstr "[unix biçimi]"
msgid "[unix]"
msgstr "[unix]"
+#, c-format
+msgid "%<PRId64> line, "
+msgid_plural "%<PRId64> lines, "
+msgstr[0] "%<PRId64> satır, "
+msgstr[1] "%<PRId64> satır, "
+
+#, c-format
+msgid "%<PRId64> byte"
+msgid_plural "%<PRId64> bytes"
+msgstr[0] "%<PRId64> bayt"
+msgstr[1] "%<PRId64> bayt"
+
msgid "[Incomplete last line]"
msgstr "[Tamamlanmamış son satır]"
msgid "[noeol]"
msgstr "[satır sonu yok]"
-msgid "WARNING: The file has been changed since reading it!!!"
-msgstr "UYARI: Bu dosya açıldıktan sonra başkası tarafından değiştirilmiş!!!"
-
-msgid "Do you really want to write to it"
-msgstr "Yine de yazmak istiyor musunuz?"
-
#, c-format
msgid "E208: Error writing to \"%s\""
msgstr "E208: Şuraya yazılamadı: \"%s\""
@@ -2128,12 +2434,30 @@ msgstr "E350: Şu anki 'foldmethod' ile kıvırma oluşturulamıyor"
msgid "E351: Cannot delete fold with current 'foldmethod'"
msgstr "E351: Şu anki 'foldmethod' ile kıvırma silinemiyor"
-msgid "E222: Add to read buffer"
-msgstr "E222: Okunan arabelleğe ekle"
+#, c-format
+msgid "+--%3ld line folded"
+msgid_plural "+--%3ld lines folded "
+msgstr[0] ""
+msgstr[1] ""
+
+#, c-format
+msgid "+-%s%3ld line: "
+msgid_plural "+-%s%3ld lines: "
+msgstr[0] "+-%s%3ld satır: "
+msgstr[1] "+-%s%3ld satır: "
-msgid "E223: recursive mapping"
+msgid "E223: Recursive mapping"
msgstr "E223: Özyineli eşlemleme"
+msgid "E1255: <Cmd> mapping must end with <CR>"
+msgstr "E1255: <Cmd> eşlemlemesi <CR> ile bitmelidir"
+
+msgid "E1136: <Cmd> mapping must end with <CR> before second <Cmd>"
+msgstr "E1136: <Cmd> eşlemlemesi ikinci <Cmd>'den önce <CR> ile bitmelidir"
+
+msgid "E222: Add to read buffer"
+msgstr "E222: Okunan arabelleğe ekle"
+
msgid "--No lines in buffer--"
msgstr "--Arabellek içinde satır yok--"
@@ -2155,9 +2479,9 @@ msgstr "E10: \\ sonrasında /, ? veya & gelmeli"
msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits"
msgstr "E11: Komut satırı penceresinde geçersiz; <CR> çalıştırır, CTRL-C çıkar"
-msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
+msgid "E12: Command not allowed in secure mode in current dir or tag search"
msgstr ""
-"E12: Geçerli dizin veya etiket aramasında exrc veya vimrc'den komutlara izin "
+"E12: Geçerli dizin veya etiket aramasında güvenli kipteyken komuta izin "
"verilmiyor"
msgid "E169: Command too recursive"
@@ -2217,8 +2541,8 @@ msgid "E983: Duplicate argument: %s"
msgstr "E983: Yinelenen argüman: %s"
#, c-format
-msgid "E15: Invalid expression: %s"
-msgstr "E15: Geçersiz ifade: %s"
+msgid "E15: Invalid expression: \"%s\""
+msgstr "E15: Geçersiz ifade: \"%s\""
msgid "E16: Invalid range"
msgstr "E16: Geçersiz erim"
@@ -2426,6 +2750,10 @@ msgid "E118: Too many arguments for function: %s"
msgstr "E118: İşlev için pek fazla argüman: %s"
#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Şu işlev için yetersiz sayıda argüman: %s"
+
+#, c-format
msgid "E716: Key not present in Dictionary: \"%s\""
msgstr "E716: Anahtar sözlükte mevcut değil: \"%s\""
@@ -2470,9 +2798,6 @@ msgstr "E255: İşaret verisinde okunamadı!"
msgid "E72: Close error on swap file"
msgstr "E72: Takas dosyasında kapatma hatası"
-msgid "E73: tag stack empty"
-msgstr "E73: Etiket yığını boş"
-
msgid "E74: Command too complex"
msgstr "E74: Komut çok karmaşık"
@@ -2517,16 +2842,17 @@ msgstr "E81: <SID> bir betik bağlamında kullanılmıyor"
msgid "E107: Missing parentheses: %s"
msgstr "E107: Ayraç eksik: %s"
-msgid "E363: pattern uses more memory than 'maxmempattern'"
-msgstr "E363: Dizgi 'maxmempattern' ögesinden daha fazla bellek kullanıyor"
-
-msgid "E749: empty buffer"
+msgid "E749: Empty buffer"
msgstr "E749: Boş arabellek"
#, c-format
msgid "E86: Buffer %<PRId64> does not exist"
msgstr "E86: Arabellek %<PRId64> mevcut değil"
+#, c-format
+msgid "E193: %s not inside a function"
+msgstr "E193: %s bir işlev içinde değil"
+
msgid "E682: Invalid search pattern or delimiter"
msgstr "E682: Geçersiz arama dizgisi veya sınırlandırıcısı"
@@ -2563,21 +2889,19 @@ msgstr "E519: Özellik desteklenmiyor"
msgid "E856: Filename too long"
msgstr "E856: Dosya adı pek uzun"
-msgid "E806: using Float as a String"
-msgstr "E806: Kayan Noktalı Değer, bir Dizi yerine kullanılıyor"
+msgid "E806: Using a Float as a String"
+msgstr "E806: Bir dizi olarak kayan noktalı değer kullanılıyor"
msgid "E788: Not allowed to edit another buffer now"
msgstr "E788: Şu anda başka bir arabellek düzenlenemez"
#, c-format
-msgid "E5500: autocmd has thrown an exception: %s"
-msgstr "E5500: Otokomut, bir istisna attı: %s"
+msgid "E1023: Using a Number as a Bool: %d"
+msgstr "E1023: Bir Boole olarak bir sayı kullanılıyor: %d"
-msgid "E5520: <Cmd> mapping must end with <CR>"
-msgstr "E5520: <Cmd> eşlemlemesi <CR> ile bitmelidir"
-
-msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>"
-msgstr "E5521: <Cmd> eşlemlemesi ikinci <Cmd>'den önce <CR> ile bitmelidir"
+#, c-format
+msgid "E1085: Not a callable type: %s"
+msgstr "E1085: Çağrılabilir bir tür değil: %s"
#, c-format
msgid "E5555: API call: %s"
@@ -2593,9 +2917,6 @@ msgstr "E5601: Pencere kapatılamıyor, yalnızca yüzen pencere açık kalır"
msgid "E5602: Cannot exchange or rotate float"
msgstr "E5602: Kayan noktalı değer değiştirilemiyor veya döndürülemiyor"
-msgid "E1142: Non-empty string required"
-msgstr "E1142: Boş olmayan dizi gerekiyor"
-
msgid "E1155: Cannot define autocommands for ALL events"
msgstr "E1155: Otokomutlar TÜM olaylar için tanımlanamıyor"
@@ -2611,11 +2932,31 @@ msgstr "E5248: Grup adında geçersiz karakter"
msgid "E1249: Highlight group name too long"
msgstr "E1249: Vurgulama grubu adı pek uzun"
+#, c-format
+msgid "E966: Invalid line number: %ld"
+msgstr "E966: Geçersiz satır numarası: %ld"
+
+#, c-format
+msgid "E1278: Stray '}' without a matching '{': %s"
+msgstr "E1278: Eşleşen bir '{' olmadan başıboş '}': %s"
+
+#, c-format
+msgid "E1279: Missing '}': %s"
+msgstr "E1279: '}' eksik: %s"
+
msgid "E5767: Cannot use :undo! to redo or move to a different undo branch"
msgstr ""
"E5767: Yinelemek veya başka bir geri al dalına taşımak için :undo! "
"kullanılamaz"
+#, c-format
+msgid "E5570: Cannot update trust file: %s"
+msgstr "E5570: Güven dosyası güncellenemiyor: %s"
+
+#, c-format
+msgid "E355: Unknown option: %s"
+msgstr "E355: Bilinmeyen seçenek: %s"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Arama dosyanın BAŞINI geçti, dosyanın SONUNDAN sürüyor"
@@ -2625,6 +2966,10 @@ msgstr "Arama dosyanın SONUNU geçti, dosyanın BAŞINDAN sürüyor"
msgid " line "
msgstr " satır "
+#, c-format
+msgid "E685: Internal error: hash_add(): duplicate key \"%s\""
+msgstr "E685: İçsel hata: hash_add(): Yinelenmiş anahtar \"%s\""
+
msgid "E478: Don't panic!"
msgstr "E478: Panik yok!"
@@ -2668,9 +3013,24 @@ msgid "E424: Too many different highlighting attributes in use"
msgstr "E424: Çok fazla değişik vurgulama kuralları kullanılıyor"
#, c-format
-msgid "E411: highlight group not found: %s"
+msgid "E411: Highlight group not found: %s"
msgstr "E411: Vurgulama grubu bulunamadı: %s"
+msgid "E414: Group has settings, highlight link ignored"
+msgstr "E414: Grup ayarları var, vurgulama bağlantısı yok sayıldı"
+
+#, c-format
+msgid "E415: Unexpected equal sign: %s"
+msgstr "E415: Beklenmedik eşittir imi: %s"
+
+#, c-format
+msgid "E416: Missing equal sign: %s"
+msgstr "E416: Eksik eşittir imi: %s"
+
+#, c-format
+msgid "E417: Missing argument: %s"
+msgstr "E417: Argüman eksik: %s"
+
#, c-format
msgid "E412: Not enough arguments: \":highlight link %s\""
msgstr "E412: Yetersiz sayıda argüman: \":highlight link %s\""
@@ -2679,25 +3039,10 @@ msgstr "E412: Yetersiz sayıda argüman: \":highlight link %s\""
msgid "E413: Too many arguments: \":highlight link %s\""
msgstr "E413: Çok fazla argüman: \":highlight link %s\""
-msgid "E414: group has settings, highlight link ignored"
-msgstr "E414: Grup ayarları mevcut, vurgulama bağlantısı yok sayıldı"
-
-#, c-format
-msgid "E415: unexpected equal sign: %s"
-msgstr "E415: Beklenmedik eşittir imi: %s"
-
msgid "E423: Illegal argument"
msgstr "E423: İzin verilmeyen argüman"
#, c-format
-msgid "E416: missing equal sign: %s"
-msgstr "E416: Eksik eşittir imi: %s"
-
-#, c-format
-msgid "E417: missing argument: %s"
-msgstr "E417: Argüman eksik: %s"
-
-#, c-format
msgid "E418: Illegal value: %s"
msgstr "E418: İzin verilmeyen değer: %s"
@@ -2721,159 +3066,6 @@ msgstr "E669: Grup adında yazdırılamayan karakter"
msgid "E849: Too many highlight and syntax groups"
msgstr "E849: Çok fazla vurgulama ve sözdizim grupları"
-msgid "Add a new database"
-msgstr "Yeni bir veritabanı ekle"
-
-msgid "Query for a pattern"
-msgstr "Bir dizgiyi sorgula"
-
-msgid "Show this message"
-msgstr "Bu iletiyi göster"
-
-msgid "Kill a connection"
-msgstr "Bir bağlantıyı kes"
-
-msgid "Reinit all connections"
-msgstr "Tüm bağlantıları yeniden kur"
-
-msgid "Show connections"
-msgstr "Bağlantıları göster"
-
-#, c-format
-msgid "E560: Usage: cs[cope] %s"
-msgstr "E560: Kullanım: cs[cope] %s"
-
-msgid "This cscope command does not support splitting the window.\n"
-msgstr "Bu cscope komutu pencereyi bölmeyi desteklemiyor.\n"
-
-msgid "E562: Usage: cstag <ident>"
-msgstr "E562: Kullanım: cstag <ad>"
-
-msgid "E257: cstag: tag not found"
-msgstr "E257: cstag: Etiket bulunamadı"
-
-#, c-format
-msgid "E563: stat(%s) error: %d"
-msgstr "E563: stat(%s) hatası: %d"
-
-#, c-format
-msgid "E564: %s is not a directory or a valid cscope database"
-msgstr "E564: %s, bir dizin veya geçerli bir cscope veritabanı değil"
-
-#, c-format
-msgid "Added cscope database %s"
-msgstr "cscope veritabanı %s eklendi"
-
-#, c-format
-msgid "E262: error reading cscope connection %<PRIu64>"
-msgstr "E262: cscope bağlantısı %<PRIu64> okunurken hata"
-
-msgid "E561: unknown cscope search type"
-msgstr "E561: Bilinmeyen cscope arama türü"
-
-msgid "E566: Could not create cscope pipes"
-msgstr "E566: cscope veri yolları oluşturulamadı"
-
-msgid "E622: Could not fork for cscope"
-msgstr "E622: cscope için çatal oluşturulamadı"
-
-msgid "cs_create_connection setpgid failed"
-msgstr "cs_create_connection: setpgid başarısız oldu"
-
-msgid "cs_create_connection exec failed"
-msgstr "cs_create_connection: exec başarısız oldu"
-
-msgid "cs_create_connection: fdopen for to_fp failed"
-msgstr "cs_create_connection: to_fp için fdopen başarısız oldu"
-
-msgid "cs_create_connection: fdopen for fr_fp failed"
-msgstr "cs_create_connection: fr_fp için fdopen başarısız oldu"
-
-msgid "E623: Could not spawn cscope process"
-msgstr "E623: cscope süreci ortaya çıkarılamadı"
-
-msgid "E567: no cscope connections"
-msgstr "E567: cscope bağlantıları yok"
-
-#, c-format
-msgid "E469: invalid cscopequickfix flag %c for %c"
-msgstr "E469: Geçersiz cscopequickfix bayrağı %c, %c için"
-
-#, c-format
-msgid "E259: no matches found for cscope query %s of %s"
-msgstr "E259: cscope sorgusu %s/%s için eşleşme bulunamadı"
-
-msgid "cscope commands:\n"
-msgstr "cscope komutları:\n"
-
-#, c-format
-msgid "%-5s: %s%*s (Usage: %s)"
-msgstr "%-5s: %s%*s (Kullanım: %s)"
-
-msgid ""
-"\n"
-" a: Find assignments to this symbol\n"
-" c: Find functions calling this function\n"
-" d: Find functions called by this function\n"
-" e: Find this egrep pattern\n"
-" f: Find this file\n"
-" g: Find this definition\n"
-" i: Find files #including this file\n"
-" s: Find this C symbol\n"
-" t: Find this text string\n"
-msgstr ""
-"\n"
-" a: Bu sembole yapılan atamaları bul\n"
-" c: Bu işlevi çağıran işlevleri bul\n"
-" d: bu işlev tarafından çağrılan işlevleri bul\n"
-" e: Bu egrep dizgisini bul\n"
-" f: Bu dosyayı bul\n"
-" g: Bu tanımı bul\n"
-" i: Bu dosyayı içeren (#including) dosyaları bul\n"
-" s: Bu \"C\" sembolünü bul\n"
-" t: Bu metin dizisini bul\n"
-
-msgid "E568: duplicate cscope database not added"
-msgstr "E568: Yinelenen cscope veritabanı eklenmemiş"
-
-#, c-format
-msgid "E261: cscope connection %s not found"
-msgstr "E261: %s cscope bağlantısı bulunamadı"
-
-#, c-format
-msgid "cscope connection %s closed"
-msgstr "%s cscope bağlantısı bitirildi"
-
-msgid "E570: fatal error in cs_manage_matches"
-msgstr "E570: cs_manage_matches içinde onulmaz hata"
-
-#, c-format
-msgid "Cscope tag: %s"
-msgstr "cscope etiketi: %s"
-
-msgid ""
-"\n"
-" # line"
-msgstr ""
-"\n"
-" # satır"
-
-msgid "filename / context / line\n"
-msgstr "dosya adı / bağlam / satır\n"
-
-#, c-format
-msgid "E609: Cscope error: %s"
-msgstr "E609: cscope hatası: %s"
-
-msgid "All cscope databases reset"
-msgstr "Tüm cscope veritabanları sıfırlandı"
-
-msgid "no cscope connections\n"
-msgstr "cscope bağlantısı yok\n"
-
-msgid " # pid database name prepend path\n"
-msgstr " # pid veritabanı adı başlangıç yolu\n"
-
msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
msgstr ""
"Sayı girip <Enter>'a veya fare düğmesine basın (q veya boş iptal eder): "
@@ -2982,14 +3174,6 @@ msgid "match %d"
msgstr "eşleşme %d"
#, c-format
-msgid "Current %slanguage: \"%s\""
-msgstr "Şu anki %sdil: \"%s\""
-
-#, c-format
-msgid "E197: Cannot set language to \"%s\""
-msgstr "E197: \"%s\" diline ayarlanamıyor"
-
-#, c-format
msgid "E1502: Lua failed to grow stack to %i"
msgstr "E1502: Lua, yığını %i olarak büyütemedi"
@@ -3080,6 +3264,14 @@ msgstr "vim.on_key Lua geri çağrısı çalıştırılırken hata: %.*s"
msgid "Error executing Lua callback: %.*s"
msgstr "Lua geri çağrısı çalıştırılırken hata: %.*s"
+#, c-format
+msgid "Error executing vim.secure.read: %.*s"
+msgstr "vim.secure.read yürütülürken hata: %.*s"
+
+#, c-format
+msgid "Error executing vim.secure.trust: %.*s"
+msgstr "vim.secure.trust yürütülürken hata: %.*s"
+
msgid "Argument missing after"
msgstr "Şundan sonra argüman eksik:"
@@ -3103,6 +3295,9 @@ msgstr "E5421: stdin açılamadı: %s"
msgid "Attempt to open script file again: \"%s %s\"\n"
msgstr "Betik dosyası yeniden açılmaya çalışılıyor: \"%s %s\"\n"
+msgid "--embed conflicts with -es/-Es/-l"
+msgstr "--embed, -es/-Es/-l ile çakışıyor"
+
#, c-format
msgid "Cannot open for reading: \"%s\": %s\n"
msgstr "Okuma için açılamıyor: \"%s\": %s\n"
@@ -3110,9 +3305,6 @@ msgstr "Okuma için açılamıyor: \"%s\": %s\n"
msgid "Cannot open for script output: \""
msgstr "Betik çıktısı için açılamıyor: \""
-msgid "--embed conflicts with -es/-Es"
-msgstr "--embed, -es/-Es ile çakışıyor"
-
msgid "pre-vimrc command line"
msgstr "vimrc uygulanma öncesi komut satırı"
@@ -3165,6 +3357,7 @@ msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"
msgstr ""
"+<komut>, -c <komut> Yapılandırma ve ilk dosya sonrası <komut> çalıştır\n"
+
msgid " -b Binary mode\n"
msgstr "-b İkili kip\n"
@@ -3270,21 +3463,24 @@ msgstr ""
"Tüm seçenekler için \":help startup-options\" yazın.\n"
#, c-format
-msgid "E224: global abbreviation already exists for %s"
+msgid "E224: Global abbreviation already exists for %s"
msgstr "E224: %s için global kısaltma hâlihazırda var"
#, c-format
-msgid "E225: global mapping already exists for %s"
+msgid "E225: Global mapping already exists for %s"
msgstr "E225: %s için global eşlemleme hâlihazırda var"
#, c-format
-msgid "E226: abbreviation already exists for %s"
+msgid "E226: Abbreviation already exists for %s"
msgstr "E226: %s için kısaltma hâlihazırda var"
#, c-format
-msgid "E227: mapping already exists for %s"
+msgid "E227: Mapping already exists for %s"
msgstr "E227: %s için eşlemleme hâlihazırda var"
+msgid "E460: Entries missing in mapset() dict argument"
+msgstr "E460: mapset() sözlük argümanında eksik girdiler"
+
msgid "No abbreviation found"
msgstr "Kısaltma bulunamadı"
@@ -3294,9 +3490,6 @@ msgstr "Eşlemleme bulunamadı"
msgid "E228: makemap: Illegal mode"
msgstr "E228: makemap: İzin verilmeyen kip"
-msgid "E460: entries missing in mapset() dict argument"
-msgstr "E460: mapset() sözlük argümanında girdiler eksik"
-
#, c-format
msgid "E357: 'langmap': Matching character missing for %s"
msgstr "E357: 'langmap': %s için eşleşen karakter eksik"
@@ -3393,10 +3586,10 @@ msgstr "E1112: Liste ögesi %d hücre genişliği geçersiz"
msgid "E1113: Overlapping ranges for 0x%lx"
msgstr "E1113: 0x%lx için üst üste binen erimler"
-msgid "E1114: Only values of 0x100 and higher supported"
+msgid "E1114: Only values of 0x80 and higher supported"
msgstr "E1114: Yalnızca 0x100 ve üstü değerler desteklenir"
-msgid "E293: block was not locked"
+msgid "E293: Block was not locked"
msgstr "E293: Blok kilitlenmemişti"
msgid "E294: Seek error in swap file read"
@@ -3414,6 +3607,37 @@ msgstr "E297: Takas dosyasında yazma hatası"
msgid "E300: Swap file already exists (symlink attack?)"
msgstr "E300: Takas dosyası hâlihazırda var (sembol bağı saldırısı?)"
+#, c-format
+msgid "E315: ml_get: Invalid lnum: %<PRId64>"
+msgstr "E315: ml_get: Geçersiz satır numarası: %<PRId64>"
+
+#, c-format
+msgid "E316: ml_get: Cannot find line %<PRId64>in buffer %d %s"
+msgstr "E316: ml_get: Şu arabellekte %<PRId64>. satır bulunamıyor: %d %s"
+
+msgid "E317: Pointer block id wrong"
+msgstr "E317: İşaretçi blok kimliği yanlış"
+
+msgid "E317: Pointer block id wrong 2"
+msgstr "E317: İşaretçi blok kimliği yanlış 2"
+
+msgid "E317: Pointer block id wrong 3"
+msgstr "E317: İşaretçi blok kimliği yanlış 3"
+
+msgid "E317: Pointer block id wrong 4"
+msgstr "E317: İşaretçi blok kimliği yanlış 4"
+
+#, c-format
+msgid "E322: Line number out of range: %<PRId64> past the end"
+msgstr "E322: Satır numarası erimin dışında: %<PRId64> en sonuncuyu geçmiş"
+
+#, c-format
+msgid "E323: Line count wrong in block %<PRId64>"
+msgstr "E323: %<PRId64>. blokta satır sayısı yanlış"
+
+msgid "E1364: Warning: Pointer block corrupted"
+msgstr "E1364: Uyarı: İşaretçi bloku hasar görmüş"
+
msgid "E298: Didn't get block nr 0?"
msgstr "E298: 0 numaralı blok alınmadı mı?"
@@ -3524,6 +3748,7 @@ msgid "??? from here until ???END lines may have been inserted/deleted"
msgstr ""
"??? konumundan ???SON konumuna kadar satırlar eklenmiş/silinmiş olabilir"
+
msgid "???END"
msgstr "???SON"
@@ -3556,11 +3781,17 @@ msgstr "Kurtarma tamamlandı. Arabellek içeriği dosya içeriğine eşit."
msgid ""
"\n"
-"You may want to delete the .swp file now.\n"
+"You may want to delete the .swp file now."
+msgstr ""
"\n"
+"Bu .swp dosyasını silmeniz iyi olur."
+
+msgid ""
+"\n"
+"Note: process STILL RUNNING: "
msgstr ""
"\n"
-"Bu .swp dosyasını silmeniz iyi olur.\n"
+"Not: Süreç HÂLÂ ÇALIŞIYOR: "
msgid "Swap files found:"
msgstr "Takas dosyası bulundu:"
@@ -3660,26 +3891,12 @@ msgstr "Dosya korundu"
msgid "E314: Preserve failed"
msgstr "E314: Koruma başarısız oldu"
-#, c-format
-msgid "E315: ml_get: invalid lnum: %<PRId64>"
-msgstr "E315: ml_get: Geçersiz satır numarası: %<PRId64>"
-
-#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
-msgstr "E316: ml_get: %<PRId64>. satır %d %s arabelleğinde bulunamıyor"
-
-msgid "E317: pointer block id wrong 3"
-msgstr "E317: Blok 3 gösterge kimliği yanlış"
-
msgid "stack_idx should be 0"
msgstr "stack_idx 0 olmalı"
msgid "E318: Updated too many blocks?"
msgstr "E318: Çok fazla blok mu güncellendi?"
-msgid "E317: pointer block id wrong 4"
-msgstr "E317: Blok 4 gösterge kimliği yanlış"
-
msgid "deleted block 1?"
msgstr "Blok 1 mi silindi?"
@@ -3687,26 +3904,12 @@ msgstr "Blok 1 mi silindi?"
msgid "E320: Cannot find line %<PRId64>"
msgstr "E320: %<PRId64>. satır bulunamıyor"
-msgid "E317: pointer block id wrong"
-msgstr "E317: Gösterge blok kimliği yanlış"
-
msgid "pe_line_count is zero"
msgstr "pe_line_count sıfır"
-#, c-format
-msgid "E322: line number out of range: %<PRId64> past the end"
-msgstr "E322: Satır numarası erimin dışında: %<PRId64> en sonuncuyu geçmiş"
-
-#, c-format
-msgid "E323: line count wrong in block %<PRId64>"
-msgstr "E323: %<PRId64>. blokta satır sayısı yanlış"
-
msgid "Stack size increases"
msgstr "Yığın boyutu artıyor"
-msgid "E317: pointer block id wrong 2"
-msgstr "E317: Blok 2 gösterge kimliği yanlış"
-
#, c-format
msgid "E773: Symlink loop for \"%s\""
msgstr "E773: \"%s\" için sembol bağı döngüsü"
@@ -3882,6 +4085,18 @@ msgstr "Yarıda kes: "
msgid "Press ENTER or type command to continue"
msgstr "Sürdürmek için ENTER'a basın veya komut girin"
+#, c-format
+msgid "%ld more line"
+msgid_plural "%ld more lines"
+msgstr[0] "%ld daha çok satır"
+msgstr[1] "%ld daha çok satır"
+
+#, c-format
+msgid "%ld line less"
+msgid_plural "%ld fewer lines"
+msgstr[0] "%ld daha az satır"
+msgstr[1] "%ld daha az satır"
+
msgid " (Interrupted)"
msgstr " (Yarıda kesildi)"
@@ -3927,6 +4142,12 @@ msgstr ""
"&Tümünü At\n"
"İ&ptal"
+msgid "E664: Changelist is empty"
+msgstr "E664: Değişiklik listesi boş"
+
+msgid "E1292: Command-line window is already open"
+msgstr "E1292: Komut satırı penceresi halihazırda açık"
+
msgid "E349: No identifier under cursor"
msgstr "E349: İmleç altında tanımlayıcı yok"
@@ -3936,9 +4157,6 @@ msgstr "E348: İmleç altında dizi yok"
msgid "E352: Cannot erase folds with current 'foldmethod'"
msgstr "E352: Şu anki 'foldmethod' ile kıvırmalar silinemiyor"
-msgid "E664: changelist is empty"
-msgstr "E664: Değişiklik listesi boş"
-
msgid "E662: At start of changelist"
msgstr "E662: Değişiklik listesinin başında"
@@ -3952,20 +4170,62 @@ msgstr ""
msgid "Type :qa and press <Enter> to exit Nvim"
msgstr "Nvim'den çıkmak için :qa yazıp <Enter>'a basın"
+msgid ""
+"E883: Search pattern and expression register may not contain two or more "
+"lines"
+msgstr ""
+"E883: Arama dizgisi ve ifade yazmacı iki veya daha fazla satır içeremez"
+
+#, c-format
+msgid "%<PRId64> line %sed %d time"
+msgid_plural "%<PRId64> line %sed %d times"
+msgstr[0] "%<PRId64> satır, %s %d kez"
+msgstr[1] "%<PRId64> satır, %s %d kez"
+
+#, c-format
+msgid "%<PRId64> lines %sed %d time"
+msgid_plural "%<PRId64> lines %sed %d times"
+msgstr[0] "%<PRId64> satır, %s %d kez"
+msgstr[1] "%<PRId64> satır, %s %d kez"
+
#, c-format
msgid "%<PRId64> lines to indent... "
msgstr "girintilenecek %<PRId64> satır kaldı... "
+#, c-format
+msgid "%<PRId64> line indented "
+msgid_plural "%<PRId64> lines indented "
+msgstr[0] "%<PRId64> satır girintilendi"
+msgstr[1] "%<PRId64> satır girintilendi"
+
msgid "E748: No previously used register"
msgstr "E748: Daha önce kullanılan bir yazmaç yok"
#, c-format
+msgid "%<PRId64> line changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> satır değiştirildi"
+msgstr[1] "%<PRId64> satır değiştirildi"
+
+#, c-format
msgid " into \"%c"
msgstr " \"%c"
#, c-format
+msgid "block of %<PRId64> line yanked%s"
+msgid_plural "block of %<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> satırlık blok şuraya kopyalandı:%s"
+msgstr[1] "%<PRId64> satırlık blok şuraya kopyalandı:%s"
+
+#, c-format
+msgid "%<PRId64> line yanked%s"
+msgid_plural "%<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> satır şuraya kopyalandı:%s"
+msgstr[1] "%<PRId64> satır şuraya kopyalandı:%s"
+
+#, c-format
msgid "E353: Nothing in register %s"
-msgstr "E353: Yazmaç %s boş"
+msgstr "E353: %s yazmacında bir şey yok"
msgid ""
"\n"
@@ -3974,11 +4234,11 @@ msgstr ""
"\n"
"Tür Ad İçerik"
-msgid ""
-"E883: search pattern and expression register may not contain two or more "
-"lines"
-msgstr ""
-"E883: Arama dizgisi ve ifade yazmacı iki veya daha fazla satır içeremez"
+#, c-format
+msgid "%<PRId64> lines changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> satır değiştirildi"
+msgstr[1] "%<PRId64> satır değiştirildi"
#, c-format
msgid "%<PRId64> Cols; "
@@ -4053,12 +4313,8 @@ msgid "E594: Need at least %d columns"
msgstr "E594: En azından %d sütun gerekli"
#, c-format
-msgid "E355: Unknown option: %s"
-msgstr "E355: Bilinmeyen seçenek: %s"
-
-#, c-format
-msgid "E521: Number required: &%s = '%s'"
-msgstr "E521: Sayı gerekiyor: &%s = '%s'"
+msgid "Invalid value for option '%s': expected %s, got %s %s"
+msgstr "'%s' seçeneği için geçersiz değer: %s bekleniyordu, %s %s alındı"
msgid ""
"\n"
@@ -4087,7 +4343,10 @@ msgstr "E356: get_varp HATASI"
msgid "E540: Unclosed expression sequence"
msgstr "E540: Kapatılmamış ifade sıralaması"
-msgid "E542: unbalanced groups"
+msgid "E536: Comma required"
+msgstr "E536: Virgül gerekiyor"
+
+msgid "E542: Unbalanced groups"
msgstr "E542: Dengelenmemiş gruplar"
msgid "E589: 'backupext' and 'patchmode' are equal"
@@ -4096,6 +4355,9 @@ msgstr "E589: 'backupext' ve 'patchmode' birbirine eşit"
msgid "E595: 'showbreak' contains unprintable or wide character"
msgstr "E595: 'showbreak' yazdırılamaz veya geniş karakter içeriyor"
+msgid "E1336: Internal error: shortmess too long"
+msgstr "E1336: İçsel hata: Kısalık pek uzun"
+
#, c-format
msgid "E539: Illegal character <%s>"
msgstr "E539: İzin verilmeyen karakter <%s>"
@@ -4104,6 +4366,9 @@ msgstr "E539: İzin verilmeyen karakter <%s>"
msgid "For option %s"
msgstr "%s seçeneği için"
+msgid "E5080: Digit expected"
+msgstr "E5080: Basamak bekleniyordu"
+
msgid "E524: Missing colon"
msgstr "E524: İki nokta eksik"
@@ -4124,13 +4389,16 @@ msgstr "E528: Bir ' değeri belirtmeli"
msgid "E535: Illegal character after <%c>"
msgstr "E535: <%c> sonrası izin verilmeyen karakter"
-msgid "E536: comma required"
-msgstr "E536: Virgül gerekiyor"
-
#, c-format
msgid "E537: 'commentstring' must be empty or contain %s"
msgstr "E537: 'commentstring' boş olmalı veya %s içermeli"
+msgid "E834: Conflicts with value of 'listchars'"
+msgstr "E834: 'listchars' değeriyle çakışmalar var"
+
+msgid "E835: Conflicts with value of 'fillchars'"
+msgstr "E835: 'fillchars' değeriyle çakışmalar var"
+
#, c-format
msgid "dlerror = \"%s\""
msgstr "dlerror = \"%s\""
@@ -4142,6 +4410,14 @@ msgstr "E5420: Dosyaya yazılamadı: %s"
msgid "Vim: Error reading input, exiting...\n"
msgstr "Vim: Girdi okunurken hata, çıkılıyor...\n"
+#, c-format
+msgid "Current %slanguage: \"%s\""
+msgstr "Şu anki %sdil: \"%s\""
+
+#, c-format
+msgid "E197: Cannot set language to \"%s\""
+msgstr "E197: \"%s\" diline ayarlanamıyor"
+
msgid ""
"\n"
"shell returned "
@@ -4160,6 +4436,7 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Girdi, kabuk komutuna yazılırken hata: %s"
+#, no-c-format
msgid "%a %b %d %H:%M:%S %Y"
msgstr "%a %b %d %H:%M:%S %Y"
@@ -4252,10 +4529,24 @@ msgid "E927: Invalid action: '%s'"
msgstr "E927: Geçersiz eylem: '%s'"
#, c-format
-msgid "E369: invalid item in %s%%[]"
+msgid "E59: Invalid character after %s@"
+msgstr "E59: %s@ sonrası geçersiz karakter"
+
+msgid "E63: Invalid use of \\_"
+msgstr "E63: Geçersiz \\_ kullanımı"
+
+msgid "E363: Pattern uses more memory than 'maxmempattern'"
+msgstr "E363: Dizgi, 'maxmempattern' ögesinden daha çok bellek kullanıyor"
+
+#, c-format
+msgid "E369: Invalid item in %s%%[]"
msgstr "E369: %s%%[] içinde geçersiz öge"
#, c-format
+msgid "E654: Missing delimiter after search pattern: %s"
+msgstr "E654: Arama dizgisi sonrası sınırlandırıcı eksik: %s"
+
+#, c-format
msgid "E769: Missing ] after %s["
msgstr "E769: %s[ sonrası ] eksik"
@@ -4298,6 +4589,14 @@ msgstr "E956: Dizgi özyineli olarak kullanılamaz"
msgid "E1204: No Number allowed after .: '\\%%%c'"
msgstr "E1204: . sonrası Sayıya izin verilmiyor: '\\%%%c'"
+#, c-format
+msgid "E1273: (NFA regexp) missing value in '\\%%%c'"
+msgstr "E1273: (BSO düzenli ifadesi) '\\%%%c' içinde değer eksik"
+
+#, c-format
+msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern"
+msgstr "E1281: '\\%%#=%c' atomu dizginin başında olmalı"
+
msgid "E1290: substitute nesting too deep"
msgstr "E1290: Değiştirme iç içe geçmesi pek derin"
@@ -4404,81 +4703,18 @@ msgstr "E167: :scriptencoding kaynak alınmış bir dosyanın dışında kullan
msgid "E168: :finish used outside of a sourced file"
msgstr "E168: :finish kaynak alınmış bir dosyanın dışında kullanıldı"
-msgid "E834: Conflicts with value of 'listchars'"
-msgstr "E834: 'listchars' değeriyle çakışmalar var"
-
-msgid "E835: Conflicts with value of 'fillchars'"
-msgstr "E835: 'fillchars' değeriyle çakışmalar var"
-
-msgid " TERMINAL"
-msgstr " UÇBİRİM"
-
-msgid " VREPLACE"
-msgstr " SANAL DEĞİŞTİR"
-
-msgid " REPLACE"
-msgstr " DEĞİŞTİR"
-
-msgid " REVERSE"
-msgstr " GERİ AL"
-
-msgid " INSERT"
-msgstr " EKLE"
-
-msgid " (terminal)"
-msgstr " (uçbirim)"
-
-msgid " (insert)"
-msgstr " (ekle)"
-
-msgid " (replace)"
-msgstr " (değiştir)"
-
-msgid " (vreplace)"
-msgstr " (sanal değiştir)"
-
-msgid " Hebrew"
-msgstr " İbranca"
-
-msgid " Arabic"
-msgstr " Arapça"
-
-msgid " (paste)"
-msgstr " (yapıştır)"
-
-msgid " VISUAL"
-msgstr " GÖRSEL"
-
-msgid " VISUAL LINE"
-msgstr " GÖRSEL SATIR"
-
-msgid " VISUAL BLOCK"
-msgstr " GÖRSEL BLOK"
-
-msgid " SELECT"
-msgstr " SEÇ"
-
-msgid " SELECT LINE"
-msgstr " SATIR SEÇ"
-
-msgid " SELECT BLOCK"
-msgstr " BLOK SEÇ"
-
-msgid "recording"
-msgstr "kaydediliyor"
-
#, c-format
-msgid "E383: Invalid search string: %s"
-msgstr "E383: Geçersiz arama dizisi: %s"
-
-#, c-format
-msgid "E384: search hit TOP without match for: %s"
+msgid "E384: Search hit TOP without match for: %s"
msgstr "E384: Arama dosyanın BAŞINA vardı, %s bulunamadı"
#, c-format
-msgid "E385: search hit BOTTOM without match for: %s"
+msgid "E385: Search hit BOTTOM without match for: %s"
msgstr "E385: Arama dosyanın SONUNA vardı, %s bulunamadı"
+#, c-format
+msgid "E383: Invalid search string: %s"
+msgstr "E383: Geçersiz arama dizisi: %s"
+
msgid "E386: Expected '?' or '/' after ';'"
msgstr "E386: ';' sonrasında '?' veya '/' bekleniyordu"
@@ -4629,14 +4865,14 @@ msgid "Writing ShaDa file \"%s\""
msgstr "Paylaşılan veri dosyası \"%s\" yazılıyor"
#, c-format
-msgid "Failed setting uid and gid for file %s: %s"
-msgstr "%s dosyası için uid ve gid ayarlanamadı: %s"
-
-#, c-format
msgid "E137: ShaDa file is not writable: %s"
msgstr "E137: Paylaşılan veri dosyası yazılabilir değil: %s"
#, c-format
+msgid "Failed setting uid and gid for file %s: %s"
+msgstr "%s dosyası için uid ve gid ayarlanamadı: %s"
+
+#, c-format
msgid "Can't rename ShaDa file from %s to %s!"
msgstr "Paylaşılan veri dosyası %s -> %s olarak yeniden adlandırılamıyor!"
@@ -4809,6 +5045,13 @@ msgstr "E753: Bulunamadı: %s"
msgid "E758: Truncated spell file"
msgstr "E758: Kırpılmış yazım dosyası"
+#, c-format
+msgid "E782: Error while reading .sug file: %s"
+msgstr "E782: .sug dosyasını okurken hata: %s"
+
+msgid "E783: Duplicate char in MAP entry"
+msgstr "E783: MAP girdisinde yinelenen karakter"
+
msgid "E1280: Illegal character in word"
msgstr "E1280: Sözcükte izin verilmeyen karakter"
@@ -4860,10 +5103,6 @@ msgid "E781: .sug file doesn't match .spl file: %s"
msgstr "E781: .sug dosyası .spl dosyasına eşleşmiyor: %s"
#, c-format
-msgid "E782: error while reading .sug file: %s"
-msgstr "E782: .sug dosyasını okurken hata: %s"
-
-#, c-format
msgid "Reading affix file %s..."
msgstr "%s ekler dosyası okunuyor..."
@@ -5126,9 +5365,6 @@ msgstr "'%.*s' sözcüğü %s dosyasına eklendi"
msgid "E763: Word characters differ between spell files"
msgstr "E763: Sözcük karakterleri yazım dosyaları arasında ayrım gösteriyor"
-msgid "E783: duplicate char in MAP entry"
-msgstr "E783: MAP girdisinde yinelenen karakter"
-
msgid "Sorry, no suggestions"
msgstr "Üzgünüm, şu an için bir önerim yok"
@@ -5163,6 +5399,16 @@ msgstr "E767: printf() için pek fazla argüman"
msgid "E390: Illegal argument: %s"
msgstr "E390: İzin verilmeyen argüman: %s"
+msgid "E395: Contains argument not accepted here"
+msgstr "E395: Burada kabul edilmeyen bir argüman içeriyor"
+
+msgid "E844: Invalid cchar value"
+msgstr "E844: Geçersiz cchar değeri"
+
+#, c-format
+msgid "E890: Trailing char after ']': %s]%s"
+msgstr "E890: ']' sonrası fazladan karakter: %s]%s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Bu arabellek için sözdizim ögeleri tanımlanmamış"
@@ -5231,12 +5477,6 @@ msgstr "; eşleşme "
msgid " line breaks"
msgstr " satır sonu"
-msgid "E395: contains argument not accepted here"
-msgstr "E395: Burada kabul edilmeyen bir argüman içeriyor"
-
-msgid "E844: invalid cchar value"
-msgstr "E844: Geçersiz cchar değeri"
-
msgid "E393: group[t]here not accepted here"
msgstr "E393: group[t]here burada kabul edilmez"
@@ -5255,10 +5495,6 @@ msgid "E789: Missing ']': %s"
msgstr "E789: ']' eksik: %s"
#, c-format
-msgid "E890: trailing char after ']': %s]%s"
-msgstr "E890: ']' sonrası fazladan karakter: %s]%s"
-
-#, c-format
msgid "E398: Missing '=': %s"
msgstr "E398: '=' eksik: %s"
@@ -5316,25 +5552,29 @@ msgid ""
msgstr ""
" TOPLAM SAYI EŞ EN YAVAŞ ORTALAMA AD DİZGİ"
-msgid "E555: at bottom of tag stack"
+msgid "E73: Tag stack empty"
+msgstr "E73: Etiket yığını boş"
+
+#, c-format
+msgid "E426: Tag not found: %s"
+msgstr "E426: Etiket bulunamadı: %s"
+
+msgid "E555: At bottom of tag stack"
msgstr "E555: Etiket yığınının en dibinde"
-msgid "E556: at top of tag stack"
+msgid "E556: At top of tag stack"
msgstr "E556: Etiket yığınının en tepesinde"
-msgid "E986: cannot modify the tag stack within tagfunc"
+msgid "E986: Cannot modify the tag stack within tagfunc"
msgstr "E986: Etiket yığını tagfunc dahilinde değiştirilemiyor"
-msgid "E987: invalid return value from tagfunc"
+msgid "E987: Invalid return value from tagfunc"
msgstr "E987: Etiket işlevinden geçersiz dönüş değeri"
+
msgid "E425: Cannot go before first matching tag"
msgstr "E425: İlk eşleşen etiketten önceye gidilemiyor"
-#, c-format
-msgid "E426: tag not found: %s"
-msgstr "E426: Etiket bulunamadı: %s"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Eşleşen yalnızca bir etiket var"
@@ -5373,10 +5613,6 @@ msgstr ""
" # ETİKETE SATIRDAN dosya/metin içinde"
#, c-format
-msgid "Searching tags file %s"
-msgstr "Etiket dosyası %s aranıyor"
-
-#, c-format
msgid "E431: Format error in tags file \"%s\""
msgstr "E431: Etiket dosyası \"%s\" içinde biçim hatası"
@@ -5385,6 +5621,10 @@ msgid "Before byte %<PRId64>"
msgstr "%<PRId64> baytından önce"
#, c-format
+msgid "Searching tags file %s"
+msgstr "Etiket dosyası %s aranıyor"
+
+#, c-format
msgid "E432: Tags file not sorted: %s"
msgstr "E432: Etiket dosyası sıralanmadı: %s"
@@ -5401,9 +5641,29 @@ msgstr "E435: Etiket bulunamadı, tahmin ediliyor!"
msgid "Duplicate field name: %s"
msgstr "Yinelenen alan adı: %s"
+msgid ""
+"E856: \"assert_fails()\" second argument must be a string or a list with one "
+"or two strings"
+msgstr ""
+"E856: \"assert_fails()\" ikinci argümanı bir dizi veya bir veya iki dizili "
+"bir liste olmalı"
+
+msgid "E1116: \"assert_fails()\" fifth argument must be a string"
+msgstr "E1116: \"assert_fails()\" beşinci argümanı bir dizi olmalı"
+
msgid "Beep!"
msgstr "Bip!"
+msgid "E439: Undo list corrupt"
+msgstr "E439: Geri al listesi hasarlı"
+
+msgid "E440: Undo line missing"
+msgstr "E440: Geri al satırı eksik"
+
+#, c-format
+msgid "E829: Write error in undo file: %s"
+msgstr "E829: Geri al dosyasında yazma hatası: %s"
+
msgid "E881: Line count changed unexpectedly"
msgstr "E881: Satır sayısı beklenmedik bir biçimde değişti"
@@ -5438,10 +5698,6 @@ msgid "Writing undo file: %s"
msgstr "Geri al dosyası yazılıyor: %s"
#, c-format
-msgid "E829: write error in undo file: %s"
-msgstr "E829: Geri al dosyasında yazma hatası: %s"
-
-#, c-format
msgid "Not reading undo file, owner differs: %s"
msgstr "Geri al dosyası okunmayacak, sahibi farklı: %s"
@@ -5509,6 +5765,12 @@ msgstr "şundan sonra:"
msgid "before"
msgstr "şundan önce:"
+#, c-format
+msgid "%<PRId64> second ago"
+msgid_plural "%<PRId64> seconds ago"
+msgstr[0] "%<PRId64> saniye önce"
+msgstr[1] "%<PRId64> saniye önce"
+
msgid "Nothing to undo"
msgstr "Geri alınacak bir şey yok"
@@ -5518,19 +5780,17 @@ msgstr "kaydedildiğinde numara değişir"
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Geri al sonrasında geri almalar birleştirilemez"
-msgid "E439: undo list corrupt"
-msgstr "E439: Geri al listesi hasarlı"
-
-msgid "E440: undo line missing"
-msgstr "E440: Geri al satırı eksik"
-
-msgid "E1208: -complete used without allowing arguments"
-msgstr "E1208: -complete, argümanlara izin vermeden kullanıldı"
+#, c-format
+msgid "E179: Argument required for %s"
+msgstr "E179: %s için argüman gerekiyor"
#, c-format
msgid "E184: No such user-defined command: %s"
msgstr "E184: Böyle bir kullanıcı tanımlı komut yok: %s"
+msgid "E1208: -complete used without allowing arguments"
+msgstr "E1208: -complete, argümanlara izin vermeden kullanıldı"
+
#, c-format
msgid "E1237: No such user-defined command in current buffer: %s"
msgstr "E1237: Geçerli arabellekte böyle bir kullanıcı tanımlı komut yok: %s"
@@ -5571,12 +5831,6 @@ msgstr "E177: Sayım iki defa belirtilemez"
msgid "E178: Invalid default value for count"
msgstr "E178: Sayım için geçersiz öntanımlı değer"
-msgid "E179: argument required for -complete"
-msgstr "E179: -complete için argüman gerekiyor"
-
-msgid "E179: argument required for -addr"
-msgstr "E179: -addr için argüman gerekiyor"
-
#, c-format
msgid "E181: Invalid attribute: %s"
msgstr "E181: Geçersiz öznitelik: %s"
@@ -5594,25 +5848,6 @@ msgstr "E183: Kullanıcı tanımlı komutlar BÜYÜK harfle başlamalıdır"
msgid "E841: Reserved name, cannot be used for user defined command"
msgstr "E841: Ayrılmış ad, kullanıcı tanımlı komut için kullanılamaz"
-msgid ""
-"\n"
-"\n"
-"Features: "
-msgstr ""
-"\n"
-"\n"
-"Özellikler: "
-
-msgid ""
-"\n"
-"Compiled "
-msgstr ""
-"\n"
-"Derleyen:"
-
-msgid "by "
-msgstr " "
-
msgid " system vimrc file: \""
msgstr " sistem vimrc dosyası: \""
@@ -5637,6 +5872,10 @@ msgstr "çıkmak için :q<Enter> "
msgid "type :help<Enter> for help "
msgstr "yardım için :help<Enter> "
+#, c-format
+msgid "type :help news<Enter> to see changes in v%s.%s"
+msgstr "v%s.%s değişiklikleri :help news<Enter>"
+
msgid "Help poor children in Uganda!"
msgstr "Uganda'daki yoksul çocuklara yardım edin!"
@@ -5655,9 +5894,6 @@ msgstr "bilgi için :help sponsor<Enter> "
msgid "type :help register<Enter> for information "
msgstr "bilgi için :help register<Enter> "
-msgid "menu Help->Sponsor/Register for information "
-msgstr "bilgi için Yardım -> Sponsorluk/Kayıt"
-
#, c-format
msgid "E15: Invalid control character present in input: %.*s"
msgstr "E15: Girdide geçersiz denetim karakteri var: %.*s"
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 06f845f113..83898cda12 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -12,18 +12,1261 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: vim 7.4\n"
+"Project-Id-Version: Neovim Ukrainian\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-04-13 10:28+0300\n"
-"PO-Revision-Date: 2020-08-23 20:19+0300\n"
+"POT-Creation-Date: 2023-08-26 20:36+0300\n"
+"PO-Revision-Date: 2023-08-26 21:00+0300\n"
"Last-Translator: Анатолій Сахнік <sakhnik@gmail.com>\n"
"Language-Team: Ukrainian\n"
"Language: uk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n"
-"%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+msgid "(local to window)"
+msgstr "(тільки у вікні)"
+
+msgid "(local to buffer)"
+msgstr "(тільки в буфері)"
+
+msgid "(global or local to buffer)"
+msgstr "(всюди або тільки в буфері)"
+
+msgid ""
+"\" Each \"set\" line shows the current value of an option (on the left)."
+msgstr "\" Кожен рядок «set» показує теперішнє значення опції (ліворуч)."
+
+msgid "\" Hit <Enter> on a \"set\" line to execute it."
+msgstr "\" Натисніть <Enter> у рядку «set» щоб виконати."
+
+msgid "\" A boolean option will be toggled."
+msgstr "\" Опцію-перемикач буде перемкнено."
+
+msgid ""
+"\" For other options you can edit the value before hitting "
+"<Enter>."
+msgstr ""
+"\" Для інших опцій можна відредагувати значення перед натисненням "
+"на <Enter>."
+
+msgid "\" Hit <Enter> on a help line to open a help window on this option."
+msgstr ""
+"\" Натисніть <Enter> на рядку 'допомога' щоб відкрити вікно з допомогою про "
+"цю опцію."
+
+msgid "\" Hit <Enter> on an index line to jump there."
+msgstr "\" Натисність <Enter> на рядку вказівника щоб туди перестрибнути."
+
+msgid "\" Hit <Space> on a \"set\" line to refresh it."
+msgstr "\" Натисніть <Пробіл> на рядку «set» для оновлення."
+
+msgid "important"
+msgstr "важливо"
+
+msgid "behave very Vi compatible (not advisable)"
+msgstr "поводитися дуже сумісно з Vi (не рекомендовано)"
+
+msgid "list of flags to specify Vi compatibility"
+msgstr "список прапорців щоб задати сумісність із Vi"
+
+msgid "paste mode, insert typed text literally"
+msgstr "режим вклеювання, вставити набраний текст буквально"
+
+msgid "list of directories used for runtime files and plugins"
+msgstr "список директорій з файлами часу виконання і плагінами"
+
+msgid "list of directories used for plugin packages"
+msgstr "список директорій з пакунками плагінів"
+
+msgid "name of the main help file"
+msgstr "назва головного файлу допомоги"
+
+msgid "moving around, searching and patterns"
+msgstr "переміщення, пошук і шаблони"
+
+msgid "list of flags specifying which commands wrap to another line"
+msgstr "список прапорців, які задають, котрі команди переносять на новий рядок"
+
+msgid ""
+"many jump commands move the cursor to the first non-blank\n"
+"character of a line"
+msgstr ""
+"багато команд стрибків переміщують курсор до першого непорожнього\n"
+"символу рядка"
+
+msgid "nroff macro names that separate paragraphs"
+msgstr "назви макросів nroff, які розділяють параграфи"
+
+msgid "nroff macro names that separate sections"
+msgstr "назви макросів nroff, що розділяють розділи"
+
+msgid "list of directory names used for file searching"
+msgstr "список назв директорій для пошуку файлів"
+
+msgid ":cd without argument goes to the home directory"
+msgstr ":cd без аргументу переходить до домашньої директорії"
+
+msgid "list of directory names used for :cd"
+msgstr "список назв директорій для :cd"
+
+msgid "change to directory of file in buffer"
+msgstr "перейти до директорії файлу в буфері"
+
+msgid "search commands wrap around the end of the buffer"
+msgstr "команди пошуку починають спочатку після кінця буфера"
+
+msgid "show match for partly typed search command"
+msgstr "показувати збіг частково набраної команди пошуку"
+
+msgid "change the way backslashes are used in search patterns"
+msgstr "змінити спосіб вжитку \\ у шаблонах пошуку"
+
+msgid "select the default regexp engine used"
+msgstr "вибрати основний рушій регулярних виразів"
+
+msgid "ignore case when using a search pattern"
+msgstr "нехтувати регістром у шаблоні пошуку"
+
+msgid "override 'ignorecase' when pattern has upper case characters"
+msgstr "відкинути 'ignorecase', коли шаблон має символи верхнього регістру"
+
+msgid "what method to use for changing case of letters"
+msgstr "який метод використати для зміни регістру букв"
+
+msgid "maximum amount of memory in Kbyte used for pattern matching"
+msgstr ""
+"максимальний обсяг пам’яті в Кб для співставлення з регулярними вирахами"
+
+msgid "pattern for a macro definition line"
+msgstr "шаблон рядка визначення макросу"
+
+msgid "pattern for an include-file line"
+msgstr "шаблон рядка включення файлу"
+
+msgid "expression used to transform an include line to a file name"
+msgstr "вираз для перетворення рядка включення у назву файлу"
+
+msgid "tags"
+msgstr "мітки"
+
+msgid "use binary searching in tags files"
+msgstr "застосовувати двійковий пошук у файлі міток"
+
+msgid "number of significant characters in a tag name or zero"
+msgstr "кількість значущих символів у назві мітки або нуль"
+
+msgid "list of file names to search for tags"
+msgstr "список назв файлів для пошуку міток"
+
+msgid ""
+"how to handle case when searching in tags files:\n"
+"\"followic\" to follow 'ignorecase', \"ignore\" or \"match\""
+msgstr ""
+"як обходитись із регістром при пошуку в файлах міток:\n"
+"«followic» так само, як у 'ignorecase', «ignore» або «match»"
+
+msgid "file names in a tags file are relative to the tags file"
+msgstr "назви файлів у файлі міток задані відносно файлу міток"
+
+msgid "a :tag command will use the tagstack"
+msgstr "команда :tag використовуватиме стек міток"
+
+msgid "when completing tags in Insert mode show more info"
+msgstr "при доповненні міток в режимі Insert показувати більше інформації"
+
+msgid "a function to be used to perform tag searches"
+msgstr "функція, яку застосовувати при пошуку міток"
+
+msgid "displaying text"
+msgstr "показування тексту"
+
+msgid "number of lines to scroll for CTRL-U and CTRL-D"
+msgstr "кількість рядків прокручування при CTRL-U і CTRL-D"
+
+msgid "scroll by screen line"
+msgstr "прокручувати по рядках на екрані"
+
+msgid "number of screen lines to show around the cursor"
+msgstr "кількість екранних рядків, які показувати навколо курсору"
+
+msgid "long lines wrap"
+msgstr "перенесення довгих рядків"
+
+msgid "wrap long lines at a character in 'breakat'"
+msgstr "переносити довгі рядки по символах у 'breakat'"
+
+msgid "preserve indentation in wrapped text"
+msgstr "зберегти відступи у перенесеному тексті"
+
+msgid "adjust breakindent behaviour"
+msgstr "підлаштувати поведінку breakindent"
+
+msgid "which characters might cause a line break"
+msgstr "які символи можуть спричинити розбиття рядка"
+
+msgid "string to put before wrapped screen lines"
+msgstr "символи, які розмістити перед перенесеними екранними рядками"
+
+msgid "minimal number of columns to scroll horizontally"
+msgstr "найменша кількість стовпців горизонтального прокручування"
+
+msgid "minimal number of columns to keep left and right of the cursor"
+msgstr ""
+"найменша кількість стовпців, які показувати ліворуч і праворуч від курсору"
+
+msgid ""
+"include \"lastline\" to show the last line even if it doesn't fit\n"
+"include \"uhex\" to show unprintable characters as a hex number"
+msgstr ""
+"додайте «lastline» щоб показати останній рядок навіть якщо він не влазить\n"
+"додайте «uhex» щоб показати недруковні символи як шістнадцяткові числа"
+
+msgid "characters to use for the status line, folds and filler lines"
+msgstr "символи для рядка стану, згорток і заповнювачів"
+
+msgid "number of lines used for the command-line"
+msgstr "кількість рядків для командного рядка"
+
+msgid "width of the display"
+msgstr "ширина дисплею"
+
+msgid "number of lines in the display"
+msgstr "кількість рядків у дисплеї"
+
+msgid "number of lines to scroll for CTRL-F and CTRL-B"
+msgstr "кількість рядків для прокручування з допомогою CTRL-F і CTRL-B"
+
+msgid "don't redraw while executing macros"
+msgstr "не перемальовувати при виконанні макросів"
+
+msgid "timeout for 'hlsearch' and :match highlighting in msec"
+msgstr "тайм-аут (мс) для підсвічування 'hlsearch' і :match"
+
+msgid ""
+"delay in msec for each char written to the display\n"
+"(for debugging)"
+msgstr ""
+"затримка (мс) для кожного символу, що записується у дисплей\n"
+"(для налагодження)"
+
+msgid "show <Tab> as ^I and end-of-line as $"
+msgstr "показувати <Tab> як ^I і кінець рядка як $"
+
+msgid "list of strings used for list mode"
+msgstr "список слів, які вживаються в режимі list"
+
+msgid "show the line number for each line"
+msgstr "показувати номери рядків"
+
+msgid "show the relative line number for each line"
+msgstr "показувати відносні номери рядків"
+
+msgid "number of columns to use for the line number"
+msgstr "кількість стовпців для показу номерів рядків"
+
+msgid "controls whether concealable text is hidden"
+msgstr "контролює приховування замаскованого тексту"
+
+msgid "modes in which text in the cursor line can be concealed"
+msgstr "режими, в яких текст в рядку з курсором маскується"
+
+msgid "syntax, highlighting and spelling"
+msgstr "синтаксис, підсвічування і орфографія"
+
+msgid "\"dark\" or \"light\"; the background color brightness"
+msgstr "«dark» або «light»; яскравість кольору фону"
+
+msgid "type of file; triggers the FileType event when set"
+msgstr "тип файлу; запускає подію FileType при встановленні"
+
+msgid "name of syntax highlighting used"
+msgstr "назва схеми підсвічування синтаксису"
+
+msgid "maximum column to look for syntax items"
+msgstr "крайній стовпець для пошуку елементів синтаксису"
+
+msgid "which highlighting to use for various occasions"
+msgstr "яке підсвічування застосовувати у різних випадках"
+
+msgid "highlight all matches for the last used search pattern"
+msgstr "підсвічування усіх збігів попереднього шаблону пошуку"
+
+msgid "use GUI colors for the terminal"
+msgstr "використовувати кольори GUI у терміналі"
+
+msgid "highlight the screen column of the cursor"
+msgstr "підсвічувати стовпець з курсором"
+
+msgid "highlight the screen line of the cursor"
+msgstr "підсвічувати екранний рядок із курсором"
+
+msgid "specifies which area 'cursorline' highlights"
+msgstr "визначає, яку ділянку підсвічує 'cursorline'"
+
+msgid "columns to highlight"
+msgstr "підсвічувані стовпці"
+
+msgid "highlight spelling mistakes"
+msgstr "підсвічувати помилки орфографії"
+
+msgid "list of accepted languages"
+msgstr "список мов перевірки орфографії"
+
+msgid "file that \"zg\" adds good words to"
+msgstr "файл, у який команда «zg» додає хороші слова"
+
+msgid "pattern to locate the end of a sentence"
+msgstr "шаблон для пошуку кінця речення"
+
+msgid "flags to change how spell checking works"
+msgstr "прапорці для налаштування перевірки орфографії"
+
+msgid "methods used to suggest corrections"
+msgstr "метод підказки виправлень"
+
+msgid "amount of memory used by :mkspell before compressing"
+msgstr "обсяг пам’яті для :mkspell перед стисненням"
+
+msgid "multiple windows"
+msgstr "багато вікон"
+
+msgid "0, 1, 2 or 3; when to use a status line for the last window"
+msgstr "0, 1, 2 або 2; коли показувати рядок стану в останньому вікні"
+
+msgid "custom format for the status column"
+msgstr "власний формат стовпця стану"
+
+msgid "alternate format to be used for a status line"
+msgstr "змінний формат рядка стану"
+
+msgid "make all windows the same size when adding/removing windows"
+msgstr "зробити всі вікна однакового розміру при додаванні/закритті вікон"
+
+msgid "in which direction 'equalalways' works: \"ver\", \"hor\" or \"both\""
+msgstr "в якому напрямку працює 'equalalways': «ver», «hor» або «both»"
+
+msgid "minimal number of lines used for the current window"
+msgstr "найменша кількість рядків у активному вікні"
+
+msgid "minimal number of lines used for any window"
+msgstr "найменша кількість рядків у будь-якому вікні"
+
+msgid "keep the height of the window"
+msgstr "витримувати висоту вікна"
+
+msgid "keep the width of the window"
+msgstr "витримувати ширину вікна"
+
+msgid "minimal number of columns used for the current window"
+msgstr "найменша кількість стовпців активного вікна"
+
+msgid "minimal number of columns used for any window"
+msgstr "найменша кількість стовпців будь-якого вікна"
+
+msgid "initial height of the help window"
+msgstr "початкова висота вікна допомоги"
+
+msgid "default height for the preview window"
+msgstr "початкова висота вікна попереднього перегляду"
+
+msgid "identifies the preview window"
+msgstr "ідентифікує вікно попереднього перегляду"
+
+msgid "don't unload a buffer when no longer shown in a window"
+msgstr "не вивантажувати буфер, який більше не показується у вікні"
+
+msgid ""
+"\"useopen\" and/or \"split\"; which window to use when jumping\n"
+"to a buffer"
+msgstr ""
+"«useopen» і/або «split»; яке вікно використовувати при стрибках\n"
+"до буферу"
+
+msgid "a new window is put below the current one"
+msgstr "нове вікно розміщується під активним"
+
+msgid "determines scroll behavior for split windows"
+msgstr "визначає поведінку прокручування у розщеплених вікнах"
+
+msgid "a new window is put right of the current one"
+msgstr "нове вікно розміщується праворуч від активного"
+
+msgid "this window scrolls together with other bound windows"
+msgstr "це вікно прокручується разом із іншими пов’язаними вікнами"
+
+msgid "\"ver\", \"hor\" and/or \"jump\"; list of options for 'scrollbind'"
+msgstr "«ver», «hor» і/або «jump»; список опцій для 'scrollbind'"
+
+msgid "this window's cursor moves together with other bound windows"
+msgstr "курсор цього вікна рухається разом із іншими пов’язаними вікнами"
+
+msgid "size of a terminal window"
+msgstr "розмір термінального вікна"
+
+msgid "key that precedes Vim commands in a terminal window"
+msgstr "клавіша перед командами Vim у термінальному вікні"
+
+msgid "multiple tab pages"
+msgstr "сторінки вкладок"
+
+msgid "0, 1 or 2; when to use a tab pages line"
+msgstr "0, 1 або 2; коли використовувати рядок сторінок вкладок"
+
+msgid "maximum number of tab pages to open for -p and \"tab all\""
+msgstr "найбільша кількість сторінок вкладок при -p і «tab all»"
+
+msgid "custom tab pages line"
+msgstr "власний рядок сторінок вкладок"
+
+msgid "custom tab page label for the GUI"
+msgstr "власний ярлик сторінок вкладок в GUI"
+
+msgid "custom tab page tooltip for the GUI"
+msgstr "власна спливаюча підказка сторінок вкладок в GUI"
+
+msgid "terminal"
+msgstr "термінал"
+
+msgid "minimal number of lines to scroll at a time"
+msgstr "найменша кількість рядків прогортання за раз"
+
+msgid "specifies what the cursor looks like in different modes"
+msgstr "визначає, який має вигляд курсор у різних режимах"
+
+msgid "show info in the window title"
+msgstr "показувати інформацію у заголовку вікна"
+
+msgid "percentage of 'columns' used for the window title"
+msgstr "відсоток 'columns' для заголовку вікна"
+
+msgid "when not empty, string to be used for the window title"
+msgstr "коли не порожня, текстовий рядок для заголовку вікна"
+
+msgid "string to restore the title to when exiting Vim"
+msgstr "текстовий рядок щоб відновити заголовок при виході з Vim"
+
+msgid "set the text of the icon for this window"
+msgstr "встановити текст образка для цього вікна"
+
+msgid "when not empty, text for the icon of this window"
+msgstr "коли не порожня, текст для образка цього вікна"
+
+msgid "using the mouse"
+msgstr "використання миші"
+
+msgid "list of flags for using the mouse"
+msgstr "список прапорців для використання миші"
+
+msgid "the window with the mouse pointer becomes the current one"
+msgstr "вікно із вказівником миші активується"
+
+msgid "hide the mouse pointer while typing"
+msgstr "ховати вказівник миші при набиранні тексту"
+
+msgid ""
+"\"extend\", \"popup\" or \"popup_setpos\"; what the right\n"
+"mouse button is used for"
+msgstr ""
+"«extend», «popup» або «popup_setpos»; для чого використовується\n"
+"права клавіша миші"
+
+msgid "maximum time in msec to recognize a double-click"
+msgstr "найбільший час (мс) щоб розпізнати подвійне натискання"
+
+msgid "what the mouse pointer looks like in different modes"
+msgstr "вигляд вказівника миші в різних режимах"
+
+msgid "GUI"
+msgstr "GUI"
+
+msgid "list of font names to be used in the GUI"
+msgstr "список назв шрифтів у GUI"
+
+msgid "pair of fonts to be used, for multibyte editing"
+msgstr "пари шрифтів для багатобайтного редагування"
+
+msgid "list of font names to be used for double-wide characters"
+msgstr "список назв шрифтів для символів подвійної ширини"
+
+msgid "list of flags that specify how the GUI works"
+msgstr "список прапорців, що визначають поведінку GUI"
+
+msgid "\"icons\", \"text\" and/or \"tooltips\"; how to show the toolbar"
+msgstr "«icons», «text» і/або «tooltips»; як показувати панель інструментів"
+
+msgid "size of toolbar icons"
+msgstr "розмір образків панелі інструментів"
+
+msgid ""
+"\"last\", \"buffer\" or \"current\": which directory used for the file "
+"browser"
+msgstr ""
+"«last», «buffer» або «current»: яку директорію використовувати для оглядача "
+"файлів"
+
+msgid "language to be used for the menus"
+msgstr "мова меню"
+
+msgid "maximum number of items in one menu"
+msgstr "найбільша кількість елементів у одному меню"
+
+msgid "\"no\", \"yes\" or \"menu\"; how to use the ALT key"
+msgstr "«no», «yes» або «menu»; як використовувати клавішу ALT"
+
+msgid "number of pixel lines to use between characters"
+msgstr "кількість рядків пікселів між символами"
+
+msgid "delay in milliseconds before a balloon may pop up"
+msgstr "затримка (мс) перед показом спливаючої підказки"
+
+msgid "use balloon evaluation in the GUI"
+msgstr "активувати balloon evaluation в GUI"
+
+msgid "use balloon evaluation in the terminal"
+msgstr "активувати balloon evaluation в терміналі"
+
+msgid "expression to show in balloon eval"
+msgstr "показуваний вираз у balloon eval"
+
+msgid "messages and info"
+msgstr "повідомлення й інформація"
+
+msgid "add 's' flag in 'shortmess' (don't show search message)"
+msgstr "додати прапорець 's' у 'shortmess' (не показувати повідомлення пошуку)"
+
+msgid "list of flags to make messages shorter"
+msgstr "список прапорців щоб зробити повідомлення коротшими"
+
+msgid "show (partial) command keys in location given by 'showcmdloc'"
+msgstr "показувати клавіші (частини) команди у місці, заданому 'showcmdloc'"
+
+msgid "location where to show the (partial) command keys for 'showcmd'"
+msgstr "місце, де показувати клавіші (частини) команди для 'showcmd'"
+
+msgid "display the current mode in the status line"
+msgstr "показувати актуальний режим у рядку стану"
+
+msgid "show cursor position below each window"
+msgstr "показувати положення курсору під кожним вікном"
+
+msgid "alternate format to be used for the ruler"
+msgstr "змінний формат лінійки"
+
+msgid "threshold for reporting number of changed lines"
+msgstr "поріг доповіді кількості змінених рядків"
+
+msgid "the higher the more messages are given"
+msgstr "чим більше, тим більше буде видано повідомлень"
+
+msgid "file to write messages in"
+msgstr "файл, у який записувати повідомлення"
+
+msgid "pause listings when the screen is full"
+msgstr "спинити виведення, коли екран заповнено"
+
+msgid "start a dialog when a command fails"
+msgstr "почати діалог, коли команда зазнає невдачі"
+
+msgid "ring the bell for error messages"
+msgstr "подати звук при повідомленнях про помилки"
+
+msgid "use a visual bell instead of beeping"
+msgstr "блимання замість звукового сигналу"
+
+msgid "do not ring the bell for these reasons"
+msgstr "не подавати сигнал у таких випадках"
+
+msgid "list of preferred languages for finding help"
+msgstr "список бажаних мов при пошуку допомоги"
+
+msgid "selecting text"
+msgstr "виділення тексту"
+
+msgid "\"old\", \"inclusive\" or \"exclusive\"; how selecting text behaves"
+msgstr "«old», «inclusive» або «exclusive»; як поводиться вибір тексту"
+
+msgid ""
+"\"mouse\", \"key\" and/or \"cmd\"; when to start Select mode\n"
+"instead of Visual mode"
+msgstr ""
+"«mouse», «key» і/або «cmd»; коли починати режим Select\n"
+"замість Visual"
+
+msgid ""
+"\"unnamed\" to use the * register like unnamed register\n"
+"\"autoselect\" to always put selected text on the clipboard"
+msgstr ""
+"«unnamed» щоб використовувати регістр * як безіменний регістр\n"
+"«autoselect» щоб завжди поміщати вибраний текст у буфер обміну"
+
+msgid "\"startsel\" and/or \"stopsel\"; what special keys can do"
+msgstr "«startsel» і/або «stopsel»; що можуть робити спеціальні клавіші"
+
+msgid "editing text"
+msgstr "редагування тексту"
+
+msgid "maximum number of changes that can be undone"
+msgstr "найбільша кількість змін, які можна повернути"
+
+msgid "automatically save and restore undo history"
+msgstr "автоматично зберігати і відновлювати історію змін"
+
+msgid "list of directories for undo files"
+msgstr "список директорій для файлів історії"
+
+msgid "maximum number lines to save for undo on a buffer reload"
+msgstr ""
+"найбільша кількість рядків, які зберігати для історії змін при перечитуванні "
+"буферу"
+
+msgid "changes have been made and not written to a file"
+msgstr "виконано зміни і не записано у файл"
+
+msgid "buffer is not to be written"
+msgstr "буфер не для запису"
+
+msgid "changes to the text are possible"
+msgstr "зміни у тексті можливі"
+
+msgid "line length above which to break a line"
+msgstr "довжина рядка, понад якою рядок розбивати"
+
+msgid "margin from the right in which to break a line"
+msgstr "відступ з правого краю, по якому розбивати рядок"
+
+msgid "specifies what <BS>, CTRL-W, etc. can do in Insert mode"
+msgstr "визначає, що <BS>, CTRL-W тощо можуть робити у режимі Insert"
+
+msgid "definition of what comment lines look like"
+msgstr "визначення того, який вигляд мають рядки коментарів"
+
+msgid "list of flags that tell how automatic formatting works"
+msgstr "список прапорців, які визначають, як працює автоматичне форматування"
+
+msgid "pattern to recognize a numbered list"
+msgstr "шаблон розпізнавання нумерованого списку"
+
+msgid "expression used for \"gq\" to format lines"
+msgstr "вираз, який використати для форматування рядків при «gq»"
+
+msgid "specifies how Insert mode completion works for CTRL-N and CTRL-P"
+msgstr ""
+"визначає, як працює доповнення тексту в режимі Insert при CTRL-N і CTRL-P"
+
+msgid "whether to use a popup menu for Insert mode completion"
+msgstr "чи використовувати спливаюче меню при доповненні в режимі Insert"
+
+msgid "maximum height of the popup menu"
+msgstr "найбільша висота спливаючого меню"
+
+msgid "minimum width of the popup menu"
+msgstr "найменша ширина спливаючого меню"
+
+msgid "user defined function for Insert mode completion"
+msgstr "користувацька функція для доповнення в режимі Insert"
+
+msgid "function for filetype-specific Insert mode completion"
+msgstr "специфічна до типу файлу функція для доповнення в режимі Insert"
+
+msgid "list of dictionary files for keyword completion"
+msgstr "список словникових файлів для доповнення ключових слів"
+
+msgid "list of thesaurus files for keyword completion"
+msgstr "список файлів тезаурусу для доповнення ключових слів"
+
+msgid "function used for thesaurus completion"
+msgstr "функція для доповнення по тезаурусу"
+
+msgid "adjust case of a keyword completion match"
+msgstr "поправляти регістр букв при доповненні ключових слів"
+
+msgid "enable entering digraphs with c1 <BS> c2"
+msgstr "увімкнути введення диграфів з допомогою c1 <BS> c2"
+
+msgid "the \"~\" command behaves like an operator"
+msgstr "команда «~» поводиться як оператор"
+
+msgid "function called for the \"g@\" operator"
+msgstr "функція, що викликається при операторові «g@»"
+
+msgid "when inserting a bracket, briefly jump to its match"
+msgstr ""
+"коли вставляється дужка, на короткий час перестрибнути до її відповідника"
+
+msgid "tenth of a second to show a match for 'showmatch'"
+msgstr "десяті частини секунди щоб показати збіг у 'showmatch'"
+
+msgid "list of pairs that match for the \"%\" command"
+msgstr "список пар, що розпізнаються командою «%»"
+
+msgid "use two spaces after '.' when joining a line"
+msgstr "використовувати два пропуски після '.' при сполученні рядків"
+
+msgid ""
+"\"alpha\", \"octal\", \"hex\", \"bin\" and/or \"unsigned\"; number formats\n"
+"recognized for CTRL-A and CTRL-X commands"
+msgstr ""
+"«alpha», «octal», «hex», «bin» і/чи «unsigned»; формати чисел,\n"
+"які розпізнаються командами CTRL-A і CTRL-X"
+
+msgid "tabs and indenting"
+msgstr "табуляція і відступи"
+
+msgid "number of spaces a <Tab> in the text stands for"
+msgstr "кількість пробілів, якій дорівнює в тексті один <Tab>"
+
+msgid "number of spaces used for each step of (auto)indent"
+msgstr "кількість пробілів у кожному кроці (авто)відступу"
+
+msgid "list of number of spaces a tab counts for"
+msgstr "список кількості пробілів на кожному рівні табуляції"
+
+msgid "list of number of spaces a soft tabsstop counts for"
+msgstr "список кількості пробілів на кожному рівні гнучкої табуляції"
+
+msgid "a <Tab> in an indent inserts 'shiftwidth' spaces"
+msgstr "<Tab> у відступі вставляє 'shiftwidth' пробілів"
+
+msgid "if non-zero, number of spaces to insert for a <Tab>"
+msgstr "якщо не нуль, кількість пробілів, які потрібно вставити при <Tab>"
+
+msgid "round to 'shiftwidth' for \"<<\" and \">>\""
+msgstr "округлити до 'shiftwidth' при «<<» і «>>»"
+
+msgid "expand <Tab> to spaces in Insert mode"
+msgstr "замінити <Tab> пробілами в режимі Insert"
+
+msgid "automatically set the indent of a new line"
+msgstr "автоматично встановити відступ нового рядка"
+
+msgid "do clever autoindenting"
+msgstr "інтелектуальний автоматичний відступ"
+
+msgid "enable specific indenting for C code"
+msgstr "увімкнути специфічні відступи в коді С"
+
+msgid "options for C-indenting"
+msgstr "опції відступу С"
+
+msgid "keys that trigger C-indenting in Insert mode"
+msgstr "клавіші, що спричиняють відступи С в режимі Insert"
+
+msgid "list of words that cause more C-indent"
+msgstr "список слів, які спричиняють більше відступу С"
+
+msgid "list of scope declaration names used by cino-g"
+msgstr "список назв оголошень області видимості, що вживаються в cino-g"
+
+msgid "expression used to obtain the indent of a line"
+msgstr "вираз для обчислення відступу рядка"
+
+msgid "keys that trigger indenting with 'indentexpr' in Insert mode"
+msgstr ""
+"клавіші, що запускають підбивання відступів з допомогою 'indentexpr' у "
+"режимі Insert"
+
+msgid "copy whitespace for indenting from previous line"
+msgstr "скопіювати пропуски з попереднього рядка при відступах"
+
+msgid "preserve kind of whitespace when changing indent"
+msgstr "зберегти вид пропуску при зміні відступу"
+
+msgid "enable lisp mode"
+msgstr "увімкнути режим lisp"
+
+msgid "words that change how lisp indenting works"
+msgstr "слова, які змінюють, як працює підбивання відступів у lisp"
+
+msgid "options for Lisp indenting"
+msgstr "опції відступу Lisp"
+
+msgid "folding"
+msgstr "згортання"
+
+msgid "unset to display all folds open"
+msgstr "скиньте щоб відкрити всі згортки"
+
+msgid "folds with a level higher than this number will be closed"
+msgstr "згортки вищого рівня, ніж це число, будуть закриті"
+
+msgid "value for 'foldlevel' when starting to edit a file"
+msgstr "значення 'foldlevel' на початку редагування файлу"
+
+msgid "width of the column used to indicate folds"
+msgstr "ширина колонки, що позначає згортки"
+
+msgid "expression used to display the text of a closed fold"
+msgstr "вираз, що використовується для зображення тексту закритої згортки"
+
+msgid "set to \"all\" to close a fold when the cursor leaves it"
+msgstr "встановити у «all» щоб закривати згортку, коли курсор з неї виходить"
+
+msgid "specifies for which commands a fold will be opened"
+msgstr "зазначає, при якій команді згортка відкриється"
+
+msgid "minimum number of screen lines for a fold to be closed"
+msgstr "найменша кількість екранних рядків щоб закрити згортку"
+
+msgid "template for comments; used to put the marker in"
+msgstr "шаблон коментарів; вживається щоб вставити маркер згорток"
+
+msgid ""
+"folding type: \"manual\", \"indent\", \"expr\", \"marker\",\n"
+"\"syntax\" or \"diff\""
+msgstr ""
+"тип згортання: «manual», «indent», «expr», «marker»,\n"
+"«syntax» або «diff»"
+
+msgid "expression used when 'foldmethod' is \"expr\""
+msgstr "вираз для 'foldmethod' «expr»"
+
+msgid "used to ignore lines when 'foldmethod' is \"indent\""
+msgstr "використовується щоб ігнорувати рядки, коли 'foldmethod' «indent»"
+
+msgid "markers used when 'foldmethod' is \"marker\""
+msgstr "позначки при 'foldmethod' «marker»"
+
+msgid "maximum fold depth for when 'foldmethod' is \"indent\" or \"syntax\""
+msgstr "найбільша глибина згорток при 'foldmethod' «indent» або «syntax»"
+
+msgid "diff mode"
+msgstr "режим порівняння"
+
+msgid "use diff mode for the current window"
+msgstr "увімкнути режим порівняння у цьому вікні"
+
+msgid "options for using diff mode"
+msgstr "опції у режимі порівняння"
+
+msgid "expression used to obtain a diff file"
+msgstr "вираз для отримання файлу порівняння"
+
+msgid "expression used to patch a file"
+msgstr "вираз для латання файлу"
+
+msgid "mapping"
+msgstr "заміни"
+
+msgid "maximum depth of mapping"
+msgstr "найбільша глибина заміни клавіш"
+
+msgid "allow timing out halfway into a mapping"
+msgstr "дозволити спливання часу посеред заміни клавіш"
+
+msgid "allow timing out halfway into a key code"
+msgstr "дозволити спливання часу посеред кодування клавіш"
+
+msgid "time in msec for 'timeout'"
+msgstr "час (мс) для 'timeout'"
+
+msgid "time in msec for 'ttimeout'"
+msgstr "час (мс) для 'ttimeout'"
+
+msgid "reading and writing files"
+msgstr "читання і запис файлів"
+
+msgid "enable using settings from modelines when reading a file"
+msgstr ""
+"увімкнути використання налаштувань із рядка режиму (modeline) при чинні файлу"
+
+msgid "allow setting expression options from a modeline"
+msgstr "дозволити встановлення опцій-виразів з рядка режиму (modeline)"
+
+msgid "number of lines to check for modelines"
+msgstr "кількість рядків пошуку рядків режиму (modeline)"
+
+msgid "binary file editing"
+msgstr "редагування двійкових файлів"
+
+msgid "last line in the file has an end-of-line"
+msgstr "останній рядок файлу має символ кінця рядка"
+
+msgid "last line in the file followed by CTRL-Z"
+msgstr "останній рядок файлу закінчується CTRL-Z"
+
+msgid "fixes missing end-of-line at end of text file"
+msgstr "виправляє символ кінця рядку, якого бракує наприкінці файлу"
+
+msgid "prepend a Byte Order Mark to the file"
+msgstr "додати на початку файлу символ порядку байтів"
+
+msgid "end-of-line format: \"dos\", \"unix\" or \"mac\""
+msgstr "формат кінця рядка: «dos», «unix» або «mac»"
+
+msgid "list of file formats to look for when editing a file"
+msgstr "список форматів файлів як підказка при редагуванні файлу"
+
+msgid "writing files is allowed"
+msgstr "записування файлів дозволено"
+
+msgid "write a backup file before overwriting a file"
+msgstr "записати резервний копію перед перезаписуванням файлу"
+
+msgid "keep a backup after overwriting a file"
+msgstr "зберегти резервний копію після перезапису файлу"
+
+msgid "patterns that specify for which files a backup is not made"
+msgstr "шаблони, які зазначають, для яких файлів не робиться резервну копію"
+
+msgid "whether to make the backup as a copy or rename the existing file"
+msgstr ""
+"створити резервну копію шляхом копіювання чи перейменування існуючого файла"
+
+msgid "list of directories to put backup files in"
+msgstr "список директорій, в яких розміщувати файли резервних копій"
+
+msgid "file name extension for the backup file"
+msgstr "розширення назв файлів резервних копій"
+
+msgid "automatically write a file when leaving a modified buffer"
+msgstr "автоматично записувати файл при покиданні модифікованого буфера"
+
+msgid "as 'autowrite', but works with more commands"
+msgstr "як 'autowrite', але працює з більшою кількістю команд"
+
+msgid "always write without asking for confirmation"
+msgstr "завжди записувати без підтвердження"
+
+msgid "automatically read a file when it was modified outside of Vim"
+msgstr "автоматично перечитувати файл, коли його було модифіковано поза Vim"
+
+msgid "keep oldest version of a file; specifies file name extension"
+msgstr "зберігати найстарішу версію файлу; задає розширення назви файлу"
+
+msgid "forcibly sync the file to disk after writing it"
+msgstr "примусово синхронізувати файл на диск після запису"
+
+msgid "the swap file"
+msgstr "файл обміну"
+
+msgid "list of directories for the swap file"
+msgstr "список директорій для файлу обміну"
+
+msgid "use a swap file for this buffer"
+msgstr "використовувати файл обміну для цього буферу"
+
+msgid "number of characters typed to cause a swap file update"
+msgstr "кількість набраних символів щоб спричинити поновлення файлу обміну"
+
+msgid "time in msec after which the swap file will be updated"
+msgstr "час (мс), після якого файл обміну буде поновлено"
+
+msgid "command line editing"
+msgstr "редагування рядка команд"
+
+msgid "how many command lines are remembered"
+msgstr "скільки рядків команд запам’ятати"
+
+msgid "key that triggers command-line expansion"
+msgstr "клавіша, яка запускає розгортання рядка команд"
+
+msgid "like 'wildchar' but can also be used in a mapping"
+msgstr "ніби 'wildchar', але також можна вжити у заміні клавіш"
+
+msgid "specifies how command line completion works"
+msgstr "визначає, як працює доповнення в рядку команд"
+
+msgid "empty or \"tagfile\" to list file name of matching tags"
+msgstr "порожнє чи «tagfile» щоб отримати список файлів для відповідних міток"
+
+msgid "list of file name extensions that have a lower priority"
+msgstr "список розширень назв файлів, які мають нижчий пріоритет"
+
+msgid "list of file name extensions added when searching for a file"
+msgstr "список розширень назв файлів, що додаються при пошуку файлу"
+
+msgid "list of patterns to ignore files for file name completion"
+msgstr "список шаблонів ігнорованих файлів при доповненні назв файлів"
+
+msgid "ignore case when using file names"
+msgstr "не зважати на регістр символів у назвах файлів"
+
+msgid "ignore case when completing file names"
+msgstr "не зважати на регістр символів при доповненні назв файлів"
+
+msgid "command-line completion shows a list of matches"
+msgstr "доповнення рядка команд показує список збігів"
+
+msgid "key used to open the command-line window"
+msgstr "клавіша відкриття вікна команд"
+
+msgid "height of the command-line window"
+msgstr "висота вікна команд"
+
+msgid "executing external commands"
+msgstr "виконання зовнішніх команд"
+
+msgid "name of the shell program used for external commands"
+msgstr "назва програми оболонки для виконання зовнішніх команд"
+
+msgid "character(s) to enclose a shell command in"
+msgstr "символ(и), якими обрамити команду оболонки"
+
+msgid "like 'shellquote' but include the redirection"
+msgstr "як 'shellquote', але включає перенаправлення"
+
+msgid "characters to escape when 'shellxquote' is ("
+msgstr "символи, які потрібно захистити, коли 'shellxquote' ("
+
+msgid "argument for 'shell' to execute a command"
+msgstr "аргумент 'shell' щоб виконати команду"
+
+msgid "used to redirect command output to a file"
+msgstr "вживається для перенаправлення виводу в файл"
+
+msgid "use a temp file for shell commands instead of using a pipe"
+msgstr "використовувати тимчасовий файл для команд оболонки замість конвеєра"
+
+msgid "program used for \"=\" command"
+msgstr "програма для команди «=»"
+
+msgid "program used to format lines with \"gq\" command"
+msgstr "програма для форматування рядків командою «gq»"
+
+msgid "program used for the \"K\" command"
+msgstr "програма для команди «K»"
+
+msgid "warn when using a shell command and a buffer has changes"
+msgstr "попереджувати при запуску команди оболонки, коли буфер має зміни"
+
+msgid "running make and jumping to errors (quickfix)"
+msgstr "виконання make і перехід до помилок (quickfix)"
+
+msgid "name of the file that contains error messages"
+msgstr "назва файлу з повідомленнями про помилки"
+
+msgid "list of formats for error messages"
+msgstr "список форматів повідомлень про помилки"
+
+msgid "program used for the \":make\" command"
+msgstr "програма команди «:make»"
+
+msgid "string used to put the output of \":make\" in the error file"
+msgstr "перенаправлення виводу програми \":make\" у файл помилок"
+
+msgid "name of the errorfile for the 'makeprg' command"
+msgstr "назва файлу помилок для команди 'makeprg' "
+
+msgid "program used for the \":grep\" command"
+msgstr "програма для команди «:grep»"
+
+msgid "list of formats for output of 'grepprg'"
+msgstr "список форматів виводу програми 'grepprg'"
+
+msgid "encoding of the \":make\" and \":grep\" output"
+msgstr "кодування виводу «:make» і «:grep»"
+
+msgid "system specific"
+msgstr "стосовно системи"
+
+msgid "use forward slashes in file names; for Unix-like shells"
+msgstr ""
+"використовувати прямі косі лінії у назвах файлів; для Unix-подібних оболонок"
+
+msgid "specifies slash/backslash used for completion"
+msgstr "визначає пряму чи зворотну косу вживати у доповненні"
+
+msgid "language specific"
+msgstr "стосовно мови"
+
+msgid "specifies the characters in a file name"
+msgstr "визначає символи у назві файлу"
+
+msgid "specifies the characters in an identifier"
+msgstr "визначає символи у ідентифікаторі"
+
+msgid "specifies the characters in a keyword"
+msgstr "визначає символи у ключовому слові"
+
+msgid "specifies printable characters"
+msgstr "визначає друковні символи"
+
+msgid "specifies escape characters in a string"
+msgstr "визначає escape-символи у текстовому рядку"
+
+msgid "display the buffer right-to-left"
+msgstr "показати буфер справа наліво"
+
+msgid "when to edit the command-line right-to-left"
+msgstr "коли редагувати рядок команд справа наліво"
+
+msgid "insert characters backwards"
+msgstr "вставляти символи в зворотному порядку"
+
+msgid "allow CTRL-_ in Insert and Command-line mode to toggle 'revins'"
+msgstr "дозволити CTRL-_ у режимі Insert і Command-line перемикати 'revins'"
+
+msgid "the ASCII code for the first letter of the Hebrew alphabet"
+msgstr "код ASCII першої літери алфавіту івриту"
+
+msgid "use Hebrew keyboard mapping"
+msgstr "використовувати розкладку клавіатури для івриту"
+
+msgid "use phonetic Hebrew keyboard mapping"
+msgstr "використовувати фонетичний набір для івриту"
+
+msgid "prepare for editing Arabic text"
+msgstr "підготувати для редагування арабського тексту"
+
+msgid "perform shaping of Arabic characters"
+msgstr "виконувати формування арабських символів"
+
+msgid "terminal will perform bidi handling"
+msgstr "термінал виконуватиме обробку двостороннього тексту"
+
+msgid "name of a keyboard mapping"
+msgstr "назва розкладки клавіатури"
+
+msgid "list of characters that are translated in Normal mode"
+msgstr "список трансльованих у режимі Normal символів"
+
+msgid "apply 'langmap' to mapped characters"
+msgstr "застосувати 'langmap' до замінюваних символів"
+
+msgid "when set never use IM; overrules following IM options"
+msgstr ""
+"коли встановлено, не використовувати IM (метод введення); пересилює подальші "
+"опції"
+
+msgid "in Insert mode: 1: use :lmap; 2: use IM; 0: neither"
+msgstr ""
+"у режимі Insert: 1: використовувати :lmap; 2: використовувати IM; 0: нічого"
+
+msgid "entering a search pattern: 1: use :lmap; 2: use IM; 0: neither"
+msgstr ""
+"введення шаблону пошуку: 1: використовувати :lmap; 2: використовувати IM; 0: "
+"нічого"
+
+msgid "when set always use IM when starting to edit a command line"
+msgstr ""
+"коли встановлено, завжди використовувати IM на початку редагування в рядку "
+"команд"
+
+msgid "function to obtain IME status"
+msgstr "функція отримання стану IME"
+
+msgid "function to enable/disable IME"
+msgstr "функція увімкнення/вимкнення IME"
+
+msgid "multi-byte characters"
+msgstr "багатобайтні символи"
+
+msgid "character encoding used in Nvim: \"utf-8\""
+msgstr "кодування символів у Nvim: «utf-8»"
+
+msgid "character encoding for the current file"
+msgstr "кодування символів у цьому файлі"
+
+msgid "automatically detected character encodings"
+msgstr "автоматично визначати кодування символів"
+
+msgid "expression used for character encoding conversion"
+msgstr "вираз для перетворення кодування символів"
+
+msgid "delete combining (composing) characters on their own"
+msgstr "видаляти комбінаційні (складальні) символи окремо"
+
+msgid "maximum number of combining (composing) characters displayed"
+msgstr "найбільша кількість зображуваних комбінаційних (складальних) символів"
+
+msgid "key that activates the X input method"
+msgstr "клавіша, що активує метод введення X"
+
+msgid "width of ambiguous width characters"
+msgstr "ширина символів неоднозначної ширини"
+
+msgid "emoji characters are full width"
+msgstr "символи емодзі повної ширини"
+
+msgid "various"
+msgstr "різне"
+
+msgid ""
+"when to use virtual editing: \"block\", \"insert\", \"all\"\n"
+"and/or \"onemore\""
+msgstr ""
+"коли використовувати віртуальне редагування: «block», «insert», «all»\n"
+"і/або «onemore»"
+
+msgid "list of autocommand events which are to be ignored"
+msgstr "список подій автокоманд, які ігнорувати"
+
+msgid "load plugin scripts when starting up"
+msgstr "завантажувати скрипти плагінів при запуску"
+
+msgid "enable reading .vimrc/.exrc/.gvimrc in the current directory"
+msgstr "увімкнути підчитування .vimrc/.exrc/.gvimrc у активній директорії"
+
+msgid "safer working with script files in the current directory"
+msgstr "безпечніша робота із файлами скриптів у активній директорії"
+
+msgid "use the 'g' flag for \":substitute\""
+msgstr "вживати прапорець 'g' у «:substitute»"
+
+msgid "allow reading/writing devices"
+msgstr "дозволити читання/записування пристроїв"
+
+msgid "maximum depth of function calls"
+msgstr "найбільша глибина виклику функцій"
+
+msgid "list of words that specifies what to put in a session file"
+msgstr "список слів, що визначає, що встановлювати у файлі сеансу"
+
+msgid "list of words that specifies what to save for :mkview"
+msgstr "список слів, що визначає, що зберігати у :mkview"
+
+msgid "directory where to store files with :mkview"
+msgstr "директорія, в якій зберігати файли при :mkview"
+
+msgid "list that specifies what to write in the ShaDa file"
+msgstr "список, що визначає, що записувати у файлі ShaDa"
+
+msgid "what happens with a buffer when it's no longer in a window"
+msgstr "що стається з буфером, коли він більше не у вікні"
+
+msgid "empty, \"nofile\", \"nowrite\", \"quickfix\", etc.: type of buffer"
+msgstr "порожній, «nofile», «nowrite», «quickfix» тощо: тип буфера"
+
+msgid "whether the buffer shows up in the buffer list"
+msgstr "чи показується буфер у списку буферів"
+
+msgid "set to \"msg\" to see all error messages"
+msgstr "встановити у «msg» щоб бачити всі повідомлення про помилки"
+
+msgid "whether to show the signcolumn"
+msgstr "чи показувати signcolumn"
+
+msgid "name of the MzScheme dynamic library"
+msgstr "назва динамічної бібліотеки MzScheme"
+
+msgid "name of the MzScheme GC dynamic library"
+msgstr "назва динамічної бібліотеки MzScheme GC"
+
+msgid "whether to use Python 2 or 3"
+msgstr "використовувати Python 2 чи 3"
+
+msgid "E249: Window layout changed unexpectedly"
+msgstr "E249: Розміщення вікна несподівано змінилася"
+
+msgid "E1156: Cannot change the argument list recursively"
+msgstr "E1156: Неможливо рекурсивно змінити список аргументів"
+
+msgid "E163: There is only one file to edit"
+msgstr "E163: Редагується лише один файл"
+
+msgid "E164: Cannot go before first file"
+msgstr "E164: Це вже найперший файл"
+
+msgid "E165: Cannot go beyond last file"
+msgstr "E165: Це вже останній файл"
+
+msgid "E610: No argument to delete"
+msgstr "E610: Немає аргументів для знищення"
+
+msgid "E218: Autocommand nesting too deep"
+msgstr "E218: Забагато вкладених автокоманд"
msgid "--Deleted--"
msgstr "--Знищено--"
@@ -40,7 +1283,7 @@ msgid "E936: Cannot delete the current group"
msgstr "E936: Не вдалося знищити цю групу"
msgid "W19: Deleting augroup that is still in use"
-msgstr "W19: Знищення автогрупи, яка все ще використовується"
+msgstr "W19: Знищується автогрупа все ще у вжитку"
msgid ""
"\n"
@@ -56,11 +1299,9 @@ msgstr "E680: <буфер=%d>: некоректний номер буфера "
msgid "E217: Can't execute autocommands for ALL events"
msgstr "E217: Не можу виконувати автокоманди для УСІХ подій"
-msgid "No matching autocommands"
-msgstr "Немає відповідних автокоманд"
-
-msgid "E218: autocommand nesting too deep"
-msgstr "E218: Забагато вкладених автокоманд"
+#, c-format
+msgid "No matching autocommands: %s"
+msgstr "Немає відповідних автокоманд: %s"
#, c-format
msgid "%s Autocommands for \"%s\""
@@ -86,24 +1327,19 @@ msgstr "E216: Немає такої події: %s"
msgid "E216: No such group or event: %s"
msgstr "E216: Немає такої групи чи події: %s"
-msgid "[Location List]"
-msgstr "[Список місць]"
-
-msgid "[Quickfix List]"
-msgstr "[Список виправлень]"
-
msgid "E855: Autocommands caused command to abort"
msgstr "E855: Автокоманди призвели до скасування команди"
+#, c-format
+msgid "E937: Attempt to delete a buffer that is in use: %s"
+msgstr "E937: Спроба видалити буфер, що використовується: %s"
+
msgid "E82: Cannot allocate any buffer, exiting..."
msgstr "E82: Немає можливості розмістити хоч один буфер, завершення роботи..."
msgid "E83: Cannot allocate buffer, using other one..."
msgstr "E83: Немає можливості розмістити буфер, буде використано інший..."
-msgid "E937: Attempt to delete a buffer that is in use"
-msgstr "E937: Спроба знищення буферу, який використовується"
-
msgid "E515: No buffers were unloaded"
msgstr "E515: Жоден з буферів не був вивантажений"
@@ -113,6 +1349,27 @@ msgstr "E516: Жоден з буферів не знищено"
msgid "E517: No buffers were wiped out"
msgstr "E517: Жоден з буферів не витерто"
+#, c-format
+msgid "%d buffer unloaded"
+msgid_plural "%d buffers unloaded"
+msgstr[0] "%d буфер вивантажено"
+msgstr[1] "%d буфери вивантажено"
+msgstr[2] "%d буферів вивантажено"
+
+#, c-format
+msgid "%d buffer deleted"
+msgid_plural "%d buffers deleted"
+msgstr[0] "%d буфер знищено"
+msgstr[1] "%d буфери знищено"
+msgstr[2] "%d буферів знищено"
+
+#, c-format
+msgid "%d buffer wiped out"
+msgid_plural "%d buffers wiped out"
+msgstr[0] "%d буфер стерто"
+msgstr[1] "%d буфери стерто"
+msgstr[2] "%d буферів стерто"
+
msgid "E90: Cannot unload last buffer"
msgstr "E90: Не можу вивантажити останній буфер"
@@ -138,16 +1395,16 @@ msgid "E89: %s will be killed (add ! to override)"
msgstr "E89: «%s» буде вбито (! щоб не зважати)"
msgid "E948: Job still running (add ! to end the job)"
-msgstr "E948: Задача все ще виконується (! щоб закінчити)"
+msgstr "E948: Завдання все ще виконується (! щоб закінчити)"
msgid "E37: No write since last change (add ! to override)"
msgstr "E37: Зміни не було записано (! щоб не зважати)"
msgid "E948: Job still running"
-msgstr "E948: Задача все ще виконується"
+msgstr "E948: Завдання все ще виконується"
msgid "E37: No write since last change"
-msgstr "E37: Не записано після останніх змін"
+msgstr "E37: Не записано найновіші зміни"
msgid "W14: Warning: List of file names overflow"
msgstr "W14: Обережно: Список назв файлів переповнено"
@@ -187,6 +1444,13 @@ msgid "[readonly]"
msgstr "[лише читати]"
#, c-format
+msgid "%<PRId64> line --%d%%--"
+msgid_plural "%<PRId64> lines --%d%%--"
+msgstr[0] "%<PRId64> рядок --%d%%--"
+msgstr[1] "%<PRId64> рядки --%d%%--"
+msgstr[2] "%<PRId64> рядків --%d%%--"
+
+#, c-format
msgid "line %<PRId64> of %<PRId64> --%d%%-- col "
msgstr "рядок %<PRId64> з %<PRId64> --%d%%-- колонка "
@@ -196,12 +1460,6 @@ msgstr "[Без назви]"
msgid "help"
msgstr "допомога"
-msgid "[Help]"
-msgstr "[Допомога]"
-
-msgid "[Preview]"
-msgstr "[Перегляд]"
-
msgid "All"
msgstr "Усе"
@@ -211,6 +1469,26 @@ msgstr "Знизу"
msgid "Top"
msgstr "Вгорі"
+#, c-format
+msgid "%d%%"
+msgstr "%d%%"
+
+#, c-format
+msgid " (%d of %d)"
+msgstr " (%d з %d)"
+
+#, c-format
+msgid " ((%d) of %d)"
+msgstr " ((%d) з %d)"
+
+#, c-format
+msgid " (file %d of %d)"
+msgstr " (файл %d з %d)"
+
+#, c-format
+msgid " (file (%d) of %d)"
+msgstr " (файл (%d) з %d)"
+
msgid "E382: Cannot write, 'buftype' option is set"
msgstr "E382: Не можу записати, вказана опція 'buftype'"
@@ -220,6 +1498,120 @@ msgstr "[Запит]"
msgid "[Scratch]"
msgstr "[З нуля]"
+msgid "[Location List]"
+msgstr "[Список місць]"
+
+msgid "[Quickfix List]"
+msgstr "[Список виправлень]"
+
+msgid "E206: Patchmode: can't touch empty original file"
+msgstr "E206: Латання: не вдалося створити оригінал"
+
+msgid "E513: Write error, conversion failed (make 'fenc' empty to override)"
+msgstr "E513: Помилка запису, перетворення не вдалося (скиньте 'fenc')"
+
+msgid "E513: Write error, conversion failed in line %"
+msgstr "E513: Помилка запису, перетворення не вдалася у рядку %"
+
+msgid "E514: Write error (file system full?)"
+msgstr "E514: Помилка запису (скінчилось вільне місце?)"
+
+#, c-format
+msgid "E676: No matching autocommands for buftype=%s buffer"
+msgstr "E676: Немає відповідних автокоманд для буфера buftype=%s"
+
+msgid "WARNING: The file has been changed since reading it!!!"
+msgstr "ЗАСТЕРЕЖЕННЯ: Файл змінився з часу останнього читання!!!"
+
+msgid "Do you really want to write to it"
+msgstr "Ви справді хочете його переписати??"
+
+msgid "E203: Autocommands deleted or unloaded buffer to be written"
+msgstr "E203: Автокоманда знищила або вивантажила буфер, що мав бути записаний"
+
+msgid "E204: Autocommand changed number of lines in unexpected way"
+msgstr "E204: Автокоманда несподіваним чином змінила кількість рядків"
+
+msgid "is a directory"
+msgstr "каталог"
+
+msgid "is not a file or writable device"
+msgstr "Не придатний для запису"
+
+msgid "is read-only (add ! to override)"
+msgstr "лише для читання (! щоб не зважати)"
+
+#, c-format
+msgid "E303: Unable to create directory \"%s\" for backup file: %s"
+msgstr "E303: Не вдалося створити каталог «%s» для резервного файлу: %s"
+
+msgid "E509: Cannot create backup file (add ! to override)"
+msgstr "E509: Не вдалося створити резервну копію (! щоб не зважати)"
+
+msgid "E510: Can't make backup file (add ! to override)"
+msgstr "E510: Не вдалося зробити резервну копію (! щоб не зважати)"
+
+msgid "E214: Can't find temp file for writing"
+msgstr "E214: Не вдалося підшукати тимчасовий файл для запису"
+
+msgid "E213: Cannot convert (add ! to write without conversion)"
+msgstr "E213: Не вдалося перетворити (! щоб записати без конвертації)"
+
+msgid "E166: Can't open linked file for writing"
+msgstr "E166: Не вдалося відкрити для запису зв'язаний файл"
+
+#, c-format
+msgid "E212: Can't open file for writing: %s"
+msgstr "E212: Не вдалося відкрити файл для запису: %s"
+
+#, c-format
+msgid "E512: Close failed: %s"
+msgstr "E512: Не вдалося закрити: %s"
+
+msgid " CONVERSION ERROR"
+msgstr " ПОМИЛКА КОНВЕРТАЦІЇ"
+
+#, c-format
+msgid " in line %<PRId64>;"
+msgstr " у рядку %<PRId64>;"
+
+msgid "[NOT converted]"
+msgstr "[НЕ конвертовано]"
+
+msgid "[converted]"
+msgstr "[конвертовано]"
+
+msgid "[Device]"
+msgstr "[Пристрій]"
+
+msgid " [a]"
+msgstr "[д]"
+
+msgid " appended"
+msgstr " дописаний"
+
+msgid " [w]"
+msgstr "[з]"
+
+msgid " written"
+msgstr " записаний"
+
+msgid "E205: Patchmode: can't save original file"
+msgstr "E205: Латання: не вдалося зберегти оригінал"
+
+msgid "E207: Can't delete backup file"
+msgstr "E207: Не вдалося знищити резервний файл"
+
+msgid ""
+"\n"
+"WARNING: Original file may be lost or damaged\n"
+msgstr ""
+"\n"
+"ЗАСТЕРЕЖЕННЯ: Оригінал, мабуть, втрачений чи пошкоджений\n"
+
+msgid "don't quit the editor until the file is successfully written!"
+msgstr "Не виходьте з редактора, доки файл не записано!"
+
msgid "W10: Warning: Changing a readonly file"
msgstr "W10: Застереження: Змінюється файл призначений лише для читання"
@@ -235,18 +1627,27 @@ msgstr "Неможливо надіслати дані у закритий по
msgid "Can't send raw data to rpc channel"
msgstr "Неможливо надіслати дані у канал завдання"
+msgid "tagname"
+msgstr "назва теґу"
+
+msgid " kind file\n"
+msgstr " тип файлу\n"
+
+msgid "'history' option is zero"
+msgstr "Опція 'history' порожня"
+
msgid "E474: Failed to convert list to msgpack string buffer"
msgstr "E474: Не вдалося перетворити список у текстовий буфер msgpack"
+msgid "E548: Digit expected"
+msgstr "E548: Очікується цифра"
+
msgid "E545: Missing colon"
msgstr "E545: Пропущено двокрапку"
msgid "E546: Illegal mode"
msgstr "E546: Неправильний режим"
-msgid "E548: digit expected"
-msgstr "E548: Потрібна цифра"
-
msgid "E549: Illegal percentage"
msgstr "E549: Неправильний відсоток"
@@ -270,11 +1671,11 @@ msgid "cmd: %s"
msgstr "команда: %s"
msgid "frame is zero"
-msgstr "кадр нульовий"
+msgstr "кадр стеку нульовий"
#, c-format
msgid "frame at highest level: %d"
-msgstr "кадр на найвищому рівні: %d"
+msgstr "кадр стеку на найвищому рівні: %d"
#, c-format
msgid "Breakpoint in \"%s%s\" line %<PRId64>"
@@ -301,7 +1702,7 @@ msgstr "E96: Не можна порівнювати понад %<PRId64> буф
#, c-format
msgid "Not enough memory to use internal diff for buffer \"%s\""
-msgstr "Недостатньо пам’яті для внутрішнього порівняння у буфері \"%s\""
+msgstr "Бракує пам’яті для внутрішнього алгоритму порівняння для буфера «%s»"
msgid "E810: Cannot read or write temp files"
msgstr "E810: Не можна читати чи записувати тимчасові файли"
@@ -310,7 +1711,7 @@ msgid "E97: Cannot create diffs"
msgstr "E97: Не вдалося створити порівняння"
msgid "E960: Problem creating the internal diff"
-msgstr "E960: Не вдалося створити внутрішнє порівняння"
+msgstr "E960: Не вдалося порівняти внутрішнім алгоритмом"
msgid "E816: Cannot read patch output"
msgstr "E816: Не вдалося прочитати результат patch"
@@ -354,7 +1755,8 @@ msgstr "E1215: Диграф має бути одним символом: %s"
msgid ""
"E1216: digraph_setlist() argument must be a list of lists with two items"
msgstr ""
-"E1216: аргумент digraph_setlist() має бути списком списків з двох елементів"
+"E1216: Аргумент digraph_setlist() повинен бути списком списків із двох "
+"елементів"
msgid "E104: Escape not allowed in digraph"
msgstr "E104: У диграфах не може міститися escape"
@@ -363,7 +1765,7 @@ msgid "Custom"
msgstr "Власне"
msgid "Latin supplement"
-msgstr "Латиниця доповнення"
+msgstr "Доповнення латиниці"
msgid "Greek and Coptic"
msgstr "Грецька і коптська"
@@ -405,7 +1807,7 @@ msgid "Mathematical operators"
msgstr "Математичні оператори"
msgid "Technical"
-msgstr "Технічне"
+msgstr "Технічні"
msgid "Box drawing"
msgstr "Малювання прямокутників"
@@ -420,19 +1822,19 @@ msgid "Symbols"
msgstr "Символи"
msgid "Dingbats"
-msgstr "Дурниці"
+msgstr "Друкарські орнаменти"
msgid "CJK symbols and punctuation"
msgstr "Символи і пунктуація CJK"
msgid "Hiragana"
-msgstr "Хірагана"
+msgstr "Хіраґана"
msgid "Katakana"
msgstr "Катакана"
msgid "Bopomofo"
-msgstr "Бопомофо"
+msgstr "Чжуїнь"
msgid "E544: Keymap file not found"
msgstr "E544: Не знайдено файл розкладки"
@@ -443,139 +1845,103 @@ msgstr "E105: :loadkeymap використано не у файлі команд
msgid "E791: Empty keymap entry"
msgstr "E791: Елемент розкладки порожній"
-msgid " Keyword completion (^N^P)"
-msgstr " Доповнення ключових слів (^N^P)"
-
-msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-msgstr " Режим ^X (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-
-msgid " Whole line completion (^L^N^P)"
-msgstr " Доповнення усього рядка (^L^N^P)"
-
-msgid " File name completion (^F^N^P)"
-msgstr " Доповнення назви файлу (^F^N^P)"
+msgid " TERMINAL"
+msgstr " ТЕРМІНАЛ"
-msgid " Tag completion (^]^N^P)"
-msgstr " Доповнення з міток (^]^N^P)"
+msgid " VREPLACE"
+msgstr " ВІРТ ЗАМІНА"
-msgid " Path pattern completion (^N^P)"
-msgstr " Доповнення шляху за зразком (^N^P)"
+msgid " REPLACE"
+msgstr " ЗАМІНА"
-msgid " Definition completion (^D^N^P)"
-msgstr " Доповнення визначення (^D^N^P)"
+msgid " REVERSE"
+msgstr " НАВИВОРІТ"
-msgid " Dictionary completion (^K^N^P)"
-msgstr " Доповнення зі словника (^K^N^P)"
+msgid " INSERT"
+msgstr " ВСТАВИТИ"
-msgid " Thesaurus completion (^T^N^P)"
-msgstr " Доповнення з тезаурусу (^T^N^P)"
+msgid " (terminal)"
+msgstr " (термінал)"
-msgid " Command-line completion (^V^N^P)"
-msgstr " Доповнення команд (^V^N^P)"
+msgid " (insert)"
+msgstr " (вставити)"
-msgid " User defined completion (^U^N^P)"
-msgstr " Користувацьке доповнення (^U^N^P)"
+msgid " (replace)"
+msgstr " (замінити)"
-msgid " Omni completion (^O^N^P)"
-msgstr " Кмітливе доповнення (^O^N^P)"
+msgid " (vreplace)"
+msgstr " (вірт заміна)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Орфографічна підказка (s^N^P)"
+msgid " Arabic"
+msgstr " Арабська"
-msgid " Keyword Local completion (^N^P)"
-msgstr " Доповнення місцевих ключових слів (^N^P)"
+msgid " (paste)"
+msgstr " (клей)"
-msgid "Hit end of paragraph"
-msgstr "Трапився кінець параграфа"
+msgid " VISUAL"
+msgstr " ВИБІР"
-msgid "E839: Completion function changed window"
-msgstr "E839: Функція доповнення змінила вікно"
+msgid " VISUAL LINE"
+msgstr " ВИБІР РЯДКІВ"
-msgid "E840: Completion function deleted text"
-msgstr "E840: Функція доповнення знищила текст"
+msgid " VISUAL BLOCK"
+msgstr " ВИБІР БЛОКУ"
-msgid "'dictionary' option is empty"
-msgstr "Опція 'dictionary' порожня"
+msgid " SELECT"
+msgstr " ВИДІЛЕННЯ"
-msgid "'thesaurus' option is empty"
-msgstr "Опція 'thesaurus' порожня"
+msgid " SELECT LINE"
+msgstr " ВИДІЛЕННЯ РЯДКІВ"
-#, c-format
-msgid "Scanning dictionary: %s"
-msgstr "Сканується словник: %s"
+msgid " SELECT BLOCK"
+msgstr " ВИДІЛЕННЯ БЛОКУ"
-msgid " (insert) Scroll (^E/^Y)"
-msgstr " (вставити) Прогорнути (^E/^Y)"
+msgid "recording"
+msgstr "йде запис"
-msgid " (replace) Scroll (^E/^Y)"
-msgstr " (заміна) Прогорнути (^E/^Y)"
+msgid "E111: Missing ']'"
+msgstr "E111: Бракує ']'"
#, c-format
-msgid "Scanning: %s"
-msgstr "Пошук у: %s"
-
-msgid "Scanning tags."
-msgstr "Пошук серед теґів."
-
-msgid "match in file"
-msgstr "збіг у файлі"
-
-msgid " Adding"
-msgstr " Додається"
-
-msgid "-- Searching..."
-msgstr "-- Пошук..."
-
-msgid "Back at original"
-msgstr "Початковий варіант"
+msgid "E697: Missing end of List ']': %s"
+msgstr "E697: Немає кінцівки списку ']': %s"
-msgid "Word from other line"
-msgstr "Слово з іншого рядка"
+msgid "E719: Cannot slice a Dictionary"
+msgstr "E719: Не можна зробити зріз словника"
-msgid "The only match"
-msgstr "Єдиний збіг"
+msgid "E909: Cannot index a special variable"
+msgstr "E909: Не можна індексувати спеціальну змінну"
-#, c-format
-msgid "match %d of %d"
-msgstr "збіг %d з %d"
+msgid "E274: No white space allowed before parenthesis"
+msgstr "E274: Перед дужкою не дозволено пробіл"
#, c-format
-msgid "match %d"
-msgstr "збіг %d"
-
-msgid "E18: Unexpected characters in :let"
-msgstr "E18: Неочікувані символи у :let"
-
-msgid "E111: Missing ']'"
-msgstr "E111: Бракує ']'"
-
-msgid "E719: Cannot use [:] with a Dictionary"
-msgstr "E719: Не можна використати [:] зі словником"
+msgid "E80: Error while writing: %s"
+msgstr "E80: Помилка під час запису: %s"
-#, c-format
-msgid "E461: Illegal variable name: %s"
-msgstr "E461: Неприпустима назва змінної: %s"
+msgid "E695: Cannot index a Funcref"
+msgstr "E695: Функція не має індексації"
-msgid "E995: Cannot modify existing variable"
-msgstr "E995: Неможливо змінити наявну змінну"
+msgid "E698: Variable nested too deep for making a copy"
+msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-msgid "E274: No white space allowed before parenthesis"
-msgstr "E274: Перед дужками не має бути пробілу"
+msgid "E1098: String, List or Blob required"
+msgstr "E1098: Потрібен String, List чи Blob"
#, c-format
-msgid "E940: Cannot lock or unlock variable %s"
-msgstr "E940: Неможливо заблокувати чи розблокувати змінну %s"
+msgid "E1169: Expression too recursive: %s"
+msgstr "E1169: Вираз занадто рекурсивний: %s"
#, c-format
-msgid "E80: Error while writing: %s"
-msgstr "E80: Помилка під час запису: %s"
+msgid "E1203: Dot can only be used on a dictionary: %s"
+msgstr "E1203: Крапку можна вжити тільки із словником: %s"
-msgid "E1098: String, List or Blob required"
-msgstr "E1098: Потрібен String, List чи Blob"
+msgid "E1192: Empty function name"
+msgstr "E1192: Порожня назва функції"
#, c-format
-msgid "E734: Wrong variable type for %s="
-msgstr "E734: Неправильний тип змінної для %s="
+msgid "E1250: Argument of %s must be a List, String, Dictionary or Blob"
+msgstr "E1250: Аргумент у %s має бути List, String, Dictionary або Blob"
msgid ""
"E5700: Expression from 'spellsuggest' must yield lists with exactly two "
@@ -583,41 +1949,6 @@ msgid ""
msgstr ""
"E5700: Вираз із 'spellsuggest' має повертати список із рівно двома елементами"
-msgid "E991: cannot use =<< here"
-msgstr "E991: Тут не можна використати =<<"
-
-msgid "E221: Marker cannot start with lower case letter"
-msgstr "E221: Позначка не може починатися із малої літери"
-
-msgid "E172: Missing marker"
-msgstr "E172: Бракує позначки"
-
-#, c-format
-msgid "E990: Missing end marker '%s'"
-msgstr "E990: Бракує позначки кінця «%s»"
-
-msgid "E687: Less targets than List items"
-msgstr "E687: Цілей менше, ніж елементів списку"
-
-msgid "E688: More targets than List items"
-msgstr "E688: Цілей більше, ніж елементів списку"
-
-msgid "E452: Double ; in list of variables"
-msgstr "E452: Друга ; у списку змінних"
-
-#, c-format
-msgid "E738: Can't list variables for %s"
-msgstr "E738: Не можна перерахувати змінні у %s"
-
-msgid "E996: Cannot lock an environment variable"
-msgstr "E996: Неможливо заблокувати змінну оточення"
-
-msgid "E996: Cannot lock an option"
-msgstr "E996: Неможливо заблокувати опцію"
-
-msgid "E996: Cannot lock a register"
-msgstr "E996: Неможливо заблокувати регістр"
-
#, c-format
msgid "E121: Undefined variable: %.*s"
msgstr "E121: Невизначена змінна: %.*s"
@@ -632,50 +1963,28 @@ msgid "E713: Cannot use empty key after ."
msgstr "E713: Неможливо вжити порожній ключ після ."
msgid "E709: [:] requires a List or Blob value"
-msgstr "E709: [:] вимагає List чи Blob"
-
-msgid "E972: Blob value does not have the right number of bytes"
-msgstr "E972: неправильна кількість байтів у значенні Blob"
+msgstr "E709: [:] потребує List чи Blob"
msgid "E996: Cannot lock a range"
msgstr "E996: Неможливо заблокувати діапазон"
-msgid "E710: List value has more items than target"
-msgstr "E710: Список має більше елементів, ніж ціль"
-
-msgid "E711: List value has not enough items"
-msgstr "E711: Список має недостатньо елементів"
-
msgid "E996: Cannot lock a list or dict"
msgstr "E996: Неможливо заблокувати список чи словник"
msgid "E690: Missing \"in\" after :for"
msgstr "E690: Пропущено «in» після :for"
-#, c-format
-msgid "E108: No such variable: \"%s\""
-msgstr "E108: Змінної немає: «%s»"
-
msgid "E109: Missing ':' after '?'"
msgstr "E109: Бракує ':' після '?'"
msgid "E804: Cannot use '%' with Float"
msgstr "E804: Не можна виконати '%' над Float"
-msgid "E973: Blob literal should have an even number of hex characters"
-msgstr "E973: Запис Blob повинен мати парну кількість шістнадцяткових символів"
-
msgid "E110: Missing ')'"
msgstr "E110: Пропущено ')'"
msgid "E260: Missing name after ->"
-msgstr "E260: Після -> бракує імені"
-
-msgid "E695: Cannot index a Funcref"
-msgstr "E695: Функція не має індексації"
-
-msgid "E909: Cannot index a special variable"
-msgstr "E909: Спеціальна змінна не має індексації"
+msgstr "E260: Після -> бракує назви"
#, c-format
msgid "E112: Option name missing: %s"
@@ -685,6 +1994,9 @@ msgstr "E112: Бракує назви опції: %s"
msgid "E113: Unknown option: %s"
msgstr "E113: Невідома опція: %s"
+msgid "E973: Blob literal should have an even number of hex characters"
+msgstr "E973: Запис Blob повинен мати парну кількість шістнадцяткових символів"
+
#, c-format
msgid "E114: Missing quote: %s"
msgstr "E114: Бракує лапки: %s"
@@ -697,10 +2009,6 @@ msgstr "E115: Бракує лапки: %s"
msgid "E696: Missing comma in List: %s"
msgstr "E696: Бракує коми у списку: %s"
-#, c-format
-msgid "E697: Missing end of List ']': %s"
-msgstr "E697: Немає кінцівки списку ']': %s"
-
msgid "Not enough memory to set references, garbage collection aborted!"
msgstr ""
"Недостатньо пам’яті для встановлення посилань, збирання сміття припинено!"
@@ -724,6 +2032,9 @@ msgstr "E723: Немає кінцівки словника '}': %s"
msgid "map() argument"
msgstr "аргумент map()"
+msgid "mapnew() argument"
+msgstr "аргумент mapnew()"
+
msgid "filter() argument"
msgstr "аргумент filter()"
@@ -731,9 +2042,6 @@ msgstr "аргумент filter()"
msgid "E700: Unknown function: %s"
msgstr "E700: Невідома функція: %s"
-msgid "E922: expected a dict"
-msgstr "E922: очікується словник"
-
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: Другий аргумент function() має бути списком чи словником"
@@ -747,29 +2055,6 @@ msgstr "Виконується команда: «%s»"
msgid "E921: Invalid callback argument"
msgstr "E921: Некоректний аргумент функції зворотнього виклику"
-#, c-format
-msgid "E963: setting %s to value with wrong type"
-msgstr "E963: встановлення %s до значення з неправильним типом"
-
-#, c-format
-msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
-msgstr "E794: Не можна встановити змінну у пісочниці: «%.*s»"
-
-#, c-format
-msgid "E795: Cannot delete variable %.*s"
-msgstr "E795: Не можна знищити змінну %.*s"
-
-#, c-format
-msgid "E704: Funcref variable name must start with a capital: %s"
-msgstr "E704: Назва змінної Funcref має починатися з великої літери: %s"
-
-#, c-format
-msgid "E705: Variable name conflicts with existing function: %s"
-msgstr "E705: Назва змінної співпадає з наявною функцією: %s"
-
-msgid "E698: variable nested too deep for making a copy"
-msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-
msgid ""
"\n"
"\tLast set from "
@@ -788,7 +2073,7 @@ msgid "E5009: Invalid 'runtimepath'"
msgstr "E5009: Некоректний 'runtimepath'"
msgid "E977: Can only compare Blob with Blob"
-msgstr "E977: Блоб можна порівняти тільки із блобом"
+msgstr "E977: Blob можна порівняти тільки з Blob"
msgid "E691: Can only compare List with List"
msgstr "E691: Список можна порівняти тільки зі списком"
@@ -854,12 +2139,6 @@ msgstr ""
"%.*s"
#, c-format
-msgid "+-%s%3ld line: "
-msgid_plural "+-%s%3ld lines: "
-msgstr[0] "+-%s%3ld рядок: "
-msgstr[1] "+-%s%3ld рядків: "
-
-#, c-format
msgid "E474: Expected string end: %.*s"
msgstr "E474: Очікувався кінець рядка: %.*s"
@@ -1062,20 +2341,31 @@ msgid "E5005: Unable to dump %s: container references itself in %s"
msgstr "E5005: Неможливо злити %s: контейнер посилається на самого себе у %s"
#, c-format
-msgid "E684: list index out of range: %<PRId64>"
+msgid "E684: List index out of range: %<PRId64>"
msgstr "E684: Індекс списку поза межами: %<PRId64>"
#, c-format
msgid "E899: Argument of %s must be a List or Blob"
-msgstr "E899: Аргумент у %s має бути списком чи блобом"
+msgstr "E899: Аргумент у %s має бути List або Blob"
msgid "E957: Invalid window number"
msgstr "E957: Некоректний номер вікна"
#, c-format
+msgid "E706: Argument of %s must be a List, String or Dictionary"
+msgstr "E706: Аргумент у %s має бути List, String чи Dictionary"
+
+#, c-format
+msgid "E935: Invalid submatch number: %d"
+msgstr "E935: неправильний номер групи збігу: %d"
+
+#, c-format
msgid "E998: Reduce of an empty %s with no initial value"
msgstr "E998: Скорочення порожнього %s без початкового значення"
+msgid "E1132: Missing function argument"
+msgstr "E1132: Бракує аргументу функції"
+
#, c-format
msgid "Error converting the call result: %s"
msgstr "Не вдалося перетворити результат виклику: %s"
@@ -1091,9 +2381,6 @@ msgstr "E158: Некоректна назва буфера: %s"
msgid "Invalid channel stream \"%s\""
msgstr "Некоректний потік завдання «%s»"
-msgid "E785: complete() can only be used in Insert mode"
-msgstr "E785: complete() можна вживати тільки в режимі вставляння"
-
msgid "&Ok"
msgstr "&O:Гаразд"
@@ -1112,6 +2399,9 @@ msgstr "аргумент flatten()"
msgid "extend() argument"
msgstr "аргумент extend()"
+msgid "extendnew() argument"
+msgstr "аргумент extendnew()"
+
msgid "E5000: Cannot find tab number."
msgstr "E5000: Не можна знайти номер вкладки."
@@ -1174,10 +2464,6 @@ msgid "E5010: List item %d of the second argument is not a string"
msgstr "E5010: Елемент списку %d другого аргументу не текст"
#, c-format
-msgid "E927: Invalid action: '%s'"
-msgstr "E927: Неправильна дія: «%s»"
-
-#, c-format
msgid "E962: Invalid action: '%s'"
msgstr "E962: Неправильна дія: «%s»"
@@ -1185,18 +2471,6 @@ msgstr "E962: Неправильна дія: «%s»"
msgid "connection failed: %s"
msgstr "з’єднання не вдалося: %s"
-msgid "sort() argument"
-msgstr "аргумент sort()"
-
-msgid "uniq() argument"
-msgstr "аргумент uniq()"
-
-msgid "E702: Sort compare function failed"
-msgstr "E702: Помилка у функції порівняння"
-
-msgid "E882: Uniq compare function failed"
-msgstr "E882: Помилка у функції порівняння uniq"
-
#, c-format
msgid "E6100: \"%s\" is not a valid stdpath"
msgstr "E6100: \"%s\" — некоректний stdpath"
@@ -1204,15 +2478,11 @@ msgstr "E6100: \"%s\" — некоректний stdpath"
msgid "(Invalid)"
msgstr "(Неможливо)"
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: неправильний номер групи співпадіння: %d"
-
msgid "Can only call this function in an unmodified buffer"
msgstr "Цю функцію можна викликати тільки у незміненому буфері"
msgid "writefile() first argument must be a List or a Blob"
-msgstr "перший аргумент writefile() має бути List або Blob"
+msgstr "Перший аргумент writefile() повинен бути List чи Blob"
#, c-format
msgid "E5060: Unknown flag: %s"
@@ -1229,17 +2499,89 @@ msgstr "E482: Не вдалося відкрити файл %s для запис
msgid "E80: Error when closing file %s: %s"
msgstr "E80: Помилка при закритті файлу %s: %s"
+msgid "E743: Variable nested too deep for (un)lock"
+msgstr "E743: Змінна має забагато вкладень щоб бути за-/відкритою."
+
+msgid "E908: Using an invalid value as a String"
+msgstr "E908: Некоректне значення вжито як String"
+
+#, c-format
+msgid "E1174: String required for argument %d"
+msgstr "E1174: У аргументі %d потрібен String"
+
+#, c-format
+msgid "E1175: Non-empty string required for argument %d"
+msgstr "E1175: У аргументі %d потрібен непорожній текстовий рядок"
+
+#, c-format
+msgid "E1206: Dictionary required for argument %d"
+msgstr "E1206: Потрібен Dictionary в аргументі %d"
+
+#, c-format
+msgid "E1210: Number required for argument %d"
+msgstr "E1210: Потрібне число у аргументі %d"
+
+#, c-format
+msgid "E1211: List required for argument %d"
+msgstr "E1211: Потрібен список в аргументі %d"
+
+#, c-format
+msgid "E1212: Bool required for argument %d"
+msgstr "E1212: Потрібен Bool в аргументі %d"
+
+#, c-format
+msgid "E1219: Float or Number required for argument %d"
+msgstr "E1219: Потрібен Float або Number в аргументі %d"
+
+#, c-format
+msgid "E1220: String or Number required for argument %d"
+msgstr "E1220: Потрібен String або Number в аргументі %d"
+
+#, c-format
+msgid "E1222: String or List required for argument %d"
+msgstr "E1222: Потрібен String або List в аргументі %d"
+
#, c-format
-msgid "E5142: Failed to open file %s: %s"
-msgstr "E5142: Не вдалося відкрити файл %s: %s"
+msgid "E1226: List or Blob required for argument %d"
+msgstr "E1226: Потрібен List або Blob в аргументі %d"
#, c-format
-msgid "E5143: Failed to write to file %s: %s"
-msgstr "E5143: Не вдалося записати у файл %s: %s"
+msgid "E1238: Blob required for argument %d"
+msgstr "E1238: У аргументі %d потрібен Blob"
#, c-format
-msgid "E5144: Failed to close file %s: %s"
-msgstr "E5144: Не вдалося закрити файл %s: %s"
+msgid "E1239: Invalid value for blob: %d"
+msgstr "E1239: Некоректне значення для blob: %d"
+
+#, c-format
+msgid "E1252: String, List or Blob required for argument %d"
+msgstr "E1252: String, List або Blob потрібен в аргументі %d"
+
+#, c-format
+msgid "E1256: String or function required for argument %d"
+msgstr "E1256: Потрібен текстовий рядок або функція в аргументі %d"
+
+#, c-format
+msgid "E1297: Non-NULL Dictionary required for argument %d"
+msgstr "E1297: Для аргументу %d потрібен не-NULL словник"
+
+msgid "E710: List value has more items than target"
+msgstr "E710: Список має більше елементів, ніж ціль"
+
+msgid "E711: List value has not enough items"
+msgstr "E711: Список має недостатньо елементів"
+
+msgid "E702: Sort compare function failed"
+msgstr "E702: Помилка у функції порівняння"
+
+msgid "E882: Uniq compare function failed"
+msgstr "E882: Помилка у функції порівняння uniq"
+
+msgid "sort() argument"
+msgstr "аргумент sort()"
+
+msgid "uniq() argument"
+msgstr "аргумент uniq()"
msgid "E6000: Argument is not a function or function name"
msgstr "E6000: Аргумент не функція чи назва функції"
@@ -1248,8 +2590,8 @@ msgstr "E6000: Аргумент не функція чи назва функці
msgid "E737: Key already exists: %s"
msgstr "E737: Ключ вже існує: %s"
-msgid "E743: variable nested too deep for (un)lock"
-msgstr "E743: Змінна має забагато вкладень щоб бути за-/відкритою."
+msgid "E972: Blob value does not have the right number of bytes"
+msgstr "E972: Неправильна кількість байтів у значенні Blob"
#, c-format
msgid "E741: Value is locked: %.*s"
@@ -1298,18 +2640,15 @@ msgstr "E974: Blob вжито як Number"
msgid "E685: using an invalid value as a Number"
msgstr "E685: некоректне значення вжито як Number"
-msgid "E730: using List as a String"
+msgid "E730: Using a List as a String"
msgstr "E730: List вжито як String"
-msgid "E731: using Dictionary as a String"
+msgid "E731: Using a Dictionary as a String"
msgstr "E731: Dictionary вжито як String"
-msgid "E976: using Blob as a String"
+msgid "E976: Using a Blob as a String"
msgstr "E976: Blob вжито як String"
-msgid "E908: using an invalid value as a String"
-msgstr "E908: некоректне значення вжито як String"
-
msgid "E891: Using a Funcref as a Float"
msgstr "E891: Funcref вжито як Float"
@@ -1323,10 +2662,10 @@ msgid "E894: Using a Dictionary as a Float"
msgstr "E894: Dictionary вжито як Float"
msgid "E362: Using a boolean value as a Float"
-msgstr "E362: Використано логічне значення як Float"
+msgstr "E362: Логічне значення вжито як Float"
msgid "E907: Using a special value as a Float"
-msgstr "E907: Використано спеціальне значення як Float"
+msgstr "E907: Спеціальне значення вжито як Float"
msgid "E975: Using a Blob as a Float"
msgstr "E975: Blob вжито як Float"
@@ -1335,6 +2674,10 @@ msgid "E808: Number or Float required"
msgstr "E808: Треба вказати Number чи Float"
#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Невідома функція: %s"
+
+#, c-format
msgid "E122: Function %s already exists, add ! to replace it"
msgstr "E122: Функція %s уже існує, ! щоб замінити"
@@ -1348,6 +2691,23 @@ msgstr "E718: Треба посилання на функцію"
msgid "E130: Unknown function: %s"
msgstr "E130: Невідома функція: %s"
+msgid "E454: Function list was modified"
+msgstr "E454: Список функцій змінився"
+
+msgid "E1058: Function nesting too deep"
+msgstr "E1058: Забагато вкладених функцій"
+
+#, c-format
+msgid "E1068: No white space allowed before '%s': %s"
+msgstr "E1068: Пробіл не дозволено перед «%s»: %s"
+
+#, c-format
+msgid "E1145: Missing heredoc end marker: %s"
+msgstr "E1145: Бракує кінцевої позначки heredoc: %s"
+
+msgid "E1300: Cannot use a partial with dictionary for :defer"
+msgstr "E1300: Неможливо вжити часткову функцію зі словником у :defer"
+
#, c-format
msgid "E125: Illegal argument: %s"
msgstr "E125: Недозволений аргумент: %s"
@@ -1357,7 +2717,13 @@ msgid "E853: Duplicate argument name: %s"
msgstr "E853: Назва аргументу повторюється: %s"
msgid "E989: Non-default argument follows default argument"
-msgstr "E989: Аргумент без домовленого значення після аргументу з домовленим значенням"
+msgstr ""
+"E989: Аргумент без домовленого значення після аргументу з домовленим "
+"значенням"
+
+#, c-format
+msgid "E451: Expected }: %s"
+msgstr "E451: Очікується }: %s"
#, c-format
msgid "E740: Too many arguments for function %s"
@@ -1394,20 +2760,12 @@ msgid "E699: Too many arguments"
msgstr "E699: Забагато аргументів"
#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Невідома функція: %s"
-
-#, c-format
msgid "E276: Cannot use function as a method: %s"
msgstr "E276: Не можна вжити функцію як метод: %s"
#, c-format
msgid "E933: Function was deleted: %s"
-msgstr "E933: Функцію було видалено: %s"
-
-#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Замало аргументів для функції %s"
+msgstr "E933: Функцію видалено: %s"
#, c-format
msgid "E120: Using <SID> not in a script context: %s"
@@ -1422,11 +2780,11 @@ msgstr "E129: Не вказано назву функції"
#, c-format
msgid "E128: Function name must start with a capital or \"s:\": %s"
-msgstr "E128: Назва функції має починатися з великої літери або \"s:\": %s"
+msgstr "E128: Назва функції має починатися з великої літери або «s:»: %s"
#, c-format
msgid "E884: Function name cannot contain a colon: %s"
-msgstr "E884: Назва функції не може містити двокрапку: %s"
+msgstr "E884: Назва функції не може мати двокрапку: %s"
#, c-format
msgid "E123: Undefined function: %s"
@@ -1441,7 +2799,7 @@ msgstr "E862: Тут не можна використати g:"
#, c-format
msgid "E932: Closure function should not be at top level: %s"
-msgstr "E932: Функція замикання не може бути на верхньому рівні: %s"
+msgstr "E932: Функція замикання не повинна бути на верхньому рівні: %s"
msgid "E126: Missing :endfunction"
msgstr "E126: Бракує :endfunction"
@@ -1473,6 +2831,83 @@ msgstr "Не вдалося знищити функцію %s: Вона вико
msgid "E133: :return not inside a function"
msgstr "E133: :return поза межами функції"
+msgid "E18: Unexpected characters in :let"
+msgstr "E18: Неочікувані символи у :let"
+
+#, c-format
+msgid "E940: Cannot lock or unlock variable %s"
+msgstr "E940: Неможливо заблокувати чи розблокувати змінну %s"
+
+#, c-format
+msgid "E963: Setting %s to value with wrong type"
+msgstr "E963: Встановлення значення з неправильним типом у %s"
+
+msgid "E991: Cannot use =<< here"
+msgstr "E991: Тут не можна застосувати =<<"
+
+msgid "E221: Marker cannot start with lower case letter"
+msgstr "E221: Позначка не може починатися із малої літери"
+
+msgid "E172: Missing marker"
+msgstr "E172: Бракує позначки"
+
+#, c-format
+msgid "E990: Missing end marker '%s'"
+msgstr "E990: Бракує позначки кінця «%s»"
+
+msgid "E687: Less targets than List items"
+msgstr "E687: Цілей менше, ніж елементів списку"
+
+msgid "E688: More targets than List items"
+msgstr "E688: Цілей більше, ніж елементів списку"
+
+msgid "E452: Double ; in list of variables"
+msgstr "E452: Друга ; у списку змінних"
+
+#, c-format
+msgid "E738: Can't list variables for %s"
+msgstr "E738: Не можна перерахувати змінні у %s"
+
+msgid "E996: Cannot lock an environment variable"
+msgstr "E996: Неможливо заблокувати змінну оточення"
+
+msgid "E996: Cannot lock an option"
+msgstr "E996: Неможливо заблокувати опцію"
+
+msgid "E996: Cannot lock a register"
+msgstr "E996: Неможливо заблокувати регістр"
+
+#, c-format
+msgid "E108: No such variable: \"%s\""
+msgstr "E108: Змінної немає: «%s»"
+
+#, c-format
+msgid "E794: Cannot set variable in the sandbox: \"%.*s\""
+msgstr "E794: Не можна встановити змінну у пісочниці: «%.*s»"
+
+#, c-format
+msgid "E1122: Variable is locked: %*s"
+msgstr "E1122: Змінна захищена: %*s"
+
+#, c-format
+msgid "E795: Cannot delete variable %.*s"
+msgstr "E795: Не можна знищити змінну %.*s"
+
+#, c-format
+msgid "E704: Funcref variable name must start with a capital: %s"
+msgstr "E704: Назва змінної Funcref має починатися з великої літери: %s"
+
+#, c-format
+msgid "E705: Variable name conflicts with existing function: %s"
+msgstr "E705: Назва змінної співпадає з наявною функцією: %s"
+
+#, c-format
+msgid "E521: Number required: &%s = '%s'"
+msgstr "E521: Потрібно вказати Number: &%s = '%s'"
+
+msgid "E1308: Cannot resize a window in another tab page"
+msgstr "E1308: Неможливо змінити розмір вікна у іншій вкладці"
+
msgid "tcp address must be host:port"
msgstr "адреса tcp має бути вузол:порт"
@@ -1482,9 +2917,12 @@ msgstr "не вдалося знайти вузол чи порт"
msgid "connection refused"
msgstr "з'єднання відмовлено"
+msgid "E144: Non-numeric argument to :z"
+msgstr "E144: Не числовий аргумент у :z"
+
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Oct %03o, Digr %s"
-msgstr "<%s>%s%s %d, шіст %02x, віс %03o, дигр %s"
+msgstr "<%s>%s%s %d, шіст %02x, віс %03o, дигр %s"
#, c-format
msgid "<%s>%s%s %d, Hex %02x, Octal %03o"
@@ -1510,6 +2948,13 @@ msgid "E134: Cannot move a range of lines into itself"
msgstr "E134: Неможливо перемістити діапазон рядків сам у себе"
#, c-format
+msgid "%<PRId64> line moved"
+msgid_plural "%<PRId64> lines moved"
+msgstr[0] "%<PRId64> рядок переміщено"
+msgstr[1] "%<PRId64> рядки переміщено"
+msgstr[2] "%<PRId64> рядків переміщено"
+
+#, c-format
msgid "E482: Can't create file %s"
msgstr "E482: Не вдалося створити файл %s"
@@ -1523,6 +2968,10 @@ msgstr "E135: Автокоманди *Filter* не повинні змінюва
msgid "[No write since last change]\n"
msgstr "[Зміни не записано]\n"
+#, c-format
+msgid "E503: \"%s\" is not a file or writable device"
+msgstr "E503: «%s» не файл або не пристрій для запису"
+
msgid "Write partial file?"
msgstr "Записати частину файлу?"
@@ -1574,9 +3023,6 @@ msgstr "E505: «%s» тільки для читання (! щоб не зваж
msgid "E143: Autocommands unexpectedly deleted new buffer %s"
msgstr "E143: Автокоманди несподівано знищили новий буфер %s"
-msgid "E144: non-numeric argument to :z"
-msgstr "E144: нечисловий аргумент для :z"
-
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: Регулярні вирази не можна розділяти літерами"
@@ -1587,64 +3033,54 @@ msgstr "Замінити на %s (y/n/a/q/l/^E/^Y)?"
msgid "(Interrupted) "
msgstr "(Перервано) "
-msgid "E147: Cannot do :global recursive with a range"
-msgstr "E147: :global не можна рекурсивно з діапазоном"
-
-msgid "E148: Regular expression missing from global"
-msgstr "E148: У global бракує зразка"
-
-#, c-format
-msgid "Pattern found in every line: %s"
-msgstr "Зразок знайдено у кожному рядку: %s"
-
-#, c-format
-msgid "Pattern not found: %s"
-msgstr "Зразок не знайдено: %s"
-
-msgid "E478: Don't panic!"
-msgstr "E478: Без паніки!"
-
-#, c-format
-msgid "E661: Sorry, no '%s' help for %s"
-msgstr "E661: Вибачте, немає допомоги '%s' для %s"
-
#, c-format
-msgid "E149: Sorry, no help for %s"
-msgstr "E149: Вибачте, немає допомоги для %s"
+msgid "%<PRId64> match on %<PRId64> line"
+msgid_plural "%<PRId64> matches on %<PRId64> line"
+msgstr[0] "%<PRId64> збіг у %<PRId64> ряд."
+msgstr[1] "%<PRId64> збіги у %<PRId64> ряд."
+msgstr[2] "%<PRId64> збігів у %<PRId64> ряд."
#, c-format
-msgid "Sorry, help file \"%s\" not found"
-msgstr "Вибачте, файл допомоги «%s» не знайдено"
+msgid "%<PRId64> substitution on %<PRId64> line"
+msgid_plural "%<PRId64> substitutions on %<PRId64> line"
+msgstr[0] "%<PRId64> заміна у %<PRId64> ряд."
+msgstr[1] "%<PRId64> заміни у %<PRId64> ряд."
+msgstr[2] "%<PRId64> замін у %<PRId64> ряд."
#, c-format
-msgid "E151: No match: %s"
-msgstr "E151: Жодного збігу: %s"
+msgid "%<PRId64> match on %<PRId64> lines"
+msgid_plural "%<PRId64> matches on %<PRId64> lines"
+msgstr[0] "%<PRId64> збіг у %<PRId64> ряд."
+msgstr[1] "%<PRId64> збіги у %<PRId64> ряд."
+msgstr[2] "%<PRId64> збігів у %<PRId64> ряд."
#, c-format
-msgid "E152: Cannot open %s for writing"
-msgstr "E152: Не вдалося відкрити %s для запису"
+msgid "%<PRId64> substitution on %<PRId64> lines"
+msgid_plural "%<PRId64> substitutions on %<PRId64> lines"
+msgstr[0] "%<PRId64> заміна у %<PRId64> ряд."
+msgstr[1] "%<PRId64> заміни у %<PRId64> ряд."
+msgstr[2] "%<PRId64> замін у %<PRId64> ряд."
-#, c-format
-msgid "E153: Unable to open %s for reading"
-msgstr "E153: Не вдалося відкрити %s для читання"
+msgid "E147: Cannot do :global recursive with a range"
+msgstr "E147: :global не можна застосовувати рекурсивно з діапазоном"
-#, c-format
-msgid "E670: Mix of help file encodings within a language: %s"
-msgstr "E670: Мішанина кодувань файлу допомоги для мови %s"
+msgid "E148: Regular expression missing from global"
+msgstr "E148: У global бракує зразка"
#, c-format
-msgid "E154: Duplicate tag \"%s\" in file %s/%s"
-msgstr "E154: Повторення мітки «%s» у файлі %s/%s"
+msgid "Pattern found in every line: %s"
+msgstr "Зразок знайдено у кожному рядку: %s"
#, c-format
-msgid "E150: Not a directory: %s"
-msgstr "E150: Не є каталогом: %s"
+msgid "Pattern not found: %s"
+msgstr "Зразок не знайдено: %s"
msgid "No old files"
msgstr "Жодного старого файлу"
-msgid "E750: First use \":profile start {fname}\""
-msgstr "E750: Спочатку зробіть «:profile start {файл}»"
+#, c-format
+msgid "E666: Compiler not supported: %s"
+msgstr "E666: Компілятор не підтримується: %s"
#, c-format
msgid "Save changes to \"%s\"?"
@@ -1656,7 +3092,7 @@ msgstr "Закрити «%s»?"
#, c-format
msgid "E947: Job still running in buffer \"%s\""
-msgstr "E947: Задача все ще запущена у буфері «%s»"
+msgstr "E947: Завдання вже ще виконується у буфері «%s»"
#, c-format
msgid "E162: No write since last change for buffer \"%s\""
@@ -1666,102 +3102,35 @@ msgid "Warning: Entered other buffer unexpectedly (check autocommands)"
msgstr ""
"Обережно: Несподівано опинилися у іншому буфері (перевірте автокоманди)"
-msgid "E163: There is only one file to edit"
-msgstr "E163: Редагується лише один файл"
-
-msgid "E164: Cannot go before first file"
-msgstr "E164: Це вже найперший файл"
-
-msgid "E165: Cannot go beyond last file"
-msgstr "E165: Це вже останній файл"
-
-msgid "E610: No argument to delete"
-msgstr "E610: Немає аргументів для знищення"
-
-#, c-format
-msgid "E666: compiler not supported: %s"
-msgstr "E666: Компілятор не підтримується: %s"
-
-#, c-format
-msgid "Cannot source a directory: \"%s\""
-msgstr "Не вдалося прочитати каталог: «%s»"
-
-#, c-format
-msgid "could not source \"%s\""
-msgstr "Не вдалося виконати «%s»"
-
-#, c-format
-msgid "line %<PRId64>: could not source \"%s\""
-msgstr "рядок %<PRId64>: не вдалося виконати «%s»"
-
-#, c-format
-msgid "sourcing \"%s\""
-msgstr "виконується «%s»"
-
-#, c-format
-msgid "line %<PRId64>: sourcing \"%s\""
-msgstr "рядок %<PRId64>: виконується «%s»"
-
-#, c-format
-msgid "finished sourcing %s"
-msgstr "закінчено виконання %s"
-
-msgid "modeline"
-msgstr "modeline"
-
-msgid "--cmd argument"
-msgstr "--cmd аргумент"
-
-msgid "-c argument"
-msgstr "-c аргумент"
-
-msgid "environment variable"
-msgstr "змінна оточення"
-
-msgid "error handler"
-msgstr "обробник помилки"
-
-msgid "changed window size"
-msgstr "змінено розмір вікна"
-
-msgid "Lua"
-msgstr "Lua"
-
-#, c-format
-msgid "API client (channel id %<PRIu64>)"
-msgstr "Клієнт API (канал «%<PRIu64>»)"
+msgid "E464: Ambiguous use of user-defined command"
+msgstr "E464: Неоднозначний вжиток команди користувача"
-msgid "anonymous :source"
-msgstr "анонімний :source"
+msgid "E489: No call stack to substitute for \"<stack>\""
+msgstr "E489: Немає стеку викликів для заміни «<stack>»"
-#, c-format
-msgid "anonymous :source (script id %d)"
-msgstr "анонімний :source (ід. скрипта %d)"
+msgid "E492: Not an editor command"
+msgstr "E492: Це не команда редактора"
-msgid "W15: Warning: Wrong line separator, ^M may be missing"
-msgstr "W15: Застереження: Неправильний роздільник рядків, можливо, бракує ^M"
+msgid "E495: No autocommand file name to substitute for \"<afile>\""
+msgstr "E495: Немає назви файлу автокоманди для заміни «<afile>»"
-msgid "E167: :scriptencoding used outside of a sourced file"
-msgstr "E167: :scriptencoding використано поза виконуваним файлом"
+msgid "E496: No autocommand buffer number to substitute for \"<abuf>\""
+msgstr "E496: Немає номера буфера автокоманди для заміни «<abuf>»"
-msgid "E168: :finish used outside of a sourced file"
-msgstr "E168: :finish використано поза виконуваним файлом"
+msgid "E497: No autocommand match name to substitute for \"<amatch>\""
+msgstr "E497: Немає назви збігу автокоманди для заміни «<amatch>»"
-#, c-format
-msgid "Current %slanguage: \"%s\""
-msgstr "Мова (%s): «%s»"
+msgid "E498: No :source file name to substitute for \"<sfile>\""
+msgstr "E498: Немає назви файлу :source для заміни «<sfile>»"
-#, c-format
-msgid "E197: Cannot set language to \"%s\""
-msgstr "E197: Не вдалося встановити мову «%s»"
+msgid "E842: No line number to use for \"<slnum>\""
+msgstr "E842: Немає номера рядка, щоб використати з «<sfile>»"
-#, c-format
-msgid "E184: No such user-defined command: %s"
-msgstr "E184: Команду користувача не знайдено: %s"
+msgid "E961: No line number to use for \"<sflnum>\""
+msgstr "E961: Немає номера рядка, щоб використати з «<sflnum>»"
-#, c-format
-msgid "E1237: No such user-defined command in current buffer: %s"
-msgstr "E1237: Немає такої команди користувача у цьому буфері: %s"
+msgid "E1274: No script file name to substitute for \"<script>\""
+msgstr "E1274: Немає назви файлу скрипту для заміни «<script>»"
msgid "Entering Ex mode. Type \"visual\" to go to Normal mode."
msgstr "Режим Ex. Для повернення до нормального режиму виконайте «visual»"
@@ -1776,24 +3145,21 @@ msgstr "Виконується: %s"
msgid "line %"
msgstr "рядок %"
-msgid "E169: Command too recursive"
-msgstr "E169: Команда занадто рекурсивна"
-
-#, c-format
-msgid "E605: Exception not caught: %s"
-msgstr "E605: Виняткова ситуація не оброблена: %s"
-
msgid "End of sourced file"
msgstr "Кінець виконуваного файлу"
msgid "End of function"
msgstr "Кінець функції"
-msgid "E464: Ambiguous use of user-defined command"
-msgstr "E464: Неоднозначний вжиток команди користувача"
+#, c-format
+msgid "E605: Exception not caught: %s"
+msgstr "E605: Виняткова ситуація не оброблена: %s"
-msgid "E492: Not an editor command"
-msgstr "E492: Це не команда редактора"
+msgid ""
+"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
+msgstr ""
+"ВНУТРІШНЄ: Не можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи "
+"ADDR_QUICKFIX"
msgid "E493: Backwards range given"
msgstr "E493: Інтервал задано навиворіт"
@@ -1804,12 +3170,6 @@ msgstr "Інтервал задано навиворіт, щоб помінят
msgid "E494: Use w or w>>"
msgstr "E494: Спробуйте w або w>>"
-msgid ""
-"INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX"
-msgstr ""
-"ВНУТРІШНЄ: Не можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи "
-"ADDR_QUICKFIX"
-
msgid "E943: Command table needs to be updated, run 'make'"
msgstr "E943: Потрібно поновити таблицю команд, запустіть 'make'"
@@ -1817,67 +3177,18 @@ msgid "E319: The command is not available in this version"
msgstr "E319: Вибачте, цієї команди немає у цій версії"
#, c-format
-msgid "E174: Command already exists: add ! to replace it: %s"
-msgstr "E174: Команда вже існує, ! щоб замінити її: %s"
-
-msgid ""
-"\n"
-" Name Args Address Complete Definition"
-msgstr ""
-"\n"
-" Назва Арг. Адреса Доповнення Визначення"
-
-msgid "No user-defined commands found"
-msgstr "Не знайдено команд користувача"
-
-msgid "E175: No attribute specified"
-msgstr "E175: Не вказано атрибутів"
-
-msgid "E176: Invalid number of arguments"
-msgstr "E176: Неправильна кількість аргументів"
-
-msgid "E177: Count cannot be specified twice"
-msgstr "E177: Лічильник не може бути вказано двічі"
-
-msgid "E178: Invalid default value for count"
-msgstr "E178: Неправильне початкове значення лічильника"
-
-msgid "E179: argument required for -complete"
-msgstr "E179: для -complete потрібний аргумент"
-
-msgid "E179: argument required for -addr"
-msgstr "E179: для -addr потрібний аргумент"
+msgid "%d more file to edit. Quit anyway?"
+msgid_plural "%d more files to edit. Quit anyway?"
+msgstr[0] "Ще редагувати %d файл. Вийти все одно?"
+msgstr[1] "Ще редагувати %d файли. Вийти все одно?"
+msgstr[2] "Ще редагувати %d файлів. Вийти все одно?"
#, c-format
-msgid "E181: Invalid attribute: %s"
-msgstr "E181: Неправильний атрибут: %s"
-
-msgid "E1208: -complete used without -nargs"
-msgstr "E1208: -complete вжито без without -nargs"
-
-msgid "E182: Invalid command name"
-msgstr "E182: Неправильна назва команди"
-
-msgid "E183: User defined commands must start with an uppercase letter"
-msgstr "E183: Команди користувача повинні починатися з великої літери"
-
-msgid "E841: Reserved name, cannot be used for user defined command"
-msgstr ""
-"E841: Зарезервована назва, не можна використати для користувацької команди"
-
-#, c-format
-msgid "E180: Invalid address type value: %s"
-msgstr "E180: Неправильне значення типу адреси: %s"
-
-#, c-format
-msgid "E180: Invalid complete value: %s"
-msgstr "E180: Неправильне доповнення: %s"
-
-msgid "E468: Completion argument only allowed for custom completion"
-msgstr "E468: Аргумент дозволений тільки для користувацького доповнення"
-
-msgid "E467: Custom completion requires a function argument"
-msgstr "E467: Користувацьке доповнення вимагає аргумент-функцію"
+msgid "E173: %<PRId64> more file to edit"
+msgid_plural "E173: %<PRId64> more files to edit"
+msgstr[0] "E173: Ще редагувати %<PRId64> файл"
+msgstr[1] "E173: Ще редагувати %<PRId64> файли"
+msgstr[2] "E173: Ще редагувати %<PRId64> файлів"
#, c-format
msgid "E185: Cannot find color scheme '%s'"
@@ -1928,24 +3239,6 @@ msgstr "E192: Забагато вкладених :normal"
msgid "E194: No alternate file name to substitute for '#'"
msgstr "E194: Немає назви вторинного файлу для заміни '#'"
-msgid "E495: no autocommand file name to substitute for \"<afile>\""
-msgstr "E495: Немає назви файлу автокоманди для заміни «<afile>»"
-
-msgid "E496: no autocommand buffer number to substitute for \"<abuf>\""
-msgstr "E496: Немає номера буфера автокоманди для заміни «<abuf>»"
-
-msgid "E497: no autocommand match name to substitute for \"<amatch>\""
-msgstr "E497: Немає назви збігу автокоманди для заміни «<amatch>»"
-
-msgid "E498: no :source file name to substitute for \"<sfile>\""
-msgstr "E498: Немає назви файлу :source для заміни «<sfile>»"
-
-msgid "E842: no line number to use for \"<slnum>\""
-msgstr "E842: немає номера рядка, щоб використати з «<sfile>»"
-
-msgid "E961: no line number to use for \"<sflnum>\""
-msgstr "E961: немає номера рядка, щоб використати з «<sflnum>»"
-
#, no-c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: Назва файлу для '%' чи '#' порожня, працює лише з «:p:h»"
@@ -1956,6 +3249,12 @@ msgstr "E500: Результат — порожній рядок"
msgid "Untitled"
msgstr "Неназваний"
+msgid "E583: Multiple :else"
+msgstr "E583: Не одне :else"
+
+msgid "E607: Multiple :finally"
+msgstr "E607: Не одне :finally"
+
msgid "E608: Cannot :throw exceptions with 'Vim' prefix"
msgstr "E608: Не можна викидати (:throw) винятки з префіксом 'Vim'"
@@ -1964,14 +3263,14 @@ msgid "Exception thrown: %s"
msgstr "Виняткова ситуація: %s"
#, c-format
-msgid "Exception finished: %s"
-msgstr "Виняток закінчено: %s"
-
-#, c-format
msgid "Exception discarded: %s"
msgstr "Виняток скинуто: %s"
#, c-format
+msgid "Exception finished: %s"
+msgstr "Виняток закінчено: %s"
+
+#, c-format
msgid "%s, line %<PRId64>"
msgstr "%s, рядок %<PRId64>"
@@ -2015,9 +3314,6 @@ msgstr "E581: :else без :if"
msgid "E582: :elseif without :if"
msgstr "E582: :elseif без :if"
-msgid "E583: multiple :else"
-msgstr "E583: Не одне :else"
-
msgid "E584: :elseif after :else"
msgstr "E584: :elseif після :else"
@@ -2048,18 +3344,9 @@ msgstr "E604: :catch після :finally"
msgid "E606: :finally without :try"
msgstr "E606: :finally без :try"
-msgid "E607: multiple :finally"
-msgstr "E607: Не одне :finally"
-
msgid "E602: :endtry without :try"
msgstr "E602: :entry без :try"
-msgid "E193: :endfunction not inside a function"
-msgstr "E193: :endfunction поза межами функції"
-
-msgid "E788: Not allowed to edit another buffer now"
-msgstr "E788: Зараз не можна редагувати інший буфер"
-
msgid "E811: Not allowed to change buffer information now"
msgstr "E811: Зараз не можна змінювати інформацію буфера"
@@ -2094,23 +3381,14 @@ msgstr "E5404: Кінець шматка %i %"
msgid "E5406: Chunk %i end %"
msgstr "E5406: Кінець шматка %i %"
-msgid "tagname"
-msgstr "назва теґу"
-
-msgid " kind file\n"
-msgstr " тип файлу\n"
-
-msgid "'history' option is zero"
-msgstr "Опція 'history' порожня"
-
msgid "[Command Line]"
msgstr "[Рядок Команд]"
msgid "E199: Active window or buffer deleted"
msgstr "E199: Активне вікно або буфер було знищено"
-msgid "E854: path too long for completion"
-msgstr "E854: шлях занадто довгий для доповнення"
+msgid "E854: Path too long for completion"
+msgstr "E854: Шлях задовгий для доповнення"
#, c-format
msgid ""
@@ -2139,9 +3417,6 @@ msgstr "E347: У шляху пошуку більше немає файлів «
msgid "E812: Autocommands changed buffer or buffer name"
msgstr "E812: Автокоманди змінили буфер чи його назву"
-msgid "is a directory"
-msgstr "каталог"
-
msgid "Illegal file name"
msgstr "Недозволена назва файлу"
@@ -2170,7 +3445,7 @@ msgid "[fifo]"
msgstr "[канал]"
msgid "[socket]"
-msgstr "[сокет]"
+msgstr "[розетка]"
msgid "[character special]"
msgstr "[спец. символьний]"
@@ -2181,12 +3456,6 @@ msgstr "[Бракує CR]"
msgid "[long lines split]"
msgstr "[довгі рядки розбито]"
-msgid "[NOT converted]"
-msgstr "[НЕ конвертовано]"
-
-msgid "[converted]"
-msgstr "[конвертовано]"
-
#, c-format
msgid "[CONVERSION ERROR in line %<PRId64>]"
msgstr "[ПОМИЛКА КОНВЕРТАЦІЇ у рядку %<PRId64>]"
@@ -2213,101 +3482,6 @@ msgstr "[Новий файл]"
msgid "[New]"
msgstr "[Новий]"
-msgid "E676: No matching autocommands for acwrite buffer"
-msgstr "E676: Немає відповідних автокоманд"
-
-msgid "E203: Autocommands deleted or unloaded buffer to be written"
-msgstr "E203: Автокоманда знищила або вивантажила буфер, що мав бути записаний"
-
-msgid "E204: Autocommand changed number of lines in unexpected way"
-msgstr "E204: Автокоманда несподіваним чином змінила кількість рядків"
-
-msgid "is not a file or writable device"
-msgstr "Не придатний для запису"
-
-msgid "is read-only (add ! to override)"
-msgstr "лише для читання (! щоб не зважати)"
-
-#, c-format
-msgid "E303: Unable to create directory \"%s\" for backup file: %s"
-msgstr "E303: Не вдалося створити каталог «%s» для резервного файлу: %s"
-
-msgid "E506: Can't write to backup file (add ! to override)"
-msgstr "E506: Не вдалося записати резервний файл (! щоб не зважати)"
-
-msgid "E509: Cannot create backup file (add ! to override)"
-msgstr "E509: Не вдалося створити резервну копію (! щоб не зважати)"
-
-msgid "E510: Can't make backup file (add ! to override)"
-msgstr "E510: Не вдалося зробити резервну копію (! щоб не зважати)"
-
-msgid "E214: Can't find temp file for writing"
-msgstr "E214: Не вдалося підшукати тимчасовий файл для запису"
-
-msgid "E213: Cannot convert (add ! to write without conversion)"
-msgstr "E213: Не вдалося перетворити (! щоб записати без конвертації)"
-
-msgid "E166: Can't open linked file for writing"
-msgstr "E166: Не вдалося відкрити для запису зв'язаний файл"
-
-#, c-format
-msgid "E212: Can't open file for writing: %s"
-msgstr "E212: Не вдалося відкрити файл для запису: %s"
-
-#, c-format
-msgid "E512: Close failed: %s"
-msgstr "E512: Не вдалося закрити: %s"
-
-msgid "E513: write error, conversion failed (make 'fenc' empty to override)"
-msgstr "E513: Помилка запису, конвертація не вдалася (скиньте 'fenc')"
-
-msgid "E513: write error, conversion failed in line %"
-msgstr "E513: Помилка запису, перетворення не вдалася у рядку %"
-
-msgid "E514: write error (file system full?)"
-msgstr "E514: Помилка запису (скінчилось вільне місце?)"
-
-msgid " CONVERSION ERROR"
-msgstr " ПОМИЛКА КОНВЕРТАЦІЇ"
-
-#, c-format
-msgid " in line %<PRId64>;"
-msgstr " у рядку %<PRId64>;"
-
-msgid "[Device]"
-msgstr "[Пристрій]"
-
-msgid " [a]"
-msgstr "[д]"
-
-msgid " appended"
-msgstr " дописаний"
-
-msgid " [w]"
-msgstr "[з]"
-
-msgid " written"
-msgstr " записаний"
-
-msgid "E205: Patchmode: can't save original file"
-msgstr "E205: Латання: не вдалося зберегти оригінал"
-
-msgid "E206: patchmode: can't touch empty original file"
-msgstr "E206: Латання: не вдалося створити оригінал"
-
-msgid "E207: Can't delete backup file"
-msgstr "E207: Не вдалося знищити резервний файл"
-
-msgid ""
-"\n"
-"WARNING: Original file may be lost or damaged\n"
-msgstr ""
-"\n"
-"ЗАСТЕРЕЖЕННЯ: Оригінал, мабуть, втрачений чи пошкоджений\n"
-
-msgid "don't quit the editor until the file is successfully written!"
-msgstr "Не виходьте з редактора, доки файл не записано!"
-
msgid "[dos format]"
msgstr "[формат dos]"
@@ -2326,18 +3500,26 @@ msgstr "[формат unix]"
msgid "[unix]"
msgstr "[unix]"
+#, c-format
+msgid "%<PRId64> line, "
+msgid_plural "%<PRId64> lines, "
+msgstr[0] "%<PRId64> рядок, "
+msgstr[1] "%<PRId64> рядки, "
+msgstr[2] "%<PRId64> рядків, "
+
+#, c-format
+msgid "%<PRId64> byte"
+msgid_plural "%<PRId64> bytes"
+msgstr[0] "%<PRId64> байт"
+msgstr[1] "%<PRId64> байти"
+msgstr[2] "%<PRId64> байтів"
+
msgid "[Incomplete last line]"
msgstr "[Неповний останній рядок]"
msgid "[noeol]"
msgstr "[noeol]"
-msgid "WARNING: The file has been changed since reading it!!!"
-msgstr "ЗАСТЕРЕЖЕННЯ: Файл змінився з часу останнього читання!!!"
-
-msgid "Do you really want to write to it"
-msgstr "Ви справді хочете його переписати??"
-
#, c-format
msgid "E208: Error writing to \"%s\""
msgstr "E208: Помилка запису у «%s»"
@@ -2419,36 +3601,31 @@ msgstr "E350: Не вдалося створити згортку методом
msgid "E351: Cannot delete fold with current 'foldmethod'"
msgstr "E351: Не вдалося знищити згортку методом 'foldmethod'"
-msgid "E222: Add to read buffer"
-msgstr "E222: Додати до буфера читання"
-
-msgid "E223: recursive mapping"
-msgstr "E223: Заміна клавіш рекурсивна"
-
-#, c-format
-msgid "E224: global abbreviation already exists for %s"
-msgstr "E224: Загальне скорочення для %s вже існує"
-
#, c-format
-msgid "E225: global mapping already exists for %s"
-msgstr "E225: Глобальна заміна клавіш для %s вже існує"
+msgid "+--%3ld line folded"
+msgid_plural "+--%3ld lines folded "
+msgstr[0] "+--%3ld рядок згорнуто "
+msgstr[1] "+--%3ld рядки згорнуто "
+msgstr[2] "+--%3ld рядків згорнуто "
#, c-format
-msgid "E226: abbreviation already exists for %s"
-msgstr "E226: Вже є скорочення для %s"
+msgid "+-%s%3ld line: "
+msgid_plural "+-%s%3ld lines: "
+msgstr[0] "+-%s%3ld рядок: "
+msgstr[1] "+-%s%3ld рядки: "
+msgstr[2] "+-%s%3ld рядків: "
-#, c-format
-msgid "E227: mapping already exists for %s"
-msgstr "E227: Вже є заміна клавіш для %s"
+msgid "E223: Recursive mapping"
+msgstr "E223: Заміна клавіш рекурсивна"
-msgid "No abbreviation found"
-msgstr "Скорочення не знайдено"
+msgid "E1255: <Cmd> mapping must end with <CR>"
+msgstr "E1255: Заміна клавіш <Cmd> має закінчуватися <CR>"
-msgid "No mapping found"
-msgstr "Заміни клавіш не знайдено"
+msgid "E1136: <Cmd> mapping must end with <CR> before second <Cmd>"
+msgstr "E1136: Заміна клавіш <Cmd> має завершитися <CR> перед другою <Cmd>"
-msgid "E228: makemap: Illegal mode"
-msgstr "E228: makemap: Неприпустимий режим"
+msgid "E222: Add to read buffer"
+msgstr "E222: Додати до буфера читання"
msgid "--No lines in buffer--"
msgstr "--Жодного рядка--"
@@ -2471,9 +3648,12 @@ msgstr "E10: За \\ має йти /, ? або &"
msgid "E11: Invalid in command-line window; <CR> executes, CTRL-C quits"
msgstr "E11: Неприпустимо у вікні команд, <CR> виконує, CTRL-C виходить"
-msgid "E12: Command not allowed from exrc/vimrc in current dir or tag search"
+msgid "E12: Command not allowed in secure mode in current dir or tag search"
msgstr ""
-"E12: Команда не дозволена у exrc/vimrc у пошуку поточного каталогу чи мітки"
+"E12: Команда не дозволена у exrc/vimrc у поточному пошуку каталогу чи мітки"
+
+msgid "E169: Command too recursive"
+msgstr "E169: Команда занадто рекурсивна"
msgid "E171: Missing :endif"
msgstr "E171: Бракує :endif"
@@ -2529,8 +3709,8 @@ msgid "E983: Duplicate argument: %s"
msgstr "E983: Аргумент повторюється: %s"
#, c-format
-msgid "E15: Invalid expression: %s"
-msgstr "E15: Неправильний вираз: %s"
+msgid "E15: Invalid expression: \"%s\""
+msgstr "E15: Неправильний вираз: «%s»"
msgid "E16: Invalid range"
msgstr "E16: Неправильні межі"
@@ -2706,6 +3886,17 @@ msgid "E45: 'readonly' option is set (add ! to override)"
msgstr "E45: Встановлено опцію 'readonly' (! щоб не зважати)"
#, c-format
+msgid "E734: Wrong variable type for %s="
+msgstr "E734: Неправильний тип змінної для %s="
+
+#, c-format
+msgid "E461: Illegal variable name: %s"
+msgstr "E461: Неприпустима назва змінної: %s"
+
+msgid "E995: Cannot modify existing variable"
+msgstr "E995: Неможливо змінити наявну змінну"
+
+#, c-format
msgid "E46: Cannot change read-only variable \"%.*s\""
msgstr "E46: Змінна тільки для читання: «%.*s»"
@@ -2727,11 +3918,15 @@ msgid "E118: Too many arguments for function: %s"
msgstr "E118: Забагато аргументів для функції: %s"
#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Замало аргументів для функції %s"
+
+#, c-format
msgid "E716: Key not present in Dictionary: \"%s\""
msgstr "E716: Немає такого ключа у словнику: «%s»"
msgid "E714: List required"
-msgstr "E714: Потрібен список"
+msgstr "E714: Потрібен List"
msgid "E897: List or Blob required"
msgstr "E897: Потрібен List або Blob"
@@ -2748,11 +3943,14 @@ msgid "E47: Error while reading errorfile"
msgstr "E47: Помилка читання файлу помилок"
msgid "E48: Not allowed in sandbox"
-msgstr "E48: На дозволено у пісочниці"
+msgstr "E48: Не дозволено у пісочниці"
msgid "E523: Not allowed here"
msgstr "E523: Не дозволено тут"
+msgid "E565: Not allowed to change text or change window"
+msgstr "E565: Не дозволено змінювати текст чи вікно"
+
msgid "E359: Screen mode setting not supported"
msgstr "E359: Режим екрану не підтримується"
@@ -2768,9 +3966,6 @@ msgstr "E255: Не можна зчитати дані напису!"
msgid "E72: Close error on swap file"
msgstr "E72: Помилка під час закриття файлу обміну"
-msgid "E73: tag stack empty"
-msgstr "E73: Стек міток порожній"
-
msgid "E74: Command too complex"
msgstr "E74: Занадто складна команда"
@@ -2815,16 +4010,17 @@ msgstr "E81: <SID> використовується не в контексті
msgid "E107: Missing parentheses: %s"
msgstr "E107: Пропущено дужки: %s"
-msgid "E363: pattern uses more memory than 'maxmempattern'"
-msgstr "E363: Зразок використовує більше, ніж 'maxmempattern', пам'яті"
-
-msgid "E749: empty buffer"
+msgid "E749: Empty buffer"
msgstr "E749: Порожній буфер"
#, c-format
msgid "E86: Buffer %<PRId64> does not exist"
msgstr "E86: Буфера %<PRId64> немає"
+#, c-format
+msgid "E193: %s not inside a function"
+msgstr "E193: %s поза межами функції"
+
msgid "E682: Invalid search pattern or delimiter"
msgstr "E682: Некоректний зразок для пошуку чи роздільник"
@@ -2845,6 +4041,9 @@ msgstr "E919: Каталог не знайдено у '%s': «%s»"
msgid "E952: Autocommand caused recursive behavior"
msgstr "E952: Автокоманди призвели до рекурсії"
+msgid "E328: Menu only exists in another mode"
+msgstr "E328: Меню може бути тільки в іншому режимі"
+
msgid "E813: Cannot close autocmd window"
msgstr "E813: Не вдалося закрити вікно autocmd"
@@ -2858,22 +4057,19 @@ msgstr "E519: Опція не підтримується"
msgid "E856: Filename too long"
msgstr "E856: Задовга назва файлу"
-msgid "E806: using Float as a String"
+msgid "E806: Using a Float as a String"
msgstr "E806: Float вжито як String"
-#, c-format
-msgid "E5500: autocmd has thrown an exception: %s"
-msgstr "E5500: автокоманда викинула виняткову ситуацію: %s"
-
-msgid "E5520: <Cmd> mapping must end with <CR>"
-msgstr "E5520: Заміна клавіш <Cmd> має закінчуватися <CR>"
+msgid "E788: Not allowed to edit another buffer now"
+msgstr "E788: Зараз не можна редагувати інший буфер"
-msgid "E5521: <Cmd> mapping must end with <CR> before second <Cmd>"
-msgstr "E5521: Заміна клавіш <Cmd> має закінчуватися <CR> перед другою <Cmd>"
+#, c-format
+msgid "E1023: Using a Number as a Bool: %d"
+msgstr "E1023: Вжито Number як Bool: %d"
#, c-format
-msgid "E5522: <Cmd> mapping must not include %s key"
-msgstr "E5522: Заміна клавіш <Cmd> не може містити ключ %s"
+msgid "E1085: Not a callable type: %s"
+msgstr "E1085: Це не схожий на функцію тип: %s"
#, c-format
msgid "E5555: API call: %s"
@@ -2889,21 +4085,46 @@ msgstr "E5601: Не вдалося закрити вікно, залишилос
msgid "E5602: Cannot exchange or rotate float"
msgstr "E5602: Не можна обміняти чи покрутити плавуче вікно"
-msgid "E1142: Non-empty string required"
-msgstr "E1142: Потрібен непорожній String"
-
msgid "E1155: Cannot define autocommands for ALL events"
-msgstr "E1155: Не можу визначити автокоманди для УСІХ подій"
+msgstr "E1155: Неможливо визначити автокоманди для УСІХ подій"
msgid "E1240: Resulting text too long"
msgstr "E1240: Текст результату задовгий"
msgid "E1247: Line number out of range"
-msgstr "E1247: Номер рядка вийшов поза межами"
+msgstr "E1247: Номер рядка поза межами"
+
+msgid "E5248: Invalid character in group name"
+msgstr "E5248: Некоректний символ у назві групи"
msgid "E1249: Highlight group name too long"
msgstr "E1249: Назва групи підсвічування задовга"
+#, c-format
+msgid "E966: Invalid line number: %ld"
+msgstr "E966: Некоректний номер рядка: %ld"
+
+#, c-format
+msgid "E1278: Stray '}' without a matching '{': %s"
+msgstr "E1278: Заблудла '}' без відповідної '{': %s"
+
+#, c-format
+msgid "E1279: Missing '}': %s"
+msgstr "E1279: Пропущено '}': %s"
+
+msgid "E5767: Cannot use :undo! to redo or move to a different undo branch"
+msgstr ""
+"E5767: Неможливо застосувати :undo! щоб повторити або перейти до іншої гілки "
+"історії змін"
+
+#, c-format
+msgid "E5570: Cannot update trust file: %s"
+msgstr "E5570: Неможливо поновити файл довіри: %s"
+
+#, c-format
+msgid "E355: Unknown option: %s"
+msgstr "E355: Невідома опція: %s"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Пошук дійшов до ПОЧАТКУ, продовжується з КІНЦЯ"
@@ -2913,38 +4134,83 @@ msgstr "Пошук дійшов до КІНЦЯ, продовжується з
msgid " line "
msgstr " рядок "
+#, c-format
+msgid "E685: Internal error: hash_add(): duplicate key \"%s\""
+msgstr "E685: Внутрішня помилка: hash_add(): повторний ключ «%s»"
-msgid "E424: Too many different highlighting attributes in use"
-msgstr "E424: Використано забагато різних атрибутів кольору"
+msgid "E478: Don't panic!"
+msgstr "E478: Без паніки!"
#, c-format
-msgid "E411: highlight group not found: %s"
-msgstr "E411: Групу підсвічування не знайдено: %s"
+msgid "E661: Sorry, no '%s' help for %s"
+msgstr "E661: Вибачте, немає допомоги '%s' для %s"
#, c-format
-msgid "E412: Not enough arguments: \":highlight link %s\""
-msgstr "E412: Недостатньо аргументів: «:highlight link %s»"
+msgid "E149: Sorry, no help for %s"
+msgstr "E149: Вибачте, немає допомоги для %s"
#, c-format
-msgid "E413: Too many arguments: \":highlight link %s\""
-msgstr "E413: Забагато аргументів: «:highlight link %s»"
+msgid "Sorry, help file \"%s\" not found"
+msgstr "Вибачте, файл допомоги «%s» не знайдено"
-msgid "E414: group has settings, highlight link ignored"
+#, c-format
+msgid "E151: No match: %s"
+msgstr "E151: Жодного збігу: %s"
+
+#, c-format
+msgid "E152: Cannot open %s for writing"
+msgstr "E152: Не вдалося відкрити %s для запису"
+
+#, c-format
+msgid "E153: Unable to open %s for reading"
+msgstr "E153: Не вдалося відкрити %s для читання"
+
+#, c-format
+msgid "E670: Mix of help file encodings within a language: %s"
+msgstr "E670: Мішанина кодувань файлу допомоги для мови %s"
+
+#, c-format
+msgid "E154: Duplicate tag \"%s\" in file %s/%s"
+msgstr "E154: Повторення мітки «%s» у файлі %s/%s"
+
+#, c-format
+msgid "E150: Not a directory: %s"
+msgstr "E150: Не є каталогом: %s"
+
+msgid "E424: Too many different highlighting attributes in use"
+msgstr "E424: Використано забагато різних атрибутів кольору"
+
+#, c-format
+msgid "E411: Highlight group not found: %s"
+msgstr "E411: Групу підсвічування не знайдено: %s"
+
+msgid "E414: Group has settings, highlight link ignored"
msgstr "E414: Грума має settings, highlight link проігноровано"
#, c-format
-msgid "E415: unexpected equal sign: %s"
+msgid "E415: Unexpected equal sign: %s"
msgstr "E415: Несподіваний знак рівності: %s"
#, c-format
-msgid "E416: missing equal sign: %s"
+msgid "E416: Missing equal sign: %s"
msgstr "E416: Пропущено знак рівності: %s"
#, c-format
-msgid "E417: missing argument: %s"
+msgid "E417: Missing argument: %s"
msgstr "E417: Пропущено аргумент: %s"
#, c-format
+msgid "E412: Not enough arguments: \":highlight link %s\""
+msgstr "E412: Недостатньо аргументів: «:highlight link %s»"
+
+#, c-format
+msgid "E413: Too many arguments: \":highlight link %s\""
+msgstr "E413: Забагато аргументів: «:highlight link %s»"
+
+msgid "E423: Illegal argument"
+msgstr "E423: Неприпустимий аргумент"
+
+#, c-format
msgid "E418: Illegal value: %s"
msgstr "E418: Неправильне значення: %s"
@@ -2965,171 +4231,114 @@ msgstr "E423: Неправильний аргумент: %s"
msgid "E669: Unprintable character in group name"
msgstr "E669: Недруковний символ у назві групи"
-msgid "E5248: Invalid character in group name"
-msgstr "E5248: Некоректний символ у назві групи"
-
msgid "E849: Too many highlight and syntax groups"
msgstr "E849: Забагато груп підсвічування і синтаксису"
-msgid "Add a new database"
-msgstr "Додати нову базу даних"
+msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
+msgstr "Наберіть число й <Enter> чи клацніть мишкою (q чи порожнє скасовує): "
-msgid "Query for a pattern"
-msgstr "Запит за зразком"
+msgid "Type number and <Enter> (q or empty cancels): "
+msgstr "Наберіть число й <Enter> (q чи порожнє скасовує): "
-msgid "Show this message"
-msgstr "Показати це повідомлення"
+msgid " Keyword completion (^N^P)"
+msgstr " Доповнення ключових слів (^N^P)"
-msgid "Kill a connection"
-msgstr "Знищити з'єднання"
+msgid " ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
+msgstr " Режим ^X (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"
-msgid "Reinit all connections"
-msgstr "Перезапустити усі з'єднання"
+msgid " Whole line completion (^L^N^P)"
+msgstr " Доповнення усього рядка (^L^N^P)"
-msgid "Show connections"
-msgstr "Показати з'єднання"
+msgid " File name completion (^F^N^P)"
+msgstr " Доповнення назви файлу (^F^N^P)"
-#, c-format
-msgid "E560: Usage: cs[cope] %s"
-msgstr "E560: Використання: cs[cope] %s"
+msgid " Tag completion (^]^N^P)"
+msgstr " Доповнення з міток (^]^N^P)"
-msgid "This cscope command does not support splitting the window.\n"
-msgstr "Ця команда cscope не вміє ділити вікно.\n"
+msgid " Path pattern completion (^N^P)"
+msgstr " Доповнення шляху за зразком (^N^P)"
-msgid "E562: Usage: cstag <ident>"
-msgstr "E562: Використання: cstag <ідентиф-ор>"
+msgid " Definition completion (^D^N^P)"
+msgstr " Доповнення визначення (^D^N^P)"
-msgid "E257: cstag: tag not found"
-msgstr "E257: cstag: мітку не знайдено"
+msgid " Dictionary completion (^K^N^P)"
+msgstr " Доповнення зі словника (^K^N^P)"
-#, c-format
-msgid "E563: stat(%s) error: %d"
-msgstr "E563: stat(%s) помилка: %d"
+msgid " Thesaurus completion (^T^N^P)"
+msgstr " Доповнення з тезаурусу (^T^N^P)"
-#, c-format
-msgid "E564: %s is not a directory or a valid cscope database"
-msgstr "E564: %s не є ні каталогом, ні базою даних cscope"
+msgid " Command-line completion (^V^N^P)"
+msgstr " Доповнення команд (^V^N^P)"
-#, c-format
-msgid "Added cscope database %s"
-msgstr "Додано базу даних cscope %s"
+msgid " User defined completion (^U^N^P)"
+msgstr " Користувацьке доповнення (^U^N^P)"
-#, c-format
-msgid "E262: error reading cscope connection %<PRIu64>"
-msgstr "E262: помилка читання зі з'єднання cscope %<PRIu64>"
+msgid " Omni completion (^O^N^P)"
+msgstr " Кмітливе доповнення (^O^N^P)"
-msgid "E561: unknown cscope search type"
-msgstr "E561: Невідомий тип пошуку cscope"
+msgid " Spelling suggestion (s^N^P)"
+msgstr " Орфографічна підказка (s^N^P)"
-msgid "E566: Could not create cscope pipes"
-msgstr "E566: Не вдалося створити канали до cscope"
+msgid " Keyword Local completion (^N^P)"
+msgstr " Доповнення місцевих ключових слів (^N^P)"
-msgid "E622: Could not fork for cscope"
-msgstr "E622: Не вдалося розділити процес для cscope"
+msgid "Hit end of paragraph"
+msgstr "Трапився кінець параграфа"
-msgid "cs_create_connection setpgid failed"
-msgstr "cs_create_connection: помилка setpgid"
+msgid "E840: Completion function deleted text"
+msgstr "E840: Функція доповнення знищила текст"
-msgid "cs_create_connection exec failed"
-msgstr "cs_create_connection: помилка під час виконання"
+msgid "'dictionary' option is empty"
+msgstr "Опція 'dictionary' порожня"
-msgid "cs_create_connection: fdopen for to_fp failed"
-msgstr "cs_create_connection: fdopen для to_fp не вдався"
+msgid "'thesaurus' option is empty"
+msgstr "Опція 'thesaurus' порожня"
-msgid "cs_create_connection: fdopen for fr_fp failed"
-msgstr "cs_create_connection: fdopen для fr_fp не вдався"
+#, c-format
+msgid "Scanning dictionary: %s"
+msgstr "Сканується словник: %s"
-msgid "E623: Could not spawn cscope process"
-msgstr "E623: Не вдалося створити процес cscope"
+msgid " (insert) Scroll (^E/^Y)"
+msgstr " (вставити) Прогорнути (^E/^Y)"
-msgid "E567: no cscope connections"
-msgstr "E567: жодного з'єднання із cscope"
+msgid " (replace) Scroll (^E/^Y)"
+msgstr " (заміна) Прогорнути (^E/^Y)"
-#, c-format
-msgid "E469: invalid cscopequickfix flag %c for %c"
-msgstr "E469: Некоректний прапорець cscopequickfix %c для %c"
+msgid "E785: complete() can only be used in Insert mode"
+msgstr "E785: complete() можна вживати тільки в режимі вставляння"
#, c-format
-msgid "E259: no matches found for cscope query %s of %s"
-msgstr "E259: Для запиту cscope %s з %s нічого не знайдено"
+msgid "Scanning: %s"
+msgstr "Пошук у: %s"
-msgid "cscope commands:\n"
-msgstr "Команди cscope:\n"
+msgid "Scanning tags."
+msgstr "Пошук серед міток."
-#, c-format
-msgid "%-5s: %s%*s (Usage: %s)"
-msgstr "%-5s: %s%*s (Використання: %s)"
+msgid "match in file"
+msgstr "збіг у файлі"
-msgid ""
-"\n"
-" a: Find assignments to this symbol\n"
-" c: Find functions calling this function\n"
-" d: Find functions called by this function\n"
-" e: Find this egrep pattern\n"
-" f: Find this file\n"
-" g: Find this definition\n"
-" i: Find files #including this file\n"
-" s: Find this C symbol\n"
-" t: Find this text string\n"
-msgstr ""
-"\n"
-" a: Знайти присвоєння цього символу\n"
-" a: Знайти присвоєння цього символу\n"
-" c: Знайти функції, що викликають цю функцію\n"
-" d: Знайти функції, що викликаються цією функцією\n"
-" e: Знайти цей шаблон egrep\n"
-" f: Знайти цей файл\n"
-" g: Знайти це визначення\n"
-" i: Знайти файли, які включають в себе цей файл\n"
-" s: Знайти цей символ C\n"
-" t: Знайти цей текст\n"
+msgid " Adding"
+msgstr " Додається"
-msgid "E568: duplicate cscope database not added"
-msgstr "E568: Повторна база даних cscope не додана"
+msgid "-- Searching..."
+msgstr "-- Пошук..."
-#, c-format
-msgid "E261: cscope connection %s not found"
-msgstr "E261: З'єднання з cscope %s не знайдено"
+msgid "Back at original"
+msgstr "Початковий варіант"
-#, c-format
-msgid "cscope connection %s closed"
-msgstr "З'єднання з cscope %s закінчено"
+msgid "Word from other line"
+msgstr "Слово з іншого рядка"
-msgid "E570: fatal error in cs_manage_matches"
-msgstr "E570: Фатальна помилка в cs_manage_matches"
+msgid "The only match"
+msgstr "Єдиний збіг"
#, c-format
-msgid "Cscope tag: %s"
-msgstr "Мітка cscope: %s"
-
-msgid ""
-"\n"
-" # line"
-msgstr ""
-"\n"
-" # рядок"
-
-msgid "filename / context / line\n"
-msgstr "файл / контекст / рядок\n"
+msgid "match %d of %d"
+msgstr "збіг %d з %d"
#, c-format
-msgid "E609: Cscope error: %s"
-msgstr "E609: Помилка cscope: %s"
-
-msgid "All cscope databases reset"
-msgstr "Усі бази даних cscope перезавантажено"
-
-msgid "no cscope connections\n"
-msgstr "Жодного з'єднання з cscope\n"
-
-msgid " # pid database name prepend path\n"
-msgstr " # pid назва бази даних шлях\n"
-
-msgid "Type number and <Enter> or click with the mouse (q or empty cancels): "
-msgstr "Наберіть число й <Enter> чи клацніть мишкою (q чи порожнє скасовує): "
-
-msgid "Type number and <Enter> (q or empty cancels): "
-msgstr "Наберіть число й <Enter> (q чи порожнє скасовує): "
+msgid "match %d"
+msgstr "збіг %d"
#, c-format
msgid "E1502: Lua failed to grow stack to %i"
@@ -3188,7 +4397,7 @@ msgid "Error executing lua callback: %.*s"
msgstr "Помилка виконання обробника lua: %.*s"
msgid "cannot save undo information"
-msgstr "Не вдалося записати інформацію повернення"
+msgstr "Не вдалося зберегти історію змін"
#, c-format
msgid "E5109: Error loading lua: %.*s"
@@ -3222,6 +4431,14 @@ msgstr "Помилка виконання обробника Lua vim.on_key: %.*
msgid "Error executing Lua callback: %.*s"
msgstr "Помилка виконання обробника Lua: %.*s"
+#, c-format
+msgid "Error executing vim.secure.read: %.*s"
+msgstr "Помилка виконання vim.secure.read: %.*s"
+
+#, c-format
+msgid "Error executing vim.secure.trust: %.*s"
+msgstr "Помилка виконання vim.secure.trust: %.*s"
+
msgid "Argument missing after"
msgstr "Пропущено аргумент після"
@@ -3245,6 +4462,9 @@ msgstr "E5421: Не вдалося відкрити stdin: %s"
msgid "Attempt to open script file again: \"%s %s\"\n"
msgstr "Спроба повторно відкрити скрипт знову: \"%s %s\"\n"
+msgid "--embed conflicts with -es/-Es/-l"
+msgstr "--embed конфліктує з -es/-Es/-l"
+
#, c-format
msgid "Cannot open for reading: \"%s\": %s\n"
msgstr "Не вдалося відкрити для читання: \"%s\": %s\n"
@@ -3252,9 +4472,6 @@ msgstr "Не вдалося відкрити для читання: \"%s\": %s\n
msgid "Cannot open for script output: \""
msgstr "Не вдалося відкрити як вихідний файл: \""
-msgid "--embed conflicts with -es/-Es"
-msgstr "--embed конфліктує з -es/-Es"
-
msgid "pre-vimrc command line"
msgstr "команди перед vimrc"
@@ -3276,14 +4493,8 @@ msgstr ""
msgid "Usage:\n"
msgstr "Вжиток:\n"
-msgid " nvim [options] [file ...] Edit file(s)\n"
-msgstr " nvim [опції] [файл ...] Редагувати файли\n"
-
-msgid " nvim [options] -t <tag> Edit file where tag is defined\n"
-msgstr " nvim [опції] -t <мітка> Редагувати файл, де визначено мітку\n"
-
-msgid " nvim [options] -q [errorfile] Edit file with first error\n"
-msgstr " nvim [опції] -q [ф.помилки] Редагувати файл з першою помилкою\n"
+msgid " nvim [options] [file ...]\n"
+msgstr " nvim [опції] [файл ...]\n"
msgid ""
"\n"
@@ -3292,12 +4503,6 @@ msgstr ""
"\n"
"Опції:\n"
-msgid " -- Only file names after this\n"
-msgstr " -- Лише назви файлів після цього\n"
-
-msgid " + Start at end of file\n"
-msgstr " + Розпочати в кінці файлу\n"
-
msgid " --cmd <cmd> Execute <cmd> before any config\n"
msgstr ""
" --cmd <команда> Виконати <команду> перед будь-якою конфігурацією\n"
@@ -3306,15 +4511,23 @@ msgid " +<cmd>, -c <cmd> Execute <cmd> after config and first file\n"
msgstr ""
" +<cmd>, -c <команда> Виконати <команду> після завантаження першого файлу\n"
-msgid " -b Binary mode\n"
-msgstr " -b Двійковий режим\n"
+msgid " -l <script> [args...] Execute Lua <script> (with optional args)\n"
+msgstr " -l <скрипт> [args...] Виконати <скрипт> Lua (з арг-тами, якщо є)\n"
+
+msgid " -S <session> Source <session> after loading the first file\n"
+msgstr ""
+" -S <сеанс> Виконати <сеанс> після першого завантаженого файлу\n"
+
+msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n"
+msgstr ""
+" -s <скрипт> Зчитати команди нормального режиму з файлу <скрипт>\n"
+
+msgid " -u <config> Use this config file\n"
+msgstr " -u <config> Вжити цей файл конфігурації\n"
msgid " -d Diff mode\n"
msgstr " -d Режим порівняння\n"
-msgid " -e, -E Ex mode\n"
-msgstr " -e, -E Режим Ex\n"
-
msgid " -es, -Es Silent (batch) mode\n"
msgstr " -es, -Es Мовчазний (пакетний) режим\n"
@@ -3324,12 +4537,6 @@ msgstr " -h, --help Надрукувати це повідомлен
msgid " -i <shada> Use this shada file\n"
msgstr " -i <shada> Вжити цей файл shada\n"
-msgid " -m Modifications (writing files) not allowed\n"
-msgstr " -m Зміни (запис файлів) не дозволено\n"
-
-msgid " -M Modifications in text not allowed\n"
-msgstr " -M Зміни в тексті файлів не дозволено\n"
-
msgid " -n No swap file, use memory only\n"
msgstr ""
" -n Не використовувати файл обміну, тримати усе в "
@@ -3348,37 +4555,30 @@ msgid " -p[N] Open N tab pages (default: one per file)\n"
msgstr ""
" -p[N] Відкрити N вкладок (стандартно: одна на файл)\n"
-msgid " -r, -L List swap files\n"
-msgstr " -r, -L Показати файли обміну\n"
-
-msgid " -r <file> Recover edit state for this file\n"
-msgstr " -r <файл> Відновити стан редагування для цього файлу\n"
-
-msgid " -R Read-only mode\n"
+msgid " -R Read-only (view) mode\n"
msgstr " -R Режим тільки для читання\n"
-msgid " -S <session> Source <session> after loading the first file\n"
-msgstr ""
-" -S <сеанс> Виконати <сеанс> після першого завантаженого файлу\n"
-
-msgid " -s <scriptin> Read Normal mode commands from <scriptin>\n"
-msgstr ""
-" -s <скрипт> Зчитати команди нормального режиму з файлу <скрипт>\n"
-
-msgid " -u <config> Use this config file\n"
-msgstr " -u <config> Вжити цей файл конфігурації\n"
-
msgid " -v, --version Print version information\n"
msgstr " -v, --version Надрукувати інформацію про версію програми\n"
msgid " -V[N][file] Verbose [level][file]\n"
msgstr " -V[N][файл] Більше повідомлень [рівень][файл]\n"
+msgid " -- Only file names after this\n"
+msgstr " -- Лише назви файлів після цього\n"
+
msgid " --api-info Write msgpack-encoded API metadata to stdout\n"
msgstr ""
" --api-info Записати метадані API, серіалізовані у msgpack, у "
"stdout\n"
+msgid ""
+" --clean \"Factory defaults\" (skip user config and plugins, "
+"shada)\n"
+msgstr ""
+" --clean «з нуля» (пропустити конфігурацію користувача і "
+"плагіни, shada)\n"
+
msgid " --embed Use stdin/stdout as a msgpack-rpc channel\n"
msgstr ""
" --embed Використати stdin/stdout, як канал msgpack-rpc\n"
@@ -3389,9 +4589,6 @@ msgstr " --headless Не запускати інтерфейс ко
msgid " --listen <address> Serve RPC API from this address\n"
msgstr " --listen <адреса> Обслуговувати RPC API за цією адресою\n"
-msgid " --noplugin Don't load plugins\n"
-msgstr " --noplugin Не завантажувати доповнення\n"
-
msgid " --remote[-subcommand] Execute commands remotely on a server\n"
msgstr " --remote[-subcommand] Виконати команди на віддаленому сервері\n"
@@ -3408,6 +4605,42 @@ msgstr ""
"\n"
"Див \":help startup-options\" щоб побачити всі опції.\n"
+#, c-format
+msgid "E224: Global abbreviation already exists for %s"
+msgstr "E224: Загальне скорочення для %s вже існує"
+
+#, c-format
+msgid "E225: Global mapping already exists for %s"
+msgstr "E225: Загальна заміна клавіш для %s вже існує"
+
+#, c-format
+msgid "E226: Abbreviation already exists for %s"
+msgstr "E226: Вже є скорочення для %s"
+
+#, c-format
+msgid "E227: Mapping already exists for %s"
+msgstr "E227: Вже є заміна клавіш для %s"
+
+msgid "E460: Entries missing in mapset() dict argument"
+msgstr "E460: Бракує записів у словниковому аргументі mapset()"
+
+msgid "No abbreviation found"
+msgstr "Скорочення не знайдено"
+
+msgid "No mapping found"
+msgstr "Заміни клавіш не знайдено"
+
+msgid "E228: makemap: Illegal mode"
+msgstr "E228: makemap: Неприпустимий режим"
+
+#, c-format
+msgid "E357: 'langmap': Matching character missing for %s"
+msgstr "E357: 'langmap': Для символу %s немає пари"
+
+#, c-format
+msgid "E358: 'langmap': Extra characters after semicolon: %s"
+msgstr "E358: 'langmap': Зайві символи після `;': %s"
+
msgid "No marks set"
msgstr "Не встановлено жодної помітки"
@@ -3462,11 +4695,11 @@ msgstr "E803: ID не знайдено: %<PRId64>"
#, c-format
msgid "E474: List item %d is either not a dictionary or an empty one"
-msgstr "E474: Елемент списку %d або не словник або порожній"
+msgstr "E474: Елемент %d списку або не словник або порожній"
#, c-format
msgid "E474: List item %d is missing one of the required keys"
-msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів"
+msgstr "E474: Елемент %d списку немає одного з обов’язкових ключів"
#, c-format
msgid "E798: ID is reserved for \":match\": %<PRId64>"
@@ -3476,7 +4709,30 @@ msgstr "E798: ID зарезервовано для \":match\": %<PRId64>"
msgid "E798: ID is reserved for \"match\": %<PRId64>"
msgstr "E798: ID зарезервовано для \"match\": %<PRId64>"
-msgid "E293: block was not locked"
+#, c-format
+msgid "E1109: List item %d is not a List"
+msgstr "E1109: Елемент %d списку не List"
+
+#, c-format
+msgid "E1110: List item %d does not contain 3 numbers"
+msgstr "E1110: Елемент %d списку не містить 3 числа"
+
+#, c-format
+msgid "E1111: List item %d range invalid"
+msgstr "E1111: Діапазон елемента %d списку некоректний"
+
+#, c-format
+msgid "E1112: List item %d cell width invalid"
+msgstr "E1112: Ширина комірки елемента %d списку некоректна"
+
+#, c-format
+msgid "E1113: Overlapping ranges for 0x%lx"
+msgstr "E1113: Накладання діапазонів у 0x%lx"
+
+msgid "E1114: Only values of 0x80 and higher supported"
+msgstr "E1114: Підтримуються тільки значення 0x80 і вище"
+
+msgid "E293: Block was not locked"
msgstr "E293: Блок не було зафіксовано"
msgid "E294: Seek error in swap file read"
@@ -3494,6 +4750,37 @@ msgstr "E297: Помилка запису файлу обміну"
msgid "E300: Swap file already exists (symlink attack?)"
msgstr "E300: Файл обміну вже існує (атака символьним посиланням?)"
+#, c-format
+msgid "E315: ml_get: Invalid lnum: %<PRId64>"
+msgstr "E315: ml_get: Неправильний lnum: %<PRId64>"
+
+#, c-format
+msgid "E316: ml_get: Cannot find line %<PRId64>in buffer %d %s"
+msgstr "E316: ml_get: Не знайшов рядок %<PRId64> у буфері %d %s"
+
+msgid "E317: Pointer block id wrong"
+msgstr "E317: Блок вказівника помилковий"
+
+msgid "E317: Pointer block id wrong 2"
+msgstr "E317: Блок вказівника помилковий 2"
+
+msgid "E317: Pointer block id wrong 3"
+msgstr "E317: Блок вказівника помилковий 3"
+
+msgid "E317: Pointer block id wrong 4"
+msgstr "E317: Блок вказівника помилковий 4"
+
+#, c-format
+msgid "E322: Line number out of range: %<PRId64> past the end"
+msgstr "E322: Номер рядка поза межами: %<PRId64> за кінцем"
+
+#, c-format
+msgid "E323: Line count wrong in block %<PRId64>"
+msgstr "E323: Неправильна кількість рядків у блоці %<PRId64>"
+
+msgid "E1364: Warning: Pointer block corrupted"
+msgstr "E1364: Попередження: Пошкоджено блок вказівника"
+
msgid "E298: Didn't get block nr 0?"
msgstr "E298: Немає блоку 0?"
@@ -3603,6 +4890,9 @@ msgstr "??? звідси і до `??? КІНЕЦЬ' рядки, можливо,
msgid "??? from here until ???END lines may have been inserted/deleted"
msgstr "??? звідси і до `??? КІНЕЦЬ' рядки, можливо, були додані/знищені"
+msgid "??? lines may be missing"
+msgstr "??? рядків може бракувати"
+
msgid "???END"
msgstr "??? КІНЕЦЬ"
@@ -3636,12 +4926,17 @@ msgstr "Відновлення закінчено. Вміст буфера сп
msgid ""
"\n"
-"You may want to delete the .swp file now.\n"
-"\n"
+"You may want to delete the .swp file now."
msgstr ""
"\n"
-"Можливо, тепер ви хочете знищити файл обміну .swp.\n"
+"Тепер ви можете знищити файл обміну .swp."
+
+msgid ""
"\n"
+"Note: process STILL RUNNING: "
+msgstr ""
+"\n"
+"Примітка: процес ЩЕ ВИКОНУЄТЬСЯ: "
msgid "Swap files found:"
msgstr "Знайдено файли обміну:"
@@ -3741,26 +5036,12 @@ msgstr "Файл збережено"
msgid "E314: Preserve failed"
msgstr "E314: Збереження не вдалося"
-#, c-format
-msgid "E315: ml_get: invalid lnum: %<PRId64>"
-msgstr "E315: ml_get: неправильний lnum: %<PRId64>"
-
-#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
-msgstr "E316: ml_get: не знайшов рядок %<PRId64> у буфері %d %s"
-
-msgid "E317: pointer block id wrong 3"
-msgstr "E317: Вказівник блоку помилковий 3"
-
msgid "stack_idx should be 0"
msgstr "stack_idx має бути рівним 0"
msgid "E318: Updated too many blocks?"
msgstr "E318: Поновлено забагато блоків?"
-msgid "E317: pointer block id wrong 4"
-msgstr "E317: Вказівник блоку помилковий 4"
-
msgid "deleted block 1?"
msgstr "блок 1 знищено?"
@@ -3768,26 +5049,12 @@ msgstr "блок 1 знищено?"
msgid "E320: Cannot find line %<PRId64>"
msgstr "E320: Не вдалося знайти рядок %<PRId64>"
-msgid "E317: pointer block id wrong"
-msgstr "E317: Вказівник блоку помилковий"
-
msgid "pe_line_count is zero"
msgstr "pe_line_count дорівнює 0"
-#, c-format
-msgid "E322: line number out of range: %<PRId64> past the end"
-msgstr "E322: Номер рядка вийшов за межі: %<PRId64> за кінцем"
-
-#, c-format
-msgid "E323: line count wrong in block %<PRId64>"
-msgstr "E323: Кількість рядків у блоці %<PRId64>"
-
msgid "Stack size increases"
msgstr "Розмір стеку збільшується"
-msgid "E317: pointer block id wrong 2"
-msgstr "E317: Вказівник блоку помилковий 2"
-
#, c-format
msgid "E773: Symlink loop for \"%s\""
msgstr "E773: Циклічні символьні посилання «%s»"
@@ -3846,7 +5113,7 @@ msgstr ""
" щоб позбутися цього повідомлення.\n"
msgid "Found a swap file that is not useful, deleting it"
-msgstr "Знайдено файл обміну, яким не можна скористатися, знищення"
+msgstr "Знайдено файл обміну, яким не можна скористатися, знищуємо"
msgid "Swap file \""
msgstr "Файл обміну «"
@@ -3906,9 +5173,6 @@ msgstr "E342: Забракло пам'яті! (потрібно було %<PRIu
msgid "E327: Part of menu-item path is not sub-menu"
msgstr "E327: Частина шляху до елемента меню не є підменю"
-msgid "E328: Menu only exists in another mode"
-msgstr "E328: Меню може бути тільки в іншому режимі"
-
#, c-format
msgid "E329: No menu \"%s\""
msgstr "E329: Немає меню «%s»"
@@ -3943,6 +5207,12 @@ msgstr "E333: Шлях повинен вести до елемента меню"
msgid "E334: Menu not found: %s"
msgstr "E334: Меню не знайдено: %s"
+msgid "E336: Menu path must lead to a sub-menu"
+msgstr "E336: Шлях до меню повинен вести до підменю"
+
+msgid "E337: Menu not found - check menu names"
+msgstr "E337: Меню не знайдено — перевірте назви меню"
+
#, c-format
msgid "Error detected while processing %s:"
msgstr "Виявлено помилку під час виконання %s:"
@@ -3961,6 +5231,20 @@ msgstr "Перервано: "
msgid "Press ENTER or type command to continue"
msgstr "Натисніть ENTER або введіть команду для продовження"
+#, c-format
+msgid "%ld more line"
+msgid_plural "%ld more lines"
+msgstr[0] "ще %ld рядок"
+msgstr[1] "ще %ld рядки"
+msgstr[2] "ще %ld рядків"
+
+#, c-format
+msgid "%ld line less"
+msgid_plural "%ld fewer lines"
+msgstr[0] "на %ld рядок менше"
+msgstr[1] "на %ld рядки менше"
+msgstr[2] "на %ld рядків менше"
+
msgid " (Interrupted)"
msgstr " (Перервано)"
@@ -4006,6 +5290,12 @@ msgstr ""
"&D:Жодного\n"
"&C:Скасувати"
+msgid "E664: Changelist is empty"
+msgstr "E664: Список змін порожній"
+
+msgid "E1292: Command-line window is already open"
+msgstr "E1292: Вікно командного рядка вже відкрите"
+
msgid "E349: No identifier under cursor"
msgstr "E349: Немає ідентифікатора над курсором"
@@ -4015,9 +5305,6 @@ msgstr "E348: Немає рядка на курсорі"
msgid "E352: Cannot erase folds with current 'foldmethod'"
msgstr "E352: Не вдалося знищити згортки поточним методом 'foldmethod'"
-msgid "E664: changelist is empty"
-msgstr "E664: Список змін порожній"
-
msgid "E662: At start of changelist"
msgstr "E662: Початок списку змін"
@@ -4031,18 +5318,66 @@ msgstr ""
msgid "Type :qa and press <Enter> to exit Nvim"
msgstr "Введіть :qa і натисність <Enter> щоб вийти з Nvim"
+msgid ""
+"E883: Search pattern and expression register may not contain two or more "
+"lines"
+msgstr ""
+"E883: Шаблон пошуку і регістр виразу не можуть містити два чи більше рядків"
+
+#, c-format
+msgid "%<PRId64> line %sed %d time"
+msgid_plural "%<PRId64> line %sed %d times"
+msgstr[0] "Зсунуто %2$s %3$d раз рядків: %1$<PRId64>"
+msgstr[1] "Зсунуто %2$s %3$d рази рядків: %1$<PRId64>"
+msgstr[2] "Зсунуто %2$s %3$d разів рядків: %1$<PRId64>"
+
+#, c-format
+msgid "%<PRId64> lines %sed %d time"
+msgid_plural "%<PRId64> lines %sed %d times"
+msgstr[0] "Зсунуто %2$s %3$d раз рядків: %1$<PRId64>"
+msgstr[1] "Зсунуто %2$s %3$d рази рядків: %1$<PRId64>"
+msgstr[2] "Зсунуто %2$s %3$d разів рядків: %1$<PRId64>"
+
#, c-format
msgid "%<PRId64> lines to indent... "
-msgstr "Залишилося вирівняти %<PRId64> рядків..."
+msgstr "Залишилося підбити відступи %<PRId64> рядків..."
+
+#, c-format
+msgid "%<PRId64> line indented "
+msgid_plural "%<PRId64> lines indented "
+msgstr[0] "Відступ %<PRId64> рядка "
+msgstr[1] "Відступ %<PRId64> рядків "
+msgstr[2] "Відступ %<PRId64> рядків "
msgid "E748: No previously used register"
msgstr "E748: Регістри перед цим не вживались"
#, c-format
+msgid "%<PRId64> line changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "Змінено %<PRId64> рядок"
+msgstr[1] "Змінено %<PRId64> рядки"
+msgstr[2] "Змінено %<PRId64> рядків"
+
+#, c-format
msgid " into \"%c"
msgstr " у \"%c"
#, c-format
+msgid "block of %<PRId64> line yanked%s"
+msgid_plural "block of %<PRId64> lines yanked%s"
+msgstr[0] "блок з %<PRId64> рядка висмикнуто%s"
+msgstr[1] "блок з %<PRId64> рядків висмикнуто%s"
+msgstr[2] "блок з %<PRId64> рядків висмикнуто%s"
+
+#, c-format
+msgid "%<PRId64> line yanked%s"
+msgid_plural "%<PRId64> lines yanked%s"
+msgstr[0] "%<PRId64> рядок висмикнуто%s"
+msgstr[1] "%<PRId64> рядки висмикнуто%s"
+msgstr[2] "%<PRId64> рядків висмикнуто%s"
+
+#, c-format
msgid "E353: Nothing in register %s"
msgstr "E353: У регістрі %s нічого немає"
@@ -4053,11 +5388,12 @@ msgstr ""
"\n"
"Тип Наз. Вміст "
-msgid ""
-"E883: search pattern and expression register may not contain two or more "
-"lines"
-msgstr ""
-"E883: шаблон пошуку і реєстр виразу не можуть містити два чи більше рядків"
+#, c-format
+msgid "%<PRId64> lines changed"
+msgid_plural "%<PRId64> lines changed"
+msgstr[0] "%<PRId64> рядок змінено"
+msgstr[1] "%<PRId64> рядки змінено"
+msgstr[2] "%<PRId64> рядків змінено"
#, c-format
msgid "%<PRId64> Cols; "
@@ -4109,7 +5445,7 @@ msgid "E520: Not allowed in a modeline"
msgstr "E520: Не дозволено у modeline"
msgid "E992: Not allowed in a modeline when 'modelineexpr' is off"
-msgstr "E992: Не дозволено у modeline, коли вимкнено 'modelineexpr'"
+msgstr "E992: Не дозволено у modeline, коли 'modelineexpr' вимкнено"
msgid "E846: Key code not set"
msgstr "E846: Код ключа не встановлено"
@@ -4117,59 +5453,6 @@ msgstr "E846: Код ключа не встановлено"
msgid "E521: Number required after ="
msgstr "E521: Після = потрібно вказати число"
-#, c-format
-msgid "E539: Illegal character <%s>"
-msgstr "E539: Недозволений символ <%s>"
-
-#, c-format
-msgid "For option %s"
-msgstr "Для опції %s"
-
-msgid "E589: 'backupext' and 'patchmode' are equal"
-msgstr "E589: Опції 'backupext' і 'patchmode' однакові"
-
-msgid "E834: Conflicts with value of 'listchars'"
-msgstr "E834: Конфліктує із значенням 'listchars'"
-
-msgid "E835: Conflicts with value of 'fillchars'"
-msgstr "E835: Конфліктує із значенням 'fillchars'"
-
-msgid "E524: Missing colon"
-msgstr "E524: Бракує двокрапки"
-
-msgid "E525: Zero length string"
-msgstr "E525: Рядок порожній"
-
-#, c-format
-msgid "E526: Missing number after <%s>"
-msgstr "E526: Після <%s> бракує числа"
-
-msgid "E527: Missing comma"
-msgstr "E527: Бракує коми"
-
-msgid "E528: Must specify a ' value"
-msgstr "E528: Потрібно вказати значення '"
-
-msgid "E595: 'showbreak' contains unprintable or wide character"
-msgstr "E595: 'showbreak' містить недруковні або розширені символи"
-
-#, c-format
-msgid "E535: Illegal character after <%c>"
-msgstr "E535: Недозволений символ після <%c>"
-
-msgid "E536: comma required"
-msgstr "E536: Потрібна кома"
-
-#, c-format
-msgid "E537: 'commentstring' must be empty or contain %s"
-msgstr "E537: 'commentstring' має бути порожньою чи містити %s"
-
-msgid "E540: Unclosed expression sequence"
-msgstr "E540: Послідовність виразів не завершено"
-
-msgid "E542: unbalanced groups"
-msgstr "E542: Групи не збалансовано"
-
msgid "E590: A preview window already exists"
msgstr "E590: Вікно перегляду вже існує"
@@ -4186,12 +5469,8 @@ msgid "E594: Need at least %d columns"
msgstr "E594: Потрібно щонайменше %d стовпців"
#, c-format
-msgid "E355: Unknown option: %s"
-msgstr "E355: Невідома опція: %s"
-
-#, c-format
-msgid "E521: Number required: &%s = '%s'"
-msgstr "E521: Потрібно вказати Number: &%s = '%s'"
+msgid "Invalid value for option '%s': expected %s, got %s %s"
+msgstr "Некоректне значення опції '%s': очікується %s, отримано %s %s"
msgid ""
"\n"
@@ -4217,13 +5496,64 @@ msgstr ""
msgid "E356: get_varp ERROR"
msgstr "E356: Помилка get_varp"
+msgid "E540: Unclosed expression sequence"
+msgstr "E540: Послідовність виразів не завершено"
+
+msgid "E536: Comma required"
+msgstr "E536: Потрібна кома"
+
+msgid "E542: Unbalanced groups"
+msgstr "E542: Групи не збалансовано"
+
+msgid "E589: 'backupext' and 'patchmode' are equal"
+msgstr "E589: Опції 'backupext' і 'patchmode' однакові"
+
+msgid "E595: 'showbreak' contains unprintable or wide character"
+msgstr "E595: 'showbreak' містить недруковні або розширені символи"
+
+msgid "E1336: Internal error: shortmess too long"
+msgstr "E1336: Внутрішня помилка: занадто довгий shortmess"
+
#, c-format
-msgid "E357: 'langmap': Matching character missing for %s"
-msgstr "E357: 'langmap': Для символу %s немає пари"
+msgid "E539: Illegal character <%s>"
+msgstr "E539: Недозволений символ <%s>"
#, c-format
-msgid "E358: 'langmap': Extra characters after semicolon: %s"
-msgstr "E358: 'langmap': Зайві символи після `;': %s"
+msgid "For option %s"
+msgstr "Для опції %s"
+
+msgid "E5080: Digit expected"
+msgstr "E5080: Очікується цифра"
+
+msgid "E524: Missing colon"
+msgstr "E524: Бракує двокрапки"
+
+msgid "E525: Zero length string"
+msgstr "E525: Рядок порожній"
+
+#, c-format
+msgid "E526: Missing number after <%s>"
+msgstr "E526: Після <%s> бракує числа"
+
+msgid "E527: Missing comma"
+msgstr "E527: Бракує коми"
+
+msgid "E528: Must specify a ' value"
+msgstr "E528: Потрібно вказати значення '"
+
+#, c-format
+msgid "E535: Illegal character after <%c>"
+msgstr "E535: Недозволений символ після <%c>"
+
+#, c-format
+msgid "E537: 'commentstring' must be empty or contain %s"
+msgstr "E537: 'commentstring' має бути порожньою чи містити %s"
+
+msgid "E834: Conflicts with value of 'listchars'"
+msgstr "E834: Конфліктує із значенням 'listchars'"
+
+msgid "E835: Conflicts with value of 'fillchars'"
+msgstr "E835: Конфліктує із значенням 'fillchars'"
#, c-format
msgid "dlerror = \"%s\""
@@ -4236,6 +5566,14 @@ msgstr "E5420: Не вдалося записати у файл: %s"
msgid "Vim: Error reading input, exiting...\n"
msgstr "Vim: Помилка читання вводу, робота завершується...\n"
+#, c-format
+msgid "Current %slanguage: \"%s\""
+msgstr "Мова (%s): «%s»"
+
+#, c-format
+msgid "E197: Cannot set language to \"%s\""
+msgstr "E197: Не вдалося встановити мову «%s»"
+
msgid ""
"\n"
"shell returned "
@@ -4254,6 +5592,7 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Не вдалося записати на вхід команди оболонки: %s"
+#, no-c-format
msgid "%a %b %d %H:%M:%S %Y"
msgstr "%H:%M:%S %a, %d %B %Y р."
@@ -4261,11 +5600,14 @@ msgstr "%H:%M:%S %a, %d %B %Y р."
msgid "E447: Can't find file \"%s\" in path"
msgstr "E447: Файл «%s» не знайдено у шляху пошуку"
+msgid "E750: First use \":profile start {fname}\""
+msgstr "E750: Спочатку зробіть «:profile start {файл}»"
+
msgid "E553: No more items"
msgstr "E553: Немає більше елементів"
msgid "E925: Current quickfix list was changed"
-msgstr "E925: Поточний список quickfix змінився"
+msgstr "E925: Список quickfix змінився"
msgid "E926: Current location list was changed"
msgstr "E926: Поточний список місць змінився"
@@ -4330,7 +5672,7 @@ msgid "Cannot open file \"%s\""
msgstr "Не вдалося відкрити файл «%s»"
msgid "cannot have both a list and a \"what\" argument"
-msgstr "не можна задавати одночасно список і аргумент «що»"
+msgstr "неможливо задати і список і аргумент «що»"
msgid "E681: Buffer is not loaded"
msgstr "E681: Буфер не завантажено"
@@ -4339,10 +5681,28 @@ msgid "E777: String or List expected"
msgstr "E777: Очікується String чи List"
#, c-format
-msgid "E369: invalid item in %s%%[]"
+msgid "E927: Invalid action: '%s'"
+msgstr "E927: Неправильна дія: «%s»"
+
+#, c-format
+msgid "E59: Invalid character after %s@"
+msgstr "E59: Некоректний символ після %s@"
+
+msgid "E63: Invalid use of \\_"
+msgstr "E63: Некоректно вжито \\_"
+
+msgid "E363: Pattern uses more memory than 'maxmempattern'"
+msgstr "E363: Зразок використовує більше пам’яті, ніж 'maxmempattern'"
+
+#, c-format
+msgid "E369: Invalid item in %s%%[]"
msgstr "E369: Некоректний елемент у %s%%[]"
#, c-format
+msgid "E654: Missing delimiter after search pattern: %s"
+msgstr "E654: Бракує роздільника після шаблону пошуку: %s"
+
+#, c-format
msgid "E769: Missing ] after %s["
msgstr "E769: Бракує ] після %s["
@@ -4379,13 +5739,24 @@ msgid "E70: Empty %s%%[]"
msgstr "E70: %s%%[] порожній"
msgid "E956: Cannot use pattern recursively"
-msgstr "E956: Не можна рекурсивно використати шаблон"
+msgstr "E956: Не можна застосувати шаблон рекурсивно"
#, c-format
msgid "E1204: No Number allowed after .: '\\%%%c'"
msgstr "E1204: Number не можна після .: '\\%%%c'"
#, c-format
+msgid "E1273: (NFA regexp) missing value in '\\%%%c'"
+msgstr "E1273: (NFA regexp) бракує значення у '\\%%%c'"
+
+#, c-format
+msgid "E1281: Atom '\\%%#=%c' must be at the start of the pattern"
+msgstr "E1281: Атом '\\%%#=%c' має бути на початку шаблону"
+
+msgid "E1290: substitute nesting too deep"
+msgstr "E1290: Забагато вкладених замін"
+
+#, c-format
msgid "E554: Syntax error in %s{...}"
msgstr "E554: Синтаксична помилка в %s{...}"
@@ -4404,6 +5775,10 @@ msgid "Switching to backtracking RE engine for pattern: "
msgstr "Перемикання до простого рушія регулярних виразів: "
#, c-format
+msgid "Searching for \"%s\" under \"%s\" in \"%s\""
+msgstr "Пошук «%s» за «%s» в «%s»"
+
+#, c-format
msgid "Searching for \"%s\" in \"%s\""
msgstr "Пошук «%s» в «%s»"
@@ -4423,71 +5798,82 @@ msgstr "Пошук «%s» в шляху виконання"
msgid "not found in runtime path: \"%s\""
msgstr "не знайдено в шляху виконання: «%s»"
-msgid " TERMINAL"
-msgstr " ТЕРМІНАЛ"
+#, c-format
+msgid "Cannot source a directory: \"%s\""
+msgstr "Не вдалося прочитати каталог: «%s»"
-msgid " VREPLACE"
-msgstr " ВІРТ ЗАМІНА"
+#, c-format
+msgid "could not source \"%s\""
+msgstr "Не вдалося виконати «%s»"
-msgid " REPLACE"
-msgstr " ЗАМІНА"
+#, c-format
+msgid "line %<PRId64>: could not source \"%s\""
+msgstr "рядок %<PRId64>: не вдалося виконати «%s»"
-msgid " REVERSE"
-msgstr " НАВИВОРІТ"
+#, c-format
+msgid "sourcing \"%s\""
+msgstr "виконується «%s»"
-msgid " INSERT"
-msgstr " ВСТАВИТИ"
+#, c-format
+msgid "line %<PRId64>: sourcing \"%s\""
+msgstr "рядок %<PRId64>: виконується «%s»"
-msgid " (insert)"
-msgstr " (вставити)"
+#, c-format
+msgid "finished sourcing %s"
+msgstr "закінчено виконання %s"
-msgid " (replace)"
-msgstr " (заміна)"
+msgid "modeline"
+msgstr "modeline"
-msgid " (vreplace)"
-msgstr " (вірт заміна)"
+msgid "--cmd argument"
+msgstr "--cmd аргумент"
-msgid " Hebrew"
-msgstr " Іврит"
+msgid "-c argument"
+msgstr "-c аргумент"
-msgid " Arabic"
-msgstr " Арабська"
+msgid "environment variable"
+msgstr "змінна оточення"
-msgid " (paste)"
-msgstr " (клей)"
+msgid "error handler"
+msgstr "обробник помилки"
-msgid " VISUAL"
-msgstr " ВИБІР"
+msgid "changed window size"
+msgstr "змінено розмір вікна"
-msgid " VISUAL LINE"
-msgstr " ВИБІР РЯДКІВ"
+msgid "Lua"
+msgstr "Lua"
-msgid " VISUAL BLOCK"
-msgstr " ВИБІР БЛОКУ"
+#, c-format
+msgid "API client (channel id %<PRIu64>)"
+msgstr "Клієнт API (канал «%<PRIu64>»)"
-msgid " SELECT"
-msgstr " ВИДІЛЕННЯ"
+msgid "anonymous :source"
+msgstr "анонімний :source"
-msgid " SELECT LINE"
-msgstr " ВИДІЛЕННЯ РЯДКІВ"
+#, c-format
+msgid "anonymous :source (script id %d)"
+msgstr "анонімний :source (ід. скрипта %d)"
-msgid " SELECT BLOCK"
-msgstr " ВИДІЛЕННЯ БЛОКУ"
+msgid "W15: Warning: Wrong line separator, ^M may be missing"
+msgstr "W15: Застереження: Неправильний роздільник рядків, можливо, бракує ^M"
-msgid "recording"
-msgstr "йде запис"
+msgid "E167: :scriptencoding used outside of a sourced file"
+msgstr "E167: :scriptencoding використано поза виконуваним файлом"
+
+msgid "E168: :finish used outside of a sourced file"
+msgstr "E168: :finish використано поза виконуваним файлом"
#, c-format
-msgid "E383: Invalid search string: %s"
-msgstr "E383: Неправильний зразок для пошуку: %s"
+msgid "E384: Search hit TOP without match for: %s"
+msgstr "E384: Пошук дійшов до ПОЧАТКУ без збігів з: %s"
#, c-format
-msgid "E384: search hit TOP without match for: %s"
-msgstr "E384: Пошук дійшов до ПОЧАТКУ без збігів з %s"
+msgid "E385: Search hit BOTTOM without match for: %s"
+msgstr "E385: Пошук дійшов до КІНЦЯ без збігів з: %s"
#, c-format
-msgid "E385: search hit BOTTOM without match for: %s"
-msgstr "E385: Пошук дійшов до КІНЦЯ без збігів з %s"
+msgid "E383: Invalid search string: %s"
+msgstr "E383: Неправильний зразок для пошуку: %s"
msgid "E386: Expected '?' or '/' after ';'"
msgstr "E386: Після `;' має бути `?' або `/'"
@@ -4638,14 +6024,14 @@ msgid "Writing ShaDa file \"%s\""
msgstr "Записується файл ShaDa «%s»"
#, c-format
-msgid "Failed setting uid and gid for file %s: %s"
-msgstr "Не вдалося встановити uid і gid для файлу %s: %s"
-
-#, c-format
msgid "E137: ShaDa file is not writable: %s"
msgstr "E137: Не дозволено запис у файл ShaDa: %s"
#, c-format
+msgid "Failed setting uid and gid for file %s: %s"
+msgstr "Не вдалося встановити uid і gid для файлу %s: %s"
+
+#, c-format
msgid "Can't rename ShaDa file from %s to %s!"
msgstr "Не вдалося перейменувати файл ShaDa з %s у %s!"
@@ -4803,21 +6189,6 @@ msgstr "E797: Автокоманда SpellFileMissing знищила буфер"
msgid "Warning: region %s not supported"
msgstr "Застереження: регіон %s не підтримується"
-msgid "Sorry, no suggestions"
-msgstr "Пробачте, немає пропозицій"
-
-#, c-format
-msgid "Sorry, only %<PRId64> suggestions"
-msgstr "Пробачте, тільки %<PRId64> пропозицій"
-
-#, c-format
-msgid "Change \"%.*s\" to:"
-msgstr "Замінити «%.*s» на:"
-
-#, c-format
-msgid " < \"%.*s\""
-msgstr " < «%.*s»"
-
msgid "E752: No previous spell replacement"
msgstr "E752: Немає попередньої заміни"
@@ -4829,6 +6200,16 @@ msgid "E758: Truncated spell file"
msgstr "E758: Обірваний файл орфографії"
#, c-format
+msgid "E782: Error while reading .sug file: %s"
+msgstr "E782: Помилка читання файлу .sug: %s"
+
+msgid "E783: Duplicate char in MAP entry"
+msgstr "E783: Повторено символ у елементі MAP"
+
+msgid "E1280: Illegal character in word"
+msgstr "E1280: Недозволений символ у слові"
+
+#, c-format
msgid "Trailing text in %s line %d: %s"
msgstr "Зайвий текст у %s у рядку %d: %s"
@@ -4876,10 +6257,6 @@ msgid "E781: .sug file doesn't match .spl file: %s"
msgstr "E781: Файл .sug не відповідає файлу .spl: %s"
#, c-format
-msgid "E782: error while reading .sug file: %s"
-msgstr "E782: Помилка читання файлу .sug: %s"
-
-#, c-format
msgid "Reading affix file %s..."
msgstr "Читається файл афіксів %s..."
@@ -4904,7 +6281,7 @@ msgid ""
"Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line "
"%d"
msgstr ""
-"Визначення COMPOUNDFORBIDFLAG після елементу PFX може дати неправильний "
+"Визначення COMPOUNDFORBIDFLAG після елемента PFX може дати неправильний "
"результат у %s у рядку %d"
#, c-format
@@ -4912,7 +6289,7 @@ msgid ""
"Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line "
"%d"
msgstr ""
-"Визначення COMPOUNDPERMITFLAG після елементу PFX можу дати неправильний "
+"Визначення COMPOUNDPERMITFLAG після елемента PFX можу дати неправильний "
"результат у %s у рядку %d"
#, c-format
@@ -4946,11 +6323,11 @@ msgstr "Подвійний афікс у %s у рядку %d: %s"
#, c-format
msgid ""
-"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGESTin %s "
+"Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in %s "
"line %d: %s"
msgstr ""
"Афікс також вживається для BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/"
-"NOSUGGEST у %s рядок %d: %s"
+"NOSUGGEST у %s у рядку %d: %s"
#, c-format
msgid "Expected Y or N in %s line %d: %s"
@@ -5141,8 +6518,96 @@ msgstr "Слово '%.*s' додано до %s"
msgid "E763: Word characters differ between spell files"
msgstr "E763: Символи у слові відрізняються у файлах орфографії"
-msgid "E783: duplicate char in MAP entry"
-msgstr "E783: Повторено символ у елементі MAP"
+msgid "Sorry, no suggestions"
+msgstr "Пробачте, немає пропозицій"
+
+#, c-format
+msgid "Sorry, only %<PRId64> suggestions"
+msgstr "Пробачте, тільки %<PRId64> пропозицій"
+
+#, c-format
+msgid "Change \"%.*s\" to:"
+msgstr "Замінити «%.*s» на:"
+
+#, c-format
+msgid " < \"%.*s\""
+msgstr " < «%.*s»"
+
+msgid "[Help]"
+msgstr "[Допомога]"
+
+msgid "[Preview]"
+msgstr "[Перегляд]"
+
+#, c-format
+msgid "E1400: Cannot mix positional and non-positional arguments: %s"
+msgstr "E1400: Неможливо змішувати позиційні й непозиційні аргументи: %s"
+
+#, c-format
+msgid "E1401: format argument %d unused in $-style format: %s"
+msgstr "E1401: аргумент %d формату не використано у $-форматуванні: %s"
+
+#, c-format
+msgid ""
+"E1402: Positional argument %d used as field width reused as different type: "
+"%s/%s"
+msgstr ""
+"E1402: Позиційний аргумент %d, що задає ширину поля, вжито як інший тип: %s/"
+"%s"
+
+#, c-format
+msgid "E1403: Positional argument %d out of bounds: %s"
+msgstr "E1403: Позиційний аргумент %d поза межами: %s"
+
+#, c-format
+msgid "E1404: Positional argument %d type used inconsistently: %s/%s"
+msgstr "E1404: Тип позиційного аргументу %d вжито нерегулярно: %s/%s"
+
+#, c-format
+msgid "E1405: Invalid format specifier: %s"
+msgstr "E1405: Некоректний специфікатор формату: %s"
+
+msgid "unknown"
+msgstr "невідомо"
+
+msgid "int"
+msgstr "int"
+
+msgid "long int"
+msgstr "long int"
+
+msgid "long long int"
+msgstr "long long int"
+
+msgid "signed size_t"
+msgstr "signed size_t"
+
+msgid "unsigned int"
+msgstr "unsigned int"
+
+msgid "unsigned long int"
+msgstr "unsigned long int"
+
+msgid "unsigned long long int"
+msgstr "unsigned long long int"
+
+msgid "size_t"
+msgstr "size_t"
+
+msgid "pointer"
+msgstr "pointer"
+
+msgid "percent"
+msgstr "percent"
+
+msgid "char"
+msgstr "char"
+
+msgid "string"
+msgstr "string"
+
+msgid "float"
+msgstr "float"
msgid "E766: Insufficient arguments for printf()"
msgstr "E766: Недостатньо аргументів для printf()"
@@ -5157,14 +6622,24 @@ msgstr "E767: Забагато аргументів для printf()"
msgid "E390: Illegal argument: %s"
msgstr "E390: Неправильний аргумент: %s"
+msgid "E395: Contains argument not accepted here"
+msgstr "E395: Містить неприйнятні тут аргументи"
+
+msgid "E844: Invalid cchar value"
+msgstr "E844: Некоректне значення cchar"
+
+#, c-format
+msgid "E890: Trailing char after ']': %s]%s"
+msgstr "E890: Зайвий символ після ']': %s]%s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Для буфера не визначено елементів синтаксису"
msgid "'redrawtime' exceeded, syntax highlighting disabled"
-msgstr "'redrawtime' перевищено, підсвічування синтаксису вимкнено"
+msgstr "'redrawtime' вичерпано, підсвічування синтаксису вимкнено"
msgid "syntax iskeyword not set"
-msgstr "не встановлено синтаксис iskeyword"
+msgstr "синтаксис iskeyword не встановлено"
#, c-format
msgid "E391: No such syntax cluster: %s"
@@ -5225,12 +6700,6 @@ msgstr "; збіг "
msgid " line breaks"
msgstr " розриви рядків"
-msgid "E395: contains argument not accepted here"
-msgstr "E395: Містить неприйнятні тут аргументи"
-
-msgid "E844: invalid cchar value"
-msgstr "E844: Некоректне значення cchar"
-
msgid "E393: group[t]here not accepted here"
msgstr "E393: group[t]here тут неприйнятний"
@@ -5249,10 +6718,6 @@ msgid "E789: Missing ']': %s"
msgstr "E789: Пропущено ']': %s"
#, c-format
-msgid "E890: trailing char after ']': %s]%s"
-msgstr "E890: Зайвий символ після ']': %s]%s"
-
-#, c-format
msgid "E398: Missing '=': %s"
msgstr "E398: Пропущено `=': %s"
@@ -5311,25 +6776,31 @@ msgid ""
msgstr ""
" ВСЬОГО К-ТЬ СПІВП. НАЙПОВІЛ. СЕРЕДН. НАЗВА ШАБЛОН"
-msgid "E555: at bottom of tag stack"
-msgstr "E555: Кінець стеку міток"
+msgid "E73: Tag stack empty"
+msgstr "E73: Стек міток порожній"
-msgid "E556: at top of tag stack"
-msgstr "E556: Вершина стеку міток"
+#, c-format
+msgid "E426: Tag not found: %s"
+msgstr "E426: Мітку не знайдено: %s"
-msgid "E986: cannot modify the tag stack within tagfunc"
+msgid "E555: At bottom of tag stack"
+msgstr "E555: На дні стеку міток"
+
+msgid "E556: At top of tag stack"
+msgstr "E556: На вершині стеку міток"
+
+msgid "E986: Cannot modify the tag stack within tagfunc"
msgstr "E986: Не можна змінювати стек міток у tagfunc"
-msgid "E987: invalid return value from tagfunc"
-msgstr "E987: Некоректне повернене значення з tagfunc"
+msgid "E987: Invalid return value from tagfunc"
+msgstr "E987: Некоректне значення, що повертається з tagfunc"
+
+msgid "E1299: Window unexpectedly closed while searching for tags"
+msgstr "E1299: Вікно несподівано закрилося під час пошуку міток"
msgid "E425: Cannot go before first matching tag"
msgstr "E425: Це вже найперша відповідна мітка"
-#, c-format
-msgid "E426: tag not found: %s"
-msgstr "E426: Мітку не знайдено: %s"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Лише одна відповідна мітка"
@@ -5368,23 +6839,23 @@ msgstr ""
" # ДО мітки З рядка у файлі/тексті"
#, c-format
-msgid "Searching tags file %s"
-msgstr "Шукається у файлі теґів %s"
-
-#, c-format
msgid "E431: Format error in tags file \"%s\""
-msgstr "E431: Помилка формату у файлі теґів «%s»"
+msgstr "E431: Помилка формату у файлі міток «%s»"
#, c-format
msgid "Before byte %<PRId64>"
msgstr "Перед байтом %<PRId64>"
#, c-format
+msgid "Searching tags file %s"
+msgstr "Шукається у файлі міток %s"
+
+#, c-format
msgid "E432: Tags file not sorted: %s"
-msgstr "E432: Файл теґів не впорядкований: %s"
+msgstr "E432: Файл міток не впорядкований: %s"
msgid "E433: No tags file"
-msgstr "E433: Немає файлу теґів"
+msgstr "E433: Немає файлу міток"
msgid "E434: Can't find tag pattern"
msgstr "E434: Не вдалося знайти зразок мітки"
@@ -5396,9 +6867,35 @@ msgstr "E435: Не вдалося знайти мітку, тільки прип
msgid "Duplicate field name: %s"
msgstr "Назва поля повторюється: %s"
+msgid ""
+"E856: \"assert_fails()\" second argument must be a string or a list with one "
+"or two strings"
+msgstr ""
+"E856: Другий аргумент «assert_fails()» має бути текстовим рядком чи список з "
+"одним-двома символьними рядками"
+
+msgid "E1115: \"assert_fails()\" fourth argument must be a number"
+msgstr "E1115: Четвертий аргумент «assert_fails()» має бути числом"
+
+msgid "E1116: \"assert_fails()\" fifth argument must be a string"
+msgstr "E1116: П’ятий аргумент «assert_fails()» має бути текстовим рядком"
+
+msgid "E1142: Calling test_garbagecollect_now() while v:testing is not set"
+msgstr "E1142: Виклик test_garbagecollect_now() поки не встановлено v:testing"
+
msgid "Beep!"
msgstr "Дзень!"
+msgid "E439: Undo list corrupt"
+msgstr "E439: Історію змін пошкоджено"
+
+msgid "E440: Undo line missing"
+msgstr "E440: Відсутній рядок в історії змін"
+
+#, c-format
+msgid "E829: Write error in undo file: %s"
+msgstr "E829: Помилка запису в файл історії: %s"
+
msgid "E881: Line count changed unexpectedly"
msgstr "E881: Кількість рядків несподівано змінилася"
@@ -5419,7 +6916,7 @@ msgstr "Не вдалося записати файл історії у жодн
#, c-format
msgid "Will not overwrite with undo file, cannot read: %s"
-msgstr "Will not overwrite with undo file, cannot read: %s"
+msgstr "Не можна перезаписати з файлу скасувань, не можна прочитати: %s"
#, c-format
msgid "Will not overwrite, this is not an undo file: %s"
@@ -5433,10 +6930,6 @@ msgid "Writing undo file: %s"
msgstr "Записується файл історії: %s"
#, c-format
-msgid "E829: write error in undo file: %s"
-msgstr "E829: Помилка запису у файлі історії: %s"
-
-#, c-format
msgid "Not reading undo file, owner differs: %s"
msgstr "Файл історії прочитано не буде, власник інший: %s"
@@ -5504,6 +6997,13 @@ msgstr "після"
msgid "before"
msgstr "перед"
+#, c-format
+msgid "%<PRId64> second ago"
+msgid_plural "%<PRId64> seconds ago"
+msgstr[0] "%<PRId64> секунду тому"
+msgstr[1] "%<PRId64> секунди тому"
+msgstr[2] "%<PRId64> секунд тому"
+
msgid "Nothing to undo"
msgstr "Немає нічого скасовувати"
@@ -5513,30 +7013,74 @@ msgstr "номер зміни час збережено"
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Не можна виконати undojoin після undo"
-msgid "E439: undo list corrupt"
-msgstr "E439: Список скасування пошкоджено"
+#, c-format
+msgid "E179: Argument required for %s"
+msgstr "E179: Потрібен аргумент для %s"
+
+#, c-format
+msgid "E184: No such user-defined command: %s"
+msgstr "E184: Команду користувача не знайдено: %s"
-msgid "E440: undo line missing"
-msgstr "E440: Відсутній рядок скасування"
+msgid "E1208: -complete used without allowing arguments"
+msgstr "E1208: -complete вжито без дозволених аргументів"
-msgid ""
-"\n"
-"\n"
-"Features: "
-msgstr ""
-"\n"
-"\n"
-"Характеристики: "
+#, c-format
+msgid "E1237: No such user-defined command in current buffer: %s"
+msgstr "E1237: Немає такої команди користувача у цьому буфері: %s"
msgid ""
"\n"
-"Compiled "
+" Name Args Address Complete Definition"
msgstr ""
"\n"
-"Скомпілював "
+" Назва Арг. Адреса Доповнення Визначення"
+
+msgid "No user-defined commands found"
+msgstr "Не знайдено команд користувача"
-msgid "by "
-msgstr " "
+#, c-format
+msgid "E180: Invalid address type value: %s"
+msgstr "E180: Неправильне значення типу адреси: %s"
+
+#, c-format
+msgid "E180: Invalid complete value: %s"
+msgstr "E180: Неправильне доповнення: %s"
+
+msgid "E468: Completion argument only allowed for custom completion"
+msgstr "E468: Аргумент дозволений тільки для користувацького доповнення"
+
+msgid "E467: Custom completion requires a function argument"
+msgstr "E467: Користувацьке доповнення вимагає аргумент-функцію"
+
+msgid "E175: No attribute specified"
+msgstr "E175: Не вказано атрибутів"
+
+msgid "E176: Invalid number of arguments"
+msgstr "E176: Неправильна кількість аргументів"
+
+msgid "E177: Count cannot be specified twice"
+msgstr "E177: Лічильник не може бути вказано двічі"
+
+msgid "E178: Invalid default value for count"
+msgstr "E178: Неправильне початкове значення лічильника"
+
+#, c-format
+msgid "E181: Invalid attribute: %s"
+msgstr "E181: Неправильний атрибут: %s"
+
+#, c-format
+msgid "E174: Command already exists: add ! to replace it: %s"
+msgstr "E174: Команда вже існує, ! щоб замінити її: %s"
+
+msgid "E182: Invalid command name"
+msgstr "E182: Неправильна назва команди"
+
+msgid "E183: User defined commands must start with an uppercase letter"
+msgstr "E183: Команди користувача повинні починатися з великої літери"
+
+msgid "E841: Reserved name, cannot be used for user defined command"
+msgstr ""
+"E841: Зарезервована назва, не можна використати для користувацької команди"
msgid " system vimrc file: \""
msgstr " системний vimrc: \""
@@ -5562,6 +7106,10 @@ msgstr ":q<Enter> вихід з Vim "
msgid "type :help<Enter> for help "
msgstr ":help<Enter> щоб отримати допомогу "
+#, c-format
+msgid "type :help news<Enter> to see changes in v%s.%s"
+msgstr ":help news<Enter> щоб побачити зміни у v%s.%s"
+
msgid "Help poor children in Uganda!"
msgstr "Допоможіть сиротам з Уганди!"
@@ -5580,9 +7128,6 @@ msgstr ":help sponsor<Enter> подальша інформація "
msgid "type :help register<Enter> for information "
msgstr ":help register<Enter> подальша інформація "
-msgid "menu Help->Sponsor/Register for information "
-msgstr "меню Допомога->Спонсор/Реєстрація подробиці "
-
#, c-format
msgid "E15: Invalid control character present in input: %.*s"
msgstr "E15: Некоректні керівні символи на вході: %.*s"
@@ -5750,17 +7295,23 @@ msgstr "E15: Бракує правої фігурної дужки у лямбд
msgid "E109: Missing ':' after '?': %.*s"
msgstr "E109: Бракує ':' після '?': %.*s"
+msgid "E1159: Cannot split a window when closing the buffer"
+msgstr "E1159: Неможливо розщепити вікно при закритті буфера"
+
msgid "Already only one window"
msgstr "Це вже єдине вікно"
msgid "E441: There is no preview window"
msgstr "E441: Немає вікна перегляду"
+msgid "E242: Can't split a window while closing another"
+msgstr "E242: Неможливо розщепити вікно, поки закривається інше"
+
msgid "E442: Can't split topleft and botright at the same time"
-msgstr "E442: Не вдалося одночасно розбити topleft і botright"
+msgstr "E442: Не вдалося одночасно розщепити topleft і botright"
msgid "E443: Cannot rotate when another window is split"
-msgstr "E443: Не вдалося перемістити вікно, заважають інші"
+msgstr "E443: Не вдалося перемістити по колу, інше вікно розщеплене"
msgid "E444: Cannot close last window"
msgstr "E444: Не вдалося закрити останнє вікно"
@@ -5773,5 +7324,3 @@ msgstr "E445: У іншому вікні є зміни"
msgid "E446: No file name under cursor"
msgstr "E446: Немає назви файлу над курсором"
-
-
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 245ce87865..f009722357 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -1,25 +1,21 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file popupmenu.c
///
/// Popup menu (PUM)
#include <assert.h>
-#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/getchar.h"
+#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
@@ -32,13 +28,14 @@
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/popupmenu.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
static pumitem_T *pum_array = NULL; // items of displayed pum
@@ -113,8 +110,6 @@ static void pum_compute_size(void)
void pum_display(pumitem_T *array, int size, int selected, bool array_changed, int cmd_startcol)
{
int context_lines;
- int above_row;
- int below_row;
int redo_count = 0;
int pum_win_row;
int cursor_col;
@@ -134,8 +129,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_is_visible = true;
pum_is_drawn = true;
validate_cursor_col();
- above_row = 0;
- below_row = cmdline_row;
+ int above_row = 0;
+ int below_row = cmdline_row;
// wildoptions=pum
if (State == MODE_CMDLINE) {
@@ -146,7 +141,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// anchor position: the start of the completed word
pum_win_row = curwin->w_wrow;
if (pum_rl) {
- cursor_col = curwin->w_width - curwin->w_wcol - 1;
+ cursor_col = curwin->w_width_inner - curwin->w_wcol - 1;
} else {
cursor_col = curwin->w_wcol;
}
@@ -167,10 +162,10 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
Array arr = arena_array(&arena, (size_t)size);
for (int i = 0; i < size; i++) {
Array item = arena_array(&arena, 4);
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_text)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_kind)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_extra)));
- ADD_C(item, STRING_OBJ(cstr_as_string(array[i].pum_info)));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_text));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_kind));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_extra));
+ ADD_C(item, CSTR_AS_OBJ(array[i].pum_info));
ADD_C(arr, ARRAY_OBJ(item));
}
ui_call_popupmenu_show(arr, selected, pum_win_row, cursor_col,
@@ -200,6 +195,21 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
+ int min_row = 0;
+ int min_col = 0;
+ int max_col = Columns;
+ int win_start_col = curwin->w_wincol;
+ int win_end_col = W_ENDCOL(curwin);
+ if (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) {
+ above_row -= curwin->w_winrow;
+ below_row = MAX(below_row - curwin->w_winrow, curwin->w_grid.rows);
+ min_row = -curwin->w_winrow;
+ min_col = -curwin->w_wincol;
+ max_col = MAX(Columns - curwin->w_wincol, curwin->w_grid.cols);
+ win_start_col = 0;
+ win_end_col = curwin->w_grid.cols;
+ }
+
// Figure out the size and position of the pum.
if (size < PUM_DEF_HEIGHT) {
pum_height = size;
@@ -207,7 +217,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_height = PUM_DEF_HEIGHT;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
}
@@ -230,15 +240,15 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
- if (pum_win_row >= size + context_lines) {
+ if (pum_win_row - min_row >= size + context_lines) {
pum_row = pum_win_row - size - context_lines;
pum_height = size;
} else {
- pum_row = 0;
- pum_height = pum_win_row - context_lines;
+ pum_row = min_row;
+ pum_height = pum_win_row - min_row - context_lines;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_row += pum_height - (int)p_ph;
pum_height = (int)p_ph;
}
@@ -266,13 +276,13 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_height = size;
}
- if ((p_ph > 0) && (pum_height > p_ph)) {
+ if (p_ph > 0 && pum_height > p_ph) {
pum_height = (int)p_ph;
}
}
// don't display when we only have room for one line
- if ((pum_height < 1) || ((pum_height == 1) && (size > 1))) {
+ if (pum_height < 1 || (pum_height == 1 && size > 1)) {
return;
}
@@ -305,22 +315,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
def_width = max_width;
}
- if ((((cursor_col < Columns - p_pw) || (cursor_col < Columns - max_width))
- && !pum_rl)
- || (pum_rl && ((cursor_col > p_pw) || (cursor_col > max_width)))) {
+ if (((cursor_col < max_col - p_pw
+ || cursor_col < max_col - max_width) && !pum_rl)
+ || (pum_rl && (cursor_col - min_col > p_pw
+ || cursor_col - min_col > max_width))) {
// align pum with "cursor_col"
pum_col = cursor_col;
// start with the maximum space available
if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
+ pum_width = pum_col - min_col - pum_scrollbar + 1;
} else {
- assert(Columns - pum_col - pum_scrollbar >= 0);
- pum_width = Columns - pum_col - pum_scrollbar;
+ assert(max_col - pum_col - pum_scrollbar >= 0);
+ pum_width = max_col - pum_col - pum_scrollbar;
}
- if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1)
- && (pum_width > p_pw)) {
+ if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
+ && pum_width > p_pw) {
// the width is more than needed for the items, make it
// narrower
pum_width = max_width + pum_kind_width + pum_extra_width + 1;
@@ -328,41 +339,42 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
if (pum_width < p_pw) {
pum_width = (int)p_pw;
}
- } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
- || (pum_rl && (cursor_col < Columns - p_pw
- || cursor_col < Columns - max_width))) {
+ } else if (((cursor_col - min_col > p_pw
+ || cursor_col - min_col > max_width) && !pum_rl)
+ || (pum_rl && (cursor_col < max_col - p_pw
+ || cursor_col < max_col - max_width))) {
// align pum edge with "cursor_col"
- if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ if (pum_rl && win_end_col < max_width + pum_scrollbar + 1) {
pum_col = cursor_col + max_width + pum_scrollbar + 1;
- if (pum_col >= Columns) {
- pum_col = Columns - 1;
+ if (pum_col >= max_col) {
+ pum_col = max_col - 1;
}
} else if (!pum_rl) {
- if (curwin->w_wincol > Columns - max_width - pum_scrollbar
+ if (win_start_col > max_col - max_width - pum_scrollbar
&& max_width <= p_pw) {
// use full width to end of the screen
- pum_col = Columns - max_width - pum_scrollbar;
- if (pum_col < 0) {
- pum_col = 0;
+ pum_col = max_col - max_width - pum_scrollbar;
+ if (pum_col < min_col) {
+ pum_col = min_col;
}
}
}
if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
+ pum_width = pum_col - min_col - pum_scrollbar + 1;
} else {
- pum_width = Columns - pum_col - pum_scrollbar;
+ pum_width = max_col - pum_col - pum_scrollbar;
}
if (pum_width < p_pw) {
pum_width = (int)p_pw;
if (pum_rl) {
- if (pum_width > pum_col) {
- pum_width = pum_col;
+ if (pum_width > pum_col - min_col) {
+ pum_width = pum_col - min_col;
}
} else {
- if (pum_width >= Columns - pum_col) {
- pum_width = Columns - pum_col - 1;
+ if (pum_width >= max_col - pum_col) {
+ pum_width = max_col - pum_col - 1;
}
}
} else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
@@ -373,26 +385,23 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
}
}
}
- } else if (Columns < def_width) {
+ } else if (max_col - min_col < def_width) {
// not enough room, will use what we have
if (pum_rl) {
- assert(Columns - 1 >= INT_MIN);
- pum_col = Columns - 1;
+ pum_col = max_col - 1;
} else {
- pum_col = 0;
+ pum_col = min_col;
}
- pum_width = Columns - 1;
+ pum_width = max_col - min_col - 1;
} else {
if (max_width > p_pw) {
// truncate
max_width = (int)p_pw;
}
-
if (pum_rl) {
- pum_col = max_width - 1;
+ pum_col = min_col + max_width - 1;
} else {
- assert(Columns - max_width >= 0);
- pum_col = Columns - max_width;
+ pum_col = max_col - max_width;
}
pum_width = max_width - pum_scrollbar;
}
@@ -400,8 +409,9 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
// Set selected item and redraw. If the window size changed need to redo
// the positioning. Limit this to two times, when there is not much
// room the window size will keep changing.
- } while (pum_set_selected(selected, redo_count) && (++redo_count <= 2));
+ } while (pum_set_selected(selected, redo_count) && ++redo_count <= 2);
+ pum_grid.zindex = (State == MODE_CMDLINE) ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu;
pum_redraw();
}
@@ -409,45 +419,47 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
void pum_redraw(void)
{
int row = 0;
- int grid_col;
- int attr_norm = win_hl_attr(curwin, HLF_PNI);
- int attr_select = win_hl_attr(curwin, HLF_PSI);
int attr_scroll = win_hl_attr(curwin, HLF_PSB);
int attr_thumb = win_hl_attr(curwin, HLF_PST);
- int attr;
- int i;
- int idx;
- char *s;
char *p = NULL;
- int totwidth, width, w;
int thumb_pos = 0;
int thumb_height = 1;
- int round;
int n;
+#define HA(hlf) (win_hl_attr(curwin, (hlf)))
+ // "word" "kind" "extra text"
+ const int attrsNorm[3] = { HA(HLF_PNI), HA(HLF_PNK), HA(HLF_PNX) };
+ const int attrsSel[3] = { HA(HLF_PSI), HA(HLF_PSK), HA(HLF_PSX) };
+#undef HA
+
int grid_width = pum_width;
int col_off = 0;
bool extra_space = false;
if (pum_rl) {
- col_off = pum_width;
- if (pum_col < curwin->w_wincol + curwin->w_width - 1) {
+ col_off = pum_width - 1;
+ assert(!(State & MODE_CMDLINE));
+ int win_end_col = ui_has(kUIMultigrid) ? curwin->w_grid.cols : W_ENDCOL(curwin);
+ if (pum_col < win_end_col - 1) {
+ grid_width += 1;
+ extra_space = true;
+ }
+ } else {
+ int min_col = (!(State & MODE_CMDLINE) && ui_has(kUIMultigrid)) ? -curwin->w_wincol : 0;
+ if (pum_col > min_col) {
grid_width += 1;
+ col_off = 1;
extra_space = true;
}
- } else if (pum_col > 0) {
- grid_width += 1;
- col_off = 1;
- extra_space = true;
}
if (pum_scrollbar > 0) {
grid_width++;
+ if (pum_rl) {
+ col_off++;
+ }
}
grid_assign_handle(&pum_grid);
- pum_grid.zindex = ((State == MODE_CMDLINE)
- ? kZIndexCmdlinePopupMenu : kZIndexPopupMenu);
-
bool moved = ui_comp_put_grid(&pum_grid, pum_row, pum_col - col_off,
pum_height, grid_width, false, true);
bool invalid_grid = moved || pum_invalid;
@@ -484,42 +496,42 @@ void pum_redraw(void)
/ (pum_size - pum_height);
}
- for (i = 0; i < pum_height; i++) {
- idx = i + pum_first;
- attr = (idx == pum_selected) ? attr_select : attr_norm;
+ for (int i = 0; i < pum_height; i++) {
+ int idx = i + pum_first;
+ const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm;
+ int attr = attrs[0]; // start with "word" highlight
- grid_puts_line_start(&pum_grid, row);
+ grid_line_start(&pum_grid, row);
// prepend a space if there is room
if (extra_space) {
if (pum_rl) {
- grid_putchar(&pum_grid, ' ', row, col_off + 1, attr);
+ grid_line_puts(col_off + 1, " ", 1, attr);
} else {
- grid_putchar(&pum_grid, ' ', row, col_off - 1, attr);
+ grid_line_puts(col_off - 1, " ", 1, attr);
}
}
// Display each entry, use two spaces for a Tab.
- // Do this 3 times: For the main text, kind and extra info
- grid_col = col_off;
- totwidth = 0;
-
- for (round = 1; round <= 3; round++) {
- width = 0;
- s = NULL;
+ // Do this 3 times:
+ // 0 - main text
+ // 1 - kind
+ // 2 - extra info
+ int grid_col = col_off;
+ int totwidth = 0;
+
+ for (int round = 0; round < 3; round++) {
+ attr = attrs[round];
+ int width = 0;
+ char *s = NULL;
switch (round) {
+ case 0:
+ p = pum_array[idx].pum_text; break;
case 1:
- p = pum_array[idx].pum_text;
- break;
-
+ p = pum_array[idx].pum_kind; break;
case 2:
- p = pum_array[idx].pum_kind;
- break;
-
- case 3:
- p = pum_array[idx].pum_extra;
- break;
+ p = pum_array[idx].pum_extra; break;
}
if (p != NULL) {
@@ -527,7 +539,7 @@ void pum_redraw(void)
if (s == NULL) {
s = p;
}
- w = ptr2cells(p);
+ int w = ptr2cells(p);
if ((*p == NUL) || (*p == TAB) || (totwidth + w > pum_width)) {
// Display the text that fits or comes before a Tab.
@@ -562,13 +574,13 @@ void pum_redraw(void)
size++;
}
}
- grid_puts_len(&pum_grid, rt, (int)strlen(rt), row, grid_col - size + 1, attr);
+ grid_line_puts(grid_col - size + 1, rt, -1, attr);
xfree(rt_start);
xfree(st);
grid_col -= width;
} else {
- // use grid_puts_len() to truncate the text
- grid_puts(&pum_grid, st, row, grid_col, attr);
+ // use grid_line_puts() to truncate the text
+ grid_line_puts(grid_col, st, -1, attr);
xfree(st);
grid_col += width;
}
@@ -579,11 +591,10 @@ void pum_redraw(void)
// Display two spaces for a Tab.
if (pum_rl) {
- grid_puts_len(&pum_grid, " ", 2, row, grid_col - 1,
- attr);
+ grid_line_puts(grid_col - 1, " ", 2, attr);
grid_col -= 2;
} else {
- grid_puts_len(&pum_grid, " ", 2, row, grid_col, attr);
+ grid_line_puts(grid_col, " ", 2, attr);
grid_col += 2;
}
totwidth += 2;
@@ -596,17 +607,17 @@ void pum_redraw(void)
}
}
- if (round > 1) {
+ if (round > 0) {
n = pum_kind_width + 1;
} else {
n = 1;
}
// Stop when there is nothing more to display.
- if ((round == 3)
- || ((round == 2)
- && (pum_array[idx].pum_extra == NULL))
+ if ((round == 2)
|| ((round == 1)
+ && (pum_array[idx].pum_extra == NULL))
+ || ((round == 0)
&& (pum_array[idx].pum_kind == NULL)
&& (pum_array[idx].pum_extra == NULL))
|| (pum_base_width + n >= pum_width)) {
@@ -614,37 +625,31 @@ void pum_redraw(void)
}
if (pum_rl) {
- grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1,
- grid_col + 1, ' ', ' ', attr);
+ grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, ' ', attr);
grid_col = col_off - pum_base_width - n + 1;
} else {
- grid_fill(&pum_grid, row, row + 1, grid_col,
- col_off + pum_base_width + n, ' ', ' ', attr);
+ grid_line_fill(grid_col, col_off + pum_base_width + n, ' ', attr);
grid_col = col_off + pum_base_width + n;
}
totwidth = pum_base_width + n;
}
if (pum_rl) {
- grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1,
- ' ', ' ', attr);
+ grid_line_fill(col_off - pum_width + 1, grid_col + 1, ' ', attr);
} else {
- grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ',
- attr);
+ grid_line_fill(grid_col, col_off + pum_width, ' ', attr);
}
if (pum_scrollbar > 0) {
if (pum_rl) {
- grid_putchar(&pum_grid, ' ', row, col_off - pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_height
- ? attr_thumb : attr_scroll);
+ grid_line_puts(col_off - pum_width, " ", 1,
+ i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
} else {
- grid_putchar(&pum_grid, ' ', row, col_off + pum_width,
- i >= thumb_pos && i < thumb_pos + thumb_height
- ? attr_thumb : attr_scroll);
+ grid_line_puts(col_off + pum_width, " ", 1,
+ i >= thumb_pos && i < thumb_pos + thumb_height ? attr_thumb : attr_scroll);
}
}
- grid_puts_line_flush(false);
+ grid_line_flush();
row++;
}
}
@@ -725,7 +730,6 @@ static bool pum_set_selected(int n, int repeat)
&& (vim_strchr(p_cot, 'p') != NULL)) {
win_T *curwin_save = curwin;
tabpage_T *curtab_save = curtab;
- int res = OK;
// Open a preview window. 3 lines by default. Prefer
// 'previewheight' if set and smaller.
@@ -744,6 +748,7 @@ static bool pum_set_selected(int n, int repeat)
g_do_tagpreview = 0;
if (curwin->w_p_pvw) {
+ int res = OK;
if (!resized
&& (curbuf->b_nwindows == 1)
&& (curbuf->b_fname == NULL)
@@ -751,7 +756,7 @@ static bool pum_set_selected(int n, int repeat)
&& (curbuf->b_p_bh[0] == 'w')) {
// Already a "wipeout" buffer, make it empty.
while (!buf_is_empty(curbuf)) {
- ml_delete((linenr_T)1, false);
+ ml_delete(1, false);
}
} else {
// Don't want to sync undo in the current buffer.
@@ -762,20 +767,19 @@ static bool pum_set_selected(int n, int repeat)
if (res == OK) {
// Edit a new, empty buffer. Set options for a "wipeout"
// buffer.
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bl", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "nofile", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "wipe", OPT_LOCAL);
- set_option_value_give_err("diff", 0L, NULL, OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bl", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("nofile"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL);
+ set_option_value_give_err("diff", BOOLEAN_OPTVAL(false), OPT_LOCAL);
}
}
if (res == OK) {
- char *p, *e;
linenr_T lnum = 0;
- for (p = pum_array[pum_selected].pum_info; *p != NUL;) {
- e = vim_strchr(p, '\n');
+ for (char *p = pum_array[pum_selected].pum_info; *p != NUL;) {
+ char *e = vim_strchr(p, '\n');
if (e == NULL) {
ml_append(lnum++, p, 0, false);
break;
@@ -971,32 +975,44 @@ void pum_set_event_info(dict_T *dict)
static void pum_position_at_mouse(int min_width)
{
+ int min_row = 0;
+ int max_row = Rows;
+ int max_col = Columns;
+ if (mouse_grid > 1) {
+ win_T *wp = get_win_by_grid_handle(mouse_grid);
+ if (wp != NULL) {
+ min_row = -wp->w_winrow;
+ max_row = MAX(Rows - wp->w_winrow, wp->w_grid.rows);
+ max_col = MAX(Columns - wp->w_wincol, wp->w_grid.cols);
+ }
+ }
pum_anchor_grid = mouse_grid;
- if (Rows - mouse_row > pum_size) {
+ if (max_row - mouse_row > pum_size) {
// Enough space below the mouse row.
pum_above = false;
pum_row = mouse_row + 1;
- if (pum_height > Rows - pum_row) {
- pum_height = Rows - pum_row;
+ if (pum_height > max_row - pum_row) {
+ pum_height = max_row - pum_row;
}
} else {
// Show above the mouse row, reduce height if it does not fit.
pum_above = true;
pum_row = mouse_row - pum_size;
- if (pum_row < 0) {
- pum_height += pum_row;
- pum_row = 0;
+ if (pum_row < min_row) {
+ pum_height += pum_row - min_row;
+ pum_row = min_row;
}
}
- if (Columns - mouse_col >= pum_base_width || Columns - mouse_col > min_width) {
+ if (max_col - mouse_col >= pum_base_width
+ || max_col - mouse_col > min_width) {
// Enough space to show at mouse column.
pum_col = mouse_col;
} else {
// Not enough space, right align with window.
- pum_col = Columns - (pum_base_width > min_width ? min_width : pum_base_width);
+ pum_col = max_col - (pum_base_width > min_width ? min_width : pum_base_width);
}
- pum_width = Columns - pum_col;
+ pum_width = max_col - pum_col;
if (pum_width > pum_base_width + 1) {
pum_width = pum_base_width + 1;
}
@@ -1053,7 +1069,7 @@ void pum_show_popupmenu(vimmenu_T *menu)
// When there are only Terminal mode menus, using "popup Edit" results in
// pum_size being zero.
if (pum_size <= 0) {
- emsg(e_menuothermode);
+ emsg(_(e_menu_only_exists_in_another_mode));
return;
}
@@ -1087,12 +1103,12 @@ void pum_show_popupmenu(vimmenu_T *menu)
ui_call_option_set(STATIC_CSTR_AS_STRING("mousemoveevent"), BOOLEAN_OBJ(true));
}
- for (;;) {
+ while (true) {
pum_is_visible = true;
pum_is_drawn = true;
+ pum_grid.zindex = kZIndexCmdlinePopupMenu; // show above cmdline area #23275
pum_redraw();
setcursor_mayforce(true);
- ui_flush();
int c = vgetc();
@@ -1156,8 +1172,15 @@ void pum_make_popup(const char *path_name, int use_mouse_pos)
if (!use_mouse_pos) {
// Hack: set mouse position at the cursor so that the menu pops up
// around there.
- mouse_row = curwin->w_winrow + curwin->w_wrow;
- mouse_col = curwin->w_wincol + curwin->w_wcol;
+ mouse_row = curwin->w_grid.row_offset + curwin->w_wrow;
+ mouse_col = curwin->w_grid.col_offset + curwin->w_wcol;
+ if (ui_has(kUIMultigrid)) {
+ mouse_grid = curwin->w_grid.target->handle;
+ } else if (curwin->w_grid.target != &default_grid) {
+ mouse_grid = 0;
+ mouse_row += curwin->w_winrow;
+ mouse_col += curwin->w_wincol;
+ }
}
vimmenu_T *menu = menu_find(path_name);
diff --git a/src/nvim/popupmenu.h b/src/nvim/popupmenu.h
index 08b791c509..24a3f8713a 100644
--- a/src/nvim/popupmenu.h
+++ b/src/nvim/popupmenu.h
@@ -1,12 +1,11 @@
-#ifndef NVIM_POPUPMENU_H
-#define NVIM_POPUPMENU_H
+#pragma once
#include <stdbool.h>
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/macros_defs.h"
+#include "nvim/menu_defs.h" // IWYU pragma: keep
/// Used for popup menu items.
typedef struct {
@@ -16,7 +15,7 @@ typedef struct {
char *pum_info; // extra info
} pumitem_T;
-EXTERN ScreenGrid pum_grid INIT(= SCREEN_GRID_INIT);
+EXTERN ScreenGrid pum_grid INIT( = SCREEN_GRID_INIT);
/// state for pum_ext_select_item.
EXTERN struct {
@@ -29,4 +28,3 @@ EXTERN struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "popupmenu.h.generated.h"
#endif
-#endif // NVIM_POPUPMENU_H
diff --git a/src/nvim/pos.h b/src/nvim/pos_defs.h
index 1b7e6273fd..98a1762a5c 100644
--- a/src/nvim/pos.h
+++ b/src/nvim/pos_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_POS_H
-#define NVIM_POS_H
+#pragma once
#include <inttypes.h>
@@ -39,5 +38,3 @@ typedef struct {
linenr_T lnum; ///< line number
colnr_T col; ///< column number
} lpos_T;
-
-#endif // NVIM_POS_H
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index fd024f2d38..53ff57dacb 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <math.h>
#include <stdbool.h>
@@ -9,14 +6,16 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -24,14 +23,13 @@
#include "nvim/keycodes.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/runtime.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "profile.c.generated.h"
@@ -232,23 +230,23 @@ void profile_reset(void)
{
// Reset sourced files.
for (int id = 1; id <= script_items.ga_len; id++) {
- scriptitem_T *si = &SCRIPT_ITEM(id);
+ scriptitem_T *si = SCRIPT_ITEM(id);
if (si->sn_prof_on) {
- si->sn_prof_on = false;
- si->sn_pr_force = false;
- si->sn_pr_child = profile_zero();
- si->sn_pr_nest = 0;
- si->sn_pr_count = 0;
- si->sn_pr_total = profile_zero();
- si->sn_pr_self = profile_zero();
- si->sn_pr_start = profile_zero();
- si->sn_pr_children = profile_zero();
+ si->sn_prof_on = false;
+ si->sn_pr_force = false;
+ si->sn_pr_child = profile_zero();
+ si->sn_pr_nest = 0;
+ si->sn_pr_count = 0;
+ si->sn_pr_total = profile_zero();
+ si->sn_pr_self = profile_zero();
+ si->sn_pr_start = profile_zero();
+ si->sn_pr_children = profile_zero();
ga_clear(&si->sn_prl_ga);
- si->sn_prl_start = profile_zero();
+ si->sn_prl_start = profile_zero();
si->sn_prl_children = profile_zero();
- si->sn_prl_wait = profile_zero();
- si->sn_prl_idx = -1;
- si->sn_prl_execed = 0;
+ si->sn_prl_wait = profile_zero();
+ si->sn_prl_idx = -1;
+ si->sn_prl_execed = 0;
}
}
@@ -262,22 +260,22 @@ void profile_reset(void)
todo--;
ufunc_T *uf = HI2UF(hi);
if (uf->uf_prof_initialized) {
- uf->uf_profiling = 0;
- uf->uf_tm_count = 0;
- uf->uf_tm_total = profile_zero();
- uf->uf_tm_self = profile_zero();
- uf->uf_tm_children = profile_zero();
+ uf->uf_profiling = 0;
+ uf->uf_tm_count = 0;
+ uf->uf_tm_total = profile_zero();
+ uf->uf_tm_self = profile_zero();
+ uf->uf_tm_children = profile_zero();
for (int i = 0; i < uf->uf_lines.ga_len; i++) {
uf->uf_tml_count[i] = 0;
uf->uf_tml_total[i] = uf->uf_tml_self[i] = 0;
}
- uf->uf_tml_start = profile_zero();
+ uf->uf_tml_start = profile_zero();
uf->uf_tml_children = profile_zero();
- uf->uf_tml_wait = profile_zero();
- uf->uf_tml_idx = -1;
- uf->uf_tml_execed = 0;
+ uf->uf_tml_wait = profile_zero();
+ uf->uf_tml_idx = -1;
+ uf->uf_tml_execed = 0;
}
}
}
@@ -290,11 +288,8 @@ void ex_profile(exarg_T *eap)
{
static proftime_T pause_time;
- char *e;
- int len;
-
- e = skiptowhite(eap->arg);
- len = (int)(e - eap->arg);
+ char *e = skiptowhite(eap->arg);
+ int len = (int)(e - eap->arg);
e = skipwhite(e);
if (len == 5 && strncmp(eap->arg, "start", 5) == 0 && *e != NUL) {
@@ -302,13 +297,13 @@ void ex_profile(exarg_T *eap)
profile_fname = expand_env_save_opt(e, true);
do_profiling = PROF_YES;
profile_set_wait(profile_zero());
- set_vim_var_nr(VV_PROFILING, 1L);
+ set_vim_var_nr(VV_PROFILING, 1);
} else if (do_profiling == PROF_NONE) {
emsg(_("E750: First use \":profile start {fname}\""));
} else if (strcmp(eap->arg, "stop") == 0) {
profile_dump();
do_profiling = PROF_NONE;
- set_vim_var_nr(VV_PROFILING, 0L);
+ set_vim_var_nr(VV_PROFILING, 0);
profile_reset();
} else if (strcmp(eap->arg, "pause") == 0) {
if (do_profiling == PROF_YES) {
@@ -407,7 +402,7 @@ bool prof_def_func(void)
FUNC_ATTR_PURE
{
if (current_sctx.sc_sid > 0) {
- return SCRIPT_ITEM(current_sctx.sc_sid).sn_pr_force;
+ return SCRIPT_ITEM(current_sctx.sc_sid)->sn_pr_force;
}
return false;
}
@@ -438,13 +433,10 @@ static void prof_func_line(FILE *fd, int count, const proftime_T *total, const p
/// @param prefer_self when equal print only self time
static void prof_sort_list(FILE *fd, ufunc_T **sorttab, int st_len, char *title, bool prefer_self)
{
- int i;
- ufunc_T *fp;
-
fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title);
fprintf(fd, "count total (s) self (s) function\n");
- for (i = 0; i < 20 && i < st_len; i++) {
- fp = sorttab[i];
+ for (int i = 0; i < 20 && i < st_len; i++) {
+ ufunc_T *fp = sorttab[i];
prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self,
prefer_self);
if ((uint8_t)fp->uf_name[0] == K_SPECIAL) {
@@ -513,8 +505,8 @@ void prof_child_enter(proftime_T *tm)
{
funccall_T *fc = get_current_funccal();
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_start();
+ if (fc != NULL && fc->fc_func->uf_profiling) {
+ fc->fc_prof_child = profile_start();
}
script_prof_save(tm);
@@ -528,14 +520,14 @@ void prof_child_exit(proftime_T *tm)
{
funccall_T *fc = get_current_funccal();
- if (fc != NULL && fc->func->uf_profiling) {
- fc->prof_child = profile_end(fc->prof_child);
+ if (fc != NULL && fc->fc_func->uf_profiling) {
+ fc->fc_prof_child = profile_end(fc->fc_prof_child);
// don't count waiting time
- fc->prof_child = profile_sub_wait(*tm, fc->prof_child);
- fc->func->uf_tm_children =
- profile_add(fc->func->uf_tm_children, fc->prof_child);
- fc->func->uf_tml_children =
- profile_add(fc->func->uf_tml_children, fc->prof_child);
+ fc->fc_prof_child = profile_sub_wait(*tm, fc->fc_prof_child);
+ fc->fc_func->uf_tm_children =
+ profile_add(fc->fc_func->uf_tm_children, fc->fc_prof_child);
+ fc->fc_func->uf_tml_children =
+ profile_add(fc->fc_func->uf_tml_children, fc->fc_prof_child);
}
script_prof_restore(tm);
}
@@ -547,7 +539,7 @@ void prof_child_exit(proftime_T *tm)
void func_line_start(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && SOURCING_LNUM >= 1 && SOURCING_LNUM <= fp->uf_lines.ga_len) {
fp->uf_tml_idx = SOURCING_LNUM - 1;
@@ -566,7 +558,7 @@ void func_line_start(void *cookie)
void func_line_exec(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
fp->uf_tml_execed = true;
@@ -577,7 +569,7 @@ void func_line_exec(void *cookie)
void func_line_end(void *cookie)
{
funccall_T *fcp = (funccall_T *)cookie;
- ufunc_T *fp = fcp->func;
+ ufunc_T *fp = fcp->fc_func;
if (fp->uf_profiling && fp->uf_tml_idx >= 0) {
if (fp->uf_tml_execed) {
@@ -598,23 +590,19 @@ void func_line_end(void *cookie)
static void func_dump_profile(FILE *fd)
{
hashtab_T *const functbl = func_tbl_get();
- hashitem_T *hi;
- int todo;
- ufunc_T *fp;
- ufunc_T **sorttab;
int st_len = 0;
- todo = (int)functbl->ht_used;
+ int todo = (int)functbl->ht_used;
if (todo == 0) {
return; // nothing to dump
}
- sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
+ ufunc_T **sorttab = xmalloc(sizeof(ufunc_T *) * (size_t)todo);
- for (hi = functbl->ht_array; todo > 0; hi++) {
+ for (hashitem_T *hi = functbl->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- fp = HI2UF(hi);
+ ufunc_T *fp = HI2UF(hi);
if (fp->uf_prof_initialized) {
sorttab[st_len++] = fp;
@@ -689,10 +677,8 @@ void profile_init(scriptitem_T *si)
/// @param tm place to store wait time
void script_prof_save(proftime_T *tm)
{
- scriptitem_T *si;
-
if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= script_items.ga_len) {
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_pr_nest++ == 0) {
si->sn_pr_child = profile_start();
}
@@ -707,7 +693,7 @@ void script_prof_restore(const proftime_T *tm)
return;
}
- scriptitem_T *si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && --si->sn_pr_nest == 0) {
si->sn_pr_child = profile_end(si->sn_pr_child);
// don't count wait time
@@ -720,12 +706,10 @@ void script_prof_restore(const proftime_T *tm)
/// Dump the profiling results for all scripts in file "fd".
static void script_dump_profile(FILE *fd)
{
- scriptitem_T *si;
- FILE *sfd;
sn_prl_T *pp;
for (int id = 1; id <= script_items.ga_len; id++) {
- si = &SCRIPT_ITEM(id);
+ scriptitem_T *si = SCRIPT_ITEM(id);
if (si->sn_prof_on) {
fprintf(fd, "SCRIPT %s\n", si->sn_name);
if (si->sn_pr_count == 1) {
@@ -738,7 +722,7 @@ static void script_dump_profile(FILE *fd)
fprintf(fd, "\n");
fprintf(fd, "count total (s) self (s)\n");
- sfd = os_fopen(si->sn_name, "r");
+ FILE *sfd = os_fopen(si->sn_name, "r");
if (sfd == NULL) {
fprintf(fd, "Cannot open file!\n");
} else {
@@ -807,13 +791,10 @@ void profile_dump(void)
/// until later and we need to store the time now.
void script_line_start(void)
{
- scriptitem_T *si;
- sn_prl_T *pp;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && SOURCING_LNUM >= 1) {
// Grow the array before starting the timer, so that the time spent
// here isn't counted.
@@ -822,7 +803,7 @@ void script_line_start(void)
while (si->sn_prl_ga.ga_len <= si->sn_prl_idx
&& si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) {
// Zero counters for a line that was not used before.
- pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
+ sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len);
pp->snp_count = 0;
pp->sn_prl_total = profile_zero();
pp->sn_prl_self = profile_zero();
@@ -838,12 +819,10 @@ void script_line_start(void)
/// Called when actually executing a function line.
void script_line_exec(void)
{
- scriptitem_T *si;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_prl_idx >= 0) {
si->sn_prl_execed = true;
}
@@ -852,17 +831,14 @@ void script_line_exec(void)
/// Called when done with a function line.
void script_line_end(void)
{
- scriptitem_T *si;
- sn_prl_T *pp;
-
if (current_sctx.sc_sid <= 0 || current_sctx.sc_sid > script_items.ga_len) {
return;
}
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ scriptitem_T *si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on && si->sn_prl_idx >= 0
&& si->sn_prl_idx < si->sn_prl_ga.ga_len) {
if (si->sn_prl_execed) {
- pp = &PRL_ITEM(si, si->sn_prl_idx);
+ sn_prl_T *pp = &PRL_ITEM(si, si->sn_prl_idx);
pp->snp_count++;
si->sn_prl_start = profile_end(si->sn_prl_start);
si->sn_prl_start = profile_sub_wait(si->sn_prl_wait, si->sn_prl_start);
diff --git a/src/nvim/profile.h b/src/nvim/profile.h
index 547d11185f..1a1800c279 100644
--- a/src/nvim/profile.h
+++ b/src/nvim/profile.h
@@ -1,11 +1,11 @@
-#ifndef NVIM_PROFILE_H
-#define NVIM_PROFILE_H
+#pragma once
-#include <stdint.h>
+#include <stdint.h> // IWYU pragma: keep
#include <time.h>
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/runtime.h"
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/runtime_defs.h" // IWYU pragma: keep
#define TIME_MSG(s) do { \
if (time_fd != NULL) time_msg(s, NULL); \
@@ -14,5 +14,3 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "profile.h.generated.h"
#endif
-
-#endif // NVIM_PROFILE_H
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 5518fdfa51..4e20eb8925 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// quickfix.c: functions for quickfix mode, using a file with error messages
#include <assert.h>
@@ -14,7 +11,7 @@
#include <time.h>
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -23,7 +20,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
@@ -33,34 +29,37 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
-#include "nvim/memfile_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/search.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
struct dir_stack_T {
@@ -80,14 +79,14 @@ struct qfline_S {
int qf_col; ///< column where the error occurred
int qf_end_col; ///< column when the error has range or zero
int qf_nr; ///< error number
- char *qf_module; ///< module name for this error
- char *qf_pattern; ///< search pattern for the error
- char *qf_text; ///< description of the error
- char qf_viscol; ///< set to true if qf_col and qf_end_col is
- // screen column
- char qf_cleared; ///< set to true if line has been deleted
- char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep
- char qf_valid; ///< valid error message detected
+ char *qf_module; ///< module name for this error
+ char *qf_pattern; ///< search pattern for the error
+ char *qf_text; ///< description of the error
+ char qf_viscol; ///< set to true if qf_col and qf_end_col is screen column
+ char qf_cleared; ///< set to true if line has been deleted
+ char qf_type; ///< type of the error (mostly 'E'); 1 for :helpgrep
+ typval_T qf_user_data; ///< custom user data associated with this item
+ char qf_valid; ///< valid error message detected
};
// There is a stack of error lists.
@@ -109,18 +108,19 @@ typedef enum {
/// created using setqflist()/setloclist() with a title and/or user context
/// information and entries can be added later using setqflist()/setloclist().
typedef struct qf_list_S {
- unsigned qf_id; ///< Unique identifier for this list
+ unsigned qf_id; ///< Unique identifier for this list
qfltype_T qfl_type;
- qfline_T *qf_start; ///< pointer to the first error
- qfline_T *qf_last; ///< pointer to the last error
- qfline_T *qf_ptr; ///< pointer to the current error
- int qf_count; ///< number of errors (0 means empty list)
- int qf_index; ///< current index in the error list
- int qf_nonevalid; ///< true if not a single valid entry found
- char *qf_title; ///< title derived from the command that created
- ///< the error list or set by setqflist
- typval_T *qf_ctx; ///< context set by setqflist/setloclist
- Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function
+ qfline_T *qf_start; ///< pointer to the first error
+ qfline_T *qf_last; ///< pointer to the last error
+ qfline_T *qf_ptr; ///< pointer to the current error
+ int qf_count; ///< number of errors (0 means empty list)
+ int qf_index; ///< current index in the error list
+ bool qf_nonevalid; ///< true if not a single valid entry found
+ bool qf_has_user_data; ///< true if at least one item has user_data attached
+ char *qf_title; ///< title derived from the command that created
+ ///< the error list or set by setqflist
+ typval_T *qf_ctx; ///< context set by setqflist/setloclist
+ Callback qf_qftf_cb; ///< 'quickfixtextfunc' callback function
struct dir_stack_T *qf_dir_stack;
char *qf_directory;
@@ -129,7 +129,7 @@ typedef struct qf_list_S {
bool qf_multiline;
bool qf_multiignore;
bool qf_multiscan;
- long qf_changedtick;
+ int qf_changedtick;
} qf_list_T;
/// Quickfix/Location list stack definition
@@ -150,7 +150,7 @@ struct qf_info_S {
static qf_info_T ql_info; // global quickfix list
static unsigned last_qf_id = 0; // Last Used quickfix list id
-#define FMT_PATTERNS 13 // maximum number of % recognized
+#define FMT_PATTERNS 14 // maximum number of % recognized
// Structure used to hold the info of one part of 'errorformat'
typedef struct efm_S efm_T;
@@ -215,6 +215,7 @@ typedef struct {
typedef struct {
char *namebuf;
+ int bnr;
char *module;
char *errmsg;
size_t errmsglen;
@@ -226,12 +227,13 @@ typedef struct {
char *pattern;
int enr;
char type;
+ typval_T *user_data;
bool valid;
} qffields_T;
/// :vimgrep command arguments
typedef struct vgr_args_S {
- long tomatch; ///< maximum number of matches to find
+ int tomatch; ///< maximum number of matches to find
char *spat; ///< search pattern
int flags; ///< search modifier
char **fnames; ///< list of files to search
@@ -244,7 +246,13 @@ typedef struct vgr_args_S {
# include "quickfix.c.generated.h"
#endif
-static char *e_no_more_items = N_("E553: No more items");
+static const char *e_no_more_items = N_("E553: No more items");
+static const char *e_current_quickfix_list_was_changed =
+ N_("E925: Current quickfix list was changed");
+static const char *e_current_location_list_was_changed =
+ N_("E926: Current location list was changed");
+
+enum { QF_WINHEIGHT = 10, }; ///< default height for quickfix window
// Quickfix window check helper macro
#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL)
@@ -257,10 +265,8 @@ static char *e_no_more_items = N_("E553: No more items");
#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX)
#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION)
-//
// Return location list for window 'wp'
// For location list window, return the referenced location list
-//
#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist)
// Macro to loop through all the items in a quickfix list
@@ -275,10 +281,38 @@ static char *e_no_more_items = N_("E553: No more items");
static char *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
-static char *e_current_quickfix_list_was_changed =
- N_("E925: Current quickfix list was changed");
-static char *e_current_location_list_was_changed =
- N_("E926: Current location list was changed");
+static garray_T qfga;
+
+/// Get a growarray to buffer text in. Shared between various commands to avoid
+/// many alloc/free calls.
+static garray_T *qfga_get(void)
+{
+ static bool initialized = false;
+
+ if (!initialized) {
+ initialized = true;
+ ga_init(&qfga, 1, 256);
+ }
+
+ // Reset the length to zero. Retain ga_data from previous use to avoid
+ // many alloc/free calls.
+ qfga.ga_len = 0;
+
+ return &qfga;
+}
+
+/// The "qfga" grow array buffer is reused across multiple quickfix commands as
+/// a temporary buffer to reduce the number of alloc/free calls. But if the
+/// buffer size is large, then to avoid holding on to that memory, clear the
+/// grow array. Otherwise just reset the grow array length.
+static void qfga_clear(void)
+{
+ if (qfga.ga_maxlen > 1000) {
+ ga_clear(&qfga);
+ } else {
+ qfga.ga_len = 0;
+ }
+}
// Counter to prevent autocmds from freeing up location lists when they are
// still being used.
@@ -309,7 +343,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T
: ((qfl->qf_currfile != NULL && fields->valid)
? qfl->qf_currfile : NULL),
fields->module,
- 0,
+ fields->bnr,
fields->errmsg,
fields->lnum,
fields->end_lnum,
@@ -319,6 +353,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T
fields->pattern,
fields->enr,
fields->type,
+ fields->user_data,
fields->valid);
}
@@ -342,8 +377,8 @@ int qf_init(win_T *wp, const char *restrict efile, char *restrict errorformat, i
qi = ll_get_or_alloc_list(wp);
}
- return qf_init_ext(qi, qi->qf_curlist, (char *)efile, curbuf, NULL, errorformat,
- newlist, (linenr_T)0, (linenr_T)0, (char *)qf_title, enc);
+ return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat,
+ newlist, 0, 0, qf_title, enc);
}
// Maximum number of bytes allowed per line while reading an errorfile.
@@ -355,20 +390,21 @@ static struct fmtpattern {
char *pattern;
} fmt_pat[FMT_PATTERNS] = {
{ 'f', ".\\+" }, // only used when at end
- { 'n', "\\d\\+" }, // 1
- { 'l', "\\d\\+" }, // 2
- { 'e', "\\d\\+" }, // 3
- { 'c', "\\d\\+" }, // 4
- { 'k', "\\d\\+" }, // 5
- { 't', "." }, // 6
-#define FMT_PATTERN_M 7
- { 'm', ".\\+" }, // 7
-#define FMT_PATTERN_R 8
- { 'r', ".*" }, // 8
- { 'p', "[- \t.]*" }, // 9
- { 'v', "\\d\\+" }, // 10
- { 's', ".\\+" }, // 11
- { 'o', ".\\+" } // 12
+ { 'b', "\\d\\+" }, // 1
+ { 'n', "\\d\\+" }, // 2
+ { 'l', "\\d\\+" }, // 3
+ { 'e', "\\d\\+" }, // 4
+ { 'c', "\\d\\+" }, // 5
+ { 'k', "\\d\\+" }, // 6
+ { 't', "." }, // 7
+#define FMT_PATTERN_M 8
+ { 'm', ".\\+" }, // 8
+#define FMT_PATTERN_R 9
+ { 'r', ".*" }, // 9
+ { 'p', "[-\t .]*" }, // 10
+ { 'v', "\\d\\+" }, // 11
+ { 's', ".\\+" }, // 12
+ { 'o', ".\\+" } // 13
};
/// Convert an errorformat pattern to a regular expression pattern.
@@ -499,7 +535,7 @@ static int efm_to_regpat(const char *efm, int len, efm_T *fmt_ptr, char *regpat)
}
}
if (idx < FMT_PATTERNS) {
- ptr = efmpat_to_regpat((char *)efmp, ptr, fmt_ptr, idx, round);
+ ptr = efmpat_to_regpat(efmp, ptr, fmt_ptr, idx, round);
if (ptr == NULL) {
return FAIL;
}
@@ -726,7 +762,7 @@ static int qf_get_next_buf_line(qfstate_T *state)
if (state->buflnum > state->lnumlast) {
return QF_END_OF_INPUT;
}
- char *p_buf = ml_get_buf(state->buf, state->buflnum, false);
+ char *p_buf = ml_get_buf(state->buf, state->buflnum);
state->buflnum += 1;
size_t len = strlen(p_buf);
@@ -768,7 +804,7 @@ retry:
memcpy(state->growbuf, IObuff, IOSIZE - 1);
size_t growbuflen = state->linelen;
- for (;;) {
+ while (true) {
errno = 0;
if (fgets(state->growbuf + growbuflen,
(int)(state->growbufsiz - growbuflen), state->fd) == NULL) {
@@ -788,7 +824,7 @@ retry:
}
state->growbufsiz = (2 * state->growbufsiz < LINE_MAXLEN)
- ? 2 * state->growbufsiz : LINE_MAXLEN;
+ ? 2 * state->growbufsiz : LINE_MAXLEN;
state->growbuf = xrealloc(state->growbuf, state->growbufsiz);
}
@@ -825,7 +861,7 @@ retry:
state->linebuf = line;
state->growbuf = line;
state->growbufsiz = state->linelen < LINE_MAXLEN
- ? state->linelen : LINE_MAXLEN;
+ ? state->linelen : LINE_MAXLEN;
}
}
}
@@ -1209,7 +1245,7 @@ static char *qf_cmdtitle(char *cmd)
{
static char qftitle_str[IOSIZE];
- snprintf((char *)qftitle_str, IOSIZE, ":%s", cmd);
+ snprintf(qftitle_str, IOSIZE, ":%s", cmd);
return qftitle_str;
}
@@ -1249,6 +1285,7 @@ static void qf_new_list(qf_info_T *qi, const char *qf_title)
qf_store_title(qfl, qf_title);
qfl->qfl_type = qi->qfl_type;
qfl->qf_id = ++last_qf_id;
+ qfl->qf_has_user_data = false;
}
/// Parse the match for filename ('%f') pattern in regmatch.
@@ -1275,6 +1312,21 @@ static int qf_parse_fmt_f(regmatch_T *rmp, int midx, qffields_T *fields, int pre
return QF_OK;
}
+/// Parse the match for buffer number ('%b') pattern in regmatch.
+/// Return the matched value in "fields->bnr".
+static int qf_parse_fmt_b(regmatch_T *rmp, int midx, qffields_T *fields)
+{
+ if (rmp->startp[midx] == NULL) {
+ return QF_FAIL;
+ }
+ int bnr = (int)atol(rmp->startp[midx]);
+ if (buflist_findnr(bnr) == NULL) {
+ return QF_FAIL;
+ }
+ fields->bnr = bnr;
+ return QF_OK;
+}
+
/// Parse the match for error number ('%n') pattern in regmatch.
/// Return the matched value in "fields->enr".
static int qf_parse_fmt_n(regmatch_T *rmp, int midx, qffields_T *fields)
@@ -1459,6 +1511,7 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
/// Keep in sync with fmt_pat[].
static int (*qf_parse_fmt[FMT_PATTERNS])(regmatch_T *, int, qffields_T *) = {
NULL, // %f
+ qf_parse_fmt_b,
qf_parse_fmt_n,
qf_parse_fmt_l,
qf_parse_fmt_e,
@@ -1531,6 +1584,7 @@ static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qf
}
fields->namebuf[0] = NUL;
+ fields->bnr = 0;
fields->module[0] = NUL;
fields->pattern[0] = NUL;
if (!qf_multiscan) {
@@ -1549,7 +1603,7 @@ static int qf_parse_get_fields(char *linebuf, size_t linelen, efm_T *fmt_ptr, qf
// Always ignore case when looking for a matching error.
regmatch.rm_ic = true;
regmatch.regprog = fmt_ptr->prog;
- int r = vim_regexec(&regmatch, linebuf, (colnr_T)0);
+ int r = vim_regexec(&regmatch, linebuf, 0);
fmt_ptr->prog = regmatch.regprog;
int status = QF_FAIL;
if (r) {
@@ -1624,7 +1678,7 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
}
if (*fields->errmsg) {
size_t textlen = strlen(qfprev->qf_text);
- size_t errlen = strlen(fields->errmsg);
+ size_t errlen = strlen(fields->errmsg);
qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2);
qfprev->qf_text[textlen] = '\n';
STRCPY(qfprev->qf_text + textlen + 1, fields->errmsg);
@@ -1804,12 +1858,14 @@ void check_quickfix_busy(void)
/// @param pattern search pattern
/// @param nr error number
/// @param type type character
+/// @param user_data custom user data or NULL
/// @param valid valid entry
///
/// @return QF_OK on success or QF_FAIL on failure.
static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, int bufnum,
char *mesg, linenr_T lnum, linenr_T end_lnum, int col, int end_col,
- char vis_col, char *pattern, int nr, char type, char valid)
+ char vis_col, char *pattern, int nr, char type, typval_T *user_data,
+ char valid)
{
qfline_T *qfp = xmalloc(sizeof(qfline_T));
@@ -1830,6 +1886,12 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in
qfp->qf_col = col;
qfp->qf_end_col = end_col;
qfp->qf_viscol = vis_col;
+ if (user_data == NULL || user_data->v_type == VAR_UNKNOWN) {
+ qfp->qf_user_data.v_type = VAR_UNKNOWN;
+ } else {
+ tv_copy(user_data, &qfp->qf_user_data);
+ qfl->qf_has_user_data = true;
+ }
if (pattern == NULL || *pattern == NUL) {
qfp->qf_pattern = NULL;
} else {
@@ -1965,6 +2027,7 @@ static int copy_loclist_entries(const qf_list_T *from_qfl, qf_list_T *to_qfl)
from_qfp->qf_pattern,
from_qfp->qf_nr,
0,
+ &from_qfp->qf_user_data,
from_qfp->qf_valid) == QF_FAIL) {
return FAIL;
}
@@ -1990,6 +2053,7 @@ static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
// Some of the fields are populated by qf_add_entry()
to_qfl->qfl_type = from_qfl->qfl_type;
to_qfl->qf_nonevalid = from_qfl->qf_nonevalid;
+ to_qfl->qf_has_user_data = from_qfl->qf_has_user_data;
to_qfl->qf_count = 0;
to_qfl->qf_index = 0;
to_qfl->qf_start = NULL;
@@ -2018,7 +2082,7 @@ static int copy_loclist(qf_list_T *from_qfl, qf_list_T *to_qfl)
// Assign a new ID for the location list
to_qfl->qf_id = ++last_qf_id;
- to_qfl->qf_changedtick = 0L;
+ to_qfl->qf_changedtick = 0;
// When no valid entries are present in the list, qf_ptr points to
// the first item in the list
@@ -2107,7 +2171,7 @@ static int qf_get_fnum(qf_list_T *qfl, char *directory, char *fname)
xfree(ptr);
} else {
xfree(qf_last_bufname);
- buf = buflist_new(bufname, NULL, (linenr_T)0, BLN_NOOPT);
+ buf = buflist_new(bufname, NULL, 0, BLN_NOOPT);
qf_last_bufname = (bufname == ptr) ? bufname : xstrdup(bufname);
set_bufref(&qf_last_bufref, buf);
}
@@ -2259,7 +2323,7 @@ static char *qf_guess_filepath(qf_list_T *qfl, char *filename)
}
/// Returns true, if a quickfix/location list with the given identifier exists.
-static bool qflist_valid(win_T *wp, unsigned int qf_id)
+static bool qflist_valid(win_T *wp, unsigned qf_id)
{
qf_info_T *qi = &ql_info;
@@ -2353,7 +2417,7 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr, int dir, int *
{
qfline_T *qf_ptr = qfl->qf_ptr;
int qf_idx = qfl->qf_index;
- char *err = e_no_more_items;
+ const char *err = e_no_more_items;
while (errornr--) {
qfline_T *prev_qf_ptr = qf_ptr;
@@ -2549,7 +2613,7 @@ static int qf_open_new_file_win(qf_info_T *ll_ref)
if (win_split(0, flags) == FAIL) {
return FAIL; // not enough room for window
}
- p_swb = empty_option; // don't split again
+ p_swb = empty_string_option; // don't split again
swb_flags = 0;
RESET_BINDING(curwin);
if (ll_ref != NULL) {
@@ -2609,7 +2673,7 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
{
win_T *win = curwin;
win_T *altwin = NULL;
- for (;;) {
+ while (true) {
if (win->w_buffer->b_fnum == qf_fnum) {
break;
}
@@ -2709,7 +2773,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
- long old_changetick = qfl->qf_changedtick;
+ int old_changetick = qfl->qf_changedtick;
int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
@@ -2722,11 +2786,11 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
no_write_message();
return FAIL;
}
- retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1,
+ retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, 1,
ECMD_HIDE + ECMD_SET_HELP,
prev_winid == curwin->handle ? curwin : NULL);
} else {
- retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
+ retval = buflist_getfile(qf_ptr->qf_fnum, 1,
GETF_SETMARK | GETF_SWITCH, forceit);
}
// If a location list, check whether the associated window is still
@@ -2746,8 +2810,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int
return QF_ABORT;
}
- if (old_qf_curlist != qi->qf_curlist // -V560
- || old_changetick != qfl->qf_changedtick // -V560
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
@@ -2789,7 +2853,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char
// Move the cursor to the first line in the buffer
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor.lnum = 0;
- if (!do_search(NULL, '/', '/', qf_pattern, (long)1, SEARCH_KEEP, NULL)) {
+ if (!do_search(NULL, '/', '/', qf_pattern, 1, SEARCH_KEEP, NULL)) {
curwin->w_cursor = save_cursor;
}
}
@@ -2799,6 +2863,8 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char
static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf_T *old_curbuf,
linenr_T old_lnum)
{
+ garray_T *const gap = qfga_get();
+
// Update the screen before showing the message, unless the screen
// scrolled up.
if (!msg_scrolled) {
@@ -2807,13 +2873,14 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
update_screen();
}
}
- snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
- qf_get_curlist(qi)->qf_count,
- qf_ptr->qf_cleared ? _(" (line deleted)") : "",
- qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
+ vim_snprintf(IObuff, IOSIZE, _("(%d of %d)%s%s: "), qf_index,
+ qf_get_curlist(qi)->qf_count,
+ qf_ptr->qf_cleared ? _(" (line deleted)") : "",
+ qf_types(qf_ptr->qf_type, qf_ptr->qf_nr));
// Add the message, skipping leading whitespace and newlines.
- int len = (int)strlen(IObuff);
- qf_fmt_text(skipwhite(qf_ptr->qf_text), IObuff + len, IOSIZE - len);
+ ga_concat(gap, IObuff);
+ qf_fmt_text(gap, skipwhite(qf_ptr->qf_text));
+ ga_append(gap, NUL);
// Output the message. Overwrite to avoid scrolling when the 'O'
// flag is present in 'shortmess'; But when not jumping, print the
@@ -2825,8 +2892,10 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
msg_scroll = false;
}
msg_ext_set_kind("quickfix");
- msg_attr_keep(IObuff, 0, true, false);
+ msg_attr_keep(gap->ga_data, 0, true, false);
msg_scroll = (int)i;
+
+ qfga_clear();
}
/// Find a usable window for opening a file from the quickfix/location list. If
@@ -2839,7 +2908,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
- long old_changetick = qfl->qf_changedtick;
+ int old_changetick = qfl->qf_changedtick;
int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
@@ -2850,7 +2919,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int
}
}
if (old_qf_curlist != qi->qf_curlist
- || old_changetick != qfl->qf_changedtick // -V560
+ || old_changetick != qfl->qf_changedtick
|| !is_qf_entry_present(qfl, qf_ptr)) {
if (qfl_type == QFLT_QUICKFIX) {
emsg(_(e_current_quickfix_list_was_changed));
@@ -3021,7 +3090,7 @@ theend:
qfl->qf_ptr = qf_ptr;
qfl->qf_index = qf_index;
}
- if (p_swb != old_swb && p_swb == empty_option) {
+ if (p_swb != old_swb && p_swb == empty_string_option) {
// Restore old 'switchbuf' value, but not when an autocommand or
// modeline has changed the value.
p_swb = old_swb;
@@ -3081,46 +3150,35 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
}
msg_putchar('\n');
- msg_outtrans_attr(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr);
+ msg_outtrans(IObuff, cursel ? HL_ATTR(HLF_QFL) : qfFileAttr);
if (qfp->qf_lnum != 0) {
msg_puts_attr(":", qfSepAttr);
}
- if (qfp->qf_lnum == 0) {
- IObuff[0] = NUL;
- } else {
- qf_range_text(qfp, IObuff, IOSIZE);
+ garray_T *gap = qfga_get();
+ if (qfp->qf_lnum != 0) {
+ qf_range_text(gap, qfp);
}
- vim_snprintf(IObuff + strlen(IObuff), IOSIZE, "%s", qf_types(qfp->qf_type, qfp->qf_nr));
- msg_puts_attr((const char *)IObuff, qfLineAttr);
+ ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr));
+ ga_append(gap, NUL);
+ msg_puts_attr(gap->ga_data, qfLineAttr);
msg_puts_attr(":", qfSepAttr);
if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff, IOSIZE);
- msg_puts((const char *)IObuff);
+ gap = qfga_get();
+ qf_fmt_text(gap, qfp->qf_pattern);
+ ga_append(gap, NUL);
+ msg_puts(gap->ga_data);
msg_puts_attr(":", qfSepAttr);
}
msg_puts(" ");
- char *tbuf = IObuff;
- size_t tbuflen = IOSIZE;
- size_t len = strlen(qfp->qf_text) + 3;
-
- if (len > IOSIZE) {
- tbuf = xmalloc(len);
- tbuflen = len;
- }
-
// Remove newlines and leading whitespace from the text. For an
// unrecognized line keep the indent, the compiler may mark a word
// with ^^^^.
- qf_fmt_text((fname != NULL || qfp->qf_lnum != 0)
- ? skipwhite(qfp->qf_text) : qfp->qf_text,
- tbuf, (int)tbuflen);
- msg_prt_line(tbuf, false);
-
- if (tbuf != IObuff) {
- xfree(tbuf);
- }
+ gap = qfga_get();
+ qf_fmt_text(gap, (fname != NULL || qfp->qf_lnum != 0) ? skipwhite(qfp->qf_text) : qfp->qf_text);
+ ga_append(gap, NUL);
+ msg_prt_line(gap->ga_data, false);
}
// ":clist": list all errors
@@ -3195,51 +3253,53 @@ void qf_list(exarg_T *eap)
}
os_breakcheck();
}
+ qfga_clear();
}
-// Remove newlines and leading whitespace from an error message.
-// Put the result in "buf[bufsize]".
-static void qf_fmt_text(const char *restrict text, char *restrict buf, int bufsize)
+/// Remove newlines and leading whitespace from an error message.
+/// Add the result to the grow array "gap".
+static void qf_fmt_text(garray_T *gap, const char *restrict text)
FUNC_ATTR_NONNULL_ALL
{
- int i;
- const char *p = (char *)text;
-
- for (i = 0; *p != NUL && i < bufsize - 1; i++) {
+ const char *p = text;
+ while (*p != NUL) {
if (*p == '\n') {
- buf[i] = ' ';
+ ga_append(gap, ' ');
while (*++p != NUL) {
if (!ascii_iswhite(*p) && *p != '\n') {
break;
}
}
} else {
- buf[i] = *p++;
+ ga_append(gap, (uint8_t)(*p++));
}
}
- buf[i] = NUL;
}
-// Range information from lnum, col, end_lnum, and end_col.
-// Put the result in "buf[bufsize]".
-static void qf_range_text(const qfline_T *qfp, char *buf, int bufsize)
+/// Add the range information from the lnum, col, end_lnum, and end_col values
+/// of a quickfix entry to the grow array "gap".
+static void qf_range_text(garray_T *gap, const qfline_T *qfp)
{
- vim_snprintf(buf, (size_t)bufsize, "%" PRIdLINENR, qfp->qf_lnum);
- int len = (int)strlen(buf);
+ char *const buf = IObuff;
+ const size_t bufsize = IOSIZE;
+
+ vim_snprintf(buf, bufsize, "%" PRIdLINENR, qfp->qf_lnum);
+ size_t len = strlen(buf);
if (qfp->qf_end_lnum > 0 && qfp->qf_lnum != qfp->qf_end_lnum) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), "-%" PRIdLINENR, qfp->qf_end_lnum);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, "-%" PRIdLINENR, qfp->qf_end_lnum);
+ len += strlen(buf + len);
}
if (qfp->qf_col > 0) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), " col %d", qfp->qf_col);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, " col %d", qfp->qf_col);
+ len += strlen(buf + len);
if (qfp->qf_end_col > 0 && qfp->qf_col != qfp->qf_end_col) {
- vim_snprintf(buf + len, (size_t)(bufsize - len), "-%d", qfp->qf_end_col);
- len += (int)strlen(buf + len);
+ vim_snprintf(buf + len, bufsize - len, "-%d", qfp->qf_end_col);
+ len += strlen(buf + len);
}
}
- buf[len] = NUL;
+
+ ga_concat_len(gap, buf, len);
}
/// Display information (list number, list size and the title) about a
@@ -3250,7 +3310,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
int count = qi->qf_lists[which].qf_count;
char buf[IOSIZE];
- vim_snprintf((char *)buf, IOSIZE, _("%serror list %d of %d; %d errors "),
+ vim_snprintf(buf, IOSIZE, _("%serror list %d of %d; %d errors "),
lead,
which + 1,
qi->qf_listcount,
@@ -3266,7 +3326,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead)
xstrlcat(buf, title, IOSIZE);
}
trunc_string(buf, buf, Columns - 1, IOSIZE);
- msg(buf);
+ msg(buf, 0);
}
/// ":colder [count]": Up in the quickfix stack.
@@ -3325,7 +3385,7 @@ void qf_history(exarg_T *eap)
}
if (qf_stack_empty(qi)) {
- msg(_("No entries"));
+ msg(_("No entries"), 0);
} else {
for (int i = 0; i < qi->qf_listcount; i++) {
qf_msg(qi, i, i == qi->qf_curlist ? "> " : " ");
@@ -3346,6 +3406,7 @@ static void qf_free_items(qf_list_T *qfl)
xfree(qfp->qf_module);
xfree(qfp->qf_text);
xfree(qfp->qf_pattern);
+ tv_clear(&qfp->qf_user_data);
stop = (qfp == qfpnext);
xfree(qfp);
if (stop) {
@@ -3353,9 +3414,10 @@ static void qf_free_items(qf_list_T *qfl)
// to avoid crashing when it's wrong.
// TODO(vim): Avoid qf_count being incorrect.
qfl->qf_count = 1;
+ } else {
+ qfl->qf_start = qfpnext;
}
}
- qfl->qf_start = qfpnext;
qfl->qf_count--;
}
@@ -3387,17 +3449,20 @@ static void qf_free(qf_list_T *qfl)
qfl->qf_ctx = NULL;
callback_free(&qfl->qf_qftf_cb);
qfl->qf_id = 0;
- qfl->qf_changedtick = 0L;
+ qfl->qf_changedtick = 0;
}
-// qf_mark_adjust: adjust marks
-bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
+/// Adjust error list entries for changed line numbers
+///
+/// Note: `buf` is the changed buffer, but `wp` is a potential location list
+/// into that buffer, or NULL to check the quickfix list.
+bool qf_mark_adjust(buf_T *buf, win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
linenr_T amount_after)
{
qf_info_T *qi = &ql_info;
int buf_has_flag = wp == NULL ? BUF_HAS_QF_ENTRY : BUF_HAS_LL_ENTRY;
- if (!(curbuf->b_has_qf_entry & buf_has_flag)) {
+ if (!(buf->b_has_qf_entry & buf_has_flag)) {
return false;
}
if (wp != NULL) {
@@ -3414,7 +3479,7 @@ bool qf_mark_adjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
qf_list_T *qfl = qf_get_list(qi, idx);
if (!qf_list_empty(qfl)) {
FOR_ALL_QFL_ITEMS(qfl, qfp, i) {
- if (qfp->qf_fnum == curbuf->b_fnum) {
+ if (qfp->qf_fnum == buf->b_fnum) {
found_one = true;
if (qfp->qf_lnum >= line1 && qfp->qf_lnum <= line2) {
if (amount == MAXLNUM) {
@@ -3475,7 +3540,7 @@ static char *qf_types(int c, int nr)
}
static char buf[20];
- snprintf((char *)buf, sizeof(buf), "%s %3d", p, nr);
+ snprintf(buf, sizeof(buf), "%s %3d", p, nr);
return buf;
}
@@ -3581,12 +3646,12 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp
static void qf_set_cwindow_options(void)
{
// switch off 'swapfile'
- set_option_value_give_err("swf", 0L, NULL, OPT_LOCAL);
- set_option_value_give_err("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value_give_err("bh", 0L, "hide", OPT_LOCAL);
+ set_option_value_give_err("swf", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value_give_err("bt", STATIC_CSTR_AS_OPTVAL("quickfix"), OPT_LOCAL);
+ set_option_value_give_err("bh", STATIC_CSTR_AS_OPTVAL("hide"), OPT_LOCAL);
RESET_BINDING(curwin);
curwin->w_p_diff = false;
- set_option_value_give_err("fdm", 0L, "manual", OPT_LOCAL);
+ set_option_value_give_err("fdm", STATIC_CSTR_AS_OPTVAL("manual"), OPT_LOCAL);
}
// Open a new quickfix or location list window, load the quickfix buffer and
@@ -3800,13 +3865,11 @@ static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index)
static int is_qf_win(const win_T *win, const qf_info_T *qi)
FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- //
// A window displaying the quickfix buffer will have the w_llist_ref field
// set to NULL.
// A window displaying a location list buffer will have the w_llist_ref
// pointing to the location list.
- //
- if (bt_quickfix(win->w_buffer)) {
+ if (buf_valid(win->w_buffer) && bt_quickfix(win->w_buffer)) {
if ((IS_QF_STACK(qi) && win->w_llist_ref == NULL)
|| (IS_LL_STACK(qi) && win->w_llist_ref == qi)) {
return true;
@@ -3854,11 +3917,12 @@ static buf_T *qf_find_buf(qf_info_T *qi)
}
/// Process the 'quickfixtextfunc' option value.
-void qf_process_qftf_option(char **errmsg)
+const char *did_set_quickfixtextfunc(optset_T *args FUNC_ATTR_UNUSED)
{
if (option_set_callback_func(p_qftf, &qftf_cb) == FAIL) {
- *errmsg = e_invarg;
+ return e_invarg;
}
+ return NULL;
}
/// Update the w:quickfix_title variable in the quickfix/location list window in
@@ -3945,21 +4009,21 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
char *dirname, char *qftf_str, bool first_bufline)
FUNC_ATTR_NONNULL_ARG(1, 2, 4, 5)
{
+ garray_T *gap = qfga_get();
+
// If the 'quickfixtextfunc' function returned a non-empty custom string
// for this entry, then use it.
if (qftf_str != NULL && *qftf_str != NUL) {
- xstrlcpy(IObuff, qftf_str, IOSIZE);
+ ga_concat(gap, qftf_str);
} else {
buf_T *errbuf;
- int len;
if (qfp->qf_module != NULL) {
- xstrlcpy(IObuff, qfp->qf_module, IOSIZE);
- len = (int)strlen(IObuff);
+ ga_concat(gap, qfp->qf_module);
} else if (qfp->qf_fnum != 0
&& (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
&& errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep
- xstrlcpy(IObuff, path_tail(errbuf->b_fname), IOSIZE);
+ ga_concat(gap, path_tail(errbuf->b_fname));
} else {
// Shorten the file name if not done already.
// For optimization, do this only for the first entry in a
@@ -3972,48 +4036,38 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
}
shorten_buf_fname(errbuf, dirname, false);
}
- xstrlcpy(IObuff, errbuf->b_fname, IOSIZE);
+ ga_concat(gap, errbuf->b_fname);
}
- len = (int)strlen(IObuff);
- } else {
- len = 0;
}
- if (len < IOSIZE - 1) {
- IObuff[len++] = '|';
- }
- if (qfp->qf_lnum > 0) {
- qf_range_text(qfp, IObuff + len, IOSIZE - len);
- len += (int)strlen(IObuff + len);
- snprintf(IObuff + len, (size_t)(IOSIZE - len), "%s", qf_types(qfp->qf_type,
- qfp->qf_nr));
- len += (int)strlen(IObuff + len);
+ ga_append(gap, '|');
+
+ if (qfp->qf_lnum > 0) {
+ qf_range_text(gap, qfp);
+ ga_concat(gap, qf_types(qfp->qf_type, qfp->qf_nr));
} else if (qfp->qf_pattern != NULL) {
- qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
- len += (int)strlen(IObuff + len);
- }
- if (len < IOSIZE - 2) {
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
+ qf_fmt_text(gap, qfp->qf_pattern);
}
+ ga_append(gap, '|');
+ ga_append(gap, ' ');
// Remove newlines and leading whitespace from the text.
// For an unrecognized line keep the indent, the compiler may
// mark a word with ^^^^.
- qf_fmt_text(len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text,
- IObuff + len, IOSIZE - len);
+ qf_fmt_text(gap, gap->ga_len > 3 ? skipwhite(qfp->qf_text) : qfp->qf_text);
}
- if (ml_append_buf(buf, lnum, IObuff,
- (colnr_T)strlen(IObuff) + 1, false) == FAIL) {
+ ga_append(gap, NUL);
+ if (ml_append_buf(buf, lnum, gap->ga_data, gap->ga_len, false) == FAIL) {
return FAIL;
}
+
return OK;
}
// Call the 'quickfixtextfunc' function to get the list of lines to display in
// the quickfix window for the entries 'start_idx' to 'end_idx'.
-static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, long start_idx, long end_idx)
+static list_T *call_qftf_func(qf_list_T *qfl, int qf_winid, int start_idx, int end_idx)
{
Callback *cb = &qftf_cb;
list_T *qftf_list = NULL;
@@ -4078,7 +4132,12 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// delete all existing lines
while ((curbuf->b_ml.ml_flags & ML_EMPTY) == 0) {
- (void)ml_delete((linenr_T)1, false);
+ // If deletion fails, this loop may run forever, so
+ // signal error and return.
+ if (ml_delete(1, false) == FAIL) {
+ internal_error("qf_fill_buffer()");
+ return;
+ }
}
}
@@ -4104,7 +4163,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
lnum = buf->b_ml.ml_line_count;
}
- list_T *qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, (long)qfl->qf_count);
+ list_T *qftf_list = call_qftf_func(qfl, qf_winid, lnum + 1, qfl->qf_count);
listitem_T *qftf_li = tv_list_first(qftf_list);
int prev_bufnr = -1;
@@ -4142,6 +4201,8 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// Delete the empty line which is now at the end
(void)ml_delete(lnum + 1, false);
}
+
+ qfga_clear();
}
// Correct cursor position.
@@ -4152,7 +4213,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
// resembles reading a file into a buffer, it's more logical when using
// autocommands.
curbuf->b_ro_locked++;
- set_option_value_give_err("ft", 0L, "qf", OPT_LOCAL);
+ set_option_value_give_err("ft", STATIC_CSTR_AS_OPTVAL("qf"), OPT_LOCAL);
curbuf->b_p_ma = false;
keep_filetype = true; // don't detect 'filetype'
@@ -4263,11 +4324,11 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname)
len += strlen(p_sp) + strlen(fname) + 3;
}
char *const cmd = xmalloc(len);
- snprintf(cmd, len, "%s%s%s", p_shq, (char *)makecmd, p_shq);
+ snprintf(cmd, len, "%s%s%s", p_shq, makecmd, p_shq);
// If 'shellpipe' empty: don't redirect to 'errorfile'.
if (*p_sp != NUL) {
- append_redir(cmd, len, p_sp, (char *)fname);
+ append_redir(cmd, len, p_sp, fname);
}
// Display the fully formed command. Output a newline if there's something
@@ -4278,7 +4339,7 @@ static char *make_get_fullcmd(const char *makecmd, const char *fname)
}
msg_start();
msg_puts(":!");
- msg_outtrans(cmd); // show what we are doing
+ msg_outtrans(cmd, 0); // show what we are doing
return cmd;
}
@@ -4391,7 +4452,7 @@ static char *get_mef_name(void)
}
// Keep trying until the name doesn't exist yet.
- for (;;) {
+ while (true) {
if (start == -1) {
start = (int)os_get_pid();
} else {
@@ -5129,9 +5190,9 @@ static void vgr_display_fname(char *fname)
msg_start();
char *p = msg_strtrunc(fname, true);
if (p == NULL) {
- msg_outtrans(fname);
+ msg_outtrans(fname, 0);
} else {
- msg_outtrans(p);
+ msg_outtrans(p, 0);
xfree(p);
}
msg_clr_eos();
@@ -5148,7 +5209,7 @@ static buf_T *vgr_load_dummy_buf(char *fname, char *dirname_start, char *dirname
// indent scripts, a great speed improvement.
char *save_ei = au_event_disable(",Filetype");
- long save_mls = p_mls;
+ OptInt save_mls = p_mls;
p_mls = 0;
// Load file into a buffer, so that 'fileencoding' is detected,
@@ -5187,11 +5248,14 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid, char *titl
/// 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 *fname, buf_T *buf, char *spat,
- regmmatch_T *regmatch, long *tomatch, int duplicate_name, int flags)
+ regmmatch_T *regmatch, int *tomatch, int duplicate_name, int flags)
FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5, 6)
{
bool found_match = false;
- const size_t pat_len = strlen(spat);
+ size_t pat_len = strlen(spat);
+ if (pat_len > MAX_FUZZY_MATCHES) {
+ pat_len = MAX_FUZZY_MATCHES;
+ }
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
@@ -5206,7 +5270,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
fname,
NULL,
duplicate_name ? 0 : buf->b_fnum,
- ml_get_buf(buf, regmatch->startpos[0].lnum + lnum, false),
+ ml_get_buf(buf, regmatch->startpos[0].lnum + lnum),
regmatch->startpos[0].lnum + lnum,
regmatch->endpos[0].lnum + lnum,
regmatch->startpos[0].col + 1,
@@ -5215,6 +5279,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
NULL, // search pattern
0, // nr
0, // type
+ NULL, // user_data
true) // valid
== QF_FAIL) {
got_int = true;
@@ -5228,17 +5293,18 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
break;
}
col = regmatch->endpos[0].col + (col == regmatch->endpos[0].col);
- if (col > (colnr_T)strlen(ml_get_buf(buf, lnum, false))) {
+ if (col > (colnr_T)strlen(ml_get_buf(buf, lnum))) {
break;
}
}
} else {
- char *const str = ml_get_buf(buf, lnum, false);
+ char *const str = ml_get_buf(buf, lnum);
int score;
uint32_t matches[MAX_FUZZY_MATCHES];
const size_t sz = sizeof(matches) / sizeof(matches[0]);
// Fuzzy string match
+ CLEAR_FIELD(matches);
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
@@ -5257,6 +5323,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
NULL, // search pattern
0, // nr
0, // type
+ NULL, // user_data
true) // valid
== QF_FAIL) {
got_int = true;
@@ -5380,7 +5447,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo
// ":lcd %:p:h" changes the meaning of short path names.
os_dirname(dirname_start, MAXPATHL);
- time_t seconds = (time_t)0;
+ time_t seconds = 0;
for (int fi = 0; fi < cmd_args->fcount && !got_int && cmd_args->tomatch > 0; fi++) {
char *fname = path_try_shorten_fname(cmd_args->fnames[fi]);
if (time(NULL) > seconds) {
@@ -5413,7 +5480,7 @@ static int vgr_process_files(win_T *wp, qf_info_T *qi, vgr_args_T *cmd_args, boo
if (buf == NULL) {
if (!got_int) {
- smsg(_("Cannot open file \"%s\""), fname);
+ smsg(0, _("Cannot open file \"%s\""), fname);
}
} else {
// Try for a match in all lines of the buffer.
@@ -5618,7 +5685,7 @@ static void restore_start_dir(char *dirname_start)
static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resulting_dir)
{
// Allocate a buffer without putting it in the buffer list.
- buf_T *newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY);
+ buf_T *newbuf = buflist_new(NULL, NULL, 1, BLN_DUMMY);
if (newbuf == NULL) {
return NULL;
}
@@ -5650,7 +5717,7 @@ static buf_T *load_dummy_buffer(char *fname, char *dirname_start, char *resultin
bufref_T newbuf_to_wipe;
newbuf_to_wipe.br_buf = NULL;
- int readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0,
+ int readfile_result = readfile(fname, NULL, 0, 0,
(linenr_T)MAXLNUM, NULL,
READ_NEW | READ_DUMMY, false);
newbuf->b_locked--;
@@ -5772,28 +5839,21 @@ static int get_qfline_items(qfline_T *qfp, list_T *list)
buf[0] = qfp->qf_type;
buf[1] = NUL;
if (tv_dict_add_nr(dict, S_LEN("bufnr"), (varnumber_T)bufnum) == FAIL
- || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("end_lnum"), (varnumber_T)qfp->qf_end_lnum)
- == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("lnum"), (varnumber_T)qfp->qf_lnum) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("end_lnum"), (varnumber_T)qfp->qf_end_lnum) == FAIL)
|| (tv_dict_add_nr(dict, S_LEN("col"), (varnumber_T)qfp->qf_col) == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("end_col"), (varnumber_T)qfp->qf_end_col)
- == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol)
- == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("end_col"), (varnumber_T)qfp->qf_end_col) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("vcol"), (varnumber_T)qfp->qf_viscol) == FAIL)
|| (tv_dict_add_nr(dict, S_LEN("nr"), (varnumber_T)qfp->qf_nr) == FAIL)
- || (tv_dict_add_str(dict, S_LEN("module"),
- (qfp->qf_module == NULL ? "" : (const char *)qfp->qf_module))
- == FAIL)
- || (tv_dict_add_str(dict, S_LEN("pattern"),
- (qfp->qf_pattern == NULL ? "" : (const char *)qfp->qf_pattern))
+ || (tv_dict_add_str(dict, S_LEN("module"), (qfp->qf_module == NULL ? "" : qfp->qf_module))
== FAIL)
- || (tv_dict_add_str(dict, S_LEN("text"),
- (qfp->qf_text == NULL ? "" : (const char *)qfp->qf_text))
+ || (tv_dict_add_str(dict, S_LEN("pattern"), (qfp->qf_pattern == NULL ? "" : qfp->qf_pattern))
== FAIL)
- || (tv_dict_add_str(dict, S_LEN("type"), (const char *)buf) == FAIL)
- || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid)
- == FAIL)) {
+ || (tv_dict_add_str(dict, S_LEN("text"), (qfp->qf_text == NULL ? "" : qfp->qf_text)) == FAIL)
+ || (tv_dict_add_str(dict, S_LEN("type"), buf) == FAIL)
+ || (qfp->qf_user_data.v_type != VAR_UNKNOWN
+ && tv_dict_add_tv(dict, S_LEN("user_data"), &qfp->qf_user_data) == FAIL)
+ || (tv_dict_add_nr(dict, S_LEN("valid"), (varnumber_T)qfp->qf_valid) == FAIL)) {
// tv_dict_add* fail only if key already exist, but this is a newly
// allocated dictionary which is thus guaranteed to have no existing keys.
abort();
@@ -5897,7 +5957,7 @@ static int qf_get_list_from_lines(dict_T *what, dictitem_T *di, dict_T *retdict)
qf_info_T *const qi = qf_alloc_stack(QFLT_INTERNAL);
if (qf_init_ext(qi, 0, NULL, NULL, &di->di_tv, errorformat,
- true, (linenr_T)0, (linenr_T)0, NULL, NULL) > 0) {
+ true, 0, 0, NULL, NULL) > 0) {
(void)get_errorlist(qi, NULL, 0, 0, l);
qf_free(&qi->qf_lists[0]);
}
@@ -6016,8 +6076,7 @@ static int qf_getprop_qfidx(qf_info_T *qi, dict_T *what)
qf_idx = INVALID_QFIDX;
}
}
- } else if (di->di_tv.v_type == VAR_STRING
- && strequal((const char *)di->di_tv.vval.v_string, "$")) {
+ } else if (di->di_tv.v_type == VAR_STRING && strequal(di->di_tv.vval.v_string, "$")) {
// Get the last quickfix list number
qf_idx = qi->qf_listcount - 1;
} else {
@@ -6046,7 +6105,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
int status = OK;
if (flags & QF_GETLIST_TITLE) {
- status = tv_dict_add_str(retdict, S_LEN("title"), (const char *)"");
+ status = tv_dict_add_str(retdict, S_LEN("title"), "");
}
if ((status == OK) && (flags & QF_GETLIST_ITEMS)) {
list_T *l = tv_list_alloc(kListLenMayKnow);
@@ -6059,7 +6118,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
status = tv_dict_add_nr(retdict, S_LEN("winid"), qf_winid(qi));
}
if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) {
- status = tv_dict_add_str(retdict, S_LEN("context"), (const char *)"");
+ status = tv_dict_add_str(retdict, S_LEN("context"), "");
}
if ((status == OK) && (flags & QF_GETLIST_ID)) {
status = tv_dict_add_nr(retdict, S_LEN("id"), 0);
@@ -6089,8 +6148,7 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r
/// Return the quickfix list title as 'title' in retdict
static int qf_getprop_title(qf_list_T *qfl, dict_T *retdict)
{
- return tv_dict_add_str(retdict, S_LEN("title"),
- (const char *)qfl->qf_title);
+ return tv_dict_add_str(retdict, S_LEN("title"), qfl->qf_title);
}
// Returns the identifier of the window used to display files from a location
@@ -6274,8 +6332,7 @@ static int qf_setprop_qftf(qf_list_T *qfl, dictitem_T *di)
/// Add a new quickfix entry to list at 'qf_idx' in the stack 'qi' from the
/// items in the dict 'd'. If it is a valid error entry, then set 'valid_entry'
/// to true.
-static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_entry,
- bool *valid_entry)
+static int qf_add_entry_from_dict(qf_list_T *qfl, dict_T *d, bool first_entry, bool *valid_entry)
FUNC_ATTR_NONNULL_ALL
{
static bool did_bufnr_emsg;
@@ -6299,6 +6356,9 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en
if (text == NULL) {
text = xcalloc(1, 1);
}
+ typval_T user_data = { .v_type = VAR_UNKNOWN };
+ tv_dict_get_tv(d, "user_data", &user_data);
+
bool valid = true;
if ((filename == NULL && bufnum == 0)
|| (lnum == 0 && pattern == NULL)) {
@@ -6335,12 +6395,14 @@ static int qf_add_entry_from_dict(qf_list_T *qfl, const dict_T *d, bool first_en
pattern, // search pattern
nr,
type == NULL ? NUL : *type,
+ &user_data,
valid);
xfree(filename);
xfree(module);
xfree(pattern);
xfree(text);
+ tv_clear(&user_data);
if (valid) {
*valid_entry = true;
@@ -6376,13 +6438,12 @@ static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, char *title,
continue; // Skip non-dict items.
}
- const dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dict_T *const d = TV_LIST_ITEM_TV(li)->vval.v_dict;
if (d == NULL) {
continue;
}
- retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list),
- &valid_entry);
+ retval = qf_add_entry_from_dict(qfl, d, li == tv_list_first(list), &valid_entry);
if (retval == QF_FAIL) {
break;
}
@@ -6439,8 +6500,7 @@ static int qf_setprop_get_qfidx(const qf_info_T *qi, const dict_T *what, int act
} else if (action != ' ') {
*newlist = false; // use the specified list
}
- } else if (di->di_tv.v_type == VAR_STRING
- && strequal((const char *)di->di_tv.vval.v_string, "$")) {
+ } else if (di->di_tv.v_type == VAR_STRING && strequal(di->di_tv.vval.v_string, "$")) {
if (!qf_stack_empty(qi)) {
qf_idx = qi->qf_listcount - 1;
} else if (*newlist) {
@@ -6525,7 +6585,7 @@ static int qf_setprop_items_from_lines(qf_info_T *qi, int qf_idx, const dict_T *
qf_free_items(&qi->qf_lists[qf_idx]);
}
if (qf_init_ext(qi, qf_idx, NULL, NULL, &di->di_tv, errorformat,
- false, (linenr_T)0, (linenr_T)0, NULL, NULL) >= 0) {
+ false, 0, 0, NULL, NULL) >= 0) {
retval = OK;
}
@@ -6721,6 +6781,27 @@ int set_errorlist(win_T *wp, list_T *list, int action, char *title, dict_T *what
return retval;
}
+static bool mark_quickfix_user_data(qf_info_T *qi, int copyID)
+{
+ bool abort = false;
+ for (int i = 0; i < LISTCOUNT && !abort; i++) {
+ qf_list_T *qfl = &qi->qf_lists[i];
+ if (!qfl->qf_has_user_data) {
+ continue;
+ }
+ qfline_T *qfp;
+ int j;
+ FOR_ALL_QFL_ITEMS(qfl, qfp, j) {
+ typval_T *user_data = &qfp->qf_user_data;
+ if (user_data != NULL && user_data->v_type != VAR_NUMBER
+ && user_data->v_type != VAR_STRING && user_data->v_type != VAR_FLOAT) {
+ abort = abort || set_ref_in_item(user_data, copyID, NULL, NULL);
+ }
+ }
+ }
+ return abort;
+}
+
/// Mark the quickfix context and callback function as in use for all the lists
/// in a quickfix stack.
static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
@@ -6750,6 +6831,11 @@ bool set_ref_in_quickfix(int copyID)
return abort;
}
+ abort = mark_quickfix_user_data(&ql_info, copyID);
+ if (abort) {
+ return abort;
+ }
+
abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL);
if (abort) {
return abort;
@@ -6761,6 +6847,11 @@ bool set_ref_in_quickfix(int copyID)
if (abort) {
return abort;
}
+
+ abort = mark_quickfix_user_data(win->w_llist, copyID);
+ if (abort) {
+ return abort;
+ }
}
if (IS_LL_WINDOW(win) && (win->w_llist_ref->qf_refcount == 1)) {
@@ -6946,18 +7037,18 @@ void ex_cexpr(exarg_T *eap)
// Evaluate the expression. When the result is a string or a list we can
// use it to fill the errorlist.
- typval_T tv;
- if (eval0(eap->arg, &tv, &eap->nextcmd, true) == FAIL) {
+ typval_T *tv = eval_expr(eap->arg, eap);
+ if (tv == NULL) {
return;
}
- if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
- || tv.v_type == VAR_LIST) {
+ if ((tv->v_type == VAR_STRING && tv->vval.v_string != NULL)
+ || tv->v_type == VAR_LIST) {
incr_quickfix_busy();
- int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, &tv, p_efm,
+ int res = qf_init_ext(qi, qi->qf_curlist, NULL, NULL, tv, p_efm,
(eap->cmdidx != CMD_caddexpr
&& eap->cmdidx != CMD_laddexpr),
- (linenr_T)0, (linenr_T)0,
+ 0, 0,
qf_cmdtitle(*eap->cmdlinep), NULL);
if (qf_stack_empty(qi)) {
decr_quickfix_busy();
@@ -6985,7 +7076,7 @@ void ex_cexpr(exarg_T *eap)
emsg(_("E777: String or List expected"));
}
cleanup:
- tv_clear(&tv);
+ tv_free(tv);
}
// Get the location list for ":lhelpgrep"
@@ -7018,7 +7109,7 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch)
while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) {
char *line = IObuff;
- if (vim_regexec(p_regmatch, line, (colnr_T)0)) {
+ if (vim_regexec(p_regmatch, line, 0)) {
int l = (int)strlen(line);
// remove trailing CR, LF, spaces, etc.
@@ -7041,7 +7132,8 @@ static void hgr_search_file(qf_list_T *qfl, char *fname, regmatch_T *p_regmatch)
NULL, // search pattern
0, // nr
1, // type
- true) // valid
+ NULL, // user_data
+ true) // valid
== QF_FAIL) {
got_int = true;
if (line != IObuff) {
@@ -7101,7 +7193,7 @@ static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch, const char
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
- hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, (char *)lang);
+ hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, lang);
}
}
@@ -7130,7 +7222,7 @@ void ex_helpgrep(exarg_T *eap)
// Make 'cpoptions' empty, the 'l' flag should not be used here.
char *const save_cpo = p_cpo;
const bool save_cpo_allocated = is_option_allocated("cpo");
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
bool new_qi = false;
if (is_loclist_cmd(eap->cmdidx)) {
@@ -7161,13 +7253,13 @@ void ex_helpgrep(exarg_T *eap)
updated = true;
}
- if (p_cpo == empty_option) {
+ if (p_cpo == empty_string_option) {
p_cpo = save_cpo;
} else {
// Darn, some plugin changed the value. If it's still empty it was
// changed and restored, need to restore in the complicated way.
if (*p_cpo == NUL) {
- set_option_value_give_err("cpo", 0L, save_cpo, 0);
+ set_option_value_give_err("cpo", CSTR_AS_OPTVAL(save_cpo), 0);
}
if (save_cpo_allocated) {
free_string_option(save_cpo);
@@ -7214,6 +7306,19 @@ void ex_helpgrep(exarg_T *eap)
}
}
+#if defined(EXITFREE)
+void free_quickfix(void)
+{
+ qf_free_all(NULL);
+ // Free all location lists
+ FOR_ALL_TAB_WINDOWS(tab, win) {
+ qf_free_all(win);
+ }
+
+ ga_clear(&qfga);
+}
+#endif
+
static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv)
{
if (what_arg->v_type == VAR_UNKNOWN) {
@@ -7250,7 +7355,7 @@ void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_qf_loc_list(true, NULL, &argvars[0], rettv);
}
-/// Create quickfix/location list from VimL values
+/// Create quickfix/location list from Vimscript values
///
/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
/// args argument in which case errors out, including VAR_UNKNOWN parameters.
@@ -7268,7 +7373,7 @@ void f_getqflist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(2, 3)
{
- static char *e_invact = N_("E927: Invalid action: '%s'");
+ static const char *e_invact = N_("E927: Invalid action: '%s'");
const char *title = NULL;
char action = ' ';
static int recursive = 0;
diff --git a/src/nvim/quickfix.h b/src/nvim/quickfix.h
index 0da43e436c..9c49564d57 100644
--- a/src/nvim/quickfix.h
+++ b/src/nvim/quickfix.h
@@ -1,15 +1,18 @@
-#ifndef NVIM_QUICKFIX_H
-#define NVIM_QUICKFIX_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
-// flags for skip_vimgrep_pat()
-#define VGR_GLOBAL 1
-#define VGR_NOJUMP 2
-#define VGR_FUZZY 4
+/// flags for skip_vimgrep_pat()
+enum {
+ VGR_GLOBAL = 1,
+ VGR_NOJUMP = 2,
+ VGR_FUZZY = 4,
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "quickfix.h.generated.h"
#endif
-#endif // NVIM_QUICKFIX_H
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
index 1088dd3778..f74f68adb6 100644
--- a/src/nvim/rbuffer.c
+++ b/src/nvim/rbuffer.c
@@ -1,12 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/rbuffer.h"
diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h
index 63d5119004..55e9849d3d 100644
--- a/src/nvim/rbuffer.h
+++ b/src/nvim/rbuffer.h
@@ -11,14 +11,11 @@
// stopped(automatic backpressure handling)
//
// Reference: http://en.wikipedia.org/wiki/Circular_buffer
-#ifndef NVIM_RBUFFER_H
-#define NVIM_RBUFFER_H
+#pragma once
#include <stddef.h>
#include <stdint.h>
-struct rbuffer;
-
// Macros that simplify working with the read/write pointers directly by hiding
// ring buffer wrap logic. Some examples:
//
@@ -38,8 +35,6 @@ struct rbuffer;
//
// Note that the rbuffer_{produced,consumed} calls are necessary or these macros
// create infinite loops
-//
-// -V:RBUFFER_UNTIL_EMPTY:1044
#define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \
for (size_t rcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \
for (char *rptr = rbuffer_read_ptr(buf, &rcnt); /* NOLINT(readability/braces) */ \
@@ -88,5 +83,3 @@ struct rbuffer {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "rbuffer.h.generated.h"
#endif
-
-#endif // NVIM_RBUFFER_H
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 122f3e2020..3536196a3b 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub()
// By default: do not create debugging logs or files related to regular
@@ -10,38 +7,170 @@
// #define REGEXP_DEBUG
#include <assert.h>
+#include <ctype.h>
#include <inttypes.h>
+#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <string.h>
#include <sys/types.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+// Structure used to save the current input state, when it needs to be
+// restored after trying a match. Used by reg_save() and reg_restore().
+// Also stores the length of "backpos".
+typedef struct {
+ union {
+ uint8_t *ptr; // rex.input pointer, for single-line regexp
+ lpos_T pos; // rex.input pos, for multi-line regexp
+ } rs_u;
+ int rs_len;
+} regsave_T;
+
+// struct to save start/end pointer/position in for \(\)
+typedef struct {
+ union {
+ uint8_t *ptr;
+ lpos_T pos;
+ } se_u;
+} save_se_T;
+
+// Values for rs_state in regitem_T.
+typedef enum regstate_E {
+ RS_NOPEN = 0, // NOPEN and NCLOSE
+ RS_MOPEN, // MOPEN + [0-9]
+ RS_MCLOSE, // MCLOSE + [0-9]
+ RS_ZOPEN, // ZOPEN + [0-9]
+ RS_ZCLOSE, // ZCLOSE + [0-9]
+ RS_BRANCH, // BRANCH
+ RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match
+ RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match
+ RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match
+ RS_NOMATCH, // NOMATCH
+ RS_BEHIND1, // BEHIND / NOBEHIND matching rest
+ RS_BEHIND2, // BEHIND / NOBEHIND matching behind part
+ RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match
+ RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match
+} regstate_T;
+
+// When there are alternatives a regstate_T is put on the regstack to remember
+// what we are doing.
+// Before it may be another type of item, depending on rs_state, to remember
+// more things.
+typedef struct regitem_S {
+ regstate_T rs_state; // what we are doing, one of RS_ above
+ int16_t rs_no; // submatch nr or BEHIND/NOBEHIND
+ uint8_t *rs_scan; // current node in program
+ union {
+ save_se_T sesave;
+ regsave_T regsave;
+ } rs_un; // room for saving rex.input
+} regitem_T;
+
+// used for BEHIND and NOBEHIND matching
+typedef struct regbehind_S {
+ regsave_T save_after;
+ regsave_T save_behind;
+ int save_need_clear_subexpr;
+ save_se_T save_start[NSUBEXP];
+ save_se_T save_end[NSUBEXP];
+} regbehind_T;
+
+// Since the out pointers in the list are always
+// uninitialized, we use the pointers themselves
+// as storage for the Ptrlists.
+typedef union Ptrlist Ptrlist;
+union Ptrlist {
+ Ptrlist *next;
+ nfa_state_T *s;
+};
+
+struct Frag {
+ nfa_state_T *start;
+ Ptrlist *out;
+};
+typedef struct Frag Frag_T;
+
+typedef struct {
+ int in_use; ///< number of subexpr with useful info
+
+ // When REG_MULTI is true list.multi is used, otherwise list.line.
+ union {
+ struct multipos {
+ linenr_T start_lnum;
+ linenr_T end_lnum;
+ colnr_T start_col;
+ colnr_T end_col;
+ } multi[NSUBEXP];
+ struct linepos {
+ uint8_t *start;
+ uint8_t *end;
+ } line[NSUBEXP];
+ } list;
+ colnr_T orig_start_col; // list.multi[0].start_col without \zs
+} regsub_T;
+
+typedef struct {
+ regsub_T norm; // \( .. \) matches
+ regsub_T synt; // \z( .. \) matches
+} regsubs_T;
+
+// nfa_pim_T stores a Postponed Invisible Match.
+typedef struct nfa_pim_S nfa_pim_T;
+struct nfa_pim_S {
+ int result; // NFA_PIM_*, see below
+ nfa_state_T *state; // the invisible match start state
+ regsubs_T subs; // submatch info, only party used
+ union {
+ lpos_T pos;
+ uint8_t *ptr;
+ } end; // where the match must end
+};
+
+// nfa_thread_T contains execution information of a NFA state
+typedef struct {
+ nfa_state_T *state;
+ int count;
+ nfa_pim_T pim; // if pim.result != NFA_PIM_UNUSED: postponed
+ // invisible match
+ regsubs_T subs; // submatch info, only party used
+} nfa_thread_T;
+
+// nfa_list_T contains the alternative NFA execution states.
+typedef struct {
+ nfa_thread_T *t; ///< allocated array of states
+ int n; ///< nr of states currently in "t"
+ int len; ///< max nr of states in "t"
+ int id; ///< ID of the list
+ int has_pim; ///< true when any state has a PIM
+} nfa_list_T;
#ifdef REGEXP_DEBUG
// show/save debugging data when BT engine is used
@@ -59,11 +188,7 @@
#define un_Magic(x) ((x) + 256)
#define is_Magic(x) ((x) < 0)
-// We should define ftpr as a pointer to a function returning a pointer to
-// a function returning a pointer to a function ...
-// This is impossible, so we declare a pointer to a function returning a
-// pointer to a function returning void. This should work for all compilers.
-typedef void (*(*fptr_T)(int *, int))(void);
+typedef void (*fptr_T)(int *, int);
static int no_Magic(int x)
{
@@ -97,31 +222,41 @@ static int toggle_Magic(int x)
#define EMSG2_RET_NULL(m, c) \
return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL)
#define EMSG3_RET_NULL(m, c, a) \
- return (semsg((const char *)(m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL)
+ return (semsg((m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL)
#define EMSG2_RET_FAIL(m, c) \
return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL)
-#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_("E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL)
-
-#define MAX_LIMIT (32767L << 16L)
-
-static char e_missingbracket[] = N_("E769: Missing ] after %s[");
-static char e_reverse_range[] = N_("E944: Reverse range in character class");
-static char e_large_class[] = N_("E945: Range too large in character class");
-static char e_unmatchedpp[] = N_("E53: Unmatched %s%%(");
-static char e_unmatchedp[] = N_("E54: Unmatched %s(");
-static char e_unmatchedpar[] = N_("E55: Unmatched %s)");
-static char e_z_not_allowed[] = N_("E66: \\z( not allowed here");
-static char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
-static char e_missing_sb[] = N_("E69: Missing ] after %s%%[");
-static char e_empty_sb[] = N_("E70: Empty %s%%[]");
-static char e_recursive[] = N_("E956: Cannot use pattern recursively");
-static char e_regexp_number_after_dot_pos_search_chr[]
+#define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_(e_invalid_item_in_str_brackets), reg_magic == MAGIC_ALL)
+
+#define MAX_LIMIT (32767 << 16)
+
+static const char e_invalid_character_after_str_at[]
+ = N_("E59: Invalid character after %s@");
+static const char e_invalid_use_of_underscore[]
+ = N_("E63: Invalid use of \\_");
+static const char e_pattern_uses_more_memory_than_maxmempattern[]
+ = N_("E363: Pattern uses more memory than 'maxmempattern'");
+static const char e_invalid_item_in_str_brackets[]
+ = N_("E369: Invalid item in %s%%[]");
+static const char e_missing_delimiter_after_search_pattern_str[]
+ = N_("E654: Missing delimiter after search pattern: %s");
+static const char e_missingbracket[] = N_("E769: Missing ] after %s[");
+static const char e_reverse_range[] = N_("E944: Reverse range in character class");
+static const char e_large_class[] = N_("E945: Range too large in character class");
+static const char e_unmatchedpp[] = N_("E53: Unmatched %s%%(");
+static const char e_unmatchedp[] = N_("E54: Unmatched %s(");
+static const char e_unmatchedpar[] = N_("E55: Unmatched %s)");
+static const char e_z_not_allowed[] = N_("E66: \\z( not allowed here");
+static const char e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
+static const char e_missing_sb[] = N_("E69: Missing ] after %s%%[");
+static const char e_empty_sb[] = N_("E70: Empty %s%%[]");
+static const char e_recursive[] = N_("E956: Cannot use pattern recursively");
+static const char e_regexp_number_after_dot_pos_search_chr[]
= N_("E1204: No Number allowed after .: '\\%%%c'");
-static char e_nfa_regexp_missing_value_in_chr[]
+static const char e_nfa_regexp_missing_value_in_chr[]
= N_("E1273: (NFA regexp) missing value in '\\%%%c'");
-static char e_atom_engine_must_be_at_start_of_pattern[]
+static const char e_atom_engine_must_be_at_start_of_pattern[]
= N_("E1281: Atom '\\%%#=%c' must be at the start of the pattern");
-static char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
+static const char e_substitute_nesting_too_deep[] = N_("E1290: substitute nesting too deep");
#define NOT_MULTI 0
#define MULTI_ONE 1
@@ -491,7 +626,7 @@ char *skip_regexp_err(char *startp, int delim, int magic)
char *p = skip_regexp(startp, delim, magic);
if (*p != delim) {
- semsg(_("E654: missing delimiter after search pattern: %s"), startp);
+ semsg(_(e_missing_delimiter_after_search_pattern_str), startp);
return NULL;
}
return p;
@@ -869,7 +1004,7 @@ static int64_t getoctchrs(void)
int c;
int i;
- for (i = 0; i < 3 && nr < 040; i++) { // -V536
+ for (i = 0; i < 3 && nr < 040; i++) {
c = (uint8_t)regparse[0];
if (c < '0' || c > '7') {
break;
@@ -889,11 +1024,11 @@ static int64_t getoctchrs(void)
// If the first character is '-', then the range is reversed.
// Should end with 'end'. If minval is missing, zero is default, if maxval is
// missing, a very big number is the default.
-static int read_limits(long *minval, long *maxval)
+static int read_limits(int *minval, int *maxval)
{
int reverse = false;
char *first_char;
- long tmp;
+ int tmp;
if (*regparse == '-') {
// Starts with '-', so reverse the range later.
@@ -901,10 +1036,10 @@ static int read_limits(long *minval, long *maxval)
reverse = true;
}
first_char = regparse;
- *minval = getdigits_long(&regparse, false, 0);
+ *minval = getdigits_int(&regparse, false, 0);
if (*regparse == ',') { // There is a comma.
if (ascii_isdigit(*++regparse)) {
- *maxval = getdigits_long(&regparse, false, MAX_LIMIT);
+ *maxval = getdigits_int(&regparse, false, MAX_LIMIT);
} else {
*maxval = MAX_LIMIT;
}
@@ -989,6 +1124,8 @@ typedef struct {
// flag in the regexp. Defaults to false, always.
bool reg_icombine;
+ bool reg_nobreak;
+
// Copy of "rmm_maxcol": maximum column to search for a match. Zero when
// there is no maximum.
colnr_T reg_maxcol;
@@ -1011,6 +1148,13 @@ typedef struct {
static regexec_T rex;
static bool rex_in_use = false;
+static void reg_breakcheck(void)
+{
+ if (!rex.reg_nobreak) {
+ fast_breakcheck();
+ }
+}
+
// Return true if character 'c' is included in 'iskeyword' option for
// "reg_buf" buffer.
static bool reg_iswordc(int c)
@@ -1030,7 +1174,7 @@ static char *reg_getline(linenr_T lnum)
// Must have matched the "\n" in the last line.
return "";
}
- return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false);
+ return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum);
}
static uint8_t *reg_startzp[NSUBEXP]; // Workspace to mark beginning
@@ -1150,9 +1294,7 @@ static bool reg_match_visual(void)
rex.line = (uint8_t *)reg_getline(rex.lnum);
rex.input = rex.line + col;
- unsigned int cols_u = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
- assert(cols_u <= MAXCOL);
- colnr_T cols = (colnr_T)cols_u;
+ colnr_T cols = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
if (cols < start || cols > end - (*p_sel == 'e')) {
return false;
}
@@ -1221,7 +1363,7 @@ static void reg_nextline(void)
{
rex.line = (uint8_t *)reg_getline(++rex.lnum);
rex.input = rex.line;
- fast_breakcheck();
+ reg_breakcheck();
}
// Check whether a backreference matches.
@@ -1239,7 +1381,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
if (bytelen != NULL) {
*bytelen = 0;
}
- for (;;) {
+ while (true) {
// Since getting one line may invalidate the other, need to make copy.
// Slow!
if (rex.line != reg_tofree) {
@@ -1393,7 +1535,7 @@ static int cstrncmp(char *s1, char *s2, int *n)
// if it failed and it's utf8 and we want to combineignore:
if (result != 0 && rex.reg_icombine) {
- char *str1, *str2;
+ const char *str1, *str2;
int c1, c2, c11, c12;
int junk;
@@ -1403,8 +1545,8 @@ static int cstrncmp(char *s1, char *s2, int *n)
str2 = s2;
c1 = c2 = 0;
while ((int)(str1 - s1) < *n) {
- c1 = mb_ptr2char_adv((const char **)&str1);
- c2 = mb_ptr2char_adv((const char **)&str2);
+ c1 = mb_ptr2char_adv(&str1);
+ c2 = mb_ptr2char_adv(&str2);
// decompose the character if necessary, into 'base' characters
// because I don't care about Arabic, I will hard-code the Hebrew
@@ -1475,34 +1617,14 @@ static inline char *cstrchr(const char *const s, const int c)
// regsub stuff //
////////////////////////////////////////////////////////////////
-// This stuff below really confuses cc on an SGI -- webb
-
-static fptr_T do_upper(int *d, int c)
-{
- *d = mb_toupper(c);
-
- return (fptr_T)NULL;
-}
-
-static fptr_T do_Upper(int *d, int c)
+static void do_upper(int *d, int c)
{
*d = mb_toupper(c);
-
- return (fptr_T)do_Upper;
-}
-
-static fptr_T do_lower(int *d, int c)
-{
- *d = mb_tolower(c);
-
- return (fptr_T)NULL;
}
-static fptr_T do_Lower(int *d, int c)
+static void do_lower(int *d, int c)
{
*d = mb_tolower(c);
-
- return (fptr_T)do_Lower;
}
/// regtilde(): Replace tildes in the pattern by the old pattern.
@@ -1518,41 +1640,46 @@ static fptr_T do_Lower(int *d, int c)
char *regtilde(char *source, int magic, bool preview)
{
char *newsub = source;
- char *tmpsub;
- char *p;
- int len;
- int prevlen;
- for (p = newsub; *p; p++) {
+ for (char *p = newsub; *p; p++) {
if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) {
if (reg_prev_sub != NULL) {
// length = len(newsub) - 1 + len(prev_sub) + 1
- prevlen = (int)strlen(reg_prev_sub);
- tmpsub = xmalloc(strlen(newsub) + (size_t)prevlen);
+ // Avoid making the text longer than MAXCOL, it will cause
+ // trouble at some point.
+ size_t prevsublen = strlen(reg_prev_sub);
+ size_t newsublen = strlen(newsub);
+ if (prevsublen > MAXCOL || newsublen > MAXCOL
+ || newsublen + prevsublen > MAXCOL) {
+ emsg(_(e_resulting_text_too_long));
+ break;
+ }
+
+ char *tmpsub = xmalloc(newsublen + prevsublen);
// copy prefix
- len = (int)(p - newsub); // not including ~
- memmove(tmpsub, newsub, (size_t)len);
+ size_t prefixlen = (size_t)(p - newsub); // not including ~
+ memmove(tmpsub, newsub, prefixlen);
// interpret tilde
- memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen);
+ memmove(tmpsub + prefixlen, reg_prev_sub, prevsublen);
// copy postfix
if (!magic) {
- p++; // back off backslash
+ p++; // back off backslash
}
- STRCPY(tmpsub + len + prevlen, p + 1);
+ STRCPY(tmpsub + prefixlen + prevsublen, p + 1);
- if (newsub != source) { // already allocated newsub
+ if (newsub != source) { // allocated newsub before
xfree(newsub);
}
newsub = tmpsub;
- p = newsub + len + prevlen;
+ p = newsub + prefixlen + prevsublen;
} else if (magic) {
- STRMOVE(p, p + 1); // remove '~'
+ STRMOVE(p, p + 1); // remove '~'
} else {
- STRMOVE(p, p + 2); // remove '\~'
+ STRMOVE(p, p + 2); // remove '\~'
}
p--;
} else {
- if (*p == '\\' && p[1]) { // skip escaped characters
+ if (*p == '\\' && p[1]) { // skip escaped characters
p++;
}
p += utfc_ptr2len(p) - 1;
@@ -1746,9 +1873,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
// "flags & REGSUB_COPY" == 0 to the call with
// "flags & REGSUB_COPY" != 0.
if (copy) {
- if (eval_result[nested] != NULL) {
+ size_t reslen = eval_result[nested] != NULL ? strlen(eval_result[nested]) : 0;
+ if (eval_result[nested] != NULL && reslen < (size_t)destlen) {
STRCPY(dest, eval_result[nested]);
- dst += strlen(eval_result[nested]);
+ dst += reslen;
XFREE_CLEAR(eval_result[nested]);
}
} else {
@@ -1812,7 +1940,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
}
tv_clear(&rettv);
} else {
- eval_result[nested] = eval_to_string(source + 2, NULL, true);
+ eval_result[nested] = eval_to_string(source + 2, true);
}
nesting--;
@@ -1866,16 +1994,16 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else if (vim_strchr("uUlLeE", (uint8_t)(*src))) {
switch (*src++) {
case 'u':
- func_one = (fptr_T)do_upper;
+ func_one = do_upper;
continue;
case 'U':
- func_all = (fptr_T)do_Upper;
+ func_all = do_upper;
continue;
case 'l':
- func_one = (fptr_T)do_lower;
+ func_one = do_lower;
continue;
case 'L':
- func_all = (fptr_T)do_Lower;
+ func_all = do_lower;
continue;
case 'e':
case 'E':
@@ -1934,11 +2062,13 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
} else {
c = utf_ptr2char(src - 1);
}
+
// Write to buffer, if copy is set.
if (func_one != NULL) {
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != NULL) {
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else {
// just copy
cc = c;
@@ -1994,7 +2124,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
}
}
if (s != NULL) {
- for (;;) {
+ while (true) {
if (len == 0) {
if (REG_MULTI) {
if (rex.reg_mmatch->endpos[no].lnum == clnum) {
@@ -2041,11 +2171,10 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
c = utf_ptr2char(s);
if (func_one != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_one = (fptr_T)(func_one(&cc, c));
+ func_one(&cc, c);
+ func_one = NULL;
} else if (func_all != (fptr_T)NULL) {
- // Turbo C complains without the typecast
- func_all = (fptr_T)(func_all(&cc, c));
+ func_all(&cc, c);
} else { // just copy
cc = c;
}
@@ -2175,7 +2304,7 @@ char *reg_submatch(int no)
}
len += rsm.sm_mmatch->endpos[no].col;
if (round == 2) {
- retval[len] = NUL; // -V595
+ retval[len] = NUL;
}
len++;
}
@@ -2237,12 +2366,12 @@ list_T *reg_submatch_list(int no)
tv_list_append_string(list, s, ecol);
}
} else {
- s = (const char *)rsm.sm_match->startp[no];
+ s = rsm.sm_match->startp[no];
if (s == NULL || rsm.sm_match->endp[no] == NULL) {
return NULL;
}
list = tv_list_alloc(1);
- tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s);
+ tv_list_append_string(list, s, rsm.sm_match->endp[no] - s);
}
tv_list_ref(list);
@@ -2265,14 +2394,13151 @@ static void init_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_
rex.reg_line_lbr = false;
rex.reg_ic = rmp->rmm_ic;
rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
rex.reg_maxcol = rmp->rmm_maxcol;
}
-// XXX Do not allow headers generator to catch definitions from regexp_nfa.c
-#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES
-# include "nvim/regexp_bt.c"
-# include "nvim/regexp_nfa.c"
+// regexp_bt.c {{{1
+
+// Backtracking regular expression implementation.
+//
+// NOTICE:
+//
+// This is NOT the original regular expression code as written by Henry
+// Spencer. This code has been modified specifically for use with the VIM
+// editor, and should not be used separately from Vim. If you want a good
+// regular expression library, get the original code. The copyright notice
+// that follows is from the original.
+//
+// END NOTICE
+//
+// Copyright (c) 1986 by University of Toronto.
+// Written by Henry Spencer. Not derived from licensed software.
+//
+// Permission is granted to anyone to use this software for any
+// purpose on any computer system, and to redistribute it freely,
+// subject to the following restrictions:
+//
+// 1. The author is not responsible for the consequences of use of
+// this software, no matter how awful, even if they arise
+// from defects in it.
+//
+// 2. The origin of this software must not be misrepresented, either
+// by explicit claim or by omission.
+//
+// 3. Altered versions must be plainly marked as such, and must not
+// be misrepresented as being the original software.
+//
+// Beware that some of this code is subtly aware of the way operator
+// precedence is structured in regular expressions. Serious changes in
+// regular-expression syntax might require a total rethink.
+//
+// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
+// Webb, Ciaran McCreesh and Bram Moolenaar.
+// Named character class support added by Walter Briscoe (1998 Jul 01)
+
+// The "internal use only" fields in regexp_defs.h are present to pass info from
+// compile to execute that permits the execute phase to run lots faster on
+// simple cases. They are:
+//
+// regstart char that must begin a match; NUL if none obvious; Can be a
+// multi-byte character.
+// reganch is the match anchored (at beginning-of-line only)?
+// regmust string (pointer into program) that match must include, or NULL
+// regmlen length of regmust string
+// regflags RF_ values or'ed together
+//
+// Regstart and reganch permit very fast decisions on suitable starting points
+// for a match, cutting down the work a lot. Regmust permits fast rejection
+// of lines that cannot possibly match. The regmust tests are costly enough
+// that vim_regcomp() supplies a regmust only if the r.e. contains something
+// potentially expensive (at present, the only such thing detected is * or +
+// at the start of the r.e., which can involve a lot of backup). Regmlen is
+// supplied because the test in vim_regexec() needs it and vim_regcomp() is
+// computing it anyway.
+
+// Structure for regexp "program". This is essentially a linear encoding
+// of a nondeterministic finite-state machine (aka syntax charts or
+// "railroad normal form" in parsing technology). Each node is an opcode
+// plus a "next" pointer, possibly plus an operand. "Next" pointers of
+// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
+// pointer with a BRANCH on both ends of it is connecting two alternatives.
+// (Here we have one of the subtle syntax dependencies: an individual BRANCH
+// (as opposed to a collection of them) is never concatenated with anything
+// because of operator precedence). The "next" pointer of a BRACES_COMPLEX
+// node points to the node after the stuff to be repeated.
+// The operand of some types of node is a literal string; for others, it is a
+// node leading into a sub-FSM. In particular, the operand of a BRANCH node
+// is the first node of the branch.
+// (NB this is *not* a tree structure: the tail of the branch connects to the
+// thing following the set of BRANCHes.)
+//
+// pattern is coded like:
+//
+// +-----------------+
+// | V
+// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
+// | ^ | ^
+// +------+ +----------+
+//
+//
+// +------------------+
+// V |
+// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +---------------+ |
+// +---------------------------------------------+
+//
+//
+// +----------------------+
+// V |
+// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
+// | | ^ ^
+// | +-----------+ |
+// +--------------------------------------------------+
+//
+//
+// +-------------------------+
+// V |
+// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
+// | | ^
+// | +----------------+
+// +-----------------------------------------------+
+//
+//
+// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
+// | | ^ ^
+// | +----------------+ |
+// +--------------------------------+
+//
+// +---------+
+// | V
+// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
+// | | | | ^ ^
+// | | | +-----+ |
+// | | +----------------+ |
+// | +---------------------------+ |
+// +------------------------------------------------------+
+//
+// They all start with a BRANCH for "\|" alternatives, even when there is only
+// one alternative.
+
+// The opcodes are:
+
+// definition number opnd? meaning
+#define END 0 // End of program or NOMATCH operand.
+#define BOL 1 // Match "" at beginning of line.
+#define EOL 2 // Match "" at end of line.
+#define BRANCH 3 // node Match this alternative, or the
+ // next...
+#define BACK 4 // Match "", "next" ptr points backward.
+#define EXACTLY 5 // str Match this string.
+#define NOTHING 6 // Match empty string.
+#define STAR 7 // node Match this (simple) thing 0 or more
+ // times.
+#define PLUS 8 // node Match this (simple) thing 1 or more
+ // times.
+#define MATCH 9 // node match the operand zero-width
+#define NOMATCH 10 // node check for no match with operand
+#define BEHIND 11 // node look behind for a match with operand
+#define NOBEHIND 12 // node look behind for no match with operand
+#define SUBPAT 13 // node match the operand here
+#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and
+ // n times (\{m,n\}).
+#define BOW 15 // Match "" after [^a-zA-Z0-9_]
+#define EOW 16 // Match "" at [^a-zA-Z0-9_]
+#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE
+ // and BRACE_COMPLEX.
+#define NEWL 18 // Match line-break
+#define BHPOS 19 // End position for BEHIND or NOBEHIND
+
+// character classes: 20-48 normal, 50-78 include a line-break
+#define ADD_NL 30
+#define FIRST_NL ANY + ADD_NL
+#define ANY 20 // Match any one character.
+#define ANYOF 21 // str Match any character in this string.
+#define ANYBUT 22 // str Match any character not in this
+ // string.
+#define IDENT 23 // Match identifier char
+#define SIDENT 24 // Match identifier char but no digit
+#define KWORD 25 // Match keyword char
+#define SKWORD 26 // Match word char but no digit
+#define FNAME 27 // Match file name char
+#define SFNAME 28 // Match file name char but no digit
+#define PRINT 29 // Match printable char
+#define SPRINT 30 // Match printable char but no digit
+#define WHITE 31 // Match whitespace char
+#define NWHITE 32 // Match non-whitespace char
+#define DIGIT 33 // Match digit char
+#define NDIGIT 34 // Match non-digit char
+#define HEX 35 // Match hex char
+#define NHEX 36 // Match non-hex char
+#define OCTAL 37 // Match octal char
+#define NOCTAL 38 // Match non-octal char
+#define WORD 39 // Match word char
+#define NWORD 40 // Match non-word char
+#define HEAD 41 // Match head char
+#define NHEAD 42 // Match non-head char
+#define ALPHA 43 // Match alpha char
+#define NALPHA 44 // Match non-alpha char
+#define LOWER 45 // Match lowercase char
+#define NLOWER 46 // Match non-lowercase char
+#define UPPER 47 // Match uppercase char
+#define NUPPER 48 // Match non-uppercase char
+#define LAST_NL NUPPER + ADD_NL
+#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL)
+
+#define MOPEN 80 // -89 Mark this point in input as start of
+ // \( … \) subexpr. MOPEN + 0 marks start of
+ // match.
+#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks
+ // end of match.
+#define BACKREF 100 // -109 node Match same string again \1-\9.
+
+#define ZOPEN 110 // -119 Mark this point in input as start of
+ // \z( … \) subexpr.
+#define ZCLOSE 120 // -129 Analogous to ZOPEN.
+#define ZREF 130 // -139 node Match external submatch \z1-\z9
+
+#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times
+
+#define NOPEN 150 // Mark this point in input as start of
+ // \%( subexpr.
+#define NCLOSE 151 // Analogous to NOPEN.
+
+#define MULTIBYTECODE 200 // mbc Match one multi-byte character
+#define RE_BOF 201 // Match "" at beginning of file.
+#define RE_EOF 202 // Match "" at end of file.
+#define CURSOR 203 // Match location of cursor.
+
+#define RE_LNUM 204 // nr cmp Match line number
+#define RE_COL 205 // nr cmp Match column number
+#define RE_VCOL 206 // nr cmp Match virtual column number
+
+#define RE_MARK 207 // mark cmp Match mark position
+#define RE_VISUAL 208 // Match Visual area
+#define RE_COMPOSING 209 // any composing characters
+
+// Flags to be passed up and down.
+#define HASWIDTH 0x1 // Known never to match null string.
+#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand.
+#define SPSTART 0x4 // Starts with * or +.
+#define HASNL 0x8 // Contains some \n.
+#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!".
+#define WORST 0 // Worst case.
+
+static int prevchr_len; ///< byte length of previous char
+static int num_complex_braces; ///< Complex \{...} count
+static uint8_t *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
+static int64_t regsize; ///< Code size.
+static int reg_toolong; ///< true when offset out of range
+static uint8_t had_endbrace[NSUBEXP]; ///< flags, true if end of () found
+static int64_t brace_min[10]; ///< Minimums for complex brace repeats
+static int64_t brace_max[10]; ///< Maximums for complex brace repeats
+static int brace_count[10]; ///< Current counts for complex brace repeats
+static int one_exactly = false; ///< only do one char for EXACTLY
+
+// When making changes to classchars also change nfa_classcodes.
+static uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU";
+static int classcodes[] = {
+ ANY, IDENT, SIDENT, KWORD, SKWORD,
+ FNAME, SFNAME, PRINT, SPRINT,
+ WHITE, NWHITE, DIGIT, NDIGIT,
+ HEX, NHEX, OCTAL, NOCTAL,
+ WORD, NWORD, HEAD, NHEAD,
+ ALPHA, NALPHA, LOWER, NLOWER,
+ UPPER, NUPPER
+};
+
+// When regcode is set to this value, code is not emitted and size is computed
+// instead.
+#define JUST_CALC_SIZE ((uint8_t *)-1)
+
+// used for STAR, PLUS and BRACE_SIMPLE matching
+typedef struct regstar_S {
+ int nextb; // next byte
+ int nextb_ic; // next byte reverse case
+ int64_t count;
+ int64_t minval;
+ int64_t maxval;
+} regstar_T;
+
+// used to store input position when a BACK was encountered, so that we now if
+// we made any progress since the last time.
+typedef struct backpos_S {
+ uint8_t *bp_scan; // "scan" where BACK was encountered
+ regsave_T bp_pos; // last input position
+} backpos_T;
+
+// "regstack" and "backpos" are used by regmatch(). They are kept over calls
+// to avoid invoking malloc() and free() often.
+// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
+// or regbehind_T.
+// "backpos_T" is a table with backpos_T for BACK
+static garray_T regstack = GA_EMPTY_INIT_VALUE;
+static garray_T backpos = GA_EMPTY_INIT_VALUE;
+
+static regsave_T behind_pos;
+
+// Both for regstack and backpos tables we use the following strategy of
+// allocation (to reduce malloc/free calls):
+// - Initial size is fairly small.
+// - When needed, the tables are grown bigger (8 times at first, double after
+// that).
+// - After executing the match we free the memory only if the array has grown.
+// Thus the memory is kept allocated when it's at the initial size.
+// This makes it fast while not keeping a lot of memory allocated.
+// A three times speed increase was observed when using many simple patterns.
+#define REGSTACK_INITIAL 2048
+#define BACKPOS_INITIAL 64
+
+// Opcode notes:
+//
+// BRANCH The set of branches constituting a single choice are hooked
+// together with their "next" pointers, since precedence prevents
+// anything being concatenated to any individual branch. The
+// "next" pointer of the last BRANCH in a choice points to the
+// thing following the whole choice. This is also where the
+// final "next" pointer of each individual branch points; each
+// branch starts with the operand node of a BRANCH node.
+//
+// BACK Normal "next" pointers all implicitly point forward; BACK
+// exists to make loop structures possible.
+//
+// STAR,PLUS '=', and complex '*' and '+', are implemented as circular
+// BRANCH structures using BACK. Simple cases (one character
+// per match) are implemented with STAR and PLUS for speed
+// and to minimize recursive plunges.
+//
+// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
+// node, and defines the min and max limits to be used for that
+// node.
+//
+// MOPEN,MCLOSE ...are numbered at compile time.
+// ZOPEN,ZCLOSE ...ditto
+///
+//
+//
+// A node is one char of opcode followed by two chars of "next" pointer.
+// "Next" pointers are stored as two 8-bit bytes, high order first. The
+// value is a positive offset from the opcode of the node containing it.
+// An operand, if any, simply follows the node. (Note that much of the
+// code generation knows about this implicit relationship.)
+//
+// Using two bytes for the "next" pointer is vast overkill for most things,
+// but allows patterns to get big without disasters.
+#define OP(p) ((int)(*(p)))
+#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377))
+#define OPERAND(p) ((p) + 3)
+// Obtain an operand that was stored as four bytes, MSB first.
+#define OPERAND_MIN(p) (((int64_t)(p)[3] << 24) + ((int64_t)(p)[4] << 16) \
+ + ((int64_t)(p)[5] << 8) + (int64_t)(p)[6])
+// Obtain a second operand stored as four bytes.
+#define OPERAND_MAX(p) OPERAND_MIN((p) + 4)
+// Obtain a second single-byte operand stored after a four bytes operand.
+#define OPERAND_CMP(p) (p)[7]
+
+static uint8_t *reg(int paren, int *flagp);
+
+#ifdef BT_REGEXP_DUMP
+static void regdump(uint8_t *, bt_regprog_T *);
+#endif
+
+#ifdef REGEXP_DEBUG
+static uint8_t *regprop(uint8_t *);
+
+static int regnarrate = 0;
+#endif
+
+// Setup to parse the regexp. Used once to get the length and once to do it.
+static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp()
+{
+ initchr((char *)expr);
+ if (re_flags & RE_MAGIC) {
+ reg_magic = MAGIC_ON;
+ } else {
+ reg_magic = MAGIC_OFF;
+ }
+ reg_string = (re_flags & RE_STRING);
+ reg_strict = (re_flags & RE_STRICT);
+ get_cpo_flags();
+
+ num_complex_braces = 0;
+ regnpar = 1;
+ CLEAR_FIELD(had_endbrace);
+ regnzpar = 1;
+ re_has_z = 0;
+ regsize = 0L;
+ reg_toolong = false;
+ regflags = 0;
+ had_eol = false;
+}
+
+// Return true if MULTIBYTECODE should be used instead of EXACTLY for
+// character "c".
+static bool use_multibytecode(int c)
+{
+ return utf_char2len(c) > 1
+ && (re_multi_type(peekchr()) != NOT_MULTI
+ || utf_iscomposing(c));
+}
+
+// Emit (if appropriate) a byte of code
+static void regc(int b)
+{
+ if (regcode == JUST_CALC_SIZE) {
+ regsize++;
+ } else {
+ *regcode++ = (uint8_t)b;
+ }
+}
+
+// Emit (if appropriate) a multi-byte character of code
+static void regmbc(int c)
+{
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += utf_char2len(c);
+ } else {
+ regcode += utf_char2bytes(c, (char *)regcode);
+ }
+}
+
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// NOTE: When changing this function, also change nfa_emit_equi_class()
+static void reg_equi_class(int c)
+{
+ {
+ switch (c) {
+ // Do not use '\300' style, it results in a negative number.
+ case 'A':
+ case 0xc0:
+ case 0xc1:
+ case 0xc2:
+ case 0xc3:
+ case 0xc4:
+ case 0xc5:
+ case 0x100:
+ case 0x102:
+ case 0x104:
+ case 0x1cd:
+ case 0x1de:
+ case 0x1e0:
+ case 0x1fa:
+ case 0x202:
+ case 0x226:
+ case 0x23a:
+ case 0x1e00:
+ case 0x1ea0:
+ case 0x1ea2:
+ case 0x1ea4:
+ case 0x1ea6:
+ case 0x1ea8:
+ case 0x1eaa:
+ case 0x1eac:
+ case 0x1eae:
+ case 0x1eb0:
+ case 0x1eb2:
+ case 0x1eb4:
+ case 0x1eb6:
+ regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2);
+ regmbc(0xc3); regmbc(0xc4); regmbc(0xc5);
+ regmbc(0x100); regmbc(0x102); regmbc(0x104);
+ regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0);
+ regmbc(0x1fa); regmbc(0x202); regmbc(0x226);
+ regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0);
+ regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6);
+ regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac);
+ regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2);
+ regmbc(0x1eb4); regmbc(0x1eb6);
+ return;
+ case 'B':
+ case 0x181:
+ case 0x243:
+ case 0x1e02:
+ case 0x1e04:
+ case 0x1e06:
+ regmbc('B');
+ regmbc(0x181); regmbc(0x243); regmbc(0x1e02);
+ regmbc(0x1e04); regmbc(0x1e06);
+ return;
+ case 'C':
+ case 0xc7:
+ case 0x106:
+ case 0x108:
+ case 0x10a:
+ case 0x10c:
+ case 0x187:
+ case 0x23b:
+ case 0x1e08:
+ case 0xa792:
+ regmbc('C'); regmbc(0xc7);
+ regmbc(0x106); regmbc(0x108); regmbc(0x10a);
+ regmbc(0x10c); regmbc(0x187); regmbc(0x23b);
+ regmbc(0x1e08); regmbc(0xa792);
+ return;
+ case 'D':
+ case 0x10e:
+ case 0x110:
+ case 0x18a:
+ case 0x1e0a:
+ case 0x1e0c:
+ case 0x1e0e:
+ case 0x1e10:
+ case 0x1e12:
+ regmbc('D'); regmbc(0x10e); regmbc(0x110);
+ regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c);
+ regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12);
+ return;
+ case 'E':
+ case 0xc8:
+ case 0xc9:
+ case 0xca:
+ case 0xcb:
+ case 0x112:
+ case 0x114:
+ case 0x116:
+ case 0x118:
+ case 0x11a:
+ case 0x204:
+ case 0x206:
+ case 0x228:
+ case 0x246:
+ case 0x1e14:
+ case 0x1e16:
+ case 0x1e18:
+ case 0x1e1a:
+ case 0x1e1c:
+ case 0x1eb8:
+ case 0x1eba:
+ case 0x1ebc:
+ case 0x1ebe:
+ case 0x1ec0:
+ case 0x1ec2:
+ case 0x1ec4:
+ case 0x1ec6:
+ regmbc('E'); regmbc(0xc8); regmbc(0xc9);
+ regmbc(0xca); regmbc(0xcb); regmbc(0x112);
+ regmbc(0x114); regmbc(0x116); regmbc(0x118);
+ regmbc(0x11a); regmbc(0x204); regmbc(0x206);
+ regmbc(0x228); regmbc(0x246); regmbc(0x1e14);
+ regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a);
+ regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba);
+ regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0);
+ regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6);
+ return;
+ case 'F':
+ case 0x191:
+ case 0x1e1e:
+ case 0xa798:
+ regmbc('F'); regmbc(0x191); regmbc(0x1e1e);
+ regmbc(0xa798);
+ return;
+ case 'G':
+ case 0x11c:
+ case 0x11e:
+ case 0x120:
+ case 0x122:
+ case 0x193:
+ case 0x1e4:
+ case 0x1e6:
+ case 0x1f4:
+ case 0x1e20:
+ case 0xa7a0:
+ regmbc('G'); regmbc(0x11c); regmbc(0x11e);
+ regmbc(0x120); regmbc(0x122); regmbc(0x193);
+ regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4);
+ regmbc(0x1e20); regmbc(0xa7a0);
+ return;
+ case 'H':
+ case 0x124:
+ case 0x126:
+ case 0x21e:
+ case 0x1e22:
+ case 0x1e24:
+ case 0x1e26:
+ case 0x1e28:
+ case 0x1e2a:
+ case 0x2c67:
+ regmbc('H'); regmbc(0x124); regmbc(0x126);
+ regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24);
+ regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a);
+ regmbc(0x2c67);
+ return;
+ case 'I':
+ case 0xcc:
+ case 0xcd:
+ case 0xce:
+ case 0xcf:
+ case 0x128:
+ case 0x12a:
+ case 0x12c:
+ case 0x12e:
+ case 0x130:
+ case 0x197:
+ case 0x1cf:
+ case 0x208:
+ case 0x20a:
+ case 0x1e2c:
+ case 0x1e2e:
+ case 0x1ec8:
+ case 0x1eca:
+ regmbc('I'); regmbc(0xcc); regmbc(0xcd);
+ regmbc(0xce); regmbc(0xcf); regmbc(0x128);
+ regmbc(0x12a); regmbc(0x12c); regmbc(0x12e);
+ regmbc(0x130); regmbc(0x197); regmbc(0x1cf);
+ regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c);
+ regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca);
+ return;
+ case 'J':
+ case 0x134:
+ case 0x248:
+ regmbc('J'); regmbc(0x134); regmbc(0x248);
+ return;
+ case 'K':
+ case 0x136:
+ case 0x198:
+ case 0x1e8:
+ case 0x1e30:
+ case 0x1e32:
+ case 0x1e34:
+ case 0x2c69:
+ case 0xa740:
+ regmbc('K'); regmbc(0x136); regmbc(0x198);
+ regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32);
+ regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740);
+ return;
+ case 'L':
+ case 0x139:
+ case 0x13b:
+ case 0x13d:
+ case 0x13f:
+ case 0x141:
+ case 0x23d:
+ case 0x1e36:
+ case 0x1e38:
+ case 0x1e3a:
+ case 0x1e3c:
+ case 0x2c60:
+ regmbc('L'); regmbc(0x139); regmbc(0x13b);
+ regmbc(0x13d); regmbc(0x13f); regmbc(0x141);
+ regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38);
+ regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60);
+ return;
+ case 'M':
+ case 0x1e3e:
+ case 0x1e40:
+ case 0x1e42:
+ regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40);
+ regmbc(0x1e42);
+ return;
+ case 'N':
+ case 0xd1:
+ case 0x143:
+ case 0x145:
+ case 0x147:
+ case 0x1f8:
+ case 0x1e44:
+ case 0x1e46:
+ case 0x1e48:
+ case 0x1e4a:
+ case 0xa7a4:
+ regmbc('N'); regmbc(0xd1);
+ regmbc(0x143); regmbc(0x145); regmbc(0x147);
+ regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46);
+ regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4);
+ return;
+ case 'O':
+ case 0xd2:
+ case 0xd3:
+ case 0xd4:
+ case 0xd5:
+ case 0xd6:
+ case 0xd8:
+ case 0x14c:
+ case 0x14e:
+ case 0x150:
+ case 0x19f:
+ case 0x1a0:
+ case 0x1d1:
+ case 0x1ea:
+ case 0x1ec:
+ case 0x1fe:
+ case 0x20c:
+ case 0x20e:
+ case 0x22a:
+ case 0x22c:
+ case 0x22e:
+ case 0x230:
+ case 0x1e4c:
+ case 0x1e4e:
+ case 0x1e50:
+ case 0x1e52:
+ case 0x1ecc:
+ case 0x1ece:
+ case 0x1ed0:
+ case 0x1ed2:
+ case 0x1ed4:
+ case 0x1ed6:
+ case 0x1ed8:
+ case 0x1eda:
+ case 0x1edc:
+ case 0x1ede:
+ case 0x1ee0:
+ case 0x1ee2:
+ regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4);
+ regmbc(0xd5); regmbc(0xd6); regmbc(0xd8);
+ regmbc(0x14c); regmbc(0x14e); regmbc(0x150);
+ regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1);
+ regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe);
+ regmbc(0x20c); regmbc(0x20e); regmbc(0x22a);
+ regmbc(0x22c); regmbc(0x22e); regmbc(0x230);
+ regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50);
+ regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece);
+ regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4);
+ regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda);
+ regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0);
+ regmbc(0x1ee2);
+ return;
+ case 'P':
+ case 0x1a4:
+ case 0x1e54:
+ case 0x1e56:
+ case 0x2c63:
+ regmbc('P'); regmbc(0x1a4); regmbc(0x1e54);
+ regmbc(0x1e56); regmbc(0x2c63);
+ return;
+ case 'Q':
+ case 0x24a:
+ regmbc('Q'); regmbc(0x24a);
+ return;
+ case 'R':
+ case 0x154:
+ case 0x156:
+ case 0x158:
+ case 0x210:
+ case 0x212:
+ case 0x24c:
+ case 0x1e58:
+ case 0x1e5a:
+ case 0x1e5c:
+ case 0x1e5e:
+ case 0x2c64:
+ case 0xa7a6:
+ regmbc('R'); regmbc(0x154); regmbc(0x156);
+ regmbc(0x210); regmbc(0x212); regmbc(0x158);
+ regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a);
+ regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64);
+ regmbc(0xa7a6);
+ return;
+ case 'S':
+ case 0x15a:
+ case 0x15c:
+ case 0x15e:
+ case 0x160:
+ case 0x218:
+ case 0x1e60:
+ case 0x1e62:
+ case 0x1e64:
+ case 0x1e66:
+ case 0x1e68:
+ case 0x2c7e:
+ case 0xa7a8:
+ regmbc('S'); regmbc(0x15a); regmbc(0x15c);
+ regmbc(0x15e); regmbc(0x160); regmbc(0x218);
+ regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64);
+ regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e);
+ regmbc(0xa7a8);
+ return;
+ case 'T':
+ case 0x162:
+ case 0x164:
+ case 0x166:
+ case 0x1ac:
+ case 0x1ae:
+ case 0x21a:
+ case 0x23e:
+ case 0x1e6a:
+ case 0x1e6c:
+ case 0x1e6e:
+ case 0x1e70:
+ regmbc('T'); regmbc(0x162); regmbc(0x164);
+ regmbc(0x166); regmbc(0x1ac); regmbc(0x23e);
+ regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a);
+ regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70);
+ return;
+ case 'U':
+ case 0xd9:
+ case 0xda:
+ case 0xdb:
+ case 0xdc:
+ case 0x168:
+ case 0x16a:
+ case 0x16c:
+ case 0x16e:
+ case 0x170:
+ case 0x172:
+ case 0x1af:
+ case 0x1d3:
+ case 0x1d5:
+ case 0x1d7:
+ case 0x1d9:
+ case 0x1db:
+ case 0x214:
+ case 0x216:
+ case 0x244:
+ case 0x1e72:
+ case 0x1e74:
+ case 0x1e76:
+ case 0x1e78:
+ case 0x1e7a:
+ case 0x1ee4:
+ case 0x1ee6:
+ case 0x1ee8:
+ case 0x1eea:
+ case 0x1eec:
+ case 0x1eee:
+ case 0x1ef0:
+ regmbc('U'); regmbc(0xd9); regmbc(0xda);
+ regmbc(0xdb); regmbc(0xdc); regmbc(0x168);
+ regmbc(0x16a); regmbc(0x16c); regmbc(0x16e);
+ regmbc(0x170); regmbc(0x172); regmbc(0x1af);
+ regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7);
+ regmbc(0x1d9); regmbc(0x1db); regmbc(0x214);
+ regmbc(0x216); regmbc(0x244); regmbc(0x1e72);
+ regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78);
+ regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6);
+ regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec);
+ regmbc(0x1eee); regmbc(0x1ef0);
+ return;
+ case 'V':
+ case 0x1b2:
+ case 0x1e7c:
+ case 0x1e7e:
+ regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c);
+ regmbc(0x1e7e);
+ return;
+ case 'W':
+ case 0x174:
+ case 0x1e80:
+ case 0x1e82:
+ case 0x1e84:
+ case 0x1e86:
+ case 0x1e88:
+ regmbc('W'); regmbc(0x174); regmbc(0x1e80);
+ regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86);
+ regmbc(0x1e88);
+ return;
+ case 'X':
+ case 0x1e8a:
+ case 0x1e8c:
+ regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c);
+ return;
+ case 'Y':
+ case 0xdd:
+ case 0x176:
+ case 0x178:
+ case 0x1b3:
+ case 0x232:
+ case 0x24e:
+ case 0x1e8e:
+ case 0x1ef2:
+ case 0x1ef6:
+ case 0x1ef4:
+ case 0x1ef8:
+ regmbc('Y'); regmbc(0xdd); regmbc(0x176);
+ regmbc(0x178); regmbc(0x1b3); regmbc(0x232);
+ regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2);
+ regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8);
+ return;
+ case 'Z':
+ case 0x179:
+ case 0x17b:
+ case 0x17d:
+ case 0x1b5:
+ case 0x1e90:
+ case 0x1e92:
+ case 0x1e94:
+ case 0x2c6b:
+ regmbc('Z'); regmbc(0x179); regmbc(0x17b);
+ regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90);
+ regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b);
+ return;
+ case 'a':
+ case 0xe0:
+ case 0xe1:
+ case 0xe2:
+ case 0xe3:
+ case 0xe4:
+ case 0xe5:
+ case 0x101:
+ case 0x103:
+ case 0x105:
+ case 0x1ce:
+ case 0x1df:
+ case 0x1e1:
+ case 0x1fb:
+ case 0x201:
+ case 0x203:
+ case 0x227:
+ case 0x1d8f:
+ case 0x1e01:
+ case 0x1e9a:
+ case 0x1ea1:
+ case 0x1ea3:
+ case 0x1ea5:
+ case 0x1ea7:
+ case 0x1ea9:
+ case 0x1eab:
+ case 0x1ead:
+ case 0x1eaf:
+ case 0x1eb1:
+ case 0x1eb3:
+ case 0x1eb5:
+ case 0x1eb7:
+ case 0x2c65:
+ regmbc('a'); regmbc(0xe0); regmbc(0xe1);
+ regmbc(0xe2); regmbc(0xe3); regmbc(0xe4);
+ regmbc(0xe5); regmbc(0x101); regmbc(0x103);
+ regmbc(0x105); regmbc(0x1ce); regmbc(0x1df);
+ regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201);
+ regmbc(0x203); regmbc(0x227); regmbc(0x1d8f);
+ regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1);
+ regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7);
+ regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead);
+ regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3);
+ regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65);
+ return;
+ case 'b':
+ case 0x180:
+ case 0x253:
+ case 0x1d6c:
+ case 0x1d80:
+ case 0x1e03:
+ case 0x1e05:
+ case 0x1e07:
+ regmbc('b');
+ regmbc(0x180); regmbc(0x253); regmbc(0x1d6c);
+ regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05);
+ regmbc(0x1e07);
+ return;
+ case 'c':
+ case 0xe7:
+ case 0x107:
+ case 0x109:
+ case 0x10b:
+ case 0x10d:
+ case 0x188:
+ case 0x23c:
+ case 0x1e09:
+ case 0xa793:
+ case 0xa794:
+ regmbc('c'); regmbc(0xe7); regmbc(0x107);
+ regmbc(0x109); regmbc(0x10b); regmbc(0x10d);
+ regmbc(0x188); regmbc(0x23c); regmbc(0x1e09);
+ regmbc(0xa793); regmbc(0xa794);
+ return;
+ case 'd':
+ case 0x10f:
+ case 0x111:
+ case 0x257:
+ case 0x1d6d:
+ case 0x1d81:
+ case 0x1d91:
+ case 0x1e0b:
+ case 0x1e0d:
+ case 0x1e0f:
+ case 0x1e11:
+ case 0x1e13:
+ regmbc('d'); regmbc(0x10f); regmbc(0x111);
+ regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81);
+ regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d);
+ regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13);
+ return;
+ case 'e':
+ case 0xe8:
+ case 0xe9:
+ case 0xea:
+ case 0xeb:
+ case 0x113:
+ case 0x115:
+ case 0x117:
+ case 0x119:
+ case 0x11b:
+ case 0x205:
+ case 0x207:
+ case 0x229:
+ case 0x247:
+ case 0x1d92:
+ case 0x1e15:
+ case 0x1e17:
+ case 0x1e19:
+ case 0x1e1b:
+ case 0x1eb9:
+ case 0x1ebb:
+ case 0x1e1d:
+ case 0x1ebd:
+ case 0x1ebf:
+ case 0x1ec1:
+ case 0x1ec3:
+ case 0x1ec5:
+ case 0x1ec7:
+ regmbc('e'); regmbc(0xe8); regmbc(0xe9);
+ regmbc(0xea); regmbc(0xeb); regmbc(0x113);
+ regmbc(0x115); regmbc(0x117); regmbc(0x119);
+ regmbc(0x11b); regmbc(0x205); regmbc(0x207);
+ regmbc(0x229); regmbc(0x247); regmbc(0x1d92);
+ regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19);
+ regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9);
+ regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf);
+ regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5);
+ regmbc(0x1ec7);
+ return;
+ case 'f':
+ case 0x192:
+ case 0x1d6e:
+ case 0x1d82:
+ case 0x1e1f:
+ case 0xa799:
+ regmbc('f'); regmbc(0x192); regmbc(0x1d6e);
+ regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799);
+ return;
+ case 'g':
+ case 0x11d:
+ case 0x11f:
+ case 0x121:
+ case 0x123:
+ case 0x1e5:
+ case 0x1e7:
+ case 0x260:
+ case 0x1f5:
+ case 0x1d83:
+ case 0x1e21:
+ case 0xa7a1:
+ regmbc('g'); regmbc(0x11d); regmbc(0x11f);
+ regmbc(0x121); regmbc(0x123); regmbc(0x1e5);
+ regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260);
+ regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1);
+ return;
+ case 'h':
+ case 0x125:
+ case 0x127:
+ case 0x21f:
+ case 0x1e23:
+ case 0x1e25:
+ case 0x1e27:
+ case 0x1e29:
+ case 0x1e2b:
+ case 0x1e96:
+ case 0x2c68:
+ case 0xa795:
+ regmbc('h'); regmbc(0x125); regmbc(0x127);
+ regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25);
+ regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b);
+ regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795);
+ return;
+ case 'i':
+ case 0xec:
+ case 0xed:
+ case 0xee:
+ case 0xef:
+ case 0x129:
+ case 0x12b:
+ case 0x12d:
+ case 0x12f:
+ case 0x1d0:
+ case 0x209:
+ case 0x20b:
+ case 0x268:
+ case 0x1d96:
+ case 0x1e2d:
+ case 0x1e2f:
+ case 0x1ec9:
+ case 0x1ecb:
+ regmbc('i'); regmbc(0xec); regmbc(0xed);
+ regmbc(0xee); regmbc(0xef); regmbc(0x129);
+ regmbc(0x12b); regmbc(0x12d); regmbc(0x12f);
+ regmbc(0x1d0); regmbc(0x209); regmbc(0x20b);
+ regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d);
+ regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb);
+ return;
+ case 'j':
+ case 0x135:
+ case 0x1f0:
+ case 0x249:
+ regmbc('j'); regmbc(0x135); regmbc(0x1f0);
+ regmbc(0x249);
+ return;
+ case 'k':
+ case 0x137:
+ case 0x199:
+ case 0x1e9:
+ case 0x1d84:
+ case 0x1e31:
+ case 0x1e33:
+ case 0x1e35:
+ case 0x2c6a:
+ case 0xa741:
+ regmbc('k'); regmbc(0x137); regmbc(0x199);
+ regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31);
+ regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a);
+ regmbc(0xa741);
+ return;
+ case 'l':
+ case 0x13a:
+ case 0x13c:
+ case 0x13e:
+ case 0x140:
+ case 0x142:
+ case 0x19a:
+ case 0x1e37:
+ case 0x1e39:
+ case 0x1e3b:
+ case 0x1e3d:
+ case 0x2c61:
+ regmbc('l'); regmbc(0x13a); regmbc(0x13c);
+ regmbc(0x13e); regmbc(0x140); regmbc(0x142);
+ regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39);
+ regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61);
+ return;
+ case 'm':
+ case 0x1d6f:
+ case 0x1e3f:
+ case 0x1e41:
+ case 0x1e43:
+ regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f);
+ regmbc(0x1e41); regmbc(0x1e43);
+ return;
+ case 'n':
+ case 0xf1:
+ case 0x144:
+ case 0x146:
+ case 0x148:
+ case 0x149:
+ case 0x1f9:
+ case 0x1d70:
+ case 0x1d87:
+ case 0x1e45:
+ case 0x1e47:
+ case 0x1e49:
+ case 0x1e4b:
+ case 0xa7a5:
+ regmbc('n'); regmbc(0xf1); regmbc(0x144);
+ regmbc(0x146); regmbc(0x148); regmbc(0x149);
+ regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87);
+ regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49);
+ regmbc(0x1e4b); regmbc(0xa7a5);
+ return;
+ case 'o':
+ case 0xf2:
+ case 0xf3:
+ case 0xf4:
+ case 0xf5:
+ case 0xf6:
+ case 0xf8:
+ case 0x14d:
+ case 0x14f:
+ case 0x151:
+ case 0x1a1:
+ case 0x1d2:
+ case 0x1eb:
+ case 0x1ed:
+ case 0x1ff:
+ case 0x20d:
+ case 0x20f:
+ case 0x22b:
+ case 0x22d:
+ case 0x22f:
+ case 0x231:
+ case 0x275:
+ case 0x1e4d:
+ case 0x1e4f:
+ case 0x1e51:
+ case 0x1e53:
+ case 0x1ecd:
+ case 0x1ecf:
+ case 0x1ed1:
+ case 0x1ed3:
+ case 0x1ed5:
+ case 0x1ed7:
+ case 0x1ed9:
+ case 0x1edb:
+ case 0x1edd:
+ case 0x1edf:
+ case 0x1ee1:
+ case 0x1ee3:
+ regmbc('o'); regmbc(0xf2); regmbc(0xf3);
+ regmbc(0xf4); regmbc(0xf5); regmbc(0xf6);
+ regmbc(0xf8); regmbc(0x14d); regmbc(0x14f);
+ regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2);
+ regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff);
+ regmbc(0x20d); regmbc(0x20f); regmbc(0x22b);
+ regmbc(0x22d); regmbc(0x22f); regmbc(0x231);
+ regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f);
+ regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd);
+ regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3);
+ regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9);
+ regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf);
+ regmbc(0x1ee1); regmbc(0x1ee3);
+ return;
+ case 'p':
+ case 0x1a5:
+ case 0x1d71:
+ case 0x1d88:
+ case 0x1d7d:
+ case 0x1e55:
+ case 0x1e57:
+ regmbc('p'); regmbc(0x1a5); regmbc(0x1d71);
+ regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55);
+ regmbc(0x1e57);
+ return;
+ case 'q':
+ case 0x24b:
+ case 0x2a0:
+ regmbc('q'); regmbc(0x24b); regmbc(0x2a0);
+ return;
+ case 'r':
+ case 0x155:
+ case 0x157:
+ case 0x159:
+ case 0x211:
+ case 0x213:
+ case 0x24d:
+ case 0x27d:
+ case 0x1d72:
+ case 0x1d73:
+ case 0x1d89:
+ case 0x1e59:
+ case 0x1e5b:
+ case 0x1e5d:
+ case 0x1e5f:
+ case 0xa7a7:
+ regmbc('r'); regmbc(0x155); regmbc(0x157);
+ regmbc(0x159); regmbc(0x211); regmbc(0x213);
+ regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73);
+ regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d);
+ regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f);
+ regmbc(0xa7a7);
+ return;
+ case 's':
+ case 0x15b:
+ case 0x15d:
+ case 0x15f:
+ case 0x161:
+ case 0x1e61:
+ case 0x219:
+ case 0x23f:
+ case 0x1d74:
+ case 0x1d8a:
+ case 0x1e63:
+ case 0x1e65:
+ case 0x1e67:
+ case 0x1e69:
+ case 0xa7a9:
+ regmbc('s'); regmbc(0x15b); regmbc(0x15d);
+ regmbc(0x15f); regmbc(0x161); regmbc(0x23f);
+ regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a);
+ regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65);
+ regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9);
+ return;
+ case 't':
+ case 0x163:
+ case 0x165:
+ case 0x167:
+ case 0x1ab:
+ case 0x1ad:
+ case 0x21b:
+ case 0x288:
+ case 0x1d75:
+ case 0x1e6b:
+ case 0x1e6d:
+ case 0x1e6f:
+ case 0x1e71:
+ case 0x1e97:
+ case 0x2c66:
+ regmbc('t'); regmbc(0x163); regmbc(0x165);
+ regmbc(0x167); regmbc(0x1ab); regmbc(0x21b);
+ regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75);
+ regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f);
+ regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66);
+ return;
+ case 'u':
+ case 0xf9:
+ case 0xfa:
+ case 0xfb:
+ case 0xfc:
+ case 0x169:
+ case 0x16b:
+ case 0x16d:
+ case 0x16f:
+ case 0x171:
+ case 0x173:
+ case 0x1b0:
+ case 0x1d4:
+ case 0x1d6:
+ case 0x1d8:
+ case 0x1da:
+ case 0x1dc:
+ case 0x215:
+ case 0x217:
+ case 0x289:
+ case 0x1e73:
+ case 0x1d7e:
+ case 0x1d99:
+ case 0x1e75:
+ case 0x1e77:
+ case 0x1e79:
+ case 0x1e7b:
+ case 0x1ee5:
+ case 0x1ee7:
+ case 0x1ee9:
+ case 0x1eeb:
+ case 0x1eed:
+ case 0x1eef:
+ case 0x1ef1:
+ regmbc('u'); regmbc(0xf9); regmbc(0xfa);
+ regmbc(0xfb); regmbc(0xfc); regmbc(0x169);
+ regmbc(0x16b); regmbc(0x16d); regmbc(0x16f);
+ regmbc(0x171); regmbc(0x173); regmbc(0x1d6);
+ regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc);
+ regmbc(0x215); regmbc(0x217); regmbc(0x1b0);
+ regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e);
+ regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75);
+ regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b);
+ regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9);
+ regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef);
+ regmbc(0x1ef1);
+ return;
+ case 'v':
+ case 0x28b:
+ case 0x1d8c:
+ case 0x1e7d:
+ case 0x1e7f:
+ regmbc('v'); regmbc(0x28b); regmbc(0x1d8c);
+ regmbc(0x1e7d); regmbc(0x1e7f);
+ return;
+ case 'w':
+ case 0x175:
+ case 0x1e81:
+ case 0x1e83:
+ case 0x1e85:
+ case 0x1e87:
+ case 0x1e89:
+ case 0x1e98:
+ regmbc('w'); regmbc(0x175); regmbc(0x1e81);
+ regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87);
+ regmbc(0x1e89); regmbc(0x1e98);
+ return;
+ case 'x':
+ case 0x1e8b:
+ case 0x1e8d:
+ regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d);
+ return;
+ case 'y':
+ case 0xfd:
+ case 0xff:
+ case 0x177:
+ case 0x1b4:
+ case 0x233:
+ case 0x24f:
+ case 0x1e8f:
+ case 0x1e99:
+ case 0x1ef3:
+ case 0x1ef5:
+ case 0x1ef7:
+ case 0x1ef9:
+ regmbc('y'); regmbc(0xfd); regmbc(0xff);
+ regmbc(0x177); regmbc(0x1b4); regmbc(0x233);
+ regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99);
+ regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7);
+ regmbc(0x1ef9);
+ return;
+ case 'z':
+ case 0x17a:
+ case 0x17c:
+ case 0x17e:
+ case 0x1b6:
+ case 0x1d76:
+ case 0x1d8e:
+ case 0x1e91:
+ case 0x1e93:
+ case 0x1e95:
+ case 0x2c6c:
+ regmbc('z'); regmbc(0x17a); regmbc(0x17c);
+ regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76);
+ regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93);
+ regmbc(0x1e95); regmbc(0x2c6c);
+ return;
+ }
+ }
+ regmbc(c);
+}
+
+// Emit a node.
+// Return pointer to generated code.
+static uint8_t *regnode(int op)
+{
+ uint8_t *ret;
+
+ ret = regcode;
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 3;
+ } else {
+ *regcode++ = (uint8_t)op;
+ *regcode++ = NUL; // Null "next" pointer.
+ *regcode++ = NUL;
+ }
+ return ret;
+}
+
+// Write a four bytes number at "p" and return pointer to the next char.
+static uint8_t *re_put_uint32(uint8_t *p, uint32_t val)
+{
+ *p++ = (uint8_t)((val >> 24) & 0377);
+ *p++ = (uint8_t)((val >> 16) & 0377);
+ *p++ = (uint8_t)((val >> 8) & 0377);
+ *p++ = (uint8_t)(val & 0377);
+ return p;
+}
+
+// regnext - dig the "next" pointer out of a node
+// Returns NULL when calculating size, when there is no next item and when
+// there is an error.
+static uint8_t *regnext(uint8_t *p)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int offset;
+
+ if (p == JUST_CALC_SIZE || reg_toolong) {
+ return NULL;
+ }
+
+ offset = NEXT(p);
+ if (offset == 0) {
+ return NULL;
+ }
+
+ if (OP(p) == BACK) {
+ return p - offset;
+ } else {
+ return p + offset;
+ }
+}
+
+// Set the next-pointer at the end of a node chain.
+static void regtail(uint8_t *p, const uint8_t *val)
+{
+ int offset;
+
+ if (p == JUST_CALC_SIZE) {
+ return;
+ }
+
+ // Find last node.
+ uint8_t *scan = p;
+ while (true) {
+ uint8_t *temp = regnext(scan);
+ if (temp == NULL) {
+ break;
+ }
+ scan = temp;
+ }
+
+ if (OP(scan) == BACK) {
+ offset = (int)(scan - val);
+ } else {
+ offset = (int)(val - scan);
+ }
+ // When the offset uses more than 16 bits it can no longer fit in the two
+ // bytes available. Use a global flag to avoid having to check return
+ // values in too many places.
+ if (offset > 0xffff) {
+ reg_toolong = true;
+ } else {
+ *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377);
+ *(scan + 2) = (uint8_t)(offset & 0377);
+ }
+}
+
+// Like regtail, on item after a BRANCH; nop if none.
+static void regoptail(uint8_t *p, uint8_t *val)
+{
+ // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless"
+ if (p == NULL || p == JUST_CALC_SIZE
+ || (OP(p) != BRANCH
+ && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) {
+ return;
+ }
+ regtail(OPERAND(p), val);
+}
+
+// Insert an operator in front of already-emitted operand
+//
+// Means relocating the operand.
+static void reginsert(int op, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 3;
+ return;
+ }
+ src = regcode;
+ regcode += 3;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place = NUL;
+}
+
+// Insert an operator in front of already-emitted operand.
+// Add a number to the operator.
+static void reginsert_nr(int op, int64_t val, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 7;
+ return;
+ }
+ src = regcode;
+ regcode += 7;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place++ = NUL;
+ assert(val >= 0 && (uintmax_t)val <= UINT32_MAX);
+ re_put_uint32(place, (uint32_t)val);
+}
+
+// Insert an operator in front of already-emitted operand.
+// The operator has the given limit values as operands. Also set next pointer.
+//
+// Means relocating the operand.
+static void reginsert_limits(int op, int64_t minval, int64_t maxval, uint8_t *opnd)
+{
+ uint8_t *src;
+ uint8_t *dst;
+ uint8_t *place;
+
+ if (regcode == JUST_CALC_SIZE) {
+ regsize += 11;
+ return;
+ }
+ src = regcode;
+ regcode += 11;
+ dst = regcode;
+ while (src > opnd) {
+ *--dst = *--src;
+ }
+
+ place = opnd; // Op node, where operand used to be.
+ *place++ = (uint8_t)op;
+ *place++ = NUL;
+ *place++ = NUL;
+ assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX);
+ place = re_put_uint32(place, (uint32_t)minval);
+ assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX);
+ place = re_put_uint32(place, (uint32_t)maxval);
+ regtail(opnd, place);
+}
+
+/// Return true if the back reference is legal. We must have seen the close
+/// brace.
+/// TODO(vim): Should also check that we don't refer to something repeated
+/// (+*=): what instance of the repetition should we match?
+static int seen_endbrace(int refnum)
+{
+ if (!had_endbrace[refnum]) {
+ uint8_t *p;
+
+ // Trick: check if "@<=" or "@<!" follows, in which case
+ // the \1 can appear before the referenced match.
+ for (p = (uint8_t *)regparse; *p != NUL; p++) {
+ if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) {
+ break;
+ }
+ }
+
+ if (*p == NUL) {
+ emsg(_("E65: Illegal back reference"));
+ rc_did_emsg = true;
+ return false;
+ }
+ }
+ return true;
+}
+
+// Parse the lowest level.
+//
+// Optimization: gobbles an entire sequence of ordinary characters so that
+// it can turn them into a single node, which is smaller to store and
+// faster to run. Don't do this when one_exactly is set.
+static uint8_t *regatom(int *flagp)
+{
+ uint8_t *ret;
+ int flags;
+ int c;
+ uint8_t *p;
+ int extra = 0;
+ int save_prev_at_start = prev_at_start;
+
+ *flagp = WORST; // Tentatively.
+
+ c = getchr();
+ switch (c) {
+ case Magic('^'):
+ ret = regnode(BOL);
+ break;
+
+ case Magic('$'):
+ ret = regnode(EOL);
+ had_eol = true;
+ break;
+
+ case Magic('<'):
+ ret = regnode(BOW);
+ break;
+
+ case Magic('>'):
+ ret = regnode(EOW);
+ break;
+
+ case Magic('_'):
+ c = no_Magic(getchr());
+ if (c == '^') { // "\_^" is start-of-line
+ ret = regnode(BOL);
+ break;
+ }
+ if (c == '$') { // "\_$" is end-of-line
+ ret = regnode(EOL);
+ had_eol = true;
+ break;
+ }
+
+ extra = ADD_NL;
+ *flagp |= HASNL;
+
+ // "\_[" is character range plus newline
+ if (c == '[') {
+ goto collection;
+ }
+
+ // "\_x" is character class plus newline
+ FALLTHROUGH;
+
+ // Character classes.
+ case Magic('.'):
+ case Magic('i'):
+ case Magic('I'):
+ case Magic('k'):
+ case Magic('K'):
+ case Magic('f'):
+ case Magic('F'):
+ case Magic('p'):
+ case Magic('P'):
+ case Magic('s'):
+ case Magic('S'):
+ case Magic('d'):
+ case Magic('D'):
+ case Magic('x'):
+ case Magic('X'):
+ case Magic('o'):
+ case Magic('O'):
+ case Magic('w'):
+ case Magic('W'):
+ case Magic('h'):
+ case Magic('H'):
+ case Magic('a'):
+ case Magic('A'):
+ case Magic('l'):
+ case Magic('L'):
+ case Magic('u'):
+ case Magic('U'):
+ p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
+ if (p == NULL) {
+ EMSG_RET_NULL(_(e_invalid_use_of_underscore));
+ }
+ // When '.' is followed by a composing char ignore the dot, so that
+ // the composing char is matched here.
+ if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ c = getchr();
+ goto do_multibyte;
+ }
+ ret = regnode(classcodes[p - classchars] + extra);
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+
+ case Magic('n'):
+ if (reg_string) {
+ // In a string "\n" matches a newline character.
+ ret = regnode(EXACTLY);
+ regc(NL);
+ regc(NUL);
+ *flagp |= HASWIDTH | SIMPLE;
+ } else {
+ // In buffer text "\n" matches the end of a line.
+ ret = regnode(NEWL);
+ *flagp |= HASWIDTH | HASNL;
+ }
+ break;
+
+ case Magic('('):
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_PAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
+ break;
+
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier.
+ // NOTREACHED
+
+ case Magic('='):
+ case Magic('?'):
+ case Magic('+'):
+ case Magic('@'):
+ case Magic('{'):
+ case Magic('*'):
+ c = no_Magic(c);
+ EMSG3_RET_NULL(_("E64: %s%c follows nothing"),
+ (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c);
+ // NOTREACHED
+
+ case Magic('~'): // previous substitute pattern
+ if (reg_prev_sub != NULL) {
+ uint8_t *lp;
+
+ ret = regnode(EXACTLY);
+ lp = (uint8_t *)reg_prev_sub;
+ while (*lp != NUL) {
+ regc(*lp++);
+ }
+ regc(NUL);
+ if (*reg_prev_sub != NUL) {
+ *flagp |= HASWIDTH;
+ if ((lp - (uint8_t *)reg_prev_sub) == 1) {
+ *flagp |= SIMPLE;
+ }
+ }
+ } else {
+ EMSG_RET_NULL(_(e_nopresub));
+ }
+ break;
+
+ case Magic('1'):
+ case Magic('2'):
+ case Magic('3'):
+ case Magic('4'):
+ case Magic('5'):
+ case Magic('6'):
+ case Magic('7'):
+ case Magic('8'):
+ case Magic('9'): {
+ int refnum;
+
+ refnum = c - Magic('0');
+ if (!seen_endbrace(refnum)) {
+ return NULL;
+ }
+ ret = regnode(BACKREF + refnum);
+ }
+ break;
+
+ case Magic('z'):
+ c = no_Magic(getchr());
+ switch (c) {
+ case '(':
+ if ((reg_do_extmatch & REX_SET) == 0) {
+ EMSG_RET_NULL(_(e_z_not_allowed));
+ }
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_ZPAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH);
+ re_has_z = REX_SET;
+ break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ if ((reg_do_extmatch & REX_USE) == 0) {
+ EMSG_RET_NULL(_(e_z1_not_allowed));
+ }
+ ret = regnode(ZREF + c - '0');
+ re_has_z = REX_USE;
+ break;
+
+ case 's':
+ ret = regnode(MOPEN + 0);
+ if (!re_mult_next("\\zs")) {
+ return NULL;
+ }
+ break;
+
+ case 'e':
+ ret = regnode(MCLOSE + 0);
+ if (!re_mult_next("\\ze")) {
+ return NULL;
+ }
+ break;
+
+ default:
+ EMSG_RET_NULL(_("E68: Invalid character after \\z"));
+ }
+ break;
+
+ case Magic('%'):
+ c = no_Magic(getchr());
+ switch (c) {
+ // () without a back reference
+ case '(':
+ if (one_exactly) {
+ EMSG_ONE_RET_NULL;
+ }
+ ret = reg(REG_NPAREN, &flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
+ break;
+
+ // Catch \%^ and \%$ regardless of where they appear in the
+ // pattern -- regardless of whether or not it makes sense.
+ case '^':
+ ret = regnode(RE_BOF);
+ break;
+
+ case '$':
+ ret = regnode(RE_EOF);
+ break;
+
+ case '#':
+ if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) {
+ // misplaced \%#=1
+ semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
+ return FAIL;
+ }
+ ret = regnode(CURSOR);
+ break;
+
+ case 'V':
+ ret = regnode(RE_VISUAL);
+ break;
+
+ case 'C':
+ ret = regnode(RE_COMPOSING);
+ break;
+
+ // \%[abc]: Emit as a list of branches, all ending at the last
+ // branch which matches nothing.
+ case '[':
+ if (one_exactly) { // doesn't nest
+ EMSG_ONE_RET_NULL;
+ }
+ {
+ uint8_t *lastbranch;
+ uint8_t *lastnode = NULL;
+ uint8_t *br;
+
+ ret = NULL;
+ while ((c = getchr()) != ']') {
+ if (c == NUL) {
+ EMSG2_RET_NULL(_(e_missing_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ br = regnode(BRANCH);
+ if (ret == NULL) {
+ ret = br;
+ } else {
+ regtail(lastnode, br);
+ if (reg_toolong) {
+ return NULL;
+ }
+ }
+
+ ungetchr();
+ one_exactly = true;
+ lastnode = regatom(flagp);
+ one_exactly = false;
+ if (lastnode == NULL) {
+ return NULL;
+ }
+ }
+ if (ret == NULL) {
+ EMSG2_RET_NULL(_(e_empty_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ lastbranch = regnode(BRANCH);
+ br = regnode(NOTHING);
+ if (ret != JUST_CALC_SIZE) {
+ regtail(lastnode, br);
+ regtail(lastbranch, br);
+ // connect all branches to the NOTHING
+ // branch at the end
+ for (br = ret; br != lastnode;) {
+ if (OP(br) == BRANCH) {
+ regtail(br, lastbranch);
+ if (reg_toolong) {
+ return NULL;
+ }
+ br = OPERAND(br);
+ } else {
+ br = regnext(br);
+ }
+ }
+ }
+ *flagp &= ~(HASWIDTH | SIMPLE);
+ break;
+ }
+
+ case 'd': // %d123 decimal
+ case 'o': // %o123 octal
+ case 'x': // %xab hex 2
+ case 'u': // %uabcd hex 4
+ case 'U': // %U1234abcd hex 8
+ {
+ int64_t i;
+
+ switch (c) {
+ case 'd':
+ i = getdecchrs(); break;
+ case 'o':
+ i = getoctchrs(); break;
+ case 'x':
+ i = gethexchrs(2); break;
+ case 'u':
+ i = gethexchrs(4); break;
+ case 'U':
+ i = gethexchrs(8); break;
+ default:
+ i = -1; break;
+ }
+
+ if (i < 0 || i > INT_MAX) {
+ EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"),
+ reg_magic == MAGIC_ALL);
+ }
+ if (use_multibytecode((int)i)) {
+ ret = regnode(MULTIBYTECODE);
+ } else {
+ ret = regnode(EXACTLY);
+ }
+ if (i == 0) {
+ regc(0x0a);
+ } else {
+ regmbc((int)i);
+ }
+ regc(NUL);
+ *flagp |= HASWIDTH;
+ break;
+ }
+
+ default:
+ if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') {
+ uint32_t n = 0;
+ int cmp;
+ bool cur = false;
+ bool got_digit = false;
+
+ cmp = c;
+ if (cmp == '<' || cmp == '>') {
+ c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
+ while (ascii_isdigit(c)) {
+ got_digit = true;
+ n = n * 10 + (uint32_t)(c - '0');
+ c = getchr();
+ }
+ if (c == '\'' && n == 0) {
+ // "\%'m", "\%<'m" and "\%>'m": Mark
+ c = getchr();
+ ret = regnode(RE_MARK);
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 2;
+ } else {
+ *regcode++ = (uint8_t)c;
+ *regcode++ = (uint8_t)cmp;
+ }
+ break;
+ } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) {
+ if (cur && n) {
+ semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
+ rc_did_emsg = true;
+ return NULL;
+ }
+ if (c == 'l') {
+ if (cur) {
+ n = (uint32_t)curwin->w_cursor.lnum;
+ }
+ ret = regnode(RE_LNUM);
+ if (save_prev_at_start) {
+ at_start = true;
+ }
+ } else if (c == 'c') {
+ if (cur) {
+ n = (uint32_t)curwin->w_cursor.col;
+ n++;
+ }
+ ret = regnode(RE_COL);
+ } else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = (uint32_t)(++vcol);
+ }
+ ret = regnode(RE_VCOL);
+ }
+ if (ret == JUST_CALC_SIZE) {
+ regsize += 5;
+ } else {
+ // put the number and the optional
+ // comparator after the opcode
+ regcode = re_put_uint32(regcode, n);
+ *regcode++ = (uint8_t)cmp;
+ }
+ break;
+ }
+ }
+
+ EMSG2_RET_NULL(_("E71: Invalid character after %s%%"),
+ reg_magic == MAGIC_ALL);
+ }
+ break;
+
+ case Magic('['):
+collection:
+ {
+ uint8_t *lp;
+
+ // If there is no matching ']', we assume the '[' is a normal
+ // character. This makes 'incsearch' and ":help [" work.
+ lp = (uint8_t *)skip_anyof(regparse);
+ if (*lp == ']') { // there is a matching ']'
+ int startc = -1; // > 0 when next '-' is a range
+ int endc;
+
+ // In a character class, different parsing rules apply.
+ // Not even \ is special anymore, nothing is.
+ if (*regparse == '^') { // Complement of range.
+ ret = regnode(ANYBUT + extra);
+ regparse++;
+ } else {
+ ret = regnode(ANYOF + extra);
+ }
+
+ // At the start ']' and '-' mean the literal character.
+ if (*regparse == ']' || *regparse == '-') {
+ startc = (uint8_t)(*regparse);
+ regc(*regparse++);
+ }
+
+ while (*regparse != NUL && *regparse != ']') {
+ if (*regparse == '-') {
+ regparse++;
+ // The '-' is not used for a range at the end and
+ // after or before a '\n'.
+ if (*regparse == ']' || *regparse == NUL
+ || startc == -1
+ || (regparse[0] == '\\' && regparse[1] == 'n')) {
+ regc('-');
+ startc = '-'; // [--x] is a range
+ } else {
+ // Also accept "a-[.z.]"
+ endc = 0;
+ if (*regparse == '[') {
+ endc = get_coll_element(&regparse);
+ }
+ if (endc == 0) {
+ endc = mb_ptr2char_adv((const char **)&regparse);
+ }
+
+ // Handle \o40, \x20 and \u20AC style sequences
+ if (endc == '\\' && !reg_cpo_lit) {
+ endc = coll_get_char();
+ }
+
+ if (startc > endc) {
+ EMSG_RET_NULL(_(e_reverse_range));
+ }
+ if (utf_char2len(startc) > 1
+ || utf_char2len(endc) > 1) {
+ // Limit to a range of 256 chars
+ if (endc > startc + 256) {
+ EMSG_RET_NULL(_(e_large_class));
+ }
+ while (++startc <= endc) {
+ regmbc(startc);
+ }
+ } else {
+ while (++startc <= endc) {
+ regc(startc);
+ }
+ }
+ startc = -1;
+ }
+ }
+ // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
+ // accepts "\t", "\e", etc., but only when the 'l' flag in
+ // 'cpoptions' is not included.
+ else if (*regparse == '\\'
+ && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
+ || (!reg_cpo_lit
+ && vim_strchr(REGEXP_ABBR,
+ (uint8_t)regparse[1]) != NULL))) {
+ regparse++;
+ if (*regparse == 'n') {
+ // '\n' in range: also match NL
+ if (ret != JUST_CALC_SIZE) {
+ // Using \n inside [^] does not change what
+ // matches. "[^\n]" is the same as ".".
+ if (*ret == ANYOF) {
+ *ret = ANYOF + ADD_NL;
+ *flagp |= HASNL;
+ }
+ // else: must have had a \n already
+ }
+ regparse++;
+ startc = -1;
+ } else if (*regparse == 'd'
+ || *regparse == 'o'
+ || *regparse == 'x'
+ || *regparse == 'u'
+ || *regparse == 'U') {
+ startc = coll_get_char();
+ if (startc == 0) {
+ regc(0x0a);
+ } else {
+ regmbc(startc);
+ }
+ } else {
+ startc = backslash_trans(*regparse++);
+ regc(startc);
+ }
+ } else if (*regparse == '[') {
+ int c_class;
+ int cu;
+
+ c_class = get_char_class(&regparse);
+ startc = -1;
+ // Characters assumed to be 8 bits!
+ switch (c_class) {
+ case CLASS_NONE:
+ c_class = get_equi_class(&regparse);
+ if (c_class != 0) {
+ // produce equivalence class
+ reg_equi_class(c_class);
+ } else if ((c_class = get_coll_element(&regparse)) != 0) {
+ // produce a collating element
+ regmbc(c_class);
+ } else {
+ // literal '[', allow [[-x] as a range
+ startc = (uint8_t)(*regparse++);
+ regc(startc);
+ }
+ break;
+ case CLASS_ALNUM:
+ for (cu = 1; cu < 128; cu++) {
+ if (isalnum(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_ALPHA:
+ for (cu = 1; cu < 128; cu++) {
+ if (isalpha(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_BLANK:
+ regc(' ');
+ regc('\t');
+ break;
+ case CLASS_CNTRL:
+ for (cu = 1; cu <= 127; cu++) {
+ if (iscntrl(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_DIGIT:
+ for (cu = 1; cu <= 127; cu++) {
+ if (ascii_isdigit(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_GRAPH:
+ for (cu = 1; cu <= 127; cu++) {
+ if (isgraph(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_LOWER:
+ for (cu = 1; cu <= 255; cu++) {
+ if (mb_islower(cu) && cu != 170 && cu != 186) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_PRINT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isprintc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_PUNCT:
+ for (cu = 1; cu < 128; cu++) {
+ if (ispunct(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_SPACE:
+ for (cu = 9; cu <= 13; cu++) {
+ regc(cu);
+ }
+ regc(' ');
+ break;
+ case CLASS_UPPER:
+ for (cu = 1; cu <= 255; cu++) {
+ if (mb_isupper(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_XDIGIT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (ascii_isxdigit(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_TAB:
+ regc('\t');
+ break;
+ case CLASS_RETURN:
+ regc('\r');
+ break;
+ case CLASS_BACKSPACE:
+ regc('\b');
+ break;
+ case CLASS_ESCAPE:
+ regc(ESC);
+ break;
+ case CLASS_IDENT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isIDc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_KEYWORD:
+ for (cu = 1; cu <= 255; cu++) {
+ if (reg_iswordc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_FNAME:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isfilec(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ }
+ } else {
+ // produce a multibyte character, including any
+ // following composing characters.
+ startc = utf_ptr2char(regparse);
+ int len = utfc_ptr2len(regparse);
+ if (utf_char2len(startc) != len) {
+ // composing chars
+ startc = -1;
+ }
+ while (--len >= 0) {
+ regc(*regparse++);
+ }
+ }
+ }
+ regc(NUL);
+ prevchr_len = 1; // last char was the ']'
+ if (*regparse != ']') {
+ EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen?
+ }
+ skipchr(); // let's be friends with the lexer again
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+ } else if (reg_strict) {
+ EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF);
+ }
+ }
+ FALLTHROUGH;
+
+ default: {
+ int len;
+
+ // A multi-byte character is handled as a separate atom if it's
+ // before a multi and when it's a composing char.
+ if (use_multibytecode(c)) {
+do_multibyte:
+ ret = regnode(MULTIBYTECODE);
+ regmbc(c);
+ *flagp |= HASWIDTH | SIMPLE;
+ break;
+ }
+
+ ret = regnode(EXACTLY);
+
+ // Append characters as long as:
+ // - there is no following multi, we then need the character in
+ // front of it as a single character operand
+ // - not running into a Magic character
+ // - "one_exactly" is not set
+ // But always emit at least one character. Might be a Multi,
+ // e.g., a "[" without matching "]".
+ for (len = 0; c != NUL && (len == 0
+ || (re_multi_type(peekchr()) == NOT_MULTI
+ && !one_exactly
+ && !is_Magic(c))); len++) {
+ c = no_Magic(c);
+ {
+ regmbc(c);
+ {
+ int l;
+
+ // Need to get composing character too.
+ while (true) {
+ l = utf_ptr2len(regparse);
+ if (!utf_composinglike(regparse, regparse + l)) {
+ break;
+ }
+ regmbc(utf_ptr2char(regparse));
+ skipchr();
+ }
+ }
+ }
+ c = getchr();
+ }
+ ungetchr();
+
+ regc(NUL);
+ *flagp |= HASWIDTH;
+ if (len == 1) {
+ *flagp |= SIMPLE;
+ }
+ }
+ break;
+ }
+
+ return ret;
+}
+
+// Parse something followed by possible [*+=].
+//
+// Note that the branching code sequences used for = and the general cases
+// of * and + are somewhat optimized: they use the same NOTHING node as
+// both the endmarker for their branch list and the body of the last branch.
+// It might seem that this node could be dispensed with entirely, but the
+// endmarker role is not redundant.
+static uint8_t *regpiece(int *flagp)
+{
+ uint8_t *ret;
+ int op;
+ uint8_t *next;
+ int flags;
+ int minval;
+ int maxval;
+
+ ret = regatom(&flags);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ op = peekchr();
+ if (re_multi_type(op) == NOT_MULTI) {
+ *flagp = flags;
+ return ret;
+ }
+ // default flags
+ *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH)));
+
+ skipchr();
+ switch (op) {
+ case Magic('*'):
+ if (flags & SIMPLE) {
+ reginsert(STAR, ret);
+ } else {
+ // Emit x* as (x&|), where & means "self".
+ reginsert(BRANCH, ret); // Either x
+ regoptail(ret, regnode(BACK)); // and loop
+ regoptail(ret, ret); // back
+ regtail(ret, regnode(BRANCH)); // or
+ regtail(ret, regnode(NOTHING)); // null.
+ }
+ break;
+
+ case Magic('+'):
+ if (flags & SIMPLE) {
+ reginsert(PLUS, ret);
+ } else {
+ // Emit x+ as x(&|), where & means "self".
+ next = regnode(BRANCH); // Either
+ regtail(ret, next);
+ regtail(regnode(BACK), ret); // loop back
+ regtail(next, regnode(BRANCH)); // or
+ regtail(ret, regnode(NOTHING)); // null.
+ }
+ *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH)));
+ break;
+
+ case Magic('@'): {
+ int lop = END;
+ int64_t nr = getdecchrs();
+
+ switch (no_Magic(getchr())) {
+ case '=':
+ lop = MATCH; break; // \@=
+ case '!':
+ lop = NOMATCH; break; // \@!
+ case '>':
+ lop = SUBPAT; break; // \@>
+ case '<':
+ switch (no_Magic(getchr())) {
+ case '=':
+ lop = BEHIND; break; // \@<=
+ case '!':
+ lop = NOBEHIND; break; // \@<!
+ }
+ }
+ if (lop == END) {
+ EMSG2_RET_NULL(_(e_invalid_character_after_str_at),
+ reg_magic == MAGIC_ALL);
+ }
+ // Look behind must match with behind_pos.
+ if (lop == BEHIND || lop == NOBEHIND) {
+ regtail(ret, regnode(BHPOS));
+ *flagp |= HASLOOKBH;
+ }
+ regtail(ret, regnode(END)); // operand ends
+ if (lop == BEHIND || lop == NOBEHIND) {
+ if (nr < 0) {
+ nr = 0; // no limit is same as zero limit
+ }
+ reginsert_nr(lop, (uint32_t)nr, ret);
+ } else {
+ reginsert(lop, ret);
+ }
+ break;
+ }
+
+ case Magic('?'):
+ case Magic('='):
+ // Emit x= as (x|)
+ reginsert(BRANCH, ret); // Either x
+ regtail(ret, regnode(BRANCH)); // or
+ next = regnode(NOTHING); // null.
+ regtail(ret, next);
+ regoptail(ret, next);
+ break;
+
+ case Magic('{'):
+ if (!read_limits(&minval, &maxval)) {
+ return NULL;
+ }
+ if (flags & SIMPLE) {
+ reginsert(BRACE_SIMPLE, ret);
+ reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
+ } else {
+ if (num_complex_braces >= 10) {
+ EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"),
+ reg_magic == MAGIC_ALL);
+ }
+ reginsert(BRACE_COMPLEX + num_complex_braces, ret);
+ regoptail(ret, regnode(BACK));
+ regoptail(ret, ret);
+ reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
+ num_complex_braces++;
+ }
+ if (minval > 0 && maxval > 0) {
+ *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH)));
+ }
+ break;
+ }
+ if (re_multi_type(peekchr()) != NOT_MULTI) {
+ // Can't have a multi follow a multi.
+ if (peekchr() == Magic('*')) {
+ EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON);
+ }
+ EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr()));
+ }
+
+ return ret;
+}
+
+// Parse one alternative of an | or & operator.
+// Implements the concatenation operator.
+static uint8_t *regconcat(int *flagp)
+{
+ uint8_t *first = NULL;
+ uint8_t *chain = NULL;
+ uint8_t *latest;
+ int flags;
+ int cont = true;
+
+ *flagp = WORST; // Tentatively.
+
+ while (cont) {
+ switch (peekchr()) {
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ cont = false;
+ break;
+ case Magic('Z'):
+ regflags |= RF_ICOMBINE;
+ skipchr_keepstart();
+ break;
+ case Magic('c'):
+ regflags |= RF_ICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('C'):
+ regflags |= RF_NOICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('v'):
+ reg_magic = MAGIC_ALL;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('m'):
+ reg_magic = MAGIC_ON;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('M'):
+ reg_magic = MAGIC_OFF;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('V'):
+ reg_magic = MAGIC_NONE;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ default:
+ latest = regpiece(&flags);
+ if (latest == NULL || reg_toolong) {
+ return NULL;
+ }
+ *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH);
+ if (chain == NULL) { // First piece.
+ *flagp |= flags & SPSTART;
+ } else {
+ regtail(chain, latest);
+ }
+ chain = latest;
+ if (first == NULL) {
+ first = latest;
+ }
+ break;
+ }
+ }
+ if (first == NULL) { // Loop ran zero times.
+ first = regnode(NOTHING);
+ }
+ return first;
+}
+
+// Parse one alternative of an | operator.
+// Implements the & operator.
+static uint8_t *regbranch(int *flagp)
+{
+ uint8_t *ret;
+ uint8_t *chain = NULL;
+ uint8_t *latest;
+ int flags;
+
+ *flagp = WORST | HASNL; // Tentatively.
+
+ ret = regnode(BRANCH);
+ while (true) {
+ latest = regconcat(&flags);
+ if (latest == NULL) {
+ return NULL;
+ }
+ // If one of the branches has width, the whole thing has. If one of
+ // the branches anchors at start-of-line, the whole thing does.
+ // If one of the branches uses look-behind, the whole thing does.
+ *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH);
+ // If one of the branches doesn't match a line-break, the whole thing
+ // doesn't.
+ *flagp &= ~HASNL | (flags & HASNL);
+ if (chain != NULL) {
+ regtail(chain, latest);
+ }
+ if (peekchr() != Magic('&')) {
+ break;
+ }
+ skipchr();
+ regtail(latest, regnode(END)); // operand ends
+ if (reg_toolong) {
+ break;
+ }
+ reginsert(MATCH, latest);
+ chain = latest;
+ }
+
+ return ret;
+}
+
+/// Parse regular expression, i.e. main body or parenthesized thing.
+///
+/// Caller must absorb opening parenthesis.
+///
+/// Combining parenthesis handling with the base level of regular expression
+/// is a trifle forced, but the need to tie the tails of the branches to what
+/// follows makes it hard to avoid.
+///
+/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
+static uint8_t *reg(int paren, int *flagp)
+{
+ uint8_t *ret;
+ uint8_t *br;
+ uint8_t *ender;
+ int parno = 0;
+ int flags;
+
+ *flagp = HASWIDTH; // Tentatively.
+
+ if (paren == REG_ZPAREN) {
+ // Make a ZOPEN node.
+ if (regnzpar >= NSUBEXP) {
+ EMSG_RET_NULL(_("E50: Too many \\z("));
+ }
+ parno = regnzpar;
+ regnzpar++;
+ ret = regnode(ZOPEN + parno);
+ } else if (paren == REG_PAREN) {
+ // Make a MOPEN node.
+ if (regnpar >= NSUBEXP) {
+ EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL);
+ }
+ parno = regnpar;
+ regnpar++;
+ ret = regnode(MOPEN + parno);
+ } else if (paren == REG_NPAREN) {
+ // Make a NOPEN node.
+ ret = regnode(NOPEN);
+ } else {
+ ret = NULL;
+ }
+
+ // Pick up the branches, linking them together.
+ br = regbranch(&flags);
+ if (br == NULL) {
+ return NULL;
+ }
+ if (ret != NULL) {
+ regtail(ret, br); // [MZ]OPEN -> first.
+ } else {
+ ret = br;
+ }
+ // If one of the branches can be zero-width, the whole thing can.
+ // If one of the branches has * at start or matches a line-break, the
+ // whole thing can.
+ if (!(flags & HASWIDTH)) {
+ *flagp &= ~HASWIDTH;
+ }
+ *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
+ while (peekchr() == Magic('|')) {
+ skipchr();
+ br = regbranch(&flags);
+ if (br == NULL || reg_toolong) {
+ return NULL;
+ }
+ regtail(ret, br); // BRANCH -> BRANCH.
+ if (!(flags & HASWIDTH)) {
+ *flagp &= ~HASWIDTH;
+ }
+ *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
+ }
+
+ // Make a closing node, and hook it on the end.
+ ender = regnode(paren == REG_ZPAREN ? ZCLOSE + parno
+ : paren == REG_PAREN ? MCLOSE + parno
+ : paren == REG_NPAREN ? NCLOSE : END);
+ regtail(ret, ender);
+
+ // Hook the tails of the branches to the closing node.
+ for (br = ret; br != NULL; br = regnext(br)) {
+ regoptail(br, ender);
+ }
+
+ // Check for proper termination.
+ if (paren != REG_NOPAREN && getchr() != Magic(')')) {
+ if (paren == REG_ZPAREN) {
+ EMSG_RET_NULL(_("E52: Unmatched \\z("));
+ } else if (paren == REG_NPAREN) {
+ EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
+ }
+ } else if (paren == REG_NOPAREN && peekchr() != NUL) {
+ if (curchr == Magic(')')) {
+ EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG_RET_NULL(_(e_trailing)); // "Can't happen".
+ }
+ // NOTREACHED
+ }
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
+ if (paren == REG_PAREN) {
+ had_endbrace[parno] = true; // have seen the close paren
+ }
+ return ret;
+}
+
+// bt_regcomp() - compile a regular expression into internal code for the
+// traditional back track matcher.
+// Returns the program in allocated space. Returns NULL for an error.
+//
+// We can't allocate space until we know how big the compiled form will be,
+// but we can't compile it (and thus know how big it is) until we've got a
+// place to put the code. So we cheat: we compile it twice, once with code
+// generation turned off and size counting turned on, and once "for real".
+// This also means that we don't allocate space until we are sure that the
+// thing really will compile successfully, and we never have to move the
+// code and thus invalidate pointers into it. (Note that it has to be in
+// one piece because free() must be able to free it all.)
+//
+// Whether upper/lower case is to be ignored is decided when executing the
+// program, it does not matter here.
+//
+// Beware that the optimization-preparation code in here knows about some
+// of the structure of the compiled regexp.
+// "re_flags": RE_MAGIC and/or RE_STRING.
+static regprog_T *bt_regcomp(uint8_t *expr, int re_flags)
+{
+ uint8_t *scan;
+ uint8_t *longest;
+ int len;
+ int flags;
+
+ if (expr == NULL) {
+ IEMSG_RET_NULL(_(e_null));
+ }
+
+ init_class_tab();
+
+ // First pass: determine size, legality.
+ regcomp_start(expr, re_flags);
+ regcode = JUST_CALC_SIZE;
+ regc(REGMAGIC);
+ if (reg(REG_NOPAREN, &flags) == NULL) {
+ return NULL;
+ }
+
+ // Allocate space.
+ bt_regprog_T *r = xmalloc(offsetof(bt_regprog_T, program) + (size_t)regsize);
+ r->re_in_use = false;
+
+ // Second pass: emit code.
+ regcomp_start(expr, re_flags);
+ regcode = r->program;
+ regc(REGMAGIC);
+ if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) {
+ xfree(r);
+ if (reg_toolong) {
+ EMSG_RET_NULL(_("E339: Pattern too long"));
+ }
+ return NULL;
+ }
+
+ // Dig out information for optimizations.
+ r->regstart = NUL; // Worst-case defaults.
+ r->reganch = 0;
+ r->regmust = NULL;
+ r->regmlen = 0;
+ r->regflags = regflags;
+ if (flags & HASNL) {
+ r->regflags |= RF_HASNL;
+ }
+ if (flags & HASLOOKBH) {
+ r->regflags |= RF_LOOKBH;
+ }
+ // Remember whether this pattern has any \z specials in it.
+ r->reghasz = (uint8_t)re_has_z;
+ scan = r->program + 1; // First BRANCH.
+ if (OP(regnext(scan)) == END) { // Only one top-level choice.
+ scan = OPERAND(scan);
+
+ // Starting-point info.
+ if (OP(scan) == BOL || OP(scan) == RE_BOF) {
+ r->reganch++;
+ scan = regnext(scan);
+ }
+
+ if (OP(scan) == EXACTLY) {
+ r->regstart = utf_ptr2char((char *)OPERAND(scan));
+ } else if (OP(scan) == BOW
+ || OP(scan) == EOW
+ || OP(scan) == NOTHING
+ || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN
+ || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) {
+ uint8_t *regnext_scan = regnext(scan);
+ if (OP(regnext_scan) == EXACTLY) {
+ r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan));
+ }
+ }
+
+ // If there's something expensive in the r.e., find the longest
+ // literal string that must appear and make it the regmust. Resolve
+ // ties in favor of later strings, since the regstart check works
+ // with the beginning of the r.e. and avoiding duplication
+ // strengthens checking. Not a strong reason, but sufficient in the
+ // absence of others.
+
+ // When the r.e. starts with BOW, it is faster to look for a regmust
+ // first. Used a lot for "#" and "*" commands. (Added by mool).
+ if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW)
+ && !(flags & HASNL)) {
+ longest = NULL;
+ len = 0;
+ for (; scan != NULL; scan = regnext(scan)) {
+ if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) {
+ longest = OPERAND(scan);
+ len = (int)strlen((char *)OPERAND(scan));
+ }
+ }
+ r->regmust = longest;
+ r->regmlen = len;
+ }
+ }
+#ifdef BT_REGEXP_DUMP
+ regdump(expr, r);
+#endif
+ r->engine = &bt_regengine;
+ return (regprog_T *)r;
+}
+
+// Check if during the previous call to vim_regcomp the EOL item "$" has been
+// found. This is messy, but it works fine.
+int vim_regcomp_had_eol(void)
+{
+ return had_eol;
+}
+
+// Get a number after a backslash that is inside [].
+// When nothing is recognized return a backslash.
+static int coll_get_char(void)
+{
+ int64_t nr = -1;
+
+ switch (*regparse++) {
+ case 'd':
+ nr = getdecchrs(); break;
+ case 'o':
+ nr = getoctchrs(); break;
+ case 'x':
+ nr = gethexchrs(2); break;
+ case 'u':
+ nr = gethexchrs(4); break;
+ case 'U':
+ nr = gethexchrs(8); break;
+ }
+ if (nr < 0 || nr > INT_MAX) {
+ // If getting the number fails be backwards compatible: the character
+ // is a backslash.
+ regparse--;
+ nr = '\\';
+ }
+ return (int)nr;
+}
+
+// Free a compiled regexp program, returned by bt_regcomp().
+static void bt_regfree(regprog_T *prog)
+{
+ xfree(prog);
+}
+
+#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
+
+// The arguments from BRACE_LIMITS are stored here. They are actually local
+// to regmatch(), but they are here to reduce the amount of stack space used
+// (it can be called recursively many times).
+static int64_t bl_minval;
+static int64_t bl_maxval;
+
+// Save the input line and position in a regsave_T.
+static void reg_save(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ save->rs_u.pos.col = (colnr_T)(rex.input - rex.line);
+ save->rs_u.pos.lnum = rex.lnum;
+ } else {
+ save->rs_u.ptr = rex.input;
+ }
+ save->rs_len = gap->ga_len;
+}
+
+// Restore the input line and position from a regsave_T.
+static void reg_restore(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ if (rex.lnum != save->rs_u.pos.lnum) {
+ // only call reg_getline() when the line number changed to save
+ // a bit of time
+ rex.lnum = save->rs_u.pos.lnum;
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save->rs_u.pos.col;
+ } else {
+ rex.input = save->rs_u.ptr;
+ }
+ gap->ga_len = save->rs_len;
+}
+
+// Return true if current position is equal to saved position.
+static bool reg_save_equal(const regsave_T *save)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (REG_MULTI) {
+ return rex.lnum == save->rs_u.pos.lnum
+ && rex.input == rex.line + save->rs_u.pos.col;
+ }
+ return rex.input == save->rs_u.ptr;
+}
+
+// Save the sub-expressions before attempting a match.
+#define save_se(savep, posp, pp) \
+ REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp))
+
+// After a failed match restore the sub-expressions.
+#define restore_se(savep, posp, pp) { \
+ if (REG_MULTI) /* NOLINT(readability/braces) */ \
+ *(posp) = (savep)->se_u.pos; \
+ else /* NOLINT */ \
+ *(pp) = (savep)->se_u.ptr; }
+
+// Tentatively set the sub-expression start to the current position (after
+// calling regmatch() they will have changed). Need to save the existing
+// values for when there is no match.
+// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
+// depending on REG_MULTI.
+static void save_se_multi(save_se_T *savep, lpos_T *posp)
+{
+ savep->se_u.pos = *posp;
+ posp->lnum = rex.lnum;
+ posp->col = (colnr_T)(rex.input - rex.line);
+}
+
+static void save_se_one(save_se_T *savep, uint8_t **pp)
+{
+ savep->se_u.ptr = *pp;
+ *pp = rex.input;
+}
+
+/// regrepeat - repeatedly match something simple, return how many.
+/// Advances rex.input (and rex.lnum) to just after the matched chars.
+///
+/// @param maxcount maximum number of matches allowed
+static int regrepeat(uint8_t *p, int64_t maxcount)
+{
+ int64_t count = 0;
+ uint8_t *opnd;
+ int mask;
+ int testval = 0;
+
+ uint8_t *scan = rex.input; // Make local copy of rex.input for speed.
+ opnd = OPERAND(p);
+ switch (OP(p)) {
+ case ANY:
+ case ANY + ADD_NL:
+ while (count < maxcount) {
+ // Matching anything means we continue until end-of-line (or
+ // end-of-file for ANY + ADD_NL), only limited by maxcount.
+ while (*scan != NUL && count < maxcount) {
+ count++;
+ MB_PTR_ADV(scan);
+ }
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr || count == maxcount) {
+ break;
+ }
+ count++; // count the line-break
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ }
+ break;
+
+ case IDENT:
+ case IDENT + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SIDENT:
+ case SIDENT + ADD_NL:
+ while (count < maxcount) {
+ if (vim_isIDc(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case KWORD:
+ case KWORD + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SKWORD:
+ case SKWORD + ADD_NL:
+ while (count < maxcount) {
+ if (vim_iswordp_buf((char *)scan, rex.reg_buf)
+ && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case FNAME:
+ case FNAME + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SFNAME:
+ case SFNAME + ADD_NL:
+ while (count < maxcount) {
+ if (vim_isfilec(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case PRINT:
+ case PRINT + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+ case SPRINT:
+ case SPRINT + ADD_NL:
+ while (count < maxcount) {
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (vim_isprintc(utf_ptr2char((char *)scan)) == 1
+ && (testval || !ascii_isdigit(*scan))) {
+ MB_PTR_ADV(scan);
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case WHITE:
+ case WHITE + ADD_NL:
+ testval = mask = RI_WHITE;
+do_class:
+ while (count < maxcount) {
+ int l;
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if ((l = utfc_ptr2len((char *)scan)) > 1) {
+ if (testval != 0) {
+ break;
+ }
+ scan += l;
+ } else if ((class_tab[*scan] & mask) == testval) {
+ scan++;
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else {
+ break;
+ }
+ count++;
+ }
+ break;
+
+ case NWHITE:
+ case NWHITE + ADD_NL:
+ mask = RI_WHITE;
+ goto do_class;
+ case DIGIT:
+ case DIGIT + ADD_NL:
+ testval = mask = RI_DIGIT;
+ goto do_class;
+ case NDIGIT:
+ case NDIGIT + ADD_NL:
+ mask = RI_DIGIT;
+ goto do_class;
+ case HEX:
+ case HEX + ADD_NL:
+ testval = mask = RI_HEX;
+ goto do_class;
+ case NHEX:
+ case NHEX + ADD_NL:
+ mask = RI_HEX;
+ goto do_class;
+ case OCTAL:
+ case OCTAL + ADD_NL:
+ testval = mask = RI_OCTAL;
+ goto do_class;
+ case NOCTAL:
+ case NOCTAL + ADD_NL:
+ mask = RI_OCTAL;
+ goto do_class;
+ case WORD:
+ case WORD + ADD_NL:
+ testval = mask = RI_WORD;
+ goto do_class;
+ case NWORD:
+ case NWORD + ADD_NL:
+ mask = RI_WORD;
+ goto do_class;
+ case HEAD:
+ case HEAD + ADD_NL:
+ testval = mask = RI_HEAD;
+ goto do_class;
+ case NHEAD:
+ case NHEAD + ADD_NL:
+ mask = RI_HEAD;
+ goto do_class;
+ case ALPHA:
+ case ALPHA + ADD_NL:
+ testval = mask = RI_ALPHA;
+ goto do_class;
+ case NALPHA:
+ case NALPHA + ADD_NL:
+ mask = RI_ALPHA;
+ goto do_class;
+ case LOWER:
+ case LOWER + ADD_NL:
+ testval = mask = RI_LOWER;
+ goto do_class;
+ case NLOWER:
+ case NLOWER + ADD_NL:
+ mask = RI_LOWER;
+ goto do_class;
+ case UPPER:
+ case UPPER + ADD_NL:
+ testval = mask = RI_UPPER;
+ goto do_class;
+ case NUPPER:
+ case NUPPER + ADD_NL:
+ mask = RI_UPPER;
+ goto do_class;
+
+ case EXACTLY: {
+ int cu, cl;
+
+ // This doesn't do a multi-byte character, because a MULTIBYTECODE
+ // would have been used for it. It does handle single-byte
+ // characters, such as latin1.
+ if (rex.reg_ic) {
+ cu = mb_toupper(*opnd);
+ cl = mb_tolower(*opnd);
+ while (count < maxcount && (*scan == cu || *scan == cl)) {
+ count++;
+ scan++;
+ }
+ } else {
+ cu = *opnd;
+ while (count < maxcount && *scan == cu) {
+ count++;
+ scan++;
+ }
+ }
+ break;
+ }
+
+ case MULTIBYTECODE: {
+ int i, len, cf = 0;
+
+ // Safety check (just in case 'encoding' was changed since
+ // compiling the program).
+ if ((len = utfc_ptr2len((char *)opnd)) > 1) {
+ if (rex.reg_ic) {
+ cf = utf_fold(utf_ptr2char((char *)opnd));
+ }
+ while (count < maxcount && utfc_ptr2len((char *)scan) >= len) {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != scan[i]) {
+ break;
+ }
+ }
+ if (i < len && (!rex.reg_ic
+ || utf_fold(utf_ptr2char((char *)scan)) != cf)) {
+ break;
+ }
+ scan += len;
+ count++;
+ }
+ }
+ }
+ break;
+
+ case ANYOF:
+ case ANYOF + ADD_NL:
+ testval = 1;
+ FALLTHROUGH;
+
+ case ANYBUT:
+ case ANYBUT + ADD_NL:
+ while (count < maxcount) {
+ int len;
+ if (*scan == NUL) {
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) {
+ break;
+ }
+ reg_nextline();
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
+ scan++;
+ } else if ((len = utfc_ptr2len((char *)scan)) > 1) {
+ if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) {
+ break;
+ }
+ scan += len;
+ } else {
+ if ((cstrchr((char *)opnd, *scan) == NULL) == testval) {
+ break;
+ }
+ scan++;
+ }
+ count++;
+ }
+ break;
+
+ case NEWL:
+ while (count < maxcount
+ && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr
+ && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) {
+ count++;
+ if (rex.reg_line_lbr) {
+ ADVANCE_REGINPUT();
+ } else {
+ reg_nextline();
+ }
+ scan = rex.input;
+ if (got_int) {
+ break;
+ }
+ }
+ break;
+
+ default: // Oh dear. Called inappropriately.
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Called regrepeat with op code %d\n", OP(p));
+#endif
+ break;
+ }
+
+ rex.input = scan;
+
+ return (int)count;
+}
+
+// Push an item onto the regstack.
+// Returns pointer to new item. Returns NULL when out of memory.
+static regitem_T *regstack_push(regstate_T state, uint8_t *scan)
+{
+ regitem_T *rp;
+
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ return NULL;
+ }
+ ga_grow(&regstack, sizeof(regitem_T));
+
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len);
+ rp->rs_state = state;
+ rp->rs_scan = scan;
+
+ regstack.ga_len += (int)sizeof(regitem_T);
+ return rp;
+}
+
+// Pop an item from the regstack.
+static void regstack_pop(uint8_t **scan)
+{
+ regitem_T *rp;
+
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
+ *scan = rp->rs_scan;
+
+ regstack.ga_len -= (int)sizeof(regitem_T);
+}
+
+// Save the current subexpr to "bp", so that they can be restored
+// later by restore_subexpr().
+static void save_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // When "rex.need_clear_subexpr" is set we don't need to save the values, only
+ // remember that this flag needs to be set again when restoring.
+ bp->save_need_clear_subexpr = rex.need_clear_subexpr;
+ if (rex.need_clear_subexpr) {
+ return;
+ }
+
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ bp->save_start[i].se_u.pos = rex.reg_startpos[i];
+ bp->save_end[i].se_u.pos = rex.reg_endpos[i];
+ } else {
+ bp->save_start[i].se_u.ptr = rex.reg_startp[i];
+ bp->save_end[i].se_u.ptr = rex.reg_endp[i];
+ }
+ }
+}
+
+// Restore the subexpr from "bp".
+static void restore_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Only need to restore saved values when they are not to be cleared.
+ rex.need_clear_subexpr = bp->save_need_clear_subexpr;
+ if (rex.need_clear_subexpr) {
+ return;
+ }
+
+ for (int i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ rex.reg_startpos[i] = bp->save_start[i].se_u.pos;
+ rex.reg_endpos[i] = bp->save_end[i].se_u.pos;
+ } else {
+ rex.reg_startp[i] = bp->save_start[i].se_u.ptr;
+ rex.reg_endp[i] = bp->save_end[i].se_u.ptr;
+ }
+ }
+}
+/// Main matching routine
+///
+/// Conceptually the strategy is simple: Check to see whether the current node
+/// matches, push an item onto the regstack and loop to see whether the rest
+/// matches, and then act accordingly. In practice we make some effort to
+/// avoid using the regstack, in particular by going through "ordinary" nodes
+/// (that don't need to know whether the rest of the match failed) by a nested
+/// loop.
+///
+/// @param scan Current node.
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return - true when there is a match. Leaves rex.input and rex.lnum
+/// just after the last matched character.
+/// - false when there is no match. Leaves rex.input and rex.lnum in an
+/// undefined state!
+static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
+{
+ uint8_t *next; // Next node.
+ int op;
+ int c;
+ regitem_T *rp;
+ int no;
+ int status; // one of the RA_ values:
+ int tm_count = 0;
+
+ // Make "regstack" and "backpos" empty. They are allocated and freed in
+ // bt_regexec_both() to reduce malloc()/free() calls.
+ regstack.ga_len = 0;
+ backpos.ga_len = 0;
+
+ // Repeat until "regstack" is empty.
+ while (true) {
+ // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q".
+ // Allow interrupting them with CTRL-C.
+ reg_breakcheck();
+
+#ifdef REGEXP_DEBUG
+ if (scan != NULL && regnarrate) {
+ os_errmsg((char *)regprop(scan));
+ os_errmsg("(\n");
+ }
+#endif
+
+ // Repeat for items that can be matched sequentially, without using the
+ // regstack.
+ while (true) {
+ if (got_int || scan == NULL) {
+ status = RA_FAIL;
+ break;
+ }
+ // Check for timeout once in a 100 times to avoid overhead.
+ if (tm != NULL && ++tm_count == 100) {
+ tm_count = 0;
+ if (profile_passed_limit(*tm)) {
+ if (timed_out != NULL) {
+ *timed_out = true;
+ }
+ status = RA_FAIL;
+ break;
+ }
+ }
+ status = RA_CONT;
+
+#ifdef REGEXP_DEBUG
+ if (regnarrate) {
+ os_errmsg((char *)regprop(scan));
+ os_errmsg("...\n");
+ if (re_extmatch_in != NULL) {
+ int i;
+
+ os_errmsg(_("External submatches:\n"));
+ for (i = 0; i < NSUBEXP; i++) {
+ os_errmsg(" \"");
+ if (re_extmatch_in->matches[i] != NULL) {
+ os_errmsg((char *)re_extmatch_in->matches[i]);
+ }
+ os_errmsg("\"\n");
+ }
+ }
+ }
+#endif
+ next = regnext(scan);
+
+ op = OP(scan);
+ // Check for character class with NL added.
+ if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
+ && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
+ reg_nextline();
+ } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') {
+ ADVANCE_REGINPUT();
+ } else {
+ if (WITH_NL(op)) {
+ op -= ADD_NL;
+ }
+ c = utf_ptr2char((char *)rex.input);
+ switch (op) {
+ case BOL:
+ if (rex.input != rex.line) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case EOL:
+ if (c != NUL) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_BOF:
+ // We're not at the beginning of the file when below the first
+ // line where we started, not at the start of the line or we
+ // didn't start at the first line of the buffer.
+ if (rex.lnum != 0 || rex.input != rex.line
+ || (REG_MULTI && rex.reg_firstlnum > 1)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_EOF:
+ if (rex.lnum != rex.reg_maxline || c != NUL) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case CURSOR:
+ // Check if the buffer is in a window and compare the
+ // rex.reg_win->w_cursor position to the match position.
+ if (rex.reg_win == NULL
+ || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
+ || ((colnr_T)(rex.input - rex.line) !=
+ rex.reg_win->w_cursor.col)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_MARK:
+ // Compare the mark position to the match position.
+ {
+ int mark = OPERAND(scan)[0];
+ int cmp = OPERAND(scan)[1];
+ pos_T *pos;
+ size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
+ fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark);
+
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
+ if (fm == NULL // mark doesn't exist
+ || fm->mark.lnum <= 0) { // mark isn't set in reg_buf
+ status = RA_NOMATCH;
+ } else {
+ pos = &fm->mark;
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)strlen(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ if (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? (cmp == '<' || cmp == '>')
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? cmp != '>'
+ : cmp != '<'))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? cmp != '>'
+ : cmp != '<')) {
+ status = RA_NOMATCH;
+ }
+ }
+ }
+ break;
+
+ case RE_VISUAL:
+ if (!reg_match_visual()) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_LNUM:
+ assert(rex.lnum + rex.reg_firstlnum >= 0
+ && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
+ if (!REG_MULTI
+ || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_COL:
+ assert(rex.input - rex.line + 1 >= 0
+ && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX);
+ if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case RE_VCOL:
+ if (!re_num_cmp((unsigned)win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win,
+ rex.reg_firstlnum + rex.lnum,
+ (char *)rex.line,
+ (colnr_T)(rex.input - rex.line)) + 1,
+ scan)) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case BOW: // \<word; rex.input points to w
+ if (c == NUL) { // Can't match at end of line
+ status = RA_NOMATCH;
+ } else {
+ // Get class of current and previous char (if it exists).
+ const int this_class =
+ mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ if (this_class <= 1) {
+ status = RA_NOMATCH; // Not on a word at all.
+ } else if (reg_prev_class() == this_class) {
+ status = RA_NOMATCH; // Previous char is in same word.
+ }
+ }
+ break;
+
+ case EOW: // word\>; rex.input points after d
+ if (rex.input == rex.line) { // Can't match at start of line
+ status = RA_NOMATCH;
+ } else {
+ int this_class, prev_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ prev_class = reg_prev_class();
+ if (this_class == prev_class
+ || prev_class == 0 || prev_class == 1) {
+ status = RA_NOMATCH;
+ }
+ }
+ break; // Matched with EOW
+
+ case ANY:
+ // ANY does not match new lines.
+ if (c == NUL) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case IDENT:
+ if (!vim_isIDc(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SIDENT:
+ if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case KWORD:
+ if (!vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SKWORD:
+ if (ascii_isdigit(*rex.input)
+ || !vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case FNAME:
+ if (!vim_isfilec(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SFNAME:
+ if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case PRINT:
+ if (!vim_isprintc(utf_ptr2char((char *)rex.input))) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case SPRINT:
+ if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char((char *)rex.input))) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case WHITE:
+ if (!ascii_iswhite(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NWHITE:
+ if (c == NUL || ascii_iswhite(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case DIGIT:
+ if (!ri_digit(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NDIGIT:
+ if (c == NUL || ri_digit(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case HEX:
+ if (!ri_hex(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NHEX:
+ if (c == NUL || ri_hex(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case OCTAL:
+ if (!ri_octal(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NOCTAL:
+ if (c == NUL || ri_octal(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case WORD:
+ if (!ri_word(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NWORD:
+ if (c == NUL || ri_word(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case HEAD:
+ if (!ri_head(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NHEAD:
+ if (c == NUL || ri_head(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case ALPHA:
+ if (!ri_alpha(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NALPHA:
+ if (c == NUL || ri_alpha(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case LOWER:
+ if (!ri_lower(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NLOWER:
+ if (c == NUL || ri_lower(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case UPPER:
+ if (!ri_upper(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case NUPPER:
+ if (c == NUL || ri_upper(c)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case EXACTLY: {
+ int len;
+ uint8_t *opnd;
+
+ opnd = OPERAND(scan);
+ // Inline the first byte, for speed.
+ if (*opnd != *rex.input
+ && (!rex.reg_ic)) {
+ status = RA_NOMATCH;
+ } else if (*opnd == NUL) {
+ // match empty string always works; happens when "~" is
+ // empty.
+ } else {
+ if (opnd[1] == NUL && !rex.reg_ic) {
+ len = 1; // matched a single byte above
+ } else {
+ // Need to match first byte again for multi-byte.
+ len = (int)strlen((char *)opnd);
+ if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ }
+ // Check for following composing character, unless %C
+ // follows (skips over all composing chars).
+ if (status != RA_NOMATCH
+ && utf_composinglike((char *)rex.input, (char *)rex.input + len)
+ && !rex.reg_icombine
+ && OP(next) != RE_COMPOSING) {
+ // raaron: This code makes a composing character get
+ // ignored, which is the correct behavior (sometimes)
+ // for voweled Hebrew texts.
+ status = RA_NOMATCH;
+ }
+ if (status != RA_NOMATCH) {
+ rex.input += len;
+ }
+ }
+ }
+ break;
+
+ case ANYOF:
+ case ANYBUT:
+ if (c == NUL) {
+ status = RA_NOMATCH;
+ } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) {
+ status = RA_NOMATCH;
+ } else {
+ ADVANCE_REGINPUT();
+ }
+ break;
+
+ case MULTIBYTECODE: {
+ int i, len;
+
+ const uint8_t *opnd = OPERAND(scan);
+ // Safety check (just in case 'encoding' was changed since
+ // compiling the program).
+ if ((len = utfc_ptr2len((char *)opnd)) < 2) {
+ status = RA_NOMATCH;
+ break;
+ }
+ const int opndc = utf_ptr2char((char *)opnd);
+ if (utf_iscomposing(opndc)) {
+ // When only a composing char is given match at any
+ // position where that composing char appears.
+ status = RA_NOMATCH;
+ for (i = 0; rex.input[i] != NUL;
+ i += utf_ptr2len((char *)rex.input + i)) {
+ const int inpc = utf_ptr2char((char *)rex.input + i);
+ if (!utf_iscomposing(inpc)) {
+ if (i > 0) {
+ break;
+ }
+ } else if (opndc == inpc) {
+ // Include all following composing chars.
+ len = i + utfc_ptr2len((char *)rex.input + i);
+ status = RA_MATCH;
+ break;
+ }
+ }
+ } else {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != rex.input[i]) {
+ status = RA_NOMATCH;
+ break;
+ }
+ }
+ }
+ rex.input += len;
+ }
+ break;
+
+ case RE_COMPOSING:
+ // Skip composing characters.
+ while (utf_iscomposing(utf_ptr2char((char *)rex.input))) {
+ MB_CPTR_ADV(rex.input);
+ }
+ break;
+
+ case NOTHING:
+ break;
+
+ case BACK: {
+ int i;
+
+ // When we run into BACK we need to check if we don't keep
+ // looping without matching any input. The second and later
+ // times a BACK is encountered it fails if the input is still
+ // at the same position as the previous time.
+ // The positions are stored in "backpos" and found by the
+ // current value of "scan", the position in the RE program.
+ backpos_T *bp = (backpos_T *)backpos.ga_data;
+ for (i = 0; i < backpos.ga_len; i++) {
+ if (bp[i].bp_scan == scan) {
+ break;
+ }
+ }
+ if (i == backpos.ga_len) {
+ backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos);
+ p->bp_scan = scan;
+ } else if (reg_save_equal(&bp[i].bp_pos)) {
+ // Still at same position as last time, fail.
+ status = RA_NOMATCH;
+ }
+
+ assert(status != RA_FAIL);
+ if (status != RA_NOMATCH) {
+ reg_save(&bp[i].bp_pos, &backpos);
+ }
+ }
+ break;
+
+ case MOPEN + 0: // Match start: \zs
+ case MOPEN + 1: // \(
+ case MOPEN + 2:
+ case MOPEN + 3:
+ case MOPEN + 4:
+ case MOPEN + 5:
+ case MOPEN + 6:
+ case MOPEN + 7:
+ case MOPEN + 8:
+ case MOPEN + 9:
+ no = op - MOPEN;
+ cleanup_subexpr();
+ rp = regstack_push(RS_MOPEN, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &rex.reg_startpos[no],
+ &rex.reg_startp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case NOPEN: // \%(
+ case NCLOSE: // \) after \%(
+ if (regstack_push(RS_NOPEN, scan) == NULL) {
+ status = RA_FAIL;
+ }
+ // We simply continue and handle the result when done.
+ break;
+
+ case ZOPEN + 1:
+ case ZOPEN + 2:
+ case ZOPEN + 3:
+ case ZOPEN + 4:
+ case ZOPEN + 5:
+ case ZOPEN + 6:
+ case ZOPEN + 7:
+ case ZOPEN + 8:
+ case ZOPEN + 9:
+ no = op - ZOPEN;
+ cleanup_zsubexpr();
+ rp = regstack_push(RS_ZOPEN, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &reg_startzpos[no],
+ &reg_startzp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case MCLOSE + 0: // Match end: \ze
+ case MCLOSE + 1: // \)
+ case MCLOSE + 2:
+ case MCLOSE + 3:
+ case MCLOSE + 4:
+ case MCLOSE + 5:
+ case MCLOSE + 6:
+ case MCLOSE + 7:
+ case MCLOSE + 8:
+ case MCLOSE + 9:
+ no = op - MCLOSE;
+ cleanup_subexpr();
+ rp = regstack_push(RS_MCLOSE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case ZCLOSE + 1: // \) after \z(
+ case ZCLOSE + 2:
+ case ZCLOSE + 3:
+ case ZCLOSE + 4:
+ case ZCLOSE + 5:
+ case ZCLOSE + 6:
+ case ZCLOSE + 7:
+ case ZCLOSE + 8:
+ case ZCLOSE + 9:
+ no = op - ZCLOSE;
+ cleanup_zsubexpr();
+ rp = regstack_push(RS_ZCLOSE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ save_se(&rp->rs_un.sesave, &reg_endzpos[no],
+ &reg_endzp[no]);
+ // We simply continue and handle the result when done.
+ }
+ break;
+
+ case BACKREF + 1:
+ case BACKREF + 2:
+ case BACKREF + 3:
+ case BACKREF + 4:
+ case BACKREF + 5:
+ case BACKREF + 6:
+ case BACKREF + 7:
+ case BACKREF + 8:
+ case BACKREF + 9: {
+ int len;
+
+ no = op - BACKREF;
+ cleanup_subexpr();
+ if (!REG_MULTI) { // Single-line regexp
+ if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) {
+ // Backref was not set: Match an empty string.
+ len = 0;
+ } else {
+ // Compare current input with back-ref in the same line.
+ len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
+ if (cstrncmp((char *)rex.reg_startp[no], (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ }
+ } else { // Multi-line regexp
+ if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) {
+ // Backref was not set: Match an empty string.
+ len = 0;
+ } else {
+ if (rex.reg_startpos[no].lnum == rex.lnum
+ && rex.reg_endpos[no].lnum == rex.lnum) {
+ // Compare back-ref within the current line.
+ len = rex.reg_endpos[no].col - rex.reg_startpos[no].col;
+ if (cstrncmp((char *)rex.line + rex.reg_startpos[no].col,
+ (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ }
+ } else {
+ // Messy situation: Need to compare between two lines.
+ int r = match_with_backref(rex.reg_startpos[no].lnum,
+ rex.reg_startpos[no].col,
+ rex.reg_endpos[no].lnum,
+ rex.reg_endpos[no].col,
+ &len);
+ if (r != RA_MATCH) {
+ status = r;
+ }
+ }
+ }
+ }
+
+ // Matched the backref, skip over it.
+ rex.input += len;
+ }
+ break;
+
+ case ZREF + 1:
+ case ZREF + 2:
+ case ZREF + 3:
+ case ZREF + 4:
+ case ZREF + 5:
+ case ZREF + 6:
+ case ZREF + 7:
+ case ZREF + 8:
+ case ZREF + 9:
+ cleanup_zsubexpr();
+ no = op - ZREF;
+ if (re_extmatch_in != NULL
+ && re_extmatch_in->matches[no] != NULL) {
+ int len = (int)strlen((char *)re_extmatch_in->matches[no]);
+ if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ } else {
+ rex.input += len;
+ }
+ } else {
+ // Backref was not set: Match an empty string.
+ }
+ break;
+
+ case BRANCH:
+ if (OP(next) != BRANCH) { // No choice.
+ next = OPERAND(scan); // Avoid recursion.
+ } else {
+ rp = regstack_push(RS_BRANCH, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ status = RA_BREAK; // rest is below
+ }
+ }
+ break;
+
+ case BRACE_LIMITS:
+ if (OP(next) == BRACE_SIMPLE) {
+ bl_minval = OPERAND_MIN(scan);
+ bl_maxval = OPERAND_MAX(scan);
+ } else if (OP(next) >= BRACE_COMPLEX
+ && OP(next) < BRACE_COMPLEX + 10) {
+ no = OP(next) - BRACE_COMPLEX;
+ brace_min[no] = OPERAND_MIN(scan);
+ brace_max[no] = OPERAND_MAX(scan);
+ brace_count[no] = 0;
+ } else {
+ internal_error("BRACE_LIMITS");
+ status = RA_FAIL;
+ }
+ break;
+
+ case BRACE_COMPLEX + 0:
+ case BRACE_COMPLEX + 1:
+ case BRACE_COMPLEX + 2:
+ case BRACE_COMPLEX + 3:
+ case BRACE_COMPLEX + 4:
+ case BRACE_COMPLEX + 5:
+ case BRACE_COMPLEX + 6:
+ case BRACE_COMPLEX + 7:
+ case BRACE_COMPLEX + 8:
+ case BRACE_COMPLEX + 9:
+ no = op - BRACE_COMPLEX;
+ brace_count[no]++;
+
+ // If not matched enough times yet, try one more
+ if (brace_count[no] <= (brace_min[no] <= brace_max[no]
+ ? brace_min[no] : brace_max[no])) {
+ rp = regstack_push(RS_BRCPLX_MORE, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ break;
+ }
+
+ // If matched enough times, may try matching some more
+ if (brace_min[no] <= brace_max[no]) {
+ // Range is the normal way around, use longest match
+ if (brace_count[no] <= brace_max[no]) {
+ rp = regstack_push(RS_BRCPLX_LONG, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)no;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ }
+ } else {
+ // Range is backwards, use shortest match first
+ if (brace_count[no] <= brace_min[no]) {
+ rp = regstack_push(RS_BRCPLX_SHORT, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ reg_save(&rp->rs_un.regsave, &backpos);
+ // We continue and handle the result when done.
+ }
+ }
+ }
+ break;
+
+ case BRACE_SIMPLE:
+ case STAR:
+ case PLUS: {
+ regstar_T rst;
+
+ // Lookahead to avoid useless match attempts when we know
+ // what character comes next.
+ if (OP(next) == EXACTLY) {
+ rst.nextb = *OPERAND(next);
+ if (rex.reg_ic) {
+ if (mb_isupper(rst.nextb)) {
+ rst.nextb_ic = mb_tolower(rst.nextb);
+ } else {
+ rst.nextb_ic = mb_toupper(rst.nextb);
+ }
+ } else {
+ rst.nextb_ic = rst.nextb;
+ }
+ } else {
+ rst.nextb = NUL;
+ rst.nextb_ic = NUL;
+ }
+ if (op != BRACE_SIMPLE) {
+ rst.minval = (op == STAR) ? 0 : 1;
+ rst.maxval = MAX_LIMIT;
+ } else {
+ rst.minval = bl_minval;
+ rst.maxval = bl_maxval;
+ }
+
+ // When maxval > minval, try matching as much as possible, up
+ // to maxval. When maxval < minval, try matching at least the
+ // minimal number (since the range is backwards, that's also
+ // maxval!).
+ rst.count = regrepeat(OPERAND(scan), rst.maxval);
+ if (got_int) {
+ status = RA_FAIL;
+ break;
+ }
+ if (rst.minval <= rst.maxval
+ ? rst.count >= rst.minval : rst.count >= rst.maxval) {
+ // It could match. Prepare for trying to match what
+ // follows. The code is below. Parameters are stored in
+ // a regstar_T on the regstack.
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ status = RA_FAIL;
+ } else {
+ ga_grow(&regstack, sizeof(regstar_T));
+ regstack.ga_len += (int)sizeof(regstar_T);
+ rp = regstack_push(rst.minval <= rst.maxval ? RS_STAR_LONG : RS_STAR_SHORT, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ *(((regstar_T *)rp) - 1) = rst;
+ status = RA_BREAK; // skip the restore bits
+ }
+ }
+ } else {
+ status = RA_NOMATCH;
+ }
+ }
+ break;
+
+ case NOMATCH:
+ case MATCH:
+ case SUBPAT:
+ rp = regstack_push(RS_NOMATCH, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ rp->rs_no = (int16_t)op;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ next = OPERAND(scan);
+ // We continue and handle the result when done.
+ }
+ break;
+
+ case BEHIND:
+ case NOBEHIND:
+ // Need a bit of room to store extra positions.
+ if ((int64_t)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ status = RA_FAIL;
+ } else {
+ ga_grow(&regstack, sizeof(regbehind_T));
+ regstack.ga_len += (int)sizeof(regbehind_T);
+ rp = regstack_push(RS_BEHIND1, scan);
+ if (rp == NULL) {
+ status = RA_FAIL;
+ } else {
+ // Need to save the subexpr to be able to restore them
+ // when there is a match but we don't use it.
+ save_subexpr(((regbehind_T *)rp) - 1);
+
+ rp->rs_no = (int16_t)op;
+ reg_save(&rp->rs_un.regsave, &backpos);
+ // First try if what follows matches. If it does then we
+ // check the behind match by looping.
+ }
+ }
+ break;
+
+ case BHPOS:
+ if (REG_MULTI) {
+ if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line)
+ || behind_pos.rs_u.pos.lnum != rex.lnum) {
+ status = RA_NOMATCH;
+ }
+ } else if (behind_pos.rs_u.ptr != rex.input) {
+ status = RA_NOMATCH;
+ }
+ break;
+
+ case NEWL:
+ if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline
+ || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) {
+ status = RA_NOMATCH;
+ } else if (rex.reg_line_lbr) {
+ ADVANCE_REGINPUT();
+ } else {
+ reg_nextline();
+ }
+ break;
+
+ case END:
+ status = RA_MATCH; // Success!
+ break;
+
+ default:
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Illegal op code %d\n", op);
+#endif
+ status = RA_FAIL;
+ break;
+ }
+ }
+
+ // If we can't continue sequentially, break the inner loop.
+ if (status != RA_CONT) {
+ break;
+ }
+
+ // Continue in inner loop, advance to next item.
+ scan = next;
+ } // end of inner loop
+
+ // If there is something on the regstack execute the code for the state.
+ // If the state is popped then loop and use the older state.
+ while (!GA_EMPTY(&regstack) && status != RA_FAIL) {
+ rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
+ switch (rp->rs_state) {
+ case RS_NOPEN:
+ // Result is passed on as-is, simply pop the state.
+ regstack_pop(&scan);
+ break;
+
+ case RS_MOPEN:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no],
+ &rex.reg_startp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_ZOPEN:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &reg_startzpos[rp->rs_no],
+ &reg_startzp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_MCLOSE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no],
+ &rex.reg_endp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_ZCLOSE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ restore_se(&rp->rs_un.sesave, &reg_endzpos[rp->rs_no],
+ &reg_endzp[rp->rs_no]);
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_BRANCH:
+ if (status == RA_MATCH) {
+ // this branch matched, use it
+ regstack_pop(&scan);
+ } else {
+ if (status != RA_BREAK) {
+ // After a non-matching branch: try next one.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = rp->rs_scan;
+ }
+ if (scan == NULL || OP(scan) != BRANCH) {
+ // no more branches, didn't find a match
+ status = RA_NOMATCH;
+ regstack_pop(&scan);
+ } else {
+ // Prepare to try a branch.
+ rp->rs_scan = regnext(scan);
+ reg_save(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(scan);
+ }
+ }
+ break;
+
+ case RS_BRCPLX_MORE:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ brace_count[rp->rs_no]--; // decrement match count
+ }
+ regstack_pop(&scan);
+ break;
+
+ case RS_BRCPLX_LONG:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ // There was no match, but we did find enough matches.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ brace_count[rp->rs_no]--;
+ // continue with the items after "\{}"
+ status = RA_CONT;
+ }
+ regstack_pop(&scan);
+ if (status == RA_CONT) {
+ scan = regnext(scan);
+ }
+ break;
+
+ case RS_BRCPLX_SHORT:
+ // Pop the state. Restore pointers when there is no match.
+ if (status == RA_NOMATCH) {
+ // There was no match, try to match one more item.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+ regstack_pop(&scan);
+ if (status == RA_NOMATCH) {
+ scan = OPERAND(scan);
+ status = RA_CONT;
+ }
+ break;
+
+ case RS_NOMATCH:
+ // Pop the state. If the operand matches for NOMATCH or
+ // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup,
+ // except for SUBPAT, and continue with the next item.
+ if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) {
+ status = RA_NOMATCH;
+ } else {
+ status = RA_CONT;
+ if (rp->rs_no != SUBPAT) { // zero-width
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+ }
+ regstack_pop(&scan);
+ if (status == RA_CONT) {
+ scan = regnext(scan);
+ }
+ break;
+
+ case RS_BEHIND1:
+ if (status == RA_NOMATCH) {
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ } else {
+ // The stuff after BEHIND/NOBEHIND matches. Now try if
+ // the behind part does (not) match before the current
+ // position in the input. This must be done at every
+ // position in the input and checking if the match ends at
+ // the current position.
+
+ // save the position after the found match for next
+ reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos);
+
+ // Start looking for a match with operand at the current
+ // position. Go back one character until we find the
+ // result, hitting the start of the line or the previous
+ // line (for multi-line matching).
+ // Set behind_pos to where the match should end, BHPOS
+ // will match it. Save the current value.
+ (((regbehind_T *)rp) - 1)->save_behind = behind_pos;
+ behind_pos = rp->rs_un.regsave;
+
+ rp->rs_state = RS_BEHIND2;
+
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(rp->rs_scan) + 4;
+ }
+ break;
+
+ case RS_BEHIND2:
+ // Looping for BEHIND / NOBEHIND match.
+ if (status == RA_MATCH && reg_save_equal(&behind_pos)) {
+ // found a match that ends where "next" started
+ behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
+ if (rp->rs_no == BEHIND) {
+ reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
+ &backpos);
+ } else {
+ // But we didn't want a match. Need to restore the
+ // subexpr, because what follows matched, so they have
+ // been set.
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ } else {
+ int64_t limit;
+
+ // No match or a match that doesn't end where we want it: Go
+ // back one character. May go to previous line once.
+ no = OK;
+ limit = OPERAND_MIN(rp->rs_scan);
+ if (REG_MULTI) {
+ if (limit > 0
+ && ((rp->rs_un.regsave.rs_u.pos.lnum
+ < behind_pos.rs_u.pos.lnum
+ ? (colnr_T)strlen((char *)rex.line)
+ : behind_pos.rs_u.pos.col)
+ - rp->rs_un.regsave.rs_u.pos.col >= limit)) {
+ no = FAIL;
+ } else if (rp->rs_un.regsave.rs_u.pos.col == 0) {
+ if (rp->rs_un.regsave.rs_u.pos.lnum
+ < behind_pos.rs_u.pos.lnum
+ || reg_getline(--rp->rs_un.regsave.rs_u.pos.lnum)
+ == NULL) {
+ no = FAIL;
+ } else {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ rp->rs_un.regsave.rs_u.pos.col =
+ (colnr_T)strlen((char *)rex.line);
+ }
+ } else {
+ const uint8_t *const line =
+ (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum);
+
+ rp->rs_un.regsave.rs_u.pos.col -=
+ utf_head_off((char *)line,
+ (char *)line + rp->rs_un.regsave.rs_u.pos.col - 1)
+ + 1;
+ }
+ } else {
+ if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
+ no = FAIL;
+ } else {
+ MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
+ if (limit > 0
+ && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) {
+ no = FAIL;
+ }
+ }
+ }
+ if (no == OK) {
+ // Advanced, prepare for finding match again.
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ scan = OPERAND(rp->rs_scan) + 4;
+ if (status == RA_MATCH) {
+ // We did match, so subexpr may have been changed,
+ // need to restore them for the next try.
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ } else {
+ // Can't advance. For NOBEHIND that's a match.
+ behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
+ if (rp->rs_no == NOBEHIND) {
+ reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
+ &backpos);
+ status = RA_MATCH;
+ } else {
+ // We do want a proper match. Need to restore the
+ // subexpr if we had a match, because they may have
+ // been set.
+ if (status == RA_MATCH) {
+ status = RA_NOMATCH;
+ restore_subexpr(((regbehind_T *)rp) - 1);
+ }
+ }
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regbehind_T);
+ }
+ }
+ break;
+
+ case RS_STAR_LONG:
+ case RS_STAR_SHORT: {
+ regstar_T *rst = ((regstar_T *)rp) - 1;
+
+ if (status == RA_MATCH) {
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regstar_T);
+ break;
+ }
+
+ // Tried once already, restore input pointers.
+ if (status != RA_BREAK) {
+ reg_restore(&rp->rs_un.regsave, &backpos);
+ }
+
+ // Repeat until we found a position where it could match.
+ while (true) {
+ if (status != RA_BREAK) {
+ // Tried first position already, advance.
+ if (rp->rs_state == RS_STAR_LONG) {
+ // Trying for longest match, but couldn't or
+ // didn't match -- back up one char.
+ if (--rst->count < rst->minval) {
+ break;
+ }
+ if (rex.input == rex.line) {
+ // backup to last char of previous line
+ if (rex.lnum == 0) {
+ status = RA_NOMATCH;
+ break;
+ }
+ rex.lnum--;
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ // Just in case regrepeat() didn't count right.
+ if (rex.line == NULL) {
+ break;
+ }
+ rex.input = rex.line + strlen((char *)rex.line);
+ reg_breakcheck();
+ } else {
+ MB_PTR_BACK(rex.line, rex.input);
+ }
+ } else {
+ // Range is backwards, use shortest match first.
+ // Careful: maxval and minval are exchanged!
+ // Couldn't or didn't match: try advancing one
+ // char.
+ if (rst->count == rst->minval
+ || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) {
+ break;
+ }
+ rst->count++;
+ }
+ if (got_int) {
+ break;
+ }
+ } else {
+ status = RA_NOMATCH;
+ }
+
+ // If it could match, try it.
+ if (rst->nextb == NUL || *rex.input == rst->nextb
+ || *rex.input == rst->nextb_ic) {
+ reg_save(&rp->rs_un.regsave, &backpos);
+ scan = regnext(rp->rs_scan);
+ status = RA_CONT;
+ break;
+ }
+ }
+ if (status != RA_CONT) {
+ // Failed.
+ regstack_pop(&scan);
+ regstack.ga_len -= (int)sizeof(regstar_T);
+ status = RA_NOMATCH;
+ }
+ }
+ break;
+ }
+
+ // If we want to continue the inner loop or didn't pop a state
+ // continue matching loop
+ if (status == RA_CONT || rp == (regitem_T *)
+ ((char *)regstack.ga_data + regstack.ga_len) - 1) {
+ break;
+ }
+ }
+
+ // May need to continue with the inner loop, starting at "scan".
+ if (status == RA_CONT) {
+ continue;
+ }
+
+ // If the regstack is empty or something failed we are done.
+ if (GA_EMPTY(&regstack) || status == RA_FAIL) {
+ if (scan == NULL) {
+ // We get here only if there's trouble -- normally "case END" is
+ // the terminating point.
+ iemsg(_(e_re_corr));
+#ifdef REGEXP_DEBUG
+ printf("Premature EOL\n");
+#endif
+ }
+ return status == RA_MATCH;
+ }
+ } // End of loop until the regstack is empty.
+
+ // NOTREACHED
+}
+
+/// Try match of "prog" with at rex.line["col"].
+///
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return 0 for failure, or number of lines contained in the match.
+static int regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
+{
+ rex.input = rex.line + col;
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessaey.
+ rex.need_clear_zsubexpr = (prog->reghasz == REX_SET);
+
+ if (regmatch(prog->program + 1, tm, timed_out) == 0) {
+ return 0;
+ }
+
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ if (rex.reg_startpos[0].lnum < 0) {
+ rex.reg_startpos[0].lnum = 0;
+ rex.reg_startpos[0].col = col;
+ }
+ if (rex.reg_endpos[0].lnum < 0) {
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
+ } else {
+ // Use line number of "\ze".
+ rex.lnum = rex.reg_endpos[0].lnum;
+ }
+ } else {
+ if (rex.reg_startp[0] == NULL) {
+ rex.reg_startp[0] = rex.line + col;
+ }
+ if (rex.reg_endp[0] == NULL) {
+ rex.reg_endp[0] = rex.input;
+ }
+ }
+ // Package any found \z(...\) matches for export. Default is none.
+ unref_extmatch(re_extmatch_out);
+ re_extmatch_out = NULL;
+
+ if (prog->reghasz == REX_SET) {
+ int i;
+
+ cleanup_zsubexpr();
+ re_extmatch_out = make_extmatch();
+ for (i = 0; i < NSUBEXP; i++) {
+ if (REG_MULTI) {
+ // Only accept single line matches.
+ if (reg_startzpos[i].lnum >= 0
+ && reg_endzpos[i].lnum == reg_startzpos[i].lnum
+ && reg_endzpos[i].col >= reg_startzpos[i].col) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave(reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col,
+ (size_t)(reg_endzpos[i].col - reg_startzpos[i].col));
+ }
+ } else {
+ if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i]));
+ }
+ }
+ }
+ }
+ return 1 + rex.lnum;
+}
+
+/// Match a regexp against a string ("line" points to the string) or multiple
+/// lines (if "line" is NULL, use reg_getline()).
+///
+/// @param startcol column to start looking for match
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return 0 for failure, or number of lines contained in the match.
+static int bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
+{
+ bt_regprog_T *prog;
+ uint8_t *s;
+ colnr_T col = startcol;
+ int retval = 0;
+
+ // Create "regstack" and "backpos" if they are not allocated yet.
+ // We allocate *_INITIAL amount of bytes first and then set the grow size
+ // to much bigger value to avoid many malloc calls in case of deep regular
+ // expressions.
+ if (regstack.ga_data == NULL) {
+ // Use an item size of 1 byte, since we push different things
+ // onto the regstack.
+ ga_init(&regstack, 1, REGSTACK_INITIAL);
+ ga_grow(&regstack, REGSTACK_INITIAL);
+ ga_set_growsize(&regstack, REGSTACK_INITIAL * 8);
+ }
+
+ if (backpos.ga_data == NULL) {
+ ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL);
+ ga_grow(&backpos, BACKPOS_INITIAL);
+ ga_set_growsize(&backpos, BACKPOS_INITIAL * 8);
+ }
+
+ if (REG_MULTI) {
+ prog = (bt_regprog_T *)rex.reg_mmatch->regprog;
+ line = (uint8_t *)reg_getline(0);
+ rex.reg_startpos = rex.reg_mmatch->startpos;
+ rex.reg_endpos = rex.reg_mmatch->endpos;
+ } else {
+ prog = (bt_regprog_T *)rex.reg_match->regprog;
+ rex.reg_startp = (uint8_t **)rex.reg_match->startp;
+ rex.reg_endp = (uint8_t **)rex.reg_match->endp;
+ }
+
+ // Be paranoid...
+ if (prog == NULL || line == NULL) {
+ iemsg(_(e_null));
+ goto theend;
+ }
+
+ // Check validity of program.
+ if (prog_magic_wrong()) {
+ goto theend;
+ }
+
+ // If the start column is past the maximum column: no need to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ goto theend;
+ }
+
+ // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
+ if (prog->regflags & RF_ICASE) {
+ rex.reg_ic = true;
+ } else if (prog->regflags & RF_NOICASE) {
+ rex.reg_ic = false;
+ }
+
+ // If pattern contains "\Z" overrule value of rex.reg_icombine
+ if (prog->regflags & RF_ICOMBINE) {
+ rex.reg_icombine = true;
+ }
+
+ // If there is a "must appear" string, look for it.
+ if (prog->regmust != NULL) {
+ int c = utf_ptr2char((char *)prog->regmust);
+ s = line + col;
+
+ // This is used very often, esp. for ":global". Use two versions of
+ // the loop to avoid overhead of conditions.
+ if (!rex.reg_ic) {
+ while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) {
+ if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
+ break; // Found it.
+ }
+ MB_PTR_ADV(s);
+ }
+ } else {
+ while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) {
+ if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
+ break; // Found it.
+ }
+ MB_PTR_ADV(s);
+ }
+ }
+ if (s == NULL) { // Not present.
+ goto theend;
+ }
+ }
+
+ rex.line = line;
+ rex.lnum = 0;
+ reg_toolong = false;
+
+ // Simplest case: Anchored match need be tried only once.
+ if (prog->reganch) {
+ int c = utf_ptr2char((char *)rex.line + col);
+ if (prog->regstart == NUL
+ || prog->regstart == c
+ || (rex.reg_ic
+ && (utf_fold(prog->regstart) == utf_fold(c)
+ || (c < 255 && prog->regstart < 255
+ && mb_tolower(prog->regstart) == mb_tolower(c))))) {
+ retval = regtry(prog, col, tm, timed_out);
+ } else {
+ retval = 0;
+ }
+ } else {
+ int tm_count = 0;
+ // Messy cases: unanchored match.
+ while (!got_int) {
+ if (prog->regstart != NUL) {
+ // Skip until the char we know it must start with.
+ s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart);
+ if (s == NULL) {
+ retval = 0;
+ break;
+ }
+ col = (int)(s - rex.line);
+ }
+
+ // Check for maximum column to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ retval = 0;
+ break;
+ }
+
+ retval = regtry(prog, col, tm, timed_out);
+ if (retval > 0) {
+ break;
+ }
+
+ // if not currently on the first line, get it again
+ if (rex.lnum != 0) {
+ rex.lnum = 0;
+ rex.line = (uint8_t *)reg_getline(0);
+ }
+ if (rex.line[col] == NUL) {
+ break;
+ }
+ col += utfc_ptr2len((char *)rex.line + col);
+ // Check for timeout once in a twenty times to avoid overhead.
+ if (tm != NULL && ++tm_count == 20) {
+ tm_count = 0;
+ if (profile_passed_limit(*tm)) {
+ if (timed_out != NULL) {
+ *timed_out = true;
+ }
+ break;
+ }
+ }
+ }
+ }
+
+theend:
+ // Free "reg_tofree" when it's a bit big.
+ // Free regstack and backpos if they are bigger than their initial size.
+ if (reg_tofreelen > 400) {
+ XFREE_CLEAR(reg_tofree);
+ }
+ if (regstack.ga_maxlen > REGSTACK_INITIAL) {
+ ga_clear(&regstack);
+ }
+ if (backpos.ga_maxlen > BACKPOS_INITIAL) {
+ ga_clear(&backpos);
+ }
+
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs
+ // and \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_mmatch->rmm_matchcol = col;
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_match->rm_matchcol = col;
+ }
+ }
+
+ return retval;
+}
+
+/// Match a regexp against a string.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
+///
+/// @param line string to match against
+/// @param col column to start looking for match
+///
+/// @return 0 for failure, number of lines contained in the match otherwise.
+static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
+{
+ rex.reg_match = rmp;
+ rex.reg_mmatch = NULL;
+ rex.reg_maxline = 0;
+ rex.reg_line_lbr = line_lbr;
+ rex.reg_buf = curbuf;
+ rex.reg_win = NULL;
+ rex.reg_ic = rmp->rm_ic;
+ rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
+ rex.reg_maxcol = 0;
+
+ int64_t r = bt_regexec_both(line, col, NULL, NULL);
+ assert(r <= INT_MAX);
+ return (int)r;
+}
+
+/// Matches a regexp against multiple lines.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+///
+/// @param win Window in which to search or NULL
+/// @param buf Buffer in which to search
+/// @param lnum Number of line to start looking for match
+/// @param col Column to start looking for match
+/// @param tm Timeout limit or NULL
+///
+/// @return zero if there is no match and number of lines contained in the match
+/// otherwise.
+static int bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
+{
+ init_regexec_multi(rmp, win, buf, lnum);
+ return bt_regexec_both(NULL, col, tm, timed_out);
+}
+
+// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
+static int re_num_cmp(uint32_t val, const uint8_t *scan)
+{
+ uint32_t n = (uint32_t)OPERAND_MIN(scan);
+
+ if (OPERAND_CMP(scan) == '>') {
+ return val > n;
+ }
+ if (OPERAND_CMP(scan) == '<') {
+ return val < n;
+ }
+ return val == n;
+}
+
+#ifdef BT_REGEXP_DUMP
+
+// regdump - dump a regexp onto stdout in vaguely comprehensible form
+static void regdump(uint8_t *pattern, bt_regprog_T *r)
+{
+ uint8_t *s;
+ int op = EXACTLY; // Arbitrary non-END op.
+ uint8_t *next;
+ uint8_t *end = NULL;
+ FILE *f;
+
+# ifdef BT_REGEXP_LOG
+ f = fopen("bt_regexp_log.log", "a");
+# else
+ f = stdout;
+# endif
+ if (f == NULL) {
+ return;
+ }
+ fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n",
+ pattern);
+
+ s = r->program + 1;
+ // Loop until we find the END that isn't before a referred next (an END
+ // can also appear in a NOMATCH operand).
+ while (op != END || s <= end) {
+ op = OP(s);
+ fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what.
+ next = regnext(s);
+ if (next == NULL) { // Next ptr.
+ fprintf(f, "(0)");
+ } else {
+ fprintf(f, "(%d)", (int)((s - r->program) + (next - s)));
+ }
+ if (end < next) {
+ end = next;
+ }
+ if (op == BRACE_LIMITS) {
+ // Two ints
+ fprintf(f, " minval %" PRId64 ", maxval %" PRId64,
+ (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s));
+ s += 8;
+ } else if (op == BEHIND || op == NOBEHIND) {
+ // one int
+ fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
+ s += 4;
+ } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) {
+ // one int plus comparator
+ fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
+ s += 5;
+ }
+ s += 3;
+ if (op == ANYOF || op == ANYOF + ADD_NL
+ || op == ANYBUT || op == ANYBUT + ADD_NL
+ || op == EXACTLY) {
+ // Literal string, where present.
+ fprintf(f, "\nxxxxxxxxx\n");
+ while (*s != NUL) {
+ fprintf(f, "%c", *s++);
+ }
+ fprintf(f, "\nxxxxxxxxx\n");
+ s++;
+ }
+ fprintf(f, "\r\n");
+ }
+
+ // Header fields of interest.
+ if (r->regstart != NUL) {
+ fprintf(f, "start `%s' 0x%x; ", r->regstart < 256
+ ? (char *)transchar(r->regstart)
+ : "multibyte", r->regstart);
+ }
+ if (r->reganch) {
+ fprintf(f, "anchored; ");
+ }
+ if (r->regmust != NULL) {
+ fprintf(f, "must have \"%s\"", r->regmust);
+ }
+ fprintf(f, "\r\n");
+
+# ifdef BT_REGEXP_LOG
+ fclose(f);
+# endif
+}
+#endif // BT_REGEXP_DUMP
+
+#ifdef REGEXP_DEBUG
+
+// regprop - printable representation of opcode
+static uint8_t *regprop(uint8_t *op)
+{
+ char *p;
+ static char buf[50];
+
+ STRCPY(buf, ":");
+
+ switch ((int)OP(op)) {
+ case BOL:
+ p = "BOL";
+ break;
+ case EOL:
+ p = "EOL";
+ break;
+ case RE_BOF:
+ p = "BOF";
+ break;
+ case RE_EOF:
+ p = "EOF";
+ break;
+ case CURSOR:
+ p = "CURSOR";
+ break;
+ case RE_VISUAL:
+ p = "RE_VISUAL";
+ break;
+ case RE_LNUM:
+ p = "RE_LNUM";
+ break;
+ case RE_MARK:
+ p = "RE_MARK";
+ break;
+ case RE_COL:
+ p = "RE_COL";
+ break;
+ case RE_VCOL:
+ p = "RE_VCOL";
+ break;
+ case BOW:
+ p = "BOW";
+ break;
+ case EOW:
+ p = "EOW";
+ break;
+ case ANY:
+ p = "ANY";
+ break;
+ case ANY + ADD_NL:
+ p = "ANY+NL";
+ break;
+ case ANYOF:
+ p = "ANYOF";
+ break;
+ case ANYOF + ADD_NL:
+ p = "ANYOF+NL";
+ break;
+ case ANYBUT:
+ p = "ANYBUT";
+ break;
+ case ANYBUT + ADD_NL:
+ p = "ANYBUT+NL";
+ break;
+ case IDENT:
+ p = "IDENT";
+ break;
+ case IDENT + ADD_NL:
+ p = "IDENT+NL";
+ break;
+ case SIDENT:
+ p = "SIDENT";
+ break;
+ case SIDENT + ADD_NL:
+ p = "SIDENT+NL";
+ break;
+ case KWORD:
+ p = "KWORD";
+ break;
+ case KWORD + ADD_NL:
+ p = "KWORD+NL";
+ break;
+ case SKWORD:
+ p = "SKWORD";
+ break;
+ case SKWORD + ADD_NL:
+ p = "SKWORD+NL";
+ break;
+ case FNAME:
+ p = "FNAME";
+ break;
+ case FNAME + ADD_NL:
+ p = "FNAME+NL";
+ break;
+ case SFNAME:
+ p = "SFNAME";
+ break;
+ case SFNAME + ADD_NL:
+ p = "SFNAME+NL";
+ break;
+ case PRINT:
+ p = "PRINT";
+ break;
+ case PRINT + ADD_NL:
+ p = "PRINT+NL";
+ break;
+ case SPRINT:
+ p = "SPRINT";
+ break;
+ case SPRINT + ADD_NL:
+ p = "SPRINT+NL";
+ break;
+ case WHITE:
+ p = "WHITE";
+ break;
+ case WHITE + ADD_NL:
+ p = "WHITE+NL";
+ break;
+ case NWHITE:
+ p = "NWHITE";
+ break;
+ case NWHITE + ADD_NL:
+ p = "NWHITE+NL";
+ break;
+ case DIGIT:
+ p = "DIGIT";
+ break;
+ case DIGIT + ADD_NL:
+ p = "DIGIT+NL";
+ break;
+ case NDIGIT:
+ p = "NDIGIT";
+ break;
+ case NDIGIT + ADD_NL:
+ p = "NDIGIT+NL";
+ break;
+ case HEX:
+ p = "HEX";
+ break;
+ case HEX + ADD_NL:
+ p = "HEX+NL";
+ break;
+ case NHEX:
+ p = "NHEX";
+ break;
+ case NHEX + ADD_NL:
+ p = "NHEX+NL";
+ break;
+ case OCTAL:
+ p = "OCTAL";
+ break;
+ case OCTAL + ADD_NL:
+ p = "OCTAL+NL";
+ break;
+ case NOCTAL:
+ p = "NOCTAL";
+ break;
+ case NOCTAL + ADD_NL:
+ p = "NOCTAL+NL";
+ break;
+ case WORD:
+ p = "WORD";
+ break;
+ case WORD + ADD_NL:
+ p = "WORD+NL";
+ break;
+ case NWORD:
+ p = "NWORD";
+ break;
+ case NWORD + ADD_NL:
+ p = "NWORD+NL";
+ break;
+ case HEAD:
+ p = "HEAD";
+ break;
+ case HEAD + ADD_NL:
+ p = "HEAD+NL";
+ break;
+ case NHEAD:
+ p = "NHEAD";
+ break;
+ case NHEAD + ADD_NL:
+ p = "NHEAD+NL";
+ break;
+ case ALPHA:
+ p = "ALPHA";
+ break;
+ case ALPHA + ADD_NL:
+ p = "ALPHA+NL";
+ break;
+ case NALPHA:
+ p = "NALPHA";
+ break;
+ case NALPHA + ADD_NL:
+ p = "NALPHA+NL";
+ break;
+ case LOWER:
+ p = "LOWER";
+ break;
+ case LOWER + ADD_NL:
+ p = "LOWER+NL";
+ break;
+ case NLOWER:
+ p = "NLOWER";
+ break;
+ case NLOWER + ADD_NL:
+ p = "NLOWER+NL";
+ break;
+ case UPPER:
+ p = "UPPER";
+ break;
+ case UPPER + ADD_NL:
+ p = "UPPER+NL";
+ break;
+ case NUPPER:
+ p = "NUPPER";
+ break;
+ case NUPPER + ADD_NL:
+ p = "NUPPER+NL";
+ break;
+ case BRANCH:
+ p = "BRANCH";
+ break;
+ case EXACTLY:
+ p = "EXACTLY";
+ break;
+ case NOTHING:
+ p = "NOTHING";
+ break;
+ case BACK:
+ p = "BACK";
+ break;
+ case END:
+ p = "END";
+ break;
+ case MOPEN + 0:
+ p = "MATCH START";
+ break;
+ case MOPEN + 1:
+ case MOPEN + 2:
+ case MOPEN + 3:
+ case MOPEN + 4:
+ case MOPEN + 5:
+ case MOPEN + 6:
+ case MOPEN + 7:
+ case MOPEN + 8:
+ case MOPEN + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MOPEN%d", OP(op) - MOPEN);
+ p = NULL;
+ break;
+ case MCLOSE + 0:
+ p = "MATCH END";
+ break;
+ case MCLOSE + 1:
+ case MCLOSE + 2:
+ case MCLOSE + 3:
+ case MCLOSE + 4:
+ case MCLOSE + 5:
+ case MCLOSE + 6:
+ case MCLOSE + 7:
+ case MCLOSE + 8:
+ case MCLOSE + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MCLOSE%d", OP(op) - MCLOSE);
+ p = NULL;
+ break;
+ case BACKREF + 1:
+ case BACKREF + 2:
+ case BACKREF + 3:
+ case BACKREF + 4:
+ case BACKREF + 5:
+ case BACKREF + 6:
+ case BACKREF + 7:
+ case BACKREF + 8:
+ case BACKREF + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BACKREF%d", OP(op) - BACKREF);
+ p = NULL;
+ break;
+ case NOPEN:
+ p = "NOPEN";
+ break;
+ case NCLOSE:
+ p = "NCLOSE";
+ break;
+ case ZOPEN + 1:
+ case ZOPEN + 2:
+ case ZOPEN + 3:
+ case ZOPEN + 4:
+ case ZOPEN + 5:
+ case ZOPEN + 6:
+ case ZOPEN + 7:
+ case ZOPEN + 8:
+ case ZOPEN + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZOPEN%d", OP(op) - ZOPEN);
+ p = NULL;
+ break;
+ case ZCLOSE + 1:
+ case ZCLOSE + 2:
+ case ZCLOSE + 3:
+ case ZCLOSE + 4:
+ case ZCLOSE + 5:
+ case ZCLOSE + 6:
+ case ZCLOSE + 7:
+ case ZCLOSE + 8:
+ case ZCLOSE + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZCLOSE%d", OP(op) - ZCLOSE);
+ p = NULL;
+ break;
+ case ZREF + 1:
+ case ZREF + 2:
+ case ZREF + 3:
+ case ZREF + 4:
+ case ZREF + 5:
+ case ZREF + 6:
+ case ZREF + 7:
+ case ZREF + 8:
+ case ZREF + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZREF%d", OP(op) - ZREF);
+ p = NULL;
+ break;
+ case STAR:
+ p = "STAR";
+ break;
+ case PLUS:
+ p = "PLUS";
+ break;
+ case NOMATCH:
+ p = "NOMATCH";
+ break;
+ case MATCH:
+ p = "MATCH";
+ break;
+ case BEHIND:
+ p = "BEHIND";
+ break;
+ case NOBEHIND:
+ p = "NOBEHIND";
+ break;
+ case SUBPAT:
+ p = "SUBPAT";
+ break;
+ case BRACE_LIMITS:
+ p = "BRACE_LIMITS";
+ break;
+ case BRACE_SIMPLE:
+ p = "BRACE_SIMPLE";
+ break;
+ case BRACE_COMPLEX + 0:
+ case BRACE_COMPLEX + 1:
+ case BRACE_COMPLEX + 2:
+ case BRACE_COMPLEX + 3:
+ case BRACE_COMPLEX + 4:
+ case BRACE_COMPLEX + 5:
+ case BRACE_COMPLEX + 6:
+ case BRACE_COMPLEX + 7:
+ case BRACE_COMPLEX + 8:
+ case BRACE_COMPLEX + 9:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BRACE_COMPLEX%d",
+ OP(op) - BRACE_COMPLEX);
+ p = NULL;
+ break;
+ case MULTIBYTECODE:
+ p = "MULTIBYTECODE";
+ break;
+ case NEWL:
+ p = "NEWL";
+ break;
+ default:
+ snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "corrupt %d", OP(op));
+ p = NULL;
+ break;
+ }
+ if (p != NULL) {
+ STRCAT(buf, p);
+ }
+ return (uint8_t *)buf;
+}
+#endif // REGEXP_DEBUG
+
+// }}}1
+
+// regexp_nfa.c {{{1
+// NFA regular expression implementation.
+
+// Logging of NFA engine.
+//
+// The NFA engine can write four log files:
+// - Error log: Contains NFA engine's fatal errors.
+// - Dump log: Contains compiled NFA state machine's information.
+// - Run log: Contains information of matching procedure.
+// - Debug log: Contains detailed information of matching procedure. Can be
+// disabled by undefining NFA_REGEXP_DEBUG_LOG.
+// The first one can also be used without debug mode.
+// The last three are enabled when compiled as debug mode and individually
+// disabled by commenting them out.
+// The log files can get quite big!
+// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in
+// regexp.c
+#ifdef REGEXP_DEBUG
+# define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log"
+# define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log"
+# define NFA_REGEXP_RUN_LOG "nfa_regexp_run.log"
+# define NFA_REGEXP_DEBUG_LOG "nfa_regexp_debug.log"
+#endif
+
+// Added to NFA_ANY - NFA_NUPPER_IC to include a NL.
+#define NFA_ADD_NL 31
+
+enum {
+ NFA_SPLIT = -1024,
+ NFA_MATCH,
+ NFA_EMPTY, // matches 0-length
+
+ NFA_START_COLL, // [abc] start
+ NFA_END_COLL, // [abc] end
+ NFA_START_NEG_COLL, // [^abc] start
+ NFA_END_NEG_COLL, // [^abc] end (postfix only)
+ NFA_RANGE, // range of the two previous items
+ // (postfix only)
+ NFA_RANGE_MIN, // low end of a range
+ NFA_RANGE_MAX, // high end of a range
+
+ NFA_CONCAT, // concatenate two previous items (postfix
+ // only)
+ NFA_OR, // \| (postfix only)
+ NFA_STAR, // greedy * (postfix only)
+ NFA_STAR_NONGREEDY, // non-greedy * (postfix only)
+ NFA_QUEST, // greedy \? (postfix only)
+ NFA_QUEST_NONGREEDY, // non-greedy \? (postfix only)
+
+ NFA_BOL, // ^ Begin line
+ NFA_EOL, // $ End line
+ NFA_BOW, // \< Begin word
+ NFA_EOW, // \> End word
+ NFA_BOF, // \%^ Begin file
+ NFA_EOF, // \%$ End file
+ NFA_NEWL,
+ NFA_ZSTART, // Used for \zs
+ NFA_ZEND, // Used for \ze
+ NFA_NOPEN, // Start of subexpression marked with \%(
+ NFA_NCLOSE, // End of subexpr. marked with \%( ... \)
+ NFA_START_INVISIBLE,
+ NFA_START_INVISIBLE_FIRST,
+ NFA_START_INVISIBLE_NEG,
+ NFA_START_INVISIBLE_NEG_FIRST,
+ NFA_START_INVISIBLE_BEFORE,
+ NFA_START_INVISIBLE_BEFORE_FIRST,
+ NFA_START_INVISIBLE_BEFORE_NEG,
+ NFA_START_INVISIBLE_BEFORE_NEG_FIRST,
+ NFA_START_PATTERN,
+ NFA_END_INVISIBLE,
+ NFA_END_INVISIBLE_NEG,
+ NFA_END_PATTERN,
+ NFA_COMPOSING, // Next nodes in NFA are part of the
+ // composing multibyte char
+ NFA_END_COMPOSING, // End of a composing char in the NFA
+ NFA_ANY_COMPOSING, // \%C: Any composing characters.
+ NFA_OPT_CHARS, // \%[abc]
+
+ // The following are used only in the postfix form, not in the NFA
+ NFA_PREV_ATOM_NO_WIDTH, // Used for \@=
+ NFA_PREV_ATOM_NO_WIDTH_NEG, // Used for \@!
+ NFA_PREV_ATOM_JUST_BEFORE, // Used for \@<=
+ NFA_PREV_ATOM_JUST_BEFORE_NEG, // Used for \@<!
+ NFA_PREV_ATOM_LIKE_PATTERN, // Used for \@>
+
+ NFA_BACKREF1, // \1
+ NFA_BACKREF2, // \2
+ NFA_BACKREF3, // \3
+ NFA_BACKREF4, // \4
+ NFA_BACKREF5, // \5
+ NFA_BACKREF6, // \6
+ NFA_BACKREF7, // \7
+ NFA_BACKREF8, // \8
+ NFA_BACKREF9, // \9
+ NFA_ZREF1, // \z1
+ NFA_ZREF2, // \z2
+ NFA_ZREF3, // \z3
+ NFA_ZREF4, // \z4
+ NFA_ZREF5, // \z5
+ NFA_ZREF6, // \z6
+ NFA_ZREF7, // \z7
+ NFA_ZREF8, // \z8
+ NFA_ZREF9, // \z9
+ NFA_SKIP, // Skip characters
+
+ NFA_MOPEN,
+ NFA_MOPEN1,
+ NFA_MOPEN2,
+ NFA_MOPEN3,
+ NFA_MOPEN4,
+ NFA_MOPEN5,
+ NFA_MOPEN6,
+ NFA_MOPEN7,
+ NFA_MOPEN8,
+ NFA_MOPEN9,
+
+ NFA_MCLOSE,
+ NFA_MCLOSE1,
+ NFA_MCLOSE2,
+ NFA_MCLOSE3,
+ NFA_MCLOSE4,
+ NFA_MCLOSE5,
+ NFA_MCLOSE6,
+ NFA_MCLOSE7,
+ NFA_MCLOSE8,
+ NFA_MCLOSE9,
+
+ NFA_ZOPEN,
+ NFA_ZOPEN1,
+ NFA_ZOPEN2,
+ NFA_ZOPEN3,
+ NFA_ZOPEN4,
+ NFA_ZOPEN5,
+ NFA_ZOPEN6,
+ NFA_ZOPEN7,
+ NFA_ZOPEN8,
+ NFA_ZOPEN9,
+
+ NFA_ZCLOSE,
+ NFA_ZCLOSE1,
+ NFA_ZCLOSE2,
+ NFA_ZCLOSE3,
+ NFA_ZCLOSE4,
+ NFA_ZCLOSE5,
+ NFA_ZCLOSE6,
+ NFA_ZCLOSE7,
+ NFA_ZCLOSE8,
+ NFA_ZCLOSE9,
+
+ // NFA_FIRST_NL
+ NFA_ANY, // Match any one character.
+ NFA_IDENT, // Match identifier char
+ NFA_SIDENT, // Match identifier char but no digit
+ NFA_KWORD, // Match keyword char
+ NFA_SKWORD, // Match word char but no digit
+ NFA_FNAME, // Match file name char
+ NFA_SFNAME, // Match file name char but no digit
+ NFA_PRINT, // Match printable char
+ NFA_SPRINT, // Match printable char but no digit
+ NFA_WHITE, // Match whitespace char
+ NFA_NWHITE, // Match non-whitespace char
+ NFA_DIGIT, // Match digit char
+ NFA_NDIGIT, // Match non-digit char
+ NFA_HEX, // Match hex char
+ NFA_NHEX, // Match non-hex char
+ NFA_OCTAL, // Match octal char
+ NFA_NOCTAL, // Match non-octal char
+ NFA_WORD, // Match word char
+ NFA_NWORD, // Match non-word char
+ NFA_HEAD, // Match head char
+ NFA_NHEAD, // Match non-head char
+ NFA_ALPHA, // Match alpha char
+ NFA_NALPHA, // Match non-alpha char
+ NFA_LOWER, // Match lowercase char
+ NFA_NLOWER, // Match non-lowercase char
+ NFA_UPPER, // Match uppercase char
+ NFA_NUPPER, // Match non-uppercase char
+ NFA_LOWER_IC, // Match [a-z]
+ NFA_NLOWER_IC, // Match [^a-z]
+ NFA_UPPER_IC, // Match [A-Z]
+ NFA_NUPPER_IC, // Match [^A-Z]
+
+ NFA_FIRST_NL = NFA_ANY + NFA_ADD_NL,
+ NFA_LAST_NL = NFA_NUPPER_IC + NFA_ADD_NL,
+
+ NFA_CURSOR, // Match cursor pos
+ NFA_LNUM, // Match line number
+ NFA_LNUM_GT, // Match > line number
+ NFA_LNUM_LT, // Match < line number
+ NFA_COL, // Match cursor column
+ NFA_COL_GT, // Match > cursor column
+ NFA_COL_LT, // Match < cursor column
+ NFA_VCOL, // Match cursor virtual column
+ NFA_VCOL_GT, // Match > cursor virtual column
+ NFA_VCOL_LT, // Match < cursor virtual column
+ NFA_MARK, // Match mark
+ NFA_MARK_GT, // Match > mark
+ NFA_MARK_LT, // Match < mark
+ NFA_VISUAL, // Match Visual area
+
+ // Character classes [:alnum:] etc
+ NFA_CLASS_ALNUM,
+ NFA_CLASS_ALPHA,
+ NFA_CLASS_BLANK,
+ NFA_CLASS_CNTRL,
+ NFA_CLASS_DIGIT,
+ NFA_CLASS_GRAPH,
+ NFA_CLASS_LOWER,
+ NFA_CLASS_PRINT,
+ NFA_CLASS_PUNCT,
+ NFA_CLASS_SPACE,
+ NFA_CLASS_UPPER,
+ NFA_CLASS_XDIGIT,
+ NFA_CLASS_TAB,
+ NFA_CLASS_RETURN,
+ NFA_CLASS_BACKSPACE,
+ NFA_CLASS_ESCAPE,
+ NFA_CLASS_IDENT,
+ NFA_CLASS_KEYWORD,
+ NFA_CLASS_FNAME,
+};
+
+// Keep in sync with classchars.
+static int nfa_classcodes[] = {
+ NFA_ANY, NFA_IDENT, NFA_SIDENT, NFA_KWORD, NFA_SKWORD,
+ NFA_FNAME, NFA_SFNAME, NFA_PRINT, NFA_SPRINT,
+ NFA_WHITE, NFA_NWHITE, NFA_DIGIT, NFA_NDIGIT,
+ NFA_HEX, NFA_NHEX, NFA_OCTAL, NFA_NOCTAL,
+ NFA_WORD, NFA_NWORD, NFA_HEAD, NFA_NHEAD,
+ NFA_ALPHA, NFA_NALPHA, NFA_LOWER, NFA_NLOWER,
+ NFA_UPPER, NFA_NUPPER
+};
+
+static const char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely");
+static const char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
+static const char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64);
+static const char e_value_too_large[] = N_("E951: \\% value too large");
+
+// Variables only used in nfa_regcomp() and descendants.
+static int nfa_re_flags; ///< re_flags passed to nfa_regcomp().
+static int *post_start; ///< holds the postfix form of r.e.
+static int *post_end;
+static int *post_ptr;
+
+// Set when the pattern should use the NFA engine.
+// E.g. [[:upper:]] only allows 8bit characters for BT engine,
+// while NFA engine handles multibyte characters correctly.
+static bool wants_nfa;
+
+static int nstate; ///< Number of states in the NFA. Also used when executing.
+static int istate; ///< Index in the state vector, used in alloc_state()
+
+// If not NULL match must end at this position
+static save_se_T *nfa_endp = NULL;
+
+// 0 for first call to nfa_regmatch(), 1 for recursive call.
+static int nfa_ll_index = 0;
+
+// Helper functions used when doing re2post() ... regatom() parsing
+#define EMIT(c) \
+ do { \
+ if (post_ptr >= post_end) { \
+ realloc_post_list(); \
+ } \
+ *post_ptr++ = c; \
+ } while (0)
+
+/// Initialize internal variables before NFA compilation.
+///
+/// @param re_flags @see vim_regcomp()
+static void nfa_regcomp_start(uint8_t *expr, int re_flags)
+{
+ size_t postfix_size;
+ size_t nstate_max;
+
+ nstate = 0;
+ istate = 0;
+ // A reasonable estimation for maximum size
+ nstate_max = (strlen((char *)expr) + 1) * 25;
+
+ // Some items blow up in size, such as [A-z]. Add more space for that.
+ // When it is still not enough realloc_post_list() will be used.
+ nstate_max += 1000;
+
+ // Size for postfix representation of expr.
+ postfix_size = sizeof(int) * nstate_max;
+
+ post_start = (int *)xmalloc(postfix_size);
+ post_ptr = post_start;
+ post_end = post_start + nstate_max;
+ wants_nfa = false;
+ rex.nfa_has_zend = false;
+ rex.nfa_has_backref = false;
+
+ // shared with BT engine
+ regcomp_start(expr, re_flags);
+}
+
+// Figure out if the NFA state list starts with an anchor, must match at start
+// of the line.
+static int nfa_get_reganch(nfa_state_T *start, int depth)
+{
+ nfa_state_T *p = start;
+
+ if (depth > 4) {
+ return 0;
+ }
+
+ while (p != NULL) {
+ switch (p->c) {
+ case NFA_BOL:
+ case NFA_BOF:
+ return 1; // yes!
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_CURSOR:
+ case NFA_VISUAL:
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ p = p->out;
+ break;
+
+ case NFA_SPLIT:
+ return nfa_get_reganch(p->out, depth + 1)
+ && nfa_get_reganch(p->out1, depth + 1);
+
+ default:
+ return 0; // noooo
+ }
+ }
+ return 0;
+}
+
+// Figure out if the NFA state list starts with a character which must match
+// at start of the match.
+static int nfa_get_regstart(nfa_state_T *start, int depth)
+{
+ nfa_state_T *p = start;
+
+ if (depth > 4) {
+ return 0;
+ }
+
+ while (p != NULL) {
+ switch (p->c) {
+ // all kinds of zero-width matches
+ case NFA_BOL:
+ case NFA_BOF:
+ case NFA_BOW:
+ case NFA_EOW:
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_CURSOR:
+ case NFA_VISUAL:
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ p = p->out;
+ break;
+
+ case NFA_SPLIT: {
+ int c1 = nfa_get_regstart(p->out, depth + 1);
+ int c2 = nfa_get_regstart(p->out1, depth + 1);
+
+ if (c1 == c2) {
+ return c1; // yes!
+ }
+ return 0;
+ }
+
+ default:
+ if (p->c > 0) {
+ return p->c; // yes!
+ }
+ return 0;
+ }
+ }
+ return 0;
+}
+
+// Figure out if the NFA state list contains just literal text and nothing
+// else. If so return a string in allocated memory with what must match after
+// regstart. Otherwise return NULL.
+static uint8_t *nfa_get_match_text(nfa_state_T *start)
+{
+ nfa_state_T *p = start;
+ int len = 0;
+ uint8_t *ret;
+ uint8_t *s;
+
+ if (p->c != NFA_MOPEN) {
+ return NULL; // just in case
+ }
+ p = p->out;
+ while (p->c > 0) {
+ len += utf_char2len(p->c);
+ p = p->out;
+ }
+ if (p->c != NFA_MCLOSE || p->out->c != NFA_MATCH) {
+ return NULL;
+ }
+
+ ret = xmalloc((size_t)len);
+ p = start->out->out; // skip first char, it goes into regstart
+ s = ret;
+ while (p->c > 0) {
+ s += utf_char2bytes(p->c, (char *)s);
+ p = p->out;
+ }
+ *s = NUL;
+
+ return ret;
+}
+
+// Allocate more space for post_start. Called when
+// running above the estimated number of states.
+static void realloc_post_list(void)
+{
+ // For weird patterns the number of states can be very high. Increasing by
+ // 50% seems a reasonable compromise between memory use and speed.
+ const size_t new_max = (size_t)(post_end - post_start) * 3 / 2;
+ int *new_start = xrealloc(post_start, new_max * sizeof(int));
+ post_ptr = new_start + (post_ptr - post_start);
+ post_end = new_start + new_max;
+ post_start = new_start;
+}
+
+// Search between "start" and "end" and try to recognize a
+// character class in expanded form. For example [0-9].
+// On success, return the id the character class to be emitted.
+// On failure, return 0 (=FAIL)
+// Start points to the first char of the range, while end should point
+// to the closing brace.
+// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
+// need to be interpreted as [a-zA-Z].
+static int nfa_recognize_char_class(uint8_t *start, const uint8_t *end, int extra_newl)
+{
+#define CLASS_not 0x80
+#define CLASS_af 0x40
+#define CLASS_AF 0x20
+#define CLASS_az 0x10
+#define CLASS_AZ 0x08
+#define CLASS_o7 0x04
+#define CLASS_o9 0x02
+#define CLASS_underscore 0x01
+
+ uint8_t *p;
+ int config = 0;
+
+ bool newl = extra_newl == true;
+
+ if (*end != ']') {
+ return FAIL;
+ }
+ p = start;
+ if (*p == '^') {
+ config |= CLASS_not;
+ p++;
+ }
+
+ while (p < end) {
+ if (p + 2 < end && *(p + 1) == '-') {
+ switch (*p) {
+ case '0':
+ if (*(p + 2) == '9') {
+ config |= CLASS_o9;
+ break;
+ } else if (*(p + 2) == '7') {
+ config |= CLASS_o7;
+ break;
+ }
+ return FAIL;
+ case 'a':
+ if (*(p + 2) == 'z') {
+ config |= CLASS_az;
+ break;
+ } else if (*(p + 2) == 'f') {
+ config |= CLASS_af;
+ break;
+ }
+ return FAIL;
+ case 'A':
+ if (*(p + 2) == 'Z') {
+ config |= CLASS_AZ;
+ break;
+ } else if (*(p + 2) == 'F') {
+ config |= CLASS_AF;
+ break;
+ }
+ return FAIL;
+ default:
+ return FAIL;
+ }
+ p += 3;
+ } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
+ newl = true;
+ p += 2;
+ } else if (*p == '_') {
+ config |= CLASS_underscore;
+ p++;
+ } else if (*p == '\n') {
+ newl = true;
+ p++;
+ } else {
+ return FAIL;
+ }
+ } // while (p < end)
+
+ if (p != end) {
+ return FAIL;
+ }
+
+ if (newl == true) {
+ extra_newl = NFA_ADD_NL;
+ }
+
+ switch (config) {
+ case CLASS_o9:
+ return extra_newl + NFA_DIGIT;
+ case CLASS_not | CLASS_o9:
+ return extra_newl + NFA_NDIGIT;
+ case CLASS_af | CLASS_AF | CLASS_o9:
+ return extra_newl + NFA_HEX;
+ case CLASS_not | CLASS_af | CLASS_AF | CLASS_o9:
+ return extra_newl + NFA_NHEX;
+ case CLASS_o7:
+ return extra_newl + NFA_OCTAL;
+ case CLASS_not | CLASS_o7:
+ return extra_newl + NFA_NOCTAL;
+ case CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
+ return extra_newl + NFA_WORD;
+ case CLASS_not | CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
+ return extra_newl + NFA_NWORD;
+ case CLASS_az | CLASS_AZ | CLASS_underscore:
+ return extra_newl + NFA_HEAD;
+ case CLASS_not | CLASS_az | CLASS_AZ | CLASS_underscore:
+ return extra_newl + NFA_NHEAD;
+ case CLASS_az | CLASS_AZ:
+ return extra_newl + NFA_ALPHA;
+ case CLASS_not | CLASS_az | CLASS_AZ:
+ return extra_newl + NFA_NALPHA;
+ case CLASS_az:
+ return extra_newl + NFA_LOWER_IC;
+ case CLASS_not | CLASS_az:
+ return extra_newl + NFA_NLOWER_IC;
+ case CLASS_AZ:
+ return extra_newl + NFA_UPPER_IC;
+ case CLASS_not | CLASS_AZ:
+ return extra_newl + NFA_NUPPER_IC;
+ }
+ return FAIL;
+}
+
+// Produce the bytes for equivalence class "c".
+// Currently only handles latin1, latin9 and utf-8.
+// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
+// equivalent to 'a OR b OR c'
+//
+// NOTE! When changing this function, also update reg_equi_class()
+static void nfa_emit_equi_class(int c)
+{
+#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
+
+ {
+#define A_grave 0xc0
+#define A_acute 0xc1
+#define A_circumflex 0xc2
+#define A_virguilla 0xc3
+#define A_diaeresis 0xc4
+#define A_ring 0xc5
+#define C_cedilla 0xc7
+#define E_grave 0xc8
+#define E_acute 0xc9
+#define E_circumflex 0xca
+#define E_diaeresis 0xcb
+#define I_grave 0xcc
+#define I_acute 0xcd
+#define I_circumflex 0xce
+#define I_diaeresis 0xcf
+#define N_virguilla 0xd1
+#define O_grave 0xd2
+#define O_acute 0xd3
+#define O_circumflex 0xd4
+#define O_virguilla 0xd5
+#define O_diaeresis 0xd6
+#define O_slash 0xd8
+#define U_grave 0xd9
+#define U_acute 0xda
+#define U_circumflex 0xdb
+#define U_diaeresis 0xdc
+#define Y_acute 0xdd
+#define a_grave 0xe0
+#define a_acute 0xe1
+#define a_circumflex 0xe2
+#define a_virguilla 0xe3
+#define a_diaeresis 0xe4
+#define a_ring 0xe5
+#define c_cedilla 0xe7
+#define e_grave 0xe8
+#define e_acute 0xe9
+#define e_circumflex 0xea
+#define e_diaeresis 0xeb
+#define i_grave 0xec
+#define i_acute 0xed
+#define i_circumflex 0xee
+#define i_diaeresis 0xef
+#define n_virguilla 0xf1
+#define o_grave 0xf2
+#define o_acute 0xf3
+#define o_circumflex 0xf4
+#define o_virguilla 0xf5
+#define o_diaeresis 0xf6
+#define o_slash 0xf8
+#define u_grave 0xf9
+#define u_acute 0xfa
+#define u_circumflex 0xfb
+#define u_diaeresis 0xfc
+#define y_acute 0xfd
+#define y_diaeresis 0xff
+ switch (c) {
+ case 'A':
+ case A_grave:
+ case A_acute:
+ case A_circumflex:
+ case A_virguilla:
+ case A_diaeresis:
+ case A_ring:
+ case 0x100:
+ case 0x102:
+ case 0x104:
+ case 0x1cd:
+ case 0x1de:
+ case 0x1e0:
+ case 0x1fa:
+ case 0x200:
+ case 0x202:
+ case 0x226:
+ case 0x23a:
+ case 0x1e00:
+ case 0x1ea0:
+ case 0x1ea2:
+ case 0x1ea4:
+ case 0x1ea6:
+ case 0x1ea8:
+ case 0x1eaa:
+ case 0x1eac:
+ case 0x1eae:
+ case 0x1eb0:
+ case 0x1eb2:
+ case 0x1eb4:
+ case 0x1eb6:
+ EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) // NOLINT(whitespace/cast)
+ EMIT2(A_circumflex) EMIT2(A_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(A_diaeresis) EMIT2(A_ring) // NOLINT(whitespace/cast)
+ EMIT2(0x100) EMIT2(0x102) EMIT2(0x104)
+ EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0)
+ EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202)
+ EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00)
+ EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4)
+ EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa)
+ EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0)
+ EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4)
+ return;
+
+ case 'B':
+ case 0x181:
+ case 0x243:
+ case 0x1e02:
+ case 0x1e04:
+ case 0x1e06:
+ EMIT2('B')
+ EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02)
+ EMIT2(0x1e04) EMIT2(0x1e06)
+ return;
+
+ case 'C':
+ case C_cedilla:
+ case 0x106:
+ case 0x108:
+ case 0x10a:
+ case 0x10c:
+ case 0x187:
+ case 0x23b:
+ case 0x1e08:
+ case 0xa792:
+ EMIT2('C') EMIT2(C_cedilla)
+ EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a)
+ EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b)
+ EMIT2(0x1e08) EMIT2(0xa792)
+ return;
+
+ case 'D':
+ case 0x10e:
+ case 0x110:
+ case 0x18a:
+ case 0x1e0a:
+ case 0x1e0c:
+ case 0x1e0e:
+ case 0x1e10:
+ case 0x1e12:
+ EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a)
+ EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e)
+ EMIT2(0x1e10) EMIT2(0x1e12)
+ return;
+
+ case 'E':
+ case E_grave:
+ case E_acute:
+ case E_circumflex:
+ case E_diaeresis:
+ case 0x112:
+ case 0x114:
+ case 0x116:
+ case 0x118:
+ case 0x11a:
+ case 0x204:
+ case 0x206:
+ case 0x228:
+ case 0x246:
+ case 0x1e14:
+ case 0x1e16:
+ case 0x1e18:
+ case 0x1e1a:
+ case 0x1e1c:
+ case 0x1eb8:
+ case 0x1eba:
+ case 0x1ebc:
+ case 0x1ebe:
+ case 0x1ec0:
+ case 0x1ec2:
+ case 0x1ec4:
+ case 0x1ec6:
+ EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) // NOLINT(whitespace/cast)
+ EMIT2(E_circumflex) EMIT2(E_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x112) EMIT2(0x114) EMIT2(0x116)
+ EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204)
+ EMIT2(0x206) EMIT2(0x228) EMIT2(0x246)
+ EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18)
+ EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8)
+ EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe)
+ EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4)
+ EMIT2(0x1ec6)
+ return;
+
+ case 'F':
+ case 0x191:
+ case 0x1e1e:
+ case 0xa798:
+ EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798)
+ return;
+
+ case 'G':
+ case 0x11c:
+ case 0x11e:
+ case 0x120:
+ case 0x122:
+ case 0x193:
+ case 0x1e4:
+ case 0x1e6:
+ case 0x1f4:
+ case 0x1e20:
+ case 0xa7a0:
+ EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120)
+ EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4)
+ EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20)
+ EMIT2(0xa7a0)
+ return;
+
+ case 'H':
+ case 0x124:
+ case 0x126:
+ case 0x21e:
+ case 0x1e22:
+ case 0x1e24:
+ case 0x1e26:
+ case 0x1e28:
+ case 0x1e2a:
+ case 0x2c67:
+ EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e)
+ EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26)
+ EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67)
+ return;
+
+ case 'I':
+ case I_grave:
+ case I_acute:
+ case I_circumflex:
+ case I_diaeresis:
+ case 0x128:
+ case 0x12a:
+ case 0x12c:
+ case 0x12e:
+ case 0x130:
+ case 0x197:
+ case 0x1cf:
+ case 0x208:
+ case 0x20a:
+ case 0x1e2c:
+ case 0x1e2e:
+ case 0x1ec8:
+ case 0x1eca:
+ EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) // NOLINT(whitespace/cast)
+ EMIT2(I_circumflex) EMIT2(I_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c)
+ EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197)
+ EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a)
+ EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8)
+ EMIT2(0x1eca)
+ return;
+
+ case 'J':
+ case 0x134:
+ case 0x248:
+ EMIT2('J') EMIT2(0x134) EMIT2(0x248)
+ return;
+
+ case 'K':
+ case 0x136:
+ case 0x198:
+ case 0x1e8:
+ case 0x1e30:
+ case 0x1e32:
+ case 0x1e34:
+ case 0x2c69:
+ case 0xa740:
+ EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8)
+ EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34)
+ EMIT2(0x2c69) EMIT2(0xa740)
+ return;
+
+ case 'L':
+ case 0x139:
+ case 0x13b:
+ case 0x13d:
+ case 0x13f:
+ case 0x141:
+ case 0x23d:
+ case 0x1e36:
+ case 0x1e38:
+ case 0x1e3a:
+ case 0x1e3c:
+ case 0x2c60:
+ EMIT2('L') EMIT2(0x139) EMIT2(0x13b)
+ EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141)
+ EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38)
+ EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60)
+ return;
+
+ case 'M':
+ case 0x1e3e:
+ case 0x1e40:
+ case 0x1e42:
+ EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40)
+ EMIT2(0x1e42)
+ return;
+
+ case 'N':
+ case N_virguilla:
+ case 0x143:
+ case 0x145:
+ case 0x147:
+ case 0x1f8:
+ case 0x1e44:
+ case 0x1e46:
+ case 0x1e48:
+ case 0x1e4a:
+ case 0xa7a4:
+ EMIT2('N') EMIT2(N_virguilla)
+ EMIT2(0x143) EMIT2(0x145) EMIT2(0x147)
+ EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46)
+ EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4)
+ return;
+
+ case 'O':
+ case O_grave:
+ case O_acute:
+ case O_circumflex:
+ case O_virguilla:
+ case O_diaeresis:
+ case O_slash:
+ case 0x14c:
+ case 0x14e:
+ case 0x150:
+ case 0x19f:
+ case 0x1a0:
+ case 0x1d1:
+ case 0x1ea:
+ case 0x1ec:
+ case 0x1fe:
+ case 0x20c:
+ case 0x20e:
+ case 0x22a:
+ case 0x22c:
+ case 0x22e:
+ case 0x230:
+ case 0x1e4c:
+ case 0x1e4e:
+ case 0x1e50:
+ case 0x1e52:
+ case 0x1ecc:
+ case 0x1ece:
+ case 0x1ed0:
+ case 0x1ed2:
+ case 0x1ed4:
+ case 0x1ed6:
+ case 0x1ed8:
+ case 0x1eda:
+ case 0x1edc:
+ case 0x1ede:
+ case 0x1ee0:
+ case 0x1ee2:
+ EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) // NOLINT(whitespace/cast)
+ EMIT2(O_circumflex) EMIT2(O_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(O_diaeresis) EMIT2(O_slash) // NOLINT(whitespace/cast)
+ EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150)
+ EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1)
+ EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe)
+ EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a)
+ EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230)
+ EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50)
+ EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece)
+ EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4)
+ EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda)
+ EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0)
+ EMIT2(0x1ee2)
+ return;
+
+ case 'P':
+ case 0x1a4:
+ case 0x1e54:
+ case 0x1e56:
+ case 0x2c63:
+ EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56)
+ EMIT2(0x2c63)
+ return;
+
+ case 'Q':
+ case 0x24a:
+ EMIT2('Q') EMIT2(0x24a)
+ return;
+
+ case 'R':
+ case 0x154:
+ case 0x156:
+ case 0x158:
+ case 0x210:
+ case 0x212:
+ case 0x24c:
+ case 0x1e58:
+ case 0x1e5a:
+ case 0x1e5c:
+ case 0x1e5e:
+ case 0x2c64:
+ case 0xa7a6:
+ EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158)
+ EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58)
+ EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64)
+ EMIT2(0xa7a6)
+ return;
+
+ case 'S':
+ case 0x15a:
+ case 0x15c:
+ case 0x15e:
+ case 0x160:
+ case 0x218:
+ case 0x1e60:
+ case 0x1e62:
+ case 0x1e64:
+ case 0x1e66:
+ case 0x1e68:
+ case 0x2c7e:
+ case 0xa7a8:
+ EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e)
+ EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62)
+ EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e)
+ EMIT2(0xa7a8)
+ return;
+
+ case 'T':
+ case 0x162:
+ case 0x164:
+ case 0x166:
+ case 0x1ac:
+ case 0x1ae:
+ case 0x21a:
+ case 0x23e:
+ case 0x1e6a:
+ case 0x1e6c:
+ case 0x1e6e:
+ case 0x1e70:
+ EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166)
+ EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a)
+ EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70)
+ return;
+
+ case 'U':
+ case U_grave:
+ case U_acute:
+ case U_diaeresis:
+ case U_circumflex:
+ case 0x168:
+ case 0x16a:
+ case 0x16c:
+ case 0x16e:
+ case 0x170:
+ case 0x172:
+ case 0x1af:
+ case 0x1d3:
+ case 0x1d5:
+ case 0x1d7:
+ case 0x1d9:
+ case 0x1db:
+ case 0x214:
+ case 0x216:
+ case 0x244:
+ case 0x1e72:
+ case 0x1e74:
+ case 0x1e76:
+ case 0x1e78:
+ case 0x1e7a:
+ case 0x1ee4:
+ case 0x1ee6:
+ case 0x1ee8:
+ case 0x1eea:
+ case 0x1eec:
+ case 0x1eee:
+ case 0x1ef0:
+ EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) // NOLINT(whitespace/cast)
+ EMIT2(U_diaeresis) EMIT2(U_circumflex) // NOLINT(whitespace/cast)
+ EMIT2(0x168) EMIT2(0x16a)
+ EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170)
+ EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3)
+ EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9)
+ EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216)
+ EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74)
+ EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a)
+ EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8)
+ EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee)
+ EMIT2(0x1ef0)
+ return;
+
+ case 'V':
+ case 0x1b2:
+ case 0x1e7c:
+ case 0x1e7e:
+ EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e)
+ return;
+
+ case 'W':
+ case 0x174:
+ case 0x1e80:
+ case 0x1e82:
+ case 0x1e84:
+ case 0x1e86:
+ case 0x1e88:
+ EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82)
+ EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88)
+ return;
+
+ case 'X':
+ case 0x1e8a:
+ case 0x1e8c:
+ EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c)
+ return;
+
+ case 'Y':
+ case Y_acute:
+ case 0x176:
+ case 0x178:
+ case 0x1b3:
+ case 0x232:
+ case 0x24e:
+ case 0x1e8e:
+ case 0x1ef2:
+ case 0x1ef4:
+ case 0x1ef6:
+ case 0x1ef8:
+ EMIT2('Y') EMIT2(Y_acute)
+ EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3)
+ EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e)
+ EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6)
+ EMIT2(0x1ef8)
+ return;
+
+ case 'Z':
+ case 0x179:
+ case 0x17b:
+ case 0x17d:
+ case 0x1b5:
+ case 0x1e90:
+ case 0x1e92:
+ case 0x1e94:
+ case 0x2c6b:
+ EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d)
+ EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92)
+ EMIT2(0x1e94) EMIT2(0x2c6b)
+ return;
+
+ case 'a':
+ case a_grave:
+ case a_acute:
+ case a_circumflex:
+ case a_virguilla:
+ case a_diaeresis:
+ case a_ring:
+ case 0x101:
+ case 0x103:
+ case 0x105:
+ case 0x1ce:
+ case 0x1df:
+ case 0x1e1:
+ case 0x1fb:
+ case 0x201:
+ case 0x203:
+ case 0x227:
+ case 0x1d8f:
+ case 0x1e01:
+ case 0x1e9a:
+ case 0x1ea1:
+ case 0x1ea3:
+ case 0x1ea5:
+ case 0x1ea7:
+ case 0x1ea9:
+ case 0x1eab:
+ case 0x1ead:
+ case 0x1eaf:
+ case 0x1eb1:
+ case 0x1eb3:
+ case 0x1eb5:
+ case 0x1eb7:
+ case 0x2c65:
+ EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) // NOLINT(whitespace/cast)
+ EMIT2(a_circumflex) EMIT2(a_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(a_diaeresis) EMIT2(a_ring) // NOLINT(whitespace/cast)
+ EMIT2(0x101) EMIT2(0x103) EMIT2(0x105)
+ EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1)
+ EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203)
+ EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01)
+ EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3)
+ EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9)
+ EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf)
+ EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5)
+ EMIT2(0x1eb7) EMIT2(0x2c65)
+ return;
+
+ case 'b':
+ case 0x180:
+ case 0x253:
+ case 0x1d6c:
+ case 0x1d80:
+ case 0x1e03:
+ case 0x1e05:
+ case 0x1e07:
+ EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c)
+ EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07)
+ return;
+
+ case 'c':
+ case c_cedilla:
+ case 0x107:
+ case 0x109:
+ case 0x10b:
+ case 0x10d:
+ case 0x188:
+ case 0x23c:
+ case 0x1e09:
+ case 0xa793:
+ case 0xa794:
+ EMIT2('c') EMIT2(c_cedilla)
+ EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b)
+ EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c)
+ EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794)
+ return;
+
+ case 'd':
+ case 0x10f:
+ case 0x111:
+ case 0x257:
+ case 0x1d6d:
+ case 0x1d81:
+ case 0x1d91:
+ case 0x1e0b:
+ case 0x1e0d:
+ case 0x1e0f:
+ case 0x1e11:
+ case 0x1e13:
+ EMIT2('d') EMIT2(0x10f) EMIT2(0x111)
+ EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81)
+ EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d)
+ EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13)
+ return;
+
+ case 'e':
+ case e_grave:
+ case e_acute:
+ case e_circumflex:
+ case e_diaeresis:
+ case 0x113:
+ case 0x115:
+ case 0x117:
+ case 0x119:
+ case 0x11b:
+ case 0x205:
+ case 0x207:
+ case 0x229:
+ case 0x247:
+ case 0x1d92:
+ case 0x1e15:
+ case 0x1e17:
+ case 0x1e19:
+ case 0x1e1b:
+ case 0x1e1d:
+ case 0x1eb9:
+ case 0x1ebb:
+ case 0x1ebd:
+ case 0x1ebf:
+ case 0x1ec1:
+ case 0x1ec3:
+ case 0x1ec5:
+ case 0x1ec7:
+ EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) // NOLINT(whitespace/cast)
+ EMIT2(e_circumflex) EMIT2(e_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x113) EMIT2(0x115)
+ EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b)
+ EMIT2(0x205) EMIT2(0x207) EMIT2(0x229)
+ EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15)
+ EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b)
+ EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb)
+ EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1)
+ EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7)
+ return;
+
+ case 'f':
+ case 0x192:
+ case 0x1d6e:
+ case 0x1d82:
+ case 0x1e1f:
+ case 0xa799:
+ EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82)
+ EMIT2(0x1e1f) EMIT2(0xa799)
+ return;
+
+ case 'g':
+ case 0x11d:
+ case 0x11f:
+ case 0x121:
+ case 0x123:
+ case 0x1e5:
+ case 0x1e7:
+ case 0x1f5:
+ case 0x260:
+ case 0x1d83:
+ case 0x1e21:
+ case 0xa7a1:
+ EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121)
+ EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7)
+ EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83)
+ EMIT2(0x1e21) EMIT2(0xa7a1)
+ return;
+
+ case 'h':
+ case 0x125:
+ case 0x127:
+ case 0x21f:
+ case 0x1e23:
+ case 0x1e25:
+ case 0x1e27:
+ case 0x1e29:
+ case 0x1e2b:
+ case 0x1e96:
+ case 0x2c68:
+ case 0xa795:
+ EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f)
+ EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27)
+ EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96)
+ EMIT2(0x2c68) EMIT2(0xa795)
+ return;
+
+ case 'i':
+ case i_grave:
+ case i_acute:
+ case i_circumflex:
+ case i_diaeresis:
+ case 0x129:
+ case 0x12b:
+ case 0x12d:
+ case 0x12f:
+ case 0x1d0:
+ case 0x209:
+ case 0x20b:
+ case 0x268:
+ case 0x1d96:
+ case 0x1e2d:
+ case 0x1e2f:
+ case 0x1ec9:
+ case 0x1ecb:
+ EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) // NOLINT(whitespace/cast)
+ EMIT2(i_circumflex) EMIT2(i_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d)
+ EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209)
+ EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96)
+ EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9)
+ EMIT2(0x1ecb) EMIT2(0x1ecb)
+ return;
+
+ case 'j':
+ case 0x135:
+ case 0x1f0:
+ case 0x249:
+ EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249)
+ return;
+
+ case 'k':
+ case 0x137:
+ case 0x199:
+ case 0x1e9:
+ case 0x1d84:
+ case 0x1e31:
+ case 0x1e33:
+ case 0x1e35:
+ case 0x2c6a:
+ case 0xa741:
+ EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9)
+ EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33)
+ EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741)
+ return;
+
+ case 'l':
+ case 0x13a:
+ case 0x13c:
+ case 0x13e:
+ case 0x140:
+ case 0x142:
+ case 0x19a:
+ case 0x1e37:
+ case 0x1e39:
+ case 0x1e3b:
+ case 0x1e3d:
+ case 0x2c61:
+ EMIT2('l') EMIT2(0x13a) EMIT2(0x13c)
+ EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142)
+ EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39)
+ EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61)
+ return;
+
+ case 'm':
+ case 0x1d6f:
+ case 0x1e3f:
+ case 0x1e41:
+ case 0x1e43:
+ EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f)
+ EMIT2(0x1e41) EMIT2(0x1e43)
+ return;
+
+ case 'n':
+ case n_virguilla:
+ case 0x144:
+ case 0x146:
+ case 0x148:
+ case 0x149:
+ case 0x1f9:
+ case 0x1d70:
+ case 0x1d87:
+ case 0x1e45:
+ case 0x1e47:
+ case 0x1e49:
+ case 0x1e4b:
+ case 0xa7a5:
+ EMIT2('n') EMIT2(n_virguilla)
+ EMIT2(0x144) EMIT2(0x146) EMIT2(0x148)
+ EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70)
+ EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47)
+ EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5)
+ return;
+
+ case 'o':
+ case o_grave:
+ case o_acute:
+ case o_circumflex:
+ case o_virguilla:
+ case o_diaeresis:
+ case o_slash:
+ case 0x14d:
+ case 0x14f:
+ case 0x151:
+ case 0x1a1:
+ case 0x1d2:
+ case 0x1eb:
+ case 0x1ed:
+ case 0x1ff:
+ case 0x20d:
+ case 0x20f:
+ case 0x22b:
+ case 0x22d:
+ case 0x22f:
+ case 0x231:
+ case 0x275:
+ case 0x1e4d:
+ case 0x1e4f:
+ case 0x1e51:
+ case 0x1e53:
+ case 0x1ecd:
+ case 0x1ecf:
+ case 0x1ed1:
+ case 0x1ed3:
+ case 0x1ed5:
+ case 0x1ed7:
+ case 0x1ed9:
+ case 0x1edb:
+ case 0x1edd:
+ case 0x1edf:
+ case 0x1ee1:
+ case 0x1ee3:
+ EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) // NOLINT(whitespace/cast)
+ EMIT2(o_circumflex) EMIT2(o_virguilla) // NOLINT(whitespace/cast)
+ EMIT2(o_diaeresis) EMIT2(o_slash) // NOLINT(whitespace/cast)
+ EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151)
+ EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb)
+ EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d)
+ EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d)
+ EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275)
+ EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51)
+ EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf)
+ EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5)
+ EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb)
+ EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1)
+ EMIT2(0x1ee3)
+ return;
+
+ case 'p':
+ case 0x1a5:
+ case 0x1d71:
+ case 0x1d7d:
+ case 0x1d88:
+ case 0x1e55:
+ case 0x1e57:
+ EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d)
+ EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57)
+ return;
+
+ case 'q':
+ case 0x24b:
+ case 0x2a0:
+ EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0)
+ return;
+
+ case 'r':
+ case 0x155:
+ case 0x157:
+ case 0x159:
+ case 0x211:
+ case 0x213:
+ case 0x24d:
+ case 0x27d:
+ case 0x1d72:
+ case 0x1d73:
+ case 0x1d89:
+ case 0x1e59:
+ case 0x1e5b:
+ case 0x1e5d:
+ case 0x1e5f:
+ case 0xa7a7:
+ EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159)
+ EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d)
+ EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59)
+ EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7)
+ return;
+
+ case 's':
+ case 0x15b:
+ case 0x15d:
+ case 0x15f:
+ case 0x161:
+ case 0x219:
+ case 0x23f:
+ case 0x1d74:
+ case 0x1d8a:
+ case 0x1e61:
+ case 0x1e63:
+ case 0x1e65:
+ case 0x1e67:
+ case 0x1e69:
+ case 0xa7a9:
+ EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f)
+ EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74)
+ EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65)
+ EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9)
+ return;
+
+ case 't':
+ case 0x163:
+ case 0x165:
+ case 0x167:
+ case 0x1ab:
+ case 0x1ad:
+ case 0x21b:
+ case 0x288:
+ case 0x1d75:
+ case 0x1e6b:
+ case 0x1e6d:
+ case 0x1e6f:
+ case 0x1e71:
+ case 0x1e97:
+ case 0x2c66:
+ EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167)
+ EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288)
+ EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f)
+ EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66)
+ return;
+
+ case 'u':
+ case u_grave:
+ case u_acute:
+ case u_circumflex:
+ case u_diaeresis:
+ case 0x169:
+ case 0x16b:
+ case 0x16d:
+ case 0x16f:
+ case 0x171:
+ case 0x173:
+ case 0x1b0:
+ case 0x1d4:
+ case 0x1d6:
+ case 0x1d8:
+ case 0x1da:
+ case 0x1dc:
+ case 0x215:
+ case 0x217:
+ case 0x289:
+ case 0x1d7e:
+ case 0x1d99:
+ case 0x1e73:
+ case 0x1e75:
+ case 0x1e77:
+ case 0x1e79:
+ case 0x1e7b:
+ case 0x1ee5:
+ case 0x1ee7:
+ case 0x1ee9:
+ case 0x1eeb:
+ case 0x1eed:
+ case 0x1eef:
+ case 0x1ef1:
+ EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) // NOLINT(whitespace/cast)
+ EMIT2(u_circumflex) EMIT2(u_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x169) EMIT2(0x16b)
+ EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171)
+ EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8)
+ EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0)
+ EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc)
+ EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e)
+ EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77)
+ EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5)
+ EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb)
+ EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1)
+ return;
+
+ case 'v':
+ case 0x28b:
+ case 0x1d8c:
+ case 0x1e7d:
+ case 0x1e7f:
+ EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d)
+ EMIT2(0x1e7f)
+ return;
+
+ case 'w':
+ case 0x175:
+ case 0x1e81:
+ case 0x1e83:
+ case 0x1e85:
+ case 0x1e87:
+ case 0x1e89:
+ case 0x1e98:
+ EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83)
+ EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98)
+ return;
+
+ case 'x':
+ case 0x1e8b:
+ case 0x1e8d:
+ EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d)
+ return;
+
+ case 'y':
+ case y_acute:
+ case y_diaeresis:
+ case 0x177:
+ case 0x1b4:
+ case 0x233:
+ case 0x24f:
+ case 0x1e8f:
+ case 0x1e99:
+ case 0x1ef3:
+ case 0x1ef5:
+ case 0x1ef7:
+ case 0x1ef9:
+ EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast)
+ EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f)
+ EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3)
+ EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9)
+ return;
+
+ case 'z':
+ case 0x17a:
+ case 0x17c:
+ case 0x17e:
+ case 0x1b6:
+ case 0x1d76:
+ case 0x1d8e:
+ case 0x1e91:
+ case 0x1e93:
+ case 0x1e95:
+ case 0x2c6c:
+ EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e)
+ EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91)
+ EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c)
+ return;
+
+ // default: character itself
+ }
+ }
+
+ EMIT2(c);
+#undef EMIT2
+}
+
+// Code to parse regular expression.
+//
+// We try to reuse parsing functions in regexp.c to
+// minimize surprise and keep the syntax consistent.
+
+// Parse the lowest level.
+//
+// An atom can be one of a long list of items. Many atoms match one character
+// in the text. It is often an ordinary character or a character class.
+// Braces can be used to make a pattern into an atom. The "\z(\)" construct
+// is only for syntax highlighting.
+//
+// atom ::= ordinary-atom
+// or \( pattern \)
+// or \%( pattern \)
+// or \z( pattern \)
+static int nfa_regatom(void)
+{
+ int c;
+ int charclass;
+ int equiclass;
+ int collclass;
+ int got_coll_char;
+ uint8_t *p;
+ uint8_t *endp;
+ uint8_t *old_regparse = (uint8_t *)regparse;
+ int extra = 0;
+ int emit_range;
+ int negated;
+ int startc = -1;
+ int save_prev_at_start = prev_at_start;
+
+ c = getchr();
+ switch (c) {
+ case NUL:
+ EMSG_RET_FAIL(_(e_nul_found));
+
+ case Magic('^'):
+ EMIT(NFA_BOL);
+ break;
+
+ case Magic('$'):
+ EMIT(NFA_EOL);
+ had_eol = true;
+ break;
+
+ case Magic('<'):
+ EMIT(NFA_BOW);
+ break;
+
+ case Magic('>'):
+ EMIT(NFA_EOW);
+ break;
+
+ case Magic('_'):
+ c = no_Magic(getchr());
+ if (c == NUL) {
+ EMSG_RET_FAIL(_(e_nul_found));
+ }
+
+ if (c == '^') { // "\_^" is start-of-line
+ EMIT(NFA_BOL);
+ break;
+ }
+ if (c == '$') { // "\_$" is end-of-line
+ EMIT(NFA_EOL);
+ had_eol = true;
+ break;
+ }
+
+ extra = NFA_ADD_NL;
+
+ // "\_[" is collection plus newline
+ if (c == '[') {
+ goto collection;
+ }
+
+ // "\_x" is character class plus newline
+ FALLTHROUGH;
+
+ // Character classes.
+ case Magic('.'):
+ case Magic('i'):
+ case Magic('I'):
+ case Magic('k'):
+ case Magic('K'):
+ case Magic('f'):
+ case Magic('F'):
+ case Magic('p'):
+ case Magic('P'):
+ case Magic('s'):
+ case Magic('S'):
+ case Magic('d'):
+ case Magic('D'):
+ case Magic('x'):
+ case Magic('X'):
+ case Magic('o'):
+ case Magic('O'):
+ case Magic('w'):
+ case Magic('W'):
+ case Magic('h'):
+ case Magic('H'):
+ case Magic('a'):
+ case Magic('A'):
+ case Magic('l'):
+ case Magic('L'):
+ case Magic('u'):
+ case Magic('U'):
+ p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
+ if (p == NULL) {
+ if (extra == NFA_ADD_NL) {
+ semsg(_(e_ill_char_class), (int64_t)c);
+ rc_did_emsg = true;
+ return FAIL;
+ }
+ siemsg("INTERNAL: Unknown character class char: %" PRId64, (int64_t)c);
+ return FAIL;
+ }
+ // When '.' is followed by a composing char ignore the dot, so that
+ // the composing char is matched here.
+ if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ old_regparse = (uint8_t *)regparse;
+ c = getchr();
+ goto nfa_do_multibyte;
+ }
+ EMIT(nfa_classcodes[p - classchars]);
+ if (extra == NFA_ADD_NL) {
+ EMIT(NFA_NEWL);
+ EMIT(NFA_OR);
+ regflags |= RF_HASNL;
+ }
+ break;
+
+ case Magic('n'):
+ if (reg_string) {
+ // In a string "\n" matches a newline character.
+ EMIT(NL);
+ } else {
+ // In buffer text "\n" matches the end of a line.
+ EMIT(NFA_NEWL);
+ regflags |= RF_HASNL;
+ }
+ break;
+
+ case Magic('('):
+ if (nfa_reg(REG_PAREN) == FAIL) {
+ return FAIL; // cascaded error
+ }
+ break;
+
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ semsg(_(e_misplaced), (char)no_Magic(c));
+ return FAIL;
+
+ case Magic('='):
+ case Magic('?'):
+ case Magic('+'):
+ case Magic('@'):
+ case Magic('*'):
+ case Magic('{'):
+ // these should follow an atom, not form an atom
+ semsg(_(e_misplaced), (char)no_Magic(c));
+ return FAIL;
+
+ case Magic('~'): {
+ uint8_t *lp;
+
+ // Previous substitute pattern.
+ // Generated as "\%(pattern\)".
+ if (reg_prev_sub == NULL) {
+ emsg(_(e_nopresub));
+ return FAIL;
+ }
+ for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) {
+ EMIT(utf_ptr2char((char *)lp));
+ if (lp != (uint8_t *)reg_prev_sub) {
+ EMIT(NFA_CONCAT);
+ }
+ }
+ EMIT(NFA_NOPEN);
+ break;
+ }
+
+ case Magic('1'):
+ case Magic('2'):
+ case Magic('3'):
+ case Magic('4'):
+ case Magic('5'):
+ case Magic('6'):
+ case Magic('7'):
+ case Magic('8'):
+ case Magic('9'): {
+ int refnum = no_Magic(c) - '1';
+
+ if (!seen_endbrace(refnum + 1)) {
+ return FAIL;
+ }
+ EMIT(NFA_BACKREF1 + refnum);
+ rex.nfa_has_backref = true;
+ }
+ break;
+
+ case Magic('z'):
+ c = no_Magic(getchr());
+ switch (c) {
+ case 's':
+ EMIT(NFA_ZSTART);
+ if (!re_mult_next("\\zs")) {
+ return false;
+ }
+ break;
+ case 'e':
+ EMIT(NFA_ZEND);
+ rex.nfa_has_zend = true;
+ if (!re_mult_next("\\zs")) {
+ return false;
+ }
+ break;
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ // \z1...\z9
+ if ((reg_do_extmatch & REX_USE) == 0) {
+ EMSG_RET_FAIL(_(e_z1_not_allowed));
+ }
+ EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
+ // No need to set rex.nfa_has_backref, the sub-matches don't
+ // change when \z1 .. \z9 matches or not.
+ re_has_z = REX_USE;
+ break;
+ case '(':
+ // \z(
+ if (reg_do_extmatch != REX_SET) {
+ EMSG_RET_FAIL(_(e_z_not_allowed));
+ }
+ if (nfa_reg(REG_ZPAREN) == FAIL) {
+ return FAIL; // cascaded error
+ }
+ re_has_z = REX_SET;
+ break;
+ default:
+ semsg(_("E867: (NFA) Unknown operator '\\z%c'"),
+ no_Magic(c));
+ return FAIL;
+ }
+ break;
+
+ case Magic('%'):
+ c = no_Magic(getchr());
+ switch (c) {
+ // () without a back reference
+ case '(':
+ if (nfa_reg(REG_NPAREN) == FAIL) {
+ return FAIL;
+ }
+ EMIT(NFA_NOPEN);
+ break;
+
+ case 'd': // %d123 decimal
+ case 'o': // %o123 octal
+ case 'x': // %xab hex 2
+ case 'u': // %uabcd hex 4
+ case 'U': // %U1234abcd hex 8
+ {
+ int64_t nr;
+
+ switch (c) {
+ case 'd':
+ nr = getdecchrs(); break;
+ case 'o':
+ nr = getoctchrs(); break;
+ case 'x':
+ nr = gethexchrs(2); break;
+ case 'u':
+ nr = gethexchrs(4); break;
+ case 'U':
+ nr = gethexchrs(8); break;
+ default:
+ nr = -1; break;
+ }
+
+ if (nr < 0 || nr > INT_MAX) {
+ EMSG2_RET_FAIL(_("E678: Invalid character after %s%%[dxouU]"),
+ reg_magic == MAGIC_ALL);
+ }
+ // A NUL is stored in the text as NL
+ // TODO(vim): what if a composing character follows?
+ EMIT(nr == 0 ? 0x0a : (int)nr);
+ }
+ break;
+
+ // Catch \%^ and \%$ regardless of where they appear in the
+ // pattern -- regardless of whether or not it makes sense.
+ case '^':
+ EMIT(NFA_BOF);
+ break;
+
+ case '$':
+ EMIT(NFA_EOF);
+ break;
+
+ case '#':
+ if (regparse[0] == '=' && regparse[1] >= 48
+ && regparse[1] <= 50) {
+ // misplaced \%#=1
+ semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
+ return FAIL;
+ }
+ EMIT(NFA_CURSOR);
+ break;
+
+ case 'V':
+ EMIT(NFA_VISUAL);
+ break;
+
+ case 'C':
+ EMIT(NFA_ANY_COMPOSING);
+ break;
+
+ case '[': {
+ int n;
+
+ // \%[abc]
+ for (n = 0; (c = peekchr()) != ']'; n++) {
+ if (c == NUL) {
+ EMSG2_RET_FAIL(_(e_missing_sb),
+ reg_magic == MAGIC_ALL);
+ }
+ // recursive call!
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ }
+ (void)getchr(); // get the ]
+ if (n == 0) {
+ EMSG2_RET_FAIL(_(e_empty_sb), reg_magic == MAGIC_ALL);
+ }
+ EMIT(NFA_OPT_CHARS);
+ EMIT(n);
+
+ // Emit as "\%(\%[abc]\)" to be able to handle
+ // "\%[abc]*" which would cause the empty string to be
+ // matched an unlimited number of times. NFA_NOPEN is
+ // added only once at a position, while NFA_SPLIT is
+ // added multiple times. This is more efficient than
+ // not allowing NFA_SPLIT multiple times, it is used
+ // a lot.
+ EMIT(NFA_NOPEN);
+ break;
+ }
+
+ default: {
+ int64_t n = 0;
+ const int cmp = c;
+ bool cur = false;
+ bool got_digit = false;
+
+ if (c == '<' || c == '>') {
+ c = getchr();
+ }
+ if (no_Magic(c) == '.') {
+ cur = true;
+ c = getchr();
+ }
+ while (ascii_isdigit(c)) {
+ if (cur) {
+ semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
+ return FAIL;
+ }
+ if (n > (INT32_MAX - (c - '0')) / 10) {
+ // overflow.
+ emsg(_(e_value_too_large));
+ return FAIL;
+ }
+ n = n * 10 + (c - '0');
+ c = getchr();
+ got_digit = true;
+ }
+ if (c == 'l' || c == 'c' || c == 'v') {
+ int32_t limit = INT32_MAX;
+
+ if (!cur && !got_digit) {
+ semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c));
+ return FAIL;
+ }
+ if (c == 'l') {
+ if (cur) {
+ n = curwin->w_cursor.lnum;
+ }
+ // \%{n}l \%{n}<l \%{n}>l
+ EMIT(cmp == '<' ? NFA_LNUM_LT
+ : cmp == '>' ? NFA_LNUM_GT : NFA_LNUM);
+ if (save_prev_at_start) {
+ at_start = true;
+ }
+ } else if (c == 'c') {
+ if (cur) {
+ n = curwin->w_cursor.col;
+ n++;
+ }
+ // \%{n}c \%{n}<c \%{n}>c
+ EMIT(cmp == '<' ? NFA_COL_LT
+ : cmp == '>' ? NFA_COL_GT : NFA_COL);
+ } else {
+ if (cur) {
+ colnr_T vcol = 0;
+ getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
+ n = ++vcol;
+ }
+ // \%{n}v \%{n}<v \%{n}>v
+ EMIT(cmp == '<' ? NFA_VCOL_LT
+ : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
+ limit = INT32_MAX / MB_MAXBYTES;
+ }
+ if (n >= limit) {
+ emsg(_(e_value_too_large));
+ return FAIL;
+ }
+ EMIT((int)n);
+ break;
+ } else if (c == '\'' && n == 0) {
+ // \%'m \%<'m \%>'m
+ EMIT(cmp == '<' ? NFA_MARK_LT
+ : cmp == '>' ? NFA_MARK_GT : NFA_MARK);
+ EMIT(getchr());
+ break;
+ }
+ }
+ semsg(_("E867: (NFA) Unknown operator '\\%%%c'"),
+ no_Magic(c));
+ return FAIL;
+ }
+ break;
+
+ case Magic('['):
+collection:
+ // [abc] uses NFA_START_COLL - NFA_END_COLL
+ // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
+ // Each character is produced as a regular state, using
+ // NFA_CONCAT to bind them together.
+ // Besides normal characters there can be:
+ // - character classes NFA_CLASS_*
+ // - ranges, two characters followed by NFA_RANGE.
+
+ p = (uint8_t *)regparse;
+ endp = (uint8_t *)skip_anyof((char *)p);
+ if (*endp == ']') {
+ // Try to reverse engineer character classes. For example,
+ // recognize that [0-9] stands for \d and [A-Za-z_] for \h,
+ // and perform the necessary substitutions in the NFA.
+ int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL);
+ if (result != FAIL) {
+ if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) {
+ EMIT(result - NFA_ADD_NL);
+ EMIT(NFA_NEWL);
+ EMIT(NFA_OR);
+ } else {
+ EMIT(result);
+ }
+ regparse = (char *)endp;
+ MB_PTR_ADV(regparse);
+ return OK;
+ }
+ // Failed to recognize a character class. Use the simple
+ // version that turns [abc] into 'a' OR 'b' OR 'c'
+ negated = false;
+ if (*regparse == '^') { // negated range
+ negated = true;
+ MB_PTR_ADV(regparse);
+ EMIT(NFA_START_NEG_COLL);
+ } else {
+ EMIT(NFA_START_COLL);
+ }
+ if (*regparse == '-') {
+ startc = '-';
+ EMIT(startc);
+ EMIT(NFA_CONCAT);
+ MB_PTR_ADV(regparse);
+ }
+ // Emit the OR branches for each character in the []
+ emit_range = false;
+ while ((uint8_t *)regparse < endp) {
+ int oldstartc = startc;
+ startc = -1;
+ got_coll_char = false;
+ if (*regparse == '[') {
+ // Check for [: :], [= =], [. .]
+ equiclass = collclass = 0;
+ charclass = get_char_class(&regparse);
+ if (charclass == CLASS_NONE) {
+ equiclass = get_equi_class(&regparse);
+ if (equiclass == 0) {
+ collclass = get_coll_element(&regparse);
+ }
+ }
+
+ // Character class like [:alpha:]
+ if (charclass != CLASS_NONE) {
+ switch (charclass) {
+ case CLASS_ALNUM:
+ EMIT(NFA_CLASS_ALNUM);
+ break;
+ case CLASS_ALPHA:
+ EMIT(NFA_CLASS_ALPHA);
+ break;
+ case CLASS_BLANK:
+ EMIT(NFA_CLASS_BLANK);
+ break;
+ case CLASS_CNTRL:
+ EMIT(NFA_CLASS_CNTRL);
+ break;
+ case CLASS_DIGIT:
+ EMIT(NFA_CLASS_DIGIT);
+ break;
+ case CLASS_GRAPH:
+ EMIT(NFA_CLASS_GRAPH);
+ break;
+ case CLASS_LOWER:
+ wants_nfa = true;
+ EMIT(NFA_CLASS_LOWER);
+ break;
+ case CLASS_PRINT:
+ EMIT(NFA_CLASS_PRINT);
+ break;
+ case CLASS_PUNCT:
+ EMIT(NFA_CLASS_PUNCT);
+ break;
+ case CLASS_SPACE:
+ EMIT(NFA_CLASS_SPACE);
+ break;
+ case CLASS_UPPER:
+ wants_nfa = true;
+ EMIT(NFA_CLASS_UPPER);
+ break;
+ case CLASS_XDIGIT:
+ EMIT(NFA_CLASS_XDIGIT);
+ break;
+ case CLASS_TAB:
+ EMIT(NFA_CLASS_TAB);
+ break;
+ case CLASS_RETURN:
+ EMIT(NFA_CLASS_RETURN);
+ break;
+ case CLASS_BACKSPACE:
+ EMIT(NFA_CLASS_BACKSPACE);
+ break;
+ case CLASS_ESCAPE:
+ EMIT(NFA_CLASS_ESCAPE);
+ break;
+ case CLASS_IDENT:
+ EMIT(NFA_CLASS_IDENT);
+ break;
+ case CLASS_KEYWORD:
+ EMIT(NFA_CLASS_KEYWORD);
+ break;
+ case CLASS_FNAME:
+ EMIT(NFA_CLASS_FNAME);
+ break;
+ }
+ EMIT(NFA_CONCAT);
+ continue;
+ }
+ // Try equivalence class [=a=] and the like
+ if (equiclass != 0) {
+ nfa_emit_equi_class(equiclass);
+ continue;
+ }
+ // Try collating class like [. .]
+ if (collclass != 0) {
+ startc = collclass; // allow [.a.]-x as a range
+ // Will emit the proper atom at the end of the
+ // while loop.
+ }
+ }
+ // Try a range like 'a-x' or '\t-z'. Also allows '-' as a
+ // start character.
+ if (*regparse == '-' && oldstartc != -1) {
+ emit_range = true;
+ startc = oldstartc;
+ MB_PTR_ADV(regparse);
+ continue; // reading the end of the range
+ }
+
+ // Now handle simple and escaped characters.
+ // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
+ // accepts "\t", "\e", etc., but only when the 'l' flag in
+ // 'cpoptions' is not included.
+ if (*regparse == '\\'
+ && (uint8_t *)regparse + 1 <= endp
+ && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
+ || (!reg_cpo_lit
+ && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1])
+ != NULL))) {
+ MB_PTR_ADV(regparse);
+
+ if (*regparse == 'n') {
+ startc = (reg_string || emit_range || regparse[1] == '-')
+ ? NL : NFA_NEWL;
+ } else if (*regparse == 'd'
+ || *regparse == 'o'
+ || *regparse == 'x'
+ || *regparse == 'u'
+ || *regparse == 'U') {
+ // TODO(RE): This needs more testing
+ startc = coll_get_char();
+ got_coll_char = true;
+ MB_PTR_BACK(old_regparse, regparse);
+ } else {
+ // \r,\t,\e,\b
+ startc = backslash_trans(*regparse);
+ }
+ }
+
+ // Normal printable char
+ if (startc == -1) {
+ startc = utf_ptr2char(regparse);
+ }
+
+ // Previous char was '-', so this char is end of range.
+ if (emit_range) {
+ int endc = startc;
+ startc = oldstartc;
+ if (startc > endc) {
+ EMSG_RET_FAIL(_(e_reverse_range));
+ }
+
+ if (endc > startc + 2) {
+ // Emit a range instead of the sequence of
+ // individual characters.
+ if (startc == 0) {
+ // \x00 is translated to \x0a, start at \x01.
+ EMIT(1);
+ } else {
+ post_ptr--; // remove NFA_CONCAT
+ }
+ EMIT(endc);
+ EMIT(NFA_RANGE);
+ EMIT(NFA_CONCAT);
+ } else if (utf_char2len(startc) > 1
+ || utf_char2len(endc) > 1) {
+ // Emit the characters in the range.
+ // "startc" was already emitted, so skip it.
+ for (c = startc + 1; c <= endc; c++) {
+ EMIT(c);
+ EMIT(NFA_CONCAT);
+ }
+ } else {
+ // Emit the range. "startc" was already emitted, so
+ // skip it.
+ for (c = startc + 1; c <= endc; c++) {
+ EMIT(c);
+ EMIT(NFA_CONCAT);
+ }
+ }
+ emit_range = false;
+ startc = -1;
+ } else {
+ // This char (startc) is not part of a range. Just
+ // emit it.
+ // Normally, simply emit startc. But if we get char
+ // code=0 from a collating char, then replace it with
+ // 0x0a.
+ // This is needed to completely mimic the behaviour of
+ // the backtracking engine.
+ if (startc == NFA_NEWL) {
+ // Line break can't be matched as part of the
+ // collection, add an OR below. But not for negated
+ // range.
+ if (!negated) {
+ extra = NFA_ADD_NL;
+ }
+ } else {
+ if (got_coll_char == true && startc == 0) {
+ EMIT(0x0a);
+ } else {
+ EMIT(startc);
+ }
+ EMIT(NFA_CONCAT);
+ }
+ }
+
+ MB_PTR_ADV(regparse);
+ } // while (p < endp)
+
+ MB_PTR_BACK(old_regparse, regparse);
+ if (*regparse == '-') { // if last, '-' is just a char
+ EMIT('-');
+ EMIT(NFA_CONCAT);
+ }
+
+ // skip the trailing ]
+ regparse = (char *)endp;
+ MB_PTR_ADV(regparse);
+
+ // Mark end of the collection.
+ if (negated == true) {
+ EMIT(NFA_END_NEG_COLL);
+ } else {
+ EMIT(NFA_END_COLL);
+ }
+
+ // \_[] also matches \n but it's not negated
+ if (extra == NFA_ADD_NL) {
+ EMIT(reg_string ? NL : NFA_NEWL);
+ EMIT(NFA_OR);
+ }
+
+ return OK;
+ } // if exists closing ]
+
+ if (reg_strict) {
+ EMSG_RET_FAIL(_(e_missingbracket));
+ }
+ FALLTHROUGH;
+
+ default: {
+ int plen;
+
+nfa_do_multibyte:
+ // plen is length of current char with composing chars
+ if (utf_char2len(c) != (plen = utfc_ptr2len((char *)old_regparse))
+ || utf_iscomposing(c)) {
+ int i = 0;
+
+ // A base character plus composing characters, or just one
+ // or more composing characters.
+ // This requires creating a separate atom as if enclosing
+ // the characters in (), where NFA_COMPOSING is the ( and
+ // NFA_END_COMPOSING is the ). Note that right now we are
+ // building the postfix form, not the NFA itself;
+ // a composing char could be: a, b, c, NFA_COMPOSING
+ // where 'b' and 'c' are chars with codes > 256. */
+ while (true) {
+ EMIT(c);
+ if (i > 0) {
+ EMIT(NFA_CONCAT);
+ }
+ if ((i += utf_char2len(c)) >= plen) {
+ break;
+ }
+ c = utf_ptr2char((char *)old_regparse + i);
+ }
+ EMIT(NFA_COMPOSING);
+ regparse = (char *)old_regparse + plen;
+ } else {
+ c = no_Magic(c);
+ EMIT(c);
+ }
+ return OK;
+ }
+ }
+
+ return OK;
+}
+
+// Parse something followed by possible [*+=].
+//
+// A piece is an atom, possibly followed by a multi, an indication of how many
+// times the atom can be matched. Example: "a*" matches any sequence of "a"
+// characters: "", "a", "aa", etc.
+//
+// piece ::= atom
+// or atom multi
+static int nfa_regpiece(void)
+{
+ int i;
+ int op;
+ int ret;
+ int minval, maxval;
+ bool greedy = true; // Braces are prefixed with '-' ?
+ parse_state_T old_state;
+ parse_state_T new_state;
+ int64_t c2;
+ int old_post_pos;
+ int my_post_start;
+ int quest;
+
+ // Save the current parse state, so that we can use it if <atom>{m,n} is
+ // next.
+ save_parse_state(&old_state);
+
+ // store current pos in the postfix form, for \{m,n} involving 0s
+ my_post_start = (int)(post_ptr - post_start);
+
+ ret = nfa_regatom();
+ if (ret == FAIL) {
+ return FAIL; // cascaded error
+ }
+ op = peekchr();
+ if (re_multi_type(op) == NOT_MULTI) {
+ return OK;
+ }
+
+ skipchr();
+ switch (op) {
+ case Magic('*'):
+ EMIT(NFA_STAR);
+ break;
+
+ case Magic('+'):
+ // Trick: Normally, (a*)\+ would match the whole input "aaa". The
+ // first and only submatch would be "aaa". But the backtracking
+ // engine interprets the plus as "try matching one more time", and
+ // a* matches a second time at the end of the input, the empty
+ // string.
+ // The submatch will be the empty string.
+ //
+ // In order to be consistent with the old engine, we replace
+ // <atom>+ with <atom><atom>*
+ restore_parse_state(&old_state);
+ curchr = -1;
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ EMIT(NFA_STAR);
+ EMIT(NFA_CONCAT);
+ skipchr(); // skip the \+
+ break;
+
+ case Magic('@'):
+ c2 = getdecchrs();
+ op = no_Magic(getchr());
+ i = 0;
+ switch (op) {
+ case '=':
+ // \@=
+ i = NFA_PREV_ATOM_NO_WIDTH;
+ break;
+ case '!':
+ // \@!
+ i = NFA_PREV_ATOM_NO_WIDTH_NEG;
+ break;
+ case '<':
+ op = no_Magic(getchr());
+ if (op == '=') {
+ // \@<=
+ i = NFA_PREV_ATOM_JUST_BEFORE;
+ } else if (op == '!') {
+ // \@<!
+ i = NFA_PREV_ATOM_JUST_BEFORE_NEG;
+ }
+ break;
+ case '>':
+ // \@>
+ i = NFA_PREV_ATOM_LIKE_PATTERN;
+ break;
+ }
+ if (i == 0) {
+ semsg(_("E869: (NFA) Unknown operator '\\@%c'"), op);
+ return FAIL;
+ }
+ EMIT(i);
+ if (i == NFA_PREV_ATOM_JUST_BEFORE
+ || i == NFA_PREV_ATOM_JUST_BEFORE_NEG) {
+ EMIT((int)c2);
+ }
+ break;
+
+ case Magic('?'):
+ case Magic('='):
+ EMIT(NFA_QUEST);
+ break;
+
+ case Magic('{'):
+ // a{2,5} will expand to 'aaa?a?a?'
+ // a{-1,3} will expand to 'aa??a??', where ?? is the nongreedy
+ // version of '?'
+ // \v(ab){2,3} will expand to '(ab)(ab)(ab)?', where all the
+ // parenthesis have the same id
+
+ greedy = true;
+ c2 = peekchr();
+ if (c2 == '-' || c2 == Magic('-')) {
+ skipchr();
+ greedy = false;
+ }
+ if (!read_limits(&minval, &maxval)) {
+ EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
+ }
+
+ // <atom>{0,inf}, <atom>{0,} and <atom>{} are equivalent to
+ // <atom>*
+ if (minval == 0 && maxval == MAX_LIMIT) {
+ if (greedy) {
+ // \{}, \{0,}
+ EMIT(NFA_STAR);
+ } else {
+ // \{-}, \{-0,}
+ EMIT(NFA_STAR_NONGREEDY);
+ }
+ break;
+ }
+
+ // Special case: x{0} or x{-0}
+ if (maxval == 0) {
+ // Ignore result of previous call to nfa_regatom()
+ post_ptr = post_start + my_post_start;
+ // NFA_EMPTY is 0-length and works everywhere
+ EMIT(NFA_EMPTY);
+ return OK;
+ }
+
+ // The engine is very inefficient (uses too many states) when the
+ // maximum is much larger than the minimum and when the maximum is
+ // large. However, when maxval is MAX_LIMIT, it is okay, as this
+ // will emit NFA_STAR.
+ // Bail out if we can use the other engine, but only, when the
+ // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
+ // does not work with characters > 8 bit with the BT engine)
+ if ((nfa_re_flags & RE_AUTO)
+ && (maxval > 500 || maxval > minval + 200)
+ && (maxval != MAX_LIMIT && minval < 200)
+ && !wants_nfa) {
+ return FAIL;
+ }
+
+ // Ignore previous call to nfa_regatom()
+ post_ptr = post_start + my_post_start;
+ // Save parse state after the repeated atom and the \{}
+ save_parse_state(&new_state);
+
+ quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY);
+ for (i = 0; i < maxval; i++) {
+ // Goto beginning of the repeated atom
+ restore_parse_state(&old_state);
+ old_post_pos = (int)(post_ptr - post_start);
+ if (nfa_regatom() == FAIL) {
+ return FAIL;
+ }
+ // after "minval" times, atoms are optional
+ if (i + 1 > minval) {
+ if (maxval == MAX_LIMIT) {
+ if (greedy) {
+ EMIT(NFA_STAR);
+ } else {
+ EMIT(NFA_STAR_NONGREEDY);
+ }
+ } else {
+ EMIT(quest);
+ }
+ }
+ if (old_post_pos != my_post_start) {
+ EMIT(NFA_CONCAT);
+ }
+ if (i + 1 > minval && maxval == MAX_LIMIT) {
+ break;
+ }
+ }
+
+ // Go to just after the repeated atom and the \{}
+ restore_parse_state(&new_state);
+ curchr = -1;
+
+ break;
+
+ default:
+ break;
+ } // end switch
+
+ if (re_multi_type(peekchr()) != NOT_MULTI) {
+ // Can't have a multi follow a multi.
+ EMSG_RET_FAIL(_("E871: (NFA regexp) Can't have a multi follow a multi"));
+ }
+
+ return OK;
+}
+
+// Parse one or more pieces, concatenated. It matches a match for the
+// first piece, followed by a match for the second piece, etc. Example:
+// "f[0-9]b", first matches "f", then a digit and then "b".
+//
+// concat ::= piece
+// or piece piece
+// or piece piece piece
+// etc.
+static int nfa_regconcat(void)
+{
+ bool cont = true;
+ bool first = true;
+
+ while (cont) {
+ switch (peekchr()) {
+ case NUL:
+ case Magic('|'):
+ case Magic('&'):
+ case Magic(')'):
+ cont = false;
+ break;
+
+ case Magic('Z'):
+ regflags |= RF_ICOMBINE;
+ skipchr_keepstart();
+ break;
+ case Magic('c'):
+ regflags |= RF_ICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('C'):
+ regflags |= RF_NOICASE;
+ skipchr_keepstart();
+ break;
+ case Magic('v'):
+ reg_magic = MAGIC_ALL;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('m'):
+ reg_magic = MAGIC_ON;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('M'):
+ reg_magic = MAGIC_OFF;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+ case Magic('V'):
+ reg_magic = MAGIC_NONE;
+ skipchr_keepstart();
+ curchr = -1;
+ break;
+
+ default:
+ if (nfa_regpiece() == FAIL) {
+ return FAIL;
+ }
+ if (first == false) {
+ EMIT(NFA_CONCAT);
+ } else {
+ first = false;
+ }
+ break;
+ }
+ }
+
+ return OK;
+}
+
+// Parse a branch, one or more concats, separated by "\&". It matches the
+// last concat, but only if all the preceding concats also match at the same
+// position. Examples:
+// "foobeep\&..." matches "foo" in "foobeep".
+// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
+//
+// branch ::= concat
+// or concat \& concat
+// or concat \& concat \& concat
+// etc.
+static int nfa_regbranch(void)
+{
+ int old_post_pos;
+
+ old_post_pos = (int)(post_ptr - post_start);
+
+ // First branch, possibly the only one
+ if (nfa_regconcat() == FAIL) {
+ return FAIL;
+ }
+
+ // Try next concats
+ while (peekchr() == Magic('&')) {
+ skipchr();
+ // if concat is empty do emit a node
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+ EMIT(NFA_NOPEN);
+ EMIT(NFA_PREV_ATOM_NO_WIDTH);
+ old_post_pos = (int)(post_ptr - post_start);
+ if (nfa_regconcat() == FAIL) {
+ return FAIL;
+ }
+ // if concat is empty do emit a node
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+ EMIT(NFA_CONCAT);
+ }
+
+ // if a branch is empty, emit one node for it
+ if (old_post_pos == (int)(post_ptr - post_start)) {
+ EMIT(NFA_EMPTY);
+ }
+
+ return OK;
+}
+
+/// Parse a pattern, one or more branches, separated by "\|". It matches
+/// anything that matches one of the branches. Example: "foo\|beep" matches
+/// "foo" and matches "beep". If more than one branch matches, the first one
+/// is used.
+///
+/// pattern ::= branch
+/// or branch \| branch
+/// or branch \| branch \| branch
+/// etc.
+///
+/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
+static int nfa_reg(int paren)
+{
+ int parno = 0;
+
+ if (paren == REG_PAREN) {
+ if (regnpar >= NSUBEXP) { // Too many `('
+ EMSG_RET_FAIL(_("E872: (NFA regexp) Too many '('"));
+ }
+ parno = regnpar++;
+ } else if (paren == REG_ZPAREN) {
+ // Make a ZOPEN node.
+ if (regnzpar >= NSUBEXP) {
+ EMSG_RET_FAIL(_("E879: (NFA regexp) Too many \\z("));
+ }
+ parno = regnzpar++;
+ }
+
+ if (nfa_regbranch() == FAIL) {
+ return FAIL; // cascaded error
+ }
+ while (peekchr() == Magic('|')) {
+ skipchr();
+ if (nfa_regbranch() == FAIL) {
+ return FAIL; // cascaded error
+ }
+ EMIT(NFA_OR);
+ }
+
+ // Check for proper termination.
+ if (paren != REG_NOPAREN && getchr() != Magic(')')) {
+ if (paren == REG_NPAREN) {
+ EMSG2_RET_FAIL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG2_RET_FAIL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
+ }
+ } else if (paren == REG_NOPAREN && peekchr() != NUL) {
+ if (peekchr() == Magic(')')) {
+ EMSG2_RET_FAIL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
+ } else {
+ EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
+ }
+ }
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
+ if (paren == REG_PAREN) {
+ had_endbrace[parno] = true; // have seen the close paren
+ EMIT(NFA_MOPEN + parno);
+ } else if (paren == REG_ZPAREN) {
+ EMIT(NFA_ZOPEN + parno);
+ }
+
+ return OK;
+}
+
+#ifdef REGEXP_DEBUG
+static uint8_t code[50];
+
+static void nfa_set_code(int c)
+{
+ int addnl = false;
+
+ if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
+ addnl = true;
+ c -= NFA_ADD_NL;
+ }
+
+ STRCPY(code, "");
+ switch (c) {
+ case NFA_MATCH:
+ STRCPY(code, "NFA_MATCH "); break;
+ case NFA_SPLIT:
+ STRCPY(code, "NFA_SPLIT "); break;
+ case NFA_CONCAT:
+ STRCPY(code, "NFA_CONCAT "); break;
+ case NFA_NEWL:
+ STRCPY(code, "NFA_NEWL "); break;
+ case NFA_ZSTART:
+ STRCPY(code, "NFA_ZSTART"); break;
+ case NFA_ZEND:
+ STRCPY(code, "NFA_ZEND"); break;
+
+ case NFA_BACKREF1:
+ STRCPY(code, "NFA_BACKREF1"); break;
+ case NFA_BACKREF2:
+ STRCPY(code, "NFA_BACKREF2"); break;
+ case NFA_BACKREF3:
+ STRCPY(code, "NFA_BACKREF3"); break;
+ case NFA_BACKREF4:
+ STRCPY(code, "NFA_BACKREF4"); break;
+ case NFA_BACKREF5:
+ STRCPY(code, "NFA_BACKREF5"); break;
+ case NFA_BACKREF6:
+ STRCPY(code, "NFA_BACKREF6"); break;
+ case NFA_BACKREF7:
+ STRCPY(code, "NFA_BACKREF7"); break;
+ case NFA_BACKREF8:
+ STRCPY(code, "NFA_BACKREF8"); break;
+ case NFA_BACKREF9:
+ STRCPY(code, "NFA_BACKREF9"); break;
+ case NFA_ZREF1:
+ STRCPY(code, "NFA_ZREF1"); break;
+ case NFA_ZREF2:
+ STRCPY(code, "NFA_ZREF2"); break;
+ case NFA_ZREF3:
+ STRCPY(code, "NFA_ZREF3"); break;
+ case NFA_ZREF4:
+ STRCPY(code, "NFA_ZREF4"); break;
+ case NFA_ZREF5:
+ STRCPY(code, "NFA_ZREF5"); break;
+ case NFA_ZREF6:
+ STRCPY(code, "NFA_ZREF6"); break;
+ case NFA_ZREF7:
+ STRCPY(code, "NFA_ZREF7"); break;
+ case NFA_ZREF8:
+ STRCPY(code, "NFA_ZREF8"); break;
+ case NFA_ZREF9:
+ STRCPY(code, "NFA_ZREF9"); break;
+ case NFA_SKIP:
+ STRCPY(code, "NFA_SKIP"); break;
+
+ case NFA_PREV_ATOM_NO_WIDTH:
+ STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH"); break;
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH_NEG"); break;
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE"); break;
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE_NEG"); break;
+ case NFA_PREV_ATOM_LIKE_PATTERN:
+ STRCPY(code, "NFA_PREV_ATOM_LIKE_PATTERN"); break;
+
+ case NFA_NOPEN:
+ STRCPY(code, "NFA_NOPEN"); break;
+ case NFA_NCLOSE:
+ STRCPY(code, "NFA_NCLOSE"); break;
+ case NFA_START_INVISIBLE:
+ STRCPY(code, "NFA_START_INVISIBLE"); break;
+ case NFA_START_INVISIBLE_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_FIRST"); break;
+ case NFA_START_INVISIBLE_NEG:
+ STRCPY(code, "NFA_START_INVISIBLE_NEG"); break;
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_NEG_FIRST"); break;
+ case NFA_START_INVISIBLE_BEFORE:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE"); break;
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_FIRST"); break;
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG"); break;
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG_FIRST"); break;
+ case NFA_START_PATTERN:
+ STRCPY(code, "NFA_START_PATTERN"); break;
+ case NFA_END_INVISIBLE:
+ STRCPY(code, "NFA_END_INVISIBLE"); break;
+ case NFA_END_INVISIBLE_NEG:
+ STRCPY(code, "NFA_END_INVISIBLE_NEG"); break;
+ case NFA_END_PATTERN:
+ STRCPY(code, "NFA_END_PATTERN"); break;
+
+ case NFA_COMPOSING:
+ STRCPY(code, "NFA_COMPOSING"); break;
+ case NFA_END_COMPOSING:
+ STRCPY(code, "NFA_END_COMPOSING"); break;
+ case NFA_OPT_CHARS:
+ STRCPY(code, "NFA_OPT_CHARS"); break;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ STRCPY(code, "NFA_MOPEN(x)");
+ code[10] = c - NFA_MOPEN + '0';
+ break;
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ STRCPY(code, "NFA_MCLOSE(x)");
+ code[11] = c - NFA_MCLOSE + '0';
+ break;
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ STRCPY(code, "NFA_ZOPEN(x)");
+ code[10] = c - NFA_ZOPEN + '0';
+ break;
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ STRCPY(code, "NFA_ZCLOSE(x)");
+ code[11] = c - NFA_ZCLOSE + '0';
+ break;
+ case NFA_EOL:
+ STRCPY(code, "NFA_EOL "); break;
+ case NFA_BOL:
+ STRCPY(code, "NFA_BOL "); break;
+ case NFA_EOW:
+ STRCPY(code, "NFA_EOW "); break;
+ case NFA_BOW:
+ STRCPY(code, "NFA_BOW "); break;
+ case NFA_EOF:
+ STRCPY(code, "NFA_EOF "); break;
+ case NFA_BOF:
+ STRCPY(code, "NFA_BOF "); break;
+ case NFA_LNUM:
+ STRCPY(code, "NFA_LNUM "); break;
+ case NFA_LNUM_GT:
+ STRCPY(code, "NFA_LNUM_GT "); break;
+ case NFA_LNUM_LT:
+ STRCPY(code, "NFA_LNUM_LT "); break;
+ case NFA_COL:
+ STRCPY(code, "NFA_COL "); break;
+ case NFA_COL_GT:
+ STRCPY(code, "NFA_COL_GT "); break;
+ case NFA_COL_LT:
+ STRCPY(code, "NFA_COL_LT "); break;
+ case NFA_VCOL:
+ STRCPY(code, "NFA_VCOL "); break;
+ case NFA_VCOL_GT:
+ STRCPY(code, "NFA_VCOL_GT "); break;
+ case NFA_VCOL_LT:
+ STRCPY(code, "NFA_VCOL_LT "); break;
+ case NFA_MARK:
+ STRCPY(code, "NFA_MARK "); break;
+ case NFA_MARK_GT:
+ STRCPY(code, "NFA_MARK_GT "); break;
+ case NFA_MARK_LT:
+ STRCPY(code, "NFA_MARK_LT "); break;
+ case NFA_CURSOR:
+ STRCPY(code, "NFA_CURSOR "); break;
+ case NFA_VISUAL:
+ STRCPY(code, "NFA_VISUAL "); break;
+ case NFA_ANY_COMPOSING:
+ STRCPY(code, "NFA_ANY_COMPOSING "); break;
+
+ case NFA_STAR:
+ STRCPY(code, "NFA_STAR "); break;
+ case NFA_STAR_NONGREEDY:
+ STRCPY(code, "NFA_STAR_NONGREEDY "); break;
+ case NFA_QUEST:
+ STRCPY(code, "NFA_QUEST"); break;
+ case NFA_QUEST_NONGREEDY:
+ STRCPY(code, "NFA_QUEST_NON_GREEDY"); break;
+ case NFA_EMPTY:
+ STRCPY(code, "NFA_EMPTY"); break;
+ case NFA_OR:
+ STRCPY(code, "NFA_OR"); break;
+
+ case NFA_START_COLL:
+ STRCPY(code, "NFA_START_COLL"); break;
+ case NFA_END_COLL:
+ STRCPY(code, "NFA_END_COLL"); break;
+ case NFA_START_NEG_COLL:
+ STRCPY(code, "NFA_START_NEG_COLL"); break;
+ case NFA_END_NEG_COLL:
+ STRCPY(code, "NFA_END_NEG_COLL"); break;
+ case NFA_RANGE:
+ STRCPY(code, "NFA_RANGE"); break;
+ case NFA_RANGE_MIN:
+ STRCPY(code, "NFA_RANGE_MIN"); break;
+ case NFA_RANGE_MAX:
+ STRCPY(code, "NFA_RANGE_MAX"); break;
+
+ case NFA_CLASS_ALNUM:
+ STRCPY(code, "NFA_CLASS_ALNUM"); break;
+ case NFA_CLASS_ALPHA:
+ STRCPY(code, "NFA_CLASS_ALPHA"); break;
+ case NFA_CLASS_BLANK:
+ STRCPY(code, "NFA_CLASS_BLANK"); break;
+ case NFA_CLASS_CNTRL:
+ STRCPY(code, "NFA_CLASS_CNTRL"); break;
+ case NFA_CLASS_DIGIT:
+ STRCPY(code, "NFA_CLASS_DIGIT"); break;
+ case NFA_CLASS_GRAPH:
+ STRCPY(code, "NFA_CLASS_GRAPH"); break;
+ case NFA_CLASS_LOWER:
+ STRCPY(code, "NFA_CLASS_LOWER"); break;
+ case NFA_CLASS_PRINT:
+ STRCPY(code, "NFA_CLASS_PRINT"); break;
+ case NFA_CLASS_PUNCT:
+ STRCPY(code, "NFA_CLASS_PUNCT"); break;
+ case NFA_CLASS_SPACE:
+ STRCPY(code, "NFA_CLASS_SPACE"); break;
+ case NFA_CLASS_UPPER:
+ STRCPY(code, "NFA_CLASS_UPPER"); break;
+ case NFA_CLASS_XDIGIT:
+ STRCPY(code, "NFA_CLASS_XDIGIT"); break;
+ case NFA_CLASS_TAB:
+ STRCPY(code, "NFA_CLASS_TAB"); break;
+ case NFA_CLASS_RETURN:
+ STRCPY(code, "NFA_CLASS_RETURN"); break;
+ case NFA_CLASS_BACKSPACE:
+ STRCPY(code, "NFA_CLASS_BACKSPACE"); break;
+ case NFA_CLASS_ESCAPE:
+ STRCPY(code, "NFA_CLASS_ESCAPE"); break;
+ case NFA_CLASS_IDENT:
+ STRCPY(code, "NFA_CLASS_IDENT"); break;
+ case NFA_CLASS_KEYWORD:
+ STRCPY(code, "NFA_CLASS_KEYWORD"); break;
+ case NFA_CLASS_FNAME:
+ STRCPY(code, "NFA_CLASS_FNAME"); break;
+
+ case NFA_ANY:
+ STRCPY(code, "NFA_ANY"); break;
+ case NFA_IDENT:
+ STRCPY(code, "NFA_IDENT"); break;
+ case NFA_SIDENT:
+ STRCPY(code, "NFA_SIDENT"); break;
+ case NFA_KWORD:
+ STRCPY(code, "NFA_KWORD"); break;
+ case NFA_SKWORD:
+ STRCPY(code, "NFA_SKWORD"); break;
+ case NFA_FNAME:
+ STRCPY(code, "NFA_FNAME"); break;
+ case NFA_SFNAME:
+ STRCPY(code, "NFA_SFNAME"); break;
+ case NFA_PRINT:
+ STRCPY(code, "NFA_PRINT"); break;
+ case NFA_SPRINT:
+ STRCPY(code, "NFA_SPRINT"); break;
+ case NFA_WHITE:
+ STRCPY(code, "NFA_WHITE"); break;
+ case NFA_NWHITE:
+ STRCPY(code, "NFA_NWHITE"); break;
+ case NFA_DIGIT:
+ STRCPY(code, "NFA_DIGIT"); break;
+ case NFA_NDIGIT:
+ STRCPY(code, "NFA_NDIGIT"); break;
+ case NFA_HEX:
+ STRCPY(code, "NFA_HEX"); break;
+ case NFA_NHEX:
+ STRCPY(code, "NFA_NHEX"); break;
+ case NFA_OCTAL:
+ STRCPY(code, "NFA_OCTAL"); break;
+ case NFA_NOCTAL:
+ STRCPY(code, "NFA_NOCTAL"); break;
+ case NFA_WORD:
+ STRCPY(code, "NFA_WORD"); break;
+ case NFA_NWORD:
+ STRCPY(code, "NFA_NWORD"); break;
+ case NFA_HEAD:
+ STRCPY(code, "NFA_HEAD"); break;
+ case NFA_NHEAD:
+ STRCPY(code, "NFA_NHEAD"); break;
+ case NFA_ALPHA:
+ STRCPY(code, "NFA_ALPHA"); break;
+ case NFA_NALPHA:
+ STRCPY(code, "NFA_NALPHA"); break;
+ case NFA_LOWER:
+ STRCPY(code, "NFA_LOWER"); break;
+ case NFA_NLOWER:
+ STRCPY(code, "NFA_NLOWER"); break;
+ case NFA_UPPER:
+ STRCPY(code, "NFA_UPPER"); break;
+ case NFA_NUPPER:
+ STRCPY(code, "NFA_NUPPER"); break;
+ case NFA_LOWER_IC:
+ STRCPY(code, "NFA_LOWER_IC"); break;
+ case NFA_NLOWER_IC:
+ STRCPY(code, "NFA_NLOWER_IC"); break;
+ case NFA_UPPER_IC:
+ STRCPY(code, "NFA_UPPER_IC"); break;
+ case NFA_NUPPER_IC:
+ STRCPY(code, "NFA_NUPPER_IC"); break;
+
+ default:
+ STRCPY(code, "CHAR(x)");
+ code[5] = c;
+ }
+
+ if (addnl == true) {
+ STRCAT(code, " + NEWLINE ");
+ }
+}
+
+static FILE *log_fd;
+static const uint8_t e_log_open_failed[] =
+ N_("Could not open temporary log file for writing, displaying on stderr... ");
+
+// Print the postfix notation of the current regexp.
+static void nfa_postfix_dump(uint8_t *expr, int retval)
+{
+ int *p;
+ FILE *f;
+
+ f = fopen(NFA_REGEXP_DUMP_LOG, "a");
+ if (f == NULL) {
+ return;
+ }
+
+ fprintf(f, "\n-------------------------\n");
+ if (retval == FAIL) {
+ fprintf(f, ">>> NFA engine failed... \n");
+ } else if (retval == OK) {
+ fprintf(f, ">>> NFA engine succeeded !\n");
+ }
+ fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr);
+ for (p = post_start; *p && p < post_ptr; p++) {
+ nfa_set_code(*p);
+ fprintf(f, "%s, ", code);
+ }
+ fprintf(f, "\"\nPostfix notation (int): ");
+ for (p = post_start; *p && p < post_ptr; p++) {
+ fprintf(f, "%d ", *p);
+ }
+ fprintf(f, "\n\n");
+ fclose(f);
+}
+
+// Print the NFA starting with a root node "state".
+static void nfa_print_state(FILE *debugf, nfa_state_T *state)
+{
+ garray_T indent;
+
+ ga_init(&indent, 1, 64);
+ ga_append(&indent, '\0');
+ nfa_print_state2(debugf, state, &indent);
+ ga_clear(&indent);
+}
+
+static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent)
+{
+ uint8_t *p;
+
+ if (state == NULL) {
+ return;
+ }
+
+ fprintf(debugf, "(%2d)", abs(state->id));
+
+ // Output indent
+ p = (uint8_t *)indent->ga_data;
+ if (indent->ga_len >= 3) {
+ int last = indent->ga_len - 3;
+ uint8_t save[2];
+
+ strncpy(save, &p[last], 2); // NOLINT(runtime/printf)
+ memcpy(&p[last], "+-", 2);
+ fprintf(debugf, " %s", p);
+ strncpy(&p[last], save, 2); // NOLINT(runtime/printf)
+ } else {
+ fprintf(debugf, " %s", p);
+ }
+
+ nfa_set_code(state->c);
+ fprintf(debugf, "%s (%d) (id=%d) val=%d\n",
+ code,
+ state->c,
+ abs(state->id),
+ state->val);
+ if (state->id < 0) {
+ return;
+ }
+
+ state->id = abs(state->id) * -1;
+
+ // grow indent for state->out
+ indent->ga_len -= 1;
+ if (state->out1) {
+ ga_concat(indent, (uint8_t *)"| ");
+ } else {
+ ga_concat(indent, (uint8_t *)" ");
+ }
+ ga_append(indent, NUL);
+
+ nfa_print_state2(debugf, state->out, indent);
+
+ // replace last part of indent for state->out1
+ indent->ga_len -= 3;
+ ga_concat(indent, (uint8_t *)" ");
+ ga_append(indent, NUL);
+
+ nfa_print_state2(debugf, state->out1, indent);
+
+ // shrink indent
+ indent->ga_len -= 3;
+ ga_append(indent, NUL);
+}
+
+// Print the NFA state machine.
+static void nfa_dump(nfa_regprog_T *prog)
+{
+ FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a");
+
+ if (debugf == NULL) {
+ return;
+ }
+
+ nfa_print_state(debugf, prog->start);
+
+ if (prog->reganch) {
+ fprintf(debugf, "reganch: %d\n", prog->reganch);
+ }
+ if (prog->regstart != NUL) {
+ fprintf(debugf, "regstart: %c (decimal: %d)\n",
+ prog->regstart, prog->regstart);
+ }
+ if (prog->match_text != NULL) {
+ fprintf(debugf, "match_text: \"%s\"\n", prog->match_text);
+ }
+
+ fclose(debugf);
+}
+#endif // REGEXP_DEBUG
+
+// Parse r.e. @expr and convert it into postfix form.
+// Return the postfix string on success, NULL otherwise.
+static int *re2post(void)
+{
+ if (nfa_reg(REG_NOPAREN) == FAIL) {
+ return NULL;
+ }
+ EMIT(NFA_MOPEN);
+ return post_start;
+}
+
+// NB. Some of the code below is inspired by Russ's.
+
+// Represents an NFA state plus zero or one or two arrows exiting.
+// if c == MATCH, no arrows out; matching state.
+// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
+// If c < 256, labeled arrow with character c to out.
+
+static nfa_state_T *state_ptr; // points to nfa_prog->state
+
+// Allocate and initialize nfa_state_T.
+static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
+{
+ nfa_state_T *s;
+
+ if (istate >= nstate) {
+ return NULL;
+ }
+
+ s = &state_ptr[istate++];
+
+ s->c = c;
+ s->out = out;
+ s->out1 = out1;
+ s->val = 0;
+
+ s->id = istate;
+ s->lastlist[0] = 0;
+ s->lastlist[1] = 0;
+
+ return s;
+}
+
+// A partially built NFA without the matching state filled in.
+// Frag_T.start points at the start state.
+// Frag_T.out is a list of places that need to be set to the
+// next state for this fragment.
+
+// Initialize a Frag_T struct and return it.
+static Frag_T frag(nfa_state_T *start, Ptrlist *out)
+{
+ Frag_T n;
+
+ n.start = start;
+ n.out = out;
+ return n;
+}
+
+// Create singleton list containing just outp.
+static Ptrlist *list1(nfa_state_T **outp)
+{
+ Ptrlist *l;
+
+ l = (Ptrlist *)outp;
+ l->next = NULL;
+ return l;
+}
+
+// Patch the list of states at out to point to start.
+static void patch(Ptrlist *l, nfa_state_T *s)
+{
+ Ptrlist *next;
+
+ for (; l; l = next) {
+ next = l->next;
+ l->s = s;
+ }
+}
+
+// Join the two lists l1 and l2, returning the combination.
+static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
+{
+ Ptrlist *oldl1;
+
+ oldl1 = l1;
+ while (l1->next) {
+ l1 = l1->next;
+ }
+ l1->next = l2;
+ return oldl1;
+}
+
+// Stack used for transforming postfix form into NFA.
+static Frag_T empty;
+
+static void st_error(int *postfix, int *end, int *p)
+{
+#ifdef NFA_REGEXP_ERROR_LOG
+ FILE *df;
+ int *p2;
+
+ df = fopen(NFA_REGEXP_ERROR_LOG, "a");
+ if (df) {
+ fprintf(df, "Error popping the stack!\n");
+# ifdef REGEXP_DEBUG
+ fprintf(df, "Current regexp is \"%s\"\n", nfa_regengine.expr);
+# endif
+ fprintf(df, "Postfix form is: ");
+# ifdef REGEXP_DEBUG
+ for (p2 = postfix; p2 < end; p2++) {
+ nfa_set_code(*p2);
+ fprintf(df, "%s, ", code);
+ }
+ nfa_set_code(*p);
+ fprintf(df, "\nCurrent position is: ");
+ for (p2 = postfix; p2 <= p; p2++) {
+ nfa_set_code(*p2);
+ fprintf(df, "%s, ", code);
+ }
+# else
+ for (p2 = postfix; p2 < end; p2++) {
+ fprintf(df, "%d, ", *p2);
+ }
+ fprintf(df, "\nCurrent position is: ");
+ for (p2 = postfix; p2 <= p; p2++) {
+ fprintf(df, "%d, ", *p2);
+ }
+# endif
+ fprintf(df, "\n--------------------------\n");
+ fclose(df);
+ }
+#endif
+ emsg(_("E874: (NFA) Could not pop the stack!"));
+}
+
+// Push an item onto the stack.
+static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
+{
+ Frag_T *stackp = *p;
+
+ if (stackp >= stack_end) {
+ return;
+ }
+ *stackp = s;
+ *p = *p + 1;
+}
+
+// Pop an item from the stack.
+static Frag_T st_pop(Frag_T **p, Frag_T *stack)
+{
+ Frag_T *stackp;
+
+ *p = *p - 1;
+ stackp = *p;
+ if (stackp < stack) {
+ return empty;
+ }
+ return **p;
+}
+
+// Estimate the maximum byte length of anything matching "state".
+// When unknown or unlimited return -1.
+static int nfa_max_width(nfa_state_T *startstate, int depth)
+{
+ int l, r;
+ nfa_state_T *state = startstate;
+ int len = 0;
+
+ // detect looping in a NFA_SPLIT
+ if (depth > 4) {
+ return -1;
+ }
+
+ while (state != NULL) {
+ switch (state->c) {
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ // the end, return what we have
+ return len;
+
+ case NFA_SPLIT:
+ // two alternatives, use the maximum
+ l = nfa_max_width(state->out, depth + 1);
+ r = nfa_max_width(state->out1, depth + 1);
+ if (l < 0 || r < 0) {
+ return -1;
+ }
+ return len + (l > r ? l : r);
+
+ case NFA_ANY:
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL:
+ // Matches some character, including composing chars.
+ len += MB_MAXBYTES;
+ if (state->c != NFA_ANY) {
+ // Skip over the characters.
+ state = state->out1->out;
+ continue;
+ }
+ break;
+
+ case NFA_DIGIT:
+ case NFA_WHITE:
+ case NFA_HEX:
+ case NFA_OCTAL:
+ // ascii
+ len++;
+ break;
+
+ case NFA_IDENT:
+ case NFA_SIDENT:
+ case NFA_KWORD:
+ case NFA_SKWORD:
+ case NFA_FNAME:
+ case NFA_SFNAME:
+ case NFA_PRINT:
+ case NFA_SPRINT:
+ case NFA_NWHITE:
+ case NFA_NDIGIT:
+ case NFA_NHEX:
+ case NFA_NOCTAL:
+ case NFA_WORD:
+ case NFA_NWORD:
+ case NFA_HEAD:
+ case NFA_NHEAD:
+ case NFA_ALPHA:
+ case NFA_NALPHA:
+ case NFA_LOWER:
+ case NFA_NLOWER:
+ case NFA_UPPER:
+ case NFA_NUPPER:
+ case NFA_LOWER_IC:
+ case NFA_NLOWER_IC:
+ case NFA_UPPER_IC:
+ case NFA_NUPPER_IC:
+ case NFA_ANY_COMPOSING:
+ // possibly non-ascii
+ len += 3;
+ break;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ // zero-width, out1 points to the END state
+ state = state->out1->out;
+ continue;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ case NFA_NEWL:
+ case NFA_SKIP:
+ // unknown width
+ return -1;
+
+ case NFA_BOL:
+ case NFA_EOL:
+ case NFA_BOF:
+ case NFA_EOF:
+ case NFA_BOW:
+ case NFA_EOW:
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_NOPEN:
+ case NFA_NCLOSE:
+
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+ case NFA_VISUAL:
+ case NFA_LNUM:
+ case NFA_CURSOR:
+ case NFA_COL:
+ case NFA_VCOL:
+ case NFA_MARK:
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ case NFA_OPT_CHARS:
+ case NFA_EMPTY:
+ case NFA_START_PATTERN:
+ case NFA_END_PATTERN:
+ case NFA_COMPOSING:
+ case NFA_END_COMPOSING:
+ // zero-width
+ break;
+
+ default:
+ if (state->c < 0) {
+ // don't know what this is
+ return -1;
+ }
+ // normal character
+ len += utf_char2len(state->c);
+ break;
+ }
+
+ // normal way to continue
+ state = state->out;
+ }
+
+ // unrecognized, "cannot happen"
+ return -1;
+}
+
+// Convert a postfix form into its equivalent NFA.
+// Return the NFA start state on success, NULL otherwise.
+static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
+{
+ int *p;
+ int mopen;
+ int mclose;
+ Frag_T *stack = NULL;
+ Frag_T *stackp = NULL;
+ Frag_T *stack_end = NULL;
+ Frag_T e1;
+ Frag_T e2;
+ Frag_T e;
+ nfa_state_T *s;
+ nfa_state_T *s1;
+ nfa_state_T *matchstate;
+ nfa_state_T *ret = NULL;
+
+ if (postfix == NULL) {
+ return NULL;
+ }
+
+#define PUSH(s) st_push((s), &stackp, stack_end)
+#define POP() st_pop(&stackp, stack); \
+ if (stackp < stack) { \
+ st_error(postfix, end, p); \
+ xfree(stack); \
+ return NULL; \
+ }
+
+ if (nfa_calc_size == false) {
+ // Allocate space for the stack. Max states on the stack: "nstate".
+ stack = xmalloc((size_t)(nstate + 1) * sizeof(Frag_T));
+ stackp = stack;
+ stack_end = stack + (nstate + 1);
+ }
+
+ for (p = postfix; p < end; p++) {
+ switch (*p) {
+ case NFA_CONCAT:
+ // Concatenation.
+ // Pay attention: this operator does not exist in the r.e. itself
+ // (it is implicit, really). It is added when r.e. is translated
+ // to postfix form in re2post().
+ if (nfa_calc_size == true) {
+ // nstate += 0;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ patch(e1.out, e2.start);
+ PUSH(frag(e1.start, e2.out));
+ break;
+
+ case NFA_OR:
+ // Alternation
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ s = alloc_state(NFA_SPLIT, e1.start, e2.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e1.out, e2.out)));
+ break;
+
+ case NFA_STAR:
+ // Zero or more, prefer more
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ PUSH(frag(s, list1(&s->out1)));
+ break;
+
+ case NFA_STAR_NONGREEDY:
+ // Zero or more, prefer zero
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, NULL, e.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ PUSH(frag(s, list1(&s->out)));
+ break;
+
+ case NFA_QUEST:
+ // one or zero atoms=> greedy match
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e.out, list1(&s->out1))));
+ break;
+
+ case NFA_QUEST_NONGREEDY:
+ // zero or one atoms => non-greedy match
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_SPLIT, NULL, e.start);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, append(e.out, list1(&s->out))));
+ break;
+
+ case NFA_END_COLL:
+ case NFA_END_NEG_COLL:
+ // On the stack is the sequence starting with NFA_START_COLL or
+ // NFA_START_NEG_COLL and all possible characters. Patch it to
+ // add the output to the start.
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ e = POP();
+ s = alloc_state(NFA_END_COLL, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ patch(e.out, s);
+ e.start->out1 = s;
+ PUSH(frag(e.start, list1(&s->out)));
+ break;
+
+ case NFA_RANGE:
+ // Before this are two characters, the low and high end of a
+ // range. Turn them into two states with MIN and MAX.
+ if (nfa_calc_size == true) {
+ // nstate += 0;
+ break;
+ }
+ e2 = POP();
+ e1 = POP();
+ e2.start->val = e2.start->c;
+ e2.start->c = NFA_RANGE_MAX;
+ e1.start->val = e1.start->c;
+ e1.start->c = NFA_RANGE_MIN;
+ patch(e1.out, e2.start);
+ PUSH(frag(e1.start, e2.out));
+ break;
+
+ case NFA_EMPTY:
+ // 0-length, used in a repetition with max/min count of 0
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ s = alloc_state(NFA_EMPTY, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, list1(&s->out)));
+ break;
+
+ case NFA_OPT_CHARS: {
+ int n;
+
+ // \%[abc] implemented as:
+ // NFA_SPLIT
+ // +-CHAR(a)
+ // | +-NFA_SPLIT
+ // | +-CHAR(b)
+ // | | +-NFA_SPLIT
+ // | | +-CHAR(c)
+ // | | | +-next
+ // | | +- next
+ // | +- next
+ // +- next
+ n = *++p; // get number of characters
+ if (nfa_calc_size == true) {
+ nstate += n;
+ break;
+ }
+ s = NULL; // avoid compiler warning
+ e1.out = NULL; // stores list with out1's
+ s1 = NULL; // previous NFA_SPLIT to connect to
+ while (n-- > 0) {
+ e = POP(); // get character
+ s = alloc_state(NFA_SPLIT, e.start, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ if (e1.out == NULL) {
+ e1 = e;
+ }
+ patch(e.out, s1);
+ append(e1.out, list1(&s->out1));
+ s1 = s;
+ }
+ PUSH(frag(s, e1.out));
+ break;
+ }
+
+ case NFA_PREV_ATOM_NO_WIDTH:
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ case NFA_PREV_ATOM_LIKE_PATTERN: {
+ int before = (*p == NFA_PREV_ATOM_JUST_BEFORE
+ || *p == NFA_PREV_ATOM_JUST_BEFORE_NEG);
+ int pattern = (*p == NFA_PREV_ATOM_LIKE_PATTERN);
+ int start_state;
+ int end_state;
+ int n = 0;
+ nfa_state_T *zend;
+ nfa_state_T *skip;
+
+ switch (*p) {
+ case NFA_PREV_ATOM_NO_WIDTH:
+ start_state = NFA_START_INVISIBLE;
+ end_state = NFA_END_INVISIBLE;
+ break;
+ case NFA_PREV_ATOM_NO_WIDTH_NEG:
+ start_state = NFA_START_INVISIBLE_NEG;
+ end_state = NFA_END_INVISIBLE_NEG;
+ break;
+ case NFA_PREV_ATOM_JUST_BEFORE:
+ start_state = NFA_START_INVISIBLE_BEFORE;
+ end_state = NFA_END_INVISIBLE;
+ break;
+ case NFA_PREV_ATOM_JUST_BEFORE_NEG:
+ start_state = NFA_START_INVISIBLE_BEFORE_NEG;
+ end_state = NFA_END_INVISIBLE_NEG;
+ break;
+ default: // NFA_PREV_ATOM_LIKE_PATTERN:
+ start_state = NFA_START_PATTERN;
+ end_state = NFA_END_PATTERN;
+ break;
+ }
+
+ if (before) {
+ n = *++p; // get the count
+ }
+ // The \@= operator: match the preceding atom with zero width.
+ // The \@! operator: no match for the preceding atom.
+ // The \@<= operator: match for the preceding atom.
+ // The \@<! operator: no match for the preceding atom.
+ // Surrounds the preceding atom with START_INVISIBLE and
+ // END_INVISIBLE, similarly to MOPEN.
+
+ if (nfa_calc_size == true) {
+ nstate += pattern ? 4 : 2;
+ break;
+ }
+ e = POP();
+ s1 = alloc_state(end_state, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+
+ s = alloc_state(start_state, e.start, s1);
+ if (s == NULL) {
+ goto theend;
+ }
+ if (pattern) {
+ // NFA_ZEND -> NFA_END_PATTERN -> NFA_SKIP -> what follows.
+ skip = alloc_state(NFA_SKIP, NULL, NULL);
+ if (skip == NULL) {
+ goto theend;
+ }
+ zend = alloc_state(NFA_ZEND, s1, NULL);
+ if (zend == NULL) {
+ goto theend;
+ }
+ s1->out = skip;
+ patch(e.out, zend);
+ PUSH(frag(s, list1(&skip->out)));
+ } else {
+ patch(e.out, s1);
+ PUSH(frag(s, list1(&s1->out)));
+ if (before) {
+ if (n <= 0) {
+ // See if we can guess the maximum width, it avoids a
+ // lot of pointless tries.
+ n = nfa_max_width(e.start, 0);
+ }
+ s->val = n; // store the count
+ }
+ }
+ break;
+ }
+
+ case NFA_COMPOSING: // char with composing char
+ FALLTHROUGH;
+
+ case NFA_MOPEN: // \( \) Submatch
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN: // \z( \) Submatch
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN: // \%( \) "Invisible Submatch"
+ if (nfa_calc_size == true) {
+ nstate += 2;
+ break;
+ }
+
+ mopen = *p;
+ switch (*p) {
+ case NFA_NOPEN:
+ mclose = NFA_NCLOSE; break;
+ case NFA_ZOPEN:
+ mclose = NFA_ZCLOSE; break;
+ case NFA_ZOPEN1:
+ mclose = NFA_ZCLOSE1; break;
+ case NFA_ZOPEN2:
+ mclose = NFA_ZCLOSE2; break;
+ case NFA_ZOPEN3:
+ mclose = NFA_ZCLOSE3; break;
+ case NFA_ZOPEN4:
+ mclose = NFA_ZCLOSE4; break;
+ case NFA_ZOPEN5:
+ mclose = NFA_ZCLOSE5; break;
+ case NFA_ZOPEN6:
+ mclose = NFA_ZCLOSE6; break;
+ case NFA_ZOPEN7:
+ mclose = NFA_ZCLOSE7; break;
+ case NFA_ZOPEN8:
+ mclose = NFA_ZCLOSE8; break;
+ case NFA_ZOPEN9:
+ mclose = NFA_ZCLOSE9; break;
+ case NFA_COMPOSING:
+ mclose = NFA_END_COMPOSING; break;
+ default:
+ // NFA_MOPEN, NFA_MOPEN1 .. NFA_MOPEN9
+ mclose = *p + NSUBEXP;
+ break;
+ }
+
+ // Allow "NFA_MOPEN" as a valid postfix representation for
+ // the empty regexp "". In this case, the NFA will be
+ // NFA_MOPEN -> NFA_MCLOSE. Note that this also allows
+ // empty groups of parenthesis, and empty mbyte chars
+ if (stackp == stack) {
+ s = alloc_state(mopen, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s1 = alloc_state(mclose, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(list1(&s->out), s1);
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+ }
+
+ // At least one node was emitted before NFA_MOPEN, so
+ // at least one node will be between NFA_MOPEN and NFA_MCLOSE
+ e = POP();
+ s = alloc_state(mopen, e.start, NULL); // `('
+ if (s == NULL) {
+ goto theend;
+ }
+
+ s1 = alloc_state(mclose, NULL, NULL); // `)'
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(e.out, s1);
+
+ if (mopen == NFA_COMPOSING) {
+ // COMPOSING->out1 = END_COMPOSING
+ patch(list1(&s->out1), s1);
+ }
+
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ if (nfa_calc_size == true) {
+ nstate += 2;
+ break;
+ }
+ s = alloc_state(*p, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s1 = alloc_state(NFA_SKIP, NULL, NULL);
+ if (s1 == NULL) {
+ goto theend;
+ }
+ patch(list1(&s->out), s1);
+ PUSH(frag(s, list1(&s1->out)));
+ break;
+
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT: {
+ int n = *++p; // lnum, col or mark name
+
+ if (nfa_calc_size == true) {
+ nstate += 1;
+ break;
+ }
+ s = alloc_state(p[-1], NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ s->val = n;
+ PUSH(frag(s, list1(&s->out)));
+ break;
+ }
+
+ case NFA_ZSTART:
+ case NFA_ZEND:
+ default:
+ // Operands
+ if (nfa_calc_size == true) {
+ nstate++;
+ break;
+ }
+ s = alloc_state(*p, NULL, NULL);
+ if (s == NULL) {
+ goto theend;
+ }
+ PUSH(frag(s, list1(&s->out)));
+ break;
+ } // switch(*p)
+ } // for(p = postfix; *p; ++p)
+
+ if (nfa_calc_size == true) {
+ nstate++;
+ goto theend; // Return value when counting size is ignored anyway
+ }
+
+ e = POP();
+ if (stackp != stack) {
+ xfree(stack);
+ EMSG_RET_NULL(_("E875: (NFA regexp) (While converting from postfix to NFA),"
+ "too many states left on stack"));
+ }
+
+ if (istate >= nstate) {
+ xfree(stack);
+ EMSG_RET_NULL(_("E876: (NFA regexp) "
+ "Not enough space to store the whole NFA "));
+ }
+
+ matchstate = &state_ptr[istate++]; // the match state
+ matchstate->c = NFA_MATCH;
+ matchstate->out = matchstate->out1 = NULL;
+ matchstate->id = 0;
+
+ patch(e.out, matchstate);
+ ret = e.start;
+
+theend:
+ xfree(stack);
+ return ret;
+
+#undef POP1
+#undef PUSH1
+#undef POP2
+#undef PUSH2
+#undef POP
+#undef PUSH
+}
+
+// After building the NFA program, inspect it to add optimization hints.
+static void nfa_postprocess(nfa_regprog_T *prog)
+{
+ int i;
+ int c;
+
+ for (i = 0; i < prog->nstate; i++) {
+ c = prog->state[i].c;
+ if (c == NFA_START_INVISIBLE
+ || c == NFA_START_INVISIBLE_NEG
+ || c == NFA_START_INVISIBLE_BEFORE
+ || c == NFA_START_INVISIBLE_BEFORE_NEG) {
+ int directly;
+
+ // Do it directly when what follows is possibly the end of the
+ // match.
+ if (match_follows(prog->state[i].out1->out, 0)) {
+ directly = true;
+ } else {
+ int ch_invisible = failure_chance(prog->state[i].out, 0);
+ int ch_follows = failure_chance(prog->state[i].out1->out, 0);
+
+ // Postpone when the invisible match is expensive or has a
+ // lower chance of failing.
+ if (c == NFA_START_INVISIBLE_BEFORE
+ || c == NFA_START_INVISIBLE_BEFORE_NEG) {
+ // "before" matches are very expensive when
+ // unbounded, always prefer what follows then,
+ // unless what follows will always match.
+ // Otherwise strongly prefer what follows.
+ if (prog->state[i].val <= 0 && ch_follows > 0) {
+ directly = false;
+ } else {
+ directly = ch_follows * 10 < ch_invisible;
+ }
+ } else {
+ // normal invisible, first do the one with the
+ // highest failure chance
+ directly = ch_follows < ch_invisible;
+ }
+ }
+ if (directly) {
+ // switch to the _FIRST state
+ prog->state[i].c++;
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////
+// NFA execution code.
+/////////////////////////////////////////////////////////////////
+
+// Values for done in nfa_pim_T.
+#define NFA_PIM_UNUSED 0 // pim not used
+#define NFA_PIM_TODO 1 // pim not done yet
+#define NFA_PIM_MATCH 2 // pim executed, matches
+#define NFA_PIM_NOMATCH 3 // pim executed, no match
+
+#ifdef REGEXP_DEBUG
+static void log_subsexpr(regsubs_T *subs)
+{
+ log_subexpr(&subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ log_subexpr(&subs->synt);
+ }
+}
+
+static void log_subexpr(regsub_T *sub)
+{
+ int j;
+
+ for (j = 0; j < sub->in_use; j++) {
+ if (REG_MULTI) {
+ fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n",
+ j,
+ sub->list.multi[j].start_col,
+ (int)sub->list.multi[j].start_lnum,
+ sub->list.multi[j].end_col,
+ (int)sub->list.multi[j].end_lnum);
+ } else {
+ char *s = (char *)sub->list.line[j].start;
+ char *e = (char *)sub->list.line[j].end;
+
+ fprintf(log_fd, "*** group %d, start: \"%s\", end: \"%s\"\n",
+ j,
+ s == NULL ? "NULL" : s,
+ e == NULL ? "NULL" : e);
+ }
+ }
+}
+
+static char *pim_info(const nfa_pim_T *pim)
+{
+ static char buf[30];
+
+ if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
+ buf[0] = NUL;
+ } else {
+ snprintf(buf, sizeof(buf), " PIM col %d",
+ REG_MULTI
+ ? (int)pim->end.pos.col
+ : (int)(pim->end.ptr - rex.input));
+ }
+ return buf;
+}
+
+#endif
+
+// Used during execution: whether a match has been found.
+static int nfa_match;
+static proftime_T *nfa_time_limit;
+static int *nfa_timed_out;
+static int nfa_time_count;
+
+// Copy postponed invisible match info from "from" to "to".
+static void copy_pim(nfa_pim_T *to, nfa_pim_T *from)
+{
+ to->result = from->result;
+ to->state = from->state;
+ copy_sub(&to->subs.norm, &from->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&to->subs.synt, &from->subs.synt);
+ }
+ to->end = from->end;
+}
+
+static void clear_sub(regsub_T *sub)
+{
+ if (REG_MULTI) {
+ // Use 0xff to set lnum to -1
+ memset(sub->list.multi, 0xff, sizeof(struct multipos) * (size_t)rex.nfa_nsubexpr);
+ } else {
+ memset(sub->list.line, 0, sizeof(struct linepos) * (size_t)rex.nfa_nsubexpr);
+ }
+ sub->in_use = 0;
+}
+
+// Copy the submatches from "from" to "to".
+static void copy_sub(regsub_T *to, regsub_T *from)
+{
+ to->in_use = from->in_use;
+ if (from->in_use <= 0) {
+ return;
+ }
+
+ // Copy the match start and end positions.
+ if (REG_MULTI) {
+ memmove(&to->list.multi[0], &from->list.multi[0],
+ sizeof(struct multipos) * (size_t)from->in_use);
+ to->orig_start_col = from->orig_start_col;
+ } else {
+ memmove(&to->list.line[0], &from->list.line[0],
+ sizeof(struct linepos) * (size_t)from->in_use);
+ }
+}
+
+// Like copy_sub() but exclude the main match.
+static void copy_sub_off(regsub_T *to, regsub_T *from)
+{
+ if (to->in_use < from->in_use) {
+ to->in_use = from->in_use;
+ }
+ if (from->in_use <= 1) {
+ return;
+ }
+
+ // Copy the match start and end positions.
+ if (REG_MULTI) {
+ memmove(&to->list.multi[1], &from->list.multi[1],
+ sizeof(struct multipos) * (size_t)(from->in_use - 1));
+ } else {
+ memmove(&to->list.line[1], &from->list.line[1],
+ sizeof(struct linepos) * (size_t)(from->in_use - 1));
+ }
+}
+
+// Like copy_sub() but only do the end of the main match if \ze is present.
+static void copy_ze_off(regsub_T *to, regsub_T *from)
+{
+ if (!rex.nfa_has_zend) {
+ return;
+ }
+
+ if (REG_MULTI) {
+ if (from->list.multi[0].end_lnum >= 0) {
+ to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
+ to->list.multi[0].end_col = from->list.multi[0].end_col;
+ }
+ } else {
+ if (from->list.line[0].end != NULL) {
+ to->list.line[0].end = from->list.line[0].end;
+ }
+ }
+}
+
+// Return true if "sub1" and "sub2" have the same start positions.
+// When using back-references also check the end position.
+static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
+{
+ int i;
+ int todo;
+ linenr_T s1;
+ linenr_T s2;
+ uint8_t *sp1;
+ uint8_t *sp2;
+
+ todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use;
+ if (REG_MULTI) {
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
+ s1 = sub1->list.multi[i].start_lnum;
+ } else {
+ s1 = -1;
+ }
+ if (i < sub2->in_use) {
+ s2 = sub2->list.multi[i].start_lnum;
+ } else {
+ s2 = -1;
+ }
+ if (s1 != s2) {
+ return false;
+ }
+ if (s1 != -1 && sub1->list.multi[i].start_col
+ != sub2->list.multi[i].start_col) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
+ if (i < sub1->in_use) {
+ s1 = sub1->list.multi[i].end_lnum;
+ } else {
+ s1 = -1;
+ }
+ if (i < sub2->in_use) {
+ s2 = sub2->list.multi[i].end_lnum;
+ } else {
+ s2 = -1;
+ }
+ if (s1 != s2) {
+ return false;
+ }
+ if (s1 != -1
+ && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
+ return false;
+ }
+ }
+ }
+ } else {
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
+ sp1 = sub1->list.line[i].start;
+ } else {
+ sp1 = NULL;
+ }
+ if (i < sub2->in_use) {
+ sp2 = sub2->list.line[i].start;
+ } else {
+ sp2 = NULL;
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
+ if (i < sub1->in_use) {
+ sp1 = sub1->list.line[i].end;
+ } else {
+ sp1 = NULL;
+ }
+ if (i < sub2->in_use) {
+ sp2 = sub2->list.line[i].end;
+ } else {
+ sp2 = NULL;
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+#ifdef REGEXP_DEBUG
+static void open_debug_log(TriState result)
+{
+ log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (log_fd == NULL) {
+ emsg(_(e_log_open_failed));
+ log_fd = stderr;
+ }
+
+ fprintf(log_fd, "****************************\n");
+ fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
+ fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE");
+ fprintf(log_fd, "****************************\n");
+}
+
+static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim)
+{
+ int col;
+
+ if (sub->in_use <= 0) {
+ col = -1;
+ } else if (REG_MULTI) {
+ col = sub->list.multi[0].start_col;
+ } else {
+ col = (int)(sub->list.line[0].start - rex.line);
+ }
+ nfa_set_code(state->c);
+ if (log_fd == NULL) {
+ open_debug_log(kNone);
+ }
+ fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n",
+ action, abs(state->id), lid, state->c, code, col,
+ pim_info(pim));
+}
+
+#endif
+
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+/// @param pim postponed match or NULL
+///
+/// @return true if the same state is already in list "l" with the same
+/// positions as "subs".
+static bool has_state_with_pos(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
+{
+ for (int i = 0; i < l->n; i++) {
+ nfa_thread_T *thread = &l->t[i];
+ if (thread->state->id == state->id
+ && sub_equal(&thread->subs.norm, &subs->norm)
+ && (!rex.nfa_has_zsubexpr
+ || sub_equal(&thread->subs.synt, &subs->synt))
+ && pim_equal(&thread->pim, pim)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Return true if "one" and "two" are equal. That includes when both are not
+// set.
+static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two)
+{
+ const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
+ const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
+
+ if (one_unused) {
+ // one is unused: equal when two is also unused
+ return two_unused;
+ }
+ if (two_unused) {
+ // one is used and two is not: not equal
+ return false;
+ }
+ // compare the state id
+ if (one->state->id != two->state->id) {
+ return false;
+ }
+ // compare the position
+ if (REG_MULTI) {
+ return one->end.pos.lnum == two->end.pos.lnum
+ && one->end.pos.col == two->end.pos.col;
+ }
+ return one->end.ptr == two->end.ptr;
+}
+
+// Return true if "state" leads to a NFA_MATCH without advancing the input.
+static bool match_follows(const nfa_state_T *startstate, int depth)
+ FUNC_ATTR_NONNULL_ALL
+{
+ const nfa_state_T *state = startstate;
+
+ // avoid too much recursion
+ if (depth > 10) {
+ return false;
+ }
+ while (state != NULL) {
+ switch (state->c) {
+ case NFA_MATCH:
+ case NFA_MCLOSE:
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ case NFA_END_PATTERN:
+ return true;
+
+ case NFA_SPLIT:
+ return match_follows(state->out, depth + 1)
+ || match_follows(state->out1, depth + 1);
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ case NFA_COMPOSING:
+ // skip ahead to next state
+ state = state->out1->out;
+ continue;
+
+ case NFA_ANY:
+ case NFA_ANY_COMPOSING:
+ case NFA_IDENT:
+ case NFA_SIDENT:
+ case NFA_KWORD:
+ case NFA_SKWORD:
+ case NFA_FNAME:
+ case NFA_SFNAME:
+ case NFA_PRINT:
+ case NFA_SPRINT:
+ case NFA_WHITE:
+ case NFA_NWHITE:
+ case NFA_DIGIT:
+ case NFA_NDIGIT:
+ case NFA_HEX:
+ case NFA_NHEX:
+ case NFA_OCTAL:
+ case NFA_NOCTAL:
+ case NFA_WORD:
+ case NFA_NWORD:
+ case NFA_HEAD:
+ case NFA_NHEAD:
+ case NFA_ALPHA:
+ case NFA_NALPHA:
+ case NFA_LOWER:
+ case NFA_NLOWER:
+ case NFA_UPPER:
+ case NFA_NUPPER:
+ case NFA_LOWER_IC:
+ case NFA_NLOWER_IC:
+ case NFA_UPPER_IC:
+ case NFA_NUPPER_IC:
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL:
+ case NFA_NEWL:
+ // state will advance input
+ return false;
+
+ default:
+ if (state->c > 0) {
+ // state will advance input
+ return false;
+ }
+ // Others: zero-width or possibly zero-width, might still find
+ // a match at the same position, keep looking.
+ break;
+ }
+ state = state->out;
+ }
+ return false;
+}
+
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+///
+/// @return true if "state" is already in list "l".
+static bool state_in_list(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (state->lastlist[nfa_ll_index] == l->id) {
+ if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Offset used for "off" by addstate_here().
+#define ADDSTATE_HERE_OFFSET 10
+
+/// Add "state" and possibly what follows to state list ".".
+///
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs_arg pointers to subexpressions
+/// @param pim postponed look-behind match
+/// @param off_arg byte offset, when -1 go to next line
+///
+/// @return "subs_arg", possibly copied into temp_subs.
+/// NULL when recursiveness is too deep.
+static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_arg, nfa_pim_T *pim,
+ int off_arg)
+ FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int subidx;
+ int off = off_arg;
+ int add_here = false;
+ int listindex = 0;
+ int k;
+ int found = false;
+ nfa_thread_T *thread;
+ struct multipos save_multipos;
+ int save_in_use;
+ uint8_t *save_ptr;
+ int i;
+ regsub_T *sub;
+ regsubs_T *subs = subs_arg;
+ static regsubs_T temp_subs;
+#ifdef REGEXP_DEBUG
+ int did_print = false;
+#endif
+ static int depth = 0;
+
+ // This function is called recursively. When the depth is too much we run
+ // out of stack and crash, limit recursiveness here.
+ if (++depth >= 5000 || subs == NULL) {
+ depth--;
+ return NULL;
+ }
+
+ if (off_arg <= -ADDSTATE_HERE_OFFSET) {
+ add_here = true;
+ off = 0;
+ listindex = -(off_arg + ADDSTATE_HERE_OFFSET);
+ }
+
+ switch (state->c) {
+ case NFA_NCLOSE:
+ case NFA_MCLOSE:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_MOPEN:
+ case NFA_ZEND:
+ case NFA_SPLIT:
+ case NFA_EMPTY:
+ // These nodes are not added themselves but their "out" and/or
+ // "out1" may be added below.
+ break;
+
+ case NFA_BOL:
+ case NFA_BOF:
+ // "^" won't match past end-of-line, don't bother trying.
+ // Except when at the end of the line, or when we are going to the
+ // next line for a look-behind match.
+ if (rex.input > rex.line
+ && *rex.input != NUL
+ && (nfa_endp == NULL
+ || !REG_MULTI
+ || rex.lnum == nfa_endp->se_u.pos.lnum)) {
+ goto skip_add;
+ }
+ FALLTHROUGH;
+
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZSTART:
+ // These nodes need to be added so that we can bail out when it
+ // was added to this list before at the same position to avoid an
+ // endless loop for "\(\)*"
+
+ default:
+ if (state->lastlist[nfa_ll_index] == l->id && state->c != NFA_SKIP) {
+ // This state is already in the list, don't add it again,
+ // unless it is an MOPEN that is used for a backreference or
+ // when there is a PIM. For NFA_MATCH check the position,
+ // lower position is preferred.
+ if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
+ && state->c != NFA_MATCH) {
+ // When called from addstate_here() do insert before
+ // existing states.
+ if (add_here) {
+ for (k = 0; k < l->n && k < listindex; k++) {
+ if (l->t[k].state->id == state->id) {
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!add_here || found) {
+skip_add:
+#ifdef REGEXP_DEBUG
+ nfa_set_code(state->c);
+ fprintf(log_fd,
+ "> Not adding state %d to list %d. char %d: %s pim: %s has_pim: %d found: %d\n",
+ abs(state->id), l->id, state->c, code,
+ pim == NULL ? "NULL" : "yes", l->has_pim, found);
+#endif
+ depth--;
+ return subs;
+ }
+ }
+
+ // Do not add the state again when it exists with the same
+ // positions.
+ if (has_state_with_pos(l, state, subs, pim)) {
+ goto skip_add;
+ }
+ }
+
+ // When there are backreferences or PIMs the number of states may
+ // be (a lot) bigger than anticipated.
+ if (l->n == l->len) {
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
+
+ if ((int64_t)(newsize >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ depth--;
+ return NULL;
+ }
+ if (subs != &temp_subs) {
+ // "subs" may point into the current array, need to make a
+ // copy before it becomes invalid.
+ copy_sub(&temp_subs.norm, &subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&temp_subs.synt, &subs->synt);
+ }
+ subs = &temp_subs;
+ }
+
+ nfa_thread_T *const newt = xrealloc(l->t, newsize);
+ l->t = newt;
+ l->len = newlen;
+ }
+
+ // add the state to the list
+ state->lastlist[nfa_ll_index] = l->id;
+ thread = &l->t[l->n++];
+ thread->state = state;
+ if (pim == NULL) {
+ thread->pim.result = NFA_PIM_UNUSED;
+ } else {
+ copy_pim(&thread->pim, pim);
+ l->has_pim = true;
+ }
+ copy_sub(&thread->subs.norm, &subs->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&thread->subs.synt, &subs->synt);
+ }
+#ifdef REGEXP_DEBUG
+ report_state("Adding", &thread->subs.norm, state, l->id, pim);
+ did_print = true;
#endif
+ }
+
+#ifdef REGEXP_DEBUG
+ if (!did_print) {
+ report_state("Processing", &subs->norm, state, l->id, pim);
+ }
+#endif
+ switch (state->c) {
+ case NFA_MATCH:
+ break;
+
+ case NFA_SPLIT:
+ // order matters here
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ subs = addstate(l, state->out1, subs, pim, off_arg);
+ break;
+
+ case NFA_EMPTY:
+ case NFA_NOPEN:
+ case NFA_NCLOSE:
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ break;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZSTART:
+ if (state->c == NFA_ZSTART) {
+ subidx = 0;
+ sub = &subs->norm;
+ } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) {
+ subidx = state->c - NFA_ZOPEN;
+ sub = &subs->synt;
+ } else {
+ subidx = state->c - NFA_MOPEN;
+ sub = &subs->norm;
+ }
+
+ // avoid compiler warnings
+ save_ptr = NULL;
+ CLEAR_FIELD(save_multipos);
+
+ // Set the position (with "off" added) in the subexpression. Save
+ // and restore it when it was in use. Otherwise fill any gap.
+ if (REG_MULTI) {
+ if (subidx < sub->in_use) {
+ save_multipos = sub->list.multi[subidx];
+ save_in_use = -1;
+ } else {
+ save_in_use = sub->in_use;
+ for (i = sub->in_use; i < subidx; i++) {
+ sub->list.multi[i].start_lnum = -1;
+ sub->list.multi[i].end_lnum = -1;
+ }
+ sub->in_use = subidx + 1;
+ }
+ if (off == -1) {
+ sub->list.multi[subidx].start_lnum = rex.lnum + 1;
+ sub->list.multi[subidx].start_col = 0;
+ } else {
+ sub->list.multi[subidx].start_lnum = rex.lnum;
+ sub->list.multi[subidx].start_col =
+ (colnr_T)(rex.input - rex.line + off);
+ }
+ sub->list.multi[subidx].end_lnum = -1;
+ } else {
+ if (subidx < sub->in_use) {
+ save_ptr = sub->list.line[subidx].start;
+ save_in_use = -1;
+ } else {
+ save_in_use = sub->in_use;
+ for (i = sub->in_use; i < subidx; i++) {
+ sub->list.line[i].start = NULL;
+ sub->list.line[i].end = NULL;
+ }
+ sub->in_use = subidx + 1;
+ }
+ sub->list.line[subidx].start = rex.input + off;
+ }
+
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
+ // "subs" may have changed, need to set "sub" again.
+ if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) {
+ sub = &subs->synt;
+ } else {
+ sub = &subs->norm;
+ }
+
+ if (save_in_use == -1) {
+ if (REG_MULTI) {
+ sub->list.multi[subidx] = save_multipos;
+ } else {
+ sub->list.line[subidx].start = save_ptr;
+ }
+ } else {
+ sub->in_use = save_in_use;
+ }
+ break;
+
+ case NFA_MCLOSE:
+ if (rex.nfa_has_zend
+ && (REG_MULTI
+ ? subs->norm.list.multi[0].end_lnum >= 0
+ : subs->norm.list.line[0].end != NULL)) {
+ // Do not overwrite the position set by \ze.
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ break;
+ }
+ FALLTHROUGH;
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_ZEND:
+ if (state->c == NFA_ZEND) {
+ subidx = 0;
+ sub = &subs->norm;
+ } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) {
+ subidx = state->c - NFA_ZCLOSE;
+ sub = &subs->synt;
+ } else {
+ subidx = state->c - NFA_MCLOSE;
+ sub = &subs->norm;
+ }
+
+ // We don't fill in gaps here, there must have been an MOPEN that
+ // has done that.
+ save_in_use = sub->in_use;
+ if (sub->in_use <= subidx) {
+ sub->in_use = subidx + 1;
+ }
+ if (REG_MULTI) {
+ save_multipos = sub->list.multi[subidx];
+ if (off == -1) {
+ sub->list.multi[subidx].end_lnum = rex.lnum + 1;
+ sub->list.multi[subidx].end_col = 0;
+ } else {
+ sub->list.multi[subidx].end_lnum = rex.lnum;
+ sub->list.multi[subidx].end_col =
+ (colnr_T)(rex.input - rex.line + off);
+ }
+ // avoid compiler warnings
+ save_ptr = NULL;
+ } else {
+ save_ptr = sub->list.line[subidx].end;
+ sub->list.line[subidx].end = rex.input + off;
+ // avoid compiler warnings
+ CLEAR_FIELD(save_multipos);
+ }
+
+ subs = addstate(l, state->out, subs, pim, off_arg);
+ if (subs == NULL) {
+ break;
+ }
+ // "subs" may have changed, need to set "sub" again.
+ if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) {
+ sub = &subs->synt;
+ } else {
+ sub = &subs->norm;
+ }
+
+ if (REG_MULTI) {
+ sub->list.multi[subidx] = save_multipos;
+ } else {
+ sub->list.line[subidx].end = save_ptr;
+ }
+ sub->in_use = save_in_use;
+ break;
+ }
+ depth--;
+ return subs;
+}
+
+/// Like addstate(), but the new state(s) are put at position "*ip".
+/// Used for zero-width matches, next state to use is the added one.
+/// This makes sure the order of states to be tried does not change, which
+/// matters for alternatives.
+///
+/// @param l runtime state list
+/// @param state state to update
+/// @param subs pointers to subexpressions
+/// @param pim postponed look-behind match
+static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim,
+ int *ip)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ int tlen = l->n;
+ int count;
+ int listidx = *ip;
+
+ // First add the state(s) at the end, so that we know how many there are.
+ // Pass the listidx as offset (avoids adding another argument to
+ // addstate()).
+ regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
+ if (r == NULL) {
+ return NULL;
+ }
+
+ // when "*ip" was at the end of the list, nothing to do
+ if (listidx + 1 == tlen) {
+ return r;
+ }
+
+ // re-order to put the new state at the current position
+ count = l->n - tlen;
+ if (count == 0) {
+ return r; // no state got added
+ }
+ if (count == 1) {
+ // overwrite the current state
+ l->t[listidx] = l->t[l->n - 1];
+ } else if (count > 1) {
+ if (l->n + count - 1 >= l->len) {
+ // not enough space to move the new states, reallocate the list
+ // and move the states to the right position
+ const int newlen = l->len * 3 / 2 + 50;
+ const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
+
+ if ((int64_t)(newsize >> 10) >= p_mmp) {
+ emsg(_(e_pattern_uses_more_memory_than_maxmempattern));
+ return NULL;
+ }
+ nfa_thread_T *const newl = xmalloc(newsize);
+ l->len = newlen;
+ memmove(&(newl[0]),
+ &(l->t[0]),
+ sizeof(nfa_thread_T) * (size_t)listidx);
+ memmove(&(newl[listidx]),
+ &(l->t[l->n - count]),
+ sizeof(nfa_thread_T) * (size_t)count);
+ memmove(&(newl[listidx + count]),
+ &(l->t[listidx + 1]),
+ sizeof(nfa_thread_T) * (size_t)(l->n - count - listidx - 1));
+ xfree(l->t);
+ l->t = newl;
+ } else {
+ // make space for new states, then move them from the
+ // end to the current position
+ memmove(&(l->t[listidx + count]),
+ &(l->t[listidx + 1]),
+ sizeof(nfa_thread_T) * (size_t)(l->n - listidx - 1));
+ memmove(&(l->t[listidx]),
+ &(l->t[l->n - 1]),
+ sizeof(nfa_thread_T) * (size_t)count);
+ }
+ }
+ l->n--;
+ *ip = listidx - 1;
+
+ return r;
+}
+
+// Check character class "class" against current character c.
+static int check_char_class(int cls, int c)
+{
+ switch (cls) {
+ case NFA_CLASS_ALNUM:
+ if (c >= 1 && c < 128 && isalnum(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_ALPHA:
+ if (c >= 1 && c < 128 && isalpha(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_BLANK:
+ if (c == ' ' || c == '\t') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_CNTRL:
+ if (c >= 1 && c <= 127 && iscntrl(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_DIGIT:
+ if (ascii_isdigit(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_GRAPH:
+ if (c >= 1 && c <= 127 && isgraph(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_LOWER:
+ if (mb_islower(c) && c != 170 && c != 186) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_PRINT:
+ if (vim_isprintc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_PUNCT:
+ if (c >= 1 && c < 128 && ispunct(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_SPACE:
+ if ((c >= 9 && c <= 13) || (c == ' ')) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_UPPER:
+ if (mb_isupper(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_XDIGIT:
+ if (ascii_isxdigit(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_TAB:
+ if (c == '\t') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_RETURN:
+ if (c == '\r') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_BACKSPACE:
+ if (c == '\b') {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_ESCAPE:
+ if (c == ESC) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_IDENT:
+ if (vim_isIDc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_KEYWORD:
+ if (reg_iswordc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_FNAME:
+ if (vim_isfilec(c)) {
+ return OK;
+ }
+ break;
+
+ default:
+ // should not be here :P
+ siemsg(_(e_ill_char_class), (int64_t)cls);
+ return FAIL;
+ }
+ return FAIL;
+}
+
+/// Check for a match with subexpression "subidx".
+///
+/// @param sub pointers to subexpressions
+/// @param bytelen out: length of match in bytes
+///
+/// @return true if it matches.
+static int match_backref(regsub_T *sub, int subidx, int *bytelen)
+{
+ int len;
+
+ if (sub->in_use <= subidx) {
+retempty:
+ // backref was not set, match an empty string
+ *bytelen = 0;
+ return true;
+ }
+
+ if (REG_MULTI) {
+ if (sub->list.multi[subidx].start_lnum < 0
+ || sub->list.multi[subidx].end_lnum < 0) {
+ goto retempty;
+ }
+ if (sub->list.multi[subidx].start_lnum == rex.lnum
+ && sub->list.multi[subidx].end_lnum == rex.lnum) {
+ len = sub->list.multi[subidx].end_col
+ - sub->list.multi[subidx].start_col;
+ if (cstrncmp((char *)rex.line + sub->list.multi[subidx].start_col,
+ (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ } else {
+ if (match_with_backref(sub->list.multi[subidx].start_lnum,
+ sub->list.multi[subidx].start_col,
+ sub->list.multi[subidx].end_lnum,
+ sub->list.multi[subidx].end_col,
+ bytelen) == RA_MATCH) {
+ return true;
+ }
+ }
+ } else {
+ if (sub->list.line[subidx].start == NULL
+ || sub->list.line[subidx].end == NULL) {
+ goto retempty;
+ }
+ len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start);
+ if (cstrncmp((char *)sub->list.line[subidx].start, (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ }
+ return false;
+}
+
+/// Check for a match with \z subexpression "subidx".
+///
+/// @param bytelen out: length of match in bytes
+///
+/// @return true if it matches.
+static int match_zref(int subidx, int *bytelen)
+{
+ int len;
+
+ cleanup_zsubexpr();
+ if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
+ // backref was not set, match an empty string
+ *bytelen = 0;
+ return true;
+ }
+
+ len = (int)strlen((char *)re_extmatch_in->matches[subidx]);
+ if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) {
+ *bytelen = len;
+ return true;
+ }
+ return false;
+}
+
+// Save list IDs for all NFA states of "prog" into "list".
+// Also reset the IDs to zero.
+// Only used for the recursive value lastlist[1].
+static void nfa_save_listids(nfa_regprog_T *prog, int *list)
+{
+ int i;
+ nfa_state_T *p;
+
+ // Order in the list is reverse, it's a bit faster that way.
+ p = &prog->state[0];
+ for (i = prog->nstate; --i >= 0;) {
+ list[i] = p->lastlist[1];
+ p->lastlist[1] = 0;
+ p++;
+ }
+}
+
+// Restore list IDs from "list" to all NFA states.
+static void nfa_restore_listids(nfa_regprog_T *prog, const int *list)
+{
+ int i;
+ nfa_state_T *p;
+
+ p = &prog->state[0];
+ for (i = prog->nstate; --i >= 0;) {
+ p->lastlist[1] = list[i];
+ p++;
+ }
+}
+
+static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
+{
+ if (op == 1) {
+ return pos > val;
+ }
+ if (op == 2) {
+ return pos < val;
+ }
+ return val == pos;
+}
+
+// Recursively call nfa_regmatch()
+// "pim" is NULL or contains info about a Postponed Invisible Match (start
+// position).
+static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
+ regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
+{
+ const int save_reginput_col = (int)(rex.input - rex.line);
+ const int save_reglnum = rex.lnum;
+ const int save_nfa_match = nfa_match;
+ const int save_nfa_listid = rex.nfa_listid;
+ save_se_T *const save_nfa_endp = nfa_endp;
+ save_se_T endpos;
+ save_se_T *endposp = NULL;
+ int need_restore = false;
+
+ if (pim != NULL) {
+ // start at the position where the postponed match was
+ if (REG_MULTI) {
+ rex.input = rex.line + pim->end.pos.col;
+ } else {
+ rex.input = pim->end.ptr;
+ }
+ }
+
+ if (state->c == NFA_START_INVISIBLE_BEFORE
+ || state->c == NFA_START_INVISIBLE_BEFORE_FIRST
+ || state->c == NFA_START_INVISIBLE_BEFORE_NEG
+ || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
+ // The recursive match must end at the current position. When "pim" is
+ // not NULL it specifies the current position.
+ endposp = &endpos;
+ if (REG_MULTI) {
+ if (pim == NULL) {
+ endpos.se_u.pos.col = (int)(rex.input - rex.line);
+ endpos.se_u.pos.lnum = rex.lnum;
+ } else {
+ endpos.se_u.pos = pim->end.pos;
+ }
+ } else {
+ if (pim == NULL) {
+ endpos.se_u.ptr = rex.input;
+ } else {
+ endpos.se_u.ptr = pim->end.ptr;
+ }
+ }
+
+ // Go back the specified number of bytes, or as far as the
+ // start of the previous line, to try matching "\@<=" or
+ // not matching "\@<!". This is very inefficient, limit the number of
+ // bytes if possible.
+ if (state->val <= 0) {
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = (uint8_t *)reg_getline(++rex.lnum);
+ }
+ }
+ rex.input = rex.line;
+ } else {
+ if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
+ // Not enough bytes in this line, go to end of
+ // previous line.
+ rex.line = (uint8_t *)reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = (uint8_t *)reg_getline(++rex.lnum);
+ rex.input = rex.line;
+ } else {
+ rex.input = rex.line + strlen((char *)rex.line);
+ }
+ }
+ if ((int)(rex.input - rex.line) >= state->val) {
+ rex.input -= state->val;
+ rex.input -= utf_head_off((char *)rex.line, (char *)rex.input);
+ } else {
+ rex.input = rex.line;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ if (log_fd != stderr) {
+ fclose(log_fd);
+ }
+ log_fd = NULL;
+#endif
+ // Have to clear the lastlist field of the NFA nodes, so that
+ // nfa_regmatch() and addstate() can run properly after recursion.
+ if (nfa_ll_index == 1) {
+ // Already calling nfa_regmatch() recursively. Save the lastlist[1]
+ // values and clear them.
+ if (*listids == NULL || *listids_len < prog->nstate) {
+ xfree(*listids);
+ *listids = xmalloc(sizeof(**listids) * (size_t)prog->nstate);
+ *listids_len = prog->nstate;
+ }
+ nfa_save_listids(prog, *listids);
+ need_restore = true;
+ // any value of rex.nfa_listid will do
+ } else {
+ // First recursive nfa_regmatch() call, switch to the second lastlist
+ // entry. Make sure rex.nfa_listid is different from a previous
+ // recursive call, because some states may still have this ID.
+ nfa_ll_index++;
+ if (rex.nfa_listid <= rex.nfa_alt_listid) {
+ rex.nfa_listid = rex.nfa_alt_listid;
+ }
+ }
+
+ // Call nfa_regmatch() to check if the current concat matches at this
+ // position. The concat ends with the node NFA_END_INVISIBLE
+ nfa_endp = endposp;
+ const int result = nfa_regmatch(prog, state->out, submatch, m);
+
+ if (need_restore) {
+ nfa_restore_listids(prog, *listids);
+ } else {
+ nfa_ll_index--;
+ rex.nfa_alt_listid = rex.nfa_listid;
+ }
+
+ // restore position in input text
+ rex.lnum = save_reglnum;
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save_reginput_col;
+ if (result != NFA_TOO_EXPENSIVE) {
+ nfa_match = save_nfa_match;
+ rex.nfa_listid = save_nfa_listid;
+ }
+ nfa_endp = save_nfa_endp;
+
+#ifdef REGEXP_DEBUG
+ open_debug_log(result);
+#endif
+
+ return result;
+}
+
+// Estimate the chance of a match with "state" failing.
+// empty match: 0
+// NFA_ANY: 1
+// specific character: 99
+static int failure_chance(nfa_state_T *state, int depth)
+{
+ int c = state->c;
+ int l, r;
+
+ // detect looping
+ if (depth > 4) {
+ return 1;
+ }
+
+ switch (c) {
+ case NFA_SPLIT:
+ if (state->out->c == NFA_SPLIT || state->out1->c == NFA_SPLIT) {
+ // avoid recursive stuff
+ return 1;
+ }
+ // two alternatives, use the lowest failure chance
+ l = failure_chance(state->out, depth + 1);
+ r = failure_chance(state->out1, depth + 1);
+ return l < r ? l : r;
+
+ case NFA_ANY:
+ // matches anything, unlikely to fail
+ return 1;
+
+ case NFA_MATCH:
+ case NFA_MCLOSE:
+ case NFA_ANY_COMPOSING:
+ // empty match works always
+ return 0;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+ case NFA_START_PATTERN:
+ // recursive regmatch is expensive, use low failure chance
+ return 5;
+
+ case NFA_BOL:
+ case NFA_EOL:
+ case NFA_BOF:
+ case NFA_EOF:
+ case NFA_NEWL:
+ return 99;
+
+ case NFA_BOW:
+ case NFA_EOW:
+ return 90;
+
+ case NFA_MOPEN:
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_ZCLOSE:
+ case NFA_ZCLOSE1:
+ case NFA_ZCLOSE2:
+ case NFA_ZCLOSE3:
+ case NFA_ZCLOSE4:
+ case NFA_ZCLOSE5:
+ case NFA_ZCLOSE6:
+ case NFA_ZCLOSE7:
+ case NFA_ZCLOSE8:
+ case NFA_ZCLOSE9:
+ case NFA_NOPEN:
+ case NFA_MCLOSE1:
+ case NFA_MCLOSE2:
+ case NFA_MCLOSE3:
+ case NFA_MCLOSE4:
+ case NFA_MCLOSE5:
+ case NFA_MCLOSE6:
+ case NFA_MCLOSE7:
+ case NFA_MCLOSE8:
+ case NFA_MCLOSE9:
+ case NFA_NCLOSE:
+ return failure_chance(state->out, depth + 1);
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ // backreferences don't match in many places
+ return 94;
+
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT:
+ case NFA_VISUAL:
+ // before/after positions don't match very often
+ return 85;
+
+ case NFA_LNUM:
+ return 90;
+
+ case NFA_CURSOR:
+ case NFA_COL:
+ case NFA_VCOL:
+ case NFA_MARK:
+ // specific positions rarely match
+ return 98;
+
+ case NFA_COMPOSING:
+ return 95;
+
+ default:
+ if (c > 0) {
+ // character match fails often
+ return 95;
+ }
+ }
+
+ // something else, includes character classes
+ return 50;
+}
+
+// Skip until the char "c" we know a match must start with.
+static int skip_to_start(int c, colnr_T *colp)
+{
+ const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c);
+ if (s == NULL) {
+ return FAIL;
+ }
+ *colp = (int)(s - rex.line);
+ return OK;
+}
+
+// Check for a match with match_text.
+// Called after skip_to_start() has found regstart.
+// Returns zero for no match, 1 for a match.
+static int find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
+{
+#define PTR2LEN(x) utf_ptr2len(x)
+
+ colnr_T col = *startcol;
+ int regstart_len = PTR2LEN((char *)rex.line + col);
+
+ while (true) {
+ bool match = true;
+ uint8_t *s1 = match_text;
+ uint8_t *s2 = rex.line + col + regstart_len; // skip regstart
+ while (*s1) {
+ int c1_len = PTR2LEN((char *)s1);
+ int c1 = utf_ptr2char((char *)s1);
+ int c2_len = PTR2LEN((char *)s2);
+ int c2 = utf_ptr2char((char *)s2);
+
+ if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
+ || c1_len != c2_len) {
+ match = false;
+ break;
+ }
+ s1 += c1_len;
+ s2 += c2_len;
+ }
+ if (match
+ // check that no composing char follows
+ && !utf_iscomposing(utf_ptr2char((char *)s2))) {
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ rex.reg_startpos[0].lnum = rex.lnum;
+ rex.reg_startpos[0].col = col;
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (colnr_T)(s2 - rex.line);
+ } else {
+ rex.reg_startp[0] = rex.line + col;
+ rex.reg_endp[0] = s2;
+ }
+ *startcol = col;
+ return 1L;
+ }
+
+ // Try finding regstart after the current match.
+ col += regstart_len; // skip regstart
+ if (skip_to_start(regstart, &col) == FAIL) {
+ break;
+ }
+ }
+
+ *startcol = col;
+ return 0L;
+
+#undef PTR2LEN
+}
+
+static int nfa_did_time_out(void)
+{
+ if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) {
+ if (nfa_timed_out != NULL) {
+ *nfa_timed_out = true;
+ }
+ return true;
+ }
+ return false;
+}
+
+/// Main matching routine.
+///
+/// Run NFA to determine whether it matches rex.input.
+///
+/// When "nfa_endp" is not NULL it is a required end-of-match position.
+///
+/// Return true if there is a match, false if there is no match,
+/// NFA_TOO_EXPENSIVE if we end up with too many states.
+/// When there is a match "submatch" contains the positions.
+///
+/// Note: Caller must ensure that: start != NULL.
+static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4)
+{
+ int result = false;
+ int flag = 0;
+ bool go_to_nextline = false;
+ nfa_thread_T *t;
+ nfa_list_T list[2];
+ int listidx;
+ nfa_list_T *thislist;
+ nfa_list_T *nextlist;
+ int *listids = NULL;
+ int listids_len = 0;
+ nfa_state_T *add_state;
+ bool add_here;
+ int add_count;
+ int add_off = 0;
+ int toplevel = start->c == NFA_MOPEN;
+ regsubs_T *r;
+ // Some patterns may take a long time to match, especially when using
+ // recursive_regmatch(). Allow interrupting them with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ return false;
+ }
+ if (nfa_did_time_out()) {
+ return false;
+ }
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a");
+
+ if (debug == NULL) {
+ semsg("(NFA) COULD NOT OPEN %s!", NFA_REGEXP_DEBUG_LOG);
+ return false;
+ }
+#endif
+ nfa_match = false;
+
+ // Allocate memory for the lists of nodes.
+ size_t size = (size_t)(prog->nstate + 1) * sizeof(nfa_thread_T);
+ list[0].t = xmalloc(size);
+ list[0].len = prog->nstate + 1;
+ list[1].t = xmalloc(size);
+ list[1].len = prog->nstate + 1;
+
+#ifdef REGEXP_DEBUG
+ log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (log_fd == NULL) {
+ emsg(_(e_log_open_failed));
+ log_fd = stderr;
+ }
+ fprintf(log_fd, "**********************************\n");
+ nfa_set_code(start->c);
+ fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n",
+ abs(start->id), code);
+ fprintf(log_fd, "**********************************\n");
+#endif
+
+ thislist = &list[0];
+ thislist->n = 0;
+ thislist->has_pim = false;
+ nextlist = &list[1];
+ nextlist->n = 0;
+ nextlist->has_pim = false;
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "(---) STARTSTATE first\n");
+#endif
+ thislist->id = rex.nfa_listid + 1;
+
+ // Inline optimized code for addstate(thislist, start, m, 0) if we know
+ // it's the first MOPEN.
+ if (toplevel) {
+ if (REG_MULTI) {
+ m->norm.list.multi[0].start_lnum = rex.lnum;
+ m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
+ m->norm.orig_start_col = m->norm.list.multi[0].start_col;
+ } else {
+ m->norm.list.line[0].start = rex.input;
+ }
+ m->norm.in_use = 1;
+ r = addstate(thislist, start->out, m, NULL, 0);
+ } else {
+ r = addstate(thislist, start, m, NULL, 0);
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+
+#define ADD_STATE_IF_MATCH(state) \
+ if (result) { \
+ add_state = (state)->out; \
+ add_off = clen; \
+ }
+
+ // Run for each character.
+ while (true) {
+ int curc = utf_ptr2char((char *)rex.input);
+ int clen = utfc_ptr2len((char *)rex.input);
+ if (curc == NUL) {
+ clen = 0;
+ go_to_nextline = false;
+ }
+
+ // swap lists
+ thislist = &list[flag];
+ nextlist = &list[flag ^= 1];
+ nextlist->n = 0; // clear nextlist
+ nextlist->has_pim = false;
+ rex.nfa_listid++;
+ if (prog->re_engine == AUTOMATIC_ENGINE
+ && (rex.nfa_listid >= NFA_MAX_STATES)) {
+ // Too many states, retry with old engine.
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+
+ thislist->id = rex.nfa_listid;
+ nextlist->id = rex.nfa_listid + 1;
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "------------------------------------------\n");
+ fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
+ fprintf(log_fd,
+ ">>> Advanced one character... Current char is %c (code %d) \n",
+ curc,
+ (int)curc);
+ fprintf(log_fd, ">>> Thislist has %d states available: ", thislist->n);
+ {
+ int i;
+
+ for (i = 0; i < thislist->n; i++) {
+ fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
+ }
+ }
+ fprintf(log_fd, "\n");
+#endif
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ fprintf(debug, "\n-------------------\n");
+#endif
+ // If the state lists are empty we can stop.
+ if (thislist->n == 0) {
+ break;
+ }
+
+ // compute nextlist
+ for (listidx = 0; listidx < thislist->n; listidx++) {
+ // If the list gets very long there probably is something wrong.
+ // At least allow interrupting with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ break;
+ }
+ if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
+ nfa_time_count = 0;
+ if (nfa_did_time_out()) {
+ break;
+ }
+ }
+ t = &thislist->t[listidx];
+
+#ifdef NFA_REGEXP_DEBUG_LOG
+ nfa_set_code(t->state->c);
+ fprintf(debug, "%s, ", code);
+#endif
+#ifdef REGEXP_DEBUG
+ {
+ int col;
+
+ if (t->subs.norm.in_use <= 0) {
+ col = -1;
+ } else if (REG_MULTI) {
+ col = t->subs.norm.list.multi[0].start_col;
+ } else {
+ col = (int)(t->subs.norm.list.line[0].start - rex.line);
+ }
+ nfa_set_code(t->state->c);
+ fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n",
+ abs(t->state->id), (int)t->state->c, code, col,
+ pim_info(&t->pim));
+ }
+#endif
+
+ // Handle the possible codes of the current state.
+ // The most important is NFA_MATCH.
+ add_state = NULL;
+ add_here = false;
+ add_count = 0;
+ switch (t->state->c) {
+ case NFA_MATCH:
+ // If the match is not at the start of the line, ends before a
+ // composing characters and rex.reg_icombine is not set, that
+ // is not really a match.
+ if (!rex.reg_icombine
+ && rex.input != rex.line
+ && utf_iscomposing(curc)) {
+ break;
+ }
+ nfa_match = true;
+ copy_sub(&submatch->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&submatch->synt, &t->subs.synt);
+ }
+#ifdef REGEXP_DEBUG
+ log_subsexpr(&t->subs);
+#endif
+ // Found the left-most longest match, do not look at any other
+ // states at this position. When the list of states is going
+ // to be empty quit without advancing, so that "rex.input" is
+ // correct.
+ if (nextlist->n == 0) {
+ clen = 0;
+ }
+ goto nextchar;
+
+ case NFA_END_INVISIBLE:
+ case NFA_END_INVISIBLE_NEG:
+ case NFA_END_PATTERN:
+ // This is only encountered after a NFA_START_INVISIBLE or
+ // NFA_START_INVISIBLE_BEFORE node.
+ // They surround a zero-width group, used with "\@=", "\&",
+ // "\@!", "\@<=" and "\@<!".
+ // If we got here, it means that the current "invisible" group
+ // finished successfully, so return control to the parent
+ // nfa_regmatch(). For a look-behind match only when it ends
+ // in the position in "nfa_endp".
+ // Submatches are stored in *m, and used in the parent call.
+#ifdef REGEXP_DEBUG
+ if (nfa_endp != NULL) {
+ if (REG_MULTI) {
+ fprintf(log_fd,
+ "Current lnum: %d, endp lnum: %d;"
+ " current col: %d, endp col: %d\n",
+ (int)rex.lnum,
+ (int)nfa_endp->se_u.pos.lnum,
+ (int)(rex.input - rex.line),
+ nfa_endp->se_u.pos.col);
+ } else {
+ fprintf(log_fd, "Current col: %d, endp col: %d\n",
+ (int)(rex.input - rex.line),
+ (int)(nfa_endp->se_u.ptr - rex.input));
+ }
+ }
+#endif
+ // If "nfa_endp" is set it's only a match if it ends at
+ // "nfa_endp"
+ if (nfa_endp != NULL
+ && (REG_MULTI
+ ? (rex.lnum != nfa_endp->se_u.pos.lnum
+ || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col)
+ : rex.input != nfa_endp->se_u.ptr)) {
+ break;
+ }
+ // do not set submatches for \@!
+ if (t->state->c != NFA_END_INVISIBLE_NEG) {
+ copy_sub(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub(&m->synt, &t->subs.synt);
+ }
+ }
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "Match found:\n");
+ log_subsexpr(m);
+#endif
+ nfa_match = true;
+ // See comment above at "goto nextchar".
+ if (nextlist->n == 0) {
+ clen = 0;
+ }
+ goto nextchar;
+
+ case NFA_START_INVISIBLE:
+ case NFA_START_INVISIBLE_FIRST:
+ case NFA_START_INVISIBLE_NEG:
+ case NFA_START_INVISIBLE_NEG_FIRST:
+ case NFA_START_INVISIBLE_BEFORE:
+ case NFA_START_INVISIBLE_BEFORE_FIRST:
+ case NFA_START_INVISIBLE_BEFORE_NEG:
+ case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "Failure chance invisible: %d, what follows: %d\n",
+ failure_chance(t->state->out, 0),
+ failure_chance(t->state->out1->out, 0));
+#endif
+ // Do it directly if there already is a PIM or when
+ // nfa_postprocess() detected it will work better.
+ if (t->pim.result != NFA_PIM_UNUSED
+ || t->state->c == NFA_START_INVISIBLE_FIRST
+ || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || t->state->c == NFA_START_INVISIBLE_BEFORE_FIRST
+ || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
+ int in_use = m->norm.in_use;
+
+ // Copy submatch info for the recursive call, opposite
+ // of what happens on success below.
+ copy_sub_off(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&m->synt, &t->subs.synt);
+ }
+ // First try matching the invisible match, then what
+ // follows.
+ result = recursive_regmatch(t->state, NULL, prog, submatch, m,
+ &listids, &listids_len);
+ if (result == NFA_TOO_EXPENSIVE) {
+ nfa_match = result;
+ goto theend;
+ }
+
+ // for \@! and \@<! it is a match when the result is
+ // false
+ if (result != (t->state->c == NFA_START_INVISIBLE_NEG
+ || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || t->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || t->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &m->synt);
+ }
+ // If the pattern has \ze and it matched in the
+ // sub pattern, use it.
+ copy_ze_off(&t->subs.norm, &m->norm);
+
+ // t->state->out1 is the corresponding
+ // END_INVISIBLE node; Add its out to the current
+ // list (zero-width match).
+ add_here = true;
+ add_state = t->state->out1->out;
+ }
+ m->norm.in_use = in_use;
+ } else {
+ nfa_pim_T pim;
+
+ // First try matching what follows. Only if a match
+ // is found verify the invisible match matches. Add a
+ // nfa_pim_T to the following states, it contains info
+ // about the invisible match.
+ pim.state = t->state;
+ pim.result = NFA_PIM_TODO;
+ pim.subs.norm.in_use = 0;
+ pim.subs.synt.in_use = 0;
+ if (REG_MULTI) {
+ pim.end.pos.col = (int)(rex.input - rex.line);
+ pim.end.pos.lnum = rex.lnum;
+ } else {
+ pim.end.ptr = rex.input;
+ }
+ // t->state->out1 is the corresponding END_INVISIBLE
+ // node; Add its out to the current list (zero-width
+ // match).
+ if (addstate_here(thislist, t->state->out1->out, &t->subs,
+ &pim, &listidx) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ break;
+
+ case NFA_START_PATTERN: {
+ nfa_state_T *skip = NULL;
+#ifdef REGEXP_DEBUG
+ int skip_lid = 0;
+#endif
+
+ // There is no point in trying to match the pattern if the
+ // output state is not going to be added to the list.
+ if (state_in_list(nextlist, t->state->out1->out, &t->subs)) {
+ skip = t->state->out1->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = nextlist->id;
+#endif
+ } else if (state_in_list(nextlist,
+ t->state->out1->out->out, &t->subs)) {
+ skip = t->state->out1->out->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = nextlist->id;
+#endif
+ } else if (state_in_list(thislist,
+ t->state->out1->out->out, &t->subs)) {
+ skip = t->state->out1->out->out;
+#ifdef REGEXP_DEBUG
+ skip_lid = thislist->id;
+#endif
+ }
+ if (skip != NULL) {
+#ifdef REGEXP_DEBUG
+ nfa_set_code(skip->c);
+ fprintf(log_fd,
+ "> Not trying to match pattern, output state %d is already in list %d. char %d: %s\n",
+ abs(skip->id), skip_lid, skip->c, code);
+#endif
+ break;
+ }
+ // Copy submatch info to the recursive call, opposite of what
+ // happens afterwards.
+ copy_sub_off(&m->norm, &t->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&m->synt, &t->subs.synt);
+ }
+
+ // First try matching the pattern.
+ result = recursive_regmatch(t->state, NULL, prog, submatch, m,
+ &listids, &listids_len);
+ if (result == NFA_TOO_EXPENSIVE) {
+ nfa_match = result;
+ goto theend;
+ }
+ if (result) {
+ int bytelen;
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_START_PATTERN matches:\n");
+ log_subsexpr(m);
+#endif
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &m->synt);
+ }
+ // Now we need to skip over the matched text and then
+ // continue with what follows.
+ if (REG_MULTI) {
+ // TODO(RE): multi-line match
+ bytelen = m->norm.list.multi[0].end_col
+ - (int)(rex.input - rex.line);
+ } else {
+ bytelen = (int)(m->norm.list.line[0].end - rex.input);
+ }
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_START_PATTERN length: %d\n", bytelen);
+#endif
+ if (bytelen == 0) {
+ // empty match, output of corresponding
+ // NFA_END_PATTERN/NFA_SKIP to be used at current
+ // position
+ add_here = true;
+ add_state = t->state->out1->out->out;
+ } else if (bytelen <= clen) {
+ // match current character, output of corresponding
+ // NFA_END_PATTERN to be used at next position.
+ add_state = t->state->out1->out->out;
+ add_off = clen;
+ } else {
+ // skip over the matched characters, set character
+ // count in NFA_SKIP
+ add_state = t->state->out1->out;
+ add_off = bytelen;
+ add_count = bytelen - clen;
+ }
+ }
+ break;
+ }
+
+ case NFA_BOL:
+ if (rex.input == rex.line) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOL:
+ if (curc == NUL) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_BOW:
+ result = true;
+
+ if (curc == NUL) {
+ result = false;
+ } else {
+ int this_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ if (this_class <= 1) {
+ result = false;
+ } else if (reg_prev_class() == this_class) {
+ result = false;
+ }
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOW:
+ result = true;
+ if (rex.input == rex.line) {
+ result = false;
+ } else {
+ int this_class, prev_class;
+
+ // Get class of current and previous char (if it exists).
+ this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
+ prev_class = reg_prev_class();
+ if (this_class == prev_class
+ || prev_class == 0 || prev_class == 1) {
+ result = false;
+ }
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_BOF:
+ if (rex.lnum == 0 && rex.input == rex.line
+ && (!REG_MULTI || rex.reg_firstlnum == 1)) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_EOF:
+ if (rex.lnum == rex.reg_maxline && curc == NUL) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_COMPOSING: {
+ int mc = curc;
+ int len = 0;
+ nfa_state_T *end;
+ nfa_state_T *sta;
+ int cchars[MAX_MCO];
+ int ccount = 0;
+ int j;
+
+ sta = t->state->out;
+ len = 0;
+ if (utf_iscomposing(sta->c)) {
+ // Only match composing character(s), ignore base
+ // character. Used for ".{composing}" and "{composing}"
+ // (no preceding character).
+ len += utf_char2len(mc);
+ }
+ if (rex.reg_icombine && len == 0) {
+ // If \Z was present, then ignore composing characters.
+ // When ignoring the base character this always matches.
+ if (sta->c != curc) {
+ result = FAIL;
+ } else {
+ result = OK;
+ }
+ while (sta->c != NFA_END_COMPOSING) {
+ sta = sta->out;
+ }
+ } else if (len > 0 || mc == sta->c) {
+ // Check base character matches first, unless ignored.
+ if (len == 0) {
+ len += utf_char2len(mc);
+ sta = sta->out;
+ }
+
+ // We don't care about the order of composing characters.
+ // Get them into cchars[] first.
+ while (len < clen) {
+ mc = utf_ptr2char((char *)rex.input + len);
+ cchars[ccount++] = mc;
+ len += utf_char2len(mc);
+ if (ccount == MAX_MCO) {
+ break;
+ }
+ }
+
+ // Check that each composing char in the pattern matches a
+ // composing char in the text. We do not check if all
+ // composing chars are matched.
+ result = OK;
+ while (sta->c != NFA_END_COMPOSING) {
+ for (j = 0; j < ccount; j++) {
+ if (cchars[j] == sta->c) {
+ break;
+ }
+ }
+ if (j == ccount) {
+ result = FAIL;
+ break;
+ }
+ sta = sta->out;
+ }
+ } else {
+ result = FAIL;
+ }
+
+ end = t->state->out1; // NFA_END_COMPOSING
+ ADD_STATE_IF_MATCH(end);
+ break;
+ }
+
+ case NFA_NEWL:
+ if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
+ && rex.lnum <= rex.reg_maxline) {
+ go_to_nextline = true;
+ // Pass -1 for the offset, which means taking the position
+ // at the start of the next line.
+ add_state = t->state->out;
+ add_off = -1;
+ } else if (curc == '\n' && rex.reg_line_lbr) {
+ // match \n as if it is an ordinary character
+ add_state = t->state->out;
+ add_off = 1;
+ }
+ break;
+
+ case NFA_START_COLL:
+ case NFA_START_NEG_COLL: {
+ // What follows is a list of characters, until NFA_END_COLL.
+ // One of them must match or none of them must match.
+ nfa_state_T *state;
+ int result_if_matched;
+ int c1, c2;
+
+ // Never match EOL. If it's part of the collection it is added
+ // as a separate state with an OR.
+ if (curc == NUL) {
+ break;
+ }
+
+ state = t->state->out;
+ result_if_matched = (t->state->c == NFA_START_COLL);
+ while (true) {
+ if (state->c == NFA_END_COLL) {
+ result = !result_if_matched;
+ break;
+ }
+ if (state->c == NFA_RANGE_MIN) {
+ c1 = state->val;
+ state = state->out; // advance to NFA_RANGE_MAX
+ c2 = state->val;
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "NFA_RANGE_MIN curc=%d c1=%d c2=%d\n",
+ curc, c1, c2);
+#endif
+ if (curc >= c1 && curc <= c2) {
+ result = result_if_matched;
+ break;
+ }
+ if (rex.reg_ic) {
+ int curc_low = utf_fold(curc);
+ int done = false;
+
+ for (; c1 <= c2; c1++) {
+ if (utf_fold(c1) == curc_low) {
+ result = result_if_matched;
+ done = true;
+ break;
+ }
+ }
+ if (done) {
+ break;
+ }
+ }
+ } else if (state->c < 0 ? check_char_class(state->c, curc)
+ : (curc == state->c
+ || (rex.reg_ic
+ && utf_fold(curc) == utf_fold(state->c)))) {
+ result = result_if_matched;
+ break;
+ }
+ state = state->out;
+ }
+ if (result) {
+ // next state is in out of the NFA_END_COLL, out1 of
+ // START points to the END state
+ add_state = t->state->out1->out;
+ add_off = clen;
+ }
+ break;
+ }
+
+ case NFA_ANY:
+ // Any char except '\0', (end of input) does not match.
+ if (curc > 0) {
+ add_state = t->state->out;
+ add_off = clen;
+ }
+ break;
+
+ case NFA_ANY_COMPOSING:
+ // On a composing character skip over it. Otherwise do
+ // nothing. Always matches.
+ if (utf_iscomposing(curc)) {
+ add_off = clen;
+ } else {
+ add_here = true;
+ add_off = 0;
+ }
+ add_state = t->state->out;
+ break;
+
+ // Character classes like \a for alpha, \d for digit etc.
+ case NFA_IDENT: // \i
+ result = vim_isIDc(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SIDENT: // \I
+ result = !ascii_isdigit(curc) && vim_isIDc(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_KWORD: // \k
+ result = vim_iswordp_buf((char *)rex.input, rex.reg_buf);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SKWORD: // \K
+ result = !ascii_isdigit(curc)
+ && vim_iswordp_buf((char *)rex.input, rex.reg_buf);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_FNAME: // \f
+ result = vim_isfilec(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SFNAME: // \F
+ result = !ascii_isdigit(curc) && vim_isfilec(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_PRINT: // \p
+ result = vim_isprintc(utf_ptr2char((char *)rex.input));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_SPRINT: // \P
+ result = !ascii_isdigit(curc) && vim_isprintc(utf_ptr2char((char *)rex.input));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_WHITE: // \s
+ result = ascii_iswhite(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NWHITE: // \S
+ result = curc != NUL && !ascii_iswhite(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_DIGIT: // \d
+ result = ri_digit(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NDIGIT: // \D
+ result = curc != NUL && !ri_digit(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_HEX: // \x
+ result = ri_hex(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NHEX: // \X
+ result = curc != NUL && !ri_hex(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_OCTAL: // \o
+ result = ri_octal(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NOCTAL: // \O
+ result = curc != NUL && !ri_octal(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_WORD: // \w
+ result = ri_word(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NWORD: // \W
+ result = curc != NUL && !ri_word(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_HEAD: // \h
+ result = ri_head(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NHEAD: // \H
+ result = curc != NUL && !ri_head(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_ALPHA: // \a
+ result = ri_alpha(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NALPHA: // \A
+ result = curc != NUL && !ri_alpha(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_LOWER: // \l
+ result = ri_lower(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NLOWER: // \L
+ result = curc != NUL && !ri_lower(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_UPPER: // \u
+ result = ri_upper(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NUPPER: // \U
+ result = curc != NUL && !ri_upper(curc);
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_LOWER_IC: // [a-z]
+ result = ri_lower(curc) || (rex.reg_ic && ri_upper(curc));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NLOWER_IC: // [^a-z]
+ result = curc != NUL
+ && !(ri_lower(curc) || (rex.reg_ic && ri_upper(curc)));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_UPPER_IC: // [A-Z]
+ result = ri_upper(curc) || (rex.reg_ic && ri_lower(curc));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_NUPPER_IC: // [^A-Z]
+ result = curc != NUL
+ && !(ri_upper(curc) || (rex.reg_ic && ri_lower(curc)));
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+
+ case NFA_BACKREF1:
+ case NFA_BACKREF2:
+ case NFA_BACKREF3:
+ case NFA_BACKREF4:
+ case NFA_BACKREF5:
+ case NFA_BACKREF6:
+ case NFA_BACKREF7:
+ case NFA_BACKREF8:
+ case NFA_BACKREF9:
+ case NFA_ZREF1:
+ case NFA_ZREF2:
+ case NFA_ZREF3:
+ case NFA_ZREF4:
+ case NFA_ZREF5:
+ case NFA_ZREF6:
+ case NFA_ZREF7:
+ case NFA_ZREF8:
+ case NFA_ZREF9:
+ // \1 .. \9 \z1 .. \z9
+ {
+ int subidx;
+ int bytelen;
+
+ if (t->state->c <= NFA_BACKREF9) {
+ subidx = t->state->c - NFA_BACKREF1 + 1;
+ result = match_backref(&t->subs.norm, subidx, &bytelen);
+ } else {
+ subidx = t->state->c - NFA_ZREF1 + 1;
+ result = match_zref(subidx, &bytelen);
+ }
+
+ if (result) {
+ if (bytelen == 0) {
+ // empty match always works, output of NFA_SKIP to be
+ // used next
+ add_here = true;
+ add_state = t->state->out->out;
+ } else if (bytelen <= clen) {
+ // match current character, jump ahead to out of
+ // NFA_SKIP
+ add_state = t->state->out->out;
+ add_off = clen;
+ } else {
+ // skip over the matched characters, set character
+ // count in NFA_SKIP
+ add_state = t->state->out;
+ add_off = bytelen;
+ add_count = bytelen - clen;
+ }
+ }
+ break;
+ }
+ case NFA_SKIP:
+ // character of previous matching \1 .. \9 or \@>
+ if (t->count - clen <= 0) {
+ // end of match, go to what follows
+ add_state = t->state->out;
+ add_off = clen;
+ } else {
+ // add state again with decremented count
+ add_state = t->state;
+ add_off = 0;
+ add_count = t->count - clen;
+ }
+ break;
+
+ case NFA_LNUM:
+ case NFA_LNUM_GT:
+ case NFA_LNUM_LT:
+ assert(t->state->val >= 0
+ && !((rex.reg_firstlnum > 0
+ && rex.lnum > LONG_MAX - rex.reg_firstlnum)
+ || (rex.reg_firstlnum < 0
+ && rex.lnum < LONG_MIN + rex.reg_firstlnum))
+ && rex.lnum + rex.reg_firstlnum >= 0);
+ result = (REG_MULTI
+ && nfa_re_num_cmp((uintmax_t)t->state->val,
+ t->state->c - NFA_LNUM,
+ (uintmax_t)rex.lnum + (uintmax_t)rex.reg_firstlnum));
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_COL:
+ case NFA_COL_GT:
+ case NFA_COL_LT:
+ assert(t->state->val >= 0
+ && rex.input >= rex.line
+ && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val,
+ t->state->c - NFA_COL,
+ (uintmax_t)(rex.input - rex.line + 1));
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_VCOL:
+ case NFA_VCOL_GT:
+ case NFA_VCOL_LT: {
+ int op = t->state->c - NFA_VCOL;
+ colnr_T col = (colnr_T)(rex.input - rex.line);
+
+ // Bail out quickly when there can't be a match, avoid the overhead of
+ // win_linetabsize() on long lines.
+ if (op != 1 && col > t->state->val * MB_MAXBYTES) {
+ break;
+ }
+
+ result = false;
+ win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win;
+ if (op == 1 && col - 1 > t->state->val && col > 100) {
+ int64_t ts = (int64_t)wp->w_buffer->b_p_ts;
+
+ // Guess that a character won't use more columns than 'tabstop',
+ // with a minimum of 4.
+ if (ts < 4) {
+ ts = 4;
+ }
+ result = col > t->state->val * ts;
+ }
+ if (!result) {
+ int lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
+ assert(t->state->val >= 0);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val, op, (uintmax_t)lts + 1);
+ }
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ }
+ break;
+
+ case NFA_MARK:
+ case NFA_MARK_GT:
+ case NFA_MARK_LT: {
+ size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
+ fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val);
+
+ // Line may have been freed, get it again.
+ if (REG_MULTI) {
+ rex.line = (uint8_t *)reg_getline(rex.lnum);
+ rex.input = rex.line + col;
+ }
+
+ // Compare the mark position to the match position, if the mark
+ // exists and mark is set in reg_buf.
+ if (fm != NULL && fm->mark.lnum > 0) {
+ pos_T *pos = &fm->mark;
+ const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
+ && pos->col == MAXCOL
+ ? (colnr_T)strlen(reg_getline(pos->lnum - rex.reg_firstlnum))
+ : pos->col;
+
+ result = pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos_col == (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK
+ : (pos_col < (colnr_T)(rex.input - rex.line)
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT))
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
+ ? t->state->c == NFA_MARK_GT
+ : t->state->c == NFA_MARK_LT);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ }
+ break;
+ }
+
+ case NFA_CURSOR:
+ result = rex.reg_win != NULL
+ && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
+ && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col);
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_VISUAL:
+ result = reg_match_visual();
+ if (result) {
+ add_here = true;
+ add_state = t->state->out;
+ }
+ break;
+
+ case NFA_MOPEN1:
+ case NFA_MOPEN2:
+ case NFA_MOPEN3:
+ case NFA_MOPEN4:
+ case NFA_MOPEN5:
+ case NFA_MOPEN6:
+ case NFA_MOPEN7:
+ case NFA_MOPEN8:
+ case NFA_MOPEN9:
+ case NFA_ZOPEN:
+ case NFA_ZOPEN1:
+ case NFA_ZOPEN2:
+ case NFA_ZOPEN3:
+ case NFA_ZOPEN4:
+ case NFA_ZOPEN5:
+ case NFA_ZOPEN6:
+ case NFA_ZOPEN7:
+ case NFA_ZOPEN8:
+ case NFA_ZOPEN9:
+ case NFA_NOPEN:
+ case NFA_ZSTART:
+ // These states are only added to be able to bail out when
+ // they are added again, nothing is to be done.
+ break;
+
+ default: // regular character
+ {
+ int c = t->state->c;
+
+#ifdef REGEXP_DEBUG
+ if (c < 0) {
+ siemsg("INTERNAL: Negative state char: %" PRId64, (int64_t)c);
+ }
+#endif
+ result = (c == curc);
+
+ if (!result && rex.reg_ic) {
+ result = utf_fold(c) == utf_fold(curc);
+ }
+
+ // If rex.reg_icombine is not set only skip over the character
+ // itself. When it is set skip over composing characters.
+ if (result && !rex.reg_icombine) {
+ clen = utf_ptr2len((char *)rex.input);
+ }
+
+ ADD_STATE_IF_MATCH(t->state);
+ break;
+ }
+ } // switch (t->state->c)
+
+ if (add_state != NULL) {
+ nfa_pim_T *pim;
+ nfa_pim_T pim_copy;
+
+ if (t->pim.result == NFA_PIM_UNUSED) {
+ pim = NULL;
+ } else {
+ pim = &t->pim;
+ }
+
+ // Handle the postponed invisible match if the match might end
+ // without advancing and before the end of the line.
+ if (pim != NULL && (clen == 0 || match_follows(add_state, 0))) {
+ if (pim->result == NFA_PIM_TODO) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "\n");
+ fprintf(log_fd, "==================================\n");
+ fprintf(log_fd, "Postponed recursive nfa_regmatch()\n");
+ fprintf(log_fd, "\n");
+#endif
+ result = recursive_regmatch(pim->state, pim, prog, submatch, m,
+ &listids, &listids_len);
+ pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
+ // for \@! and \@<! it is a match when the result is
+ // false
+ if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
+ || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&pim->subs.norm, &m->norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&pim->subs.synt, &m->synt);
+ }
+ }
+ } else {
+ result = (pim->result == NFA_PIM_MATCH);
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "\n");
+ fprintf(log_fd,
+ "Using previous recursive nfa_regmatch() result, result == %d\n",
+ pim->result);
+ fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false");
+ fprintf(log_fd, "\n");
+#endif
+ }
+
+ // for \@! and \@<! it is a match when result is false
+ if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
+ || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG
+ || pim->state->c
+ == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
+ // Copy submatch info from the recursive call
+ copy_sub_off(&t->subs.norm, &pim->subs.norm);
+ if (rex.nfa_has_zsubexpr) {
+ copy_sub_off(&t->subs.synt, &pim->subs.synt);
+ }
+ } else {
+ // look-behind match failed, don't add the state
+ continue;
+ }
+
+ // Postponed invisible match was handled, don't add it to
+ // following states.
+ pim = NULL;
+ }
+
+ // If "pim" points into l->t it will become invalid when
+ // adding the state causes the list to be reallocated. Make a
+ // local copy to avoid that.
+ if (pim == &t->pim) {
+ copy_pim(&pim_copy, pim);
+ pim = &pim_copy;
+ }
+
+ if (add_here) {
+ r = addstate_here(thislist, add_state, &t->subs, pim, &listidx);
+ } else {
+ r = addstate(nextlist, add_state, &t->subs, pim, add_off);
+ if (add_count > 0) {
+ nextlist->t[nextlist->n - 1].count = add_count;
+ }
+ }
+ if (r == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ } // for (thislist = thislist; thislist->state; thislist++)
+
+ // Look for the start of a match in the current position by adding the
+ // start state to the list of states.
+ // The first found match is the leftmost one, thus the order of states
+ // matters!
+ // Do not add the start state in recursive calls of nfa_regmatch(),
+ // because recursive calls should only start in the first position.
+ // Unless "nfa_endp" is not NULL, then we match the end position.
+ // Also don't start a match past the first line.
+ if (!nfa_match
+ && ((toplevel
+ && rex.lnum == 0
+ && clen != 0
+ && (rex.reg_maxcol == 0
+ || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
+ || (nfa_endp != NULL
+ && (REG_MULTI
+ ? (rex.lnum < nfa_endp->se_u.pos.lnum
+ || (rex.lnum == nfa_endp->se_u.pos.lnum
+ && (int)(rex.input - rex.line)
+ < nfa_endp->se_u.pos.col))
+ : rex.input < nfa_endp->se_u.ptr)))) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, "(---) STARTSTATE\n");
+#endif
+ // Inline optimized code for addstate() if we know the state is
+ // the first MOPEN.
+ if (toplevel) {
+ int add = true;
+
+ if (prog->regstart != NUL && clen != 0) {
+ if (nextlist->n == 0) {
+ colnr_T col = (colnr_T)(rex.input - rex.line) + clen;
+
+ // Nextlist is empty, we can skip ahead to the
+ // character that must appear at the start.
+ if (skip_to_start(prog->regstart, &col) == FAIL) {
+ break;
+ }
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
+ col - ((colnr_T)(rex.input - rex.line) + clen));
+#endif
+ rex.input = rex.line + col - clen;
+ } else {
+ // Checking if the required start character matches is
+ // cheaper than adding a state that won't match.
+ const int c = utf_ptr2char((char *)rex.input + clen);
+ if (c != prog->regstart
+ && (!rex.reg_ic
+ || utf_fold(c) != utf_fold(prog->regstart))) {
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd,
+ " Skipping start state, regstart does not match\n");
+#endif
+ add = false;
+ }
+ }
+ }
+
+ if (add) {
+ if (REG_MULTI) {
+ m->norm.list.multi[0].start_col =
+ (colnr_T)(rex.input - rex.line) + clen;
+ m->norm.orig_start_col =
+ m->norm.list.multi[0].start_col;
+ } else {
+ m->norm.list.line[0].start = rex.input + clen;
+ }
+ if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ } else {
+ if (addstate(nextlist, start, m, NULL, clen) == NULL) {
+ nfa_match = NFA_TOO_EXPENSIVE;
+ goto theend;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ fprintf(log_fd, ">>> Thislist had %d states available: ", thislist->n);
+ {
+ int i;
+
+ for (i = 0; i < thislist->n; i++) {
+ fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
+ }
+ }
+ fprintf(log_fd, "\n");
+#endif
+
+nextchar:
+ // Advance to the next character, or advance to the next line, or
+ // finish.
+ if (clen != 0) {
+ rex.input += clen;
+ } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
+ && rex.lnum < nfa_endp->se_u.pos.lnum)) {
+ reg_nextline();
+ } else {
+ break;
+ }
+
+ // Allow interrupting with CTRL-C.
+ reg_breakcheck();
+ if (got_int) {
+ break;
+ }
+ // Check for timeout once every twenty times to avoid overhead.
+ if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
+ nfa_time_count = 0;
+ if (nfa_did_time_out()) {
+ break;
+ }
+ }
+ }
+
+#ifdef REGEXP_DEBUG
+ if (log_fd != stderr) {
+ fclose(log_fd);
+ }
+ log_fd = NULL;
+#endif
+
+theend:
+ // Free memory
+ xfree(list[0].t);
+ xfree(list[1].t);
+ xfree(listids);
+#undef ADD_STATE_IF_MATCH
+#ifdef NFA_REGEXP_DEBUG_LOG
+ fclose(debug);
+#endif
+
+ return nfa_match;
+}
+
+/// Try match of "prog" with at rex.line["col"].
+///
+/// @param tm timeout limit or NULL
+/// @param timed_out flag set on timeout or NULL
+///
+/// @return <= 0 for failure, number of lines contained in the match otherwise.
+static int nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
+{
+ int i;
+ regsubs_T subs, m;
+ nfa_state_T *start = prog->start;
+#ifdef REGEXP_DEBUG
+ FILE *f;
+#endif
+
+ rex.input = rex.line + col;
+ nfa_time_limit = tm;
+ nfa_timed_out = timed_out;
+ nfa_time_count = 0;
+
+#ifdef REGEXP_DEBUG
+ f = fopen(NFA_REGEXP_RUN_LOG, "a");
+ if (f != NULL) {
+ fprintf(f,
+ "\n\n\t=======================================================\n");
+# ifdef REGEXP_DEBUG
+ fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
+# endif
+ fprintf(f, "\tInput text is \"%s\" \n", rex.input);
+ fprintf(f, "\t=======================================================\n\n");
+ nfa_print_state(f, start);
+ fprintf(f, "\n\n");
+ fclose(f);
+ } else {
+ emsg("Could not open temporary log file for writing");
+ }
+#endif
+
+ clear_sub(&subs.norm);
+ clear_sub(&m.norm);
+ clear_sub(&subs.synt);
+ clear_sub(&m.synt);
+
+ int result = nfa_regmatch(prog, start, &subs, &m);
+ if (!result) {
+ return 0;
+ } else if (result == NFA_TOO_EXPENSIVE) {
+ return result;
+ }
+
+ cleanup_subexpr();
+ if (REG_MULTI) {
+ for (i = 0; i < subs.norm.in_use; i++) {
+ rex.reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum;
+ rex.reg_startpos[i].col = subs.norm.list.multi[i].start_col;
+
+ rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum;
+ rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col;
+ }
+ if (rex.reg_mmatch != NULL) {
+ rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col;
+ }
+
+ if (rex.reg_startpos[0].lnum < 0) {
+ rex.reg_startpos[0].lnum = 0;
+ rex.reg_startpos[0].col = col;
+ }
+ if (rex.reg_endpos[0].lnum < 0) {
+ // pattern has a \ze but it didn't match, use current end
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
+ } else {
+ // Use line number of "\ze".
+ rex.lnum = rex.reg_endpos[0].lnum;
+ }
+ } else {
+ for (i = 0; i < subs.norm.in_use; i++) {
+ rex.reg_startp[i] = subs.norm.list.line[i].start;
+ rex.reg_endp[i] = subs.norm.list.line[i].end;
+ }
+
+ if (rex.reg_startp[0] == NULL) {
+ rex.reg_startp[0] = rex.line + col;
+ }
+ if (rex.reg_endp[0] == NULL) {
+ rex.reg_endp[0] = rex.input;
+ }
+ }
+
+ // Package any found \z(...\) matches for export. Default is none.
+ unref_extmatch(re_extmatch_out);
+ re_extmatch_out = NULL;
+
+ if (prog->reghasz == REX_SET) {
+ cleanup_zsubexpr();
+ re_extmatch_out = make_extmatch();
+ // Loop over \z1, \z2, etc. There is no \z0.
+ for (i = 1; i < subs.synt.in_use; i++) {
+ if (REG_MULTI) {
+ struct multipos *mpos = &subs.synt.list.multi[i];
+
+ // Only accept single line matches that are valid.
+ if (mpos->start_lnum >= 0
+ && mpos->start_lnum == mpos->end_lnum
+ && mpos->end_col >= mpos->start_col) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave(reg_getline(mpos->start_lnum) + mpos->start_col,
+ (size_t)(mpos->end_col - mpos->start_col));
+ }
+ } else {
+ struct linepos *lpos = &subs.synt.list.line[i];
+
+ if (lpos->start != NULL && lpos->end != NULL) {
+ re_extmatch_out->matches[i] =
+ (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start));
+ }
+ }
+ }
+ }
+
+ return 1 + rex.lnum;
+}
+
+/// Match a regexp against a string ("line" points to the string) or multiple
+/// lines (if "line" is NULL, use reg_getline()).
+///
+/// @param line String in which to search or NULL
+/// @param startcol Column to start looking for match
+/// @param tm Timeout limit or NULL
+/// @param timed_out Flag set on timeout or NULL
+///
+/// @return <= 0 if there is no match and number of lines contained in the
+/// match otherwise.
+static int nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
+{
+ nfa_regprog_T *prog;
+ int retval = 0;
+ colnr_T col = startcol;
+
+ if (REG_MULTI) {
+ prog = (nfa_regprog_T *)rex.reg_mmatch->regprog;
+ line = (uint8_t *)reg_getline(0); // relative to the cursor
+ rex.reg_startpos = rex.reg_mmatch->startpos;
+ rex.reg_endpos = rex.reg_mmatch->endpos;
+ } else {
+ prog = (nfa_regprog_T *)rex.reg_match->regprog;
+ rex.reg_startp = (uint8_t **)rex.reg_match->startp;
+ rex.reg_endp = (uint8_t **)rex.reg_match->endp;
+ }
+
+ // Be paranoid...
+ if (prog == NULL || line == NULL) {
+ iemsg(_(e_null));
+ goto theend;
+ }
+
+ // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
+ if (prog->regflags & RF_ICASE) {
+ rex.reg_ic = true;
+ } else if (prog->regflags & RF_NOICASE) {
+ rex.reg_ic = false;
+ }
+
+ // If pattern contains "\Z" overrule value of rex.reg_icombine
+ if (prog->regflags & RF_ICOMBINE) {
+ rex.reg_icombine = true;
+ }
+
+ rex.line = line;
+ rex.lnum = 0; // relative to line
+
+ rex.nfa_has_zend = prog->has_zend;
+ rex.nfa_has_backref = prog->has_backref;
+ rex.nfa_nsubexpr = prog->nsubexp;
+ rex.nfa_listid = 1;
+ rex.nfa_alt_listid = 2;
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = prog->pattern;
+#endif
+
+ if (prog->reganch && col > 0) {
+ return 0L;
+ }
+
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessary.
+ if (prog->reghasz == REX_SET) {
+ rex.nfa_has_zsubexpr = true;
+ rex.need_clear_zsubexpr = true;
+ } else {
+ rex.nfa_has_zsubexpr = false;
+ rex.need_clear_zsubexpr = false;
+ }
+
+ if (prog->regstart != NUL) {
+ // Skip ahead until a character we know the match must start with.
+ // When there is none there is no match.
+ if (skip_to_start(prog->regstart, &col) == FAIL) {
+ return 0L;
+ }
+
+ // If match_text is set it contains the full text that must match.
+ // Nothing else to try. Doesn't handle combining chars well.
+ if (prog->match_text != NULL && !rex.reg_icombine) {
+ retval = find_match_text(&col, prog->regstart, prog->match_text);
+ if (REG_MULTI) {
+ rex.reg_mmatch->rmm_matchcol = col;
+ } else {
+ rex.reg_match->rm_matchcol = col;
+ }
+ return retval;
+ }
+ }
+
+ // If the start column is past the maximum column: no need to try.
+ if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
+ goto theend;
+ }
+
+ // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when
+ // it's accidentally used during execution.
+ nstate = 0;
+ for (int i = 0; i < prog->nstate; i++) {
+ prog->state[i].id = i;
+ prog->state[i].lastlist[0] = 0;
+ prog->state[i].lastlist[1] = 0;
+ }
+
+ retval = nfa_regtry(prog, col, tm, timed_out);
+
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = NULL;
+#endif
+
+theend:
+ if (retval > 0) {
+ // Make sure the end is never before the start. Can happen when \zs and
+ // \ze are used.
+ if (REG_MULTI) {
+ const lpos_T *const start = &rex.reg_mmatch->startpos[0];
+ const lpos_T *const end = &rex.reg_mmatch->endpos[0];
+
+ if (end->lnum < start->lnum
+ || (end->lnum == start->lnum && end->col < start->col)) {
+ rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
+ }
+ } else {
+ if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
+ rex.reg_match->endp[0] = rex.reg_match->startp[0];
+ }
+
+ // startpos[0] may be set by "\zs", also return the column where
+ // the whole pattern matched.
+ rex.reg_match->rm_matchcol = col;
+ }
+ }
+
+ return retval;
+}
+
+// Compile a regular expression into internal code for the NFA matcher.
+// Returns the program in allocated space. Returns NULL for an error.
+static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags)
+{
+ nfa_regprog_T *prog = NULL;
+ int *postfix;
+
+ if (expr == NULL) {
+ return NULL;
+ }
+
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = expr;
+#endif
+ nfa_re_flags = re_flags;
+
+ init_class_tab();
+
+ nfa_regcomp_start(expr, re_flags);
+
+ // Build postfix form of the regexp. Needed to build the NFA
+ // (and count its size).
+ postfix = re2post();
+ if (postfix == NULL) {
+ goto fail; // Cascaded (syntax?) error
+ }
+
+ // In order to build the NFA, we parse the input regexp twice:
+ // 1. first pass to count size (so we can allocate space)
+ // 2. second to emit code
+#ifdef REGEXP_DEBUG
+ {
+ FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a");
+
+ if (f != NULL) {
+ fprintf(f,
+ "\n*****************************\n\n\n\n\t"
+ "Compiling regexp \"%s\"... hold on !\n",
+ expr);
+ fclose(f);
+ }
+ }
+#endif
+
+ // PASS 1
+ // Count number of NFA states in "nstate". Do not build the NFA.
+ post2nfa(postfix, post_ptr, true);
+
+ // allocate the regprog with space for the compiled regexp
+ size_t prog_size = offsetof(nfa_regprog_T, state) + sizeof(nfa_state_T) * (size_t)nstate;
+ prog = xmalloc(prog_size);
+ state_ptr = prog->state;
+ prog->re_in_use = false;
+
+ // PASS 2
+ // Build the NFA
+ prog->start = post2nfa(postfix, post_ptr, false);
+ if (prog->start == NULL) {
+ goto fail;
+ }
+ prog->regflags = regflags;
+ prog->engine = &nfa_regengine;
+ prog->nstate = nstate;
+ prog->has_zend = rex.nfa_has_zend;
+ prog->has_backref = rex.nfa_has_backref;
+ prog->nsubexp = regnpar;
+
+ nfa_postprocess(prog);
+
+ prog->reganch = nfa_get_reganch(prog->start, 0);
+ prog->regstart = nfa_get_regstart(prog->start, 0);
+ prog->match_text = nfa_get_match_text(prog->start);
+
+#ifdef REGEXP_DEBUG
+ nfa_postfix_dump(expr, OK);
+ nfa_dump(prog);
+#endif
+ // Remember whether this pattern has any \z specials in it.
+ prog->reghasz = re_has_z;
+ prog->pattern = xstrdup((char *)expr);
+#ifdef REGEXP_DEBUG
+ nfa_regengine.expr = NULL;
+#endif
+
+out:
+ xfree(post_start);
+ post_start = post_ptr = post_end = NULL;
+ state_ptr = NULL;
+ return (regprog_T *)prog;
+
+fail:
+ XFREE_CLEAR(prog);
+#ifdef REGEXP_DEBUG
+ nfa_postfix_dump(expr, FAIL);
+ nfa_regengine.expr = NULL;
+#endif
+ goto out;
+}
+
+// Free a compiled regexp program, returned by nfa_regcomp().
+static void nfa_regfree(regprog_T *prog)
+{
+ if (prog == NULL) {
+ return;
+ }
+
+ xfree(((nfa_regprog_T *)prog)->match_text);
+ xfree(((nfa_regprog_T *)prog)->pattern);
+ xfree(prog);
+}
+
+/// Match a regexp against a string.
+/// "rmp->regprog" is a compiled regexp as returned by nfa_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
+///
+/// @param line string to match against
+/// @param col column to start looking for match
+///
+/// @return <= 0 for failure, number of lines contained in the match otherwise.
+static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
+{
+ rex.reg_match = rmp;
+ rex.reg_mmatch = NULL;
+ rex.reg_maxline = 0;
+ rex.reg_line_lbr = line_lbr;
+ rex.reg_buf = curbuf;
+ rex.reg_win = NULL;
+ rex.reg_ic = rmp->rm_ic;
+ rex.reg_icombine = false;
+ rex.reg_nobreak = rmp->regprog->re_flags & RE_NOBREAK;
+ rex.reg_maxcol = 0;
+ return nfa_regexec_both(line, col, NULL, NULL);
+}
+
+/// Matches a regexp against multiple lines.
+/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
+/// Uses curbuf for line count and 'iskeyword'.
+///
+/// @param win Window in which to search or NULL
+/// @param buf Buffer in which to search
+/// @param lnum Number of line to start looking for match
+/// @param col Column to start looking for match
+/// @param tm Timeout limit or NULL
+/// @param timed_out Flag set on timeout or NULL
+///
+/// @return <= 0 if there is no match and number of lines contained in the match
+/// otherwise.
+///
+/// @note The body is the same as bt_regexec() except for nfa_regexec_both()
+///
+/// @warning
+/// Match may actually be in another line. e.g.:
+/// when r.e. is \nc, cursor is at 'a' and the text buffer looks like
+///
+/// @par
+///
+/// +-------------------------+
+/// |a |
+/// |b |
+/// |c |
+/// | |
+/// +-------------------------+
+///
+/// @par
+/// then nfa_regexec_multi() returns 3. while the original vim_regexec_multi()
+/// returns 0 and a second call at line 2 will return 2.
+///
+/// @par
+/// FIXME if this behavior is not compatible.
+static int nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
+{
+ init_regexec_multi(rmp, win, buf, lnum);
+ return nfa_regexec_both(NULL, col, tm, timed_out);
+}
+// }}}1
static regengine_T bt_regengine = {
bt_regcomp,
@@ -2304,10 +15570,10 @@ static uint8_t regname[][30] = {
// Returns the program in allocated memory.
// Use vim_regfree() to free the memory.
// Returns NULL for an error.
-regprog_T *vim_regcomp(char *expr_arg, int re_flags)
+regprog_T *vim_regcomp(const char *expr_arg, int re_flags)
{
regprog_T *prog = NULL;
- char *expr = expr_arg;
+ const char *expr = expr_arg;
regexp_engine = (int)p_re;
@@ -2321,7 +15587,7 @@ regprog_T *vim_regcomp(char *expr_arg, int re_flags)
regexp_engine = expr[4] - '0';
expr += 5;
#ifdef REGEXP_DEBUG
- smsg("New regexp mode selected (%d): %s",
+ smsg(0, "New regexp mode selected (%d): %s",
regexp_engine,
regname[newengine]);
#endif
@@ -2403,7 +15669,7 @@ void free_regexp_stuff(void)
#endif
-static void report_re_switch(char *pat)
+static void report_re_switch(const char *pat)
{
if (p_verbose > 0) {
verbose_enter();
@@ -2425,7 +15691,7 @@ static void report_re_switch(char *pat)
/// @param nl
///
/// @return true if there is a match, false if not.
-static bool vim_regexec_string(regmatch_T *rmp, char *line, colnr_T col, bool nl)
+static bool vim_regexec_string(regmatch_T *rmp, const char *line, colnr_T col, bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
@@ -2482,7 +15748,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char *line, colnr_T col, bool nl
// Note: "*prog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char *line, colnr_T col)
+bool vim_regexec_prog(regprog_T **prog, bool ignore_case, const char *line, colnr_T col)
{
regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
bool r = vim_regexec_string(&regmatch, line, col, false);
@@ -2492,7 +15758,7 @@ bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char *line, colnr_T co
// Note: "rmp->regprog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col)
+bool vim_regexec(regmatch_T *rmp, const char *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, false);
}
@@ -2500,7 +15766,7 @@ bool vim_regexec(regmatch_T *rmp, char *line, colnr_T col)
// Like vim_regexec(), but consider a "\n" in "line" to be a line break.
// Note: "rmp->regprog" may be freed and changed.
// Return true if there is a match, false if not.
-bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col)
+bool vim_regexec_nl(regmatch_T *rmp, const char *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, true);
}
@@ -2519,8 +15785,8 @@ bool vim_regexec_nl(regmatch_T *rmp, char *line, colnr_T col)
///
/// @return zero if there is no match. Return number of lines contained in the
/// match otherwise.
-long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
+int vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
+ proftime_T *tm, int *timed_out)
FUNC_ATTR_NONNULL_ARG(1)
{
regexec_T rex_save;
@@ -2539,7 +15805,7 @@ long vim_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum,
}
rex_in_use = true;
- long result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out);
+ int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out);
rmp->regprog->re_in_use = false;
// NFA engine aborted because it's very slow, use backtracking engine instead.
diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h
index dcc58fa34c..447f4860a9 100644
--- a/src/nvim/regexp.h
+++ b/src/nvim/regexp.h
@@ -1,27 +1,22 @@
-#ifndef NVIM_REGEXP_H
-#define NVIM_REGEXP_H
+#pragma once
-#include "nvim/buffer_defs.h"
-#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/regexp_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
// Second argument for vim_regcomp().
#define RE_MAGIC 1 ///< 'magic' option
#define RE_STRING 2 ///< match in string instead of buffer text
#define RE_STRICT 4 ///< don't allow [abc] without ]
#define RE_AUTO 8 ///< automatic engine selection
+#define RE_NOBREAK 16 ///< don't use breakcheck functions
// values for reg_do_extmatch
#define REX_SET 1 ///< to allow \z\(...\),
#define REX_USE 2 ///< to allow \z\1 et al.
#define REX_ALL (REX_SET | REX_USE)
-// regexp.c
-// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "regexp.h.generated.h"
-# include "regexp_bt.h.generated.h"
#endif
-// uncrustify:on
-
-#endif // NVIM_REGEXP_H
diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c
deleted file mode 100644
index af3d93f7c4..0000000000
--- a/src/nvim/regexp_bt.c
+++ /dev/null
@@ -1,5661 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// Backtracking regular expression implementation.
-//
-// This file is included in "regexp.c".
-//
-// NOTICE:
-//
-// This is NOT the original regular expression code as written by Henry
-// Spencer. This code has been modified specifically for use with the VIM
-// editor, and should not be used separately from Vim. If you want a good
-// regular expression library, get the original code. The copyright notice
-// that follows is from the original.
-//
-// END NOTICE
-//
-// Copyright (c) 1986 by University of Toronto.
-// Written by Henry Spencer. Not derived from licensed software.
-//
-// Permission is granted to anyone to use this software for any
-// purpose on any computer system, and to redistribute it freely,
-// subject to the following restrictions:
-//
-// 1. The author is not responsible for the consequences of use of
-// this software, no matter how awful, even if they arise
-// from defects in it.
-//
-// 2. The origin of this software must not be misrepresented, either
-// by explicit claim or by omission.
-//
-// 3. Altered versions must be plainly marked as such, and must not
-// be misrepresented as being the original software.
-//
-// Beware that some of this code is subtly aware of the way operator
-// precedence is structured in regular expressions. Serious changes in
-// regular-expression syntax might require a total rethink.
-//
-// Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert
-// Webb, Ciaran McCreesh and Bram Moolenaar.
-// Named character class support added by Walter Briscoe (1998 Jul 01)
-
-// The "internal use only" fields in regexp_defs.h are present to pass info from
-// compile to execute that permits the execute phase to run lots faster on
-// simple cases. They are:
-//
-// regstart char that must begin a match; NUL if none obvious; Can be a
-// multi-byte character.
-// reganch is the match anchored (at beginning-of-line only)?
-// regmust string (pointer into program) that match must include, or NULL
-// regmlen length of regmust string
-// regflags RF_ values or'ed together
-//
-// Regstart and reganch permit very fast decisions on suitable starting points
-// for a match, cutting down the work a lot. Regmust permits fast rejection
-// of lines that cannot possibly match. The regmust tests are costly enough
-// that vim_regcomp() supplies a regmust only if the r.e. contains something
-// potentially expensive (at present, the only such thing detected is * or +
-// at the start of the r.e., which can involve a lot of backup). Regmlen is
-// supplied because the test in vim_regexec() needs it and vim_regcomp() is
-// computing it anyway.
-
-// Structure for regexp "program". This is essentially a linear encoding
-// of a nondeterministic finite-state machine (aka syntax charts or
-// "railroad normal form" in parsing technology). Each node is an opcode
-// plus a "next" pointer, possibly plus an operand. "Next" pointers of
-// all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next"
-// pointer with a BRANCH on both ends of it is connecting two alternatives.
-// (Here we have one of the subtle syntax dependencies: an individual BRANCH
-// (as opposed to a collection of them) is never concatenated with anything
-// because of operator precedence). The "next" pointer of a BRACES_COMPLEX
-// node points to the node after the stuff to be repeated.
-// The operand of some types of node is a literal string; for others, it is a
-// node leading into a sub-FSM. In particular, the operand of a BRANCH node
-// is the first node of the branch.
-// (NB this is *not* a tree structure: the tail of the branch connects to the
-// thing following the set of BRANCHes.)
-//
-// pattern is coded like:
-//
-// +-----------------+
-// | V
-// <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END
-// | ^ | ^
-// +------+ +----------+
-//
-//
-// +------------------+
-// V |
-// <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END
-// | | ^ ^
-// | +---------------+ |
-// +---------------------------------------------+
-//
-//
-// +----------------------+
-// V |
-// <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END
-// | | ^ ^
-// | +-----------+ |
-// +--------------------------------------------------+
-//
-//
-// +-------------------------+
-// V |
-// <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END
-// | | ^
-// | +----------------+
-// +-----------------------------------------------+
-//
-//
-// <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END
-// | | ^ ^
-// | +----------------+ |
-// +--------------------------------+
-//
-// +---------+
-// | V
-// \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END
-// | | | | ^ ^
-// | | | +-----+ |
-// | | +----------------+ |
-// | +---------------------------+ |
-// +------------------------------------------------------+
-//
-// They all start with a BRANCH for "\|" alternatives, even when there is only
-// one alternative.
-
-#include <assert.h>
-#include <inttypes.h>
-#include <stdbool.h>
-#include <string.h>
-
-#include "nvim/garray.h"
-#include "nvim/profile.h"
-#include "nvim/regexp.h"
-
-// The opcodes are:
-
-// definition number opnd? meaning
-#define END 0 // End of program or NOMATCH operand.
-#define BOL 1 // Match "" at beginning of line.
-#define EOL 2 // Match "" at end of line.
-#define BRANCH 3 // node Match this alternative, or the
- // next...
-#define BACK 4 // Match "", "next" ptr points backward.
-#define EXACTLY 5 // str Match this string.
-#define NOTHING 6 // Match empty string.
-#define STAR 7 // node Match this (simple) thing 0 or more
- // times.
-#define PLUS 8 // node Match this (simple) thing 1 or more
- // times.
-#define MATCH 9 // node match the operand zero-width
-#define NOMATCH 10 // node check for no match with operand
-#define BEHIND 11 // node look behind for a match with operand
-#define NOBEHIND 12 // node look behind for no match with operand
-#define SUBPAT 13 // node match the operand here
-#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and
- // n times (\{m,n\}).
-#define BOW 15 // Match "" after [^a-zA-Z0-9_]
-#define EOW 16 // Match "" at [^a-zA-Z0-9_]
-#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE
- // and BRACE_COMPLEX.
-#define NEWL 18 // Match line-break
-#define BHPOS 19 // End position for BEHIND or NOBEHIND
-
-// character classes: 20-48 normal, 50-78 include a line-break
-#define ADD_NL 30
-#define FIRST_NL ANY + ADD_NL
-#define ANY 20 // Match any one character.
-#define ANYOF 21 // str Match any character in this string.
-#define ANYBUT 22 // str Match any character not in this
- // string.
-#define IDENT 23 // Match identifier char
-#define SIDENT 24 // Match identifier char but no digit
-#define KWORD 25 // Match keyword char
-#define SKWORD 26 // Match word char but no digit
-#define FNAME 27 // Match file name char
-#define SFNAME 28 // Match file name char but no digit
-#define PRINT 29 // Match printable char
-#define SPRINT 30 // Match printable char but no digit
-#define WHITE 31 // Match whitespace char
-#define NWHITE 32 // Match non-whitespace char
-#define DIGIT 33 // Match digit char
-#define NDIGIT 34 // Match non-digit char
-#define HEX 35 // Match hex char
-#define NHEX 36 // Match non-hex char
-#define OCTAL 37 // Match octal char
-#define NOCTAL 38 // Match non-octal char
-#define WORD 39 // Match word char
-#define NWORD 40 // Match non-word char
-#define HEAD 41 // Match head char
-#define NHEAD 42 // Match non-head char
-#define ALPHA 43 // Match alpha char
-#define NALPHA 44 // Match non-alpha char
-#define LOWER 45 // Match lowercase char
-#define NLOWER 46 // Match non-lowercase char
-#define UPPER 47 // Match uppercase char
-#define NUPPER 48 // Match non-uppercase char
-#define LAST_NL NUPPER + ADD_NL
-// -V:WITH_NL:560
-#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL)
-
-#define MOPEN 80 // -89 Mark this point in input as start of
- // \( … \) subexpr. MOPEN + 0 marks start of
- // match.
-#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks
- // end of match.
-#define BACKREF 100 // -109 node Match same string again \1-\9.
-
-#define ZOPEN 110 // -119 Mark this point in input as start of
- // \z( … \) subexpr.
-#define ZCLOSE 120 // -129 Analogous to ZOPEN.
-#define ZREF 130 // -139 node Match external submatch \z1-\z9
-
-#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times
-
-#define NOPEN 150 // Mark this point in input as start of
- // \%( subexpr.
-#define NCLOSE 151 // Analogous to NOPEN.
-
-#define MULTIBYTECODE 200 // mbc Match one multi-byte character
-#define RE_BOF 201 // Match "" at beginning of file.
-#define RE_EOF 202 // Match "" at end of file.
-#define CURSOR 203 // Match location of cursor.
-
-#define RE_LNUM 204 // nr cmp Match line number
-#define RE_COL 205 // nr cmp Match column number
-#define RE_VCOL 206 // nr cmp Match virtual column number
-
-#define RE_MARK 207 // mark cmp Match mark position
-#define RE_VISUAL 208 // Match Visual area
-#define RE_COMPOSING 209 // any composing characters
-
-// Flags to be passed up and down.
-#define HASWIDTH 0x1 // Known never to match null string.
-#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand.
-#define SPSTART 0x4 // Starts with * or +.
-#define HASNL 0x8 // Contains some \n.
-#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!".
-#define WORST 0 // Worst case.
-
-static int prevchr_len; ///< byte length of previous char
-static int num_complex_braces; ///< Complex \{...} count
-static uint8_t *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
-static long regsize; ///< Code size.
-static int reg_toolong; ///< true when offset out of range
-static uint8_t had_endbrace[NSUBEXP]; ///< flags, true if end of () found
-static long brace_min[10]; ///< Minimums for complex brace repeats
-static long brace_max[10]; ///< Maximums for complex brace repeats
-static int brace_count[10]; ///< Current counts for complex brace repeats
-static int one_exactly = false; ///< only do one char for EXACTLY
-
-// When making changes to classchars also change nfa_classcodes.
-static uint8_t *classchars = (uint8_t *)".iIkKfFpPsSdDxXoOwWhHaAlLuU";
-static int classcodes[] = {
- ANY, IDENT, SIDENT, KWORD, SKWORD,
- FNAME, SFNAME, PRINT, SPRINT,
- WHITE, NWHITE, DIGIT, NDIGIT,
- HEX, NHEX, OCTAL, NOCTAL,
- WORD, NWORD, HEAD, NHEAD,
- ALPHA, NALPHA, LOWER, NLOWER,
- UPPER, NUPPER
-};
-
-// When regcode is set to this value, code is not emitted and size is computed
-// instead.
-#define JUST_CALC_SIZE ((uint8_t *)-1)
-
-// Values for rs_state in regitem_T.
-typedef enum regstate_E {
- RS_NOPEN = 0, // NOPEN and NCLOSE
- RS_MOPEN, // MOPEN + [0-9]
- RS_MCLOSE, // MCLOSE + [0-9]
- RS_ZOPEN, // ZOPEN + [0-9]
- RS_ZCLOSE, // ZCLOSE + [0-9]
- RS_BRANCH, // BRANCH
- RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match
- RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match
- RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match
- RS_NOMATCH, // NOMATCH
- RS_BEHIND1, // BEHIND / NOBEHIND matching rest
- RS_BEHIND2, // BEHIND / NOBEHIND matching behind part
- RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match
- RS_STAR_SHORT, // STAR/PLUS/BRACE_SIMPLE shortest match
-} regstate_T;
-
-// Structure used to save the current input state, when it needs to be
-// restored after trying a match. Used by reg_save() and reg_restore().
-// Also stores the length of "backpos".
-typedef struct {
- union {
- uint8_t *ptr; // rex.input pointer, for single-line regexp
- lpos_T pos; // rex.input pos, for multi-line regexp
- } rs_u;
- int rs_len;
-} regsave_T;
-
-// struct to save start/end pointer/position in for \(\)
-typedef struct {
- union {
- uint8_t *ptr;
- lpos_T pos;
- } se_u;
-} save_se_T;
-
-// used for BEHIND and NOBEHIND matching
-typedef struct regbehind_S {
- regsave_T save_after;
- regsave_T save_behind;
- int save_need_clear_subexpr;
- save_se_T save_start[NSUBEXP];
- save_se_T save_end[NSUBEXP];
-} regbehind_T;
-
-// When there are alternatives a regstate_T is put on the regstack to remember
-// what we are doing.
-// Before it may be another type of item, depending on rs_state, to remember
-// more things.
-typedef struct regitem_S {
- regstate_T rs_state; // what we are doing, one of RS_ above
- int16_t rs_no; // submatch nr or BEHIND/NOBEHIND
- uint8_t *rs_scan; // current node in program
- union {
- save_se_T sesave;
- regsave_T regsave;
- } rs_un; // room for saving rex.input
-} regitem_T;
-
-// used for STAR, PLUS and BRACE_SIMPLE matching
-typedef struct regstar_S {
- int nextb; // next byte
- int nextb_ic; // next byte reverse case
- long count;
- long minval;
- long maxval;
-} regstar_T;
-
-// used to store input position when a BACK was encountered, so that we now if
-// we made any progress since the last time.
-typedef struct backpos_S {
- uint8_t *bp_scan; // "scan" where BACK was encountered
- regsave_T bp_pos; // last input position
-} backpos_T;
-
-// "regstack" and "backpos" are used by regmatch(). They are kept over calls
-// to avoid invoking malloc() and free() often.
-// "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T
-// or regbehind_T.
-// "backpos_T" is a table with backpos_T for BACK
-static garray_T regstack = GA_EMPTY_INIT_VALUE;
-static garray_T backpos = GA_EMPTY_INIT_VALUE;
-
-static regsave_T behind_pos;
-
-// Both for regstack and backpos tables we use the following strategy of
-// allocation (to reduce malloc/free calls):
-// - Initial size is fairly small.
-// - When needed, the tables are grown bigger (8 times at first, double after
-// that).
-// - After executing the match we free the memory only if the array has grown.
-// Thus the memory is kept allocated when it's at the initial size.
-// This makes it fast while not keeping a lot of memory allocated.
-// A three times speed increase was observed when using many simple patterns.
-#define REGSTACK_INITIAL 2048
-#define BACKPOS_INITIAL 64
-
-// Opcode notes:
-//
-// BRANCH The set of branches constituting a single choice are hooked
-// together with their "next" pointers, since precedence prevents
-// anything being concatenated to any individual branch. The
-// "next" pointer of the last BRANCH in a choice points to the
-// thing following the whole choice. This is also where the
-// final "next" pointer of each individual branch points; each
-// branch starts with the operand node of a BRANCH node.
-//
-// BACK Normal "next" pointers all implicitly point forward; BACK
-// exists to make loop structures possible.
-//
-// STAR,PLUS '=', and complex '*' and '+', are implemented as circular
-// BRANCH structures using BACK. Simple cases (one character
-// per match) are implemented with STAR and PLUS for speed
-// and to minimize recursive plunges.
-//
-// BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX
-// node, and defines the min and max limits to be used for that
-// node.
-//
-// MOPEN,MCLOSE ...are numbered at compile time.
-// ZOPEN,ZCLOSE ...ditto
-///
-//
-//
-// A node is one char of opcode followed by two chars of "next" pointer.
-// "Next" pointers are stored as two 8-bit bytes, high order first. The
-// value is a positive offset from the opcode of the node containing it.
-// An operand, if any, simply follows the node. (Note that much of the
-// code generation knows about this implicit relationship.)
-//
-// Using two bytes for the "next" pointer is vast overkill for most things,
-// but allows patterns to get big without disasters.
-#define OP(p) ((int)(*(p)))
-#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377))
-#define OPERAND(p) ((p) + 3)
-// Obtain an operand that was stored as four bytes, MSB first.
-#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \
- + ((long)(p)[5] << 8) + (long)(p)[6])
-// Obtain a second operand stored as four bytes.
-#define OPERAND_MAX(p) OPERAND_MIN((p) + 4)
-// Obtain a second single-byte operand stored after a four bytes operand.
-#define OPERAND_CMP(p) (p)[7]
-
-static uint8_t *reg(int paren, int *flagp);
-
-#ifdef BT_REGEXP_DUMP
-static void regdump(uint8_t *, bt_regprog_T *);
-#endif
-
-#ifdef REGEXP_DEBUG
-static uint8_t *regprop(uint8_t *);
-
-static int regnarrate = 0;
-#endif
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "regexp_bt.c.generated.h"
-#endif
-
-// Setup to parse the regexp. Used once to get the length and once to do it.
-static void regcomp_start(uint8_t *expr, int re_flags) // see vim_regcomp()
-{
- initchr((char *)expr);
- if (re_flags & RE_MAGIC) {
- reg_magic = MAGIC_ON;
- } else {
- reg_magic = MAGIC_OFF;
- }
- reg_string = (re_flags & RE_STRING);
- reg_strict = (re_flags & RE_STRICT);
- get_cpo_flags();
-
- num_complex_braces = 0;
- regnpar = 1;
- CLEAR_FIELD(had_endbrace);
- regnzpar = 1;
- re_has_z = 0;
- regsize = 0L;
- reg_toolong = false;
- regflags = 0;
- had_eol = false;
-}
-
-// Return true if MULTIBYTECODE should be used instead of EXACTLY for
-// character "c".
-static bool use_multibytecode(int c)
-{
- return utf_char2len(c) > 1
- && (re_multi_type(peekchr()) != NOT_MULTI
- || utf_iscomposing(c));
-}
-
-// Emit (if appropriate) a byte of code
-static void regc(int b)
-{
- if (regcode == JUST_CALC_SIZE) {
- regsize++;
- } else {
- *regcode++ = (uint8_t)b;
- }
-}
-
-// Emit (if appropriate) a multi-byte character of code
-static void regmbc(int c)
-{
- if (regcode == JUST_CALC_SIZE) {
- regsize += utf_char2len(c);
- } else {
- regcode += utf_char2bytes(c, (char *)regcode);
- }
-}
-
-// Produce the bytes for equivalence class "c".
-// Currently only handles latin1, latin9 and utf-8.
-// NOTE: When changing this function, also change nfa_emit_equi_class()
-static void reg_equi_class(int c)
-{
- {
- switch (c) {
- // Do not use '\300' style, it results in a negative number.
- case 'A':
- case 0xc0:
- case 0xc1:
- case 0xc2:
- case 0xc3:
- case 0xc4:
- case 0xc5:
- case 0x100:
- case 0x102:
- case 0x104:
- case 0x1cd:
- case 0x1de:
- case 0x1e0:
- case 0x1fa:
- case 0x202:
- case 0x226:
- case 0x23a:
- case 0x1e00:
- case 0x1ea0:
- case 0x1ea2:
- case 0x1ea4:
- case 0x1ea6:
- case 0x1ea8:
- case 0x1eaa:
- case 0x1eac:
- case 0x1eae:
- case 0x1eb0:
- case 0x1eb2:
- case 0x1eb4:
- case 0x1eb6:
- regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2);
- regmbc(0xc3); regmbc(0xc4); regmbc(0xc5);
- regmbc(0x100); regmbc(0x102); regmbc(0x104);
- regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0);
- regmbc(0x1fa); regmbc(0x202); regmbc(0x226);
- regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0);
- regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6);
- regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac);
- regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2);
- regmbc(0x1eb4); regmbc(0x1eb6);
- return;
- case 'B':
- case 0x181:
- case 0x243:
- case 0x1e02:
- case 0x1e04:
- case 0x1e06:
- regmbc('B');
- regmbc(0x181); regmbc(0x243); regmbc(0x1e02);
- regmbc(0x1e04); regmbc(0x1e06);
- return;
- case 'C':
- case 0xc7:
- case 0x106:
- case 0x108:
- case 0x10a:
- case 0x10c:
- case 0x187:
- case 0x23b:
- case 0x1e08:
- case 0xa792:
- regmbc('C'); regmbc(0xc7);
- regmbc(0x106); regmbc(0x108); regmbc(0x10a);
- regmbc(0x10c); regmbc(0x187); regmbc(0x23b);
- regmbc(0x1e08); regmbc(0xa792);
- return;
- case 'D':
- case 0x10e:
- case 0x110:
- case 0x18a:
- case 0x1e0a:
- case 0x1e0c:
- case 0x1e0e:
- case 0x1e10:
- case 0x1e12:
- regmbc('D'); regmbc(0x10e); regmbc(0x110);
- regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c);
- regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12);
- return;
- case 'E':
- case 0xc8:
- case 0xc9:
- case 0xca:
- case 0xcb:
- case 0x112:
- case 0x114:
- case 0x116:
- case 0x118:
- case 0x11a:
- case 0x204:
- case 0x206:
- case 0x228:
- case 0x246:
- case 0x1e14:
- case 0x1e16:
- case 0x1e18:
- case 0x1e1a:
- case 0x1e1c:
- case 0x1eb8:
- case 0x1eba:
- case 0x1ebc:
- case 0x1ebe:
- case 0x1ec0:
- case 0x1ec2:
- case 0x1ec4:
- case 0x1ec6:
- regmbc('E'); regmbc(0xc8); regmbc(0xc9);
- regmbc(0xca); regmbc(0xcb); regmbc(0x112);
- regmbc(0x114); regmbc(0x116); regmbc(0x118);
- regmbc(0x11a); regmbc(0x204); regmbc(0x206);
- regmbc(0x228); regmbc(0x246); regmbc(0x1e14);
- regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a);
- regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba);
- regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0);
- regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6);
- return;
- case 'F':
- case 0x191:
- case 0x1e1e:
- case 0xa798:
- regmbc('F'); regmbc(0x191); regmbc(0x1e1e);
- regmbc(0xa798);
- return;
- case 'G':
- case 0x11c:
- case 0x11e:
- case 0x120:
- case 0x122:
- case 0x193:
- case 0x1e4:
- case 0x1e6:
- case 0x1f4:
- case 0x1e20:
- case 0xa7a0:
- regmbc('G'); regmbc(0x11c); regmbc(0x11e);
- regmbc(0x120); regmbc(0x122); regmbc(0x193);
- regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4);
- regmbc(0x1e20); regmbc(0xa7a0);
- return;
- case 'H':
- case 0x124:
- case 0x126:
- case 0x21e:
- case 0x1e22:
- case 0x1e24:
- case 0x1e26:
- case 0x1e28:
- case 0x1e2a:
- case 0x2c67:
- regmbc('H'); regmbc(0x124); regmbc(0x126);
- regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24);
- regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a);
- regmbc(0x2c67);
- return;
- case 'I':
- case 0xcc:
- case 0xcd:
- case 0xce:
- case 0xcf:
- case 0x128:
- case 0x12a:
- case 0x12c:
- case 0x12e:
- case 0x130:
- case 0x197:
- case 0x1cf:
- case 0x208:
- case 0x20a:
- case 0x1e2c:
- case 0x1e2e:
- case 0x1ec8:
- case 0x1eca:
- regmbc('I'); regmbc(0xcc); regmbc(0xcd);
- regmbc(0xce); regmbc(0xcf); regmbc(0x128);
- regmbc(0x12a); regmbc(0x12c); regmbc(0x12e);
- regmbc(0x130); regmbc(0x197); regmbc(0x1cf);
- regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c);
- regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca);
- return;
- case 'J':
- case 0x134:
- case 0x248:
- regmbc('J'); regmbc(0x134); regmbc(0x248);
- return;
- case 'K':
- case 0x136:
- case 0x198:
- case 0x1e8:
- case 0x1e30:
- case 0x1e32:
- case 0x1e34:
- case 0x2c69:
- case 0xa740:
- regmbc('K'); regmbc(0x136); regmbc(0x198);
- regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32);
- regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740);
- return;
- case 'L':
- case 0x139:
- case 0x13b:
- case 0x13d:
- case 0x13f:
- case 0x141:
- case 0x23d:
- case 0x1e36:
- case 0x1e38:
- case 0x1e3a:
- case 0x1e3c:
- case 0x2c60:
- regmbc('L'); regmbc(0x139); regmbc(0x13b);
- regmbc(0x13d); regmbc(0x13f); regmbc(0x141);
- regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38);
- regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60);
- return;
- case 'M':
- case 0x1e3e:
- case 0x1e40:
- case 0x1e42:
- regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40);
- regmbc(0x1e42);
- return;
- case 'N':
- case 0xd1:
- case 0x143:
- case 0x145:
- case 0x147:
- case 0x1f8:
- case 0x1e44:
- case 0x1e46:
- case 0x1e48:
- case 0x1e4a:
- case 0xa7a4:
- regmbc('N'); regmbc(0xd1);
- regmbc(0x143); regmbc(0x145); regmbc(0x147);
- regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46);
- regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4);
- return;
- case 'O':
- case 0xd2:
- case 0xd3:
- case 0xd4:
- case 0xd5:
- case 0xd6:
- case 0xd8:
- case 0x14c:
- case 0x14e:
- case 0x150:
- case 0x19f:
- case 0x1a0:
- case 0x1d1:
- case 0x1ea:
- case 0x1ec:
- case 0x1fe:
- case 0x20c:
- case 0x20e:
- case 0x22a:
- case 0x22c:
- case 0x22e:
- case 0x230:
- case 0x1e4c:
- case 0x1e4e:
- case 0x1e50:
- case 0x1e52:
- case 0x1ecc:
- case 0x1ece:
- case 0x1ed0:
- case 0x1ed2:
- case 0x1ed4:
- case 0x1ed6:
- case 0x1ed8:
- case 0x1eda:
- case 0x1edc:
- case 0x1ede:
- case 0x1ee0:
- case 0x1ee2:
- regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4);
- regmbc(0xd5); regmbc(0xd6); regmbc(0xd8);
- regmbc(0x14c); regmbc(0x14e); regmbc(0x150);
- regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1);
- regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe);
- regmbc(0x20c); regmbc(0x20e); regmbc(0x22a);
- regmbc(0x22c); regmbc(0x22e); regmbc(0x230);
- regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50);
- regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece);
- regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4);
- regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda);
- regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0);
- regmbc(0x1ee2);
- return;
- case 'P':
- case 0x1a4:
- case 0x1e54:
- case 0x1e56:
- case 0x2c63:
- regmbc('P'); regmbc(0x1a4); regmbc(0x1e54);
- regmbc(0x1e56); regmbc(0x2c63);
- return;
- case 'Q':
- case 0x24a:
- regmbc('Q'); regmbc(0x24a);
- return;
- case 'R':
- case 0x154:
- case 0x156:
- case 0x158:
- case 0x210:
- case 0x212:
- case 0x24c:
- case 0x1e58:
- case 0x1e5a:
- case 0x1e5c:
- case 0x1e5e:
- case 0x2c64:
- case 0xa7a6:
- regmbc('R'); regmbc(0x154); regmbc(0x156);
- regmbc(0x210); regmbc(0x212); regmbc(0x158);
- regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a);
- regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64);
- regmbc(0xa7a6);
- return;
- case 'S':
- case 0x15a:
- case 0x15c:
- case 0x15e:
- case 0x160:
- case 0x218:
- case 0x1e60:
- case 0x1e62:
- case 0x1e64:
- case 0x1e66:
- case 0x1e68:
- case 0x2c7e:
- case 0xa7a8:
- regmbc('S'); regmbc(0x15a); regmbc(0x15c);
- regmbc(0x15e); regmbc(0x160); regmbc(0x218);
- regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64);
- regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e);
- regmbc(0xa7a8);
- return;
- case 'T':
- case 0x162:
- case 0x164:
- case 0x166:
- case 0x1ac:
- case 0x1ae:
- case 0x21a:
- case 0x23e:
- case 0x1e6a:
- case 0x1e6c:
- case 0x1e6e:
- case 0x1e70:
- regmbc('T'); regmbc(0x162); regmbc(0x164);
- regmbc(0x166); regmbc(0x1ac); regmbc(0x23e);
- regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a);
- regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70);
- return;
- case 'U':
- case 0xd9:
- case 0xda:
- case 0xdb:
- case 0xdc:
- case 0x168:
- case 0x16a:
- case 0x16c:
- case 0x16e:
- case 0x170:
- case 0x172:
- case 0x1af:
- case 0x1d3:
- case 0x1d5:
- case 0x1d7:
- case 0x1d9:
- case 0x1db:
- case 0x214:
- case 0x216:
- case 0x244:
- case 0x1e72:
- case 0x1e74:
- case 0x1e76:
- case 0x1e78:
- case 0x1e7a:
- case 0x1ee4:
- case 0x1ee6:
- case 0x1ee8:
- case 0x1eea:
- case 0x1eec:
- case 0x1eee:
- case 0x1ef0:
- regmbc('U'); regmbc(0xd9); regmbc(0xda);
- regmbc(0xdb); regmbc(0xdc); regmbc(0x168);
- regmbc(0x16a); regmbc(0x16c); regmbc(0x16e);
- regmbc(0x170); regmbc(0x172); regmbc(0x1af);
- regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7);
- regmbc(0x1d9); regmbc(0x1db); regmbc(0x214);
- regmbc(0x216); regmbc(0x244); regmbc(0x1e72);
- regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78);
- regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6);
- regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec);
- regmbc(0x1eee); regmbc(0x1ef0);
- return;
- case 'V':
- case 0x1b2:
- case 0x1e7c:
- case 0x1e7e:
- regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c);
- regmbc(0x1e7e);
- return;
- case 'W':
- case 0x174:
- case 0x1e80:
- case 0x1e82:
- case 0x1e84:
- case 0x1e86:
- case 0x1e88:
- regmbc('W'); regmbc(0x174); regmbc(0x1e80);
- regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86);
- regmbc(0x1e88);
- return;
- case 'X':
- case 0x1e8a:
- case 0x1e8c:
- regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c);
- return;
- case 'Y':
- case 0xdd:
- case 0x176:
- case 0x178:
- case 0x1b3:
- case 0x232:
- case 0x24e:
- case 0x1e8e:
- case 0x1ef2:
- case 0x1ef6:
- case 0x1ef4:
- case 0x1ef8:
- regmbc('Y'); regmbc(0xdd); regmbc(0x176);
- regmbc(0x178); regmbc(0x1b3); regmbc(0x232);
- regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2);
- regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8);
- return;
- case 'Z':
- case 0x179:
- case 0x17b:
- case 0x17d:
- case 0x1b5:
- case 0x1e90:
- case 0x1e92:
- case 0x1e94:
- case 0x2c6b:
- regmbc('Z'); regmbc(0x179); regmbc(0x17b);
- regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90);
- regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b);
- return;
- case 'a':
- case 0xe0:
- case 0xe1:
- case 0xe2:
- case 0xe3:
- case 0xe4:
- case 0xe5:
- case 0x101:
- case 0x103:
- case 0x105:
- case 0x1ce:
- case 0x1df:
- case 0x1e1:
- case 0x1fb:
- case 0x201:
- case 0x203:
- case 0x227:
- case 0x1d8f:
- case 0x1e01:
- case 0x1e9a:
- case 0x1ea1:
- case 0x1ea3:
- case 0x1ea5:
- case 0x1ea7:
- case 0x1ea9:
- case 0x1eab:
- case 0x1ead:
- case 0x1eaf:
- case 0x1eb1:
- case 0x1eb3:
- case 0x1eb5:
- case 0x1eb7:
- case 0x2c65:
- regmbc('a'); regmbc(0xe0); regmbc(0xe1);
- regmbc(0xe2); regmbc(0xe3); regmbc(0xe4);
- regmbc(0xe5); regmbc(0x101); regmbc(0x103);
- regmbc(0x105); regmbc(0x1ce); regmbc(0x1df);
- regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201);
- regmbc(0x203); regmbc(0x227); regmbc(0x1d8f);
- regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1);
- regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7);
- regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead);
- regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3);
- regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65);
- return;
- case 'b':
- case 0x180:
- case 0x253:
- case 0x1d6c:
- case 0x1d80:
- case 0x1e03:
- case 0x1e05:
- case 0x1e07:
- regmbc('b');
- regmbc(0x180); regmbc(0x253); regmbc(0x1d6c);
- regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05);
- regmbc(0x1e07);
- return;
- case 'c':
- case 0xe7:
- case 0x107:
- case 0x109:
- case 0x10b:
- case 0x10d:
- case 0x188:
- case 0x23c:
- case 0x1e09:
- case 0xa793:
- case 0xa794:
- regmbc('c'); regmbc(0xe7); regmbc(0x107);
- regmbc(0x109); regmbc(0x10b); regmbc(0x10d);
- regmbc(0x188); regmbc(0x23c); regmbc(0x1e09);
- regmbc(0xa793); regmbc(0xa794);
- return;
- case 'd':
- case 0x10f:
- case 0x111:
- case 0x257:
- case 0x1d6d:
- case 0x1d81:
- case 0x1d91:
- case 0x1e0b:
- case 0x1e0d:
- case 0x1e0f:
- case 0x1e11:
- case 0x1e13:
- regmbc('d'); regmbc(0x10f); regmbc(0x111);
- regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81);
- regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d);
- regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13);
- return;
- case 'e':
- case 0xe8:
- case 0xe9:
- case 0xea:
- case 0xeb:
- case 0x113:
- case 0x115:
- case 0x117:
- case 0x119:
- case 0x11b:
- case 0x205:
- case 0x207:
- case 0x229:
- case 0x247:
- case 0x1d92:
- case 0x1e15:
- case 0x1e17:
- case 0x1e19:
- case 0x1e1b:
- case 0x1eb9:
- case 0x1ebb:
- case 0x1e1d:
- case 0x1ebd:
- case 0x1ebf:
- case 0x1ec1:
- case 0x1ec3:
- case 0x1ec5:
- case 0x1ec7:
- regmbc('e'); regmbc(0xe8); regmbc(0xe9);
- regmbc(0xea); regmbc(0xeb); regmbc(0x113);
- regmbc(0x115); regmbc(0x117); regmbc(0x119);
- regmbc(0x11b); regmbc(0x205); regmbc(0x207);
- regmbc(0x229); regmbc(0x247); regmbc(0x1d92);
- regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19);
- regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9);
- regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf);
- regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5);
- regmbc(0x1ec7);
- return;
- case 'f':
- case 0x192:
- case 0x1d6e:
- case 0x1d82:
- case 0x1e1f:
- case 0xa799:
- regmbc('f'); regmbc(0x192); regmbc(0x1d6e);
- regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799);
- return;
- case 'g':
- case 0x11d:
- case 0x11f:
- case 0x121:
- case 0x123:
- case 0x1e5:
- case 0x1e7:
- case 0x260:
- case 0x1f5:
- case 0x1d83:
- case 0x1e21:
- case 0xa7a1:
- regmbc('g'); regmbc(0x11d); regmbc(0x11f);
- regmbc(0x121); regmbc(0x123); regmbc(0x1e5);
- regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260);
- regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1);
- return;
- case 'h':
- case 0x125:
- case 0x127:
- case 0x21f:
- case 0x1e23:
- case 0x1e25:
- case 0x1e27:
- case 0x1e29:
- case 0x1e2b:
- case 0x1e96:
- case 0x2c68:
- case 0xa795:
- regmbc('h'); regmbc(0x125); regmbc(0x127);
- regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25);
- regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b);
- regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795);
- return;
- case 'i':
- case 0xec:
- case 0xed:
- case 0xee:
- case 0xef:
- case 0x129:
- case 0x12b:
- case 0x12d:
- case 0x12f:
- case 0x1d0:
- case 0x209:
- case 0x20b:
- case 0x268:
- case 0x1d96:
- case 0x1e2d:
- case 0x1e2f:
- case 0x1ec9:
- case 0x1ecb:
- regmbc('i'); regmbc(0xec); regmbc(0xed);
- regmbc(0xee); regmbc(0xef); regmbc(0x129);
- regmbc(0x12b); regmbc(0x12d); regmbc(0x12f);
- regmbc(0x1d0); regmbc(0x209); regmbc(0x20b);
- regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d);
- regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb);
- return;
- case 'j':
- case 0x135:
- case 0x1f0:
- case 0x249:
- regmbc('j'); regmbc(0x135); regmbc(0x1f0);
- regmbc(0x249);
- return;
- case 'k':
- case 0x137:
- case 0x199:
- case 0x1e9:
- case 0x1d84:
- case 0x1e31:
- case 0x1e33:
- case 0x1e35:
- case 0x2c6a:
- case 0xa741:
- regmbc('k'); regmbc(0x137); regmbc(0x199);
- regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31);
- regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a);
- regmbc(0xa741);
- return;
- case 'l':
- case 0x13a:
- case 0x13c:
- case 0x13e:
- case 0x140:
- case 0x142:
- case 0x19a:
- case 0x1e37:
- case 0x1e39:
- case 0x1e3b:
- case 0x1e3d:
- case 0x2c61:
- regmbc('l'); regmbc(0x13a); regmbc(0x13c);
- regmbc(0x13e); regmbc(0x140); regmbc(0x142);
- regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39);
- regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61);
- return;
- case 'm':
- case 0x1d6f:
- case 0x1e3f:
- case 0x1e41:
- case 0x1e43:
- regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f);
- regmbc(0x1e41); regmbc(0x1e43);
- return;
- case 'n':
- case 0xf1:
- case 0x144:
- case 0x146:
- case 0x148:
- case 0x149:
- case 0x1f9:
- case 0x1d70:
- case 0x1d87:
- case 0x1e45:
- case 0x1e47:
- case 0x1e49:
- case 0x1e4b:
- case 0xa7a5:
- regmbc('n'); regmbc(0xf1); regmbc(0x144);
- regmbc(0x146); regmbc(0x148); regmbc(0x149);
- regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87);
- regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49);
- regmbc(0x1e4b); regmbc(0xa7a5);
- return;
- case 'o':
- case 0xf2:
- case 0xf3:
- case 0xf4:
- case 0xf5:
- case 0xf6:
- case 0xf8:
- case 0x14d:
- case 0x14f:
- case 0x151:
- case 0x1a1:
- case 0x1d2:
- case 0x1eb:
- case 0x1ed:
- case 0x1ff:
- case 0x20d:
- case 0x20f:
- case 0x22b:
- case 0x22d:
- case 0x22f:
- case 0x231:
- case 0x275:
- case 0x1e4d:
- case 0x1e4f:
- case 0x1e51:
- case 0x1e53:
- case 0x1ecd:
- case 0x1ecf:
- case 0x1ed1:
- case 0x1ed3:
- case 0x1ed5:
- case 0x1ed7:
- case 0x1ed9:
- case 0x1edb:
- case 0x1edd:
- case 0x1edf:
- case 0x1ee1:
- case 0x1ee3:
- regmbc('o'); regmbc(0xf2); regmbc(0xf3);
- regmbc(0xf4); regmbc(0xf5); regmbc(0xf6);
- regmbc(0xf8); regmbc(0x14d); regmbc(0x14f);
- regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2);
- regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff);
- regmbc(0x20d); regmbc(0x20f); regmbc(0x22b);
- regmbc(0x22d); regmbc(0x22f); regmbc(0x231);
- regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f);
- regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd);
- regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3);
- regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9);
- regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf);
- regmbc(0x1ee1); regmbc(0x1ee3);
- return;
- case 'p':
- case 0x1a5:
- case 0x1d71:
- case 0x1d88:
- case 0x1d7d:
- case 0x1e55:
- case 0x1e57:
- regmbc('p'); regmbc(0x1a5); regmbc(0x1d71);
- regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55);
- regmbc(0x1e57);
- return;
- case 'q':
- case 0x24b:
- case 0x2a0:
- regmbc('q'); regmbc(0x24b); regmbc(0x2a0);
- return;
- case 'r':
- case 0x155:
- case 0x157:
- case 0x159:
- case 0x211:
- case 0x213:
- case 0x24d:
- case 0x27d:
- case 0x1d72:
- case 0x1d73:
- case 0x1d89:
- case 0x1e59:
- case 0x1e5b:
- case 0x1e5d:
- case 0x1e5f:
- case 0xa7a7:
- regmbc('r'); regmbc(0x155); regmbc(0x157);
- regmbc(0x159); regmbc(0x211); regmbc(0x213);
- regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73);
- regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d);
- regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f);
- regmbc(0xa7a7);
- return;
- case 's':
- case 0x15b:
- case 0x15d:
- case 0x15f:
- case 0x161:
- case 0x1e61:
- case 0x219:
- case 0x23f:
- case 0x1d74:
- case 0x1d8a:
- case 0x1e63:
- case 0x1e65:
- case 0x1e67:
- case 0x1e69:
- case 0xa7a9:
- regmbc('s'); regmbc(0x15b); regmbc(0x15d);
- regmbc(0x15f); regmbc(0x161); regmbc(0x23f);
- regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a);
- regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65);
- regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9);
- return;
- case 't':
- case 0x163:
- case 0x165:
- case 0x167:
- case 0x1ab:
- case 0x1ad:
- case 0x21b:
- case 0x288:
- case 0x1d75:
- case 0x1e6b:
- case 0x1e6d:
- case 0x1e6f:
- case 0x1e71:
- case 0x1e97:
- case 0x2c66:
- regmbc('t'); regmbc(0x163); regmbc(0x165);
- regmbc(0x167); regmbc(0x1ab); regmbc(0x21b);
- regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75);
- regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f);
- regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66);
- return;
- case 'u':
- case 0xf9:
- case 0xfa:
- case 0xfb:
- case 0xfc:
- case 0x169:
- case 0x16b:
- case 0x16d:
- case 0x16f:
- case 0x171:
- case 0x173:
- case 0x1b0:
- case 0x1d4:
- case 0x1d6:
- case 0x1d8:
- case 0x1da:
- case 0x1dc:
- case 0x215:
- case 0x217:
- case 0x289:
- case 0x1e73:
- case 0x1d7e:
- case 0x1d99:
- case 0x1e75:
- case 0x1e77:
- case 0x1e79:
- case 0x1e7b:
- case 0x1ee5:
- case 0x1ee7:
- case 0x1ee9:
- case 0x1eeb:
- case 0x1eed:
- case 0x1eef:
- case 0x1ef1:
- regmbc('u'); regmbc(0xf9); regmbc(0xfa);
- regmbc(0xfb); regmbc(0xfc); regmbc(0x169);
- regmbc(0x16b); regmbc(0x16d); regmbc(0x16f);
- regmbc(0x171); regmbc(0x173); regmbc(0x1d6);
- regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc);
- regmbc(0x215); regmbc(0x217); regmbc(0x1b0);
- regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e);
- regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75);
- regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b);
- regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9);
- regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef);
- regmbc(0x1ef1);
- return;
- case 'v':
- case 0x28b:
- case 0x1d8c:
- case 0x1e7d:
- case 0x1e7f:
- regmbc('v'); regmbc(0x28b); regmbc(0x1d8c);
- regmbc(0x1e7d); regmbc(0x1e7f);
- return;
- case 'w':
- case 0x175:
- case 0x1e81:
- case 0x1e83:
- case 0x1e85:
- case 0x1e87:
- case 0x1e89:
- case 0x1e98:
- regmbc('w'); regmbc(0x175); regmbc(0x1e81);
- regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87);
- regmbc(0x1e89); regmbc(0x1e98);
- return;
- case 'x':
- case 0x1e8b:
- case 0x1e8d:
- regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d);
- return;
- case 'y':
- case 0xfd:
- case 0xff:
- case 0x177:
- case 0x1b4:
- case 0x233:
- case 0x24f:
- case 0x1e8f:
- case 0x1e99:
- case 0x1ef3:
- case 0x1ef5:
- case 0x1ef7:
- case 0x1ef9:
- regmbc('y'); regmbc(0xfd); regmbc(0xff);
- regmbc(0x177); regmbc(0x1b4); regmbc(0x233);
- regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99);
- regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7);
- regmbc(0x1ef9);
- return;
- case 'z':
- case 0x17a:
- case 0x17c:
- case 0x17e:
- case 0x1b6:
- case 0x1d76:
- case 0x1d8e:
- case 0x1e91:
- case 0x1e93:
- case 0x1e95:
- case 0x2c6c:
- regmbc('z'); regmbc(0x17a); regmbc(0x17c);
- regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76);
- regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93);
- regmbc(0x1e95); regmbc(0x2c6c);
- return;
- }
- }
- regmbc(c);
-}
-
-// Emit a node.
-// Return pointer to generated code.
-static uint8_t *regnode(int op)
-{
- uint8_t *ret;
-
- ret = regcode;
- if (ret == JUST_CALC_SIZE) {
- regsize += 3;
- } else {
- *regcode++ = (uint8_t)op;
- *regcode++ = NUL; // Null "next" pointer.
- *regcode++ = NUL;
- }
- return ret;
-}
-
-// Write a four bytes number at "p" and return pointer to the next char.
-static uint8_t *re_put_uint32(uint8_t *p, uint32_t val)
-{
- *p++ = (uint8_t)((val >> 24) & 0377);
- *p++ = (uint8_t)((val >> 16) & 0377);
- *p++ = (uint8_t)((val >> 8) & 0377);
- *p++ = (uint8_t)(val & 0377);
- return p;
-}
-
-// regnext - dig the "next" pointer out of a node
-// Returns NULL when calculating size, when there is no next item and when
-// there is an error.
-static uint8_t *regnext(uint8_t *p)
- FUNC_ATTR_NONNULL_ALL
-{
- int offset;
-
- if (p == JUST_CALC_SIZE || reg_toolong) {
- return NULL;
- }
-
- offset = NEXT(p);
- if (offset == 0) {
- return NULL;
- }
-
- if (OP(p) == BACK) {
- return p - offset;
- } else {
- return p + offset;
- }
-}
-
-// Set the next-pointer at the end of a node chain.
-static void regtail(uint8_t *p, uint8_t *val)
-{
- int offset;
-
- if (p == JUST_CALC_SIZE) {
- return;
- }
-
- // Find last node.
- uint8_t *scan = p;
- for (;;) {
- uint8_t *temp = regnext(scan);
- if (temp == NULL) {
- break;
- }
- scan = temp;
- }
-
- if (OP(scan) == BACK) {
- offset = (int)(scan - val);
- } else {
- offset = (int)(val - scan);
- }
- // When the offset uses more than 16 bits it can no longer fit in the two
- // bytes available. Use a global flag to avoid having to check return
- // values in too many places.
- if (offset > 0xffff) {
- reg_toolong = true;
- } else {
- *(scan + 1) = (uint8_t)(((unsigned)offset >> 8) & 0377);
- *(scan + 2) = (uint8_t)(offset & 0377);
- }
-}
-
-// Like regtail, on item after a BRANCH; nop if none.
-static void regoptail(uint8_t *p, uint8_t *val)
-{
- // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless"
- if (p == NULL || p == JUST_CALC_SIZE
- || (OP(p) != BRANCH
- && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) {
- return;
- }
- regtail(OPERAND(p), val);
-}
-
-// Insert an operator in front of already-emitted operand
-//
-// Means relocating the operand.
-static void reginsert(int op, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 3;
- return;
- }
- src = regcode;
- regcode += 3;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place = NUL;
-}
-
-// Insert an operator in front of already-emitted operand.
-// Add a number to the operator.
-static void reginsert_nr(int op, long val, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 7;
- return;
- }
- src = regcode;
- regcode += 7;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place++ = NUL;
- assert(val >= 0 && (uintmax_t)val <= UINT32_MAX);
- re_put_uint32(place, (uint32_t)val);
-}
-
-// Insert an operator in front of already-emitted operand.
-// The operator has the given limit values as operands. Also set next pointer.
-//
-// Means relocating the operand.
-static void reginsert_limits(int op, long minval, long maxval, uint8_t *opnd)
-{
- uint8_t *src;
- uint8_t *dst;
- uint8_t *place;
-
- if (regcode == JUST_CALC_SIZE) {
- regsize += 11;
- return;
- }
- src = regcode;
- regcode += 11;
- dst = regcode;
- while (src > opnd) {
- *--dst = *--src;
- }
-
- place = opnd; // Op node, where operand used to be.
- *place++ = (uint8_t)op;
- *place++ = NUL;
- *place++ = NUL;
- assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX);
- place = re_put_uint32(place, (uint32_t)minval);
- assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX);
- place = re_put_uint32(place, (uint32_t)maxval);
- regtail(opnd, place);
-}
-
-/// Return true if the back reference is legal. We must have seen the close
-/// brace.
-/// TODO(vim): Should also check that we don't refer to something repeated
-/// (+*=): what instance of the repetition should we match?
-static int seen_endbrace(int refnum)
-{
- if (!had_endbrace[refnum]) {
- uint8_t *p;
-
- // Trick: check if "@<=" or "@<!" follows, in which case
- // the \1 can appear before the referenced match.
- for (p = (uint8_t *)regparse; *p != NUL; p++) {
- if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) {
- break;
- }
- }
-
- if (*p == NUL) {
- emsg(_("E65: Illegal back reference"));
- rc_did_emsg = true;
- return false;
- }
- }
- return true;
-}
-
-// Parse the lowest level.
-//
-// Optimization: gobbles an entire sequence of ordinary characters so that
-// it can turn them into a single node, which is smaller to store and
-// faster to run. Don't do this when one_exactly is set.
-static uint8_t *regatom(int *flagp)
-{
- uint8_t *ret;
- int flags;
- int c;
- uint8_t *p;
- int extra = 0;
- int save_prev_at_start = prev_at_start;
-
- *flagp = WORST; // Tentatively.
-
- c = getchr();
- switch (c) {
- case Magic('^'):
- ret = regnode(BOL);
- break;
-
- case Magic('$'):
- ret = regnode(EOL);
- had_eol = true;
- break;
-
- case Magic('<'):
- ret = regnode(BOW);
- break;
-
- case Magic('>'):
- ret = regnode(EOW);
- break;
-
- case Magic('_'):
- c = no_Magic(getchr());
- if (c == '^') { // "\_^" is start-of-line
- ret = regnode(BOL);
- break;
- }
- if (c == '$') { // "\_$" is end-of-line
- ret = regnode(EOL);
- had_eol = true;
- break;
- }
-
- extra = ADD_NL;
- *flagp |= HASNL;
-
- // "\_[" is character range plus newline
- if (c == '[') {
- goto collection;
- }
-
- // "\_x" is character class plus newline
- FALLTHROUGH;
-
- // Character classes.
- case Magic('.'):
- case Magic('i'):
- case Magic('I'):
- case Magic('k'):
- case Magic('K'):
- case Magic('f'):
- case Magic('F'):
- case Magic('p'):
- case Magic('P'):
- case Magic('s'):
- case Magic('S'):
- case Magic('d'):
- case Magic('D'):
- case Magic('x'):
- case Magic('X'):
- case Magic('o'):
- case Magic('O'):
- case Magic('w'):
- case Magic('W'):
- case Magic('h'):
- case Magic('H'):
- case Magic('a'):
- case Magic('A'):
- case Magic('l'):
- case Magic('L'):
- case Magic('u'):
- case Magic('U'):
- p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
- if (p == NULL) {
- EMSG_RET_NULL(_("E63: invalid use of \\_"));
- }
- // When '.' is followed by a composing char ignore the dot, so that
- // the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
- c = getchr();
- goto do_multibyte;
- }
- ret = regnode(classcodes[p - classchars] + extra);
- *flagp |= HASWIDTH | SIMPLE;
- break;
-
- case Magic('n'):
- if (reg_string) {
- // In a string "\n" matches a newline character.
- ret = regnode(EXACTLY);
- regc(NL);
- regc(NUL);
- *flagp |= HASWIDTH | SIMPLE;
- } else {
- // In buffer text "\n" matches the end of a line.
- ret = regnode(NEWL);
- *flagp |= HASWIDTH | HASNL;
- }
- break;
-
- case Magic('('):
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_PAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
- break;
-
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier.
- // NOTREACHED
-
- case Magic('='):
- case Magic('?'):
- case Magic('+'):
- case Magic('@'):
- case Magic('{'):
- case Magic('*'):
- c = no_Magic(c);
- EMSG3_RET_NULL(_("E64: %s%c follows nothing"),
- (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c);
- // NOTREACHED
-
- case Magic('~'): // previous substitute pattern
- if (reg_prev_sub != NULL) {
- uint8_t *lp;
-
- ret = regnode(EXACTLY);
- lp = (uint8_t *)reg_prev_sub;
- while (*lp != NUL) {
- regc(*lp++);
- }
- regc(NUL);
- if (*reg_prev_sub != NUL) {
- *flagp |= HASWIDTH;
- if ((lp - (uint8_t *)reg_prev_sub) == 1) {
- *flagp |= SIMPLE;
- }
- }
- } else {
- EMSG_RET_NULL(_(e_nopresub));
- }
- break;
-
- case Magic('1'):
- case Magic('2'):
- case Magic('3'):
- case Magic('4'):
- case Magic('5'):
- case Magic('6'):
- case Magic('7'):
- case Magic('8'):
- case Magic('9'): {
- int refnum;
-
- refnum = c - Magic('0');
- if (!seen_endbrace(refnum)) {
- return NULL;
- }
- ret = regnode(BACKREF + refnum);
- }
- break;
-
- case Magic('z'):
- c = no_Magic(getchr());
- switch (c) {
- case '(':
- if ((reg_do_extmatch & REX_SET) == 0) {
- EMSG_RET_NULL(_(e_z_not_allowed));
- }
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_ZPAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH);
- re_has_z = REX_SET;
- break;
-
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- if ((reg_do_extmatch & REX_USE) == 0) {
- EMSG_RET_NULL(_(e_z1_not_allowed));
- }
- ret = regnode(ZREF + c - '0');
- re_has_z = REX_USE;
- break;
-
- case 's':
- ret = regnode(MOPEN + 0);
- if (!re_mult_next("\\zs")) {
- return NULL;
- }
- break;
-
- case 'e':
- ret = regnode(MCLOSE + 0);
- if (!re_mult_next("\\ze")) {
- return NULL;
- }
- break;
-
- default:
- EMSG_RET_NULL(_("E68: Invalid character after \\z"));
- }
- break;
-
- case Magic('%'):
- c = no_Magic(getchr());
- switch (c) {
- // () without a back reference
- case '(':
- if (one_exactly) {
- EMSG_ONE_RET_NULL;
- }
- ret = reg(REG_NPAREN, &flags);
- if (ret == NULL) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH);
- break;
-
- // Catch \%^ and \%$ regardless of where they appear in the
- // pattern -- regardless of whether or not it makes sense.
- case '^':
- ret = regnode(RE_BOF);
- break;
-
- case '$':
- ret = regnode(RE_EOF);
- break;
-
- case '#':
- if (regparse[0] == '=' && regparse[1] >= 48 && regparse[1] <= 50) {
- // misplaced \%#=1
- semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
- return FAIL;
- }
- ret = regnode(CURSOR);
- break;
-
- case 'V':
- ret = regnode(RE_VISUAL);
- break;
-
- case 'C':
- ret = regnode(RE_COMPOSING);
- break;
-
- // \%[abc]: Emit as a list of branches, all ending at the last
- // branch which matches nothing.
- case '[':
- if (one_exactly) { // doesn't nest
- EMSG_ONE_RET_NULL;
- }
- {
- uint8_t *lastbranch;
- uint8_t *lastnode = NULL;
- uint8_t *br;
-
- ret = NULL;
- while ((c = getchr()) != ']') {
- if (c == NUL) {
- EMSG2_RET_NULL(_(e_missing_sb),
- reg_magic == MAGIC_ALL);
- }
- br = regnode(BRANCH);
- if (ret == NULL) {
- ret = br;
- } else {
- regtail(lastnode, br);
- if (reg_toolong) {
- return NULL;
- }
- }
-
- ungetchr();
- one_exactly = true;
- lastnode = regatom(flagp);
- one_exactly = false;
- if (lastnode == NULL) {
- return NULL;
- }
- }
- if (ret == NULL) {
- EMSG2_RET_NULL(_(e_empty_sb),
- reg_magic == MAGIC_ALL);
- }
- lastbranch = regnode(BRANCH);
- br = regnode(NOTHING);
- if (ret != JUST_CALC_SIZE) {
- regtail(lastnode, br);
- regtail(lastbranch, br);
- // connect all branches to the NOTHING
- // branch at the end
- for (br = ret; br != lastnode;) {
- if (OP(br) == BRANCH) {
- regtail(br, lastbranch);
- if (reg_toolong) {
- return NULL;
- }
- br = OPERAND(br);
- } else {
- br = regnext(br);
- }
- }
- }
- *flagp &= ~(HASWIDTH | SIMPLE);
- break;
- }
-
- case 'd': // %d123 decimal
- case 'o': // %o123 octal
- case 'x': // %xab hex 2
- case 'u': // %uabcd hex 4
- case 'U': // %U1234abcd hex 8
- {
- int64_t i;
-
- switch (c) {
- case 'd':
- i = getdecchrs(); break;
- case 'o':
- i = getoctchrs(); break;
- case 'x':
- i = gethexchrs(2); break;
- case 'u':
- i = gethexchrs(4); break;
- case 'U':
- i = gethexchrs(8); break;
- default:
- i = -1; break;
- }
-
- if (i < 0 || i > INT_MAX) {
- EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"),
- reg_magic == MAGIC_ALL);
- }
- if (use_multibytecode((int)i)) {
- ret = regnode(MULTIBYTECODE);
- } else {
- ret = regnode(EXACTLY);
- }
- if (i == 0) {
- regc(0x0a);
- } else {
- regmbc((int)i);
- }
- regc(NUL);
- *flagp |= HASWIDTH;
- break;
- }
-
- default:
- if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') {
- uint32_t n = 0;
- int cmp;
- bool cur = false;
- bool got_digit = false;
-
- cmp = c;
- if (cmp == '<' || cmp == '>') {
- c = getchr();
- }
- if (no_Magic(c) == '.') {
- cur = true;
- c = getchr();
- }
- while (ascii_isdigit(c)) {
- got_digit = true;
- n = n * 10 + (uint32_t)(c - '0');
- c = getchr();
- }
- if (c == '\'' && n == 0) {
- // "\%'m", "\%<'m" and "\%>'m": Mark
- c = getchr();
- ret = regnode(RE_MARK);
- if (ret == JUST_CALC_SIZE) {
- regsize += 2;
- } else {
- *regcode++ = (uint8_t)c;
- *regcode++ = (uint8_t)cmp;
- }
- break;
- } else if ((c == 'l' || c == 'c' || c == 'v') && (cur || got_digit)) {
- if (cur && n) {
- semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
- rc_did_emsg = true;
- return NULL;
- }
- if (c == 'l') {
- if (cur) {
- n = (uint32_t)curwin->w_cursor.lnum;
- }
- ret = regnode(RE_LNUM);
- if (save_prev_at_start) {
- at_start = true;
- }
- } else if (c == 'c') {
- if (cur) {
- n = (uint32_t)curwin->w_cursor.col;
- n++;
- }
- ret = regnode(RE_COL);
- } else {
- if (cur) {
- colnr_T vcol = 0;
- getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- n = (uint32_t)(++vcol);
- }
- ret = regnode(RE_VCOL);
- }
- if (ret == JUST_CALC_SIZE) {
- regsize += 5;
- } else {
- // put the number and the optional
- // comparator after the opcode
- regcode = re_put_uint32(regcode, n);
- *regcode++ = (uint8_t)cmp;
- }
- break;
- }
- }
-
- EMSG2_RET_NULL(_("E71: Invalid character after %s%%"),
- reg_magic == MAGIC_ALL);
- }
- break;
-
- case Magic('['):
-collection:
- {
- uint8_t *lp;
-
- // If there is no matching ']', we assume the '[' is a normal
- // character. This makes 'incsearch' and ":help [" work.
- lp = (uint8_t *)skip_anyof(regparse);
- if (*lp == ']') { // there is a matching ']'
- int startc = -1; // > 0 when next '-' is a range
- int endc;
-
- // In a character class, different parsing rules apply.
- // Not even \ is special anymore, nothing is.
- if (*regparse == '^') { // Complement of range.
- ret = regnode(ANYBUT + extra);
- regparse++;
- } else {
- ret = regnode(ANYOF + extra);
- }
-
- // At the start ']' and '-' mean the literal character.
- if (*regparse == ']' || *regparse == '-') {
- startc = *regparse;
- regc(*regparse++);
- }
-
- while (*regparse != NUL && *regparse != ']') {
- if (*regparse == '-') {
- regparse++;
- // The '-' is not used for a range at the end and
- // after or before a '\n'.
- if (*regparse == ']' || *regparse == NUL
- || startc == -1
- || (regparse[0] == '\\' && regparse[1] == 'n')) {
- regc('-');
- startc = '-'; // [--x] is a range
- } else {
- // Also accept "a-[.z.]"
- endc = 0;
- if (*regparse == '[') {
- endc = get_coll_element(&regparse);
- }
- if (endc == 0) {
- endc = mb_ptr2char_adv((const char **)&regparse);
- }
-
- // Handle \o40, \x20 and \u20AC style sequences
- if (endc == '\\' && !reg_cpo_lit) {
- endc = coll_get_char();
- }
-
- if (startc > endc) {
- EMSG_RET_NULL(_(e_reverse_range));
- }
- if (utf_char2len(startc) > 1
- || utf_char2len(endc) > 1) {
- // Limit to a range of 256 chars
- if (endc > startc + 256) {
- EMSG_RET_NULL(_(e_large_class));
- }
- while (++startc <= endc) {
- regmbc(startc);
- }
- } else {
- while (++startc <= endc) {
- regc(startc);
- }
- }
- startc = -1;
- }
- }
- // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
- // accepts "\t", "\e", etc., but only when the 'l' flag in
- // 'cpoptions' is not included.
- else if (*regparse == '\\'
- && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
- || (!reg_cpo_lit
- && vim_strchr(REGEXP_ABBR,
- (uint8_t)regparse[1]) != NULL))) {
- regparse++;
- if (*regparse == 'n') {
- // '\n' in range: also match NL
- if (ret != JUST_CALC_SIZE) {
- // Using \n inside [^] does not change what
- // matches. "[^\n]" is the same as ".".
- if (*ret == ANYOF) {
- *ret = ANYOF + ADD_NL;
- *flagp |= HASNL;
- }
- // else: must have had a \n already
- }
- regparse++;
- startc = -1;
- } else if (*regparse == 'd'
- || *regparse == 'o'
- || *regparse == 'x'
- || *regparse == 'u'
- || *regparse == 'U') {
- startc = coll_get_char();
- if (startc == 0) {
- regc(0x0a);
- } else {
- regmbc(startc);
- }
- } else {
- startc = backslash_trans(*regparse++);
- regc(startc);
- }
- } else if (*regparse == '[') {
- int c_class;
- int cu;
-
- c_class = get_char_class(&regparse);
- startc = -1;
- // Characters assumed to be 8 bits!
- switch (c_class) {
- case CLASS_NONE:
- c_class = get_equi_class(&regparse);
- if (c_class != 0) {
- // produce equivalence class
- reg_equi_class(c_class);
- } else if ((c_class = get_coll_element(&regparse)) != 0) {
- // produce a collating element
- regmbc(c_class);
- } else {
- // literal '[', allow [[-x] as a range
- startc = *regparse++;
- regc(startc);
- }
- break;
- case CLASS_ALNUM:
- for (cu = 1; cu < 128; cu++) {
- if (isalnum(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_ALPHA:
- for (cu = 1; cu < 128; cu++) {
- if (isalpha(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_BLANK:
- regc(' ');
- regc('\t');
- break;
- case CLASS_CNTRL:
- for (cu = 1; cu <= 127; cu++) {
- if (iscntrl(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_DIGIT:
- for (cu = 1; cu <= 127; cu++) {
- if (ascii_isdigit(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_GRAPH:
- for (cu = 1; cu <= 127; cu++) {
- if (isgraph(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_LOWER:
- for (cu = 1; cu <= 255; cu++) {
- if (mb_islower(cu) && cu != 170 && cu != 186) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_PRINT:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isprintc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_PUNCT:
- for (cu = 1; cu < 128; cu++) {
- if (ispunct(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_SPACE:
- for (cu = 9; cu <= 13; cu++) {
- regc(cu);
- }
- regc(' ');
- break;
- case CLASS_UPPER:
- for (cu = 1; cu <= 255; cu++) {
- if (mb_isupper(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_XDIGIT:
- for (cu = 1; cu <= 255; cu++) {
- if (ascii_isxdigit(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_TAB:
- regc('\t');
- break;
- case CLASS_RETURN:
- regc('\r');
- break;
- case CLASS_BACKSPACE:
- regc('\b');
- break;
- case CLASS_ESCAPE:
- regc(ESC);
- break;
- case CLASS_IDENT:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isIDc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_KEYWORD:
- for (cu = 1; cu <= 255; cu++) {
- if (reg_iswordc(cu)) {
- regmbc(cu);
- }
- }
- break;
- case CLASS_FNAME:
- for (cu = 1; cu <= 255; cu++) {
- if (vim_isfilec(cu)) {
- regmbc(cu);
- }
- }
- break;
- }
- } else {
- // produce a multibyte character, including any
- // following composing characters.
- startc = utf_ptr2char((char *)regparse);
- int len = utfc_ptr2len((char *)regparse);
- if (utf_char2len(startc) != len) {
- // composing chars
- startc = -1;
- }
- while (--len >= 0) {
- regc(*regparse++);
- }
- }
- }
- regc(NUL);
- prevchr_len = 1; // last char was the ']'
- if (*regparse != ']') {
- EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen?
- }
- skipchr(); // let's be friends with the lexer again
- *flagp |= HASWIDTH | SIMPLE;
- break;
- } else if (reg_strict) {
- EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF);
- }
- }
- FALLTHROUGH;
-
- default: {
- int len;
-
- // A multi-byte character is handled as a separate atom if it's
- // before a multi and when it's a composing char.
- if (use_multibytecode(c)) {
-do_multibyte:
- ret = regnode(MULTIBYTECODE);
- regmbc(c);
- *flagp |= HASWIDTH | SIMPLE;
- break;
- }
-
- ret = regnode(EXACTLY);
-
- // Append characters as long as:
- // - there is no following multi, we then need the character in
- // front of it as a single character operand
- // - not running into a Magic character
- // - "one_exactly" is not set
- // But always emit at least one character. Might be a Multi,
- // e.g., a "[" without matching "]".
- for (len = 0; c != NUL && (len == 0
- || (re_multi_type(peekchr()) == NOT_MULTI
- && !one_exactly
- && !is_Magic(c))); len++) {
- c = no_Magic(c);
- {
- regmbc(c);
- {
- int l;
-
- // Need to get composing character too.
- for (;;) {
- l = utf_ptr2len((char *)regparse);
- if (!utf_composinglike(regparse, regparse + l)) {
- break;
- }
- regmbc(utf_ptr2char((char *)regparse));
- skipchr();
- }
- }
- }
- c = getchr();
- }
- ungetchr();
-
- regc(NUL);
- *flagp |= HASWIDTH;
- if (len == 1) {
- *flagp |= SIMPLE;
- }
- }
- break;
- }
-
- return ret;
-}
-
-// Parse something followed by possible [*+=].
-//
-// Note that the branching code sequences used for = and the general cases
-// of * and + are somewhat optimized: they use the same NOTHING node as
-// both the endmarker for their branch list and the body of the last branch.
-// It might seem that this node could be dispensed with entirely, but the
-// endmarker role is not redundant.
-static uint8_t *regpiece(int *flagp)
-{
- uint8_t *ret;
- int op;
- uint8_t *next;
- int flags;
- long minval;
- long maxval;
-
- ret = regatom(&flags);
- if (ret == NULL) {
- return NULL;
- }
-
- op = peekchr();
- if (re_multi_type(op) == NOT_MULTI) {
- *flagp = flags;
- return ret;
- }
- // default flags
- *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH)));
-
- skipchr();
- switch (op) {
- case Magic('*'):
- if (flags & SIMPLE) {
- reginsert(STAR, ret);
- } else {
- // Emit x* as (x&|), where & means "self".
- reginsert(BRANCH, ret); // Either x
- regoptail(ret, regnode(BACK)); // and loop
- regoptail(ret, ret); // back
- regtail(ret, regnode(BRANCH)); // or
- regtail(ret, regnode(NOTHING)); // null.
- }
- break;
-
- case Magic('+'):
- if (flags & SIMPLE) {
- reginsert(PLUS, ret);
- } else {
- // Emit x+ as x(&|), where & means "self".
- next = regnode(BRANCH); // Either
- regtail(ret, next);
- regtail(regnode(BACK), ret); // loop back
- regtail(next, regnode(BRANCH)); // or
- regtail(ret, regnode(NOTHING)); // null.
- }
- *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH)));
- break;
-
- case Magic('@'): {
- int lop = END;
- int64_t nr = getdecchrs();
-
- switch (no_Magic(getchr())) {
- case '=':
- lop = MATCH; break; // \@=
- case '!':
- lop = NOMATCH; break; // \@!
- case '>':
- lop = SUBPAT; break; // \@>
- case '<':
- switch (no_Magic(getchr())) {
- case '=':
- lop = BEHIND; break; // \@<=
- case '!':
- lop = NOBEHIND; break; // \@<!
- }
- }
- if (lop == END) {
- EMSG2_RET_NULL(_("E59: invalid character after %s@"),
- reg_magic == MAGIC_ALL);
- }
- // Look behind must match with behind_pos.
- if (lop == BEHIND || lop == NOBEHIND) {
- regtail(ret, regnode(BHPOS));
- *flagp |= HASLOOKBH;
- }
- regtail(ret, regnode(END)); // operand ends
- if (lop == BEHIND || lop == NOBEHIND) {
- if (nr < 0) {
- nr = 0; // no limit is same as zero limit
- }
- reginsert_nr(lop, (uint32_t)nr, ret);
- } else {
- reginsert(lop, ret);
- }
- break;
- }
-
- case Magic('?'):
- case Magic('='):
- // Emit x= as (x|)
- reginsert(BRANCH, ret); // Either x
- regtail(ret, regnode(BRANCH)); // or
- next = regnode(NOTHING); // null.
- regtail(ret, next);
- regoptail(ret, next);
- break;
-
- case Magic('{'):
- if (!read_limits(&minval, &maxval)) {
- return NULL;
- }
- if (flags & SIMPLE) {
- reginsert(BRACE_SIMPLE, ret);
- reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
- } else {
- if (num_complex_braces >= 10) {
- EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"),
- reg_magic == MAGIC_ALL);
- }
- reginsert(BRACE_COMPLEX + num_complex_braces, ret);
- regoptail(ret, regnode(BACK));
- regoptail(ret, ret);
- reginsert_limits(BRACE_LIMITS, minval, maxval, ret);
- num_complex_braces++;
- }
- if (minval > 0 && maxval > 0) {
- *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH)));
- }
- break;
- }
- if (re_multi_type(peekchr()) != NOT_MULTI) {
- // Can't have a multi follow a multi.
- if (peekchr() == Magic('*')) {
- EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON);
- }
- EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr()));
- }
-
- return ret;
-}
-
-// Parse one alternative of an | or & operator.
-// Implements the concatenation operator.
-static uint8_t *regconcat(int *flagp)
-{
- uint8_t *first = NULL;
- uint8_t *chain = NULL;
- uint8_t *latest;
- int flags;
- int cont = true;
-
- *flagp = WORST; // Tentatively.
-
- while (cont) {
- switch (peekchr()) {
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- cont = false;
- break;
- case Magic('Z'):
- regflags |= RF_ICOMBINE;
- skipchr_keepstart();
- break;
- case Magic('c'):
- regflags |= RF_ICASE;
- skipchr_keepstart();
- break;
- case Magic('C'):
- regflags |= RF_NOICASE;
- skipchr_keepstart();
- break;
- case Magic('v'):
- reg_magic = MAGIC_ALL;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('m'):
- reg_magic = MAGIC_ON;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('M'):
- reg_magic = MAGIC_OFF;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('V'):
- reg_magic = MAGIC_NONE;
- skipchr_keepstart();
- curchr = -1;
- break;
- default:
- latest = regpiece(&flags);
- if (latest == NULL || reg_toolong) {
- return NULL;
- }
- *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH);
- if (chain == NULL) { // First piece.
- *flagp |= flags & SPSTART;
- } else {
- regtail(chain, latest);
- }
- chain = latest;
- if (first == NULL) {
- first = latest;
- }
- break;
- }
- }
- if (first == NULL) { // Loop ran zero times.
- first = regnode(NOTHING);
- }
- return first;
-}
-
-// Parse one alternative of an | operator.
-// Implements the & operator.
-static uint8_t *regbranch(int *flagp)
-{
- uint8_t *ret;
- uint8_t *chain = NULL;
- uint8_t *latest;
- int flags;
-
- *flagp = WORST | HASNL; // Tentatively.
-
- ret = regnode(BRANCH);
- for (;;) {
- latest = regconcat(&flags);
- if (latest == NULL) {
- return NULL;
- }
- // If one of the branches has width, the whole thing has. If one of
- // the branches anchors at start-of-line, the whole thing does.
- // If one of the branches uses look-behind, the whole thing does.
- *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH);
- // If one of the branches doesn't match a line-break, the whole thing
- // doesn't.
- *flagp &= ~HASNL | (flags & HASNL);
- if (chain != NULL) {
- regtail(chain, latest);
- }
- if (peekchr() != Magic('&')) {
- break;
- }
- skipchr();
- regtail(latest, regnode(END)); // operand ends
- if (reg_toolong) {
- break;
- }
- reginsert(MATCH, latest);
- chain = latest;
- }
-
- return ret;
-}
-
-/// Parse regular expression, i.e. main body or parenthesized thing.
-///
-/// Caller must absorb opening parenthesis.
-///
-/// Combining parenthesis handling with the base level of regular expression
-/// is a trifle forced, but the need to tie the tails of the branches to what
-/// follows makes it hard to avoid.
-///
-/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
-static uint8_t *reg(int paren, int *flagp)
-{
- uint8_t *ret;
- uint8_t *br;
- uint8_t *ender;
- int parno = 0;
- int flags;
-
- *flagp = HASWIDTH; // Tentatively.
-
- if (paren == REG_ZPAREN) {
- // Make a ZOPEN node.
- if (regnzpar >= NSUBEXP) {
- EMSG_RET_NULL(_("E50: Too many \\z("));
- }
- parno = regnzpar;
- regnzpar++;
- ret = regnode(ZOPEN + parno);
- } else if (paren == REG_PAREN) {
- // Make a MOPEN node.
- if (regnpar >= NSUBEXP) {
- EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL);
- }
- parno = regnpar;
- regnpar++;
- ret = regnode(MOPEN + parno);
- } else if (paren == REG_NPAREN) {
- // Make a NOPEN node.
- ret = regnode(NOPEN);
- } else {
- ret = NULL;
- }
-
- // Pick up the branches, linking them together.
- br = regbranch(&flags);
- if (br == NULL) {
- return NULL;
- }
- if (ret != NULL) {
- regtail(ret, br); // [MZ]OPEN -> first.
- } else {
- ret = br;
- }
- // If one of the branches can be zero-width, the whole thing can.
- // If one of the branches has * at start or matches a line-break, the
- // whole thing can.
- if (!(flags & HASWIDTH)) {
- *flagp &= ~HASWIDTH;
- }
- *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
- while (peekchr() == Magic('|')) {
- skipchr();
- br = regbranch(&flags);
- if (br == NULL || reg_toolong) {
- return NULL;
- }
- regtail(ret, br); // BRANCH -> BRANCH.
- if (!(flags & HASWIDTH)) {
- *flagp &= ~HASWIDTH;
- }
- *flagp |= flags & (SPSTART | HASNL | HASLOOKBH);
- }
-
- // Make a closing node, and hook it on the end.
- ender = regnode(paren == REG_ZPAREN ? ZCLOSE + parno :
- paren == REG_PAREN ? MCLOSE + parno :
- paren == REG_NPAREN ? NCLOSE : END);
- regtail(ret, ender);
-
- // Hook the tails of the branches to the closing node.
- for (br = ret; br != NULL; br = regnext(br)) {
- regoptail(br, ender);
- }
-
- // Check for proper termination.
- if (paren != REG_NOPAREN && getchr() != Magic(')')) {
- if (paren == REG_ZPAREN) {
- EMSG_RET_NULL(_("E52: Unmatched \\z("));
- } else if (paren == REG_NPAREN) {
- EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
- } else {
- EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
- }
- } else if (paren == REG_NOPAREN && peekchr() != NUL) {
- if (curchr == Magic(')')) {
- EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
- } else {
- EMSG_RET_NULL(_(e_trailing)); // "Can't happen".
- }
- // NOTREACHED
- }
- // Here we set the flag allowing back references to this set of
- // parentheses.
- if (paren == REG_PAREN) {
- had_endbrace[parno] = true; // have seen the close paren
- }
- return ret;
-}
-
-// bt_regcomp() - compile a regular expression into internal code for the
-// traditional back track matcher.
-// Returns the program in allocated space. Returns NULL for an error.
-//
-// We can't allocate space until we know how big the compiled form will be,
-// but we can't compile it (and thus know how big it is) until we've got a
-// place to put the code. So we cheat: we compile it twice, once with code
-// generation turned off and size counting turned on, and once "for real".
-// This also means that we don't allocate space until we are sure that the
-// thing really will compile successfully, and we never have to move the
-// code and thus invalidate pointers into it. (Note that it has to be in
-// one piece because free() must be able to free it all.)
-//
-// Whether upper/lower case is to be ignored is decided when executing the
-// program, it does not matter here.
-//
-// Beware that the optimization-preparation code in here knows about some
-// of the structure of the compiled regexp.
-// "re_flags": RE_MAGIC and/or RE_STRING.
-static regprog_T *bt_regcomp(uint8_t *expr, int re_flags)
-{
- uint8_t *scan;
- uint8_t *longest;
- int len;
- int flags;
-
- if (expr == NULL) {
- IEMSG_RET_NULL(_(e_null));
- }
-
- init_class_tab();
-
- // First pass: determine size, legality.
- regcomp_start(expr, re_flags);
- regcode = JUST_CALC_SIZE;
- regc(REGMAGIC);
- if (reg(REG_NOPAREN, &flags) == NULL) {
- return NULL;
- }
-
- // Allocate space.
- bt_regprog_T *r = xmalloc(offsetof(bt_regprog_T, program) + (size_t)regsize);
- r->re_in_use = false;
-
- // Second pass: emit code.
- regcomp_start(expr, re_flags);
- regcode = r->program;
- regc(REGMAGIC);
- if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) {
- xfree(r);
- if (reg_toolong) {
- EMSG_RET_NULL(_("E339: Pattern too long"));
- }
- return NULL;
- }
-
- // Dig out information for optimizations.
- r->regstart = NUL; // Worst-case defaults.
- r->reganch = 0;
- r->regmust = NULL;
- r->regmlen = 0;
- r->regflags = regflags;
- if (flags & HASNL) {
- r->regflags |= RF_HASNL;
- }
- if (flags & HASLOOKBH) {
- r->regflags |= RF_LOOKBH;
- }
- // Remember whether this pattern has any \z specials in it.
- r->reghasz = (uint8_t)re_has_z;
- scan = r->program + 1; // First BRANCH.
- if (OP(regnext(scan)) == END) { // Only one top-level choice.
- scan = OPERAND(scan);
-
- // Starting-point info.
- if (OP(scan) == BOL || OP(scan) == RE_BOF) {
- r->reganch++;
- scan = regnext(scan);
- }
-
- if (OP(scan) == EXACTLY) {
- r->regstart = utf_ptr2char((char *)OPERAND(scan));
- } else if (OP(scan) == BOW
- || OP(scan) == EOW
- || OP(scan) == NOTHING
- || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN
- || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) {
- uint8_t *regnext_scan = regnext(scan);
- if (OP(regnext_scan) == EXACTLY) {
- r->regstart = utf_ptr2char((char *)OPERAND(regnext_scan));
- }
- }
-
- // If there's something expensive in the r.e., find the longest
- // literal string that must appear and make it the regmust. Resolve
- // ties in favor of later strings, since the regstart check works
- // with the beginning of the r.e. and avoiding duplication
- // strengthens checking. Not a strong reason, but sufficient in the
- // absence of others.
-
- // When the r.e. starts with BOW, it is faster to look for a regmust
- // first. Used a lot for "#" and "*" commands. (Added by mool).
- if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW)
- && !(flags & HASNL)) {
- longest = NULL;
- len = 0;
- for (; scan != NULL; scan = regnext(scan)) {
- if (OP(scan) == EXACTLY && strlen((char *)OPERAND(scan)) >= (size_t)len) {
- longest = OPERAND(scan);
- len = (int)strlen((char *)OPERAND(scan));
- }
- }
- r->regmust = longest;
- r->regmlen = len;
- }
- }
-#ifdef BT_REGEXP_DUMP
- regdump(expr, r);
-#endif
- r->engine = &bt_regengine;
- return (regprog_T *)r;
-}
-
-// Check if during the previous call to vim_regcomp the EOL item "$" has been
-// found. This is messy, but it works fine.
-int vim_regcomp_had_eol(void)
-{
- return had_eol;
-}
-
-// Get a number after a backslash that is inside [].
-// When nothing is recognized return a backslash.
-static int coll_get_char(void)
-{
- int64_t nr = -1;
-
- switch (*regparse++) {
- case 'd':
- nr = getdecchrs(); break;
- case 'o':
- nr = getoctchrs(); break;
- case 'x':
- nr = gethexchrs(2); break;
- case 'u':
- nr = gethexchrs(4); break;
- case 'U':
- nr = gethexchrs(8); break;
- }
- if (nr < 0 || nr > INT_MAX) {
- // If getting the number fails be backwards compatible: the character
- // is a backslash.
- regparse--;
- nr = '\\';
- }
- return (int)nr;
-}
-
-// Free a compiled regexp program, returned by bt_regcomp().
-static void bt_regfree(regprog_T *prog)
-{
- xfree(prog);
-}
-
-#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
-
-// The arguments from BRACE_LIMITS are stored here. They are actually local
-// to regmatch(), but they are here to reduce the amount of stack space used
-// (it can be called recursively many times).
-static long bl_minval;
-static long bl_maxval;
-
-// Save the input line and position in a regsave_T.
-static void reg_save(regsave_T *save, garray_T *gap)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- save->rs_u.pos.col = (colnr_T)(rex.input - rex.line);
- save->rs_u.pos.lnum = rex.lnum;
- } else {
- save->rs_u.ptr = rex.input;
- }
- save->rs_len = gap->ga_len;
-}
-
-// Restore the input line and position from a regsave_T.
-static void reg_restore(regsave_T *save, garray_T *gap)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- if (rex.lnum != save->rs_u.pos.lnum) {
- // only call reg_getline() when the line number changed to save
- // a bit of time
- rex.lnum = save->rs_u.pos.lnum;
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- }
- rex.input = rex.line + save->rs_u.pos.col;
- } else {
- rex.input = save->rs_u.ptr;
- }
- gap->ga_len = save->rs_len;
-}
-
-// Return true if current position is equal to saved position.
-static bool reg_save_equal(const regsave_T *save)
- FUNC_ATTR_NONNULL_ALL
-{
- if (REG_MULTI) {
- return rex.lnum == save->rs_u.pos.lnum
- && rex.input == rex.line + save->rs_u.pos.col;
- }
- return rex.input == save->rs_u.ptr;
-}
-
-// Save the sub-expressions before attempting a match.
-#define save_se(savep, posp, pp) \
- REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp))
-
-// After a failed match restore the sub-expressions.
-#define restore_se(savep, posp, pp) { \
- if (REG_MULTI) /* NOLINT(readability/braces) */ \
- *(posp) = (savep)->se_u.pos; \
- else /* NOLINT */ \
- *(pp) = (savep)->se_u.ptr; }
-
-// Tentatively set the sub-expression start to the current position (after
-// calling regmatch() they will have changed). Need to save the existing
-// values for when there is no match.
-// Use se_save() to use pointer (save_se_multi()) or position (save_se_one()),
-// depending on REG_MULTI.
-static void save_se_multi(save_se_T *savep, lpos_T *posp)
-{
- savep->se_u.pos = *posp;
- posp->lnum = rex.lnum;
- posp->col = (colnr_T)(rex.input - rex.line);
-}
-
-static void save_se_one(save_se_T *savep, uint8_t **pp)
-{
- savep->se_u.ptr = *pp;
- *pp = rex.input;
-}
-
-/// regrepeat - repeatedly match something simple, return how many.
-/// Advances rex.input (and rex.lnum) to just after the matched chars.
-///
-/// @param maxcount maximum number of matches allowed
-static int regrepeat(uint8_t *p, long maxcount)
-{
- long count = 0;
- uint8_t *opnd;
- int mask;
- int testval = 0;
-
- uint8_t *scan = rex.input; // Make local copy of rex.input for speed.
- opnd = OPERAND(p);
- switch (OP(p)) {
- case ANY:
- case ANY + ADD_NL:
- while (count < maxcount) {
- // Matching anything means we continue until end-of-line (or
- // end-of-file for ANY + ADD_NL), only limited by maxcount.
- while (*scan != NUL && count < maxcount) {
- count++;
- MB_PTR_ADV(scan);
- }
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr || count == maxcount) {
- break;
- }
- count++; // count the line-break
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- }
- break;
-
- case IDENT:
- case IDENT + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SIDENT:
- case SIDENT + ADD_NL:
- while (count < maxcount) {
- if (vim_isIDc(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case KWORD:
- case KWORD + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SKWORD:
- case SKWORD + ADD_NL:
- while (count < maxcount) {
- if (vim_iswordp_buf((char *)scan, rex.reg_buf)
- && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case FNAME:
- case FNAME + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SFNAME:
- case SFNAME + ADD_NL:
- while (count < maxcount) {
- if (vim_isfilec(utf_ptr2char((char *)scan)) && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case PRINT:
- case PRINT + ADD_NL:
- testval = 1;
- FALLTHROUGH;
- case SPRINT:
- case SPRINT + ADD_NL:
- while (count < maxcount) {
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (vim_isprintc(utf_ptr2char((char *)scan)) == 1
- && (testval || !ascii_isdigit(*scan))) {
- MB_PTR_ADV(scan);
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case WHITE:
- case WHITE + ADD_NL:
- testval = mask = RI_WHITE;
-do_class:
- while (count < maxcount) {
- int l;
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if ((l = utfc_ptr2len((char *)scan)) > 1) {
- if (testval != 0) {
- break;
- }
- scan += l;
- } else if ((class_tab[*scan] & mask) == testval) {
- scan++;
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else {
- break;
- }
- count++;
- }
- break;
-
- case NWHITE:
- case NWHITE + ADD_NL:
- mask = RI_WHITE;
- goto do_class;
- case DIGIT:
- case DIGIT + ADD_NL:
- testval = mask = RI_DIGIT;
- goto do_class;
- case NDIGIT:
- case NDIGIT + ADD_NL:
- mask = RI_DIGIT;
- goto do_class;
- case HEX:
- case HEX + ADD_NL:
- testval = mask = RI_HEX;
- goto do_class;
- case NHEX:
- case NHEX + ADD_NL:
- mask = RI_HEX;
- goto do_class;
- case OCTAL:
- case OCTAL + ADD_NL:
- testval = mask = RI_OCTAL;
- goto do_class;
- case NOCTAL:
- case NOCTAL + ADD_NL:
- mask = RI_OCTAL;
- goto do_class;
- case WORD:
- case WORD + ADD_NL:
- testval = mask = RI_WORD;
- goto do_class;
- case NWORD:
- case NWORD + ADD_NL:
- mask = RI_WORD;
- goto do_class;
- case HEAD:
- case HEAD + ADD_NL:
- testval = mask = RI_HEAD;
- goto do_class;
- case NHEAD:
- case NHEAD + ADD_NL:
- mask = RI_HEAD;
- goto do_class;
- case ALPHA:
- case ALPHA + ADD_NL:
- testval = mask = RI_ALPHA;
- goto do_class;
- case NALPHA:
- case NALPHA + ADD_NL:
- mask = RI_ALPHA;
- goto do_class;
- case LOWER:
- case LOWER + ADD_NL:
- testval = mask = RI_LOWER;
- goto do_class;
- case NLOWER:
- case NLOWER + ADD_NL:
- mask = RI_LOWER;
- goto do_class;
- case UPPER:
- case UPPER + ADD_NL:
- testval = mask = RI_UPPER;
- goto do_class;
- case NUPPER:
- case NUPPER + ADD_NL:
- mask = RI_UPPER;
- goto do_class;
-
- case EXACTLY: {
- int cu, cl;
-
- // This doesn't do a multi-byte character, because a MULTIBYTECODE
- // would have been used for it. It does handle single-byte
- // characters, such as latin1.
- if (rex.reg_ic) {
- cu = mb_toupper(*opnd);
- cl = mb_tolower(*opnd);
- while (count < maxcount && (*scan == cu || *scan == cl)) {
- count++;
- scan++;
- }
- } else {
- cu = *opnd;
- while (count < maxcount && *scan == cu) {
- count++;
- scan++;
- }
- }
- break;
- }
-
- case MULTIBYTECODE: {
- int i, len, cf = 0;
-
- // Safety check (just in case 'encoding' was changed since
- // compiling the program).
- if ((len = utfc_ptr2len((char *)opnd)) > 1) {
- if (rex.reg_ic) {
- cf = utf_fold(utf_ptr2char((char *)opnd));
- }
- while (count < maxcount && utfc_ptr2len((char *)scan) >= len) {
- for (i = 0; i < len; i++) {
- if (opnd[i] != scan[i]) {
- break;
- }
- }
- if (i < len && (!rex.reg_ic
- || utf_fold(utf_ptr2char((char *)scan)) != cf)) {
- break;
- }
- scan += len;
- count++;
- }
- }
- }
- break;
-
- case ANYOF:
- case ANYOF + ADD_NL:
- testval = 1;
- FALLTHROUGH;
-
- case ANYBUT:
- case ANYBUT + ADD_NL:
- while (count < maxcount) {
- int len;
- if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) {
- break;
- }
- reg_nextline();
- scan = rex.input;
- if (got_int) {
- break;
- }
- } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
- scan++;
- } else if ((len = utfc_ptr2len((char *)scan)) > 1) {
- if ((cstrchr((char *)opnd, utf_ptr2char((char *)scan)) == NULL) == testval) {
- break;
- }
- scan += len;
- } else {
- if ((cstrchr((char *)opnd, *scan) == NULL) == testval) {
- break;
- }
- scan++;
- }
- count++;
- }
- break;
-
- case NEWL:
- while (count < maxcount
- && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr
- && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) {
- count++;
- if (rex.reg_line_lbr) {
- ADVANCE_REGINPUT();
- } else {
- reg_nextline();
- }
- scan = rex.input;
- if (got_int) {
- break;
- }
- }
- break;
-
- default: // Oh dear. Called inappropriately.
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Called regrepeat with op code %d\n", OP(p));
-#endif
- break;
- }
-
- rex.input = scan;
-
- return (int)count;
-}
-
-// Push an item onto the regstack.
-// Returns pointer to new item. Returns NULL when out of memory.
-static regitem_T *regstack_push(regstate_T state, uint8_t *scan)
-{
- regitem_T *rp;
-
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- return NULL;
- }
- ga_grow(&regstack, sizeof(regitem_T));
-
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len);
- rp->rs_state = state;
- rp->rs_scan = scan;
-
- regstack.ga_len += (int)sizeof(regitem_T);
- return rp;
-}
-
-// Pop an item from the regstack.
-static void regstack_pop(uint8_t **scan)
-{
- regitem_T *rp;
-
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
- *scan = rp->rs_scan;
-
- regstack.ga_len -= (int)sizeof(regitem_T);
-}
-
-// Save the current subexpr to "bp", so that they can be restored
-// later by restore_subexpr().
-static void save_subexpr(regbehind_T *bp)
- FUNC_ATTR_NONNULL_ALL
-{
- // When "rex.need_clear_subexpr" is set we don't need to save the values, only
- // remember that this flag needs to be set again when restoring.
- bp->save_need_clear_subexpr = rex.need_clear_subexpr;
- if (rex.need_clear_subexpr) {
- return;
- }
-
- for (int i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- bp->save_start[i].se_u.pos = rex.reg_startpos[i];
- bp->save_end[i].se_u.pos = rex.reg_endpos[i];
- } else {
- bp->save_start[i].se_u.ptr = rex.reg_startp[i];
- bp->save_end[i].se_u.ptr = rex.reg_endp[i];
- }
- }
-}
-
-// Restore the subexpr from "bp".
-static void restore_subexpr(regbehind_T *bp)
- FUNC_ATTR_NONNULL_ALL
-{
- // Only need to restore saved values when they are not to be cleared.
- rex.need_clear_subexpr = bp->save_need_clear_subexpr;
- if (rex.need_clear_subexpr) {
- return;
- }
-
- for (int i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- rex.reg_startpos[i] = bp->save_start[i].se_u.pos;
- rex.reg_endpos[i] = bp->save_end[i].se_u.pos;
- } else {
- rex.reg_startp[i] = bp->save_start[i].se_u.ptr;
- rex.reg_endp[i] = bp->save_end[i].se_u.ptr;
- }
- }
-}
-/// Main matching routine
-///
-/// Conceptually the strategy is simple: Check to see whether the current node
-/// matches, push an item onto the regstack and loop to see whether the rest
-/// matches, and then act accordingly. In practice we make some effort to
-/// avoid using the regstack, in particular by going through "ordinary" nodes
-/// (that don't need to know whether the rest of the match failed) by a nested
-/// loop.
-///
-/// @param scan Current node.
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return - true when there is a match. Leaves rex.input and rex.lnum
-/// just after the last matched character.
-/// - false when there is no match. Leaves rex.input and rex.lnum in an
-/// undefined state!
-static bool regmatch(uint8_t *scan, proftime_T *tm, int *timed_out)
-{
- uint8_t *next; // Next node.
- int op;
- int c;
- regitem_T *rp;
- int no;
- int status; // one of the RA_ values:
- int tm_count = 0;
-
- // Make "regstack" and "backpos" empty. They are allocated and freed in
- // bt_regexec_both() to reduce malloc()/free() calls.
- regstack.ga_len = 0;
- backpos.ga_len = 0;
-
- // Repeat until "regstack" is empty.
- for (;;) {
- // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q".
- // Allow interrupting them with CTRL-C.
- fast_breakcheck();
-
-#ifdef REGEXP_DEBUG
- if (scan != NULL && regnarrate) {
- os_errmsg((char *)regprop(scan));
- os_errmsg("(\n");
- }
-#endif
-
- // Repeat for items that can be matched sequentially, without using the
- // regstack.
- for (;;) {
- if (got_int || scan == NULL) {
- status = RA_FAIL;
- break;
- }
- // Check for timeout once in a 100 times to avoid overhead.
- if (tm != NULL && ++tm_count == 100) {
- tm_count = 0;
- if (profile_passed_limit(*tm)) {
- if (timed_out != NULL) {
- *timed_out = true;
- }
- status = RA_FAIL;
- break;
- }
- }
- status = RA_CONT;
-
-#ifdef REGEXP_DEBUG
- if (regnarrate) {
- os_errmsg((char *)regprop(scan));
- os_errmsg("...\n");
- if (re_extmatch_in != NULL) {
- int i;
-
- os_errmsg(_("External submatches:\n"));
- for (i = 0; i < NSUBEXP; i++) {
- os_errmsg(" \"");
- if (re_extmatch_in->matches[i] != NULL) {
- os_errmsg((char *)re_extmatch_in->matches[i]);
- }
- os_errmsg("\"\n");
- }
- }
- }
-#endif
- next = regnext(scan);
-
- op = OP(scan);
- // Check for character class with NL added.
- if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
- && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
- reg_nextline();
- } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') {
- ADVANCE_REGINPUT();
- } else {
- if (WITH_NL(op)) {
- op -= ADD_NL;
- }
- c = utf_ptr2char((char *)rex.input);
- switch (op) {
- case BOL:
- if (rex.input != rex.line) {
- status = RA_NOMATCH;
- }
- break;
-
- case EOL:
- if (c != NUL) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_BOF:
- // We're not at the beginning of the file when below the first
- // line where we started, not at the start of the line or we
- // didn't start at the first line of the buffer.
- if (rex.lnum != 0 || rex.input != rex.line
- || (REG_MULTI && rex.reg_firstlnum > 1)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_EOF:
- if (rex.lnum != rex.reg_maxline || c != NUL) {
- status = RA_NOMATCH;
- }
- break;
-
- case CURSOR:
- // Check if the buffer is in a window and compare the
- // rex.reg_win->w_cursor position to the match position.
- if (rex.reg_win == NULL
- || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
- || ((colnr_T)(rex.input - rex.line) !=
- rex.reg_win->w_cursor.col)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_MARK:
- // Compare the mark position to the match position.
- {
- int mark = OPERAND(scan)[0];
- int cmp = OPERAND(scan)[1];
- pos_T *pos;
- size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
- fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, mark);
-
- // Line may have been freed, get it again.
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- rex.input = rex.line + col;
- }
-
- if (fm == NULL // mark doesn't exist
- || fm->mark.lnum <= 0) { // mark isn't set in reg_buf
- status = RA_NOMATCH;
- } else {
- pos = &fm->mark;
- const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
- && pos->col == MAXCOL
- ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum))
- : pos->col;
-
- if (pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos_col == (colnr_T)(rex.input - rex.line)
- ? (cmp == '<' || cmp == '>')
- : (pos_col < (colnr_T)(rex.input - rex.line)
- ? cmp != '>'
- : cmp != '<'))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? cmp != '>'
- : cmp != '<')) {
- status = RA_NOMATCH;
- }
- }
- }
- break;
-
- case RE_VISUAL:
- if (!reg_match_visual()) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_LNUM:
- assert(rex.lnum + rex.reg_firstlnum >= 0
- && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
- if (!REG_MULTI
- || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_COL:
- assert(rex.input - rex.line + 1 >= 0
- && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX);
- if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case RE_VCOL:
- if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL
- ? curwin : rex.reg_win,
- rex.reg_firstlnum + rex.lnum,
- (char *)rex.line,
- (colnr_T)(rex.input - rex.line)) + 1,
- scan)) {
- status = RA_NOMATCH;
- }
- break;
-
- case BOW: // \<word; rex.input points to w
- if (c == NUL) { // Can't match at end of line
- status = RA_NOMATCH;
- } else {
- // Get class of current and previous char (if it exists).
- const int this_class =
- mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- if (this_class <= 1) {
- status = RA_NOMATCH; // Not on a word at all.
- } else if (reg_prev_class() == this_class) {
- status = RA_NOMATCH; // Previous char is in same word.
- }
- }
- break;
-
- case EOW: // word\>; rex.input points after d
- if (rex.input == rex.line) { // Can't match at start of line
- status = RA_NOMATCH;
- } else {
- int this_class, prev_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- prev_class = reg_prev_class();
- if (this_class == prev_class
- || prev_class == 0 || prev_class == 1) {
- status = RA_NOMATCH;
- }
- }
- break; // Matched with EOW
-
- case ANY:
- // ANY does not match new lines.
- if (c == NUL) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case IDENT:
- if (!vim_isIDc(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SIDENT:
- if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case KWORD:
- if (!vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SKWORD:
- if (ascii_isdigit(*rex.input)
- || !vim_iswordp_buf((char *)rex.input, rex.reg_buf)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case FNAME:
- if (!vim_isfilec(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SFNAME:
- if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case PRINT:
- if (!vim_isprintc(utf_ptr2char((char *)rex.input))) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case SPRINT:
- if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char((char *)rex.input))) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case WHITE:
- if (!ascii_iswhite(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NWHITE:
- if (c == NUL || ascii_iswhite(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case DIGIT:
- if (!ri_digit(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NDIGIT:
- if (c == NUL || ri_digit(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case HEX:
- if (!ri_hex(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NHEX:
- if (c == NUL || ri_hex(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case OCTAL:
- if (!ri_octal(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NOCTAL:
- if (c == NUL || ri_octal(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case WORD:
- if (!ri_word(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NWORD:
- if (c == NUL || ri_word(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case HEAD:
- if (!ri_head(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NHEAD:
- if (c == NUL || ri_head(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case ALPHA:
- if (!ri_alpha(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NALPHA:
- if (c == NUL || ri_alpha(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case LOWER:
- if (!ri_lower(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NLOWER:
- if (c == NUL || ri_lower(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case UPPER:
- if (!ri_upper(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case NUPPER:
- if (c == NUL || ri_upper(c)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case EXACTLY: {
- int len;
- uint8_t *opnd;
-
- opnd = OPERAND(scan);
- // Inline the first byte, for speed.
- if (*opnd != *rex.input
- && (!rex.reg_ic)) {
- status = RA_NOMATCH;
- } else if (*opnd == NUL) {
- // match empty string always works; happens when "~" is
- // empty.
- } else {
- if (opnd[1] == NUL && !rex.reg_ic) {
- len = 1; // matched a single byte above
- } else {
- // Need to match first byte again for multi-byte.
- len = (int)strlen((char *)opnd);
- if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- }
- // Check for following composing character, unless %C
- // follows (skips over all composing chars).
- if (status != RA_NOMATCH
- && utf_composinglike((char *)rex.input, (char *)rex.input + len)
- && !rex.reg_icombine
- && OP(next) != RE_COMPOSING) {
- // raaron: This code makes a composing character get
- // ignored, which is the correct behavior (sometimes)
- // for voweled Hebrew texts.
- status = RA_NOMATCH;
- }
- if (status != RA_NOMATCH) {
- rex.input += len;
- }
- }
- }
- break;
-
- case ANYOF:
- case ANYBUT:
- if (c == NUL) {
- status = RA_NOMATCH;
- } else if ((cstrchr((char *)OPERAND(scan), c) == NULL) == (op == ANYOF)) {
- status = RA_NOMATCH;
- } else {
- ADVANCE_REGINPUT();
- }
- break;
-
- case MULTIBYTECODE: {
- int i, len;
-
- const uint8_t *opnd = OPERAND(scan);
- // Safety check (just in case 'encoding' was changed since
- // compiling the program).
- if ((len = utfc_ptr2len((char *)opnd)) < 2) {
- status = RA_NOMATCH;
- break;
- }
- const int opndc = utf_ptr2char((char *)opnd);
- if (utf_iscomposing(opndc)) {
- // When only a composing char is given match at any
- // position where that composing char appears.
- status = RA_NOMATCH;
- for (i = 0; rex.input[i] != NUL;
- i += utf_ptr2len((char *)rex.input + i)) {
- const int inpc = utf_ptr2char((char *)rex.input + i);
- if (!utf_iscomposing(inpc)) {
- if (i > 0) {
- break;
- }
- } else if (opndc == inpc) {
- // Include all following composing chars.
- len = i + utfc_ptr2len((char *)rex.input + i);
- status = RA_MATCH;
- break;
- }
- }
- } else {
- for (i = 0; i < len; i++) {
- if (opnd[i] != rex.input[i]) {
- status = RA_NOMATCH;
- break;
- }
- }
- }
- rex.input += len;
- }
- break;
-
- case RE_COMPOSING:
- // Skip composing characters.
- while (utf_iscomposing(utf_ptr2char((char *)rex.input))) {
- MB_CPTR_ADV(rex.input);
- }
- break;
-
- case NOTHING:
- break;
-
- case BACK: {
- int i;
-
- // When we run into BACK we need to check if we don't keep
- // looping without matching any input. The second and later
- // times a BACK is encountered it fails if the input is still
- // at the same position as the previous time.
- // The positions are stored in "backpos" and found by the
- // current value of "scan", the position in the RE program.
- backpos_T *bp = (backpos_T *)backpos.ga_data;
- for (i = 0; i < backpos.ga_len; i++) {
- if (bp[i].bp_scan == scan) {
- break;
- }
- }
- if (i == backpos.ga_len) {
- backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos);
- p->bp_scan = scan;
- } else if (reg_save_equal(&bp[i].bp_pos)) {
- // Still at same position as last time, fail.
- status = RA_NOMATCH;
- }
-
- assert(status != RA_FAIL);
- if (status != RA_NOMATCH) {
- reg_save(&bp[i].bp_pos, &backpos);
- }
- }
- break;
-
- case MOPEN + 0: // Match start: \zs
- case MOPEN + 1: // \(
- case MOPEN + 2:
- case MOPEN + 3:
- case MOPEN + 4:
- case MOPEN + 5:
- case MOPEN + 6:
- case MOPEN + 7:
- case MOPEN + 8:
- case MOPEN + 9:
- no = op - MOPEN;
- cleanup_subexpr();
- rp = regstack_push(RS_MOPEN, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &rex.reg_startpos[no],
- &rex.reg_startp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case NOPEN: // \%(
- case NCLOSE: // \) after \%(
- if (regstack_push(RS_NOPEN, scan) == NULL) {
- status = RA_FAIL;
- }
- // We simply continue and handle the result when done.
- break;
-
- case ZOPEN + 1:
- case ZOPEN + 2:
- case ZOPEN + 3:
- case ZOPEN + 4:
- case ZOPEN + 5:
- case ZOPEN + 6:
- case ZOPEN + 7:
- case ZOPEN + 8:
- case ZOPEN + 9:
- no = op - ZOPEN;
- cleanup_zsubexpr();
- rp = regstack_push(RS_ZOPEN, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &reg_startzpos[no],
- &reg_startzp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case MCLOSE + 0: // Match end: \ze
- case MCLOSE + 1: // \)
- case MCLOSE + 2:
- case MCLOSE + 3:
- case MCLOSE + 4:
- case MCLOSE + 5:
- case MCLOSE + 6:
- case MCLOSE + 7:
- case MCLOSE + 8:
- case MCLOSE + 9:
- no = op - MCLOSE;
- cleanup_subexpr();
- rp = regstack_push(RS_MCLOSE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case ZCLOSE + 1: // \) after \z(
- case ZCLOSE + 2:
- case ZCLOSE + 3:
- case ZCLOSE + 4:
- case ZCLOSE + 5:
- case ZCLOSE + 6:
- case ZCLOSE + 7:
- case ZCLOSE + 8:
- case ZCLOSE + 9:
- no = op - ZCLOSE;
- cleanup_zsubexpr();
- rp = regstack_push(RS_ZCLOSE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- save_se(&rp->rs_un.sesave, &reg_endzpos[no],
- &reg_endzp[no]);
- // We simply continue and handle the result when done.
- }
- break;
-
- case BACKREF + 1:
- case BACKREF + 2:
- case BACKREF + 3:
- case BACKREF + 4:
- case BACKREF + 5:
- case BACKREF + 6:
- case BACKREF + 7:
- case BACKREF + 8:
- case BACKREF + 9: {
- int len;
-
- no = op - BACKREF;
- cleanup_subexpr();
- if (!REG_MULTI) { // Single-line regexp
- if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) {
- // Backref was not set: Match an empty string.
- len = 0;
- } else {
- // Compare current input with back-ref in the same line.
- len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
- if (cstrncmp((char *)rex.reg_startp[no], (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- }
- } else { // Multi-line regexp
- if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) {
- // Backref was not set: Match an empty string.
- len = 0;
- } else {
- if (rex.reg_startpos[no].lnum == rex.lnum
- && rex.reg_endpos[no].lnum == rex.lnum) {
- // Compare back-ref within the current line.
- len = rex.reg_endpos[no].col - rex.reg_startpos[no].col;
- if (cstrncmp((char *)rex.line + rex.reg_startpos[no].col,
- (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- }
- } else {
- // Messy situation: Need to compare between two lines.
- int r = match_with_backref(rex.reg_startpos[no].lnum,
- rex.reg_startpos[no].col,
- rex.reg_endpos[no].lnum,
- rex.reg_endpos[no].col,
- &len);
- if (r != RA_MATCH) {
- status = r;
- }
- }
- }
- }
-
- // Matched the backref, skip over it.
- rex.input += len;
- }
- break;
-
- case ZREF + 1:
- case ZREF + 2:
- case ZREF + 3:
- case ZREF + 4:
- case ZREF + 5:
- case ZREF + 6:
- case ZREF + 7:
- case ZREF + 8:
- case ZREF + 9:
- cleanup_zsubexpr();
- no = op - ZREF;
- if (re_extmatch_in != NULL
- && re_extmatch_in->matches[no] != NULL) {
- int len = (int)strlen((char *)re_extmatch_in->matches[no]);
- if (cstrncmp((char *)re_extmatch_in->matches[no], (char *)rex.input, &len) != 0) {
- status = RA_NOMATCH;
- } else {
- rex.input += len;
- }
- } else {
- // Backref was not set: Match an empty string.
- }
- break;
-
- case BRANCH:
- if (OP(next) != BRANCH) { // No choice.
- next = OPERAND(scan); // Avoid recursion.
- } else {
- rp = regstack_push(RS_BRANCH, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- status = RA_BREAK; // rest is below
- }
- }
- break;
-
- case BRACE_LIMITS:
- if (OP(next) == BRACE_SIMPLE) {
- bl_minval = OPERAND_MIN(scan);
- bl_maxval = OPERAND_MAX(scan);
- } else if (OP(next) >= BRACE_COMPLEX
- && OP(next) < BRACE_COMPLEX + 10) {
- no = OP(next) - BRACE_COMPLEX;
- brace_min[no] = OPERAND_MIN(scan);
- brace_max[no] = OPERAND_MAX(scan);
- brace_count[no] = 0;
- } else {
- internal_error("BRACE_LIMITS");
- status = RA_FAIL;
- }
- break;
-
- case BRACE_COMPLEX + 0:
- case BRACE_COMPLEX + 1:
- case BRACE_COMPLEX + 2:
- case BRACE_COMPLEX + 3:
- case BRACE_COMPLEX + 4:
- case BRACE_COMPLEX + 5:
- case BRACE_COMPLEX + 6:
- case BRACE_COMPLEX + 7:
- case BRACE_COMPLEX + 8:
- case BRACE_COMPLEX + 9:
- no = op - BRACE_COMPLEX;
- brace_count[no]++;
-
- // If not matched enough times yet, try one more
- if (brace_count[no] <= (brace_min[no] <= brace_max[no]
- ? brace_min[no] : brace_max[no])) {
- rp = regstack_push(RS_BRCPLX_MORE, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- break;
- }
-
- // If matched enough times, may try matching some more
- if (brace_min[no] <= brace_max[no]) {
- // Range is the normal way around, use longest match
- if (brace_count[no] <= brace_max[no]) {
- rp = regstack_push(RS_BRCPLX_LONG, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)no;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- }
- } else {
- // Range is backwards, use shortest match first
- if (brace_count[no] <= brace_min[no]) {
- rp = regstack_push(RS_BRCPLX_SHORT, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- reg_save(&rp->rs_un.regsave, &backpos);
- // We continue and handle the result when done.
- }
- }
- }
- break;
-
- case BRACE_SIMPLE:
- case STAR:
- case PLUS: {
- regstar_T rst;
-
- // Lookahead to avoid useless match attempts when we know
- // what character comes next.
- if (OP(next) == EXACTLY) {
- rst.nextb = *OPERAND(next);
- if (rex.reg_ic) {
- if (mb_isupper(rst.nextb)) {
- rst.nextb_ic = mb_tolower(rst.nextb);
- } else {
- rst.nextb_ic = mb_toupper(rst.nextb);
- }
- } else {
- rst.nextb_ic = rst.nextb;
- }
- } else {
- rst.nextb = NUL;
- rst.nextb_ic = NUL;
- }
- if (op != BRACE_SIMPLE) {
- rst.minval = (op == STAR) ? 0 : 1;
- rst.maxval = MAX_LIMIT;
- } else {
- rst.minval = bl_minval;
- rst.maxval = bl_maxval;
- }
-
- // When maxval > minval, try matching as much as possible, up
- // to maxval. When maxval < minval, try matching at least the
- // minimal number (since the range is backwards, that's also
- // maxval!).
- rst.count = regrepeat(OPERAND(scan), rst.maxval);
- if (got_int) {
- status = RA_FAIL;
- break;
- }
- if (rst.minval <= rst.maxval
- ? rst.count >= rst.minval : rst.count >= rst.maxval) {
- // It could match. Prepare for trying to match what
- // follows. The code is below. Parameters are stored in
- // a regstar_T on the regstack.
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- status = RA_FAIL;
- } else {
- ga_grow(&regstack, sizeof(regstar_T));
- regstack.ga_len += (int)sizeof(regstar_T);
- rp = regstack_push(rst.minval <= rst.maxval ? RS_STAR_LONG : RS_STAR_SHORT, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- *(((regstar_T *)rp) - 1) = rst;
- status = RA_BREAK; // skip the restore bits
- }
- }
- } else {
- status = RA_NOMATCH;
- }
- }
- break;
-
- case NOMATCH:
- case MATCH:
- case SUBPAT:
- rp = regstack_push(RS_NOMATCH, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- rp->rs_no = (int16_t)op;
- reg_save(&rp->rs_un.regsave, &backpos);
- next = OPERAND(scan);
- // We continue and handle the result when done.
- }
- break;
-
- case BEHIND:
- case NOBEHIND:
- // Need a bit of room to store extra positions.
- if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- status = RA_FAIL;
- } else {
- ga_grow(&regstack, sizeof(regbehind_T));
- regstack.ga_len += (int)sizeof(regbehind_T);
- rp = regstack_push(RS_BEHIND1, scan);
- if (rp == NULL) {
- status = RA_FAIL;
- } else {
- // Need to save the subexpr to be able to restore them
- // when there is a match but we don't use it.
- save_subexpr(((regbehind_T *)rp) - 1);
-
- rp->rs_no = (int16_t)op;
- reg_save(&rp->rs_un.regsave, &backpos);
- // First try if what follows matches. If it does then we
- // check the behind match by looping.
- }
- }
- break;
-
- case BHPOS:
- if (REG_MULTI) {
- if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line)
- || behind_pos.rs_u.pos.lnum != rex.lnum) {
- status = RA_NOMATCH;
- }
- } else if (behind_pos.rs_u.ptr != rex.input) {
- status = RA_NOMATCH;
- }
- break;
-
- case NEWL:
- if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline
- || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) {
- status = RA_NOMATCH;
- } else if (rex.reg_line_lbr) {
- ADVANCE_REGINPUT();
- } else {
- reg_nextline();
- }
- break;
-
- case END:
- status = RA_MATCH; // Success!
- break;
-
- default:
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Illegal op code %d\n", op);
-#endif
- status = RA_FAIL;
- break;
- }
- }
-
- // If we can't continue sequentially, break the inner loop.
- if (status != RA_CONT) {
- break;
- }
-
- // Continue in inner loop, advance to next item.
- scan = next;
- } // end of inner loop
-
- // If there is something on the regstack execute the code for the state.
- // If the state is popped then loop and use the older state.
- while (!GA_EMPTY(&regstack) && status != RA_FAIL) {
- rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1;
- switch (rp->rs_state) {
- case RS_NOPEN:
- // Result is passed on as-is, simply pop the state.
- regstack_pop(&scan);
- break;
-
- case RS_MOPEN:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no],
- &rex.reg_startp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_ZOPEN:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &reg_startzpos[rp->rs_no],
- &reg_startzp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_MCLOSE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no],
- &rex.reg_endp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_ZCLOSE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- restore_se(&rp->rs_un.sesave, &reg_endzpos[rp->rs_no],
- &reg_endzp[rp->rs_no]);
- }
- regstack_pop(&scan);
- break;
-
- case RS_BRANCH:
- if (status == RA_MATCH) {
- // this branch matched, use it
- regstack_pop(&scan);
- } else {
- if (status != RA_BREAK) {
- // After a non-matching branch: try next one.
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = rp->rs_scan;
- }
- if (scan == NULL || OP(scan) != BRANCH) {
- // no more branches, didn't find a match
- status = RA_NOMATCH;
- regstack_pop(&scan);
- } else {
- // Prepare to try a branch.
- rp->rs_scan = regnext(scan);
- reg_save(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(scan);
- }
- }
- break;
-
- case RS_BRCPLX_MORE:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- reg_restore(&rp->rs_un.regsave, &backpos);
- brace_count[rp->rs_no]--; // decrement match count
- }
- regstack_pop(&scan);
- break;
-
- case RS_BRCPLX_LONG:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- // There was no match, but we did find enough matches.
- reg_restore(&rp->rs_un.regsave, &backpos);
- brace_count[rp->rs_no]--;
- // continue with the items after "\{}"
- status = RA_CONT;
- }
- regstack_pop(&scan);
- if (status == RA_CONT) {
- scan = regnext(scan);
- }
- break;
-
- case RS_BRCPLX_SHORT:
- // Pop the state. Restore pointers when there is no match.
- if (status == RA_NOMATCH) {
- // There was no match, try to match one more item.
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
- regstack_pop(&scan);
- if (status == RA_NOMATCH) {
- scan = OPERAND(scan);
- status = RA_CONT;
- }
- break;
-
- case RS_NOMATCH:
- // Pop the state. If the operand matches for NOMATCH or
- // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup,
- // except for SUBPAT, and continue with the next item.
- if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) {
- status = RA_NOMATCH;
- } else {
- status = RA_CONT;
- if (rp->rs_no != SUBPAT) { // zero-width
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
- }
- regstack_pop(&scan);
- if (status == RA_CONT) {
- scan = regnext(scan);
- }
- break;
-
- case RS_BEHIND1:
- if (status == RA_NOMATCH) {
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- } else {
- // The stuff after BEHIND/NOBEHIND matches. Now try if
- // the behind part does (not) match before the current
- // position in the input. This must be done at every
- // position in the input and checking if the match ends at
- // the current position.
-
- // save the position after the found match for next
- reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos);
-
- // Start looking for a match with operand at the current
- // position. Go back one character until we find the
- // result, hitting the start of the line or the previous
- // line (for multi-line matching).
- // Set behind_pos to where the match should end, BHPOS
- // will match it. Save the current value.
- (((regbehind_T *)rp) - 1)->save_behind = behind_pos;
- behind_pos = rp->rs_un.regsave;
-
- rp->rs_state = RS_BEHIND2;
-
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(rp->rs_scan) + 4;
- }
- break;
-
- case RS_BEHIND2:
- // Looping for BEHIND / NOBEHIND match.
- if (status == RA_MATCH && reg_save_equal(&behind_pos)) {
- // found a match that ends where "next" started
- behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
- if (rp->rs_no == BEHIND) {
- reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
- &backpos);
- } else {
- // But we didn't want a match. Need to restore the
- // subexpr, because what follows matched, so they have
- // been set.
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- } else {
- long limit;
-
- // No match or a match that doesn't end where we want it: Go
- // back one character. May go to previous line once.
- no = OK;
- limit = OPERAND_MIN(rp->rs_scan);
- if (REG_MULTI) {
- if (limit > 0
- && ((rp->rs_un.regsave.rs_u.pos.lnum
- < behind_pos.rs_u.pos.lnum
- ? (colnr_T)strlen((char *)rex.line)
- : behind_pos.rs_u.pos.col)
- - rp->rs_un.regsave.rs_u.pos.col >= limit)) {
- no = FAIL;
- } else if (rp->rs_un.regsave.rs_u.pos.col == 0) {
- if (rp->rs_un.regsave.rs_u.pos.lnum
- < behind_pos.rs_u.pos.lnum
- || reg_getline(--rp->rs_un.regsave.rs_u.pos.lnum)
- == NULL) {
- no = FAIL;
- } else {
- reg_restore(&rp->rs_un.regsave, &backpos);
- rp->rs_un.regsave.rs_u.pos.col =
- (colnr_T)strlen((char *)rex.line);
- }
- } else {
- const uint8_t *const line =
- (uint8_t *)reg_getline(rp->rs_un.regsave.rs_u.pos.lnum);
-
- rp->rs_un.regsave.rs_u.pos.col -=
- utf_head_off((char *)line,
- (char *)line + rp->rs_un.regsave.rs_u.pos.col - 1)
- + 1;
- }
- } else {
- if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
- no = FAIL;
- } else {
- MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
- if (limit > 0
- && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) {
- no = FAIL;
- }
- }
- }
- if (no == OK) {
- // Advanced, prepare for finding match again.
- reg_restore(&rp->rs_un.regsave, &backpos);
- scan = OPERAND(rp->rs_scan) + 4;
- if (status == RA_MATCH) {
- // We did match, so subexpr may have been changed,
- // need to restore them for the next try.
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- } else {
- // Can't advance. For NOBEHIND that's a match.
- behind_pos = (((regbehind_T *)rp) - 1)->save_behind;
- if (rp->rs_no == NOBEHIND) {
- reg_restore(&(((regbehind_T *)rp) - 1)->save_after,
- &backpos);
- status = RA_MATCH;
- } else {
- // We do want a proper match. Need to restore the
- // subexpr if we had a match, because they may have
- // been set.
- if (status == RA_MATCH) {
- status = RA_NOMATCH;
- restore_subexpr(((regbehind_T *)rp) - 1);
- }
- }
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regbehind_T);
- }
- }
- break;
-
- case RS_STAR_LONG:
- case RS_STAR_SHORT: {
- regstar_T *rst = ((regstar_T *)rp) - 1;
-
- if (status == RA_MATCH) {
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regstar_T);
- break;
- }
-
- // Tried once already, restore input pointers.
- if (status != RA_BREAK) {
- reg_restore(&rp->rs_un.regsave, &backpos);
- }
-
- // Repeat until we found a position where it could match.
- for (;;) {
- if (status != RA_BREAK) {
- // Tried first position already, advance.
- if (rp->rs_state == RS_STAR_LONG) {
- // Trying for longest match, but couldn't or
- // didn't match -- back up one char.
- if (--rst->count < rst->minval) {
- break;
- }
- if (rex.input == rex.line) {
- // backup to last char of previous line
- if (rex.lnum == 0) {
- status = RA_NOMATCH;
- break;
- }
- rex.lnum--;
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- // Just in case regrepeat() didn't count right.
- if (rex.line == NULL) {
- break;
- }
- rex.input = rex.line + strlen((char *)rex.line);
- fast_breakcheck();
- } else {
- MB_PTR_BACK(rex.line, rex.input);
- }
- } else {
- // Range is backwards, use shortest match first.
- // Careful: maxval and minval are exchanged!
- // Couldn't or didn't match: try advancing one
- // char.
- if (rst->count == rst->minval
- || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) {
- break;
- }
- rst->count++;
- }
- if (got_int) {
- break;
- }
- } else {
- status = RA_NOMATCH;
- }
-
- // If it could match, try it.
- if (rst->nextb == NUL || *rex.input == rst->nextb
- || *rex.input == rst->nextb_ic) {
- reg_save(&rp->rs_un.regsave, &backpos);
- scan = regnext(rp->rs_scan);
- status = RA_CONT;
- break;
- }
- }
- if (status != RA_CONT) {
- // Failed.
- regstack_pop(&scan);
- regstack.ga_len -= (int)sizeof(regstar_T);
- status = RA_NOMATCH;
- }
- }
- break;
- }
-
- // If we want to continue the inner loop or didn't pop a state
- // continue matching loop
- if (status == RA_CONT || rp == (regitem_T *)
- ((char *)regstack.ga_data + regstack.ga_len) - 1) {
- break;
- }
- }
-
- // May need to continue with the inner loop, starting at "scan".
- if (status == RA_CONT) {
- continue;
- }
-
- // If the regstack is empty or something failed we are done.
- if (GA_EMPTY(&regstack) || status == RA_FAIL) {
- if (scan == NULL) {
- // We get here only if there's trouble -- normally "case END" is
- // the terminating point.
- iemsg(_(e_re_corr));
-#ifdef REGEXP_DEBUG
- printf("Premature EOL\n");
-#endif
- }
- return status == RA_MATCH;
- }
- } // End of loop until the regstack is empty.
-
- // NOTREACHED
-}
-
-/// Try match of "prog" with at rex.line["col"].
-///
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return 0 for failure, or number of lines contained in the match.
-static long regtry(bt_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
-{
- rex.input = rex.line + col;
- rex.need_clear_subexpr = true;
- // Clear the external match subpointers if necessaey.
- rex.need_clear_zsubexpr = (prog->reghasz == REX_SET);
-
- if (regmatch(prog->program + 1, tm, timed_out) == 0) {
- return 0;
- }
-
- cleanup_subexpr();
- if (REG_MULTI) {
- if (rex.reg_startpos[0].lnum < 0) {
- rex.reg_startpos[0].lnum = 0;
- rex.reg_startpos[0].col = col;
- }
- if (rex.reg_endpos[0].lnum < 0) {
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (int)(rex.input - rex.line);
- } else {
- // Use line number of "\ze".
- rex.lnum = rex.reg_endpos[0].lnum;
- }
- } else {
- if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = rex.line + col;
- }
- if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = rex.input;
- }
- }
- // Package any found \z(...\) matches for export. Default is none.
- unref_extmatch(re_extmatch_out);
- re_extmatch_out = NULL;
-
- if (prog->reghasz == REX_SET) {
- int i;
-
- cleanup_zsubexpr();
- re_extmatch_out = make_extmatch();
- for (i = 0; i < NSUBEXP; i++) {
- if (REG_MULTI) {
- // Only accept single line matches.
- if (reg_startzpos[i].lnum >= 0
- && reg_endzpos[i].lnum == reg_startzpos[i].lnum
- && reg_endzpos[i].col >= reg_startzpos[i].col) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_getline(reg_startzpos[i].lnum) + reg_startzpos[i].col,
- (size_t)(reg_endzpos[i].col - reg_startzpos[i].col));
- }
- } else {
- if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_startzp[i], (size_t)(reg_endzp[i] - reg_startzp[i]));
- }
- }
- }
- }
- return 1 + rex.lnum;
-}
-
-/// Match a regexp against a string ("line" points to the string) or multiple
-/// lines (if "line" is NULL, use reg_getline()).
-///
-/// @param startcol column to start looking for match
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return 0 for failure, or number of lines contained in the match.
-static long bt_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
-{
- bt_regprog_T *prog;
- uint8_t *s;
- colnr_T col = startcol;
- long retval = 0L;
-
- // Create "regstack" and "backpos" if they are not allocated yet.
- // We allocate *_INITIAL amount of bytes first and then set the grow size
- // to much bigger value to avoid many malloc calls in case of deep regular
- // expressions.
- if (regstack.ga_data == NULL) {
- // Use an item size of 1 byte, since we push different things
- // onto the regstack.
- ga_init(&regstack, 1, REGSTACK_INITIAL);
- ga_grow(&regstack, REGSTACK_INITIAL);
- ga_set_growsize(&regstack, REGSTACK_INITIAL * 8);
- }
-
- if (backpos.ga_data == NULL) {
- ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL);
- ga_grow(&backpos, BACKPOS_INITIAL);
- ga_set_growsize(&backpos, BACKPOS_INITIAL * 8);
- }
-
- if (REG_MULTI) {
- prog = (bt_regprog_T *)rex.reg_mmatch->regprog;
- line = (uint8_t *)reg_getline((linenr_T)0);
- rex.reg_startpos = rex.reg_mmatch->startpos;
- rex.reg_endpos = rex.reg_mmatch->endpos;
- } else {
- prog = (bt_regprog_T *)rex.reg_match->regprog;
- rex.reg_startp = (uint8_t **)rex.reg_match->startp;
- rex.reg_endp = (uint8_t **)rex.reg_match->endp;
- }
-
- // Be paranoid...
- if (prog == NULL || line == NULL) {
- iemsg(_(e_null));
- goto theend;
- }
-
- // Check validity of program.
- if (prog_magic_wrong()) {
- goto theend;
- }
-
- // If the start column is past the maximum column: no need to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- goto theend;
- }
-
- // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
- if (prog->regflags & RF_ICASE) {
- rex.reg_ic = true;
- } else if (prog->regflags & RF_NOICASE) {
- rex.reg_ic = false;
- }
-
- // If pattern contains "\Z" overrule value of rex.reg_icombine
- if (prog->regflags & RF_ICOMBINE) {
- rex.reg_icombine = true;
- }
-
- // If there is a "must appear" string, look for it.
- if (prog->regmust != NULL) {
- int c = utf_ptr2char((char *)prog->regmust);
- s = line + col;
-
- // This is used very often, esp. for ":global". Use two versions of
- // the loop to avoid overhead of conditions.
- if (!rex.reg_ic) {
- while ((s = (uint8_t *)vim_strchr((char *)s, c)) != NULL) {
- if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
- break; // Found it.
- }
- MB_PTR_ADV(s);
- }
- } else {
- while ((s = (uint8_t *)cstrchr((char *)s, c)) != NULL) {
- if (cstrncmp((char *)s, (char *)prog->regmust, &prog->regmlen) == 0) {
- break; // Found it.
- }
- MB_PTR_ADV(s);
- }
- }
- if (s == NULL) { // Not present.
- goto theend;
- }
- }
-
- rex.line = line;
- rex.lnum = 0;
- reg_toolong = false;
-
- // Simplest case: Anchored match need be tried only once.
- if (prog->reganch) {
- int c = utf_ptr2char((char *)rex.line + col);
- if (prog->regstart == NUL
- || prog->regstart == c
- || (rex.reg_ic
- && (utf_fold(prog->regstart) == utf_fold(c)
- || (c < 255 && prog->regstart < 255
- && mb_tolower(prog->regstart) == mb_tolower(c))))) {
- retval = regtry(prog, col, tm, timed_out);
- } else {
- retval = 0;
- }
- } else {
- int tm_count = 0;
- // Messy cases: unanchored match.
- while (!got_int) {
- if (prog->regstart != NUL) {
- // Skip until the char we know it must start with.
- s = (uint8_t *)cstrchr((char *)rex.line + col, prog->regstart);
- if (s == NULL) {
- retval = 0;
- break;
- }
- col = (int)(s - rex.line);
- }
-
- // Check for maximum column to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- retval = 0;
- break;
- }
-
- retval = regtry(prog, col, tm, timed_out);
- if (retval > 0) {
- break;
- }
-
- // if not currently on the first line, get it again
- if (rex.lnum != 0) {
- rex.lnum = 0;
- rex.line = (uint8_t *)reg_getline((linenr_T)0);
- }
- if (rex.line[col] == NUL) {
- break;
- }
- col += utfc_ptr2len((char *)rex.line + col);
- // Check for timeout once in a twenty times to avoid overhead.
- if (tm != NULL && ++tm_count == 20) {
- tm_count = 0;
- if (profile_passed_limit(*tm)) {
- if (timed_out != NULL) {
- *timed_out = true;
- }
- break;
- }
- }
- }
- }
-
-theend:
- // Free "reg_tofree" when it's a bit big.
- // Free regstack and backpos if they are bigger than their initial size.
- if (reg_tofreelen > 400) {
- XFREE_CLEAR(reg_tofree);
- }
- if (regstack.ga_maxlen > REGSTACK_INITIAL) {
- ga_clear(&regstack);
- }
- if (backpos.ga_maxlen > BACKPOS_INITIAL) {
- ga_clear(&backpos);
- }
-
- if (retval > 0) {
- // Make sure the end is never before the start. Can happen when \zs
- // and \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
-
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_mmatch->rmm_matchcol = col;
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_match->rm_matchcol = col;
- }
- }
-
- return retval;
-}
-
-/// Match a regexp against a string.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
-///
-/// @param line string to match against
-/// @param col column to start looking for match
-///
-/// @return 0 for failure, number of lines contained in the match otherwise.
-static int bt_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
-{
- rex.reg_match = rmp;
- rex.reg_mmatch = NULL;
- rex.reg_maxline = 0;
- rex.reg_line_lbr = line_lbr;
- rex.reg_buf = curbuf;
- rex.reg_win = NULL;
- rex.reg_ic = rmp->rm_ic;
- rex.reg_icombine = false;
- rex.reg_maxcol = 0;
-
- long r = bt_regexec_both(line, col, NULL, NULL);
- assert(r <= INT_MAX);
- return (int)r;
-}
-
-/// Matches a regexp against multiple lines.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-///
-/// @param win Window in which to search or NULL
-/// @param buf Buffer in which to search
-/// @param lnum Number of line to start looking for match
-/// @param col Column to start looking for match
-/// @param tm Timeout limit or NULL
-///
-/// @return zero if there is no match and number of lines contained in the match
-/// otherwise.
-static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
-{
- init_regexec_multi(rmp, win, buf, lnum);
- return bt_regexec_both(NULL, col, tm, timed_out);
-}
-
-// Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL.
-static int re_num_cmp(uint32_t val, uint8_t *scan)
-{
- uint32_t n = (uint32_t)OPERAND_MIN(scan);
-
- if (OPERAND_CMP(scan) == '>') {
- return val > n;
- }
- if (OPERAND_CMP(scan) == '<') {
- return val < n;
- }
- return val == n;
-}
-
-#ifdef BT_REGEXP_DUMP
-
-// regdump - dump a regexp onto stdout in vaguely comprehensible form
-static void regdump(uint8_t *pattern, bt_regprog_T *r)
-{
- uint8_t *s;
- int op = EXACTLY; // Arbitrary non-END op.
- uint8_t *next;
- uint8_t *end = NULL;
- FILE *f;
-
-# ifdef BT_REGEXP_LOG
- f = fopen("bt_regexp_log.log", "a");
-# else
- f = stdout;
-# endif
- if (f == NULL) {
- return;
- }
- fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n",
- pattern);
-
- s = r->program + 1;
- // Loop until we find the END that isn't before a referred next (an END
- // can also appear in a NOMATCH operand).
- while (op != END || s <= end) {
- op = OP(s);
- fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what.
- next = regnext(s);
- if (next == NULL) { // Next ptr.
- fprintf(f, "(0)");
- } else {
- fprintf(f, "(%d)", (int)((s - r->program) + (next - s)));
- }
- if (end < next) {
- end = next;
- }
- if (op == BRACE_LIMITS) {
- // Two ints
- fprintf(f, " minval %" PRId64 ", maxval %" PRId64,
- (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s));
- s += 8;
- } else if (op == BEHIND || op == NOBEHIND) {
- // one int
- fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
- s += 4;
- } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) {
- // one int plus comparator
- fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s));
- s += 5;
- }
- s += 3;
- if (op == ANYOF || op == ANYOF + ADD_NL
- || op == ANYBUT || op == ANYBUT + ADD_NL
- || op == EXACTLY) {
- // Literal string, where present.
- fprintf(f, "\nxxxxxxxxx\n");
- while (*s != NUL) {
- fprintf(f, "%c", *s++);
- }
- fprintf(f, "\nxxxxxxxxx\n");
- s++;
- }
- fprintf(f, "\r\n");
- }
-
- // Header fields of interest.
- if (r->regstart != NUL) {
- fprintf(f, "start `%s' 0x%x; ", r->regstart < 256
- ? (char *)transchar(r->regstart)
- : "multibyte", r->regstart);
- }
- if (r->reganch) {
- fprintf(f, "anchored; ");
- }
- if (r->regmust != NULL) {
- fprintf(f, "must have \"%s\"", r->regmust);
- }
- fprintf(f, "\r\n");
-
-# ifdef BT_REGEXP_LOG
- fclose(f);
-# endif
-}
-#endif // BT_REGEXP_DUMP
-
-#ifdef REGEXP_DEBUG
-
-// regprop - printable representation of opcode
-static uint8_t *regprop(uint8_t *op)
-{
- char *p;
- static char buf[50];
-
- STRCPY(buf, ":");
-
- switch ((int)OP(op)) {
- case BOL:
- p = "BOL";
- break;
- case EOL:
- p = "EOL";
- break;
- case RE_BOF:
- p = "BOF";
- break;
- case RE_EOF:
- p = "EOF";
- break;
- case CURSOR:
- p = "CURSOR";
- break;
- case RE_VISUAL:
- p = "RE_VISUAL";
- break;
- case RE_LNUM:
- p = "RE_LNUM";
- break;
- case RE_MARK:
- p = "RE_MARK";
- break;
- case RE_COL:
- p = "RE_COL";
- break;
- case RE_VCOL:
- p = "RE_VCOL";
- break;
- case BOW:
- p = "BOW";
- break;
- case EOW:
- p = "EOW";
- break;
- case ANY:
- p = "ANY";
- break;
- case ANY + ADD_NL:
- p = "ANY+NL";
- break;
- case ANYOF:
- p = "ANYOF";
- break;
- case ANYOF + ADD_NL:
- p = "ANYOF+NL";
- break;
- case ANYBUT:
- p = "ANYBUT";
- break;
- case ANYBUT + ADD_NL:
- p = "ANYBUT+NL";
- break;
- case IDENT:
- p = "IDENT";
- break;
- case IDENT + ADD_NL:
- p = "IDENT+NL";
- break;
- case SIDENT:
- p = "SIDENT";
- break;
- case SIDENT + ADD_NL:
- p = "SIDENT+NL";
- break;
- case KWORD:
- p = "KWORD";
- break;
- case KWORD + ADD_NL:
- p = "KWORD+NL";
- break;
- case SKWORD:
- p = "SKWORD";
- break;
- case SKWORD + ADD_NL:
- p = "SKWORD+NL";
- break;
- case FNAME:
- p = "FNAME";
- break;
- case FNAME + ADD_NL:
- p = "FNAME+NL";
- break;
- case SFNAME:
- p = "SFNAME";
- break;
- case SFNAME + ADD_NL:
- p = "SFNAME+NL";
- break;
- case PRINT:
- p = "PRINT";
- break;
- case PRINT + ADD_NL:
- p = "PRINT+NL";
- break;
- case SPRINT:
- p = "SPRINT";
- break;
- case SPRINT + ADD_NL:
- p = "SPRINT+NL";
- break;
- case WHITE:
- p = "WHITE";
- break;
- case WHITE + ADD_NL:
- p = "WHITE+NL";
- break;
- case NWHITE:
- p = "NWHITE";
- break;
- case NWHITE + ADD_NL:
- p = "NWHITE+NL";
- break;
- case DIGIT:
- p = "DIGIT";
- break;
- case DIGIT + ADD_NL:
- p = "DIGIT+NL";
- break;
- case NDIGIT:
- p = "NDIGIT";
- break;
- case NDIGIT + ADD_NL:
- p = "NDIGIT+NL";
- break;
- case HEX:
- p = "HEX";
- break;
- case HEX + ADD_NL:
- p = "HEX+NL";
- break;
- case NHEX:
- p = "NHEX";
- break;
- case NHEX + ADD_NL:
- p = "NHEX+NL";
- break;
- case OCTAL:
- p = "OCTAL";
- break;
- case OCTAL + ADD_NL:
- p = "OCTAL+NL";
- break;
- case NOCTAL:
- p = "NOCTAL";
- break;
- case NOCTAL + ADD_NL:
- p = "NOCTAL+NL";
- break;
- case WORD:
- p = "WORD";
- break;
- case WORD + ADD_NL:
- p = "WORD+NL";
- break;
- case NWORD:
- p = "NWORD";
- break;
- case NWORD + ADD_NL:
- p = "NWORD+NL";
- break;
- case HEAD:
- p = "HEAD";
- break;
- case HEAD + ADD_NL:
- p = "HEAD+NL";
- break;
- case NHEAD:
- p = "NHEAD";
- break;
- case NHEAD + ADD_NL:
- p = "NHEAD+NL";
- break;
- case ALPHA:
- p = "ALPHA";
- break;
- case ALPHA + ADD_NL:
- p = "ALPHA+NL";
- break;
- case NALPHA:
- p = "NALPHA";
- break;
- case NALPHA + ADD_NL:
- p = "NALPHA+NL";
- break;
- case LOWER:
- p = "LOWER";
- break;
- case LOWER + ADD_NL:
- p = "LOWER+NL";
- break;
- case NLOWER:
- p = "NLOWER";
- break;
- case NLOWER + ADD_NL:
- p = "NLOWER+NL";
- break;
- case UPPER:
- p = "UPPER";
- break;
- case UPPER + ADD_NL:
- p = "UPPER+NL";
- break;
- case NUPPER:
- p = "NUPPER";
- break;
- case NUPPER + ADD_NL:
- p = "NUPPER+NL";
- break;
- case BRANCH:
- p = "BRANCH";
- break;
- case EXACTLY:
- p = "EXACTLY";
- break;
- case NOTHING:
- p = "NOTHING";
- break;
- case BACK:
- p = "BACK";
- break;
- case END:
- p = "END";
- break;
- case MOPEN + 0:
- p = "MATCH START";
- break;
- case MOPEN + 1:
- case MOPEN + 2:
- case MOPEN + 3:
- case MOPEN + 4:
- case MOPEN + 5:
- case MOPEN + 6:
- case MOPEN + 7:
- case MOPEN + 8:
- case MOPEN + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MOPEN%d", OP(op) - MOPEN);
- p = NULL;
- break;
- case MCLOSE + 0:
- p = "MATCH END";
- break;
- case MCLOSE + 1:
- case MCLOSE + 2:
- case MCLOSE + 3:
- case MCLOSE + 4:
- case MCLOSE + 5:
- case MCLOSE + 6:
- case MCLOSE + 7:
- case MCLOSE + 8:
- case MCLOSE + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "MCLOSE%d", OP(op) - MCLOSE);
- p = NULL;
- break;
- case BACKREF + 1:
- case BACKREF + 2:
- case BACKREF + 3:
- case BACKREF + 4:
- case BACKREF + 5:
- case BACKREF + 6:
- case BACKREF + 7:
- case BACKREF + 8:
- case BACKREF + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BACKREF%d", OP(op) - BACKREF);
- p = NULL;
- break;
- case NOPEN:
- p = "NOPEN";
- break;
- case NCLOSE:
- p = "NCLOSE";
- break;
- case ZOPEN + 1:
- case ZOPEN + 2:
- case ZOPEN + 3:
- case ZOPEN + 4:
- case ZOPEN + 5:
- case ZOPEN + 6:
- case ZOPEN + 7:
- case ZOPEN + 8:
- case ZOPEN + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZOPEN%d", OP(op) - ZOPEN);
- p = NULL;
- break;
- case ZCLOSE + 1:
- case ZCLOSE + 2:
- case ZCLOSE + 3:
- case ZCLOSE + 4:
- case ZCLOSE + 5:
- case ZCLOSE + 6:
- case ZCLOSE + 7:
- case ZCLOSE + 8:
- case ZCLOSE + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZCLOSE%d", OP(op) - ZCLOSE);
- p = NULL;
- break;
- case ZREF + 1:
- case ZREF + 2:
- case ZREF + 3:
- case ZREF + 4:
- case ZREF + 5:
- case ZREF + 6:
- case ZREF + 7:
- case ZREF + 8:
- case ZREF + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "ZREF%d", OP(op) - ZREF);
- p = NULL;
- break;
- case STAR:
- p = "STAR";
- break;
- case PLUS:
- p = "PLUS";
- break;
- case NOMATCH:
- p = "NOMATCH";
- break;
- case MATCH:
- p = "MATCH";
- break;
- case BEHIND:
- p = "BEHIND";
- break;
- case NOBEHIND:
- p = "NOBEHIND";
- break;
- case SUBPAT:
- p = "SUBPAT";
- break;
- case BRACE_LIMITS:
- p = "BRACE_LIMITS";
- break;
- case BRACE_SIMPLE:
- p = "BRACE_SIMPLE";
- break;
- case BRACE_COMPLEX + 0:
- case BRACE_COMPLEX + 1:
- case BRACE_COMPLEX + 2:
- case BRACE_COMPLEX + 3:
- case BRACE_COMPLEX + 4:
- case BRACE_COMPLEX + 5:
- case BRACE_COMPLEX + 6:
- case BRACE_COMPLEX + 7:
- case BRACE_COMPLEX + 8:
- case BRACE_COMPLEX + 9:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "BRACE_COMPLEX%d",
- OP(op) - BRACE_COMPLEX);
- p = NULL;
- break;
- case MULTIBYTECODE:
- p = "MULTIBYTECODE";
- break;
- case NEWL:
- p = "NEWL";
- break;
- default:
- snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "corrupt %d", OP(op));
- p = NULL;
- break;
- }
- if (p != NULL) {
- STRCAT(buf, p);
- }
- return (uint8_t *)buf;
-}
-#endif // REGEXP_DEBUG
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 16bb2db464..079f3b6929 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -7,13 +7,13 @@
//
// NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
-#ifndef NVIM_REGEXP_DEFS_H
-#define NVIM_REGEXP_DEFS_H
+#pragma once
#include <stdbool.h>
+#include <stdint.h>
-#include "nvim/pos.h"
-#include "nvim/types.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
/// Used for "magic_overruled".
typedef enum {
@@ -33,25 +33,29 @@ typedef enum {
MAGIC_ALL = 4, ///< "\v" very magic
} magic_T;
-// The number of sub-matches is limited to 10.
-// The first one (index 0) is the whole match, referenced with "\0".
-// The second one (index 1) is the first sub-match, referenced with "\1".
-// This goes up to the tenth (index 9), referenced with "\9".
-#define NSUBEXP 10
+/// The number of sub-matches is limited to 10.
+/// The first one (index 0) is the whole match, referenced with "\0".
+/// The second one (index 1) is the first sub-match, referenced with "\1".
+/// This goes up to the tenth (index 9), referenced with "\9".
+enum { NSUBEXP = 10, };
-// In the NFA engine: how many braces are allowed.
-// TODO(RE): Use dynamic memory allocation instead of static, like here
-#define NFA_MAX_BRACES 20
+/// In the NFA engine: how many braces are allowed.
+/// TODO(RE): Use dynamic memory allocation instead of static, like here
+enum { NFA_MAX_BRACES = 20, };
-// In the NFA engine: how many states are allowed.
-#define NFA_MAX_STATES 100000
-#define NFA_TOO_EXPENSIVE (-1)
+/// In the NFA engine: how many states are allowed.
+enum {
+ NFA_MAX_STATES = 100000,
+ NFA_TOO_EXPENSIVE = -1,
+};
-// Which regexp engine to use? Needed for vim_regcomp().
-// Must match with 'regexpengine'.
-#define AUTOMATIC_ENGINE 0
-#define BACKTRACKING_ENGINE 1
-#define NFA_ENGINE 2
+/// Which regexp engine to use? Needed for vim_regcomp().
+/// Must match with 'regexpengine'.
+enum {
+ AUTOMATIC_ENGINE = 0,
+ BACKTRACKING_ENGINE = 1,
+ NFA_ENGINE = 2,
+};
typedef struct regengine regengine_T;
typedef struct regprog regprog_T;
@@ -70,14 +74,14 @@ typedef struct {
colnr_T rmm_matchcol; ///< match start without "\zs"
int rmm_ic;
- colnr_T rmm_maxcol; /// when not zero: maximum column
+ colnr_T rmm_maxcol; ///< when not zero: maximum column
} regmmatch_T;
#include "nvim/buffer_defs.h"
-// Structure returned by vim_regcomp() to pass on to vim_regexec().
-// This is the general structure. For the actual matcher, two specific
-// structures are used. See code below.
+/// Structure returned by vim_regcomp() to pass on to vim_regexec().
+/// This is the general structure. For the actual matcher, two specific
+/// structures are used. See code below.
struct regprog {
regengine_T *engine;
unsigned regflags;
@@ -86,9 +90,9 @@ struct regprog {
bool re_in_use; ///< prog is being executed
};
-// Structure used by the back track matcher.
-// These fields are only to be used in regexp.c!
-// See regexp.c for an explanation.
+/// Structure used by the back track matcher.
+/// These fields are only to be used in regexp.c!
+/// See regexp.c for an explanation.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -98,26 +102,26 @@ typedef struct {
bool re_in_use;
int regstart;
- char_u reganch;
- char_u *regmust;
+ uint8_t reganch;
+ uint8_t *regmust;
int regmlen;
- char_u reghasz;
- char_u program[1]; // actually longer..
+ uint8_t reghasz;
+ uint8_t program[];
} bt_regprog_T;
-// Structure representing a NFA state.
-// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
+/// Structure representing a NFA state.
+/// An NFA state may have no outgoing edge, when it is a NFA_MATCH state.
typedef struct nfa_state nfa_state_T;
struct nfa_state {
int c;
nfa_state_T *out;
nfa_state_T *out1;
int id;
- int lastlist[2]; // 0: normal, 1: recursive
+ int lastlist[2]; ///< 0: normal, 1: recursive
int val;
};
-// Structure used by the NFA matcher.
+/// Structure used by the NFA matcher.
typedef struct {
// These four members implement regprog_T.
regengine_T *engine;
@@ -126,24 +130,24 @@ typedef struct {
unsigned re_flags;
bool re_in_use;
- nfa_state_T *start; // points into state[]
+ nfa_state_T *start; ///< points into state[]
- int reganch; // pattern starts with ^
- int regstart; // char at start of pattern
- char_u *match_text; // plain text to match with
+ int reganch; ///< pattern starts with ^
+ int regstart; ///< char at start of pattern
+ uint8_t *match_text; ///< plain text to match with
- int has_zend; // pattern contains \ze
- int has_backref; // pattern contains \1 .. \9
+ int has_zend; ///< pattern contains \ze
+ int has_backref; ///< pattern contains \1 .. \9
int reghasz;
char *pattern;
- int nsubexp; // number of ()
+ int nsubexp; ///< number of ()
int nstate;
- nfa_state_T state[1]; // actually longer..
+ nfa_state_T state[];
} nfa_regprog_T;
-// Structure to be used for single-line matching.
-// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
-// When there is no match, the pointer is NULL.
+/// Structure to be used for single-line matching.
+/// Sub-match "no" starts at "startp[no]" and ends just before "endp[no]".
+/// When there is no match, the pointer is NULL.
typedef struct {
regprog_T *regprog;
char *startp[NSUBEXP];
@@ -153,29 +157,29 @@ typedef struct {
bool rm_ic;
} regmatch_T;
-// Structure used to store external references: "\z\(\)" to "\z\1".
-// Use a reference count to avoid the need to copy this around. When it goes
-// from 1 to zero the matches need to be freed.
+/// Structure used to store external references: "\z\(\)" to "\z\1".
+/// Use a reference count to avoid the need to copy this around. When it goes
+/// from 1 to zero the matches need to be freed.
struct reg_extmatch {
int16_t refcnt;
- char_u *matches[NSUBEXP];
+ uint8_t *matches[NSUBEXP];
};
struct regengine {
/// bt_regcomp or nfa_regcomp
- regprog_T *(*regcomp)(char_u *, int);
+ regprog_T *(*regcomp)(uint8_t *, int);
/// bt_regfree or nfa_regfree
void (*regfree)(regprog_T *);
/// bt_regexec_nl or nfa_regexec_nl
- int (*regexec_nl)(regmatch_T *, char_u *, colnr_T, bool);
+ int (*regexec_nl)(regmatch_T *, uint8_t *, colnr_T, bool);
/// bt_regexec_mult or nfa_regexec_mult
- long (*regexec_multi)(regmmatch_T *, win_T *, buf_T *, linenr_T, colnr_T, proftime_T *, int *);
- // char_u *expr;
+ int (*regexec_multi)(regmmatch_T *, win_T *, buf_T *, linenr_T, colnr_T, proftime_T *, int *);
+ // uint8_t *expr;
};
-// Flags used by vim_regsub() and vim_regsub_both()
-#define REGSUB_COPY 1
-#define REGSUB_MAGIC 2
-#define REGSUB_BACKSLASH 4
-
-#endif // NVIM_REGEXP_DEFS_H
+/// Flags used by vim_regsub() and vim_regsub_both()
+enum {
+ REGSUB_COPY = 1,
+ REGSUB_MAGIC = 2,
+ REGSUB_BACKSLASH = 4,
+};
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
deleted file mode 100644
index ea59e7d464..0000000000
--- a/src/nvim/regexp_nfa.c
+++ /dev/null
@@ -1,7639 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// NFA regular expression implementation.
-//
-// This file is included in "regexp.c".
-
-#include <assert.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-
-#include "nvim/ascii.h"
-#include "nvim/garray.h"
-#include "nvim/os/input.h"
-
-// Logging of NFA engine.
-//
-// The NFA engine can write four log files:
-// - Error log: Contains NFA engine's fatal errors.
-// - Dump log: Contains compiled NFA state machine's information.
-// - Run log: Contains information of matching procedure.
-// - Debug log: Contains detailed information of matching procedure. Can be
-// disabled by undefining NFA_REGEXP_DEBUG_LOG.
-// The first one can also be used without debug mode.
-// The last three are enabled when compiled as debug mode and individually
-// disabled by commenting them out.
-// The log files can get quite big!
-// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in
-// regexp.c
-#ifdef REGEXP_DEBUG
-# define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log"
-# define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log"
-# define NFA_REGEXP_RUN_LOG "nfa_regexp_run.log"
-# define NFA_REGEXP_DEBUG_LOG "nfa_regexp_debug.log"
-#endif
-
-// Added to NFA_ANY - NFA_NUPPER_IC to include a NL.
-#define NFA_ADD_NL 31
-
-enum {
- NFA_SPLIT = -1024,
- NFA_MATCH,
- NFA_EMPTY, // matches 0-length
-
- NFA_START_COLL, // [abc] start
- NFA_END_COLL, // [abc] end
- NFA_START_NEG_COLL, // [^abc] start
- NFA_END_NEG_COLL, // [^abc] end (postfix only)
- NFA_RANGE, // range of the two previous items
- // (postfix only)
- NFA_RANGE_MIN, // low end of a range
- NFA_RANGE_MAX, // high end of a range
-
- NFA_CONCAT, // concatenate two previous items (postfix
- // only)
- NFA_OR, // \| (postfix only)
- NFA_STAR, // greedy * (postfix only)
- NFA_STAR_NONGREEDY, // non-greedy * (postfix only)
- NFA_QUEST, // greedy \? (postfix only)
- NFA_QUEST_NONGREEDY, // non-greedy \? (postfix only)
-
- NFA_BOL, // ^ Begin line
- NFA_EOL, // $ End line
- NFA_BOW, // \< Begin word
- NFA_EOW, // \> End word
- NFA_BOF, // \%^ Begin file
- NFA_EOF, // \%$ End file
- NFA_NEWL,
- NFA_ZSTART, // Used for \zs
- NFA_ZEND, // Used for \ze
- NFA_NOPEN, // Start of subexpression marked with \%(
- NFA_NCLOSE, // End of subexpr. marked with \%( ... \)
- NFA_START_INVISIBLE,
- NFA_START_INVISIBLE_FIRST,
- NFA_START_INVISIBLE_NEG,
- NFA_START_INVISIBLE_NEG_FIRST,
- NFA_START_INVISIBLE_BEFORE,
- NFA_START_INVISIBLE_BEFORE_FIRST,
- NFA_START_INVISIBLE_BEFORE_NEG,
- NFA_START_INVISIBLE_BEFORE_NEG_FIRST,
- NFA_START_PATTERN,
- NFA_END_INVISIBLE,
- NFA_END_INVISIBLE_NEG,
- NFA_END_PATTERN,
- NFA_COMPOSING, // Next nodes in NFA are part of the
- // composing multibyte char
- NFA_END_COMPOSING, // End of a composing char in the NFA
- NFA_ANY_COMPOSING, // \%C: Any composing characters.
- NFA_OPT_CHARS, // \%[abc]
-
- // The following are used only in the postfix form, not in the NFA
- NFA_PREV_ATOM_NO_WIDTH, // Used for \@=
- NFA_PREV_ATOM_NO_WIDTH_NEG, // Used for \@!
- NFA_PREV_ATOM_JUST_BEFORE, // Used for \@<=
- NFA_PREV_ATOM_JUST_BEFORE_NEG, // Used for \@<!
- NFA_PREV_ATOM_LIKE_PATTERN, // Used for \@>
-
- NFA_BACKREF1, // \1
- NFA_BACKREF2, // \2
- NFA_BACKREF3, // \3
- NFA_BACKREF4, // \4
- NFA_BACKREF5, // \5
- NFA_BACKREF6, // \6
- NFA_BACKREF7, // \7
- NFA_BACKREF8, // \8
- NFA_BACKREF9, // \9
- NFA_ZREF1, // \z1
- NFA_ZREF2, // \z2
- NFA_ZREF3, // \z3
- NFA_ZREF4, // \z4
- NFA_ZREF5, // \z5
- NFA_ZREF6, // \z6
- NFA_ZREF7, // \z7
- NFA_ZREF8, // \z8
- NFA_ZREF9, // \z9
- NFA_SKIP, // Skip characters
-
- NFA_MOPEN,
- NFA_MOPEN1,
- NFA_MOPEN2,
- NFA_MOPEN3,
- NFA_MOPEN4,
- NFA_MOPEN5,
- NFA_MOPEN6,
- NFA_MOPEN7,
- NFA_MOPEN8,
- NFA_MOPEN9,
-
- NFA_MCLOSE,
- NFA_MCLOSE1,
- NFA_MCLOSE2,
- NFA_MCLOSE3,
- NFA_MCLOSE4,
- NFA_MCLOSE5,
- NFA_MCLOSE6,
- NFA_MCLOSE7,
- NFA_MCLOSE8,
- NFA_MCLOSE9,
-
- NFA_ZOPEN,
- NFA_ZOPEN1,
- NFA_ZOPEN2,
- NFA_ZOPEN3,
- NFA_ZOPEN4,
- NFA_ZOPEN5,
- NFA_ZOPEN6,
- NFA_ZOPEN7,
- NFA_ZOPEN8,
- NFA_ZOPEN9,
-
- NFA_ZCLOSE,
- NFA_ZCLOSE1,
- NFA_ZCLOSE2,
- NFA_ZCLOSE3,
- NFA_ZCLOSE4,
- NFA_ZCLOSE5,
- NFA_ZCLOSE6,
- NFA_ZCLOSE7,
- NFA_ZCLOSE8,
- NFA_ZCLOSE9,
-
- // NFA_FIRST_NL
- NFA_ANY, // Match any one character.
- NFA_IDENT, // Match identifier char
- NFA_SIDENT, // Match identifier char but no digit
- NFA_KWORD, // Match keyword char
- NFA_SKWORD, // Match word char but no digit
- NFA_FNAME, // Match file name char
- NFA_SFNAME, // Match file name char but no digit
- NFA_PRINT, // Match printable char
- NFA_SPRINT, // Match printable char but no digit
- NFA_WHITE, // Match whitespace char
- NFA_NWHITE, // Match non-whitespace char
- NFA_DIGIT, // Match digit char
- NFA_NDIGIT, // Match non-digit char
- NFA_HEX, // Match hex char
- NFA_NHEX, // Match non-hex char
- NFA_OCTAL, // Match octal char
- NFA_NOCTAL, // Match non-octal char
- NFA_WORD, // Match word char
- NFA_NWORD, // Match non-word char
- NFA_HEAD, // Match head char
- NFA_NHEAD, // Match non-head char
- NFA_ALPHA, // Match alpha char
- NFA_NALPHA, // Match non-alpha char
- NFA_LOWER, // Match lowercase char
- NFA_NLOWER, // Match non-lowercase char
- NFA_UPPER, // Match uppercase char
- NFA_NUPPER, // Match non-uppercase char
- NFA_LOWER_IC, // Match [a-z]
- NFA_NLOWER_IC, // Match [^a-z]
- NFA_UPPER_IC, // Match [A-Z]
- NFA_NUPPER_IC, // Match [^A-Z]
-
- NFA_FIRST_NL = NFA_ANY + NFA_ADD_NL,
- NFA_LAST_NL = NFA_NUPPER_IC + NFA_ADD_NL,
-
- NFA_CURSOR, // Match cursor pos
- NFA_LNUM, // Match line number
- NFA_LNUM_GT, // Match > line number
- NFA_LNUM_LT, // Match < line number
- NFA_COL, // Match cursor column
- NFA_COL_GT, // Match > cursor column
- NFA_COL_LT, // Match < cursor column
- NFA_VCOL, // Match cursor virtual column
- NFA_VCOL_GT, // Match > cursor virtual column
- NFA_VCOL_LT, // Match < cursor virtual column
- NFA_MARK, // Match mark
- NFA_MARK_GT, // Match > mark
- NFA_MARK_LT, // Match < mark
- NFA_VISUAL, // Match Visual area
-
- // Character classes [:alnum:] etc
- NFA_CLASS_ALNUM,
- NFA_CLASS_ALPHA,
- NFA_CLASS_BLANK,
- NFA_CLASS_CNTRL,
- NFA_CLASS_DIGIT,
- NFA_CLASS_GRAPH,
- NFA_CLASS_LOWER,
- NFA_CLASS_PRINT,
- NFA_CLASS_PUNCT,
- NFA_CLASS_SPACE,
- NFA_CLASS_UPPER,
- NFA_CLASS_XDIGIT,
- NFA_CLASS_TAB,
- NFA_CLASS_RETURN,
- NFA_CLASS_BACKSPACE,
- NFA_CLASS_ESCAPE,
- NFA_CLASS_IDENT,
- NFA_CLASS_KEYWORD,
- NFA_CLASS_FNAME,
-};
-
-// Keep in sync with classchars.
-static int nfa_classcodes[] = {
- NFA_ANY, NFA_IDENT, NFA_SIDENT, NFA_KWORD, NFA_SKWORD,
- NFA_FNAME, NFA_SFNAME, NFA_PRINT, NFA_SPRINT,
- NFA_WHITE, NFA_NWHITE, NFA_DIGIT, NFA_NDIGIT,
- NFA_HEX, NFA_NHEX, NFA_OCTAL, NFA_NOCTAL,
- NFA_WORD, NFA_NWORD, NFA_HEAD, NFA_NHEAD,
- NFA_ALPHA, NFA_NALPHA, NFA_LOWER, NFA_NLOWER,
- NFA_UPPER, NFA_NUPPER
-};
-
-static char e_nul_found[] = N_("E865: (NFA) Regexp end encountered prematurely");
-static char e_misplaced[] = N_("E866: (NFA regexp) Misplaced %c");
-static char e_ill_char_class[] = N_("E877: (NFA regexp) Invalid character class: %" PRId64);
-static char e_value_too_large[] = N_("E951: \\% value too large");
-
-// Since the out pointers in the list are always
-// uninitialized, we use the pointers themselves
-// as storage for the Ptrlists.
-typedef union Ptrlist Ptrlist;
-union Ptrlist {
- Ptrlist *next;
- nfa_state_T *s;
-};
-
-struct Frag {
- nfa_state_T *start;
- Ptrlist *out;
-};
-typedef struct Frag Frag_T;
-
-typedef struct {
- int in_use; ///< number of subexpr with useful info
-
- // When REG_MULTI is true list.multi is used, otherwise list.line.
- union {
- struct multipos {
- linenr_T start_lnum;
- linenr_T end_lnum;
- colnr_T start_col;
- colnr_T end_col;
- } multi[NSUBEXP];
- struct linepos {
- uint8_t *start;
- uint8_t *end;
- } line[NSUBEXP];
- } list;
- colnr_T orig_start_col; // list.multi[0].start_col without \zs
-} regsub_T;
-
-typedef struct {
- regsub_T norm; // \( .. \) matches
- regsub_T synt; // \z( .. \) matches
-} regsubs_T;
-
-// nfa_pim_T stores a Postponed Invisible Match.
-typedef struct nfa_pim_S nfa_pim_T;
-struct nfa_pim_S {
- int result; // NFA_PIM_*, see below
- nfa_state_T *state; // the invisible match start state
- regsubs_T subs; // submatch info, only party used
- union {
- lpos_T pos;
- uint8_t *ptr;
- } end; // where the match must end
-};
-
-// nfa_thread_T contains execution information of a NFA state
-typedef struct {
- nfa_state_T *state;
- int count;
- nfa_pim_T pim; // if pim.result != NFA_PIM_UNUSED: postponed
- // invisible match
- regsubs_T subs; // submatch info, only party used
-} nfa_thread_T;
-
-// nfa_list_T contains the alternative NFA execution states.
-typedef struct {
- nfa_thread_T *t; ///< allocated array of states
- int n; ///< nr of states currently in "t"
- int len; ///< max nr of states in "t"
- int id; ///< ID of the list
- int has_pim; ///< true when any state has a PIM
-} nfa_list_T;
-
-// Variables only used in nfa_regcomp() and descendants.
-static int nfa_re_flags; ///< re_flags passed to nfa_regcomp().
-static int *post_start; ///< holds the postfix form of r.e.
-static int *post_end;
-static int *post_ptr;
-
-// Set when the pattern should use the NFA engine.
-// E.g. [[:upper:]] only allows 8bit characters for BT engine,
-// while NFA engine handles multibyte characters correctly.
-static bool wants_nfa;
-
-static int nstate; ///< Number of states in the NFA. Also used when executing.
-static int istate; ///< Index in the state vector, used in alloc_state()
-
-// If not NULL match must end at this position
-static save_se_T *nfa_endp = NULL;
-
-// 0 for first call to nfa_regmatch(), 1 for recursive call.
-static int nfa_ll_index = 0;
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "regexp_nfa.c.generated.h"
-#endif
-
-// Helper functions used when doing re2post() ... regatom() parsing
-#define EMIT(c) \
- do { \
- if (post_ptr >= post_end) { \
- realloc_post_list(); \
- } \
- *post_ptr++ = c; \
- } while (0)
-
-/// Initialize internal variables before NFA compilation.
-///
-/// @param re_flags @see vim_regcomp()
-static void nfa_regcomp_start(uint8_t *expr, int re_flags)
-{
- size_t postfix_size;
- size_t nstate_max;
-
- nstate = 0;
- istate = 0;
- // A reasonable estimation for maximum size
- nstate_max = (strlen((char *)expr) + 1) * 25;
-
- // Some items blow up in size, such as [A-z]. Add more space for that.
- // When it is still not enough realloc_post_list() will be used.
- nstate_max += 1000;
-
- // Size for postfix representation of expr.
- postfix_size = sizeof(int) * nstate_max;
-
- post_start = (int *)xmalloc(postfix_size);
- post_ptr = post_start;
- post_end = post_start + nstate_max;
- wants_nfa = false;
- rex.nfa_has_zend = false;
- rex.nfa_has_backref = false;
-
- // shared with BT engine
- regcomp_start(expr, re_flags);
-}
-
-// Figure out if the NFA state list starts with an anchor, must match at start
-// of the line.
-static int nfa_get_reganch(nfa_state_T *start, int depth)
-{
- nfa_state_T *p = start;
-
- if (depth > 4) {
- return 0;
- }
-
- while (p != NULL) {
- switch (p->c) {
- case NFA_BOL:
- case NFA_BOF:
- return 1; // yes!
-
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_CURSOR:
- case NFA_VISUAL:
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_NOPEN:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- p = p->out;
- break;
-
- case NFA_SPLIT:
- return nfa_get_reganch(p->out, depth + 1)
- && nfa_get_reganch(p->out1, depth + 1);
-
- default:
- return 0; // noooo
- }
- }
- return 0;
-}
-
-// Figure out if the NFA state list starts with a character which must match
-// at start of the match.
-static int nfa_get_regstart(nfa_state_T *start, int depth)
-{
- nfa_state_T *p = start;
-
- if (depth > 4) {
- return 0;
- }
-
- while (p != NULL) {
- switch (p->c) {
- // all kinds of zero-width matches
- case NFA_BOL:
- case NFA_BOF:
- case NFA_BOW:
- case NFA_EOW:
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_CURSOR:
- case NFA_VISUAL:
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_NOPEN:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- p = p->out;
- break;
-
- case NFA_SPLIT: {
- int c1 = nfa_get_regstart(p->out, depth + 1);
- int c2 = nfa_get_regstart(p->out1, depth + 1);
-
- if (c1 == c2) {
- return c1; // yes!
- }
- return 0;
- }
-
- default:
- if (p->c > 0) {
- return p->c; // yes!
- }
- return 0;
- }
- }
- return 0;
-}
-
-// Figure out if the NFA state list contains just literal text and nothing
-// else. If so return a string in allocated memory with what must match after
-// regstart. Otherwise return NULL.
-static uint8_t *nfa_get_match_text(nfa_state_T *start)
-{
- nfa_state_T *p = start;
- int len = 0;
- uint8_t *ret;
- uint8_t *s;
-
- if (p->c != NFA_MOPEN) {
- return NULL; // just in case
- }
- p = p->out;
- while (p->c > 0) {
- len += utf_char2len(p->c);
- p = p->out;
- }
- if (p->c != NFA_MCLOSE || p->out->c != NFA_MATCH) {
- return NULL;
- }
-
- ret = xmalloc((size_t)len);
- p = start->out->out; // skip first char, it goes into regstart
- s = ret;
- while (p->c > 0) {
- s += utf_char2bytes(p->c, (char *)s);
- p = p->out;
- }
- *s = NUL;
-
- return ret;
-}
-
-// Allocate more space for post_start. Called when
-// running above the estimated number of states.
-static void realloc_post_list(void)
-{
- // For weird patterns the number of states can be very high. Increasing by
- // 50% seems a reasonable compromise between memory use and speed.
- const size_t new_max = (size_t)(post_end - post_start) * 3 / 2;
- int *new_start = xrealloc(post_start, new_max * sizeof(int));
- post_ptr = new_start + (post_ptr - post_start);
- post_end = new_start + new_max;
- post_start = new_start;
-}
-
-// Search between "start" and "end" and try to recognize a
-// character class in expanded form. For example [0-9].
-// On success, return the id the character class to be emitted.
-// On failure, return 0 (=FAIL)
-// Start points to the first char of the range, while end should point
-// to the closing brace.
-// Keep in mind that 'ignorecase' applies at execution time, thus [a-z] may
-// need to be interpreted as [a-zA-Z].
-static int nfa_recognize_char_class(uint8_t *start, uint8_t *end, int extra_newl)
-{
-#define CLASS_not 0x80
-#define CLASS_af 0x40
-#define CLASS_AF 0x20
-#define CLASS_az 0x10
-#define CLASS_AZ 0x08
-#define CLASS_o7 0x04
-#define CLASS_o9 0x02
-#define CLASS_underscore 0x01
-
- uint8_t *p;
- int config = 0;
-
- bool newl = extra_newl == true;
-
- if (*end != ']') {
- return FAIL;
- }
- p = start;
- if (*p == '^') {
- config |= CLASS_not;
- p++;
- }
-
- while (p < end) {
- if (p + 2 < end && *(p + 1) == '-') {
- switch (*p) {
- case '0':
- if (*(p + 2) == '9') {
- config |= CLASS_o9;
- break;
- } else if (*(p + 2) == '7') {
- config |= CLASS_o7;
- break;
- }
- return FAIL;
- case 'a':
- if (*(p + 2) == 'z') {
- config |= CLASS_az;
- break;
- } else if (*(p + 2) == 'f') {
- config |= CLASS_af;
- break;
- }
- return FAIL;
- case 'A':
- if (*(p + 2) == 'Z') {
- config |= CLASS_AZ;
- break;
- } else if (*(p + 2) == 'F') {
- config |= CLASS_AF;
- break;
- }
- return FAIL;
- default:
- return FAIL;
- }
- p += 3;
- } else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
- newl = true;
- p += 2;
- } else if (*p == '_') {
- config |= CLASS_underscore;
- p++;
- } else if (*p == '\n') {
- newl = true;
- p++;
- } else {
- return FAIL;
- }
- } // while (p < end)
-
- if (p != end) {
- return FAIL;
- }
-
- if (newl == true) {
- extra_newl = NFA_ADD_NL;
- }
-
- switch (config) {
- case CLASS_o9:
- return extra_newl + NFA_DIGIT;
- case CLASS_not | CLASS_o9:
- return extra_newl + NFA_NDIGIT;
- case CLASS_af | CLASS_AF | CLASS_o9:
- return extra_newl + NFA_HEX;
- case CLASS_not | CLASS_af | CLASS_AF | CLASS_o9:
- return extra_newl + NFA_NHEX;
- case CLASS_o7:
- return extra_newl + NFA_OCTAL;
- case CLASS_not | CLASS_o7:
- return extra_newl + NFA_NOCTAL;
- case CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
- return extra_newl + NFA_WORD;
- case CLASS_not | CLASS_az | CLASS_AZ | CLASS_o9 | CLASS_underscore:
- return extra_newl + NFA_NWORD;
- case CLASS_az | CLASS_AZ | CLASS_underscore:
- return extra_newl + NFA_HEAD;
- case CLASS_not | CLASS_az | CLASS_AZ | CLASS_underscore:
- return extra_newl + NFA_NHEAD;
- case CLASS_az | CLASS_AZ:
- return extra_newl + NFA_ALPHA;
- case CLASS_not | CLASS_az | CLASS_AZ:
- return extra_newl + NFA_NALPHA;
- case CLASS_az:
- return extra_newl + NFA_LOWER_IC;
- case CLASS_not | CLASS_az:
- return extra_newl + NFA_NLOWER_IC;
- case CLASS_AZ:
- return extra_newl + NFA_UPPER_IC;
- case CLASS_not | CLASS_AZ:
- return extra_newl + NFA_NUPPER_IC;
- }
- return FAIL;
-}
-
-// Produce the bytes for equivalence class "c".
-// Currently only handles latin1, latin9 and utf-8.
-// Emits bytes in postfix notation: 'a,b,NFA_OR,c,NFA_OR' is
-// equivalent to 'a OR b OR c'
-//
-// NOTE! When changing this function, also update reg_equi_class()
-static void nfa_emit_equi_class(int c)
-{
-#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT);
-
- {
-#define A_grave 0xc0
-#define A_acute 0xc1
-#define A_circumflex 0xc2
-#define A_virguilla 0xc3
-#define A_diaeresis 0xc4
-#define A_ring 0xc5
-#define C_cedilla 0xc7
-#define E_grave 0xc8
-#define E_acute 0xc9
-#define E_circumflex 0xca
-#define E_diaeresis 0xcb
-#define I_grave 0xcc
-#define I_acute 0xcd
-#define I_circumflex 0xce
-#define I_diaeresis 0xcf
-#define N_virguilla 0xd1
-#define O_grave 0xd2
-#define O_acute 0xd3
-#define O_circumflex 0xd4
-#define O_virguilla 0xd5
-#define O_diaeresis 0xd6
-#define O_slash 0xd8
-#define U_grave 0xd9
-#define U_acute 0xda
-#define U_circumflex 0xdb
-#define U_diaeresis 0xdc
-#define Y_acute 0xdd
-#define a_grave 0xe0
-#define a_acute 0xe1
-#define a_circumflex 0xe2
-#define a_virguilla 0xe3
-#define a_diaeresis 0xe4
-#define a_ring 0xe5
-#define c_cedilla 0xe7
-#define e_grave 0xe8
-#define e_acute 0xe9
-#define e_circumflex 0xea
-#define e_diaeresis 0xeb
-#define i_grave 0xec
-#define i_acute 0xed
-#define i_circumflex 0xee
-#define i_diaeresis 0xef
-#define n_virguilla 0xf1
-#define o_grave 0xf2
-#define o_acute 0xf3
-#define o_circumflex 0xf4
-#define o_virguilla 0xf5
-#define o_diaeresis 0xf6
-#define o_slash 0xf8
-#define u_grave 0xf9
-#define u_acute 0xfa
-#define u_circumflex 0xfb
-#define u_diaeresis 0xfc
-#define y_acute 0xfd
-#define y_diaeresis 0xff
- switch (c) {
- case 'A':
- case A_grave:
- case A_acute:
- case A_circumflex:
- case A_virguilla:
- case A_diaeresis:
- case A_ring:
- case 0x100:
- case 0x102:
- case 0x104:
- case 0x1cd:
- case 0x1de:
- case 0x1e0:
- case 0x1fa:
- case 0x200:
- case 0x202:
- case 0x226:
- case 0x23a:
- case 0x1e00:
- case 0x1ea0:
- case 0x1ea2:
- case 0x1ea4:
- case 0x1ea6:
- case 0x1ea8:
- case 0x1eaa:
- case 0x1eac:
- case 0x1eae:
- case 0x1eb0:
- case 0x1eb2:
- case 0x1eb4:
- case 0x1eb6:
- EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) // NOLINT(whitespace/cast)
- EMIT2(A_circumflex) EMIT2(A_virguilla) // NOLINT(whitespace/cast)
- EMIT2(A_diaeresis) EMIT2(A_ring) // NOLINT(whitespace/cast)
- EMIT2(0x100) EMIT2(0x102) EMIT2(0x104)
- EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0)
- EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202)
- EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00)
- EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4)
- EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa)
- EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0)
- EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4)
- return;
-
- case 'B':
- case 0x181:
- case 0x243:
- case 0x1e02:
- case 0x1e04:
- case 0x1e06:
- EMIT2('B')
- EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02)
- EMIT2(0x1e04) EMIT2(0x1e06)
- return;
-
- case 'C':
- case C_cedilla:
- case 0x106:
- case 0x108:
- case 0x10a:
- case 0x10c:
- case 0x187:
- case 0x23b:
- case 0x1e08:
- case 0xa792:
- EMIT2('C') EMIT2(C_cedilla)
- EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a)
- EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b)
- EMIT2(0x1e08) EMIT2(0xa792)
- return;
-
- case 'D':
- case 0x10e:
- case 0x110:
- case 0x18a:
- case 0x1e0a:
- case 0x1e0c:
- case 0x1e0e:
- case 0x1e10:
- case 0x1e12:
- EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a)
- EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e)
- EMIT2(0x1e10) EMIT2(0x1e12)
- return;
-
- case 'E':
- case E_grave:
- case E_acute:
- case E_circumflex:
- case E_diaeresis:
- case 0x112:
- case 0x114:
- case 0x116:
- case 0x118:
- case 0x11a:
- case 0x204:
- case 0x206:
- case 0x228:
- case 0x246:
- case 0x1e14:
- case 0x1e16:
- case 0x1e18:
- case 0x1e1a:
- case 0x1e1c:
- case 0x1eb8:
- case 0x1eba:
- case 0x1ebc:
- case 0x1ebe:
- case 0x1ec0:
- case 0x1ec2:
- case 0x1ec4:
- case 0x1ec6:
- EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) // NOLINT(whitespace/cast)
- EMIT2(E_circumflex) EMIT2(E_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x112) EMIT2(0x114) EMIT2(0x116)
- EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204)
- EMIT2(0x206) EMIT2(0x228) EMIT2(0x246)
- EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18)
- EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8)
- EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe)
- EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4)
- EMIT2(0x1ec6)
- return;
-
- case 'F':
- case 0x191:
- case 0x1e1e:
- case 0xa798:
- EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798)
- return;
-
- case 'G':
- case 0x11c:
- case 0x11e:
- case 0x120:
- case 0x122:
- case 0x193:
- case 0x1e4:
- case 0x1e6:
- case 0x1f4:
- case 0x1e20:
- case 0xa7a0:
- EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120)
- EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4)
- EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20)
- EMIT2(0xa7a0)
- return;
-
- case 'H':
- case 0x124:
- case 0x126:
- case 0x21e:
- case 0x1e22:
- case 0x1e24:
- case 0x1e26:
- case 0x1e28:
- case 0x1e2a:
- case 0x2c67:
- EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e)
- EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26)
- EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67)
- return;
-
- case 'I':
- case I_grave:
- case I_acute:
- case I_circumflex:
- case I_diaeresis:
- case 0x128:
- case 0x12a:
- case 0x12c:
- case 0x12e:
- case 0x130:
- case 0x197:
- case 0x1cf:
- case 0x208:
- case 0x20a:
- case 0x1e2c:
- case 0x1e2e:
- case 0x1ec8:
- case 0x1eca:
- EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) // NOLINT(whitespace/cast)
- EMIT2(I_circumflex) EMIT2(I_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c)
- EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197)
- EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a)
- EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8)
- EMIT2(0x1eca)
- return;
-
- case 'J':
- case 0x134:
- case 0x248:
- EMIT2('J') EMIT2(0x134) EMIT2(0x248)
- return;
-
- case 'K':
- case 0x136:
- case 0x198:
- case 0x1e8:
- case 0x1e30:
- case 0x1e32:
- case 0x1e34:
- case 0x2c69:
- case 0xa740:
- EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8)
- EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34)
- EMIT2(0x2c69) EMIT2(0xa740)
- return;
-
- case 'L':
- case 0x139:
- case 0x13b:
- case 0x13d:
- case 0x13f:
- case 0x141:
- case 0x23d:
- case 0x1e36:
- case 0x1e38:
- case 0x1e3a:
- case 0x1e3c:
- case 0x2c60:
- EMIT2('L') EMIT2(0x139) EMIT2(0x13b)
- EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141)
- EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38)
- EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60)
- return;
-
- case 'M':
- case 0x1e3e:
- case 0x1e40:
- case 0x1e42:
- EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40)
- EMIT2(0x1e42)
- return;
-
- case 'N':
- case N_virguilla:
- case 0x143:
- case 0x145:
- case 0x147:
- case 0x1f8:
- case 0x1e44:
- case 0x1e46:
- case 0x1e48:
- case 0x1e4a:
- case 0xa7a4:
- EMIT2('N') EMIT2(N_virguilla)
- EMIT2(0x143) EMIT2(0x145) EMIT2(0x147)
- EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46)
- EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4)
- return;
-
- case 'O':
- case O_grave:
- case O_acute:
- case O_circumflex:
- case O_virguilla:
- case O_diaeresis:
- case O_slash:
- case 0x14c:
- case 0x14e:
- case 0x150:
- case 0x19f:
- case 0x1a0:
- case 0x1d1:
- case 0x1ea:
- case 0x1ec:
- case 0x1fe:
- case 0x20c:
- case 0x20e:
- case 0x22a:
- case 0x22c:
- case 0x22e:
- case 0x230:
- case 0x1e4c:
- case 0x1e4e:
- case 0x1e50:
- case 0x1e52:
- case 0x1ecc:
- case 0x1ece:
- case 0x1ed0:
- case 0x1ed2:
- case 0x1ed4:
- case 0x1ed6:
- case 0x1ed8:
- case 0x1eda:
- case 0x1edc:
- case 0x1ede:
- case 0x1ee0:
- case 0x1ee2:
- EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) // NOLINT(whitespace/cast)
- EMIT2(O_circumflex) EMIT2(O_virguilla) // NOLINT(whitespace/cast)
- EMIT2(O_diaeresis) EMIT2(O_slash) // NOLINT(whitespace/cast)
- EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150)
- EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1)
- EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe)
- EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a)
- EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230)
- EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50)
- EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece)
- EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4)
- EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda)
- EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0)
- EMIT2(0x1ee2)
- return;
-
- case 'P':
- case 0x1a4:
- case 0x1e54:
- case 0x1e56:
- case 0x2c63:
- EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56)
- EMIT2(0x2c63)
- return;
-
- case 'Q':
- case 0x24a:
- EMIT2('Q') EMIT2(0x24a)
- return;
-
- case 'R':
- case 0x154:
- case 0x156:
- case 0x158:
- case 0x210:
- case 0x212:
- case 0x24c:
- case 0x1e58:
- case 0x1e5a:
- case 0x1e5c:
- case 0x1e5e:
- case 0x2c64:
- case 0xa7a6:
- EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158)
- EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58)
- EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64)
- EMIT2(0xa7a6)
- return;
-
- case 'S':
- case 0x15a:
- case 0x15c:
- case 0x15e:
- case 0x160:
- case 0x218:
- case 0x1e60:
- case 0x1e62:
- case 0x1e64:
- case 0x1e66:
- case 0x1e68:
- case 0x2c7e:
- case 0xa7a8:
- EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e)
- EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62)
- EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e)
- EMIT2(0xa7a8)
- return;
-
- case 'T':
- case 0x162:
- case 0x164:
- case 0x166:
- case 0x1ac:
- case 0x1ae:
- case 0x21a:
- case 0x23e:
- case 0x1e6a:
- case 0x1e6c:
- case 0x1e6e:
- case 0x1e70:
- EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166)
- EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a)
- EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70)
- return;
-
- case 'U':
- case U_grave:
- case U_acute:
- case U_diaeresis:
- case U_circumflex:
- case 0x168:
- case 0x16a:
- case 0x16c:
- case 0x16e:
- case 0x170:
- case 0x172:
- case 0x1af:
- case 0x1d3:
- case 0x1d5:
- case 0x1d7:
- case 0x1d9:
- case 0x1db:
- case 0x214:
- case 0x216:
- case 0x244:
- case 0x1e72:
- case 0x1e74:
- case 0x1e76:
- case 0x1e78:
- case 0x1e7a:
- case 0x1ee4:
- case 0x1ee6:
- case 0x1ee8:
- case 0x1eea:
- case 0x1eec:
- case 0x1eee:
- case 0x1ef0:
- EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) // NOLINT(whitespace/cast)
- EMIT2(U_diaeresis) EMIT2(U_circumflex) // NOLINT(whitespace/cast)
- EMIT2(0x168) EMIT2(0x16a)
- EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170)
- EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3)
- EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9)
- EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216)
- EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74)
- EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a)
- EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8)
- EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee)
- EMIT2(0x1ef0)
- return;
-
- case 'V':
- case 0x1b2:
- case 0x1e7c:
- case 0x1e7e:
- EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e)
- return;
-
- case 'W':
- case 0x174:
- case 0x1e80:
- case 0x1e82:
- case 0x1e84:
- case 0x1e86:
- case 0x1e88:
- EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82)
- EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88)
- return;
-
- case 'X':
- case 0x1e8a:
- case 0x1e8c:
- EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c)
- return;
-
- case 'Y':
- case Y_acute:
- case 0x176:
- case 0x178:
- case 0x1b3:
- case 0x232:
- case 0x24e:
- case 0x1e8e:
- case 0x1ef2:
- case 0x1ef4:
- case 0x1ef6:
- case 0x1ef8:
- EMIT2('Y') EMIT2(Y_acute)
- EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3)
- EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e)
- EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6)
- EMIT2(0x1ef8)
- return;
-
- case 'Z':
- case 0x179:
- case 0x17b:
- case 0x17d:
- case 0x1b5:
- case 0x1e90:
- case 0x1e92:
- case 0x1e94:
- case 0x2c6b:
- EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d)
- EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92)
- EMIT2(0x1e94) EMIT2(0x2c6b)
- return;
-
- case 'a':
- case a_grave:
- case a_acute:
- case a_circumflex:
- case a_virguilla:
- case a_diaeresis:
- case a_ring:
- case 0x101:
- case 0x103:
- case 0x105:
- case 0x1ce:
- case 0x1df:
- case 0x1e1:
- case 0x1fb:
- case 0x201:
- case 0x203:
- case 0x227:
- case 0x1d8f:
- case 0x1e01:
- case 0x1e9a:
- case 0x1ea1:
- case 0x1ea3:
- case 0x1ea5:
- case 0x1ea7:
- case 0x1ea9:
- case 0x1eab:
- case 0x1ead:
- case 0x1eaf:
- case 0x1eb1:
- case 0x1eb3:
- case 0x1eb5:
- case 0x1eb7:
- case 0x2c65:
- EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) // NOLINT(whitespace/cast)
- EMIT2(a_circumflex) EMIT2(a_virguilla) // NOLINT(whitespace/cast)
- EMIT2(a_diaeresis) EMIT2(a_ring) // NOLINT(whitespace/cast)
- EMIT2(0x101) EMIT2(0x103) EMIT2(0x105)
- EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1)
- EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203)
- EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01)
- EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3)
- EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9)
- EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf)
- EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5)
- EMIT2(0x1eb7) EMIT2(0x2c65)
- return;
-
- case 'b':
- case 0x180:
- case 0x253:
- case 0x1d6c:
- case 0x1d80:
- case 0x1e03:
- case 0x1e05:
- case 0x1e07:
- EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c)
- EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07)
- return;
-
- case 'c':
- case c_cedilla:
- case 0x107:
- case 0x109:
- case 0x10b:
- case 0x10d:
- case 0x188:
- case 0x23c:
- case 0x1e09:
- case 0xa793:
- case 0xa794:
- EMIT2('c') EMIT2(c_cedilla)
- EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b)
- EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c)
- EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794)
- return;
-
- case 'd':
- case 0x10f:
- case 0x111:
- case 0x257:
- case 0x1d6d:
- case 0x1d81:
- case 0x1d91:
- case 0x1e0b:
- case 0x1e0d:
- case 0x1e0f:
- case 0x1e11:
- case 0x1e13:
- EMIT2('d') EMIT2(0x10f) EMIT2(0x111)
- EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81)
- EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d)
- EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13)
- return;
-
- case 'e':
- case e_grave:
- case e_acute:
- case e_circumflex:
- case e_diaeresis:
- case 0x113:
- case 0x115:
- case 0x117:
- case 0x119:
- case 0x11b:
- case 0x205:
- case 0x207:
- case 0x229:
- case 0x247:
- case 0x1d92:
- case 0x1e15:
- case 0x1e17:
- case 0x1e19:
- case 0x1e1b:
- case 0x1e1d:
- case 0x1eb9:
- case 0x1ebb:
- case 0x1ebd:
- case 0x1ebf:
- case 0x1ec1:
- case 0x1ec3:
- case 0x1ec5:
- case 0x1ec7:
- EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) // NOLINT(whitespace/cast)
- EMIT2(e_circumflex) EMIT2(e_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x113) EMIT2(0x115)
- EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b)
- EMIT2(0x205) EMIT2(0x207) EMIT2(0x229)
- EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15)
- EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b)
- EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb)
- EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1)
- EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7)
- return;
-
- case 'f':
- case 0x192:
- case 0x1d6e:
- case 0x1d82:
- case 0x1e1f:
- case 0xa799:
- EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82)
- EMIT2(0x1e1f) EMIT2(0xa799)
- return;
-
- case 'g':
- case 0x11d:
- case 0x11f:
- case 0x121:
- case 0x123:
- case 0x1e5:
- case 0x1e7:
- case 0x1f5:
- case 0x260:
- case 0x1d83:
- case 0x1e21:
- case 0xa7a1:
- EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121)
- EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7)
- EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83)
- EMIT2(0x1e21) EMIT2(0xa7a1)
- return;
-
- case 'h':
- case 0x125:
- case 0x127:
- case 0x21f:
- case 0x1e23:
- case 0x1e25:
- case 0x1e27:
- case 0x1e29:
- case 0x1e2b:
- case 0x1e96:
- case 0x2c68:
- case 0xa795:
- EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f)
- EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27)
- EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96)
- EMIT2(0x2c68) EMIT2(0xa795)
- return;
-
- case 'i':
- case i_grave:
- case i_acute:
- case i_circumflex:
- case i_diaeresis:
- case 0x129:
- case 0x12b:
- case 0x12d:
- case 0x12f:
- case 0x1d0:
- case 0x209:
- case 0x20b:
- case 0x268:
- case 0x1d96:
- case 0x1e2d:
- case 0x1e2f:
- case 0x1ec9:
- case 0x1ecb:
- EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) // NOLINT(whitespace/cast)
- EMIT2(i_circumflex) EMIT2(i_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d)
- EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209)
- EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96)
- EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9)
- EMIT2(0x1ecb) EMIT2(0x1ecb)
- return;
-
- case 'j':
- case 0x135:
- case 0x1f0:
- case 0x249:
- EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249)
- return;
-
- case 'k':
- case 0x137:
- case 0x199:
- case 0x1e9:
- case 0x1d84:
- case 0x1e31:
- case 0x1e33:
- case 0x1e35:
- case 0x2c6a:
- case 0xa741:
- EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9)
- EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33)
- EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741)
- return;
-
- case 'l':
- case 0x13a:
- case 0x13c:
- case 0x13e:
- case 0x140:
- case 0x142:
- case 0x19a:
- case 0x1e37:
- case 0x1e39:
- case 0x1e3b:
- case 0x1e3d:
- case 0x2c61:
- EMIT2('l') EMIT2(0x13a) EMIT2(0x13c)
- EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142)
- EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39)
- EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61)
- return;
-
- case 'm':
- case 0x1d6f:
- case 0x1e3f:
- case 0x1e41:
- case 0x1e43:
- EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f)
- EMIT2(0x1e41) EMIT2(0x1e43)
- return;
-
- case 'n':
- case n_virguilla:
- case 0x144:
- case 0x146:
- case 0x148:
- case 0x149:
- case 0x1f9:
- case 0x1d70:
- case 0x1d87:
- case 0x1e45:
- case 0x1e47:
- case 0x1e49:
- case 0x1e4b:
- case 0xa7a5:
- EMIT2('n') EMIT2(n_virguilla)
- EMIT2(0x144) EMIT2(0x146) EMIT2(0x148)
- EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70)
- EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47)
- EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5)
- return;
-
- case 'o':
- case o_grave:
- case o_acute:
- case o_circumflex:
- case o_virguilla:
- case o_diaeresis:
- case o_slash:
- case 0x14d:
- case 0x14f:
- case 0x151:
- case 0x1a1:
- case 0x1d2:
- case 0x1eb:
- case 0x1ed:
- case 0x1ff:
- case 0x20d:
- case 0x20f:
- case 0x22b:
- case 0x22d:
- case 0x22f:
- case 0x231:
- case 0x275:
- case 0x1e4d:
- case 0x1e4f:
- case 0x1e51:
- case 0x1e53:
- case 0x1ecd:
- case 0x1ecf:
- case 0x1ed1:
- case 0x1ed3:
- case 0x1ed5:
- case 0x1ed7:
- case 0x1ed9:
- case 0x1edb:
- case 0x1edd:
- case 0x1edf:
- case 0x1ee1:
- case 0x1ee3:
- EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) // NOLINT(whitespace/cast)
- EMIT2(o_circumflex) EMIT2(o_virguilla) // NOLINT(whitespace/cast)
- EMIT2(o_diaeresis) EMIT2(o_slash) // NOLINT(whitespace/cast)
- EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151)
- EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb)
- EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d)
- EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d)
- EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275)
- EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51)
- EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf)
- EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5)
- EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb)
- EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1)
- EMIT2(0x1ee3)
- return;
-
- case 'p':
- case 0x1a5:
- case 0x1d71:
- case 0x1d7d:
- case 0x1d88:
- case 0x1e55:
- case 0x1e57:
- EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d)
- EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57)
- return;
-
- case 'q':
- case 0x24b:
- case 0x2a0:
- EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0)
- return;
-
- case 'r':
- case 0x155:
- case 0x157:
- case 0x159:
- case 0x211:
- case 0x213:
- case 0x24d:
- case 0x27d:
- case 0x1d72:
- case 0x1d73:
- case 0x1d89:
- case 0x1e59:
- case 0x1e5b:
- case 0x1e5d:
- case 0x1e5f:
- case 0xa7a7:
- EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159)
- EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d)
- EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59)
- EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7)
- return;
-
- case 's':
- case 0x15b:
- case 0x15d:
- case 0x15f:
- case 0x161:
- case 0x219:
- case 0x23f:
- case 0x1d74:
- case 0x1d8a:
- case 0x1e61:
- case 0x1e63:
- case 0x1e65:
- case 0x1e67:
- case 0x1e69:
- case 0xa7a9:
- EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f)
- EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74)
- EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65)
- EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9)
- return;
-
- case 't':
- case 0x163:
- case 0x165:
- case 0x167:
- case 0x1ab:
- case 0x1ad:
- case 0x21b:
- case 0x288:
- case 0x1d75:
- case 0x1e6b:
- case 0x1e6d:
- case 0x1e6f:
- case 0x1e71:
- case 0x1e97:
- case 0x2c66:
- EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167)
- EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288)
- EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f)
- EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66)
- return;
-
- case 'u':
- case u_grave:
- case u_acute:
- case u_circumflex:
- case u_diaeresis:
- case 0x169:
- case 0x16b:
- case 0x16d:
- case 0x16f:
- case 0x171:
- case 0x173:
- case 0x1b0:
- case 0x1d4:
- case 0x1d6:
- case 0x1d8:
- case 0x1da:
- case 0x1dc:
- case 0x215:
- case 0x217:
- case 0x289:
- case 0x1d7e:
- case 0x1d99:
- case 0x1e73:
- case 0x1e75:
- case 0x1e77:
- case 0x1e79:
- case 0x1e7b:
- case 0x1ee5:
- case 0x1ee7:
- case 0x1ee9:
- case 0x1eeb:
- case 0x1eed:
- case 0x1eef:
- case 0x1ef1:
- EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) // NOLINT(whitespace/cast)
- EMIT2(u_circumflex) EMIT2(u_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x169) EMIT2(0x16b)
- EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171)
- EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8)
- EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0)
- EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc)
- EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e)
- EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77)
- EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5)
- EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb)
- EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1)
- return;
-
- case 'v':
- case 0x28b:
- case 0x1d8c:
- case 0x1e7d:
- case 0x1e7f:
- EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d)
- EMIT2(0x1e7f)
- return;
-
- case 'w':
- case 0x175:
- case 0x1e81:
- case 0x1e83:
- case 0x1e85:
- case 0x1e87:
- case 0x1e89:
- case 0x1e98:
- EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83)
- EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98)
- return;
-
- case 'x':
- case 0x1e8b:
- case 0x1e8d:
- EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d)
- return;
-
- case 'y':
- case y_acute:
- case y_diaeresis:
- case 0x177:
- case 0x1b4:
- case 0x233:
- case 0x24f:
- case 0x1e8f:
- case 0x1e99:
- case 0x1ef3:
- case 0x1ef5:
- case 0x1ef7:
- case 0x1ef9:
- EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) // NOLINT(whitespace/cast)
- EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f)
- EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3)
- EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9)
- return;
-
- case 'z':
- case 0x17a:
- case 0x17c:
- case 0x17e:
- case 0x1b6:
- case 0x1d76:
- case 0x1d8e:
- case 0x1e91:
- case 0x1e93:
- case 0x1e95:
- case 0x2c6c:
- EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e)
- EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91)
- EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c)
- return;
-
- // default: character itself
- }
- }
-
- EMIT2(c);
-#undef EMIT2
-}
-
-// Code to parse regular expression.
-//
-// We try to reuse parsing functions in regexp.c to
-// minimize surprise and keep the syntax consistent.
-
-// Parse the lowest level.
-//
-// An atom can be one of a long list of items. Many atoms match one character
-// in the text. It is often an ordinary character or a character class.
-// Braces can be used to make a pattern into an atom. The "\z(\)" construct
-// is only for syntax highlighting.
-//
-// atom ::= ordinary-atom
-// or \( pattern \)
-// or \%( pattern \)
-// or \z( pattern \)
-static int nfa_regatom(void)
-{
- int c;
- int charclass;
- int equiclass;
- int collclass;
- int got_coll_char;
- uint8_t *p;
- uint8_t *endp;
- uint8_t *old_regparse = (uint8_t *)regparse;
- int extra = 0;
- int emit_range;
- int negated;
- int startc = -1;
- int save_prev_at_start = prev_at_start;
-
- c = getchr();
- switch (c) {
- case NUL:
- EMSG_RET_FAIL(_(e_nul_found));
-
- case Magic('^'):
- EMIT(NFA_BOL);
- break;
-
- case Magic('$'):
- EMIT(NFA_EOL);
- had_eol = true;
- break;
-
- case Magic('<'):
- EMIT(NFA_BOW);
- break;
-
- case Magic('>'):
- EMIT(NFA_EOW);
- break;
-
- case Magic('_'):
- c = no_Magic(getchr());
- if (c == NUL) {
- EMSG_RET_FAIL(_(e_nul_found));
- }
-
- if (c == '^') { // "\_^" is start-of-line
- EMIT(NFA_BOL);
- break;
- }
- if (c == '$') { // "\_$" is end-of-line
- EMIT(NFA_EOL);
- had_eol = true;
- break;
- }
-
- extra = NFA_ADD_NL;
-
- // "\_[" is collection plus newline
- if (c == '[') {
- goto collection;
- }
-
- // "\_x" is character class plus newline
- FALLTHROUGH;
-
- // Character classes.
- case Magic('.'):
- case Magic('i'):
- case Magic('I'):
- case Magic('k'):
- case Magic('K'):
- case Magic('f'):
- case Magic('F'):
- case Magic('p'):
- case Magic('P'):
- case Magic('s'):
- case Magic('S'):
- case Magic('d'):
- case Magic('D'):
- case Magic('x'):
- case Magic('X'):
- case Magic('o'):
- case Magic('O'):
- case Magic('w'):
- case Magic('W'):
- case Magic('h'):
- case Magic('H'):
- case Magic('a'):
- case Magic('A'):
- case Magic('l'):
- case Magic('L'):
- case Magic('u'):
- case Magic('U'):
- p = (uint8_t *)vim_strchr((char *)classchars, no_Magic(c));
- if (p == NULL) {
- if (extra == NFA_ADD_NL) {
- semsg(_(e_ill_char_class), (int64_t)c);
- rc_did_emsg = true;
- return FAIL;
- }
- siemsg("INTERNAL: Unknown character class char: %" PRId64, (int64_t)c);
- return FAIL;
- }
- // When '.' is followed by a composing char ignore the dot, so that
- // the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
- old_regparse = (uint8_t *)regparse;
- c = getchr();
- goto nfa_do_multibyte;
- }
- EMIT(nfa_classcodes[p - classchars]);
- if (extra == NFA_ADD_NL) {
- EMIT(NFA_NEWL);
- EMIT(NFA_OR);
- regflags |= RF_HASNL;
- }
- break;
-
- case Magic('n'):
- if (reg_string) {
- // In a string "\n" matches a newline character.
- EMIT(NL);
- } else {
- // In buffer text "\n" matches the end of a line.
- EMIT(NFA_NEWL);
- regflags |= RF_HASNL;
- }
- break;
-
- case Magic('('):
- if (nfa_reg(REG_PAREN) == FAIL) {
- return FAIL; // cascaded error
- }
- break;
-
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- semsg(_(e_misplaced), (int64_t)no_Magic(c)); // -V1037
- return FAIL;
-
- case Magic('='):
- case Magic('?'):
- case Magic('+'):
- case Magic('@'):
- case Magic('*'):
- case Magic('{'):
- // these should follow an atom, not form an atom
- semsg(_(e_misplaced), (int64_t)no_Magic(c));
- return FAIL;
-
- case Magic('~'): {
- uint8_t *lp;
-
- // Previous substitute pattern.
- // Generated as "\%(pattern\)".
- if (reg_prev_sub == NULL) {
- emsg(_(e_nopresub));
- return FAIL;
- }
- for (lp = (uint8_t *)reg_prev_sub; *lp != NUL; MB_CPTR_ADV(lp)) {
- EMIT(utf_ptr2char((char *)lp));
- if (lp != (uint8_t *)reg_prev_sub) {
- EMIT(NFA_CONCAT);
- }
- }
- EMIT(NFA_NOPEN);
- break;
- }
-
- case Magic('1'):
- case Magic('2'):
- case Magic('3'):
- case Magic('4'):
- case Magic('5'):
- case Magic('6'):
- case Magic('7'):
- case Magic('8'):
- case Magic('9'): {
- int refnum = no_Magic(c) - '1';
-
- if (!seen_endbrace(refnum + 1)) {
- return FAIL;
- }
- EMIT(NFA_BACKREF1 + refnum);
- rex.nfa_has_backref = true;
- }
- break;
-
- case Magic('z'):
- c = no_Magic(getchr());
- switch (c) {
- case 's':
- EMIT(NFA_ZSTART);
- if (!re_mult_next("\\zs")) {
- return false;
- }
- break;
- case 'e':
- EMIT(NFA_ZEND);
- rex.nfa_has_zend = true;
- if (!re_mult_next("\\zs")) {
- return false;
- }
- break;
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- // \z1...\z9
- if ((reg_do_extmatch & REX_USE) == 0) {
- EMSG_RET_FAIL(_(e_z1_not_allowed));
- }
- EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
- // No need to set rex.nfa_has_backref, the sub-matches don't
- // change when \z1 .. \z9 matches or not.
- re_has_z = REX_USE;
- break;
- case '(':
- // \z(
- if (reg_do_extmatch != REX_SET) {
- EMSG_RET_FAIL(_(e_z_not_allowed));
- }
- if (nfa_reg(REG_ZPAREN) == FAIL) {
- return FAIL; // cascaded error
- }
- re_has_z = REX_SET;
- break;
- default:
- semsg(_("E867: (NFA) Unknown operator '\\z%c'"),
- no_Magic(c));
- return FAIL;
- }
- break;
-
- case Magic('%'):
- c = no_Magic(getchr());
- switch (c) {
- // () without a back reference
- case '(':
- if (nfa_reg(REG_NPAREN) == FAIL) {
- return FAIL;
- }
- EMIT(NFA_NOPEN);
- break;
-
- case 'd': // %d123 decimal
- case 'o': // %o123 octal
- case 'x': // %xab hex 2
- case 'u': // %uabcd hex 4
- case 'U': // %U1234abcd hex 8
- {
- int64_t nr;
-
- switch (c) {
- case 'd':
- nr = getdecchrs(); break;
- case 'o':
- nr = getoctchrs(); break;
- case 'x':
- nr = gethexchrs(2); break;
- case 'u':
- nr = gethexchrs(4); break;
- case 'U':
- nr = gethexchrs(8); break;
- default:
- nr = -1; break;
- }
-
- if (nr < 0 || nr > INT_MAX) {
- EMSG2_RET_FAIL(_("E678: Invalid character after %s%%[dxouU]"),
- reg_magic == MAGIC_ALL);
- }
- // A NUL is stored in the text as NL
- // TODO(vim): what if a composing character follows?
- EMIT(nr == 0 ? 0x0a : (int)nr);
- }
- break;
-
- // Catch \%^ and \%$ regardless of where they appear in the
- // pattern -- regardless of whether or not it makes sense.
- case '^':
- EMIT(NFA_BOF);
- break;
-
- case '$':
- EMIT(NFA_EOF);
- break;
-
- case '#':
- if (regparse[0] == '=' && regparse[1] >= 48
- && regparse[1] <= 50) {
- // misplaced \%#=1
- semsg(_(e_atom_engine_must_be_at_start_of_pattern), regparse[1]);
- return FAIL;
- }
- EMIT(NFA_CURSOR);
- break;
-
- case 'V':
- EMIT(NFA_VISUAL);
- break;
-
- case 'C':
- EMIT(NFA_ANY_COMPOSING);
- break;
-
- case '[': {
- int n;
-
- // \%[abc]
- for (n = 0; (c = peekchr()) != ']'; n++) {
- if (c == NUL) {
- EMSG2_RET_FAIL(_(e_missing_sb),
- reg_magic == MAGIC_ALL);
- }
- // recursive call!
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- }
- (void)getchr(); // get the ]
- if (n == 0) {
- EMSG2_RET_FAIL(_(e_empty_sb), reg_magic == MAGIC_ALL);
- }
- EMIT(NFA_OPT_CHARS);
- EMIT(n);
-
- // Emit as "\%(\%[abc]\)" to be able to handle
- // "\%[abc]*" which would cause the empty string to be
- // matched an unlimited number of times. NFA_NOPEN is
- // added only once at a position, while NFA_SPLIT is
- // added multiple times. This is more efficient than
- // not allowing NFA_SPLIT multiple times, it is used
- // a lot.
- EMIT(NFA_NOPEN);
- break;
- }
-
- default: {
- int64_t n = 0;
- const int cmp = c;
- bool cur = false;
- bool got_digit = false;
-
- if (c == '<' || c == '>') {
- c = getchr();
- }
- if (no_Magic(c) == '.') {
- cur = true;
- c = getchr();
- }
- while (ascii_isdigit(c)) {
- if (cur) {
- semsg(_(e_regexp_number_after_dot_pos_search_chr), no_Magic(c));
- return FAIL;
- }
- if (n > (INT32_MAX - (c - '0')) / 10) {
- // overflow.
- emsg(_(e_value_too_large));
- return FAIL;
- }
- n = n * 10 + (c - '0');
- c = getchr();
- got_digit = true;
- }
- if (c == 'l' || c == 'c' || c == 'v') {
- int32_t limit = INT32_MAX;
-
- if (!cur && !got_digit) {
- semsg(_(e_nfa_regexp_missing_value_in_chr), no_Magic(c));
- return FAIL;
- }
- if (c == 'l') {
- if (cur) {
- n = curwin->w_cursor.lnum;
- }
- // \%{n}l \%{n}<l \%{n}>l
- EMIT(cmp == '<' ? NFA_LNUM_LT :
- cmp == '>' ? NFA_LNUM_GT : NFA_LNUM);
- if (save_prev_at_start) {
- at_start = true;
- }
- } else if (c == 'c') {
- if (cur) {
- n = curwin->w_cursor.col;
- n++;
- }
- // \%{n}c \%{n}<c \%{n}>c
- EMIT(cmp == '<' ? NFA_COL_LT :
- cmp == '>' ? NFA_COL_GT : NFA_COL);
- } else {
- if (cur) {
- colnr_T vcol = 0;
- getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
- n = ++vcol;
- }
- // \%{n}v \%{n}<v \%{n}>v
- EMIT(cmp == '<' ? NFA_VCOL_LT :
- cmp == '>' ? NFA_VCOL_GT : NFA_VCOL);
- limit = INT32_MAX / MB_MAXBYTES;
- }
- if (n >= limit) {
- emsg(_(e_value_too_large));
- return FAIL;
- }
- EMIT((int)n);
- break;
- } else if (c == '\'' && n == 0) {
- // \%'m \%<'m \%>'m
- EMIT(cmp == '<' ? NFA_MARK_LT :
- cmp == '>' ? NFA_MARK_GT : NFA_MARK);
- EMIT(getchr());
- break;
- }
- }
- semsg(_("E867: (NFA) Unknown operator '\\%%%c'"),
- no_Magic(c));
- return FAIL;
- }
- break;
-
- case Magic('['):
-collection:
- // [abc] uses NFA_START_COLL - NFA_END_COLL
- // [^abc] uses NFA_START_NEG_COLL - NFA_END_NEG_COLL
- // Each character is produced as a regular state, using
- // NFA_CONCAT to bind them together.
- // Besides normal characters there can be:
- // - character classes NFA_CLASS_*
- // - ranges, two characters followed by NFA_RANGE.
-
- p = (uint8_t *)regparse;
- endp = (uint8_t *)skip_anyof((char *)p);
- if (*endp == ']') {
- // Try to reverse engineer character classes. For example,
- // recognize that [0-9] stands for \d and [A-Za-z_] for \h,
- // and perform the necessary substitutions in the NFA.
- int result = nfa_recognize_char_class((uint8_t *)regparse, endp, extra == NFA_ADD_NL);
- if (result != FAIL) {
- if (result >= NFA_FIRST_NL && result <= NFA_LAST_NL) {
- EMIT(result - NFA_ADD_NL);
- EMIT(NFA_NEWL);
- EMIT(NFA_OR);
- } else {
- EMIT(result);
- }
- regparse = (char *)endp;
- MB_PTR_ADV(regparse);
- return OK;
- }
- // Failed to recognize a character class. Use the simple
- // version that turns [abc] into 'a' OR 'b' OR 'c'
- startc = -1;
- negated = false;
- if (*regparse == '^') { // negated range
- negated = true;
- MB_PTR_ADV(regparse);
- EMIT(NFA_START_NEG_COLL);
- } else {
- EMIT(NFA_START_COLL);
- }
- if (*regparse == '-') {
- startc = '-';
- EMIT(startc);
- EMIT(NFA_CONCAT);
- MB_PTR_ADV(regparse);
- }
- // Emit the OR branches for each character in the []
- emit_range = false;
- while ((uint8_t *)regparse < endp) {
- int oldstartc = startc;
- startc = -1;
- got_coll_char = false;
- if (*regparse == '[') {
- // Check for [: :], [= =], [. .]
- equiclass = collclass = 0;
- charclass = get_char_class(&regparse);
- if (charclass == CLASS_NONE) {
- equiclass = get_equi_class(&regparse);
- if (equiclass == 0) {
- collclass = get_coll_element(&regparse);
- }
- }
-
- // Character class like [:alpha:]
- if (charclass != CLASS_NONE) {
- switch (charclass) {
- case CLASS_ALNUM:
- EMIT(NFA_CLASS_ALNUM);
- break;
- case CLASS_ALPHA:
- EMIT(NFA_CLASS_ALPHA);
- break;
- case CLASS_BLANK:
- EMIT(NFA_CLASS_BLANK);
- break;
- case CLASS_CNTRL:
- EMIT(NFA_CLASS_CNTRL);
- break;
- case CLASS_DIGIT:
- EMIT(NFA_CLASS_DIGIT);
- break;
- case CLASS_GRAPH:
- EMIT(NFA_CLASS_GRAPH);
- break;
- case CLASS_LOWER:
- wants_nfa = true;
- EMIT(NFA_CLASS_LOWER);
- break;
- case CLASS_PRINT:
- EMIT(NFA_CLASS_PRINT);
- break;
- case CLASS_PUNCT:
- EMIT(NFA_CLASS_PUNCT);
- break;
- case CLASS_SPACE:
- EMIT(NFA_CLASS_SPACE);
- break;
- case CLASS_UPPER:
- wants_nfa = true;
- EMIT(NFA_CLASS_UPPER);
- break;
- case CLASS_XDIGIT:
- EMIT(NFA_CLASS_XDIGIT);
- break;
- case CLASS_TAB:
- EMIT(NFA_CLASS_TAB);
- break;
- case CLASS_RETURN:
- EMIT(NFA_CLASS_RETURN);
- break;
- case CLASS_BACKSPACE:
- EMIT(NFA_CLASS_BACKSPACE);
- break;
- case CLASS_ESCAPE:
- EMIT(NFA_CLASS_ESCAPE);
- break;
- case CLASS_IDENT:
- EMIT(NFA_CLASS_IDENT);
- break;
- case CLASS_KEYWORD:
- EMIT(NFA_CLASS_KEYWORD);
- break;
- case CLASS_FNAME:
- EMIT(NFA_CLASS_FNAME);
- break;
- }
- EMIT(NFA_CONCAT);
- continue;
- }
- // Try equivalence class [=a=] and the like
- if (equiclass != 0) {
- nfa_emit_equi_class(equiclass);
- continue;
- }
- // Try collating class like [. .]
- if (collclass != 0) {
- startc = collclass; // allow [.a.]-x as a range
- // Will emit the proper atom at the end of the
- // while loop.
- }
- }
- // Try a range like 'a-x' or '\t-z'. Also allows '-' as a
- // start character.
- if (*regparse == '-' && oldstartc != -1) {
- emit_range = true;
- startc = oldstartc;
- MB_PTR_ADV(regparse);
- continue; // reading the end of the range
- }
-
- // Now handle simple and escaped characters.
- // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim
- // accepts "\t", "\e", etc., but only when the 'l' flag in
- // 'cpoptions' is not included.
- if (*regparse == '\\'
- && (uint8_t *)regparse + 1 <= endp
- && (vim_strchr(REGEXP_INRANGE, (uint8_t)regparse[1]) != NULL
- || (!reg_cpo_lit
- && vim_strchr(REGEXP_ABBR, (uint8_t)regparse[1])
- != NULL))) {
- MB_PTR_ADV(regparse);
-
- if (*regparse == 'n') {
- startc = (reg_string || emit_range || regparse[1] == '-')
- ? NL : NFA_NEWL;
- } else if (*regparse == 'd'
- || *regparse == 'o'
- || *regparse == 'x'
- || *regparse == 'u'
- || *regparse == 'U') {
- // TODO(RE): This needs more testing
- startc = coll_get_char();
- got_coll_char = true;
- MB_PTR_BACK(old_regparse, regparse);
- } else {
- // \r,\t,\e,\b
- startc = backslash_trans(*regparse);
- }
- }
-
- // Normal printable char
- if (startc == -1) {
- startc = utf_ptr2char((char *)regparse);
- }
-
- // Previous char was '-', so this char is end of range.
- if (emit_range) {
- int endc = startc;
- startc = oldstartc;
- if (startc > endc) {
- EMSG_RET_FAIL(_(e_reverse_range));
- }
-
- if (endc > startc + 2) {
- // Emit a range instead of the sequence of
- // individual characters.
- if (startc == 0) {
- // \x00 is translated to \x0a, start at \x01.
- EMIT(1);
- } else {
- post_ptr--; // remove NFA_CONCAT
- }
- EMIT(endc);
- EMIT(NFA_RANGE);
- EMIT(NFA_CONCAT);
- } else if (utf_char2len(startc) > 1
- || utf_char2len(endc) > 1) {
- // Emit the characters in the range.
- // "startc" was already emitted, so skip it.
- for (c = startc + 1; c <= endc; c++) {
- EMIT(c);
- EMIT(NFA_CONCAT);
- }
- } else {
- // Emit the range. "startc" was already emitted, so
- // skip it.
- for (c = startc + 1; c <= endc; c++) {
- EMIT(c);
- EMIT(NFA_CONCAT);
- }
- }
- emit_range = false;
- startc = -1;
- } else {
- // This char (startc) is not part of a range. Just
- // emit it.
- // Normally, simply emit startc. But if we get char
- // code=0 from a collating char, then replace it with
- // 0x0a.
- // This is needed to completely mimic the behaviour of
- // the backtracking engine.
- if (startc == NFA_NEWL) {
- // Line break can't be matched as part of the
- // collection, add an OR below. But not for negated
- // range.
- if (!negated) {
- extra = NFA_ADD_NL;
- }
- } else {
- if (got_coll_char == true && startc == 0) {
- EMIT(0x0a);
- } else {
- EMIT(startc);
- }
- EMIT(NFA_CONCAT);
- }
- }
-
- MB_PTR_ADV(regparse);
- } // while (p < endp)
-
- MB_PTR_BACK(old_regparse, regparse);
- if (*regparse == '-') { // if last, '-' is just a char
- EMIT('-');
- EMIT(NFA_CONCAT);
- }
-
- // skip the trailing ]
- regparse = (char *)endp;
- MB_PTR_ADV(regparse);
-
- // Mark end of the collection.
- if (negated == true) {
- EMIT(NFA_END_NEG_COLL);
- } else {
- EMIT(NFA_END_COLL);
- }
-
- // \_[] also matches \n but it's not negated
- if (extra == NFA_ADD_NL) {
- EMIT(reg_string ? NL : NFA_NEWL);
- EMIT(NFA_OR);
- }
-
- return OK;
- } // if exists closing ]
-
- if (reg_strict) {
- EMSG_RET_FAIL(_(e_missingbracket));
- }
- FALLTHROUGH;
-
- default: {
- int plen;
-
-nfa_do_multibyte:
- // plen is length of current char with composing chars
- if (utf_char2len(c) != (plen = utfc_ptr2len((char *)old_regparse))
- || utf_iscomposing(c)) {
- int i = 0;
-
- // A base character plus composing characters, or just one
- // or more composing characters.
- // This requires creating a separate atom as if enclosing
- // the characters in (), where NFA_COMPOSING is the ( and
- // NFA_END_COMPOSING is the ). Note that right now we are
- // building the postfix form, not the NFA itself;
- // a composing char could be: a, b, c, NFA_COMPOSING
- // where 'b' and 'c' are chars with codes > 256. */
- for (;;) {
- EMIT(c);
- if (i > 0) {
- EMIT(NFA_CONCAT);
- }
- if ((i += utf_char2len(c)) >= plen) {
- break;
- }
- c = utf_ptr2char((char *)old_regparse + i);
- }
- EMIT(NFA_COMPOSING);
- regparse = (char *)old_regparse + plen;
- } else {
- c = no_Magic(c);
- EMIT(c);
- }
- return OK;
- }
- }
-
- return OK;
-}
-
-// Parse something followed by possible [*+=].
-//
-// A piece is an atom, possibly followed by a multi, an indication of how many
-// times the atom can be matched. Example: "a*" matches any sequence of "a"
-// characters: "", "a", "aa", etc.
-//
-// piece ::= atom
-// or atom multi
-static int nfa_regpiece(void)
-{
- int i;
- int op;
- int ret;
- long minval, maxval;
- bool greedy = true; // Braces are prefixed with '-' ?
- parse_state_T old_state;
- parse_state_T new_state;
- int64_t c2;
- int old_post_pos;
- int my_post_start;
- int quest;
-
- // Save the current parse state, so that we can use it if <atom>{m,n} is
- // next.
- save_parse_state(&old_state);
-
- // store current pos in the postfix form, for \{m,n} involving 0s
- my_post_start = (int)(post_ptr - post_start);
-
- ret = nfa_regatom();
- if (ret == FAIL) {
- return FAIL; // cascaded error
- }
- op = peekchr();
- if (re_multi_type(op) == NOT_MULTI) {
- return OK;
- }
-
- skipchr();
- switch (op) {
- case Magic('*'):
- EMIT(NFA_STAR);
- break;
-
- case Magic('+'):
- // Trick: Normally, (a*)\+ would match the whole input "aaa". The
- // first and only submatch would be "aaa". But the backtracking
- // engine interprets the plus as "try matching one more time", and
- // a* matches a second time at the end of the input, the empty
- // string.
- // The submatch will be the empty string.
- //
- // In order to be consistent with the old engine, we replace
- // <atom>+ with <atom><atom>*
- restore_parse_state(&old_state);
- curchr = -1;
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- EMIT(NFA_STAR);
- EMIT(NFA_CONCAT);
- skipchr(); // skip the \+
- break;
-
- case Magic('@'):
- c2 = getdecchrs();
- op = no_Magic(getchr());
- i = 0;
- switch (op) {
- case '=':
- // \@=
- i = NFA_PREV_ATOM_NO_WIDTH;
- break;
- case '!':
- // \@!
- i = NFA_PREV_ATOM_NO_WIDTH_NEG;
- break;
- case '<':
- op = no_Magic(getchr());
- if (op == '=') {
- // \@<=
- i = NFA_PREV_ATOM_JUST_BEFORE;
- } else if (op == '!') {
- // \@<!
- i = NFA_PREV_ATOM_JUST_BEFORE_NEG;
- }
- break;
- case '>':
- // \@>
- i = NFA_PREV_ATOM_LIKE_PATTERN;
- break;
- }
- if (i == 0) {
- semsg(_("E869: (NFA) Unknown operator '\\@%c'"), op);
- return FAIL;
- }
- EMIT(i);
- if (i == NFA_PREV_ATOM_JUST_BEFORE
- || i == NFA_PREV_ATOM_JUST_BEFORE_NEG) {
- EMIT((int)c2);
- }
- break;
-
- case Magic('?'):
- case Magic('='):
- EMIT(NFA_QUEST);
- break;
-
- case Magic('{'):
- // a{2,5} will expand to 'aaa?a?a?'
- // a{-1,3} will expand to 'aa??a??', where ?? is the nongreedy
- // version of '?'
- // \v(ab){2,3} will expand to '(ab)(ab)(ab)?', where all the
- // parenthesis have the same id
-
- greedy = true;
- c2 = peekchr();
- if (c2 == '-' || c2 == Magic('-')) {
- skipchr();
- greedy = false;
- }
- if (!read_limits(&minval, &maxval)) {
- EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
- }
-
- // <atom>{0,inf}, <atom>{0,} and <atom>{} are equivalent to
- // <atom>*
- if (minval == 0 && maxval == MAX_LIMIT) {
- if (greedy) {
- // \{}, \{0,}
- EMIT(NFA_STAR);
- } else {
- // \{-}, \{-0,}
- EMIT(NFA_STAR_NONGREEDY);
- }
- break;
- }
-
- // Special case: x{0} or x{-0}
- if (maxval == 0) {
- // Ignore result of previous call to nfa_regatom()
- post_ptr = post_start + my_post_start;
- // NFA_EMPTY is 0-length and works everywhere
- EMIT(NFA_EMPTY);
- return OK;
- }
-
- // The engine is very inefficient (uses too many states) when the
- // maximum is much larger than the minimum and when the maximum is
- // large. However, when maxval is MAX_LIMIT, it is okay, as this
- // will emit NFA_STAR.
- // Bail out if we can use the other engine, but only, when the
- // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\}
- // does not work with characters > 8 bit with the BT engine)
- if ((nfa_re_flags & RE_AUTO)
- && (maxval > 500 || maxval > minval + 200)
- && (maxval != MAX_LIMIT && minval < 200)
- && !wants_nfa) {
- return FAIL;
- }
-
- // Ignore previous call to nfa_regatom()
- post_ptr = post_start + my_post_start;
- // Save parse state after the repeated atom and the \{}
- save_parse_state(&new_state);
-
- quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY);
- for (i = 0; i < maxval; i++) {
- // Goto beginning of the repeated atom
- restore_parse_state(&old_state);
- old_post_pos = (int)(post_ptr - post_start);
- if (nfa_regatom() == FAIL) {
- return FAIL;
- }
- // after "minval" times, atoms are optional
- if (i + 1 > minval) {
- if (maxval == MAX_LIMIT) {
- if (greedy) {
- EMIT(NFA_STAR);
- } else {
- EMIT(NFA_STAR_NONGREEDY);
- }
- } else {
- EMIT(quest);
- }
- }
- if (old_post_pos != my_post_start) {
- EMIT(NFA_CONCAT);
- }
- if (i + 1 > minval && maxval == MAX_LIMIT) {
- break;
- }
- }
-
- // Go to just after the repeated atom and the \{}
- restore_parse_state(&new_state);
- curchr = -1;
-
- break;
-
- default:
- break;
- } // end switch
-
- if (re_multi_type(peekchr()) != NOT_MULTI) {
- // Can't have a multi follow a multi.
- EMSG_RET_FAIL(_("E871: (NFA regexp) Can't have a multi follow a multi"));
- }
-
- return OK;
-}
-
-// Parse one or more pieces, concatenated. It matches a match for the
-// first piece, followed by a match for the second piece, etc. Example:
-// "f[0-9]b", first matches "f", then a digit and then "b".
-//
-// concat ::= piece
-// or piece piece
-// or piece piece piece
-// etc.
-static int nfa_regconcat(void)
-{
- bool cont = true;
- bool first = true;
-
- while (cont) {
- switch (peekchr()) {
- case NUL:
- case Magic('|'):
- case Magic('&'):
- case Magic(')'):
- cont = false;
- break;
-
- case Magic('Z'):
- regflags |= RF_ICOMBINE;
- skipchr_keepstart();
- break;
- case Magic('c'):
- regflags |= RF_ICASE;
- skipchr_keepstart();
- break;
- case Magic('C'):
- regflags |= RF_NOICASE;
- skipchr_keepstart();
- break;
- case Magic('v'):
- reg_magic = MAGIC_ALL;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('m'):
- reg_magic = MAGIC_ON;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('M'):
- reg_magic = MAGIC_OFF;
- skipchr_keepstart();
- curchr = -1;
- break;
- case Magic('V'):
- reg_magic = MAGIC_NONE;
- skipchr_keepstart();
- curchr = -1;
- break;
-
- default:
- if (nfa_regpiece() == FAIL) {
- return FAIL;
- }
- if (first == false) {
- EMIT(NFA_CONCAT);
- } else {
- first = false;
- }
- break;
- }
- }
-
- return OK;
-}
-
-// Parse a branch, one or more concats, separated by "\&". It matches the
-// last concat, but only if all the preceding concats also match at the same
-// position. Examples:
-// "foobeep\&..." matches "foo" in "foobeep".
-// ".*Peter\&.*Bob" matches in a line containing both "Peter" and "Bob"
-//
-// branch ::= concat
-// or concat \& concat
-// or concat \& concat \& concat
-// etc.
-static int nfa_regbranch(void)
-{
- int old_post_pos;
-
- old_post_pos = (int)(post_ptr - post_start);
-
- // First branch, possibly the only one
- if (nfa_regconcat() == FAIL) {
- return FAIL;
- }
-
- // Try next concats
- while (peekchr() == Magic('&')) {
- skipchr();
- // if concat is empty do emit a node
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
- EMIT(NFA_NOPEN);
- EMIT(NFA_PREV_ATOM_NO_WIDTH);
- old_post_pos = (int)(post_ptr - post_start);
- if (nfa_regconcat() == FAIL) {
- return FAIL;
- }
- // if concat is empty do emit a node
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
- EMIT(NFA_CONCAT);
- }
-
- // if a branch is empty, emit one node for it
- if (old_post_pos == (int)(post_ptr - post_start)) {
- EMIT(NFA_EMPTY);
- }
-
- return OK;
-}
-
-/// Parse a pattern, one or more branches, separated by "\|". It matches
-/// anything that matches one of the branches. Example: "foo\|beep" matches
-/// "foo" and matches "beep". If more than one branch matches, the first one
-/// is used.
-///
-/// pattern ::= branch
-/// or branch \| branch
-/// or branch \| branch \| branch
-/// etc.
-///
-/// @param paren REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN
-static int nfa_reg(int paren)
-{
- int parno = 0;
-
- if (paren == REG_PAREN) {
- if (regnpar >= NSUBEXP) { // Too many `('
- EMSG_RET_FAIL(_("E872: (NFA regexp) Too many '('"));
- }
- parno = regnpar++;
- } else if (paren == REG_ZPAREN) {
- // Make a ZOPEN node.
- if (regnzpar >= NSUBEXP) {
- EMSG_RET_FAIL(_("E879: (NFA regexp) Too many \\z("));
- }
- parno = regnzpar++;
- }
-
- if (nfa_regbranch() == FAIL) {
- return FAIL; // cascaded error
- }
- while (peekchr() == Magic('|')) {
- skipchr();
- if (nfa_regbranch() == FAIL) {
- return FAIL; // cascaded error
- }
- EMIT(NFA_OR);
- }
-
- // Check for proper termination.
- if (paren != REG_NOPAREN && getchr() != Magic(')')) {
- if (paren == REG_NPAREN) {
- EMSG2_RET_FAIL(_(e_unmatchedpp), reg_magic == MAGIC_ALL);
- } else {
- EMSG2_RET_FAIL(_(e_unmatchedp), reg_magic == MAGIC_ALL);
- }
- } else if (paren == REG_NOPAREN && peekchr() != NUL) {
- if (peekchr() == Magic(')')) {
- EMSG2_RET_FAIL(_(e_unmatchedpar), reg_magic == MAGIC_ALL);
- } else {
- EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
- }
- }
- // Here we set the flag allowing back references to this set of
- // parentheses.
- if (paren == REG_PAREN) {
- had_endbrace[parno] = true; // have seen the close paren
- EMIT(NFA_MOPEN + parno);
- } else if (paren == REG_ZPAREN) {
- EMIT(NFA_ZOPEN + parno);
- }
-
- return OK;
-}
-
-#ifdef REGEXP_DEBUG
-static uint8_t code[50];
-
-static void nfa_set_code(int c)
-{
- int addnl = false;
-
- if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
- addnl = true;
- c -= NFA_ADD_NL;
- }
-
- STRCPY(code, "");
- switch (c) {
- case NFA_MATCH:
- STRCPY(code, "NFA_MATCH "); break;
- case NFA_SPLIT:
- STRCPY(code, "NFA_SPLIT "); break;
- case NFA_CONCAT:
- STRCPY(code, "NFA_CONCAT "); break;
- case NFA_NEWL:
- STRCPY(code, "NFA_NEWL "); break;
- case NFA_ZSTART:
- STRCPY(code, "NFA_ZSTART"); break;
- case NFA_ZEND:
- STRCPY(code, "NFA_ZEND"); break;
-
- case NFA_BACKREF1:
- STRCPY(code, "NFA_BACKREF1"); break;
- case NFA_BACKREF2:
- STRCPY(code, "NFA_BACKREF2"); break;
- case NFA_BACKREF3:
- STRCPY(code, "NFA_BACKREF3"); break;
- case NFA_BACKREF4:
- STRCPY(code, "NFA_BACKREF4"); break;
- case NFA_BACKREF5:
- STRCPY(code, "NFA_BACKREF5"); break;
- case NFA_BACKREF6:
- STRCPY(code, "NFA_BACKREF6"); break;
- case NFA_BACKREF7:
- STRCPY(code, "NFA_BACKREF7"); break;
- case NFA_BACKREF8:
- STRCPY(code, "NFA_BACKREF8"); break;
- case NFA_BACKREF9:
- STRCPY(code, "NFA_BACKREF9"); break;
- case NFA_ZREF1:
- STRCPY(code, "NFA_ZREF1"); break;
- case NFA_ZREF2:
- STRCPY(code, "NFA_ZREF2"); break;
- case NFA_ZREF3:
- STRCPY(code, "NFA_ZREF3"); break;
- case NFA_ZREF4:
- STRCPY(code, "NFA_ZREF4"); break;
- case NFA_ZREF5:
- STRCPY(code, "NFA_ZREF5"); break;
- case NFA_ZREF6:
- STRCPY(code, "NFA_ZREF6"); break;
- case NFA_ZREF7:
- STRCPY(code, "NFA_ZREF7"); break;
- case NFA_ZREF8:
- STRCPY(code, "NFA_ZREF8"); break;
- case NFA_ZREF9:
- STRCPY(code, "NFA_ZREF9"); break;
- case NFA_SKIP:
- STRCPY(code, "NFA_SKIP"); break;
-
- case NFA_PREV_ATOM_NO_WIDTH:
- STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH"); break;
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- STRCPY(code, "NFA_PREV_ATOM_NO_WIDTH_NEG"); break;
- case NFA_PREV_ATOM_JUST_BEFORE:
- STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE"); break;
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- STRCPY(code, "NFA_PREV_ATOM_JUST_BEFORE_NEG"); break;
- case NFA_PREV_ATOM_LIKE_PATTERN:
- STRCPY(code, "NFA_PREV_ATOM_LIKE_PATTERN"); break;
-
- case NFA_NOPEN:
- STRCPY(code, "NFA_NOPEN"); break;
- case NFA_NCLOSE:
- STRCPY(code, "NFA_NCLOSE"); break;
- case NFA_START_INVISIBLE:
- STRCPY(code, "NFA_START_INVISIBLE"); break;
- case NFA_START_INVISIBLE_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_FIRST"); break;
- case NFA_START_INVISIBLE_NEG:
- STRCPY(code, "NFA_START_INVISIBLE_NEG"); break;
- case NFA_START_INVISIBLE_NEG_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_NEG_FIRST"); break;
- case NFA_START_INVISIBLE_BEFORE:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE"); break;
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_FIRST"); break;
- case NFA_START_INVISIBLE_BEFORE_NEG:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG"); break;
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- STRCPY(code, "NFA_START_INVISIBLE_BEFORE_NEG_FIRST"); break;
- case NFA_START_PATTERN:
- STRCPY(code, "NFA_START_PATTERN"); break;
- case NFA_END_INVISIBLE:
- STRCPY(code, "NFA_END_INVISIBLE"); break;
- case NFA_END_INVISIBLE_NEG:
- STRCPY(code, "NFA_END_INVISIBLE_NEG"); break;
- case NFA_END_PATTERN:
- STRCPY(code, "NFA_END_PATTERN"); break;
-
- case NFA_COMPOSING:
- STRCPY(code, "NFA_COMPOSING"); break;
- case NFA_END_COMPOSING:
- STRCPY(code, "NFA_END_COMPOSING"); break;
- case NFA_OPT_CHARS:
- STRCPY(code, "NFA_OPT_CHARS"); break;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- STRCPY(code, "NFA_MOPEN(x)");
- code[10] = c - NFA_MOPEN + '0';
- break;
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- STRCPY(code, "NFA_MCLOSE(x)");
- code[11] = c - NFA_MCLOSE + '0';
- break;
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- STRCPY(code, "NFA_ZOPEN(x)");
- code[10] = c - NFA_ZOPEN + '0';
- break;
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- STRCPY(code, "NFA_ZCLOSE(x)");
- code[11] = c - NFA_ZCLOSE + '0';
- break;
- case NFA_EOL:
- STRCPY(code, "NFA_EOL "); break;
- case NFA_BOL:
- STRCPY(code, "NFA_BOL "); break;
- case NFA_EOW:
- STRCPY(code, "NFA_EOW "); break;
- case NFA_BOW:
- STRCPY(code, "NFA_BOW "); break;
- case NFA_EOF:
- STRCPY(code, "NFA_EOF "); break;
- case NFA_BOF:
- STRCPY(code, "NFA_BOF "); break;
- case NFA_LNUM:
- STRCPY(code, "NFA_LNUM "); break;
- case NFA_LNUM_GT:
- STRCPY(code, "NFA_LNUM_GT "); break;
- case NFA_LNUM_LT:
- STRCPY(code, "NFA_LNUM_LT "); break;
- case NFA_COL:
- STRCPY(code, "NFA_COL "); break;
- case NFA_COL_GT:
- STRCPY(code, "NFA_COL_GT "); break;
- case NFA_COL_LT:
- STRCPY(code, "NFA_COL_LT "); break;
- case NFA_VCOL:
- STRCPY(code, "NFA_VCOL "); break;
- case NFA_VCOL_GT:
- STRCPY(code, "NFA_VCOL_GT "); break;
- case NFA_VCOL_LT:
- STRCPY(code, "NFA_VCOL_LT "); break;
- case NFA_MARK:
- STRCPY(code, "NFA_MARK "); break;
- case NFA_MARK_GT:
- STRCPY(code, "NFA_MARK_GT "); break;
- case NFA_MARK_LT:
- STRCPY(code, "NFA_MARK_LT "); break;
- case NFA_CURSOR:
- STRCPY(code, "NFA_CURSOR "); break;
- case NFA_VISUAL:
- STRCPY(code, "NFA_VISUAL "); break;
- case NFA_ANY_COMPOSING:
- STRCPY(code, "NFA_ANY_COMPOSING "); break;
-
- case NFA_STAR:
- STRCPY(code, "NFA_STAR "); break;
- case NFA_STAR_NONGREEDY:
- STRCPY(code, "NFA_STAR_NONGREEDY "); break;
- case NFA_QUEST:
- STRCPY(code, "NFA_QUEST"); break;
- case NFA_QUEST_NONGREEDY:
- STRCPY(code, "NFA_QUEST_NON_GREEDY"); break;
- case NFA_EMPTY:
- STRCPY(code, "NFA_EMPTY"); break;
- case NFA_OR:
- STRCPY(code, "NFA_OR"); break;
-
- case NFA_START_COLL:
- STRCPY(code, "NFA_START_COLL"); break;
- case NFA_END_COLL:
- STRCPY(code, "NFA_END_COLL"); break;
- case NFA_START_NEG_COLL:
- STRCPY(code, "NFA_START_NEG_COLL"); break;
- case NFA_END_NEG_COLL:
- STRCPY(code, "NFA_END_NEG_COLL"); break;
- case NFA_RANGE:
- STRCPY(code, "NFA_RANGE"); break;
- case NFA_RANGE_MIN:
- STRCPY(code, "NFA_RANGE_MIN"); break;
- case NFA_RANGE_MAX:
- STRCPY(code, "NFA_RANGE_MAX"); break;
-
- case NFA_CLASS_ALNUM:
- STRCPY(code, "NFA_CLASS_ALNUM"); break;
- case NFA_CLASS_ALPHA:
- STRCPY(code, "NFA_CLASS_ALPHA"); break;
- case NFA_CLASS_BLANK:
- STRCPY(code, "NFA_CLASS_BLANK"); break;
- case NFA_CLASS_CNTRL:
- STRCPY(code, "NFA_CLASS_CNTRL"); break;
- case NFA_CLASS_DIGIT:
- STRCPY(code, "NFA_CLASS_DIGIT"); break;
- case NFA_CLASS_GRAPH:
- STRCPY(code, "NFA_CLASS_GRAPH"); break;
- case NFA_CLASS_LOWER:
- STRCPY(code, "NFA_CLASS_LOWER"); break;
- case NFA_CLASS_PRINT:
- STRCPY(code, "NFA_CLASS_PRINT"); break;
- case NFA_CLASS_PUNCT:
- STRCPY(code, "NFA_CLASS_PUNCT"); break;
- case NFA_CLASS_SPACE:
- STRCPY(code, "NFA_CLASS_SPACE"); break;
- case NFA_CLASS_UPPER:
- STRCPY(code, "NFA_CLASS_UPPER"); break;
- case NFA_CLASS_XDIGIT:
- STRCPY(code, "NFA_CLASS_XDIGIT"); break;
- case NFA_CLASS_TAB:
- STRCPY(code, "NFA_CLASS_TAB"); break;
- case NFA_CLASS_RETURN:
- STRCPY(code, "NFA_CLASS_RETURN"); break;
- case NFA_CLASS_BACKSPACE:
- STRCPY(code, "NFA_CLASS_BACKSPACE"); break;
- case NFA_CLASS_ESCAPE:
- STRCPY(code, "NFA_CLASS_ESCAPE"); break;
- case NFA_CLASS_IDENT:
- STRCPY(code, "NFA_CLASS_IDENT"); break;
- case NFA_CLASS_KEYWORD:
- STRCPY(code, "NFA_CLASS_KEYWORD"); break;
- case NFA_CLASS_FNAME:
- STRCPY(code, "NFA_CLASS_FNAME"); break;
-
- case NFA_ANY:
- STRCPY(code, "NFA_ANY"); break;
- case NFA_IDENT:
- STRCPY(code, "NFA_IDENT"); break;
- case NFA_SIDENT:
- STRCPY(code, "NFA_SIDENT"); break;
- case NFA_KWORD:
- STRCPY(code, "NFA_KWORD"); break;
- case NFA_SKWORD:
- STRCPY(code, "NFA_SKWORD"); break;
- case NFA_FNAME:
- STRCPY(code, "NFA_FNAME"); break;
- case NFA_SFNAME:
- STRCPY(code, "NFA_SFNAME"); break;
- case NFA_PRINT:
- STRCPY(code, "NFA_PRINT"); break;
- case NFA_SPRINT:
- STRCPY(code, "NFA_SPRINT"); break;
- case NFA_WHITE:
- STRCPY(code, "NFA_WHITE"); break;
- case NFA_NWHITE:
- STRCPY(code, "NFA_NWHITE"); break;
- case NFA_DIGIT:
- STRCPY(code, "NFA_DIGIT"); break;
- case NFA_NDIGIT:
- STRCPY(code, "NFA_NDIGIT"); break;
- case NFA_HEX:
- STRCPY(code, "NFA_HEX"); break;
- case NFA_NHEX:
- STRCPY(code, "NFA_NHEX"); break;
- case NFA_OCTAL:
- STRCPY(code, "NFA_OCTAL"); break;
- case NFA_NOCTAL:
- STRCPY(code, "NFA_NOCTAL"); break;
- case NFA_WORD:
- STRCPY(code, "NFA_WORD"); break;
- case NFA_NWORD:
- STRCPY(code, "NFA_NWORD"); break;
- case NFA_HEAD:
- STRCPY(code, "NFA_HEAD"); break;
- case NFA_NHEAD:
- STRCPY(code, "NFA_NHEAD"); break;
- case NFA_ALPHA:
- STRCPY(code, "NFA_ALPHA"); break;
- case NFA_NALPHA:
- STRCPY(code, "NFA_NALPHA"); break;
- case NFA_LOWER:
- STRCPY(code, "NFA_LOWER"); break;
- case NFA_NLOWER:
- STRCPY(code, "NFA_NLOWER"); break;
- case NFA_UPPER:
- STRCPY(code, "NFA_UPPER"); break;
- case NFA_NUPPER:
- STRCPY(code, "NFA_NUPPER"); break;
- case NFA_LOWER_IC:
- STRCPY(code, "NFA_LOWER_IC"); break;
- case NFA_NLOWER_IC:
- STRCPY(code, "NFA_NLOWER_IC"); break;
- case NFA_UPPER_IC:
- STRCPY(code, "NFA_UPPER_IC"); break;
- case NFA_NUPPER_IC:
- STRCPY(code, "NFA_NUPPER_IC"); break;
-
- default:
- STRCPY(code, "CHAR(x)");
- code[5] = c;
- }
-
- if (addnl == true) {
- STRCAT(code, " + NEWLINE ");
- }
-}
-
-static FILE *log_fd;
-static uint8_t e_log_open_failed[] =
- N_("Could not open temporary log file for writing, displaying on stderr... ");
-
-// Print the postfix notation of the current regexp.
-static void nfa_postfix_dump(uint8_t *expr, int retval)
-{
- int *p;
- FILE *f;
-
- f = fopen(NFA_REGEXP_DUMP_LOG, "a");
- if (f == NULL) {
- return;
- }
-
- fprintf(f, "\n-------------------------\n");
- if (retval == FAIL) {
- fprintf(f, ">>> NFA engine failed... \n");
- } else if (retval == OK) {
- fprintf(f, ">>> NFA engine succeeded !\n");
- }
- fprintf(f, "Regexp: \"%s\"\nPostfix notation (char): \"", expr);
- for (p = post_start; *p && p < post_ptr; p++) {
- nfa_set_code(*p);
- fprintf(f, "%s, ", code);
- }
- fprintf(f, "\"\nPostfix notation (int): ");
- for (p = post_start; *p && p < post_ptr; p++) {
- fprintf(f, "%d ", *p);
- }
- fprintf(f, "\n\n");
- fclose(f);
-}
-
-// Print the NFA starting with a root node "state".
-static void nfa_print_state(FILE *debugf, nfa_state_T *state)
-{
- garray_T indent;
-
- ga_init(&indent, 1, 64);
- ga_append(&indent, '\0');
- nfa_print_state2(debugf, state, &indent);
- ga_clear(&indent);
-}
-
-static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent)
-{
- uint8_t *p;
-
- if (state == NULL) {
- return;
- }
-
- fprintf(debugf, "(%2d)", abs(state->id));
-
- // Output indent
- p = (uint8_t *)indent->ga_data;
- if (indent->ga_len >= 3) {
- int last = indent->ga_len - 3;
- uint8_t save[2];
-
- strncpy(save, &p[last], 2); // NOLINT(runtime/printf)
- memcpy(&p[last], "+-", 2);
- fprintf(debugf, " %s", p);
- strncpy(&p[last], save, 2); // NOLINT(runtime/printf)
- } else {
- fprintf(debugf, " %s", p);
- }
-
- nfa_set_code(state->c);
- fprintf(debugf, "%s (%d) (id=%d) val=%d\n",
- code,
- state->c,
- abs(state->id),
- state->val);
- if (state->id < 0) {
- return;
- }
-
- state->id = abs(state->id) * -1;
-
- // grow indent for state->out
- indent->ga_len -= 1;
- if (state->out1) {
- ga_concat(indent, (uint8_t *)"| ");
- } else {
- ga_concat(indent, (uint8_t *)" ");
- }
- ga_append(indent, NUL);
-
- nfa_print_state2(debugf, state->out, indent);
-
- // replace last part of indent for state->out1
- indent->ga_len -= 3;
- ga_concat(indent, (uint8_t *)" ");
- ga_append(indent, NUL);
-
- nfa_print_state2(debugf, state->out1, indent);
-
- // shrink indent
- indent->ga_len -= 3;
- ga_append(indent, NUL);
-}
-
-// Print the NFA state machine.
-static void nfa_dump(nfa_regprog_T *prog)
-{
- FILE *debugf = fopen(NFA_REGEXP_DUMP_LOG, "a");
-
- if (debugf == NULL) {
- return;
- }
-
- nfa_print_state(debugf, prog->start);
-
- if (prog->reganch) {
- fprintf(debugf, "reganch: %d\n", prog->reganch);
- }
- if (prog->regstart != NUL) {
- fprintf(debugf, "regstart: %c (decimal: %d)\n",
- prog->regstart, prog->regstart);
- }
- if (prog->match_text != NULL) {
- fprintf(debugf, "match_text: \"%s\"\n", prog->match_text);
- }
-
- fclose(debugf);
-}
-#endif // REGEXP_DEBUG
-
-// Parse r.e. @expr and convert it into postfix form.
-// Return the postfix string on success, NULL otherwise.
-static int *re2post(void)
-{
- if (nfa_reg(REG_NOPAREN) == FAIL) {
- return NULL;
- }
- EMIT(NFA_MOPEN);
- return post_start;
-}
-
-// NB. Some of the code below is inspired by Russ's.
-
-// Represents an NFA state plus zero or one or two arrows exiting.
-// if c == MATCH, no arrows out; matching state.
-// If c == SPLIT, unlabeled arrows to out and out1 (if != NULL).
-// If c < 256, labeled arrow with character c to out.
-
-static nfa_state_T *state_ptr; // points to nfa_prog->state
-
-// Allocate and initialize nfa_state_T.
-static nfa_state_T *alloc_state(int c, nfa_state_T *out, nfa_state_T *out1)
-{
- nfa_state_T *s;
-
- if (istate >= nstate) {
- return NULL;
- }
-
- s = &state_ptr[istate++];
-
- s->c = c;
- s->out = out;
- s->out1 = out1;
- s->val = 0;
-
- s->id = istate;
- s->lastlist[0] = 0;
- s->lastlist[1] = 0;
-
- return s;
-}
-
-// A partially built NFA without the matching state filled in.
-// Frag_T.start points at the start state.
-// Frag_T.out is a list of places that need to be set to the
-// next state for this fragment.
-
-// Initialize a Frag_T struct and return it.
-static Frag_T frag(nfa_state_T *start, Ptrlist *out)
-{
- Frag_T n;
-
- n.start = start;
- n.out = out;
- return n;
-}
-
-// Create singleton list containing just outp.
-static Ptrlist *list1(nfa_state_T **outp)
-{
- Ptrlist *l;
-
- l = (Ptrlist *)outp;
- l->next = NULL;
- return l;
-}
-
-// Patch the list of states at out to point to start.
-static void patch(Ptrlist *l, nfa_state_T *s)
-{
- Ptrlist *next;
-
- for (; l; l = next) {
- next = l->next;
- l->s = s;
- }
-}
-
-// Join the two lists l1 and l2, returning the combination.
-static Ptrlist *append(Ptrlist *l1, Ptrlist *l2)
-{
- Ptrlist *oldl1;
-
- oldl1 = l1;
- while (l1->next) {
- l1 = l1->next;
- }
- l1->next = l2;
- return oldl1;
-}
-
-// Stack used for transforming postfix form into NFA.
-static Frag_T empty;
-
-static void st_error(int *postfix, int *end, int *p)
-{
-#ifdef NFA_REGEXP_ERROR_LOG
- FILE *df;
- int *p2;
-
- df = fopen(NFA_REGEXP_ERROR_LOG, "a");
- if (df) {
- fprintf(df, "Error popping the stack!\n");
-# ifdef REGEXP_DEBUG
- fprintf(df, "Current regexp is \"%s\"\n", nfa_regengine.expr);
-# endif
- fprintf(df, "Postfix form is: ");
-# ifdef REGEXP_DEBUG
- for (p2 = postfix; p2 < end; p2++) {
- nfa_set_code(*p2);
- fprintf(df, "%s, ", code);
- }
- nfa_set_code(*p);
- fprintf(df, "\nCurrent position is: ");
- for (p2 = postfix; p2 <= p; p2++) {
- nfa_set_code(*p2);
- fprintf(df, "%s, ", code);
- }
-# else
- for (p2 = postfix; p2 < end; p2++) {
- fprintf(df, "%d, ", *p2);
- }
- fprintf(df, "\nCurrent position is: ");
- for (p2 = postfix; p2 <= p; p2++) {
- fprintf(df, "%d, ", *p2);
- }
-# endif
- fprintf(df, "\n--------------------------\n");
- fclose(df);
- }
-#endif
- emsg(_("E874: (NFA) Could not pop the stack!"));
-}
-
-// Push an item onto the stack.
-static void st_push(Frag_T s, Frag_T **p, Frag_T *stack_end)
-{
- Frag_T *stackp = *p;
-
- if (stackp >= stack_end) {
- return;
- }
- *stackp = s;
- *p = *p + 1;
-}
-
-// Pop an item from the stack.
-static Frag_T st_pop(Frag_T **p, Frag_T *stack)
-{
- Frag_T *stackp;
-
- *p = *p - 1;
- stackp = *p;
- if (stackp < stack) {
- return empty;
- }
- return **p;
-}
-
-// Estimate the maximum byte length of anything matching "state".
-// When unknown or unlimited return -1.
-static int nfa_max_width(nfa_state_T *startstate, int depth)
-{
- int l, r;
- nfa_state_T *state = startstate;
- int len = 0;
-
- // detect looping in a NFA_SPLIT
- if (depth > 4) {
- return -1;
- }
-
- while (state != NULL) {
- switch (state->c) {
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- // the end, return what we have
- return len;
-
- case NFA_SPLIT:
- // two alternatives, use the maximum
- l = nfa_max_width(state->out, depth + 1);
- r = nfa_max_width(state->out1, depth + 1);
- if (l < 0 || r < 0) {
- return -1;
- }
- return len + (l > r ? l : r);
-
- case NFA_ANY:
- case NFA_START_COLL:
- case NFA_START_NEG_COLL:
- // Matches some character, including composing chars.
- len += MB_MAXBYTES;
- if (state->c != NFA_ANY) {
- // Skip over the characters.
- state = state->out1->out;
- continue;
- }
- break;
-
- case NFA_DIGIT:
- case NFA_WHITE:
- case NFA_HEX:
- case NFA_OCTAL:
- // ascii
- len++;
- break;
-
- case NFA_IDENT:
- case NFA_SIDENT:
- case NFA_KWORD:
- case NFA_SKWORD:
- case NFA_FNAME:
- case NFA_SFNAME:
- case NFA_PRINT:
- case NFA_SPRINT:
- case NFA_NWHITE:
- case NFA_NDIGIT:
- case NFA_NHEX:
- case NFA_NOCTAL:
- case NFA_WORD:
- case NFA_NWORD:
- case NFA_HEAD:
- case NFA_NHEAD:
- case NFA_ALPHA:
- case NFA_NALPHA:
- case NFA_LOWER:
- case NFA_NLOWER:
- case NFA_UPPER:
- case NFA_NUPPER:
- case NFA_LOWER_IC:
- case NFA_NLOWER_IC:
- case NFA_UPPER_IC:
- case NFA_NUPPER_IC:
- case NFA_ANY_COMPOSING:
- // possibly non-ascii
- len += 3;
- break;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- // zero-width, out1 points to the END state
- state = state->out1->out;
- continue;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- case NFA_NEWL:
- case NFA_SKIP:
- // unknown width
- return -1;
-
- case NFA_BOL:
- case NFA_EOL:
- case NFA_BOF:
- case NFA_EOF:
- case NFA_BOW:
- case NFA_EOW:
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_NOPEN:
- case NFA_NCLOSE:
-
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
- case NFA_VISUAL:
- case NFA_LNUM:
- case NFA_CURSOR:
- case NFA_COL:
- case NFA_VCOL:
- case NFA_MARK:
-
- case NFA_ZSTART:
- case NFA_ZEND:
- case NFA_OPT_CHARS:
- case NFA_EMPTY:
- case NFA_START_PATTERN:
- case NFA_END_PATTERN:
- case NFA_COMPOSING:
- case NFA_END_COMPOSING:
- // zero-width
- break;
-
- default:
- if (state->c < 0) {
- // don't know what this is
- return -1;
- }
- // normal character
- len += utf_char2len(state->c);
- break;
- }
-
- // normal way to continue
- state = state->out;
- }
-
- // unrecognized, "cannot happen"
- return -1;
-}
-
-// Convert a postfix form into its equivalent NFA.
-// Return the NFA start state on success, NULL otherwise.
-static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
-{
- int *p;
- int mopen;
- int mclose;
- Frag_T *stack = NULL;
- Frag_T *stackp = NULL;
- Frag_T *stack_end = NULL;
- Frag_T e1;
- Frag_T e2;
- Frag_T e;
- nfa_state_T *s;
- nfa_state_T *s1;
- nfa_state_T *matchstate;
- nfa_state_T *ret = NULL;
-
- if (postfix == NULL) {
- return NULL;
- }
-
-#define PUSH(s) st_push((s), &stackp, stack_end)
-#define POP() st_pop(&stackp, stack); \
- if (stackp < stack) { \
- st_error(postfix, end, p); \
- xfree(stack); \
- return NULL; \
- }
-
- if (nfa_calc_size == false) {
- // Allocate space for the stack. Max states on the stack: "nstate".
- stack = xmalloc((size_t)(nstate + 1) * sizeof(Frag_T));
- stackp = stack;
- stack_end = stack + (nstate + 1);
- }
-
- for (p = postfix; p < end; p++) {
- switch (*p) {
- case NFA_CONCAT:
- // Concatenation.
- // Pay attention: this operator does not exist in the r.e. itself
- // (it is implicit, really). It is added when r.e. is translated
- // to postfix form in re2post().
- if (nfa_calc_size == true) {
- // nstate += 0;
- break;
- }
- e2 = POP();
- e1 = POP();
- patch(e1.out, e2.start);
- PUSH(frag(e1.start, e2.out));
- break;
-
- case NFA_OR:
- // Alternation
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e2 = POP();
- e1 = POP();
- s = alloc_state(NFA_SPLIT, e1.start, e2.start);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e1.out, e2.out)));
- break;
-
- case NFA_STAR:
- // Zero or more, prefer more
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- PUSH(frag(s, list1(&s->out1)));
- break;
-
- case NFA_STAR_NONGREEDY:
- // Zero or more, prefer zero
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, NULL, e.start);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- PUSH(frag(s, list1(&s->out)));
- break;
-
- case NFA_QUEST:
- // one or zero atoms=> greedy match
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e.out, list1(&s->out1))));
- break;
-
- case NFA_QUEST_NONGREEDY:
- // zero or one atoms => non-greedy match
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_SPLIT, NULL, e.start);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, append(e.out, list1(&s->out))));
- break;
-
- case NFA_END_COLL:
- case NFA_END_NEG_COLL:
- // On the stack is the sequence starting with NFA_START_COLL or
- // NFA_START_NEG_COLL and all possible characters. Patch it to
- // add the output to the start.
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- e = POP();
- s = alloc_state(NFA_END_COLL, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- patch(e.out, s);
- e.start->out1 = s;
- PUSH(frag(e.start, list1(&s->out)));
- break;
-
- case NFA_RANGE:
- // Before this are two characters, the low and high end of a
- // range. Turn them into two states with MIN and MAX.
- if (nfa_calc_size == true) {
- // nstate += 0;
- break;
- }
- e2 = POP();
- e1 = POP();
- e2.start->val = e2.start->c;
- e2.start->c = NFA_RANGE_MAX;
- e1.start->val = e1.start->c;
- e1.start->c = NFA_RANGE_MIN;
- patch(e1.out, e2.start);
- PUSH(frag(e1.start, e2.out));
- break;
-
- case NFA_EMPTY:
- // 0-length, used in a repetition with max/min count of 0
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- s = alloc_state(NFA_EMPTY, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, list1(&s->out)));
- break;
-
- case NFA_OPT_CHARS: {
- int n;
-
- // \%[abc] implemented as:
- // NFA_SPLIT
- // +-CHAR(a)
- // | +-NFA_SPLIT
- // | +-CHAR(b)
- // | | +-NFA_SPLIT
- // | | +-CHAR(c)
- // | | | +-next
- // | | +- next
- // | +- next
- // +- next
- n = *++p; // get number of characters
- if (nfa_calc_size == true) {
- nstate += n;
- break;
- }
- s = NULL; // avoid compiler warning
- e1.out = NULL; // stores list with out1's
- s1 = NULL; // previous NFA_SPLIT to connect to
- while (n-- > 0) {
- e = POP(); // get character
- s = alloc_state(NFA_SPLIT, e.start, NULL);
- if (s == NULL) {
- goto theend;
- }
- if (e1.out == NULL) {
- e1 = e;
- }
- patch(e.out, s1);
- append(e1.out, list1(&s->out1));
- s1 = s;
- }
- PUSH(frag(s, e1.out));
- break;
- }
-
- case NFA_PREV_ATOM_NO_WIDTH:
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- case NFA_PREV_ATOM_JUST_BEFORE:
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- case NFA_PREV_ATOM_LIKE_PATTERN: {
- int before = (*p == NFA_PREV_ATOM_JUST_BEFORE
- || *p == NFA_PREV_ATOM_JUST_BEFORE_NEG);
- int pattern = (*p == NFA_PREV_ATOM_LIKE_PATTERN);
- int start_state;
- int end_state;
- int n = 0;
- nfa_state_T *zend;
- nfa_state_T *skip;
-
- switch (*p) {
- case NFA_PREV_ATOM_NO_WIDTH:
- start_state = NFA_START_INVISIBLE;
- end_state = NFA_END_INVISIBLE;
- break;
- case NFA_PREV_ATOM_NO_WIDTH_NEG:
- start_state = NFA_START_INVISIBLE_NEG;
- end_state = NFA_END_INVISIBLE_NEG;
- break;
- case NFA_PREV_ATOM_JUST_BEFORE:
- start_state = NFA_START_INVISIBLE_BEFORE;
- end_state = NFA_END_INVISIBLE;
- break;
- case NFA_PREV_ATOM_JUST_BEFORE_NEG:
- start_state = NFA_START_INVISIBLE_BEFORE_NEG;
- end_state = NFA_END_INVISIBLE_NEG;
- break;
- default: // NFA_PREV_ATOM_LIKE_PATTERN:
- start_state = NFA_START_PATTERN;
- end_state = NFA_END_PATTERN;
- break;
- }
-
- if (before) {
- n = *++p; // get the count
- }
- // The \@= operator: match the preceding atom with zero width.
- // The \@! operator: no match for the preceding atom.
- // The \@<= operator: match for the preceding atom.
- // The \@<! operator: no match for the preceding atom.
- // Surrounds the preceding atom with START_INVISIBLE and
- // END_INVISIBLE, similarly to MOPEN.
-
- if (nfa_calc_size == true) {
- nstate += pattern ? 4 : 2;
- break;
- }
- e = POP();
- s1 = alloc_state(end_state, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
-
- s = alloc_state(start_state, e.start, s1);
- if (s == NULL) {
- goto theend;
- }
- if (pattern) {
- // NFA_ZEND -> NFA_END_PATTERN -> NFA_SKIP -> what follows.
- skip = alloc_state(NFA_SKIP, NULL, NULL);
- if (skip == NULL) {
- goto theend;
- }
- zend = alloc_state(NFA_ZEND, s1, NULL);
- if (zend == NULL) {
- goto theend;
- }
- s1->out= skip;
- patch(e.out, zend);
- PUSH(frag(s, list1(&skip->out)));
- } else {
- patch(e.out, s1);
- PUSH(frag(s, list1(&s1->out)));
- if (before) {
- if (n <= 0) {
- // See if we can guess the maximum width, it avoids a
- // lot of pointless tries.
- n = nfa_max_width(e.start, 0);
- }
- s->val = n; // store the count
- }
- }
- break;
- }
-
- case NFA_COMPOSING: // char with composing char
- FALLTHROUGH;
-
- case NFA_MOPEN: // \( \) Submatch
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN: // \z( \) Submatch
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN: // \%( \) "Invisible Submatch"
- if (nfa_calc_size == true) {
- nstate += 2;
- break;
- }
-
- mopen = *p;
- switch (*p) {
- case NFA_NOPEN:
- mclose = NFA_NCLOSE; break;
- case NFA_ZOPEN:
- mclose = NFA_ZCLOSE; break;
- case NFA_ZOPEN1:
- mclose = NFA_ZCLOSE1; break;
- case NFA_ZOPEN2:
- mclose = NFA_ZCLOSE2; break;
- case NFA_ZOPEN3:
- mclose = NFA_ZCLOSE3; break;
- case NFA_ZOPEN4:
- mclose = NFA_ZCLOSE4; break;
- case NFA_ZOPEN5:
- mclose = NFA_ZCLOSE5; break;
- case NFA_ZOPEN6:
- mclose = NFA_ZCLOSE6; break;
- case NFA_ZOPEN7:
- mclose = NFA_ZCLOSE7; break;
- case NFA_ZOPEN8:
- mclose = NFA_ZCLOSE8; break;
- case NFA_ZOPEN9:
- mclose = NFA_ZCLOSE9; break;
- case NFA_COMPOSING:
- mclose = NFA_END_COMPOSING; break;
- default:
- // NFA_MOPEN, NFA_MOPEN1 .. NFA_MOPEN9
- mclose = *p + NSUBEXP;
- break;
- }
-
- // Allow "NFA_MOPEN" as a valid postfix representation for
- // the empty regexp "". In this case, the NFA will be
- // NFA_MOPEN -> NFA_MCLOSE. Note that this also allows
- // empty groups of parenthesis, and empty mbyte chars
- if (stackp == stack) {
- s = alloc_state(mopen, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s1 = alloc_state(mclose, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
- patch(list1(&s->out), s1);
- PUSH(frag(s, list1(&s1->out)));
- break;
- }
-
- // At least one node was emitted before NFA_MOPEN, so
- // at least one node will be between NFA_MOPEN and NFA_MCLOSE
- e = POP();
- s = alloc_state(mopen, e.start, NULL); // `('
- if (s == NULL) {
- goto theend;
- }
-
- s1 = alloc_state(mclose, NULL, NULL); // `)'
- if (s1 == NULL) {
- goto theend;
- }
- patch(e.out, s1);
-
- if (mopen == NFA_COMPOSING) {
- // COMPOSING->out1 = END_COMPOSING
- patch(list1(&s->out1), s1);
- }
-
- PUSH(frag(s, list1(&s1->out)));
- break;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- if (nfa_calc_size == true) {
- nstate += 2;
- break;
- }
- s = alloc_state(*p, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s1 = alloc_state(NFA_SKIP, NULL, NULL);
- if (s1 == NULL) {
- goto theend;
- }
- patch(list1(&s->out), s1);
- PUSH(frag(s, list1(&s1->out)));
- break;
-
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT: {
- int n = *++p; // lnum, col or mark name
-
- if (nfa_calc_size == true) {
- nstate += 1;
- break;
- }
- s = alloc_state(p[-1], NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- s->val = n;
- PUSH(frag(s, list1(&s->out)));
- break;
- }
-
- case NFA_ZSTART:
- case NFA_ZEND:
- default:
- // Operands
- if (nfa_calc_size == true) {
- nstate++;
- break;
- }
- s = alloc_state(*p, NULL, NULL);
- if (s == NULL) {
- goto theend;
- }
- PUSH(frag(s, list1(&s->out)));
- break;
- } // switch(*p)
- } // for(p = postfix; *p; ++p)
-
- if (nfa_calc_size == true) {
- nstate++;
- goto theend; // Return value when counting size is ignored anyway
- }
-
- e = POP();
- if (stackp != stack) {
- xfree(stack);
- EMSG_RET_NULL(_("E875: (NFA regexp) (While converting from postfix to NFA),"
- "too many states left on stack"));
- }
-
- if (istate >= nstate) {
- xfree(stack);
- EMSG_RET_NULL(_("E876: (NFA regexp) "
- "Not enough space to store the whole NFA "));
- }
-
- matchstate = &state_ptr[istate++]; // the match state
- matchstate->c = NFA_MATCH;
- matchstate->out = matchstate->out1 = NULL;
- matchstate->id = 0;
-
- patch(e.out, matchstate);
- ret = e.start;
-
-theend:
- xfree(stack);
- return ret;
-
-#undef POP1
-#undef PUSH1
-#undef POP2
-#undef PUSH2
-#undef POP
-#undef PUSH
-}
-
-// After building the NFA program, inspect it to add optimization hints.
-static void nfa_postprocess(nfa_regprog_T *prog)
-{
- int i;
- int c;
-
- for (i = 0; i < prog->nstate; i++) {
- c = prog->state[i].c;
- if (c == NFA_START_INVISIBLE
- || c == NFA_START_INVISIBLE_NEG
- || c == NFA_START_INVISIBLE_BEFORE
- || c == NFA_START_INVISIBLE_BEFORE_NEG) {
- int directly;
-
- // Do it directly when what follows is possibly the end of the
- // match.
- if (match_follows(prog->state[i].out1->out, 0)) {
- directly = true;
- } else {
- int ch_invisible = failure_chance(prog->state[i].out, 0);
- int ch_follows = failure_chance(prog->state[i].out1->out, 0);
-
- // Postpone when the invisible match is expensive or has a
- // lower chance of failing.
- if (c == NFA_START_INVISIBLE_BEFORE
- || c == NFA_START_INVISIBLE_BEFORE_NEG) {
- // "before" matches are very expensive when
- // unbounded, always prefer what follows then,
- // unless what follows will always match.
- // Otherwise strongly prefer what follows.
- if (prog->state[i].val <= 0 && ch_follows > 0) {
- directly = false;
- } else {
- directly = ch_follows * 10 < ch_invisible;
- }
- } else {
- // normal invisible, first do the one with the
- // highest failure chance
- directly = ch_follows < ch_invisible;
- }
- }
- if (directly) {
- // switch to the _FIRST state
- prog->state[i].c++;
- }
- }
- }
-}
-
-/////////////////////////////////////////////////////////////////
-// NFA execution code.
-/////////////////////////////////////////////////////////////////
-
-// Values for done in nfa_pim_T.
-#define NFA_PIM_UNUSED 0 // pim not used
-#define NFA_PIM_TODO 1 // pim not done yet
-#define NFA_PIM_MATCH 2 // pim executed, matches
-#define NFA_PIM_NOMATCH 3 // pim executed, no match
-
-#ifdef REGEXP_DEBUG
-static void log_subsexpr(regsubs_T *subs)
-{
- log_subexpr(&subs->norm);
- if (rex.nfa_has_zsubexpr) {
- log_subexpr(&subs->synt);
- }
-}
-
-static void log_subexpr(regsub_T *sub)
-{
- int j;
-
- for (j = 0; j < sub->in_use; j++) {
- if (REG_MULTI) {
- fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n",
- j,
- sub->list.multi[j].start_col,
- (int)sub->list.multi[j].start_lnum,
- sub->list.multi[j].end_col,
- (int)sub->list.multi[j].end_lnum);
- } else {
- char *s = (char *)sub->list.line[j].start;
- char *e = (char *)sub->list.line[j].end;
-
- fprintf(log_fd, "*** group %d, start: \"%s\", end: \"%s\"\n",
- j,
- s == NULL ? "NULL" : s,
- e == NULL ? "NULL" : e);
- }
- }
-}
-
-static char *pim_info(const nfa_pim_T *pim)
-{
- static char buf[30];
-
- if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
- buf[0] = NUL;
- } else {
- snprintf(buf, sizeof(buf), " PIM col %d",
- REG_MULTI
- ? (int)pim->end.pos.col
- : (int)(pim->end.ptr - rex.input));
- }
- return buf;
-}
-
-#endif
-
-// Used during execution: whether a match has been found.
-static int nfa_match;
-static proftime_T *nfa_time_limit;
-static int *nfa_timed_out;
-static int nfa_time_count;
-
-// Copy postponed invisible match info from "from" to "to".
-static void copy_pim(nfa_pim_T *to, nfa_pim_T *from)
-{
- to->result = from->result;
- to->state = from->state;
- copy_sub(&to->subs.norm, &from->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&to->subs.synt, &from->subs.synt);
- }
- to->end = from->end;
-}
-
-static void clear_sub(regsub_T *sub)
-{
- if (REG_MULTI) {
- // Use 0xff to set lnum to -1
- memset(sub->list.multi, 0xff, sizeof(struct multipos) * (size_t)rex.nfa_nsubexpr);
- } else {
- memset(sub->list.line, 0, sizeof(struct linepos) * (size_t)rex.nfa_nsubexpr);
- }
- sub->in_use = 0;
-}
-
-// Copy the submatches from "from" to "to".
-static void copy_sub(regsub_T *to, regsub_T *from)
-{
- to->in_use = from->in_use;
- if (from->in_use <= 0) {
- return;
- }
-
- // Copy the match start and end positions.
- if (REG_MULTI) {
- memmove(&to->list.multi[0], &from->list.multi[0],
- sizeof(struct multipos) * (size_t)from->in_use);
- to->orig_start_col = from->orig_start_col;
- } else {
- memmove(&to->list.line[0], &from->list.line[0],
- sizeof(struct linepos) * (size_t)from->in_use);
- }
-}
-
-// Like copy_sub() but exclude the main match.
-static void copy_sub_off(regsub_T *to, regsub_T *from)
-{
- if (to->in_use < from->in_use) {
- to->in_use = from->in_use;
- }
- if (from->in_use <= 1) {
- return;
- }
-
- // Copy the match start and end positions.
- if (REG_MULTI) {
- memmove(&to->list.multi[1], &from->list.multi[1],
- sizeof(struct multipos) * (size_t)(from->in_use - 1));
- } else {
- memmove(&to->list.line[1], &from->list.line[1],
- sizeof(struct linepos) * (size_t)(from->in_use - 1));
- }
-}
-
-// Like copy_sub() but only do the end of the main match if \ze is present.
-static void copy_ze_off(regsub_T *to, regsub_T *from)
-{
- if (!rex.nfa_has_zend) {
- return;
- }
-
- if (REG_MULTI) {
- if (from->list.multi[0].end_lnum >= 0) {
- to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
- to->list.multi[0].end_col = from->list.multi[0].end_col;
- }
- } else {
- if (from->list.line[0].end != NULL) {
- to->list.line[0].end = from->list.line[0].end;
- }
- }
-}
-
-// Return true if "sub1" and "sub2" have the same start positions.
-// When using back-references also check the end position.
-static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
-{
- int i;
- int todo;
- linenr_T s1;
- linenr_T s2;
- uint8_t *sp1;
- uint8_t *sp2;
-
- todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use;
- if (REG_MULTI) {
- for (i = 0; i < todo; i++) {
- if (i < sub1->in_use) {
- s1 = sub1->list.multi[i].start_lnum;
- } else {
- s1 = -1;
- }
- if (i < sub2->in_use) {
- s2 = sub2->list.multi[i].start_lnum;
- } else {
- s2 = -1;
- }
- if (s1 != s2) {
- return false;
- }
- if (s1 != -1 && sub1->list.multi[i].start_col
- != sub2->list.multi[i].start_col) {
- return false;
- }
- if (rex.nfa_has_backref) {
- if (i < sub1->in_use) {
- s1 = sub1->list.multi[i].end_lnum;
- } else {
- s1 = -1;
- }
- if (i < sub2->in_use) {
- s2 = sub2->list.multi[i].end_lnum;
- } else {
- s2 = -1;
- }
- if (s1 != s2) {
- return false;
- }
- if (s1 != -1
- && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
- return false;
- }
- }
- }
- } else {
- for (i = 0; i < todo; i++) {
- if (i < sub1->in_use) {
- sp1 = sub1->list.line[i].start;
- } else {
- sp1 = NULL;
- }
- if (i < sub2->in_use) {
- sp2 = sub2->list.line[i].start;
- } else {
- sp2 = NULL;
- }
- if (sp1 != sp2) {
- return false;
- }
- if (rex.nfa_has_backref) {
- if (i < sub1->in_use) {
- sp1 = sub1->list.line[i].end;
- } else {
- sp1 = NULL;
- }
- if (i < sub2->in_use) {
- sp2 = sub2->list.line[i].end;
- } else {
- sp2 = NULL;
- }
- if (sp1 != sp2) {
- return false;
- }
- }
- }
- }
-
- return true;
-}
-
-#ifdef REGEXP_DEBUG
-static void open_debug_log(TriState result)
-{
- log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (log_fd == NULL) {
- emsg(_(e_log_open_failed));
- log_fd = stderr;
- }
-
- fprintf(log_fd, "****************************\n");
- fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
- fprintf(log_fd, "MATCH = %s\n", result == kTrue ? "OK" : result == kNone ? "MAYBE" : "FALSE");
- fprintf(log_fd, "****************************\n");
-}
-
-static void report_state(char *action, regsub_T *sub, nfa_state_T *state, int lid, nfa_pim_T *pim)
-{
- int col;
-
- if (sub->in_use <= 0) {
- col = -1;
- } else if (REG_MULTI) {
- col = sub->list.multi[0].start_col;
- } else {
- col = (int)(sub->list.line[0].start - rex.line);
- }
- nfa_set_code(state->c);
- if (log_fd == NULL) {
- open_debug_log(kNone);
- }
- fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n",
- action, abs(state->id), lid, state->c, code, col,
- pim_info(pim));
-}
-
-#endif
-
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-/// @param pim postponed match or NULL
-///
-/// @return true if the same state is already in list "l" with the same
-/// positions as "subs".
-static bool has_state_with_pos(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim)
- FUNC_ATTR_NONNULL_ARG(1, 2, 3)
-{
- for (int i = 0; i < l->n; i++) {
- nfa_thread_T *thread = &l->t[i];
- if (thread->state->id == state->id
- && sub_equal(&thread->subs.norm, &subs->norm)
- && (!rex.nfa_has_zsubexpr
- || sub_equal(&thread->subs.synt, &subs->synt))
- && pim_equal(&thread->pim, pim)) {
- return true;
- }
- }
- return false;
-}
-
-// Return true if "one" and "two" are equal. That includes when both are not
-// set.
-static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two)
-{
- const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
- const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
-
- if (one_unused) {
- // one is unused: equal when two is also unused
- return two_unused;
- }
- if (two_unused) {
- // one is used and two is not: not equal
- return false;
- }
- // compare the state id
- if (one->state->id != two->state->id) {
- return false;
- }
- // compare the position
- if (REG_MULTI) {
- return one->end.pos.lnum == two->end.pos.lnum
- && one->end.pos.col == two->end.pos.col;
- }
- return one->end.ptr == two->end.ptr;
-}
-
-// Return true if "state" leads to a NFA_MATCH without advancing the input.
-static bool match_follows(const nfa_state_T *startstate, int depth)
- FUNC_ATTR_NONNULL_ALL
-{
- const nfa_state_T *state = startstate;
-
- // avoid too much recursion
- if (depth > 10) {
- return false;
- }
- while (state != NULL) {
- switch (state->c) {
- case NFA_MATCH:
- case NFA_MCLOSE:
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- case NFA_END_PATTERN:
- return true;
-
- case NFA_SPLIT:
- return match_follows(state->out, depth + 1)
- || match_follows(state->out1, depth + 1);
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- case NFA_COMPOSING:
- // skip ahead to next state
- state = state->out1->out;
- continue;
-
- case NFA_ANY:
- case NFA_ANY_COMPOSING:
- case NFA_IDENT:
- case NFA_SIDENT:
- case NFA_KWORD:
- case NFA_SKWORD:
- case NFA_FNAME:
- case NFA_SFNAME:
- case NFA_PRINT:
- case NFA_SPRINT:
- case NFA_WHITE:
- case NFA_NWHITE:
- case NFA_DIGIT:
- case NFA_NDIGIT:
- case NFA_HEX:
- case NFA_NHEX:
- case NFA_OCTAL:
- case NFA_NOCTAL:
- case NFA_WORD:
- case NFA_NWORD:
- case NFA_HEAD:
- case NFA_NHEAD:
- case NFA_ALPHA:
- case NFA_NALPHA:
- case NFA_LOWER:
- case NFA_NLOWER:
- case NFA_UPPER:
- case NFA_NUPPER:
- case NFA_LOWER_IC:
- case NFA_NLOWER_IC:
- case NFA_UPPER_IC:
- case NFA_NUPPER_IC:
- case NFA_START_COLL:
- case NFA_START_NEG_COLL:
- case NFA_NEWL:
- // state will advance input
- return false;
-
- default:
- if (state->c > 0) {
- // state will advance input
- return false;
- }
- // Others: zero-width or possibly zero-width, might still find
- // a match at the same position, keep looking.
- break;
- }
- state = state->out;
- }
- return false;
-}
-
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-///
-/// @return true if "state" is already in list "l".
-static bool state_in_list(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs)
- FUNC_ATTR_NONNULL_ALL
-{
- if (state->lastlist[nfa_ll_index] == l->id) {
- if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
- return true;
- }
- }
- return false;
-}
-
-// Offset used for "off" by addstate_here().
-#define ADDSTATE_HERE_OFFSET 10
-
-/// Add "state" and possibly what follows to state list ".".
-///
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs_arg pointers to subexpressions
-/// @param pim postponed look-behind match
-/// @param off_arg byte offset, when -1 go to next line
-///
-/// @return "subs_arg", possibly copied into temp_subs.
-/// NULL when recursiveness is too deep.
-static regsubs_T *addstate(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs_arg, nfa_pim_T *pim,
- int off_arg)
- FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- int subidx;
- int off = off_arg;
- int add_here = false;
- int listindex = 0;
- int k;
- int found = false;
- nfa_thread_T *thread;
- struct multipos save_multipos;
- int save_in_use;
- uint8_t *save_ptr;
- int i;
- regsub_T *sub;
- regsubs_T *subs = subs_arg;
- static regsubs_T temp_subs;
-#ifdef REGEXP_DEBUG
- int did_print = false;
-#endif
- static int depth = 0;
-
- // This function is called recursively. When the depth is too much we run
- // out of stack and crash, limit recursiveness here.
- if (++depth >= 5000 || subs == NULL) {
- depth--;
- return NULL;
- }
-
- if (off_arg <= -ADDSTATE_HERE_OFFSET) {
- add_here = true;
- off = 0;
- listindex = -(off_arg + ADDSTATE_HERE_OFFSET);
- }
-
- switch (state->c) {
- case NFA_NCLOSE:
- case NFA_MCLOSE:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_MOPEN:
- case NFA_ZEND:
- case NFA_SPLIT:
- case NFA_EMPTY:
- // These nodes are not added themselves but their "out" and/or
- // "out1" may be added below.
- break;
-
- case NFA_BOL:
- case NFA_BOF:
- // "^" won't match past end-of-line, don't bother trying.
- // Except when at the end of the line, or when we are going to the
- // next line for a look-behind match.
- if (rex.input > rex.line
- && *rex.input != NUL
- && (nfa_endp == NULL
- || !REG_MULTI
- || rex.lnum == nfa_endp->se_u.pos.lnum)) {
- goto skip_add;
- }
- FALLTHROUGH;
-
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN:
- case NFA_ZSTART:
- // These nodes need to be added so that we can bail out when it
- // was added to this list before at the same position to avoid an
- // endless loop for "\(\)*"
-
- default:
- if (state->lastlist[nfa_ll_index] == l->id && state->c != NFA_SKIP) {
- // This state is already in the list, don't add it again,
- // unless it is an MOPEN that is used for a backreference or
- // when there is a PIM. For NFA_MATCH check the position,
- // lower position is preferred.
- if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
- && state->c != NFA_MATCH) {
- // When called from addstate_here() do insert before
- // existing states.
- if (add_here) {
- for (k = 0; k < l->n && k < listindex; k++) {
- if (l->t[k].state->id == state->id) {
- found = true;
- break;
- }
- }
- }
-
- if (!add_here || found) {
-skip_add:
-#ifdef REGEXP_DEBUG
- nfa_set_code(state->c);
- fprintf(log_fd,
- "> Not adding state %d to list %d. char %d: %s pim: %s has_pim: %d found: %d\n",
- abs(state->id), l->id, state->c, code,
- pim == NULL ? "NULL" : "yes", l->has_pim, found);
-#endif
- depth--;
- return subs;
- }
- }
-
- // Do not add the state again when it exists with the same
- // positions.
- if (has_state_with_pos(l, state, subs, pim)) {
- goto skip_add;
- }
- }
-
- // When there are backreferences or PIMs the number of states may
- // be (a lot) bigger than anticipated.
- if (l->n == l->len) {
- const int newlen = l->len * 3 / 2 + 50;
- const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
-
- if ((long)(newsize >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- depth--;
- return NULL;
- }
- if (subs != &temp_subs) {
- // "subs" may point into the current array, need to make a
- // copy before it becomes invalid.
- copy_sub(&temp_subs.norm, &subs->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&temp_subs.synt, &subs->synt);
- }
- subs = &temp_subs;
- }
-
- nfa_thread_T *const newt = xrealloc(l->t, newsize);
- l->t = newt;
- l->len = newlen;
- }
-
- // add the state to the list
- state->lastlist[nfa_ll_index] = l->id;
- thread = &l->t[l->n++];
- thread->state = state;
- if (pim == NULL) {
- thread->pim.result = NFA_PIM_UNUSED;
- } else {
- copy_pim(&thread->pim, pim);
- l->has_pim = true;
- }
- copy_sub(&thread->subs.norm, &subs->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&thread->subs.synt, &subs->synt);
- }
-#ifdef REGEXP_DEBUG
- report_state("Adding", &thread->subs.norm, state, l->id, pim);
- did_print = true;
-#endif
- }
-
-#ifdef REGEXP_DEBUG
- if (!did_print) {
- report_state("Processing", &subs->norm, state, l->id, pim);
- }
-#endif
- switch (state->c) {
- case NFA_MATCH:
- break;
-
- case NFA_SPLIT:
- // order matters here
- subs = addstate(l, state->out, subs, pim, off_arg);
- subs = addstate(l, state->out1, subs, pim, off_arg);
- break;
-
- case NFA_EMPTY:
- case NFA_NOPEN:
- case NFA_NCLOSE:
- subs = addstate(l, state->out, subs, pim, off_arg);
- break;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZSTART:
- if (state->c == NFA_ZSTART) {
- subidx = 0;
- sub = &subs->norm;
- } else if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560
- subidx = state->c - NFA_ZOPEN;
- sub = &subs->synt;
- } else {
- subidx = state->c - NFA_MOPEN;
- sub = &subs->norm;
- }
-
- // avoid compiler warnings
- save_ptr = NULL;
- CLEAR_FIELD(save_multipos);
-
- // Set the position (with "off" added) in the subexpression. Save
- // and restore it when it was in use. Otherwise fill any gap.
- if (REG_MULTI) {
- if (subidx < sub->in_use) {
- save_multipos = sub->list.multi[subidx];
- save_in_use = -1;
- } else {
- save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; i++) {
- sub->list.multi[i].start_lnum = -1;
- sub->list.multi[i].end_lnum = -1;
- }
- sub->in_use = subidx + 1;
- }
- if (off == -1) {
- sub->list.multi[subidx].start_lnum = rex.lnum + 1;
- sub->list.multi[subidx].start_col = 0;
- } else {
- sub->list.multi[subidx].start_lnum = rex.lnum;
- sub->list.multi[subidx].start_col =
- (colnr_T)(rex.input - rex.line + off);
- }
- sub->list.multi[subidx].end_lnum = -1;
- } else {
- if (subidx < sub->in_use) {
- save_ptr = sub->list.line[subidx].start;
- save_in_use = -1;
- } else {
- save_in_use = sub->in_use;
- for (i = sub->in_use; i < subidx; i++) {
- sub->list.line[i].start = NULL;
- sub->list.line[i].end = NULL;
- }
- sub->in_use = subidx + 1;
- }
- sub->list.line[subidx].start = rex.input + off;
- }
-
- subs = addstate(l, state->out, subs, pim, off_arg);
- if (subs == NULL) {
- break;
- }
- // "subs" may have changed, need to set "sub" again.
- if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560
- sub = &subs->synt;
- } else {
- sub = &subs->norm;
- }
-
- if (save_in_use == -1) {
- if (REG_MULTI) {
- sub->list.multi[subidx] = save_multipos;
- } else {
- sub->list.line[subidx].start = save_ptr;
- }
- } else {
- sub->in_use = save_in_use;
- }
- break;
-
- case NFA_MCLOSE:
- if (rex.nfa_has_zend
- && (REG_MULTI
- ? subs->norm.list.multi[0].end_lnum >= 0
- : subs->norm.list.line[0].end != NULL)) {
- // Do not overwrite the position set by \ze.
- subs = addstate(l, state->out, subs, pim, off_arg);
- break;
- }
- FALLTHROUGH;
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_ZEND:
- if (state->c == NFA_ZEND) {
- subidx = 0;
- sub = &subs->norm;
- } else if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560
- subidx = state->c - NFA_ZCLOSE;
- sub = &subs->synt;
- } else {
- subidx = state->c - NFA_MCLOSE;
- sub = &subs->norm;
- }
-
- // We don't fill in gaps here, there must have been an MOPEN that
- // has done that.
- save_in_use = sub->in_use;
- if (sub->in_use <= subidx) {
- sub->in_use = subidx + 1;
- }
- if (REG_MULTI) {
- save_multipos = sub->list.multi[subidx];
- if (off == -1) {
- sub->list.multi[subidx].end_lnum = rex.lnum + 1;
- sub->list.multi[subidx].end_col = 0;
- } else {
- sub->list.multi[subidx].end_lnum = rex.lnum;
- sub->list.multi[subidx].end_col =
- (colnr_T)(rex.input - rex.line + off);
- }
- // avoid compiler warnings
- save_ptr = NULL;
- } else {
- save_ptr = sub->list.line[subidx].end;
- sub->list.line[subidx].end = rex.input + off;
- // avoid compiler warnings
- CLEAR_FIELD(save_multipos);
- }
-
- subs = addstate(l, state->out, subs, pim, off_arg);
- if (subs == NULL) {
- break;
- }
- // "subs" may have changed, need to set "sub" again.
- if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560
- sub = &subs->synt;
- } else {
- sub = &subs->norm;
- }
-
- if (REG_MULTI) {
- sub->list.multi[subidx] = save_multipos;
- } else {
- sub->list.line[subidx].end = save_ptr;
- }
- sub->in_use = save_in_use;
- break;
- }
- depth--;
- return subs;
-}
-
-/// Like addstate(), but the new state(s) are put at position "*ip".
-/// Used for zero-width matches, next state to use is the added one.
-/// This makes sure the order of states to be tried does not change, which
-/// matters for alternatives.
-///
-/// @param l runtime state list
-/// @param state state to update
-/// @param subs pointers to subexpressions
-/// @param pim postponed look-behind match
-static regsubs_T *addstate_here(nfa_list_T *l, nfa_state_T *state, regsubs_T *subs, nfa_pim_T *pim,
- int *ip)
- FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- int tlen = l->n;
- int count;
- int listidx = *ip;
-
- // First add the state(s) at the end, so that we know how many there are.
- // Pass the listidx as offset (avoids adding another argument to
- // addstate()).
- regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET);
- if (r == NULL) {
- return NULL;
- }
-
- // when "*ip" was at the end of the list, nothing to do
- if (listidx + 1 == tlen) {
- return r;
- }
-
- // re-order to put the new state at the current position
- count = l->n - tlen;
- if (count == 0) {
- return r; // no state got added
- }
- if (count == 1) {
- // overwrite the current state
- l->t[listidx] = l->t[l->n - 1];
- } else if (count > 1) {
- if (l->n + count - 1 >= l->len) {
- // not enough space to move the new states, reallocate the list
- // and move the states to the right position
- const int newlen = l->len * 3 / 2 + 50;
- const size_t newsize = (size_t)newlen * sizeof(nfa_thread_T);
-
- if ((long)(newsize >> 10) >= p_mmp) {
- emsg(_(e_maxmempat));
- return NULL;
- }
- nfa_thread_T *const newl = xmalloc(newsize);
- l->len = newlen;
- memmove(&(newl[0]),
- &(l->t[0]),
- sizeof(nfa_thread_T) * (size_t)listidx);
- memmove(&(newl[listidx]),
- &(l->t[l->n - count]),
- sizeof(nfa_thread_T) * (size_t)count);
- memmove(&(newl[listidx + count]),
- &(l->t[listidx + 1]),
- sizeof(nfa_thread_T) * (size_t)(l->n - count - listidx - 1));
- xfree(l->t);
- l->t = newl;
- } else {
- // make space for new states, then move them from the
- // end to the current position
- memmove(&(l->t[listidx + count]),
- &(l->t[listidx + 1]),
- sizeof(nfa_thread_T) * (size_t)(l->n - listidx - 1));
- memmove(&(l->t[listidx]),
- &(l->t[l->n - 1]),
- sizeof(nfa_thread_T) * (size_t)count);
- }
- }
- l->n--;
- *ip = listidx - 1;
-
- return r;
-}
-
-// Check character class "class" against current character c.
-static int check_char_class(int class, int c)
-{
- switch (class) {
- case NFA_CLASS_ALNUM:
- if (c >= 1 && c < 128 && isalnum(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_ALPHA:
- if (c >= 1 && c < 128 && isalpha(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_BLANK:
- if (c == ' ' || c == '\t') {
- return OK;
- }
- break;
- case NFA_CLASS_CNTRL:
- if (c >= 1 && c <= 127 && iscntrl(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_DIGIT:
- if (ascii_isdigit(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_GRAPH:
- if (c >= 1 && c <= 127 && isgraph(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_LOWER:
- if (mb_islower(c) && c != 170 && c != 186) {
- return OK;
- }
- break;
- case NFA_CLASS_PRINT:
- if (vim_isprintc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_PUNCT:
- if (c >= 1 && c < 128 && ispunct(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_SPACE:
- if ((c >= 9 && c <= 13) || (c == ' ')) {
- return OK;
- }
- break;
- case NFA_CLASS_UPPER:
- if (mb_isupper(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_XDIGIT:
- if (ascii_isxdigit(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_TAB:
- if (c == '\t') {
- return OK;
- }
- break;
- case NFA_CLASS_RETURN:
- if (c == '\r') {
- return OK;
- }
- break;
- case NFA_CLASS_BACKSPACE:
- if (c == '\b') {
- return OK;
- }
- break;
- case NFA_CLASS_ESCAPE:
- if (c == ESC) {
- return OK;
- }
- break;
- case NFA_CLASS_IDENT:
- if (vim_isIDc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_KEYWORD:
- if (reg_iswordc(c)) {
- return OK;
- }
- break;
- case NFA_CLASS_FNAME:
- if (vim_isfilec(c)) {
- return OK;
- }
- break;
-
- default:
- // should not be here :P
- siemsg(_(e_ill_char_class), (int64_t)class);
- return FAIL;
- }
- return FAIL;
-}
-
-/// Check for a match with subexpression "subidx".
-///
-/// @param sub pointers to subexpressions
-/// @param bytelen out: length of match in bytes
-///
-/// @return true if it matches.
-static int match_backref(regsub_T *sub, int subidx, int *bytelen)
-{
- int len;
-
- if (sub->in_use <= subidx) {
-retempty:
- // backref was not set, match an empty string
- *bytelen = 0;
- return true;
- }
-
- if (REG_MULTI) {
- if (sub->list.multi[subidx].start_lnum < 0
- || sub->list.multi[subidx].end_lnum < 0) {
- goto retempty;
- }
- if (sub->list.multi[subidx].start_lnum == rex.lnum
- && sub->list.multi[subidx].end_lnum == rex.lnum) {
- len = sub->list.multi[subidx].end_col
- - sub->list.multi[subidx].start_col;
- if (cstrncmp((char *)rex.line + sub->list.multi[subidx].start_col,
- (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- } else {
- if (match_with_backref(sub->list.multi[subidx].start_lnum,
- sub->list.multi[subidx].start_col,
- sub->list.multi[subidx].end_lnum,
- sub->list.multi[subidx].end_col,
- bytelen) == RA_MATCH) {
- return true;
- }
- }
- } else {
- if (sub->list.line[subidx].start == NULL
- || sub->list.line[subidx].end == NULL) {
- goto retempty;
- }
- len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start);
- if (cstrncmp((char *)sub->list.line[subidx].start, (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- }
- return false;
-}
-
-/// Check for a match with \z subexpression "subidx".
-///
-/// @param bytelen out: length of match in bytes
-///
-/// @return true if it matches.
-static int match_zref(int subidx, int *bytelen)
-{
- int len;
-
- cleanup_zsubexpr();
- if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
- // backref was not set, match an empty string
- *bytelen = 0;
- return true;
- }
-
- len = (int)strlen((char *)re_extmatch_in->matches[subidx]);
- if (cstrncmp((char *)re_extmatch_in->matches[subidx], (char *)rex.input, &len) == 0) {
- *bytelen = len;
- return true;
- }
- return false;
-}
-
-// Save list IDs for all NFA states of "prog" into "list".
-// Also reset the IDs to zero.
-// Only used for the recursive value lastlist[1].
-static void nfa_save_listids(nfa_regprog_T *prog, int *list)
-{
- int i;
- nfa_state_T *p;
-
- // Order in the list is reverse, it's a bit faster that way.
- p = &prog->state[0];
- for (i = prog->nstate; --i >= 0;) {
- list[i] = p->lastlist[1];
- p->lastlist[1] = 0;
- p++;
- }
-}
-
-// Restore list IDs from "list" to all NFA states.
-static void nfa_restore_listids(nfa_regprog_T *prog, int *list)
-{
- int i;
- nfa_state_T *p;
-
- p = &prog->state[0];
- for (i = prog->nstate; --i >= 0;) {
- p->lastlist[1] = list[i];
- p++;
- }
-}
-
-static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
-{
- if (op == 1) {
- return pos > val;
- }
- if (op == 2) {
- return pos < val;
- }
- return val == pos;
-}
-
-// Recursively call nfa_regmatch()
-// "pim" is NULL or contains info about a Postponed Invisible Match (start
-// position).
-static int recursive_regmatch(nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
- regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
- FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
-{
- const int save_reginput_col = (int)(rex.input - rex.line);
- const int save_reglnum = rex.lnum;
- const int save_nfa_match = nfa_match;
- const int save_nfa_listid = rex.nfa_listid;
- save_se_T *const save_nfa_endp = nfa_endp;
- save_se_T endpos;
- save_se_T *endposp = NULL;
- int need_restore = false;
-
- if (pim != NULL) {
- // start at the position where the postponed match was
- if (REG_MULTI) {
- rex.input = rex.line + pim->end.pos.col;
- } else {
- rex.input = pim->end.ptr;
- }
- }
-
- if (state->c == NFA_START_INVISIBLE_BEFORE
- || state->c == NFA_START_INVISIBLE_BEFORE_FIRST
- || state->c == NFA_START_INVISIBLE_BEFORE_NEG
- || state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
- // The recursive match must end at the current position. When "pim" is
- // not NULL it specifies the current position.
- endposp = &endpos;
- if (REG_MULTI) {
- if (pim == NULL) {
- endpos.se_u.pos.col = (int)(rex.input - rex.line);
- endpos.se_u.pos.lnum = rex.lnum;
- } else {
- endpos.se_u.pos = pim->end.pos;
- }
- } else {
- if (pim == NULL) {
- endpos.se_u.ptr = rex.input;
- } else {
- endpos.se_u.ptr = pim->end.ptr;
- }
- }
-
- // Go back the specified number of bytes, or as far as the
- // start of the previous line, to try matching "\@<=" or
- // not matching "\@<!". This is very inefficient, limit the number of
- // bytes if possible.
- if (state->val <= 0) {
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(--rex.lnum);
- if (rex.line == NULL) {
- // can't go before the first line
- rex.line = (uint8_t *)reg_getline(++rex.lnum);
- }
- }
- rex.input = rex.line;
- } else {
- if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
- // Not enough bytes in this line, go to end of
- // previous line.
- rex.line = (uint8_t *)reg_getline(--rex.lnum);
- if (rex.line == NULL) {
- // can't go before the first line
- rex.line = (uint8_t *)reg_getline(++rex.lnum);
- rex.input = rex.line;
- } else {
- rex.input = rex.line + strlen((char *)rex.line);
- }
- }
- if ((int)(rex.input - rex.line) >= state->val) {
- rex.input -= state->val;
- rex.input -= utf_head_off((char *)rex.line, (char *)rex.input);
- } else {
- rex.input = rex.line;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- if (log_fd != stderr) {
- fclose(log_fd);
- }
- log_fd = NULL;
-#endif
- // Have to clear the lastlist field of the NFA nodes, so that
- // nfa_regmatch() and addstate() can run properly after recursion.
- if (nfa_ll_index == 1) {
- // Already calling nfa_regmatch() recursively. Save the lastlist[1]
- // values and clear them.
- if (*listids == NULL || *listids_len < prog->nstate) {
- xfree(*listids);
- *listids = xmalloc(sizeof(**listids) * (size_t)prog->nstate);
- *listids_len = prog->nstate;
- }
- nfa_save_listids(prog, *listids);
- need_restore = true;
- // any value of rex.nfa_listid will do
- } else {
- // First recursive nfa_regmatch() call, switch to the second lastlist
- // entry. Make sure rex.nfa_listid is different from a previous
- // recursive call, because some states may still have this ID.
- nfa_ll_index++;
- if (rex.nfa_listid <= rex.nfa_alt_listid) {
- rex.nfa_listid = rex.nfa_alt_listid;
- }
- }
-
- // Call nfa_regmatch() to check if the current concat matches at this
- // position. The concat ends with the node NFA_END_INVISIBLE
- nfa_endp = endposp;
- const int result = nfa_regmatch(prog, state->out, submatch, m);
-
- if (need_restore) {
- nfa_restore_listids(prog, *listids);
- } else {
- nfa_ll_index--;
- rex.nfa_alt_listid = rex.nfa_listid;
- }
-
- // restore position in input text
- rex.lnum = save_reglnum;
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- }
- rex.input = rex.line + save_reginput_col;
- if (result != NFA_TOO_EXPENSIVE) {
- nfa_match = save_nfa_match;
- rex.nfa_listid = save_nfa_listid;
- }
- nfa_endp = save_nfa_endp;
-
-#ifdef REGEXP_DEBUG
- open_debug_log(result);
-#endif
-
- return result;
-}
-
-// Estimate the chance of a match with "state" failing.
-// empty match: 0
-// NFA_ANY: 1
-// specific character: 99
-static int failure_chance(nfa_state_T *state, int depth)
-{
- int c = state->c;
- int l, r;
-
- // detect looping
- if (depth > 4) {
- return 1;
- }
-
- switch (c) {
- case NFA_SPLIT:
- if (state->out->c == NFA_SPLIT || state->out1->c == NFA_SPLIT) {
- // avoid recursive stuff
- return 1;
- }
- // two alternatives, use the lowest failure chance
- l = failure_chance(state->out, depth + 1);
- r = failure_chance(state->out1, depth + 1);
- return l < r ? l : r;
-
- case NFA_ANY:
- // matches anything, unlikely to fail
- return 1;
-
- case NFA_MATCH:
- case NFA_MCLOSE:
- case NFA_ANY_COMPOSING:
- // empty match works always
- return 0;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
- case NFA_START_PATTERN:
- // recursive regmatch is expensive, use low failure chance
- return 5;
-
- case NFA_BOL:
- case NFA_EOL:
- case NFA_BOF:
- case NFA_EOF:
- case NFA_NEWL:
- return 99;
-
- case NFA_BOW:
- case NFA_EOW:
- return 90;
-
- case NFA_MOPEN:
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_ZCLOSE:
- case NFA_ZCLOSE1:
- case NFA_ZCLOSE2:
- case NFA_ZCLOSE3:
- case NFA_ZCLOSE4:
- case NFA_ZCLOSE5:
- case NFA_ZCLOSE6:
- case NFA_ZCLOSE7:
- case NFA_ZCLOSE8:
- case NFA_ZCLOSE9:
- case NFA_NOPEN:
- case NFA_MCLOSE1:
- case NFA_MCLOSE2:
- case NFA_MCLOSE3:
- case NFA_MCLOSE4:
- case NFA_MCLOSE5:
- case NFA_MCLOSE6:
- case NFA_MCLOSE7:
- case NFA_MCLOSE8:
- case NFA_MCLOSE9:
- case NFA_NCLOSE:
- return failure_chance(state->out, depth + 1);
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- // backreferences don't match in many places
- return 94;
-
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- case NFA_COL_GT:
- case NFA_COL_LT:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT:
- case NFA_MARK_GT:
- case NFA_MARK_LT:
- case NFA_VISUAL:
- // before/after positions don't match very often
- return 85;
-
- case NFA_LNUM:
- return 90;
-
- case NFA_CURSOR:
- case NFA_COL:
- case NFA_VCOL:
- case NFA_MARK:
- // specific positions rarely match
- return 98;
-
- case NFA_COMPOSING:
- return 95;
-
- default:
- if (c > 0) {
- // character match fails often
- return 95;
- }
- }
-
- // something else, includes character classes
- return 50;
-}
-
-// Skip until the char "c" we know a match must start with.
-static int skip_to_start(int c, colnr_T *colp)
-{
- const uint8_t *const s = (uint8_t *)cstrchr((char *)rex.line + *colp, c);
- if (s == NULL) {
- return FAIL;
- }
- *colp = (int)(s - rex.line);
- return OK;
-}
-
-// Check for a match with match_text.
-// Called after skip_to_start() has found regstart.
-// Returns zero for no match, 1 for a match.
-static long find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
-{
-#define PTR2LEN(x) utf_ptr2len(x)
-
- colnr_T col = *startcol;
- int regstart_len = PTR2LEN((char *)rex.line + col);
-
- for (;;) {
- bool match = true;
- uint8_t *s1 = match_text;
- uint8_t *s2 = rex.line + col + regstart_len; // skip regstart
- while (*s1) {
- int c1_len = PTR2LEN((char *)s1);
- int c1 = utf_ptr2char((char *)s1);
- int c2_len = PTR2LEN((char *)s2);
- int c2 = utf_ptr2char((char *)s2);
-
- if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
- || c1_len != c2_len) {
- match = false;
- break;
- }
- s1 += c1_len;
- s2 += c2_len;
- }
- if (match
- // check that no composing char follows
- && !utf_iscomposing(utf_ptr2char((char *)s2))) {
- cleanup_subexpr();
- if (REG_MULTI) {
- rex.reg_startpos[0].lnum = rex.lnum;
- rex.reg_startpos[0].col = col;
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (colnr_T)(s2 - rex.line);
- } else {
- rex.reg_startp[0] = rex.line + col;
- rex.reg_endp[0] = s2;
- }
- *startcol = col;
- return 1L;
- }
-
- // Try finding regstart after the current match.
- col += regstart_len; // skip regstart
- if (skip_to_start(regstart, &col) == FAIL) {
- break;
- }
- }
-
- *startcol = col;
- return 0L;
-
-#undef PTR2LEN
-}
-
-static int nfa_did_time_out(void)
-{
- if (nfa_time_limit != NULL && profile_passed_limit(*nfa_time_limit)) {
- if (nfa_timed_out != NULL) {
- *nfa_timed_out = true;
- }
- return true;
- }
- return false;
-}
-
-/// Main matching routine.
-///
-/// Run NFA to determine whether it matches rex.input.
-///
-/// When "nfa_endp" is not NULL it is a required end-of-match position.
-///
-/// Return true if there is a match, false if there is no match,
-/// NFA_TOO_EXPENSIVE if we end up with too many states.
-/// When there is a match "submatch" contains the positions.
-///
-/// Note: Caller must ensure that: start != NULL.
-static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *submatch, regsubs_T *m)
- FUNC_ATTR_NONNULL_ARG(1, 2, 4)
-{
- int result = false;
- int flag = 0;
- bool go_to_nextline = false;
- nfa_thread_T *t;
- nfa_list_T list[2];
- int listidx;
- nfa_list_T *thislist;
- nfa_list_T *nextlist;
- int *listids = NULL;
- int listids_len = 0;
- nfa_state_T *add_state;
- bool add_here;
- int add_count;
- int add_off = 0;
- int toplevel = start->c == NFA_MOPEN;
- regsubs_T *r;
- // Some patterns may take a long time to match, especially when using
- // recursive_regmatch(). Allow interrupting them with CTRL-C.
- fast_breakcheck();
- if (got_int) {
- return false;
- }
- if (nfa_did_time_out()) {
- return false;
- }
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a");
-
- if (debug == NULL) {
- semsg("(NFA) COULD NOT OPEN %s!", NFA_REGEXP_DEBUG_LOG);
- return false;
- }
-#endif
- nfa_match = false;
-
- // Allocate memory for the lists of nodes.
- size_t size = (size_t)(prog->nstate + 1) * sizeof(nfa_thread_T);
- list[0].t = xmalloc(size);
- list[0].len = prog->nstate + 1;
- list[1].t = xmalloc(size);
- list[1].len = prog->nstate + 1;
-
-#ifdef REGEXP_DEBUG
- log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (log_fd == NULL) {
- emsg(_(e_log_open_failed));
- log_fd = stderr;
- }
- fprintf(log_fd, "**********************************\n");
- nfa_set_code(start->c);
- fprintf(log_fd, " RUNNING nfa_regmatch() starting with state %d, code %s\n",
- abs(start->id), code);
- fprintf(log_fd, "**********************************\n");
-#endif
-
- thislist = &list[0];
- thislist->n = 0;
- thislist->has_pim = false;
- nextlist = &list[1];
- nextlist->n = 0;
- nextlist->has_pim = false;
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "(---) STARTSTATE first\n");
-#endif
- thislist->id = rex.nfa_listid + 1;
-
- // Inline optimized code for addstate(thislist, start, m, 0) if we know
- // it's the first MOPEN.
- if (toplevel) {
- if (REG_MULTI) {
- m->norm.list.multi[0].start_lnum = rex.lnum;
- m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
- m->norm.orig_start_col = m->norm.list.multi[0].start_col;
- } else {
- m->norm.list.line[0].start = rex.input;
- }
- m->norm.in_use = 1;
- r = addstate(thislist, start->out, m, NULL, 0);
- } else {
- r = addstate(thislist, start, m, NULL, 0);
- }
- if (r == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
-
-#define ADD_STATE_IF_MATCH(state) \
- if (result) { \
- add_state = (state)->out; \
- add_off = clen; \
- }
-
- // Run for each character.
- for (;;) {
- int curc = utf_ptr2char((char *)rex.input);
- int clen = utfc_ptr2len((char *)rex.input);
- if (curc == NUL) {
- clen = 0;
- go_to_nextline = false;
- }
-
- // swap lists
- thislist = &list[flag];
- nextlist = &list[flag ^= 1];
- nextlist->n = 0; // clear nextlist
- nextlist->has_pim = false;
- rex.nfa_listid++;
- if (prog->re_engine == AUTOMATIC_ENGINE
- && (rex.nfa_listid >= NFA_MAX_STATES)) {
- // Too many states, retry with old engine.
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
-
- thislist->id = rex.nfa_listid;
- nextlist->id = rex.nfa_listid + 1;
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "------------------------------------------\n");
- fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
- fprintf(log_fd,
- ">>> Advanced one character... Current char is %c (code %d) \n",
- curc,
- (int)curc);
- fprintf(log_fd, ">>> Thislist has %d states available: ", thislist->n);
- {
- int i;
-
- for (i = 0; i < thislist->n; i++) {
- fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
- }
- }
- fprintf(log_fd, "\n");
-#endif
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- fprintf(debug, "\n-------------------\n");
-#endif
- // If the state lists are empty we can stop.
- if (thislist->n == 0) {
- break;
- }
-
- // compute nextlist
- for (listidx = 0; listidx < thislist->n; listidx++) {
- // If the list gets very long there probably is something wrong.
- // At least allow interrupting with CTRL-C.
- fast_breakcheck();
- if (got_int) {
- break;
- }
- if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
- nfa_time_count = 0;
- if (nfa_did_time_out()) {
- break;
- }
- }
- t = &thislist->t[listidx];
-
-#ifdef NFA_REGEXP_DEBUG_LOG
- nfa_set_code(t->state->c);
- fprintf(debug, "%s, ", code);
-#endif
-#ifdef REGEXP_DEBUG
- {
- int col;
-
- if (t->subs.norm.in_use <= 0) {
- col = -1;
- } else if (REG_MULTI) {
- col = t->subs.norm.list.multi[0].start_col;
- } else {
- col = (int)(t->subs.norm.list.line[0].start - rex.line);
- }
- nfa_set_code(t->state->c);
- fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n",
- abs(t->state->id), (int)t->state->c, code, col,
- pim_info(&t->pim));
- }
-#endif
-
- // Handle the possible codes of the current state.
- // The most important is NFA_MATCH.
- add_state = NULL;
- add_here = false;
- add_count = 0;
- switch (t->state->c) {
- case NFA_MATCH:
- // If the match is not at the start of the line, ends before a
- // composing characters and rex.reg_icombine is not set, that
- // is not really a match.
- if (!rex.reg_icombine
- && rex.input != rex.line
- && utf_iscomposing(curc)) {
- break;
- }
- nfa_match = true;
- copy_sub(&submatch->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&submatch->synt, &t->subs.synt);
- }
-#ifdef REGEXP_DEBUG
- log_subsexpr(&t->subs);
-#endif
- // Found the left-most longest match, do not look at any other
- // states at this position. When the list of states is going
- // to be empty quit without advancing, so that "rex.input" is
- // correct.
- if (nextlist->n == 0) {
- clen = 0;
- }
- goto nextchar;
-
- case NFA_END_INVISIBLE:
- case NFA_END_INVISIBLE_NEG:
- case NFA_END_PATTERN:
- // This is only encountered after a NFA_START_INVISIBLE or
- // NFA_START_INVISIBLE_BEFORE node.
- // They surround a zero-width group, used with "\@=", "\&",
- // "\@!", "\@<=" and "\@<!".
- // If we got here, it means that the current "invisible" group
- // finished successfully, so return control to the parent
- // nfa_regmatch(). For a look-behind match only when it ends
- // in the position in "nfa_endp".
- // Submatches are stored in *m, and used in the parent call.
-#ifdef REGEXP_DEBUG
- if (nfa_endp != NULL) {
- if (REG_MULTI) {
- fprintf(log_fd,
- "Current lnum: %d, endp lnum: %d;"
- " current col: %d, endp col: %d\n",
- (int)rex.lnum,
- (int)nfa_endp->se_u.pos.lnum,
- (int)(rex.input - rex.line),
- nfa_endp->se_u.pos.col);
- } else {
- fprintf(log_fd, "Current col: %d, endp col: %d\n",
- (int)(rex.input - rex.line),
- (int)(nfa_endp->se_u.ptr - rex.input));
- }
- }
-#endif
- // If "nfa_endp" is set it's only a match if it ends at
- // "nfa_endp"
- if (nfa_endp != NULL
- && (REG_MULTI
- ? (rex.lnum != nfa_endp->se_u.pos.lnum
- || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col)
- : rex.input != nfa_endp->se_u.ptr)) {
- break;
- }
- // do not set submatches for \@!
- if (t->state->c != NFA_END_INVISIBLE_NEG) {
- copy_sub(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub(&m->synt, &t->subs.synt);
- }
- }
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "Match found:\n");
- log_subsexpr(m);
-#endif
- nfa_match = true;
- // See comment above at "goto nextchar".
- if (nextlist->n == 0) {
- clen = 0;
- }
- goto nextchar;
-
- case NFA_START_INVISIBLE:
- case NFA_START_INVISIBLE_FIRST:
- case NFA_START_INVISIBLE_NEG:
- case NFA_START_INVISIBLE_NEG_FIRST:
- case NFA_START_INVISIBLE_BEFORE:
- case NFA_START_INVISIBLE_BEFORE_FIRST:
- case NFA_START_INVISIBLE_BEFORE_NEG:
- case NFA_START_INVISIBLE_BEFORE_NEG_FIRST:
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "Failure chance invisible: %d, what follows: %d\n",
- failure_chance(t->state->out, 0),
- failure_chance(t->state->out1->out, 0));
-#endif
- // Do it directly if there already is a PIM or when
- // nfa_postprocess() detected it will work better.
- if (t->pim.result != NFA_PIM_UNUSED
- || t->state->c == NFA_START_INVISIBLE_FIRST
- || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || t->state->c == NFA_START_INVISIBLE_BEFORE_FIRST
- || t->state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
- int in_use = m->norm.in_use;
-
- // Copy submatch info for the recursive call, opposite
- // of what happens on success below.
- copy_sub_off(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&m->synt, &t->subs.synt);
- }
- // First try matching the invisible match, then what
- // follows.
- result = recursive_regmatch(t->state, NULL, prog, submatch, m,
- &listids, &listids_len);
- if (result == NFA_TOO_EXPENSIVE) {
- nfa_match = result;
- goto theend;
- }
-
- // for \@! and \@<! it is a match when the result is
- // false
- if (result != (t->state->c == NFA_START_INVISIBLE_NEG
- || t->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || t->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || t->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &m->synt);
- }
- // If the pattern has \ze and it matched in the
- // sub pattern, use it.
- copy_ze_off(&t->subs.norm, &m->norm);
-
- // t->state->out1 is the corresponding
- // END_INVISIBLE node; Add its out to the current
- // list (zero-width match).
- add_here = true;
- add_state = t->state->out1->out;
- }
- m->norm.in_use = in_use;
- } else {
- nfa_pim_T pim;
-
- // First try matching what follows. Only if a match
- // is found verify the invisible match matches. Add a
- // nfa_pim_T to the following states, it contains info
- // about the invisible match.
- pim.state = t->state;
- pim.result = NFA_PIM_TODO;
- pim.subs.norm.in_use = 0;
- pim.subs.synt.in_use = 0;
- if (REG_MULTI) {
- pim.end.pos.col = (int)(rex.input - rex.line);
- pim.end.pos.lnum = rex.lnum;
- } else {
- pim.end.ptr = rex.input;
- }
- // t->state->out1 is the corresponding END_INVISIBLE
- // node; Add its out to the current list (zero-width
- // match).
- if (addstate_here(thislist, t->state->out1->out, &t->subs,
- &pim, &listidx) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- break;
-
- case NFA_START_PATTERN: {
- nfa_state_T *skip = NULL;
-#ifdef REGEXP_DEBUG
- int skip_lid = 0;
-#endif
-
- // There is no point in trying to match the pattern if the
- // output state is not going to be added to the list.
- if (state_in_list(nextlist, t->state->out1->out, &t->subs)) {
- skip = t->state->out1->out;
-#ifdef REGEXP_DEBUG
- skip_lid = nextlist->id;
-#endif
- } else if (state_in_list(nextlist,
- t->state->out1->out->out, &t->subs)) {
- skip = t->state->out1->out->out;
-#ifdef REGEXP_DEBUG
- skip_lid = nextlist->id;
-#endif
- } else if (state_in_list(thislist,
- t->state->out1->out->out, &t->subs)) {
- skip = t->state->out1->out->out;
-#ifdef REGEXP_DEBUG
- skip_lid = thislist->id;
-#endif
- }
- if (skip != NULL) {
-#ifdef REGEXP_DEBUG
- nfa_set_code(skip->c);
- fprintf(log_fd,
- "> Not trying to match pattern, output state %d is already in list %d. char %d: %s\n", // NOLINT(whitespace/line_length)
- abs(skip->id), skip_lid, skip->c, code);
-#endif
- break;
- }
- // Copy submatch info to the recursive call, opposite of what
- // happens afterwards.
- copy_sub_off(&m->norm, &t->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&m->synt, &t->subs.synt);
- }
-
- // First try matching the pattern.
- result = recursive_regmatch(t->state, NULL, prog, submatch, m,
- &listids, &listids_len);
- if (result == NFA_TOO_EXPENSIVE) {
- nfa_match = result;
- goto theend;
- }
- if (result) {
- int bytelen;
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_START_PATTERN matches:\n");
- log_subsexpr(m);
-#endif
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &m->synt);
- }
- // Now we need to skip over the matched text and then
- // continue with what follows.
- if (REG_MULTI) {
- // TODO(RE): multi-line match
- bytelen = m->norm.list.multi[0].end_col
- - (int)(rex.input - rex.line);
- } else {
- bytelen = (int)(m->norm.list.line[0].end - rex.input);
- }
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_START_PATTERN length: %d\n", bytelen);
-#endif
- if (bytelen == 0) {
- // empty match, output of corresponding
- // NFA_END_PATTERN/NFA_SKIP to be used at current
- // position
- add_here = true;
- add_state = t->state->out1->out->out;
- } else if (bytelen <= clen) {
- // match current character, output of corresponding
- // NFA_END_PATTERN to be used at next position.
- add_state = t->state->out1->out->out;
- add_off = clen;
- } else {
- // skip over the matched characters, set character
- // count in NFA_SKIP
- add_state = t->state->out1->out;
- add_off = bytelen;
- add_count = bytelen - clen;
- }
- }
- break;
- }
-
- case NFA_BOL:
- if (rex.input == rex.line) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOL:
- if (curc == NUL) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_BOW:
- result = true;
-
- if (curc == NUL) {
- result = false;
- } else {
- int this_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- if (this_class <= 1) {
- result = false;
- } else if (reg_prev_class() == this_class) {
- result = false;
- }
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOW:
- result = true;
- if (rex.input == rex.line) {
- result = false;
- } else {
- int this_class, prev_class;
-
- // Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab((char *)rex.input, rex.reg_buf->b_chartab);
- prev_class = reg_prev_class();
- if (this_class == prev_class
- || prev_class == 0 || prev_class == 1) {
- result = false;
- }
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_BOF:
- if (rex.lnum == 0 && rex.input == rex.line
- && (!REG_MULTI || rex.reg_firstlnum == 1)) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_EOF:
- if (rex.lnum == rex.reg_maxline && curc == NUL) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_COMPOSING: {
- int mc = curc;
- int len = 0;
- nfa_state_T *end;
- nfa_state_T *sta;
- int cchars[MAX_MCO];
- int ccount = 0;
- int j;
-
- sta = t->state->out;
- len = 0;
- if (utf_iscomposing(sta->c)) {
- // Only match composing character(s), ignore base
- // character. Used for ".{composing}" and "{composing}"
- // (no preceding character).
- len += utf_char2len(mc);
- }
- if (rex.reg_icombine && len == 0) {
- // If \Z was present, then ignore composing characters.
- // When ignoring the base character this always matches.
- if (sta->c != curc) {
- result = FAIL;
- } else {
- result = OK;
- }
- while (sta->c != NFA_END_COMPOSING) {
- sta = sta->out;
- }
- } else if (len > 0 || mc == sta->c) {
- // Check base character matches first, unless ignored.
- if (len == 0) {
- len += utf_char2len(mc);
- sta = sta->out;
- }
-
- // We don't care about the order of composing characters.
- // Get them into cchars[] first.
- while (len < clen) {
- mc = utf_ptr2char((char *)rex.input + len);
- cchars[ccount++] = mc;
- len += utf_char2len(mc);
- if (ccount == MAX_MCO) {
- break;
- }
- }
-
- // Check that each composing char in the pattern matches a
- // composing char in the text. We do not check if all
- // composing chars are matched.
- result = OK;
- while (sta->c != NFA_END_COMPOSING) {
- for (j = 0; j < ccount; j++) {
- if (cchars[j] == sta->c) {
- break;
- }
- }
- if (j == ccount) {
- result = FAIL;
- break;
- }
- sta = sta->out;
- }
- } else {
- result = FAIL;
- }
-
- end = t->state->out1; // NFA_END_COMPOSING
- ADD_STATE_IF_MATCH(end);
- break;
- }
-
- case NFA_NEWL:
- if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
- && rex.lnum <= rex.reg_maxline) {
- go_to_nextline = true;
- // Pass -1 for the offset, which means taking the position
- // at the start of the next line.
- add_state = t->state->out;
- add_off = -1;
- } else if (curc == '\n' && rex.reg_line_lbr) {
- // match \n as if it is an ordinary character
- add_state = t->state->out;
- add_off = 1;
- }
- break;
-
- case NFA_START_COLL:
- case NFA_START_NEG_COLL: {
- // What follows is a list of characters, until NFA_END_COLL.
- // One of them must match or none of them must match.
- nfa_state_T *state;
- int result_if_matched;
- int c1, c2;
-
- // Never match EOL. If it's part of the collection it is added
- // as a separate state with an OR.
- if (curc == NUL) {
- break;
- }
-
- state = t->state->out;
- result_if_matched = (t->state->c == NFA_START_COLL);
- for (;;) {
- if (state->c == NFA_END_COLL) {
- result = !result_if_matched;
- break;
- }
- if (state->c == NFA_RANGE_MIN) {
- c1 = state->val;
- state = state->out; // advance to NFA_RANGE_MAX
- c2 = state->val;
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "NFA_RANGE_MIN curc=%d c1=%d c2=%d\n",
- curc, c1, c2);
-#endif
- if (curc >= c1 && curc <= c2) {
- result = result_if_matched;
- break;
- }
- if (rex.reg_ic) {
- int curc_low = utf_fold(curc);
- int done = false;
-
- for (; c1 <= c2; c1++) {
- if (utf_fold(c1) == curc_low) {
- result = result_if_matched;
- done = true;
- break;
- }
- }
- if (done) {
- break;
- }
- }
- } else if (state->c < 0 ? check_char_class(state->c, curc)
- : (curc == state->c
- || (rex.reg_ic
- && utf_fold(curc) == utf_fold(state->c)))) {
- result = result_if_matched;
- break;
- }
- state = state->out;
- }
- if (result) {
- // next state is in out of the NFA_END_COLL, out1 of
- // START points to the END state
- add_state = t->state->out1->out;
- add_off = clen;
- }
- break;
- }
-
- case NFA_ANY:
- // Any char except '\0', (end of input) does not match.
- if (curc > 0) {
- add_state = t->state->out;
- add_off = clen;
- }
- break;
-
- case NFA_ANY_COMPOSING:
- // On a composing character skip over it. Otherwise do
- // nothing. Always matches.
- if (utf_iscomposing(curc)) {
- add_off = clen;
- } else {
- add_here = true;
- add_off = 0;
- }
- add_state = t->state->out;
- break;
-
- // Character classes like \a for alpha, \d for digit etc.
- case NFA_IDENT: // \i
- result = vim_isIDc(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SIDENT: // \I
- result = !ascii_isdigit(curc) && vim_isIDc(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_KWORD: // \k
- result = vim_iswordp_buf((char *)rex.input, rex.reg_buf);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SKWORD: // \K
- result = !ascii_isdigit(curc)
- && vim_iswordp_buf((char *)rex.input, rex.reg_buf);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_FNAME: // \f
- result = vim_isfilec(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SFNAME: // \F
- result = !ascii_isdigit(curc) && vim_isfilec(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_PRINT: // \p
- result = vim_isprintc(utf_ptr2char((char *)rex.input));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_SPRINT: // \P
- result = !ascii_isdigit(curc) && vim_isprintc(utf_ptr2char((char *)rex.input));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_WHITE: // \s
- result = ascii_iswhite(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NWHITE: // \S
- result = curc != NUL && !ascii_iswhite(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_DIGIT: // \d
- result = ri_digit(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NDIGIT: // \D
- result = curc != NUL && !ri_digit(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_HEX: // \x
- result = ri_hex(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NHEX: // \X
- result = curc != NUL && !ri_hex(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_OCTAL: // \o
- result = ri_octal(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NOCTAL: // \O
- result = curc != NUL && !ri_octal(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_WORD: // \w
- result = ri_word(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NWORD: // \W
- result = curc != NUL && !ri_word(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_HEAD: // \h
- result = ri_head(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NHEAD: // \H
- result = curc != NUL && !ri_head(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_ALPHA: // \a
- result = ri_alpha(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NALPHA: // \A
- result = curc != NUL && !ri_alpha(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_LOWER: // \l
- result = ri_lower(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NLOWER: // \L
- result = curc != NUL && !ri_lower(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_UPPER: // \u
- result = ri_upper(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NUPPER: // \U
- result = curc != NUL && !ri_upper(curc);
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_LOWER_IC: // [a-z]
- result = ri_lower(curc) || (rex.reg_ic && ri_upper(curc));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NLOWER_IC: // [^a-z]
- result = curc != NUL
- && !(ri_lower(curc) || (rex.reg_ic && ri_upper(curc)));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_UPPER_IC: // [A-Z]
- result = ri_upper(curc) || (rex.reg_ic && ri_lower(curc));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_NUPPER_IC: // [^A-Z]
- result = curc != NUL
- && !(ri_upper(curc) || (rex.reg_ic && ri_lower(curc)));
- ADD_STATE_IF_MATCH(t->state);
- break;
-
- case NFA_BACKREF1:
- case NFA_BACKREF2:
- case NFA_BACKREF3:
- case NFA_BACKREF4:
- case NFA_BACKREF5:
- case NFA_BACKREF6:
- case NFA_BACKREF7:
- case NFA_BACKREF8:
- case NFA_BACKREF9:
- case NFA_ZREF1:
- case NFA_ZREF2:
- case NFA_ZREF3:
- case NFA_ZREF4:
- case NFA_ZREF5:
- case NFA_ZREF6:
- case NFA_ZREF7:
- case NFA_ZREF8:
- case NFA_ZREF9:
- // \1 .. \9 \z1 .. \z9
- {
- int subidx;
- int bytelen;
-
- if (t->state->c <= NFA_BACKREF9) {
- subidx = t->state->c - NFA_BACKREF1 + 1;
- result = match_backref(&t->subs.norm, subidx, &bytelen);
- } else {
- subidx = t->state->c - NFA_ZREF1 + 1;
- result = match_zref(subidx, &bytelen);
- }
-
- if (result) {
- if (bytelen == 0) {
- // empty match always works, output of NFA_SKIP to be
- // used next
- add_here = true;
- add_state = t->state->out->out;
- } else if (bytelen <= clen) {
- // match current character, jump ahead to out of
- // NFA_SKIP
- add_state = t->state->out->out;
- add_off = clen;
- } else {
- // skip over the matched characters, set character
- // count in NFA_SKIP
- add_state = t->state->out;
- add_off = bytelen;
- add_count = bytelen - clen;
- }
- }
- break;
- }
- case NFA_SKIP:
- // character of previous matching \1 .. \9 or \@>
- if (t->count - clen <= 0) {
- // end of match, go to what follows
- add_state = t->state->out;
- add_off = clen;
- } else {
- // add state again with decremented count
- add_state = t->state;
- add_off = 0;
- add_count = t->count - clen;
- }
- break;
-
- case NFA_LNUM:
- case NFA_LNUM_GT:
- case NFA_LNUM_LT:
- assert(t->state->val >= 0
- && !((rex.reg_firstlnum > 0
- && rex.lnum > LONG_MAX - rex.reg_firstlnum)
- || (rex.reg_firstlnum < 0
- && rex.lnum < LONG_MIN + rex.reg_firstlnum))
- && rex.lnum + rex.reg_firstlnum >= 0);
- result = (REG_MULTI
- && nfa_re_num_cmp((uintmax_t)t->state->val,
- t->state->c - NFA_LNUM,
- (uintmax_t)(rex.lnum + rex.reg_firstlnum)));
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_COL:
- case NFA_COL_GT:
- case NFA_COL_LT:
- assert(t->state->val >= 0
- && rex.input >= rex.line
- && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1);
- result = nfa_re_num_cmp((uintmax_t)t->state->val,
- t->state->c - NFA_COL,
- (uintmax_t)(rex.input - rex.line + 1));
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_VCOL:
- case NFA_VCOL_GT:
- case NFA_VCOL_LT: {
- int op = t->state->c - NFA_VCOL;
- colnr_T col = (colnr_T)(rex.input - rex.line);
-
- // Bail out quickly when there can't be a match, avoid the overhead of
- // win_linetabsize() on long lines.
- if (op != 1 && col > t->state->val * MB_MAXBYTES) {
- break;
- }
-
- result = false;
- win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win;
- if (op == 1 && col - 1 > t->state->val && col > 100) {
- long ts = wp->w_buffer->b_p_ts;
-
- // Guess that a character won't use more columns than 'tabstop',
- // with a minimum of 4.
- if (ts < 4) {
- ts = 4;
- }
- result = col > t->state->val * ts;
- }
- if (!result) {
- uintmax_t lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
- assert(t->state->val >= 0);
- result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1);
- }
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- }
- break;
-
- case NFA_MARK:
- case NFA_MARK_GT:
- case NFA_MARK_LT: {
- size_t col = REG_MULTI ? (size_t)(rex.input - rex.line) : 0;
- fmark_T *fm = mark_get(rex.reg_buf, curwin, NULL, kMarkBufLocal, t->state->val);
-
- // Line may have been freed, get it again.
- if (REG_MULTI) {
- rex.line = (uint8_t *)reg_getline(rex.lnum);
- rex.input = rex.line + col;
- }
-
- // Compare the mark position to the match position, if the mark
- // exists and mark is set in reg_buf.
- if (fm != NULL && fm->mark.lnum > 0) {
- pos_T *pos = &fm->mark;
- const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum
- && pos->col == MAXCOL
- ? (colnr_T)strlen((char *)reg_getline(pos->lnum - rex.reg_firstlnum))
- : pos->col;
-
- result = pos->lnum == rex.lnum + rex.reg_firstlnum
- ? (pos_col == (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK
- : (pos_col < (colnr_T)(rex.input - rex.line)
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT))
- : (pos->lnum < rex.lnum + rex.reg_firstlnum
- ? t->state->c == NFA_MARK_GT
- : t->state->c == NFA_MARK_LT);
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- }
- break;
- }
-
- case NFA_CURSOR:
- result = rex.reg_win != NULL
- && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
- && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col);
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_VISUAL:
- result = reg_match_visual();
- if (result) {
- add_here = true;
- add_state = t->state->out;
- }
- break;
-
- case NFA_MOPEN1:
- case NFA_MOPEN2:
- case NFA_MOPEN3:
- case NFA_MOPEN4:
- case NFA_MOPEN5:
- case NFA_MOPEN6:
- case NFA_MOPEN7:
- case NFA_MOPEN8:
- case NFA_MOPEN9:
- case NFA_ZOPEN:
- case NFA_ZOPEN1:
- case NFA_ZOPEN2:
- case NFA_ZOPEN3:
- case NFA_ZOPEN4:
- case NFA_ZOPEN5:
- case NFA_ZOPEN6:
- case NFA_ZOPEN7:
- case NFA_ZOPEN8:
- case NFA_ZOPEN9:
- case NFA_NOPEN:
- case NFA_ZSTART:
- // These states are only added to be able to bail out when
- // they are added again, nothing is to be done.
- break;
-
- default: // regular character
- {
- int c = t->state->c;
-
-#ifdef REGEXP_DEBUG
- if (c < 0) {
- siemsg("INTERNAL: Negative state char: %" PRId64, (int64_t)c);
- }
-#endif
- result = (c == curc);
-
- if (!result && rex.reg_ic) {
- result = utf_fold(c) == utf_fold(curc);
- }
-
- // If rex.reg_icombine is not set only skip over the character
- // itself. When it is set skip over composing characters.
- if (result && !rex.reg_icombine) {
- clen = utf_ptr2len((char *)rex.input);
- }
-
- ADD_STATE_IF_MATCH(t->state);
- break;
- }
- } // switch (t->state->c)
-
- if (add_state != NULL) {
- nfa_pim_T *pim;
- nfa_pim_T pim_copy;
-
- if (t->pim.result == NFA_PIM_UNUSED) {
- pim = NULL;
- } else {
- pim = &t->pim;
- }
-
- // Handle the postponed invisible match if the match might end
- // without advancing and before the end of the line.
- if (pim != NULL && (clen == 0 || match_follows(add_state, 0))) {
- if (pim->result == NFA_PIM_TODO) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "\n");
- fprintf(log_fd, "==================================\n");
- fprintf(log_fd, "Postponed recursive nfa_regmatch()\n");
- fprintf(log_fd, "\n");
-#endif
- result = recursive_regmatch(pim->state, pim, prog, submatch, m,
- &listids, &listids_len);
- pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
- // for \@! and \@<! it is a match when the result is
- // false
- if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
- || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&pim->subs.norm, &m->norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&pim->subs.synt, &m->synt);
- }
- }
- } else {
- result = (pim->result == NFA_PIM_MATCH);
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "\n");
- fprintf(log_fd,
- "Using previous recursive nfa_regmatch() result, result == %d\n",
- pim->result);
- fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false");
- fprintf(log_fd, "\n");
-#endif
- }
-
- // for \@! and \@<! it is a match when result is false
- if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
- || pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG
- || pim->state->c
- == NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
- // Copy submatch info from the recursive call
- copy_sub_off(&t->subs.norm, &pim->subs.norm);
- if (rex.nfa_has_zsubexpr) {
- copy_sub_off(&t->subs.synt, &pim->subs.synt);
- }
- } else {
- // look-behind match failed, don't add the state
- continue;
- }
-
- // Postponed invisible match was handled, don't add it to
- // following states.
- pim = NULL;
- }
-
- // If "pim" points into l->t it will become invalid when
- // adding the state causes the list to be reallocated. Make a
- // local copy to avoid that.
- if (pim == &t->pim) {
- copy_pim(&pim_copy, pim);
- pim = &pim_copy;
- }
-
- if (add_here) {
- r = addstate_here(thislist, add_state, &t->subs, pim, &listidx);
- } else {
- r = addstate(nextlist, add_state, &t->subs, pim, add_off);
- if (add_count > 0) {
- nextlist->t[nextlist->n - 1].count = add_count;
- }
- }
- if (r == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- } // for (thislist = thislist; thislist->state; thislist++)
-
- // Look for the start of a match in the current position by adding the
- // start state to the list of states.
- // The first found match is the leftmost one, thus the order of states
- // matters!
- // Do not add the start state in recursive calls of nfa_regmatch(),
- // because recursive calls should only start in the first position.
- // Unless "nfa_endp" is not NULL, then we match the end position.
- // Also don't start a match past the first line.
- if (!nfa_match
- && ((toplevel
- && rex.lnum == 0
- && clen != 0
- && (rex.reg_maxcol == 0
- || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
- || (nfa_endp != NULL
- && (REG_MULTI
- ? (rex.lnum < nfa_endp->se_u.pos.lnum
- || (rex.lnum == nfa_endp->se_u.pos.lnum
- && (int)(rex.input - rex.line)
- < nfa_endp->se_u.pos.col))
- : rex.input < nfa_endp->se_u.ptr)))) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, "(---) STARTSTATE\n");
-#endif
- // Inline optimized code for addstate() if we know the state is
- // the first MOPEN.
- if (toplevel) {
- int add = true;
-
- if (prog->regstart != NUL && clen != 0) {
- if (nextlist->n == 0) {
- colnr_T col = (colnr_T)(rex.input - rex.line) + clen;
-
- // Nextlist is empty, we can skip ahead to the
- // character that must appear at the start.
- if (skip_to_start(prog->regstart, &col) == FAIL) {
- break;
- }
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
- col - ((colnr_T)(rex.input - rex.line) + clen));
-#endif
- rex.input = rex.line + col - clen;
- } else {
- // Checking if the required start character matches is
- // cheaper than adding a state that won't match.
- const int c = utf_ptr2char((char *)rex.input + clen);
- if (c != prog->regstart
- && (!rex.reg_ic
- || utf_fold(c) != utf_fold(prog->regstart))) {
-#ifdef REGEXP_DEBUG
- fprintf(log_fd,
- " Skipping start state, regstart does not match\n");
-#endif
- add = false;
- }
- }
- }
-
- if (add) {
- if (REG_MULTI) {
- m->norm.list.multi[0].start_col =
- (colnr_T)(rex.input - rex.line) + clen;
- m->norm.orig_start_col =
- m->norm.list.multi[0].start_col;
- } else {
- m->norm.list.line[0].start = rex.input + clen;
- }
- if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- } else {
- if (addstate(nextlist, start, m, NULL, clen) == NULL) {
- nfa_match = NFA_TOO_EXPENSIVE;
- goto theend;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- fprintf(log_fd, ">>> Thislist had %d states available: ", thislist->n);
- {
- int i;
-
- for (i = 0; i < thislist->n; i++) {
- fprintf(log_fd, "%d ", abs(thislist->t[i].state->id));
- }
- }
- fprintf(log_fd, "\n");
-#endif
-
-nextchar:
- // Advance to the next character, or advance to the next line, or
- // finish.
- if (clen != 0) {
- rex.input += clen;
- } else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
- && rex.lnum < nfa_endp->se_u.pos.lnum)) {
- reg_nextline();
- } else {
- break;
- }
-
- // Allow interrupting with CTRL-C.
- line_breakcheck();
- if (got_int) {
- break;
- }
- // Check for timeout once every twenty times to avoid overhead.
- if (nfa_time_limit != NULL && ++nfa_time_count == 20) {
- nfa_time_count = 0;
- if (nfa_did_time_out()) {
- break;
- }
- }
- }
-
-#ifdef REGEXP_DEBUG
- if (log_fd != stderr) {
- fclose(log_fd);
- }
- log_fd = NULL;
-#endif
-
-theend:
- // Free memory
- xfree(list[0].t);
- xfree(list[1].t);
- xfree(listids);
-#undef ADD_STATE_IF_MATCH
-#ifdef NFA_REGEXP_DEBUG_LOG
- fclose(debug);
-#endif
-
- return nfa_match;
-}
-
-/// Try match of "prog" with at rex.line["col"].
-///
-/// @param tm timeout limit or NULL
-/// @param timed_out flag set on timeout or NULL
-///
-/// @return <= 0 for failure, number of lines contained in the match otherwise.
-static long nfa_regtry(nfa_regprog_T *prog, colnr_T col, proftime_T *tm, int *timed_out)
-{
- int i;
- regsubs_T subs, m;
- nfa_state_T *start = prog->start;
-#ifdef REGEXP_DEBUG
- FILE *f;
-#endif
-
- rex.input = rex.line + col;
- nfa_time_limit = tm;
- nfa_timed_out = timed_out;
- nfa_time_count = 0;
-
-#ifdef REGEXP_DEBUG
- f = fopen(NFA_REGEXP_RUN_LOG, "a");
- if (f != NULL) {
- fprintf(f,
- "\n\n\t=======================================================\n");
-# ifdef REGEXP_DEBUG
- fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
-# endif
- fprintf(f, "\tInput text is \"%s\" \n", rex.input);
- fprintf(f, "\t=======================================================\n\n");
- nfa_print_state(f, start);
- fprintf(f, "\n\n");
- fclose(f);
- } else {
- emsg("Could not open temporary log file for writing");
- }
-#endif
-
- clear_sub(&subs.norm);
- clear_sub(&m.norm);
- clear_sub(&subs.synt);
- clear_sub(&m.synt);
-
- int result = nfa_regmatch(prog, start, &subs, &m);
- if (!result) {
- return 0;
- } else if (result == NFA_TOO_EXPENSIVE) {
- return result;
- }
-
- cleanup_subexpr();
- if (REG_MULTI) {
- for (i = 0; i < subs.norm.in_use; i++) {
- rex.reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum;
- rex.reg_startpos[i].col = subs.norm.list.multi[i].start_col;
-
- rex.reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum;
- rex.reg_endpos[i].col = subs.norm.list.multi[i].end_col;
- }
- if (rex.reg_mmatch != NULL) {
- rex.reg_mmatch->rmm_matchcol = subs.norm.orig_start_col;
- }
-
- if (rex.reg_startpos[0].lnum < 0) {
- rex.reg_startpos[0].lnum = 0;
- rex.reg_startpos[0].col = col;
- }
- if (rex.reg_endpos[0].lnum < 0) {
- // pattern has a \ze but it didn't match, use current end
- rex.reg_endpos[0].lnum = rex.lnum;
- rex.reg_endpos[0].col = (int)(rex.input - rex.line);
- } else {
- // Use line number of "\ze".
- rex.lnum = rex.reg_endpos[0].lnum;
- }
- } else {
- for (i = 0; i < subs.norm.in_use; i++) {
- rex.reg_startp[i] = subs.norm.list.line[i].start;
- rex.reg_endp[i] = subs.norm.list.line[i].end;
- }
-
- if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = rex.line + col;
- }
- if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = rex.input;
- }
- }
-
- // Package any found \z(...\) matches for export. Default is none.
- unref_extmatch(re_extmatch_out);
- re_extmatch_out = NULL;
-
- if (prog->reghasz == REX_SET) {
- cleanup_zsubexpr();
- re_extmatch_out = make_extmatch();
- // Loop over \z1, \z2, etc. There is no \z0.
- for (i = 1; i < subs.synt.in_use; i++) {
- if (REG_MULTI) {
- struct multipos *mpos = &subs.synt.list.multi[i];
-
- // Only accept single line matches that are valid.
- if (mpos->start_lnum >= 0
- && mpos->start_lnum == mpos->end_lnum
- && mpos->end_col >= mpos->start_col) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)reg_getline(mpos->start_lnum) + mpos->start_col,
- (size_t)(mpos->end_col - mpos->start_col));
- }
- } else {
- struct linepos *lpos = &subs.synt.list.line[i];
-
- if (lpos->start != NULL && lpos->end != NULL) {
- re_extmatch_out->matches[i] =
- (uint8_t *)xstrnsave((char *)lpos->start, (size_t)(lpos->end - lpos->start));
- }
- }
- }
- }
-
- return 1 + rex.lnum;
-}
-
-/// Match a regexp against a string ("line" points to the string) or multiple
-/// lines (if "line" is NULL, use reg_getline()).
-///
-/// @param line String in which to search or NULL
-/// @param startcol Column to start looking for match
-/// @param tm Timeout limit or NULL
-/// @param timed_out Flag set on timeout or NULL
-///
-/// @return <= 0 if there is no match and number of lines contained in the
-/// match otherwise.
-static long nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int *timed_out)
-{
- nfa_regprog_T *prog;
- long retval = 0L;
- colnr_T col = startcol;
-
- if (REG_MULTI) {
- prog = (nfa_regprog_T *)rex.reg_mmatch->regprog;
- line = (uint8_t *)reg_getline((linenr_T)0); // relative to the cursor
- rex.reg_startpos = rex.reg_mmatch->startpos;
- rex.reg_endpos = rex.reg_mmatch->endpos;
- } else {
- prog = (nfa_regprog_T *)rex.reg_match->regprog;
- rex.reg_startp = (uint8_t **)rex.reg_match->startp;
- rex.reg_endp = (uint8_t **)rex.reg_match->endp;
- }
-
- // Be paranoid...
- if (prog == NULL || line == NULL) {
- iemsg(_(e_null));
- goto theend;
- }
-
- // If pattern contains "\c" or "\C": overrule value of rex.reg_ic
- if (prog->regflags & RF_ICASE) {
- rex.reg_ic = true;
- } else if (prog->regflags & RF_NOICASE) {
- rex.reg_ic = false;
- }
-
- // If pattern contains "\Z" overrule value of rex.reg_icombine
- if (prog->regflags & RF_ICOMBINE) {
- rex.reg_icombine = true;
- }
-
- rex.line = line;
- rex.lnum = 0; // relative to line
-
- rex.nfa_has_zend = prog->has_zend;
- rex.nfa_has_backref = prog->has_backref;
- rex.nfa_nsubexpr = prog->nsubexp;
- rex.nfa_listid = 1;
- rex.nfa_alt_listid = 2;
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = prog->pattern;
-#endif
-
- if (prog->reganch && col > 0) {
- return 0L;
- }
-
- rex.need_clear_subexpr = true;
- // Clear the external match subpointers if necessary.
- if (prog->reghasz == REX_SET) {
- rex.nfa_has_zsubexpr = true;
- rex.need_clear_zsubexpr = true;
- } else {
- rex.nfa_has_zsubexpr = false;
- rex.need_clear_zsubexpr = false;
- }
-
- if (prog->regstart != NUL) {
- // Skip ahead until a character we know the match must start with.
- // When there is none there is no match.
- if (skip_to_start(prog->regstart, &col) == FAIL) {
- return 0L;
- }
-
- // If match_text is set it contains the full text that must match.
- // Nothing else to try. Doesn't handle combining chars well.
- if (prog->match_text != NULL && !rex.reg_icombine) {
- retval = find_match_text(&col, prog->regstart, prog->match_text);
- if (REG_MULTI) {
- rex.reg_mmatch->rmm_matchcol = col;
- } else {
- rex.reg_match->rm_matchcol = col;
- }
- return retval;
- }
- }
-
- // If the start column is past the maximum column: no need to try.
- if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) {
- goto theend;
- }
-
- // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when
- // it's accidentally used during execution.
- nstate = 0;
- for (int i = 0; i < prog->nstate; i++) {
- prog->state[i].id = i;
- prog->state[i].lastlist[0] = 0;
- prog->state[i].lastlist[1] = 0;
- }
-
- retval = nfa_regtry(prog, col, tm, timed_out);
-
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = NULL;
-#endif
-
-theend:
- if (retval > 0) {
- // Make sure the end is never before the start. Can happen when \zs and
- // \ze are used.
- if (REG_MULTI) {
- const lpos_T *const start = &rex.reg_mmatch->startpos[0];
- const lpos_T *const end = &rex.reg_mmatch->endpos[0];
-
- if (end->lnum < start->lnum
- || (end->lnum == start->lnum && end->col < start->col)) {
- rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0];
- }
- } else {
- if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) {
- rex.reg_match->endp[0] = rex.reg_match->startp[0];
- }
-
- // startpos[0] may be set by "\zs", also return the column where
- // the whole pattern matched.
- rex.reg_match->rm_matchcol = col;
- }
- }
-
- return retval;
-}
-
-// Compile a regular expression into internal code for the NFA matcher.
-// Returns the program in allocated space. Returns NULL for an error.
-static regprog_T *nfa_regcomp(uint8_t *expr, int re_flags)
-{
- nfa_regprog_T *prog = NULL;
- int *postfix;
-
- if (expr == NULL) {
- return NULL;
- }
-
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = expr;
-#endif
- nfa_re_flags = re_flags;
-
- init_class_tab();
-
- nfa_regcomp_start(expr, re_flags);
-
- // Build postfix form of the regexp. Needed to build the NFA
- // (and count its size).
- postfix = re2post();
- if (postfix == NULL) {
- goto fail; // Cascaded (syntax?) error
- }
-
- // In order to build the NFA, we parse the input regexp twice:
- // 1. first pass to count size (so we can allocate space)
- // 2. second to emit code
-#ifdef REGEXP_DEBUG
- {
- FILE *f = fopen(NFA_REGEXP_RUN_LOG, "a");
-
- if (f != NULL) {
- fprintf(f,
- "\n*****************************\n\n\n\n\t"
- "Compiling regexp \"%s\"... hold on !\n",
- expr);
- fclose(f);
- }
- }
-#endif
-
- // PASS 1
- // Count number of NFA states in "nstate". Do not build the NFA.
- post2nfa(postfix, post_ptr, true);
-
- // allocate the regprog with space for the compiled regexp
- size_t prog_size = offsetof(nfa_regprog_T, state) + sizeof(nfa_state_T) * (size_t)nstate;
- prog = xmalloc(prog_size);
- state_ptr = prog->state;
- prog->re_in_use = false;
-
- // PASS 2
- // Build the NFA
- prog->start = post2nfa(postfix, post_ptr, false);
- if (prog->start == NULL) {
- goto fail;
- }
- prog->regflags = regflags;
- prog->engine = &nfa_regengine;
- prog->nstate = nstate;
- prog->has_zend = rex.nfa_has_zend;
- prog->has_backref = rex.nfa_has_backref;
- prog->nsubexp = regnpar;
-
- nfa_postprocess(prog);
-
- prog->reganch = nfa_get_reganch(prog->start, 0);
- prog->regstart = nfa_get_regstart(prog->start, 0);
- prog->match_text = nfa_get_match_text(prog->start);
-
-#ifdef REGEXP_DEBUG
- nfa_postfix_dump(expr, OK);
- nfa_dump(prog);
-#endif
- // Remember whether this pattern has any \z specials in it.
- prog->reghasz = re_has_z;
- prog->pattern = xstrdup((char *)expr);
-#ifdef REGEXP_DEBUG
- nfa_regengine.expr = NULL;
-#endif
-
-out:
- xfree(post_start);
- post_start = post_ptr = post_end = NULL;
- state_ptr = NULL;
- return (regprog_T *)prog;
-
-fail:
- XFREE_CLEAR(prog);
-#ifdef REGEXP_DEBUG
- nfa_postfix_dump(expr, FAIL);
- nfa_regengine.expr = NULL;
-#endif
- goto out;
-}
-
-// Free a compiled regexp program, returned by nfa_regcomp().
-static void nfa_regfree(regprog_T *prog)
-{
- if (prog == NULL) {
- return;
- }
-
- xfree(((nfa_regprog_T *)prog)->match_text);
- xfree(((nfa_regprog_T *)prog)->pattern);
- xfree(prog);
-}
-
-/// Match a regexp against a string.
-/// "rmp->regprog" is a compiled regexp as returned by nfa_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-/// If "line_lbr" is true, consider a "\n" in "line" to be a line break.
-///
-/// @param line string to match against
-/// @param col column to start looking for match
-///
-/// @return <= 0 for failure, number of lines contained in the match otherwise.
-static int nfa_regexec_nl(regmatch_T *rmp, uint8_t *line, colnr_T col, bool line_lbr)
-{
- rex.reg_match = rmp;
- rex.reg_mmatch = NULL;
- rex.reg_maxline = 0;
- rex.reg_line_lbr = line_lbr;
- rex.reg_buf = curbuf;
- rex.reg_win = NULL;
- rex.reg_ic = rmp->rm_ic;
- rex.reg_icombine = false;
- rex.reg_maxcol = 0;
- return (int)nfa_regexec_both(line, col, NULL, NULL);
-}
-
-/// Matches a regexp against multiple lines.
-/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp().
-/// Uses curbuf for line count and 'iskeyword'.
-///
-/// @param win Window in which to search or NULL
-/// @param buf Buffer in which to search
-/// @param lnum Number of line to start looking for match
-/// @param col Column to start looking for match
-/// @param tm Timeout limit or NULL
-/// @param timed_out Flag set on timeout or NULL
-///
-/// @return <= 0 if there is no match and number of lines contained in the match
-/// otherwise.
-///
-/// @note The body is the same as bt_regexec() except for nfa_regexec_both()
-///
-/// @warning
-/// Match may actually be in another line. e.g.:
-/// when r.e. is \nc, cursor is at 'a' and the text buffer looks like
-///
-/// @par
-///
-/// +-------------------------+
-/// |a |
-/// |b |
-/// |c |
-/// | |
-/// +-------------------------+
-///
-/// @par
-/// then nfa_regexec_multi() returns 3. while the original vim_regexec_multi()
-/// returns 0 and a second call at line 2 will return 2.
-///
-/// @par
-/// FIXME if this behavior is not compatible.
-static long nfa_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, linenr_T lnum, colnr_T col,
- proftime_T *tm, int *timed_out)
-{
- init_regexec_multi(rmp, win, buf, lnum);
- return nfa_regexec_both(NULL, col, tm, timed_out);
-}
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index b071e10cf9..38d3942126 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file runtime.c
///
/// Management of runtime files (including packages)
@@ -9,44 +6,58 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <uv.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/debugger.h"
#include "nvim/eval.h"
+#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
+#include "nvim/hashtab.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
-#include "nvim/map.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
+#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
+#include "nvim/types_defs.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
+#ifdef USE_CRNL
+# include "nvim/highlight.h"
+#endif
/// Structure used to store info for each sourced file.
/// It is shared between do_source() and getsourceline().
@@ -57,7 +68,7 @@ struct source_cookie {
char *nextline; ///< if not NULL: line that was read ahead
linenr_T sourcing_lnum; ///< line number of the source file
int finished; ///< ":finish" used
-#if defined(USE_CRNL)
+#ifdef USE_CRNL
int fileformat; ///< EOL_UNKNOWN, EOL_UNIX or EOL_DOS
bool error; ///< true if LF found after CR-LF
#endif
@@ -68,12 +79,26 @@ struct source_cookie {
vimconv_T conv; ///< type of conversion
};
+typedef struct {
+ char *path;
+ bool after;
+ TriState has_lua;
+} SearchPathItem;
+
+typedef kvec_t(SearchPathItem) RuntimeSearchPath;
+typedef kvec_t(char *) CharVec;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "runtime.c.generated.h"
#endif
garray_T exestack = { 0, 0, sizeof(estack_T), 50, NULL };
-garray_T script_items = { 0, 0, sizeof(scriptitem_T), 4, NULL };
+garray_T script_items = { 0, 0, sizeof(scriptitem_T *), 20, NULL };
+
+/// The names of packages that once were loaded are remembered.
+static garray_T ga_loaded = { 0, 0, sizeof(char *), 4, NULL };
+
+static int last_current_SID_seq = 0;
/// Initialize the execution stack.
void estack_init(void)
@@ -144,8 +169,8 @@ char *estack_sfile(estack_arg_T which)
? &entry->es_info.ufunc->uf_script_ctx
: &entry->es_info.aucmd->script_ctx);
return def_ctx->sc_sid > 0
- ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid).sn_name))
- : NULL;
+ ? xstrdup((SCRIPT_ITEM(def_ctx->sc_sid)->sn_name))
+ : NULL;
} else if (entry->es_type == ETYPE_SCRIPT) {
return xstrdup(entry->es_name);
}
@@ -178,8 +203,8 @@ char *estack_sfile(estack_arg_T which)
len += strlen(type_name);
ga_grow(&ga, (int)len);
linenr_T lnum = idx == exestack.ga_len - 1
- ? which == ESTACK_STACK ? SOURCING_LNUM : 0
- : entry->es_lnum;
+ ? which == ESTACK_STACK ? SOURCING_LNUM : 0
+ : entry->es_lnum;
char *dots = idx == exestack.ga_len - 1 ? "" : "..";
if (lnum == 0) {
// For the bottom entry of <sfile>: do not add the line number,
@@ -246,6 +271,7 @@ void ex_runtime(exarg_T *eap)
int flags = eap->forceit ? DIP_ALL : 0;
char *p = skiptowhite(arg);
flags += get_runtime_cmd_flags(&arg, (size_t)(p - arg));
+ assert(arg != NULL); // suppress clang false positive
source_runtime(arg, flags);
}
@@ -257,29 +283,83 @@ void set_context_in_runtime_cmd(expand_T *xp, const char *arg)
char *p = skiptowhite(arg);
runtime_expand_flags
= *p != NUL ? get_runtime_cmd_flags((char **)&arg, (size_t)(p - arg)) : 0;
+ // Skip to the last argument.
+ while (*(p = skiptowhite_esc(arg)) != NUL) {
+ if (runtime_expand_flags == 0) {
+ // When there are multiple arguments and [where] is not specified,
+ // use an unrelated non-zero flag to avoid expanding [where].
+ runtime_expand_flags = DIP_ALL;
+ }
+ arg = skipwhite(p);
+ }
xp->xp_context = EXPAND_RUNTIME;
xp->xp_pattern = (char *)arg;
}
-static void source_callback(char *fname, void *cookie)
+/// Source all .vim and .lua files in "fnames" with .vim files being sourced first.
+static bool source_callback_vim_lua(int num_fnames, char **fnames, bool all, void *cookie)
{
- (void)do_source(fname, false, DOSO_NONE);
+ bool did_one = false;
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (path_with_extension(fnames[i], "vim")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (path_with_extension(fnames[i], "lua")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ return did_one;
}
-/// Find the file "name" in all directories in "path" and invoke
+/// Source all files in "fnames" with .vim files sourced first, .lua files
+/// sourced second, and any remaining files sourced last.
+static bool source_callback(int num_fnames, char **fnames, bool all, void *cookie)
+{
+ bool did_one = source_callback_vim_lua(num_fnames, fnames, all, cookie);
+
+ if (!all && did_one) {
+ return true;
+ }
+
+ for (int i = 0; i < num_fnames; i++) {
+ if (!path_with_extension(fnames[i], "vim")
+ && !path_with_extension(fnames[i], "lua")) {
+ (void)do_source(fnames[i], false, DOSO_NONE, cookie);
+ did_one = true;
+ if (!all) {
+ return true;
+ }
+ }
+ }
+
+ return did_one;
+}
+
+/// Find the patterns in "name" in all directories in "path" and invoke
/// "callback(fname, cookie)".
-/// "name" can contain wildcards.
+/// "prefix" is prepended to each pattern in "name".
/// When "flags" has DIP_ALL: source all files, otherwise only the first one.
/// When "flags" has DIP_DIR: find directories instead of files.
/// When "flags" has DIP_ERR: give an error message if there is no match.
///
-/// return FAIL when no file could be sourced, OK otherwise.
-int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, void *cookie)
+/// Return FAIL when no file could be sourced, OK otherwise.
+int do_in_path(const char *path, const char *prefix, char *name, int flags,
+ DoInRuntimepathCB callback, void *cookie)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
- char *tail;
- int num_files;
- char **files;
- int i;
bool did_one = false;
// Make a copy of 'runtimepath'. Invoking the callback may change the
@@ -287,15 +367,22 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
char *rtp_copy = xstrdup(path);
char *buf = xmallocz(MAXPATHL);
{
+ char *tail;
if (p_verbose > 10 && name != NULL) {
verbose_enter();
- smsg(_("Searching for \"%s\" in \"%s\""), name, path);
+ if (*prefix != NUL) {
+ smsg(0, _("Searching for \"%s\" under \"%s\" in \"%s\""), name, prefix, path);
+ } else {
+ smsg(0, _("Searching for \"%s\" in \"%s\""), name, path);
+ }
verbose_leave();
}
+ bool do_all = (flags & DIP_ALL) != 0;
+
// Loop over all entries in 'runtimepath'.
char *rtp = rtp_copy;
- while (*rtp != NUL && ((flags & DIP_ALL) || !did_one)) {
+ while (*rtp != NUL && (do_all || !did_one)) {
// Copy the path from 'runtimepath' to buf[].
copy_option_part(&rtp, buf, MAXPATHL, ",");
size_t buflen = strlen(buf);
@@ -311,39 +398,31 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
}
if (name == NULL) {
- (*callback)(buf, cookie);
+ (*callback)(1, &buf, do_all, cookie);
did_one = true;
- } else if (buflen + strlen(name) + 2 < MAXPATHL) {
+ } else if (buflen + 2 + strlen(prefix) + strlen(name) < MAXPATHL) {
add_pathsep(buf);
+ STRCAT(buf, prefix);
tail = buf + strlen(buf);
// Loop over all patterns in "name"
char *np = name;
- while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
+ while (*np != NUL && (do_all || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - buf));
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
- smsg(_("Searching for \"%s\""), buf);
+ smsg(0, _("Searching for \"%s\""), buf);
verbose_leave();
}
int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE)
- | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0;
-
- // Expand wildcards, invoke the callback for each match.
- if (gen_expand_wildcards(1, &buf, &num_files, &files, ew_flags) == OK) {
- for (i = 0; i < num_files; i++) {
- (*callback)(files[i], cookie);
- did_one = true;
- if (!(flags & DIP_ALL)) {
- break;
- }
- }
- FreeWild(num_files, files);
- }
+ | ((flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0);
+
+ did_one |= gen_expand_wildcards_and_cb(1, &buf, ew_flags, do_all, callback,
+ cookie) == OK;
}
}
}
@@ -357,7 +436,7 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
semsg(_(e_dirnotf), basepath, name);
} else if (p_verbose > 1) {
verbose_enter();
- smsg(_("not found in '%s': \"%s\""), basepath, name);
+ smsg(0, _("not found in '%s': \"%s\""), basepath, name);
verbose_leave();
}
}
@@ -365,7 +444,7 @@ int do_in_path(char *path, char *name, int flags, DoInRuntimepathCB callback, vo
return did_one ? OK : FAIL;
}
-RuntimeSearchPath runtime_search_path_get_cached(int *ref)
+static RuntimeSearchPath runtime_search_path_get_cached(int *ref)
FUNC_ATTR_NONNULL_ALL
{
runtime_search_path_validate();
@@ -380,7 +459,7 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref)
return runtime_search_path;
}
-RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
+static RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
{
RuntimeSearchPath dst = KV_INITIAL_VALUE;
for (size_t j = 0; j < kv_size(src); j++) {
@@ -391,7 +470,7 @@ RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src)
return dst;
}
-void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
+static void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
FUNC_ATTR_NONNULL_ALL
{
if (*ref) {
@@ -411,25 +490,24 @@ void runtime_search_path_unref(RuntimeSearchPath path, const int *ref)
/// When "flags" has DIP_ERR: give an error message if there is no match.
///
/// return FAIL when no file could be sourced, OK otherwise.
-int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
+static int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *cookie)
{
char *tail;
- int num_files;
- char **files;
- int i;
bool did_one = false;
char buf[MAXPATHL];
if (p_verbose > 10 && name != NULL) {
verbose_enter();
- smsg(_("Searching for \"%s\" in runtime path"), name);
+ smsg(0, _("Searching for \"%s\" in runtime path"), name);
verbose_leave();
}
int ref;
RuntimeSearchPath path = runtime_search_path_get_cached(&ref);
+ bool do_all = (flags & DIP_ALL) != 0;
+
// Loop over all entries in cached path
for (size_t j = 0; j < kv_size(path); j++) {
SearchPathItem item = kv_A(path, j);
@@ -444,7 +522,7 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
}
if (name == NULL) {
- (*callback)(item.path, cookie);
+ (*callback)(1, &item.path, do_all, cookie);
} else if (buflen + strlen(name) + 2 < MAXPATHL) {
STRCPY(buf, item.path);
add_pathsep(buf);
@@ -452,32 +530,25 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
// Loop over all patterns in "name"
char *np = name;
- while (*np != NUL && ((flags & DIP_ALL) || !did_one)) {
+
+ while (*np != NUL && (do_all || !did_one)) {
// Append the pattern from "name" to buf[].
assert(MAXPATHL >= (tail - buf));
copy_option_part(&np, tail, (size_t)(MAXPATHL - (tail - buf)), "\t ");
if (p_verbose > 10) {
verbose_enter();
- smsg(_("Searching for \"%s\""), buf);
+ smsg(0, _("Searching for \"%s\""), buf);
verbose_leave();
}
int ew_flags = ((flags & DIP_DIR) ? EW_DIR : EW_FILE)
- | (flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0;
+ | ((flags & DIP_DIRFILE) ? (EW_DIR|EW_FILE) : 0)
+ | EW_NOBREAK;
// Expand wildcards, invoke the callback for each match.
char *(pat[]) = { buf };
- if (gen_expand_wildcards(1, pat, &num_files, &files, ew_flags) == OK) {
- for (i = 0; i < num_files; i++) {
- (*callback)(files[i], cookie);
- did_one = true;
- if (!(flags & DIP_ALL)) {
- break;
- }
- }
- FreeWild(num_files, files);
- }
+ did_one |= gen_expand_wildcards_and_cb(1, pat, ew_flags, do_all, callback, cookie) == OK;
}
}
}
@@ -487,7 +558,7 @@ int do_in_cached_path(char *name, int flags, DoInRuntimepathCB callback, void *c
semsg(_(e_dirnotf), "runtime path", name);
} else if (p_verbose > 1) {
verbose_enter();
- smsg(_("not found in runtime path: \"%s\""), name);
+ smsg(0, _("not found in runtime path: \"%s\""), name);
verbose_leave();
}
}
@@ -505,7 +576,7 @@ Array runtime_inspect(void)
for (size_t i = 0; i < kv_size(path); i++) {
SearchPathItem *item = &kv_A(path, i);
Array entry = ARRAY_DICT_INIT;
- ADD(entry, STRING_OBJ(cstr_to_string(item->path)));
+ ADD(entry, CSTR_TO_OBJ(item->path));
ADD(entry, BOOLEAN_OBJ(item->after));
if (item->has_lua != kNone) {
ADD(entry, BOOLEAN_OBJ(item->has_lua == kTrue));
@@ -538,8 +609,8 @@ ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all)
return rv;
}
-ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
- RuntimeSearchPath path, char *buf, size_t buf_len)
+static ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
+ RuntimeSearchPath path, char *buf, size_t buf_len)
{
ArrayOf(String) rv = ARRAY_DICT_INIT;
for (size_t i = 0; i < kv_size(path); i++) {
@@ -561,7 +632,7 @@ ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all,
item->path, pat_item.data.string.data);
if (size < buf_len) {
if (os_file_is_readable(buf)) {
- ADD(rv, STRING_OBJ(cstr_to_string(buf)));
+ ADD(rv, CSTR_TO_OBJ(buf));
if (!all) {
goto done;
}
@@ -587,73 +658,46 @@ int do_in_path_and_pp(char *path, char *name, int flags, DoInRuntimepathCB callb
int done = FAIL;
if ((flags & DIP_NORTP) == 0) {
- done |= do_in_path(path, (name && !*name) ? NULL : name, flags, callback,
+ done |= do_in_path(path, "", (name && !*name) ? NULL : name, flags, callback,
cookie);
}
if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_START)) {
- char *start_dir = "pack/*/start/*/%s%s"; // NOLINT
- size_t len = strlen(start_dir) + strlen(name) + 6;
- char *s = xmallocz(len); // TODO(bfredl): get rid of random allocations
- char *suffix = (flags & DIP_AFTER) ? "after/" : "";
-
- vim_snprintf(s, len, start_dir, suffix, name);
- done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie);
-
- xfree(s);
+ const char *prefix
+ = (flags & DIP_AFTER) ? "pack/*/start/*/after/" : "pack/*/start/*/"; // NOLINT
+ done |= do_in_path(p_pp, prefix, name, flags & ~DIP_AFTER, callback, cookie);
if (done == FAIL || (flags & DIP_ALL)) {
- start_dir = "start/*/%s%s"; // NOLINT
- len = strlen(start_dir) + strlen(name) + 6;
- s = xmallocz(len);
-
- vim_snprintf(s, len, start_dir, suffix, name);
- done |= do_in_path(p_pp, s, flags & ~DIP_AFTER, callback, cookie);
-
- xfree(s);
+ prefix = (flags & DIP_AFTER) ? "start/*/after/" : "start/*/"; // NOLINT
+ done |= do_in_path(p_pp, prefix, name, flags & ~DIP_AFTER, callback, cookie);
}
}
if ((done == FAIL || (flags & DIP_ALL)) && (flags & DIP_OPT)) {
- char *opt_dir = "pack/*/opt/*/%s"; // NOLINT
- size_t len = strlen(opt_dir) + strlen(name);
- char *s = xmallocz(len);
-
- vim_snprintf(s, len, opt_dir, name);
- done |= do_in_path(p_pp, s, flags, callback, cookie);
-
- xfree(s);
+ done |= do_in_path(p_pp, "pack/*/opt/*/", name, flags, callback, cookie); // NOLINT
if (done == FAIL || (flags & DIP_ALL)) {
- opt_dir = "opt/*/%s"; // NOLINT
- len = strlen(opt_dir) + strlen(name);
- s = xmallocz(len);
-
- vim_snprintf(s, len, opt_dir, name);
- done |= do_in_path(p_pp, s, flags, callback, cookie);
-
- xfree(s);
+ done |= do_in_path(p_pp, "opt/*/", name, flags, callback, cookie); // NOLINT
}
}
return done;
}
-static void push_path(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used, char *entry,
+static void push_path(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
bool after)
{
- handle_T h = map_get(String, handle_T)(rtp_used, cstr_as_string(entry));
- if (h == 0) {
- char *allocated = xstrdup(entry);
- map_put(String, handle_T)(rtp_used, cstr_as_string(allocated), 1);
- kv_push(*search_path, ((SearchPathItem){ allocated, after, kNone }));
+ String *key_alloc;
+ if (set_put_ref(String, rtp_used, cstr_as_string(entry), &key_alloc)) {
+ *key_alloc = cstr_to_string(entry);
+ kv_push(*search_path, ((SearchPathItem){ key_alloc->data, after, kNone }));
}
}
-static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used,
- char *entry, bool after)
+static void expand_rtp_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used, char *entry,
+ bool after)
{
- if (map_get(String, handle_T)(rtp_used, cstr_as_string(entry))) {
+ if (set_has(String, rtp_used, cstr_as_string(entry))) {
return;
}
@@ -664,7 +708,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_
int num_files;
char **files;
char *(pat[]) = { entry };
- if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR) == OK) {
+ if (gen_expand_wildcards(1, pat, &num_files, &files, EW_DIR | EW_NOBREAK) == OK) {
for (int i = 0; i < num_files; i++) {
push_path(search_path, rtp_used, files[i], after);
}
@@ -672,7 +716,7 @@ static void expand_rtp_entry(RuntimeSearchPath *search_path, Map(String, handle_
}
}
-static void expand_pack_entry(RuntimeSearchPath *search_path, Map(String, handle_T) *rtp_used,
+static void expand_pack_entry(RuntimeSearchPath *search_path, Set(String) *rtp_used,
CharVec *after_path, char *pack_entry, size_t pack_entry_len)
{
static char buf[MAXPATHL];
@@ -702,13 +746,11 @@ static bool path_is_after(char *buf, size_t buflen)
&& strcmp(buf + buflen - 5, "after") == 0;
}
-RuntimeSearchPath runtime_search_path_build(void)
+static RuntimeSearchPath runtime_search_path_build(void)
{
kvec_t(String) pack_entries = KV_INITIAL_VALUE;
- // TODO(bfredl): these should just be sets, when Set(String) is do merge to
- // master.
- Map(String, handle_T) pack_used = MAP_INIT;
- Map(String, handle_T) rtp_used = MAP_INIT;
+ Map(String, int) pack_used = MAP_INIT;
+ Set(String) rtp_used = SET_INIT;
RuntimeSearchPath search_path = KV_INITIAL_VALUE;
CharVec after_path = KV_INITIAL_VALUE;
@@ -720,7 +762,7 @@ RuntimeSearchPath runtime_search_path_build(void)
String the_entry = { .data = cur_entry, .size = strlen(buf) };
kv_push(pack_entries, the_entry);
- map_put(String, handle_T)(&pack_used, the_entry, 0);
+ map_put(String, int)(&pack_used, the_entry, 0);
}
char *rtp_entry;
@@ -737,7 +779,7 @@ RuntimeSearchPath runtime_search_path_build(void)
// fact: &rtp entries can contain wild chars
expand_rtp_entry(&search_path, &rtp_used, buf, false);
- handle_T *h = map_ref(String, handle_T)(&pack_used, cstr_as_string(buf), false);
+ handle_T *h = map_ref(String, int)(&pack_used, cstr_as_string(buf), NULL);
if (h) {
(*h)++;
expand_pack_entry(&search_path, &rtp_used, &after_path, buf, buflen);
@@ -746,7 +788,7 @@ RuntimeSearchPath runtime_search_path_build(void)
for (size_t i = 0; i < kv_size(pack_entries); i++) {
String item = kv_A(pack_entries, i);
- handle_T h = map_get(String, handle_T)(&pack_used, item);
+ handle_T h = map_get(String, int)(&pack_used, item);
if (h == 0) {
expand_pack_entry(&search_path, &rtp_used, &after_path, item.data, item.size);
}
@@ -767,18 +809,19 @@ RuntimeSearchPath runtime_search_path_build(void)
// strings are not owned
kv_destroy(pack_entries);
kv_destroy(after_path);
- map_destroy(String, handle_T)(&pack_used);
- map_destroy(String, handle_T)(&rtp_used);
+ map_destroy(String, &pack_used);
+ set_destroy(String, &rtp_used);
return search_path;
}
-void runtime_search_path_invalidate(void)
+const char *did_set_runtimepackpath(optset_T *args)
{
runtime_search_path_valid = false;
+ return NULL;
}
-void runtime_search_path_free(RuntimeSearchPath path)
+static void runtime_search_path_free(RuntimeSearchPath path)
{
for (size_t j = 0; j < kv_size(path); j++) {
SearchPathItem item = kv_A(path, j);
@@ -836,27 +879,46 @@ int source_runtime(char *name, int flags)
return do_in_runtimepath(name, flags, source_callback, NULL);
}
-/// Just like source_runtime(), but use "path" instead of 'runtimepath'.
-int source_in_path(char *path, char *name, int flags)
+/// Just like source_runtime(), but only source vim and lua files
+int source_runtime_vim_lua(char *name, int flags)
+{
+ return do_in_runtimepath(name, flags, source_callback_vim_lua, NULL);
+}
+
+/// Just like source_runtime(), but:
+/// - use "path" instead of 'runtimepath'.
+/// - only source .vim and .lua files
+int source_in_path_vim_lua(char *path, char *name, int flags)
{
- return do_in_path_and_pp(path, name, flags, source_callback, NULL);
+ return do_in_path_and_pp(path, name, flags, source_callback_vim_lua, NULL);
}
-// Expand wildcards in "pat" and invoke do_source()/nlua_exec_file()
-// for each match.
-static void source_all_matches(char *pat)
+/// Expand wildcards in "pats" and invoke callback matches.
+///
+/// @param num_pat is number of input patterns.
+/// @param patx is an array of pointers to input patterns.
+/// @param flags is a combination of EW_* flags used in
+/// expand_wildcards().
+/// @param all invoke callback on all matches or just one
+/// @param callback called for each match.
+/// @param cookie context for callback
+///
+/// @returns OK when some files were found, FAIL otherwise.
+static int gen_expand_wildcards_and_cb(int num_pat, char **pats, int flags, bool all,
+ DoInRuntimepathCB callback, void *cookie)
{
int num_files;
char **files;
- if (gen_expand_wildcards(1, &pat, &num_files, &files, EW_FILE) != OK) {
- return;
+ if (gen_expand_wildcards(num_pat, pats, &num_files, &files, flags) != OK) {
+ return FAIL;
}
- for (int i = 0; i < num_files; i++) {
- (void)do_source(files[i], false, DOSO_NONE);
- }
+ (*callback)(num_files, files, all, cookie);
+
FreeWild(num_files, files);
+
+ return OK;
}
/// Add the package directory to 'runtimepath'
@@ -866,7 +928,6 @@ static void source_all_matches(char *pat)
static int add_pack_dir_to_rtp(char *fname, bool is_pack)
{
char *p;
- char *buf = NULL;
char *afterdir = NULL;
int retval = FAIL;
@@ -901,29 +962,16 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
// Find "ffname" in "p_rtp", ignoring '/' vs '\' differences
// Also stop at the first "after" directory
size_t fname_len = strlen(ffname);
- buf = try_malloc(MAXPATHL);
+ char *buf = try_malloc(MAXPATHL);
if (buf == NULL) {
goto theend;
}
const char *insp = NULL;
const char *after_insp = NULL;
- for (const char *entry = (const char *)p_rtp; *entry != NUL;) {
+ for (const char *entry = p_rtp; *entry != NUL;) {
const char *cur_entry = entry;
copy_option_part((char **)&entry, buf, MAXPATHL, ",");
- if (insp == NULL) {
- add_pathsep(buf);
- char *const rtp_ffname = fix_fname(buf);
- if (rtp_ffname == NULL) {
- goto theend;
- }
- bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0;
- xfree(rtp_ffname);
- if (match) {
- // Insert "ffname" after this entry (and comma).
- insp = entry;
- }
- }
if ((p = strstr(buf, "after")) != NULL
&& p > buf
@@ -937,11 +985,25 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
after_insp = cur_entry;
break;
}
+
+ if (insp == NULL) {
+ add_pathsep(buf);
+ char *const rtp_ffname = fix_fname(buf);
+ if (rtp_ffname == NULL) {
+ goto theend;
+ }
+ bool match = path_fnamencmp(rtp_ffname, ffname, fname_len) == 0;
+ xfree(rtp_ffname);
+ if (match) {
+ // Insert "ffname" after this entry (and comma).
+ insp = entry;
+ }
+ }
}
if (insp == NULL) {
// Both "fname" and "after" not found, append at the end.
- insp = (const char *)p_rtp + strlen(p_rtp);
+ insp = p_rtp + strlen(p_rtp);
}
// check if rtp/pack/name/start/name/after exists
@@ -962,7 +1024,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
// We now have 'rtp' parts: {keep}{keep_after}{rest}.
// Create new_rtp, first: {keep},{fname}
- size_t keep = (size_t)(insp - (const char *)p_rtp);
+ size_t keep = (size_t)(insp - p_rtp);
memmove(new_rtp, p_rtp, keep);
size_t new_rtp_len = keep;
if (*insp == NUL) {
@@ -975,7 +1037,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
}
if (afterlen > 0 && after_insp != NULL) {
- size_t keep_after = (size_t)(after_insp - (const char *)p_rtp);
+ size_t keep_after = (size_t)(after_insp - p_rtp);
// Add to new_rtp: {keep},{fname}{keep_after},{afterdir}
memmove(new_rtp + new_rtp_len, p_rtp + keep, keep_after - keep);
@@ -1000,7 +1062,7 @@ static int add_pack_dir_to_rtp(char *fname, bool is_pack)
xstrlcat(new_rtp, afterdir, new_rtp_capacity);
}
- set_option_value_give_err("rtp", 0L, new_rtp, 0);
+ set_option_value_give_err("rtp", CSTR_AS_OPTVAL(new_rtp), 0);
xfree(new_rtp);
retval = OK;
@@ -1013,30 +1075,27 @@ theend:
/// Load scripts in "plugin" directory of the package.
/// For opt packages, also load scripts in "ftdetect" (start packages already
-/// load these from filetype.vim)
+/// load these from filetype.lua)
static int load_pack_plugin(bool opt, char *fname)
{
- static const char *ftpat = "%s/ftdetect/*.vim"; // NOLINT
+ static const char plugpat[] = "%s/plugin/**/*"; // NOLINT
+ static const char ftpat[] = "%s/ftdetect/*"; // NOLINT
char *const ffname = fix_fname(fname);
- size_t len = strlen(ffname) + strlen(ftpat);
+ size_t len = strlen(ffname) + sizeof(plugpat);
char *pat = xmallocz(len);
- vim_snprintf(pat, len, "%s/plugin/**/*.vim", ffname); // NOLINT
- source_all_matches(pat);
- vim_snprintf(pat, len, "%s/plugin/**/*.lua", ffname); // NOLINT
- source_all_matches(pat);
+ vim_snprintf(pat, len, plugpat, ffname); // NOLINT
+ gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL);
char *cmd = xstrdup("g:did_load_filetypes");
- // If runtime/filetype.vim wasn't loaded yet, the scripts will be
+ // If runtime/filetype.lua wasn't loaded yet, the scripts will be
// found when it loads.
if (opt && eval_to_number(cmd) > 0) {
do_cmdline_cmd("augroup filetypedetect");
vim_snprintf(pat, len, ftpat, ffname);
- source_all_matches(pat);
- vim_snprintf((char *)pat, len, "%s/ftdetect/*.lua", ffname); // NOLINT
- source_all_matches(pat);
+ gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL);
do_cmdline_cmd("augroup END");
}
xfree(cmd);
@@ -1051,48 +1110,68 @@ static int APP_ADD_DIR;
static int APP_LOAD;
static int APP_BOTH;
-static void add_pack_plugin(bool opt, char *fname, void *cookie)
+static void add_pack_plugins(bool opt, int num_fnames, char **fnames, bool all, void *cookie)
{
+ bool did_one = false;
+
if (cookie != &APP_LOAD) {
char *buf = xmalloc(MAXPATHL);
- bool found = false;
-
- const char *p = (const char *)p_rtp;
- while (*p != NUL) {
- copy_option_part((char **)&p, buf, MAXPATHL, ",");
- if (path_fnamecmp(buf, fname) == 0) {
- found = true;
+ for (int i = 0; i < num_fnames; i++) {
+ bool found = false;
+
+ const char *p = p_rtp;
+ while (*p != NUL) {
+ copy_option_part((char **)&p, buf, MAXPATHL, ",");
+ if (path_fnamecmp(buf, fnames[i]) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ // directory is not yet in 'runtimepath', add it
+ if (add_pack_dir_to_rtp(fnames[i], false) == FAIL) {
+ xfree(buf);
+ return;
+ }
+ }
+ did_one = true;
+ if (!all) {
break;
}
}
xfree(buf);
- if (!found) {
- // directory is not yet in 'runtimepath', add it
- if (add_pack_dir_to_rtp(fname, false) == FAIL) {
- return;
- }
- }
+ }
+
+ if (!all && did_one) {
+ return;
}
if (cookie != &APP_ADD_DIR) {
- load_pack_plugin(opt, fname);
+ for (int i = 0; i < num_fnames; i++) {
+ load_pack_plugin(opt, fnames[i]);
+ if (!all) {
+ break;
+ }
+ }
}
}
-static void add_start_pack_plugin(char *fname, void *cookie)
+static bool add_start_pack_plugins(int num_fnames, char **fnames, bool all, void *cookie)
{
- add_pack_plugin(false, fname, cookie);
+ add_pack_plugins(false, num_fnames, fnames, all, cookie);
+ return num_fnames > 0;
}
-static void add_opt_pack_plugin(char *fname, void *cookie)
+static bool add_opt_pack_plugins(int num_fnames, char **fnames, bool all, void *cookie)
{
- add_pack_plugin(true, fname, cookie);
+ add_pack_plugins(true, num_fnames, fnames, all, cookie);
+ return num_fnames > 0;
}
/// Add all packages in the "start" directory to 'runtimepath'.
void add_pack_start_dirs(void)
{
- do_in_path(p_pp, NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL);
+ do_in_path(p_pp, "", NULL, DIP_ALL + DIP_DIR, add_pack_start_dir, NULL);
}
static bool pack_has_entries(char *buf)
@@ -1106,30 +1185,38 @@ static bool pack_has_entries(char *buf)
return num_files > 0;
}
-static void add_pack_start_dir(char *fname, void *cookie)
+static bool add_pack_start_dir(int num_fnames, char **fnames, bool all, void *cookie)
{
static char buf[MAXPATHL];
- char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT
- for (int i = 0; i < 2; i++) {
- if (strlen(fname) + strlen(start_pat[i]) + 1 > MAXPATHL) {
- continue;
+ for (int i = 0; i < num_fnames; i++) {
+ char *(start_pat[]) = { "/start/*", "/pack/*/start/*" }; // NOLINT
+ for (int j = 0; j < 2; j++) {
+ if (strlen(fnames[i]) + strlen(start_pat[j]) + 1 > MAXPATHL) {
+ continue;
+ }
+ xstrlcpy(buf, fnames[i], MAXPATHL);
+ xstrlcat(buf, start_pat[j], sizeof buf);
+ if (pack_has_entries(buf)) {
+ add_pack_dir_to_rtp(buf, true);
+ }
}
- xstrlcpy(buf, fname, MAXPATHL);
- xstrlcat(buf, start_pat[i], sizeof buf);
- if (pack_has_entries(buf)) {
- add_pack_dir_to_rtp(buf, true);
+
+ if (!all) {
+ break;
}
}
+
+ return num_fnames > 1;
}
/// Load plugins from all packages in the "start" directory.
void load_start_packages(void)
{
did_source_packages = true;
- do_in_path(p_pp, "pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT
- add_start_pack_plugin, &APP_LOAD);
- do_in_path(p_pp, "start/*", DIP_ALL + DIP_DIR, // NOLINT
- add_start_pack_plugin, &APP_LOAD);
+ do_in_path(p_pp, "", "pack/*/start/*", DIP_ALL + DIP_DIR, // NOLINT
+ add_start_pack_plugins, &APP_LOAD);
+ do_in_path(p_pp, "", "start/*", DIP_ALL + DIP_DIR, // NOLINT
+ add_start_pack_plugins, &APP_LOAD);
}
// ":packloadall"
@@ -1150,17 +1237,16 @@ void load_plugins(void)
{
if (p_lpl) {
char *rtp_copy = p_rtp;
- char *const plugin_pattern_vim = "plugin/**/*.vim"; // NOLINT
- char *const plugin_pattern_lua = "plugin/**/*.lua"; // NOLINT
+ char *const plugin_pattern = "plugin/**/*"; // NOLINT
if (!did_source_packages) {
rtp_copy = xstrdup(p_rtp);
add_pack_start_dirs();
}
- // don't use source_runtime() yet so we can check for :packloadall below
- source_in_path(rtp_copy, plugin_pattern_vim, DIP_ALL | DIP_NOAFTER);
- source_in_path(rtp_copy, plugin_pattern_lua, DIP_ALL | DIP_NOAFTER);
+ // Don't use source_runtime_vim_lua() yet so we can check for :packloadall below.
+ // NB: after calling this "rtp_copy" may have been freed if it wasn't copied.
+ source_in_path_vim_lua(rtp_copy, plugin_pattern, DIP_ALL | DIP_NOAFTER);
TIME_MSG("loading rtp plugins");
// Only source "start" packages if not done already with a :packloadall
@@ -1171,8 +1257,7 @@ void load_plugins(void)
}
TIME_MSG("loading packages");
- source_runtime(plugin_pattern_vim, DIP_ALL | DIP_AFTER);
- source_runtime(plugin_pattern_lua, DIP_ALL | DIP_AFTER);
+ source_runtime_vim_lua(plugin_pattern, DIP_ALL | DIP_AFTER);
TIME_MSG("loading after plugins");
}
}
@@ -1180,7 +1265,7 @@ void load_plugins(void)
/// ":packadd[!] {name}"
void ex_packadd(exarg_T *eap)
{
- static const char *plugpat = "pack/*/%s/%s"; // NOLINT
+ static const char plugpat[] = "pack/*/%s/%s"; // NOLINT
int res = OK;
// Round 1: use "start", round 2: use "opt".
@@ -1190,14 +1275,15 @@ void ex_packadd(exarg_T *eap)
continue;
}
- const size_t len = strlen(plugpat) + strlen(eap->arg) + 5;
+ const size_t len = sizeof(plugpat) + strlen(eap->arg) + 5;
char *pat = xmallocz(len);
vim_snprintf(pat, len, plugpat, round == 1 ? "start" : "opt", eap->arg);
// The first round don't give a "not found" error, in the second round
// only when nothing was found in the first round.
res =
- do_in_path(p_pp, pat, DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0),
- round == 1 ? add_start_pack_plugin : add_opt_pack_plugin,
+ do_in_path(p_pp, "", pat,
+ DIP_ALL + DIP_DIR + (round == 2 && res == FAIL ? DIP_ERR : 0),
+ round == 1 ? add_start_pack_plugins : add_opt_pack_plugins,
eap->forceit ? &APP_ADD_DIR : &APP_BOTH);
xfree(pat);
}
@@ -1216,9 +1302,9 @@ static void ExpandRTDir_int(char *pat, size_t pat_len, int flags, bool keep_ext,
bool expand_dirs = false;
if (*dirnames[i] == NUL) { // empty dir used for :runtime
- snprintf(tail, tail_buflen, "%s*.\\(vim\\|lua\\)", pat);
+ snprintf(tail, tail_buflen, "%s*.{vim,lua}", pat);
} else {
- snprintf(tail, tail_buflen, "%s/%s*.\\(vim\\|lua\\)", dirnames[i], pat);
+ snprintf(tail, tail_buflen, "%s/%s*.{vim,lua}", dirnames[i], pat);
}
expand:
@@ -1292,11 +1378,11 @@ expand:
/// Expand color scheme, compiler or filetype names.
/// Search from 'runtimepath':
-/// 'runtimepath'/{dirnames}/{pat}.(vim|lua)
+/// 'runtimepath'/{dirnames}/{pat}.{vim,lua}
/// When "flags" has DIP_START: search also from "start" of 'packpath':
-/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.(vim|lua)
+/// 'packpath'/pack/*/start/*/{dirnames}/{pat}.{vim,lua}
/// When "flags" has DIP_OPT: search also from "opt" of 'packpath':
-/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.(vim|lua)
+/// 'packpath'/pack/*/opt/*/{dirnames}/{pat}.{vim,lua}
/// "dirnames" is an array with one or more directory names.
int ExpandRTDir(char *pat, int flags, int *num_file, char ***file, char *dirnames[])
{
@@ -1437,7 +1523,6 @@ static inline size_t compute_double_env_sep_len(const char *const val, const siz
return ret;
}
-#define NVIM_SIZE (sizeof("nvim") - 1)
/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one
///
/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
@@ -1466,6 +1551,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
return dest;
}
const void *iter = NULL;
+ const char *appname = get_appname();
+ const size_t appname_len = strlen(appname);
do {
size_t dir_len;
const char *dir;
@@ -1476,8 +1563,8 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
- memmove(dest, "nvim", NVIM_SIZE);
- dest += NVIM_SIZE;
+ memmove(dest, appname, appname_len);
+ dest += appname_len;
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -1531,14 +1618,18 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
+ assert(appname_len < (IOSIZE - sizeof("-data")));
+ xstrlcpy(IObuff, appname, appname_len + 1);
#if defined(MSWIN)
- size_t size = (type == kXDGDataHome ? sizeof("nvim-data") - 1 : NVIM_SIZE);
- memmove(dest, (type == kXDGDataHome ? "nvim-data" : "nvim"), size);
- dest += size;
-#else
- memmove(dest, "nvim", NVIM_SIZE);
- dest += NVIM_SIZE;
+ if (type == kXDGDataHome || type == kXDGStateHome) {
+ xstrlcat(IObuff, "-data", IOSIZE);
+ appname_len += 5;
+ }
#endif
+ xstrlcpy(dest, IObuff, appname_len + 1);
+ dest += appname_len;
if (suf1 != NULL) {
*dest++ = PATHSEP;
memmove(dest, suf1, len1);
@@ -1566,7 +1657,7 @@ char *get_lib_dir(void)
// Find library path relative to the nvim binary: ../lib/nvim/
char exe_name[MAXPATHL];
vim_get_prefix_from_exepath(exe_name);
- if (append_path(exe_name, "lib" _PATHSEPSTR "nvim", MAXPATHL) == OK) {
+ if (append_path(exe_name, "lib/nvim", MAXPATHL) == OK) {
return xstrdup(exe_name);
}
return NULL;
@@ -1583,11 +1674,11 @@ char *runtimepath_default(bool clean_arg)
{
size_t rtp_size = 0;
char *const data_home = clean_arg
- ? NULL
- : stdpaths_get_xdg_var(kXDGDataHome);
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGDataHome);
char *const config_home = clean_arg
- ? NULL
- : stdpaths_get_xdg_var(kXDGConfigHome);
+ ? NULL
+ : stdpaths_get_xdg_var(kXDGConfigHome);
char *const vimruntime = vim_getenv("VIMRUNTIME");
char *const libdir = get_lib_dir();
char *const data_dirs = stdpaths_get_xdg_var(kXDGDataDirs);
@@ -1598,16 +1689,17 @@ char *runtimepath_default(bool clean_arg)
size_t config_len = 0;
size_t vimruntime_len = 0;
size_t libdir_len = 0;
+ const char *appname = get_appname();
+ size_t appname_len = strlen(appname);
if (data_home != NULL) {
data_len = strlen(data_home);
- if (data_len != 0) {
+ size_t nvim_data_size = appname_len;
#if defined(MSWIN)
- size_t nvim_size = (sizeof("nvim-data") - 1);
-#else
- size_t nvim_size = NVIM_SIZE;
+ nvim_data_size += sizeof("-data") - 1; // -1: NULL byte should be ignored
#endif
+ if (data_len != 0) {
rtp_size += ((data_len + memcnt(data_home, ',', data_len)
- + nvim_size + 1 + SITE_SIZE + 1
+ + nvim_data_size + 1 + SITE_SIZE + 1
+ !after_pathsep(data_home, data_home + data_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -1616,7 +1708,7 @@ char *runtimepath_default(bool clean_arg)
config_len = strlen(config_home);
if (config_len != 0) {
rtp_size += ((config_len + memcnt(config_home, ',', config_len)
- + NVIM_SIZE + 1
+ + appname_len + 1
+ !after_pathsep(config_home, config_home + config_len)) * 2
+ AFTER_SIZE + 1);
}
@@ -1634,9 +1726,9 @@ char *runtimepath_default(bool clean_arg)
}
}
rtp_size += compute_double_env_sep_len(data_dirs,
- NVIM_SIZE + 1 + SITE_SIZE + 1,
+ appname_len + 1 + SITE_SIZE + 1,
AFTER_SIZE + 1);
- rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1,
+ rtp_size += compute_double_env_sep_len(config_dirs, appname_len + 1,
AFTER_SIZE + 1);
char *rtp = NULL;
if (rtp_size == 0) {
@@ -1677,7 +1769,6 @@ freeall:
return rtp;
}
-#undef NVIM_SIZE
static void cmd_source(char *fname, exarg_T *eap)
{
@@ -1695,7 +1786,7 @@ static void cmd_source(char *fname, exarg_T *eap)
|| eap->cstack->cs_idx >= 0);
// ":source" read ex commands
- } else if (do_source(fname, false, DOSO_NONE) == FAIL) {
+ } else if (do_source(fname, false, DOSO_NONE, NULL) == FAIL) {
semsg(_(e_notopen), fname);
}
}
@@ -1759,7 +1850,7 @@ static FILE *fopen_noinh_readbin(char *filename)
return fdopen(fd_tmp, READBIN);
}
-/// Concatenate VimL line if it starts with a line continuation into a growarray
+/// Concatenate Vimscript line if it starts with a line continuation into a growarray
/// (excluding the continuation chars and leading whitespace)
///
/// @note Growsize of the growarray may be changed to speed up concatenations!
@@ -1775,7 +1866,7 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, c
size_t len)
FUNC_ATTR_NONNULL_ALL
{
- const char *const line = skipwhite_len((char *)p, len);
+ const char *const line = skipwhite_len(p, len);
len -= (size_t)(line - p);
// Skip lines starting with '\" ', concat lines starting with '\'
if (len >= 3 && strncmp(line, "\"\\ ", 3) == 0) {
@@ -1823,7 +1914,7 @@ static char *get_str_line(int c, void *cookie, int indent, bool do_concat)
if (!concat_continued_line(&ga, 400, line, (size_t)(next_eol - line))) {
break;
}
- eol = (char *)next_eol;
+ eol = next_eol;
}
}
ga_append(&ga, NUL);
@@ -1846,13 +1937,18 @@ scriptitem_T *new_script_item(char *const name, scid_T *const sid_out)
}
ga_grow(&script_items, sid - script_items.ga_len);
while (script_items.ga_len < sid) {
+ scriptitem_T *si = xcalloc(1, sizeof(scriptitem_T));
script_items.ga_len++;
- SCRIPT_ITEM(script_items.ga_len).sn_name = NULL;
- SCRIPT_ITEM(script_items.ga_len).sn_prof_on = false;
+ SCRIPT_ITEM(script_items.ga_len) = si;
+ si->sn_name = NULL;
+
+ // Allocate the local script variables to use for this script.
+ new_script_vars(script_items.ga_len);
+
+ si->sn_prof_on = false;
}
- SCRIPT_ITEM(sid).sn_name = name;
- new_script_vars(sid); // Allocate the local script variables to use for this script.
- return &SCRIPT_ITEM(sid);
+ SCRIPT_ITEM(sid)->sn_name = name;
+ return SCRIPT_ITEM(sid);
}
static int source_using_linegetter(void *cookie, LineGetter fgetline, const char *traceback_name)
@@ -1864,7 +1960,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char
if (save_sourcing_name == NULL) {
sname = (char *)traceback_name;
} else {
- snprintf((char *)sourcing_name_buf, sizeof(sourcing_name_buf),
+ snprintf(sourcing_name_buf, sizeof(sourcing_name_buf),
"%s called at %s:%" PRIdLINENR, traceback_name, save_sourcing_name,
save_sourcing_lnum);
sname = sourcing_name_buf;
@@ -1910,13 +2006,11 @@ static void cmd_source_buffer(const exarg_T *const eap)
.buf = ga.ga_data,
.offset = 0,
};
- if (curbuf->b_fname
- && path_with_extension((const char *)curbuf->b_fname, "lua")) {
- nlua_source_using_linegetter(get_str_line, (void *)&cookie,
- ":source (no file)");
+ if (strequal(curbuf->b_p_ft, "lua")
+ || (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua"))) {
+ nlua_source_using_linegetter(get_str_line, (void *)&cookie, ":source (no file)");
} else {
- source_using_linegetter((void *)&cookie, get_str_line,
- ":source (no file)");
+ source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)");
}
ga_clear(&ga);
}
@@ -1943,13 +2037,14 @@ int do_source_str(const char *cmd, const char *traceback_name)
/// @param fname
/// @param check_other check for .vimrc and _vimrc
/// @param is_vimrc DOSO_ value
+/// @param ret_sid if not NULL and we loaded the script before, don't load it again
///
/// @return FAIL if file could not be opened, OK otherwise
-int do_source(char *fname, int check_other, int is_vimrc)
+///
+/// If a scriptitem_T was found or created "*ret_sid" is set to the SID.
+int do_source(char *fname, int check_other, int is_vimrc, int *ret_sid)
{
struct source_cookie cookie;
- char *p;
- char *fname_exp;
uint8_t *firstline = NULL;
int retval = FAIL;
int save_debug_break_level = debug_break_level;
@@ -1957,17 +2052,26 @@ int do_source(char *fname, int check_other, int is_vimrc)
proftime_T wait_start;
bool trigger_source_post = false;
- p = expand_env_save(fname);
+ char *p = expand_env_save(fname);
if (p == NULL) {
return retval;
}
- fname_exp = fix_fname(p);
+ char *fname_exp = fix_fname(p);
xfree(p);
if (fname_exp == NULL) {
return retval;
}
if (os_isdir(fname_exp)) {
- smsg(_("Cannot source a directory: \"%s\""), fname);
+ smsg(0, _("Cannot source a directory: \"%s\""), fname);
+ goto theend;
+ }
+
+ // See if we loaded this script before.
+ int sid = find_script_by_name(fname_exp);
+ if (sid > 0 && ret_sid != NULL) {
+ // Already loaded and no need to load again, return here.
+ *ret_sid = sid;
+ retval = OK;
goto theend;
}
@@ -2002,9 +2106,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (p_verbose > 1) {
verbose_enter();
if (SOURCING_NAME == NULL) {
- smsg(_("could not source \"%s\""), fname);
+ smsg(0, _("could not source \"%s\""), fname);
} else {
- smsg(_("line %" PRId64 ": could not source \"%s\""),
+ smsg(0, _("line %" PRId64 ": could not source \"%s\""),
(int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
@@ -2018,9 +2122,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (p_verbose > 1) {
verbose_enter();
if (SOURCING_NAME == NULL) {
- smsg(_("sourcing \"%s\""), fname);
+ smsg(0, _("sourcing \"%s\""), fname);
} else {
- smsg(_("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
+ smsg(0, _("line %" PRId64 ": sourcing \"%s\""), (int64_t)SOURCING_LNUM, fname);
}
verbose_leave();
}
@@ -2043,7 +2147,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
cookie.finished = false;
// Check if this script has a breakpoint.
- cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, (linenr_T)0);
+ cookie.breakpoint = dbg_find_breakpoint(true, fname_exp, 0);
cookie.fname = fname_exp;
cookie.dbg_tick = debug_tick;
@@ -2069,7 +2173,24 @@ int do_source(char *fname, int check_other, int is_vimrc)
save_funccal(&funccalp_entry);
const sctx_T save_current_sctx = current_sctx;
- si = get_current_script_id(&fname_exp, &current_sctx);
+
+ current_sctx.sc_lnum = 0;
+
+ // Always use a new sequence number.
+ current_sctx.sc_seq = ++last_current_SID_seq;
+
+ if (sid > 0) {
+ // loading the same script again
+ si = SCRIPT_ITEM(sid);
+ } else {
+ // It's new, generate a new SID.
+ si = new_script_item(fname_exp, &sid);
+ fname_exp = xstrdup(si->sn_name); // used for autocmd
+ if (ret_sid != NULL) {
+ *ret_sid = sid;
+ }
+ }
+ current_sctx.sc_sid = sid;
// Keep the sourcing name/lnum, for recursive calls.
estack_push(ETYPE_SCRIPT, si->sn_name, 0);
@@ -2091,12 +2212,12 @@ int do_source(char *fname, int check_other, int is_vimrc)
cookie.conv.vc_type = CONV_NONE; // no conversion
- if (path_with_extension((const char *)fname_exp, "lua")) {
+ if (path_with_extension(fname_exp, "lua")) {
const sctx_T current_sctx_backup = current_sctx;
current_sctx.sc_sid = SID_LUA;
current_sctx.sc_lnum = 0;
// Source the file as lua
- nlua_exec_file((const char *)fname_exp);
+ nlua_exec_file(fname_exp);
current_sctx = current_sctx_backup;
} else {
// Read the first line so we can check for a UTF-8 BOM.
@@ -2120,7 +2241,7 @@ int do_source(char *fname, int check_other, int is_vimrc)
if (l_do_profiling == PROF_YES) {
// Get "si" again, "script_items" may have been reallocated.
- si = &SCRIPT_ITEM(current_sctx.sc_sid);
+ si = SCRIPT_ITEM(current_sctx.sc_sid);
if (si->sn_prof_on) {
si->sn_pr_start = profile_end(si->sn_pr_start);
si->sn_pr_start = profile_sub_wait(wait_start, si->sn_pr_start);
@@ -2136,9 +2257,9 @@ int do_source(char *fname, int check_other, int is_vimrc)
estack_pop();
if (p_verbose > 1) {
verbose_enter();
- smsg(_("finished sourcing %s"), fname);
+ smsg(0, _("finished sourcing %s"), fname);
if (SOURCING_NAME != NULL) {
- smsg(_("continuing in %s"), SOURCING_NAME);
+ smsg(0, _("continuing in %s"), SOURCING_NAME);
}
verbose_leave();
}
@@ -2179,42 +2300,23 @@ theend:
return retval;
}
-/// Check if fname was sourced before to finds its SID.
-/// If it's new, generate a new SID.
-///
-/// @param[in,out] fnamep pointer to file path of script
-/// @param[out] ret_sctx sctx of this script
-scriptitem_T *get_current_script_id(char **fnamep, sctx_T *ret_sctx)
+/// Find an already loaded script "name".
+/// If found returns its script ID. If not found returns -1.
+int find_script_by_name(char *name)
{
- static int last_current_SID_seq = 0;
-
- sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq,
- .sc_lnum = 0,
- .sc_sid = 0 };
- scriptitem_T *si = NULL;
-
assert(script_items.ga_len >= 0);
- for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; script_sctx.sc_sid--) {
+ for (int sid = script_items.ga_len; sid > 0; sid--) {
// We used to check inode here, but that doesn't work:
// - If a script is edited and written, it may get a different
// inode number, even though to the user it is the same script.
// - If a script is deleted and another script is written, with a
// different name, the inode may be re-used.
- si = &SCRIPT_ITEM(script_sctx.sc_sid);
- if (si->sn_name != NULL && path_fnamecmp(si->sn_name, *fnamep) == 0) {
- // Found it!
- break;
+ scriptitem_T *si = SCRIPT_ITEM(sid);
+ if (si->sn_name != NULL && path_fnamecmp(si->sn_name, name) == 0) {
+ return sid;
}
}
- if (script_sctx.sc_sid == 0) {
- si = new_script_item(*fnamep, &script_sctx.sc_sid);
- *fnamep = xstrdup(si->sn_name);
- }
- if (ret_sctx != NULL) {
- *ret_sctx = script_sctx;
- }
-
- return si;
+ return -1;
}
/// ":scriptnames"
@@ -2226,7 +2328,7 @@ void ex_scriptnames(exarg_T *eap)
emsg(_(e_invarg));
} else {
if (eap->addr_count > 0) {
- eap->arg = SCRIPT_ITEM(eap->line2).sn_name;
+ eap->arg = SCRIPT_ITEM(eap->line2)->sn_name;
} else {
expand_env(eap->arg, NameBuff, MAXPATHL);
eap->arg = NameBuff;
@@ -2237,12 +2339,12 @@ void ex_scriptnames(exarg_T *eap)
}
for (int i = 1; i <= script_items.ga_len && !got_int; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true);
+ if (SCRIPT_ITEM(i)->sn_name != NULL) {
+ home_replace(NULL, SCRIPT_ITEM(i)->sn_name, NameBuff, MAXPATHL, true);
vim_snprintf(IObuff, IOSIZE, "%3d: %s", i, NameBuff);
if (!message_filtered(IObuff)) {
msg_putchar('\n');
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
line_breakcheck();
}
}
@@ -2254,8 +2356,8 @@ void ex_scriptnames(exarg_T *eap)
void scriptnames_slash_adjust(void)
{
for (int i = 1; i <= script_items.ga_len; i++) {
- if (SCRIPT_ITEM(i).sn_name != NULL) {
- slash_adjust(SCRIPT_ITEM(i).sn_name);
+ if (SCRIPT_ITEM(i)->sn_name != NULL) {
+ slash_adjust(SCRIPT_ITEM(i)->sn_name);
}
}
}
@@ -2289,7 +2391,7 @@ char *get_scriptname(LastSet last_set, bool *should_free)
case SID_STR:
return _("anonymous :source");
default: {
- char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid).sn_name;
+ char *const sname = SCRIPT_ITEM(last_set.script_ctx.sc_sid)->sn_name;
if (sname == NULL) {
snprintf(IObuff, IOSIZE, _("anonymous :source (script id %d)"),
last_set.script_ctx.sc_sid);
@@ -2307,17 +2409,126 @@ void free_scriptnames(void)
{
profile_reset();
-# define FREE_SCRIPTNAME(item) xfree((item)->sn_name)
- GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME);
+# define FREE_SCRIPTNAME(item) \
+ do { \
+ scriptitem_T *_si = *(item); \
+ /* the variables themselves are cleared in evalvars_clear() */ \
+ xfree(_si->sn_vars); \
+ xfree(_si->sn_name); \
+ ga_clear(&_si->sn_prl_ga); \
+ xfree(_si); \
+ } while (0) \
+
+ GA_DEEP_CLEAR(&script_items, scriptitem_T *, FREE_SCRIPTNAME);
}
#endif
+void free_autoload_scriptnames(void)
+{
+ ga_clear_strings(&ga_loaded);
+}
+
linenr_T get_sourced_lnum(LineGetter fgetline, void *cookie)
FUNC_ATTR_PURE
{
return fgetline == getsourceline
- ? ((struct source_cookie *)cookie)->sourcing_lnum
- : SOURCING_LNUM;
+ ? ((struct source_cookie *)cookie)->sourcing_lnum
+ : SOURCING_LNUM;
+}
+
+/// Return a List of script-local functions defined in the script with id "sid".
+static list_T *get_script_local_funcs(scid_T sid)
+{
+ hashtab_T *const functbl = func_tbl_get();
+ list_T *l = tv_list_alloc((ptrdiff_t)functbl->ht_used);
+
+ // Iterate through all the functions in the global function hash table
+ // looking for functions with script ID "sid".
+ HASHTAB_ITER(functbl, hi, {
+ const ufunc_T *const fp = HI2UF(hi);
+ // Add functions with script id == "sid"
+ if (fp->uf_script_ctx.sc_sid == sid) {
+ const char *const name = fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
+ tv_list_append_string(l, name, -1);
+ }
+ });
+
+ return l;
+}
+
+/// "getscriptinfo()" function
+void f_getscriptinfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, script_items.ga_len);
+
+ if (tv_check_for_opt_dict_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ list_T *l = rettv->vval.v_list;
+
+ regmatch_T regmatch = {
+ .regprog = NULL,
+ .rm_ic = p_ic,
+ };
+ bool filterpat = false;
+ varnumber_T sid = -1;
+
+ char *pat = NULL;
+ if (argvars[0].v_type == VAR_DICT) {
+ dictitem_T *sid_di = tv_dict_find(argvars[0].vval.v_dict, S_LEN("sid"));
+ if (sid_di != NULL) {
+ bool error = false;
+ sid = tv_get_number_chk(&sid_di->di_tv, &error);
+ if (error) {
+ return;
+ }
+ if (sid <= 0) {
+ semsg(_(e_invargNval), "sid", tv_get_string(&sid_di->di_tv));
+ return;
+ }
+ } else {
+ pat = tv_dict_get_string(argvars[0].vval.v_dict, "name", true);
+ if (pat != NULL) {
+ regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING);
+ }
+ if (regmatch.regprog != NULL) {
+ filterpat = true;
+ }
+ }
+ }
+
+ for (varnumber_T i = sid > 0 ? sid : 1;
+ (i == sid || sid <= 0) && i <= script_items.ga_len; i++) {
+ scriptitem_T *si = SCRIPT_ITEM(i);
+
+ if (si->sn_name == NULL) {
+ continue;
+ }
+
+ if (filterpat && !vim_regexec(&regmatch, si->sn_name, 0)) {
+ continue;
+ }
+
+ dict_T *d = tv_dict_alloc();
+ tv_list_append_dict(l, d);
+ tv_dict_add_str(d, S_LEN("name"), si->sn_name);
+ tv_dict_add_nr(d, S_LEN("sid"), i);
+ tv_dict_add_nr(d, S_LEN("version"), 1);
+ // Vim9 autoload script (:h vim9-autoload), not applicable to Nvim.
+ tv_dict_add_bool(d, S_LEN("autoload"), false);
+
+ // When a script ID is specified, return information about only the
+ // specified script, and add the script-local variables and functions.
+ if (sid > 0) {
+ dict_T *var_dict = tv_dict_copy(NULL, &si->sn_vars->sv_dict, true, get_copyID());
+ tv_dict_add_dict(d, S_LEN("variables"), var_dict);
+ tv_dict_add_list(d, S_LEN("functions"), get_script_local_funcs((scid_T)sid));
+ }
+ }
+
+ vim_regfree(regmatch.regprog);
+ xfree(pat);
}
/// Get one full line from a sourced file.
@@ -2329,7 +2540,6 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
{
struct source_cookie *sp = (struct source_cookie *)cookie;
char *line;
- char *p;
// If breakpoints have been added/deleted need to check for it.
if (sp->dbg_tick < debug_tick) {
@@ -2359,6 +2569,7 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
// Only concatenate lines starting with a \ when 'cpoptions' doesn't
// contain the 'C' flag.
if (line != NULL && do_concat && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) {
+ char *p;
// compensate for the one line read-ahead
sp->sourcing_lnum--;
@@ -2386,10 +2597,8 @@ char *getsourceline(int c, void *cookie, int indent, bool do_concat)
}
if (line != NULL && sp->conv.vc_type != CONV_NONE) {
- char *s;
-
// Convert the encoding of the script line.
- s = string_convert(&sp->conv, line, NULL);
+ char *s = string_convert(&sp->conv, line, NULL);
if (s != NULL) {
xfree(line);
line = s;
@@ -2423,7 +2632,7 @@ static char *get_one_sourceline(struct source_cookie *sp)
// Loop until there is a finished line (or end-of-file).
sp->sourcing_lnum++;
- for (;;) {
+ while (true) {
// make room to read at least 120 (more) characters
ga_grow(&ga, 120);
buf = ga.ga_data;
@@ -2551,8 +2760,6 @@ void ex_finish(exarg_T *eap)
/// an extra do_cmdline(). "reanimate" is used in the latter case.
void do_finish(exarg_T *eap, int reanimate)
{
- int idx;
-
if (reanimate) {
((struct source_cookie *)getline_cookie(eap->getline,
eap->cookie))->finished = false;
@@ -2562,7 +2769,7 @@ void do_finish(exarg_T *eap, int reanimate)
// not in its finally clause (which then is to be executed next) is found.
// In this case, make the ":finish" pending for execution at the ":endtry".
// Otherwise, finish normally.
- idx = cleanup_conditionals(eap->cstack, 0, true);
+ int idx = cleanup_conditionals(eap->cstack, 0, true);
if (idx >= 0) {
eap->cstack->cs_pending[idx] = CSTP_FINISH;
report_make_pending(CSTP_FINISH, NULL);
@@ -2580,3 +2787,79 @@ bool source_finished(LineGetter fgetline, void *cookie)
return getline_equal(fgetline, cookie, getsourceline)
&& ((struct source_cookie *)getline_cookie(fgetline, cookie))->finished;
}
+
+/// Return the autoload script name for a function or variable name
+/// Caller must make sure that "name" contains AUTOLOAD_CHAR.
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+///
+/// @return [allocated] autoload script name.
+char *autoload_name(const char *const name, const size_t name_len)
+ FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // Get the script file name: replace '#' with '/', append ".vim".
+ char *const scriptname = xmalloc(name_len + sizeof("autoload/.vim"));
+ memcpy(scriptname, "autoload/", sizeof("autoload/") - 1);
+ memcpy(scriptname + sizeof("autoload/") - 1, name, name_len);
+ size_t auchar_idx = 0;
+ for (size_t i = sizeof("autoload/") - 1;
+ i - sizeof("autoload/") + 1 < name_len;
+ i++) {
+ if (scriptname[i] == AUTOLOAD_CHAR) {
+ scriptname[i] = '/';
+ auchar_idx = i;
+ }
+ }
+ memcpy(scriptname + auchar_idx, ".vim", sizeof(".vim"));
+
+ return scriptname;
+}
+
+/// If name has a package name try autoloading the script for it
+///
+/// @param[in] name Variable/function name.
+/// @param[in] name_len Name length.
+/// @param[in] reload If true, load script again when already loaded.
+///
+/// @return true if a package was loaded.
+bool script_autoload(const char *const name, const size_t name_len, const bool reload)
+{
+ // If there is no '#' after name[0] there is no package name.
+ const char *p = memchr(name, AUTOLOAD_CHAR, name_len);
+ if (p == NULL || p == name) {
+ return false;
+ }
+
+ bool ret = false;
+ char *tofree = autoload_name(name, name_len);
+ char *scriptname = tofree;
+
+ // Find the name in the list of previously loaded package names. Skip
+ // "autoload/", it's always the same.
+ int i = 0;
+ for (; i < ga_loaded.ga_len; i++) {
+ if (strcmp(((char **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) {
+ break;
+ }
+ }
+ if (!reload && i < ga_loaded.ga_len) {
+ ret = false; // Was loaded already.
+ } else {
+ // Remember the name if it wasn't loaded already.
+ if (i == ga_loaded.ga_len) {
+ GA_APPEND(char *, &ga_loaded, scriptname);
+ tofree = NULL;
+ }
+
+ // Try loading the package from $VIMRUNTIME/autoload/<name>.vim
+ // Use "ret_sid" to avoid loading the same script again.
+ int ret_sid;
+ if (do_in_runtimepath(scriptname, 0, source_callback, &ret_sid) == OK) {
+ ret = true;
+ }
+ }
+
+ xfree(tofree);
+ return ret;
+}
diff --git a/src/nvim/runtime.h b/src/nvim/runtime.h
index 97063b900c..87e4436618 100644
--- a/src/nvim/runtime.h
+++ b/src/nvim/runtime.h
@@ -1,43 +1,16 @@
-#ifndef NVIM_RUNTIME_H
-#define NVIM_RUNTIME_H
+#pragma once
-#include <stdbool.h>
+#include <stddef.h> // IWYU pragma: keep
-#include "klib/kvec.h"
-#include "nvim/autocmd.h"
-#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/ex_eval_defs.h"
-#include "nvim/garray.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-typedef enum {
- ETYPE_TOP, ///< toplevel
- ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx
- ETYPE_UFUNC, ///< user function, use es_info.ufunc
- ETYPE_AUCMD, ///< autocomand, use es_info.aucmd
- ETYPE_MODELINE, ///< modeline, use es_info.sctx
- ETYPE_EXCEPT, ///< exception, use es_info.exception
- ETYPE_ARGS, ///< command line argument
- ETYPE_ENV, ///< environment variable
- ETYPE_INTERNAL, ///< internal operation
- ETYPE_SPELL, ///< loading spell file
-} etype_T;
-
-/// Entry in the execution stack "exestack".
-typedef struct {
- linenr_T es_lnum; ///< replaces "sourcing_lnum"
- char *es_name; ///< replaces "sourcing_name"
- etype_T es_type;
- union {
- sctx_T *sctx; ///< script and modeline info
- ufunc_T *ufunc; ///< function info
- AutoPatCmd *aucmd; ///< autocommand info
- except_T *except; ///< exception info
- } es_info;
-} estack_T;
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/runtime_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Stack of execution contexts. Each entry is an estack_T.
/// Current context is at ga_len - 1.
@@ -47,67 +20,30 @@ extern garray_T exestack;
/// line number in the message source or zero
#define SOURCING_LNUM (((estack_T *)exestack.ga_data)[exestack.ga_len - 1].es_lnum)
-/// Argument for estack_sfile().
-typedef enum {
- ESTACK_NONE,
- ESTACK_SFILE,
- ESTACK_STACK,
- ESTACK_SCRIPT,
-} estack_arg_T;
-
-typedef struct scriptitem_S {
- char *sn_name;
- bool sn_prof_on; ///< true when script is/was profiled
- bool sn_pr_force; ///< forceit: profile functions in this script
- proftime_T sn_pr_child; ///< time set when going into first child
- int sn_pr_nest; ///< nesting for sn_pr_child
- // profiling the script as a whole
- int sn_pr_count; ///< nr of times sourced
- proftime_T sn_pr_total; ///< time spent in script + children
- proftime_T sn_pr_self; ///< time spent in script itself
- proftime_T sn_pr_start; ///< time at script start
- proftime_T sn_pr_children; ///< time in children after script start
- // profiling the script per line
- garray_T sn_prl_ga; ///< things stored for every line
- proftime_T sn_prl_start; ///< start time for current line
- proftime_T sn_prl_children; ///< time spent in children for this line
- proftime_T sn_prl_wait; ///< wait start time for current line
- linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
- int sn_prl_execed; ///< line being timed was executed
-} scriptitem_T;
-
/// Growarray to store info about already sourced scripts.
extern garray_T script_items;
-#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1])
+#define SCRIPT_ITEM(id) (((scriptitem_T **)script_items.ga_data)[(id) - 1])
#define SCRIPT_ID_VALID(id) ((id) > 0 && (id) <= script_items.ga_len)
-typedef void (*DoInRuntimepathCB)(char *, void *);
-
-typedef struct {
- char *path;
- bool after;
- TriState has_lua;
-} SearchPathItem;
-
-typedef kvec_t(SearchPathItem) RuntimeSearchPath;
-typedef kvec_t(char *) CharVec;
-
-// last argument for do_source()
-#define DOSO_NONE 0
-#define DOSO_VIMRC 1 // loading vimrc file
-
-// Used for flags in do_in_path()
-#define DIP_ALL 0x01 // all matches, not just the first one
-#define DIP_DIR 0x02 // find directories instead of files
-#define DIP_ERR 0x04 // give an error message when none found
-#define DIP_START 0x08 // also use "start" directory in 'packpath'
-#define DIP_OPT 0x10 // also use "opt" directory in 'packpath'
-#define DIP_NORTP 0x20 // do not use 'runtimepath'
-#define DIP_NOAFTER 0x40 // skip "after" directories
-#define DIP_AFTER 0x80 // only use "after" directories
-#define DIP_DIRFILE 0x200 // find both files and directories
+/// last argument for do_source()
+enum {
+ DOSO_NONE = 0,
+ DOSO_VIMRC = 1, ///< loading vimrc file
+};
+
+/// Used for flags in do_in_path()
+enum {
+ DIP_ALL = 0x01, ///< all matches, not just the first one
+ DIP_DIR = 0x02, ///< find directories instead of files
+ DIP_ERR = 0x04, ///< give an error message when none found
+ DIP_START = 0x08, ///< also use "start" directory in 'packpath'
+ DIP_OPT = 0x10, ///< also use "opt" directory in 'packpath'
+ DIP_NORTP = 0x20, ///< do not use 'runtimepath'
+ DIP_NOAFTER = 0x40, ///< skip "after" directories
+ DIP_AFTER = 0x80, ///< only use "after" directories
+ DIP_DIRFILE = 0x200, ///< find both files and directories
+};
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "runtime.h.generated.h"
#endif
-#endif // NVIM_RUNTIME_H
diff --git a/src/nvim/runtime_defs.h b/src/nvim/runtime_defs.h
new file mode 100644
index 0000000000..169cadfb94
--- /dev/null
+++ b/src/nvim/runtime_defs.h
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <stdbool.h>
+
+#include "nvim/autocmd_defs.h"
+#include "nvim/eval/typval_defs.h"
+#include "nvim/ex_eval_defs.h"
+#include "nvim/garray_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
+
+typedef enum {
+ ETYPE_TOP, ///< toplevel
+ ETYPE_SCRIPT, ///< sourcing script, use es_info.sctx
+ ETYPE_UFUNC, ///< user function, use es_info.ufunc
+ ETYPE_AUCMD, ///< autocomand, use es_info.aucmd
+ ETYPE_MODELINE, ///< modeline, use es_info.sctx
+ ETYPE_EXCEPT, ///< exception, use es_info.exception
+ ETYPE_ARGS, ///< command line argument
+ ETYPE_ENV, ///< environment variable
+ ETYPE_INTERNAL, ///< internal operation
+ ETYPE_SPELL, ///< loading spell file
+} etype_T;
+
+/// Entry in the execution stack "exestack".
+typedef struct {
+ linenr_T es_lnum; ///< replaces "sourcing_lnum"
+ char *es_name; ///< replaces "sourcing_name"
+ etype_T es_type;
+ union {
+ sctx_T *sctx; ///< script and modeline info
+ ufunc_T *ufunc; ///< function info
+ AutoPatCmd *aucmd; ///< autocommand info
+ except_T *except; ///< exception info
+ } es_info;
+} estack_T;
+
+/// Argument for estack_sfile().
+typedef enum {
+ ESTACK_NONE,
+ ESTACK_SFILE,
+ ESTACK_STACK,
+ ESTACK_SCRIPT,
+} estack_arg_T;
+
+/// Holds the hashtab with variables local to each sourced script.
+/// Each item holds a variable (nameless) that points to the dict_T.
+typedef struct {
+ ScopeDictDictItem sv_var;
+ dict_T sv_dict;
+} scriptvar_T;
+
+typedef struct {
+ scriptvar_T *sn_vars; ///< stores s: variables for this script
+
+ char *sn_name;
+ bool sn_prof_on; ///< true when script is/was profiled
+ bool sn_pr_force; ///< forceit: profile functions in this script
+ proftime_T sn_pr_child; ///< time set when going into first child
+ int sn_pr_nest; ///< nesting for sn_pr_child
+ // profiling the script as a whole
+ int sn_pr_count; ///< nr of times sourced
+ proftime_T sn_pr_total; ///< time spent in script + children
+ proftime_T sn_pr_self; ///< time spent in script itself
+ proftime_T sn_pr_start; ///< time at script start
+ proftime_T sn_pr_children; ///< time in children after script start
+ // profiling the script per line
+ garray_T sn_prl_ga; ///< things stored for every line
+ proftime_T sn_prl_start; ///< start time for current line
+ proftime_T sn_prl_children; ///< time spent in children for this line
+ proftime_T sn_prl_wait; ///< wait start time for current line
+ linenr_T sn_prl_idx; ///< index of line being timed; -1 if none
+ int sn_prl_execed; ///< line being timed was executed
+} scriptitem_T;
+
+typedef bool (*DoInRuntimepathCB)(int, char **, bool, void *);
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
deleted file mode 100644
index 05da6e0ef1..0000000000
--- a/src/nvim/screen.c
+++ /dev/null
@@ -1,1110 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// screen.c: Lower level code for displaying on the screen.
-// grid.c contains some other lower-level code.
-
-// Output to the screen (console, terminal emulator or GUI window) is minimized
-// by remembering what is already on the screen, and only updating the parts
-// that changed.
-
-#include <assert.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "nvim/ascii.h"
-#include "nvim/buffer.h"
-#include "nvim/charset.h"
-#include "nvim/cursor.h"
-#include "nvim/eval.h"
-#include "nvim/fold.h"
-#include "nvim/getchar.h"
-#include "nvim/gettext.h"
-#include "nvim/globals.h"
-#include "nvim/grid.h"
-#include "nvim/grid_defs.h"
-#include "nvim/highlight.h"
-#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
-#include "nvim/memory.h"
-#include "nvim/message.h"
-#include "nvim/move.h"
-#include "nvim/normal.h"
-#include "nvim/option.h"
-#include "nvim/os/os.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/profile.h"
-#include "nvim/regexp.h"
-#include "nvim/screen.h"
-#include "nvim/search.h"
-#include "nvim/state.h"
-#include "nvim/statusline.h"
-#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/ui.h"
-#include "nvim/vim.h"
-#include "nvim/window.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "screen.c.generated.h"
-#endif
-
-static char e_conflicts_with_value_of_listchars[] = N_("E834: Conflicts with value of 'listchars'");
-static char e_conflicts_with_value_of_fillchars[] = N_("E835: Conflicts with value of 'fillchars'");
-
-/// Return true if the cursor line in window "wp" may be concealed, according
-/// to the 'concealcursor' option.
-bool conceal_cursor_line(const win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- int c;
-
- if (*wp->w_p_cocu == NUL) {
- return false;
- }
- if (get_real_state() & MODE_VISUAL) {
- c = 'v';
- } else if (State & MODE_INSERT) {
- c = 'i';
- } else if (State & MODE_NORMAL) {
- c = 'n';
- } else if (State & MODE_CMDLINE) {
- c = 'c';
- } else {
- return false;
- }
- return vim_strchr(wp->w_p_cocu, c) != NULL;
-}
-
-/// Whether cursorline is drawn in a special way
-///
-/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows.
-bool win_cursorline_standout(const win_T *wp)
- FUNC_ATTR_NONNULL_ALL
-{
- return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp));
-}
-
-/// Returns width of the signcolumn that should be used for the whole window
-///
-/// @param wp window we want signcolumn width from
-/// @return max width of signcolumn (cell unit)
-///
-/// @note Returns a constant for now but hopefully we can improve neovim so that
-/// the returned value width adapts to the maximum number of marks to draw
-/// for the window
-/// TODO(teto)
-int win_signcol_width(win_T *wp)
-{
- // 2 is vim default value
- return 2;
-}
-
-/// Call grid_fill() with columns adjusted for 'rightleft' if needed.
-/// Return the new offset.
-static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, int endrow,
- int attr)
-{
- int nn = off + width;
-
- if (nn > wp->w_grid.cols) {
- nn = wp->w_grid.cols;
- }
-
- if (wp->w_p_rl) {
- grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - nn, W_ENDCOL(wp) - off,
- c1, c2, attr);
- } else {
- grid_fill(&wp->w_grid, row, endrow, off, nn, c1, c2, attr);
- }
-
- return nn;
-}
-
-/// Clear lines near the end of the window and mark the unused lines with "c1".
-/// Use "c2" as filler character.
-/// When "draw_margin" is true, then draw the sign/fold/number columns.
-void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl)
-{
- assert(hl >= 0 && hl < HLF_COUNT);
- int n = 0;
-
- if (draw_margin) {
- // draw the fold column
- int fdc = compute_foldcolumn(wp, 0);
- if (fdc > 0) {
- n = win_fill_end(wp, ' ', ' ', n, fdc, row, endrow,
- win_hl_attr(wp, HLF_FC));
- }
- // draw the sign column
- int count = wp->w_scwidth;
- if (count > 0) {
- n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row,
- endrow, win_hl_attr(wp, HLF_SC));
- }
- // draw the number column
- if ((wp->w_p_nu || wp->w_p_rnu) && vim_strchr(p_cpo, CPO_NUMCOL) == NULL) {
- n = win_fill_end(wp, ' ', ' ', n, number_width(wp) + 1, row, endrow,
- win_hl_attr(wp, HLF_N));
- }
- }
-
- int attr = hl_combine_attr(win_bg_attr(wp), win_hl_attr(wp, (int)hl));
-
- if (wp->w_p_rl) {
- grid_fill(&wp->w_grid, row, endrow, wp->w_wincol, W_ENDCOL(wp) - 1 - n,
- c2, c2, attr);
- grid_fill(&wp->w_grid, row, endrow, W_ENDCOL(wp) - 1 - n, W_ENDCOL(wp) - n,
- c1, c2, attr);
- } else {
- grid_fill(&wp->w_grid, row, endrow, n, wp->w_grid.cols, c1, c2, attr);
- }
-}
-
-/// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
-/// space is available for window "wp", minus "col".
-int compute_foldcolumn(win_T *wp, int col)
-{
- int fdc = win_fdccol_count(wp);
- int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
- int wwidth = wp->w_grid.cols;
-
- if (fdc > wwidth - (col + wmw)) {
- fdc = wwidth - (col + wmw);
- }
- return fdc;
-}
-
-/// Fills the foldcolumn at "p" for window "wp".
-/// Only to be called when 'foldcolumn' > 0.
-///
-/// @param[out] p Char array to write into
-/// @param lnum Absolute current line number
-/// @param closed Whether it is in 'foldcolumn' mode
-///
-/// Assume monocell characters
-/// @return number of chars added to \param p
-size_t fill_foldcolumn(char *p, win_T *wp, foldinfo_T foldinfo, linenr_T lnum)
-{
- int i = 0;
- int level;
- int first_level;
- int fdc = compute_foldcolumn(wp, 0); // available cell width
- size_t char_counter = 0;
- int symbol = 0;
- int len = 0;
- bool closed = foldinfo.fi_lines > 0;
- // Init to all spaces.
- memset(p, ' ', MAX_MCO * (size_t)fdc + 1);
-
- level = foldinfo.fi_level;
-
- // If the column is too narrow, we start at the lowest level that
- // fits and use numbers to indicate the depth.
- first_level = level - fdc - closed + 1;
- if (first_level < 1) {
- first_level = 1;
- }
-
- for (i = 0; i < MIN(fdc, level); i++) {
- if (foldinfo.fi_lnum == lnum
- && first_level + i >= foldinfo.fi_low_level) {
- symbol = wp->w_p_fcs_chars.foldopen;
- } else if (first_level == 1) {
- symbol = wp->w_p_fcs_chars.foldsep;
- } else if (first_level + i <= 9) {
- symbol = '0' + first_level + i;
- } else {
- symbol = '>';
- }
-
- len = utf_char2bytes(symbol, &p[char_counter]);
- char_counter += (size_t)len;
- if (first_level + i >= level) {
- i++;
- break;
- }
- }
-
- if (closed) {
- if (symbol != 0) {
- // rollback previous write
- char_counter -= (size_t)len;
- memset(&p[char_counter], ' ', (size_t)len);
- }
- len = utf_char2bytes(wp->w_p_fcs_chars.foldclosed, &p[char_counter]);
- char_counter += (size_t)len;
- }
-
- return MAX(char_counter + (size_t)(fdc - i), (size_t)fdc);
-}
-
-/// Mirror text "str" for right-left displaying.
-/// Only works for single-byte characters (e.g., numbers).
-void rl_mirror(char *str)
-{
- char *p1, *p2;
- char t;
-
- for (p1 = str, p2 = str + strlen(str) - 1; p1 < p2; p1++, p2--) {
- t = *p1;
- *p1 = *p2;
- *p2 = t;
- }
-}
-
-/// Only call if (wp->w_vsep_width != 0).
-///
-/// @return true if the status line of window "wp" is connected to the status
-/// line of the window right of it. If not, then it's a vertical separator.
-bool stl_connected(win_T *wp)
-{
- frame_T *fr;
-
- fr = wp->w_frame;
- while (fr->fr_parent != NULL) {
- if (fr->fr_parent->fr_layout == FR_COL) {
- if (fr->fr_next != NULL) {
- break;
- }
- } else {
- if (fr->fr_next != NULL) {
- return true;
- }
- }
- fr = fr->fr_parent;
- }
- return false;
-}
-
-/// Get the value to show for the language mappings, active 'keymap'.
-///
-/// @param fmt format string containing one %s item
-/// @param buf buffer for the result
-/// @param len length of buffer
-bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
-{
- char *p;
-
- if (wp->w_buffer->b_p_iminsert != B_IMODE_LMAP) {
- return false;
- }
-
- buf_T *old_curbuf = curbuf;
- win_T *old_curwin = curwin;
- char *s;
-
- curbuf = wp->w_buffer;
- curwin = wp;
- STRCPY(buf, "b:keymap_name"); // must be writable
- emsg_skip++;
- s = p = eval_to_string(buf, NULL, false);
- emsg_skip--;
- curbuf = old_curbuf;
- curwin = old_curwin;
- if (p == NULL || *p == NUL) {
- if (wp->w_buffer->b_kmap_state & KEYMAP_LOADED) {
- p = wp->w_buffer->b_p_keymap;
- } else {
- p = "lang";
- }
- }
- if (vim_snprintf(buf, (size_t)len, fmt, p) > len - 1) {
- buf[0] = NUL;
- }
- xfree(s);
- return buf[0] != NUL;
-}
-
-/// Prepare for 'hlsearch' highlighting.
-void start_search_hl(void)
-{
- if (!p_hls || no_hlsearch) {
- return;
- }
-
- end_search_hl(); // just in case it wasn't called before
- last_pat_prog(&screen_search_hl.rm);
- // Set the time limit to 'redrawtime'.
- screen_search_hl.tm = profile_setlimit(p_rdt);
-}
-
-/// Clean up for 'hlsearch' highlighting.
-void end_search_hl(void)
-{
- if (screen_search_hl.rm.regprog == NULL) {
- return;
- }
-
- vim_regfree(screen_search_hl.rm.regprog);
- screen_search_hl.rm.regprog = NULL;
-}
-
-/// Check if there should be a delay. Used before clearing or redrawing the
-/// screen or the command line.
-void check_for_delay(bool check_msg_scroll)
-{
- if ((emsg_on_display || (check_msg_scroll && msg_scroll))
- && !did_wait_return
- && emsg_silent == 0
- && !in_assert_fails) {
- ui_flush();
- os_delay(1006L, true);
- emsg_on_display = false;
- if (check_msg_scroll) {
- msg_scroll = false;
- }
- }
-}
-
-/// Set cursor to its position in the current window.
-void setcursor(void)
-{
- setcursor_mayforce(false);
-}
-
-/// Set cursor to its position in the current window.
-/// @param force when true, also when not redrawing.
-void setcursor_mayforce(bool force)
-{
- if (force || redrawing()) {
- validate_cursor();
-
- ScreenGrid *grid = &curwin->w_grid;
- int row = curwin->w_wrow;
- int col = curwin->w_wcol;
- if (curwin->w_p_rl) {
- // With 'rightleft' set and the cursor on a double-wide character,
- // position it on the leftmost column.
- col = curwin->w_width_inner - curwin->w_wcol
- - ((utf_ptr2cells(get_cursor_pos_ptr()) == 2
- && vim_isprintc(gchar_cursor())) ? 2 : 1);
- }
-
- grid_adjust(&grid, &row, &col);
- ui_grid_cursor_goto(grid->handle, row, col);
- }
-}
-
-/// Scroll `line_count` lines at 'row' in window 'wp'.
-///
-/// Positive `line_count` means scrolling down, so that more space is available
-/// at 'row'. Negative `line_count` implies deleting lines at `row`.
-void win_scroll_lines(win_T *wp, int row, int line_count)
-{
- if (!redrawing() || line_count == 0) {
- return;
- }
-
- // No lines are being moved, just draw over the entire area
- if (row + abs(line_count) >= wp->w_grid.rows) {
- return;
- }
-
- if (line_count < 0) {
- grid_del_lines(&wp->w_grid, row, -line_count,
- wp->w_grid.rows, 0, wp->w_grid.cols);
- } else {
- grid_ins_lines(&wp->w_grid, row, line_count,
- wp->w_grid.rows, 0, wp->w_grid.cols);
- }
-}
-
-/// @return true when postponing displaying the mode message: when not redrawing
-/// or inside a mapping.
-bool skip_showmode(void)
-{
- // Call char_avail() only when we are going to show something, because it
- // takes a bit of time. redrawing() may also call char_avail().
- if (global_busy || msg_silent != 0 || !redrawing() || (char_avail() && !KeyTyped)) {
- redraw_mode = true; // show mode later
- return true;
- }
- return false;
-}
-
-/// Show the current mode and ruler.
-///
-/// If clear_cmdline is true, clear the rest of the cmdline.
-/// If clear_cmdline is false there may be a message there that needs to be
-/// cleared only if a mode is shown.
-/// If redraw_mode is true show or clear the mode.
-/// @return the length of the message (0 if no message).
-int showmode(void)
-{
- bool need_clear;
- int length = 0;
- int do_mode;
- int attr;
- int sub_attr;
-
- if (ui_has(kUIMessages) && clear_cmdline) {
- msg_ext_clear(true);
- }
-
- // don't make non-flushed message part of the showmode
- msg_ext_ui_flush();
-
- msg_grid_validate();
-
- do_mode = ((p_smd && msg_silent == 0)
- && ((State & MODE_TERMINAL)
- || (State & MODE_INSERT)
- || restart_edit != NUL
- || VIsual_active));
- if (do_mode || reg_recording != 0) {
- if (skip_showmode()) {
- return 0; // show mode later
- }
-
- bool nwr_save = need_wait_return;
-
- // wait a bit before overwriting an important message
- check_for_delay(false);
-
- // if the cmdline is more than one line high, erase top lines
- need_clear = clear_cmdline;
- if (clear_cmdline && cmdline_row < Rows - 1) {
- msg_clr_cmdline(); // will reset clear_cmdline
- }
-
- // Position on the last line in the window, column 0
- msg_pos_mode();
- attr = HL_ATTR(HLF_CM); // Highlight mode
-
- // When the screen is too narrow to show the entire mode message,
- // avoid scrolling and truncate instead.
- msg_no_more = true;
- int save_lines_left = lines_left;
- lines_left = 0;
-
- if (do_mode) {
- msg_puts_attr("--", attr);
- // CTRL-X in Insert mode
- if (edit_submode != NULL && !shortmess(SHM_COMPLETIONMENU)) {
- // These messages can get long, avoid a wrap in a narrow window.
- // Prefer showing edit_submode_extra. With external messages there
- // is no imposed limit.
- if (ui_has(kUIMessages)) {
- length = INT_MAX;
- } else {
- length = (Rows - msg_row) * Columns - 3;
- }
- if (edit_submode_extra != NULL) {
- length -= vim_strsize(edit_submode_extra);
- }
- if (length > 0) {
- if (edit_submode_pre != NULL) {
- length -= vim_strsize(edit_submode_pre);
- }
- if (length - vim_strsize(edit_submode) > 0) {
- if (edit_submode_pre != NULL) {
- msg_puts_attr((const char *)edit_submode_pre, attr);
- }
- msg_puts_attr((const char *)edit_submode, attr);
- }
- if (edit_submode_extra != NULL) {
- msg_puts_attr(" ", attr); // Add a space in between.
- if ((int)edit_submode_highl < HLF_COUNT) {
- sub_attr = win_hl_attr(curwin, (int)edit_submode_highl);
- } else {
- sub_attr = attr;
- }
- msg_puts_attr((const char *)edit_submode_extra, sub_attr);
- }
- }
- } else {
- if (State & MODE_TERMINAL) {
- msg_puts_attr(_(" TERMINAL"), attr);
- } else if (State & VREPLACE_FLAG) {
- msg_puts_attr(_(" VREPLACE"), attr);
- } else if (State & REPLACE_FLAG) {
- msg_puts_attr(_(" REPLACE"), attr);
- } else if (State & MODE_INSERT) {
- if (p_ri) {
- msg_puts_attr(_(" REVERSE"), attr);
- }
- msg_puts_attr(_(" INSERT"), attr);
- } else if (restart_edit == 'I' || restart_edit == 'i'
- || restart_edit == 'a' || restart_edit == 'A') {
- if (curbuf->terminal) {
- msg_puts_attr(_(" (terminal)"), attr);
- } else {
- msg_puts_attr(_(" (insert)"), attr);
- }
- } else if (restart_edit == 'R') {
- msg_puts_attr(_(" (replace)"), attr);
- } else if (restart_edit == 'V') {
- msg_puts_attr(_(" (vreplace)"), attr);
- }
- if (p_hkmap) {
- msg_puts_attr(_(" Hebrew"), attr);
- }
- if (State & MODE_LANGMAP) {
- if (curwin->w_p_arab) {
- msg_puts_attr(_(" Arabic"), attr);
- } else if (get_keymap_str(curwin, " (%s)",
- NameBuff, MAXPATHL)) {
- msg_puts_attr(NameBuff, attr);
- }
- }
- if ((State & MODE_INSERT) && p_paste) {
- msg_puts_attr(_(" (paste)"), attr);
- }
-
- if (VIsual_active) {
- char *p;
-
- // Don't concatenate separate words to avoid translation
- // problems.
- switch ((VIsual_select ? 4 : 0)
- + (VIsual_mode == Ctrl_V) * 2
- + (VIsual_mode == 'V')) {
- case 0:
- p = N_(" VISUAL"); break;
- case 1:
- p = N_(" VISUAL LINE"); break;
- case 2:
- p = N_(" VISUAL BLOCK"); break;
- case 4:
- p = N_(" SELECT"); break;
- case 5:
- p = N_(" SELECT LINE"); break;
- default:
- p = N_(" SELECT BLOCK"); break;
- }
- msg_puts_attr(_(p), attr);
- }
- msg_puts_attr(" --", attr);
- }
-
- need_clear = true;
- }
- if (reg_recording != 0
- && edit_submode == NULL // otherwise it gets too long
- ) {
- recording_mode(attr);
- need_clear = true;
- }
-
- mode_displayed = true;
- if (need_clear || clear_cmdline || redraw_mode) {
- msg_clr_eos();
- }
- msg_didout = false; // overwrite this message
- length = msg_col;
- msg_col = 0;
- msg_no_more = false;
- lines_left = save_lines_left;
- need_wait_return = nwr_save; // never ask for hit-return for this
- } else if (clear_cmdline && msg_silent == 0) {
- // Clear the whole command line. Will reset "clear_cmdline".
- msg_clr_cmdline();
- } else if (redraw_mode) {
- msg_pos_mode();
- msg_clr_eos();
- }
-
- // NB: also handles clearing the showmode if it was empty or disabled
- msg_ext_flush_showmode();
-
- // In Visual mode the size of the selected area must be redrawn.
- if (VIsual_active) {
- clear_showcmd();
- }
-
- // If the last window has no status line and global statusline is disabled,
- // the ruler is after the mode message and must be redrawn
- win_T *last = lastwin_nofloating();
- if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) {
- win_redr_ruler(last, true);
- }
-
- redraw_cmdline = false;
- redraw_mode = false;
- clear_cmdline = false;
-
- return length;
-}
-
-/// Position for a mode message.
-static void msg_pos_mode(void)
-{
- msg_col = 0;
- msg_row = Rows - 1;
-}
-
-/// Delete mode message. Used when ESC is typed which is expected to end
-/// Insert mode (but Insert mode didn't end yet!).
-/// Caller should check "mode_displayed".
-void unshowmode(bool force)
-{
- // Don't delete it right now, when not redrawing or inside a mapping.
- if (!redrawing() || (!force && char_avail() && !KeyTyped)) {
- redraw_cmdline = true; // delete mode later
- } else {
- clearmode();
- }
-}
-
-// Clear the mode message.
-void clearmode(void)
-{
- const int save_msg_row = msg_row;
- const int save_msg_col = msg_col;
-
- msg_ext_ui_flush();
- msg_pos_mode();
- if (reg_recording != 0) {
- recording_mode(HL_ATTR(HLF_CM));
- }
- msg_clr_eos();
- msg_ext_flush_showmode();
-
- msg_col = save_msg_col;
- msg_row = save_msg_row;
-}
-
-static void recording_mode(int attr)
-{
- msg_puts_attr(_("recording"), attr);
- if (shortmess(SHM_RECORDING)) {
- return;
- }
-
- char s[4];
- snprintf(s, ARRAY_SIZE(s), " @%c", reg_recording);
- msg_puts_attr(s, attr);
-}
-
-void get_trans_bufname(buf_T *buf)
-{
- if (buf_spname(buf) != NULL) {
- xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL);
- } else {
- home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true);
- }
- trans_characters(NameBuff, MAXPATHL);
-}
-
-/// Get the character to use in a separator between vertically split windows.
-/// Get its attributes in "*attr".
-int fillchar_vsep(win_T *wp, int *attr)
-{
- *attr = win_hl_attr(wp, HLF_C);
- return wp->w_p_fcs_chars.vert;
-}
-
-/// Get the character to use in a separator between horizontally split windows.
-/// Get its attributes in "*attr".
-int fillchar_hsep(win_T *wp, int *attr)
-{
- *attr = win_hl_attr(wp, HLF_C);
- return wp->w_p_fcs_chars.horiz;
-}
-
-/// Return true if redrawing should currently be done.
-bool redrawing(void)
-{
- return !RedrawingDisabled
- && !(p_lz && char_avail() && !KeyTyped && !do_redraw);
-}
-
-/// Return true if printing messages should currently be done.
-bool messaging(void)
-{
- // TODO(bfredl): with general support for "async" messages with p_ch,
- // this should be re-enabled.
- return !(p_lz && char_avail() && !KeyTyped) && (p_ch > 0 || ui_has(kUIMessages));
-}
-
-#define COL_RULER 17 // columns needed by standard ruler
-
-/// Compute columns for ruler and shown command. 'sc_col' is also used to
-/// decide what the maximum length of a message on the status line can be.
-/// If there is a status line for the last window, 'sc_col' is independent
-/// of 'ru_col'.
-void comp_col(void)
-{
- int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW));
-
- sc_col = 0;
- ru_col = 0;
- if (p_ru) {
- ru_col = (ru_wid ? ru_wid : COL_RULER) + 1;
- // no last status line, adjust sc_col
- if (!last_has_status) {
- sc_col = ru_col;
- }
- }
- if (p_sc) {
- sc_col += SHOWCMD_COLS;
- if (!p_ru || last_has_status) { // no need for separating space
- sc_col++;
- }
- }
- assert(sc_col >= 0
- && INT_MIN + sc_col <= Columns);
- sc_col = Columns - sc_col;
- assert(ru_col >= 0
- && INT_MIN + ru_col <= Columns);
- ru_col = Columns - ru_col;
- if (sc_col <= 0) { // screen too narrow, will become a mess
- sc_col = 1;
- }
- if (ru_col <= 0) {
- ru_col = 1;
- }
- set_vim_var_nr(VV_ECHOSPACE, sc_col - 1);
-}
-
-/// Return the width of the 'number' and 'relativenumber' column.
-/// Caller may need to check if 'number' or 'relativenumber' is set.
-/// Otherwise it depends on 'numberwidth' and the line count.
-int number_width(win_T *wp)
-{
- linenr_T lnum;
-
- if (wp->w_p_rnu && !wp->w_p_nu) {
- // cursor line shows "0"
- lnum = wp->w_height_inner;
- } else {
- // cursor line shows absolute line number
- lnum = wp->w_buffer->b_ml.ml_line_count;
- }
-
- if (lnum == wp->w_nrwidth_line_count) {
- return wp->w_nrwidth_width;
- }
- wp->w_nrwidth_line_count = lnum;
-
- // reset for 'statuscolumn'
- if (*wp->w_p_stc != NUL) {
- wp->w_nrwidth_width = (wp->w_p_nu || wp->w_p_rnu) * (int)wp->w_p_nuw;
- return wp->w_nrwidth_width;
- }
-
- int n = 0;
- do {
- lnum /= 10;
- n++;
- } while (lnum > 0);
-
- // 'numberwidth' gives the minimal width plus one
- if (n < wp->w_p_nuw - 1) {
- n = (int)wp->w_p_nuw - 1;
- }
-
- // If 'signcolumn' is set to 'number' and there is a sign to display, then
- // the minimal width for the number column is 2.
- if (n < 2 && (wp->w_buffer->b_signlist != NULL)
- && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
- n = 2;
- }
-
- wp->w_nrwidth_width = n;
- return n;
-}
-
-/// Calls mb_cptr2char_adv(p) and returns the character.
-/// If "p" starts with "\x", "\u" or "\U" the hex or unicode value is used.
-/// Returns 0 for invalid hex or invalid UTF-8 byte.
-static int get_encoded_char_adv(const char **p)
-{
- const char *s = *p;
-
- if (s[0] == '\\' && (s[1] == 'x' || s[1] == 'u' || s[1] == 'U')) {
- int64_t num = 0;
- for (int bytes = s[1] == 'x' ? 1 : s[1] == 'u' ? 2 : 4; bytes > 0; bytes--) {
- *p += 2;
- int n = hexhex2nr(*p);
- if (n < 0) {
- return 0;
- }
- num = num * 256 + n;
- }
- *p += 2;
- return (int)num;
- }
-
- // TODO(bfredl): use schar_T representation and utfc_ptr2len
- int clen = utf_ptr2len(s);
- int c = mb_cptr2char_adv(p);
- if (clen == 1 && c > 127) { // Invalid UTF-8 byte
- return 0;
- }
- return c;
-}
-
-/// Handle setting 'listchars' or 'fillchars'.
-/// Assume monocell characters
-///
-/// @param varp either the global or the window-local value.
-/// @param apply if false, do not store the flags, only check for errors.
-/// @return error message, NULL if it's OK.
-char *set_chars_option(win_T *wp, char **varp, bool apply)
-{
- const char *last_multispace = NULL; // Last occurrence of "multispace:"
- const char *last_lmultispace = NULL; // Last occurrence of "leadmultispace:"
- int multispace_len = 0; // Length of lcs-multispace string
- int lead_multispace_len = 0; // Length of lcs-leadmultispace string
- const bool is_listchars = (varp == &p_lcs || varp == &wp->w_p_lcs);
-
- struct chars_tab {
- int *cp; ///< char value
- char *name; ///< char id
- int def; ///< default value
- };
-
- // XXX: Characters taking 2 columns is forbidden (TUI limitation?). Set old defaults in this case.
- struct chars_tab fcs_tab[] = {
- { &wp->w_p_fcs_chars.stl, "stl", ' ' },
- { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' },
- { &wp->w_p_fcs_chars.wbr, "wbr", ' ' },
- { &wp->w_p_fcs_chars.horiz, "horiz", char2cells(0x2500) == 1 ? 0x2500 : '-' }, // ─
- { &wp->w_p_fcs_chars.horizup, "horizup", char2cells(0x2534) == 1 ? 0x2534 : '-' }, // ┴
- { &wp->w_p_fcs_chars.horizdown, "horizdown", char2cells(0x252c) == 1 ? 0x252c : '-' }, // ┬
- { &wp->w_p_fcs_chars.vert, "vert", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
- { &wp->w_p_fcs_chars.vertleft, "vertleft", char2cells(0x2524) == 1 ? 0x2524 : '|' }, // ┤
- { &wp->w_p_fcs_chars.vertright, "vertright", char2cells(0x251c) == 1 ? 0x251c : '|' }, // ├
- { &wp->w_p_fcs_chars.verthoriz, "verthoriz", char2cells(0x253c) == 1 ? 0x253c : '+' }, // ┼
- { &wp->w_p_fcs_chars.fold, "fold", char2cells(0x00b7) == 1 ? 0x00b7 : '-' }, // ·
- { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' },
- { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' },
- { &wp->w_p_fcs_chars.foldsep, "foldsep", char2cells(0x2502) == 1 ? 0x2502 : '|' }, // │
- { &wp->w_p_fcs_chars.diff, "diff", '-' },
- { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' },
- { &wp->w_p_fcs_chars.eob, "eob", '~' },
- { &wp->w_p_fcs_chars.lastline, "lastline", '@' },
- };
-
- struct chars_tab lcs_tab[] = {
- { &wp->w_p_lcs_chars.eol, "eol", NUL },
- { &wp->w_p_lcs_chars.ext, "extends", NUL },
- { &wp->w_p_lcs_chars.nbsp, "nbsp", NUL },
- { &wp->w_p_lcs_chars.prec, "precedes", NUL },
- { &wp->w_p_lcs_chars.space, "space", NUL },
- { &wp->w_p_lcs_chars.tab2, "tab", NUL },
- { &wp->w_p_lcs_chars.lead, "lead", NUL },
- { &wp->w_p_lcs_chars.trail, "trail", NUL },
- { &wp->w_p_lcs_chars.conceal, "conceal", NUL },
- };
-
- struct chars_tab *tab;
- int entries;
- const char *value = *varp;
- if (is_listchars) {
- tab = lcs_tab;
- entries = ARRAY_SIZE(lcs_tab);
- if (varp == &wp->w_p_lcs && wp->w_p_lcs[0] == NUL) {
- value = p_lcs; // local value is empty, use the global value
- }
- } else {
- tab = fcs_tab;
- entries = ARRAY_SIZE(fcs_tab);
- if (varp == &wp->w_p_fcs && wp->w_p_fcs[0] == NUL) {
- value = p_fcs; // local value is empty, use the global value
- }
- }
-
- // first round: check for valid value, second round: assign values
- for (int round = 0; round <= (apply ? 1 : 0); round++) {
- if (round > 0) {
- // After checking that the value is valid: set defaults
- for (int i = 0; i < entries; i++) {
- if (tab[i].cp != NULL) {
- *(tab[i].cp) = tab[i].def;
- }
- }
- if (is_listchars) {
- wp->w_p_lcs_chars.tab1 = NUL;
- wp->w_p_lcs_chars.tab3 = NUL;
-
- xfree(wp->w_p_lcs_chars.multispace);
- if (multispace_len > 0) {
- wp->w_p_lcs_chars.multispace = xmalloc(((size_t)multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.multispace[multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.multispace = NULL;
- }
-
- xfree(wp->w_p_lcs_chars.leadmultispace);
- if (lead_multispace_len > 0) {
- wp->w_p_lcs_chars.leadmultispace
- = xmalloc(((size_t)lead_multispace_len + 1) * sizeof(int));
- wp->w_p_lcs_chars.leadmultispace[lead_multispace_len] = NUL;
- } else {
- wp->w_p_lcs_chars.leadmultispace = NULL;
- }
- }
- }
- const char *p = value;
- while (*p) {
- int i;
- for (i = 0; i < entries; i++) {
- const size_t len = strlen(tab[i].name);
- if (strncmp(p, tab[i].name, len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- const char *s = p + len + 1;
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- int c2 = 0, c3 = 0;
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- if (*s == NUL) {
- return e_invarg;
- }
- c2 = get_encoded_char_adv(&s);
- if (c2 == 0 || char2cells(c2) > 1) {
- return e_invarg;
- }
- if (!(*s == ',' || *s == NUL)) {
- c3 = get_encoded_char_adv(&s);
- if (c3 == 0 || char2cells(c3) > 1) {
- return e_invarg;
- }
- }
- }
- if (*s == ',' || *s == NUL) {
- if (round > 0) {
- if (tab[i].cp == &wp->w_p_lcs_chars.tab2) {
- wp->w_p_lcs_chars.tab1 = c1;
- wp->w_p_lcs_chars.tab2 = c2;
- wp->w_p_lcs_chars.tab3 = c3;
- } else if (tab[i].cp != NULL) {
- *(tab[i].cp) = c1;
- }
- }
- p = s;
- break;
- }
- }
- }
-
- if (i == entries) {
- const size_t len = strlen("multispace");
- const size_t len2 = strlen("leadmultispace");
- if (is_listchars
- && strncmp(p, "multispace", len) == 0
- && p[len] == ':'
- && p[len + 1] != NUL) {
- const char *s = p + len + 1;
- if (round == 0) {
- // Get length of lcs-multispace string in the first round
- last_multispace = p;
- multispace_len = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- multispace_len++;
- }
- if (multispace_len == 0) {
- // lcs-multispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (p == last_multispace) {
- wp->w_p_lcs_chars.multispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else if (is_listchars
- && strncmp(p, "leadmultispace", len2) == 0
- && p[len2] == ':'
- && p[len2 + 1] != NUL) {
- const char *s = p + len2 + 1;
- if (round == 0) {
- // get length of lcs-leadmultispace string in first round
- last_lmultispace = p;
- lead_multispace_len = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (c1 == 0 || char2cells(c1) > 1) {
- return e_invarg;
- }
- lead_multispace_len++;
- }
- if (lead_multispace_len == 0) {
- // lcs-leadmultispace cannot be an empty string
- return e_invarg;
- }
- p = s;
- } else {
- int multispace_pos = 0;
- while (*s != NUL && *s != ',') {
- int c1 = get_encoded_char_adv(&s);
- if (p == last_lmultispace) {
- wp->w_p_lcs_chars.leadmultispace[multispace_pos++] = c1;
- }
- }
- p = s;
- }
- } else {
- return e_invarg;
- }
- }
-
- if (*p == ',') {
- p++;
- }
- }
- }
-
- return NULL; // no error
-}
-
-/// Check all global and local values of 'listchars' and 'fillchars'.
-/// May set different defaults in case character widths change.
-///
-/// @return an untranslated error message if any of them is invalid, NULL otherwise.
-char *check_chars_options(void)
-{
- if (set_chars_option(curwin, &p_lcs, false) != NULL) {
- return e_conflicts_with_value_of_listchars;
- }
- if (set_chars_option(curwin, &p_fcs, false) != NULL) {
- return e_conflicts_with_value_of_fillchars;
- }
- FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (set_chars_option(wp, &wp->w_p_lcs, true) != NULL) {
- return e_conflicts_with_value_of_listchars;
- }
- if (set_chars_option(wp, &wp->w_p_fcs, true) != NULL) {
- return e_conflicts_with_value_of_fillchars;
- }
- }
- return NULL;
-}
-
-/// Check if the new Nvim application "screen" dimensions are valid.
-/// Correct it if it's too small or way too big.
-void check_screensize(void)
-{
- // Limit Rows and Columns to avoid an overflow in Rows * Columns.
- if (Rows < min_rows()) {
- // need room for one window and command line
- Rows = min_rows();
- } else if (Rows > 1000) {
- Rows = 1000;
- }
-
- if (Columns < MIN_COLUMNS) {
- Columns = MIN_COLUMNS;
- } else if (Columns > 10000) {
- Columns = 10000;
- }
-}
diff --git a/src/nvim/screen.h b/src/nvim/screen.h
deleted file mode 100644
index 1d8de8ca21..0000000000
--- a/src/nvim/screen.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#ifndef NVIM_SCREEN_H
-#define NVIM_SCREEN_H
-
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/fold.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-
-EXTERN match_T screen_search_hl; // used for 'hlsearch' highlight matching
-
-#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
-#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "screen.h.generated.h"
-#endif
-#endif // NVIM_SCREEN_H
diff --git a/src/nvim/search.c b/src/nvim/search.c
index eb5cc2e07f..642219c1e0 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// search.c: code for normal mode searching commands
#include <assert.h>
@@ -11,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,13 +24,14 @@
#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -43,23 +41,31 @@
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
+#include "nvim/tag.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.c.generated.h"
#endif
+static const char e_search_hit_top_without_match_for_str[]
+ = N_("E384: Search hit TOP without match for: %s");
+static const char e_search_hit_bottom_without_match_for_str[]
+ = N_("E385: Search hit BOTTOM without match for: %s");
+
// This file contains various searching-related routines. These fall into
// three groups:
// 1. string searches (for /, ?, n, and N)
@@ -88,14 +94,14 @@
static struct spat spats[2] = {
// Last used search pattern
- [0] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL },
+ [0] = { NULL, true, false, 0, { '/', false, false, 0 }, NULL },
// Last used substitute pattern
- [1] = { NULL, true, false, 0, { '/', false, false, 0L }, NULL }
+ [1] = { NULL, true, false, 0, { '/', false, false, 0 }, NULL }
};
static int last_idx = 0; // index in spats[] for RE_LAST
-static char_u lastc[2] = { NUL, NUL }; // last character searched for
+static uint8_t lastc[2] = { NUL, NUL }; // last character searched for
static Direction lastcdir = FORWARD; // last direction of character search
static int last_t_cmd = true; // last search t_cmd
static char lastc_bytes[MB_MAXBYTES + 1];
@@ -136,14 +142,12 @@ typedef struct SearchedFile {
int search_regcomp(char *pat, char **used_pat, int pat_save, int pat_use, int options,
regmmatch_T *regmatch)
{
- int magic;
- int i;
-
rc_did_emsg = false;
- magic = magic_isset();
+ int magic = magic_isset();
// If no pattern given, use a previously defined pattern.
if (pat == NULL || *pat == NUL) {
+ int i;
if (pat_use == RE_LAST) {
i = last_idx;
} else {
@@ -343,13 +347,13 @@ void restore_last_search_pattern(void)
static void save_incsearch_state(void)
{
saved_search_match_endcol = search_match_endcol;
- saved_search_match_lines = search_match_lines;
+ saved_search_match_lines = search_match_lines;
}
static void restore_incsearch_state(void)
{
search_match_endcol = saved_search_match_endcol;
- search_match_lines = saved_search_match_lines;
+ search_match_lines = saved_search_match_lines;
}
char *last_search_pattern(void)
@@ -439,7 +443,7 @@ int last_csearch_until(void)
void set_last_csearch(int c, char *s, int len)
{
- *lastc = (char_u)c;
+ *lastc = (uint8_t)c;
lastc_bytelen = len;
if (len) {
memcpy(lastc_bytes, s, (size_t)len);
@@ -547,7 +551,7 @@ void last_pat_prog(regmmatch_T *regmatch)
/// the index of the first matching
/// subpattern plus one; one if there was none.
int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir, char *pat,
- long count, int options, int pat_use, searchit_arg_T *extra_arg)
+ int count, int options, int pat_use, searchit_arg_T *extra_arg)
{
int found;
linenr_T lnum; // no init to shut up Apollo cc
@@ -557,12 +561,10 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
lpos_T endpos;
lpos_T matchpos;
int loop;
- pos_T start_pos;
- int at_first_line;
int extra_col;
int start_char_len;
bool match_ok;
- long nmatched;
+ int nmatched;
int submatch = 0;
bool first_match = true;
const int called_emsg_before = called_emsg;
@@ -596,7 +598,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
&& pos->lnum <= buf->b_ml.ml_line_count
&& pos->col < MAXCOL - 2) {
// Watch out for the "col" being MAXCOL - 2, used in a closed fold.
- ptr = ml_get_buf(buf, pos->lnum, false);
+ ptr = ml_get_buf(buf, pos->lnum);
if ((int)strlen(ptr) <= pos->col) {
start_char_len = 1;
} else {
@@ -611,9 +613,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
extra_col = (options & SEARCH_START) ? start_char_len : 0;
}
- start_pos = *pos; // remember start pos for detecting no match
+ pos_T start_pos = *pos; // remember start pos for detecting no match
found = 0; // default: not found
- at_first_line = true; // default: start in first line
+ int at_first_line = true; // default: start in first line
if (pos->lnum == 0) { // correct lnum for when starting in line 0
pos->lnum = 1;
pos->col = 0;
@@ -667,7 +669,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (lnum + matchpos.lnum > buf->b_ml.ml_line_count) {
ptr = "";
} else {
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum);
}
// Forward search in the first line: match should be after
@@ -683,9 +685,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// otherwise "/$" will get stuck on end of line.
while (matchpos.lnum == 0
&& (((options & SEARCH_END) && first_match)
- ? (nmatched == 1
- && (int)endpos.col - 1
- < (int)start_pos.col + extra_col)
+ ? (nmatched == 1
+ && (int)endpos.col - 1
+ < (int)start_pos.col + extra_col)
: ((int)matchpos.col
- (ptr[matchpos.col] == NUL)
< (int)start_pos.col + extra_col))) {
@@ -739,7 +741,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
}
// Need to get the line pointer again, a multi-line search may
// have made it invalid.
- ptr = ml_get_buf(buf, lnum, false);
+ ptr = ml_get_buf(buf, lnum);
}
if (!match_ok) {
continue;
@@ -752,7 +754,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// When putting the new cursor at the end, compare
// relative to the end of the match.
match_ok = false;
- for (;;) {
+ while (true) {
// Remember a position that is before the start
// position, we use it if it's the last match in
// the line. Always accept a position after
@@ -821,7 +823,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
}
// Need to get the line pointer again, a
// multi-line search may have made it invalid.
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum);
}
// If there is only a match after the cursor, skip
@@ -844,12 +846,12 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (endpos.col == 0) {
if (pos->lnum > 1) { // just in case
pos->lnum--;
- pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum, false));
+ pos->col = (colnr_T)strlen(ml_get_buf(buf, pos->lnum));
}
} else {
pos->col--;
if (pos->lnum <= buf->b_ml.ml_line_count) {
- ptr = ml_get_buf(buf, pos->lnum, false);
+ ptr = ml_get_buf(buf, pos->lnum);
pos->col -= utf_head_off(ptr, ptr + pos->col);
}
}
@@ -913,19 +915,22 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
|| found || loop) {
break;
}
- //
+
// If 'wrapscan' is set we continue at the other end of the file.
- // If 'shortmess' does not contain 's', we give a message.
+ // If 'shortmess' does not contain 's', we give a message, but
+ // only, if we won't show the search stat later anyhow,
+ // (so SEARCH_COUNT must be absent).
// This message is also remembered in keep_msg for when the screen
// is redrawn. The keep_msg is cleared whenever another message is
// written.
- //
if (dir == BACKWARD) { // start second loop at the other end
lnum = buf->b_ml.ml_line_count;
} else {
lnum = 1;
}
- if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG)) {
+ if (!shortmess(SHM_SEARCH)
+ && shortmess(SHM_SEARCHCOUNT)
+ && (options & SEARCH_MSG)) {
give_warning(_(dir == BACKWARD ? top_bot_msg : bot_top_msg), true);
}
if (extra_arg != NULL) {
@@ -948,11 +953,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
if (p_ws) {
semsg(_(e_patnotf2), mr_pattern);
} else if (lnum == 0) {
- semsg(_("E384: search hit TOP without match for: %s"),
- mr_pattern);
+ semsg(_(e_search_hit_top_without_match_for_str), mr_pattern);
} else {
- semsg(_("E385: search hit BOTTOM without match for: %s"),
- mr_pattern);
+ semsg(_(e_search_hit_bottom_without_match_for_str), mr_pattern);
}
}
return FAIL;
@@ -961,7 +964,7 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// A pattern like "\n\zs" may go past the last line.
if (pos->lnum > buf->b_ml.ml_line_count) {
pos->lnum = buf->b_ml.ml_line_count;
- pos->col = (int)strlen(ml_get_buf(buf, pos->lnum, false));
+ pos->col = (int)strlen(ml_get_buf(buf, pos->lnum));
if (pos->col > 0) {
pos->col--;
}
@@ -977,7 +980,7 @@ void set_search_direction(int cdir)
static void set_vv_searchforward(void)
{
- set_vim_var_nr(VV_SEARCHFORWARD, (long)(spats[0].off.dir == '/'));
+ set_vim_var_nr(VV_SEARCHFORWARD, spats[0].off.dir == '/');
}
// Return the number of the first subpat that matched.
@@ -1023,15 +1026,14 @@ static int first_submatch(regmmatch_T *rp)
/// @param sia optional arguments or NULL
///
/// @return 0 for failure, 1 for found, 2 for found and line offset added.
-int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, int options,
+int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, int count, int options,
searchit_arg_T *sia)
{
pos_T pos; // position of the last match
char *searchstr;
- struct soffset old_off;
int retval; // Return value
char *p;
- long c;
+ int64_t c;
char *dircp;
char *strcopy = NULL;
char *ps;
@@ -1047,13 +1049,13 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
// Save the values for when (options & SEARCH_KEEP) is used.
// (there is no "if ()" around this because gcc wants them initialized)
- old_off = spats[0].off;
+ struct soffset old_off = spats[0].off;
pos = curwin->w_cursor; // start searching at the cursor position
// Find out the direction of the search.
if (dirc == 0) {
- dirc = (char_u)spats[0].off.dir;
+ dirc = (uint8_t)spats[0].off.dir;
} else {
spats[0].off.dir = (char)dirc;
set_vv_searchforward();
@@ -1085,7 +1087,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
}
// Repeat the search when pattern followed by ';', e.g. "/foo/;?bar".
- for (;;) {
+ while (true) {
bool show_top_bot_msg = false;
searchstr = pat;
@@ -1169,7 +1171,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
// Get the offset, so we know how long it is.
if (!cmd_silent
&& (spats[0].off.line || spats[0].off.end || spats[0].off.off)) {
- p = off_buf; // -V507
+ p = off_buf;
*p++ = (char)dirc;
if (spats[0].off.end) {
*p++ = 'e';
@@ -1262,7 +1264,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, long count, i
memset(msgbuf + pat_len, ' ', (size_t)(r - msgbuf));
}
}
- msg_outtrans(msgbuf);
+ msg_outtrans(msgbuf, 0);
msg_clr_eos();
msg_check();
@@ -1434,13 +1436,11 @@ end_do_search:
int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
{
linenr_T start = 0;
- char *ptr;
- char *p;
if (buf->b_ml.ml_line_count == 0) {
return FAIL;
}
- for (;;) {
+ while (true) {
pos->lnum += dir;
if (pos->lnum < 1) {
if (p_ws) {
@@ -1469,21 +1469,21 @@ int search_for_exact_line(buf_T *buf, pos_T *pos, Direction dir, char *pat)
if (start == 0) {
start = pos->lnum;
}
- ptr = ml_get_buf(buf, pos->lnum, false);
- p = skipwhite(ptr);
+ char *ptr = ml_get_buf(buf, pos->lnum);
+ char *p = skipwhite(ptr);
pos->col = (colnr_T)(p - ptr);
// when adding lines the matching line may be empty but it is not
// ignored because we are interested in the next line -- Acevedo
if (compl_status_adding() && !compl_status_sol()) {
- if (mb_strcmp_ic((bool)p_ic, (const char *)p, (const char *)pat) == 0) {
+ if (mb_strcmp_ic((bool)p_ic, p, pat) == 0) {
return OK;
}
} else if (*p != NUL) { // Ignore empty lines.
// Expanding lines or words.
assert(ins_compl_len() >= 0);
if ((p_ic ? mb_strnicmp(p, pat, (size_t)ins_compl_len())
- : strncmp(p, pat, (size_t)ins_compl_len())) == 0) {
+ : strncmp(p, pat, (size_t)ins_compl_len())) == 0) {
return OK;
}
}
@@ -1502,15 +1502,12 @@ int searchc(cmdarg_T *cap, int t_cmd)
{
int c = cap->nchar; // char to search for
int dir = cap->arg; // true for searching forward
- long count = cap->count1; // repeat count
- int col;
- char *p;
- int len;
+ int count = cap->count1; // repeat count
bool stop = true;
if (c != NUL) { // normal search: remember args for repeat
if (!KeyStuffed) { // don't remember when redoing
- *lastc = (char_u)c;
+ *lastc = (uint8_t)c;
set_csearch_direction(dir);
set_csearch_until(t_cmd);
lastc_bytelen = utf_char2bytes(c, lastc_bytes);
@@ -1524,7 +1521,7 @@ int searchc(cmdarg_T *cap, int t_cmd)
}
}
} else { // repeat previous search
- if (*lastc == NUL && lastc_bytelen == 1) {
+ if (*lastc == NUL && lastc_bytelen <= 1) {
return FAIL;
}
if (dir) { // repeat in opposite direction
@@ -1550,12 +1547,12 @@ int searchc(cmdarg_T *cap, int t_cmd)
cap->oap->inclusive = true;
}
- p = get_cursor_line_ptr();
- col = curwin->w_cursor.col;
- len = (int)strlen(p);
+ char *p = get_cursor_line_ptr();
+ int col = curwin->w_cursor.col;
+ int len = (int)strlen(p);
while (count--) {
- for (;;) {
+ while (true) {
if (dir > 0) {
col += utfc_ptr2len(p + col);
if (col >= len) {
@@ -1567,7 +1564,7 @@ int searchc(cmdarg_T *cap, int t_cmd)
}
col -= utf_head_off(p, p + col - 1) + 1;
}
- if (lastc_bytelen == 1) {
+ if (lastc_bytelen <= 1) {
if (p[col] == c && stop) {
break;
}
@@ -1632,7 +1629,7 @@ static bool find_rawstring_end(char *linep, pos_T *startpos, pos_T *endpos)
for (p = linep + startpos->col + 1; *p && *p != '('; p++) {}
size_t delim_len = (size_t)((p - linep) - startpos->col - 1);
- char *delim_copy = xstrnsave(linep + startpos->col + 1, delim_len);
+ char *delim_copy = xmemdupz(linep + startpos->col + 1, delim_len);
bool found = false;
for (lnum = startpos->lnum; lnum <= endpos->lnum; lnum++) {
char *line = ml_get(lnum);
@@ -1821,7 +1818,7 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
if (linep[pos.col] == NUL && pos.col) {
pos.col--;
}
- for (;;) {
+ while (true) {
initc = utf_ptr2char(linep + pos.col);
if (initc == NUL) {
break;
@@ -1841,11 +1838,11 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
return NULL;
}
} else if (!cpo_bsl) {
- int col, bslcnt = 0;
+ int bslcnt = 0;
// Set "match_escaped" if there are an odd number of
// backslashes.
- for (col = pos.col; check_prevcol(linep, col, '\\', &col);) {
+ for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
bslcnt++;
}
match_escaped = (bslcnt & 1);
@@ -2207,10 +2204,10 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel)
// quotes when the start is also inside of quotes.
if ((!inquote || start_in_quotes == kTrue)
&& (c == initc || c == findc)) {
- int col, bslcnt = 0;
+ int bslcnt = 0;
if (!cpo_bsl) {
- for (col = pos.col; check_prevcol(linep, col, '\\', &col);) {
+ for (int col = pos.col; check_prevcol(linep, col, '\\', &col);) {
bslcnt++;
}
}
@@ -2244,10 +2241,10 @@ int check_linecomment(const char *line)
const char *p = line; // scan from start
// skip Lispish one-line comments
if (curbuf->b_p_lisp) {
- if (vim_strchr((char *)p, ';') != NULL) { // there may be comments
+ if (vim_strchr(p, ';') != NULL) { // there may be comments
bool in_str = false; // inside of string
- while ((p = strpbrk((char *)p, "\";")) != NULL) {
+ while ((p = strpbrk(p, "\";")) != NULL) {
if (*p == '"') {
if (in_str) {
if (*(p - 1) != '\\') { // skip escaped quote
@@ -2269,7 +2266,7 @@ int check_linecomment(const char *line)
p = NULL;
}
} else {
- while ((p = vim_strchr((char *)p, '/')) != NULL) {
+ 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. Only
// accept the position if it is not inside a string.
@@ -2295,15 +2292,10 @@ int check_linecomment(const char *line)
/// @param c char to show match for
void showmatch(int c)
{
- pos_T *lpos, save_cursor;
- pos_T mpos;
+ pos_T *lpos;
colnr_T vcol;
- long *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
- long *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
- long save_so;
- long save_siso;
- int save_state;
- colnr_T save_dollar_vcol;
+ OptInt *so = curwin->w_p_so >= 0 ? &curwin->w_p_so : &p_so;
+ OptInt *siso = curwin->w_p_siso >= 0 ? &curwin->w_p_siso : &p_siso;
char *p;
// Only show match for chars in the 'matchpairs' option.
@@ -2345,26 +2337,26 @@ void showmatch(int c)
return;
}
- mpos = *lpos; // save the pos, update_screen() may change it
- save_cursor = curwin->w_cursor;
- save_so = *so;
- save_siso = *siso;
+ pos_T mpos = *lpos; // save the pos, update_screen() may change it
+ pos_T save_cursor = curwin->w_cursor;
+ OptInt save_so = *so;
+ OptInt save_siso = *siso;
// Handle "$" in 'cpo': If the ')' is typed on top of the "$",
// stop displaying the "$".
if (dollar_vcol >= 0 && dollar_vcol == curwin->w_virtcol) {
dollar_vcol = -1;
}
curwin->w_virtcol++; // do display ')' just before "$"
- update_screen(); // show the new char first
- save_dollar_vcol = dollar_vcol;
- save_state = State;
+ colnr_T save_dollar_vcol = dollar_vcol;
+ int save_state = State;
State = MODE_SHOWMATCH;
ui_cursor_shape(); // may show different cursor shape
curwin->w_cursor = mpos; // move to matching char
*so = 0; // don't use 'scrolloff' here
*siso = 0; // don't use 'sidescrolloff' here
- show_cursor_info(false);
+ show_cursor_info_later(false);
+ update_screen(); // show the new char
setcursor();
ui_flush();
// Restore dollar_vcol(), because setcursor() may call curs_rows()
@@ -2375,9 +2367,9 @@ void showmatch(int c)
// brief pause, unless 'm' is present in 'cpo' and a character is
// available.
if (vim_strchr(p_cpo, CPO_SHOWMATCH) != NULL) {
- os_delay((uint64_t)p_mat * 100L + 8, true);
+ os_delay((uint64_t)p_mat * 100 + 8, true);
} else if (!char_avail()) {
- os_delay((uint64_t)p_mat * 100L + 9, false);
+ os_delay((uint64_t)p_mat * 100 + 9, false);
}
curwin->w_cursor = save_cursor; // restore cursor position
*so = save_so;
@@ -2390,7 +2382,7 @@ void showmatch(int c)
/// Used while an operator is pending, and in Visual mode.
///
/// @param forward true for forward, false for backward
-int current_search(long count, bool forward)
+int current_search(int count, bool forward)
{
bool old_p_ws = p_ws;
pos_T save_VIsual = VIsual;
@@ -2475,7 +2467,7 @@ int current_search(long count, bool forward)
} else { // try again from end of buffer
// searching backwards, so set pos to last line and col
pos.lnum = curwin->w_buffer->b_ml.ml_line_count;
- pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count));
+ pos.col = (colnr_T)strlen(ml_get(curwin->w_buffer->b_ml.ml_line_count));
}
}
}
@@ -2531,7 +2523,6 @@ int current_search(long count, bool forward)
static int is_zero_width(char *pattern, int move, pos_T *cur, Direction direction)
{
regmmatch_T regmatch;
- int nmatched = 0;
int result = -1;
pos_T pos;
const int called_emsg_before = called_emsg;
@@ -2558,13 +2549,14 @@ static int is_zero_width(char *pattern, int move, pos_T *cur, Direction directio
}
if (searchit(curwin, curbuf, &pos, NULL, direction, pattern, 1,
SEARCH_KEEP + flag, RE_SEARCH, NULL) != FAIL) {
+ int nmatched = 0;
// Zero-width pattern should match somewhere, then we can check if
// start and end are in the same position.
do {
regmatch.startpos[0].col++;
- nmatched = (int)vim_regexec_multi(&regmatch, curwin, curbuf,
- pos.lnum, regmatch.startpos[0].col,
- NULL, NULL);
+ nmatched = vim_regexec_multi(&regmatch, curwin, curbuf,
+ pos.lnum, regmatch.startpos[0].col,
+ NULL, NULL);
if (nmatched != 0) {
break;
}
@@ -2587,16 +2579,14 @@ static int is_zero_width(char *pattern, int move, pos_T *cur, Direction directio
/// return true if line 'lnum' is empty or has white chars only.
int linewhite(linenr_T lnum)
{
- char *p;
-
- p = skipwhite(ml_get(lnum));
+ char *p = skipwhite(ml_get(lnum));
return *p == NUL;
}
/// Add the search count "[3/19]" to "msgbuf".
/// See update_search_stat() for other arguments.
static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool show_top_bot_msg,
- char *msgbuf, bool recompute, int maxcount, long timeout)
+ char *msgbuf, bool recompute, int maxcount, int timeout)
{
searchstat_T stat;
@@ -2668,7 +2658,7 @@ static void cmdline_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, bool sh
// dirc == '/': find the next match
// dirc == '?': find the previous match
static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchstat_T *stat,
- bool recompute, int maxcount, long timeout)
+ bool recompute, int maxcount, int timeout)
{
int save_ws = p_ws;
bool wraparound = false;
@@ -2682,7 +2672,6 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
static int chgtick = 0;
static char *lastpat = NULL;
static buf_T *lbuf = NULL;
- proftime_T start;
CLEAR_POINTER(stat);
@@ -2718,15 +2707,18 @@ static void update_search_stat(int dirc, pos_T *pos, pos_T *cursor_pos, searchst
lbuf = curbuf;
}
+ // when searching backwards and having jumped to the first occurrence,
+ // cur must remain greater than 1
if (equalpos(lastpos, *cursor_pos) && !wraparound
- && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 0)) {
+ && (dirc == 0 || dirc == '/' ? cur < cnt : cur > 1)) {
cur += dirc == 0 ? 0 : dirc == '/' ? 1 : -1;
} else {
+ proftime_T start;
bool done_search = false;
pos_T endpos = { 0, 0, 0 };
p_ws = false;
if (timeout > 0) {
- start = profile_setlimit(timeout);
+ start = profile_setlimit(timeout);
}
while (!got_int && searchit(curwin, curbuf, &lastpos, &endpos,
FORWARD, NULL, 1, SEARCH_KEEP, RE_LAST,
@@ -2775,7 +2767,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
pos_T pos = curwin->w_cursor;
char *pattern = NULL;
int maxcount = SEARCH_STAT_DEF_MAX_COUNT;
- long timeout = SEARCH_STAT_DEF_TIMEOUT;
+ int timeout = SEARCH_STAT_DEF_TIMEOUT;
bool recompute = true;
searchstat_T stat;
@@ -2788,29 +2780,27 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (argvars[0].v_type != VAR_UNKNOWN) {
dict_T *dict;
dictitem_T *di;
- listitem_T *li;
bool error = false;
- if (argvars[0].v_type != VAR_DICT || argvars[0].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 0) == FAIL) {
return;
}
dict = argvars[0].vval.v_dict;
- di = tv_dict_find(dict, (const char *)"timeout", -1);
+ di = tv_dict_find(dict, "timeout", -1);
if (di != NULL) {
- timeout = (long)tv_get_number_chk(&di->di_tv, &error);
+ timeout = (int)tv_get_number_chk(&di->di_tv, &error);
if (error) {
return;
}
}
- di = tv_dict_find(dict, (const char *)"maxcount", -1);
+ di = tv_dict_find(dict, "maxcount", -1);
if (di != NULL) {
maxcount = (int)tv_get_number_chk(&di->di_tv, &error);
if (error) {
return;
}
}
- di = tv_dict_find(dict, (const char *)"recompute", -1);
+ di = tv_dict_find(dict, "recompute", -1);
if (di != NULL) {
recompute = tv_get_number_chk(&di->di_tv, &error);
if (error) {
@@ -2824,7 +2814,7 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
}
- di = tv_dict_find(dict, (const char *)"pos", -1);
+ di = tv_dict_find(dict, "pos", -1);
if (di != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
semsg(_(e_invarg2), "pos");
@@ -2834,21 +2824,21 @@ void f_searchcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invarg2), "List format should be [lnum, col, off]");
return;
}
- li = tv_list_find(di->di_tv.vval.v_list, 0L);
+ listitem_T *li = tv_list_find(di->di_tv.vval.v_list, 0);
if (li != NULL) {
pos.lnum = (linenr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
if (error) {
return;
}
}
- li = tv_list_find(di->di_tv.vval.v_list, 1L);
+ li = tv_list_find(di->di_tv.vval.v_list, 1);
if (li != NULL) {
pos.col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error) - 1;
if (error) {
return;
}
}
- li = tv_list_find(di->di_tv.vval.v_list, 2L);
+ li = tv_list_find(di->di_tv.vval.v_list, 2);
if (li != NULL) {
pos.coladd = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(li), &error);
if (error) {
@@ -3055,8 +3045,8 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
// Loop through fuzpat and str looking for a match
bool first_match = true;
while (*fuzpat != NUL && *str != NUL) {
- const int c1 = utf_ptr2char((char *)fuzpat);
- const int c2 = utf_ptr2char((char *)str);
+ const int c1 = utf_ptr2char(fuzpat);
+ const int c2 = utf_ptr2char(str);
// Found match
if (mb_tolower(c1) == mb_tolower(c2)) {
@@ -3065,6 +3055,10 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
return 0;
}
+ int recursiveScore = 0;
+ uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
+ CLEAR_FIELD(recursiveMatches);
+
// "Copy-on-Write" srcMatches into matches
if (first_match && srcMatches != NULL) {
memcpy(matches, srcMatches, (size_t)nextMatch * sizeof(srcMatches[0]));
@@ -3072,9 +3066,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
}
// Recursive call that "skips" this match
- uint32_t recursiveMatches[MAX_FUZZY_MATCHES];
- int recursiveScore = 0;
- const char *const next_char = (char *)str + utfc_ptr2len((char *)str);
+ const char *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,
@@ -3125,8 +3117,7 @@ static int fuzzy_match_recursive(const char *fuzpat, const char *str, uint32_t s
/// 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.
+/// 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".
@@ -3216,10 +3207,10 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
static void fuzzy_match_in_list(list_T *const l, char *const str, const bool matchseq,
const char *const key, Callback *const item_cb,
const bool retmatchpos, list_T *const fmatchlist,
- const long max_matches)
+ const int max_matches)
FUNC_ATTR_NONNULL_ARG(2, 5, 7)
{
- long len = tv_list_len(l);
+ int len = tv_list_len(l);
if (len == 0) {
return;
}
@@ -3228,7 +3219,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
}
fuzzyItem_T *const items = xcalloc((size_t)len, sizeof(fuzzyItem_T));
- long match_count = 0;
+ int match_count = 0;
uint32_t matches[MAX_FUZZY_MATCHES];
// For all the string items in items, get the fuzzy matching score
@@ -3247,7 +3238,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
// 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 = tv_dict_get_string(tv->vval.v_dict, (const char *)key, false);
+ itemstr = tv_dict_get_string(tv->vval.v_dict, key, false);
} else {
typval_T argv[2];
@@ -3277,7 +3268,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
if (retmatchpos) {
items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow);
int j = 0;
- const char *p = (char *)str;
+ const char *p = str;
while (*p != NUL) {
if (!ascii_iswhite(utf_ptr2char(p)) || matchseq) {
tv_list_append_number(items[match_count].lmatchpos, matches[j]);
@@ -3312,7 +3303,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
}
// Copy the matching strings with a valid score to the return list
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3325,7 +3316,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
retlist = TV_LIST_ITEM_TV(li)->vval.v_list;
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3336,7 +3327,7 @@ static void fuzzy_match_in_list(list_T *const l, char *const str, const bool mat
li = tv_list_find(fmatchlist, -1);
assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL);
retlist = TV_LIST_ITEM_TV(li)->vval.v_list;
- for (long i = 0; i < match_count; i++) {
+ for (int i = 0; i < match_count; i++) {
if (items[i].score == SCORE_NONE) {
break;
}
@@ -3366,10 +3357,9 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
Callback cb = CALLBACK_NONE;
const char *key = NULL;
bool matchseq = false;
- long max_matches = 0;
+ int max_matches = 0;
if (argvars[2].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 2) == FAIL) {
return;
}
@@ -3394,7 +3384,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv,
semsg(_(e_invarg2), tv_get_string(&di->di_tv));
return;
}
- max_matches = (long)tv_get_number_chk(&di->di_tv, NULL);
+ max_matches = (int)tv_get_number_chk(&di->di_tv, NULL);
}
if (tv_dict_find(d, "matchseq", -1) != NULL) {
@@ -3558,26 +3548,19 @@ static char *get_line_and_copy(linenr_T lnum, char *buf)
/// @param start_lnum first line to start searching
/// @param end_lnum last line for searching
void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool skip_comments,
- int type, long count, int action, linenr_T start_lnum, linenr_T end_lnum)
+ int type, int count, int action, linenr_T start_lnum, linenr_T end_lnum)
{
SearchedFile *files; // Stack of included files
SearchedFile *bigger; // When we need more space
int max_path_depth = 50;
- long match_count = 1;
+ int match_count = 1;
- char *pat;
char *new_fname;
char *curr_fname = curbuf->b_fname;
char *prev_fname = NULL;
- linenr_T lnum;
- int depth;
int depth_displayed; // For type==CHECK_PATH
- int old_files;
int already_searched;
- char *file_line;
- char *line;
char *p;
- char save_char;
bool define_matched;
regmatch_T regmatch;
regmatch_T incl_regmatch;
@@ -3595,14 +3578,14 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
incl_regmatch.regprog = NULL;
def_regmatch.regprog = NULL;
- file_line = xmalloc(LSIZE);
+ char *file_line = xmalloc(LSIZE);
if (type != CHECK_PATH && type != FIND_DEFINE
// when CONT_SOL is set compare "ptr" with the beginning of the
// line is faster than quote_meta/regcomp/regexec "ptr" -- Acevedo
&& !compl_status_sol()) {
size_t patlen = len + 5;
- pat = xmalloc(patlen);
+ char *pat = xmalloc(patlen);
assert(len <= INT_MAX);
snprintf(pat, patlen, whole ? "\\<%.*s\\>" : "%.*s", (int)len, ptr);
// ignore case according to p_ic, p_scs and pat
@@ -3631,23 +3614,23 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
def_regmatch.rm_ic = false; // don't ignore case in define pat.
}
files = xcalloc((size_t)max_path_depth, sizeof(SearchedFile));
- old_files = max_path_depth;
- depth = depth_displayed = -1;
+ int old_files = max_path_depth;
+ int depth = depth_displayed = -1;
- lnum = start_lnum;
+ linenr_T lnum = start_lnum;
if (end_lnum > curbuf->b_ml.ml_line_count) {
end_lnum = curbuf->b_ml.ml_line_count;
}
if (lnum > end_lnum) { // do at least one line
lnum = end_lnum;
}
- line = get_line_and_copy(lnum, file_line);
+ char *line = get_line_and_copy(lnum, file_line);
- for (;;) {
+ while (true) {
if (incl_regmatch.regprog != NULL
- && vim_regexec(&incl_regmatch, line, (colnr_T)0)) {
+ && vim_regexec(&incl_regmatch, line, 0)) {
char *p_fname = (curr_fname == curbuf->b_fname)
- ? curbuf->b_ffname : curr_fname;
+ ? curbuf->b_ffname : curr_fname;
if (inc_opt != NULL && strstr(inc_opt, "\\zs") != NULL) {
// Use text from '\zs' to '\ze' (or end) of 'include'.
@@ -3655,11 +3638,11 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
(size_t)(incl_regmatch.endp[0]
- incl_regmatch.startp[0]),
FNAME_EXP|FNAME_INCL|FNAME_REL,
- 1L, p_fname);
+ 1, p_fname);
} else {
// Use text after match with 'include'.
new_fname = file_name_in_line(incl_regmatch.endp[0], 0,
- FNAME_EXP|FNAME_INCL|FNAME_REL, 1L, p_fname,
+ FNAME_EXP|FNAME_INCL|FNAME_REL, 1, p_fname,
NULL);
}
already_searched = false;
@@ -3720,7 +3703,7 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
if (new_fname != NULL) {
// using "new_fname" is more reliable, e.g., when
// 'includeexpr' is set.
- msg_outtrans_attr(new_fname, HL_ATTR(HLF_D));
+ msg_outtrans(new_fname, HL_ATTR(HLF_D));
} else {
// Isolate the file name.
// Include the surrounding "" or <> if present.
@@ -3752,9 +3735,9 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
i++;
}
}
- save_char = p[i];
+ char save_char = p[i];
p[i] = NUL;
- msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ msg_outtrans(p, HL_ATTR(HLF_D));
p[i] = save_char;
}
@@ -3802,14 +3785,14 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
files[depth].lnum = 0;
files[depth].matched = false;
if (action == ACTION_EXPAND) {
- msg_hist_off = true; // reset in msg_trunc_attr()
+ msg_hist_off = true; // reset in msg_trunc()
vim_snprintf(IObuff, IOSIZE,
_("Scanning included file: %s"),
new_fname);
- msg_trunc_attr(IObuff, true, HL_ATTR(HLF_R));
+ msg_trunc(IObuff, true, HL_ATTR(HLF_R));
} else if (p_verbose >= 5) {
verbose_enter();
- smsg(_("Searching included file %s"), new_fname);
+ smsg(0, _("Searching included file %s"), new_fname);
verbose_leave();
}
}
@@ -3820,7 +3803,7 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
search_line:
define_matched = false;
if (def_regmatch.regprog != NULL
- && vim_regexec(&def_regmatch, line, (colnr_T)0)) {
+ && vim_regexec(&def_regmatch, line, 0)) {
// Pattern must be first identifier after 'define', so skip
// to that position before checking for match of pattern. Also
// don't let it match beyond the end of this identifier.
@@ -4005,7 +3988,7 @@ search_line:
} else if (action == ACTION_SHOW) {
show_pat_in_path(line, type, did_show, action,
(depth == -1) ? NULL : files[depth].fp,
- (depth == -1) ? &lnum : &files[depth].lnum, 1L);
+ (depth == -1) ? &lnum : &files[depth].lnum, 1);
did_show = true;
} else {
// ":psearch" uses the preview window
@@ -4035,7 +4018,7 @@ search_line:
curwin->w_cursor.lnum = lnum;
check_cursor();
} else {
- if (!GETFILE_SUCCESS(getfile(0, (char *)files[depth].name, NULL, true,
+ if (!GETFILE_SUCCESS(getfile(0, files[depth].name, NULL, true,
files[depth].lnum, false))) {
break; // failed to jump to file
}
@@ -4112,7 +4095,7 @@ exit_matched:
}
already = NULL;
}
- // End of big for (;;) loop.
+ // End of big while (true) loop.
// Close any files that are still open.
for (i = 0; i <= depth; i++) {
@@ -4127,9 +4110,9 @@ exit_matched:
if (type == CHECK_PATH) {
if (!did_show) {
if (action != ACTION_SHOW_ALL) {
- msg(_("All included files were found"));
+ msg(_("All included files were found"), 0);
} else {
- msg(_("No included files"));
+ msg(_("No included files"), 0);
}
}
} else if (!found
@@ -4154,11 +4137,9 @@ fpip_end:
}
static void show_pat_in_path(char *line, int type, bool did_show, int action, FILE *fp,
- linenr_T *lnum, long count)
+ linenr_T *lnum, int count)
FUNC_ATTR_NONNULL_ARG(1, 6)
{
- char *p;
-
if (did_show) {
msg_putchar('\n'); // cursor below last one
} else if (!msg_silent) {
@@ -4167,8 +4148,8 @@ static void show_pat_in_path(char *line, int type, bool did_show, int action, FI
if (got_int) { // 'q' typed at "--more--" message
return;
}
- for (;;) {
- p = line + strlen(line) - 1;
+ while (true) {
+ char *p = line + strlen(line) - 1;
if (fp != NULL) {
// We used fgets(), so get rid of newline at end
if (p >= line && *p == '\n') {
@@ -4180,11 +4161,11 @@ static void show_pat_in_path(char *line, int type, bool did_show, int action, FI
*(p + 1) = NUL;
}
if (action == ACTION_SHOW_ALL) {
- snprintf(IObuff, IOSIZE, "%3ld: ", count); // Show match nr.
- msg_puts((const char *)IObuff);
+ snprintf(IObuff, IOSIZE, "%3d: ", count); // Show match nr.
+ msg_puts(IObuff);
snprintf(IObuff, IOSIZE, "%4" PRIdLINENR, *lnum); // Show line nr.
// Highlight line numbers.
- msg_puts_attr((const char *)IObuff, HL_ATTR(HLF_N));
+ msg_puts_attr(IObuff, HL_ATTR(HLF_N));
msg_puts(" ");
}
msg_prt_line(line, false);
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 2f140ba840..48ca555e7f 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -1,17 +1,16 @@
-#ifndef NVIM_SEARCH_H
-#define NVIM_SEARCH_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/normal.h"
-#include "nvim/os/time.h"
-#include "nvim/pos.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/regexp_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
// Values for the find_pattern_in_path() function args 'type' and 'action':
#define FIND_ANY 1
@@ -53,7 +52,7 @@
#define RE_LAST 2 // use last used pattern if "pat" is NULL
// Values for searchcount()
-#define SEARCH_STAT_DEF_TIMEOUT 40L
+#define SEARCH_STAT_DEF_TIMEOUT 40
#define SEARCH_STAT_DEF_MAX_COUNT 99
#define SEARCH_STAT_BUF_LEN 12
@@ -110,4 +109,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "search.h.generated.h"
#endif
-#endif // NVIM_SEARCH_H
diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c
index db647f3ecb..d49224a987 100644
--- a/src/nvim/sha256.c
+++ b/src/nvim/sha256.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file sha256.c
///
/// FIPS-180-2 compliant SHA-256 implementation
@@ -18,8 +15,8 @@
#include <stdio.h>
#include <string.h>
+#include "nvim/memory.h"
#include "nvim/sha256.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sha256.c.generated.h"
@@ -201,14 +198,14 @@ void sha256_update(context_sha256_T *ctx, const uint8_t *input, size_t length)
memcpy(ctx->buffer + left, input, fill);
sha256_process(ctx, ctx->buffer);
length -= fill;
- input += fill;
+ input += fill;
left = 0;
}
while (length >= SHA256_BUFFER_SIZE) {
sha256_process(ctx, input);
length -= SHA256_BUFFER_SIZE;
- input += SHA256_BUFFER_SIZE;
+ input += SHA256_BUFFER_SIZE;
}
if (length) {
@@ -230,7 +227,7 @@ void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE])
uint8_t msglen[8];
high = (ctx->total[0] >> 29) | (ctx->total[1] << 3);
- low = (ctx->total[0] << 3);
+ low = (ctx->total[0] << 3);
PUT_UINT32(high, msglen, 0);
PUT_UINT32(low, msglen, 4);
diff --git a/src/nvim/sha256.h b/src/nvim/sha256.h
index eeb4031509..942836a411 100644
--- a/src/nvim/sha256.h
+++ b/src/nvim/sha256.h
@@ -1,11 +1,8 @@
-#ifndef NVIM_SHA256_H
-#define NVIM_SHA256_H
+#pragma once
-#include <stddef.h>
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
-#include "nvim/types.h"
-
#define SHA256_BUFFER_SIZE 64
#define SHA256_SUM_SIZE 32
@@ -18,4 +15,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sha256.h.generated.h"
#endif
-#endif // NVIM_SHA256_H
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 90a01aaf97..be898142f0 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <msgpack/object.h>
@@ -16,25 +13,25 @@
#include <uv.h>
#include "auto/config.h"
-#include "klib/khash.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/cmdhist.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
@@ -43,38 +40,26 @@
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fileio.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/os/fs.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/search.h"
#include "nvim/shada.h"
#include "nvim/strings.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef HAVE_BE64TOH
-# define _BSD_SOURCE 1
-# define _DEFAULT_SOURCE 1
+# define _BSD_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
+# define _DEFAULT_SOURCE 1 // NOLINT(bugprone-reserved-identifier)
# include ENDIAN_INCLUDE_FILE
#endif
-// Note: when using bufset hash pointers are intentionally casted to uintptr_t
-// and not to khint32_t or khint64_t: this way compiler must give a warning
-// (-Wconversion) when types change.
-#ifdef ARCH_32
-KHASH_SET_INIT_INT(bufset)
-#elif defined(ARCH_64)
-KHASH_SET_INIT_INT64(bufset)
-#else
-# error Not a 64- or 32-bit architecture
-#endif
-KHASH_MAP_INIT_STR(fnamebufs, buf_T *)
-KHASH_SET_INIT_STR(strset)
-
#define SEARCH_KEY_MAGIC "sm"
#define SEARCH_KEY_SMARTCASE "sc"
#define SEARCH_KEY_HAS_LINE_OFFSET "sl"
@@ -305,8 +290,6 @@ typedef struct hm_llist_entry {
struct hm_llist_entry *prev; ///< Pointer to previous entry or NULL.
} HMLListEntry;
-KHASH_MAP_INIT_STR(hmll_entries, HMLListEntry *)
-
/// Sized linked list structure for history merger
typedef struct {
HMLListEntry *entries; ///< Pointer to the start of the allocated array of
@@ -318,9 +301,8 @@ typedef struct {
HMLListEntry *last_free_entry; ///< Last unused element in entries array.
size_t size; ///< Number of allocated entries.
size_t num_entries; ///< Number of entries already used.
- khash_t(hmll_entries) contained_entries; ///< Hash mapping all history entry
- ///< strings to corresponding entry
- ///< pointers.
+ PMap(cstr_t) contained_entries; ///< Map all history entry strings to
+ ///< corresponding entry pointers.
} HMLList;
typedef struct {
@@ -348,8 +330,6 @@ typedef struct {
Timestamp greatest_timestamp; ///< Greatest timestamp among marks.
} FileMarks;
-KHASH_MAP_INIT_STR(file_marks, FileMarks)
-
/// State structure used by shada_write
///
/// Before actually writing most of the data is read to this structure.
@@ -363,8 +343,8 @@ typedef struct {
PossiblyFreedShadaEntry search_pattern; ///< Last search pattern.
PossiblyFreedShadaEntry sub_search_pattern; ///< Last s/ search pattern.
PossiblyFreedShadaEntry replacement; ///< Last s// replacement string.
- khash_t(strset) dumped_variables; ///< Names of already dumped variables.
- khash_t(file_marks) file_marks; ///< All file marks.
+ Set(cstr_t) dumped_variables; ///< Names of already dumped variables.
+ PMap(cstr_t) file_marks; ///< All file marks.
} WriteMergerState;
struct sd_read_def;
@@ -504,7 +484,7 @@ static inline void hmll_init(HMLList *const hmll, const size_t size)
.free_entry = NULL,
.size = size,
.num_entries = 0,
- .contained_entries = KHASH_EMPTY_TABLE(hmll_entries),
+ .contained_entries = MAP_INIT,
};
hmll->last_free_entry = hmll->entries;
}
@@ -535,10 +515,10 @@ static inline void hmll_remove(HMLList *const hmll, HMLListEntry *const hmll_ent
assert(hmll->free_entry == NULL);
hmll->free_entry = hmll_entry;
}
- const khiter_t k = kh_get(hmll_entries, &hmll->contained_entries,
- hmll_entry->data.data.history_item.string);
- assert(k != kh_end(&hmll->contained_entries));
- kh_del(hmll_entries, &hmll->contained_entries, k);
+ ptr_t val = pmap_del(cstr_t)(&hmll->contained_entries,
+ hmll_entry->data.data.history_item.string, NULL);
+ assert(val);
+ (void)val;
if (hmll_entry->next == NULL) {
hmll->last = hmll_entry->prev;
} else {
@@ -586,11 +566,11 @@ static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, co
}
target_entry->data = data;
target_entry->can_free_entry = can_free_entry;
- int kh_ret;
- const khiter_t k = kh_put(hmll_entries, &hmll->contained_entries,
- data.data.history_item.string, &kh_ret);
- if (kh_ret > 0) {
- kh_val(&hmll->contained_entries, k) = target_entry;
+ bool new_item = false;
+ ptr_t *val = pmap_put_ref(cstr_t)(&hmll->contained_entries, data.data.history_item.string,
+ NULL, &new_item);
+ if (new_item) {
+ *val = target_entry;
}
hmll->num_entries++;
target_entry->prev = hmll_entry;
@@ -614,7 +594,7 @@ static inline void hmll_insert(HMLList *const hmll, HMLListEntry *hmll_entry, co
static inline void hmll_dealloc(HMLList *const hmll)
FUNC_ATTR_NONNULL_ALL
{
- kh_dealloc(hmll_entries, &hmll->contained_entries);
+ map_destroy(cstr_t, &hmll->contained_entries);
xfree(hmll->entries);
}
@@ -771,30 +751,6 @@ static void close_file(void *cookie)
}
}
-/// Check whether buffer is in the given set
-///
-/// @param[in] set Set to check within.
-/// @param[in] buf Buffer to find.
-///
-/// @return true or false.
-static inline bool in_bufset(const khash_t(bufset) *const set, const buf_T *buf)
- FUNC_ATTR_PURE
-{
- return kh_get(bufset, set, (uintptr_t)buf) != kh_end(set);
-}
-
-/// Check whether string is in the given set
-///
-/// @param[in] set Set to check within.
-/// @param[in] buf Buffer to find.
-///
-/// @return true or false.
-static inline bool in_strset(const khash_t(strset) *const set, char *str)
- FUNC_ATTR_PURE
-{
- return kh_get(strset, set, str) != kh_end(set);
-}
-
/// Msgpack callback for writing to ShaDaWriteDef*
static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)
{
@@ -837,7 +793,7 @@ static int shada_read_file(const char *const file, const int flags)
if (p_verbose > 1) {
verbose_enter();
- smsg(_("Reading ShaDa file \"%s\"%s%s%s%s"),
+ smsg(0, _("Reading ShaDa file \"%s\"%s%s%s%s"),
fname,
(flags & kShaDaWantInfo) ? _(" info") : "",
(flags & kShaDaWantMarks) ? _(" marks") : "",
@@ -930,10 +886,11 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
}
}
HMLList *const hmll = &hms_p->hmll;
- const khiter_t k = kh_get(hmll_entries, &hms_p->hmll.contained_entries,
- entry.data.history_item.string);
- if (k != kh_end(&hmll->contained_entries)) {
- HMLListEntry *const existing_entry = kh_val(&hmll->contained_entries, k);
+ cstr_t *key_alloc = NULL;
+ ptr_t *val = pmap_ref(cstr_t)(&hms_p->hmll.contained_entries, entry.data.history_item.string,
+ &key_alloc);
+ if (val) {
+ HMLListEntry *const existing_entry = *val;
if (entry.timestamp > existing_entry->data.timestamp) {
hmll_remove(hmll, existing_entry);
} else if (!do_iter && entry.timestamp == existing_entry->data.timestamp) {
@@ -944,7 +901,7 @@ static void hms_insert(HistoryMergerState *const hms_p, const ShadaEntry entry,
existing_entry->data = entry;
existing_entry->can_free_entry = can_free_entry;
// Previous key was freed above, as part of freeing the ShaDa entry.
- kh_key(&hmll->contained_entries, k) = entry.data.history_item.string;
+ *key_alloc = entry.data.history_item.string;
return;
} else {
return;
@@ -1046,24 +1003,27 @@ static inline void hms_dealloc(HistoryMergerState *const hms_p)
/// @param[in] fname File name to find.
///
/// @return Pointer to the buffer or NULL.
-static buf_T *find_buffer(khash_t(fnamebufs) *const fname_bufs, const char *const fname)
+static buf_T *find_buffer(PMap(cstr_t) *const fname_bufs, const char *const fname)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- int kh_ret;
- khint_t k = kh_put(fnamebufs, fname_bufs, fname, &kh_ret);
- if (!kh_ret) {
- return kh_val(fname_bufs, k);
+ cstr_t *key_alloc = NULL;
+ bool new_item = false;
+ buf_T **ref = (buf_T **)pmap_put_ref(cstr_t)(fname_bufs, fname, &key_alloc, &new_item);
+ if (new_item) {
+ *key_alloc = xstrdup(fname);
+ } else {
+ return *ref; // item already existed (can be a NULL value)
}
- kh_key(fname_bufs, k) = xstrdup(fname);
+
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL) {
if (path_fnamecmp(fname, buf->b_ffname) == 0) {
- kh_val(fname_bufs, k) = buf;
+ *ref = buf;
return buf;
}
}
}
- kh_val(fname_bufs, k) = NULL;
+ *ref = NULL;
return NULL;
}
@@ -1163,9 +1123,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
}
}
ShadaEntry cur_entry;
- khash_t(bufset) cl_bufs = KHASH_EMPTY_TABLE(bufset);
- khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs);
- khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset);
+ Set(ptr_t) cl_bufs = SET_INIT;
+ PMap(cstr_t) fname_bufs = MAP_INIT;
+ Set(cstr_t) oldfiles_set = SET_INIT;
if (get_old_files && (oldfiles_list == NULL || force)) {
oldfiles_list = tv_list_alloc(kListLenUnknown);
set_vim_var_list(VV_OLDFILES, oldfiles_list);
@@ -1307,6 +1267,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.mark = cur_entry.data.filemark.mark,
.fnum = (buf == NULL ? 0 : buf->b_fnum),
.timestamp = cur_entry.timestamp,
+ .view = INIT_FMARKV,
.additional_data = cur_entry.data.filemark.additional_data,
},
};
@@ -1358,8 +1319,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
case kSDItemChange:
case kSDItemLocalMark: {
- if (get_old_files && !in_strset(&oldfiles_set,
- cur_entry.data.filemark.fname)) {
+ if (get_old_files && !set_has(cstr_t, &oldfiles_set, cur_entry.data.filemark.fname)) {
char *fname = cur_entry.data.filemark.fname;
if (want_marks) {
// Do not bother with allocating memory for the string if already
@@ -1367,8 +1327,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
// want_marks is set because this way it may be used for a mark.
fname = xstrdup(fname);
}
- int kh_ret;
- (void)kh_put(strset, &oldfiles_set, fname, &kh_ret);
+ set_put(cstr_t, &oldfiles_set, fname);
tv_list_append_allocated_string(oldfiles_list, fname);
if (!want_marks) {
// Avoid free because this string was already used.
@@ -1388,6 +1347,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.mark = cur_entry.data.filemark.mark,
.fnum = 0,
.timestamp = cur_entry.timestamp,
+ .view = INIT_FMARKV,
.additional_data = cur_entry.data.filemark.additional_data,
};
if (cur_entry.type == kSDItemLocalMark) {
@@ -1396,8 +1356,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
break;
}
} else {
- int kh_ret;
- (void)kh_put(bufset, &cl_bufs, (uintptr_t)buf, &kh_ret);
+ set_put(ptr_t, &cl_bufs, buf);
#define SDE_TO_FMARK(entry) fm
#define AFTERFREE(entry) (entry).data.filemark.fname = NULL
#define DUMMY_IDX_ADJ(i)
@@ -1435,21 +1394,21 @@ shada_read_main_cycle_end:
hms_dealloc(&hms[i]);
}
}
- if (cl_bufs.n_occupied) {
+ if (cl_bufs.h.n_occupied) {
FOR_ALL_TAB_WINDOWS(tp, wp) {
(void)tp;
- if (in_bufset(&cl_bufs, wp->w_buffer)) {
+ if (set_has(ptr_t, &cl_bufs, wp->w_buffer)) {
wp->w_changelistidx = wp->w_buffer->b_changelistlen;
}
}
}
- kh_dealloc(bufset, &cl_bufs);
+ set_destroy(ptr_t, &cl_bufs);
const char *key;
- kh_foreach_key(&fname_bufs, key, {
- xfree((void *)key);
+ map_foreach_key(&fname_bufs, key, {
+ xfree((char *)key);
})
- kh_dealloc(fnamebufs, &fname_bufs);
- kh_dealloc(strset, &oldfiles_set);
+ map_destroy(cstr_t, &fname_bufs);
+ set_destroy(cstr_t, &oldfiles_set);
}
/// Default shada file location: cached path
@@ -1491,7 +1450,7 @@ static char *shada_filename(const char *file)
// If shell is not performing them then they should be done in main.c
// where arguments are parsed, *not here*.
expand_env((char *)file, &(NameBuff[0]), MAXPATHL);
- file = (const char *)&(NameBuff[0]);
+ file = &(NameBuff[0]);
}
}
return xstrdup(file);
@@ -1544,13 +1503,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
dict_T *const d = (src); \
if (d != NULL) { \
size_t todo = d->dv_hashtab.ht_used; \
- for (const hashitem_T *hi= d->dv_hashtab.ht_array; todo; hi++) { \
+ for (const hashitem_T *hi = d->dv_hashtab.ht_array; todo; hi++) { \
if (!HASHITEM_EMPTY(hi)) { \
todo--; \
dictitem_T *const di = TV_DICT_HI2DI(hi); \
- const size_t key_len = strlen((const char *)hi->hi_key); \
+ const size_t key_len = strlen(hi->hi_key); \
msgpack_pack_str(spacker, key_len); \
- msgpack_pack_str_body(spacker, (const char *)hi->hi_key, key_len); \
+ msgpack_pack_str_body(spacker, hi->hi_key, key_len); \
if (encode_vim_to_msgpack(spacker, &di->di_tv, \
_("additional data of ShaDa " what)) \
== FAIL) { \
@@ -1603,15 +1562,14 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemVariable: {
- if (entry.data.global_var.value.v_type == VAR_TYPE_BLOB) {
+ if (entry.data.global_var.value.v_type == VAR_BLOB) {
// Strings and Blobs both pack as msgpack BINs; differentiate them by
// storing an additional VAR_TYPE_BLOB element alongside Blobs
list_T *const list = tv_list_alloc(1);
tv_list_append_number(list, VAR_TYPE_BLOB);
entry.data.global_var.additional_elements = list;
}
- const size_t arr_size = 2 + (size_t)(
- tv_list_len(entry.data.global_var.additional_elements));
+ const size_t arr_size = 2 + (size_t)(tv_list_len(entry.data.global_var.additional_elements));
msgpack_pack_array(spacker, arr_size);
const String varname = cstr_as_string(entry.data.global_var.name);
PACK_BIN(varname);
@@ -1639,24 +1597,24 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemSearchPattern: {
- const size_t map_size = (size_t)(
- 1 // Search pattern is always present
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
- + ONE_IF_NOT_DEFAULT(entry,
- search_pattern.is_substitute_pattern)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
- // finally, additional data:
- + (size_t)(
- entry.data.search_pattern.additional_data
- ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
- : 0));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // Search pattern is always present
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ + ONE_IF_NOT_DEFAULT(entry,
+ search_pattern.is_substitute_pattern)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
+ // finally, additional data:
+ + (
+ entry.data.search_pattern.additional_data
+ ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
+ : 0));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(SEARCH_KEY_PAT);
PACK_BIN(cstr_as_string(entry.data.search_pattern.pat));
PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
@@ -1680,17 +1638,17 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
case kSDItemGlobalMark:
case kSDItemLocalMark:
case kSDItemJump: {
- const size_t map_size = (size_t)(
- 1 // File name
- + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
- + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
- + ONE_IF_NOT_DEFAULT(entry, filemark.name)
- // Additional entries, if any:
- + (size_t)(
- entry.data.filemark.additional_data == NULL
- ? 0
- : entry.data.filemark.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // File name
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ + ONE_IF_NOT_DEFAULT(entry, filemark.name)
+ // Additional entries, if any:
+ + (
+ entry.data.filemark.additional_data == NULL
+ ? 0
+ : entry.data.filemark.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(KEY_FILE);
PACK_BIN(cstr_as_string(entry.data.filemark.fname));
if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
@@ -1713,15 +1671,15 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
break;
}
case kSDItemRegister: {
- const size_t map_size = (size_t)(2 // Register contents and name
- + ONE_IF_NOT_DEFAULT(entry, reg.type)
- + ONE_IF_NOT_DEFAULT(entry, reg.width)
- + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
- // Additional entries, if any:
- + (size_t)(entry.data.reg.additional_data == NULL
- ? 0
- : entry.data.reg.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (2 // Register contents and name
+ + ONE_IF_NOT_DEFAULT(entry, reg.type)
+ + ONE_IF_NOT_DEFAULT(entry, reg.width)
+ + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
+ // Additional entries, if any:
+ + (entry.data.reg.additional_data == NULL
+ ? 0
+ : entry.data.reg.additional_data->dv_hashtab.ht_used));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(REG_KEY_CONTENTS);
msgpack_pack_array(spacker, entry.data.reg.contents_size);
for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
@@ -1751,20 +1709,20 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
case kSDItemBufferList:
msgpack_pack_array(spacker, entry.data.buffer_list.size);
for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
- const size_t map_size = (size_t)(
- 1 // Buffer name
- + (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
- != default_pos.lnum)
- + (size_t)(entry.data.buffer_list.buffers[i].pos.col
- != default_pos.col)
- // Additional entries, if any:
- + (size_t)(
- entry.data.buffer_list.buffers[i].additional_data
- == NULL
- ? 0
- : (entry.data.buffer_list.buffers[i].additional_data
- ->dv_hashtab.ht_used)));
- msgpack_pack_map(spacker, map_size);
+ size_t entry_map_size = (
+ 1 // Buffer name
+ + (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
+ != default_pos.lnum)
+ + (size_t)(entry.data.buffer_list.buffers[i].pos.col
+ != default_pos.col)
+ // Additional entries, if any:
+ + (
+ entry.data.buffer_list.buffers[i].additional_data
+ == NULL
+ ? 0
+ : (entry.data.buffer_list.buffers[i].additional_data
+ ->dv_hashtab.ht_used)));
+ msgpack_pack_map(spacker, entry_map_size);
PACK_STATIC_STR(KEY_FILE);
PACK_BIN(cstr_as_string(entry.data.buffer_list.buffers[i].fname));
if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
@@ -2152,7 +2110,7 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
break;
}
case kSDItemVariable:
- if (!in_strset(&wms->dumped_variables, entry.data.global_var.name)) {
+ if (!set_has(cstr_t, &wms->dumped_variables, entry.data.global_var.name)) {
ret = shada_pack_entry(packer, entry, 0);
}
shada_free_shada_entry(&entry);
@@ -2199,6 +2157,12 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
shada_free_shada_entry(&entry);
break;
}
+ if (wms->global_marks[idx].data.type == kSDItemMissing) {
+ if (namedfm[idx].fmark.timestamp >= entry.timestamp) {
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ }
COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
}
break;
@@ -2208,14 +2172,13 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
shada_free_shada_entry(&entry);
break;
}
- const char *const fname = (const char *)entry.data.filemark.fname;
- khiter_t k;
- int kh_ret;
- k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
- FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
- if (kh_ret > 0) {
- CLEAR_POINTER(filemarks);
+ const char *const fname = entry.data.filemark.fname;
+ cstr_t *key = NULL;
+ ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, &key, NULL);
+ if (*val == NULL) {
+ *val = xcalloc(1, sizeof(FileMarks));
}
+ FileMarks *const filemarks = *val;
if (entry.timestamp > filemarks->greatest_timestamp) {
filemarks->greatest_timestamp = entry.timestamp;
}
@@ -2229,20 +2192,35 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
entry;
} else {
PossiblyFreedShadaEntry *const wms_entry = &filemarks->marks[idx];
+ bool set_wms = true;
if (wms_entry->data.type != kSDItemMissing) {
if (wms_entry->data.timestamp >= entry.timestamp) {
shada_free_shada_entry(&entry);
break;
}
if (wms_entry->can_free_entry) {
- if (kh_key(&wms->file_marks, k)
- == wms_entry->data.data.filemark.fname) {
- kh_key(&wms->file_marks, k) = entry.data.filemark.fname;
+ if (*key == wms_entry->data.data.filemark.fname) {
+ *key = entry.data.filemark.fname;
}
shada_free_shada_entry(&wms_entry->data);
}
+ } else {
+ FOR_ALL_BUFFERS(buf) {
+ if (buf->b_ffname != NULL
+ && path_fnamecmp(entry.data.filemark.fname, buf->b_ffname) == 0) {
+ fmark_T fm;
+ mark_get(buf, curwin, &fm, kMarkBufLocal, (int)entry.data.filemark.name);
+ if (fm.timestamp >= entry.timestamp) {
+ set_wms = false;
+ shada_free_shada_entry(&entry);
+ break;
+ }
+ }
+ }
+ }
+ if (set_wms) {
+ *wms_entry = pfs_entry;
}
- *wms_entry = pfs_entry;
}
} else {
#define AFTERFREE_DUMMY(entry)
@@ -2279,11 +2257,11 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
/// @param[in] removable_bufs Cache of buffers ignored due to their location.
///
/// @return true or false.
-static inline bool ignore_buf(const buf_T *const buf, khash_t(bufset) *const removable_bufs)
+static inline bool ignore_buf(const buf_T *const buf, Set(ptr_t) *const removable_bufs)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \
- || bt_terminal(buf) || in_bufset(removable_bufs, buf));
+ || bt_terminal(buf) || set_has(ptr_t, removable_bufs, (ptr_t)buf));
}
/// Get list of buffers to write to the shada file
@@ -2291,7 +2269,7 @@ static inline bool ignore_buf(const buf_T *const buf, khash_t(bufset) *const rem
/// @param[in] removable_bufs Buffers which are ignored
///
/// @return ShadaEntry List of buffers to save, kSDItemBufferList entry.
-static inline ShadaEntry shada_get_buflist(khash_t(bufset) *const removable_bufs)
+static inline ShadaEntry shada_get_buflist(Set(ptr_t) *const removable_bufs)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
int max_bufs = get_shada_parameter('%');
@@ -2369,8 +2347,8 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
: pat.off.line),
.place_cursor_at_end = (
is_substitute_pattern
- ? defaults.data.search_pattern.place_cursor_at_end
- : pat.off.end),
+ ? defaults.data.search_pattern.place_cursor_at_end
+ : pat.off.end),
.offset = (is_substitute_pattern
? defaults.data.search_pattern.offset
: pat.off.off),
@@ -2459,12 +2437,11 @@ static inline void replace_numbered_mark(WriteMergerState *const wms, const size
/// Find buffers ignored due to their location.
///
/// @param[out] removable_bufs Cache of buffers ignored due to their location.
-static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
+static inline void find_removable_bufs(Set(ptr_t) *removable_bufs)
{
FOR_ALL_BUFFERS(buf) {
if (buf->b_ffname != NULL && shada_removable(buf->b_ffname)) {
- int kh_ret;
- (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret);
+ set_put(ptr_t, removable_bufs, (ptr_t)buf);
}
}
}
@@ -2516,7 +2493,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
max_reg_lines = get_shada_parameter('"');
}
const bool dump_registers = (max_reg_lines != 0);
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
const size_t max_kbyte = (size_t)max_kbyte_i;
const size_t num_marked_files = (size_t)get_shada_parameter('\'');
const bool dump_global_marks = get_shada_parameter('f') != 0;
@@ -2524,9 +2501,9 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
// Initialize history merger
for (HistoryType i = 0; i < HIST_COUNT; i++) {
- long num_saved = get_shada_parameter(hist_type2char(i));
+ int num_saved = get_shada_parameter(hist_type2char(i));
if (num_saved == -1) {
- num_saved = p_hi;
+ num_saved = (int)p_hi;
}
if (num_saved > 0) {
dump_history = true;
@@ -2571,15 +2548,15 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
.capacity = 5,
.items = ((KeyValuePair[]) {
{ STATIC_CSTR_AS_STRING("generator"),
- STRING_OBJ(STATIC_CSTR_AS_STRING("nvim")) },
+ STATIC_CSTR_AS_OBJ("nvim") },
{ STATIC_CSTR_AS_STRING("version"),
- STRING_OBJ(cstr_as_string(longVersion)) },
+ CSTR_AS_OBJ(longVersion) },
{ STATIC_CSTR_AS_STRING("max_kbyte"),
INTEGER_OBJ((Integer)max_kbyte) },
{ STATIC_CSTR_AS_STRING("pid"),
INTEGER_OBJ((Integer)os_get_pid()) },
{ STATIC_CSTR_AS_STRING("encoding"),
- STRING_OBJ(cstr_as_string((char *)p_enc)) },
+ CSTR_AS_OBJ(p_enc) },
}),
}
}
@@ -2660,8 +2637,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
tv_clear(&vartv);
tv_clear(&tgttv);
if (spe_ret == kSDWriteSuccessful) {
- int kh_ret;
- (void)kh_put(strset, &wms->dumped_variables, name, &kh_ret);
+ set_put(cstr_t, &wms->dumped_variables, name);
}
} while (var_iter != NULL);
}
@@ -2714,17 +2690,17 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
const char *fname;
if (fm.fmark.fnum == 0) {
assert(fm.fname != NULL);
- if (shada_removable((const char *)fm.fname)) {
+ if (shada_removable(fm.fname)) {
continue;
}
- fname = (const char *)fm.fname;
+ fname = fm.fname;
} else {
const buf_T *const buf = buflist_findnr(fm.fmark.fnum);
if (buf == NULL || buf->b_ffname == NULL
- || in_bufset(&removable_bufs, buf)) {
+ || set_has(ptr_t, &removable_bufs, (ptr_t)buf)) {
continue;
}
- fname = (const char *)buf->b_ffname;
+ fname = buf->b_ffname;
}
const PossiblyFreedShadaEntry pf_entry = {
.can_free_entry = false,
@@ -2757,18 +2733,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
// Initialize buffers
if (num_marked_files > 0) {
FOR_ALL_BUFFERS(buf) {
- if (buf->b_ffname == NULL || in_bufset(&removable_bufs, buf)) {
+ if (buf->b_ffname == NULL || set_has(ptr_t, &removable_bufs, buf)) {
continue;
}
const void *local_marks_iter = NULL;
- const char *const fname = (const char *)buf->b_ffname;
- khiter_t k;
- int kh_ret;
- k = kh_put(file_marks, &wms->file_marks, fname, &kh_ret);
- FileMarks *const filemarks = &kh_val(&wms->file_marks, k);
- if (kh_ret > 0) {
- CLEAR_POINTER(filemarks);
+ const char *const fname = buf->b_ffname;
+ ptr_t *val = pmap_put_ref(cstr_t)(&wms->file_marks, fname, NULL, NULL);
+ if (*val == NULL) {
+ *val = xcalloc(1, sizeof(FileMarks));
}
+ FileMarks *const filemarks = *val;
do {
fmark_T fm;
char name = NUL;
@@ -2885,16 +2859,14 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, ShaDaReadDef
PACK_WMS_ENTRY(wms->replacement);
#undef PACK_WMS_ENTRY
- const size_t file_markss_size = kh_size(&wms->file_marks);
+ const size_t file_markss_size = map_size(&wms->file_marks);
FileMarks **const all_file_markss =
xmalloc(file_markss_size * sizeof(*all_file_markss));
FileMarks **cur_file_marks = all_file_markss;
- for (khint_t i = kh_begin(&wms->file_marks); i != kh_end(&wms->file_marks);
- i++) {
- if (kh_exist(&wms->file_marks, i)) {
- *cur_file_marks++ = &kh_val(&wms->file_marks, i);
- }
- }
+ ptr_t val;
+ map_foreach_value(&wms->file_marks, val, {
+ *cur_file_marks++ = val;
+ })
qsort((void *)all_file_markss, file_markss_size, sizeof(*all_file_markss),
&compare_file_marks);
const size_t file_markss_to_dump = MIN(num_marked_files, file_markss_size);
@@ -2947,10 +2919,13 @@ shada_write_exit:
hms_dealloc(&wms->hms[i]);
}
}
- kh_dealloc(file_marks, &wms->file_marks);
- kh_dealloc(bufset, &removable_bufs);
+ map_foreach_value(&wms->file_marks, val, {
+ xfree(val);
+ })
+ map_destroy(cstr_t, &wms->file_marks);
+ set_destroy(ptr_t, &removable_bufs);
msgpack_packer_free(packer);
- kh_dealloc(strset, &wms->dumped_variables);
+ set_destroy(cstr_t, &wms->dumped_variables);
xfree(wms);
return ret;
}
@@ -3040,7 +3015,7 @@ shada_write_file_nomerge: {}
if (!os_isdir(fname)) {
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(fname, 0700, &failed_dir, NULL)) != 0) {
semsg(_(SERR "Failed to create directory %s "
"for writing ShaDa file: %s"),
failed_dir, os_strerror(ret));
@@ -3071,7 +3046,7 @@ shada_write_file_nomerge: {}
if (p_verbose > 1) {
verbose_enter();
- smsg(_("Writing ShaDa file \"%s\""), fname);
+ smsg(0, _("Writing ShaDa file \"%s\""), fname);
verbose_leave();
}
@@ -3166,8 +3141,8 @@ int shada_read_everything(const char *const fname, const bool forceit, const boo
{
return shada_read_file(fname,
kShaDaWantInfo|kShaDaWantMarks|kShaDaGetOldfiles
- |(forceit?kShaDaForceit:0)
- |(missing_ok?0:kShaDaMissingError));
+ |(forceit ? kShaDaForceit : 0)
+ |(missing_ok ? 0 : kShaDaMissingError));
}
static void shada_free_shada_entry(ShadaEntry *const entry)
@@ -3361,7 +3336,6 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
#define ID(s) s
#define BINDUP(b) xmemdupz((b).ptr, (b).size)
#define TOINT(s) ((int)(s))
-#define TOLONG(s) ((long)(s))
#define TOCHAR(s) ((char)(s))
#define TOU8(s) ((uint8_t)(s))
#define TOSIZE(s) ((size_t)(s))
@@ -3440,7 +3414,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
if (msgpack_to_vim(obj, &adtv) == FAIL \
|| adtv.v_type != VAR_DICT) { \
semsg(_(READERR(name, \
- "cannot be converted to a VimL dictionary")), \
+ "cannot be converted to a Vimscript dictionary")), \
initial_fpos); \
ga_clear(&ad_ga); \
tv_clear(&adtv); \
@@ -3464,7 +3438,7 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
}; \
typval_T aetv; \
if (msgpack_to_vim(obj, &aetv) == FAIL) { \
- semsg(_(READERR(name, "cannot be converted to a VimL list")), \
+ semsg(_(READERR(name, "cannot be converted to a Vimscript list")), \
initial_fpos); \
tv_clear(&aetv); \
goto shada_read_next_item_error; \
@@ -3602,7 +3576,6 @@ shada_read_next_item_start:
ret = spm_ret;
goto shada_read_next_item_error;
}
- ret = kSDReadStatusMalformed;
entry->data = sd_default_values[type_u64].data;
switch ((ShadaEntryType)type_u64) {
case kSDItemHeader:
@@ -3864,7 +3837,7 @@ shada_read_next_item_start:
} else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
&(entry->data.global_var.value)) == FAIL) {
semsg(_(READERR("variable", "has value that cannot "
- "be converted to the VimL value")), initial_fpos);
+ "be converted to the Vimscript value")), initial_fpos);
goto shada_read_next_item_error;
}
break;
@@ -3921,7 +3894,7 @@ shada_read_next_item_start:
// XXX: Temporarily reassign `i` because the macros depend on it.
const size_t j = i;
{
- for (i = 0; i < unpacked_2.data.via.map.size; i++) { // -V535
+ for (i = 0; i < unpacked_2.data.via.map.size; i++) {
CHECK_KEY_IS_STR(unpacked_2, "buffer list entry")
INTEGER_KEY(unpacked_2, "buffer list entry", KEY_LNUM,
entry->data.buffer_list.buffers[j].pos.lnum)
@@ -3988,7 +3961,6 @@ shada_read_next_item_error:
#undef BINDUP
#undef TOCHAR
#undef TOINT
-#undef TOLONG
#undef TYPED_KEY
#undef INT_KEY
#undef INTEGER_KEY
@@ -4006,12 +3978,11 @@ shada_read_next_item_error:
static bool shada_removable(const char *name)
FUNC_ATTR_WARN_UNUSED_RESULT
{
- char *p;
char part[MAXPATHL + 1];
bool retval = false;
- char *new_name = home_replace_save(NULL, (char *)name);
- for (p = p_shada; *p;) {
+ char *new_name = home_replace_save(NULL, name);
+ for (char *p = p_shada; *p;) {
(void)copy_option_part(&p, part, ARRAY_SIZE(part), ", ");
if (part[0] == 'r') {
home_replace(NULL, part + 1, NameBuff, MAXPATHL, true);
@@ -4034,7 +4005,7 @@ static bool shada_removable(const char *name)
///
/// @return number of jumplist entries
static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
- khash_t(bufset) *const removable_bufs)
+ Set(ptr_t) *const removable_bufs)
{
// Initialize jump list
size_t jumps_size = 0;
@@ -4055,7 +4026,7 @@ static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
? NULL
: buflist_findnr(fm.fmark.fnum));
if (buf != NULL
- ? in_bufset(removable_bufs, buf)
+ ? set_has(ptr_t, removable_bufs, (ptr_t)buf)
: fm.fmark.fnum != 0) {
continue;
}
@@ -4110,7 +4081,7 @@ void shada_encode_regs(msgpack_sbuffer *const sbuf)
void shada_encode_jumps(msgpack_sbuffer *const sbuf)
FUNC_ATTR_NONNULL_ALL
{
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
@@ -4129,7 +4100,7 @@ void shada_encode_jumps(msgpack_sbuffer *const sbuf)
void shada_encode_buflist(msgpack_sbuffer *const sbuf)
FUNC_ATTR_NONNULL_ALL
{
- khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset);
+ Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
msgpack_packer packer;
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index 2a945a06bc..d7cac24afc 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,7 +1,6 @@
-#ifndef NVIM_SHADA_H
-#define NVIM_SHADA_H
+#pragma once
-#include <msgpack.h>
+#include <msgpack.h> // IWYU pragma: keep
/// Flags for shada_read_file and children
typedef enum {
@@ -15,4 +14,3 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.h.generated.h"
#endif
-#endif // NVIM_SHADA_H
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 00e282b76e..d05c708d2c 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -1,69 +1,53 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-//
// sign.c: functions for managing with signs
-//
+#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "klib/kvec.h"
+#include "nvim/api/extmark.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/map_defs.h"
+#include "nvim/marktree.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/option.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/sign.h"
#include "nvim/sign_defs.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
-/// Struct to hold the sign properties.
-typedef struct sign sign_T;
-
-struct sign {
- sign_T *sn_next; // next sign in list
- int sn_typenr; // type number of sign
- char *sn_name; // name of sign
- char *sn_icon; // name of pixmap
- char *sn_text; // text used instead of pixmap
- int sn_line_hl; // highlight ID for line
- int sn_text_hl; // highlight ID for text
- int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
- int sn_num_hl; // highlight ID for line number
-};
-
-static sign_T *first_sign = NULL;
-static int next_sign_typenr = 1;
-
-static void sign_list_defined(sign_T *sp);
-static void sign_undefine(sign_T *sp, sign_T *sp_prev);
+static PMap(cstr_t) sign_map INIT( = MAP_INIT);
+static kvec_t(Integer) sign_ns INIT( = MAP_INIT);
static char *cmds[] = {
"define",
@@ -82,736 +66,249 @@ static char *cmds[] = {
#define SIGNCMD_LAST 6
};
-static hashtab_T sg_table; // sign group (signgroup_T) hashtable
-static int next_sign_id = 1; // next sign id in the global group
-
-/// Initialize data needed for managing signs
-void init_signs(void)
-{
- hash_init(&sg_table); // sign group hash table
-}
-
-/// A new sign in group 'groupname' is added. If the group is not present,
-/// create it. Otherwise reference the group.
-static signgroup_T *sign_group_ref(const char *groupname)
+// Convert the supplied "group" to a namespace filter
+static int64_t group_get_ns(const char *group)
{
- hash_T hash;
- hashitem_T *hi;
- signgroup_T *group;
-
- hash = hash_hash(groupname);
- hi = hash_lookup(&sg_table, (char *)groupname, strlen(groupname), hash);
- if (HASHITEM_EMPTY(hi)) {
- // new group
- group = xmalloc(offsetof(signgroup_T, sg_name) + strlen(groupname) + 1);
-
- STRCPY(group->sg_name, groupname);
- group->sg_refcount = 1;
- group->sg_next_sign_id = 1;
- hash_add_item(&sg_table, hi, group->sg_name, hash);
- } else {
- // existing group
- group = HI2SG(hi);
- group->sg_refcount++;
+ if (group == NULL) {
+ return 0; // Global namespace
+ } else if (strcmp(group, "*") == 0) {
+ return UINT32_MAX; // All namespaces
}
-
- return group;
+ // Specific or non-existing namespace
+ int ns = map_get(String, int)(&namespace_ids, cstr_as_string((char *)group));
+ return ns ? ns : -1;
}
-/// A sign in group 'groupname' is removed. If all the signs in this group are
-/// removed, then remove the group.
-static void sign_group_unref(char *groupname)
+static const char *sign_get_name(DecorSignHighlight *sh)
{
- hashitem_T *hi = hash_find(&sg_table, groupname);
- if (HASHITEM_EMPTY(hi)) {
- return;
- }
-
- signgroup_T *group = HI2SG(hi);
- group->sg_refcount--;
- if (group->sg_refcount == 0) {
- // All the signs in this group are removed
- hash_remove(&sg_table, hi);
- xfree(group);
- }
-}
-
-/// @return true if 'sign' is in 'group'.
-/// A sign can either be in the global group (sign->group == NULL)
-/// or in a named group. If 'group' is '*', then the sign is part of the group.
-static bool sign_in_group(sign_entry_T *sign, const char *group)
-{
- return ((group != NULL && strcmp(group, "*") == 0)
- || (group == NULL && sign->se_group == NULL)
- || (group != NULL && sign->se_group != NULL
- && strcmp(group, sign->se_group->sg_name) == 0));
-}
-
-/// Get the next free sign identifier in the specified group
-static int sign_group_get_next_signid(buf_T *buf, const char *groupname)
-{
- int id = 1;
- signgroup_T *group = NULL;
- sign_entry_T *sign;
- hashitem_T *hi;
- int found = false;
-
- if (groupname != NULL) {
- hi = hash_find(&sg_table, (char *)groupname);
- if (HASHITEM_EMPTY(hi)) {
- return id;
- }
- group = HI2SG(hi);
- }
-
- // Search for the next usable sign identifier
- while (!found) {
- if (group == NULL) {
- id = next_sign_id++; // global group
- } else {
- id = group->sg_next_sign_id++;
- }
-
- // Check whether this sign is already placed in the buffer
- found = true;
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (id == sign->se_id && sign_in_group(sign, (char *)groupname)) {
- found = false; // sign identifier is in use
- break;
- }
- }
- }
-
- return id;
+ char *name = sh->sign_name;
+ return !name ? "" : map_has(cstr_t, &sign_map, name) ? name : "[Deleted]";
}
-/// Insert a new sign into the signlist for buffer 'buf' between the 'prev' and
-/// 'next' signs.
+/// Create or update a sign extmark.
///
/// @param buf buffer to store sign in
-/// @param prev previous sign entry
-/// @param next next sign entry
/// @param id sign ID
-/// @param group sign group; NULL for global group
+/// @param group sign group
/// @param prio sign priority
/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int id,
- const char *group, int prio, linenr_T lnum, int typenr,
- bool has_text_or_icon)
+/// @param sp sign properties
+static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr_T lnum, sign_T *sp)
{
- sign_entry_T *newsign = xmalloc(sizeof(sign_entry_T));
- newsign->se_id = id;
- newsign->se_lnum = lnum;
- newsign->se_typenr = typenr;
- newsign->se_has_text_or_icon = has_text_or_icon;
- if (group != NULL) {
- newsign->se_group = sign_group_ref(group);
- } else {
- newsign->se_group = NULL;
- }
- newsign->se_priority = prio;
- newsign->se_next = next;
- newsign->se_prev = prev;
- if (next != NULL) {
- next->se_prev = newsign;
+ if (group && !map_get(String, int)(&namespace_ids, cstr_as_string(group))) {
+ kv_push(sign_ns, nvim_create_namespace(cstr_as_string(group)));
}
- buf_signcols_add_check(buf, newsign);
+ uint32_t ns = group ? (uint32_t)nvim_create_namespace(cstr_as_string(group)) : 0;
+ DecorSignHighlight sign = DECOR_SIGN_HIGHLIGHT_INIT;
- if (prev == NULL) {
- // When adding first sign need to redraw the windows to create the
- // column for signs.
- if (buf->b_signlist == NULL) {
- redraw_buf_later(buf, UPD_NOT_VALID);
- changed_line_abv_curs();
- }
+ sign.flags |= kSHIsSign;
+ sign.text.ptr = sp->sn_text ? xstrdup(sp->sn_text) : NULL;
+ sign.sign_name = xstrdup(sp->sn_name);
+ sign.hl_id = sp->sn_text_hl;
+ sign.line_hl_id = sp->sn_line_hl;
+ sign.number_hl_id = sp->sn_num_hl;
+ sign.cursorline_hl_id = sp->sn_cul_hl;
+ sign.priority = (DecorPriority)prio;
- // first sign in signlist
- buf->b_signlist = newsign;
- } else {
- prev->se_next = newsign;
- }
+ bool has_hl = (sp->sn_line_hl || sp->sn_num_hl || sp->sn_cul_hl);
+ uint16_t decor_flags = (sp->sn_text ? MT_FLAG_DECOR_SIGNTEXT : 0)
+ | (has_hl ? MT_FLAG_DECOR_SIGNHL : 0);
+
+ DecorInline decor = { .ext = true, .data.ext = { .vt = NULL, .sh_idx = decor_put_sh(sign) } };
+ extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, decor, decor_flags, true,
+ false, true, true, NULL);
}
-/// Insert a new sign sorted by line number and sign priority.
+/// For an existing, placed sign with "id", modify the sign, group or priority.
+/// Returns the line number of the sign, or zero if the sign is not found.
///
/// @param buf buffer to store sign in
-/// @param prev previous sign entry
/// @param id sign ID
-/// @param group sign group; NULL for global group
+/// @param group sign group
/// @param prio sign priority
-/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, const char *group,
- int prio, linenr_T lnum, int typenr, bool has_text_or_icon)
-{
- sign_entry_T *sign;
-
- // keep signs sorted by lnum, priority and id: insert new sign at
- // the proper position in the list for this lnum.
- while (prev != NULL && prev->se_lnum == lnum
- && (prev->se_priority < prio
- || (prev->se_priority == prio && prev->se_id <= id))) {
- prev = prev->se_prev;
- }
- if (prev == NULL) {
- sign = buf->b_signlist;
- } else {
- sign = prev->se_next;
- }
-
- insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon);
-}
-
-/// Lookup a sign by typenr. Returns NULL if sign is not found.
-static sign_T *find_sign_by_typenr(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp;
- }
- }
- return NULL;
-}
-
-/// Get the name of a sign by its typenr.
-static char *sign_typenr2name(int typenr)
-{
- sign_T *sp;
-
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (sp->sn_typenr == typenr) {
- return sp->sn_name;
- }
- }
- return _("[Deleted]");
-}
-
-/// Return information about a sign in a Dict
-static dict_T *sign_get_info(sign_entry_T *sign)
-{
- dict_T *d = tv_dict_alloc();
- tv_dict_add_nr(d, S_LEN("id"), sign->se_id);
- tv_dict_add_str(d, S_LEN("group"), ((sign->se_group == NULL)
- ? (char *)""
- : (char *)sign->se_group->sg_name));
- tv_dict_add_nr(d, S_LEN("lnum"), sign->se_lnum);
- tv_dict_add_str(d, S_LEN("name"), sign_typenr2name(sign->se_typenr));
- tv_dict_add_nr(d, S_LEN("priority"), sign->se_priority);
-
- return d;
-}
-
-// Sort the signs placed on the same line as "sign" by priority. Invoked after
-// changing the priority of an already placed sign. Assumes the signs in the
-// buffer are sorted by line number and priority.
-static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign)
- FUNC_ATTR_NONNULL_ALL
+/// @param sp sign pointer
+static linenr_T buf_mod_sign(buf_T *buf, uint32_t *id, char *group, int prio, sign_T *sp)
{
- // If there is only one sign in the buffer or only one sign on the line or
- // the sign is already sorted by priority, then return.
- if ((sign->se_prev == NULL
- || sign->se_prev->se_lnum != sign->se_lnum
- || sign->se_prev->se_priority > sign->se_priority)
- && (sign->se_next == NULL
- || sign->se_next->se_lnum != sign->se_lnum
- || sign->se_next->se_priority < sign->se_priority)) {
- return;
- }
-
- // One or more signs on the same line as 'sign'
- // Find a sign after which 'sign' should be inserted
-
- // First search backward for a sign with higher priority on the same line
- sign_entry_T *p = sign;
- while (p->se_prev != NULL
- && p->se_prev->se_lnum == sign->se_lnum
- && p->se_prev->se_priority <= sign->se_priority) {
- p = p->se_prev;
- }
- if (p == sign) {
- // Sign not found. Search forward for a sign with priority just before
- // 'sign'.
- p = sign->se_next;
- while (p->se_next != NULL
- && p->se_next->se_lnum == sign->se_lnum
- && p->se_next->se_priority > sign->se_priority) {
- p = p->se_next;
- }
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || (group && ns == 0)) {
+ return 0;
}
- // Remove 'sign' from the list
- if (buf->b_signlist == sign) {
- buf->b_signlist = sign->se_next;
- }
- if (sign->se_prev != NULL) {
- sign->se_prev->se_next = sign->se_next;
- }
- if (sign->se_next != NULL) {
- sign->se_next->se_prev = sign->se_prev;
- }
- sign->se_prev = NULL;
- sign->se_next = NULL;
-
- // Re-insert 'sign' at the right place
- if (p->se_priority <= sign->se_priority) {
- // 'sign' has a higher priority and should be inserted before 'p'
- sign->se_prev = p->se_prev;
- sign->se_next = p;
- p->se_prev = sign;
- if (sign->se_prev != NULL) {
- sign->se_prev->se_next = sign;
- }
- if (buf->b_signlist == p) {
- buf->b_signlist = sign;
- }
- } else {
- // 'sign' has a lower priority and should be inserted after 'p'
- sign->se_prev = p;
- sign->se_next = p->se_next;
- p->se_next = sign;
- if (sign->se_next != NULL) {
- sign->se_next->se_prev = sign;
- }
+ MTKey mark = marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, *id, false, NULL);
+ if (mark.pos.row >= 0) {
+ buf_set_sign(buf, id, group, prio, mark.pos.row + 1, sp);
}
+ return mark.pos.row + 1;
}
-/// Add the sign into the signlist. Find the right spot to do it though.
+/// Find the line number of the sign with the requested id in group 'group'. If
+/// the sign does not exist, return 0 as the line number. This will still let
+/// the correct file get loaded.
///
/// @param buf buffer to store sign in
/// @param id sign ID
-/// @param groupname sign group
-/// @param prio sign priority
-/// @param lnum line number which gets the mark
-/// @param typenr typenr of sign we are adding
-/// @param has_text_or_icon sign has text or icon
-static void buf_addsign(buf_T *buf, int id, const char *groupname, int prio, linenr_T lnum,
- int typenr, bool has_text_or_icon)
-{
- sign_entry_T *sign; // a sign in the signlist
- sign_entry_T *prev; // the previous sign
-
- prev = NULL;
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (lnum == sign->se_lnum && id == sign->se_id
- && sign_in_group(sign, groupname)) {
- // Update an existing sign
- sign->se_typenr = typenr;
- sign->se_priority = prio;
- sign_sort_by_prio_on_line(buf, sign);
- return;
- } else if (lnum < sign->se_lnum) {
- insert_sign_by_lnum_prio(buf,
- prev,
- id,
- groupname,
- prio,
- lnum,
- typenr,
- has_text_or_icon);
- return;
- }
- prev = sign;
- }
-
- insert_sign_by_lnum_prio(buf,
- prev,
- id,
- groupname,
- prio,
- lnum,
- typenr,
- has_text_or_icon);
-}
-
-/// For an existing, placed sign "markId" change the type to "typenr".
-/// Returns the line number of the sign, or zero if the sign is not found.
-///
-/// @param buf buffer to store sign in
-/// @param markId sign ID
/// @param group sign group
-/// @param typenr typenr of sign we are adding
-/// @param prio sign priority
-static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char *group, int typenr,
- int prio)
+static int buf_findsign(buf_T *buf, int id, char *group)
{
- sign_entry_T *sign; // a sign in the signlist
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_id == markId && sign_in_group(sign, (char *)group)) {
- sign->se_typenr = typenr;
- sign->se_priority = prio;
- sign_sort_by_prio_on_line(buf, sign);
- return sign->se_lnum;
- }
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || (group && ns == 0)) {
+ return 0;
}
-
- return (linenr_T)0;
+ return marktree_lookup_ns(buf->b_marktree, (uint32_t)ns, (uint32_t)id, false, NULL).pos.row + 1;
}
-/// Return the sign attrs which has the attribute specified by 'type'. Returns
-/// NULL if a sign is not found with the specified attribute.
-/// @param type Type of sign to look for
-/// @param sattrs Sign attrs to search through
-/// @param idx if there multiple signs, this index will pick the n-th
-/// out of the most `max_signs` sorted ascending by Id.
-/// @param max_signs the number of signs, with priority for the ones
-/// with the highest Ids.
-/// @return Attrs of the matching sign, or NULL
-SignTextAttrs *sign_get_attr(int idx, SignTextAttrs sattrs[], int max_signs)
+/// qsort() function to sort signs by line number, priority, id and recency.
+int sign_cmp(const void *p1, const void *p2)
{
- SignTextAttrs *matches[SIGN_SHOW_MAX];
- int sattr_matches = 0;
-
- for (int i = 0; i < SIGN_SHOW_MAX; i++) {
- if (sattrs[i].text != NULL) {
- matches[sattr_matches++] = &sattrs[i];
- // attr list is sorted with most important (priority, id), thus we
- // may stop as soon as we have max_signs matches
- if (sattr_matches >= max_signs) {
- break;
- }
- }
- }
+ const MTKey *s1 = (MTKey *)p1;
+ const MTKey *s2 = (MTKey *)p2;
+ int n = s1->pos.row - s2->pos.row;
- if (sattr_matches > idx) {
- return matches[sattr_matches - idx - 1];
+ if (n) {
+ return n;
}
- return NULL;
-}
-
-/// Return the attributes of all the signs placed on line 'lnum' in buffer
-/// 'buf'. Used when refreshing the screen. Returns the number of signs.
-/// @param buf Buffer in which to search
-/// @param lnum Line in which to search
-/// @param sattrs Output array for attrs
-/// @return Number of signs of which attrs were found
-int buf_get_signattrs(buf_T *buf, linenr_T lnum, SignTextAttrs sattrs[], HlPriAttr *num_attrs,
- HlPriAttr *line_attrs, HlPriAttr *cul_attrs)
-{
- sign_entry_T *sign;
-
- int sattr_matches = 0;
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > lnum) {
- // Signs are sorted by line number in the buffer. No need to check
- // for signs after the specified line number 'lnum'.
- break;
- }
+ DecorSignHighlight *sh1 = decor_find_sign(mt_decor(*s1));
+ DecorSignHighlight *sh2 = decor_find_sign(mt_decor(*s2));
+ assert(sh1 && sh2);
- if (sign->se_lnum < lnum) {
- continue;
- }
+ n = sh2->priority - sh1->priority;
- sign_T *sp = find_sign_by_typenr(sign->se_typenr);
- if (sp == NULL) {
- continue;
- }
-
- if (sp->sn_text != NULL && sattr_matches < SIGN_SHOW_MAX) {
- sattrs[sattr_matches++] = (SignTextAttrs) {
- .text = sp->sn_text,
- .hl_attr_id = sp->sn_text_hl == 0 ? 0 : syn_id2attr(sp->sn_text_hl),
- .priority = sign->se_priority
- };
- }
-
- struct { HlPriAttr *dest; int hl; } cattrs[] = {
- { line_attrs, sp->sn_line_hl },
- { num_attrs, sp->sn_num_hl },
- { cul_attrs, sp->sn_cul_hl },
- { NULL, -1 },
- };
- for (int i = 0; cattrs[i].dest; i++) {
- if (cattrs[i].hl != 0 && sign->se_priority >= cattrs[i].dest->priority) {
- *cattrs[i].dest = (HlPriAttr) {
- .attr_id = syn_id2attr(cattrs[i].hl),
- .priority = sign->se_priority
- };
- }
- }
- }
- return sattr_matches;
+ return n ? n : (n = (int)(s2->id - s1->id))
+ ? n : (sh2->sign_add_id - sh1->sign_add_id);
}
-/// Delete sign 'id' in group 'group' from buffer 'buf'.
-/// If 'id' is zero, then delete all the signs in group 'group'. Otherwise
-/// delete only the specified sign.
-/// If 'group' is '*', then delete the sign in all the groups. If 'group' is
-/// NULL, then delete the sign in the global group. Otherwise delete the sign in
-/// the specified group.
+/// Delete the specified sign(s)
///
-/// @param buf buffer sign is stored in
-/// @param atlnum sign at this line, 0 - at any line
-/// @param id sign id
+/// @param buf buffer sign is stored in or NULL for all buffers
/// @param group sign group
-///
-/// @return the line number of the deleted sign. If multiple signs are deleted,
-/// then returns the line number of the last sign deleted.
-static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char *group)
+/// @param id sign id
+/// @param atlnum single sign at this line, specified signs at any line when -1
+static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum)
{
- sign_entry_T **lastp; // pointer to pointer to current sign
- sign_entry_T *sign; // a sign in a b_signlist
- sign_entry_T *next; // the next sign in a b_signlist
- linenr_T lnum; // line number whose sign was deleted
-
- lastp = &buf->b_signlist;
- lnum = 0;
- for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
- if ((id == 0 || sign->se_id == id)
- && (atlnum == 0 || sign->se_lnum == atlnum)
- && sign_in_group(sign, group)) {
- *lastp = next;
- if (next != NULL) {
- next->se_prev = sign->se_prev;
- }
- lnum = sign->se_lnum;
- buf_signcols_del_check(buf, lnum, lnum);
- if (sign->se_group != NULL) {
- sign_group_unref((char *)sign->se_group->sg_name);
- }
- xfree(sign);
- redraw_buf_line_later(buf, lnum, false);
- // Check whether only one sign needs to be deleted
- // If deleting a sign with a specific identifier in a particular
- // group or deleting any sign at a particular line number, delete
- // only one sign.
- if (group == NULL
- || (*group != '*' && id != 0)
- || (*group == '*' && atlnum != 0)) {
- break;
- }
- } else {
- lastp = &sign->se_next;
- }
- }
-
- // When deleting the last sign the cursor position may change, because the
- // sign columns no longer shows. And the 'signcolumn' may be hidden.
- if (buf->b_signlist == NULL) {
- redraw_buf_later(buf, UPD_NOT_VALID);
- changed_line_abv_curs();
+ int64_t ns = group_get_ns(group);
+ if (ns < 0) {
+ return FAIL;
}
- return lnum;
-}
-
-/// Find the line number of the sign with the requested id in group 'group'. If
-/// the sign does not exist, return 0 as the line number. This will still let
-/// the correct file get loaded.
-///
-/// @param buf buffer to store sign in
-/// @param id sign ID
-/// @param group sign group
-static int buf_findsign(buf_T *buf, int id, char *group)
-{
- sign_entry_T *sign; // a sign in the signlist
+ MarkTreeIter itr[1];
+ int row = atlnum > 0 ? atlnum - 1 : 0;
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ // Store signs at a specific line number to remove one later.
+ if (atlnum > 0) {
+ if (!marktree_itr_get_overlap(buf->b_marktree, row, 0, itr)) {
+ return FAIL;
+ }
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_id == id && sign_in_group(sign, group)) {
- return (int)sign->se_lnum;
+ MTPair pair;
+ while (marktree_itr_step_overlap(buf->b_marktree, itr, &pair)) {
+ if ((ns == UINT32_MAX || ns == pair.start.ns) && mt_decor_sign(pair.start)) {
+ kv_push(signs, pair.start);
+ }
}
+ } else {
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
}
- return 0;
-}
-
-/// Return the sign at line 'lnum' in buffer 'buf'. Returns NULL if a sign is
-/// not found at the line. If 'groupname' is NULL, searches in the global group.
-///
-/// @param buf buffer whose sign we are searching for
-/// @param lnum line number of sign
-/// @param groupname sign group name
-static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char *groupname)
-{
- sign_entry_T *sign; // a sign in the signlist
-
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (sign->se_lnum > lnum) {
- // Signs are sorted by line number in the buffer. No need to check
- // for signs after the specified line number 'lnum'.
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (row && mark.pos.row > row) {
break;
}
-
- if (sign->se_lnum == lnum && sign_in_group(sign, groupname)) {
- return sign;
+ if (!mt_end(mark) && mt_decor_sign(mark)
+ && (id == 0 || (int)mark.id == id)
+ && (ns == UINT32_MAX || ns == mark.ns)) {
+ if (atlnum > 0) {
+ kv_push(signs, mark);
+ marktree_itr_next(buf->b_marktree, itr);
+ } else {
+ extmark_del(buf, itr, mark, true);
+ }
+ } else {
+ marktree_itr_next(buf->b_marktree, itr);
}
}
- return NULL;
-}
-
-/// Return the identifier of the sign at line number 'lnum' in buffer 'buf'.
-///
-/// @param buf buffer whose sign we are searching for
-/// @param lnum line number of sign
-/// @param groupname sign group name
-static int buf_findsign_id(buf_T *buf, linenr_T lnum, char *groupname)
-{
- sign_entry_T *sign; // a sign in the signlist
-
- sign = buf_getsign_at_line(buf, lnum, groupname);
- if (sign != NULL) {
- return sign->se_id;
+ // Sort to remove the highest priority sign at a specific line number.
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ extmark_del_id(buf, kv_A(signs, 0).ns, kv_A(signs, 0).id);
+ kv_destroy(signs);
+ } else if (atlnum > 0) {
+ return FAIL;
}
- return 0;
-}
-
-/// Delete signs in buffer "buf".
-void buf_delete_signs(buf_T *buf, char *group)
-{
- sign_entry_T *sign;
- sign_entry_T **lastp; // pointer to pointer to current sign
- sign_entry_T *next;
-
// When deleting the last sign need to redraw the windows to remove the
// sign column. Not when curwin is NULL (this means we're exiting).
- if (buf->b_signlist != NULL && curwin != NULL) {
+ if (!buf->b_signs_with_text && curwin != NULL) {
changed_line_abv_curs();
}
- lastp = &buf->b_signlist;
- for (sign = buf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
- if (sign_in_group(sign, group)) {
- *lastp = next;
- if (next != NULL) {
- next->se_prev = sign->se_prev;
- }
- if (sign->se_group != NULL) {
- sign_group_unref((char *)sign->se_group->sg_name);
- }
- xfree(sign);
- } else {
- lastp = &sign->se_next;
- }
- }
- buf_signcols_del_check(buf, 1, MAXLNUM);
+ return OK;
}
/// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers.
-static void sign_list_placed(buf_T *rbuf, char *sign_group)
+static void sign_list_placed(buf_T *rbuf, char *group)
{
- buf_T *buf;
- sign_entry_T *sign;
char lbuf[MSG_BUF_LEN];
- char group[MSG_BUF_LEN];
+ char namebuf[MSG_BUF_LEN];
+ char groupbuf[MSG_BUF_LEN];
+ buf_T *buf = rbuf ? rbuf : firstbuf;
+ int64_t ns = group_get_ns(group);
msg_puts_title(_("\n--- Signs ---"));
msg_putchar('\n');
- if (rbuf == NULL) {
- buf = firstbuf;
- } else {
- buf = rbuf;
- }
+
while (buf != NULL && !got_int) {
- if (buf->b_signlist != NULL) {
+ if (buf->b_signs) {
vim_snprintf(lbuf, MSG_BUF_LEN, _("Signs for %s:"), buf->b_fname);
msg_puts_attr(lbuf, HL_ATTR(HLF_D));
msg_putchar('\n');
}
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (got_int) {
- break;
- }
- if (!sign_in_group(sign, sign_group)) {
- continue;
- }
- if (sign->se_group != NULL) {
- vim_snprintf(group, MSG_BUF_LEN, _(" group=%s"),
- sign->se_group->sg_name);
- } else {
- group[0] = '\0';
- }
- vim_snprintf(lbuf, MSG_BUF_LEN,
- _(" line=%ld id=%d%s name=%s priority=%d"),
- (long)sign->se_lnum, sign->se_id, group,
- sign_typenr2name(sign->se_typenr), sign->se_priority);
- msg_puts(lbuf);
- msg_putchar('\n');
- }
- if (rbuf != NULL) {
- break;
- }
- buf = buf->b_next;
- }
-}
-
-/// Adjust or delete a placed sign for inserted/deleted lines.
-///
-/// @return the new line number of the sign, or 0 if the sign is in deleted lines.
-static linenr_T sign_adjust_one(const linenr_T se_lnum, linenr_T line1, linenr_T line2,
- linenr_T amount, linenr_T amount_after)
-{
- if (se_lnum < line1) {
- // Ignore changes to lines after the sign
- return se_lnum;
- }
- if (se_lnum > line2) {
- // Lines inserted or deleted before the sign
- return se_lnum + amount_after;
- }
- if (amount == MAXLNUM) { // sign in deleted lines
- return 0;
- }
- return se_lnum + amount;
-}
-/// Adjust placed signs for inserted/deleted lines.
-void sign_mark_adjust(linenr_T line1, linenr_T line2, linenr_T amount, linenr_T amount_after)
-{
- sign_entry_T *sign; // a sign in a b_signlist
- sign_entry_T *next; // the next sign in a b_signlist
- sign_entry_T *last = NULL; // pointer to pointer to current sign
- sign_entry_T **lastp = NULL; // pointer to pointer to current sign
- linenr_T new_lnum; // new line number to assign to sign
- int is_fixed = 0;
- int signcol = win_signcol_configured(curwin, &is_fixed);
-
- if (amount == MAXLNUM) { // deleting
- buf_signcols_del_check(curbuf, line1, line2);
- }
-
- lastp = &curbuf->b_signlist;
+ if (ns >= 0) {
+ MarkTreeIter itr[1];
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
- for (sign = curbuf->b_signlist; sign != NULL; sign = next) {
- next = sign->se_next;
-
- new_lnum = sign_adjust_one(sign->se_lnum, line1, line2, amount, amount_after);
- if (new_lnum == 0) { // sign in deleted lines
- if (!is_fixed || signcol >= 2) {
- *lastp = next;
- if (next) {
- next->se_prev = last;
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (!mt_end(mark) && mt_decor_sign(mark)
+ && (ns == UINT32_MAX || ns == mark.ns)) {
+ kv_push(signs, mark);
}
- xfree(sign);
- continue;
+ marktree_itr_next(buf->b_marktree, itr);
}
- } else {
- // If the new sign line number is past the last line in the buffer,
- // then don't adjust the line number. Otherwise, it will always be past
- // the last line and will not be visible.
- if (new_lnum <= curbuf->b_ml.ml_line_count) {
- sign->se_lnum = new_lnum;
+
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ namebuf[0] = '\0';
+ groupbuf[0] = '\0';
+ MTKey mark = kv_A(signs, i);
+
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
+ if (sh->sign_name != NULL) {
+ vim_snprintf(namebuf, MSG_BUF_LEN, _(" name=%s"), sign_get_name(sh));
+ }
+ if (mark.ns != 0) {
+ vim_snprintf(groupbuf, MSG_BUF_LEN, _(" group=%s"), describe_ns((int)mark.ns, ""));
+ }
+ vim_snprintf(lbuf, MSG_BUF_LEN, _(" line=%" PRIdLINENR " id=%u%s%s priority=%d"),
+ mark.pos.row + 1, mark.id, groupbuf, namebuf, sh->priority);
+ msg_puts(lbuf);
+ msg_putchar('\n');
+ }
+ kv_destroy(signs);
}
}
- last = sign;
- lastp = &sign->se_next;
- }
-
- new_lnum = sign_adjust_one(curbuf->b_signcols.sentinel, line1, line2, amount, amount_after);
- if (new_lnum != 0) {
- curbuf->b_signcols.sentinel = new_lnum;
+ if (rbuf != NULL) {
+ return;
+ }
+ buf = buf->b_next;
}
}
@@ -835,93 +332,22 @@ static int sign_cmd_idx(char *begin_cmd, char *end_cmd)
return idx;
}
-/// Find a sign by name. Also returns pointer to the previous sign.
-static sign_T *sign_find(const char *name, sign_T **sp_prev)
-{
- sign_T *sp;
-
- if (sp_prev != NULL) {
- *sp_prev = NULL;
- }
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (strcmp(sp->sn_name, name) == 0) {
- break;
- }
- if (sp_prev != NULL) {
- *sp_prev = sp;
- }
- }
-
- return sp;
-}
-
-/// Allocate a new sign
-static sign_T *alloc_new_sign(char *name)
-{
- sign_T *sp;
- sign_T *lp;
- int start = next_sign_typenr;
-
- // Allocate a new sign.
- sp = xcalloc(1, sizeof(sign_T));
-
- // Check that next_sign_typenr is not already being used.
- // This only happens after wrapping around. Hopefully
- // another one got deleted and we can use its number.
- for (lp = first_sign; lp != NULL;) {
- if (lp->sn_typenr == next_sign_typenr) {
- next_sign_typenr++;
- if (next_sign_typenr == MAX_TYPENR) {
- next_sign_typenr = 1;
- }
- if (next_sign_typenr == start) {
- xfree(sp);
- emsg(_("E612: Too many signs defined"));
- return NULL;
- }
- lp = first_sign; // start all over
- continue;
- }
- lp = lp->sn_next;
- }
-
- sp->sn_typenr = next_sign_typenr;
- if (++next_sign_typenr == MAX_TYPENR) {
- next_sign_typenr = 1; // wrap around
- }
-
- sp->sn_name = xstrdup(name);
-
- return sp;
-}
-
-/// Initialize the icon information for a new sign
-static void sign_define_init_icon(sign_T *sp, char *icon)
-{
- xfree(sp->sn_icon);
- sp->sn_icon = xstrdup(icon);
- backslash_halve(sp->sn_icon);
-}
-
-/// Initialize the text for a new sign
-static int sign_define_init_text(sign_T *sp, char *text)
+/// Initialize the "text" for a new sign and store in "sign_text".
+/// "sp" is NULL for signs added through nvim_buf_set_extmark().
+int init_sign_text(sign_T *sp, char **sign_text, char *text)
{
char *s;
- char *endp;
- int cells;
- size_t len;
+ char *endp = text + (int)strlen(text);
- endp = text + (int)strlen(text);
- for (s = text; s + 1 < endp; s++) {
+ for (s = sp ? text : endp; s + 1 < endp; s++) {
if (*s == '\\') {
- // Remove a backslash, so that it is possible
- // to use a space.
+ // Remove a backslash, so that it is possible to use a space.
STRMOVE(s, s + 1);
endp--;
}
}
// Count cells and check for non-printable chars
- cells = 0;
+ int cells = 0;
for (s = text; s < endp; s += utfc_ptr2len(s)) {
if (!vim_isprintc(utf_ptr2char(s))) {
break;
@@ -930,95 +356,69 @@ static int sign_define_init_text(sign_T *sp, char *text)
}
// Currently must be empty, one or two display cells
if (s != endp || cells > 2) {
- semsg(_("E239: Invalid sign text: %s"), text);
+ if (sp != NULL) {
+ semsg(_("E239: Invalid sign text: %s"), text);
+ }
return FAIL;
}
if (cells < 1) {
- sp->sn_text = NULL;
+ if (sp != NULL) {
+ sp->sn_text = NULL;
+ }
return OK;
}
- xfree(sp->sn_text);
- // Allocate one byte more if we need to pad up
- // with a space.
- len = (size_t)(endp - text + ((cells == 1) ? 1 : 0));
- sp->sn_text = xstrnsave(text, len);
+ if (sp != NULL) {
+ xfree(sp->sn_text);
+ }
+ // Allocate one byte more if we need to pad up with a space.
+ size_t len = (size_t)(endp - text + (cells == 1));
+ *sign_text = xstrnsave(text, len);
if (cells == 1) {
- STRCPY(sp->sn_text + len - 1, " ");
+ STRCPY(*sign_text + len - 1, " ");
}
return OK;
}
/// Define a new sign or update an existing sign
-static int sign_define_by_name(char *name, char *icon, char *linehl, char *text, char *texthl,
+static int sign_define_by_name(char *name, char *icon, char *text, char *linehl, char *texthl,
char *culhl, char *numhl)
{
- sign_T *sp_prev;
- sign_T *sp;
+ cstr_t *key;
+ sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, NULL);
- sp = sign_find(name, &sp_prev);
- if (sp == NULL) {
- sp = alloc_new_sign(name);
- if (sp == NULL) {
- return FAIL;
- }
-
- // add the new sign to the list of signs
- if (sp_prev == NULL) {
- first_sign = sp;
- } else {
- sp_prev->sn_next = sp;
- }
+ if (*sp == NULL) {
+ *key = xstrdup(name);
+ *sp = xcalloc(1, sizeof(sign_T));
+ (*sp)->sn_name = (char *)(*key);
} else {
- // Signs may already exist, a redraw is needed in windows with a
- // non-empty sign list.
+ // Signs may already exist, a redraw is needed in windows with a non-empty sign list.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp->w_buffer->b_signlist != NULL) {
+ if (wp->w_buffer->b_signs) {
redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
}
}
}
- // set values for a defined sign.
+ // Set values for a defined sign.
if (icon != NULL) {
- sign_define_init_icon(sp, icon);
+ /// Initialize the icon information for a new sign
+ xfree((*sp)->sn_icon);
+ (*sp)->sn_icon = xstrdup(icon);
+ backslash_halve((*sp)->sn_icon);
}
- if (text != NULL && (sign_define_init_text(sp, text) == FAIL)) {
+ if (text != NULL && (init_sign_text(*sp, &(*sp)->sn_text, text) == FAIL)) {
return FAIL;
}
- if (linehl != NULL) {
- if (*linehl == NUL) {
- sp->sn_line_hl = 0;
- } else {
- sp->sn_line_hl = syn_check_group(linehl, strlen(linehl));
- }
- }
-
- if (texthl != NULL) {
- if (*texthl == NUL) {
- sp->sn_text_hl = 0;
- } else {
- sp->sn_text_hl = syn_check_group(texthl, strlen(texthl));
- }
- }
-
- if (culhl != NULL) {
- if (*culhl == NUL) {
- sp->sn_cul_hl = 0;
- } else {
- sp->sn_cul_hl = syn_check_group(culhl, strlen(culhl));
- }
- }
-
- if (numhl != NULL) {
- if (*numhl == NUL) {
- sp->sn_num_hl = 0;
- } else {
- sp->sn_num_hl = syn_check_group(numhl, strlen(numhl));
+ char *arg[] = { linehl, texthl, culhl, numhl };
+ int *hl[] = { &(*sp)->sn_line_hl, &(*sp)->sn_text_hl, &(*sp)->sn_cul_hl, &(*sp)->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (arg[i] != NULL) {
+ *hl[i] = *arg[i] ? syn_check_group(arg[i], strlen(arg[i])) : 0;
}
}
@@ -1028,25 +428,47 @@ static int sign_define_by_name(char *name, char *icon, char *linehl, char *text,
/// Free the sign specified by 'name'.
static int sign_undefine_by_name(const char *name)
{
- sign_T *sp_prev;
- sign_T *sp;
-
- sp = sign_find(name, &sp_prev);
+ sign_T *sp = pmap_del(cstr_t)(&sign_map, name, NULL);
if (sp == NULL) {
semsg(_("E155: Unknown sign: %s"), name);
return FAIL;
}
- sign_undefine(sp, sp_prev);
+ xfree(sp->sn_name);
+ xfree(sp->sn_text);
+ xfree(sp->sn_icon);
+ xfree(sp);
return OK;
}
+/// List one sign.
+static void sign_list_defined(sign_T *sp)
+{
+ smsg(0, "sign %s", sp->sn_name);
+ if (sp->sn_icon != NULL) {
+ msg_puts(" icon=");
+ msg_outtrans(sp->sn_icon, 0);
+ msg_puts(_(" (not supported)"));
+ }
+ if (sp->sn_text != NULL) {
+ msg_puts(" text=");
+ msg_outtrans(sp->sn_text, 0);
+ }
+ static char *arg[] = { " linehl=", " texthl=", " culhl=", " numhl=" };
+ int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (hl[i] > 0) {
+ msg_puts(arg[i]);
+ const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false);
+ msg_puts(p ? p : "NONE");
+ }
+ }
+}
+
/// List the signs matching 'name'
static void sign_list_by_name(char *name)
{
- sign_T *sp;
-
- sp = sign_find(name, NULL);
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, name);
if (sp != NULL) {
sign_list_defined(sp);
} else {
@@ -1066,116 +488,97 @@ static void may_force_numberwidth_recompute(buf_T *buf, int unplace)
}
/// Place a sign at the specified file location or update a sign.
-static int sign_place(int *sign_id, const char *sign_group, const char *sign_name, buf_T *buf,
- linenr_T lnum, int prio)
+static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_T lnum, int prio)
{
- sign_T *sp;
-
// Check for reserved character '*' in group name
- if (sign_group != NULL && (*sign_group == '*' || *sign_group == '\0')) {
+ if (group != NULL && (*group == '*' || *group == '\0')) {
return FAIL;
}
- for (sp = first_sign; sp != NULL; sp = sp->sn_next) {
- if (strcmp(sp->sn_name, sign_name) == 0) {
- break;
- }
- }
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, name);
if (sp == NULL) {
- semsg(_("E155: Unknown sign: %s"), sign_name);
+ semsg(_("E155: Unknown sign: %s"), name);
return FAIL;
}
- if (*sign_id == 0) {
- *sign_id = sign_group_get_next_signid(buf, (char *)sign_group);
- }
if (lnum > 0) {
- // ":sign place {id} line={lnum} name={name} file={fname}":
- // place a sign
- bool has_text_or_icon = sp->sn_text != NULL || sp->sn_icon != NULL;
- buf_addsign(buf,
- *sign_id,
- (char *)sign_group,
- prio,
- lnum,
- sp->sn_typenr,
- has_text_or_icon);
+ // ":sign place {id} line={lnum} name={name} file={fname}": place a sign
+ buf_set_sign(buf, id, group, prio, lnum, sp);
} else {
// ":sign place {id} file={fname}": change sign type and/or priority
- lnum = buf_change_sign_type(buf, *sign_id, (char *)sign_group, sp->sn_typenr, prio);
+ lnum = buf_mod_sign(buf, id, group, prio, sp);
}
if (lnum > 0) {
- redraw_buf_line_later(buf, lnum, false);
-
// When displaying signs in the 'number' column, if the width of the
// number column is less than 2, then force recomputing the width.
may_force_numberwidth_recompute(buf, false);
} else {
- semsg(_("E885: Not possible to change sign %s"), sign_name);
+ semsg(_("E885: Not possible to change sign %s"), name);
return FAIL;
}
return OK;
}
-/// Unplace the specified sign
-static int sign_unplace(int sign_id, char *sign_group, buf_T *buf, linenr_T atlnum)
+static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum)
{
- if (buf->b_signlist == NULL) { // No signs in the buffer
- return OK;
+ if (!buf->b_signs) { // No signs in the buffer
+ return FAIL;
}
- if (sign_id == 0) {
- // Delete all the signs in the specified buffer
- redraw_buf_later(buf, UPD_NOT_VALID);
- buf_delete_signs(buf, sign_group);
- } else {
- linenr_T lnum;
- // Delete only the specified signs
- lnum = buf_delsign(buf, atlnum, sign_id, sign_group);
- if (lnum == 0) {
+ if (id == 0 || atlnum > 0 || (group != NULL && *group == '*')) {
+ // Delete multiple specified signs
+ if (!buf_delete_signs(buf, group, id, atlnum)) {
+ return FAIL;
+ }
+ } else {
+ // Delete only a single sign
+ int64_t ns = group_get_ns(group);
+ if (ns < 0 || !extmark_del_id(buf, (uint32_t)ns, (uint32_t)id)) {
return FAIL;
}
- redraw_buf_line_later(buf, lnum, false);
}
// When all the signs in a buffer are removed, force recomputing the
// number column width (if enabled) in all the windows displaying the
// buffer if 'signcolumn' is set to 'number' in that window.
- if (buf->b_signlist == NULL) {
+ if (!buf->b_signs_with_text) {
may_force_numberwidth_recompute(buf, true);
}
return OK;
}
-/// Unplace the sign at the current cursor line.
-static void sign_unplace_at_cursor(char *groupname)
+/// Unplace the specified sign for a single or all buffers
+static int sign_unplace(buf_T *buf, int id, char *group, linenr_T atlnum)
{
- int id = -1;
-
- id = buf_findsign_id(curwin->w_buffer, curwin->w_cursor.lnum, groupname);
- if (id > 0) {
- sign_unplace(id, groupname, curwin->w_buffer, curwin->w_cursor.lnum);
+ if (buf != NULL) {
+ return sign_unplace_inner(buf, id, group, atlnum);
} else {
- emsg(_("E159: Missing sign number"));
+ int retval = OK;
+ FOR_ALL_BUFFERS(cbuf) {
+ if (!sign_unplace_inner(cbuf, id, group, atlnum)) {
+ retval = FAIL;
+ }
+ }
+ return retval;
}
}
/// Jump to a sign.
-static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
+static linenr_T sign_jump(int id, char *group, buf_T *buf)
{
- linenr_T lnum;
+ linenr_T lnum = buf_findsign(buf, id, group);
- if ((lnum = buf_findsign(buf, sign_id, sign_group)) <= 0) {
- semsg(_("E157: Invalid sign ID: %" PRId64), (int64_t)sign_id);
+ if (lnum <= 0) {
+ semsg(_("E157: Invalid sign ID: %" PRId32), id);
return -1;
}
// goto a sign ...
if (buf_jump_open_win(buf) != NULL) { // ... in a current window
curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
+ check_cursor_lnum(curwin);
beginline(BL_WHITE);
} else { // ... not currently in a window
if (buf->b_fname == NULL) {
@@ -1184,8 +587,7 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
}
size_t cmdlen = strlen(buf->b_fname) + 24;
char *cmd = xmallocz(cmdlen);
- snprintf(cmd, cmdlen, "e +%" PRId64 " %s",
- (int64_t)lnum, buf->b_fname);
+ snprintf(cmd, cmdlen, "e +%" PRId64 " %s", (int64_t)lnum, buf->b_fname);
do_cmdline_cmd(cmd);
xfree(cmd);
}
@@ -1196,72 +598,49 @@ static linenr_T sign_jump(int sign_id, char *sign_group, buf_T *buf)
}
/// ":sign define {name} ..." command
-static void sign_define_cmd(char *sign_name, char *cmdline)
+static void sign_define_cmd(char *name, char *cmdline)
{
- char *arg;
- char *p = cmdline;
char *icon = NULL;
char *text = NULL;
char *linehl = NULL;
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
- int failed = false;
// set values for a defined sign.
- for (;;) {
- arg = skipwhite(p);
+ while (true) {
+ char *arg = skipwhite(cmdline);
if (*arg == NUL) {
break;
}
- p = skiptowhite_esc(arg);
+ cmdline = skiptowhite_esc(arg);
if (strncmp(arg, "icon=", 5) == 0) {
- arg += 5;
- XFREE_CLEAR(icon);
- icon = xstrnsave(arg, (size_t)(p - arg));
+ icon = arg + 5;
} else if (strncmp(arg, "text=", 5) == 0) {
- arg += 5;
- XFREE_CLEAR(text);
- text = xstrnsave(arg, (size_t)(p - arg));
+ text = arg + 5;
} else if (strncmp(arg, "linehl=", 7) == 0) {
- arg += 7;
- XFREE_CLEAR(linehl);
- linehl = xstrnsave(arg, (size_t)(p - arg));
+ linehl = arg + 7;
} else if (strncmp(arg, "texthl=", 7) == 0) {
- arg += 7;
- XFREE_CLEAR(texthl);
- texthl = xstrnsave(arg, (size_t)(p - arg));
+ texthl = arg + 7;
} else if (strncmp(arg, "culhl=", 6) == 0) {
- arg += 6;
- XFREE_CLEAR(culhl);
- culhl = xstrnsave(arg, (size_t)(p - arg));
+ culhl = arg + 6;
} else if (strncmp(arg, "numhl=", 6) == 0) {
- arg += 6;
- XFREE_CLEAR(numhl);
- numhl = xstrnsave(arg, (size_t)(p - arg));
+ numhl = arg + 6;
} else {
semsg(_(e_invarg2), arg);
- failed = true;
+ return;
+ }
+ if (*cmdline == NUL) {
break;
}
+ *cmdline++ = NUL;
}
- if (!failed) {
- sign_define_by_name(sign_name, icon, linehl, text,
- texthl, culhl, numhl);
- }
-
- xfree(icon);
- xfree(text);
- xfree(linehl);
- xfree(texthl);
- xfree(culhl);
- xfree(numhl);
+ sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl);
}
/// ":sign place" command
-static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, char *group,
- int prio)
+static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *group, int prio)
{
if (id <= 0) {
// List signs placed in a file/buffer
@@ -1274,74 +653,37 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *sign_name, int id, c
// :sign place
// :sign place group={group}
// :sign place group=*
- if (lnum >= 0 || sign_name != NULL
- || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
} else {
sign_list_placed(buf, group);
}
} else {
// Place a new sign
- if (sign_name == NULL || buf == NULL
- || (group != NULL && *group == '\0')) {
+ if (name == NULL || buf == NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
return;
}
-
- sign_place(&id, group, sign_name, buf, lnum, prio);
+ uint32_t uid = (uint32_t)id;
+ sign_place(&uid, group, name, buf, lnum, prio);
}
}
/// ":sign unplace" command
-static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group)
+static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group)
{
- if (lnum >= 0 || sign_name != NULL || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
emsg(_(e_invarg));
return;
}
- if (id == -2) {
- if (buf != NULL) {
- // :sign unplace * file={fname}
- // :sign unplace * group={group} file={fname}
- // :sign unplace * group=* file={fname}
- // :sign unplace * buffer={nr}
- // :sign unplace * group={group} buffer={nr}
- // :sign unplace * group=* buffer={nr}
- sign_unplace(0, group, buf, 0);
- } else {
- // :sign unplace *
- // :sign unplace * group={group}
- // :sign unplace * group=*
- FOR_ALL_BUFFERS(cbuf) {
- if (cbuf->b_signlist != NULL) {
- buf_delete_signs(cbuf, group);
- }
- }
- }
- } else {
- if (buf != NULL) {
- // :sign unplace {id} file={fname}
- // :sign unplace {id} group={group} file={fname}
- // :sign unplace {id} group=* file={fname}
- // :sign unplace {id} buffer={nr}
- // :sign unplace {id} group={group} buffer={nr}
- // :sign unplace {id} group=* buffer={nr}
- sign_unplace(id, group, buf, 0);
- } else {
- if (id == -1) {
- // :sign unplace group={group}
- // :sign unplace group=*
- sign_unplace_at_cursor(group);
- } else {
- // :sign unplace {id}
- // :sign unplace {id} group={group}
- // :sign unplace {id} group=*
- FOR_ALL_BUFFERS(cbuf) {
- sign_unplace(id, group, cbuf, 0);
- }
- }
- }
+ if (id == -1) {
+ lnum = curwin->w_cursor.lnum;
+ buf = curwin->w_buffer;
+ }
+
+ if (!sign_unplace(buf, MAX(0, id), group, lnum) && lnum > 0) {
+ emsg(_("E159: Missing sign number"));
}
}
@@ -1350,15 +692,14 @@ static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, i
/// :sign jump {id} buffer={nr}
/// :sign jump {id} group={group} file={fname}
/// :sign jump {id} group={group} buffer={nr}
-static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int id, char *group)
+static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group)
{
- if (sign_name == NULL && group == NULL && id == -1) {
+ if (name == NULL && group == NULL && id == -1) {
emsg(_(e_argreq));
return;
}
- if (buf == NULL || (group != NULL && *group == '\0')
- || lnum >= 0 || sign_name != NULL) {
+ if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || name != NULL) {
// File or buffer is not specified or an empty group is used
// or a line number or a sign name is specified.
emsg(_(e_invarg));
@@ -1372,20 +713,18 @@ static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *sign_name, int
/// ":sign jump" commands.
/// The supported arguments are: line={lnum} name={name} group={group}
/// priority={prio} and file={fname} or buffer={nr}.
-static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid, char **group,
- int *prio, buf_T **buf, linenr_T *lnum)
+static int parse_sign_cmd_args(int cmd, char *arg, char **name, int *id, char **group, int *prio,
+ buf_T **buf, linenr_T *lnum)
{
- char *arg1;
- char *name;
+ char *arg1 = arg;
char *filename = NULL;
int lnum_arg = false;
// first arg could be placed sign id
- arg1 = arg;
if (ascii_isdigit(*arg)) {
- *signid = getdigits_int(&arg, true, 0);
+ *id = getdigits_int(&arg, true, 0);
if (!ascii_iswhite(*arg) && *arg != NUL) {
- *signid = -1;
+ *id = -1;
arg = arg1;
} else {
arg = skipwhite(arg);
@@ -1399,23 +738,23 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
arg = skiptowhite(arg);
lnum_arg = true;
} else if (strncmp(arg, "*", 1) == 0 && cmd == SIGNCMD_UNPLACE) {
- if (*signid != -1) {
+ if (*id != -1) {
emsg(_(e_invarg));
return FAIL;
}
- *signid = -2;
+ *id = -2;
arg = skiptowhite(arg + 1);
} else if (strncmp(arg, "name=", 5) == 0) {
arg += 5;
- name = arg;
+ char *namep = arg;
arg = skiptowhite(arg);
if (*arg != NUL) {
*arg++ = NUL;
}
- while (name[0] == '0' && name[1] != NUL) {
- name++;
+ while (namep[0] == '0' && namep[1] != NUL) {
+ namep++;
}
- *sign_name = name;
+ *name = namep;
} else if (strncmp(arg, "group=", 6) == 0) {
arg += 6;
*group = arg;
@@ -1454,8 +793,7 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
// If the filename is not supplied for the sign place or the sign jump
// command, then use the current buffer.
- if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg)
- || cmd == SIGNCMD_JUMP)) {
+ if (filename == NULL && ((cmd == SIGNCMD_PLACE && lnum_arg) || cmd == SIGNCMD_JUMP)) {
*buf = curwin->w_buffer;
}
return OK;
@@ -1465,13 +803,10 @@ static int parse_sign_cmd_args(int cmd, char *arg, char **sign_name, int *signid
void ex_sign(exarg_T *eap)
{
char *arg = eap->arg;
- char *p;
- int idx;
- sign_T *sp;
// Parse the subcommand.
- p = skiptowhite(arg);
- idx = sign_cmd_idx(arg, p);
+ char *p = skiptowhite(arg);
+ int idx = sign_cmd_idx(arg, p);
if (idx == SIGNCMD_LAST) {
semsg(_("E160: Unknown sign command: %s"), arg);
return;
@@ -1482,14 +817,13 @@ void ex_sign(exarg_T *eap)
// Define, undefine or list signs.
if (idx == SIGNCMD_LIST && *arg == NUL) {
// ":sign list": list all defined signs
- for (sp = first_sign; sp != NULL && !got_int; sp = sp->sn_next) {
+ sign_T *sp;
+ map_foreach_value(&sign_map, sp, {
sign_list_defined(sp);
- }
+ });
} else if (*arg == NUL) {
emsg(_("E156: Missing sign name"));
} else {
- char *name;
-
// Isolate the sign name. If it's a number skip leading zeroes,
// so that "099" and "99" are the same sign. But keep "0".
p = skiptowhite(arg);
@@ -1499,248 +833,170 @@ void ex_sign(exarg_T *eap)
while (arg[0] == '0' && arg[1] != NUL) {
arg++;
}
- name = xstrdup(arg);
if (idx == SIGNCMD_DEFINE) {
- sign_define_cmd(name, p);
+ sign_define_cmd(arg, p);
} else if (idx == SIGNCMD_LIST) {
// ":sign list {name}"
- sign_list_by_name(name);
+ sign_list_by_name(arg);
} else {
// ":sign undefine {name}"
- sign_undefine_by_name(name);
+ sign_undefine_by_name(arg);
}
- xfree(name);
return;
}
} else {
int id = -1;
linenr_T lnum = -1;
- char *sign_name = NULL;
+ char *name = NULL;
char *group = NULL;
int prio = SIGN_DEF_PRIO;
buf_T *buf = NULL;
// Parse command line arguments
- if (parse_sign_cmd_args(idx, arg, &sign_name, &id, &group, &prio,
- &buf, &lnum) == FAIL) {
+ if (parse_sign_cmd_args(idx, arg, &name, &id, &group, &prio, &buf, &lnum) == FAIL) {
return;
}
if (idx == SIGNCMD_PLACE) {
- sign_place_cmd(buf, lnum, sign_name, id, group, prio);
+ sign_place_cmd(buf, lnum, name, id, group, prio);
} else if (idx == SIGNCMD_UNPLACE) {
- sign_unplace_cmd(buf, lnum, sign_name, id, group);
+ sign_unplace_cmd(buf, lnum, name, id, group);
} else if (idx == SIGNCMD_JUMP) {
- sign_jump_cmd(buf, lnum, sign_name, id, group);
+ sign_jump_cmd(buf, lnum, name, id, group);
}
}
}
-/// Return information about a specified sign
-static void sign_getinfo(sign_T *sp, dict_T *retdict)
+/// Get dictionary of information for a defined sign "sp"
+static dict_T *sign_get_info_dict(sign_T *sp)
{
- const char *p;
+ dict_T *d = tv_dict_alloc();
+
+ tv_dict_add_str(d, S_LEN("name"), sp->sn_name);
- tv_dict_add_str(retdict, S_LEN("name"), sp->sn_name);
if (sp->sn_icon != NULL) {
- tv_dict_add_str(retdict, S_LEN("icon"), sp->sn_icon);
+ tv_dict_add_str(d, S_LEN("icon"), sp->sn_icon);
}
if (sp->sn_text != NULL) {
- tv_dict_add_str(retdict, S_LEN("text"), sp->sn_text);
- }
- if (sp->sn_line_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_line_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
- }
- 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"), p);
+ tv_dict_add_str(d, S_LEN("text"), sp->sn_text);
}
- if (sp->sn_cul_hl > 0) {
- p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false);
- if (p == NULL) {
- p = "NONE";
+ static char *arg[] = { "linehl", "texthl", "culhl", "numhl" };
+ int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
+ for (int i = 0; i < 4; i++) {
+ if (hl[i] > 0) {
+ const char *p = get_highlight_name_ext(NULL, hl[i] - 1, false);
+ tv_dict_add_str(d, arg[i], strlen(arg[i]), p ? p : "NONE");
}
- 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"), p);
}
+ return d;
}
-/// If 'name' is NULL, return a list of all the defined signs.
-/// Otherwise, return information about the specified sign.
-static void sign_getlist(const char *name, list_T *retlist)
+/// Get dictionary of information for placed sign "mark"
+static dict_T *sign_get_placed_info_dict(MTKey mark)
{
- sign_T *sp = first_sign;
- dict_T *dict;
-
- if (name != NULL) {
- sp = sign_find((char *)name, NULL);
- if (sp == NULL) {
- return;
- }
- }
+ dict_T *d = tv_dict_alloc();
- for (; sp != NULL && !got_int; sp = sp->sn_next) {
- dict = tv_dict_alloc();
- tv_list_append_dict(retlist, dict);
- sign_getinfo(sp, dict);
+ DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
- if (name != NULL) { // handle only the specified sign
- break;
- }
- }
+ tv_dict_add_str(d, S_LEN("name"), sign_get_name(sh));
+ tv_dict_add_nr(d, S_LEN("id"), (int)mark.id);
+ tv_dict_add_str(d, S_LEN("group"), describe_ns((int)mark.ns, ""));
+ tv_dict_add_nr(d, S_LEN("lnum"), mark.pos.row + 1);
+ tv_dict_add_nr(d, S_LEN("priority"), sh->priority);
+ return d;
}
/// Returns information about signs placed in a buffer as list of dicts.
list_T *get_buffer_signs(buf_T *buf)
- FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- sign_entry_T *sign;
- dict_T *d;
list_T *const l = tv_list_alloc(kListLenMayKnow);
+ MarkTreeIter itr[1];
+ marktree_itr_get(buf->b_marktree, 0, 0, itr);
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- d = sign_get_info(sign);
- tv_list_append_dict(l, d);
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (!mt_end(mark) && mt_decor_sign(mark)) {
+ tv_list_append_dict(l, sign_get_placed_info_dict(mark));
+ }
+ marktree_itr_next(buf->b_marktree, itr);
}
+
return l;
}
/// @return information about all the signs placed in a buffer
-static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group,
+static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const char *group,
list_T *retlist)
{
- dict_T *d;
- list_T *l;
- sign_entry_T *sign;
-
- d = tv_dict_alloc();
+ dict_T *d = tv_dict_alloc();
tv_list_append_dict(retlist, d);
- tv_dict_add_nr(d, S_LEN("bufnr"), (long)buf->b_fnum);
+ tv_dict_add_nr(d, S_LEN("bufnr"), buf->b_fnum);
- l = tv_list_alloc(kListLenMayKnow);
+ list_T *l = tv_list_alloc(kListLenMayKnow);
tv_dict_add_list(d, S_LEN("signs"), l);
- FOR_ALL_SIGNS_IN_BUF(buf, sign) {
- if (!sign_in_group(sign, (char *)sign_group)) {
- continue;
+ int64_t ns = group_get_ns(group);
+ if (!buf->b_signs || ns < 0) {
+ return;
+ }
+
+ MarkTreeIter itr[1];
+ kvec_t(MTKey) signs = KV_INITIAL_VALUE;
+ marktree_itr_get(buf->b_marktree, lnum ? lnum - 1 : 0, 0, itr);
+
+ while (itr->x) {
+ MTKey mark = marktree_itr_current(itr);
+ if (lnum && mark.pos.row >= lnum) {
+ break;
}
- if ((lnum == 0 && sign_id == 0)
- || (sign_id == 0 && lnum == sign->se_lnum)
- || (lnum == 0 && sign_id == sign->se_id)
- || (lnum == sign->se_lnum && sign_id == sign->se_id)) {
- tv_list_append_dict(l, sign_get_info(sign));
+ if (!mt_end(mark)
+ && (ns == UINT32_MAX || ns == mark.ns)
+ && ((lnum == 0 && sign_id == 0)
+ || (sign_id == 0 && lnum == mark.pos.row + 1)
+ || (lnum == 0 && sign_id == (int)mark.id)
+ || (lnum == mark.pos.row + 1 && sign_id == (int)mark.id))) {
+ if (mt_decor_sign(mark)) {
+ kv_push(signs, mark);
+ }
}
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+
+ if (kv_size(signs)) {
+ qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_cmp);
+ for (size_t i = 0; i < kv_size(signs); i++) {
+ tv_list_append_dict(l, sign_get_placed_info_dict(kv_A(signs, i)));
+ }
+ kv_destroy(signs);
}
}
/// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the
/// sign placed at the line number. If 'lnum' is zero, return all the signs
/// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers.
-static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char *sign_group,
- list_T *retlist)
+static void sign_get_placed(buf_T *buf, linenr_T lnum, int id, const char *group, list_T *retlist)
{
if (buf != NULL) {
- sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist);
+ sign_get_placed_in_buf(buf, lnum, id, group, retlist);
} else {
FOR_ALL_BUFFERS(cbuf) {
- if (cbuf->b_signlist != NULL) {
- sign_get_placed_in_buf(cbuf, 0, sign_id, sign_group, retlist);
+ if (cbuf->b_signs) {
+ sign_get_placed_in_buf(cbuf, 0, id, group, retlist);
}
}
}
}
-/// List one sign.
-static void sign_list_defined(sign_T *sp)
-{
- smsg("sign %s", sp->sn_name);
- if (sp->sn_icon != NULL) {
- msg_puts(" icon=");
- msg_outtrans(sp->sn_icon);
- msg_puts(_(" (not supported)"));
- }
- if (sp->sn_text != NULL) {
- msg_puts(" text=");
- msg_outtrans(sp->sn_text);
- }
- if (sp->sn_line_hl > 0) {
- msg_puts(" linehl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_line_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_text_hl > 0) {
- msg_puts(" texthl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_text_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_cul_hl > 0) {
- msg_puts(" culhl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_cul_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
- if (sp->sn_num_hl > 0) {
- msg_puts(" numhl=");
- const char *const p = get_highlight_name_ext(NULL,
- sp->sn_num_hl - 1, false);
- if (p == NULL) {
- msg_puts("NONE");
- } else {
- msg_puts(p);
- }
- }
-}
-
-/// Undefine a sign and free its memory.
-static void sign_undefine(sign_T *sp, sign_T *sp_prev)
-{
- xfree(sp->sn_name);
- xfree(sp->sn_icon);
- xfree(sp->sn_text);
- if (sp_prev == NULL) {
- first_sign = sp->sn_next;
- } else {
- sp_prev->sn_next = sp->sn_next;
- }
- xfree(sp);
-}
-
-/// Undefine/free all signs.
void free_signs(void)
{
- while (first_sign != NULL) {
- sign_undefine(first_sign, NULL);
- }
+ cstr_t name;
+ map_foreach_key(&sign_map, name, {
+ sign_undefine_by_name(name);
+ });
}
static enum {
@@ -1758,12 +1014,13 @@ static char *get_nth_sign_name(int idx)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// Complete with name of signs already defined
+ cstr_t name;
int current_idx = 0;
- for (sign_T *sp = first_sign; sp != NULL; sp = sp->sn_next) {
+ map_foreach_key(&sign_map, name, {
if (current_idx++ == idx) {
- return sp->sn_name;
+ return (char *)name;
}
- }
+ });
return NULL;
}
@@ -1771,35 +1028,24 @@ static char *get_nth_sign_name(int idx)
static char *get_nth_sign_group_name(int idx)
{
// Complete with name of sign groups already defined
- int current_idx = 0;
- int todo = (int)sg_table.ht_used;
- for (hashitem_T *hi = sg_table.ht_array; todo > 0; hi++) {
- if (!HASHITEM_EMPTY(hi)) {
- todo--;
- if (current_idx++ == idx) {
- signgroup_T *const group = HI2SG(hi);
- return (char *)group->sg_name;
- }
- }
+ if (idx < (int)kv_size(sign_ns)) {
+ return (char *)describe_ns((NS)kv_A(sign_ns, idx), "");
}
return NULL;
}
-/// Function given to ExpandGeneric() to obtain the sign command
-/// expansion.
+/// Function given to ExpandGeneric() to obtain the sign command expansion.
char *get_sign_name(expand_T *xp, int idx)
{
switch (expand_what) {
case EXP_SUBCMD:
return cmds[idx];
case EXP_DEFINE: {
- char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=",
- NULL };
+ char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL };
return define_arg[idx];
}
case EXP_PLACE: {
- char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=",
- "buffer=", NULL };
+ char *place_arg[] = { "line=", "name=", "group=", "priority=", "file=", "buffer=", NULL };
return place_arg[idx];
}
case EXP_LIST: {
@@ -1822,29 +1068,24 @@ char *get_sign_name(expand_T *xp, int idx)
/// Handle command line completion for :sign command.
void set_context_in_sign_cmd(expand_T *xp, char *arg)
{
- char *end_subcmd;
- char *last;
- int cmd_idx;
- char *begin_subcmd_args;
-
// Default: expand subcommands.
xp->xp_context = EXPAND_SIGN;
expand_what = EXP_SUBCMD;
xp->xp_pattern = arg;
- end_subcmd = skiptowhite(arg);
+ char *end_subcmd = skiptowhite(arg);
if (*end_subcmd == NUL) {
// expand subcmd name
// :sign {subcmd}<CTRL-D>
return;
}
- cmd_idx = sign_cmd_idx(arg, end_subcmd);
+ int cmd_idx = sign_cmd_idx(arg, end_subcmd);
// :sign {subcmd} {subcmd_args}
// |
// begin_subcmd_args
- begin_subcmd_args = skipwhite(end_subcmd);
+ char *begin_subcmd_args = skipwhite(end_subcmd);
// Expand last argument of subcmd.
//
@@ -1853,6 +1094,7 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg)
// p
// Loop until reaching last argument.
+ char *last;
char *p = begin_subcmd_args;
do {
p = skipwhite(p);
@@ -1940,63 +1182,40 @@ void set_context_in_sign_cmd(expand_T *xp, char *arg)
/// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on
/// failure.
-static int sign_define_from_dict(const char *name_arg, dict_T *dict)
+static int sign_define_from_dict(char *name, dict_T *dict)
{
- char *name = NULL;
+ if (name == NULL) {
+ name = tv_dict_get_string(dict, "name", false);
+ if (name == NULL || name[0] == NUL) {
+ return -1;
+ }
+ }
+
char *icon = NULL;
char *linehl = NULL;
char *text = NULL;
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
- int retval = -1;
- if (name_arg == NULL) {
- if (dict == NULL) {
- return -1;
- }
- name = tv_dict_get_string(dict, "name", true);
- } else {
- name = xstrdup(name_arg);
- }
- if (name == NULL || name[0] == NUL) {
- goto cleanup;
- }
if (dict != NULL) {
- icon = tv_dict_get_string(dict, "icon", true);
- linehl = tv_dict_get_string(dict, "linehl", true);
- text = tv_dict_get_string(dict, "text", true);
- texthl = tv_dict_get_string(dict, "texthl", true);
- culhl = tv_dict_get_string(dict, "culhl", true);
- numhl = tv_dict_get_string(dict, "numhl", true);
- }
-
- if (sign_define_by_name(name, icon, linehl,
- text, texthl, culhl, numhl)
- == OK) {
- retval = 0;
- }
-
-cleanup:
- xfree(name);
- xfree(icon);
- xfree(linehl);
- xfree(text);
- xfree(texthl);
- xfree(culhl);
- xfree(numhl);
-
- return retval;
+ icon = tv_dict_get_string(dict, "icon", false);
+ linehl = tv_dict_get_string(dict, "linehl", false);
+ text = tv_dict_get_string(dict, "text", false);
+ texthl = tv_dict_get_string(dict, "texthl", false);
+ culhl = tv_dict_get_string(dict, "culhl", false);
+ numhl = tv_dict_get_string(dict, "numhl", false);
+ }
+
+ return sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl) - 1;
}
/// Define multiple signs using attributes from list 'l' and store the return
/// values in 'retlist'.
static void sign_define_multiple(list_T *l, list_T *retlist)
{
- int retval;
-
TV_LIST_ITER_CONST(l, li, {
- retval = -1;
+ int retval = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
retval = sign_define_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
@@ -2009,8 +1228,6 @@ static void sign_define_multiple(list_T *l, list_T *retlist)
/// "sign_define()" function
void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name;
-
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
// Define multiple signs
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -2022,41 +1239,41 @@ void f_sign_define(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Define a single sign
rettv->vval.v_number = -1;
- name = tv_get_string_chk(&argvars[0]);
+ char *name = (char *)tv_get_string_chk(&argvars[0]);
if (name == NULL) {
return;
}
- if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if (tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
- rettv->vval.v_number = sign_define_from_dict(name,
- argvars[1].v_type ==
- VAR_DICT ? argvars[1].vval.v_dict : NULL);
+ dict_T *d = argvars[1].v_type == VAR_DICT ? argvars[1].vval.v_dict : NULL;
+ rettv->vval.v_number = sign_define_from_dict(name, d);
}
/// "sign_getdefined()" function
void f_sign_getdefined(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name = NULL;
-
tv_list_alloc_ret(rettv, 0);
- if (argvars[0].v_type != VAR_UNKNOWN) {
- name = tv_get_string(&argvars[0]);
+ if (argvars[0].v_type == VAR_UNKNOWN) {
+ sign_T *sp;
+ map_foreach_value(&sign_map, sp, {
+ tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp));
+ });
+ } else {
+ sign_T *sp = pmap_get(cstr_t)(&sign_map, tv_get_string(&argvars[0]));
+ if (sp != NULL) {
+ tv_list_append_dict(rettv->vval.v_list, sign_get_info_dict(sp));
+ }
}
-
- sign_getlist(name, rettv->vval.v_list);
}
/// "sign_getplaced()" function
void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
buf_T *buf = NULL;
- dict_T *dict;
- dictitem_T *di;
linenr_T lnum = 0;
int sign_id = 0;
const char *group = NULL;
@@ -2072,19 +1289,17 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT
- || ((dict = argvars[1].vval.v_dict) == NULL)) {
- emsg(_(e_dictreq));
+ if (tv_check_for_nonnull_dict_arg(argvars, 1) == FAIL) {
return;
}
+ dictitem_T *di;
+ dict_T *dict = argvars[1].vval.v_dict;
if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) {
// get signs placed at this line
- lnum = (linenr_T)tv_get_number_chk(&di->di_tv, &notanum);
- if (notanum) {
+ lnum = tv_get_lnum(&di->di_tv);
+ if (lnum <= 0) {
return;
}
- (void)lnum;
- lnum = tv_get_lnum(&di->di_tv);
}
if ((di = tv_dict_find(dict, "id", -1)) != NULL) {
// get sign placed with this identifier
@@ -2111,103 +1326,81 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "sign_jump()" function
void f_sign_jump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int sign_id;
- char *sign_group = NULL;
- buf_T *buf;
- bool notanum = false;
-
rettv->vval.v_number = -1;
// Sign identifier
- sign_id = (int)tv_get_number_chk(&argvars[0], &notanum);
+ bool notanum = false;
+ int id = (int)tv_get_number_chk(&argvars[0], &notanum);
if (notanum) {
return;
}
- if (sign_id <= 0) {
+ if (id <= 0) {
emsg(_(e_invarg));
return;
}
// Sign group
- const char *sign_group_chk = tv_get_string_chk(&argvars[1]);
- if (sign_group_chk == NULL) {
+ char *group = (char *)tv_get_string_chk(&argvars[1]);
+ if (group == NULL) {
return;
}
- if (sign_group_chk[0] == '\0') {
- sign_group = NULL; // global sign group
- } else {
- sign_group = xstrdup(sign_group_chk);
+ if (group[0] == NUL) {
+ group = NULL;
}
// Buffer to place the sign
- buf = get_buf_arg(&argvars[2]);
+ buf_T *buf = get_buf_arg(&argvars[2]);
if (buf == NULL) {
- goto cleanup;
+ return;
}
- rettv->vval.v_number = sign_jump(sign_id, sign_group, buf);
-
-cleanup:
- xfree(sign_group);
+ rettv->vval.v_number = sign_jump(id, group, buf);
}
/// Place a new sign using the values specified in dict 'dict'. Returns the sign
-/// identifier if successfully placed, otherwise returns 0.
+/// identifier if successfully placed, otherwise returns -1.
static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv,
typval_T *buf_tv, dict_T *dict)
{
- int sign_id = 0;
- char *group = NULL;
- char *sign_name = NULL;
- buf_T *buf = NULL;
dictitem_T *di;
- linenr_T lnum = 0;
- int prio = SIGN_DEF_PRIO;
- bool notanum = false;
- int ret_sign_id = -1;
- // sign identifier
+ int id = 0;
+ bool notanum = false;
if (id_tv == NULL) {
di = tv_dict_find(dict, "id", -1);
if (di != NULL) {
id_tv = &di->di_tv;
}
}
- if (id_tv == NULL) {
- sign_id = 0;
- } else {
- sign_id = (int)tv_get_number_chk(id_tv, &notanum);
+ if (id_tv != NULL) {
+ id = (int)tv_get_number_chk(id_tv, &notanum);
if (notanum) {
return -1;
}
- if (sign_id < 0) {
+ if (id < 0) {
emsg(_(e_invarg));
return -1;
}
}
- // sign group
+ char *group = NULL;
if (group_tv == NULL) {
di = tv_dict_find(dict, "group", -1);
if (di != NULL) {
group_tv = &di->di_tv;
}
}
- if (group_tv == NULL) {
- group = NULL; // global group
- } else {
+ if (group_tv != NULL) {
group = (char *)tv_get_string_chk(group_tv);
if (group == NULL) {
- goto cleanup;
+ return -1;
}
- if (group[0] == '\0') { // global sign group
+ if (group[0] == NUL) {
group = NULL;
- } else {
- group = xstrdup(group);
}
}
- // sign name
+ char *name = NULL;
if (name_tv == NULL) {
di = tv_dict_find(dict, "name", -1);
if (di != NULL) {
@@ -2215,14 +1408,13 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n
}
}
if (name_tv == NULL) {
- goto cleanup;
+ return -1;
}
- sign_name = (char *)tv_get_string_chk(name_tv);
- if (sign_name == NULL) {
- goto cleanup;
+ name = (char *)tv_get_string_chk(name_tv);
+ if (name == NULL) {
+ return -1;
}
- // buffer to place the sign
if (buf_tv == NULL) {
di = tv_dict_find(dict, "buffer", -1);
if (di != NULL) {
@@ -2230,40 +1422,38 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n
}
}
if (buf_tv == NULL) {
- goto cleanup;
+ return -1;
}
- buf = get_buf_arg(buf_tv);
+ buf_T *buf = get_buf_arg(buf_tv);
if (buf == NULL) {
- goto cleanup;
+ return -1;
}
- // line number of the sign
+ linenr_T lnum = 0;
di = tv_dict_find(dict, "lnum", -1);
if (di != NULL) {
lnum = tv_get_lnum(&di->di_tv);
if (lnum <= 0) {
emsg(_(e_invarg));
- goto cleanup;
+ return -1;
}
}
- // sign priority
+ int prio = SIGN_DEF_PRIO;
di = tv_dict_find(dict, "priority", -1);
if (di != NULL) {
prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
if (notanum) {
- goto cleanup;
+ return -1;
}
}
- if (sign_place(&sign_id, group, sign_name, buf, lnum, prio) == OK) {
- ret_sign_id = sign_id;
+ uint32_t uid = (uint32_t)id;
+ if (sign_place(&uid, group, name, buf, lnum, prio) == OK) {
+ return (int)uid;
}
-cleanup:
- xfree(group);
-
- return ret_sign_id;
+ return -1;
}
/// "sign_place()" function
@@ -2273,22 +1463,20 @@ void f_sign_place(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[4].v_type != VAR_UNKNOWN
- && (argvars[4].v_type != VAR_DICT
- || ((dict = argvars[4].vval.v_dict) == NULL))) {
- emsg(_(e_dictreq));
- return;
+ if (argvars[4].v_type != VAR_UNKNOWN) {
+ if (tv_check_for_nonnull_dict_arg(argvars, 4) == FAIL) {
+ return;
+ }
+ dict = argvars[4].vval.v_dict;
}
- rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3],
- dict);
+ rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1],
+ &argvars[2], &argvars[3], dict);
}
/// "sign_placelist()" function. Place multiple signs.
void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int sign_id;
-
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (argvars[0].v_type != VAR_LIST) {
@@ -2298,7 +1486,7 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Process the List of sign attributes
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- sign_id = -1;
+ int sign_id = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
@@ -2311,13 +1499,10 @@ void f_sign_placelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// Undefine multiple signs
static void sign_undefine_multiple(list_T *l, list_T *retlist)
{
- char *name;
- int retval;
-
TV_LIST_ITER_CONST(l, li, {
- retval = -1;
- name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li));
- if (name != NULL && (sign_undefine_by_name((char *)name) == OK)) {
+ int retval = -1;
+ char *name = (char *)tv_get_string_chk(TV_LIST_ITEM_TV(li));
+ if (name != NULL && (sign_undefine_by_name(name) == OK)) {
retval = 0;
}
tv_list_append_number(retlist, retval);
@@ -2327,8 +1512,6 @@ static void sign_undefine_multiple(list_T *l, list_T *retlist)
/// "sign_undefine()" function
void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *name;
-
if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) {
// Undefine multiple signs
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -2345,7 +1528,7 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = 0;
} else {
// Free only the specified sign
- name = tv_get_string_chk(&argvars[0]);
+ const char *name = tv_get_string_chk(&argvars[0]);
if (name == NULL) {
return;
}
@@ -2361,57 +1544,31 @@ void f_sign_undefine(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict)
{
dictitem_T *di;
- int sign_id = 0;
+ int id = 0;
buf_T *buf = NULL;
- char *group = NULL;
- int retval = -1;
-
- // sign group
- if (group_tv != NULL) {
- group = (char *)tv_get_string(group_tv);
- } else {
- group = tv_dict_get_string(dict, "group", false);
- }
- if (group != NULL) {
- if (group[0] == '\0') { // global sign group
- group = NULL;
- } else {
- group = xstrdup(group);
- }
+ char *group = (group_tv != NULL) ? (char *)tv_get_string(group_tv)
+ : tv_dict_get_string(dict, "group", false);
+ if (group != NULL && group[0] == NUL) {
+ group = NULL;
}
if (dict != NULL) {
if ((di = tv_dict_find(dict, "buffer", -1)) != NULL) {
buf = get_buf_arg(&di->di_tv);
if (buf == NULL) {
- goto cleanup;
+ return -1;
}
}
if (tv_dict_find(dict, "id", -1) != NULL) {
- sign_id = (int)tv_dict_get_number(dict, "id");
- if (sign_id <= 0) {
+ id = (int)tv_dict_get_number(dict, "id");
+ if (id <= 0) {
emsg(_(e_invarg));
- goto cleanup;
+ return -1;
}
}
}
- if (buf == NULL) {
- // Delete the sign in all the buffers
- retval = 0;
- FOR_ALL_BUFFERS(buf2) {
- if (sign_unplace(sign_id, group, buf2, 0) != OK) {
- retval = -1;
- }
- }
- } else if (sign_unplace(sign_id, group, buf, 0) == OK) {
- retval = 0;
- }
-
-cleanup:
- xfree(group);
-
- return retval;
+ return sign_unplace(buf, id, group, 0) - 1;
}
/// "sign_unplace()" function
@@ -2421,16 +1578,12 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = -1;
- if (argvars[0].v_type != VAR_STRING) {
- emsg(_(e_invarg));
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_dict_arg(argvars, 1) == FAIL) {
return;
}
if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[1].v_type != VAR_DICT) {
- emsg(_(e_dictreq));
- return;
- }
dict = argvars[1].vval.v_dict;
}
@@ -2440,8 +1593,6 @@ void f_sign_unplace(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "sign_unplacelist()" function
void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- int retval;
-
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (argvars[0].v_type != VAR_LIST) {
@@ -2450,7 +1601,7 @@ void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- retval = -1;
+ int retval = -1;
if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) {
retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict);
} else {
diff --git a/src/nvim/sign.h b/src/nvim/sign.h
index ba84cd71a4..1c607bc7de 100644
--- a/src/nvim/sign.h
+++ b/src/nvim/sign.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_SIGN_H
-#define NVIM_SIGN_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/sign_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/sign_defs.h" // IWYU pragma: export
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "sign.h.generated.h"
#endif
-#endif // NVIM_SIGN_H
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index 7aa06ce48a..79d21585fc 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -1,54 +1,22 @@
-#ifndef NVIM_SIGN_DEFS_H
-#define NVIM_SIGN_DEFS_H
-
-#include <stdbool.h>
-
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-// signs: line annotations
-
-// Sign group
-typedef struct signgroup_S {
- int sg_next_sign_id; ///< next sign id for this group
- uint16_t sg_refcount; ///< number of signs in this group
- char sg_name[1]; ///< sign group name, actually longer
-} signgroup_T;
-
-// Macros to get the sign group structure from the group name
-#define SGN_KEY_OFF offsetof(signgroup_T, sg_name)
-#define HI2SG(hi) ((signgroup_T *)((hi)->hi_key - SGN_KEY_OFF))
-
-typedef struct sign_entry sign_entry_T;
-
-struct sign_entry {
- int se_id; // unique identifier for each placed sign
- int se_typenr; // typenr of sign
- int se_priority; // priority for highlighting
- bool se_has_text_or_icon; // has text or icon
- linenr_T se_lnum; // line number which has this sign
- signgroup_T *se_group; // sign group
- sign_entry_T *se_next; // next entry in a list of signs
- sign_entry_T *se_prev; // previous entry -- for easy reordering
-};
+#pragma once
/// Sign attributes. Used by the screen refresh routines.
typedef struct {
char *text;
- int hl_attr_id;
- int priority;
+ int hl_id;
} SignTextAttrs;
-#define SIGN_SHOW_MAX 9
-
-// Default sign priority for highlighting
-#define SIGN_DEF_PRIO 10
-
-// type argument for sign_get_attr()
-typedef enum {
- SIGN_LINEHL,
- SIGN_NUMHL,
- SIGN_TEXT,
-} SignType;
-
-#endif // NVIM_SIGN_DEFS_H
+/// Struct to hold the sign properties.
+typedef struct sign {
+ char *sn_name; // name of sign
+ char *sn_icon; // name of pixmap
+ char *sn_text; // text used instead of pixmap
+ int sn_line_hl; // highlight ID for line
+ int sn_text_hl; // highlight ID for text
+ int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
+ int sn_num_hl; // highlight ID for line number
+} sign_T;
+
+enum { SIGN_WIDTH = 2, }; ///< Number of display cells for a sign in the signcolumn
+enum { SIGN_SHOW_MAX = 9, }; ///< Maximum number of signs shown on a single line
+enum { SIGN_DEF_PRIO = 10, }; ///< Default sign highlight priority
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 2204cda169..905f5c25b4 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spell.c: code for spell checking
//
// See spellfile.c for the Vim spell file format.
@@ -64,7 +61,7 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/change.h"
@@ -76,6 +73,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
@@ -83,18 +81,18 @@
#include "nvim/highlight_defs.h"
#include "nvim/insexpand.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
@@ -104,9 +102,9 @@
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// Result values. Lower number is accepted over higher one.
@@ -150,7 +148,7 @@ typedef struct matchinf_S {
// for when checking a compound word
int mi_compoff; // start of following word offset
- char_u mi_compflags[MAXWLEN]; // flags for compound words used
+ uint8_t mi_compflags[MAXWLEN]; // flags for compound words used
int mi_complen; // nr of compound words used
int mi_compextra; // nr of COMPOUNDROOT words
@@ -184,12 +182,21 @@ int did_set_spelltab;
# include "spell.c.generated.h"
#endif
-// mode values for find_word
-#define FIND_FOLDWORD 0 // find word case-folded
-#define FIND_KEEPWORD 1 // find keep-case word
-#define FIND_PREFIX 2 // find word after prefix
-#define FIND_COMPOUND 3 // find case-folded compound word
-#define FIND_KEEPCOMPOUND 4 // find keep-case compound word
+/// mode values for find_word
+enum {
+ FIND_FOLDWORD = 0, ///< find word case-folded
+ FIND_KEEPWORD = 1, ///< find keep-case word
+ FIND_PREFIX = 2, ///< find word after prefix
+ FIND_COMPOUND = 3, ///< find case-folded compound word
+ FIND_KEEPCOMPOUND = 4, ///< find keep-case compound word
+};
+
+/// type values for get_char_type
+enum {
+ CHAR_OTHER = 0,
+ CHAR_UPPER = 1,
+ CHAR_DIGIT = 2,
+};
char *e_format = N_("E759: Format error in spell file");
@@ -222,7 +229,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
size_t wrongcaplen = 0;
bool count_word = docount;
bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0;
- bool camel_case = false;
+ bool is_camel_case = false;
// A word never starts at a space or a control character. Return quickly
// then, skipping over the character.
@@ -255,24 +262,14 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
mi.mi_word = ptr;
mi.mi_fend = ptr;
if (spell_iswordp(mi.mi_fend, wp)) {
- bool this_upper = false; // init for gcc
-
if (use_camel_case) {
- int c = utf_ptr2char(mi.mi_fend);
- this_upper = SPELL_ISUPPER(c);
+ mi.mi_fend = advance_camelcase_word(ptr, wp, &is_camel_case);
+ } else {
+ do {
+ MB_PTR_ADV(mi.mi_fend);
+ } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp));
}
- do {
- MB_PTR_ADV(mi.mi_fend);
- if (use_camel_case) {
- const bool prev_upper = this_upper;
- int c = utf_ptr2char(mi.mi_fend);
- this_upper = SPELL_ISUPPER(c);
- camel_case = !prev_upper && this_upper;
- }
- } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)
- && !camel_case);
-
if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) {
// Check word starting with capital letter.
int c = utf_ptr2char(ptr);
@@ -304,7 +301,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
MAXWLEN + 1);
mi.mi_fwordlen = (int)strlen(mi.mi_fword);
- if (camel_case && mi.mi_fwordlen > 0) {
+ if (is_camel_case && mi.mi_fwordlen > 0) {
// introduce a fake word end space into the folded word.
mi.mi_fword[mi.mi_fwordlen - 1] = ' ';
}
@@ -380,7 +377,7 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
MB_PTR_ADV(mi.mi_end);
} else if (mi.mi_result == SP_BAD
&& LANGP_ENTRY(wp->w_s->b_langp, 0)->lp_slang->sl_nobreak) {
- char *p, *fp;
+ char *p;
int save_result = mi.mi_result;
// First language in 'spelllang' is NOBREAK. Find first position
@@ -388,8 +385,8 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, 0);
if (mi.mi_lp->lp_slang->sl_fidxs != NULL) {
p = mi.mi_word;
- fp = mi.mi_fword;
- for (;;) {
+ char *fp = mi.mi_fword;
+ while (true) {
MB_PTR_ADV(p);
MB_PTR_ADV(fp);
if (p >= mi.mi_end) {
@@ -424,6 +421,66 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
return (size_t)(mi.mi_end - ptr);
}
+/// Determine the type of character "c".
+static int get_char_type(int c)
+{
+ if (ascii_isdigit(c)) {
+ return CHAR_DIGIT;
+ }
+ if (SPELL_ISUPPER(c)) {
+ return CHAR_UPPER;
+ }
+ return CHAR_OTHER;
+}
+
+/// Returns a pointer to the end of the word starting at "str".
+/// Supports camelCase words.
+static char *advance_camelcase_word(char *str, win_T *wp, bool *is_camel_case)
+{
+ char *end = str;
+
+ *is_camel_case = false;
+
+ if (*str == NUL) {
+ return str;
+ }
+
+ int c = utf_ptr2char(end);
+ MB_PTR_ADV(end);
+ // We need at most the types of the type of the last two chars.
+ int last_last_type = -1;
+ int last_type = get_char_type(c);
+
+ while (*end != NUL && spell_iswordp(end, wp)) {
+ c = utf_ptr2char(end);
+ int this_type = get_char_type(c);
+
+ if (last_last_type == CHAR_UPPER && last_type == CHAR_UPPER
+ && this_type == CHAR_OTHER) {
+ // Handle the following cases:
+ // UpperUpperLower
+ *is_camel_case = true;
+ // Back up by one char.
+ MB_PTR_BACK(str, end);
+ break;
+ } else if ((this_type == CHAR_UPPER && last_type == CHAR_OTHER)
+ || (this_type != last_type
+ && (this_type == CHAR_DIGIT || last_type == CHAR_DIGIT))) {
+ // Handle the following cases:
+ // LowerUpper LowerDigit UpperDigit DigitUpper DigitLower
+ *is_camel_case = true;
+ break;
+ }
+
+ last_last_type = last_type;
+ last_type = this_type;
+
+ MB_PTR_ADV(end);
+ }
+
+ return end;
+}
+
// Check if the word at "mip->mi_word" is in the tree.
// When "mode" is FIND_FOLDWORD check in fold-case word tree.
// When "mode" is FIND_KEEPWORD check in keep-case word tree.
@@ -437,14 +494,14 @@ static void find_word(matchinf_T *mip, int mode)
int flen;
char *ptr;
slang_T *slang = mip->mi_lp->lp_slang;
- char_u *byts;
+ uint8_t *byts;
idx_T *idxs;
if (mode == FIND_KEEPWORD || mode == FIND_KEEPCOMPOUND) {
// Check for word with matching case in keep-case tree.
ptr = mip->mi_word;
flen = 9999; // no case folding, always enough bytes
- byts = (char_u *)slang->sl_kbyts;
+ byts = slang->sl_kbyts;
idxs = slang->sl_kidxs;
if (mode == FIND_KEEPCOMPOUND) {
@@ -455,7 +512,7 @@ static void find_word(matchinf_T *mip, int mode)
// Check for case-folded in case-folded tree.
ptr = mip->mi_fword;
flen = mip->mi_fwordlen; // available case-folded bytes
- byts = (char_u *)slang->sl_fbyts;
+ byts = slang->sl_fbyts;
idxs = slang->sl_fidxs;
if (mode == FIND_PREFIX) {
@@ -482,7 +539,7 @@ static void find_word(matchinf_T *mip, int mode)
// - there is a byte that doesn't match,
// - we reach the end of the tree,
// - or we reach the end of the line.
- for (;;) {
+ while (true) {
if (flen <= 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -549,7 +606,7 @@ static void find_word(matchinf_T *mip, int mode)
// One space in the good word may stand for several spaces in the
// checked word.
if (c == ' ') {
- for (;;) {
+ while (true) {
if (flen <= 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -737,7 +794,7 @@ static void find_word(matchinf_T *mip, int mode)
// If the word ends the sequence of compound flags of the
// words must match with one of the COMPOUNDRULE items and
// the number of syllables must not be too large.
- mip->mi_compflags[mip->mi_complen] = (char_u)((unsigned)flags >> 24);
+ mip->mi_compflags[mip->mi_complen] = (uint8_t)((unsigned)flags >> 24);
mip->mi_compflags[mip->mi_complen + 1] = NUL;
if (word_ends) {
char fword[MAXWLEN] = { 0 };
@@ -795,9 +852,6 @@ static void find_word(matchinf_T *mip, int mode)
mip->mi_compoff = (int)(p - mip->mi_fword);
}
}
-#if 0
- c = mip->mi_compoff;
-#endif
mip->mi_complen++;
if (flags & WF_COMPROOT) {
mip->mi_compextra++;
@@ -823,16 +877,6 @@ static void find_word(matchinf_T *mip, int mode)
// Find following word in keep-case tree.
mip->mi_compoff = wlen;
find_word(mip, FIND_KEEPCOMPOUND);
-
-#if 0 // Disabled, a prefix must not appear halfway through a compound
- // word, unless the COMPOUNDPERMITFLAG is used, in which case it
- // can't be a postponed prefix.
- if (!slang->sl_nobreak || mip->mi_result == SP_BAD) {
- // Check for following word with prefix.
- mip->mi_compoff = c;
- find_prefix(mip, FIND_COMPOUND);
- }
-#endif
}
if (!slang->sl_nobreak) {
@@ -961,10 +1005,10 @@ bool can_compound(slang_T *slang, const char *word, const uint8_t *flags)
// compound rule. This is used to stop trying a compound if the flags
// collected so far can't possibly match any compound rule.
// Caller must check that slang->sl_comprules is not NULL.
-bool match_compoundrule(slang_T *slang, const char_u *compflags)
+bool match_compoundrule(slang_T *slang, const uint8_t *compflags)
{
// loop over all the COMPOUNDRULE entries
- for (char_u *p = (char_u *)slang->sl_comprules; *p != NUL; p++) {
+ for (char *p = (char *)slang->sl_comprules; *p != NUL; p++) {
// loop over the flags in the compound word we have made, match
// them against the current rule entry
for (int i = 0;; i++) {
@@ -982,21 +1026,21 @@ bool match_compoundrule(slang_T *slang, const char_u *compflags)
// compare against all the flags in []
p++;
while (*p != ']' && *p != NUL) {
- if (*p++ == c) {
+ if ((uint8_t)(*p++) == c) {
match = true;
}
}
if (!match) {
break; // none matches
}
- } else if (*p != c) {
+ } else if ((uint8_t)(*p) != c) {
break; // flag of word doesn't match flag in pattern
}
p++;
}
// Skip to the next "/", where the next pattern starts.
- p = (char_u *)vim_strchr((char *)p, '/');
+ p = vim_strchr(p, '/');
if (p == NULL) {
break;
}
@@ -1062,13 +1106,13 @@ static void find_prefix(matchinf_T *mip, int mode)
int wlen = 0;
slang_T *slang = mip->mi_lp->lp_slang;
- char_u *byts = (char_u *)slang->sl_pbyts;
+ uint8_t *byts = slang->sl_pbyts;
if (byts == NULL) {
return; // array is empty
}
// We use the case-folded word here, since prefixes are always
// case-folded.
- char_u *ptr = (char_u *)mip->mi_fword;
+ char *ptr = mip->mi_fword;
int flen = mip->mi_fwordlen; // available case-folded bytes
if (mode == FIND_COMPOUND) {
// Skip over the previously found word(s).
@@ -1081,7 +1125,7 @@ static void find_prefix(matchinf_T *mip, int mode)
// - there is a byte that doesn't match,
// - we reach the end of the tree,
// - or we reach the end of the line.
- for (;;) {
+ while (true) {
if (flen == 0 && *mip->mi_fend != NUL) {
flen = fold_more(mip);
}
@@ -1126,7 +1170,7 @@ static void find_prefix(matchinf_T *mip, int mode)
}
// Perform a binary search in the list of accepted bytes.
- int c = ptr[wlen];
+ int c = (uint8_t)ptr[wlen];
idx_T lo = arridx;
idx_T hi = arridx + len - 1;
while (lo < hi) {
@@ -1189,11 +1233,19 @@ bool spell_valid_case(int wordflags, int treeflags)
|| (wordflags & WF_ONECAP) != 0));
}
-// Returns true if spell checking is not enabled.
+/// Return true if spell checking is enabled for "wp".
+bool spell_check_window(win_T *wp)
+{
+ return wp->w_p_spell
+ && *wp->w_s->b_p_spl != NUL
+ && wp->w_s->b_langp.ga_len > 0
+ && *(char **)(wp->w_s->b_langp.ga_data) != NULL;
+}
+
+/// Return true and give an error if spell checking is not enabled.
bool no_spell_checking(win_T *wp)
{
- if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL
- || GA_EMPTY(&wp->w_s->b_langp)) {
+ if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) {
emsg(_(e_no_spell));
return true;
}
@@ -1203,19 +1255,18 @@ bool no_spell_checking(win_T *wp)
static void decor_spell_nav_start(win_T *wp)
{
decor_state = (DecorState){ 0 };
- decor_redraw_reset(wp->w_buffer, &decor_state);
+ decor_redraw_reset(wp, &decor_state);
}
-static bool decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col,
- char **decor_error)
+static TriState decor_spell_nav_col(win_T *wp, linenr_T lnum, linenr_T *decor_lnum, int col)
{
if (*decor_lnum != lnum) {
- decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1, decor_error);
- decor_redraw_line(wp->w_buffer, lnum - 1, &decor_state);
+ decor_providers_invoke_spell(wp, lnum - 1, col, lnum - 1, -1);
+ decor_redraw_line(wp, lnum - 1, &decor_state);
*decor_lnum = lnum;
}
- decor_redraw_col(wp->w_buffer, col, col, false, &decor_state);
- return decor_state.spell == kTrue;
+ decor_redraw_col(wp, col, 0, false, &decor_state);
+ return decor_state.spell;
}
static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col)
@@ -1269,7 +1320,6 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
linenr_T lnum = wp->w_cursor.lnum;
clearpos(&found_pos);
- char *decor_error = NULL;
// Ephemeral extmarks are currently stored in the global decor_state.
// When looking for spell errors, we need to:
// - temporarily reset decor_state
@@ -1283,7 +1333,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
decor_spell_nav_start(wp);
while (!got_int) {
- char *line = ml_get_buf(wp->w_buffer, lnum, false);
+ char *line = ml_get_buf(wp->w_buffer, lnum);
len = strlen(line);
if (buflen < len + MAXWLEN + 2) {
@@ -1304,23 +1354,23 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
} else if (curline && wp == curwin) {
// For spellbadword(): check if first word needs a capital.
col = (colnr_T)getwhitecols(line);
- if (check_need_cap(lnum, col)) {
+ if (check_need_cap(curwin, lnum, col)) {
capcol = col;
}
// Need to get the line again, may have looked at the previous
// one.
- line = ml_get_buf(wp->w_buffer, lnum, false);
+ line = ml_get_buf(wp->w_buffer, lnum);
}
// Copy the line into "buf" and append the start of the next line if
// possible. Note: this ml_get_buf() may make "line" invalid, check
// for empty line first.
- bool empty_line = *skipwhite((const char *)line) == NUL;
+ bool empty_line = *skipwhite(line) == NUL;
STRCPY(buf, line);
if (lnum < wp->w_buffer->b_ml.ml_line_count) {
spell_cat_line(buf + strlen(buf),
- ml_get_buf(wp->w_buffer, lnum + 1, false),
+ ml_get_buf(wp->w_buffer, lnum + 1),
MAXWLEN);
}
char *p = buf + skip;
@@ -1352,9 +1402,18 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
: p - buf) > wp->w_cursor.col)) {
col = (colnr_T)(p - buf);
- bool can_spell = (!has_syntax && (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) == 0)
- || decor_spell_nav_col(wp, lnum, &decor_lnum, col, &decor_error)
- || (has_syntax && can_syn_spell(wp, lnum, col));
+ bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
+ bool can_spell = !no_plain_buffer;
+ switch (decor_spell_nav_col(wp, lnum, &decor_lnum, col)) {
+ case kTrue:
+ can_spell = true; break;
+ case kFalse:
+ can_spell = false; break;
+ case kNone:
+ if (has_syntax) {
+ can_spell = can_syn_spell(wp, lnum, col);
+ }
+ }
if (!can_spell) {
attr = HLF_COUNT;
@@ -1471,7 +1530,6 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att
theend:
decor_state_free(&decor_state);
- xfree(decor_error);
decor_state = saved_decor_start;
xfree(buf);
return ret;
@@ -1483,9 +1541,9 @@ theend:
// to skip those bytes if the word was OK.
void spell_cat_line(char *buf, char *line, int maxlen)
{
- char_u *p = (char_u *)skipwhite(line);
+ char *p = skipwhite(line);
while (vim_strchr("*#/\"\t", (uint8_t)(*p)) != NULL) {
- p = (char_u *)skipwhite((char *)p + 1);
+ p = skipwhite(p + 1);
}
if (*p == NUL) {
@@ -1494,10 +1552,10 @@ void spell_cat_line(char *buf, char *line, int maxlen)
// Only worth concatenating if there is something else than spaces to
// concatenate.
- int n = (int)(p - (char_u *)line) + 1;
+ int n = (int)(p - line) + 1;
if (n < maxlen - 1) {
memset(buf, ' ', (size_t)n);
- xstrlcpy(buf + n, (char *)p, (size_t)(maxlen - n));
+ xstrlcpy(buf + n, p, (size_t)(maxlen - n));
}
}
@@ -1553,7 +1611,7 @@ static void spell_load_lang(char *lang)
lang);
do_cmdline_cmd(autocmd_buf);
} else {
- smsg(_("Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""),
+ smsg(0, _("Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""),
lang, spell_enc(), lang);
}
} else if (sl.sl_slang != NULL) {
@@ -1700,23 +1758,32 @@ void slang_clear_sug(slang_T *lp)
// Load one spell file and store the info into a slang_T.
// Invoked through do_in_runtimepath().
-static void spell_load_cb(char *fname, void *cookie)
+static bool spell_load_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
spelload_T *slp = (spelload_T *)cookie;
- slang_T *slang = spell_load_file(fname, slp->sl_lang, NULL, false);
- if (slang == NULL) {
- return;
- }
+ for (int i = 0; i < num_fnames; i++) {
+ slang_T *slang = spell_load_file(fnames[i], slp->sl_lang, NULL, false);
+
+ if (slang == NULL) {
+ continue;
+ }
- // When a previously loaded file has NOBREAK also use it for the
- // ".add" files.
- if (slp->sl_nobreak && slang->sl_add) {
- slang->sl_nobreak = true;
- } else if (slang->sl_nobreak) {
- slp->sl_nobreak = true;
+ // When a previously loaded file has NOBREAK also use it for the
+ // ".add" files.
+ if (slp->sl_nobreak && slang->sl_add) {
+ slang->sl_nobreak = true;
+ } else if (slang->sl_nobreak) {
+ slp->sl_nobreak = true;
+ }
+
+ slp->sl_slang = slang;
+
+ if (!all) {
+ break;
+ }
}
- slp->sl_slang = slang;
+ return num_fnames > 0;
}
/// Add a word to the hashtable of common words.
@@ -1743,12 +1810,12 @@ void count_common_word(slang_T *lp, char *word, int len, uint8_t count)
wordcount_T *wc;
hash_T hash = hash_hash(p);
const size_t p_len = strlen(p);
- hashitem_T *hi = hash_lookup(&lp->sl_wordcount, (const char *)p, p_len, hash);
+ hashitem_T *hi = hash_lookup(&lp->sl_wordcount, p, p_len, hash);
if (HASHITEM_EMPTY(hi)) {
wc = xmalloc(offsetof(wordcount_T, wc_word) + p_len + 1);
memcpy(wc->wc_word, p, p_len + 1);
wc->wc_count = count;
- hash_add_item(&lp->sl_wordcount, hi, (char *)wc->wc_word, hash);
+ hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash);
} else {
wc = HI2WC(hi);
wc->wc_count = (uint16_t)(wc->wc_count + count);
@@ -1775,7 +1842,7 @@ bool byte_in_str(uint8_t *str, int n)
int init_syl_tab(slang_T *slang)
{
ga_init(&slang->sl_syl_items, sizeof(syl_item_T), 4);
- char *p = vim_strchr((char *)slang->sl_syllable, '/');
+ char *p = vim_strchr(slang->sl_syllable, '/');
while (p != NULL) {
*p++ = NUL;
if (*p == NUL) { // trailing slash
@@ -1838,7 +1905,7 @@ static int count_syllables(slang_T *slang, const char *word)
// No recognized syllable item, at least a syllable char then?
int c = utf_ptr2char(p);
len = utfc_ptr2len(p);
- if (vim_strchr((char *)slang->sl_syllable, c) == NULL) {
+ if (vim_strchr(slang->sl_syllable, c) == NULL) {
skip = false; // No, search for next syllable
} else if (!skip) {
cnt++; // Yes, count it
@@ -1851,7 +1918,7 @@ static int count_syllables(slang_T *slang, const char *word)
/// Parse 'spelllang' and set w_s->b_langp accordingly.
/// @return NULL if it's OK, an untranslated error message otherwise.
-char *did_set_spelllang(win_T *wp)
+char *parse_spelllang(win_T *wp)
{
garray_T ga;
char *splp;
@@ -1863,14 +1930,12 @@ char *did_set_spelllang(win_T *wp)
int c;
char lang[MAXWLEN + 1];
char spf_name[MAXPATHL];
- int len;
char *p;
int round;
char *spf;
char *use_region = NULL;
bool dont_use_region = false;
bool nobreak = false;
- langp_T *lp, *lp2;
static bool recursive = false;
char *ret_msg = NULL;
char *spl_copy;
@@ -1900,7 +1965,7 @@ char *did_set_spelllang(win_T *wp)
// Get one language name.
copy_option_part(&splp, lang, MAXWLEN, ",");
region = NULL;
- len = (int)strlen(lang);
+ int len = (int)strlen(lang);
if (!valid_spelllang(lang)) {
continue;
@@ -1994,7 +2059,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// This is probably an error. Give a warning and
// accept the words anyway.
- smsg(_("Warning: region %s not supported"),
+ smsg(0, _("Warning: region %s not supported"),
region);
}
} else {
@@ -2107,7 +2172,7 @@ char *did_set_spelllang(win_T *wp)
// REP items. If the language doesn't support it itself use another one
// with the same name. E.g. for "en-math" use "en".
for (int i = 0; i < ga.ga_len; i++) {
- lp = LANGP_ENTRY(ga, i);
+ langp_T *lp = LANGP_ENTRY(ga, i);
// sound folding
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
@@ -2116,7 +2181,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// find first similar language that does sound folding
for (int j = 0; j < ga.ga_len; j++) {
- lp2 = LANGP_ENTRY(ga, j);
+ langp_T *lp2 = LANGP_ENTRY(ga, j);
if (!GA_EMPTY(&lp2->lp_slang->sl_sal)
&& strncmp(lp->lp_slang->sl_name,
lp2->lp_slang->sl_name, 2) == 0) {
@@ -2133,7 +2198,7 @@ char *did_set_spelllang(win_T *wp)
} else {
// find first similar language that has REP items
for (int j = 0; j < ga.ga_len; j++) {
- lp2 = LANGP_ENTRY(ga, j);
+ langp_T *lp2 = LANGP_ENTRY(ga, j);
if (!GA_EMPTY(&lp2->lp_slang->sl_rep)
&& strncmp(lp->lp_slang->sl_name,
lp2->lp_slang->sl_name, 2) == 0) {
@@ -2167,14 +2232,14 @@ static void use_midword(slang_T *lp, win_T *wp)
return;
}
- for (char *p = (char *)lp->sl_midword; *p != NUL;) {
+ for (char *p = lp->sl_midword; *p != NUL;) {
const int c = utf_ptr2char(p);
const int l = utfc_ptr2len(p);
if (c < 256 && l <= 2) {
wp->w_s->b_spell_ismw[c] = true;
} else if (wp->w_s->b_spell_ismw_mb == NULL) {
// First multi-byte char in "b_spell_ismw_mb".
- wp->w_s->b_spell_ismw_mb = xstrnsave(p, (size_t)l);
+ wp->w_s->b_spell_ismw_mb = xmemdupz(p, (size_t)l);
} else {
// Append multi-byte chars to "b_spell_ismw_mb".
const int n = (int)strlen(wp->w_s->b_spell_ismw_mb);
@@ -2215,10 +2280,10 @@ static int find_region(const char *rp, const char *region)
/// @param[in] end End of word or NULL for NUL delimited string
///
/// @returns Case type of word
-int captype(char *word, const char *end)
+int captype(const char *word, const char *end)
FUNC_ATTR_NONNULL_ARG(1)
{
- char *p;
+ const char *p;
// find first letter
for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) {
@@ -2226,7 +2291,7 @@ int captype(char *word, const char *end)
return 0; // only non-word characters, illegal word
}
}
- int c = mb_ptr2char_adv((const char **)&p);
+ int c = mb_ptr2char_adv(&p);
bool allcap;
bool firstcap = allcap = SPELL_ISUPPER(c);
bool past_second = false; // past second word char
@@ -2309,7 +2374,7 @@ void spell_reload(void)
// window for this buffer in which 'spell' is set.
if (*wp->w_s->b_p_spl != NUL) {
if (wp->w_p_spell) {
- (void)did_set_spelllang(wp);
+ (void)parse_spelllang(wp);
break;
}
}
@@ -2354,8 +2419,8 @@ void clear_spell_chartab(spelltab_T *sp)
CLEAR_FIELD(sp->st_isu);
for (int i = 0; i < 256; i++) {
- sp->st_fold[i] = (char_u)i;
- sp->st_upper[i] = (char_u)i;
+ sp->st_fold[i] = (uint8_t)i;
+ sp->st_upper[i] = (uint8_t)i;
}
// We include digits. A word shouldn't start with a digit, but handling
@@ -2366,11 +2431,11 @@ void clear_spell_chartab(spelltab_T *sp)
for (int i = 'A'; i <= 'Z'; i++) {
sp->st_isw[i] = true;
sp->st_isu[i] = true;
- sp->st_fold[i] = (char_u)(i + 0x20);
+ sp->st_fold[i] = (uint8_t)(i + 0x20);
}
for (int i = 'a'; i <= 'z'; i++) {
sp->st_isw[i] = true;
- sp->st_upper[i] = (char_u)(i - 0x20);
+ sp->st_upper[i] = (uint8_t)(i - 0x20);
}
}
@@ -2391,8 +2456,8 @@ void init_spell_chartab(void)
// The folded/upper-cased value is different between latin1 and
// utf8 for 0xb5, causing E763 for no good reason. Use the latin1
// value for utf-8 to avoid this.
- spelltab.st_fold[i] = (f < 256) ? (char_u)f : (char_u)i;
- spelltab.st_upper[i] = (u < 256) ? (char_u)u : (char_u)i;
+ spelltab.st_fold[i] = (f < 256) ? (uint8_t)f : (uint8_t)i;
+ spelltab.st_upper[i] = (u < 256) ? (uint8_t)u : (uint8_t)i;
}
}
@@ -2480,7 +2545,7 @@ static bool spell_iswordp_w(const int *p, const win_T *wp)
// Uses the character definitions from the .spl file.
// When using a multi-byte 'encoding' the length may change!
// Returns FAIL when something wrong.
-int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
+int spell_casefold(const win_T *wp, const char *str, int len, char *buf, int buflen)
FUNC_ATTR_NONNULL_ALL
{
if (len >= buflen) {
@@ -2491,12 +2556,12 @@ int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
int outi = 0;
// Fold one character at a time.
- for (char *p = str; p < str + len;) {
+ for (const char *p = str; p < str + len;) {
if (outi + MB_MAXBYTES > buflen) {
buf[outi] = NUL;
return FAIL;
}
- int c = mb_cptr2char_adv((const char **)&p);
+ int c = mb_cptr2char_adv(&p);
// Exception: greek capital sigma 0x03A3 folds to 0x03C3, except
// when it is the last character in a word, then it folds to
@@ -2519,25 +2584,24 @@ int spell_casefold(const win_T *wp, char *str, int len, char *buf, int buflen)
}
// Check if the word at line "lnum" column "col" is required to start with a
-// capital. This uses 'spellcapcheck' of the current buffer.
-bool check_need_cap(linenr_T lnum, colnr_T col)
+// capital. This uses 'spellcapcheck' of the buffer in window "wp".
+bool check_need_cap(win_T *wp, linenr_T lnum, colnr_T col)
{
- bool need_cap = false;
-
- if (curwin->w_s->b_cap_prog == NULL) {
+ if (wp->w_s->b_cap_prog == NULL) {
return false;
}
- char *line = get_cursor_line_ptr();
+ bool need_cap = false;
+ char *line = col ? ml_get_buf(wp->w_buffer, lnum) : NULL;
char *line_copy = NULL;
colnr_T endcol = 0;
- if (getwhitecols(line) >= (int)col) {
+ if (col == 0 || getwhitecols(line) >= col) {
// At start of line, check if previous line is empty or sentence
// ends there.
if (lnum == 1) {
need_cap = true;
} else {
- line = ml_get(lnum - 1);
+ line = ml_get_buf(wp->w_buffer, lnum - 1);
if (*skipwhite(line) == NUL) {
need_cap = true;
} else {
@@ -2554,13 +2618,13 @@ bool check_need_cap(linenr_T lnum, colnr_T col)
if (endcol > 0) {
// Check if sentence ends before the bad word.
regmatch_T regmatch = {
- .regprog = curwin->w_s->b_cap_prog,
+ .regprog = wp->w_s->b_cap_prog,
.rm_ic = false
};
char *p = line + endcol;
- for (;;) {
+ while (true) {
MB_PTR_BACK(line, p);
- if (p == line || spell_iswordp_nmw(p, curwin)) {
+ if (p == line || spell_iswordp_nmw(p, wp)) {
break;
}
if (vim_regexec(&regmatch, p, 0)
@@ -2569,7 +2633,7 @@ bool check_need_cap(linenr_T lnum, colnr_T col)
break;
}
}
- curwin->w_s->b_cap_prog = regmatch.regprog;
+ wp->w_s->b_cap_prog = regmatch.regprog;
}
xfree(line_copy);
@@ -2588,9 +2652,11 @@ void ex_spellrepall(exarg_T *eap)
emsg(_("E752: No previous spell replacement"));
return;
}
- int addlen = (int)(strlen(repl_to) - strlen(repl_from));
+ const size_t repl_from_len = strlen(repl_from);
+ const size_t repl_to_len = strlen(repl_to);
+ const int addlen = (int)(repl_to_len - repl_from_len);
- size_t frompatlen = strlen(repl_from) + 7;
+ const size_t frompatlen = repl_from_len + 7;
char *frompat = xmalloc(frompatlen);
snprintf(frompat, frompatlen, "\\V\\<%s\\>", repl_from);
p_ws = false;
@@ -2599,7 +2665,7 @@ void ex_spellrepall(exarg_T *eap)
sub_nlines = 0;
curwin->w_cursor.lnum = 0;
while (!got_int) {
- if (do_search(NULL, '/', '/', frompat, 1L, SEARCH_KEEP, NULL) == 0
+ if (do_search(NULL, '/', '/', frompat, 1, SEARCH_KEEP, NULL) == 0
|| u_save_cursor() == FAIL) {
break;
}
@@ -2607,14 +2673,15 @@ void ex_spellrepall(exarg_T *eap)
// Only replace when the right word isn't there yet. This happens
// when changing "etc" to "etc.".
char *line = get_cursor_line_ptr();
- if (addlen <= 0 || strncmp(line + curwin->w_cursor.col,
- repl_to, strlen(repl_to)) != 0) {
+ if (addlen <= 0
+ || strncmp(line + curwin->w_cursor.col, repl_to, repl_to_len) != 0) {
char *p = xmalloc(strlen(line) + (size_t)addlen + 1);
memmove(p, line, (size_t)curwin->w_cursor.col);
STRCPY(p + curwin->w_cursor.col, repl_to);
- STRCAT(p, line + curwin->w_cursor.col + strlen(repl_from));
+ STRCAT(p, line + curwin->w_cursor.col + repl_from_len);
ml_replace(curwin->w_cursor.lnum, p, false);
- changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ inserted_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col,
+ (int)repl_from_len, (int)repl_to_len);
if (curwin->w_cursor.lnum != prev_lnum) {
sub_nlines++;
@@ -2622,7 +2689,7 @@ void ex_spellrepall(exarg_T *eap)
}
sub_nsubs++;
}
- curwin->w_cursor.col += (colnr_T)strlen(repl_to);
+ curwin->w_cursor.col += (colnr_T)repl_to_len;
}
p_ws = save_ws;
@@ -2643,10 +2710,10 @@ void ex_spellrepall(exarg_T *eap)
/// @param[in] word source string to copy
/// @param[in,out] wcopy copied string, with case of first letter changed
/// @param[in] upper True to upper case, otherwise lower case
-void onecap_copy(char *word, char *wcopy, bool upper)
+void onecap_copy(const char *word, char *wcopy, bool upper)
{
- char *p = word;
- int c = mb_cptr2char_adv((const char **)&p);
+ const char *p = word;
+ int c = mb_cptr2char_adv(&p);
if (upper) {
c = SPELL_TOUPPER(c);
} else {
@@ -2658,26 +2725,26 @@ void onecap_copy(char *word, char *wcopy, bool upper)
// Make a copy of "word" with all the letters upper cased into
// "wcopy[MAXWLEN]". The result is NUL terminated.
-void allcap_copy(char *word, char *wcopy)
+void allcap_copy(const char *word, char *wcopy)
{
- char_u *d = (char_u *)wcopy;
- for (char *s = word; *s != NUL;) {
- int c = mb_cptr2char_adv((const char **)&s);
+ char *d = wcopy;
+ for (const char *s = word; *s != NUL;) {
+ int c = mb_cptr2char_adv(&s);
if (c == 0xdf) {
c = 'S';
- if (d - (char_u *)wcopy >= MAXWLEN - 1) {
+ if (d - wcopy >= MAXWLEN - 1) {
break;
}
- *d++ = (char_u)c;
+ *d++ = (char)c;
} else {
c = SPELL_TOUPPER(c);
}
- if (d - (char_u *)wcopy >= MAXWLEN - MB_MAXBYTES) {
+ if (d - wcopy >= MAXWLEN - MB_MAXBYTES) {
break;
}
- d += utf_char2bytes(c, (char *)d);
+ d += utf_char2bytes(c, d);
}
*d = NUL;
}
@@ -2777,7 +2844,7 @@ void spell_soundfold(slang_T *slang, char *inword, bool folded, char *res)
// Perform sound folding of "inword" into "res" according to SOFOFROM and
// SOFOTO lines.
-static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
+static void spell_soundfold_sofo(slang_T *slang, const char *inword, char *res)
{
int ri = 0;
@@ -2785,8 +2852,8 @@ static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
// The sl_sal_first[] table contains the translation for chars up to
// 255, sl_sal the rest.
- for (char *s = inword; *s != NUL;) {
- int c = mb_cptr2char_adv((const char **)&s);
+ for (const char *s = inword; *s != NUL;) {
+ int c = mb_cptr2char_adv(&s);
if (utf_class(c) == 0) {
c = ' ';
} else if (c < 256) {
@@ -2796,7 +2863,7 @@ static void spell_soundfold_sofo(slang_T *slang, char *inword, char *res)
if (ip == NULL) { // empty list, can't match
c = NUL;
} else {
- for (;;) { // find "c" in the list
+ while (true) { // find "c" in the list
if (*ip == 0) { // not found
c = NUL;
break;
@@ -2834,7 +2901,6 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
int j, z;
int reslen;
int k = 0;
- int z0;
int k0;
int n0;
int pri;
@@ -2846,8 +2912,8 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
// Remove accents, if wanted. We actually remove all non-word characters.
// But keep white space.
int wordlen = 0;
- for (const char *s = (char *)inword; *s != NUL;) {
- const char_u *t = (char_u *)s;
+ for (const char *s = inword; *s != NUL;) {
+ const char *t = s;
int c = mb_cptr2char_adv(&s);
if (slang->sl_rem_accents) {
if (utf_class(c) == 0) {
@@ -2858,7 +2924,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
did_white = true;
} else {
did_white = false;
- if (!spell_iswordp_nmw((char *)t, curwin)) {
+ if (!spell_iswordp_nmw(t, curwin)) {
continue;
}
}
@@ -2875,7 +2941,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
while ((c = word[i]) != NUL) {
// Start with the first rule that has the character in the word.
int n = slang->sl_sal_first[c & 0xff];
- z0 = 0;
+ int z0 = 0;
if (n >= 0) {
// Check all rules for the same index byte.
@@ -2915,10 +2981,10 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
k++;
}
- char_u *s = (char_u *)smp[n].sm_rules;
+ char *s = smp[n].sm_rules;
pri = 5; // default priority
- p0 = *s;
+ p0 = (uint8_t)(*s);
k0 = k;
while (*s == '-' && k > 1) {
k--;
@@ -2929,7 +2995,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
if (ascii_isdigit(*s)) {
// determine priority
- pri = *s - '0';
+ pri = (uint8_t)(*s) - '0';
s++;
}
if (*s == '^' && *(s + 1) == '^') {
@@ -2992,7 +3058,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
p0 = 5;
- s = (char_u *)smp[n0].sm_rules;
+ s = smp[n0].sm_rules;
while (*s == '-') {
// "k0" gets NOT reduced because
// "if (k0 == k)"
@@ -3002,7 +3068,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
s++;
}
if (ascii_isdigit(*s)) {
- p0 = *s - '0';
+ p0 = (uint8_t)(*s) - '0';
s++;
}
@@ -3033,8 +3099,8 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
// replace string
ws = smp[n].sm_to_w;
- s = (char_u *)smp[n].sm_rules;
- p0 = (vim_strchr((char *)s, '<') != NULL) ? 1 : 0;
+ s = smp[n].sm_rules;
+ p0 = (vim_strchr(s, '<') != NULL) ? 1 : 0;
if (p0 == 1 && z == 0) {
// rule with '<' is used
if (reslen > 0 && ws != NULL && *ws != NUL
@@ -3077,7 +3143,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
} else {
c = *ws;
}
- if (strstr((char *)s, "^^") != NULL) {
+ if (strstr(s, "^^") != NULL) {
if (c != NUL) {
wres[reslen++] = c;
}
@@ -3130,9 +3196,9 @@ void ex_spellinfo(exarg_T *eap)
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len && !got_int; lpi++) {
langp_T *const lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
msg_puts("file: ");
- msg_puts((const char *)lp->lp_slang->sl_fname);
+ msg_puts(lp->lp_slang->sl_fname);
msg_putchar('\n');
- const char *const p = (const char *)lp->lp_slang->sl_info;
+ const char *const p = lp->lp_slang->sl_info;
if (p != NULL) {
msg_puts(p);
msg_putchar('\n');
@@ -3153,17 +3219,15 @@ void ex_spelldump(exarg_T *eap)
if (no_spell_checking(curwin)) {
return;
}
- char *spl;
- long dummy;
- (void)get_option_value("spl", &dummy, &spl, NULL, OPT_LOCAL);
+ OptVal spl = get_option_value("spl", NULL, OPT_LOCAL, NULL);
// Create a new empty buffer in a new window.
do_cmdline_cmd("new");
// enable spelling locally in the new window
- set_option_value_give_err("spell", true, "", OPT_LOCAL);
- set_option_value_give_err("spl", dummy, spl, OPT_LOCAL);
- xfree(spl);
+ set_option_value_give_err("spell", BOOLEAN_OPTVAL(true), OPT_LOCAL);
+ set_option_value_give_err("spl", spl, OPT_LOCAL);
+ optval_free(spl);
if (!buf_is_empty(curbuf)) {
return;
@@ -3195,7 +3259,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
int curi[MAXWLEN];
char word[MAXWLEN];
int c;
- char *byts;
+ uint8_t *byts;
idx_T *idxs;
linenr_T lnum = 0;
int depth;
@@ -3238,11 +3302,9 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
}
}
- if (do_region && region_names != NULL) {
- if (pat == NULL) {
- vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names);
- ml_append(lnum++, IObuff, (colnr_T)0, false);
- }
+ if (do_region && region_names != NULL && pat == NULL) {
+ vim_snprintf(IObuff, IOSIZE, "/regions=%s", region_names);
+ ml_append(lnum++, IObuff, 0, false);
} else {
do_region = false;
}
@@ -3257,7 +3319,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
if (pat == NULL) {
vim_snprintf(IObuff, IOSIZE, "# file: %s", slang->sl_fname);
- ml_append(lnum++, IObuff, (colnr_T)0, false);
+ ml_append(lnum++, IObuff, 0, false);
}
// When matching with a pattern and there are no prefixes only use
@@ -3297,7 +3359,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
// Do one more byte at this node.
n = arridx[depth] + curi[depth];
curi[depth]++;
- c = (uint8_t)byts[n];
+ c = byts[n];
if (c == 0 || depth >= MAXWLEN - 1) {
// End of word or reached maximum length, deal with the
// word.
@@ -3426,7 +3488,7 @@ static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int
}
}
- ml_append(lnum, p, (colnr_T)0, false);
+ ml_append(lnum, p, 0, false);
} else if (((dumpflags & DUMPFLAG_ICASE)
? mb_strnicmp(p, pat, strlen(pat)) == 0
: strncmp(p, pat, strlen(pat)) == 0)
@@ -3463,7 +3525,7 @@ static linenr_T dump_prefixes(slang_T *slang, char *word, char *pat, Direction *
has_word_up = true;
}
- char_u *byts = (char_u *)slang->sl_pbyts;
+ uint8_t *byts = slang->sl_pbyts;
idx_T *idxs = slang->sl_pidxs;
if (byts != NULL) { // array not is empty
// Loop over all prefixes, building them byte-by-byte in prefix[].
@@ -3585,7 +3647,7 @@ static bool spell_expand_need_cap;
void spell_expand_check_cap(colnr_T col)
{
- spell_expand_need_cap = check_need_cap(curwin->w_cursor.lnum, col);
+ spell_expand_need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, col);
}
// Get list of spelling suggestions.
@@ -3613,16 +3675,16 @@ bool valid_spellfile(const char *val)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
for (const char *s = val; *s != NUL; s++) {
- if (!vim_isfilec((uint8_t)(*s)) && *s != ',' && *s != ' ') {
+ if (!vim_is_fname_char((uint8_t)(*s))) {
return false;
}
}
return true;
}
-char *did_set_spell_option(bool is_spellfile)
+const char *did_set_spell_option(bool is_spellfile)
{
- char *errmsg = NULL;
+ const char *errmsg = NULL;
if (is_spellfile) {
int l = (int)strlen(curwin->w_s->b_p_spf);
@@ -3638,7 +3700,7 @@ char *did_set_spell_option(bool is_spellfile)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == curbuf && wp->w_p_spell) {
- errmsg = did_set_spelllang(wp);
+ errmsg = parse_spelllang(wp);
break;
}
}
@@ -3647,7 +3709,7 @@ char *did_set_spell_option(bool is_spellfile)
/// Set curbuf->b_cap_prog to the regexp program for 'spellcapcheck'.
/// Return error message when failed, NULL when OK.
-char *compile_cap_prog(synblock_T *synblock)
+const char *compile_cap_prog(synblock_T *synblock)
FUNC_ATTR_NONNULL_ALL
{
regprog_T *rp = synblock->b_cap_prog;
diff --git a/src/nvim/spell.h b/src/nvim/spell.h
index f941f0e5e7..f3977fdaf2 100644
--- a/src/nvim/spell.h
+++ b/src/nvim/spell.h
@@ -1,14 +1,27 @@
-#ifndef NVIM_SPELL_H
-#define NVIM_SPELL_H
+#pragma once
#include <stdbool.h>
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/globals.h"
-#include "nvim/spell_defs.h"
-#include "nvim/vim.h"
+#include "nvim/spell_defs.h" // IWYU pragma: export
+#include "nvim/vim_defs.h"
+
+/// First language that is loaded, start of the linked list of loaded languages.
+extern slang_T *first_lang;
+
+/// file used for "zG" and "zW"
+extern char *int_wordlist;
+
+extern spelltab_T spelltab;
+extern int did_set_spelltab;
+
+extern char *e_format;
+
+// Remember what "z?" replaced.
+extern char *repl_from;
+extern char *repl_to;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spell.h.generated.h"
#endif
-#endif // NVIM_SPELL_H
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index 726af7d698..dfa399750f 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -1,196 +1,201 @@
-#ifndef NVIM_SPELL_DEFS_H
-#define NVIM_SPELL_DEFS_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "nvim/buffer_defs.h"
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
+#include "nvim/hashtab_defs.h"
#include "nvim/regexp_defs.h"
-#include "nvim/types.h"
-#define MAXWLEN 254 // Assume max. word len is this many bytes.
- // Some places assume a word length fits in a
- // byte, thus it can't be above 255.
+/// Assume max. word len is this many bytes.
+/// Some places assume a word length fits in a byte, thus it can't be above 255.
+enum { MAXWLEN = 254, };
-// Number of regions supported.
-#define MAXREGIONS 8
+/// Number of regions supported.
+enum { MAXREGIONS = 8, };
-// Type used for indexes in the word tree need to be at least 4 bytes. If int
-// is 8 bytes we could use something smaller, but what?
+/// Type used for indexes in the word tree need to be at least 4 bytes. If int
+/// is 8 bytes we could use something smaller, but what?
typedef int idx_T;
#define SPL_FNAME_TMPL "%s.%s.spl"
#define SPL_FNAME_ADD ".add."
#define SPL_FNAME_ASCII ".ascii."
-// Flags used for a word. Only the lowest byte can be used, the region byte
-// comes above it.
-#define WF_REGION 0x01 // region byte follows
-#define WF_ONECAP 0x02 // word with one capital (or all capitals)
-#define WF_ALLCAP 0x04 // word must be all capitals
-#define WF_RARE 0x08 // rare word
-#define WF_BANNED 0x10 // bad word
-#define WF_AFX 0x20 // affix ID follows
-#define WF_FIXCAP 0x40 // keep-case word, allcap not allowed
-#define WF_KEEPCAP 0x80 // keep-case word
-
-#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP)
-
-// for <flags2>, shifted up one byte to be used in wn_flags
-#define WF_HAS_AFF 0x0100 // word includes affix
-#define WF_NEEDCOMP 0x0200 // word only valid in compound
-#define WF_NOSUGGEST 0x0400 // word not to be suggested
-#define WF_COMPROOT 0x0800 // already compounded word, COMPOUNDROOT
-#define WF_NOCOMPBEF 0x1000 // no compounding before this word
-#define WF_NOCOMPAFT 0x2000 // no compounding after this word
-
-// flags for <pflags>
-#define WFP_RARE 0x01 // rare prefix
-#define WFP_NC 0x02 // prefix is not combining
-#define WFP_UP 0x04 // to-upper prefix
-#define WFP_COMPPERMIT 0x08 // prefix with COMPOUNDPERMITFLAG
-#define WFP_COMPFORBID 0x10 // prefix with COMPOUNDFORBIDFLAG
-
-// Flags for postponed prefixes in "sl_pidxs". Must be above affixID (one
-// byte) and prefcondnr (two bytes).
-#define WF_RAREPFX (WFP_RARE << 24) // rare postponed prefix
-#define WF_PFX_NC (WFP_NC << 24) // non-combining postponed prefix
-#define WF_PFX_UP (WFP_UP << 24) // to-upper postponed prefix
-#define WF_PFX_COMPPERMIT (WFP_COMPPERMIT << 24) // postponed prefix with
- // COMPOUNDPERMITFLAG
-#define WF_PFX_COMPFORBID (WFP_COMPFORBID << 24) // postponed prefix with
- // COMPOUNDFORBIDFLAG
-
-// flags for <compoptions>
-#define COMP_CHECKDUP 1 // CHECKCOMPOUNDDUP
-#define COMP_CHECKREP 2 // CHECKCOMPOUNDREP
-#define COMP_CHECKCASE 4 // CHECKCOMPOUNDCASE
-#define COMP_CHECKTRIPLE 8 // CHECKCOMPOUNDTRIPLE
-
-// Info from "REP", "REPSAL" and "SAL" entries in ".aff" file used in si_rep,
-// si_repsal, sl_rep, and si_sal. Not for sl_sal!
-// One replacement: from "ft_from" to "ft_to".
+/// Flags used for a word. Only the lowest byte can be used, the region byte
+/// comes above it.
+enum {
+ WF_REGION = 0x01, ///< region byte follows
+ WF_ONECAP = 0x02, ///< word with one capital (or all capitals)
+ WF_ALLCAP = 0x04, ///< word must be all capitals
+ WF_RARE = 0x08, ///< rare word
+ WF_BANNED = 0x10, ///< bad word
+ WF_AFX = 0x20, ///< affix ID follows
+ WF_FIXCAP = 0x40, ///< keep-case word, allcap not allowed
+ WF_KEEPCAP = 0x80, ///< keep-case word
+ WF_CAPMASK = (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP),
+};
+
+/// for <flags2>, shifted up one byte to be used in wn_flags
+enum {
+ WF_HAS_AFF = 0x0100, ///< word includes affix
+ WF_NEEDCOMP = 0x0200, ///< word only valid in compound
+ WF_NOSUGGEST = 0x0400, ///< word not to be suggested
+ WF_COMPROOT = 0x0800, ///< already compounded word, COMPOUNDROOT
+ WF_NOCOMPBEF = 0x1000, ///< no compounding before this word
+ WF_NOCOMPAFT = 0x2000, ///< no compounding after this word
+};
+
+/// flags for <pflags>
+enum {
+ WFP_RARE = 0x01, ///< rare prefix
+ WFP_NC = 0x02, ///< prefix is not combining
+ WFP_UP = 0x04, ///< to-upper prefix
+ WFP_COMPPERMIT = 0x08, ///< prefix with COMPOUNDPERMITFLAG
+ WFP_COMPFORBID = 0x10, ///< prefix with COMPOUNDFORBIDFLAG
+};
+
+/// Flags for postponed prefixes in "sl_pidxs". Must be above affixID (one
+/// byte) and prefcondnr (two bytes).
+enum {
+ WF_RAREPFX = WFP_RARE << 24, ///< rare postponed prefix
+ WF_PFX_NC = WFP_NC << 24, ///< non-combining postponed prefix
+ WF_PFX_UP = WFP_UP << 24, ///< to-upper postponed prefix
+ WF_PFX_COMPPERMIT = WFP_COMPPERMIT << 24, ///< postponed prefix with COMPOUNDPERMITFLAG
+ WF_PFX_COMPFORBID = WFP_COMPFORBID << 24, ///< postponed prefix with COMPOUNDFORBIDFLAG
+};
+
+/// flags for <compoptions>
+enum {
+ COMP_CHECKDUP = 1, ///< CHECKCOMPOUNDDUP
+ COMP_CHECKREP = 2, ///< CHECKCOMPOUNDREP
+ COMP_CHECKCASE = 4, ///< CHECKCOMPOUNDCASE
+ COMP_CHECKTRIPLE = 8, ///< CHECKCOMPOUNDTRIPLE
+};
+
+/// Info from "REP", "REPSAL" and "SAL" entries in ".aff" file used in si_rep,
+/// si_repsal, sl_rep, and si_sal. Not for sl_sal!
+/// One replacement: from "ft_from" to "ft_to".
typedef struct fromto_S {
char *ft_from;
char *ft_to;
} fromto_T;
-// Info from "SAL" entries in ".aff" file used in sl_sal.
-// The info is split for quick processing by spell_soundfold().
-// Note that "sm_oneof" and "sm_rules" point into sm_lead.
+/// Info from "SAL" entries in ".aff" file used in sl_sal.
+/// The info is split for quick processing by spell_soundfold().
+/// Note that "sm_oneof" and "sm_rules" point into sm_lead.
typedef struct salitem_S {
- char *sm_lead; // leading letters
- int sm_leadlen; // length of "sm_lead"
- char_u *sm_oneof; // letters from () or NULL
- char *sm_rules; // rules like ^, $, priority
- char *sm_to; // replacement.
- int *sm_lead_w; // wide character copy of "sm_lead"
- int *sm_oneof_w; // wide character copy of "sm_oneof"
- int *sm_to_w; // wide character copy of "sm_to"
+ char *sm_lead; ///< leading letters
+ int sm_leadlen; ///< length of "sm_lead"
+ char *sm_oneof; ///< letters from () or NULL
+ char *sm_rules; ///< rules like ^, $, priority
+ char *sm_to; ///< replacement.
+ int *sm_lead_w; ///< wide character copy of "sm_lead"
+ int *sm_oneof_w; ///< wide character copy of "sm_oneof"
+ int *sm_to_w; ///< wide character copy of "sm_to"
} salitem_T;
typedef int salfirst_T;
-// Values for SP_*ERROR are negative, positive values are used by
-// read_cnt_string().
-#define SP_TRUNCERROR (-1) // spell file truncated error
-#define SP_FORMERROR (-2) // format error in spell file
-#define SP_OTHERERROR (-3) // other error while reading spell file
-
-// Structure used to store words and other info for one language, loaded from
-// a .spl file.
-// The main access is through the tree in "sl_fbyts/sl_fidxs", storing the
-// case-folded words. "sl_kbyts/sl_kidxs" is for keep-case words.
-//
-// The "byts" array stores the possible bytes in each tree node, preceded by
-// the number of possible bytes, sorted on byte value:
-// <len> <byte1> <byte2> ...
-// The "idxs" array stores the index of the child node corresponding to the
-// byte in "byts".
-// Exception: when the byte is zero, the word may end here and "idxs" holds
-// the flags, region mask and affixID for the word. There may be several
-// zeros in sequence for alternative flag/region/affixID combinations.
+/// Values for SP_*ERROR are negative, positive values are used by
+/// read_cnt_string().
+enum {
+ SP_TRUNCERROR = -1, ///< spell file truncated error
+ SP_FORMERROR = -2, ///< format error in spell file
+ SP_OTHERERROR = -3, ///< other error while reading spell file
+};
+
+/// Structure used to store words and other info for one language, loaded from
+/// a .spl file.
+/// The main access is through the tree in "sl_fbyts/sl_fidxs", storing the
+/// case-folded words. "sl_kbyts/sl_kidxs" is for keep-case words.
+///
+/// The "byts" array stores the possible bytes in each tree node, preceded by
+/// the number of possible bytes, sorted on byte value:
+/// <len> <byte1> <byte2> ...
+/// The "idxs" array stores the index of the child node corresponding to the
+/// byte in "byts".
+/// Exception: when the byte is zero, the word may end here and "idxs" holds
+/// the flags, region mask and affixID for the word. There may be several
+/// zeros in sequence for alternative flag/region/affixID combinations.
typedef struct slang_S slang_T;
struct slang_S {
- slang_T *sl_next; // next language
- char *sl_name; // language name "en", "en.rare", "nl", etc.
- char *sl_fname; // name of .spl file
- bool sl_add; // true if it's a .add file.
-
- char *sl_fbyts; // case-folded word bytes
- long sl_fbyts_len; // length of sl_fbyts
- idx_T *sl_fidxs; // case-folded word indexes
- char *sl_kbyts; // keep-case word bytes
- idx_T *sl_kidxs; // keep-case word indexes
- char *sl_pbyts; // prefix tree word bytes
- idx_T *sl_pidxs; // prefix tree word indexes
-
- char_u *sl_info; // infotext string or NULL
-
+ slang_T *sl_next; ///< next language
+ char *sl_name; ///< language name "en", "en.rare", "nl", etc.
+ char *sl_fname; ///< name of .spl file
+ bool sl_add; ///< true if it's a .add file.
+
+ uint8_t *sl_fbyts; ///< case-folded word bytes
+ int sl_fbyts_len; ///< length of sl_fbyts
+ idx_T *sl_fidxs; ///< case-folded word indexes
+ uint8_t *sl_kbyts; ///< keep-case word bytes
+ idx_T *sl_kidxs; ///< keep-case word indexes
+ uint8_t *sl_pbyts; ///< prefix tree word bytes
+ idx_T *sl_pidxs; ///< prefix tree word indexes
+
+ char *sl_info; ///< infotext string or NULL
+
+ /// table with up to 8 region names plus NUL
char sl_regions[MAXREGIONS * 2 + 1];
- // table with up to 8 region names plus NUL
-
- char_u *sl_midword; // MIDWORD string or NULL
-
- hashtab_T sl_wordcount; // hashtable with word count, wordcount_T
-
- int sl_compmax; // COMPOUNDWORDMAX (default: MAXWLEN)
- int sl_compminlen; // COMPOUNDMIN (default: 0)
- int sl_compsylmax; // COMPOUNDSYLMAX (default: MAXWLEN)
- int sl_compoptions; // COMP_* flags
- garray_T sl_comppat; // CHECKCOMPOUNDPATTERN items
- regprog_T *sl_compprog; // COMPOUNDRULE turned into a regexp progrm
- // (NULL when no compounding)
- uint8_t *sl_comprules; // all COMPOUNDRULE concatenated (or NULL)
- uint8_t *sl_compstartflags; // flags for first compound word
- uint8_t *sl_compallflags; // all flags for compound words
- bool sl_nobreak; // When true: no spaces between words
- char_u *sl_syllable; // SYLLABLE repeatable chars or NULL
- garray_T sl_syl_items; // syllable items
-
- int sl_prefixcnt; // number of items in "sl_prefprog"
- regprog_T **sl_prefprog; // table with regprogs for prefixes
-
- garray_T sl_rep; // list of fromto_T entries from REP lines
- int16_t sl_rep_first[256]; // indexes where byte first appears, -1 if
- // there is none
- garray_T sl_sal; // list of salitem_T entries from SAL lines
- salfirst_T sl_sal_first[256]; // indexes where byte first appears, -1 if
- // there is none
- bool sl_followup; // SAL followup
- bool sl_collapse; // SAL collapse_result
- bool sl_rem_accents; // SAL remove_accents
- bool sl_sofo; // SOFOFROM and SOFOTO instead of SAL items:
- // "sl_sal_first" maps chars
- // "sl_sal" is a list of wide char lists.
- garray_T sl_repsal; // list of fromto_T entries from REPSAL lines
- int16_t sl_repsal_first[256]; // sl_rep_first for REPSAL lines
- bool sl_nosplitsugs; // don't suggest splitting a word
- bool sl_nocompoundsugs; // don't suggest compounding
+
+ char *sl_midword; ///< MIDWORD string or NULL
+
+ hashtab_T sl_wordcount; ///< hashtable with word count, wordcount_T
+
+ int sl_compmax; ///< COMPOUNDWORDMAX (default: MAXWLEN)
+ int sl_compminlen; ///< COMPOUNDMIN (default: 0)
+ int sl_compsylmax; ///< COMPOUNDSYLMAX (default: MAXWLEN)
+ int sl_compoptions; ///< COMP_* flags
+ garray_T sl_comppat; ///< CHECKCOMPOUNDPATTERN items
+ regprog_T *sl_compprog; ///< COMPOUNDRULE turned into a regexp progrm
+ ///< (NULL when no compounding)
+ uint8_t *sl_comprules; ///< all COMPOUNDRULE concatenated (or NULL)
+ uint8_t *sl_compstartflags; ///< flags for first compound word
+ uint8_t *sl_compallflags; ///< all flags for compound words
+ bool sl_nobreak; ///< When true: no spaces between words
+ char *sl_syllable; ///< SYLLABLE repeatable chars or NULL
+ garray_T sl_syl_items; ///< syllable items
+
+ int sl_prefixcnt; ///< number of items in "sl_prefprog"
+ regprog_T **sl_prefprog; ///< table with regprogs for prefixes
+
+ garray_T sl_rep; ///< list of fromto_T entries from REP lines
+ int16_t sl_rep_first[256]; ///< indexes where byte first appears, -1 if there is none
+ garray_T sl_sal; ///< list of salitem_T entries from SAL lines
+ salfirst_T sl_sal_first[256]; ///< indexes where byte first appears, -1 if there is none
+ bool sl_followup; ///< SAL followup
+ bool sl_collapse; ///< SAL collapse_result
+ bool sl_rem_accents; ///< SAL remove_accents
+ bool sl_sofo; ///< SOFOFROM and SOFOTO instead of SAL items:
+ ///< "sl_sal_first" maps chars
+ ///< "sl_sal" is a list of wide char lists.
+ garray_T sl_repsal; ///< list of fromto_T entries from REPSAL lines
+ int16_t sl_repsal_first[256]; ///< sl_rep_first for REPSAL lines
+ bool sl_nosplitsugs; ///< don't suggest splitting a word
+ bool sl_nocompoundsugs; ///< don't suggest compounding
// Info from the .sug file. Loaded on demand.
- time_t sl_sugtime; // timestamp for .sug file
- char *sl_sbyts; // soundfolded word bytes
- idx_T *sl_sidxs; // soundfolded word indexes
- buf_T *sl_sugbuf; // buffer with word number table
- bool sl_sugloaded; // true when .sug file was loaded or failed to
- // load
-
- bool sl_has_map; // true, if there is a MAP line
- hashtab_T sl_map_hash; // MAP for multi-byte chars
- int sl_map_array[256]; // MAP for first 256 chars
- hashtab_T sl_sounddone; // table with soundfolded words that have
- // handled, see add_sound_suggest()
+ time_t sl_sugtime; ///< timestamp for .sug file
+ uint8_t *sl_sbyts; ///< soundfolded word bytes
+ idx_T *sl_sidxs; ///< soundfolded word indexes
+ buf_T *sl_sugbuf; ///< buffer with word number table
+ bool sl_sugloaded; ///< true when .sug file was loaded or failed to load
+
+ bool sl_has_map; ///< true, if there is a MAP line
+ hashtab_T sl_map_hash; ///< MAP for multi-byte chars
+ int sl_map_array[256]; ///< MAP for first 256 chars
+ hashtab_T sl_sounddone; ///< table with soundfolded words that have
+ ///< handled, see add_sound_suggest()
};
-// Structure used in "b_langp", filled from 'spelllang'.
+/// Structure used in "b_langp", filled from 'spelllang'.
typedef struct langp_S {
- slang_T *lp_slang; // info for this language
- slang_T *lp_sallang; // language used for sound folding or NULL
- slang_T *lp_replang; // language used for REP items or NULL
- int lp_region; // bitmask for region or REGION_ALL
+ slang_T *lp_slang; ///< info for this language
+ slang_T *lp_sallang; ///< language used for sound folding or NULL
+ slang_T *lp_replang; ///< language used for REP items or NULL
+ int lp_region; ///< bitmask for region or REGION_ALL
} langp_T;
#define LANGP_ENTRY(ga, i) (((langp_T *)(ga).ga_data) + (i))
@@ -199,15 +204,15 @@ typedef struct langp_S {
#define VIMSUGMAGICL 6
#define VIMSUGVERSION 1
-#define REGION_ALL 0xff // word valid in all regions
+enum { REGION_ALL = 0xff, }; ///< word valid in all regions
-// The tables used for recognizing word characters according to spelling.
-// These are only used for the first 256 characters of 'encoding'.
+/// The tables used for recognizing word characters according to spelling.
+/// These are only used for the first 256 characters of 'encoding'.
typedef struct {
- bool st_isw[256]; // flags: is word char
- bool st_isu[256]; // flags: is uppercase char
- char_u st_fold[256]; // chars: folded case
- char_u st_upper[256]; // chars: upper case
+ bool st_isw[256]; ///< flags: is word char
+ bool st_isu[256]; ///< flags: is uppercase char
+ uint8_t st_fold[256]; ///< chars: folded case
+ uint8_t st_upper[256]; ///< chars: upper case
} spelltab_T;
// Use our own character-case definitions, because the current locale may
@@ -222,19 +227,7 @@ typedef struct {
#define SPELL_ISUPPER(c) ((c) >= 128 ? mb_isupper(c) : spelltab.st_isu[c])
-// First language that is loaded, start of the linked list of loaded
-// languages.
-extern slang_T *first_lang;
-
-// file used for "zG" and "zW"
-extern char *int_wordlist;
-
-extern spelltab_T spelltab;
-extern int did_set_spelltab;
-
-extern char *e_format;
-
-// Values for "what" argument of spell_add_word()
+/// Values for "what" argument of spell_add_word()
typedef enum {
SPELL_ADD_GOOD = 0,
SPELL_ADD_BAD = 1,
@@ -242,16 +235,10 @@ typedef enum {
} SpellAddType;
typedef struct wordcount_S {
- uint16_t wc_count; ///< nr of times word was seen
- char_u wc_word[1]; ///< word, actually longer
+ uint16_t wc_count; ///< nr of times word was seen
+ char wc_word[]; ///< word
} wordcount_T;
#define WC_KEY_OFF offsetof(wordcount_T, wc_word)
#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF))
-#define MAXWORDCOUNT 0xffff
-
-// Remember what "z?" replaced.
-extern char *repl_from;
-extern char *repl_to;
-
-#endif // NVIM_SPELL_DEFS_H
+enum { MAXWORDCOUNT = 0xffff, };
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 7b124ae6b6..2607fddc31 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spellfile.c: code for reading and writing spell files.
//
// See spell.c for information about spell checking.
@@ -231,44 +228,46 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include "auto/config.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/spell.h"
#include "nvim/spell_defs.h"
#include "nvim/spellfile.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Special byte values for <byte>. Some are only used in the tree for
// postponed prefixes, some only in the other trees. This is a bit messy...
@@ -322,11 +321,15 @@ enum {
CF_UPPER = 0x02,
};
-static char *e_spell_trunc = N_("E758: Truncated spell file");
-static char *e_illegal_character_in_word = N_("E1280: Illegal character in word");
-static char *e_afftrailing = N_("Trailing text in %s line %d: %s");
-static char *e_affname = N_("Affix name too long in %s line %d: %s");
-static char *msg_compressing = N_("Compressing word tree...");
+static const char *e_spell_trunc = N_("E758: Truncated spell file");
+static const char e_error_while_reading_sug_file_str[]
+ = N_("E782: Error while reading .sug file: %s");
+static const char e_duplicate_char_in_map_entry[]
+ = N_("E783: Duplicate char in MAP entry");
+static const char *e_illegal_character_in_word = N_("E1280: Illegal character in word");
+static const char *e_afftrailing = N_("Trailing text in %s line %d: %s");
+static const char *e_affname = N_("Affix name too long in %s line %d: %s");
+static const char *msg_compressing = N_("Compressing word tree...");
#define MAXLINELEN 500 // Maximum length in bytes of a line in a .aff
// and .dic file.
@@ -386,7 +389,7 @@ typedef struct affheader_S {
// Flag used in compound items.
typedef struct compitem_S {
- char_u ci_key[AH_KEY_LEN]; // key for hashtab == name of compound
+ char ci_key[AH_KEY_LEN]; // key for hashtab == name of compound
unsigned ci_flag; // affix name as number, uses "af_flagtype"
int ci_newID; // affix ID after renumbering.
} compitem_T;
@@ -404,14 +407,14 @@ typedef struct sblock_S sblock_T;
struct sblock_S {
int sb_used; // nr of bytes already in use
sblock_T *sb_next; // next block in list
- char_u sb_data[1]; // data, actually longer
+ char sb_data[]; // data
};
// A node in the tree.
typedef struct wordnode_S wordnode_T;
struct wordnode_S {
union { // shared to save space
- char_u hashkey[6]; // the hash key, only used while compressing
+ uint8_t hashkey[6]; // the hash key, only used while compressing
int index; // index in written nodes (valid after first
// round)
} wn_u1;
@@ -422,17 +425,17 @@ struct wordnode_S {
wordnode_T *wn_child; // child (next byte in word)
wordnode_T *wn_sibling; // next sibling (alternate byte in word,
// always sorted)
- int wn_refs; // Nr. of references to this node. Only
- // relevant for first node in a list of
- // siblings, in following siblings it is
- // always one.
- char_u wn_byte; // Byte for this node. NUL for word end
+ int wn_refs; // Nr. of references to this node. Only
+ // relevant for first node in a list of
+ // siblings, in following siblings it is
+ // always one.
+ uint8_t wn_byte; // Byte for this node. NUL for word end
// Info for when "wn_byte" is NUL.
// In PREFIXTREE "wn_region" is used for the prefcondnr.
// In the soundfolded word tree "wn_flags" has the MSW of the wordnr and
// "wn_region" the LSW of the wordnr.
- char_u wn_affixID; // supported/required prefix ID or 0
+ uint8_t wn_affixID; // supported/required prefix ID or 0
uint16_t wn_flags; // WF_ flags
int16_t wn_region; // region mask
@@ -448,24 +451,24 @@ struct wordnode_S {
// Info used while reading the spell files.
typedef struct spellinfo_S {
wordnode_T *si_foldroot; // tree with case-folded words
- long si_foldwcount; // nr of words in si_foldroot
+ int si_foldwcount; // nr of words in si_foldroot
wordnode_T *si_keeproot; // tree with keep-case words
- long si_keepwcount; // nr of words in si_keeproot
+ int si_keepwcount; // nr of words in si_keeproot
wordnode_T *si_prefroot; // tree with postponed prefixes
- long si_sugtree; // creating the soundfolding trie
+ int si_sugtree; // creating the soundfolding trie
sblock_T *si_blocks; // memory blocks used
- long si_blocks_cnt; // memory blocks allocated
+ int si_blocks_cnt; // memory blocks allocated
int si_did_emsg; // true when ran out of memory
- long si_compress_cnt; // words to add before lowering
- // compression limit
+ int si_compress_cnt; // words to add before lowering
+ // compression limit
wordnode_T *si_first_free; // List of nodes that have been freed during
// compression, linked by "wn_child" field.
- long si_free_count; // number of nodes in si_first_free
+ int si_free_count; // number of nodes in si_first_free
#ifdef SPELL_PRINTTREE
int si_wordnode_nr; // sequence nr for nodes
#endif
@@ -482,7 +485,7 @@ typedef struct spellinfo_S {
char *si_info; // info text chars or NULL
int si_region_count; // number of regions supported (1 when there
// are no regions)
- char_u si_region_name[MAXREGIONS * 2 + 1];
+ char si_region_name[MAXREGIONS * 2 + 1];
// region names; used only if
// si_region_count > 1)
@@ -508,7 +511,7 @@ typedef struct spellinfo_S {
garray_T si_comppat; // CHECKCOMPOUNDPATTERN items, each stored as
// a string
char *si_compflags; // flags used for compounding
- char_u si_nobreak; // NOBREAK
+ char si_nobreak; // NOBREAK
char *si_syllable; // syllable string
garray_T si_prefcond; // table with conditions for postponed
// prefixes, each stored as a string
@@ -593,29 +596,25 @@ static inline int spell_check_magic_string(FILE *const fd)
/// @return the slang_T the spell file was loaded into. NULL for error.
slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
{
- FILE *fd;
char *p;
- int n;
- int len;
slang_T *lp = NULL;
- int c = 0;
int res;
bool did_estack_push = false;
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
if (!silent) {
semsg(_(e_notopen), fname);
} else if (p_verbose > 2) {
verbose_enter();
- smsg((char *)e_notopen, fname);
+ smsg(0, e_notopen, fname);
verbose_leave();
}
goto endFAIL;
}
if (p_verbose > 2) {
verbose_enter();
- smsg(_("Reading spell file \"%s\""), fname);
+ smsg(0, _("Reading spell file \"%s\""), fname);
verbose_leave();
}
@@ -649,7 +648,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
case 0:
break;
}
- c = getc(fd); // <versionnr>
+ int c = getc(fd); // <versionnr>
if (c < VIMSPELLVERSION) {
emsg(_("E771: Old spell file, needs to be updated"));
goto endFAIL;
@@ -660,13 +659,13 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
// <SECTIONS>: <section> ... <sectionend>
// <section>: <sectionID> <sectionflags> <sectionlen> (section contents)
- for (;;) {
- n = getc(fd); // <sectionID> or <sectionend>
+ while (true) {
+ int n = getc(fd); // <sectionID> or <sectionend>
if (n == SN_END) {
break;
}
c = getc(fd); // <sectionflags>
- len = get4c(fd); // <sectionlen>
+ int len = get4c(fd); // <sectionlen>
if (len < 0) {
goto truncerr;
}
@@ -674,7 +673,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
res = 0;
switch (n) {
case SN_INFO:
- lp->sl_info = READ_STRING(fd, len); // <infotext>
+ lp->sl_info = read_string(fd, (size_t)len); // <infotext>
if (lp->sl_info == NULL) {
goto endFAIL;
}
@@ -689,7 +688,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_MIDWORD:
- lp->sl_midword = READ_STRING(fd, len); // <midword>
+ lp->sl_midword = read_string(fd, (size_t)len); // <midword>
if (lp->sl_midword == NULL) {
goto endFAIL;
}
@@ -716,7 +715,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_MAP:
- p = (char *)READ_STRING(fd, len); // <mapstr>
+ p = read_string(fd, (size_t)len); // <mapstr>
if (p == NULL) {
goto endFAIL;
}
@@ -749,7 +748,7 @@ slang_T *spell_load_file(char *fname, char *lang, slang_T *old_lp, bool silent)
break;
case SN_SYLLABLE:
- lp->sl_syllable = READ_STRING(fd, len); // <syllable>
+ lp->sl_syllable = read_string(fd, (size_t)len); // <syllable>
if (lp->sl_syllable == NULL) {
goto endFAIL;
}
@@ -838,19 +837,16 @@ endOK:
// Fill in the wordcount fields for a trie.
// Returns the total number of words.
-static void tree_count_words(const char_u *byts, idx_T *idxs)
+static void tree_count_words(const uint8_t *byts, idx_T *idxs)
{
- int depth;
idx_T arridx[MAXWLEN];
int curi[MAXWLEN];
- int c;
- idx_T n;
int wordcount[MAXWLEN];
arridx[0] = 0;
curi[0] = 1;
wordcount[0] = 0;
- depth = 0;
+ int depth = 0;
while (depth >= 0 && !got_int) {
if (curi[depth] > byts[arridx[depth]]) {
// Done all bytes at this node, go up one level.
@@ -863,10 +859,10 @@ static void tree_count_words(const char_u *byts, idx_T *idxs)
fast_breakcheck();
} else {
// Do one more byte at this node.
- n = arridx[depth] + curi[depth];
+ idx_T n = arridx[depth] + curi[depth];
curi[depth]++;
- c = byts[n];
+ int c = byts[n];
if (c == 0) {
// End of word, count it.
wordcount[depth]++;
@@ -891,40 +887,31 @@ static void tree_count_words(const char_u *byts, idx_T *idxs)
/// Load the .sug files for languages that have one and weren't loaded yet.
void suggest_load_files(void)
{
- langp_T *lp;
- slang_T *slang;
- char *dotp;
- FILE *fd;
char buf[MAXWLEN];
- int i;
- time_t timestamp;
- int wcount;
- int wordnr;
garray_T ga;
- int c;
// Do this for all languages that support sound folding.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (slang->sl_sugtime != 0 && !slang->sl_sugloaded) {
// Change ".spl" to ".sug" and open the file. When the file isn't
// found silently skip it. Do set "sl_sugloaded" so that we
// don't try again and again.
slang->sl_sugloaded = true;
- dotp = strrchr(slang->sl_fname, '.');
+ char *dotp = strrchr(slang->sl_fname, '.');
if (dotp == NULL || path_fnamecmp(dotp, ".spl") != 0) {
continue;
}
STRCPY(dotp, ".sug");
- fd = os_fopen(slang->sl_fname, "r");
+ FILE *fd = os_fopen(slang->sl_fname, "r");
if (fd == NULL) {
goto nextone;
}
// <SUGHEADER>: <fileID> <versionnr> <timestamp>
- for (i = 0; i < VIMSUGMAGICL; i++) {
+ for (int i = 0; i < VIMSUGMAGICL; i++) {
buf[i] = (char)getc(fd); // <fileID>
}
if (strncmp(buf, VIMSUGMAGIC, VIMSUGMAGICL) != 0) {
@@ -932,7 +919,7 @@ void suggest_load_files(void)
slang->sl_fname);
goto nextone;
}
- c = getc(fd); // <versionnr>
+ int c = getc(fd); // <versionnr>
if (c < VIMSUGVERSION) {
semsg(_("E779: Old .sug file, needs to be updated: %s"),
slang->sl_fname);
@@ -945,7 +932,7 @@ void suggest_load_files(void)
// Check the timestamp, it must be exactly the same as the one in
// the .spl file. Otherwise the word numbers won't match.
- timestamp = get8ctime(fd); // <timestamp>
+ time_t timestamp = get8ctime(fd); // <timestamp>
if (timestamp != slang->sl_sugtime) {
semsg(_("E781: .sug file doesn't match .spl file: %s"),
slang->sl_fname);
@@ -957,7 +944,7 @@ void suggest_load_files(void)
if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs,
false, 0) != 0) {
someerror:
- semsg(_("E782: error while reading .sug file: %s"),
+ semsg(_(e_error_while_reading_sug_file_str),
slang->sl_fname);
slang_clear_sug(slang);
goto nextone;
@@ -971,7 +958,7 @@ someerror:
slang->sl_sugbuf = open_spellbuf();
// <sugwcount>
- wcount = get4c(fd);
+ int wcount = get4c(fd);
if (wcount < 0) {
goto someerror;
}
@@ -979,14 +966,14 @@ someerror:
// Read all the wordnr lists into the buffer, one NUL terminated
// list per line.
ga_init(&ga, 1, 100);
- for (wordnr = 0; wordnr < wcount; wordnr++) {
+ for (int wordnr = 0; wordnr < wcount; wordnr++) {
ga.ga_len = 0;
- for (;;) {
+ while (true) {
c = getc(fd); // <sugline>
if (c < 0) {
goto someerror;
}
- GA_APPEND(char_u, &ga, (char_u)c);
+ GA_APPEND(uint8_t, &ga, (uint8_t)c);
if (c == NUL) {
break;
}
@@ -1000,8 +987,8 @@ someerror:
// Need to put word counts in the word tries, so that we can find
// a word by its number.
- tree_count_words((char_u *)slang->sl_fbyts, slang->sl_fidxs);
- tree_count_words((char_u *)slang->sl_sbyts, slang->sl_sidxs);
+ tree_count_words(slang->sl_fbyts, slang->sl_fidxs);
+ tree_count_words(slang->sl_sbyts, slang->sl_sidxs);
nextone:
if (fd != NULL) {
@@ -1017,10 +1004,9 @@ nextone:
// Returns NULL when the count is zero.
// Sets "*cntp" to SP_*ERROR when there is an error, length of the result
// otherwise.
-static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
+static char *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
{
int cnt = 0;
- char_u *str;
// read the length bytes, MSB first
for (int i = 0; i < cnt_bytes; i++) {
@@ -1036,7 +1022,7 @@ static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
if (cnt == 0) {
return NULL; // nothing to read, return NULL
}
- str = READ_STRING(fd, cnt);
+ char *str = read_string(fd, (size_t)cnt);
if (str == NULL) {
*cntp = SP_OTHERERROR;
}
@@ -1060,18 +1046,16 @@ static int read_region_section(FILE *fd, slang_T *lp, int len)
// Return SP_*ERROR flags.
static int read_charflags_section(FILE *fd)
{
- char *flags;
- char_u *fol;
int flagslen, follen;
// <charflagslen> <charflags>
- flags = (char *)read_cnt_string(fd, 1, &flagslen);
+ char *flags = read_cnt_string(fd, 1, &flagslen);
if (flagslen < 0) {
return flagslen;
}
// <folcharslen> <folchars>
- fol = read_cnt_string(fd, 2, &follen);
+ char *fol = read_cnt_string(fd, 2, &follen);
if (follen < 0) {
xfree(flags);
return follen;
@@ -1079,7 +1063,7 @@ static int read_charflags_section(FILE *fd)
// Set the word-char flags and fill SPELL_ISUPPER() table.
if (flags != NULL && fol != NULL) {
- set_spell_charflags((char_u *)flags, flagslen, (char *)fol);
+ set_spell_charflags(flags, flagslen, fol);
}
xfree(flags);
@@ -1129,10 +1113,9 @@ static int read_prefcond_section(FILE *fd, slang_T *lp)
// Return SP_*ERROR flags.
static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
{
- int cnt;
fromto_T *ftp;
- cnt = get2c(fd); // <repcount>
+ int cnt = get2c(fd); // <repcount>
if (cnt < 0) {
return SP_TRUNCERROR;
}
@@ -1143,14 +1126,14 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
for (; gap->ga_len < cnt; gap->ga_len++) {
int c;
ftp = &((fromto_T *)gap->ga_data)[gap->ga_len];
- ftp->ft_from = (char *)read_cnt_string(fd, 1, &c);
+ ftp->ft_from = read_cnt_string(fd, 1, &c);
if (c < 0) {
return c;
}
if (c == 0) {
return SP_FORMERROR;
}
- ftp->ft_to = (char *)read_cnt_string(fd, 1, &c);
+ ftp->ft_to = read_cnt_string(fd, 1, &c);
if (c <= 0) {
xfree(ftp->ft_from);
if (c < 0) {
@@ -1177,12 +1160,6 @@ static int read_rep_section(FILE *fd, garray_T *gap, int16_t *first)
// Return SP_*ERROR flags.
static int read_sal_section(FILE *fd, slang_T *slang)
{
- int cnt;
- garray_T *gap;
- salitem_T *smp;
- int ccnt;
- char_u *p;
-
slang->sl_sofo = false;
const int flags = getc(fd); // <salflags>
@@ -1196,12 +1173,12 @@ static int read_sal_section(FILE *fd, slang_T *slang)
slang->sl_rem_accents = true;
}
- cnt = get2c(fd); // <salcount>
+ int cnt = get2c(fd); // <salcount>
if (cnt < 0) {
return SP_TRUNCERROR;
}
- gap = &slang->sl_sal;
+ garray_T *gap = &slang->sl_sal;
ga_init(gap, sizeof(salitem_T), 10);
ga_grow(gap, cnt + 1);
@@ -1209,13 +1186,13 @@ static int read_sal_section(FILE *fd, slang_T *slang)
for (; gap->ga_len < cnt; gap->ga_len++) {
int c = NUL;
- smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
- ccnt = getc(fd); // <salfromlen>
+ salitem_T *smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
+ int ccnt = getc(fd); // <salfromlen>
if (ccnt < 0) {
return SP_TRUNCERROR;
}
- p = xmalloc((size_t)ccnt + 2);
- smp->sm_lead = (char *)p;
+ char *p = xmalloc((size_t)ccnt + 2);
+ smp->sm_lead = p;
// Read up to the first special char into sm_lead.
int i = 0;
@@ -1224,9 +1201,9 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (vim_strchr("0123456789(-<^$", c) != NULL) {
break;
}
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
- smp->sm_leadlen = (int)(p - (char_u *)smp->sm_lead);
+ smp->sm_leadlen = (int)(p - smp->sm_lead);
*p++ = NUL;
// Put (abc) chars in sm_oneof, if any.
@@ -1237,7 +1214,7 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (c == ')') {
break;
}
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
*p++ = NUL;
if (++i < ccnt) {
@@ -1248,22 +1225,22 @@ static int read_sal_section(FILE *fd, slang_T *slang)
}
// Any following chars go in sm_rules.
- smp->sm_rules = (char *)p;
+ smp->sm_rules = p;
if (i < ccnt) {
// store the char we got while checking for end of sm_lead
- *p++ = (char_u)c;
+ *p++ = (char)(uint8_t)c;
}
i++;
if (i < ccnt) {
SPELL_READ_NONNUL_BYTES( // <salfrom>
- (char *)p, (size_t)(ccnt - i), fd,
+ p, (size_t)(ccnt - i), fd,
xfree(smp->sm_lead));
p += (ccnt - i);
}
*p++ = NUL;
// <saltolen> <salto>
- smp->sm_to = (char *)read_cnt_string(fd, 1, &ccnt);
+ smp->sm_to = read_cnt_string(fd, 1, &ccnt);
if (ccnt < 0) {
xfree(smp->sm_lead);
return ccnt;
@@ -1275,7 +1252,7 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (smp->sm_oneof == NULL) {
smp->sm_oneof_w = NULL;
} else {
- smp->sm_oneof_w = mb_str2wide((char *)smp->sm_oneof);
+ smp->sm_oneof_w = mb_str2wide(smp->sm_oneof);
}
if (smp->sm_to == NULL) {
smp->sm_to_w = NULL;
@@ -1287,15 +1264,15 @@ static int read_sal_section(FILE *fd, slang_T *slang)
if (!GA_EMPTY(gap)) {
// Add one extra entry to mark the end with an empty sm_lead. Avoids
// that we need to check the index every time.
- smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
- p = xmalloc(1);
+ salitem_T *smp = &((salitem_T *)gap->ga_data)[gap->ga_len];
+ char *p = xmalloc(1);
p[0] = NUL;
- smp->sm_lead = (char *)p;
+ smp->sm_lead = p;
smp->sm_lead_w = mb_str2wide(smp->sm_lead);
smp->sm_leadlen = 0;
smp->sm_oneof = NULL;
smp->sm_oneof_w = NULL;
- smp->sm_rules = (char *)p;
+ smp->sm_rules = p;
smp->sm_to = NULL;
smp->sm_to_w = NULL;
gap->ga_len++;
@@ -1313,17 +1290,16 @@ static int read_words_section(FILE *fd, slang_T *lp, int len)
{
int done = 0;
int i;
- int c;
- char_u word[MAXWLEN];
+ uint8_t word[MAXWLEN];
while (done < len) {
// Read one word at a time.
for (i = 0;; i++) {
- c = getc(fd);
+ int c = getc(fd);
if (c == EOF) {
return SP_TRUNCERROR;
}
- word[i] = (char_u)c;
+ word[i] = (uint8_t)c;
if (word[i] == NUL) {
break;
}
@@ -1344,19 +1320,18 @@ static int read_words_section(FILE *fd, slang_T *lp, int len)
static int read_sofo_section(FILE *fd, slang_T *slang)
{
int cnt;
- char *from, *to;
int res;
slang->sl_sofo = true;
// <sofofromlen> <sofofrom>
- from = (char *)read_cnt_string(fd, 2, &cnt);
+ char *from = read_cnt_string(fd, 2, &cnt);
if (cnt < 0) {
return cnt;
}
// <sofotolen> <sofoto>
- to = (char *)read_cnt_string(fd, 2, &cnt);
+ char *to = read_cnt_string(fd, 2, &cnt);
if (cnt < 0) {
xfree(from);
return cnt;
@@ -1382,16 +1357,13 @@ static int read_sofo_section(FILE *fd, slang_T *slang)
static int read_compound(FILE *fd, slang_T *slang, int len)
{
int todo = len;
- int c;
- int atstart;
int cnt;
- garray_T *gap;
if (todo < 2) {
return SP_FORMERROR; // need at least two bytes
}
todo--;
- c = getc(fd); // <compmax>
+ int c = getc(fd); // <compmax>
if (c < 2) {
c = MAXWLEN;
}
@@ -1420,7 +1392,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
todo--;
slang->sl_compoptions = c;
- gap = &slang->sl_comppat;
+ garray_T *gap = &slang->sl_comppat;
c = get2c(fd); // <comppatcount>
if (c < 0) {
return SP_TRUNCERROR;
@@ -1429,7 +1401,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
ga_init(gap, sizeof(char *), c);
ga_grow(gap, c);
while (--c >= 0) {
- ((char **)(gap->ga_data))[gap->ga_len++] = (char *)read_cnt_string(fd, 1, &cnt);
+ ((char **)(gap->ga_data))[gap->ga_len++] = read_cnt_string(fd, 1, &cnt);
// <comppatlen> <comppattext>
if (cnt < 0) {
return cnt;
@@ -1465,12 +1437,12 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
uint8_t *crp = xmalloc((size_t)todo + 1);
slang->sl_comprules = crp;
- char_u *pp = (char_u *)pat;
+ char *pp = pat;
*pp++ = '^';
*pp++ = '\\';
*pp++ = '(';
- atstart = 1;
+ int atstart = 1;
while (todo-- > 0) {
c = getc(fd); // <compflags>
if (c == EOF) {
@@ -1481,7 +1453,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Add all flags to "sl_compallflags".
if (vim_strchr("?*+[]/", c) == NULL
&& !byte_in_str(slang->sl_compallflags, c)) {
- *ap++ = (char_u)c;
+ *ap++ = (uint8_t)c;
*ap = NUL;
}
@@ -1494,7 +1466,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
atstart = 0;
} else {
if (!byte_in_str(slang->sl_compstartflags, c)) {
- *cp++ = (char_u)c;
+ *cp++ = (uint8_t)c;
*cp = NUL;
}
if (atstart == 1) {
@@ -1509,7 +1481,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
XFREE_CLEAR(slang->sl_comprules);
crp = NULL;
} else {
- *crp++ = (char_u)c;
+ *crp++ = (uint8_t)c;
}
}
@@ -1521,7 +1493,7 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
if (c == '?' || c == '+' || c == '~') {
*pp++ = '\\'; // "a?" becomes "a\?", "a+" becomes "a\+"
}
- pp += utf_char2bytes(c, (char *)pp);
+ pp += utf_char2bytes(c, pp);
}
}
@@ -1545,10 +1517,10 @@ static int read_compound(FILE *fd, slang_T *slang, int len)
// Set the SOFOFROM and SOFOTO items in language "lp".
// Returns SP_*ERROR flags when there is something wrong.
-static int set_sofo(slang_T *lp, char *from, char *to)
+static int set_sofo(slang_T *lp, const char *from, const char *to)
{
- char *s;
- char *p;
+ const char *s;
+ const char *p;
// Use "sl_sal" as an array with 256 pointers to a list of wide
// characters. The index is the low byte of the character.
@@ -1563,7 +1535,7 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// First count the number of items for each list. Temporarily use
// sl_sal_first[] for this.
for (p = from, s = to; *p != NUL && *s != NUL;) {
- const int c = mb_cptr2char_adv((const char **)&p);
+ const int c = mb_cptr2char_adv(&p);
MB_CPTR_ADV(s);
if (c >= 256) {
lp->sl_sal_first[c & 0xff]++;
@@ -1586,8 +1558,8 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// list.
memset(lp->sl_sal_first, 0, sizeof(salfirst_T) * 256);
for (p = from, s = to; *p != NUL && *s != NUL;) {
- const int c = mb_cptr2char_adv((const char **)&p);
- const int i = mb_cptr2char_adv((const char **)&s);
+ const int c = mb_cptr2char_adv(&p);
+ const int i = mb_cptr2char_adv(&s);
if (c >= 256) {
// Append the from-to chars at the end of the list with
// the low byte.
@@ -1610,21 +1582,18 @@ static int set_sofo(slang_T *lp, char *from, char *to)
// Fill the first-index table for "lp".
static void set_sal_first(slang_T *lp)
{
- salfirst_T *sfirst;
- salitem_T *smp;
- int c;
garray_T *gap = &lp->sl_sal;
- sfirst = lp->sl_sal_first;
+ salfirst_T *sfirst = lp->sl_sal_first;
for (int i = 0; i < 256; i++) {
sfirst[i] = -1;
}
- smp = (salitem_T *)gap->ga_data;
+ salitem_T *smp = (salitem_T *)gap->ga_data;
for (int i = 0; i < gap->ga_len; i++) {
// Use the lowest byte of the first character. For latin1 it's
// the character, for other encodings it should differ for most
// characters.
- c = *smp[i].sm_lead_w & 0xff;
+ int c = *smp[i].sm_lead_w & 0xff;
if (sfirst[c] == -1) {
sfirst[c] = i;
@@ -1656,13 +1625,13 @@ static void set_sal_first(slang_T *lp)
// Turn a multi-byte string into a wide character string.
// Return it in allocated memory.
-static int *mb_str2wide(char *s)
+static int *mb_str2wide(const char *s)
{
int i = 0;
int *res = xmalloc(((size_t)mb_charlen(s) + 1) * sizeof(int));
- for (char *p = s; *p != NUL;) {
- res[i++] = mb_ptr2char_adv((const char **)&p);
+ for (const char *p = s; *p != NUL;) {
+ res[i++] = mb_ptr2char_adv(&p);
}
res[i] = NUL;
@@ -1677,21 +1646,17 @@ static int *mb_str2wide(char *s)
/// @param prefixcnt when "prefixtree" is true: prefix count
///
/// @return zero when OK, SP_ value for an error.
-static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxsp, bool prefixtree,
- int prefixcnt)
+static int spell_read_tree(FILE *fd, uint8_t **bytsp, int *bytsp_len, idx_T **idxsp,
+ bool prefixtree, int prefixcnt)
FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
- int idx;
- char *bp;
- idx_T *ip;
-
// The tree size was computed when writing the file, so that we can
// allocate it as one long block. <nodecount>
- long len = get4c(fd);
+ int len = get4c(fd);
if (len < 0) {
return SP_TRUNCERROR;
}
- if ((size_t)len >= SIZE_MAX / sizeof(int)) { // -V547
+ if ((size_t)len >= SIZE_MAX / sizeof(int)) {
// Invalid length, multiply with sizeof(int) would overflow.
return SP_FORMERROR;
}
@@ -1700,18 +1665,18 @@ static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxs
}
// Allocate the byte array.
- bp = xmalloc((size_t)len);
+ uint8_t *bp = xmalloc((size_t)len);
*bytsp = bp;
if (bytsp_len != NULL) {
*bytsp_len = len;
}
// Allocate the index array.
- ip = xcalloc((size_t)len, sizeof(*ip));
+ idx_T *ip = xcalloc((size_t)len, sizeof(*ip));
*idxsp = ip;
// Recursively read the tree and store it in the array.
- idx = read_tree_node(fd, (char_u *)bp, ip, (int)len, 0, prefixtree, prefixcnt);
+ int idx = read_tree_node(fd, bp, ip, len, 0, prefixtree, prefixcnt);
if (idx < 0) {
return idx;
}
@@ -1731,18 +1696,13 @@ static int spell_read_tree(FILE *fd, char **bytsp, long *bytsp_len, idx_T **idxs
/// @param startidx current index in "byts" and "idxs"
/// @param prefixtree true for reading PREFIXTREE
/// @param maxprefcondnr maximum for <prefcondnr>
-static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx_T startidx,
+static idx_T read_tree_node(FILE *fd, uint8_t *byts, idx_T *idxs, int maxidx, idx_T startidx,
bool prefixtree, int maxprefcondnr)
{
- int len;
- int i;
- int n;
idx_T idx = startidx;
- int c;
- int c2;
#define SHARED_MASK 0x8000000
- len = getc(fd); // <siblingcount>
+ int len = getc(fd); // <siblingcount>
if (len <= 0) {
return SP_TRUNCERROR;
}
@@ -1750,11 +1710,11 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
if (startidx + len >= maxidx) {
return SP_FORMERROR;
}
- byts[idx++] = (char_u)len;
+ byts[idx++] = (uint8_t)len;
// Read the byte values, flag/region bytes and shared indexes.
- for (i = 1; i <= len; i++) {
- c = getc(fd); // <byte>
+ for (int i = 1; i <= len; i++) {
+ int c = getc(fd); // <byte>
if (c < 0) {
return SP_TRUNCERROR;
}
@@ -1776,7 +1736,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c |= getc(fd); // <affixID>
- n = get2c(fd); // <prefcondnr>
+ int n = get2c(fd); // <prefcondnr>
if (n >= maxprefcondnr) {
return SP_FORMERROR;
}
@@ -1785,7 +1745,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
// Read flags and optional region and prefix ID. In
// idxs[] the flags go in the low two bytes, region above
// that and prefix ID above the region.
- c2 = c;
+ int c2 = c;
c = getc(fd); // <flags>
if (c2 == BY_FLAGS2) {
c = (getc(fd) << 8) + c; // <flags2>
@@ -1802,7 +1762,7 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c = 0;
} else { // c == BY_INDEX
// <nodeidx>
- n = get3c(fd);
+ int n = get3c(fd);
if (n < 0 || n >= maxidx) {
return SP_FORMERROR;
}
@@ -1810,20 +1770,19 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
c = getc(fd); // <xbyte>
}
}
- byts[idx++] = (char_u)c;
+ byts[idx++] = (uint8_t)c;
}
// Recursively read the children for non-shared siblings.
// Skip the end-of-word ones (zero byte value) and the shared ones (and
// remove SHARED_MASK)
- for (i = 1; i <= len; i++) {
+ for (int i = 1; i <= len; i++) {
if (byts[startidx + i] != 0) {
if (idxs[startidx + i] & SHARED_MASK) {
idxs[startidx + i] &= ~SHARED_MASK;
} else {
idxs[startidx + i] = idx;
- idx = read_tree_node(fd, byts, idxs, maxidx, idx,
- prefixtree, maxprefcondnr);
+ idx = read_tree_node(fd, byts, idxs, maxidx, idx, prefixtree, maxprefcondnr);
if (idx < 0) {
break;
}
@@ -1839,10 +1798,9 @@ static idx_T read_tree_node(FILE *fd, char_u *byts, idx_T *idxs, int maxidx, idx
/// @param added_word invoked through "zg"
static void spell_reload_one(char *fname, bool added_word)
{
- slang_T *slang;
bool didit = false;
- for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
+ for (slang_T *slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(fname, slang->sl_fname, false, true) == kEqualFiles) {
slang_clear(slang);
if (spell_load_file(fname, NULL, slang, false) == NULL) {
@@ -1857,7 +1815,7 @@ static void spell_reload_one(char *fname, bool added_word)
// When "zg" was used and the file wasn't loaded yet, should redo
// 'spelllang' to load it now.
if (added_word && !didit) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
}
}
@@ -1876,24 +1834,21 @@ static void spell_reload_one(char *fname, bool added_word)
// Tunable parameters for when the tree is compressed. Filled from the
// 'mkspellmem' option.
-static long compress_start = 30000; // memory / SBLOCKSIZE
-static long compress_inc = 100; // memory / SBLOCKSIZE
-static long compress_added = 500000; // word count
+static int compress_start = 30000; // memory / SBLOCKSIZE
+static int compress_inc = 100; // memory / SBLOCKSIZE
+static int compress_added = 500000; // word count
// Check the 'mkspellmem' option. Return FAIL if it's wrong.
// Sets "sps_flags".
int spell_check_msm(void)
{
char *p = p_msm;
- long start = 0;
- long incr = 0;
- long added = 0;
if (!ascii_isdigit(*p)) {
return FAIL;
}
// block count = (value * 1024) / SBLOCKSIZE (but avoid overflow)
- start = (getdigits_long(&p, true, 0) * 10) / (SBLOCKSIZE / 102);
+ int start = (getdigits_int(&p, true, 0) * 10) / (SBLOCKSIZE / 102);
if (*p != ',') {
return FAIL;
}
@@ -1901,7 +1856,7 @@ int spell_check_msm(void)
if (!ascii_isdigit(*p)) {
return FAIL;
}
- incr = (getdigits_long(&p, true, 0) * 102) / (SBLOCKSIZE / 10);
+ int incr = (getdigits_int(&p, true, 0) * 102) / (SBLOCKSIZE / 10);
if (*p != ',') {
return FAIL;
}
@@ -1909,7 +1864,7 @@ int spell_check_msm(void)
if (!ascii_isdigit(*p)) {
return FAIL;
}
- added = getdigits_long(&p, true, 0) * 1024;
+ int added = getdigits_int(&p, true, 0) * 1024;
if (*p != NUL) {
return FAIL;
}
@@ -1957,9 +1912,9 @@ static void spell_print_node(wordnode_T *node, int depth)
PRINTSOME(line1, depth, "(%d)", node->wn_nr, 0);
PRINTSOME(line2, depth, " ", 0, 0);
PRINTSOME(line3, depth, " ", 0, 0);
- msg((char_u *)line1);
- msg((char_u *)line2);
- msg((char_u *)line3);
+ msg(line1, 0);
+ msg(line2, 0);
+ msg(line3, 0);
} else {
node->wn_u1.index = true;
@@ -1983,9 +1938,9 @@ static void spell_print_node(wordnode_T *node, int depth)
}
if (node->wn_byte == NUL) {
- msg((char_u *)line1);
- msg((char_u *)line2);
- msg((char_u *)line3);
+ msg(line1, 0);
+ msg(line2, 0);
+ msg(line3, 0);
}
// do the children
@@ -2022,13 +1977,11 @@ static void spell_print_tree(wordnode_T *root)
// Returns an afffile_T, NULL for complete failure.
static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
{
- FILE *fd;
char rline[MAXLINELEN];
char *line;
char *pc = NULL;
#define MAXITEMCNT 30
char *(items[MAXITEMCNT]);
- int itemcnt;
char *p;
int lnum = 0;
affheader_T *cur_aff = NULL;
@@ -2038,13 +1991,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char *low = NULL;
char *fol = NULL;
char *upp = NULL;
- int do_rep;
- int do_repsal;
- int do_sal;
- int do_mapline;
bool found_map = false;
hashitem_T *hi;
- int l;
int compminlen = 0; // COMPOUNDMIN value
int compsylmax = 0; // COMPOUNDSYLMAX value
int compoptions = 0; // COMP_ flags
@@ -2057,7 +2005,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char *sofoto = NULL; // SOFOTO value
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return NULL;
@@ -2067,16 +2015,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
spell_message(spin, IObuff);
// Only do REP lines when not done in another .aff file already.
- do_rep = GA_EMPTY(&spin->si_rep);
+ int do_rep = GA_EMPTY(&spin->si_rep);
// Only do REPSAL lines when not done in another .aff file already.
- do_repsal = GA_EMPTY(&spin->si_repsal);
+ int do_repsal = GA_EMPTY(&spin->si_repsal);
// Only do SAL lines when not done in another .aff file already.
- do_sal = GA_EMPTY(&spin->si_sal);
+ int do_sal = GA_EMPTY(&spin->si_sal);
// Only do MAP lines when not done in another .aff file already.
- do_mapline = GA_EMPTY(&spin->si_map);
+ int do_mapline = GA_EMPTY(&spin->si_map);
// Allocate and init the afffile_T structure.
afffile_T *aff = getroom(spin, sizeof(*aff), true);
@@ -2099,7 +2047,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (spin->si_conv.vc_type != CONV_NONE) {
pc = string_convert(&spin->si_conv, rline, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %d: %s"),
+ smsg(0, _("Conversion failure for word in %s line %d: %s"),
fname, lnum, rline);
continue;
}
@@ -2111,7 +2059,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Split the line up in white separated items. Put a NUL after each
// item.
- itemcnt = 0;
+ int itemcnt = 0;
for (p = line;;) {
while (*p != NUL && (uint8_t)(*p) <= ' ') { // skip white space and CR/NL
p++;
@@ -2143,10 +2091,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > 0) {
if (is_aff_rule(items, itemcnt, "SET", 2) && aff->af_enc == NULL) {
// Setup for conversion from "ENC" to 'encoding'.
- aff->af_enc = enc_canonize((char *)items[1]);
+ aff->af_enc = enc_canonize(items[1]);
if (!spin->si_ascii
&& convert_setup(&spin->si_conv, aff->af_enc, p_enc) == FAIL) {
- smsg(_("Conversion in %s not supported: from %s to %s"),
+ smsg(0, _("Conversion in %s not supported: from %s to %s"),
fname, aff->af_enc, p_enc);
}
spin->si_conv.vc_fail = true;
@@ -2159,7 +2107,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (strcmp(items[1], "caplong") == 0) {
aff->af_flagtype = AFT_CAPLONG;
} else {
- smsg(_("Invalid value for FLAG in %s line %d: %s"),
+ smsg(0, _("Invalid value for FLAG in %s line %d: %s"),
fname, lnum, items[1]);
}
if (aff->af_rare != 0
@@ -2173,7 +2121,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| compflags != NULL
|| aff->af_suff.ht_used > 0
|| aff->af_pref.ht_used > 0) {
- smsg(_("FLAG after using flags in %s line %d: %s"),
+ smsg(0, _("FLAG after using flags in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (spell_info_item(items[0]) && itemcnt > 1) {
@@ -2225,14 +2173,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& aff->af_compforbid == 0) {
aff->af_compforbid = affitem2flag(aff->af_flagtype, items[1], fname, lnum);
if (aff->af_pref.ht_used > 0) {
- smsg(_("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"),
+ smsg(0,
+ _("Defining COMPOUNDFORBIDFLAG after PFX item may give wrong results in %s line %d"),
fname, lnum);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDPERMITFLAG", 2)
&& aff->af_comppermit == 0) {
aff->af_comppermit = affitem2flag(aff->af_flagtype, items[1], fname, lnum);
if (aff->af_pref.ht_used > 0) {
- smsg(_("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"),
+ smsg(0,
+ _("Defining COMPOUNDPERMITFLAG after PFX item may give wrong results in %s line %d"),
fname, lnum);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDFLAG", 2)
@@ -2246,16 +2196,16 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (is_aff_rule(items, itemcnt, "COMPOUNDRULES", 2)) {
// We don't use the count, but do check that it's a number and
// not COMPOUNDRULE mistyped.
- if (atoi((char *)items[1]) == 0) {
- smsg(_("Wrong COMPOUNDRULES value in %s line %d: %s"),
+ if (atoi(items[1]) == 0) {
+ smsg(0, _("Wrong COMPOUNDRULES value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDRULE", 2)) {
// Don't use the first rule if it is a number.
- if (compflags != NULL || *skipdigits((char *)items[1]) != NUL) {
+ if (compflags != NULL || *skipdigits(items[1]) != NUL) {
// Concatenate this string to previously defined ones,
// using a slash to separate them.
- l = (int)strlen(items[1]) + 1;
+ int l = (int)strlen(items[1]) + 1;
if (compflags != NULL) {
l += (int)strlen(compflags) + 1;
}
@@ -2269,23 +2219,23 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDWORDMAX", 2)
&& compmax == 0) {
- compmax = atoi((char *)items[1]);
+ compmax = atoi(items[1]);
if (compmax == 0) {
- smsg(_("Wrong COMPOUNDWORDMAX value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDWORDMAX value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDMIN", 2)
&& compminlen == 0) {
- compminlen = atoi((char *)items[1]);
+ compminlen = atoi(items[1]);
if (compminlen == 0) {
- smsg(_("Wrong COMPOUNDMIN value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDMIN value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDSYLMAX", 2)
&& compsylmax == 0) {
- compsylmax = atoi((char *)items[1]);
+ compsylmax = atoi(items[1]);
if (compsylmax == 0) {
- smsg(_("Wrong COMPOUNDSYLMAX value in %s line %d: %s"),
+ smsg(0, _("Wrong COMPOUNDSYLMAX value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDDUP", 1)) {
@@ -2297,8 +2247,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDTRIPLE", 1)) {
compoptions |= COMP_CHECKTRIPLE;
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 2)) {
- if (atoi((char *)items[1]) == 0) {
- smsg(_("Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"),
+ if (atoi(items[1]) == 0) {
+ smsg(0, _("Wrong CHECKCOMPOUNDPATTERN value in %s line %d: %s"),
fname, lnum, items[1]);
}
} else if (is_aff_rule(items, itemcnt, "CHECKCOMPOUNDPATTERN", 3)) {
@@ -2350,15 +2300,15 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// "S" flag on all but the last block, thus we check for that
// and store it in ah_follows.
xstrlcpy(key, items[1], AH_KEY_LEN);
- hi = hash_find(tp, (char *)key);
+ hi = hash_find(tp, key);
if (!HASHITEM_EMPTY(hi)) {
cur_aff = HI2AH(hi);
if (cur_aff->ah_combine != (*items[2] == 'Y')) {
- smsg(_("Different combining flag in continued affix block in %s line %d: %s"),
+ smsg(0, _("Different combining flag in continued affix block in %s line %d: %s"),
fname, lnum, items[1]);
}
if (!cur_aff->ah_follows) {
- smsg(_("Duplicate affix in %s line %d: %s"),
+ smsg(0, _("Duplicate affix in %s line %d: %s"),
fname, lnum, items[1]);
}
} else {
@@ -2376,9 +2326,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| cur_aff->ah_flag == aff->af_nosuggest
|| cur_aff->ah_flag == aff->af_needcomp
|| cur_aff->ah_flag == aff->af_comproot) {
- smsg(_("Affix also used for "
- "BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST "
- "in %s line %d: %s"),
+ smsg(0, _("Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST "
+ "in %s line %d: %s"),
fname, lnum, items[1]);
}
STRCPY(cur_aff->ah_key, items[1]);
@@ -2402,11 +2351,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > lasti
&& !aff->af_ignoreextra
&& *items[lasti] != '#') {
- smsg(_(e_afftrailing), fname, lnum, items[lasti]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[lasti]);
}
if (strcmp(items[2], "Y") != 0 && strcmp(items[2], "N") != 0) {
- smsg(_("Expected Y or N in %s line %d: %s"),
+ smsg(0, _("Expected Y or N in %s line %d: %s"),
fname, lnum, items[2]);
}
@@ -2427,14 +2376,13 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
}
}
- aff_todo = atoi((char *)items[3]);
+ aff_todo = atoi(items[3]);
} else if ((strcmp(items[0], "PFX") == 0
|| strcmp(items[0], "SFX") == 0)
&& aff_todo > 0
&& strcmp(cur_aff->ah_key, items[1]) == 0
&& itemcnt >= 5) {
affentry_T *aff_entry;
- bool upper = false;
int lasti = 5;
// Myspell allows extra text after the item, but that might
@@ -2443,7 +2391,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (itemcnt > lasti && *items[lasti] != '#'
&& (strcmp(items[lasti], "-") != 0
|| itemcnt != lasti + 1)) {
- smsg(_(e_afftrailing), fname, lnum, items[lasti]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[lasti]);
}
// New item for an affix letter.
@@ -2475,14 +2423,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
char buf[MAXLINELEN];
aff_entry->ae_cond = getroom_save(spin, items[4]);
- if (*items[0] == 'P') {
- sprintf(buf, "^%s", items[4]); // NOLINT(runtime/printf)
- } else {
- sprintf(buf, "%s$", items[4]); // NOLINT(runtime/printf)
- }
+ snprintf(buf, sizeof(buf), *items[0] == 'P' ? "^%s" : "%s$", items[4]);
aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING + RE_STRICT);
if (aff_entry->ae_prog == NULL) {
- smsg(_("Broken condition in %s line %d: %s"),
+ smsg(0, _("Broken condition in %s line %d: %s"),
fname, lnum, items[4]);
}
}
@@ -2493,6 +2437,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// COMPOUNDFORBIDFLAG and COMPOUNDPERMITFLAG.
if (*items[0] == 'P' && aff->af_pfxpostpone
&& aff_entry->ae_flags == NULL) {
+ bool upper = false;
// When the chop string is one lower-case letter and
// the add string ends in the upper-case letter we set
// the "upper" flag, clear "ae_chop" and remove the
@@ -2502,10 +2447,8 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& aff_entry->ae_add != NULL
&& aff_entry->ae_chop[utfc_ptr2len(aff_entry->ae_chop)] ==
NUL) {
- int c, c_up;
-
- c = utf_ptr2char(aff_entry->ae_chop);
- c_up = SPELL_TOUPPER(c);
+ int c = utf_ptr2char(aff_entry->ae_chop);
+ int c_up = SPELL_TOUPPER(c);
if (c_up != c
&& (aff_entry->ae_cond == NULL
|| utf_ptr2char(aff_entry->ae_cond) == c)) {
@@ -2524,7 +2467,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
onecap_copy(items[4], buf, true);
aff_entry->ae_cond = getroom_save(spin, buf);
if (aff_entry->ae_cond != NULL) {
- sprintf(buf, "^%s", aff_entry->ae_cond); // NOLINT(runtime/printf)
+ snprintf(buf, MAXLINELEN, "^%s", aff_entry->ae_cond);
vim_regfree(aff_entry->ae_prog);
aff_entry->ae_prog = vim_regcomp(buf, RE_MAGIC + RE_STRING);
}
@@ -2535,8 +2478,6 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (aff_entry->ae_chop == NULL) {
int idx;
- char_u **pp;
- int n;
// Find a previously used condition.
for (idx = spin->si_prefcond.ga_len - 1; idx >= 0; idx--) {
@@ -2548,9 +2489,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (idx < 0) {
// Not found, add a new condition.
idx = spin->si_prefcond.ga_len;
- pp = GA_APPEND_VIA_PTR(char_u *, &spin->si_prefcond);
- *pp = (aff_entry->ae_cond == NULL) ?
- NULL : (char_u *)getroom_save(spin, aff_entry->ae_cond);
+ char **pp = GA_APPEND_VIA_PTR(char *, &spin->si_prefcond);
+ *pp = (aff_entry->ae_cond == NULL)
+ ? NULL : getroom_save(spin, aff_entry->ae_cond);
}
// Add the prefix to the prefix tree.
@@ -2562,7 +2503,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// PFX_FLAGS is a negative number, so that
// tree_add_word() knows this is the prefix tree.
- n = PFX_FLAGS;
+ int n = PFX_FLAGS;
if (!cur_aff->ah_combine) {
n |= WFP_NC;
}
@@ -2575,7 +2516,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (aff_entry->ae_compforbid) {
n |= WFP_COMPFORBID;
}
- tree_add_word(spin, (char_u *)p, spin->si_prefroot, n,
+ tree_add_word(spin, p, spin->si_prefroot, n,
idx, cur_aff->ah_newID);
did_postpone_prefix = true;
}
@@ -2597,7 +2538,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
|| is_aff_rule(items, itemcnt, "REPSAL", 2)) {
// Ignore REP/REPSAL count
if (!isdigit((uint8_t)(*items[1]))) {
- smsg(_("Expected REP(SAL) count in %s line %d"),
+ smsg(0, _("Expected REP(SAL) count in %s line %d"),
fname, lnum);
}
} else if ((strcmp(items[0], "REP") == 0
@@ -2607,7 +2548,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Myspell ignores extra arguments, we require it starts with
// # to detect mistakes.
if (itemcnt > 3 && items[3][0] != '#') {
- smsg(_(e_afftrailing), fname, lnum, items[3]);
+ smsg(0, _(e_afftrailing), fname, lnum, items[3]);
}
if (items[0][3] == 'S' ? do_repsal : do_rep) {
// Replace underscore with space (can't include a space
@@ -2632,27 +2573,25 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// First line contains the count.
found_map = true;
if (!isdigit((uint8_t)(*items[1]))) {
- smsg(_("Expected MAP count in %s line %d"),
+ smsg(0, _("Expected MAP count in %s line %d"),
fname, lnum);
}
} else if (do_mapline) {
- int c;
-
// Check that every character appears only once.
for (p = items[1]; *p != NUL;) {
- c = mb_ptr2char_adv((const char **)&p);
+ int c = mb_ptr2char_adv((const char **)&p);
if ((!GA_EMPTY(&spin->si_map)
&& vim_strchr(spin->si_map.ga_data, c)
!= NULL)
|| vim_strchr(p, c) != NULL) {
- smsg(_("Duplicate character in MAP in %s line %d"),
+ smsg(0, _("Duplicate character in MAP in %s line %d"),
fname, lnum);
}
}
// We simply concatenate all the MAP strings, separated by
// slashes.
- ga_concat(&spin->si_map, (char *)items[1]);
+ ga_concat(&spin->si_map, items[1]);
ga_append(&spin->si_map, '/');
}
}
@@ -2671,7 +2610,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// when "to" is "_" it means empty
add_fromto(spin, &spin->si_sal, items[1],
strcmp(items[2], "_") == 0 ? ""
- : items[2]);
+ : items[2]);
}
}
} else if (is_aff_rule(items, itemcnt, "SOFOFROM", 2)
@@ -2681,16 +2620,14 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
&& sofoto == NULL) {
sofoto = getroom_save(spin, items[1]);
} else if (strcmp(items[0], "COMMON") == 0) {
- int i;
-
- for (i = 1; i < itemcnt; i++) {
- if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, (char *)items[i]))) {
+ for (int i = 1; i < itemcnt; i++) {
+ if (HASHITEM_EMPTY(hash_find(&spin->si_commonwords, items[i]))) {
p = xstrdup(items[i]);
hash_add(&spin->si_commonwords, p);
}
}
} else {
- smsg(_("Unrecognized or duplicate item in %s line %d: %s"),
+ smsg(0, _("Unrecognized or duplicate item in %s line %d: %s"),
fname, lnum, items[0]);
}
}
@@ -2722,7 +2659,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (compsylmax != 0) {
if (syllable == NULL) {
- smsg("%s", _("COMPOUNDSYLMAX used without SYLLABLE"));
+ smsg(0, "%s", _("COMPOUNDSYLMAX used without SYLLABLE"));
}
aff_check_number(spin->si_compsylmax, compsylmax, "COMPOUNDSYLMAX");
spin->si_compsylmax = compsylmax;
@@ -2740,11 +2677,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// Check that we didn't use too many renumbered flags.
if (spin->si_newcompID < spin->si_newprefID) {
if (spin->si_newcompID == 127 || spin->si_newcompID == 255) {
- msg(_("Too many postponed prefixes"));
+ msg(_("Too many postponed prefixes"), 0);
} else if (spin->si_newprefID == 0 || spin->si_newprefID == 127) {
- msg(_("Too many compound flags"));
+ msg(_("Too many compound flags"), 0);
} else {
- msg(_("Too many postponed prefixes and/or compound flags"));
+ msg(_("Too many postponed prefixes and/or compound flags"), 0);
}
}
@@ -2755,10 +2692,10 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
if (sofofrom != NULL || sofoto != NULL) {
if (sofofrom == NULL || sofoto == NULL) {
- smsg(_("Missing SOFO%s line in %s"),
+ smsg(0, _("Missing SOFO%s line in %s"),
sofofrom == NULL ? "FROM" : "TO", fname);
} else if (!GA_EMPTY(&spin->si_sal)) {
- smsg(_("Both SAL and SOFO lines in %s"), fname);
+ smsg(0, _("Both SAL and SOFO lines in %s"), fname);
} else {
aff_check_string(spin->si_sofofr, sofofrom, "SOFOFROM");
aff_check_string(spin->si_sofoto, sofoto, "SOFOTO");
@@ -2790,18 +2727,14 @@ static bool is_aff_rule(char **items, int itemcnt, char *rulename, int mincount)
// ae_flags to ae_comppermit and ae_compforbid.
static void aff_process_flags(afffile_T *affile, affentry_T *entry)
{
- char *p;
- char_u *prevp;
- unsigned flag;
-
if (entry->ae_flags != NULL
&& (affile->af_compforbid != 0 || affile->af_comppermit != 0)) {
- for (p = entry->ae_flags; *p != NUL;) {
- prevp = (char_u *)p;
- flag = get_affitem(affile->af_flagtype, &p);
+ for (char *p = entry->ae_flags; *p != NUL;) {
+ char *prevp = p;
+ unsigned flag = get_affitem(affile->af_flagtype, &p);
if (flag == affile->af_comppermit || flag == affile->af_compforbid) {
- STRMOVE(prevp, (char *)p);
- p = (char *)prevp;
+ STRMOVE(prevp, p);
+ p = prevp;
if (flag == affile->af_comppermit) {
entry->ae_comppermit = true;
} else {
@@ -2833,21 +2766,20 @@ static bool spell_info_item(char *s)
// returns zero for failure.
static unsigned affitem2flag(int flagtype, char *item, char *fname, int lnum)
{
- unsigned res;
char *p = item;
- res = get_affitem(flagtype, &p);
+ unsigned res = get_affitem(flagtype, &p);
if (res == 0) {
if (flagtype == AFT_NUM) {
- smsg(_("Flag is not a number in %s line %d: %s"),
+ smsg(0, _("Flag is not a number in %s line %d: %s"),
fname, lnum, item);
} else {
- smsg(_("Illegal flag in %s line %d: %s"),
+ smsg(0, _("Illegal flag in %s line %d: %s"),
fname, lnum, item);
}
}
if (*p != NUL) {
- smsg(_(e_affname), fname, lnum, item);
+ smsg(0, _(e_affname), fname, lnum, item);
return 0;
}
@@ -2889,44 +2821,38 @@ static unsigned get_affitem(int flagtype, char **pp)
/// they fit in one byte.
static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags)
{
- char *p;
- char *prevp;
- unsigned flag;
compitem_T *ci;
int id;
- int len;
- char_u *tp;
char key[AH_KEY_LEN];
- hashitem_T *hi;
// Make room for the old and the new compflags, concatenated with a / in
// between. Processing it makes it shorter, but we don't know by how
// much, thus allocate the maximum.
- len = (int)strlen(compflags) + 1;
+ int len = (int)strlen(compflags) + 1;
if (spin->si_compflags != NULL) {
len += (int)strlen(spin->si_compflags) + 1;
}
- p = getroom(spin, (size_t)len, false);
+ char *p = getroom(spin, (size_t)len, false);
if (spin->si_compflags != NULL) {
STRCPY(p, spin->si_compflags);
STRCAT(p, "/");
}
spin->si_compflags = p;
- tp = (char_u *)p + strlen(p);
+ uint8_t *tp = (uint8_t *)p + strlen(p);
for (p = compflags; *p != NUL;) {
if (vim_strchr("/?*+[]", (uint8_t)(*p)) != NULL) {
// Copy non-flag characters directly.
- *tp++ = (char_u)(*p++);
+ *tp++ = (uint8_t)(*p++);
} else {
// First get the flag number, also checks validity.
- prevp = p;
- flag = get_affitem(aff->af_flagtype, &p);
+ char *prevp = p;
+ unsigned flag = get_affitem(aff->af_flagtype, &p);
if (flag != 0) {
// Find the flag in the hashtable. If it was used before, use
// the existing ID. Otherwise add a new entry.
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&aff->af_comp, (char *)key);
+ hashitem_T *hi = hash_find(&aff->af_comp, key);
if (!HASHITEM_EMPTY(hi)) {
id = HI2CI(hi)->ci_newID;
} else {
@@ -2940,9 +2866,9 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags
id = spin->si_newcompID--;
} while (vim_strchr("/?*+[]\\-^", id) != NULL);
ci->ci_newID = id;
- hash_add(&aff->af_comp, (char *)ci->ci_key);
+ hash_add(&aff->af_comp, ci->ci_key);
}
- *tp++ = (char_u)id;
+ *tp++ = (uint8_t)id;
}
if (aff->af_flagtype == AFT_NUM && *p == ',') {
p++;
@@ -2968,17 +2894,14 @@ static void check_renumber(spellinfo_T *spin)
// Returns true if flag "flag" appears in affix list "afflist".
static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
{
- char *p;
- unsigned n;
-
switch (flagtype) {
case AFT_CHAR:
return vim_strchr(afflist, (int)flag) != NULL;
case AFT_CAPLONG:
case AFT_LONG:
- for (p = afflist; *p != NUL;) {
- n = (unsigned)mb_ptr2char_adv((const char **)&p);
+ for (char *p = afflist; *p != NUL;) {
+ unsigned n = (unsigned)mb_ptr2char_adv((const char **)&p);
if ((flagtype == AFT_LONG || (n >= 'A' && n <= 'Z'))
&& *p != NUL) {
n = (unsigned)mb_ptr2char_adv((const char **)&p) + (n << 16);
@@ -2990,10 +2913,10 @@ static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
break;
case AFT_NUM:
- for (p = afflist; *p != NUL;) {
+ for (char *p = afflist; *p != NUL;) {
int digits = getdigits_int(&p, true, 0);
assert(digits >= 0);
- n = (unsigned int)digits;
+ unsigned n = (unsigned)digits;
if (n == 0) {
n = ZERO_FLAG;
}
@@ -3013,7 +2936,7 @@ static bool flag_in_afflist(int flagtype, char *afflist, unsigned flag)
static void aff_check_number(int spinval, int affval, char *name)
{
if (spinval != 0 && spinval != affval) {
- smsg(_("%s value differs from what is used in another .aff file"),
+ smsg(0, _("%s value differs from what is used in another .aff file"),
name);
}
}
@@ -3022,7 +2945,7 @@ static void aff_check_number(int spinval, int affval, char *name)
static void aff_check_string(char *spinval, char *affval, char *name)
{
if (spinval != NULL && strcmp(spinval, affval) != 0) {
- smsg(_("%s value differs from what is used in another .aff file"),
+ smsg(0, _("%s value differs from what is used in another .aff file"),
name);
}
}
@@ -3059,22 +2982,16 @@ static bool sal_to_bool(char *s)
// Free the structure filled by spell_read_aff().
static void spell_free_aff(afffile_T *aff)
{
- hashtab_T *ht;
- hashitem_T *hi;
- int todo;
- affheader_T *ah;
- affentry_T *ae;
-
xfree(aff->af_enc);
// All this trouble to free the "ae_prog" items...
- for (ht = &aff->af_pref;; ht = &aff->af_suff) {
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0; hi++) {
+ for (hashtab_T *ht = &aff->af_pref;; ht = &aff->af_suff) {
+ int todo = (int)ht->ht_used;
+ for (hashitem_T *hi = ht->ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- ah = HI2AH(hi);
- for (ae = ah->ah_first; ae != NULL; ae = ae->ae_next) {
+ affheader_T *ah = HI2AH(hi);
+ for (affentry_T *ae = ah->ah_first; ae != NULL; ae = ae->ae_next) {
vim_regfree(ae->ae_prog);
}
}
@@ -3095,28 +3012,18 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
{
hashtab_T ht;
char line[MAXLINELEN];
- char_u *p;
- char_u *afflist;
- char_u store_afflist[MAXWLEN];
- int pfxlen;
- bool need_affix;
- char *dw;
- char_u *pc;
- char_u *w;
- int l;
- hash_T hash;
- hashitem_T *hi;
- FILE *fd;
+ char store_afflist[MAXWLEN];
+ char *pc;
+ char *w;
int lnum = 1;
int non_ascii = 0;
int retval = OK;
- char_u message[MAXLINELEN + MAXWLEN];
- int flags;
+ char message[MAXLINELEN + MAXWLEN];
int duplicate = 0;
Timestamp last_msg_time = 0;
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return FAIL;
@@ -3133,14 +3040,14 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
spin->si_msg_count = 999999;
// Read and ignore the first line: word count.
- if (vim_fgets((char *)line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite((char *)line))) {
+ if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) {
semsg(_("E760: No word count in %s"), fname);
}
// Read all the lines in the file one by one.
// The words are converted to 'encoding' here, before being added to
// the hashtable.
- while (!vim_fgets((char *)line, MAXLINELEN, fd) && !got_int) {
+ while (!vim_fgets(line, MAXLINELEN, fd) && !got_int) {
line_breakcheck();
lnum++;
if (line[0] == '#' || line[0] == '/') {
@@ -3148,8 +3055,8 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Remove CR, LF and white space from the end. White space halfway through
// the word is kept to allow multi-word terms like "et al.".
- l = (int)strlen(line);
- while (l > 0 && line[l - 1] <= ' ') {
+ int l = (int)strlen(line);
+ while (l > 0 && (uint8_t)line[l - 1] <= ' ') {
l--;
}
if (l == 0) {
@@ -3159,24 +3066,24 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
// Convert from "SET" to 'encoding' when needed.
if (spin->si_conv.vc_type != CONV_NONE) {
- pc = (char_u *)string_convert(&spin->si_conv, (char *)line, NULL);
+ pc = string_convert(&spin->si_conv, line, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %d: %s"),
+ smsg(0, _("Conversion failure for word in %s line %d: %s"),
fname, lnum, line);
continue;
}
w = pc;
} else {
pc = NULL;
- w = (char_u *)line;
+ w = line;
}
// Truncate the word at the "/", set "afflist" to what follows.
// Replace "\/" by "/" and "\\" by "\".
- afflist = NULL;
- for (p = w; *p != NUL; MB_PTR_ADV(p)) {
+ char *afflist = NULL;
+ for (char *p = w; *p != NUL; MB_PTR_ADV(p)) {
if (*p == '\\' && (p[1] == '\\' || p[1] == '/')) {
- STRMOVE(p, (char *)p + 1);
+ STRMOVE(p, p + 1);
} else if (*p == '/') {
*p = NUL;
afflist = p + 1;
@@ -3185,7 +3092,7 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Skip non-ASCII words when "spin->si_ascii" is true.
- if (spin->si_ascii && has_non_ascii((char *)w)) {
+ if (spin->si_ascii && has_non_ascii(w)) {
non_ascii++;
xfree(pc);
continue;
@@ -3197,11 +3104,11 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
spin->si_msg_count = 0;
if (os_time() > last_msg_time) {
last_msg_time = os_time();
- vim_snprintf((char *)message, sizeof(message),
- _("line %6d, word %6ld - %s"),
+ vim_snprintf(message, sizeof(message),
+ _("line %6d, word %6d - %s"),
lnum, spin->si_foldwcount + spin->si_keepwcount, w);
msg_start();
- msg_outtrans_long_attr((char *)message, 0);
+ msg_outtrans_long(message, 0);
msg_clr_eos();
msg_didout = false;
msg_col = 0;
@@ -3210,21 +3117,21 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
// Store the word in the hashtable to be able to find duplicates.
- dw = getroom_save(spin, (char *)w);
+ char *dw = getroom_save(spin, w);
if (dw == NULL) {
retval = FAIL;
xfree(pc);
break;
}
- hash = hash_hash(dw);
- hi = hash_lookup(&ht, (const char *)dw, strlen(dw), hash);
+ hash_T hash = hash_hash(dw);
+ hashitem_T *hi = hash_lookup(&ht, dw, strlen(dw), hash);
if (!HASHITEM_EMPTY(hi)) {
if (p_verbose > 0) {
- smsg(_("Duplicate word in %s line %d: %s"),
+ smsg(0, _("Duplicate word in %s line %d: %s"),
fname, lnum, dw);
} else if (duplicate == 0) {
- smsg(_("First duplicate word in %s line %d: %s"),
+ smsg(0, _("First duplicate word in %s line %d: %s"),
fname, lnum, dw);
}
duplicate++;
@@ -3232,51 +3139,51 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
hash_add_item(&ht, hi, dw, hash);
}
- flags = 0;
+ int flags = 0;
store_afflist[0] = NUL;
- pfxlen = 0;
- need_affix = false;
+ int pfxlen = 0;
+ bool need_affix = false;
if (afflist != NULL) {
// Extract flags from the affix list.
- flags |= get_affix_flags(affile, (char *)afflist);
+ flags |= get_affix_flags(affile, afflist);
if (affile->af_needaffix != 0
- && flag_in_afflist(affile->af_flagtype, (char *)afflist,
+ && flag_in_afflist(affile->af_flagtype, afflist,
affile->af_needaffix)) {
need_affix = true;
}
if (affile->af_pfxpostpone) {
// Need to store the list of prefix IDs with the word.
- pfxlen = get_pfxlist(affile, (char *)afflist, store_afflist);
+ pfxlen = get_pfxlist(affile, afflist, store_afflist);
}
if (spin->si_compflags != NULL) {
// Need to store the list of compound flags with the word.
// Concatenate them to the list of prefix IDs.
- get_compflags(affile, (char *)afflist, store_afflist + pfxlen);
+ get_compflags(affile, afflist, store_afflist + pfxlen);
}
}
// Add the word to the word tree(s).
if (store_word(spin, dw, flags, spin->si_region,
- (char *)store_afflist, need_affix) == FAIL) {
+ store_afflist, need_affix) == FAIL) {
retval = FAIL;
}
if (afflist != NULL) {
// Find all matching suffixes and add the resulting words.
// Additionally do matching prefixes that combine.
- if (store_aff_word(spin, dw, (char *)afflist, affile,
+ if (store_aff_word(spin, dw, afflist, affile,
&affile->af_suff, &affile->af_pref,
- CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) {
+ CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) {
retval = FAIL;
}
// Find all matching prefixes and add the resulting words.
- if (store_aff_word(spin, dw, (char *)afflist, affile,
+ if (store_aff_word(spin, dw, afflist, affile,
&affile->af_pref, NULL,
- CONDIT_SUF, flags, (char *)store_afflist, pfxlen) == FAIL) {
+ CONDIT_SUF, flags, store_afflist, pfxlen) == FAIL) {
retval = FAIL;
}
}
@@ -3285,10 +3192,10 @@ static int spell_read_dic(spellinfo_T *spin, char *fname, afffile_T *affile)
}
if (duplicate > 0) {
- smsg(_("%d duplicate word(s) in %s"), duplicate, fname);
+ smsg(0, _("%d duplicate word(s) in %s"), duplicate, fname);
}
if (spin->si_ascii && non_ascii > 0) {
- smsg(_("Ignored %d word(s) with non-ASCII characters in %s"),
+ smsg(0, _("Ignored %d word(s) with non-ASCII characters in %s"),
non_ascii, fname);
}
hash_clear(&ht);
@@ -3338,26 +3245,22 @@ static int get_affix_flags(afffile_T *affile, char *afflist)
// Used for PFXPOSTPONE.
// Put the resulting flags in "store_afflist[MAXWLEN]" with a terminating NUL
// and return the number of affixes.
-static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist)
+static int get_pfxlist(afffile_T *affile, char *afflist, char *store_afflist)
{
- char *p;
- char *prevp;
int cnt = 0;
- int id;
char key[AH_KEY_LEN];
- hashitem_T *hi;
- for (p = afflist; *p != NUL;) {
- prevp = p;
+ for (char *p = afflist; *p != NUL;) {
+ char *prevp = p;
if (get_affitem(affile->af_flagtype, &p) != 0) {
// A flag is a postponed prefix flag if it appears in "af_pref"
// and its ID is not zero.
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&affile->af_pref, (char *)key);
+ hashitem_T *hi = hash_find(&affile->af_pref, key);
if (!HASHITEM_EMPTY(hi)) {
- id = HI2AH(hi)->ah_newID;
+ int id = HI2AH(hi)->ah_newID;
if (id != 0) {
- store_afflist[cnt++] = (char_u)id;
+ store_afflist[cnt++] = (char)(uint8_t)id;
}
}
}
@@ -3373,22 +3276,19 @@ static int get_pfxlist(afffile_T *affile, char *afflist, char_u *store_afflist)
// Get the list of compound IDs from the affix list "afflist" that are used
// for compound words.
// Puts the flags in "store_afflist[]".
-static void get_compflags(afffile_T *affile, char *afflist, char_u *store_afflist)
+static void get_compflags(afffile_T *affile, char *afflist, char *store_afflist)
{
- char *p;
- char *prevp;
int cnt = 0;
char key[AH_KEY_LEN];
- hashitem_T *hi;
- for (p = afflist; *p != NUL;) {
- prevp = p;
+ for (char *p = afflist; *p != NUL;) {
+ char *prevp = p;
if (get_affitem(affile->af_flagtype, &p) != 0) {
// A flag is a compound flag if it appears in "af_comp".
xstrlcpy(key, prevp, (size_t)(p - prevp) + 1);
- hi = hash_find(&affile->af_comp, (char *)key);
+ hashitem_T *hi = hash_find(&affile->af_comp, key);
if (!HASHITEM_EMPTY(hi)) {
- store_afflist[cnt++] = (char_u)HI2CI(hi)->ci_newID;
+ store_afflist[cnt++] = (char)(uint8_t)HI2CI(hi)->ci_newID;
}
}
if (affile->af_flagtype == AFT_NUM && *p == ',') {
@@ -3418,28 +3318,19 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
hashtab_T *ht, hashtab_T *xht, int condit, int flags, char *pfxlist,
int pfxlen)
{
- int todo;
- hashitem_T *hi;
- affheader_T *ah;
affentry_T *ae;
char newword[MAXWLEN];
int retval = OK;
- int i, j;
- char *p;
- int use_flags;
- char *use_pfxlist;
- int use_pfxlen;
- bool need_affix;
- char_u store_afflist[MAXWLEN];
+ int j;
+ char store_afflist[MAXWLEN];
char pfx_pfxlist[MAXWLEN];
size_t wordlen = strlen(word);
- int use_condit;
- todo = (int)ht->ht_used;
- for (hi = ht->ht_array; todo > 0 && retval == OK; hi++) {
+ int todo = (int)ht->ht_used;
+ for (hashitem_T *hi = ht->ht_array; todo > 0 && retval == OK; hi++) {
if (!HASHITEM_EMPTY(hi)) {
todo--;
- ah = HI2AH(hi);
+ affheader_T *ah = HI2AH(hi);
// Check that the affix combines, if required, and that the word
// supports this affix.
@@ -3464,7 +3355,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
&& (ae->ae_chop == NULL
|| strlen(ae->ae_chop) < wordlen)
&& (ae->ae_prog == NULL
- || vim_regexec_prog(&ae->ae_prog, false, word, (colnr_T)0))
+ || vim_regexec_prog(&ae->ae_prog, false, word, 0))
&& (((condit & CONDIT_CFIX) == 0)
== ((condit & CONDIT_AFF) == 0
|| ae->ae_flags == NULL
@@ -3478,10 +3369,10 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
} else {
xstrlcpy(newword, ae->ae_add, MAXWLEN);
}
- p = word;
+ char *p = word;
if (ae->ae_chop != NULL) {
// Skip chop string.
- i = mb_charlen(ae->ae_chop);
+ int i = mb_charlen(ae->ae_chop);
for (; i > 0; i--) {
MB_PTR_ADV(p);
}
@@ -3492,8 +3383,8 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
xstrlcpy(newword, word, MAXWLEN);
if (ae->ae_chop != NULL) {
// Remove chop string.
- p = newword + strlen(newword);
- i = mb_charlen(ae->ae_chop);
+ char *p = newword + strlen(newword);
+ int i = mb_charlen(ae->ae_chop);
for (; i > 0; i--) {
MB_PTR_BACK(newword, p);
}
@@ -3504,11 +3395,11 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
}
}
- use_flags = flags;
- use_pfxlist = pfxlist;
- use_pfxlen = pfxlen;
- need_affix = false;
- use_condit = condit | CONDIT_COMB | CONDIT_AFF;
+ int use_flags = flags;
+ char *use_pfxlist = pfxlist;
+ int use_pfxlen = pfxlen;
+ bool need_affix = false;
+ int use_condit = condit | CONDIT_COMB | CONDIT_AFF;
if (ae->ae_flags != NULL) {
// Extract flags from the affix list.
use_flags |= get_affix_flags(affile, ae->ae_flags);
@@ -3539,11 +3430,11 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
} else {
use_pfxlen = 0;
}
- use_pfxlist = (char *)store_afflist;
+ use_pfxlist = store_afflist;
// Combine the prefix IDs. Avoid adding the
// same ID twice.
- for (i = 0; i < pfxlen; i++) {
+ for (int i = 0; i < pfxlen; i++) {
for (j = 0; j < use_pfxlen; j++) {
if (pfxlist[i] == use_pfxlist[j]) {
break;
@@ -3557,7 +3448,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
if (spin->si_compflags != NULL) {
// Get compound IDS from the affix list.
get_compflags(affile, ae->ae_flags,
- (char_u *)use_pfxlist + use_pfxlen);
+ use_pfxlist + use_pfxlen);
} else {
use_pfxlist[use_pfxlen] = NUL;
}
@@ -3565,7 +3456,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
// Combine the list of compound flags.
// Concatenate them to the prefix IDs list.
// Avoid adding the same ID twice.
- for (i = pfxlen; pfxlist[i] != NUL; i++) {
+ for (int i = pfxlen; pfxlist[i] != NUL; i++) {
for (j = use_pfxlen; use_pfxlist[j] != NUL; j++) {
if (pfxlist[i] == use_pfxlist[j]) {
break;
@@ -3624,7 +3515,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
if (store_aff_word(spin, newword, ae->ae_flags,
affile, &affile->af_suff, xht,
use_condit & (xht == NULL
- ? ~0 : ~CONDIT_SUF),
+ ? ~0 : ~CONDIT_SUF),
use_flags, use_pfxlist, pfxlen) == FAIL) {
retval = FAIL;
}
@@ -3660,21 +3551,16 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
// Read a file with a list of words.
static int spell_read_wordfile(spellinfo_T *spin, char *fname)
{
- FILE *fd;
- long lnum = 0;
+ linenr_T lnum = 0;
char rline[MAXLINELEN];
char *line;
- char_u *pc = NULL;
- char_u *p;
- int l;
+ char *pc = NULL;
int retval = OK;
bool did_word = false;
int non_ascii = 0;
- int flags;
- int regionmask;
// Open the file.
- fd = os_fopen(fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return FAIL;
@@ -3694,7 +3580,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
}
// Remove CR, LF and white space from the end.
- l = (int)strlen(rline);
+ int l = (int)strlen(rline);
while (l > 0 && (uint8_t)rline[l - 1] <= ' ') {
l--;
}
@@ -3706,13 +3592,13 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
// Convert from "/encoding={encoding}" to 'encoding' when needed.
xfree(pc);
if (spin->si_conv.vc_type != CONV_NONE) {
- pc = (char_u *)string_convert(&spin->si_conv, rline, NULL);
+ pc = string_convert(&spin->si_conv, rline, NULL);
if (pc == NULL) {
- smsg(_("Conversion failure for word in %s line %ld: %s"),
+ smsg(0, _("Conversion failure for word in %s line %" PRIdLINENR ": %s"),
fname, lnum, rline);
continue;
}
- line = (char *)pc;
+ line = pc;
} else {
pc = NULL;
line = rline;
@@ -3722,10 +3608,10 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
line++;
if (strncmp(line, "encoding=", 9) == 0) {
if (spin->si_conv.vc_type != CONV_NONE) {
- smsg(_("Duplicate /encoding= line ignored in %s line %ld: %s"),
+ smsg(0, _("Duplicate /encoding= line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
} else if (did_word) {
- smsg(_("/encoding= line after word ignored in %s line %ld: %s"),
+ smsg(0, _("/encoding= line after word ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
} else {
char *enc;
@@ -3735,7 +3621,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
enc = enc_canonize(line);
if (!spin->si_ascii
&& convert_setup(&spin->si_conv, enc, p_enc) == FAIL) {
- smsg(_("Conversion in %s not supported: from %s to %s"),
+ smsg(0, _("Conversion in %s not supported: from %s to %s"),
fname, line, p_enc);
}
xfree(enc);
@@ -3746,12 +3632,12 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
if (strncmp(line, "regions=", 8) == 0) {
if (spin->si_region_count > 1) {
- smsg(_("Duplicate /regions= line ignored in %s line %ld: %s"),
+ smsg(0, _("Duplicate /regions= line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line);
} else {
line += 8;
if (strlen(line) > MAXREGIONS * 2) {
- smsg(_("Too many regions in %s line %ld: %s"),
+ smsg(0, _("Too many regions in %s line %" PRIdLINENR ": %s"),
fname, lnum, line);
} else {
spin->si_region_count = (int)strlen(line) / 2;
@@ -3764,16 +3650,16 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
continue;
}
- smsg(_("/ line ignored in %s line %ld: %s"),
+ smsg(0, _("/ line ignored in %s line %" PRIdLINENR ": %s"),
fname, lnum, line - 1);
continue;
}
- flags = 0;
- regionmask = spin->si_region;
+ int flags = 0;
+ int regionmask = spin->si_region;
// Check for flags and region after a slash.
- p = (char_u *)vim_strchr(line, '/');
+ char *p = vim_strchr(line, '/');
if (p != NULL) {
*p++ = NUL;
while (*p != NUL) {
@@ -3783,21 +3669,21 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
flags |= WF_BANNED;
} else if (*p == '?') { // Rare word.
flags |= WF_RARE;
- } else if (ascii_isdigit(*p)) { // region number(s)
+ } else if (ascii_isdigit((uint8_t)(*p))) { // region number(s)
if ((flags & WF_REGION) == 0) { // first one
regionmask = 0;
}
flags |= WF_REGION;
- l = *p - '0';
+ l = (uint8_t)(*p) - '0';
if (l == 0 || l > spin->si_region_count) {
- smsg(_("Invalid region nr in %s line %ld: %s"),
+ smsg(0, _("Invalid region nr in %s line %" PRIdLINENR ": %s"),
fname, lnum, p);
break;
}
regionmask |= 1 << (l - 1);
} else {
- smsg(_("Unrecognized flags in %s line %ld: %s"),
+ smsg(0, _("Unrecognized flags in %s line %" PRIdLINENR ": %s"),
fname, lnum, p);
break;
}
@@ -3842,7 +3728,6 @@ static int spell_read_wordfile(spellinfo_T *spin, char *fname)
static void *getroom(spellinfo_T *spin, size_t len, bool align)
FUNC_ATTR_NONNULL_RET
{
- char_u *p;
sblock_T *bl = spin->si_blocks;
assert(len <= SBLOCKSIZE);
@@ -3862,7 +3747,7 @@ static void *getroom(spellinfo_T *spin, size_t len, bool align)
spin->si_blocks_cnt++;
}
- p = bl->sb_data + bl->sb_used;
+ char *p = bl->sb_data + bl->sb_used;
bl->sb_used += (int)len;
return p;
@@ -3880,10 +3765,8 @@ static char *getroom_save(spellinfo_T *spin, char *s)
// Free the list of allocated sblock_T.
static void free_blocks(sblock_T *bl)
{
- sblock_T *next;
-
while (bl != NULL) {
- next = bl->sb_next;
+ sblock_T *next = bl->sb_next;
xfree(bl);
bl = next;
}
@@ -3901,7 +3784,7 @@ static wordnode_T *wordtree_alloc(spellinfo_T *spin)
/// Control characters and trailing '/' are invalid. Space is OK.
static bool valid_spell_word(const char *word, const char *end)
{
- if (!utf_valid_string((char_u *)word, (char_u *)end)) {
+ if (!utf_valid_string(word, end)) {
return false;
}
for (const char *p = word; *p != NUL && p < end; p += utfc_ptr2len(p)) {
@@ -3929,7 +3812,7 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
{
int len = (int)strlen(word);
int ct = captype(word, word + len);
- char_u foldword[MAXWLEN];
+ char foldword[MAXWLEN];
int res = OK;
// Avoid adding illegal bytes to the word tree.
@@ -3937,8 +3820,8 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
return FAIL;
}
- (void)spell_casefold(curwin, word, len, (char *)foldword, MAXWLEN);
- for (const char_u *p = (char_u *)pfxlist; res == OK; p++) {
+ (void)spell_casefold(curwin, word, len, foldword, MAXWLEN);
+ for (const char *p = pfxlist; res == OK; p++) {
if (!need_affix || (p != NULL && *p != NUL)) {
res = tree_add_word(spin, foldword, spin->si_foldroot, ct | flags,
region, p == NULL ? 0 : *p);
@@ -3950,9 +3833,9 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
spin->si_foldwcount++;
if (res == OK && (ct == WF_KEEPCAP || (flags & WF_KEEPCAP))) {
- for (const char_u *p = (char_u *)pfxlist; res == OK; p++) {
+ for (const char *p = pfxlist; res == OK; p++) {
if (!need_affix || (p != NULL && *p != NUL)) {
- res = tree_add_word(spin, (char_u *)word, spin->si_keeproot, flags,
+ res = tree_add_word(spin, word, spin->si_keeproot, flags,
region, p == NULL ? 0 : *p);
}
if (p == NULL || *p == NUL) {
@@ -3968,26 +3851,23 @@ static int store_word(spellinfo_T *spin, char *word, int flags, int region, cons
// When "flags" < 0 we are adding to the prefix tree where "flags" is used for
// "rare" and "region" is the condition nr.
// Returns FAIL when out of memory.
-static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root, int flags,
+static int tree_add_word(spellinfo_T *spin, const char *word, wordnode_T *root, int flags,
int region, int affixID)
{
wordnode_T *node = root;
- wordnode_T *np;
- wordnode_T *copyp, **copyprev;
wordnode_T **prev = NULL;
- int i;
// Add each byte of the word to the tree, including the NUL at the end.
- for (i = 0;; i++) {
+ for (int i = 0;; i++) {
// When there is more than one reference to this node we need to make
// a copy, so that we can modify it. Copy the whole list of siblings
// (we don't optimize for a partly shared list of siblings).
if (node != NULL && node->wn_refs > 1) {
node->wn_refs--;
- copyprev = prev;
- for (copyp = node; copyp != NULL; copyp = copyp->wn_sibling) {
+ wordnode_T **copyprev = prev;
+ for (wordnode_T *copyp = node; copyp != NULL; copyp = copyp->wn_sibling) {
// Allocate a new node and copy the info.
- np = get_wordnode(spin);
+ wordnode_T *np = get_wordnode(spin);
if (np == NULL) {
return FAIL;
}
@@ -4021,7 +3901,7 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
// higher byte value. For zero bytes (end of word) the sorting is
// done on flags and then on affixID.
while (node != NULL
- && (node->wn_byte < word[i]
+ && (node->wn_byte < (uint8_t)word[i]
|| (node->wn_byte == NUL
&& (flags < 0
? node->wn_affixID < (unsigned)affixID
@@ -4035,18 +3915,18 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
node = *prev;
}
if (node == NULL
- || node->wn_byte != word[i]
+ || node->wn_byte != (uint8_t)word[i]
|| (word[i] == NUL
&& (flags < 0
|| spin->si_sugtree
|| node->wn_flags != (flags & WN_MASK)
|| node->wn_affixID != affixID))) {
// Allocate a new node.
- np = get_wordnode(spin);
+ wordnode_T *np = get_wordnode(spin);
if (np == NULL) {
return FAIL;
}
- np->wn_byte = word[i];
+ np->wn_byte = (uint8_t)word[i];
// If "node" is NULL this is a new child or the end of the sibling
// list: ref count is one. Otherwise use ref count of sibling and
@@ -4068,14 +3948,14 @@ static int tree_add_word(spellinfo_T *spin, const char_u *word, wordnode_T *root
if (word[i] == NUL) {
node->wn_flags = (uint16_t)flags;
node->wn_region |= (int16_t)region;
- node->wn_affixID = (char_u)affixID;
+ node->wn_affixID = (uint8_t)affixID;
break;
}
prev = &node->wn_child;
node = *prev;
}
#ifdef SPELL_PRINTTREE
- smsg((char_u *)"Added \"%s\"", word);
+ smsg(0, "Added \"%s\"", word);
spell_print_tree(root->wn_sibling);
#endif
@@ -4164,11 +4044,10 @@ static wordnode_T *get_wordnode(spellinfo_T *spin)
static int deref_wordnode(spellinfo_T *spin, wordnode_T *node)
FUNC_ATTR_NONNULL_ALL
{
- wordnode_T *np;
int cnt = 0;
if (--node->wn_refs == 0) {
- for (np = node; np != NULL; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
if (np->wn_child != NULL) {
cnt += deref_wordnode(spin, np->wn_child);
}
@@ -4195,7 +4074,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
FUNC_ATTR_NONNULL_ALL
{
hashtab_T ht;
- long tot = 0;
+ int tot = 0;
long perc;
// Skip the root itself, it's not actually used. The first sibling is the
@@ -4205,7 +4084,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
}
hash_init(&ht);
- const long n = node_compress(spin, root->wn_sibling, &ht, &tot);
+ const int n = node_compress(spin, root->wn_sibling, &ht, &tot);
#ifndef SPELL_PRINTTREE
if (spin->si_verbose || p_verbose > 2)
@@ -4219,7 +4098,7 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
perc = (tot - n) * 100 / tot;
}
vim_snprintf(IObuff, IOSIZE,
- _("Compressed %s of %ld nodes; %ld (%ld%%) remaining"),
+ _("Compressed %s of %d nodes; %d (%ld%%) remaining"),
name, tot, tot - n, perc);
spell_message(spin, IObuff);
}
@@ -4233,32 +4112,29 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
/// Returns the number of compressed nodes.
///
/// @param tot total count of nodes before compressing, incremented while going through the tree
-static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, long *tot)
+static int node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, int *tot)
FUNC_ATTR_NONNULL_ALL
{
- wordnode_T *np;
wordnode_T *tp;
wordnode_T *child;
- hash_T hash;
- hashitem_T *hi;
- long len = 0;
- unsigned nr, n;
- long compressed = 0;
+ int len = 0;
+ unsigned n;
+ int compressed = 0;
// Go through the list of siblings. Compress each child and then try
// finding an identical child to replace it.
// Note that with "child" we mean not just the node that is pointed to,
// but the whole list of siblings of which the child node is the first.
- for (np = node; np != NULL && !got_int; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL && !got_int; np = np->wn_sibling) {
len++;
if ((child = np->wn_child) != NULL) {
// Compress the child first. This fills hashkey.
compressed += node_compress(spin, child, ht, tot);
// Try to find an identical child.
- hash = hash_hash((char *)child->wn_u1.hashkey);
- hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey,
- strlen((char *)child->wn_u1.hashkey), hash);
+ hash_T hash = hash_hash((char *)child->wn_u1.hashkey);
+ hashitem_T *hi = hash_lookup(ht, (const char *)child->wn_u1.hashkey,
+ strlen((char *)child->wn_u1.hashkey), hash);
if (!HASHITEM_EMPTY(hi)) {
// There are children we encountered before with a hash value
// identical to the current child. Now check if there is one
@@ -4294,9 +4170,9 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo
// Make a hash key for the node and its siblings, so that we can quickly
// find a lookalike node. This must be done after compressing the sibling
// list, otherwise the hash key would become invalid by the compression.
- node->wn_u1.hashkey[0] = (char_u)len;
- nr = 0;
- for (np = node; np != NULL; np = np->wn_sibling) {
+ node->wn_u1.hashkey[0] = (uint8_t)len;
+ unsigned nr = 0;
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
if (np->wn_byte == NUL) {
// end node: use wn_flags, wn_region and wn_affixID
n = (unsigned)(np->wn_flags + (np->wn_region << 8) + (np->wn_affixID << 16));
@@ -4309,13 +4185,13 @@ static long node_compress(spellinfo_T *spin, wordnode_T *node, hashtab_T *ht, lo
// Avoid NUL bytes, it terminates the hash key.
n = nr & 0xff;
- node->wn_u1.hashkey[1] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[1] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 8) & 0xff;
- node->wn_u1.hashkey[2] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[2] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 16) & 0xff;
- node->wn_u1.hashkey[3] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[3] = n == 0 ? 1 : (uint8_t)n;
n = (nr >> 24) & 0xff;
- node->wn_u1.hashkey[4] = n == 0 ? 1 : (char_u)n;
+ node->wn_u1.hashkey[4] = n == 0 ? 1 : (uint8_t)n;
node->wn_u1.hashkey[5] = NUL;
// Check for CTRL-C pressed now and then.
@@ -4371,7 +4247,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <HEADER>: <fileID> <versionnr>
// <fileID>
size_t fwv = fwrite(VIMSPELLMAGIC, VIMSPELLMAGICL, 1, fd);
- if (fwv != (size_t)1) {
+ if (fwv != 1) {
// Catch first write error, don't try writing more.
goto theend;
}
@@ -4414,7 +4290,6 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// the table (avoids that it conflicts). File is shorter too.
if (!spin->si_ascii && !spin->si_add) {
char folchars[128 * 8];
- int flags;
putc(SN_CHARFLAGS, fd); // <sectionID>
putc(SNF_REQUIRED, fd); // <sectionflags>
@@ -4422,13 +4297,13 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// Form the <folchars> string first, we need to know its length.
size_t l = 0;
for (size_t i = 128; i < 256; i++) {
- l += (size_t)utf_char2bytes(spelltab.st_fold[i], (char *)folchars + l);
+ l += (size_t)utf_char2bytes(spelltab.st_fold[i], folchars + l);
}
put_bytes(fd, 1 + 128 + 2 + l, 4); // <sectionlen>
fputc(128, fd); // <charflagslen>
for (size_t i = 128; i < 256; i++) {
- flags = 0;
+ int flags = 0;
if (spelltab.st_isw[i]) {
flags |= CF_WORD;
}
@@ -4471,7 +4346,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// round 1: SN_REP section
// round 2: SN_SAL section (unless SN_SOFO is used)
// round 3: SN_REPSAL section
- for (unsigned int round = 1; round <= 3; round++) {
+ for (unsigned round = 1; round <= 3; round++) {
garray_T *gap;
if (round == 1) {
gap = &spin->si_rep;
@@ -4534,7 +4409,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <rep> : <repfromlen> <repfrom> <reptolen> <repto>
// <sal> : <salfromlen> <salfrom> <saltolen> <salto>
fromto_T *ftp = &((fromto_T *)gap->ga_data)[i];
- for (unsigned int rr = 1; rr <= 2; rr++) {
+ for (unsigned rr = 1; rr <= 2; rr++) {
char *p = rr == 1 ? ftp->ft_from : ftp->ft_to;
l = strlen(p);
assert(l < INT_MAX);
@@ -4571,7 +4446,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// round 1: count the bytes
// round 2: write the bytes
- for (unsigned int round = 1; round <= 2; round++) {
+ for (unsigned round = 1; round <= 2; round++) {
size_t todo;
size_t len = 0;
hashitem_T *hi;
@@ -4694,7 +4569,7 @@ static int write_vim_spell(spellinfo_T *spin, char *fname)
// <LWORDTREE> <KWORDTREE> <PREFIXTREE>
spin->si_memtot = 0;
- for (unsigned int round = 1; round <= 3; round++) {
+ for (unsigned round = 1; round <= 3; round++) {
wordnode_T *tree;
if (round == 1) {
tree = spin->si_foldroot->wn_sibling;
@@ -4730,7 +4605,7 @@ theend:
retval = FAIL;
}
- if (fwv != (size_t)1) {
+ if (fwv != 1) {
retval = FAIL;
}
if (retval == FAIL) {
@@ -4745,10 +4620,8 @@ theend:
// space.
static void clear_node(wordnode_T *node)
{
- wordnode_T *np;
-
if (node != NULL) {
- for (np = node; np != NULL; np = np->wn_sibling) {
+ for (wordnode_T *np = node; np != NULL; np = np->wn_sibling) {
np->wn_u1.index = 0;
np->wn_u2.wnode = NULL;
@@ -4906,7 +4779,6 @@ void ex_mkspell(exarg_T *eap)
static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
{
char *fname = NULL;
- int len;
slang_T *slang;
bool free_slang = false;
@@ -4953,7 +4825,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
goto theend;
}
- smsg(_("Number of words after soundfolding: %" PRId64),
+ smsg(0, _("Number of words after soundfolding: %" PRId64),
(int64_t)spin->si_spellbuf->b_ml.ml_line_count);
// Compress the soundfold trie.
@@ -4964,7 +4836,7 @@ static void spell_make_sugfile(spellinfo_T *spin, char *wfname)
// Make the file name by changing ".spl" to ".sug".
fname = xmalloc(MAXPATHL);
xstrlcpy(fname, wfname, MAXPATHL);
- len = (int)strlen(fname);
+ int len = (int)strlen(fname);
fname[len - 2] = 'u';
fname[len - 1] = 'g';
sug_write(spin, fname);
@@ -4981,15 +4853,10 @@ theend:
// Build the soundfold trie for language "slang".
static int sug_filltree(spellinfo_T *spin, slang_T *slang)
{
- char_u *byts;
- idx_T *idxs;
- int depth;
idx_T arridx[MAXWLEN];
int curi[MAXWLEN];
- char_u tword[MAXWLEN];
- char_u tsalword[MAXWLEN];
- int c;
- idx_T n;
+ char tword[MAXWLEN];
+ char tsalword[MAXWLEN];
unsigned words_done = 0;
int wordcount[MAXWLEN];
@@ -5000,15 +4867,18 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
spin->si_sugtree = true;
// Go through the whole case-folded tree, soundfold each word and put it
- // in the trie.
- byts = (char_u *)slang->sl_fbyts;
- idxs = slang->sl_fidxs;
+ // in the trie. Bail out if the tree is empty.
+ uint8_t *byts = slang->sl_fbyts;
+ idx_T *idxs = slang->sl_fidxs;
+ if (byts == NULL || idxs == NULL) {
+ return FAIL;
+ }
arridx[0] = 0;
curi[0] = 1;
wordcount[0] = 0;
- depth = 0;
+ int depth = 0;
while (depth >= 0 && !got_int) {
if (curi[depth] > byts[arridx[depth]]) {
// Done all bytes at this node, go up one level.
@@ -5021,14 +4891,14 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
line_breakcheck();
} else {
// Do one more byte at this node.
- n = arridx[depth] + curi[depth];
+ idx_T n = arridx[depth] + curi[depth];
curi[depth]++;
- c = byts[n];
+ int c = byts[n];
if (c == 0) {
// Sound-fold the word.
tword[depth] = NUL;
- spell_soundfold(slang, (char *)tword, true, (char *)tsalword);
+ spell_soundfold(slang, tword, true, tsalword);
// We use the "flags" field for the MSB of the wordnr,
// "region" for the LSB of the wordnr.
@@ -5053,7 +4923,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
}
} else {
// Normal char, go one level deeper.
- tword[depth++] = (char_u)c;
+ tword[depth++] = (char)(uint8_t)c;
arridx[depth] = idxs[n];
curi[depth] = 1;
wordcount[depth] = 0;
@@ -5061,7 +4931,7 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
}
}
- smsg(_("Total number of words: %d"), words_done);
+ smsg(0, _("Total number of words: %d"), words_done);
return OK;
}
@@ -5100,31 +4970,27 @@ static int sug_maketable(spellinfo_T *spin)
/// @param gap place to store line of numbers
static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, garray_T *gap)
{
- wordnode_T *p, *np;
int wordnr = startwordnr;
- int nr;
- int prev_nr;
- for (p = node; p != NULL; p = p->wn_sibling) {
+ for (wordnode_T *p = node; p != NULL; p = p->wn_sibling) {
if (p->wn_byte == NUL) {
gap->ga_len = 0;
- prev_nr = 0;
- for (np = p; np != NULL && np->wn_byte == NUL; np = np->wn_sibling) {
+ int prev_nr = 0;
+ for (wordnode_T *np = p; np != NULL && np->wn_byte == NUL; np = np->wn_sibling) {
ga_grow(gap, 10);
- nr = (np->wn_flags << 16) + (np->wn_region & 0xffff);
+ int nr = (np->wn_flags << 16) + (np->wn_region & 0xffff);
// Compute the offset from the previous nr and store the
// offset in a way that it takes a minimum number of bytes.
// It's a bit like utf-8, but without the need to mark
// following bytes.
nr -= prev_nr;
prev_nr += nr;
- gap->ga_len += offset2bytes(nr,
- (char_u *)gap->ga_data + gap->ga_len);
+ gap->ga_len += offset2bytes(nr, (char *)gap->ga_data + gap->ga_len);
}
// add the NUL byte
- ((char_u *)gap->ga_data)[gap->ga_len++] = NUL;
+ ((char *)gap->ga_data)[gap->ga_len++] = NUL;
if (ml_append_buf(spin->si_spellbuf, (linenr_T)wordnr,
gap->ga_data, gap->ga_len, true) == FAIL) {
@@ -5155,39 +5021,38 @@ static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, g
// Convert an offset into a minimal number of bytes.
// Similar to utf_char2byters, but use 8 bits in followup bytes and avoid NUL
// bytes.
-static int offset2bytes(int nr, char_u *buf)
+static int offset2bytes(int nr, char *buf_in)
{
- int rem;
- int b1, b2, b3, b4;
+ uint8_t *buf = (uint8_t *)buf_in;
// Split the number in parts of base 255. We need to avoid NUL bytes.
- b1 = nr % 255 + 1;
- rem = nr / 255;
- b2 = rem % 255 + 1;
+ int b1 = nr % 255 + 1;
+ int rem = nr / 255;
+ int b2 = rem % 255 + 1;
rem = rem / 255;
- b3 = rem % 255 + 1;
- b4 = rem / 255 + 1;
+ int b3 = rem % 255 + 1;
+ int b4 = rem / 255 + 1;
if (b4 > 1 || b3 > 0x1f) { // 4 bytes
- buf[0] = (char_u)(0xe0 + b4);
- buf[1] = (char_u)b3;
- buf[2] = (char_u)b2;
- buf[3] = (char_u)b1;
+ buf[0] = (uint8_t)(0xe0 + b4);
+ buf[1] = (uint8_t)b3;
+ buf[2] = (uint8_t)b2;
+ buf[3] = (uint8_t)b1;
return 4;
}
if (b3 > 1 || b2 > 0x3f) { // 3 bytes
- buf[0] = (char_u)(0xc0 + b3);
- buf[1] = (char_u)b2;
- buf[2] = (char_u)b1;
+ buf[0] = (uint8_t)(0xc0 + b3);
+ buf[1] = (uint8_t)b2;
+ buf[2] = (uint8_t)b1;
return 3;
}
if (b2 > 1 || b1 > 0x7f) { // 2 bytes
- buf[0] = (char_u)(0x80 + b2);
- buf[1] = (char_u)b1;
+ buf[0] = (uint8_t)(0x80 + b2);
+ buf[1] = (uint8_t)b1;
return 2;
}
// 1 byte
- buf[0] = (char_u)b1;
+ buf[0] = (uint8_t)b1;
return 1;
}
@@ -5206,7 +5071,7 @@ static void sug_write(spellinfo_T *spin, char *fname)
spell_message(spin, IObuff);
// <SUGHEADER>: <fileID> <versionnr> <timestamp>
- if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, (size_t)1, fd) != 1) { // <fileID>
+ if (fwrite(VIMSUGMAGIC, VIMSUGMAGICL, 1, fd) != 1) { // <fileID>
emsg(_(e_write));
goto theend;
}
@@ -5242,7 +5107,7 @@ static void sug_write(spellinfo_T *spin, char *fname)
for (linenr_T lnum = 1; lnum <= wcount; lnum++) {
// <sugline>: <sugnr> ... NUL
- char *line = ml_get_buf(spin->si_spellbuf, lnum, false);
+ char *line = ml_get_buf(spin->si_spellbuf, lnum);
size_t len = strlen(line) + 1;
if (fwrite(line, len, 1, fd) == 0) {
emsg(_(e_write));
@@ -5278,11 +5143,7 @@ theend:
static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool added_word)
{
char *fname = NULL;
- char **innames;
- int incount;
afffile_T *(afile[MAXREGIONS]);
- int i;
- int len;
bool error = false;
spellinfo_T spin;
@@ -5302,13 +5163,13 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// default: fnames[0] is output file, following are input files
// When "fcount" is 1 there is only one file.
- innames = &fnames[fcount == 1 ? 0 : 1];
- incount = fcount - 1;
+ char **innames = &fnames[fcount == 1 ? 0 : 1];
+ int incount = fcount - 1;
char *wfname = xmalloc(MAXPATHL);
if (fcount >= 1) {
- len = (int)strlen(fnames[0]);
+ int len = (int)strlen(fnames[0]);
if (fcount == 1 && len > 4 && strcmp(fnames[0] + len - 4, ".add") == 0) {
// For ":mkspell path/en.latin1.add" output file is
// "path/en.latin1.add.spl".
@@ -5361,18 +5222,18 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// Init the aff and dic pointers.
// Get the region names if there are more than 2 arguments.
- for (i = 0; i < incount; i++) {
+ for (int i = 0; i < incount; i++) {
afile[i] = NULL;
if (incount > 1) {
- len = (int)strlen(innames[i]);
+ int len = (int)strlen(innames[i]);
if (strlen(path_tail(innames[i])) < 5
|| innames[i][len - 3] != '_') {
semsg(_("E755: Invalid region in %s"), innames[i]);
goto theend;
}
- spin.si_region_name[i * 2] = (char_u)TOLOWER_ASC(innames[i][len - 2]);
- spin.si_region_name[i * 2 + 1] = (char_u)TOLOWER_ASC(innames[i][len - 1]);
+ spin.si_region_name[i * 2] = (char)(uint8_t)TOLOWER_ASC(innames[i][len - 2]);
+ spin.si_region_name[i * 2 + 1] = (char)(uint8_t)TOLOWER_ASC(innames[i][len - 1]);
}
}
spin.si_region_count = incount;
@@ -5393,7 +5254,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
// Read all the .aff and .dic files.
// Text is converted to 'encoding'.
// Words are stored in the case-folded and keep-case trees.
- for (i = 0; i < incount && !error; i++) {
+ for (int i = 0; i < incount && !error; i++) {
spin.si_conv.vc_type = CONV_NONE;
spin.si_region = 1 << i;
@@ -5424,7 +5285,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
}
if (spin.si_compflags != NULL && spin.si_nobreak) {
- msg(_("Warning: both compounding and NOBREAK specified"));
+ msg(_("Warning: both compounding and NOBREAK specified"), 0);
}
if (!error && !got_int) {
@@ -5464,7 +5325,7 @@ static void mkspell(int fcount, char **fnames, bool ascii, bool over_write, bool
hash_clear_all(&spin.si_commonwords, 0);
// Free the .aff file structures.
- for (i = 0; i < incount; i++) {
+ for (int i = 0; i < incount; i++) {
if (afile[i] != NULL) {
spell_free_aff(afile[i]);
}
@@ -5494,7 +5355,7 @@ static void spell_message(const spellinfo_T *spin, char *str)
if (!spin->si_verbose) {
verbose_enter();
}
- msg(str);
+ msg(str, 0);
ui_flush();
if (!spin->si_verbose) {
verbose_leave();
@@ -5509,8 +5370,9 @@ static void spell_message(const spellinfo_T *spin, char *str)
void ex_spell(exarg_T *eap)
{
spell_add_word(eap->arg, (int)strlen(eap->arg),
- eap->cmdidx == CMD_spellwrong ? SPELL_ADD_BAD :
- eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
+ eap->cmdidx == CMD_spellwrong
+ ? SPELL_ADD_BAD
+ : eap->cmdidx == CMD_spellrare ? SPELL_ADD_RARE : SPELL_ADD_GOOD,
eap->forceit ? 0 : (int)eap->line2,
eap->cmdidx == CMD_spellundo);
}
@@ -5528,8 +5390,6 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
char *fname;
char *fnamebuf = NULL;
char line[MAXWLEN * 2];
- long fpos, fpos_next = 0;
- int i;
char *spf;
if (!valid_spell_word(word, word + len)) {
@@ -5546,6 +5406,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
}
fname = int_wordlist;
} else {
+ int i;
// If 'spellfile' isn't set figure out a good default value.
if (*curwin->w_s->b_p_spf == NUL) {
init_spellfile();
@@ -5585,13 +5446,15 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
}
if (what == SPELL_ADD_BAD || undo) {
+ int fpos_next = 0;
+ int fpos = 0;
// When the word appears as good word we need to remove that one,
// since its flags sort before the one with WF_BANNED.
fd = os_fopen(fname, "r");
if (fd != NULL) {
- while (!vim_fgets((char *)line, MAXWLEN * 2, fd)) {
+ while (!vim_fgets(line, MAXWLEN * 2, fd)) {
fpos = fpos_next;
- fpos_next = ftell(fd);
+ fpos_next = (int)ftell(fd);
if (fpos_next < 0) {
break; // should never happen
}
@@ -5609,7 +5472,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
fputc('#', fd);
if (undo) {
home_replace(NULL, fname, NameBuff, MAXPATHL, true);
- smsg(_("Word '%.*s' removed from %s"), len, word, NameBuff);
+ smsg(0, _("Word '%.*s' removed from %s"), len, word, NameBuff);
}
}
if (fseek(fd, fpos_next, SEEK_SET) != 0) {
@@ -5627,21 +5490,21 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
if (!undo) {
fd = os_fopen(fname, "a");
if (fd == NULL && new_spf) {
- char_u *p;
+ char *p;
// We just initialized the 'spellfile' option and can't open the
// file. We may need to create the "spell" directory first. We
// already checked the runtime directory is writable in
// init_spellfile().
if (!dir_of_file_exists(fname)
- && (p = (char_u *)path_tail_with_sep(fname)) != (char_u *)fname) {
- int c = *p;
+ && (p = path_tail_with_sep(fname)) != fname) {
+ char c = *p;
// The directory doesn't exist. Try creating it and opening
// the file again.
*p = NUL;
os_mkdir(fname, 0755);
- *p = (char_u)c;
+ *p = c;
fd = os_fopen(fname, "a");
}
}
@@ -5659,7 +5522,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
fclose(fd);
home_replace(NULL, fname, NameBuff, MAXPATHL, true);
- smsg(_("Word '%.*s' added to %s"), len, word, NameBuff);
+ smsg(0, _("Word '%.*s' added to %s"), len, word, NameBuff);
}
}
@@ -5680,10 +5543,7 @@ void spell_add_word(char *word, int len, SpellAddType what, int idx, bool undo)
// Initialize 'spellfile' for the current buffer.
static void init_spellfile(void)
{
- char *buf;
int l;
- char *fname;
- char *rtp;
char *lend;
bool aspath = false;
char *lstart = curbuf->b_s.b_p_spl;
@@ -5692,7 +5552,7 @@ static void init_spellfile(void)
return;
}
- buf = xmalloc(MAXPATHL);
+ char *buf = xmalloc(MAXPATHL);
// Find the end of the language name. Exclude the region. If there
// is a path separator remember the start of the tail.
@@ -5706,7 +5566,7 @@ static void init_spellfile(void)
// Loop over all entries in 'runtimepath'. Use the first one where we
// are allowed to write.
- rtp = p_rtp;
+ char *rtp = p_rtp;
while (*rtp != NUL) {
if (aspath) {
// Use directory of an entry with path, e.g., for
@@ -5734,14 +5594,14 @@ static void init_spellfile(void)
"/%.*s", (int)(lend - lstart), lstart);
}
l = (int)strlen(buf);
- fname = LANGP_ENTRY(curwin->w_s->b_langp, 0)
- ->lp_slang->sl_fname;
+ char *fname = LANGP_ENTRY(curwin->w_s->b_langp, 0)
+ ->lp_slang->sl_fname;
vim_snprintf(buf + l, MAXPATHL - (size_t)l, ".%s.add",
((fname != NULL
&& strstr(path_tail(fname), ".ascii.") != NULL)
? "ascii"
- : (const char *)spell_enc()));
- set_option_value_give_err("spellfile", 0L, buf, OPT_LOCAL);
+ : spell_enc()));
+ set_option_value_give_err("spellfile", CSTR_AS_OPTVAL(buf), OPT_LOCAL);
break;
}
aspath = false;
@@ -5753,28 +5613,27 @@ static void init_spellfile(void)
/// Set the spell character tables from strings in the .spl file.
///
/// @param cnt length of "flags"
-static void set_spell_charflags(const char_u *flags, int cnt, char *fol)
+static void set_spell_charflags(const char *flags_in, int cnt, const char *fol)
{
+ const uint8_t *flags = (uint8_t *)flags_in;
// We build the new tables here first, so that we can compare with the
// previous one.
spelltab_T new_st;
- int i;
- char *p = fol;
- int c;
+ const char *p = fol;
clear_spell_chartab(&new_st);
- for (i = 0; i < 128; i++) {
+ for (int i = 0; i < 128; i++) {
if (i < cnt) {
new_st.st_isw[i + 128] = (flags[i] & CF_WORD) != 0;
new_st.st_isu[i + 128] = (flags[i] & CF_UPPER) != 0;
}
if (*p != NUL) {
- c = mb_ptr2char_adv((const char **)&p);
- new_st.st_fold[i + 128] = (char_u)c;
+ int c = mb_ptr2char_adv(&p);
+ new_st.st_fold[i + 128] = (uint8_t)c;
if (i + 128 != c && new_st.st_isu[i + 128] && c < 256) {
- new_st.st_upper[c] = (char_u)(i + 128);
+ new_st.st_upper[c] = (uint8_t)(i + 128);
}
}
}
@@ -5784,11 +5643,9 @@ static void set_spell_charflags(const char_u *flags, int cnt, char *fol)
static int set_spell_finish(spelltab_T *new_st)
{
- int i;
-
if (did_set_spelltab) {
// check that it's the same table
- for (i = 0; i < 256; i++) {
+ for (int i = 0; i < 256; i++) {
if (spelltab.st_isw[i] != new_st->st_isw[i]
|| spelltab.st_isu[i] != new_st->st_isu[i]
|| spelltab.st_fold[i] != new_st->st_fold[i]
@@ -5837,12 +5694,9 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv)
}
// Use map string "map" for languages "lp".
-static void set_map_str(slang_T *lp, char *map)
+static void set_map_str(slang_T *lp, const char *map)
{
- char *p;
int headc = 0;
- int c;
- int i;
if (*map == NUL) {
lp->sl_has_map = false;
@@ -5851,7 +5705,7 @@ static void set_map_str(slang_T *lp, char *map)
lp->sl_has_map = true;
// Init the array and hash tables empty.
- for (i = 0; i < 256; i++) {
+ for (int i = 0; i < 256; i++) {
lp->sl_map_array[i] = 0;
}
hash_init(&lp->sl_map_hash);
@@ -5859,8 +5713,8 @@ static void set_map_str(slang_T *lp, char *map)
// The similar characters are stored separated with slashes:
// "aaa/bbb/ccc/". Fill sl_map_array[c] with the character before c and
// before the same slash. For characters above 255 sl_map_hash is used.
- for (p = map; *p != NUL;) {
- c = mb_cptr2char_adv((const char **)&p);
+ for (const char *p = map; *p != NUL;) {
+ int c = mb_cptr2char_adv(&p);
if (c == '/') {
headc = 0;
} else {
@@ -5883,13 +5737,13 @@ static void set_map_str(slang_T *lp, char *map)
utf_char2bytes(headc, b + cl + 1);
b[cl + 1 + headcl] = NUL;
hash = hash_hash(b);
- hi = hash_lookup(&lp->sl_map_hash, (const char *)b, strlen(b), hash);
+ hi = hash_lookup(&lp->sl_map_hash, b, strlen(b), hash);
if (HASHITEM_EMPTY(hi)) {
hash_add_item(&lp->sl_map_hash, hi, b, hash);
} else {
// This should have been checked when generating the .spl
// file.
- emsg(_("E783: duplicate char in MAP entry"));
+ emsg(_(e_duplicate_char_in_map_entry));
xfree(b);
}
} else {
diff --git a/src/nvim/spellfile.h b/src/nvim/spellfile.h
index 235bc07f61..3e68ddd5a4 100644
--- a/src/nvim/spellfile.h
+++ b/src/nvim/spellfile.h
@@ -1,13 +1,8 @@
-#ifndef NVIM_SPELLFILE_H
-#define NVIM_SPELLFILE_H
+#pragma once
-#include <stdbool.h>
-
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/spell_defs.h"
-#include "nvim/types.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/spell_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellfile.h.generated.h"
#endif
-#endif // NVIM_SPELLFILE_H
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index 54b6f552b5..d9dd28527e 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -1,53 +1,51 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// spellsuggest.c: functions for spelling suggestions
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/fileio.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/highlight_defs.h"
#include "nvim/input.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
-#include "nvim/screen.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
// Use this to adjust the score after finding suggestions, based on the
// suggested word sounding like the bad word. This is much faster than doing
@@ -74,9 +72,9 @@ typedef struct suginfo_S {
char *su_badptr; ///< start of bad word in line
int su_badlen; ///< length of detected bad word in line
int su_badflags; ///< caps flags for bad word
- char_u su_badword[MAXWLEN]; ///< bad word truncated at su_badlen
+ char su_badword[MAXWLEN]; ///< bad word truncated at su_badlen
char su_fbadword[MAXWLEN]; ///< su_badword case-folded
- char_u su_sal_badword[MAXWLEN]; ///< su_badword soundfolded
+ char su_sal_badword[MAXWLEN]; ///< su_badword soundfolded
hashtab_T su_banned; ///< table with banned words
slang_T *su_sallang; ///< default language for sound folding
} suginfo_T;
@@ -189,28 +187,28 @@ typedef enum {
/// Struct to keep the state at each level in suggest_try_change().
typedef struct trystate_S {
- state_T ts_state; ///< state at this level, STATE_
- int ts_score; ///< score
- idx_T ts_arridx; ///< index in tree array, start of node
- int16_t ts_curi; ///< index in list of child nodes
- char_u ts_fidx; ///< index in fword[], case-folded bad word
- char_u ts_fidxtry; ///< ts_fidx at which bytes may be changed
- char_u ts_twordlen; ///< valid length of tword[]
- char_u ts_prefixdepth; ///< stack depth for end of prefix or
- ///< PFD_PREFIXTREE or PFD_NOPREFIX
- char_u ts_flags; ///< TSF_ flags
- char_u ts_tcharlen; ///< number of bytes in tword character
- char_u ts_tcharidx; ///< current byte index in tword character
- char_u ts_isdiff; ///< DIFF_ values
- char_u ts_fcharstart; ///< index in fword where badword char started
- char_u ts_prewordlen; ///< length of word in "preword[]"
- char_u ts_splitoff; ///< index in "tword" after last split
- char_u ts_splitfidx; ///< "ts_fidx" at word split
- char_u ts_complen; ///< nr of compound words used
- char_u ts_compsplit; ///< index for "compflags" where word was spit
- char_u ts_save_badflags; ///< su_badflags saved here
- char_u ts_delidx; ///< index in fword for char that was deleted,
- ///< valid when "ts_flags" has TSF_DIDDEL
+ state_T ts_state; ///< state at this level, STATE_
+ int ts_score; ///< score
+ idx_T ts_arridx; ///< index in tree array, start of node
+ int16_t ts_curi; ///< index in list of child nodes
+ uint8_t ts_fidx; ///< index in fword[], case-folded bad word
+ uint8_t ts_fidxtry; ///< ts_fidx at which bytes may be changed
+ uint8_t ts_twordlen; ///< valid length of tword[]
+ uint8_t ts_prefixdepth; ///< stack depth for end of prefix or
+ ///< PFD_PREFIXTREE or PFD_NOPREFIX
+ uint8_t ts_flags; ///< TSF_ flags
+ uint8_t ts_tcharlen; ///< number of bytes in tword character
+ uint8_t ts_tcharidx; ///< current byte index in tword character
+ uint8_t ts_isdiff; ///< DIFF_ values
+ uint8_t ts_fcharstart; ///< index in fword where badword char started
+ uint8_t ts_prewordlen; ///< length of word in "preword[]"
+ uint8_t ts_splitoff; ///< index in "tword" after last split
+ uint8_t ts_splitfidx; ///< "ts_fidx" at word split
+ uint8_t ts_complen; ///< nr of compound words used
+ uint8_t ts_compsplit; ///< index for "compflags" where word was spit
+ uint8_t ts_save_badflags; ///< su_badflags saved here
+ uint8_t ts_delidx; ///< index in fword for char that was deleted,
+ ///< valid when "ts_flags" has TSF_DIDDEL
} trystate_T;
// values for ts_isdiff
@@ -234,7 +232,7 @@ enum {
PFD_NOTSPECIAL = 0xfd, // highest value that's not special
};
-static long spell_suggest_timeout = 5000;
+static int spell_suggest_timeout = 5000;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellsuggest.c.generated.h"
@@ -243,7 +241,7 @@ static long spell_suggest_timeout = 5000;
/// Returns true when the sequence of flags in "compflags" plus "flag" can
/// possibly form a valid compounded word. This also checks the COMPOUNDRULE
/// lines if they don't contain wildcards.
-static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag)
+static bool can_be_compound(trystate_T *sp, slang_T *slang, uint8_t *compflags, int flag)
{
// If the flag doesn't appear in sl_compstartflags or sl_compallflags
// then it can't possibly compound.
@@ -256,7 +254,7 @@ static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, i
// possibly can form a match with COMPOUNDRULE patterns. This only
// makes sense when we have two or more words.
if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) {
- compflags[sp->ts_complen] = (char_u)flag;
+ compflags[sp->ts_complen] = (uint8_t)flag;
compflags[sp->ts_complen + 1] = NUL;
bool v = match_compoundrule(slang, compflags + sp->ts_compsplit);
compflags[sp->ts_complen] = NUL;
@@ -269,18 +267,17 @@ static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, i
/// Adjust the score of common words.
///
/// @param split word was split, less bonus
-static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split)
+static int score_wordcount_adj(slang_T *slang, int score, char *word, bool split)
{
- wordcount_T *wc;
int bonus;
int newscore;
- hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word);
+ hashitem_T *hi = hash_find(&slang->sl_wordcount, word);
if (HASHITEM_EMPTY(hi)) {
return score;
}
- wc = HI2WC(hi);
+ wordcount_T *wc = HI2WC(hi);
if (wc->wc_count < SCORE_THRES2) {
bonus = SCORE_COMMON1;
} else if (wc->wc_count < SCORE_THRES3) {
@@ -302,24 +299,21 @@ static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool spl
/// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a
/// capital. So that make_case_word() can turn WOrd into Word.
/// Add ALLCAP for "WOrD".
-static int badword_captype(char_u *word, char_u *end)
+static int badword_captype(char *word, char *end)
FUNC_ATTR_NONNULL_ALL
{
- int flags = captype((char *)word, (char *)end);
- int c;
- int l, u;
- bool first;
- char_u *p;
+ int flags = captype(word, end);
if (!(flags & WF_KEEPCAP)) {
return flags;
}
// Count the number of UPPER and lower case letters.
- l = u = 0;
- first = false;
- for (p = word; p < end; MB_PTR_ADV(p)) {
- c = utf_ptr2char((char *)p);
+ int l = 0;
+ int u = 0;
+ bool first = false;
+ for (char *p = word; p < end; MB_PTR_ADV(p)) {
+ int c = utf_ptr2char(p);
if (SPELL_ISUPPER(c)) {
u++;
if (p == word) {
@@ -351,13 +345,12 @@ static int badword_captype(char_u *word, char_u *end)
/// "pp" points to the bytes and is advanced over it.
///
/// @return the offset.
-static int bytes2offset(char_u **pp)
+static int bytes2offset(char **pp)
{
- char_u *p = *pp;
+ uint8_t *p = (uint8_t *)(*pp);
int nr;
- int c;
- c = *p++;
+ int c = *p++;
if ((c & 0x80) == 0x00) { // 1 byte
nr = c - 1;
} else if ((c & 0xc0) == 0x80) { // 2 bytes
@@ -374,7 +367,7 @@ static int bytes2offset(char_u **pp)
nr = nr * 255 + (*p++ - 1);
}
- *pp = p;
+ *pp = (char *)p;
return nr;
}
@@ -392,24 +385,22 @@ static int sps_limit = 9999; ///< max nr of suggestions given
/// Sets "sps_flags" and "sps_limit".
int spell_check_sps(void)
{
- char *p;
- char *s;
char buf[MAXPATHL];
- int f;
sps_flags = 0;
sps_limit = 9999;
- for (p = p_sps; *p != NUL;) {
- copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+ for (char *p = p_sps; *p != NUL;) {
+ copy_option_part(&p, buf, MAXPATHL, ",");
- f = 0;
+ int f = 0;
if (ascii_isdigit(*buf)) {
- s = (char *)buf;
+ char *s = buf;
sps_limit = getdigits_int(&s, true, 0);
if (*s != NUL && !ascii_isdigit(*s)) {
f = -1;
}
+ // Note: Keep this in sync with p_sps_values.
} else if (strcmp(buf, "best") == 0) {
f = SPS_BEST;
} else if (strcmp(buf, "fast") == 0) {
@@ -447,15 +438,11 @@ int spell_check_sps(void)
/// When "count" is non-zero use that suggestion.
void spell_suggest(int count)
{
- char *line;
pos_T prev_cursor = curwin->w_cursor;
char wcopy[MAXWLEN + 2];
- char_u *p;
- int c;
suginfo_T sug;
suggest_T *stp;
int mouse_used;
- int need_cap;
int limit;
int selected = count;
int badlen = 0;
@@ -463,7 +450,7 @@ void spell_suggest(int count)
const int wo_spell_save = curwin->w_p_spell;
if (!curwin->w_p_spell) {
- did_set_spelllang(curwin);
+ parse_spelllang(curwin);
curwin->w_p_spell = true;
}
@@ -488,7 +475,7 @@ void spell_suggest(int count)
badlen++;
end_visual_mode();
// make sure we don't include the NUL at the end of the line
- line = get_cursor_line_ptr();
+ char *line = get_cursor_line_ptr();
if (badlen > (int)strlen(line) - (int)curwin->w_cursor.col) {
badlen = (int)strlen(line) - (int)curwin->w_cursor.col;
}
@@ -498,31 +485,31 @@ void spell_suggest(int count)
// No bad word or it starts after the cursor: use the word under the
// cursor.
curwin->w_cursor = prev_cursor;
- line = get_cursor_line_ptr();
- p = (char_u *)line + curwin->w_cursor.col;
+ char *line = get_cursor_line_ptr();
+ char *p = line + curwin->w_cursor.col;
// Backup to before start of word.
- while (p > (char_u *)line && spell_iswordp_nmw((char *)p, curwin)) {
+ while (p > line && spell_iswordp_nmw(p, curwin)) {
MB_PTR_BACK(line, p);
}
// Forward to start of word.
- while (*p != NUL && !spell_iswordp_nmw((char *)p, curwin)) {
+ while (*p != NUL && !spell_iswordp_nmw(p, curwin)) {
MB_PTR_ADV(p);
}
- if (!spell_iswordp_nmw((char *)p, curwin)) { // No word found.
+ if (!spell_iswordp_nmw(p, curwin)) { // No word found.
beep_flush();
return;
}
- curwin->w_cursor.col = (colnr_T)(p - (char_u *)line);
+ curwin->w_cursor.col = (colnr_T)(p - line);
}
// Get the word and its length.
// Figure out if the word should be capitalised.
- need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col);
+ int need_cap = check_need_cap(curwin, curwin->w_cursor.lnum, curwin->w_cursor.col);
// Make a copy of current line since autocommands may free the line.
- line = xstrdup(get_cursor_line_ptr());
+ char *line = xstrdup(get_cursor_line_ptr());
spell_suggest_timeout = 5000;
// Get the list of suggestions. Limit to 'lines' - 2 or the number in
@@ -532,36 +519,32 @@ void spell_suggest(int count)
} else {
limit = sps_limit;
}
- spell_find_suggest((char_u *)line + curwin->w_cursor.col, badlen, &sug, limit,
+ spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
true, need_cap, true);
if (GA_EMPTY(&sug.su_ga)) {
- msg(_("Sorry, no suggestions"));
+ msg(_("Sorry, no suggestions"), 0);
} else if (count > 0) {
if (count > sug.su_ga.ga_len) {
- smsg(_("Sorry, only %" PRId64 " suggestions"),
+ smsg(0, _("Sorry, only %" PRId64 " suggestions"),
(int64_t)sug.su_ga.ga_len);
}
} else {
// When 'rightleft' is set the list is drawn right-left.
cmdmsg_rl = curwin->w_p_rl;
- if (cmdmsg_rl) {
- msg_col = Columns - 1;
- }
// List the suggestions.
msg_start();
msg_row = Rows - 1; // for when 'cmdheight' > 1
lines_left = Rows; // avoid more prompt
- vim_snprintf(IObuff, IOSIZE, _("Change \"%.*s\" to:"),
- sug.su_badlen, sug.su_badptr);
- if (cmdmsg_rl && strncmp(IObuff, "Change", 6) == 0) {
+ char *fmt = _("Change \"%.*s\" to:");
+ if (cmdmsg_rl && strncmp(fmt, "Change", 6) == 0) {
// And now the rabbit from the high hat: Avoid showing the
// untranslated message rightleft.
- vim_snprintf(IObuff, IOSIZE, ":ot \"%.*s\" egnahC",
- sug.su_badlen, sug.su_badptr);
+ fmt = ":ot \"%.*s\" egnahC";
}
- msg_puts((const char *)IObuff);
+ vim_snprintf(IObuff, IOSIZE, fmt, sug.su_badlen, sug.su_badptr);
+ msg_puts(IObuff);
msg_clr_eos();
msg_putchar('\n');
@@ -578,18 +561,18 @@ void spell_suggest(int count)
}
vim_snprintf(IObuff, IOSIZE, "%2d", i + 1);
if (cmdmsg_rl) {
- rl_mirror(IObuff);
+ rl_mirror_ascii(IObuff, NULL);
}
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
vim_snprintf(IObuff, IOSIZE, " \"%s\"", wcopy);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
// The word may replace more than "su_badlen".
if (sug.su_badlen < stp->st_orglen) {
vim_snprintf(IObuff, IOSIZE, _(" < \"%.*s\""),
stp->st_orglen, sug.su_badptr);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
}
if (p_verbose > 0) {
@@ -604,10 +587,10 @@ void spell_suggest(int count)
}
if (cmdmsg_rl) {
// Mirror the numbers, but keep the leading space.
- rl_mirror(IObuff + 1);
+ rl_mirror_ascii(IObuff + 1, NULL);
}
msg_advance(30);
- msg_puts((const char *)IObuff);
+ msg_puts(IObuff);
}
msg_putchar('\n');
}
@@ -650,8 +633,8 @@ void spell_suggest(int count)
}
// Replace the word.
- p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
- c = (int)(sug.su_badptr - line);
+ char *p = xmalloc(strlen(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1);
+ int c = (int)(sug.su_badptr - line);
memmove(p, line, (size_t)c);
STRCPY(p + c, stp->st_word);
STRCAT(p, sug.su_badptr + stp->st_orglen);
@@ -659,12 +642,12 @@ void spell_suggest(int count)
// For redo we use a change-word command.
ResetRedobuff();
AppendToRedobuff("ciw");
- AppendToRedobuffLit((char *)p + c,
+ AppendToRedobuffLit(p + c,
stp->st_wordlen + sug.su_badlen - stp->st_orglen);
AppendCharToRedobuff(ESC);
// "p" may be freed here
- ml_replace(curwin->w_cursor.lnum, (char *)p, false);
+ ml_replace(curwin->w_cursor.lnum, p, false);
curwin->w_cursor.col = c;
inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen);
@@ -685,23 +668,21 @@ void spell_suggest(int count)
void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap, bool interactive)
{
suginfo_T sug;
- suggest_T *stp;
- char_u *wcopy;
- spell_find_suggest((char_u *)word, 0, &sug, maxcount, false, need_cap, interactive);
+ spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive);
// Make room in "gap".
- ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
+ ga_init(gap, sizeof(char *), sug.su_ga.ga_len + 1);
ga_grow(gap, sug.su_ga.ga_len);
for (int i = 0; i < sug.su_ga.ga_len; i++) {
- stp = &SUG(sug.su_ga, i);
+ suggest_T *stp = &SUG(sug.su_ga, i);
// The suggested word may replace only part of "word", add the not
// replaced part.
- wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1);
+ char *wcopy = xmalloc((size_t)stp->st_wordlen + strlen(sug.su_badptr + stp->st_orglen) + 1);
STRCPY(wcopy, stp->st_word);
STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen);
- ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy;
+ ((char **)gap->ga_data)[gap->ga_len++] = wcopy;
}
spell_find_cleanup(&sug);
@@ -716,17 +697,13 @@ void spell_suggest_list(garray_T *gap, char *word, int maxcount, bool need_cap,
/// @param badlen length of bad word or 0 if unknown
/// @param banbadword don't include badword in suggestions
/// @param need_cap word should start with capital
-static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount,
+static void spell_find_suggest(char *badptr, int badlen, suginfo_T *su, int maxcount,
bool banbadword, bool need_cap, bool interactive)
{
hlf_T attr = HLF_COUNT;
char buf[MAXPATHL];
- char *p;
bool do_combine = false;
- char *sps_copy;
static bool expr_busy = false;
- int c;
- langp_T *lp;
bool did_intern = false;
// Set the info in "*su".
@@ -738,7 +715,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
}
hash_init(&su->su_banned);
- su->su_badptr = (char *)badptr;
+ su->su_badptr = badptr;
if (badlen != 0) {
su->su_badlen = badlen;
} else {
@@ -752,7 +729,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
if (su->su_badlen >= MAXWLEN) {
su->su_badlen = MAXWLEN - 1; // just in case
}
- xstrlcpy((char *)su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1);
+ xstrlcpy(su->su_badword, su->su_badptr, (size_t)su->su_badlen + 1);
(void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
MAXWLEN);
@@ -762,8 +739,8 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
su->su_fbadword[su->su_badlen] = NUL;
// get caps flags for bad word
- su->su_badflags = badword_captype((char_u *)su->su_badptr,
- (char_u *)su->su_badptr + su->su_badlen);
+ su->su_badflags = badword_captype(su->su_badptr,
+ su->su_badptr + su->su_badlen);
if (need_cap) {
su->su_badflags |= WF_ONECAP;
}
@@ -773,7 +750,7 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
// using multiple files for one language, it's not that bad when mixing
// languages (e.g., "pl,en").
for (int i = 0; i < curbuf->b_s.b_langp.ga_len; i++) {
- lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
+ langp_T *lp = LANGP_ENTRY(curbuf->b_s.b_langp, i);
if (lp->lp_sallang != NULL) {
su->su_sallang = lp->lp_sallang;
break;
@@ -783,46 +760,46 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
// Soundfold the bad word with the default sound folding, so that we don't
// have to do this many times.
if (su->su_sallang != NULL) {
- spell_soundfold(su->su_sallang, (char *)su->su_fbadword, true,
- (char *)su->su_sal_badword);
+ spell_soundfold(su->su_sallang, su->su_fbadword, true,
+ su->su_sal_badword);
}
// If the word is not capitalised and spell_check() doesn't consider the
// word to be bad then it might need to be capitalised. Add a suggestion
// for that.
- c = utf_ptr2char(su->su_badptr);
+ int c = utf_ptr2char(su->su_badptr);
if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) {
- make_case_word((char *)su->su_badword, buf, WF_ONECAP);
- add_suggestion(su, &su->su_ga, (char *)buf, su->su_badlen, SCORE_ICASE,
+ make_case_word(su->su_badword, buf, WF_ONECAP);
+ add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
0, true, su->su_sallang, false);
}
// Ban the bad word itself. It may appear in another region.
if (banbadword) {
- add_banned(su, (char *)su->su_badword);
+ add_banned(su, su->su_badword);
}
// Make a copy of 'spellsuggest', because the expression may change it.
- sps_copy = xstrdup(p_sps);
+ char *sps_copy = xstrdup(p_sps);
// Loop over the items in 'spellsuggest'.
- for (p = sps_copy; *p != NUL;) {
- copy_option_part(&p, (char *)buf, MAXPATHL, ",");
+ for (char *p = sps_copy; *p != NUL;) {
+ copy_option_part(&p, buf, MAXPATHL, ",");
if (strncmp(buf, "expr:", 5) == 0) {
// Evaluate an expression. Skip this when called recursively,
// when using spellsuggest() in the expression.
if (!expr_busy) {
expr_busy = true;
- spell_suggest_expr(su, (char_u *)buf + 5);
+ spell_suggest_expr(su, buf + 5);
expr_busy = false;
}
} else if (strncmp(buf, "file:", 5) == 0) {
// Use list of suggestions in a file.
- spell_suggest_file(su, (char_u *)buf + 5);
+ spell_suggest_file(su, buf + 5);
} else if (strncmp(buf, "timeout:", 8) == 0) {
// Limit the time searching for suggestions.
- spell_suggest_timeout = atol((char *)buf + 8);
+ spell_suggest_timeout = atoi(buf + 8);
} else if (!did_intern) {
// Use internal method once.
spell_suggest_intern(su, interactive);
@@ -843,21 +820,20 @@ static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int ma
}
/// Find suggestions by evaluating expression "expr".
-static void spell_suggest_expr(suginfo_T *su, char_u *expr)
+static void spell_suggest_expr(suginfo_T *su, char *expr)
{
- int score;
const char *p;
// The work is split up in a few parts to avoid having to export
// suginfo_T.
// First evaluate the expression and get the resulting list.
- list_T *const list = eval_spell_expr((char *)su->su_badword, (char *)expr);
+ list_T *const list = eval_spell_expr(su->su_badword, expr);
if (list != NULL) {
// Loop over the items in the list.
TV_LIST_ITER(list, li, {
if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) {
// Get the word and the score from the items.
- score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
+ int score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p);
if (score >= 0 && score <= su->su_maxscore) {
add_suggestion(su, &su->su_ga, p, su->su_badlen,
score, 0, true, su->su_sallang, false);
@@ -873,43 +849,41 @@ static void spell_suggest_expr(suginfo_T *su, char_u *expr)
}
/// Find suggestions in file "fname". Used for "file:" in 'spellsuggest'.
-static void spell_suggest_file(suginfo_T *su, char_u *fname)
+static void spell_suggest_file(suginfo_T *su, char *fname)
{
- FILE *fd;
- char_u line[MAXWLEN * 2];
- char_u *p;
+ char line[MAXWLEN * 2];
int len;
- char_u cword[MAXWLEN];
+ char cword[MAXWLEN];
// Open the file.
- fd = os_fopen((char *)fname, "r");
+ FILE *fd = os_fopen(fname, "r");
if (fd == NULL) {
semsg(_(e_notopen), fname);
return;
}
// Read it line by line.
- while (!vim_fgets((char *)line, MAXWLEN * 2, fd) && !got_int) {
+ while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) {
line_breakcheck();
- p = (char_u *)vim_strchr((char *)line, '/');
+ char *p = vim_strchr(line, '/');
if (p == NULL) {
continue; // No Tab found, just skip the line.
}
*p++ = NUL;
if (STRICMP(su->su_badword, line) == 0) {
// Match! Isolate the good word, until CR or NL.
- for (len = 0; p[len] >= ' '; len++) {}
+ for (len = 0; (uint8_t)p[len] >= ' '; len++) {}
p[len] = NUL;
// If the suggestion doesn't have specific case duplicate the case
// of the bad word.
- if (captype((char *)p, NULL) == 0) {
- make_case_word((char *)p, (char *)cword, su->su_badflags);
+ if (captype(p, NULL) == 0) {
+ make_case_word(p, cword, su->su_badflags);
p = cword;
}
- add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
SCORE_FILE, 0, true, su->su_sallang, false);
}
}
@@ -1013,24 +987,23 @@ static void spell_find_cleanup(suginfo_T *su)
/// Try finding suggestions by recognizing specific situations.
static void suggest_try_special(suginfo_T *su)
{
- char c;
- char_u word[MAXWLEN];
+ char word[MAXWLEN];
// Recognize a word that is repeated: "the the".
- char *p = skiptowhite((char *)su->su_fbadword);
- size_t len = (size_t)(p - (char *)su->su_fbadword);
+ char *p = skiptowhite(su->su_fbadword);
+ size_t len = (size_t)(p - su->su_fbadword);
p = skipwhite(p);
if (strlen(p) == len && strncmp(su->su_fbadword, p, len) == 0) {
// Include badflags: if the badword is onecap or allcap
// use that for the goodword too: "The the" -> "The".
- c = su->su_fbadword[len];
+ char c = su->su_fbadword[len];
su->su_fbadword[len] = NUL;
- make_case_word(su->su_fbadword, (char *)word, su->su_badflags);
+ make_case_word(su->su_fbadword, word, su->su_badflags);
su->su_fbadword[len] = c;
// Give a soundalike score of 0, compute the score as if deleting one
// character.
- add_suggestion(su, &su->su_ga, (char *)word, su->su_badlen,
+ add_suggestion(su, &su->su_ga, word, su->su_badlen,
RESCORE(SCORE_REP, 0), 0, true, su->su_sallang, false);
}
}
@@ -1084,16 +1057,13 @@ static void prof_report(char *name)
static void suggest_try_change(suginfo_T *su)
{
char fword[MAXWLEN]; // copy of the bad word, case-folded
- int n;
- char *p;
- langp_T *lp;
// We make a copy of the case-folded bad word, so that we can modify it
// to find matches (esp. REP items). Append some more text, changing
// chars after the bad word may help.
STRCPY(fword, su->su_fbadword);
- n = (int)strlen(fword);
- p = su->su_badptr + su->su_badlen;
+ int n = (int)strlen(fword);
+ char *p = su->su_badptr + su->su_badlen;
(void)spell_casefold(curwin, p, (int)strlen(p), fword + n, MAXWLEN - n);
// Make sure the resulting text is not longer than the original text.
@@ -1103,7 +1073,7 @@ static void suggest_try_change(suginfo_T *su)
}
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
// If reloading a spell file fails it's still in the list but
// everything has been cleared.
@@ -1164,45 +1134,36 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// words and split word. NUL terminated
// when going deeper but not when coming
// back.
- char_u compflags[MAXWLEN]; // compound flags, one for each word
- trystate_T *sp;
- int newscore;
- int score;
- char_u *byts, *fbyts, *pbyts;
+ uint8_t compflags[MAXWLEN]; // compound flags, one for each word
+ uint8_t *byts, *fbyts, *pbyts;
idx_T *idxs, *fidxs, *pidxs;
- int depth;
int c, c2, c3;
int n = 0;
- int flags;
garray_T *gap;
idx_T arridx;
- int len;
- char *p;
- fromto_T *ftp;
- int fl = 0, tl;
+ int fl = 0;
+ int tl;
int repextra = 0; // extra bytes in fword[] from REP item
slang_T *slang = lp->lp_slang;
- int fword_ends;
bool goodword_ends;
#ifdef DEBUG_TRIEWALK
// Stores the name of the change made at each level.
- char_u changename[MAXWLEN][80];
+ uint8_t changename[MAXWLEN][80];
#endif
int breakcheckcount = 1000;
- bool compound_ok;
// Go through the whole case-fold tree, try changes at each node.
// "tword[]" contains the word collected from nodes in the tree.
// "fword[]" the word we are trying to match with (initially the bad
// word).
- depth = 0;
- sp = &stack[0];
- CLEAR_POINTER(sp); // -V1068
+ int depth = 0;
+ trystate_T *sp = &stack[0];
+ CLEAR_POINTER(sp);
sp->ts_curi = 1;
if (soundfold) {
// Going through the soundfold tree.
- byts = fbyts = (char_u *)slang->sl_sbyts;
+ byts = fbyts = slang->sl_sbyts;
idxs = fidxs = slang->sl_sidxs;
pbyts = NULL;
pidxs = NULL;
@@ -1211,9 +1172,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else {
// When there are postponed prefixes we need to use these first. At
// the end of the prefix we continue in the case-fold tree.
- fbyts = (char_u *)slang->sl_fbyts;
+ fbyts = slang->sl_fbyts;
fidxs = slang->sl_fidxs;
- pbyts = (char_u *)slang->sl_pbyts;
+ pbyts = slang->sl_pbyts;
pidxs = slang->sl_pidxs;
if (pbyts != NULL) {
byts = pbyts;
@@ -1230,7 +1191,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// The loop may take an indefinite amount of time. Break out after some
// time.
- proftime_T time_limit;
+ proftime_T time_limit = 0;
if (spell_suggest_timeout > 0) {
time_limit = profile_setlimit(spell_suggest_timeout);
}
@@ -1248,7 +1209,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Start of node: Deal with NUL bytes, which means
// tword[] may end here.
arridx = sp->ts_arridx; // current node in the tree
- len = byts[arridx]; // bytes in this node
+ int len = byts[arridx]; // bytes in this node
arridx += sp->ts_curi; // index of current byte
if (sp->ts_prefixdepth == PFD_PREFIXTREE) {
@@ -1260,7 +1221,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
n = (int)sp->ts_state;
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
// At end of a prefix or at start of prefixtree: check for
// following word.
@@ -1268,16 +1229,16 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Set su->su_badflags to the caps type at this position.
// Use the caps type until here for the prefix itself.
n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- flags = badword_captype((char_u *)su->su_badptr, (char_u *)su->su_badptr + n);
- su->su_badflags = badword_captype((char_u *)su->su_badptr + n,
- (char_u *)su->su_badptr + su->su_badlen);
+ int flags = badword_captype(su->su_badptr, su->su_badptr + n);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
#ifdef DEBUG_TRIEWALK
sprintf(changename[depth], "prefix"); // NOLINT(runtime/printf)
#endif
go_deeper(stack, depth, 0);
depth++;
sp = &stack[depth];
- sp->ts_prefixdepth = (char_u)(depth - 1);
+ sp->ts_prefixdepth = (uint8_t)(depth - 1);
byts = fbyts;
idxs = fidxs;
sp->ts_arridx = 0;
@@ -1287,7 +1248,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tword[sp->ts_twordlen] = NUL;
make_case_word(tword + sp->ts_splitoff,
preword + sp->ts_prewordlen, flags);
- sp->ts_prewordlen = (char_u)strlen(preword);
+ sp->ts_prewordlen = (uint8_t)strlen(preword);
sp->ts_splitoff = sp->ts_twordlen;
}
break;
@@ -1297,24 +1258,24 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Past bytes in node and/or past NUL bytes.
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_ENDNUL;
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
break;
}
// End of word in tree.
sp->ts_curi++; // eat one NUL byte
- flags = (int)idxs[arridx];
+ int flags = (int)idxs[arridx];
// Skip words with the NOSUGGEST flag.
if (flags & WF_NOSUGGEST) {
break;
}
- fword_ends = (fword[sp->ts_fidx] == NUL
- || (soundfold
- ? ascii_iswhite(fword[sp->ts_fidx])
- : !spell_iswordp(fword + sp->ts_fidx, curwin)));
+ int fword_ends = (fword[sp->ts_fidx] == NUL
+ || (soundfold
+ ? ascii_iswhite(fword[sp->ts_fidx])
+ : !spell_iswordp(fword + sp->ts_fidx, curwin)));
tword[sp->ts_twordlen] = NUL;
if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
@@ -1356,8 +1317,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
goodword_ends = true;
}
- p = NULL;
- compound_ok = true;
+ char *p = NULL;
+ bool compound_ok = true;
if (sp->ts_complen > sp->ts_compsplit) {
if (slang->sl_nobreak) {
// There was a word before this word. When there was no
@@ -1370,12 +1331,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tword + sp->ts_splitoff,
(size_t)(sp->ts_fidx - sp->ts_splitfidx)) == 0) {
preword[sp->ts_prewordlen] = NUL;
- newscore = score_wordcount_adj(slang, sp->ts_score,
- (char_u *)preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
+ int newscore = score_wordcount_adj(slang, sp->ts_score,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
// Add the suggestion if the score isn't too bad.
if (newscore <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_splitfidx - repextra,
newscore, 0, false,
lp->lp_sallang, false);
@@ -1400,7 +1361,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
break;
}
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen] = (uint8_t)((unsigned)flags >> 24);
compflags[sp->ts_complen + 1] = NUL;
xstrlcpy(preword + sp->ts_prewordlen,
tword + sp->ts_splitoff,
@@ -1437,7 +1398,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
} else if (flags & WF_KEEPCAP) {
// Must find the word in the keep-case tree.
- find_keepcap_word(slang, (char *)tword + sp->ts_splitoff, preword + sp->ts_prewordlen);
+ find_keepcap_word(slang, tword + sp->ts_splitoff, preword + sp->ts_prewordlen);
} else {
// Include badflags: If the badword is onecap or allcap
// use that for the goodword too. But if the badword is
@@ -1465,8 +1426,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
break;
}
if ((sp->ts_complen == sp->ts_compsplit
- && WAS_BANNED(su, (char *)preword + sp->ts_prewordlen))
- || WAS_BANNED(su, (char *)preword)) {
+ && WAS_BANNED(su, preword + sp->ts_prewordlen))
+ || WAS_BANNED(su, preword)) {
if (slang->sl_compprog == NULL) {
break;
}
@@ -1475,7 +1436,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
}
- newscore = 0;
+ int newscore = 0;
if (!soundfold) { // soundfold words don't have flags
if ((flags & WF_REGION)
&& (((unsigned)flags >> 16) & (unsigned)lp->lp_region) == 0) {
@@ -1502,9 +1463,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
int j;
// print the stack of changes that brought us here
- smsg("------ %s -------", fword);
+ smsg(0, "------ %s -------", fword);
for (j = 0; j < depth; j++) {
- smsg("%s", changename[j]);
+ smsg(0, "%s", changename[j]);
}
}
#endif
@@ -1526,14 +1487,14 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
// Give a bonus to words seen before.
- score = score_wordcount_adj(slang,
- sp->ts_score + newscore,
- (char_u *)preword + sp->ts_prewordlen,
- sp->ts_prewordlen > 0);
+ int score = score_wordcount_adj(slang,
+ sp->ts_score + newscore,
+ preword + sp->ts_prewordlen,
+ sp->ts_prewordlen > 0);
// Add the suggestion if the score isn't too bad.
if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_fidx - repextra,
score, 0, false, lp->lp_sallang, false);
@@ -1546,7 +1507,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
preword + sp->ts_prewordlen,
c == 0 ? WF_ALLCAP : 0);
- add_suggestion(su, &su->su_ga, (char *)preword,
+ add_suggestion(su, &su->su_ga, preword,
sp->ts_fidx - repextra,
score + SCORE_ICASE, 0, false,
lp->lp_sallang, false);
@@ -1598,7 +1559,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
< slang->sl_compmax)
&& (can_be_compound(sp, slang, compflags, (int)((unsigned)flags >> 24)))) {
try_compound = true;
- compflags[sp->ts_complen] = (char_u)((unsigned)flags >> 24);
+ compflags[sp->ts_complen] = (uint8_t)((unsigned)flags >> 24);
compflags[sp->ts_complen + 1] = NUL;
}
@@ -1617,7 +1578,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
sp->ts_curi--; // do the same NUL again
compflags[sp->ts_complen] = NUL;
} else {
- sp->ts_flags &= (char_u) ~TSF_DIDSPLIT;
+ sp->ts_flags &= (uint8_t) ~TSF_DIDSPLIT;
}
if (try_split || try_compound) {
@@ -1647,7 +1608,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Give a bonus to words seen before.
newscore = score_wordcount_adj(slang, newscore,
- (char_u *)preword + sp->ts_prewordlen, true);
+ preword + sp->ts_prewordlen, true);
}
if (TRY_DEEPER(su, stack, depth, newscore)) {
@@ -1662,7 +1623,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
#endif
// Save things to be restored at STATE_SPLITUNDO.
- sp->ts_save_badflags = (char_u)su->su_badflags;
+ sp->ts_save_badflags = (uint8_t)su->su_badflags;
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_SPLITUNDO;
@@ -1673,7 +1634,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (!try_compound && !fword_ends) {
STRCAT(preword, " ");
}
- sp->ts_prewordlen = (char_u)strlen(preword);
+ sp->ts_prewordlen = (uint8_t)strlen(preword);
sp->ts_splitoff = sp->ts_twordlen;
sp->ts_splitfidx = sp->ts_fidx;
@@ -1694,12 +1655,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (fword_ends) {
// Copy the skipped character to preword.
memmove(preword + sp->ts_prewordlen, fword + sp->ts_fidx, (size_t)l);
- sp->ts_prewordlen = (char_u)(sp->ts_prewordlen + l);
+ sp->ts_prewordlen = (uint8_t)(sp->ts_prewordlen + l);
preword[sp->ts_prewordlen] = NUL;
} else {
sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
}
- sp->ts_fidx = (char_u)(sp->ts_fidx + l);
+ sp->ts_fidx = (uint8_t)(sp->ts_fidx + l);
}
// When compounding include compound flag in
@@ -1715,8 +1676,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// set su->su_badflags to the caps type at this
// position
n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
- su->su_badflags = badword_captype((char_u *)su->su_badptr + n,
- (char_u *)su->su_badptr + su->su_badlen);
+ su->su_badflags = badword_captype(su->su_badptr + n,
+ su->su_badptr + su->su_badlen);
// Restart at top of the tree.
sp->ts_arridx = 0;
@@ -1824,7 +1785,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// First byte.
sp->ts_tcharidx = 0;
sp->ts_tcharlen = MB_BYTE2LEN(c);
- sp->ts_fcharstart = (char_u)(sp->ts_fidx - 1);
+ sp->ts_fcharstart = (uint8_t)(sp->ts_fidx - 1);
sp->ts_isdiff = (newscore != 0)
? DIFF_YES : DIFF_NONE;
} else if (sp->ts_isdiff == DIFF_INSERT && sp->ts_fidx > 0) {
@@ -1837,13 +1798,13 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
if (sp->ts_isdiff == DIFF_YES) {
// Correct ts_fidx for the byte length of the
// character (we didn't check that before).
- sp->ts_fidx = (char_u)(sp->ts_fcharstart
- + utfc_ptr2len(fword + sp->ts_fcharstart));
+ sp->ts_fidx = (uint8_t)(sp->ts_fcharstart
+ + utfc_ptr2len(fword + sp->ts_fcharstart));
// For changing a composing character adjust
// the score from SCORE_SUBST to
// SCORE_SUBCOMP.
- if (utf_iscomposing(utf_ptr2char((char *)tword + sp->ts_twordlen
+ if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
- sp->ts_tcharlen))
&& utf_iscomposing(utf_ptr2char(fword
+ sp->ts_fcharstart))) {
@@ -1851,7 +1812,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else if (!soundfold
&& slang->sl_has_map
&& similar_chars(slang,
- utf_ptr2char((char *)tword + sp->ts_twordlen -
+ utf_ptr2char(tword + sp->ts_twordlen -
sp->ts_tcharlen),
utf_ptr2char(fword + sp->ts_fcharstart))) {
// For a similar character adjust score from
@@ -1860,7 +1821,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
}
} else if (sp->ts_isdiff == DIFF_INSERT
&& sp->ts_twordlen > sp->ts_tcharlen) {
- p = (char *)tword + sp->ts_twordlen - sp->ts_tcharlen;
+ p = tword + sp->ts_twordlen - sp->ts_tcharlen;
c = utf_ptr2char(p);
if (utf_iscomposing(c)) {
// Inserting a composing char doesn't
@@ -1926,7 +1887,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// results.
c = utf_ptr2char(fword + sp->ts_fidx);
stack[depth].ts_fidx =
- (char_u)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx));
+ (uint8_t)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx));
if (utf_iscomposing(c)) {
stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
} else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) {
@@ -1948,7 +1909,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// skip over NUL bytes
n = sp->ts_arridx;
- for (;;) {
+ while (true) {
if (sp->ts_curi > byts[n]) {
// Only NUL bytes at this node, go to next state.
PROF_STORE(sp->ts_state)
@@ -2006,7 +1967,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// There are following bytes for the same character.
// We must find all bytes before trying
// delete/insert/swap/etc.
- sp->ts_tcharlen = (char_u)fl;
+ sp->ts_tcharlen = (uint8_t)fl;
sp->ts_tcharidx = 1;
sp->ts_isdiff = DIFF_INSERT;
}
@@ -2082,7 +2043,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl = utf_char2len(c2);
memmove(p, p + n, (size_t)fl);
utf_char2bytes(c, p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl);
} else {
// If this swap doesn't work then SWAP3 won't either.
PROF_STORE(sp->ts_state)
@@ -2139,7 +2100,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
memmove(p, p + n + fl, (size_t)tl);
utf_char2bytes(c2, p + tl);
utf_char2bytes(c, p + fl + tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl + tl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2187,7 +2148,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl += utf_ptr2len(p + n + fl);
memmove(p, p + n, (size_t)fl);
utf_char2bytes(c, p + fl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + fl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + fl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2224,7 +2185,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
tl = utf_ptr2len(p + n);
memmove(p + tl, p, (size_t)n);
utf_char2bytes(c, p);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + n + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + n + tl);
} else {
PROF_STORE(sp->ts_state)
sp->ts_state = STATE_REP_INI;
@@ -2287,7 +2248,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
gap = &lp->lp_replang->sl_rep;
}
while (sp->ts_curi < gap->ga_len) {
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
+ fromto_T *ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
if (*ftp->ft_from != *p) {
// past possible matching entries
sp->ts_curi = (int16_t)gap->ga_len;
@@ -2310,11 +2271,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
fl = (int)strlen(ftp->ft_from);
tl = (int)strlen(ftp->ft_to);
if (fl != tl) {
- STRMOVE(p + tl, (char *)p + fl);
+ STRMOVE(p + tl, p + fl);
repextra += tl - fl;
}
memmove(p, ftp->ft_to, (size_t)tl);
- stack[depth].ts_fidxtry = (char_u)(sp->ts_fidx + tl);
+ stack[depth].ts_fidxtry = (uint8_t)(sp->ts_fidx + tl);
stack[depth].ts_tcharlen = 0;
break;
}
@@ -2335,12 +2296,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
} else {
gap = &lp->lp_replang->sl_rep;
}
- ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
+ fromto_T *ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
fl = (int)strlen(ftp->ft_from);
tl = (int)strlen(ftp->ft_to);
p = fword + sp->ts_fidx;
if (fl != tl) {
- STRMOVE(p + fl, (char *)p + tl);
+ STRMOVE(p + fl, p + tl);
repextra -= tl - fl;
}
memmove(p, ftp->ft_from, (size_t)fl);
@@ -2387,7 +2348,6 @@ static void go_deeper(trystate_T *stack, int depth, int score_add)
static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
{
char uword[MAXWLEN]; // "fword" in upper-case
- int depth;
idx_T tryidx;
// The following arrays are used at each depth in the tree.
@@ -2397,13 +2357,9 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
int uwordidx[MAXWLEN];
int kwordlen[MAXWLEN];
- int flen, ulen;
int l;
- int len;
- int c;
- idx_T lo, hi, m;
- char_u *p;
- char_u *byts = (char_u *)slang->sl_kbyts; // array with bytes of the words
+ char *p;
+ uint8_t *byts = slang->sl_kbyts; // array with bytes of the words
idx_T *idxs = slang->sl_kidxs; // array with indexes
if (byts == NULL) {
@@ -2418,7 +2374,7 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
// Each character needs to be tried both case-folded and upper-case.
// All this gets very complicated if we keep in mind that changing case
// may change the byte length of a multi-byte character...
- depth = 0;
+ int depth = 0;
arridx[0] = 0;
round[0] = 0;
fwordidx[0] = 0;
@@ -2442,24 +2398,24 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
} else {
// round[depth] == 1: Try using the folded-case character.
// round[depth] == 2: Try using the upper-case character.
- flen = utf_ptr2len(fword + fwordidx[depth]);
- ulen = utf_ptr2len((char *)uword + uwordidx[depth]);
+ int flen = utf_ptr2len(fword + fwordidx[depth]);
+ int ulen = utf_ptr2len(uword + uwordidx[depth]);
if (round[depth] == 1) {
- p = (char_u *)fword + fwordidx[depth];
+ p = fword + fwordidx[depth];
l = flen;
} else {
- p = (char_u *)uword + uwordidx[depth];
+ p = uword + uwordidx[depth];
l = ulen;
}
for (tryidx = arridx[depth]; l > 0; l--) {
// Perform a binary search in the list of accepted bytes.
- len = byts[tryidx++];
- c = *p++;
- lo = tryidx;
- hi = tryidx + len - 1;
+ int len = byts[tryidx++];
+ int c = (uint8_t)(*p++);
+ idx_T lo = tryidx;
+ idx_T hi = tryidx + len - 1;
while (lo < hi) {
- m = (lo + hi) / 2;
+ idx_T m = (lo + hi) / 2;
if (byts[m] > c) {
hi = m - 1;
} else if (byts[m] < c) {
@@ -2511,31 +2467,26 @@ static void find_keepcap_word(slang_T *slang, char *fword, char *kword)
/// su->su_sga.
static void score_comp_sal(suginfo_T *su)
{
- langp_T *lp;
- char_u badsound[MAXWLEN];
- int i;
- suggest_T *stp;
- suggest_T *sstp;
- int score;
+ char badsound[MAXWLEN];
ga_grow(&su->su_sga, su->su_ga.ga_len);
// Use the sound-folding of the first language that supports it.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
// soundfold the bad word
- spell_soundfold(lp->lp_slang, (char *)su->su_fbadword, true, (char *)badsound);
+ spell_soundfold(lp->lp_slang, su->su_fbadword, true, badsound);
- for (i = 0; i < su->su_ga.ga_len; i++) {
- stp = &SUG(su->su_ga, i);
+ for (int i = 0; i < su->su_ga.ga_len; i++) {
+ suggest_T *stp = &SUG(su->su_ga, i);
// Case-fold the suggested word, sound-fold it and compute the
// sound-a-like score.
- score = stp_sal_score(stp, su, lp->lp_slang, badsound);
+ int score = stp_sal_score(stp, su, lp->lp_slang, badsound);
if (score < SCORE_MAXMAX) {
// Add the suggestion.
- sstp = &SUG(su->su_sga, su->su_sga.ga_len);
+ suggest_T *sstp = &SUG(su->su_sga, su->su_sga.ga_len);
sstp->st_word = xstrdup(stp->st_word);
sstp->st_wordlen = stp->st_wordlen;
sstp->st_score = score;
@@ -2554,25 +2505,21 @@ static void score_comp_sal(suginfo_T *su)
static void score_combine(suginfo_T *su)
{
garray_T ga;
- garray_T *gap;
- langp_T *lp;
- suggest_T *stp;
char *p;
char badsound[MAXWLEN];
- int round;
slang_T *slang = NULL;
// Add the alternate score to su_ga.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
if (!GA_EMPTY(&lp->lp_slang->sl_sal)) {
// soundfold the bad word
slang = lp->lp_slang;
- spell_soundfold(slang, (char *)su->su_fbadword, true, badsound);
+ spell_soundfold(slang, su->su_fbadword, true, badsound);
for (int i = 0; i < su->su_ga.ga_len; i++) {
- stp = &SUG(su->su_ga, i);
- stp->st_altscore = stp_sal_score(stp, su, slang, (char_u *)badsound);
+ suggest_T *stp = &SUG(su->su_ga, i);
+ stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
if (stp->st_altscore == SCORE_MAXMAX) {
stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
} else {
@@ -2592,8 +2539,8 @@ static void score_combine(suginfo_T *su)
// Add the alternate score to su_sga.
for (int i = 0; i < su->su_sga.ga_len; i++) {
- stp = &SUG(su->su_sga, i);
- stp->st_altscore = spell_edit_score(slang, (char_u *)su->su_badword, (char_u *)stp->st_word);
+ suggest_T *stp = &SUG(su->su_sga, i);
+ stp->st_altscore = spell_edit_score(slang, su->su_badword, stp->st_word);
if (stp->st_score == SCORE_MAXMAX) {
stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
} else {
@@ -2612,12 +2559,12 @@ static void score_combine(suginfo_T *su)
ga_init(&ga, (int)sizeof(suginfo_T), 1);
ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len);
- stp = &SUG(ga, 0);
+ suggest_T *stp = &SUG(ga, 0);
for (int i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; i++) {
// round 1: get a suggestion from su_ga
// round 2: get a suggestion from su_sga
- for (round = 1; round <= 2; round++) {
- gap = round == 1 ? &su->su_ga : &su->su_sga;
+ for (int round = 1; round <= 2; round++) {
+ garray_T *gap = round == 1 ? &su->su_ga : &su->su_sga;
if (i < gap->ga_len) {
// Don't add a word if it's already there.
p = SUG(*gap, i).st_word;
@@ -2654,23 +2601,21 @@ static void score_combine(suginfo_T *su)
/// badword.
///
/// @param badsound sound-folded badword
-static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound)
+static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char *badsound)
{
- char_u *p;
- char_u *pbad;
- char_u *pgood;
- char_u badsound2[MAXWLEN];
- char_u fword[MAXWLEN];
- char_u goodsound[MAXWLEN];
+ char *pbad;
+ char *pgood;
+ char badsound2[MAXWLEN];
+ char fword[MAXWLEN];
+ char goodsound[MAXWLEN];
char goodword[MAXWLEN];
- int lendiff;
- lendiff = su->su_badlen - stp->st_orglen;
+ int lendiff = su->su_badlen - stp->st_orglen;
if (lendiff >= 0) {
pbad = badsound;
} else {
// soundfold the bad word with more characters following
- (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, (char *)fword, MAXWLEN);
+ (void)spell_casefold(curwin, su->su_badptr, stp->st_orglen, fword, MAXWLEN);
// When joining two words the sound often changes a lot. E.g., "t he"
// sounds like "t h" while "the" sounds like "@". Avoid that by
@@ -2678,12 +2623,12 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *
// space.
if (ascii_iswhite(su->su_badptr[su->su_badlen])
&& *skiptowhite(stp->st_word) == NUL) {
- for (p = fword; *(p = (char_u *)skiptowhite((char *)p)) != NUL;) {
- STRMOVE(p, (char *)p + 1);
+ for (char *p = fword; *(p = skiptowhite(p)) != NUL;) {
+ STRMOVE(p, p + 1);
}
}
- spell_soundfold(slang, (char *)fword, true, (char *)badsound2);
+ spell_soundfold(slang, fword, true, badsound2);
pbad = badsound2;
}
@@ -2693,39 +2638,36 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *
STRCPY(goodword, stp->st_word);
xstrlcpy(goodword + stp->st_wordlen,
su->su_badptr + su->su_badlen - lendiff, (size_t)lendiff + 1);
- pgood = (char_u *)goodword;
+ pgood = goodword;
} else {
- pgood = (char_u *)stp->st_word;
+ pgood = stp->st_word;
}
// Sound-fold the word and compute the score for the difference.
- spell_soundfold(slang, (char *)pgood, false, (char *)goodsound);
+ spell_soundfold(slang, pgood, false, goodsound);
- return soundalike_score((char *)goodsound, (char *)pbad);
+ return soundalike_score(goodsound, pbad);
}
/// structure used to store soundfolded words that add_sound_suggest() has
/// handled already.
typedef struct {
int16_t sft_score; ///< lowest score used
- char_u sft_word[1]; ///< soundfolded word, actually longer
+ uint8_t sft_word[]; ///< soundfolded word
} sftword_T;
static sftword_T dumsft;
-#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft)))
+#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (uint8_t *)&dumsft)))
#define HI2SFT(hi) HIKEY2SFT((hi)->hi_key)
/// Prepare for calling suggest_try_soundalike().
static void suggest_try_soundalike_prep(void)
{
- langp_T *lp;
- slang_T *slang;
-
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// prepare the hashtable used by add_sound_suggest()
hash_init(&slang->sl_sounddone);
@@ -2737,18 +2679,16 @@ static void suggest_try_soundalike_prep(void)
/// Note: This doesn't support postponed prefixes.
static void suggest_try_soundalike(suginfo_T *su)
{
- char_u salword[MAXWLEN];
- langp_T *lp;
- slang_T *slang;
+ char salword[MAXWLEN];
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// soundfold the bad word
- spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)salword);
+ spell_soundfold(slang, su->su_fbadword, true, salword);
// try all kinds of inserts/deletes/swaps/etc.
// TODO(vim): also soundfold the next words, so that we can try joining
@@ -2756,7 +2696,7 @@ static void suggest_try_soundalike(suginfo_T *su)
#ifdef SUGGEST_PROFILE
prof_init();
#endif
- suggest_trie_walk(su, lp, (char *)salword, true);
+ suggest_trie_walk(su, lp, salword, true);
#ifdef SUGGEST_PROFILE
prof_report("soundalike");
#endif
@@ -2767,20 +2707,15 @@ static void suggest_try_soundalike(suginfo_T *su)
/// Finish up after calling suggest_try_soundalike().
static void suggest_try_soundalike_finish(void)
{
- langp_T *lp;
- slang_T *slang;
- int todo;
- hashitem_T *hi;
-
// Do this for all languages that support sound folding and for which a
// .sug file has been loaded.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (!GA_EMPTY(&slang->sl_sal) && slang->sl_sbyts != NULL) {
// Free the info about handled words.
- todo = (int)slang->sl_sounddone.ht_used;
- for (hi = slang->sl_sounddone.ht_array; todo > 0; hi++) {
+ int todo = (int)slang->sl_sounddone.ht_used;
+ for (hashitem_T *hi = slang->sl_sounddone.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
xfree(HI2SFT(hi));
todo--;
@@ -2801,32 +2736,21 @@ static void suggest_try_soundalike_finish(void)
static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T *lp)
{
slang_T *slang = lp->lp_slang; // language for sound folding
- int sfwordnr;
- char_u *nrline;
- int orgnr;
- char_u theword[MAXWLEN];
+ char theword[MAXWLEN];
int i;
int wlen;
- char_u *byts;
- idx_T *idxs;
- int n;
- int wordcount;
+ uint8_t *byts;
int wc;
int goodscore;
- hash_T hash;
- hashitem_T *hi;
sftword_T *sft;
- int bc, gc;
- int limit;
// It's very well possible that the same soundfold word is found several
// times with different scores. Since the following is quite slow only do
// the words that have a better score than before. Use a hashtable to
// remember the words that have been done.
- hash = hash_hash(goodword);
+ hash_T hash = hash_hash(goodword);
const size_t goodword_len = strlen(goodword);
- hi = hash_lookup(&slang->sl_sounddone, (const char *)goodword, goodword_len,
- hash);
+ hashitem_T *hi = hash_lookup(&slang->sl_sounddone, goodword, goodword_len, hash);
if (HASHITEM_EMPTY(hi)) {
sft = xmalloc(offsetof(sftword_T, sft_word) + goodword_len + 1);
sft->sft_score = (int16_t)score;
@@ -2841,26 +2765,26 @@ static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T
}
// Find the word nr in the soundfold tree.
- sfwordnr = soundfold_find(slang, (char_u *)goodword);
+ int sfwordnr = soundfold_find(slang, goodword);
if (sfwordnr < 0) {
internal_error("add_sound_suggest()");
return;
}
// Go over the list of good words that produce this soundfold word
- nrline = (char_u *)ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false);
- orgnr = 0;
+ char *nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1);
+ int orgnr = 0;
while (*nrline != NUL) {
// The wordnr was stored in a minimal nr of bytes as an offset to the
// previous wordnr.
orgnr += bytes2offset(&nrline);
- byts = (char_u *)slang->sl_fbyts;
- idxs = slang->sl_fidxs;
+ byts = slang->sl_fbyts;
+ idx_T *idxs = slang->sl_fidxs;
// Lookup the word "orgnr" one of the two tries.
- n = 0;
- wordcount = 0;
+ int n = 0;
+ int wordcount = 0;
for (wlen = 0; wlen < MAXWLEN - 3; wlen++) {
i = 1;
if (wordcount == orgnr && byts[n + 1] == NUL) {
@@ -2888,7 +2812,7 @@ static void add_sound_suggest(suginfo_T *su, char *goodword, int score, langp_T
wordcount += wc;
}
- theword[wlen] = byts[n + i];
+ theword[wlen] = (char)byts[n + i];
n = idxs[n + i];
}
badword:
@@ -2896,8 +2820,8 @@ badword:
// Go over the possible flags and regions.
for (; i <= byts[n] && byts[n + i] == NUL; i++) {
- char_u cword[MAXWLEN];
- char_u *p;
+ char cword[MAXWLEN];
+ char *p;
int flags = (int)idxs[n + i];
// Skip words with the NOSUGGEST flag
@@ -2907,13 +2831,13 @@ badword:
if (flags & WF_KEEPCAP) {
// Must find the word in the keep-case tree.
- find_keepcap_word(slang, (char *)theword, (char *)cword);
+ find_keepcap_word(slang, theword, cword);
p = cword;
} else {
flags |= su->su_badflags;
if ((flags & WF_CAPMASK) != 0) {
// Need to fix case according to "flags".
- make_case_word((char *)theword, (char *)cword, flags);
+ make_case_word(theword, cword, flags);
p = cword;
} else {
p = theword;
@@ -2924,7 +2848,7 @@ badword:
if (sps_flags & SPS_DOUBLE) {
// Add the suggestion if the score isn't too bad.
if (score <= su->su_maxscore) {
- add_suggestion(su, &su->su_sga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_sga, p, su->su_badlen,
score, 0, false, slang, false);
}
} else {
@@ -2940,9 +2864,9 @@ badword:
// lower to upper case. Helps for "tath" -> "Kath", which is
// less common than "tath" -> "path". Don't do it when the
// letter is the same, that has already been counted.
- gc = utf_ptr2char((char *)p);
+ int gc = utf_ptr2char(p);
if (SPELL_ISUPPER(gc)) {
- bc = utf_ptr2char((char *)su->su_badword);
+ int bc = utf_ptr2char(su->su_badword);
if (!SPELL_ISUPPER(bc)
&& SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) {
goodscore += SCORE_ICASE / 2;
@@ -2956,12 +2880,11 @@ badword:
// MAXSCORE(), because RESCORE() will change the score.
// If the limit is very high then the iterative method is
// inefficient, using an array is quicker.
- limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
+ int limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
if (limit > SCORE_LIMITMAX) {
- goodscore += spell_edit_score(slang, (char_u *)su->su_badword, p);
+ goodscore += spell_edit_score(slang, su->su_badword, p);
} else {
- goodscore += spell_edit_score_limit(slang, (char_u *)su->su_badword,
- p, limit);
+ goodscore += spell_edit_score_limit(slang, su->su_badword, p, limit);
}
// When going over the limit don't bother to do the rest.
@@ -2972,7 +2895,7 @@ badword:
// Add the suggestion if the score isn't too bad.
goodscore = RESCORE(goodscore, score);
if (goodscore <= su->su_sfmaxscore) {
- add_suggestion(su, &su->su_ga, (char *)p, su->su_badlen,
+ add_suggestion(su, &su->su_ga, p, su->su_badlen,
goodscore, score, true, slang, true);
}
}
@@ -2982,27 +2905,23 @@ badword:
}
/// Find word "word" in fold-case tree for "slang" and return the word number.
-static int soundfold_find(slang_T *slang, char_u *word)
+static int soundfold_find(slang_T *slang, char *word)
{
idx_T arridx = 0;
- int len;
int wlen = 0;
- int c;
- char_u *ptr = word;
- char_u *byts;
- idx_T *idxs;
+ uint8_t *ptr = (uint8_t *)word;
int wordnr = 0;
- byts = (char_u *)slang->sl_sbyts;
- idxs = slang->sl_sidxs;
+ uint8_t *byts = slang->sl_sbyts;
+ idx_T *idxs = slang->sl_sidxs;
- for (;;) {
+ while (true) {
// First byte is the number of possible bytes.
- len = byts[arridx++];
+ int len = byts[arridx++];
// If the first possible byte is a zero the word could end here.
// If the word ends we found the word. If not skip the NUL bytes.
- c = ptr[wlen];
+ int c = ptr[wlen];
if (byts[arridx] == NUL) {
if (c == NUL) {
break;
@@ -3061,12 +2980,11 @@ static int soundfold_find(slang_T *slang, char_u *word)
static bool similar_chars(slang_T *slang, int c1, int c2)
{
int m1, m2;
- char buf[MB_MAXBYTES + 1];
- hashitem_T *hi;
+ char buf[MB_MAXCHAR + 1];
if (c1 >= 256) {
- buf[utf_char2bytes(c1, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
+ buf[utf_char2bytes(c1, buf)] = 0;
+ hashitem_T *hi = hash_find(&slang->sl_map_hash, buf);
if (HASHITEM_EMPTY(hi)) {
m1 = 0;
} else {
@@ -3080,8 +2998,8 @@ static bool similar_chars(slang_T *slang, int c1, int c2)
}
if (c2 >= 256) {
- buf[utf_char2bytes(c2, (char *)buf)] = 0;
- hi = hash_find(&slang->sl_map_hash, buf);
+ buf[utf_char2bytes(c2, buf)] = 0;
+ hashitem_T *hi = hash_find(&slang->sl_map_hash, buf);
if (HASHITEM_EMPTY(hi)) {
m2 = 0;
} else {
@@ -3107,14 +3025,13 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
{
int goodlen; // len of goodword changed
int badlen; // len of bad word changed
- suggest_T *stp;
suggest_T new_sug;
// Minimize "badlen" for consistency. Avoids that changing "the the" to
// "thee the" is added next to changing the first "the" the "thee".
const char *pgood = goodword + strlen(goodword);
char *pbad = su->su_badptr + badlenarg;
- for (;;) {
+ while (true) {
goodlen = (int)(pgood - goodword);
badlen = (int)(pbad - su->su_badptr);
if (goodlen <= 0 || badlen <= 0) {
@@ -3140,7 +3057,7 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
// Check if the word is already there. Also check the length that is
// being replaced "thes," -> "these" is a different suggestion from
// "thes" -> "these".
- stp = &SUG(*gap, 0);
+ suggest_T *stp = &SUG(*gap, 0);
for (i = gap->ga_len; --i >= 0; stp++) {
if (stp->st_wordlen == goodlen
&& stp->st_orglen == badlen
@@ -3184,8 +3101,8 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
if (i < 0) {
// Add a suggestion.
- stp = GA_APPEND_VIA_PTR(suggest_T, gap);
- stp->st_word = xstrnsave(goodword, (size_t)goodlen);
+ suggest_T *stp = GA_APPEND_VIA_PTR(suggest_T, gap);
+ stp->st_word = xmemdupz(goodword, (size_t)goodlen);
stp->st_wordlen = goodlen;
stp->st_score = score;
stp->st_altscore = altscore;
@@ -3213,22 +3130,18 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
/// @param gap either su_ga or su_sga
static void check_suggestions(suginfo_T *su, garray_T *gap)
{
- suggest_T *stp;
char longword[MAXWLEN + 1];
- int len;
- hlf_T attr;
if (gap->ga_len == 0) {
return;
}
- stp = &SUG(*gap, 0);
+ suggest_T *stp = &SUG(*gap, 0);
for (int i = gap->ga_len - 1; i >= 0; i--) {
// Need to append what follows to check for "the the".
xstrlcpy(longword, stp[i].st_word, MAXWLEN + 1);
- len = stp[i].st_wordlen;
- xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen,
- (size_t)(MAXWLEN - len + 1));
- attr = HLF_COUNT;
+ int len = stp[i].st_wordlen;
+ xstrlcpy(longword + len, su->su_badptr + stp[i].st_orglen, MAXWLEN + 1 - (size_t)len);
+ hlf_T attr = HLF_COUNT;
(void)spell_check(curwin, longword, &attr, NULL, false);
if (attr != HLF_COUNT) {
// Remove this entry.
@@ -3244,18 +3157,14 @@ static void check_suggestions(suginfo_T *su, garray_T *gap)
/// Add a word to be banned.
static void add_banned(suginfo_T *su, char *word)
{
- char_u *s;
- hash_T hash;
- hashitem_T *hi;
-
- hash = hash_hash(word);
+ hash_T hash = hash_hash(word);
const size_t word_len = strlen(word);
- hi = hash_lookup(&su->su_banned, word, word_len, hash);
+ hashitem_T *hi = hash_lookup(&su->su_banned, word, word_len, hash);
if (!HASHITEM_EMPTY(hi)) { // already present
return;
}
- s = xmemdupz(word, word_len);
- hash_add_item(&su->su_banned, hi, (char *)s, hash);
+ char *s = xmemdupz(word, word_len);
+ hash_add_item(&su->su_banned, hi, s, hash);
}
/// Recompute the score for all suggestions if sound-folding is possible. This
@@ -3273,16 +3182,16 @@ static void rescore_suggestions(suginfo_T *su)
static void rescore_one(suginfo_T *su, suggest_T *stp)
{
slang_T *slang = stp->st_slang;
- char_u sal_badword[MAXWLEN];
- char_u *p;
+ char sal_badword[MAXWLEN];
// Only rescore suggestions that have no sal score yet and do have a
// language.
if (slang != NULL && !GA_EMPTY(&slang->sl_sal) && !stp->st_had_bonus) {
+ char *p;
if (slang == su->su_sallang) {
p = su->su_sal_badword;
} else {
- spell_soundfold(slang, (char *)su->su_fbadword, true, (char *)sal_badword);
+ spell_soundfold(slang, su->su_fbadword, true, sal_badword);
p = sal_badword;
}
@@ -3355,9 +3264,6 @@ static int soundalike_score(char *goodstart, char *badstart)
{
char *goodsound = goodstart;
char *badsound = badstart;
- int goodlen;
- int badlen;
- int n;
char *pl, *ps;
char *pl2, *ps2;
int score = 0;
@@ -3390,12 +3296,12 @@ static int soundalike_score(char *goodstart, char *badstart)
}
}
- goodlen = (int)strlen(goodsound);
- badlen = (int)strlen(badsound);
+ int goodlen = (int)strlen(goodsound);
+ int badlen = (int)strlen(badsound);
// Return quickly if the lengths are too different to be fixed by two
// changes.
- n = goodlen - badlen;
+ int n = goodlen - badlen;
if (n < -2 || n > 2) {
return SCORE_MAXMAX;
}
@@ -3575,13 +3481,8 @@ static int soundalike_score(char *goodstart, char *badstart)
/// The implementation of the algorithm comes from Aspell editdist.cpp,
/// edit_distance(). It has been converted from C++ to C and modified to
/// support multi-byte characters.
-static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u *goodword)
+static int spell_edit_score(slang_T *slang, const char *badword, const char *goodword)
{
- int *cnt;
- int j, i;
- int t;
- int bc, gc;
- int pbc, pgc;
int wbadword[MAXWLEN];
int wgoodword[MAXWLEN];
@@ -3592,12 +3493,12 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
// Get the characters from the multi-byte strings and put them in an
// int array for easy access.
badlen = 0;
- for (const char *p = (char *)badword; *p != NUL;) {
+ for (const char *p = badword; *p != NUL;) {
wbadword[badlen++] = mb_cptr2char_adv(&p);
}
wbadword[badlen++] = 0;
goodlen = 0;
- for (const char *p = (char *)goodword; *p != NUL;) {
+ for (const char *p = goodword; *p != NUL;) {
wgoodword[goodlen++] = mb_cptr2char_adv(&p);
}
wgoodword[goodlen++] = 0;
@@ -3605,18 +3506,18 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
// We use "cnt" as an array: CNT(badword_idx, goodword_idx).
#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)]
- cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
+ int *cnt = xmalloc(sizeof(int) * ((size_t)badlen + 1) * ((size_t)goodlen + 1));
CNT(0, 0) = 0;
- for (j = 1; j <= goodlen; j++) {
+ for (int j = 1; j <= goodlen; j++) {
CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
}
- for (i = 1; i <= badlen; i++) {
+ for (int i = 1; i <= badlen; i++) {
CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
- for (j = 1; j <= goodlen; j++) {
- bc = wbadword[i - 1];
- gc = wgoodword[j - 1];
+ for (int j = 1; j <= goodlen; j++) {
+ int bc = wbadword[i - 1];
+ int gc = wgoodword[j - 1];
if (bc == gc) {
CNT(i, j) = CNT(i - 1, j - 1);
} else {
@@ -3635,16 +3536,16 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
}
if (i > 1 && j > 1) {
- pbc = wbadword[i - 2];
- pgc = wgoodword[j - 2];
+ int pbc = wbadword[i - 2];
+ int pgc = wgoodword[j - 2];
if (bc == pgc && pbc == gc) {
- t = SCORE_SWAP + CNT(i - 2, j - 2);
+ int t = SCORE_SWAP + CNT(i - 2, j - 2);
if (t < CNT(i, j)) {
CNT(i, j) = t;
}
}
}
- t = SCORE_DEL + CNT(i - 1, j);
+ int t = SCORE_DEL + CNT(i - 1, j);
if (t < CNT(i, j)) {
CNT(i, j) = t;
}
@@ -3656,7 +3557,7 @@ static int spell_edit_score(slang_T *slang, const char_u *badword, const char_u
}
}
- i = CNT(badlen - 1, goodlen - 1);
+ int i = CNT(badlen - 1, goodlen - 1);
xfree(cnt);
return i;
}
@@ -3673,37 +3574,31 @@ typedef struct {
/// This uses a stack for the edits still to be tried.
/// The idea comes from Aspell leditdist.cpp. Rewritten in C and added support
/// for multi-byte characters.
-static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit)
+static int spell_edit_score_limit(slang_T *slang, char *badword, char *goodword, int limit)
{
return spell_edit_score_limit_w(slang, badword, goodword, limit);
}
/// Multi-byte version of spell_edit_score_limit().
/// Keep it in sync with the above!
-static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const char_u *goodword,
+static int spell_edit_score_limit_w(slang_T *slang, const char *badword, const char *goodword,
int limit)
{
limitscore_T stack[10]; // allow for over 3 * 2 edits
- int stackidx;
- int bi, gi;
- int bi2, gi2;
int bc, gc;
- int score;
int score_off;
- int minscore;
- int round;
int wbadword[MAXWLEN];
int wgoodword[MAXWLEN];
// Get the characters from the multi-byte strings and put them in an
// int array for easy access.
- bi = 0;
- for (const char *p = (char *)badword; *p != NUL;) {
+ int bi = 0;
+ for (const char *p = badword; *p != NUL;) {
wbadword[bi++] = mb_cptr2char_adv(&p);
}
wbadword[bi++] = 0;
- gi = 0;
- for (const char *p = (char *)goodword; *p != NUL;) {
+ int gi = 0;
+ for (const char *p = goodword; *p != NUL;) {
wgoodword[gi++] = mb_cptr2char_adv(&p);
}
wgoodword[gi++] = 0;
@@ -3715,15 +3610,15 @@ static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const
// pushed unto a stack and tried later, some are tried right away. At the
// end of the word the score for one alternative is known. The lowest
// possible score is stored in "minscore".
- stackidx = 0;
+ int stackidx = 0;
bi = 0;
gi = 0;
- score = 0;
- minscore = limit + 1;
+ int score = 0;
+ int minscore = limit + 1;
- for (;;) {
+ while (true) {
// Skip over an equal part, score remains the same.
- for (;;) {
+ while (true) {
bc = wbadword[bi];
gc = wgoodword[gi];
@@ -3759,15 +3654,15 @@ static int spell_edit_score_limit_w(slang_T *slang, const char_u *badword, const
// that may lead to a lower score than "minscore".
// round 0: try deleting a char from badword
// round 1: try inserting a char in badword
- for (round = 0; round <= 1; round++) {
+ for (int round = 0; round <= 1; round++) {
score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
if (score_off < minscore) {
if (score_off + SCORE_EDIT_MIN >= minscore) {
// Near the limit, rest of the words must match. We
// can check that right now, no need to push an item
// onto the stack.
- bi2 = bi + 1 - round;
- gi2 = gi + round;
+ int bi2 = bi + 1 - round;
+ int gi2 = gi + round;
while (wgoodword[gi2] == wbadword[bi2]) {
if (wgoodword[gi2] == NUL) {
minscore = score_off;
diff --git a/src/nvim/spellsuggest.h b/src/nvim/spellsuggest.h
index 8813a5b3f1..40e51a0f88 100644
--- a/src/nvim/spellsuggest.h
+++ b/src/nvim/spellsuggest.h
@@ -1,9 +1,7 @@
-#ifndef NVIM_SPELLSUGGEST_H
-#define NVIM_SPELLSUGGEST_H
+#pragma once
-#include "nvim/garray.h"
+#include "nvim/garray_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "spellsuggest.h.generated.h"
#endif
-#endif // NVIM_SPELLSUGGEST_H
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 9ba5f81776..900eac0826 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -1,35 +1,29 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
-#include <stddef.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
+#include "nvim/ex_getln.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
+#include "nvim/memory.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
-#include "nvim/screen.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "state.c.generated.h" // IWYU pragma: export
@@ -37,7 +31,7 @@
void state_enter(VimState *s)
{
- for (;;) {
+ while (true) {
int check_result = s->check ? s->check(s) : 1;
if (!check_result) {
@@ -62,6 +56,8 @@ getkey:
if (vpeekc() != NUL || typebuf.tb_len > 0) {
key = safe_vgetc();
} else if (!multiqueue_empty(main_loop.events)) {
+ // No input available and processing events may take time, flush now.
+ ui_flush();
// Event was made available after the last multiqueue_process_events call
key = K_EVENT;
} else {
@@ -71,7 +67,7 @@ getkey:
update_screen();
setcursor(); // put cursor back where it belongs
}
- // Flush screen updates before blocking
+ // Flush screen updates before blocking.
ui_flush();
// Call `os_inchar` directly to block for events or user input without
// consuming anything from `input_buffer`(os/input.c) or calling the
@@ -92,8 +88,9 @@ getkey:
may_sync_undo();
}
-#if MIN_LOG_LEVEL <= LOGLVL_DBG
- log_key(LOGLVL_DBG, key);
+#ifdef NVIM_LOG_DEBUG
+ char *keyname = key == K_EVENT ? "K_EVENT" : get_special_key_name(key, mod_mask);
+ DLOG("input: %s", keyname);
#endif
int execute_result = s->execute(s, key);
@@ -135,7 +132,7 @@ void state_handle_k_event(void)
/// Return true if in the current mode we need to use virtual.
bool virtual_active(void)
{
- unsigned int cur_ve_flags = get_ve_flags();
+ unsigned cur_ve_flags = get_ve_flags();
// While an operator is being executed we return "virtual_op", because
// VIsual_active has already been reset, thus we can't check for "block"
@@ -214,6 +211,9 @@ void get_mode(char *buf)
if (exmode_active) {
buf[i++] = 'v';
}
+ if ((State & MODE_CMDLINE) && cmdline_overstrike()) {
+ buf[i++] = 'r';
+ }
} else if (State & MODE_TERMINAL) {
buf[i++] = 't';
} else {
@@ -268,3 +268,50 @@ void may_trigger_modechanged(void)
restore_v_event(v_event, &save_v_event);
}
+
+/// When true in a safe state when starting to wait for a character.
+static bool was_safe = false;
+
+/// Return whether currently it is safe, assuming it was safe before (high level
+/// state didn't change).
+static bool is_safe_now(void)
+{
+ return stuff_empty()
+ && typebuf.tb_len == 0
+ && !using_script()
+ && !global_busy
+ && !debug_mode;
+}
+
+/// Trigger SafeState if currently in s safe state, that is "safe" is TRUE and
+/// there is no typeahead.
+void may_trigger_safestate(bool safe)
+{
+ bool is_safe = safe && is_safe_now();
+
+ if (was_safe != is_safe) {
+ // Only log when the state changes, otherwise it happens at nearly
+ // every key stroke.
+ DLOG(is_safe ? "SafeState: Start triggering" : "SafeState: Stop triggering");
+ }
+ if (is_safe) {
+ apply_autocmds(EVENT_SAFESTATE, NULL, NULL, false, curbuf);
+ }
+ was_safe = is_safe;
+}
+
+/// Something changed which causes the state possibly to be unsafe, e.g. a
+/// character was typed. It will remain unsafe until the next call to
+/// may_trigger_safestate().
+void state_no_longer_safe(const char *reason)
+{
+ if (was_safe && reason != NULL) {
+ DLOG("SafeState reset: %s", reason);
+ }
+ was_safe = false;
+}
+
+bool get_was_safe_state(void)
+{
+ return was_safe;
+}
diff --git a/src/nvim/state.h b/src/nvim/state.h
index 76a38b0dab..8c5957bf9a 100644
--- a/src/nvim/state.h
+++ b/src/nvim/state.h
@@ -1,22 +1,7 @@
-#ifndef NVIM_STATE_H
-#define NVIM_STATE_H
+#pragma once
-#include <stddef.h>
-
-struct vim_state;
-
-typedef struct vim_state VimState;
-
-typedef int (*state_check_callback)(VimState *state);
-typedef int (*state_execute_callback)(VimState *state, int key);
-
-struct vim_state {
- state_check_callback check;
- state_execute_callback execute;
-};
+#include "nvim/state_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "state.h.generated.h"
#endif
-
-#endif // NVIM_STATE_H
diff --git a/src/nvim/state_defs.h b/src/nvim/state_defs.h
new file mode 100644
index 0000000000..0b32412f5a
--- /dev/null
+++ b/src/nvim/state_defs.h
@@ -0,0 +1,45 @@
+#pragma once
+
+typedef struct vim_state VimState;
+
+typedef int (*state_check_callback)(VimState *state);
+typedef int (*state_execute_callback)(VimState *state, int key);
+
+struct vim_state {
+ state_check_callback check;
+ state_execute_callback execute;
+};
+
+/// Values for State
+///
+/// The lower bits up to 0x80 are used to distinguish normal/visual/op_pending
+/// /cmdline/insert/replace/terminal mode. This is used for mapping. If none
+/// of these bits are set, no mapping is done. See the comment above do_map().
+/// The upper bits are used to distinguish between other states and variants of
+/// the base modes.
+enum {
+ MODE_NORMAL = 0x01, ///< Normal mode, command expected
+ MODE_VISUAL = 0x02, ///< Visual mode - use get_real_state()
+ MODE_OP_PENDING = 0x04, ///< Normal mode, operator is pending - use get_real_state()
+ MODE_CMDLINE = 0x08, ///< Editing the command line
+ MODE_INSERT = 0x10, ///< Insert mode, also for Replace mode
+ MODE_LANGMAP = 0x20, ///< Language mapping, can be combined with MODE_INSERT and MODE_CMDLINE
+ MODE_SELECT = 0x40, ///< Select mode, use get_real_state()
+ MODE_TERMINAL = 0x80, ///< Terminal mode
+
+ MAP_ALL_MODES = 0xff, ///< all mode bits used for mapping
+
+ REPLACE_FLAG = 0x100, ///< Replace mode flag
+ MODE_REPLACE = REPLACE_FLAG | MODE_INSERT,
+ VREPLACE_FLAG = 0x200, ///< Virtual-replace mode flag
+ MODE_VREPLACE = REPLACE_FLAG | VREPLACE_FLAG | MODE_INSERT,
+ MODE_LREPLACE = REPLACE_FLAG | MODE_LANGMAP,
+
+ MODE_NORMAL_BUSY = 0x1000 | MODE_NORMAL, ///< Normal mode, busy with a command
+ MODE_HITRETURN = 0x2000 | MODE_NORMAL, ///< waiting for return or command
+ MODE_ASKMORE = 0x3000, ///< Asking if you want --more--
+ MODE_SETWSIZE = 0x4000, ///< window size has changed
+ MODE_EXTERNCMD = 0x5000, ///< executing an external command
+ MODE_SHOWMATCH = 0x6000 | MODE_INSERT, ///< show matching paren
+ MODE_CONFIRM = 0x7000, ///< ":confirm" prompt
+};
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index 6ad1f31143..4dac1b1451 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -1,7 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-//
-
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
@@ -12,10 +8,12 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/digraph.h"
+#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -25,26 +23,25 @@
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/os.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
-#include "nvim/sign_defs.h"
+#include "nvim/plines.h"
+#include "nvim/pos_defs.h"
+#include "nvim/sign.h"
+#include "nvim/state_defs.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
#include "nvim/window.h"
// Determines how deeply nested %{} blocks will be evaluated in statusline.
@@ -62,13 +59,8 @@ typedef enum {
/// If inversion is possible we use it. Else '=' characters are used.
void win_redr_status(win_T *wp)
{
- int row;
- int col;
- char *p;
- int len;
int fillchar;
int attr;
- int this_ru_col;
bool is_stl_global = global_stl_height() > 0;
static bool busy = false;
@@ -97,8 +89,8 @@ void win_redr_status(win_T *wp)
const int stl_width = is_stl_global ? Columns : wp->w_width;
get_trans_bufname(wp->w_buffer);
- p = NameBuff;
- len = (int)strlen(p);
+ char *p = NameBuff;
+ int len = (int)strlen(p);
if ((bt_help(wp->w_buffer)
|| wp->w_p_pvw
@@ -124,7 +116,7 @@ void win_redr_status(win_T *wp)
// len += (int)strlen(p + len); // dead assignment
}
- this_ru_col = ru_col - (Columns - stl_width);
+ int this_ru_col = ru_col - (Columns - stl_width);
if (this_ru_col < (stl_width + 1) / 2) {
this_ru_col = (stl_width + 1) / 2;
}
@@ -132,10 +124,10 @@ void win_redr_status(win_T *wp)
p = "<"; // No room for file name!
len = 1;
} else {
- int clen = 0, i;
+ int i;
// Count total number of display cells.
- clen = (int)mb_string2cells(p);
+ int clen = (int)mb_string2cells(p);
// Find first character that will fit.
// Going from start to end is much faster for DBCS.
@@ -151,29 +143,29 @@ void win_redr_status(win_T *wp)
}
}
- row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
- col = is_stl_global ? 0 : wp->w_wincol;
- grid_puts(&default_grid, p, row, col, attr);
- grid_fill(&default_grid, row, row + 1, len + col,
- this_ru_col + col, fillchar, fillchar, attr);
+ grid_line_start(&default_grid, is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp));
+ int col = is_stl_global ? 0 : wp->w_wincol;
+
+ int width = grid_line_puts(col, p, -1, attr);
+ grid_line_fill(width + col, this_ru_col + col, fillchar, attr);
if (get_keymap_str(wp, "<%s>", NameBuff, MAXPATHL)
&& this_ru_col - len > (int)(strlen(NameBuff) + 1)) {
- grid_puts(&default_grid, NameBuff, row,
- (int)((size_t)this_ru_col - strlen(NameBuff) - 1), attr);
+ grid_line_puts((int)((size_t)this_ru_col - strlen(NameBuff) - 1), NameBuff, -1, attr);
}
- win_redr_ruler(wp, true);
+ win_redr_ruler(wp);
// Draw the 'showcmd' information if 'showcmdloc' == "statusline".
if (p_sc && *p_sloc == 's') {
const int sc_width = MIN(10, this_ru_col - len - 2);
if (sc_width > 0) {
- grid_puts_len(&default_grid, showcmd_buf, sc_width, row,
- wp->w_wincol + this_ru_col - sc_width - 1, attr);
+ grid_line_puts(wp->w_wincol + this_ru_col - sc_width - 1, showcmd_buf, sc_width, attr);
}
}
+
+ grid_line_flush();
}
// May need to draw the character below the vertical separator.
@@ -181,13 +173,48 @@ void win_redr_status(win_T *wp)
if (stl_connected(wp)) {
fillchar = fillchar_status(&attr, wp);
} else {
- fillchar = fillchar_vsep(wp, &attr);
+ attr = win_hl_attr(wp, HLF_C);
+ fillchar = wp->w_p_fcs_chars.vert;
}
- grid_putchar(&default_grid, fillchar, W_ENDROW(wp), W_ENDCOL(wp), attr);
+ grid_line_start(&default_grid, W_ENDROW(wp));
+ grid_line_put_schar(W_ENDCOL(wp), schar_from_char(fillchar), attr);
+ grid_line_flush();
}
busy = false;
}
+void get_trans_bufname(buf_T *buf)
+{
+ if (buf_spname(buf) != NULL) {
+ xstrlcpy(NameBuff, buf_spname(buf), MAXPATHL);
+ } else {
+ home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, true);
+ }
+ trans_characters(NameBuff, MAXPATHL);
+}
+
+/// Only call if (wp->w_vsep_width != 0).
+///
+/// @return true if the status line of window "wp" is connected to the status
+/// line of the window right of it. If not, then it's a vertical separator.
+bool stl_connected(win_T *wp)
+{
+ frame_T *fr = wp->w_frame;
+ while (fr->fr_parent != NULL) {
+ if (fr->fr_parent->fr_layout == FR_COL) {
+ if (fr->fr_next != NULL) {
+ break;
+ }
+ } else {
+ if (fr->fr_next != NULL) {
+ return true;
+ }
+ }
+ fr = fr->fr_parent;
+ }
+ return false;
+}
+
/// Clear status line, window bar or tab page line click definition table
///
/// @param[out] tpcd Table to clear.
@@ -205,7 +232,7 @@ void stl_clear_click_defs(StlClickDefinition *const click_defs, const size_t cli
}
/// Allocate or resize the click definitions array if needed.
-StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, size_t *size)
+StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, int width, size_t *size)
{
if (*size < (size_t)width) {
xfree(cdp);
@@ -216,8 +243,8 @@ StlClickDefinition *stl_alloc_click_defs(StlClickDefinition *cdp, long width, si
}
/// Fill the click definitions array if needed.
-void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs, char *buf,
- int width, bool tabline)
+void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_recs,
+ const char *buf, int width, bool tabline)
{
if (click_defs == NULL) {
return;
@@ -231,6 +258,7 @@ void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_r
};
for (int i = 0; click_recs[i].start != NULL; i++) {
len += vim_strnsize(buf, (int)(click_recs[i].start - buf));
+ assert(len <= width);
if (col < len) {
while (col < len) {
click_defs[col++] = cur_click_def;
@@ -238,7 +266,7 @@ void stl_fill_click_defs(StlClickDefinition *click_defs, StlClickRecord *click_r
} else {
xfree(cur_click_def.func);
}
- buf = (char *)click_recs[i].start;
+ buf = click_recs[i].start;
cur_click_def = click_recs[i].def;
if (!tabline && !(cur_click_def.type == kStlClickDisabled
|| cur_click_def.type == kStlClickFuncRun)) {
@@ -261,23 +289,17 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
{
static bool entered = false;
int attr;
- int curattr;
int row;
int col = 0;
int maxwidth;
- int width;
- int n;
- int len;
int fillchar;
char buf[MAXPATHL];
+ char transbuf[MAXPATHL];
char *stl;
- char *p;
char *opt_name;
int opt_scope = 0;
stl_hlrec_t *hltab;
StlClickRecord *tabtab;
- win_T *ewp;
- int p_crb_save;
bool is_stl_global = global_stl_height() > 0;
ScreenGrid *grid = &default_grid;
@@ -309,7 +331,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
grid_adjust(&grid, &row, &col);
if (row < 0) {
- return;
+ goto theend;
}
fillchar = wp->w_p_fcs_chars.wbr;
@@ -321,7 +343,8 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
} else {
row = is_stl_global ? (Rows - (int)p_ch - 1) : W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp);
- maxwidth = is_stl_global ? Columns : wp->w_width;
+ const bool in_status_line = wp->w_status_height != 0 || is_stl_global;
+ maxwidth = in_status_line && !is_stl_global ? wp->w_width : Columns;
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
wp->w_status_click_defs = stl_alloc_click_defs(wp->w_status_click_defs, maxwidth,
&wp->w_status_click_defs_size);
@@ -347,8 +370,8 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
if (col < (maxwidth + 1) / 2) {
col = (maxwidth + 1) / 2;
}
- maxwidth = maxwidth - col;
- if (!wp->w_status_height && !is_stl_global) {
+ maxwidth -= col;
+ if (!in_status_line) {
grid = &msg_grid_adj;
row = Rows - 1;
maxwidth--; // writing in last column may cause scrolling
@@ -361,7 +384,9 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
opt_scope = ((*wp->w_p_stl != NUL) ? OPT_LOCAL : 0);
}
- col += is_stl_global ? 0 : wp->w_wincol;
+ if (in_status_line && !is_stl_global) {
+ col += wp->w_wincol;
+ }
}
if (maxwidth <= 0) {
@@ -370,41 +395,34 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
// Temporarily reset 'cursorbind', we don't want a side effect from moving
// the cursor away and back.
- ewp = wp == NULL ? curwin : wp;
- p_crb_save = ewp->w_p_crb;
+ win_T *ewp = wp == NULL ? curwin : wp;
+ int p_crb_save = ewp->w_p_crb;
ewp->w_p_crb = false;
// Make a copy, because the statusline may include a function call that
// might change the option value and free the memory.
stl = xstrdup(stl);
- width = build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope,
- fillchar, maxwidth, &hltab, &tabtab, NULL);
+ build_stl_str_hl(ewp, buf, sizeof(buf), stl, opt_name, opt_scope,
+ fillchar, maxwidth, &hltab, &tabtab, NULL);
xfree(stl);
ewp->w_p_crb = p_crb_save;
- // Make all characters printable.
- p = transstr(buf, true);
- len = (int)xstrlcpy(buf, p, sizeof(buf));
- len = (size_t)len < sizeof(buf) ? len : (int)sizeof(buf) - 1;
- xfree(p);
-
- // fill up with "fillchar"
- while (width < maxwidth && len < (int)sizeof(buf) - 1) {
- len += utf_char2bytes(fillchar, buf + len);
- width++;
- }
- buf[len] = NUL;
+ int len = (int)strlen(buf);
+ int start_col = col;
// Draw each snippet with the specified highlighting.
- grid_puts_line_start(grid, row);
+ if (!draw_ruler) {
+ grid_line_start(grid, row);
+ }
- curattr = attr;
- p = buf;
- for (n = 0; hltab[n].start != NULL; n++) {
+ int curattr = attr;
+ char *p = buf;
+ for (int n = 0; hltab[n].start != NULL; n++) {
int textlen = (int)(hltab[n].start - p);
- grid_puts_len(grid, p, textlen, row, col, curattr);
- col += vim_strnsize(p, textlen);
+ // Make all characters printable.
+ size_t tsize = transstr_buf(p, textlen, transbuf, sizeof transbuf, true);
+ col += grid_line_puts(col, transbuf, (int)tsize, curattr);
p = hltab[n].start;
if (hltab[n].userhl == 0) {
@@ -418,9 +436,16 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
}
}
// Make sure to use an empty string instead of p, if p is beyond buf + len.
- grid_puts(grid, p >= buf + len ? "" : p, row, col, curattr);
+ size_t tsize = transstr_buf(p >= buf + len ? "" : p, -1, transbuf, sizeof transbuf, true);
+ col += grid_line_puts(col, transbuf, (int)tsize, curattr);
+ int maxcol = start_col + maxwidth;
- grid_puts_line_flush(false);
+ // fill up with "fillchar"
+ grid_line_fill(col, maxcol, fillchar, curattr);
+
+ if (!draw_ruler) {
+ grid_line_flush();
+ }
// Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
// in the tab page line, status line or window bar
@@ -453,7 +478,8 @@ void win_redr_winbar(win_T *wp)
entered = false;
}
-void win_redr_ruler(win_T *wp, bool always)
+/// must be called after a grid_line_start() at the intended row
+void win_redr_ruler(win_T *wp)
{
bool is_stl_global = global_stl_height() > 0;
static bool did_show_ext_ruler = false;
@@ -471,7 +497,8 @@ void win_redr_ruler(win_T *wp, bool always)
// Don't draw the ruler while doing insert-completion, it might overwrite
// the (long) mode message.
- if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) {
+ win_T *ruler_win = curwin->w_status_height == 0 ? curwin : lastwin_nofloating();
+ if (wp == ruler_win && ruler_win->w_status_height == 0 && !is_stl_global) {
if (edit_submode != NULL) {
return;
}
@@ -483,138 +510,105 @@ void win_redr_ruler(win_T *wp, bool always)
}
// Check if not in Insert mode and the line is empty (will show "0-1").
- int empty_line = false;
- if ((State & MODE_INSERT) == 0 && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, false) == NUL) {
- empty_line = true;
- }
+ int empty_line = (State & MODE_INSERT) == 0
+ && *ml_get_buf(wp->w_buffer, wp->w_cursor.lnum) == NUL;
- // Only draw the ruler when something changed.
- validate_virtcol_win(wp);
- if (redraw_cmdline
- || always
- || wp->w_cursor.lnum != wp->w_ru_cursor.lnum
- || wp->w_cursor.col != wp->w_ru_cursor.col
- || wp->w_virtcol != wp->w_ru_virtcol
- || wp->w_cursor.coladd != wp->w_ru_cursor.coladd
- || wp->w_topline != wp->w_ru_topline
- || wp->w_buffer->b_ml.ml_line_count != wp->w_ru_line_count
- || wp->w_topfill != wp->w_ru_topfill
- || empty_line != wp->w_ru_empty) {
- int width;
- int row;
- int fillchar;
- int attr;
- int off;
- bool part_of_status = false;
-
- if (wp->w_status_height) {
- row = W_ENDROW(wp);
- fillchar = fillchar_status(&attr, wp);
- off = wp->w_wincol;
- width = wp->w_width;
- part_of_status = true;
- } else if (is_stl_global) {
- row = Rows - (int)p_ch - 1;
- fillchar = fillchar_status(&attr, wp);
- off = 0;
- width = Columns;
- part_of_status = true;
- } else {
- row = Rows - 1;
- fillchar = ' ';
- attr = HL_ATTR(HLF_MSG);
- width = Columns;
- off = 0;
- }
+ int width;
+ int fillchar;
+ int attr;
+ int off;
+ bool part_of_status = false;
- if (!part_of_status && p_ch == 0 && !ui_has(kUIMessages)) {
- return;
- }
+ if (wp->w_status_height) {
+ fillchar = fillchar_status(&attr, wp);
+ off = wp->w_wincol;
+ width = wp->w_width;
+ part_of_status = true;
+ } else if (is_stl_global) {
+ fillchar = fillchar_status(&attr, wp);
+ off = 0;
+ width = Columns;
+ part_of_status = true;
+ } else {
+ fillchar = ' ';
+ attr = HL_ATTR(HLF_MSG);
+ width = Columns;
+ off = 0;
+ }
- // In list mode virtcol needs to be recomputed
- colnr_T virtcol = wp->w_virtcol;
- if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
- wp->w_p_list = false;
- getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
- wp->w_p_list = true;
- }
+ // In list mode virtcol needs to be recomputed
+ colnr_T virtcol = wp->w_virtcol;
+ if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) {
+ wp->w_p_list = false;
+ getvvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL);
+ wp->w_p_list = true;
+ }
#define RULER_BUF_LEN 70
- char buffer[RULER_BUF_LEN];
-
- // Some sprintfs return the length, some return a pointer.
- // To avoid portability problems we use strlen() here.
- vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
- (wp->w_buffer->b_ml.ml_flags &
- ML_EMPTY) ? (int64_t)0L : (int64_t)wp->w_cursor.lnum);
- size_t len = strlen(buffer);
- col_print(buffer + len, RULER_BUF_LEN - len,
- empty_line ? 0 : (int)wp->w_cursor.col + 1,
- (int)virtcol + 1);
-
- // Add a "50%" if there is room for it.
- // On the last line, don't print in the last column (scrolls the
- // screen up on some terminals).
- int i = (int)strlen(buffer);
- get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
- int o = i + vim_strsize(buffer + i + 1);
- if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
+ char buffer[RULER_BUF_LEN];
+
+ // Some sprintfs return the length, some return a pointer.
+ // To avoid portability problems we use strlen() here.
+ vim_snprintf(buffer, RULER_BUF_LEN, "%" PRId64 ",",
+ (wp->w_buffer->b_ml.ml_flags &
+ ML_EMPTY) ? 0 : (int64_t)wp->w_cursor.lnum);
+ size_t len = strlen(buffer);
+ col_print(buffer + len, RULER_BUF_LEN - len,
+ empty_line ? 0 : (int)wp->w_cursor.col + 1,
+ (int)virtcol + 1);
+
+ // Add a "50%" if there is room for it.
+ // On the last line, don't print in the last column (scrolls the
+ // screen up on some terminals).
+ int i = (int)strlen(buffer);
+ get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1);
+ int o = i + vim_strsize(buffer + i + 1);
+ if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
+ o++;
+ }
+ int this_ru_col = ru_col - (Columns - width);
+ if (this_ru_col < 0) {
+ this_ru_col = 0;
+ }
+ // Never use more than half the window/screen width, leave the other half
+ // for the filename.
+ if (this_ru_col < (width + 1) / 2) {
+ this_ru_col = (width + 1) / 2;
+ }
+ if (this_ru_col + o < width) {
+ // Need at least 3 chars left for get_rel_pos() + NUL.
+ while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
+ i += utf_char2bytes(fillchar, buffer + i);
o++;
}
- int this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < 0) {
- this_ru_col = 0;
- }
- // Never use more than half the window/screen width, leave the other half
- // for the filename.
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
- if (this_ru_col + o < width) {
- // Need at least 3 chars left for get_rel_pos() + NUL.
- while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
- i += utf_char2bytes(fillchar, buffer + i);
- o++;
- }
- get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
- }
+ get_rel_pos(wp, buffer + i, RULER_BUF_LEN - i);
+ }
- if (ui_has(kUIMessages) && !part_of_status) {
- MAXSIZE_TEMP_ARRAY(content, 1);
- MAXSIZE_TEMP_ARRAY(chunk, 2);
- ADD_C(chunk, INTEGER_OBJ(attr));
- ADD_C(chunk, STRING_OBJ(cstr_as_string((char *)buffer)));
- ADD_C(content, ARRAY_OBJ(chunk));
- ui_call_msg_ruler(content);
- did_show_ext_ruler = true;
- } else {
- if (did_show_ext_ruler) {
- ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
- did_show_ext_ruler = false;
- }
- // Truncate at window boundary.
- o = 0;
- for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
- o += utf_ptr2cells(buffer + i);
- if (this_ru_col + o > width) {
- buffer[i] = NUL;
- break;
- }
+ if (ui_has(kUIMessages) && !part_of_status) {
+ MAXSIZE_TEMP_ARRAY(content, 1);
+ MAXSIZE_TEMP_ARRAY(chunk, 2);
+ ADD_C(chunk, INTEGER_OBJ(attr));
+ ADD_C(chunk, CSTR_AS_OBJ(buffer));
+ ADD_C(content, ARRAY_OBJ(chunk));
+ ui_call_msg_ruler(content);
+ did_show_ext_ruler = true;
+ } else {
+ if (did_show_ext_ruler) {
+ ui_call_msg_ruler((Array)ARRAY_DICT_INIT);
+ did_show_ext_ruler = false;
+ }
+ // Truncate at window boundary.
+ o = 0;
+ for (i = 0; buffer[i] != NUL; i += utfc_ptr2len(buffer + i)) {
+ o += utf_ptr2cells(buffer + i);
+ if (this_ru_col + o > width) {
+ buffer[i] = NUL;
+ break;
}
-
- ScreenGrid *grid = part_of_status ? &default_grid : &msg_grid_adj;
- grid_puts(grid, buffer, row, this_ru_col + off, attr);
- grid_fill(grid, row, row + 1,
- this_ru_col + off + (int)strlen(buffer), off + width, fillchar,
- fillchar, attr);
}
- wp->w_ru_cursor = wp->w_cursor;
- wp->w_ru_virtcol = wp->w_virtcol;
- wp->w_ru_empty = (char)empty_line;
- wp->w_ru_topline = wp->w_topline;
- wp->w_ru_line_count = wp->w_buffer->b_ml.ml_line_count;
- wp->w_ru_topfill = wp->w_topfill;
+ int w = grid_line_puts(this_ru_col + off, buffer, -1, attr);
+ grid_line_fill(this_ru_col + off + w, off + width, fillchar, attr);
}
}
@@ -630,18 +624,7 @@ int fillchar_status(int *attr, win_T *wp)
*attr = win_hl_attr(wp, HLF_SNC);
fill = wp->w_p_fcs_chars.stlnc;
}
- // Use fill when there is highlighting, and highlighting of current
- // window differs, or the fillchars differ, or this is not the
- // current window
- if (*attr != 0 && ((win_hl_attr(wp, HLF_S) != win_hl_attr(wp, HLF_SNC)
- || !is_curwin || ONE_WINDOW)
- || (wp->w_p_fcs_chars.stl != wp->w_p_fcs_chars.stlnc))) {
- return fill;
- }
- if (is_curwin) {
- return '^';
- }
- return '=';
+ return fill;
}
/// Redraw the status line according to 'statusline' and take care of any
@@ -710,21 +693,9 @@ static void ui_ext_tabline_update(void)
/// Draw the tab pages line at the top of the Vim window.
void draw_tabline(void)
{
- int tabcount = 0;
- int tabwidth = 0;
- int col = 0;
- int scol = 0;
- int attr;
win_T *wp;
- win_T *cwp;
- int wincount;
- int modified;
- int c;
- int len;
int attr_nosel = HL_ATTR(HLF_TP);
int attr_fill = HL_ATTR(HLF_TPF);
- char *p;
- int room;
int use_sep_chars = (t_colors < 8);
if (default_grid.chars == NULL) {
@@ -749,6 +720,15 @@ void draw_tabline(void)
if (*p_tal != NUL) {
win_redr_custom(NULL, false, false);
} else {
+ int tabcount = 0;
+ int tabwidth = 0;
+ int col = 0;
+ win_T *cwp;
+ int wincount;
+ int c;
+ int len;
+ char *p;
+ grid_line_start(&default_grid, 0);
FOR_ALL_TABS(tp) {
tabcount++;
}
@@ -761,7 +741,7 @@ void draw_tabline(void)
tabwidth = 6;
}
- attr = attr_nosel;
+ int attr = attr_nosel;
tabcount = 0;
FOR_ALL_TABS(tp) {
@@ -769,7 +749,7 @@ void draw_tabline(void)
break;
}
- scol = col;
+ int scol = col;
if (tp == curtab) {
cwp = curwin;
@@ -783,16 +763,16 @@ void draw_tabline(void)
attr = win_hl_attr(cwp, HLF_TPS);
}
if (use_sep_chars && col > 0) {
- grid_putchar(&default_grid, '|', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii('|'), attr);
}
if (tp->tp_topframe != topframe) {
attr = win_hl_attr(cwp, HLF_TP);
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
- modified = false;
+ int modified = false;
for (wincount = 0; wp != NULL; wp = wp->w_next, wincount++) {
if (bufIsChanged(wp->w_buffer)) {
@@ -807,17 +787,17 @@ void draw_tabline(void)
if (col + len >= Columns - 3) {
break;
}
- grid_puts_len(&default_grid, NameBuff, len, 0, col,
- hl_combine_attr(attr, win_hl_attr(cwp, HLF_T)));
+ grid_line_puts(col, NameBuff, len,
+ hl_combine_attr(attr, win_hl_attr(cwp, HLF_T)));
col += len;
}
if (modified) {
- grid_puts_len(&default_grid, "+", 1, 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii('+'), attr);
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
}
- room = scol - col + tabwidth - 1;
+ int room = scol - col + tabwidth - 1;
if (room > 0) {
// Get buffer name in NameBuff[]
get_trans_bufname(cwp->w_buffer);
@@ -832,10 +812,10 @@ void draw_tabline(void)
len = Columns - col - 1;
}
- grid_puts_len(&default_grid, p, (int)strlen(p), 0, col, attr);
+ grid_line_puts(col, p, -1, attr);
col += len;
}
- grid_putchar(&default_grid, ' ', 0, col++, attr);
+ grid_line_put_schar(col++, schar_from_ascii(' '), attr);
// Store the tab page number in tab_page_click_defs[], so that
// jump_to_mouse() knows where each one is.
@@ -854,27 +834,29 @@ void draw_tabline(void)
} else {
c = ' ';
}
- grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill);
+ grid_line_fill(col, Columns, c, attr_fill);
// Draw the 'showcmd' information if 'showcmdloc' == "tabline".
if (p_sc && *p_sloc == 't') {
const int sc_width = MIN(10, (int)Columns - col - (tabcount > 1) * 3);
if (sc_width > 0) {
- grid_puts_len(&default_grid, showcmd_buf, sc_width, 0,
- Columns - sc_width - (tabcount > 1) * 2, attr_nosel);
+ grid_line_puts(Columns - sc_width - (tabcount > 1) * 2,
+ showcmd_buf, sc_width, attr_nosel);
}
}
// Put an "X" for closing the current tab if there are several.
if (tabcount > 1) {
- grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel);
+ grid_line_put_schar(Columns - 1, schar_from_ascii('X'), attr_nosel);
tab_page_click_defs[Columns - 1] = (StlClickDefinition) {
.type = kStlClickTabClose,
.tabnr = 999,
.func = NULL,
};
}
+
+ grid_line_flush();
}
// Reset the flag here again, in case evaluating 'tabline' causes it to be
@@ -885,13 +867,12 @@ void draw_tabline(void)
/// Build the 'statuscolumn' string for line "lnum". When "relnum" == -1,
/// the v:lnum and v:relnum variables don't have to be updated.
///
-/// @param hlrec HL attributes (can be NULL)
-/// @param stcp Status column attributes (can be NULL)
/// @return The width of the built status column string for line "lnum"
-int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int fillchar,
- char *buf, stl_hlrec_t **hlrec, statuscol_T *stcp)
+int build_statuscol_str(win_T *wp, linenr_T lnum, linenr_T relnum, statuscol_T *stcp)
{
- bool fillclick = relnum >= 0 && lnum == wp->w_topline;
+ // Only update click definitions once per window per redraw.
+ // Don't update when current width is 0, since it will be redrawn again if not empty.
+ const bool fillclick = relnum >= 0 && stcp->width > 0 && lnum == wp->w_topline;
if (relnum >= 0) {
set_vim_var_nr(VV_LNUM, lnum);
@@ -900,16 +881,15 @@ int build_statuscol_str(win_T *wp, linenr_T lnum, long relnum, int maxwidth, int
StlClickRecord *clickrec;
char *stc = xstrdup(wp->w_p_stc);
- int width = build_stl_str_hl(wp, buf, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, fillchar,
- maxwidth, hlrec, fillclick ? &clickrec : NULL, stcp);
+ int width = build_stl_str_hl(wp, stcp->text, MAXPATHL, stc, "statuscolumn", OPT_LOCAL, ' ',
+ stcp->width, &stcp->hlrec, fillclick ? &clickrec : NULL, stcp);
xfree(stc);
- // Only update click definitions once per window per redraw
if (fillclick) {
stl_clear_click_defs(wp->w_statuscol_click_defs, wp->w_statuscol_click_defs_size);
- wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, width,
+ wp->w_statuscol_click_defs = stl_alloc_click_defs(wp->w_statuscol_click_defs, stcp->width,
&wp->w_statuscol_click_defs_size);
- stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, buf, width, false);
+ stl_fill_click_defs(wp->w_statuscol_click_defs, clickrec, stcp->text, stcp->width, false);
}
return width;
@@ -971,7 +951,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Allocate one more, because the last element is used to indicate the
// end of the list.
- stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * (stl_items_len + 1));
stl_tabtab = xmalloc(sizeof(StlClickRecord) * (stl_items_len + 1));
stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
@@ -991,7 +971,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
};
set_var(S_LEN("g:statusline_winid"), &tv, false);
- usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox);
+ usefmt = eval_to_string_safe(fmt + 2, use_sandbox);
if (usefmt == NULL) {
usefmt = fmt;
}
@@ -1012,7 +992,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// Get line & check if empty (cursorpos will show "0-1").
- const char *line_ptr = ml_get_buf(wp->w_buffer, lnum, false);
+ const char *line_ptr = ml_get_buf(wp->w_buffer, lnum);
bool empty_line = (*line_ptr == NUL);
// Get the byte value now, in case we need it below. This is more
@@ -1030,9 +1010,10 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
int groupdepth = 0;
- int evaldepth = 0;
+ int evaldepth = 0;
int curitem = 0;
+ int foldsignitem = -1;
bool prevchar_isflag = true;
bool prevchar_isitem = false;
@@ -1095,7 +1076,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
continue;
}
- // STL_SEPARATE: Separation place between left and right aligned items.
+ // STL_SEPARATE: Separation between items, filled with white space.
if (*fmt_p == STL_SEPARATE) {
fmt_p++;
// Ignored when we are inside of a grouping
@@ -1182,7 +1163,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// { Determine the number of bytes to remove
// Find the first character that should be included.
- long n = 0;
+ int n = 0;
while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) {
group_len -= ptr2cells(t + n);
n += utfc_ptr2len(t + n);
@@ -1307,7 +1288,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (*fmt_p == STL_TABCLOSENR) {
if (minwid == 0) {
// %X ends the close label, go back to the previous tab label nr.
- for (long n = curitem - 1; n >= 0; n--) {
+ for (int n = curitem - 1; n >= 0; n--) {
if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) {
minwid = stl_items[n].minwid;
break;
@@ -1379,6 +1360,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// An invalid item was specified.
// Continue processing on the next character of the format string.
if (vim_strchr(STL_ALL, (uint8_t)(*fmt_p)) == NULL) {
+ if (*fmt_p == NUL) { // can happen with "%0"
+ break;
+ }
fmt_p++;
continue;
}
@@ -1390,7 +1374,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
NumberBase base = kNumBaseDecimal;
bool itemisflag = false;
bool fillable = true;
- long num = -1;
+ int num = -1;
char *str = NULL;
switch (opt) {
case STL_FILEPATH:
@@ -1403,7 +1387,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
xstrlcpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL);
} else {
char *t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname
- : wp->w_buffer->b_fname;
+ : wp->w_buffer->b_fname;
home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, true);
}
trans_characters(NameBuff, MAXPATHL);
@@ -1449,8 +1433,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Store the current buffer number as a string variable
vim_snprintf(buf_tmp, sizeof(buf_tmp), "%d", curbuf->b_fnum);
set_internal_string_var("g:actual_curbuf", buf_tmp);
- vim_snprintf((char *)win_tmp, sizeof(win_tmp), "%d", curwin->handle);
- set_internal_string_var("g:actual_curwin", (char *)win_tmp);
+ vim_snprintf(win_tmp, sizeof(win_tmp), "%d", curwin->handle);
+ set_internal_string_var("g:actual_curwin", win_tmp);
buf_T *const save_curbuf = curbuf;
win_T *const save_curwin = curwin;
@@ -1463,7 +1447,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// Note: The result stored in `t` is unused.
- str = eval_to_string_safe(out_p, &t, use_sandbox);
+ str = eval_to_string_safe(out_p, use_sandbox);
curwin = save_curwin;
curbuf = save_curbuf;
@@ -1488,7 +1472,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// If the output of the expression needs to be evaluated
// replace the %{} block with the result of evaluation
if (reevaluate && str != NULL && *str != 0
- && strchr((const char *)str, '%') != NULL
+ && strchr(str, '%') != NULL
&& evaldepth < MAX_STL_EVAL_DEPTH) {
size_t parsed_usefmt = (size_t)(block_start - usefmt);
size_t str_length = strlen(str);
@@ -1518,12 +1502,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
case STL_LINE:
// Overload %l with v:lnum for 'statuscolumn'
- if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) {
+ if (stcp != NULL) {
if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = get_vim_var_nr(VV_LNUM);
+ num = (int)get_vim_var_nr(VV_LNUM);
}
} else {
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0L : (long)(wp->w_cursor.lnum);
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0 : wp->w_cursor.lnum;
}
break;
@@ -1544,13 +1528,12 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
? 0 : (int)wp->w_cursor.col + 1))) {
break;
}
- num = (long)virtcol;
+ num = virtcol;
break;
}
case STL_PERCENTAGE:
- num = (int)(((long)wp->w_cursor.lnum * 100L) /
- (long)wp->w_buffer->b_ml.ml_line_count);
+ num = ((wp->w_cursor.lnum * 100) / wp->w_buffer->b_ml.ml_line_count);
break;
case STL_ALTPERCENT:
@@ -1578,7 +1561,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Note: The call will only return true if it actually
// appended data to the `buf_tmp` buffer.
- if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp), false)) {
+ if (append_arg_number(wp, buf_tmp, (int)sizeof(buf_tmp))) {
str = buf_tmp;
}
break;
@@ -1601,11 +1584,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
base = kNumBaseHexadecimal;
FALLTHROUGH;
case STL_OFFSET: {
- long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
- false);
- num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ?
- 0L : l + 1 + ((State & MODE_INSERT) == 0 && empty_line ?
- 0 : (int)wp->w_cursor.col);
+ int l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL,
+ false);
+ num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0
+ ? 0 : l + 1 + ((State & MODE_INSERT) == 0 && empty_line
+ ? 0 : (int)wp->w_cursor.col);
break;
}
case STL_BYTEVAL_X:
@@ -1623,9 +1606,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
case STL_ROFLAG:
case STL_ROFLAG_ALT:
// Overload %r with v:relnum for 'statuscolumn'
- if (opt_name != NULL && strcmp(opt_name, "statuscolumn") == 0) {
+ if (stcp != NULL) {
if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = get_vim_var_nr(VV_RELNUM);
+ num = (int)get_vim_var_nr(VV_RELNUM);
}
} else {
itemisflag = true;
@@ -1648,25 +1631,40 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (stcp == NULL) {
break;
}
-
bool fold = opt == STL_FOLDCOL;
- *buf_tmp = NUL;
- for (int i = 0; i <= SIGN_SHOW_MAX; i++) {
- char *p = fold ? stcp->fold_text : stcp->sign_text[i];
- if ((!p || !*p) && *buf_tmp == NUL) {
- break;
+ int width = fold ? (compute_foldcolumn(wp, 0) > 0) : wp->w_scwidth;
+
+ if (width == 0) {
+ break;
+ }
+ foldsignitem = curitem;
+
+ char *p = NULL;
+ if (fold) {
+ size_t n = fill_foldcolumn(out_p, wp, stcp->foldinfo,
+ (linenr_T)get_vim_var_nr(VV_LNUM), NULL);
+ stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1);
+ p = out_p;
+ p[n] = NUL;
+ }
+
+ size_t buflen = 0;
+ varnumber_T virtnum = get_vim_var_nr(VV_VIRTNUM);
+ for (int i = 0; i < width; i++) {
+ if (!fold) {
+ SignTextAttrs *sattr = virtnum ? NULL : &stcp->sattrs[i];
+ p = sattr && sattr->text ? sattr->text : " ";
+ stl_items[curitem].minwid = -(sattr && sattr->text
+ ? (stcp->sign_cul_id ? stcp->sign_cul_id : sattr->hl_id)
+ : (stcp->use_cul ? HLF_CLS : HLF_SC) + 1);
}
stl_items[curitem].type = Highlight;
- stl_items[curitem].start = out_p + strlen(buf_tmp);
- stl_items[curitem].minwid = !p || (fold && i) ? 0 : fold ? stcp->fold_attr
- : stcp->sign_attr[i];
+ stl_items[curitem].start = out_p + buflen;
+ xstrlcpy(buf_tmp + buflen, p, TMPLEN - buflen);
+ buflen += strlen(p);
curitem++;
- if (!p || (fold && i)) {
- str = buf_tmp;
- break;
- }
- STRCAT(buf_tmp, p);
}
+ str = buf_tmp;
break;
}
@@ -1773,7 +1771,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// }
- long l = vim_strsize(t);
+ int l = vim_strsize(t);
// If this item is non-empty, record that the last thing
// we put in the output buffer was an item
@@ -1809,6 +1807,13 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
}
minwid = 0;
+ // For a 'statuscolumn' sign or fold item, shift the added items
+ if (foldsignitem >= 0) {
+ ptrdiff_t offset = out_p - stl_items[foldsignitem].start;
+ for (int i = foldsignitem; i < curitem; i++) {
+ stl_items[i].start += offset;
+ }
+ }
} else {
// Note: The negative value denotes a left aligned item.
// Here we switch the minimum width back to a positive value.
@@ -1828,6 +1833,14 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
}
// }
+ // For a 'statuscolumn' sign or fold item, add an item to reset the highlight group
+ if (foldsignitem >= 0) {
+ foldsignitem = -1;
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = 0;
+ }
+
// For left-aligned items, fill any remaining space with the fillchar
for (; l < minwid && out_p < out_end_p; l++) {
MB_CHAR2BYTES(fillchar, out_p);
@@ -1861,8 +1874,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// { Determine how many characters the number will take up when printed
// Note: We have to cast the base because the compiler uses
// unsigned ints for the enum values.
- long num_chars = 1;
- for (long n = num; n >= (int)base; n /= (int)base) {
+ int num_chars = 1;
+ for (int n = num; n >= (int)base; n /= (int)base) {
num_chars++;
}
@@ -1885,11 +1898,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
num_chars += 2;
// How many extra characters there are
- long n = num_chars - maxwid;
+ int n = num_chars - maxwid;
// { Reduce the number by base^n
while (num_chars-- > maxwid) {
- num /= (long)base;
+ num /= (int)base;
}
// }
@@ -1942,8 +1955,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int width = vim_strsize(out);
// Return truncated width for 'statuscolumn'
- if (stcp != NULL && width > maxwidth) {
- stcp->truncate = width - maxwidth;
+ if (stcp != NULL && width > stcp->width) {
+ stcp->truncate = width - stcp->width;
}
if (maxwidth > 0 && width > maxwidth) {
// Result is too long, must truncate somewhere.
@@ -1977,7 +1990,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// string to find the last character that will fit.
trunc_p = out;
width = 0;
- for (;;) {
+ while (true) {
width += ptr2cells(trunc_p);
if (width >= maxwidth) {
break;
@@ -2009,9 +2022,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Truncate at the truncation point we found
} else {
// { Determine how many bytes to remove
- long trunc_len = 0;
+ int trunc_len = 0;
while (width >= maxwidth) {
- width -= ptr2cells(trunc_p + trunc_len);
+ width -= ptr2cells(trunc_p + trunc_len);
trunc_len += utfc_ptr2len(trunc_p + trunc_len);
}
// }
@@ -2022,17 +2035,6 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Put a `<` to mark where we truncated at
*trunc_p = '<';
-
- if (width + 1 < maxwidth) {
- // Advance the pointer to the end of the string
- trunc_p = trunc_p + strlen(trunc_p);
- }
-
- // Fill up for half a double-wide character.
- while (++width < maxwidth) {
- MB_CHAR2BYTES(fillchar, trunc_p);
- *trunc_p = NUL;
- }
// }
// { Change the start point for items based on
@@ -2040,20 +2042,31 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
// Note: The offset is one less than the truncation length because
// the truncation marker `<` is not counted.
- long item_offset = trunc_len - 1;
+ int item_offset = trunc_len - 1;
for (int i = item_idx; i < itemcnt; i++) {
// Items starting at or after the end of the truncated section need
// to be moved backwards.
if (stl_items[i].start >= trunc_end_p) {
stl_items[i].start -= item_offset;
+ } else {
// Anything inside the truncated area is set to start
// at the `<` truncation character.
- } else {
stl_items[i].start = trunc_p;
}
}
// }
+
+ if (width + 1 < maxwidth) {
+ // Advance the pointer to the end of the string
+ trunc_p = trunc_p + strlen(trunc_p);
+ }
+
+ // Fill up for half a double-wide character.
+ while (++width < maxwidth) {
+ MB_CHAR2BYTES(fillchar, trunc_p);
+ *trunc_p = NUL;
+ }
}
width = maxwidth;
@@ -2067,8 +2080,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int num_separators = 0;
for (int i = 0; i < itemcnt; i++) {
if (stl_items[i].type == Separate) {
- // Create an array of the start location for each
- // separator mark.
+ // Create an array of the start location for each separator mark.
stl_separator_locations[num_separators] = i;
num_separators++;
}
@@ -2080,17 +2092,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
int final_spaces = (maxwidth - width) -
standard_spaces * (num_separators - 1);
- for (int i = 0; i < num_separators; i++) {
- int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces;
+ for (int l = 0; l < num_separators; l++) {
+ int dislocation = (l == (num_separators - 1)) ? final_spaces : standard_spaces;
dislocation *= utf_char2len(fillchar);
- char *start = stl_items[stl_separator_locations[i]].start;
+ char *start = stl_items[stl_separator_locations[l]].start;
char *seploc = start + dislocation;
STRMOVE(seploc, start);
for (char *s = start; s < seploc;) {
MB_CHAR2BYTES(fillchar, s);
}
- for (int item_idx = stl_separator_locations[i] + 1;
+ for (int item_idx = stl_separator_locations[l] + 1;
item_idx < itemcnt;
item_idx++) {
stl_items[item_idx].start += dislocation;
@@ -2105,7 +2117,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (hltab != NULL) {
*hltab = stl_hltab;
stl_hlrec_t *sp = stl_hltab;
- for (long l = 0; l < itemcnt; l++) {
+ for (int l = 0; l < itemcnt; l++) {
if (stl_items[l].type == Highlight) {
sp->start = stl_items[l].start;
sp->userhl = stl_items[l].minwid;
@@ -2120,7 +2132,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, char *opt_n
if (tabtab != NULL) {
*tabtab = stl_tabtab;
StlClickRecord *cur_tab_rec = stl_tabtab;
- for (long l = 0; l < itemcnt; l++) {
+ for (int l = 0; l < itemcnt; l++) {
if (stl_items[l].type == TabPage) {
cur_tab_rec->start = stl_items[l].start;
if (stl_items[l].minwid == 0) {
diff --git a/src/nvim/statusline.h b/src/nvim/statusline.h
index f7e36f138c..eab7c1aa47 100644
--- a/src/nvim/statusline.h
+++ b/src/nvim/statusline.h
@@ -1,19 +1,16 @@
-#ifndef NVIM_STATUSLINE_H
-#define NVIM_STATUSLINE_H
+#pragma once
#include <stddef.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/statusline_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/statusline_defs.h" // IWYU pragma: export
/// Array defining what should be done when tabline is clicked
-EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL);
+EXTERN StlClickDefinition *tab_page_click_defs INIT( = NULL);
/// Size of the tab_page_click_defs array
-EXTERN size_t tab_page_click_defs_size INIT(= 0);
+EXTERN size_t tab_page_click_defs_size INIT( = 0);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "statusline.h.generated.h"
#endif
-
-#endif // NVIM_STATUSLINE_H
diff --git a/src/nvim/statusline_defs.h b/src/nvim/statusline_defs.h
index eac9dfd690..c1a54f4f54 100644
--- a/src/nvim/statusline_defs.h
+++ b/src/nvim/statusline_defs.h
@@ -1,9 +1,12 @@
-#ifndef NVIM_STATUSLINE_DEFS_H
-#define NVIM_STATUSLINE_DEFS_H
+#pragma once
+#include <stdbool.h>
#include <stddef.h>
-#include "nvim/macros.h"
+#include "nvim/fold_defs.h"
+#include "nvim/macros_defs.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/sign_defs.h"
/// Status line click definition
typedef struct {
@@ -23,4 +26,52 @@ typedef struct {
const char *start; ///< Location where region starts.
} StlClickRecord;
-#endif // NVIM_STATUSLINE_DEFS_H
+/// Used for highlighting in the status line.
+typedef struct stl_hlrec stl_hlrec_t;
+struct stl_hlrec {
+ char *start;
+ int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
+};
+
+/// Used for building the status line.
+typedef struct stl_item stl_item_t;
+struct stl_item {
+ // Where the item starts in the status line output buffer
+ char *start;
+ // Function to run for ClickFunc items.
+ char *cmd;
+ // The minimum width of the item
+ int minwid;
+ // The maximum width of the item
+ int maxwid;
+ enum {
+ Normal,
+ Empty,
+ Group,
+ Separate,
+ Highlight,
+ TabPage,
+ ClickFunc,
+ Trunc,
+ } type;
+};
+
+/// Struct to hold info for 'statuscolumn'
+typedef struct statuscol statuscol_T;
+
+struct statuscol {
+ int width; ///< width of the status column
+ int cur_attr; ///< current attributes in text
+ int num_attr; ///< default highlight attr
+ int sign_cul_id; ///< cursorline sign highlight id
+ int truncate; ///< truncated width
+ bool draw; ///< whether to draw the statuscolumn
+ bool use_cul; ///< whether to use cursorline attrs
+ char text[MAXPATHL]; ///< text in status column
+ char *textp; ///< current position in text
+ char *text_end; ///< end of text (the NUL byte)
+ stl_hlrec_t *hlrec; ///< highlight groups
+ stl_hlrec_t *hlrecp; ///< current highlight group
+ foldinfo_T foldinfo; ///< fold information
+ SignTextAttrs *sattrs; ///< sign attributes
+};
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 34b3c38103..a439d11818 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <inttypes.h>
#include <math.h>
@@ -12,23 +9,56 @@
#include <string.h>
#include "auto/config.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/garray.h"
#include "nvim/gettext.h"
-#include "nvim/macros.h"
+#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/plines.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+static const char e_cannot_mix_positional_and_non_positional_str[]
+ = N_("E1500: Cannot mix positional and non-positional arguments: %s");
+static const char e_fmt_arg_nr_unused_str[]
+ = N_("E1501: format argument %d unused in $-style format: %s");
+static const char e_positional_num_field_spec_reused_str_str[]
+ = N_("E1502: Positional argument %d used as field width reused as different type: %s/%s");
+static const char e_positional_nr_out_of_bounds_str[]
+ = N_("E1503: Positional argument %d out of bounds: %s");
+static const char e_positional_arg_num_type_inconsistent_str_str[]
+ = N_("E1504: Positional argument %d type used inconsistently: %s/%s");
+static const char e_invalid_format_specifier_str[]
+ = N_("E1505: Invalid format specifier: %s");
+static const char e_aptypes_is_null_nr_str[]
+ = "E1507: Internal error: ap_types or ap_types[idx] is NULL: %d: %s";
+
+static const char typename_unknown[] = N_("unknown");
+static const char typename_int[] = N_("int");
+static const char typename_longint[] = N_("long int");
+static const char typename_longlongint[] = N_("long long int");
+static const char typename_signedsizet[] = N_("signed size_t");
+static const char typename_unsignedint[] = N_("unsigned int");
+static const char typename_unsignedlongint[] = N_("unsigned long int");
+static const char typename_unsignedlonglongint[] = N_("unsigned long long int");
+static const char typename_sizet[] = N_("size_t");
+static const char typename_pointer[] = N_("pointer");
+static const char typename_percent[] = N_("percent");
+static const char typename_char[] = N_("char");
+static const char typename_string[] = N_("string");
+static const char typename_float[] = N_("float");
/// Copy up to `len` bytes of `string` into newly allocated memory and
/// terminate with a NUL. The allocated memory always has size `len + 1`, even
@@ -350,18 +380,6 @@ void del_trailing_spaces(char *ptr)
}
}
-#if !defined(HAVE_STRNLEN)
-size_t xstrnlen(const char *s, size_t n)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
-{
- const char *end = memchr(s, '\0', n);
- if (end == NULL) {
- return n;
- }
- return (size_t)(end - s);
-}
-#endif
-
#if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP))
// Compare two strings, ignoring case, using current locale.
// Doesn't work for multi-byte characters.
@@ -371,7 +389,7 @@ int vim_stricmp(const char *s1, const char *s2)
{
int i;
- for (;;) {
+ while (true) {
i = (int)TOLOWER_LOC((uint8_t)(*s1)) - (int)TOLOWER_LOC((uint8_t)(*s2));
if (i != 0) {
return i; // this character different
@@ -411,6 +429,13 @@ int vim_strnicmp(const char *s1, const char *s2, size_t len)
}
#endif
+/// Case-insensitive `strequal`.
+bool striequal(const char *a, const char *b)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (a == NULL && b == NULL) || (a && b && STRICMP(a, b) == 0);
+}
+
/// strchr() version which handles multibyte strings
///
/// @param[in] string String to search in.
@@ -455,10 +480,8 @@ void sort_strings(char **files, int count)
bool has_non_ascii(const char *s)
FUNC_ATTR_PURE
{
- const char *p;
-
if (s != NULL) {
- for (p = s; *p != NUL; p++) {
+ for (const char *p = s; *p != NUL; p++) {
if ((uint8_t)(*p) >= 128) {
return true;
}
@@ -498,13 +521,10 @@ static const char *const e_printf =
/// Get number argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
-/// insufficient entries.
+/// Will give an error message for Vimscript entry with invalid type or for insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
-/// value.
-/// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts
-/// at 1.
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN value.
+/// @param[in,out] idxp Index in a list. Will be incremented. Indexing starts at 1.
///
/// @return Number value or 0 in case of error.
static varnumber_T tv_nr(typval_T *tvs, int *idxp)
@@ -528,10 +548,10 @@ static varnumber_T tv_nr(typval_T *tvs, int *idxp)
/// Get string argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN
/// value.
/// @param[in,out] idxp Index in a list. Will be incremented.
/// @param[out] tofree If the idxp entry in tvs is not a String or a Number,
@@ -562,7 +582,7 @@ static const char *tv_str(typval_T *tvs, int *idxp, char **const tofree)
/// Get pointer argument from the next entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
/// @param[in] tvs List of typval_T values.
@@ -573,7 +593,7 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
#define OFF(attr) offsetof(union typval_vval_union, attr)
- STATIC_ASSERT(OFF(v_string) == OFF(v_list) // -V568
+ STATIC_ASSERT(OFF(v_string) == OFF(v_list)
&& OFF(v_string) == OFF(v_dict)
&& OFF(v_string) == OFF(v_partial)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list)
@@ -593,11 +613,10 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
/// Get float argument from idxp entry in tvs
///
-/// Will give an error message for VimL entry with invalid type or for
+/// Will give an error message for Vimscript entry with invalid type or for
/// insufficient entries.
///
-/// @param[in] tvs List of VimL values. List is terminated by VAR_UNKNOWN
-/// value.
+/// @param[in] tvs List of Vimscript values. List is terminated by VAR_UNKNOWN value.
/// @param[in,out] idxp Index in a list. Will be incremented.
///
/// @return Floating-point value or zero in case of error.
@@ -719,23 +738,608 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap)
return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL);
}
+enum {
+ TYPE_UNKNOWN = -1,
+ TYPE_INT,
+ TYPE_LONGINT,
+ TYPE_LONGLONGINT,
+ TYPE_SIGNEDSIZET,
+ TYPE_UNSIGNEDINT,
+ TYPE_UNSIGNEDLONGINT,
+ TYPE_UNSIGNEDLONGLONGINT,
+ TYPE_SIZET,
+ TYPE_POINTER,
+ TYPE_PERCENT,
+ TYPE_CHAR,
+ TYPE_STRING,
+ TYPE_FLOAT,
+};
+
+/// Types that can be used in a format string
+static int format_typeof(const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // current conversion specifier character
+ char fmt_spec = '\0';
+
+ // parse 'h', 'l', 'll' and 'z' length modifiers
+ if (*type == 'h' || *type == 'l' || *type == 'z') {
+ length_modifier = *type;
+ type++;
+ if (length_modifier == 'l' && *type == 'l') {
+ // double l = long long
+ length_modifier = 'L';
+ type++;
+ }
+ }
+ fmt_spec = *type;
+
+ // common synonyms:
+ switch (fmt_spec) {
+ case 'i':
+ fmt_spec = 'd'; break;
+ case '*':
+ fmt_spec = 'd'; length_modifier = 'h'; break;
+ case 'D':
+ fmt_spec = 'd'; length_modifier = 'l'; break;
+ case 'U':
+ fmt_spec = 'u'; length_modifier = 'l'; break;
+ case 'O':
+ fmt_spec = 'o'; length_modifier = 'l'; break;
+ default:
+ break;
+ }
+
+ // get parameter value, do initial processing
+ switch (fmt_spec) {
+ // '%' and 'c' behave similar to 's' regarding flags and field
+ // widths
+ case '%':
+ return TYPE_PERCENT;
+
+ case 'c':
+ return TYPE_CHAR;
+
+ case 's':
+ case 'S':
+ return TYPE_STRING;
+
+ case 'd':
+ case 'u':
+ case 'b':
+ case 'B':
+ case 'o':
+ case 'x':
+ case 'X':
+ case 'p':
+ // NOTE: the u, b, o, x, X and p conversion specifiers
+ // imply the value is unsigned; d implies a signed
+ // value
+
+ // 0 if numeric argument is zero (or if pointer is
+ // NULL for 'p'), +1 if greater than zero (or nonzero
+ // for unsigned arguments), -1 if negative (unsigned
+ // argument is never negative)
+
+ if (fmt_spec == 'p') {
+ return TYPE_POINTER;
+ } else if (fmt_spec == 'b' || fmt_spec == 'B') {
+ return TYPE_UNSIGNEDLONGLONGINT;
+ } else if (fmt_spec == 'd') {
+ // signed
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ // char and short arguments are passed as int.
+ return TYPE_INT;
+ case 'l':
+ return TYPE_LONGINT;
+ case 'L':
+ return TYPE_LONGLONGINT;
+ case 'z':
+ return TYPE_SIGNEDSIZET;
+ }
+ } else {
+ // unsigned
+ switch (length_modifier) {
+ case '\0':
+ case 'h':
+ return TYPE_UNSIGNEDINT;
+ case 'l':
+ return TYPE_UNSIGNEDLONGINT;
+ case 'L':
+ return TYPE_UNSIGNEDLONGLONGINT;
+ case 'z':
+ return TYPE_SIZET;
+ }
+ }
+ break;
+
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ return TYPE_FLOAT;
+ }
+
+ return TYPE_UNKNOWN;
+}
+
+static char *format_typename(const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (format_typeof(type)) {
+ case TYPE_INT:
+ return _(typename_int);
+ case TYPE_LONGINT:
+ return _(typename_longint);
+ case TYPE_LONGLONGINT:
+ return _(typename_longlongint);
+ case TYPE_UNSIGNEDINT:
+ return _(typename_unsignedint);
+ case TYPE_SIGNEDSIZET:
+ return _(typename_signedsizet);
+ case TYPE_UNSIGNEDLONGINT:
+ return _(typename_unsignedlongint);
+ case TYPE_UNSIGNEDLONGLONGINT:
+ return _(typename_unsignedlonglongint);
+ case TYPE_SIZET:
+ return _(typename_sizet);
+ case TYPE_POINTER:
+ return _(typename_pointer);
+ case TYPE_PERCENT:
+ return _(typename_percent);
+ case TYPE_CHAR:
+ return _(typename_char);
+ case TYPE_STRING:
+ return _(typename_string);
+ case TYPE_FLOAT:
+ return _(typename_float);
+ }
+
+ return _(typename_unknown);
+}
+
+static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const char *type)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*ap_types == NULL || *num_posarg < arg) {
+ const char **new_types = *ap_types == NULL
+ ? xcalloc(sizeof(const char *), (size_t)arg)
+ : xrealloc(*ap_types, (size_t)arg * sizeof(const char *));
+
+ for (int idx = *num_posarg; idx < arg; idx++) {
+ new_types[idx] = NULL;
+ }
+
+ *ap_types = new_types;
+ *num_posarg = arg;
+ }
+
+ if ((*ap_types)[arg - 1] != NULL) {
+ if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*') {
+ const char *pt = type;
+ if (pt[0] == '*') {
+ pt = (*ap_types)[arg - 1];
+ }
+
+ if (pt[0] != '*') {
+ switch (pt[0]) {
+ case 'd':
+ case 'i':
+ break;
+ default:
+ semsg(_(e_positional_num_field_spec_reused_str_str), arg,
+ format_typename((*ap_types)[arg - 1]), format_typename(type));
+ return FAIL;
+ }
+ }
+ } else {
+ if (format_typeof(type) != format_typeof((*ap_types)[arg - 1])) {
+ semsg(_(e_positional_arg_num_type_inconsistent_str_str), arg,
+ format_typename(type), format_typename((*ap_types)[arg - 1]));
+ return FAIL;
+ }
+ }
+ }
+
+ (*ap_types)[arg - 1] = type;
+
+ return OK;
+}
+
+static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *fmt, typval_T *tvs)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
+{
+ const char *p = fmt;
+ const char *arg = NULL;
+
+ int any_pos = 0;
+ int any_arg = 0;
+
+#define CHECK_POS_ARG \
+ do { \
+ if (any_pos && any_arg) { \
+ semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt); \
+ goto error; \
+ } \
+ } while (0);
+
+ if (p == NULL) {
+ return OK;
+ }
+
+ while (*p != NUL) {
+ if (*p != '%') {
+ char *q = strchr(p + 1, '%');
+ size_t n = (q == NULL) ? strlen(p) : (size_t)(q - p);
+
+ p += n;
+ } else {
+ // allowed values: \0, h, l, L
+ char length_modifier = '\0';
+
+ // variable for positional arg
+ int pos_arg = -1;
+
+ p++; // skip '%'
+
+ // First check to see if we find a positional
+ // argument specifier
+ const char *ptype = p;
+
+ while (ascii_isdigit(*ptype)) {
+ ptype++;
+ }
+
+ if (*ptype == '$') {
+ if (*p == '0') {
+ // 0 flag at the wrong place
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+
+ // Positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ pos_arg = (int)uj;
+
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ p++;
+ }
+
+ // parse flags
+ while (*p == '0' || *p == '-' || *p == '+' || *p == ' '
+ || *p == '#' || *p == '\'') {
+ switch (*p) {
+ case '0':
+ break;
+ case '-':
+ break;
+ case '+':
+ break;
+ case ' ': // If both the ' ' and '+' flags appear, the ' '
+ // flag should be ignored
+ break;
+ case '#':
+ break;
+ case '\'':
+ break;
+ }
+ p++;
+ }
+ // If the '0' and '-' flags both appear, the '0' flag should be
+ // ignored.
+
+ // parse field width
+ if (*(arg = p) == '*') {
+ p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // Positional argument field width
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p != '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ } else {
+ p++;
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) {
+ goto error;
+ }
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ } else if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we treat
+ // argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+
+ // parse precision
+ if (*p == '.') {
+ p++;
+
+ if (*(arg = p) == '*') {
+ p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // Parse precision
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ p++;
+
+ if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) {
+ goto error;
+ }
+ } else {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ } else if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+
+ if (*p == '$') {
+ semsg(_(e_invalid_format_specifier_str), fmt);
+ goto error;
+ }
+ }
+ }
+
+ if (pos_arg != -1) {
+ any_pos = 1;
+ CHECK_POS_ARG;
+
+ ptype = p;
+ }
+
+ // parse 'h', 'l', 'll' and 'z' length modifiers
+ if (*p == 'h' || *p == 'l' || *p == 'z') {
+ length_modifier = *p;
+ p++;
+ if (length_modifier == 'l' && *p == 'l') {
+ // double l = long long
+ // length_modifier = 'L';
+ p++;
+ }
+ }
+
+ switch (*p) {
+ // Check for known format specifiers. % is special!
+ case 'i':
+ case '*':
+ case 'd':
+ case 'u':
+ case 'o':
+ case 'D':
+ case 'U':
+ case 'O':
+ case 'x':
+ case 'X':
+ case 'b':
+ case 'B':
+ case 'c':
+ case 's':
+ case 'S':
+ case 'p':
+ case 'f':
+ case 'F':
+ case 'e':
+ case 'E':
+ case 'g':
+ case 'G':
+ if (pos_arg != -1) {
+ if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL) {
+ goto error;
+ }
+ } else {
+ any_arg = 1;
+ CHECK_POS_ARG;
+ }
+ break;
+
+ default:
+ if (pos_arg != -1) {
+ semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt);
+ goto error;
+ }
+ }
+
+ if (*p != NUL) {
+ p++; // step over the just processed conversion specifier
+ }
+ }
+ }
+
+ for (int arg_idx = 0; arg_idx < *num_posarg; arg_idx++) {
+ if ((*ap_types)[arg_idx] == NULL) {
+ semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt);
+ goto error;
+ }
+
+ if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN) {
+ semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt);
+ goto error;
+ }
+ }
+
+ return OK;
+
+error:
+ xfree(*ap_types);
+ *ap_types = NULL;
+ *num_posarg = 0;
+ return FAIL;
+}
+
+static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, int *arg_idx,
+ int *arg_cur, const char *fmt)
+ FUNC_ATTR_NONNULL_ARG(3, 4, 5)
+{
+ int arg_min = 0;
+
+ if (*arg_cur + 1 == *arg_idx) {
+ (*arg_cur)++;
+ (*arg_idx)++;
+ return;
+ }
+
+ if (*arg_cur >= *arg_idx) {
+ // Reset ap to ap_start and skip arg_idx - 1 types
+ va_end(*ap);
+ va_copy(*ap, ap_start);
+ } else {
+ // Skip over any we should skip
+ arg_min = *arg_cur;
+ }
+
+ for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; (*arg_cur)++) {
+ if (ap_types == NULL || ap_types[*arg_cur] == NULL) {
+ siemsg(e_aptypes_is_null_nr_str, fmt, *arg_cur);
+ return;
+ }
+
+ const char *p = ap_types[*arg_cur];
+
+ int fmt_type = format_typeof(p);
+
+ // get parameter value, do initial processing
+ switch (fmt_type) {
+ case TYPE_PERCENT:
+ case TYPE_UNKNOWN:
+ break;
+
+ case TYPE_CHAR:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_STRING:
+ va_arg(*ap, const char *);
+ break;
+
+ case TYPE_POINTER:
+ va_arg(*ap, void *);
+ break;
+
+ case TYPE_INT:
+ va_arg(*ap, int);
+ break;
+
+ case TYPE_LONGINT:
+ va_arg(*ap, long);
+ break;
+
+ case TYPE_LONGLONGINT:
+ va_arg(*ap, long long); // NOLINT(runtime/int)
+ break;
+
+ case TYPE_SIGNEDSIZET: // implementation-defined, usually ptrdiff_t
+ va_arg(*ap, ptrdiff_t);
+ break;
+
+ case TYPE_UNSIGNEDINT:
+ va_arg(*ap, unsigned);
+ break;
+
+ case TYPE_UNSIGNEDLONGINT:
+ va_arg(*ap, unsigned long);
+ break;
+
+ case TYPE_UNSIGNEDLONGLONGINT:
+ va_arg(*ap, unsigned long long); // NOLINT(runtime/int)
+ break;
+
+ case TYPE_SIZET:
+ va_arg(*ap, size_t);
+ break;
+
+ case TYPE_FLOAT:
+ va_arg(*ap, double);
+ break;
+ }
+ }
+
+ // Because we know that after we return from this call,
+ // a va_arg() call is made, we can pre-emptively
+ // increment the current argument index.
+ (*arg_cur)++;
+ (*arg_idx)++;
+}
+
/// Write formatted value to the string
///
/// @param[out] str String to write to.
/// @param[in] str_m String length.
/// @param[in] fmt String format.
/// @param[in] ap Values that should be formatted. Ignored if tvs is not NULL.
-/// @param[in] tvs Values that should be formatted, for printf() VimL
+/// @param[in] tvs Values that should be formatted, for printf() Vimscript
/// function. Must be NULL in other cases.
///
/// @return Number of bytes excluding NUL byte that would be written to the
/// string if str_m was greater or equal to the return value.
-int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs)
+int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_start,
+ typval_T *const tvs)
{
size_t str_l = 0;
bool str_avail = str_l < str_m;
const char *p = fmt;
+ int arg_cur = 0;
+ int num_posarg = 0;
int arg_idx = 1;
+ va_list ap;
+ const char **ap_types = NULL;
+
+ if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL) {
+ return 0;
+ }
+
+ va_copy(ap, ap_start);
if (!p) {
p = "";
@@ -791,8 +1395,31 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// buffer for 's' and 'S' specs
char *tofree = NULL;
+ // variable for positional arg
+ int pos_arg = -1;
+
p++; // skip '%'
+ // First check to see if we find a positional
+ // argument specifier
+ const char *ptype = p;
+
+ while (ascii_isdigit(*ptype)) {
+ ptype++;
+ }
+
+ if (*ptype == '$') {
+ // Positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ pos_arg = (int)uj;
+
+ p++;
+ }
+
// parse flags
while (true) {
switch (*p) {
@@ -819,7 +1446,25 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// parse field width
if (*p == '*') {
p++;
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+
+ if (ascii_isdigit((int)(*p))) {
+ // Positional argument field width
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ arg_idx = (int)uj;
+
+ p++;
+ }
+
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
if (j >= 0) {
min_field_width = (size_t)j;
} else {
@@ -829,10 +1474,10 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
} else if (ascii_isdigit((int)(*p))) {
// size_t could be wider than unsigned int; make sure we treat
// argument like common implementations do
- unsigned int uj = (unsigned)(*p++ - '0');
+ unsigned uj = (unsigned)(*p++ - '0');
while (ascii_isdigit((int)(*p))) {
- uj = 10 * uj + (unsigned int)(*p++ - '0');
+ uj = 10 * uj + (unsigned)(*p++ - '0');
}
min_field_width = uj;
}
@@ -841,24 +1486,43 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == '.') {
p++;
precision_specified = 1;
- if (*p == '*') {
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+
+ if (ascii_isdigit((int)(*p))) {
+ // size_t could be wider than unsigned int; make sure we
+ // treat argument like common implementations do
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ precision = uj;
+ } else if (*p == '*') {
p++;
+
+ if (ascii_isdigit((int)(*p))) {
+ // positional argument
+ unsigned uj = (unsigned)(*p++ - '0');
+
+ while (ascii_isdigit((int)(*p))) {
+ uj = 10 * uj + (unsigned)(*p++ - '0');
+ }
+ arg_idx = (int)uj;
+
+ p++;
+ }
+
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
if (j >= 0) {
precision = (size_t)j;
} else {
precision_specified = 0;
precision = 0;
}
- } else if (ascii_isdigit((int)(*p))) {
- // size_t could be wider than unsigned int; make sure we
- // treat argument like common implementations do
- unsigned int uj = (unsigned)(*p++ - '0');
-
- while (ascii_isdigit((int)(*p))) {
- uj = 10 * uj + (unsigned int)(*p++ - '0');
- }
- precision = uj;
}
}
@@ -866,8 +1530,9 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
if (*p == 'h' || *p == 'l' || *p == 'z') {
length_modifier = *p;
p++;
- if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2
- length_modifier = '2';
+ if (length_modifier == 'l' && *p == 'l') {
+ // double l = long long
+ length_modifier = 'L';
p++;
}
}
@@ -897,10 +1562,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 'x':
case 'X':
if (tvs && length_modifier == '\0') {
- length_modifier = '2';
+ length_modifier = 'L';
}
}
+ if (pos_arg != -1) {
+ arg_idx = pos_arg;
+ }
+
// get parameter value, do initial processing
switch (fmt_spec) {
// '%' and 'c' behave similar to 's' regarding flags and field widths
@@ -915,7 +1584,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
break;
case 'c': {
- const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int);
+ const int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
+
// standard demands unsigned char
uchar_arg = (unsigned char)j;
str_arg = (char *)&uchar_arg;
@@ -924,8 +1598,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
case 's':
case 'S':
- str_arg = tvs ? tv_str(tvs, &arg_idx, &tofree)
- : va_arg(ap, const char *);
+ str_arg = (tvs
+ ? tv_str(tvs, &arg_idx, &tofree)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, const char *)));
+
if (!str_arg) {
str_arg = "[NULL]";
str_arg_l = 6;
@@ -946,10 +1624,10 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
- str_arg);
}
if (fmt_spec == 'S') {
- char *p1;
+ const char *p1;
size_t i;
- for (i = 0, p1 = (char *)str_arg; *p1; p1 += utfc_ptr2len(p1)) {
+ for (i = 0, p1 = str_arg; *p1; p1 += utfc_ptr2len(p1)) {
size_t cell = (size_t)utf_ptr2cells(p1);
if (precision_specified && i + cell > precision) {
break;
@@ -992,7 +1670,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
const void *ptr_arg = NULL;
if (fmt_spec == 'p') {
- ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *);
+ ptr_arg = (tvs
+ ? tv_ptr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, void *)));
+
if (ptr_arg) {
arg_sign = 1;
}
@@ -1000,23 +1683,41 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// signed
switch (length_modifier) {
case '\0':
- arg = (int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
+ arg = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
break;
case 'h':
// char and short arguments are passed as int16_t
- arg = (int16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int));
+ arg = (int16_t)
+ (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
break;
case 'l':
- arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long));
+ arg = (tvs
+ ? (long)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, long)));
break;
- case '2':
- arg = (
- tvs
- ? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int)
- : va_arg(ap, long long)); // NOLINT (runtime/int)
+ case 'L':
+ arg = (tvs
+ ? (long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, long long))); // NOLINT(runtime/int)
break;
- case 'z':
- arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t));
+ case 'z': // implementation-defined, usually ptrdiff_t
+ arg = (tvs
+ ? (ptrdiff_t)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, ptrdiff_t)));
break;
}
if (arg > 0) {
@@ -1028,23 +1729,40 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
// unsigned
switch (length_modifier) {
case '\0':
- uarg = (unsigned int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
+ uarg = (tvs
+ ? (unsigned)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned)));
break;
case 'h':
- uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned int));
+ uarg = (uint16_t)
+ (tvs
+ ? (unsigned)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned)));
break;
case 'l':
- uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long));
+ uarg = (tvs
+ ? (unsigned long)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned long)));
break;
- case '2':
- uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int)
- tvs
- ? ((unsigned long long) // NOLINT (runtime/int)
- tv_nr(tvs, &arg_idx))
- : va_arg(ap, unsigned long long)); // NOLINT (runtime/int)
+ case 'L':
+ uarg = (tvs
+ ? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, unsigned long long))); // NOLINT(runtime/int)
break;
case 'z':
- uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t));
+ uarg = (tvs
+ ? (size_t)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, size_t)));
break;
}
arg_sign = (uarg != 0);
@@ -1179,7 +1897,12 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
char format[40];
int remove_trailing_zeroes = false;
- double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double);
+ double f = (tvs
+ ? tv_float(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, double)));
+
double abs_f = f < 0 ? -f : f;
if (fmt_spec == 'g' || fmt_spec == 'G') {
@@ -1234,7 +1957,6 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
assert(str_arg_l < sizeof(tmp));
if (remove_trailing_zeroes) {
- int i;
char *tp;
// using %g or %G: remove superfluous zeroes
@@ -1249,7 +1971,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
STRMOVE(tp + 1, tp + 2);
str_arg_l--;
}
- i = (tp[1] == '-') ? 2 : 1;
+ int i = (tp[1] == '-') ? 2 : 1;
while (tp[i] == '0') {
// change "1.0e07" to "1.0e7"
STRMOVE(tp + i, tp + i + 1);
@@ -1397,10 +2119,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t
str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
}
- if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) {
+ if (tvs != NULL
+ && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN) {
emsg(_("E767: Too many arguments to printf()"));
}
+ xfree(ap_types);
+ va_end(ap);
+
// return the number of characters formatted (excluding trailing nul
// character); that is, the number of characters that would have been
// written to the buffer if it were large enough.
@@ -1441,20 +2167,17 @@ int kv_do_printf(StringBuilder *str, const char *fmt, ...)
///
/// @return the allocated string.
char *reverse_text(char *s)
- FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
- // Reverse the pattern.
size_t len = strlen(s);
char *rev = xmalloc(len + 1);
- size_t rev_i = len;
- for (size_t s_i = 0; s_i < len; s_i++) {
+ for (size_t s_i = 0, rev_i = len; s_i < len; s_i++) {
const int mb_len = utfc_ptr2len(s + s_i);
rev_i -= (size_t)mb_len;
memmove(rev + rev_i, s + s_i, (size_t)mb_len);
s_i += (size_t)mb_len - 1;
}
rev[len] = NUL;
-
return rev;
}
@@ -1468,7 +2191,7 @@ char *reverse_text(char *s)
/// @return [allocated] Copy of the string.
char *strrep(const char *src, const char *what, const char *rep)
{
- char *pos = (char *)src;
+ const char *pos = src;
size_t whatlen = strlen(what);
// Count occurrences
@@ -1499,3 +2222,761 @@ char *strrep(const char *src, const char *what, const char *rep)
return ret;
}
+
+/// Implementation of "byteidx()" and "byteidxcomp()" functions
+static void byteidx_common(typval_T *argvars, typval_T *rettv, int comp)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ utf16idx = tv_get_bool_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (utf16idx < 0 || utf16idx > 1) {
+ semsg(_(e_using_number_as_bool_nr), utf16idx);
+ return;
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (comp) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *t = str;
+ for (; idx > 0; idx--) {
+ if (*t == NUL) { // EOL reached.
+ return;
+ }
+ if (utf16idx) {
+ const int clen = ptr2len(t);
+ const int c = (clen > 1) ? utf_ptr2char(t) : *t;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ if (idx > 0) {
+ t += ptr2len(t);
+ }
+ }
+ rettv->vval.v_number = (varnumber_T)(t - str);
+}
+
+/// "byteidx()" function
+void f_byteidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx_common(argvars, rettv, false);
+}
+
+/// "byteidxcomp()" function
+void f_byteidxcomp(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ byteidx_common(argvars, rettv, true);
+}
+
+/// "charidx()" function
+void f_charidx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T utf16idx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ utf16idx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ for (p = str, len = 0; utf16idx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ // If the index is exactly the number of bytes or utf-16 code units
+ // in the string then return the length of the string in characters.
+ if (utf16idx ? (idx == 0) : (p == (str + idx))) {
+ rettv->vval.v_number = len;
+ }
+ return;
+ }
+ if (utf16idx) {
+ idx--;
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ idx--;
+ }
+ }
+ p += ptr2len(p);
+ }
+
+ rettv->vval.v_number = len > 0 ? len - 1 : 0;
+}
+
+/// "str2list()" function
+void f_str2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ const char *p = tv_get_string(&argvars[0]);
+
+ for (; *p != NUL; p += utf_ptr2len(p)) {
+ tv_list_append_number(rettv->vval.v_list, utf_ptr2char(p));
+ }
+}
+
+/// "str2nr()" function
+void f_str2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int base = 10;
+ int what = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ base = (int)tv_get_number(&argvars[1]);
+ if (base != 2 && base != 8 && base != 10 && base != 16) {
+ emsg(_(e_invarg));
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN && tv_get_bool(&argvars[2])) {
+ what |= STR2NR_QUOTE;
+ }
+ }
+
+ char *p = skipwhite(tv_get_string(&argvars[0]));
+ bool isneg = (*p == '-');
+ if (*p == '+' || *p == '-') {
+ p = skipwhite(p + 1);
+ }
+ switch (base) {
+ case 2:
+ what |= STR2NR_BIN | STR2NR_FORCE;
+ break;
+ case 8:
+ what |= STR2NR_OCT | STR2NR_OOCT | STR2NR_FORCE;
+ break;
+ case 16:
+ what |= STR2NR_HEX | STR2NR_FORCE;
+ break;
+ }
+ varnumber_T n;
+ vim_str2nr(p, NULL, NULL, what, &n, NULL, 0, false, NULL);
+ // Text after the number is silently ignored.
+ if (isneg) {
+ rettv->vval.v_number = -n;
+ } else {
+ rettv->vval.v_number = n;
+ }
+}
+
+/// "strgetchar()" function
+void f_strgetchar(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ if (str == NULL) {
+ return;
+ }
+ bool error = false;
+ varnumber_T charidx = tv_get_number_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+
+ const size_t len = strlen(str);
+ size_t byteidx = 0;
+
+ while (charidx >= 0 && byteidx < len) {
+ if (charidx == 0) {
+ rettv->vval.v_number = utf_ptr2char(str + byteidx);
+ break;
+ }
+ charidx--;
+ byteidx += (size_t)utf_ptr2len(str + byteidx);
+ }
+}
+
+/// "stridx()" function
+void f_stridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *haystack = tv_get_string_buf_chk(&argvars[0], buf);
+ const char *const haystack_start = haystack;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+
+ const ptrdiff_t start_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2],
+ &error);
+ if (error || start_idx >= (ptrdiff_t)strlen(haystack)) {
+ return;
+ }
+ if (start_idx >= 0) {
+ haystack += start_idx;
+ }
+ }
+
+ const char *pos = strstr(haystack, needle);
+ if (pos != NULL) {
+ rettv->vval.v_number = (varnumber_T)(pos - haystack_start);
+ }
+}
+
+/// "string()" function
+void f_string(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = encode_tv2string(&argvars[0], NULL);
+}
+
+/// "strlen()" function
+void f_strlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0]));
+}
+
+static void strchar_common(typval_T *argvars, typval_T *rettv, bool skipcc)
+{
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = skipcc ? mb_ptr2char_adv : mb_cptr2char_adv;
+ while (*s != NUL) {
+ func_mb_ptr2char_adv(&s);
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strcharlen()" function
+void f_strcharlen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ strchar_common(argvars, rettv, true);
+}
+
+/// "strchars()" function
+void f_strchars(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ varnumber_T skipcc = false;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ skipcc = tv_get_bool_chk(&argvars[1], &error);
+ if (error) {
+ return;
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ semsg(_(e_using_number_as_bool_nr), skipcc);
+ return;
+ }
+ }
+
+ strchar_common(argvars, rettv, skipcc);
+}
+
+/// "strutf16len()" function
+void f_strutf16len(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[1]);
+ }
+
+ const char *s = tv_get_string(&argvars[0]);
+ varnumber_T len = 0;
+ int (*func_mb_ptr2char_adv)(const char **pp);
+
+ func_mb_ptr2char_adv = countcc ? mb_cptr2char_adv : mb_ptr2char_adv;
+ while (*s != NUL) {
+ const int ch = func_mb_ptr2char_adv(&s);
+ if (ch > 0xFFFF) {
+ len++;
+ }
+ len++;
+ }
+ rettv->vval.v_number = len;
+}
+
+/// "strdisplaywidth()" function
+void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+ int col = 0;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ col = (int)tv_get_number(&argvars[1]);
+ }
+
+ rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char *)s) - col);
+}
+
+/// "strwidth()" function
+void f_strwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const s = tv_get_string(&argvars[0]);
+
+ rettv->vval.v_number = (varnumber_T)mb_string2cells(s);
+}
+
+/// "strcharpart()" function
+void f_strcharpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ int nbyte = 0;
+ varnumber_T skipcc = false;
+ bool error = false;
+ varnumber_T nchar = tv_get_number_chk(&argvars[1], &error);
+ if (!error) {
+ if (argvars[2].v_type != VAR_UNKNOWN
+ && argvars[3].v_type != VAR_UNKNOWN) {
+ skipcc = tv_get_bool_chk(&argvars[3], &error);
+ if (error) {
+ return;
+ }
+ if (skipcc < 0 || skipcc > 1) {
+ semsg(_(e_using_number_as_bool_nr), skipcc);
+ return;
+ }
+ }
+
+ if (nchar > 0) {
+ while (nchar > 0 && (size_t)nbyte < slen) {
+ if (skipcc) {
+ nbyte += utfc_ptr2len(p + nbyte);
+ } else {
+ nbyte += utf_ptr2len(p + nbyte);
+ }
+ nchar--;
+ }
+ } else {
+ nbyte = (int)nchar;
+ }
+ }
+ int len = 0;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ int charlen = (int)tv_get_number(&argvars[2]);
+ while (charlen > 0 && nbyte + len < (int)slen) {
+ int off = nbyte + len;
+
+ if (off < 0) {
+ len += 1;
+ } else {
+ if (skipcc) {
+ len += utfc_ptr2len(p + off);
+ } else {
+ len += utf_ptr2len(p + off);
+ }
+ }
+ charlen--;
+ }
+ } else {
+ len = (int)slen - nbyte; // default: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (nbyte < 0) {
+ len += nbyte;
+ nbyte = 0;
+ } else if ((size_t)nbyte > slen) {
+ nbyte = (int)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (nbyte + len > (int)slen) {
+ len = (int)slen - nbyte;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(p + nbyte, (size_t)len);
+}
+
+/// "strpart()" function
+void f_strpart(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ bool error = false;
+
+ const char *const p = tv_get_string(&argvars[0]);
+ const size_t slen = strlen(p);
+
+ varnumber_T n = tv_get_number_chk(&argvars[1], &error);
+ varnumber_T len;
+ if (error) {
+ len = 0;
+ } else if (argvars[2].v_type != VAR_UNKNOWN) {
+ len = tv_get_number(&argvars[2]);
+ } else {
+ len = (varnumber_T)slen - n; // Default len: all bytes that are available.
+ }
+
+ // Only return the overlap between the specified part and the actual
+ // string.
+ if (n < 0) {
+ len += n;
+ n = 0;
+ } else if (n > (varnumber_T)slen) {
+ n = (varnumber_T)slen;
+ }
+ if (len < 0) {
+ len = 0;
+ } else if (n + len > (varnumber_T)slen) {
+ len = (varnumber_T)slen - n;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
+ int off;
+
+ // length in characters
+ for (off = (int)n; off < (int)slen && len > 0; len--) {
+ off += utfc_ptr2len(p + off);
+ }
+ len = off - n;
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xmemdupz(p + n, (size_t)len);
+}
+
+/// "strridx()" function
+void f_strridx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ const char *const needle = tv_get_string_chk(&argvars[1]);
+ const char *const haystack = tv_get_string_buf_chk(&argvars[0], buf);
+
+ rettv->vval.v_number = -1;
+ if (needle == NULL || haystack == NULL) {
+ return; // Type error; errmsg already given.
+ }
+
+ const size_t haystack_len = strlen(haystack);
+ ptrdiff_t end_idx;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // Third argument: upper limit for index.
+ end_idx = (ptrdiff_t)tv_get_number_chk(&argvars[2], NULL);
+ if (end_idx < 0) {
+ return; // Can never find a match.
+ }
+ } else {
+ end_idx = (ptrdiff_t)haystack_len;
+ }
+
+ const char *lastmatch = NULL;
+ if (*needle == NUL) {
+ // Empty string matches past the end.
+ lastmatch = haystack + end_idx;
+ } else {
+ for (const char *rest = haystack; *rest != NUL; rest++) {
+ rest = strstr(rest, needle);
+ if (rest == NULL || rest > haystack + end_idx) {
+ break;
+ }
+ lastmatch = rest;
+ }
+ }
+
+ if (lastmatch != NULL) {
+ rettv->vval.v_number = (varnumber_T)(lastmatch - haystack);
+ }
+}
+
+/// "strtrans()" function
+void f_strtrans(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = transstr(tv_get_string(&argvars[0]), true);
+}
+
+/// "utf16idx()" function
+///
+/// Converts a byte or character offset in a string to the corresponding UTF-16
+/// code unit offset.
+void f_utf16idx(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_number_arg(argvars, 1) == FAIL
+ || tv_check_for_opt_bool_arg(argvars, 2) == FAIL
+ || (argvars[2].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_bool_arg(argvars, 3) == FAIL)) {
+ return;
+ }
+
+ const char *const str = tv_get_string_chk(&argvars[0]);
+ varnumber_T idx = tv_get_number_chk(&argvars[1], NULL);
+ if (str == NULL || idx < 0) {
+ return;
+ }
+
+ varnumber_T countcc = false;
+ varnumber_T charidx = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ countcc = tv_get_bool(&argvars[2]);
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ charidx = tv_get_bool(&argvars[3]);
+ }
+ }
+
+ int (*ptr2len)(const char *);
+ if (countcc) {
+ ptr2len = utf_ptr2len;
+ } else {
+ ptr2len = utfc_ptr2len;
+ }
+
+ const char *p;
+ int len;
+ int utf16idx = 0;
+ for (p = str, len = 0; charidx ? idx >= 0 : p <= str + idx; len++) {
+ if (*p == NUL) {
+ // If the index is exactly the number of bytes or characters in the
+ // string then return the length of the string in utf-16 code units.
+ if (charidx ? (idx == 0) : (p == (str + idx))) {
+ rettv->vval.v_number = len;
+ }
+ return;
+ }
+ utf16idx = len;
+ const int clen = ptr2len(p);
+ const int c = (clen > 1) ? utf_ptr2char(p) : *p;
+ if (c > 0xFFFF) {
+ len++;
+ }
+ p += ptr2len(p);
+ if (charidx) {
+ idx--;
+ }
+ }
+
+ rettv->vval.v_number = utf16idx;
+}
+
+/// "tolower(string)" function
+void f_tolower(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), false);
+}
+
+/// "toupper(string)" function
+void f_toupper(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = strcase_save(tv_get_string(&argvars[0]), true);
+}
+
+/// "tr(string, fromstr, tostr)" function
+void f_tr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+
+ const char *in_str = tv_get_string(&argvars[0]);
+ const char *fromstr = tv_get_string_buf_chk(&argvars[1], buf);
+ const char *tostr = tv_get_string_buf_chk(&argvars[2], buf2);
+
+ // Default return value: empty string.
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (fromstr == NULL || tostr == NULL) {
+ return; // Type error; errmsg already given.
+ }
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char), 80);
+
+ // fromstr and tostr have to contain the same number of chars.
+ bool first = true;
+ while (*in_str != NUL) {
+ const char *cpstr = in_str;
+ const int inlen = utfc_ptr2len(in_str);
+ int cplen = inlen;
+ int idx = 0;
+ int fromlen;
+ for (const char *p = fromstr; *p != NUL; p += fromlen) {
+ fromlen = utfc_ptr2len(p);
+ if (fromlen == inlen && strncmp(in_str, p, (size_t)inlen) == 0) {
+ int tolen;
+ for (p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ if (idx-- == 0) {
+ cplen = tolen;
+ cpstr = p;
+ break;
+ }
+ }
+ if (*p == NUL) { // tostr is shorter than fromstr.
+ goto error;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ if (first && cpstr == in_str) {
+ // Check that fromstr and tostr have the same number of
+ // (multi-byte) characters. Done only once when a character
+ // of in_str doesn't appear in fromstr.
+ first = false;
+ int tolen;
+ for (const char *p = tostr; *p != NUL; p += tolen) {
+ tolen = utfc_ptr2len(p);
+ idx--;
+ }
+ if (idx != 0) {
+ goto error;
+ }
+ }
+
+ ga_grow(&ga, cplen);
+ memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen);
+ ga.ga_len += cplen;
+
+ in_str += inlen;
+ }
+
+ // add a terminating NUL
+ ga_append(&ga, NUL);
+
+ rettv->vval.v_string = ga.ga_data;
+ return;
+error:
+ semsg(_(e_invarg2), fromstr);
+ ga_clear(&ga);
+}
+
+/// "trim({expr})" function
+void f_trim(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ const char *head = tv_get_string_buf_chk(&argvars[0], buf1);
+ const char *mask = NULL;
+ const char *prev;
+ const char *p;
+ int dir = 0;
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+ if (head == NULL) {
+ return;
+ }
+
+ if (tv_check_for_opt_string_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ if (argvars[1].v_type == VAR_STRING) {
+ mask = tv_get_string_buf_chk(&argvars[1], buf2);
+ if (*mask == NUL) {
+ mask = NULL;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (dir < 0 || dir > 2) {
+ semsg(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
+ }
+
+ if (dir == 0 || dir == 1) {
+ // Trim leading characters
+ while (*head != NUL) {
+ int c1 = utf_ptr2char(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ MB_PTR_ADV(head);
+ }
+ }
+
+ const char *tail = head + strlen(head);
+ if (dir == 0 || dir == 2) {
+ // Trim trailing characters
+ for (; tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ int c1 = utf_ptr2char(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == utf_ptr2char(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ }
+ }
+ }
+ }
+ rettv->vval.v_string = xstrnsave(head, (size_t)(tail - head));
+}
diff --git a/src/nvim/strings.h b/src/nvim/strings.h
index 6ad9daf5bf..d717362f87 100644
--- a/src/nvim/strings.h
+++ b/src/nvim/strings.h
@@ -1,13 +1,14 @@
-#ifndef NVIM_STRINGS_H
-#define NVIM_STRINGS_H
+#pragma once
-#include <stdarg.h>
-#include <stdbool.h>
+#include <stdarg.h> // IWYU pragma: keep
#include <string.h>
+#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/eval/typval.h"
-#include "nvim/types.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/func_attr.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
/// Append string to string and return pointer to the next byte
///
@@ -31,4 +32,23 @@ typedef kvec_t(char) StringBuilder;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "strings.h.generated.h"
#endif
-#endif // NVIM_STRINGS_H
+
+#ifdef HAVE_STRCASECMP
+# define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s))
+#else
+# ifdef HAVE_STRICMP
+# define STRICMP(d, s) stricmp((char *)(d), (char *)(s))
+# else
+# define STRICMP(d, s) vim_stricmp((char *)(d), (char *)(s))
+# endif
+#endif
+
+#ifdef HAVE_STRNCASECMP
+# define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n))
+#else
+# ifdef HAVE_STRNICMP
+# define STRNICMP(d, s, n) strnicmp((char *)(d), (char *)(s), (size_t)(n))
+# else
+# define STRNICMP(d, s, n) vim_strnicmp((char *)(d), (char *)(s), (size_t)(n))
+# endif
+#endif
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 49b63ad324..11282ea170 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -1,19 +1,18 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// syntax.c: code for syntax highlighting
#include <assert.h>
#include <inttypes.h>
#include <stdbool.h>
+#include <stddef.h>
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
@@ -21,30 +20,31 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/profile.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
static bool did_syntax_onoff = false;
@@ -58,7 +58,13 @@ static bool did_syntax_onoff = false;
#define SPO_LC_OFF 6 // leading context offset
#define SPO_COUNT 7
-static char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+static const char e_illegal_arg[] = N_("E390: Illegal argument: %s");
+static const char e_contains_argument_not_accepted_here[]
+ = N_("E395: Contains argument not accepted here");
+static const char e_invalid_cchar_value[]
+ = N_("E844: Invalid cchar value");
+static const char e_trailing_char_after_rsb_str_str[]
+ = N_("E890: Trailing char after ']': %s]%s");
// The patterns that are being searched for are stored in a syn_pattern.
// A match item consists of one pattern.
@@ -114,7 +120,7 @@ typedef struct state_item {
int si_end_idx; // group ID for end pattern or zero
int si_ends; // if match ends before si_m_endpos
int si_attr; // attributes in this state
- long si_flags; // HL_HAS_EOL flag in this state, and
+ int si_flags; // HL_HAS_EOL flag in this state, and
// HL_SKIP* for si_next_list
int si_seqnr; // sequence number
int si_cchar; // substitution character for conceal
@@ -231,8 +237,6 @@ static keyentry_T dumkey;
#define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char *)&dumkey)))
#define HI2KE(hi) HIKEY2KE((hi)->hi_key)
-// -V:HI2KE:782
-
// To reduce the time spent in keepend(), remember at which level in the state
// stack the first item with "keepend" is present. When "-1", there is no
// "keepend" on the stack.
@@ -257,7 +261,7 @@ static lpos_T next_match_m_endpos; // position for end of next match
static lpos_T next_match_h_startpos; // pos. for highl. start of next match
static lpos_T next_match_h_endpos; // pos. for highl. end of next match
static int next_match_idx; // index of matched item
-static long next_match_flags; // flags for next match
+static int next_match_flags; // flags for next match
static lpos_T next_match_eos_pos; // end of start pattn (start region)
static lpos_T next_match_eoe_pos; // pos. for end of end pattern
static int next_match_end_idx; // ID of group for end pattn or zero
@@ -304,11 +308,10 @@ void syn_set_timeout(proftime_T *tm)
// window.
void syntax_start(win_T *wp, linenr_T lnum)
{
- synstate_T *p;
synstate_T *last_valid = NULL;
synstate_T *last_min_valid = NULL;
- synstate_T *sp, *prev = NULL;
- linenr_T parsed_lnum;
+ synstate_T *sp;
+ synstate_T *prev = NULL;
linenr_T first_stored;
int dist;
static varnumber_T changedtick = 0; // remember the last change ID
@@ -359,7 +362,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
// Only do this if lnum is not before and not to far beyond a saved state.
if (INVALID_STATE(&current_state) && syn_block->b_sst_array != NULL) {
// Find last valid saved state before start_lnum.
- for (p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
+ for (synstate_T *p = syn_block->b_sst_first; p != NULL; p = p->sst_next) {
if (p->sst_lnum > lnum) {
break;
}
@@ -423,7 +426,7 @@ void syntax_start(win_T *wp, linenr_T lnum)
if (sp != NULL
&& sp->sst_lnum == current_lnum
&& syn_stack_equal(sp)) {
- parsed_lnum = current_lnum;
+ linenr_T parsed_lnum = current_lnum;
prev = sp;
while (sp != NULL && sp->sst_change_lnum <= parsed_lnum) {
if (sp->sst_lnum <= lnum) {
@@ -503,7 +506,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
int found_flags = 0;
int found_match_idx = 0;
linenr_T found_current_lnum = 0;
- int found_current_col= 0;
+ int found_current_col = 0;
lpos_T found_m_endpos;
colnr_T prev_current_col;
@@ -619,7 +622,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
for (current_lnum = lnum; current_lnum < end_lnum; current_lnum++) {
syn_start_line();
- for (;;) {
+ while (true) {
had_sync_point = syn_finish_line(true);
// When a sync point has been found, remember where, and
// continue to look for another one, further on in the line.
@@ -721,7 +724,7 @@ static void syn_sync(win_T *wp, linenr_T start_lnum, synstate_T *last_valid)
static void save_chartab(char *chartab)
{
- if (syn_block->b_syn_isk == empty_option) {
+ if (syn_block->b_syn_isk == empty_string_option) {
return;
}
@@ -731,7 +734,7 @@ static void save_chartab(char *chartab)
static void restore_chartab(char *chartab)
{
- if (syn_win->w_s->b_syn_isk != empty_option) {
+ if (syn_win->w_s->b_syn_isk != empty_string_option) {
memmove(syn_buf->b_chartab, chartab, (size_t)32);
}
}
@@ -750,7 +753,7 @@ static int syn_match_linecont(linenr_T lnum)
regmatch.rmm_ic = syn_block->b_syn_linecont_ic;
regmatch.regprog = syn_block->b_syn_linecont_prog;
- int r = syn_regexec(&regmatch, lnum, (colnr_T)0,
+ int r = syn_regexec(&regmatch, lnum, 0,
IF_SYN_TIME(&syn_block->b_syn_linecont_time));
syn_block->b_syn_linecont_prog = regmatch.regprog;
@@ -874,13 +877,11 @@ static void syn_update_ends(bool startofline)
static void syn_stack_free_block(synblock_T *block)
{
- synstate_T *p;
-
if (block->b_sst_array == NULL) {
return;
}
- for (p = block->b_sst_first; p != NULL; p = p->sst_next) {
+ for (synstate_T *p = block->b_sst_first; p != NULL; p = p->sst_next) {
clear_syn_state(p);
}
XFREE_CLEAR(block->b_sst_array);
@@ -907,9 +908,6 @@ void syn_stack_free_all(synblock_T *block)
// Also used to allocate b_sst_array[] for the first time.
static void syn_stack_alloc(void)
{
- synstate_T *to, *from;
- synstate_T *sstp;
-
int len = syn_buf->b_ml.ml_line_count / SST_DIST + Rows * 2;
if (len < SST_MIN_ENTRIES) {
len = SST_MIN_ENTRIES;
@@ -937,12 +935,12 @@ static void syn_stack_alloc(void)
}
assert(len >= 0);
- sstp = xcalloc((size_t)len, sizeof(synstate_T));
+ synstate_T *sstp = xcalloc((size_t)len, sizeof(synstate_T));
- to = sstp - 1;
+ synstate_T *to = sstp - 1;
if (syn_block->b_sst_array != NULL) {
// Move the states from the old array to the new one.
- for (from = syn_block->b_sst_first; from != NULL;
+ for (synstate_T *from = syn_block->b_sst_first; from != NULL;
from = from->sst_next) {
to++;
*to = *from;
@@ -988,16 +986,13 @@ void syn_stack_apply_changes(buf_T *buf)
static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
{
- synstate_T *p, *prev, *np;
- linenr_T n;
-
- prev = NULL;
- for (p = block->b_sst_first; p != NULL;) {
+ synstate_T *prev = NULL;
+ for (synstate_T *p = block->b_sst_first; p != NULL;) {
if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) {
- n = p->sst_lnum + buf->b_mod_xlines;
+ linenr_T n = p->sst_lnum + buf->b_mod_xlines;
if (n <= buf->b_mod_bot) {
// this state is inside the changed area, remove it
- np = p->sst_next;
+ synstate_T *np = p->sst_next;
if (prev == NULL) {
block->b_sst_first = np;
} else {
@@ -1034,7 +1029,7 @@ static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf)
/// @return true if at least one entry was freed.
static bool syn_stack_cleanup(void)
{
- synstate_T *p, *prev;
+ synstate_T *prev;
disptick_T tick;
int dist;
bool retval = false;
@@ -1056,7 +1051,7 @@ static bool syn_stack_cleanup(void)
tick = syn_block->b_sst_lasttick;
bool above = false;
prev = syn_block->b_sst_first;
- for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
+ for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
if (prev->sst_lnum + dist > p->sst_lnum) {
if (p->sst_tick > syn_block->b_sst_lasttick) {
if (!above || p->sst_tick < tick) {
@@ -1072,7 +1067,7 @@ static bool syn_stack_cleanup(void)
// Go through the list to make the entries for the oldest tick at an
// interval of several lines.
prev = syn_block->b_sst_first;
- for (p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
+ for (synstate_T *p = prev->sst_next; p != NULL; prev = p, p = p->sst_next) {
if (p->sst_tick == tick && prev->sst_lnum + dist > p->sst_lnum) {
// Move this entry from used list to free list
prev->sst_next = p->sst_next;
@@ -1098,10 +1093,8 @@ static void syn_stack_free_entry(synblock_T *block, synstate_T *p)
// Returns NULL when there is no entry or the first entry is after "lnum".
static synstate_T *syn_stack_find_entry(linenr_T lnum)
{
- synstate_T *p, *prev;
-
- prev = NULL;
- for (p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
+ synstate_T *prev = NULL;
+ for (synstate_T *p = syn_block->b_sst_first; p != NULL; prev = p, p = p->sst_next) {
if (p->sst_lnum == lnum) {
return p;
}
@@ -1201,7 +1194,7 @@ static synstate_T *store_current_state(void)
}
for (i = 0; i < sp->sst_stacksize; i++) {
bp[i].bs_idx = CUR_STATE(i).si_idx;
- bp[i].bs_flags = (int)CUR_STATE(i).si_flags;
+ bp[i].bs_flags = CUR_STATE(i).si_flags;
bp[i].bs_seqnr = CUR_STATE(i).si_seqnr;
bp[i].bs_cchar = CUR_STATE(i).si_cchar;
bp[i].bs_extmatch = ref_extmatch(CUR_STATE(i).si_extmatch);
@@ -1218,7 +1211,6 @@ static synstate_T *store_current_state(void)
// Copy a state stack from "from" in b_sst_array[] to current_state;
static void load_current_state(synstate_T *from)
{
- int i;
bufstate_T *bp;
clear_current_state();
@@ -1231,7 +1223,7 @@ static void load_current_state(synstate_T *from)
} else {
bp = from->sst_union.sst_stack;
}
- for (i = 0; i < from->sst_stacksize; i++) {
+ for (int i = 0; i < from->sst_stacksize; i++) {
CUR_STATE(i).si_idx = bp[i].bs_idx;
CUR_STATE(i).si_flags = bp[i].bs_flags;
CUR_STATE(i).si_seqnr = bp[i].bs_seqnr;
@@ -1263,7 +1255,6 @@ static void load_current_state(synstate_T *from)
static bool syn_stack_equal(synstate_T *sp)
{
bufstate_T *bp;
- reg_extmatch_T *six, *bsx;
// First a quick check if the stacks have the same size end nextlist.
if (sp->sst_stacksize != current_state.ga_len
@@ -1289,8 +1280,8 @@ static bool syn_stack_equal(synstate_T *sp)
}
// When the extmatch pointers are different, the strings in them can
// still be the same. Check if the extmatch references are equal.
- bsx = bp[i].bs_extmatch;
- six = CUR_STATE(i).si_extmatch;
+ reg_extmatch_T *bsx = bp[i].bs_extmatch;
+ reg_extmatch_T *six = CUR_STATE(i).si_extmatch;
// If one of the extmatch pointers is NULL the states are different.
if (bsx == NULL || six == NULL) {
break;
@@ -1501,10 +1492,11 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
lpos_T eos_pos; // end-of-start match (start region)
lpos_T eoe_pos; // end-of-end pattern
int end_idx; // group ID for end pattern
- stateitem_T *cur_si, *sip = NULL;
+ stateitem_T *cur_si;
+ stateitem_T *sip = NULL;
int startcol;
int endcol;
- long flags;
+ int flags;
int cchar;
int16_t *next_list;
bool found_match; // found usable match
@@ -1877,7 +1869,7 @@ static int syn_current_attr(const bool syncing, const bool displaying, bool *con
current_attr = sip->si_attr;
current_id = sip->si_id;
current_trans_id = sip->si_trans_id;
- current_flags = (int)sip->si_flags;
+ current_flags = sip->si_flags;
current_seqnr = sip->si_seqnr;
current_sub_char = sip->si_cchar;
break;
@@ -2071,7 +2063,7 @@ static void check_state_ends(void)
int had_extend;
cur_si = &CUR_STATE(current_state.ga_len - 1);
- for (;;) {
+ while (true) {
if (cur_si->si_ends
&& (cur_si->si_m_endpos.lnum < current_lnum
|| (cur_si->si_m_endpos.lnum == current_lnum
@@ -2107,7 +2099,7 @@ static void check_state_ends(void)
// handle next_list, unless at end of line and no "skipnl" or
// "skipempty"
current_next_list = cur_si->si_next_list;
- current_next_flags = (int)cur_si->si_flags;
+ current_next_flags = cur_si->si_flags;
if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))
&& syn_getcurline()[current_col] == NUL) {
current_next_list = NULL;
@@ -2355,12 +2347,10 @@ static void pop_current_state(void)
/// @param end_endpos return: end of end pattern match
/// @param end_idx return: group ID for end pat. match, or 0
/// @param start_ext submatches from the start pattern
-static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos,
- long *flagsp, lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext)
+static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_endpos, int *flagsp,
+ lpos_T *end_endpos, int *end_idx, reg_extmatch_T *start_ext)
{
- colnr_T matchcol;
- synpat_T *spp, *spp_skip;
- int start_idx;
+ synpat_T *spp_skip;
int best_idx;
regmmatch_T regmatch;
regmmatch_T best_regmatch; // startpos/endpos of best match
@@ -2377,14 +2367,14 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
// Check for being called with a START pattern.
// Can happen with a match that continues to the next line, because it
// contained a region.
- spp = &(SYN_ITEMS(syn_block)[idx]);
+ synpat_T *spp = &(SYN_ITEMS(syn_block)[idx]);
if (spp->sp_type != SPTYPE_START) {
*hl_endpos = *startpos;
return;
}
// Find the SKIP or first END pattern after the last START pattern.
- for (;;) {
+ while (true) {
spp = &(SYN_ITEMS(syn_block)[idx]);
if (spp->sp_type != SPTYPE_START) {
break;
@@ -2404,14 +2394,14 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
unref_extmatch(re_extmatch_in);
re_extmatch_in = ref_extmatch(start_ext);
- matchcol = startpos->col; // start looking for a match at sstart
- start_idx = idx; // remember the first END pattern.
+ colnr_T matchcol = startpos->col; // start looking for a match at sstart
+ int start_idx = idx; // remember the first END pattern.
best_regmatch.startpos[0].col = 0; // avoid compiler warning
// use syntax iskeyword option
save_chartab(buf_chartab);
- for (;;) {
+ while (true) {
// Find end pattern that matches first after "matchcol".
best_idx = -1;
for (idx = start_idx; idx < syn_block->b_syn_patterns.ga_len; idx++) {
@@ -2470,7 +2460,7 @@ static void find_endpos(int idx, lpos_T *startpos, lpos_T *m_endpos, lpos_T *hl_
break;
}
- line = ml_get_buf(syn_buf, startpos->lnum, false);
+ line = ml_get_buf(syn_buf, startpos->lnum);
int line_len = (int)strlen(line);
// take care of an empty match or negative offset
@@ -2606,7 +2596,7 @@ static void syn_add_end_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *spp
if (result->lnum > syn_buf->b_ml.ml_line_count) {
col = 0;
} else if (off != 0) {
- base = ml_get_buf(syn_buf, result->lnum, false);
+ base = ml_get_buf(syn_buf, result->lnum);
p = base + col;
if (off > 0) {
while (off-- > 0 && *p != NUL) {
@@ -2648,10 +2638,10 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s
if (result->lnum > syn_buf->b_ml.ml_line_count) {
// a "\n" at the end of the pattern may take us below the last line
result->lnum = syn_buf->b_ml.ml_line_count;
- col = (int)strlen(ml_get_buf(syn_buf, result->lnum, false));
+ col = (int)strlen(ml_get_buf(syn_buf, result->lnum));
}
if (off != 0) {
- base = ml_get_buf(syn_buf, result->lnum, false);
+ base = ml_get_buf(syn_buf, result->lnum);
p = base + col;
if (off > 0) {
while (off-- && *p != NUL) {
@@ -2670,7 +2660,7 @@ static void syn_add_start_off(lpos_T *result, regmmatch_T *regmatch, synpat_T *s
/// Get current line in syntax buffer.
static char *syn_getcurline(void)
{
- return ml_get_buf(syn_buf, current_lnum, false);
+ return ml_get_buf(syn_buf, current_lnum);
}
// Call vim_regexec() to find a match with "rmp" in "syn_buf".
@@ -2693,7 +2683,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
rmp->rmm_maxcol = (colnr_T)syn_buf->b_p_smc;
- long r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, syn_tm, &timed_out);
+ int r = vim_regexec_multi(rmp, syn_win, syn_buf, lnum, col, syn_tm, &timed_out);
if (l_syn_time_on) {
pt = profile_end(pt);
@@ -2708,7 +2698,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
}
if (timed_out && !syn_win->w_s->b_syn_slow) {
syn_win->w_s->b_syn_slow = true;
- msg(_("'redrawtime' exceeded, syntax highlighting disabled"));
+ msg(_("'redrawtime' exceeded, syntax highlighting disabled"), 0);
}
if (r > 0) {
@@ -2730,7 +2720,7 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T
/// @param cur_si item at the top of the stack
/// @param ccharp conceal substitution char
static int check_keyword_id(char *const line, const int startcol, int *const endcolp,
- long *const flagsp, int16_t **const next_listp,
+ int *const flagsp, int16_t **const next_listp,
stateitem_T *const cur_si, int *const ccharp)
{
// Find first character after the keyword. First character was already
@@ -2811,9 +2801,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", 0);
} else {
- msg("syntax conceal off");
+ msg("syntax conceal off", 0);
}
} else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) {
curwin->w_s->b_syn_conceal = true;
@@ -2838,9 +2828,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", 0);
} else {
- msg("syntax case match");
+ msg("syntax case match", 0);
}
} else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) {
curwin->w_s->b_syn_ic = false;
@@ -2865,9 +2855,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", 0); break;
case SYNFLD_MINIMUM:
- msg("syntax foldlevel minimum"); break;
+ msg("syntax foldlevel minimum", 0); break;
default:
break;
}
@@ -2904,11 +2894,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", 0);
} else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) {
- msg("syntax spell notoplevel");
+ msg("syntax spell notoplevel", 0);
} else {
- msg("syntax spell default");
+ msg("syntax spell default", 0);
}
} else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) {
curwin->w_s->b_syn_spell = SYNSPL_TOP;
@@ -2939,11 +2929,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
arg = skipwhite(arg);
if (*arg == NUL) {
msg_puts("\n");
- if (curwin->w_s->b_syn_isk != empty_option) {
+ if (curwin->w_s->b_syn_isk != empty_string_option) {
msg_puts("syntax iskeyword ");
- msg_outtrans(curwin->w_s->b_syn_isk);
+ msg_outtrans(curwin->w_s->b_syn_isk, 0);
} else {
- msg_outtrans(_("syntax iskeyword not set"));
+ msg_outtrans(_("syntax iskeyword not set"), 0);
}
} else {
if (STRNICMP(arg, "clear", 5) == 0) {
@@ -3230,7 +3220,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing)
}
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
@@ -3422,7 +3412,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
msg_putchar(' ');
if (spp->sp_sync_idx >= 0) {
msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s)
- [spp->sp_sync_idx].sp_syn.id - 1));
+ [spp->sp_sync_idx].sp_syn.id - 1), 0);
} else {
msg_puts("NONE");
}
@@ -3435,15 +3425,13 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
(void)syn_list_header(did_header, 0, id, true);
msg_puts_attr("links to", attr);
msg_putchar(' ');
- msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1));
+ msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1), 0);
}
}
static void syn_list_flags(struct name_list *nlist, int flags, int attr)
{
- int i;
-
- for (i = 0; nlist[i].flag != 0; i++) {
+ for (int i = 0; nlist[i].flag != 0; i++) {
if (flags & nlist[i].flag) {
msg_puts_attr(nlist[i].name, attr);
msg_putchar(' ');
@@ -3458,7 +3446,7 @@ static void syn_list_cluster(int id)
// slight hack: roughly duplicate the guts of syn_list_header()
msg_putchar('\n');
- msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name);
+ msg_outtrans(SYN_CLSTR(curwin->w_s)[id].scl_name, 0);
if (msg_col >= endcol) { // output at least one space
endcol = msg_col + 1;
@@ -3495,9 +3483,9 @@ static void put_id_list(const char *const name, const int16_t *const list, const
int scl_id = *p - SYNID_CLUSTER;
msg_putchar('@');
- msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name);
+ msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name, 0);
} else {
- msg_outtrans(highlight_group_name(*p - 1));
+ msg_outtrans(highlight_group_name(*p - 1), 0);
}
if (p[1]) {
msg_putchar(',');
@@ -3517,9 +3505,9 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
msg_puts_attr("matchgroup", attr);
msg_putchar('=');
if (last_matchgroup == 0) {
- msg_outtrans("NONE");
+ msg_outtrans("NONE", 0);
} else {
- msg_outtrans(highlight_group_name(last_matchgroup - 1));
+ msg_outtrans(highlight_group_name(last_matchgroup - 1), 0);
}
msg_putchar(' ');
}
@@ -3536,7 +3524,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
}
}
msg_putchar(sepchars[i]);
- msg_outtrans(spp->sp_pattern);
+ msg_outtrans(spp->sp_pattern, 0);
msg_putchar(sepchars[i]);
// output any pattern options
@@ -3550,7 +3538,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const
msg_putchar(','); // Separate with commas.
}
msg_puts(spo_name_tab[i]);
- const long n = spp->sp_offsets[i];
+ const int n = spp->sp_offsets[i];
if (i != SPO_LC_OFF) {
if (spp->sp_off_flags & mask) {
msg_putchar('s');
@@ -3646,7 +3634,7 @@ static bool syn_list_keywords(const int id, const hashtab_T *const ht, bool did_
prev_skipempty = (kp->flags & HL_SKIPEMPTY);
}
}
- msg_outtrans((char *)kp->keyword);
+ msg_outtrans(kp->keyword, 0);
}
}
}
@@ -3677,7 +3665,7 @@ static void syn_clear_keyword(int id, hashtab_T *ht)
if (kp_next == NULL) {
hash_remove(ht, hi);
} else {
- hi->hi_key = (char *)KE2HIKEY(kp_next);
+ hi->hi_key = KE2HIKEY(kp_next);
}
} else {
kp_prev->ke_next = kp_next;
@@ -3732,9 +3720,9 @@ static void add_keyword(char *const name, const int id, const int flags,
{
char name_folded[MAXKEYWLEN + 1];
const char *const name_ic = (curwin->w_s->b_syn_ic)
- ? str_foldcase(name, (int)strlen(name), name_folded,
- sizeof(name_folded))
- : name;
+ ? str_foldcase(name, (int)strlen(name), name_folded,
+ sizeof(name_folded))
+ : name;
keyentry_T *const kp = xmalloc(offsetof(keyentry_T, keyword) + strlen(name_ic) + 1);
STRCPY(kp->keyword, name_ic);
@@ -3750,9 +3738,9 @@ static void add_keyword(char *const name, const int id, const int flags,
const hash_T hash = hash_hash(kp->keyword);
hashtab_T *const ht = (curwin->w_s->b_syn_ic)
- ? &curwin->w_s->b_keywtab_ic
- : &curwin->w_s->b_keywtab;
- hashitem_T *const hi = hash_lookup(ht, (const char *)kp->keyword,
+ ? &curwin->w_s->b_keywtab_ic
+ : &curwin->w_s->b_keywtab;
+ hashitem_T *const hi = hash_lookup(ht, kp->keyword,
strlen(kp->keyword), hash);
// even though it looks like only the kp->keyword member is
@@ -3766,7 +3754,7 @@ static void add_keyword(char *const name, const int id, const int flags,
} else {
// keyword already exists, prepend to list
kp->ke_next = HI2KE(hi);
- hi->hi_key = (char *)KE2HIKEY(kp);
+ hi->hi_key = KE2HIKEY(kp);
}
}
@@ -3803,7 +3791,6 @@ static char *get_group_name(char *arg, char **name_end)
/// Return NULL for any error;
static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, int skip)
{
- char *gname_start, *gname;
int syn_id;
int len = 0;
char *p;
@@ -3841,7 +3828,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
opt->flags |= HL_CONCEAL;
}
- for (;;) {
+ while (true) {
// This is used very often when a large number of keywords is defined.
// Need to skip quickly when no option name is found.
// Also avoid tolower(), it's slow.
@@ -3877,7 +3864,7 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
if (flagtab[fidx].argtype == 1) {
if (!opt->has_cont_list) {
- emsg(_("E395: contains argument not accepted here"));
+ emsg(_(e_contains_argument_not_accepted_here));
return NULL;
}
if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) {
@@ -3895,8 +3882,8 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
// cchar=?
*conceal_char = utf_ptr2char(arg + 6);
arg += utfc_ptr2len(arg + 6) - 1;
- if (!vim_isprintc_strict(*conceal_char)) {
- emsg(_("E844: invalid cchar value"));
+ if (!vim_isprintc(*conceal_char)) {
+ emsg(_(e_invalid_cchar_value));
return NULL;
}
arg = skipwhite(arg + 7);
@@ -3910,12 +3897,12 @@ static char *get_syn_options(char *arg, syn_opt_arg_T *opt, int *conceal_char, i
emsg(_("E393: group[t]here not accepted here"));
return NULL;
}
- gname_start = arg;
+ char *gname_start = arg;
arg = skiptowhite(arg);
if (gname_start == arg) {
return NULL;
}
- gname = xstrnsave(gname_start, (size_t)(arg - gname_start));
+ char *gname = xstrnsave(gname_start, (size_t)(arg - gname_start));
if (strcmp(gname, "NONE") == 0) {
*opt->sync_idx = NONE_IDX;
} else {
@@ -3976,7 +3963,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
int sgl_id = 1;
char *group_name_end;
char *rest;
- char *errormsg = NULL;
+ const char *errormsg = NULL;
int prev_toplvl_grp;
int prev_syn_inc_tag;
bool source = false;
@@ -4029,7 +4016,7 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
curwin->w_s->b_syn_topgrp = sgl_id;
if (source
- ? do_source(eap->arg, false, DOSO_NONE) == FAIL
+ ? do_source(eap->arg, false, DOSO_NONE, NULL) == FAIL
: source_runtime(eap->arg, DIP_ALL) == FAIL) {
semsg(_(e_notopen), eap->arg);
}
@@ -4114,8 +4101,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing)
}
if (p[1] == ']') {
if (p[2] != NUL) {
- semsg(_("E890: trailing char after ']': %s]%s"),
- kw, &p[2]);
+ semsg(_(e_trailing_char_after_rsb_str_str), kw, &p[2]);
goto error;
}
kw = p + 1;
@@ -4400,8 +4386,8 @@ static void syn_cmd_region(exarg_T *eap, int syncing)
SYN_ITEMS(curwin->w_s)[idx] = *(ppp->pp_synp);
SYN_ITEMS(curwin->w_s)[idx].sp_syncing = syncing;
SYN_ITEMS(curwin->w_s)[idx].sp_type =
- (item == ITEM_START) ? SPTYPE_START :
- (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
+ (item == ITEM_START) ? SPTYPE_START
+ : (item == ITEM_SKIP) ? SPTYPE_SKIP : SPTYPE_END;
SYN_ITEMS(curwin->w_s)[idx].sp_flags |= syn_opt_arg.flags;
SYN_ITEMS(curwin->w_s)[idx].sp_syn.id = (int16_t)syn_id;
SYN_ITEMS(curwin->w_s)[idx].sp_syn.inc_tag =
@@ -4678,7 +4664,7 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing)
}
scl_id -= SYNID_CLUSTER;
- for (;;) {
+ while (true) {
if (STRNICMP(rest, "add", 3) == 0
&& (ascii_iswhite(rest[3]) || rest[3] == '=')) {
opt_len = 3;
@@ -4736,17 +4722,15 @@ static void init_syn_patterns(void)
/// @return a pointer to the next argument, or NULL in case of an error.
static char *get_syn_pattern(char *arg, synpat_T *ci)
{
- char *end;
int *p;
int idx;
- char *cpo_save;
// need at least three chars
if (arg == NULL || arg[0] == NUL || arg[1] == NUL || arg[2] == NUL) {
return NULL;
}
- end = skip_regexp(arg + 1, *arg, true);
+ char *end = skip_regexp(arg + 1, *arg, true);
if (*end != *arg) { // end delimiter not found
semsg(_("E401: Pattern delimiter not found: %s"), arg);
return NULL;
@@ -4755,8 +4739,8 @@ static char *get_syn_pattern(char *arg, synpat_T *ci)
ci->sp_pattern = xstrnsave(arg + 1, (size_t)(end - arg) - 1);
// Make 'cpoptions' empty, to avoid the 'l' flag
- cpo_save = p_cpo;
- p_cpo = empty_option;
+ char *cpo_save = p_cpo;
+ p_cpo = empty_string_option;
ci->sp_prog = vim_regcomp(ci->sp_pattern, RE_MAGIC);
p_cpo = cpo_save;
@@ -4913,7 +4897,7 @@ static void syn_cmd_sync(exarg_T *eap, int syncing)
// Make 'cpoptions' empty, to avoid the 'l' flag
cpo_save = p_cpo;
- p_cpo = empty_option;
+ p_cpo = empty_string_option;
curwin->w_s->b_syn_linecont_prog =
vim_regcomp(curwin->w_s->b_syn_linecont_pat, RE_MAGIC);
p_cpo = cpo_save;
@@ -5047,7 +5031,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
regmatch.rm_ic = true;
id = 0;
for (int i = highlight_num_groups(); --i >= 0;) {
- if (vim_regexec(&regmatch, highlight_group_name(i), (colnr_T)0)) {
+ if (vim_regexec(&regmatch, highlight_group_name(i), 0)) {
if (round == 2) {
// Got more items than expected; can happen
// when adding items that match:
@@ -5057,7 +5041,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
xfree(retval);
round = 1;
} else {
- retval[count] = (int16_t)(i + 1); // -V522
+ retval[count] = (int16_t)(i + 1);
}
}
count++;
@@ -5145,7 +5129,6 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
{
int retval;
int16_t *scl_list;
- int16_t item;
int16_t id = ssp->id;
static int depth = 0;
int r;
@@ -5181,7 +5164,7 @@ static int in_id_list(stateitem_T *cur_si, int16_t *list, struct sp_syn *ssp, in
// If the first item is "ALLBUT", return true if "id" is NOT in the
// contains list. We also require that "id" is at the same ":syn include"
// level as the list.
- item = *list;
+ int16_t item = *list;
if (item >= SYNID_ALLBUT && item < SYNID_CLUSTER) {
if (item < SYNID_TOP) {
// ALL or ALLBUT: accept all groups in the same file
@@ -5291,16 +5274,13 @@ void ex_syntax(exarg_T *eap)
void ex_ownsyntax(exarg_T *eap)
{
- char *old_value;
- char *new_value;
-
if (curwin->w_s == &curwin->w_buffer->b_s) {
curwin->w_s = xcalloc(1, sizeof(synblock_T));
hash_init(&curwin->w_s->b_keywtab);
hash_init(&curwin->w_s->b_keywtab_ic);
// TODO(vim): Keep the spell checking as it was.
curwin->w_p_spell = false; // No spell checking
- // make sure option values are "empty_option" instead of NULL
+ // make sure option values are "empty_string_option" instead of NULL
clear_string_option(&curwin->w_s->b_p_spc);
clear_string_option(&curwin->w_s->b_p_spf);
clear_string_option(&curwin->w_s->b_p_spl);
@@ -5309,7 +5289,7 @@ void ex_ownsyntax(exarg_T *eap)
}
// Save value of b:current_syntax.
- old_value = get_var_value("b:current_syntax");
+ char *old_value = get_var_value("b:current_syntax");
if (old_value != NULL) {
old_value = xstrdup(old_value);
}
@@ -5318,7 +5298,7 @@ void ex_ownsyntax(exarg_T *eap)
apply_autocmds(EVENT_SYNTAX, eap->arg, curbuf->b_fname, true, curbuf);
// Move value of b:current_syntax to w:current_syntax.
- new_value = get_var_value("b:current_syntax");
+ char *new_value = get_var_value("b:current_syntax");
if (new_value != NULL) {
set_internal_string_var("w:current_syntax", new_value);
}
@@ -5379,7 +5359,7 @@ void set_context_in_syntax_cmd(expand_T *xp, const char *arg)
}
// (part of) subcommand already typed
- const char *p = (const char *)skiptowhite(arg);
+ const char *p = skiptowhite(arg);
if (*p == NUL) {
return;
}
@@ -5483,10 +5463,9 @@ int get_syntax_info(int *seqnrp)
int syn_get_concealed_id(win_T *wp, linenr_T lnum, colnr_T col)
{
int seqnr;
- int syntax_flags;
(void)syn_get_id(wp, lnum, col, false, NULL, false);
- syntax_flags = get_syntax_info(&seqnr);
+ int syntax_flags = get_syntax_info(&seqnr);
if (syntax_flags & HL_CONCEAL) {
return seqnr;
@@ -5595,7 +5574,7 @@ static void syntime_clear(void)
synpat_T *spp;
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
for (int idx = 0; idx < curwin->w_s->b_syn_patterns.ga_len; idx++) {
@@ -5633,7 +5612,7 @@ static int syn_compare_syntime(const void *v1, const void *v2)
static void syntime_report(void)
{
if (!syntax_present(curwin)) {
- msg(_(msg_no_items));
+ msg(_(msg_no_items), 0);
return;
}
@@ -5649,11 +5628,11 @@ static void syntime_report(void)
p = GA_APPEND_VIA_PTR(time_entry_T, &ga);
p->total = spp->sp_time.total;
total_total = profile_add(total_total, spp->sp_time.total);
- p->count = (int)spp->sp_time.count;
- p->match = (int)spp->sp_time.match;
- total_count += (int)spp->sp_time.count;
+ p->count = spp->sp_time.count;
+ p->match = spp->sp_time.match;
+ total_count += spp->sp_time.count;
p->slowest = spp->sp_time.slowest;
- proftime_T tm = profile_divide(spp->sp_time.total, (int)spp->sp_time.count);
+ proftime_T tm = profile_divide(spp->sp_time.total, spp->sp_time.count);
p->average = tm;
p->id = spp->sp_syn.id;
p->pattern = spp->sp_pattern;
@@ -5687,7 +5666,7 @@ static void syntime_report(void)
msg_puts(profile_msg(p->average));
msg_puts(" ");
msg_advance(50);
- msg_outtrans(highlight_group_name(p->id - 1));
+ msg_outtrans(highlight_group_name(p->id - 1), 0);
msg_puts(" ");
msg_advance(69);
@@ -5700,7 +5679,7 @@ static void syntime_report(void)
if (len > (int)strlen(p->pattern)) {
len = (int)strlen(p->pattern);
}
- msg_outtrans_len(p->pattern, len);
+ msg_outtrans_len(p->pattern, len, 0);
msg_puts("\n");
}
ga_clear(&ga);
diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h
index 0a63392a04..4e9c7a27dc 100644
--- a/src/nvim/syntax.h
+++ b/src/nvim/syntax.h
@@ -1,12 +1,13 @@
-#ifndef NVIM_SYNTAX_H
-#define NVIM_SYNTAX_H
+#pragma once
#include <stdbool.h>
-#include "nvim/buffer_defs.h"
-#include "nvim/ex_cmds_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/globals.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
+#include "nvim/syntax_defs.h" // IWYU pragma: export
#define HL_CONTAINED 0x01 // not used on toplevel
#define HL_TRANSP 0x02 // has no highlighting
@@ -36,5 +37,3 @@ extern const char *const highlight_init_cmdline[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "syntax.h.generated.h"
#endif
-
-#endif // NVIM_SYNTAX_H
diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h
index 3f3101f7e3..ae4997154e 100644
--- a/src/nvim/syntax_defs.h
+++ b/src/nvim/syntax_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_SYNTAX_DEFS_H
-#define NVIM_SYNTAX_DEFS_H
+#pragma once
#include "nvim/highlight_defs.h"
@@ -30,7 +29,7 @@ struct keyentry {
int16_t *next_list; // ID list for next match (if non-zero)
int flags;
int k_char; // conceal substitute character
- char keyword[1]; // actually longer
+ char keyword[];
};
// Struct used to store one state of the state stack.
@@ -59,5 +58,3 @@ struct syn_state {
linenr_T sst_change_lnum; // when non-zero, change in this line
// may have made the state invalid
};
-
-#endif // NVIM_SYNTAX_DEFS_H
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 42618e8924..c6a1a13606 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Code to handle tags and the tag stack
#include <assert.h>
@@ -11,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
@@ -20,44 +17,45 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
#include "nvim/help.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/input.h"
#include "nvim/insexpand.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
#include "nvim/option.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/regexp.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
-#include "nvim/types.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
// Structure to hold pointers to various items in a tag line.
@@ -188,11 +186,19 @@ typedef struct {
# include "tag.c.generated.h"
#endif
-static char *bottommsg = N_("E555: at bottom of tag stack");
-static char *topmsg = N_("E556: at top of tag stack");
-static char *recurmsg = N_("E986: cannot modify the tag stack within tagfunc");
-static char *tfu_inv_ret_msg = N_("E987: invalid return value from tagfunc");
-static char e_window_unexpectedly_close_while_searching_for_tags[]
+static const char e_tag_stack_empty[]
+ = N_("E73: Tag stack empty");
+static const char e_tag_not_found_str[]
+ = N_("E426: Tag not found: %s");
+static const char e_at_bottom_of_tag_stack[]
+ = N_("E555: At bottom of tag stack");
+static const char e_at_top_of_tag_stack[]
+ = N_("E556: At top of tag stack");
+static const char e_cannot_modify_tag_stack_within_tagfunc[]
+ = N_("E986: Cannot modify the tag stack within tagfunc");
+static const char e_invalid_return_value_from_tagfunc[]
+ = N_("E987: Invalid return value from tagfunc");
+static const char e_window_unexpectedly_close_while_searching_for_tags[]
= N_("E1299: Window unexpectedly closed while searching for tags");
static char *tagmatchname = NULL; // name of last used tag
@@ -210,20 +216,23 @@ static Callback tfu_cb; // 'tagfunc' callback function
/// Reads the 'tagfunc' option value and convert that to a callback value.
/// Invoked when the 'tagfunc' option is set. The option value can be a name of
/// a function (string), or function(<name>) or funcref(<name>) or a lambda.
-void set_tagfunc_option(char **errmsg)
+const char *did_set_tagfunc(optset_T *args)
{
+ buf_T *buf = (buf_T *)args->os_buf;
+
callback_free(&tfu_cb);
- callback_free(&curbuf->b_tfu_cb);
+ callback_free(&buf->b_tfu_cb);
- if (*curbuf->b_p_tfu == NUL) {
- return;
+ if (*buf->b_p_tfu == NUL) {
+ return NULL;
}
- if (option_set_callback_func(curbuf->b_p_tfu, &tfu_cb) == FAIL) {
- *errmsg = e_invarg;
+ if (option_set_callback_func(buf->b_p_tfu, &tfu_cb) == FAIL) {
+ return e_invarg;
}
- callback_copy(&curbuf->b_tfu_cb, &tfu_cb);
+ callback_copy(&buf->b_tfu_cb, &tfu_cb);
+ return NULL;
}
#if defined(EXITFREE)
@@ -278,10 +287,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
int cur_fnum = curbuf->b_fnum;
int oldtagstackidx = tagstackidx;
int prevtagstackidx = tagstackidx;
- int prev_num_matches;
int new_tag = false;
- int i;
- int ic;
int no_regexp = false;
int error_cur_match = 0;
int save_pos = false;
@@ -301,7 +307,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
static int flags;
if (tfu_in_use) {
- emsg(_(recurmsg));
+ emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return;
}
@@ -320,7 +326,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
use_tfu = 0;
}
- prev_num_matches = num_matches;
+ int prev_num_matches = num_matches;
free_string_option(nofile_fname);
nofile_fname = NULL;
@@ -329,7 +335,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// Don't add a tag to the tagstack if 'tagstack' has been reset.
assert(tag != NULL);
- if (!p_tgst && *tag != NUL) { // -V522
+ if (!p_tgst && *tag != NUL) {
use_tagstack = false;
new_tag = true;
if (g_do_tagpreview != 0) {
@@ -369,7 +375,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (++tagstacklen > TAGSTACKSIZE) {
tagstacklen = TAGSTACKSIZE;
tagstack_clear_entry(&tagstack[0]);
- for (i = 1; i < tagstacklen; i++) {
+ for (int i = 1; i < tagstacklen; i++) {
tagstack[i - 1] = tagstack[i];
}
tagstackidx--;
@@ -385,17 +391,17 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
new_tag = true;
} else {
- if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL :
- tagstacklen == 0) {
+ if (g_do_tagpreview != 0 ? ptag_entry.tagname == NULL
+ : tagstacklen == 0) {
// empty stack
- emsg(_(e_tagstack));
+ emsg(_(e_tag_stack_empty));
goto end_do_tag;
}
if (type == DT_POP) { // go to older position
const bool old_KeyTyped = KeyTyped;
if ((tagstackidx -= count) < 0) {
- emsg(_(bottommsg));
+ emsg(_(e_at_bottom_of_tag_stack));
if (tagstackidx + count == 0) {
// We did [num]^T from the bottom of the stack
tagstackidx = 0;
@@ -405,7 +411,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// way to the bottom now.
tagstackidx = 0;
} else if (tagstackidx >= tagstacklen) { // count == 0?
- emsg(_(topmsg));
+ emsg(_(e_at_top_of_tag_stack));
goto end_do_tag;
}
@@ -454,10 +460,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// go to the last one. Don't store the cursor
// position.
tagstackidx = tagstacklen - 1;
- emsg(_(topmsg));
+ emsg(_(e_at_top_of_tag_stack));
save_pos = false;
} else if (tagstackidx < 0) { // must have been count == 0
- emsg(_(bottommsg));
+ emsg(_(e_at_bottom_of_tag_stack));
tagstackidx = 0;
goto end_do_tag;
}
@@ -538,7 +544,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
}
// Repeat searching for tags, when a file has not been found.
- for (;;) {
+ while (true) {
int other_name;
char *name;
@@ -607,19 +613,18 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// to the start. Avoids that the order changes when using
// ":tnext" and jumping to another file.
if (!new_tag && !other_name) {
- int j, k;
int idx = 0;
tagptrs_T tagp, tagp2;
// Find the position of each old match in the new list. Need
// to use parse_match() to find the tag line.
- for (j = 0; j < num_matches; j++) {
+ for (int j = 0; j < num_matches; j++) {
parse_match(matches[j], &tagp);
- for (i = idx; i < new_num_matches; i++) {
+ for (int i = idx; i < new_num_matches; i++) {
parse_match(new_matches[i], &tagp2);
if (strcmp(tagp.tagname, tagp2.tagname) == 0) {
char *p = new_matches[i];
- for (k = i; k > idx; k--) {
+ for (int k = i; k > idx; k--) {
new_matches[k] = new_matches[k - 1];
}
new_matches[idx++] = p;
@@ -635,7 +640,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (num_matches <= 0) {
if (verbose) {
- semsg(_("E426: tag not found: %s"), name);
+ semsg(_(e_tag_not_found_str), name);
}
g_do_tagpreview = 0;
} else {
@@ -658,7 +663,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
if (ask_for_selection) {
// Ask to select a tag from the list.
- i = prompt_for_number(NULL);
+ int i = prompt_for_number(NULL);
if (i <= 0 || i > num_matches || got_int) {
// no valid choice: don't change anything
if (use_tagstack) {
@@ -696,7 +701,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
&& tagp2.user_data) {
XFREE_CLEAR(tagstack[tagstackidx].user_data);
tagstack[tagstackidx].user_data =
- xstrnsave(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data));
+ xmemdupz(tagp2.user_data, (size_t)(tagp2.user_data_end - tagp2.user_data));
}
tagstackidx++;
@@ -708,10 +713,10 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
// Only when going to try the next match, report that the previous
// file didn't exist. Otherwise an emsg() is given below.
if (nofile_fname != NULL && error_cur_match != cur_match) {
- smsg(_("File \"%s\" does not exist"), nofile_fname);
+ smsg(0, _("File \"%s\" does not exist"), nofile_fname);
}
- ic = (matches[cur_match][0] & MT_IC_OFF);
+ int ic = (matches[cur_match][0] & MT_IC_OFF);
if (type != DT_TAG && type != DT_SELECT && type != DT_JUMP
&& (num_matches > 1 || ic)
&& !skip_msg) {
@@ -721,22 +726,18 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
num_matches,
max_num_matches != MAXCOL ? _(" or more") : "");
if (ic) {
- STRCAT(IObuff, _(" Using tag with different case!"));
+ xstrlcat(IObuff, _(" Using tag with different case!"), IOSIZE);
}
if ((num_matches > prev_num_matches || new_tag)
&& num_matches > 1) {
- if (ic) {
- msg_attr((const char *)IObuff, HL_ATTR(HLF_W));
- } else {
- msg(IObuff);
- }
+ msg(IObuff, ic ? HL_ATTR(HLF_W) : 0);
msg_scroll = true; // Don't overwrite this message.
} else {
give_warning(IObuff, ic);
}
if (ic && !msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1007L, true);
+ os_delay(1007, true);
}
}
@@ -745,7 +746,7 @@ void do_tag(char *tag, int type, int count, int forceit, int verbose)
set_vim_var_string(VV_SWAPCOMMAND, IObuff, -1);
// Jump to the desired match.
- i = jumpto_tag(matches[cur_match], forceit, true);
+ int i = jumpto_tag(matches[cur_match], forceit, true);
set_vim_var_string(VV_SWAPCOMMAND, NULL, -1);
@@ -795,17 +796,12 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
{
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
- int i;
- char *p;
- char *command_end;
tagptrs_T tagp;
- int taglen;
- int attr;
// Assume that the first match indicates how long the tags can
// be, and align the file names to that.
parse_match(matches[0], &tagp);
- taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
+ int taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
if (taglen < 18) {
taglen = 18;
}
@@ -821,7 +817,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
taglen_advance(taglen);
msg_puts_attr(_("file\n"), HL_ATTR(HLF_T));
- for (i = 0; i < num_matches && !got_int; i++) {
+ for (int i = 0; i < num_matches && !got_int; i++) {
parse_match(matches[i], &tagp);
if (!new_tag && (
(g_do_tagpreview != 0
@@ -837,21 +833,18 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
mt_names[matches[i][0] & MT_MASK]);
msg_puts(IObuff);
if (tagp.tagkind != NULL) {
- msg_outtrans_len(tagp.tagkind,
- (int)(tagp.tagkind_end - tagp.tagkind));
+ msg_outtrans_len(tagp.tagkind, (int)(tagp.tagkind_end - tagp.tagkind), 0);
}
msg_advance(13);
- msg_outtrans_len_attr(tagp.tagname,
- (int)(tagp.tagname_end - tagp.tagname),
- HL_ATTR(HLF_T));
+ msg_outtrans_len(tagp.tagname, (int)(tagp.tagname_end - tagp.tagname), HL_ATTR(HLF_T));
msg_putchar(' ');
taglen_advance(taglen);
// Find out the actual file name. If it is long, truncate
// it and put "..." in the middle
- p = tag_full_fname(&tagp);
+ const char *p = tag_full_fname(&tagp);
if (p != NULL) {
- msg_outtrans_attr(p, HL_ATTR(HLF_D));
+ msg_outtrans(p, HL_ATTR(HLF_D));
XFREE_CLEAR(p);
}
if (msg_col > 0) {
@@ -863,7 +856,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
msg_advance(15);
// print any extra fields
- command_end = tagp.command_end;
+ const char *command_end = tagp.command_end;
if (command_end != NULL) {
p = command_end + 3;
while (*p && *p != '\r' && *p != '\n') {
@@ -884,7 +877,7 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
continue;
}
// print all other extra fields
- attr = HL_ATTR(HLF_CM);
+ int attr = HL_ATTR(HLF_CM);
while (*p && *p != '\r' && *p != '\n') {
if (msg_col + ptr2cells(p) >= Columns) {
msg_putchar('\n');
@@ -979,27 +972,20 @@ static void print_tag_list(int new_tag, int use_tagstack, int num_matches, char
/// window.
static int add_llist_tags(char *tag, int num_matches, char **matches)
{
- list_T *list;
char tag_name[128 + 1];
- char *fname;
- char *cmd;
- int i;
- char *p;
tagptrs_T tagp;
- fname = xmalloc(MAXPATHL + 1);
- cmd = xmalloc(CMDBUFFSIZE + 1);
- list = tv_list_alloc(0);
+ char *fname = xmalloc(MAXPATHL + 1);
+ char *cmd = xmalloc(CMDBUFFSIZE + 1);
+ list_T *list = tv_list_alloc(0);
- for (i = 0; i < num_matches; i++) {
- int len, cmd_len;
- long lnum;
+ for (int i = 0; i < num_matches; i++) {
dict_T *dict;
parse_match(matches[i], &tagp);
// Save the tag name
- len = (int)(tagp.tagname_end - tagp.tagname);
+ int len = (int)(tagp.tagname_end - tagp.tagname);
if (len > 128) {
len = 128;
}
@@ -1007,7 +993,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
tag_name[len] = NUL;
// Save the tag file name
- p = tag_full_fname(&tagp);
+ char *p = tag_full_fname(&tagp);
if (p == NULL) {
continue;
}
@@ -1016,18 +1002,16 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
// Get the line number or the search pattern used to locate
// the tag.
- lnum = 0;
+ linenr_T lnum = 0;
if (isdigit((uint8_t)(*tagp.command))) {
// Line number is used to locate the tag
- lnum = atol(tagp.command);
+ lnum = atoi(tagp.command);
} else {
- char *cmd_start, *cmd_end;
-
// Search pattern is used to locate the tag
// Locate the end of the command
- cmd_start = tagp.command;
- cmd_end = tagp.command_end;
+ char *cmd_start = tagp.command;
+ char *cmd_end = tagp.command_end;
if (cmd_end == NULL) {
for (p = tagp.command;
*p && *p != '\r' && *p != '\n'; p++) {}
@@ -1065,7 +1049,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
STRCAT(cmd, "\\V");
len += 2;
- cmd_len = (int)(cmd_end - cmd_start + 1);
+ int cmd_len = (int)(cmd_end - cmd_start + 1);
if (cmd_len > (CMDBUFFSIZE - 5)) {
cmd_len = CMDBUFFSIZE - 5;
}
@@ -1088,7 +1072,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
tv_list_append_dict(list, dict);
tv_dict_add_str(dict, S_LEN("text"), tag_name);
- tv_dict_add_str(dict, S_LEN("filename"), (const char *)fname);
+ tv_dict_add_str(dict, S_LEN("filename"), fname);
tv_dict_add_nr(dict, S_LEN("lnum"), lnum);
if (lnum == 0) {
tv_dict_add_str(dict, S_LEN("pattern"), cmd);
@@ -1124,17 +1108,15 @@ static void taglen_advance(int l)
// Print the tag stack
void do_tags(exarg_T *eap)
{
- int i;
- char *name;
taggy_T *tagstack = curwin->w_tagstack;
int tagstackidx = curwin->w_tagstackidx;
int tagstacklen = curwin->w_tagstacklen;
// Highlight title
msg_puts_title(_("\n # TO tag FROM line in file/text"));
- for (i = 0; i < tagstacklen; i++) {
+ for (int i = 0; i < tagstacklen; i++) {
if (tagstack[i].tagname != NULL) {
- name = fm_getname(&(tagstack[i].fmark), 30);
+ char *name = fm_getname(&(tagstack[i].fmark), 30);
if (name == NULL) { // file name not available
continue;
}
@@ -1146,9 +1128,8 @@ void do_tags(exarg_T *eap)
tagstack[i].cur_match + 1,
tagstack[i].tagname,
tagstack[i].fmark.mark.lnum);
- msg_outtrans(IObuff);
- msg_outtrans_attr(name, tagstack[i].fmark.fnum == curbuf->b_fnum
- ? HL_ATTR(HLF_D) : 0);
+ msg_outtrans(IObuff, 0);
+ msg_outtrans(name, tagstack[i].fmark.fnum == curbuf->b_fnum ? HL_ATTR(HLF_D) : 0);
xfree(name);
}
}
@@ -1162,10 +1143,8 @@ void do_tags(exarg_T *eap)
// Make sure case is folded to uppercase in comparison (like for 'sort -f')
static int tag_strnicmp(char *s1, char *s2, size_t len)
{
- int i;
-
while (len > 0) {
- i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2));
+ int i = TOUPPER_ASC((uint8_t)(*s1)) - TOUPPER_ASC((uint8_t)(*s2));
if (i != 0) {
return i; // this character different
}
@@ -1227,10 +1206,7 @@ static void prepare_pats(pat_T *pats, int has_re)
/// @param buf_ffname name of buffer for priority
static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flags, char *buf_ffname)
{
- pos_T save_pos;
- list_T *taglist;
int ntags = 0;
- int result = FAIL;
typval_T args[4];
typval_T rettv;
char flagString[4];
@@ -1256,10 +1232,10 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
// create 'info' dict argument
dict_T *const d = tv_dict_alloc_lock(VAR_FIXED);
if (tag != NULL && tag->user_data != NULL) {
- tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data);
+ tv_dict_add_str(d, S_LEN("user_data"), tag->user_data);
}
if (buf_ffname != NULL) {
- tv_dict_add_str(d, S_LEN("buf_ffname"), (const char *)buf_ffname);
+ tv_dict_add_str(d, S_LEN("buf_ffname"), buf_ffname);
}
d->dv_refcount++;
@@ -1270,12 +1246,12 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
vim_snprintf(flagString, sizeof(flagString),
"%s%s%s",
- g_tag_at_cursor ? "c": "",
- flags & TAG_INS_COMP ? "i": "",
- flags & TAG_REGEXP ? "r": "");
+ g_tag_at_cursor ? "c" : "",
+ flags & TAG_INS_COMP ? "i" : "",
+ flags & TAG_REGEXP ? "r" : "");
- save_pos = curwin->w_cursor;
- result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
+ pos_T save_pos = curwin->w_cursor;
+ int result = callback_call(&curbuf->b_tfu_cb, 3, args, &rettv);
curwin->w_cursor = save_pos; // restore the cursor position
d->dv_refcount--;
@@ -1288,10 +1264,10 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
if (rettv.v_type != VAR_LIST || !rettv.vval.v_list) {
tv_clear(&rettv);
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
return FAIL;
}
- taglist = rettv.vval.v_list;
+ list_T *taglist = rettv.vval.v_list;
TV_LIST_ITER_CONST(taglist, li, {
char *res_name;
@@ -1302,7 +1278,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
int name_only = flags & TAG_NAMES;
if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) {
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
@@ -1313,7 +1289,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
res_kind = NULL;
TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
- const char *dict_key = (char *)di->di_key;
+ const char *dict_key = di->di_key;
typval_T *tv = &di->di_tv;
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
@@ -1348,7 +1324,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
if (!res_name || !res_fname || !res_cmd) {
- emsg(_(tfu_inv_ret_msg));
+ emsg(_(e_invalid_return_value_from_tagfunc));
break;
}
@@ -1382,7 +1358,7 @@ static int find_tagfunc_tags(char *pat, garray_T *ga, int *match_count, int flag
}
TV_DICT_ITER(TV_LIST_ITEM_TV(li)->vval.v_dict, di, {
- const char *dict_key = (char *)di->di_key;
+ const char *dict_key = di->di_key;
typval_T *tv = &di->di_tv;
if (tv->v_type != VAR_STRING || tv->vval.v_string == NULL) {
continue;
@@ -1554,11 +1530,10 @@ static int findtags_apply_tfu(findtags_state_T *st, char *pat, char *buf_ffname)
static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch_info_T *sinfo_p)
{
int eof;
- off_T offset;
// For binary search: compute the next offset to use.
if (st->state == TS_BINARY) {
- offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2);
+ off_T offset = sinfo_p->low_offset + ((sinfo_p->high_offset - sinfo_p->low_offset) / 2);
if (offset == sinfo_p->curr_offset) {
return TAGS_READ_EOF; // End the binary search without a match.
} else {
@@ -1569,7 +1544,7 @@ static tags_read_status_T findtags_get_next_line(findtags_state_T *st, tagsearch
sinfo_p->curr_offset -= st->lbuf_size * 2;
if (sinfo_p->curr_offset < 0) {
sinfo_p->curr_offset = 0;
- rewind(st->fp);
+ (void)fseek(st->fp, 0, SEEK_SET);
st->state = TS_STEP_FORWARD;
}
}
@@ -1733,9 +1708,6 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
tagsearch_info_T *sinfo_p)
{
int status;
- int i;
- int cmplen;
- int tagcmp;
// Figure out where the different strings are in this line.
// For "normal" tags: Do a quick check if the tag matches.
@@ -1751,7 +1723,7 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
// Skip this line if the length of the tag is different and
// there is no regexp, or the tag is too short.
- cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
+ int cmplen = (int)(tagpp->tagname_end - tagpp->tagname);
if (p_tl != 0 && cmplen > p_tl) { // adjust for 'taglength'
cmplen = (int)p_tl;
}
@@ -1762,8 +1734,9 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
}
if (st->state == TS_BINARY) {
+ int tagcmp;
// Simplistic check for unsorted tags file.
- i = (int)tagpp->tagname[0];
+ int i = (uint8_t)tagpp->tagname[0];
if (margs->sortic) {
i = TOUPPER_ASC(tagpp->tagname[0]);
}
@@ -1912,13 +1885,13 @@ static bool findtags_match_tag(findtags_state_T *st, tagptrs_T *tagpp, findtags_
if (!match && st->orgpat->regmatch.regprog != NULL) {
char cc = *tagpp->tagname_end;
*tagpp->tagname_end = NUL;
- match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, (colnr_T)0);
+ match = vim_regexec(&st->orgpat->regmatch, tagpp->tagname, 0);
if (match) {
margs->matchoff = (int)(st->orgpat->regmatch.startp[0] - tagpp->tagname);
if (st->orgpat->regmatch.rm_ic) {
st->orgpat->regmatch.rm_ic = false;
margs->match_no_ic = vim_regexec(&st->orgpat->regmatch,
- tagpp->tagname, (colnr_T)0);
+ tagpp->tagname, 0);
st->orgpat->regmatch.rm_ic = true;
}
}
@@ -1959,11 +1932,10 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
const bool name_only = (st->flags & TAG_NAMES);
int mtt;
size_t len = 0;
+ size_t mfp_size = 0;
bool is_current; // file name matches
bool is_static; // current tag line is static
char *mfp;
- char *p;
- char *s;
// Decide in which array to store this match.
is_current = test_for_current(tagpp->fname, tagpp->fname_end,
@@ -2002,13 +1974,14 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// The format is {tagname}@{lang}NUL{heuristic}NUL
*tagpp->tagname_end = NUL;
len = (size_t)(tagpp->tagname_end - tagpp->tagname);
- mfp = xmalloc(sizeof(char) + len + 10 + ML_EXTRA + 1);
+ mfp_size = sizeof(char) + len + 10 + ML_EXTRA + 1;
+ mfp = xmalloc(mfp_size);
- p = mfp;
+ char *p = mfp;
STRCPY(p, tagpp->tagname);
p[len] = '@';
STRCPY(p + len + 1, st->help_lang);
- snprintf(p + len + 1 + ML_EXTRA, strlen(p) + len + 1 + ML_EXTRA, "%06d",
+ snprintf(p + len + 1 + ML_EXTRA, mfp_size - (len + 1 + ML_EXTRA), "%06d",
help_heuristic(tagpp->tagname,
margs->match_re ? margs->matchoff : 0,
!margs->match_no_ic) + st->help_pri);
@@ -2055,7 +2028,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// Here <mtt> is the "mtt" value plus 1 to avoid NUL.
len = tag_fname_len + strlen(st->lbuf) + 3;
mfp = xmalloc(sizeof(char) + len + 1);
- p = mfp;
+ char *p = mfp;
p[0] = (char)(mtt + 1);
STRCPY(p + 1, st->tag_fname);
#ifdef BACKSLASH_IN_FILENAME
@@ -2064,7 +2037,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
slash_adjust(p + 1);
#endif
p[tag_fname_len + 1] = TAG_SEP;
- s = p + 1 + tag_fname_len + 1;
+ char *s = p + 1 + tag_fname_len + 1;
STRCPY(s, st->lbuf);
}
@@ -2077,7 +2050,7 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
// follow after it. E.g. help tags store the priority
// after the NUL.
*hash = hash_hash(mfp);
- hi = hash_lookup(&st->ht_match[mtt], (const char *)mfp, strlen(mfp), *hash);
+ hi = hash_lookup(&st->ht_match[mtt], mfp, strlen(mfp), *hash);
if (HASHITEM_EMPTY(hi)) {
hash_add_item(&st->ht_match[mtt], hi, mfp, *hash);
GA_APPEND(char *, &st->ga_match[mtt], mfp);
@@ -2096,7 +2069,6 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
{
tagptrs_T tagp;
tagsearch_info_T search_info;
- int retval;
hash_T hash = 0;
// This is only to avoid a compiler warning for using search_info
@@ -2104,7 +2076,7 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
CLEAR_FIELD(search_info);
// Read and parse the lines in the file one by one
- for (;;) {
+ while (true) {
// check for CTRL-C typed, more often when jumping around
if (st->state == TS_BINARY || st->state == TS_SKIP_BACK) {
line_breakcheck();
@@ -2128,7 +2100,7 @@ static void findtags_get_all_tags(findtags_state_T *st, findtags_match_args_T *m
goto line_read_in;
}
- retval = (int)findtags_get_next_line(st, &search_info);
+ int retval = (int)findtags_get_next_line(st, &search_info);
if (retval == TAGS_READ_IGNORE) {
continue;
}
@@ -2218,7 +2190,7 @@ static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname)
if (p_verbose >= 5) {
verbose_enter();
- smsg(_("Searching tags file %s"), st->tag_fname);
+ smsg(0, _("Searching tags file %s"), st->tag_fname);
verbose_leave();
}
st->did_open = true; // remember that we found at least one file
@@ -2252,10 +2224,6 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
{
const bool name_only = (st->flags & TAG_NAMES);
char **matches;
- int mtt;
- int i;
- char *mfp;
- char *p;
if (st->match_count > 0) {
matches = xmalloc((size_t)st->match_count * sizeof(char *));
@@ -2263,9 +2231,9 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
matches = NULL;
}
st->match_count = 0;
- for (mtt = 0; mtt < MT_COUNT; mtt++) {
- for (i = 0; i < st->ga_match[mtt].ga_len; i++) {
- mfp = ((char **)(st->ga_match[mtt].ga_data))[i];
+ for (int mtt = 0; mtt < MT_COUNT; mtt++) {
+ for (int i = 0; i < st->ga_match[mtt].ga_len; i++) {
+ char *mfp = ((char **)(st->ga_match[mtt].ga_data))[i];
if (matches == NULL) {
xfree(mfp);
} else {
@@ -2274,7 +2242,7 @@ static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
*mfp = (char)(*mfp - 1);
// change the TAG_SEP back to NUL
- for (p = mfp + 1; *p != NUL; p++) {
+ for (char *p = mfp + 1; *p != NUL; p++) {
if (*p == TAG_SEP) {
*p = NUL;
}
@@ -2330,11 +2298,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
tagname_T tn; // info for get_tagfname()
int first_file; // trying first tag file
int retval = FAIL; // return value
- int round;
-
- int save_emsg_off;
- int help_save;
int i;
char *saved_pat = NULL; // copy of pat[]
@@ -2344,11 +2308,12 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
int verbose = (flags & TAG_VERBOSE);
int save_p_ic = p_ic;
+ // uncrustify:off
+
// Change the value of 'ignorecase' according to 'tagcase' for the
// duration of this function.
switch (curbuf->b_tc_flags ? curbuf->b_tc_flags : tc_flags) {
- case TC_FOLLOWIC:
- break;
+ case TC_FOLLOWIC: break;
case TC_IGNORE:
p_ic = true;
break;
@@ -2365,7 +2330,9 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
abort();
}
- help_save = curbuf->b_help;
+ // uncrustify:on
+
+ int help_save = curbuf->b_help;
findtags_state_init(&st, pat, flags, mincount);
@@ -2390,7 +2357,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
st.orgpat->len = (int)p_tl;
}
- save_emsg_off = emsg_off;
+ int save_emsg_off = emsg_off;
emsg_off = true; // don't want error for invalid RE here
prepare_pats(st.orgpat, has_re);
emsg_off = save_emsg_off;
@@ -2425,7 +2392,7 @@ int find_tags(char *pat, int *num_matches, char ***matchesp, int flags, int minc
// Only ignore case when TAG_NOIC not used or 'ignorecase' set.
st.orgpat->regmatch.rm_ic = ((p_ic || !noic)
&& (findall || st.orgpat->headlen == 0 || !p_tbs));
- for (round = 1; round <= 2; round++) {
+ for (int round = 1; round <= 2; round++) {
st.linear = (st.orgpat->headlen == 0 || !p_tbs || round == 2);
// Try tag file names from tags option one by one.
@@ -2482,15 +2449,23 @@ static garray_T tag_fnames = GA_EMPTY_INIT_VALUE;
// Callback function for finding all "tags" and "tags-??" files in
// 'runtimepath' doc directories.
-static void found_tagfile_cb(char *fname, void *cookie)
+static bool found_tagfile_cb(int num_fnames, char **fnames, bool all, void *cookie)
{
- char *const tag_fname = xstrdup(fname);
+ for (int i = 0; i < num_fnames; i++) {
+ char *const tag_fname = xstrdup(fnames[i]);
#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(tag_fname);
+ slash_adjust(tag_fname);
#endif
- simplify_filename(tag_fname);
- GA_APPEND(char *, &tag_fnames, tag_fname);
+ simplify_filename(tag_fname);
+ GA_APPEND(char *, &tag_fnames, tag_fname);
+
+ if (!all) {
+ break;
+ }
+ }
+
+ return num_fnames > 0;
}
#if defined(EXITFREE)
@@ -2516,7 +2491,6 @@ void free_tag_stuff(void)
int get_tagfname(tagname_T *tnp, int first, char *buf)
{
char *fname = NULL;
- char *r_ptr;
if (first) {
CLEAR_POINTER(tnp);
@@ -2541,7 +2515,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
}
tnp->tn_hf_idx++;
STRCPY(buf, p_hf);
- STRCPY(path_tail((char *)buf), "tags");
+ STRCPY(path_tail(buf), "tags");
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(buf);
#endif
@@ -2569,7 +2543,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
// There are two states:
// tnp->tn_did_filefind_init == false: setup for next part in 'tags'.
// tnp->tn_did_filefind_init == true: find next file in this part.
- for (;;) {
+ while (true) {
if (tnp->tn_did_filefind_init) {
fname = vim_findfile(tnp->tn_search_ctx);
if (fname != NULL) {
@@ -2591,7 +2565,7 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
buf[0] = NUL;
(void)copy_option_part(&tnp->tn_np, buf, MAXPATHL - 1, " ,");
- r_ptr = vim_findfile_stopdir(buf);
+ char *r_ptr = vim_findfile_stopdir(buf);
// move the filename one char forward and truncate the
// filepath with a NUL
filename = path_tail(buf);
@@ -2633,11 +2607,9 @@ void tagname_free(tagname_T *tnp)
/// @return FAIL if there is a format error in this line, OK otherwise.
static int parse_tag_line(char *lbuf, tagptrs_T *tagp)
{
- char *p;
-
// Isolate the tagname, from lbuf up to the first white
tagp->tagname = lbuf;
- p = vim_strchr(lbuf, TAB);
+ char *p = vim_strchr(lbuf, TAB);
if (p == NULL) {
return FAIL;
}
@@ -2680,10 +2652,8 @@ static int parse_tag_line(char *lbuf, tagptrs_T *tagp)
// Return false if it is not a static tag.
static bool test_for_static(tagptrs_T *tagp)
{
- char *p;
-
// Check for new style static tag ":...<Tab>file:[<Tab>...]"
- p = tagp->command;
+ char *p = tagp->command;
while ((p = vim_strchr(p, '\t')) != NULL) {
p++;
if (strncmp(p, "file:", 5) == 0) {
@@ -2717,15 +2687,11 @@ static size_t matching_line_len(const char *const lbuf)
/// @return OK or FAIL.
static int parse_match(char *lbuf, tagptrs_T *tagp)
{
- int retval;
- char *p;
- char *pc, *pt;
-
tagp->tag_fname = lbuf + 1;
lbuf += strlen(tagp->tag_fname) + 2;
// Find search pattern and the file name for non-etags.
- retval = parse_tag_line(lbuf, tagp);
+ int retval = parse_tag_line(lbuf, tagp);
tagp->tagkind = NULL;
tagp->user_data = NULL;
@@ -2737,7 +2703,7 @@ static int parse_match(char *lbuf, tagptrs_T *tagp)
}
// Try to find a kind field: "kind:<kind>" or just "<kind>"
- p = tagp->command;
+ char *p = tagp->command;
if (find_extra(&p) == OK) {
tagp->command_end = p;
if (p > tagp->command && p[-1] == '|') {
@@ -2759,8 +2725,8 @@ static int parse_match(char *lbuf, tagptrs_T *tagp)
break;
}
- pc = vim_strchr(p, ':');
- pt = vim_strchr(p, '\t');
+ char *pc = vim_strchr(p, ':');
+ char *pt = vim_strchr(p, '\t');
if (pc == NULL || (pt != NULL && pc > pt)) {
tagp->tagkind = p;
}
@@ -2809,14 +2775,8 @@ static char *tag_full_fname(tagptrs_T *tagp)
/// @return OK for success, NOTAGFILE when file not found, FAIL otherwise.
static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
{
- bool save_p_ws;
- int save_p_scs, save_p_ic;
- linenr_T save_lnum;
- char *str;
- char *pbuf; // search pattern buffer
char *pbuf_end;
char *tofree_fname = NULL;
- char *fname;
tagptrs_T tagp;
int retval = FAIL;
int getfile_result = GETFILE_UNUSED;
@@ -2829,7 +2789,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
char *lbuf = xmalloc(len);
memmove(lbuf, lbuf_arg, len);
- pbuf = xmalloc(LSIZE);
+ char *pbuf = xmalloc(LSIZE); // search pattern buffer
// parse the match line into the tagp structure
if (parse_match(lbuf, &tagp) == FAIL) {
@@ -2839,10 +2799,10 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// truncate the file name, so it can be used as a string
*tagp.fname_end = NUL;
- fname = tagp.fname;
+ char *fname = tagp.fname;
// copy the command to pbuf[], remove trailing CR/NL
- str = tagp.command;
+ char *str = tagp.command;
for (pbuf_end = pbuf; *str && *str != '\n' && *str != '\r';) {
*pbuf_end++ = *str++;
if (pbuf_end - pbuf + 1 >= LSIZE) {
@@ -2901,21 +2861,10 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
buf_T *const existing_buf = buflist_findname_exp(fname);
if (existing_buf != NULL) {
- const win_T *wp = NULL;
-
- if (swb_flags & SWB_USEOPEN) {
- wp = buf_jump_open_win(existing_buf);
- }
-
- // If 'switchbuf' contains "usetab": jump to first window in any tab
- // page containing "existing_buf" if one exists
- if (wp == NULL && (swb_flags & SWB_USETAB)) {
- wp = buf_jump_open_tab(existing_buf);
- }
-
- // We've switched to the buffer, the usual loading of the file must
- // be skipped.
- if (wp != NULL) {
+ // If 'switchbuf' is set jump to the window containing "buf".
+ if (swbuf_goto_win_with_buf(existing_buf) != NULL) {
+ // We've switched to the buffer, the usual loading of the file
+ // must be skipped.
getfile_result = GETFILE_SAME_FILE;
}
}
@@ -2943,7 +2892,7 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
if (getfile_result == GETFILE_UNUSED) {
// Careful: getfile() may trigger autocommands and call jumpto_tag()
// recursively.
- getfile_result = getfile(0, fname, NULL, true, (linenr_T)0, forceit);
+ getfile_result = getfile(0, fname, NULL, true, 0, forceit);
}
keep_help_flag = false;
@@ -2976,13 +2925,13 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
str = skip_regexp(pbuf + 1, pbuf[0], false) + 1;
}
if (str > pbuf_end - 1) { // search command with nothing following
- save_p_ws = p_ws;
- save_p_ic = p_ic;
- save_p_scs = p_scs;
+ bool save_p_ws = p_ws;
+ int save_p_ic = p_ic;
+ int save_p_scs = p_scs;
p_ws = true; // need 'wrapscan' for backward searches
p_ic = false; // don't ignore case now
p_scs = false;
- save_lnum = curwin->w_cursor.lnum;
+ linenr_T save_lnum = curwin->w_cursor.lnum;
if (tagp.tagline > 0) {
// start search before line from "line:" field
curwin->w_cursor.lnum = tagp.tagline - 1;
@@ -2990,28 +2939,26 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// start search before first line
curwin->w_cursor.lnum = 0;
}
- if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
- search_options, NULL)) {
+ if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, 1, search_options, NULL)) {
retval = OK;
} else {
int found = 1;
- char cc;
// try again, ignore case now
p_ic = true;
- if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, (long)1,
+ if (!do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, 1,
search_options, NULL)) {
// Failed to find pattern, take a guess: "^func ("
found = 2;
(void)test_for_static(&tagp);
- cc = *tagp.tagname_end;
+ char cc = *tagp.tagname_end;
*tagp.tagname_end = NUL;
snprintf(pbuf, LSIZE, "^%s\\s\\*(", tagp.tagname);
- if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, 1, search_options, NULL)) {
// Guess again: "^char * \<func ("
snprintf(pbuf, LSIZE, "^\\[#a-zA-Z_]\\.\\*\\<%s\\s\\*(",
tagp.tagname);
- if (!do_search(NULL, '/', '/', pbuf, (long)1, search_options, NULL)) {
+ if (!do_search(NULL, '/', '/', pbuf, 1, search_options, NULL)) {
found = 0;
}
}
@@ -3024,17 +2971,17 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, int keep_help)
// Only give a message when really guessed, not when 'ic'
// is set and match found while ignoring case.
if (found == 2 || !save_p_ic) {
- msg(_("E435: Couldn't find tag, just guessing!"));
+ msg(_("E435: Couldn't find tag, just guessing!"), 0);
if (!msg_scrolled && msg_silent == 0) {
ui_flush();
- os_delay(1010L, true);
+ os_delay(1010, true);
}
}
retval = OK;
}
}
p_ws = save_p_ws;
- p_ic = save_p_ic; // -V519
+ p_ic = save_p_ic;
p_scs = save_p_scs;
// A search command may have positioned the cursor beyond the end
@@ -3155,10 +3102,10 @@ static char *expand_tag_fname(char *fname, char *const tag_fname, const bool exp
/// file.
static int test_for_current(char *fname, char *fname_end, char *tag_fname, char *buf_ffname)
{
- char c;
int retval = false;
if (buf_ffname != NULL) { // if the buffer has a name
+ char c;
{
c = *fname_end;
*fname_end = NUL;
@@ -3180,7 +3127,7 @@ static int find_extra(char **pp)
char first_char = **pp;
// Repeat for addresses separated with ';'
- for (;;) {
+ while (true) {
if (ascii_isdigit(*str)) {
str = skipdigits(str + 1);
} else if (*str == '/' || *str == '?') {
@@ -3225,14 +3172,12 @@ static void tagstack_clear_entry(taggy_T *item)
/// @param tagnames expand tag names
int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
{
- int i;
int extra_flag;
- char *name_buf;
size_t name_buf_size = 100;
tagptrs_T t_p;
int ret;
- name_buf = xmalloc(name_buf_size);
+ char *name_buf = xmalloc(name_buf_size);
if (tagnames) {
extra_flag = TAG_NAMES;
@@ -3251,7 +3196,7 @@ int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
if (ret == OK && !tagnames) {
// Reorganize the tags for display and matching as strings of:
// "<tagname>\0<kind>\0<filename>\0"
- for (i = 0; i < *num_file; i++) {
+ for (int i = 0; i < *num_file; i++) {
size_t len;
parse_match((*file)[i], &t_p);
@@ -3267,7 +3212,7 @@ int expand_tags(int tagnames, char *pat, int *num_file, char ***file)
memmove(name_buf, t_p.tagname, len);
name_buf[len++] = 0;
name_buf[len++] = (t_p.tagkind != NULL && *t_p.tagkind)
- ? *t_p.tagkind : 'f';
+ ? *t_p.tagkind : 'f';
name_buf[len++] = 0;
memmove((*file)[i] + len, t_p.fname, (size_t)(t_p.fname_end - t_p.fname));
(*file)[i][len + (size_t)(t_p.fname_end - t_p.fname)] = 0;
@@ -3287,13 +3232,12 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
FUNC_ATTR_NONNULL_ARG(1, 2)
{
int len = 0;
- int retval;
// Check that the field name doesn't exist yet.
if (tv_dict_find(dict, field_name, -1) != NULL) {
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Duplicate field name: %s"), field_name);
+ smsg(0, _("Duplicate field name: %s"), field_name);
verbose_leave();
}
return FAIL;
@@ -3313,7 +3257,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
xstrlcpy(buf, start, (size_t)len + 1);
}
buf[len] = NUL;
- retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf);
+ int retval = tv_dict_add_str(dict, field_name, strlen(field_name), buf);
xfree(buf);
return retval;
}
@@ -3322,27 +3266,22 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
/// as a dictionary. Use "buf_fname" for priority, unless NULL.
int get_tags(list_T *list, char *pat, char *buf_fname)
{
- int num_matches, i, ret;
+ int num_matches;
char **matches;
- char *full_fname;
- dict_T *dict;
tagptrs_T tp;
- bool is_static;
- ret = find_tags(pat, &num_matches, &matches,
- TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname);
+ int ret = find_tags(pat, &num_matches, &matches, TAG_REGEXP | TAG_NOIC, MAXCOL, buf_fname);
if (ret != OK || num_matches <= 0) {
return ret;
}
- for (i = 0; i < num_matches; i++) {
- int parse_result = parse_match(matches[i], &tp);
-
- // Avoid an unused variable warning in release builds.
- (void)parse_result;
- assert(parse_result == OK);
+ for (int i = 0; i < num_matches; i++) {
+ if (parse_match(matches[i], &tp) == FAIL) {
+ xfree(matches[i]);
+ continue;
+ }
- is_static = test_for_static(&tp);
+ bool is_static = test_for_static(&tp);
// Skip pseudo-tag lines.
if (strncmp(tp.tagname, "!_TAG_", 6) == 0) {
@@ -3350,10 +3289,10 @@ int get_tags(list_T *list, char *pat, char *buf_fname)
continue;
}
- dict = tv_dict_alloc();
+ dict_T *dict = tv_dict_alloc();
tv_list_append_dict(list, dict);
- full_fname = tag_full_fname(&tp);
+ char *full_fname = tag_full_fname(&tp);
if (add_tag_field(dict, "name", tp.tagname, tp.tagname_end) == FAIL
|| add_tag_field(dict, "filename", full_fname, NULL) == FAIL
|| add_tag_field(dict, "cmd", tp.command, tp.command_end) == FAIL
@@ -3377,7 +3316,7 @@ int get_tags(list_T *list, char *pat, char *buf_fname)
// skip "file:" (static tag)
p += 4;
} else if (!ascii_iswhite(*p)) {
- char *s, *n;
+ char *n;
int len;
// Add extra field as a dict entry. Fields are
@@ -3388,7 +3327,7 @@ int get_tags(list_T *list, char *pat, char *buf_fname)
}
len = (int)(p - n);
if (*p == ':' && len > 0) {
- s = ++p;
+ char *s = ++p;
while (*p != NUL && (uint8_t)(*p) >= ' ') {
p++;
}
@@ -3419,20 +3358,17 @@ int get_tags(list_T *list, char *pat, char *buf_fname)
// Return information about 'tag' in dict 'retdict'.
static void get_tag_details(taggy_T *tag, dict_T *retdict)
{
- list_T *pos;
- fmark_T *fmark;
-
- tv_dict_add_str(retdict, S_LEN("tagname"), (const char *)tag->tagname);
+ tv_dict_add_str(retdict, S_LEN("tagname"), tag->tagname);
tv_dict_add_nr(retdict, S_LEN("matchnr"), tag->cur_match + 1);
tv_dict_add_nr(retdict, S_LEN("bufnr"), tag->cur_fnum);
if (tag->user_data) {
- tv_dict_add_str(retdict, S_LEN("user_data"), (const char *)tag->user_data);
+ tv_dict_add_str(retdict, S_LEN("user_data"), tag->user_data);
}
- pos = tv_list_alloc(4);
+ list_T *pos = tv_list_alloc(4);
tv_dict_add_list(retdict, S_LEN("from"), pos);
- fmark = &tag->fmark;
+ fmark_T *fmark = &tag->fmark;
tv_list_append_number(pos,
(varnumber_T)(fmark->fnum != -1 ? fmark->fnum : 0));
tv_list_append_number(pos, (varnumber_T)fmark->mark.lnum);
@@ -3445,17 +3381,13 @@ static void get_tag_details(taggy_T *tag, dict_T *retdict)
// 'retdict'.
void get_tagstack(win_T *wp, dict_T *retdict)
{
- list_T *l;
- int i;
- dict_T *d;
-
tv_dict_add_nr(retdict, S_LEN("length"), wp->w_tagstacklen);
tv_dict_add_nr(retdict, S_LEN("curidx"), wp->w_tagstackidx + 1);
- l = tv_list_alloc(2);
+ list_T *l = tv_list_alloc(2);
tv_dict_add_list(retdict, S_LEN("items"), l);
- for (i = 0; i < wp->w_tagstacklen; i++) {
- d = tv_dict_alloc();
+ for (int i = 0; i < wp->w_tagstacklen; i++) {
+ dict_T *d = tv_dict_alloc();
tv_list_append_dict(l, d);
get_tag_details(&wp->w_tagstack[i], d);
}
@@ -3512,20 +3444,18 @@ static void tagstack_push_item(win_T *wp, char *tagname, int cur_fnum, int cur_m
/// Add a list of items to the tag stack in the specified window
static void tagstack_push_items(win_T *wp, list_T *l)
{
- listitem_T *li;
dictitem_T *di;
- dict_T *itemdict;
char *tagname;
pos_T mark;
int fnum;
// Add one entry at a time to the tag stack
- for (li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
+ for (listitem_T *li = tv_list_first(l); li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT
|| TV_LIST_ITEM_TV(li)->vval.v_dict == NULL) {
continue; // Skip non-dict items
}
- itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
+ dict_T *itemdict = TV_LIST_ITEM_TV(li)->vval.v_dict;
// parse 'from' for the cursor position before the tag jump
if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) {
@@ -3576,7 +3506,7 @@ int set_tagstack(win_T *wp, const dict_T *d, int action)
// not allowed to alter the tag stack entries from inside tagfunc
if (tfu_in_use) {
- emsg(_(recurmsg));
+ emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return FAIL;
}
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index e08145f727..87e71c8bef 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -1,39 +1,44 @@
-#ifndef NVIM_TAG_H
-#define NVIM_TAG_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/types.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/option_defs.h" // IWYU pragma: keep
-// Values for do_tag().
-#define DT_TAG 1 // jump to newer position or same tag again
-#define DT_POP 2 // jump to older position
-#define DT_NEXT 3 // jump to next match of same tag
-#define DT_PREV 4 // jump to previous match of same tag
-#define DT_FIRST 5 // jump to first match of same tag
-#define DT_LAST 6 // jump to first match of same tag
-#define DT_SELECT 7 // jump to selection from list
-#define DT_HELP 8 // like DT_TAG, but no wildcards
-#define DT_JUMP 9 // jump to new tag or selection from list
-#define DT_LTAG 11 // tag using location list
-#define DT_FREE 99 // free cached matches
+enum { LSIZE = 512, }; ///< max. size of a line in the tags file
-// flags for find_tags().
-#define TAG_HELP 1 // only search for help tags
-#define TAG_NAMES 2 // only return name of tag
-#define TAG_REGEXP 4 // use tag pattern as regexp
-#define TAG_NOIC 8 // don't always ignore case
-#define TAG_VERBOSE 32 // message verbosity
-#define TAG_INS_COMP 64 // Currently doing insert completion
-#define TAG_KEEP_LANG 128 // keep current language
-#define TAG_NO_TAGFUNC 256 // do not use 'tagfunc'
+/// Values for do_tag().
+enum {
+ DT_TAG = 1, ///< jump to newer position or same tag again
+ DT_POP = 2, ///< jump to older position
+ DT_NEXT = 3, ///< jump to next match of same tag
+ DT_PREV = 4, ///< jump to previous match of same tag
+ DT_FIRST = 5, ///< jump to first match of same tag
+ DT_LAST = 6, ///< jump to first match of same tag
+ DT_SELECT = 7, ///< jump to selection from list
+ DT_HELP = 8, ///< like DT_TAG, but no wildcards
+ DT_JUMP = 9, ///< jump to new tag or selection from list
+ DT_LTAG = 11, ///< tag using location list
+ DT_FREE = 99, ///< free cached matches
+};
-#define TAG_MANY 300 // When finding many tags (for completion),
- // find up to this many tags
+/// flags for find_tags().
+enum {
+ TAG_HELP = 1, ///< only search for help tags
+ TAG_NAMES = 2, ///< only return name of tag
+ TAG_REGEXP = 4, ///< use tag pattern as regexp
+ TAG_NOIC = 8, ///< don't always ignore case
+ TAG_VERBOSE = 32, ///< message verbosity
+ TAG_INS_COMP = 64, ///< Currently doing insert completion
+ TAG_KEEP_LANG = 128, ///< keep current language
+ TAG_NO_TAGFUNC = 256, ///< do not use 'tagfunc'
+ TAG_MANY = 300, ///< When finding many tags (for completion), find up to this many tags
+};
-// Structure used for get_tagfname().
+/// Structure used for get_tagfname().
typedef struct {
- char *tn_tags; // value of 'tags' when starting
- char *tn_np; // current position in tn_tags
+ char *tn_tags; ///< value of 'tags' when starting
+ char *tn_np; ///< current position in tn_tags
int tn_did_filefind_init;
int tn_hf_idx;
void *tn_search_ctx;
@@ -42,4 +47,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tag.h.generated.h"
#endif
-#endif // NVIM_TAG_H
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index a52a34cd66..1527738165 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// VT220/xterm-like terminal emulator.
// Powered by libvterm http://www.leonerd.org.uk/code/libvterm
//
@@ -48,7 +45,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -59,19 +56,18 @@
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
-#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
#include "nvim/event/time.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -79,15 +75,16 @@
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/normal.h"
+#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/state.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
typedef struct terminal_state {
VimState state;
@@ -161,16 +158,16 @@ struct terminal {
};
static VTermScreenCallbacks vterm_screen_callbacks = {
- .damage = term_damage,
- .moverect = term_moverect,
- .movecursor = term_movecursor,
+ .damage = term_damage,
+ .moverect = term_moverect,
+ .movecursor = term_movecursor,
.settermprop = term_settermprop,
- .bell = term_bell,
+ .bell = term_bell,
.sb_pushline = term_sb_push, // Called before a line goes offscreen.
- .sb_popline = term_sb_pop,
+ .sb_popline = term_sb_pop,
};
-static PMap(ptr_t) invalidated_terminals = MAP_INIT;
+static Set(ptr_t) invalidated_terminals = SET_INIT;
void terminal_init(void)
{
@@ -184,10 +181,10 @@ void terminal_teardown(void)
time_watcher_stop(&refresh_timer);
multiqueue_free(refresh_timer.events);
time_watcher_close(&refresh_timer, NULL);
- pmap_destroy(ptr_t)(&invalidated_terminals);
+ set_destroy(ptr_t, &invalidated_terminals);
// terminal_destroy might be called after terminal_teardown is invoked
// make sure it is in an empty, valid state
- pmap_init(ptr_t, &invalidated_terminals);
+ invalidated_terminals = (Set(ptr_t)) SET_INIT;
}
static void term_output_callback(const char *s, size_t len, void *user_data)
@@ -204,10 +201,11 @@ static void term_output_callback(const char *s, size_t len, void *user_data)
///
/// @param buf Buffer used for presentation of the terminal.
/// @param opts PTY process channel, various terminal properties and callbacks.
-Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
+void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
+ FUNC_ATTR_NONNULL_ALL
{
// Create a new terminal instance and configure it
- Terminal *rv = xcalloc(1, sizeof(Terminal));
+ Terminal *rv = *termpp = xcalloc(1, sizeof(Terminal));
rv->opts = opts;
rv->cursor.visible = true;
// Associate the terminal instance with the new buffer
@@ -221,6 +219,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
// Set up screen
rv->vts = vterm_obtain_screen(rv->vt);
vterm_screen_enable_altscreen(rv->vts, true);
+ vterm_screen_enable_reflow(rv->vts, true);
// delete empty lines at the end of the buffer
vterm_screen_set_callbacks(rv->vts, &vterm_screen_callbacks, rv);
vterm_screen_set_damage_merge(rv->vts, VTERM_DAMAGE_SCROLL);
@@ -235,7 +234,7 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
aucmd_prepbuf(&aco, buf);
refresh_screen(rv, buf);
- set_option_value("buftype", 0, "terminal", OPT_LOCAL); // -V666
+ set_option_value("buftype", STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL);
// Default settings for terminal buffers
buf->b_p_ma = false; // 'nomodifiable'
@@ -243,26 +242,34 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
buf->b_p_scbk = // 'scrollback' (initialize local from global)
(p_scbk < 0) ? 10000 : MAX(1, p_scbk);
buf->b_p_tw = 0; // 'textwidth'
- set_option_value("wrap", false, NULL, OPT_LOCAL);
- set_option_value("list", false, NULL, OPT_LOCAL);
+ set_option_value("wrap", BOOLEAN_OPTVAL(false), OPT_LOCAL);
+ set_option_value("list", BOOLEAN_OPTVAL(false), OPT_LOCAL);
if (buf->b_ffname != NULL) {
buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname));
}
RESET_BINDING(curwin);
// Reset cursor in current window.
curwin->w_cursor = (pos_T){ .lnum = 1, .col = 0, .coladd = 0 };
- // Initialize to check if the scrollback buffer has been allocated inside a TermOpen autocmd
+ // Initialize to check if the scrollback buffer has been allocated in a TermOpen autocmd.
rv->sb_buffer = NULL;
// Apply TermOpen autocmds _before_ configuring the scrollback buffer.
apply_autocmds(EVENT_TERMOPEN, NULL, NULL, false, buf);
- // Local 'scrollback' _after_ autocmds.
- buf->b_p_scbk = (buf->b_p_scbk < 1) ? SB_MAX : buf->b_p_scbk;
aucmd_restbuf(&aco);
- // Configure the scrollback buffer.
- rv->sb_size = (size_t)buf->b_p_scbk;
- rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
+ if (*termpp == NULL) {
+ return; // Terminal has already been destroyed.
+ }
+
+ if (rv->sb_buffer == NULL) {
+ // Local 'scrollback' _after_ autocmds.
+ if (buf->b_p_scbk < 1) {
+ buf->b_p_scbk = SB_MAX;
+ }
+ // Configure the scrollback buffer.
+ rv->sb_size = (size_t)buf->b_p_scbk;
+ rv->sb_buffer = xmalloc(sizeof(ScrollbackLine *) * rv->sb_size);
+ }
// Configure the color palette. Try to get the color from:
//
@@ -290,14 +297,13 @@ Terminal *terminal_open(buf_T *buf, TerminalOptions opts)
}
}
}
-
- return rv;
}
/// Closes the Terminal buffer.
///
/// May call terminal_destroy, which sets caller storage to NULL.
void terminal_close(Terminal **termpp, int status)
+ FUNC_ATTR_NONNULL_ALL
{
Terminal *term = *termpp;
if (term->destroy) {
@@ -437,8 +443,8 @@ bool terminal_enter(void)
char *save_w_p_culopt = NULL;
uint8_t save_w_p_culopt_flags = curwin->w_p_culopt_flags;
int save_w_p_cuc = curwin->w_p_cuc;
- long save_w_p_so = curwin->w_p_so;
- long save_w_p_siso = curwin->w_p_siso;
+ OptInt save_w_p_so = curwin->w_p_so;
+ OptInt save_w_p_siso = curwin->w_p_siso;
if (curwin->w_p_cul && curwin->w_p_culopt_flags & CULOPT_NBR) {
if (strcmp(curwin->w_p_culopt, "number") != 0) {
save_w_p_culopt = curwin->w_p_culopt;
@@ -532,6 +538,7 @@ static int terminal_check(VimState *state)
}
terminal_check_cursor();
+ validate_cursor();
if (must_redraw) {
update_screen();
@@ -576,6 +583,8 @@ static int terminal_execute(VimState *state, int key)
case K_RIGHTRELEASE:
case K_MOUSEDOWN:
case K_MOUSEUP:
+ case K_MOUSELEFT:
+ case K_MOUSERIGHT:
if (send_mouse_event(s->term, key)) {
return 0;
}
@@ -597,7 +606,7 @@ static int terminal_execute(VimState *state, int key)
break;
case K_LUA:
- map_execute_lua();
+ map_execute_lua(false);
break;
case Ctrl_N:
@@ -644,6 +653,7 @@ static int terminal_execute(VimState *state, int key)
/// Frees the given Terminal structure and sets the caller storage to NULL (in the spirit of
/// XFREE_CLEAR).
void terminal_destroy(Terminal **termpp)
+ FUNC_ATTR_NONNULL_ALL
{
Terminal *term = *termpp;
buf_T *buf = handle_get_buffer(term->buf_handle);
@@ -653,12 +663,12 @@ void terminal_destroy(Terminal **termpp)
}
if (!term->refcount) {
- if (pmap_has(ptr_t)(&invalidated_terminals, term)) {
+ if (set_has(ptr_t, &invalidated_terminals, term)) {
// flush any pending changes to the buffer
block_autocmds();
refresh_terminal(term);
unblock_autocmds();
- pmap_del(ptr_t)(&invalidated_terminals, term);
+ set_del(ptr_t, &invalidated_terminals, term);
}
for (size_t i = 0; i < term->sb_current; i++) {
xfree(term->sb_buffer[i]);
@@ -681,7 +691,7 @@ void terminal_send(Terminal *term, char *data, size_t size)
static bool is_filter_char(int c)
{
- unsigned int flag = 0;
+ unsigned flag = 0;
switch (c) {
case 0x08:
flag = TPF_BS;
@@ -711,7 +721,7 @@ static bool is_filter_char(int c)
return !!(tpf_flags & flag);
}
-void terminal_paste(long count, char **y_array, size_t y_size)
+void terminal_paste(int count, char **y_array, size_t y_size)
{
if (y_size == 0) {
return;
@@ -719,12 +729,16 @@ void terminal_paste(long count, char **y_array, size_t y_size)
vterm_keyboard_start_paste(curbuf->terminal->vt);
size_t buff_len = strlen(y_array[0]);
char *buff = xmalloc(buff_len);
- for (int i = 0; i < count; i++) { // -V756
+ for (int i = 0; i < count; i++) {
// feed the lines to the terminal
for (size_t j = 0; j < y_size; j++) {
if (j) {
// terminate the previous line
+#ifdef MSWIN
+ terminal_send(curbuf->terminal, "\r\n", 2);
+#else
terminal_send(curbuf->terminal, "\n", 1);
+#endif
}
size_t len = strlen(y_array[j]);
if (len > buff_len) {
@@ -762,7 +776,7 @@ void terminal_send_key(Terminal *term, int c)
if (key) {
vterm_keyboard_key(term->vt, key, mod);
- } else {
+ } else if (!IS_SPECIAL(c)) {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);
}
}
@@ -836,7 +850,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
| (cell.attrs.italic ? HL_ITALIC : 0)
| (cell.attrs.reverse ? HL_INVERSE : 0)
| get_underline_hl_flag(cell.attrs)
- | (cell.attrs.strike ? HL_STRIKETHROUGH: 0)
+ | (cell.attrs.strike ? HL_STRIKETHROUGH : 0)
| ((fg_indexed && !fg_set) ? HL_FG_INDEXED : 0)
| ((bg_indexed && !bg_set) ? HL_BG_INDEXED : 0);
@@ -893,13 +907,13 @@ static int term_moverect(VTermRect dest, VTermRect src, void *data)
return 1;
}
-static int term_movecursor(VTermPos new, VTermPos old, int visible, void *data)
+static int term_movecursor(VTermPos new_pos, VTermPos old_pos, int visible, void *data)
{
Terminal *term = data;
- term->cursor.row = new.row;
- term->cursor.col = new.col;
- invalidate_terminal(term, old.row, old.row + 1);
- invalidate_terminal(term, new.row, new.row + 1);
+ term->cursor.row = new_pos.row;
+ term->cursor.col = new_pos.col;
+ invalidate_terminal(term, old_pos.row, old_pos.row + 1);
+ invalidate_terminal(term, new_pos.row, new_pos.row + 1);
return 1;
}
@@ -1024,7 +1038,7 @@ static int term_sb_push(int cols, const VTermScreenCell *cells, void *data)
}
memcpy(sbrow->cells, cells, sizeof(cells[0]) * c);
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
return 1;
}
@@ -1065,7 +1079,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
}
xfree(sbrow);
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
return 1;
}
@@ -1091,6 +1105,8 @@ static void convert_modifiers(int key, VTermModifier *statep)
case K_S_DOWN:
case K_S_LEFT:
case K_S_RIGHT:
+ case K_S_HOME:
+ case K_S_END:
case K_S_F1:
case K_S_F2:
case K_S_F3:
@@ -1108,6 +1124,8 @@ static void convert_modifiers(int key, VTermModifier *statep)
case K_C_LEFT:
case K_C_RIGHT:
+ case K_C_HOME:
+ case K_C_END:
*statep |= VTERM_MOD_CTRL;
break;
}
@@ -1154,8 +1172,16 @@ static VTermKey convert_key(int key, VTermModifier *statep)
return VTERM_KEY_INS;
case K_DEL:
return VTERM_KEY_DEL;
+ case K_S_HOME:
+ FALLTHROUGH;
+ case K_C_HOME:
+ FALLTHROUGH;
case K_HOME:
return VTERM_KEY_HOME;
+ case K_S_END:
+ FALLTHROUGH;
+ case K_C_END:
+ FALLTHROUGH;
case K_END:
return VTERM_KEY_END;
case K_PAGEUP:
@@ -1389,13 +1415,14 @@ static void mouse_action(Terminal *term, int button, int row, int col, bool pres
static bool send_mouse_event(Terminal *term, int c)
{
int row = mouse_row, col = mouse_col, grid = mouse_grid;
- int offset;
win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win == NULL || (offset = win_col_off(mouse_win)) > col) {
+ if (mouse_win == NULL) {
goto end;
}
- if (term->forward_mouse && mouse_win->w_buffer->terminal == term) {
+ int offset;
+ if (term->forward_mouse && mouse_win->w_buffer->terminal == term
+ && col >= (offset = win_col_off(mouse_win))) {
// event in the terminal window and mouse events was enabled by the
// program. translate and forward the event
int button;
@@ -1423,26 +1450,52 @@ static bool send_mouse_event(Terminal *term, int c)
pressed = true; button = 4; break;
case K_MOUSEUP:
pressed = true; button = 5; break;
+ case K_MOUSELEFT:
+ pressed = true; button = 7; break;
+ case K_MOUSERIGHT:
+ pressed = true; button = 6; break;
default:
return false;
}
- mouse_action(term, button, row, col - offset, pressed, 0);
+ VTermModifier mod = VTERM_MOD_NONE;
+ convert_modifiers(c, &mod);
+ mouse_action(term, button, row, col - offset, pressed, mod);
return false;
}
- if (c == K_MOUSEDOWN || c == K_MOUSEUP) {
+ if (c == K_MOUSEUP || c == K_MOUSEDOWN || c == K_MOUSELEFT || c == K_MOUSERIGHT) {
win_T *save_curwin = curwin;
// switch window/buffer to perform the scroll
curwin = mouse_win;
curbuf = curwin->w_buffer;
- int direction = c == K_MOUSEDOWN ? MSCR_DOWN : MSCR_UP;
- if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
- scroll_redraw(direction, curwin->w_botline - curwin->w_topline);
- } else if (p_mousescroll_vert > 0) {
- scroll_redraw(direction, p_mousescroll_vert);
+
+ cmdarg_T cap;
+ oparg_T oa;
+ CLEAR_FIELD(cap);
+ clear_oparg(&oa);
+ cap.oap = &oa;
+
+ switch (cap.cmdchar = c) {
+ case K_MOUSEUP:
+ cap.arg = MSCR_UP;
+ break;
+ case K_MOUSEDOWN:
+ cap.arg = MSCR_DOWN;
+ break;
+ case K_MOUSELEFT:
+ cap.arg = MSCR_LEFT;
+ break;
+ case K_MOUSERIGHT:
+ cap.arg = MSCR_RIGHT;
+ break;
+ default:
+ abort();
}
+ // Call the common mouse scroll function shared with other modes.
+ do_mousescroll(&cap);
+
curwin->w_redr_status = true;
curwin = save_curwin;
curbuf = curwin->w_buffer;
@@ -1452,13 +1505,14 @@ static bool send_mouse_event(Terminal *term, int c)
return mouse_win == curwin;
}
- // ignore left release action if it was not processed above
- // to prevent leaving Terminal mode after entering to it using a mouse
- if (c == K_LEFTRELEASE && mouse_win->w_buffer->terminal == term) {
+end:
+ // Ignore left release action if it was not forwarded to prevent
+ // leaving Terminal mode after entering to it using a mouse.
+ if ((c == K_LEFTRELEASE && mouse_win != NULL && mouse_win->w_buffer->terminal == term)
+ || c == K_MOUSEMOVE) {
return false;
}
-end:
ins_char_typebuf(vgetc_char, vgetc_mod_mask);
return true;
}
@@ -1521,7 +1575,7 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row)
term->invalid_end = MAX(term->invalid_end, end_row);
}
- pmap_put(ptr_t)(&invalidated_terminals, term, NULL);
+ set_put(ptr_t, &invalidated_terminals, term);
if (!refresh_pending) {
time_watcher_start(&refresh_timer, refresh_timer_cb, REFRESH_DELAY, 0);
refresh_pending = true;
@@ -1539,7 +1593,7 @@ static void refresh_terminal(Terminal *term)
}
return;
}
- long ml_before = buf->b_ml.ml_line_count;
+ linenr_T ml_before = buf->b_ml.ml_line_count;
// refresh_ functions assume the terminal buffer is current
aco_save_T aco;
@@ -1549,7 +1603,7 @@ static void refresh_terminal(Terminal *term)
refresh_screen(term, buf);
aucmd_restbuf(&aco);
- long ml_added = buf->b_ml.ml_line_count - ml_before;
+ int ml_added = buf->b_ml.ml_line_count - ml_before;
adjust_topline(term, buf, ml_added);
}
@@ -1564,10 +1618,10 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data)
void *stub; (void)(stub);
// don't process autocommands while updating terminal buffers
block_autocmds();
- map_foreach(&invalidated_terminals, term, stub, {
+ set_foreach(&invalidated_terminals, term, {
refresh_terminal(term);
});
- pmap_clear(ptr_t)(&invalidated_terminals);
+ set_clear(ptr_t, &invalidated_terminals);
unblock_autocmds();
}
@@ -1704,12 +1758,12 @@ static void refresh_screen(Terminal *term, buf_T *buf)
int change_start = row_to_linenr(term, term->invalid_start);
int change_end = change_start + changed;
- changed_lines(change_start, 0, change_end, added, true);
+ changed_lines(buf, change_start, 0, change_end, added, true);
term->invalid_start = INT_MAX;
term->invalid_end = -1;
}
-static void adjust_topline(Terminal *term, buf_T *buf, long added)
+static void adjust_topline(Terminal *term, buf_T *buf, int added)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == buf) {
diff --git a/src/nvim/terminal.h b/src/nvim/terminal.h
index a83929e224..66cad7ee7a 100644
--- a/src/nvim/terminal.h
+++ b/src/nvim/terminal.h
@@ -1,7 +1,5 @@
-#ifndef NVIM_TERMINAL_H
-#define NVIM_TERMINAL_H
+#pragma once
-#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -10,7 +8,7 @@ typedef void (*terminal_write_cb)(char *buffer, size_t size, void *data);
typedef void (*terminal_resize_cb)(uint16_t width, uint16_t height, void *data);
typedef void (*terminal_close_cb)(void *data);
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
typedef struct {
void *data; // PTY process channel
@@ -23,4 +21,3 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "terminal.h.generated.h"
#endif
-#endif // NVIM_TERMINAL_H
diff --git a/src/nvim/testdir/sautest/autoload/sourced.vim b/src/nvim/testdir/sautest/autoload/sourced.vim
deleted file mode 100644
index f69f00cb53..0000000000
--- a/src/nvim/testdir/sautest/autoload/sourced.vim
+++ /dev/null
@@ -1,3 +0,0 @@
-let g:loaded_sourced_vim += 1
-func! sourced#something()
-endfunc
diff --git a/src/nvim/testdir/test_behave.vim b/src/nvim/testdir/test_behave.vim
deleted file mode 100644
index c26bfe7ce3..0000000000
--- a/src/nvim/testdir/test_behave.vim
+++ /dev/null
@@ -1,29 +0,0 @@
-" Test the :behave command
-
-func Test_behave()
- behave mswin
- call assert_equal('mouse,key', &selectmode)
- call assert_equal('popup', &mousemodel)
- call assert_equal('startsel,stopsel', &keymodel)
- call assert_equal('exclusive', &selection)
-
- behave xterm
- call assert_equal('', &selectmode)
- call assert_equal('extend', &mousemodel)
- call assert_equal('', &keymodel)
- call assert_equal('inclusive', &selection)
-
- set selection&
- set mousemodel&
- set keymodel&
- set selection&
-endfunc
-
-func Test_behave_completion()
- call feedkeys(":behave \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"behave mswin xterm', @:)
-endfunc
-
-func Test_behave_error()
- call assert_fails('behave x', 'E475:')
-endfunc
diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim
deleted file mode 100644
index 046acb81e1..0000000000
--- a/src/nvim/testdir/test_blob.vim
+++ /dev/null
@@ -1,367 +0,0 @@
-" Tests for the Blob types
-
-func TearDown()
- " Run garbage collection after every test
- call test_garbagecollect_now()
-endfunc
-
-" Tests for Blob type
-
-" Blob creation from constant
-func Test_blob_create()
- let b = 0zDEADBEEF
- call assert_equal(v:t_blob, type(b))
- call assert_equal(4, len(b))
- call assert_equal(0xDE, b[0])
- call assert_equal(0xAD, b[1])
- call assert_equal(0xBE, b[2])
- call assert_equal(0xEF, b[3])
- call assert_fails('let x = b[4]')
-
- call assert_equal(0xDE, get(b, 0))
- call assert_equal(0xEF, get(b, 3))
-
- call assert_fails('let b = 0z1', 'E973:')
- call assert_fails('let b = 0z1x', 'E973:')
- call assert_fails('let b = 0z12345', 'E973:')
-
- call assert_equal(0z, v:_null_blob)
-
- let b = 0z001122.33445566.778899.aabbcc.dd
- call assert_equal(0z00112233445566778899aabbccdd, b)
- call assert_fails('let b = 0z1.1')
- call assert_fails('let b = 0z.')
- call assert_fails('let b = 0z001122.')
- call assert_fails('call get("", 1)', 'E896:')
- call assert_equal(0, len(v:_null_blob))
-endfunc
-
-" assignment to a blob
-func Test_blob_assign()
- let b = 0zDEADBEEF
- let b2 = b[1:2]
- call assert_equal(0zADBE, b2)
-
- let bcopy = b[:]
- call assert_equal(b, bcopy)
- call assert_false(b is bcopy)
-
- let b = 0zDEADBEEF
- let b2 = b
- call assert_true(b is b2)
- let b[:] = 0z11223344
- call assert_equal(0z11223344, b)
- call assert_equal(0z11223344, b2)
- call assert_true(b is b2)
-
- let b = 0zDEADBEEF
- let b[3:] = 0z66
- call assert_equal(0zDEADBE66, b)
- let b[:1] = 0z8899
- call assert_equal(0z8899BE66, b)
-
- call assert_fails('let b[2:3] = 0z112233', 'E972:')
- call assert_fails('let b[2:3] = 0z11', 'E972:')
- call assert_fails('let b[3:2] = 0z', 'E979:')
-
- let b = 0zDEADBEEF
- let b += 0z99
- call assert_equal(0zDEADBEEF99, b)
-
- call assert_fails('let b .= 0z33', 'E734:')
- call assert_fails('let b .= "xx"', 'E734:')
- call assert_fails('let b += "xx"', 'E734:')
- call assert_fails('let b[1:1] .= 0z55', 'E734:')
-
- let l = [0z12]
- let m = deepcopy(l)
- let m[0] = 0z34 " E742 or E741 should not occur.
-endfunc
-
-func Test_blob_get_range()
- let b = 0z0011223344
- call assert_equal(0z2233, b[2:3])
- call assert_equal(0z223344, b[2:-1])
- call assert_equal(0z00, b[0:-5])
- call assert_equal(0z, b[0:-11])
- call assert_equal(0z44, b[-1:])
- call assert_equal(0z0011223344, b[:])
- call assert_equal(0z0011223344, b[:-1])
- call assert_equal(0z, b[5:6])
- call assert_equal(0z0011, b[-10:1])
-endfunc
-
-func Test_blob_get()
- let b = 0z0011223344
- call assert_equal(0x00, get(b, 0))
- call assert_equal(0x22, get(b, 2, 999))
- call assert_equal(0x44, get(b, 4))
- call assert_equal(0x44, get(b, -1))
- call assert_equal(-1, get(b, 5))
- call assert_equal(999, get(b, 5, 999))
- call assert_equal(-1, get(b, -8))
- call assert_equal(999, get(b, -8, 999))
- call assert_equal(10, get(v:_null_blob, 2, 10))
-
- call assert_equal(0x00, b[0])
- call assert_equal(0x22, b[2])
- call assert_equal(0x44, b[4])
- call assert_equal(0x44, b[-1])
- call assert_fails('echo b[5]', 'E979:')
- call assert_fails('echo b[-8]', 'E979:')
-endfunc
-
-func Test_blob_to_string()
- let b = 0z00112233445566778899aabbccdd
- call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
- call assert_equal(b, eval(string(b)))
- call remove(b, 4, -1)
- call assert_equal('0z00112233', string(b))
- call remove(b, 0, 3)
- call assert_equal('0z', string(b))
-endfunc
-
-func Test_blob_compare()
- let b1 = 0z0011
- let b2 = 0z1100
- let b3 = 0z001122
- call assert_true(b1 == b1)
- call assert_false(b1 == b2)
- call assert_false(b1 == b3)
- call assert_true(b1 != b2)
- call assert_true(b1 != b3)
- call assert_true(b1 == 0z0011)
- call assert_fails('echo b1 == 9', 'E977:')
- call assert_fails('echo b1 != 9', 'E977:')
-
- call assert_false(b1 is b2)
- let b2 = b1
- call assert_true(b1 == b2)
- call assert_true(b1 is b2)
- let b2 = copy(b1)
- call assert_true(b1 == b2)
- call assert_false(b1 is b2)
- let b2 = b1[:]
- call assert_true(b1 == b2)
- call assert_false(b1 is b2)
-
- call assert_fails('let x = b1 > b2')
- call assert_fails('let x = b1 < b2')
- call assert_fails('let x = b1 - b2')
- call assert_fails('let x = b1 / b2')
- call assert_fails('let x = b1 * b2')
-endfunc
-
-" test for range assign
-func Test_blob_range_assign()
- let b = 0z00
- let b[1] = 0x11
- let b[2] = 0x22
- call assert_equal(0z001122, b)
- call assert_fails('let b[4] = 0x33', 'E979:')
-endfunc
-
-func Test_blob_for_loop()
- let blob = 0z00010203
- let i = 0
- for byte in blob
- call assert_equal(i, byte)
- let i += 1
- endfor
- call assert_equal(4, i)
-
- let blob = 0z00
- call remove(blob, 0)
- call assert_equal(0, len(blob))
- for byte in blob
- call assert_error('loop over empty blob')
- endfor
-
- let blob = 0z0001020304
- let i = 0
- for byte in blob
- call assert_equal(i, byte)
- if i == 1
- call remove(blob, 0)
- elseif i == 3
- call remove(blob, 3)
- endif
- let i += 1
- endfor
- call assert_equal(5, i)
-endfunc
-
-func Test_blob_concatenate()
- let b = 0z0011
- let b += 0z2233
- call assert_equal(0z00112233, b)
-
- call assert_fails('let b += "a"')
- call assert_fails('let b += 88')
-
- let b = 0zDEAD + 0zBEEF
- call assert_equal(0zDEADBEEF, b)
-endfunc
-
-func Test_blob_add()
- let b = 0z0011
- call add(b, 0x22)
- call assert_equal(0z001122, b)
- call add(b, '51')
- call assert_equal(0z00112233, b)
- call assert_equal(1, add(v:_null_blob, 0x22))
-
- call assert_fails('call add(b, [9])', 'E745:')
- call assert_fails('call add("", 0x01)', 'E897:')
-endfunc
-
-func Test_blob_empty()
- call assert_false(empty(0z001122))
- call assert_true(empty(0z))
- call assert_true(empty(v:_null_blob))
-endfunc
-
-" Test removing items in blob
-func Test_blob_func_remove()
- " Test removing 1 element
- let b = 0zDEADBEEF
- call assert_equal(0xDE, remove(b, 0))
- call assert_equal(0zADBEEF, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0xEF, remove(b, -1))
- call assert_equal(0zDEADBE, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0xAD, remove(b, 1))
- call assert_equal(0zDEBEEF, b)
-
- " Test removing range of element(s)
- let b = 0zDEADBEEF
- call assert_equal(0zBE, remove(b, 2, 2))
- call assert_equal(0zDEADEF, b)
-
- let b = 0zDEADBEEF
- call assert_equal(0zADBE, remove(b, 1, 2))
- call assert_equal(0zDEEF, b)
-
- " Test invalid cases
- let b = 0zDEADBEEF
- call assert_fails("call remove(b, 5)", 'E979:')
- call assert_fails("call remove(b, 1, 5)", 'E979:')
- call assert_fails("call remove(b, 3, 2)", 'E979:')
- call assert_fails("call remove(1, 0)", 'E896:')
- call assert_fails("call remove(b, b)", 'E974:')
- call assert_fails("call remove(b, 1, [])", 'E745:')
- call assert_fails("call remove(v:_null_blob, 1, 2)", 'E979:')
-
- " Translated from v8.2.3284
- let b = 0zDEADBEEF
- lockvar b
- call assert_fails('call remove(b, 0)', 'E741:')
- unlockvar b
-endfunc
-
-func Test_blob_read_write()
- let b = 0zDEADBEEF
- call writefile(b, 'Xblob')
- let br = readfile('Xblob', 'B')
- call assert_equal(b, br)
- call delete('Xblob')
-
- " This was crashing when calling readfile() with a directory.
- call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
-endfunc
-
-" filter() item in blob
-func Test_blob_filter()
- call assert_equal(v:_null_blob, filter(v:_null_blob, '0'))
- call assert_equal(0z, filter(0zDEADBEEF, '0'))
- call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
- call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
- call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
- call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
- call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
- call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
-endfunc
-
-" map() item in blob
-func Test_blob_map()
- call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
- call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
- call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
-
- call assert_fails("call map(0z00, '[9]')", 'E978:')
-endfunc
-
-func Test_blob_index()
- call assert_equal(2, index(0zDEADBEEF, 0xBE))
- call assert_equal(-1, index(0zDEADBEEF, 0))
- call assert_equal(2, index(0z11111111, 0x11, 2))
- call assert_equal(3, 0z11110111->index(0x11, 2))
- call assert_equal(2, index(0z11111111, 0x11, -2))
- call assert_equal(3, index(0z11110111, 0x11, -2))
- call assert_equal(0, index(0z11110111, 0x11, -10))
- call assert_fails("echo index(0z11110111, 0x11, [])", 'E745:')
- call assert_equal(-1, index(v:_null_blob, 1))
-
- call assert_fails('call index("asdf", 0)', 'E897:')
-endfunc
-
-func Test_blob_insert()
- let b = 0zDEADBEEF
- call insert(b, 0x33)
- call assert_equal(0z33DEADBEEF, b)
-
- let b = 0zDEADBEEF
- call insert(b, 0x33, 2)
- call assert_equal(0zDEAD33BEEF, b)
-
- call assert_fails('call insert(b, -1)', 'E475:')
- call assert_fails('call insert(b, 257)', 'E475:')
- call assert_fails('call insert(b, 0, [9])', 'E745:')
- call assert_fails('call insert(b, 0, -20)', 'E475:')
- call assert_fails('call insert(b, 0, 20)', 'E475:')
- call assert_fails('call insert(b, [])', 'E745:')
- call assert_equal(0, insert(v:_null_blob, 0x33))
-
- " Translated from v8.2.3284
- let b = 0zDEADBEEF
- lockvar b
- call assert_fails('call insert(b, 3)', 'E741:')
- unlockvar b
-endfunc
-
-func Test_blob_reverse()
- call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
- call assert_equal(0zBEADDE, reverse(0zDEADBE))
- call assert_equal(0zADDE, reverse(0zDEAD))
- call assert_equal(0zDE, reverse(0zDE))
- call assert_equal(0z, reverse(v:_null_blob))
-endfunc
-
-func Test_blob_lock()
- let b = 0z112233
- lockvar b
- call assert_fails('let b = 0z44', 'E741:')
- unlockvar b
- let b = 0z44
-endfunc
-
-func Test_blob_sort()
- if has('float')
- call assert_fails('call sort([1.0, 0z11], "f")', 'E975:')
- else
- call assert_fails('call sort(["abc", 0z11], "f")', 'E702:')
- endif
-endfunc
-
-" The following used to cause an out-of-bounds memory access
-func Test_blob2string()
- let v = '0z' .. repeat('01010101.', 444)
- let v ..= '01'
- exe 'let b = ' .. v
- call assert_equal(v, string(b))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim
deleted file mode 100644
index 6b49f153c6..0000000000
--- a/src/nvim/testdir/test_delete.vim
+++ /dev/null
@@ -1,110 +0,0 @@
-" Test for delete().
-
-source check.vim
-
-func Test_file_delete()
- split Xfile
- call setline(1, ['a', 'b'])
- wq
- call assert_equal(['a', 'b'], readfile('Xfile'))
- call assert_equal(0, delete('Xfile'))
- call assert_fails('call readfile("Xfile")', 'E484:')
- call assert_equal(-1, delete('Xfile'))
- bwipe Xfile
-endfunc
-
-func Test_dir_delete()
- call mkdir('Xdir1')
- call assert_true(isdirectory('Xdir1'))
- call assert_equal(0, delete('Xdir1', 'd'))
- call assert_false(isdirectory('Xdir1'))
- call assert_equal(-1, delete('Xdir1', 'd'))
-endfunc
-
-func Test_recursive_delete()
- call mkdir('Xdir1')
- call mkdir('Xdir1/subdir')
- call mkdir('Xdir1/empty')
- split Xdir1/Xfile
- call setline(1, ['a', 'b'])
- w
- w Xdir1/subdir/Xfile
- close
- call assert_true(isdirectory('Xdir1'))
- call assert_equal(['a', 'b'], readfile('Xdir1/Xfile'))
- call assert_true(isdirectory('Xdir1/subdir'))
- call assert_equal(['a', 'b'], readfile('Xdir1/subdir/Xfile'))
- call assert_true('Xdir1/empty'->isdirectory())
- call assert_equal(0, delete('Xdir1', 'rf'))
- call assert_false(isdirectory('Xdir1'))
- call assert_equal(-1, delete('Xdir1', 'd'))
- bwipe Xdir1/Xfile
- bwipe Xdir1/subdir/Xfile
-endfunc
-
-func Test_symlink_delete()
- CheckUnix
- split Xfile
- call setline(1, ['a', 'b'])
- wq
- silent !ln -s Xfile Xlink
- " Delete the link, not the file
- call assert_equal(0, delete('Xlink'))
- call assert_equal(-1, delete('Xlink'))
- call assert_equal(0, delete('Xfile'))
- bwipe Xfile
-endfunc
-
-func Test_symlink_dir_delete()
- CheckUnix
- call mkdir('Xdir1')
- silent !ln -s Xdir1 Xlink
- call assert_true(isdirectory('Xdir1'))
- call assert_true(isdirectory('Xlink'))
- " Delete the link, not the directory
- call assert_equal(0, delete('Xlink'))
- call assert_equal(-1, delete('Xlink'))
- call assert_equal(0, delete('Xdir1', 'd'))
-endfunc
-
-func Test_symlink_recursive_delete()
- CheckUnix
- call mkdir('Xdir3')
- call mkdir('Xdir3/subdir')
- call mkdir('Xdir4')
- split Xdir3/Xfile
- call setline(1, ['a', 'b'])
- w
- w Xdir3/subdir/Xfile
- w Xdir4/Xfile
- close
- silent !ln -s ../Xdir4 Xdir3/Xlink
-
- call assert_true(isdirectory('Xdir3'))
- call assert_equal(['a', 'b'], readfile('Xdir3/Xfile'))
- call assert_true(isdirectory('Xdir3/subdir'))
- call assert_equal(['a', 'b'], readfile('Xdir3/subdir/Xfile'))
- call assert_true(isdirectory('Xdir4'))
- call assert_true(isdirectory('Xdir3/Xlink'))
- call assert_equal(['a', 'b'], readfile('Xdir4/Xfile'))
-
- call assert_equal(0, delete('Xdir3', 'rf'))
- call assert_false(isdirectory('Xdir3'))
- call assert_equal(-1, delete('Xdir3', 'd'))
- " symlink is deleted, not the directory it points to
- call assert_true(isdirectory('Xdir4'))
- call assert_equal(['a', 'b'], readfile('Xdir4/Xfile'))
- call assert_equal(0, delete('Xdir4/Xfile'))
- call assert_equal(0, delete('Xdir4', 'd'))
-
- bwipe Xdir3/Xfile
- bwipe Xdir3/subdir/Xfile
- bwipe Xdir4/Xfile
-endfunc
-
-func Test_delete_errors()
- call assert_fails('call delete('''')', 'E474:')
- call assert_fails('call delete(''foo'', 0)', 'E15:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
deleted file mode 100644
index 47f7f5eb0e..0000000000
--- a/src/nvim/testdir/test_expr.vim
+++ /dev/null
@@ -1,676 +0,0 @@
-" Tests for expressions.
-
-source check.vim
-
-func Test_equal()
- let base = {}
- func base.method()
- return 1
- endfunc
- func base.other() dict
- return 1
- endfunc
- let instance = copy(base)
- call assert_true(base.method == instance.method)
- call assert_true([base.method] == [instance.method])
- call assert_true(base.other == instance.other)
- call assert_true([base.other] == [instance.other])
-
- call assert_false(base.method == base.other)
- call assert_false([base.method] == [base.other])
- call assert_false(base.method == instance.other)
- call assert_false([base.method] == [instance.other])
-
- call assert_fails('echo base.method > instance.method')
-endfunc
-
-func Test_version()
- call assert_true(has('patch-7.4.001'))
- call assert_true(has('patch-7.4.01'))
- call assert_true(has('patch-7.4.1'))
- call assert_true(has('patch-6.9.999'))
- call assert_true(has('patch-7.1.999'))
- call assert_true(has('patch-7.4.123'))
-
- call assert_false(has('patch-7'))
- call assert_false(has('patch-7.4'))
- call assert_false(has('patch-7.4.'))
- call assert_false(has('patch-9.1.0'))
- call assert_false(has('patch-9.9.1'))
-endfunc
-
-func Test_dict()
- let d = {'': 'empty', 'a': 'a', 0: 'zero'}
- call assert_equal('empty', d[''])
- call assert_equal('a', d['a'])
- call assert_equal('zero', d[0])
- call assert_true(has_key(d, ''))
- call assert_true(has_key(d, 'a'))
- call assert_fails("let i = has_key([], 'a')", 'E715:')
-
- let d[''] = 'none'
- let d['a'] = 'aaa'
- call assert_equal('none', d[''])
- call assert_equal('aaa', d['a'])
-
- let d[ 'b' ] = 'bbb'
- call assert_equal('bbb', d[ 'b' ])
-endfunc
-
-func Test_strgetchar()
- call assert_equal(char2nr('a'), strgetchar('axb', 0))
- call assert_equal(char2nr('x'), 'axb'->strgetchar(1))
- call assert_equal(char2nr('b'), strgetchar('axb', 2))
-
- call assert_equal(-1, strgetchar('axb', -1))
- call assert_equal(-1, strgetchar('axb', 3))
- call assert_equal(-1, strgetchar('', 0))
- call assert_fails("let c=strgetchar([], 1)", 'E730:')
- call assert_fails("let c=strgetchar('axb', [])", 'E745:')
-endfunc
-
-func Test_strcharpart()
- call assert_equal('a', strcharpart('axb', 0, 1))
- call assert_equal('x', 'axb'->strcharpart(1, 1))
- call assert_equal('b', strcharpart('axb', 2, 1))
- call assert_equal('xb', strcharpart('axb', 1))
-
- call assert_equal('', strcharpart('axb', 1, 0))
- call assert_equal('', strcharpart('axb', 1, -1))
- call assert_equal('', strcharpart('axb', -1, 1))
- call assert_equal('', strcharpart('axb', -2, 2))
-
- call assert_equal('a', strcharpart('axb', -1, 2))
-
- call assert_equal('edit', "editor"[-10:3])
-endfunc
-
-func Test_getreg_empty_list()
- call assert_equal('', getreg('x'))
- call assert_equal([], getreg('x', 1, 1))
- let x = getreg('x', 1, 1)
- let y = x
- call add(x, 'foo')
- call assert_equal(['foo'], y)
- call assert_fails('call getreg([])', 'E730:')
-endfunc
-
-func Test_loop_over_null_list()
- let null_list = v:_null_list
- for i in null_list
- call assert_report('should not get here')
- endfor
-endfunc
-
-func Test_set_reg_null_list()
- call setreg('x', v:_null_list)
-endfunc
-
-func Test_special_char()
- " The failure is only visible using valgrind.
- call assert_fails('echo "\<C-">')
-endfunc
-
-func Test_option_value()
- " boolean
- set bri
- call assert_equal(1, &bri)
- set nobri
- call assert_equal(0, &bri)
-
- " number
- set ts=1
- call assert_equal(1, &ts)
- set ts=8
- call assert_equal(8, &ts)
-
- " string
- exe "set cedit=\<Esc>"
- call assert_equal("\<Esc>", &cedit)
- set cpo=
- call assert_equal("", &cpo)
- set cpo=abcdefi
- call assert_equal("abcdefi", &cpo)
- set cpo&vim
-endfunc
-
-function Test_printf_64bit()
- call assert_equal("123456789012345", printf('%d', 123456789012345))
-endfunc
-
-function Test_printf_spec_s()
- " number
- call assert_equal("1234567890", printf('%s', 1234567890))
-
- " string
- call assert_equal("abcdefgi", printf('%s', "abcdefgi"))
-
- " float
- call assert_equal("1.23", printf('%s', 1.23))
-
- " list
- let value = [1, 'two', ['three', 4]]
- call assert_equal(string(value), printf('%s', value))
-
- " dict
- let value = {'key1' : 'value1', 'key2' : ['list', 'value'], 'key3' : {'dict' : 'value'}}
- call assert_equal(string(value), printf('%s', value))
-
- " funcref
- call assert_equal('printf', printf('%s', 'printf'->function()))
-
- " partial
- call assert_equal(string(function('printf', ['%s'])), printf('%s', function('printf', ['%s'])))
-endfunc
-
-function Test_printf_spec_b()
- call assert_equal("0", printf('%b', 0))
- call assert_equal("00001100", printf('%08b', 12))
- call assert_equal("11111111", printf('%08b', 0xff))
- call assert_equal(" 1111011", printf('%10b', 123))
- call assert_equal("0001111011", printf('%010b', 123))
- call assert_equal(" 0b1111011", printf('%#10b', 123))
- call assert_equal("0B01111011", printf('%#010B', 123))
- call assert_equal("1001001100101100000001011010010", printf('%b', 1234567890))
- call assert_equal("11100000100100010000110000011011101111101111001", printf('%b', 123456789012345))
- call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%b', -1))
-endfunc
-
-function Test_printf_misc()
- call assert_equal('123', printf('123'))
- call assert_fails("call printf('123', 3)", "E767:")
-
- call assert_equal('123', printf('%d', 123))
- call assert_equal('123', printf('%i', 123))
- call assert_equal('123', printf('%D', 123))
- call assert_equal('123', printf('%U', 123))
- call assert_equal('173', printf('%o', 123))
- call assert_equal('173', printf('%O', 123))
- call assert_equal('7b', printf('%x', 123))
- call assert_equal('7B', printf('%X', 123))
-
- call assert_equal('123', printf('%hd', 123))
- call assert_equal('-123', printf('%hd', -123))
- call assert_equal('-1', printf('%hd', 0xFFFF))
- call assert_equal('-1', printf('%hd', 0x1FFFFF))
-
- call assert_equal('123', printf('%hu', 123))
- call assert_equal('65413', printf('%hu', -123))
- call assert_equal('65535', printf('%hu', 0xFFFF))
- call assert_equal('65535', printf('%hu', 0x1FFFFF))
-
- call assert_equal('123', printf('%ld', 123))
- call assert_equal('-123', printf('%ld', -123))
- call assert_equal('65535', printf('%ld', 0xFFFF))
- call assert_equal('131071', printf('%ld', 0x1FFFF))
-
- call assert_equal('{', printf('%c', 123))
- call assert_equal('abc', printf('%s', 'abc'))
- call assert_equal('abc', printf('%S', 'abc'))
-
- call assert_equal('+123', printf('%+d', 123))
- call assert_equal('-123', printf('%+d', -123))
- call assert_equal('+123', printf('%+ d', 123))
- call assert_equal(' 123', printf('% d', 123))
- call assert_equal(' 123', printf('% d', 123))
- call assert_equal('-123', printf('% d', -123))
-
- call assert_equal('123', printf('%2d', 123))
- call assert_equal(' 123', printf('%6d', 123))
- call assert_equal('000123', printf('%06d', 123))
- call assert_equal('+00123', printf('%+06d', 123))
- call assert_equal(' 00123', printf('% 06d', 123))
- call assert_equal(' +123', printf('%+6d', 123))
- call assert_equal(' 123', printf('% 6d', 123))
- call assert_equal(' -123', printf('% 6d', -123))
-
- " Test left adjusted.
- call assert_equal('123 ', printf('%-6d', 123))
- call assert_equal('+123 ', printf('%-+6d', 123))
- call assert_equal(' 123 ', printf('%- 6d', 123))
- call assert_equal('-123 ', printf('%- 6d', -123))
-
- call assert_equal(' 00123', printf('%7.5d', 123))
- call assert_equal(' -00123', printf('%7.5d', -123))
- call assert_equal(' +00123', printf('%+7.5d', 123))
- " Precision field should not be used when combined with %0
- call assert_equal(' 00123', printf('%07.5d', 123))
- call assert_equal(' -00123', printf('%07.5d', -123))
-
- call assert_equal(' 123', printf('%*d', 5, 123))
- call assert_equal('123 ', printf('%*d', -5, 123))
- call assert_equal('00123', printf('%.*d', 5, 123))
- call assert_equal(' 123', printf('% *d', 5, 123))
- call assert_equal(' +123', printf('%+ *d', 5, 123))
-
- call assert_equal('foobar', printf('%.*s', 9, 'foobar'))
- call assert_equal('foo', printf('%.*s', 3, 'foobar'))
- call assert_equal('', printf('%.*s', 0, 'foobar'))
- call assert_equal('foobar', printf('%.*s', -1, 'foobar'))
-
- " Simple quote (thousand grouping char) is ignored.
- call assert_equal('+00123456', printf("%+'09d", 123456))
-
- " Unrecognized format specifier kept as-is.
- call assert_equal('_123', printf("%_%d", 123))
-
- " Test alternate forms.
- call assert_equal('0x7b', printf('%#x', 123))
- call assert_equal('0X7B', printf('%#X', 123))
- call assert_equal('0173', printf('%#o', 123))
- call assert_equal('0173', printf('%#O', 123))
- call assert_equal('abc', printf('%#s', 'abc'))
- call assert_equal('abc', printf('%#S', 'abc'))
- call assert_equal(' 0173', printf('%#6o', 123))
- call assert_equal(' 00173', printf('%#6.5o', 123))
- call assert_equal(' 0173', printf('%#6.2o', 123))
- call assert_equal(' 0173', printf('%#6.2o', 123))
- call assert_equal('0173', printf('%#2.2o', 123))
-
- call assert_equal(' 00123', printf('%6.5d', 123))
- call assert_equal(' 0007b', printf('%6.5x', 123))
-
- call assert_equal('123', printf('%.2d', 123))
- call assert_equal('0123', printf('%.4d', 123))
- call assert_equal('0000000123', printf('%.10d', 123))
- call assert_equal('123', printf('%.0d', 123))
-
- call assert_equal('abc', printf('%2s', 'abc'))
- call assert_equal('abc', printf('%2S', 'abc'))
- call assert_equal('abc', printf('%.4s', 'abc'))
- call assert_equal('abc', printf('%.4S', 'abc'))
- call assert_equal('ab', printf('%.2s', 'abc'))
- call assert_equal('ab', printf('%.2S', 'abc'))
- call assert_equal('', printf('%.0s', 'abc'))
- call assert_equal('', printf('%.s', 'abc'))
- call assert_equal(' abc', printf('%4s', 'abc'))
- call assert_equal(' abc', printf('%4S', 'abc'))
- call assert_equal('0abc', printf('%04s', 'abc'))
- call assert_equal('0abc', printf('%04S', 'abc'))
- call assert_equal('abc ', printf('%-4s', 'abc'))
- call assert_equal('abc ', printf('%-4S', 'abc'))
-
- call assert_equal('🐍', printf('%.2S', '🐍🐍'))
- call assert_equal('', printf('%.1S', '🐍🐍'))
-
- call assert_equal('[ あいう]', printf('[%10.6S]', 'あいうえお'))
- call assert_equal('[ あいうえ]', printf('[%10.8S]', 'あいうえお'))
- call assert_equal('[あいうえお]', printf('[%10.10S]', 'あいうえお'))
- call assert_equal('[あいうえお]', printf('[%10.12S]', 'あいうえお'))
-
- call assert_equal('あいう', printf('%S', 'あいう'))
- call assert_equal('あいう', printf('%#S', 'あいう'))
-
- call assert_equal('あb', printf('%2S', 'あb'))
- call assert_equal('あb', printf('%.4S', 'あb'))
- call assert_equal('あ', printf('%.2S', 'あb'))
- call assert_equal(' あb', printf('%4S', 'あb'))
- call assert_equal('0あb', printf('%04S', 'あb'))
- call assert_equal('あb ', printf('%-4S', 'あb'))
- call assert_equal('あ ', printf('%-4.2S', 'あb'))
-
- call assert_equal('aい', printf('%2S', 'aい'))
- call assert_equal('aい', printf('%.4S', 'aい'))
- call assert_equal('a', printf('%.2S', 'aい'))
- call assert_equal(' aい', printf('%4S', 'aい'))
- call assert_equal('0aい', printf('%04S', 'aい'))
- call assert_equal('aい ', printf('%-4S', 'aい'))
- call assert_equal('a ', printf('%-4.2S', 'aい'))
-
- call assert_equal('[あいう]', printf('[%05S]', 'あいう'))
- call assert_equal('[あいう]', printf('[%06S]', 'あいう'))
- call assert_equal('[0あいう]', printf('[%07S]', 'あいう'))
-
- call assert_equal('[あiう]', printf('[%05S]', 'あiう'))
- call assert_equal('[0あiう]', printf('[%06S]', 'あiう'))
- call assert_equal('[00あiう]', printf('[%07S]', 'あiう'))
-
- call assert_equal('[0あい]', printf('[%05.4S]', 'あいう'))
- call assert_equal('[00あい]', printf('[%06.4S]', 'あいう'))
- call assert_equal('[000あい]', printf('[%07.4S]', 'あいう'))
-
- call assert_equal('[00あi]', printf('[%05.4S]', 'あiう'))
- call assert_equal('[000あi]', printf('[%06.4S]', 'あiう'))
- call assert_equal('[0000あi]', printf('[%07.4S]', 'あiう'))
-
- call assert_equal('[0あい]', printf('[%05.5S]', 'あいう'))
- call assert_equal('[00あい]', printf('[%06.5S]', 'あいう'))
- call assert_equal('[000あい]', printf('[%07.5S]', 'あいう'))
-
- call assert_equal('[あiう]', printf('[%05.5S]', 'あiう'))
- call assert_equal('[0あiう]', printf('[%06.5S]', 'あiう'))
- call assert_equal('[00あiう]', printf('[%07.5S]', 'あiう'))
-
- call assert_equal('[0000000000]', printf('[%010.0S]', 'あいう'))
- call assert_equal('[0000000000]', printf('[%010.1S]', 'あいう'))
- call assert_equal('[00000000あ]', printf('[%010.2S]', 'あいう'))
- call assert_equal('[00000000あ]', printf('[%010.3S]', 'あいう'))
- call assert_equal('[000000あい]', printf('[%010.4S]', 'あいう'))
- call assert_equal('[000000あい]', printf('[%010.5S]', 'あいう'))
- call assert_equal('[0000あいう]', printf('[%010.6S]', 'あいう'))
- call assert_equal('[0000あいう]', printf('[%010.7S]', 'あいう'))
-
- call assert_equal('[0000000000]', printf('[%010.1S]', 'あiう'))
- call assert_equal('[00000000あ]', printf('[%010.2S]', 'あiう'))
- call assert_equal('[0000000あi]', printf('[%010.3S]', 'あiう'))
- call assert_equal('[0000000あi]', printf('[%010.4S]', 'あiう'))
- call assert_equal('[00000あiう]', printf('[%010.5S]', 'あiう'))
- call assert_equal('[00000あiう]', printf('[%010.6S]', 'あiう'))
- call assert_equal('[00000あiう]', printf('[%010.7S]', 'あiう'))
-
- call assert_equal('1%', printf('%d%%', 1))
-endfunc
-
-function Test_printf_float()
- call assert_equal('1.000000', printf('%f', 1))
- call assert_equal('1.230000', printf('%f', 1.23))
- call assert_equal('1.230000', printf('%F', 1.23))
- call assert_equal('9999999.9', printf('%g', 9999999.9))
- call assert_equal('9999999.9', printf('%G', 9999999.9))
- call assert_equal('1.00000001e7', printf('%.8g', 10000000.1))
- call assert_equal('1.00000001E7', printf('%.8G', 10000000.1))
- call assert_equal('1.230000e+00', printf('%e', 1.23))
- call assert_equal('1.230000E+00', printf('%E', 1.23))
- call assert_equal('1.200000e-02', printf('%e', 0.012))
- call assert_equal('-1.200000e-02', printf('%e', -0.012))
- call assert_equal('0.33', printf('%.2f', 1.0/3.0))
- call assert_equal(' 0.33', printf('%6.2f', 1.0/3.0))
- call assert_equal(' -0.33', printf('%6.2f', -1.0/3.0))
- call assert_equal('000.33', printf('%06.2f', 1.0/3.0))
- call assert_equal('-00.33', printf('%06.2f', -1.0/3.0))
- call assert_equal('-00.33', printf('%+06.2f', -1.0/3.0))
- call assert_equal('+00.33', printf('%+06.2f', 1.0/3.0))
- call assert_equal(' 00.33', printf('% 06.2f', 1.0/3.0))
- call assert_equal('000.33', printf('%06.2g', 1.0/3.0))
- call assert_equal('-00.33', printf('%06.2g', -1.0/3.0))
- call assert_equal('0.33', printf('%3.2f', 1.0/3.0))
- call assert_equal('003.33e-01', printf('%010.2e', 1.0/3.0))
- call assert_equal(' 03.33e-01', printf('% 010.2e', 1.0/3.0))
- call assert_equal('+03.33e-01', printf('%+010.2e', 1.0/3.0))
- call assert_equal('-03.33e-01', printf('%010.2e', -1.0/3.0))
-
- " When precision is 0, the dot should be omitted.
- call assert_equal(' 2', printf('%3.f', 7.0/3.0))
- call assert_equal(' 2', printf('%3.g', 7.0/3.0))
- call assert_equal(' 2e+00', printf('%7.e', 7.0/3.0))
-
- " Float zero can be signed.
- call assert_equal('+0.000000', printf('%+f', 0.0))
- call assert_equal('0.000000', printf('%f', 1.0/(1.0/0.0)))
- call assert_equal('-0.000000', printf('%f', 1.0/(-1.0/0.0)))
- call assert_equal('0.0', printf('%s', 1.0/(1.0/0.0)))
- call assert_equal('-0.0', printf('%s', 1.0/(-1.0/0.0)))
- call assert_equal('0.0', printf('%S', 1.0/(1.0/0.0)))
- call assert_equal('-0.0', printf('%S', 1.0/(-1.0/0.0)))
-
- " Float infinity can be signed.
- call assert_equal('inf', printf('%f', 1.0/0.0))
- call assert_equal('-inf', printf('%f', -1.0/0.0))
- call assert_equal('inf', printf('%g', 1.0/0.0))
- call assert_equal('-inf', printf('%g', -1.0/0.0))
- call assert_equal('inf', printf('%e', 1.0/0.0))
- call assert_equal('-inf', printf('%e', -1.0/0.0))
- call assert_equal('INF', printf('%F', 1.0/0.0))
- call assert_equal('-INF', printf('%F', -1.0/0.0))
- call assert_equal('INF', printf('%E', 1.0/0.0))
- call assert_equal('-INF', printf('%E', -1.0/0.0))
- call assert_equal('INF', printf('%E', 1.0/0.0))
- call assert_equal('-INF', printf('%G', -1.0/0.0))
- call assert_equal('+inf', printf('%+f', 1.0/0.0))
- call assert_equal('-inf', printf('%+f', -1.0/0.0))
- call assert_equal(' inf', printf('% f', 1.0/0.0))
- call assert_equal(' inf', printf('%6f', 1.0/0.0))
- call assert_equal(' -inf', printf('%6f', -1.0/0.0))
- call assert_equal(' inf', printf('%6g', 1.0/0.0))
- call assert_equal(' -inf', printf('%6g', -1.0/0.0))
- call assert_equal(' +inf', printf('%+6f', 1.0/0.0))
- call assert_equal(' inf', printf('% 6f', 1.0/0.0))
- call assert_equal(' +inf', printf('%+06f', 1.0/0.0))
- call assert_equal('inf ', printf('%-6f', 1.0/0.0))
- call assert_equal('-inf ', printf('%-6f', -1.0/0.0))
- call assert_equal('+inf ', printf('%-+6f', 1.0/0.0))
- call assert_equal(' inf ', printf('%- 6f', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6F', -1.0/0.0))
- call assert_equal('+INF ', printf('%-+6F', 1.0/0.0))
- call assert_equal(' INF ', printf('%- 6F', 1.0/0.0))
- call assert_equal('INF ', printf('%-6G', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6G', -1.0/0.0))
- call assert_equal('INF ', printf('%-6E', 1.0/0.0))
- call assert_equal('-INF ', printf('%-6E', -1.0/0.0))
- call assert_equal("str2float('inf')", printf('%s', 1.0/0.0))
- call assert_equal("-str2float('inf')", printf('%s', -1.0/0.0))
-
- " Test special case where max precision is truncated at 340.
- call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.330f', 1.0))
- call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.340f', 1.0))
- call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.350f', 1.0))
-
- " Float nan (not a number) has no sign.
- call assert_equal('nan', printf('%f', sqrt(-1.0)))
- call assert_equal('nan', printf('%f', 0.0/0.0))
- call assert_equal('nan', printf('%f', -0.0/0.0))
- call assert_equal('nan', printf('%g', 0.0/0.0))
- call assert_equal('nan', printf('%e', 0.0/0.0))
- call assert_equal('NAN', printf('%F', 0.0/0.0))
- call assert_equal('NAN', printf('%G', 0.0/0.0))
- call assert_equal('NAN', printf('%E', 0.0/0.0))
- call assert_equal('NAN', printf('%F', -0.0/0.0))
- call assert_equal('NAN', printf('%G', -0.0/0.0))
- call assert_equal('NAN', printf('%E', -0.0/0.0))
- call assert_equal(' nan', printf('%6f', 0.0/0.0))
- call assert_equal(' nan', printf('%06f', 0.0/0.0))
- call assert_equal('nan ', printf('%-6f', 0.0/0.0))
- call assert_equal('nan ', printf('%- 6f', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%s', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%s', -0.0/0.0))
- call assert_equal("str2float('nan')", printf('%S', 0.0/0.0))
- call assert_equal("str2float('nan')", printf('%S', -0.0/0.0))
-
- call assert_fails('echo printf("%f", "a")', 'E807:')
-endfunc
-
-function Test_printf_errors()
- call assert_fails('echo printf("%d", {})', 'E728:')
- call assert_fails('echo printf("%d", [])', 'E745:')
- call assert_fails('echo printf("%d", 1, 2)', 'E767:')
- call assert_fails('echo printf("%*d", 1)', 'E766:')
- call assert_fails('echo printf("%s")', 'E766:')
- if has('float')
- call assert_fails('echo printf("%d", 1.2)', 'E805:')
- call assert_fails('echo printf("%f")')
- endif
-endfunc
-
-function Test_max_min_errors()
- call assert_fails('call max(v:true)', 'E712:')
- call assert_fails('call max(v:true)', 'max()')
- call assert_fails('call min(v:true)', 'E712:')
- call assert_fails('call min(v:true)', 'min()')
-endfunc
-
-func Test_function_with_funcref()
- let s:f = function('type')
- let s:fref = function(s:f)
- call assert_equal(v:t_string, s:fref('x'))
- call assert_fails("call function('s:f')", 'E700:')
-
- call assert_fails("call function('foo()')", 'E475:')
- call assert_fails("call function('foo()')", 'foo()')
- call assert_fails("function('')", 'E129:')
-
- let Len = {s -> strlen(s)}
- call assert_equal(6, Len('foobar'))
- let name = string(Len)
- " can evaluate "function('<lambda>99')"
- call execute('let Ref = ' .. name)
- call assert_equal(4, Ref('text'))
-endfunc
-
-func Test_funcref()
- func! One()
- return 1
- endfunc
- let OneByName = function('One')
- let OneByRef = funcref('One')
- func! One()
- return 2
- endfunc
- call assert_equal(2, OneByName())
- call assert_equal(1, OneByRef())
- let OneByRef = 'One'->funcref()
- call assert_equal(2, OneByRef())
- call assert_fails('echo funcref("{")', 'E475:')
- let OneByRef = funcref("One", repeat(["foo"], 20))
- call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
- call assert_fails('echo function("min") =~ function("min")', 'E694:')
-endfunc
-
-" Test for calling function() and funcref() outside of a Vim script context.
-func Test_function_outside_script()
- let cleanup =<< trim END
- call writefile([execute('messages')], 'Xtest.out')
- qall
- END
- call writefile(cleanup, 'Xverify.vim')
- call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim")
- call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
- call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim")
- call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
- call delete('Xtest.out')
- call delete('Xverify.vim')
-endfunc
-
-func Test_setmatches()
- hi def link 1 Comment
- hi def link 2 PreProc
- let set = [{"group": 1, "pattern": 2, "id": 3, "priority": 4}]
- let exp = [{"group": '1', "pattern": '2', "id": 3, "priority": 4}]
- if has('conceal')
- let set[0]['conceal'] = 5
- let exp[0]['conceal'] = '5'
- endif
- eval set->setmatches()
- call assert_equal(exp, getmatches())
- call assert_fails('let m = setmatches([], [])', 'E745:')
-endfunc
-
-func Test_empty_concatenate()
- call assert_equal('b', 'a'[4:0] . 'b')
- call assert_equal('b', 'b' . 'a'[4:0])
-endfunc
-
-func Test_broken_number()
- let X = 'bad'
- call assert_fails('echo 1X', 'E15:')
- call assert_fails('echo 0b1X', 'E15:')
- call assert_fails('echo 0b12', 'E15:')
- call assert_fails('echo 0x1X', 'E15:')
- call assert_fails('echo 011X', 'E15:')
- call assert_equal(2, str2nr('2a'))
- call assert_fails('inoremap <Char-0b1z> b', 'E474:')
-endfunc
-
-func Test_eval_after_if()
- let s:val = ''
- func SetVal(x)
- let s:val ..= a:x
- endfunc
- if 0 | eval SetVal('a') | endif | call SetVal('b')
- call assert_equal('b', s:val)
-endfunc
-
-func Test_divide_by_zero()
- " only tests that this doesn't crash, the result is not important
- echo 0 / 0
- echo 0 / 0 / -1
-endfunc
-
-" Test for command-line completion of expressions
-func Test_expr_completion()
- CheckFeature cmdline_compl
- for cmd in [
- \ 'let a = ',
- \ 'const a = ',
- \ 'if',
- \ 'elseif',
- \ 'while',
- \ 'for',
- \ 'echo',
- \ 'echon',
- \ 'execute',
- \ 'echomsg',
- \ 'echoerr',
- \ 'call',
- \ 'return',
- \ 'cexpr',
- \ 'caddexpr',
- \ 'cgetexpr',
- \ 'lexpr',
- \ 'laddexpr',
- \ 'lgetexpr']
- call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt')
- call assert_equal('"' . cmd . ' getline(', getreg(':'))
- endfor
-
- " completion for the expression register
- call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt')
- call assert_equal('"float2nr("', @=)
-
- " completion for window local variables
- let w:wvar1 = 10
- let w:wvar2 = 10
- call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo w:wvar1 w:wvar2', @:)
- unlet w:wvar1 w:wvar2
-
- " completion for tab local variables
- let t:tvar1 = 10
- let t:tvar2 = 10
- call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo t:tvar1 t:tvar2', @:)
- unlet t:tvar1 t:tvar2
-
- " completion for variables
- let g:tvar1 = 1
- let g:tvar2 = 2
- call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"let g:tvar1 g:tvar2', @:)
- " completion for variables after a ||
- call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:)
-
- " completion for options
- call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo &compatible', @:)
- call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo 1 && &compatible', @:)
- call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"echo &g:equalalways', @:)
-
- " completion for string
- call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:)
- call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo 'Hello World'\<C-A>", @:)
-
- " completion for command after a |
- call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"echo 'Hello' | cwindow", @:)
-
- " completion for environment variable
- let $X_VIM_TEST_COMPLETE_ENV = 'foo'
- call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:)
- unlet $X_VIM_TEST_COMPLETE_ENV
-endfunc
-
-" Test for errors in expression evaluation
-func Test_expr_eval_error()
- call assert_fails("let i = 'abc' . []", 'E730:')
- call assert_fails("let l = [] + 10", 'E745:')
- call assert_fails("let v = 10 + []", 'E745:')
- call assert_fails("let v = 10 / []", 'E745:')
- call assert_fails("let v = -{}", 'E728:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
deleted file mode 100644
index c75177ea39..0000000000
--- a/src/nvim/testdir/test_filter_map.vim
+++ /dev/null
@@ -1,108 +0,0 @@
-" Test filter() and map()
-
-" list with expression string
-func Test_filter_map_list_expr_string()
- " filter()
- call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
- call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
- call assert_equal([], filter([1, 2, 3, 4], 0))
-
- " map()
- call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
- call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
- call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
- call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
-endfunc
-
-" dict with expression string
-func Test_filter_map_dict_expr_string()
- let dict = {"foo": 1, "bar": 2, "baz": 3}
-
- " filter()
- call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
- call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
- call assert_equal({}, filter(copy(dict), 0))
-
- " map()
- call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
- call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
- call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
-endfunc
-
-" list with funcref
-func Test_filter_map_list_expr_funcref()
- " filter()
- func! s:filter1(index, val) abort
- return a:val > 1
- endfunc
- call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
-
- func! s:filter2(index, val) abort
- return a:index > 1
- endfunc
- call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
-
- " map()
- func! s:filter3(index, val) abort
- return a:val * 2
- endfunc
- call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
-
- func! s:filter4(index, val) abort
- return a:index * 2
- endfunc
- call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
-endfunc
-
-" dict with funcref
-func Test_filter_map_dict_expr_funcref()
- let dict = {"foo": 1, "bar": 2, "baz": 3}
-
- " filter()
- func! s:filter1(key, val) abort
- return a:val > 1
- endfunc
- call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
-
- func! s:filter2(key, val) abort
- return a:key > "bar"
- endfunc
- call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
-
- " map()
- func! s:filter3(key, val) abort
- return a:val * 2
- endfunc
- call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
-
- func! s:filter4(key, val) abort
- return a:key[0]
- endfunc
- call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
-endfunc
-
-func Test_map_filter_fails()
- call assert_fails('call map([1], "42 +")', 'E15:')
- call assert_fails('call filter([1], "42 +")', 'E15:')
- call assert_fails("let l = map('abc', '\"> \" . v:val')", 'E896:')
- call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:')
- call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
- call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
- call assert_fails("let l = filter([1, 2], {})", 'E731:')
- call assert_equal(v:_null_list, filter(v:_null_list, 0))
- call assert_equal(v:_null_dict, filter(v:_null_dict, 0))
- call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val'))
- call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val'))
-endfunc
-
-func Test_map_and_modify()
- let l = ["abc"]
- " cannot change the list halfway a map()
- call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
-
- let d = #{a: 1, b: 2, c: 3}
- call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
- call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
deleted file mode 100644
index 9ecd83265a..0000000000
--- a/src/nvim/testdir/test_listdict.vim
+++ /dev/null
@@ -1,1070 +0,0 @@
-" Tests for the List and Dict types
-
-func TearDown()
- " Run garbage collection after every test
- call test_garbagecollect_now()
-endfunc
-
-" Tests for List type
-
-" List creation
-func Test_list_create()
- " Creating List directly with different types
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l))
- call assert_equal({'a' : 1}, l[-1])
- call assert_equal(1, l[-4])
- let x = 10
- try
- let x = l[-5]
- catch
- call assert_match('E684:', v:exception)
- endtry
- call assert_equal(10, x)
-endfunc
-
-" List slices
-func Test_list_slice()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:])
- call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:])
- call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2])
- call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8])
- call assert_equal([], l[8:-1])
- call assert_equal([], l[0:-10])
- " perform an operation on a list slice
- let l = [1, 2, 3]
- let l[:1] += [1, 2]
- let l[2:] -= [1]
- call assert_equal([2, 4, 2], l)
-endfunc
-
-" List identity
-func Test_list_identity()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- let ll = l
- let lx = copy(l)
- call assert_true(l == ll)
- call assert_false(l isnot ll)
- call assert_true(l is ll)
- call assert_true(l == lx)
- call assert_false(l is lx)
- call assert_true(l isnot lx)
-endfunc
-
-" removing items with :unlet
-func Test_list_unlet()
- let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
- unlet l[2]
- call assert_equal([1, 'as''d', {'a': 1}], l)
- let l = range(8)
- unlet l[:3]
- unlet l[1:]
- call assert_equal([4], l)
-
- " removing items out of range: silently skip items that don't exist
- let l = [0, 1, 2, 3]
- call assert_fails('unlet l[2:1]', 'E684')
- let l = [0, 1, 2, 3]
- unlet l[2:2]
- call assert_equal([0, 1, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[2:3]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- unlet l[2:4]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- unlet l[2:5]
- call assert_equal([0, 1], l)
- let l = [0, 1, 2, 3]
- call assert_fails('unlet l[-1:2]', 'E684')
- let l = [0, 1, 2, 3]
- unlet l[-2:2]
- call assert_equal([0, 1, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[-3:2]
- call assert_equal([0, 3], l)
- let l = [0, 1, 2, 3]
- unlet l[-4:2]
- call assert_equal([3], l)
- let l = [0, 1, 2, 3]
- unlet l[-5:2]
- call assert_equal([3], l)
- let l = [0, 1, 2, 3]
- unlet l[-6:2]
- call assert_equal([3], l)
-endfunc
-
-" assignment to a list
-func Test_list_assign()
- let l = [0, 1, 2, 3]
- let [va, vb] = l[2:3]
- call assert_equal([2, 3], [va, vb])
- call assert_fails('let [va, vb] = l', 'E687')
- call assert_fails('let [va, vb] = l[1:1]', 'E688')
-endfunc
-
-" test for range assign
-func Test_list_range_assign()
- let l = [0]
- let l[:] = [1, 2]
- call assert_equal([1, 2], l)
- let l[-4:-1] = [5, 6]
- call assert_equal([5, 6], l)
-endfunc
-
-" Test removing items in list
-func Test_list_func_remove()
- " Test removing 1 element
- let l = [1, 2, 3, 4]
- call assert_equal(1, remove(l, 0))
- call assert_equal([2, 3, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal(2, remove(l, 1))
- call assert_equal([1, 3, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal(4, remove(l, -1))
- call assert_equal([1, 2, 3], l)
-
- " Test removing range of element(s)
- let l = [1, 2, 3, 4]
- call assert_equal([3], remove(l, 2, 2))
- call assert_equal([1, 2, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal([2, 3], remove(l, 1, 2))
- call assert_equal([1, 4], l)
-
- let l = [1, 2, 3, 4]
- call assert_equal([2, 3], remove(l, -3, -2))
- call assert_equal([1, 4], l)
-
- " Test invalid cases
- let l = [1, 2, 3, 4]
- call assert_fails("call remove(l, 5)", 'E684:')
- call assert_fails("call remove(l, 1, 5)", 'E684:')
- call assert_fails("call remove(l, 3, 2)", 'E16:')
- call assert_fails("call remove(1, 0)", 'E896:')
- call assert_fails("call remove(l, l)", 'E745:')
-endfunc
-
-" List add() function
-func Test_list_add()
- let l = []
- call add(l, 1)
- call add(l, [2, 3])
- call add(l, [])
- call add(l, v:_null_list)
- call add(l, {'k' : 3})
- call add(l, {})
- call add(l, v:_null_dict)
- call assert_equal([1, [2, 3], [], [], {'k' : 3}, {}, {}], l)
- " call assert_equal(1, add(v:_null_list, 4))
-endfunc
-
-" Tests for Dictionary type
-
-func Test_dict()
- " Creating Dictionary directly with different types
- let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
- call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
- call assert_equal('asd', d.1)
- call assert_equal(['-1', '1', 'b'], sort(keys(d)))
- call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d))
- let v = []
- for [key, val] in items(d)
- call extend(v, [key, val])
- unlet key val
- endfor
- call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v)
-
- call extend(d, {3:33, 1:99})
- call extend(d, {'b':'bbb', 'c':'ccc'}, "keep")
- call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737')
- call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d)
- call filter(d, 'v:key =~ ''[ac391]''')
- call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d)
-
- " duplicate key
- call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:')
- " missing comma
- call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:')
- " missing curly brace
- call assert_fails("let d = {'k' : 10,", 'E723:')
- " invalid key
- call assert_fails('let d = #{++ : 10}', 'E15:')
- " wrong type for key
- call assert_fails('let d={[] : 10}', 'E730:')
- " undefined variable as value
- call assert_fails("let d={'k' : i}", 'E121:')
-
- " allow key starting with number at the start, not a curly expression
- call assert_equal({'1foo': 77}, #{1foo: 77})
-
- " #{expr} is not a curly expression
- let x = 'x'
- call assert_equal(#{g: x}, #{g:x})
-endfunc
-
-" Dictionary identity
-func Test_dict_identity()
- let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
- let dd = d
- let dx = copy(d)
- call assert_true(d == dd)
- call assert_false(d isnot dd)
- call assert_true(d is dd)
- call assert_true(d == dx)
- call assert_false(d is dx)
- call assert_true(d isnot dx)
-endfunc
-
-" removing items with :unlet
-func Test_dict_unlet()
- let d = {'b':'bbb', '1': 99, '3': 33, '-1': {'a': 1}}
- unlet d.b
- unlet d[-1]
- call assert_equal({'1': 99, '3': 33}, d)
-endfunc
-
-" manipulating a big Dictionary (hashtable.c has a border of 1000 entries)
-func Test_dict_big()
- let d = {}
- for i in range(1500)
- let d[i] = 3000 - i
- endfor
- call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]])
- let str = ''
- try
- let n = d[1500]
- catch
- let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '')
- endtry
- call assert_equal('Vim(let):E716: "1500"', str)
-
- " lookup each items
- for i in range(1500)
- call assert_equal(3000 - i, d[i])
- endfor
- let i += 1
-
- " delete even items
- while i >= 2
- let i -= 2
- unlet d[i]
- endwhile
- call assert_equal('NONE', get(d, 1500 - 100, 'NONE'))
- call assert_equal(2999, d[1])
-
- " delete odd items, checking value, one intentionally wrong
- let d[33] = 999
- let i = 1
- while i < 1500
- if i != 33
- call assert_equal(3000 - i, d[i])
- else
- call assert_equal(999, d[i])
- endif
- unlet d[i]
- let i += 2
- endwhile
- call assert_equal({}, d)
- unlet d
-endfunc
-
-" Dictionary function
-func Test_dict_func()
- let d = {}
- func d.func(a) dict
- return a:a . len(self.data)
- endfunc
- let d.data = [1,2,3]
- call assert_equal('len: 3', d.func('len: '))
- let x = d.func('again: ')
- call assert_equal('again: 3', x)
- let Fn = d.func
- call assert_equal('xxx3', Fn('xxx'))
-endfunc
-
-func Test_dict_assign()
- let d = {}
- let d.1 = 1
- let d._ = 2
- call assert_equal({'1': 1, '_': 2}, d)
-
- let n = 0
- call assert_fails('let n.key = 3', 'E1203: Dot can only be used on a dictionary: n.key = 3')
-endfunc
-
-" Function in script-local List or Dict
-func Test_script_local_dict_func()
- let g:dict = {}
- function g:dict.func() dict
- return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf')
- endfunc
- let g:dict.foo = ['-', 2, 3]
- call insert(g:dict.foo, function('strlen'))
- call assert_equal('g:dict.func-4', g:dict.func())
- unlet g:dict
-endfunc
-
-" Test removing items in a dictionary
-func Test_dict_func_remove()
- let d = {1:'a', 2:'b', 3:'c'}
- call assert_equal('b', remove(d, 2))
- call assert_equal({1:'a', 3:'c'}, d)
-
- call assert_fails("call remove(d, 1, 2)", 'E118:')
- call assert_fails("call remove(d, 'a')", 'E716:')
- call assert_fails("call remove(d, [])", 'E730:')
-endfunc
-
-" Nasty: remove func from Dict that's being called (works)
-func Test_dict_func_remove_in_use()
- let d = {1:1}
- func d.func(a)
- return "a:" . a:a
- endfunc
- let expected = 'a:' . string(get(d, 'func'))
- call assert_equal(expected, d.func(string(remove(d, 'func'))))
-endfunc
-
-func Test_dict_literal_keys()
- call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
-
- " why *{} cannot be used
- let blue = 'blue'
- call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
-endfunc
-
-" Nasty: deepcopy() dict that refers to itself (fails when noref used)
-func Test_dict_deepcopy()
- let d = {1:1, 2:2}
- let l = [4, d, 6]
- let d[3] = l
- let dc = deepcopy(d)
- call assert_fails('call deepcopy(d, 1)', 'E698:')
- let l2 = [0, l, l, 3]
- let l[1] = l2
- let l3 = deepcopy(l2)
- call assert_true(l3[1] is l3[2])
- call assert_fails("call deepcopy([1, 2], 2)", 'E1023:')
-endfunc
-
-" Locked variables
-func Test_list_locked_var()
- let expected = [
- \ [['1000-000', 'ppppppF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1000-000', 'ppppppF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1100-100', 'ppFppFF'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1110-110', 'pFFpFFF'],
- \ ['0010-010', 'pFppFpp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1111-111', 'FFFFFFF'],
- \ ['0011-011', 'FFpFFpp'],
- \ ['0000-000', 'ppppppp']]
- \ ]
- for depth in range(5)
- for u in range(3)
- unlet! l
- let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
- exe "lockvar " . depth . " l"
- if u == 1
- exe "unlockvar l"
- elseif u == 2
- exe "unlockvar " . depth . " l"
- endif
- let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
- call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
- let ps = ''
- try
- let l[1][1][0] = 99
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[1][1] = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[1] = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2]['6'][7] = 99
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2][6] = {99: 99}
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l[2] = {99: 99}
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- let l = [99]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth)
- endfor
- endfor
- call assert_fails("let x=islocked('a b')", 'E488:')
- let mylist = [1, 2, 3]
- call assert_fails("let x = islocked('mylist[1:2]')", 'E786:')
- let mydict = {'k' : 'v'}
- call assert_fails("let x = islocked('mydict.a')", 'E716:')
-endfunc
-
-" Unletting locked variables
-func Test_list_locked_var_unlet()
- let expected = [
- \ [['1000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1000-000', 'ppFppFp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1100-100', 'pFFpFFp'],
- \ ['0000-000', 'ppppppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1110-110', 'FFFFFFp'],
- \ ['0010-010', 'FppFppp'],
- \ ['0000-000', 'ppppppp']],
- \ [['1111-111', 'FFFFFFp'],
- \ ['0011-011', 'FppFppp'],
- \ ['0000-000', 'ppppppp']]
- \ ]
-
- for depth in range(5)
- for u in range(3)
- unlet! l
- let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
- exe "lockvar " . depth . " l"
- if u == 1
- exe "unlockvar l"
- elseif u == 2
- exe "unlockvar " . depth . " l"
- endif
- let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
- call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
- let ps = ''
- try
- unlet l[2]['6'][7]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[2][6]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[2]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1][1][0]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1][1]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l[1]
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- try
- unlet l
- let ps .= 'p'
- catch
- let ps .= 'F'
- endtry
- call assert_equal(expected[depth][u][1], ps)
- endfor
- endfor
- " Deleting a list range should fail if the range is locked
- let l = [1, 2, 3, 4]
- lockvar l[1:2]
- call assert_fails('unlet l[1:2]', 'E741:')
- unlet l
-endfunc
-
-" Locked variables and :unlet or list / dict functions
-
-" No :unlet after lock on dict:
-func Test_dict_lock_unlet()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar 1 d
- call assert_fails('unlet d.a', 'E741')
-endfunc
-
-" unlet after lock on dict item
-func Test_dict_item_lock_unlet()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- unlet d.a
- call assert_equal({'b' : 100}, d)
-endfunc
-
-" filter() after lock on dict item
-func Test_dict_lock_filter()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- call filter(d, 'v:key != "a"')
- call assert_equal({'b' : 100}, d)
-endfunc
-
-" map() after lock on dict
-func Test_dict_lock_map()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar 1 d
- call map(d, 'v:val + 200')
- call assert_equal({'a' : 299, 'b' : 300}, d)
-endfunc
-
-" No extend() after lock on dict item
-func Test_dict_lock_extend()
- unlet! d
- let d = {'a': 99, 'b': 100}
- lockvar d.a
- call assert_fails("call extend(d, {'a' : 123})", 'E741')
- call assert_equal({'a': 99, 'b': 100}, d)
-endfunc
-
-" Cannot use += with a locked dict
-func Test_dict_lock_operator()
- unlet! d
- let d = {}
- lockvar d
- call assert_fails("let d += {'k' : 10}", 'E741:')
- unlockvar d
-endfunc
-
-" No remove() of write-protected scope-level variable
-func Tfunc1(this_is_a_long_parameter_name)
- call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742')
-endfunc
-func Test_dict_scope_var_remove()
- 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 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 Tfunc2('testval')
-endfunc
-
-" No :unlet of variable in locked scope
-func Test_lock_var_unlet()
- let b:testvar = 123
- lockvar 1 b:
- call assert_fails('unlet b:testvar', 'E741:')
- unlockvar 1 b:
- unlet! b:testvar
-endfunc
-
-" No :let += of locked list variable
-func Test_let_lock_list()
- let l = ['a', 'b', 3]
- lockvar 1 l
- call assert_fails("let l += ['x']", 'E741:')
- call assert_equal(['a', 'b', 3], l)
-
- unlet l
- let l = [1, 2, 3, 4]
- lockvar! l
- call assert_equal([1, 2, 3, 4], l)
- unlockvar l[1]
- call assert_fails('unlet l[0:1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- call assert_fails('unlet l[1:2]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- unlockvar l[1]
- call assert_fails('let l[0:1] = [0, 1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- call assert_fails('let l[1:2] = [0, 1]', 'E741:')
- call assert_equal([1, 2, 3, 4], l)
- unlet l
-endfunc
-
-" Locking part of the list
-func Test_let_lock_list_items()
- let l = [1, 2, 3, 4]
- lockvar l[2:]
- call assert_equal(0, islocked('l[0]'))
- call assert_equal(1, islocked('l[2]'))
- call assert_equal(1, islocked('l[3]'))
- call assert_fails('let l[2] = 10', 'E741:')
- call assert_fails('let l[3] = 20', 'E741:')
- unlet l
-endfunc
-
-" lockvar/islocked() triggering script autoloading
-func Test_lockvar_script_autoload()
- let old_rtp = &rtp
- set rtp+=./sautest
- lockvar g:footest#x
- unlockvar g:footest#x
- call assert_equal(-1, 'g:footest#x'->islocked())
- call assert_equal(0, exists('g:footest#x'))
- call assert_equal(1, g:footest#x)
- let &rtp = old_rtp
-endfunc
-
-" a:000 function argument test
-func s:arg_list_test(...)
- call assert_fails('let a:000 = [1, 2]', 'E46:')
- call assert_fails('let a:000[0] = 9', 'E742:')
- call assert_fails('let a:000[2] = [9, 10]', 'E742:')
- call assert_fails('let a:000[3] = {9 : 10}', 'E742:')
-
- " now the tests that should pass
- let a:000[2][1] = 9
- call extend(a:000[2], [5, 6])
- let a:000[3][5] = 8
- let a:000[3]['a'] = 12
- call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000)
-endfunc
-
-func Test_func_arg_list()
- call s:arg_list_test(1, 2, [3, 4], {5: 6})
-endfunc
-
-func Test_dict_item_locked()
-endfunc
-
-" Tests for reverse(), sort(), uniq()
-func Test_reverse_sort_uniq()
- let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5]
- call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l)))
- call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l))
- call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l)))
- if has('float')
- call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l))
- call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l)))
- call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l))))
- call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l)))
-
- let l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four']
- call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n'))
-
- let l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []]
- call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1))
- call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
- call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
- endif
-
- call assert_fails('call reverse("")', 'E899:')
- call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
- call assert_fails("call sort([1, 2], function('min'), 1)", "E715:")
- call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
- call assert_fails("call sort([1, 2], function('min'))", "E118:")
-endfunc
-
-" reduce a list or a blob
-func Test_reduce()
- call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
- call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
- call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
- call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
- call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
- call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
-
- let l = ['x', 'y', 'z']
- call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
- call assert_equal(['x', 'y', 'z'], l)
-
- call assert_equal(1, reduce([1], { acc, val -> acc + val }))
- call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
- call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
- call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
-
- call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
- call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
- call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
-
- call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
- call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
- call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
-
- call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
- call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
- call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E39:')
-
- let g:lut = [1, 2, 3, 4]
- func EvilRemove()
- call remove(g:lut, 1)
- return 1
- endfunc
- call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
- unlet g:lut
- delfunc EvilRemove
-
- call assert_equal(42, reduce(v:_null_list, function('add'), 42))
- call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
-endfunc
-
-" splitting a string to a List using split()
-func Test_str_split()
- call assert_equal(['aa', 'bb'], split(' aa bb '))
- call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0))
- call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1))
- call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1))
- call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0))
- call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1))
- call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1))
- call assert_equal(['a', 'b', 'c'], split('abc', '\zs'))
- call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1))
- call assert_fails("call split('abc', [])", 'E730:')
- call assert_fails("call split('abc', 'b', [])", 'E745:')
- call assert_equal(['abc'], split('abc', '\\%('))
-endfunc
-
-" compare recursively linked list and dict
-func Test_listdict_compare()
- let l = [1, 2, 3, 4]
- let d = {'1': 1, '2': l, '3': 3}
- let l[1] = d
- call assert_true(l == l)
- call assert_true(d == d)
- call assert_false(l != deepcopy(l))
- call assert_false(d != deepcopy(d))
-
- " comparison errors
- call assert_fails('echo [1, 2] =~ {}', 'E691:')
- call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:')
- call assert_fails('echo {} =~ 5', 'E735:')
- call assert_fails('echo {} =~ {}', 'E736:')
-endfunc
-
- " compare complex recursively linked list and dict
-func Test_listdict_compare_complex()
- let l = []
- call add(l, l)
- let dict4 = {"l": l}
- call add(dict4.l, dict4)
- let lcopy = deepcopy(l)
- let dict4copy = deepcopy(dict4)
- call assert_true(l == lcopy)
- call assert_true(dict4 == dict4copy)
-endfunc
-
-func Test_listdict_extend()
- " Test extend() with lists
-
- " Pass the same List to extend()
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3, 1, 2, 3], extend(l, l))
- call assert_equal([1, 2, 3, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call assert_equal([1, 2, 3, 4, 5, 6], extend(l, [4, 5, 6]))
- call assert_equal([1, 2, 3, 4, 5, 6], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 0)
- call assert_equal([4, 5, 6, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 1)
- call assert_equal([1, 4, 5, 6, 2, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], 3)
- call assert_equal([1, 2, 3, 4, 5, 6], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], -1)
- call assert_equal([1, 2, 4, 5, 6, 3], l)
-
- let l = [1, 2, 3]
- call extend(l, [4, 5, 6], -3)
- call assert_equal([4, 5, 6, 1, 2, 3], l)
-
- let l = [1, 2, 3]
- call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
- call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
- if has('float')
- call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
- endif
-
- " Test extend() with dictionaries.
-
- " Pass the same Dict to extend()
- let d = { 'a': {'b': 'B'}}
- call extend(d, d)
- call assert_equal({'a': {'b': 'B'}}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, extend(d, {'b': 0, 'c':'C'}))
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call extend(d, {'a': 'A', 'b': 0, 'c': 'C'}, "force")
- call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call extend(d, {'b': 0, 'c':'C'}, "keep")
- call assert_equal({'a': 'A', 'b': 'B', 'c': 'C'}, d)
-
- let d = {'a': 'A', 'b': 'B'}
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
- if has('float')
- call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E806:')
- endif
- call assert_equal({'a': 'A', 'b': 'B'}, d)
-
- call assert_fails("call extend([1, 2], 1)", 'E712:')
- call assert_fails("call extend([1, 2], {})", 'E712:')
-
- " Extend g: dictionary with an invalid variable name
- call assert_fails("call extend(g:, {'-!' : 10})", 'E461:')
-
- " Extend a list with itself.
- let l = [1, 5, 7]
- call extend(l, l, 0)
- call assert_equal([1, 5, 7, 1, 5, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 1)
- call assert_equal([1, 1, 5, 7, 5, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 2)
- call assert_equal([1, 5, 1, 5, 7, 7], l)
- let l = [1, 5, 7]
- call extend(l, l, 3)
- call assert_equal([1, 5, 7, 1, 5, 7], l)
-endfunc
-
-func s:check_scope_dict(x, fixed)
- func s:gen_cmd(cmd, x)
- return substitute(a:cmd, '\<x\ze:', a:x, 'g')
- endfunc
-
- let cmd = s:gen_cmd('let x:foo = 1', a:x)
- if a:fixed
- call assert_fails(cmd, 'E461')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(1, x:foo)', a:x)
- endif
-
- let cmd = s:gen_cmd('let x:["bar"] = 2', a:x)
- if a:fixed
- call assert_fails(cmd, 'E461')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(2, x:bar)', a:x)
- endif
-
- let cmd = s:gen_cmd('call extend(x:, {"baz": 3})', a:x)
- if a:fixed
- call assert_fails(cmd, 'E742')
- else
- exe cmd
- exe s:gen_cmd('call assert_equal(3, x:baz)', a:x)
- endif
-
- if a:fixed
- if a:x ==# 'a'
- call assert_fails('unlet a:x', 'E795')
- call assert_fails('call remove(a:, "x")', 'E742')
- elseif a:x ==# 'v'
- call assert_fails('unlet v:count', 'E795')
- call assert_fails('call remove(v:, "count")', 'E742')
- endif
- else
- exe s:gen_cmd('unlet x:foo', a:x)
- exe s:gen_cmd('unlet x:bar', a:x)
- exe s:gen_cmd('call remove(x:, "baz")', a:x)
- endif
-
- delfunc s:gen_cmd
-endfunc
-
-func Test_scope_dict()
- " Test for g:
- call s:check_scope_dict('g', v:false)
-
- " Test for s:
- call s:check_scope_dict('s', v:false)
-
- " Test for l:
- call s:check_scope_dict('l', v:false)
-
- " Test for a:
- call s:check_scope_dict('a', v:true)
-
- " Test for b:
- call s:check_scope_dict('b', v:false)
-
- " Test for w:
- call s:check_scope_dict('w', v:false)
-
- " Test for t:
- call s:check_scope_dict('t', v:false)
-
- " Test for v:
- call s:check_scope_dict('v', v:true)
-endfunc
-
-" Test for deep nesting of lists (> 100)
-func Test_deep_nested_list()
- let deep_list = []
- let l = deep_list
- for i in range(102)
- let newlist = []
- call add(l, newlist)
- let l = newlist
- endfor
- call add(l, 102)
-
- call assert_fails('let m = deepcopy(deep_list)', 'E698:')
- call assert_fails('lockvar 110 deep_list', 'E743:')
- call assert_fails('unlockvar 110 deep_list', 'E743:')
- " Nvim implements :echo very differently
- " call assert_fails('let x = execute("echo deep_list")', 'E724:')
- call test_garbagecollect_now()
- unlet deep_list
-endfunc
-
-" Test for deep nesting of dicts (> 100)
-func Test_deep_nested_dict()
- let deep_dict = {}
- let d = deep_dict
- for i in range(102)
- let newdict = {}
- let d.k = newdict
- let d = newdict
- endfor
- let d.k = 'v'
-
- call assert_fails('let m = deepcopy(deep_dict)', 'E698:')
- call assert_fails('lockvar 110 deep_dict', 'E743:')
- call assert_fails('unlockvar 110 deep_dict', 'E743:')
- " Nvim implements :echo very differently
- " call assert_fails('let x = execute("echo deep_dict")', 'E724:')
- call test_garbagecollect_now()
- unlet deep_dict
-endfunc
-
-" List and dict indexing tests
-func Test_listdict_index()
- call assert_fails('echo function("min")[0]', 'E695:')
- call assert_fails('echo v:true[0]', 'E909:')
- let d = {'k' : 10}
- call assert_fails('echo d.', 'E15:')
- call assert_fails('echo d[1:2]', 'E719:')
- call assert_fails("let v = [4, 6][{-> 1}]", 'E729:')
- call assert_fails("let v = range(5)[2:[]]", 'E730:')
- call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:'])
- call assert_fails("let v = range(5)[2:3", 'E111:')
- call assert_fails("let l = insert([1,2,3], 4, 10)", 'E684:')
- call assert_fails("let l = insert([1,2,3], 4, -10)", 'E684:')
- call assert_fails("let l = insert([1,2,3], 4, [])", 'E745:')
- let l = [1, 2, 3]
- call assert_fails("let l[i] = 3", 'E121:')
- call assert_fails("let l[1.1] = 4", 'E806:')
- call assert_fails("let l[:i] = [4, 5]", 'E121:')
- call assert_fails("let l[:3.2] = [4, 5]", 'E806:')
-endfunc
-
-" Test for a null list
-func Test_null_list()
- let l = v:_null_list
- call assert_equal('', join(l))
- call assert_equal(0, len(l))
- call assert_equal(1, empty(l))
- call assert_fails('let s = join([1, 2], [])', 'E730:')
- call assert_equal([], split(v:_null_string))
- call assert_equal([], l[:2])
- call assert_true([] == l)
- call assert_equal('[]', string(l))
- " call assert_equal(0, sort(l))
- " call assert_equal(0, sort(l))
- " call assert_equal(0, uniq(l))
- let k = [] + l
- call assert_equal([], k)
- let k = l + []
- call assert_equal([], k)
- call assert_equal(0, len(copy(l)))
- call assert_equal(0, count(l, 5))
- call assert_equal([], deepcopy(l))
- call assert_equal(5, get(l, 2, 5))
- call assert_equal(-1, index(l, 2, 5))
- " call assert_equal(0, insert(l, 2, -1))
- call assert_equal(0, min(l))
- call assert_equal(0, max(l))
- " call assert_equal(0, remove(l, 0, 2))
- call assert_equal([], repeat(l, 2))
- " call assert_equal(0, reverse(l))
- " call assert_equal(0, sort(l))
- call assert_equal('[]', string(l))
- " call assert_equal(0, extend(l, l, 0))
- lockvar l
- call assert_equal(1, islocked('l'))
- unlockvar l
-endfunc
-
-" Test for a null dict
-func Test_null_dict()
- call assert_equal(v:_null_dict, v:_null_dict)
- let d = v:_null_dict
- call assert_equal({}, d)
- call assert_equal(0, len(d))
- call assert_equal(1, empty(d))
- call assert_equal(0, items(d))
- call assert_equal(0, keys(d))
- call assert_equal(0, values(d))
- call assert_false(has_key(d, 'k'))
- call assert_equal('{}', string(d))
- call assert_fails('let x = v:_null_dict[10]')
- call assert_equal({}, {})
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim
deleted file mode 100644
index f903f5b934..0000000000
--- a/src/nvim/testdir/test_maparg.vim
+++ /dev/null
@@ -1,325 +0,0 @@
-" Tests for maparg(), mapcheck() and mapset().
-" Also test utf8 map with a 0x80 byte.
-" Also test mapcheck()
-
-func s:SID()
- return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$'))
-endfunc
-
-func Test_maparg()
- new
- set cpo-=<
- set encoding=utf8
- " Test maparg() with a string result
- let sid = s:SID()
- let lnum = expand('<sflnum>')
- map foo<C-V> is<F4>foo
- vnoremap <script> <buffer> <expr> <silent> bar isbar
- call assert_equal("is<F4>foo", maparg('foo<C-V>'))
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
- \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
- \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1,
- \ 'rhs': 'is<F4>foo', 'buffer': 0},
- \ maparg('foo<C-V>', '', 0, 1))
- call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
- \ 'lhsraw': 'bar', 'mode': 'v',
- \ 'nowait': 0, 'expr': 1, 'sid': sid, 'lnum': lnum + 2,
- \ 'rhs': 'isbar', 'buffer': 1},
- \ 'bar'->maparg('', 0, 1))
- let lnum = expand('<sflnum>')
- map <buffer> <nowait> foo bar
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo',
- \ 'lhsraw': 'foo', 'mode': ' ',
- \ 'nowait': 1, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'bar',
- \ 'buffer': 1},
- \ maparg('foo', '', 0, 1))
- let lnum = expand('<sflnum>')
- tmap baz foo
- call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz',
- \ 'lhsraw': 'baz', 'mode': 't',
- \ 'nowait': 0, 'expr': 0, 'sid': sid, 'lnum': lnum + 1, 'rhs': 'foo',
- \ 'buffer': 0},
- \ maparg('baz', 't', 0, 1))
-
- map abc x<char-114>x
- call assert_equal("xrx", maparg('abc'))
- map abc y<S-char-114>y
- call assert_equal("yRy", maparg('abc'))
-
- " character with K_SPECIAL byte
- nmap abc …
- call assert_equal('…', maparg('abc'))
-
- " modified character with K_SPECIAL byte
- nmap abc <M-…>
- call assert_equal('<M-…>', maparg('abc'))
-
- " illegal bytes
- let str = ":\x7f:\x80:\x90:\xd0:"
- exe 'nmap abc ' .. str
- call assert_equal(str, maparg('abc'))
- unlet str
-
- omap { w
- let d = maparg('{', 'o', 0, 1)
- call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
- ounmap {
-
- lmap { w
- let d = maparg('{', 'l', 0, 1)
- call assert_equal(['{', 'w', 'l'], [d.lhs, d.rhs, d.mode])
- lunmap {
-
- nmap { w
- let d = maparg('{', 'n', 0, 1)
- call assert_equal(['{', 'w', 'n'], [d.lhs, d.rhs, d.mode])
- nunmap {
-
- xmap { w
- let d = maparg('{', 'x', 0, 1)
- call assert_equal(['{', 'w', 'x'], [d.lhs, d.rhs, d.mode])
- xunmap {
-
- smap { w
- let d = maparg('{', 's', 0, 1)
- call assert_equal(['{', 'w', 's'], [d.lhs, d.rhs, d.mode])
- sunmap {
-
- map <C-I> foo
- unmap <Tab>
- " This used to cause a segfault
- call maparg('<C-I>', '', 0, 1)
- unmap <C-I>
-
- map abc <Nop>
- call assert_equal("<Nop>", maparg('abc'))
- unmap abc
-
- call feedkeys(":abbr esc \<C-V>\<C-V>\<C-V>\<C-V>\<C-V>\<Esc>\<CR>", "xt")
- let d = maparg('esc', 'i', 1, 1)
- call assert_equal(['esc', "\<C-V>\<C-V>\<Esc>", '!'], [d.lhs, d.rhs, d.mode])
- abclear
- unlet d
-endfunc
-
-func Test_mapcheck()
- call assert_equal('', mapcheck('a'))
- call assert_equal('', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
-
- map a something
- call assert_equal('something', mapcheck('a'))
- call assert_equal('something', mapcheck('a', 'n'))
- call assert_equal('', mapcheck('a', 'c'))
- call assert_equal('', mapcheck('a', 'i'))
- call assert_equal('something', 'abc'->mapcheck())
- call assert_equal('something', 'ax'->mapcheck())
- call assert_equal('', mapcheck('b'))
- unmap a
-
- map ab foobar
- call assert_equal('foobar', mapcheck('a'))
- call assert_equal('foobar', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
- unmap ab
-
- map abc barfoo
- call assert_equal('barfoo', mapcheck('a'))
- call assert_equal('barfoo', mapcheck('a', 'n', 0))
- call assert_equal('', mapcheck('a', 'n', 1))
- call assert_equal('barfoo', mapcheck('abc'))
- call assert_equal('', mapcheck('ax'))
- call assert_equal('', mapcheck('b'))
- unmap abc
-
- abbr ab abbrev
- call assert_equal('abbrev', mapcheck('a', 'i', 1))
- call assert_equal('', mapcheck('a', 'n', 1))
- call assert_equal('', mapcheck('a', 'i', 0))
- unabbr ab
-endfunc
-
-func Test_range_map()
- new
- " Outside of the range, minimum
- inoremap <Char-0x1040> a
- execute "normal a\u1040\<Esc>"
- " Inside of the range, minimum
- inoremap <Char-0x103f> b
- execute "normal a\u103f\<Esc>"
- " Inside of the range, maximum
- inoremap <Char-0xf03f> c
- execute "normal a\uf03f\<Esc>"
- " Outside of the range, maximum
- inoremap <Char-0xf040> d
- execute "normal a\uf040\<Esc>"
- call assert_equal("abcd", getline(1))
-endfunc
-
-func One_mapset_test(keys, rhs)
- exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs
- let orig = maparg(a:keys, 'n', 0, 1)
- call assert_equal(a:keys, orig.lhs)
- call assert_equal(a:rhs, orig.rhs)
- call assert_equal('n', orig.mode)
-
- exe 'nunmap ' .. a:keys
- let d = maparg(a:keys, 'n', 0, 1)
- call assert_equal({}, d)
-
- call mapset('n', 0, orig)
- let d = maparg(a:keys, 'n', 0, 1)
- call assert_equal(a:keys, d.lhs)
- call assert_equal(a:rhs, d.rhs)
- call assert_equal('n', d.mode)
-
- exe 'nunmap ' .. a:keys
-endfunc
-
-func Test_mapset()
- call One_mapset_test('K', 'original<CR>')
- call One_mapset_test('<F3>', 'original<CR>')
- call One_mapset_test('<F3>', '<lt>Nop>')
-
- " Check <> key conversion
- new
- inoremap K one<Left>x
- call feedkeys("iK\<Esc>", 'xt')
- call assert_equal('onxe', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('one<Left>x', orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('onxe', getline(1))
-
- iunmap K
-
- " Test that <Nop> is restored properly
- inoremap K <Nop>
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('<Nop>', orig.rhs)
- call assert_equal('i', orig.mode)
-
- inoremap K foo
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('foo', getline(1))
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('', getline(1))
-
- iunmap K
-
- " Test literal <CR> using a backslash
- let cpo_save = &cpo
- set cpo-=B
- inoremap K one\<CR>two
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal('one\<CR>two', orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- iunmap K
-
- " Test literal <CR> using CTRL-V
- inoremap K one<CR>two
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- let orig = maparg('K', 'i', 0, 1)
- call assert_equal('K', orig.lhs)
- call assert_equal("one\x16<CR>two", orig.rhs)
- call assert_equal('i', orig.mode)
-
- iunmap K
- let d = maparg('K', 'i', 0, 1)
- call assert_equal({}, d)
-
- call mapset('i', 0, orig)
- call feedkeys("SK\<Esc>", 'xt')
- call assert_equal('one<CR>two', getline(1))
-
- iunmap K
- let &cpo = cpo_save
- bwipe!
-
- call assert_fails('call mapset([], v:false, {})', 'E730:')
- call assert_fails('call mapset("i", 0, "")', 'E715:')
- call assert_fails('call mapset("i", 0, {})', 'E460:')
-endfunc
-
-func Check_ctrlb_map(d, check_alt)
- call assert_equal('<C-B>', a:d.lhs)
- if a:check_alt
- call assert_equal("\x80\xfc\x04B", a:d.lhsraw)
- call assert_equal("\x02", a:d.lhsrawalt)
- else
- call assert_equal("\x02", a:d.lhsraw)
- endif
-endfunc
-
-func Test_map_local()
- nmap a global
- nmap <buffer>a local
-
- let prev_map_list = split(execute('nmap a'), "\n")
- call assert_match('n\s*a\s*@local', prev_map_list[0])
- call assert_match('n\s*a\s*global', prev_map_list[1])
-
- let mapping = maparg('a', 'n', 0, 1)
- call assert_equal(1, mapping.buffer)
- let mapping.rhs = 'new_local'
- call mapset('n', 0, mapping)
-
- " Check that the global mapping is left untouched.
- let map_list = split(execute('nmap a'), "\n")
- call assert_match('n\s*a\s*@new_local', map_list[0])
- call assert_match('n\s*a\s*global', map_list[1])
-
- nunmap a
-endfunc
-
-func Test_map_restore()
- " Test restoring map with alternate keycode
- nmap <C-B> back
- let d = maparg('<C-B>', 'n', 0, 1)
- call Check_ctrlb_map(d, 1)
- let dsimp = maparg("\x02", 'n', 0, 1)
- call Check_ctrlb_map(dsimp, 0)
- nunmap <C-B>
- call mapset('n', 0, d)
- let d = maparg('<C-B>', 'n', 0, 1)
- call Check_ctrlb_map(d, 1)
- let dsimp = maparg("\x02", 'n', 0, 1)
- call Check_ctrlb_map(dsimp, 0)
-
- nunmap <C-B>
-
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
deleted file mode 100644
index f51de94bac..0000000000
--- a/src/nvim/testdir/test_options.vim
+++ /dev/null
@@ -1,1304 +0,0 @@
-" Test for options
-
-source check.vim
-source view_util.vim
-
-func Test_whichwrap()
- set whichwrap=b,s
- call assert_equal('b,s', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap+=h,l
- call assert_equal('b,s,h,l', &whichwrap)
-
- set whichwrap=h,h
- call assert_equal('h', &whichwrap)
-
- set whichwrap=h,h,h
- call assert_equal('h', &whichwrap)
-
- " For compatibility with Vim 3.0 and before, number values are also
- " supported for 'whichwrap'
- set whichwrap=1
- call assert_equal('b', &whichwrap)
- set whichwrap=2
- call assert_equal('s', &whichwrap)
- set whichwrap=4
- call assert_equal('h,l', &whichwrap)
- set whichwrap=8
- call assert_equal('<,>', &whichwrap)
- set whichwrap=16
- call assert_equal('[,]', &whichwrap)
- set whichwrap=31
- call assert_equal('b,s,h,l,<,>,[,]', &whichwrap)
-
- set whichwrap&
-endfunc
-
-func Test_isfname()
- " This used to cause Vim to access uninitialized memory.
- set isfname=
- call assert_equal("~X", expand("~X"))
- set isfname&
-endfunc
-
-" Test for getting the value of 'pastetoggle'
-func Test_pastetoggle()
- " character with K_SPECIAL byte
- let &pastetoggle = '…'
- call assert_equal('…', &pastetoggle)
- call assert_equal("\n pastetoggle=…", execute('set pastetoggle?'))
-
- " modified character with K_SPECIAL byte
- let &pastetoggle = '<M-…>'
- call assert_equal('<M-…>', &pastetoggle)
- call assert_equal("\n pastetoggle=<M-…>", execute('set pastetoggle?'))
-
- " illegal bytes
- let str = ":\x7f:\x80:\x90:\xd0:"
- let &pastetoggle = str
- call assert_equal(str, &pastetoggle)
- call assert_equal("\n pastetoggle=" .. strtrans(str), execute('set pastetoggle?'))
-
- unlet str
- set pastetoggle&
-endfunc
-
-func Test_wildchar()
- " Empty 'wildchar' used to access invalid memory.
- call assert_fails('set wildchar=', 'E521:')
- call assert_fails('set wildchar=abc', 'E521:')
- set wildchar=<Esc>
- let a=execute('set wildchar?')
- call assert_equal("\n wildchar=<Esc>", a)
- set wildchar=27
- let a=execute('set wildchar?')
- call assert_equal("\n wildchar=<Esc>", a)
- set wildchar&
-endfunc
-
-func Test_wildoptions()
- set wildoptions=
- set wildoptions+=tagfile
- set wildoptions+=tagfile
- call assert_equal('tagfile', &wildoptions)
-endfunc
-
-func Test_options_command()
- let caught = 'ok'
- try
- options
- catch
- let caught = v:throwpoint . "\n" . v:exception
- endtry
- call assert_equal('ok', caught)
-
- " Check if the option-window is opened horizontally.
- wincmd j
- call assert_notequal('option-window', bufname(''))
- wincmd k
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the option-window vertically.
- vert options
- " Check if the option-window is opened vertically.
- wincmd l
- call assert_notequal('option-window', bufname(''))
- wincmd h
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the option-window at the top.
- set splitbelow
- topleft options
- call assert_equal(1, winnr())
- close
-
- " Open the option-window at the bottom.
- set nosplitbelow
- botright options
- call assert_equal(winnr('$'), winnr())
- close
- set splitbelow&
-
- " Open the option-window in a new tab.
- tab options
- " Check if the option-window is opened in a tab.
- normal gT
- call assert_notequal('option-window', bufname(''))
- normal gt
- call assert_equal('option-window', bufname(''))
- " close option-window
- close
-
- " Open the options window browse
- if has('browse')
- browse set
- call assert_equal('option-window', bufname(''))
- close
- endif
-endfunc
-
-func Test_path_keep_commas()
- " Test that changing 'path' keeps two commas.
- set path=foo,,bar
- set path-=bar
- set path+=bar
- call assert_equal('foo,,bar', &path)
-
- set path&
-endfunc
-
-func Test_path_too_long()
- exe 'set path=' .. repeat('x', 10000)
- call assert_fails('find x', 'E854:')
- set path&
-endfunc
-
-func Test_signcolumn()
- CheckFeature signs
- call assert_equal("auto", &signcolumn)
- set signcolumn=yes
- set signcolumn=no
- call assert_fails('set signcolumn=nope')
-endfunc
-
-func Test_filetype_valid()
- set ft=valid_name
- call assert_equal("valid_name", &filetype)
- set ft=valid-name
- call assert_equal("valid-name", &filetype)
-
- call assert_fails(":set ft=wrong;name", "E474:")
- call assert_fails(":set ft=wrong\\\\name", "E474:")
- call assert_fails(":set ft=wrong\\|name", "E474:")
- call assert_fails(":set ft=wrong/name", "E474:")
- call assert_fails(":set ft=wrong\\\nname", "E474:")
- call assert_equal("valid-name", &filetype)
-
- exe "set ft=trunc\x00name"
- call assert_equal("trunc", &filetype)
-endfunc
-
-func Test_syntax_valid()
- if !has('syntax')
- return
- endif
- set syn=valid_name
- call assert_equal("valid_name", &syntax)
- set syn=valid-name
- call assert_equal("valid-name", &syntax)
-
- call assert_fails(":set syn=wrong;name", "E474:")
- call assert_fails(":set syn=wrong\\\\name", "E474:")
- call assert_fails(":set syn=wrong\\|name", "E474:")
- call assert_fails(":set syn=wrong/name", "E474:")
- call assert_fails(":set syn=wrong\\\nname", "E474:")
- call assert_equal("valid-name", &syntax)
-
- exe "set syn=trunc\x00name"
- call assert_equal("trunc", &syntax)
-endfunc
-
-func Test_keymap_valid()
- if !has('keymap')
- return
- endif
- call assert_fails(":set kmp=valid_name", "E544:")
- call assert_fails(":set kmp=valid_name", "valid_name")
- call assert_fails(":set kmp=valid-name", "E544:")
- call assert_fails(":set kmp=valid-name", "valid-name")
-
- call assert_fails(":set kmp=wrong;name", "E474:")
- call assert_fails(":set kmp=wrong\\\\name", "E474:")
- call assert_fails(":set kmp=wrong\\|name", "E474:")
- call assert_fails(":set kmp=wrong/name", "E474:")
- call assert_fails(":set kmp=wrong\\\nname", "E474:")
-
- call assert_fails(":set kmp=trunc\x00name", "E544:")
- call assert_fails(":set kmp=trunc\x00name", "trunc")
-endfunc
-
-func Check_dir_option(name)
- " Check that it's possible to set the option.
- exe 'set ' . a:name . '=/usr/share/dict/words'
- call assert_equal('/usr/share/dict/words', eval('&' . a:name))
- exe 'set ' . a:name . '=/usr/share/dict/words,/and/there'
- call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name))
- exe 'set ' . a:name . '=/usr/share/dict\ words'
- call assert_equal('/usr/share/dict words', eval('&' . a:name))
-
- " Check rejecting weird characters.
- call assert_fails("set " . a:name . "=/not&there", "E474:")
- call assert_fails("set " . a:name . "=/not>there", "E474:")
- call assert_fails("set " . a:name . "=/not.*there", "E474:")
-endfunc
-
-func Test_cinkeys()
- " This used to cause invalid memory access
- set cindent cinkeys=0
- norm a
- set cindent& cinkeys&
-endfunc
-
-func Test_dictionary()
- call Check_dir_option('dictionary')
-endfunc
-
-func Test_thesaurus()
- call Check_dir_option('thesaurus')
-endfun
-
-func Test_complete()
- " Trailing single backslash used to cause invalid memory access.
- set complete=s\
- new
- call feedkeys("i\<C-N>\<Esc>", 'xt')
- bwipe!
- call assert_fails('set complete=ix', 'E535:')
- set complete&
-endfun
-
-func Test_set_completion()
- call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:)
-
- call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:)
-
- call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:)
-
- " Expand boolean options. When doing :set no<Tab>
- " vim displays the options names without "no" but completion uses "no...".
- call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set nodiff digraph', @:)
-
- call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set invdiff digraph', @:)
-
- " Expand abbreviation of options.
- call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set tabstop thesaurus thesaurusfunc', @:)
-
- " Expand current value
- call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:)
-
- call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:)
-
- " Expand key codes.
- " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx')
- " call assert_equal('"set <Help> <Home>', @:)
-
- " Expand terminal options.
- " call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx')
- " call assert_equal('"set t_AB t_AF t_AU t_AL', @:)
- " call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:')
-
- " Expand directories.
- call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ', @:)
- call assert_notmatch('./small.vim ', @:)
-
- " Expand files and directories.
- call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('./samples/ ./sautest/ ./screendump.vim ./script_util.vim ./setup.vim ./shared.vim', @:)
-
- call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
-
- set tags&
-
- " Expand values for 'filetype'
- call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set filetype=sshdconfig', @:)
- call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
-endfunc
-
-func Test_set_errors()
- call assert_fails('set scroll=-1', 'E49:')
- call assert_fails('set backupcopy=', 'E474:')
- call assert_fails('set regexpengine=3', 'E474:')
- call assert_fails('set history=10001', 'E474:')
- call assert_fails('set numberwidth=21', 'E474:')
- call assert_fails('set colorcolumn=-a', 'E474:')
- call assert_fails('set colorcolumn=a', 'E474:')
- call assert_fails('set colorcolumn=1,', 'E474:')
- call assert_fails('set colorcolumn=1;', 'E474:')
- call assert_fails('set cmdheight=-1', 'E487:')
- call assert_fails('set cmdwinheight=-1', 'E487:')
- if has('conceal')
- call assert_fails('set conceallevel=-1', 'E487:')
- call assert_fails('set conceallevel=4', 'E474:')
- endif
- call assert_fails('set helpheight=-1', 'E487:')
- call assert_fails('set history=-1', 'E487:')
- call assert_fails('set report=-1', 'E487:')
- 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('let &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:')
- call assert_fails('set updatetime=-1', 'E487:')
- call assert_fails('set winheight=-1', 'E487:')
- call assert_fails('set tabstop!', 'E488:')
- call assert_fails('set xxx', 'E518:')
- call assert_fails('set beautify?', 'E518:')
- call assert_fails('set undolevels=x', 'E521:')
- call assert_fails('set tabstop=', 'E521:')
- call assert_fails('set comments=-', 'E524:')
- call assert_fails('set comments=a', 'E525:')
- call assert_fails('set foldmarker=x', 'E536:')
- call assert_fails('set commentstring=x', 'E537:')
- call assert_fails('let &commentstring = "x"', 'E537:')
- call assert_fails('set complete=x', 'E539:')
- call assert_fails('set rulerformat=%-', 'E539:')
- call assert_fails('set rulerformat=%(', 'E542:')
- call assert_fails('set rulerformat=%15(%%', 'E542:')
- call assert_fails('set statusline=%$', 'E539:')
- call assert_fails('set statusline=%{', 'E540:')
- call assert_fails('set statusline=%{%', 'E540:')
- call assert_fails('set statusline=%{%}', 'E539:')
- call assert_fails('set statusline=%(', 'E542:')
- call assert_fails('set statusline=%)', 'E542:')
- call assert_fails('set tabline=%$', 'E539:')
- call assert_fails('set tabline=%{', 'E540:')
- call assert_fails('set tabline=%{%', 'E540:')
- call assert_fails('set tabline=%{%}', 'E539:')
- call assert_fails('set tabline=%(', 'E542:')
- call assert_fails('set tabline=%)', 'E542:')
-
- if has('cursorshape')
- " This invalid value for 'guicursor' used to cause Vim to crash.
- call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:')
- call assert_fails('set guicursor=i-ci', 'E545:')
- call assert_fails('set guicursor=x', 'E545:')
- call assert_fails('set guicursor=x:', 'E546:')
- call assert_fails('set guicursor=r-cr:horx', 'E548:')
- call assert_fails('set guicursor=r-cr:hor0', 'E549:')
- endif
- if has('mouseshape')
- call assert_fails('se mouseshape=i-r:x', 'E547:')
- endif
- call assert_fails('set backupext=~ patchmode=~', 'E589:')
- call assert_fails('set winminheight=10 winheight=9', 'E591:')
- call assert_fails('set winminwidth=10 winwidth=9', 'E592:')
- call assert_fails("set showbreak=\x01", 'E595:')
- call assert_fails('set t_foo=', 'E846:')
- call assert_fails('set tabstop??', 'E488:')
- call assert_fails('set wrapscan!!', 'E488:')
- call assert_fails('set tabstop&&', 'E488:')
- call assert_fails('set wrapscan<<', 'E488:')
- call assert_fails('set wrapscan=1', 'E474:')
- call assert_fails('set autoindent@', 'E488:')
- call assert_fails('set wildchar=<abc>', 'E474:')
- call assert_fails('set cmdheight=1a', 'E521:')
- call assert_fails('set invcmdheight', 'E474:')
- if has('python') || has('python3')
- call assert_fails('set pyxversion=6', 'E474:')
- endif
- call assert_fails("let &tabstop='ab'", 'E521:')
- call assert_fails('set spellcapcheck=%\\(', 'E54:')
- call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
- call assert_fails('set foldmarker={{{,', 'E474:')
- call assert_fails('set sessionoptions=sesdir,curdir', 'E474:')
- setlocal listchars=trail:·
- call assert_fails('set ambiwidth=double', 'E834:')
- setlocal listchars=trail:-
- setglobal listchars=trail:·
- call assert_fails('set ambiwidth=double', 'E834:')
- set listchars&
- setlocal fillchars=stl:·
- call assert_fails('set ambiwidth=double', 'E835:')
- setlocal fillchars=stl:-
- setglobal fillchars=stl:·
- call assert_fails('set ambiwidth=double', 'E835:')
- set fillchars&
- call assert_fails('set fileencoding=latin1,utf-8', 'E474:')
- set nomodifiable
- call assert_fails('set fileencoding=latin1', 'E21:')
- set modifiable&
- " call assert_fails('set t_#-&', 'E522:')
-endfunc
-
-func CheckWasSet(name)
- let verb_cm = execute('verbose set ' .. a:name .. '?')
- call assert_match('Last set from.*test_options.vim', verb_cm)
-endfunc
-func CheckWasNotSet(name)
- let verb_cm = execute('verbose set ' .. a:name .. '?')
- call assert_notmatch('Last set from', verb_cm)
-endfunc
-
-" Must be executed before other tests that set 'term'.
-func Test_000_term_option_verbose()
- throw "Skipped: Nvim does not support setting 'term'"
- CheckNotGui
-
- call CheckWasNotSet('t_cm')
-
- let term_save = &term
- set term=ansi
- call CheckWasSet('t_cm')
- let &term = term_save
-endfunc
-
-func Test_copy_context()
- setlocal list
- call CheckWasSet('list')
- split
- call CheckWasSet('list')
- quit
- setlocal nolist
-
- set ai
- call CheckWasSet('ai')
- set filetype=perl
- call CheckWasSet('filetype')
- set fo=tcroq
- call CheckWasSet('fo')
-
- split Xsomebuf
- call CheckWasSet('ai')
- call CheckWasNotSet('filetype')
- call CheckWasSet('fo')
-endfunc
-
-func Test_set_ttytype()
- throw "Skipped: Nvim does not support 'ttytype'"
- CheckUnix
- CheckNotGui
-
- " Setting 'ttytype' used to cause a double-free when exiting vim and
- " when vim is compiled with -DEXITFREE.
- set ttytype=ansi
- call assert_equal('ansi', &ttytype)
- call assert_equal(&ttytype, &term)
- set ttytype=xterm
- call assert_equal('xterm', &ttytype)
- call assert_equal(&ttytype, &term)
- try
- set ttytype=
- call assert_report('set ttytype= did not fail')
- catch /E529/
- endtry
-
- " Some systems accept any terminal name and return dumb settings,
- " check for failure of finding the entry and for missing 'cm' entry.
- try
- set ttytype=xxx
- call assert_report('set ttytype=xxx did not fail')
- catch /E522\|E437/
- endtry
-
- set ttytype&
- call assert_equal(&ttytype, &term)
-
- if has('gui') && !has('gui_running')
- call assert_fails('set term=gui', 'E531:')
- endif
-endfunc
-
-func Test_set_all()
- set tw=75
- set iskeyword=a-z,A-Z
- set nosplitbelow
- let out = execute('set all')
- call assert_match('textwidth=75', out)
- call assert_match('iskeyword=a-z,A-Z', out)
- call assert_match('nosplitbelow', out)
- set tw& iskeyword& splitbelow&
-endfunc
-
-func Test_set_one_column()
- let out_mult = execute('set all')->split("\n")
- let out_one = execute('set! all')->split("\n")
- " one column should be two to four times as many lines
- call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one))
-endfunc
-
-func Test_set_values()
- " opt_test.vim is generated from ../optiondefs.h using gen_opt_test.vim
- if filereadable('opt_test.vim')
- source opt_test.vim
- else
- throw 'Skipped: opt_test.vim does not exist'
- endif
-endfunc
-
-func Test_renderoptions()
- throw 'skipped: Nvim does not support renderoptions'
- " Only do this for Windows Vista and later, fails on Windows XP and earlier.
- " Doesn't hurt to do this on a non-Windows system.
- if windowsversion() !~ '^[345]\.'
- set renderoptions=type:directx
- set rop=type:directx
- endif
-endfunc
-
-func ResetIndentexpr()
- set indentexpr=
-endfunc
-
-func Test_set_indentexpr()
- " this was causing usage of freed memory
- set indentexpr=ResetIndentexpr()
- new
- call feedkeys("i\<c-f>", 'x')
- call assert_equal('', &indentexpr)
- bwipe!
-endfunc
-
-func Test_backupskip()
- " Option 'backupskip' may contain several comma-separated path
- " specifications if one or more of the environment variables TMPDIR, TMP,
- " or TEMP is defined. To simplify testing, convert the string value into a
- " list.
- let bsklist = split(&bsk, ',')
-
- if has("mac")
- let found = (index(bsklist, '/private/tmp/*') >= 0)
- call assert_true(found, '/private/tmp not in option bsk: ' . &bsk)
- elseif has("unix")
- let found = (index(bsklist, '/tmp/*') >= 0)
- call assert_true(found, '/tmp not in option bsk: ' . &bsk)
- endif
-
- " If our test platform is Windows, the path(s) in option bsk will use
- " backslash for the path separator and the components could be in short
- " (8.3) format. As such, we need to replace the backslashes with forward
- " slashes and convert the path components to long format. The expand()
- " function will do this but it cannot handle comma-separated paths. This is
- " why bsk was converted from a string into a list of strings above.
- "
- " One final complication is that the wildcard "/*" is at the end of each
- " path and so expand() might return a list of matching files. To prevent
- " this, we need to remove the wildcard before calling expand() and then
- " append it afterwards.
- if has('win32')
- let item_nbr = 0
- while item_nbr < len(bsklist)
- let path_spec = bsklist[item_nbr]
- let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2)
- let path_spec = substitute(expand(path_spec), '\\', '/', 'g')
- let bsklist[item_nbr] = path_spec . '/*'
- let item_nbr += 1
- endwhile
- endif
-
- " Option bsk will also include these environment variables if defined.
- " If they're defined, verify they appear in the option value.
- for var in ['$TMPDIR', '$TMP', '$TEMP']
- if exists(var)
- let varvalue = substitute(expand(var), '\\', '/', 'g')
- let varvalue = substitute(varvalue, '/$', '', '')
- let varvalue .= '/*'
- let found = (index(bsklist, varvalue) >= 0)
- call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk)
- endif
- endfor
-
- " Duplicates from environment variables should be filtered out (option has
- " P_NODUP). Run this in a separate instance and write v:errors in a file,
- " so that we see what happens on startup.
- let after =<< trim [CODE]
- let bsklist = split(&backupskip, ',')
- call assert_equal(uniq(copy(bsklist)), bsklist)
- call writefile(['errors:'] + v:errors, 'Xtestout')
- qall
- [CODE]
- call writefile(after, 'Xafter')
- " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
- let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
-
- let saveenv = {}
- for var in ['TMPDIR', 'TMP', 'TEMP']
- let saveenv[var] = getenv(var)
- call setenv(var, '/duplicate/path')
- endfor
-
- exe 'silent !' . cmd
- call assert_equal(['errors:'], readfile('Xtestout'))
-
- " restore environment variables
- for var in ['TMPDIR', 'TMP', 'TEMP']
- call setenv(var, saveenv[var])
- endfor
-
- call delete('Xtestout')
- call delete('Xafter')
-
- " Duplicates should be filtered out (option has P_NODUP)
- let backupskip = &backupskip
- set backupskip=
- set backupskip+=/test/dir
- set backupskip+=/other/dir
- set backupskip+=/test/dir
- call assert_equal('/test/dir,/other/dir', &backupskip)
- let &backupskip = backupskip
-endfunc
-
-func Test_copy_winopt()
- set hidden
-
- " Test copy option from current buffer in window
- split
- enew
- setlocal numberwidth=5
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(5,&numberwidth)
- bw!
- call assert_equal(4,&numberwidth)
-
- " Test copy value from window that used to be display the buffer
- split
- enew
- setlocal numberwidth=6
- bnext
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(6,&numberwidth)
- bw!
-
- " Test that if buffer is current, don't use the stale cached value
- " from the last time the buffer was displayed.
- split
- enew
- setlocal numberwidth=7
- bnext
- bnext
- setlocal numberwidth=8
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(8,&numberwidth)
- bw!
-
- " Test value is not copied if window already has seen the buffer
- enew
- split
- setlocal numberwidth=9
- bnext
- setlocal numberwidth=10
- wincmd w
- call assert_equal(4,&numberwidth)
- bnext
- call assert_equal(4,&numberwidth)
- bw!
-
- set hidden&
-endfunc
-
-func Test_shortmess_F()
- new
- call assert_match('\[No Name\]', execute('file'))
- set shortmess+=F
- call assert_match('\[No Name\]', execute('file'))
- call assert_match('^\s*$', execute('file foo'))
- call assert_match('foo', execute('file'))
- set shortmess-=F
- call assert_match('bar', execute('file bar'))
- call assert_match('bar', execute('file'))
- set shortmess&
- bwipe
-endfunc
-
-func Test_shortmess_F2()
- e file1
- e file2
- " Accommodate Nvim default.
- set shortmess-=F
- call assert_match('file1', execute('bn', ''))
- call assert_match('file2', execute('bn', ''))
- set shortmess+=F
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- set hidden
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- set nohidden
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- call assert_true(empty(execute('bn', '')))
- " call assert_false(test_getvalue('need_fileinfo'))
- " Accommodate Nvim default.
- set shortmess-=F
- call assert_match('file1', execute('bn', ''))
- call assert_match('file2', execute('bn', ''))
- bwipe
- bwipe
-endfunc
-
-func Test_local_scrolloff()
- set so=5
- set siso=7
- split
- call assert_equal(5, &so)
- setlocal so=3
- call assert_equal(3, &so)
- wincmd w
- call assert_equal(5, &so)
- wincmd w
- setlocal so<
- call assert_equal(5, &so)
- setlocal so=0
- call assert_equal(0, &so)
- setlocal so=-1
- call assert_equal(5, &so)
-
- call assert_equal(7, &siso)
- setlocal siso=3
- call assert_equal(3, &siso)
- wincmd w
- call assert_equal(7, &siso)
- wincmd w
- setlocal siso<
- call assert_equal(7, &siso)
- setlocal siso=0
- call assert_equal(0, &siso)
- setlocal siso=-1
- call assert_equal(7, &siso)
-
- close
- set so&
- set siso&
-endfunc
-
-func Test_visualbell()
- set belloff=
- set visualbell
- call assert_beeps('normal 0h')
- set novisualbell
- set belloff=all
-endfunc
-
-" Test for the 'write' option
-func Test_write()
- new
- call setline(1, ['L1'])
- set nowrite
- call assert_fails('write Xfile', 'E142:')
- set write
- close!
-endfunc
-
-" Test for 'buftype' option
-func Test_buftype()
- new
- call setline(1, ['L1'])
- set buftype=nowrite
- call assert_fails('write', 'E382:')
-
- " for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup']
- for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'prompt']
- exe 'set buftype=' .. val
- call writefile(['something'], 'XBuftype')
- call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val)
- endfor
-
- call delete('XBuftype')
- bwipe!
-endfunc
-
-" Test for the 'shell' option
-func Test_shell()
- throw 'Skipped: Nvim does not have :shell'
- CheckUnix
- let save_shell = &shell
- set shell=
- let caught_e91 = 0
- try
- shell
- catch /E91:/
- let caught_e91 = 1
- endtry
- call assert_equal(1, caught_e91)
- let &shell = save_shell
-endfunc
-
-" Test for the 'shellquote' option
-func Test_shellquote()
- CheckUnix
- set shellquote=#
- set verbose=20
- redir => v
- silent! !echo Hello
- redir END
- set verbose&
- set shellquote&
- call assert_match(': "#echo Hello#"', v)
-endfunc
-
-" Test for the 'rightleftcmd' option
-func Test_rightleftcmd()
- CheckFeature rightleft
- set rightleft
-
- let g:l = []
- func AddPos()
- call add(g:l, screencol())
- return ''
- endfunc
- cmap <expr> <F2> AddPos()
-
- set rightleftcmd=
- call feedkeys("/\<F2>abc\<Right>\<F2>\<Left>\<Left>\<F2>" ..
- \ "\<Right>\<F2>\<Esc>", 'xt')
- call assert_equal([2, 5, 3, 4], g:l)
-
- let g:l = []
- set rightleftcmd=search
- call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" ..
- \ "\<Left>\<F2>\<Esc>", 'xt')
- call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l)
-
- cunmap <F2>
- unlet g:l
- set rightleftcmd&
- set rightleft&
-endfunc
-
-" Test for the 'debug' option
-func Test_debug_option()
- " redraw to avoid matching previous messages
- redraw
- set debug=beep
- exe "normal \<C-c>"
- call assert_equal('Beep!', Screenline(&lines))
- call assert_equal('line 4:', Screenline(&lines - 1))
- " only match the final colon in the line that shows the source
- call assert_match(':$', Screenline(&lines - 2))
- set debug&
-endfunc
-
-" Test for the default CDPATH option
-func Test_opt_default_cdpath()
- let after =<< trim [CODE]
- call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath)
- call writefile(v:errors, 'Xtestout')
- qall
- [CODE]
- if has('unix')
- let $CDPATH='/path/to/dir1:/path/to/dir2'
- else
- let $CDPATH='/path/to/dir1;/path/to/dir2'
- endif
- if RunVim([], after, '')
- call assert_equal([], readfile('Xtestout'))
- call delete('Xtestout')
- endif
-endfunc
-
-" Test for setting keycodes using set
-func Test_opt_set_keycode()
- call assert_fails('set <t_k1=l', 'E474:')
- call assert_fails('set <Home=l', 'E474:')
- set <t_k9>=abcd
- " call assert_equal('abcd', &t_k9)
- set <t_k9>&
- set <F9>=xyz
- " call assert_equal('xyz', &t_k9)
- set <t_k9>&
-endfunc
-
-" Test for changing options in a sandbox
-func Test_opt_sandbox()
- for opt in ['backupdir', 'cdpath', 'exrc']
- call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
- endfor
-endfunc
-
-" Test for setting an option with local value to global value
-func Test_opt_local_to_global()
- setglobal equalprg=gprg
- setlocal equalprg=lprg
- call assert_equal('gprg', &g:equalprg)
- call assert_equal('lprg', &l:equalprg)
- call assert_equal('lprg', &equalprg)
- set equalprg<
- call assert_equal('', &l:equalprg)
- call assert_equal('gprg', &equalprg)
- setglobal equalprg=gnewprg
- setlocal equalprg=lnewprg
- setlocal equalprg<
- call assert_equal('gnewprg', &l:equalprg)
- call assert_equal('gnewprg', &equalprg)
- set equalprg&
-
- " Test for setting the global/local value of a boolean option
- setglobal autoread
- setlocal noautoread
- call assert_false(&autoread)
- set autoread<
- call assert_true(&autoread)
- setglobal noautoread
- setlocal autoread
- setlocal autoread<
- call assert_false(&autoread)
- set autoread&
-endfunc
-
-func Test_set_in_sandbox()
- " Some boolean options cannot be set in sandbox, some can.
- call assert_fails('sandbox set modelineexpr', 'E48:')
- sandbox set number
- call assert_true(&number)
- set number&
-
- " Some boolean options cannot be set in sandbox, some can.
- if has('python') || has('python3')
- call assert_fails('sandbox set pyxversion=3', 'E48:')
- endif
- sandbox set tabstop=4
- call assert_equal(4, &tabstop)
- set tabstop&
-
- " Some string options cannot be set in sandbox, some can.
- call assert_fails('sandbox set backupdir=/tmp', 'E48:')
- sandbox set filetype=perl
- call assert_equal('perl', &filetype)
- set filetype&
-endfunc
-
-" Test for incrementing, decrementing and multiplying a number option value
-func Test_opt_num_op()
- set shiftwidth=4
- set sw+=2
- call assert_equal(6, &sw)
- set sw-=2
- call assert_equal(4, &sw)
- set sw^=2
- call assert_equal(8, &sw)
- set shiftwidth&
-endfunc
-
-" Test for setting option values using v:false and v:true
-func Test_opt_boolean()
- set number&
- set number
- call assert_equal(1, &nu)
- set nonu
- call assert_equal(0, &nu)
- let &nu = v:true
- call assert_equal(1, &nu)
- let &nu = v:false
- call assert_equal(0, &nu)
- set number&
-endfunc
-
-" Test for the 'window' option
-func Test_window_opt()
- " Needs only one open widow
- %bw!
- call setline(1, range(1, 8))
- set window=5
- exe "normal \<C-F>"
- call assert_equal(4, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(7, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(8, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(5, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(1, line('w0'))
- set window=1
- exe "normal gg\<C-F>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-F>"
- call assert_equal(3, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(2, line('w0'))
- exe "normal \<C-B>"
- call assert_equal(1, line('w0'))
- enew!
- set window&
-endfunc
-
-" Test for the 'winminheight' option
-func Test_opt_winminheight()
- only!
- let &winheight = &lines + 4
- call assert_fails('let &winminheight = &lines + 2', 'E36:')
- call assert_true(&winminheight <= &lines)
- set winminheight&
- set winheight&
-endfunc
-
-func Test_opt_winminheight_term()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
-
- " The tabline should be taken into account.
- let lines =<< trim END
- set wmh=0 stal=2
- below sp | wincmd _
- below sp | wincmd _
- below sp | wincmd _
- below sp
- END
- call writefile(lines, 'Xwinminheight')
- let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
- call term_sendkeys(buf, ":set wmh=1\n")
- call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
-
- call StopVimInTerminal(buf)
- call delete('Xwinminheight')
-endfunc
-
-func Test_opt_winminheight_term_tabs()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
-
- " The tabline should be taken into account.
- let lines =<< trim END
- set wmh=0 stal=2
- split
- split
- split
- split
- tabnew
- END
- call writefile(lines, 'Xwinminheight')
- let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
- call term_sendkeys(buf, ":set wmh=1\n")
- call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
-
- call StopVimInTerminal(buf)
- call delete('Xwinminheight')
-endfunc
-
-" Test for the 'winminwidth' option
-func Test_opt_winminwidth()
- only!
- let &winwidth = &columns + 4
- call assert_fails('let &winminwidth = &columns + 2', 'E36:')
- call assert_true(&winminwidth <= &columns)
- set winminwidth&
- set winwidth&
-endfunc
-
-" Test for setting option value containing spaces with isfname+=32
-func Test_isfname_with_options()
- set isfname+=32
- setlocal keywordprg=:term\ help.exe
- call assert_equal(':term help.exe', &keywordprg)
- set isfname&
- setlocal keywordprg&
-endfunc
-
-" Test that resetting laststatus does change scroll option
-func Test_opt_reset_scroll()
- " See test/functional/legacy/options_spec.lua
- CheckRunVimInTerminal
- let vimrc =<< trim [CODE]
- set scroll=2
- set laststatus=2
- [CODE]
- call writefile(vimrc, 'Xscroll')
- let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45})
- call term_sendkeys(buf, ":verbose set scroll?\n")
- call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))})
- call assert_match('^\s*scroll=7$', term_getline(buf, 14))
- call StopVimInTerminal(buf)
-
- " clean up
- call delete('Xscroll')
-endfunc
-
-" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm'
-func Test_VIM_POSIX()
- throw 'Skipped: Nvim does not support $VIM_POSIX'
- let saved_VIM_POSIX = getenv("VIM_POSIX")
-
- call setenv('VIM_POSIX', "1")
- let after =<< trim [CODE]
- call writefile([&cpo, &shm], 'X_VIM_POSIX')
- qall
- [CODE]
- if RunVim([], after, '')
- call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
- \ 'AS'], readfile('X_VIM_POSIX'))
- endif
-
- call setenv('VIM_POSIX', v:null)
- let after =<< trim [CODE]
- call writefile([&cpo, &shm], 'X_VIM_POSIX')
- qall
- [CODE]
- if RunVim([], after, '')
- call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
- \ 'S'], readfile('X_VIM_POSIX'))
- endif
-
- call delete('X_VIM_POSIX')
- call setenv('VIM_POSIX', saved_VIM_POSIX)
-endfunc
-
-" Test for setting an option to a Vi or Vim default
-func Test_opt_default()
- throw 'Skipped: Nvim has different defaults'
- set formatoptions&vi
- call assert_equal('vt', &formatoptions)
- set formatoptions&vim
- call assert_equal('tcq', &formatoptions)
-endfunc
-
-" Test for the 'cmdheight' option
-func Test_cmdheight()
- %bw!
- let ht = &lines
- set cmdheight=9999
- call assert_equal(1, winheight(0))
- call assert_equal(ht - 1, &cmdheight)
- set cmdheight&
-endfunc
-
-" To specify a control character as a option value, '^' can be used
-func Test_opt_control_char()
- set wildchar=^v
- call assert_equal("\<C-V>", nr2char(&wildchar))
- set wildcharm=^r
- call assert_equal("\<C-R>", nr2char(&wildcharm))
- " Bug: This doesn't work for the 'cedit' and 'termwinkey' options
- set wildchar& wildcharm&
-endfunc
-
-" Test for the 'errorbells' option
-func Test_opt_errorbells()
- set errorbells
- call assert_beeps('s/a1b2/x1y2/')
- set noerrorbells
-endfunc
-
-func Test_opt_scrolljump()
- help
- resize 10
-
- " Test with positive 'scrolljump'.
- set scrolljump=2
- norm! Lj
- call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
- \ 'topline':3, 'coladd':0, 'skipcol':0, 'curswant':0},
- \ winsaveview())
-
- " Test with negative 'scrolljump' (percentage of window height).
- set scrolljump=-40
- norm! ggLj
- call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
- \ 'topline':5, 'coladd':0, 'skipcol':0, 'curswant':0},
- \ winsaveview())
-
- set scrolljump&
- bw
-endfunc
-
-" Test for the 'cdhome' option
-func Test_opt_cdhome()
- if has('unix') || has('vms')
- throw 'Skipped: only works on non-Unix'
- endif
-
- set cdhome&
- call assert_equal(0, &cdhome)
- set cdhome
-
- " This paragraph is copied from Test_cd_no_arg().
- let path = getcwd()
- cd
- call assert_equal($HOME, getcwd())
- call assert_notequal(path, getcwd())
- exe 'cd ' .. fnameescape(path)
- call assert_equal(path, getcwd())
-
- set cdhome&
-endfunc
-
-func Test_set_completion_2()
- CheckOption termguicolors
-
- " Test default option completion
- set wildoptions=
- call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set termguicolors', @:)
-
- call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set notermguicolors', @:)
-
- " Test fuzzy option completion
- set wildoptions=fuzzy
- call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
- " Nvim doesn't have 'termencoding'
- " call assert_equal('"set termguicolors termencoding', @:)
- call assert_equal('"set termguicolors', @:)
-
- call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"set notermguicolors', @:)
-
- set wildoptions=
-endfunc
-
-func Test_switchbuf_reset()
- set switchbuf=useopen
- sblast
- call assert_equal(1, winnr('$'))
- set all&
- " Nvim has a different default for 'switchbuf'
- " call assert_equal('', &switchbuf)
- call assert_equal('uselast', &switchbuf)
- sblast
- call assert_equal(2, winnr('$'))
- only!
-endfunc
-
-" :set empty string for global 'keywordprg' falls back to ":help"
-func Test_keywordprg_empty()
- let k = &keywordprg
- set keywordprg=man
- call assert_equal('man', &keywordprg)
- set keywordprg=
- call assert_equal(':help', &keywordprg)
- set keywordprg=man
- call assert_equal('man', &keywordprg)
- call assert_equal("\n keywordprg=:help", execute('set kp= kp?'))
- let &keywordprg = k
-endfunc
-
-" check that the very first buffer created does not have 'endoffile' set
-func Test_endoffile_default()
- let after =<< trim [CODE]
- call writefile([execute('set eof?')], 'Xtestout')
- qall!
- [CODE]
- if RunVim([], after, '')
- call assert_equal(["\nnoendoffile"], readfile('Xtestout'))
- endif
- call delete('Xtestout')
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_python2.vim b/src/nvim/testdir/test_python2.vim
deleted file mode 100644
index 745b7da086..0000000000
--- a/src/nvim/testdir/test_python2.vim
+++ /dev/null
@@ -1,173 +0,0 @@
-" Test for python 2 commands.
-" TODO: move tests from test86.in here.
-
-source check.vim
-CheckFeature python
-
-func Test_pydo()
- " Check deleting lines does not trigger ml_get error.
- py import vim
- new
- call setline(1, ['one', 'two', 'three'])
- pydo vim.command("%d_")
- bwipe!
-
- " Disabled until neovim/neovim#8554 is resolved
- if 0
- " Check switching to another buffer does not trigger ml_get error.
- new
- let wincount = winnr('$')
- call setline(1, ['one', 'two', 'three'])
- pydo vim.command("new")
- call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
- endif
-endfunc
-
-func Test_set_cursor()
- " Check that setting the cursor position works.
- py import vim
- new
- call setline(1, ['first line', 'second line'])
- normal gg
- pydo vim.current.window.cursor = (1, 5)
- call assert_equal([1, 6], [line('.'), col('.')])
-
- " Check that movement after setting cursor position keeps current column.
- normal j
- call assert_equal([2, 6], [line('.'), col('.')])
-endfunc
-
-func Test_vim_function()
- throw 'skipped: Nvim does not support vim.bindeval()'
- " Check creating vim.Function object
- py import vim
-
- func s:foo()
- return matchstr(expand('<sfile>'), '<SNR>\zs\d\+_foo$')
- endfunc
- let name = '<SNR>' . s:foo()
-
- try
- py f = vim.bindeval('function("s:foo")')
- call assert_equal(name, pyeval('f.name'))
- catch
- call assert_false(v:exception)
- endtry
-
- try
- py f = vim.Function('\x80\xfdR' + vim.eval('s:foo()'))
- call assert_equal(name, 'f.name'->pyeval())
- catch
- call assert_false(v:exception)
- endtry
-
- py del f
- delfunc s:foo
-endfunc
-
-func Test_skipped_python_command_does_not_affect_pyxversion()
- set pyxversion=0
- if 0
- python import vim
- endif
- call assert_equal(0, &pyxversion) " This assertion would have failed with Vim 8.0.0251. (pyxversion was introduced in 8.0.0251.)
-endfunc
-
-func _SetUpHiddenBuffer()
- py import vim
- new
- edit hidden
- setlocal bufhidden=hide
-
- enew
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
-
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func _CleanUpHiddenBuffer()
- bwipe! hidden
- bwipe!
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Clear()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_List()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][:] = [ 'test' ]
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_Str()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = 'test'
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func Test_Write_To_HiddenBuffer_Does_Not_Fix_Cursor_ClearLine()
- call _SetUpHiddenBuffer()
- py vim.buffers[ int( vim.eval( 'bufnr("hidden")' ) ) ][0] = None
- call assert_equal( line( '.' ), 11 )
- call _CleanUpHiddenBuffer()
-endfunc
-
-func _SetUpVisibleBuffer()
- py import vim
- new
- let lnum = 0
- while lnum < 10
- call append( 1, string( lnum ) )
- let lnum = lnum + 1
- endwhile
- normal G
- call assert_equal( line( '.' ), 11 )
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Clear()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[:] = None
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunc
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_List()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[:] = [ 'test' ]
- call assert_equal( line( '.' ), 1 )
-
- bwipe!
-endfunction
-
-func Test_Write_To_Current_Buffer_Fixes_Cursor_Str()
- call _SetUpVisibleBuffer()
-
- py vim.current.buffer[-1] = None
- call assert_equal( line( '.' ), 10 )
-
- bwipe!
-endfunction
-
-func Test_Catch_Exception_Message()
- try
- py raise RuntimeError( 'TEST' )
- catch /.*/
- call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
- endtry
-endfunc
diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim
deleted file mode 100644
index eee825fa9b..0000000000
--- a/src/nvim/testdir/test_pyx2.vim
+++ /dev/null
@@ -1,81 +0,0 @@
-" Test for pyx* commands and functions with Python 2.
-
-source check.vim
-CheckFeature python
-set pyx=2
-
-let s:py2pattern = '^2\.[0-7]\.\d\+'
-let s:py3pattern = '^3\.\d\+\.\d\+'
-
-
-func Test_has_pythonx()
- call assert_true(has('pythonx'))
-endfunc
-
-
-func Test_pyx()
- redir => var
- pyx << EOF
-import sys
-print(sys.version)
-EOF
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-endfunc
-
-
-func Test_pyxdo()
- pyx import sys
- enew
- pyxdo return sys.version.split("\n")[0]
- call assert_match(s:py2pattern, split(getline('.'))[0])
-endfunc
-
-
-func Test_pyxeval()
- pyx import sys
- call assert_match(s:py2pattern, split('sys.version'->pyxeval())[0])
-endfunc
-
-
-func Test_pyxfile()
- " No special comments nor shebangs
- redir => var
- pyxfile pyxfile/pyx.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- " Python 2 special comment
- redir => var
- pyxfile pyxfile/py2_magic.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- " Python 2 shebang
- redir => var
- pyxfile pyxfile/py2_shebang.py
- redir END
- call assert_match(s:py2pattern, split(var)[0])
-
- if has('python3')
- " Python 3 special comment
- redir => var
- pyxfile pyxfile/py3_magic.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
-
- " Python 3 shebang
- redir => var
- pyxfile pyxfile/py3_shebang.py
- redir END
- call assert_match(s:py3pattern, split(var)[0])
- endif
-endfunc
-
-func Test_Catch_Exception_Message()
- try
- pyx raise RuntimeError( 'TEST' )
- catch /.*/
- call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
- endtry
-endfunc
diff --git a/src/nvim/testdir/test_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim
deleted file mode 100644
index 44ec146666..0000000000
--- a/src/nvim/testdir/test_scriptnames.vim
+++ /dev/null
@@ -1,32 +0,0 @@
-" Test for :scriptnames
-
-func Test_scriptnames()
- call writefile(['let did_load_script = 123'], 'Xscripting')
- source Xscripting
- call assert_equal(123, g:did_load_script)
-
- let scripts = split(execute('scriptnames'), "\n")
- let last = scripts[-1]
- call assert_match('\<Xscripting\>', last)
- let lastnr = substitute(last, '\D*\(\d\+\):.*', '\1', '')
- exe 'script ' . lastnr
- call assert_equal('Xscripting', expand('%:t'))
-
- call assert_fails('script ' . (lastnr + 1), 'E474:')
- call assert_fails('script 0', 'E939:')
-
- new
- call setline(1, 'nothing')
- call assert_fails('script ' . lastnr, 'E37:')
- exe 'script! ' . lastnr
- call assert_equal('Xscripting', expand('%:t'))
-
- bwipe
- call delete('Xscripting')
-
- let msgs = execute('messages')
- scriptnames
- call assert_equal(msgs, execute('messages'))
-endfunc
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scroll_opt.vim b/src/nvim/testdir/test_scroll_opt.vim
deleted file mode 100644
index 77920eb8b0..0000000000
--- a/src/nvim/testdir/test_scroll_opt.vim
+++ /dev/null
@@ -1,36 +0,0 @@
-" Test for reset 'scroll'
-"
-
-func Test_reset_scroll()
- let scr = &l:scroll
-
- setlocal scroll=1
- setlocal scroll&
- call assert_equal(scr, &l:scroll)
-
- setlocal scroll=1
- setlocal scroll=0
- call assert_equal(scr, &l:scroll)
-
- try
- execute 'setlocal scroll=' . (winheight(0) + 1)
- " not reached
- call assert_false(1)
- catch
- call assert_exception('E49:')
- endtry
-
- split
-
- let scr = &l:scroll
-
- setlocal scroll=1
- setlocal scroll&
- call assert_equal(scr, &l:scroll)
-
- setlocal scroll=1
- setlocal scroll=0
- call assert_equal(scr, &l:scroll)
-
- quit!
-endfunc
diff --git a/src/nvim/testdir/test_termcodes.vim b/src/nvim/testdir/test_termcodes.vim
deleted file mode 100644
index 99bc2d1d37..0000000000
--- a/src/nvim/testdir/test_termcodes.vim
+++ /dev/null
@@ -1,63 +0,0 @@
-
-" Test for translation of special key codes (<xF1>, <xF2>, etc.)
-func Test_Keycode_Translation()
- let keycodes = [
- \ ["<xUp>", "<Up>"],
- \ ["<xDown>", "<Down>"],
- \ ["<xLeft>", "<Left>"],
- \ ["<xRight>", "<Right>"],
- \ ["<xHome>", "<Home>"],
- \ ["<xEnd>", "<End>"],
- \ ["<zHome>", "<Home>"],
- \ ["<zEnd>", "<End>"],
- \ ["<xF1>", "<F1>"],
- \ ["<xF2>", "<F2>"],
- \ ["<xF3>", "<F3>"],
- \ ["<xF4>", "<F4>"],
- \ ["<S-xF1>", "<S-F1>"],
- \ ["<S-xF2>", "<S-F2>"],
- \ ["<S-xF3>", "<S-F3>"],
- \ ["<S-xF4>", "<S-F4>"]]
- for [k1, k2] in keycodes
- exe "nnoremap " .. k1 .. " 2wx"
- call assert_true(maparg(k1, 'n', 0, 1).lhs == k2)
- exe "nunmap " .. k1
- endfor
-endfunc
-
-" Test for terminal keycodes that doesn't have termcap entries
-func Test_special_term_keycodes()
- new
- " Test for <xHome>, <S-xHome> and <C-xHome>
- " send <K_SPECIAL> <KS_EXTRA> keycode
- call feedkeys("i\<C-K>\x80\xfd\x3f\n", 'xt')
- " send <K_SPECIAL> <KS_MODIFIER> bitmap <K_SPECIAL> <KS_EXTRA> keycode
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3f\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3f\n", 'xt')
- " Test for <xEnd>, <S-xEnd> and <C-xEnd>
- call feedkeys("i\<C-K>\x80\xfd\x3d\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3d\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3d\n", 'xt')
- " Test for <zHome>, <S-zHome> and <C-zHome>
- call feedkeys("i\<C-K>\x80\xfd\x40\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x40\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x40\n", 'xt')
- " Test for <zEnd>, <S-zEnd> and <C-zEnd>
- call feedkeys("i\<C-K>\x80\xfd\x3e\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3e\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3e\n", 'xt')
- " Test for <xUp>, <xDown>, <xLeft> and <xRight>
- call feedkeys("i\<C-K>\x80\xfd\x41\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x42\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x43\n", 'xt')
- call feedkeys("i\<C-K>\x80\xfd\x44\n", 'xt')
- call assert_equal(['<Home>', '<S-Home>', '<C-Home>',
- \ '<End>', '<S-End>', '<C-End>',
- \ '<Home>', '<S-Home>', '<C-Home>',
- \ '<End>', '<S-End>', '<C-End>',
- \ '<Up>', '<Down>', '<Left>', '<Right>', ''], getline(1, '$'))
- bw!
-endfunc
-
-
-" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testing.c b/src/nvim/testing.c
index b5921b3445..cada04d276 100644
--- a/src/nvim/testing.c
+++ b/src/nvim/testing.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// testing.c: Support for tests
#include <inttypes.h>
@@ -9,38 +6,50 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/hashtab.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/os/os.h"
+#include "nvim/os/fs.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/testing.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+
+/// Type of assert_* check being performed
+typedef enum {
+ ASSERT_EQUAL,
+ ASSERT_NOTEQUAL,
+ ASSERT_MATCH,
+ ASSERT_NOTMATCH,
+ ASSERT_FAILS,
+ ASSERT_OTHER,
+} assert_type_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "testing.c.generated.h"
#endif
-static char e_assert_fails_second_arg[]
- = N_("E856: assert_fails() second argument must be a string or a list with one or two strings");
-static char e_assert_fails_fourth_argument[]
- = N_("E1115: assert_fails() fourth argument must be a number");
-static char e_assert_fails_fifth_argument[]
- = N_("E1116: assert_fails() fifth argument must be a string");
-static char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
+static const char e_assert_fails_second_arg[]
+ = N_(
+ "E856: \"assert_fails()\" second argument must be a string or a list with one or two strings");
+static const char e_assert_fails_fourth_argument[]
+ = N_("E1115: \"assert_fails()\" fourth argument must be a number");
+static const char e_assert_fails_fifth_argument[]
+ = N_("E1116: \"assert_fails()\" fifth argument must be a string");
+static const char e_calling_test_garbagecollect_now_while_v_testing_is_not_set[]
= N_("E1142: Calling test_garbagecollect_now() while v:testing is not set");
/// Prepare "gap" for an assert error and add the sourcing position.
@@ -121,7 +130,7 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
for (const char *p = str; *p != NUL; p++) {
int same_len = 1;
const char *s = p;
- const int c = mb_ptr2char_adv(&s);
+ const int c = mb_cptr2char_adv(&s);
const int clen = (int)(s - p);
while (*s != NUL && c == utf_ptr2char(s)) {
same_len++;
@@ -142,10 +151,9 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
}
/// Fill "gap" with information about an assert error.
-static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str,
+static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, const char *exp_str,
typval_T *exp_tv_arg, typval_T *got_tv_arg, assert_type_T atype)
{
- char *tofree;
typval_T *exp_tv = exp_tv_arg;
typval_T *got_tv = got_tv_arg;
bool did_copy = false;
@@ -155,7 +163,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
&& !(opt_msg_tv->v_type == VAR_STRING
&& (opt_msg_tv->vval.v_string == NULL
|| *opt_msg_tv->vval.v_string == NUL))) {
- tofree = encode_tv2echo(opt_msg_tv, NULL);
+ char *tofree = encode_tv2echo(opt_msg_tv, NULL);
ga_concat(gap, tofree);
xfree(tofree);
ga_concat(gap, ": ");
@@ -185,16 +193,14 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
int todo = (int)exp_d->dv_hashtab.ht_used;
for (const hashitem_T *hi = exp_d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- dictitem_T *item2 = tv_dict_find(got_d, (const char *)hi->hi_key, -1);
+ dictitem_T *item2 = tv_dict_find(got_d, hi->hi_key, -1);
if (item2 == NULL
|| !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) {
// item of exp_d not present in got_d or values differ.
const size_t key_len = strlen(hi->hi_key);
- tv_dict_add_tv(exp_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &TV_DICT_HI2DI(hi)->di_tv);
+ tv_dict_add_tv(exp_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
if (item2 != NULL) {
- tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &item2->di_tv);
+ tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &item2->di_tv);
}
} else {
omitted++;
@@ -207,23 +213,28 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
todo = (int)got_d->dv_hashtab.ht_used;
for (const hashitem_T *hi = got_d->dv_hashtab.ht_array; todo > 0; hi++) {
if (!HASHITEM_EMPTY(hi)) {
- dictitem_T *item2 = tv_dict_find(exp_d, (const char *)hi->hi_key, -1);
+ dictitem_T *item2 = tv_dict_find(exp_d, hi->hi_key, -1);
if (item2 == NULL) {
// item of got_d not present in exp_d
const size_t key_len = strlen(hi->hi_key);
- tv_dict_add_tv(got_tv->vval.v_dict, (const char *)hi->hi_key, key_len,
- &TV_DICT_HI2DI(hi)->di_tv);
+ tv_dict_add_tv(got_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
}
todo--;
}
}
}
- tofree = encode_tv2string(exp_tv, NULL);
+ char *tofree = encode_tv2string(exp_tv, NULL);
ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
} else {
+ if (atype == ASSERT_FAILS) {
+ ga_concat(gap, "'");
+ }
ga_concat_shorten_esc(gap, exp_str);
+ if (atype == ASSERT_FAILS) {
+ ga_concat(gap, "'");
+ }
}
if (atype != ASSERT_NOTEQUAL) {
@@ -234,7 +245,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char *exp_str
} else {
ga_concat(gap, " but got ");
}
- tofree = encode_tv2string(got_tv, NULL);
+ char *tofree = encode_tv2string(got_tv, NULL);
ga_concat_shorten_esc(gap, tofree);
xfree(tofree);
@@ -274,11 +285,10 @@ static int assert_match_common(typval_T *argvars, assert_type_T atype)
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
- const int called_emsg_before = called_emsg;
const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1);
const char *const text = tv_get_string_buf_chk(&argvars[1], buf2);
- if (called_emsg == called_emsg_before
+ if (pat != NULL && text != NULL
&& pattern_match(pat, text, false) != (atype == ASSERT_MATCH)) {
garray_T ga;
prepare_assert_error(&ga);
@@ -380,12 +390,10 @@ static int assert_equalfile(typval_T *argvars)
{
char buf1[NUMBUFLEN];
char buf2[NUMBUFLEN];
- const int called_emsg_before = called_emsg;
const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1);
const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2);
- garray_T ga;
- if (called_emsg > called_emsg_before) {
+ if (fname1 == NULL || fname2 == NULL) {
return 0;
}
@@ -395,12 +403,12 @@ static int assert_equalfile(typval_T *argvars)
char line2[200];
ptrdiff_t lineidx = 0;
if (fd1 == NULL) {
- snprintf(IObuff, IOSIZE, (char *)e_notread, fname1);
+ snprintf(IObuff, IOSIZE, e_notread, fname1);
} else {
FILE *const fd2 = os_fopen(fname2, READBIN);
if (fd2 == NULL) {
fclose(fd1);
- snprintf(IObuff, IOSIZE, (char *)e_notread, fname2);
+ snprintf(IObuff, IOSIZE, e_notread, fname2);
} else {
int64_t linecount = 1;
for (int64_t count = 0;; count++) {
@@ -408,11 +416,11 @@ static int assert_equalfile(typval_T *argvars)
const int c2 = fgetc(fd2);
if (c1 == EOF) {
if (c2 != EOF) {
- STRCPY(IObuff, "first file is shorter");
+ xstrlcpy(IObuff, "first file is shorter", IOSIZE);
}
break;
} else if (c2 == EOF) {
- STRCPY(IObuff, "second file is shorter");
+ xstrlcpy(IObuff, "second file is shorter", IOSIZE);
break;
} else {
line1[lineidx] = (char)c1;
@@ -438,7 +446,9 @@ static int assert_equalfile(typval_T *argvars)
fclose(fd2);
}
}
+
if (IObuff[0] != NUL) {
+ garray_T ga;
prepare_assert_error(&ga);
if (argvars[2].v_type != VAR_UNKNOWN) {
char *const tofree = encode_tv2echo(&argvars[2], NULL);
@@ -462,6 +472,7 @@ static int assert_equalfile(typval_T *argvars)
ga_clear(&ga);
return 1;
}
+
return 0;
}
@@ -503,11 +514,21 @@ void f_assert_exception(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// "assert_fails(cmd [, error [, msg]])" function
void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- const char *const cmd = tv_get_string_chk(&argvars[0]);
garray_T ga;
- int save_trylevel = trylevel;
+ const int save_trylevel = trylevel;
const int called_emsg_before = called_emsg;
- char *wrong_arg_msg = NULL;
+ const char *wrong_arg_msg = NULL;
+ char *tofree = NULL;
+
+ if (tv_check_for_string_or_number_arg(argvars, 0) == FAIL
+ || tv_check_for_opt_string_or_list_arg(argvars, 1) == FAIL
+ || (argvars[1].v_type != VAR_UNKNOWN
+ && (argvars[2].v_type != VAR_UNKNOWN
+ && (tv_check_for_opt_number_arg(argvars, 3) == FAIL
+ || (argvars[3].v_type != VAR_UNKNOWN
+ && tv_check_for_opt_string_arg(argvars, 4) == FAIL))))) {
+ return;
+ }
// trylevel must be zero for a ":throw" command to be considered failed
trylevel = 0;
@@ -515,7 +536,13 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
in_assert_fails = true;
no_wait_return++;
+ const char *const cmd = tv_get_string_chk(&argvars[0]);
do_cmdline_cmd(cmd);
+
+ // reset here for any errors reported below
+ trylevel = save_trylevel;
+ suppress_errthrow = false;
+
if (called_emsg == called_emsg_before) {
prepare_assert_error(&ga);
ga_concat(&ga, "command did not fail: ");
@@ -526,6 +553,7 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
} else if (argvars[1].v_type != VAR_UNKNOWN) {
char buf[NUMBUFLEN];
const char *expected;
+ const char *expected_str = NULL;
bool error_found = false;
int error_found_index = 1;
char *actual = emsg_assert_fails_msg == NULL ? "[unknown]" : emsg_assert_fails_msg;
@@ -541,14 +569,23 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
const typval_T *tv = TV_LIST_ITEM_TV(tv_list_first(list));
expected = tv_get_string_buf_chk(tv, buf);
+ if (expected == NULL) {
+ goto theend;
+ }
if (!pattern_match(expected, actual, false)) {
error_found = true;
+ expected_str = expected;
} else if (tv_list_len(list) == 2) {
+ // make a copy, an error in pattern_match() may free it
+ tofree = actual = xstrdup(get_vim_var_str(VV_ERRMSG));
tv = TV_LIST_ITEM_TV(tv_list_last(list));
- actual = get_vim_var_str(VV_ERRMSG);
expected = tv_get_string_buf_chk(tv, buf);
+ if (expected == NULL) {
+ goto theend;
+ }
if (!pattern_match(expected, actual, false)) {
error_found = true;
+ expected_str = expected;
}
}
} else {
@@ -592,8 +629,8 @@ void f_assert_fails(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
actual_tv.v_type = VAR_STRING;
actual_tv.vval.v_string = actual;
}
- fill_assert_error(&ga, &argvars[2], NULL,
- &argvars[error_found_index], &actual_tv, ASSERT_OTHER);
+ fill_assert_error(&ga, &argvars[2], expected_str,
+ &argvars[error_found_index], &actual_tv, ASSERT_FAILS);
ga_concat(&ga, ": ");
assert_append_cmd_or_arg(&ga, argvars, cmd);
assert_error(&ga);
@@ -615,6 +652,7 @@ theend:
msg_reset_scroll();
lines_left = Rows;
XFREE_CLEAR(emsg_assert_fails_msg);
+ xfree(tofree);
set_vim_var_string(VV_ERRMSG, NULL, 0);
if (wrong_arg_msg != NULL) {
emsg(_(wrong_arg_msg));
@@ -642,16 +680,9 @@ static int assert_inrange(typval_T *argvars)
if (factual < flower || factual > fupper) {
garray_T ga;
prepare_assert_error(&ga);
- if (argvars[3].v_type != VAR_UNKNOWN) {
- char *const tofree = encode_tv2string(&argvars[3], NULL);
- ga_concat(&ga, tofree);
- xfree(tofree);
- } else {
- char msg[80];
- vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g",
- flower, fupper, factual);
- ga_concat(&ga, msg);
- }
+ char expected_str[200];
+ vim_snprintf(expected_str, sizeof(expected_str), "range %g - %g,", flower, fupper);
+ fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
return 1;
@@ -667,13 +698,11 @@ static int assert_inrange(typval_T *argvars)
if (actual < lower || actual > upper) {
garray_T ga;
prepare_assert_error(&ga);
-
- char msg[55];
- vim_snprintf(msg, sizeof(msg),
+ char expected_str[200];
+ vim_snprintf(expected_str, sizeof(expected_str),
"range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",",
- lower, upper); // -V576
- fill_assert_error(&ga, &argvars[3], msg, NULL, &argvars[2],
- ASSERT_INRANGE);
+ lower, upper);
+ fill_assert_error(&ga, &argvars[3], expected_str, NULL, &argvars[2], ASSERT_OTHER);
assert_error(&ga);
ga_clear(&ga);
return 1;
@@ -685,6 +714,13 @@ static int assert_inrange(typval_T *argvars)
/// "assert_inrange(lower, upper[, msg])" function
void f_assert_inrange(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
+ if (tv_check_for_float_or_nr_arg(argvars, 0) == FAIL
+ || tv_check_for_float_or_nr_arg(argvars, 1) == FAIL
+ || tv_check_for_float_or_nr_arg(argvars, 2) == FAIL
+ || tv_check_for_opt_string_arg(argvars, 3) == FAIL) {
+ return;
+ }
+
rettv->vval.v_number = assert_inrange(argvars);
}
@@ -737,5 +773,4 @@ void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, EvalF
if (fname == NULL) {
return;
}
- list_write_log(fname);
}
diff --git a/src/nvim/testing.h b/src/nvim/testing.h
index 69596d725c..a34ed209c1 100644
--- a/src/nvim/testing.h
+++ b/src/nvim/testing.h
@@ -1,9 +1,8 @@
-#ifndef NVIM_TESTING_H
-#define NVIM_TESTING_H
+#pragma once
-#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "testing.h.generated.h"
#endif
-#endif // NVIM_TESTING_H
diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c
index e30580a748..b69d438a59 100644
--- a/src/nvim/textformat.c
+++ b/src/nvim/textformat.c
@@ -1,13 +1,10 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// textformat.c: text formatting functions
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
@@ -17,29 +14,30 @@
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/move.h"
-#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/textformat.h"
#include "nvim/textobject.h"
-#include "nvim/types.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -72,11 +70,11 @@ bool has_format_option(int x)
void internal_format(int textwidth, int second_indent, int flags, bool format_only, int c)
{
int cc;
- int save_char = NUL;
+ char save_char = NUL;
bool haveto_redraw = false;
const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK);
- const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
+ const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
const bool fo_white_par = has_format_option(FO_WHITE_PAR);
bool first_line = true;
colnr_T leader_len;
@@ -93,7 +91,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
&& !(State & VREPLACE_FLAG)) {
cc = gchar_cursor();
if (ascii_iswhite(cc)) {
- save_char = cc;
+ save_char = (char)cc;
pchar_cursor('x');
}
}
@@ -104,7 +102,6 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
int wantcol; // column at textwidth border
int foundcol; // column for start of spaces
int end_foundcol = 0; // column for start of word
- colnr_T len;
colnr_T virtcol;
int orig_col = 0;
char *saved_text = NULL;
@@ -309,7 +306,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
col = curwin->w_cursor.col;
inc_cursor();
- cc = ncc;
+ cc = ncc;
ncc = gchar_cursor();
// handle insert
ncc = (ncc != NUL) ? ncc : c;
@@ -318,7 +315,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
if (allow_break) {
// Break only when we are not at end of line.
- end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col;
+ end_foundcol = foundcol = ncc == NUL ? 0 : curwin->w_cursor.col;
break;
}
curwin->w_cursor.col = col;
@@ -423,7 +420,6 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
for (int i = 0; i < padding; i++) {
ins_str(" ");
}
- changed_bytes(curwin->w_cursor.lnum, leader_len);
} else {
(void)set_indent(second_indent, SIN_CHANGED);
}
@@ -441,7 +437,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// Check if cursor is not past the NUL off the line, cindent
// may have added or removed indent.
curwin->w_cursor.col += startcol;
- len = (colnr_T)strlen(get_cursor_line_ptr());
+ colnr_T len = (colnr_T)strlen(get_cursor_line_ptr());
if (curwin->w_cursor.col > len) {
curwin->w_cursor.col = len;
}
@@ -458,7 +454,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
}
if (save_char != NUL) { // put back space after cursor
- pchar_cursor((char_u)save_char);
+ pchar_cursor(save_char);
}
curwin->w_p_lbr = has_lbr;
@@ -521,10 +517,8 @@ static bool ends_in_white(linenr_T lnum)
static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int leader2_len,
char *leader2_flags)
{
- int idx1 = 0, idx2 = 0;
- char *p;
- char *line1;
- char *line2;
+ int idx1 = 0;
+ int idx2 = 0;
if (leader1_len == 0) {
return leader2_len == 0;
@@ -536,7 +530,7 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
// If first leader has 's' flag, the lines can only be joined if there is
// some text after it and the second line has the 'm' flag.
if (leader1_flags != NULL) {
- for (p = leader1_flags; *p && *p != ':'; p++) {
+ for (char *p = leader1_flags; *p && *p != ':'; p++) {
if (*p == COM_FIRST) {
return leader2_len == 0;
}
@@ -563,9 +557,9 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
// Get current line and next line, compare the leaders.
// The first line has to be saved, only one line can be locked at a time.
- line1 = xstrdup(ml_get(lnum));
+ char *line1 = xstrdup(ml_get(lnum));
for (idx1 = 0; ascii_iswhite(line1[idx1]); idx1++) {}
- line2 = ml_get(lnum + 1);
+ char *line2 = ml_get(lnum + 1);
for (idx2 = 0; idx2 < leader2_len; idx2++) {
if (!ascii_iswhite(line2[idx2])) {
if (line1[idx1++] != line2[idx2]) {
@@ -588,7 +582,6 @@ static bool same_leader(linenr_T lnum, int leader1_len, char *leader1_flags, int
/// false when the previous line is in the same paragraph.
static bool paragraph_start(linenr_T lnum)
{
- char *p;
int leader_len = 0; // leader len of current line
char *leader_flags = NULL; // flags for leader of current line
int next_leader_len = 0; // leader len of next line
@@ -597,7 +590,7 @@ static bool paragraph_start(linenr_T lnum)
if (lnum <= 1) {
return true; // start of the file
}
- p = ml_get(lnum - 1);
+ char *p = ml_get(lnum - 1);
if (*p == NUL) {
return true; // after empty line
}
@@ -633,19 +626,14 @@ static bool paragraph_start(linenr_T lnum)
/// @param prev_line may start in previous line
void auto_format(bool trailblank, bool prev_line)
{
- pos_T pos;
- colnr_T len;
- char *old;
- char *new, *pnew;
- int wasatend;
- int cc;
+ char *linep;
if (!has_format_option(FO_AUTO)) {
return;
}
- pos = curwin->w_cursor;
- old = get_cursor_line_ptr();
+ pos_T pos = curwin->w_cursor;
+ char *old = get_cursor_line_ptr();
// may remove added space
check_auto_format(false);
@@ -655,10 +643,10 @@ void auto_format(bool trailblank, bool prev_line)
// in 'formatoptions' and there is a single character before the cursor.
// Otherwise the line would be broken and when typing another non-white
// next they are not joined back together.
- wasatend = (pos.col == (colnr_T)strlen(old));
+ int wasatend = (pos.col == (colnr_T)strlen(old));
if (*old != NUL && !trailblank && wasatend) {
dec_cursor();
- cc = gchar_cursor();
+ int cc = gchar_cursor();
if (!WHITECHAR(cc) && curwin->w_cursor.col > 0
&& has_format_option(FO_ONE_LETTER)) {
dec_cursor();
@@ -708,13 +696,13 @@ void auto_format(bool trailblank, bool prev_line)
// need to add a space when 'w' is in 'formatoptions' to keep a paragraph
// formatted.
if (!wasatend && has_format_option(FO_WHITE_PAR)) {
- new = get_cursor_line_ptr();
- len = (colnr_T)strlen(new);
+ linep = get_cursor_line_ptr();
+ colnr_T len = (colnr_T)strlen(linep);
if (curwin->w_cursor.col == len) {
- pnew = xstrnsave(new, (size_t)len + 2);
- pnew[len] = ' ';
- pnew[len + 1] = NUL;
- ml_replace(curwin->w_cursor.lnum, pnew, false);
+ char *plinep = xstrnsave(linep, (size_t)len + 2);
+ plinep[len] = ' ';
+ plinep[len + 1] = NUL;
+ ml_replace(curwin->w_cursor.lnum, plinep, false);
// remove the space later
did_add_space = true;
} else {
@@ -733,18 +721,16 @@ void auto_format(bool trailblank, bool prev_line)
/// @param end_insert true when ending Insert mode
void check_auto_format(bool end_insert)
{
- int c = ' ';
- int cc;
-
if (!did_add_space) {
return;
}
- cc = gchar_cursor();
+ int cc = gchar_cursor();
if (!WHITECHAR(cc)) {
// Somehow the space was removed already.
did_add_space = false;
} else {
+ int c = ' ';
if (!end_insert) {
inc_cursor();
c = gchar_cursor();
@@ -827,7 +813,7 @@ void op_format(oparg_T *oap, bool keep_cursor)
saved_cursor = oap->cursor_start;
}
- format_lines((linenr_T)oap->line_count, keep_cursor);
+ format_lines(oap->line_count, keep_cursor);
// Leave the cursor at the first non-blank of the last formatted line.
// If the cursor was moved one line back (e.g. with "Q}") go to the next
@@ -886,7 +872,7 @@ void op_formatexpr(oparg_T *oap)
int fex_format(linenr_T lnum, long count, int c)
{
int use_sandbox = was_set_insecurely(curwin, "formatexpr", OPT_LOCAL);
- int r;
+ const sctx_T save_sctx = current_sctx;
// Set v:lnum to the first line number and v:count to the number of lines.
// Set v:char to the character to be inserted (can be NUL).
@@ -896,17 +882,20 @@ int fex_format(linenr_T lnum, long count, int c)
// Make a copy, the option could be changed while calling it.
char *fex = xstrdup(curbuf->b_p_fex);
+ current_sctx = curbuf->b_p_script_ctx[BV_FEX].script_ctx;
+
// Evaluate the function.
if (use_sandbox) {
sandbox++;
}
- r = (int)eval_to_number(fex);
+ int r = (int)eval_to_number(fex);
if (use_sandbox) {
sandbox--;
}
set_vim_var_string(VV_CHAR, NULL, -1);
xfree(fex);
+ current_sctx = save_sctx;
return r;
}
@@ -942,7 +931,7 @@ void format_lines(linenr_T line_count, bool avoid_fex)
// length of a line to force formatting: 3 * 'tw'
const int max_len = comp_textwidth(true) * 3;
- // check for 'q', '2' and '1' in 'formatoptions'
+ // check for 'q', '2', 'n' and 'w' in 'formatoptions'
const bool do_comments = has_format_option(FO_Q_COMS); // format comments
int do_comments_list = 0; // format comments with 'n' or '2'
const bool do_second_indent = has_format_option(FO_Q_SECOND);
@@ -1110,15 +1099,13 @@ void format_lines(linenr_T line_count, bool avoid_fex)
}
if (next_leader_len > 0) {
(void)del_bytes(next_leader_len, false, false);
- mark_col_adjust(curwin->w_cursor.lnum, (colnr_T)0, 0L,
- (long)-next_leader_len, 0);
+ mark_col_adjust(curwin->w_cursor.lnum, 0, 0, -next_leader_len, 0);
} else if (second_indent > 0) { // the "leader" for FO_Q_SECOND
int indent = (int)getwhitecols_curline();
if (indent > 0) {
(void)del_bytes(indent, false, false);
- mark_col_adjust(curwin->w_cursor.lnum,
- (colnr_T)0, 0L, (long)-indent, 0);
+ mark_col_adjust(curwin->w_cursor.lnum, 0, 0, -indent, 0);
}
}
curwin->w_cursor.lnum--;
diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h
index fcc9c2d6f4..25e7152f1b 100644
--- a/src/nvim/textformat.h
+++ b/src/nvim/textformat.h
@@ -1,10 +1,8 @@
-#ifndef NVIM_TEXTFORMAT_H
-#define NVIM_TEXTFORMAT_H
+#pragma once
-#include "nvim/normal.h"
-#include "nvim/pos.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textformat.h.generated.h"
#endif
-#endif // NVIM_TEXTFORMAT_H
diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c
index 8e786c271c..d4310d47a4 100644
--- a/src/nvim/textobject.c
+++ b/src/nvim/textobject.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// textobject.c: functions for text objects
#include <stdbool.h>
@@ -8,28 +5,27 @@
#include <stdio.h>
#include <string.h>
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
#include "nvim/eval/funcs.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/indent.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
-#include "nvim/normal.h"
-#include "nvim/option_defs.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/move.h"
+#include "nvim/option_vars.h"
+#include "nvim/pos_defs.h"
#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/textobject.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textobject.c.generated.h"
@@ -40,14 +36,13 @@
/// sentence when found. If the next sentence is found, return OK. Return FAIL
/// otherwise. See ":h sentence" for the precise definition of a "sentence"
/// text object.
-int findsent(Direction dir, long count)
+int findsent(Direction dir, int count)
{
- pos_T pos, tpos;
int c;
int (*func)(pos_T *);
bool noskip = false; // do not skip blanks
- pos = curwin->w_cursor;
+ pos_T pos = curwin->w_cursor;
if (dir == FORWARD) {
func = incl;
} else {
@@ -84,7 +79,7 @@ int findsent(Direction dir, long count)
bool found_dot = false;
while (c = gchar_pos(&pos), ascii_iswhite(c)
|| vim_strchr(".!?)]\"'", c) != NULL) {
- tpos = pos;
+ pos_T tpos = pos;
if (decl(&tpos) == -1 || (LINEEMPTY(tpos.lnum) && dir == FORWARD)) {
break;
}
@@ -105,7 +100,7 @@ int findsent(Direction dir, long count)
const int startlnum = pos.lnum;
const bool cpo_J = vim_strchr(p_cpo, CPO_ENDOFSENT) != NULL;
- for (;;) { // find end of sentence
+ while (true) { // find end of sentence
c = gchar_pos(&pos);
if (c == NUL || (pos.col == 0 && startPS(pos.lnum, NUL, false))) {
if (dir == BACKWARD && pos.lnum != startlnum) {
@@ -114,7 +109,7 @@ int findsent(Direction dir, long count)
break;
}
if (c == '.' || c == '!' || c == '?') {
- tpos = pos;
+ pos_T tpos = pos;
do {
if ((c = inc(&tpos)) == -1) {
break;
@@ -173,10 +168,9 @@ found:
/// @param pincl Return: true if last char is to be included
///
/// @return true if the next paragraph or section was found.
-bool findpar(bool *pincl, int dir, long count, int what, bool both)
+bool findpar(bool *pincl, int dir, int count, int what, bool both)
{
linenr_T curr;
- bool did_skip; // true after separating lines have been skipped
bool first; // true on first line
linenr_T fold_first; // first line of a closed fold
linenr_T fold_last; // last line of a closed fold
@@ -186,7 +180,7 @@ bool findpar(bool *pincl, int dir, long count, int what, bool both)
curr = curwin->w_cursor.lnum;
while (count--) {
- did_skip = false;
+ bool did_skip = false; // true after separating lines have been skipped
for (first = true;; first = false) {
if (*ml_get(curr) != NUL) {
did_skip = true;
@@ -322,12 +316,8 @@ static int cls(void)
/// If eol is true, last word stops at end of line (for operators).
///
/// @param bigword "W", "E" or "B"
-int fwd_word(long count, bool bigword, bool eol)
+int fwd_word(int count, bool bigword, bool eol)
{
- int sclass; // starting class
- int i;
- int last_line;
-
curwin->w_cursor.coladd = 0;
cls_bigword = bigword;
while (--count >= 0) {
@@ -336,12 +326,12 @@ int fwd_word(long count, bool bigword, bool eol)
if (hasFolding(curwin->w_cursor.lnum, NULL, &curwin->w_cursor.lnum)) {
coladvance(MAXCOL);
}
- sclass = cls();
+ int sclass = cls(); // starting class
// We always move at least one character, unless on the last
// character in the buffer.
- last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
- i = inc_cursor();
+ int last_line = (curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count);
+ int i = inc_cursor();
if (i == -1 || (i >= 1 && last_line)) { // started at last char in file
return FAIL;
}
@@ -380,7 +370,7 @@ int fwd_word(long count, bool bigword, bool eol)
/// If stop is true and we are already on the start of a word, move one less.
///
/// Returns FAIL if top of the file was reached.
-int bck_word(long count, bool bigword, bool stop)
+int bck_word(int count, bool bigword, bool stop)
{
int sclass; // starting class
@@ -420,6 +410,7 @@ int bck_word(long count, bool bigword, bool stop)
finished:
stop = false;
}
+ adjust_skipcol();
return OK;
}
@@ -436,7 +427,7 @@ finished:
///
/// If stop is true and we are already on the end of a word, move one less.
/// If empty is true stop on an empty line.
-int end_word(long count, bool bigword, bool stop, bool empty)
+int end_word(int count, bool bigword, bool stop, bool empty)
{
int sclass; // starting class
@@ -491,15 +482,13 @@ finished:
/// @param eol if true, then stop at end of line.
///
/// @return FAIL if start of the file was reached.
-int bckend_word(long count, bool bigword, bool eol)
+int bckend_word(int count, bool bigword, bool eol)
{
- int sclass; // starting class
- int i;
-
curwin->w_cursor.coladd = 0;
cls_bigword = bigword;
while (--count >= 0) {
- sclass = cls();
+ int i;
+ int sclass = cls(); // starting class
if ((i = dec_cursor()) == -1) {
return FAIL;
}
@@ -526,6 +515,7 @@ int bckend_word(long count, bool bigword, bool eol)
}
}
}
+ adjust_skipcol();
return OK;
}
@@ -548,7 +538,7 @@ static void back_in_line(void)
int sclass; // starting class
sclass = cls();
- for (;;) {
+ while (true) {
if (curwin->w_cursor.col == 0) { // stop at start of line
break;
}
@@ -562,10 +552,8 @@ static void back_in_line(void)
static void find_first_blank(pos_T *posp)
{
- int c;
-
while (decl(posp) != -1) {
- c = gchar_pos(posp);
+ int c = gchar_pos(posp);
if (!ascii_iswhite(c)) {
incl(posp);
break;
@@ -576,10 +564,10 @@ static void find_first_blank(pos_T *posp)
/// Skip count/2 sentences and count/2 separating white spaces.
///
/// @param at_start_sent cursor is at start of sentence
-static void findsent_forward(long count, bool at_start_sent)
+static void findsent_forward(int count, bool at_start_sent)
{
while (count--) {
- findsent(FORWARD, 1L);
+ findsent(FORWARD, 1);
if (at_start_sent) {
find_first_blank(&curwin->w_cursor);
}
@@ -595,10 +583,9 @@ static void findsent_forward(long count, bool at_start_sent)
///
/// @param include true: include word and white space
/// @param bigword false == word, true == WORD
-int current_word(oparg_T *oap, long count, bool include, bool bigword)
+int current_word(oparg_T *oap, int count, bool include, bool bigword)
{
pos_T start_pos;
- pos_T pos;
bool inclusive = true;
int include_white = false;
@@ -621,7 +608,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// (" word"), or start is not on white space, and white space should
// not be included ("word"), find end of word.
if ((cls() == 0) == include) {
- if (end_word(1L, bigword, true, true) == FAIL) {
+ if (end_word(1, bigword, true, true) == FAIL) {
return FAIL;
}
} else {
@@ -630,7 +617,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// space should not be included (" "), find start of word.
// If we end up in the first column of the next line (single char
// word) back up to end of the line.
- fwd_word(1L, bigword, true);
+ fwd_word(1, bigword, true);
if (curwin->w_cursor.col == 0) {
decl(&curwin->w_cursor);
} else {
@@ -662,11 +649,11 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
return FAIL;
}
if (include != (cls() != 0)) {
- if (bck_word(1L, bigword, true) == FAIL) {
+ if (bck_word(1, bigword, true) == FAIL) {
return FAIL;
}
} else {
- if (bckend_word(1L, bigword, true) == FAIL) {
+ if (bckend_word(1, bigword, true) == FAIL) {
return FAIL;
}
(void)incl(&curwin->w_cursor);
@@ -677,7 +664,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
return FAIL;
}
if (include != (cls() == 0)) {
- if (fwd_word(1L, bigword, true) == FAIL && count > 1) {
+ if (fwd_word(1, bigword, true) == FAIL && count > 1) {
return FAIL;
}
// If end is just past a new-line, we don't want to include
@@ -687,7 +674,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
inclusive = false;
}
} else {
- if (end_word(1L, bigword, true, true) == FAIL) {
+ if (end_word(1, bigword, true, true) == FAIL) {
return FAIL;
}
}
@@ -703,7 +690,7 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
// word). Also when "2daw" deletes "word." at the end of the line
// (cursor is at start of next line).
// But don't delete white space at start of line (indent).
- pos = curwin->w_cursor; // save cursor position
+ pos_T pos = curwin->w_cursor; // save cursor position
curwin->w_cursor = start_pos;
if (oneleft() == OK) {
back_in_line();
@@ -735,18 +722,18 @@ int current_word(oparg_T *oap, long count, bool include, bool bigword)
/// Find sentence(s) under the cursor, cursor at end.
/// When Visual active, extend it by one or more sentences.
-int current_sent(oparg_T *oap, long count, bool include)
+int current_sent(oparg_T *oap, int count, bool include)
{
pos_T start_pos;
pos_T pos;
bool start_blank;
int c;
bool at_start_sent;
- long ncount;
+ int ncount;
start_pos = curwin->w_cursor;
pos = start_pos;
- findsent(FORWARD, 1L); // Find start of next sentence.
+ findsent(FORWARD, 1); // Find start of next sentence.
// When the Visual area is bigger than one character: Extend it.
if (VIsual_active && !equalpos(start_pos, VIsual)) {
@@ -768,12 +755,12 @@ extend:
incl(&pos);
}
if (!at_start_sent) {
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
if (equalpos(curwin->w_cursor, start_pos)) {
at_start_sent = true; // exactly at start of sentence
} else {
// inside a sentence, go to its end (start of next)
- findsent(FORWARD, 1L);
+ findsent(FORWARD, 1);
}
}
if (include) { // "as" gets twice as much as "is"
@@ -785,7 +772,7 @@ extend:
}
c = gchar_cursor();
if (!at_start_sent || (!include && !ascii_iswhite(c))) {
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
}
at_start_sent = !at_start_sent;
}
@@ -808,7 +795,7 @@ extend:
incl(&pos);
}
if (at_start_sent) { // in the sentence
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
} else { // in/before white before a sentence
curwin->w_cursor = start_pos;
}
@@ -835,7 +822,7 @@ extend:
find_first_blank(&start_pos); // go back to first blank
} else {
start_blank = false;
- findsent(BACKWARD, 1L);
+ findsent(BACKWARD, 1);
start_pos = curwin->w_cursor;
}
if (include) {
@@ -898,19 +885,16 @@ extend:
/// @param include true == include white space
/// @param what '(', '{', etc.
/// @param other ')', '}', etc.
-int current_block(oparg_T *oap, long count, bool include, int what, int other)
+int current_block(oparg_T *oap, int count, bool include, int what, int other)
{
- pos_T old_pos;
pos_T *pos = NULL;
pos_T start_pos;
pos_T *end_pos;
- pos_T old_start, old_end;
- char *save_cpo;
bool sol = false; // '{' at start of line
- old_pos = curwin->w_cursor;
- old_end = curwin->w_cursor; // remember where we started
- old_start = old_end;
+ pos_T old_pos = curwin->w_cursor;
+ pos_T old_end = curwin->w_cursor; // remember where we started
+ pos_T old_start = old_end;
// If we start on '(', '{', ')', '}', etc., use the whole block inclusive.
if (!VIsual_active || equalpos(VIsual, curwin->w_cursor)) {
@@ -937,7 +921,7 @@ int current_block(oparg_T *oap, long count, bool include, int what, int other)
// Put this position in start_pos.
// Ignore quotes here. Keep the "M" flag in 'cpo', as that is what the
// user wants.
- save_cpo = p_cpo;
+ char *save_cpo = p_cpo;
p_cpo = vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%";
if ((pos = findmatch(NULL, what)) != NULL) {
while (count-- > 0) {
@@ -1042,7 +1026,6 @@ static bool in_html_tag(bool end_tag)
{
char *line = get_cursor_line_ptr();
char *p;
- int c;
int lc = NUL;
pos_T pos;
@@ -1074,11 +1057,11 @@ static bool in_html_tag(bool end_tag)
}
// check that the matching '>' is not preceded by '/'
- for (;;) {
+ while (true) {
if (inc(&pos) < 0) {
return false;
}
- c = (uint8_t)(*ml_get_pos(&pos));
+ int c = (uint8_t)(*ml_get_pos(&pos));
if (c == '>') {
break;
}
@@ -1090,16 +1073,10 @@ static bool in_html_tag(bool end_tag)
/// Find tag block under the cursor, cursor at end.
///
/// @param include true == include white space
-int current_tagblock(oparg_T *oap, long count_arg, bool include)
+int current_tagblock(oparg_T *oap, int count_arg, bool include)
{
- long count = count_arg;
- pos_T old_pos;
- pos_T start_pos;
- pos_T end_pos;
- pos_T old_start, old_end;
- char *p;
+ int count = count_arg;
char *cp;
- int len;
bool do_include = include;
bool save_p_ws = p_ws;
int retval = FAIL;
@@ -1107,9 +1084,9 @@ int current_tagblock(oparg_T *oap, long count_arg, bool include)
p_ws = false;
- old_pos = curwin->w_cursor;
- old_end = curwin->w_cursor; // remember where we started
- old_start = old_end;
+ pos_T old_pos = curwin->w_cursor;
+ pos_T old_end = curwin->w_cursor; // remember where we started
+ pos_T old_start = old_end;
if (!VIsual_active || *p_sel == 'e') {
decl(&old_end); // old_end is inclusive
}
@@ -1152,24 +1129,24 @@ int current_tagblock(oparg_T *oap, long count_arg, bool include)
again:
// Search backwards for unclosed "<aaa>".
// Put this position in start_pos.
- for (long n = 0; n < count; n++) {
+ for (int n = 0; n < count; n++) {
if (do_searchpair("<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
"",
"</[^>]*>", BACKWARD, NULL, 0,
- NULL, (linenr_T)0, 0L) <= 0) {
+ NULL, 0, 0) <= 0) {
curwin->w_cursor = old_pos;
goto theend;
}
}
- start_pos = curwin->w_cursor;
+ pos_T start_pos = curwin->w_cursor;
// Search for matching "</aaa>". First isolate the "aaa".
inc_cursor();
- p = get_cursor_pos_ptr();
+ char *p = get_cursor_pos_ptr();
for (cp = p;
*cp != NUL && *cp != '>' && !ascii_iswhite(*cp);
MB_PTR_ADV(cp)) {}
- len = (int)(cp - p);
+ int len = (int)(cp - p);
if (len == 0) {
curwin->w_cursor = old_pos;
goto theend;
@@ -1182,7 +1159,7 @@ again:
"<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p);
snprintf(epat, epat_len, "</%.*s>\\c", len, p);
- const int r = (int)do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, (linenr_T)0, 0L);
+ const int r = do_searchpair(spat, "", epat, FORWARD, NULL, 0, NULL, 0, 0);
xfree(spat);
xfree(epat);
@@ -1215,7 +1192,7 @@ again:
dec_cursor();
}
}
- end_pos = curwin->w_cursor;
+ pos_T end_pos = curwin->w_cursor;
if (!do_include) {
// Exclude the start tag.
@@ -1274,24 +1251,17 @@ theend:
/// @param include true == include white space
/// @param type 'p' for paragraph, 'S' for section
-int current_par(oparg_T *oap, long count, bool include, int type)
+int current_par(oparg_T *oap, int count, bool include, int type)
{
- linenr_T start_lnum;
- linenr_T end_lnum;
- int white_in_front;
int dir;
- int start_is_white;
- int prev_start_is_white;
int retval = OK;
int do_white = false;
- int t;
- int i;
if (type == 'S') { // not implemented yet
return FAIL;
}
- start_lnum = curwin->w_cursor.lnum;
+ linenr_T start_lnum = curwin->w_cursor.lnum;
// When visual area is more than one line: extend it.
if (VIsual_active && start_lnum != VIsual.lnum) {
@@ -1301,22 +1271,22 @@ extend:
} else {
dir = FORWARD;
}
- for (i = (int)count; --i >= 0;) {
+ for (int i = count; --i >= 0;) {
if (start_lnum ==
(dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) {
retval = FAIL;
break;
}
- prev_start_is_white = -1;
- for (t = 0; t < 2; t++) {
+ int prev_start_is_white = -1;
+ for (int t = 0; t < 2; t++) {
start_lnum += dir;
- start_is_white = linewhite(start_lnum);
+ int start_is_white = linewhite(start_lnum);
if (prev_start_is_white == start_is_white) {
start_lnum -= dir;
break;
}
- for (;;) {
+ while (true) {
if (start_lnum == (dir == BACKWARD
? 1 : curbuf->b_ml.ml_line_count)) {
break;
@@ -1345,7 +1315,7 @@ extend:
}
// First move back to the start_lnum of the paragraph or white lines
- white_in_front = linewhite(start_lnum);
+ int white_in_front = linewhite(start_lnum);
while (start_lnum > 1) {
if (white_in_front) { // stop at first white line
if (!linewhite(start_lnum - 1)) {
@@ -1360,13 +1330,13 @@ extend:
}
// Move past the end of any white lines.
- end_lnum = start_lnum;
+ linenr_T end_lnum = start_lnum;
while (end_lnum <= curbuf->b_ml.ml_line_count && linewhite(end_lnum)) {
end_lnum++;
}
end_lnum--;
- i = (int)count;
+ int i = count;
if (!include && white_in_front) {
i--;
}
@@ -1443,10 +1413,8 @@ extend:
/// @return column number of "quotechar" or -1 when not found.
static int find_next_quote(char *line, int col, int quotechar, char *escape)
{
- int c;
-
- for (;;) {
- c = (uint8_t)line[col];
+ while (true) {
+ int c = (uint8_t)line[col];
if (c == NUL) {
return -1;
} else if (escape != NULL && vim_strchr(escape, c)) {
@@ -1471,12 +1439,10 @@ static int find_next_quote(char *line, int col, int quotechar, char *escape)
/// @return the found column or zero.
static int find_prev_quote(char *line, int col_start, int quotechar, char *escape)
{
- int n;
-
while (col_start > 0) {
col_start--;
col_start -= utf_head_off(line, line + col_start);
- n = 0;
+ int n = 0;
if (escape != NULL) {
while (col_start - n > 0 && vim_strchr(escape,
(uint8_t)line[col_start - n - 1]) != NULL) {
@@ -1498,7 +1464,7 @@ static int find_prev_quote(char *line, int col_start, int quotechar, char *escap
/// @param quotechar Quote character
///
/// @return true if found, else false.
-bool current_quote(oparg_T *oap, long count, bool include, int quotechar)
+bool current_quote(oparg_T *oap, int count, bool include, int quotechar)
FUNC_ATTR_NONNULL_ALL
{
char *line = get_cursor_line_ptr();
@@ -1621,7 +1587,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar)
// Also do this when there is a Visual area, a' may leave the cursor
// in between two strings.
col_start = 0;
- for (;;) {
+ while (true) {
// Find open quote character.
col_start = find_next_quote(line, col_start, quotechar, NULL);
if (col_start < 0 || col_start > first_col) {
diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h
index e31557f297..a540c7c98f 100644
--- a/src/nvim/textobject.h
+++ b/src/nvim/textobject.h
@@ -1,11 +1,9 @@
-#ifndef NVIM_TEXTOBJECT_H
-#define NVIM_TEXTOBJECT_H
+#pragma once
-#include "nvim/normal.h"
-#include "nvim/pos.h"
-#include "nvim/vim.h"
+#include "nvim/normal_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/vim_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "textobject.h.generated.h"
#endif
-#endif // NVIM_TEXTOBJECT_H
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 733aa25f03..bdbb5e4872 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -1,28 +1,23 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
-#include "nvim/charset.h"
#include "nvim/event/defs.h"
-#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/func_attr.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
-#include "nvim/option.h"
-#include "nvim/os/input.h"
+#include "nvim/option_vars.h"
#include "nvim/os/os.h"
+#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/input_defs.h"
#include "nvim/tui/tui.h"
-#include "nvim/types.h"
#include "nvim/ui_client.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -30,10 +25,11 @@
#include "nvim/event/rstream.h"
#include "nvim/msgpack_rpc/channel.h"
+#define READ_STREAM_SIZE 0xfff
#define KEY_BUFFER_SIZE 0xfff
static const struct kitty_key_map_entry {
- KittyKey key;
+ int key;
const char *name;
} kitty_key_map_entry[] = {
{ KITTY_KEY_ESCAPE, "Esc" },
@@ -115,7 +111,7 @@ static const struct kitty_key_map_entry {
{ KITTY_KEY_KP_BEGIN, "kOrigin" },
};
-static Map(KittyKey, cstr_t) kitty_key_map = MAP_INIT;
+static PMap(int) kitty_key_map = MAP_INIT;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/input.c.generated.h"
@@ -126,19 +122,13 @@ void tinput_init(TermInput *input, Loop *loop)
input->loop = loop;
input->paste = 0;
input->in_fd = STDIN_FILENO;
- input->waiting_for_bg_response = 0;
- input->extkeys_type = kExtkeysNone;
- // The main thread is waiting for the UI thread to call CONTINUE, so it can
- // safely access global variables.
+ input->key_encoding = kKeyEncodingLegacy;
input->ttimeout = (bool)p_ttimeout;
input->ttimeoutlen = p_ttm;
input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
- uv_mutex_init(&input->key_buffer_mutex);
- uv_cond_init(&input->key_buffer_cond);
for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) {
- map_put(KittyKey, cstr_t)(&kitty_key_map, kitty_key_map_entry[i].key,
- kitty_key_map_entry[i].name);
+ pmap_put(int)(&kitty_key_map, kitty_key_map_entry[i].key, (ptr_t)kitty_key_map_entry[i].name);
}
input->in_fd = STDIN_FILENO;
@@ -157,17 +147,17 @@ void tinput_init(TermInput *input, Loop *loop)
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
- rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff);
+ rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE);
+ termkey_set_buffer_size(input->tk, rbuffer_capacity(input->read_stream.buffer));
+
// initialize a timer handle for handling ESC with libtermkey
time_watcher_init(loop, &input->timer_handle, input);
}
void tinput_destroy(TermInput *input)
{
- map_destroy(KittyKey, cstr_t)(&kitty_key_map);
+ map_destroy(int, &kitty_key_map);
rbuffer_free(input->key_buffer);
- uv_mutex_destroy(&input->key_buffer_mutex);
- uv_cond_destroy(&input->key_buffer_cond);
time_watcher_close(&input->timer_handle, NULL);
stream_close(&input->read_stream, NULL, NULL);
termkey_destroy(input->tk);
@@ -185,13 +175,14 @@ void tinput_stop(TermInput *input)
}
static void tinput_done_event(void **argv)
+ FUNC_ATTR_NORETURN
{
- input_done();
+ os_exit(1);
}
-static void tinput_wait_enqueue(void **argv)
+/// Send all pending input in key buffer to Nvim server.
+static void tinput_flush(TermInput *input)
{
- TermInput *input = argv[0];
if (input->paste) { // produce exactly one paste event
const size_t len = rbuffer_size(input->key_buffer);
String keys = { .data = xmallocz(len), .size = len };
@@ -207,7 +198,7 @@ static void tinput_wait_enqueue(void **argv)
input->paste = 2;
}
rbuffer_reset(input->key_buffer);
- } else { // enqueue input for the main thread or Nvim server
+ } else { // enqueue input
RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
const String keys = { .data = buf, .size = len };
MAXSIZE_TEMP_ARRAY(args, 1);
@@ -221,42 +212,66 @@ static void tinput_wait_enqueue(void **argv)
}
}
-static void tinput_flush(TermInput *input, bool wait_until_empty)
-{
- size_t drain_boundary = wait_until_empty ? 0 : 0xff;
- do {
- tinput_wait_enqueue((void **)&input);
- } while (rbuffer_size(input->key_buffer) > drain_boundary);
-}
-
static void tinput_enqueue(TermInput *input, char *buf, size_t size)
{
if (rbuffer_size(input->key_buffer) >
rbuffer_capacity(input->key_buffer) - 0xff) {
// don't ever let the buffer get too full or we risk putting incomplete keys
// into it
- tinput_flush(input, false);
+ tinput_flush(input);
}
rbuffer_write(input->key_buffer, buf, size);
}
+/// Handle TERMKEY_KEYMOD_* modifiers, i.e. Shift, Alt and Ctrl.
+///
+/// @return The number of bytes written into "buf", excluding the final NUL.
+static size_t handle_termkey_modifiers(TermKeyKey *key, char *buf, size_t buflen)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t len = 0;
+ if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { // Shift
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
+ }
+ if (key->modifiers & TERMKEY_KEYMOD_ALT) { // Alt
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
+ }
+ if (key->modifiers & TERMKEY_KEYMOD_CTRL) { // Ctrl
+ len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
+ }
+ assert(len < buflen);
+ return len;
+}
+
+/// Handle modifiers not handled by libtermkey.
+/// Currently only Super ("D-") and Meta ("T-") are supported in Nvim.
+///
+/// @return The number of bytes written into "buf", excluding the final NUL.
+static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen)
+ FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ size_t len = 0;
+ if (key->modifiers & 8) { // Super
+ len += (size_t)snprintf(buf + len, buflen - len, "D-");
+ }
+ if (key->modifiers & 32) { // Meta
+ len += (size_t)snprintf(buf + len, buflen - len, "T-");
+ }
+ assert(len < buflen);
+ return len;
+}
+
static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key)
{
- const char *name = map_get(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint);
+ const char *name = pmap_get(int)(&kitty_key_map, (int)key->code.codepoint);
if (name) {
char buf[64];
size_t len = 0;
buf[len++] = '<';
- if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
- }
- if (key->modifiers & TERMKEY_KEYMOD_ALT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
- }
- if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
- }
+ len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
+ len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%s>", name);
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
}
@@ -268,7 +283,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
char *ptr = key->utf8;
if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
- && map_has(KittyKey, cstr_t)(&kitty_key_map, (KittyKey)key->code.codepoint)) {
+ && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
handle_kitty_key_protocol(input, key);
return;
}
@@ -278,6 +293,7 @@ static void forward_simple_utf8(TermInput *input, TermKeyKey *key)
} else {
buf[len++] = *ptr;
}
+ assert(len < sizeof(buf));
ptr++;
}
@@ -297,8 +313,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
} else {
assert(key->modifiers);
if (key->code.codepoint >= 0xE000 && key->code.codepoint <= 0xF8FF
- && map_has(KittyKey, cstr_t)(&kitty_key_map,
- (KittyKey)key->code.codepoint)) {
+ && map_has(int, &kitty_key_map, (int)key->code.codepoint)) {
handle_kitty_key_protocol(input, key);
return;
}
@@ -309,7 +324,7 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
if ((key->modifiers & TERMKEY_KEYMOD_CTRL)
&& !(key->modifiers & TERMKEY_KEYMOD_SHIFT)
&& ASCII_ISUPPER(key->code.codepoint)) {
- assert(len <= 62);
+ assert(len + 2 < sizeof(buf));
// Make room for the S-
memmove(buf + 3, buf + 1, len - 1);
buf[1] = 'S';
@@ -318,6 +333,16 @@ static void forward_modified_utf8(TermInput *input, TermKeyKey *key)
}
}
+ char more_buf[25];
+ size_t more_len = handle_more_modifiers(key, more_buf, sizeof(more_buf));
+ if (more_len > 0) {
+ assert(len + more_len < sizeof(buf));
+ memmove(buf + 1 + more_len, buf + 1, len - 1);
+ memcpy(buf + 1, more_buf, more_len);
+ len += more_len;
+ }
+
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
@@ -341,9 +366,10 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
if (ev == TERMKEY_MOUSE_UNKNOWN && !(key->code.mouse[0] & 0x20)) {
int code = key->code.mouse[0] & ~0x3c;
+ // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Other-buttons
if (code == 66 || code == 67) {
ev = TERMKEY_MOUSE_PRESS;
- button = code - 60;
+ button = code + 4 - 64;
}
}
@@ -355,17 +381,9 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
row--; col--; // Termkey uses 1-based coordinates
buf[len++] = '<';
- if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "S-");
- }
-
- if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "C-");
- }
-
- if (key->modifiers & TERMKEY_KEYMOD_ALT) {
- len += (size_t)snprintf(buf + len, sizeof(buf) - len, "A-");
- }
+ len += handle_termkey_modifiers(key, buf + len, sizeof(buf) - len);
+ // Doesn't actually work because there are only 3 bits (0x1c) for modifiers.
+ // len += handle_more_modifiers(key, buf + len, sizeof(buf) - len);
if (button == 1) {
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Left");
@@ -402,6 +420,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key)
}
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "><%d,%d>", col, row);
+ assert(len < sizeof(buf));
tinput_enqueue(input, buf, len);
}
@@ -425,38 +444,11 @@ static void tk_getkeys(TermInput *input, bool force)
} else if (key.type == TERMKEY_TYPE_MOUSE) {
forward_mouse_event(input, &key);
} else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) {
- // There is no specified limit on the number of parameters a CSI sequence can contain, so just
- // allocate enough space for a large upper bound
- long args[16];
- size_t nargs = 16;
- unsigned long cmd;
- if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) {
- uint8_t intermediate = (cmd >> 16) & 0xFF;
- uint8_t initial = (cmd >> 8) & 0xFF;
- uint8_t command = cmd & 0xFF;
-
- // Currently unused
- (void)intermediate;
-
- if (input->waiting_for_csiu_response > 0) {
- if (initial == '?' && command == 'u') {
- // The first (and only) argument contains the current progressive
- // enhancement flags. Only enable CSI u mode if the first bit
- // (disambiguate escape codes) is not already set
- if (nargs > 0 && (args[0] & 0x1) == 0) {
- input->extkeys_type = kExtkeysCSIu;
- } else {
- input->extkeys_type = kExtkeysNone;
- }
- } else if (initial == '?' && command == 'c') {
- // Received Primary Device Attributes response
- input->waiting_for_csiu_response = 0;
- tui_enable_extkeys(input->tui_data);
- } else {
- input->waiting_for_csiu_response--;
- }
- }
- }
+ handle_unknown_csi(input, &key);
+ } else if (key.type == TERMKEY_TYPE_OSC || key.type == TERMKEY_TYPE_DCS) {
+ handle_term_response(input, &key);
+ } else if (key.type == TERMKEY_TYPE_MODEREPORT) {
+ handle_modereport(input, &key);
}
}
@@ -486,7 +478,7 @@ static void tinput_timer_cb(TimeWatcher *watcher, void *data)
handle_raw_buffer(input, true);
}
tk_getkeys(input, true);
- tinput_flush(input, true);
+ tinput_flush(input);
}
/// Handle focus events.
@@ -534,13 +526,13 @@ static HandleState handle_bracketed_paste(TermInput *input)
if (enable) {
// Flush before starting paste.
- tinput_flush(input, true);
+ tinput_flush(input);
// Paste phase: "first-chunk".
input->paste = 1;
} else if (input->paste) {
// Paste phase: "last-chunk".
input->paste = input->paste == 2 ? 3 : -1;
- tinput_flush(input, true);
+ tinput_flush(input);
// Paste phase: "disabled".
input->paste = 0;
}
@@ -555,124 +547,116 @@ static HandleState handle_bracketed_paste(TermInput *input)
return kNotApplicable;
}
-static void set_bg(char *bgvalue)
+/// Handle an OSC or DCS response sequence from the terminal.
+static void handle_term_response(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
{
- if (ui_client_attached) {
+ const char *str = NULL;
+ if (termkey_interpret_string(input->tk, key, &str) == TERMKEY_RES_KEY) {
+ assert(str != NULL);
+
+ // Send an event to nvim core. This will update the v:termresponse variable
+ // and fire the TermResponse event
MAXSIZE_TEMP_ARRAY(args, 2);
- ADD_C(args, STRING_OBJ(cstr_as_string("term_background")));
- ADD_C(args, STRING_OBJ(cstr_as_string(bgvalue)));
- rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args);
+ ADD_C(args, STATIC_CSTR_AS_OBJ("termresponse"));
+
+ // libtermkey strips the OSC/DCS bytes from the response. We add it back in
+ // so that downstream consumers of v:termresponse can differentiate between
+ // the two.
+ StringBuilder response = KV_INITIAL_VALUE;
+ switch (key->type) {
+ case TERMKEY_TYPE_OSC:
+ kv_printf(response, "\x1b]%s", str);
+ break;
+ case TERMKEY_TYPE_DCS:
+ kv_printf(response, "\x1bP%s", str);
+ break;
+ default:
+ // Key type already checked for OSC/DCS in termkey_interpret_string
+ UNREACHABLE;
+ }
+
+ ADD_C(args, STRING_OBJ(cbuf_as_string(response.items, response.size)));
+ rpc_send_event(ui_client_channel_id, "nvim_ui_term_event", args);
+ kv_destroy(response);
}
}
-// During startup, tui.c requests the background color (see `ext.get_bg`).
-//
-// Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If
-// COLOR matches `rgb:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits,
-// then compute the luminance[1] of the RGB color and classify it as light/dark
-// accordingly. Note that the color components may have anywhere from one to
-// four hex digits, and require scaling accordingly as values out of 4, 8, 12,
-// or 16 bits. Also note the A(lpha) component is optional, and is parsed but
-// ignored in the calculations.
-//
-// [1] https://en.wikipedia.org/wiki/Luma_%28video%29
-HandleState handle_background_color(TermInput *input)
+/// Handle a mode report (DECRPM) sequence from the terminal.
+static void handle_modereport(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
{
- if (input->waiting_for_bg_response <= 0) {
- return kNotApplicable;
+ int initial;
+ int mode;
+ int value;
+ if (termkey_interpret_modereport(input->tk, key, &initial, &mode, &value) == TERMKEY_RES_KEY) {
+ (void)initial; // Unused
+ tui_handle_term_mode(input->tui_data, (TermMode)mode, (TermModeState)value);
}
- size_t count = 0;
- size_t component = 0;
- size_t header_size = 0;
- size_t num_components = 0;
- size_t buf_size = rbuffer_size(input->read_stream.buffer);
- uint16_t rgb[] = { 0, 0, 0 };
- uint16_t rgb_max[] = { 0, 0, 0 };
- bool eat_backslash = false;
- bool done = false;
- bool bad = false;
- if (buf_size >= 9
- && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) {
- header_size = 9;
- num_components = 3;
- } else if (buf_size >= 10
- && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) {
- header_size = 10;
- num_components = 4;
- } else if (buf_size < 10
- && !rbuffer_cmp(input->read_stream.buffer,
- "\x1b]11;rgba", buf_size)) {
- // An incomplete sequence was found, waiting for the next input.
- return kIncomplete;
- } else {
- input->waiting_for_bg_response--;
- if (input->waiting_for_bg_response == 0) {
- DLOG("did not get a response for terminal background query");
- }
- return kNotApplicable;
+}
+
+/// Handle a CSI sequence from the terminal that is unrecognized by libtermkey.
+static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // There is no specified limit on the number of parameters a CSI sequence can
+ // contain, so just allocate enough space for a large upper bound
+ long args[16];
+ size_t nargs = 16;
+ unsigned long cmd;
+ if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) {
+ return;
}
- RBUFFER_EACH(input->read_stream.buffer, c, i) {
- count = i + 1;
- // Skip the header.
- if (i < header_size) {
- continue;
- }
- if (eat_backslash) {
- done = true;
- break;
- } else if (c == '\x07') {
- done = true;
+
+ uint8_t intermediate = (cmd >> 16) & 0xFF;
+ uint8_t initial = (cmd >> 8) & 0xFF;
+ uint8_t command = cmd & 0xFF;
+
+ // Currently unused
+ (void)intermediate;
+
+ switch (command) {
+ case 'u':
+ switch (initial) {
+ case '?':
+ // Kitty keyboard protocol query response.
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+ input->key_encoding = kKeyEncodingKitty;
+ tui_set_key_encoding(input->tui_data);
+ }
+
break;
- } else if (c == '\x1b') {
- eat_backslash = true;
- } else if (bad) {
- // ignore
- } else if ((c == '/') && (++component < num_components)) {
- // work done in condition
- } else if (ascii_isxdigit(c)) {
- if (component < 3 && rgb_max[component] != 0xffff) {
- rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf);
- rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c));
+ }
+ break;
+ case 'c':
+ switch (initial) {
+ case '?':
+ // Primary Device Attributes response
+ if (input->waiting_for_kkp_response) {
+ input->waiting_for_kkp_response = false;
+
+ // Enable the fallback key encoding (if any)
+ tui_set_key_encoding(input->tui_data);
}
- } else {
- bad = true;
+
+ break;
}
+ break;
+ default:
+ break;
}
- if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) {
- rbuffer_consumed(input->read_stream.buffer, count);
- double r = (double)rgb[0] / (double)rgb_max[0];
- double g = (double)rgb[1] / (double)rgb_max[1];
- double b = (double)rgb[2] / (double)rgb_max[2];
- double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601
- bool is_dark = luminance < 0.5;
- char *bgvalue = is_dark ? "dark" : "light";
- DLOG("bg response: %s", bgvalue);
- ui_client_bg_response = is_dark ? kTrue : kFalse;
- set_bg(bgvalue);
- input->waiting_for_bg_response = 0;
- } else if (!done && !bad) {
- // An incomplete sequence was found, waiting for the next input.
- return kIncomplete;
- } else {
- input->waiting_for_bg_response = 0;
- rbuffer_consumed(input->read_stream.buffer, count);
- DLOG("failed to parse bg response");
- return kNotApplicable;
- }
- return kComplete;
}
static void handle_raw_buffer(TermInput *input, bool force)
{
HandleState is_paste = kNotApplicable;
- HandleState is_bc = kNotApplicable;
do {
if (!force
&& (handle_focus_event(input)
- || (is_paste = handle_bracketed_paste(input)) != kNotApplicable
- || (is_bc = handle_background_color(input)) != kNotApplicable)) {
- if (is_paste == kIncomplete || is_bc == kIncomplete) {
+ || (is_paste = handle_bracketed_paste(input)) != kNotApplicable)) {
+ if (is_paste == kIncomplete) {
// Wait for the next input, leaving it in the raw buffer due to an
// incomplete sequence.
return;
@@ -710,9 +694,9 @@ static void handle_raw_buffer(TermInput *input, bool force)
RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len));
// termkey_push_bytes can return (size_t)-1, so it is possible that
- // `consumed > input->read_stream.buffer->size`, but since tk_getkeys is
+ // `consumed > rbuffer_size(input->read_stream.buffer)`, but since tk_getkeys is
// called soon, it shouldn't happen.
- assert(consumed <= input->read_stream.buffer->size);
+ assert(consumed <= rbuffer_size(input->read_stream.buffer));
rbuffer_consumed(input->read_stream.buffer, consumed);
// Process the keys now: there is no guarantee `count` will
// fit into libtermkey's input buffer.
@@ -734,15 +718,15 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *da
}
handle_raw_buffer(input, false);
- tinput_flush(input, true);
+ tinput_flush(input);
// An incomplete sequence was found. Leave it in the raw buffer and wait for
// the next input.
if (rbuffer_size(input->read_stream.buffer)) {
// If 'ttimeout' is not set, start the timer with a timeout of 0 to process
// the next input.
- long ms = input->ttimeout ?
- (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0;
+ int64_t ms = input->ttimeout
+ ? (input->ttimeoutlen >= 0 ? input->ttimeoutlen : 0) : 0;
// Stop the current timer if already running
time_watcher_stop(&input->timer_handle);
time_watcher_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index d33cea6383..2743a5e286 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TUI_INPUT_H
-#define NVIM_TUI_INPUT_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -10,33 +9,34 @@
#include "nvim/event/stream.h"
#include "nvim/event/time.h"
#include "nvim/rbuffer.h"
-#include "nvim/tui/input_defs.h"
+#include "nvim/tui/input_defs.h" // IWYU pragma: export
#include "nvim/tui/tui.h"
+#include "nvim/types_defs.h"
typedef enum {
- kExtkeysNone,
- kExtkeysCSIu,
- kExtkeysXterm,
-} ExtkeysType;
+ kKeyEncodingLegacy, ///< Legacy key encoding
+ kKeyEncodingKitty, ///< Kitty keyboard protocol encoding
+ kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS)
+} KeyEncoding;
-typedef struct term_input {
+typedef struct {
int in_fd;
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
int8_t paste;
- bool waiting;
bool ttimeout;
- int8_t waiting_for_bg_response;
- int8_t waiting_for_csiu_response;
- ExtkeysType extkeys_type;
- long ttimeoutlen;
+
+ bool waiting_for_kkp_response; ///< True if we are expecting to receive a response to a query for
+ ///< Kitty keyboard protocol support
+
+ KeyEncoding key_encoding; ///< The key encoding used by the terminal emulator
+
+ OptInt ttimeoutlen;
TermKey *tk;
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
TimeWatcher timer_handle;
Loop *loop;
Stream read_stream;
RBuffer *key_buffer;
- uv_mutex_t key_buffer_mutex;
- uv_cond_t key_buffer_cond;
TUIData *tui_data;
} TermInput;
@@ -49,5 +49,3 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/input.h.generated.h"
#endif
-
-#endif // NVIM_TUI_INPUT_H
diff --git a/src/nvim/tui/input_defs.h b/src/nvim/tui/input_defs.h
index 846cf45350..9aea8460c8 100644
--- a/src/nvim/tui/input_defs.h
+++ b/src/nvim/tui/input_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TUI_INPUT_DEFS_H
-#define NVIM_TUI_INPUT_DEFS_H
+#pragma once
typedef enum {
KITTY_KEY_ESCAPE = 57344,
@@ -114,5 +113,3 @@ typedef enum {
KITTY_KEY_ISO_LEVEL3_SHIFT = 57453,
KITTY_KEY_ISO_LEVEL5_SHIFT = 57454,
} KittyKey;
-
-#endif // NVIM_TUI_INPUT_DEFS_H
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index d630a34ce2..3cf9650428 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Built-in fallback terminfo entries.
#include <stdbool.h>
@@ -10,7 +7,7 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/memory.h"
#include "nvim/strings.h"
diff --git a/src/nvim/tui/terminfo.h b/src/nvim/tui/terminfo.h
index 178d384457..039c722d25 100644
--- a/src/nvim/tui/terminfo.h
+++ b/src/nvim/tui/terminfo.h
@@ -1,12 +1,9 @@
-#ifndef NVIM_TUI_TERMINFO_H
-#define NVIM_TUI_TERMINFO_H
+#pragma once
-#include <unibilium.h>
+#include <unibilium.h> // IWYU pragma: keep
-#include "nvim/api/private/defs.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/terminfo.h.generated.h"
#endif
-
-#endif // NVIM_TUI_TERMINFO_H
diff --git a/src/nvim/tui/terminfo_defs.h b/src/nvim/tui/terminfo_defs.h
index 0bc4972dd3..e221e80d1e 100644
--- a/src/nvim/tui/terminfo_defs.h
+++ b/src/nvim/tui/terminfo_defs.h
@@ -1,14 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// uncrustify:off
-//
-// Generated by scripts/update_terminfo.sh and ncurses 6.3.20211021
-//
+// Generated by scripts/update_terminfo.sh and ncurses 6.4.20221231
-#ifndef NVIM_TUI_TERMINFO_DEFS_H
-#define NVIM_TUI_TERMINFO_DEFS_H
+#pragma once
#include <stdint.h>
@@ -2309,4 +2303,3 @@ static const int8_t win32con_terminfo[] = {
static const int8_t xterm_256colour_terminfo[] = {
30,2,37,0,38,0,15,0,-99,1,51,6,120,116,101,114,109,45,50,53,54,99,111,108,111,114,124,120,116,101,114,109,32,119,105,116,104,32,50,53,54,32,99,111,108,111,114,115,0,0,1,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1,0,80,0,0,0,8,0,0,0,24,0,0,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,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,0,0,0,0,1,0,0,0,4,0,6,0,8,0,25,0,30,0,38,0,42,0,46,0,-1,-1,57,0,74,0,76,0,80,0,87,0,-1,-1,89,0,102,0,-1,-1,106,0,110,0,120,0,124,0,-1,-1,-1,-1,-128,0,-124,0,-119,0,-114,0,-1,-1,-96,0,-91,0,-86,0,-1,-1,-81,0,-76,0,-71,0,-66,0,-57,0,-53,0,-46,0,-1,-1,-28,0,-23,0,-17,0,-11,0,-1,-1,-1,-1,-1,-1,7,1,-1,-1,-1,-1,-1,-1,25,1,-1,-1,29,1,-1,-1,-1,-1,-1,-1,31,1,-1,-1,36,1,-1,-1,-1,-1,-1,-1,-1,-1,40,1,44,1,50,1,54,1,58,1,62,1,68,1,74,1,80,1,86,1,92,1,96,1,-1,-1,101,1,-1,-1,105,1,110,1,115,1,119,1,126,1,-1,-1,-123,1,-119,1,-111,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-103,1,-94,1,-85,1,-1,-1,-82,1,-73,1,-64,1,-55,1,-46,1,-37,1,-28,1,-19,1,-10,1,-1,1,-1,-1,-1,-1,-1,-1,8,2,12,2,17,2,22,2,42,2,51,2,-1,-1,-1,-1,69,2,72,2,83,2,86,2,88,2,91,2,-72,2,-1,-1,-69,2,-1,-1,-1,-1,-1,-1,-1,-1,-67,2,-63,2,-59,2,-55,2,-51,2,-1,-1,-1,-1,-47,2,-1,-1,6,3,-1,-1,-1,-1,10,3,16,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,3,26,3,-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,-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,30,3,-1,-1,-1,-1,37,3,-1,-1,-1,-1,-1,-1,-1,-1,44,3,51,3,58,3,-1,-1,-1,-1,65,3,-1,-1,72,3,-1,-1,-1,-1,-1,-1,79,3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,86,3,92,3,98,3,105,3,112,3,119,3,126,3,-122,3,-114,3,-106,3,-98,3,-90,3,-82,3,-74,3,-66,3,-59,3,-52,3,-45,3,-38,3,-30,3,-22,3,-14,3,-6,3,2,4,10,4,18,4,26,4,33,4,40,4,47,4,54,4,62,4,70,4,78,4,86,4,94,4,102,4,110,4,118,4,125,4,-124,4,-117,4,-110,4,-102,4,-94,4,-86,4,-78,4,-70,4,-62,4,-54,4,-46,4,-39,4,-32,4,-25,4,-20,4,-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,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-13,4,-2,4,3,5,22,5,26,5,35,5,42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-120,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-115,5,-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,-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,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-109,5,-1,-1,-1,-1,-1,-1,-105,5,-42,5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,22,6,-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,-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,-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,-1,-1,-1,-1,-1,-1,-1,-1,-1,45,6,48,6,27,91,90,0,7,0,13,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,114,0,27,91,51,103,0,27,91,72,27,91,50,74,0,27,91,75,0,27,91,74,0,27,91,37,105,37,112,49,37,100,71,0,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,72,0,10,0,27,91,72,0,27,91,63,50,53,108,0,8,0,27,91,63,49,50,108,27,91,63,50,53,104,0,27,91,67,0,27,91,65,0,27,91,63,49,50,59,50,53,104,0,27,91,80,0,27,91,77,0,27,40,48,0,27,91,53,109,0,27,91,49,109,0,27,91,63,49,48,52,57,104,27,91,50,50,59,48,59,48,116,0,27,91,50,109,0,27,91,52,104,0,27,91,56,109,0,27,91,55,109,0,27,91,55,109,0,27,91,52,109,0,27,91,37,112,49,37,100,88,0,27,40,66,0,27,40,66,27,91,109,0,27,91,63,49,48,52,57,108,27,91,50,51,59,48,59,48,116,0,27,91,52,108,0,27,91,50,55,109,0,27,91,50,52,109,0,27,91,63,53,104,36,60,49,48,48,47,62,27,91,63,53,108,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,91,76,0,8,0,27,91,51,126,0,27,79,66,0,27,79,80,0,27,91,50,49,126,0,27,79,81,0,27,79,82,0,27,79,83,0,27,91,49,53,126,0,27,91,49,55,126,0,27,91,49,56,126,0,27,91,49,57,126,0,27,91,50,48,126,0,27,79,72,0,27,91,50,126,0,27,79,68,0,27,91,54,126,0,27,91,53,126,0,27,79,67,0,27,91,49,59,50,66,0,27,91,49,59,50,65,0,27,79,65,0,27,91,63,49,108,27,62,0,27,91,63,49,104,27,61,0,27,91,63,49,48,51,52,108,0,27,91,63,49,48,51,52,104,0,27,69,0,27,91,37,112,49,37,100,80,0,27,91,37,112,49,37,100,77,0,27,91,37,112,49,37,100,66,0,27,91,37,112,49,37,100,64,0,27,91,37,112,49,37,100,83,0,27,91,37,112,49,37,100,76,0,27,91,37,112,49,37,100,68,0,27,91,37,112,49,37,100,67,0,27,91,37,112,49,37,100,84,0,27,91,37,112,49,37,100,65,0,27,91,105,0,27,91,52,105,0,27,91,53,105,0,37,112,49,37,99,27,91,37,112,50,37,123,49,125,37,45,37,100,98,0,27,99,27,93,49,48,52,7,0,27,91,33,112,27,91,63,51,59,52,108,27,91,52,108,27,62,0,27,56,0,27,91,37,105,37,112,49,37,100,100,0,27,55,0,10,0,27,77,0,37,63,37,112,57,37,116,27,40,48,37,101,27,40,66,37,59,27,91,48,37,63,37,112,54,37,116,59,49,37,59,37,63,37,112,53,37,116,59,50,37,59,37,63,37,112,50,37,116,59,52,37,59,37,63,37,112,49,37,112,51,37,124,37,116,59,55,37,59,37,63,37,112,52,37,116,59,53,37,59,37,63,37,112,55,37,116,59,56,37,59,109,0,27,72,0,9,0,27,79,119,0,27,79,121,0,27,79,117,0,27,79,113,0,27,79,115,0,96,96,97,97,102,102,103,103,105,105,106,106,107,107,108,108,109,109,110,110,111,111,112,112,113,113,114,114,115,115,116,116,117,117,118,118,119,119,120,120,121,121,122,122,123,123,124,124,125,125,126,126,0,27,91,90,0,27,91,63,55,104,0,27,91,63,55,108,0,27,79,70,0,27,79,77,0,27,91,51,59,50,126,0,27,91,49,59,50,70,0,27,91,49,59,50,72,0,27,91,50,59,50,126,0,27,91,49,59,50,68,0,27,91,54,59,50,126,0,27,91,53,59,50,126,0,27,91,49,59,50,67,0,27,91,50,51,126,0,27,91,50,52,126,0,27,91,49,59,50,80,0,27,91,49,59,50,81,0,27,91,49,59,50,82,0,27,91,49,59,50,83,0,27,91,49,53,59,50,126,0,27,91,49,55,59,50,126,0,27,91,49,56,59,50,126,0,27,91,49,57,59,50,126,0,27,91,50,48,59,50,126,0,27,91,50,49,59,50,126,0,27,91,50,51,59,50,126,0,27,91,50,52,59,50,126,0,27,91,49,59,53,80,0,27,91,49,59,53,81,0,27,91,49,59,53,82,0,27,91,49,59,53,83,0,27,91,49,53,59,53,126,0,27,91,49,55,59,53,126,0,27,91,49,56,59,53,126,0,27,91,49,57,59,53,126,0,27,91,50,48,59,53,126,0,27,91,50,49,59,53,126,0,27,91,50,51,59,53,126,0,27,91,50,52,59,53,126,0,27,91,49,59,54,80,0,27,91,49,59,54,81,0,27,91,49,59,54,82,0,27,91,49,59,54,83,0,27,91,49,53,59,54,126,0,27,91,49,55,59,54,126,0,27,91,49,56,59,54,126,0,27,91,49,57,59,54,126,0,27,91,50,48,59,54,126,0,27,91,50,49,59,54,126,0,27,91,50,51,59,54,126,0,27,91,50,52,59,54,126,0,27,91,49,59,51,80,0,27,91,49,59,51,81,0,27,91,49,59,51,82,0,27,91,49,59,51,83,0,27,91,49,53,59,51,126,0,27,91,49,55,59,51,126,0,27,91,49,56,59,51,126,0,27,91,49,57,59,51,126,0,27,91,50,48,59,51,126,0,27,91,50,49,59,51,126,0,27,91,50,51,59,51,126,0,27,91,50,52,59,51,126,0,27,91,49,59,52,80,0,27,91,49,59,52,81,0,27,91,49,59,52,82,0,27,91,49,75,0,27,91,63,54,57,108,0,27,91,37,105,37,100,59,37,100,82,0,27,91,54,110,0,27,91,63,37,91,59,48,49,50,51,52,53,54,55,56,57,93,99,0,27,91,99,0,27,91,51,57,59,52,57,109,0,27,93,49,48,52,7,0,27,93,52,59,37,112,49,37,100,59,114,103,98,58,37,112,50,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,51,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,47,37,112,52,37,123,50,53,53,125,37,42,37,123,49,48,48,48,125,37,47,37,50,46,50,88,27,92,0,27,91,51,109,0,27,91,50,51,109,0,27,91,60,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,51,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,57,37,112,49,37,123,56,125,37,45,37,100,37,101,51,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,37,63,37,112,49,37,123,56,125,37,60,37,116,52,37,112,49,37,100,37,101,37,112,49,37,123,49,54,125,37,60,37,116,49,48,37,112,49,37,123,56,125,37,45,37,100,37,101,52,56,59,53,59,37,112,49,37,100,37,59,109,0,27,91,63,54,57,104,27,91,37,105,37,112,49,37,100,59,37,112,50,37,100,115,0,27,108,0,27,109,0,0,2,0,0,0,74,0,-106,0,-84,3,1,1,0,0,7,0,19,0,24,0,42,0,48,0,58,0,90,0,97,0,104,0,111,0,118,0,125,0,-124,0,-117,0,-110,0,-103,0,-96,0,-89,0,-82,0,-75,0,-68,0,-61,0,-54,0,-47,0,-40,0,-33,0,-26,0,-19,0,-12,0,-5,0,2,1,9,1,16,1,23,1,30,1,37,1,44,1,51,1,58,1,65,1,72,1,79,1,86,1,93,1,100,1,107,1,114,1,121,1,-128,1,-121,1,-114,1,-107,1,-100,1,-93,1,-86,1,-79,1,-72,1,-65,1,-58,1,-54,1,-50,1,-46,1,-42,1,-38,1,-34,1,-30,1,-26,1,-22,1,-18,1,-14,1,-10,1,-4,1,1,2,0,0,3,0,6,0,9,0,12,0,15,0,18,0,21,0,24,0,27,0,32,0,37,0,42,0,47,0,52,0,56,0,61,0,66,0,71,0,76,0,81,0,87,0,93,0,99,0,105,0,111,0,117,0,123,0,-127,0,-121,0,-115,0,-110,0,-105,0,-100,0,-95,0,-90,0,-84,0,-78,0,-72,0,-66,0,-60,0,-54,0,-48,0,-42,0,-36,0,-30,0,-24,0,-18,0,-12,0,-6,0,0,1,6,1,12,1,18,1,24,1,30,1,34,1,39,1,44,1,49,1,54,1,59,1,63,1,67,1,71,1,75,1,79,1,85,1,91,1,97,1,103,1,109,1,115,1,121,1,126,1,-125,1,27,93,49,49,50,7,0,27,93,49,50,59,37,112,49,37,115,7,0,27,91,51,74,0,27,93,53,50,59,37,112,49,37,115,59,37,112,50,37,115,7,0,27,91,50,32,113,0,27,91,37,112,49,37,100,32,113,0,27,91,63,49,48,48,54,59,49,48,48,48,37,63,37,112,49,37,123,49,125,37,61,37,116,104,37,101,108,37,59,0,27,91,51,59,51,126,0,27,91,51,59,52,126,0,27,91,51,59,53,126,0,27,91,51,59,54,126,0,27,91,51,59,55,126,0,27,91,49,59,50,66,0,27,91,49,59,51,66,0,27,91,49,59,52,66,0,27,91,49,59,53,66,0,27,91,49,59,54,66,0,27,91,49,59,55,66,0,27,91,49,59,51,70,0,27,91,49,59,52,70,0,27,91,49,59,53,70,0,27,91,49,59,54,70,0,27,91,49,59,55,70,0,27,91,49,59,51,72,0,27,91,49,59,52,72,0,27,91,49,59,53,72,0,27,91,49,59,54,72,0,27,91,49,59,55,72,0,27,91,50,59,51,126,0,27,91,50,59,52,126,0,27,91,50,59,53,126,0,27,91,50,59,54,126,0,27,91,50,59,55,126,0,27,91,49,59,51,68,0,27,91,49,59,52,68,0,27,91,49,59,53,68,0,27,91,49,59,54,68,0,27,91,49,59,55,68,0,27,91,54,59,51,126,0,27,91,54,59,52,126,0,27,91,54,59,53,126,0,27,91,54,59,54,126,0,27,91,54,59,55,126,0,27,91,53,59,51,126,0,27,91,53,59,52,126,0,27,91,53,59,53,126,0,27,91,53,59,54,126,0,27,91,53,59,55,126,0,27,91,49,59,51,67,0,27,91,49,59,52,67,0,27,91,49,59,53,67,0,27,91,49,59,54,67,0,27,91,49,59,55,67,0,27,91,49,59,50,65,0,27,91,49,59,51,65,0,27,91,49,59,52,65,0,27,91,49,59,53,65,0,27,91,49,59,54,65,0,27,91,49,59,55,65,0,27,79,120,0,27,79,116,0,27,79,118,0,27,79,114,0,27,79,69,0,27,79,107,0,27,79,108,0,27,79,111,0,27,79,110,0,27,79,106,0,27,79,109,0,27,79,112,0,27,91,50,57,109,0,27,91,57,109,0,27,91,60,37,105,37,112,51,37,100,59,37,112,49,37,100,59,37,112,50,37,100,59,37,63,37,112,52,37,116,77,37,101,109,37,59,0,65,88,0,88,84,0,67,114,0,67,115,0,69,51,0,77,115,0,83,101,0,83,115,0,88,77,0,107,68,67,51,0,107,68,67,52,0,107,68,67,53,0,107,68,67,54,0,107,68,67,55,0,107,68,78,0,107,68,78,51,0,107,68,78,52,0,107,68,78,53,0,107,68,78,54,0,107,68,78,55,0,107,69,78,68,51,0,107,69,78,68,52,0,107,69,78,68,53,0,107,69,78,68,54,0,107,69,78,68,55,0,107,72,79,77,51,0,107,72,79,77,52,0,107,72,79,77,53,0,107,72,79,77,54,0,107,72,79,77,55,0,107,73,67,51,0,107,73,67,52,0,107,73,67,53,0,107,73,67,54,0,107,73,67,55,0,107,76,70,84,51,0,107,76,70,84,52,0,107,76,70,84,53,0,107,76,70,84,54,0,107,76,70,84,55,0,107,78,88,84,51,0,107,78,88,84,52,0,107,78,88,84,53,0,107,78,88,84,54,0,107,78,88,84,55,0,107,80,82,86,51,0,107,80,82,86,52,0,107,80,82,86,53,0,107,80,82,86,54,0,107,80,82,86,55,0,107,82,73,84,51,0,107,82,73,84,52,0,107,82,73,84,53,0,107,82,73,84,54,0,107,82,73,84,55,0,107,85,80,0,107,85,80,51,0,107,85,80,52,0,107,85,80,53,0,107,85,80,54,0,107,85,80,55,0,107,97,50,0,107,98,49,0,107,98,51,0,107,99,50,0,107,112,53,0,107,112,65,68,68,0,107,112,67,77,65,0,107,112,68,73,86,0,107,112,68,79,84,0,107,112,77,85,76,0,107,112,83,85,66,0,107,112,90,82,79,0,114,109,120,120,0,115,109,120,120,0,120,109,0
};
-#endif // NVIM_TUI_TERMINFO_DEFS_H
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index f760e99262..197bbcabb5 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Terminal UI functions. Invoked (by ui_client.c) on the UI process.
#include <assert.h>
@@ -16,33 +13,35 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/cursor_shape.h"
-#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
-#include "nvim/event/multiqueue.h"
#include "nvim/event/signal.h"
#include "nvim/event/stream.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
+#include "nvim/grid.h"
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/os/input.h"
#include "nvim/os/os.h"
-#include "nvim/ui_client.h"
-#ifdef MSWIN
-# include "nvim/os/os_win_console.h"
-#endif
#include "nvim/tui/input.h"
#include "nvim/tui/terminfo.h"
#include "nvim/tui/tui.h"
+#include "nvim/types_defs.h"
#include "nvim/ugrid.h"
#include "nvim/ui.h"
+#include "nvim/ui_client.h"
+
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+# include "nvim/os/tty.h"
+#endif
// Space reserved in two output buffers to make the cursor normal or invisible
// when flushing. No existing terminal will require 32 bytes to do that.
@@ -58,27 +57,14 @@
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
-#ifdef NVIM_UNIBI_HAS_VAR_FROM
-# define UNIBI_SET_NUM_VAR(var, num) \
+#define UNIBI_SET_NUM_VAR(var, num) \
do { \
(var) = unibi_var_from_num((num)); \
} while (0)
-# define UNIBI_SET_STR_VAR(var, str) \
+#define UNIBI_SET_STR_VAR(var, str) \
do { \
(var) = unibi_var_from_str((str)); \
} while (0)
-#else
-# define UNIBI_SET_NUM_VAR(var, num) \
- do { \
- (var).p = NULL; \
- (var).i = (num); \
- } while (0)
-# define UNIBI_SET_STR_VAR(var, str) \
- do { \
- (var).i = INT_MIN; \
- (var).p = str; \
- } while (0)
-#endif
typedef struct {
int top, bot, left, right;
@@ -89,9 +75,6 @@ struct TUIData {
unibi_var_t params[9];
char buf[OUTBUF_SIZE];
size_t bufpos;
- char norm[CNORM_COMMAND_MAX_SIZE];
- char invis[CNORM_COMMAND_MAX_SIZE];
- size_t normlen, invislen;
TermInput input;
uv_loop_t write_loop;
unibi_term *ut;
@@ -117,6 +100,8 @@ struct TUIData {
bool bce;
bool mouse_enabled;
bool mouse_move_enabled;
+ bool title_enabled;
+ bool sync_output;
bool busy, is_invisible, want_invisible;
bool cork, overflow;
bool set_cursor_color_as_str;
@@ -146,14 +131,13 @@ struct TUIData {
int reset_scroll_region;
int set_cursor_style, reset_cursor_style;
int save_title, restore_title;
- int get_bg;
int set_underline_style;
int set_underline_color;
- int enable_extended_keys, disable_extended_keys;
- int get_extkeys;
+ int sync;
} unibi_ext;
char *space_buf;
bool stopped;
+ int seen_error_exit;
int width;
int height;
bool rgb;
@@ -165,12 +149,13 @@ static bool cursor_style_enabled = false;
# include "tui/tui.c.generated.h"
#endif
-TUIData *tui_start(int *width, int *height, char **term)
+void tui_start(TUIData **tui_p, int *width, int *height, char **term)
{
TUIData *tui = xcalloc(1, sizeof(TUIData));
tui->is_starting = true;
tui->screenshot = NULL;
tui->stopped = false;
+ tui->seen_error_exit = 0;
tui->loop = &main_loop;
kv_init(tui->invalid_regions);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
@@ -188,45 +173,92 @@ TUIData *tui_start(int *width, int *height, char **term)
uv_timer_start(&tui->startup_delay_timer, after_startup_cb,
100, 0);
+ *tui_p = tui;
loop_poll_events(&main_loop, 1);
*width = tui->width;
*height = tui->height;
*term = tui->term;
- return tui;
}
-void tui_enable_extkeys(TUIData *tui)
+void tui_set_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
{
- TermInput input = tui->input;
- unibi_term *ut = tui->ut;
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[>1u"));
+ break;
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;2m"));
+ break;
+ case kKeyEncodingLegacy:
+ break;
+ }
+}
- switch (input.extkeys_type) {
- case kExtkeysCSIu:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>1u");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[<1u");
+static void tui_reset_key_encoding(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (tui->input.key_encoding) {
+ case kKeyEncodingKitty:
+ out(tui, S_LEN("\x1b[<1u"));
break;
- case kExtkeysXterm:
- tui->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys",
- "\x1b[>4;2m");
- tui->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys",
- "\x1b[>4;0m");
+ case kKeyEncodingXterm:
+ out(tui, S_LEN("\x1b[>4;0m"));
break;
- default:
+ case kKeyEncodingLegacy:
break;
}
+}
- unibi_out_ext(tui, tui->unibi_ext.enable_extended_keys);
+/// Request the terminal's mode (DECRQM).
+///
+/// @see handle_modereport
+static void tui_request_term_mode(TUIData *tui, TermMode mode)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // 5 bytes for \x1b[?$p, 1 byte for null terminator, 6 bytes for mode digits (more than enough)
+ char buf[12];
+ int len = snprintf(buf, sizeof(buf), "\x1b[?%d$p", (int)mode);
+ assert((len > 0) && (len < (int)sizeof(buf)));
+ out(tui, buf, (size_t)len);
}
-static size_t unibi_pre_fmt_str(TUIData *tui, unsigned int unibi_index, char *buf, size_t len)
+/// Handle a mode report (DECRPM) from the terminal.
+void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
+ FUNC_ATTR_NONNULL_ALL
{
- const char *str = unibi_get_str(tui->ut, unibi_index);
- if (!str) {
- return 0U;
+ switch (state) {
+ case kTermModeNotRecognized:
+ case kTermModePermanentlySet:
+ case kTermModePermanentlyReset:
+ // If the mode is not recognized, or if the terminal emulator does not allow it to be changed,
+ // then there is nothing to do
+ break;
+ case kTermModeSet:
+ case kTermModeReset:
+ // The terminal supports changing the given mode
+ switch (mode) {
+ case kTermModeSynchronizedOutput:
+ // Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
+ tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync",
+ "\x1b[?2026%?%p1%{1}%-%tl%eh%;");
+ }
}
- return unibi_run(str, tui->params, buf, len);
+}
+
+/// Query the terminal emulator to see if it supports Kitty's keyboard protocol.
+///
+/// Write CSI ? u followed by a primary device attributes request (CSI c). If
+/// a primary device attributes response is received without first receiving an
+/// answer to the progressive enhancement query (CSI u), then the terminal does
+/// not support the Kitty keyboard protocol.
+///
+/// See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
+static void tui_query_kitty_keyboard(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ tui->input.waiting_for_kkp_response = true;
+ out(tui, S_LEN("\x1b[?u\x1b[c"));
}
static void terminfo_start(TUIData *tui)
@@ -261,11 +293,8 @@ static void terminfo_start(TUIData *tui)
tui->unibi_ext.reset_scroll_region = -1;
tui->unibi_ext.set_cursor_style = -1;
tui->unibi_ext.reset_cursor_style = -1;
- tui->unibi_ext.get_bg = -1;
tui->unibi_ext.set_underline_color = -1;
- tui->unibi_ext.enable_extended_keys = -1;
- tui->unibi_ext.disable_extended_keys = -1;
- tui->unibi_ext.get_extkeys = -1;
+ tui->unibi_ext.sync = -1;
tui->out_fd = STDOUT_FILENO;
tui->out_isatty = os_isatty(tui->out_fd);
tui->input.tui_data = tui;
@@ -295,7 +324,7 @@ static void terminfo_start(TUIData *tui)
const char *colorterm = os_getenv("COLORTERM");
const char *termprg = os_getenv("TERM_PROGRAM");
const char *vte_version_env = os_getenv("VTE_VERSION");
- long vtev = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0;
+ int vtev = vte_version_env ? (int)strtol(vte_version_env, NULL, 10) : 0;
bool iterm_env = termprg && strstr(termprg, "iTerm.app");
bool nsterm = (termprg && strstr(termprg, "Apple_Terminal"))
|| terminfo_is_term_family(term, "nsterm");
@@ -303,8 +332,8 @@ static void terminfo_start(TUIData *tui)
|| os_getenv("KONSOLE_PROFILE_NAME")
|| os_getenv("KONSOLE_DBUS_SESSION");
const char *konsolev_env = os_getenv("KONSOLE_VERSION");
- long konsolev = konsolev_env ? strtol(konsolev_env, NULL, 10)
- : (konsole ? 1 : 0);
+ int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10)
+ : (konsole ? 1 : 0);
patch_terminfo_bugs(tui, term, colorterm, vtev, konsolev, iterm_env, nsterm);
augment_terminfo(tui, term, vtev, konsolev, iterm_env, nsterm);
@@ -327,28 +356,24 @@ static void terminfo_start(TUIData *tui)
|| terminfo_is_term_family(term, "win32con")
|| terminfo_is_term_family(term, "interix");
tui->bce = unibi_get_bool(tui->ut, unibi_back_color_erase);
- tui->normlen = unibi_pre_fmt_str(tui, unibi_cursor_normal,
- tui->norm, sizeof tui->norm);
- tui->invislen = unibi_pre_fmt_str(tui, unibi_cursor_invisible,
- tui->invis, sizeof tui->invis);
// Set 't_Co' from the result of unibilium & fix_terminfo.
t_colors = unibi_get_num(tui->ut, unibi_max_colors);
// Enter alternate screen, save title, and clear.
// NOTE: Do this *before* changing terminal settings. #6433
unibi_out(tui, unibi_enter_ca_mode);
- // Save title/icon to the "stack". #4063
- unibi_out_ext(tui, tui->unibi_ext.save_title);
unibi_out(tui, unibi_keypad_xmit);
unibi_out(tui, unibi_clear_screen);
- // Ask the terminal to send us the background color.
- tui->input.waiting_for_bg_response = 5;
- unibi_out_ext(tui, tui->unibi_ext.get_bg);
+
// Enable bracketed paste
unibi_out_ext(tui, tui->unibi_ext.enable_bracketed_paste);
- // Query the terminal to see if it supports CSI u
- tui->input.waiting_for_csiu_response = 5;
- unibi_out_ext(tui, tui->unibi_ext.get_extkeys);
+ // Query support for mode 2026 (Synchronized Output). Some terminals also
+ // support an older DCS sequence for synchronized output, but we will only use
+ // mode 2026
+ tui_request_term_mode(tui, kTermModeSynchronizedOutput);
+
+ // Query the terminal to see if it supports Kitty's keyboard protocol
+ tui_query_kitty_keyboard(tui);
int ret;
uv_loop_init(&tui->write_loop);
@@ -357,12 +382,7 @@ static void terminfo_start(TUIData *tui)
if (ret) {
ELOG("uv_tty_init failed: %s", uv_strerror(ret));
}
-#ifdef MSWIN
- ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_RAW);
- if (ret) {
- ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret));
- }
-#else
+#ifndef MSWIN
int retry_count = 10;
// A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a
// few times. #12322
@@ -396,11 +416,22 @@ static void terminfo_stop(TUIData *tui)
// Reset cursor to normal before exiting alternate screen.
unibi_out(tui, unibi_cursor_normal);
unibi_out(tui, unibi_keypad_local);
- // Disable extended keys before exiting alternate screen.
- unibi_out_ext(tui, tui->unibi_ext.disable_extended_keys);
- unibi_out(tui, unibi_exit_ca_mode);
- // Restore title/icon from the "stack". #4063
- unibi_out_ext(tui, tui->unibi_ext.restore_title);
+
+ // Reset the key encoding
+ tui_reset_key_encoding(tui);
+
+ // May restore old title before exiting alternate screen.
+ tui_set_title(tui, (String)STRING_INIT);
+ if (ui_client_exit_status == 0) {
+ ui_client_exit_status = tui->seen_error_exit;
+ }
+ // if nvim exited with nonzero status, without indicated this was an
+ // intentional exit (like `:1cquit`), it likely was an internal failure.
+ // Don't clobber the stderr error message in this case.
+ if (ui_client_exit_status == tui->seen_error_exit) {
+ // Exit alternate screen.
+ unibi_out(tui, unibi_exit_ca_mode);
+ }
if (tui->cursor_color_changed) {
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
}
@@ -408,6 +439,11 @@ static void terminfo_stop(TUIData *tui)
unibi_out_ext(tui, tui->unibi_ext.disable_bracketed_paste);
// Disable focus reporting
unibi_out_ext(tui, tui->unibi_ext.disable_focus_reporting);
+
+ // Disable synchronized output
+ UNIBI_SET_NUM_VAR(tui->params[0], 0);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+
flush_buf(tui);
uv_tty_reset_mode();
uv_close((uv_handle_t *)&tui->output_handle, NULL);
@@ -445,7 +481,7 @@ static void tui_terminal_after_startup(TUIData *tui)
/// stop the terminal but allow it to restart later (like after suspend)
static void tui_terminal_stop(TUIData *tui)
{
- if (uv_is_closing(STRUCT_CAST(uv_handle_t, &tui->output_handle))) {
+ if (uv_is_closing((uv_handle_t *)&tui->output_handle)) {
// Race between SIGCONT (tui.c) and SIGHUP (os/signal.c)? #8075
ELOG("TUI already stopped (race?)");
tui->stopped = true;
@@ -453,14 +489,18 @@ static void tui_terminal_stop(TUIData *tui)
}
tinput_stop(&tui->input);
signal_watcher_stop(&tui->winch_handle);
+ // Position the cursor on the last screen line, below all the text
+ cursor_goto(tui, tui->height - 1, 0);
terminfo_stop(tui);
}
+void tui_error_exit(TUIData *tui, Integer status)
+{
+ tui->seen_error_exit = (int)status;
+}
+
void tui_stop(TUIData *tui)
{
- if (tui->stopped) {
- return;
- }
tui_terminal_stop(tui);
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
tinput_destroy(&tui->input);
@@ -470,7 +510,7 @@ void tui_stop(TUIData *tui)
}
/// Returns true if UI `ui` is stopped.
-static bool tui_is_stopped(TUIData *tui)
+bool tui_is_stopped(TUIData *tui)
{
return tui->stopped;
}
@@ -692,15 +732,15 @@ static void final_column_wrap(TUIData *tui)
/// It is undocumented, but in the majority of terminals and terminal emulators
/// printing at the right margin does not cause an automatic wrap until the
/// next character is printed, holding the cursor in place until then.
-static void print_cell(TUIData *tui, UCell *ptr)
+static void print_cell(TUIData *tui, char *buf, sattr_T attr)
{
UGrid *grid = &tui->grid;
if (!tui->immediate_wrap_after_last_column) {
// Printing the next character finally advances the cursor.
final_column_wrap(tui);
}
- update_attrs(tui, ptr->attr);
- out(tui, ptr->data, strlen(ptr->data));
+ update_attrs(tui, attr);
+ out(tui, buf, strlen(buf));
grid->col++;
if (tui->immediate_wrap_after_last_column) {
// Printing at the right margin immediately advances the cursor.
@@ -720,8 +760,8 @@ static bool cheap_to_print(TUIData *tui, int row, int col, int next)
return false;
}
}
- if (strlen(cell->data) > 1) {
- return false;
+ if (schar_get_ascii(cell->data) == 0) {
+ return false; // not ascii
}
cell++;
}
@@ -751,11 +791,15 @@ static void cursor_goto(TUIData *tui, int row, int col)
if (grid->row == -1) {
goto safe_move;
}
- if (0 == col ? col != grid->col :
- row != grid->row ? false :
- 1 == col ? 2 < grid->col && cheap_to_print(tui, grid->row, 0, col) :
- 2 == col ? 5 < grid->col && cheap_to_print(tui, grid->row, 0, col) :
- false) {
+ if (0 == col
+ ? col != grid->col
+ : (row != grid->row
+ ? false
+ : (1 == col
+ ? (2 < grid->col && cheap_to_print(tui, grid->row, 0, col))
+ : (2 == col
+ ? (5 < grid->col && cheap_to_print(tui, grid->row, 0, col))
+ : false)))) {
// Motion to left margin from anywhere else, or CR + printing chars is
// even less expensive than using BSes or CUB.
unibi_out(tui, unibi_carriage_return);
@@ -848,14 +892,16 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
{
UGrid *grid = &tui->grid;
- if (grid->row == -1 && cell->data[0] == NUL) {
+ if (grid->row == -1 && cell->data == NUL) {
// If cursor needs to repositioned and there is nothing to print, don't move cursor.
return;
}
cursor_goto(tui, row, col);
- bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(cell->data));
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell->data);
+ bool is_ambiwidth = utf_ambiguous_width(utf_ptr2char(buf));
if (is_ambiwidth && is_doublewidth) {
// Clear the two screen cells.
// If the character is single-width in the host terminal it won't change the second cell.
@@ -864,7 +910,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
cursor_goto(tui, row, col);
}
- print_cell(tui, cell);
+ print_cell(tui, buf, cell->attr);
if (is_ambiwidth) {
// Force repositioning cursor after printing an ambiguous-width character.
@@ -993,8 +1039,10 @@ void tui_grid_clear(TUIData *tui, Integer g)
{
UGrid *grid = &tui->grid;
ugrid_clear(grid);
+ // safe to clear cache at this point
+ schar_cache_clear_if_full();
kv_size(tui->invalid_regions) = 0;
- clear_region(tui, 0, grid->height, 0, grid->width, 0);
+ clear_region(tui, 0, tui->height, 0, tui->width, 0);
}
void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col)
@@ -1004,7 +1052,7 @@ void tui_grid_cursor_goto(TUIData *tui, Integer grid, Integer row, Integer col)
tui->col = (int)col;
}
-CursorShape tui_cursor_decode_shape(const char *shape_str)
+static CursorShape tui_cursor_decode_shape(const char *shape_str)
{
CursorShape shape;
if (strequal(shape_str, "block")) {
@@ -1097,7 +1145,7 @@ void tui_mouse_off(TUIData *tui)
}
}
-void tui_set_mode(TUIData *tui, ModeShape mode)
+static void tui_set_mode(TUIData *tui, ModeShape mode)
{
if (!cursor_style_enabled) {
return;
@@ -1108,15 +1156,13 @@ void tui_set_mode(TUIData *tui, ModeShape mode)
HlAttrs aep = kv_A(tui->attrs, c.id);
tui->want_invisible = aep.hl_blend == 100;
- if (tui->want_invisible) {
- unibi_out(tui, unibi_cursor_invisible);
- } else if (aep.rgb_ae_attr & HL_INVERSE) {
+ if (!tui->want_invisible && aep.rgb_ae_attr & HL_INVERSE) {
// We interpret "inverse" as "default" (no termcode for "inverse"...).
// Hopefully the user's default cursor color is inverse.
unibi_out_ext(tui, tui->unibi_ext.reset_cursor_color);
} else {
+ char hexbuf[8];
if (tui->set_cursor_color_as_str) {
- char hexbuf[8];
snprintf(hexbuf, 7 + 1, "#%06x", aep.rgb_bg_color);
UNIBI_SET_STR_VAR(tui->params[0], hexbuf);
} else {
@@ -1133,8 +1179,6 @@ void tui_set_mode(TUIData *tui, ModeShape mode)
int shape;
switch (c.shape) {
- default:
- abort(); break;
case SHAPE_BLOCK:
shape = 1; break;
case SHAPE_HOR:
@@ -1153,7 +1197,7 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx)
// If stdin is not a TTY, the LHS of pipe may change the state of the TTY
// after calling uv_tty_set_mode. So, set the mode of the TTY again here.
// #13073
- if (tui->is_starting && tui->input.in_fd == STDERR_FILENO) {
+ if (tui->is_starting && !stdin_isatty) {
int ret = uv_tty_set_mode(&tui->output_handle.tty, UV_TTY_MODE_NORMAL);
if (ret) {
ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret));
@@ -1174,9 +1218,8 @@ void tui_mode_change(TUIData *tui, String mode, Integer mode_idx)
tui->showing_mode = (ModeShape)mode_idx;
}
-void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, // -V751
- Integer endrow, Integer startcol, Integer endcol, Integer rows,
- Integer cols FUNC_ATTR_UNUSED)
+void tui_grid_scroll(TUIData *tui, Integer g, Integer startrow, Integer endrow, Integer startcol,
+ Integer endcol, Integer rows, Integer cols FUNC_ATTR_UNUSED)
{
UGrid *grid = &tui->grid;
int top = (int)startrow, bot = (int)endrow - 1;
@@ -1265,6 +1308,39 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
}
+/// Begin flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// begin a synchronized update. Otherwise, hide the cursor to avoid cursor jumping.
+static void tui_flush_start(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 1);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ } else if (!tui->is_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+}
+
+/// Finish flushing the TUI. If 'termsync' is set and the terminal supports synchronized updates,
+/// end a synchronized update. Otherwise, make the cursor visible again.
+static void tui_flush_end(TUIData *tui)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tui->sync_output && tui->unibi_ext.sync != -1) {
+ UNIBI_SET_NUM_VAR(tui->params[0], 0);
+ unibi_out_ext(tui, tui->unibi_ext.sync);
+ }
+ bool should_invisible = tui->busy || tui->want_invisible;
+ if (tui->is_invisible && !should_invisible) {
+ unibi_out(tui, unibi_cursor_normal);
+ tui->is_invisible = false;
+ } else if (!tui->is_invisible && should_invisible) {
+ unibi_out(tui, unibi_cursor_invisible);
+ tui->is_invisible = true;
+ }
+}
+
void tui_flush(TUIData *tui)
{
UGrid *grid = &tui->grid;
@@ -1281,6 +1357,8 @@ void tui_flush(TUIData *tui)
tui_busy_stop(tui); // avoid hidden cursor
}
+ tui_flush_start(tui);
+
while (kv_size(tui->invalid_regions)) {
Rect r = kv_pop(tui->invalid_regions);
assert(r.bot <= grid->height && r.right <= grid->width);
@@ -1290,7 +1368,7 @@ void tui_flush(TUIData *tui)
int clear_col;
for (clear_col = r.right; clear_col > 0; clear_col--) {
UCell *cell = &grid->cells[row][clear_col - 1];
- if (!(cell->data[0] == ' ' && cell->data[1] == NUL
+ if (!(cell->data == schar_from_ascii(' ')
&& cell->attr == clear_attr)) {
break;
}
@@ -1298,7 +1376,7 @@ void tui_flush(TUIData *tui)
UGRID_FOREACH_CELL(grid, row, r.left, clear_col, {
print_cell_at_pos(tui, row, curcol, cell,
- curcol < clear_col - 1 && (cell + 1)->data[0] == NUL);
+ curcol < clear_col - 1 && (cell + 1)->data == NUL);
});
if (clear_col < r.right) {
clear_region(tui, row, row + 1, clear_col, r.right, clear_attr);
@@ -1308,6 +1386,8 @@ void tui_flush(TUIData *tui)
cursor_goto(tui, tui->row, tui->col);
+ tui_flush_end(tui);
+
flush_buf(tui);
}
@@ -1321,16 +1401,16 @@ static void show_verbose_terminfo(TUIData *tui)
Array chunks = ARRAY_DICT_INIT;
Array title = ARRAY_DICT_INIT;
- ADD(title, STRING_OBJ(cstr_to_string("\n\n--- Terminal info --- {{{\n")));
- ADD(title, STRING_OBJ(cstr_to_string("Title")));
+ ADD(title, CSTR_TO_OBJ("\n\n--- Terminal info --- {{{\n"));
+ ADD(title, CSTR_TO_OBJ("Title"));
ADD(chunks, ARRAY_OBJ(title));
Array info = ARRAY_DICT_INIT;
String str = terminfo_info_msg(ut, tui->term);
ADD(info, STRING_OBJ(str));
ADD(chunks, ARRAY_OBJ(info));
Array end_fold = ARRAY_DICT_INIT;
- ADD(end_fold, STRING_OBJ(cstr_to_string("}}}\n")));
- ADD(end_fold, STRING_OBJ(cstr_to_string("Title")));
+ ADD(end_fold, CSTR_TO_OBJ("}}}\n"));
+ ADD(end_fold, CSTR_TO_OBJ("Title"));
ADD(chunks, ARRAY_OBJ(end_fold));
Array args = ARRAY_DICT_INIT;
@@ -1343,10 +1423,10 @@ static void show_verbose_terminfo(TUIData *tui)
api_free_array(args);
}
-#ifdef UNIX
-static void suspend_event(void **argv)
+void tui_suspend(TUIData *tui)
{
- TUIData *tui = argv[0];
+// on a non-UNIX system, this is a no-op
+#ifdef UNIX
ui_client_detach();
bool enable_mouse = tui->mouse_enabled;
tui_terminal_stop(tui);
@@ -1361,34 +1441,34 @@ static void suspend_event(void **argv)
}
stream_set_blocking(tui->input.in_fd, false); // libuv expects this
ui_client_attach(tui->width, tui->height, tui->term);
-}
-#endif
-
-void tui_suspend(TUIData *tui)
-{
-// on a non-UNIX system, this is a no-op
-#ifdef UNIX
- // kill(0, SIGTSTP) won't stop the UI thread, so we must poll for SIGCONT
- // before continuing. This is done in another callback to avoid
- // loop_poll_events recursion
- multiqueue_put_event(resize_events,
- event_create(suspend_event, 1, tui));
#endif
}
void tui_set_title(TUIData *tui, String title)
{
- if (!(title.data && unibi_get_str(tui->ut, unibi_to_status_line)
+ if (!(unibi_get_str(tui->ut, unibi_to_status_line)
&& unibi_get_str(tui->ut, unibi_from_status_line))) {
return;
}
- unibi_out(tui, unibi_to_status_line);
- out(tui, title.data, title.size);
- unibi_out(tui, unibi_from_status_line);
+ if (title.size > 0) {
+ if (!tui->title_enabled) {
+ // Save title/icon to the "stack". #4063
+ unibi_out_ext(tui, tui->unibi_ext.save_title);
+ tui->title_enabled = true;
+ }
+ unibi_out(tui, unibi_to_status_line);
+ out(tui, title.data, title.size);
+ unibi_out(tui, unibi_from_status_line);
+ } else if (tui->title_enabled) {
+ // Restore title/icon from the "stack". #4063
+ unibi_out_ext(tui, tui->unibi_ext.restore_title);
+ tui->title_enabled = false;
+ }
}
void tui_set_icon(TUIData *tui, String icon)
-{}
+{
+}
void tui_screenshot(TUIData *tui, String path)
{
@@ -1404,7 +1484,10 @@ void tui_screenshot(TUIData *tui, String path)
for (int i = 0; i < grid->height; i++) {
cursor_goto(tui, i, 0);
for (int j = 0; j < grid->width; j++) {
- print_cell(tui, &grid->cells[i][j]);
+ UCell cell = grid->cells[i][j];
+ char buf[MAX_SCHAR_SIZE];
+ schar_get(buf, cell.data);
+ print_cell(tui, buf, cell.attr);
}
}
flush_buf(tui);
@@ -1432,16 +1515,18 @@ void tui_option_set(TUIData *tui, String name, Object value)
if (ui_client_channel_id) {
MAXSIZE_TEMP_ARRAY(args, 2);
- ADD_C(args, STRING_OBJ(cstr_as_string("rgb")));
+ ADD_C(args, CSTR_AS_OBJ("rgb"));
ADD_C(args, BOOLEAN_OBJ(value.data.boolean));
rpc_send_event(ui_client_channel_id, "nvim_ui_set_option", args);
}
} else if (strequal(name.data, "ttimeout")) {
tui->input.ttimeout = value.data.boolean;
} else if (strequal(name.data, "ttimeoutlen")) {
- tui->input.ttimeoutlen = (long)value.data.integer;
+ tui->input.ttimeoutlen = (OptInt)value.data.integer;
} else if (strequal(name.data, "verbose")) {
tui->verbose = value.data.integer;
+ } else if (strequal(name.data, "termsync")) {
+ tui->sync_output = value.data.boolean;
}
}
@@ -1451,13 +1536,13 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
{
UGrid *grid = &tui->grid;
for (Integer c = startcol; c < endcol; c++) {
- memcpy(grid->cells[linerow][c].data, chunk[c - startcol], sizeof(schar_T));
+ grid->cells[linerow][c].data = chunk[c - startcol];
assert((size_t)attrs[c - startcol] < kv_size(tui->attrs));
grid->cells[linerow][c].attr = attrs[c - startcol];
}
UGRID_FOREACH_CELL(grid, (int)linerow, (int)startcol, (int)endcol, {
print_cell_at_pos(tui, (int)linerow, curcol, cell,
- curcol < endcol - 1 && (cell + 1)->data[0] == NUL);
+ curcol < endcol - 1 && (cell + 1)->data == NUL);
});
if (clearcol > endcol) {
@@ -1474,7 +1559,7 @@ void tui_raw_line(TUIData *tui, Integer g, Integer linerow, Integer startcol, In
if (endcol != grid->width) {
// Print the last char of the row, if we haven't already done so.
- int size = grid->cells[linerow][grid->width - 1].data[0] == NUL ? 2 : 1;
+ int size = grid->cells[linerow][grid->width - 1].data == NUL ? 2 : 1;
print_cell_at_pos(tui, (int)linerow, grid->width - size,
&grid->cells[linerow][grid->width - size], size == 2);
}
@@ -1545,12 +1630,11 @@ void tui_guess_size(TUIData *tui)
height = DFLT_ROWS;
}
- if (tui->width != width || tui->height != height) {
- tui->width = width;
- tui->height = height;
+ tui->width = width;
+ tui->height = height;
- ui_client_set_size(width, height);
- }
+ // Redraw on SIGWINCH event if size didn't change. #23411
+ ui_client_set_size(width, height);
}
static void unibi_goto(TUIData *tui, int row, int col)
@@ -1635,7 +1719,7 @@ static void pad(void *ctx, size_t delay, int scale FUNC_ATTR_UNUSED, int force)
}
flush_buf(tui);
- uv_sleep((unsigned int)(delay/10));
+ uv_sleep((unsigned)(delay/10));
}
static void unibi_set_if_empty(unibi_term *ut, enum unibi_string str, const char *val)
@@ -1673,13 +1757,10 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name)
/// Several entries in terminfo are known to be deficient or outright wrong;
/// and several terminal emulators falsely announce incorrect terminal types.
static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colorterm,
- long vte_version, long konsolev, bool iterm_env, bool nsterm)
+ int vte_version, int konsolev, bool iterm_env, bool nsterm)
{
unibi_term *ut = tui->ut;
const char *xterm_version = os_getenv("XTERM_VERSION");
-#if 0 // We don't need to identify this specifically, for now.
- bool roxterm = !!os_getenv("ROXTERM_ID");
-#endif
bool xterm = terminfo_is_term_family(term, "xterm")
// Treat Terminal.app as generic xterm-like, for now.
|| nsterm;
@@ -1872,15 +1953,6 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- tui->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
- "\x1b]11;?\x07");
-
- // Query the terminal to see if it supports CSI u key encoding by writing CSI
- // ? u followed by a request for the primary device attributes (CSI c)
- // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol
- tui->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys",
- "\x1b[?u\x1b[c");
-
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
// See http://fedoraproject.org/wiki/Features/256_Color_Terminals
@@ -2003,7 +2075,7 @@ static void patch_terminfo_bugs(TUIData *tui, const char *term, const char *colo
/// This adds stuff that is not in standard terminfo as extended unibilium
/// capabilities.
-static void augment_terminfo(TUIData *tui, const char *term, long vte_version, long konsolev,
+static void augment_terminfo(TUIData *tui, const char *term, int vte_version, int konsolev,
bool iterm_env, bool nsterm)
{
unibi_term *ut = tui->ut;
@@ -2142,9 +2214,12 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l
"\x1b[?2004l");
// For urxvt send BOTH xterm and old urxvt sequences. #8695
tui->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.enable_focus",
- rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
- tui->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, "ext.disable_focus",
- rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
+ rxvt
+ ? "\x1b[?1004h\x1b]777;focus;on\x7"
+ : "\x1b[?1004h");
+ tui->unibi_ext.disable_focus_reporting =
+ (int)unibi_add_ext_str(ut, "ext.disable_focus",
+ rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
tui->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, "ext.enable_mouse",
"\x1b[?1002h\x1b[?1006h");
tui->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, "ext.disable_mouse",
@@ -2173,71 +2248,29 @@ static void augment_terminfo(TUIData *tui, const char *term, long vte_version, l
}
if (!kitty && (vte_version == 0 || vte_version >= 5400)) {
- // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u
- tui->input.extkeys_type = kExtkeysXterm;
+ // Fallback to Xterm's modifyOtherKeys if terminal does not support the
+ // Kitty keyboard protocol
+ tui->input.key_encoding = kKeyEncodingXterm;
}
}
static void flush_buf(TUIData *tui)
{
uv_write_t req;
- uv_buf_t bufs[3];
- uv_buf_t *bufp = &bufs[0];
-
- // The content of the output for each condition is shown in the following
- // table. Therefore, if tui->bufpos == 0 and N/A or invis + norm, there is
- // no need to output it.
- //
- // | is_invisible | !is_invisible
- // ------+-----------------+--------------+---------------
- // busy | want_invisible | N/A | invis
- // | !want_invisible | N/A | invis
- // ------+-----------------+--------------+---------------
- // !busy | want_invisible | N/A | invis
- // | !want_invisible | norm | invis + norm
- // ------+-----------------+--------------+---------------
- //
- if (tui->bufpos <= 0
- && ((tui->is_invisible && tui->busy)
- || (tui->is_invisible && !tui->busy && tui->want_invisible)
- || (!tui->is_invisible && !tui->busy && !tui->want_invisible))) {
- return;
- }
-
- if (!tui->is_invisible) {
- // cursor is visible. Write a "cursor invisible" command before writing the
- // buffer.
- bufp->base = tui->invis;
- bufp->len = UV_BUF_LEN(tui->invislen);
- bufp++;
- tui->is_invisible = true;
- }
+ uv_buf_t buf;
- if (tui->bufpos > 0) {
- bufp->base = tui->buf;
- bufp->len = UV_BUF_LEN(tui->bufpos);
- bufp++;
+ if (tui->bufpos <= 0) {
+ return;
}
- if (!tui->busy) {
- assert(tui->is_invisible);
- // not busy and the cursor is invisible. Write a "cursor normal" command
- // after writing the buffer.
- if (!tui->want_invisible) {
- bufp->base = tui->norm;
- bufp->len = UV_BUF_LEN(tui->normlen);
- bufp++;
- tui->is_invisible = false;
- }
- }
+ buf.base = tui->buf;
+ buf.len = UV_BUF_LEN(tui->bufpos);
if (tui->screenshot) {
- for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
- fwrite(bufs[i].base, bufs[i].len, 1, tui->screenshot);
- }
+ fwrite(buf.base, buf.len, 1, tui->screenshot);
} else {
- int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &tui->output_handle),
- bufs, (unsigned)(bufp - bufs), NULL);
+ int ret
+ = uv_write(&req, (uv_stream_t *)&tui->output_handle, &buf, 1, NULL);
if (ret) {
ELOG("uv_write failed: %s", uv_strerror(ret));
}
diff --git a/src/nvim/tui/tui.h b/src/nvim/tui/tui.h
index 88ea73e99c..8eb4ac9bd8 100644
--- a/src/nvim/tui/tui.h
+++ b/src/nvim/tui/tui.h
@@ -1,13 +1,24 @@
-#ifndef NVIM_TUI_TUI_H
-#define NVIM_TUI_TUI_H
+#pragma once
-#include "nvim/cursor_shape.h"
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/highlight_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
typedef struct TUIData TUIData;
+typedef enum {
+ kTermModeSynchronizedOutput = 2026,
+} TermMode;
+
+typedef enum {
+ kTermModeNotRecognized = 0,
+ kTermModeSet = 1,
+ kTermModeReset = 2,
+ kTermModePermanentlySet = 3,
+ kTermModePermanentlyReset = 4,
+} TermModeState;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/tui.h.generated.h"
#endif
-
-#endif // NVIM_TUI_TUI_H
diff --git a/src/nvim/types.h b/src/nvim/types_defs.h
index d90c623955..c06737abb5 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types_defs.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_TYPES_H
-#define NVIM_TYPES_H
+#pragma once
#include <stdbool.h>
#include <stdint.h>
@@ -7,12 +6,12 @@
// dummy to pass an ACL to a function
typedef void *vim_acl_T;
-// Shorthand for unsigned variables. Many systems, but not all, have u_char
-// already defined, so we use char_u to avoid trouble.
-typedef unsigned char char_u;
-
-// Can hold one decoded UTF-8 character.
-typedef uint32_t u8char_T;
+// if data[0] is 0xFF, then data[1..4] is a 24-bit index (in machine endianness)
+// otherwise it must be a UTF-8 string of length maximum 4 (no NUL when n=4)
+typedef uint32_t schar_T;
+typedef int32_t sattr_T;
+// must be at least as big as the biggest of schar_T, sattr_T, colnr_T
+typedef int32_t sscratch_T;
// Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T;
@@ -22,7 +21,7 @@ typedef int handle_T;
// absent callback etc.
typedef int LuaRef;
-/// Type used for VimL VAR_FLOAT values
+/// Type used for Vimscript VAR_FLOAT values
typedef double float_T;
typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler;
@@ -30,13 +29,11 @@ typedef struct MsgpackRpcRequestHandler MsgpackRpcRequestHandler;
typedef union {
float_T (*float_func)(float_T);
const MsgpackRpcRequestHandler *api_handler;
- void *nullptr;
+ void *null;
} EvalFuncData;
typedef handle_T NS;
-typedef struct expand expand_T;
-
typedef uint64_t proftime_T;
typedef enum {
@@ -48,6 +45,6 @@ typedef enum {
#define TRISTATE_TO_BOOL(val, \
default) ((val) == kTrue ? true : ((val) == kFalse ? false : (default)))
-typedef struct Decoration Decoration;
+#define TRISTATE_FROM_INT(val) ((val) == 0 ? kFalse : ((val) >= 1 ? kTrue : kNone))
-#endif // NVIM_TYPES_H
+typedef int64_t OptInt;
diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c
index be1746983c..f02b9626cd 100644
--- a/src/nvim/ugrid.c
+++ b/src/nvim/ugrid.c
@@ -1,9 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <string.h>
+#include "nvim/grid.h"
#include "nvim/memory.h"
#include "nvim/ugrid.h"
@@ -63,10 +61,8 @@ void ugrid_scroll(UGrid *grid, int top, int bot, int left, int right, int count)
step = -1;
}
- int i;
-
// Copy cell data
- for (i = start; i != stop; i += step) {
+ for (int i = start; i != stop; i += step) {
UCell *target_row = grid->cells[i] + left;
UCell *source_row = grid->cells[i + count] + left;
assert(right >= left && left >= 0);
@@ -79,8 +75,7 @@ static void clear_region(UGrid *grid, int top, int bot, int left, int right, sat
{
for (int row = top; row <= bot; row++) {
UGRID_FOREACH_CELL(grid, row, left, right + 1, {
- cell->data[0] = ' ';
- cell->data[1] = 0;
+ cell->data = schar_from_ascii(' ');
cell->attr = attr;
});
}
diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h
index a85a6fb4a8..54cd33e58f 100644
--- a/src/nvim/ugrid.h
+++ b/src/nvim/ugrid.h
@@ -1,30 +1,17 @@
-#ifndef NVIM_UGRID_H
-#define NVIM_UGRID_H
+#pragma once
-#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
-#include "nvim/ui.h"
+#include "nvim/types_defs.h"
-struct ucell;
-struct ugrid;
-
-typedef struct ucell UCell;
-typedef struct ugrid UGrid;
-
-#define CELLBYTES (sizeof(schar_T))
-
-struct ucell {
- char data[CELLBYTES + 1];
+typedef struct ucell {
+ schar_T data;
sattr_T attr;
-};
+} UCell;
-struct ugrid {
+typedef struct ugrid {
int row, col;
int width, height;
UCell **cells;
-};
-
-// -V:UGRID_FOREACH_CELL:625
+} UGrid;
#define UGRID_FOREACH_CELL(grid, row, startcol, endcol, code) \
do { \
@@ -39,4 +26,3 @@ struct ugrid {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ugrid.h.generated.h"
#endif
-#endif // NVIM_UGRID_H
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 9f1cb87eb0..36f34bc75a 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
@@ -11,10 +8,11 @@
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/private/validate.h"
#include "nvim/api/ui.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/buffer.h"
#include "nvim/cursor_shape.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_getln.h"
@@ -25,17 +23,19 @@
#include "nvim/highlight_defs.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
+#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.c.generated.h"
@@ -62,32 +62,32 @@ static int pending_has_mouse = -1;
static Array call_buf = ARRAY_DICT_INIT;
-#if MIN_LOG_LEVEL > LOGLVL_DBG
-# define UI_LOG(funname)
-#else
+#ifdef NVIM_LOG_DEBUG
static size_t uilog_seen = 0;
-static char uilog_last_event[1024] = { 0 };
+static const char *uilog_last_event = NULL;
-# ifndef EXITFREE
-# define entered_free_all_mem false
+static void ui_log(const char *funname)
+{
+# ifdef EXITFREE
+ if (entered_free_all_mem) {
+ return; // do nothing, we cannot log now
+ }
# endif
-# define UI_LOG(funname) \
- do { \
- if (entered_free_all_mem) { \
- /* do nothing, we cannot log now */ \
- } else if (strequal(uilog_last_event, STR(funname))) { \
- uilog_seen++; \
- } else { \
- if (uilog_seen > 0) { \
- logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, \
- "%s (+%zu times...)", uilog_last_event, uilog_seen); \
- } \
- logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, STR(funname)); \
- uilog_seen = 0; \
- xstrlcpy(uilog_last_event, STR(funname), sizeof(uilog_last_event)); \
- } \
- } while (0)
+ if (uilog_last_event == funname) {
+ uilog_seen++;
+ } else {
+ if (uilog_seen > 0) {
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true,
+ "%s (+%zu times...)", uilog_last_event, uilog_seen);
+ }
+ logmsg(LOGLVL_DBG, "UI: ", NULL, -1, true, "%s", funname);
+ uilog_seen = 0;
+ uilog_last_event = funname;
+ }
+}
+#else
+# define ui_log(funname)
#endif
// UI_CALL invokes a function on all registered UI instances.
@@ -104,7 +104,7 @@ static char uilog_last_event[1024] = { 0 };
} \
} \
if (any_call) { \
- UI_LOG(funname); \
+ ui_log(STR(funname)); \
} \
} while (0)
@@ -129,10 +129,11 @@ void ui_free_all_mem(void)
map_foreach_value(&ui_event_cbs, event_cb, {
free_ui_event_callback(event_cb);
})
- pmap_destroy(uint32_t)(&ui_event_cbs);
+ map_destroy(uint32_t, &ui_event_cbs);
}
#endif
+/// Returns true if any `rgb=true` UI is attached.
bool ui_rgb_attached(void)
{
if (!headless_mode && p_tgc) {
@@ -146,6 +147,18 @@ bool ui_rgb_attached(void)
return false;
}
+/// Returns true if a GUI is attached.
+bool ui_gui_attached(void)
+{
+ for (size_t i = 0; i < ui_count; i++) {
+ bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty;
+ if (!tui) {
+ return true;
+ }
+ }
+ return false;
+}
+
/// Returns true if any UI requested `override=true`.
bool ui_override(void)
{
@@ -215,7 +228,7 @@ void ui_refresh(void)
p_lz = save_p_lz;
if (ext_widgets[kUIMessages]) {
- set_option_value("cmdheight", 0L, NULL, 0);
+ set_option_value("cmdheight", NUMBER_OPTVAL(0), 0);
command_height();
}
ui_mode_info_set();
@@ -318,7 +331,17 @@ void vim_beep(unsigned val)
// comes from.
if (vim_strchr(p_debug, 'e') != NULL) {
msg_source(HL_ATTR(HLF_W));
- msg_attr(_("Beep!"), HL_ATTR(HLF_W));
+ msg(_("Beep!"), HL_ATTR(HLF_W));
+ }
+}
+
+/// Trigger UIEnter for all attached UIs.
+/// Used on startup after VimEnter.
+void do_autocmd_uienter_all(void)
+{
+ for (size_t i = 0; i < ui_count; i++) {
+ UIData *data = uis[i]->data;
+ do_autocmd_uienter(data->channel_id, true);
}
}
@@ -334,6 +357,7 @@ void ui_attach_impl(UI *ui, uint64_t chanid)
uis[ui_count++] = ui;
ui_refresh_options();
+ resettitle();
for (UIExtension i = kUIGlobalCount; (int)i < kUIExtCount; i++) {
ui_set_ext_option(ui, i, ui->ui_ext[i]);
@@ -419,13 +443,13 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
(const sattr_T *)grid->attrs + off);
// 'writedelay': flush & delay each time.
- if (p_wd && !(rdb_flags & RDB_COMPOSITOR)) {
+ if (p_wd && (rdb_flags & RDB_LINE)) {
// If 'writedelay' is active, set the cursor to indicate what was drawn.
ui_call_grid_cursor_goto(grid->handle, row,
MIN(clearcol, (int)grid->cols - 1));
ui_call_flush();
- uint64_t wd = (uint64_t)labs(p_wd);
- os_microdelay(wd * 1000U, true);
+ uint64_t wd = (uint64_t)llabs(p_wd);
+ os_sleep(wd);
pending_cursor_update = true; // restore the cursor later
}
}
@@ -472,11 +496,6 @@ int ui_current_col(void)
return cursor_col;
}
-handle_T ui_cursor_grid(void)
-{
- return cursor_grid_handle;
-}
-
void ui_flush(void)
{
assert(!ui_client_channel_id);
@@ -510,6 +529,10 @@ void ui_flush(void)
pending_has_mouse = has_mouse;
}
ui_call_flush();
+
+ if (p_wd && (rdb_flags & RDB_FLUSH)) {
+ os_sleep((uint64_t)llabs(p_wd));
+ }
}
/// Check if 'mouse' is active for the current mode
@@ -598,6 +621,17 @@ Array ui_array(void)
PUT(info, "height", INTEGER_OBJ(ui->height));
PUT(info, "rgb", BOOLEAN_OBJ(ui->rgb));
PUT(info, "override", BOOLEAN_OBJ(ui->override));
+
+ // TUI fields. (`stdin_fd` is intentionally omitted.)
+ PUT(info, "term_name", CSTR_TO_OBJ(ui->term_name));
+
+ // term_background is deprecated. Populate with an empty string
+ PUT(info, "term_background", CSTR_TO_OBJ(""));
+
+ PUT(info, "term_colors", INTEGER_OBJ(ui->term_colors));
+ PUT(info, "stdin_tty", BOOLEAN_OBJ(ui->stdin_tty));
+ PUT(info, "stdout_tty", BOOLEAN_OBJ(ui->stdout_tty));
+
for (UIExtension j = 0; j < kUIExtCount; j++) {
if (ui_ext_names[j][0] != '_' || ui->ui_ext[j]) {
PUT(info, ui_ext_names[j], BOOLEAN_OBJ(ui->ui_ext[j]));
@@ -609,7 +643,7 @@ Array ui_array(void)
return all_uis;
}
-void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
+void ui_grid_resize(handle_T grid_handle, int width, int height, Error *err)
{
if (grid_handle == DEFAULT_GRID_HANDLE) {
screen_resize(width, height);
@@ -617,11 +651,9 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error)
}
win_T *wp = get_win_by_grid_handle(grid_handle);
- if (wp == NULL) {
- api_set_error(error, kErrorTypeValidation,
- "No window with the given handle");
+ VALIDATE_INT((wp != NULL), "window handle", (int64_t)grid_handle, {
return;
- }
+ });
if (wp->w_floating) {
if (width != wp->w_width || height != wp->w_height) {
@@ -656,6 +688,8 @@ void ui_call_event(char *name, Array args)
if (!handled) {
UI_CALL(true, event, ui, name, args);
}
+
+ ui_log(name);
}
void ui_cb_update_ext(void)
@@ -689,9 +723,9 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
event_cb->ext_widgets[kUICmdline] = true;
}
- UIEventCallback **item = (UIEventCallback **)pmap_ref(uint32_t)(&ui_event_cbs, ns_id, true);
+ ptr_t *item = pmap_put_ref(uint32_t)(&ui_event_cbs, ns_id, NULL, NULL);
if (*item) {
- free_ui_event_callback(*item);
+ free_ui_event_callback((UIEventCallback *)(*item));
}
*item = event_cb;
@@ -701,9 +735,9 @@ void ui_add_cb(uint32_t ns_id, LuaRef cb, bool *ext_widgets)
void ui_remove_cb(uint32_t ns_id)
{
- if (pmap_has(uint32_t)(&ui_event_cbs, ns_id)) {
- free_ui_event_callback(pmap_get(uint32_t)(&ui_event_cbs, ns_id));
- pmap_del(uint32_t)(&ui_event_cbs, ns_id);
+ if (map_has(uint32_t, &ui_event_cbs, ns_id)) {
+ UIEventCallback *item = pmap_del(uint32_t)(&ui_event_cbs, ns_id, NULL);
+ free_ui_event_callback(item);
}
ui_cb_update_ext();
ui_refresh();
diff --git a/src/nvim/ui.h b/src/nvim/ui.h
index 9140a9f1f3..666a869c89 100644
--- a/src/nvim/ui.h
+++ b/src/nvim/ui.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_UI_H
-#define NVIM_UI_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
@@ -9,9 +8,9 @@
#include "nvim/event/multiqueue.h"
#include "nvim/globals.h"
#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
struct ui_t;
@@ -30,7 +29,7 @@ typedef enum {
kUIExtCount,
} UIExtension;
-EXTERN const char *ui_ext_names[] INIT(= {
+EXTERN const char *ui_ext_names[] INIT( = {
"ext_cmdline",
"ext_popupmenu",
"ext_tabline",
@@ -79,6 +78,8 @@ typedef struct {
uint32_t ncalls; ///< number of calls made to the current event (plus one for the name!)
bool flushed_events; ///< events where sent to client without "flush" event
+ size_t ncells_pending; ///< total number of cells since last buffer flush
+
int hl_id; // Current highlight for legacy put event.
Integer cursor_row, cursor_col; // Intended visible cursor position.
@@ -101,6 +102,14 @@ struct ui_t {
double pum_height;
double pum_width;
+ // TUI fields.
+ char *term_name;
+ char *term_background; ///< Deprecated. No longer needed since background color detection happens
+ ///< in Lua. To be removed in a future release.
+ int term_colors;
+ bool stdin_tty;
+ bool stdout_tty;
+
// TODO(bfredl): integrate into struct!
UIData data[1];
};
@@ -113,9 +122,8 @@ typedef struct ui_event_callback {
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui.h.generated.h"
-# include "ui_events_call.h.generated.h"
+# include "ui_events_call.h.generated.h" // IWYU pragma: export
#endif
// uncrustify:on
EXTERN MultiQueue *resize_events;
-#endif // NVIM_UI_H
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index b5c8dff412..30f44d182d 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -1,27 +1,32 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+/// Nvim's own UI client, which attaches to a child or remote Nvim server.
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
+#include "nvim/api/keysets_defs.h"
+#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/loop.h"
+#include "nvim/func_attr.h"
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel.h"
-#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/os/os_defs.h"
#include "nvim/tui/tui.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
+#ifdef MSWIN
+# include "nvim/os/os_win_console.h"
+#endif
+
static TUIData *tui = NULL;
static bool ui_client_is_remote = false;
@@ -31,14 +36,13 @@ static bool ui_client_is_remote = false;
# include "ui_events_client.generated.h"
#endif
// uncrustify:on
-//
uint64_t ui_client_start_server(int argc, char **argv)
{
varnumber_T exit_status;
char **args = xmalloc(((size_t)(2 + argc)) * sizeof(char *));
int args_idx = 0;
- args[args_idx++] = xstrdup((const char *)get_vim_var_str(VV_PROGPATH));
+ args[args_idx++] = xstrdup(argv[0]);
args[args_idx++] = xstrdup("--embed");
for (int i = 1; i < argc; i++) {
args[args_idx++] = xstrdup(argv[i]);
@@ -48,8 +52,8 @@ uint64_t ui_client_start_server(int argc, char **argv)
CallbackReader on_err = CALLBACK_READER_INIT;
on_err.fwd_err = true;
- Channel *channel = channel_job_start(args, CALLBACK_READER_INIT,
- on_err, CALLBACK_NONE,
+ Channel *channel = channel_job_start(args, get_vim_var_str(VV_PROGPATH),
+ CALLBACK_READER_INIT, on_err, CALLBACK_NONE,
false, true, true, false, kChannelStdinPipe,
NULL, 0, 0, NULL, &exit_status);
@@ -78,18 +82,16 @@ void ui_client_attach(int width, int height, char *term)
PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true));
PUT_C(opts, "ext_termcolors", BOOLEAN_OBJ(true));
if (term) {
- PUT_C(opts, "term_name", STRING_OBJ(cstr_as_string(term)));
- }
- if (ui_client_bg_response != kNone) {
- bool is_dark = (ui_client_bg_response == kTrue);
- PUT_C(opts, "term_background", STRING_OBJ(cstr_as_string(is_dark ? "dark" : "light")));
+ PUT_C(opts, "term_name", CSTR_AS_OBJ(term));
}
+
PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors));
if (!ui_client_is_remote) {
PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty));
PUT_C(opts, "stdout_tty", BOOLEAN_OBJ(stdout_isatty));
if (ui_client_forward_stdin) {
PUT_C(opts, "stdin_fd", INTEGER_OBJ(UI_CLIENT_STDIN_FD));
+ ui_client_forward_stdin = false; // stdin shouldn't be forwarded again #22292
}
}
ADD_C(args, DICTIONARY_OBJ(opts));
@@ -110,7 +112,7 @@ void ui_client_run(bool remote_ui)
ui_client_is_remote = remote_ui;
int width, height;
char *term;
- tui = tui_start(&width, &height, &term);
+ tui_start(&tui, &width, &height, &term);
ui_client_attach(width, height, term);
@@ -122,7 +124,9 @@ void ui_client_run(bool remote_ui)
void ui_client_stop(void)
{
- tui_stop(tui);
+ if (!tui_is_stopped(tui)) {
+ tui_stop(tui);
+ }
}
void ui_client_set_size(int width, int height)
@@ -147,7 +151,7 @@ UIClientHandler ui_client_get_redraw_handler(const char *name, size_t name_len,
/// Placeholder for _sync_ requests with 'redraw' method name
///
-/// async 'redraw' events, which are expected when nvim acts as an ui client.
+/// async 'redraw' events, which are expected when nvim acts as a ui client.
/// get handled in msgpack_rpc/unpacker.c and directly dispatched to handlers
/// of specific ui events, like ui_client_event_grid_resize and so on.
Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Error *error)
@@ -202,9 +206,7 @@ void ui_client_event_raw_line(GridLineEvent *g)
int grid = g->args[0], row = g->args[1], startcol = g->args[2];
Integer endcol = startcol + g->coloff;
Integer clearcol = endcol + g->clear_width;
-
- // TODO(hlpr98): Accommodate other LineFlags when included in grid_line
- LineFlags lineflags = 0;
+ LineFlags lineflags = g->wrap ? kLineFlagWrap : 0;
tui_raw_line(tui, grid, row, startcol, endcol, clearcol, g->cur_attr, lineflags,
(const schar_T *)grid_line_buf_char, grid_line_buf_attr);
diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h
index 7e5f847039..93170ed86d 100644
--- a/src/nvim/ui_client.h
+++ b/src/nvim/ui_client.h
@@ -1,14 +1,13 @@
-#ifndef NVIM_UI_CLIENT_H
-#define NVIM_UI_CLIENT_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "nvim/api/private/defs.h"
-#include "nvim/grid_defs.h"
-#include "nvim/macros.h"
-#include "nvim/types.h"
+#include "nvim/grid_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/types_defs.h"
typedef struct {
const char *name;
@@ -16,33 +15,30 @@ typedef struct {
} UIClientHandler;
// Temporary buffer for converting a single grid_line event
-EXTERN size_t grid_line_buf_size INIT(= 0);
-EXTERN schar_T *grid_line_buf_char INIT(= NULL);
-EXTERN sattr_T *grid_line_buf_attr INIT(= NULL);
+EXTERN size_t grid_line_buf_size INIT( = 0);
+EXTERN schar_T *grid_line_buf_char INIT( = NULL);
+EXTERN sattr_T *grid_line_buf_attr INIT( = NULL);
// ID of the ui client channel. If zero, the client is not running.
-EXTERN uint64_t ui_client_channel_id INIT(= 0);
+EXTERN uint64_t ui_client_channel_id INIT( = 0);
+
+// exit status from embedded nvim process
+EXTERN int ui_client_exit_status INIT( = 0);
// TODO(bfredl): the current structure for how tui and ui_client.c communicate is a bit awkward.
// This will be restructured as part of The UI Devirtualization Project.
/// Whether ui client has sent nvim_ui_attach yet
-EXTERN bool ui_client_attached INIT(= false);
-
-/// Whether ui client has gotten a response about the bg color of the terminal,
-/// kTrue=dark, kFalse=light, kNone=no response yet
-EXTERN TriState ui_client_bg_response INIT(= kNone);
+EXTERN bool ui_client_attached INIT( = false);
/// The ui client should forward its stdin to the nvim process
/// by convention, this uses fd=3 (next free number after stdio)
-EXTERN bool ui_client_forward_stdin INIT(= false);
+EXTERN bool ui_client_forward_stdin INIT( = false);
#define UI_CLIENT_STDIN_FD 3
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_client.h.generated.h"
-# include "ui_events_client.h.generated.h"
+# include "ui_events_client.h.generated.h" // IWYU pragma: export
#endif
// uncrustify:on
-
-#endif // NVIM_UI_CLIENT_H
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index 9ff9eabff8..b698e017dc 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// Compositor: merge floating grids with the main grid for display in
// TUI and non-multigrid UIs.
//
@@ -10,28 +7,26 @@
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
-#include "nvim/ascii.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/ascii_defs.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight.h"
#include "nvim/highlight_group.h"
#include "nvim/log.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/time.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
-#include "nvim/vim.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_compositor.c.generated.h"
@@ -55,7 +50,7 @@ static int msg_current_row = INT_MAX;
static bool msg_was_scrolled = false;
static int msg_sep_row = -1;
-static schar_T msg_sep_char = { ' ', NUL };
+static schar_T msg_sep_char = schar_from_ascii(' ');
static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose;
@@ -318,7 +313,6 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
sattr_T *bg_attrs = &default_grid.attrs[default_grid.line_offset[row]
+ (size_t)startcol];
- int grid_width, grid_height;
while (col < endcol) {
int until = 0;
for (size_t i = 0; i < kv_size(layers); i++) {
@@ -328,8 +322,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// first check to see if any grids have pending updates to width/height,
// to ensure that we don't accidentally put any characters into `linebuf`
// that have been invalidated.
- grid_width = MIN(g->cols, g->comp_width);
- grid_height = MIN(g->rows, g->comp_height);
+ int grid_width = MIN(g->cols, g->comp_width);
+ int grid_height = MIN(g->rows, g->comp_height);
if (g->comp_row > row || row >= g->comp_row + grid_height
|| g->comp_disabled) {
continue;
@@ -354,7 +348,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
grid = &msg_grid;
sattr_T msg_sep_attr = (sattr_T)HL_ATTR(HLF_MSGSEP);
for (int i = col; i < until; i++) {
- memcpy(linebuf[i - startcol], msg_sep_char, sizeof(*linebuf));
+ linebuf[i - startcol] = msg_sep_char;
attrbuf[i - startcol] = msg_sep_attr;
}
} else {
@@ -363,9 +357,8 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
memcpy(linebuf + (col - startcol), grid->chars + off, n * sizeof(*linebuf));
memcpy(attrbuf + (col - startcol), grid->attrs + off, n * sizeof(*attrbuf));
if (grid->comp_col + grid->cols > until
- && grid->chars[off + n][0] == NUL) {
- linebuf[until - 1 - startcol][0] = ' ';
- linebuf[until - 1 - startcol][1] = '\0';
+ && grid->chars[off + n] == NUL) {
+ linebuf[until - 1 - startcol] = schar_from_ascii(' ');
if (col == startcol && n == 1) {
skipstart = 0;
}
@@ -378,10 +371,10 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
for (int i = col - (int)startcol; i < until - startcol; i += width) {
width = 1;
// negative space
- bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL;
- if (i + 1 < endcol - startcol && bg_line[i + 1][0] == NUL) {
+ bool thru = linebuf[i] == schar_from_ascii(' ') && bg_line[i] != NUL;
+ if (i + 1 < endcol - startcol && bg_line[i + 1] == NUL) {
width = 2;
- thru &= strequal((char *)linebuf[i + 1], " ");
+ thru &= linebuf[i + 1] == schar_from_ascii(' ');
}
attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru);
if (width == 2) {
@@ -396,28 +389,25 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
// Tricky: if overlap caused a doublewidth char to get cut-off, must
// replace the visible half with a space.
- if (linebuf[col - startcol][0] == NUL) {
- linebuf[col - startcol][0] = ' ';
- linebuf[col - startcol][1] = NUL;
+ if (linebuf[col - startcol] == NUL) {
+ linebuf[col - startcol] = schar_from_ascii(' ');
if (col == endcol - 1) {
skipend = 0;
}
- } else if (n > 1 && linebuf[col - startcol + 1][0] == NUL) {
+ } else if (col == startcol && n > 1 && linebuf[1] == NUL) {
skipstart = 0;
}
col = until;
}
- if (linebuf[endcol - startcol - 1][0] == NUL) {
+ if (linebuf[endcol - startcol - 1] == NUL) {
skipend = 0;
}
assert(endcol <= chk_width);
assert(row < chk_height);
- if (!(grid && grid == &default_grid)) {
- // TODO(bfredl): too conservative, need check
- // grid->line_wraps if grid->Width == Width
+ if (!(grid && (grid == &default_grid || (grid->comp_col == 0 && grid->cols == Columns)))) {
flags = flags & ~kLineFlagWrap;
}
@@ -439,7 +429,7 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, LineFlag
static void compose_debug(Integer startrow, Integer endrow, Integer startcol, Integer endcol,
int syn_id, bool delay)
{
- if (!(rdb_flags & RDB_COMPOSITOR)) {
+ if (!(rdb_flags & RDB_COMPOSITOR) || startcol >= endcol) {
return;
}
@@ -465,9 +455,9 @@ static void compose_debug(Integer startrow, Integer endrow, Integer startcol, In
static void debug_delay(Integer lines)
{
ui_call_flush();
- uint64_t wd = (uint64_t)labs(p_wd);
+ uint64_t wd = (uint64_t)llabs(p_wd);
uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
- os_microdelay(factor * wd * 1000U, true);
+ os_sleep(factor * wd);
}
static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol)
@@ -537,7 +527,7 @@ void ui_comp_raw_line(Integer grid, Integer row, Integer startcol, Integer endco
compose_debug(row, row + 1, startcol, clearcol, dbghl_composed, true);
compose_line(row, startcol, clearcol, flags);
} else {
- compose_debug(row, row + 1, startcol, endcol, dbghl_normal, false);
+ compose_debug(row, row + 1, startcol, endcol, dbghl_normal, endcol >= clearcol);
compose_debug(row, row + 1, endcol, clearcol, dbghl_clear, true);
#ifndef NDEBUG
for (int i = 0; i < endcol - startcol; i++) {
@@ -568,7 +558,7 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
if (scrolled && row > 0) {
msg_sep_row = (int)row - 1;
if (sep_char.data) {
- xstrlcpy(msg_sep_char, sep_char.data, sizeof(msg_sep_char));
+ msg_sep_char = schar_from_buf(sep_char.data, sep_char.size);
}
} else {
msg_sep_row = -1;
@@ -580,11 +570,11 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
&& (msg_current_row < Rows || (scrolled && !msg_was_scrolled))) {
int delta = msg_current_row - (int)row;
if (msg_grid.blending) {
- int first_row = MAX((int)row - (scrolled?1:0), 0);
+ int first_row = MAX((int)row - (scrolled ? 1 : 0), 0);
compose_area(first_row, Rows - delta, 0, Columns);
} else {
// scroll separator together with message text
- int first_row = MAX((int)row - (msg_was_scrolled?1:0), 0);
+ int first_row = MAX((int)row - (msg_was_scrolled ? 1 : 0), 0);
ui_composed_call_grid_scroll(1, first_row, Rows, 0, Columns, delta, 0);
if (scrolled && !msg_was_scrolled && row > 0) {
compose_area(row - 1, row, 0, Columns);
@@ -602,8 +592,8 @@ void ui_comp_msg_set_pos(Integer grid, Integer row, Boolean scrolled, String sep
static bool curgrid_covered_above(int row)
{
bool above_msg = (kv_A(layers, kv_size(layers) - 1) == &msg_grid
- && row < msg_current_row - (msg_was_scrolled?1:0));
- return kv_size(layers) - (above_msg?1:0) > curgrid->comp_index + 1;
+ && row < msg_current_row - (msg_was_scrolled ? 1 : 0));
+ return kv_size(layers) - (above_msg ? 1 : 0) > curgrid->comp_index + 1;
}
void ui_comp_grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right,
diff --git a/src/nvim/ui_compositor.h b/src/nvim/ui_compositor.h
index 01538105cb..f3f5981680 100644
--- a/src/nvim/ui_compositor.h
+++ b/src/nvim/ui_compositor.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_UI_COMPOSITOR_H
-#define NVIM_UI_COMPOSITOR_H
+#pragma once
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
#include "nvim/event/defs.h"
+#include "nvim/grid_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/ui.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ui_compositor.h.generated.h"
#endif
-#endif // NVIM_UI_COMPOSITOR_H
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 0f12c00f15..93a973c33d 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// undo.c: multi level undo facility
// The saved lines are stored in a list of lists (one for each buffer):
@@ -80,12 +77,13 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
#include <time.h>
#include <uv.h>
#include "auto/config.h"
#include "klib/kvec.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_updates.h"
@@ -93,39 +91,41 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
-#include "nvim/macros.h"
+#include "nvim/highlight.h"
+#include "nvim/macros_defs.h"
#include "nvim/mark.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/os/fs_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/os/input.h"
-#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
-#include "nvim/pos.h"
-#include "nvim/screen.h"
+#include "nvim/pos_defs.h"
#include "nvim/sha256.h"
+#include "nvim/spell.h"
#include "nvim/state.h"
#include "nvim/strings.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/undo.h"
#include "nvim/undo_defs.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
/// Structure passed around between undofile functions.
typedef struct {
@@ -137,8 +137,15 @@ typedef struct {
# include "undo.c.generated.h"
#endif
+static const char e_undo_list_corrupt[]
+ = N_("E439: Undo list corrupt");
+static const char e_undo_line_missing[]
+ = N_("E440: Undo line missing");
+static const char e_write_error_in_undo_file_str[]
+ = N_("E829: Write error in undo file: %s");
+
// used in undo_end() to report number of added and deleted lines
-static long u_newcount, u_oldcount;
+static int u_newcount, u_oldcount;
// When 'u' flag included in 'cpoptions', we behave like vi. Need to remember
// the action that "u" should do.
@@ -174,12 +181,12 @@ static void u_check_tree(u_header_T *uhp, u_header_T *exp_uh_next, u_header_T *e
// Check pointers back are correct.
if (uhp->uh_next.ptr != exp_uh_next) {
emsg("uh_next wrong");
- smsg("expected: 0x%x, actual: 0x%x",
+ smsg(0, "expected: 0x%x, actual: 0x%x",
exp_uh_next, uhp->uh_next.ptr);
}
if (uhp->uh_alt_prev.ptr != exp_uh_alt_prev) {
emsg("uh_alt_prev wrong");
- smsg("expected: 0x%x, actual: 0x%x",
+ smsg(0, "expected: 0x%x, actual: 0x%x",
exp_uh_alt_prev, uhp->uh_alt_prev.ptr);
}
@@ -216,7 +223,7 @@ static void u_check(int newhead_may_be_NULL)
}
if (header_count != curbuf->b_u_numhead) {
emsg("b_u_numhead invalid");
- smsg("expected: %" PRId64 ", actual: %" PRId64,
+ smsg(0, "expected: %" PRId64 ", actual: %" PRId64,
(int64_t)header_count, (int64_t)curbuf->b_u_numhead);
}
}
@@ -241,15 +248,20 @@ int u_save_cursor(void)
/// Returns FAIL when lines could not be saved, OK otherwise.
int u_save(linenr_T top, linenr_T bot)
{
- if (top >= bot || bot > (curbuf->b_ml.ml_line_count + 1)) {
+ return u_save_buf(curbuf, top, bot);
+}
+
+int u_save_buf(buf_T *buf, linenr_T top, linenr_T bot)
+{
+ if (top >= bot || bot > (buf->b_ml.ml_line_count + 1)) {
return FAIL; // rely on caller to do error messages
}
if (top + 2 == bot) {
- u_saveline((linenr_T)(top + 1));
+ u_saveline(buf, top + 1);
}
- return u_savecommon(curbuf, top, bot, (linenr_T)0, false);
+ return u_savecommon(buf, top, bot, 0, false);
}
/// Save the line "lnum" (used by ":s" and "~" command).
@@ -275,9 +287,9 @@ int u_inssub(linenr_T lnum)
/// becomes empty.
/// Careful: may trigger autocommands that reload the buffer.
/// Returns FAIL when lines could not be saved, OK otherwise.
-int u_savedel(linenr_T lnum, long nlines)
+int u_savedel(linenr_T lnum, linenr_T nlines)
{
- return u_savecommon(curbuf, lnum - 1, lnum + (linenr_T)nlines,
+ return u_savecommon(curbuf, lnum - 1, lnum + nlines,
nlines == curbuf->b_ml.ml_line_count ? 2 : lnum, false);
}
@@ -301,7 +313,7 @@ bool undo_allowed(buf_T *buf)
// Don't allow changes in the buffer while editing the cmdline. The
// caller of getcmdline() may get confused.
- if (textlock != 0) {
+ if (textlock != 0 || expr_map_locked()) {
emsg(_(e_textlock));
return false;
}
@@ -310,7 +322,7 @@ bool undo_allowed(buf_T *buf)
}
/// Get the 'undolevels' value for the current buffer.
-static long get_undolevel(buf_T *buf)
+static OptInt get_undolevel(buf_T *buf)
{
if (buf->b_p_ul == NO_LOCAL_UNDOLEVEL) {
return p_ul;
@@ -364,7 +376,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
u_entry_T *uep;
u_entry_T *prev_uep;
- long size = bot - top - 1;
+ linenr_T size = bot - top - 1;
// If curbuf->b_u_synced == true make a new header.
if (buf->b_u_synced) {
@@ -493,7 +505,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
if (size == 1) {
uep = u_get_headentry(buf);
prev_uep = NULL;
- for (long i = 0; i < 10; i++) {
+ for (int i = 0; i < 10; i++) {
if (uep == NULL) {
break;
}
@@ -575,7 +587,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
if (size > 0) {
uep->ue_array = xmalloc(sizeof(char *) * (size_t)size);
linenr_T lnum;
- long i;
+ int i;
for (i = 0, lnum = top + 1; i < size; i++) {
fast_breakcheck();
if (got_int) {
@@ -624,7 +636,7 @@ int u_savecommon(buf_T *buf, linenr_T top, linenr_T bot, linenr_T newbot, int re
// extra fields for uhp
#define UHP_SAVE_NR 1
-static char e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
+static const char e_not_open[] = N_("E828: Cannot open undo file for writing: %s");
/// Compute the hash for a buffer text into hash[UNDO_HASH_SIZE].
///
@@ -636,7 +648,7 @@ void u_compute_hash(buf_T *buf, uint8_t *hash)
context_sha256_T ctx;
sha256_start(&ctx);
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) {
- char *p = ml_get_buf(buf, lnum, false);
+ char *p = ml_get_buf(buf, lnum);
sha256_update(&ctx, (uint8_t *)p, strlen(p) + 1);
}
sha256_finish(&ctx, hash);
@@ -705,7 +717,7 @@ char *u_get_undo_file_name(const char *const buf_ffname, const bool reading)
// Last directory in the list does not exist, create it.
int ret;
char *failed_dir;
- if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir)) != 0) {
+ if ((ret = os_mkdir_recurse(dir_name, 0755, &failed_dir, NULL)) != 0) {
semsg(_("E5003: Unable to create directory \"%s\" for undo file: %s"),
failed_dir, os_strerror(ret));
xfree(failed_dir);
@@ -904,7 +916,7 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi, const char *file_name)
uhp->uh_time = undo_read_time(bi);
// Unserialize optional fields.
- for (;;) {
+ while (true) {
int len = undo_read_byte(bi);
if (len == EOF) {
@@ -1074,7 +1086,7 @@ static u_entry_T *unserialize_uep(bufinfo_T *bi, bool *error, const char *file_n
char **array = NULL;
if (uep->ue_size > 0) {
- if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) { // -V547
+ if ((size_t)uep->ue_size < SIZE_MAX / sizeof(char *)) {
array = xmalloc(sizeof(char *) * (size_t)uep->ue_size);
memset(array, 0, sizeof(char *) * (size_t)uep->ue_size);
}
@@ -1165,7 +1177,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (file_name == NULL) {
if (p_verbose > 0) {
verbose_enter();
- smsg("%s", _("Cannot write undo file in any directory in 'undodir'"));
+ smsg(0, "%s", _("Cannot write undo file in any directory in 'undodir'"));
verbose_leave();
}
return;
@@ -1179,7 +1191,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
// allow the user to access the undo file.
int perm = 0600;
if (buf->b_ffname != NULL) {
- perm = os_getperm((const char *)buf->b_ffname);
+ perm = os_getperm(buf->b_ffname);
if (perm < 0) {
perm = 0600;
}
@@ -1201,7 +1213,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (name == NULL) {
verbose_enter();
}
- smsg(_("Will not overwrite with undo file, cannot read: %s"),
+ smsg(0, _("Will not overwrite with undo file, cannot read: %s"),
file_name);
if (name == NULL) {
verbose_leave();
@@ -1218,7 +1230,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
if (name == NULL) {
verbose_enter();
}
- smsg(_("Will not overwrite, this is not an undo file: %s"),
+ smsg(0, _("Will not overwrite, this is not an undo file: %s"),
file_name);
if (name == NULL) {
verbose_leave();
@@ -1248,7 +1260,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
(void)os_setperm(file_name, perm);
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Writing undo file: %s"), file_name);
+ smsg(0, _("Writing undo file: %s"), file_name);
verbose_leave();
}
@@ -1333,20 +1345,22 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
}
#endif
+ if (p_fs && fflush(fp) == 0 && os_fsync(fd) != 0) {
+ write_ok = false;
+ }
+
write_error:
fclose(fp);
if (!write_ok) {
- semsg(_("E829: write error in undo file: %s"), file_name);
+ semsg(_(e_write_error_in_undo_file_str), file_name);
}
-#ifdef HAVE_ACL
if (buf->b_ffname != NULL) {
// For systems that support ACL: get the ACL from the original file.
vim_acl_T acl = os_get_acl(buf->b_ffname);
os_set_acl(file_name, acl);
os_free_acl(acl);
}
-#endif
theend:
if (file_name != name) {
@@ -1383,7 +1397,7 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
&& file_info_undo.stat.st_uid != getuid()) {
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Not reading undo file, owner differs: %s"),
+ smsg(0, _("Not reading undo file, owner differs: %s"),
file_name);
verbose_leave();
}
@@ -1396,7 +1410,7 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
if (p_verbose > 0) {
verbose_enter();
- smsg(_("Reading undo file: %s"), file_name);
+ smsg(0, _("Reading undo file: %s"), file_name);
verbose_leave();
}
@@ -1472,8 +1486,8 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
time_t seq_time = undo_read_time(&bi);
// Optional header fields.
- long last_save_nr = 0;
- for (;;) {
+ int last_save_nr = 0;
+ while (true) {
int len = undo_read_byte(&bi);
if (len == 0 || len == EOF) {
@@ -1498,12 +1512,12 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
// sequence numbers of the headers.
// When there are no headers uhp_table is NULL.
if (num_head > 0) {
- if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) { // -V547
+ if ((size_t)num_head < SIZE_MAX / sizeof(*uhp_table)) {
uhp_table = xmalloc((size_t)num_head * sizeof(*uhp_table));
}
}
- long num_read_uhps = 0;
+ int num_read_uhps = 0;
int c;
while ((c = undo_read_2c(&bi)) == UF_HEADER_MAGIC) {
@@ -1632,14 +1646,14 @@ void u_read_undo(char *name, const uint8_t *hash, const char *orig_name FUNC_ATT
#endif
if (name != NULL) {
- smsg(_("Finished reading undo file %s"), file_name);
+ smsg(0, _("Finished reading undo file %s"), file_name);
}
goto theend;
error:
xfree(line_ptr);
if (uhp_table != NULL) {
- for (long i = 0; i < num_read_uhps; i++) {
+ for (int i = 0; i < num_read_uhps; i++) {
if (uhp_table[i] != NULL) {
u_free_uhp(uhp_table[i]);
}
@@ -1811,8 +1825,8 @@ bool u_undo_and_forget(int count, bool do_buf_event)
if (curbuf->b_u_curhead) {
to_forget->uh_alt_next.ptr = NULL;
curbuf->b_u_curhead->uh_alt_prev.ptr = to_forget->uh_alt_prev.ptr;
- curbuf->b_u_seq_cur = curbuf->b_u_curhead->uh_next.ptr ?
- curbuf->b_u_curhead->uh_next.ptr->uh_seq : 0;
+ curbuf->b_u_seq_cur = curbuf->b_u_curhead->uh_next.ptr
+ ? curbuf->b_u_curhead->uh_next.ptr->uh_seq : 0;
} else if (curbuf->b_u_newhead) {
curbuf->b_u_seq_cur = curbuf->b_u_newhead->uh_seq;
}
@@ -1867,7 +1881,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
curbuf->b_u_curhead = curbuf->b_u_oldhead;
beep_flush();
if (count == startcount - 1) {
- msg(_("Already at oldest change"));
+ msg(_("Already at oldest change"), 0);
return;
}
break;
@@ -1878,7 +1892,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
if (curbuf->b_u_curhead == NULL || get_undolevel(curbuf) <= 0) {
beep_flush(); // nothing to redo
if (count == startcount - 1) {
- msg(_("Already at newest change"));
+ msg(_("Already at newest change"), 0);
return;
}
break;
@@ -1904,7 +1918,7 @@ static void u_doit(int startcount, bool quiet, bool do_buf_event)
// When "file" is true use "step" as a number of file writes.
// When "absolute" is true use "step" as the sequence number to jump to.
// "sec" must be false then.
-void undo_time(long step, bool sec, bool file, bool absolute)
+void undo_time(int step, bool sec, bool file, bool absolute)
{
if (text_locked()) {
text_locked_msg();
@@ -1922,8 +1936,8 @@ void undo_time(long step, bool sec, bool file, bool absolute)
u_oldcount = -1;
}
- long target;
- long closest;
+ int target;
+ int closest;
u_header_T *uhp = NULL;
bool dosec = sec;
bool dofile = file;
@@ -1937,7 +1951,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
closest = -1;
} else {
if (dosec) {
- target = (long)(curbuf->b_u_time_cur) + step;
+ target = (int)curbuf->b_u_time_cur + step;
} else if (dofile) {
if (step < 0) {
// Going back to a previous write. If there were changes after
@@ -1982,7 +1996,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
closest = -1;
} else {
if (dosec) {
- closest = (long)(os_time() + 1);
+ closest = (int)(os_time() + 1);
} else if (dofile) {
closest = curbuf->b_u_save_nr_last + 2;
} else {
@@ -1993,8 +2007,8 @@ void undo_time(long step, bool sec, bool file, bool absolute)
}
}
}
- long closest_start = closest;
- long closest_seq = curbuf->b_u_seq_cur;
+ int closest_start = closest;
+ int closest_seq = curbuf->b_u_seq_cur;
int mark;
int nomark = 0; // shut up compiler
@@ -2026,9 +2040,9 @@ void undo_time(long step, bool sec, bool file, bool absolute)
while (uhp != NULL) {
uhp->uh_walk = mark;
- long val = dosec ? (long)(uhp->uh_time) :
- dofile ? uhp->uh_save_nr
- : uhp->uh_seq;
+ int val = dosec ? (int)(uhp->uh_time)
+ : dofile ? uhp->uh_save_nr
+ : uhp->uh_seq;
if (round == 1 && !(dofile && val == 0)) {
// Remember the header that is closest to the target.
@@ -2036,7 +2050,7 @@ void undo_time(long step, bool sec, bool file, bool absolute)
// "b_u_seq_cur"). When the timestamp is equal find the
// highest/lowest sequence number.
if ((step < 0 ? uhp->uh_seq <= curbuf->b_u_seq_cur
- : uhp->uh_seq > curbuf->b_u_seq_cur)
+ : uhp->uh_seq > curbuf->b_u_seq_cur)
&& ((dosec && val == closest)
? (step < 0
? uhp->uh_seq < closest_seq
@@ -2102,9 +2116,9 @@ void undo_time(long step, bool sec, bool file, bool absolute)
if (closest == closest_start) {
if (step < 0) {
- msg(_("Already at oldest change"));
+ msg(_("Already at oldest change"), 0);
} else {
- msg(_("Already at newest change"));
+ msg(_("Already at newest change"), 0);
}
return;
}
@@ -2277,12 +2291,12 @@ static void u_undoredo(int undo, bool do_buf_event)
|| bot > curbuf->b_ml.ml_line_count + 1) {
unblock_autocmds();
iemsg(_("E438: u_undo: line numbers wrong"));
- changed(); // don't want UNCHANGED now
+ changed(curbuf); // don't want UNCHANGED now
return;
}
linenr_T oldsize = bot - top - 1; // number of lines before undo
- linenr_T newsize = (linenr_T)uep->ue_size; // number of lines after undo
+ linenr_T newsize = uep->ue_size; // number of lines after undo
if (top < newlnum) {
// If the saved cursor is somewhere in this undo block, move it to
@@ -2296,7 +2310,7 @@ static void u_undoredo(int undo, bool do_buf_event)
// Use the first line that actually changed. Avoids that
// undoing auto-formatting puts the cursor in the previous
// line.
- long i;
+ int i;
for (i = 0; i < newsize && i < oldsize; i++) {
if (strcmp(uep->ue_array[i], ml_get(top + 1 + (linenr_T)i)) != 0) {
break;
@@ -2318,7 +2332,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (oldsize > 0) {
newarray = xmalloc(sizeof(char *) * (size_t)oldsize);
// delete backwards, it goes faster in most cases
- long i;
+ int i;
linenr_T lnum;
for (lnum = bot - 1, i = oldsize; --i >= 0; lnum--) {
// what can we do when we run out of memory?
@@ -2336,15 +2350,15 @@ static void u_undoredo(int undo, bool do_buf_event)
// insert the lines in u_array between top and bot
if (newsize) {
- long i;
+ int i;
linenr_T lnum;
for (lnum = top, i = 0; i < newsize; i++, lnum++) {
// If the file is empty, there is an empty line 1 that we
// should get rid of, by replacing it with the new line
if (empty_buffer && lnum == 0) {
- ml_replace((linenr_T)1, uep->ue_array[i], true);
+ ml_replace(1, uep->ue_array[i], true);
} else {
- ml_append(lnum, uep->ue_array[i], (colnr_T)0, false);
+ ml_append(lnum, uep->ue_array[i], 0, false);
}
xfree(uep->ue_array[i]);
}
@@ -2362,7 +2376,13 @@ static void u_undoredo(int undo, bool do_buf_event)
}
}
- changed_lines(top + 1, 0, bot, newsize - oldsize, do_buf_event);
+ changed_lines(curbuf, top + 1, 0, bot, newsize - oldsize, do_buf_event);
+ // When text has been changed, possibly the start of the next line
+ // may have SpellCap that should be removed or it needs to be
+ // displayed. Schedule the next line for redrawing just in case.
+ if (spell_check_window(curwin) && bot <= curbuf->b_ml.ml_line_count) {
+ redrawWinline(curwin, bot);
+ }
// Set the '[ mark.
if (top + 1 < curbuf->b_op_start.lnum) {
@@ -2398,13 +2418,13 @@ static void u_undoredo(int undo, bool do_buf_event)
// Adjust Extmarks
ExtmarkUndoObject undo_info;
if (undo) {
- for (long i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
+ for (int i = (int)kv_size(curhead->uh_extmark) - 1; i > -1; i--) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
// redo
} else {
- for (long i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
+ for (int i = 0; i < (int)kv_size(curhead->uh_extmark); i++) {
undo_info = kv_A(curhead->uh_extmark, i);
extmark_apply_undo(undo_info, undo);
}
@@ -2422,7 +2442,7 @@ static void u_undoredo(int undo, bool do_buf_event)
curbuf->b_ml.ml_flags |= ML_EMPTY;
}
if (old_flags & UH_CHANGED) {
- changed();
+ changed(curbuf);
} else {
unchanged(curbuf, false, true);
}
@@ -2435,7 +2455,7 @@ static void u_undoredo(int undo, bool do_buf_event)
}
// restore marks from before undo/redo
- for (long i = 0; i < NMARKS; i++) {
+ for (int i = 0; i < NMARKS; i++) {
if (curhead->uh_namedm[i].mark.lnum != 0) {
free_fmark(curbuf->b_namedm[i]);
curbuf->b_namedm[i] = curhead->uh_namedm[i];
@@ -2462,7 +2482,7 @@ static void u_undoredo(int undo, bool do_buf_event)
if (curhead->uh_cursor.lnum == curwin->w_cursor.lnum) {
curwin->w_cursor.col = curhead->uh_cursor.col;
if (virtual_active() && curhead->uh_cursor_vcol >= 0) {
- coladvance((colnr_T)curhead->uh_cursor_vcol);
+ coladvance(curhead->uh_cursor_vcol);
} else {
curwin->w_cursor.coladd = 0;
}
@@ -2486,8 +2506,8 @@ static void u_undoredo(int undo, bool do_buf_event)
if (undo) {
// We are below the previous undo. However, to make ":earlier 1s"
// work we compute this as being just above the just undone change.
- curbuf->b_u_seq_cur = curhead->uh_next.ptr ?
- curhead->uh_next.ptr->uh_seq : 0;
+ curbuf->b_u_seq_cur = curhead->uh_next.ptr
+ ? curhead->uh_next.ptr->uh_seq : 0;
}
// Remember where we are for ":earlier 1f" and ":later 1f".
@@ -2589,7 +2609,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet)
u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount,
_(msgstr),
did_undo ? _("before") : _("after"),
- uhp == NULL ? (int64_t)0L : (int64_t)uhp->uh_seq,
+ uhp == NULL ? 0 : (int64_t)uhp->uh_seq,
msgbuf);
}
@@ -2600,7 +2620,7 @@ void undo_fmt_time(char *buf, size_t buflen, time_t tt)
struct tm curtime;
os_localtime_r(&tt, &curtime);
size_t n;
- if (time(NULL) - tt < (60L * 60L * 12L)) {
+ if (time(NULL) - tt < (60 * 60 * 12)) {
// within 12 hours
n = strftime(buf, buflen, "%H:%M:%S", &curtime);
} else {
@@ -2654,13 +2674,13 @@ void ex_undolist(exarg_T *eap)
while (uhp != NULL) {
if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark
&& uhp->uh_walk != mark) {
- vim_snprintf(IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes);
+ vim_snprintf(IObuff, IOSIZE, "%6d %7d ", uhp->uh_seq, changes);
undo_fmt_time(IObuff + strlen(IObuff), IOSIZE - strlen(IObuff), uhp->uh_time);
if (uhp->uh_save_nr > 0) {
while (strlen(IObuff) < 33) {
- STRCAT(IObuff, " ");
+ xstrlcat(IObuff, " ", IOSIZE);
}
- vim_snprintf_add(IObuff, IOSIZE, " %3ld", uhp->uh_save_nr);
+ vim_snprintf_add(IObuff, IOSIZE, " %3d", uhp->uh_save_nr);
}
GA_APPEND(char *, &ga, xstrdup(IObuff));
}
@@ -2697,7 +2717,7 @@ void ex_undolist(exarg_T *eap)
}
if (GA_EMPTY(&ga)) {
- msg(_("Nothing to undo"));
+ msg(_("Nothing to undo"), 0);
} else {
sort_strings(ga.ga_data, ga.ga_len);
@@ -2762,7 +2782,7 @@ void u_find_first_changed(void)
linenr_T lnum;
for (lnum = 1; lnum < curbuf->b_ml.ml_line_count
&& lnum <= uep->ue_size; lnum++) {
- if (strcmp(ml_get_buf(curbuf, lnum, false), uep->ue_array[lnum - 1]) != 0) {
+ if (strcmp(ml_get_buf(curbuf, lnum), uep->ue_array[lnum - 1]) != 0) {
clearpos(&(uhp->uh_cursor));
uhp->uh_cursor.lnum = lnum;
return;
@@ -2807,7 +2827,7 @@ static void u_unch_branch(u_header_T *uhp)
static u_entry_T *u_get_headentry(buf_T *buf)
{
if (buf->b_u_newhead == NULL || buf->b_u_newhead->uh_entry == NULL) {
- iemsg(_("E439: undo list corrupt"));
+ iemsg(_(e_undo_list_corrupt));
return NULL;
}
return buf->b_u_newhead->uh_entry;
@@ -2828,9 +2848,9 @@ static void u_getbot(buf_T *buf)
// inserted (0 - deleted) since calling u_save. This is equal to the
// old line count subtracted from the current line count.
linenr_T extra = buf->b_ml.ml_line_count - uep->ue_lcount;
- uep->ue_bot = uep->ue_top + (linenr_T)uep->ue_size + 1 + extra;
+ uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra;
if (uep->ue_bot < 1 || uep->ue_bot > buf->b_ml.ml_line_count) {
- iemsg(_("E440: undo line missing"));
+ iemsg(_(e_undo_line_missing));
uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will
// get all the old lines back
// without deleting the current
@@ -2912,8 +2932,6 @@ static void u_freebranch(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
/// @param uhpp if not NULL reset when freeing this header
static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
{
- u_entry_T *uep, *nuep;
-
// Check for pointers to the header that become invalid now.
if (buf->b_u_curhead == uhp) {
buf->b_u_curhead = NULL;
@@ -2925,7 +2943,8 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
*uhpp = NULL;
}
- for (uep = uhp->uh_entry; uep != NULL; uep = nuep) {
+ u_entry_T *nuep;
+ for (u_entry_T *uep = uhp->uh_entry; uep != NULL; uep = nuep) {
nuep = uep->ue_next;
u_freeentry(uep, uep->ue_size);
}
@@ -2940,7 +2959,7 @@ static void u_freeentries(buf_T *buf, u_header_T *uhp, u_header_T **uhpp)
}
/// free entry 'uep' and 'n' lines in uep->ue_array[]
-static void u_freeentry(u_entry_T *uep, long n)
+static void u_freeentry(u_entry_T *uep, int n)
{
while (n > 0) {
xfree(uep->ue_array[--n]);
@@ -2962,35 +2981,35 @@ void u_clearall(buf_T *buf)
buf->b_u_line_lnum = 0;
}
-/// save the line "lnum" for the "U" command
-void u_saveline(linenr_T lnum)
+/// Save the line "lnum" for the "U" command.
+void u_saveline(buf_T *buf, linenr_T lnum)
{
- if (lnum == curbuf->b_u_line_lnum) { // line is already saved
+ if (lnum == buf->b_u_line_lnum) { // line is already saved
return;
}
- if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { // should never happen
+ if (lnum < 1 || lnum > buf->b_ml.ml_line_count) { // should never happen
return;
}
- u_clearline();
- curbuf->b_u_line_lnum = lnum;
- if (curwin->w_cursor.lnum == lnum) {
- curbuf->b_u_line_colnr = curwin->w_cursor.col;
+ u_clearline(buf);
+ buf->b_u_line_lnum = lnum;
+ if (curwin->w_buffer == buf && curwin->w_cursor.lnum == lnum) {
+ buf->b_u_line_colnr = curwin->w_cursor.col;
} else {
- curbuf->b_u_line_colnr = 0;
+ buf->b_u_line_colnr = 0;
}
- curbuf->b_u_line_ptr = u_save_line(lnum);
+ buf->b_u_line_ptr = u_save_line_buf(buf, lnum);
}
/// clear the line saved for the "U" command
/// (this is used externally for crossing a line while in insert mode)
-void u_clearline(void)
+void u_clearline(buf_T *buf)
{
- if (curbuf->b_u_line_ptr == NULL) {
+ if (buf->b_u_line_ptr == NULL) {
return;
}
- XFREE_CLEAR(curbuf->b_u_line_ptr);
- curbuf->b_u_line_lnum = 0;
+ XFREE_CLEAR(buf->b_u_line_ptr);
+ buf->b_u_line_lnum = 0;
}
/// Implementation of the "U" command.
@@ -3007,7 +3026,7 @@ void u_undoline(void)
// first save the line for the 'u' command
if (u_savecommon(curbuf, curbuf->b_u_line_lnum - 1,
- curbuf->b_u_line_lnum + 1, (linenr_T)0, false) == FAIL) {
+ curbuf->b_u_line_lnum + 1, 0, false) == FAIL) {
return;
}
@@ -3056,7 +3075,7 @@ static char *u_save_line(linenr_T lnum)
/// @param buf buffer to copy from
static char *u_save_line_buf(buf_T *buf, linenr_T lnum)
{
- return xstrdup(ml_get_buf(buf, lnum, false));
+ return xstrdup(ml_get_buf(buf, lnum));
}
/// Check if the 'modified' flag is set, or 'ff' has changed (only need to
@@ -3102,7 +3121,7 @@ bool curbufIsChanged(void)
/// @param[in] first_uhp Undo blocks list to start with.
///
/// @return [allocated] List with a representation of undo blocks.
-list_T *u_eval_tree(const u_header_T *const first_uhp)
+static list_T *u_eval_tree(buf_T *const buf, const u_header_T *const first_uhp)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
list_T *const list = tv_list_alloc(kListLenMayKnow);
@@ -3111,10 +3130,10 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
dict_T *const dict = tv_dict_alloc();
tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq);
tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time);
- if (uhp == curbuf->b_u_newhead) {
+ if (uhp == buf->b_u_newhead) {
tv_dict_add_nr(dict, S_LEN("newhead"), 1);
}
- if (uhp == curbuf->b_u_curhead) {
+ if (uhp == buf->b_u_curhead) {
tv_dict_add_nr(dict, S_LEN("curhead"), 1);
}
if (uhp->uh_save_nr > 0) {
@@ -3123,7 +3142,7 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
if (uhp->uh_alt_next.ptr != NULL) {
// Recursive call to add alternate undo tree.
- tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr));
+ tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(buf, uhp->uh_alt_next.ptr));
}
tv_list_append_dict(list, dict);
@@ -3132,6 +3151,48 @@ list_T *u_eval_tree(const u_header_T *const first_uhp)
return list;
}
+/// "undofile(name)" function
+void f_undofile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *const fname = tv_get_string(&argvars[0]);
+
+ if (*fname == NUL) {
+ // If there is no file name there will be no undo file.
+ rettv->vval.v_string = NULL;
+ } else {
+ char *ffname = FullName_save(fname, true);
+
+ if (ffname != NULL) {
+ rettv->vval.v_string = u_get_undo_file_name(ffname, false);
+ }
+ xfree(ffname);
+ }
+}
+
+/// "undotree(expr)" function
+void f_undotree(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_dict_alloc_ret(rettv);
+
+ typval_T *const tv = &argvars[0];
+ buf_T *const buf = tv->v_type == VAR_UNKNOWN ? curbuf : get_buf_arg(tv);
+ if (buf == NULL) {
+ return;
+ }
+
+ dict_T *dict = rettv->vval.v_dict;
+
+ tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)buf->b_u_synced);
+ tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)buf->b_u_seq_last);
+ tv_dict_add_nr(dict, S_LEN("save_last"), (varnumber_T)buf->b_u_save_nr_last);
+ tv_dict_add_nr(dict, S_LEN("seq_cur"), (varnumber_T)buf->b_u_seq_cur);
+ tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)buf->b_u_time_cur);
+ tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)buf->b_u_save_nr_cur);
+
+ tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(buf, buf->b_u_oldhead));
+}
+
// Given the buffer, Return the undo header. If none is set, set one first.
// NULL will be returned if e.g undolevels = -1 (undo disabled)
u_header_T *u_force_get_undo_header(buf_T *buf)
@@ -3144,15 +3205,13 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
}
// Create the first undo header for the buffer
if (!uhp) {
- // Undo is normally invoked in change code, which already has swapped
- // curbuf.
// Args are tricky: this means replace empty range by empty range..
- u_savecommon(curbuf, 0, 1, 1, true);
+ u_savecommon(buf, 0, 1, 1, true);
uhp = buf->b_u_curhead;
if (!uhp) {
uhp = buf->b_u_newhead;
- if (get_undolevel(curbuf) > 0 && !uhp) {
+ if (get_undolevel(buf) > 0 && !uhp) {
abort();
}
}
diff --git a/src/nvim/undo.h b/src/nvim/undo.h
index f494d4de86..f3a8a60d45 100644
--- a/src/nvim/undo.h
+++ b/src/nvim/undo.h
@@ -1,10 +1,11 @@
-#ifndef NVIM_UNDO_H
-#define NVIM_UNDO_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/undo_defs.h"
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+#include "nvim/undo_defs.h" // IWYU pragma: export
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "undo.h.generated.h"
#endif
-#endif // NVIM_UNDO_H
diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h
index 7c065c540b..0b78ea543f 100644
--- a/src/nvim/undo_defs.h
+++ b/src/nvim/undo_defs.h
@@ -1,34 +1,36 @@
-#ifndef NVIM_UNDO_DEFS_H
-#define NVIM_UNDO_DEFS_H
+#pragma once
-#include <time.h> // for time_t
+#include <time.h>
#include "nvim/extmark_defs.h"
#include "nvim/mark_defs.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
+
+/// Size in bytes of the hash used in the undo file.
+enum { UNDO_HASH_SIZE = 32, };
typedef struct u_header u_header_T;
-// Structure to store info about the Visual area.
+/// Structure to store info about the Visual area.
typedef struct {
- pos_T vi_start; // start pos of last VIsual
- pos_T vi_end; // end position of last VIsual
- int vi_mode; // VIsual_mode of last VIsual
- colnr_T vi_curswant; // MAXCOL from w_curswant
+ pos_T vi_start; ///< start pos of last VIsual
+ pos_T vi_end; ///< end position of last VIsual
+ int vi_mode; ///< VIsual_mode of last VIsual
+ colnr_T vi_curswant; ///< MAXCOL from w_curswant
} visualinfo_T;
#include "nvim/buffer_defs.h"
typedef struct u_entry u_entry_T;
struct u_entry {
- u_entry_T *ue_next; // pointer to next entry in list
- linenr_T ue_top; // number of line above undo block
- linenr_T ue_bot; // number of line below undo block
- linenr_T ue_lcount; // linecount when u_save called
- char **ue_array; // array of lines in undo block
- long ue_size; // number of lines in ue_array
+ u_entry_T *ue_next; ///< pointer to next entry in list
+ linenr_T ue_top; ///< number of line above undo block
+ linenr_T ue_bot; ///< number of line below undo block
+ linenr_T ue_lcount; ///< linecount when u_save called
+ char **ue_array; ///< array of lines in undo block
+ linenr_T ue_size; ///< number of lines in ue_array
#ifdef U_DEBUG
- int ue_magic; // magic number to check allocation
+ int ue_magic; ///< magic number to check allocation
#endif
};
@@ -36,42 +38,42 @@ struct u_header {
// The following have a pointer and a number. The number is used when reading
// the undo file in u_read_undo()
union {
- u_header_T *ptr; // pointer to next undo header in list
- long seq;
+ u_header_T *ptr; ///< pointer to next undo header in list
+ int seq;
} uh_next;
union {
- u_header_T *ptr; // pointer to previous header in list
- long seq;
+ u_header_T *ptr; ///< pointer to previous header in list
+ int seq;
} uh_prev;
union {
- u_header_T *ptr; // pointer to next header for alt. redo
- long seq;
+ u_header_T *ptr; ///< pointer to next header for alt. redo
+ int seq;
} uh_alt_next;
union {
- u_header_T *ptr; // pointer to previous header for alt. redo
- long seq;
+ u_header_T *ptr; ///< pointer to previous header for alt. redo
+ int seq;
} uh_alt_prev;
- long uh_seq; // sequence number, higher == newer undo
- int uh_walk; // used by undo_time()
- u_entry_T *uh_entry; // pointer to first entry
- u_entry_T *uh_getbot_entry; // pointer to where ue_bot must be set
- pos_T uh_cursor; // cursor position before saving
- long uh_cursor_vcol;
- int uh_flags; // see below
- fmark_T uh_namedm[NMARKS]; // marks before undo/after redo
- extmark_undo_vec_t uh_extmark; // info to move extmarks
- visualinfo_T uh_visual; // Visual areas before undo/after redo
- time_t uh_time; // timestamp when the change was made
- long uh_save_nr; // set when the file was saved after the
- // changes in this block
+ int uh_seq; ///< sequence number, higher == newer undo
+ int uh_walk; ///< used by undo_time()
+ u_entry_T *uh_entry; ///< pointer to first entry
+ u_entry_T *uh_getbot_entry; ///< pointer to where ue_bot must be set
+ pos_T uh_cursor; ///< cursor position before saving
+ colnr_T uh_cursor_vcol;
+ int uh_flags; ///< see below
+ fmark_T uh_namedm[NMARKS]; ///< marks before undo/after redo
+ extmark_undo_vec_t uh_extmark; ///< info to move extmarks
+ visualinfo_T uh_visual; ///< Visual areas before undo/after redo
+ time_t uh_time; ///< timestamp when the change was made
+ int uh_save_nr; ///< set when the file was saved after the
+ ///< changes in this block
#ifdef U_DEBUG
- int uh_magic; // magic number to check allocation
+ int uh_magic; ///< magic number to check allocation
#endif
};
-// values for uh_flags
-#define UH_CHANGED 0x01 // b_changed flag before undo/after redo
-#define UH_EMPTYBUF 0x02 // buffer was empty
-#define UH_RELOAD 0x04 // buffer was reloaded
-
-#endif // NVIM_UNDO_DEFS_H
+/// values for uh_flags
+enum {
+ UH_CHANGED = 0x01, ///< b_changed flag before undo/after redo
+ UH_EMPTYBUF = 0x02, ///< buffer was empty
+ UH_RELOAD = 0x04, ///< buffer was reloaded
+};
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index ef13f67e49..7c65af5138 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -1,41 +1,40 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
// usercmd.c: User defined command support
#include <assert.h>
#include <inttypes.h>
+#include <lauxlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "auto/config.h"
-#include "lauxlib.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand_defs.h"
#include "nvim/eval.h"
#include "nvim/ex_docmd.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/keycodes.h"
#include "nvim/lua/executor.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/menu.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
#include "nvim/os/input.h"
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/usercmd.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -44,11 +43,13 @@
garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL };
-static char e_complete_used_without_allowing_arguments[]
- = N_("E1208: -complete used without allowing arguments");
-static char e_no_such_user_defined_command_str[]
+static const char e_argument_required_for_str[]
+ = N_("E179: Argument required for %s");
+static const char e_no_such_user_defined_command_str[]
= N_("E184: No such user-defined command: %s");
-static char e_no_such_user_defined_command_in_current_buffer_str[]
+static const char e_complete_used_without_allowing_arguments[]
+ = N_("E1208: -complete used without allowing arguments");
+static const char e_no_such_user_defined_command_in_current_buffer_str[]
= N_("E1237: No such user-defined command in current buffer: %s");
/// List of names for completion for ":command" with the EXPAND_ flag.
@@ -56,7 +57,6 @@ static char e_no_such_user_defined_command_in_current_buffer_str[]
static const char *command_complete[] = {
[EXPAND_ARGLIST] = "arglist",
[EXPAND_AUGROUP] = "augroup",
- [EXPAND_BEHAVE] = "behave",
[EXPAND_BUFFERS] = "buffer",
[EXPAND_CHECKHEALTH] = "checkhealth",
[EXPAND_COLORS] = "color",
@@ -129,23 +129,21 @@ static struct {
char *find_ucmd(exarg_T *eap, char *p, int *full, expand_T *xp, int *complp)
{
int len = (int)(p - eap->cmd);
- int j, k, matchlen = 0;
- ucmd_T *uc;
+ int matchlen = 0;
bool found = false;
bool possible = false;
- char *cp, *np; // Point into typed cmd and test name
- garray_T *gap;
bool amb_local = false; // Found ambiguous buffer-local command,
// only full match global is accepted.
// Look for buffer-local user commands first, then global ones.
- gap = &prevwin_curwin()->w_buffer->b_ucmds;
- for (;;) {
+ garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
+ while (true) {
+ int j;
for (j = 0; j < gap->ga_len; j++) {
- uc = USER_CMD_GA(gap, j);
- cp = eap->cmd;
- np = uc->uc_name;
- k = 0;
+ ucmd_T *uc = USER_CMD_GA(gap, j);
+ char *cp = eap->cmd;
+ char *np = uc->uc_name;
+ int k = 0;
while (k < len && *np != NUL && *cp++ == *np++) {
k++;
}
@@ -234,7 +232,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
// Check for attributes
while (*arg == '-') {
arg++; // Skip "-".
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
if (*p == NUL) {
// Cursor is still in the attribute.
p = strchr(arg, '=');
@@ -262,11 +260,11 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
}
return NULL;
}
- arg = (const char *)skipwhite(p);
+ arg = skipwhite(p);
}
// After the attributes comes the new command name.
- p = (const char *)skiptowhite(arg);
+ p = skiptowhite(arg);
if (*p == NUL) {
xp->xp_context = EXPAND_USER_COMMANDS;
xp->xp_pattern = (char *)arg;
@@ -274,7 +272,7 @@ const char *set_context_in_user_cmd(expand_T *xp, const char *arg_in)
}
// And finally comes a normal command.
- return (const char *)skipwhite(p);
+ return skipwhite(p);
}
/// Set the completion context for the argument of a user defined command.
@@ -292,14 +290,14 @@ const char *set_context_in_user_cmdarg(const char *cmd FUNC_ATTR_UNUSED, const c
}
if (context == EXPAND_MENUS) {
- return (const char *)set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
+ return set_context_in_menu_cmd(xp, cmd, (char *)arg, forceit);
}
if (context == EXPAND_COMMANDS) {
return arg;
}
if (context == EXPAND_MAPPINGS) {
- return (const char *)set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false,
- CMD_map);
+ return set_context_in_map_cmd(xp, "map", (char *)arg, forceit, false, false,
+ CMD_map);
}
// Find start of last argument.
const char *p = arg;
@@ -424,6 +422,13 @@ char *get_user_cmd_complete(expand_T *xp, int idx)
int cmdcomplete_str_to_type(const char *complete_str)
{
+ if (strncmp(complete_str, "custom,", 7) == 0) {
+ return EXPAND_USER_DEFINED;
+ }
+ if (strncmp(complete_str, "customlist,", 11) == 0) {
+ return EXPAND_USER_LIST;
+ }
+
for (int i = 0; i < (int)(ARRAY_SIZE(command_complete)); i++) {
char *cmd_compl = get_command_complete(i);
if (cmd_compl == NULL) {
@@ -439,17 +444,15 @@ int cmdcomplete_str_to_type(const char *complete_str)
static void uc_list(char *name, size_t name_len)
{
- int i, j;
bool found = false;
- ucmd_T *cmd;
- uint32_t a;
// In cmdwin, the alternative buffer should be used.
const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds;
- for (;;) {
+ while (true) {
+ int i;
for (i = 0; i < gap->ga_len; i++) {
- cmd = USER_CMD_GA(gap, i);
- a = cmd->uc_argt;
+ ucmd_T *cmd = USER_CMD_GA(gap, i);
+ uint32_t a = cmd->uc_argt;
// Skip commands which don't match the requested prefix and
// commands filtered out.
@@ -470,7 +473,7 @@ static void uc_list(char *name, size_t name_len)
}
// Special cases
- int len = 4;
+ size_t len = 4;
if (a & EX_BANG) {
msg_putchar('!');
len--;
@@ -491,8 +494,8 @@ static void uc_list(char *name, size_t name_len)
msg_putchar(' ');
}
- msg_outtrans_attr(cmd->uc_name, HL_ATTR(HLF_D));
- len = (int)strlen(cmd->uc_name) + 4;
+ msg_outtrans(cmd->uc_name, HL_ATTR(HLF_D));
+ len = strlen(cmd->uc_name) + 4;
do {
msg_putchar(' ');
@@ -501,7 +504,7 @@ static void uc_list(char *name, size_t name_len)
// "over" is how much longer the name is than the column width for
// the name, we'll try to align what comes after.
- const int over = len - 22;
+ const int64_t over = (int64_t)len - 22;
len = 0;
// Arguments
@@ -525,22 +528,22 @@ static void uc_list(char *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 5 - over);
+ } while ((int64_t)len < 5 - over);
// Address / Range
if (a & (EX_RANGE | EX_COUNT)) {
if (a & EX_COUNT) {
// -count=N
- snprintf(IObuff + len, IOSIZE, "%" PRId64 "c",
- (int64_t)cmd->uc_def);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "c", cmd->uc_def);
+ assert(rc > 0);
+ len += (size_t)rc;
} else if (a & EX_DFLALL) {
IObuff[len++] = '%';
} else if (cmd->uc_def >= 0) {
// -range=N
- snprintf(IObuff + len, IOSIZE, "%" PRId64 "",
- (int64_t)cmd->uc_def);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%" PRId64 "", cmd->uc_def);
+ assert(rc > 0);
+ len += (size_t)rc;
} else {
IObuff[len++] = '.';
}
@@ -548,35 +551,37 @@ static void uc_list(char *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 8 - over);
+ } while ((int64_t)len < 8 - over);
// Address Type
- for (j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
+ for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- STRCPY(IObuff + len, addr_type_complete[j].shortname);
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%s", addr_type_complete[j].shortname);
+ assert(rc > 0);
+ len += (size_t)rc;
break;
}
}
do {
IObuff[len++] = ' ';
- } while (len < 13 - over);
+ } while ((int64_t)len < 13 - over);
// Completion
char *cmd_compl = get_command_complete(cmd->uc_compl);
if (cmd_compl != NULL) {
- STRCPY(IObuff + len, get_command_complete(cmd->uc_compl));
- len += (int)strlen(IObuff + len);
+ int rc = snprintf(IObuff + len, IOSIZE - len, "%s", get_command_complete(cmd->uc_compl));
+ assert(rc > 0);
+ len += (size_t)rc;
}
do {
IObuff[len++] = ' ';
- } while (len < 25 - over);
+ } while ((int64_t)len < 25 - over);
IObuff[len] = '\0';
- msg_outtrans(IObuff);
+ msg_outtrans(IObuff, 0);
if (cmd->uc_luaref != LUA_NOREF) {
char *fn = nlua_funcref_str(cmd->uc_luaref);
@@ -605,7 +610,7 @@ static void uc_list(char *name, size_t name_len)
}
if (!found) {
- msg(_("No user-defined commands found"));
+ msg(_("No user-defined commands found"), 0);
}
}
@@ -613,11 +618,11 @@ static void uc_list(char *name, size_t name_len)
int parse_addr_type_arg(char *value, int vallen, cmd_addr_T *addr_type_arg)
FUNC_ATTR_NONNULL_ALL
{
- int i, a, b;
+ int i;
for (i = 0; addr_type_complete[i].expand != ADDR_NONE; i++) {
- a = (int)strlen(addr_type_complete[i].name) == vallen;
- b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0;
+ int a = (int)strlen(addr_type_complete[i].name) == vallen;
+ int b = strncmp(value, addr_type_complete[i].name, (size_t)vallen) == 0;
if (a && b) {
*addr_type_arg = addr_type_complete[i].expand;
break;
@@ -647,11 +652,10 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
{
const char *arg = NULL;
size_t arglen = 0;
- int i;
int valend = vallen;
// Look for any argument part - which is the part after any ','
- for (i = 0; i < vallen; i++) {
+ for (int i = 0; i < vallen; i++) {
if (value[i] == ',') {
arg = (char *)&value[i + 1];
arglen = (size_t)(vallen - i - 1);
@@ -660,6 +664,7 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
}
}
+ int i;
for (i = 0; i < (int)ARRAY_SIZE(command_complete); i++) {
if (get_command_complete(i) == NULL) {
continue;
@@ -699,12 +704,10 @@ int parse_compl_arg(const char *value, int vallen, int *complp, uint32_t *argt,
return OK;
}
-static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *flags, int *complp,
+static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, int *def, int *flags, int *complp,
char **compl_arg, cmd_addr_T *addr_type_arg)
FUNC_ATTR_NONNULL_ALL
{
- char *p;
-
if (len == 0) {
emsg(_("E175: No attribute specified"));
return FAIL;
@@ -722,13 +725,12 @@ static int uc_scan_attr(char *attr, size_t len, uint32_t *argt, long *def, int *
} else if (STRNICMP(attr, "bar", len) == 0) {
*argt |= EX_TRLBAR;
} else {
- int i;
char *val = NULL;
size_t vallen = 0;
size_t attrlen = len;
// Look for the attribute name - which is the part before any '='
- for (i = 0; i < (int)len; i++) {
+ for (int i = 0; i < (int)len; i++) {
if (attr[i] == '=') {
val = &attr[i + 1];
vallen = len - (size_t)i - 1;
@@ -762,14 +764,14 @@ wrong_nargs:
if (vallen == 1 && *val == '%') {
*argt |= EX_DFLALL;
} else if (val != NULL) {
- p = val;
+ char *p = val;
if (*def >= 0) {
two_count:
emsg(_("E177: Count cannot be specified twice"));
return FAIL;
}
- *def = getdigits_long(&p, true, 0);
+ *def = getdigits_int(&p, true, 0);
*argt |= EX_ZEROR;
if (p != val + vallen || vallen == 0) {
@@ -790,12 +792,12 @@ invalid_count:
}
if (val != NULL) {
- p = val;
+ char *p = val;
if (*def >= 0) {
goto two_count;
}
- *def = getdigits_long(&p, true, 0);
+ *def = getdigits_int(&p, true, 0);
if (p != val + vallen) {
goto invalid_count;
@@ -807,7 +809,7 @@ invalid_count:
}
} else if (STRNICMP(attr, "complete", attrlen) == 0) {
if (val == NULL) {
- emsg(_("E179: argument required for -complete"));
+ semsg(_(e_argument_required_for_str), "-complete");
return FAIL;
}
@@ -818,7 +820,7 @@ invalid_count:
} else if (STRNICMP(attr, "addr", attrlen) == 0) {
*argt |= EX_RANGE;
if (val == NULL) {
- emsg(_("E179: argument required for -addr"));
+ semsg(_(e_argument_required_for_str), "-addr");
return FAIL;
}
if (parse_addr_type_arg(val, (int)vallen, addr_type_arg) == FAIL) {
@@ -862,18 +864,17 @@ char *uc_validate_name(char *name)
/// This function takes ownership of compl_arg, compl_luaref, and luaref.
///
/// @return OK if the command is created, FAIL otherwise.
-int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, long def, int flags,
- int compl, char *compl_arg, LuaRef compl_luaref, LuaRef preview_luaref,
- cmd_addr_T addr_type, LuaRef luaref, bool force)
+int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt, int64_t def,
+ int flags, int context, char *compl_arg, LuaRef compl_luaref,
+ LuaRef preview_luaref, cmd_addr_T addr_type, LuaRef luaref, bool force)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ucmd_T *cmd = NULL;
- int i;
int cmp = 1;
char *rep_buf = NULL;
garray_T *gap;
- replace_termcodes(rep, strlen(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS);
+ replace_termcodes(rep, strlen(rep), &rep_buf, 0, 0, NULL, CPO_TO_CPO_FLAGS);
if (rep_buf == NULL) {
// Can't replace termcodes - try using the string as is
rep_buf = xstrdup(rep);
@@ -889,12 +890,12 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt,
gap = &ucmds;
}
+ int i;
+
// Search for the command in the already defined commands.
for (i = 0; i < gap->ga_len; i++) {
- size_t len;
-
cmd = USER_CMD_GA(gap, i);
- len = strlen(cmd->uc_name);
+ size_t len = strlen(cmd->uc_name);
cmp = strncmp(name, cmd->uc_name, name_len);
if (cmp == 0) {
if (name_len < len) {
@@ -946,7 +947,7 @@ int uc_add_command(char *name, size_t name_len, const char *rep, uint32_t argt,
cmd->uc_rep = rep_buf;
cmd->uc_argt = argt;
cmd->uc_def = def;
- cmd->uc_compl = compl;
+ cmd->uc_compl = context;
cmd->uc_script_ctx = current_sctx;
cmd->uc_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&cmd->uc_script_ctx);
@@ -970,25 +971,22 @@ fail:
/// ":command ..."
void ex_command(exarg_T *eap)
{
- char *name;
char *end;
- char *p;
uint32_t argt = 0;
- long def = -1;
+ int def = -1;
int flags = 0;
- int compl = EXPAND_NOTHING;
+ int context = EXPAND_NOTHING;
char *compl_arg = NULL;
cmd_addr_T addr_type_arg = ADDR_NONE;
int has_attr = (eap->arg[0] == '-');
- size_t name_len;
- p = eap->arg;
+ char *p = eap->arg;
// Check for attributes
while (*p == '-') {
p++;
end = skiptowhite(p);
- if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &compl, &compl_arg,
+ if (uc_scan_attr(p, (size_t)(end - p), &argt, &def, &flags, &context, &compl_arg,
&addr_type_arg) == FAIL) {
goto theend;
}
@@ -996,13 +994,13 @@ void ex_command(exarg_T *eap)
}
// Get the name (if any) and skip to the following argument.
- name = p;
+ char *name = p;
end = uc_validate_name(name);
if (!end) {
emsg(_("E182: Invalid command name"));
goto theend;
}
- name_len = (size_t)(end - name);
+ size_t name_len = (size_t)(end - name);
// If there is nothing after the name, and no attributes were specified,
// we are listing commands
@@ -1013,10 +1011,10 @@ void ex_command(exarg_T *eap)
emsg(_("E183: User defined commands must start with an uppercase letter"));
} else if (name_len <= 4 && strncmp(name, "Next", name_len) == 0) {
emsg(_("E841: Reserved name, cannot be used for user defined command"));
- } else if (compl > 0 && (argt & EX_EXTRA) == 0) {
+ } else if (context > 0 && (argt & EX_EXTRA) == 0) {
emsg(_(e_complete_used_without_allowing_arguments));
} else {
- uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, LUA_NOREF,
+ uc_add_command(name, name_len, p, argt, def, flags, context, compl_arg, LUA_NOREF, LUA_NOREF,
addr_type_arg, LUA_NOREF, eap->forceit);
return; // success
@@ -1055,7 +1053,6 @@ void ex_delcommand(exarg_T *eap)
int i = 0;
ucmd_T *cmd = NULL;
int res = -1;
- garray_T *gap;
const char *arg = eap->arg;
bool buffer_only = false;
@@ -1064,8 +1061,8 @@ void ex_delcommand(exarg_T *eap)
arg = skipwhite(arg + 7);
}
- gap = &curbuf->b_ucmds;
- for (;;) {
+ garray_T *gap = &curbuf->b_ucmds;
+ while (true) {
for (i = 0; i < gap->ga_len; i++) {
cmd = USER_CMD_GA(gap, i);
res = strcmp(arg, cmd->uc_name);
@@ -1140,17 +1137,13 @@ bool uc_split_args_iter(const char *arg, size_t arglen, size_t *end, char *buf,
}
/// split and quote args for <f-args>
-static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t argc, size_t *lenp)
+static char *uc_split_args(const char *arg, char **args, const size_t *arglens, size_t argc,
+ size_t *lenp)
{
- char *buf;
- char *p;
- char *q;
- int len;
-
// Precalculate length
- len = 2; // Initial and final quotes
+ int len = 2; // Initial and final quotes
if (args == NULL) {
- p = arg;
+ const char *p = arg;
while (*p) {
if (p[0] == '\\' && p[1] == '\\') {
@@ -1177,7 +1170,7 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
}
} else {
for (size_t i = 0; i < argc; i++) {
- p = args[i];
+ const char *p = args[i];
const char *arg_end = args[i] + arglens[i];
while (p < arg_end) {
@@ -1198,13 +1191,13 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
}
}
- buf = xmalloc((size_t)len + 1);
+ char *buf = xmalloc((size_t)len + 1);
- q = buf;
+ char *q = buf;
*q++ = '"';
if (args == NULL) {
- p = arg;
+ const char *p = arg;
while (*p) {
if (p[0] == '\\' && p[1] == '\\') {
*q++ = '\\';
@@ -1226,12 +1219,12 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
*q++ = ' ';
*q++ = '"';
} else {
- mb_copy_char((const char **)&p, &q);
+ mb_copy_char(&p, &q);
}
}
} else {
for (size_t i = 0; i < argc; i++) {
- p = args[i];
+ const char *p = args[i];
const char *arg_end = args[i] + arglens[i];
while (p < arg_end) {
@@ -1239,7 +1232,7 @@ static char *uc_split_args(char *arg, char **args, const size_t *arglens, size_t
*q++ = '\\';
*q++ = *p++;
} else {
- mb_copy_char((const char **)&p, &q);
+ mb_copy_char(&p, &q);
}
}
if (i != argc - 1) {
@@ -1540,13 +1533,16 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar
case ct_RANGE:
case ct_COUNT: {
char num_buf[20];
- long num = (type == ct_LINE1) ? eap->line1 :
- (type == ct_LINE2) ? eap->line2 :
- (type == ct_RANGE) ? eap->addr_count :
- (eap->addr_count > 0) ? eap->line2 : cmd->uc_def;
+ int64_t num = type == ct_LINE1
+ ? eap->line1
+ : (type == ct_LINE2
+ ? eap->line2
+ : (type == ct_RANGE
+ ? eap->addr_count
+ : (eap->addr_count > 0 ? eap->line2 : cmd->uc_def)));
size_t num_len;
- snprintf(num_buf, sizeof(num_buf), "%" PRId64, (int64_t)num);
+ snprintf(num_buf, sizeof(num_buf), "%" PRId64, num);
num_len = strlen(num_buf);
result = num_len;
@@ -1611,14 +1607,7 @@ static size_t uc_check_code(char *code, size_t len, char *buf, ucmd_T *cmd, exar
int do_ucmd(exarg_T *eap, bool preview)
{
- char *buf;
- char *p;
- char *q;
-
- char *start;
char *end = NULL;
- char *ksp;
- size_t len, totlen;
size_t split_len = 0;
char *split_buf = NULL;
@@ -1627,7 +1616,7 @@ int do_ucmd(exarg_T *eap, bool preview)
if (eap->cmdidx == CMD_USER) {
cmd = USER_CMD(eap->useridx);
} else {
- cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx);
+ cmd = USER_CMD_GA(&prevwin_curwin()->w_buffer->b_ucmds, eap->useridx);
}
if (preview) {
@@ -1643,18 +1632,19 @@ int do_ucmd(exarg_T *eap, bool preview)
// Replace <> in the command by the arguments.
// First round: "buf" is NULL, compute length, allocate "buf".
// Second round: copy result into "buf".
- buf = NULL;
- for (;;) {
- p = cmd->uc_rep; // source
- q = buf; // destination
- totlen = 0;
-
- for (;;) {
- start = vim_strchr(p, '<');
+ char *buf = NULL;
+ while (true) {
+ char *p = cmd->uc_rep; // source
+ char *q = buf; // destination
+ size_t totlen = 0;
+
+ while (true) {
+ char *start = vim_strchr(p, '<');
if (start != NULL) {
end = vim_strchr(start + 1, '>');
}
if (buf != NULL) {
+ char *ksp;
for (ksp = p; *ksp != NUL && (uint8_t)(*ksp) != K_SPECIAL; ksp++) {}
if ((uint8_t)(*ksp) == K_SPECIAL
&& (start == NULL || ksp < start || end == NULL)
@@ -1662,7 +1652,7 @@ int do_ucmd(exarg_T *eap, bool preview)
// K_SPECIAL has been put in the buffer as K_SPECIAL
// KS_SPECIAL KE_FILLER, like for mappings, but
// do_cmdline() doesn't handle that, so convert it back.
- len = (size_t)(ksp - p);
+ size_t len = (size_t)(ksp - p);
if (len > 0) {
memmove(q, p, len);
q += len;
@@ -1682,7 +1672,7 @@ int do_ucmd(exarg_T *eap, bool preview)
end++;
// Take everything up to the '<'
- len = (size_t)(start - p);
+ size_t len = (size_t)(start - p);
if (buf == NULL) {
totlen += len;
} else {
@@ -1751,8 +1741,8 @@ Dictionary commands_array(buf_T *buf)
Dictionary d = ARRAY_DICT_INIT;
ucmd_T *cmd = USER_CMD_GA(gap, i);
- PUT(d, "name", STRING_OBJ(cstr_to_string((char *)cmd->uc_name)));
- PUT(d, "definition", STRING_OBJ(cstr_to_string((char *)cmd->uc_rep)));
+ PUT(d, "name", CSTR_TO_OBJ(cmd->uc_name));
+ PUT(d, "definition", CSTR_TO_OBJ(cmd->uc_rep));
PUT(d, "script_id", INTEGER_OBJ(cmd->uc_script_ctx.sc_sid));
PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG)));
PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR)));
@@ -1772,21 +1762,21 @@ Dictionary commands_array(buf_T *buf)
case (EX_EXTRA | EX_NOSPC | EX_NEEDARG):
arg[0] = '1'; break;
}
- PUT(d, "nargs", STRING_OBJ(cstr_to_string(arg)));
+ PUT(d, "nargs", CSTR_TO_OBJ(arg));
char *cmd_compl = get_command_complete(cmd->uc_compl);
PUT(d, "complete", (cmd_compl == NULL
- ? NIL : STRING_OBJ(cstr_to_string(cmd_compl))));
+ ? NIL : CSTR_TO_OBJ(cmd_compl)));
PUT(d, "complete_arg", cmd->uc_compl_arg == NULL
- ? NIL : STRING_OBJ(cstr_to_string((char *)cmd->uc_compl_arg)));
+ ? NIL : CSTR_TO_OBJ(cmd->uc_compl_arg));
Object obj = NIL;
if (cmd->uc_argt & EX_COUNT) {
if (cmd->uc_def >= 0) {
- snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def);
- obj = STRING_OBJ(cstr_to_string(str)); // -count=N
+ snprintf(str, sizeof(str), "%" PRId64, cmd->uc_def);
+ obj = CSTR_TO_OBJ(str); // -count=N
} else {
- obj = STRING_OBJ(cstr_to_string("0")); // -count
+ obj = CSTR_TO_OBJ("0"); // -count
}
}
PUT(d, "count", obj);
@@ -1794,12 +1784,12 @@ Dictionary commands_array(buf_T *buf)
obj = NIL;
if (cmd->uc_argt & EX_RANGE) {
if (cmd->uc_argt & EX_DFLALL) {
- obj = STRING_OBJ(cstr_to_string("%")); // -range=%
+ obj = CSTR_TO_OBJ("%"); // -range=%
} else if (cmd->uc_def >= 0) {
- snprintf(str, sizeof(str), "%" PRId64, (int64_t)cmd->uc_def);
- obj = STRING_OBJ(cstr_to_string(str)); // -range=N
+ snprintf(str, sizeof(str), "%" PRId64, cmd->uc_def);
+ obj = CSTR_TO_OBJ(str); // -range=N
} else {
- obj = STRING_OBJ(cstr_to_string(".")); // -range
+ obj = CSTR_TO_OBJ("."); // -range
}
}
PUT(d, "range", obj);
@@ -1808,13 +1798,13 @@ Dictionary commands_array(buf_T *buf)
for (int j = 0; addr_type_complete[j].expand != ADDR_NONE; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- obj = STRING_OBJ(cstr_to_string(addr_type_complete[j].name));
+ obj = CSTR_TO_OBJ(addr_type_complete[j].name);
break;
}
}
PUT(d, "addr", obj);
- PUT(rv, (char *)cmd->uc_name, DICTIONARY_OBJ(d));
+ PUT(rv, cmd->uc_name, DICTIONARY_OBJ(d));
}
return rv;
}
diff --git a/src/nvim/usercmd.h b/src/nvim/usercmd.h
index b6bf6c1e33..bcabf9460b 100644
--- a/src/nvim/usercmd.h
+++ b/src/nvim/usercmd.h
@@ -1,28 +1,29 @@
-#ifndef NVIM_USERCMD_H
-#define NVIM_USERCMD_H
+#pragma once
+#include <stddef.h> // IWYU pragma: keep
#include <stdint.h>
+#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
-#include "nvim/garray.h"
-#include "nvim/types.h"
+#include "nvim/garray_defs.h"
+#include "nvim/types_defs.h" // IWYU pragma: keep
typedef struct ucmd {
- char *uc_name; // The command name
- uint32_t uc_argt; // The argument type
- char *uc_rep; // The command's replacement string
- long uc_def; // The default value for a range/count
- int uc_compl; // completion type
- cmd_addr_T uc_addr_type; // The command's address type
- sctx_T uc_script_ctx; // SCTX where the command was defined
- char *uc_compl_arg; // completion argument if any
- LuaRef uc_compl_luaref; // Reference to Lua completion function
- LuaRef uc_preview_luaref; // Reference to Lua preview function
- LuaRef uc_luaref; // Reference to Lua function
+ char *uc_name; ///< The command name
+ uint32_t uc_argt; ///< The argument type
+ char *uc_rep; ///< The command's replacement string
+ int64_t uc_def; ///< The default value for a range/count
+ int uc_compl; ///< completion type
+ cmd_addr_T uc_addr_type; ///< The command's address type
+ sctx_T uc_script_ctx; ///< SCTX where the command was defined
+ char *uc_compl_arg; ///< completion argument if any
+ LuaRef uc_compl_luaref; ///< Reference to Lua completion function
+ LuaRef uc_preview_luaref; ///< Reference to Lua preview function
+ LuaRef uc_luaref; ///< Reference to Lua function
} ucmd_T;
-#define UC_BUFFER 1 // -buffer: local to current buffer
+enum { UC_BUFFER = 1, }; ///< -buffer: local to current buffer
extern garray_T ucmds;
@@ -32,4 +33,3 @@ extern garray_T ucmds;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "usercmd.h.generated.h"
#endif
-#endif // NVIM_USERCMD_H
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 3324ac2a94..cb9088afae 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// @file version.c
///
/// Nvim was forked from Vim 7.4.160.
@@ -9,33 +6,33 @@
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
+#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
-#include "auto/config.h"
#include "auto/versiondef.h" // version info generated by the build system
#include "auto/versiondef_git.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
#include "nvim/ex_cmds_defs.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
-#include "nvim/highlight_defs.h"
+#include "nvim/highlight.h"
#include "nvim/lua/executor.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
-#include "nvim/option_defs.h"
-#include "nvim/os/os_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/os.h"
#include "nvim/strings.h"
#include "nvim/version.h"
-#include "nvim/vim.h"
// for ":version", ":intro", and "nvim --version"
#ifndef NVIM_VERSION_MEDIUM
@@ -57,17 +54,6 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS;
# include "version.c.generated.h"
#endif
-static char *features[] = {
-#ifdef HAVE_ACL
- "+acl",
-#else
- "-acl",
-#endif
-
- "+tui",
- NULL
-};
-
// clang-format off
static const int included_patches[] = {
2424,
@@ -91,7 +77,7 @@ static const int included_patches[] = {
2406,
2405,
2404,
- // 2403,
+ 2403,
2402,
2401,
2400,
@@ -121,7 +107,7 @@ static const int included_patches[] = {
2376,
2375,
2374,
- // 2373,
+ 2373,
2372,
// 2371,
2370,
@@ -218,7 +204,7 @@ static const int included_patches[] = {
// 2279,
2278,
// 2277,
- // 2276,
+ 2276,
2275,
2274,
// 2273,
@@ -346,7 +332,7 @@ static const int included_patches[] = {
2151,
2150,
2149,
- // 2148,
+ 2148,
2147,
// 2146,
2145,
@@ -395,12 +381,12 @@ static const int included_patches[] = {
2102,
2101,
2100,
- // 2099,
+ 2099,
2098,
2097,
2096,
2095,
- // 2094,
+ 2094,
// 2093,
// 2092,
2091,
@@ -424,15 +410,15 @@ static const int included_patches[] = {
2073,
2072,
// 2071,
- // 2070,
- // 2069,
- // 2068,
- // 2067,
- // 2066,
+ 2070,
+ 2069,
+ 2068,
+ 2067,
+ 2066,
2065,
2064,
2063,
- // 2062,
+ 2062,
2061,
2060,
2059,
@@ -441,16 +427,16 @@ static const int included_patches[] = {
2056,
2055,
2054,
- // 2053,
+ 2053,
2052,
2051,
2050,
2049,
- // 2048,
- // 2047,
- // 2046,
+ 2048,
+ 2047,
+ 2046,
2045,
- // 2044,
+ 2044,
2043,
2042,
2041,
@@ -667,7 +653,7 @@ static const int included_patches[] = {
1830,
1829,
1828,
- // 1827,
+ 1827,
1826,
1825,
1824,
@@ -972,43 +958,43 @@ static const int included_patches[] = {
// 1525,
1524,
// 1523,
- // 1522,
- // 1521,
- // 1520,
+ 1522,
+ 1521,
+ 1520,
1519,
- // 1518,
- // 1517,
+ 1518,
+ 1517,
1516,
- // 1515,
+ 1515,
1514,
- // 1513,
+ 1513,
1512,
- // 1511,
+ 1511,
1510,
1509,
- // 1508,
+ 1508,
1507,
1506,
1505,
1504,
1503,
- // 1502,
+ 1502,
1501,
1500,
- // 1499,
+ 1499,
1498,
- // 1497,
- // 1496,
- // 1495,
- // 1494,
- // 1493,
+ 1497,
+ 1496,
+ 1495,
+ 1494,
+ 1493,
1492,
1491,
1490,
1489,
1488,
1487,
- // 1486,
+ 1486,
1485,
1484,
1483,
@@ -1031,83 +1017,83 @@ static const int included_patches[] = {
1466,
1465,
1464,
- // 1463,
+ 1463,
1462,
1461,
- // 1460,
- // 1459,
+ 1460,
+ 1459,
1458,
1457,
1456,
- // 1455,
+ 1455,
1454,
- // 1453,
- // 1452,
- // 1451,
- // 1450,
- // 1449,
- // 1448,
- // 1447,
- // 1446,
- // 1445,
- // 1444,
- // 1443,
- // 1442,
- // 1441,
+ 1453,
+ 1452,
+ 1451,
+ 1450,
+ 1449,
+ 1448,
+ 1447,
+ 1446,
+ 1445,
+ 1444,
+ 1443,
+ 1442,
+ 1441,
1440,
1439,
- // 1438,
+ 1438,
1437,
1436,
1435,
1434,
1433,
- // 1432,
- // 1431,
- // 1430,
- // 1429,
- // 1428,
- // 1427,
- // 1426,
+ 1432,
+ 1431,
+ 1430,
+ 1429,
+ 1428,
+ 1427,
+ 1426,
1425,
1424,
- // 1423,
- // 1422,
- // 1421,
- // 1420,
- // 1419,
+ 1423,
+ 1422,
+ 1421,
+ 1420,
+ 1419,
1418,
// 1417,
- // 1416,
+ 1416,
1415,
- // 1414,
+ 1414,
// 1413,
1412,
1411,
- // 1410,
+ 1410,
1409,
- // 1408,
- // 1407,
- // 1406,
- // 1405,
+ 1408,
+ 1407,
+ 1406,
+ 1405,
1404,
1403,
- // 1402,
+ 1402,
1401,
- // 1400,
- // 1399,
+ 1400,
+ 1399,
1398,
1397,
- // 1396,
+ 1396,
// 1395,
1394,
1393,
1392,
- // 1391,
+ 1391,
1390,
// 1389,
- // 1388,
- // 1387,
+ 1388,
+ 1387,
1386,
1385,
1384,
@@ -1118,7 +1104,7 @@ static const int included_patches[] = {
1379,
1378,
1377,
- // 1376,
+ 1376,
1375,
1374,
1373,
@@ -1130,12 +1116,12 @@ static const int included_patches[] = {
1367,
1366,
1365,
- // 1364,
+ 1364,
1363,
1362,
1361,
1360,
- // 1359,
+ 1359,
1358,
1357,
1356,
@@ -1143,38 +1129,38 @@ static const int included_patches[] = {
1354,
1353,
1352,
- // 1351,
+ 1351,
1350,
1349,
1348,
1347,
1346,
1345,
- // 1344,
- // 1343,
+ 1344,
+ 1343,
1342,
- // 1341,
- // 1340,
+ 1341,
+ 1340,
1339,
1338,
- // 1337,
+ 1337,
1336,
- // 1335,
+ 1335,
1334,
- // 1333,
- // 1332,
+ 1333,
+ 1332,
1331,
1330,
- // 1329,
- // 1328,
+ 1329,
+ 1328,
1327,
- // 1326,
+ 1326,
1325,
1324,
1323,
1322,
- // 1321,
- // 1320,
+ 1321,
+ 1320,
1319,
1318,
1317,
@@ -1214,11 +1200,11 @@ static const int included_patches[] = {
1283,
1282,
1281,
- // 1280,
+ 1280,
1279,
- // 1278,
+ 1278,
1277,
- // 1276,
+ 1276,
1275,
1274,
1273,
@@ -1227,9 +1213,9 @@ static const int included_patches[] = {
1270,
1269,
1268,
- // 1267,
+ 1267,
1266,
- // 1265,
+ 1265,
1264,
1263,
1262,
@@ -1268,7 +1254,7 @@ static const int included_patches[] = {
1229,
1228,
1227,
- // 1226,
+ 1226,
1225,
// 1224,
1223,
@@ -1391,7 +1377,7 @@ static const int included_patches[] = {
// 1106,
1105,
1104,
- // 1103,
+ 1103,
1102,
1101,
1100,
@@ -1404,7 +1390,7 @@ static const int included_patches[] = {
1093,
1092,
1091,
- // 1090,
+ 1090,
1089,
1088,
1087,
@@ -1459,14 +1445,14 @@ static const int included_patches[] = {
1038,
1037,
1036,
- // 1035,
+ 1035,
1034,
1033,
1032,
1031,
1030,
1029,
- // 1028,
+ 1028,
1027,
1026,
1025,
@@ -1475,7 +1461,7 @@ static const int included_patches[] = {
1022,
1021,
1020,
- // 1019,
+ 1019,
1018,
1017,
1016,
@@ -1524,7 +1510,7 @@ static const int included_patches[] = {
973,
972,
971,
- // 970,
+ 970,
// 969,
968,
967,
@@ -1554,7 +1540,7 @@ static const int included_patches[] = {
943,
942,
941,
- // 940,
+ 940,
939,
938,
937,
@@ -1566,7 +1552,7 @@ static const int included_patches[] = {
931,
930,
929,
- // 928,
+ 928,
927,
926,
925,
@@ -1576,7 +1562,7 @@ static const int included_patches[] = {
921,
920,
919,
- // 918,
+ 918,
917,
916,
915,
@@ -1585,10 +1571,10 @@ static const int included_patches[] = {
912,
911,
910,
- // 909,
+ 909,
908,
907,
- // 906,
+ 906,
905,
904,
903,
@@ -1599,12 +1585,12 @@ static const int included_patches[] = {
898,
897,
896,
- // 895,
+ 895,
// 894,
893,
892,
891,
- // 890,
+ 890,
889,
888,
887,
@@ -1614,8 +1600,8 @@ static const int included_patches[] = {
883,
882,
881,
- // 880,
- // 879,
+ 880,
+ 879,
878,
877,
// 876,
@@ -1624,7 +1610,7 @@ static const int included_patches[] = {
873,
872,
871,
- // 870,
+ 870,
869,
868,
867,
@@ -1649,7 +1635,7 @@ static const int included_patches[] = {
848,
847,
846,
- // 845,
+ 845,
844,
843,
842,
@@ -1670,7 +1656,7 @@ static const int included_patches[] = {
827,
826,
825,
- // 824,
+ 824,
823,
822,
821,
@@ -1733,7 +1719,7 @@ static const int included_patches[] = {
764,
763,
762,
- // 761,
+ 761,
760,
759,
758,
@@ -1787,11 +1773,11 @@ static const int included_patches[] = {
// 710,
709,
708,
- // 707,
+ 707,
706,
705,
704,
- // 703,
+ 703,
702,
701,
700,
@@ -1800,26 +1786,26 @@ static const int included_patches[] = {
697,
696,
695,
- // 694,
+ 694,
693,
692,
- // 691,
- // 690,
- // 689,
- // 688,
+ 691,
+ 690,
+ 689,
+ 688,
687,
686,
685,
- // 684,
+ 684,
683,
- // 682,
- // 681,
+ 682,
+ 681,
680,
679,
678,
677,
- // 676,
- // 675,
+ 676,
+ 675,
674,
673,
672,
@@ -1827,11 +1813,11 @@ static const int included_patches[] = {
670,
669,
668,
- // 667,
+ 667,
666,
- // 665,
+ 665,
664,
- // 663,
+ 663,
662,
661,
660,
@@ -1839,8 +1825,8 @@ static const int included_patches[] = {
658,
657,
656,
- // 655,
- // 654,
+ 655,
+ 654,
653,
652,
651,
@@ -1851,16 +1837,16 @@ static const int included_patches[] = {
646,
645,
644,
- // 643,
+ 643,
642,
641,
640,
- // 639,
- // 638,
+ 639,
+ 638,
637,
// 636,
635,
- // 634,
+ 634,
633,
632,
631,
@@ -1882,7 +1868,7 @@ static const int included_patches[] = {
615,
614,
613,
- // 612,
+ 612,
611,
610,
609,
@@ -1893,7 +1879,7 @@ static const int included_patches[] = {
604,
603,
602,
- // 601,
+ 601,
600,
599,
598,
@@ -1912,10 +1898,10 @@ static const int included_patches[] = {
585,
584,
583,
- // 582,
+ 582,
581,
580,
- // 579,
+ 579,
578,
577,
576,
@@ -2127,7 +2113,7 @@ static const int included_patches[] = {
370,
369,
368,
- // 367,
+ 367,
366,
365,
364,
@@ -2182,7 +2168,7 @@ static const int included_patches[] = {
315,
314,
313,
- // 312,
+ 312,
311,
310,
309,
@@ -2190,7 +2176,7 @@ static const int included_patches[] = {
307,
306,
305,
- // 304,
+ 304,
303,
302,
301,
@@ -2422,7 +2408,7 @@ static const int included_patches[] = {
75,
74,
73,
- // 72,
+ 72,
71,
70,
69,
@@ -2432,7 +2418,7 @@ static const int included_patches[] = {
// 65,
64,
63,
- // 62,
+ 62,
61,
60,
59,
@@ -2444,8 +2430,8 @@ static const int included_patches[] = {
53,
52,
51,
- // 50,
- // 49,
+ 50,
+ 49,
48,
47,
46,
@@ -2507,14 +2493,13 @@ bool has_nvim_version(const char *const version_str)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
const char *p = version_str;
- int major = 0;
int minor = 0;
int patch = 0;
if (!ascii_isdigit(*p)) {
return false;
}
- major = atoi(p);
+ int major = atoi(p);
p = strchr(p, '.'); // Find the next dot.
if (p) {
@@ -2571,6 +2556,11 @@ Dictionary version_dict(void)
PUT(d, "major", INTEGER_OBJ(NVIM_VERSION_MAJOR));
PUT(d, "minor", INTEGER_OBJ(NVIM_VERSION_MINOR));
PUT(d, "patch", INTEGER_OBJ(NVIM_VERSION_PATCH));
+#ifndef NVIM_VERSION_BUILD
+ PUT(d, "build", NIL);
+#else
+ PUT(d, "build", CSTR_AS_OBJ(NVIM_VERSION_BUILD));
+#endif
PUT(d, "prerelease", BOOLEAN_OBJ(NVIM_VERSION_PRERELEASE[0] != '\0'));
PUT(d, "api_level", INTEGER_OBJ(NVIM_API_LEVEL));
PUT(d, "api_compatible", INTEGER_OBJ(NVIM_API_LEVEL_COMPAT));
@@ -2619,21 +2609,6 @@ static void version_msg(char *s)
version_msg_wrap(s, false);
}
-/// List all features.
-/// This does not use list_in_columns (as in Vim), because there are only a
-/// few, and we do not start at a new line.
-static void list_features(void)
-{
- version_msg(_("\n\nFeatures: "));
- for (int i = 0; features[i] != NULL; i++) {
- version_msg(features[i]);
- if (features[i + 1] != NULL) {
- version_msg(" ");
- }
- }
- version_msg("\nSee \":help feature-compile\"\n\n");
-}
-
/// List string items nicely aligned in columns.
/// When "size" is < 0 then the last entry is marked with NULL.
/// The entry with index "current" is inclosed in [].
@@ -2643,8 +2618,7 @@ void list_in_columns(char **items, int size, int current)
int width = 0;
// Find the length of the longest item, use that + 1 as the column width.
- int i;
- for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) {
+ for (int i = 0; size < 0 ? items[i] != NULL : i < size; i++) {
int l = vim_strsize(items[i]) + (i == current ? 2 : 0);
if (l > width) {
@@ -2656,7 +2630,7 @@ void list_in_columns(char **items, int size, int current)
if (Columns < width) {
// Not enough screen columns - show one per line
- for (i = 0; i < item_count; i++) {
+ for (int i = 0; i < item_count; i++) {
version_msg_wrap(items[i], i == current);
if (msg_col > 0 && i < item_count - 1) {
msg_putchar('\n');
@@ -2672,7 +2646,7 @@ void list_in_columns(char **items, int size, int current)
int cur_row = 1;
// "i" counts columns then rows. "idx" counts rows then columns.
- for (i = 0; !got_int && i < nrow * ncol; i++) {
+ for (int i = 0; !got_int && i < nrow * ncol; i++) {
int idx = (i / ncol) + (i % ncol) * nrow;
if (idx < item_count) {
int last_col = (i + 1) % ncol == 0;
@@ -2712,59 +2686,48 @@ void list_lua_version(void)
Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err);
assert(!ERROR_SET(&err));
assert(ret.type == kObjectTypeString);
- msg(ret.data.string.data);
+ msg(ret.data.string.data, 0);
api_free_object(ret);
}
void list_version(void)
{
- msg(longVersion);
- msg(version_buildtype);
+ msg(longVersion, 0);
+ msg(version_buildtype, 0);
list_lua_version();
+
+ if (p_verbose > 0) {
#ifndef NDEBUG
- msg(version_cflags);
+ msg(version_cflags, 0);
#endif
+ version_msg("\n\n");
-#ifdef HAVE_PATHDEF
-
- if ((*compiled_user != NUL) || (*compiled_sys != NUL)) {
- msg_puts(_("\nCompiled "));
+#ifdef SYS_VIMRC_FILE
+ version_msg(_(" system vimrc file: \""));
+ version_msg(SYS_VIMRC_FILE);
+ version_msg("\"\n");
+#endif
- if (*compiled_user != NUL) {
- msg_puts(_("by "));
- msg_puts((const char *)compiled_user);
+#ifdef HAVE_PATHDEF
+ if (*default_vim_dir != NUL) {
+ version_msg(_(" fall-back for $VIM: \""));
+ version_msg(default_vim_dir);
+ version_msg("\"\n");
}
- if (*compiled_sys != NUL) {
- msg_puts("@");
- msg_puts((const char *)compiled_sys);
+ if (*default_vimruntime_dir != NUL) {
+ version_msg(_(" f-b for $VIMRUNTIME: \""));
+ version_msg(default_vimruntime_dir);
+ version_msg("\"\n");
}
+#endif
}
-#endif // ifdef HAVE_PATHDEF
-
- list_features();
-
-#ifdef SYS_VIMRC_FILE
- version_msg(_(" system vimrc file: \""));
- version_msg(SYS_VIMRC_FILE);
- version_msg("\"\n");
-#endif // ifdef SYS_VIMRC_FILE
-#ifdef HAVE_PATHDEF
-
- if (*default_vim_dir != NUL) {
- version_msg(_(" fall-back for $VIM: \""));
- version_msg(default_vim_dir);
- version_msg("\"\n");
- }
-
- if (*default_vimruntime_dir != NUL) {
- version_msg(_(" f-b for $VIMRUNTIME: \""));
- version_msg(default_vimruntime_dir);
- version_msg("\"\n");
- }
-#endif // ifdef HAVE_PATHDEF
- version_msg("\nRun :checkhealth for more info");
+ version_msg(p_verbose > 0
+ ? "\nRun :checkhealth for more info"
+ : (starting
+ ? "\nRun \"nvim -V1 -v\" for more info"
+ : "\nRun \":verbose version\" for more info"));
}
/// Show the intro message when not editing a file.
@@ -2806,7 +2769,7 @@ void intro_message(int colon)
size_t lines_size = ARRAY_SIZE(lines);
assert(lines_size <= LONG_MAX);
- long blanklines = Rows - ((long)lines_size - 1L);
+ int blanklines = Rows - ((int)lines_size - 1);
// Don't overwrite a statusline. Depends on 'cmdheight'.
if (p_ls > 1) {
@@ -2823,7 +2786,7 @@ void intro_message(int colon)
sponsor = ((sponsor & 2) == 0) - ((sponsor & 4) == 0);
// start displaying the message lines after half of the blank lines
- long row = blanklines / 2;
+ int row = blanklines / 2;
if (((row >= 2) && (Columns >= 50)) || colon) {
for (int i = 0; i < (int)ARRAY_SIZE(lines); i++) {
@@ -2869,22 +2832,14 @@ void intro_message(int colon)
}
}
}
-
- // Make the wait-return message appear just below the text.
- if (colon) {
- assert(row <= INT_MAX);
- msg_row = (int)row;
- }
}
-static void do_intro_line(long row, char *mesg, int attr)
+static void do_intro_line(int row, char *mesg, int attr)
{
- char *p;
int l;
- int clen;
// Center the message horizontally.
- long col = vim_strsize(mesg);
+ int col = vim_strsize(mesg);
col = (Columns - col) / 2;
@@ -2892,21 +2847,18 @@ static void do_intro_line(long row, char *mesg, int attr)
col = 0;
}
+ grid_line_start(&default_grid, row);
// Split up in parts to highlight <> items differently.
- for (p = mesg; *p != NUL; p += l) {
- clen = 0;
-
+ for (char *p = mesg; *p != NUL; p += l) {
for (l = 0;
p[l] != NUL && (l == 0 || (p[l] != '<' && p[l - 1] != '>'));
l++) {
- clen += ptr2cells(p + l);
l += utfc_ptr2len(p + l) - 1;
}
assert(row <= INT_MAX && col <= INT_MAX);
- grid_puts_len(&default_grid, p, l, (int)row, (int)col,
- *p == '<' ? HL_ATTR(HLF_8) : attr);
- col += clen;
+ col += grid_line_puts(col, p, l, *p == '<' ? HL_ATTR(HLF_8) : attr);
}
+ grid_line_flush();
}
/// ":intro": clear screen, display intro screen and wait for return.
diff --git a/src/nvim/version.h b/src/nvim/version.h
index 484350edee..94219aaddc 100644
--- a/src/nvim/version.h
+++ b/src/nvim/version.h
@@ -1,12 +1,14 @@
-#ifndef NVIM_VERSION_H
-#define NVIM_VERSION_H
+#pragma once
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/macros.h"
+#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
// defined in version.c
extern char *Version;
extern char *longVersion;
+#ifndef NDEBUG
+extern char *version_cflags;
+#endif
//
// Vim version number, name, etc. Patchlevel is defined in version.c.
@@ -29,4 +31,3 @@ extern char *longVersion;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "version.h.generated.h"
#endif
-#endif // NVIM_VERSION_H
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
deleted file mode 100644
index 3c765f8eb2..0000000000
--- a/src/nvim/vim.h
+++ /dev/null
@@ -1,274 +0,0 @@
-#ifndef NVIM_VIM_H
-#define NVIM_VIM_H
-
-#include "nvim/pos.h"
-#include "nvim/types.h"
-
-// Some defines from the old feature.h
-#define SESSION_FILE "Session.vim"
-#define MAX_MSG_HIST_LEN 200
-#define SYS_OPTWIN_FILE "$VIMRUNTIME/optwin.vim"
-#define RUNTIME_DIRNAME "runtime"
-
-#include "auto/config.h"
-#define HAVE_PATHDEF
-
-// Check if configure correctly managed to find sizeof(int). If this failed,
-// it becomes zero. This is likely a problem of not being able to run the
-// test program. Other items from configure may also be wrong then!
-#if (SIZEOF_INT == 0)
-# error Configure did not run properly.
-#endif
-
-#include "nvim/os/os_defs.h" // bring lots of system header files
-
-/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
-enum { NUMBUFLEN = 65, };
-
-#define MAX_TYPENR 65535
-
-#define ROOT_UID 0
-
-#include "nvim/gettext.h"
-#include "nvim/keycodes.h"
-#include "nvim/macros.h"
-
-// special attribute addition: Put message in history
-#define MSG_HIST 0x1000
-
-// Values for State
-//
-// The lower bits up to 0x80 are used to distinguish normal/visual/op_pending
-// /cmdline/insert/replace/terminal mode. This is used for mapping. If none
-// of these bits are set, no mapping is done. See the comment above do_map().
-// The upper bits are used to distinguish between other states and variants of
-// the base modes.
-
-#define MODE_NORMAL 0x01 // Normal mode, command expected
-#define MODE_VISUAL 0x02 // Visual mode - use get_real_state()
-#define MODE_OP_PENDING 0x04 // Normal mode, operator is pending - use
- // get_real_state()
-#define MODE_CMDLINE 0x08 // Editing the command line
-#define MODE_INSERT 0x10 // Insert mode, also for Replace mode
-#define MODE_LANGMAP 0x20 // Language mapping, can be combined with
- // MODE_INSERT and MODE_CMDLINE
-#define MODE_SELECT 0x40 // Select mode, use get_real_state()
-#define MODE_TERMINAL 0x80 // Terminal mode
-
-#define MAP_ALL_MODES 0xff // all mode bits used for mapping
-
-#define REPLACE_FLAG 0x100 // Replace mode flag
-#define MODE_REPLACE (REPLACE_FLAG | MODE_INSERT)
-#define VREPLACE_FLAG 0x200 // Virtual-replace mode flag
-#define MODE_VREPLACE (REPLACE_FLAG | VREPLACE_FLAG | MODE_INSERT)
-#define MODE_LREPLACE (REPLACE_FLAG | MODE_LANGMAP)
-
-#define MODE_NORMAL_BUSY (0x1000 | MODE_NORMAL) // Normal mode, busy with a command
-#define MODE_HITRETURN (0x2000 | MODE_NORMAL) // waiting for return or command
-#define MODE_ASKMORE 0x3000 // Asking if you want --more--
-#define MODE_SETWSIZE 0x4000 // window size has changed
-#define MODE_EXTERNCMD 0x5000 // executing an external command
-#define MODE_SHOWMATCH (0x6000 | MODE_INSERT) // show matching paren
-#define MODE_CONFIRM 0x7000 // ":confirm" prompt
-
-/// Directions.
-typedef enum {
- kDirectionNotSet = 0,
- FORWARD = 1,
- BACKWARD = (-1),
- FORWARD_FILE = 3,
- BACKWARD_FILE = (-3),
-} Direction;
-
-// return values for functions
-#if !(defined(OK) && (OK == 1))
-// OK already defined to 1 in MacOS X curses, skip this
-# define OK 1
-#endif
-#define FAIL 0
-#define NOTDONE 2 // not OK or FAIL but skipped
-
-// Type values for type().
-#define VAR_TYPE_NUMBER 0
-#define VAR_TYPE_STRING 1
-#define VAR_TYPE_FUNC 2
-#define VAR_TYPE_LIST 3
-#define VAR_TYPE_DICT 4
-#define VAR_TYPE_FLOAT 5
-#define VAR_TYPE_BOOL 6
-#define VAR_TYPE_SPECIAL 7
-#define VAR_TYPE_BLOB 10
-
-// values for xp_context when doing command line completion
-
-enum {
- EXPAND_UNSUCCESSFUL = -2,
- EXPAND_OK = -1,
- EXPAND_NOTHING = 0,
- EXPAND_COMMANDS,
- EXPAND_FILES,
- EXPAND_DIRECTORIES,
- EXPAND_SETTINGS,
- EXPAND_BOOL_SETTINGS,
- EXPAND_TAGS,
- EXPAND_OLD_SETTING,
- EXPAND_HELP,
- EXPAND_BUFFERS,
- EXPAND_EVENTS,
- EXPAND_MENUS,
- EXPAND_SYNTAX,
- EXPAND_HIGHLIGHT,
- EXPAND_AUGROUP,
- EXPAND_USER_VARS,
- EXPAND_MAPPINGS,
- EXPAND_TAGS_LISTFILES,
- EXPAND_FUNCTIONS,
- EXPAND_USER_FUNC,
- EXPAND_EXPRESSION,
- EXPAND_MENUNAMES,
- EXPAND_USER_COMMANDS,
- EXPAND_USER_CMD_FLAGS,
- EXPAND_USER_NARGS,
- EXPAND_USER_COMPLETE,
- EXPAND_ENV_VARS,
- EXPAND_LANGUAGE,
- EXPAND_COLORS,
- EXPAND_COMPILER,
- EXPAND_USER_DEFINED,
- EXPAND_USER_LIST,
- EXPAND_USER_LUA,
- EXPAND_SHELLCMD,
- EXPAND_SIGN,
- EXPAND_PROFILE,
- EXPAND_BEHAVE,
- EXPAND_FILETYPE,
- EXPAND_FILES_IN_PATH,
- EXPAND_OWNSYNTAX,
- EXPAND_LOCALES,
- EXPAND_HISTORY,
- EXPAND_USER,
- EXPAND_SYNTIME,
- EXPAND_USER_ADDR_TYPE,
- EXPAND_PACKADD,
- EXPAND_MESSAGES,
- EXPAND_MAPCLEAR,
- EXPAND_ARGLIST,
- EXPAND_DIFF_BUFFERS,
- EXPAND_BREAKPOINT,
- EXPAND_SCRIPTNAMES,
- EXPAND_RUNTIME,
- EXPAND_CHECKHEALTH,
- EXPAND_LUA,
-};
-
-// Minimal size for block 0 of a swap file.
-// NOTE: This depends on size of struct block0! It's not done with a sizeof(),
-// because struct block0 is defined in memline.c (Sorry).
-// The maximal block size is arbitrary.
-
-#define MIN_SWAP_PAGE_SIZE 1048
-#define MAX_SWAP_PAGE_SIZE 50000
-
-#define STATUS_HEIGHT 1 // height of a status line under a window
-#define QF_WINHEIGHT 10 // default height for quickfix window
-
-// Buffer sizes
-
-#ifndef CMDBUFFSIZE
-# define CMDBUFFSIZE 256 // size of the command processing buffer
-#endif
-
-#define LSIZE 512 // max. size of a line in the tags file
-
-#define DIALOG_MSG_SIZE 1000 // buffer size for dialog_msg()
-
-enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext()
-
-// Maximum length of key sequence to be mapped.
-// Must be able to hold an Amiga resize report.
-
-#define MAXMAPLEN 50
-
-// Size in bytes of the hash used in the undo file.
-#define UNDO_HASH_SIZE 32
-
-#define CLEAR_FIELD(field) memset(&(field), 0, sizeof(field))
-#define CLEAR_POINTER(ptr) memset((ptr), 0, sizeof(*(ptr)))
-
-// defines to avoid typecasts from (char_u *) to (char *) and back
-// (vim_strchr() is now in strings.c)
-
-#ifndef HAVE_STRNLEN
-# define strnlen xstrnlen // Older versions of SunOS may not have strnlen
-#endif
-
-#define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
-#ifdef HAVE_STRCASECMP
-# define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s))
-#else
-# ifdef HAVE_STRICMP
-# define STRICMP(d, s) stricmp((char *)(d), (char *)(s))
-# else
-# define STRICMP(d, s) vim_stricmp((char *)(d), (char *)(s))
-# endif
-#endif
-
-// Like strcpy() but allows overlapped source and destination.
-#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1)
-
-#ifdef HAVE_STRNCASECMP
-# define STRNICMP(d, s, n) strncasecmp((char *)(d), (char *)(s), (size_t)(n))
-#else
-# ifdef HAVE_STRNICMP
-# define STRNICMP(d, s, n) strnicmp((char *)(d), (char *)(s), (size_t)(n))
-# else
-# define STRNICMP(d, s, n) vim_strnicmp((char *)(d), (char *)(s), (size_t)(n))
-# endif
-#endif
-
-#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
-
-// Character used as separated in autoload function/variable names.
-#define AUTOLOAD_CHAR '#'
-
-#include "nvim/message.h"
-
-// Prefer using semsg(), because perror() may send the output to the wrong
-// destination and mess up the screen.
-#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno))
-
-#include "nvim/path.h"
-
-// Enums need a typecast to be used as array index.
-#define HL_ATTR(n) hl_attr_active[(int)(n)]
-
-/// Maximum number of bytes in a multi-byte character. It can be one 32-bit
-/// character of up to 6 bytes, or one 16-bit character of up to three bytes
-/// plus six following composing characters of three bytes each.
-#define MB_MAXBYTES 21
-
-#ifndef MSWIN
-/// Headless (no UI) error message handler.
-# define os_errmsg(str) fprintf(stderr, "%s", (str))
-/// Headless (no UI) message handler.
-# define os_msg(str) printf("%s", (str))
-#endif
-
-#include "nvim/buffer_defs.h" // buffer and windows
-#include "nvim/ex_cmds_defs.h" // Ex command defines
-#include "nvim/globals.h" // global variables and messages
-
-// Lowest number used for window ID. Cannot have this many windows per tab.
-#define LOWEST_WIN_ID 1000
-
-// BSD is supposed to cover FreeBSD and similar systems.
-#if (defined(BSD) || defined(__FreeBSD_kernel__)) \
- && (defined(S_ISCHR) || defined(S_IFCHR))
-# define OPEN_CHR_FILES
-#endif
-
-// Replacement for nchar used by nv_replace().
-#define REPLACE_CR_NCHAR (-1)
-#define REPLACE_NL_NCHAR (-2)
-
-#endif // NVIM_VIM_H
diff --git a/src/nvim/vim_defs.h b/src/nvim/vim_defs.h
new file mode 100644
index 0000000000..faf79b1c79
--- /dev/null
+++ b/src/nvim/vim_defs.h
@@ -0,0 +1,41 @@
+#pragma once
+
+// Some defines from the old feature.h
+#define SESSION_FILE "Session.vim"
+#define MAX_MSG_HIST_LEN 200
+#define SYS_OPTWIN_FILE "$VIMRUNTIME/optwin.vim"
+#define RUNTIME_DIRNAME "runtime"
+
+#include "auto/config.h"
+
+// Check if configure correctly managed to find sizeof(int). If this failed,
+// it becomes zero. This is likely a problem of not being able to run the
+// test program. Other items from configure may also be wrong then!
+#if (SIZEOF_INT == 0)
+# error Configure did not run properly.
+#endif
+
+// bring lots of system header files
+#include "nvim/os/os_defs.h" // IWYU pragma: keep
+
+/// length of a buffer to store a number in ASCII (64 bits binary + NUL)
+enum { NUMBUFLEN = 65, };
+
+#define MAX_TYPENR 65535
+
+/// Directions.
+typedef enum {
+ kDirectionNotSet = 0,
+ FORWARD = 1,
+ BACKWARD = -1,
+ FORWARD_FILE = 3,
+ BACKWARD_FILE = -3,
+} Direction;
+
+// return values for functions
+#if !(defined(OK) && (OK == 1))
+// OK already defined to 1 in MacOS X curses, skip this
+# define OK 1
+#endif
+#define FAIL 0
+#define NOTDONE 2 // not OK or FAIL but skipped
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 53224f2ee9..8b637fbb9b 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1,10 +1,7 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-/// VimL expression parser
+/// Vimscript expression parser
// Planned incompatibilities (to be included into vim_diff.txt when this parser
-// will be an actual part of VimL evaluation process):
+// will be an actual part of Vimscript evaluation process):
//
// 1. Expressions are first fully parsed and only then executed. This means
// that while ":echo [system('touch abc')" will create file "abc" in Vim and
@@ -58,17 +55,17 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/ascii.h"
-#include "nvim/assert.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/assert_defs.h"
#include "nvim/charset.h"
-#include "nvim/eval/typval.h"
+#include "nvim/eval.h"
+#include "nvim/func_attr.h"
#include "nvim/gettext.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
-#include "nvim/types.h"
-#include "nvim/vim.h"
+#include "nvim/types_defs.h"
#include "nvim/viml/parser/expressions.h"
#include "nvim/viml/parser/parser.h"
@@ -89,7 +86,7 @@ typedef enum {
/// Parse type: what is being parsed currently
typedef enum {
- /// Parsing regular VimL expression
+ /// Parsing regular Vimscript expression
kEPTExpr = 0,
/// Parsing lambda arguments
///
@@ -171,7 +168,7 @@ static inline float_T scale_number(const float_T num, const uint8_t base,
return ret;
}
-/// Get next token for the VimL expression input
+/// Get next token for the Vimscript expression input
///
/// @param pstate Parser state.
/// @param[in] flags Flags, @see LexExprFlags.
@@ -367,7 +364,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
// uses recorded position to scale number down when processing exponent.
float_T significand_part = 0;
uvarnumber_T exp_part = 0;
- const size_t frac_size = (size_t)(frac_end - frac_start);
+ const size_t frac_size = frac_end - frac_start;
for (size_t i = 0; i < frac_end; i++) {
if (i == frac_start - 1) {
continue;
@@ -376,7 +373,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
}
if (exp_start) {
vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part,
- (int)(ret.len - exp_start), false);
+ (int)(ret.len - exp_start), false, NULL);
}
if (exp_negative) {
exp_part += frac_size;
@@ -394,7 +391,7 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags)
int len;
int prep;
vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL,
- &ret.data.num.val.integer, (int)pline.size, false);
+ &ret.data.num.val.integer, (int)pline.size, false, NULL);
ret.len = (size_t)len;
const uint8_t bases[] = {
[0] = 10,
@@ -938,7 +935,6 @@ static const char *intchar2str(const int ch)
}
#ifdef UNIT_TESTING
-# include <stdio.h>
REAL_FATTR_UNUSED
static inline void viml_pexpr_debug_print_ast_node(const ExprASTNode *const *const eastnode_p,
@@ -1168,7 +1164,7 @@ static struct {
// represented as "list(comma(a, comma(b, comma(c, d))))" then if it is
// "list(comma(comma(comma(a, b), c), d))" in which case you will need to
// traverse all three comma() structures. And with comma operator (including
- // actual comma operator from C which is not present in VimL) nobody cares
+ // actual comma operator from C which is not present in Vimscript) nobody cares
// about associativity, only about order of execution.
[kExprNodeComma] = { kEOpLvlComma, kEOpAssRight },
@@ -1273,8 +1269,8 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ExprASTStack
const ExprOpAssociativity bop_node_ass = (
(bop_node->type == kExprNodeCall
|| bop_node->type == kExprNodeSubscript)
- ? kEOpAssLeft
- : node_ass(*bop_node));
+ ? kEOpAssLeft
+ : node_ass(*bop_node));
#endif
do {
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
@@ -1915,7 +1911,7 @@ static const uint8_t base_to_prefix_length[] = {
[16] = 2,
};
-/// Parse one VimL expression
+/// Parse one Vimscript expression
///
/// @param pstate Parser state.
/// @param[in] flags Additional flags, see ExprParserFlags
@@ -2207,8 +2203,8 @@ viml_pexpr_parse_process_token:
cur_node->data.opt.ident_len = 0;
cur_node->data.opt.scope = (
cur_token.len == 3
- ? (ExprOptScope)pline.data[cur_token.start.col + 1]
- : kExprOptScopeUnspecified);
+ ? (ExprOptScope)pline.data[cur_token.start.col + 1]
+ : kExprOptScopeUnspecified);
} else {
cur_node->data.opt.ident = cur_token.data.opt.name;
cur_node->data.opt.ident_len = cur_token.data.opt.len;
@@ -2866,7 +2862,7 @@ viml_pexpr_parse_no_paren_closing_error: {}
case kENodeOperator:
if (prev_token.type == kExprLexSpacing) {
// For some reason "function (args)" is a function call, but
- // "(funcref) (args)" is not. AFAIR this somehow involves
+ // "(funcref) (args)" is not. As far as I remember this somehow involves
// compatibility and Bram was commenting that this is
// intentionally inconsistent and he is not very happy with the
// situation himself.
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index 6fe6a784a0..94287ea4e1 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -1,13 +1,11 @@
-#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H
-#define NVIM_VIML_PARSER_EXPRESSIONS_H
+#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/viml/parser/parser.h"
struct expr_ast_node;
@@ -388,5 +386,3 @@ extern const char *const expr_asgn_type_tab[];
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "viml/parser/expressions.h.generated.h"
#endif
-
-#endif // NVIM_VIML_PARSER_EXPRESSIONS_H
diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c
index 1547feba90..b854aedca6 100644
--- a/src/nvim/viml/parser/parser.c
+++ b/src/nvim/viml/parser/parser.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include "nvim/viml/parser/parser.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h
index f387301c2d..cd5a493643 100644
--- a/src/nvim/viml/parser/parser.h
+++ b/src/nvim/viml/parser/parser.h
@@ -1,5 +1,4 @@
-#ifndef NVIM_VIML_PARSER_PARSER_H
-#define NVIM_VIML_PARSER_PARSER_H
+#pragma once
#include <assert.h>
#include <stdbool.h>
@@ -227,5 +226,3 @@ static inline void viml_parser_highlight(ParserState *const pstate, const Parser
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "viml/parser/parser.h.generated.h"
#endif
-
-#endif // NVIM_VIML_PARSER_PARSER_H
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 6b40ccdb84..bcf245ef93 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -1,12 +1,8 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
-#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -15,7 +11,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/arglist.h"
-#include "nvim/ascii.h"
+#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -27,7 +23,6 @@
#include "nvim/edit.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds.h"
@@ -38,6 +33,7 @@
#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/func_attr.h"
#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext.h"
@@ -45,40 +41,39 @@
#include "nvim/grid.h"
#include "nvim/hashtab.h"
#include "nvim/keycodes.h"
-#include "nvim/macros.h"
+#include "nvim/macros_defs.h"
#include "nvim/main.h"
-#include "nvim/map.h"
+#include "nvim/map_defs.h"
#include "nvim/mapping.h" // IWYU pragma: keep (langmap_adjust_mb)
#include "nvim/mark.h"
#include "nvim/match.h"
#include "nvim/mbyte.h"
-#include "nvim/memline_defs.h"
#include "nvim/memory.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/option.h"
-#include "nvim/optionstr.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
+#include "nvim/option_defs.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fs.h"
#include "nvim/path.h"
#include "nvim/plines.h"
-#include "nvim/pos.h"
+#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
-#include "nvim/screen.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/terminal.h"
-#include "nvim/types.h"
+#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/undo.h"
-#include "nvim/vim.h"
+#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "nvim/winfloat.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.c.generated.h"
@@ -97,7 +92,7 @@ typedef enum {
WEE_TRIGGER_LEAVE_AUTOCMDS = 0x10,
} wee_flags_T;
-static char e_cannot_split_window_when_closing_buffer[]
+static const char e_cannot_split_window_when_closing_buffer[]
= N_("E1159: Cannot split a window when closing the buffer");
static char *m_onlyone = N_("Already only one window");
@@ -140,15 +135,41 @@ win_T *prevwin_curwin(void)
return is_in_cmdwin() && prevwin != NULL ? prevwin : curwin;
}
+/// If the 'switchbuf' option contains "useopen" or "usetab", then try to jump
+/// to a window containing "buf".
+/// Returns the pointer to the window that was jumped to or NULL.
+win_T *swbuf_goto_win_with_buf(buf_T *buf)
+{
+ win_T *wp = NULL;
+
+ if (buf == NULL) {
+ return wp;
+ }
+
+ // If 'switchbuf' contains "useopen": jump to first window in the current
+ // tab page containing "buf" if one exists.
+ if (swb_flags & SWB_USEOPEN) {
+ wp = buf_jump_open_win(buf);
+ }
+
+ // If 'switchbuf' contains "usetab": jump to first window in any tab page
+ // containing "buf" if one exists.
+ if (wp == NULL && (swb_flags & SWB_USETAB)) {
+ wp = buf_jump_open_tab(buf);
+ }
+
+ return wp;
+}
+
/// all CTRL-W window commands are handled here, called from normal_cmd().
///
/// @param xchar extra char from ":wincmd gx" or NUL
-void do_window(int nchar, long Prenum, int xchar)
+void do_window(int nchar, int Prenum, int xchar)
{
int type = FIND_DEFINE;
char cbuf[40];
- long Prenum1 = Prenum == 0 ? 1 : Prenum;
+ int Prenum1 = Prenum == 0 ? 1 : Prenum;
#define CHECK_CMDWIN \
do { \
@@ -170,7 +191,7 @@ void do_window(int nchar, long Prenum, int xchar)
if (bt_quickfix(curbuf)) {
goto newwindow;
}
- (void)win_split((int)Prenum, 0);
+ (void)win_split(Prenum, 0);
break;
// split current window in two parts, vertically
@@ -183,7 +204,7 @@ void do_window(int nchar, long Prenum, int xchar)
if (bt_quickfix(curbuf)) {
goto newwindow;
}
- (void)win_split((int)Prenum, WSP_VERT);
+ (void)win_split(Prenum, WSP_VERT);
break;
// split current window and edit alternate file
@@ -192,7 +213,7 @@ void do_window(int nchar, long Prenum, int xchar)
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : (int)Prenum) == NULL) {
+ if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) {
if (Prenum == 0) {
emsg(_(e_noalt));
} else {
@@ -202,8 +223,8 @@ void do_window(int nchar, long Prenum, int xchar)
}
if (!curbuf_locked() && win_split(0, 0) == OK) {
- (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : (int)Prenum,
- (linenr_T)0, GETF_ALT, false);
+ (void)buflist_getfile(Prenum == 0 ? curwin->w_alt_fnum : Prenum,
+ 0, GETF_ALT, false);
}
break;
@@ -355,17 +376,16 @@ newwindow:
case 'T':
CHECK_CMDWIN;
if (one_window(curwin)) {
- msg(_(m_onlyone));
+ msg(_(m_onlyone), 0);
} else {
tabpage_T *oldtab = curtab;
- tabpage_T *newtab;
// First create a new tab with the window, then go back to
// the old tab and close the window there.
win_T *wp = curwin;
- if (win_new_tabpage((int)Prenum, NULL) == OK
+ if (win_new_tabpage(Prenum, NULL) == OK
&& valid_tabpage(oldtab)) {
- newtab = curtab;
+ tabpage_T *newtab = curtab;
goto_tabpage_tp(oldtab, true, true);
if (curwin == wp) {
win_close(curwin, false, false);
@@ -412,14 +432,14 @@ newwindow:
case 'r':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- win_rotate(false, (int)Prenum1); // downwards
+ win_rotate(false, Prenum1); // downwards
break;
// rotate windows upwards
case 'R':
CHECK_CMDWIN;
reset_VIsual_and_resel(); // stop Visual mode
- win_rotate(true, (int)Prenum1); // upwards
+ win_rotate(true, Prenum1); // upwards
break;
// move window to the very top/bottom/left/right
@@ -428,7 +448,7 @@ newwindow:
case 'H':
case 'L':
CHECK_CMDWIN;
- win_totop((int)Prenum,
+ win_totop(Prenum,
((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0)
| ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT));
break;
@@ -442,40 +462,40 @@ newwindow:
// increase current window height
case '+':
- win_setheight(curwin->w_height + (int)Prenum1);
+ win_setheight(curwin->w_height + Prenum1);
break;
// decrease current window height
case '-':
- win_setheight(curwin->w_height - (int)Prenum1);
+ win_setheight(curwin->w_height - Prenum1);
break;
// set current window height
case Ctrl__:
case '_':
- win_setheight(Prenum ? (int)Prenum : Rows - 1);
+ win_setheight(Prenum ? Prenum : Rows - 1);
break;
// increase current window width
case '>':
- win_setwidth(curwin->w_width + (int)Prenum1);
+ win_setwidth(curwin->w_width + Prenum1);
break;
// decrease current window width
case '<':
- win_setwidth(curwin->w_width - (int)Prenum1);
+ win_setwidth(curwin->w_width - Prenum1);
break;
// set current window width
case '|':
- win_setwidth(Prenum != 0 ? (int)Prenum : Columns);
+ win_setwidth(Prenum != 0 ? Prenum : Columns);
break;
// jump to tag and split window if tag exists (in preview window)
case '}':
CHECK_CMDWIN;
if (Prenum) {
- g_do_tagpreview = (int)Prenum;
+ g_do_tagpreview = Prenum;
} else {
g_do_tagpreview = (int)p_pvh;
}
@@ -485,7 +505,7 @@ newwindow:
CHECK_CMDWIN;
// Keep visual mode, can select words to use as a tag.
if (Prenum) {
- postponed_split = (int)Prenum;
+ postponed_split = Prenum;
} else {
postponed_split = -1;
}
@@ -506,6 +526,9 @@ newwindow:
case Ctrl_F: {
wingotofile:
CHECK_CMDWIN;
+ if (check_text_or_curbuf_locked(NULL)) {
+ break;
+ }
linenr_T lnum = -1;
char *ptr = grab_file_name(Prenum1, &lnum);
@@ -513,18 +536,31 @@ wingotofile:
tabpage_T *oldtab = curtab;
win_T *oldwin = curwin;
setpcmark();
- if (win_split(0, 0) == OK) {
+
+ // If 'switchbuf' is set to 'useopen' or 'usetab' and the
+ // file is already opened in a window, then jump to it.
+ win_T *wp = NULL;
+ if ((swb_flags & (SWB_USEOPEN | SWB_USETAB))
+ && cmdmod.cmod_tab == 0) {
+ wp = swbuf_goto_win_with_buf(buflist_findname_exp(ptr));
+ }
+
+ if (wp == NULL && win_split(0, 0) == OK) {
RESET_BINDING(curwin);
if (do_ecmd(0, ptr, NULL, NULL, ECMD_LASTL, ECMD_HIDE, NULL) == FAIL) {
// Failed to open the file, close the window opened for it.
win_close(curwin, false, false);
goto_tabpage_win(oldtab, oldwin);
- } else if (nchar == 'F' && lnum >= 0) {
- curwin->w_cursor.lnum = lnum;
- check_cursor_lnum();
- beginline(BL_SOL | BL_FIX);
+ } else {
+ wp = curwin;
}
}
+
+ if (wp != NULL && nchar == 'F' && lnum >= 0) {
+ curwin->w_cursor.lnum = lnum;
+ check_cursor_lnum(curwin);
+ beginline(BL_SOL | BL_FIX);
+ }
xfree(ptr);
}
break;
@@ -546,7 +582,7 @@ wingotofile:
}
// Make a copy, if the line was changed it will be freed.
- ptr = xstrnsave(ptr, len);
+ ptr = xmemdupz(ptr, len);
find_pattern_in_path(ptr, 0, len, true, Prenum == 0,
type, Prenum1, ACTION_SPLIT, 1, MAXLNUM);
@@ -581,7 +617,7 @@ wingotofile:
case '}':
xchar = Ctrl_RSB;
if (Prenum) {
- g_do_tagpreview = (int)Prenum;
+ g_do_tagpreview = Prenum;
} else {
g_do_tagpreview = (int)p_pvh;
}
@@ -590,7 +626,7 @@ wingotofile:
case Ctrl_RSB:
// Keep visual mode, can select words to use as a tag.
if (Prenum) {
- postponed_split = (int)Prenum;
+ postponed_split = Prenum;
} else {
postponed_split = -1;
}
@@ -608,11 +644,11 @@ wingotofile:
goto wingotofile;
case 't': // CTRL-W gt: go to next tab page
- goto_tabpage((int)Prenum);
+ goto_tabpage(Prenum);
break;
case 'T': // CTRL-W gT: go to previous tab page
- goto_tabpage(-(int)Prenum1);
+ goto_tabpage(-Prenum1);
break;
case TAB: // CTRL-W g<Tab>: go to last used tab page
@@ -658,17 +694,13 @@ static void cmd_with_count(char *cmd, char *bufp, size_t bufsize, int64_t Prenum
}
}
-void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
+void win_set_buf(win_T *win, buf_T *buf, bool noautocmd, Error *err)
+ FUNC_ATTR_NONNULL_ALL
{
- win_T *win = find_window_by_handle(window, err);
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!win || !buf) {
- return;
- }
-
tabpage_T *tab = win_find_tabpage(win);
+ // no redrawing and don't set the window title
+ RedrawingDisabled++;
if (noautocmd) {
block_autocmds();
}
@@ -678,7 +710,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
api_set_error(err,
kErrorTypeException,
"Failed to switch to window %d",
- window);
+ win->handle);
}
try_start();
@@ -687,7 +719,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
api_set_error(err,
kErrorTypeException,
"Failed to set buffer %d",
- buffer);
+ buf->handle);
}
// If window is not current, state logic will not validate its cursor.
@@ -698,210 +730,13 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err)
if (noautocmd) {
unblock_autocmds();
}
-}
-
-/// Create a new float.
-///
-/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
-/// It must then already belong to the current tabpage!
-/// @param last make the window the last one in the window list.
-/// Only used when allocating the autocommand window.
-/// @param config must already have been validated!
-win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
-{
- if (wp == NULL) {
- wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
- win_init(wp, curwin, 0);
- } else {
- assert(!last);
- assert(!wp->w_floating);
- if (firstwin == wp && lastwin_nofloating() == wp) {
- // last non-float
- api_set_error(err, kErrorTypeException,
- "Cannot change last window into float");
- return NULL;
- } else if (!win_valid(wp)) {
- api_set_error(err, kErrorTypeException,
- "Cannot change window from different tabpage into float");
- return NULL;
- }
- int dir;
- winframe_remove(wp, &dir, NULL);
- XFREE_CLEAR(wp->w_frame);
- (void)win_comp_pos(); // recompute window positions
- win_remove(wp, NULL);
- win_append(lastwin_nofloating(), wp);
- }
- wp->w_floating = true;
- wp->w_status_height = 0;
- wp->w_winbar_height = 0;
- wp->w_hsep_height = 0;
- wp->w_vsep_width = 0;
-
- win_config_float(wp, fconfig);
- win_set_inner_size(wp, true);
- wp->w_pos_changed = true;
- redraw_later(wp, UPD_VALID);
- return wp;
-}
-
-void win_set_minimal_style(win_T *wp)
-{
- wp->w_p_nu = false;
- wp->w_p_rnu = false;
- wp->w_p_cul = false;
- wp->w_p_cuc = false;
- wp->w_p_spell = false;
- wp->w_p_list = false;
-
- // Hide EOB region: use " " fillchar and cleared highlighting
- if (wp->w_p_fcs_chars.eob != ' ') {
- char *old = wp->w_p_fcs;
- wp->w_p_fcs = ((*old == NUL)
- ? xstrdup("eob: ")
- : concat_str(old, ",eob: "));
- free_string_option(old);
- }
-
- // TODO(bfredl): this could use a highlight namespace directly,
- // and avoid peculiarities around window options
- char *old = wp->w_p_winhl;
- wp->w_p_winhl = ((*old == NUL)
- ? xstrdup("EndOfBuffer:")
- : concat_str(old, ",EndOfBuffer:"));
- free_string_option(old);
- parse_winhl_opt(wp);
-
- // signcolumn: use 'auto'
- if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
- free_string_option(wp->w_p_scl);
- wp->w_p_scl = xstrdup("auto");
- }
-
- // foldcolumn: use '0'
- if (wp->w_p_fdc[0] != '0') {
- free_string_option(wp->w_p_fdc);
- wp->w_p_fdc = xstrdup("0");
- }
-
- // colorcolumn: cleared
- if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
- free_string_option(wp->w_p_cc);
- wp->w_p_cc = xstrdup("");
- }
-
- // statuscolumn: cleared
- if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
- free_string_option(wp->w_p_stc);
- wp->w_p_stc = xstrdup("");
- }
-}
-
-void win_config_float(win_T *wp, FloatConfig fconfig)
-{
- wp->w_width = MAX(fconfig.width, 1);
- wp->w_height = MAX(fconfig.height, 1);
-
- if (fconfig.relative == kFloatRelativeCursor) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += curwin->w_wrow;
- fconfig.col += curwin->w_wcol;
- fconfig.window = curwin->handle;
- } else if (fconfig.relative == kFloatRelativeMouse) {
- int row = mouse_row, col = mouse_col, grid = mouse_grid;
- win_T *mouse_win = mouse_find_win(&grid, &row, &col);
- if (mouse_win != NULL) {
- fconfig.relative = kFloatRelativeWindow;
- fconfig.row += row;
- fconfig.col += col;
- fconfig.window = mouse_win->handle;
- }
- }
-
- bool change_external = fconfig.external != wp->w_float_config.external;
- bool change_border = (fconfig.border != wp->w_float_config.border
- || memcmp(fconfig.border_hl_ids,
- wp->w_float_config.border_hl_ids,
- sizeof fconfig.border_hl_ids) != 0);
-
- wp->w_float_config = fconfig;
-
- bool has_border = wp->w_floating && wp->w_float_config.border;
- for (int i = 0; i < 4; i++) {
- int new_adj = has_border && wp->w_float_config.border_chars[2 * i + 1][0];
- if (new_adj != wp->w_border_adj[i]) {
- change_border = true;
- wp->w_border_adj[i] = new_adj;
- }
- }
-
- if (!ui_has(kUIMultigrid)) {
- wp->w_height = MIN(wp->w_height, Rows - 1 - win_border_height(wp));
- wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
- }
-
- win_set_inner_size(wp, true);
- must_redraw = MAX(must_redraw, UPD_VALID);
-
- wp->w_pos_changed = true;
- if (change_external || change_border) {
- wp->w_hl_needs_update = true;
- redraw_later(wp, UPD_NOT_VALID);
- }
-
- // compute initial position
- if (wp->w_float_config.relative == kFloatRelativeWindow) {
- int row = (int)wp->w_float_config.row;
- int col = (int)wp->w_float_config.col;
- Error dummy = ERROR_INIT;
- win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
- if (parent) {
- row += parent->w_winrow;
- col += parent->w_wincol;
- ScreenGrid *grid = &parent->w_grid;
- int row_off = 0, col_off = 0;
- grid_adjust(&grid, &row_off, &col_off);
- row += row_off;
- col += col_off;
- if (wp->w_float_config.bufpos.lnum >= 0) {
- pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
- wp->w_float_config.bufpos.col, 0 };
- int trow, tcol, tcolc, tcole;
- textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
- row += trow - 1;
- col += tcol - 1;
- }
- }
- api_clear_error(&dummy);
- wp->w_winrow = row;
- wp->w_wincol = col;
- } else {
- wp->w_winrow = (int)fconfig.row;
- wp->w_wincol = (int)fconfig.col;
- }
-
- // changing border style while keeping border only requires redrawing border
- if (fconfig.border) {
- wp->w_redr_border = true;
- redraw_later(wp, UPD_VALID);
- }
-}
-
-void win_check_anchored_floats(win_T *win)
-{
- for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- // float might be anchored to moved window
- if (wp->w_float_config.relative == kFloatRelativeWindow
- && wp->w_float_config.window == win->handle) {
- wp->w_pos_changed = true;
- }
- }
+ RedrawingDisabled--;
}
/// Return the number of fold columns to display
int win_fdccol_count(win_T *wp)
{
- const char *fdc = (const char *)wp->w_p_fdc;
+ const char *fdc = wp->w_p_fdc;
// auto:<NUM>
if (strncmp(fdc, "auto", 4) == 0) {
@@ -928,7 +763,13 @@ void ui_ext_win_position(win_T *wp, bool validate)
if (c.relative == kFloatRelativeWindow) {
Error dummy = ERROR_INIT;
win_T *win = find_window_by_handle(c.window, &dummy);
- if (win) {
+ api_clear_error(&dummy);
+ if (win != NULL) {
+ // When a floating window is anchored to another window,
+ // update the position of its anchored window first.
+ if (win->w_pos_changed && win->w_grid_alloc.chars != NULL && win_valid(win)) {
+ ui_ext_win_position(win, validate);
+ }
grid = &win->w_grid;
int row_off = 0, col_off = 0;
grid_adjust(&grid, &row_off, &col_off);
@@ -942,15 +783,18 @@ void ui_ext_win_position(win_T *wp, bool validate)
col += tcol - 1;
}
}
- api_clear_error(&dummy);
}
wp->w_grid_alloc.zindex = wp->w_float_config.zindex;
if (ui_has(kUIMultigrid)) {
String anchor = cstr_as_string((char *)float_anchor_str[c.anchor]);
- ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
- grid->handle, row, col, c.focusable,
- wp->w_grid_alloc.zindex);
+ if (!c.hide) {
+ ui_call_win_float_pos(wp->w_grid_alloc.handle, wp->handle, anchor,
+ grid->handle, row, col, c.focusable,
+ wp->w_grid_alloc.zindex);
+ } else {
+ ui_call_win_hide(wp->w_grid_alloc.handle);
+ }
} else {
bool valid = (wp->w_redr_type == 0);
if (!valid && !validate) {
@@ -967,16 +811,23 @@ void ui_ext_win_position(win_T *wp, bool validate)
comp_row += grid->comp_row;
comp_col += grid->comp_col;
comp_row = MAX(MIN(comp_row, Rows - wp->w_height_outer - (p_ch > 0 ? 1 : 0)), 0);
- comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ if (!c.fixed || east) {
+ comp_col = MAX(MIN(comp_col, Columns - wp->w_width_outer), 0);
+ }
wp->w_winrow = comp_row;
wp->w_wincol = comp_col;
- ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
- wp->w_height_outer, wp->w_width_outer, valid, false);
- ui_check_cursor_grid(wp->w_grid_alloc.handle);
- wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
- if (!valid) {
- wp->w_grid_alloc.valid = false;
- redraw_later(wp, UPD_NOT_VALID);
+
+ if (!c.hide) {
+ ui_comp_put_grid(&wp->w_grid_alloc, comp_row, comp_col,
+ wp->w_height_outer, wp->w_width_outer, valid, false);
+ ui_check_cursor_grid(wp->w_grid_alloc.handle);
+ wp->w_grid_alloc.focusable = wp->w_float_config.focusable;
+ if (!valid) {
+ wp->w_grid_alloc.valid = false;
+ redraw_later(wp, UPD_NOT_VALID);
+ }
+ } else {
+ ui_comp_remove_grid(&wp->w_grid_alloc);
}
}
} else {
@@ -986,22 +837,63 @@ void ui_ext_win_position(win_T *wp, bool validate)
void ui_ext_win_viewport(win_T *wp)
{
- if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid) {
- int botline = wp->w_botline;
- int line_count = wp->w_buffer->b_ml.ml_line_count;
- if (botline == line_count + 1 && wp->w_empty_rows == 0) {
+ // NOTE: The win_viewport command is delayed until the next flush when there are pending updates.
+ // This ensures that the updates and the viewport are sent together.
+ if ((wp == curwin || ui_has(kUIMultigrid)) && wp->w_viewport_invalid && wp->w_redr_type == 0) {
+ const linenr_T line_count = wp->w_buffer->b_ml.ml_line_count;
+ // Avoid ml_get errors when producing "scroll_delta".
+ const linenr_T cur_topline = MIN(wp->w_topline, line_count);
+ const linenr_T cur_botline = MIN(wp->w_botline, line_count);
+ int64_t delta = 0;
+ linenr_T last_topline = wp->w_viewport_last_topline;
+ linenr_T last_botline = wp->w_viewport_last_botline;
+ int last_topfill = wp->w_viewport_last_topfill;
+ int64_t last_skipcol = wp->w_viewport_last_skipcol;
+ if (last_topline > line_count) {
+ delta -= last_topline - line_count;
+ last_topline = line_count;
+ last_topfill = 0;
+ last_skipcol = MAXCOL;
+ }
+ last_botline = MIN(last_botline, line_count);
+ if (cur_topline < last_topline
+ || (cur_topline == last_topline && wp->w_skipcol < last_skipcol)) {
+ if (last_topline > 0 && cur_botline < last_topline) {
+ // Scrolling too many lines: only give an approximate "scroll_delta".
+ delta -= win_text_height(wp, cur_topline, wp->w_skipcol, cur_botline, 0, NULL);
+ delta -= last_topline - cur_botline;
+ } else {
+ delta -= win_text_height(wp, cur_topline, wp->w_skipcol, last_topline, last_skipcol, NULL);
+ }
+ } else if (cur_topline > last_topline
+ || (cur_topline == last_topline && wp->w_skipcol > last_skipcol)) {
+ if (last_botline > 0 && cur_topline > last_botline) {
+ // Scrolling too many lines: only give an approximate "scroll_delta".
+ delta += win_text_height(wp, last_topline, last_skipcol, last_botline, 0, NULL);
+ delta += cur_topline - last_botline;
+ } else {
+ delta += win_text_height(wp, last_topline, last_skipcol, cur_topline, wp->w_skipcol, NULL);
+ }
+ }
+ delta += last_topfill;
+ delta -= wp->w_topfill;
+ linenr_T ev_botline = wp->w_botline;
+ if (ev_botline == line_count + 1 && wp->w_empty_rows == 0) {
// TODO(bfredl): The might be more cases to consider, like how does this
// interact with incomplete final line? Diff filler lines?
- botline = wp->w_buffer->b_ml.ml_line_count;
+ ev_botline = line_count;
}
- ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline - 1,
- botline, wp->w_cursor.lnum - 1, wp->w_cursor.col,
- line_count);
+ ui_call_win_viewport(wp->w_grid_alloc.handle, wp->handle, wp->w_topline - 1, ev_botline,
+ wp->w_cursor.lnum - 1, wp->w_cursor.col, line_count, delta);
wp->w_viewport_invalid = false;
+ wp->w_viewport_last_topline = wp->w_topline;
+ wp->w_viewport_last_botline = wp->w_botline;
+ wp->w_viewport_last_topfill = wp->w_topfill;
+ wp->w_viewport_last_skipcol = wp->w_skipcol;
}
}
-/// If "split_disallowed" is set given an error and return FAIL.
+/// If "split_disallowed" is set give an error and return FAIL.
/// Otherwise return OK.
static int check_split_disallowed(void)
{
@@ -1058,10 +950,10 @@ int win_split(int size, int flags)
return win_split_ins(size, flags, NULL, 0);
}
-// When "new_wp" is NULL: split the current window in two.
-// When "new_wp" is not NULL: insert this window at the far
-// top/left/right/bottom.
-// return FAIL for failure, OK otherwise
+/// When "new_wp" is NULL: split the current window in two.
+/// When "new_wp" is not NULL: insert this window at the far
+/// top/left/right/bottom.
+/// @return FAIL for failure, OK otherwise
int win_split_ins(int size, int flags, win_T *new_wp, int dir)
{
win_T *wp = new_wp;
@@ -1435,7 +1327,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
frame_fix_width(oldwin);
frame_fix_width(wp);
} else {
- bool is_stl_global = global_stl_height() > 0;
+ const bool is_stl_global = global_stl_height() > 0;
// width and column of new window is same as current window
if (flags & (WSP_TOP | WSP_BOT)) {
wp->w_wincol = 0;
@@ -1451,6 +1343,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// "new_size" of the current window goes to the new window, use
// one row for the status line
win_new_height(wp, new_size);
+ const int old_status_height = oldwin->w_status_height;
if (before) {
wp->w_hsep_height = is_stl_global ? 1 : 0;
} else {
@@ -1459,15 +1352,19 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
}
if (flags & (WSP_TOP | WSP_BOT)) {
int new_fr_height = curfrp->fr_height - new_size;
-
- if (!((flags & WSP_BOT) && p_ls == 0) && !is_stl_global) {
- new_fr_height -= STATUS_HEIGHT;
- } else if (is_stl_global) {
+ if (is_stl_global) {
if (flags & WSP_BOT) {
frame_add_hsep(curfrp);
} else {
new_fr_height -= 1;
}
+ } else {
+ if (!((flags & WSP_BOT) && p_ls == 0)) {
+ new_fr_height -= STATUS_HEIGHT;
+ }
+ if (flags & WSP_BOT) {
+ frame_add_statusline(curfrp);
+ }
}
frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false);
} else {
@@ -1476,7 +1373,6 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (before) { // new window above current one
wp->w_winrow = oldwin->w_winrow;
-
if (is_stl_global) {
wp->w_status_height = 0;
oldwin->w_winrow += wp->w_height + 1;
@@ -1490,15 +1386,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
wp->w_status_height = 0;
} else {
wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT;
- wp->w_status_height = oldwin->w_status_height;
+ wp->w_status_height = old_status_height;
if (!(flags & WSP_BOT)) {
oldwin->w_status_height = STATUS_HEIGHT;
}
}
}
- if ((flags & WSP_BOT) && !is_stl_global) {
- frame_add_statusline(curfrp);
- }
frame_fix_height(wp);
frame_fix_height(oldwin);
}
@@ -1525,7 +1418,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// equalize the window sizes.
if (do_equal || dir != 0) {
win_equal(wp, true, (flags & WSP_VERT) ? (dir == 'v' ? 'b' : 'h') : (dir == 'h' ? 'b' : 'v'));
- } else if (*p_spk != 'c' && !is_aucmd_win(wp)) {
+ } else if (!is_aucmd_win(wp)) {
win_fix_scroll(false);
}
@@ -1567,7 +1460,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
// The windows will both edit the same buffer.
// WSP_NEWLOC may be specified in flags to prevent the location list from
// being copied.
-static void win_init(win_T *newp, win_T *oldp, int flags)
+void win_init(win_T *newp, win_T *oldp, int flags)
{
newp->w_buffer = oldp->w_buffer;
newp->w_s = &(oldp->w_buffer->b_s);
@@ -1599,6 +1492,9 @@ static void win_init(win_T *newp, win_T *oldp, int flags)
? NULL : xstrdup(oldp->w_prevdir);
if (*p_spk != 'c') {
+ if (*p_spk == 't') {
+ newp->w_skipcol = oldp->w_skipcol;
+ }
newp->w_botline = oldp->w_botline;
newp->w_prev_height = oldp->w_height;
newp->w_prev_winrow = oldp->w_winrow;
@@ -1641,24 +1537,6 @@ static void win_init_some(win_T *newp, win_T *oldp)
win_copy_options(oldp, newp);
}
-/// Return true if "win" is floating window in the current tab page.
-///
-/// @param win window to check
-bool win_valid_floating(const win_T *win)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (win == NULL) {
- return false;
- }
-
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (wp == win) {
- return wp->w_floating;
- }
- }
- return false;
-}
-
/// Check if "win" is a pointer to an existing window in the current tabpage.
///
/// @param win window to check
@@ -1790,7 +1668,7 @@ int make_windows(int count, bool vertical)
}
// Exchange current and next window
-static void win_exchange(long Prenum)
+static void win_exchange(int Prenum)
{
if (curwin->w_floating) {
emsg(e_floatexchange);
@@ -1802,6 +1680,10 @@ static void win_exchange(long Prenum)
beep_flush();
return;
}
+ if (text_or_buf_locked()) {
+ beep_flush();
+ return;
+ }
frame_T *frp;
@@ -1900,8 +1782,8 @@ static void win_rotate(bool upwards, int count)
}
}
- win_T *wp1;
- win_T *wp2;
+ win_T *wp1 = NULL;
+ win_T *wp2 = NULL;
while (count--) {
if (upwards) { // first window becomes last window
@@ -2115,7 +1997,7 @@ void win_equal(win_T *next_curwin, bool current, int dir)
win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current,
topframe, dir, 0, tabline_height(),
Columns, topframe->fr_height);
- if (*p_spk != 'c' && !is_aucmd_win(next_curwin)) {
+ if (!is_aucmd_win(next_curwin)) {
win_fix_scroll(true);
}
}
@@ -2258,6 +2140,9 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
}
if (hnc) { // add next_curwin size
next_curwin_size -= (int)p_wiw - (m - n);
+ if (next_curwin_size < 0) {
+ next_curwin_size = 0;
+ }
new_size += next_curwin_size;
room -= new_size - next_curwin_size;
} else {
@@ -2407,7 +2292,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
}
}
-static void leaving_window(win_T *const win)
+void leaving_window(win_T *const win)
FUNC_ATTR_NONNULL_ALL
{
// Only matters for a prompt window.
@@ -2585,6 +2470,23 @@ static bool can_close_floating_windows(void)
return true;
}
+/// @return true if, considering the cmdwin, `win` is safe to close.
+/// If false and `win` is the cmdwin, it is closed; otherwise, `err` is set.
+bool can_close_in_cmdwin(win_T *win, Error *err)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (cmdwin_type != 0) {
+ if (win == curwin) {
+ cmdwin_result = Ctrl_C;
+ return false;
+ } else if (win == cmdwin_old_curwin) {
+ api_set_error(err, kErrorTypeException, "%s", e_cmdwin);
+ return false;
+ }
+ }
+ return true;
+}
+
/// Close the possibly last window in a tab page.
///
/// @param win window to close
@@ -2637,10 +2539,11 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev
return true;
}
-/// Close the buffer of "win" and unload it if "free_buf" is true.
+/// Close the buffer of "win" and unload it if "action" is DOBUF_UNLOAD.
+/// "action" can also be zero (do nothing).
/// "abort_if_last" is passed to close_buffer(): abort closing if all other
/// windows are closed.
-static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last)
+static void win_close_buffer(win_T *win, int action, bool abort_if_last)
{
// Free independent synblock before the buffer is freed.
if (win->w_buffer != NULL) {
@@ -2659,7 +2562,7 @@ static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last)
bufref_T bufref;
set_bufref(&bufref, curbuf);
win->w_closing = true;
- close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, abort_if_last, true);
+ close_buffer(win, win->w_buffer, action, abort_if_last, true);
if (win_valid_any_tab(win)) {
win->w_closing = false;
}
@@ -2757,6 +2660,9 @@ int win_close(win_T *win, bool free_buf, bool force)
reset_VIsual_and_resel(); // stop Visual mode
other_buffer = true;
+ if (!win_valid(win)) {
+ return FAIL;
+ }
win->w_closing = true;
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) {
@@ -2789,7 +2695,7 @@ int win_close(win_T *win, bool free_buf, bool force)
return OK;
}
- win_close_buffer(win, free_buf, true);
+ win_close_buffer(win, free_buf ? DOBUF_UNLOAD : 0, true);
if (only_one_window() && win_valid(win) && win->w_buffer == NULL
&& (last_window(win) || curtab != prev_curtab
@@ -2873,7 +2779,7 @@ int win_close(win_T *win, bool free_buf, bool force)
if (wp->w_p_pvw || bt_quickfix(wp->w_buffer)) {
// If the cursor goes to the preview or the quickfix window, try
// finding another window to go to.
- for (;;) {
+ while (true) {
if (wp->w_next == NULL) {
wp = firstwin;
} else {
@@ -2908,9 +2814,7 @@ int win_close(win_T *win, bool free_buf, bool force)
win_equal(curwin, curwin->w_frame->fr_parent == win_frame, dir);
} else {
(void)win_comp_pos();
- if (*p_spk != 'c') {
- win_fix_scroll(false);
- }
+ win_fix_scroll(false);
}
}
@@ -3327,20 +3231,20 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
// By default the next window will get the space that was abandoned by this
// window
frame_T *target_fr = frp->fr_next;
- frame_T *other_fr = frp->fr_prev;
+ frame_T *other_fr = frp->fr_prev;
// If this is part of a column of windows and 'splitbelow' is true then the
// previous window will get the space.
if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_COL && p_sb) {
target_fr = frp->fr_prev;
- other_fr = frp->fr_next;
+ other_fr = frp->fr_next;
}
// If this is part of a row of windows, and 'splitright' is true then the
// previous window will get the space.
if (frp->fr_parent != NULL && frp->fr_parent->fr_layout == FR_ROW && p_spr) {
target_fr = frp->fr_prev;
- other_fr = frp->fr_next;
+ other_fr = frp->fr_next;
}
// If 'wfh' or 'wfw' is set for the target and not for the alternate
@@ -3577,12 +3481,7 @@ static void frame_add_statusline(frame_T *frp)
{
if (frp->fr_layout == FR_LEAF) {
win_T *wp = frp->fr_win;
- if (wp->w_status_height == 0) {
- if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative
- wp->w_height -= STATUS_HEIGHT;
- }
- wp->w_status_height = STATUS_HEIGHT;
- }
+ wp->w_status_height = STATUS_HEIGHT;
} else if (frp->fr_layout == FR_ROW) {
// Handle all the frames in the row.
FOR_ALL_FRAMES(frp, frp->fr_child) {
@@ -3729,12 +3628,7 @@ static void frame_add_hsep(const frame_T *frp)
{
if (frp->fr_layout == FR_LEAF) {
win_T *wp = frp->fr_win;
- if (wp->w_hsep_height == 0) {
- if (wp->w_height > 0) { // don't make it negative
- wp->w_height++;
- }
- wp->w_hsep_height = 1;
- }
+ wp->w_hsep_height = 1;
} else if (frp->fr_layout == FR_ROW) {
// Handle all the frames in the row.
FOR_ALL_FRAMES(frp, frp->fr_child) {
@@ -3871,7 +3765,7 @@ void close_others(int message, int forceit)
if (one_nonfloat() && !lastwin->w_floating) {
if (message
&& !autocmd_busy) {
- msg(_(m_onlyone));
+ msg(_(m_onlyone), 0);
}
return;
}
@@ -3884,6 +3778,12 @@ void close_others(int message, int forceit)
continue;
}
+ // autoccommands messed this one up
+ if (!buf_valid(wp->w_buffer) && win_valid(wp)) {
+ wp->w_buffer = NULL;
+ win_close(wp, false, false);
+ continue;
+ }
// Check if it's allowed to abandon this window
int r = can_abandon(wp->w_buffer, forceit);
if (!win_valid(wp)) { // autocommands messed wp up
@@ -3969,7 +3869,7 @@ static int win_alloc_firstwin(win_T *oldwin)
if (oldwin == NULL) {
// Very first window, need to create an empty buffer for it and
// initialize from scratch.
- curbuf = buflist_new(NULL, NULL, 1L, BLN_LISTED);
+ curbuf = buflist_new(NULL, NULL, 1, BLN_LISTED);
if (curbuf == NULL) {
return FAIL;
}
@@ -4025,7 +3925,7 @@ static tabpage_T *alloc_tabpage(void)
static int last_tp_handle = 0;
tabpage_T *tp = xcalloc(1, sizeof(tabpage_T));
tp->handle = ++last_tp_handle;
- pmap_put(handle_T)(&tabpage_handles, tp->handle, tp);
+ pmap_put(int)(&tabpage_handles, tp->handle, tp);
// Init t: variables.
tp->tp_vars = tv_dict_alloc();
@@ -4038,7 +3938,7 @@ static tabpage_T *alloc_tabpage(void)
void free_tabpage(tabpage_T *tp)
{
- pmap_del(handle_T)(&tabpage_handles, tp->handle);
+ pmap_del(int)(&tabpage_handles, tp->handle, NULL);
diff_clear(tp);
for (int idx = 0; idx < SNAP_COUNT; idx++) {
clear_snapshot(tp, idx);
@@ -4082,7 +3982,7 @@ int win_new_tabpage(int after, char *filename)
}
newtp->tp_localdir = old_curtab->tp_localdir
- ? xstrdup(old_curtab->tp_localdir) : NULL;
+ ? xstrdup(old_curtab->tp_localdir) : NULL;
curtab = newtp;
@@ -4151,7 +4051,11 @@ int may_open_tabpage(void)
cmdmod.cmod_tab = 0; // reset it to avoid doing it twice
postponed_split_tab = 0;
- return win_new_tabpage(n, NULL);
+ int status = win_new_tabpage(n, NULL);
+ if (status == OK) {
+ apply_autocmds(EVENT_TABNEWENTERED, NULL, NULL, false, curbuf);
+ }
+ return status;
}
// Create up to "maxcount" tabpages with empty windows.
@@ -4618,7 +4522,7 @@ tabpage_T *win_find_tabpage(win_T *win)
/// @param count nth neighbor window
///
/// @return found window
-win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
+win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, int count)
{
frame_T *foundfr = wp->w_frame;
@@ -4631,7 +4535,7 @@ win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
// First go upwards in the tree of frames until we find an upwards or
// downwards neighbor.
frame_T *fr = foundfr;
- for (;;) {
+ while (true) {
if (fr == tp->tp_topframe) {
goto end;
}
@@ -4647,7 +4551,7 @@ win_T *win_vert_neighbor(tabpage_T *tp, win_T *wp, bool up, long count)
}
// Now go downwards to find the bottom or top frame in it.
- for (;;) {
+ while (true) {
if (nfr->fr_layout == FR_LEAF) {
foundfr = nfr;
break;
@@ -4677,7 +4581,7 @@ end:
///
/// @param up true to go to win above
/// @param count go count times into direction
-static void win_goto_ver(bool up, long count)
+static void win_goto_ver(bool up, int count)
{
win_T *win = win_vert_neighbor(curtab, curwin, up, count);
if (win != NULL) {
@@ -4694,7 +4598,7 @@ static void win_goto_ver(bool up, long count)
/// @param count nth neighbor window
///
/// @return found window
-win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
+win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, int count)
{
frame_T *foundfr = wp->w_frame;
@@ -4707,7 +4611,7 @@ win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
// First go upwards in the tree of frames until we find a left or
// right neighbor.
frame_T *fr = foundfr;
- for (;;) {
+ while (true) {
if (fr == tp->tp_topframe) {
goto end;
}
@@ -4723,7 +4627,7 @@ win_T *win_horz_neighbor(tabpage_T *tp, win_T *wp, bool left, long count)
}
// Now go downwards to find the leftmost or rightmost frame in it.
- for (;;) {
+ while (true) {
if (nfr->fr_layout == FR_LEAF) {
foundfr = nfr;
break;
@@ -4753,7 +4657,7 @@ end:
///
/// @param left true to go to left window
/// @param count go count times into direction
-static void win_goto_hor(bool left, long count)
+static void win_goto_hor(bool left, int count)
{
win_T *win = win_horz_neighbor(curtab, curwin, left, count);
if (win != NULL) {
@@ -4814,7 +4718,7 @@ static void win_enter_ext(win_T *const wp, const int flags)
// Might need to scroll the old window before switching, e.g., when the
// cursor was moved.
- if (*p_spk == 'c') {
+ if (*p_spk == 'c' && !curwin_invalid) {
update_topline(curwin);
}
@@ -4836,7 +4740,9 @@ static void win_enter_ext(win_T *const wp, const int flags)
if (*p_spk == 'c') {
changed_line_abv_curs(); // assume cursor position needs updating
} else {
- win_fix_cursor(true);
+ // Make sure the cursor position is valid, either by moving the cursor
+ // or by scrolling the text.
+ win_fix_cursor(get_real_state() & (MODE_NORMAL|MODE_CMDLINE|MODE_TERMINAL));
}
fix_current_dir();
@@ -4851,8 +4757,6 @@ static void win_enter_ext(win_T *const wp, const int flags)
if (other_buffer) {
apply_autocmds(EVENT_BUFENTER, NULL, NULL, false, curbuf);
}
- apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf);
- curwin->w_last_cursormoved = curwin->w_cursor;
}
maketitle();
@@ -5000,7 +4904,7 @@ win_T *buf_jump_open_tab(buf_T *buf)
/// @param hidden allocate a window structure and link it in the window if
// false.
-static win_T *win_alloc(win_T *after, bool hidden)
+win_T *win_alloc(win_T *after, bool hidden)
{
static int last_win_id = LOWEST_WIN_ID - 1;
@@ -5008,7 +4912,7 @@ static win_T *win_alloc(win_T *after, bool hidden)
win_T *new_wp = xcalloc(1, sizeof(win_T));
new_wp->handle = ++last_win_id;
- pmap_put(handle_T)(&window_handles, new_wp->handle, new_wp);
+ pmap_put(int)(&window_handles, new_wp->handle, new_wp);
grid_assign_handle(&new_wp->w_grid_alloc);
@@ -5037,12 +4941,13 @@ static win_T *win_alloc(win_T *after, bool hidden)
new_wp->w_floating = 0;
new_wp->w_float_config = FLOAT_CONFIG_INIT;
new_wp->w_viewport_invalid = true;
+ new_wp->w_viewport_last_topline = 1;
new_wp->w_ns_hl = -1;
// use global option for global-local options
- new_wp->w_p_so = -1;
- new_wp->w_p_siso = -1;
+ new_wp->w_allbuf_opt.wo_so = new_wp->w_p_so = -1;
+ new_wp->w_allbuf_opt.wo_siso = new_wp->w_p_siso = -1;
// We won't calculate w_fraction until resizing the window
new_wp->w_fraction = 0;
@@ -5069,14 +4974,13 @@ void free_wininfo(wininfo_T *wip, buf_T *bp)
/// @param tp tab page "win" is in, NULL for current
static void win_free(win_T *wp, tabpage_T *tp)
{
- pmap_del(handle_T)(&window_handles, wp->handle);
+ pmap_del(int)(&window_handles, wp->handle, NULL);
clearFolding(wp);
// reduce the reference count to the argument list.
alist_unlink(wp->w_alist);
// Don't execute autocommands while the window is halfway being deleted.
- // gui_mch_destroy_scrollbar() may trigger a FocusGained event.
block_autocmds();
clear_winopt(&wp->w_onebuf_opt);
@@ -5148,8 +5052,9 @@ static void win_free(win_T *wp, tabpage_T *tp)
}
}
- // free the border title text
+ // free the border text
clear_virttext(&wp->w_float_config.title_chunks);
+ clear_virttext(&wp->w_float_config.footer_chunks);
clear_matches(wp);
@@ -5266,8 +5171,8 @@ static void frame_remove(frame_T *frp)
void win_new_screensize(void)
{
- static long old_Rows = 0;
- static long old_Columns = 0;
+ static int old_Rows = 0;
+ static int old_Columns = 0;
if (old_Rows != Rows) {
// If 'window' uses the whole screen, keep it using that.
@@ -5309,7 +5214,7 @@ void win_new_screen_rows(void)
compute_cmdrow();
curtab->tp_ch_used = p_ch;
- if (*p_spk != 'c' && !skip_win_fix_scroll) {
+ if (!skip_win_fix_scroll) {
win_fix_scroll(true);
}
}
@@ -5367,7 +5272,7 @@ static dict_T *make_win_info_dict(int width, int height, int topline, int topfil
d->dv_refcount = 1;
// not actually looping, for breaking out on error
- while (1) {
+ while (true) {
typval_T tv = {
.v_lock = VAR_UNLOCKED,
.v_type = VAR_NUMBER,
@@ -5620,8 +5525,9 @@ void win_size_save(garray_T *gap)
{
ga_init(gap, (int)sizeof(int), 1);
ga_grow(gap, win_count() * 2 + 1);
- // first entry is value of 'lines'
- ((int *)gap->ga_data)[gap->ga_len++] = Rows;
+ // first entry is the total lines available for windows
+ ((int *)gap->ga_data)[gap->ga_len++] =
+ (int)ROWS_AVAIL + global_stl_height() - last_stl_height(false);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
((int *)gap->ga_data)[gap->ga_len++] =
@@ -5631,13 +5537,14 @@ void win_size_save(garray_T *gap)
}
// Restore window sizes, but only if the number of windows is still the same
-// and 'lines' didn't change.
+// and total lines available for windows didn't change.
// Does not free the growarray.
void win_size_restore(garray_T *gap)
FUNC_ATTR_NONNULL_ALL
{
if (win_count() * 2 + 1 == gap->ga_len
- && ((int *)gap->ga_data)[0] == Rows) {
+ && ((int *)gap->ga_data)[0] ==
+ ROWS_AVAIL + global_stl_height() - last_stl_height(false)) {
// The order matters, because frames contain other frames, but it's
// difficult to get right. The easy way out is to do it twice.
for (int j = 0; j < 2; j++) {
@@ -5675,13 +5582,6 @@ int win_comp_pos(void)
return row + global_stl_height();
}
-void win_reconfig_floats(void)
-{
- for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
- win_config_float(wp, wp->w_float_config);
- }
-}
-
// Update the position of the windows in frame "topfrp", using the width and
// height of the frames.
// "*row" and "*col" are the top-left position of the frame. They are updated
@@ -5756,9 +5656,7 @@ void win_setheight_win(int height, win_T *win)
msg_row = row;
msg_col = 0;
- if (*p_spk != 'c') {
- win_fix_scroll(true);
- }
+ win_fix_scroll(true);
redraw_all_later(UPD_NOT_VALID);
redraw_cmdline = true;
@@ -6082,7 +5980,7 @@ static void frame_setwidth(frame_T *curfrp, int width)
}
// Check 'winminheight' for a valid value and reduce it if needed.
-void win_setminheight(void)
+const char *did_set_winminheight(optset_T *args FUNC_ATTR_UNUSED)
{
bool first = true;
@@ -6099,10 +5997,11 @@ void win_setminheight(void)
first = false;
}
}
+ return NULL;
}
// Check 'winminwidth' for a valid value and reduce it if needed.
-void win_setminwidth(void)
+const char *did_set_winminwidth(optset_T *args FUNC_ATTR_UNUSED)
{
bool first = true;
@@ -6119,6 +6018,7 @@ void win_setminwidth(void)
first = false;
}
}
+ return NULL;
}
/// Status line of dragwin is dragged "offset" lines down (negative is up).
@@ -6236,9 +6136,7 @@ void win_drag_status_line(win_T *dragwin, int offset)
p_ch = MAX(Rows - cmdline_row, p_ch_was_zero ? 0 : 1);
curtab->tp_ch_used = p_ch;
- if (*p_spk != 'c') {
- win_fix_scroll(true);
- }
+ win_fix_scroll(true);
redraw_all_later(UPD_SOME_VALID);
showmode();
@@ -6343,7 +6241,7 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
redraw_all_later(UPD_NOT_VALID);
}
-#define FRACTION_MULT 16384L
+#define FRACTION_MULT 16384
// Set wp->w_fraction for the current w_wrow and w_height.
// Has no effect when the window is less than two lines.
@@ -6353,45 +6251,56 @@ void set_fraction(win_T *wp)
// When cursor is in the first line the percentage is computed as if
// it's halfway that line. Thus with two lines it is 25%, with three
// lines 17%, etc. Similarly for the last line: 75%, 83%, etc.
- wp->w_fraction = (int)(wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / wp->w_height_inner;
+ wp->w_fraction = (wp->w_wrow * FRACTION_MULT + FRACTION_MULT / 2) / wp->w_height_inner;
}
}
-/// Handle scroll position for 'splitkeep'. Replaces scroll_to_fraction()
-/// call from win_set_inner_size(). Instead we iterate over all windows in a
-/// tabpage and calculate the new scroll position.
-/// TODO(luukvbaal): Ensure this also works with wrapped lines.
-/// Requires topline to be able to be set to a bufferline with some
-/// offset(row-wise scrolling/smoothscroll).
+/// Handle scroll position, depending on 'splitkeep'. Replaces the
+/// scroll_to_fraction() call from win_new_height() if 'splitkeep' is "screen"
+/// or "topline". Instead we iterate over all windows in a tabpage and
+/// calculate the new scroll position.
+/// TODO(vim): Ensure this also works with wrapped lines.
+/// Requires a not fully visible cursor line to be allowed at the bottom of
+/// a window("zb"), probably only when 'smoothscroll' is also set.
void win_fix_scroll(int resize)
{
- linenr_T lnum;
+ if (*p_spk == 'c') {
+ return; // 'splitkeep' is "cursor"
+ }
skip_update_topline = true;
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
// Skip when window height has not changed or when floating.
if (!wp->w_floating && wp->w_height != wp->w_prev_height) {
+ // Cursor position in this window may now be invalid. It is kept
+ // potentially invalid until the window is made the current window.
+ wp->w_do_win_fix_cursor = true;
+
// If window has moved update botline to keep the same screenlines.
if (*p_spk == 's' && wp->w_winrow != wp->w_prev_winrow
&& wp->w_botline - 1 <= wp->w_buffer->b_ml.ml_line_count) {
- lnum = wp->w_cursor.lnum;
int diff = (wp->w_winrow - wp->w_prev_winrow)
+ (wp->w_height - wp->w_prev_height);
+ linenr_T lnum = wp->w_cursor.lnum;
wp->w_cursor.lnum = wp->w_botline - 1;
+
// Add difference in height and row to botline.
if (diff > 0) {
cursor_down_inner(wp, diff);
} else {
cursor_up_inner(wp, -diff);
}
- // Bring the new cursor position to the bottom of the screen.
+
+ // Scroll to put the new cursor position at the bottom of the
+ // screen.
wp->w_fraction = FRACTION_MULT;
scroll_to_fraction(wp, wp->w_prev_height);
wp->w_cursor.lnum = lnum;
} else if (wp == curwin) {
wp->w_valid &= ~VALID_CROW;
}
- invalidate_botline_win(wp);
+
+ invalidate_botline(wp);
validate_botline(wp);
}
wp->w_prev_height = wp->w_height;
@@ -6408,40 +6317,45 @@ void win_fix_scroll(int resize)
/// Make sure the cursor position is valid for 'splitkeep'.
/// If it is not, put the cursor position in the jumplist and move it.
-/// If we are not in normal mode, scroll to make valid instead.
-static void win_fix_cursor(int normal)
+/// If we are not in normal mode ("normal" is false), make it valid by scrolling
+/// instead.
+static void win_fix_cursor(bool normal)
{
win_T *wp = curwin;
- long so = get_scrolloff_value(wp);
- linenr_T nlnum = 0;
- linenr_T lnum = wp->w_cursor.lnum;
- if (wp->w_buffer->b_ml.ml_line_count < wp->w_height
- || skip_win_fix_cursor) {
+ if (skip_win_fix_cursor
+ || !wp->w_do_win_fix_cursor
+ || wp->w_buffer->b_ml.ml_line_count < wp->w_height_inner) {
return;
}
+ wp->w_do_win_fix_cursor = false;
// Determine valid cursor range.
- so = MIN(wp->w_height_inner / 2, so);
+ int so = MIN(wp->w_height_inner / 2, get_scrolloff_value(wp));
+ linenr_T lnum = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = wp->w_topline;
- linenr_T top = cursor_down_inner(wp, so);
+ cursor_down_inner(wp, so);
+ linenr_T top = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = wp->w_botline - 1;
- linenr_T bot = cursor_up_inner(wp, so);
+ cursor_up_inner(wp, so);
+ linenr_T bot = wp->w_cursor.lnum;
+
wp->w_cursor.lnum = lnum;
// Check if cursor position is above or below valid cursor range.
+ linenr_T nlnum = 0;
if (lnum > bot && (wp->w_botline - wp->w_buffer->b_ml.ml_line_count) != 1) {
nlnum = bot;
} else if (lnum < top && wp->w_topline != 1) {
- nlnum = (so == wp->w_height / 2) ? bot : top;
+ nlnum = (so == wp->w_height_inner / 2) ? bot : top;
}
- if (nlnum) { // Cursor is invalid for current scroll position.
- if (normal) {
- // Save to jumplist and set cursor to avoid scrolling.
+ if (nlnum != 0) { // Cursor is invalid for current scroll position.
+ if (normal) { // Save to jumplist and set cursor to avoid scrolling.
setmark('\'');
wp->w_cursor.lnum = nlnum;
- } else {
- // Scroll instead when not in normal mode.
+ } else { // Scroll instead when not in normal mode.
wp->w_fraction = (nlnum == bot) ? FRACTION_MULT : 0;
scroll_to_fraction(wp, wp->w_prev_height);
validate_botline(curwin);
@@ -6488,8 +6402,8 @@ void scroll_to_fraction(win_T *wp, int prev_height)
if (lnum < 1) { // can happen when starting up
lnum = 1;
}
- wp->w_wrow = (int)((long)wp->w_fraction * (long)height - 1L) / FRACTION_MULT;
- int line_size = plines_win_col(wp, lnum, (long)(wp->w_cursor.col)) - 1;
+ wp->w_wrow = (wp->w_fraction * height - 1) / FRACTION_MULT;
+ int line_size = plines_win_col(wp, lnum, wp->w_cursor.col) - 1;
int sline = wp->w_wrow - line_size;
if (sline >= 0) {
@@ -6552,9 +6466,6 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
if (wp == curwin) {
- if (get_scrolloff_value(wp)) {
- update_topline(wp);
- }
curs_columns(wp, false); // validate w_wrow
}
if (prev_height > 0) {
@@ -6562,7 +6473,7 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
redraw_later(wp, UPD_SOME_VALID);
- invalidate_botline_win(wp);
+ invalidate_botline(wp);
}
void win_set_inner_size(win_T *wp, bool valid_cursor)
@@ -6585,20 +6496,20 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
// call win_new_height() recursively.
validate_cursor();
}
- if (wp->w_height_inner != prev_height) { // -V547
+ if (wp->w_height_inner != prev_height) {
return; // Recursive call already changed the size, bail out.
}
if (wp->w_wrow != wp->w_prev_fraction_row) {
set_fraction(wp);
}
}
- wp->w_skipcol = 0;
wp->w_height_inner = height;
win_comp_scroll(wp);
// There is no point in adjusting the scroll position when exiting. Some
// values might be invalid.
if (valid_cursor && !exiting && *p_spk == 'c') {
+ wp->w_skipcol = 0;
scroll_to_fraction(wp, prev_height);
}
redraw_later(wp, UPD_SOME_VALID);
@@ -6609,12 +6520,9 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
wp->w_lines_valid = 0;
if (valid_cursor) {
changed_line_abv_curs_win(wp);
- invalidate_botline_win(wp);
- if (wp == curwin) {
- skip_update_topline = (*p_spk != 'c');
- update_topline(wp);
+ invalidate_botline(wp);
+ if (wp == curwin && *p_spk == 'c') {
curs_columns(wp, true); // validate w_wrow
- skip_update_topline = false;
}
}
redraw_later(wp, UPD_NOT_VALID);
@@ -6631,27 +6539,18 @@ void win_set_inner_size(win_T *wp, bool valid_cursor)
wp->w_redr_status = true;
}
-static int win_border_height(win_T *wp)
-{
- return wp->w_border_adj[0] + wp->w_border_adj[2];
-}
-
-static int win_border_width(win_T *wp)
-{
- return wp->w_border_adj[1] + wp->w_border_adj[3];
-}
-
/// Set the width of a window.
void win_new_width(win_T *wp, int width)
{
- wp->w_width = width;
+ // Should we give an error if width < 0?
+ wp->w_width = width < 0 ? 0 : width;
wp->w_pos_changed = true;
win_set_inner_size(wp, true);
}
void win_comp_scroll(win_T *wp)
{
- const long old_w_p_scr = wp->w_p_scr;
+ const OptInt old_w_p_scr = wp->w_p_scr;
wp->w_p_scr = wp->w_height_inner / 2;
if (wp->w_p_scr == 0) {
@@ -6747,7 +6646,7 @@ void command_height(void)
static void frame_add_height(frame_T *frp, int n)
{
frame_new_height(frp, frp->fr_height + n, false, false);
- for (;;) {
+ while (true) {
frp = frp->fr_parent;
if (frp == NULL) {
break;
@@ -6759,7 +6658,7 @@ static void frame_add_height(frame_T *frp, int n)
// Get the file name at the cursor.
// If Visual mode is active, use the selected text if it's in one line.
// Returns the name in allocated memory, NULL for failure.
-char *grab_file_name(long count, linenr_T *file_lnum)
+char *grab_file_name(int count, linenr_T *file_lnum)
{
int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
if (VIsual_active) {
@@ -6772,7 +6671,7 @@ char *grab_file_name(long count, linenr_T *file_lnum)
if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) {
char *p = ptr + len + 1;
- *file_lnum = (linenr_T)getdigits_long(&p, false, 0);
+ *file_lnum = getdigits_int32(&p, false, 0);
}
return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
}
@@ -6790,7 +6689,7 @@ char *grab_file_name(long count, linenr_T *file_lnum)
// FNAME_EXP expand to path
// FNAME_HYP check for hypertext link
// FNAME_INCL apply "includeexpr"
-char *file_name_at_cursor(int options, long count, linenr_T *file_lnum)
+char *file_name_at_cursor(int options, int count, linenr_T *file_lnum)
{
return file_name_in_line(get_cursor_line_ptr(),
curwin->w_cursor.col, options, count, curbuf->b_ffname,
@@ -6801,7 +6700,7 @@ char *file_name_at_cursor(int options, long count, linenr_T *file_lnum)
/// @param file_lnum line number after the file name
///
/// @return the name of the file under or after ptr[col]. Otherwise like file_name_at_cursor().
-char *file_name_in_line(char *line, int col, int options, long count, char *rel_fname,
+char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname,
linenr_T *file_lnum)
{
// search forward for what could be the start of a file name
@@ -6821,12 +6720,12 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
bool is_url = false;
// Search backward for first char of the file name.
- // Go one char back to ":" before "//" even when ':' is not in 'isfname'.
+ // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":"
+ // is not in 'isfname').
while (ptr > line) {
if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) {
ptr -= len + 1;
- } else if (vim_isfilec((uint8_t)ptr[-1])
- || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
+ } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
ptr--;
} else {
break;
@@ -6835,14 +6734,13 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
// Search forward for the last char of the file name.
// Also allow ":/" when ':' is not in 'isfname'.
- len = 0;
+ len = path_has_drive_letter(ptr) ? 2 : 0;
while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
|| ((options & FNAME_HYP) && path_is_url(ptr + len))
|| (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) {
// After type:// we also include :, ?, & and = as valid characters, so that
// http://google.com:8080?q=this&that=ok works.
- if ((ptr[len] >= 'A' && ptr[len] <= 'Z')
- || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
+ if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
if (in_type && path_is_url(ptr + len + 1)) {
is_url = true;
}
@@ -6900,8 +6798,7 @@ char *file_name_in_line(char *line, int col, int options, long count, char *rel_
void last_status(bool morewin)
{
// Don't make a difference between horizontal or vertical split.
- last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_nonfloat()))),
- global_stl_height() > 0);
+ last_status_rec(topframe, last_stl_height(morewin) > 0, global_stl_height() > 0);
}
// Remove status line from window, replacing it with a horizontal separator if needed.
@@ -7100,6 +6997,14 @@ int global_stl_height(void)
return (p_ls == 3) ? STATUS_HEIGHT : 0;
}
+/// Return the height of the last window's statusline, or the global statusline if set.
+///
+/// @param morewin pretend there are two or more windows if true.
+int last_stl_height(bool morewin)
+{
+ return (p_ls > 1 || (p_ls == 1 && (morewin || !one_nonfloat()))) ? STATUS_HEIGHT : 0;
+}
+
/// Return the minimal number of rows that is needed on the screen to display
/// the current number of windows.
int min_rows(void)
@@ -7196,8 +7101,9 @@ void reset_lnums(void)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
if (wp->w_buffer == curbuf) {
- // Restore the value if the autocommand didn't change it and it was
- // set.
+ // Restore the value if the autocommand didn't change it and it was set.
+ // Note: This triggers e.g. on BufReadPre, when the buffer is not yet
+ // loaded, so cannot validate the buffer line
if (equalpos(wp->w_save_cursor.w_cursor_corr, wp->w_cursor)
&& wp->w_save_cursor.w_cursor_save.lnum != 0) {
wp->w_cursor = wp->w_save_cursor.w_cursor_save;
@@ -7206,6 +7112,9 @@ void reset_lnums(void)
&& wp->w_save_cursor.w_topline_save != 0) {
wp->w_topline = wp->w_save_cursor.w_topline_save;
}
+ if (wp->w_save_cursor.w_topline_save > wp->w_buffer->b_ml.ml_line_count) {
+ wp->w_valid &= ~VALID_TOPLINE;
+ }
}
}
}
@@ -7408,13 +7317,13 @@ static int int_cmp(const void *a, const void *b)
/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
///
/// @return error message, NULL if it's OK.
-char *check_colorcolumn(win_T *wp)
+const char *check_colorcolumn(win_T *wp)
{
if (wp->w_buffer == NULL) {
return NULL; // buffer was closed
}
- unsigned int count = 0;
+ unsigned count = 0;
int color_cols[256];
for (char *s = wp->w_p_cc; *s != NUL && count < 255;) {
int col;
@@ -7467,7 +7376,7 @@ skip:
qsort(color_cols, count, sizeof(int), int_cmp);
int j = 0;
- for (unsigned int i = 0; i < count; i++) {
+ for (unsigned i = 0; i < count; i++) {
// skip duplicates
if (j == 0 || wp->w_p_cc_cols[j - 1] != color_cols[i]) {
wp->w_p_cc_cols[j++] = color_cols[i];
diff --git a/src/nvim/window.h b/src/nvim/window.h
index 4ab2bea60a..3650fef46e 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -1,44 +1,49 @@
-#ifndef NVIM_WINDOW_H
-#define NVIM_WINDOW_H
+#pragma once
#include <stdbool.h>
-#include <stddef.h>
-
-#include "nvim/buffer.h"
-#include "nvim/buffer_defs.h"
-#include "nvim/macros.h"
-#include "nvim/mark.h"
-#include "nvim/os/os.h"
-#include "nvim/os/os_defs.h"
-#include "nvim/vim.h"
-
-// Values for file_name_in_line()
-#define FNAME_MESS 1 // give error message
-#define FNAME_EXP 2 // expand to path
-#define FNAME_HYP 4 // check for hypertext link
-#define FNAME_INCL 8 // apply 'includeexpr'
-#define FNAME_REL 16 // ".." and "./" are relative to the (current)
- // file instead of the current directory
-#define FNAME_UNESC 32 // remove backslashes used for escaping
-
-// arguments for win_split()
-#define WSP_ROOM 0x01 // require enough room
-#define WSP_VERT 0x02 // split/equalize vertically
-#define WSP_HOR 0x04 // equalize horizontally
-#define WSP_TOP 0x08 // window at top-left of shell
-#define WSP_BOT 0x10 // window at bottom-right of shell
-#define WSP_HELP 0x20 // creating the help window
-#define WSP_BELOW 0x40 // put new window below/right
-#define WSP_ABOVE 0x80 // put new window above/left
-#define WSP_NEWLOC 0x100 // don't copy location list
-
-// Minimum screen size
-#define MIN_COLUMNS 12 // minimal columns for screen
-#define MIN_LINES 2 // minimal lines for screen
-
-// Set to true if 'cmdheight' was explicitly set to 0.
-EXTERN bool p_ch_was_zero INIT(= false);
+
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+#include "nvim/garray_defs.h" // IWYU pragma: keep
+#include "nvim/macros_defs.h"
+#include "nvim/option_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+/// Values for file_name_in_line()
+enum {
+ FNAME_MESS = 1, ///< give error message
+ FNAME_EXP = 2, ///< expand to path
+ FNAME_HYP = 4, ///< check for hypertext link
+ FNAME_INCL = 8, ///< apply 'includeexpr'
+ FNAME_REL = 16, ///< ".." and "./" are relative to the (current)
+ ///< file instead of the current directory
+ FNAME_UNESC = 32, ///< remove backslashes used for escaping
+};
+
+/// arguments for win_split()
+enum {
+ WSP_ROOM = 0x01, ///< require enough room
+ WSP_VERT = 0x02, ///< split/equalize vertically
+ WSP_HOR = 0x04, ///< equalize horizontally
+ WSP_TOP = 0x08, ///< window at top-left of shell
+ WSP_BOT = 0x10, ///< window at bottom-right of shell
+ WSP_HELP = 0x20, ///< creating the help window
+ WSP_BELOW = 0x40, ///< put new window below/right
+ WSP_ABOVE = 0x80, ///< put new window above/left
+ WSP_NEWLOC = 0x100, ///< don't copy location list
+};
+
+enum {
+ MIN_COLUMNS = 12, ///< minimal columns for screen
+ MIN_LINES = 2, ///< minimal lines for screen
+ STATUS_HEIGHT = 1, ///< height of a status line under a window
+};
+
+/// Lowest number used for window ID. Cannot have this many windows per tab.
+enum { LOWEST_WIN_ID = 1000, };
+
+/// Set to true if 'cmdheight' was explicitly set to 0.
+EXTERN bool p_ch_was_zero INIT( = false);
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "window.h.generated.h"
#endif
-#endif // NVIM_WINDOW_H
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
new file mode 100644
index 0000000000..44f0e2fc0b
--- /dev/null
+++ b/src/nvim/winfloat.c
@@ -0,0 +1,289 @@
+#include <assert.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "klib/kvec.h"
+#include "nvim/api/private/defs.h"
+#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/drawscreen.h"
+#include "nvim/func_attr.h"
+#include "nvim/globals.h"
+#include "nvim/grid.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
+#include "nvim/mouse.h"
+#include "nvim/move.h"
+#include "nvim/option.h"
+#include "nvim/optionstr.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/vim_defs.h"
+#include "nvim/window.h"
+#include "nvim/winfloat.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.c.generated.h"
+#endif
+
+/// Create a new float.
+///
+/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float.
+/// It must then already belong to the current tabpage!
+/// @param last make the window the last one in the window list.
+/// Only used when allocating the autocommand window.
+/// @param config must already have been validated!
+win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err)
+{
+ if (wp == NULL) {
+ wp = win_alloc(last ? lastwin : lastwin_nofloating(), false);
+ win_init(wp, curwin, 0);
+ } else {
+ assert(!last);
+ assert(!wp->w_floating);
+ if (firstwin == wp && lastwin_nofloating() == wp) {
+ // last non-float
+ api_set_error(err, kErrorTypeException,
+ "Cannot change last window into float");
+ return NULL;
+ } else if (!win_valid(wp)) {
+ api_set_error(err, kErrorTypeException,
+ "Cannot change window from different tabpage into float");
+ return NULL;
+ }
+ int dir;
+ winframe_remove(wp, &dir, NULL);
+ XFREE_CLEAR(wp->w_frame);
+ (void)win_comp_pos(); // recompute window positions
+ win_remove(wp, NULL);
+ win_append(lastwin_nofloating(), wp);
+ }
+ wp->w_floating = true;
+ wp->w_status_height = 0;
+ wp->w_winbar_height = 0;
+ wp->w_hsep_height = 0;
+ wp->w_vsep_width = 0;
+
+ win_config_float(wp, fconfig);
+ win_set_inner_size(wp, true);
+ wp->w_pos_changed = true;
+ redraw_later(wp, UPD_VALID);
+ return wp;
+}
+
+void win_set_minimal_style(win_T *wp)
+{
+ wp->w_p_nu = false;
+ wp->w_p_rnu = false;
+ wp->w_p_cul = false;
+ wp->w_p_cuc = false;
+ wp->w_p_spell = false;
+ wp->w_p_list = false;
+
+ // Hide EOB region: use " " fillchar and cleared highlighting
+ if (wp->w_p_fcs_chars.eob != ' ') {
+ char *old = wp->w_p_fcs;
+ wp->w_p_fcs = ((*old == NUL)
+ ? xstrdup("eob: ")
+ : concat_str(old, ",eob: "));
+ free_string_option(old);
+ }
+
+ // TODO(bfredl): this could use a highlight namespace directly,
+ // and avoid peculiarities around window options
+ char *old = wp->w_p_winhl;
+ wp->w_p_winhl = ((*old == NUL)
+ ? xstrdup("EndOfBuffer:")
+ : concat_str(old, ",EndOfBuffer:"));
+ free_string_option(old);
+ parse_winhl_opt(wp);
+
+ // signcolumn: use 'auto'
+ if (wp->w_p_scl[0] != 'a' || strlen(wp->w_p_scl) >= 8) {
+ free_string_option(wp->w_p_scl);
+ wp->w_p_scl = xstrdup("auto");
+ }
+
+ // foldcolumn: use '0'
+ if (wp->w_p_fdc[0] != '0') {
+ free_string_option(wp->w_p_fdc);
+ wp->w_p_fdc = xstrdup("0");
+ }
+
+ // colorcolumn: cleared
+ if (wp->w_p_cc != NULL && *wp->w_p_cc != NUL) {
+ free_string_option(wp->w_p_cc);
+ wp->w_p_cc = xstrdup("");
+ }
+
+ // statuscolumn: cleared
+ if (wp->w_p_stc != NULL && *wp->w_p_stc != NUL) {
+ free_string_option(wp->w_p_stc);
+ wp->w_p_stc = xstrdup("");
+ }
+}
+
+int win_border_height(win_T *wp)
+{
+ return wp->w_border_adj[0] + wp->w_border_adj[2];
+}
+
+int win_border_width(win_T *wp)
+{
+ return wp->w_border_adj[1] + wp->w_border_adj[3];
+}
+
+void win_config_float(win_T *wp, FloatConfig fconfig)
+{
+ wp->w_width = MAX(fconfig.width, 1);
+ wp->w_height = MAX(fconfig.height, 1);
+
+ if (fconfig.relative == kFloatRelativeCursor) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += curwin->w_wrow;
+ fconfig.col += curwin->w_wcol;
+ fconfig.window = curwin->handle;
+ } else if (fconfig.relative == kFloatRelativeMouse) {
+ int row = mouse_row, col = mouse_col, grid = mouse_grid;
+ win_T *mouse_win = mouse_find_win(&grid, &row, &col);
+ if (mouse_win != NULL) {
+ fconfig.relative = kFloatRelativeWindow;
+ fconfig.row += row;
+ fconfig.col += col;
+ fconfig.window = mouse_win->handle;
+ }
+ }
+
+ bool change_external = fconfig.external != wp->w_float_config.external;
+ bool change_border = (fconfig.border != wp->w_float_config.border
+ || memcmp(fconfig.border_hl_ids,
+ wp->w_float_config.border_hl_ids,
+ sizeof fconfig.border_hl_ids) != 0);
+
+ wp->w_float_config = fconfig;
+
+ bool has_border = wp->w_floating && wp->w_float_config.border;
+ for (int i = 0; i < 4; i++) {
+ int new_adj = has_border && wp->w_float_config.border_chars[2 * i + 1][0];
+ if (new_adj != wp->w_border_adj[i]) {
+ change_border = true;
+ wp->w_border_adj[i] = new_adj;
+ }
+ }
+
+ if (!ui_has(kUIMultigrid)) {
+ wp->w_height = MIN(wp->w_height, Rows - win_border_height(wp));
+ wp->w_width = MIN(wp->w_width, Columns - win_border_width(wp));
+ }
+
+ win_set_inner_size(wp, true);
+ must_redraw = MAX(must_redraw, UPD_VALID);
+
+ wp->w_pos_changed = true;
+ if (change_external || change_border) {
+ wp->w_hl_needs_update = true;
+ redraw_later(wp, UPD_NOT_VALID);
+ }
+
+ // compute initial position
+ if (wp->w_float_config.relative == kFloatRelativeWindow) {
+ int row = (int)wp->w_float_config.row;
+ int col = (int)wp->w_float_config.col;
+ Error dummy = ERROR_INIT;
+ win_T *parent = find_window_by_handle(wp->w_float_config.window, &dummy);
+ if (parent) {
+ row += parent->w_winrow;
+ col += parent->w_wincol;
+ ScreenGrid *grid = &parent->w_grid;
+ int row_off = 0, col_off = 0;
+ grid_adjust(&grid, &row_off, &col_off);
+ row += row_off;
+ col += col_off;
+ if (wp->w_float_config.bufpos.lnum >= 0) {
+ pos_T pos = { wp->w_float_config.bufpos.lnum + 1,
+ wp->w_float_config.bufpos.col, 0 };
+ int trow, tcol, tcolc, tcole;
+ textpos2screenpos(parent, &pos, &trow, &tcol, &tcolc, &tcole, true);
+ row += trow - 1;
+ col += tcol - 1;
+ }
+ }
+ api_clear_error(&dummy);
+ wp->w_winrow = row;
+ wp->w_wincol = col;
+ } else {
+ wp->w_winrow = (int)fconfig.row;
+ wp->w_wincol = (int)fconfig.col;
+ }
+
+ // changing border style while keeping border only requires redrawing border
+ if (fconfig.border) {
+ wp->w_redr_border = true;
+ redraw_later(wp, UPD_VALID);
+ }
+}
+
+static int float_zindex_cmp(const void *a, const void *b)
+{
+ return (*(win_T **)b)->w_float_config.zindex - (*(win_T **)a)->w_float_config.zindex;
+}
+
+void win_float_remove(bool bang, int count)
+{
+ kvec_t(win_T *) float_win_arr = KV_INITIAL_VALUE;
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ kv_push(float_win_arr, wp);
+ }
+ qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp);
+ for (size_t i = 0; i < float_win_arr.size; i++) {
+ if (win_close(float_win_arr.items[i], false, false) == FAIL) {
+ break;
+ }
+ if (!bang) {
+ count--;
+ if (count == 0) {
+ break;
+ }
+ }
+ }
+ kv_destroy(float_win_arr);
+}
+
+void win_check_anchored_floats(win_T *win)
+{
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ // float might be anchored to moved window
+ if (wp->w_float_config.relative == kFloatRelativeWindow
+ && wp->w_float_config.window == win->handle) {
+ wp->w_pos_changed = true;
+ }
+ }
+}
+
+void win_reconfig_floats(void)
+{
+ for (win_T *wp = lastwin; wp && wp->w_floating; wp = wp->w_prev) {
+ win_config_float(wp, wp->w_float_config);
+ }
+}
+
+/// Return true if "win" is floating window in the current tab page.
+///
+/// @param win window to check
+bool win_float_valid(const win_T *win)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (win == NULL) {
+ return false;
+ }
+
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (wp == win) {
+ return wp->w_floating;
+ }
+ }
+ return false;
+}
diff --git a/src/nvim/winfloat.h b/src/nvim/winfloat.h
new file mode 100644
index 0000000000..876ea9ccea
--- /dev/null
+++ b/src/nvim/winfloat.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "winfloat.h.generated.h"
+#endif
diff --git a/src/uncrustify.cfg b/src/uncrustify.cfg
index f9e6617d40..ffd4955829 100644
--- a/src/uncrustify.cfg
+++ b/src/uncrustify.cfg
@@ -1,4 +1,4 @@
-# Uncrustify-0.75.1_f
+# Uncrustify-0.78.1_f
#
# General options
@@ -84,7 +84,7 @@ sp_arith = ignore # ignore/add/remove/force/not_defined
sp_arith_additive = force # ignore/add/remove/force/not_defined
# Add or remove space around assignment operator '=', '+=', etc.
-sp_assign = ignore # ignore/add/remove/force/not_defined
+sp_assign = force # ignore/add/remove/force/not_defined
# Add or remove space around '=' in C++11 lambda capture specifications.
#
@@ -101,6 +101,12 @@ sp_cpp_lambda_square_brace = ignore # ignore/add/remove/force/not_defined
# Add or remove space after the opening parenthesis and before the closing
# parenthesis of a argument list of a C++11 lambda, as in
+# '[]( <here> ){ ... }'
+# with an empty list.
+sp_cpp_lambda_argument_list_empty = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space after the opening parenthesis and before the closing
+# parenthesis of a argument list of a C++11 lambda, as in
# '[]( <here> int x <here> ){ ... }'.
sp_cpp_lambda_argument_list = ignore # ignore/add/remove/force/not_defined
@@ -125,12 +131,12 @@ sp_before_assign = ignore # ignore/add/remove/force/not_defined
# Add or remove space after assignment operator '=', '+=', etc.
#
# Overrides sp_assign.
-sp_after_assign = force # ignore/add/remove/force/not_defined
+sp_after_assign = ignore # ignore/add/remove/force/not_defined
# Add or remove space in 'enum {'.
#
# Default: add
-sp_enum_brace = add # ignore/add/remove/force/not_defined
+sp_enum_brace = force # ignore/add/remove/force/not_defined
# Add or remove space in 'NS_ENUM ('.
sp_enum_paren = ignore # ignore/add/remove/force/not_defined
@@ -195,9 +201,36 @@ sp_before_ptr_star = force # ignore/add/remove/force/not_defined
# variable name. If set to ignore, sp_before_ptr_star is used instead.
sp_before_unnamed_ptr_star = ignore # ignore/add/remove/force/not_defined
+# Add or remove space before pointer star '*' that is followed by a qualifier.
+# If set to ignore, sp_before_unnamed_ptr_star is used instead.
+sp_before_qualifier_ptr_star = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space before pointer star '*' that is followed by 'operator' keyword.
+# If set to ignore, sp_before_unnamed_ptr_star is used instead.
+sp_before_operator_ptr_star = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space before pointer star '*' that is followed by
+# a class scope (as in 'int *MyClass::method()') or namespace scope
+# (as in 'int *my_ns::func()').
+# If set to ignore, sp_before_unnamed_ptr_star is used instead.
+sp_before_scope_ptr_star = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space before pointer star '*' that is followed by '::',
+# as in 'int *::func()'.
+# If set to ignore, sp_before_unnamed_ptr_star is used instead.
+sp_before_global_scope_ptr_star = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space between a qualifier and a pointer star '*' that isn't
+# followed by a variable name, as in '(char const *)'. If set to ignore,
+# sp_before_ptr_star is used instead.
+sp_qualifier_unnamed_ptr_star = ignore # ignore/add/remove/force/not_defined
+
# Add or remove space between pointer stars '*', as in 'int ***a;'.
sp_between_ptr_star = ignore # ignore/add/remove/force/not_defined
+# Add or remove space between pointer star '*' and reference '&', as in 'int *& a;'.
+sp_between_ptr_ref = ignore # ignore/add/remove/force/not_defined
+
# Add or remove space after pointer star '*', if followed by a word.
#
# Overrides sp_type_func.
@@ -232,13 +265,24 @@ sp_ptr_star_func_type = ignore # ignore/add/remove/force/not_defined
sp_ptr_star_paren = ignore # ignore/add/remove/force/not_defined
# Add or remove space before a pointer star '*', if followed by a function
-# prototype or function definition.
+# prototype or function definition. If set to ignore, sp_before_ptr_star is
+# used instead.
sp_before_ptr_star_func = ignore # ignore/add/remove/force/not_defined
+# Add or remove space between a qualifier and a pointer star '*' followed by
+# the name of the function in a function prototype or definition, as in
+# 'char const *foo()`. If set to ignore, sp_before_ptr_star is used instead.
+sp_qualifier_ptr_star_func = ignore # ignore/add/remove/force/not_defined
+
# Add or remove space before a pointer star '*' in the trailing return of a
# function prototype or function definition.
sp_before_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined
+# Add or remove space between a qualifier and a pointer star '*' in the
+# trailing return of a function prototype or function definition, as in
+# 'auto foo() -> char const *'.
+sp_qualifier_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined
+
# Add or remove space before a reference sign '&'.
sp_before_byref = ignore # ignore/add/remove/force/not_defined
@@ -297,6 +341,7 @@ sp_before_angle = ignore # ignore/add/remove/force/not_defined
sp_inside_angle = ignore # ignore/add/remove/force/not_defined
# Add or remove space inside '<>'.
+# if empty.
sp_inside_angle_empty = ignore # ignore/add/remove/force/not_defined
# Add or remove space between '>' and ':'.
@@ -343,7 +388,7 @@ sp_inside_sparen_open = ignore # ignore/add/remove/force/not_defined
sp_inside_sparen_close = ignore # ignore/add/remove/force/not_defined
# Add or remove space inside '(' and ')' of 'for' statements.
-sp_inside_for = ignore # ignore/add/remove/force/not_defined
+sp_inside_for = remove # ignore/add/remove/force/not_defined
# Add or remove space after '(' of 'for' statements.
#
@@ -433,6 +478,7 @@ sp_cpp_before_struct_binding = ignore # ignore/add/remove/force/not_defined
sp_inside_square = remove # ignore/add/remove/force/not_defined
# Add or remove space inside '[]'.
+# if empty.
sp_inside_square_empty = ignore # ignore/add/remove/force/not_defined
# (OC) Add or remove space inside a non-empty Objective-C boxed array '@[' and
@@ -447,15 +493,15 @@ sp_after_comma = add # ignore/add/remove/force/not_defined
# Default: remove
sp_before_comma = remove # ignore/add/remove/force/not_defined
-# (C#) Add or remove space between ',' and ']' in multidimensional array type
+# (C#, Vala) Add or remove space between ',' and ']' in multidimensional array type
# like 'int[,,]'.
sp_after_mdatype_commas = ignore # ignore/add/remove/force/not_defined
-# (C#) Add or remove space between '[' and ',' in multidimensional array type
+# (C#, Vala) Add or remove space between '[' and ',' in multidimensional array type
# like 'int[,,]'.
sp_before_mdatype_commas = ignore # ignore/add/remove/force/not_defined
-# (C#) Add or remove space between ',' in multidimensional array type
+# (C#, Vala) Add or remove space between ',' in multidimensional array type
# like 'int[,,]'.
sp_between_mdatype_commas = ignore # ignore/add/remove/force/not_defined
@@ -497,7 +543,7 @@ sp_paren_qualifier = ignore # ignore/add/remove/force/not_defined
sp_paren_noexcept = ignore # ignore/add/remove/force/not_defined
# Add or remove space after class ':'.
-sp_after_class_colon = ignore # ignore/add/remove/force/not_defined
+sp_after_class_colon = remove # ignore/add/remove/force/not_defined
# Add or remove space before class ':'.
sp_before_class_colon = ignore # ignore/add/remove/force/not_defined
@@ -561,7 +607,7 @@ sp_decltype_paren = ignore # ignore/add/remove/force/not_defined
sp_after_tag = ignore # ignore/add/remove/force/not_defined
# Add or remove space inside enum '{' and '}'.
-sp_inside_braces_enum = ignore # ignore/add/remove/force/not_defined
+sp_inside_braces_enum = force # ignore/add/remove/force/not_defined
# Add or remove space inside struct/union '{' and '}'.
sp_inside_braces_struct = ignore # ignore/add/remove/force/not_defined
@@ -591,6 +637,7 @@ sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force/not_defined
sp_inside_braces = add # ignore/add/remove/force/not_defined
# Add or remove space inside '{}'.
+# if empty.
sp_inside_braces_empty = remove # ignore/add/remove/force/not_defined
# Add or remove space around trailing return operator '->'.
@@ -608,7 +655,7 @@ sp_type_brace_init_lst = ignore # ignore/add/remove/force/not_defined
sp_func_proto_paren = remove # ignore/add/remove/force/not_defined
# Add or remove space between function name and '()' on function declaration
-# without parameters.
+# if empty.
sp_func_proto_paren_empty = ignore # ignore/add/remove/force/not_defined
# Add or remove space between function name and '(' with a typedef specifier.
@@ -618,7 +665,7 @@ sp_func_type_paren = ignore # ignore/add/remove/force/not_defined
sp_func_def_paren = remove # ignore/add/remove/force/not_defined
# Add or remove space between function name and '()' on function definition
-# without parameters.
+# if empty.
sp_func_def_paren_empty = ignore # ignore/add/remove/force/not_defined
# Add or remove space inside empty function '()'.
@@ -628,6 +675,16 @@ sp_inside_fparens = remove # ignore/add/remove/force/not_defined
# Add or remove space inside function '(' and ')'.
sp_inside_fparen = remove # ignore/add/remove/force/not_defined
+# Add or remove space inside user functor '(' and ')'.
+sp_func_call_user_inside_rparen = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space inside empty functor '()'.
+# Overrides sp_after_angle unless use_sp_after_angle_always is set to true.
+sp_inside_rparens = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space inside functor '(' and ')'.
+sp_inside_rparen = ignore # ignore/add/remove/force/not_defined
+
# Add or remove space inside the first parentheses in a function type, as in
# 'void (*x)(...)'.
sp_inside_tparen = remove # ignore/add/remove/force/not_defined
@@ -652,7 +709,7 @@ sp_fparen_brace_initializer = ignore # ignore/add/remove/force/not_defined
sp_fparen_dbrace = ignore # ignore/add/remove/force/not_defined
# Add or remove space between function name and '(' on function calls.
-sp_func_call_paren = ignore # ignore/add/remove/force/not_defined
+sp_func_call_paren = remove # ignore/add/remove/force/not_defined
# Add or remove space between function name and '()' on function calls without
# parameters. If set to ignore (the default), sp_func_call_paren is used.
@@ -744,10 +801,10 @@ sp_macro = ignore # ignore/add/remove/force/not_defined
sp_macro_func = ignore # ignore/add/remove/force/not_defined
# Add or remove space between 'else' and '{' if on the same line.
-sp_else_brace = add # ignore/add/remove/force/not_defined
+sp_else_brace = force # ignore/add/remove/force/not_defined
# Add or remove space between '}' and 'else' if on the same line.
-sp_brace_else = add # ignore/add/remove/force/not_defined
+sp_brace_else = force # ignore/add/remove/force/not_defined
# Add or remove space between '}' and the name of a typedef on the same line.
sp_brace_typedef = force # ignore/add/remove/force/not_defined
@@ -816,7 +873,7 @@ sp_inv = remove # ignore/add/remove/force/not_defined
# affect the spacing after a '&' that is part of a type.
#
# Default: remove
-sp_addr = remove # ignore/add/remove/force/not_defined
+sp_addr = ignore # ignore/add/remove/force/not_defined
# Add or remove space around the '.' or '->' operators.
#
@@ -915,7 +972,7 @@ sp_after_oc_property = ignore # ignore/add/remove/force/not_defined
sp_after_oc_synchronized = ignore # ignore/add/remove/force/not_defined
# Add or remove space around the ':' in 'b ? t : f'.
-sp_cond_colon = ignore # ignore/add/remove/force/not_defined
+sp_cond_colon = force # ignore/add/remove/force/not_defined
# Add or remove space before the ':' in 'b ? t : f'.
#
@@ -928,7 +985,7 @@ sp_cond_colon_before = ignore # ignore/add/remove/force/not_defined
sp_cond_colon_after = ignore # ignore/add/remove/force/not_defined
# Add or remove space around the '?' in 'b ? t : f'.
-sp_cond_question = ignore # ignore/add/remove/force/not_defined
+sp_cond_question = force # ignore/add/remove/force/not_defined
# Add or remove space before the '?' in 'b ? t : f'.
#
@@ -967,6 +1024,14 @@ sp_extern_paren = ignore # ignore/add/remove/force/not_defined
# Add or remove space after the opening of a C++ comment, as in '// <here> A'.
sp_cmt_cpp_start = ignore # ignore/add/remove/force/not_defined
+# remove space after the '//' and the pvs command '-V1234',
+# only works with sp_cmt_cpp_start set to add or force.
+sp_cmt_cpp_pvs = false # true/false
+
+# remove space after the '//' and the command 'lint',
+# only works with sp_cmt_cpp_start set to add or force.
+sp_cmt_cpp_lint = false # true/false
+
# Add or remove space in a C++ region marker comment, as in '// <here> BEGIN'.
# A region marker is defined as a comment which is not preceded by other text
# (i.e. the comment is the first non-whitespace on the line), and which starts
@@ -995,7 +1060,7 @@ sp_between_new_paren = ignore # ignore/add/remove/force/not_defined
# Add or remove space between ')' and type in 'new(foo) BAR'.
sp_after_newop_paren = ignore # ignore/add/remove/force/not_defined
-# Add or remove space inside parenthesis of the new operator
+# Add or remove space inside parentheses of the new operator
# as in 'new(foo) BAR'.
sp_inside_newop_paren = ignore # ignore/add/remove/force/not_defined
@@ -1049,6 +1114,12 @@ sp_after_noexcept = ignore # ignore/add/remove/force/not_defined
# Add or remove space after '_'.
sp_vala_after_translation = ignore # ignore/add/remove/force/not_defined
+# Add or remove space before a bit colon ':'.
+sp_before_bit_colon = ignore # ignore/add/remove/force/not_defined
+
+# Add or remove space after a bit colon ':'.
+sp_after_bit_colon = ignore # ignore/add/remove/force/not_defined
+
# If true, a <TAB> is inserted after #define.
force_tab_after_define = false # true/false
@@ -1162,7 +1233,7 @@ indent_extern = false # true/false
indent_class = false # true/false
# Whether to ignore indent for the leading base class colon.
-indent_ignore_before_class_colon = false # true/false
+indent_ignore_before_class_colon = true # true/false
# Additional indent before the leading base class colon.
# Negative values decrease indent down to the first column.
@@ -1344,7 +1415,7 @@ indent_col1_multi_string_literal = false # true/false
indent_comment_align_thresh = 3 # unsigned number
# Whether to ignore indent for goto labels.
-indent_ignore_label = false # true/false
+indent_ignore_label = true # true/false
# How to indent goto labels. Requires indent_ignore_label=false.
#
@@ -1537,9 +1608,9 @@ indent_using_block = true # true/false
# How to indent the continuation of ternary operator.
#
# 0: Off (default)
-# 1: When the `if_false` is a continuation, indent it under `if_false`
+# 1: When the `if_false` is a continuation, indent it under the `if_true` branch
# 2: When the `:` is a continuation, indent it under `?`
-indent_ternary_operator = 0 # unsigned number
+indent_ternary_operator = 2 # unsigned number
# Whether to indent the statements inside ternary operator.
indent_inside_ternary_operator = false # true/false
@@ -1565,10 +1636,15 @@ donot_indent_func_def_close_paren = false # true/false
# Newline adding and removing options
#
-# Whether to collapse empty blocks between '{' and '}'.
-# If true, overrides nl_inside_empty_func
+# Whether to collapse empty blocks between '{' and '}' except for functions.
+# Use nl_collapse_empty_body_functions to specify how empty function braces
+# should be formatted.
nl_collapse_empty_body = true # true/false
+# Whether to collapse empty blocks between '{' and '}' for functions only.
+# If true, overrides nl_inside_empty_func.
+nl_collapse_empty_body_functions = false # true/false
+
# Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'.
nl_assign_leave_one_liners = false # true/false
@@ -2021,6 +2097,12 @@ nl_template_end = false # true/false
# See nl_oc_msg_leave_one_liner.
nl_oc_msg_args = false # true/false
+# (OC) Minimum number of Objective-C message parameters before applying nl_oc_msg_args.
+nl_oc_msg_args_min_params = 0 # unsigned number
+
+# (OC) Max code width of Objective-C message before applying nl_oc_msg_args.
+nl_oc_msg_args_max_code_width = 0 # unsigned number
+
# Add or remove newline between function signature and '{'.
nl_fdef_brace = force # ignore/add/remove/force/not_defined
@@ -2034,6 +2116,9 @@ nl_cpp_ldef_brace = ignore # ignore/add/remove/force/not_defined
# Add or remove newline between 'return' and the return expression.
nl_return_expr = remove # ignore/add/remove/force/not_defined
+# Add or remove newline between 'throw' and the throw expression.
+nl_throw_expr = ignore # ignore/add/remove/force/not_defined
+
# Whether to add a newline after semicolons, except in 'for' statements.
nl_after_semicolon = false # true/false
@@ -2091,7 +2176,7 @@ nl_define_macro = false # true/false
# Whether to alter newlines between consecutive parenthesis closes. The number
# of closing parentheses in a line will depend on respective open parenthesis
# lines.
-nl_squeeze_paren_close = false # true/false
+nl_squeeze_paren_close = true # true/false
# Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and
# '#endif'. Does not affect top-level #ifdefs.
@@ -2227,7 +2312,7 @@ nl_max_blank_in_func = 0 # unsigned number
# The number of newlines inside an empty function body.
# This option overrides eat_blanks_after_open_brace and
# eat_blanks_before_close_brace, but is ignored when
-# nl_collapse_empty_body=true
+# nl_collapse_empty_body_functions=true
nl_inside_empty_func = 0 # unsigned number
# The number of newlines before a function prototype.
@@ -2265,8 +2350,21 @@ nl_after_func_class_proto_group = 0 # unsigned number
nl_class_leave_one_liner_groups = false # true/false
# The number of newlines after '}' of a multi-line function body.
+#
+# Overrides nl_min_after_func_body and nl_max_after_func_body.
nl_after_func_body = 0 # unsigned number
+# The minimum number of newlines after '}' of a multi-line function body.
+#
+# Only works when nl_after_func_body is 0.
+nl_min_after_func_body = 0 # unsigned number
+
+# The maximum number of newlines after '}' of a multi-line function body.
+#
+# Only works when nl_after_func_body is 0.
+# Takes precedence over nl_min_after_func_body.
+nl_max_after_func_body = 0 # unsigned number
+
# The number of newlines after '}' of a multi-line function body in a class
# declaration. Also affects class constructors/destructors.
#
@@ -2279,12 +2377,6 @@ nl_after_func_body_class = 0 # unsigned number
# Overrides nl_after_func_body and nl_after_func_body_class.
nl_after_func_body_one_liner = 0 # unsigned number
-# The number of blank lines after a block of variable definitions at the top
-# of a function body.
-#
-# 0: No change (default).
-nl_func_var_def_blk = 0 # unsigned number
-
# The number of newlines before a block of typedefs. If nl_after_access_spec
# is non-zero, that option takes precedence.
#
@@ -2301,15 +2393,27 @@ nl_typedef_blk_end = 0 # unsigned number
# 0: No change (default).
nl_typedef_blk_in = 0 # unsigned number
-# The number of empty newlines before a block of variable definitions
+# The minimum number of blank lines after a block of variable definitions
+# at the top of a function body. If any preprocessor directives appear
+# between the opening brace of the function and the variable block, then
+# it is considered as not at the top of the function.Newlines are added
+# before trailing preprocessor directives, if any exist.
+#
+# 0: No change (default).
+nl_var_def_blk_end_func_top = 0 # unsigned number
+
+# The minimum number of empty newlines before a block of variable definitions
# not at the top of a function body. If nl_after_access_spec is non-zero,
-# that option takes precedence.
+# that option takes precedence. Newlines are not added at the top of the
+# file or just after an opening brace. Newlines are added above any
+# preprocessor directives before the block.
#
# 0: No change (default).
nl_var_def_blk_start = 0 # unsigned number
-# The number of empty newlines after a block of variable definitions
-# not at the top of a function body.
+# The minimum number of empty newlines after a block of variable definitions
+# not at the top of a function body. Newlines are not added if the block
+# is at the bottom of the file or just before a preprocessor directive.
#
# 0: No change (default).
nl_var_def_blk_end = 0 # unsigned number
@@ -2466,7 +2570,7 @@ pos_compare = ignore # ignore/break/force/lead/trail/join/
# The position of conditional operators, as in the '?' and ':' of
# 'expr ? stmt : stmt', in wrapped expressions.
-pos_conditional = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
+pos_conditional = lead # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
# The position of the comma in wrapped expressions.
pos_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force
@@ -2625,11 +2729,6 @@ align_var_def_inline = false # true/false
# 0: Don't align (default).
align_assign_span = 0 # unsigned number
-# The span for aligning on '{' in braced init list.
-#
-# 0: Don't align (default).
-align_braced_init_list_span = 0 # unsigned number
-
# The span for aligning on '=' in function prototype modifier.
#
# 0: Don't align (default).
@@ -2646,6 +2745,11 @@ align_assign_thresh = 0 # number
# Depends on 'align_assign_span' and 'align_assign_thresh' settings.
align_assign_on_multi_var_defs = false # true/false
+# The span for aligning on '{' in braced init list.
+#
+# 0: Don't align (default).
+align_braced_init_list_span = 0 # unsigned number
+
# The threshold for aligning on '{' in braced init list.
# Use a negative number for absolute thresholds.
#
@@ -2764,6 +2868,13 @@ align_right_cmt_at_col = 0 # unsigned number
# 0: Don't align (default).
align_func_proto_span = 0 # unsigned number
+# Whether to ignore continuation lines when evaluating the number of
+# new lines for the function prototype alignment's span.
+#
+# false: continuation lines are part of the newlines count
+# true: continuation lines are not counted
+align_func_proto_span_ignore_cont_lines = false # true/false
+
# How to consider (or treat) the '*' in the alignment of function prototypes.
#
# 0: Part of the type 'void * foo();' (default)
@@ -2813,9 +2924,22 @@ align_single_line_brace_gap = 0 # unsigned number
# 0: Don't align (default).
align_oc_msg_spec_span = 0 # unsigned number
-# Whether to align macros wrapped with a backslash and a newline. This will
-# not work right if the macro contains a multi-line comment.
-align_nl_cont = false # true/false
+# Whether and how to align backslashes that split a macro onto multiple lines.
+# This will not work right if the macro contains a multi-line comment.
+#
+# 0: Do nothing (default)
+# 1: Align the backslashes in the column at the end of the longest line
+# 2: Align with the backslash that is farthest to the left, or, if that
+# backslash is farther left than the end of the longest line, at the end of
+# the longest line
+# 3: Align with the backslash that is farthest to the right
+align_nl_cont = 0 # unsigned number
+
+# The minimum number of spaces between the end of a line and its continuation
+# backslash. Requires align_nl_cont.
+#
+# Default: 1
+align_nl_cont_spaces = 1 # unsigned number
# Whether to align macro functions and variables together.
align_pp_define_together = false # true/false
@@ -3099,9 +3223,12 @@ mod_full_brace_nl = 0 # unsigned number
# mod_full_brace_function
mod_full_brace_nl_block_rem_mlcond = false # true/false
-# Add or remove unnecessary parenthesis on 'return' statement.
+# Add or remove unnecessary parentheses on 'return' statement.
mod_paren_on_return = ignore # ignore/add/remove/force/not_defined
+# Add or remove unnecessary parentheses on 'throw' statement.
+mod_paren_on_throw = ignore # ignore/add/remove/force/not_defined
+
# (Pawn) Whether to change optional semicolons to real semicolons.
mod_pawn_semicolon = false # true/false
@@ -3123,6 +3250,12 @@ mod_remove_extra_semicolon = true # true/false
# Whether to remove duplicate include.
mod_remove_duplicate_include = true # true/false
+# the following options (mod_XX_closebrace_comment) use different comment,
+# depending of the setting of the next option.
+# false: Use the c comment (default)
+# true : Use the cpp comment
+mod_add_force_c_closebrace_comment = false # true/false
+
# If a function body exceeds the specified number of newlines and doesn't have
# a comment after the close brace, a comment will be added.
mod_add_long_function_closebrace_comment = 0 # unsigned number
@@ -3190,7 +3323,7 @@ mod_move_case_return = false # true/false
# Add or remove braces around a fully braced case statement. Will only remove
# braces if there are no variable declarations in the block.
-mod_case_brace = remove # ignore/add/remove/force/not_defined
+mod_case_brace = ignore # ignore/add/remove/force/not_defined
# Whether to remove a void 'return;' that appears as the last statement in a
# function.
@@ -3199,6 +3332,49 @@ mod_remove_empty_return = false # true/false
# Add or remove the comma after the last value of an enumeration.
mod_enum_last_comma = add # ignore/add/remove/force/not_defined
+# Syntax to use for infinite loops.
+#
+# 0: Leave syntax alone (default)
+# 1: Rewrite as `for(;;)`
+# 2: Rewrite as `while(true)`
+# 3: Rewrite as `do`...`while(true);`
+# 4: Rewrite as `while(1)`
+# 5: Rewrite as `do`...`while(1);`
+#
+# Infinite loops that do not already match one of these syntaxes are ignored.
+# Other options that affect loop formatting will be applied after transforming
+# the syntax.
+mod_infinite_loop = 2 # unsigned number
+
+# Add or remove the 'int' keyword in 'int short'.
+mod_int_short = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'short int'.
+mod_short_int = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int long'.
+mod_int_long = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'long int'.
+mod_long_int = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int signed'.
+mod_int_signed = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'signed int'.
+mod_signed_int = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'int unsigned'.
+mod_int_unsigned = remove # ignore/add/remove/force/not_defined
+
+# Add or remove the 'int' keyword in 'unsigned int'.
+mod_unsigned_int = remove # ignore/add/remove/force/not_defined
+
+# If there is a situation where mod_int_* and mod_*_int would result in
+# multiple int keywords, whether to keep the rightmost int (the default) or the
+# leftmost int.
+mod_int_prefer_int_on_left = false # true/false
+
# (OC) Whether to organize the properties. If true, properties will be
# rearranged according to the mod_sort_oc_property_*_weight factors.
mod_sort_oc_properties = false # true/false
@@ -3230,6 +3406,16 @@ mod_sort_oc_property_nullability_weight = 0 # number
# Preprocessor options
#
+# How to use tabs when indenting preprocessor code.
+#
+# -1: Use 'indent_with_tabs' setting (default)
+# 0: Spaces only
+# 1: Indent with tabs to brace level, align with spaces
+# 2: Indent and align with tabs, using spaces when not on a tabstop
+#
+# Default: -1
+pp_indent_with_tabs = -1 # number
+
# Add or remove indentation of preprocessor directives inside #if blocks
# at brace level 0 (file-level).
pp_indent = remove # ignore/add/remove/force/not_defined
@@ -3250,11 +3436,11 @@ pp_indent_at_level0 = false # true/false
# Default: 1
pp_indent_count = 1 # unsigned number
-# Add or remove space after # based on pp_level of #if blocks.
-pp_space = force # ignore/add/remove/force/not_defined
+# Add or remove space after # based on pp level of #if blocks.
+pp_space_after = force # ignore/add/remove/force/not_defined
-# Sets the number of spaces per level added with pp_space.
-pp_space_count = 0 # unsigned number
+# Sets the number of spaces per level added with pp_space_after.
+pp_space_count = 1 # unsigned number
# The indent for '#region' and '#endregion' in C# and '#pragma region' in
# C/C++. Negative values decrease indent down to the first column.
@@ -3286,22 +3472,34 @@ pp_include_at_level = false # true/false
# Whether to ignore the '#define' body while formatting.
pp_ignore_define_body = false # true/false
+# An offset value that controls the indentation of the body of a multiline #define.
+# 'body' refers to all the lines of a multiline #define except the first line.
+# Requires 'pp_ignore_define_body = false'.
+#
+# <0: Absolute column: the body indentation starts off at the specified column
+# (ex. -3 ==> the body is indented starting from column 3)
+# >=0: Relative to the column of the '#' of '#define'
+# (ex. 3 ==> the body is indented starting 3 columns at the right of '#')
+#
+# Default: 8
+pp_multiline_define_body_indent = 2 # number
+
# Whether to indent case statements between #if, #else, and #endif.
-# Only applies to the indent of the preprocesser that the case statements
+# Only applies to the indent of the preprocessor that the case statements
# directly inside of.
#
# Default: true
pp_indent_case = true # true/false
# Whether to indent whole function definitions between #if, #else, and #endif.
-# Only applies to the indent of the preprocesser that the function definition
+# Only applies to the indent of the preprocessor that the function definition
# is directly inside of.
#
# Default: true
pp_indent_func_def = true # true/false
# Whether to indent extern C blocks between #if, #else, and #endif.
-# Only applies to the indent of the preprocesser that the extern block is
+# Only applies to the indent of the preprocessor that the extern block is
# directly inside of.
#
# Default: true
@@ -3309,7 +3507,7 @@ pp_indent_extern = true # true/false
# How to indent braces directly inside #if, #else, and #endif.
# Requires pp_if_indent_code=true and only applies to the indent of the
-# preprocesser that the braces are directly inside of.
+# preprocessor that the braces are directly inside of.
# 0: No extra indent
# 1: Indent by one level
# -1: Preserve original indentation
@@ -3382,10 +3580,10 @@ use_indent_continue_only_once = false # true/false
# The indentation can be:
# - after the assignment, at the '[' character
-# - at the begin of the lambda body
+# - at the beginning of the lambda body
#
-# true: indentation will be after the assignment
-# false: indentation will be at the begin of the lambda body (default)
+# true: indentation will be at the beginning of the lambda body
+# false: indentation will be after the assignment (default)
indent_cpp_lambda_only_once = false # true/false
# Whether sp_after_angle takes precedence over sp_inside_fparen. This was the
@@ -3437,6 +3635,23 @@ debug_timeout = 0 # number
# 0: do not truncate.
debug_truncate = 0 # unsigned number
+# sort (or not) the tracking info.
+#
+# Default: true
+debug_sort_the_tracks = true # true/false
+
+# decode (or not) the flags as a new line.
+# only if the -p option is set.
+debug_decode_the_flags = false # true/false
+
+# use (or not) the exit(EX_SOFTWARE) function.
+#
+# Default: true
+debug_use_the_exit_function_pop = true # true/false
+
+# insert the number of the line at the beginning of each line
+set_numbering_for_html_output = false # true/false
+
# Meaning of the settings:
# Ignore - do not do any changes
# Add - makes sure there is 1 or more space/brace/newline/etc
@@ -3489,31 +3704,32 @@ debug_truncate = 0 # unsigned number
# `macro-close END_MESSAGE_MAP`
#
#
-set QUESTION FUNC_API_CHECK_TEXTLOCK
-set QUESTION FUNC_API_DEPRECATED_SINCE
-set QUESTION FUNC_API_FAST
-set QUESTION FUNC_API_LUA_ONLY
-set QUESTION FUNC_API_NOEXPORT
-set QUESTION FUNC_API_REMOTE_ONLY
-set QUESTION FUNC_API_SINCE
-set QUESTION FUNC_ATTR_ALWAYS_INLINE
-set QUESTION FUNC_ATTR_CONST
-set QUESTION FUNC_ATTR_MALLOC
-set QUESTION FUNC_ATTR_NONNULL_ALL
-set QUESTION FUNC_ATTR_NONNULL_ARG
-set QUESTION FUNC_ATTR_NONNULL_RET
-set QUESTION FUNC_ATTR_NORETURN
-set QUESTION FUNC_ATTR_NO_SANITIZE_UNDEFINED
-set QUESTION FUNC_ATTR_PRINTF
-set QUESTION FUNC_ATTR_PURE
-set QUESTION FUNC_ATTR_UNUSED
-set QUESTION FUNC_ATTR_WARN_UNUSED_RESULT
+set CLASS_COLON FUNC_API_DEPRECATED_SINCE
+set CLASS_COLON FUNC_API_FAST
+set CLASS_COLON FUNC_API_LUA_ONLY
+set CLASS_COLON FUNC_API_NOEXPORT
+set CLASS_COLON FUNC_API_REMOTE_ONLY
+set CLASS_COLON FUNC_API_SINCE
+set CLASS_COLON FUNC_API_TEXTLOCK
+set CLASS_COLON FUNC_API_TEXTLOCK_ALLOW_CMDWIN
+set CLASS_COLON FUNC_ATTR_ALWAYS_INLINE
+set CLASS_COLON FUNC_ATTR_CONST
+set CLASS_COLON FUNC_ATTR_MALLOC
+set CLASS_COLON FUNC_ATTR_NONNULL_ALL
+set CLASS_COLON FUNC_ATTR_NONNULL_ARG
+set CLASS_COLON FUNC_ATTR_NONNULL_RET
+set CLASS_COLON FUNC_ATTR_NORETURN
+set CLASS_COLON FUNC_ATTR_NO_SANITIZE_ADDRESS
+set CLASS_COLON FUNC_ATTR_NO_SANITIZE_UNDEFINED
+set CLASS_COLON FUNC_ATTR_PRINTF
+set CLASS_COLON FUNC_ATTR_PURE
+set CLASS_COLON FUNC_ATTR_WARN_UNUSED_RESULT
set PP_PRAGMA PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
set PP_PRAGMA PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
-set QUESTION REAL_FATTR_ALWAYS_INLINE
-set QUESTION REAL_FATTR_CONST
-set QUESTION REAL_FATTR_NONNULL_ALL
-set QUESTION REAL_FATTR_PURE
-set QUESTION REAL_FATTR_WARN_UNUSED_RESULT
-# option(s) with 'not default' value: 112
+set CLASS_COLON REAL_FATTR_ALWAYS_INLINE
+set CLASS_COLON REAL_FATTR_CONST
+set CLASS_COLON REAL_FATTR_NONNULL_ALL
+set CLASS_COLON REAL_FATTR_PURE
+set CLASS_COLON REAL_FATTR_WARN_UNUSED_RESULT
+# option(s) with 'not default' value: 135
#
diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h
index 49985a0e9a..a6ce57dcb1 100644
--- a/src/xdiff/xdiff.h
+++ b/src/xdiff/xdiff.h
@@ -69,12 +69,12 @@ extern "C" {
typedef struct s_mmfile {
char *ptr;
- long size;
+ int size;
} mmfile_t;
typedef struct s_mmbuffer {
char *ptr;
- long size;
+ int size;
} mmbuffer_t;
typedef struct s_xpparam {
@@ -102,8 +102,8 @@ typedef struct s_xdemitcb {
typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
-typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a,
- long start_b, long count_b,
+typedef int (*xdl_emit_hunk_consume_func_t)(int start_a, int count_a,
+ int start_b, int count_b,
void *cb_data);
typedef struct s_xdemitconf {
diff --git a/test/.luarc.json b/test/.luarc.json
new file mode 100644
index 0000000000..5d19a37266
--- /dev/null
+++ b/test/.luarc.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
+ "runtime": {
+ "version": "LuaJIT"
+ },
+ "workspace": {
+ "library": [
+ "../runtime/lua",
+ "../build/usr/share/lua/5.1",
+ "../build",
+ "${3rd}/busted/library",
+ "${3rd}/luassert/library",
+ "${3rd}/luv/library"
+ ],
+ "checkThirdParty": false
+ },
+ "diagnostics": {
+ "groupFileStatus": {
+ "strict": "Opened",
+ "strong": "Opened"
+ },
+ "groupSeverity": {
+ "strong": "Warning",
+ "strict": "Warning"
+ },
+ "unusedLocalExclude": [ "_*" ]
+ }
+}
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
new file mode 100644
index 0000000000..e9be79edc0
--- /dev/null
+++ b/test/CMakeLists.txt
@@ -0,0 +1,70 @@
+add_subdirectory(functional/fixtures) # compile test programs
+get_directory_property(GENERATED_HELP_TAGS DIRECTORY ${PROJECT_SOURCE_DIR}/runtime DEFINITION GENERATED_HELP_TAGS)
+
+if(NOT BUSTED_OUTPUT_TYPE)
+ set(BUSTED_OUTPUT_TYPE "nvim")
+endif()
+
+get_target_property(TEST_INCLUDE_DIRS main_lib INTERFACE_INCLUDE_DIRECTORIES)
+
+set(UNITTEST_PREREQS nvim)
+set(FUNCTIONALTEST_PREREQS nvim printenv-test printargs-test shell-test pwsh-test streams-test tty-test ${GENERATED_HELP_TAGS})
+set(BENCHMARK_PREREQS nvim tty-test)
+
+check_lua_module(${LUA_PRG} "ffi" LUA_HAS_FFI)
+if(LUA_HAS_FFI)
+ add_custom_target(unittest
+ COMMAND ${CMAKE_COMMAND}
+ -D NVIM_PRG=$<TARGET_FILE:nvim>
+ -D WORKING_DIR=${PROJECT_SOURCE_DIR}
+ -D BUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
+ -D TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}
+ -D BUILD_DIR=${CMAKE_BINARY_DIR}
+ -D DEPS_INSTALL_DIR=${DEPS_INSTALL_DIR}
+ -D TEST_TYPE=unit
+ -D CIRRUS_CI=$ENV{CIRRUS_CI}
+ -D CI_BUILD=${CI_BUILD}
+ -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
+ DEPENDS ${UNITTEST_PREREQS}
+ USES_TERMINAL)
+ add_dependencies(unittest lua-dev-deps)
+else()
+ message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}")
+endif()
+
+configure_file(
+ ${CMAKE_SOURCE_DIR}/test/cmakeconfig/paths.lua.in
+ ${CMAKE_BINARY_DIR}/test/cmakeconfig/paths.lua)
+
+add_custom_target(functionaltest
+ COMMAND ${CMAKE_COMMAND}
+ -D NVIM_PRG=$<TARGET_FILE:nvim>
+ -D WORKING_DIR=${PROJECT_SOURCE_DIR}
+ -D BUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
+ -D TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}
+ -D BUILD_DIR=${CMAKE_BINARY_DIR}
+ -D DEPS_INSTALL_DIR=${DEPS_INSTALL_DIR}
+ -D TEST_TYPE=functional
+ -D CIRRUS_CI=$ENV{CIRRUS_CI}
+ -D CI_BUILD=${CI_BUILD}
+ -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
+ DEPENDS ${FUNCTIONALTEST_PREREQS}
+ USES_TERMINAL)
+
+add_custom_target(benchmark
+ COMMAND ${CMAKE_COMMAND}
+ -D NVIM_PRG=$<TARGET_FILE:nvim>
+ -D WORKING_DIR=${PROJECT_SOURCE_DIR}
+ -D BUSTED_OUTPUT_TYPE=${BUSTED_OUTPUT_TYPE}
+ -D TEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}
+ -D BUILD_DIR=${CMAKE_BINARY_DIR}
+ -D DEPS_INSTALL_DIR=${DEPS_INSTALL_DIR}
+ -D TEST_TYPE=benchmark
+ -D CIRRUS_CI=$ENV{CIRRUS_CI}
+ -D CI_BUILD=${CI_BUILD}
+ -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake
+ DEPENDS ${BENCHMARK_PREREQS}
+ USES_TERMINAL)
+
+add_dependencies(functionaltest lua-dev-deps)
+add_dependencies(benchmark lua-dev-deps)
diff --git a/test/README.md b/test/README.md
index a67040e68c..3aafe1273e 100644
--- a/test/README.md
+++ b/test/README.md
@@ -3,7 +3,7 @@ Tests
Tests are broadly divided into *unit tests* ([test/unit](https://github.com/neovim/neovim/tree/master/test/unit/)),
*functional tests* ([test/functional](https://github.com/neovim/neovim/tree/master/test/functional/)),
-and *old tests* ([src/nvim/testdir/](https://github.com/neovim/neovim/tree/master/src/nvim/testdir/)).
+and *old tests* ([test/old/testdir/](https://github.com/neovim/neovim/tree/master/test/old/testdir/)).
- _Unit_ testing is achieved by compiling the tests as a shared library which is
loaded and called by [LuaJit FFI](http://luajit.org/ext_ffi.html).
@@ -48,7 +48,7 @@ Layout
- `/test/*/**/*_spec.lua` : actual tests. Files that do not end with
`_spec.lua` are libraries like `/test/**/helpers.lua`, except that they have
some common topic.
-- `/src/nvim/testdir` : old tests (from Vim)
+- `/test/old/testdir` : old tests (from Vim)
Running tests
@@ -83,7 +83,7 @@ To run a *single* legacy test file you can use either:
or:
- make src/nvim/testdir/test_syntax.vim
+ make test/old/testdir/test_syntax.vim
- Specify only the test file name, not the full path.
@@ -102,9 +102,25 @@ Debugging tests
DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file)
INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0
```
-- You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527).
- And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of
+- You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527):
+
+ ```sh
+ GDB=1 TEST_FILE=test/functional/api/buffer_spec.lua TEST_FILTER='nvim_buf_set_text works$' make functionaltest
+ ```
+
+ Read more about [filtering tests](#filtering-tests).
+
+ Then, in another terminal:
+
+ ```sh
+ gdb -ex 'target remote localhost:7777' build/bin/nvim
+ ```
+
+ If `$VALGRIND` is also set it will pass `--vgdb=yes` to valgrind instead of
starting gdbserver directly.
+
+ See [test/functional/helpers.lua](https://github.com/neovim/neovim/blob/9cadbf1d36b63f53f0de48c8c5ff6c752ff05d70/test/functional/helpers.lua#L52-L69) for details.
+
- Hanging tests can happen due to unexpected "press-enter" prompts. The
default screen width is 50 columns. Commands that try to print lines longer
than 50 columns in the command-line, e.g. `:edit very...long...path`, will
@@ -124,7 +140,7 @@ Filtering Tests
### Filter by name
-Another filter method is by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`.
+Tests can be filtered by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`.
``` lua
it('foo api',function()
@@ -149,14 +165,26 @@ To run a *specific* unit test:
TEST_FILE=test/unit/foo.lua make unittest
+or
+
+ cmake -E env "TEST_FILE=test/unit/foo.lua" cmake --build build --target unittest
+
To run a *specific* functional test:
TEST_FILE=test/functional/foo.lua make functionaltest
+or
+
+ cmake -E env "TEST_FILE=test/functional/foo.lua" cmake --build build --target functionaltest
+
To *repeat* a test:
BUSTED_ARGS="--repeat=100 --no-keep-going" TEST_FILE=test/functional/foo_spec.lua make functionaltest
+or
+
+ cmake -E env "TEST_FILE=test/functional/foo_spec.lua" cmake -E env BUSTED_ARGS="--repeat=100 --no-keep-going" cmake --build build --target functionaltest
+
### Filter by tag
Tests can be "tagged" by adding `#` before a token in the test description.
@@ -273,7 +301,7 @@ Number; !must be defined to function properly):
- `VALGRIND_LOG` (F) (S): overrides valgrind log file name used for `VALGRIND`.
-- `TEST_COLORS` (F) (U) (D): enable pretty colors in test runner.
+- `TEST_COLORS` (F) (U) (D): enable pretty colors in test runner. Set to true by default.
- `TEST_SKIP_FRAGILE` (F) (D): makes test suite skip some fragile tests.
diff --git a/test/benchmark/autocmd_spec.lua b/test/benchmark/autocmd_spec.lua
new file mode 100644
index 0000000000..cd1af23640
--- /dev/null
+++ b/test/benchmark/autocmd_spec.lua
@@ -0,0 +1,175 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+
+local N = 7500
+
+describe('autocmd perf', function()
+ before_each(function()
+ clear()
+
+ exec_lua([[
+ out = {}
+ function start()
+ ts = vim.uv.hrtime()
+ end
+ function stop(name)
+ out[#out+1] = ('%14.6f ms - %s'):format((vim.uv.hrtime() - ts) / 1000000, name)
+ end
+ ]])
+ end)
+
+ after_each(function()
+ for _, line in ipairs(exec_lua([[return out]])) do
+ print(line)
+ end
+ end)
+
+ it('nvim_create_autocmd, nvim_del_autocmd (same pattern)', function()
+ exec_lua([[
+ local N = ...
+ local ids = {}
+
+ start()
+ for i = 1, N do
+ ids[i] = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ end
+ stop('nvim_create_autocmd')
+
+ start()
+ for i = 1, N do
+ vim.api.nvim_del_autocmd(ids[i])
+ end
+ stop('nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_create_autocmd, nvim_del_autocmd (unique patterns)', function()
+ exec_lua([[
+ local N = ...
+ local ids = {}
+
+ start()
+ for i = 1, N do
+ ids[i] = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark' .. i,
+ command = 'eval 0', -- noop
+ })
+ end
+ stop('nvim_create_autocmd')
+
+ start()
+ for i = 1, N do
+ vim.api.nvim_del_autocmd(ids[i])
+ end
+ stop('nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_create_autocmd + nvim_del_autocmd', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for _ = 1, N do
+ local id = vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ vim.api.nvim_del_autocmd(id)
+ end
+ stop('nvim_create_autocmd + nvim_del_autocmd')
+ ]], N)
+ end)
+
+ it('nvim_exec_autocmds (same pattern)', function()
+ exec_lua([[
+ local N = ...
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ })
+ end
+
+ start()
+ vim.api.nvim_exec_autocmds('User', { pattern = 'Benchmark', modeline = false })
+ stop('nvim_exec_autocmds')
+ ]], N)
+ end)
+
+ it('nvim_del_augroup_by_id', function()
+ exec_lua([[
+ local N = ...
+ local group = vim.api.nvim_create_augroup('Benchmark', {})
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ group = group,
+ })
+ end
+
+ start()
+ vim.api.nvim_del_augroup_by_id(group)
+ stop('nvim_del_augroup_by_id')
+ ]], N)
+ end)
+
+ it('nvim_del_augroup_by_name', function()
+ exec_lua([[
+ local N = ...
+ local group = vim.api.nvim_create_augroup('Benchmark', {})
+
+ for i = 1, N do
+ vim.api.nvim_create_autocmd('User', {
+ pattern = 'Benchmark',
+ command = 'eval 0', -- noop
+ group = group,
+ })
+ end
+
+ start()
+ vim.api.nvim_del_augroup_by_name('Benchmark')
+ stop('nvim_del_augroup_by_id')
+ ]], N)
+ end)
+
+ it(':autocmd, :autocmd! (same pattern)', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for i = 1, N do
+ vim.cmd('autocmd User Benchmark eval 0')
+ end
+ stop(':autocmd')
+
+ start()
+ vim.cmd('autocmd! User Benchmark')
+ stop(':autocmd!')
+ ]], N)
+ end)
+
+ it(':autocmd, :autocmd! (unique patterns)', function()
+ exec_lua([[
+ local N = ...
+
+ start()
+ for i = 1, N do
+ vim.cmd(('autocmd User Benchmark%d eval 0'):format(i))
+ end
+ stop(':autocmd')
+
+ start()
+ vim.cmd('autocmd! User')
+ stop(':autocmd!')
+ ]], N)
+ end)
+end)
diff --git a/test/benchmark/bench_regexp_spec.lua b/test/benchmark/bench_regexp_spec.lua
index 903af5f574..6128549f0f 100644
--- a/test/benchmark/bench_regexp_spec.lua
+++ b/test/benchmark/bench_regexp_spec.lua
@@ -7,7 +7,7 @@ local clear, command = helpers.clear, helpers.command
-- Temporary file for gathering benchmarking results for each regexp engine.
local result_file = 'benchmark.out'
-- Fixture containing an HTML fragment that can make a search appear to freeze.
-local sample_file = 'src/nvim/testdir/samples/re.freeze.txt'
+local sample_file = 'test/old/testdir/samples/re.freeze.txt'
-- Vim script code that does both the work and the benchmarking of that work.
local measure_cmd =
diff --git a/test/benchmark/iter_spec.lua b/test/benchmark/iter_spec.lua
new file mode 100644
index 0000000000..d176079997
--- /dev/null
+++ b/test/benchmark/iter_spec.lua
@@ -0,0 +1,215 @@
+local N = 500
+local test_table_size = 100000
+
+describe('vim.iter perf', function()
+ local function mean(t)
+ assert(#t > 0)
+ local sum = 0
+ for _, v in ipairs(t) do
+ sum = sum + v
+ end
+ return sum / #t
+ end
+
+ local function median(t)
+ local len = #t
+ if len % 2 == 0 then
+ return t[len / 2]
+ end
+ return t[(len + 1) / 2]
+ end
+
+ -- Assert that results are equal between each benchmark
+ local last = nil
+
+ local function reset()
+ last = nil
+ end
+
+ local input = {}
+ for i = 1, test_table_size do
+ input[#input + 1] = i
+ end
+
+ local function measure(f)
+ local stats = {}
+ local result
+ for _ = 1, N do
+ local tic = vim.uv.hrtime()
+ result = f(input)
+ local toc = vim.uv.hrtime()
+ stats[#stats + 1] = (toc - tic) / 1000000
+ end
+ table.sort(stats)
+ print(
+ string.format(
+ '\nMin: %0.6f ms, Max: %0.6f ms, Median: %0.6f ms, Mean: %0.6f ms',
+ math.min(unpack(stats)),
+ math.max(unpack(stats)),
+ median(stats),
+ mean(stats)
+ )
+ )
+
+ if last ~= nil then
+ assert(#result == #last)
+ for i, v in ipairs(result) do
+ if type(v) == 'string' or type(v) == 'number' then
+ assert(last[i] == v)
+ elseif type(v) == 'table' then
+ for k, vv in pairs(v) do
+ assert(last[i][k] == vv)
+ end
+ end
+ end
+ end
+
+ last = result
+ end
+
+ describe('list like table', function()
+ describe('simple map', function()
+ reset()
+
+ it('vim.iter', function()
+ local function f(t)
+ return vim
+ .iter(t)
+ :map(function(v)
+ return v * 2
+ end)
+ :totable()
+ end
+ measure(f)
+ end)
+
+ it('for loop', function()
+ local function f(t)
+ local res = {}
+ for i = 1, #t do
+ res[#res + 1] = t[i] * 2
+ end
+ return res
+ end
+ measure(f)
+ end)
+ end)
+
+ describe('filter, map, skip, reverse', function()
+ reset()
+
+ it('vim.iter', function()
+ local function f(t)
+ local i = 0
+ return vim
+ .iter(t)
+ :map(function(v)
+ i = i + 1
+ if i % 2 == 0 then
+ return v * 2
+ end
+ end)
+ :skip(1000)
+ :rev()
+ :totable()
+ end
+ measure(f)
+ end)
+
+ it('tables', function()
+ local function f(t)
+ local a = {}
+ for i = 1, #t do
+ if i % 2 == 0 then
+ a[#a + 1] = t[i] * 2
+ end
+ end
+
+ local b = {}
+ for i = 1001, #a do
+ b[#b + 1] = a[i]
+ end
+
+ local c = {}
+ for i = 1, #b do
+ c[#c + 1] = b[#b - i + 1]
+ end
+ return c
+ end
+ measure(f)
+ end)
+ end)
+ end)
+
+ describe('iterator', function()
+ describe('simple map', function()
+ reset()
+ it('vim.iter', function()
+ local function f(t)
+ return vim
+ .iter(ipairs(t))
+ :map(function(i, v)
+ return i + v
+ end)
+ :totable()
+ end
+ measure(f)
+ end)
+
+ it('ipairs', function()
+ local function f(t)
+ local res = {}
+ for i, v in ipairs(t) do
+ res[#res + 1] = i + v
+ end
+ return res
+ end
+ measure(f)
+ end)
+ end)
+
+ describe('multiple stages', function()
+ reset()
+ it('vim.iter', function()
+ local function f(t)
+ return vim
+ .iter(ipairs(t))
+ :map(function(i, v)
+ if i % 2 ~= 0 then
+ return v
+ end
+ end)
+ :map(function(v)
+ return v * 3
+ end)
+ :skip(50)
+ :totable()
+ end
+ measure(f)
+ end)
+
+ it('ipairs', function()
+ local function f(t)
+ local a = {}
+ for i, v in ipairs(t) do
+ if i % 2 ~= 0 then
+ a[#a + 1] = v
+ end
+ end
+ local b = {}
+ for _, v in ipairs(a) do
+ b[#b + 1] = v * 3
+ end
+ local c = {}
+ for i, v in ipairs(b) do
+ if i > 50 then
+ c[#c + 1] = v
+ end
+ end
+ return c
+ end
+ measure(f)
+ end)
+ end)
+ end)
+end)
diff --git a/test/benchmark/treesitter_spec.lua b/test/benchmark/treesitter_spec.lua
index 5ce128c54a..6d82f5de8d 100644
--- a/test/benchmark/treesitter_spec.lua
+++ b/test/benchmark/treesitter_spec.lua
@@ -37,7 +37,7 @@ describe('treesitter perf', function()
return "qq" .. acc .. "q"
end
- local start = vim.loop.hrtime()
+ local start = vim.uv.hrtime()
keys(mk_keys(10))
for _ = 1, 100 do
@@ -45,7 +45,7 @@ describe('treesitter perf', function()
vim.cmd'redraw!'
end
- return vim.loop.hrtime() - start
+ return vim.uv.hrtime() - start
]]
end)
diff --git a/test/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua
index 2ce32c3b7a..4bfa21fe49 100644
--- a/test/busted/outputHandlers/nvim.lua
+++ b/test/busted/outputHandlers/nvim.lua
@@ -1,9 +1,15 @@
local pretty = require 'pl.pretty'
local global_helpers = require('test.helpers')
--- Colors are disabled by default. #15610
local colors = setmetatable({}, {__index = function() return function(s) return s == nil and '' or tostring(s) end end})
+
+local enable_colors = true
if os.getenv "TEST_COLORS" then
+ local test_colors = os.getenv("TEST_COLORS"):lower()
+ local disable_colors = test_colors == 'false' or test_colors == '0' or test_colors == 'no' or test_colors == 'off'
+ enable_colors = not disable_colors
+end
+if enable_colors then
colors = require 'term.colors'
end
@@ -204,7 +210,7 @@ return function(options)
handler.fileStart = function(file)
fileTestCount = 0
- io.write(fileStartString:format(file.name))
+ io.write(fileStartString:format(vim.fs.normalize(file.name)))
io.flush()
return nil, true
end
@@ -213,7 +219,7 @@ return function(options)
local elapsedTime_ms = getElapsedTime(file)
local tests = (fileTestCount == 1 and 'test' or 'tests')
fileCount = fileCount + 1
- io.write(fileEndString:format(fileTestCount, tests, file.name, elapsedTime_ms))
+ io.write(fileEndString:format(fileTestCount, tests, vim.fs.normalize(file.name), elapsedTime_ms))
io.flush()
return nil, true
end
diff --git a/test/busted_runner.lua b/test/busted_runner.lua
deleted file mode 100644
index b195ce3cc5..0000000000
--- a/test/busted_runner.lua
+++ /dev/null
@@ -1 +0,0 @@
-require 'busted.runner'({ standalone = false })
diff --git a/test/client/msgpack_rpc_stream.lua b/test/client/msgpack_rpc_stream.lua
new file mode 100644
index 0000000000..5711616b17
--- /dev/null
+++ b/test/client/msgpack_rpc_stream.lua
@@ -0,0 +1,112 @@
+local mpack = require('mpack')
+
+-- temporary hack to be able to manipulate buffer/window/tabpage
+local Buffer = {}
+Buffer.__index = Buffer
+function Buffer.new(id) return setmetatable({id=id}, Buffer) end
+local Window = {}
+Window.__index = Window
+function Window.new(id) return setmetatable({id=id}, Window) end
+local Tabpage = {}
+Tabpage.__index = Tabpage
+function Tabpage.new(id) return setmetatable({id=id}, Tabpage) end
+
+local Response = {}
+Response.__index = Response
+
+function Response.new(msgpack_rpc_stream, request_id)
+ return setmetatable({
+ _msgpack_rpc_stream = msgpack_rpc_stream,
+ _request_id = request_id
+ }, Response)
+end
+
+function Response:send(value, is_error)
+ local data = self._msgpack_rpc_stream._session:reply(self._request_id)
+ if is_error then
+ data = data .. self._msgpack_rpc_stream._pack(value)
+ data = data .. self._msgpack_rpc_stream._pack(mpack.NIL)
+ else
+ data = data .. self._msgpack_rpc_stream._pack(mpack.NIL)
+ data = data .. self._msgpack_rpc_stream._pack(value)
+ end
+ self._msgpack_rpc_stream._stream:write(data)
+end
+
+local MsgpackRpcStream = {}
+MsgpackRpcStream.__index = MsgpackRpcStream
+
+function MsgpackRpcStream.new(stream)
+ return setmetatable({
+ _stream = stream,
+ _pack = mpack.Packer({
+ ext = {
+ [Buffer] = function(o) return 0, mpack.encode(o.id) end,
+ [Window] = function(o) return 1, mpack.encode(o.id) end,
+ [Tabpage] = function(o) return 2, mpack.encode(o.id) end
+ }
+ }),
+ _session = mpack.Session({
+ unpack = mpack.Unpacker({
+ ext = {
+ [0] = function(_c, s) return Buffer.new(mpack.decode(s)) end,
+ [1] = function(_c, s) return Window.new(mpack.decode(s)) end,
+ [2] = function(_c, s) return Tabpage.new(mpack.decode(s)) end
+ }
+ })
+ }),
+ }, MsgpackRpcStream)
+end
+
+function MsgpackRpcStream:write(method, args, response_cb)
+ local data
+ if response_cb then
+ assert(type(response_cb) == 'function')
+ data = self._session:request(response_cb)
+ else
+ data = self._session:notify()
+ end
+
+ data = data .. self._pack(method) .. self._pack(args)
+ self._stream:write(data)
+end
+
+function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb)
+ self._stream:read_start(function(data)
+ if not data then
+ return eof_cb()
+ end
+ local type, id_or_cb, method_or_error, args_or_result
+ local pos = 1
+ local len = #data
+ while pos <= len do
+ type, id_or_cb, method_or_error, args_or_result, pos =
+ self._session:receive(data, pos)
+ if type == 'request' or type == 'notification' then
+ if type == 'request' then
+ request_cb(method_or_error, args_or_result, Response.new(self,
+ id_or_cb))
+ else
+ notification_cb(method_or_error, args_or_result)
+ end
+ elseif type == 'response' then
+ if method_or_error == mpack.NIL then
+ method_or_error = nil
+ else
+ args_or_result = nil
+ end
+ id_or_cb(method_or_error, args_or_result)
+ end
+ end
+ end)
+end
+
+function MsgpackRpcStream:read_stop()
+ self._stream:read_stop()
+end
+
+function MsgpackRpcStream:close(signal)
+ self._stream:close(signal)
+end
+
+return MsgpackRpcStream
diff --git a/test/client/session.lua b/test/client/session.lua
new file mode 100644
index 0000000000..b1bf5ea75e
--- /dev/null
+++ b/test/client/session.lua
@@ -0,0 +1,198 @@
+local uv = require('luv')
+local MsgpackRpcStream = require('test.client.msgpack_rpc_stream')
+
+local Session = {}
+Session.__index = Session
+if package.loaded['jit'] then
+ -- luajit pcall is already coroutine safe
+ Session.safe_pcall = pcall
+else
+ Session.safe_pcall = require'coxpcall'.pcall
+end
+
+local function resume(co, ...)
+ local status, result = coroutine.resume(co, ...)
+
+ if coroutine.status(co) == 'dead' then
+ if not status then
+ error(result)
+ end
+ return
+ end
+
+ assert(coroutine.status(co) == 'suspended')
+ result(co)
+end
+
+local function coroutine_exec(func, ...)
+ local args = {...}
+ local on_complete
+
+ if #args > 0 and type(args[#args]) == 'function' then
+ -- completion callback
+ on_complete = table.remove(args)
+ end
+
+ resume(coroutine.create(function()
+ local status, result, flag = Session.safe_pcall(func, unpack(args))
+ if on_complete then
+ coroutine.yield(function()
+ -- run the completion callback on the main thread
+ on_complete(status, result, flag)
+ end)
+ end
+ end))
+end
+
+function Session.new(stream)
+ return setmetatable({
+ _msgpack_rpc_stream = MsgpackRpcStream.new(stream),
+ _pending_messages = {},
+ _prepare = uv.new_prepare(),
+ _timer = uv.new_timer(),
+ _is_running = false
+ }, Session)
+end
+
+function Session:next_message(timeout)
+ local function on_request(method, args, response)
+ table.insert(self._pending_messages, {'request', method, args, response})
+ uv.stop()
+ end
+
+ local function on_notification(method, args)
+ table.insert(self._pending_messages, {'notification', method, args})
+ uv.stop()
+ end
+
+ if self._is_running then
+ error('Event loop already running')
+ end
+
+ if #self._pending_messages > 0 then
+ return table.remove(self._pending_messages, 1)
+ end
+
+ -- if closed, only return pending messages
+ if self.closed then
+ return nil
+ end
+
+ self:_run(on_request, on_notification, timeout)
+ return table.remove(self._pending_messages, 1)
+end
+
+function Session:notify(method, ...)
+ self._msgpack_rpc_stream:write(method, {...})
+end
+
+function Session:request(method, ...)
+ local args = {...}
+ local err, result
+ if self._is_running then
+ err, result = self:_yielding_request(method, args)
+ else
+ err, result = self:_blocking_request(method, args)
+ end
+
+ if err then
+ return false, err
+ end
+
+ return true, result
+end
+
+function Session:run(request_cb, notification_cb, setup_cb, timeout)
+ local function on_request(method, args, response)
+ coroutine_exec(request_cb, method, args, function(status, result, flag)
+ if status then
+ response:send(result, flag)
+ else
+ response:send(result, true)
+ end
+ end)
+ end
+
+ local function on_notification(method, args)
+ coroutine_exec(notification_cb, method, args)
+ end
+
+ self._is_running = true
+
+ if setup_cb then
+ coroutine_exec(setup_cb)
+ end
+
+ while #self._pending_messages > 0 do
+ local msg = table.remove(self._pending_messages, 1)
+ if msg[1] == 'request' then
+ on_request(msg[2], msg[3], msg[4])
+ else
+ on_notification(msg[2], msg[3])
+ end
+ end
+
+ self:_run(on_request, on_notification, timeout)
+ self._is_running = false
+end
+
+function Session:stop()
+ uv.stop()
+end
+
+function Session:close(signal)
+ if not self._timer:is_closing() then self._timer:close() end
+ if not self._prepare:is_closing() then self._prepare:close() end
+ self._msgpack_rpc_stream:close(signal)
+ self.closed = true
+end
+
+function Session:_yielding_request(method, args)
+ return coroutine.yield(function(co)
+ self._msgpack_rpc_stream:write(method, args, function(err, result)
+ resume(co, err, result)
+ end)
+ end)
+end
+
+function Session:_blocking_request(method, args)
+ local err, result
+
+ local function on_request(method_, args_, response)
+ table.insert(self._pending_messages, {'request', method_, args_, response})
+ end
+
+ local function on_notification(method_, args_)
+ table.insert(self._pending_messages, {'notification', method_, args_})
+ end
+
+ self._msgpack_rpc_stream:write(method, args, function(e, r)
+ err = e
+ result = r
+ uv.stop()
+ end)
+
+ self:_run(on_request, on_notification)
+ return (err or self.eof_err), result
+end
+
+function Session:_run(request_cb, notification_cb, timeout)
+ if type(timeout) == 'number' then
+ self._prepare:start(function()
+ self._timer:start(timeout, 0, function()
+ uv.stop()
+ end)
+ self._prepare:stop()
+ end)
+ end
+ self._msgpack_rpc_stream:read_start(request_cb, notification_cb, function()
+ uv.stop()
+ self.eof_err = {1, "EOF was received from Nvim. Likely the Nvim process crashed."}
+ end)
+ uv.run()
+ self._prepare:stop()
+ self._timer:stop()
+ self._msgpack_rpc_stream:read_stop()
+end
+
+return Session
diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua
new file mode 100644
index 0000000000..cea77f0dbd
--- /dev/null
+++ b/test/client/uv_stream.lua
@@ -0,0 +1,169 @@
+local uv = require('luv')
+
+local StdioStream = {}
+StdioStream.__index = StdioStream
+
+function StdioStream.open()
+ local self = setmetatable({
+ _in = uv.new_pipe(false),
+ _out = uv.new_pipe(false)
+ }, StdioStream)
+ self._in:open(0)
+ self._out:open(1)
+ return self
+end
+
+function StdioStream:write(data)
+ self._out:write(data)
+end
+
+function StdioStream:read_start(cb)
+ self._in:read_start(function(err, chunk)
+ if err then
+ error(err)
+ end
+ cb(chunk)
+ end)
+end
+
+function StdioStream:read_stop()
+ self._in:read_stop()
+end
+
+function StdioStream:close()
+ self._in:close()
+ self._out:close()
+end
+
+local SocketStream = {}
+SocketStream.__index = SocketStream
+
+function SocketStream.open(file)
+ local socket = uv.new_pipe(false)
+ local self = setmetatable({
+ _socket = socket,
+ _stream_error = nil
+ }, SocketStream)
+ uv.pipe_connect(socket, file, function (err)
+ self._stream_error = self._stream_error or err
+ end)
+ return self
+end
+
+function SocketStream.connect(host, port)
+ local socket = uv.new_tcp()
+ local self = setmetatable({
+ _socket = socket,
+ _stream_error = nil
+ }, SocketStream)
+ uv.tcp_connect(socket, host, port, function (err)
+ self._stream_error = self._stream_error or err
+ end)
+ return self
+end
+
+
+function SocketStream:write(data)
+ if self._stream_error then
+ error(self._stream_error)
+ end
+ uv.write(self._socket, data, function(err)
+ if err then
+ error(self._stream_error or err)
+ end
+ end)
+end
+
+function SocketStream:read_start(cb)
+ if self._stream_error then
+ error(self._stream_error)
+ end
+ uv.read_start(self._socket, function(err, chunk)
+ if err then
+ error(err)
+ end
+ cb(chunk)
+ end)
+end
+
+function SocketStream:read_stop()
+ if self._stream_error then
+ error(self._stream_error)
+ end
+ uv.read_stop(self._socket)
+end
+
+function SocketStream:close()
+ uv.close(self._socket)
+end
+
+local ChildProcessStream = {}
+ChildProcessStream.__index = ChildProcessStream
+
+function ChildProcessStream.spawn(argv, env, io_extra)
+ local self = setmetatable({
+ _child_stdin = uv.new_pipe(false);
+ _child_stdout = uv.new_pipe(false);
+ _exiting = false;
+ }, ChildProcessStream)
+ local prog = argv[1]
+ local args = {}
+ for i = 2, #argv do
+ args[#args + 1] = argv[i]
+ end
+ self._proc, self._pid = uv.spawn(prog, {
+ stdio = {self._child_stdin, self._child_stdout, 2, io_extra},
+ args = args,
+ env = env,
+ }, function(status, signal)
+ self.status = status
+ self.signal = signal
+ end)
+
+ if not self._proc then
+ local err = self._pid
+ error(err)
+ end
+
+ return self
+end
+
+function ChildProcessStream:write(data)
+ self._child_stdin:write(data)
+end
+
+function ChildProcessStream:read_start(cb)
+ self._child_stdout:read_start(function(err, chunk)
+ if err then
+ error(err)
+ end
+ cb(chunk)
+ end)
+end
+
+function ChildProcessStream:read_stop()
+ self._child_stdout:read_stop()
+end
+
+function ChildProcessStream:close(signal)
+ if self._closed then
+ return
+ end
+ self._closed = true
+ self:read_stop()
+ self._child_stdin:close()
+ self._child_stdout:close()
+ if type(signal) == 'string' then
+ self._proc:kill('sig'..signal)
+ end
+ while self.status == nil do
+ uv.run 'once'
+ end
+ return self.status, self.signal
+end
+
+return {
+ StdioStream = StdioStream;
+ ChildProcessStream = ChildProcessStream;
+ SocketStream = SocketStream;
+}
diff --git a/test/compat.lua b/test/compat.lua
deleted file mode 100644
index 2c9786d491..0000000000
--- a/test/compat.lua
+++ /dev/null
@@ -1,12 +0,0 @@
--- Lua 5.1 forward-compatibility layer.
--- For background see https://github.com/neovim/neovim/pull/9280
---
--- Reference the lua-compat-5.2 project for hints:
--- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/compat52/mstrict.lua
--- https://github.com/keplerproject/lua-compat-5.2/blob/c164c8f339b95451b572d6b4b4d11e944dc7169d/tests/test.lua
-
-local lua_version = _VERSION:sub(-3)
-
-if lua_version >= '5.2' then
- unpack = table.unpack -- luacheck: ignore 121 143
-end
diff --git a/test/deprecated.lua b/test/deprecated.lua
index b162c8fc93..e30dfcf3ab 100644
--- a/test/deprecated.lua
+++ b/test/deprecated.lua
@@ -3,7 +3,7 @@
local M = {}
function M.redir_exec()
- error('redir_exec is deprecated, use nvim_exec() or pcall_err()')
+ error('redir_exec is deprecated, use nvim_exec2() or pcall_err()')
end
return M
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua
index 22a1311ee9..fd46a1dcfa 100644
--- a/test/functional/api/autocmd_spec.lua
+++ b/test/functional/api/autocmd_spec.lua
@@ -14,14 +14,40 @@ before_each(clear)
describe('autocmd api', function()
describe('nvim_create_autocmd', function()
- it('"command" and "callback" are mutually exclusive', function()
- local rv = pcall_err(meths.create_autocmd, "BufReadPost", {
- pattern = "*.py,*.pyi",
+ it('validation', function()
+ eq("Cannot use both 'callback' and 'command'", pcall_err(meths.create_autocmd, 'BufReadPost', {
+ pattern = '*.py,*.pyi',
command = "echo 'Should Have Errored",
- callback = "NotAllowed",
- })
-
- eq("specify either 'callback' or 'command', not both", rv)
+ callback = 'NotAllowed',
+ }))
+ eq("Cannot use both 'pattern' and 'buffer' for the same autocmd", pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'let g:called = g:called + 1',
+ buffer = 0,
+ pattern = '*.py',
+ }))
+ eq("Required: 'event'", pcall_err(meths.create_autocmd, {}, {
+ command = 'ls',
+ }))
+ eq("Required: 'command' or 'callback'", pcall_err(meths.create_autocmd, 'FileType', {
+ }))
+ eq("Invalid 'desc': expected String, got Integer", pcall_err(meths.create_autocmd, 'FileType', {
+ command = 'ls',
+ desc = 42,
+ }))
+ eq("Invalid 'callback': expected Lua function or Vim function name, got Integer", pcall_err(meths.create_autocmd, 'FileType', {
+ callback = 0,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.create_autocmd,
+ {'FileType', {}}, {}))
+ eq("Invalid 'group': 0", pcall_err(meths.create_autocmd, 'FileType', {
+ group = 0,
+ command = 'ls',
+ }))
+
+ eq("Invalid 'event': 'foo'", pcall_err(meths.create_autocmd, 'foo', { command = '' }))
+ eq("Invalid 'event': 'VimEnter '", pcall_err(meths.create_autocmd, 'VimEnter ', { command = '' }))
+ eq("Invalid 'event': 'VimEnter foo'", pcall_err(meths.create_autocmd, 'VimEnter foo', { command = '' }))
+ eq("Invalid 'event': 'BufAdd,BufDelete'", pcall_err(meths.create_autocmd, 'BufAdd,BufDelete', { command = '' }))
end)
it('doesnt leak when you use ++once', function()
@@ -59,18 +85,8 @@ describe('autocmd api', function()
eq(1, meths.get_var('called'))
end)
- it('does not allow passing buffer and patterns', function()
- local rv = pcall_err(meths.create_autocmd, "Filetype", {
- command = "let g:called = g:called + 1",
- buffer = 0,
- pattern = "*.py",
- })
-
- eq("cannot pass both: 'pattern' and 'buffer' for the same autocmd", rv)
- end)
-
it('does not allow passing invalid buffers', function()
- local ok, msg = pcall(meths.create_autocmd, "Filetype", {
+ local ok, msg = pcall(meths.create_autocmd, 'FileType', {
command = "let g:called = g:called + 1",
buffer = -1,
})
@@ -209,7 +225,7 @@ describe('autocmd api', function()
local aus = meths.get_autocmds({ event = 'User', pattern = 'Test' })
local first = aus[1]
- eq(first.id, 1)
+ eq(true, first.id > 0)
meths.set_var("some_condition", true)
meths.exec_autocmds("User", {pattern = "Test"})
@@ -217,23 +233,28 @@ describe('autocmd api', function()
end)
it('receives an args table', function()
- local res = exec_lua [[
- local group_id = vim.api.nvim_create_augroup("TestGroup", {})
- local autocmd_id = vim.api.nvim_create_autocmd("User", {
+ local group_id = meths.create_augroup("TestGroup", {})
+ -- Having an existing autocmd calling expand("<afile>") shouldn't change args #18964
+ meths.create_autocmd('User', {
+ group = 'TestGroup',
+ pattern = 'Te*',
+ command = 'call expand("<afile>")',
+ })
+
+ local autocmd_id = exec_lua [[
+ return vim.api.nvim_create_autocmd("User", {
group = "TestGroup",
pattern = "Te*",
callback = function(args)
vim.g.autocmd_args = args
end,
})
-
- return {group_id, autocmd_id}
]]
meths.exec_autocmds("User", {pattern = "Test pattern"})
eq({
- id = res[2],
- group = res[1],
+ id = autocmd_id,
+ group = group_id,
event = "User",
match = "Test pattern",
file = "Test pattern",
@@ -241,27 +262,24 @@ describe('autocmd api', function()
}, meths.get_var("autocmd_args"))
-- Test without a group
- res = exec_lua [[
- local autocmd_id = vim.api.nvim_create_autocmd("User", {
+ autocmd_id = exec_lua [[
+ return vim.api.nvim_create_autocmd("User", {
pattern = "*",
callback = function(args)
vim.g.autocmd_args = args
end,
})
-
- return {autocmd_id}
]]
meths.exec_autocmds("User", {pattern = "some_pat"})
eq({
- id = res[1],
+ id = autocmd_id,
group = nil,
event = "User",
match = "some_pat",
file = "some_pat",
buf = 1,
}, meths.get_var("autocmd_args"))
-
end)
it('can receive arbitrary data', function()
@@ -294,8 +312,32 @@ describe('autocmd api', function()
end)
describe('nvim_get_autocmds', function()
+ it('validation', function()
+ eq("Invalid 'group': 9997999", pcall_err(meths.get_autocmds, {
+ group = 9997999,
+ }))
+ eq("Invalid 'group': 'bogus'", pcall_err(meths.get_autocmds, {
+ group = 'bogus',
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.get_autocmds, {
+ group = 0,
+ }))
+ eq("Invalid 'group': expected String or Integer, got Array", pcall_err(meths.get_autocmds, {
+ group = {},
+ }))
+ eq("Invalid 'buffer': expected Integer or Array, got Boolean", pcall_err(meths.get_autocmds, {
+ buffer = true,
+ }))
+ eq("Invalid 'event': expected String or Array", pcall_err(meths.get_autocmds, {
+ event = true,
+ }))
+ eq("Invalid 'pattern': expected String or Array, got Boolean", pcall_err(meths.get_autocmds, {
+ pattern = true,
+ }))
+ end)
+
describe('events', function()
- it('should return one autocmd when there is only one for an event', function()
+ it('returns one autocmd when there is only one for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -303,7 +345,7 @@ describe('autocmd api', function()
eq(1, #aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -312,7 +354,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return the same thing if you use string or list', function()
+ it('returns the same thing if you use string or list', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
command [[au InsertEnter * :echo "2"]]
@@ -322,7 +364,7 @@ describe('autocmd api', function()
eq(string_aus, array_aus)
end)
- it('should return two autocmds when there are two for an event', function()
+ it('returns two autocmds when there are two for an event', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -332,7 +374,7 @@ describe('autocmd api', function()
eq(2, #aus)
end)
- it('should return different IDs for different autocmds', function()
+ it('returns different IDs for different autocmds', function()
command [[au! InsertEnter]]
command [[au! InsertLeave]]
command [[au InsertEnter * :echo "1"]]
@@ -356,7 +398,7 @@ describe('autocmd api', function()
eq(first, new_aus[1])
end)
- it('should return event name', function()
+ it('returns event name', function()
command [[au! InsertEnter]]
command [[au InsertEnter * :echo "1"]]
@@ -364,7 +406,7 @@ describe('autocmd api', function()
eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus)
end)
- it('should work with buffer numbers', function()
+ it('works with buffer numbers', function()
command [[new]]
command [[au! InsertEnter]]
command [[au InsertEnter <buffer=1> :echo "1"]]
@@ -407,8 +449,8 @@ describe('autocmd api', function()
pattern = "<buffer=2>",
}}, aus)
- eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
- eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
+ eq("Invalid 'buffer': expected Integer or Array, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" }))
+ eq("Invalid 'buffer': expected Integer, got String", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } }))
eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } }))
local bufs = {}
@@ -416,10 +458,10 @@ describe('autocmd api', function()
table.insert(bufs, meths.create_buf(true, false))
end
- eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
+ eq("Too many buffers (maximum of 256)", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs }))
end)
- it('should return autocmds when group is specified by id', function()
+ it('returns autocmds when group is specified by id', function()
local auid = meths.create_augroup("nvim_test_augroup", { clear = true })
meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' })
meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' })
@@ -431,7 +473,7 @@ describe('autocmd api', function()
eq(0, #aus2)
end)
- it('should return autocmds when group is specified by name', function()
+ it('returns autocmds when group is specified by name', function()
local auname = "nvim_test_augroup"
meths.create_augroup(auname, { clear = true })
meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' })
@@ -531,7 +573,7 @@ describe('autocmd api', function()
command [[augroup END]]
end)
- it('should return all groups if no group is specified', function()
+ it('returns all groups if no group is specified', function()
local aus = meths.get_autocmds { event = "InsertEnter" }
if #aus ~= 4 then
eq({}, aus)
@@ -540,7 +582,7 @@ describe('autocmd api', function()
eq(4, #aus)
end)
- it('should return only the group specified', function()
+ it('returns only the group specified', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupOne",
@@ -551,7 +593,7 @@ describe('autocmd api', function()
eq("GroupOne", aus[1].group_name)
end)
- it('should return only the group specified, multiple values', function()
+ it('returns only the group specified, multiple values', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
group = "GroupTwo",
@@ -578,7 +620,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: NotDefined', code)
+ matches("Invalid 'group': 'NotDefined'", code)
end)
it('raises error for undefined augroup id', function()
@@ -596,7 +638,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches('invalid augroup: 1', code)
+ matches("Invalid 'group': 1", code)
end)
it('raises error for invalid group type', function()
@@ -611,7 +653,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("'group' must be a string or an integer", code)
+ matches("Invalid 'group': expected String or Integer, got Boolean", code)
end)
it('raises error for invalid pattern array', function()
@@ -625,7 +667,7 @@ describe('autocmd api', function()
]], {}))
eq(false, success)
- matches("All entries in 'pattern' must be strings", code)
+ matches("Invalid 'pattern' item: expected String, got Array", code)
end)
end)
@@ -640,7 +682,7 @@ describe('autocmd api', function()
command [[au InsertEnter <buffer> :echo "Buffer"]]
end)
- it('should should return for literal match', function()
+ it('returns for literal match', function()
local aus = meths.get_autocmds {
event = "InsertEnter",
pattern = "*"
@@ -650,7 +692,7 @@ describe('autocmd api', function()
eq([[:echo "No Group"]], aus[1].command)
end)
- it('should return for multiple matches', function()
+ it('returns for multiple matches', function()
-- vim.api.nvim_get_autocmds
local aus = meths.get_autocmds {
event = "InsertEnter",
@@ -687,6 +729,26 @@ describe('autocmd api', function()
end)
describe('nvim_exec_autocmds', function()
+ it('validation', function()
+ eq("Invalid 'group': 9997999", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 9997999,
+ }))
+ eq("Invalid 'group': 'bogus'", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 'bogus',
+ }))
+ eq("Invalid 'group': expected String or Integer, got Array", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = {},
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.exec_autocmds, 'FileType', {
+ group = 0,
+ }))
+ eq("Invalid 'buffer': expected Buffer, got Array", pcall_err(meths.exec_autocmds, 'FileType', {
+ buffer = {},
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.exec_autocmds,
+ {'FileType', {}}, {}))
+ end)
+
it("can trigger builtin autocmds", function()
meths.set_var("autocmd_executed", false)
@@ -1004,6 +1066,12 @@ describe('autocmd api', function()
eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, -12342)]])
eq('Vim:E367: No such group: "--Deleted--"', pcall_err(meths.del_augroup_by_id, -12312))
+
+ eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, 0)]])
+ eq('Vim:E367: No such group: "[NULL]"', pcall_err(meths.del_augroup_by_id, 0))
+
+ eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, 12342)]])
+ eq('Vim:E367: No such group: "[NULL]"', pcall_err(meths.del_augroup_by_id, 12312))
end)
it('groups work with once', function()
@@ -1036,7 +1104,7 @@ describe('autocmd api', function()
local augroup = "WillBeDeleted"
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd({"Filetype"}, {
+ meths.create_autocmd({"FileType"}, {
pattern = "*",
command = "echo 'does not matter'",
})
@@ -1055,7 +1123,7 @@ describe('autocmd api', function()
meths.set_var("value_set", false)
meths.create_augroup(augroup, { clear = true })
- meths.create_autocmd("Filetype", {
+ meths.create_autocmd("FileType", {
pattern = "<buffer>",
command = "let g:value_set = v:true",
})
@@ -1171,6 +1239,17 @@ describe('autocmd api', function()
end)
describe('nvim_clear_autocmds', function()
+ it('validation', function()
+ eq("Cannot use both 'pattern' and 'buffer'", pcall_err(meths.clear_autocmds, {
+ pattern = '*',
+ buffer = 42,
+ }))
+ eq("Invalid 'event' item: expected String, got Array", pcall_err(meths.clear_autocmds, {
+ event = {'FileType', {}}
+ }))
+ eq("Invalid 'group': 0", pcall_err(meths.clear_autocmds, {group = 0}))
+ end)
+
it('should clear based on event + pattern', function()
command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"')
command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"')
@@ -1254,7 +1333,7 @@ describe('autocmd api', function()
local without_group = meths.get_autocmds(search)
eq(2, #without_group)
- -- Doest clear with passing group.
+ -- Doesn't clear with passing group.
meths.clear_autocmds { buffer = 0, group = search.group }
local with_group = meths.get_autocmds(search)
eq(1, #with_group)
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua
index 6b13729994..6ed9aa574a 100644
--- a/test/functional/api/buffer_spec.lua
+++ b/test/functional/api/buffer_spec.lua
@@ -16,6 +16,7 @@ local command = helpers.command
local bufmeths = helpers.bufmeths
local feed = helpers.feed
local pcall_err = helpers.pcall_err
+local assert_alive = helpers.assert_alive
describe('api/buf', function()
before_each(clear)
@@ -41,6 +42,14 @@ describe('api/buf', function()
eq(1, curbuf_depr('line_count'))
end)
+ it("doesn't crash just after set undolevels=1 #24894", function()
+ local buf = meths.create_buf(false, true)
+ meths.buf_set_option(buf, 'undolevels', -1)
+ meths.buf_set_lines(buf, 0, 1, false, { })
+
+ assert_alive()
+ end)
+
it('cursor position is maintained after lines are inserted #9961', function()
-- replace the buffer contents with these three lines.
request('nvim_buf_set_lines', 0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
@@ -76,6 +85,38 @@ describe('api/buf', function()
eq({4, 2}, curwin('get_cursor'))
end)
+ it('cursor position is maintained in non-current window', function()
+ meths.buf_set_lines(0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
+ meths.win_set_cursor(0, {3, 2})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+
+ meths.buf_set_lines(buf, 1, 2, 1, {"line5", "line6"})
+ eq({"line1", "line5", "line6", "line3", "line4"}, meths.buf_get_lines(buf, 0, -1, true))
+ eq({4, 2}, meths.win_get_cursor(win))
+ end)
+
+ it('cursor position is maintained in TWO non-current windows', function()
+ meths.buf_set_lines(0, 0, -1, 1, {"line1", "line2", "line3", "line4"})
+ meths.win_set_cursor(0, {3, 2})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(0, {4, 2})
+ local win2 = meths.get_current_win()
+
+ -- set current window to third one with another buffer
+ command("new")
+
+ meths.buf_set_lines(buf, 1, 2, 1, {"line5", "line6"})
+ eq({"line1", "line5", "line6", "line3", "line4"}, meths.buf_get_lines(buf, 0, -1, true))
+ eq({4, 2}, meths.win_get_cursor(win))
+ eq({5, 2}, meths.win_get_cursor(win2))
+ end)
+
it('line_count has defined behaviour for unloaded buffers', function()
-- we'll need to know our bufnr for when it gets unloaded
local bufnr = curbuf('get_number')
@@ -105,6 +146,285 @@ describe('api/buf', function()
-- it's impossible to get out-of-bounds errors for an unloaded buffer
eq({}, buffer('get_lines', bufnr, 8888, 9999, 1))
end)
+
+ describe('handles topline', function()
+ local screen
+ before_each(function()
+ screen = Screen.new(20, 12)
+ screen:set_default_attr_ids {
+ [1] = {bold = true, foreground = Screen.colors.Blue1};
+ [2] = {reverse = true, bold = true};
+ [3] = {reverse = true};
+ }
+ screen:attach()
+ meths.buf_set_lines(0, 0, -1, 1, {"aaa", "bbb", "ccc", "ddd", "www", "xxx", "yyy", "zzz"})
+ meths.set_option_value('modified', false, {})
+ end)
+
+ it('of current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new | wincmd w')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline does not scroll up if cursor would be moved
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]], unchanged=true}
+
+ meths.win_set_cursor(0, {7, 0})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ wwweeee |
+ xxx |
+ ^yyy |
+ zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 4, 4, true, {"mmmeeeee"})
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ mmmeeeee |
+ wwweeee |
+ xxx |
+ ^yyy |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of non-current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ wwweeee |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline scrolls up
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ mmm |
+ wwweeee |
+ xxx |
+ yyy |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of split windows with same buffer', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(win, {8,0})
+ meths.win_set_cursor(0, {1,0})
+
+ screen:expect{grid=[[
+ ^aaa |
+ bbb |
+ ccc |
+ ddd |
+ www |
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_lines(buf, 0, 2, true, {"aaabbb"})
+
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ www |
+ xxx |
+ {2:[No Name] [+] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- replacing topline keeps it the topline
+ meths.buf_set_lines(buf, 3, 4, true, {"wwweeee"})
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ wwweeee |
+ xxx |
+ {2:[No Name] [+] }|
+ wwweeee |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+
+ -- inserting just before topline scrolls up
+ meths.buf_set_lines(buf, 3, 3, true, {"mmm"})
+ screen:expect{grid=[[
+ ^aaabbb |
+ ccc |
+ ddd |
+ mmm |
+ wwweeee |
+ {2:[No Name] [+] }|
+ mmm |
+ wwweeee |
+ xxx |
+ yyy |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+ end)
+
+ it('handles clearing out non-current buffer #24911', function()
+ local buf = meths.get_current_buf()
+ meths.buf_set_lines(buf, 0, -1, true, {"aaa", "bbb", "ccc"})
+ command("new")
+
+ meths.buf_set_lines(0, 0, -1, true, {"xxx", "yyy", "zzz"})
+
+ meths.buf_set_lines(buf, 0, -1, true, {})
+ eq({"xxx", "yyy", "zzz"}, meths.buf_get_lines(0, 0, -1, true))
+ eq({''}, meths.buf_get_lines(buf, 0, -1, true))
+ end)
end)
describe('deprecated: {get,set,del}_line', function()
@@ -193,7 +513,7 @@ describe('api/buf', function()
it('fails correctly when input is not valid', function()
eq(1, api.curbufmeths.get_number())
- eq([[String cannot contain newlines]],
+ eq([['replacement string' item contains newlines]],
pcall_err(bufmeths.set_lines, 1, 1, 2, false, {'b\na'}))
end)
@@ -393,6 +713,21 @@ describe('api/buf', function()
feed('<c-w>p')
eq(3, funcs.winnr())
end)
+
+ it('set_lines on unloaded buffer #8659 #22670', function()
+ local bufnr = curbuf('get_number')
+ meths.buf_set_lines(bufnr, 0, -1, false, {'a', 'b', 'c'})
+ meths.buf_set_name(bufnr, 'set_lines')
+ finally(function()
+ os.remove('set_lines')
+ end)
+ command('write!')
+ command('new')
+ command('bunload! '..bufnr)
+ local new_bufnr = funcs.bufnr('set_lines', true)
+ meths.buf_set_lines(new_bufnr, 0, -1, false, {})
+ eq({''}, meths.buf_get_lines(new_bufnr, 0, -1, false))
+ end)
end)
describe('nvim_buf_set_text', function()
@@ -437,6 +772,10 @@ describe('api/buf', function()
-- can append to a line
set_text(1, 4, -1, 4, {' and', 'more'})
eq({'goodbye bar', 'text and', 'more'}, get_lines(0, 3, true))
+
+ -- can use negative column numbers
+ set_text(0, -5, 0, -1, {'!'})
+ eq({'goodbye!'}, get_lines(0, 1, true))
end)
it('works with undo', function()
@@ -480,6 +819,754 @@ describe('api/buf', function()
eq({1, 9}, curwin('get_cursor'))
end)
+ it('updates the cursor position in non-current window', function()
+ insert([[
+ hello world!]])
+
+ -- position the cursor on `!`
+ meths.win_set_cursor(0, {1, 11})
+
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command("new")
+
+ -- replace 'world' with 'foo'
+ meths.buf_set_text(buf, 0, 6, 0, 11, {'foo'})
+ eq({'hello foo!'}, meths.buf_get_lines(buf, 0, -1, true))
+ -- cursor should be moved left by two columns (replacement is shorter by 2 chars)
+ eq({1, 9}, meths.win_get_cursor(win))
+ end)
+
+ it('updates the cursor position in TWO non-current windows', function()
+ insert([[
+ hello world!]])
+
+ -- position the cursor on `!`
+ meths.win_set_cursor(0, {1, 11})
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command("split")
+ local win2 = meths.get_current_win()
+ -- position the cursor on `w`
+ meths.win_set_cursor(0, {1, 6})
+
+ command("new")
+
+ -- replace 'hello' with 'foo'
+ meths.buf_set_text(buf, 0, 0, 0, 5, {'foo'})
+ eq({'foo world!'}, meths.buf_get_lines(buf, 0, -1, true))
+
+ -- both cursors should be moved left by two columns (replacement is shorter by 2 chars)
+ eq({1, 9}, meths.win_get_cursor(win))
+ eq({1, 4}, meths.win_get_cursor(win2))
+ end)
+
+ describe('when text is being added right at cursor position #22526', function()
+ it('updates the cursor position in NORMAL mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'c'
+ curwin('set_cursor', {1, 2})
+ -- add 'xxx' before 'c'
+ set_text(0, 2, 0, 2, {'xxx'})
+ eq({'abxxxcd'}, get_lines(0, -1, true))
+ -- cursor should be on 'c'
+ eq({1, 5}, curwin('get_cursor'))
+ end)
+
+ it('updates the cursor position only in non-current window when in INSERT mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'c'
+ curwin('set_cursor', {1, 2})
+ -- open vertical split
+ feed('<c-w>v')
+ -- get into INSERT mode to treat cursor
+ -- as being after 'b', not on 'c'
+ feed('i')
+ -- add 'xxx' between 'b' and 'c'
+ set_text(0, 2, 0, 2, {'xxx'})
+ eq({'abxxxcd'}, get_lines(0, -1, true))
+ -- in the current window cursor should stay after 'b'
+ eq({1, 2}, curwin('get_cursor'))
+ -- quit INSERT mode
+ feed('<esc>')
+ -- close current window
+ feed('<c-w>c')
+ -- in another window cursor should be on 'c'
+ eq({1, 5}, curwin('get_cursor'))
+ end)
+ end)
+
+ describe('when text is being deleted right at cursor position', function()
+ it('leaves cursor at the same position in NORMAL mode', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'b'
+ curwin('set_cursor', {1, 1})
+ -- delete 'b'
+ set_text(0, 1, 0, 2, {})
+ eq({'acd'}, get_lines(0, -1, true))
+ -- cursor is now on 'c'
+ eq({1, 1}, curwin('get_cursor'))
+ end)
+
+ it('leaves cursor at the same position in INSERT mode in current and non-current window', function()
+ insert([[
+ abcd]])
+
+ -- position the cursor on 'b'
+ curwin('set_cursor', {1, 1})
+ -- open vertical split
+ feed('<c-w>v')
+ -- get into INSERT mode to treat cursor
+ -- as being after 'a', not on 'b'
+ feed('i')
+ -- delete 'b'
+ set_text(0, 1, 0, 2, {})
+ eq({'acd'}, get_lines(0, -1, true))
+ -- cursor in the current window should stay after 'a'
+ eq({1, 1}, curwin('get_cursor'))
+ -- quit INSERT mode
+ feed('<esc>')
+ -- close current window
+ feed('<c-w>c')
+ -- cursor in non-current window should stay on 'c'
+ eq({1, 1}, curwin('get_cursor'))
+ end)
+ end)
+
+ describe('when cursor is inside replaced row range', function()
+ it('keeps cursor at the same position if cursor is at start_row, but before start_col', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on ' ' before 'first'
+ curwin('set_cursor', {1, 14})
+
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same position
+ eq({1, 14}, curwin('get_cursor'))
+ end)
+
+ it('keeps cursor at the same position if cursor is at start_row and column is still valid', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'f' in 'first'
+ curwin('set_cursor', {1, 15})
+
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same position
+ eq({1, 15}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column to keep it valid if start_row got smaller', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be last' }, get_lines(0, -1, true))
+ -- cursor should end up on 't' in 'last'
+ eq({1, 18}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 18}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid if start_row got smaller in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- enter INSERT mode to treat cursor as being after 't'
+ feed('a')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 24, {'last'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be last' }, get_lines(0, -1, true))
+ -- cursor should end up after 't' in 'last'
+ eq({1, 19}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 19}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid in a row after start_row if it got smaller', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'w' in 'want'
+ curwin('set_cursor', {2, 31})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ '1',
+ 'then 2',
+ 'and then',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be 1',
+ 'then 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor column should end up at the end of a row
+ eq({2, 5}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 5}, cursor)
+ end)
+
+ it('adjusts cursor column to keep it valid in a row after start_row if it got smaller in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'w' in 'want'
+ curwin('set_cursor', {2, 31})
+ -- enter INSERT mode
+ feed('a')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ '1',
+ 'then 2',
+ 'and then',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be 1',
+ 'then 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor column should end up at the end of a row
+ eq({2, 6}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 6}, cursor)
+ end)
+
+ it('adjusts cursor line and column to keep it inside replacement range', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'n' in 'finally'
+ curwin('set_cursor', {3, 6})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'y' in 'hopefully'
+ -- to stay in the range, because it got smaller
+ eq({2, 12}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 12}, cursor)
+ end)
+
+ it('adjusts cursor line and column if replacement is empty', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'r' in 'there'
+ curwin('set_cursor', {2, 8})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 12, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the last one' }, get_lines(0, -1, true))
+ -- cursor should end up on the next column after deleted range
+ eq({1, 15}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 15}, cursor)
+ end)
+
+ it('adjusts cursor line and column if replacement is empty and start_col == 0', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'r' in 'there'
+ curwin('set_cursor', {2, 8})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 2, 4, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'finally the last one' }, get_lines(0, -1, true))
+ -- cursor should end up in column 0
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+
+ it('adjusts cursor column if replacement ends at cursor row, after cursor column', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' in 'finally'
+ curwin('set_cursor', {3, 10})
+ set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
+
+ eq({
+ 'This should be 1',
+ 'this 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'n' in 'then'
+ eq({3, 7}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacement ends at cursor row, at cursor column in INSERT mode', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' at 'finally'
+ curwin('set_cursor', {3, 10})
+ -- enter INSERT mode to treat cursor as being between 'l' and 'y'
+ feed('i')
+ set_text(0, 15, 2, 11, { '1', 'this 2', 'and then' })
+
+ eq({
+ 'This should be 1',
+ 'this 2',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up after 'n' in 'then'
+ eq({3, 8}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacement is inside of a single line', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'y' in 'finally'
+ curwin('set_cursor', {3, 10})
+ set_text(2, 4, 2, 11, { 'then' })
+
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and then the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'n' in 'then'
+ eq({3, 7}, curwin('get_cursor'))
+ end)
+
+ it('does not move cursor column after end of a line', function()
+ insert([[
+ This should be the only line here
+ !!!]])
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 33, 1, 3, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the only line here' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 32}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 32}, cursor)
+ end)
+
+ it('does not move cursor column before start of a line', function()
+ insert('\n!!!')
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 1, 3, {})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ '' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+
+ describe('with virtualedit', function()
+ it('adjusts cursor line and column to keep it inside replacement range if cursor is not after eol', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'want'
+ curwin('set_cursor', {2, 34})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up on 'y' in 'hopefully'
+ -- to stay in the range
+ eq({2, 12}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 12}, cursor)
+ -- coladd should be 0
+ eq(0, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row got shorter', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'want'
+ curwin('set_cursor', {2, 34})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 5 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({2, 26}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 26}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(13, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row got longer', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 21 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({1, 38}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 38}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(2, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row extended past cursor column', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position cursor on 't' in 'first'
+ curwin('set_cursor', {1, 19})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol just a bit
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 3 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay at the same screen column
+ eq({1, 22}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 22}, cursor)
+ -- coladd should become 0
+ eq(0, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+
+ it('does not change cursor screen column when cursor is after eol and row range decreased', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and one more
+ and finally the last one]])
+
+ -- position cursor on 'e' in 'more'
+ curwin('set_cursor', {3, 11})
+ -- turn on virtualedit
+ command('set virtualedit=all')
+ -- move cursor after eol
+ exec_lua([[
+ vim.fn.winrestview({ coladd = 28 })
+ ]])
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 15, 3, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should end up at eol of a new row
+ eq({2, 26}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({2, 26}, cursor)
+ -- coladd should be increased so that cursor stays in the same screen column
+ eq(13, exec_lua([[
+ return vim.fn.winsaveview().coladd
+ ]]))
+ end)
+ end)
+ end)
+
+ describe('when cursor is at end_row and after end_col', function()
+ it('adjusts cursor column when only a newline is added or deleted', function()
+ insert([[
+ first line
+ second
+ line]])
+
+ -- position the cursor on 'i'
+ curwin('set_cursor', {3, 2})
+ set_text(1, 6, 2, 0, {})
+ eq({'first line', 'second line'}, get_lines(0, -1, true))
+ -- cursor should stay on 'i'
+ eq({2, 8}, curwin('get_cursor'))
+
+ -- add a newline back
+ set_text(1, 6, 1, 6, {'', ''})
+ eq({'first line', 'second', ' line'}, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 2}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if the range is not bound to either start or end of a line', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 'h' in 'the'
+ curwin('set_cursor', {3, 13})
+ set_text(0, 14, 2, 11, {})
+ eq({'This should be the last one'}, get_lines(0, -1, true))
+ -- cursor should stay on 'h'
+ eq({1, 16}, curwin('get_cursor'))
+ -- add deleted lines back
+ set_text(0, 14, 0, 14, {
+ ' first',
+ 'then there is a line we do not want',
+ 'and finally',
+ })
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and finally the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 13}, curwin('get_cursor'))
+ end)
+
+ it('adjusts cursor column if replacing lines in range, not just deleting and adding', function()
+ insert([[
+ This should be first
+ then there is a line we do not want
+ and finally the last one]])
+
+ -- position the cursor on 's' in 'last'
+ curwin('set_cursor', {3, 18})
+ set_text(0, 15, 2, 11, {
+ 'the line we do not want',
+ 'but hopefully',
+ })
+
+ eq({
+ 'This should be the line we do not want',
+ 'but hopefully the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should stay on 's'
+ eq({2, 20}, curwin('get_cursor'))
+
+ set_text(0, 15, 1, 13, {
+ 'first',
+ 'then there is a line we do not want',
+ 'and finally',
+ })
+
+ eq({
+ 'This should be first',
+ 'then there is a line we do not want',
+ 'and finally the last one',
+ }, get_lines(0, -1, true))
+ -- cursor should return back to the original position
+ eq({3, 18}, curwin('get_cursor'))
+ end)
+
+ it('does not move cursor column after end of a line', function()
+ insert([[
+ This should be the only line here
+ ]])
+
+ -- position cursor at the empty line
+ curwin('set_cursor', {2, 0})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 33, 1, 0, {'!'})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ 'This should be the only line here!' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 33}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 33}, cursor)
+ end)
+
+ it('does not move cursor column before start of a line', function()
+ insert('\n')
+
+ eq({ '', '' }, get_lines(0, -1, true))
+
+ -- position cursor on the last '1'
+ curwin('set_cursor', {2, 2})
+
+ local cursor = exec_lua([[
+ vim.api.nvim_buf_set_text(0, 0, 0, 1, 0, {''})
+ return vim.api.nvim_win_get_cursor(0)
+ ]])
+
+ eq({ '' }, get_lines(0, -1, true))
+ -- cursor should end up on '!'
+ eq({1, 0}, curwin('get_cursor'))
+ -- immediate call to nvim_win_get_cursor should have returned the same position
+ eq({1, 0}, cursor)
+ end)
+ end)
+
it('can handle NULs', function()
set_text(0, 0, 0, 0, {'ab\0cd'})
eq('ab\0cd', curbuf_depr('get_line', 0))
@@ -553,20 +1640,20 @@ describe('api/buf', function()
insert([[
hello foo!
text]])
- eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {}))
- eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {}))
- eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {}))
- eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {}))
- eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, 2, 0, 3, 0, {}))
+ eq("Invalid 'start_row': out of range", pcall_err(set_text, -3, 0, 0, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, 2, 0, {}))
+ eq("Invalid 'end_row': out of range", pcall_err(set_text, 0, 0, -3, 0, {}))
+ eq("Invalid 'start_col': out of range", pcall_err(set_text, 1, 5, 1, 5, {}))
+ eq("Invalid 'end_col': out of range", pcall_err(set_text, 1, 0, 1, 5, {}))
end)
it('errors when start is greater than end', function()
insert([[
hello foo!
text]])
- eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {}))
- eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(set_text, 0, 1, 0, 0, {}))
end)
it('no heap-use-after-free when called consecutively #19643', function()
@@ -579,6 +1666,139 @@ describe('api/buf', function()
]])
eq({'one', 'two'}, get_lines(0, 2, true))
end)
+
+ describe('handles topline', function()
+ local screen
+ before_each(function()
+ screen = Screen.new(20, 12)
+ screen:set_default_attr_ids {
+ [1] = {bold = true, foreground = Screen.colors.Blue1};
+ [2] = {reverse = true, bold = true};
+ [3] = {reverse = true};
+ }
+ screen:attach()
+ meths.buf_set_lines(0, 0, -1, 1, {"aaa", "bbb", "ccc", "ddd", "www", "xxx", "yyy", "zzz"})
+ meths.set_option_value('modified', false, {})
+ end)
+
+ it('of current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new | wincmd w')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ ^zzz |
+ {2:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of non-current window', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('new')
+ meths.win_set_cursor(win, {8,0})
+
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('of split windows with same buffer', function()
+ local win = meths.get_current_win()
+ local buf = meths.get_current_buf()
+
+ command('split')
+ meths.win_set_cursor(win, {8,0})
+ meths.win_set_cursor(0, {1,1})
+
+ screen:expect{grid=[[
+ a^aa |
+ bbb |
+ ccc |
+ ddd |
+ www |
+ {2:[No Name] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] }|
+ |
+ ]]}
+ meths.buf_set_text(buf, 0,3, 1,0, {"X"})
+
+ screen:expect{grid=[[
+ a^aaXbbb |
+ ccc |
+ ddd |
+ www |
+ xxx |
+ {2:[No Name] [+] }|
+ www |
+ xxx |
+ yyy |
+ zzz |
+ {3:[No Name] [+] }|
+ |
+ ]]}
+ end)
+ end)
end)
describe_lua_and_rpc('nvim_buf_get_text', function(api)
@@ -611,7 +1831,7 @@ describe('api/buf', function()
end)
it('errors when start is greater than end', function()
- eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {}))
+ eq("'start' is higher than 'end'", pcall_err(get_text, 1, 0, 0, 0, {}))
eq('start_col must be less than end_col', pcall_err(get_text, 0, 1, 0, 0, {}))
end)
end)
@@ -630,19 +1850,19 @@ describe('api/buf', function()
eq('Index out of bounds', pcall_err(get_offset, 6))
eq('Index out of bounds', pcall_err(get_offset, -1))
- curbufmeths.set_option('eol', false)
- curbufmeths.set_option('fixeol', false)
+ meths.set_option_value('eol', false, {})
+ meths.set_option_value('fixeol', false, {})
eq(28, get_offset(5))
-- fileformat is ignored
- curbufmeths.set_option('fileformat', 'dos')
+ meths.set_option_value('fileformat', 'dos', {})
eq(0, get_offset(0))
eq(6, get_offset(1))
eq(15, get_offset(2))
eq(16, get_offset(3))
eq(24, get_offset(4))
eq(28, get_offset(5))
- curbufmeths.set_option('eol', true)
+ meths.set_option_value('eol', true, {})
eq(29, get_offset(5))
command("set hidden")
@@ -650,6 +1870,18 @@ describe('api/buf', function()
eq(6, bufmeths.get_offset(1,1))
command("bunload! 1")
eq(-1, bufmeths.get_offset(1,1))
+ eq(-1, bufmeths.get_offset(1,0))
+ end)
+
+ it('works in empty buffer', function()
+ eq(0, get_offset(0))
+ eq(1, get_offset(1))
+ end)
+
+ it('works in buffer with one line inserted', function()
+ feed('itext')
+ eq(0, get_offset(0))
+ eq(5, get_offset(1))
end)
end)
@@ -697,23 +1929,23 @@ describe('api/buf', function()
end)
end)
- describe('nvim_buf_get_option, nvim_buf_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- eq(8, curbuf('get_option', 'shiftwidth'))
- curbuf('set_option', 'shiftwidth', 4)
- eq(4, curbuf('get_option', 'shiftwidth'))
+ eq(8, nvim('get_option_value', 'shiftwidth', {}))
+ nvim('set_option_value', 'shiftwidth', 4, {})
+ eq(4, nvim('get_option_value', 'shiftwidth', {}))
-- global-local option
- curbuf('set_option', 'define', 'test')
- eq('test', curbuf('get_option', 'define'))
+ nvim('set_option_value', 'define', 'test', {buf = 0})
+ eq('test', nvim('get_option_value', 'define', {buf = 0}))
-- Doesn't change the global value
- eq([[^\s*#\s*define]], nvim('get_option', 'define'))
+ eq("", nvim('get_option_value', 'define', {scope='global'}))
end)
it('returns values for unset local options', function()
-- 'undolevels' is only set to its "unset" value when a new buffer is
-- created
command('enew')
- eq(-123456, curbuf('get_option', 'undolevels'))
+ eq(-123456, nvim('get_option_value', 'undolevels', {buf=0}))
end)
end)
diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua
index d5f06c8f1f..80e29c1ff2 100644
--- a/test/functional/api/buffer_updates_spec.lua
+++ b/test/functional/api/buffer_updates_spec.lua
@@ -75,7 +75,7 @@ local function reopenwithfolds(b)
local tick = reopen(b, origlines)
-- use markers for folds, make all folds open by default
- command('setlocal foldmethod=marker foldlevel=20')
+ command('setlocal foldmethod=marker foldlevel=20 commentstring=/*%s*/')
-- add a fold
command('2,4fold')
@@ -762,7 +762,7 @@ describe('API: buffer events:', function()
it('returns a proper error on nonempty options dict', function()
clear()
local b = editoriginal(false)
- eq("unexpected key: builtin", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
+ eq("Invalid 'opts' key: 'builtin'", pcall_err(buffer, 'attach', b, false, {builtin="asfd"}))
end)
it('nvim_buf_attach returns response after delay #8634', function()
diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua
index d0fb26edc7..1ddb289ded 100644
--- a/test/functional/api/command_spec.lua
+++ b/test/functional/api/command_spec.lua
@@ -24,7 +24,7 @@ describe('nvim_get_commands', function()
eq({}, meths.get_commands({builtin=false}))
end)
- it('validates input', function()
+ it('validation', function()
eq('builtin=true not implemented', pcall_err(meths.get_commands,
{builtin=true}))
eq("Invalid key: 'foo'", pcall_err(meths.get_commands,
@@ -543,23 +543,19 @@ describe('nvim_create_user_command', function()
end)
it('does not allow invalid command names', function()
- matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[
+ eq("Invalid command name (must start with uppercase): 'test'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('test', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 't@'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('t@', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 'T@st'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('T@st', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: 'Test!'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('Test!', 'echo "hi"', {})
]]))
-
- matches('Invalid command name', pcall_err(exec_lua, [[
+ eq("Invalid command name: '💩'", pcall_err(exec_lua, [[
vim.api.nvim_create_user_command('💩', 'echo "hi"', {})
]]))
end)
diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua
index 9902826c72..44a151cf6a 100644
--- a/test/functional/api/extmark_spec.lua
+++ b/test/functional/api/extmark_spec.lua
@@ -11,6 +11,7 @@ local insert = helpers.insert
local feed = helpers.feed
local clear = helpers.clear
local command = helpers.command
+local exec = helpers.exec
local meths = helpers.meths
local assert_alive = helpers.assert_alive
@@ -101,12 +102,31 @@ describe('API/extmarks', function()
ns2 = request('nvim_create_namespace', "my-fancy-plugin2")
end)
+ it('validation', function()
+ eq("Invalid 'end_col': expected Integer, got Array", pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = {}, end_row = 1 }))
+ eq("Invalid 'end_row': expected Integer, got Array", pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = {} }))
+ eq("Invalid 'virt_text_pos': expected String, got Integer", pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 0 }))
+ eq("Invalid 'virt_text_pos': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { virt_text_pos = 'foo' }))
+ eq("Invalid 'hl_mode': expected String, got Integer", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 0 }))
+ eq("Invalid 'hl_mode': 'foo'", pcall_err(set_extmark, ns, marks[2], 0, 0, { hl_mode = 'foo' }))
+ eq("Invalid 'id': expected Integer, got Array", pcall_err(set_extmark, ns, {}, 0, 0, { end_col = 1, end_row = 1 }))
+ eq("Invalid mark position: expected 2 Integer items", pcall_err(get_extmarks, ns, {}, {-1, -1}))
+ eq("Invalid mark position: expected mark id Integer or 2-item Array", pcall_err(get_extmarks, ns, true, {-1, -1}))
+ -- No memory leak with virt_text, virt_lines, sign_text
+ eq("right_gravity is not a boolean", pcall_err(set_extmark, ns, marks[2], 0, 0, {
+ virt_text = {{'foo', 'Normal'}},
+ virt_lines = {{{'bar', 'Normal'}}},
+ sign_text = 'a',
+ right_gravity = 'baz',
+ }))
+ end)
+
it("can end extranges past final newline using end_col = 0", function()
set_extmark(ns, marks[1], 0, 0, {
end_col = 0,
end_row = 1
})
- eq("end_col value outside range",
+ eq("Invalid 'end_col': out of range",
pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 }))
end)
@@ -188,6 +208,23 @@ describe('API/extmarks', function()
eq({}, get_extmarks(ns2, {0, 0}, {-1, -1}))
end)
+ it('can undo with extmarks (#25147)', function()
+ feed('itest<esc>')
+ set_extmark(ns, 1, 0, 0)
+ set_extmark(ns, 2, 1, 0)
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ feed('dd')
+ eq({ { 1, 1, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ eq({}, get_extmarks(ns, {0, 0}, {-1, -1}))
+ set_extmark(ns, 1, 0, 0, { right_gravity = false })
+ set_extmark(ns, 2, 1, 0, { right_gravity = false })
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ feed('u')
+ eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, {0, 0}, {-1, -1}))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ end)
+
it('querying for information and ranges', function()
--marks = {1, 2, 3}
--positions = {{0, 0,}, {0, 2}, {0, 3}}
@@ -733,7 +770,14 @@ describe('API/extmarks', function()
})
end)
- -- TODO(bfredl): add more tests!
+ it('can get overlapping extmarks', function()
+ set_extmark(ns, 1, 0, 0, {end_row = 5, end_col=0})
+ set_extmark(ns, 2, 2, 5, {end_row = 2, end_col=30})
+ set_extmark(ns, 3, 0, 5, {end_row = 2, end_col=10})
+ set_extmark(ns, 4, 0, 0, {end_row = 1, end_col=0})
+ eq({{ 2, 2, 5 }}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=false }))
+ eq({{ 1, 0, 0 }, { 3, 0, 5}, {2, 2, 5}}, get_extmarks(ns, {2, 0}, {2, -1}, { overlap=true }))
+ end)
end)
it('replace works', function()
@@ -1344,10 +1388,10 @@ describe('API/extmarks', function()
it('throws consistent error codes', function()
local ns_invalid = ns2 + 1
- eq("Invalid ns_id", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
- eq("Invalid ns_id", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
- eq("Invalid ns_id", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
- eq("Invalid ns_id", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
+ eq("Invalid 'ns_id': 3", pcall_err(set_extmark, ns_invalid, marks[1], positions[1][1], positions[1][2]))
+ eq("Invalid 'ns_id': 3", pcall_err(curbufmeths.del_extmark, ns_invalid, marks[1]))
+ eq("Invalid 'ns_id': 3", pcall_err(get_extmarks, ns_invalid, positions[1], positions[2]))
+ eq("Invalid 'ns_id': 3", pcall_err(get_extmark_by_id, ns_invalid, marks[1]))
end)
it('when col = line-length, set the mark on eol', function()
@@ -1362,13 +1406,13 @@ describe('API/extmarks', function()
it('when col = line-length, set the mark on eol', function()
local invalid_col = init_text:len() + 1
- eq("col value outside range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
+ eq("Invalid 'col': out of range", pcall_err(set_extmark, ns, marks[1], 0, invalid_col))
end)
it('fails when line > line_count', function()
local invalid_col = init_text:len() + 1
local invalid_lnum = 3
- eq('line value outside range', pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
+ eq("Invalid 'line': out of range", pcall_err(set_extmark, ns, marks[1], invalid_lnum, invalid_col))
eq({}, get_extmark_by_id(ns, marks[1]))
end)
@@ -1385,7 +1429,7 @@ describe('API/extmarks', function()
it('in read-only buffer', function()
command("view! runtime/doc/help.txt")
- eq(true, curbufmeths.get_option('ro'))
+ eq(true, meths.get_option_value('ro', {}))
local id = set_extmark(ns, 0, 0, 2)
eq({{id, 0, 2}}, get_extmarks(ns,0, -1))
end)
@@ -1398,12 +1442,12 @@ describe('API/extmarks', function()
end)
it('does not crash with append/delete/undo sequence', function()
- meths.exec([[
+ exec([[
let ns = nvim_create_namespace('myplugin')
call nvim_buf_set_extmark(0, ns, 0, 0, {})
call append(0, '')
%delete
- undo]],false)
+ undo]])
assert_alive()
end)
@@ -1435,7 +1479,7 @@ describe('API/extmarks', function()
feed('u')
-- handles pasting
- meths.exec([[let @a='asdfasdf']], false)
+ exec([[let @a='asdfasdf']])
feed([["ap]])
eq({ {1, 0, 0}, {2, 0, 8} },
meths.buf_get_extmarks(0, ns, 0, -1, {}))
@@ -1447,6 +1491,7 @@ describe('API/extmarks', function()
end_line = 1
})
eq({ {1, 0, 0, {
+ ns_id = 1,
end_col = 0,
end_row = 1,
right_gravity = true,
@@ -1457,57 +1502,184 @@ describe('API/extmarks', function()
it('in prompt buffer', function()
feed('dd')
local id = set_extmark(ns, marks[1], 0, 0, {})
- curbufmeths.set_option('buftype', 'prompt')
+ meths.set_option_value('buftype', 'prompt', {})
feed('i<esc>')
eq({{id, 0, 2}}, get_extmarks(ns, 0, -1))
end)
it('can get details', function()
set_extmark(ns, marks[1], 0, 0, {
+ conceal = "c",
+ cursorline_hl_group = "Statement",
end_col = 0,
- end_row = 1,
- right_gravity = false,
end_right_gravity = true,
- priority = 0,
+ end_row = 1,
hl_eol = true,
- hl_mode = "blend",
hl_group = "String",
- virt_text = { { "text", "Statement" } },
- virt_text_pos = "right_align",
- virt_text_hide = true,
- virt_lines = { { { "lines", "Statement" } }},
+ hl_mode = "blend",
+ line_hl_group = "Statement",
+ number_hl_group = "Statement",
+ priority = 0,
+ right_gravity = false,
+ sign_hl_group = "Statement",
+ sign_text = ">>",
+ spell = true,
+ virt_lines = {
+ { { "lines", "Macro" }, { "???" } },
+ { { "stack", { "Type", "Search" } }, { "!!!" } },
+ },
virt_lines_above = true,
virt_lines_leftcol = true,
+ virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } },
+ virt_text_hide = true,
+ virt_text_pos = "right_align",
})
set_extmark(ns, marks[2], 0, 0, {
priority = 0,
- virt_text = { { "text", "Statement" } },
+ virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } },
virt_text_win_col = 1,
})
eq({0, 0, {
+ conceal = "c",
+ cursorline_hl_group = "Statement",
end_col = 0,
- end_row = 1,
- right_gravity = false,
end_right_gravity = true,
- priority = 0,
+ end_row = 1,
hl_eol = true,
- hl_mode = "blend",
hl_group = "String",
- virt_text = { { "text", "Statement" } },
- virt_text_pos = "right_align",
- virt_text_hide = true,
- virt_lines = { { { "lines", "Statement" } }},
+ hl_mode = "blend",
+ line_hl_group = "Statement",
+ ns_id = 1,
+ number_hl_group = "Statement",
+ priority = 0,
+ right_gravity = false,
+ sign_hl_group = "Statement",
+ sign_text = ">>",
+ spell = true,
+ virt_lines = {
+ { { "lines", "Macro" }, { "???" } },
+ { { "stack", { "Type", "Search" } }, { "!!!" } },
+ },
virt_lines_above = true,
virt_lines_leftcol = true,
+ virt_text = { { "text", "Macro" }, { "???" }, { "stack", { "Type", "Search" } } },
+ virt_text_hide = true,
+ virt_text_pos = "right_align",
} }, get_extmark_by_id(ns, marks[1], { details = true }))
eq({0, 0, {
+ ns_id = 1,
right_gravity = true,
priority = 0,
- virt_text = { { "text", "Statement" } },
+ virt_text = { { "", "Macro" }, { "", { "Type", "Search" } }, { "" } },
virt_text_hide = false,
virt_text_pos = "win_col",
virt_text_win_col = 1,
} }, get_extmark_by_id(ns, marks[2], { details = true }))
+ set_extmark(ns, marks[3], 0, 0, { cursorline_hl_group = "Statement" })
+ eq({0, 0, {
+ ns_id = 1,
+ cursorline_hl_group = "Statement",
+ priority = 4096,
+ right_gravity = true,
+ } }, get_extmark_by_id(ns, marks[3], { details = true }))
+ curbufmeths.clear_namespace(ns, 0, -1)
+ -- legacy sign mark includes sign name
+ command('sign define sign1 text=s1 texthl=Title linehl=LineNR numhl=Normal culhl=CursorLine')
+ command('sign place 1 name=sign1 line=1')
+ eq({ {1, 0, 0, {
+ cursorline_hl_group = 'CursorLine',
+ invalidate = true,
+ line_hl_group = 'LineNr',
+ ns_id = 0,
+ number_hl_group = 'Normal',
+ priority = 10,
+ right_gravity = true,
+ sign_hl_group = 'Title',
+ sign_name = 'sign1',
+ sign_text = 's1',
+ undo_restore = false
+ } } }, get_extmarks(-1, 0, -1, { details = true }))
+ end)
+
+ it('can get marks from anonymous namespaces', function()
+ ns = request('nvim_create_namespace', "")
+ ns2 = request('nvim_create_namespace', "")
+ set_extmark(ns, 1, 0, 0, {})
+ set_extmark(ns2, 2, 1, 0, {})
+ eq({{ 1, 0, 0, { ns_id = ns, right_gravity = true }},
+ { 2, 1, 0, { ns_id = ns2, right_gravity = true }}},
+ get_extmarks(-1, 0, -1, { details = true }))
+ end)
+
+ it('can filter by extmark properties', function()
+ set_extmark(ns, 1, 0, 0, {})
+ set_extmark(ns, 2, 0, 0, { hl_group = 'Normal' })
+ set_extmark(ns, 3, 0, 0, { sign_text = '>>' })
+ set_extmark(ns, 4, 0, 0, { virt_text = {{'text', 'Normal'}}})
+ set_extmark(ns, 5, 0, 0, { virt_lines = {{{ 'line', 'Normal' }}}})
+ eq(5, #get_extmarks(-1, 0, -1, {}))
+ eq({{ 2, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'highlight' }))
+ eq({{ 3, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'sign' }))
+ eq({{ 4, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'virt_text' }))
+ eq({{ 5, 0, 0 }}, get_extmarks(-1, 0, -1, { type = 'virt_lines' }))
+ end)
+
+ it("invalidated marks are deleted", function()
+ screen = Screen.new(40, 6)
+ screen:attach()
+ feed('dd6iaaa bbb ccc<CR><ESC>gg')
+ set_extmark(ns, 1, 0, 0, { invalidate = true, sign_text = 'S1' })
+ set_extmark(ns, 2, 1, 0, { invalidate = true, sign_text = 'S2' })
+ -- mark with invalidate is removed
+ command('d')
+ screen:expect([[
+ S2^aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ |
+ ]])
+ -- mark is restored with undo_restore == true
+ command('silent undo')
+ screen:expect([[
+ S1^aaa bbb ccc |
+ S2aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ aaa bbb ccc |
+ |
+ ]])
+ -- mark is deleted with undo_restore == false
+ set_extmark(ns, 1, 0, 0, { invalidate = true, undo_restore = false, sign_text = 'S1' })
+ set_extmark(ns, 2, 1, 0, { invalidate = true, undo_restore = false, sign_text = 'S2' })
+ command('1d 2')
+ eq(0, #get_extmarks(-1, 0, -1, {}))
+ -- mark is not removed when deleting bytes before the range
+ set_extmark(ns, 3, 0, 4, { invalidate = true, undo_restore = false,
+ hl_group = 'Error', end_col = 7 })
+ feed('dw')
+ eq(3, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
+ -- mark is not removed when deleting bytes at the start of the range
+ feed('x')
+ eq(2, get_extmark_by_id(ns, 3, { details = true })[3].end_col)
+ -- mark is not removed when deleting bytes from the end of the range
+ feed('lx')
+ eq(1, get_extmark_by_id(ns, 3, { details = true})[3].end_col)
+ -- mark is not removed when deleting bytes beyond end of the range
+ feed('x')
+ eq(1, get_extmark_by_id(ns, 3, { details = true})[3].end_col)
+ -- mark is removed when all bytes in the range are deleted
+ feed('hx')
+ eq({}, get_extmark_by_id(ns, 3, {}))
+ -- multiline mark is not removed when start of its range is deleted
+ set_extmark(ns, 4, 1, 4, { undo_restore = false, invalidate = true,
+ hl_group = 'Error', end_col = 7, end_row = 3 })
+ feed('ddDdd')
+ eq({0, 0}, get_extmark_by_id(ns, 4, {}))
+ -- multiline mark is removed when entirety of its range is deleted
+ feed('vj2ed')
+ eq({}, get_extmark_by_id(ns, 4, {}))
end)
end)
diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua
index 7f044977cb..7d7d07e30e 100644
--- a/test/functional/api/highlight_spec.lua
+++ b/test/functional/api/highlight_spec.lua
@@ -57,9 +57,7 @@ describe('API: highlight',function()
eq(expected_rgb, nvim("get_hl_by_id", hl_id, true))
-- Test invalid id.
- local err, emsg = pcall(meths.get_hl_by_id, 30000, false)
- eq(false, err)
- eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 30000', pcall_err(meths.get_hl_by_id, 30000, false))
-- Test all highlight properties.
command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine')
@@ -70,22 +68,14 @@ describe('API: highlight',function()
eq(expected_undercurl, nvim("get_hl_by_id", hl_id, true))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_id, { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_id, expecting Integer',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_id, { nil }, false))
-- Test 0 argument.
- err, emsg = pcall(meths.get_hl_by_id, 0, false)
- eq(false, err)
- eq('Invalid highlight id: 0',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: 0', pcall_err(meths.get_hl_by_id, 0, false))
-- Test -1 argument.
- err, emsg = pcall(meths.get_hl_by_id, -1, false)
- eq(false, err)
- eq('Invalid highlight id: -1',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight id: -1', pcall_err(meths.get_hl_by_id, -1, false))
-- Test highlight group without ctermbg value.
command('hi Normal ctermfg=red ctermbg=yellow')
@@ -119,22 +109,16 @@ describe('API: highlight',function()
eq(expected_normal, nvim("get_hl_by_name", 'Normal', true))
-- Test invalid name.
- local err, emsg = pcall(meths.get_hl_by_name , 'unknown_highlight', false)
- eq(false, err)
- eq('Invalid highlight name: unknown_highlight',
- string.match(emsg, 'Invalid.*'))
+ eq("Invalid highlight name: 'unknown_highlight'",
+ pcall_err(meths.get_hl_by_name , 'unknown_highlight', false))
-- Test nil argument.
- err, emsg = pcall(meths.get_hl_by_name , { nil }, false)
- eq(false, err)
eq('Wrong type for argument 1 when calling nvim_get_hl_by_name, expecting String',
- string.match(emsg, 'Wrong.*'))
+ pcall_err(meths.get_hl_by_name , { nil }, false))
-- Test empty string argument.
- err, emsg = pcall(meths.get_hl_by_name , '', false)
- eq(false, err)
- eq('Invalid highlight name: ',
- string.match(emsg, 'Invalid.*'))
+ eq('Invalid highlight name',
+ pcall_err(meths.get_hl_by_name , '', false))
-- Test "standout" attribute. #8054
eq({ underline = true, },
@@ -155,7 +139,7 @@ describe('API: highlight',function()
it('nvim_get_hl_id_by_name', function()
-- precondition: use a hl group that does not yet exist
- eq('Invalid highlight name: Shrubbery', pcall_err(meths.get_hl_by_name, "Shrubbery", true))
+ eq("Invalid highlight name: 'Shrubbery'", pcall_err(meths.get_hl_by_name, "Shrubbery", true))
eq(0, funcs.hlID("Shrubbery"))
local hl_id = meths.get_hl_id_by_name("Shrubbery")
@@ -171,9 +155,9 @@ describe('API: highlight',function()
it("nvim_buf_add_highlight to other buffer doesn't crash if undo is disabled #12873", function()
command('vsplit file')
- local err, _ = pcall(meths.buf_set_option, 1, 'undofile', false)
+ local err, _ = pcall(meths.set_option_value, 'undofile', false, { buf = 1 })
eq(true, err)
- err, _ = pcall(meths.buf_set_option, 1, 'undolevels', -1)
+ err, _ = pcall(meths.set_option_value, 'undolevels', -1, { buf = 1 })
eq(true, err)
err, _ = pcall(meths.buf_add_highlight, 1, -1, 'Question', 0, 0, -1)
eq(true, err)
@@ -253,25 +237,32 @@ describe("API: set highlight", function()
before_each(clear)
- it ("can set gui highlight", function()
+ it('validation', function()
+ eq("Invalid 'blend': out of range",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend=999}))
+ eq("Invalid 'blend': expected Integer, got Array",
+ pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='#FF00FF', blend={}}))
+ end)
+
+ it("can set gui highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq(highlight1, meths.get_hl_by_name('Test_hl', true))
end)
- it ("can set cterm highlight", function()
+ it("can set cterm highlight", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight2_config)
eq(highlight2_result, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can set empty cterm attr", function()
+ it("can set empty cterm attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', { cterm = {} })
eq({}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("cterm attr defaults to gui attr", function()
+ it("cterm attr defaults to gui attr", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight1)
eq({
@@ -280,14 +271,28 @@ describe("API: set highlight", function()
}, meths.get_hl_by_name('Test_hl', false))
end)
- it ("can overwrite attr for cterm", function()
+ it("can overwrite attr for cterm", function()
local ns = get_ns()
meths.set_hl(ns, 'Test_hl', highlight3_config)
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()
+ it("only allows one underline attribute #22371", function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', {
+ underdouble = true,
+ underdotted = true,
+ cterm = {
+ underline = true,
+ undercurl = true,
+ },
+ })
+ eq({ undercurl = true }, meths.get_hl_by_name('Test_hl', false))
+ eq({ underdotted = true }, meths.get_hl_by_name('Test_hl', true))
+ 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'))
@@ -307,7 +312,7 @@ describe("API: set highlight", function()
exec_capture('highlight Test_hl3'))
end)
- it ("can modify a highlight in the global namespace", function()
+ it("can modify a highlight in the global namespace", function()
meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'})
eq('Test_hl3 xxx guifg=Blue guibg=Red',
exec_capture('highlight Test_hl3'))
@@ -328,17 +333,17 @@ describe("API: set highlight", function()
eq('Test_hl3 xxx ctermbg=9',
exec_capture('highlight Test_hl3'))
- eq("'redd' is not a valid color",
+ eq("Invalid highlight color: 'redd'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'}))
- eq("'bleu' is not a valid color",
+ eq("Invalid highlight color: 'bleu'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'}))
meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'})
eq('Test_hl3 xxx guifg=#ff00ff',
exec_capture('highlight Test_hl3'))
- eq("'#FF00FF' is not a valid color",
+ eq("Invalid highlight color: '#FF00FF'",
pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'}))
for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do
@@ -353,14 +358,298 @@ describe("API: set highlight", function()
end)
- it ("correctly sets 'Normal' internal properties", function()
+ it("correctly sets 'Normal' internal properties", function()
-- Normal has some special handling internally. #18024
meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'})
eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true))
end)
it('does not segfault on invalid group name #20009', function()
- eq('Invalid highlight name: foo bar', pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
+ eq("Invalid highlight name: 'foo bar'", pcall_err(meths.set_hl, 0, 'foo bar', {bold = true}))
assert_alive()
end)
end)
+
+describe('API: get highlight', function()
+ local highlight_color = {
+ fg = tonumber('0xff0000'),
+ bg = tonumber('0x0032aa'),
+ ctermfg = 8,
+ ctermbg = 15,
+ }
+ local highlight1 = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ bold = true, italic = true,
+ cterm = {bold = true, italic = true},
+ }
+ local highlight2 = {
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ underline = true, reverse = true,
+ cterm = {underline = true, reverse = true},
+ }
+ local highlight3_config = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ bold = true,
+ italic = true,
+ reverse = true,
+ underdashed = true,
+ strikethrough = true,
+ altfont = true,
+ cterm = {
+ italic = true,
+ reverse = true,
+ strikethrough = true,
+ altfont = true,
+ nocombine = true,
+ },
+ }
+ local highlight3_result = {
+ bg = highlight_color.bg,
+ fg = highlight_color.fg,
+ ctermbg = highlight_color.ctermbg,
+ ctermfg = highlight_color.ctermfg,
+ bold = true, italic = true, reverse = true, underdashed = true, strikethrough = true, altfont = true,
+ cterm = {italic = true, nocombine = true, reverse = true, strikethrough = true, altfont = true}
+ }
+
+ local function get_ns()
+ -- Test namespace filtering behavior
+ local ns2 = meths.create_namespace('Another_namespace')
+ meths.set_hl(ns2, 'Test_hl', { ctermfg = 23 })
+ meths.set_hl(ns2, 'Test_another_hl', { link = 'Test_hl' })
+ meths.set_hl(ns2, 'Test_hl_link', { link = 'Test_another_hl' })
+ meths.set_hl(ns2, 'Test_another_hl_link', { link = 'Test_hl_link' })
+
+ local ns = meths.create_namespace('Test_set_hl')
+ meths.set_hl_ns(ns)
+
+ return ns
+ end
+
+ before_each(clear)
+
+ it('validation', function()
+ eq("Invalid 'name': expected String, got Integer",
+ pcall_err(meths.get_hl, 0, { name = 177 }))
+ eq('Highlight id out of bounds', pcall_err(meths.get_hl, 0, { name = 'Test set hl' }))
+ end)
+
+ it('nvim_get_hl with create flag', function()
+ eq({}, nvim("get_hl", 0, {name = 'Foo', create = false}))
+ eq(0, funcs.hlexists('Foo'))
+ meths.get_hl(0, {name = 'Bar', create = true})
+ eq(1, funcs.hlexists('Bar'))
+ meths.get_hl(0, {name = 'FooBar'})
+ eq(1, funcs.hlexists('FooBar'))
+ end)
+
+ it('can get all highlights in current namespace', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', { bg = '#B4BEFE' })
+ meths.set_hl(ns, 'Test_hl_link', { link = 'Test_hl' })
+ eq({
+ Test_hl = {
+ bg = 11845374
+ },
+ Test_hl_link = {
+ link = 'Test_hl'
+ }
+ }, meths.get_hl(ns, {}))
+ end)
+
+ it('can get gui highlight', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq(highlight1, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get cterm highlight', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight2)
+ eq(highlight2, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get empty cterm attr', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', { cterm = {} })
+ eq({}, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('cterm attr defaults to gui attr', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight1)
+ eq(highlight1, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can overwrite attr for cterm', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', highlight3_config)
+ eq(highlight3_result, meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('only allows one underline attribute #22371', function()
+ local ns = get_ns()
+ meths.set_hl(ns, 'Test_hl', {
+ underdouble = true,
+ underdotted = true,
+ cterm = {
+ underline = true,
+ undercurl = true,
+ },
+ })
+ eq({ underdotted = true, cterm = { undercurl = true} },
+ meths.get_hl(ns, { name = 'Test_hl' }))
+ end)
+
+ it('can get a highlight in the global namespace', function()
+ meths.set_hl(0, 'Test_hl', highlight2)
+ eq(highlight2, meths.get_hl(0, { name = 'Test_hl' }))
+
+ meths.set_hl(0, 'Test_hl', { background = highlight_color.bg })
+ eq({
+ bg = 12970,
+ }, meths.get_hl(0, { name = 'Test_hl' }))
+
+ meths.set_hl(0, 'Test_hl2', highlight3_config)
+ eq(highlight3_result, meths.get_hl(0, { name = 'Test_hl2' }))
+
+ -- Colors are stored with the name they are defined, but
+ -- with canonical casing
+ meths.set_hl(0, 'Test_hl3', { bg = 'reD', fg = 'bLue' })
+ eq({
+ bg = 16711680,
+ fg = 255,
+ }, meths.get_hl(0, { name = 'Test_hl3' }))
+ end)
+
+ it('nvim_get_hl by id', function()
+ local hl_id = meths.get_hl_id_by_name('NewHighlight')
+
+ command(
+ 'hi NewHighlight cterm=underline ctermbg=green guifg=red guibg=yellow guisp=blue gui=bold'
+ )
+ eq({ fg = 16711680, bg = 16776960, sp = 255, bold = true,
+ ctermbg = 10, cterm = { underline = true },
+ }, meths.get_hl(0, { id = hl_id }))
+
+ -- Test 0 argument
+ eq('Highlight id out of bounds', pcall_err(meths.get_hl, 0, { id = 0 }))
+
+ eq(
+ "Invalid 'id': expected Integer, got String",
+ pcall_err(meths.get_hl, 0, { id = 'Test_set_hl' })
+ )
+
+ -- Test all highlight properties.
+ command('hi NewHighlight gui=underline,bold,italic,reverse,strikethrough,altfont,nocombine')
+ eq({ fg = 16711680, bg = 16776960, sp = 255,
+ altfont = true, bold = true, italic = true, nocombine = true, reverse = true, strikethrough = true, underline = true,
+ ctermbg = 10, cterm = {underline = true},
+ }, meths.get_hl(0, { id = hl_id }))
+
+ -- Test undercurl
+ command('hi NewHighlight gui=undercurl')
+ eq({ fg = 16711680, bg = 16776960, sp = 255, undercurl = true,
+ ctermbg = 10,
+ cterm = {underline = true},
+ }, meths.get_hl(0, { id = hl_id }))
+ end)
+
+ it('can correctly detect links', function()
+ command('hi String guifg=#a6e3a1')
+ command('hi link @string string')
+ command('hi link @string.cpp @string')
+ eq({ fg = 10937249 }, meths.get_hl(0, { name = 'String' }))
+ eq({ link = 'String' }, meths.get_hl(0, { name = '@string' }))
+ eq({ fg = 10937249 }, meths.get_hl(0, { name = '@string.cpp', link = false }))
+ end)
+
+ it('can get all attributes for a linked group', function()
+ command('hi Bar guifg=red')
+ command('hi Foo guifg=#00ff00 gui=bold,underline')
+ command('hi! link Foo Bar')
+ eq({ link = 'Bar', fg = tonumber('00ff00', 16), bold = true, underline = true }, meths.get_hl(0, { name = 'Foo', link = true }))
+ end)
+
+ it('can set link as well as other attributes', function()
+ command('hi Bar guifg=red')
+ local hl = { link = 'Bar', fg = tonumber('00ff00', 16), bold = true, cterm = { bold = true } }
+ meths.set_hl(0, 'Foo', hl)
+ eq(hl, meths.get_hl(0, { name = 'Foo', link = true }))
+ end)
+
+ it("doesn't contain unset groups", function()
+ local id = meths.get_hl_id_by_name "@foobar.hubbabubba"
+ ok(id > 0)
+
+ local data = meths.get_hl(0, {})
+ eq(nil, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+
+ command 'hi @foobar.hubbabubba gui=bold'
+ data = meths.get_hl(0, {})
+ eq({bold = true}, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+
+ -- @foobar.hubbabubba was explicitly cleared and thus shows up
+ -- but @foobar was never touched, and thus doesn't
+ command 'hi clear @foobar.hubbabubba'
+ data = meths.get_hl(0, {})
+ eq({}, data["@foobar.hubbabubba"])
+ eq(nil, data["@foobar"])
+ end)
+
+ it('should return default flag', function()
+ meths.set_hl(0, 'Tried', { fg = "#00ff00", default = true })
+ eq({ fg = tonumber('00ff00', 16), default = true }, meths.get_hl(0, { name = 'Tried' }))
+ end)
+
+ it('should not output empty gui and cterm #23474', function()
+ meths.set_hl(0, 'Foo', {default = true})
+ meths.set_hl(0, 'Bar', { default = true, fg = '#ffffff' })
+ meths.set_hl(0, 'FooBar', { default = true, fg = '#ffffff', cterm = {bold = true} })
+ meths.set_hl(0, 'FooBarA', { default = true, fg = '#ffffff', cterm = {bold = true,italic = true}})
+
+ eq('Foo xxx cleared',
+ exec_capture('highlight Foo'))
+ eq({default = true}, meths.get_hl(0, {name = 'Foo'}))
+ eq('Bar xxx guifg=#ffffff',
+ exec_capture('highlight Bar'))
+ eq('FooBar xxx cterm=bold guifg=#ffffff',
+ exec_capture('highlight FooBar'))
+ eq('FooBarA xxx cterm=bold,italic guifg=#ffffff',
+ exec_capture('highlight FooBarA'))
+ end)
+
+ it('can override exist highlight group by force #20323', function()
+ local white = tonumber('ffffff', 16)
+ local green = tonumber('00ff00', 16)
+ meths.set_hl(0, 'Foo', { fg=white })
+ meths.set_hl(0, 'Foo', { fg=green, force = true })
+ eq({ fg = green },meths.get_hl(0, {name = 'Foo'}))
+ meths.set_hl(0, 'Bar', {link = 'Comment', default = true})
+ meths.set_hl(0, 'Bar', {link = 'Foo',default = true, force = true})
+ eq({link ='Foo', default = true}, meths.get_hl(0, {name = 'Bar'}))
+ end)
+end)
+
+describe('API: set/get highlight namespace', function()
+ it('set/get highlight namespace', function()
+ eq(0, meths.get_hl_ns({}))
+ local ns = meths.create_namespace('')
+ meths.set_hl_ns(ns)
+ eq(ns, meths.get_hl_ns({}))
+ end)
+
+ it('set/get window highlight namespace', function()
+ eq(-1, meths.get_hl_ns({winid = 0}))
+ local ns = meths.create_namespace('')
+ meths.win_set_hl_ns(0, ns)
+ eq(ns, meths.get_hl_ns({winid = 0}))
+ end)
+end)
diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua
index 5be4425162..434f117956 100644
--- a/test/functional/api/keymap_spec.lua
+++ b/test/functional/api/keymap_spec.lua
@@ -19,6 +19,20 @@ local sleep = helpers.sleep
local sid_api_client = -9
local sid_lua = -8
+local mode_bits_map = {
+ ['n'] = 0x01,
+ ['x'] = 0x02,
+ ['o'] = 0x04,
+ ['c'] = 0x08,
+ ['i'] = 0x10,
+ ['l'] = 0x20,
+ ['s'] = 0x40,
+ ['t'] = 0x80,
+ [' '] = 0x47,
+ ['v'] = 0x42,
+ ['!'] = 0x18,
+}
+
describe('nvim_get_keymap', function()
before_each(clear)
@@ -32,9 +46,12 @@ describe('nvim_get_keymap', function()
rhs='bar',
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=1,
lnum=0,
}
@@ -81,6 +98,7 @@ describe('nvim_get_keymap', function()
-- The table will be the same except for the mode
local insert_table = shallowcopy(foo_bar_map_table)
insert_table['mode'] = 'i'
+ insert_table['mode_bits'] = 0x10
eq({insert_table}, meths.get_keymap('i'))
end)
@@ -258,8 +276,10 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
+ abbr=0,
noremap=1,
lnum=0,
}
@@ -268,6 +288,7 @@ describe('nvim_get_keymap', function()
ret.lhs = lhs
ret.rhs = rhs
ret.mode = mode
+ ret.mode_bits = mode_bits_map[mode]
return ret
end
@@ -323,10 +344,13 @@ describe('nvim_get_keymap', function()
lhsraw='| |',
rhs='| |',
mode='n',
+ mode_bits=0x01,
+ abbr=0,
script=0,
silent=0,
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
noremap=1,
@@ -365,15 +389,18 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=sid_lua,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=0,
lnum=0,
}, mapargs[1])
end)
- it ('can handle map descriptions', function()
+ it('can handle map descriptions', function()
meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"})
eq({
lhs='lhs',
@@ -383,9 +410,12 @@ describe('nvim_get_keymap', function()
silent=0,
expr=0,
sid=sid_api_client,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=0,
lnum=0,
desc='map description'
@@ -400,6 +430,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- maparg(), which does not accept "!" (though it returns "!" in its output
-- if getting a mapping set with |:map!|).
local function normalize_mapmode(mode, generate_expected)
+ if mode:sub(-1) == 'a' then
+ mode = mode:sub(1, -2)
+ end
if not generate_expected and mode == '!' then
-- Cannot retrieve mapmode-ic mappings with "!", but can with "i" or "c".
mode = 'i'
@@ -417,7 +450,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end
local to_return = {}
- to_return.mode = normalize_mapmode(mode, true)
+ local expected_mode = normalize_mapmode(mode, true)
+ to_return.mode = expected_mode
+ to_return.mode_bits = mode_bits_map[expected_mode]
+ to_return.abbr = mode:sub(-1) == 'a' and 1 or 0
to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs
to_return.rhs = rhs
@@ -426,6 +462,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
to_return.nowait = not opts.nowait and 0 or 1
to_return.expr = not opts.expr and 0 or 1
to_return.sid = not opts.sid and sid_api_client or opts.sid
+ to_return.scriptversion = 1
to_return.buffer = not opts.buffer and 0 or opts.buffer
to_return.lnum = not opts.lnum and 0 or opts.lnum
to_return.desc = opts.desc
@@ -435,7 +472,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- Gets a maparg() dict from Nvim, if one exists.
local function get_mapargs(mode, lhs)
- local mapargs = funcs.maparg(lhs, normalize_mapmode(mode), false, true)
+ local mapargs = funcs.maparg(lhs, normalize_mapmode(mode), mode:sub(-1) == 'a', true)
-- drop "lhsraw" and "lhsrawalt" which are hard to check
mapargs.lhsraw = nil
mapargs.lhsrawalt = nil
@@ -450,7 +487,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('error if LHS longer than MAXMAPLEN', function()
- -- assume MAXMAPLEN of 50 chars, as declared in vim.h
+ -- assume MAXMAPLEN of 50 chars, as declared in mapping_defs.h
local MAXMAPLEN = 50
local lhs = ''
for i=1,MAXMAPLEN do
@@ -484,35 +521,33 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
get_mapargs('', 'lhs'))
end)
- it('throws errors when given too-long mode shortnames', function()
- eq('Shortname is too long: map',
- pcall_err(meths.set_keymap, 'map', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: vmap',
- pcall_err(meths.set_keymap, 'vmap', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: xnoremap',
- pcall_err(meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}))
-
- eq('Shortname is too long: map', pcall_err(meths.del_keymap, 'map', 'lhs'))
- eq('Shortname is too long: vmap', pcall_err(meths.del_keymap, 'vmap', 'lhs'))
- eq('Shortname is too long: xnoremap', pcall_err(meths.del_keymap, 'xnoremap', 'lhs'))
- end)
-
it('error on invalid mode shortname', function()
- eq('Invalid mode shortname: " "',
- pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "m"',
- pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "?"',
- pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "y"',
- pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
- eq('Invalid mode shortname: "p"',
- pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: " "', pcall_err(meths.set_keymap, ' ', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "m"', pcall_err(meths.set_keymap, 'm', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "?"', pcall_err(meths.set_keymap, '?', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "y"', pcall_err(meths.set_keymap, 'y', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "p"', pcall_err(meths.set_keymap, 'p', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "a"', pcall_err(meths.set_keymap, 'a', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "oa"', pcall_err(meths.set_keymap, 'oa', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!o"', pcall_err(meths.set_keymap, '!o', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!i"', pcall_err(meths.set_keymap, '!i', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "!!"', pcall_err(meths.set_keymap, '!!', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "map"', pcall_err(meths.set_keymap, 'map', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "vmap"', pcall_err(meths.set_keymap, 'vmap', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.set_keymap, 'xnoremap', 'lhs', 'rhs', {}))
+ eq('Invalid mode shortname: " "', pcall_err(meths.del_keymap, ' ', 'lhs'))
+ eq('Invalid mode shortname: "m"', pcall_err(meths.del_keymap, 'm', 'lhs'))
eq('Invalid mode shortname: "?"', pcall_err(meths.del_keymap, '?', 'lhs'))
eq('Invalid mode shortname: "y"', pcall_err(meths.del_keymap, 'y', 'lhs'))
eq('Invalid mode shortname: "p"', pcall_err(meths.del_keymap, 'p', 'lhs'))
+ eq('Invalid mode shortname: "a"', pcall_err(meths.del_keymap, 'a', 'lhs'))
+ eq('Invalid mode shortname: "oa"', pcall_err(meths.del_keymap, 'oa', 'lhs'))
+ eq('Invalid mode shortname: "!o"', pcall_err(meths.del_keymap, '!o', 'lhs'))
+ eq('Invalid mode shortname: "!i"', pcall_err(meths.del_keymap, '!i', 'lhs'))
+ eq('Invalid mode shortname: "!!"', pcall_err(meths.del_keymap, '!!', 'lhs'))
+ eq('Invalid mode shortname: "map"', pcall_err(meths.del_keymap, 'map', 'lhs'))
+ eq('Invalid mode shortname: "vmap"', pcall_err(meths.del_keymap, 'vmap', 'lhs'))
+ eq('Invalid mode shortname: "xnoremap"', pcall_err(meths.del_keymap, 'xnoremap', 'lhs'))
end)
it('error on invalid optnames', function()
@@ -681,13 +716,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
it('can set <expr> mappings whose RHS change dynamically', function()
- meths.exec([[
+ exec([[
function! FlipFlop() abort
if !exists('g:flip') | let g:flip = 0 | endif
let g:flip = !g:flip
return g:flip
endfunction
- ]], true)
+ ]])
eq(1, meths.call_function('FlipFlop', {}))
eq(0, meths.call_function('FlipFlop', {}))
eq(1, meths.call_function('FlipFlop', {}))
@@ -744,7 +779,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
end)
-- Perform exhaustive tests of basic functionality
- local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', ''}
+ local mapmodes = {'n', 'v', 'x', 's', 'o', '!', 'i', 'l', 'c', 't', '', 'ia', 'ca', '!a'}
for _, mapmode in ipairs(mapmodes) do
it('can set/unset normal mappings in mapmode '..mapmode, function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {})
@@ -773,11 +808,9 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- remove some map arguments that are harder to test, or were already tested
optnames = {'nowait', 'silent', 'expr', 'noremap'}
for _, mapmode in ipairs(mapmodes) do
- local printable_mode = normalize_mapmode(mapmode)
-
-- Test with single mappings
for _, maparg in ipairs(optnames) do
- it('can set/unset '..printable_mode..'-mappings with maparg: '..maparg,
+ it('can set/unset '..mapmode..'-mappings with maparg: '..maparg,
function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = true})
eq(generate_mapargs(mapmode, 'lhs', 'rhs', {[maparg] = true}),
@@ -785,7 +818,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
meths.del_keymap(mapmode, 'lhs')
eq({}, get_mapargs(mapmode, 'lhs'))
end)
- it ('can set/unset '..printable_mode..'-mode mappings with maparg '..
+ it ('can set/unset '..mapmode..'-mode mappings with maparg '..
maparg..', whose value is false', function()
meths.set_keymap(mapmode, 'lhs', 'rhs', {[maparg] = false})
eq(generate_mapargs(mapmode, 'lhs', 'rhs'),
@@ -798,7 +831,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
-- Test with triplets of mappings, one of which is false
for i = 1, (#optnames - 2) do
local opt1, opt2, opt3 = optnames[i], optnames[i + 1], optnames[i + 2]
- it('can set/unset '..printable_mode..'-mode mappings with mapargs '..
+ it('can set/unset '..mapmode..'-mode mappings with mapargs '..
opt1..', '..opt2..', '..opt3, function()
local opts = {[opt1] = true, [opt2] = false, [opt3] = true}
meths.set_keymap(mapmode, 'lhs', 'rhs', opts)
@@ -813,33 +846,36 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
feed('asdf\n')
eq(1, exec_lua[[return GlobalCount]])
-
end)
- it (':map command shows lua mapping correctly', function()
+ it(':map command shows lua mapping correctly', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
- assert.truthy(string.match(exec_lua[[return vim.api.nvim_exec(':nmap asdf', true)]],
- "^\nn asdf <Lua %d+>"))
+ assert.truthy(
+ string.match(
+ exec_lua[[return vim.api.nvim_exec2(':nmap asdf', { output = true }).output]],
+ "^\nn asdf <Lua %d+>"
+ )
+ )
end)
- it ('mapcheck() returns lua mapping correctly', function()
+ it('mapcheck() returns lua mapping correctly', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() print('jkl;') end })
]]
assert.truthy(string.match(funcs.mapcheck('asdf', 'n'),
"^<Lua %d+>"))
end)
- it ('maparg() returns lua mapping correctly', function()
+ it('maparg() returns lua mapping correctly', function()
eq(0, exec_lua([[
GlobalCount = 0
vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
@@ -867,7 +903,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua expr mappings replacing keycodes', function()
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
+ vim.api.nvim_set_keymap('n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
]]
feed('aa')
@@ -877,7 +913,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can make lua expr mappings without replacing keycodes', function()
exec_lua [[
- vim.api.nvim_set_keymap ('i', 'aa', '', {callback = function() return '<space>' end, expr = true })
+ vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return '<space>' end, expr = true })
]]
feed('iaa<esc>')
@@ -887,7 +923,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('lua expr mapping returning nil is equivalent to returning an empty string', function()
exec_lua [[
- vim.api.nvim_set_keymap ('i', 'aa', '', {callback = function() return nil end, expr = true })
+ vim.api.nvim_set_keymap('i', 'aa', '', {callback = function() return nil end, expr = true })
]]
feed('iaa<esc>')
@@ -898,17 +934,29 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('does not reset pum in lua mapping', function()
eq(0, exec_lua [[
VisibleCount = 0
- vim.api.nvim_set_keymap ('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end})
+ vim.api.nvim_set_keymap('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end})
return VisibleCount
]])
feed('i<C-X><C-V><F2><F2><esc>')
eq(2, exec_lua[[return VisibleCount]])
end)
+ it('redo of lua mappings in op-pending mode work', function()
+ eq(0, exec_lua [[
+ OpCount = 0
+ vim.api.nvim_set_keymap('o', '<F2>', '', {callback = function() OpCount = OpCount + 1 end})
+ return OpCount
+ ]])
+ feed('d<F2>')
+ eq(1, exec_lua[[return OpCount]])
+ feed('.')
+ eq(2, exec_lua[[return OpCount]])
+ end)
+
it('can overwrite lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -917,7 +965,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq(1, exec_lua[[return GlobalCount]])
exec_lua [[
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
]]
feed('asdf\n')
@@ -928,7 +976,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can unmap lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_set_keymap('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -973,6 +1021,54 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
eq("\nn lhs rhs\n map description",
helpers.exec_capture("nmap lhs"))
end)
+
+ it('can define !-mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('!a', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The 1 and the bar and the 2 again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The 3 is the one', meths.eval'x')
+ end)
+
+ it('can define insert mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('ia', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The 1 and the bar and the 2 again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The foo is the one', meths.eval'x')
+ end)
+
+ it('can define cmdline mode abbreviations with lua callbacks', function()
+ exec_lua [[
+ GlobalCount = 0
+ vim.api.nvim_set_keymap('ca', 'foo', '', {expr = true, callback = function()
+ GlobalCount = GlobalCount + 1
+ return tostring(GlobalCount)
+ end})
+ ]]
+
+ feed 'iThe foo and the bar and the foo again<esc>'
+ eq('The foo and the bar and the foo again', meths.get_current_line())
+
+ feed ':let x = "The foo is the one"<cr>'
+ eq('The 1 is the one', meths.eval'x')
+ end)
end)
describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
@@ -1074,7 +1170,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -1085,7 +1181,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua expr mappings replacing keycodes', function()
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'aa', '', {callback = function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, expr = true, replace_keycodes = true })
]]
feed('aa')
@@ -1095,7 +1191,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can make lua expr mappings without replacing keycodes', function()
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'i', 'aa', '', {callback = function() return '<space>' end, expr = true })
+ vim.api.nvim_buf_set_keymap(0, 'i', 'aa', '', {callback = function() return '<space>' end, expr = true })
]]
feed('iaa<esc>')
@@ -1107,7 +1203,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can overwrite lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
@@ -1116,7 +1212,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
eq(1, exec_lua[[return GlobalCount]])
exec_lua [[
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end })
]]
feed('asdf\n')
@@ -1127,7 +1223,7 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function()
it('can unmap lua mappings', function()
eq(0, exec_lua [[
GlobalCount = 0
- vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
+ vim.api.nvim_buf_set_keymap(0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end })
return GlobalCount
]])
diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua
index 2028a8fba5..20edea3feb 100644
--- a/test/functional/api/proc_spec.lua
+++ b/test/functional/api/proc_spec.lua
@@ -42,14 +42,14 @@ describe('API', function()
end)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc_children", -1)
eq(false, status)
- eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': -1", string.match(rv, "Invalid.*"))
status, rv = pcall(request, "nvim_get_proc_children", 0)
eq(false, status)
- eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': 0", string.match(rv, "Invalid.*"))
-- Assume PID 99999 does not exist.
status, rv = pcall(request, "nvim_get_proc_children", 99999)
@@ -68,14 +68,14 @@ describe('API', function()
neq(pid, pinfo.ppid)
end)
- it('validates input', function()
+ it('validation', function()
local status, rv = pcall(request, "nvim_get_proc", -1)
eq(false, status)
- eq("Invalid pid: -1", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': -1", string.match(rv, "Invalid.*"))
status, rv = pcall(request, "nvim_get_proc", 0)
eq(false, status)
- eq("Invalid pid: 0", string.match(rv, "Invalid.*"))
+ eq("Invalid 'pid': 0", string.match(rv, "Invalid.*"))
-- Assume PID 99999 does not exist.
status, rv = pcall(request, "nvim_get_proc", 99999)
diff --git a/test/functional/api/rpc_fixture.lua b/test/functional/api/rpc_fixture.lua
index 94df751363..c860a6da59 100644
--- a/test/functional/api/rpc_fixture.lua
+++ b/test/functional/api/rpc_fixture.lua
@@ -4,9 +4,8 @@
package.path = arg[1]
package.cpath = arg[2]
-local mpack = require('mpack')
-local StdioStream = require('nvim.stdio_stream')
-local Session = require('nvim.session')
+local StdioStream = require'test.client.uv_stream'.StdioStream
+local Session = require'test.client.session'
local stdio_stream = StdioStream.open()
local session = Session.new(stdio_stream)
@@ -19,7 +18,7 @@ local function on_request(method, args)
return "done!"
elseif method == "exit" then
session:stop()
- return mpack.NIL
+ return vim.NIL
end
end
diff --git a/test/functional/api/server_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua
index 53642858b2..bc43f6564d 100644
--- a/test/functional/api/server_notifications_spec.lua
+++ b/test/functional/api/server_notifications_spec.lua
@@ -6,9 +6,7 @@ local eq, clear, eval, command, nvim, next_msg =
local meths = helpers.meths
local exec_lua = helpers.exec_lua
local retry = helpers.retry
-local is_ci = helpers.is_ci
local assert_alive = helpers.assert_alive
-local skip = helpers.skip
local testlog = 'Xtest-server-notify-log'
@@ -90,7 +88,6 @@ describe('notify', function()
end)
it('cancels stale events on channel close', function()
- skip(is_ci(), 'hangs on CI #14083 #15251')
local catchan = eval("jobstart(['cat'], {'rpc': v:true})")
local catpath = eval('exepath("cat")')
eq({id=catchan, argv={catpath}, stream='job', mode='rpc', client = {}}, exec_lua ([[
diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua
index ceff390dc5..1ad4ad3a02 100644
--- a/test/functional/api/server_requests_spec.lua
+++ b/test/functional/api/server_requests_spec.lua
@@ -199,7 +199,7 @@ describe('server -> client', function()
it('can communicate buffers, tabpages, and windows', function()
eq({1}, eval("rpcrequest(vim, 'nvim_list_tabpages')"))
- -- Window IDs start at 1000 (LOWEST_WIN_ID in vim.h)
+ -- Window IDs start at 1000 (LOWEST_WIN_ID in window.h)
eq({1000}, eval("rpcrequest(vim, 'nvim_list_wins')"))
local buf = eval("rpcrequest(vim, 'nvim_list_bufs')")[1]
@@ -237,7 +237,7 @@ describe('server -> client', function()
\ }
]])
meths.set_var("args", {
- helpers.test_lua_prg,
+ nvim_prog, '-ll',
'test/functional/api/rpc_fixture.lua',
package.path,
package.cpath,
@@ -337,6 +337,21 @@ describe('server -> client', function()
eq('localhost:', string.sub(address,1,10))
connect_test(server, 'tcp', address)
end)
+
+ it('does not crash on receiving UI events', function()
+ local server = spawn(nvim_argv)
+ set_session(server)
+ local address = funcs.serverlist()[1]
+ local client = spawn(nvim_argv, false, nil, true)
+ set_session(client)
+
+ local id = funcs.sockconnect('pipe', address, {rpc=true})
+ funcs.rpcrequest(id, 'nvim_ui_attach', 80, 24, {})
+ assert_alive()
+
+ server:close()
+ client:close()
+ end)
end)
describe('connecting to its own pipe address', function()
diff --git a/test/functional/api/ui_spec.lua b/test/functional/api/ui_spec.lua
index 279cd1856d..6efb6726fe 100644
--- a/test/functional/api/ui_spec.lua
+++ b/test/functional/api/ui_spec.lua
@@ -1,8 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local command = helpers.command
local eq = helpers.eq
local eval = helpers.eval
+local exec = helpers.exec
+local feed = helpers.feed
local meths = helpers.meths
local request = helpers.request
local pcall_err = helpers.pcall_err
@@ -11,17 +14,35 @@ describe('nvim_ui_attach()', function()
before_each(function()
clear()
end)
+
it('handles very large width/height #2180', function()
local screen = Screen.new(999, 999)
screen:attach()
eq(999, eval('&lines'))
eq(999, eval('&columns'))
end)
- it('invalid option returns error', function()
+
+ it('validation', function()
eq('No such UI option: foo',
pcall_err(meths.ui_attach, 80, 24, { foo={'foo'} }))
- end)
- it('validates channel arg', function()
+
+ eq("Invalid 'ext_linegrid': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { ext_linegrid={} }))
+ eq("Invalid 'override': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { override={} }))
+ eq("Invalid 'rgb': expected Boolean, got Array",
+ pcall_err(meths.ui_attach, 80, 24, { rgb={} }))
+ eq("Invalid 'term_name': expected String, got Boolean",
+ pcall_err(meths.ui_attach, 80, 24, { term_name=true }))
+ eq("Invalid 'term_colors': expected Integer, got Boolean",
+ pcall_err(meths.ui_attach, 80, 24, { term_colors=true }))
+ eq("Invalid 'stdin_fd': expected Integer, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdin_fd='foo' }))
+ eq("Invalid 'stdin_tty': expected Boolean, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdin_tty='foo' }))
+ eq("Invalid 'stdout_tty': expected Boolean, got String",
+ pcall_err(meths.ui_attach, 80, 24, { stdout_tty='foo' }))
+
eq('UI not attached to channel: 1',
pcall_err(request, 'nvim_ui_try_resize', 40, 10))
eq('UI not attached to channel: 1',
@@ -37,14 +58,13 @@ describe('nvim_ui_attach()', function()
end)
it('autocmds UIEnter/UILeave', function()
- clear{
- args_rm={'--headless'},
- args={
- '--cmd', 'let g:evs = []',
- '--cmd', 'autocmd UIEnter * :call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)',
- '--cmd', 'autocmd UILeave * :call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)',
- '--cmd', 'autocmd VimEnter * :call add(g:evs, "VimEnter")',
- }}
+ clear{args_rm={'--headless'}}
+ exec([[
+ let g:evs = []
+ autocmd UIEnter * call add(g:evs, "UIEnter") | let g:uienter_ev = deepcopy(v:event)
+ autocmd UILeave * call add(g:evs, "UILeave") | let g:uileave_ev = deepcopy(v:event)
+ autocmd VimEnter * call add(g:evs, "VimEnter")
+ ]])
local screen = Screen.new()
screen:attach()
eq({chan=1}, eval('g:uienter_ev'))
@@ -56,3 +76,45 @@ it('autocmds UIEnter/UILeave', function()
'UILeave',
}, eval('g:evs'))
end)
+
+it('autocmds VimSuspend/VimResume #22041', function()
+ clear()
+ local screen = Screen.new()
+ screen:attach()
+ exec([[
+ let g:ev = []
+ autocmd VimResume * :call add(g:ev, 'r')
+ autocmd VimSuspend * :call add(g:ev, 's')
+ ]])
+
+ eq(false, screen.suspended)
+ feed('<C-Z>')
+ screen:expect(function() eq(true, screen.suspended) end)
+ eq({ 's' }, eval('g:ev'))
+ screen.suspended = false
+ feed('<Ignore>')
+ eq({ 's', 'r' }, eval('g:ev'))
+
+ command('suspend')
+ screen:expect(function() eq(true, screen.suspended) end)
+ eq({ 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ meths.input_mouse('move', '', '', 0, 0, 0)
+ eq({ 's', 'r', 's', 'r' }, eval('g:ev'))
+
+ feed('<C-Z><C-Z><C-Z>')
+ screen:expect(function() eq(true, screen.suspended) end)
+ meths.ui_set_focus(false)
+ eq({ 's', 'r', 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ meths.ui_set_focus(true)
+ eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev'))
+
+ command('suspend | suspend | suspend')
+ screen:expect(function() eq(true, screen.suspended) end)
+ screen:detach()
+ eq({ 's', 'r', 's', 'r', 's', 'r', 's' }, eval('g:ev'))
+ screen.suspended = false
+ screen:attach()
+ eq({ 's', 'r', 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev'))
+end)
diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua
index 771192e9ab..6d466b0cc1 100644
--- a/test/functional/api/version_spec.lua
+++ b/test/functional/api/version_spec.lua
@@ -34,6 +34,7 @@ describe("api_info()['version']", function()
local minor = version['minor']
local patch = version['patch']
local prerelease = version['prerelease']
+ local build = version['build']
eq("number", type(major))
eq("number", type(minor))
eq("number", type(patch))
@@ -42,6 +43,7 @@ describe("api_info()['version']", function()
eq(0, funcs.has("nvim-"..major.."."..minor.."."..(patch + 1)))
eq(0, funcs.has("nvim-"..major.."."..(minor + 1).."."..patch))
eq(0, funcs.has("nvim-"..(major + 1).."."..minor.."."..patch))
+ assert(build == nil or type(build) == 'string')
end)
end)
diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua
index 8fcdd9620b..8bbadda9b0 100644
--- a/test/functional/api/vim_spec.lua
+++ b/test/functional/api/vim_spec.lua
@@ -1,6 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local lfs = require('lfs')
local luv = require('luv')
local fmt = string.format
@@ -9,6 +8,7 @@ local NIL = helpers.NIL
local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq
local command = helpers.command
local exec = helpers.exec
+local exec_capture = helpers.exec_capture
local eval = helpers.eval
local expect = helpers.expect
local funcs = helpers.funcs
@@ -28,7 +28,6 @@ local write_file = helpers.write_file
local exec_lua = helpers.exec_lua
local exc_exec = helpers.exc_exec
local insert = helpers.insert
-local expect_exit = helpers.expect_exit
local skip = helpers.skip
local pcall_err = helpers.pcall_err
@@ -59,7 +58,7 @@ describe('API', function()
-- XXX: This must be the last one, else next one will fail:
-- "Packer instance already working. Use another Packer ..."
- matches("can't serialize object$",
+ matches("can't serialize object of type .$",
pcall_err(request, nil))
end)
@@ -89,132 +88,145 @@ describe('API', function()
eq({mode='i', blocking=false}, nvim("get_mode"))
end)
- describe('nvim_exec', function()
+ describe('nvim_exec2', function()
+ it('always returns table', function()
+ -- In built version this results into `vim.empty_dict()`
+ eq({}, nvim('exec2', 'echo "Hello"', {}))
+ eq({}, nvim('exec2', 'echo "Hello"', { output = false }))
+ eq({ output = 'Hello' }, nvim('exec2', 'echo "Hello"', { output = true }))
+ end)
+
+ it('default options', function()
+ -- Should be equivalent to { output = false }
+ nvim('exec2', "let x0 = 'a'", {})
+ eq('a', nvim('get_var', 'x0'))
+ end)
+
it('one-line input', function()
- nvim('exec', "let x1 = 'a'", false)
+ nvim('exec2', "let x1 = 'a'", { output = false })
eq('a', nvim('get_var', 'x1'))
end)
it(':verbose set {option}?', function()
- nvim('exec', 'set nowrap', false)
- eq('nowrap\n\tLast set from anonymous :source',
- nvim('exec', 'verbose set wrap?', true))
+ nvim('exec2', 'set nowrap', { output = false })
+ eq({ output = 'nowrap\n\tLast set from anonymous :source' },
+ nvim('exec2', 'verbose set wrap?', { output = true }))
-- Using script var to force creation of a script item
- nvim('exec', [[
+ nvim('exec2', [[
let s:a = 1
set nowrap
- ]], false)
- eq('nowrap\n\tLast set from anonymous :source (script id 1)',
- nvim('exec', 'verbose set wrap?', true))
+ ]], { output = false })
+ eq({ output = 'nowrap\n\tLast set from anonymous :source (script id 1)' },
+ nvim('exec2', 'verbose set wrap?', { output = true }))
end)
it('multiline input', function()
-- Heredoc + empty lines.
- nvim('exec', "let x2 = 'a'\n", false)
+ nvim('exec2', "let x2 = 'a'\n", { output = false })
eq('a', nvim('get_var', 'x2'))
- nvim('exec','lua <<EOF\n\n\n\ny=3\n\n\nEOF', false)
+ nvim('exec2','lua <<EOF\n\n\n\ny=3\n\n\nEOF', { output = false })
eq(3, nvim('eval', "luaeval('y')"))
- eq('', nvim('exec', 'lua <<EOF\ny=3\nEOF', false))
+ eq({}, nvim('exec2', 'lua <<EOF\ny=3\nEOF', { output = false }))
eq(3, nvim('eval', "luaeval('y')"))
-- Multiple statements
- nvim('exec', 'let x1=1\nlet x2=2\nlet x3=3\n', false)
+ nvim('exec2', 'let x1=1\nlet x2=2\nlet x3=3\n', { output = false })
eq(1, nvim('eval', 'x1'))
eq(2, nvim('eval', 'x2'))
eq(3, nvim('eval', 'x3'))
-- Functions
- nvim('exec', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', false)
+ nvim('exec2', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', { output = false })
eq('', nvim('get_current_line'))
- nvim('exec', 'call Foo()', false)
+ nvim('exec2', 'call Foo()', { output = false })
eq('xxx', nvim('get_current_line'))
-- Autocmds
- nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false)
+ nvim('exec2','autocmd BufAdd * :let x1 = "Hello"', { output = false })
nvim('command', 'new foo')
eq('Hello', request('nvim_eval', 'g:x1'))
-- Line continuations
- nvim('exec', [[
+ nvim('exec2', [[
let abc = #{
\ a: 1,
"\ b: 2,
\ c: 3
- \ }]], false)
+ \ }]], { output = false })
eq({a = 1, c = 3}, request('nvim_eval', 'g:abc'))
-- try no spaces before continuations to catch off-by-one error
- nvim('exec', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', false)
+ nvim('exec2', 'let ab = #{\n\\a: 98,\n"\\ b: 2\n\\}', { output = false })
eq({a = 98}, request('nvim_eval', 'g:ab'))
-- Script scope (s:)
- eq('ahoy! script-scoped varrrrr', nvim('exec', [[
+ eq({ output = 'ahoy! script-scoped varrrrr' }, nvim('exec2', [[
let s:pirate = 'script-scoped varrrrr'
function! s:avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
echo <sid>avast_ye_hades('ahoy!')
- ]], true))
+ ]], { output = true }))
- eq('ahoy! script-scoped varrrrr', nvim('exec', [[
+ eq({ output = "{'output': 'ahoy! script-scoped varrrrr'}" }, nvim('exec2', [[
let s:pirate = 'script-scoped varrrrr'
function! Avast_ye_hades(s) abort
return a:s .. ' ' .. s:pirate
endfunction
- echo nvim_exec('echo Avast_ye_hades(''ahoy!'')', 1)
- ]], true))
+ echo nvim_exec2('echo Avast_ye_hades(''ahoy!'')', {'output': v:true})
+ ]], { output = true }))
matches('Vim%(echo%):E121: Undefined variable: s:pirate$',
- pcall_err(request, 'nvim_exec', [[
+ pcall_err(request, 'nvim_exec2', [[
let s:pirate = 'script-scoped varrrrr'
- call nvim_exec('echo s:pirate', 1)
- ]], false))
+ call nvim_exec2('echo s:pirate', {'output': v:true})
+ ]], { output = false }))
-- Script items are created only on script var access
- eq('1\n0', nvim('exec', [[
+ eq({ output = '1\n0' }, nvim('exec2', [[
echo expand("<SID>")->empty()
let s:a = 123
echo expand("<SID>")->empty()
- ]], true))
+ ]], { output = true }))
- eq('1\n0', nvim('exec', [[
+ eq({ output = '1\n0' }, nvim('exec2', [[
echo expand("<SID>")->empty()
function s:a() abort
endfunction
echo expand("<SID>")->empty()
- ]], true))
+ ]], { output = true }))
end)
it('non-ASCII input', function()
- nvim('exec', [=[
+ nvim('exec2', [=[
new
exe "normal! i ax \n Ax "
:%s/ax/--a1234--/g | :%s/Ax/--A1234--/g
- ]=], false)
+ ]=], { output = false })
nvim('command', '1')
eq(' --a1234-- ', nvim('get_current_line'))
nvim('command', '2')
eq(' --A1234-- ', nvim('get_current_line'))
- nvim('exec', [[
+ nvim('exec2', [[
new
call setline(1,['xxx'])
call feedkeys('r')
call feedkeys('ñ', 'xt')
- ]], false)
+ ]], { output = false })
eq('ñxx', nvim('get_current_line'))
end)
it('execution error', function()
- eq('nvim_exec(): Vim:E492: Not an editor command: bogus_command',
- pcall_err(request, 'nvim_exec', 'bogus_command', false))
+ eq('nvim_exec2(): Vim:E492: Not an editor command: bogus_command',
+ pcall_err(request, 'nvim_exec2', 'bogus_command', {}))
eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
- eq('nvim_exec(): Vim(buffer):E86: Buffer 23487 does not exist',
- pcall_err(request, 'nvim_exec', 'buffer 23487', false))
+ eq('nvim_exec2(): Vim(buffer):E86: Buffer 23487 does not exist',
+ pcall_err(request, 'nvim_exec2', 'buffer 23487', {}))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
@@ -222,17 +234,17 @@ describe('API', function()
it('recursion', function()
local fname = tmpname()
write_file(fname, 'let x1 = "set from :source file"\n')
- -- nvim_exec
+ -- nvim_exec2
-- :source
- -- nvim_exec
- request('nvim_exec', [[
+ -- nvim_exec2
+ request('nvim_exec2', [[
let x2 = substitute('foo','o','X','g')
let x4 = 'should be overwritten'
- call nvim_exec("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec','g')\nlet x5='overwritten'\nlet x4=x5\n", v:false)
- ]], false)
+ call nvim_exec2("source ]]..fname..[[\nlet x3 = substitute('foo','foo','set by recursive nvim_exec2','g')\nlet x5='overwritten'\nlet x4=x5\n", {'output': v:false})
+ ]], { output = false })
eq('set from :source file', request('nvim_get_var', 'x1'))
eq('fXX', request('nvim_get_var', 'x2'))
- eq('set by recursive nvim_exec', request('nvim_get_var', 'x3'))
+ eq('set by recursive nvim_exec2', request('nvim_get_var', 'x3'))
eq('overwritten', request('nvim_get_var', 'x4'))
eq('overwritten', request('nvim_get_var', 'x5'))
os.remove(fname)
@@ -242,35 +254,35 @@ describe('API', function()
local fname = tmpname()
write_file(fname, 'echo "hello"\n')
local sourcing_fname = tmpname()
- write_file(sourcing_fname, 'call nvim_exec("source '..fname..'", v:false)\n')
- meths.exec('set verbose=2', false)
+ write_file(sourcing_fname, 'call nvim_exec2("source '..fname..'", {"output": v:false})\n')
+ meths.exec2('set verbose=2', { output = false })
local traceback_output = 'line 0: sourcing "'..sourcing_fname..'"\n'..
'line 0: sourcing "'..fname..'"\n'..
'hello\n'..
'finished sourcing '..fname..'\n'..
- 'continuing in nvim_exec() called at '..sourcing_fname..':1\n'..
+ 'continuing in nvim_exec2() called at '..sourcing_fname..':1\n'..
'finished sourcing '..sourcing_fname..'\n'..
- 'continuing in nvim_exec() called at nvim_exec():0'
- eq(traceback_output,
- meths.exec('call nvim_exec("source '..sourcing_fname..'", v:false)', true))
+ 'continuing in nvim_exec2() called at nvim_exec2():0'
+ eq({ output = traceback_output },
+ meths.exec2('call nvim_exec2("source '..sourcing_fname..'", {"output": v:false})', { output = true }))
os.remove(fname)
os.remove(sourcing_fname)
end)
it('returns output', function()
- eq('this is spinal tap',
- nvim('exec', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', true))
- eq('', nvim('exec', 'echo', true))
- eq('foo 42', nvim('exec', 'echo "foo" 42', true))
+ eq({ output = 'this is spinal tap' },
+ nvim('exec2', 'lua <<EOF\n\n\nprint("this is spinal tap")\n\n\nEOF', { output = true }))
+ eq({ output = '' }, nvim('exec2', 'echo', { output = true }))
+ eq({ output = 'foo 42' }, nvim('exec2', 'echo "foo" 42', { output = true }))
end)
- it('displays messages when output=false', function()
+ it('displays messages when opts.output=false', function()
local screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
})
- meths.exec("echo 'hello'", false)
+ meths.exec2("echo 'hello'", { output = false })
screen:expect{grid=[[
^ |
{0:~ }|
@@ -289,7 +301,7 @@ describe('API', function()
screen:set_default_attr_ids({
[0] = {bold=true, foreground=Screen.colors.Blue},
})
- meths.exec("echo 'hello'", true)
+ meths.exec2("echo 'hello'", { output = true })
screen:expect{grid=[[
^ |
{0:~ }|
@@ -300,7 +312,7 @@ describe('API', function()
]]}
exec([[
func Print()
- call nvim_exec('echo "hello"', v:true)
+ call nvim_exec2('echo "hello"', { 'output': v:true })
endfunc
]])
feed([[:echon 1 | call Print() | echon 5<CR>]])
@@ -322,8 +334,7 @@ describe('API', function()
nvim('command', 'edit '..fname)
nvim('command', 'normal itesting\napi')
nvim('command', 'w')
- local f = io.open(fname)
- ok(f ~= nil)
+ local f = assert(io.open(fname))
if is_os('win') then
eq('testing\r\napi\r\n', f:read('*a'))
else
@@ -333,21 +344,27 @@ describe('API', function()
os.remove(fname)
end)
- it('VimL validation error: fails with specific error', function()
+ it('Vimscript validation error: fails with specific error', function()
local status, rv = pcall(nvim, "command", "bogus_command")
eq(false, status) -- nvim_command() failed.
- eq("E492:", string.match(rv, "E%d*:")) -- VimL error was returned.
+ eq("E492:", string.match(rv, "E%d*:")) -- Vimscript error was returned.
eq('', nvim('eval', 'v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
- it('VimL execution error: fails with specific error', function()
+ it('Vimscript execution error: fails with specific error', function()
local status, rv = pcall(nvim, "command", "buffer 23487")
eq(false, status) -- nvim_command() failed.
eq("E86: Buffer 23487 does not exist", string.match(rv, "E%d*:.*"))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
eq('', eval('v:exception'))
end)
+
+ it('gives E493 instead of prompting on backwards range', function()
+ command('split')
+ eq('Vim(windo):E493: Backwards range given: 2,1windo echo',
+ pcall_err(command, '2,1windo echo'))
+ end)
end)
describe('nvim_command_output', function()
@@ -403,7 +420,7 @@ describe('API', function()
eq(':!echo foo\r\n\nfoo'..win_lf..'\n', nvim('command_output', [[!echo foo]]))
end)
- it('VimL validation error: fails with specific error', function()
+ it('Vimscript validation error: fails with specific error', function()
local status, rv = pcall(nvim, "command_output", "bogus commannnd")
eq(false, status) -- nvim_command_output() failed.
eq("E492: Not an editor command: bogus commannnd",
@@ -413,7 +430,7 @@ describe('API', function()
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
- it('VimL execution error: fails with specific error', function()
+ it('Vimscript execution error: fails with specific error', function()
local status, rv = pcall(nvim, "command_output", "buffer 42")
eq(false, status) -- nvim_command_output() failed.
eq("E86: Buffer 42 does not exist", string.match(rv, "E%d*:.*"))
@@ -422,7 +439,7 @@ describe('API', function()
eq({mode='n', blocking=false}, nvim("get_mode"))
end)
- it('Does not cause heap buffer overflow with large output', function()
+ it('does not cause heap buffer overflow with large output', function()
eq(eval('string(range(1000000))'),
nvim('command_output', 'echo range(1000000)'))
end)
@@ -444,7 +461,7 @@ describe('API', function()
eq(2, request("vim_eval", "1+1"))
end)
- it("VimL error: returns error details, does NOT update v:errmsg", function()
+ it("Vimscript error: returns error details, does NOT update v:errmsg", function()
eq('Vim:E121: Undefined variable: bogus',
pcall_err(request, 'nvim_eval', 'bogus expression'))
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
@@ -459,7 +476,7 @@ describe('API', function()
eq('foo', nvim('call_function', 'simplify', {'this/./is//redundant/../../../foo'}))
end)
- it("VimL validation error: returns specific error, does NOT update v:errmsg", function()
+ it("Vimscript validation error: returns specific error, does NOT update v:errmsg", function()
eq('Vim:E117: Unknown function: bogus function',
pcall_err(request, 'nvim_call_function', 'bogus function', {'arg1'}))
eq('Vim:E119: Not enough arguments for function: atan',
@@ -468,7 +485,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it("VimL error: returns error details, does NOT update v:errmsg", function()
+ it("Vimscript error: returns error details, does NOT update v:errmsg", function()
eq('Vim:E808: Number or Float required',
pcall_err(request, 'nvim_call_function', 'atan', {'foo'}))
eq('Vim:Invalid channel stream "xxx"',
@@ -479,7 +496,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it("VimL exception: returns exception details, does NOT update v:errmsg", function()
+ it("Vimscript exception: returns exception details, does NOT update v:errmsg", function()
source([[
function! Foo() abort
throw 'wtf'
@@ -490,7 +507,7 @@ describe('API', function()
eq('', eval('v:errmsg')) -- v:errmsg was not updated.
end)
- it('validates args', function()
+ it('validation', function()
local too_many_args = { 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x' }
source([[
function! Foo(...) abort
@@ -504,7 +521,7 @@ describe('API', function()
end)
describe('nvim_call_dict_function', function()
- it('invokes VimL dict function', function()
+ it('invokes Vimscript dict function', function()
source([[
function! F(name) dict
return self.greeting.', '.a:name.'!'
@@ -532,7 +549,7 @@ describe('API', function()
eq('@it works@', nvim('call_dict_function', { result = 'it works', G = 'G'}, 'G', {}))
end)
- it('validates args', function()
+ it('validation', function()
command('let g:d={"baz":"zub","meep":[]}')
eq('Not found: bogus',
pcall_err(request, 'nvim_call_dict_function', 'g:d', 'bogus', {1,2}))
@@ -559,7 +576,6 @@ describe('API', function()
local start_dir
before_each(function()
- clear()
funcs.mkdir("Xtestdir")
start_dir = funcs.getcwd()
end)
@@ -575,7 +591,7 @@ describe('API', function()
it('sets previous directory', function()
meths.set_current_dir("Xtestdir")
- meths.exec('cd -', false)
+ command('cd -')
eq(funcs.getcwd(), start_dir)
end)
end)
@@ -634,7 +650,7 @@ describe('API', function()
end)
describe('nvim_input', function()
- it("VimL error: does NOT fail, updates v:errmsg", function()
+ it("Vimscript error: does NOT fail, updates v:errmsg", function()
local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>")
local v_errnum = string.match(nvim("eval", "v:errmsg"), "E%d*:")
eq(true, status) -- nvim_input() did not fail.
@@ -648,10 +664,10 @@ describe('API', function()
end)
describe('nvim_paste', function()
- it('validates args', function()
- eq('Invalid phase: -2',
+ it('validation', function()
+ eq("Invalid 'phase': -2",
pcall_err(request, 'nvim_paste', 'foo', true, -2))
- eq('Invalid phase: 4',
+ eq("Invalid 'phase': 4",
pcall_err(request, 'nvim_paste', 'foo', true, 4))
end)
local function run_streamed_paste_tests()
@@ -1032,7 +1048,7 @@ describe('API', function()
line 3
]])
eq({0,4,1,0}, funcs.getpos('.')) -- Cursor follows the paste.
- eq(false, nvim('get_option', 'paste'))
+ eq(false, nvim('get_option_value', 'paste', {}))
command('%delete _')
-- Without final "\n".
nvim('paste', 'line 1\nline 2\nline 3', true, -1)
@@ -1072,7 +1088,7 @@ describe('API', function()
nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', true, -1)
expect('line 1\n\n\nline 2\nline 3\nline 4\n')
eq({0,7,1,0}, funcs.getpos('.'))
- eq(false, nvim('get_option', 'paste'))
+ eq(false, nvim('get_option_value', 'paste', {}))
end)
it('Replace-mode', function()
-- Within single line
@@ -1154,10 +1170,10 @@ describe('API', function()
end)
describe('nvim_put', function()
- it('validates args', function()
- eq('Invalid lines (expected array of strings)',
+ it('validation', function()
+ eq("Invalid 'line': expected String, got Integer",
pcall_err(request, 'nvim_put', {42}, 'l', false, false))
- eq("Invalid type: 'x'",
+ eq("Invalid 'type': 'x'",
pcall_err(request, 'nvim_put', {'foo'}, 'x', false, false))
end)
it("fails if 'nomodifiable'", function()
@@ -1259,9 +1275,9 @@ describe('API', function()
yyybc line 2
line 3
]])
- eq("Invalid type: 'bx'",
+ eq("Invalid 'type': 'bx'",
pcall_err(meths.put, {'xxx', 'yyy'}, 'bx', false, true))
- eq("Invalid type: 'b3x'",
+ eq("Invalid 'type': 'b3x'",
pcall_err(meths.put, {'xxx', 'yyy'}, 'b3x', false, true))
end)
end)
@@ -1288,6 +1304,11 @@ describe('API', function()
end)
describe('set/get/del variables', function()
+ it('validation', function()
+ eq('Key not found: bogus', pcall_err(meths.get_var, 'bogus'))
+ eq('Key not found: bogus', pcall_err(meths.del_var, 'bogus'))
+ end)
+
it('nvim_get_var, nvim_set_var, nvim_del_var', function()
nvim('set_var', 'lua', {1, 2, {['3'] = 1}})
eq({1, 2, {['3'] = 1}}, nvim('get_var', 'lua'))
@@ -1329,12 +1350,59 @@ describe('API', function()
end)
it('nvim_get_vvar, nvim_set_vvar', function()
- -- Set readonly v: var.
- eq('Key is read-only: count',
- pcall_err(request, 'nvim_set_vvar', 'count', 42))
- -- Set writable v: var.
+ eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42))
+ eq('Dictionary is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42))
meths.set_vvar('errmsg', 'set by API')
eq('set by API', meths.get_vvar('errmsg'))
+ meths.set_vvar('errmsg', 42)
+ eq('42', eval('v:errmsg'))
+ meths.set_vvar('oldfiles', { 'one', 'two' })
+ eq({ 'one', 'two' }, eval('v:oldfiles'))
+ meths.set_vvar('oldfiles', {})
+ eq({}, eval('v:oldfiles'))
+ eq('Setting v:oldfiles to value with wrong type', pcall_err(meths.set_vvar, 'oldfiles', 'a'))
+ eq({}, eval('v:oldfiles'))
+
+ feed('i foo foo foo<Esc>0/foo<CR>')
+ eq({1, 1}, meths.win_get_cursor(0))
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+ meths.set_vvar('searchforward', 0)
+ eq(0, eval('v:searchforward'))
+ feed('n')
+ eq({1, 1}, meths.win_get_cursor(0))
+ meths.set_vvar('searchforward', 1)
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+
+ local screen = Screen.new(60, 3)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Yellow},
+ })
+ screen:attach()
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
+ meths.set_vvar('hlsearch', 0)
+ eq(0, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ foo ^foo foo |
+ {0:~ }|
+ |
+ ]]}
+ meths.set_vvar('hlsearch', 1)
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
end)
it('vim_set_var returns the old value', function()
@@ -1358,56 +1426,71 @@ describe('API', function()
end)
end)
- describe('nvim_get_option, nvim_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- ok(nvim('get_option', 'equalalways'))
- nvim('set_option', 'equalalways', false)
- ok(not nvim('get_option', 'equalalways'))
+ ok(nvim('get_option_value', 'equalalways', {}))
+ nvim('set_option_value', 'equalalways', false, {})
+ ok(not nvim('get_option_value', 'equalalways', {}))
end)
it('works to get global value of local options', function()
- eq(false, nvim('get_option', 'lisp'))
- eq(8, nvim('get_option', 'shiftwidth'))
+ eq(false, nvim('get_option_value', 'lisp', {}))
+ eq(8, nvim('get_option_value', 'shiftwidth', {}))
end)
it('works to set global value of local options', function()
- nvim('set_option', 'lisp', true)
- eq(true, nvim('get_option', 'lisp'))
- eq(false, helpers.curbuf('get_option', 'lisp'))
+ nvim('set_option_value', 'lisp', true, {scope='global'})
+ eq(true, nvim('get_option_value', 'lisp', {scope='global'}))
+ eq(false, nvim('get_option_value', 'lisp', {}))
eq(nil, nvim('command_output', 'setglobal lisp?'):match('nolisp'))
eq('nolisp', nvim('command_output', 'setlocal lisp?'):match('nolisp'))
- nvim('set_option', 'shiftwidth', 20)
+ nvim('set_option_value', 'shiftwidth', 20, {scope='global'})
eq('20', nvim('command_output', 'setglobal shiftwidth?'):match('%d+'))
eq('8', nvim('command_output', 'setlocal shiftwidth?'):match('%d+'))
end)
- it('most window-local options have no global value', function()
- local status, err = pcall(nvim, 'get_option', 'foldcolumn')
- eq(false, status)
- ok(err:match('Invalid option name') ~= nil)
- end)
-
it('updates where the option was last set from', function()
- nvim('set_option', 'equalalways', false)
+ nvim('set_option_value', 'equalalways', false, {})
local status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
ok(nil ~= string.find(rv, 'noequalalways\n'..
'\tLast set from API client %(channel id %d+%)'))
- nvim('exec_lua', 'vim.api.nvim_set_option("equalalways", true)', {})
+ nvim('exec_lua', 'vim.api.nvim_set_option_value("equalalways", true, {})', {})
status, rv = pcall(nvim, 'command_output',
'verbose set equalalways?')
eq(true, status)
eq(' equalalways\n\tLast set from Lua', rv)
end)
- end)
- describe('nvim_get_option_value, nvim_set_option_value', function()
- it('works', function()
- ok(nvim('get_option_value', 'equalalways', {}))
- nvim('set_option_value', 'equalalways', false, {})
- ok(not nvim('get_option_value', 'equalalways', {}))
+ it('updates whether the option has ever been set #25025', function()
+ eq(false, nvim('get_option_info2', 'autochdir', {}).was_set)
+ nvim('set_option_value', 'autochdir', true, {})
+ eq(true, nvim('get_option_info2', 'autochdir', {}).was_set)
+
+ eq(false, nvim('get_option_info2', 'cmdwinheight', {}).was_set)
+ nvim('set_option_value', 'cmdwinheight', 10, {})
+ eq(true, nvim('get_option_info2', 'cmdwinheight', {}).was_set)
+
+ eq(false, nvim('get_option_info2', 'debug', {}).was_set)
+ nvim('set_option_value', 'debug', 'beep', {})
+ eq(true, nvim('get_option_info2', 'debug', {}).was_set)
+ end)
+
+ it('validation', function()
+ eq("Invalid 'scope': expected 'local' or 'global'",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 'bogus'}))
+ eq("Invalid 'scope': expected 'local' or 'global'",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', 1, {scope = 'bogus'}))
+ eq("Invalid 'scope': expected String, got Integer",
+ pcall_err(nvim, 'get_option_value', 'scrolloff', {scope = 42}))
+ eq("Invalid 'value': expected valid option type, got Array",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', {}, {}))
+ eq("Invalid value for option 'scrolloff': expected Number, got Boolean true",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', true, {}))
+ eq("Invalid value for option 'scrolloff': expected Number, got String \"wrong\"",
+ pcall_err(nvim, 'set_option_value', 'scrolloff', 'wrong', {}))
end)
it('can get local values when global value is set', function()
@@ -1453,19 +1536,22 @@ describe('API', function()
-- Now try with options with a special "local is unset" value (e.g. 'undolevels')
nvim('set_option_value', 'undolevels', 1000, {})
- eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'}))
+ nvim('set_option_value', 'undolevels', 1200, {scope = 'local'})
+ eq(1200, nvim('get_option_value', 'undolevels', {scope = 'local'}))
nvim('set_option_value', 'undolevels', NIL, {scope = 'local'})
eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'}))
+ eq(1000, nvim('get_option_value', 'undolevels', {}))
nvim('set_option_value', 'autoread', true, {})
- eq(true, nvim('get_option_value', 'autoread', {scope = 'local'}))
+ nvim('set_option_value', 'autoread', false, {scope = 'local'})
+ eq(false, nvim('get_option_value', 'autoread', {scope = 'local'}))
nvim('set_option_value', 'autoread', NIL, {scope = 'local'})
eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'}))
+ eq(true, nvim('get_option_value', 'autoread', {}))
end)
it('set window options', function()
- -- Same as to nvim_win_set_option
- nvim('set_option_value', 'colorcolumn', '4,3', {win=0})
+ nvim('set_option_value', 'colorcolumn', '4,3', {})
eq('4,3', nvim('get_option_value', 'colorcolumn', {scope = 'local'}))
command("set modified hidden")
command("enew") -- edit new buffer, window option is preserved
@@ -1473,7 +1559,6 @@ describe('API', function()
end)
it('set local window options', function()
- -- Different to nvim_win_set_option
nvim('set_option_value', 'colorcolumn', '4,3', {win=0, scope='local'})
eq('4,3', nvim('get_option_value', 'colorcolumn', {win = 0, scope = 'local'}))
command("set modified hidden")
@@ -1484,11 +1569,11 @@ describe('API', function()
it('get buffer or window-local options', function()
nvim('command', 'new')
local buf = nvim('get_current_buf').id
- nvim('buf_set_option', buf, 'tagfunc', 'foobar')
+ nvim('set_option_value', 'tagfunc', 'foobar', {buf=buf})
eq('foobar', nvim('get_option_value', 'tagfunc', {buf = buf}))
local win = nvim('get_current_win').id
- nvim('win_set_option', win, 'number', true)
+ nvim('set_option_value', 'number', true, {win=win})
eq(true, nvim('get_option_value', 'number', {win = win}))
end)
@@ -1502,6 +1587,40 @@ describe('API', function()
nvim('get_option_value', 'filetype', {buf = buf})
eq({1, 9}, nvim('win_get_cursor', win))
end)
+
+ it('can get default option values for filetypes', function()
+ command('filetype plugin on')
+ for ft, opts in pairs {
+ lua = { commentstring = '-- %s' },
+ vim = { commentstring = '"%s' },
+ man = { tagfunc = 'v:lua.require\'man\'.goto_tag' },
+ xml = { formatexpr = 'xmlformat#Format()' }
+ } do
+ for option, value in pairs(opts) do
+ eq(value, nvim('get_option_value', option, { filetype = ft }))
+ end
+ end
+
+ command'au FileType lua setlocal commentstring=NEW\\ %s'
+
+ eq('NEW %s', nvim('get_option_value', 'commentstring', { filetype = 'lua' }))
+ end)
+
+ it('errors for bad FileType autocmds', function()
+ command'au FileType lua setlocal commentstring=BAD'
+ eq([[FileType Autocommands for "lua": Vim(setlocal):E537: 'commentstring' must be empty or contain %s: commentstring=BAD]],
+ pcall_err(nvim, 'get_option_value', 'commentstring', { filetype = 'lua' }))
+ end)
+
+ it("value of 'modified' is always false for scratch buffers", function()
+ nvim('set_current_buf', nvim('create_buf', true, true))
+ insert([[
+ foo
+ bar
+ baz
+ ]])
+ eq(false, nvim('get_option_value', 'modified', {}))
+ end)
end)
describe('nvim_{get,set}_current_buf, nvim_list_bufs', function()
@@ -1774,15 +1893,32 @@ describe('API', function()
feed('<C-D>')
expect('a') -- recognized i_0_CTRL-D
end)
+
+ it("does not interrupt with 'digraph'", function()
+ command('set digraph')
+ feed('i,')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('<BS>')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('.')
+ expect('…') -- digraph ",." worked
+ feed('<Esc>')
+ feed(':,')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('<BS>')
+ eq(2, eval('1+1')) -- causes K_EVENT key
+ feed('.')
+ eq('…', funcs.getcmdline()) -- digraph ",." worked
+ end)
end)
describe('nvim_get_context', function()
- it('validates args', function()
+ it('validation', function()
eq("Invalid key: 'blah'",
pcall_err(nvim, 'get_context', {blah={}}))
- eq('invalid value for key: types',
+ eq("Invalid 'types': expected Array, got Integer",
pcall_err(nvim, 'get_context', {types=42}))
- eq('unexpected type: zub',
+ eq("Invalid 'type': 'zub'",
pcall_err(nvim, 'get_context', {types={'jumps', 'zub', 'zam',}}))
end)
it('returns map of current editor state', function()
@@ -1843,6 +1979,13 @@ describe('API', function()
nvim('load_context', ctx)
eq({1, 2 ,3}, eval('[g:one, g:Two, g:THREE]'))
end)
+
+ it('errors when context dictionary is invalid', function()
+ eq('E474: Failed to convert list to msgpack string buffer',
+ pcall_err(nvim, 'load_context', { regs = { {} }, jumps = { {} } }))
+ eq("Empty dictionary keys aren't allowed",
+ pcall_err(nvim, 'load_context', { regs = { { [''] = '' } } }))
+ end)
end)
describe('nvim_replace_termcodes', function()
@@ -1886,6 +2029,12 @@ describe('API', function()
-- value.
eq('', meths.replace_termcodes('', true, true, true))
end)
+
+ -- Not exactly the case, as nvim_replace_termcodes() escapes K_SPECIAL in Unicode
+ it('translates the result of keytrans() on string with 0x80 byte back', function()
+ local s = 'ff\128\253\097tt'
+ eq(s, meths.replace_termcodes(funcs.keytrans(s), true, true, true))
+ end)
end)
describe('nvim_feedkeys', function()
@@ -1916,6 +2065,19 @@ describe('API', function()
end)
describe('nvim_out_write', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [2] = {bold = true, reverse = true},
+ [3] = {foreground = Screen.colors.Blue},
+ })
+ end)
+
it('prints long messages correctly #20534', function()
exec([[
set more
@@ -1935,6 +2097,46 @@ describe('API', function()
]])
eq('\naaa\n' .. ('a'):rep(5002) .. '\naaa', meths.get_var('out'))
end)
+
+ it('blank line in message', function()
+ feed([[:call nvim_out_write("\na\n")<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ |
+ a |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<CR>')
+ feed([[:call nvim_out_write("b\n\nc\n")<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ b |
+ |
+ c |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+
+ it('NUL bytes in message', function()
+ feed([[:lua vim.api.nvim_out_write('aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ aaa{3:^@}bbb{3:^@^@}ccc |
+ ddd{3:^@^@^@}eee |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
end)
describe('nvim_err_write', function()
@@ -2023,6 +2225,60 @@ describe('API', function()
]])
feed('<cr>') -- exit the press ENTER screen
end)
+
+ it('NUL bytes in message', function()
+ nvim_async('err_write', 'aaa\0bbb\0\0ccc\nddd\0\0\0eee\n')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3: }|
+ {1:aaa^@bbb^@^@ccc} |
+ {1:ddd^@^@^@eee} |
+ {2:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
+ end)
+
+ describe('nvim_err_writeln', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold=true, foreground=Screen.colors.Blue},
+ [1] = {foreground = Screen.colors.White, background = Screen.colors.Red},
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [3] = {bold = true, reverse = true},
+ })
+ end)
+
+ it('shows only one return prompt after all lines are shown', function()
+ nvim_async('err_writeln', 'FAILURE\nERROR\nEXCEPTION\nTRACEBACK')
+ screen:expect([[
+ |
+ {0:~ }|
+ {3: }|
+ {1:FAILURE} |
+ {1:ERROR} |
+ {1:EXCEPTION} |
+ {1:TRACEBACK} |
+ {2:Press ENTER or type command to continue}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
end)
describe('nvim_list_chans, nvim_get_chan_info', function()
@@ -2115,7 +2371,7 @@ describe('API', function()
it('stream=job :terminal channel', function()
command(':terminal')
eq({id=1}, meths.get_current_buf())
- eq(3, meths.buf_get_option(1, 'channel'))
+ eq(3, meths.get_option_value('channel', {buf=1}))
local info = {
stream='job',
@@ -2223,15 +2479,14 @@ describe('API', function()
eq(5, meths.get_var('avar'))
end)
- it('throws error on malformed arguments', function()
+ it('validation', function()
local req = {
{'nvim_set_var', {'avar', 1}},
{'nvim_set_var'},
{'nvim_set_var', {'avar', 2}},
}
- local status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays of size 2') ~= nil)
+ eq("Invalid 'calls' item: expected 2-item Array",
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
@@ -2239,18 +2494,16 @@ describe('API', function()
{ 'nvim_set_var', { 'bvar', { 2, 3 } } },
12,
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Items in calls array must be arrays') ~= nil)
+ eq("Invalid 'calls' item: expected Array, got Integer",
+ pcall_err(meths.call_atomic, req))
eq({2,3}, meths.get_var('bvar'))
req = {
{'nvim_set_current_line', 'little line'},
{'nvim_set_var', {'avar', 3}},
}
- status, err = pcall(meths.call_atomic, req)
- eq(false, status)
- ok(err:match('Args must be Array') ~= nil)
+ eq("Invalid call args: expected Array, got String",
+ pcall_err(meths.call_atomic, req))
-- call before was done, but not after
eq(1, meths.get_var('avar'))
eq({''}, meths.buf_get_lines(0, 0, -1, true))
@@ -2271,45 +2524,45 @@ describe('API', function()
end)
it('returns nothing with empty &runtimepath', function()
- meths.set_option('runtimepath', '')
+ meths.set_option_value('runtimepath', '', {})
eq({}, meths.list_runtime_paths())
end)
it('returns single runtimepath', function()
- meths.set_option('runtimepath', 'a')
+ meths.set_option_value('runtimepath', 'a', {})
eq({'a'}, meths.list_runtime_paths())
end)
it('returns two runtimepaths', function()
- meths.set_option('runtimepath', 'a,b')
+ meths.set_option_value('runtimepath', 'a,b', {})
eq({'a', 'b'}, meths.list_runtime_paths())
end)
it('returns empty strings when appropriate', function()
- meths.set_option('runtimepath', 'a,,b')
+ meths.set_option_value('runtimepath', 'a,,b', {})
eq({'a', '', 'b'}, meths.list_runtime_paths())
- meths.set_option('runtimepath', ',a,b')
+ meths.set_option_value('runtimepath', ',a,b', {})
eq({'', 'a', 'b'}, meths.list_runtime_paths())
-- Trailing "," is ignored. Use ",," if you really really want CWD.
- meths.set_option('runtimepath', 'a,b,')
+ meths.set_option_value('runtimepath', 'a,b,', {})
eq({'a', 'b'}, meths.list_runtime_paths())
- meths.set_option('runtimepath', 'a,b,,')
+ meths.set_option_value('runtimepath', 'a,b,,', {})
eq({'a', 'b', ''}, meths.list_runtime_paths())
end)
it('truncates too long paths', function()
local long_path = ('/a'):rep(8192)
- meths.set_option('runtimepath', long_path)
+ meths.set_option_value('runtimepath', long_path, {})
local paths_list = meths.list_runtime_paths()
eq({}, paths_list)
end)
end)
it('can throw exceptions', function()
- local status, err = pcall(nvim, 'get_option', 'invalid-option')
+ local status, err = pcall(nvim, 'get_option_value', 'invalid-option', {})
eq(false, status)
- ok(err:match('Invalid option name') ~= nil)
+ ok(err:match("Unknown option 'invalid%-option'") ~= nil)
end)
it('does not truncate error message <1 MB #5984', function()
local very_long_name = 'A'..('x'):rep(10000)..'Z'
- local status, err = pcall(nvim, 'get_option', very_long_name)
+ local status, err = pcall(nvim, 'get_option_value', very_long_name, {})
eq(false, status)
eq(very_long_name, err:match('Ax+Z?'))
end)
@@ -2322,7 +2575,7 @@ describe('API', function()
describe('nvim_parse_expression', function()
before_each(function()
- meths.set_option('isident', '')
+ meths.set_option_value('isident', '', {})
end)
local function simplify_east_api_node(line, east_api_node)
@@ -2536,20 +2789,26 @@ describe('API', function()
{
chan = 1,
ext_cmdline = false,
- ext_popupmenu = false,
- ext_tabline = false,
- ext_wildmenu = false,
+ ext_hlstate = false,
ext_linegrid = screen._options.ext_linegrid or false,
+ ext_messages = false,
ext_multigrid = false,
- ext_hlstate = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
ext_termcolors = false,
- ext_messages = false,
+ ext_wildmenu = false,
height = 4,
- rgb = true,
override = true,
+ rgb = true,
+ stdin_tty = false,
+ stdout_tty = false,
+ term_background = '',
+ term_colors = 0,
+ term_name = '',
width = 20,
}
}
+
eq(expected, nvim("list_uis"))
screen:detach()
@@ -2601,9 +2860,9 @@ describe('API', function()
end)
it('can change buftype before visiting', function()
- meths.set_option("hidden", false)
+ meths.set_option_value("hidden", false, {})
eq({id=2}, meths.create_buf(true, false))
- meths.buf_set_option(2, "buftype", "nofile")
+ meths.set_option_value("buftype", "nofile", {buf=2})
meths.buf_set_lines(2, 0, -1, true, {"test text"})
command("split | buffer 2")
eq({id=2}, meths.get_current_buf())
@@ -2622,6 +2881,18 @@ describe('API', function()
eq(false, eval('g:fired'))
end)
+ it('TextChanged and TextChangedI do not trigger without changes', function()
+ local buf = meths.create_buf(true, false)
+ command([[let g:changed = '']])
+ meths.create_autocmd({'TextChanged', 'TextChangedI'}, {
+ buffer = buf,
+ command = 'let g:changed ..= mode()',
+ })
+ meths.set_current_buf(buf)
+ feed('i')
+ eq('', meths.get_var('changed'))
+ end)
+
it('scratch-buffer', function()
eq({id=2}, meths.create_buf(false, true))
eq({id=3}, meths.create_buf(true, true))
@@ -2630,7 +2901,7 @@ describe('API', function()
eq(' 1 %a "[No Name]" line 1\n'..
' 3 h "[Scratch]" line 0\n'..
' 4 h "[Scratch]" line 0',
- meths.exec('ls', true))
+ exec_capture('ls'))
-- current buffer didn't change
eq({id=1}, meths.get_current_buf())
@@ -2646,10 +2917,10 @@ describe('API', function()
local edited_buf = 2
meths.buf_set_lines(edited_buf, 0, -1, true, {"some text"})
for _,b in ipairs(scratch_bufs) do
- eq('nofile', meths.buf_get_option(b, 'buftype'))
- eq('hide', meths.buf_get_option(b, 'bufhidden'))
- eq(false, meths.buf_get_option(b, 'swapfile'))
- eq(false, meths.buf_get_option(b, 'modeline'))
+ eq('nofile', meths.get_option_value('buftype', {buf=b}))
+ eq('hide', meths.get_option_value('bufhidden', {buf=b}))
+ eq(false, meths.get_option_value('swapfile', {buf=b}))
+ eq(false, meths.get_option_value('modeline', {buf=b}))
end
--
@@ -2662,10 +2933,10 @@ describe('API', function()
{1:~ }|
|
]])
- eq('nofile', meths.buf_get_option(edited_buf, 'buftype'))
- eq('hide', meths.buf_get_option(edited_buf, 'bufhidden'))
- eq(false, meths.buf_get_option(edited_buf, 'swapfile'))
- eq(false, meths.buf_get_option(edited_buf, 'modeline'))
+ eq('nofile', meths.get_option_value('buftype', {buf=edited_buf}))
+ eq('hide', meths.get_option_value('bufhidden', {buf=edited_buf}))
+ eq(false, meths.get_option_value('swapfile', {buf=edited_buf}))
+ eq(false, meths.get_option_value('modeline', {buf=edited_buf}))
-- Scratch buffer can be wiped without error.
command('bwipe')
@@ -2679,7 +2950,9 @@ describe('API', function()
it('does not cause heap-use-after-free on exit while setting options', function()
command('au OptionSet * q')
- expect_exit(command, 'silent! call nvim_create_buf(0, 1)')
+ command('silent! call nvim_create_buf(0, 1)')
+ -- nowadays this works because we don't execute any spurious autocmds at all #24824
+ assert_alive()
end)
end)
@@ -2744,13 +3017,13 @@ describe('API', function()
end)
it('should not crash when echoed', function()
- meths.exec("echo nvim_get_all_options_info()", true)
+ meths.exec2("echo nvim_get_all_options_info()", { output = true })
end)
end)
describe('nvim_get_option_info', function()
it('should error for unknown options', function()
- eq("no such option: 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
+ eq("Invalid option (not found): 'bogus'", pcall_err(meths.get_option_info, 'bogus'))
end)
it('should return the same options for short and long name', function()
@@ -2796,7 +3069,7 @@ describe('API', function()
it('should have information about global options', function()
-- precondition: the option was changed from its default
-- in test setup.
- eq(false, meths.get_option'showcmd')
+ eq(false, meths.get_option_value('showcmd', {}))
eq({
allows_duplicates = true,
@@ -2813,6 +3086,117 @@ describe('API', function()
type = "boolean",
was_set = true
}, meths.get_option_info'showcmd')
+
+ meths.set_option_value('showcmd', true, {})
+
+ eq({
+ allows_duplicates = true,
+ commalist = false,
+ default = true,
+ flaglist = false,
+ global_local = false,
+ last_set_chan = 1,
+ last_set_linenr = 0,
+ last_set_sid = -9,
+ name = "showcmd",
+ scope = "global",
+ shortname = "sc",
+ type = "boolean",
+ was_set = true
+ }, meths.get_option_info'showcmd')
+ end)
+ end)
+
+ describe('nvim_get_option_info2', function()
+ local fname
+ local bufs
+ local wins
+
+ before_each(function()
+ fname = tmpname()
+ write_file(fname, [[
+ setglobal dictionary=mydict " 1, global-local (buffer)
+ setlocal formatprg=myprg " 2, global-local (buffer)
+ setglobal equalprg=prg1 " 3, global-local (buffer)
+ setlocal equalprg=prg2 " 4, global-local (buffer)
+ setglobal fillchars=stl:x " 5, global-local (window)
+ setlocal listchars=eol:c " 6, global-local (window)
+ setglobal showbreak=aaa " 7, global-local (window)
+ setlocal showbreak=bbb " 8, global-local (window)
+ setglobal completeopt=menu " 9, global
+ ]])
+
+ exec_lua 'vim.cmd.vsplit()'
+ meths.create_buf(false, false)
+
+ bufs = meths.list_bufs()
+ wins = meths.list_wins()
+
+ meths.win_set_buf(wins[1].id, bufs[1].id)
+ meths.win_set_buf(wins[2].id, bufs[2].id)
+
+ meths.set_current_win(wins[2].id)
+ meths.exec('source ' .. fname, false)
+
+ meths.set_current_win(wins[1].id)
+ end)
+
+ after_each(function()
+ os.remove(fname)
+ end)
+
+ it('should return option information', function()
+ eq(meths.get_option_info('dictionary'), meths.get_option_info2('dictionary', {})) -- buffer
+ eq(meths.get_option_info('fillchars'), meths.get_option_info2('fillchars', {})) -- window
+ eq(meths.get_option_info('completeopt'), meths.get_option_info2('completeopt', {})) -- global
+ end)
+
+ describe('last set', function()
+ local tests = {
+ {desc="(buf option, global requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {scope='global'}}},
+ {desc="(buf option, global requested, local set) is not set", linenr=0, sid=0, args={'formatprg', {scope='global'}}},
+ {desc="(buf option, global requested, both set) points to global", linenr=3, sid=1, args={'equalprg', {scope='global'}}},
+ {desc="(buf option, local requested, global set) is not set", linenr=0, sid=0, args={'dictionary', {scope='local'}}},
+ {desc="(buf option, local requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {scope='local'}}},
+ {desc="(buf option, local requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {scope='local'}}},
+ {desc="(buf option, fallback requested, global set) points to global", linenr=1, sid=1, args={'dictionary', {}}},
+ {desc="(buf option, fallback requested, local set) points to local", linenr=2, sid=1, args={'formatprg', {}}},
+ {desc="(buf option, fallback requested, both set) points to local", linenr=4, sid=1, args={'equalprg', {}}},
+ {desc="(win option, global requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {scope='global'}}},
+ {desc="(win option, global requested, local set) is not set", linenr=0, sid=0, args={'listchars', {scope='global'}}},
+ {desc="(win option, global requested, both set) points to global", linenr=7, sid=1, args={'showbreak', {scope='global'}}},
+ {desc="(win option, local requested, global set) is not set", linenr=0, sid=0, args={'fillchars', {scope='local'}}},
+ {desc="(win option, local requested, local set) points to local", linenr=6, sid=1, args={'listchars', {scope='local'}}},
+ {desc="(win option, local requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {scope='local'}}},
+ {desc="(win option, fallback requested, global set) points to global", linenr=5, sid=1, args={'fillchars', {}}},
+ {desc="(win option, fallback requested, local set) points to local", linenr=6, sid=1, args={'listchars', {}}},
+ {desc="(win option, fallback requested, both set) points to local", linenr=8, sid=1, args={'showbreak', {}}},
+ {desc="(global option, global requested) points to global", linenr=9, sid=1, args={'completeopt', {scope='global'}}},
+ {desc="(global option, local requested) is not set", linenr=0, sid=0, args={'completeopt', {scope='local'}}},
+ {desc="(global option, fallback requested) points to global", linenr=9, sid=1, args={'completeopt', {}}},
+ }
+
+ for _, t in pairs(tests) do
+ it(t.desc, function()
+ -- Switch to the target buffer/window so that curbuf/curwin are used.
+ meths.set_current_win(wins[2].id)
+ local info = meths.get_option_info2(unpack(t.args))
+ eq(t.linenr, info.last_set_linenr)
+ eq(t.sid, info.last_set_sid)
+ end)
+ end
+
+ it('is provided for cross-buffer requests', function()
+ local info = meths.get_option_info2('formatprg', {buf=bufs[2].id})
+ eq(2, info.last_set_linenr)
+ eq(1, info.last_set_sid)
+ end)
+
+ it('is provided for cross-window requests', function()
+ local info = meths.get_option_info2('listchars', {win=wins[2].id})
+ eq(6, info.last_set_linenr)
+ eq(1, info.last_set_sid)
+ end)
end)
end)
@@ -2820,11 +3204,10 @@ describe('API', function()
local screen
before_each(function()
- clear()
screen = Screen.new(40, 8)
screen:attach()
screen:set_default_attr_ids({
- [0] = {bold=true, foreground=Screen.colors.Blue},
+ [0] = {bold = true, foreground = Screen.colors.Blue},
[1] = {bold = true, foreground = Screen.colors.SeaGreen},
[2] = {bold = true, reverse = true},
[3] = {foreground = Screen.colors.Brown, bold = true}, -- Statement
@@ -2879,13 +3262,13 @@ describe('API', function()
it('can save message history', function()
nvim('command', 'set cmdheight=2') -- suppress Press ENTER
nvim("echo", {{"msg\nmsg"}, {"msg"}}, true, {})
- eq("msg\nmsgmsg", meths.exec('messages', true))
+ eq("msg\nmsgmsg", exec_capture('messages'))
end)
it('can disable saving message history', function()
nvim('command', 'set cmdheight=2') -- suppress Press ENTER
nvim_async("echo", {{"msg\nmsg"}, {"msg"}}, false, {})
- eq("", meths.exec("messages", true))
+ eq("", exec_capture('messages'))
end)
end)
@@ -2894,7 +3277,6 @@ describe('API', function()
local screen
before_each(function()
- clear()
screen = Screen.new(100, 35)
screen:attach()
screen:set_default_attr_ids({
@@ -3031,10 +3413,10 @@ describe('API', function()
eq(true, meths.del_mark('F'))
eq({0, 0}, meths.buf_get_mark(buf, 'F'))
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.del_mark, 'f'))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.del_mark, '!'))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.del_mark, 'fail'))
end)
end)
describe('nvim_get_mark', function()
@@ -3048,10 +3430,10 @@ describe('API', function()
assert(string.find(mark[4], "mybuf$"))
eq({2, 2, buf.id, mark[4]}, mark)
end)
- it('fails when invalid marks are used', function()
- eq(false, pcall(meths.del_mark, 'f'))
- eq(false, pcall(meths.del_mark, '!'))
- eq(false, pcall(meths.del_mark, 'fail'))
+ it('validation', function()
+ eq("Invalid mark name (must be file/uppercase): 'f'", pcall_err(meths.get_mark, 'f', {}))
+ eq("Invalid mark name (must be file/uppercase): '!'", pcall_err(meths.get_mark, '!', {}))
+ eq("Invalid mark name (must be a single char): 'fail'", pcall_err(meths.get_mark, 'fail', {}))
end)
it('returns the expected when mark is not set', function()
eq(true, meths.del_mark('A'))
@@ -3113,15 +3495,15 @@ describe('API', function()
meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 }))
end)
it('rejects multiple-character fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected single character",
pcall_err(meths.eval_statusline, '', { fillchar = 'aa' }))
end)
it('rejects empty string fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected single character",
pcall_err(meths.eval_statusline, '', { fillchar = '' }))
end)
it('rejects non-string fillchar', function()
- eq('fillchar must be a single character',
+ eq("Invalid 'fillchar': expected String, got Integer",
pcall_err(meths.eval_statusline, '', { fillchar = 1 }))
end)
it('rejects invalid string', function()
@@ -3213,6 +3595,38 @@ describe('API', function()
'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight',
{ use_winbar = true, highlights = true }))
end)
+ it('works with statuscolumn', function()
+ exec([[
+ let &stc='%C%s%=%l '
+ set cul nu nuw=3 scl=yes:2 fdc=2
+ call setline(1, repeat(['aaaaa'], 5))
+ let g:ns = nvim_create_namespace('')
+ call sign_define('a', {'text':'aa', 'texthl':'IncSearch', 'numhl':'Normal'})
+ call sign_place(2, 1, 'a', bufnr(), {'lnum':4})
+ call nvim_buf_set_extmark(0, g:ns, 3, 1, { 'sign_text':'bb', 'sign_hl_group':'ErrorMsg' })
+ 1,5fold | 1,5 fold | foldopen!
+ norm 4G
+ ]])
+ eq({
+ str = '││aabb 4 ',
+ width = 9,
+ highlights = {
+ { group = 'CursorLineFold', start = 0 },
+ { group = 'Normal', start = 6 },
+ { group = 'IncSearch', start = 6 },
+ { group = 'ErrorMsg', start = 8 },
+ { group = 'Normal', start = 10 }
+ }
+ }, meths.eval_statusline('%C%s%=%l ', { use_statuscol_lnum = 4, highlights = true }))
+ eq({
+ str = '3 ' ,
+ width = 2,
+ highlights = {
+ { group = 'LineNr', start = 0 },
+ { group = 'ErrorMsg', start = 1 }
+ }
+ }, meths.eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }))
+ end)
it('no memory leak with click functions', function()
meths.eval_statusline('%@ClickFunc@StatusLineStringWithClickFunc%T', {})
eq({
@@ -3226,6 +3640,7 @@ describe('API', function()
end)
end)
end)
+
describe('nvim_parse_cmd', function()
it('works', function()
eq({
@@ -3708,7 +4123,7 @@ describe('API', function()
}
}, meths.parse_cmd('MyCommand test it', {}))
end)
- it('errors for invalid command', function()
+ it('validates command', function()
eq('Error while parsing command line', pcall_err(meths.parse_cmd, '', {}))
eq('Error while parsing command line', pcall_err(meths.parse_cmd, '" foo', {}))
eq('Error while parsing command line: E492: Not an editor command: Fubar',
@@ -3807,20 +4222,87 @@ describe('API', function()
meths.cmd(meths.parse_cmd("set cursorline", {}), {})
eq(true, meths.get_option_value("cursorline", {}))
end)
+ it('no side-effects (error messages) in pcall() #20339', function()
+ eq({ false, 'Error while parsing command line: E16: Invalid range' },
+ exec_lua([=[return {pcall(vim.api.nvim_parse_cmd, "'<,'>n", {})}]=]))
+ eq('', eval('v:errmsg'))
+ end)
end)
+
describe('nvim_cmd', function()
it('works', function ()
meths.cmd({ cmd = "set", args = { "cursorline" } }, {})
eq(true, meths.get_option_value("cursorline", {}))
end)
+
+ it('validation', function()
+ eq("Invalid 'cmd': expected non-empty String",
+ pcall_err(meths.cmd, { cmd = ""}, {}))
+ eq("Invalid 'cmd': expected String, got Array",
+ pcall_err(meths.cmd, { cmd = {}}, {}))
+ eq("Invalid 'args': expected Array, got Boolean",
+ pcall_err(meths.cmd, { cmd = "set", args = true }, {}))
+ eq("Invalid command arg: expected non-whitespace",
+ pcall_err(meths.cmd, { cmd = "set", args = {' '}, }, {}))
+ eq("Invalid command arg: expected valid type, got Array",
+ pcall_err(meths.cmd, { cmd = "set", args = {{}}, }, {}))
+ eq("Wrong number of arguments",
+ pcall_err(meths.cmd, { cmd = "aboveleft", args = {}, }, {}))
+ eq("Command cannot accept bang: print",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, bang = true }, {}))
+
+ eq("Command cannot accept range: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, range = {1} }, {}))
+ eq("Invalid 'range': expected Array, got Boolean",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = true }, {}))
+ eq("Invalid 'range': expected <=2 elements",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = {1,2,3,4} }, {}))
+ eq("Invalid range element: expected non-negative Integer",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, range = {-1} }, {}))
+
+ eq("Command cannot accept count: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, count = 1 }, {}))
+ eq("Invalid 'count': expected Integer, got Boolean",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, count = true }, {}))
+ eq("Invalid 'count': expected non-negative Integer",
+ pcall_err(meths.cmd, { cmd = "print", args = {}, count = -1 }, {}))
+
+ eq("Command cannot accept register: set",
+ pcall_err(meths.cmd, { cmd = "set", args = {}, reg = 'x' }, {}))
+ eq('Cannot use register "=',
+ pcall_err(meths.cmd, { cmd = "put", args = {}, reg = '=' }, {}))
+ eq("Invalid 'reg': expected single character, got xx",
+ pcall_err(meths.cmd, { cmd = "put", args = {}, reg = 'xx' }, {}))
+
+ -- #20681
+ eq('Invalid command: "win_getid"', pcall_err(meths.cmd, { cmd = 'win_getid'}, {}))
+ eq('Invalid command: "echo "hi""', pcall_err(meths.cmd, { cmd = 'echo "hi"'}, {}))
+ eq('Invalid command: "win_getid"', pcall_err(exec_lua, [[return vim.cmd.win_getid{}]]))
+
+ -- Lua call allows empty {} for dict item.
+ eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, magic = {} }]]))
+ eq('', exec_lua([[return vim.cmd{ cmd = "set", args = {}, mods = {} }]]))
+ eq('', meths.cmd({ cmd = "set", args = {}, magic = {} }, {}))
+
+ -- Lua call does not allow non-empty list-like {} for dict item.
+ eq("Invalid 'magic': Expected Dict-like Lua table",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { 'a' } }]]))
+ eq("Invalid key: 'bogus'",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, magic = { bogus = true } }]]))
+ eq("Invalid key: 'bogus'",
+ pcall_err(exec_lua, [[return vim.cmd{ cmd = "set", args = {}, mods = { bogus = true } }]]))
+ end)
+
it('captures output', function()
eq("foo", meths.cmd({ cmd = "echo", args = { '"foo"' } }, { output = true }))
end)
+
it('sets correct script context', function()
meths.cmd({ cmd = "set", args = { "cursorline" } }, {})
- local str = meths.exec([[verbose set cursorline?]], true)
+ local str = exec_capture([[verbose set cursorline?]])
neq(nil, str:find("cursorline\n\tLast set from API client %(channel id %d+%)"))
end)
+
it('works with range', function()
insert [[
line1
@@ -3867,7 +4349,7 @@ describe('API', function()
line6
]]
meths.cmd({ cmd = "del", range = { 2, 4 }, reg = 'a' }, {})
- meths.exec("1put a", false)
+ command('1put a')
expect [[
line1
line2
@@ -3914,7 +4396,7 @@ describe('API', function()
meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } },
{ output = true }))
- -- with emsg_silent = true error is suppresed
+ -- with emsg_silent = true error is suppressed
feed([[:lua vim.api.nvim_cmd({ cmd = 'call', mods = { emsg_silent = true } }, {})<CR>]])
eq('', meths.cmd({ cmd = 'messages' }, { output = true }))
-- error from the next command typed is not suppressed #21420
@@ -3927,16 +4409,16 @@ describe('API', function()
vim.api.nvim_echo({{ opts.fargs[1] }}, false, {})
end, { nargs = 1 })
]])
- eq(lfs.currentdir(),
+ eq(luv.cwd(),
meths.cmd({ cmd = "Foo", args = { '%:p:h' }, magic = { file = true } },
{ output = true }))
end)
it('splits arguments correctly', function()
- meths.exec([[
+ exec([[
function! FooFunc(...)
echo a:000
endfunction
- ]], false)
+ ]])
meths.create_user_command("Foo", "call FooFunc(<f-args>)", { nargs = '+' })
eq([=[['a quick', 'brown fox', 'jumps over the', 'lazy dog']]=],
meths.cmd({ cmd = "Foo", args = { "a quick", "brown fox", "jumps over the", "lazy dog"}},
@@ -3948,7 +4430,7 @@ describe('API', function()
it('splits arguments correctly for Lua callback', function()
meths.exec_lua([[
local function FooFunc(opts)
- vim.pretty_print(opts.fargs)
+ vim.print(opts.fargs)
end
vim.api.nvim_create_user_command("Foo", FooFunc, { nargs = '+' })
@@ -4054,8 +4536,6 @@ describe('API', function()
eq('1', meths.cmd({cmd = 'echo', args = {true}}, {output = true}))
end)
describe('first argument as count', function()
- before_each(clear)
-
it('works', function()
command('vsplit | enew')
meths.cmd({cmd = 'bdelete', args = {meths.get_current_buf()}}, {})
diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua
index ecab6a4713..6737c2d15b 100644
--- a/test/functional/api/window_spec.lua
+++ b/test/functional/api/window_spec.lua
@@ -43,6 +43,25 @@ describe('API/win', function()
eq('Invalid buffer id: 23', pcall_err(window, 'set_buf', nvim('get_current_win'), 23))
eq('Invalid window id: 23', pcall_err(window, 'set_buf', 23, nvim('get_current_buf')))
end)
+
+ it('disallowed in cmdwin if win={old_}curwin or buf=curbuf', function()
+ local new_buf = meths.create_buf(true, true)
+ local old_win = meths.get_current_win()
+ local new_win = meths.open_win(new_buf, false, {
+ relative='editor', row=10, col=10, width=50, height=10,
+ })
+ feed('q:')
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, 0, new_buf))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, old_win, new_buf))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_set_buf, new_win, 0))
+
+ local next_buf = meths.create_buf(true, true)
+ meths.win_set_buf(new_win, next_buf)
+ eq(next_buf, meths.win_get_buf(new_win))
+ end)
end)
describe('{get,set}_cursor', function()
@@ -285,6 +304,22 @@ describe('API/win', function()
eq(2, window('get_height', nvim('list_wins')[2]))
end)
+ it('correctly handles height=1', function()
+ nvim('command', 'split')
+ nvim('set_current_win', nvim('list_wins')[1])
+ window('set_height', nvim('list_wins')[2], 1)
+ eq(1, window('get_height', nvim('list_wins')[2]))
+ end)
+
+ it('correctly handles height=1 with a winbar', function()
+ nvim('command', 'set winbar=foobar')
+ nvim('command', 'set winminheight=0')
+ nvim('command', 'split')
+ nvim('set_current_win', nvim('list_wins')[1])
+ window('set_height', nvim('list_wins')[2], 1)
+ eq(1, window('get_height', nvim('list_wins')[2]))
+ end)
+
it('do not cause ml_get errors with foldmethod=expr #19989', function()
insert([[
aaaaa
@@ -363,22 +398,22 @@ describe('API/win', function()
end)
end)
- describe('nvim_win_get_option, nvim_win_set_option', function()
+ describe('nvim_get_option_value, nvim_set_option_value', function()
it('works', function()
- curwin('set_option', 'colorcolumn', '4,3')
- eq('4,3', curwin('get_option', 'colorcolumn'))
+ nvim('set_option_value', 'colorcolumn', '4,3', {})
+ eq('4,3', nvim('get_option_value', 'colorcolumn', {}))
command("set modified hidden")
command("enew") -- edit new buffer, window option is preserved
- eq('4,3', curwin('get_option', 'colorcolumn'))
+ eq('4,3', nvim('get_option_value', 'colorcolumn', {}))
-- global-local option
- curwin('set_option', 'statusline', 'window-status')
- eq('window-status', curwin('get_option', 'statusline'))
- eq('', nvim('get_option', 'statusline'))
+ nvim('set_option_value', 'statusline', 'window-status', {win=0})
+ eq('window-status', nvim('get_option_value', 'statusline', {win=0}))
+ eq('', nvim('get_option_value', 'statusline', {scope='global'}))
command("set modified")
command("enew") -- global-local: not preserved in new buffer
-- confirm local value was not copied
- eq('', curwin('get_option', 'statusline'))
+ eq('', nvim('get_option_value', 'statusline', {win = 0}))
eq('', eval('&l:statusline'))
end)
@@ -386,16 +421,16 @@ describe('API/win', function()
nvim('command', 'tabnew')
local tab1 = unpack(nvim('list_tabpages'))
local win1 = unpack(tabpage('list_wins', tab1))
- window('set_option', win1, 'statusline', 'window-status')
+ nvim('set_option_value', 'statusline', 'window-status', {win=win1.id})
nvim('command', 'split')
nvim('command', 'wincmd J')
nvim('command', 'wincmd j')
- eq('window-status', window('get_option', win1, 'statusline'))
+ eq('window-status', nvim('get_option_value', 'statusline', {win = win1.id}))
assert_alive()
end)
it('returns values for unset local options', function()
- eq(-1, curwin('get_option', 'scrolloff'))
+ eq(-1, nvim('get_option_value', 'scrolloff', {win=0, scope='local'}))
end)
end)
@@ -508,15 +543,21 @@ describe('API/win', function()
command('split')
eq(2, #meths.list_wins())
local oldwin = meths.get_current_win()
+ local otherwin = meths.open_win(0, false, {
+ relative='editor', row=10, col=10, width=10, height=10,
+ })
-- Open cmdline-window.
feed('q:')
- eq(3, #meths.list_wins())
+ eq(4, #meths.list_wins())
eq(':', funcs.getcmdwintype())
- -- Vim: not allowed to close other windows from cmdline-window.
+ -- Not allowed to close previous window from cmdline-window.
eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
- pcall_err(meths.win_close, oldwin, true))
+ pcall_err(meths.win_close, oldwin, true))
+ -- Closing other windows is fine.
+ meths.win_close(otherwin, true)
+ eq(false, meths.win_is_valid(otherwin))
-- Close cmdline-window.
- meths.win_close(0,true)
+ meths.win_close(0, true)
eq(2, #meths.list_wins())
eq('', funcs.getcmdwintype())
end)
@@ -568,15 +609,247 @@ describe('API/win', function()
it('deletes the buffer when bufhidden=wipe', function()
local oldwin = meths.get_current_win()
local oldbuf = meths.get_current_buf()
- local buf = meths.create_buf(true, false)
+ local buf = meths.create_buf(true, false).id
local newwin = meths.open_win(buf, true, {
relative='win', row=3, col=3, width=12, height=3
})
- meths.buf_set_option(buf, 'bufhidden', 'wipe')
+ meths.set_option_value('bufhidden', 'wipe', {buf=buf})
meths.win_hide(newwin)
eq({oldwin}, meths.list_wins())
eq({oldbuf}, meths.list_bufs())
end)
+ it('in the cmdwin', function()
+ feed('q:')
+ -- Can close the cmdwin.
+ meths.win_hide(0)
+ eq('', funcs.getcmdwintype())
+
+ local old_win = meths.get_current_win()
+ local other_win = meths.open_win(0, false, {
+ relative='win', row=3, col=3, width=12, height=3
+ })
+ feed('q:')
+ -- Cannot close the previous window.
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.win_hide, old_win))
+ -- Can close other windows.
+ meths.win_hide(other_win)
+ eq(false, meths.win_is_valid(other_win))
+ end)
+ end)
+
+ describe('text_height', function()
+ it('validation', function()
+ local X = meths.get_vvar('maxcol')
+ insert([[
+ aaa
+ bbb
+ ccc
+ ddd
+ eee]])
+ eq("Invalid window id: 23",
+ pcall_err(meths.win_text_height, 23, {}))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { start_row = 5 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { start_row = -6 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { end_row = 5 }))
+ eq("Line index out of bounds",
+ pcall_err(curwinmeths.text_height, { end_row = -6 }))
+ eq("'start_row' is higher than 'end_row'",
+ pcall_err(curwinmeths.text_height, { start_row = 3, end_row = 1 }))
+ eq("'start_vcol' specified without 'start_row'",
+ pcall_err(curwinmeths.text_height, { end_row = 2, start_vcol = 0 }))
+ eq("'end_vcol' specified without 'end_row'",
+ pcall_err(curwinmeths.text_height, { start_row = 2, end_vcol = 0 }))
+ eq("Invalid 'start_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { start_row = 2, start_vcol = -1 }))
+ eq("Invalid 'start_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { start_row = 2, start_vcol = X + 1 }))
+ eq("Invalid 'end_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { end_row = 2, end_vcol = -1 }))
+ eq("Invalid 'end_vcol': out of range",
+ pcall_err(curwinmeths.text_height, { end_row = 2, end_vcol = X + 1 }))
+ eq("'start_vcol' is higher than 'end_vcol'",
+ pcall_err(curwinmeths.text_height, { start_row = 2, end_row = 2, start_vcol = 10, end_vcol = 5 }))
+ end)
+
+ it('with two diff windows', function()
+ local X = meths.get_vvar('maxcol')
+ local screen = Screen.new(45, 22)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue1, bold = true};
+ [1] = {foreground = Screen.colors.Blue4, background = Screen.colors.Grey};
+ [2] = {foreground = Screen.colors.Brown};
+ [3] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightCyan1, bold = true};
+ [4] = {background = Screen.colors.LightBlue};
+ [5] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey};
+ [6] = {background = Screen.colors.Plum1};
+ [7] = {background = Screen.colors.Red, bold = true};
+ [8] = {reverse = true};
+ [9] = {bold = true, reverse = true};
+ })
+ screen:attach()
+ exec([[
+ set diffopt+=context:2 number
+ let expr = 'printf("%08d", v:val) .. repeat("!", v:val)'
+ call setline(1, map(range(1, 20) + range(25, 45), expr))
+ vnew
+ call setline(1, map(range(3, 20) + range(28, 50), expr))
+ windo diffthis
+ ]])
+ feed('24gg')
+ screen:expect{grid=[[
+ {1: }{2: }{3:----------------}│{1: }{2: 1 }{4:00000001! }|
+ {1: }{2: }{3:----------------}│{1: }{2: 2 }{4:00000002!! }|
+ {1: }{2: 1 }00000003!!! │{1: }{2: 3 }00000003!!! |
+ {1: }{2: 2 }00000004!!!! │{1: }{2: 4 }00000004!!!! |
+ {1:+ }{2: 3 }{5:+-- 14 lines: 00}│{1:+ }{2: 5 }{5:+-- 14 lines: 00}|
+ {1: }{2: 17 }00000019!!!!!!!!│{1: }{2: 19 }00000019!!!!!!!!|
+ {1: }{2: 18 }00000020!!!!!!!!│{1: }{2: 20 }00000020!!!!!!!!|
+ {1: }{2: }{3:----------------}│{1: }{2: 21 }{4:00000025!!!!!!!!}|
+ {1: }{2: }{3:----------------}│{1: }{2: 22 }{4:00000026!!!!!!!!}|
+ {1: }{2: }{3:----------------}│{1: }{2: 23 }{4:00000027!!!!!!!!}|
+ {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!|
+ {1: }{2: 20 }00000029!!!!!!!!│{1: }{2: 25 }00000029!!!!!!!!|
+ {1:+ }{2: 21 }{5:+-- 14 lines: 00}│{1:+ }{2: 26 }{5:+-- 14 lines: 00}|
+ {1: }{2: 35 }00000044!!!!!!!!│{1: }{2: 40 }00000044!!!!!!!!|
+ {1: }{2: 36 }00000045!!!!!!!!│{1: }{2: 41 }00000045!!!!!!!!|
+ {1: }{2: 37 }{4:00000046!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 38 }{4:00000047!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 39 }{4:00000048!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 40 }{4:00000049!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {1: }{2: 41 }{4:00000050!!!!!!!!}│{1: }{2: }{3:----------------}|
+ {8:[No Name] [+] }{9:[No Name] [+] }|
+ |
+ ]]}
+ screen:try_resize(45, 3)
+ screen:expect{grid=[[
+ {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!|
+ {8:[No Name] [+] }{9:[No Name] [+] }|
+ |
+ ]]}
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1000, {}))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, {}))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1000, { start_row = 0 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { start_row = 0 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(1000, { end_row = -1 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(1000, { end_row = 40 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { end_row = -1 }))
+ eq({ all = 20, fill = 5 }, meths.win_text_height(1001, { end_row = 40 }))
+ eq({ all = 10, fill = 5 }, meths.win_text_height(1000, { start_row = 23 }))
+ eq({ all = 13, fill = 3 }, meths.win_text_height(1001, { start_row = 18 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(1000, { end_row = 23 }))
+ eq({ all = 11, fill = 5 }, meths.win_text_height(1001, { end_row = 18 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(1000, { start_row = 3, end_row = 39 }))
+ eq({ all = 11, fill = 3 }, meths.win_text_height(1001, { start_row = 1, end_row = 34 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 4, end_row = 38 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 2, end_row = 33 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 5, end_row = 37 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 3, end_row = 32 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(1000, { start_row = 17, end_row = 25 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 15, end_row = 20 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(1000, { start_row = 18, end_row = 24 }))
+ eq({ all = 7, fill = 3 }, meths.win_text_height(1001, { start_row = 16, end_row = 19 }))
+ eq({ all = 6, fill = 5 }, meths.win_text_height(1000, { start_row = -1 }))
+ eq({ all = 5, fill = 5 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = X, end_row = -1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(1000, { start_row = -1, start_vcol = 0, end_row = -1, end_vcol = X }))
+ eq({ all = 3, fill = 2 }, meths.win_text_height(1001, { end_row = 0 }))
+ eq({ all = 2, fill = 2 }, meths.win_text_height(1001, { end_row = 0, end_vcol = 0 }))
+ eq({ all = 2, fill = 2 }, meths.win_text_height(1001, { start_row = 0, end_row = 0, end_vcol = 0 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = 0 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 0, end_vcol = X }))
+ eq({ all = 11, fill = 5 }, meths.win_text_height(1001, { end_row = 18 }))
+ eq({ all = 9, fill = 3 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18 }))
+ eq({ all = 10, fill = 5 }, meths.win_text_height(1001, { end_row = 18, end_vcol = 0 }))
+ eq({ all = 8, fill = 3 }, meths.win_text_height(1001, { start_row = 0, start_vcol = 0, end_row = 18, end_vcol = 0 }))
+ end)
+
+ it('with wrapped lines', function()
+ local X = meths.get_vvar('maxcol')
+ local screen = Screen.new(45, 22)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue1, bold = true};
+ [1] = {foreground = Screen.colors.Brown};
+ [2] = {background = Screen.colors.Yellow};
+ })
+ screen:attach()
+ exec([[
+ set number cpoptions+=n
+ call setline(1, repeat([repeat('foobar-', 36)], 3))
+ ]])
+ local ns = meths.create_namespace('')
+ meths.buf_set_extmark(0, ns, 1, 100, { virt_text = {{('?'):rep(15), 'Search'}}, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 2, 200, { virt_text = {{('!'):rep(75), 'Search'}}, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar-foobar-foobar-foobar-foobar-f|
+ oobar-foobar-foobar-foobar-foobar-foobar-foob|
+ ar-foobar-foobar-foobar-foobar- |
+ {1: 2 }foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-fo{2:???????????????}obar-foobar-foob|
+ ar-foobar-foobar-foobar-foobar-foobar-foobar-|
+ foobar-foobar-foobar-foobar-foobar-foobar-foo|
+ bar-foobar-foobar-foobar-foobar-foobar-foobar|
+ - |
+ {1: 3 }foobar-foobar-foobar-foobar-foobar-foobar|
+ -foobar-foobar-foobar-foobar-foobar-foobar-fo|
+ obar-foobar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar-foobar-foobar-foobar-foobar-f|
+ oobar-foobar-foobar-foob{2:!!!!!!!!!!!!!!!!!!!!!}|
+ {2:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}|
+ {2:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba|
+ r-foobar-foobar- |
+ |
+ ]]}
+ screen:try_resize(45, 2)
+ screen:expect{grid=[[
+ {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar|
+ |
+ ]]}
+ eq({ all = 21, fill = 0 }, meths.win_text_height(0, {}))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 0, end_row = 0 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, end_row = 1 }))
+ eq({ all = 8, fill = 0 }, meths.win_text_height(0, { start_row = 2, end_row = 2 }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 0 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 41 }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 42 }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 86 }))
+ eq({ all = 3, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 87 }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 266 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 267 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 311 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = 312 }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 0, end_row = 1, end_vcol = X }))
+ eq({ all = 7, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 40, end_row = 1, end_vcol = X }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 41, end_row = 1, end_vcol = X }))
+ eq({ all = 6, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 85, end_row = 1, end_vcol = X }))
+ eq({ all = 5, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = X }))
+ eq({ all = 2, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 265, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 266, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 310, end_row = 1, end_vcol = X }))
+ eq({ all = 0, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 311, end_row = 1, end_vcol = X }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 86, end_row = 1, end_vcol = 131 }))
+ eq({ all = 1, fill = 0 }, meths.win_text_height(0, { start_row = 1, start_vcol = 221, end_row = 1, end_vcol = 266 }))
+ eq({ all = 18, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 131 }))
+ eq({ all = 19, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 130 }))
+ eq({ all = 20, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 311 }))
+ eq({ all = 21, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 312 }))
+ eq({ all = 17, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 131, end_row = 2, end_vcol = 311 }))
+ eq({ all = 19, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 130, end_row = 2, end_vcol = 312 }))
+ eq({ all = 16, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 221 }))
+ eq({ all = 17, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 220 }))
+ eq({ all = 14, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 41 }))
+ eq({ all = 15, fill = 0 }, meths.win_text_height(0, { end_row = 2, end_vcol = 42 }))
+ eq({ all = 9, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 221, end_row = 2, end_vcol = 41 }))
+ eq({ all = 11, fill = 0 }, meths.win_text_height(0, { start_row = 0, start_vcol = 220, end_row = 2, end_vcol = 42 }))
+ end)
end)
describe('open_win', function()
@@ -591,19 +864,45 @@ describe('API/win', function()
})
eq(1, funcs.exists('g:fired'))
end)
+
+ it('disallowed in cmdwin if enter=true or buf=curbuf', function()
+ local new_buf = meths.create_buf(true, true)
+ feed('q:')
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.open_win, new_buf, true, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.open_win, 0, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+
+ eq(new_buf, meths.win_get_buf(meths.open_win(new_buf, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ })))
+ end)
+
+ it('aborts if buffer is invalid', function()
+ local wins_before = meths.list_wins()
+ eq('Invalid buffer id: 1337', pcall_err(meths.open_win, 1337, false, {
+ relative='editor', row=5, col=5, width=5, height=5,
+ }))
+ eq(wins_before, meths.list_wins())
+ end)
end)
describe('get_config', function()
it('includes border', function()
local b = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }
local win = meths.open_win(0, true, {
- relative='win', row=3, col=3, width=12, height=3,
- border = b,
+ relative='win', row=3, col=3, width=12, height=3,
+ border = b,
})
local cfg = meths.win_get_config(win)
eq(b, cfg.border)
end)
+
it('includes border with highlight group', function()
local b = {
{'a', 'Normal'},
@@ -616,12 +915,25 @@ describe('API/win', function()
{'h', 'PreProc'},
}
local win = meths.open_win(0, true, {
- relative='win', row=3, col=3, width=12, height=3,
- border = b,
+ relative='win', row=3, col=3, width=12, height=3,
+ border = b,
})
local cfg = meths.win_get_config(win)
eq(b, cfg.border)
end)
+
+ it('includes title and footer', function()
+ local title = { {'A', {'StatusLine', 'TabLine'}}, {'B'}, {'C', 'WinBar'} }
+ local footer = { {'A', 'WinBar'}, {'B'}, {'C', {'StatusLine', 'TabLine'}} }
+ local win = meths.open_win(0, true, {
+ relative='win', row=3, col=3, width=12, height=3,
+ border = 'single', title = title, footer = footer,
+ })
+
+ local cfg = meths.win_get_config(win)
+ eq(title, cfg.title)
+ eq(footer, cfg.footer)
+ end)
end)
end)
diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua
index ad3687d7b0..dfd746a06e 100644
--- a/test/functional/autocmd/autocmd_oldtest_spec.lua
+++ b/test/functional/autocmd/autocmd_oldtest_spec.lua
@@ -1,13 +1,12 @@
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local eq = helpers.eq
local meths = helpers.meths
local funcs = helpers.funcs
-
-local exec = function(str)
- meths.exec(str, false)
-end
+local exec = helpers.exec
+local feed = helpers.feed
describe('oldtests', function()
before_each(clear)
@@ -53,6 +52,7 @@ describe('oldtests', function()
it('should fire on unload buf', function()
funcs.writefile({'Test file Xxx1'}, 'Xxx1')
funcs.writefile({'Test file Xxx2'}, 'Xxx2')
+ local fname = 'Xtest_functional_autocmd_unload'
local content = [[
func UnloadAllBufs()
@@ -72,15 +72,55 @@ describe('oldtests', function()
q
]]
- funcs.writefile(funcs.split(content, "\n"), 'Xtest')
+ funcs.writefile(funcs.split(content, "\n"), fname)
funcs.delete('Xout')
- funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest')
+ funcs.system(string.format('%s -u NORC -i NONE -N -S %s', meths.get_vvar('progpath'), fname))
eq(1, funcs.filereadable('Xout'))
funcs.delete('Xxx1')
funcs.delete('Xxx2')
- funcs.delete('Xtest')
+ funcs.delete(fname)
funcs.delete('Xout')
end)
+
+ -- oldtest: Test_delete_ml_get_errors()
+ it('no ml_get error with TextChanged autocommand and delete', function()
+ local screen = Screen.new(75, 10)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Cyan};
+ })
+ exec([[
+ set noshowcmd noruler scrolloff=0
+ source test/old/testdir/samples/matchparen.vim
+ edit test/old/testdir/samples/box.txt
+ ]])
+ feed('249GV<C-End>d')
+ screen:expect{grid=[[
+ const auto themeEmoji = _forPeer->themeEmoji(); |
+ if (themeEmoji.isEmpty()) { |
+ return nonCustom; |
+ } |
+ const auto &themes = _forPeer->owner().cloudThemes(); |
+ const auto theme = themes.themeForEmoji(themeEmoji); |
+ if (!theme) {1:{} |
+ return nonCustom; |
+ {1:^}} |
+ 353 fewer lines |
+ ]]}
+ feed('<PageUp>')
+ screen:expect{grid=[[
+ |
+ auto BackgroundBox::Inner::resolveResetCustomPaper() const |
+ -> std::optional<Data::WallPaper> { |
+ if (!_forPeer) { |
+ return {}; |
+ } |
+ const auto nonCustom = Window::Theme::Background()->paper(); |
+ const auto themeEmoji = _forPeer->themeEmoji(); |
+ ^if (themeEmoji.isEmpty()) { |
+ 353 fewer lines |
+ ]]}
+ end)
end)
diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua
index fb5bab445c..63a487c8bc 100644
--- a/test/functional/autocmd/autocmd_spec.lua
+++ b/test/functional/autocmd/autocmd_spec.lua
@@ -141,7 +141,7 @@ describe('autocmd', function()
describe('BufLeave autocommand', function()
it('can wipe out the buffer created by :edit which triggered autocmd',
function()
- meths.set_option('hidden', true)
+ meths.set_option_value('hidden', true, {})
curbufmeths.set_lines(0, 1, false, {
'start of test file xx',
'end of test file xx'})
@@ -611,4 +611,38 @@ describe('autocmd', function()
eq(4, #meths.get_autocmds { event = "BufReadCmd", group = "TestingPatterns" })
end)
end)
+
+ it('no use-after-free when adding autocommands from a callback', function()
+ exec_lua [[
+ vim.cmd "autocmd! TabNew"
+ vim.g.count = 0
+ vim.api.nvim_create_autocmd('TabNew', {
+ callback = function()
+ vim.g.count = vim.g.count + 1
+ for _ = 1, 100 do
+ vim.cmd "autocmd TabNew * let g:count += 1"
+ end
+ return true
+ end,
+ })
+ vim.cmd "tabnew"
+ ]]
+ eq(1, eval('g:count')) -- Added autocommands should not be executed
+ end)
+
+ it('no crash when clearing a group inside a callback #23355', function()
+ exec_lua [[
+ vim.cmd "autocmd! TabNew"
+ local group = vim.api.nvim_create_augroup('Test', {})
+ local id
+ id = vim.api.nvim_create_autocmd('TabNew', {
+ group = group,
+ callback = function()
+ vim.api.nvim_del_autocmd(id)
+ vim.api.nvim_create_augroup('Test', { clear = true })
+ end,
+ })
+ vim.cmd "tabnew"
+ ]]
+ end)
end)
diff --git a/test/functional/autocmd/cursorhold_spec.lua b/test/functional/autocmd/cursorhold_spec.lua
index b04bd5233a..e6bcb19682 100644
--- a/test/functional/autocmd/cursorhold_spec.lua
+++ b/test/functional/autocmd/cursorhold_spec.lua
@@ -26,7 +26,7 @@ describe('CursorHold', function()
-- if testing with small 'updatetime' fails, double its value and test again
retry(10, nil, function()
ut = ut * 2
- meths.set_option('updatetime', ut)
+ meths.set_option_value('updatetime', ut, {})
feed('0') -- reset did_cursorhold
meths.set_var('cursorhold', 0)
sleep(ut / 4)
@@ -51,12 +51,12 @@ describe('CursorHold', function()
end)
it("reducing 'updatetime' while waiting for CursorHold #20241", function()
- meths.set_option('updatetime', 10000)
+ meths.set_option_value('updatetime', 10000, {})
feed('0') -- reset did_cursorhold
meths.set_var('cursorhold', 0)
sleep(50)
eq(0, meths.get_var('cursorhold'))
- meths.set_option('updatetime', 20)
+ meths.set_option_value('updatetime', 20, {})
sleep(10)
eq(1, meths.get_var('cursorhold'))
end)
diff --git a/test/functional/autocmd/cursormoved_spec.lua b/test/functional/autocmd/cursormoved_spec.lua
index 85d8628d7e..854e14b088 100644
--- a/test/functional/autocmd/cursormoved_spec.lua
+++ b/test/functional/autocmd/cursormoved_spec.lua
@@ -3,40 +3,55 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local eval = helpers.eval
-local funcs = helpers.funcs
+local meths = helpers.meths
local source = helpers.source
+local command = helpers.command
describe('CursorMoved', function()
before_each(clear)
- it('is triggered by changing windows', function()
+ it('is triggered after BufEnter when changing or splitting windows #11878 #12031', function()
source([[
- let g:cursormoved = 0
- vsplit
- autocmd CursorMoved * let g:cursormoved += 1
- wincmd w
- wincmd w
+ call setline(1, 'foo')
+ let g:log = []
+ autocmd BufEnter * let g:log += ['BufEnter' .. expand("<abuf>")]
+ autocmd CursorMoved * let g:log += ['CursorMoved' .. expand("<abuf>")]
]])
- eq(2, eval('g:cursormoved'))
+ eq({}, eval('g:log'))
+ command('new')
+ eq({'BufEnter2', 'CursorMoved2'}, eval('g:log'))
+ command('wincmd w')
+ eq({'BufEnter2', 'CursorMoved2', 'BufEnter1', 'CursorMoved1'}, eval('g:log'))
+ end)
+
+ it('is not triggered by temporarily switching window', function()
+ source([[
+ let g:cursormoved = 0
+ vnew
+ autocmd CursorMoved * let g:cursormoved += 1
+ ]])
+ command('wincmd w | wincmd p')
+ eq(0, eval('g:cursormoved'))
end)
it("is not triggered by functions that don't change the window", function()
source([[
- let g:cursormoved = 0
- let g:buf = bufnr('%')
- vsplit foo
- autocmd CursorMoved * let g:cursormoved += 1
- call nvim_buf_set_lines(g:buf, 0, -1, v:true, ['aaa'])
+ let g:cursormoved = 0
+ let g:buf = bufnr('%')
+ vsplit foo
+ autocmd CursorMoved * let g:cursormoved += 1
]])
- eq({'aaa'}, funcs.nvim_buf_get_lines(eval('g:buf'), 0, -1, true))
+ meths.buf_set_lines(eval('g:buf'), 0, -1, true, {'aaa'})
+ eq(0, eval('g:cursormoved'))
+ eq({'aaa'}, meths.buf_get_lines(eval('g:buf'), 0, -1, true))
eq(0, eval('g:cursormoved'))
end)
it("is not triggered by cursor movement prior to first CursorMoved instantiation", function()
source([[
- let g:cursormoved = 0
- autocmd! CursorMoved
- autocmd CursorMoved * let g:cursormoved += 1
+ let g:cursormoved = 0
+ autocmd! CursorMoved
+ autocmd CursorMoved * let g:cursormoved += 1
]])
eq(0, eval('g:cursormoved'))
end)
diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua
index 828cffa460..20aa07d058 100644
--- a/test/functional/autocmd/dirchanged_spec.lua
+++ b/test/functional/autocmd/dirchanged_spec.lua
@@ -1,4 +1,4 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
@@ -9,7 +9,7 @@ local request = helpers.request
local is_os = helpers.is_os
describe('autocmd DirChanged and DirChangedPre', function()
- local curdir = string.gsub(lfs.currentdir(), '\\', '/')
+ local curdir = string.gsub(luv.cwd(), '\\', '/')
local dirs = {
curdir .. '/Xtest-functional-autocmd-dirchanged.dir1',
curdir .. '/Xtest-functional-autocmd-dirchanged.dir2',
diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua
index d7a87e17ed..33e4d88c7b 100644
--- a/test/functional/autocmd/focus_spec.lua
+++ b/test/functional/autocmd/focus_spec.lua
@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers')
-local lfs = require('lfs')
+local luv = require('luv')
local clear = helpers.clear
local nvim_prog = helpers.nvim_prog
local feed_command = helpers.feed_command
@@ -32,7 +32,8 @@ describe('autoread TUI FocusGained/FocusLost', function()
]]
helpers.write_file(path, '')
- lfs.touch(path, os.time() - 10)
+ local atime = os.time() - 10
+ luv.fs_utime(path, atime, atime)
screen:expect{grid=[[
{1: } |
diff --git a/test/functional/autocmd/modechanged_spec.lua b/test/functional/autocmd/modechanged_spec.lua
index be5a291ac9..69a722a0e9 100644
--- a/test/functional/autocmd/modechanged_spec.lua
+++ b/test/functional/autocmd/modechanged_spec.lua
@@ -1,17 +1,19 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq
local feed, command = helpers.feed, helpers.command
+local exec_lua = helpers.exec_lua
describe('ModeChanged', function()
before_each(function()
clear()
+ end)
+
+ it('picks up terminal mode changes', function()
command('let g:count = 0')
command('au ModeChanged * let g:event = copy(v:event)')
command('au ModeChanged * let g:count += 1')
- end)
- it('picks up terminal mode changes', function()
- command("term")
+ command('term')
feed('i')
eq({
old_mode = 'nt',
@@ -28,4 +30,35 @@ describe('ModeChanged', function()
-- v:event is cleared after the autocommand is done
eq({}, eval('v:event'))
end)
+
+ it('does not repeatedly trigger for scheduled callback', function()
+ exec_lua([[
+ vim.g.s_count = 0
+ vim.g.s_mode = ""
+ vim.g.t_count = 0
+ vim.g.t_mode = ""
+ vim.api.nvim_create_autocmd("ModeChanged", {
+ callback = function()
+ vim.g.s_count = vim.g.s_count + 1
+ vim.g.s_mode = vim.api.nvim_get_mode().mode
+ vim.schedule(function()
+ vim.g.t_count = vim.g.t_count + 1
+ vim.g.t_mode = vim.api.nvim_get_mode().mode
+ end)
+ end,
+ })
+ ]])
+
+ feed('d')
+ eq(1, eval('g:s_count'))
+ eq('no', eval('g:s_mode'))
+ eq(1, eval('g:t_count'))
+ eq('no', eval('g:t_mode'))
+
+ feed('<Esc>')
+ eq(2, eval('g:s_count'))
+ eq('n', eval('g:s_mode'))
+ eq(2, eval('g:t_count'))
+ eq('n', eval('g:t_mode'))
+ end)
end)
diff --git a/test/functional/autocmd/safestate_spec.lua b/test/functional/autocmd/safestate_spec.lua
new file mode 100644
index 0000000000..73693749e4
--- /dev/null
+++ b/test/functional/autocmd/safestate_spec.lua
@@ -0,0 +1,57 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local exec = helpers.exec
+local feed = helpers.feed
+local meths = helpers.meths
+
+before_each(clear)
+
+describe('SafeState autocommand', function()
+ local function create_autocmd()
+ exec([[
+ let g:safe = 0
+ autocmd SafeState * ++once let g:safe += 1
+ ]])
+ end
+
+ it('with pending operator', function()
+ feed('d')
+ create_autocmd()
+ eq(0, meths.get_var('safe'))
+ feed('d')
+ eq(1, meths.get_var('safe'))
+ end)
+
+ it('with specified register', function()
+ feed('"r')
+ create_autocmd()
+ eq(0, meths.get_var('safe'))
+ feed('x')
+ eq(1, meths.get_var('safe'))
+ end)
+
+ it('with i_CTRL-O', function()
+ feed('i<C-O>')
+ create_autocmd()
+ eq(0, meths.get_var('safe'))
+ feed('x')
+ eq(1, meths.get_var('safe'))
+ end)
+
+ it('with Insert mode completion', function()
+ feed('i<C-X><C-V>')
+ create_autocmd()
+ eq(0, meths.get_var('safe'))
+ feed('<C-X><C-Z>')
+ eq(1, meths.get_var('safe'))
+ end)
+
+ it('with Cmdline completion', function()
+ feed(':<Tab>')
+ create_autocmd()
+ eq(0, meths.get_var('safe'))
+ feed('<C-E>')
+ eq(1, meths.get_var('safe'))
+ end)
+end)
diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua
index 505bed834b..9e0a5b819a 100644
--- a/test/functional/autocmd/show_spec.lua
+++ b/test/functional/autocmd/show_spec.lua
@@ -180,4 +180,45 @@ describe(":autocmd", function()
test_3 User
B echo "B3"]]), funcs.execute('autocmd test_3 * B'))
end)
+
+ it('should skip consecutive patterns', function()
+ exec([[
+ autocmd! BufEnter
+ augroup test_1
+ autocmd BufEnter A echo 'A'
+ autocmd BufEnter A echo 'B'
+ autocmd BufEnter A echo 'C'
+ autocmd BufEnter B echo 'D'
+ autocmd BufEnter B echo 'E'
+ autocmd BufEnter B echo 'F'
+ augroup END
+ augroup test_2
+ autocmd BufEnter C echo 'A'
+ autocmd BufEnter C echo 'B'
+ autocmd BufEnter C echo 'C'
+ autocmd BufEnter D echo 'D'
+ autocmd BufEnter D echo 'E'
+ autocmd BufEnter D echo 'F'
+ augroup END
+
+ let g:output = execute('autocmd BufEnter')
+ ]])
+ eq(dedent([[
+
+ --- Autocommands ---
+ test_1 BufEnter
+ A echo 'A'
+ echo 'B'
+ echo 'C'
+ B echo 'D'
+ echo 'E'
+ echo 'F'
+ test_2 BufEnter
+ C echo 'A'
+ echo 'B'
+ echo 'C'
+ D echo 'D'
+ echo 'E'
+ echo 'F']]), eval('g:output'))
+ end)
end)
diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua
index b186aa1f50..6e167dd55c 100644
--- a/test/functional/autocmd/tabnewentered_spec.lua
+++ b/test/functional/autocmd/tabnewentered_spec.lua
@@ -21,9 +21,9 @@ describe('TabNewEntered', function()
end)
describe('with FILE as <afile>', function()
it('matches when opening a new tab for FILE', function()
+ clear()
nvim('command', 'au! TabNewEntered Xtest-tabnewentered echom "tabnewentered:match"')
- eq('tabnewentered:4:4\ntabnewentered:match',
- nvim('exec', 'tabnew Xtest-tabnewentered', true))
+ eq('tabnewentered:match', nvim('exec', 'tabnew Xtest-tabnewentered', true))
end)
end)
describe('with CTRL-W T', function()
@@ -35,6 +35,14 @@ describe('TabNewEntered', function()
eq('entered', nvim('exec', 'execute "normal \\<C-W>T"', true))
end)
end)
+ describe('with tab split #4334', function()
+ it('works when create a tab by using tab split command', function()
+ clear()
+ nvim('command', 'au! TabNewEntered * let b:entered = "entered"')
+ nvim('command', 'tab split')
+ eq('entered', nvim('exec', 'echo b:entered', true))
+ end)
+ end)
end)
end)
diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua
index 0a33f1b2ac..332a936e3f 100644
--- a/test/functional/autocmd/termxx_spec.lua
+++ b/test/functional/autocmd/termxx_spec.lua
@@ -1,5 +1,6 @@
local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
+local thelpers = require('test.functional.terminal.helpers')
local clear, command, nvim, testprg =
helpers.clear, helpers.command, helpers.nvim, helpers.testprg
@@ -8,6 +9,7 @@ local eval, eq, neq, retry =
local matches = helpers.matches
local ok = helpers.ok
local feed = helpers.feed
+local meths = helpers.meths
local pcall_err = helpers.pcall_err
local assert_alive = helpers.assert_alive
local skip = helpers.skip
@@ -16,12 +18,13 @@ local is_os = helpers.is_os
describe('autocmd TermClose', function()
before_each(function()
clear()
- nvim('set_option', 'shell', testprg('shell-test'))
+ nvim('set_option_value', 'shell', testprg('shell-test'), {})
command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=')
end)
-
local function test_termclose_delete_own_buf()
+ -- The terminal process needs to keep running so that TermClose isn't triggered immediately.
+ nvim('set_option_value', 'shell', string.format('"%s" INTERACT', testprg('shell-test')), {})
command('autocmd TermClose * bdelete!')
command('terminal')
matches('^TermClose Autocommands for "%*": Vim%(bdelete%):E937: Attempt to delete a buffer that is in use: term://',
@@ -48,7 +51,8 @@ describe('autocmd TermClose', function()
end)
it('triggers when long-running terminal job gets stopped', function()
- nvim('set_option', 'shell', is_os('win') and 'cmd.exe' or 'sh')
+ skip(is_os('win'))
+ nvim('set_option_value', 'shell', is_os('win') and 'cmd.exe' or 'sh', {})
command('autocmd TermClose * let g:test_termclose = 23')
command('terminal')
command('call jobstop(b:terminal_job_id)')
@@ -57,8 +61,8 @@ describe('autocmd TermClose', function()
it('kills job trapping SIGTERM', function()
skip(is_os('win'))
- nvim('set_option', 'shell', 'sh')
- nvim('set_option', 'shellcmdflag', '-c')
+ nvim('set_option_value', 'shell', 'sh', {})
+ nvim('set_option_value', 'shellcmdflag', '-c', {})
command([[ let g:test_job = jobstart('trap "" TERM && echo 1 && sleep 60', { ]]
.. [[ 'on_stdout': {-> execute('let g:test_job_started = 1')}, ]]
.. [[ 'on_exit': {-> execute('let g:test_job_exited = 1')}}) ]])
@@ -77,8 +81,8 @@ describe('autocmd TermClose', function()
it('kills PTY job trapping SIGHUP and SIGTERM', function()
skip(is_os('win'))
- nvim('set_option', 'shell', 'sh')
- nvim('set_option', 'shellcmdflag', '-c')
+ nvim('set_option_value', 'shell', 'sh', {})
+ nvim('set_option_value', 'shellcmdflag', '-c', {})
command([[ let g:test_job = jobstart('trap "" HUP TERM && echo 1 && sleep 60', { ]]
.. [[ 'pty': 1,]]
.. [[ 'on_stdout': {-> execute('let g:test_job_started = 1')}, ]]
@@ -98,12 +102,13 @@ describe('autocmd TermClose', function()
it('reports the correct <abuf>', function()
command('set hidden')
+ command('set shellcmdflag=EXE')
command('autocmd TermClose * let g:abuf = expand("<abuf>")')
command('edit foo')
command('edit bar')
eq(2, eval('bufnr("%")'))
- command('terminal')
+ command('terminal ls')
retry(nil, nil, function() eq(3, eval('bufnr("%")')) end)
command('buffer 1')
@@ -143,19 +148,37 @@ it('autocmd TermEnter, TermLeave', function()
-- TermLeave is also triggered by :quit.
command('split foo')
+ feed('<Ignore>') -- Add input to separate two RPC requests
command('wincmd w')
feed('i')
command('q!')
- eq(
- {
- {'TermOpen', 'n'},
- {'TermEnter', 't'},
- {'TermLeave', 'n'},
- {'TermEnter', 't'},
- {'TermLeave', 'n'},
- {'TermEnter', 't'},
- {'TermClose', 't'},
- {'TermLeave', 'n'},
- },
- eval('g:evs'))
+ feed('<Ignore>') -- Add input to separate two RPC requests
+ eq({
+ {'TermOpen', 'n'},
+ {'TermEnter', 't'},
+ {'TermLeave', 'n'},
+ {'TermEnter', 't'},
+ {'TermLeave', 'n'},
+ {'TermEnter', 't'},
+ {'TermClose', 't'},
+ {'TermLeave', 'n'},
+ }, eval('g:evs'))
+end)
+
+describe('autocmd TextChangedT', function()
+ clear()
+ local screen = thelpers.screen_setup()
+
+ it('works', function()
+ command('autocmd TextChangedT * ++once let g:called = 1')
+ thelpers.feed_data('a')
+ retry(nil, nil, function() eq(1, meths.get_var('called')) end)
+ end)
+
+ it('cannot delete terminal buffer', function()
+ command([[autocmd TextChangedT * call nvim_input('<CR>') | bwipe!]])
+ thelpers.feed_data('a')
+ screen:expect({any = 'E937: '})
+ matches('^E937: Attempt to delete a buffer that is in use: term://', meths.get_vvar('errmsg'))
+ end)
end)
diff --git a/test/functional/autocmd/textchanged_spec.lua b/test/functional/autocmd/textchanged_spec.lua
new file mode 100644
index 0000000000..b621eb36bf
--- /dev/null
+++ b/test/functional/autocmd/textchanged_spec.lua
@@ -0,0 +1,182 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec = helpers.exec
+local command = helpers.command
+local feed = helpers.feed
+local eq = helpers.eq
+local neq = helpers.neq
+local eval = helpers.eval
+local poke_eventloop = helpers.poke_eventloop
+
+before_each(clear)
+
+-- oldtest: Test_ChangedP()
+it('TextChangedI and TextChangedP autocommands', function()
+ -- The oldtest uses feedkeys() with 'x' flag, which never triggers TextChanged.
+ -- So don't add TextChanged autocommand here.
+ exec([[
+ call setline(1, ['foo', 'bar', 'foobar'])
+ set complete=. completeopt=menuone
+ au! TextChangedI <buffer> let g:autocmd ..= 'I'
+ au! TextChangedP <buffer> let g:autocmd ..= 'P'
+ call cursor(3, 1)
+ ]])
+
+ command([[let g:autocmd = '']])
+ feed('o')
+ poke_eventloop()
+ feed('<esc>')
+ -- TextChangedI triggers only if text is actually changed in Insert mode
+ eq('I', eval('g:autocmd'))
+
+ command([[let g:autocmd = '']])
+ feed('S')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ eq('II', eval('g:autocmd'))
+ feed('<esc>')
+
+ command([[let g:autocmd = '']])
+ feed('S')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ eq('IIP', eval('g:autocmd'))
+ feed('<esc>')
+
+ command([[let g:autocmd = '']])
+ feed('S')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ eq('IIPP', eval('g:autocmd'))
+ feed('<esc>')
+
+ command([[let g:autocmd = '']])
+ feed('S')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ eq('IIPPP', eval('g:autocmd'))
+ feed('<esc>')
+
+ command([[let g:autocmd = '']])
+ feed('S')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ poke_eventloop()
+ feed('<C-N>')
+ eq('IIPPPP', eval('g:autocmd'))
+ feed('<esc>')
+
+ eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")'))
+end)
+
+-- oldtest: Test_TextChangedI_with_setline()
+it('TextChangedI with setline()', function()
+ exec([[
+ let g:setline_handled = v:false
+ func SetLineOne()
+ if !g:setline_handled
+ call setline(1, "(x)")
+ let g:setline_handled = v:true
+ endif
+ endfunc
+ autocmd TextChangedI <buffer> call SetLineOne()
+ ]])
+
+ feed('i')
+ poke_eventloop()
+ feed('(')
+ poke_eventloop()
+ feed('<CR>')
+ poke_eventloop()
+ feed('<Esc>')
+ eq('(', eval('getline(1)'))
+ eq('x)', eval('getline(2)'))
+ command('undo')
+ eq('', eval('getline(1)'))
+ eq('', eval('getline(2)'))
+end)
+
+-- oldtest: Test_Changed_ChangedI()
+it('TextChangedI and TextChanged', function()
+ exec([[
+ let [g:autocmd_i, g:autocmd_n] = ['','']
+
+ func! TextChangedAutocmdI(char)
+ let g:autocmd_{tolower(a:char)} = a:char .. b:changedtick
+ endfunc
+
+ augroup Test_TextChanged
+ au!
+ au TextChanged <buffer> :call TextChangedAutocmdI('N')
+ au TextChangedI <buffer> :call TextChangedAutocmdI('I')
+ augroup END
+ ]])
+
+ feed('i')
+ poke_eventloop()
+ feed('f')
+ poke_eventloop()
+ feed('o')
+ poke_eventloop()
+ feed('o')
+ poke_eventloop()
+ feed('<esc>')
+ eq('', eval('g:autocmd_n'))
+ eq('I5', eval('g:autocmd_i'))
+
+ feed('yyp')
+ eq('N6', eval('g:autocmd_n'))
+ eq('I5', eval('g:autocmd_i'))
+
+ -- TextChangedI should only trigger if change was done in Insert mode
+ command([[let g:autocmd_i = '']])
+ feed('yypi<esc>')
+ eq('', eval('g:autocmd_i'))
+
+ -- TextChanged should only trigger if change was done in Normal mode
+ command([[let g:autocmd_n = '']])
+ feed('ibar<esc>')
+ eq('', eval('g:autocmd_n'))
+
+ local function validate_mixed_textchangedi(keys)
+ feed('ifoo<esc>')
+ command([[let g:autocmd_i = '']])
+ command([[let g:autocmd_n = '']])
+ for _, s in ipairs(keys) do
+ feed(s)
+ poke_eventloop()
+ end
+ neq('', eval('g:autocmd_i'))
+ eq('', eval('g:autocmd_n'))
+ end
+
+ validate_mixed_textchangedi({'o', '<esc>'})
+ validate_mixed_textchangedi({'O', '<esc>'})
+ validate_mixed_textchangedi({'ciw', '<esc>'})
+ validate_mixed_textchangedi({'cc', '<esc>'})
+ validate_mixed_textchangedi({'C', '<esc>'})
+ validate_mixed_textchangedi({'s', '<esc>'})
+ validate_mixed_textchangedi({'S', '<esc>'})
+end)
diff --git a/test/functional/autocmd/textyankpost_spec.lua b/test/functional/autocmd/textyankpost_spec.lua
index 3898d59e58..1640916ad8 100644
--- a/test/functional/autocmd/textyankpost_spec.lua
+++ b/test/functional/autocmd/textyankpost_spec.lua
@@ -8,7 +8,7 @@ describe('TextYankPost', function()
clear()
-- emulate the clipboard so system clipboard isn't affected
- command('let &rtp = "test/functional/fixtures,".&rtp')
+ command('set rtp^=test/functional/fixtures')
command('let g:count = 0')
command('autocmd TextYankPost * let g:event = copy(v:event)')
diff --git a/test/functional/autocmd/win_scrolled_resized_spec.lua b/test/functional/autocmd/win_scrolled_resized_spec.lua
index 4957f56dd4..e6fdd9560d 100644
--- a/test/functional/autocmd/win_scrolled_resized_spec.lua
+++ b/test/functional/autocmd/win_scrolled_resized_spec.lua
@@ -297,7 +297,7 @@ describe('WinScrolled', function()
eq(0, eval('g:scrolled'))
local buf = meths.create_buf(true, true)
- meths.buf_set_lines(buf, 0, -1, false, {'a', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'})
+ meths.buf_set_lines(buf, 0, -1, false, {'@', 'b', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n'})
local win = meths.open_win(buf, false, {
height = 5,
width = 10,
@@ -306,6 +306,7 @@ describe('WinScrolled', function()
relative = 'editor',
style = 'minimal'
})
+ screen:expect({ any = '@' })
local winid_str = tostring(win.id)
-- WinScrolled should not be triggered when creating a new floating window
eq(0, eval('g:scrolled'))
diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua
index 8275575c24..5771ddcb94 100644
--- a/test/functional/core/channels_spec.lua
+++ b/test/functional/core/channels_spec.lua
@@ -230,6 +230,7 @@ describe('channels', function()
end)
it('can use buffered output mode', function()
+ skip(funcs.executable('grep') == 0, 'missing "grep" command')
source([[
let g:job_opts = {
\ 'on_stdout': function('OnEvent'),
@@ -262,6 +263,7 @@ describe('channels', function()
end)
it('can use buffered output mode with no stream callback', function()
+ skip(funcs.executable('grep') == 0, 'missing "grep" command')
source([[
function! OnEvent(id, data, event) dict
call rpcnotify(1, a:event, a:id, a:data, self.stdout)
diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua
index 05a69e1992..d474b77806 100644
--- a/test/functional/core/exit_spec.lua
+++ b/test/functional/core/exit_spec.lua
@@ -25,30 +25,34 @@ describe('v:exiting', function()
eq(1, eval('v:exiting is v:null'))
end)
- it('is 0 on normal exit', function()
+ local function test_exiting(setup_fn)
local function on_setup()
- command('autocmd VimLeavePre * call rpcrequest('..cid..', "")')
- command('autocmd VimLeave * call rpcrequest('..cid..', "")')
- command('quit')
+ command('autocmd VimLeavePre * call rpcrequest('..cid..', "exit", "VimLeavePre")')
+ command('autocmd VimLeave * call rpcrequest('..cid..', "exit", "VimLeave")')
+ setup_fn()
end
- local function on_request()
+ local requests_args = {}
+ local function on_request(name, args)
+ eq('exit', name)
+ table.insert(requests_args, args)
eq(0, eval('v:exiting'))
return ''
end
run(on_request, nil, on_setup)
+ eq({{'VimLeavePre'}, {'VimLeave'}}, requests_args)
+ end
+
+ it('is 0 on normal exit', function()
+ test_exiting(function()
+ command('quit')
+ end)
end)
+
it('is 0 on exit from Ex mode involving try-catch vim-patch:8.0.0184', function()
- local function on_setup()
- command('autocmd VimLeavePre * call rpcrequest('..cid..', "")')
- command('autocmd VimLeave * call rpcrequest('..cid..', "")')
+ test_exiting(function()
feed('gQ')
feed_command('try', 'call NoFunction()', 'catch', 'echo "bye"', 'endtry', 'quit')
- end
- local function on_request()
- eq(0, eval('v:exiting'))
- return ''
- end
- run(on_request, nil, on_setup)
+ end)
end)
end)
@@ -89,14 +93,14 @@ describe(':cquit', function()
end)
it('exits with redir msg for multiple exit codes after :cquit 1 2', function()
- test_cq('cquit 1 2', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: 2: cquit 1 2')
+ test_cq('cquit 1 2', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: 2: cquit 1 2')
end)
it('exits with redir msg for non-number exit code after :cquit X', function()
- test_cq('cquit X', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: X: cquit X')
+ test_cq('cquit X', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: X: cquit X')
end)
it('exits with redir msg for negative exit code after :cquit -1', function()
- test_cq('cquit -1', nil, 'nvim_exec(): Vim(cquit):E488: Trailing characters: -1: cquit -1')
+ test_cq('cquit -1', nil, 'nvim_exec2(): Vim(cquit):E488: Trailing characters: -1: cquit -1')
end)
end)
diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua
index 4e9891a4de..65f947132e 100644
--- a/test/functional/core/fileio_spec.lua
+++ b/test/functional/core/fileio_spec.lua
@@ -1,4 +1,4 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local assert_log = helpers.assert_log
@@ -15,10 +15,10 @@ local request = helpers.request
local retry = helpers.retry
local rmdir = helpers.rmdir
local matches = helpers.matches
+local meths = helpers.meths
local mkdir = helpers.mkdir
local sleep = helpers.sleep
local read_file = helpers.read_file
-local tmpname = helpers.tmpname
local trim = helpers.trim
local currentdir = helpers.funcs.getcwd
local assert_alive = helpers.assert_alive
@@ -40,15 +40,18 @@ describe('fileio', function()
os.remove('Xtest_startup_file1')
os.remove('Xtest_startup_file1~')
os.remove('Xtest_startup_file2')
+ os.remove('Xtest_startup_file2~')
os.remove('Xtest_тест.md')
os.remove('Xtest-u8-int-max')
os.remove('Xtest-overwrite-forced')
rmdir('Xtest_startup_swapdir')
rmdir('Xtest_backupdir')
+ rmdir('Xtest_backupdir with spaces')
end)
it('fsync() codepaths #8304', function()
clear({ args={ '-i', 'Xtest_startup_shada',
+ '--cmd', 'set nofsync',
'--cmd', 'set directory=Xtest_startup_swapdir' } })
-- These cases ALWAYS force fsync (regardless of 'fsync' option):
@@ -132,6 +135,28 @@ describe('fileio', function()
eq('foo', foo_contents);
end)
+ it('backup with full path with spaces', function()
+ skip(is_ci('cirrus'))
+ clear()
+ mkdir('Xtest_backupdir with spaces')
+ command('set backup')
+ command('set backupdir=Xtest_backupdir\\ with\\ spaces//')
+ command('write Xtest_startup_file1')
+ feed('ifoo<esc>')
+ command('write')
+ feed('Abar<esc>')
+ command('write')
+
+ -- Backup filename = fullpath, separators replaced with "%".
+ local backup_file_name = string.gsub(currentdir()..'/Xtest_startup_file1',
+ is_os('win') and '[:/\\]' or '/', '%%') .. '~'
+ local foo_contents = trim(read_file('Xtest_backupdir with spaces/'..backup_file_name))
+ local foobar_contents = trim(read_file('Xtest_startup_file1'))
+
+ eq('foobar', foobar_contents);
+ eq('foo', foo_contents);
+ end)
+
it('backup symlinked files #11349', function()
skip(is_ci('cirrus'))
clear()
@@ -141,7 +166,7 @@ describe('fileio', function()
local backup_file_name = link_file_name .. '~'
write_file('Xtest_startup_file1', initial_content, false)
- lfs.link('Xtest_startup_file1', link_file_name, true)
+ luv.fs_symlink('Xtest_startup_file1', link_file_name)
command('set backup')
command('set backupcopy=yes')
command('edit ' .. link_file_name)
@@ -165,7 +190,7 @@ describe('fileio', function()
local backup_file_name = backup_dir .. sep .. link_file_name .. '~'
write_file('Xtest_startup_file1', initial_content, false)
- lfs.link('Xtest_startup_file1', link_file_name, true)
+ luv.fs_symlink('Xtest_startup_file1', link_file_name)
mkdir(backup_dir)
command('set backup')
command('set backupcopy=yes')
@@ -235,8 +260,8 @@ describe('fileio', function()
screen:expect([[
{2:WARNING: The file has been changed since}|
{2: reading it!!!} |
- {3:Do you really want to write to it (y/n)^?}|
- |
+ {3:Do you really want to write to it (y/n)?}|
+ ^ |
]])
feed("n")
@@ -261,13 +286,11 @@ end)
describe('tmpdir', function()
local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=]
local testlog = 'Xtest_tmpdir_log'
- local faketmp
+ local os_tmpdir
before_each(function()
-- Fake /tmp dir so that we can mess it up.
- faketmp = tmpname()
- os.remove(faketmp)
- mkdir(faketmp)
+ os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
end)
after_each(function()
@@ -275,16 +298,21 @@ describe('tmpdir', function()
os.remove(testlog)
end)
- it('failure modes', function()
- clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
- assert_nolog('tempdir is not a directory', testlog)
- assert_nolog('tempdir has invalid permissions', testlog)
-
+ local function get_tmproot()
-- Tempfiles typically look like: "…/nvim.<user>/xxx/0".
-- - "…/nvim.<user>/xxx/" is the per-process tmpdir, not shared with other Nvims.
-- - "…/nvim.<user>/" is the tmpdir root, shared by all Nvims (normally).
local tmproot = (funcs.tempname()):match(tmproot_pat)
ok(tmproot:len() > 4, 'tmproot like "nvim.foo"', tmproot)
+ return tmproot
+ end
+
+ it('failure modes', function()
+ clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } })
+ assert_nolog('tempdir is not a directory', testlog)
+ assert_nolog('tempdir has invalid permissions', testlog)
+
+ local tmproot = get_tmproot()
-- Test how Nvim handles invalid tmpdir root (by hostile users or accidents).
--
@@ -292,7 +320,7 @@ describe('tmpdir', function()
expect_exit(command, ':qall!')
rmdir(tmproot)
write_file(tmproot, '') -- Not a directory, vim_mktempdir() should skip it.
- clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
+ clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } })
matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
-- Assert that broken tmpdir root was handled.
assert_log('tempdir root not a directory', testlog, 100)
@@ -303,18 +331,62 @@ describe('tmpdir', function()
os.remove(tmproot)
mkdir(tmproot)
funcs.setfperm(tmproot, 'rwxr--r--') -- Invalid permissions, vim_mktempdir() should skip it.
- clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } })
+ clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } })
matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
-- Assert that broken tmpdir root was handled.
assert_log('tempdir root has invalid permissions', testlog, 100)
end)
it('too long', function()
- local bigname = ('%s/%s'):format(faketmp, ('x'):rep(666))
+ local bigname = ('%s/%s'):format(os_tmpdir, ('x'):rep(666))
mkdir(bigname)
clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=bigname, } })
matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir().
local len = (funcs.tempname()):len()
ok(len > 4 and len < 256, '4 < len < 256', tostring(len))
end)
+
+ it('disappeared #1432', function()
+ clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=os_tmpdir, } })
+ assert_nolog('tempdir disappeared', testlog)
+
+ local function rm_tmpdir()
+ local tmpname1 = funcs.tempname()
+ local tmpdir1 = funcs.fnamemodify(tmpname1, ':h')
+ eq(funcs.stdpath('run'), tmpdir1)
+
+ rmdir(tmpdir1)
+ retry(nil, 1000, function()
+ eq(0, funcs.isdirectory(tmpdir1))
+ end)
+ local tmpname2 = funcs.tempname()
+ local tmpdir2 = funcs.fnamemodify(tmpname2, ':h')
+ neq(tmpdir1, tmpdir2)
+ end
+
+ -- Your antivirus hates you...
+ rm_tmpdir()
+ assert_log('tempdir disappeared', testlog, 100)
+ funcs.tempname()
+ funcs.tempname()
+ funcs.tempname()
+ eq('', meths.get_vvar('errmsg'))
+ rm_tmpdir()
+ funcs.tempname()
+ funcs.tempname()
+ funcs.tempname()
+ eq('E5431: tempdir disappeared (2 times)', meths.get_vvar('errmsg'))
+ rm_tmpdir()
+ eq('E5431: tempdir disappeared (3 times)', meths.get_vvar('errmsg'))
+ end)
+
+ it('$NVIM_APPNAME relative path', function()
+ clear({ env={
+ NVIM_APPNAME='a/b',
+ NVIM_LOG_FILE=testlog,
+ TMPDIR=os_tmpdir,
+ } })
+ matches([=[.*[/\\]a%%b%.[^/\\]+]=], funcs.tempname())
+ end)
+
end)
diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua
index 1bae626b98..038368c387 100644
--- a/test/functional/core/job_spec.lua
+++ b/test/functional/core/job_spec.lua
@@ -88,7 +88,6 @@ describe('jobs', function()
end)
it('append environment with pty #env', function()
- skip(is_os('win'))
nvim('command', "let $VAR = 'abc'")
nvim('command', "let $TOTO = 'goodbye world'")
nvim('command', "let g:job_opts.pty = v:true")
@@ -701,7 +700,7 @@ describe('jobs', function()
os.remove('Xtest_jobstart_env')
end)
- describe('jobwait', function()
+ describe('jobwait()', function()
before_each(function()
if is_os('win') then
helpers.set_shell_powershell()
@@ -806,7 +805,6 @@ describe('jobs', function()
end)
it('can be called recursively', function()
- skip(is_os('win'), "TODO: Need `cat`")
source([[
let g:opts = {}
let g:counter = 0
@@ -876,6 +874,43 @@ describe('jobs', function()
eq({'notification', 'wait', {{-1, -1}}}, next_msg())
end)
end)
+
+ it('hides cursor and flushes messages before blocking', function()
+ local screen = Screen.new(50, 6)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue, bold = true}; -- NonText
+ [1] = {bold = true, reverse = true}; -- MsgSeparator
+ [2] = {bold = true, foreground = Screen.colors.SeaGreen}; -- MoreMsg
+ })
+ screen:attach()
+ command([[let g:id = jobstart([v:progpath, '--clean', '--headless'])]])
+ source([[
+ func PrintAndWait()
+ echon "aaa\nbbb"
+ call jobwait([g:id], 300)
+ echon "\nccc"
+ endfunc
+ ]])
+ feed_command('call PrintAndWait()')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {1: }|
+ aaa |
+ bbb |
+ ]], timeout=100}
+ screen:expect{grid=[[
+ |
+ {1: }|
+ aaa |
+ bbb |
+ ccc |
+ {2:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<CR>')
+ funcs.jobstop(meths.get_var('id'))
+ end)
end)
pending('exit event follows stdout, stderr', function()
diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua
index ab11e14a67..19c7a93730 100644
--- a/test/functional/core/main_spec.lua
+++ b/test/functional/core/main_spec.lua
@@ -1,8 +1,9 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
+local matches = helpers.matches
local feed = helpers.feed
local eval = helpers.eval
local clear = helpers.clear
@@ -12,34 +13,38 @@ local write_file = helpers.write_file
local is_os = helpers.is_os
local skip = helpers.skip
-describe('Command-line option', function()
+describe('command-line option', function()
describe('-s', function()
local fname = 'Xtest-functional-core-main-s'
local fname_2 = fname .. '.2'
local nonexistent_fname = fname .. '.nonexistent'
local dollar_fname = '$' .. fname
+
before_each(function()
clear()
os.remove(fname)
os.remove(dollar_fname)
end)
+
after_each(function()
os.remove(fname)
os.remove(dollar_fname)
end)
+
it('treats - as stdin', function()
- eq(nil, lfs.attributes(fname))
+ eq(nil, luv.fs_stat(fname))
funcs.system(
{nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', '--headless',
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'-s', '-', fname},
{':call setline(1, "42")', ':wqall!', ''})
eq(0, eval('v:shell_error'))
- local attrs = lfs.attributes(fname)
+ local attrs = luv.fs_stat(fname)
eq(#('42\n'), attrs.size)
end)
+
it('does not expand $VAR', function()
- eq(nil, lfs.attributes(fname))
+ eq(nil, luv.fs_stat(fname))
eq(true, not not dollar_fname:find('%$%w+'))
write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n')
funcs.system(
@@ -47,9 +52,10 @@ describe('Command-line option', function()
'--cmd', 'set noswapfile shortmess+=IFW fileformats=unix',
'-s', dollar_fname, fname})
eq(0, eval('v:shell_error'))
- local attrs = lfs.attributes(fname)
+ local attrs = luv.fs_stat(fname)
eq(#('100500\n'), attrs.size)
end)
+
it('does not crash after reading from stdin in non-headless mode', function()
skip(is_os('win'))
local screen = Screen.new(40, 8)
@@ -100,6 +106,7 @@ describe('Command-line option', function()
]])
]=]
end)
+
it('errors out when trying to use nonexistent file with -s', function()
eq(
'Cannot open for reading: "'..nonexistent_fname..'": no such file or directory\n',
@@ -110,6 +117,7 @@ describe('Command-line option', function()
'-s', nonexistent_fname}))
eq(2, eval('v:shell_error'))
end)
+
it('errors out when trying to use -s twice', function()
write_file(fname, ':call setline(1, "1")\n:wqall!\n')
write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n')
@@ -121,7 +129,14 @@ describe('Command-line option', function()
'--cmd', 'language C',
'-s', fname, '-s', dollar_fname, fname_2}))
eq(2, eval('v:shell_error'))
- eq(nil, lfs.attributes(fname_2))
+ eq(nil, luv.fs_stat(fname_2))
end)
end)
+
+ it('nvim -v, :version', function()
+ matches('Run ":verbose version"', funcs.execute(':version'))
+ matches('Compilation: .*Run :checkhealth', funcs.execute(':verbose version'))
+ matches('Run "nvim %-V1 %-v"', funcs.system({nvim_prog_abs(), '-v'}))
+ matches('Compilation: .*Run :checkhealth', funcs.system({nvim_prog_abs(), '-V1', '-v'}))
+ end)
end)
diff --git a/test/functional/core/path_spec.lua b/test/functional/core/path_spec.lua
index a786887bbd..97c32f7de6 100644
--- a/test/functional/core/path_spec.lua
+++ b/test/functional/core/path_spec.lua
@@ -1,21 +1,25 @@
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 command = helpers.command
-local insert = helpers.insert
local feed = helpers.feed
+local funcs = helpers.funcs
+local insert = helpers.insert
local is_os = helpers.is_os
+local mkdir = helpers.mkdir
+local rmdir = helpers.rmdir
+local write_file = helpers.write_file
+
+local function join_path(...)
+ local pathsep = (is_os('win') and '\\' or '/')
+ return table.concat({...}, pathsep)
+end
describe('path collapse', function()
local targetdir
local expected_path
- local function join_path(...)
- local pathsep = (is_os('win') and '\\' or '/')
- return table.concat({...}, pathsep)
- end
-
before_each(function()
targetdir = join_path('test', 'functional', 'fixtures')
clear()
@@ -57,14 +61,108 @@ describe('path collapse', function()
end)
end)
+describe('expand wildcard', function()
+ before_each(clear)
+
+ it('with special characters #24421', function()
+ local folders = is_os('win') and {
+ '{folder}',
+ 'folder$name'
+ } or {
+ 'folder-name',
+ 'folder#name'
+ }
+ for _, folder in ipairs(folders) do
+ mkdir(folder)
+ local file = join_path(folder, 'file.txt')
+ write_file(file, '')
+ eq(file, eval('expand("'..folder..'/*")'))
+ rmdir(folder)
+ end
+ end)
+end)
+
describe('file search', function()
+ local testdir = 'Xtest_path_spec'
+
before_each(clear)
- it('find multibyte file name in line #20517', function()
+ setup(function()
+ mkdir(testdir)
+ end)
+
+ teardown(function()
+ rmdir(testdir)
+ end)
+
+ it('gf finds multibyte filename in line #20517', function()
command('cd test/functional/fixtures')
insert('filename_with_unicode_ααα')
eq('', eval('expand("%")'))
feed('gf')
eq('filename_with_unicode_ααα', eval('expand("%:t")'))
end)
+
+ it('gf/<cfile> matches Windows drive-letter filepaths (without ":" in &isfname)', function()
+ local iswin = is_os('win')
+ local function test_cfile(input, expected, expected_win)
+ expected = (iswin and expected_win or expected) or input
+ command('%delete')
+ insert(input)
+ command('norm! 0')
+ eq(expected, eval('expand("<cfile>")'))
+ end
+
+ test_cfile([[c:/d:/foo/bar.txt]]) -- TODO(justinmk): should return "d:/foo/bar.txt" ?
+ test_cfile([[//share/c:/foo/bar/]])
+ test_cfile([[file://c:/foo/bar]])
+ test_cfile([[file://c:/foo/bar:42]])
+ test_cfile([[file://c:/foo/bar:42:666]])
+ test_cfile([[https://c:/foo/bar]])
+ test_cfile([[\foo\bar]], [[foo]], [[\foo\bar]])
+ test_cfile([[/foo/bar]], [[/foo/bar]])
+ test_cfile([[c:\foo\bar]], [[c:]], [[c:\foo\bar]])
+ test_cfile([[c:\foo\bar:42:666]], [[c:]], [[c:\foo\bar]])
+ test_cfile([[c:/foo/bar]])
+ test_cfile([[c:/foo/bar:42]], [[c:/foo/bar]])
+ test_cfile([[c:/foo/bar:42:666]], [[c:/foo/bar]])
+ test_cfile([[c:foo\bar]], [[c]])
+ test_cfile([[c:foo/bar]], [[c]])
+ test_cfile([[c:foo]], [[c]])
+ -- Examples from: https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#example-ways-to-refer-to-the-same-file
+ test_cfile([[c:\temp\test-file.txt]], [[c:]], [[c:\temp\test-file.txt]])
+ test_cfile([[\\127.0.0.1\c$\temp\test-file.txt]], [[127.0.0.1]], [[\\127.0.0.1\c$\temp\test-file.txt]])
+ test_cfile([[\\LOCALHOST\c$\temp\test-file.txt]], [[LOCALHOST]], [[\\LOCALHOST\c$\temp\test-file.txt]])
+ -- not supported yet
+ test_cfile([[\\.\c:\temp\test-file.txt]], [[.]], [[\\.\c]])
+ -- not supported yet
+ test_cfile([[\\?\c:\temp\test-file.txt]], [[c:]], [[\\]])
+ test_cfile([[\\.\UNC\LOCALHOST\c$\temp\test-file.txt]], [[.]], [[\\.\UNC\LOCALHOST\c$\temp\test-file.txt]])
+ test_cfile([[\\127.0.0.1\c$\temp\test-file.txt]], [[127.0.0.1]], [[\\127.0.0.1\c$\temp\test-file.txt]])
+ end)
+
+ ---@param funcname 'finddir' | 'findfile'
+ local function test_find_func(funcname, folder, item)
+ local d = join_path(testdir, folder)
+ mkdir(d)
+ local expected = join_path(d, item)
+ if funcname == 'finddir' then
+ mkdir(expected)
+ else
+ write_file(expected, '')
+ end
+ eq(expected, funcs[funcname](item, d:gsub(' ', [[\ ]])))
+ end
+
+ it('finddir()', function()
+ test_find_func('finddir', 'directory', 'folder')
+ test_find_func('finddir', 'directory', 'folder name')
+ test_find_func('finddir', 'fold#er name', 'directory')
+ end)
+
+ it('findfile()', function()
+ test_find_func('findfile', 'directory', 'file.txt')
+ test_find_func('findfile', 'directory', 'file name.txt')
+ test_find_func('findfile', 'fold#er name', 'file.txt')
+ end)
end)
diff --git a/test/functional/core/remote_spec.lua b/test/functional/core/remote_spec.lua
index 846d79abf3..a0ec748446 100644
--- a/test/functional/core/remote_spec.lua
+++ b/test/functional/core/remote_spec.lua
@@ -3,11 +3,12 @@ local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
+local exec_capture = helpers.exec_capture
local exec_lua = helpers.exec_lua
local expect = helpers.expect
local funcs = helpers.funcs
local insert = helpers.insert
-local meths = helpers.meths
+local nvim_prog = helpers.nvim_prog
local new_argv = helpers.new_argv
local neq = helpers.neq
local set_session = helpers.set_session
@@ -38,10 +39,10 @@ describe('Remote', function()
server:close()
end)
+ -- Run a `nvim --remote*` command and return { stdout, stderr } of the process
local function run_remote(...)
set_session(server)
local addr = funcs.serverlist()[1]
- local client_argv = new_argv({args={'--server', addr, ...}})
-- Create an nvim instance just to run the remote-invoking nvim. We want
-- to wait for the remote instance to exit and calling jobwait blocks
@@ -50,32 +51,43 @@ describe('Remote', function()
local client_starter = spawn(new_argv(), false, nil, true)
set_session(client_starter)
-- Call jobstart() and jobwait() in the same RPC request to reduce flakiness.
- eq({ 0 }, exec_lua([[return vim.fn.jobwait({ vim.fn.jobstart(...) })]], client_argv))
+ eq({ 0 }, exec_lua([[return vim.fn.jobwait({ vim.fn.jobstart({...}, {
+ stdout_buffered = true,
+ stderr_buffered = true,
+ on_stdout = function(_, data, _)
+ _G.Remote_stdout = table.concat(data, '\n')
+ end,
+ on_stderr = function(_, data, _)
+ _G.Remote_stderr = table.concat(data, '\n')
+ end,
+ }) })]], nvim_prog, '--clean', '--headless', '--server', addr, ...))
+ local res = exec_lua([[return { _G.Remote_stdout, _G.Remote_stderr }]])
client_starter:close()
set_session(server)
+ return res
end
it('edit a single file', function()
- run_remote('--remote', fname)
+ eq({ '', '' }, run_remote('--remote', fname))
expect(contents)
eq(2, #funcs.getbufinfo())
end)
it('tab edit a single file with a non-changed buffer', function()
- run_remote('--remote-tab', fname)
+ eq({ '', '' }, run_remote('--remote-tab', fname))
expect(contents)
eq(1, #funcs.gettabinfo())
end)
it('tab edit a single file with a changed buffer', function()
insert('hello')
- run_remote('--remote-tab', fname)
+ eq({ '', '' }, run_remote('--remote-tab', fname))
expect(contents)
eq(2, #funcs.gettabinfo())
end)
it('edit multiple files', function()
- run_remote('--remote', fname, other_fname)
+ eq({ '', '' }, run_remote('--remote', fname, other_fname))
expect(contents)
command('next')
expect(other_contents)
@@ -83,7 +95,7 @@ describe('Remote', function()
end)
it('send keys', function()
- run_remote('--remote-send', ':edit '..fname..'<CR><C-W>v')
+ eq({ '', '' }, run_remote('--remote-send', ':edit '..fname..'<CR><C-W>v'))
expect(contents)
eq(2, #funcs.getwininfo())
-- Only a single buffer as we're using edit and not drop like --remote does
@@ -91,8 +103,13 @@ describe('Remote', function()
end)
it('evaluate expressions', function()
- run_remote('--remote-expr', 'setline(1, "Yo")')
+ eq({ '0', '' }, run_remote('--remote-expr', 'setline(1, "Yo")'))
+ eq({ 'Yo', '' }, run_remote('--remote-expr', 'getline(1)'))
expect('Yo')
+ eq({ ('k'):rep(1234), '' }, run_remote('--remote-expr', 'repeat("k", 1234)'))
+ eq({ '1.25', '' }, run_remote('--remote-expr', '1.25'))
+ eq({ 'no', '' }, run_remote('--remote-expr', '0z6E6F'))
+ eq({ '\t', '' }, run_remote('--remote-expr', '"\t"'))
end)
end)
@@ -101,7 +118,7 @@ describe('Remote', function()
expect(contents)
eq(1, #funcs.getbufinfo())
-- Since we didn't pass silent, we should get a complaint
- neq(nil, string.find(meths.exec('messages', true), 'E247'))
+ neq(nil, string.find(exec_capture('messages'), 'E247:'))
end)
it('creates server if not found with tabs', function()
@@ -110,10 +127,10 @@ describe('Remote', function()
eq(2, #funcs.gettabinfo())
eq(2, #funcs.getbufinfo())
-- We passed silent, so no message should be issued about the server not being found
- eq(nil, string.find(meths.exec('messages', true), 'E247'))
+ eq(nil, string.find(exec_capture('messages'), 'E247:'))
end)
- pending('exits with error on', function()
+ describe('exits with error on', function()
local function run_and_check_exit_code(...)
local bogus_argv = new_argv(...)
diff --git a/test/functional/core/spellfile_spec.lua b/test/functional/core/spellfile_spec.lua
index afd2c1bce4..e3a59085cf 100644
--- a/test/functional/core/spellfile_spec.lua
+++ b/test/functional/core/spellfile_spec.lua
@@ -1,5 +1,4 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
local eq = helpers.eq
local clear = helpers.clear
@@ -7,6 +6,7 @@ local meths = helpers.meths
local exc_exec = helpers.exc_exec
local rmdir = helpers.rmdir
local write_file = helpers.write_file
+local mkdir = helpers.mkdir
local testdir = 'Xtest-functional-spell-spellfile.d'
@@ -14,8 +14,8 @@ describe('spellfile', function()
before_each(function()
clear()
rmdir(testdir)
- lfs.mkdir(testdir)
- lfs.mkdir(testdir .. '/spell')
+ mkdir(testdir)
+ mkdir(testdir .. '/spell')
end)
after_each(function()
rmdir(testdir)
@@ -24,7 +24,7 @@ describe('spellfile', function()
-- │ ┌ Spell file version (#VIMSPELLVERSION)
local spellheader = 'VIMspell\050'
it('errors out when prefcond section is truncated', function()
- meths.set_option('runtimepath', testdir)
+ meths.set_option_value('runtimepath', testdir, {})
write_file(testdir .. '/spell/en.ascii.spl',
-- ┌ Section identifier (#SN_PREFCOND)
-- │ ┌ Section flags (#SNF_REQUIRED or zero)
@@ -34,12 +34,12 @@ describe('spellfile', function()
-- │ ┌ Condition length (1 byte)
-- │ │ ┌ Condition regex (missing!)
.. '\000\001\001')
- meths.set_option('spelllang', 'en')
+ meths.set_option_value('spelllang', 'en', {})
eq('Vim(set):E758: Truncated spell file',
exc_exec('set spell'))
end)
it('errors out when prefcond regexp contains NUL byte', function()
- meths.set_option('runtimepath', testdir)
+ meths.set_option_value('runtimepath', testdir, {})
write_file(testdir .. '/spell/en.ascii.spl',
-- ┌ Section identifier (#SN_PREFCOND)
-- │ ┌ Section flags (#SNF_REQUIRED or zero)
@@ -54,12 +54,12 @@ describe('spellfile', function()
-- │ ┌ KWORDTREE tree length (4 bytes)
-- │ │ ┌ PREFIXTREE tree length
.. '\000\000\000\000\000\000\000\000\000\000\000\000')
- meths.set_option('spelllang', 'en')
+ meths.set_option_value('spelllang', 'en', {})
eq('Vim(set):E759: Format error in spell file',
exc_exec('set spell'))
end)
it('errors out when region contains NUL byte', function()
- meths.set_option('runtimepath', testdir)
+ meths.set_option_value('runtimepath', testdir, {})
write_file(testdir .. '/spell/en.ascii.spl',
-- ┌ Section identifier (#SN_REGION)
-- │ ┌ Section flags (#SNF_REQUIRED or zero)
@@ -71,12 +71,12 @@ describe('spellfile', function()
-- │ ┌ KWORDTREE tree length (4 bytes)
-- │ │ ┌ PREFIXTREE tree length
.. '\000\000\000\000\000\000\000\000\000\000\000\000')
- meths.set_option('spelllang', 'en')
+ meths.set_option_value('spelllang', 'en', {})
eq('Vim(set):E759: Format error in spell file',
exc_exec('set spell'))
end)
it('errors out when SAL section contains NUL byte', function()
- meths.set_option('runtimepath', testdir)
+ meths.set_option_value('runtimepath', testdir, {})
write_file(testdir .. '/spell/en.ascii.spl',
-- ┌ Section identifier (#SN_SAL)
-- │ ┌ Section flags (#SNF_REQUIRED or zero)
@@ -95,15 +95,15 @@ describe('spellfile', function()
-- │ ┌ KWORDTREE tree length (4 bytes)
-- │ │ ┌ PREFIXTREE tree length
.. '\000\000\000\000\000\000\000\000\000\000\000\000')
- meths.set_option('spelllang', 'en')
+ meths.set_option_value('spelllang', 'en', {})
eq('Vim(set):E759: Format error in spell file',
exc_exec('set spell'))
end)
it('errors out when spell header contains NUL bytes', function()
- meths.set_option('runtimepath', testdir)
+ meths.set_option_value('runtimepath', testdir, {})
write_file(testdir .. '/spell/en.ascii.spl',
spellheader:sub(1, -3) .. '\000\000')
- meths.set_option('spelllang', 'en')
+ meths.set_option_value('spelllang', 'en', {})
eq('Vim(set):E757: This does not look like a spell file',
exc_exec('set spell'))
end)
diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua
index e9b47a0251..94ec3d4907 100644
--- a/test/functional/core/startup_spec.lua
+++ b/test/functional/core/startup_spec.lua
@@ -9,6 +9,8 @@ local ok = helpers.ok
local eq = helpers.eq
local matches = helpers.matches
local eval = helpers.eval
+local exec = helpers.exec
+local exec_capture = helpers.exec_capture
local exec_lua = helpers.exec_lua
local feed = helpers.feed
local funcs = helpers.funcs
@@ -27,21 +29,36 @@ local meths = helpers.meths
local alter_slashes = helpers.alter_slashes
local is_os = helpers.is_os
local dedent = helpers.dedent
-
-local testfile = 'Xtest_startuptime'
-after_each(function()
- os.remove(testfile)
-end)
+local tbl_map = helpers.tbl_map
+local tbl_filter = helpers.tbl_filter
+local endswith = helpers.endswith
describe('startup', function()
it('--clean', function()
clear()
- ok(string.find(alter_slashes(meths.get_option('runtimepath')), funcs.stdpath('config'), 1, true) ~= nil)
+ ok(string.find(alter_slashes(meths.get_option_value('runtimepath', {})), funcs.stdpath('config'), 1, true) ~= nil)
clear('--clean')
- ok(string.find(alter_slashes(meths.get_option('runtimepath')), funcs.stdpath('config'), 1, true) == nil)
+ ok(string.find(alter_slashes(meths.get_option_value('runtimepath', {})), funcs.stdpath('config'), 1, true) == nil)
+ end)
+
+ it('prevents remote UI infinite loop', function()
+ clear()
+ local screen
+ screen = Screen.new(84, 3)
+ screen:attach()
+ funcs.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' })
+ screen:expect([[
+ ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) |
+ |
+ |
+ ]])
end)
it('--startuptime', function()
+ local testfile = 'Xtest_startuptime'
+ finally(function()
+ os.remove(testfile)
+ end)
clear({ args = {'--startuptime', testfile}})
assert_log('sourcing', testfile, 100)
assert_log("require%('vim%._editor'%)", testfile, 100)
@@ -58,7 +75,7 @@ describe('startup', function()
^ |
|
Entering Debug mode. Type "cont" to continue. |
- nvim_exec() |
+ nvim_exec2() |
cmd: aunmenu * |
> |
|
@@ -77,13 +94,7 @@ describe('startup', function()
end)
describe('startup', function()
- before_each(function()
- clear()
- os.remove('Xtest_startup_ttyout')
- end)
- after_each(function()
- os.remove('Xtest_startup_ttyout')
- end)
+ before_each(clear)
describe('-l Lua', function()
local function assert_l_out(expected, nvim_args, lua_args, script, input)
@@ -104,10 +115,11 @@ describe('startup', function()
it('os.exit() sets Nvim exitcode', function()
-- tricky: LeakSanitizer triggers on os.exit() and disrupts the return value, disable it
exec_lua [[
- local asan_options = os.getenv 'ASAN_OPTIONS'
- if asan_options ~= nil and asan_options ~= '' then
- vim.loop.os_setenv('ASAN_OPTIONS', asan_options..':detect_leaks=0')
+ local asan_options = os.getenv('ASAN_OPTIONS') or ''
+ if asan_options ~= '' then
+ asan_options = asan_options .. ':'
end
+ vim.uv.os_setenv('ASAN_OPTIONS', asan_options .. ':detect_leaks=0')
]]
-- nvim -l foo.lua -arg1 -- a b c
assert_l_out([[
@@ -133,12 +145,12 @@ describe('startup', function()
end)
it('executes stdin "-"', function()
- assert_l_out('arg0=- args=2 whoa',
+ assert_l_out('arg0=- args=2 whoa\n',
nil,
{ 'arg1', 'arg 2' },
'-',
"print(('arg0=%s args=%d %s'):format(_G.arg[0], #_G.arg, 'whoa'))")
- assert_l_out('biiig input: 1000042',
+ assert_l_out('biiig input: 1000042\n',
nil,
nil,
'-',
@@ -146,14 +158,37 @@ describe('startup', function()
eq(0, eval('v:shell_error'))
end)
+ it('does not truncate long print() message', function()
+ assert_l_out(('k'):rep(1234) .. '\n', nil, nil, '-', "print(('k'):rep(1234))")
+ end)
+
+ it('does not add newline when unnecessary', function()
+ assert_l_out('', nil, nil, '-', '')
+ assert_l_out('foobar\n', nil, nil, '-', [[print('foobar\n')]])
+ end)
+
it('sets _G.arg', function()
+ -- nvim -l foo.lua
+ assert_l_out([[
+ bufs:
+ nvim args: 3
+ lua args: {
+ [0] = "test/functional/fixtures/startup.lua"
+ }
+ ]],
+ {},
+ {}
+ )
+ eq(0, eval('v:shell_error'))
+
-- nvim -l foo.lua [args]
assert_l_out([[
bufs:
nvim args: 7
lua args: { "-arg1", "--arg2", "--", "arg3",
[0] = "test/functional/fixtures/startup.lua"
- }]],
+ }
+ ]],
{},
{ '-arg1', '--arg2', '--', 'arg3' }
)
@@ -165,7 +200,8 @@ describe('startup', function()
nvim args: 10
lua args: { "-arg1", "arg 2", "--", "file3", "file4",
[0] = "test/functional/fixtures/startup.lua"
- }]],
+ }
+ ]],
{ 'file1', 'file2', },
{ '-arg1', 'arg 2', '--', 'file3', 'file4' }
)
@@ -177,7 +213,8 @@ describe('startup', function()
nvim args: 5
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
- }]],
+ }
+ ]],
{},
{ '-c', 'set wrap?' }
)
@@ -193,7 +230,8 @@ describe('startup', function()
nvim args: 7
lua args: { "-c", "set wrap?",
[0] = "test/functional/fixtures/startup.lua"
- }]],
+ }
+ ]],
{ '-c', 'set wrap?' },
{ '-c', 'set wrap?' }
)
@@ -201,15 +239,24 @@ describe('startup', function()
end)
it('disables swapfile/shada/config/plugins', function()
- assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scriptnames=1',
+ assert_l_out('updatecount=0 shadafile=NONE loadplugins=false scripts=1\n',
nil,
nil,
'-',
- [[print(('updatecount=%d shadafile=%s loadplugins=%s scriptnames=%d'):format(
- vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.split(vim.fn.execute('scriptnames'),'\n'))))]])
+ [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format(
+ vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]])
end)
end)
+ it('--cmd/-c/+ do not truncate long Lua print() message with --headless', function()
+ local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
+ '--cmd', 'lua print(("A"):rep(1234))',
+ '-c', 'lua print(("B"):rep(1234))',
+ '+lua print(("C"):rep(1234))',
+ '+q' })
+ eq(('A'):rep(1234) .. '\r\n' .. ('B'):rep(1234) .. '\r\n' .. ('C'):rep(1234), out)
+ end)
+
it('pipe at both ends: has("ttyin")==0 has("ttyout")==0', function()
-- system() puts a pipe at both ends.
local out = funcs.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless',
@@ -254,6 +301,10 @@ describe('startup', function()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
+ os.remove('Xtest_startup_ttyout')
+ finally(function()
+ os.remove('Xtest_startup_ttyout')
+ end)
-- Running in :terminal
command([[exe printf("terminal %s -u NONE -i NONE --cmd \"]]
..nvim_set..[[\"]]
@@ -271,6 +322,10 @@ describe('startup', function()
if is_os('win') then
command([[set shellcmdflag=/s\ /c shellxquote=\"]])
end
+ os.remove('Xtest_startup_ttyout')
+ finally(function()
+ os.remove('Xtest_startup_ttyout')
+ end)
-- Running in :terminal
command([[exe printf("terminal echo foo | ]] -- Input from a pipe.
..[[%s -u NONE -i NONE --cmd \"]]
@@ -330,28 +385,6 @@ describe('startup', function()
{ '' }))
end)
- it('-e/-E interactive #7679', function()
- clear('-e')
- local screen = Screen.new(25, 3)
- screen:attach()
- feed("put ='from -e'<CR>")
- screen:expect([[
- :put ='from -e' |
- from -e |
- :^ |
- ]])
-
- clear('-E')
- screen = Screen.new(25, 3)
- screen:attach()
- feed("put ='from -E'<CR>")
- screen:expect([[
- :put ='from -E' |
- from -E |
- :^ |
- ]])
- end)
-
it('stdin with -es/-Es #7679', function()
local input = { 'append', 'line1', 'line2', '.', '%print', '' }
local inputstr = table.concat(input, '\n')
@@ -397,33 +430,16 @@ describe('startup', function()
for _,arg in ipairs({'-es', '-Es'}) do
local out = funcs.system({nvim_prog, arg,
'+set swapfile? updatecount? shadafile?',
- "+put =execute('scriptnames')", '+%print'})
+ "+put =map(getscriptinfo(), {-> v:val.name})", '+%print'})
local line1 = string.match(out, '^.-\n')
-- updatecount=0 means swapfile was disabled.
eq(" swapfile updatecount=0 shadafile=\n", line1)
-- Standard plugins were loaded, but not user config.
- eq('health.vim', string.match(out, 'health.vim'))
- eq(nil, string.match(out, 'init.vim'))
+ ok(string.find(out, 'man.lua') ~= nil)
+ ok(string.find(out, 'init.vim') == nil)
end
end)
- it('-e sets ex mode', function()
- local screen = Screen.new(25, 3)
- clear('-e')
- screen:attach()
- -- Verify we set the proper mode both before and after :vi.
- feed("put =mode(1)<CR>vi<CR>:put =mode(1)<CR>")
- screen:expect([[
- cv |
- ^n |
- :put =mode(1) |
- ]])
-
- eq('cv\n',
- funcs.system({nvim_prog, '-n', '-es' },
- { 'put =mode(1)', 'print', '' }))
- end)
-
it('fails on --embed with -es/-Es/-l', function()
matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l',
funcs.system({nvim_prog, '--embed', '-es' }))
@@ -433,17 +449,6 @@ describe('startup', function()
funcs.system({nvim_prog, '--embed', '-l', 'foo.lua' }))
end)
- it('does not crash if --embed is given twice', function()
- clear{args={'--embed'}}
- assert_alive()
- end)
-
- it('does not crash when expanding cdpath during early_init', function()
- clear{env={CDPATH='~doesnotexist'}}
- assert_alive()
- eq(',~doesnotexist', eval('&cdpath'))
- end)
-
it('ENTER dismisses early message #7967', function()
local screen
screen = Screen.new(60, 6)
@@ -469,22 +474,24 @@ describe('startup', function()
]])
end)
- it("sets 'shortmess' when loading other tabs", function()
- clear({args={'-p', 'a', 'b', 'c'}})
- local screen = Screen.new(25, 4)
- screen:attach()
- screen:expect({grid=[[
- {1: a }{2: b c }{3: }{2:X}|
- ^ |
- {4:~ }|
- |
- ]],
- attr_ids={
- [1] = {bold = true},
- [2] = {background = Screen.colors.LightGrey, underline = true},
- [3] = {reverse = true},
- [4] = {bold = true, foreground = Screen.colors.Blue1},
- }})
+ it('-r works without --headless in PTY #23294', function()
+ exec([[
+ func Normalize(data) abort
+ " Windows: remove ^M and term escape sequences
+ return map(a:data, 'substitute(substitute(v:val, "\r", "", "g"), "\x1b\\%(\\]\\d\\+;.\\{-}\x07\\|\\[.\\{-}[\x40-\x7E]\\)", "", "g")')
+ endfunc
+ func OnOutput(id, data, event) dict
+ let g:stdout = Normalize(a:data)
+ endfunc
+ call jobstart([v:progpath, '-u', 'NONE', '-i', 'NONE', '-r'], {
+ \ 'pty': v:true,
+ \ 'stdout_buffered': v:true,
+ \ 'on_stdout': function('OnOutput'),
+ \ })
+ ]])
+ retry(nil, nil, function()
+ eq('Swap files found:', eval('g:stdout[0]'))
+ end)
end)
it('fixed hang issue with --headless (#11386)', function()
@@ -512,7 +519,102 @@ describe('startup', function()
'+q' })
eq('[\'+q\'] 1', out)
end)
+end)
+describe('startup', function()
+ it('-e/-E interactive #7679', function()
+ clear('-e')
+ local screen = Screen.new(25, 3)
+ screen:attach()
+ feed("put ='from -e'<CR>")
+ screen:expect([[
+ :put ='from -e' |
+ from -e |
+ :^ |
+ ]])
+
+ clear('-E')
+ screen = Screen.new(25, 3)
+ screen:attach()
+ feed("put ='from -E'<CR>")
+ screen:expect([[
+ :put ='from -E' |
+ from -E |
+ :^ |
+ ]])
+ end)
+
+ it('-e sets ex mode', function()
+ local screen = Screen.new(25, 3)
+ clear('-e')
+ screen:attach()
+ -- Verify we set the proper mode both before and after :vi.
+ feed("put =mode(1)<CR>vi<CR>:put =mode(1)<CR>")
+ screen:expect([[
+ cv |
+ ^n |
+ :put =mode(1) |
+ ]])
+
+ eq('cv\n',
+ funcs.system({nvim_prog, '-n', '-es' },
+ { 'put =mode(1)', 'print', '' }))
+ end)
+
+ it('-d does not diff non-arglist windows #13720 #21289', function()
+ write_file('Xdiff.vim', [[
+ let bufnr = nvim_create_buf(0, 1)
+ let config = {
+ \ 'relative': 'editor',
+ \ 'focusable': v:false,
+ \ 'width': 1,
+ \ 'height': 1,
+ \ 'row': 3,
+ \ 'col': 3
+ \ }
+ autocmd WinEnter * call nvim_open_win(bufnr, v:false, config)]])
+ finally(function()
+ os.remove('Xdiff.vim')
+ end)
+ clear{args={'-u', 'Xdiff.vim', '-d', 'Xdiff.vim', 'Xdiff.vim'}}
+ eq(true, meths.get_option_value('diff', {win = funcs.win_getid(1)}))
+ eq(true, meths.get_option_value('diff', {win = funcs.win_getid(2)}))
+ local float_win = funcs.win_getid(3)
+ eq('editor', meths.win_get_config(float_win).relative)
+ eq(false, meths.get_option_value('diff', {win = float_win}))
+ end)
+
+ it('does not crash if --embed is given twice', function()
+ clear{args={'--embed'}}
+ assert_alive()
+ end)
+
+ it('does not crash when expanding cdpath during early_init', function()
+ clear{env={CDPATH='~doesnotexist'}}
+ assert_alive()
+ eq(',~doesnotexist', eval('&cdpath'))
+ end)
+
+ it("sets 'shortmess' when loading other tabs", function()
+ clear({args={'-p', 'a', 'b', 'c'}})
+ local screen = Screen.new(25, 4)
+ screen:attach()
+ screen:expect({grid=[[
+ {1: a }{2: b c }{3: }{2:X}|
+ ^ |
+ {4:~ }|
+ |
+ ]],
+ attr_ids={
+ [1] = {bold = true},
+ [2] = {background = Screen.colors.LightGrey, underline = true},
+ [3] = {reverse = true},
+ [4] = {bold = true, foreground = Screen.colors.Blue1},
+ }})
+ end)
+end)
+
+describe('startup', function()
local function pack_clear(cmd)
-- add packages after config dir in rtp but before config/after
clear{args={'--cmd', 'set packpath=test/functional/fixtures', '--cmd', 'let paths=split(&rtp, ",")', '--cmd', 'let &rtp = paths[0]..",test/functional/fixtures,test/functional/fixtures/middle,"..join(paths[1:],",")', '--cmd', cmd}, env={XDG_CONFIG_HOME='test/functional/fixtures/'},
@@ -520,7 +622,6 @@ describe('startup', function()
}
end
-
it("handles &packpath during startup", function()
pack_clear [[
let g:x = bar#test()
@@ -585,7 +686,7 @@ describe('startup', function()
]]
eq({'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]])
- local rtp = meths.get_option'rtp'
+ local rtp = meths.get_option_value('rtp', {})
ok(startswith(rtp, 'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'),
'startswith(…)', 'rtp='..rtp)
end)
@@ -627,13 +728,13 @@ describe('startup', function()
end)
it('window widths are correct when modelines set &columns with tabpages', function()
- write_file('tab1.noft', 'vim: columns=81')
- write_file('tab2.noft', 'vim: columns=81')
+ write_file('Xtab1.noft', 'vim: columns=81')
+ write_file('Xtab2.noft', 'vim: columns=81')
finally(function()
- os.remove('tab1.noft')
- os.remove('tab2.noft')
+ os.remove('Xtab1.noft')
+ os.remove('Xtab2.noft')
end)
- clear({args = {'-p', 'tab1.noft', 'tab2.noft'}})
+ clear({args = {'-p', 'Xtab1.noft', 'Xtab2.noft'}})
eq(81, meths.win_get_width(0))
command('tabnext')
eq(81, meths.win_get_width(0))
@@ -691,7 +792,6 @@ describe('sysinit', function()
eq('loaded 1 xdg 0 vim 1',
eval('printf("loaded %d xdg %d vim %d", g:loaded, get(g:, "xdg", 0), get(g:, "vim", 0))'))
end)
-
end)
describe('user config init', function()
@@ -718,15 +818,16 @@ describe('user config init', function()
end)
it('loads init.lua from XDG config home by default', function()
- clear{ args_rm={'-u' }, env=xenv }
+ clear{ args_rm={'-u'}, env=xenv }
eq(1, eval('g:lua_rc'))
eq(funcs.fnamemodify(init_lua_path, ':p'), eval('$MYVIMRC'))
end)
- describe('with existing .exrc in cwd', function()
+ describe('loads existing', function()
local exrc_path = '.exrc'
local xstate = 'Xstate'
+ local xstateenv = { XDG_CONFIG_HOME=xconfig, XDG_DATA_HOME=xdata, XDG_STATE_HOME=xstate }
local function setup_exrc_file(filename)
exrc_path = filename
@@ -756,10 +857,10 @@ describe('user config init', function()
end)
for _, filename in ipairs({ '.exrc', '.nvimrc', '.nvim.lua' }) do
- it('loads ' .. filename, function ()
+ it(filename .. ' in cwd', function()
setup_exrc_file(filename)
- clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } }
+ clear{ args_rm={'-u'}, env=xstateenv }
-- The 'exrc' file is not trusted, and the prompt is skipped because there is no UI.
eq('---', eval('g:exrc_file'))
@@ -791,7 +892,7 @@ describe('user config init', function()
-- TERMINAL -- |
]], filename, string.rep(' ', 50 - #filename)))
- clear{ args_rm = {'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_STATE_HOME=xstate } }
+ clear{ args_rm={'-u'}, env=xstateenv }
-- The 'exrc' file is now trusted.
eq(filename, eval('g:exrc_file'))
end)
@@ -824,7 +925,7 @@ describe('user config init', function()
clear{ args_rm={'-u'}, env=xenv }
feed('<cr><c-c>') -- Dismiss "Conflicting config …" message.
eq(1, eval('g:lua_rc'))
- matches('^E5422: Conflicting configs', meths.exec('messages', true))
+ matches('^E5422: Conflicting configs', exec_capture('messages'))
end)
end)
end)
@@ -850,40 +951,41 @@ describe('runtime:', function()
local plugin_folder_path = table.concat({xconfig, 'nvim', 'plugin'}, pathsep)
local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep)
mkdir_p(plugin_folder_path)
+ finally(function()
+ rmdir(plugin_folder_path)
+ end)
write_file(plugin_file_path, [[ vim.g.lua_plugin = 1 ]])
clear{ args_rm={'-u'}, env=xenv }
eq(1, eval('g:lua_plugin'))
- rmdir(plugin_folder_path)
end)
it('loads plugin/*.lua from start packages', function()
- local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'category',
- 'start', 'test_plugin'}, pathsep)
+ local plugin_path = table.concat({xconfig, 'nvim', 'pack', 'category', 'start', 'test_plugin'}, pathsep)
local plugin_folder_path = table.concat({plugin_path, 'plugin'}, pathsep)
- local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'},
- pathsep)
+ local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep)
local profiler_file = 'test_startuptime.log'
-
mkdir_p(plugin_folder_path)
+ finally(function()
+ os.remove(profiler_file)
+ rmdir(plugin_path)
+ end)
+
write_file(plugin_file_path, [[vim.g.lua_plugin = 2]])
clear{ args_rm={'-u'}, args={'--startuptime', profiler_file}, env=xenv }
eq(2, eval('g:lua_plugin'))
- -- Check if plugin_file_path is listed in :scriptname
- local scripts = meths.exec(':scriptnames', true)
- assert(scripts:find(plugin_file_path))
+ -- Check if plugin_file_path is listed in getscriptinfo()
+ local scripts = tbl_map(function(s) return s.name end, funcs.getscriptinfo())
+ ok(#tbl_filter(function(s) return endswith(s, plugin_file_path) end, scripts) > 0)
-- Check if plugin_file_path is listed in startup profile
local profile_reader = io.open(profiler_file, 'r')
local profile_log = profile_reader:read('*a')
profile_reader:close()
- assert(profile_log:find(plugin_file_path))
-
- os.remove(profiler_file)
- rmdir(plugin_path)
+ ok(profile_log:find(plugin_file_path) ~= nil)
end)
it('loads plugin/*.lua from site packages', function()
@@ -893,30 +995,59 @@ describe('runtime:', function()
local plugin_after_path = table.concat({plugin_path, 'after', 'plugin'}, pathsep)
local plugin_file_path = table.concat({plugin_folder_path, 'plugin.lua'}, pathsep)
local plugin_after_file_path = table.concat({plugin_after_path, 'helloo.lua'}, pathsep)
-
mkdir_p(plugin_folder_path)
- write_file(plugin_file_path, [[table.insert(_G.lista, "unos")]])
mkdir_p(plugin_after_path)
+ finally(function()
+ rmdir(plugin_path)
+ end)
+
+ write_file(plugin_file_path, [[table.insert(_G.lista, "unos")]])
write_file(plugin_after_file_path, [[table.insert(_G.lista, "dos")]])
clear{ args_rm={'-u'}, args={'--cmd', 'lua _G.lista = {}'}, env=xenv }
eq({'unos', 'dos'}, exec_lua "return _G.lista")
-
- rmdir(plugin_path)
end)
+ it('no crash setting &rtp in plugins with :packloadall called before #18315', function()
+ local plugin_folder_path = table.concat({xconfig, 'nvim', 'plugin'}, pathsep)
+ mkdir_p(plugin_folder_path)
+ finally(function()
+ rmdir(plugin_folder_path)
+ end)
+
+ write_file(table.concat({plugin_folder_path, 'plugin.vim'}, pathsep), [[
+ let &runtimepath = &runtimepath
+ let g:vim_plugin = 1
+ ]])
+ write_file(table.concat({plugin_folder_path, 'plugin.lua'}, pathsep), [[
+ vim.o.runtimepath = vim.o.runtimepath
+ vim.g.lua_plugin = 1
+ ]])
- it('loads ftdetect/*.lua', function()
- local ftdetect_folder = table.concat({xconfig, 'nvim', 'ftdetect'}, pathsep)
- local ftdetect_file = table.concat({ftdetect_folder , 'new-ft.lua'}, pathsep)
- mkdir_p(ftdetect_folder)
- write_file(ftdetect_file , [[vim.g.lua_ftdetect = 1]])
+ clear{ args_rm={'-u'}, args = {'--cmd', 'packloadall'}, env=xenv }
- clear{ args_rm={'-u'}, env=xenv }
+ eq(1, eval('g:vim_plugin'))
+ eq(1, eval('g:lua_plugin'))
+ end)
- eq(1, eval('g:lua_ftdetect'))
- rmdir(ftdetect_folder)
+ it("loads ftdetect/*.{vim,lua} respecting 'rtp' order", function()
+ local ftdetect_folder = table.concat({xconfig, 'nvim', 'ftdetect'}, pathsep)
+ local after_ftdetect_folder = table.concat({xconfig, 'nvim', 'after', 'ftdetect'}, pathsep)
+ mkdir_p(ftdetect_folder)
+ mkdir_p(after_ftdetect_folder)
+ finally(function()
+ rmdir(ftdetect_folder)
+ rmdir(after_ftdetect_folder)
+ end)
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/ftdetect/ are loaded after all files in ftdetect/.
+ write_file(table.concat({ftdetect_folder, 'new-ft.vim'}, pathsep), [[let g:seq ..= 'A']])
+ write_file(table.concat({ftdetect_folder, 'new-ft.lua'}, pathsep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({after_ftdetect_folder, 'new-ft.vim'}, pathsep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_ftdetect_folder, 'new-ft.lua'}, pathsep), [[vim.g.seq = vim.g.seq .. 'b']])
+ clear{ args_rm={'-u'}, args = {'--cmd', 'let g:seq = ""'}, env=xenv }
+ eq('ABab', eval('g:seq'))
end)
end)
diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua
index 3b5580540f..b964fb3467 100644
--- a/test/functional/editor/K_spec.lua
+++ b/test/functional/editor/K_spec.lua
@@ -59,7 +59,7 @@ describe('K', function()
end)
it('empty string falls back to :help #19298', function()
- meths.set_option('keywordprg', '')
+ meths.set_option_value('keywordprg', '', {})
meths.buf_set_lines(0, 0, -1, true, {'doesnotexist'})
feed('K')
eq('E149: Sorry, no help for doesnotexist', meths.get_vvar('errmsg'))
diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua
index 22857efe5b..cbaf401f06 100644
--- a/test/functional/editor/completion_spec.lua
+++ b/test/functional/editor/completion_spec.lua
@@ -941,6 +941,15 @@ describe('completion', function()
end)
end)
+ it('cmdline completion supports various string options', function()
+ eq('auto', funcs.getcompletion('set foldcolumn=', 'cmdline')[2])
+ eq({'nosplit', 'split'}, funcs.getcompletion('set inccommand=', 'cmdline'))
+ eq({'ver:3,hor:6', 'hor:', 'ver:'}, funcs.getcompletion('set mousescroll=', 'cmdline'))
+ eq('BS', funcs.getcompletion('set termpastefilter=', 'cmdline')[2])
+ eq('SpecialKey', funcs.getcompletion('set winhighlight=', 'cmdline')[1])
+ eq('SpecialKey', funcs.getcompletion('set winhighlight=NonText:', 'cmdline')[1])
+ end)
+
describe('from the commandline window', function()
it('is cleared after CTRL-C', function ()
feed('q:')
@@ -988,7 +997,7 @@ describe('completion', function()
return ''
endfunction
]])
- meths.set_option('completeopt', 'menuone,noselect')
+ meths.set_option_value('completeopt', 'menuone,noselect', {})
meths.set_var('_complist', {{
word=0,
abbr=1,
@@ -1032,93 +1041,6 @@ describe('completion', function()
]])
end)
- -- oldtest: Test_ChangedP()
- it('TextChangedI and TextChangedP autocommands', function()
- curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar'})
- source([[
- set complete=. completeopt=menuone
- let g:foo = []
- autocmd! TextChanged * :call add(g:foo, "N")
- autocmd! TextChangedI * :call add(g:foo, "I")
- autocmd! TextChangedP * :call add(g:foo, "P")
- call cursor(3, 1)
- ]])
-
- command('let g:foo = []')
- feed('o')
- poke_eventloop()
- feed('<esc>')
- eq({'I'}, eval('g:foo'))
-
- command('let g:foo = []')
- feed('S')
- poke_eventloop()
- feed('f')
- poke_eventloop()
- eq({'I', 'I'}, eval('g:foo'))
- feed('<esc>')
-
- command('let g:foo = []')
- feed('S')
- poke_eventloop()
- feed('f')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- eq({'I', 'I', 'P'}, eval('g:foo'))
- feed('<esc>')
-
- command('let g:foo = []')
- feed('S')
- poke_eventloop()
- feed('f')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- eq({'I', 'I', 'P', 'P'}, eval('g:foo'))
- feed('<esc>')
-
- command('let g:foo = []')
- feed('S')
- poke_eventloop()
- feed('f')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- eq({'I', 'I', 'P', 'P', 'P'}, eval('g:foo'))
- feed('<esc>')
-
- command('let g:foo = []')
- feed('S')
- poke_eventloop()
- feed('f')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- poke_eventloop()
- feed('<C-N>')
- eq({'I', 'I', 'P', 'P', 'P', 'P'}, eval('g:foo'))
- feed('<esc>')
-
- eq({'foo', 'bar', 'foobar', 'foo'}, eval('getline(1, "$")'))
-
- source([[
- au! TextChanged
- au! TextChangedI
- au! TextChangedP
- set complete&vim completeopt&vim
- ]])
- end)
-
it('CompleteChanged autocommand', function()
curbufmeths.set_lines(0, 1, false, { 'foo', 'bar', 'foobar', ''})
source([[
@@ -1306,4 +1228,33 @@ describe('completion', function()
expect('colorscheme NOSUCHCOLORSCHEME')
assert_alive()
end)
+
+ it('complete with f flag #25598', function()
+ screen:try_resize(20, 9)
+ command('set complete+=f | edit foo | edit bar |edit foa |edit .hidden')
+ feed('i<C-n>')
+ screen:expect{grid=[[
+ foo^ |
+ {2:foo }{0: }|
+ {1:bar }{0: }|
+ {1:foa }{0: }|
+ {1:.hidden }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- }{4:match 1 of 4} |
+ ]]}
+ feed('<Esc>ccf<C-n>')
+ screen:expect{grid=[[
+ foo^ |
+ {2:foo }{0: }|
+ {1:foa }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- }{4:match 1 of 2} |
+ ]]}
+ end)
end)
diff --git a/test/functional/editor/fold_spec.lua b/test/functional/editor/fold_spec.lua
index 00e83bedc8..424ce839a2 100644
--- a/test/functional/editor/fold_spec.lua
+++ b/test/functional/editor/fold_spec.lua
@@ -4,17 +4,18 @@ local clear = helpers.clear
local insert = helpers.insert
local feed = helpers.feed
local expect = helpers.expect
-local feed_command = helpers.feed_command
+local command = helpers.command
local funcs = helpers.funcs
-local foldlevel = funcs.foldlevel
-local foldclosedend = funcs.foldclosedend
local eq = helpers.eq
+local neq = helpers.neq
describe('Folds', function()
local tempfname = 'Xtest-fold.txt'
- clear()
- before_each(function() feed_command('enew!') end)
+
+ setup(clear)
+ before_each(function() command('bwipe! | new') end)
after_each(function() os.remove(tempfname) end)
+
it('manual folding adjusts with filter', function()
insert([[
1
@@ -37,8 +38,11 @@ describe('Folds', function()
18
19
20]])
- feed_command('4,$fold', '%foldopen', '10,$fold', '%foldopen')
- feed_command('1,8! cat')
+ command('4,$fold')
+ command('%foldopen')
+ command('10,$fold')
+ command('%foldopen')
+ command('1,8! cat')
feed('5ggzdzMGdd')
expect([[
1
@@ -51,22 +55,24 @@ describe('Folds', function()
8
9]])
end)
+
describe('adjusting folds after :move', function()
local function manually_fold_indent()
-- setting foldmethod twice is a trick to get vim to set the folds for me
- feed_command('set foldmethod=indent', 'set foldmethod=manual')
+ command('setlocal foldmethod=indent')
+ command('setlocal foldmethod=manual')
-- Ensure that all folds will get closed (makes it easier to test the
-- length of folds).
- feed_command('set foldminlines=0')
+ command('setlocal foldminlines=0')
-- Start with all folds open (so :move ranges aren't affected by closed
-- folds).
- feed_command('%foldopen!')
+ command('%foldopen!')
end
local function get_folds()
local rettab = {}
for i = 1, funcs.line('$') do
- table.insert(rettab, foldlevel(i))
+ table.insert(rettab, funcs.foldlevel(i))
end
return rettab
end
@@ -75,16 +81,16 @@ describe('Folds', function()
-- This test is easy because we just need to ensure that the resulting
-- fold is the same as calculated when creating folds from scratch.
insert(insert_string)
- feed_command(move_command)
+ command(move_command)
local after_move_folds = get_folds()
-- Doesn't change anything, but does call foldUpdateAll()
- feed_command('set foldminlines=0')
+ command('setlocal foldminlines=0')
eq(after_move_folds, get_folds())
-- Set up the buffer with insert_string for the manual fold testing.
- feed_command('enew!')
+ command('enew!')
insert(insert_string)
manually_fold_indent()
- feed_command(move_command)
+ command(move_command)
end
it('neither closes nor corrupts folds', function()
@@ -130,19 +136,20 @@ a
for i = 1,funcs.line('$') do
eq(-1, funcs.foldclosed(i))
if i == 1 or i == 7 or i == 13 then
- eq(0, foldlevel(i))
+ eq(0, funcs.foldlevel(i))
elseif i == 4 then
- eq(2, foldlevel(i))
+ eq(2, funcs.foldlevel(i))
else
- eq(1, foldlevel(i))
+ eq(1, funcs.foldlevel(i))
end
end
-- folds are not corrupted
feed('zM')
- eq(6, foldclosedend(2))
- eq(12, foldclosedend(8))
- eq(18, foldclosedend(14))
+ eq(6, funcs.foldclosedend(2))
+ eq(12, funcs.foldclosedend(8))
+ eq(18, funcs.foldclosedend(14))
end)
+
it("doesn't split a fold when the move is within it", function()
test_move_indent([[
a
@@ -157,6 +164,7 @@ a
a]], '5m6')
eq({0, 1, 1, 2, 2, 2, 2, 1, 1, 0}, get_folds())
end)
+
it('truncates folds that end in the moved range', function()
test_move_indent([[
a
@@ -168,6 +176,7 @@ a
a]], '4,5m6')
eq({0, 1, 2, 0, 0, 0, 0}, get_folds())
end)
+
it('moves folds that start between moved range and destination', function()
test_move_indent([[
a
@@ -185,6 +194,7 @@ a
a]], '3,4m$')
eq({0, 1, 1, 0, 0, 1, 2, 1, 0, 0, 1, 0, 0}, get_folds())
end)
+
it('does not affect folds outside changed lines', function()
test_move_indent([[
a
@@ -198,6 +208,7 @@ a
a]], '4m5')
eq({1, 1, 1, 0, 0, 0, 1, 1, 1}, get_folds())
end)
+
it('moves and truncates folds that start in moved range', function()
test_move_indent([[
a
@@ -212,6 +223,7 @@ a
a]], '1,3m7')
eq({0, 0, 0, 0, 0, 1, 2, 0, 0, 0}, get_folds())
end)
+
it('breaks a fold when moving text into it', function()
test_move_indent([[
a
@@ -223,6 +235,7 @@ a
a]], '$m4')
eq({0, 1, 2, 2, 0, 0, 0}, get_folds())
end)
+
it('adjusts correctly when moving a range backwards', function()
test_move_indent([[
a
@@ -232,6 +245,7 @@ a
a]], '2,3m0')
eq({1, 2, 0, 0, 0}, get_folds())
end)
+
it('handles shifting all remaining folds', function()
test_move_indent([[
a
@@ -252,6 +266,7 @@ a]], '13m7')
eq({1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 0}, get_folds())
end)
end)
+
it('updates correctly on :read', function()
-- luacheck: ignore 621
helpers.write_file(tempfname, [[
@@ -265,8 +280,10 @@ a]], '13m7')
a
a
]])
- feed_command('set foldmethod=indent', '2', '%foldopen')
- feed_command('read ' .. tempfname)
+ command('setlocal foldmethod=indent')
+ command('2')
+ command('%foldopen')
+ command('read ' .. tempfname)
-- Just to check we have the correct file text.
expect([[
a
@@ -288,6 +305,7 @@ a]], '13m7')
eq(1, funcs.foldlevel(i))
end
end)
+
it('combines folds when removing separating space', function()
-- luacheck: ignore 621
insert([[
@@ -300,9 +318,11 @@ a]], '13m7')
a
a
]])
- feed_command('set foldmethod=indent', '3,5d')
+ command('setlocal foldmethod=indent')
+ command('3,5d')
eq(5, funcs.foldclosedend(1))
end)
+
it("doesn't combine folds that have a specified end", function()
insert([[
{{{
@@ -314,9 +334,12 @@ a]], '13m7')
}}}
]])
- feed_command('set foldmethod=marker', '3,5d', '%foldclose')
+ command('setlocal foldmethod=marker')
+ command('3,5d')
+ command('%foldclose')
eq(2, funcs.foldclosedend(1))
end)
+
it('splits folds according to >N and <N with foldexpr', function()
helpers.source([[
function TestFoldExpr(lnum)
@@ -350,8 +373,11 @@ a]], '13m7')
a
a
]])
- feed_command('set foldmethod=expr', 'set foldexpr=TestFoldExpr(v:lnum)', '2', 'foldopen')
- feed_command('read ' .. tempfname, '%foldclose')
+ command('setlocal foldmethod=expr foldexpr=TestFoldExpr(v:lnum)')
+ command('2')
+ command('foldopen')
+ command('read ' .. tempfname)
+ command('%foldclose')
eq(2, funcs.foldclosedend(1))
eq(0, funcs.foldlevel(3))
eq(0, funcs.foldlevel(4))
@@ -359,4 +385,64 @@ a]], '13m7')
eq(10, funcs.foldclosedend(7))
eq(14, funcs.foldclosedend(11))
end)
+
+ it('no folds remain if :delete makes buffer empty #19671', function()
+ command('setlocal foldmethod=manual')
+ funcs.setline(1, {'foo', 'bar', 'baz'})
+ command('2,3fold')
+ command('%delete')
+ eq(0, funcs.foldlevel(1))
+ end)
+
+ it('multibyte fold markers work #20438', function()
+ command('setlocal foldmethod=marker foldmarker=«,» commentstring=/*%s*/')
+ insert([[
+ bbbbb
+ bbbbb
+ bbbbb]])
+ feed('zfgg')
+ expect([[
+ bbbbb/*«*/
+ bbbbb
+ bbbbb/*»*/]])
+ eq(1, funcs.foldlevel(1))
+ end)
+
+ it('updates correctly with indent method and visual blockwise insertion #22898', function()
+ insert([[
+ a
+ b
+ ]])
+ command('setlocal foldmethod=indent shiftwidth=2')
+ feed('gg0<C-v>jI <Esc>') -- indent both lines using visual blockwise mode
+ eq(1, funcs.foldlevel(1))
+ eq(1, funcs.foldlevel(2))
+ end)
+
+ it("doesn't open folds with indent method when inserting lower foldlevel line", function()
+ insert([[
+ insert an unindented line under this line
+ keep the lines under this line folded
+ keep this line folded 1
+ keep this line folded 2
+ ]])
+ command('set foldmethod=indent shiftwidth=2 noautoindent')
+ eq(1, funcs.foldlevel(1))
+ eq(1, funcs.foldlevel(2))
+ eq(2, funcs.foldlevel(3))
+ eq(2, funcs.foldlevel(4))
+
+ feed('zo') -- open the outer fold
+ neq(-1, funcs.foldclosed(3)) -- make sure the inner fold is not open
+
+ feed('gg0oa<Esc>') -- insert unindented line
+
+ eq(1, funcs.foldlevel(1)) --| insert an unindented line under this line
+ eq(0, funcs.foldlevel(2)) --|a
+ eq(1, funcs.foldlevel(3)) --| keep the lines under this line folded
+ eq(2, funcs.foldlevel(4)) --| keep this line folded 1
+ eq(2, funcs.foldlevel(5)) --| keep this line folded 2
+
+ neq(-1, funcs.foldclosed(4)) -- make sure the inner fold is still not open
+ end)
end)
diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua
index 63f522fe6e..dc056cb252 100644
--- a/test/functional/editor/jump_spec.lua
+++ b/test/functional/editor/jump_spec.lua
@@ -48,6 +48,48 @@ describe('jumplist', function()
feed('<C-O>')
eq(buf1, funcs.bufnr('%'))
end)
+
+ it('<C-O> scrolls cursor halfway when switching buffer #25763', function()
+ write_file(fname1, ('foobar\n'):rep(100))
+ write_file(fname2, 'baz')
+
+ local screen = Screen.new(5, 25)
+ screen:attach()
+ command('set number')
+ command('edit '..fname1)
+ feed('35gg')
+ command('edit '..fname2)
+ feed('<C-O>')
+ screen:expect{grid=[[
+ {1: 24 }foobar |
+ {1: 25 }foobar |
+ {1: 26 }foobar |
+ {1: 27 }foobar |
+ {1: 28 }foobar |
+ {1: 29 }foobar |
+ {1: 30 }foobar |
+ {1: 31 }foobar |
+ {1: 32 }foobar |
+ {1: 33 }foobar |
+ {1: 34 }foobar |
+ {1: 35 }^foobar |
+ {1: 36 }foobar |
+ {1: 37 }foobar |
+ {1: 38 }foobar |
+ {1: 39 }foobar |
+ {1: 40 }foobar |
+ {1: 41 }foobar |
+ {1: 42 }foobar |
+ {1: 43 }foobar |
+ {1: 44 }foobar |
+ {1: 45 }foobar |
+ {1: 46 }foobar |
+ {1: 47 }foobar |
+ |
+ ]], attr_ids={
+ [1] = {foreground = Screen.colors.Brown};
+ }}
+ end)
end)
describe("jumpoptions=stack behaves like 'tagstack'", function()
diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua
index b3b190ef79..e669d7f2bb 100644
--- a/test/functional/editor/mark_spec.lua
+++ b/test/functional/editor/mark_spec.lua
@@ -24,7 +24,6 @@ describe('named marks', function()
os.remove(file2)
end)
-
it("can be set", function()
command("edit " .. file1)
command("mark a")
@@ -40,59 +39,59 @@ describe('named marks', function()
it("errors when set out of range with :mark", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "1000mark x")
- eq("nvim_exec(): Vim(mark):E16: Invalid range: 1000mark x", err)
+ eq("nvim_exec2(): Vim(mark):E16: Invalid range: 1000mark x", err)
end)
it("errors when set out of range with :k", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "1000kx")
- eq("nvim_exec(): Vim(k):E16: Invalid range: 1000kx", err)
+ eq("nvim_exec2(): Vim(k):E16: Invalid range: 1000kx", err)
end)
it("errors on unknown mark name with :mark", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "mark #")
- eq("nvim_exec(): Vim(mark):E191: Argument must be a letter or forward/backward quote", err)
+ eq("nvim_exec2(): Vim(mark):E191: Argument must be a letter or forward/backward quote", err)
end)
it("errors on unknown mark name with '", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! '#")
- eq("nvim_exec(): Vim(normal):E78: Unknown mark", err)
+ eq("nvim_exec2(): Vim(normal):E78: Unknown mark", err)
end)
it("errors on unknown mark name with `", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! `#")
- eq("nvim_exec(): Vim(normal):E78: Unknown mark", err)
+ eq("nvim_exec2(): Vim(normal):E78: Unknown mark", err)
end)
it("errors when moving to a mark that is not set with '", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! 'z")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
err = pcall_err(helpers.exec_capture, "normal! '.")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
end)
it("errors when moving to a mark that is not set with `", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! `z")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
err = pcall_err(helpers.exec_capture, "normal! `>")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
end)
it("errors when moving to a global mark that is not set with '", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! 'Z")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
end)
it("errors when moving to a global mark that is not set with `", function()
command("edit " .. file1)
local err = pcall_err(helpers.exec_capture, "normal! `Z")
- eq("nvim_exec(): Vim(normal):E20: Mark not set", err)
+ eq("nvim_exec2(): Vim(normal):E20: Mark not set", err)
end)
it("can move to them using '", function()
@@ -147,13 +146,27 @@ describe('named marks', function()
eq({2, 2}, cursor())
end)
+ it("can move to them using :'", function()
+ command("args " .. file1 .. " " .. file2)
+ feed("j")
+ feed("ma")
+ feed("G")
+ command("'a")
+ eq({2, 0}, cursor())
+ feed("mA")
+ command("next")
+ command("'A")
+ eq(1, meths.get_current_buf().id)
+ eq({2, 0}, cursor())
+ end)
+
it("errors when it can't find the buffer", function()
command("args " .. file1 .. " " .. file2)
feed("mA")
command("next")
command("bw! " .. file1 )
local err = pcall_err(helpers.exec_capture, "normal! 'A")
- eq("nvim_exec(): Vim(normal):E92: Buffer 1 not found", err)
+ eq("nvim_exec2(): Vim(normal):E92: Buffer 1 not found", err)
os.remove(file1)
end)
@@ -161,7 +174,7 @@ describe('named marks', function()
feed('ifoo<Esc>mA')
command('enew')
feed('ibar<Esc>')
- eq('Vim(print):E20: Mark not set', pcall_err(command, [['Aprint]]))
+ eq("Vim(print):E20: Mark not set: 'Aprint", pcall_err(command, [['Aprint]]))
end)
it("leave a context mark when moving with '", function()
@@ -413,8 +426,52 @@ describe('named marks view', function()
6 line |
^7 line |
8 line |
- {MATCH:.*} |
+ {MATCH:.*marks} |
|
]])
end)
+
+ it('fallback to standard behavior when mark is loaded from shada', function()
+ local screen = Screen.new(10, 6)
+ screen:attach()
+ command('edit ' .. file1)
+ feed('G')
+ feed('mA')
+ screen:expect([[
+ 26 line |
+ 27 line |
+ 28 line |
+ 29 line |
+ ^30 line |
+ |
+ ]])
+ command('set shadafile=Xtestfile-functional-editor-marks-shada')
+ finally(function()
+ command('set shadafile=NONE')
+ os.remove('Xtestfile-functional-editor-marks-shada')
+ end)
+ command('wshada!')
+ command('bwipe!')
+ screen:expect([[
+ ^ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ command('rshada!')
+ command('edit ' .. file1)
+ feed('`"')
+ screen:expect([[
+ 26 line |
+ 27 line |
+ 28 line |
+ 29 line |
+ ^30 line |
+ |
+ ]])
+ feed('`A')
+ screen:expect_unchanged()
+ end)
end)
diff --git a/test/functional/editor/mode_cmdline_spec.lua b/test/functional/editor/mode_cmdline_spec.lua
index 50cc5e17ee..d34b5a1a28 100644
--- a/test/functional/editor/mode_cmdline_spec.lua
+++ b/test/functional/editor/mode_cmdline_spec.lua
@@ -1,9 +1,11 @@
-- Cmdline-mode tests.
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local clear, insert, funcs, eq, feed =
helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed
local eval = helpers.eval
+local command = helpers.command
local meths = helpers.meths
describe('cmdline', function()
@@ -43,6 +45,30 @@ describe('cmdline', function()
eq('"<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>', eval('@:'))
end)
+ it('redraws statusline when toggling overstrike', function()
+ local screen = Screen.new(60, 4)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {reverse = true, bold = true}, -- StatusLine
+ })
+ screen:attach()
+ command('set laststatus=2 statusline=%!mode(1)')
+ feed(':')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {1:c }|
+ :^ |
+ ]]}
+ feed('<Insert>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {1:cr }|
+ :^ |
+ ]]}
+ end)
+
describe('history', function()
it('correctly clears start of the history', function()
-- Regression test: check absence of the memory leak when clearing start of
@@ -55,7 +81,7 @@ describe('cmdline', function()
it('correctly clears end of the history', function()
-- Regression test: check absence of the memory leak when clearing end of
-- the history using ex_getln.c/clr_history().
- meths.set_option('history', 1)
+ meths.set_option_value('history', 1, {})
eq(1, funcs.histadd(':', 'foo'))
eq(1, funcs.histdel(':'))
eq('', funcs.histget(':', -1))
diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua
index cd51a65be3..37651164f5 100644
--- a/test/functional/editor/mode_insert_spec.lua
+++ b/test/functional/editor/mode_insert_spec.lua
@@ -1,6 +1,7 @@
-- Insert-mode tests.
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert
local expect = helpers.expect
local command = helpers.command
@@ -48,6 +49,56 @@ describe('insert-mode', function()
feed('i<C-r>"')
expect('påskägg')
end)
+
+ it('double quote is removed after hit-enter prompt #22609', function()
+ local screen = Screen.new(60, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {foreground = Screen.colors.Blue}, -- SpecialKey
+ [2] = {foreground = Screen.colors.SlateBlue},
+ [3] = {bold = true}, -- ModeMsg
+ [4] = {reverse = true, bold = true}, -- MsgSeparator
+ [5] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg
+ [6] = {foreground = Screen.colors.SeaGreen, bold = true}, -- MoreMsg
+ })
+ screen:attach()
+ feed('i<C-R>')
+ screen:expect([[
+ {1:^"} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- INSERT --} |
+ ]])
+ feed('={}')
+ screen:expect([[
+ {1:"} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ={2:{}}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ {1:"} |
+ {0:~ }|
+ {4: }|
+ ={2:{}} |
+ {5:E731: Using a Dictionary as a String} |
+ {6:Press ENTER or type command to continue}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- INSERT --} |
+ ]])
+ end)
end)
describe('Ctrl-O', function()
@@ -141,4 +192,38 @@ describe('insert-mode', function()
feed('i<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><Esc>')
expect('<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>')
end)
+
+ it('multi-char mapping updates screen properly #25626', function()
+ local screen = Screen.new(60, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [1] = {bold = true, reverse = true}; -- StatusLine
+ [2] = {reverse = true}; -- StatusLineNC
+ [3] = {bold = true}; -- ModeMsg
+ })
+ screen:attach()
+ command('vnew')
+ insert('foo\nfoo\nfoo')
+ command('wincmd w')
+ command('set timeoutlen=10000')
+ command('inoremap jk <Esc>')
+ feed('i<CR>βββ<Left><Left>j')
+ screen:expect{grid=[[
+ foo │ |
+ foo │β^jβ |
+ foo │{0:~ }|
+ {0:~ }│{0:~ }|
+ {2:[No Name] [+] }{1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]]}
+ feed('k')
+ screen:expect{grid=[[
+ foo │ |
+ foo │^βββ |
+ foo │{0:~ }|
+ {0:~ }│{0:~ }|
+ {2:[No Name] [+] }{1:[No Name] [+] }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua
index 5050edff5c..47068470bb 100644
--- a/test/functional/editor/put_spec.lua
+++ b/test/functional/editor/put_spec.lua
@@ -9,22 +9,21 @@ local eq = helpers.eq
local map = helpers.tbl_map
local filter = helpers.tbl_filter
local feed_command = helpers.feed_command
+local command = helpers.command
local curbuf_contents = helpers.curbuf_contents
local funcs = helpers.funcs
local dedent = helpers.dedent
-local getreg = funcs.getreg
local function reset()
- feed_command('enew!')
+ command('bwipe! | new')
insert([[
Line of words 1
Line of words 2]])
- feed_command('goto 1')
+ command('goto 1')
feed('itest_string.<esc>u')
funcs.setreg('a', 'test_stringa', 'V')
funcs.setreg('b', 'test_stringb\ntest_stringb\ntest_stringb', 'b')
funcs.setreg('"', 'test_string"', 'v')
- feed_command('set virtualedit=')
end
-- We check the last inserted register ". in each of these tests because it is
@@ -508,10 +507,10 @@ describe('put command', function()
test_expect(exception_table, after_redo)
if selection_string then
if not conversion_table.put_backwards then
- eq(selection_string, getreg('"'))
+ eq(selection_string, funcs.getreg('"'))
end
else
- eq('test_string"', getreg('"'))
+ eq('test_string"', funcs.getreg('"'))
end
end
end
@@ -644,7 +643,7 @@ describe('put command', function()
-- Set curswant to '8' to be at the end of the tab character
-- This is where the cursor is put back after the 'u' command.
funcs.setpos('.', {0, 2, 1, 0, 8})
- feed_command('set autoindent')
+ command('set autoindent')
end
)
end)
@@ -655,7 +654,7 @@ describe('put command', function()
test_stringx" Line of words 2]]
run_normal_mode_tests(test_string, 'p', function()
funcs.setline('$', ' Line of words 2')
- feed_command('set virtualedit=all')
+ command('setlocal virtualedit=all')
funcs.setpos('.', {0, 2, 1, 2, 3})
end)
end)
@@ -667,7 +666,7 @@ describe('put command', function()
Line of words 2]]
run_normal_mode_tests(test_string, 'p', function()
funcs.setline('$', ' Line of words 2')
- feed_command('set virtualedit=all')
+ command('setlocal virtualedit=all')
funcs.setpos('.', {0, 1, 16, 1, 17})
end, true)
end)
@@ -717,7 +716,7 @@ describe('put command', function()
return function(exception_table, after_redo)
test_expect(exception_table, after_redo)
if not conversion_table.put_backwards then
- eq('Line of words 1\n', getreg('"'))
+ eq('Line of words 1\n', funcs.getreg('"'))
end
end
end
@@ -753,7 +752,7 @@ describe('put command', function()
return function(e,c)
test_expect(e,c)
if not conversion_table.put_backwards then
- eq('Lin\nLin', getreg('"'))
+ eq('Lin\nLin', funcs.getreg('"'))
end
end
end
@@ -836,7 +835,7 @@ describe('put command', function()
'vp',
function()
funcs.setline('$', ' Line of words 2')
- feed_command('set virtualedit=all')
+ command('setlocal virtualedit=all')
funcs.setpos('.', {0, 2, 1, 2, 3})
end,
nil,
@@ -851,7 +850,7 @@ describe('put command', function()
base_expect_string,
'vp',
function()
- feed_command('set virtualedit=all')
+ command('setlocal virtualedit=all')
funcs.setpos('.', {0, 1, 16, 2, 18})
end,
true,
@@ -903,6 +902,8 @@ describe('put command', function()
end
end
end, unchanged=(not should_ring)}
+ screen.bell = false
+ screen.visualbell = false
end
it('should not ring the bell with gp at end of line', function()
@@ -920,12 +921,12 @@ describe('put command', function()
end)
it('should ring the bell when deleting if not appropriate', function()
- feed_command('goto 2')
- feed('i<bs><esc>')
- expect([[
- ine of words 1
- Line of words 2]])
- bell_test(function() feed('".P') end, true)
+ command('goto 2')
+ feed('i<bs><esc>')
+ expect([[
+ ine of words 1
+ Line of words 2]])
+ bell_test(function() feed('".P') end, true)
end)
it('should restore cursor position after undo of ".p', function()
@@ -935,7 +936,7 @@ describe('put command', function()
end)
it("should be unaffected by 'autoindent' with V\".2p", function()
- feed_command('set autoindent')
+ command('set autoindent')
feed('i test_string.<esc>u')
feed('V".2p')
expect([[
diff --git a/test/functional/ex_cmds/append_spec.lua b/test/functional/ex_cmds/append_spec.lua
index fadb5c9b42..4134eed87e 100644
--- a/test/functional/ex_cmds/append_spec.lua
+++ b/test/functional/ex_cmds/append_spec.lua
@@ -8,6 +8,7 @@ local clear = helpers.clear
local funcs = helpers.funcs
local command = helpers.command
local curbufmeths = helpers.curbufmeths
+local meths = helpers.meths
local Screen = require('test.functional.ui.screen')
local cmdtest = function(cmd, prep, ret1)
@@ -42,7 +43,7 @@ local cmdtest = function(cmd, prep, ret1)
eq(hisline, funcs.histget(':', -2))
eq(cmd, funcs.histget(':'))
-- Test that command-line window was launched
- eq('nofile', curbufmeths.get_option('buftype'))
+ eq('nofile', meths.get_option_value('buftype', {}))
eq('n', funcs.mode(1))
feed('<CR>')
eq('c', funcs.mode(1))
diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua
index 5ed71651c7..b6a3713158 100644
--- a/test/functional/ex_cmds/cd_spec.lua
+++ b/test/functional/ex_cmds/cd_spec.lua
@@ -1,6 +1,6 @@
-- Specs for :cd, :tcd, :lcd and getcwd()
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
@@ -11,6 +11,7 @@ local exc_exec = helpers.exc_exec
local pathsep = helpers.get_pathsep()
local skip = helpers.skip
local is_os = helpers.is_os
+local mkdir = helpers.mkdir
-- These directories will be created for testing
local directories = {
@@ -36,14 +37,14 @@ for _, cmd in ipairs {'cd', 'chdir'} do
before_each(function()
clear()
for _, d in pairs(directories) do
- lfs.mkdir(d)
+ mkdir(d)
end
directories.start = cwd()
end)
after_each(function()
for _, d in pairs(directories) do
- lfs.rmdir(d)
+ luv.fs_rmdir(d)
end
end)
@@ -273,7 +274,7 @@ end
describe("getcwd()", function ()
before_each(function()
clear()
- lfs.mkdir(directories.global)
+ mkdir(directories.global)
end)
after_each(function()
diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua
index 919d167712..2a2628350d 100644
--- a/test/functional/ex_cmds/cmd_map_spec.lua
+++ b/test/functional/ex_cmds/cmd_map_spec.lua
@@ -90,7 +90,7 @@ describe('mappings with <Cmd>', function()
{1:~ }|
{1:~ }|
{1:~ }|
- {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} |
+ {2:E1136: <Cmd> mapping must end with <CR> before second <Cmd>} |
]])
command('noremap <F3> <Cmd>let x = 3')
@@ -103,7 +103,7 @@ describe('mappings with <Cmd>', function()
{1:~ }|
{1:~ }|
{1:~ }|
- {2:E5520: <Cmd> mapping must end with <CR>} |
+ {2:E1255: <Cmd> mapping must end with <CR>} |
]])
eq(0, eval('x'))
end)
@@ -451,7 +451,7 @@ describe('mappings with <Cmd>', function()
]])
eq('s', funcs.mode(1))
- -- visual mapping in select mode restart selct mode after operator
+ -- visual mapping in select mode restart select mode after operator
feed('<F5>')
eq('s', funcs.mode(1))
eq({'some short l'}, funcs.getreg('a',1,1))
diff --git a/test/functional/ex_cmds/dict_notifications_spec.lua b/test/functional/ex_cmds/dict_notifications_spec.lua
index afa6b519d5..6a0b40bd88 100644
--- a/test/functional/ex_cmds/dict_notifications_spec.lua
+++ b/test/functional/ex_cmds/dict_notifications_spec.lua
@@ -9,7 +9,7 @@ local command = helpers.command
local eval = helpers.eval
-describe('VimL dictionary notifications', function()
+describe('Vimscript dictionary notifications', function()
local channel
before_each(function()
diff --git a/test/functional/ex_cmds/excmd_spec.lua b/test/functional/ex_cmds/excmd_spec.lua
index e243f3c524..a92329ede5 100644
--- a/test/functional/ex_cmds/excmd_spec.lua
+++ b/test/functional/ex_cmds/excmd_spec.lua
@@ -2,6 +2,7 @@ local helpers = require("test.functional.helpers")(after_each)
local command = helpers.command
local eq = helpers.eq
local clear = helpers.clear
+local funcs = helpers.funcs
local pcall_err = helpers.pcall_err
local assert_alive = helpers.assert_alive
@@ -10,21 +11,43 @@ describe('Ex cmds', function()
clear()
end)
+ local function check_excmd_err(cmd, err)
+ eq(err .. ': ' .. cmd, pcall_err(command, cmd))
+ end
+
it('handle integer overflow from user-input #5555', function()
command(':9999999999999999999999999999999999999999')
command(':later 9999999999999999999999999999999999999999')
command(':echo expand("#<9999999999999999999999999999999999999999")')
command(':lockvar 9999999999999999999999999999999999999999')
command(':winsize 9999999999999999999999999999999999999999 9999999999999999999999999999999999999999')
- eq('Vim(tabnext):E475: Invalid argument: 9999999999999999999999999999999999999999',
- pcall_err(command, ':tabnext 9999999999999999999999999999999999999999'))
- eq('Vim(Next):E939: Positive count required',
- pcall_err(command, ':N 9999999999999999999999999999999999999999'))
+ check_excmd_err(':tabnext 9999999999999999999999999999999999999999',
+ 'Vim(tabnext):E475: Invalid argument: 9999999999999999999999999999999999999999')
+ check_excmd_err(':N 9999999999999999999999999999999999999999',
+ 'Vim(Next):E939: Positive count required')
+ check_excmd_err(':bdelete 9999999999999999999999999999999999999999',
+ 'Vim(bdelete):E939: Positive count required')
eq('Vim(menu):E329: No menu "9999999999999999999999999999999999999999"',
pcall_err(command, ':menu 9999999999999999999999999999999999999999'))
- eq('Vim(bdelete):E939: Positive count required',
- pcall_err(command, ':bdelete 9999999999999999999999999999999999999999'))
assert_alive()
end)
-end)
+ it('listing long user command does not crash', function()
+ command('execute "command" repeat("T", 255) ":"')
+ command('command')
+ end)
+
+ it(':def is an unknown command #23149', function()
+ eq('Vim:E492: Not an editor command: def', pcall_err(command, 'def'))
+ eq(1, funcs.exists(':d'))
+ eq('delete', funcs.fullcommand('d'))
+ eq(1, funcs.exists(':de'))
+ eq('delete', funcs.fullcommand('de'))
+ eq(0, funcs.exists(':def'))
+ eq('', funcs.fullcommand('def'))
+ eq(1, funcs.exists(':defe'))
+ eq('defer', funcs.fullcommand('defe'))
+ eq(2, funcs.exists(':defer'))
+ eq('defer', funcs.fullcommand('defer'))
+ end)
+end)
diff --git a/test/functional/ex_cmds/file_spec.lua b/test/functional/ex_cmds/file_spec.lua
index 771c283134..131661828e 100644
--- a/test/functional/ex_cmds/file_spec.lua
+++ b/test/functional/ex_cmds/file_spec.lua
@@ -1,17 +1,18 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
local rmdir = helpers.rmdir
+local mkdir = helpers.mkdir
describe(':file', function()
- local swapdir = lfs.currentdir()..'/Xtest-file_spec'
+ local swapdir = luv.cwd()..'/Xtest-file_spec'
before_each(function()
clear()
rmdir(swapdir)
- lfs.mkdir(swapdir)
+ mkdir(swapdir)
end)
after_each(function()
command('%bwipeout!')
diff --git a/test/functional/ex_cmds/help_spec.lua b/test/functional/ex_cmds/help_spec.lua
index 0ec7249218..aca0cbbaa6 100644
--- a/test/functional/ex_cmds/help_spec.lua
+++ b/test/functional/ex_cmds/help_spec.lua
@@ -4,6 +4,10 @@ local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local funcs = helpers.funcs
+local meths = helpers.meths
+local mkdir = helpers.mkdir
+local rmdir = helpers.rmdir
+local write_file = helpers.write_file
describe(':help', function()
before_each(clear)
@@ -25,4 +29,17 @@ describe(':help', function()
-- Before #9773, Nvim would crash on quitting the help window.
eq(1002, funcs.win_getid())
end)
+
+ it('multibyte help tags work #23975', function()
+ mkdir('Xhelptags')
+ finally(function()
+ rmdir('Xhelptags')
+ end)
+ mkdir('Xhelptags/doc')
+ write_file('Xhelptags/doc/Xhelptags.txt', '*…*')
+ command('helptags Xhelptags/doc')
+ command('set rtp+=Xhelptags')
+ command('help …')
+ eq('*…*', meths.get_current_line())
+ end)
end)
diff --git a/test/functional/ex_cmds/highlight_spec.lua b/test/functional/ex_cmds/highlight_spec.lua
index 1cd6759a53..958dd99226 100644
--- a/test/functional/ex_cmds/highlight_spec.lua
+++ b/test/functional/ex_cmds/highlight_spec.lua
@@ -3,6 +3,9 @@ local helpers = require("test.functional.helpers")(after_each)
local eq, command = helpers.eq, helpers.command
local clear = helpers.clear
local eval, exc_exec = helpers.eval, helpers.exc_exec
+local exec = helpers.exec
+local funcs = helpers.funcs
+local meths = helpers.meths
describe(':highlight', function()
local screen
@@ -21,7 +24,7 @@ describe(':highlight', function()
end)
it('invalid group name', function()
- eq('Vim(highlight):E411: highlight group not found: foo',
+ eq('Vim(highlight):E411: Highlight group not found: foo',
exc_exec("highlight foo"))
end)
@@ -36,4 +39,29 @@ describe(':highlight', function()
command('highlight normal ctermbg=red')
eq('9', eval('synIDattr(hlID("Normal"), "bg", "cterm")'))
end)
+
+ it('only the last underline style takes effect #22371', function()
+ command('highlight NonText gui=underline,undercurl')
+ eq('', eval('synIDattr(hlID("NonText"), "underline", "gui")'))
+ eq('1', eval('synIDattr(hlID("NonText"), "undercurl", "gui")'))
+ command('highlight NonText gui=undercurl,underline')
+ eq('', eval('synIDattr(hlID("NonText"), "undercurl", "gui")'))
+ eq('1', eval('synIDattr(hlID("NonText"), "underline", "gui")'))
+ end)
+
+ it('clear', function()
+ meths.set_var('colors_name', 'foo')
+ eq(1, funcs.exists('g:colors_name'))
+ command('hi clear')
+ eq(0, funcs.exists('g:colors_name'))
+ meths.set_var('colors_name', 'foo')
+ eq(1, funcs.exists('g:colors_name'))
+ exec([[
+ func HiClear()
+ hi clear
+ endfunc
+ ]])
+ funcs.HiClear()
+ eq(0, funcs.exists('g:colors_name'))
+ end)
end)
diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua
index 2583d80269..d02af21731 100644
--- a/test/functional/ex_cmds/ls_spec.lua
+++ b/test/functional/ex_cmds/ls_spec.lua
@@ -14,7 +14,7 @@ describe(':ls', function()
end)
it('R, F for :terminal buffers', function()
- nvim('set_option', 'shell', string.format('"%s" INTERACT', testprg('shell-test')))
+ nvim('set_option_value', 'shell', string.format('"%s" INTERACT', testprg('shell-test')), {})
command('edit foo')
command('set hidden')
diff --git a/test/functional/ex_cmds/make_spec.lua b/test/functional/ex_cmds/make_spec.lua
index bf585ee44c..d82f59ddf9 100644
--- a/test/functional/ex_cmds/make_spec.lua
+++ b/test/functional/ex_cmds/make_spec.lua
@@ -22,20 +22,18 @@ describe(':make', function()
end)
it('captures stderr & non zero exit code #14349', function ()
- nvim('set_option', 'makeprg', testprg('shell-test')..' foo')
+ nvim('set_option_value', 'makeprg', testprg('shell-test')..' foo', {})
local out = eval('execute("make")')
- -- Make program exit code correctly captured
- matches('\nshell returned 3', out)
-- Error message is captured in the file and printed in the footer
- matches('\n.*%: Unknown first argument%: foo', out)
+ matches('[\r\n]+.*[\r\n]+Unknown first argument%: foo[\r\n]+%(1 of 1%)%: Unknown first argument%: foo', out)
end)
it('captures stderr & zero exit code #14349', function ()
- nvim('set_option', 'makeprg', testprg('shell-test'))
+ nvim('set_option_value', 'makeprg', testprg('shell-test'), {})
local out = eval('execute("make")')
-- Ensure there are no "shell returned X" messages between
- -- command and last line (indicating zero exit)
- matches('LastExitCode%s+[(]', out)
+ -- command and last line (indicating zero exit)
+ matches('LastExitCode%s+ready [$]%s+[(]', out)
matches('\n.*%: ready [$]', out)
end)
diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua
index ec912053b2..a580e88b93 100644
--- a/test/functional/ex_cmds/map_spec.lua
+++ b/test/functional/ex_cmds/map_spec.lua
@@ -18,7 +18,7 @@ describe(':*map', function()
it('are not affected by &isident', function()
meths.set_var('counter', 0)
command('nnoremap <C-x> :let counter+=1<CR>')
- meths.set_option('isident', ('%u'):format(('>'):byte()))
+ meths.set_option_value('isident', ('%u'):format(('>'):byte()), {})
command('nnoremap <C-y> :let counter+=1<CR>')
-- &isident used to disable keycode parsing here as well
feed('\24\25<C-x><C-y>')
@@ -152,7 +152,7 @@ describe('Screen', function()
~ |
~ |
~ |
- > |
+ -- INSERT -- |
]])
end)
diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua
index 0a0c7ca410..7522d4a99c 100644
--- a/test/functional/ex_cmds/mksession_spec.lua
+++ b/test/functional/ex_cmds/mksession_spec.lua
@@ -1,4 +1,3 @@
-local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
@@ -15,16 +14,19 @@ local sleep = helpers.sleep
local meths = helpers.meths
local skip = helpers.skip
local is_os = helpers.is_os
+local mkdir = helpers.mkdir
local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec'
+if helpers.skip(helpers.is_os('win')) then return end
+
describe(':mksession', function()
local session_file = file_prefix .. '.vim'
local tab_dir = file_prefix .. '.d'
before_each(function()
clear()
- lfs.mkdir(tab_dir)
+ mkdir(tab_dir)
end)
after_each(function()
@@ -79,13 +81,13 @@ describe(':mksession', function()
local buf_count = #meths.list_bufs()
eq(2, buf_count)
- eq('terminal', meths.buf_get_option(0, 'buftype'))
+ eq('terminal', meths.get_option_value('buftype', {}))
test_terminal_session_disabled(2)
-- no terminal should be set. As a side effect we end up with a blank buffer
- eq('', meths.buf_get_option(meths.list_bufs()[1], 'buftype'))
- eq('', meths.buf_get_option(meths.list_bufs()[2], 'buftype'))
+ eq('', meths.get_option_value('buftype', { buf = meths.list_bufs()[1] }))
+ eq('', meths.get_option_value('buftype', { buf = meths.list_bufs()[2] }))
end
)
@@ -110,7 +112,7 @@ describe(':mksession', function()
it('do not restore :terminal if not set in sessionoptions, only buffer #13078', function()
command('terminal')
- eq('terminal', meths.buf_get_option(0, 'buftype'))
+ eq('terminal', meths.get_option_value('buftype', {}))
local buf_count = #meths.list_bufs()
eq(1, buf_count)
@@ -118,7 +120,7 @@ describe(':mksession', function()
test_terminal_session_disabled(1)
-- no terminal should be set
- eq('', meths.buf_get_option(0, 'buftype'))
+ eq('', meths.get_option_value('buftype', {}))
end)
it('restores tab-local working directories', function()
@@ -247,7 +249,7 @@ describe(':mksession', function()
style = 'minimal',
}
meths.open_win(buf, false, config)
- local cmdheight = meths.get_option('cmdheight')
+ local cmdheight = meths.get_option_value('cmdheight', {})
command('mksession ' .. session_file)
-- Create a new test instance of Nvim.
@@ -260,7 +262,7 @@ describe(':mksession', function()
-- window was not restored.
eq(1, funcs.winnr('$'))
-- The command-line height should remain the same as it was.
- eq(cmdheight, meths.get_option('cmdheight'))
+ eq(cmdheight, meths.get_option_value('cmdheight', {}))
os.remove(tmpfile)
end)
diff --git a/test/functional/ex_cmds/mkview_spec.lua b/test/functional/ex_cmds/mkview_spec.lua
index fef8065b2e..f71b826210 100644
--- a/test/functional/ex_cmds/mkview_spec.lua
+++ b/test/functional/ex_cmds/mkview_spec.lua
@@ -1,4 +1,3 @@
-local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
@@ -7,6 +6,7 @@ local get_pathsep = helpers.get_pathsep
local eq = helpers.eq
local funcs = helpers.funcs
local rmdir = helpers.rmdir
+local mkdir = helpers.mkdir
local file_prefix = 'Xtest-functional-ex_cmds-mkview_spec'
@@ -17,8 +17,8 @@ describe(':mkview', function()
before_each(function()
clear()
- lfs.mkdir(view_dir)
- lfs.mkdir(local_dir)
+ mkdir(view_dir)
+ mkdir(local_dir)
end)
after_each(function()
diff --git a/test/functional/ex_cmds/oldfiles_spec.lua b/test/functional/ex_cmds/oldfiles_spec.lua
index 5f87c3cdd9..19611429e0 100644
--- a/test/functional/ex_cmds/oldfiles_spec.lua
+++ b/test/functional/ex_cmds/oldfiles_spec.lua
@@ -2,6 +2,8 @@ local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
+local command = helpers.command
+local expect_exit = helpers.expect_exit
local buf, eq, feed_command = helpers.curbufmeths, helpers.eq, helpers.feed_command
local feed, poke_eventloop = helpers.feed, helpers.poke_eventloop
local ok = helpers.ok
@@ -19,6 +21,7 @@ describe(':oldfiles', function()
before_each(_clear)
after_each(function()
+ expect_exit(command, 'qall!')
os.remove(shada_file)
end)
@@ -42,6 +45,7 @@ describe(':oldfiles', function()
|
Press ENTER or type command to continue^ |
]])
+ feed('<CR>')
end)
it('can be filtered with :filter', function()
@@ -107,6 +111,7 @@ describe(':browse oldfiles', function()
end)
after_each(function()
+ expect_exit(command, 'qall!')
os.remove(shada_file)
end)
diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua
index 2b92f8d0de..249373a9c4 100644
--- a/test/functional/ex_cmds/profile_spec.lua
+++ b/test/functional/ex_cmds/profile_spec.lua
@@ -1,5 +1,5 @@
require('os')
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local eval = helpers.eval
@@ -12,25 +12,24 @@ local read_file = helpers.read_file
-- tmpname() also creates the file on POSIX systems. Remove it again.
-- We just need the name, ignoring any race conditions.
-if lfs.attributes(tempfile, 'uid') then
+if luv.fs_stat(tempfile).uid then
os.remove(tempfile)
end
local function assert_file_exists(filepath)
- -- Use 2-argument lfs.attributes() so no extra table gets created.
- -- We don't really care for the uid.
- neq(nil, lfs.attributes(filepath, 'uid'))
+ neq(nil, luv.fs_stat(filepath).uid)
end
local function assert_file_exists_not(filepath)
- eq(nil, lfs.attributes(filepath, 'uid'))
+ eq(nil, luv.fs_stat(filepath))
end
describe(':profile', function()
before_each(helpers.clear)
after_each(function()
- if lfs.attributes(tempfile, 'uid') ~= nil then
+ helpers.expect_exit(command, 'qall!')
+ if luv.fs_stat(tempfile).uid ~= nil then
os.remove(tempfile)
end
end)
diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua
index bf69ada820..62249caa5e 100644
--- a/test/functional/ex_cmds/script_spec.lua
+++ b/test/functional/ex_cmds/script_spec.lua
@@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
local neq = helpers.neq
local command = helpers.command
+local exec_capture = helpers.exec_capture
local write_file = helpers.write_file
local meths = helpers.meths
local clear = helpers.clear
@@ -34,7 +35,7 @@ describe('script_get-based command', function()
%s %s
endif
]])):format(cmd, garbage)))
- eq('', meths.exec('messages', true))
+ eq('', exec_capture('messages'))
if check_neq then
neq(0, exc_exec(dedent([[
%s %s
@@ -49,7 +50,7 @@ describe('script_get-based command', function()
EOF
endif
]])):format(cmd, garbage)))
- eq('', meths.exec('messages', true))
+ eq('', exec_capture('messages'))
if check_neq then
eq(true, pcall(source, (dedent([[
let g:exc = 0
diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua
index 64c3464be7..24987354a4 100644
--- a/test/functional/ex_cmds/source_spec.lua
+++ b/test/functional/ex_cmds/source_spec.lua
@@ -7,6 +7,7 @@ local meths = helpers.meths
local feed = helpers.feed
local feed_command = helpers.feed_command
local write_file = helpers.write_file
+local tmpname = helpers.tmpname
local exec = helpers.exec
local exc_exec = helpers.exc_exec
local exec_lua = helpers.exec_lua
@@ -48,7 +49,7 @@ describe(':source', function()
pending("'shellslash' only works on Windows")
return
end
- meths.set_option('shellslash', false)
+ meths.set_option_value('shellslash', false, {})
mkdir('Xshellslash')
write_file([[Xshellslash/Xstack.vim]], [[
@@ -96,12 +97,12 @@ describe(':source', function()
let d = s:s]])
command('source')
- eq('2', meths.exec('echo a', true))
- eq("{'k': 'v'}", meths.exec('echo b', true))
+ eq('2', exec_capture('echo a'))
+ eq("{'k': 'v'}", exec_capture('echo b'))
-- Script items are created only on script var access
- eq("1", meths.exec('echo c', true))
- eq("0zBEEFCAFE", meths.exec('echo d', true))
+ eq("1", exec_capture('echo c'))
+ eq("0zBEEFCAFE", exec_capture('echo d'))
exec('set cpoptions+=C')
eq('Vim(let):E723: Missing end of Dictionary \'}\': ', exc_exec('source'))
@@ -124,14 +125,14 @@ describe(':source', function()
-- Source the 2nd line only
feed('ggjV')
feed_command(':source')
- eq('3', meths.exec('echo a', true))
+ eq('3', exec_capture('echo a'))
-- Source from 2nd line to end of file
feed('ggjVG')
feed_command(':source')
- eq('4', meths.exec('echo a', true))
- eq("{'K': 'V'}", meths.exec('echo b', true))
- eq("<SNR>1_C()", meths.exec('echo D()', true))
+ eq('4', exec_capture('echo a'))
+ eq("{'K': 'V'}", exec_capture('echo b'))
+ eq("<SNR>1_C()", exec_capture('echo D()'))
-- Source last line only
feed_command(':$source')
@@ -147,7 +148,7 @@ describe(':source', function()
let a = 123
]]
command('source')
- eq('123', meths.exec('echo a', true))
+ eq('123', exec_capture('echo a'))
end)
it('multiline heredoc command', function()
@@ -157,7 +158,7 @@ describe(':source', function()
EOF]])
command('source')
- eq('4', meths.exec('echo luaeval("y")', true))
+ eq('4', exec_capture('echo luaeval("y")'))
end)
it('can source lua files', function()
@@ -179,56 +180,65 @@ describe(':source', function()
os.remove(test_file)
end)
- it('can source selected region in lua file', function()
- local test_file = 'test.lua'
-
- write_file (test_file, [[
- vim.g.b = 5
- vim.g.b = 6
- vim.g.b = 7
- a = [=[
- "\ a
- \ b]=]
- ]])
-
- command('edit '..test_file)
-
- feed('ggjV')
- feed_command(':source')
- eq(6, eval('g:b'))
-
- feed('GVkk')
- feed_command(':source')
- eq(' "\\ a\n \\ b', exec_lua('return _G.a'))
-
- os.remove(test_file)
- end)
-
- it('can source current lua buffer without argument', function()
- local test_file = 'test.lua'
-
- write_file(test_file, [[
- vim.g.c = 10
- vim.g.c = 11
- vim.g.c = 12
- a = [=[
- \ 1
- "\ 2]=]
- vim.g.sfile_value = vim.fn.expand('<sfile>')
- vim.g.stack_value = vim.fn.expand('<stack>')
- vim.g.script_value = vim.fn.expand('<script>')
- ]])
-
- command('edit '..test_file)
- feed_command(':source')
-
- eq(12, eval('g:c'))
- eq(' \\ 1\n "\\ 2', exec_lua('return _G.a'))
- eq(':source (no file)', meths.get_var('sfile_value'))
- eq(':source (no file)', meths.get_var('stack_value'))
- eq(':source (no file)', meths.get_var('script_value'))
+ describe('can source current buffer', function()
+ local function test_source_lua_curbuf()
+ it('selected region', function()
+ insert([[
+ vim.g.b = 5
+ vim.g.b = 6
+ vim.g.b = 7
+ a = [=[
+ "\ a
+ \ b]=]
+ ]])
+ feed('dd')
+
+ feed('ggjV')
+ feed_command(':source')
+ eq(6, eval('g:b'))
+
+ feed('GVkk')
+ feed_command(':source')
+ eq(' "\\ a\n \\ b', exec_lua('return _G.a'))
+ end)
+
+ it('whole buffer', function()
+ insert([[
+ vim.g.c = 10
+ vim.g.c = 11
+ vim.g.c = 12
+ a = [=[
+ \ 1
+ "\ 2]=]
+ vim.g.sfile_value = vim.fn.expand('<sfile>')
+ vim.g.stack_value = vim.fn.expand('<stack>')
+ vim.g.script_value = vim.fn.expand('<script>')
+ ]])
+ feed('dd')
+
+ feed_command(':source')
+
+ eq(12, eval('g:c'))
+ eq(' \\ 1\n "\\ 2', exec_lua('return _G.a'))
+ eq(':source (no file)', meths.get_var('sfile_value'))
+ eq(':source (no file)', meths.get_var('stack_value'))
+ eq(':source (no file)', meths.get_var('script_value'))
+ end)
+ end
- os.remove(test_file)
+ describe('with ft=lua', function()
+ before_each(function()
+ command('setlocal ft=lua')
+ end)
+ test_source_lua_curbuf()
+ end)
+
+ describe('with .lua extension', function()
+ before_each(function()
+ command('edit ' .. tmpname() .. '.lua')
+ end)
+ test_source_lua_curbuf()
+ end)
end)
it("doesn't throw E484 for lua parsing/runtime errors", function()
diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
index 8eed00c973..436873b464 100644
--- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
+++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua
@@ -1,6 +1,5 @@
local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
local luv = require('luv')
local eq, eval, expect, exec =
helpers.eq, helpers.eval, helpers.expect, helpers.exec
@@ -13,13 +12,19 @@ local nvim_prog = helpers.nvim_prog
local ok = helpers.ok
local rmdir = helpers.rmdir
local new_argv = helpers.new_argv
+local new_pipename = helpers.new_pipename
local pesc = helpers.pesc
local os_kill = helpers.os_kill
local set_session = helpers.set_session
local spawn = helpers.spawn
-local nvim_async = helpers.nvim_async
+local async_meths = helpers.async_meths
local expect_msg_seq = helpers.expect_msg_seq
local pcall_err = helpers.pcall_err
+local mkdir = helpers.mkdir
+local poke_eventloop = helpers.poke_eventloop
+local meths = helpers.meths
+local retry = helpers.retry
+local write_file = helpers.write_file
describe(':recover', function()
before_each(clear)
@@ -37,40 +42,40 @@ describe(':recover', function()
end)
-describe(':preserve', function()
- local swapdir = lfs.currentdir()..'/Xtest_recover_dir'
+describe("preserve and (R)ecover with custom 'directory'", function()
+ local swapdir = luv.cwd()..'/Xtest_recover_dir'
+ local testfile = 'Xtest_recover_file1'
+ -- Put swapdir at the start of the 'directory' list. #1836
+ -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
+ -- attempt to create a swapfile in different directory.
+ local init = [[
+ set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
+ set swapfile fileformat=unix undolevels=-1
+ ]]
+
+ local nvim0
before_each(function()
- clear()
+ nvim0 = spawn(new_argv())
+ set_session(nvim0)
rmdir(swapdir)
- lfs.mkdir(swapdir)
+ mkdir(swapdir)
end)
after_each(function()
command('%bwipeout!')
rmdir(swapdir)
end)
- it("saves to custom 'directory' and (R)ecovers #1836", function()
- local testfile = 'Xtest_recover_file1'
- -- Put swapdir at the start of the 'directory' list. #1836
- -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
- -- attempt to create a swapfile in different directory.
- local init = [[
- set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//
- set swapfile fileformat=unix undolevels=-1
- ]]
-
+ local function setup_swapname()
exec(init)
command('edit! '..testfile)
feed('isometext<esc>')
- command('preserve')
exec('redir => g:swapname | silent swapname | redir END')
+ return eval('g:swapname')
+ end
- local swappath1 = eval('g:swapname')
-
- os_kill(eval('getpid()'))
+ local function test_recover(swappath1)
-- Start another Nvim instance.
- local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'},
- true)
+ local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true)
set_session(nvim2)
exec(init)
@@ -90,12 +95,41 @@ describe(':preserve', function()
-- Verify that :swapname was not truncated (:help 'shortmess').
ok(nil == string.find(swappath1, '%.%.%.'))
ok(nil == string.find(swappath2, '%.%.%.'))
+ end
+
+ it('with :preserve and SIGKILL', function()
+ local swappath1 = setup_swapname()
+ command('preserve')
+ os_kill(eval('getpid()'))
+ test_recover(swappath1)
+ end)
+
+ it('closing stdio channel without :preserve #22096', function()
+ local swappath1 = setup_swapname()
+ nvim0:close()
+ test_recover(swappath1)
+ end)
+
+ it('killing TUI process without :preserve #22096', function()
+ helpers.skip(helpers.is_os('win'))
+ local screen0 = Screen.new()
+ screen0:attach()
+ local child_server = new_pipename()
+ funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server})
+ screen0:expect({any = pesc('[No Name]')}) -- Wait for the child process to start.
+ local child_session = helpers.connect(child_server)
+ set_session(child_session)
+ local swappath1 = setup_swapname()
+ set_session(nvim0)
+ command('call chanclose(&channel)') -- Kill the child process.
+ screen0:expect({any = pesc('[Process exited 1]')}) -- Wait for the child process to stop.
+ test_recover(swappath1)
end)
end)
describe('swapfile detection', function()
- local swapdir = lfs.currentdir()..'/Xtest_swapdialog_dir'
+ local swapdir = luv.cwd()..'/Xtest_swapdialog_dir'
local nvim0
-- Put swapdir at the start of the 'directory' list. #1836
-- Note: `set swapfile` *must* go after `set directory`: otherwise it may
@@ -108,7 +142,7 @@ describe('swapfile detection', function()
nvim0 = spawn(new_argv())
set_session(nvim0)
rmdir(swapdir)
- lfs.mkdir(swapdir)
+ mkdir(swapdir)
end)
after_each(function()
set_session(nvim0)
@@ -137,6 +171,7 @@ describe('swapfile detection', function()
local screen2 = Screen.new(256, 40)
screen2:attach()
exec(init)
+ command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
-- With shortmess+=F
command('set shortmess+=F')
@@ -160,7 +195,7 @@ describe('swapfile detection', function()
feed('e') -- Chose "Edit" at the swap dialog.
screen2:expect(expected_no_dialog)
- -- With API (via eval/VimL) call and shortmess+=F
+ -- With API (via eval/Vimscript) call and shortmess+=F
feed(':call nvim_command("edit %")<CR>')
screen2:expect{any=[[Found a swap file by the name ".*]]
..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
@@ -169,7 +204,7 @@ describe('swapfile detection', function()
screen2:expect(expected_no_dialog)
-- With API call and shortmess+=F
- nvim_async('command', 'edit %')
+ async_meths.command('edit %')
screen2:expect{any=[[Found a swap file by the name ".*]]
..[[Xtest_swapdialog_dir[/\].*]]..testfile..[[%.swp"]]}
feed('e') -- Chose "Edit" at the swap dialog.
@@ -185,11 +220,29 @@ describe('swapfile detection', function()
nvim2:close()
end)
+ it('default SwapExists handler selects "(E)dit" and skips prompt', function()
+ exec(init)
+ command('edit Xfile1')
+ command("put ='some text...'")
+ command('preserve') -- Make sure the swap file exists.
+ local nvimpid = funcs.getpid()
+
+ local nvim1 = spawn(new_argv(), true, nil, true)
+ set_session(nvim1)
+ local screen = Screen.new(75, 18)
+ screen:attach()
+ exec(init)
+ feed(':edit Xfile1\n')
+
+ screen:expect({ any = ('W325: Ignoring swapfile from Nvim process %d'):format(nvimpid) })
+ nvim1:close()
+ end)
+
-- oldtest: Test_swap_prompt_splitwin()
it('selecting "q" in the attention prompt', function()
exec(init)
command('edit Xfile1')
- command('preserve') -- should help to make sure the swap file exists
+ command('preserve') -- Make sure the swap file exists.
local screen = Screen.new(75, 18)
screen:set_default_attr_ids({
@@ -201,7 +254,9 @@ describe('swapfile detection', function()
set_session(nvim1)
screen:attach()
exec(init)
+ command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
feed(':split Xfile1\n')
+ -- The default SwapExists handler does _not_ skip this prompt.
screen:expect({
any = pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^')
})
@@ -233,6 +288,7 @@ describe('swapfile detection', function()
set_session(nvim2)
screen:attach()
exec(init)
+ command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
command('set more')
command('au bufadd * let foo_w = wincol()')
feed(':e Xfile1<CR>')
@@ -266,8 +322,9 @@ describe('swapfile detection', function()
nvim2:close()
end)
- -- oldtest: Test_nocatch_process_still_running()
- it('allows deleting swapfile created before boot vim-patch:8.2.2586', function()
+ --- @param swapexists boolean Enable the default SwapExists handler.
+ --- @param on_swapfile_running fun(screen: any) Called after swapfile ("STILL RUNNING") prompt.
+ local function test_swapfile_after_reboot(swapexists, on_swapfile_running)
local screen = Screen.new(75, 30)
screen:set_default_attr_ids({
[0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
@@ -277,6 +334,9 @@ describe('swapfile detection', function()
screen:attach()
exec(init)
+ if not swapexists then
+ command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog).
+ end
command('set nohidden')
exec([=[
@@ -313,12 +373,7 @@ describe('swapfile detection', function()
os.rename('Xswap', swname)
feed(':edit Xswaptest<CR>')
- screen:expect({any = table.concat({
- pesc('{2:E325: ATTENTION}'),
- 'file name: .*Xswaptest',
- 'process ID: %d* %(STILL RUNNING%)',
- pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'),
- }, '.*')})
+ on_swapfile_running(screen)
feed('e')
@@ -330,7 +385,8 @@ describe('swapfile detection', function()
]])
-- pretend that the swapfile was created before boot
- lfs.touch(swname, os.time() - luv.uptime() - 10)
+ local atime = os.time() - luv.uptime() - 10
+ luv.fs_utime(swname, atime, atime)
feed(':edit Xswaptest<CR>')
screen:expect({any = table.concat({
@@ -339,5 +395,112 @@ describe('swapfile detection', function()
}, '.*')})
feed('e')
+ end
+
+ -- oldtest: Test_nocatch_process_still_running()
+ it('swapfile created before boot vim-patch:8.2.2586', function()
+ test_swapfile_after_reboot(false, function(screen)
+ screen:expect({any = table.concat({
+ pesc('{2:E325: ATTENTION}'),
+ 'file name: .*Xswaptest',
+ 'process ID: %d* %(STILL RUNNING%)',
+ pesc('{1:[O]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: }^'),
+ }, '.*')})
+ end)
+ end)
+
+ it('swapfile created before boot + default SwapExists handler', function()
+ test_swapfile_after_reboot(true, function(screen)
+ screen:expect({ any = 'W325: Ignoring swapfile from Nvim process' })
+ end)
+ end)
+
+end)
+
+describe('quitting swapfile dialog on startup stops TUI properly', function()
+ local swapdir = luv.cwd()..'/Xtest_swapquit_dir'
+ local testfile = 'Xtest_swapquit_file1'
+ local otherfile = 'Xtest_swapquit_file2'
+ -- Put swapdir at the start of the 'directory' list. #1836
+ -- Note: `set swapfile` *must* go after `set directory`: otherwise it may
+ -- attempt to create a swapfile in different directory.
+ local init_dir = [[set directory^=]]..swapdir:gsub([[\]], [[\\]])..[[//]]
+ local init_set = [[set swapfile fileformat=unix nomodified undolevels=-1 nohidden]]
+
+ before_each(function()
+ clear({args = {'--cmd', init_dir, '--cmd', init_set}})
+ rmdir(swapdir)
+ mkdir(swapdir)
+ write_file(testfile, [[
+ first
+ second
+ third
+
+ ]])
+ command('edit! '..testfile)
+ feed('Gisometext<esc>')
+ poke_eventloop()
+ clear() -- Leaves a swap file behind
+ meths.ui_attach(80, 30, {})
+ end)
+ after_each(function()
+ rmdir(swapdir)
+ os.remove(testfile)
+ os.remove(otherfile)
+ end)
+
+ it('(Q)uit at first file argument', function()
+ local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE',
+ '--cmd', init_dir, '--cmd', init_set,
+ testfile})
+ retry(nil, nil, function()
+ eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:',
+ eval("getline('$')->trim(' ', 2)"))
+ end)
+ meths.chan_send(chan, 'q')
+ retry(nil, nil, function()
+ eq({'', '[Process exited 1]', ''},
+ eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})"))
+ end)
+ end)
+
+ it('(A)bort at second file argument with -p', function()
+ local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE',
+ '--cmd', init_dir, '--cmd', init_set,
+ '-p', otherfile, testfile})
+ retry(nil, nil, function()
+ eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:',
+ eval("getline('$')->trim(' ', 2)"))
+ end)
+ meths.chan_send(chan, 'a')
+ retry(nil, nil, function()
+ eq({'', '[Process exited 1]', ''},
+ eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})"))
+ end)
+ end)
+
+ it('(Q)uit at file opened by -t', function()
+ write_file(otherfile, ([[
+ !_TAG_FILE_ENCODING utf-8 //
+ first %s /^ \zsfirst$/
+ second %s /^ \zssecond$/
+ third %s /^ \zsthird$/]]):format(testfile, testfile, testfile))
+ local chan = funcs.termopen({nvim_prog, '-u', 'NONE', '-i', 'NONE',
+ '--cmd', init_dir, '--cmd', init_set,
+ '--cmd', 'set tags='..otherfile, '-tsecond'})
+ retry(nil, nil, function()
+ eq('[O]pen Read-Only, (E)dit anyway, (R)ecover, (D)elete it, (Q)uit, (A)bort:',
+ eval("getline('$')->trim(' ', 2)"))
+ end)
+ meths.chan_send(chan, 'q')
+ retry(nil, nil, function()
+ eq('Press ENTER or type command to continue',
+ eval("getline('$')->trim(' ', 2)"))
+ end)
+ meths.chan_send(chan, '\r')
+ retry(nil, nil, function()
+ eq({'', '[Process exited 1]', ''},
+ eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})"))
+ end)
end)
end)
diff --git a/test/functional/ex_cmds/trust_spec.lua b/test/functional/ex_cmds/trust_spec.lua
index 10ee02a790..fe13bd7cd2 100644
--- a/test/functional/ex_cmds/trust_spec.lua
+++ b/test/functional/ex_cmds/trust_spec.lua
@@ -1,9 +1,10 @@
local helpers = require('test.functional.helpers')(after_each)
-local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
local clear = helpers.clear
local command = helpers.command
+local exec_capture = helpers.exec_capture
+local matches = helpers.matches
local pathsep = helpers.get_pathsep()
local is_os = helpers.is_os
local funcs = helpers.funcs
@@ -29,147 +30,49 @@ describe(':trust', function()
end)
it('trust then deny then remove a file using current buffer', function()
- local screen = Screen.new(80, 8)
- screen:attach()
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- })
-
local cwd = funcs.getcwd()
local hash = funcs.sha256(helpers.read_file('test_file'))
command('edit test_file')
- command('trust')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}|
- ]])
+ matches('^Allowed ".*test_file" in trust database%.$', exec_capture('trust'))
local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust))
- command('trust ++deny')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
- ]])
+ matches('^Denied ".*test_file" in trust database%.$', exec_capture('trust ++deny'))
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
- command('trust ++remove')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
- ]])
+ matches('^Removed ".*test_file" from trust database%.$', exec_capture('trust ++remove'))
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format(''), vim.trim(trust))
end)
it('deny then trust then remove a file using current buffer', function()
- local screen = Screen.new(80, 8)
- screen:attach()
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- })
-
local cwd = funcs.getcwd()
local hash = funcs.sha256(helpers.read_file('test_file'))
command('edit test_file')
- command('trust ++deny')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
- ]])
+ matches('^Denied ".*test_file" in trust database%.$', exec_capture('trust ++deny'))
local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
- command('trust')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" trusted.{MATCH:%s+}|
- ]])
+ matches('^Allowed ".*test_file" in trust database%.$', exec_capture('trust'))
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format('%s %s', hash, cwd .. pathsep .. 'test_file'), vim.trim(trust))
- command('trust ++remove')
- screen:expect([[
- ^test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
- ]])
+ matches('^Removed ".*test_file" from trust database%.$', exec_capture('trust ++remove'))
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format(''), vim.trim(trust))
end)
it('deny then remove a file using file path', function()
- local screen = Screen.new(80, 8)
- screen:attach()
- screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
- })
-
local cwd = funcs.getcwd()
- command('trust ++deny test_file')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" denied.{MATCH:%s+}|
- ]])
+ matches('^Denied ".*test_file" in trust database%.$', exec_capture('trust ++deny test_file'))
local trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format('! %s', cwd .. pathsep .. 'test_file'), vim.trim(trust))
- command('trust ++remove test_file')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- "]] .. cwd .. pathsep .. [[test_file" removed.{MATCH:%s+}|
- ]])
+ matches('^Removed ".*test_file" from trust database%.$', exec_capture('trust ++remove test_file'))
trust = helpers.read_file(funcs.stdpath('state') .. pathsep .. 'trust')
eq(string.format(''), vim.trim(trust))
end)
diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua
index 000e746f1c..50077e9e0c 100644
--- a/test/functional/ex_cmds/verbose_spec.lua
+++ b/test/functional/ex_cmds/verbose_spec.lua
@@ -7,7 +7,7 @@ local exec_capture = helpers.exec_capture
local write_file = helpers.write_file
local call_viml_function = helpers.meths.call_function
-describe('lua :verbose', function()
+local function last_set_tests(cmd)
local script_location, script_file
-- All test cases below use the same nvim instance.
setup(function()
@@ -18,17 +18,17 @@ describe('lua :verbose', function()
script_location = table.concat{current_dir, helpers.get_pathsep(), script_file}
write_file(script_file, [[
-vim.api.nvim_set_option('hlsearch', false)
+vim.api.nvim_set_option_value('hlsearch', false, {})
vim.bo.expandtab = true
vim.opt.number = true
vim.api.nvim_set_keymap('n', '<leader>key1', ':echo "test"<cr>', {noremap = true})
vim.keymap.set('n', '<leader>key2', ':echo "test"<cr>')
-vim.api.nvim_exec("augroup test_group\
+vim.api.nvim_exec2("augroup test_group\
autocmd!\
autocmd FileType c setl cindent\
augroup END\
- ", false)
+ ", {})
vim.api.nvim_command("command Bdelete :bd")
vim.api.nvim_create_user_command("TestCommand", ":echo 'Hello'", {})
@@ -46,7 +46,7 @@ endfunction\
let &tw = s:return80()\
", true)
]])
- exec(':source '..script_file)
+ exec(cmd .. ' ' .. script_file)
end)
teardown(function()
@@ -106,6 +106,9 @@ test_group FileType
end)
it('"Last set" for command defined by nvim_command', function()
+ if cmd == 'luafile' then
+ pending('nvim_command does not set the script context')
+ end
local result = exec_capture(':verbose command Bdelete')
eq(string.format([[
Name Args Address Complete Definition
@@ -123,7 +126,7 @@ test_group FileType
script_location), result)
end)
- it('"Last set for function', function()
+ it('"Last set" for function', function()
local result = exec_capture(':verbose function Close_Window')
eq(string.format([[
function Close_Window() abort
@@ -140,6 +143,14 @@ test_group FileType
Last set from %s line 22]],
script_location), result)
end)
+end
+
+describe('lua :verbose when using :source', function()
+ last_set_tests('source')
+end)
+
+describe('lua :verbose when using :luafile', function()
+ last_set_tests('luafile')
end)
describe('lua verbose:', function()
@@ -149,7 +160,7 @@ describe('lua verbose:', function()
clear()
script_file = 'test_luafile.lua'
write_file(script_file, [[
- vim.api.nvim_set_option('hlsearch', false)
+ vim.api.nvim_set_option_value('hlsearch', false, {})
]])
exec(':source '..script_file)
end)
diff --git a/test/functional/ex_cmds/write_spec.lua b/test/functional/ex_cmds/write_spec.lua
index 1ccd27875e..0b8ce93b09 100644
--- a/test/functional/ex_cmds/write_spec.lua
+++ b/test/functional/ex_cmds/write_spec.lua
@@ -1,5 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local eq, eval, clear, write_file, source, insert =
helpers.eq, helpers.eval, helpers.clear, helpers.write_file,
helpers.source, helpers.insert
@@ -129,18 +129,18 @@ describe(':write', function()
eq(('Vim(write):E17: "'..funcs.fnamemodify('.', ':p:h')..'" is a directory'),
pcall_err(command, 'write .'))
end
- meths.set_option('writeany', true)
+ meths.set_option_value('writeany', true, {})
-- Message from buf_write
eq(('Vim(write):E502: "." is a directory'), pcall_err(command, 'write .'))
funcs.mkdir(fname_bak)
- meths.set_option('backupdir', '.')
- meths.set_option('backup', true)
+ meths.set_option_value('backupdir', '.', {})
+ meths.set_option_value('backup', true, {})
write_file(fname, 'content0')
command('edit ' .. fname)
funcs.setline(1, 'TTY')
eq('Vim(write):E510: Can\'t make backup file (add ! to override)',
pcall_err(command, 'write'))
- meths.set_option('backup', false)
+ meths.set_option_value('backup', false, {})
funcs.setfperm(fname, 'r--------')
eq('Vim(write):E505: "Xtest-functional-ex_cmds-write" is read-only (add ! to override)',
pcall_err(command, 'write'))
@@ -153,7 +153,7 @@ describe(':write', function()
end
write_file(fname_bak, 'TTYX')
skip(is_os('win'), [[FIXME: exc_exec('write!') outputs 0 in Windows]])
- lfs.link(fname_bak .. ('/xxxxx'):rep(20), fname, true)
+ luv.fs_symlink(fname_bak .. ('/xxxxx'):rep(20), fname)
eq('Vim(write):E166: Can\'t open linked file for writing',
pcall_err(command, 'write!'))
end)
diff --git a/test/functional/ex_cmds/wviminfo_spec.lua b/test/functional/ex_cmds/wviminfo_spec.lua
index 861a977ea6..7525343891 100644
--- a/test/functional/ex_cmds/wviminfo_spec.lua
+++ b/test/functional/ex_cmds/wviminfo_spec.lua
@@ -1,5 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local clear = helpers.clear
local command, eq, neq, write_file =
helpers.command, helpers.eq, helpers.neq, helpers.write_file
@@ -21,10 +21,10 @@ describe(':wshada', function()
it('creates a shada file', function()
-- file should _not_ exist
- eq(nil, lfs.attributes(shada_file))
+ eq(nil, luv.fs_stat(shada_file))
command('wsh! '..shada_file)
-- file _should_ exist
- neq(nil, lfs.attributes(shada_file))
+ neq(nil, luv.fs_stat(shada_file))
end)
it('overwrites existing files', function()
@@ -35,7 +35,7 @@ describe(':wshada', function()
-- sanity check
eq(text, read_file(shada_file))
- neq(nil, lfs.attributes(shada_file))
+ neq(nil, luv.fs_stat(shada_file))
command('wsh! '..shada_file)
diff --git a/test/functional/fixtures/api_level_11.mpack b/test/functional/fixtures/api_level_11.mpack
new file mode 100644
index 0000000000..2399854c42
--- /dev/null
+++ b/test/functional/fixtures/api_level_11.mpack
Binary files differ
diff --git a/test/functional/fixtures/autoload/health/broken.vim b/test/functional/fixtures/autoload/health/broken.vim
deleted file mode 100644
index a2a595b96f..0000000000
--- a/test/functional/fixtures/autoload/health/broken.vim
+++ /dev/null
@@ -1,3 +0,0 @@
-function! health#broken#check()
- throw 'caused an error'
-endfunction
diff --git a/test/functional/fixtures/autoload/health/full_render.vim b/test/functional/fixtures/autoload/health/full_render.vim
deleted file mode 100644
index 2064b8606e..0000000000
--- a/test/functional/fixtures/autoload/health/full_render.vim
+++ /dev/null
@@ -1,8 +0,0 @@
-function! health#full_render#check()
- call health#report_start("report 1")
- call health#report_ok("life is fine")
- call health#report_warn("no what installed", ["pip what", "make what"])
- call health#report_start("report 2")
- call health#report_info("stuff is stable")
- call health#report_error("why no hardcopy", [":h :hardcopy", ":h :TOhtml"])
-endfunction
diff --git a/test/functional/fixtures/autoload/health/success1.vim b/test/functional/fixtures/autoload/health/success1.vim
deleted file mode 100644
index a360347455..0000000000
--- a/test/functional/fixtures/autoload/health/success1.vim
+++ /dev/null
@@ -1,6 +0,0 @@
-function! health#success1#check()
- call health#report_start("report 1")
- call health#report_ok("everything is fine")
- call health#report_start("report 2")
- call health#report_ok("nothing to see here")
-endfunction
diff --git a/test/functional/fixtures/autoload/health/success2.vim b/test/functional/fixtures/autoload/health/success2.vim
deleted file mode 100644
index b742b4879d..0000000000
--- a/test/functional/fixtures/autoload/health/success2.vim
+++ /dev/null
@@ -1,4 +0,0 @@
-function! health#success2#check()
- call health#report_start("another 1")
- call health#report_ok("ok")
-endfunction
diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua
index db0c8c0c3f..0db9265a29 100644
--- a/test/functional/fixtures/fake-lsp-server.lua
+++ b/test/functional/fixtures/fake-lsp-server.lua
@@ -272,6 +272,7 @@ function tests.text_document_save_did_open()
end;
body = function()
notify('start')
+ expect_notification('textDocument/didClose')
expect_notification('textDocument/didOpen')
expect_notification('textDocument/didSave')
notify('shutdown')
@@ -787,6 +788,9 @@ function tests.code_action_server_side_command()
codeActionProvider = {
resolveProvider = false,
},
+ executeCommandProvider = {
+ commands = {"dummy1"}
+ },
},
}
end,
@@ -830,21 +834,21 @@ function tests.code_action_filter()
isPreferred = true,
command = 'preferred_command',
}
- local quickfix_action = {
+ local type_annotate_action = {
title = 'Action 3',
- kind = 'quickfix',
- command = 'quickfix_command',
+ kind = 'type-annotate',
+ command = 'type_annotate_command',
}
- local quickfix_foo_action = {
+ local type_annotate_foo_action = {
title = 'Action 4',
- kind = 'quickfix.foo',
- command = 'quickfix_foo_command',
+ kind = 'type-annotate.foo',
+ command = 'type_annotate_foo_command',
}
expect_request('textDocument/codeAction', function()
- return nil, { action, preferred_action, quickfix_action, quickfix_foo_action, }
+ return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action, }
end)
expect_request('textDocument/codeAction', function()
- return nil, { action, preferred_action, quickfix_action, quickfix_foo_action, }
+ return nil, { action, preferred_action, type_annotate_action, type_annotate_foo_action, }
end)
notify('shutdown')
end;
@@ -927,12 +931,52 @@ function tests.basic_formatting()
}
end
+function tests.set_defaults_all_capabilities()
+ skeleton {
+ on_init = function(_)
+ return {
+ capabilities = {
+ definitionProvider = true,
+ completionProvider = true,
+ documentRangeFormattingProvider = true,
+ hoverProvider = true,
+ }
+ }
+ end;
+ body = function()
+ notify('test')
+ end;
+ }
+end
+
+function tests.inlay_hint()
+ skeleton {
+ on_init = function(params)
+ local expected_capabilities = protocol.make_client_capabilities()
+ assert_eq(params.capabilities, expected_capabilities)
+ return {
+ capabilities = {
+ inlayHintProvider = true;
+ }
+ }
+ end;
+ body = function()
+ notify('start')
+ expect_request('textDocument/inlayHint', function()
+ return nil, {}
+ end)
+ expect_notification("finish")
+ notify('finish')
+ end;
+ }
+end
+
-- Tests will be indexed by test_name
local test_name = arg[1]
local timeout = arg[2]
assert(type(test_name) == 'string', 'test_name must be specified as first arg.')
-local kill_timer = vim.loop.new_timer()
+local kill_timer = vim.uv.new_timer()
kill_timer:start(timeout or 1e3, 0, function()
kill_timer:stop()
kill_timer:close()
diff --git a/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim b/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim
deleted file mode 100644
index de05f56e9e..0000000000
--- a/test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim
+++ /dev/null
@@ -1,3 +0,0 @@
-function! health#success1#check()
- call health#report_start("If you see this I'm broken")
-endfunction
diff --git a/test/functional/fixtures/lua/test_plug/full_render/health.lua b/test/functional/fixtures/lua/test_plug/full_render/health.lua
new file mode 100644
index 0000000000..10833f6239
--- /dev/null
+++ b/test/functional/fixtures/lua/test_plug/full_render/health.lua
@@ -0,0 +1,12 @@
+local M = {}
+
+M.check = function()
+ vim.health.start('report 1')
+ vim.health.ok('life is fine')
+ vim.health.warn('no what installed', { 'pip what', 'make what' })
+ vim.health.start('report 2')
+ vim.health.info('stuff is stable')
+ vim.health.error('why no hardcopy', { ':h :hardcopy', ':h :TOhtml' })
+end
+
+return M
diff --git a/test/functional/fixtures/lua/test_plug/health/init.lua b/test/functional/fixtures/lua/test_plug/health/init.lua
index 58162d4515..ef0c5ae3cd 100644
--- a/test/functional/fixtures/lua/test_plug/health/init.lua
+++ b/test/functional/fixtures/lua/test_plug/health/init.lua
@@ -1,10 +1,10 @@
local M = {}
M.check = function()
- vim.health.report_start("report 1")
- vim.health.report_ok("everything is fine")
- vim.health.report_start("report 2")
- vim.health.report_ok("nothing to see here")
+ vim.health.start('report 1')
+ vim.health.ok('everything is fine')
+ vim.health.start('report 2')
+ vim.health.ok('nothing to see here')
end
return M
diff --git a/test/functional/fixtures/lua/test_plug/submodule/health.lua b/test/functional/fixtures/lua/test_plug/submodule/health.lua
index 58162d4515..ef0c5ae3cd 100644
--- a/test/functional/fixtures/lua/test_plug/submodule/health.lua
+++ b/test/functional/fixtures/lua/test_plug/submodule/health.lua
@@ -1,10 +1,10 @@
local M = {}
M.check = function()
- vim.health.report_start("report 1")
- vim.health.report_ok("everything is fine")
- vim.health.report_start("report 2")
- vim.health.report_ok("nothing to see here")
+ vim.health.start('report 1')
+ vim.health.ok('everything is fine')
+ vim.health.start('report 2')
+ vim.health.ok('nothing to see here')
end
return M
diff --git a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua
deleted file mode 100644
index ee5f4e404b..0000000000
--- a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua
+++ /dev/null
@@ -1,11 +0,0 @@
-local M = {}
-
-M.check = function()
- vim.health.report_start("report 1")
- vim.health.report_ok("everything is fine")
- vim.health.report_warn("About to add a number to nil")
- local a = nil + 2
- return a
-end
-
-return M
diff --git a/test/functional/fixtures/lua/test_plug/success1/health.lua b/test/functional/fixtures/lua/test_plug/success1/health.lua
new file mode 100644
index 0000000000..ef0c5ae3cd
--- /dev/null
+++ b/test/functional/fixtures/lua/test_plug/success1/health.lua
@@ -0,0 +1,10 @@
+local M = {}
+
+M.check = function()
+ vim.health.start('report 1')
+ vim.health.ok('everything is fine')
+ vim.health.start('report 2')
+ vim.health.ok('nothing to see here')
+end
+
+return M
diff --git a/test/functional/fixtures/lua/test_plug/success2/health.lua b/test/functional/fixtures/lua/test_plug/success2/health.lua
new file mode 100644
index 0000000000..240a2d33de
--- /dev/null
+++ b/test/functional/fixtures/lua/test_plug/success2/health.lua
@@ -0,0 +1,8 @@
+local M = {}
+
+M.check = function()
+ vim.health.start('another 1')
+ vim.health.ok('ok')
+end
+
+return M
diff --git a/test/functional/fixtures/printargs-test.c b/test/functional/fixtures/printargs-test.c
index be54605817..2c25cf8447 100644
--- a/test/functional/fixtures/printargs-test.c
+++ b/test/functional/fixtures/printargs-test.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdio.h>
int main(int argc, char **argv)
diff --git a/test/functional/fixtures/printenv-test.c b/test/functional/fixtures/printenv-test.c
index 295b4f04c3..f826a28db4 100644
--- a/test/functional/fixtures/printenv-test.c
+++ b/test/functional/fixtures/printenv-test.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdio.h>
#ifdef MSWIN
diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c
index 4196716799..ef9f23e3f9 100644
--- a/test/functional/fixtures/shell-test.c
+++ b/test/functional/fixtures/shell-test.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdio.h>
#include <string.h>
#include <stdint.h>
diff --git a/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua b/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua
index 45226ce24b..ffbd8a4f83 100644
--- a/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua
+++ b/test/functional/fixtures/start/nvim-leftpad/lua/async_leftpad.lua
@@ -1,5 +1,5 @@
return function (val, res)
local handle
- handle = vim.loop.new_async(function() _G[res] = require'leftpad'(val) handle:close() end)
+ handle = vim.uv.new_async(function() _G[res] = require'leftpad'(val) handle:close() end)
handle:send()
end
diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c
index be40edfe7e..5a59abb33b 100644
--- a/test/functional/fixtures/streams-test.c
+++ b/test/functional/fixtures/streams-test.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
/// Helper program to exit and keep stdout open (like "xclip -i -loops 1").
#include <stdio.h>
diff --git a/test/functional/fixtures/tty-test.c b/test/functional/fixtures/tty-test.c
index 6ee7715021..f94af2fd12 100644
--- a/test/functional/fixtures/tty-test.c
+++ b/test/functional/fixtures/tty-test.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -11,8 +8,6 @@
# include <unistd.h>
#endif
-// -V:STRUCT_CAST:641
-#define STRUCT_CAST(Type, obj) ((Type *)(obj))
#define is_terminal(stream) (uv_guess_handle(fileno(stream)) == UV_TTY)
#define BUF_SIZE 0xfff
#define CTRL_C 0x03
@@ -108,10 +103,10 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
.len = (size_t)cnt
#endif
};
- uv_write(&req, STRUCT_CAST(uv_stream_t, &out), &b, 1, NULL);
+ uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL);
uv_run(&write_loop, UV_RUN_DEFAULT);
- uv_close(STRUCT_CAST(uv_handle_t, &out), NULL);
+ uv_close((uv_handle_t *)&out, NULL);
uv_run(&write_loop, UV_RUN_DEFAULT);
if (uv_loop_close(&write_loop)) {
abort();
@@ -181,7 +176,7 @@ int main(int argc, char **argv)
#endif
uv_tty_set_mode(&tty, UV_TTY_MODE_RAW);
tty.data = &interrupted;
- uv_read_start(STRUCT_CAST(uv_stream_t, &tty), alloc_cb, read_cb);
+ uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb);
#ifndef MSWIN
struct sigaction sa;
sigemptyset(&sa.sa_mask);
diff --git a/test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC b/test/functional/fixtures/wildpum/Xnamedir/XdirA/XdirB/XfileC
index e69de29bb2..e69de29bb2 100644
--- a/test/functional/fixtures/wildpum/Xdir/XdirA/XdirB/XfileC
+++ b/test/functional/fixtures/wildpum/Xnamedir/XdirA/XdirB/XfileC
diff --git a/test/functional/fixtures/wildpum/Xdir/XdirA/XfileB b/test/functional/fixtures/wildpum/Xnamedir/XdirA/XfileB
index e69de29bb2..e69de29bb2 100644
--- a/test/functional/fixtures/wildpum/Xdir/XdirA/XfileB
+++ b/test/functional/fixtures/wildpum/Xnamedir/XdirA/XfileB
diff --git a/test/functional/fixtures/wildpum/Xdir/XfileA b/test/functional/fixtures/wildpum/Xnamedir/XfileA
index e69de29bb2..e69de29bb2 100644
--- a/test/functional/fixtures/wildpum/Xdir/XfileA
+++ b/test/functional/fixtures/wildpum/Xnamedir/XfileA
diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua
index 6400db9f87..dcaaa664b9 100644
--- a/test/functional/helpers.lua
+++ b/test/functional/helpers.lua
@@ -1,14 +1,10 @@
-require('coxpcall')
local luv = require('luv')
-local lfs = require('lfs')
-local mpack = require('mpack')
local global_helpers = require('test.helpers')
--- nvim client: Found in .deps/usr/share/lua/<version>/nvim/ if "bundled".
-local Session = require('nvim.session')
-local TcpStream = require('nvim.tcp_stream')
-local SocketStream = require('nvim.socket_stream')
-local ChildProcessStream = require('nvim.child_process_stream')
+local Session = require('test.client.session')
+local uv_stream = require('test.client.uv_stream')
+local SocketStream = uv_stream.SocketStream
+local ChildProcessStream = uv_stream.ChildProcessStream
local check_cores = global_helpers.check_cores
local check_logs = global_helpers.check_logs
@@ -22,12 +18,9 @@ local sleep = global_helpers.sleep
local tbl_contains = global_helpers.tbl_contains
local fail = global_helpers.fail
-local module = {
- NIL = mpack.NIL,
- mkdir = lfs.mkdir,
-}
+local module = {}
-local start_dir = lfs.currentdir()
+local start_dir = luv.cwd()
local runtime_set = 'set runtimepath^=./build/lib/nvim/'
module.nvim_prog = (
os.getenv('NVIM_PRG')
@@ -89,6 +82,13 @@ end
local session, loop_running, last_error, method_error
+if not is_os('win') then
+ local sigpipe_handler = luv.new_signal()
+ luv.signal_start(sigpipe_handler, "sigpipe", function()
+ print("warning: got SIGPIPE signal. Likely related to a crash in nvim")
+ end)
+end
+
function module.get_session()
return session
end
@@ -202,7 +202,7 @@ function module.expect_msg_seq(...)
end
local function call_and_stop_on_error(lsession, ...)
- local status, result = copcall(...) -- luacheck: ignore
+ local status, result = Session.safe_pcall(...) -- luacheck: ignore
if not status then
lsession:stop()
last_error = result
@@ -271,7 +271,7 @@ function module.nvim_prog_abs()
end
end
--- Executes an ex-command. VimL errors manifest as client (lua) errors, but
+-- Executes an ex-command. Vimscript errors manifest as client (lua) errors, but
-- v:errmsg will not be updated.
function module.command(cmd)
module.request('nvim_command', cmd)
@@ -295,26 +295,26 @@ function module.expect_exit(fn_or_timeout, ...)
end
end
--- Evaluates a VimL expression.
--- Fails on VimL error, but does not update v:errmsg.
+-- Evaluates a Vimscript expression.
+-- Fails on Vimscript error, but does not update v:errmsg.
function module.eval(expr)
return module.request('nvim_eval', expr)
end
--- Executes a VimL function via RPC.
--- Fails on VimL error, but does not update v:errmsg.
+-- Executes a Vimscript function via RPC.
+-- Fails on Vimscript error, but does not update v:errmsg.
function module.call(name, ...)
return module.request('nvim_call_function', name, {...})
end
--- Executes a VimL function via Lua.
--- Fails on VimL error, but does not update v:errmsg.
+-- Executes a Vimscript function via Lua.
+-- Fails on Vimscript error, but does not update v:errmsg.
function module.call_lua(name, ...)
return module.exec_lua([[return vim.call(...)]], name, ...)
end
-- Sends user input to Nvim.
--- Does not fail on VimL error, but v:errmsg will be updated.
+-- Does not fail on Vimscript error, but v:errmsg will be updated.
local function nvim_feed(input)
while #input > 0 do
local written = module.request('nvim_input', input)
@@ -428,7 +428,7 @@ end
-- Creates a new Session connected by domain socket (named pipe) or TCP.
function module.connect(file_or_address)
local addr, port = string.match(file_or_address, "(.*):(%d+)")
- local stream = (addr and port) and TcpStream.open(addr, port) or
+ local stream = (addr and port) and SocketStream.connect(addr, port) or
SocketStream.open(file_or_address)
return Session.new(stream)
end
@@ -524,7 +524,7 @@ function module.insert(...)
nvim_feed('<ESC>')
end
--- Executes an ex-command by user input. Because nvim_input() is used, VimL
+-- Executes an ex-command by user input. Because nvim_input() is used, Vimscript
-- errors will not manifest as client (lua) errors. Use command() for that.
function module.feed_command(...)
for _, v in ipairs({...}) do
@@ -537,7 +537,7 @@ function module.feed_command(...)
end
end
--- @deprecated use nvim_exec()
+-- @deprecated use nvim_exec2()
function module.source(code)
module.exec(dedent(code))
end
@@ -557,16 +557,18 @@ function module.set_shell_powershell(fake)
assert(found)
end
local shell = found and (is_os('win') and 'powershell' or 'pwsh') or module.testprg('pwsh-test')
- local set_encoding = '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();'
- local cmd = set_encoding..'Remove-Item -Force '..table.concat(is_os('win')
- and {'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort'}
+ local cmd = 'Remove-Item -Force '..table.concat(is_os('win')
+ and {'alias:cat', 'alias:echo', 'alias:sleep', 'alias:sort', 'alias:tee'}
or {'alias:echo'}, ',')..';'
module.exec([[
let &shell = ']]..shell..[['
set shellquote= shellxquote=
- let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ]]..cmd..[['
- let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode'
- let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode'
+ let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command '
+ let &shellcmdflag .= '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();'
+ let &shellcmdflag .= '$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';'
+ let &shellcmdflag .= ']]..cmd..[['
+ let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
+ let &shellpipe = '2>&1 | %%{ "$_" } | tee %s; exit $LastExitCode'
]])
return found
end
@@ -732,21 +734,17 @@ function module.assert_visible(bufnr, visible)
end
local function do_rmdir(path)
- local mode, errmsg, errcode = lfs.attributes(path, 'mode')
- if mode == nil then
- if errcode == 2 then
- -- "No such file or directory", don't complain.
- return
- end
- error(string.format('rmdir: %s (%d)', errmsg, errcode))
+ local stat = luv.fs_stat(path)
+ if stat == nil then
+ return
end
- if mode ~= 'directory' then
+ if stat.type ~= 'directory' then
error(string.format('rmdir: not a directory: %s', path))
end
- for file in lfs.dir(path) do
+ for file in vim.fs.dir(path) do
if file ~= '.' and file ~= '..' then
local abspath = path..'/'..file
- if lfs.attributes(abspath, 'mode') == 'directory' then
+ if global_helpers.isdir(abspath) then
do_rmdir(abspath) -- recurse
else
local ret, err = os.remove(abspath)
@@ -766,9 +764,9 @@ local function do_rmdir(path)
end
end
end
- local ret, err = lfs.rmdir(path)
+ local ret, err = luv.fs_rmdir(path)
if not ret then
- error('lfs.rmdir('..path..'): '..err)
+ error('luv.fs_rmdir('..path..'): '..err)
end
end
@@ -828,13 +826,15 @@ function module.skip_fragile(pending_fn, cond)
end
function module.exec(code)
- return module.meths.exec(code, false)
+ module.meths.exec2(code, {})
end
function module.exec_capture(code)
- return module.meths.exec(code, true)
+ return module.meths.exec2(code, { output = true }).output
end
+--- @param code string
+--- @return any
function module.exec_lua(code, ...)
return module.meths.exec_lua(code, {...})
end
@@ -858,12 +858,20 @@ function module.testprg(name)
return ('%s/%s%s'):format(module.nvim_dir, name, ext)
end
+function module.is_asan()
+ local version = module.eval('execute("verbose version")')
+ return version:match('-fsanitize=[a-z,]*address')
+end
+
-- Returns a valid, platform-independent Nvim listen address.
-- Useful for communicating with child instances.
function module.new_pipename()
-- HACK: Start a server temporarily, get the name, then stop it.
local pipename = module.eval('serverstart()')
module.funcs.serverstop(pipename)
+ -- Remove the pipe so that trying to connect to it without a server listening
+ -- will be an error instead of a hang.
+ os.remove(pipename)
return pipename
end
@@ -902,7 +910,7 @@ local load_factor = 1
if global_helpers.is_ci() then
-- Compute load factor only once (but outside of any tests).
module.clear()
- module.request('nvim_command', 'source src/nvim/testdir/load.vim')
+ module.request('nvim_command', 'source test/old/testdir/load.vim')
load_factor = module.request('nvim_eval', 'g:test_load_factor')
end
function module.load_adjust(num)
@@ -944,8 +952,10 @@ function module.mkdir_p(path)
or 'mkdir -p '..path))
end
+--- @class test.functional.helpers: test.helpers
module = global_helpers.tbl_extend('error', module, global_helpers)
+--- @return test.functional.helpers
return function(after_each)
if after_each then
after_each(function()
diff --git a/test/functional/legacy/011_autocommands_spec.lua b/test/functional/legacy/011_autocommands_spec.lua
index 7ae851467f..5b6d030567 100644
--- a/test/functional/legacy/011_autocommands_spec.lua
+++ b/test/functional/legacy/011_autocommands_spec.lua
@@ -13,7 +13,7 @@
-- being modified outside of Vim (noticed on Solaris).
local helpers= require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local clear, feed_command, expect, eq, neq, dedent, write_file, feed =
helpers.clear, helpers.feed_command, helpers.expect, helpers.eq, helpers.neq,
helpers.dedent, helpers.write_file, helpers.feed
@@ -31,8 +31,8 @@ local function prepare_gz_file(name, text)
-- Compress the file with gzip.
command([[call system(['gzip', '--force', ']]..name..[['])]])
-- This should create the .gz file and delete the original.
- neq(nil, lfs.attributes(name..'.gz'))
- eq(nil, lfs.attributes(name))
+ neq(nil, luv.fs_stat(name..'.gz'))
+ eq(nil, luv.fs_stat(name))
end
describe('file reading, writing and bufnew and filter autocommands', function()
diff --git a/test/functional/legacy/012_directory_spec.lua b/test/functional/legacy/012_directory_spec.lua
index dd207ca0b4..25d0dcb81e 100644
--- a/test/functional/legacy/012_directory_spec.lua
+++ b/test/functional/legacy/012_directory_spec.lua
@@ -4,7 +4,7 @@
-- - "dir", in directory relative to current dir
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local eq = helpers.eq
local neq = helpers.neq
@@ -15,12 +15,12 @@ local clear = helpers.clear
local insert = helpers.insert
local command = helpers.command
local write_file = helpers.write_file
-local curbufmeths = helpers.curbufmeths
local expect_exit = helpers.expect_exit
+local mkdir = helpers.mkdir
local function ls_dir_sorted(dirname)
local files = {}
- for f in lfs.dir(dirname) do
+ for f in vim.fs.dir(dirname) do
if f ~= "." and f~= ".." then
table.insert(files, f)
end
@@ -38,8 +38,8 @@ describe("'directory' option", function()
end of testfile
]]
write_file('Xtest1', text)
- lfs.mkdir('Xtest.je')
- lfs.mkdir('Xtest2')
+ mkdir('Xtest.je')
+ mkdir('Xtest2')
write_file('Xtest2/Xtest3', text)
clear()
end)
@@ -57,33 +57,33 @@ describe("'directory' option", function()
line 3 Abcdefghij
end of testfile]])
- meths.set_option('swapfile', true)
- curbufmeths.set_option('swapfile', true)
- meths.set_option('directory', '.')
+ meths.set_option_value('swapfile', true, {})
+ meths.set_option_value('swapfile', true, {})
+ meths.set_option_value('directory', '.', {})
-- sanity check: files should not exist yet.
- eq(nil, lfs.attributes('.Xtest1.swp'))
+ eq(nil, luv.fs_stat('.Xtest1.swp'))
command('edit! Xtest1')
poke_eventloop()
eq('Xtest1', funcs.buffer_name('%'))
-- Verify that the swapfile exists. In the legacy test this was done by
-- reading the output from :!ls.
- neq(nil, lfs.attributes('.Xtest1.swp'))
+ neq(nil, luv.fs_stat('.Xtest1.swp'))
- meths.set_option('directory', './Xtest2,.')
+ meths.set_option_value('directory', './Xtest2,.', {})
command('edit Xtest1')
poke_eventloop()
-- swapfile should no longer exist in CWD.
- eq(nil, lfs.attributes('.Xtest1.swp'))
+ eq(nil, luv.fs_stat('.Xtest1.swp'))
eq({ "Xtest1.swp", "Xtest3" }, ls_dir_sorted("Xtest2"))
- meths.set_option('directory', 'Xtest.je')
+ meths.set_option_value('directory', 'Xtest.je', {})
command('bdelete')
command('edit Xtest2/Xtest3')
- eq(true, curbufmeths.get_option('swapfile'))
+ eq(true, meths.get_option_value('swapfile', {}))
poke_eventloop()
eq({ "Xtest3" }, ls_dir_sorted("Xtest2"))
diff --git a/test/functional/legacy/061_undo_tree_spec.lua b/test/functional/legacy/061_undo_tree_spec.lua
index 1a8ef067d0..b5af8f7d52 100644
--- a/test/functional/legacy/061_undo_tree_spec.lua
+++ b/test/functional/legacy/061_undo_tree_spec.lua
@@ -22,29 +22,30 @@ end
describe('undo tree:', function()
before_each(clear)
+ local fname = 'Xtest_functional_legacy_undotree'
teardown(function()
- os.remove('Xtest.source')
+ os.remove(fname .. '.source')
end)
describe(':earlier and :later', function()
before_each(function()
- os.remove('Xtest')
+ os.remove(fname)
end)
teardown(function()
- os.remove('Xtest')
+ os.remove(fname)
end)
it('time specifications, g- g+', function()
-- We write the test text to a file in order to prevent nvim to record
-- the inserting of the text into the undo history.
- write_file('Xtest', '\n123456789\n')
+ write_file(fname, '\n123456789\n')
-- `:earlier` and `:later` are (obviously) time-sensitive, so this test
-- sometimes fails if the system is under load. It is wrapped in a local
-- function to allow multiple attempts.
local function test_earlier_later()
clear()
- feed_command('e Xtest')
+ feed_command('e ' .. fname)
-- Assert that no undo history is present.
eq({}, eval('undotree().entries'))
-- Delete three characters and undo.
@@ -103,7 +104,7 @@ describe('undo tree:', function()
it('file-write specifications', function()
feed('ione one one<esc>')
- feed_command('w Xtest')
+ feed_command('w ' .. fname)
feed('otwo<esc>')
feed('otwo<esc>')
feed_command('w')
@@ -187,7 +188,7 @@ describe('undo tree:', function()
it('undo an expression-register', function()
local normal_commands = 'o1\027a2\018=string(123)\n\027'
- write_file('Xtest.source', normal_commands)
+ write_file(fname .. '.source', normal_commands)
feed('oa<esc>')
feed('ob<esc>')
@@ -221,7 +222,7 @@ describe('undo tree:', function()
c
12]])
feed('od<esc>')
- feed_command('so! Xtest.source')
+ feed_command('so! ' .. fname .. '.source')
expect([[
a
diff --git a/test/functional/legacy/074_global_var_in_viminfo_spec.lua b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
index 445d742c1f..06d8b276d7 100644
--- a/test/functional/legacy/074_global_var_in_viminfo_spec.lua
+++ b/test/functional/legacy/074_global_var_in_viminfo_spec.lua
@@ -1,7 +1,7 @@
-- Tests for storing global variables in the .shada file
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local clear, command, eq, neq, eval, poke_eventloop =
helpers.clear, helpers.command, helpers.eq, helpers.neq, helpers.eval,
helpers.poke_eventloop
@@ -39,7 +39,7 @@ describe('storing global variables in ShaDa files', function()
poke_eventloop()
-- Assert that the shada file exists.
- neq(nil, lfs.attributes(tempname))
+ neq(nil, luv.fs_stat(tempname))
command('unlet MY_GLOBAL_DICT')
command('unlet MY_GLOBAL_LIST')
-- Assert that the variables where deleted.
diff --git a/test/functional/legacy/088_conceal_tabs_spec.lua b/test/functional/legacy/088_conceal_tabs_spec.lua
deleted file mode 100644
index a4c7e26583..0000000000
--- a/test/functional/legacy/088_conceal_tabs_spec.lua
+++ /dev/null
@@ -1,97 +0,0 @@
--- Tests for correct display (cursor column position) with +conceal and
--- tabulators.
-
-local helpers = require('test.functional.helpers')(after_each)
-local feed, insert, clear, feed_command =
- helpers.feed, helpers.insert, helpers.clear, helpers.feed_command
-
-local expect_pos = function(row, col)
- return helpers.eq({row, col}, helpers.eval('[screenrow(), screencol()]'))
-end
-
-describe('cursor and column position with conceal and tabulators', function()
- setup(clear)
-
- -- luacheck: ignore 621 (Indentation)
- it('are working', function()
- insert([[
- start:
- .concealed. text
- |concealed| text
-
- .concealed. text
- |concealed| text
-
- .a. .b. .c. .d.
- |a| |b| |c| |d|]])
-
- -- Conceal settings.
- feed_command('set conceallevel=2')
- feed_command('set concealcursor=nc')
- feed_command('syntax match test /|/ conceal')
- -- Start test.
- feed_command('/^start:')
- feed('ztj')
- expect_pos(2, 1)
- -- We should end up in the same column when running these commands on the
- -- two lines.
- feed('ft')
- expect_pos(2, 17)
- feed('$')
- expect_pos(2, 20)
- feed('0j')
- expect_pos(3, 1)
- feed('ft')
- expect_pos(3, 17)
- feed('$')
- expect_pos(3, 20)
- feed('j0j')
- expect_pos(5, 8)
- -- Same for next test block.
- feed('ft')
- expect_pos(5, 25)
- feed('$')
- expect_pos(5, 28)
- feed('0j')
- expect_pos(6, 8)
- feed('ft')
- expect_pos(6, 25)
- feed('$')
- expect_pos(6, 28)
- feed('0j0j')
- expect_pos(8, 1)
- -- And check W with multiple tabs and conceals in a line.
- feed('W')
- expect_pos(8, 9)
- feed('W')
- expect_pos(8, 17)
- feed('W')
- expect_pos(8, 25)
- feed('$')
- expect_pos(8, 27)
- feed('0j')
- expect_pos(9, 1)
- feed('W')
- expect_pos(9, 9)
- feed('W')
- expect_pos(9, 17)
- feed('W')
- expect_pos(9, 25)
- feed('$')
- expect_pos(9, 26)
- feed_command('set lbr')
- feed('$')
- expect_pos(9, 26)
- feed_command('set list listchars=tab:>-')
- feed('0')
- expect_pos(9, 1)
- feed('W')
- expect_pos(9, 9)
- feed('W')
- expect_pos(9, 17)
- feed('W')
- expect_pos(9, 25)
- feed('$')
- expect_pos(9, 26)
- end)
-end)
diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua
index 4829a0bbe1..d49d7d665a 100644
--- a/test/functional/legacy/assert_spec.lua
+++ b/test/functional/legacy/assert_spec.lua
@@ -145,10 +145,10 @@ describe('assert function:', function()
call assert_true('', 'file two')
]])
expected_errors({
- "nvim_exec(): equal assertion failed: Expected 1 but got 100",
- "nvim_exec(): true assertion failed: Expected False but got 'true'",
- "nvim_exec(): false assertion failed: Expected True but got 'false'",
- "nvim_exec(): file two: Expected True but got ''",
+ "nvim_exec2(): equal assertion failed: Expected 1 but got 100",
+ "nvim_exec2(): true assertion failed: Expected False but got 'true'",
+ "nvim_exec2(): false assertion failed: Expected True but got 'false'",
+ "nvim_exec2(): file two: Expected True but got ''",
})
end)
end)
diff --git a/test/functional/legacy/autochdir_spec.lua b/test/functional/legacy/autochdir_spec.lua
index 13cb6cd287..5da54b4850 100644
--- a/test/functional/legacy/autochdir_spec.lua
+++ b/test/functional/legacy/autochdir_spec.lua
@@ -1,8 +1,8 @@
-local lfs = require('lfs')
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, matches = helpers.clear, helpers.eq, helpers.matches
local eval, command, call, meths = helpers.eval, helpers.command, helpers.call, helpers.meths
local source, exec_capture = helpers.source, helpers.exec_capture
+local mkdir = helpers.mkdir
local function expected_empty()
eq({}, meths.get_vvar('errors'))
@@ -12,7 +12,7 @@ describe('autochdir behavior', function()
local dir = 'Xtest_functional_legacy_autochdir'
before_each(function()
- lfs.mkdir(dir)
+ mkdir(dir)
clear()
command('set shellslash')
end)
diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua
index e00b468c16..6034d13e2a 100644
--- a/test/functional/legacy/autocmd_option_spec.lua
+++ b/test/functional/legacy/autocmd_option_spec.lua
@@ -48,10 +48,10 @@ end
local function expected_table(option, oldval, oldval_l, oldval_g, newval, scope, cmd, attr)
return {
option = option,
- oldval = tostring(oldval),
- oldval_l = tostring(oldval_l),
- oldval_g = tostring(oldval_g),
- newval = tostring(newval),
+ oldval = oldval,
+ oldval_l = oldval_l,
+ oldval_g = oldval_g,
+ newval = newval,
scope = scope,
cmd = cmd,
attr = attr,
@@ -129,44 +129,44 @@ describe('au OptionSet', function()
it('should be called in setting number option', function()
command('set nu')
- expected_combination({'number', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'number', false, false, false, true, 'global', 'set'})
command('setlocal nonu')
- expected_combination({'number', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'number', true, true, '', false, 'local', 'setlocal'})
command('setglobal nonu')
- expected_combination({'number', 1, '', 1, 0, 'global', 'setglobal'})
+ expected_combination({'number', true, '', true, false, 'global', 'setglobal'})
end)
it('should be called in setting autoindent option',function()
command('setlocal ai')
- expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'autoindent', false, false, '', true, 'local', 'setlocal'})
command('setglobal ai')
- expected_combination({'autoindent', 0, '', 0, 1, 'global', 'setglobal'})
+ expected_combination({'autoindent', false, '', false, true, 'global', 'setglobal'})
command('set noai')
- expected_combination({'autoindent', 1, 1, 1, 0, 'global', 'set'})
+ expected_combination({'autoindent', true, true, true, false, 'global', 'set'})
end)
it('should be called in inverting global autoindent option',function()
command('set ai!')
- expected_combination({'autoindent', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'autoindent', false, false, false, true, 'global', 'set'})
end)
it('should be called in being unset local autoindent option',function()
command('setlocal ai')
- expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'autoindent', false, false, '', true, 'local', 'setlocal'})
command('setlocal ai<')
- expected_combination({'autoindent', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'autoindent', true, true, '', false, 'local', 'setlocal'})
end)
it('should be called in setting global list and number option at the same time',function()
command('set list nu')
expected_combination(
- {'list', 0, 0, 0, 1, 'global', 'set'},
- {'number', 0, 0, 0, 1, 'global', 'set'}
+ {'list', false, false, false, true, 'global', 'set'},
+ {'number', false, false, false, true, 'global', 'set'}
)
end)
@@ -177,20 +177,20 @@ describe('au OptionSet', function()
it('should be called in setting local acd', function()
command('setlocal acd')
- expected_combination({'autochdir', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'autochdir', false, false, '', true, 'local', 'setlocal'})
end)
it('should be called in setting autoread', function()
command('set noar')
- expected_combination({'autoread', 1, 1, 1, 0, 'global', 'set'})
+ expected_combination({'autoread', true, true, true, false, 'global', 'set'})
command('setlocal ar')
- expected_combination({'autoread', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'autoread', false, false, '', true, 'local', 'setlocal'})
end)
it('should be called in inverting global autoread', function()
command('setglobal invar')
- expected_combination({'autoread', 1, '', 1, 0, 'global', 'setglobal'})
+ expected_combination({'autoread', true, '', true, false, 'global', 'setglobal'})
end)
it('should be called in setting backspace option through :let', function()
@@ -208,7 +208,7 @@ describe('au OptionSet', function()
it('should trigger using correct option name', function()
command('call setbufvar(1, "&backup", 1)')
- expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'backup', false, false, '', true, 'local', 'setlocal'})
end)
it('should trigger if the current buffer is different from the targeted buffer', function()
@@ -260,7 +260,7 @@ describe('au OptionSet', function()
command('setlocal tags=tagpath2')
expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'})
- -- Note: v:option_old is the old global value for global-local string options
+ -- Note: v:option_old is the old global value for global-local options.
-- but the old local value for all other kinds of options.
command('noa setglobal tags=tag_global')
command('noa setlocal tags=tag_local')
@@ -269,12 +269,12 @@ describe('au OptionSet', function()
'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set'
})
- -- Note: v:option_old is the old global value for global-local string options
+ -- Note: v:option_old is the old global value for global-local options.
-- but the old local value for all other kinds of options.
command('noa set tags=tag_global')
command('noa setlocal tags=')
command('set tags=tagpath')
- expected_combination({'tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set'})
+ expected_combination({'tags', 'tag_global', 'tag_global', 'tag_global', 'tagpath', 'global', 'set'})
end)
it('with string local (to buffer) option', function()
@@ -295,7 +295,7 @@ describe('au OptionSet', function()
command('setlocal spelllang=klingon')
expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'})
- -- Note: v:option_old is the old global value for global-local string options
+ -- Note: v:option_old is the old global value for global-local options.
-- but the old local value for all other kinds of options.
command('noa setglobal spelllang=spellglobal')
command('noa setlocal spelllang=spelllocal')
@@ -311,7 +311,7 @@ describe('au OptionSet', function()
command('set statusline=foo')
expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'})
- -- Note: v:option_old is the old global value for global-local string options
+ -- Note: v:option_old is the old global value for global-local options.
-- but the old local value for all other kinds of options.
command('set statusline&')
expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'})
@@ -323,7 +323,7 @@ describe('au OptionSet', function()
command('setlocal statusline=baz')
expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'})
- -- Note: v:option_old is the old global value for global-local string options
+ -- Note: v:option_old is the old global value for global-local options.
-- but the old local value for all other kinds of options.
command('noa setglobal statusline=bar')
command('noa setlocal statusline=baz')
@@ -364,11 +364,15 @@ describe('au OptionSet', function()
command('setlocal cmdheight=2')
expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa setglobal cmdheight=8')
command('noa setlocal cmdheight=1') -- Sets the global(!) value
command('set cmdheight=2')
expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa set cmdheight=8')
command('set cmdheight=2')
expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'})
@@ -385,11 +389,15 @@ describe('au OptionSet', function()
command('setlocal undolevels=2')
expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa setglobal undolevels=8')
command('noa setlocal undolevels=1')
command('set undolevels=2')
- expected_combination({'undolevels', 1, 1, 8, 2, 'global', 'set'})
+ expected_combination({'undolevels', 8, 1, 8, 2, 'global', 'set'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa set undolevels=8')
command('set undolevels=2')
expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'})
@@ -427,11 +435,15 @@ describe('au OptionSet', function()
command('setlocal scrolloff=2')
expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa setglobal scrolloff=8')
command('noa setlocal scrolloff=1')
command('set scrolloff=2')
- expected_combination({'scrolloff', 1, 1, 8, 2, 'global', 'set'})
+ expected_combination({'scrolloff', 8, 1, 8, 2, 'global', 'set'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa set scrolloff=8')
command('set scrolloff=2')
expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'})
@@ -441,114 +453,111 @@ describe('au OptionSet', function()
command('noa setglobal foldcolumn=8')
command('noa setlocal foldcolumn=1')
command('setglobal foldcolumn=2')
- expected_combination({'foldcolumn', 8, '', 8, 2, 'global', 'setglobal'})
+ expected_combination({'foldcolumn', '8', '', '8', '2', 'global', 'setglobal'})
command('noa setglobal foldcolumn=8')
command('noa setlocal foldcolumn=1')
command('setlocal foldcolumn=2')
- expected_combination({'foldcolumn', 1, 1, '', 2, 'local', 'setlocal'})
+ expected_combination({'foldcolumn', '1', '1', '', '2', 'local', 'setlocal'})
command('noa setglobal foldcolumn=8')
command('noa setlocal foldcolumn=1')
command('set foldcolumn=2')
- expected_combination({'foldcolumn', 1, 1, 8, 2, 'global', 'set'})
+ expected_combination({'foldcolumn', '1', '1', '8', '2', 'global', 'set'})
command('noa set foldcolumn=8')
command('set foldcolumn=2')
- expected_combination({'foldcolumn', 8, 8, 8, 2, 'global', 'set'})
+ expected_combination({'foldcolumn', '8', '8', '8', '2', 'global', 'set'})
end)
it('with boolean global option', function()
command('noa setglobal nowrapscan')
command('noa setlocal wrapscan') -- Sets the global(!) value
command('setglobal nowrapscan')
- expected_combination({'wrapscan', 1, '', 1, 0, 'global', 'setglobal'})
+ expected_combination({'wrapscan', true, '', true, false, 'global', 'setglobal'})
command('noa setglobal nowrapscan')
command('noa setlocal wrapscan') -- Sets the global(!) value
command('setlocal nowrapscan')
- expected_combination({'wrapscan', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'wrapscan', true, true, '', false, 'local', 'setlocal'})
command('noa setglobal nowrapscan')
command('noa setlocal wrapscan') -- Sets the global(!) value
command('set nowrapscan')
- expected_combination({'wrapscan', 1, 1, 1, 0, 'global', 'set'})
+ expected_combination({'wrapscan', true, true, true, false, 'global', 'set'})
command('noa set nowrapscan')
command('set wrapscan')
- expected_combination({'wrapscan', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'wrapscan', false, false, false, true, 'global', 'set'})
end)
it('with boolean global-local (to buffer) option', function()
command('noa setglobal noautoread')
command('noa setlocal autoread')
command('setglobal autoread')
- expected_combination({'autoread', 0, '', 0, 1, 'global', 'setglobal'})
+ expected_combination({'autoread', false, '', false, true, 'global', 'setglobal'})
command('noa setglobal noautoread')
command('noa setlocal autoread')
command('setlocal noautoread')
- expected_combination({'autoread', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'autoread', true, true, '', false, 'local', 'setlocal'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa setglobal noautoread')
command('noa setlocal autoread')
command('set autoread')
- expected_combination({'autoread', 1, 1, 0, 1, 'global', 'set'})
+ expected_combination({'autoread', false, true, false, true, 'global', 'set'})
+ -- Note: v:option_old is the old global value for global-local options.
+ -- but the old local value for all other kinds of options.
command('noa set noautoread')
command('set autoread')
- expected_combination({'autoread', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'autoread', false, false, false, true, 'global', 'set'})
end)
it('with boolean local (to buffer) option', function()
command('noa setglobal nocindent')
command('noa setlocal cindent')
command('setglobal cindent')
- expected_combination({'cindent', 0, '', 0, 1, 'global', 'setglobal'})
+ expected_combination({'cindent', false, '', false, true, 'global', 'setglobal'})
command('noa setglobal nocindent')
command('noa setlocal cindent')
command('setlocal nocindent')
- expected_combination({'cindent', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'cindent', true, true, '', false, 'local', 'setlocal'})
command('noa setglobal nocindent')
command('noa setlocal cindent')
command('set cindent')
- expected_combination({'cindent', 1, 1, 0, 1, 'global', 'set'})
+ expected_combination({'cindent', true, true, false, true, 'global', 'set'})
command('noa set nocindent')
command('set cindent')
- expected_combination({'cindent', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'cindent', false, false, false, true, 'global', 'set'})
end)
it('with boolean local (to window) option', function()
command('noa setglobal nocursorcolumn')
command('noa setlocal cursorcolumn')
command('setglobal cursorcolumn')
- expected_combination({'cursorcolumn', 0, '', 0, 1, 'global', 'setglobal'})
+ expected_combination({'cursorcolumn', false, '', false, true, 'global', 'setglobal'})
command('noa setglobal nocursorcolumn')
command('noa setlocal cursorcolumn')
command('setlocal nocursorcolumn')
- expected_combination({'cursorcolumn', 1, 1, '', 0, 'local', 'setlocal'})
+ expected_combination({'cursorcolumn', true, true, '', false, 'local', 'setlocal'})
command('noa setglobal nocursorcolumn')
command('noa setlocal cursorcolumn')
command('set cursorcolumn')
- expected_combination({'cursorcolumn', 1, 1, 0, 1, 'global', 'set'})
+ expected_combination({'cursorcolumn', true, true, false, true, 'global', 'set'})
command('noa set nocursorcolumn')
command('set cursorcolumn')
- expected_combination({'cursorcolumn', 0, 0, 0, 1, 'global', 'set'})
+ expected_combination({'cursorcolumn', false, false, false, true, 'global', 'set'})
end)
- it('with option value converted internally', function()
- command('noa set backspace=1')
- command('set backspace=2')
- expected_combination(({
- 'backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set'
- }))
- end)
end)
describe('with specific option', function()
@@ -566,13 +575,13 @@ describe('au OptionSet', function()
expected_empty()
command('setlocal ro')
- expected_combination({'readonly', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'readonly', false, false, '', true, 'local', 'setlocal'})
command('setglobal ro')
- expected_combination({'readonly', 0, '', 0, 1, 'global', 'setglobal'})
+ expected_combination({'readonly', false, '', false, true, 'global', 'setglobal'})
command('set noro')
- expected_combination({'readonly', 1, 1, 1, 0, 'global', 'set'})
+ expected_combination({'readonly', true, true, true, false, 'global', 'set'})
end)
describe('being set by setbufvar()', function()
@@ -587,7 +596,7 @@ describe('au OptionSet', function()
set_hook('backup')
command('call setbufvar(1, "&backup", 1)')
- expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'backup', false, false, '', true, 'local', 'setlocal'})
end)
it('should trigger if the current buffer is different from the targeted buffer', function()
@@ -597,7 +606,8 @@ describe('au OptionSet', function()
local new_bufnr = buf.get_number(new_buffer)
command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")')
- expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}})
+ expected_combination({ 'buftype', '', '', '', 'nofile', 'local', 'setlocal',
+ { bufnr = new_bufnr } })
end)
end)
@@ -613,7 +623,7 @@ describe('au OptionSet', function()
set_hook('backup')
command('call setwinvar(1, "&backup", 1)')
- expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})
+ expected_combination({'backup', false, false, '', true, 'local', 'setlocal'})
end)
it('should not trigger if the current window is different from the targeted window', function()
@@ -622,7 +632,7 @@ describe('au OptionSet', function()
local new_winnr = get_new_window_number()
command('call setwinvar(' .. new_winnr .. ', "&cursorcolumn", 1)')
- -- expected_combination({'cursorcolumn', 0, 1, 'local', {winnr = new_winnr}})
+ -- expected_combination({'cursorcolumn', false, true, 'local', {winnr = new_winnr}})
expected_empty()
end)
end)
@@ -631,24 +641,24 @@ describe('au OptionSet', function()
it('should trigger if a boolean option be set globally', function()
set_hook('autochdir')
- nvim.set_option('autochdir', true)
- eq(true, nvim.get_option('autochdir'))
- expected_combination({'autochdir', 0, '', 0, 1, 'global', 'setglobal'})
+ nvim.set_option_value('autochdir', true, {scope='global'})
+ eq(true, nvim.get_option_value('autochdir', {scope='global'}))
+ expected_combination({'autochdir', false, '', false, true, 'global', 'setglobal'})
end)
it('should trigger if a number option be set globally', function()
set_hook('cmdheight')
- nvim.set_option('cmdheight', 5)
- eq(5, nvim.get_option('cmdheight'))
+ nvim.set_option_value('cmdheight', 5, {scope='global'})
+ eq(5, nvim.get_option_value('cmdheight', {scope='global'}))
expected_combination({'cmdheight', 1, '', 1, 5, 'global', 'setglobal'})
end)
it('should trigger if a string option be set globally', function()
set_hook('ambiwidth')
- nvim.set_option('ambiwidth', 'double')
- eq('double', nvim.get_option('ambiwidth'))
+ nvim.set_option_value('ambiwidth', 'double', {scope='global'})
+ eq('double', nvim.get_option_value('ambiwidth', {scope='global'}))
expected_combination({'ambiwidth', 'single', '', 'single', 'double', 'global', 'setglobal'})
end)
end)
diff --git a/test/functional/legacy/breakindent_spec.lua b/test/functional/legacy/breakindent_spec.lua
index d7779684a4..3913ba935a 100644
--- a/test/functional/legacy/breakindent_spec.lua
+++ b/test/functional/legacy/breakindent_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local command = helpers.command
local exec = helpers.exec
local feed = helpers.feed
@@ -17,28 +18,47 @@ describe('breakindent', function()
})
screen:attach()
exec([[
+ set listchars=eol:$
let &signcolumn = 'yes'
- let &showbreak = '+'
+ let &showbreak = '++'
+ let &breakindentopt = 'shift:2'
let leftcol = win_getid()->getwininfo()->get(0, {})->get('textoff')
eval repeat('x', &columns - leftcol - 1)->setline(1)
eval 'second line'->setline(2)
]])
+ feed('AX')
screen:expect([[
- {1: }^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
- {1: }second line |
+ {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX|
+ {1: }^second line |
{0:~ }|
{0:~ }|
{0:~ }|
- |
+ {2:-- INSERT --} |
]])
- feed('AX')
- screen:expect([[
+ -- No line wraps, so changing 'showbreak' should lead to the same screen.
+ command('setlocal showbreak=+')
+ screen:expect_unchanged()
+ -- No line wraps, so setting 'breakindent' should lead to the same screen.
+ command('setlocal breakindent')
+ screen:expect_unchanged()
+ -- The first line now wraps because of "eol" in 'listchars'.
+ command('setlocal list')
+ screen:expect{grid=[[
{1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX|
- {1: }^second line |
+ {1: } {0:+^$} |
+ {1: }second line{0:$} |
{0:~ }|
{0:~ }|
+ {2:-- INSERT --} |
+ ]]}
+ command('setlocal nobreakindent')
+ screen:expect{grid=[[
+ {1: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxX|
+ {1: }{0:+^$} |
+ {1: }second line{0:$} |
+ {0:~ }|
{0:~ }|
{2:-- INSERT --} |
- ]])
+ ]]}
end)
end)
diff --git a/test/functional/legacy/buffer_spec.lua b/test/functional/legacy/buffer_spec.lua
index acaa9a51f1..1e8909f0d0 100644
--- a/test/functional/legacy/buffer_spec.lua
+++ b/test/functional/legacy/buffer_spec.lua
@@ -10,7 +10,7 @@ describe('buffer', function()
before_each(function()
clear()
meths.ui_attach(80, 24, {})
- meths.set_option('hidden', false)
+ meths.set_option_value('hidden', false, {})
end)
it('deleting a modified buffer with :confirm', function()
diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua
index 2fceb6a132..3cbff2a01b 100644
--- a/test/functional/legacy/cmdline_spec.lua
+++ b/test/functional/legacy/cmdline_spec.lua
@@ -225,9 +225,9 @@ describe('cmdline', function()
[3] = {reverse = true}, -- TabLineFill
})
screen:attach()
- meths.set_option('laststatus', 2)
- meths.set_option('showtabline', 2)
- meths.set_option('cmdheight', 1)
+ meths.set_option_value('laststatus', 2, {})
+ meths.set_option_value('showtabline', 2, {})
+ meths.set_option_value('cmdheight', 1, {})
screen:expect([[
{2: [No Name] }{3: }|
^ |
@@ -239,6 +239,25 @@ describe('cmdline', function()
|
]])
end)
+
+ -- oldtest: Test_rulerformat_position()
+ it("ruler has correct position with 'rulerformat' set", function()
+ local screen = Screen.new(20, 3)
+ screen:set_default_attr_ids {
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ }
+ screen:attach()
+ meths.set_option_value('ruler', true, {})
+ meths.set_option_value('rulerformat', 'longish', {})
+ meths.set_option_value('laststatus', 0, {})
+ meths.set_option_value('winwidth', 1, {})
+ feed [[<C-W>v<C-W>|<C-W>p]]
+ screen:expect [[
+ │^ |
+ {0:~ }│{0:~}|
+ longish |
+ ]]
+ end)
end)
describe('cmdwin', function()
diff --git a/test/functional/legacy/conceal_spec.lua b/test/functional/legacy/conceal_spec.lua
new file mode 100644
index 0000000000..6aaa93f886
--- /dev/null
+++ b/test/functional/legacy/conceal_spec.lua
@@ -0,0 +1,587 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local command = helpers.command
+local exec = helpers.exec
+local feed = helpers.feed
+
+local expect_pos = function(row, col)
+ return helpers.eq({row, col}, helpers.eval('[screenrow(), screencol()]'))
+end
+
+describe('Conceal', function()
+ before_each(function()
+ clear()
+ command('set nohlsearch')
+ end)
+
+ -- oldtest: Test_conceal_two_windows()
+ it('works', function()
+ local screen = Screen.new(75, 12)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {bold = true, reverse = true}, -- StatusLine
+ [2] = {reverse = true}, -- StatusLineNC, IncSearch
+ [3] = {bold = true}, -- ModeMsg
+ })
+ screen:attach()
+ exec([[
+ let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"]
+ call setline(1, lines)
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ new
+ call setline(1, lines)
+ call setline(4, "Second window")
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=nc
+ exe "normal /here\r"
+ ]])
+
+ -- Check that cursor line is concealed
+ screen:expect([[
+ one one one one one |
+ two ^here |
+ three three |
+ Second window |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three three |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ /here |
+ ]])
+
+ -- Check that with concealed text vertical cursor movement is correct.
+ feed('k')
+ screen:expect([[
+ one one one o^ne one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three three |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ /here |
+ ]])
+
+ -- Check that with cursor line is not concealed
+ feed('j')
+ command('set concealcursor=')
+ screen:expect([[
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ Second window |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three three |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ /here |
+ ]])
+
+ -- Check that with cursor line is not concealed when moving cursor down
+ feed('j')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three |hidden^| three |
+ Second window |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three three |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ /here |
+ ]])
+
+ -- Check that with cursor line is not concealed when switching windows
+ feed('<C-W><C-W>')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ /here |
+ ]])
+
+ -- Check that with cursor line is only concealed in Normal mode
+ command('set concealcursor=n')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ /here |
+ ]])
+ feed('a')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h^ere |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Esc>/e')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h{2:e}re |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ /e^ |
+ ]])
+ feed('<Esc>v')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- VISUAL --} |
+ ]])
+ feed('<Esc>')
+
+ -- Check that with cursor line is only concealed in Insert mode
+ command('set concealcursor=i')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ |
+ ]])
+ feed('a')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two h^ere |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Esc>/e')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h{2:e}re |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ /e^ |
+ ]])
+ feed('<Esc>v')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- VISUAL --} |
+ ]])
+ feed('<Esc>')
+
+ -- Check that with cursor line is only concealed in Visual mode
+ command('set concealcursor=v')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ |
+ ]])
+ feed('a')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h^ere |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Esc>/e')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h{2:e}re |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ /e^ |
+ ]])
+ feed('<Esc>v')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two ^here |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- VISUAL --} |
+ ]])
+ feed('<Esc>')
+
+ -- Check moving the cursor while in insert mode.
+ command('set concealcursor=')
+ feed('a')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two |hidden| h^ere |
+ three three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Down>')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three |hidden|^ three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Esc>')
+
+ -- Check the "o" command
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three |hidden^| three |
+ {0:~ }|
+ {1:[No Name] [+] }|
+ |
+ ]])
+ feed('o')
+ screen:expect([[
+ one one one one one |
+ two here |
+ three three |
+ Second window |
+ {0:~ }|
+ {2:[No Name] [+] }|
+ one one one one one |
+ two here |
+ three three |
+ ^ |
+ {1:[No Name] [+] }|
+ {3:-- INSERT --} |
+ ]])
+ feed('<Esc>')
+ end)
+
+ -- oldtest: Test_conceal_with_cursorcolumn()
+ it('CursorColumn and ColorColumn on wrapped line', function()
+ local screen = Screen.new(40, 10)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {background = Screen.colors.Grey90}, -- CursorColumn
+ [2] = {background = Screen.colors.LightRed}, -- ColorColumn
+ })
+ screen:attach()
+ -- Check that cursorcolumn and colorcolumn don't get broken in presence of
+ -- wrapped lines containing concealed text
+ -- luacheck: push ignore 613 (trailing whitespace in a string)
+ exec([[
+ let lines = ["one one one |hidden| one one one one one one one one",
+ \ "two two two two |hidden| here two two",
+ \ "three |hidden| three three three three three three three three"]
+ call setline(1, lines)
+ set wrap linebreak
+ set showbreak=\ >>>\
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ set cursorcolumn
+ set colorcolumn=50
+ ]])
+ -- luacheck: pop
+
+ screen:expect([[
+ one one one one one one {1:o}ne |
+ {0: >>> }one {2:o}ne one one |
+ two two two two |hidden| ^here two two |
+ three three three three {1:t}hree |
+ {0: >>> }thre{2:e} three three three |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ /here |
+ ]])
+
+ -- move cursor to the end of line (the cursor jumps to the next screen line)
+ feed('$')
+ screen:expect([[
+ one one one one one one one |
+ {0: >>> }one {2:o}ne one one |
+ two two two two |hidden| here two tw^o |
+ three three three three three |
+ {0: >>> }thre{2:e} three three three |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ /here |
+ ]])
+ end)
+
+ -- oldtest: Test_conceal_resize_term()
+ it('resize editor', function()
+ local screen = Screen.new(75, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {foreground = Screen.colors.Blue}, -- Comment
+ })
+ screen:attach()
+ exec([[
+ call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed')
+ setl cocu=n cole=3
+ syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends
+ normal fb
+ ]])
+ screen:expect([[
+ one two three four five, the ^backticks should be concealed |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+
+ screen:try_resize(75, 7)
+ screen:expect([[
+ one two three four five, the ^backticks should be concealed |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_conceal_linebreak()
+ it('with linebreak', function()
+ local screen = Screen.new(75, 8)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ })
+ screen:attach()
+ exec([[
+ let &wrap = v:true
+ let &conceallevel = 2
+ let &concealcursor = 'nc'
+ let &linebreak = v:true
+ let &showbreak = '+ '
+ let line = 'a`a`a`a`'
+ \ .. 'a'->repeat(&columns - 15)
+ \ .. ' b`b`'
+ \ .. 'b'->repeat(&columns - 10)
+ \ .. ' cccccc'
+ eval ['x'->repeat(&columns), '', line]->setline(1)
+ syntax region CodeSpan matchgroup=Delimiter start=/\z(`\+\)/ end=/\z1/ concealends
+ ]])
+ screen:expect([[
+ ^xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+ |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+ {0:+ }bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
+ {0:+ }cccccc |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
+
+ -- Tests for correct display (cursor column position) with +conceal and tabulators.
+ -- oldtest: Test_conceal_cursor_pos()
+ it('cursor and column position with conceal and tabulators', function()
+ exec([[
+ let l = ['start:', '.concealed. text', "|concealed|\ttext"]
+ let l += ['', "\t.concealed.\ttext", "\t|concealed|\ttext", '']
+ let l += [".a.\t.b.\t.c.\t.d.", "|a|\t|b|\t|c|\t|d|"]
+ call append(0, l)
+ call cursor(1, 1)
+ " Conceal settings.
+ set conceallevel=2
+ set concealcursor=nc
+ syntax match test /|/ conceal
+ ]])
+ feed('ztj')
+ expect_pos(2, 1)
+ -- We should end up in the same column when running these commands on the
+ -- two lines.
+ feed('ft')
+ expect_pos(2, 17)
+ feed('$')
+ expect_pos(2, 20)
+ feed('0j')
+ expect_pos(3, 1)
+ feed('ft')
+ expect_pos(3, 17)
+ feed('$')
+ expect_pos(3, 20)
+ feed('j0j')
+ expect_pos(5, 8)
+ -- Same for next test block.
+ feed('ft')
+ expect_pos(5, 25)
+ feed('$')
+ expect_pos(5, 28)
+ feed('0j')
+ expect_pos(6, 8)
+ feed('ft')
+ expect_pos(6, 25)
+ feed('$')
+ expect_pos(6, 28)
+ feed('0j0j')
+ expect_pos(8, 1)
+ -- And check W with multiple tabs and conceals in a line.
+ feed('W')
+ expect_pos(8, 9)
+ feed('W')
+ expect_pos(8, 17)
+ feed('W')
+ expect_pos(8, 25)
+ feed('$')
+ expect_pos(8, 27)
+ feed('0j')
+ expect_pos(9, 1)
+ feed('W')
+ expect_pos(9, 9)
+ feed('W')
+ expect_pos(9, 17)
+ feed('W')
+ expect_pos(9, 25)
+ feed('$')
+ expect_pos(9, 26)
+ command('set lbr')
+ feed('$')
+ expect_pos(9, 26)
+ command('set list listchars=tab:>-')
+ feed('0')
+ expect_pos(9, 1)
+ feed('W')
+ expect_pos(9, 9)
+ feed('W')
+ expect_pos(9, 17)
+ feed('W')
+ expect_pos(9, 25)
+ feed('$')
+ expect_pos(9, 26)
+ end)
+end)
diff --git a/test/functional/legacy/crash_spec.lua b/test/functional/legacy/crash_spec.lua
new file mode 100644
index 0000000000..5094f81847
--- /dev/null
+++ b/test/functional/legacy/crash_spec.lua
@@ -0,0 +1,16 @@
+local helpers = require('test.functional.helpers')(after_each)
+local assert_alive = helpers.assert_alive
+local clear = helpers.clear
+local command = helpers.command
+local feed = helpers.feed
+
+before_each(clear)
+
+-- oldtest: Test_crash1()
+it('no crash when ending Visual mode while editing buffer closes window', function()
+ command('new')
+ command('autocmd ModeChanged v:n ++once close')
+ feed('v')
+ command('enew')
+ assert_alive()
+end)
diff --git a/test/functional/legacy/debugger_spec.lua b/test/functional/legacy/debugger_spec.lua
new file mode 100644
index 0000000000..e6fb81719f
--- /dev/null
+++ b/test/functional/legacy/debugger_spec.lua
@@ -0,0 +1,85 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local command = helpers.command
+local feed = helpers.feed
+local write_file = helpers.write_file
+
+before_each(clear)
+
+describe('debugger', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(999, 10)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue};
+ [1] = {reverse = true, bold = true};
+ })
+ screen:attach()
+ end)
+
+ -- oldtest: Test_Debugger_breakadd_expr()
+ it(':breakadd expr', function()
+ write_file('XdebugBreakExpr.vim', 'let g:Xtest_var += 1')
+ finally(function()
+ os.remove('XdebugBreakExpr.vim')
+ end)
+
+ command('edit XdebugBreakExpr.vim')
+ command(':let g:Xtest_var = 10')
+ command(':breakadd expr g:Xtest_var')
+ feed(':source %<CR>')
+ screen:expect{grid=[[
+ ^let g:Xtest_var += 1{MATCH: *}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ :source %{MATCH: *}|
+ ]]}
+ feed(':source %<CR>')
+ screen:expect{grid=[[
+ let g:Xtest_var += 1{MATCH: *}|
+ {0:~{MATCH: *}}|
+ {1:{MATCH: *}}|
+ Breakpoint in "{MATCH:.*}XdebugBreakExpr.vim" line 1{MATCH: *}|
+ Entering Debug mode. Type "cont" to continue.{MATCH: *}|
+ Oldval = "10"{MATCH: *}|
+ Newval = "11"{MATCH: *}|
+ {MATCH:.*}XdebugBreakExpr.vim{MATCH: *}|
+ line 1: let g:Xtest_var += 1{MATCH: *}|
+ >^{MATCH: *}|
+ ]]}
+ feed('cont<CR>')
+ screen:expect{grid=[[
+ ^let g:Xtest_var += 1{MATCH: *}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {0:~{MATCH: *}}|
+ {MATCH: *}|
+ ]]}
+ feed(':source %<CR>')
+ screen:expect{grid=[[
+ let g:Xtest_var += 1{MATCH: *}|
+ {0:~{MATCH: *}}|
+ {1:{MATCH: *}}|
+ Breakpoint in "{MATCH:.*}XdebugBreakExpr.vim" line 1{MATCH: *}|
+ Entering Debug mode. Type "cont" to continue.{MATCH: *}|
+ Oldval = "11"{MATCH: *}|
+ Newval = "12"{MATCH: *}|
+ {MATCH:.*}XdebugBreakExpr.vim{MATCH: *}|
+ line 1: let g:Xtest_var += 1{MATCH: *}|
+ >^{MATCH: *}|
+ ]]}
+ end)
+end)
diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua
index cefcd6c6c4..1b9ec9af62 100644
--- a/test/functional/legacy/delete_spec.lua
+++ b/test/functional/legacy/delete_spec.lua
@@ -63,6 +63,6 @@ describe('Test for delete()', function()
it('gives correct emsgs', function()
eq('Vim(call):E474: Invalid argument', exc_exec("call delete('')"))
- eq('Vim(call):E15: Invalid expression: 0', exc_exec("call delete('foo', 0)"))
+ eq('Vim(call):E15: Invalid expression: "0"', exc_exec("call delete('foo', 0)"))
end)
end)
diff --git a/test/functional/legacy/digraph_spec.lua b/test/functional/legacy/digraph_spec.lua
index 0cb0bb84be..7eeb83eb5f 100644
--- a/test/functional/legacy/digraph_spec.lua
+++ b/test/functional/legacy/digraph_spec.lua
@@ -22,7 +22,7 @@ describe('digraph', function()
{0:~ }|
{0:~ }|
{0:~ }|
- {2:-- INSERT -} |
+ {2:-- INSERT --}|
]])
feed('1')
screen:expect([[
@@ -31,7 +31,7 @@ describe('digraph', function()
{0:~ }|
{0:~ }|
{0:~ }|
- {2:-- INSERT -} |
+ {2:-- INSERT --}|
]])
feed('2')
screen:expect([[
@@ -40,7 +40,7 @@ describe('digraph', function()
{0:~ }|
{0:~ }|
{0:~ }|
- {2:-- INSERT -} |
+ {2:-- INSERT --}|
]])
end)
end)
diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua
index f9b78f5dcd..9c6f521882 100644
--- a/test/functional/legacy/display_spec.lua
+++ b/test/functional/legacy/display_spec.lua
@@ -58,51 +58,6 @@ describe('display', function()
]])
end)
- -- oldtest: Test_matchparen_clear_highlight()
- it('matchparen highlight is cleared when switching buffer', function()
- local screen = Screen.new(20, 5)
- screen:set_default_attr_ids({
- [0] = {bold = true, foreground = Screen.colors.Blue},
- [1] = {background = Screen.colors.Cyan},
- })
- screen:attach()
-
- local screen1 = [[
- {1:^()} |
- {0:~ }|
- {0:~ }|
- {0:~ }|
- |
- ]]
- local screen2 = [[
- ^aa |
- {0:~ }|
- {0:~ }|
- {0:~ }|
- |
- ]]
-
- exec([[
- source $VIMRUNTIME/plugin/matchparen.vim
- set hidden
- call setline(1, ['()'])
- normal 0
- ]])
- screen:expect(screen1)
-
- exec([[
- enew
- exe "normal iaa\<Esc>0"
- ]])
- screen:expect(screen2)
-
- feed('<C-^>')
- screen:expect(screen1)
-
- feed('<C-^>')
- screen:expect(screen2)
- end)
-
local function run_test_display_lastline(euro)
local screen = Screen.new(75, 10)
screen:set_default_attr_ids({
@@ -194,4 +149,149 @@ describe('display', function()
it('display "lastline" works correctly with multibyte fillchar', function()
run_test_display_lastline(true)
end)
+
+ -- oldtest: Test_display_long_lastline()
+ it('"lastline" shows correct text when end of wrapped line is deleted', function()
+ local screen = Screen.new(35, 14)
+ screen:attach()
+ exec([[
+ set display=lastline smoothscroll scrolloff=0
+ call setline(1, [
+ \'aaaaa'->repeat(150),
+ \'bbbbb '->repeat(7) .. 'ccccc '->repeat(7) .. 'ddddd '->repeat(7)
+ \])
+ ]])
+ feed('736|')
+ screen:expect([[
+ <<<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ ^aaaaaaaaaaaaaaa |
+ |
+ ]])
+ -- The correct part of the last line is moved into view.
+ feed('D')
+ screen:expect([[
+ <<<aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^a|
+ bbbbb bbbbb bbbbb bbbbb bbbbb bb@@@|
+ |
+ ]])
+ -- "w_skipcol" does not change because the topline is still long enough
+ -- to maintain the current skipcol.
+ feed('g04l11gkD')
+ screen:expect([[
+ <<<^a |
+ bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb|
+ bbbbb ccccc ccccc ccccc ccccc cccc|
+ c ccccc ccccc ddddd ddddd ddddd ddd|
+ dd ddddd ddddd ddddd |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ -- "w_skipcol" is reset to bring the entire topline into view because
+ -- the line length is now smaller than the current skipcol + marker.
+ feed('x')
+ screen:expect([[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aa^a |
+ bbbbb bbbbb bbbbb bbbbb bbbbb bbbbb|
+ bbbbb ccccc ccccc ccccc ccccc cccc|
+ c ccccc ccccc ddddd ddddd ddddd @@@|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_display_cursor_long_line()
+ it("correctly shows line that doesn't fit in the window", function()
+ local screen = Screen.new(75, 8)
+ screen:attach()
+ exec([[
+ call setline(1, ['a', 'b ' .. 'bbbbb'->repeat(150), 'c'])
+ norm $j
+ ]])
+ screen:expect([[
+ <<<bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ b^b |
+ |
+ ]])
+ -- FIXME: moving the cursor above the topline does not set w_skipcol
+ -- correctly with cpo+=n and zero scrolloff (curs_columns() extra == 1).
+ exec('set number cpo+=n scrolloff=0')
+ feed('$0')
+ screen:expect([[
+ <<<b^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ |
+ ]])
+ -- Going to the start of the line with "b" did not set w_skipcol correctly with 'smoothscroll'.
+ exec('set smoothscroll')
+ feed('$b')
+ screen:expect([[
+ 2 b ^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ |
+ ]])
+ -- Same for "ge".
+ feed('$ge')
+ screen:expect([[
+ 2 ^b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua
index 362d33a0fd..939999e21b 100644
--- a/test/functional/legacy/edit_spec.lua
+++ b/test/functional/legacy/edit_spec.lua
@@ -43,7 +43,7 @@ describe('edit', function()
{0:~ }|
{0:~ }|
{0:~ }|
- {2:-- INSERT -} |
+ {2:-- INSERT --}|
]])
feed('=')
screen:expect([[
@@ -55,4 +55,68 @@ describe('edit', function()
=^ |
]])
end)
+
+ -- oldtest: Test_edit_ctrl_r_failed()
+ it('positioning cursor after CTRL-R expression failed', function()
+ local screen = Screen.new(60, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {foreground = Screen.colors.Blue}, -- SpecialKey
+ [2] = {foreground = Screen.colors.SlateBlue},
+ [3] = {bold = true}, -- ModeMsg
+ [4] = {reverse = true, bold = true}, -- MsgSeparator
+ [5] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg
+ [6] = {foreground = Screen.colors.SeaGreen, bold = true}, -- MoreMsg
+ })
+ screen:attach()
+
+ feed('i<C-R>')
+ screen:expect([[
+ {1:^"} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- INSERT --} |
+ ]])
+ feed('={}')
+ screen:expect([[
+ {1:"} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ={2:{}}^ |
+ ]])
+ -- trying to insert a dictionary produces an error
+ feed('<CR>')
+ screen:expect([[
+ {1:"} |
+ {0:~ }|
+ {4: }|
+ ={2:{}} |
+ {5:E731: Using a Dictionary as a String} |
+ {6:Press ENTER or type command to continue}^ |
+ ]])
+
+ feed(':')
+ screen:expect([[
+ :^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- INSERT --} |
+ ]])
+ -- ending Insert mode should put the cursor back on the ':'
+ feed('<Esc>')
+ screen:expect([[
+ ^: |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua
index b5e45a86c1..c531c59fd1 100644
--- a/test/functional/legacy/eval_spec.lua
+++ b/test/functional/legacy/eval_spec.lua
@@ -613,15 +613,15 @@ describe('eval', function()
Executing call setreg(1, 2, 3, 4)
Vim(call):E118: Too many arguments for function: setreg
Executing call setreg([], 2)
- Vim(call):E730: using List as a String
+ Vim(call):E730: Using a List as a String
Executing call setreg(1, 2, [])
- Vim(call):E730: using List as a String
+ Vim(call):E730: Using a List as a String
Executing call setreg("/", ["1", "2"])
- Vim(call):E883: search pattern and expression register may not contain two or more lines
+ Vim(call):E883: Search pattern and expression register may not contain two or more lines
Executing call setreg("=", ["1", "2"])
- Vim(call):E883: search pattern and expression register may not contain two or more lines
+ Vim(call):E883: Search pattern and expression register may not contain two or more lines
Executing call setreg(1, ["", "", [], ""])
- Vim(call):E730: using List as a String]])
+ Vim(call):E730: Using a List as a String]])
end)
it('function name not starting with a capital', function()
diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua
index cea1d6ac30..c8e772f597 100644
--- a/test/functional/legacy/filechanged_spec.lua
+++ b/test/functional/legacy/filechanged_spec.lua
@@ -12,8 +12,8 @@ describe('file changed dialog', function()
before_each(function()
clear()
meths.ui_attach(80, 24, {})
- meths.set_option('autoread', false)
- meths.set_option('fsync', true)
+ meths.set_option_value('autoread', false, {})
+ meths.set_option_value('fsync', true, {})
end)
it('works', function()
diff --git a/test/functional/legacy/glob2regpat_spec.lua b/test/functional/legacy/glob2regpat_spec.lua
index 029d95206e..de304f3e4b 100644
--- a/test/functional/legacy/glob2regpat_spec.lua
+++ b/test/functional/legacy/glob2regpat_spec.lua
@@ -1,16 +1,10 @@
--- Tests for signs
-
local helpers = require('test.functional.helpers')(after_each)
-local clear, exc_exec = helpers.clear, helpers.exc_exec
+local clear = helpers.clear
local eq, eval = helpers.eq, helpers.eval
describe('glob2regpat()', function()
before_each(clear)
- it('handles invalid input', function()
- eq('Vim(call):E806: using Float as a String',
- exc_exec('call glob2regpat(1.33)'))
- end)
it('returns ^$ for empty input', function()
eq('^$', eval("glob2regpat('')"))
end)
diff --git a/test/functional/legacy/051_highlight_spec.lua b/test/functional/legacy/highlight_spec.lua
index d3f2897493..0a130f1607 100644
--- a/test/functional/legacy/051_highlight_spec.lua
+++ b/test/functional/legacy/highlight_spec.lua
@@ -1,5 +1,3 @@
--- Tests for ":highlight".
-
local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
local clear, feed = helpers.clear, helpers.feed
@@ -8,10 +6,11 @@ local eq = helpers.eq
local poke_eventloop = helpers.poke_eventloop
local exc_exec = helpers.exc_exec
local feed_command = helpers.feed_command
+local exec = helpers.exec
-describe(':highlight', function()
- setup(clear)
+before_each(clear)
+describe(':highlight', function()
it('is working', function()
local screen = Screen.new(35, 10)
screen:attach()
@@ -94,3 +93,30 @@ describe(':highlight', function()
Group3 xxx cleared]])
end)
end)
+
+describe('Visual selection highlight', function()
+ -- oldtest: Test_visual_sbr()
+ it("when 'showbreak' is set", function()
+ local screen = Screen.new(60, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {background = Screen.colors.LightGrey}, -- Visual
+ [2] = {bold = true}, -- ModeMsg
+ })
+ screen:attach()
+ exec([[
+ set showbreak=>
+ call setline(1, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.')
+ exe "normal! z1\<CR>"
+ ]])
+ feed('v$')
+ screen:expect([[
+ {0:>}{1:n, no sea takimata sanctus est Lorem ipsum dolor sit amet.}^ |
+ |
+ |
+ |
+ |
+ {2:-- VISUAL --} |
+ ]])
+ end)
+end)
diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua
new file mode 100644
index 0000000000..22d9247698
--- /dev/null
+++ b/test/functional/legacy/matchparen_spec.lua
@@ -0,0 +1,116 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local exec = helpers.exec
+local feed = helpers.feed
+
+describe('matchparen', function()
+ before_each(clear)
+
+ -- oldtest: Test_visual_block_scroll()
+ it('redraws properly after scrolling with scrolloff=1', function()
+ local screen = Screen.new(30, 7)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true},
+ [2] = {background = Screen.colors.LightGrey},
+ })
+
+ exec([[
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set scrolloff=1
+ call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
+ call cursor(5, 1)
+ ]])
+
+ feed('V<c-d><c-d>')
+ screen:expect([[
+ {2:{} |
+ {2:}} |
+ {2:{} |
+ {2:f} |
+ ^g |
+ } |
+ {1:-- VISUAL LINE --} |
+ ]])
+ end)
+
+ -- oldtest: Test_matchparen_clear_highlight()
+ it('matchparen highlight is cleared when switching buffer', function()
+ local screen = Screen.new(20, 5)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Cyan},
+ })
+ screen:attach()
+
+ local screen1 = [[
+ {1:^()} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]
+ local screen2 = [[
+ ^aa |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]
+
+ exec([[
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set hidden
+ call setline(1, ['()'])
+ normal 0
+ ]])
+ screen:expect(screen1)
+
+ exec([[
+ enew
+ exe "normal iaa\<Esc>0"
+ ]])
+ screen:expect(screen2)
+
+ feed('<C-^>')
+ screen:expect(screen1)
+
+ feed('<C-^>')
+ screen:expect(screen2)
+ end)
+
+ -- oldtest: Test_matchparen_pum_clear()
+ it('is cleared when completion popup is shown', function()
+ local screen = Screen.new(30, 9)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue};
+ [1] = {background = Screen.colors.Plum1};
+ [2] = {background = Screen.colors.Grey};
+ [3] = {bold = true};
+ [4] = {bold = true, foreground = Screen.colors.SeaGreen};
+ })
+
+ exec([[
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set completeopt=menuone
+ call setline(1, ['aa', 'aaa', 'aaaa', '(a)'])
+ call cursor(4, 3)
+ ]])
+
+ feed('i<C-X><C-N><C-N>')
+ screen:expect{grid=[[
+ aa |
+ aaa |
+ aaaa |
+ (aaa^) |
+ {1: aa }{0: }|
+ {2: aaa }{0: }|
+ {1: aaaa }{0: }|
+ {0:~ }|
+ {3:-- }{4:match 2 of 3} |
+ ]]}
+ end)
+end)
diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua
index 59839157ea..5f722e5190 100644
--- a/test/functional/legacy/memory_usage_spec.lua
+++ b/test/functional/legacy/memory_usage_spec.lua
@@ -11,14 +11,10 @@ local load_adjust = helpers.load_adjust
local write_file = helpers.write_file
local is_os = helpers.is_os
local is_ci = helpers.is_ci
-
-local function isasan()
- local version = eval('execute("version")')
- return version:match('-fsanitize=[a-z,]*address')
-end
+local is_asan = helpers.is_asan
clear()
-if isasan() then
+if is_asan() then
pending('ASAN build is difficult to estimate memory usage', function() end)
return
elseif is_os('win') then
diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua
index 71a53c8381..e0cc1dc79c 100644
--- a/test/functional/legacy/messages_spec.lua
+++ b/test/functional/legacy/messages_spec.lua
@@ -6,6 +6,7 @@ local exec = helpers.exec
local feed = helpers.feed
local meths = helpers.meths
local nvim_dir = helpers.nvim_dir
+local assert_alive = helpers.assert_alive
before_each(clear)
@@ -49,6 +50,71 @@ describe('messages', function()
]])
end)
+ -- oldtest: Test_message_not_cleared_after_mode()
+ it('clearing mode does not remove message', function()
+ screen = Screen.new(60, 10)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- ErrorMsg
+ })
+ screen:attach()
+ exec([[
+ nmap <silent> gx :call DebugSilent('normal')<CR>
+ vmap <silent> gx :call DebugSilent('visual')<CR>
+ function DebugSilent(arg)
+ echomsg "from DebugSilent" a:arg
+ endfunction
+ set showmode
+ set cmdheight=1
+ call setline(1, ['one', 'NoSuchFile', 'three'])
+ ]])
+
+ feed('gx')
+ screen:expect([[
+ ^one |
+ NoSuchFile |
+ three |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ from DebugSilent normal |
+ ]])
+
+ -- removing the mode message used to also clear the intended message
+ feed('vEgx')
+ screen:expect([[
+ ^one |
+ NoSuchFile |
+ three |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ from DebugSilent visual |
+ ]])
+
+ -- removing the mode message used to also clear the error message
+ command('set cmdheight=2')
+ feed('2GvEgf')
+ screen:expect([[
+ one |
+ NoSuchFil^e |
+ three |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ from DebugSilent visual |
+ {1:E447: Can't find file "NoSuchFile" in path} |
+ ]])
+ end)
+
describe('more prompt', function()
before_each(function()
command('set more')
@@ -361,9 +427,9 @@ describe('messages', function()
screen:attach()
command('cd '..nvim_dir)
- meths.set_option('shell', './shell-test')
- meths.set_option('shellcmdflag', 'REP 20')
- meths.set_option('shellxquote', '') -- win: avoid extra quotes
+ meths.set_option_value('shell', './shell-test', {})
+ meths.set_option_value('shellcmdflag', 'REP 20', {})
+ meths.set_option_value('shellxquote', '', {}) -- win: avoid extra quotes
-- display a page and go back, results in exactly the same view
feed([[:4 verbose echo system('foo')<CR>]])
@@ -661,6 +727,46 @@ describe('messages', function()
]])
end)
+ -- oldtest: Test_fileinfo_tabpage_cmdheight()
+ it("fileinfo works when 'cmdheight' has just decreased", function()
+ screen = Screen.new(40, 6)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [1] = {bold = true}; -- TabLineSel
+ [2] = {underline = true, background = Screen.colors.LightGrey}; -- TabLine
+ [3] = {reverse = true}; -- TabLineFill
+ })
+ screen:attach()
+
+ exec([[
+ set shortmess-=o
+ set shortmess-=O
+ set shortmess-=F
+ tabnew
+ set cmdheight=2
+ ]])
+ command('mode') -- FIXME: bottom is invalid after scrolling
+ screen:expect([[
+ {2: [No Name] }{1: [No Name] }{3: }{2:X}|
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ |
+ ]])
+
+ feed(':tabprev | edit Xfileinfo.txt<CR>')
+ screen:expect([[
+ {1: Xfileinfo.txt }{2: [No Name] }{3: }{2:X}|
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ "Xfileinfo.txt" [New] |
+ ]])
+ assert_alive()
+ end)
+
-- oldtest: Test_fileinfo_after_echo()
it('fileinfo does not overwrite echo message vim-patch:8.2.4156', function()
screen = Screen.new(40, 6)
@@ -668,6 +774,7 @@ describe('messages', function()
[0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
})
screen:attach()
+
exec([[
set shortmess-=F
@@ -681,6 +788,7 @@ describe('messages', function()
autocmd CursorHold * buf b.txt | w | echo "'b' written"
]])
+
command('set updatetime=50')
feed('0$')
screen:expect([[
@@ -693,4 +801,9 @@ describe('messages', function()
]])
os.remove('b.txt')
end)
+
+ it('no crash when truncating overlong message', function()
+ pcall(command, 'source test/old/testdir/crash/vim_msg_trunc_poc')
+ assert_alive()
+ end)
end)
diff --git a/test/functional/legacy/move_spec.lua b/test/functional/legacy/move_spec.lua
new file mode 100644
index 0000000000..855996da8f
--- /dev/null
+++ b/test/functional/legacy/move_spec.lua
@@ -0,0 +1,49 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local feed = helpers.feed
+local funcs = helpers.funcs
+
+before_each(clear)
+
+describe(':move', function()
+ -- oldtest: Test_move_undo()
+ it('redraws correctly when undone', function()
+ local screen = Screen.new(60, 10)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ })
+ screen:attach()
+
+ funcs.setline(1, {'First', 'Second', 'Third', 'Fourth'})
+ feed('gg:move +1<CR>')
+ screen:expect([[
+ Second |
+ ^First |
+ Third |
+ Fourth |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ :move +1 |
+ ]])
+
+ -- here the display would show the last few lines scrolled down
+ feed('u')
+ feed(':<Esc>')
+ screen:expect([[
+ ^First |
+ Second |
+ Third |
+ Fourth |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
+end)
diff --git a/test/functional/legacy/normal_spec.lua b/test/functional/legacy/normal_spec.lua
new file mode 100644
index 0000000000..ba875460f5
--- /dev/null
+++ b/test/functional/legacy/normal_spec.lua
@@ -0,0 +1,41 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local exec = helpers.exec
+
+before_each(clear)
+
+describe('normal', function()
+ -- oldtest: Test_normal_j_below_botline()
+ it([["j" does not skip lines when scrolling below botline and 'foldmethod' is not "manual"]], function()
+ local screen = Screen.new(40, 19)
+ screen:attach()
+ screen:set_default_attr_ids({{foreground = Screen.colors.Brown}})
+ exec([[
+ set number foldmethod=diff scrolloff=0
+ call setline(1, map(range(1, 9), 'repeat(v:val, 200)'))
+ norm Lj
+ ]])
+ screen:expect([[
+ {1: 2 }222222222222222222222222222222222222|
+ {1: }222222222222222222222222222222222222|
+ {1: }222222222222222222222222222222222222|
+ {1: }222222222222222222222222222222222222|
+ {1: }222222222222222222222222222222222222|
+ {1: }22222222222222222222 |
+ {1: 3 }333333333333333333333333333333333333|
+ {1: }333333333333333333333333333333333333|
+ {1: }333333333333333333333333333333333333|
+ {1: }333333333333333333333333333333333333|
+ {1: }333333333333333333333333333333333333|
+ {1: }33333333333333333333 |
+ {1: 4 }^444444444444444444444444444444444444|
+ {1: }444444444444444444444444444444444444|
+ {1: }444444444444444444444444444444444444|
+ {1: }444444444444444444444444444444444444|
+ {1: }444444444444444444444444444444444444|
+ {1: }44444444444444444444 |
+ |
+ ]])
+ end)
+end)
diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua
index bd14f3bc53..ce46ea013d 100644
--- a/test/functional/legacy/options_spec.lua
+++ b/test/functional/legacy/options_spec.lua
@@ -1,4 +1,4 @@
--- See also: src/nvim/testdir/test_options.vim
+-- See also: test/old/testdir/test_options.vim
local helpers = require('test.functional.helpers')(after_each)
local command, clear = helpers.command, helpers.clear
local source, expect = helpers.source, helpers.expect
diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua
index 63338b8dba..5c3f8a6f8c 100644
--- a/test/functional/legacy/prompt_buffer_spec.lua
+++ b/test/functional/legacy/prompt_buffer_spec.lua
@@ -3,10 +3,11 @@ local Screen = require('test.functional.ui.screen')
local feed = helpers.feed
local source = helpers.source
local clear = helpers.clear
-local feed_command = helpers.feed_command
+local command = helpers.command
local poke_eventloop = helpers.poke_eventloop
local meths = helpers.meths
local eq = helpers.eq
+local neq = helpers.neq
describe('prompt buffer', function()
local screen
@@ -15,65 +16,78 @@ describe('prompt buffer', function()
clear()
screen = Screen.new(25, 10)
screen:attach()
+ command('set laststatus=0 nohidden')
+ end)
+
+ local function source_script()
source([[
func TextEntered(text)
if a:text == "exit"
+ " Reset &modified to allow the buffer to be closed.
set nomodified
stopinsert
close
else
+ " Add the output above the current prompt.
call append(line("$") - 1, 'Command: "' . a:text . '"')
+ " Reset &modified to allow the buffer to be closed.
set nomodified
call timer_start(20, {id -> TimerFunc(a:text)})
endif
endfunc
func TimerFunc(text)
+ " Add the output above the current prompt.
call append(line("$") - 1, 'Result: "' . a:text .'"')
+ " Reset &modified to allow the buffer to be closed.
+ set nomodified
endfunc
func SwitchWindows()
call timer_start(0, {-> execute("wincmd p", "")})
endfunc
- ]])
- feed_command("set noshowmode | set laststatus=0")
- feed_command("call setline(1, 'other buffer')")
- feed_command("new")
- feed_command("set buftype=prompt")
- feed_command("call prompt_setcallback(bufnr(''), function('TextEntered'))")
- feed_command("eval bufnr('')->prompt_setprompt('cmd: ')")
- end)
- after_each(function()
- screen:detach()
- end)
-
- it('works', function()
+ call setline(1, "other buffer")
+ set nomodified
+ new
+ set buftype=prompt
+ call prompt_setcallback(bufnr(''), function("TextEntered"))
+ eval bufnr("")->prompt_setprompt("cmd: ")
+ startinsert
+ ]])
screen:expect([[
- ^ |
+ cmd: ^ |
~ |
~ |
~ |
- [Prompt] |
+ [Prompt] [+] |
other buffer |
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
- feed("i")
+ end
+
+ after_each(function()
+ screen:detach()
+ end)
+
+ -- oldtest: Test_prompt_basic()
+ it('works', function()
+ source_script()
feed("hello\n")
screen:expect([[
cmd: hello |
Command: "hello" |
Result: "hello" |
cmd: ^ |
- [Prompt] [+] |
+ [Prompt] |
other buffer |
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
feed("exit\n")
screen:expect([[
@@ -90,20 +104,9 @@ describe('prompt buffer', function()
]])
end)
+ -- oldtest: Test_prompt_editing()
it('editing', function()
- screen:expect([[
- ^ |
- ~ |
- ~ |
- ~ |
- [Prompt] |
- other buffer |
- ~ |
- ~ |
- ~ |
- |
- ]])
- feed("i")
+ source_script()
feed("hello<BS><BS>")
screen:expect([[
cmd: hel^ |
@@ -115,7 +118,7 @@ describe('prompt buffer', function()
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
feed("<Left><Left><Left><BS>-")
screen:expect([[
@@ -128,7 +131,7 @@ describe('prompt buffer', function()
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
feed("<C-O>lz")
screen:expect([[
@@ -141,7 +144,7 @@ describe('prompt buffer', function()
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
feed("<End>x")
screen:expect([[
@@ -154,7 +157,7 @@ describe('prompt buffer', function()
~ |
~ |
~ |
- |
+ -- INSERT -- |
]])
feed("<C-U>exit\n")
screen:expect([[
@@ -171,21 +174,9 @@ describe('prompt buffer', function()
]])
end)
+ -- oldtest: Test_prompt_switch_windows()
it('switch windows', function()
- feed_command("set showmode")
- feed("i")
- screen:expect([[
- cmd: ^ |
- ~ |
- ~ |
- ~ |
- [Prompt] [+] |
- other buffer |
- ~ |
- ~ |
- ~ |
- -- INSERT -- |
- ]])
+ source_script()
feed("<C-O>:call SwitchWindows()<CR>")
screen:expect{grid=[[
cmd: |
@@ -227,13 +218,49 @@ describe('prompt buffer', function()
]])
end)
+ -- oldtest: Test_prompt_while_writing_to_hidden_buffer()
it('keeps insert mode after aucmd_restbuf in callback', function()
+ source_script()
source [[
let s:buf = nvim_create_buf(1, 1)
call timer_start(0, {-> nvim_buf_set_lines(s:buf, -1, -1, 0, ['walrus'])})
- startinsert
]]
poke_eventloop()
- eq({ mode = "i", blocking = false }, meths.get_mode())
+ eq({ mode = 'i', blocking = false }, meths.get_mode())
+ end)
+
+ -- oldtest: Test_prompt_appending_while_hidden()
+ it('accessing hidden prompt buffer does not start insert mode', function()
+ local prev_win = meths.get_current_win()
+ source([[
+ new prompt
+ set buftype=prompt
+ set bufhidden=hide
+
+ func s:TextEntered(text)
+ if a:text == 'exit'
+ close
+ endif
+ let g:entered = a:text
+ endfunc
+ call prompt_setcallback(bufnr(), function('s:TextEntered'))
+
+ func DoAppend()
+ call appendbufline('prompt', '$', 'Test')
+ return ''
+ endfunc
+ ]])
+ feed('asomething<CR>')
+ eq('something', meths.get_var('entered'))
+ neq(prev_win, meths.get_current_win())
+ feed('exit<CR>')
+ eq(prev_win, meths.get_current_win())
+ eq({ mode = 'n', blocking = false }, meths.get_mode())
+ command('call DoAppend()')
+ eq({ mode = 'n', blocking = false }, meths.get_mode())
+ feed('i')
+ eq({ mode = 'i', blocking = false }, meths.get_mode())
+ command('call DoAppend()')
+ eq({ mode = 'i', blocking = false }, meths.get_mode())
end)
end)
diff --git a/test/functional/legacy/put_spec.lua b/test/functional/legacy/put_spec.lua
index 3ddf65490e..4a42a1c8a3 100644
--- a/test/functional/legacy/put_spec.lua
+++ b/test/functional/legacy/put_spec.lua
@@ -1,4 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local exec_lua = helpers.exec_lua
local meths = helpers.meths
@@ -42,4 +43,56 @@ describe('put', function()
bwipe!
]]
end)
+
+ -- oldtest: Test_put_other_window()
+ it('above topline in buffer in two splits', function()
+ local screen = Screen.new(80, 10)
+ screen:attach()
+ source([[
+ 40vsplit
+ 0put ='some text at the top'
+ put =' one more text'
+ put =' two more text'
+ put =' three more text'
+ put =' four more text'
+ ]])
+
+ screen:expect([[
+ some text at the top │some text at the top |
+ one more text │ one more text |
+ two more text │ two more text |
+ three more text │ three more text |
+ ^four more text │ four more text |
+ │ |
+ ~ │~ |
+ ~ │~ |
+ [No Name] [+] [No Name] [+] |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_put_in_last_displayed_line()
+ it('in last displayed line', function()
+ local screen = Screen.new(75, 10)
+ screen:attach()
+ source([[
+ autocmd CursorMoved * eval line('w$')
+ let @a = 'x'->repeat(&columns * 2 - 2)
+ eval range(&lines)->setline(1)
+ call feedkeys('G"ap')
+ ]])
+
+ screen:expect([[
+ 2 |
+ 3 |
+ 4 |
+ 5 |
+ 6 |
+ 7 |
+ 8 |
+ 9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx|
+ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx^x |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/legacy/scroll_opt_spec.lua b/test/functional/legacy/scroll_opt_spec.lua
new file mode 100644
index 0000000000..8ac1141c2b
--- /dev/null
+++ b/test/functional/legacy/scroll_opt_spec.lua
@@ -0,0 +1,1193 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local exec = helpers.exec
+local feed = helpers.feed
+local assert_alive = helpers.assert_alive
+
+before_each(clear)
+
+describe('smoothscroll', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 12)
+ screen:attach()
+ end)
+
+ -- oldtest: Test_CtrlE_CtrlY_stop_at_end()
+ it('disabled does not break <C-E> and <C-Y> stop at end', function()
+ exec([[
+ enew
+ call setline(1, ['one', 'two'])
+ set number
+ ]])
+ feed('<C-Y>')
+ screen:expect({any = " 1 ^one"})
+ feed('<C-E><C-E><C-E>')
+ screen:expect({any = " 2 ^two"})
+ end)
+
+ -- oldtest: Test_smoothscroll_CtrlE_CtrlY()
+ it('works with <C-E> and <C-E>', function()
+ exec([[
+ call setline(1, [ 'line one', 'word '->repeat(20), 'line three', 'long word '->repeat(7), 'line', 'line', 'line', ])
+ set smoothscroll scrolloff=5
+ :5
+ ]])
+ local s1 = [[
+ word word word word word word word word |
+ word word word word word word word word |
+ word word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ ^line |
+ line |
+ line |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s2 = [[
+ <<<d word word word word word word word |
+ word word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ ^line |
+ line |
+ line |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s3 = [[
+ <<<d word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ ^line |
+ line |
+ line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s4 = [[
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ line |
+ line |
+ ^line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s5 = [[
+ <<<d word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ line |
+ line |
+ ^line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s6 = [[
+ <<<d word word word word word word word |
+ word word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ line |
+ line |
+ ^line |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s7 = [[
+ word word word word word word word word |
+ word word word word word word word word |
+ word word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ line |
+ line |
+ ^line |
+ ~ |
+ ~ |
+ |
+ ]]
+ local s8 = [[
+ line one |
+ word word word word word word word word |
+ word word word word word word word word |
+ word word word word |
+ line three |
+ long word long word long word long word |
+ long word long word long word |
+ line |
+ line |
+ ^line |
+ ~ |
+ |
+ ]]
+ feed('<C-E>')
+ screen:expect(s1)
+ feed('<C-E>')
+ screen:expect(s2)
+ feed('<C-E>')
+ screen:expect(s3)
+ feed('<C-E>')
+ screen:expect(s4)
+ feed('<C-Y>')
+ screen:expect(s5)
+ feed('<C-Y>')
+ screen:expect(s6)
+ feed('<C-Y>')
+ screen:expect(s7)
+ feed('<C-Y>')
+ screen:expect(s8)
+ exec('set foldmethod=indent')
+ -- move the cursor so we can reuse the same dumps
+ feed('5G<C-E>')
+ screen:expect(s1)
+ feed('<C-E>')
+ screen:expect(s2)
+ feed('7G<C-Y>')
+ screen:expect(s7)
+ feed('<C-Y>')
+ screen:expect(s8)
+ end)
+
+ -- oldtest: Test_smoothscroll_multibyte()
+ it('works with multibyte characters', function()
+ screen:try_resize(40, 6)
+ exec([[
+ set scrolloff=0 smoothscroll
+ call setline(1, [repeat('ϛ', 45), repeat('2', 36)])
+ exe "normal G35l\<C-E>k"
+ ]])
+ screen:expect([[
+ ϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛϛ^ϛϛϛϛϛ|
+ ϛϛϛϛϛ |
+ 222222222222222222222222222222222222 |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_number()
+ it("works 'number' and 'cpo'+=n", function()
+ exec([[
+ call setline(1, [ 'one ' .. 'word '->repeat(20), 'two ' .. 'long word '->repeat(7), 'line', 'line', 'line', ])
+ set smoothscroll scrolloff=5
+ set splitkeep=topline
+ set number cpo+=n
+ :3
+ func g:DoRel()
+ set number relativenumber scrolloff=0
+ :%del
+ call setline(1, [ 'one', 'very long text '->repeat(12), 'three', ])
+ exe "normal 2Gzt\<C-E>"
+ endfunc
+ ]])
+ screen:expect([[
+ 1 one word word word word word word wo|
+ rd word word word word word word word wo|
+ rd word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long word |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<<word word word word word word word wo|
+ rd word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long word |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<<word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long word |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ exec('set cpo-=n')
+ screen:expect([[
+ <<< d word word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long wor|
+ d |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('<C-Y>')
+ screen:expect([[
+ <<< rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long wor|
+ d |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('<C-Y>')
+ screen:expect([[
+ 1 one word word word word word word wo|
+ rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long wor|
+ d |
+ 3 ^line |
+ 4 line |
+ 5 line |
+ ~ |
+ ~ |
+ |
+ ]])
+ exec('botright split')
+ feed('gg')
+ screen:expect([[
+ 1 one word word word word word word wo|
+ rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word@@@|
+ [No Name] [+] |
+ 1 ^one word word word word word word wo|
+ rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word lo|
+ ng word long word long word long @@@|
+ [No Name] [+] |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ 1 one word word word word word word wo|
+ rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word@@@|
+ [No Name] [+] |
+ <<< rd word word word word word word wor|
+ d word word word word word word^ |
+ 2 two long word long word long word lo|
+ ng word long word long word long wor|
+ d |
+ [No Name] [+] |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ 1 one word word word word word word wo|
+ rd word word word word word word wor|
+ d word word word word word word |
+ 2 two long word long word long word@@@|
+ [No Name] [+] |
+ <<< d word word word word word word^ |
+ 2 two long word long word long word lo|
+ ng word long word long word long wor|
+ d |
+ 3 line |
+ [No Name] [+] |
+ |
+ ]])
+ exec('close')
+ exec('call DoRel()')
+ screen:expect([[
+ 2<<<^ong text very long text very long te|
+ xt very long text very long text ver|
+ y long text very long text very long|
+ text very long text very long text |
+ 1 three |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ --No lines in buffer-- |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_list()
+ it("works with list mode", function()
+ screen:try_resize(40, 8)
+ exec([[
+ set smoothscroll scrolloff=0
+ set list
+ call setline(1, [ 'one', 'very long text '->repeat(12), 'three', ])
+ exe "normal 2Gzt\<C-E>"
+ ]])
+ screen:expect([[
+ <<<t very long text very long text very |
+ ^long text very long text very long text |
+ very long text very long text very long |
+ text very long text- |
+ three |
+ ~ |
+ ~ |
+ |
+ ]])
+ exec('set listchars+=precedes:#')
+ screen:expect([[
+ #ext very long text very long text very |
+ ^long text very long text very long text |
+ very long text very long text very long |
+ text very long text- |
+ three |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_diff_mode()
+ it("works with diff mode", function()
+ screen:try_resize(40, 8)
+ exec([[
+ let text = 'just some text here'
+ call setline(1, text)
+ set smoothscroll
+ diffthis
+ new
+ call setline(1, text)
+ set smoothscroll
+ diffthis
+ ]])
+ screen:expect([[
+ - ^just some text here |
+ ~ |
+ ~ |
+ [No Name] [+] |
+ - just some text here |
+ ~ |
+ [No Name] [+] |
+ |
+ ]])
+ feed('<C-Y>')
+ screen:expect_unchanged()
+ feed('<C-E>')
+ screen:expect_unchanged()
+ end)
+
+ -- oldtest: Test_smoothscroll_wrap_scrolloff_zero()
+ it("works with zero 'scrolloff'", function()
+ screen:try_resize(40, 8)
+ exec([[
+ call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7))
+ set smoothscroll scrolloff=0 display=
+ :3
+ ]])
+ screen:expect([[
+ <<<h some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ ^Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ |
+ ]])
+ feed('j')
+ screen:expect_unchanged()
+ -- moving cursor down - whole bottom line shows
+ feed('<C-E>j')
+ screen:expect_unchanged()
+ feed('G')
+ screen:expect_unchanged()
+ feed('4<C-Y>G')
+ screen:expect_unchanged()
+ -- moving cursor up right after the <<< marker - no need to show whole line
+ feed('2gj3l2k')
+ screen:expect([[
+ <<<^h some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ |
+ ]])
+ -- moving cursor up where the <<< marker is - whole top line shows
+ feed('2j02k')
+ screen:expect([[
+ ^Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ @ |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_wrap_long_line()
+ it("adjusts the cursor position in a long line", function()
+ screen:try_resize(40, 6)
+ exec([[
+ call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(30)) .. ' end', 'four'])
+ set smoothscroll scrolloff=0
+ normal 3G10|zt
+ ]])
+ -- scrolling up, cursor moves screen line down
+ screen:expect([[
+ Line with^ lots of text with lots of text|
+ with lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text with lots of te|
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<<th lot^s of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text with lots of te|
+ xt with lots of text with lots of text w|
+ |
+ ]])
+ feed('5<C-E>')
+ screen:expect([[
+ <<< lots ^of text with lots of text with |
+ lots of text with lots of text with lots|
+ of text with lots of text with lots of |
+ text with lots of text with lots of text|
+ with lots of text with lots of text wit|
+ |
+ ]])
+ -- scrolling down, cursor moves screen line up
+ feed('5<C-Y>')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text with lots of te|
+ xt with l^ots of text with lots of text w|
+ |
+ ]])
+ feed('<C-Y>')
+ screen:expect([[
+ Line with lots of text with lots of text|
+ with lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text wi^th lots of text with lots of te|
+ |
+ ]])
+ -- 'scrolloff' set to 1, scrolling up, cursor moves screen line down
+ exec('set scrolloff=1')
+ feed('10|<C-E>')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of^ text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text with lots of te|
+ xt with lots of text with lots of text w|
+ |
+ ]])
+ -- 'scrolloff' set to 1, scrolling down, cursor moves screen line up
+ feed('<C-E>gjgj<C-Y>')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text wi^th lots of text with lots of te|
+ xt with lots of text with lots of text w|
+ |
+ ]])
+ -- 'scrolloff' set to 2, scrolling up, cursor moves screen line down
+ exec('set scrolloff=2')
+ feed('10|<C-E>')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of tex^t with lots of text with lots o|
+ f text with lots of text with lots of te|
+ xt with lots of text with lots of text w|
+ |
+ ]])
+ -- 'scrolloff' set to 2, scrolling down, cursor moves screen line up
+ feed('<C-E>gj<C-Y>')
+ screen:expect_unchanged()
+ -- 'scrolloff' set to 0, move cursor down one line. Cursor should move properly,
+ -- and since this is a really long line, it will be put on top of the screen.
+ exec('set scrolloff=0')
+ feed('0j')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text end |
+ ^four |
+ |
+ ]])
+ -- Test zt/zz/zb that they work properly when a long line is above it
+ feed('zt')
+ screen:expect([[
+ ^four |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('zz')
+ screen:expect([[
+ <<<of text with lots of text with lots o|
+ f text with lots of text end |
+ ^four |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('zb')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text end |
+ ^four |
+ |
+ ]])
+ -- Repeat the step and move the cursor down again.
+ -- This time, use a shorter long line that is barely long enough to span more
+ -- than one window. Note that the cursor is at the bottom this time because
+ -- Vim prefers to do so if we are scrolling a few lines only.
+ exec("call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(10)) .. ' end', 'four'])")
+ -- Currently visible lines were replaced, test that the lines and cursor
+ -- are correctly displayed.
+ screen:expect_unchanged()
+ feed('3Gztj')
+ screen:expect_unchanged()
+ -- Repeat the step but this time start it when the line is smooth-scrolled by
+ -- one line. This tests that the offset calculation is still correct and
+ -- still end up scrolling down to the next line with cursor at bottom of
+ -- screen.
+ feed('3Gzt<C-E>j')
+ screen:expect([[
+ <<<th lots of text with lots of text wit|
+ h lots of text with lots of text with lo|
+ ts of text with lots of text with lots o|
+ f text with lots of text end |
+ fou^r |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_one_long_line()
+ it("scrolls correctly when moving the cursor", function()
+ screen:try_resize(40, 6)
+ exec([[
+ call setline(1, 'with lots of text '->repeat(7))
+ set smoothscroll scrolloff=0
+ ]])
+ local s1 = [[
+ ^with lots of text with lots of text with|
+ lots of text with lots of text with lot|
+ s of text with lots of text with lots of|
+ text |
+ ~ |
+ |
+ ]]
+ screen:expect(s1)
+ feed('<C-E>')
+ screen:expect([[
+ <<<ts of text with lots of text with lot|
+ ^s of text with lots of text with lots of|
+ text |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('0')
+ screen:expect(s1)
+ end)
+
+ -- oldtest: Test_smoothscroll_long_line_showbreak()
+ it("cursor is not one screen line too far down", function()
+ screen:try_resize(40, 6)
+ -- a line that spans four screen lines
+ exec("call setline(1, 'with lots of text in one line '->repeat(6))")
+ exec('set smoothscroll scrolloff=0 showbreak=+++\\ ')
+ local s1 = [[
+ ^with lots of text in one line with lots |
+ +++ of text in one line with lots of tex|
+ +++ t in one line with lots of text in o|
+ +++ ne line with lots of text in one lin|
+ +++ e with lots of text in one line |
+ |
+ ]]
+ screen:expect(s1)
+ feed('<C-E>')
+ screen:expect([[
+ +++ ^of text in one line with lots of tex|
+ +++ t in one line with lots of text in o|
+ +++ ne line with lots of text in one lin|
+ +++ e with lots of text in one line |
+ ~ |
+ |
+ ]])
+ feed('0')
+ screen:expect(s1)
+ end)
+
+ -- oldtest: Test_smoothscroll_marker_over_double_width_dump()
+ it('marker is drawn over double-width char correctly', function()
+ screen:try_resize(40, 6)
+ exec([[
+ call setline(1, 'a'->repeat(&columns) .. '口'->repeat(10))
+ setlocal smoothscroll
+ ]])
+ screen:expect([[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ 口口口口口口口口口口 |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<< 口口口口口口口^口 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_zero_width()
+ it("does not divide by zero with a narrow window", function()
+ screen:try_resize(12, 2)
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.Brown},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ })
+ exec([[
+ call setline(1, ['a'->repeat(100)])
+ set wrap smoothscroll number laststatus=0
+ wincmd v
+ wincmd v
+ wincmd v
+ wincmd v
+ ]])
+ screen:expect([[
+ {1: 1^ }│{1: }│{1: }│{1: }│{1: }|
+ |
+ ]])
+ feed('llllllllll<C-W>o')
+ screen:expect([[
+ {2:<<<}{1: }aa^aaaaaa|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_ins_lines()
+ it("does not unnecessarily insert lines", function()
+ screen:try_resize(40, 6)
+ exec([=[
+ set wrap smoothscroll scrolloff=0 conceallevel=2 concealcursor=nc
+ call setline(1, [
+ \'line one' .. 'with lots of text in one line '->repeat(2),
+ \'line two',
+ \'line three',
+ \'line four',
+ \'line five'
+ \])
+ ]=])
+ feed('<C-E>gjgk')
+ screen:expect([[
+ <<<lots of text in one line^ |
+ line two |
+ line three |
+ line four |
+ line five |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_cursormoved_line()
+ it("does not place the cursor in the command line", function()
+ screen:try_resize(40, 6)
+ exec([=[
+ set smoothscroll
+ call setline(1, [
+ \'',
+ \'_'->repeat(&lines * &columns),
+ \(('_')->repeat(&columns - 2) .. 'xxx')->repeat(2)
+ \])
+ autocmd CursorMoved * eval [line('w0'), line('w$')]
+ call search('xxx')
+ ]=])
+ screen:expect([[
+ <<<_____________________________________|
+ ________________________________________|
+ ______________________________________^xx|
+ x______________________________________x|
+ xx |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_eob()
+ it("does not scroll halfway at end of buffer", function()
+ screen:try_resize(40, 10)
+ exec([[
+ set smoothscroll
+ call setline(1, ['']->repeat(100))
+ norm G
+ ]])
+ -- does not scroll halfway when scrolling to end of buffer
+ screen:expect([[
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ ^ |
+ |
+ ]])
+ exec("call setline(92, 'a'->repeat(100))")
+ feed('<C-B>G')
+ -- cursor is not placed below window
+ screen:expect([[
+ <<<aaaaaaaaaaaaaaaaa |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ ^ |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_incsearch()
+ it("does not reset skipcol when doing incremental search on the same word", function()
+ screen:try_resize(40, 8)
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.Brown},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ [3] = {background = Screen.colors.Yellow1},
+ [4] = {reverse = true},
+ })
+ exec([[
+ set smoothscroll number scrolloff=0 incsearch
+ call setline(1, repeat([''], 20))
+ call setline(11, repeat('a', 100))
+ call setline(14, 'bbbb')
+ ]])
+ feed('/b')
+ screen:expect([[
+ {2:<<<}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+ {1: 12 } |
+ {1: 13 } |
+ {1: 14 }{4:b}{3:bbb} |
+ {1: 15 } |
+ {1: 16 } |
+ {1: 17 } |
+ /b^ |
+ ]])
+ feed('b')
+ screen:expect([[
+ {2:<<<}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+ {1: 12 } |
+ {1: 13 } |
+ {1: 14 }{4:bb}{3:bb} |
+ {1: 15 } |
+ {1: 16 } |
+ {1: 17 } |
+ /bb^ |
+ ]])
+ feed('b')
+ screen:expect([[
+ {2:<<<}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+ {1: 12 } |
+ {1: 13 } |
+ {1: 14 }{4:bbb}b |
+ {1: 15 } |
+ {1: 16 } |
+ {1: 17 } |
+ /bbb^ |
+ ]])
+ feed('b')
+ screen:expect([[
+ {2:<<<}{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaa |
+ {1: 12 } |
+ {1: 13 } |
+ {1: 14 }{4:bbbb} |
+ {1: 15 } |
+ {1: 16 } |
+ {1: 17 } |
+ /bbbb^ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_multi_skipcol()
+ it('scrolling multiple lines and stopping at non-zero skipcol', function()
+ screen:try_resize(40, 10)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue, bold = true},
+ [1] = {background = Screen.colors.Grey90},
+ })
+ exec([[
+ setlocal cursorline scrolloff=0 smoothscroll
+ call setline(1, repeat([''], 8))
+ call setline(3, repeat('a', 50))
+ call setline(4, repeat('a', 50))
+ call setline(7, 'bbb')
+ call setline(8, 'ccc')
+ redraw
+ ]])
+ screen:expect([[
+ {1:^ }|
+ |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaa |
+ |
+ |
+ bbb |
+ |
+ ]])
+ feed('3<C-E>')
+ screen:expect([[
+ {0:<<<}{1:aaaaaa^a }|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaa |
+ |
+ |
+ bbb |
+ ccc |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ feed('2<C-E>')
+ screen:expect([[
+ {0:<<<}{1:aaaaaa^a }|
+ |
+ |
+ bbb |
+ ccc |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_zero_width_scroll_cursor_bot()
+ it('does not divide by zero in zero-width window', function()
+ screen:try_resize(40, 19)
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.Brown}; -- LineNr
+ [2] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [3] = {bold = true, reverse = true}; -- StatusLine
+ [4] = {reverse = true}; -- StatusLineNC
+ })
+ exec([[
+ silent normal yy
+ silent normal 19p
+ set cpoptions+=n
+ vsplit
+ vertical resize 0
+ set foldcolumn=1
+ set number
+ set smoothscroll
+ silent normal 20G
+ ]])
+ screen:expect([[
+ {1: }│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:@}│ |
+ {2:^@}│ |
+ {3:< }{4:[No Name] [+] }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_cursor_top()
+ it('resets skipcol when scrolling cursor to top', function()
+ screen:try_resize(40, 12)
+ exec([[
+ set smoothscroll scrolloff=2
+ new | 11resize | wincmd j
+ call setline(1, ['line1', 'line2', 'line3'->repeat(20), 'line4'])
+ exe "norm G3\<C-E>k"
+ ]])
+ screen:expect([[
+ |
+ [No Name] |
+ line1 |
+ line2 |
+ ^line3line3line3line3line3line3line3line3|
+ line3line3line3line3line3line3line3line3|
+ line3line3line3line3 |
+ line4 |
+ ~ |
+ ~ |
+ [No Name] [+] |
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_smoothscroll_crash()
+ it('does not crash with small window and cpo+=n', function()
+ screen:try_resize(40, 12)
+ exec([[
+ 20 new
+ vsp
+ put =repeat('aaaa', 20)
+ set nu fdc=1 smoothscroll cpo+=n
+ vert resize 0
+ exe "norm! 0\<c-e>"
+ ]])
+ feed('2<C-E>')
+ assert_alive()
+ end)
+
+ it("works with virt_lines above and below", function()
+ screen:try_resize(55, 7)
+ exec([=[
+ call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(3))
+ set smoothscroll
+ let ns = nvim_create_namespace('')
+ call nvim_buf_set_extmark(0, ns, 0, 0, {'virt_lines':[[['virt_below1']]]})
+ call nvim_buf_set_extmark(0, ns, 1, 0, {'virt_lines':[[['virt_above1']]],'virt_lines_above':1})
+ call nvim_buf_set_extmark(0, ns, 1, 0, {'virt_lines':[[['virt_below2']]]})
+ call nvim_buf_set_extmark(0, ns, 2, 0, {'virt_lines':[[['virt_above2']]],'virt_lines_above':1})
+ norm ggL
+ ]=])
+ screen:expect([[
+ Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ virt_below1 |
+ virt_above1 |
+ ^Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<<e text with some text with some text with some text |
+ virt_below1 |
+ virt_above1 |
+ ^Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ virt_below2 |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ virt_below1 |
+ virt_above1 |
+ ^Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ virt_below2 |
+ virt_above2 |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ virt_above1 |
+ ^Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ virt_below2 |
+ virt_above2 |
+ Line with some text with some text with some text wi@@@|
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ ^Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ virt_below2 |
+ virt_above2 |
+ Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ <<<e text with some text with some text with some tex^t |
+ virt_below2 |
+ virt_above2 |
+ Line with some text with some text with some text with |
+ some text with some text with some text with some text |
+ ~ |
+ |
+ ]])
+ end)
+
+ it('<<< marker shows with tabline, winbar and splits', function()
+ screen:try_resize(40, 12)
+ exec([[
+ call setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7))
+ set smoothscroll scrolloff=0
+ norm sj
+ ]])
+ screen:expect([[
+ <<<e text with some text with some text |
+ with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ [No Name] [+] |
+ <<<e text with some text with some text |
+ ^with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some te@@@|
+ [No Name] [+] |
+ |
+ ]])
+ exec('set showtabline=2')
+ feed('<C-E>')
+ screen:expect([[
+ 2+ [No Name] |
+ <<<e text with some text with some text |
+ with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some text |
+ with some text with some text |
+ [No Name] [+] |
+ <<<e text with some text with some text |
+ ^with some text with some text |
+ Line with some text with some text wi@@@|
+ [No Name] [+] |
+ |
+ ]])
+ exec('set winbar=winbar')
+ feed('<C-w>k<C-E>')
+ screen:expect([[
+ 2+ [No Name] |
+ winbar |
+ <<<e text with some text with some text |
+ ^with some text with some text |
+ Line with some text with some text with |
+ some text with some text with some te@@@|
+ [No Name] [+] |
+ winbar |
+ <<<e text with some text with some text |
+ with some text with some text |
+ [No Name] [+] |
+ |
+ ]])
+ end)
+
+ it('works with very long line', function()
+ screen:set_default_attr_ids({
+ [1] = {foreground = Screen.colors.Brown},
+ [2] = {foreground = Screen.colors.Blue1, bold = true},
+ })
+ exec([[
+ edit test/functional/fixtures/bigfile_oneline.txt
+ setlocal smoothscroll number
+ ]])
+ screen:expect([[
+ {1: 1 }^0000;<control>;Cc;0;BN;;;;;N;NULL;;;|
+ {1: }; 0001;<control>;Cc;0;BN;;;;;N;START|
+ {1: } OF HEADING;;;; 0002;<control>;Cc;0;|
+ {1: }BN;;;;;N;START OF TEXT;;;; 0003;<con|
+ {1: }trol>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ {1: }0004;<control>;Cc;0;BN;;;;;N;END OF |
+ {1: }TRANSMISSION;;;; 0005;<control>;Cc;0|
+ {1: };BN;;;;;N;ENQUIRY;;;; 0006;<control>|
+ {1: };Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; 0007;|
+ {1: }<control>;Cc;0;BN;;;;;N;BELL;;;; 000|
+ {1: }8;<control>;Cc;0;BN;;;;;N;BACKSPACE;|
+ |
+ ]])
+ feed('j')
+ screen:expect([[
+ {2:<<<}{1: }CJK COMPATIBILITY IDEOGRAPH-2F91F;Lo|
+ {1: };0;L;243AB;;;;N;;;;; 2F920;CJK COMPA|
+ {1: }TIBILITY IDEOGRAPH-2F920;Lo;0;L;7228|
+ {1: };;;;N;;;;; 2F921;CJK COMPATIBILITY I|
+ {1: }DEOGRAPH-2F921;Lo;0;L;7235;;;;N;;;;;|
+ {1: } 2F922;CJK COMPATIBILITY IDEOGRAPH-2|
+ {1: }F922;Lo;0;L;7250;;;;N;;;;; |
+ {1: 2 }^2F923;CJK COMPATIBILITY IDEOGRAPH-2F|
+ {1: }923;Lo;0;L;24608;;;;N;;;;; |
+ {1: 3 }2F924;CJK COMPATIBILITY IDEOGRAPH-2F|
+ {1: }924;Lo;0;L;7280;;;;N;;;;; |
+ |
+ ]])
+ end)
+end)
diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua
index 3f1f85cf28..25620f5262 100644
--- a/test/functional/legacy/search_spec.lua
+++ b/test/functional/legacy/search_spec.lua
@@ -290,8 +290,8 @@ describe('search cmdline', function()
-- First match
feed('/thei')
screen:expect([[
+ 3 the |
4 {inc:thei}r |
- 5 there |
/thei^ |
]])
-- Match from initial cursor position when modifying search
@@ -642,7 +642,7 @@ describe('search cmdline', function()
end)
-- oldtest: Test_incsearch_substitute_dump2()
- it('detects empty pattern properly vim-patch:8.2.2295', function()
+ it('incsearch detects empty pattern properly vim-patch:8.2.2295', function()
screen:try_resize(70, 6)
exec([[
set incsearch hlsearch scrolloff=0
@@ -675,6 +675,46 @@ describe('search cmdline', function()
:1,5s/\v|^ |
]])
end)
+
+ -- oldtest: Test_incsearch_restore_view()
+ it('incsearch restores viewport', function()
+ screen:try_resize(20, 6)
+ exec([[
+ set incsearch nohlsearch
+ setlocal scrolloff=0 smoothscroll
+ call setline(1, [join(range(25), ' '), '', '', '', '', 'xxx'])
+ call feedkeys("2\<C-E>", 't')
+ ]])
+ local s = [[
+ {tilde:<<<} 18 19 20 21 22 2|
+ ^3 24 |
+ |
+ |
+ |
+ |
+ ]]
+ screen:expect(s)
+ feed('/xx')
+ screen:expect([[
+ |
+ |
+ |
+ |
+ {inc:xx}x |
+ /xx^ |
+ ]])
+ feed('x')
+ screen:expect([[
+ |
+ |
+ |
+ |
+ {inc:xxx} |
+ /xxx^ |
+ ]])
+ feed('<Esc>')
+ screen:expect(s)
+ end)
end)
describe('Search highlight', function()
diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua
index 9fcf798836..bd5ab68e5c 100644
--- a/test/functional/legacy/search_stat_spec.lua
+++ b/test/functional/legacy/search_stat_spec.lua
@@ -1,7 +1,6 @@
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
@@ -11,8 +10,9 @@ describe('search stat', function()
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
+ [3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}, -- Folded
[4] = {reverse = true}, -- IncSearch, TabLineFill
+ [5] = {foreground = Screen.colors.Red}, -- WarningMsg
})
screen:attach()
end)
@@ -80,12 +80,11 @@ describe('search stat', function()
{1:~ }|
/foo [1/2] |
]])
+ -- Note: there is an intermediate state where the search stat disappears.
feed('n')
- poke_eventloop()
- screen:expect_unchanged()
+ screen:expect_unchanged(true)
feed('n')
- poke_eventloop()
- screen:expect_unchanged()
+ screen:expect_unchanged(true)
end)
-- oldtest: Test_search_stat_then_gd()
@@ -185,4 +184,57 @@ describe('search stat', function()
/abc^ |
]])
end)
+
+ -- oldtest: Test_search_stat_backwards()
+ it('when searching backwards', function()
+ screen:try_resize(60, 10)
+ exec([[
+ set shm-=S
+ call setline(1, ['test', ''])
+ ]])
+
+ feed('*')
+ screen:expect([[
+ {2:^test} |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ /\<test\> [1/1] |
+ ]])
+
+ feed('N')
+ screen:expect([[
+ {2:^test} |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ?\<test\> [1/1] |
+ ]])
+
+ command('set shm+=S')
+ feed('N')
+ -- shows "Search Hit Bottom.."
+ screen:expect([[
+ {2:^test} |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {5:search hit TOP, continuing at BOTTOM} |
+ ]])
+ end)
end)
diff --git a/test/functional/legacy/vimscript_spec.lua b/test/functional/legacy/vimscript_spec.lua
index f59a87f824..16a1080396 100644
--- a/test/functional/legacy/vimscript_spec.lua
+++ b/test/functional/legacy/vimscript_spec.lua
@@ -12,7 +12,7 @@ describe('Vim script', function()
it('Error when if/for/while/try/function is nested too deep',function()
local screen = Screen.new(80, 24)
screen:attach()
- meths.set_option('laststatus', 2)
+ meths.set_option_value('laststatus', 2, {})
exec([[
" Deep nesting of if ... endif
func Test1()
diff --git a/test/functional/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua
deleted file mode 100644
index 1a08fb4c0e..0000000000
--- a/test/functional/legacy/visual_mode_spec.lua
+++ /dev/null
@@ -1,79 +0,0 @@
-local helpers = require('test.functional.helpers')(after_each)
-
-local Screen = require('test.functional.ui.screen')
-local clear = helpers.clear
-local feed = helpers.feed
-local exec = helpers.exec
-
-before_each(clear)
-
-describe('visual line mode', function()
- -- oldtest: Test_visual_block_scroll()
- it('redraws properly after scrolling with matchparen loaded and scrolloff=1', function()
- local screen = Screen.new(30, 7)
- screen:attach()
- screen:set_default_attr_ids({
- [1] = {bold = true},
- [2] = {background = Screen.colors.LightGrey},
- })
-
- exec([[
- source $VIMRUNTIME/plugin/matchparen.vim
- set scrolloff=1
- call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
- call cursor(5, 1)
- ]])
-
- feed('V<c-d><c-d>')
- screen:expect([[
- {2:{} |
- {2:}} |
- {2:{} |
- {2:f} |
- ^g |
- } |
- {1:-- VISUAL LINE --} |
- ]])
- end)
-end)
-
-describe('visual block mode', function()
- -- oldtest: Test_visual_block_with_virtualedit()
- it('shows selection correctly with virtualedit=block', function()
- local screen = Screen.new(30, 7)
- screen:set_default_attr_ids({
- [1] = {bold = true}, -- ModeMsg
- [2] = {background = Screen.colors.LightGrey}, -- Visual
- [3] = {foreground = Screen.colors.Blue, bold = true} -- NonText
- })
- screen:attach()
-
- exec([[
- call setline(1, ['aaaaaa', 'bbbb', 'cc'])
- set virtualedit=block
- normal G
- ]])
-
- feed('<C-V>gg$')
- screen:expect([[
- {2:aaaaaa}^ |
- {2:bbbb } |
- {2:cc } |
- {3:~ }|
- {3:~ }|
- {3:~ }|
- {1:-- VISUAL BLOCK --} |
- ]])
-
- feed('<Esc>gg<C-V>G$')
- screen:expect([[
- {2:aaaaaa } |
- {2:bbbb } |
- {2:cc}^ {2: } |
- {3:~ }|
- {3:~ }|
- {3:~ }|
- {1:-- VISUAL BLOCK --} |
- ]])
- end)
-end)
diff --git a/test/functional/legacy/visual_spec.lua b/test/functional/legacy/visual_spec.lua
new file mode 100644
index 0000000000..629fab5eb5
--- /dev/null
+++ b/test/functional/legacy/visual_spec.lua
@@ -0,0 +1,69 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local feed = helpers.feed
+local exec = helpers.exec
+
+before_each(clear)
+
+describe('Visual highlight', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(50, 6)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue, bold = true}, -- NonText
+ [1] = {bold = true}, -- ModeMsg
+ [2] = {background = Screen.colors.LightGrey}, -- Visual
+ })
+ screen:attach()
+ end)
+
+ -- oldtest: Test_visual_block_with_virtualedit()
+ it('shows selection correctly with virtualedit=block', function()
+ exec([[
+ call setline(1, ['aaaaaa', 'bbbb', 'cc'])
+ set virtualedit=block
+ normal G
+ ]])
+
+ feed('<C-V>gg$')
+ screen:expect([[
+ {2:aaaaaa}^ |
+ {2:bbbb } |
+ {2:cc } |
+ {0:~ }|
+ {0:~ }|
+ {1:-- VISUAL BLOCK --} |
+ ]])
+
+ feed('<Esc>gg<C-V>G$')
+ screen:expect([[
+ {2:aaaaaa } |
+ {2:bbbb } |
+ {2:cc}^ {2: } |
+ {0:~ }|
+ {0:~ }|
+ {1:-- VISUAL BLOCK --} |
+ ]])
+ end)
+
+ -- oldtest: Test_visual_hl_with_showbreak()
+ it("with cursor at end of screen line and 'showbreak'", function()
+ exec([[
+ setlocal showbreak=+
+ call setline(1, repeat('a', &columns + 10))
+ normal g$v4lo
+ ]])
+
+ screen:expect([[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^a|
+ {0:+}{2:aaaa}aaaaaa |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {1:-- VISUAL --} |
+ ]])
+ end)
+end)
diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua
index 0e9775060d..979b46ae47 100644
--- a/test/functional/legacy/window_cmd_spec.lua
+++ b/test/functional/legacy/window_cmd_spec.lua
@@ -3,8 +3,37 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local exec = helpers.exec
local exec_lua = helpers.exec_lua
+local command = helpers.command
local feed = helpers.feed
+-- oldtest: Test_window_cmd_ls0_split_scrolling()
+it('scrolling with laststatus=0 and :botright split', function()
+ clear('--cmd', 'set ruler')
+ local screen = Screen.new(40, 10)
+ screen:set_default_attr_ids({
+ [1] = {reverse = true}, -- StatusLineNC
+ })
+ screen:attach()
+ exec([[
+ set laststatus=0
+ call setline(1, range(1, 100))
+ normal! G
+ ]])
+ command('botright split')
+ screen:expect([[
+ 97 |
+ 98 |
+ 99 |
+ 100 |
+ {1:[No Name] [+] 100,1 Bot}|
+ 97 |
+ 98 |
+ 99 |
+ ^100 |
+ 100,1 Bot |
+ ]])
+end)
+
describe('splitkeep', function()
local screen
@@ -14,6 +43,61 @@ describe('splitkeep', function()
screen:attach()
end)
+ -- oldtest: Test_splitkeep_cursor()
+ it('does not adjust cursor in window that did not change size', function()
+ screen:try_resize(75, 8)
+ -- FIXME: bottom window is different without the "vsplit | close"
+ exec([[
+ vsplit | close
+ set scrolloff=5
+ set splitkeep=screen
+ autocmd CursorMoved * wincmd p | wincmd p
+ call setline(1, range(1, 200))
+ func CursorEqualize()
+ call cursor(100, 1)
+ wincmd =
+ endfunc
+ wincmd s
+ call CursorEqualize()
+ ]])
+
+ screen:expect([[
+ 99 |
+ ^100 |
+ 101 |
+ [No Name] [+] |
+ 5 |
+ 6 |
+ [No Name] [+] |
+ |
+ ]])
+
+ feed('j')
+ screen:expect([[
+ 100 |
+ ^101 |
+ 102 |
+ [No Name] [+] |
+ 5 |
+ 6 |
+ [No Name] [+] |
+ |
+ ]])
+
+ command('set scrolloff=0')
+ feed('G')
+ screen:expect([[
+ 198 |
+ 199 |
+ ^200 |
+ [No Name] [+] |
+ 5 |
+ 6 |
+ [No Name] [+] |
+ |
+ ]])
+ end)
+
-- oldtest: Test_splitkeep_callback()
it('does not scroll when split in callback', function()
exec([[
@@ -116,6 +200,7 @@ describe('splitkeep', function()
-- oldtest: Test_splitkeep_fold()
it('does not scroll when window has closed folds', function()
exec([[
+ set commentstring=/*%s*/
set splitkeep=screen
set foldmethod=marker
set number
@@ -224,4 +309,29 @@ describe('splitkeep', function()
|
]])
end)
+
+ -- oldtest: Test_splitkeep_skipcol()
+ it('skipcol is not reset unnecessarily and is copied to new window', function()
+ screen:try_resize(40, 12)
+ exec([[
+ set splitkeep=topline smoothscroll splitbelow scrolloff=0
+ call setline(1, 'with lots of text in one line '->repeat(6))
+ norm 2
+ wincmd s
+ ]])
+ screen:expect([[
+ <<<e line with lots of text in one line |
+ with lots of text in one line with lots |
+ of text in one line |
+ ~ |
+ [No Name] [+] |
+ <<<e line with lots of text in one line |
+ ^with lots of text in one line with lots |
+ of text in one line |
+ ~ |
+ ~ |
+ [No Name] [+] |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua
index 03832978a4..d808693a9e 100644
--- a/test/functional/lua/api_spec.lua
+++ b/test/functional/lua/api_spec.lua
@@ -9,6 +9,7 @@ local eval = helpers.eval
local NIL = helpers.NIL
local eq = helpers.eq
local exec_lua = helpers.exec_lua
+local pcall_err = helpers.pcall_err
before_each(clear)
@@ -33,7 +34,7 @@ describe('luaeval(vim.api.…)', function()
describe('with errors', function()
it('transforms API error from nvim_buf_set_lines into lua error', function()
funcs.setline(1, {"abc", "def", "a\nb", "ttt"})
- eq({false, 'String cannot contain newlines'},
+ eq({false, "'replacement string' item contains newlines"},
funcs.luaeval('{pcall(vim.api.nvim_buf_set_lines, 1, 1, 2, false, {"b\\na"})}'))
end)
@@ -103,9 +104,9 @@ describe('luaeval(vim.api.…)', function()
eq(NIL, funcs.luaeval('vim.api.nvim__id(nil)'))
-- API strings from Blobs can work as NUL-terminated C strings
- eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ',
+ eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ""',
exc_exec('call nvim_eval(v:_null_blob)'))
- eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ',
+ eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ""',
exc_exec('call nvim_eval(0z)'))
eq(1, eval('nvim_eval(0z31)'))
@@ -171,9 +172,32 @@ describe('luaeval(vim.api.…)', function()
eq(4, eval([[type(luaeval('vim.api.nvim__id_dictionary({})'))]]))
end)
+ it('converts booleans in positional args', function()
+ eq({''}, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, false) ]])
+ eq({''}, exec_lua [[ return vim.api.nvim_buf_get_lines(0, 0, 10, nil) ]])
+ eq('Index out of bounds', pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, true) ]]))
+ eq('Index out of bounds', pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 1) ]]))
+
+ -- this follows lua conventions for bools (not api convention for Boolean)
+ eq('Index out of bounds', pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, 0) ]]))
+ eq('Index out of bounds', pcall_err(exec_lua, [[ return vim.api.nvim_buf_get_lines(0, 0, 10, {}) ]]))
+ end)
+
+ it('converts booleans in optional args', function()
+ eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=false}) ]])
+ eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {}) ]]) -- same as {output=nil}
+
+ -- API conventions (not lua conventions): zero is falsy
+ eq({}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=0}) ]])
+
+ eq({output='foobar'}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=true}) ]])
+ eq({output='foobar'}, exec_lua [[ return vim.api.nvim_exec2("echo 'foobar'", {output=1}) ]])
+ eq([[Invalid 'output': not a boolean]], pcall_err(exec_lua, [[ return vim.api.nvim_exec2("echo 'foobar'", {output={}}) ]]))
+ end)
+
it('errors out correctly when working with API', function()
-- Conversion errors
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua table',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'obj': Cannot convert given Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]])))
-- Errors in number of arguments
eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument',
@@ -183,32 +207,32 @@ describe('luaeval(vim.api.…)', function()
eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 2 arguments',
remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2, 3)")]])))
-- Error in argument types
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua string',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'name': Expected Lua string]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_var(1, 2)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua number',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'start': Expected Lua number]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Number is not integral',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'start': Number is not integral]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected Lua number',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'window': Expected Lua number]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim_win_is_valid(nil)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'flt': Expected Lua number]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'flt': Expected Float-like Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_float({[vim.type_idx]=vim.types.dictionary})")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'arr': Expected Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_array(1)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'arr': Expected Array-like Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_array({[vim.type_idx]=vim.types.dictionary})")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Unexpected type',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Dict-like Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]])))
- eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table',
+ eq([[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected Lua table]],
remove_trace(exc_exec([[call luaeval("vim.api.nvim_set_keymap('', '', '', '')")]])))
-- TODO: check for errors with Tabpage argument
diff --git a/test/functional/lua/base64_spec.lua b/test/functional/lua/base64_spec.lua
new file mode 100644
index 0000000000..f0d112c23e
--- /dev/null
+++ b/test/functional/lua/base64_spec.lua
@@ -0,0 +1,105 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+local pcall_err = helpers.pcall_err
+local matches = helpers.matches
+
+describe('vim.base64', function()
+ before_each(clear)
+
+ local function encode(s)
+ return exec_lua([[return vim.base64.encode(...)]], s)
+ end
+
+ local function decode(s)
+ return exec_lua([[return vim.base64.decode(...)]], s)
+ end
+
+ it('works', function()
+ local values = {
+ '',
+ 'Many hands make light work.',
+ [[
+ Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in
+ my purse, and nothing particular to interest me on shore, I thought I would sail about a
+ little and see the watery part of the world.
+ ]],
+ [[
+ It is a truth universally acknowledged, that a single man in possession of a good fortune,
+ must be in want of a wife.
+ ]],
+ 'Happy families are all alike; every unhappy family is unhappy in its own way.',
+ 'ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя',
+ 'ÅÍÎÏ˝ÓÔÒÚÆ☃',
+ '𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆',
+ '👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦',
+ 'مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،',
+ [[
+ Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣
+ ̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰
+ ̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟
+ ̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕
+ Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮
+ ]],
+ }
+
+ for _, v in ipairs(values) do
+ eq(v, decode(encode(v)))
+ end
+
+ -- Explicitly check encoded output
+ eq('VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZwo=', encode('The quick brown fox jumps over the lazy dog\n'))
+
+ -- Test vectors from rfc4648
+ local rfc4648 = {
+ { '', '' },
+ { 'f', 'Zg==', },
+ { 'fo', 'Zm8=' },
+ { 'foo', 'Zm9v' },
+ { 'foob', 'Zm9vYg==' },
+ { 'fooba', 'Zm9vYmE=' },
+ { 'foobar', 'Zm9vYmFy' },
+ }
+
+ for _, v in ipairs(rfc4648) do
+ local input = v[1]
+ local output = v[2]
+ eq(output, encode(input))
+ eq(input, decode(output))
+ end
+ end)
+
+ it('detects invalid input', function()
+ local invalid = {
+ 'A',
+ 'AA',
+ 'AAA',
+ 'A..A',
+ 'AA=A',
+ 'AA/=',
+ 'A/==',
+ 'A===',
+ '====',
+ 'Zm9vYmFyZm9vYmFyA..A',
+ 'Zm9vYmFyZm9vYmFyAA=A',
+ 'Zm9vYmFyZm9vYmFyAA/=',
+ 'Zm9vYmFyZm9vYmFyA/==',
+ 'Zm9vYmFyZm9vYmFyA===',
+ 'A..AZm9vYmFyZm9vYmFy',
+ 'Zm9vYmFyZm9vAA=A',
+ 'Zm9vYmFyZm9vAA/=',
+ 'Zm9vYmFyZm9vA/==',
+ 'Zm9vYmFyZm9vA===',
+ }
+
+ for _, v in ipairs(invalid) do
+ eq('Invalid input', pcall_err(decode, v))
+ end
+
+ eq('Expected 1 argument', pcall_err(encode))
+ eq('Expected 1 argument', pcall_err(decode))
+ matches('expected string', pcall_err(encode, 42))
+ matches('expected string', pcall_err(decode, 42))
+ end)
+end)
diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua
index 2fd44b8b5f..51e4548edb 100644
--- a/test/functional/lua/buffer_updates_spec.lua
+++ b/test/functional/lua/buffer_updates_spec.lua
@@ -1,6 +1,6 @@
-- Test suite for testing interactions with API bindings
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local command = helpers.command
local meths = helpers.meths
@@ -317,7 +317,18 @@ describe('lua buffer event callbacks: on_lines', function()
feed('1G0')
feed('P')
eq(meths.get_var('linesev'), { "lines", 1, 6, 0, 3, 3, 9 })
+ end)
+ it('calling nvim_buf_call() from callback does not cause Normal mode CTRL-A to misbehave #16729', function()
+ exec_lua([[
+ vim.api.nvim_buf_attach(0, false, {
+ on_lines = function(...)
+ vim.api.nvim_buf_call(0, function() end)
+ end,
+ })
+ ]])
+ feed('itest123<Esc><C-A>')
+ eq('test124', meths.get_current_line())
end)
end)
@@ -404,7 +415,7 @@ describe('lua: nvim_buf_attach on_bytes', function()
it('opening lines', function()
local check_events = setup_eventcheck(verify, origlines)
- -- meths.buf_set_option(0, 'autoindent', true)
+ -- meths.set_option_value('autoindent', true, {})
feed 'Go'
check_events {
{ "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 1 };
@@ -417,7 +428,7 @@ describe('lua: nvim_buf_attach on_bytes', function()
it('opening lines with autoindent', function()
local check_events = setup_eventcheck(verify, origlines)
- meths.buf_set_option(0, 'autoindent', true)
+ meths.set_option_value('autoindent', true, {})
feed 'Go'
check_events {
{ "test1", "bytes", 1, 4, 7, 0, 114, 0, 0, 0, 1, 0, 5 };
@@ -451,8 +462,8 @@ describe('lua: nvim_buf_attach on_bytes', function()
it('continuing comments with fo=or', function()
local check_events = setup_eventcheck(verify, {'// Comment'})
- meths.buf_set_option(0, 'formatoptions', 'ro')
- meths.buf_set_option(0, 'filetype', 'c')
+ meths.set_option_value('formatoptions', 'ro', {})
+ meths.set_option_value('filetype', 'c', {})
feed 'A<CR>'
check_events {
{ "test1", "bytes", 1, 4, 0, 10, 10, 0, 0, 0, 1, 3, 4 };
@@ -592,7 +603,7 @@ describe('lua: nvim_buf_attach on_bytes', function()
it('inccomand=nosplit and substitute', function()
local check_events = setup_eventcheck(verify,
{"abcde", "12345"})
- meths.set_option('inccommand', 'nosplit')
+ meths.set_option_value('inccommand', 'nosplit', {})
-- linewise substitute
feed(':%s/bcd/')
@@ -743,7 +754,8 @@ describe('lua: nvim_buf_attach on_bytes', function()
write_file("Xtest-reload", dedent [[
old line 1
old line 2]])
- lfs.touch("Xtest-reload", os.time() - 10)
+ local atime = os.time() - 10
+ luv.fs_utime("Xtest-reload", atime, atime)
command "e Xtest-reload"
command "set autoread"
@@ -814,53 +826,53 @@ describe('lua: nvim_buf_attach on_bytes', function()
feed("<esc>u")
check_events {
- { "test1", "bytes", 1, 8, 0, 0, 0, 0, 1, 1, 0, 4, 4 },
- { "test1", "bytes", 1, 8, 0, 0, 0, 0, 4, 4, 0, 0, 0 }
+ { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 4, 4 },
+ { "test1", "bytes", 1, 9, 0, 0, 0, 0, 4, 4, 0, 0, 0 }
}
-- in REPLACE mode
feed("R<tab><tab>")
check_events {
- { "test1", "bytes", 1, 9, 0, 0, 0, 0, 1, 1, 0, 1, 1 },
- { "test1", "bytes", 1, 10, 0, 1, 1, 0, 0, 0, 0, 1, 1 },
- { "test1", "bytes", 1, 11, 0, 2, 2, 0, 1, 1, 0, 1, 1 },
- { "test1", "bytes", 1, 12, 0, 3, 3, 0, 0, 0, 0, 1, 1 },
- { "test1", "bytes", 1, 13, 0, 0, 0, 0, 4, 4, 0, 1, 1 },
+ { "test1", "bytes", 1, 10, 0, 0, 0, 0, 1, 1, 0, 1, 1 },
+ { "test1", "bytes", 1, 11, 0, 1, 1, 0, 0, 0, 0, 1, 1 },
+ { "test1", "bytes", 1, 12, 0, 2, 2, 0, 1, 1, 0, 1, 1 },
+ { "test1", "bytes", 1, 13, 0, 3, 3, 0, 0, 0, 0, 1, 1 },
+ { "test1", "bytes", 1, 14, 0, 0, 0, 0, 4, 4, 0, 1, 1 },
}
feed("<esc>u")
check_events {
- { "test1", "bytes", 1, 14, 0, 0, 0, 0, 1, 1, 0, 4, 4 },
- { "test1", "bytes", 1, 14, 0, 2, 2, 0, 2, 2, 0, 1, 1 },
- { "test1", "bytes", 1, 14, 0, 0, 0, 0, 2, 2, 0, 1, 1 }
+ { "test1", "bytes", 1, 16, 0, 0, 0, 0, 1, 1, 0, 4, 4 },
+ { "test1", "bytes", 1, 16, 0, 2, 2, 0, 2, 2, 0, 1, 1 },
+ { "test1", "bytes", 1, 16, 0, 0, 0, 0, 2, 2, 0, 1, 1 }
}
-- in VISUALREPLACE mode
feed("gR<tab><tab>")
check_events {
- { "test1", "bytes", 1, 15, 0, 0, 0, 0, 1, 1, 0, 1, 1 };
- { "test1", "bytes", 1, 16, 0, 1, 1, 0, 1, 1, 0, 1, 1 };
- { "test1", "bytes", 1, 17, 0, 2, 2, 0, 1, 1, 0, 1, 1 };
- { "test1", "bytes", 1, 18, 0, 3, 3, 0, 1, 1, 0, 1, 1 };
- { "test1", "bytes", 1, 19, 0, 3, 3, 0, 1, 1, 0, 0, 0 };
- { "test1", "bytes", 1, 20, 0, 3, 3, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 22, 0, 2, 2, 0, 1, 1, 0, 0, 0 };
- { "test1", "bytes", 1, 23, 0, 2, 2, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 25, 0, 1, 1, 0, 1, 1, 0, 0, 0 };
- { "test1", "bytes", 1, 26, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 28, 0, 0, 0, 0, 1, 1, 0, 0, 0 };
- { "test1", "bytes", 1, 29, 0, 0, 0, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 31, 0, 0, 0, 0, 4, 4, 0, 1, 1 };
+ { "test1", "bytes", 1, 17, 0, 0, 0, 0, 1, 1, 0, 1, 1 };
+ { "test1", "bytes", 1, 18, 0, 1, 1, 0, 1, 1, 0, 1, 1 };
+ { "test1", "bytes", 1, 19, 0, 2, 2, 0, 1, 1, 0, 1, 1 };
+ { "test1", "bytes", 1, 20, 0, 3, 3, 0, 1, 1, 0, 1, 1 };
+ { "test1", "bytes", 1, 21, 0, 3, 3, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 22, 0, 3, 3, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 24, 0, 2, 2, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 25, 0, 2, 2, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 27, 0, 1, 1, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 28, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 30, 0, 0, 0, 0, 1, 1, 0, 0, 0 };
+ { "test1", "bytes", 1, 31, 0, 0, 0, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 33, 0, 0, 0, 0, 4, 4, 0, 1, 1 };
}
-- inserting tab after other tabs
command("set sw=4")
feed("<esc>0a<tab>")
check_events {
- { "test1", "bytes", 1, 32, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 33, 0, 2, 2, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 34, 0, 3, 3, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 35, 0, 4, 4, 0, 0, 0, 0, 1, 1 };
- { "test1", "bytes", 1, 36, 0, 1, 1, 0, 4, 4, 0, 1, 1 };
+ { "test1", "bytes", 1, 34, 0, 1, 1, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 35, 0, 2, 2, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 36, 0, 3, 3, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 37, 0, 4, 4, 0, 0, 0, 0, 1, 1 };
+ { "test1", "bytes", 1, 38, 0, 1, 1, 0, 4, 4, 0, 1, 1 };
}
end)
@@ -986,7 +998,7 @@ describe('lua: nvim_buf_attach on_bytes', function()
it("virtual edit", function ()
local check_events = setup_eventcheck(verify, { "", " " })
- meths.set_option("virtualedit", "all")
+ meths.set_option_value('virtualedit', "all", {})
feed [[<Right><Right>iab<ESC>]]
@@ -1152,12 +1164,36 @@ describe('lua: nvim_buf_attach on_bytes', function()
end)
it("works with accepting spell suggestions", function()
- local check_events = setup_eventcheck(verify, {"hallo"})
+ local check_events = setup_eventcheck(verify, {"hallo world", "hallo world"})
feed("gg0z=4<cr><cr>") -- accepts 'Hello'
check_events {
{ "test1", "bytes", 1, 3, 0, 0, 0, 0, 2, 2, 0, 2, 2 };
}
+
+ command("spellrepall") -- replaces whole words
+ check_events {
+ { "test1", "bytes", 1, 4, 1, 0, 12, 0, 5, 5, 0, 5, 5 };
+ }
+ end)
+
+ it('works with :diffput and :diffget', function()
+ local check_events = setup_eventcheck(verify, {"AAA"})
+ command('diffthis')
+ command('new')
+ command('diffthis')
+ meths.buf_set_lines(0, 0, -1, true, {"AAA", "BBB"})
+ feed('G')
+ command('diffput')
+ check_events {
+ { "test1", "bytes", 1, 3, 1, 0, 4, 0, 0, 0, 1, 0, 4 };
+ }
+ meths.buf_set_lines(0, 0, -1, true, {"AAA", "CCC"})
+ feed('<C-w>pG')
+ command('diffget')
+ check_events {
+ { "test1", "bytes", 1, 4, 1, 0, 4, 1, 0, 4, 1, 0, 4 };
+ }
end)
local function test_lockmarks(mode)
diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua
index 3a5966755e..177e077f4a 100644
--- a/test/functional/lua/command_line_completion_spec.lua
+++ b/test/functional/lua/command_line_completion_spec.lua
@@ -5,7 +5,7 @@ local eq = helpers.eq
local exec_lua = helpers.exec_lua
local get_completions = function(input, env)
- return exec_lua("return {vim._expand_pat(...)}", '^' .. input, env)
+ return exec_lua("return {vim._expand_pat(...)}", input, env)
end
local get_compl_parts = function(parts)
@@ -31,14 +31,12 @@ describe('nlua_expand_pat', function()
eq(
{{
'nvim_buf_set_lines',
- 'nvim_buf_set_option'
}, 8
},
get_completions('vim.api.nvim_buf_', {
vim = {
api = {
nvim_buf_set_lines = true,
- nvim_buf_set_option = true,
nvim_win_doesnt_match = true,
},
other_key = true,
@@ -68,14 +66,12 @@ describe('nlua_expand_pat', function()
eq(
{{
'nvim_buf_set_lines',
- 'nvim_buf_set_option'
}, 11
},
get_completions('vim["api"].nvim_buf_', {
vim = {
api = {
nvim_buf_set_lines = true,
- nvim_buf_set_option = true,
nvim_win_doesnt_match = true,
},
other_key = true,
@@ -88,7 +84,6 @@ describe('nlua_expand_pat', function()
eq(
{{
'nvim_buf_set_lines',
- 'nvim_buf_set_option'
}, 21
},
get_completions('vim["nested"]["api"].nvim_buf_', {
@@ -96,7 +91,6 @@ describe('nlua_expand_pat', function()
nested = {
api = {
nvim_buf_set_lines = true,
- nvim_buf_set_option = true,
nvim_win_doesnt_match = true,
},
},
@@ -107,9 +101,12 @@ describe('nlua_expand_pat', function()
end)
it('should work with lazy submodules of "vim" global', function()
- eq({{ 'inspect' }, 4 },
+ eq({{ 'inspect', 'inspect_pos' }, 4 },
get_completions('vim.inspec'))
+ eq({{ 'treesitter' }, 4 },
+ get_completions('vim.treesi'))
+
eq({{ 'set' }, 11 },
get_completions('vim.keymap.se'))
end)
@@ -118,7 +115,6 @@ describe('nlua_expand_pat', function()
eq(
{{
'nvim_buf_set_lines',
- 'nvim_buf_set_option'
}, 12
},
get_completions('vim[MY_VAR].nvim_buf_', {
@@ -126,7 +122,6 @@ describe('nlua_expand_pat', function()
vim = {
api = {
nvim_buf_set_lines = true,
- nvim_buf_set_option = true,
nvim_win_doesnt_match = true,
},
other_key = true,
diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua
index b8346df290..fca619348d 100644
--- a/test/functional/lua/commands_spec.lua
+++ b/test/functional/lua/commands_spec.lua
@@ -7,7 +7,10 @@ local NIL = helpers.NIL
local eval = helpers.eval
local feed = helpers.feed
local clear = helpers.clear
+local matches = helpers.matches
local meths = helpers.meths
+local exec_lua = helpers.exec_lua
+local exec_capture = helpers.exec_capture
local funcs = helpers.funcs
local source = helpers.source
local dedent = helpers.dedent
@@ -15,7 +18,6 @@ local command = helpers.command
local exc_exec = helpers.exc_exec
local pcall_err = helpers.pcall_err
local write_file = helpers.write_file
-local exec_capture = helpers.exec_capture
local curbufmeths = helpers.curbufmeths
local remove_trace = helpers.remove_trace
@@ -26,22 +28,27 @@ describe(':lua command', function()
eq('', exec_capture(
'lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TEST"})'))
eq({'', 'TEST'}, curbufmeths.get_lines(0, 100, false))
- source(dedent([[
+ source([[
lua << EOF
vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TSET"})
- EOF]]))
+ EOF]])
eq({'', 'TSET'}, curbufmeths.get_lines(0, 100, false))
- source(dedent([[
+ source([[
lua << EOF
- vim.api.nvim_buf_set_lines(1, 1, 2, false, {"SETT"})]]))
+ vim.api.nvim_buf_set_lines(1, 1, 2, false, {"SETT"})]])
eq({'', 'SETT'}, curbufmeths.get_lines(0, 100, false))
- source(dedent([[
+ source([[
lua << EOF
vim.api.nvim_buf_set_lines(1, 1, 2, false, {"ETTS"})
vim.api.nvim_buf_set_lines(1, 2, 3, false, {"TTSE"})
vim.api.nvim_buf_set_lines(1, 3, 4, false, {"STTE"})
- EOF]]))
+ EOF]])
eq({'', 'ETTS', 'TTSE', 'STTE'}, curbufmeths.get_lines(0, 100, false))
+ matches('.*Vim%(lua%):E15: Invalid expression: .*', pcall_err(source, [[
+ lua << eval EOF
+ {}
+ EOF
+ ]]))
end)
it('throws catchable errors', function()
eq([[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']],
@@ -142,22 +149,30 @@ describe(':lua command', function()
]]}
end)
- it('Can print results of =expr', function()
- helpers.exec_lua("x = 5")
- eq("5", helpers.exec_capture(':lua =x'))
- helpers.exec_lua("function x() return 'hello' end")
- eq([["hello"]], helpers.exec_capture(':lua = x()'))
- helpers.exec_lua("x = {a = 1, b = 2}")
- eq("{\n a = 1,\n b = 2\n}", helpers.exec_capture(':lua =x'))
- helpers.exec_lua([[function x(success)
+ it('prints result of =expr', function()
+ exec_lua("x = 5")
+ eq("5", exec_capture(':lua =x'))
+ eq("5", exec_capture('=x'))
+ exec_lua("function x() return 'hello' end")
+ eq('hello', exec_capture(':lua = x()'))
+ exec_lua("x = {a = 1, b = 2}")
+ eq("{\n a = 1,\n b = 2\n}", exec_capture(':lua =x'))
+ exec_lua([[function x(success)
if success then
return true, "Return value"
else
return false, nil, "Error message"
end
end]])
- eq([[true "Return value"]], helpers.exec_capture(':lua =x(true)'))
- eq([[false nil "Error message"]], helpers.exec_capture(':lua =x(false)'))
+ eq(dedent[[
+ true
+ Return value]],
+ exec_capture(':lua =x(true)'))
+ eq(dedent[[
+ false
+ nil
+ Error message]],
+ exec_capture('=x(false)'))
end)
end)
@@ -183,7 +198,7 @@ describe(':luado command', function()
end)
it('works correctly when changing lines out of range', function()
curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
- eq('Vim(luado):E322: line number out of range: 1 past the end',
+ eq('Vim(luado):E322: Line number out of range: 1 past the end',
pcall_err(command, '2,$luado vim.api.nvim_command("%d") return linenr'))
eq({''}, curbufmeths.get_lines(0, -1, false))
end)
@@ -199,7 +214,7 @@ describe(':luado command', function()
end)
it('fails in sandbox when needed', function()
curbufmeths.set_lines(0, 1, false, {"ABC", "def", "gHi"})
- eq('Vim(luado):E48: Not allowed in sandbox',
+ eq('Vim(luado):E48: Not allowed in sandbox: sandbox luado runs = (runs or 0) + 1',
pcall_err(command, 'sandbox luado runs = (runs or 0) + 1'))
eq(NIL, funcs.luaeval('runs'))
end)
diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua
index d364986ad7..f061fac50a 100644
--- a/test/functional/lua/diagnostic_spec.lua
+++ b/test/functional/lua/diagnostic_spec.lua
@@ -79,13 +79,10 @@ describe('vim.diagnostic', function()
]])
end)
- after_each(function()
- clear()
- end)
-
it('creates highlight groups', function()
command('runtime plugin/diagnostic.vim')
eq({
+ 'DiagnosticDeprecated',
'DiagnosticError',
'DiagnosticFloatingError',
'DiagnosticFloatingHint',
@@ -105,6 +102,7 @@ describe('vim.diagnostic', function()
'DiagnosticUnderlineInfo',
'DiagnosticUnderlineOk',
'DiagnosticUnderlineWarn',
+ 'DiagnosticUnnecessary',
'DiagnosticVirtualTextError',
'DiagnosticVirtualTextHint',
'DiagnosticVirtualTextInfo',
@@ -183,6 +181,18 @@ describe('vim.diagnostic', function()
eq(0, #diagnostics)
end)
+ it('always returns a copy of diagnostic tables', function()
+ local result = exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error('Diagnostic #1', 1, 1, 1, 1),
+ })
+ local diag = vim.diagnostic.get()
+ diag[1].col = 10000
+ return vim.diagnostic.get()[1].col == 10000
+ ]]
+ eq(result, false)
+ end)
+
it('resolves buffer number 0 to the current buffer', function()
eq(2, exec_lua [[
vim.api.nvim_set_current_buf(diagnostic_bufnr)
@@ -862,7 +872,7 @@ end)
]])
end)
- it('returns only requested diagnostics when severity is supplied', function()
+ it('returns only requested diagnostics when severity range is supplied', function()
eq({2, 3, 2}, exec_lua [[
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
make_error("Error 1", 1, 1, 1, 5),
@@ -884,6 +894,28 @@ end)
]])
end)
+ it('returns only requested diagnostics when severities are supplied', function()
+ eq({1, 1, 2}, exec_lua [[
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
+ make_error("Error 1", 1, 1, 1, 5),
+ make_warning("Warning on Server 1", 1, 1, 2, 3),
+ make_info("Ignored information", 1, 1, 2, 3),
+ make_hint("Here's a hint", 1, 1, 2, 3),
+ })
+
+ return {
+ #vim.diagnostic.get(diagnostic_bufnr, { severity = {vim.diagnostic.severity.WARN} }),
+ #vim.diagnostic.get(diagnostic_bufnr, { severity = {vim.diagnostic.severity.ERROR} }),
+ #vim.diagnostic.get(diagnostic_bufnr, {
+ severity = {
+ vim.diagnostic.severity.INFO,
+ vim.diagnostic.severity.WARN,
+ }
+ }),
+ }
+ ]])
+ end)
+
it('allows filtering by line', function()
eq(1, exec_lua [[
vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {
@@ -1044,7 +1076,7 @@ end)
local virt_text = get_virt_text_extmarks(diagnostic_ns)[1][4].virt_text
local virt_texts = {}
- for i = 2, #virt_text do
+ for i = 2, #virt_text - 1 do
table.insert(virt_texts, (string.gsub(virt_text[i][2], "DiagnosticVirtualText", "")))
end
@@ -1088,7 +1120,7 @@ end)
})
local extmarks = get_virt_text_extmarks(diagnostic_ns)
- local virt_text = extmarks[1][4].virt_text[2][1]
+ local virt_text = extmarks[1][4].virt_text[3][1]
return virt_text
]]
eq(' source x: Some error', result)
@@ -1103,7 +1135,7 @@ end)
}, diagnostic_ns)
local extmarks = get_virt_text_extmarks(diagnostic_ns)
- local virt_text = extmarks[1][4].virt_text[2][1]
+ local virt_text = extmarks[1][4].virt_text[3][1]
return virt_text
]]
eq(' Some error', result)
@@ -1123,7 +1155,7 @@ end)
})
local extmarks = get_virt_text_extmarks(diagnostic_ns)
- local virt_text = {extmarks[1][4].virt_text[2][1], extmarks[2][4].virt_text[2][1]}
+ local virt_text = {extmarks[1][4].virt_text[3][1], extmarks[2][4].virt_text[3][1]}
return virt_text
]]
eq(' source x: Some error', result[1])
@@ -1153,8 +1185,8 @@ end)
local extmarks = get_virt_text_extmarks(diagnostic_ns)
return {extmarks[1][4].virt_text, extmarks[2][4].virt_text}
]]
- eq(" 👀 Warning", result[1][2][1])
- eq(" 🔥 Error", result[2][2][1])
+ eq(" 👀 Warning", result[1][3][1])
+ eq(" 🔥 Error", result[2][3][1])
end)
it('includes source for formatted diagnostics', function()
@@ -1181,8 +1213,48 @@ end)
local extmarks = get_virt_text_extmarks(diagnostic_ns)
return {extmarks[1][4].virt_text, extmarks[2][4].virt_text}
]]
- eq(" some_linter: 👀 Warning", result[1][2][1])
- eq(" another_linter: 🔥 Error", result[2][2][1])
+ eq(" some_linter: 👀 Warning", result[1][3][1])
+ eq(" another_linter: 🔥 Error", result[2][3][1])
+ end)
+
+ it('can add a prefix to virtual text', function()
+ eq('E Some error', exec_lua [[
+ local diagnostics = {
+ make_error('Some error', 0, 0, 0, 0),
+ }
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, {
+ underline = false,
+ virtual_text = {
+ prefix = 'E',
+ suffix = '',
+ }
+ })
+
+ local extmarks = get_virt_text_extmarks(diagnostic_ns)
+ local prefix = extmarks[1][4].virt_text[2][1]
+ local message = extmarks[1][4].virt_text[3][1]
+ return prefix .. message
+ ]])
+
+ eq('[(1/1) err-code] Some error', exec_lua [[
+ local diagnostics = {
+ make_error('Some error', 0, 0, 0, 0, nil, 'err-code'),
+ }
+
+ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, {
+ underline = false,
+ virtual_text = {
+ prefix = function(diag, i, total) return string.format('[(%d/%d) %s]', i, total, diag.code) end,
+ suffix = '',
+ }
+ })
+
+ local extmarks = get_virt_text_extmarks(diagnostic_ns)
+ local prefix = extmarks[1][4].virt_text[2][1]
+ local message = extmarks[1][4].virt_text[3][1]
+ return prefix .. message
+ ]])
end)
it('can add a suffix to virtual text', function()
@@ -1200,7 +1272,7 @@ end)
})
local extmarks = get_virt_text_extmarks(diagnostic_ns)
- local virt_text = extmarks[1][4].virt_text[2][1]
+ local virt_text = extmarks[1][4].virt_text[3][1]
return virt_text
]])
@@ -1218,7 +1290,7 @@ end)
})
local extmarks = get_virt_text_extmarks(diagnostic_ns)
- local virt_text = extmarks[1][4].virt_text[2][1]
+ local virt_text = extmarks[1][4].virt_text[3][1]
return virt_text
]])
end)
diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua
index 18b13a8959..3a37b18cd1 100644
--- a/test/functional/lua/ffi_spec.lua
+++ b/test/functional/lua/ffi_spec.lua
@@ -25,7 +25,6 @@ describe('ffi.cdef', function()
local ffi = require('ffi')
ffi.cdef[[
- typedef unsigned char char_u;
typedef struct window_S win_T;
typedef struct {} stl_hlrec_t;
typedef struct {} StlClickRecord;
@@ -63,5 +62,14 @@ describe('ffi.cdef', function()
nil
)
]=])
+
+ -- Check that extern symbols are exported and accessible
+ eq(true, exec_lua[[
+ local ffi = require('ffi')
+
+ ffi.cdef('uint64_t display_tick;')
+
+ return ffi.C.display_tick >= 0
+ ]])
end)
end)
diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua
index 2a7be53164..b3d95e1c7f 100644
--- a/test/functional/lua/filetype_spec.lua
+++ b/test/functional/lua/filetype_spec.lua
@@ -94,11 +94,46 @@ describe('vim.filetype', function()
return vim.filetype.match({ buf = 0 })
]])
end)
+
+ it('works with contents #22180', function()
+ eq('sh', exec_lua [[
+ -- Needs to be set so detect#sh doesn't fail
+ vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$'
+ return vim.filetype.match({ contents = { '#!/usr/bin/env bash' } })
+ ]])
+ end)
+
+ it('considers extension mappings when matching from hashbang', function()
+ eq('fooscript', exec_lua [[
+ vim.filetype.add({
+ extension = {
+ foo = 'fooscript',
+ }
+ })
+ return vim.filetype.match({ contents = { '#!/usr/bin/env foo' } })
+ ]])
+ end)
+
+ it('can get default option values for filetypes via vim.filetype.get_option()', function()
+ command('filetype plugin on')
+
+ for ft, opts in pairs {
+ lua = { commentstring = '-- %s' },
+ vim = { commentstring = '"%s' },
+ man = { tagfunc = 'v:lua.require\'man\'.goto_tag' },
+ xml = { formatexpr = 'xmlformat#Format()' }
+ } do
+ for option, value in pairs(opts) do
+ eq(value, exec_lua([[ return vim.filetype.get_option(...) ]], ft, option))
+ end
+ end
+
+ end)
end)
describe('filetype.lua', function()
it('does not override user autocommands that set filetype #20333', function()
clear({args={'--clean', '--cmd', 'autocmd BufRead *.md set filetype=notmarkdown', 'README.md'}})
- eq('notmarkdown', meths.buf_get_option(0, 'filetype'))
+ eq('notmarkdown', meths.get_option_value('filetype', {}))
end)
end)
diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua
index 642d36f63a..6bdb9ed79d 100644
--- a/test/functional/lua/fs_spec.lua
+++ b/test/functional/lua/fs_spec.lua
@@ -1,5 +1,5 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local uv = require('luv')
local clear = helpers.clear
local exec_lua = helpers.exec_lua
@@ -8,8 +8,10 @@ local mkdir_p = helpers.mkdir_p
local rmdir = helpers.rmdir
local nvim_dir = helpers.nvim_dir
local test_build_dir = helpers.test_build_dir
+local test_source_path = helpers.test_source_path
local nvim_prog = helpers.nvim_prog
local is_os = helpers.is_os
+local mkdir = helpers.mkdir
local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim'
@@ -132,10 +134,10 @@ describe('vim.fs', function()
describe('dir()', function()
before_each(function()
- lfs.mkdir('testd')
- lfs.mkdir('testd/a')
- lfs.mkdir('testd/a/b')
- lfs.mkdir('testd/a/b/c')
+ mkdir('testd')
+ mkdir('testd/a')
+ mkdir('testd/a/b')
+ mkdir('testd/a/b/c')
end)
after_each(function()
@@ -222,7 +224,7 @@ describe('vim.fs', function()
describe('find()', function()
it('works', function()
- eq({test_build_dir}, exec_lua([[
+ eq({test_build_dir .. "/build"}, exec_lua([[
local dir = ...
return vim.fs.find('build', { path = dir, upward = true, type = 'directory' })
]], nvim_dir))
@@ -238,7 +240,7 @@ describe('vim.fs', function()
end)
it('accepts predicate as names', function()
- eq({test_build_dir}, exec_lua([[
+ eq({test_build_dir .. "/build"}, exec_lua([[
local dir = ...
local opts = { path = dir, upward = true, type = 'directory' }
return vim.fs.find(function(x) return x == 'build' end, opts)
@@ -252,6 +254,27 @@ describe('vim.fs', function()
local opts = { path = dir, upward = true, type = 'directory' }
return vim.fs.find(function(x) return x == 'no-match' end, opts)
]], nvim_dir))
+ eq(
+ exec_lua([[
+ local dir = ...
+ return vim.tbl_map(vim.fs.basename, vim.fn.glob(dir..'/contrib/*', false, true))
+ ]], test_source_path),
+ exec_lua([[
+ local dir = ...
+ local opts = { path = dir .. "/contrib", limit = math.huge }
+ return vim.tbl_map(vim.fs.basename, vim.fs.find(function(_, d) return d:match('[\\/]contrib$') end, opts))
+ ]], test_source_path))
+ end)
+ end)
+
+ describe('joinpath()', function()
+ it('works', function()
+ eq('foo/bar/baz', exec_lua([[
+ return vim.fs.joinpath('foo', 'bar', 'baz')
+ ]], nvim_dir))
+ eq('foo/bar/baz', exec_lua([[
+ return vim.fs.joinpath('foo', '/bar/', '/baz')
+ ]], nvim_dir))
end)
end)
@@ -259,11 +282,19 @@ describe('vim.fs', function()
it('works with backward slashes', function()
eq('C:/Users/jdoe', exec_lua [[ return vim.fs.normalize('C:\\Users\\jdoe') ]])
end)
+ it('removes trailing /', function()
+ eq('/home/user', exec_lua [[ return vim.fs.normalize('/home/user/') ]])
+ end)
+ it('works with /', function()
+ eq('/', exec_lua [[ return vim.fs.normalize('/') ]])
+ end)
it('works with ~', function()
- if is_os('win') then
- pending([[$HOME does not exist on Windows ¯\_(ツ)_/¯]])
- end
- eq(os.getenv('HOME') .. '/src/foo', exec_lua [[ return vim.fs.normalize('~/src/foo') ]])
+ eq(
+ exec_lua([[
+ local home = ...
+ return home .. '/src/foo'
+ ]], vim.fs.normalize(uv.os_homedir())),
+ exec_lua [[ return vim.fs.normalize('~/src/foo') ]])
end)
it('works with environment variables', function()
local xdg_config_home = test_build_dir .. '/.config'
@@ -272,5 +303,10 @@ describe('vim.fs', function()
return vim.fs.normalize('$XDG_CONFIG_HOME/nvim')
]], xdg_config_home))
end)
+ if is_os('win') then
+ it('Last slash is not truncated from root drive', function()
+ eq('C:/', exec_lua [[ return vim.fs.normalize('C:/') ]])
+ end)
+ end
end)
end)
diff --git a/test/functional/lua/help_spec.lua b/test/functional/lua/help_spec.lua
index b396e2ba30..ef1b8ebf3f 100644
--- a/test/functional/lua/help_spec.lua
+++ b/test/functional/lua/help_spec.lua
@@ -8,6 +8,8 @@ local exec_lua = helpers.exec_lua
local eq = helpers.eq
local ok = helpers.ok
+if helpers.skip(helpers.is_ci('cirrus'), 'No need to run this on Cirrus') then return end
+
describe(':help docs', function()
before_each(clear)
it('validate', function()
@@ -19,11 +21,12 @@ describe(':help docs', function()
local rv = exec_lua([[return require('scripts.gen_help_html').validate('./build/runtime/doc')]])
-- Check that we actually found helpfiles.
ok(rv.helpfiles > 100, '>100 :help files', rv.helpfiles)
+
+ eq({}, rv.parse_errors, 'no parse errors')
+ eq(0, rv.err_count, 'no parse errors')
eq({}, rv.invalid_links, 'invalid tags in :help docs')
eq({}, rv.invalid_urls, 'invalid URLs in :help docs')
- -- Check that parse errors did not increase wildly.
- -- TODO: Fix all parse errors in :help files.
- ok(rv.err_count < 250, '<250 parse errors', rv.err_count)
+ eq({}, rv.invalid_spelling, 'invalid spelling in :help docs (see spell_dict in scripts/gen_help_html.lua)')
end)
it('gen_help_html.lua generates HTML', function()
diff --git a/test/functional/lua/highlight_spec.lua b/test/functional/lua/highlight_spec.lua
index 60d0ed5017..8e499f1e79 100644
--- a/test/functional/lua/highlight_spec.lua
+++ b/test/functional/lua/highlight_spec.lua
@@ -24,7 +24,7 @@ describe('vim.highlight.on_yank', function()
it('does not close timer twice', function()
exec_lua([[
vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y"}})
- vim.loop.sleep(10)
+ vim.uv.sleep(10)
vim.schedule(function()
vim.highlight.on_yank({timeout = 0, on_macro = true, event = {operator = "y"}})
end)
diff --git a/test/functional/lua/inspector_spec.lua b/test/functional/lua/inspector_spec.lua
index 5e488bb082..c369956e56 100644
--- a/test/functional/lua/inspector_spec.lua
+++ b/test/functional/lua/inspector_spec.lua
@@ -12,19 +12,62 @@ describe('vim.inspect_pos', function()
it('it returns items', function()
local ret = exec_lua([[
local buf = vim.api.nvim_create_buf(true, false)
+ local buf1 = vim.api.nvim_create_buf(true, false)
+ local ns1 = vim.api.nvim_create_namespace("ns1")
+ local ns2 = vim.api.nvim_create_namespace("")
vim.api.nvim_set_current_buf(buf)
vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"})
- vim.api.nvim_buf_set_option(buf, "filetype", "lua")
+ vim.api.nvim_buf_set_lines(buf1, 0, -1, false, {"--commentline"})
+ vim.bo[buf].filetype = 'lua'
+ vim.bo[buf1].filetype = 'lua'
+ vim.api.nvim_buf_set_extmark(buf, ns1, 0, 10, { hl_group = "Normal" })
+ vim.api.nvim_buf_set_extmark(buf, ns2, 0, 10, { hl_group = "Normal" })
vim.cmd("syntax on")
- return {buf, vim.inspect_pos(0, 0, 10)}
+ return {buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax }
]])
- local buf, items = unpack(ret)
+ local buf, items, other_buf_syntax = unpack(ret)
+
eq('', eval('v:errmsg'))
eq({
buffer = buf,
col = 10,
row = 0,
- extmarks = {},
+ extmarks = {
+ {
+ col = 10,
+ end_col = 11,
+ end_row = 0,
+ id = 1,
+ ns = 'ns1',
+ ns_id = 1,
+ opts = {
+ hl_eol = false,
+ hl_group = 'Normal',
+ hl_group_link = 'Normal',
+ ns_id = 1,
+ priority = 4096,
+ right_gravity = true
+ },
+ row = 0
+ },
+ {
+ col = 10,
+ end_col = 11,
+ end_row = 0,
+ id = 1,
+ ns = '',
+ ns_id = 2,
+ opts = {
+ hl_eol = false,
+ hl_group = 'Normal',
+ hl_group_link = 'Normal',
+ ns_id = 2,
+ priority = 4096,
+ right_gravity = true
+ },
+ row = 0
+ }
+ },
treesitter = {},
semantic_tokens = {},
syntax = {
@@ -34,6 +77,13 @@ describe('vim.inspect_pos', function()
},
},
}, items)
+
+ eq({
+ {
+ hl_group = 'luaComment',
+ hl_group_link = 'Comment',
+ },
+ }, other_buf_syntax)
end)
end)
@@ -47,7 +97,7 @@ describe('vim.show_pos', function()
local buf = vim.api.nvim_create_buf(true, false)
vim.api.nvim_set_current_buf(buf)
vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"})
- vim.api.nvim_buf_set_option(buf, "filetype", "lua")
+ vim.bo[buf].filetype = 'lua'
vim.cmd("syntax on")
return {buf, vim.show_pos(0, 0, 10)}
]])
diff --git a/test/functional/lua/iter_spec.lua b/test/functional/lua/iter_spec.lua
new file mode 100644
index 0000000000..ffa28e7b11
--- /dev/null
+++ b/test/functional/lua/iter_spec.lua
@@ -0,0 +1,402 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local matches = helpers.matches
+local pcall_err = helpers.pcall_err
+
+describe('vim.iter', function()
+ it('new() on iterable class instance', function()
+ local rb = vim.ringbuf(3)
+ rb:push("a")
+ rb:push("b")
+
+ local it = vim.iter(rb)
+ eq({"a", "b"}, it:totable())
+ end)
+
+ it('filter()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ local t = { 1, 2, 3, 4, 5 }
+ eq({ 1, 3, 5 }, vim.iter(t):filter(odd):totable())
+ eq({ 2, 4 }, vim.iter(t):filter(function(v) return not odd(v) end):totable())
+ eq({}, vim.iter(t):filter(function(v) return v > 5 end):totable())
+
+ do
+ local it = vim.iter(ipairs(t))
+ it:filter(function(i, v) return i > 1 and v < 5 end)
+ it:map(function(_, v) return v * 2 end)
+ eq({ 4, 6, 8 }, it:totable())
+ end
+
+ local it = vim.iter(string.gmatch('the quick brown fox', '%w+'))
+ eq({'the', 'fox'}, it:filter(function(s) return #s <= 3 end):totable())
+ end)
+
+ it('map()', function()
+ local t = { 1, 2, 3, 4, 5 }
+ eq(
+ { 2, 4, 6, 8, 10 },
+ vim
+ .iter(t)
+ :map(function(v)
+ return 2 * v
+ end)
+ :totable()
+ )
+
+ local it = vim.gsplit(
+ [[
+ Line 1
+ Line 2
+ Line 3
+ Line 4
+ ]],
+ '\n'
+ )
+
+ eq(
+ { 'Lion 2', 'Lion 4' },
+ vim
+ .iter(it)
+ :map(function(s)
+ local lnum = s:match('(%d+)')
+ if lnum and tonumber(lnum) % 2 == 0 then
+ return vim.trim(s:gsub('Line', 'Lion'))
+ end
+ end)
+ :totable()
+ )
+ end)
+
+ it('for loops', function()
+ local t = {1, 2, 3, 4, 5}
+ local acc = 0
+ for v in vim.iter(t):map(function(v) return v * 3 end) do
+ acc = acc + v
+ end
+ eq(45, acc)
+ end)
+
+ it('totable()', function()
+ do
+ local it = vim.iter({1, 2, 3}):map(function(v) return v, v*v end)
+ eq({{1, 1}, {2, 4}, {3, 9}}, it:totable())
+ end
+
+ do
+ local it = vim.iter(string.gmatch('1,4,lol,17,blah,2,9,3', '%d+')):map(tonumber)
+ eq({1, 4, 17, 2, 9, 3}, it:totable())
+ end
+ end)
+
+ it('next()', function()
+ local it = vim.iter({1, 2, 3}):map(function(v) return 2 * v end)
+ eq(2, it:next())
+ eq(4, it:next())
+ eq(6, it:next())
+ eq(nil, it:next())
+ end)
+
+ it('rev()', function()
+ eq({3, 2, 1}, vim.iter({1, 2, 3}):rev():totable())
+
+ local it = vim.iter(string.gmatch("abc", "%w"))
+ matches('rev%(%) requires a list%-like table', pcall_err(it.rev, it))
+ end)
+
+ it('skip()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(t, vim.iter(t):skip(0):totable())
+ eq({3, 2, 1}, vim.iter(t):skip(1):totable())
+ eq({2, 1}, vim.iter(t):skip(2):totable())
+ eq({1}, vim.iter(t):skip(#t - 1):totable())
+ eq({}, vim.iter(t):skip(#t):totable())
+ eq({}, vim.iter(t):skip(#t + 1):totable())
+ end
+
+ do
+ local function skip(n)
+ return vim.iter(vim.gsplit('a|b|c|d', '|')):skip(n):totable()
+ end
+ eq({'a', 'b', 'c', 'd'}, skip(0))
+ eq({'b', 'c', 'd'}, skip(1))
+ eq({'c', 'd'}, skip(2))
+ eq({'d'}, skip(3))
+ eq({}, skip(4))
+ eq({}, skip(5))
+ end
+ end)
+
+ it('skipback()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(t, vim.iter(t):skipback(0):totable())
+ eq({4, 3, 2}, vim.iter(t):skipback(1):totable())
+ eq({4, 3}, vim.iter(t):skipback(2):totable())
+ eq({4}, vim.iter(t):skipback(#t - 1):totable())
+ eq({}, vim.iter(t):skipback(#t):totable())
+ eq({}, vim.iter(t):skipback(#t + 1):totable())
+ end
+
+ local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+ matches('skipback%(%) requires a list%-like table', pcall_err(it.skipback, it, 0))
+ end)
+
+ it('slice()', function()
+ local t = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
+ eq({3, 4, 5, 6, 7}, vim.iter(t):slice(3, 7):totable())
+ eq({}, vim.iter(t):slice(6, 5):totable())
+ eq({}, vim.iter(t):slice(0, 0):totable())
+ eq({1}, vim.iter(t):slice(1, 1):totable())
+ eq({1, 2}, vim.iter(t):slice(1, 2):totable())
+ eq({10}, vim.iter(t):slice(10, 10):totable())
+ eq({8, 9, 10}, vim.iter(t):slice(8, 11):totable())
+
+ local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+ matches('slice%(%) requires a list%-like table', pcall_err(it.slice, it, 1, 3))
+ end)
+
+ it('nth()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(nil, vim.iter(t):nth(0))
+ eq(4, vim.iter(t):nth(1))
+ eq(3, vim.iter(t):nth(2))
+ eq(2, vim.iter(t):nth(3))
+ eq(1, vim.iter(t):nth(4))
+ eq(nil, vim.iter(t):nth(5))
+ end
+
+ do
+ local function nth(n)
+ return vim.iter(vim.gsplit('a|b|c|d', '|')):nth(n)
+ end
+ eq(nil, nth(0))
+ eq('a', nth(1))
+ eq('b', nth(2))
+ eq('c', nth(3))
+ eq('d', nth(4))
+ eq(nil, nth(5))
+ end
+ end)
+
+ it('nthback()', function()
+ do
+ local t = {4, 3, 2, 1}
+ eq(nil, vim.iter(t):nthback(0))
+ eq(1, vim.iter(t):nthback(1))
+ eq(2, vim.iter(t):nthback(2))
+ eq(3, vim.iter(t):nthback(3))
+ eq(4, vim.iter(t):nthback(4))
+ eq(nil, vim.iter(t):nthback(5))
+ end
+
+ local it = vim.iter(vim.gsplit('a|b|c|d', '|'))
+ matches('skipback%(%) requires a list%-like table', pcall_err(it.nthback, it, 1))
+ end)
+
+ it('any()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ do
+ local t = { 4, 8, 9, 10 }
+ eq(true, vim.iter(t):any(odd))
+ end
+
+ do
+ local t = { 4, 8, 10 }
+ eq(false, vim.iter(t):any(odd))
+ end
+
+ do
+ eq(true, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'd' end))
+ eq(false, vim.iter(vim.gsplit('a|b|c|d', '|')):any(function(s) return s == 'e' end))
+ end
+ end)
+
+ it('all()', function()
+ local function odd(v)
+ return v % 2 ~= 0
+ end
+
+ do
+ local t = { 3, 5, 7, 9 }
+ eq(true, vim.iter(t):all(odd))
+ end
+
+ do
+ local t = { 3, 5, 7, 10 }
+ eq(false, vim.iter(t):all(odd))
+ end
+
+ do
+ eq(true, vim.iter(vim.gsplit('a|a|a|a', '|')):all(function(s) return s == 'a' end))
+ eq(false, vim.iter(vim.gsplit('a|a|a|b', '|')):all(function(s) return s == 'a' end))
+ end
+ end)
+
+ it('last()', function()
+ local s = 'abcdefghijklmnopqrstuvwxyz'
+ eq('z', vim.iter(vim.split(s, '')):last())
+ eq('z', vim.iter(vim.gsplit(s, '')):last())
+ end)
+
+ it('enumerate()', function()
+ local it = vim.iter(vim.gsplit('abc', '')):enumerate()
+ eq({1, 'a'}, {it:next()})
+ eq({2, 'b'}, {it:next()})
+ eq({3, 'c'}, {it:next()})
+ eq({}, {it:next()})
+ end)
+
+ it('peek()', function()
+ do
+ local it = vim.iter({ 3, 6, 9, 12 })
+ eq(3, it:peek())
+ eq(3, it:peek())
+ eq(3, it:next())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('peek%(%) requires a list%-like table', pcall_err(it.peek, it))
+ end
+ end)
+
+ it('find()', function()
+ local t = {3, 6, 9, 12}
+ eq(12, vim.iter(t):find(12))
+ eq(nil, vim.iter(t):find(15))
+ eq(12, vim.iter(t):find(function(v) return v % 4 == 0 end))
+
+ do
+ local it = vim.iter(t)
+ local pred = function(v) return v % 3 == 0 end
+ eq(3, it:find(pred))
+ eq(6, it:find(pred))
+ eq(9, it:find(pred))
+ eq(12, it:find(pred))
+ eq(nil, it:find(pred))
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('AbCdE', ''))
+ local pred = function(s) return s:match('[A-Z]') end
+ eq('A', it:find(pred))
+ eq('C', it:find(pred))
+ eq('E', it:find(pred))
+ eq(nil, it:find(pred))
+ end
+ end)
+
+ it('rfind()', function()
+ local t = {1, 2, 3, 2, 1}
+ do
+ local it = vim.iter(t)
+ eq(1, it:rfind(1))
+ eq(1, it:rfind(1))
+ eq(nil, it:rfind(1))
+ end
+
+ do
+ local it = vim.iter(t):enumerate()
+ local pred = function(i) return i % 2 ~= 0 end
+ eq({5, 1}, {it:rfind(pred)})
+ eq({3, 3}, {it:rfind(pred)})
+ eq({1, 1}, {it:rfind(pred)})
+ eq(nil, it:rfind(pred))
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('AbCdE', ''))
+ matches('rfind%(%) requires a list%-like table', pcall_err(it.rfind, it, 'E'))
+ end
+ end)
+
+ it('nextback()', function()
+ do
+ local it = vim.iter({ 1, 2, 3, 4 })
+ eq(4, it:nextback())
+ eq(3, it:nextback())
+ eq(2, it:nextback())
+ eq(1, it:nextback())
+ eq(nil, it:nextback())
+ eq(nil, it:nextback())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('nextback%(%) requires a list%-like table', pcall_err(it.nextback, it))
+ end
+ end)
+
+ it('peekback()', function()
+ do
+ local it = vim.iter({ 1, 2, 3, 4 })
+ eq(4, it:peekback())
+ eq(4, it:peekback())
+ eq(4, it:nextback())
+ end
+
+ do
+ local it = vim.iter(vim.gsplit('hi', ''))
+ matches('peekback%(%) requires a list%-like table', pcall_err(it.peekback, it))
+ end
+ end)
+
+ it('fold()', function()
+ local t = {1, 2, 3, 4, 5}
+ eq(115, vim.iter(t):fold(100, function(acc, v) return acc + v end))
+ eq({5, 4, 3, 2, 1}, vim.iter(t):fold({}, function(acc, v)
+ table.insert(acc, 1, v)
+ return acc
+ end))
+ end)
+
+ it('handles map-like tables', function()
+ local it = vim.iter({ a = 1, b = 2, c = 3 }):map(function(k, v)
+ if v % 2 ~= 0 then
+ return k:upper(), v * 2
+ end
+ end)
+
+ local t = it:fold({}, function(t, k, v)
+ t[k] = v
+ return t
+ end)
+ eq({ A = 2, C = 6 }, t)
+ end)
+
+ it('handles table values mid-pipeline', function()
+ local map = {
+ item = {
+ file = 'test',
+ },
+ item_2 = {
+ file = 'test',
+ },
+ item_3 = {
+ file = 'test',
+ },
+ }
+
+ local output = vim.iter(map):map(function(key, value)
+ return { [key] = value.file }
+ end):totable()
+
+ table.sort(output, function(a, b)
+ return next(a) < next(b)
+ end)
+
+ eq({
+ { item = 'test' },
+ { item_2 = 'test' },
+ { item_3 = 'test' },
+ }, output)
+ end)
+end)
diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua
index fbb21bfd57..25fdb48eea 100644
--- a/test/functional/lua/json_spec.lua
+++ b/test/functional/lua/json_spec.lua
@@ -1,20 +1,57 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
-local NIL = helpers.NIL
local exec_lua = helpers.exec_lua
local eq = helpers.eq
+local pcall_err = helpers.pcall_err
-describe('vim.json.decode function', function()
+describe('vim.json.decode()', function()
before_each(function()
clear()
end)
it('parses null, true, false', function()
- eq(NIL, exec_lua([[return vim.json.decode('null')]]))
+ eq(vim.NIL, exec_lua([[return vim.json.decode('null')]]))
eq(true, exec_lua([[return vim.json.decode('true')]]))
eq(false, exec_lua([[return vim.json.decode('false')]]))
end)
+ it('validation', function()
+ eq('Expected object key string but found invalid token at character 2',
+ pcall_err(exec_lua, [[return vim.json.decode('{a:"b"}')]]))
+ end)
+
+ it('options', function()
+ local jsonstr = '{"arr":[1,2,null],"bar":[3,7],"foo":{"a":"b"},"baz":null}'
+ eq({
+ arr = { 1, 2, vim.NIL },
+ bar = { 3, 7 },
+ baz = vim.NIL,
+ foo = { a = 'b' },
+ },
+ exec_lua([[return vim.json.decode(..., {})]], jsonstr))
+ eq({
+ arr = { 1, 2, vim.NIL },
+ bar = { 3, 7 },
+ -- baz = nil,
+ foo = { a = 'b' },
+ },
+ exec_lua([[return vim.json.decode(..., { luanil = { object = true } })]], jsonstr))
+ eq({
+ arr = { 1, 2 },
+ bar = { 3, 7 },
+ baz = vim.NIL,
+ foo = { a = 'b' },
+ },
+ exec_lua([[return vim.json.decode(..., { luanil = { array = true } })]], jsonstr))
+ eq({
+ arr = { 1, 2 },
+ bar = { 3, 7 },
+ -- baz = nil,
+ foo = { a = 'b' },
+ },
+ exec_lua([[return vim.json.decode(..., { luanil = { array = true, object = true } })]], jsonstr))
+ end)
+
it('parses integer numbers', function()
eq(100000, exec_lua([[return vim.json.decode('100000')]]))
eq(-100000, exec_lua([[return vim.json.decode('-100000')]]))
@@ -60,7 +97,7 @@ describe('vim.json.decode function', function()
it('parses containers', function()
eq({1}, exec_lua([[return vim.json.decode('[1]')]]))
- eq({NIL, 1}, exec_lua([[return vim.json.decode('[null, 1]')]]))
+ eq({vim.NIL, 1}, exec_lua([[return vim.json.decode('[null, 1]')]]))
eq({['1']=2}, exec_lua([[return vim.json.decode('{"1": 2}')]]))
eq({['1']=2, ['3']={{['4']={['5']={{}, 1}}}}},
exec_lua([[return vim.json.decode('{"1": 2, "3": [{"4": {"5": [ [], 1]}}]}')]]))
@@ -88,43 +125,43 @@ describe('vim.json.decode function', function()
end)
-describe('vim.json.encode function', function()
+describe('vim.json.encode()', function()
before_each(function()
clear()
end)
- it('dumps strings', function()
- eq('"Test"', exec_lua([[return vim.json.encode('Test')]]))
- eq('""', exec_lua([[return vim.json.encode('')]]))
- eq('"\\t"', exec_lua([[return vim.json.encode('\t')]]))
- eq('"\\n"', exec_lua([[return vim.json.encode('\n')]]))
- -- vim.fn.json_encode return \\u001B
- eq('"\\u001b"', exec_lua([[return vim.json.encode('\27')]]))
- eq('"þÿþ"', exec_lua([[return vim.json.encode('þÿþ')]]))
- end)
-
- it('dumps numbers', function()
- eq('0', exec_lua([[return vim.json.encode(0)]]))
- eq('10', exec_lua([[return vim.json.encode(10)]]))
- eq('-10', exec_lua([[return vim.json.encode(-10)]]))
- end)
-
- it('dumps floats', function()
- eq('10.5', exec_lua([[return vim.json.encode(10.5)]]))
- eq('-10.5', exec_lua([[return vim.json.encode(-10.5)]]))
- eq('-1e-05', exec_lua([[return vim.json.encode(-1e-5)]]))
- end)
-
- it('dumps lists', function()
- eq('[]', exec_lua([[return vim.json.encode({})]]))
- eq('[[]]', exec_lua([[return vim.json.encode({{}})]]))
- eq('[[],[]]', exec_lua([[return vim.json.encode({{}, {}})]]))
- end)
-
- it('dumps dictionaries', function()
- eq('{}', exec_lua([[return vim.json.encode(vim.empty_dict())]]))
- eq('{"d":[]}', exec_lua([[return vim.json.encode({d={}})]]))
- end)
+ it('dumps strings', function()
+ eq('"Test"', exec_lua([[return vim.json.encode('Test')]]))
+ eq('""', exec_lua([[return vim.json.encode('')]]))
+ eq('"\\t"', exec_lua([[return vim.json.encode('\t')]]))
+ eq('"\\n"', exec_lua([[return vim.json.encode('\n')]]))
+ -- vim.fn.json_encode return \\u001B
+ eq('"\\u001b"', exec_lua([[return vim.json.encode('\27')]]))
+ eq('"þÿþ"', exec_lua([[return vim.json.encode('þÿþ')]]))
+ end)
+
+ it('dumps numbers', function()
+ eq('0', exec_lua([[return vim.json.encode(0)]]))
+ eq('10', exec_lua([[return vim.json.encode(10)]]))
+ eq('-10', exec_lua([[return vim.json.encode(-10)]]))
+ end)
+
+ it('dumps floats', function()
+ eq('10.5', exec_lua([[return vim.json.encode(10.5)]]))
+ eq('-10.5', exec_lua([[return vim.json.encode(-10.5)]]))
+ eq('-1e-05', exec_lua([[return vim.json.encode(-1e-5)]]))
+ end)
+
+ it('dumps lists', function()
+ eq('[]', exec_lua([[return vim.json.encode({})]]))
+ eq('[[]]', exec_lua([[return vim.json.encode({{}})]]))
+ eq('[[],[]]', exec_lua([[return vim.json.encode({{}, {}})]]))
+ end)
+
+ it('dumps dictionaries', function()
+ eq('{}', exec_lua([[return vim.json.encode(vim.empty_dict())]]))
+ eq('{"d":[]}', exec_lua([[return vim.json.encode({d={}})]]))
+ end)
it('dumps vim.NIL', function()
eq('null', exec_lua([[return vim.json.encode(vim.NIL)]]))
diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua
new file mode 100644
index 0000000000..34c36b04ef
--- /dev/null
+++ b/test/functional/lua/loader_spec.lua
@@ -0,0 +1,56 @@
+-- Test suite for testing interactions with API bindings
+local helpers = require('test.functional.helpers')(after_each)
+
+local exec_lua = helpers.exec_lua
+local command = helpers.command
+local eq = helpers.eq
+
+describe('vim.loader', function()
+ before_each(helpers.clear)
+
+ it('handles changing files (#23027)', function()
+ exec_lua[[
+ vim.loader.enable()
+ ]]
+
+ local tmp = helpers.tmpname()
+ command('edit ' .. tmp)
+
+ eq(1, exec_lua([[
+ vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=1'})
+ vim.cmd.write()
+ loadfile(...)()
+ return _G.TEST
+ ]], tmp))
+
+ -- fs latency
+ helpers.sleep(10)
+
+ eq(2, exec_lua([[
+ vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=2'})
+ vim.cmd.write()
+ loadfile(...)()
+ return _G.TEST
+ ]], tmp))
+ end)
+
+ it('handles % signs in modpath (#24491)', function()
+ exec_lua[[
+ vim.loader.enable()
+ ]]
+
+ local tmp1, tmp2 = (function (t)
+ assert(os.remove(t))
+ assert(helpers.mkdir(t))
+ assert(helpers.mkdir(t .. '/%'))
+ return t .. '/%/x', t .. '/%%x'
+ end)(helpers.tmpname())
+
+ helpers.write_file(tmp1, 'return 1', true)
+ helpers.write_file(tmp2, 'return 2', true)
+ vim.uv.fs_utime(tmp1, 0, 0)
+ vim.uv.fs_utime(tmp2, 0, 0)
+ eq(1, exec_lua('return loadfile(...)()', tmp1))
+ eq(2, exec_lua('return loadfile(...)()', tmp2))
+ end)
+end)
diff --git a/test/functional/lua/loop_spec.lua b/test/functional/lua/loop_spec.lua
index 7f3787d7bf..c0924fa0c0 100644
--- a/test/functional/lua/loop_spec.lua
+++ b/test/functional/lua/loop_spec.lua
@@ -14,24 +14,24 @@ local retry = helpers.retry
before_each(clear)
-describe('vim.loop', function()
+describe('vim.uv', function()
it('version', function()
- assert(funcs.luaeval('vim.loop.version()')>=72961, "libuv version too old")
- matches("(%d+)%.(%d+)%.(%d+)", funcs.luaeval('vim.loop.version_string()'))
+ assert(funcs.luaeval('vim.uv.version()')>=72961, "libuv version too old")
+ matches("(%d+)%.(%d+)%.(%d+)", funcs.luaeval('vim.uv.version_string()'))
end)
it('timer', function()
exec_lua('vim.api.nvim_set_var("coroutine_cnt", 0)', {})
local code=[[
- local loop = vim.loop
+ local uv = vim.uv
local touch = 0
local function wait(ms)
local this = coroutine.running()
assert(this)
- local timer = loop.new_timer()
+ local timer = uv.new_timer()
timer:start(ms, 0, vim.schedule_wrap(function ()
timer:close()
touch = touch + 1
@@ -73,7 +73,7 @@ describe('vim.loop', function()
-- deferred API functions are disabled, as their safety can't be guaranteed
exec_lua([[
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(20, 0, function ()
_G.is_fast = vim.in_fast_event()
timer:close()
@@ -101,7 +101,7 @@ describe('vim.loop', function()
-- callbacks can be scheduled to be executed in the main event loop
-- where the entire API is available
exec_lua([[
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(20, 0, vim.schedule_wrap(function ()
_G.is_fast = vim.in_fast_event()
timer:close()
@@ -127,7 +127,7 @@ describe('vim.loop', function()
-- fast (not deferred) API functions are allowed to be called directly
exec_lua([[
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(20, 0, function ()
timer:close()
-- input is queued for processing after the callback returns
@@ -151,6 +151,6 @@ describe('vim.loop', function()
end)
it("is equal to require('luv')", function()
- eq(true, exec_lua("return vim.loop == require('luv')"))
+ eq(true, exec_lua("return vim.uv == require('luv')"))
end)
end)
diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua
index 9f313eab9e..dfbd2fb18b 100644
--- a/test/functional/lua/luaeval_spec.lua
+++ b/test/functional/lua/luaeval_spec.lua
@@ -514,7 +514,7 @@ describe('v:lua', function()
[5] = {bold = true, foreground = Screen.colors.SeaGreen4},
})
screen:attach()
- meths.buf_set_option(0, 'omnifunc', 'v:lua.mymod.omni')
+ meths.set_option_value('omnifunc', 'v:lua.mymod.omni', {})
feed('isome st<c-x><c-o>')
screen:expect{grid=[[
some stuff^ |
@@ -526,7 +526,7 @@ describe('v:lua', function()
{1:~ }|
{4:-- Omni completion (^O^N^P) }{5:match 1 of 3} |
]]}
- meths.set_option('operatorfunc', 'v:lua.mymod.noisy')
+ meths.set_option_value('operatorfunc', 'v:lua.mymod.noisy', {})
feed('<Esc>g@g@')
eq("hey line", meths.get_current_line())
end)
@@ -539,13 +539,13 @@ describe('v:lua', function()
end)
it('throw errors for invalid use', function()
- eq('Vim(let):E15: Invalid expression: v:lua.func', pcall_err(command, "let g:Func = v:lua.func"))
- eq('Vim(let):E15: Invalid expression: v:lua', pcall_err(command, "let g:Func = v:lua"))
- eq("Vim(let):E15: Invalid expression: v:['lua']", pcall_err(command, "let g:Func = v:['lua']"))
+ eq([[Vim(let):E15: Invalid expression: "v:lua.func"]], pcall_err(command, "let g:Func = v:lua.func"))
+ eq([[Vim(let):E15: Invalid expression: "v:lua"]], pcall_err(command, "let g:Func = v:lua"))
+ eq([[Vim(let):E15: Invalid expression: "v:['lua']"]], pcall_err(command, "let g:Func = v:['lua']"))
- eq("Vim:E15: Invalid expression: v:['lua'].foo()", pcall_err(eval, "v:['lua'].foo()"))
+ eq([[Vim:E15: Invalid expression: "v:['lua'].foo()"]], pcall_err(eval, "v:['lua'].foo()"))
eq("Vim(call):E461: Illegal variable name: v:['lua']", pcall_err(command, "call v:['lua'].baar()"))
- eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "v:lua()"))
+ eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "v:lua()"))
eq("Vim(let):E46: Cannot change read-only variable \"v:['lua']\"", pcall_err(command, "let v:['lua'] = 'xx'"))
eq("Vim(let):E46: Cannot change read-only variable \"v:lua\"", pcall_err(command, "let v:lua = 'xx'"))
@@ -553,7 +553,7 @@ describe('v:lua', function()
eq("Vim:E107: Missing parentheses: v:lua.func", pcall_err(eval, "'bad'->v:lua.func"))
eq("Vim:E274: No white space allowed before parenthesis", pcall_err(eval, "'bad'->v:lua.func ()"))
eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua"))
- eq("Vim:E117: Unknown function: v:lua", pcall_err(eval, "'bad'->v:lua()"))
- eq("Vim:E15: Invalid expression: v:lua.()", pcall_err(eval, "'bad'->v:lua.()"))
+ eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "'bad'->v:lua()"))
+ eq([[Vim:E15: Invalid expression: "v:lua.()"]], pcall_err(eval, "'bad'->v:lua.()"))
end)
end)
diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua
index 3f107811ae..c08f3d06a9 100644
--- a/test/functional/lua/overrides_spec.lua
+++ b/test/functional/lua/overrides_spec.lua
@@ -15,8 +15,6 @@ local exec_lua = helpers.exec_lua
local pcall_err = helpers.pcall_err
local is_os = helpers.is_os
-local screen
-
local fname = 'Xtest-functional-lua-overrides-luafile'
before_each(clear)
@@ -56,7 +54,7 @@ describe('print', function()
-- TODO(bfredl): these look weird, print() should not use "E5114:" style errors..
eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: [NULL]',
pcall_err(command, 'lua print("foo", v_nilerr, "bar")'))
- eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:0: abc',
+ eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: Xtest-functional-lua-overrides-luafile:2: abc',
pcall_err(command, 'lua print("foo", v_abcerr, "bar")'))
eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>',
pcall_err(command, 'lua print("foo", v_tblout, "bar")'))
@@ -86,9 +84,9 @@ describe('print', function()
end
]])
eq('', exec_capture('luafile ' .. fname))
- eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: my mistake',
+ eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:1: my mistake',
pcall_err(command, 'lua string_error()'))
- eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: 1234',
+ eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:2: 1234',
pcall_err(command, 'lua number_error()'))
eq('Vim(lua):E5108: Error executing lua [NULL]',
pcall_err(command, 'lua nil_error()'))
@@ -100,7 +98,7 @@ describe('print', function()
pcall_err(command, 'lua bad_custom_error()'))
end)
it('prints strings with NULs and NLs correctly', function()
- meths.set_option('more', true)
+ meths.set_option_value('more', true, {})
eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n',
exec_capture([[lua print("abc \0 def\nghi\0\0\0jkl\nTEST\n\n\nT\n")]]))
eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT^@',
@@ -119,7 +117,7 @@ describe('print', function()
exec_lua([[
local cmd = ...
function test()
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
local done = false
timer:start(10, 0, function()
print("very fast")
@@ -130,7 +128,7 @@ describe('print', function()
-- loop until we know for sure the callback has been executed
while not done do
os.execute(cmd)
- vim.loop.run("nowait") -- fake os_breakcheck()
+ vim.uv.run("nowait") -- fake os_breakcheck()
end
print("very slow")
vim.api.nvim_command("sleep 1m") -- force deferred event processing
@@ -138,9 +136,44 @@ describe('print', function()
]], (is_os('win') and "timeout 1") or "sleep 0.1")
eq('very slow\nvery fast', exec_capture('lua test()'))
end)
+
+ it('blank line in message works', function()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground=Screen.colors.Blue},
+ [1] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [2] = {bold = true, reverse = true},
+ })
+ feed([[:lua print('\na')<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ |
+ a |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ feed('<CR>')
+ feed([[:lua print('b\n\nc')<CR>]])
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2: }|
+ b |
+ |
+ c |
+ {1:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
end)
describe('debug.debug', function()
+ local screen
+
before_each(function()
screen = Screen.new()
screen:attach()
@@ -338,3 +371,11 @@ describe('os.getenv', function()
eq(value, funcs.luaeval('os.getenv("XTEST_1")'))
end)
end)
+
+-- "bit" module is always available, regardless if nvim is built with
+-- luajit or PUC lua 5.1.
+describe('bit module', function()
+ it('works', function()
+ eq (9, exec_lua [[ return require'bit'.band(11,13) ]])
+ end)
+end)
diff --git a/test/functional/lua/runtime_spec.lua b/test/functional/lua/runtime_spec.lua
index 884ef3ef8e..0b8b2234db 100644
--- a/test/functional/lua/runtime_spec.lua
+++ b/test/functional/lua/runtime_spec.lua
@@ -18,7 +18,11 @@ describe('runtime:', function()
io.open(init, 'w'):close() -- touch init file
clear{args = {'-u', init}}
exec('set rtp+=' .. plug_dir)
- exec('set completeslash=slash')
+ exec([[
+ set shell=doesnotexist
+ set completeslash=slash
+ set isfname+=(,)
+ ]])
end)
teardown(function()
@@ -36,10 +40,12 @@ describe('runtime:', function()
describe('colors', function()
local colorscheme_folder = plug_dir .. sep .. 'colors'
-
- it('loads lua colorscheme', function()
- local colorscheme_file = colorscheme_folder .. sep .. 'new_colorscheme.lua'
+ before_each(function()
mkdir_p(colorscheme_folder)
+ end)
+
+ it('lua colorschemes work and are included in cmdline completion', function()
+ local colorscheme_file = table.concat({colorscheme_folder, 'new_colorscheme.lua'}, sep)
write_file(colorscheme_file, [[vim.g.lua_colorscheme = 1]])
eq({'new_colorscheme'}, funcs.getcompletion('new_c', 'color'))
@@ -48,28 +54,64 @@ describe('runtime:', function()
exec('colorscheme new_colorscheme')
eq(1, eval('g:lua_colorscheme'))
- rmdir(colorscheme_folder)
end)
- it('loads vim colorscheme when both lua and vim version exist', function()
- local colorscheme_file = colorscheme_folder .. sep .. 'new_colorscheme'
- mkdir_p(colorscheme_folder)
- write_file(colorscheme_file..'.vim', [[let g:colorscheme = 'vim']])
- write_file(colorscheme_file..'.lua', [[vim.g.colorscheme = 'lua']])
+ it("'rtp'/'pp' order is respected", function()
+ local pack_dir = 'Test_Pack'
+ mkdir_p(pack_dir)
+ finally(function()
+ rmdir(pack_dir)
+ end)
+ exec('set pp+=' .. pack_dir)
+
+ local pack_opt_dir = table.concat({pack_dir, 'pack', 'some_name', 'opt'}, sep)
+ local colors_opt_dir = table.concat({pack_opt_dir, 'some_pack', 'colors'}, sep)
+ mkdir_p(colors_opt_dir)
+
+ local after_colorscheme_folder = table.concat({plug_dir, 'after', 'colors'}, sep)
+ mkdir_p(after_colorscheme_folder)
+ exec('set rtp+=' .. plug_dir .. '/after')
+
+ write_file(table.concat({colors_opt_dir, 'new_colorscheme.lua'}, sep),
+ [[vim.g.colorscheme = 'lua_pp']])
+ exec('colorscheme new_colorscheme')
+ eq('lua_pp', eval('g:colorscheme'))
+
+ write_file(table.concat({colors_opt_dir, 'new_colorscheme.vim'}, sep),
+ [[let g:colorscheme = 'vim_pp']])
+ exec('colorscheme new_colorscheme')
+ eq('vim_pp', eval('g:colorscheme'))
+ write_file(table.concat({after_colorscheme_folder, 'new_colorscheme.lua'}, sep),
+ [[vim.g.colorscheme = 'lua_rtp_after']])
exec('colorscheme new_colorscheme')
+ eq('lua_rtp_after', eval('g:colorscheme'))
- eq('vim', eval('g:colorscheme'))
- rmdir(colorscheme_folder)
+ write_file(table.concat({after_colorscheme_folder, 'new_colorscheme.vim'}, sep),
+ [[let g:colorscheme = 'vim_rtp_after']])
+ exec('colorscheme new_colorscheme')
+ eq('vim_rtp_after', eval('g:colorscheme'))
+
+ write_file(table.concat({colorscheme_folder, 'new_colorscheme.lua'}, sep),
+ [[vim.g.colorscheme = 'lua_rtp']])
+ exec('colorscheme new_colorscheme')
+ eq('lua_rtp', eval('g:colorscheme'))
+
+ write_file(table.concat({colorscheme_folder, 'new_colorscheme.vim'}, sep),
+ [[let g:colorscheme = 'vim_rtp']])
+ exec('colorscheme new_colorscheme')
+ eq('vim_rtp', eval('g:colorscheme'))
end)
end)
describe('compiler', function()
- local compiler_folder = plug_dir .. sep .. 'compiler'
+ local compiler_folder = table.concat({plug_dir, 'compiler'}, sep)
+ before_each(function()
+ mkdir_p(compiler_folder)
+ end)
- it('loads lua compilers', function()
+ it('lua compilers work and are included in cmdline completion', function()
local compiler_file = compiler_folder .. sep .. 'new_compiler.lua'
- mkdir_p(compiler_folder)
write_file(compiler_file, [[vim.b.lua_compiler = 1]])
eq({'new_compiler'}, funcs.getcompletion('new_c', 'compiler'))
@@ -78,28 +120,31 @@ describe('runtime:', function()
exec('compiler new_compiler')
eq(1, eval('b:lua_compiler'))
- rmdir(compiler_folder)
end)
- it('loads vim compilers when both lua and vim version exist', function()
- local compiler_file = compiler_folder .. sep .. 'new_compiler'
- mkdir_p(compiler_folder)
- write_file(compiler_file..'.vim', [[let b:compiler = 'vim']])
- write_file(compiler_file..'.lua', [[vim.b.compiler = 'lua']])
-
+ it("'rtp' order is respected", function()
+ local after_compiler_folder = table.concat({plug_dir, 'after', 'compiler'}, sep)
+ mkdir_p(table.concat({compiler_folder, 'new_compiler'}, sep))
+ mkdir_p(table.concat({after_compiler_folder, 'new_compiler'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/compiler/ are loaded after all files in compiler/.
+ write_file(table.concat({compiler_folder, 'new_compiler.vim'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({compiler_folder, 'new_compiler.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({after_compiler_folder, 'new_compiler.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_compiler_folder, 'new_compiler.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
exec('compiler new_compiler')
-
- eq('vim', eval('b:compiler'))
- rmdir(compiler_folder)
+ eq('ABab', eval('g:seq'))
end)
end)
describe('ftplugin', function()
local ftplugin_folder = table.concat({plug_dir, 'ftplugin'}, sep)
- it('loads lua ftplugins', function()
- local ftplugin_file = table.concat({ftplugin_folder , 'new-ft.lua'}, sep)
+ it('lua ftplugins work and are included in cmdline completion', function()
mkdir_p(ftplugin_folder)
+ local ftplugin_file = table.concat({ftplugin_folder , 'new-ft.lua'}, sep)
write_file(ftplugin_file , [[vim.b.lua_ftplugin = 1]])
eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype'))
@@ -107,16 +152,64 @@ describe('runtime:', function()
exec [[set filetype=new-ft]]
eq(1, eval('b:lua_ftplugin'))
- rmdir(ftplugin_folder)
+ end)
+
+ it("'rtp' order is respected", function()
+ local after_ftplugin_folder = table.concat({plug_dir, 'after', 'ftplugin'}, sep)
+ mkdir_p(table.concat({ftplugin_folder, 'new-ft'}, sep))
+ mkdir_p(table.concat({after_ftplugin_folder, 'new-ft'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/ftplugin/ are loaded after all files in ftplugin/.
+ write_file(table.concat({ftplugin_folder, 'new-ft.vim'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({ftplugin_folder, 'new-ft.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({ftplugin_folder, 'new-ft_a.vim'}, sep), [[let g:seq ..= 'C']])
+ write_file(table.concat({ftplugin_folder, 'new-ft_a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'D']])
+ write_file(table.concat({ftplugin_folder, 'new-ft', 'a.vim'}, sep), [[let g:seq ..= 'E']])
+ write_file(table.concat({ftplugin_folder, 'new-ft', 'a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'F']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft_a.vim'}, sep), [[let g:seq ..= 'c']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft_a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'd']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft', 'a.vim'}, sep), [[let g:seq ..= 'e']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft', 'a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'f']])
+ exec('setfiletype new-ft')
+ eq('ABCDEFabcdef', eval('g:seq'))
+ end)
+
+ it("'rtp' order is respected with 'fileignorecase'", function()
+ exec('set fileignorecase')
+ local after_ftplugin_folder = table.concat({plug_dir, 'after', 'ftplugin'}, sep)
+ mkdir_p(table.concat({ftplugin_folder, 'new-ft'}, sep))
+ mkdir_p(table.concat({after_ftplugin_folder, 'new-ft'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/ftplugin/ are loaded after all files in ftplugin/.
+ write_file(table.concat({ftplugin_folder, 'new-ft.VIM'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({ftplugin_folder, 'new-ft.LUA'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({ftplugin_folder, 'new-ft_a.vim'}, sep), [[let g:seq ..= 'C']])
+ write_file(table.concat({ftplugin_folder, 'new-ft_a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'D']])
+ write_file(table.concat({ftplugin_folder, 'new-ft', 'a.VIM'}, sep), [[let g:seq ..= 'E']])
+ write_file(table.concat({ftplugin_folder, 'new-ft', 'a.LUA'}, sep), [[vim.g.seq = vim.g.seq .. 'F']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft_a.VIM'}, sep), [[let g:seq ..= 'c']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft_a.LUA'}, sep), [[vim.g.seq = vim.g.seq .. 'd']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft', 'a.vim'}, sep), [[let g:seq ..= 'e']])
+ write_file(table.concat({after_ftplugin_folder, 'new-ft', 'a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'f']])
+ exec('setfiletype new-ft')
+ eq('ABCDEFabcdef', eval('g:seq'))
end)
end)
describe('indent', function()
local indent_folder = table.concat({plug_dir, 'indent'}, sep)
- it('loads lua indents', function()
- local indent_file = table.concat({indent_folder , 'new-ft.lua'}, sep)
+ it('lua indents work and are included in cmdline completion', function()
mkdir_p(indent_folder)
+ local indent_file = table.concat({indent_folder , 'new-ft.lua'}, sep)
write_file(indent_file , [[vim.b.lua_indent = 1]])
eq({'new-ft'}, funcs.getcompletion('new-f', 'filetype'))
@@ -124,7 +217,22 @@ describe('runtime:', function()
exec [[set filetype=new-ft]]
eq(1, eval('b:lua_indent'))
- rmdir(indent_folder)
+ end)
+
+ it("'rtp' order is respected", function()
+ local after_indent_folder = table.concat({plug_dir, 'after', 'indent'}, sep)
+ mkdir_p(table.concat({indent_folder, 'new-ft'}, sep))
+ mkdir_p(table.concat({after_indent_folder, 'new-ft'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/indent/ are loaded after all files in indent/.
+ write_file(table.concat({indent_folder, 'new-ft.vim'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({indent_folder, 'new-ft.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({after_indent_folder, 'new-ft.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_indent_folder, 'new-ft.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
+ exec('setfiletype new-ft')
+ eq('ABab', eval('g:seq'))
end)
end)
@@ -132,8 +240,8 @@ describe('runtime:', function()
local syntax_folder = table.concat({plug_dir, 'syntax'}, sep)
before_each(function()
- local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep)
mkdir_p(syntax_folder)
+ local syntax_file = table.concat({syntax_folder , 'my-lang.lua'}, sep)
write_file(syntax_file , [[vim.b.current_syntax = 'my-lang']])
exec([[let b:current_syntax = '']])
end)
@@ -159,7 +267,45 @@ describe('runtime:', function()
eq({'my-lang'}, funcs.getcompletion('my-l', 'syntax'))
eq({'syntax/my-lang.lua'}, funcs.getcompletion('syntax/my-l', 'runtime'))
end)
+
+ it("'rtp' order is respected", function()
+ local after_syntax_folder = table.concat({plug_dir, 'after', 'syntax'}, sep)
+ mkdir_p(table.concat({syntax_folder, 'my-lang'}, sep))
+ mkdir_p(table.concat({after_syntax_folder, 'my-lang'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/syntax/ are loaded after all files in syntax/.
+ write_file(table.concat({syntax_folder, 'my-lang.vim'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({syntax_folder, 'my-lang.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({syntax_folder, 'my-lang', 'a.vim'}, sep), [[let g:seq ..= 'C']])
+ write_file(table.concat({syntax_folder, 'my-lang', 'a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'D']])
+ write_file(table.concat({after_syntax_folder, 'my-lang.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_syntax_folder, 'my-lang.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
+ write_file(table.concat({after_syntax_folder, 'my-lang', 'a.vim'}, sep), [[let g:seq ..= 'c']])
+ write_file(table.concat({after_syntax_folder, 'my-lang', 'a.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'd']])
+ exec('setfiletype my-lang')
+ eq('ABCDabcd', eval('g:seq'))
+ end)
end)
-end)
+ describe('spell', function()
+ it("loads spell/LANG.{vim,lua} respecting 'rtp' order", function()
+ local spell_folder = table.concat({plug_dir, 'spell'}, sep)
+ local after_spell_folder = table.concat({plug_dir, 'after', 'spell'}, sep)
+ mkdir_p(table.concat({spell_folder, 'Xtest'}, sep))
+ mkdir_p(table.concat({after_spell_folder, 'Xtest'}, sep))
+ exec('set rtp+=' .. plug_dir .. '/after')
+ exec('let g:seq = ""')
+ -- A .lua file is loaded after a .vim file if they only differ in extension.
+ -- All files in after/spell/ are loaded after all files in spell/.
+ write_file(table.concat({spell_folder, 'Xtest.vim'}, sep), [[let g:seq ..= 'A']])
+ write_file(table.concat({spell_folder, 'Xtest.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'B']])
+ write_file(table.concat({after_spell_folder, 'Xtest.vim'}, sep), [[let g:seq ..= 'a']])
+ write_file(table.concat({after_spell_folder, 'Xtest.lua'}, sep), [[vim.g.seq = vim.g.seq .. 'b']])
+ exec('set spelllang=Xtest')
+ eq('ABab', eval('g:seq'))
+ end)
+ end)
+end)
diff --git a/test/functional/lua/secure_spec.lua b/test/functional/lua/secure_spec.lua
index 2647b2be46..fc20a06390 100644
--- a/test/functional/lua/secure_spec.lua
+++ b/test/functional/lua/secure_spec.lua
@@ -6,7 +6,7 @@ local clear = helpers.clear
local command = helpers.command
local pathsep = helpers.get_pathsep()
local is_os = helpers.is_os
-local curbufmeths = helpers.curbufmeths
+local meths = helpers.meths
local exec_lua = helpers.exec_lua
local feed_command = helpers.feed_command
local feed = helpers.feed
@@ -19,21 +19,14 @@ describe('vim.secure', function()
local xstate = 'Xstate'
setup(function()
+ clear{env={XDG_STATE_HOME=xstate}}
helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
- end)
-
- teardown(function()
- helpers.rmdir(xstate)
- end)
-
- before_each(function()
helpers.write_file('Xfile', [[
let g:foobar = 42
]])
- clear{env={XDG_STATE_HOME=xstate}}
end)
- after_each(function()
+ teardown(function()
os.remove('Xfile')
helpers.rmdir(xstate)
end)
@@ -48,6 +41,7 @@ describe('vim.secure', function()
[4] = {reverse = true},
})
+ --- XXX: screen:expect() may fail if this path is too long.
local cwd = funcs.getcwd()
-- Need to use feed_command instead of exec_lua because of the confirmation prompt
@@ -59,7 +53,7 @@ describe('vim.secure', function()
{1:~ }|
{2: }|
:lua vim.secure.read('Xfile') |
- {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
+ {3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH:%s+}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
]]}
feed('d')
@@ -88,7 +82,7 @@ describe('vim.secure', function()
{1:~ }|
{2: }|
:lua vim.secure.read('Xfile') |
- {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
+ {3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH:%s+}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
]]}
feed('a')
@@ -118,7 +112,7 @@ describe('vim.secure', function()
{1:~ }|
{2: }|
:lua vim.secure.read('Xfile') |
- {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
+ {3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH:%s+}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
]]}
feed('i')
@@ -145,7 +139,7 @@ describe('vim.secure', function()
{1:~ }|
{2: }|
:lua vim.secure.read('Xfile') |
- {3:]] .. cwd .. pathsep .. [[Xfile is untrusted}{MATCH:%s+}|
+ {3:]] .. cwd .. pathsep .. [[Xfile is not trusted.}{MATCH:%s+}|
{3:[i]gnore, (v)iew, (d)eny, (a)llow: }^ |
]]}
feed('v')
@@ -153,7 +147,7 @@ describe('vim.secure', function()
^let g:foobar = 42 |
{1:~ }|
{1:~ }|
- {2:]] .. funcs.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH:%s+}|
+ {2:]] .. funcs.fnamemodify(cwd, ':~') .. pathsep .. [[Xfile [RO]{MATCH:%s+}}|
|
{1:~ }|
{4:[No Name] }|
@@ -166,7 +160,7 @@ describe('vim.secure', function()
-- Cannot write file
pcall_err(command, 'write')
- eq(true, curbufmeths.get_option('readonly'))
+ eq(true, meths.get_option_value('readonly', {}))
end)
end)
@@ -174,6 +168,7 @@ describe('vim.secure', function()
local xstate = 'Xstate'
setup(function()
+ clear{env={XDG_STATE_HOME=xstate}}
helpers.mkdir_p(xstate .. pathsep .. (is_os('win') and 'nvim-data' or 'nvim'))
end)
diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua
new file mode 100644
index 0000000000..f0b3b44139
--- /dev/null
+++ b/test/functional/lua/snippet_spec.lua
@@ -0,0 +1,202 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local buf_lines = helpers.buf_lines
+local clear = helpers.clear
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local matches = helpers.matches
+local pcall_err = helpers.pcall_err
+local sleep = helpers.sleep
+
+describe('vim.snippet', function()
+ before_each(function()
+ clear()
+
+ exec_lua([[
+ vim.keymap.set({ 'i', 's' }, '<Tab>', function() vim.snippet.jump(1) end, { buffer = true })
+ vim.keymap.set({ 'i', 's' }, '<S-Tab>', function() vim.snippet.jump(-1) end, { buffer = true })
+ ]])
+ end)
+ after_each(clear)
+
+ --- @param snippet string[]
+ --- @param expected string[]
+ --- @param settings? string
+ --- @param prefix? string
+ local function test_expand_success(snippet, expected, settings, prefix)
+ if settings then
+ exec_lua(settings)
+ end
+ if prefix then
+ feed('i' .. prefix)
+ end
+ exec_lua('vim.snippet.expand(...)', table.concat(snippet, '\n'))
+ eq(expected, buf_lines(0))
+ end
+
+ --- @param snippet string
+ --- @param err string
+ local function test_expand_fail(snippet, err)
+ matches(err, pcall_err(exec_lua, string.format('vim.snippet.expand("%s")', snippet)))
+ end
+
+ it('adds base indentation to inserted text', function()
+ test_expand_success(
+ { 'function $1($2)', ' $0', 'end' },
+ { ' function ()', ' ', ' end' },
+ '',
+ ' '
+ )
+ end)
+
+ it('adds indentation based on the start of snippet lines', function()
+ test_expand_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' })
+ end)
+
+ it('replaces tabs with spaces when expandtab is set', function()
+ test_expand_success(
+ { 'function $1($2)', '\t$0', 'end' },
+ { 'function ()', ' ', 'end' },
+ [[
+ vim.o.expandtab = true
+ vim.o.shiftwidth = 2
+ ]]
+ )
+ end)
+
+ it('respects tabs when expandtab is not set', function()
+ test_expand_success(
+ { 'function $1($2)', '\t$0', 'end' },
+ { 'function ()', '\t', 'end' },
+ 'vim.o.expandtab = false'
+ )
+ end)
+
+ it('inserts known variable value', function()
+ test_expand_success({ '; print($TM_CURRENT_LINE)' }, { 'foo; print(foo)' }, nil, 'foo')
+ end)
+
+ it('uses default when variable is not set', function()
+ test_expand_success({ 'print(${TM_CURRENT_WORD:foo})' }, { 'print(foo)' })
+ end)
+
+ it('replaces unknown variables by placeholders', function()
+ test_expand_success({ 'print($UNKNOWN)' }, { 'print(UNKNOWN)' })
+ end)
+
+ it('does not jump outside snippet range', function()
+ test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
+ eq(false, exec_lua('return vim.snippet.jumpable(-1)'))
+ feed('<Tab><Tab>i')
+ eq(false, exec_lua('return vim.snippet.jumpable(1)'))
+ end)
+
+ it('navigates backwards', function()
+ test_expand_success({ 'function $1($2) end' }, { 'function () end' })
+ feed('<Tab><S-Tab>foo')
+ eq({ 'function foo() end' }, buf_lines(0))
+ end)
+
+ it('visits all tabstops', function()
+ local function cursor()
+ return exec_lua('return vim.api.nvim_win_get_cursor(0)')
+ end
+
+ test_expand_success({ 'function $1($2)', ' $0', 'end' }, { 'function ()', ' ', 'end' })
+ eq({ 1, 9 }, cursor())
+ feed('<Tab>')
+ eq({ 1, 10 }, cursor())
+ feed('<Tab>')
+ eq({ 2, 2 }, cursor())
+ end)
+
+ it('syncs text of tabstops with equal indexes', function()
+ test_expand_success({ 'var double = ${1:x} + ${1:x}' }, { 'var double = x + x' })
+ feed('123')
+ eq({ 'var double = 123 + 123' }, buf_lines(0))
+ end)
+
+ it('cancels session with changes outside the snippet', function()
+ test_expand_success({ 'print($1)' }, { 'print()' })
+ feed('<Esc>O-- A comment')
+ eq(false, exec_lua('return vim.snippet.active()'))
+ eq({ '-- A comment', 'print()' }, buf_lines(0))
+ end)
+
+ it('handles non-consecutive tabstops', function()
+ test_expand_success({ 'class $1($3) {', ' $0', '}' }, { 'class () {', ' ', '}' })
+ feed('Foo') -- First tabstop
+ feed('<Tab><Tab>') -- Jump to $0
+ feed('// Inside') -- Insert text
+ eq({ 'class Foo() {', ' // Inside', '}' }, buf_lines(0))
+ end)
+
+ it('handles multiline placeholders', function()
+ test_expand_success(
+ { 'public void foo() {', ' ${0:// TODO Auto-generated', ' throw;}', '}' },
+ { 'public void foo() {', ' // TODO Auto-generated', ' throw;', '}' }
+ )
+ end)
+
+ it('inserts placeholder in all tabstops when the first tabstop has the placeholder', function()
+ test_expand_success(
+ { 'for (${1:int} ${2:x} = ${3:0}; $2 < ${4:N}; $2++) {', ' $0', '}' },
+ { 'for (int x = 0; x < N; x++) {', ' ', '}' }
+ )
+ end)
+
+ it('inserts placeholder in all tabstops when a later tabstop has the placeholder', function()
+ test_expand_success(
+ { 'for (${1:int} $2 = ${3:0}; ${2:x} < ${4:N}; $2++) {', ' $0', '}' },
+ { 'for (int x = 0; x < N; x++) {', ' ', '}' }
+ )
+ end)
+
+ it('errors with multiple placeholders for the same index', function()
+ test_expand_fail('class ${1:Foo} { void ${1:foo}() {} }', 'multiple placeholders for tabstop $1')
+ end)
+
+ it('errors with multiple $0 tabstops', function()
+ test_expand_fail('function $1() { $0 }$0', 'multiple $0 tabstops')
+ end)
+
+ it('cancels session when deleting the snippet', function()
+ test_expand_success({ 'local function $1()', ' $0', 'end' }, { 'local function ()', ' ', 'end' })
+ feed('<esc>Vjjd')
+ eq(false, exec_lua('return vim.snippet.active()'))
+ end)
+
+ it('cancels session when inserting outside snippet region', function()
+ feed('i<cr>')
+ test_expand_success({ 'local function $1()', ' $0', 'end' }, { '', 'local function ()', ' ', 'end' })
+ feed('<esc>O-- A comment')
+ eq(false, exec_lua('return vim.snippet.active()'))
+ end)
+
+ it('inserts choice', function ()
+ test_expand_success({ 'console.${1|assert,log,error|}()' }, { 'console.()' })
+ sleep(100)
+ feed('<Down><C-y>')
+ eq({ 'console.log()' }, buf_lines(0))
+ end)
+
+ it('closes the choice completion menu when jumping', function ()
+ test_expand_success({ 'console.${1|assert,log,error|}($2)' }, { 'console.()' })
+ sleep(100)
+ exec_lua('vim.snippet.jump(1)')
+ eq(0, exec_lua('return vim.fn.pumvisible()'))
+ end)
+
+ it('jumps to next tabstop after inserting choice', function()
+ test_expand_success(
+ { '${1|public,protected,private|} function ${2:name}() {', '\t$0', '}' },
+ { ' function name() {', '\t', '}' }
+ )
+ sleep(100)
+ feed('<C-y><Tab>')
+ sleep(10)
+ feed('foo')
+ eq({ 'public function foo() {', '\t', '}' }, buf_lines(0))
+ end)
+end)
diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua
new file mode 100644
index 0000000000..a988d3f0d7
--- /dev/null
+++ b/test/functional/lua/system_spec.lua
@@ -0,0 +1,100 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local exec_lua = helpers.exec_lua
+local eq = helpers.eq
+
+local function system_sync(cmd, opts)
+ return exec_lua([[
+ local cmd, opts = ...
+ local obj = vim.system(...)
+
+ if opts.timeout then
+ -- Minor delay before calling wait() so the timeout uv timer can have a headstart over the
+ -- internal call to vim.wait() in wait().
+ vim.wait(10)
+ end
+
+ local res = obj:wait()
+
+ -- Check the process is no longer running
+ local proc = vim.api.nvim_get_proc(obj.pid)
+ assert(not proc, 'process still exists')
+
+ return res
+ ]], cmd, opts)
+end
+
+local function system_async(cmd, opts)
+ return exec_lua([[
+ local cmd, opts = ...
+ _G.done = false
+ local obj = vim.system(cmd, opts, function(obj)
+ _G.done = true
+ _G.ret = obj
+ end)
+
+ local ok = vim.wait(10000, function()
+ return _G.done
+ end)
+
+ assert(ok, 'process did not exit')
+
+ -- Check the process is no longer running
+ local proc = vim.api.nvim_get_proc(obj.pid)
+ assert(not proc, 'process still exists')
+
+ return _G.ret
+ ]], cmd, opts)
+end
+
+describe('vim.system', function()
+ before_each(function()
+ clear()
+ end)
+
+ for name, system in pairs{ sync = system_sync, async = system_async, } do
+ describe('('..name..')', function()
+ it('can run simple commands', function()
+ eq('hello\n', system({'echo', 'hello' }, { text = true }).stdout)
+ end)
+
+ it('handle input', function()
+ eq('hellocat', system({ 'cat' }, { stdin = 'hellocat', text = true }).stdout)
+ end)
+
+ it('supports timeout', function()
+ eq({
+ code = 124,
+ signal = 15,
+ stdout = '',
+ stderr = ''
+ }, system({ 'sleep', '10' }, { timeout = 1000 }))
+ end)
+ end)
+ end
+
+ it('kill processes', function()
+ exec_lua([[
+ local signal
+ local cmd = vim.system({ 'cat', '-' }, { stdin = true }, function(r)
+ signal = r.signal
+ end) -- run forever
+
+ cmd:kill('sigint')
+
+ -- wait for the process not to exist
+ local done = vim.wait(2000, function()
+ return signal ~= nil
+ end)
+
+ assert(done, 'process did not exit')
+
+ -- Check the process is no longer running
+ local proc = vim.api.nvim_get_proc(cmd.pid)
+ assert(not proc, 'process still exists')
+
+ assert(signal == 2)
+ ]])
+ end)
+
+end)
diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua
new file mode 100644
index 0000000000..68206557c3
--- /dev/null
+++ b/test/functional/lua/text_spec.lua
@@ -0,0 +1,23 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+
+describe('vim.text', function()
+ before_each(clear)
+
+ describe('hexencode() and hexdecode()', function()
+ it('works', function()
+ local cases = {
+ { 'Hello world!', '48656C6C6F20776F726C6421' },
+ { '😂', 'F09F9882' },
+ }
+
+ for _, v in ipairs(cases) do
+ local input, output = unpack(v)
+ eq(output, vim.text.hexencode(input))
+ eq(input, vim.text.hexdecode(output))
+ end
+ end)
+ end)
+end)
+
diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua
index c7f2783cf3..e79d26a641 100644
--- a/test/functional/lua/thread_spec.lua
+++ b/test/functional/lua/thread_spec.lua
@@ -27,10 +27,10 @@ describe('thread', function()
it('entry func is executed in protected mode', function()
exec_lua [[
- local thread = vim.loop.new_thread(function()
+ local thread = vim.uv.new_thread(function()
error('Error in thread entry func')
end)
- vim.loop.thread_join(thread)
+ vim.uv.thread_join(thread)
]]
screen:expect([[
@@ -51,17 +51,17 @@ describe('thread', function()
it('callback is executed in protected mode', function()
exec_lua [[
- local thread = vim.loop.new_thread(function()
- local timer = vim.loop.new_timer()
+ local thread = vim.uv.new_thread(function()
+ local timer = vim.uv.new_timer()
local function ontimeout()
timer:stop()
timer:close()
error('Error in thread callback')
end
timer:start(10, 0, ontimeout)
- vim.loop.run()
+ vim.uv.run()
end)
- vim.loop.thread_join(thread)
+ vim.uv.thread_join(thread)
]]
screen:expect([[
@@ -83,10 +83,10 @@ describe('thread', function()
describe('print', function()
it('works', function()
exec_lua [[
- local thread = vim.loop.new_thread(function()
+ local thread = vim.uv.new_thread(function()
print('print in thread')
end)
- vim.loop.thread_join(thread)
+ vim.uv.thread_join(thread)
]]
screen:expect([[
@@ -105,10 +105,10 @@ describe('thread', function()
it('vim.inspect', function()
exec_lua [[
- local thread = vim.loop.new_thread(function()
+ local thread = vim.uv.new_thread(function()
print(vim.inspect({1,2}))
end)
- vim.loop.thread_join(thread)
+ vim.uv.thread_join(thread)
]]
screen:expect([[
@@ -140,13 +140,13 @@ describe('thread', function()
function Thread_Test:do_test()
local async
local on_async = self.on_async
- async = vim.loop.new_async(function(ret)
+ async = vim.uv.new_async(function(ret)
on_async(ret)
async:close()
end)
local thread =
- vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args)
- vim.loop.thread_join(thread)
+ vim.uv.new_thread(self.entry_func, async, self.entry_str, self.args)
+ vim.uv.thread_join(thread)
end
Thread_Test.new = function(entry, on_async, ...)
@@ -175,10 +175,10 @@ describe('thread', function()
eq({'notification', 'result', {true}}, next_msg())
end)
- it('loop', function()
+ it('uv', function()
exec_lua [[
local entry = function(async)
- async:send(vim.loop.version())
+ async:send(vim.uv.version())
end
local on_async = function(ret)
vim.rpcnotify(1, ret)
@@ -259,7 +259,7 @@ describe('threadpool', function()
local after_work_fn = function(ret)
vim.rpcnotify(1, 'result', ret)
end
- local work = vim.loop.new_work(work_fn, after_work_fn)
+ local work = vim.uv.new_work(work_fn, after_work_fn)
work:queue()
]]
@@ -268,7 +268,7 @@ describe('threadpool', function()
it('with invalid argument', function()
local status = pcall_err(exec_lua, [[
- local work = vim.loop.new_thread(function() end, function() end)
+ local work = vim.uv.new_thread(function() end, function() end)
work:queue({})
]])
@@ -288,7 +288,7 @@ describe('threadpool', function()
})
exec_lua [[
- local work = vim.loop.new_work(function() return {} end, function() end)
+ local work = vim.uv.new_work(function() return {} end, function() end)
work:queue()
]]
@@ -319,7 +319,7 @@ describe('threadpool', function()
function Threadpool_Test:do_test()
local work =
- vim.loop.new_work(self.work_fn, self.after_work)
+ vim.uv.new_work(self.work_fn, self.after_work)
work:queue(self.work_fn_str, self.args)
end
@@ -334,10 +334,10 @@ describe('threadpool', function()
]]
end)
- it('loop', function()
+ it('uv', function()
exec_lua [[
local work_fn = function()
- return vim.loop.version()
+ return vim.uv.version()
end
local after_work_fn = function(ret)
vim.rpcnotify(1, ret)
diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua
index 6481da900e..373d45da61 100644
--- a/test/functional/lua/ui_event_spec.lua
+++ b/test/functional/lua/ui_event_spec.lua
@@ -77,13 +77,7 @@ describe('vim.ui_attach', function()
}
feed '<c-y>'
- screen:expect{grid=[[
- foobar^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]], intermediate=true}
+ screen:expect_unchanged()
expect_events {
{ "popupmenu_hide" };
}
diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua
index 9ee99b4905..d4c150c5f2 100644
--- a/test/functional/lua/ui_spec.lua
+++ b/test/functional/lua/ui_spec.lua
@@ -1,9 +1,12 @@
local helpers = require('test.functional.helpers')(after_each)
local eq = helpers.eq
+local matches = helpers.matches
local exec_lua = helpers.exec_lua
local clear = helpers.clear
local feed = helpers.feed
local eval = helpers.eval
+local is_ci = helpers.is_ci
+local is_os = helpers.is_os
local poke_eventloop = helpers.poke_eventloop
describe('vim.ui', function()
@@ -11,8 +14,7 @@ describe('vim.ui', function()
clear()
end)
-
- describe('select', function()
+ describe('select()', function()
it('can select an item', function()
local result = exec_lua[[
local items = {
@@ -47,7 +49,7 @@ describe('vim.ui', function()
end)
end)
- describe('input', function()
+ describe('input()', function()
it('can input text', function()
local result = exec_lua[[
local opts = {
@@ -130,4 +132,23 @@ describe('vim.ui', function()
end)
end)
+
+ describe('open()', function()
+ it('validation', function()
+ if is_os('win') or not is_ci('github') then
+ exec_lua[[vim.system = function() return { wait=function() return { code=3} end } end]]
+ end
+ if not is_os('bsd') then
+ matches('vim.ui.open: command failed %(%d%): { "[^"]+", .*"non%-existent%-file" }',
+ exec_lua[[local _, err = vim.ui.open('non-existent-file') ; return err]])
+ end
+
+ exec_lua[[
+ vim.fn.has = function() return 0 end
+ vim.fn.executable = function() return 0 end
+ ]]
+ eq('vim.ui.open: no handler found (tried: wslview, xdg-open)',
+ exec_lua[[local _, err = vim.ui.open('foo') ; return err]])
+ end)
+ end)
end)
diff --git a/test/functional/lua/version_spec.lua b/test/functional/lua/version_spec.lua
new file mode 100644
index 0000000000..d1c981c388
--- /dev/null
+++ b/test/functional/lua/version_spec.lua
@@ -0,0 +1,273 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local ok = helpers.ok
+local exec_lua = helpers.exec_lua
+local matches = helpers.matches
+local pcall_err = helpers.pcall_err
+
+local function v(ver)
+ return vim.version._version(ver)
+end
+
+describe('version', function()
+
+ it('package', function()
+ clear()
+ eq({ major = 42, minor = 3, patch = 99 }, exec_lua("return vim.version.parse('v42.3.99')"))
+ end)
+
+ it('version() returns Nvim version', function()
+ local expected = exec_lua('return vim.fn.api_info().version')
+ local actual = exec_lua('return vim.version()')
+ eq(expected.major, actual.major)
+ eq(expected.minor, actual.minor)
+ eq(expected.patch, actual.patch)
+ eq(expected.prerelease and 'dev' or nil, actual.prerelease)
+
+ -- tostring() #23863
+ matches([[%d+%.%d+%.%d+]], exec_lua('return tostring(vim.version())'))
+ end)
+
+ describe('_version()', function()
+ local tests = {
+ ['v1.2.3'] = { major = 1, minor = 2, patch = 3 },
+ ['v1.2'] = { major = 1, minor = 2, patch = 0 },
+ ['v1.2.3-prerelease'] = { major = 1, minor = 2, patch = 3, prerelease = 'prerelease' },
+ ['v1.2-prerelease'] = { major = 1, minor = 2, patch = 0, prerelease = 'prerelease' },
+ ['v1.2.3-prerelease+build'] = { major = 1, minor = 2, patch = 3, prerelease = 'prerelease', build = 'build' },
+ ['1.2.3+build'] = { major = 1, minor = 2, patch = 3, build = 'build' },
+ }
+ for input, output in pairs(tests) do
+ it('parses ' .. input, function()
+ eq(output, v(input))
+ end)
+ end
+ end)
+
+ describe('range', function()
+ local tests = {
+ ['1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } },
+ ['1.2'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['=1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 2, 4 } },
+ ['>1.2.3'] = { from = { 1, 2, 4 } },
+ ['>=1.2.3'] = { from = { 1, 2, 3 } },
+ ['<1.2.3'] = { from = { 0, 0, 0 }, to = { 1, 2, 3 } },
+ ['<=1.2.3'] = { from = { 0, 0, 0 }, to = { 1, 2, 4 } },
+ ['~1.2.3'] = { from = { 1, 2, 3 }, to = { 1, 3, 0 } },
+ ['^1.2.3'] = { from = { 1, 2, 3 }, to = { 2, 0, 0 } },
+ ['^0.2.3'] = { from = { 0, 2, 3 }, to = { 0, 3, 0 } },
+ ['^0.0.1'] = { from = { 0, 0, 1 }, to = { 0, 0, 2 } },
+ ['^1.2'] = { from = { 1, 2, 0 }, to = { 2, 0, 0 } },
+ ['~1.2'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['~1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['^1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.*'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.x'] = { from = { 1, 0, 0 }, to = { 2, 0, 0 } },
+ ['1.2.x'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['1.2.*'] = { from = { 1, 2, 0 }, to = { 1, 3, 0 } },
+ ['*'] = { from = { 0, 0, 0 } },
+ ['1.2 - 2.3.0'] = { from = { 1, 2, 0 }, to = { 2, 3, 0 } },
+ ['1.2.3 - 2.3.4'] = { from = { 1, 2, 3 }, to = { 2, 3, 4 } },
+ ['1.2.3 - 2'] = { from = { 1, 2, 3 }, to = { 3, 0, 0 } },
+ }
+ for input, output in pairs(tests) do
+ output.from = v(output.from)
+ output.to = output.to and v(output.to)
+ local range = vim.version.range(input)
+
+ it('parses ' .. input, function()
+ eq(output, range)
+ end)
+
+ it('[from] in range ' .. input, function()
+ assert(range:has(output.from))
+ end)
+
+ it('[from-1] not in range ' .. input, function()
+ local lower = vim.deepcopy(range.from)
+ lower.major = lower.major - 1
+ assert(not range:has(lower))
+ end)
+
+ it('[to] not in range ' .. input .. ' to:' .. tostring(range.to), function()
+ if range.to then
+ assert(not (range.to < range.to))
+ assert(not range:has(range.to))
+ end
+ end)
+ end
+
+ it('handles prerelease', function()
+ assert(not vim.version.range('1.2.3'):has('1.2.3-alpha'))
+ assert(vim.version.range('1.2.3-alpha'):has('1.2.3-alpha'))
+ assert(not vim.version.range('1.2.3-alpha'):has('1.2.3-beta'))
+ end)
+ end)
+
+ describe('cmp()', function()
+ local testcases = {
+ { v1 = 'v0.0.99', v2 = 'v9.0.0', want = -1, },
+ { v1 = 'v0.4.0', v2 = 'v0.9.99', want = -1, },
+ { v1 = 'v0.2.8', v2 = 'v1.0.9', want = -1, },
+ { v1 = 'v0.0.0', v2 = 'v0.0.0', want = 0, },
+ { v1 = 'v9.0.0', v2 = 'v0.9.0', want = 1, },
+ { v1 = 'v0.9.0', v2 = 'v0.0.0', want = 1, },
+ { v1 = 'v0.0.9', v2 = 'v0.0.0', want = 1, },
+ { v1 = 'v0.0.9+aaa', v2 = 'v0.0.9+bbb', want = 0, },
+
+ -- prerelease 💩 https://semver.org/#spec-item-11
+ { v1 = 'v1.0.0-alpha', v2 = 'v1.0.0', want = -1, },
+ { v1 = '1.0.0', v2 = '1.0.0-alpha', want = 1, },
+ { v1 = '1.0.0-2', v2 = '1.0.0-1', want = 1, },
+ { v1 = '1.0.0-2', v2 = '1.0.0-9', want = -1, },
+ { v1 = '1.0.0-2', v2 = '1.0.0-2.0', want = -1, },
+ { v1 = '1.0.0-2.0', v2 = '1.0.0-2', want = 1, },
+ { v1 = '1.0.0-2.0', v2 = '1.0.0-2.0', want = 0, },
+ { v1 = '1.0.0-alpha', v2 = '1.0.0-alpha', want = 0, },
+ { v1 = '1.0.0-alpha', v2 = '1.0.0-beta', want = -1, },
+ { v1 = '1.0.0-beta', v2 = '1.0.0-alpha', want = 1, },
+ { v1 = '1.0.0-alpha', v2 = '1.0.0-alpha.1', want = -1, },
+ { v1 = '1.0.0-alpha.1', v2 = '1.0.0-alpha', want = 1, },
+ { v1 = '1.0.0-alpha.beta', v2 = '1.0.0-alpha', want = 1, },
+ { v1 = '1.0.0-alpha', v2 = '1.0.0-alpha.beta', want = -1, },
+ { v1 = '1.0.0-alpha.beta', v2 = '1.0.0-beta', want = -1, },
+ { v1 = '1.0.0-beta.2', v2 = '1.0.0-beta.11', want = -1, },
+ { v1 = '1.0.0-beta.20', v2 = '1.0.0-beta.11', want = 1, },
+ { v1 = '1.0.0-alpha.20', v2 = '1.0.0-beta.11', want = -1, },
+ { v1 = '1.0.0-a.01.x.3', v2 = '1.0.0-a.1.x.003', want = 0, },
+ { v1 = 'v0.9.0-dev-92+9', v2 = 'v0.9.0-dev-120+3', want = -1, },
+ }
+ for _, tc in ipairs(testcases) do
+ local msg = function(s) return ('v1 %s v2'):format(s == 0 and '==' or (s == 1 and '>' or '<')) end
+ it(string.format('(v1 = %s, v2 = %s)', tc.v1, tc.v2),
+ function()
+ local rv = vim.version.cmp(tc.v1, tc.v2, { strict = true })
+ ok(tc.want == rv, msg(tc.want), msg(rv))
+ end
+ )
+ end
+ end)
+
+ describe('parse()', function()
+ describe('strict=true', function()
+ local testcases = {
+ { desc = 'Nvim version', version = 'v0.9.0-dev-1233+g210120dde81e', want = { major = 0, minor = 9, patch = 0, prerelease = 'dev-1233', build = 'g210120dde81e', }, },
+ { desc = 'no v', version = '10.20.123', want = { major = 10, minor = 20, patch = 123, prerelease = nil, build = nil, }, },
+ { desc = 'with v', version = 'v1.2.3', want = { major = 1, minor = 2, patch = 3 }, },
+ { desc = 'prerelease', version = '1.2.3-alpha', want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha' }, },
+ { desc = 'prerelease.x', version = '1.2.3-alpha.1', want = { major = 1, minor = 2, patch = 3, prerelease = 'alpha.1' }, },
+ { desc = 'build.x', version = '1.2.3+build.15', want = { major = 1, minor = 2, patch = 3, build = 'build.15' }, },
+ { desc = 'prerelease and build', version = '1.2.3-rc1+build.15', want = { major = 1, minor = 2, patch = 3, prerelease = 'rc1', build = 'build.15', }, },
+ }
+ for _, tc in ipairs(testcases) do
+ it(
+ string.format('%q: version = %q', tc.desc, tc.version),
+ function()
+ eq(tc.want, vim.version.parse(tc.version))
+ end
+ )
+ end
+ end)
+
+ describe('strict=false', function()
+ local testcases = {
+ { version = '1.2', want = { major = 1, minor = 2, patch = 0 }, },
+ { version = '1', want = { major = 1, minor = 0, patch = 0 }, },
+ { version = '1.1-0', want = { major = 1, minor = 1, patch = 0, prerelease = '0' }, },
+ { version = '1-1.0', want = { major = 1, minor = 0, patch = 0, prerelease = '1.0' }, },
+ { version = 'v1.2.3 ', want = { major = 1, minor = 2, patch = 3 }, },
+ { version = ' v1.2.3', want = { major = 1, minor = 2, patch = 3 }, },
+ { version = 'tmux 3.2a', want = { major = 3, minor = 2, patch = 0, }, },
+ }
+ for _, tc in ipairs(testcases) do
+ it(
+ string.format('version = %q', tc.version),
+ function()
+ eq(tc.want, vim.version.parse(tc.version, { strict = false }))
+ end
+ )
+ end
+ end)
+
+ describe('invalid semver', function()
+ local testcases = {
+ { version = 'foo' },
+ { version = '' },
+ { version = '0.0.0.' },
+ { version = '.0.0.0' },
+ { version = '-1.0.0' },
+ { version = '0.-1.0' },
+ { version = '0.0.-1' },
+ { version = 'foobar1.2.3' },
+ { version = '1.2.3foobar' },
+ { version = '1.2.3-%?' },
+ { version = '1.2.3+%?' },
+ { version = '1.2.3+build.0-rc1' },
+ { version = '3.2a', },
+ { version = 'tmux 3.2a', },
+ }
+
+ local function quote_empty(s)
+ return tostring(s) == '' and '""' or tostring(s)
+ end
+
+ for _, tc in ipairs(testcases) do
+ it(quote_empty(tc.version), function()
+ eq(nil, vim.version.parse(tc.version, { strict = true }))
+ end)
+ end
+ end)
+
+ describe('invalid shape', function()
+ local testcases = {
+ { desc = 'no parameters' },
+ { desc = 'nil', version = nil },
+ { desc = 'number', version = 0 },
+ { desc = 'float', version = 0.01 },
+ { desc = 'table', version = {} },
+ }
+ for _, tc in ipairs(testcases) do
+ it(string.format('(%s): %s', tc.desc, tostring(tc.version)), function()
+ local expected = string.format(type(tc.version) == 'string'
+ and 'invalid version: "%s"' or 'invalid version: %s', tostring(tc.version))
+ matches(expected, pcall_err(vim.version.parse, tc.version, { strict = true }))
+ end)
+ end
+ end)
+ end)
+
+ it('relational metamethods (== < >)', function()
+ assert(v('v1.2.3') == v('1.2.3'))
+ assert(not (v('v1.2.3') < v('1.2.3')))
+ assert(v('v1.2.3') > v('1.2.3-prerelease'))
+ assert(v('v1.2.3-alpha') < v('1.2.3-beta'))
+ assert(v('v1.2.3-prerelease') < v('1.2.3'))
+ assert(v('v1.2.3') >= v('1.2.3'))
+ assert(v('v1.2.3') >= v('1.0.3'))
+ assert(v('v1.2.3') >= v('1.2.2'))
+ assert(v('v1.2.3') > v('1.2.2'))
+ assert(v('v1.2.3') > v('1.0.3'))
+ eq(vim.version.last({ v('1.2.3'), v('2.0.0') }), v('2.0.0'))
+ eq(vim.version.last({ v('2.0.0'), v('1.2.3') }), v('2.0.0'))
+ end)
+
+ it('lt()', function()
+ eq(true, vim.version.lt('1', '2'))
+ eq(false, vim.version.lt({3}, {0, 7, 4}))
+ eq(false, vim.version.lt({major=3, minor=3, patch=0}, {3, 2, 0}))
+ end)
+
+ it('gt()', function()
+ eq(true, vim.version.gt('2', '1'))
+ eq(true, vim.version.gt({3}, {0, 7, 4}))
+ eq(true, vim.version.gt({major=3, minor=3, patch=0}, {3, 2, 0}))
+ end)
+
+ it('eq()', function()
+ eq(true, vim.version.eq('2', '2'))
+ eq(true, vim.version.eq({3, 1, 0}, '3.1.0'))
+ eq(true, vim.version.eq({major=3, minor=3, patch=0}, {3, 3, 0}))
+ end)
+end)
diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua
index 867f366d06..ebe5fc254e 100644
--- a/test/functional/lua/vim_spec.lua
+++ b/test/functional/lua/vim_spec.lua
@@ -6,10 +6,12 @@ local nvim_prog = helpers.nvim_prog
local funcs = helpers.funcs
local meths = helpers.meths
local command = helpers.command
+local dedent = helpers.dedent
local insert = helpers.insert
local clear = helpers.clear
local eq = helpers.eq
local ok = helpers.ok
+local pesc = helpers.pesc
local eval = helpers.eval
local feed = helpers.feed
local pcall_err = helpers.pcall_err
@@ -126,6 +128,22 @@ describe('lua stdlib', function()
eq(1, funcs.luaeval('vim.stricmp("\\0C\\0", "\\0B\\0")'))
end)
+ it('vim.deprecate', function()
+ -- vim.deprecate(name, alternative, version, plugin, backtrace)
+ eq(dedent[[
+ foo.bar() is deprecated, use zub.wooo{ok=yay} instead. :help deprecated
+ This feature will be removed in Nvim version 2.17]],
+ exec_lua('return vim.deprecate(...)', 'foo.bar()', 'zub.wooo{ok=yay}', '2.17'))
+ -- Same message, skipped.
+ eq(vim.NIL,
+ exec_lua('return vim.deprecate(...)', 'foo.bar()', 'zub.wooo{ok=yay}', '2.17'))
+ -- When `plugin` is specified, don't show ":help deprecated". #22235
+ eq(dedent[[
+ foo.bar() is deprecated, use zub.wooo{ok=yay} instead.
+ This feature will be removed in my-plugin.nvim version 0.3.0]],
+ exec_lua('return vim.deprecate(...)', 'foo.bar()', 'zub.wooo{ok=yay}', '0.3.0', 'my-plugin.nvim', false))
+ end)
+
it('vim.startswith', function()
eq(true, funcs.luaeval('vim.startswith("123", "1")'))
eq(true, funcs.luaeval('vim.startswith("123", "")'))
@@ -259,7 +277,7 @@ describe('lua stdlib', function()
|
]]}
- -- nvim_command causes a vimL exception, check that it is properly caught
+ -- nvim_command causes a Vimscript exception, check that it is properly caught
-- and propagated as an error message in async contexts.. #10809
exec_lua([[
vim.schedule(function()
@@ -275,51 +293,53 @@ describe('lua stdlib', function()
]]}
end)
- it("vim.split", function()
- local split = function(str, sep, kwargs)
- return exec_lua('return vim.split(...)', str, sep, kwargs)
- end
-
+ it('vim.gsplit, vim.split', function()
local tests = {
- { "a,b", ",", false, false, { 'a', 'b' } },
- { ":aa::bb:", ":", false, false, { '', 'aa', '', 'bb', '' } },
- { ":aa::bb:", ":", false, true, { 'aa', '', 'bb' } },
- { "::ee::ff:", ":", false, false, { '', '', 'ee', '', 'ff', '' } },
- { "::ee::ff:", ":", false, true, { 'ee', '', 'ff' } },
- { "ab", ".", false, false, { '', '', '' } },
- { "a1b2c", "[0-9]", false, false, { 'a', 'b', 'c' } },
- { "xy", "", false, false, { 'x', 'y' } },
- { "here be dragons", " ", false, false, { "here", "be", "dragons"} },
- { "axaby", "ab?", false, false, { '', 'x', 'y' } },
- { "f v2v v3v w2w ", "([vw])2%1", false, false, { 'f ', ' v3v ', ' ' } },
- { "", "", false, false, {} },
- { "", "a", false, false, { '' } },
- { "x*yz*oo*l", "*", true, false, { 'x', 'yz', 'oo', 'l' } },
+ -- plain trimempty
+ { 'a,b', ',', false, false, { 'a', 'b' } },
+ { ':aa::::bb:', ':', false, false, { '', 'aa', '', '', '', 'bb', '' } },
+ { ':aa::::bb:', ':', false, true, { 'aa', '', '', '', 'bb' } },
+ { 'aa::::bb:', ':', false, true, { 'aa', '', '', '', 'bb' } },
+ { ':aa::bb:', ':', false, true, { 'aa', '', 'bb' } },
+ { '/a/b:/b/\n', '[:\n]', false, true, { '/a/b', '/b/' } },
+ { '::ee::ff:', ':', false, false, { '', '', 'ee', '', 'ff', '' } },
+ { '::ee::ff::', ':', false, true, { 'ee', '', 'ff' } },
+ { 'ab', '.', false, false, { '', '', '' } },
+ { 'a1b2c', '[0-9]', false, false, { 'a', 'b', 'c' } },
+ { 'xy', '', false, false, { 'x', 'y' } },
+ { 'here be dragons', ' ', false, false, { 'here', 'be', 'dragons'} },
+ { 'axaby', 'ab?', false, false, { '', 'x', 'y' } },
+ { 'f v2v v3v w2w ', '([vw])2%1', false, false, { 'f ', ' v3v ', ' ' } },
+ { '', '', false, false, {} },
+ { '', '', false, true, {} },
+ { '\n', '[:\n]', false, true, {} },
+ { '', 'a', false, false, { '' } },
+ { 'x*yz*oo*l', '*', true, false, { 'x', 'yz', 'oo', 'l' } },
}
for _, t in ipairs(tests) do
- eq(t[5], split(t[1], t[2], {plain=t[3], trimempty=t[4]}))
+ eq(t[5], vim.split(t[1], t[2], {plain=t[3], trimempty=t[4]}), t[1])
end
-- Test old signature
- eq({'x', 'yz', 'oo', 'l'}, split("x*yz*oo*l", "*", true))
+ eq({'x', 'yz', 'oo', 'l'}, vim.split("x*yz*oo*l", "*", true))
local loops = {
{ "abc", ".-" },
}
for _, t in ipairs(loops) do
- matches("Infinite loop detected", pcall_err(split, t[1], t[2]))
+ matches("Infinite loop detected", pcall_err(vim.split, t[1], t[2]))
end
-- Validates args.
- eq(true, pcall(split, 'string', 'string'))
+ eq(true, pcall(vim.split, 'string', 'string'))
matches('s: expected string, got number',
- pcall_err(split, 1, 'string'))
+ pcall_err(vim.split, 1, 'string'))
matches('sep: expected string, got number',
- pcall_err(split, 'string', 1))
- matches('kwargs: expected table, got number',
- pcall_err(split, 'string', 'string', 1))
+ pcall_err(vim.split, 'string', 1))
+ matches('opts: expected table, got number',
+ pcall_err(vim.split, 'string', 'string', 1))
end)
it('vim.trim', function()
@@ -444,6 +464,22 @@ describe('lua stdlib', function()
pcall_err(exec_lua, [[return vim.pesc(2)]]))
end)
+ it('vim.list_contains', function()
+ eq(true, exec_lua("return vim.list_contains({'a','b','c'}, 'c')"))
+ eq(false, exec_lua("return vim.list_contains({'a','b','c'}, 'd')"))
+ end)
+
+ it('vim.tbl_contains', function()
+ eq(true, exec_lua("return vim.tbl_contains({'a','b','c'}, 'c')"))
+ eq(false, exec_lua("return vim.tbl_contains({'a','b','c'}, 'd')"))
+ eq(true, exec_lua("return vim.tbl_contains({[2]='a',foo='b',[5] = 'c'}, 'c')"))
+ eq(true, exec_lua([[
+ return vim.tbl_contains({ 'a', { 'b', 'c' } }, function(v)
+ return vim.deep_equal(v, { 'b', 'c' })
+ end, { predicate = true })
+ ]]))
+ end)
+
it('vim.tbl_keys', function()
eq({}, exec_lua("return vim.tbl_keys({})"))
for _, v in pairs(exec_lua("return vim.tbl_keys({'a', 'b', 'c'})")) do
@@ -488,6 +524,19 @@ describe('lua stdlib', function()
]]))
end)
+ it('vim.tbl_isarray', function()
+ eq(true, exec_lua("return vim.tbl_isarray({})"))
+ eq(false, exec_lua("return vim.tbl_isarray(vim.empty_dict())"))
+ eq(true, exec_lua("return vim.tbl_isarray({'a', 'b', 'c'})"))
+ eq(false, exec_lua("return vim.tbl_isarray({'a', '32', a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_isarray({1, a='hello', b='baz'})"))
+ eq(false, exec_lua("return vim.tbl_isarray({a='hello', b='baz', 1})"))
+ eq(false, exec_lua("return vim.tbl_isarray({1, 2, nil, a='hello'})"))
+ eq(true, exec_lua("return vim.tbl_isarray({1, 2, nil, 4})"))
+ eq(true, exec_lua("return vim.tbl_isarray({nil, 2, 3, 4})"))
+ eq(false, exec_lua("return vim.tbl_isarray({1, [1.5]=2, [3]=3})"))
+ end)
+
it('vim.tbl_islist', function()
eq(true, exec_lua("return vim.tbl_islist({})"))
eq(false, exec_lua("return vim.tbl_islist(vim.empty_dict())"))
@@ -496,6 +545,9 @@ describe('lua stdlib', function()
eq(false, exec_lua("return vim.tbl_islist({1, a='hello', b='baz'})"))
eq(false, exec_lua("return vim.tbl_islist({a='hello', b='baz', 1})"))
eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, a='hello'})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, 2, nil, 4})"))
+ eq(false, exec_lua("return vim.tbl_islist({nil, 2, 3, 4})"))
+ eq(false, exec_lua("return vim.tbl_islist({1, [1.5]=2, [3]=3})"))
end)
it('vim.tbl_isempty', function()
@@ -780,7 +832,7 @@ describe('lua stdlib', function()
it('vim.call, vim.fn', function()
eq(true, exec_lua([[return vim.call('sin', 0.0) == 0.0 ]]))
eq(true, exec_lua([[return vim.fn.sin(0.0) == 0.0 ]]))
- -- compat: nvim_call_function uses "special" value for vimL float
+ -- compat: nvim_call_function uses "special" value for Vimscript float
eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]]))
exec([[
@@ -831,7 +883,7 @@ describe('lua stdlib', function()
it('vim.fn is allowed in "fast" context by some functions #18306', function()
exec_lua([[
- local timer = vim.loop.new_timer()
+ local timer = vim.uv.new_timer()
timer:start(0, 0, function()
timer:close()
assert(vim.in_fast_event())
@@ -897,7 +949,7 @@ describe('lua stdlib', function()
})
screen:attach()
exec_lua([[
- timer = vim.loop.new_timer()
+ timer = vim.uv.new_timer()
timer:start(20, 0, function ()
-- notify ok (executed later when safe)
vim.rpcnotify(chan, 'nvim_set_var', 'yy', {3, vim.NIL})
@@ -1440,14 +1492,68 @@ describe('lua stdlib', function()
eq(NIL, funcs.luaeval "vim.v.null")
matches([[attempt to index .* nil value]],
pcall_err(exec_lua, 'return vim.v[0].progpath'))
+ eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]]))
+ eq('Dictionary is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]]))
+ eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]]))
+ exec_lua([[vim.v.errmsg = 'set by Lua']])
+ eq('set by Lua', eval('v:errmsg'))
+ exec_lua([[vim.v.errmsg = 42]])
+ eq('42', eval('v:errmsg'))
+ exec_lua([[vim.v.oldfiles = { 'one', 'two' }]])
+ eq({ 'one', 'two' }, eval('v:oldfiles'))
+ exec_lua([[vim.v.oldfiles = {}]])
+ eq({}, eval('v:oldfiles'))
+ eq('Setting v:oldfiles to value with wrong type', pcall_err(exec_lua, [[vim.v.oldfiles = 'a']]))
+ eq({}, eval('v:oldfiles'))
+
+ feed('i foo foo foo<Esc>0/foo<CR>')
+ eq({1, 1}, meths.win_get_cursor(0))
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+ exec_lua([[vim.v.searchforward = 0]])
+ eq(0, eval('v:searchforward'))
+ feed('n')
+ eq({1, 1}, meths.win_get_cursor(0))
+ exec_lua([[vim.v.searchforward = 1]])
+ eq(1, eval('v:searchforward'))
+ feed('n')
+ eq({1, 5}, meths.win_get_cursor(0))
+
+ local screen = Screen.new(60, 3)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ [1] = {background = Screen.colors.Yellow},
+ })
+ screen:attach()
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
+ exec_lua([[vim.v.hlsearch = 0]])
+ eq(0, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ foo ^foo foo |
+ {0:~ }|
+ |
+ ]]}
+ exec_lua([[vim.v.hlsearch = 1]])
+ eq(1, eval('v:hlsearch'))
+ screen:expect{grid=[[
+ {1:foo} {1:^foo} {1:foo} |
+ {0:~ }|
+ |
+ ]]}
end)
it('vim.bo', function()
eq('', funcs.luaeval "vim.bo.filetype")
exec_lua [[
- vim.api.nvim_buf_set_option(0, "filetype", "markdown")
+ vim.api.nvim_set_option_value("filetype", "markdown", {})
BUF = vim.api.nvim_create_buf(false, true)
- vim.api.nvim_buf_set_option(BUF, "modifiable", false)
+ vim.api.nvim_set_option_value("modifiable", false, {buf = BUF})
]]
eq(false, funcs.luaeval "vim.bo.modified")
eq('markdown', funcs.luaeval "vim.bo.filetype")
@@ -1458,9 +1564,9 @@ describe('lua stdlib', function()
]]
eq('', funcs.luaeval "vim.bo.filetype")
eq(true, funcs.luaeval "vim.bo[BUF].modifiable")
- matches("no such option: 'nosuchopt'$",
+ matches("Unknown option 'nosuchopt'$",
pcall_err(exec_lua, 'return vim.bo.nosuchopt'))
- matches("Expected lua string$",
+ matches("Expected Lua string$",
pcall_err(exec_lua, 'return vim.bo[0][0].autoread'))
matches("Invalid buffer id: %-1$",
pcall_err(exec_lua, 'return vim.bo[-1].filetype'))
@@ -1468,9 +1574,9 @@ describe('lua stdlib', function()
it('vim.wo', function()
exec_lua [[
- vim.api.nvim_win_set_option(0, "cole", 2)
+ vim.api.nvim_set_option_value("cole", 2, {})
vim.cmd "split"
- vim.api.nvim_win_set_option(0, "cole", 2)
+ vim.api.nvim_set_option_value("cole", 2, {})
]]
eq(2, funcs.luaeval "vim.wo.cole")
exec_lua [[
@@ -1479,10 +1585,8 @@ describe('lua stdlib', function()
eq(0, funcs.luaeval "vim.wo.cole")
eq(0, funcs.luaeval "vim.wo[0].cole")
eq(0, funcs.luaeval "vim.wo[1001].cole")
- matches("no such option: 'notanopt'$",
+ matches("Unknown option 'notanopt'$",
pcall_err(exec_lua, 'return vim.wo.notanopt'))
- matches("Expected lua string$",
- pcall_err(exec_lua, 'return vim.wo[0][0].list'))
matches("Invalid window id: %-1$",
pcall_err(exec_lua, 'return vim.wo[-1].list'))
eq(2, funcs.luaeval "vim.wo[1000].cole")
@@ -1497,6 +1601,11 @@ describe('lua stdlib', function()
eq(200, funcs.luaeval "vim.wo.scrolloff")
exec_lua [[vim.wo.scrolloff = -1]]
eq(100, funcs.luaeval "vim.wo.scrolloff")
+ exec_lua [[
+ vim.wo[0][0].scrolloff = 200
+ vim.cmd "enew"
+ ]]
+ eq(100, funcs.luaeval "vim.wo.scrolloff")
end)
describe('vim.opt', function()
@@ -1515,8 +1624,8 @@ describe('lua stdlib', function()
local result = exec_lua [[
local result = {}
- table.insert(result, vim.api.nvim_get_option('scrolloff'))
- table.insert(result, vim.api.nvim_win_get_option(0, 'scrolloff'))
+ table.insert(result, vim.api.nvim_get_option_value('scrolloff', {scope='global'}))
+ table.insert(result, vim.api.nvim_get_option_value('scrolloff', {win=0}))
return result
]]
@@ -1580,20 +1689,20 @@ describe('lua stdlib', function()
local result = {}
vim.opt.makeprg = "global-local"
- table.insert(result, vim.api.nvim_get_option('makeprg'))
- table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ table.insert(result, vim.go.makeprg)
+ table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0}))
vim.opt_local.mp = "only-local"
- table.insert(result, vim.api.nvim_get_option('makeprg'))
- table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ table.insert(result, vim.go.makeprg)
+ table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0}))
vim.opt_global.makeprg = "only-global"
- table.insert(result, vim.api.nvim_get_option('makeprg'))
- table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ table.insert(result, vim.go.makeprg)
+ table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0}))
vim.opt.makeprg = "global-local"
- table.insert(result, vim.api.nvim_get_option('makeprg'))
- table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg'))
+ table.insert(result, vim.go.makeprg)
+ table.insert(result, vim.api.nvim_get_option_value('makeprg', {buf=0}))
return result
]]
@@ -2122,7 +2231,7 @@ describe('lua stdlib', function()
it('can handle isfname ,,,', function()
local result = exec_lua [[
vim.opt.isfname = "a,b,,,c"
- return { vim.opt.isfname:get(), vim.api.nvim_get_option('isfname') }
+ return { vim.opt.isfname:get(), vim.go.isfname }
]]
eq({{",", "a", "b", "c"}, "a,b,,,c"}, result)
@@ -2132,7 +2241,7 @@ describe('lua stdlib', function()
it('can handle isfname ,^,,', function()
local result = exec_lua [[
vim.opt.isfname = "a,b,^,,c"
- return { vim.opt.isfname:get(), vim.api.nvim_get_option('isfname') }
+ return { vim.opt.isfname:get(), vim.go.isfname }
]]
eq({{"^,", "a", "b", "c"}, "a,b,^,,c"}, result)
@@ -2203,8 +2312,8 @@ describe('lua stdlib', function()
end)
end) -- vim.opt
- describe('opt_local', function()
- it('should be able to append to an array list type option', function()
+ describe('vim.opt_local', function()
+ it('appends into global value when changing local option value', function()
eq({ "foo,bar,baz,qux" }, exec_lua [[
local result = {}
@@ -2219,6 +2328,19 @@ describe('lua stdlib', function()
end)
end)
+ describe('vim.opt_global', function()
+ it('gets current global option value', function()
+ eq({ "yes" }, exec_lua [[
+ local result = {}
+
+ vim.cmd "setglobal signcolumn=yes"
+ table.insert(result, vim.opt_global.signcolumn:get())
+
+ return result
+ ]])
+ end)
+ end)
+
it('vim.cmd', function()
exec_lua [[
vim.cmd "autocmd BufNew * ++once lua BUF = vim.fn.expand('<abuf>')"
@@ -2269,17 +2391,36 @@ describe('lua stdlib', function()
describe('vim.region', function()
it('charwise', function()
- insert(helpers.dedent( [[
+ insert(dedent( [[
text tααt tααt text
text tαxt txtα tex
text tαxt tαxt
]]))
- eq({5,15}, exec_lua[[ return vim.region(0,{1,5},{1,14},'v',true)[1] ]])
+ eq({5,13}, exec_lua[[ return vim.region(0,{0,5},{0,13},'v',false)[0] ]])
+ eq({5,15}, exec_lua[[ return vim.region(0,{0,5},{0,13},'v',true)[0] ]])
+ eq({5,15}, exec_lua[[ return vim.region(0,{0,5},{0,14},'v',true)[0] ]])
+ eq({5,15}, exec_lua[[ return vim.region(0,{0,5},{0,15},'v',false)[0] ]])
+ eq({5,17}, exec_lua[[ return vim.region(0,{0,5},{0,15},'v',true)[0] ]])
+ eq({5,17}, exec_lua[[ return vim.region(0,{0,5},{0,16},'v',true)[0] ]])
+ eq({5,17}, exec_lua[[ return vim.region(0,{0,5},{0,17},'v',false)[0] ]])
+ eq({5,18}, exec_lua[[ return vim.region(0,{0,5},{0,17},'v',true)[0] ]])
end)
it('blockwise', function()
insert([[αα]])
eq({0,5}, exec_lua[[ return vim.region(0,{0,0},{0,4},'3',true)[0] ]])
end)
+ it('linewise', function()
+ insert(dedent( [[
+ text tααt tααt text
+ text tαxt txtα tex
+ text tαxt tαxt
+ ]]))
+ eq({0,-1}, exec_lua[[ return vim.region(0,{1,5},{1,14},'V',true)[1] ]])
+ end)
+ it('getpos() input', function()
+ insert('getpos')
+ eq({0,6}, exec_lua[[ return vim.region(0,{0,0},'.','v',true)[0] ]])
+ end)
end)
describe('vim.on_key', function()
@@ -2305,6 +2446,12 @@ describe('lua stdlib', function()
end)
it('allows removing on_key listeners', function()
+ -- Create some unused namespaces
+ meths.create_namespace('unused1')
+ meths.create_namespace('unused2')
+ meths.create_namespace('unused3')
+ meths.create_namespace('unused4')
+
insert([[hello world]])
exec_lua [[
@@ -2420,13 +2567,12 @@ describe('lua stdlib', function()
]])
end)
-
it('should not block other events', function()
eq({time = true, wait_result = true}, exec_lua[[
start_time = get_time()
vim.g.timer_result = false
- timer = vim.loop.new_timer()
+ timer = vim.uv.new_timer()
timer:start(100, 0, vim.schedule_wrap(function()
vim.g.timer_result = true
end))
@@ -2448,7 +2594,7 @@ describe('lua stdlib', function()
start_time = get_time()
vim.g.timer_result = false
- timer = vim.loop.new_timer()
+ timer = vim.uv.new_timer()
timer:start(100, 0, vim.schedule_wrap(function()
vim.g.timer_result = true
end))
@@ -2462,6 +2608,7 @@ describe('lua stdlib', function()
}
]])
end)
+
it('should work with vim.defer_fn', function()
eq({time = true, wait_result = true}, exec_lua[[
start_time = get_time()
@@ -2491,17 +2638,17 @@ describe('lua stdlib', function()
it('should allow waiting with no callback, explicit', function()
eq(true, exec_lua [[
- local start_time = vim.loop.hrtime()
+ local start_time = vim.uv.hrtime()
vim.wait(50, nil)
- return vim.loop.hrtime() - start_time > 25000
+ return vim.uv.hrtime() - start_time > 25000
]])
end)
it('should allow waiting with no callback, implicit', function()
eq(true, exec_lua [[
- local start_time = vim.loop.hrtime()
+ local start_time = vim.uv.hrtime()
vim.wait(50)
- return vim.loop.hrtime() - start_time > 25000
+ return vim.uv.hrtime() - start_time > 25000
]])
end)
@@ -2623,6 +2770,23 @@ describe('lua stdlib', function()
eq({'notification', 'wait', {-2}}, next_msg(500))
end)
end)
+
+ it('should not run in fast callbacks #26122', function()
+ local screen = Screen.new(80, 10)
+ screen:attach()
+ exec_lua([[
+ local timer = vim.uv.new_timer()
+ timer:start(0, 0, function()
+ timer:close()
+ vim.wait(100, function() end)
+ end)
+ ]])
+ screen:expect({
+ any = pesc('E5560: vim.wait must not be called in a lua loop callback'),
+ })
+ feed('<CR>')
+ assert_alive()
+ end)
end)
it('vim.notify_once', function()
@@ -2679,14 +2843,14 @@ describe('lua stdlib', function()
describe('vim.api.nvim_buf_call', function()
it('can access buf options', function()
- local buf1 = meths.get_current_buf()
+ local buf1 = meths.get_current_buf().id
local buf2 = exec_lua [[
buf2 = vim.api.nvim_create_buf(false, true)
return buf2
]]
- eq(false, meths.buf_get_option(buf1, 'autoindent'))
- eq(false, meths.buf_get_option(buf2, 'autoindent'))
+ eq(false, meths.get_option_value('autoindent', {buf=buf1}))
+ eq(false, meths.get_option_value('autoindent', {buf=buf2}))
local val = exec_lua [[
return vim.api.nvim_buf_call(buf2, function()
@@ -2695,20 +2859,20 @@ describe('lua stdlib', function()
end)
]]
- eq(false, meths.buf_get_option(buf1, 'autoindent'))
- eq(true, meths.buf_get_option(buf2, 'autoindent'))
- eq(buf1, meths.get_current_buf())
+ eq(false, meths.get_option_value('autoindent', {buf=buf1}))
+ eq(true, meths.get_option_value('autoindent', {buf=buf2}))
+ eq(buf1, meths.get_current_buf().id)
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)
+ local api = vim.api
+ local t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end
+ api.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"})
+ api.nvim_feedkeys(t "G<C-V>", "txn", false)
+ api.nvim_buf_call(api.nvim_create_buf(false, true), function() vim.cmd "redraw" end)
]]
end)
@@ -2716,10 +2880,10 @@ describe('lua stdlib', function()
eq(true, exec_lua([[
local function scratch_buf_call(fn)
local buf = vim.api.nvim_create_buf(false, true)
- vim.api.nvim_buf_set_option(buf, 'cindent', true)
+ vim.api.nvim_set_option_value('cindent', true, {buf = buf})
return vim.api.nvim_buf_call(buf, function()
return vim.api.nvim_get_current_buf() == buf
- and vim.api.nvim_buf_get_option(buf, 'cindent')
+ and vim.api.nvim_get_option_value('cindent', {buf = buf})
and fn()
end) and vim.api.nvim_buf_delete(buf, {}) == nil
end
@@ -2756,7 +2920,7 @@ describe('lua stdlib', function()
describe('vim.api.nvim_win_call', function()
it('can access window options', function()
command('vsplit')
- local win1 = meths.get_current_win()
+ local win1 = meths.get_current_win().id
command('wincmd w')
local win2 = exec_lua [[
win2 = vim.api.nvim_get_current_win()
@@ -2764,8 +2928,8 @@ describe('lua stdlib', function()
]]
command('wincmd p')
- eq('', meths.win_get_option(win1, 'winhighlight'))
- eq('', meths.win_get_option(win2, 'winhighlight'))
+ eq('', meths.get_option_value('winhighlight', {win=win1}))
+ eq('', meths.get_option_value('winhighlight', {win=win2}))
local val = exec_lua [[
return vim.api.nvim_win_call(win2, function()
@@ -2774,38 +2938,38 @@ describe('lua stdlib', function()
end)
]]
- eq('', meths.win_get_option(win1, 'winhighlight'))
- eq('Normal:Normal', meths.win_get_option(win2, 'winhighlight'))
- eq(win1, meths.get_current_win())
+ eq('', meths.get_option_value('winhighlight', {win=win1}))
+ eq('Normal:Normal', meths.get_option_value('winhighlight', {win=win2}))
+ eq(win1, meths.get_current_win().id)
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()
+ _G.api = vim.api
+ _G.t = function(s) return api.nvim_replace_termcodes(s, true, true, true) end
+ _G.win_lines = api.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"})
+ _G.win_empty = api.nvim_get_current_win()
+ api.nvim_set_current_win(win_lines)
+ api.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)
+ api.nvim_feedkeys(t "G<C-V>", "txn", false)
+ api.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)
+ api.nvim_feedkeys(t "<Esc>gg", "txn", false)
+ api.nvim_set_current_win(win_empty)
+ api.nvim_feedkeys(t "gg<C-V>", "txn", false)
+ api.nvim_win_call(win_lines, function() api.nvim_feedkeys(t "G<C-V>", "txn", false) end)
vim.cmd "redraw"
]]
end)
@@ -2819,14 +2983,14 @@ describe('lua stdlib', function()
}
screen:attach()
exec_lua [[
- _G.a = vim.api
+ _G.api = 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})
+ api.nvim_buf_set_lines(0, 0, -1, true, lines)
+ api.nvim_win_set_cursor(0, {20, 0})
vim.cmd "split"
- _G.win = a.nvim_get_current_win()
+ _G.win = api.nvim_get_current_win()
vim.cmd "wincmd w | redraw"
]]
screen:expect [[
@@ -2837,7 +3001,7 @@ describe('lua stdlib', function()
|
]]
exec_lua [[
- a.nvim_win_call(win, function() a.nvim_win_set_cursor(0, {100, 0}) end)
+ api.nvim_win_call(win, function() api.nvim_win_set_cursor(0, {100, 0}) end)
vim.cmd "redraw"
]]
screen:expect [[
@@ -2898,8 +3062,133 @@ describe('lua stdlib', function()
return a
]])
end)
+
+ it('accepts the key name', function()
+ eq({ b = 'b', c = 'c' }, exec_lua [[
+ local a = vim.defaulttable(function(k) return k end)
+ local _ = a.b
+ local _ = a.c
+ return a
+ ]])
+ end)
end)
+ it('vim.lua_omnifunc', function()
+ local screen = Screen.new(60,5)
+ screen:set_default_attr_ids {
+ [1] = {foreground = Screen.colors.Blue1, bold = true};
+ [2] = {background = Screen.colors.WebGray};
+ [3] = {background = Screen.colors.LightMagenta};
+ [4] = {bold = true};
+ [5] = {foreground = Screen.colors.SeaGreen, bold = true};
+ }
+ screen:attach()
+ command [[ set omnifunc=v:lua.vim.lua_omnifunc ]]
+
+ -- Note: the implementation is shared with lua command line completion.
+ -- More tests for completion in lua/command_line_completion_spec.lua
+ feed [[ivim.insp<c-x><c-o>]]
+ screen:expect{grid=[[
+ vim.inspect^ |
+ {1:~ }{2: inspect }{1: }|
+ {1:~ }{3: inspect_pos }{1: }|
+ {1:~ }|
+ {4:-- Omni completion (^O^N^P) }{5:match 1 of 2} |
+ ]]}
+ end)
+
+ it('vim.print', function()
+ -- vim.print() returns its args.
+ eq({42, 'abc', { a = { b = 77 }}},
+ exec_lua[[return {vim.print(42, 'abc', { a = { b = 77 }})}]])
+
+ -- vim.print() pretty-prints the args.
+ eq(dedent[[
+
+ 42
+ abc
+ {
+ a = {
+ b = 77
+ }
+ }]],
+ eval[[execute('lua vim.print(42, "abc", { a = { b = 77 }})')]])
+ end)
+
+ it('vim.F.if_nil', function()
+ local function if_nil(...)
+ return exec_lua([[
+ local args = {...}
+ local nargs = select('#', ...)
+ for i = 1, nargs do
+ if args[i] == vim.NIL then
+ args[i] = nil
+ end
+ end
+ return vim.F.if_nil(unpack(args, 1, nargs))
+ ]], ...)
+ end
+
+ local a = NIL
+ local b = NIL
+ local c = 42
+ local d = false
+ eq(42, if_nil(a, c))
+ eq(false, if_nil(d, b))
+ eq(42, if_nil(a, b, c, d))
+ eq(false, if_nil(d))
+ eq(false, if_nil(d, c))
+ eq(NIL, if_nil(a))
+ end)
+
+ it('lpeg', function()
+ eq(5, exec_lua [[
+ local m = vim.lpeg
+ return m.match(m.R'09'^1, '4504ab')
+ ]])
+
+ eq(4, exec_lua [[ return vim.re.match("abcde", '[a-c]+') ]])
+ end)
+
+ it("vim.ringbuf", function()
+ local results = exec_lua([[
+ local ringbuf = vim.ringbuf(3)
+ ringbuf:push("a") -- idx: 0
+ local peeka1 = ringbuf:peek()
+ local peeka2 = ringbuf:peek()
+ local popa = ringbuf:pop()
+ local popnil = ringbuf:pop()
+ ringbuf:push("a") -- idx: 1
+ ringbuf:push("b") -- idx: 2
+
+ -- doesn't read last added item, but uses separate read index
+ local pop_after_add_b = ringbuf:pop()
+
+ ringbuf:push("c") -- idx: 3 wraps around, overrides idx: 0 "a"
+ ringbuf:push("d") -- idx: 4 wraps around, overrides idx: 1 "a"
+ return {
+ peeka1 = peeka1,
+ peeka2 = peeka2,
+ pop1 = popa,
+ pop2 = popnil,
+ pop3 = ringbuf:pop(),
+ pop4 = ringbuf:pop(),
+ pop5 = ringbuf:pop(),
+ pop_after_add_b = pop_after_add_b,
+ }
+ ]])
+ local expected = {
+ peeka1 = "a",
+ peeka2 = "a",
+ pop1 = "a",
+ pop2 = nil,
+ pop3 = "b",
+ pop4 = "c",
+ pop5 = "d",
+ pop_after_add_b = "a",
+ }
+ eq(expected, results)
+ end)
end)
describe('lua: builtin modules', function()
diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua
new file mode 100644
index 0000000000..cdcef08a1a
--- /dev/null
+++ b/test/functional/lua/watch_spec.lua
@@ -0,0 +1,178 @@
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local clear = helpers.clear
+local is_os = helpers.is_os
+local skip = helpers.skip
+
+describe('vim._watch', function()
+ before_each(function()
+ clear()
+ end)
+
+ describe('watch', function()
+ it('detects file changes', function()
+ skip(is_os('bsd'), "Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38")
+ local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
+
+ local result = exec_lua(
+ [[
+ local root_dir = ...
+
+ local events = {}
+
+ local expected_events = 0
+ local function wait_for_events()
+ assert(vim.wait(100, function() return #events == expected_events end), 'Timed out waiting for expected number of events. Current events seen so far: ' .. vim.inspect(events))
+ end
+
+ local stop = vim._watch.watch(root_dir, {}, function(path, change_type)
+ table.insert(events, { path = path, change_type = change_type })
+ end)
+
+ -- Only BSD seems to need some extra time for the watch to be ready to respond to events
+ if vim.fn.has('bsd') then
+ vim.wait(50)
+ end
+
+ local watched_path = root_dir .. '/file'
+ local watched, err = io.open(watched_path, 'w')
+ assert(not err, err)
+
+ expected_events = expected_events + 1
+ wait_for_events()
+
+ watched:close()
+ os.remove(watched_path)
+
+ expected_events = expected_events + 1
+ wait_for_events()
+
+ stop()
+ -- No events should come through anymore
+
+ local watched_path = root_dir .. '/file'
+ local watched, err = io.open(watched_path, 'w')
+ assert(not err, err)
+
+ vim.wait(50)
+
+ watched:close()
+ os.remove(watched_path)
+
+ vim.wait(50)
+
+ return events
+ ]],
+ root_dir
+ )
+
+ local expected = {
+ {
+ change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
+ path = root_dir .. '/file',
+ },
+ {
+ change_type = exec_lua([[return vim._watch.FileChangeType.Deleted]]),
+ path = root_dir .. '/file',
+ },
+ }
+
+ -- kqueue only reports events on the watched path itself, so creating a file within a
+ -- watched directory results in a "rename" libuv event on the directory.
+ if is_os('bsd') then
+ expected = {
+ {
+ change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
+ path = root_dir,
+ },
+ {
+ change_type = exec_lua([[return vim._watch.FileChangeType.Created]]),
+ path = root_dir,
+ },
+ }
+ end
+
+ eq(expected, result)
+ end)
+ end)
+
+ describe('poll', function()
+ it('detects file changes', function()
+ skip(is_os('bsd'), "bsd only reports rename on folders if file inside change")
+ local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(helpers.tmpname()) .. '/nvim_XXXXXXXXXX')
+
+ local result = exec_lua(
+ [[
+ local root_dir = ...
+ local lpeg = vim.lpeg
+
+ local events = {}
+
+ local debounce = 100
+ local wait_ms = debounce + 200
+
+ local expected_events = 0
+ local function wait_for_events()
+ assert(vim.wait(wait_ms, function() return #events == expected_events end), 'Timed out waiting for expected number of events. Current events seen so far: ' .. vim.inspect(events))
+ end
+
+ local incl = lpeg.P(root_dir) * lpeg.P("/file")^-1
+ local excl = lpeg.P(root_dir..'/file.unwatched')
+ local stop = vim._watch.poll(root_dir, {
+ debounce = debounce,
+ include_pattern = incl,
+ exclude_pattern = excl,
+ }, function(path, change_type)
+ table.insert(events, { path = path, change_type = change_type })
+ end)
+
+ local watched_path = root_dir .. '/file'
+ local watched, err = io.open(watched_path, 'w')
+ assert(not err, err)
+ local unwatched_path = root_dir .. '/file.unwatched'
+ local unwatched, err = io.open(unwatched_path, 'w')
+ assert(not err, err)
+
+ expected_events = expected_events + 1
+ wait_for_events()
+
+ watched:close()
+ os.remove(watched_path)
+ unwatched:close()
+ os.remove(unwatched_path)
+
+ expected_events = expected_events + 1
+ wait_for_events()
+
+ stop()
+ -- No events should come through anymore
+
+ local watched_path = root_dir .. '/file'
+ local watched, err = io.open(watched_path, 'w')
+ assert(not err, err)
+
+ watched:close()
+ os.remove(watched_path)
+
+ return events
+ ]],
+ root_dir
+ )
+
+ local created = exec_lua([[return vim._watch.FileChangeType.Created]])
+ local deleted = exec_lua([[return vim._watch.FileChangeType.Deleted]])
+ local expected = {
+ {
+ change_type = created,
+ path = root_dir .. "/file",
+ },
+ {
+ change_type = deleted,
+ path = root_dir .. "/file",
+ }
+ }
+ eq(expected, result)
+ end)
+ end)
+end)
diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua
index 0b6fe9533c..c75a98f35b 100644
--- a/test/functional/options/autochdir_spec.lua
+++ b/test/functional/options/autochdir_spec.lua
@@ -1,9 +1,10 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
local eq = helpers.eq
local funcs = helpers.funcs
local command = helpers.command
+local mkdir = helpers.mkdir
describe("'autochdir'", function()
it('given on the shell gets processed properly', function()
@@ -20,11 +21,11 @@ describe("'autochdir'", function()
end)
it('is not overwritten by getwinvar() call #17609',function()
- local curdir = string.gsub(lfs.currentdir(), '\\', '/')
+ local curdir = string.gsub(luv.cwd(), '\\', '/')
local dir_a = curdir..'/Xtest-functional-options-autochdir.dir_a'
local dir_b = curdir..'/Xtest-functional-options-autochdir.dir_b'
- lfs.mkdir(dir_a)
- lfs.mkdir(dir_b)
+ mkdir(dir_a)
+ mkdir(dir_b)
clear()
command('set shellslash')
command('set autochdir')
diff --git a/test/functional/options/cursorbind_spec.lua b/test/functional/options/cursorbind_spec.lua
index 1a03ed099a..498206936a 100644
--- a/test/functional/options/cursorbind_spec.lua
+++ b/test/functional/options/cursorbind_spec.lua
@@ -8,6 +8,7 @@ local feed = helpers.feed
before_each(clear)
describe("'cursorbind'", function()
+ -- oldtest: Test_cursorline_cursorbind_horizontal_scroll()
it("behaves consistently whether 'cursorline' is set or not vim-patch:8.2.4795", function()
local screen = Screen.new(60, 8)
screen:set_default_attr_ids({
diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua
index 84ec43f4cb..7858b626de 100644
--- a/test/functional/options/defaults_spec.lua
+++ b/test/functional/options/defaults_spec.lua
@@ -8,6 +8,7 @@ local meths = helpers.meths
local command = helpers.command
local clear = helpers.clear
local exc_exec = helpers.exc_exec
+local exec_lua = helpers.exec_lua
local eval = helpers.eval
local eq = helpers.eq
local ok = helpers.ok
@@ -199,11 +200,23 @@ describe('startup defaults', function()
it("'shadafile' ('viminfofile')", function()
local env = {XDG_DATA_HOME='Xtest-userdata', XDG_STATE_HOME='Xtest-userstate', XDG_CONFIG_HOME='Xtest-userconfig'}
+ finally(function()
+ command('set shadafile=NONE') -- Avoid writing shada file on exit
+ rmdir('Xtest-userstate')
+ os.remove('Xtest-foo')
+ end)
+
clear{args={}, args_rm={'-i'}, env=env}
-- Default 'shadafile' is empty.
-- This means use the default location. :help shada-file-name
- eq('', meths.get_option('shadafile'))
- eq('', meths.get_option('viminfofile'))
+ eq('', meths.get_option_value('shadafile', {}))
+ eq('', meths.get_option_value('viminfofile', {}))
+ -- Handles viminfo/viminfofile as alias for shada/shadafile.
+ eq('\n shadafile=', eval('execute("set shadafile?")'))
+ eq('\n shadafile=', eval('execute("set viminfofile?")'))
+ eq("\n shada=!,'100,<50,s10,h", eval('execute("set shada?")'))
+ eq("\n shada=!,'100,<50,s10,h", eval('execute("set viminfo?")'))
+
-- Check that shada data (such as v:oldfiles) is saved/restored.
command('edit Xtest-foo')
command('write')
@@ -212,14 +225,6 @@ describe('startup defaults', function()
expect_exit(command, 'qall')
clear{args={}, args_rm={'-i'}, env=env}
eq({ f }, eval('v:oldfiles'))
- os.remove('Xtest-foo')
- rmdir('Xtest-userstate')
-
- -- Handles viminfo/viminfofile as alias for shada/shadafile.
- eq('\n shadafile=', eval('execute("set shadafile?")'))
- eq('\n shadafile=', eval('execute("set viminfofile?")'))
- eq("\n shada=!,'100,<50,s10,h", eval('execute("set shada?")'))
- eq("\n shada=!,'100,<50,s10,h", eval('execute("set viminfo?")'))
end)
it("'packpath'", function()
@@ -227,13 +232,13 @@ describe('startup defaults', function()
args_rm={'runtimepath'},
}
-- Defaults to &runtimepath.
- eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
+ eq(meths.get_option_value('runtimepath', {}), meths.get_option_value('packpath', {}))
-- Does not follow modifications to runtimepath.
meths.command('set runtimepath+=foo')
- neq(meths.get_option('runtimepath'), meths.get_option('packpath'))
+ neq(meths.get_option_value('runtimepath', {}), meths.get_option_value('packpath', {}))
meths.command('set packpath+=foo')
- eq(meths.get_option('runtimepath'), meths.get_option('packpath'))
+ eq(meths.get_option_value('runtimepath', {}), meths.get_option_value('packpath', {}))
end)
it('v:progpath is set to the absolute path', function()
@@ -318,10 +323,10 @@ describe('XDG defaults', function()
USER=nil,
}})
- eq('.', meths.get_option('backupdir'))
- eq('.', meths.get_option('viewdir'))
- eq('.', meths.get_option('directory'))
- eq('.', meths.get_option('undodir'))
+ eq('.', meths.get_option_value('backupdir', {}))
+ eq('.', meths.get_option_value('viewdir', {}))
+ eq('.', meths.get_option_value('directory', {}))
+ eq('.', meths.get_option_value('undodir', {}))
ok((funcs.tempname()):len() > 4)
end)
end)
@@ -383,7 +388,7 @@ describe('XDG defaults', function()
.. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
.. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
.. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
- ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ ):gsub('\\', '/')), (meths.get_option_value('runtimepath', {})):gsub('\\', '/'))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
@@ -407,15 +412,15 @@ describe('XDG defaults', function()
.. ',' .. root_path .. ('/b'):rep(2048) .. '/nvim/after'
.. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after'
.. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after'
- ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ ):gsub('\\', '/')), (meths.get_option_value('runtimepath', {})):gsub('\\', '/'))
eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. state_dir .. '/backup//',
- (meths.get_option('backupdir'):gsub('\\', '/')))
+ (meths.get_option_value('backupdir', {}):gsub('\\', '/')))
eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//',
- (meths.get_option('directory')):gsub('\\', '/'))
+ (meths.get_option_value('directory', {})):gsub('\\', '/'))
eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//',
- (meths.get_option('undodir')):gsub('\\', '/'))
+ (meths.get_option_value('undodir', {})):gsub('\\', '/'))
eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//',
- (meths.get_option('viewdir')):gsub('\\', '/'))
+ (meths.get_option_value('viewdir', {})):gsub('\\', '/'))
end)
end)
@@ -431,7 +436,12 @@ describe('XDG defaults', function()
XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR',
XDG_STATE_HOME='$XDG_CONFIG_HOME',
XDG_DATA_DIRS='$XDG_CONFIG_DIRS',
- }})
+ }
+ })
+ end)
+
+ after_each(function()
+ command('set shadafile=NONE') -- Avoid writing shada file on exit
end)
it('are not expanded', function()
@@ -450,7 +460,7 @@ describe('XDG defaults', function()
.. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ ):gsub('\\', '/')), (meths.get_option_value('runtimepath', {})):gsub('\\', '/'))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
@@ -466,15 +476,15 @@ describe('XDG defaults', function()
.. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ ):gsub('\\', '/')), (meths.get_option_value('runtimepath', {})):gsub('\\', '/'))
eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
- meths.get_option('backupdir'):gsub('\\', '/'))
+ meths.get_option_value('backupdir', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
- meths.get_option('directory'):gsub('\\', '/'))
+ meths.get_option_value('directory', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
- meths.get_option('undodir'):gsub('\\', '/'))
+ meths.get_option_value('undodir', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
- meths.get_option('viewdir'):gsub('\\', '/'))
+ meths.get_option_value('viewdir', {}):gsub('\\', '/'))
meths.command('set all&')
eq(('$XDG_DATA_HOME/nvim'
.. ',$XDG_DATA_DIRS/nvim'
@@ -486,15 +496,15 @@ describe('XDG defaults', function()
.. ',$XDG_CONFIG_HOME/' .. data_dir .. '/site/after'
.. ',$XDG_DATA_DIRS/nvim/after'
.. ',$XDG_DATA_HOME/nvim/after'
- ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/'))
+ ):gsub('\\', '/'), (meths.get_option_value('runtimepath', {})):gsub('\\', '/'))
eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'),
- meths.get_option('backupdir'):gsub('\\', '/'))
+ meths.get_option_value('backupdir', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'),
- meths.get_option('directory'):gsub('\\', '/'))
+ meths.get_option_value('directory', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'),
- meths.get_option('undodir'):gsub('\\', '/'))
+ meths.get_option_value('undodir', {}):gsub('\\', '/'))
eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'),
- meths.get_option('viewdir'):gsub('\\', '/'))
+ meths.get_option_value('viewdir', {}):gsub('\\', '/'))
eq(nil, (funcs.tempname()):match('XDG_RUNTIME_DIR'))
end)
end)
@@ -529,7 +539,7 @@ describe('XDG defaults', function()
.. ',-\\,-\\,-' .. path_sep .. 'nvim' .. path_sep .. 'after'
.. ',\\,-\\,-\\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
.. ',\\, \\, \\,' .. path_sep .. 'nvim' .. path_sep .. 'after'
- ), meths.get_option('runtimepath'))
+ ), meths.get_option_value('runtimepath', {}))
meths.command('set runtimepath&')
meths.command('set backupdir&')
meths.command('set directory&')
@@ -549,15 +559,15 @@ describe('XDG defaults', function()
.. ',-\\,-\\,-' .. path_sep ..'nvim' .. path_sep ..'after'
.. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after'
.. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after'
- ), meths.get_option('runtimepath'))
+ ), meths.get_option_value('runtimepath', {}))
eq('.,\\,=\\,=\\,' .. path_sep .. state_dir .. '' .. path_sep ..'backup' .. (path_sep):rep(2),
- meths.get_option('backupdir'))
+ meths.get_option_value('backupdir', {}))
eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2),
- meths.get_option('directory'))
+ meths.get_option_value('directory', {}))
eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'undo' .. (path_sep):rep(2),
- meths.get_option('undodir'))
+ meths.get_option_value('undodir', {}))
eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'view' .. (path_sep):rep(2),
- meths.get_option('viewdir'))
+ meths.get_option_value('viewdir', {}))
end)
end)
end)
@@ -566,8 +576,12 @@ end)
describe('stdpath()', function()
-- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions
-- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same.
- local datadir = is_os('win') and 'nvim-data' or 'nvim'
- local statedir = is_os('win') and 'nvim-data' or 'nvim'
+ local function maybe_data(name)
+ return is_os('win') and name .. '-data' or name
+ end
+
+ local datadir = maybe_data('nvim')
+ local statedir = maybe_data('nvim')
local env_sep = is_os('win') and ';' or ':'
it('acceptance', function()
@@ -583,7 +597,45 @@ describe('stdpath()', function()
assert_alive() -- Check for crash. #8393
end)
- context('returns a String', function()
+ it('reacts to $NVIM_APPNAME', function()
+ local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106)
+ clear({env={ NVIM_APPNAME=appname }})
+ eq(appname, funcs.fnamemodify(funcs.stdpath('config'), ':t'))
+ eq(appname, funcs.fnamemodify(funcs.stdpath('cache'), ':t'))
+ eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('log'), ':t'))
+ eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('data'), ':t'))
+ eq(maybe_data(appname), funcs.fnamemodify(funcs.stdpath('state'), ':t'))
+ -- config_dirs and data_dirs are empty on windows, so don't check them on
+ -- that platform
+ if not is_os('win') then
+ eq(appname, funcs.fnamemodify(funcs.stdpath('config_dirs')[1], ':t'))
+ eq(appname, funcs.fnamemodify(funcs.stdpath('data_dirs')[1], ':t'))
+ end
+ assert_alive() -- Check for crash. #8393
+
+ -- Check that Nvim rejects invalid APPNAMEs
+ -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness.
+ local function test_appname(testAppname, expected_exitcode)
+ local lua_code = string.format([[
+ local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '+qall!' }, { env = { NVIM_APPNAME = %q } })
+ return vim.fn.jobwait({ child }, %d)[1]
+ ]], alter_slashes(testAppname), 3000)
+ eq(expected_exitcode, exec_lua(lua_code))
+ end
+ -- Invalid appnames:
+ test_appname('a/../b', 1)
+ test_appname('../a', 1)
+ test_appname('a/..', 1)
+ test_appname('..', 1)
+ test_appname('.', 1)
+ test_appname('/', 1)
+ test_appname(is_os('win') and 'C:/a/b' or '/a/b', 1)
+ -- Valid appnames:
+ test_appname('a/b', 0)
+ test_appname('a/b\\c', 0)
+ end)
+
+ describe('returns a String', function()
describe('with "config"' , function ()
it('knows XDG_CONFIG_HOME', function()
@@ -718,7 +770,7 @@ describe('stdpath()', function()
end)
end)
- context('returns a List', function()
+ describe('returns a List', function()
-- Some OS specific variables the system would have set.
local function base_env()
if is_os('win') then
@@ -870,8 +922,8 @@ describe('stdpath()', function()
end)
it('on non-strings', function()
- eq('Vim(call):E731: using Dictionary as a String', exc_exec('call stdpath({"eris": 23})'))
- eq('Vim(call):E730: using List as a String', exc_exec('call stdpath([23])'))
+ eq('Vim(call):E731: Using a Dictionary as a String', exc_exec('call stdpath({"eris": 23})'))
+ eq('Vim(call):E730: Using a List as a String', exc_exec('call stdpath([23])'))
end)
end)
end)
diff --git a/test/functional/options/keymap_spec.lua b/test/functional/options/keymap_spec.lua
index 4fdc6ef4be..c390e3d943 100644
--- a/test/functional/options/keymap_spec.lua
+++ b/test/functional/options/keymap_spec.lua
@@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each)
local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq
local expect, command, eval = helpers.expect, helpers.command, helpers.eval
local insert, call = helpers.insert, helpers.call
-local funcs, dedent = helpers.funcs, helpers.dedent
+local exec_capture, dedent = helpers.exec_capture, helpers.dedent
-- First test it's implemented using the :lmap and :lnoremap commands, then
-- check those mappings behave as expected.
@@ -30,7 +30,7 @@ describe("'keymap' / :lmap", function()
command('lmapclear <buffer>')
command('set keymap=dvorak')
command('set nomore')
- local bindings = funcs.nvim_exec('lmap', true)
+ local bindings = exec_capture('lmap')
eq(dedent([[
l " @_
diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua
index 5bff45a836..38a9692792 100644
--- a/test/functional/options/mousescroll_spec.lua
+++ b/test/functional/options/mousescroll_spec.lua
@@ -20,7 +20,7 @@ end
describe("'mousescroll'", function()
local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll='
- local digit_expected = 'Vim(set):E548: digit expected: mousescroll='
+ local digit_expected = 'Vim(set):E5080: Digit expected: mousescroll='
local function should_fail(val, errorstr)
eq(errorstr..val, exc_exec('set mousescroll='..val))
diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua
index f343e2da75..16a53c75e6 100644
--- a/test/functional/options/num_options_spec.lua
+++ b/test/functional/options/num_options_spec.lua
@@ -11,7 +11,7 @@ local function should_fail(opt, value, errmsg)
feed_command('setlocal ' .. opt .. '=' .. value)
eq(errmsg, eval("v:errmsg"):match("E%d*"))
feed_command('let v:errmsg = ""')
- local status, err = pcall(meths.set_option, opt, value)
+ local status, err = pcall(meths.set_option_value, opt, value, {})
eq(status, false)
eq(errmsg, err:match("E%d*"))
eq('', eval("v:errmsg"))
@@ -20,8 +20,8 @@ end
local function should_succeed(opt, value)
feed_command('setglobal ' .. opt .. '=' .. value)
feed_command('setlocal ' .. opt .. '=' .. value)
- meths.set_option(opt, value)
- eq(value, meths.get_option(opt))
+ meths.set_option_value(opt, value, {})
+ eq(value, meths.get_option_value(opt, {}))
eq('', eval("v:errmsg"))
end
@@ -29,12 +29,12 @@ describe(':setlocal', function()
before_each(clear)
it('setlocal sets only local value', function()
- eq(0, meths.get_option('iminsert'))
+ eq(0, meths.get_option_value('iminsert', {scope='global'}))
feed_command('setlocal iminsert=1')
- eq(0, meths.get_option('iminsert'))
- eq(-1, meths.get_option('imsearch'))
+ eq(0, meths.get_option_value('iminsert', {scope='global'}))
+ eq(-1, meths.get_option_value('imsearch', {scope='global'}))
feed_command('setlocal imsearch=1')
- eq(-1, meths.get_option('imsearch'))
+ eq(-1, meths.get_option_value('imsearch', {scope='global'}))
end)
end)
@@ -77,8 +77,8 @@ describe(':set validation', function()
-- If smaller than 1 this one is set to 'lines'-1
feed_command('setglobal window=-10')
- meths.set_option('window', -10)
- eq(23, meths.get_option('window'))
+ meths.set_option_value('window', -10, {})
+ eq(23, meths.get_option_value('window', {}))
eq('', eval("v:errmsg"))
-- 'scrolloff' and 'sidescrolloff' can have a -1 value when
@@ -112,8 +112,8 @@ describe(':set validation', function()
local function setto(value)
feed_command('setglobal maxcombine=' .. value)
feed_command('setlocal maxcombine=' .. value)
- meths.set_option('maxcombine', value)
- eq(6, meths.get_option('maxcombine'))
+ meths.set_option_value('maxcombine', value, {})
+ eq(6, meths.get_option_value('maxcombine', {}))
eq('', eval("v:errmsg"))
end
setto(0)
diff --git a/test/functional/options/pastetoggle_spec.lua b/test/functional/options/pastetoggle_spec.lua
deleted file mode 100644
index 40c14fa187..0000000000
--- a/test/functional/options/pastetoggle_spec.lua
+++ /dev/null
@@ -1,90 +0,0 @@
-local helpers = require('test.functional.helpers')(after_each)
-
-local clear = helpers.clear
-local feed = helpers.feed
-local command = helpers.command
-local eq = helpers.eq
-local expect = helpers.expect
-local eval = helpers.eval
-local insert = helpers.insert
-local meths = helpers.meths
-local sleep = helpers.sleep
-
-describe("'pastetoggle' option", function()
- before_each(clear)
- it("toggles 'paste'", function()
- command('set pastetoggle=a')
- eq(0, eval('&paste'))
- feed('a')
- -- Need another key so that the vgetorpeek() function returns.
- feed('j')
- eq(1, eval('&paste'))
- end)
- describe("multiple key 'pastetoggle'", function()
- before_each(function()
- eq(0, eval('&paste'))
- command('set timeoutlen=1 ttimeoutlen=10000')
- end)
- it('is waited for when chars are typed', function()
- local pastetoggle = 'lllll'
- command('set pastetoggle=' .. pastetoggle)
- feed(pastetoggle:sub(0, 2))
- -- sleep() for long enough that vgetorpeek() is gotten into, but short
- -- enough that ttimeoutlen is not reached.
- sleep(200)
- feed(pastetoggle:sub(3, -1))
- -- Need another key so that the vgetorpeek() function returns.
- feed('j')
- eq(1, eval('&paste'))
- end)
-
- it('is not waited for when there are no typed chars after mapped chars', function()
- command('set pastetoggle=abc')
- command('imap d a')
- meths.feedkeys('id', 't', true)
- -- sleep() for long enough that vgetorpeek() is gotten into, but short
- -- enough that ttimeoutlen is not reached.
- sleep(200)
- feed('bc')
- -- Need another key so that the vgetorpeek() function returns.
- feed('j')
- -- 'ttimeoutlen' should NOT apply
- eq(0, eval('&paste'))
- end)
-
- it('is waited for when there are typed chars after mapped chars', function()
- command('set pastetoggle=abc')
- command('imap d a')
- meths.feedkeys('idb', 't', true)
- -- sleep() for long enough that vgetorpeek() is gotten into, but short
- -- enough that ttimeoutlen is not reached.
- sleep(200)
- feed('c')
- -- Need another key so that the vgetorpeek() function returns.
- feed('j')
- -- 'ttimeoutlen' should apply
- eq(1, eval('&paste'))
- end)
-
- it('is waited for when there are typed chars after noremapped chars', function()
- command('set pastetoggle=abc')
- command('inoremap d a')
- meths.feedkeys('idb', 't', true)
- -- sleep() for long enough that vgetorpeek() is gotten into, but short
- -- enough that ttimeoutlen is not reached.
- sleep(200)
- feed('c')
- -- Need another key so that the vgetorpeek() function returns.
- feed('j')
- -- 'ttimeoutlen' should apply
- eq(1, eval('&paste'))
- end)
- end)
- it('does not interfere with character-find', function()
- insert('foo,bar')
- feed('0')
- command('set pastetoggle=,sp')
- feed('dt,')
- expect(',bar')
- end)
-end)
diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua
index e6a2550aba..ac78003a8c 100644
--- a/test/functional/plugin/editorconfig_spec.lua
+++ b/test/functional/plugin/editorconfig_spec.lua
@@ -3,9 +3,9 @@ local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
local pathsep = helpers.get_pathsep()
-local curbufmeths = helpers.curbufmeths
local funcs = helpers.funcs
local meths = helpers.meths
+local exec_lua = helpers.exec_lua
local testdir = 'Xtest-editorconfig'
@@ -13,7 +13,7 @@ local function test_case(name, expected)
local filename = testdir .. pathsep .. name
command('edit ' .. filename)
for opt, val in pairs(expected) do
- eq(val, curbufmeths.get_option(opt), name)
+ eq(val, meths.get_option_value(opt, {buf=0}), name)
end
end
@@ -160,8 +160,8 @@ describe('editorconfig', function()
end)
it('sets newline options', function()
- test_case('with_newline.txt', { fixendofline = true, endofline = true })
- test_case('without_newline.txt', { fixendofline = false, endofline = false })
+ test_case('with_newline.txt', { fixendofline = true })
+ test_case('without_newline.txt', { fixendofline = false })
end)
it('respects trim_trailing_whitespace', function()
@@ -207,4 +207,15 @@ But not this one
test_case('3_space.txt', { shiftwidth = 42 })
test_case('4_space.py', { shiftwidth = 4 })
end)
+
+ it('does not operate on invalid buffers', function()
+ local ok, err = unpack(exec_lua([[
+ vim.cmd.edit('test.txt')
+ local bufnr = vim.api.nvim_get_current_buf()
+ vim.cmd.bwipeout(bufnr)
+ return {pcall(require('editorconfig').config, bufnr)}
+ ]]))
+
+ eq(true, ok, err)
+ end)
end)
diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua
index 97d32313e5..50b1d03f36 100644
--- a/test/functional/plugin/health_spec.lua
+++ b/test/functional/plugin/health_spec.lua
@@ -1,5 +1,4 @@
local helpers = require('test.functional.helpers')(after_each)
-local global_helpers = require('test.helpers')
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
@@ -43,20 +42,17 @@ end)
describe('health.vim', function()
before_each(function()
clear{args={'-u', 'NORC'}}
- -- Provides functions:
- -- health#broken#check()
- -- health#success1#check()
- -- health#success2#check()
+ -- Provides healthcheck functions
command("set runtimepath+=test/functional/fixtures")
end)
describe(":checkhealth", function()
- it("functions health#report_*() render correctly", function()
+ it("functions report_*() render correctly", function()
command("checkhealth full_render")
helpers.expect([[
==============================================================================
- full_render: health#full_render#check
+ test_plug.full_render: require("test_plug.full_render.health").check()
report 1 ~
- OK life is fine
@@ -79,7 +75,7 @@ describe('health.vim', function()
helpers.expect([[
==============================================================================
- success1: health#success1#check
+ test_plug: require("test_plug.health").check()
report 1 ~
- OK everything is fine
@@ -88,36 +84,19 @@ describe('health.vim', function()
- OK nothing to see here
==============================================================================
- success2: health#success2#check
-
- another 1 ~
- - OK ok
-
- ==============================================================================
- test_plug: require("test_plug.health").check()
+ test_plug.success1: require("test_plug.success1.health").check()
report 1 ~
- OK everything is fine
report 2 ~
- OK nothing to see here
- ]])
- end)
-
- it("lua plugins, skips vimscript healthchecks with the same name", function()
- command("checkhealth test_plug")
- -- Existing file in test/functional/fixtures/lua/test_plug/autoload/health/test_plug.vim
- -- and the Lua healthcheck is used instead.
- helpers.expect([[
==============================================================================
- test_plug: require("test_plug.health").check()
-
- report 1 ~
- - OK everything is fine
+ test_plug.success2: require("test_plug.success2.health").check()
- report 2 ~
- - OK nothing to see here
+ another 1 ~
+ - OK ok
]])
end)
@@ -136,57 +115,6 @@ describe('health.vim', function()
]])
end)
- it("lua plugins submodules with expression '*'", function()
- command("checkhealth test_plug*")
- local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
- -- avoid dealing with path separators
- local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5)
- local expected = helpers.dedent([[
-
- ==============================================================================
- test_plug: require("test_plug.health").check()
-
- report 1 ~
- - OK everything is fine
-
- report 2 ~
- - OK nothing to see here
-
- ==============================================================================
- test_plug.submodule: require("test_plug.submodule.health").check()
-
- report 1 ~
- - OK everything is fine
-
- report 2 ~
- - OK nothing to see here
-
- ==============================================================================
- test_plug.submodule_empty: require("test_plug.submodule_empty.health").check()
-
- - ERROR The healthcheck report for "test_plug.submodule_empty" plugin is empty.
-
- ==============================================================================
- test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
-
- - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
- function health#check, line 25]])
- eq(expected, received)
- end)
-
- it("gracefully handles broken healthcheck", function()
- command("checkhealth broken")
- helpers.expect([[
-
- ==============================================================================
- broken: health#broken#check
-
- - ERROR Failed to run healthcheck for "broken" plugin. Exception:
- function health#check[25]..health#broken#check, line 1
- caused an error
- ]])
- end)
-
it("... including empty reports", function()
command("checkhealth test_plug.submodule_empty")
helpers.expect([[
@@ -198,36 +126,17 @@ describe('health.vim', function()
]])
end)
- it("gracefully handles broken lua healthcheck", function()
- command("checkhealth test_plug.submodule_failed")
- local buf_lines = helpers.curbuf('get_lines', 0, -1, true)
- local received = table.concat(buf_lines, '\n', 1, #buf_lines - 5)
- -- avoid dealing with path separators
- local lua_err = "attempt to perform arithmetic on a nil value"
- local last_line = buf_lines[#buf_lines - 4]
- assert(string.find(last_line, lua_err) ~= nil, "Lua error not present")
-
- local expected = global_helpers.dedent([[
-
- ==============================================================================
- test_plug.submodule_failed: require("test_plug.submodule_failed.health").check()
-
- - ERROR Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception:
- function health#check, line 25]])
- eq(expected, received)
- end)
-
it("highlights OK, ERROR", function()
local screen = Screen.new(50, 12)
screen:attach()
screen:set_default_attr_ids({
- Ok = { foreground = Screen.colors.Grey3, background = 6291200 },
- Error = { foreground = Screen.colors.Grey100, background = Screen.colors.Red },
+ Ok = { foreground = Screen.colors.LightGreen },
+ Error = { foreground = Screen.colors.Red },
Heading = { foreground = tonumber('0x6a0dad') },
Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey },
})
command("checkhealth foo success1")
- command("set nowrap laststatus=0")
+ command("set nofoldenable nowrap laststatus=0")
screen:expect{grid=[[
^ |
{Bar:──────────────────────────────────────────────────}|
@@ -236,7 +145,7 @@ describe('health.vim', function()
- {Error:ERROR} No healthcheck found for "foo" plugin. |
|
{Bar:──────────────────────────────────────────────────}|
- {Heading:success1: health#success1#check} |
+ {Heading:test_plug.success1: require("test_plug.success1.he}|
|
{Heading:report 1} |
- {Ok:OK} everything is fine |
@@ -244,6 +153,22 @@ describe('health.vim', function()
]]}
end)
+ it("fold healthchecks", function()
+ local screen = Screen.new(50, 7)
+ screen:attach()
+ command("checkhealth foo success1")
+ command("set nowrap laststatus=0")
+ screen:expect{grid=[[
+ ^ |
+ ──────────────────────────────────────────────────|
+ +WE 4 lines: foo: ·······························|
+ ──────────────────────────────────────────────────|
+ +-- 8 lines: test_plug.success1: require("test_pl|
+ ~ |
+ |
+ ]]}
+ end)
+
it("gracefully handles invalid healthcheck", function()
command("checkhealth non_existent_healthcheck")
-- luacheck: ignore 613
diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua
new file mode 100644
index 0000000000..9354654afe
--- /dev/null
+++ b/test/functional/plugin/lsp/completion_spec.lua
@@ -0,0 +1,239 @@
+---@diagnostic disable: no-unknown
+local helpers = require('test.functional.helpers')(after_each)
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+
+
+--- Convert completion results.
+---
+---@param line string line contents. Mark cursor position with `|`
+---@param candidates lsp.CompletionList|lsp.CompletionItem[]
+---@param lnum? integer 0-based, defaults to 0
+---@return {items: table[], server_start_boundary: integer?}
+local function complete(line, candidates, lnum)
+ lnum = lnum or 0
+ -- nvim_win_get_cursor returns 0 based column, line:find returns 1 based
+ local cursor_col = line:find("|") - 1
+ line = line:gsub("|", "")
+ return exec_lua([[
+ local line, cursor_col, lnum, result = ...
+ local line_to_cursor = line:sub(1, cursor_col)
+ local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$')
+ local items, server_start_boundary = require("vim.lsp._completion")._convert_results(
+ line,
+ lnum,
+ cursor_col,
+ client_start_boundary,
+ nil,
+ result,
+ "utf-16"
+ )
+ return {
+ items = items,
+ server_start_boundary = server_start_boundary
+ }
+ ]], line, cursor_col, lnum, candidates)
+end
+
+
+describe("vim.lsp._completion", function()
+ before_each(helpers.clear)
+
+ -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+ it('prefers textEdit over label as word', function()
+ local range0 = {
+ start = { line = 0, character = 0 },
+ ["end"] = { line = 0, character = 0 },
+ }
+ local completion_list = {
+ -- resolves into label
+ { label = 'foobar', sortText = 'a', documentation = 'documentation' },
+ {
+ label = 'foobar',
+ sortText = 'b',
+ documentation = { value = 'documentation' },
+ },
+ -- resolves into insertText
+ { label = 'foocar', sortText = 'c', insertText = 'foobar' },
+ { label = 'foocar', sortText = 'd', insertText = 'foobar' },
+ -- resolves into textEdit.newText
+ { label = 'foocar', sortText = 'e', insertText = 'foodar', textEdit = { newText = 'foobar', range = range0 } },
+ { label = 'foocar', sortText = 'f', textEdit = { newText = 'foobar', range = range0 } },
+ -- real-world snippet text
+ {
+ label = 'foocar',
+ sortText = 'g',
+ insertText = 'foodar',
+ insertTextFormat = 2,
+ textEdit = { newText = 'foobar(${1:place holder}, ${2:more ...holder{\\}})', range = range0 },
+ },
+ {
+ label = 'foocar',
+ sortText = 'h',
+ insertText = 'foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}',
+ insertTextFormat = 2,
+ },
+ -- nested snippet tokens
+ {
+ label = 'foocar',
+ sortText = 'i',
+ insertText = 'foodar(${1:${2|typ1,typ2|}}) {$0\\}',
+ insertTextFormat = 2,
+ },
+ -- braced tabstop
+ { label = 'foocar', sortText = 'j', insertText = 'foodar()${0}', insertTextFormat = 2},
+ -- plain text
+ {
+ label = 'foocar',
+ sortText = 'k',
+ insertText = 'foodar(${1:var1})',
+ insertTextFormat = 1,
+ },
+ }
+ local expected = {
+ {
+ abbr = 'foobar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foobar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foobar',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foobar(place holder, more ...holder{})',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foodar(var1 typ1, var2 *typ2) {}',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foodar(typ1) {}',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foodar()',
+ },
+ {
+ abbr = 'foocar',
+ word = 'foodar(${1:var1})',
+ },
+ }
+ local result = complete('|', completion_list)
+ result = vim.tbl_map(function(x)
+ return {
+ abbr = x.abbr,
+ word = x.word
+ }
+ end, result.items)
+ eq(expected, result)
+ end)
+ it("uses correct start boundary", function()
+ local completion_list = {
+ isIncomplete = false,
+ items = {
+ {
+ filterText = "this_thread",
+ insertText = "this_thread",
+ insertTextFormat = 1,
+ kind = 9,
+ label = " this_thread",
+ score = 1.3205767869949,
+ sortText = "4056f757this_thread",
+ textEdit = {
+ newText = "this_thread",
+ range = {
+ start = { line = 0, character = 7 },
+ ["end"] = { line = 0, character = 11 },
+ },
+ }
+ },
+ }
+ }
+ local expected = {
+ abbr = ' this_thread',
+ dup = 1,
+ empty = 1,
+ icase = 1,
+ kind = 'Module',
+ menu = '',
+ word = 'this_thread',
+ }
+ local result = complete(" std::this|", completion_list)
+ eq(7, result.server_start_boundary)
+ local item = result.items[1]
+ item.user_data = nil
+ eq(expected, item)
+ end)
+
+ it("should search from start boundary to cursor position", function()
+ local completion_list = {
+ isIncomplete = false,
+ items = {
+ {
+ filterText = "this_thread",
+ insertText = "this_thread",
+ insertTextFormat = 1,
+ kind = 9,
+ label = " this_thread",
+ score = 1.3205767869949,
+ sortText = "4056f757this_thread",
+ textEdit = {
+ newText = "this_thread",
+ range = {
+ start = { line = 0, character = 7 },
+ ["end"] = { line = 0, character = 11 },
+ },
+ }
+ },
+ {
+ filterText = "notthis_thread",
+ insertText = "notthis_thread",
+ insertTextFormat = 1,
+ kind = 9,
+ label = " notthis_thread",
+ score = 1.3205767869949,
+ sortText = "4056f757this_thread",
+ textEdit = {
+ newText = "notthis_thread",
+ range = {
+ start = { line = 0, character = 7 },
+ ["end"] = { line = 0, character = 11 },
+ },
+ }
+ },
+ }
+ }
+ local expected = {
+ abbr = ' this_thread',
+ dup = 1,
+ empty = 1,
+ icase = 1,
+ kind = 'Module',
+ menu = '',
+ word = 'this_thread',
+ }
+ local result = complete(" std::this|is", completion_list)
+ eq(1, #result.items)
+ local item = result.items[1]
+ item.user_data = nil
+ eq(expected, item)
+ end)
+end)
diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua
index f73ffc29b0..1da0222114 100644
--- a/test/functional/plugin/lsp/diagnostic_spec.lua
+++ b/test/functional/plugin/lsp/diagnostic_spec.lua
@@ -1,10 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
+local lsp_helpers = require('test.functional.plugin.lsp.helpers')
local clear = helpers.clear
local exec_lua = helpers.exec_lua
local eq = helpers.eq
local neq = require('test.helpers').neq
+local create_server_definition = lsp_helpers.create_server_definition
+
describe('vim.lsp.diagnostic', function()
local fake_uri
@@ -81,6 +84,7 @@ describe('vim.lsp.diagnostic', function()
local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"}
vim.fn.bufload(diagnostic_bufnr)
vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
+ vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
return diagnostic_bufnr
]], fake_uri)
end)
@@ -97,7 +101,6 @@ describe('vim.lsp.diagnostic', function()
}
diagnostics[1].code = 42
- diagnostics[1].tags = {"foo", "bar"}
diagnostics[1].data = "Hello world"
vim.lsp.diagnostic.on_publish_diagnostics(nil, {
@@ -110,10 +113,9 @@ describe('vim.lsp.diagnostic', function()
vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)[1],
}
]]
- eq({code = 42, tags = {"foo", "bar"}, data = "Hello world"}, result[1].user_data.lsp)
+ eq({code = 42, data = "Hello world"}, result[1].user_data.lsp)
eq(42, result[1].code)
eq(42, result[2].code)
- eq({"foo", "bar"}, result[2].tags)
eq("Hello world", result[2].data)
end)
end)
@@ -267,4 +269,155 @@ describe('vim.lsp.diagnostic', function()
eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 0)
end)
end)
+
+ describe('vim.lsp.diagnostic.on_diagnostic', function()
+ before_each(function()
+ exec_lua(create_server_definition)
+ exec_lua([[
+ server = _create_server({
+ capabilities = {
+ diagnosticProvider = {
+ }
+ }
+ })
+
+ function get_extmarks(bufnr, client_id)
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id, true)
+ local ns = vim.diagnostic.get_namespace(namespace)
+ local extmarks = {}
+ if ns.user_data.virt_text_ns then
+ for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do
+ table.insert(extmarks, e)
+ end
+ end
+ if ns.user_data.underline_ns then
+ for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do
+ table.insert(extmarks, e)
+ end
+ end
+ return extmarks
+ end
+
+ client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ ]])
+ end)
+
+ it('adds diagnostics to vim.diagnostics', function()
+ local diags = exec_lua([[
+ vim.lsp.diagnostic.on_diagnostic(nil,
+ {
+ kind = 'full',
+ items = {
+ make_error('Pull Diagnostic', 4, 4, 4, 4),
+ }
+ },
+ {
+ params = {
+ textDocument = { uri = fake_uri },
+ },
+ uri = fake_uri,
+ client_id = client_id,
+ },
+ {}
+ )
+
+ return vim.diagnostic.get(diagnostic_bufnr)
+ ]])
+ eq(1, #diags)
+ eq('Pull Diagnostic', diags[1].message)
+ end)
+
+ it('allows configuring the virtual text via vim.lsp.with', function()
+ local expected_spacing = 10
+ local extmarks = exec_lua(
+ [[
+ Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, {
+ virtual_text = {
+ spacing = ...,
+ },
+ })
+
+ Diagnostic(nil,
+ {
+ kind = 'full',
+ items = {
+ make_error('Pull Diagnostic', 4, 4, 4, 4),
+ }
+ },
+ {
+ params = {
+ textDocument = { uri = fake_uri },
+ },
+ uri = fake_uri,
+ client_id = client_id,
+ },
+ {}
+ )
+
+ return get_extmarks(diagnostic_bufnr, client_id)
+ ]],
+ expected_spacing
+ )
+ eq(2, #extmarks)
+ eq(expected_spacing, #extmarks[1][4].virt_text[1][1])
+ end)
+
+ it('clears diagnostics when client detaches', function()
+ exec_lua([[
+ vim.lsp.diagnostic.on_diagnostic(nil,
+ {
+ kind = 'full',
+ items = {
+ make_error('Pull Diagnostic', 4, 4, 4, 4),
+ }
+ },
+ {
+ params = {
+ textDocument = { uri = fake_uri },
+ },
+ uri = fake_uri,
+ client_id = client_id,
+ },
+ {}
+ )
+ ]])
+ local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
+ eq(1, #diags)
+
+ exec_lua([[ vim.lsp.stop_client(client_id) ]])
+
+ diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
+ eq(0, #diags)
+ end)
+
+ it('keeps diagnostics when one client detaches and others still are attached', function()
+ exec_lua([[
+ client_id2 = vim.lsp.start({ name = 'dummy2', cmd = server.cmd })
+
+ vim.lsp.diagnostic.on_diagnostic(nil,
+ {
+ kind = 'full',
+ items = {
+ make_error('Pull Diagnostic', 4, 4, 4, 4),
+ }
+ },
+ {
+ params = {
+ textDocument = { uri = fake_uri },
+ },
+ uri = fake_uri,
+ client_id = client_id,
+ },
+ {}
+ )
+ ]])
+ local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
+ eq(1, #diags)
+
+ exec_lua([[ vim.lsp.stop_client(client_id2) ]])
+
+ diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]])
+ eq(1, #diags)
+ end)
+ end)
end)
diff --git a/test/functional/plugin/lsp/helpers.lua b/test/functional/plugin/lsp/helpers.lua
index caab174b4d..15e6a62781 100644
--- a/test/functional/plugin/lsp/helpers.lua
+++ b/test/functional/plugin/lsp/helpers.lua
@@ -13,6 +13,7 @@ function M.clear_notrace()
-- solution: don't look too closely for dragons
clear {env={
NVIM_LUA_NOTRACK="1";
+ NVIM_APPNAME="nvim_lsp_test";
VIMRUNTIME=os.getenv"VIMRUNTIME";
}}
end
@@ -85,6 +86,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options, settings)
cmd_env = {
NVIM_LOG_FILE = fake_lsp_logfile;
NVIM_LUA_NOTRACK = "1";
+ NVIM_APPNAME = "nvim_lsp_test";
};
cmd = {
vim.v.progpath, '-l', fake_lsp_code, test_name, tostring(timeout),
@@ -97,7 +99,7 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options, settings)
end;
});
workspace_folders = {{
- uri = 'file://' .. vim.loop.cwd(),
+ uri = 'file://' .. vim.uv.cwd(),
name = 'test_folder',
}};
on_init = function(client, result)
diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua
index 4985da9cd7..724b3efb97 100644
--- a/test/functional/plugin/lsp/incremental_sync_spec.lua
+++ b/test/functional/plugin/lsp/incremental_sync_spec.lua
@@ -21,7 +21,7 @@ before_each(function ()
-- ["mac"] = '\r',
-- }
- -- local line_ending = format_line_ending[vim.api.nvim_buf_get_option(0, 'fileformat')]
+ -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})]
function test_register(bufnr, id, offset_encoding, line_ending)
diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua
new file mode 100644
index 0000000000..d0d55df72b
--- /dev/null
+++ b/test/functional/plugin/lsp/inlay_hint_spec.lua
@@ -0,0 +1,204 @@
+local helpers = require('test.functional.helpers')(after_each)
+local lsp_helpers = require('test.functional.plugin.lsp.helpers')
+local Screen = require('test.functional.ui.screen')
+
+local eq = helpers.eq
+local dedent = helpers.dedent
+local exec_lua = helpers.exec_lua
+local insert = helpers.insert
+
+local clear_notrace = lsp_helpers.clear_notrace
+local create_server_definition = lsp_helpers.create_server_definition
+
+local text = dedent([[
+auto add(int a, int b) { return a + b; }
+
+int main() {
+ int x = 1;
+ int y = 2;
+ return add(x,y);
+}
+}]])
+
+local response = [==[
+[
+{"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false},
+{"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true},
+{"kind":2,"paddingLeft":false,"label":"b:","position":{"character":17,"line":5},"paddingRight":true}
+]
+]==]
+
+local grid_without_inlay_hints = [[
+ auto add(int a, int b) { return a + b; } |
+ |
+ int main() { |
+ int x = 1; |
+ int y = 2; |
+ return add(x,y); |
+ } |
+ ^} |
+ |
+]]
+
+local grid_with_inlay_hints = [[
+ auto add(int a, int b)-> int { return a + b; } |
+ |
+ int main() { |
+ int x = 1; |
+ int y = 2; |
+ return add(a: x,b: y); |
+ } |
+ ^} |
+ |
+]]
+
+--- @type test.functional.ui.screen
+local screen
+before_each(function()
+ clear_notrace()
+ screen = Screen.new(50, 9)
+ screen:attach()
+
+ exec_lua(create_server_definition)
+ exec_lua([[
+ local response = ...
+ server = _create_server({
+ capabilities = {
+ inlayHintProvider = true,
+ },
+ handlers = {
+ ['textDocument/inlayHint'] = function()
+ return vim.json.decode(response)
+ end,
+ }
+ })
+
+ bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_win_set_buf(0, bufnr)
+
+ client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ ]], response)
+
+ insert(text)
+ exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]])
+ screen:expect({ grid = grid_with_inlay_hints })
+end)
+
+after_each(function()
+ exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })")
+end)
+
+describe('vim.lsp.inlay_hint', function()
+ it('clears inlay hints when sole client detaches', function()
+ exec_lua([[vim.lsp.stop_client(client_id)]])
+ screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
+ end)
+
+ it('does not clear inlay hints when one of several clients detaches', function()
+ exec_lua([[
+ server2 = _create_server({
+ capabilities = {
+ inlayHintProvider = true,
+ },
+ handlers = {
+ ['textDocument/inlayHint'] = function()
+ return {}
+ end,
+ }
+ })
+ client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
+ vim.lsp.inlay_hint.enable(bufnr)
+ ]])
+
+ exec_lua([[ vim.lsp.stop_client(client2) ]])
+ screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
+ end)
+
+ describe('enable()', function()
+ it('clears/applies inlay hints when passed false/true/nil', function()
+ exec_lua([[vim.lsp.inlay_hint.enable(bufnr, false)]])
+ screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
+
+ exec_lua([[vim.lsp.inlay_hint.enable(bufnr, true)]])
+ screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
+
+ exec_lua([[vim.lsp.inlay_hint.enable(bufnr, not vim.lsp.inlay_hint.is_enabled(bufnr))]])
+ screen:expect({ grid = grid_without_inlay_hints, unchanged = true })
+
+ exec_lua([[vim.lsp.inlay_hint.enable(bufnr)]])
+ screen:expect({ grid = grid_with_inlay_hints, unchanged = true })
+ end)
+ end)
+
+ describe('get()', function()
+ it('returns filtered inlay hints', function()
+ --- @type lsp.InlayHint[]
+ local expected = vim.json.decode(response)
+ local expected2 = {
+ kind = 1,
+ paddingLeft = false,
+ label = ': int',
+ position = {
+ character = 10,
+ line = 2,
+ },
+ paddingRight = false,
+ }
+
+ exec_lua([[
+ local expected2 = ...
+ server2 = _create_server({
+ capabilities = {
+ inlayHintProvider = true,
+ },
+ handlers = {
+ ['textDocument/inlayHint'] = function()
+ return { expected2 }
+ end,
+ }
+ })
+ client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })
+ vim.lsp.inlay_hint.enable(bufnr)
+ ]], expected2)
+
+ --- @type vim.lsp.inlay_hint.get.ret
+ local res = exec_lua([[return vim.lsp.inlay_hint.get()]])
+ eq(res, {
+ { bufnr = 1, client_id = 1, inlay_hint = expected[1] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
+ { bufnr = 1, client_id = 2, inlay_hint = expected2 },
+ })
+
+ --- @type vim.lsp.inlay_hint.get.ret
+ res = exec_lua([[return vim.lsp.inlay_hint.get({
+ range = {
+ start = { line = 2, character = 10 },
+ ["end"] = { line = 2, character = 10 },
+ },
+ })]])
+ eq(res, {
+ { bufnr = 1, client_id = 2, inlay_hint = expected2 },
+ })
+
+ --- @type vim.lsp.inlay_hint.get.ret
+ res = exec_lua([[return vim.lsp.inlay_hint.get({
+ bufnr = vim.api.nvim_get_current_buf(),
+ range = {
+ start = { line = 4, character = 18 },
+ ["end"] = { line = 5, character = 17 },
+ },
+ })]])
+ eq(res, {
+ { bufnr = 1, client_id = 1, inlay_hint = expected[2] },
+ { bufnr = 1, client_id = 1, inlay_hint = expected[3] },
+ })
+
+ --- @type vim.lsp.inlay_hint.get.ret
+ res = exec_lua([[return vim.lsp.inlay_hint.get({
+ bufnr = vim.api.nvim_get_current_buf() + 1,
+ })]])
+ eq(res, {})
+ end)
+ end)
+end)
diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua
index 9c1ba86fe1..b7ac53f270 100644
--- a/test/functional/plugin/lsp/semantic_tokens_spec.lua
+++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua
@@ -24,6 +24,27 @@ end)
describe('semantic token highlighting', function()
+ local screen
+ before_each(function()
+ screen = Screen.new(40, 16)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = { bold = true, foreground = Screen.colors.Blue1 };
+ [2] = { foreground = Screen.colors.DarkCyan };
+ [3] = { foreground = Screen.colors.SlateBlue };
+ [4] = { bold = true, foreground = Screen.colors.SeaGreen };
+ [5] = { foreground = tonumber('0x6a0dad') };
+ [6] = { foreground = Screen.colors.Blue1 };
+ [7] = { bold = true, foreground = Screen.colors.DarkCyan };
+ [8] = { bold = true, foreground = Screen.colors.SlateBlue };
+ [9] = { bold = true, foreground = tonumber('0x6a0dad') };
+ }
+ command([[ hi link @lsp.type.namespace Type ]])
+ command([[ hi link @lsp.type.function Special ]])
+ command([[ hi link @lsp.type.comment Comment ]])
+ command([[ hi @lsp.mod.declaration gui=bold ]])
+ end)
+
describe('general', function()
local text = dedent([[
#include <iostream>
@@ -58,24 +79,7 @@ describe('semantic token highlighting', function()
"resultId":"2"
}]]
- local screen
before_each(function()
- screen = Screen.new(40, 16)
- screen:attach()
- screen:set_default_attr_ids {
- [1] = { bold = true, foreground = Screen.colors.Blue1 };
- [2] = { foreground = Screen.colors.DarkCyan };
- [3] = { foreground = Screen.colors.SlateBlue };
- [4] = { bold = true, foreground = Screen.colors.SeaGreen };
- [5] = { foreground = tonumber('0x6a0dad') };
- [6] = { foreground = Screen.colors.Blue1 };
- [7] = { bold = true, foreground = Screen.colors.DarkCyan };
- [8] = { bold = true, foreground = Screen.colors.SlateBlue };
- }
- command([[ hi link @namespace Type ]])
- command([[ hi link @function Special ]])
- command([[ hi @declaration gui=bold ]])
-
exec_lua(create_server_definition)
exec_lua([[
local legend, response, edit_response = ...
@@ -102,6 +106,7 @@ describe('semantic token highlighting', function()
exec_lua([[
bufnr = vim.api.nvim_get_current_buf()
vim.api.nvim_win_set_buf(0, bufnr)
+ vim.bo[bufnr].filetype = 'some-filetype'
client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
]])
@@ -127,6 +132,46 @@ describe('semantic token highlighting', function()
]] }
end)
+ it('use LspTokenUpdate and highlight_token', function()
+ exec_lua([[
+ vim.api.nvim_create_autocmd("LspTokenUpdate", {
+ callback = function(args)
+ local token = args.data.token
+ if token.type == "function" and token.modifiers.declaration then
+ vim.lsp.semantic_tokens.highlight_token(
+ token, args.buf, args.data.client_id, "Macro"
+ )
+ end
+ end,
+ })
+ bufnr = vim.api.nvim_get_current_buf()
+ vim.api.nvim_win_set_buf(0, bufnr)
+ client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ ]])
+
+ insert(text)
+
+ screen:expect { grid = [[
+ #include <iostream> |
+ |
+ int {9:main}() |
+ { |
+ int {7:x}; |
+ #ifdef {5:__cplusplus} |
+ {4:std}::{2:cout} << {2:x} << "\n"; |
+ {6:#else} |
+ {6: printf("%d\n", x);} |
+ {6:#endif} |
+ } |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]] }
+
+ end)
+
it('buffer is unhighlighted when client is detached', function()
exec_lua([[
bufnr = vim.api.nvim_get_current_buf()
@@ -578,16 +623,33 @@ describe('semantic token highlighting', function()
expected = {
{
line = 0,
- modifiers = {
- 'declaration',
- 'globalScope',
- },
+ modifiers = { declaration = true, globalScope = true },
start_col = 6,
end_col = 9,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
},
+ expected_screen = function()
+ screen:expect{grid=[[
+ char* {7:foo} = "\n"^; |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
{
it = 'clangd-15 on C++',
@@ -613,67 +675,67 @@ int main()
expected = {
{ -- main
line = 1,
- modifiers = { 'declaration', 'globalScope' },
+ modifiers = { declaration = true, globalScope = true },
start_col = 4,
end_col = 8,
type = 'function',
- extmark_added = true,
+ marked = true,
},
{ -- __cplusplus
line = 3,
- modifiers = { 'globalScope' },
+ modifiers = { globalScope = true },
start_col = 9,
end_col = 20,
type = 'macro',
- extmark_added = true,
+ marked = true,
},
{ -- x
line = 4,
- modifiers = { 'declaration', 'readonly', 'functionScope' },
+ modifiers = { declaration = true, readonly = true, functionScope = true },
start_col = 12,
end_col = 13,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{ -- std
line = 5,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
start_col = 2,
end_col = 5,
type = 'namespace',
- extmark_added = true,
+ marked = true,
},
{ -- cout
line = 5,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
start_col = 7,
end_col = 11,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{ -- x
line = 5,
- modifiers = { 'readonly', 'functionScope' },
+ modifiers = { readonly = true, functionScope = true },
start_col = 15,
end_col = 16,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{ -- std
line = 5,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
start_col = 20,
end_col = 23,
type = 'namespace',
- extmark_added = true,
+ marked = true,
},
{ -- endl
line = 5,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
start_col = 25,
end_col = 29,
type = 'function',
- extmark_added = true,
+ marked = true,
},
{ -- #else comment #endif
line = 6,
@@ -681,7 +743,7 @@ int main()
start_col = 0,
end_col = 7,
type = 'comment',
- extmark_added = true,
+ marked = true,
},
{
line = 7,
@@ -689,7 +751,7 @@ int main()
start_col = 0,
end_col = 11,
type = 'comment',
- extmark_added = true,
+ marked = true,
},
{
line = 8,
@@ -697,9 +759,29 @@ int main()
start_col = 0,
end_col = 8,
type = 'comment',
- extmark_added = true,
+ marked = true,
},
},
+ expected_screen = function()
+ screen:expect{grid=[[
+ #include <iostream> |
+ int {8:main}() |
+ { |
+ #ifdef {5:__cplusplus} |
+ const int {7:x} = 1; |
+ {4:std}::{2:cout} << {2:x} << {4:std}::{3:endl}; |
+ {6: #else} |
+ {6: comment} |
+ {6: #endif} |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
{
it = 'sumneko_lua',
@@ -722,25 +804,45 @@ b = "as"]],
start_col = 0,
end_col = 10,
type = 'comment', -- comment
- extmark_added = true,
+ marked = true,
},
{
line = 1,
- modifiers = { 'declaration' }, -- a
+ modifiers = { declaration = true }, -- a
start_col = 6,
end_col = 7,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{
line = 2,
- modifiers = { 'static' }, -- b (global)
+ modifiers = { static = true }, -- b (global)
start_col = 0,
end_col = 1,
type = 'variable',
- extmark_added = true,
+ marked = true,
},
},
+ expected_screen = function()
+ screen:expect{grid=[[
+ {6:-- comment} |
+ local {7:a} = 1 |
+ {2:b} = "as^" |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
{
it = 'rust-analyzer',
@@ -768,7 +870,7 @@ b = "as"]],
start_col = 0,
end_col = 3, -- pub
type = 'keyword',
- extmark_added = true,
+ marked = true,
},
{
line = 0,
@@ -776,15 +878,15 @@ b = "as"]],
start_col = 4,
end_col = 6, -- fn
type = 'keyword',
- extmark_added = true,
+ marked = true,
},
{
line = 0,
- modifiers = { 'declaration', 'public' },
+ modifiers = { declaration = true, public = true },
start_col = 7,
end_col = 11, -- main
type = 'function',
- extmark_added = true,
+ marked = true,
},
{
line = 0,
@@ -792,7 +894,7 @@ b = "as"]],
start_col = 11,
end_col = 12,
type = 'parenthesis',
- extmark_added = true,
+ marked = true,
},
{
line = 0,
@@ -800,7 +902,7 @@ b = "as"]],
start_col = 12,
end_col = 13,
type = 'parenthesis',
- extmark_added = true,
+ marked = true,
},
{
line = 0,
@@ -808,15 +910,15 @@ b = "as"]],
start_col = 14,
end_col = 15,
type = 'brace',
- extmark_added = true,
+ marked = true,
},
{
line = 1,
- modifiers = { 'controlFlow' },
+ modifiers = { controlFlow = true },
start_col = 4,
end_col = 9, -- break
type = 'keyword',
- extmark_added = true,
+ marked = true,
},
{
line = 1,
@@ -824,7 +926,7 @@ b = "as"]],
start_col = 10,
end_col = 13, -- rust
type = 'unresolvedReference',
- extmark_added = true,
+ marked = true,
},
{
line = 1,
@@ -832,15 +934,15 @@ b = "as"]],
start_col = 13,
end_col = 13,
type = 'semicolon',
- extmark_added = true,
+ marked = true,
},
{
line = 2,
- modifiers = { 'documentation' },
+ modifiers = { documentation = true },
start_col = 4,
end_col = 11,
type = 'comment', -- /// what?
- extmark_added = true,
+ marked = true,
},
{
line = 3,
@@ -848,9 +950,29 @@ b = "as"]],
start_col = 0,
end_col = 1,
type = 'brace',
- extmark_added = true,
+ marked = true,
},
},
+ expected_screen = function()
+ screen:expect{grid=[[
+ pub fn {8:main}() { |
+ break rust; |
+ //{6:/ what?} |
+ } |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
}) do
it(test.it, function()
@@ -877,6 +999,8 @@ b = "as"]],
insert(test.text)
+ test.expected_screen()
+
local highlights = exec_lua([[
local semantic_tokens = vim.lsp.semantic_tokens
return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
@@ -906,28 +1030,68 @@ b = "as"]],
{
line = 0,
modifiers = {
- 'declaration',
- 'globalScope',
+ declaration = true,
+ globalScope = true,
},
start_col = 6,
end_col = 9,
type = 'variable',
- extmark_added = true,
+ marked = true,
}
},
expected2 = {
{
line = 1,
modifiers = {
- 'declaration',
- 'globalScope',
+ declaration = true,
+ globalScope = true,
},
start_col = 6,
end_col = 9,
type = 'variable',
- extmark_added = true,
+ marked = true,
}
},
+ expected_screen1 = function()
+ screen:expect{grid=[[
+ char* {7:foo} = "\n"^; |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
+ expected_screen2 = function()
+ screen:expect{grid=[[
+ ^ |
+ char* {7:foo} = "\n"; |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
{
it = 'response with multiple delta edits',
@@ -976,55 +1140,55 @@ int main()
line = 2,
start_col = 4,
end_col = 8,
- modifiers = { 'declaration', 'globalScope' },
+ modifiers = { declaration = true, globalScope = true },
type = 'function',
- extmark_added = true,
+ marked = true,
},
{
line = 4,
start_col = 8,
end_col = 9,
- modifiers = { 'declaration', 'functionScope' },
+ modifiers = { declaration = true, functionScope = true },
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{
line = 5,
start_col = 7,
end_col = 18,
- modifiers = { 'globalScope' },
+ modifiers = { globalScope = true },
type = 'macro',
- extmark_added = true,
+ marked = true,
},
{
line = 6,
start_col = 4,
end_col = 7,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
type = 'namespace',
- extmark_added = true,
+ marked = true,
},
{
line = 6,
start_col = 9,
end_col = 13,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{
line = 6,
start_col = 17,
end_col = 18,
- extmark_added = true,
- modifiers = { 'functionScope' },
+ marked = true,
+ modifiers = { functionScope = true },
type = 'variable',
},
{
line = 7,
start_col = 0,
end_col = 5,
- extmark_added = true,
+ marked = true,
modifiers = {},
type = 'comment',
},
@@ -1034,7 +1198,7 @@ int main()
modifiers = {},
start_col = 0,
type = 'comment',
- extmark_added = true,
+ marked = true,
},
{
line = 9,
@@ -1042,7 +1206,7 @@ int main()
end_col = 6,
modifiers = {},
type = 'comment',
- extmark_added = true,
+ marked = true,
}
},
expected2 = {
@@ -1050,63 +1214,63 @@ int main()
line = 2,
start_col = 4,
end_col = 8,
- modifiers = { 'declaration', 'globalScope' },
+ modifiers = { declaration = true, globalScope = true },
type = 'function',
- extmark_added = true,
+ marked = true,
},
{
line = 4,
start_col = 8,
end_col = 9,
- modifiers = { 'declaration', 'globalScope' },
+ modifiers = { declaration = true, globalScope = true },
type = 'function',
- extmark_added = true,
+ marked = true,
},
{
line = 5,
end_col = 12,
start_col = 11,
- modifiers = { 'declaration', 'functionScope' },
+ modifiers = { declaration = true, functionScope = true },
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{
line = 6,
start_col = 7,
end_col = 18,
- modifiers = { 'globalScope' },
+ modifiers = { globalScope = true },
type = 'macro',
- extmark_added = true,
+ marked = true,
},
{
line = 7,
start_col = 4,
end_col = 7,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
type = 'namespace',
- extmark_added = true,
+ marked = true,
},
{
line = 7,
start_col = 9,
end_col = 13,
- modifiers = { 'defaultLibrary', 'globalScope' },
+ modifiers = { defaultLibrary = true, globalScope = true },
type = 'variable',
- extmark_added = true,
+ marked = true,
},
{
line = 7,
start_col = 17,
end_col = 18,
- extmark_added = true,
- modifiers = { 'globalScope' },
+ marked = true,
+ modifiers = { globalScope = true },
type = 'function',
},
{
line = 8,
start_col = 0,
end_col = 5,
- extmark_added = true,
+ marked = true,
modifiers = {},
type = 'comment',
},
@@ -1116,7 +1280,7 @@ int main()
modifiers = {},
start_col = 0,
type = 'comment',
- extmark_added = true,
+ marked = true,
},
{
line = 10,
@@ -1124,9 +1288,49 @@ int main()
end_col = 6,
modifiers = {},
type = 'comment',
- extmark_added = true,
+ marked = true,
}
},
+ expected_screen1 = function()
+ screen:expect{grid=[[
+ #include <iostream> |
+ |
+ int {8:main}() |
+ { |
+ int {7:x}; |
+ #ifdef {5:__cplusplus} |
+ {4:std}::{2:cout} << {2:x} << "\n"; |
+ {6:#else} |
+ {6: printf("%d\n", x);} |
+ {6:#endif} |
+ ^} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
+ expected_screen2 = function()
+ screen:expect{grid=[[
+ #include <iostream> |
+ |
+ int {8:main}() |
+ { |
+ int {8:x}(); |
+ double {7:y}; |
+ #ifdef {5:__cplusplus} |
+ {4:std}::{2:cout} << {3:x} << "\n"; |
+ {6:#else} |
+ {6: printf("%d\n", x);} |
+ {6:^#endif} |
+ } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
{
it = 'optional token_edit.data on deletion',
@@ -1146,16 +1350,56 @@ int main()
{
line = 0,
modifiers = {
- 'declaration',
+ declaration = true,
},
start_col = 0,
end_col = 6,
type = 'variable',
- extmark_added = true,
+ marked = true,
}
},
expected2 = {
},
+ expected_screen1 = function()
+ screen:expect{grid=[[
+ {7:string} = "test^" |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
+ expected_screen2 = function()
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end,
},
}) do
it(test.it, function()
@@ -1192,6 +1436,8 @@ int main()
insert(test.text1)
+ test.expected_screen1()
+
local highlights = exec_lua([[
return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
]])
@@ -1204,10 +1450,12 @@ int main()
exec_lua([[
local text = ...
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n"))
- vim.wait(15) -- wait fot debounce
+ vim.wait(15) -- wait for debounce
]], test.text2)
end
+ test.expected_screen2()
+
highlights = exec_lua([[
return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights
]])
diff --git a/test/functional/plugin/lsp/snippet_spec.lua b/test/functional/plugin/lsp/snippet_spec.lua
index 7903885420..ba8bc7fe04 100644
--- a/test/functional/plugin/lsp/snippet_spec.lua
+++ b/test/functional/plugin/lsp/snippet_spec.lua
@@ -1,130 +1,71 @@
local helpers = require('test.functional.helpers')(after_each)
-local snippet = require('vim.lsp._snippet')
+local snippet = require('vim.lsp._snippet_grammar')
+local type = snippet.NodeType
local eq = helpers.eq
local exec_lua = helpers.exec_lua
-describe('vim.lsp._snippet', function()
+describe('vim.lsp._snippet_grammar', function()
before_each(helpers.clear)
after_each(helpers.clear)
local parse = function(...)
- return exec_lua('return require("vim.lsp._snippet").parse(...)', ...)
+ local res = exec_lua('return require("vim.lsp._snippet_grammar").parse(...)', ...)
+ return res.data.children
end
- it('should parse only text', function()
+ it('parses only text', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- },
+ { type = type.Text, data = { text = 'TE$}XT' } },
}, parse('TE\\$\\}XT'))
end)
- it('should parse tabstop', function()
+ it('parses tabstops', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 2,
- },
- },
+ { type = type.Tabstop, data = { tabstop = 1 } },
+ { type = type.Tabstop, data = { tabstop = 2 } },
}, parse('$1${2}'))
end)
- it('should parse placeholders', function()
+ it('parses nested placeholders', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.PLACEHOLDER,
+ {
+ type = type.Placeholder,
+ data = {
tabstop = 1,
- children = {
- {
- type = snippet.NodeType.PLACEHOLDER,
+ value = {
+ type = type.Placeholder,
+ data = {
tabstop = 2,
- children = {
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 3,
- },
- {
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
- option = 'i',
- format = {
- {
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
- },
- },
- },
- },
- {
- type = snippet.NodeType.TEXT,
- raw = 'TE\\$\\}XT',
- esc = 'TE$}XT',
- },
- },
+ value = { type = type.Tabstop, data = { tabstop = 3 } },
},
},
},
},
- }, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}'))
+ }, parse('${1:${2:${3}}}'))
end)
- it('should parse variables', function()
+ it('parses variables', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- },
- {
- type = snippet.NodeType.VARIABLE,
+ { type = type.Variable, data = { name = 'VAR' } },
+ { type = type.Variable, data = { name = 'VAR' } },
+ {
+ type = type.Variable,
+ data = {
name = 'VAR',
+ default = { type = type.Tabstop, data = { tabstop = 1 } },
},
- {
- type = snippet.NodeType.VARIABLE,
+ },
+ {
+ type = type.Variable,
+ data = {
name = 'VAR',
- children = {
+ regex = 'regex',
+ options = '',
+ format = {
{
- type = snippet.NodeType.TABSTOP,
- tabstop = 1,
- },
- },
- },
- {
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
- format = {
- {
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
- },
+ type = type.Format,
+ data = { capture = 1, modifier = 'upcase' },
},
},
},
@@ -132,105 +73,99 @@ describe('vim.lsp._snippet', function()
}, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}'))
end)
- it('should parse choice', function()
+ it('parses choice', function()
eq({
- type = snippet.NodeType.SNIPPET,
- children = {
- {
- type = snippet.NodeType.CHOICE,
- tabstop = 1,
- items = {
- ',',
- '|',
- },
- },
+ {
+ type = type.Choice,
+ data = { tabstop = 1, values = { ',', '|' } },
},
}, parse('${1|\\,,\\||}'))
end)
- it('should parse format', function()
- eq({
- type = snippet.NodeType.SNIPPET,
- children = {
+ it('parses format', function()
+ eq(
+ {
{
- type = snippet.NodeType.VARIABLE,
- name = 'VAR',
- transform = {
- type = snippet.NodeType.TRANSFORM,
- pattern = 'regex',
+ type = type.Variable,
+ data = {
+ name = 'VAR',
+ regex = 'regex',
+ options = '',
format = {
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- modifier = 'upcase',
+ type = type.Format,
+ data = { capture = 1, modifier = 'upcase' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = 'if_text',
- else_text = '',
+ type = type.Format,
+ data = { capture = 1, if_text = 'if_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = '',
- else_text = 'else_text',
+ type = type.Format,
+ data = { capture = 1, else_text = 'else_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- else_text = 'else_text',
- if_text = 'if_text',
+ type = type.Format,
+ data = { capture = 1, if_text = 'if_text', else_text = 'else_text' },
},
{
- type = snippet.NodeType.FORMAT,
- capture_index = 1,
- if_text = '',
- else_text = 'else_text',
+ type = type.Format,
+ data = { capture = 1, else_text = 'else_text' },
},
},
},
},
},
- }, parse('${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}'))
+ parse(
+ '${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}'
+ )
+ )
end)
- it('should parse empty strings', function()
+ it('parses empty strings', function()
eq({
- children = {
- {
- children = { {
- esc = '',
- raw = '',
- type = 7,
- } },
+ {
+ type = type.Placeholder,
+ data = {
tabstop = 1,
- type = 2,
- },
- {
- esc = ' ',
- raw = ' ',
- type = 7,
+ value = { type = type.Text, data = { text = '' } },
},
- {
+ },
+ {
+ type = type.Text,
+ data = { text = ' ' },
+ },
+ {
+ type = type.Variable,
+ data = {
name = 'VAR',
- transform = {
- format = {
- {
- capture_index = 1,
- else_text = '',
- if_text = '',
- type = 6,
- },
+ regex = 'erg',
+ format = {
+ {
+ type = type.Format,
+ data = { capture = 1, if_text = '' },
},
- option = 'g',
- pattern = 'erg',
- type = 5,
},
- type = 3,
+ options = 'g',
},
},
- type = 0,
- }, parse('${1:} ${VAR/erg/${1:?:}/g}'))
+ }, parse('${1:} ${VAR/erg/${1:+}/g}'))
+ end)
+
+ it('parses closing curly brace as text', function()
+ eq(
+ {
+ { type = type.Text, data = { text = 'function ' } },
+ { type = type.Tabstop, data = { tabstop = 1 } },
+ { type = type.Text, data = { text = '() {\n ' } },
+ { type = type.Tabstop, data = { tabstop = 0 } },
+ { type = type.Text, data = { text = '\n}' } },
+ },
+ parse(table.concat({
+ 'function $1() {',
+ ' $0',
+ '}',
+ }, '\n'))
+ )
end)
end)
diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua
new file mode 100644
index 0000000000..804dc32f0d
--- /dev/null
+++ b/test/functional/plugin/lsp/utils_spec.lua
@@ -0,0 +1,226 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local feed = helpers.feed
+
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+
+describe('vim.lsp.util', function()
+ before_each(helpers.clear)
+
+ describe('stylize_markdown', function()
+ local stylize_markdown = function(content, opts)
+ return exec_lua([[
+ local bufnr = vim.uri_to_bufnr("file:///fake/uri")
+ vim.fn.bufload(bufnr)
+
+ local args = { ... }
+ local content = args[1]
+ local opts = args[2]
+ local stripped_content = vim.lsp.util.stylize_markdown(bufnr, content, opts)
+
+ return stripped_content
+ ]], content, opts)
+ end
+
+ it('code fences', function()
+ local lines = {
+ "```lua",
+ "local hello = 'world'",
+ "```",
+ }
+ local expected = {
+ "local hello = 'world'",
+ }
+ local opts = {}
+ eq(expected, stylize_markdown(lines, opts))
+ end)
+
+ it('code fences with whitespace surrounded info string', function()
+ local lines = {
+ "``` lua ",
+ "local hello = 'world'",
+ "```",
+ }
+ local expected = {
+ "local hello = 'world'",
+ }
+ local opts = {}
+ eq(expected, stylize_markdown(lines, opts))
+ end)
+
+ it('adds separator after code block', function()
+ local lines = {
+ "```lua",
+ "local hello = 'world'",
+ "```",
+ "",
+ "something",
+ }
+ local expected = {
+ "local hello = 'world'",
+ "─────────────────────",
+ "something",
+ }
+ local opts = { separator = true }
+ eq(expected, stylize_markdown(lines, opts))
+ end)
+
+ it('replaces supported HTML entities', function()
+ local lines = {
+ "1 &lt; 2",
+ "3 &gt; 2",
+ "&quot;quoted&quot;",
+ "&apos;apos&apos;",
+ "&ensp; &emsp;",
+ "&amp;",
+ }
+ local expected = {
+ "1 < 2",
+ "3 > 2",
+ '"quoted"',
+ "'apos'",
+ " ",
+ "&",
+ }
+ local opts = {}
+ eq(expected, stylize_markdown(lines, opts))
+ end)
+ end)
+
+ describe('normalize_markdown', function ()
+ it('collapses consecutive blank lines', function ()
+ local result = exec_lua [[
+ local lines = {
+ 'foo',
+ '',
+ '',
+ '',
+ 'bar',
+ '',
+ 'baz'
+ }
+ return vim.lsp.util._normalize_markdown(lines)
+ ]]
+ local expected = {'foo', '', 'bar', '', 'baz'}
+ eq(expected, result)
+ end)
+
+ it('removes preceding and trailing empty lines', function ()
+ local result = exec_lua [[
+ local lines = {
+ '',
+ 'foo',
+ 'bar',
+ '',
+ ''
+ }
+ return vim.lsp.util._normalize_markdown(lines)
+ ]]
+ local expected = {'foo', 'bar'}
+ eq(expected, result)
+ end)
+ end)
+
+ describe("make_floating_popup_options", function ()
+
+ local function assert_anchor(anchor_bias, expected_anchor)
+ local opts = exec_lua([[
+ local args = { ... }
+ local anchor_bias = args[1]
+ return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias })
+ ]], anchor_bias)
+
+ eq(expected_anchor, string.sub(opts.anchor, 1, 1))
+ end
+
+ local screen
+ before_each(function ()
+ helpers.clear()
+ screen = Screen.new(80, 80)
+ screen:attach()
+ feed("79i<CR><Esc>") -- fill screen with empty lines
+ end)
+
+ describe('when on the first line it places window below', function ()
+ before_each(function ()
+ feed('gg')
+ end)
+
+ it('for anchor_bias = "auto"', function ()
+ assert_anchor('auto', 'N')
+ end)
+
+ it('for anchor_bias = "above"', function ()
+ assert_anchor('above', 'N')
+ end)
+
+ it('for anchor_bias = "below"', function ()
+ assert_anchor('below', 'N')
+ end)
+ end)
+
+ describe('when on the last line it places window above', function ()
+ before_each(function ()
+ feed('G')
+ end)
+
+ it('for anchor_bias = "auto"', function ()
+ assert_anchor('auto', 'S')
+ end)
+
+ it('for anchor_bias = "above"', function ()
+ assert_anchor('above', 'S')
+ end)
+
+ it('for anchor_bias = "below"', function ()
+ assert_anchor('below', 'S')
+ end)
+ end)
+
+ describe('with 20 lines above, 59 lines below', function ()
+ before_each(function ()
+ feed('gg20j')
+ end)
+
+ it('places window below for anchor_bias = "auto"', function ()
+ assert_anchor('auto', 'N')
+ end)
+
+ it('places window above for anchor_bias = "above"', function ()
+ assert_anchor('above', 'S')
+ end)
+
+ it('places window below for anchor_bias = "below"', function ()
+ assert_anchor('below', 'N')
+ end)
+ end)
+
+ describe('with 59 lines above, 20 lines below', function ()
+ before_each(function ()
+ feed('G20k')
+ end)
+
+ it('places window above for anchor_bias = "auto"', function ()
+ assert_anchor('auto', 'S')
+ end)
+
+ it('places window above for anchor_bias = "above"', function ()
+ assert_anchor('above', 'S')
+ end)
+
+ it('places window below for anchor_bias = "below"', function ()
+ assert_anchor('below', 'N')
+ end)
+
+ it('bordered window truncates dimensions correctly', function ()
+ local opts = exec_lua([[
+ return vim.lsp.util.make_floating_popup_options(100, 100, { border = 'single' })
+ ]])
+
+ eq(56, opts.height)
+ end)
+ end)
+ end)
+
+end)
diff --git a/test/functional/plugin/lsp/watchfiles_spec.lua b/test/functional/plugin/lsp/watchfiles_spec.lua
new file mode 100644
index 0000000000..a8260e0c98
--- /dev/null
+++ b/test/functional/plugin/lsp/watchfiles_spec.lua
@@ -0,0 +1,222 @@
+local helpers = require('test.functional.helpers')(after_each)
+
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+
+describe('vim.lsp._watchfiles', function()
+ before_each(helpers.clear)
+ after_each(helpers.clear)
+
+ local match = function(...)
+ return exec_lua('return require("vim.lsp._watchfiles")._match(...)', ...)
+ end
+
+ describe('glob matching', function()
+ it('should match literal strings', function()
+ eq(true, match('', ''))
+ eq(false, match('', 'a'))
+ eq(true, match('a', 'a'))
+ eq(true, match('/', '/'))
+ eq(true, match('abc', 'abc'))
+ eq(false, match('abc', 'abcdef'))
+ eq(false, match('abc', 'a'))
+ eq(false, match('abc', 'bc'))
+ eq(false, match('a', 'b'))
+ eq(false, match('.', 'a'))
+ eq(true, match('$', '$'))
+ eq(true, match('/dir', '/dir'))
+ eq(true, match('dir/', 'dir/'))
+ eq(true, match('dir/subdir', 'dir/subdir'))
+ eq(false, match('dir/subdir', 'subdir'))
+ eq(false, match('dir/subdir', 'dir/subdir/file'))
+ eq(true, match('🤠', '🤠'))
+ end)
+
+ it('should match * wildcards', function()
+ eq(false, match('*', ''))
+ eq(true, match('*', 'a'))
+ eq(false, match('*', '/'))
+ eq(false, match('*', '/a'))
+ eq(false, match('*', 'a/'))
+ eq(true, match('*', 'aaa'))
+ eq(true, match('*a', 'aa'))
+ eq(true, match('*a', 'abca'))
+ eq(true, match('*.txt', 'file.txt'))
+ eq(false, match('*.txt', 'file.txtxt'))
+ eq(false, match('*.txt', 'dir/file.txt'))
+ eq(false, match('*.txt', '/dir/file.txt'))
+ eq(false, match('*.txt', 'C:/dir/file.txt'))
+ eq(false, match('*.dir', 'test.dir/file'))
+ eq(true, match('file.*', 'file.txt'))
+ eq(false, match('file.*', 'not-file.txt'))
+ eq(true, match('*/file.txt', 'dir/file.txt'))
+ eq(false, match('*/file.txt', 'dir/subdir/file.txt'))
+ eq(false, match('*/file.txt', '/dir/file.txt'))
+ eq(true, match('dir/*', 'dir/file.txt'))
+ eq(false, match('dir/*', 'dir'))
+ eq(false, match('dir/*.txt', 'file.txt'))
+ eq(true, match('dir/*.txt', 'dir/file.txt'))
+ eq(false, match('dir/*.txt', 'dir/subdir/file.txt'))
+ eq(false, match('dir/*/file.txt', 'dir/file.txt'))
+ eq(true, match('dir/*/file.txt', 'dir/subdir/file.txt'))
+ eq(false, match('dir/*/file.txt', 'dir/subdir/subdir/file.txt'))
+
+ -- TODO: The spec does not describe this, but VSCode only interprets ** when it's by
+ -- itself in a path segment, and otherwise interprets ** as consecutive * directives.
+ -- The following tests show how this behavior should work, but is not yet fully implemented.
+ -- Currently, "a**" parses incorrectly as "a" "**" and "**a" parses correctly as "*" "*" "a".
+ -- see: https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/base/common/glob.ts#L112
+ eq(true, match('a**', 'abc')) -- '**' should parse as two '*'s when not by itself in a path segment
+ eq(true, match('**c', 'abc'))
+ -- eq(false, match('a**', 'ab')) -- each '*' should still represent at least one character
+ eq(false, match('**c', 'bc'))
+ eq(true, match('a**', 'abcd'))
+ eq(true, match('**d', 'abcd'))
+ -- eq(false, match('a**', 'abc/d'))
+ eq(false, match('**d', 'abc/d'))
+ end)
+
+ it('should match ? wildcards', function()
+ eq(false, match('?', ''))
+ eq(true, match('?', 'a'))
+ eq(false, match('??', 'a'))
+ eq(false, match('?', 'ab'))
+ eq(true, match('??', 'ab'))
+ eq(true, match('a?c', 'abc'))
+ eq(false, match('a?c', 'a/c'))
+ end)
+
+ it('should match ** wildcards', function()
+ eq(true, match('**', ''))
+ eq(true, match('**', 'a'))
+ eq(true, match('**', '/'))
+ eq(true, match('**', 'a/'))
+ eq(true, match('**', '/a'))
+ eq(true, match('**', 'C:/a'))
+ eq(true, match('**', 'a/a'))
+ eq(true, match('**', 'a/a/a'))
+ eq(false, match('/**', '')) -- /** matches leading / literally
+ eq(true, match('/**', '/'))
+ eq(true, match('/**', '/a/b/c'))
+ eq(true, match('**/', '')) -- **/ absorbs trailing /
+ eq(true, match('**/', '/a/b/c'))
+ eq(true, match('**/**', ''))
+ eq(true, match('**/**', 'a'))
+ eq(false, match('a/**', ''))
+ eq(false, match('a/**', 'a'))
+ eq(true, match('a/**', 'a/b'))
+ eq(true, match('a/**', 'a/b/c'))
+ eq(false, match('a/**', 'b/a'))
+ eq(false, match('a/**', '/a'))
+ eq(false, match('**/a', ''))
+ eq(true, match('**/a', 'a'))
+ eq(false, match('**/a', 'a/b'))
+ eq(true, match('**/a', '/a'))
+ eq(true, match('**/a', '/b/a'))
+ eq(true, match('**/a', '/c/b/a'))
+ eq(true, match('**/a', '/a/a'))
+ eq(true, match('**/a', '/abc/a'))
+ eq(false, match('a/**/c', 'a'))
+ eq(false, match('a/**/c', 'c'))
+ eq(true, match('a/**/c', 'a/c'))
+ eq(true, match('a/**/c', 'a/b/c'))
+ eq(true, match('a/**/c', 'a/b/b/c'))
+ eq(false, match('**/a/**', 'a'))
+ eq(true, match('**/a/**', 'a/'))
+ eq(false, match('**/a/**', '/dir/a'))
+ eq(false, match('**/a/**', 'dir/a'))
+ eq(true, match('**/a/**', 'dir/a/'))
+ eq(true, match('**/a/**', 'a/dir'))
+ eq(true, match('**/a/**', 'dir/a/dir'))
+ eq(true, match('**/a/**', '/a/dir'))
+ eq(true, match('**/a/**', 'C:/a/dir'))
+ eq(false, match('**/a/**', 'a.txt'))
+ end)
+
+ it('should match {} groups', function()
+ eq(true, match('{}', ''))
+ eq(false, match('{}', 'a'))
+ eq(true, match('a{}', 'a'))
+ eq(true, match('{}a', 'a'))
+ eq(true, match('{,}', ''))
+ eq(true, match('{a,}', ''))
+ eq(true, match('{a,}', 'a'))
+ eq(true, match('{a}', 'a'))
+ eq(false, match('{a}', 'aa'))
+ eq(false, match('{a}', 'ab'))
+ eq(true, match('{a?c}', 'abc'))
+ eq(false, match('{ab}', 'a'))
+ eq(false, match('{ab}', 'b'))
+ eq(true, match('{ab}', 'ab'))
+ eq(true, match('{a,b}', 'a'))
+ eq(true, match('{a,b}', 'b'))
+ eq(false, match('{a,b}', 'ab'))
+ eq(true, match('{ab,cd}', 'ab'))
+ eq(false, match('{ab,cd}', 'a'))
+ eq(true, match('{ab,cd}', 'cd'))
+ eq(true, match('{a,b,c}', 'c'))
+ eq(true, match('{a,{b,c}}', 'c'))
+ end)
+
+ it('should match [] groups', function()
+ eq(true, match('[]', '[]')) -- empty [] is a literal
+ eq(false, match('[a-z]', ''))
+ eq(true, match('[a-z]', 'a'))
+ eq(false, match('[a-z]', 'ab'))
+ eq(true, match('[a-z]', 'z'))
+ eq(true, match('[a-z]', 'j'))
+ eq(false, match('[a-f]', 'j'))
+ eq(false, match('[a-z]', '`')) -- 'a' - 1
+ eq(false, match('[a-z]', '{')) -- 'z' + 1
+ eq(false, match('[a-z]', 'A'))
+ eq(false, match('[a-z]', '5'))
+ eq(true, match('[A-Z]', 'A'))
+ eq(true, match('[A-Z]', 'Z'))
+ eq(true, match('[A-Z]', 'J'))
+ eq(false, match('[A-Z]', '@')) -- 'A' - 1
+ eq(false, match('[A-Z]', '[')) -- 'Z' + 1
+ eq(false, match('[A-Z]', 'a'))
+ eq(false, match('[A-Z]', '5'))
+ eq(true, match('[a-zA-Z0-9]', 'z'))
+ eq(true, match('[a-zA-Z0-9]', 'Z'))
+ eq(true, match('[a-zA-Z0-9]', '9'))
+ eq(false, match('[a-zA-Z0-9]', '&'))
+ end)
+
+ it('should match [!...] groups', function()
+ eq(true, match('[!]', '[!]')) -- [!] is a literal
+ eq(false, match('[!a-z]', ''))
+ eq(false, match('[!a-z]', 'a'))
+ eq(false, match('[!a-z]', 'z'))
+ eq(false, match('[!a-z]', 'j'))
+ eq(true, match('[!a-f]', 'j'))
+ eq(false, match('[!a-f]', 'jj'))
+ eq(true, match('[!a-z]', '`')) -- 'a' - 1
+ eq(true, match('[!a-z]', '{')) -- 'z' + 1
+ eq(false, match('[!a-zA-Z0-9]', 'a'))
+ eq(false, match('[!a-zA-Z0-9]', 'A'))
+ eq(false, match('[!a-zA-Z0-9]', '0'))
+ eq(true, match('[!a-zA-Z0-9]', '!'))
+ end)
+
+ it('should match complex patterns', function()
+ eq(false, match('**/*.{c,h}', ''))
+ eq(false, match('**/*.{c,h}', 'c'))
+ eq(false, match('**/*.{c,h}', 'file.m'))
+ eq(true, match('**/*.{c,h}', 'file.c'))
+ eq(true, match('**/*.{c,h}', 'file.h'))
+ eq(true, match('**/*.{c,h}', '/file.c'))
+ eq(true, match('**/*.{c,h}', 'dir/subdir/file.c'))
+ eq(true, match('**/*.{c,h}', 'dir/subdir/file.h'))
+ eq(true, match('**/*.{c,h}', '/dir/subdir/file.c'))
+ eq(true, match('**/*.{c,h}', 'C:/dir/subdir/file.c'))
+ eq(true, match('/dir/**/*.{c,h}', '/dir/file.c'))
+ eq(false, match('/dir/**/*.{c,h}', 'dir/file.c'))
+ eq(true, match('/dir/**/*.{c,h}', '/dir/subdir/subdir/file.c'))
+
+ eq(true, match('{[0-9],[a-z]}', '0'))
+ eq(true, match('{[0-9],[a-z]}', 'a'))
+ eq(false, match('{[0-9],[a-z]}', 'A'))
+ end)
+ end)
+end)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index fd162961ff..56d31a0e44 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -23,6 +23,7 @@ local is_ci = helpers.is_ci
local meths = helpers.meths
local is_os = helpers.is_os
local skip = helpers.skip
+local mkdir = helpers.mkdir
local clear_notrace = lsp_helpers.clear_notrace
local create_server_definition = lsp_helpers.create_server_definition
@@ -30,6 +31,13 @@ local fake_lsp_code = lsp_helpers.fake_lsp_code
local fake_lsp_logfile = lsp_helpers.fake_lsp_logfile
local test_rpc_server = lsp_helpers.test_rpc_server
+local function get_buf_option(name, bufnr)
+ bufnr = bufnr or "BUFFER"
+ return exec_lua(
+ string.format("return vim.api.nvim_get_option_value('%s', { buf = %s })", name, bufnr)
+ )
+end
+
-- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837
if skip(is_os('win')) then return end
@@ -51,12 +59,13 @@ describe('LSP', function()
return lsp.start_client {
cmd_env = {
NVIM_LOG_FILE = fake_lsp_logfile;
+ NVIM_APPNAME = "nvim_lsp_test";
};
cmd = {
vim.v.progpath, '-l', fake_lsp_code, test_name;
};
workspace_folders = {{
- uri = 'file://' .. vim.loop.cwd(),
+ uri = 'file://' .. vim.uv.cwd(),
name = 'test_folder',
}};
}
@@ -73,7 +82,7 @@ describe('LSP', function()
describe('server_name specified', function()
it('start_client(), stop_client()', function()
retry(nil, 4000, function()
- eq(1, exec_lua('return #lsp.get_active_clients()'))
+ eq(1, exec_lua('return #lsp.get_clients()'))
end)
eq(2, exec_lua([[
TEST_CLIENT2 = test__start_client()
@@ -84,20 +93,20 @@ describe('LSP', function()
return TEST_CLIENT3
]]))
retry(nil, 4000, function()
- eq(3, exec_lua('return #lsp.get_active_clients()'))
+ eq(3, exec_lua('return #lsp.get_clients()'))
end)
eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).is_stopped()'))
exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).stop()')
retry(nil, 4000, function()
- eq(2, exec_lua('return #lsp.get_active_clients()'))
+ eq(2, exec_lua('return #lsp.get_clients()'))
end)
eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil'))
exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})')
retry(nil, 4000, function()
- eq(0, exec_lua('return #lsp.get_active_clients()'))
+ eq(0, exec_lua('return #lsp.get_clients()'))
end)
end)
@@ -107,12 +116,12 @@ describe('LSP', function()
TEST_CLIENT3 = test__start_client()
]])
retry(nil, 4000, function()
- eq(3, exec_lua('return #lsp.get_active_clients()'))
+ eq(3, exec_lua('return #lsp.get_clients()'))
end)
-- Stop all clients.
- exec_lua('lsp.stop_client(lsp.get_active_clients())')
+ exec_lua('lsp.stop_client(lsp.get_clients())')
retry(nil, 4000, function()
- eq(0, exec_lua('return #lsp.get_active_clients()'))
+ eq(0, exec_lua('return #lsp.get_clients()'))
end)
end)
end)
@@ -142,7 +151,7 @@ describe('LSP', function()
describe('basic_init test', function()
after_each(function()
stop()
- exec_lua("lsp.stop_client(lsp.get_active_clients(), true)")
+ exec_lua("lsp.stop_client(lsp.get_clients(), true)")
exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })")
end)
@@ -209,6 +218,34 @@ describe('LSP', function()
})
end)
+ it("should set the client's offset_encoding when positionEncoding capability is supported", function()
+ clear()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server({
+ capabilities = {
+ positionEncoding = "utf-8"
+ },
+ })
+
+ local client_id = vim.lsp.start({
+ name = 'dummy',
+ cmd = server.cmd,
+ })
+
+ if not client_id then
+ return 'vim.lsp.start did not return client_id'
+ end
+
+ local client = vim.lsp.get_client_by_id(client_id)
+ if not client then
+ return 'No client found with id ' .. client_id
+ end
+ return client.offset_encoding
+ ]])
+ eq('utf-8', result)
+ end)
+
it('should succeed with manual shutdown', function()
if is_ci() then
pending('hangs the build on CI #14028, re-enable with freeze timeout #14204')
@@ -312,6 +349,118 @@ describe('LSP', function()
}
end)
+ it('should set default options on attach', function()
+ local client
+ test_rpc_server {
+ test_name = "set_defaults_all_capabilities";
+ on_init = function(_client)
+ client = _client
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
+ ]]
+ end;
+ on_handler = function(_, _, ctx)
+ if ctx.method == 'test' then
+ eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc"))
+ eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc"))
+ eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr"))
+ eq('', get_buf_option("keywordprg"))
+ eq(true, exec_lua[[
+ local keymap
+ vim.api.nvim_buf_call(BUFFER, function()
+ keymap = vim.fn.maparg("K", "n", false, true)
+ end)
+ return keymap.callback == vim.lsp.buf.hover
+ ]])
+ client.stop()
+ end
+ end;
+ on_exit = function(_, _)
+ eq('', get_buf_option("tagfunc"))
+ eq('', get_buf_option("omnifunc"))
+ eq('', get_buf_option("formatexpr"))
+ eq('', exec_lua[[
+ local keymap
+ vim.api.nvim_buf_call(BUFFER, function()
+ keymap = vim.fn.maparg("K", "n", false, false)
+ end)
+ return keymap
+ ]])
+ end;
+ }
+ end)
+
+ it('should overwrite options set by ftplugins', function()
+ local client
+ test_rpc_server {
+ test_name = "set_defaults_all_capabilities";
+ on_init = function(_client)
+ client = _client
+ exec_lua [[
+ vim.api.nvim_command('filetype plugin on')
+ BUFFER_1 = vim.api.nvim_create_buf(false, true)
+ BUFFER_2 = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_set_option_value('filetype', 'man', { buf = BUFFER_1 })
+ vim.api.nvim_set_option_value('filetype', 'xml', { buf = BUFFER_2 })
+ ]]
+
+ -- Sanity check to ensure that some values are set after setting filetype.
+ eq('v:lua.require\'man\'.goto_tag', get_buf_option("tagfunc", "BUFFER_1"))
+ eq('xmlcomplete#CompleteTags', get_buf_option("omnifunc", "BUFFER_2"))
+ eq('xmlformat#Format()', get_buf_option("formatexpr", "BUFFER_2"))
+
+ exec_lua [[
+ lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID)
+ lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID)
+ ]]
+ end;
+ on_handler = function(_, _, ctx)
+ if ctx.method == 'test' then
+ eq('v:lua.vim.lsp.tagfunc', get_buf_option("tagfunc", "BUFFER_1"))
+ eq('v:lua.vim.lsp.omnifunc', get_buf_option("omnifunc", "BUFFER_2"))
+ eq('v:lua.vim.lsp.formatexpr()', get_buf_option("formatexpr", "BUFFER_2"))
+ client.stop()
+ end
+ end;
+ on_exit = function(_, _)
+ eq('', get_buf_option("tagfunc", "BUFFER_1"))
+ eq('', get_buf_option("omnifunc", "BUFFER_2"))
+ eq('', get_buf_option("formatexpr", "BUFFER_2"))
+ end;
+ }
+ end)
+
+ it('should not overwrite user-defined options', function()
+ local client
+ test_rpc_server {
+ test_name = "set_defaults_all_capabilities";
+ on_init = function(_client)
+ client = _client
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = BUFFER })
+ vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = BUFFER })
+ vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = BUFFER })
+ lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)
+ ]]
+ end;
+ on_handler = function(_, _, ctx)
+ if ctx.method == 'test' then
+ eq('tfu', get_buf_option("tagfunc"))
+ eq('ofu', get_buf_option("omnifunc"))
+ eq('fex', get_buf_option("formatexpr"))
+ client.stop()
+ end
+ end;
+ on_exit = function(_, _)
+ eq('tfu', get_buf_option("tagfunc"))
+ eq('ofu', get_buf_option("omnifunc"))
+ eq('fex', get_buf_option("formatexpr"))
+ end;
+ }
+ end)
+
it('should detach buffer on bufwipe', function()
clear()
exec_lua(create_server_definition)
@@ -842,7 +991,7 @@ describe('LSP', function()
test_name = "check_tracked_requests_cleared";
on_init = function(_client)
command('let g:requests = 0')
- command('autocmd User LspRequest let g:requests+=1')
+ command('autocmd LspRequest * let g:requests+=1')
client = _client
client.request("slow_request")
eq(1, eval('g:requests'))
@@ -936,7 +1085,7 @@ describe('LSP', function()
eq(full_kind, client.server_capabilities().textDocumentSync.change)
eq(true, client.server_capabilities().textDocumentSync.openClose)
exec_lua [[
- assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice")
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Already attached, returns true")
]]
end;
on_exit = function(code, signal)
@@ -1062,7 +1211,7 @@ describe('LSP', function()
"testing";
"123";
})
- vim.api.nvim_buf_set_option(BUFFER, 'eol', false)
+ vim.bo[BUFFER].eol = false
]]
end;
on_init = function(_client)
@@ -1095,6 +1244,67 @@ describe('LSP', function()
}
end)
+ it('should send correct range for inlay hints with noeol', function()
+ local expected_handlers = {
+ {NIL, {}, {method="shutdown", client_id=1}};
+ {NIL, {}, {method="finish", client_id=1}};
+ {NIL, {}, {
+ method="textDocument/inlayHint",
+ params = {
+ textDocument = {
+ uri = 'file://',
+ },
+ range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 1, character = 3 },
+ }
+ },
+ bufnr=2,
+ client_id=1,
+ }};
+ {NIL, {}, {method="start", client_id=1}};
+ }
+ local client
+ test_rpc_server {
+ test_name = "inlay_hint";
+ on_setup = function()
+ exec_lua [[
+ BUFFER = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, {
+ "testing";
+ "123";
+ })
+ vim.bo[BUFFER].eol = false
+ ]]
+ end;
+ on_init = function(_client)
+ client = _client
+ eq(true, client.supports_method('textDocument/inlayHint'))
+ exec_lua [[
+ assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID))
+ ]]
+ end;
+ on_exit = function(code, signal)
+ eq(0, code, "exit code")
+ eq(0, signal, "exit signal")
+ end;
+ on_handler = function(err, result, ctx)
+ if ctx.method == 'start' then
+ exec_lua [[
+ vim.lsp.inlay_hint.enable(BUFFER)
+ ]]
+ end
+ if ctx.method == 'textDocument/inlayHint' then
+ client.notify('finish')
+ end
+ eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler")
+ if ctx.method == 'finish' then
+ client.stop()
+ end
+ end;
+ }
+ end)
+
it('should check the body and didChange incremental', function()
local expected_handlers = {
{NIL, {}, {method="shutdown", client_id=1}};
@@ -1545,6 +1755,54 @@ describe('LSP', function()
'foobar';
}, buf_lines(1))
end)
+ it('it restores marks', function()
+ local edits = {
+ make_edit(1, 0, 2, 5, "foobar");
+ make_edit(4, 0, 5, 0, "barfoo");
+ }
+ eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 2, 1, {})'))
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16")
+ eq({
+ 'First line of text';
+ 'foobar line of text';
+ 'Fourth line of text';
+ 'barfoo';
+ }, buf_lines(1))
+ local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")')
+ eq({ 2, 1 }, mark)
+ end)
+
+ it('it restores marks to last valid col', function()
+ local edits = {
+ make_edit(1, 0, 2, 15, "foobar");
+ make_edit(4, 0, 5, 0, "barfoo");
+ }
+ eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 2, 10, {})'))
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16")
+ eq({
+ 'First line of text';
+ 'foobarext';
+ 'Fourth line of text';
+ 'barfoo';
+ }, buf_lines(1))
+ local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")')
+ eq({ 2, 9 }, mark)
+ end)
+
+ it('it restores marks to last valid line', function()
+ local edits = {
+ make_edit(1, 0, 4, 5, "foobar");
+ make_edit(4, 0, 5, 0, "barfoo");
+ }
+ eq(true, exec_lua('return vim.api.nvim_buf_set_mark(1, "a", 4, 1, {})'))
+ exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16")
+ eq({
+ 'First line of text';
+ 'foobaro';
+ }, buf_lines(1))
+ local mark = exec_lua('return vim.api.nvim_buf_get_mark(1, "a")')
+ eq({ 2, 1 }, mark)
+ end)
describe('cursor position', function()
it('don\'t fix the cursor if the range contains the cursor', function()
@@ -1590,7 +1848,7 @@ describe('LSP', function()
eq({
'First line of text';
}, buf_lines(1))
- eq({ 1, 6 }, funcs.nvim_win_get_cursor(0))
+ eq({ 1, 17 }, funcs.nvim_win_get_cursor(0))
end)
it('fix the cursor row', function()
@@ -1609,6 +1867,9 @@ describe('LSP', function()
end)
it('fix the cursor col', function()
+ -- append empty last line. See #22636
+ exec_lua('vim.api.nvim_buf_set_lines(...)', 1, -1, -1, true, {''})
+
funcs.nvim_win_set_cursor(0, { 2, 11 })
local edits = {
make_edit(1, 7, 1, 11, '')
@@ -1620,6 +1881,7 @@ describe('LSP', function()
'Third line of text';
'Fourth line of text';
'å å ɧ 汉语 ↥ 🤦 🦄';
+ '';
}, buf_lines(1))
eq({ 2, 7 }, funcs.nvim_win_get_cursor(0))
end)
@@ -1921,7 +2183,7 @@ describe('LSP', function()
}
}
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
end)
it('Supports file creation in folder that needs to be created with CreateFile payload', function()
local tmpfile = helpers.tmpname()
@@ -1937,7 +2199,7 @@ describe('LSP', function()
}
}
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
end)
it('createFile does not touch file if it exists and ignoreIfExists is set', function()
local tmpfile = helpers.tmpname()
@@ -1955,7 +2217,7 @@ describe('LSP', function()
}
}
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
eq('Dummy content', read_file(tmpfile))
end)
it('createFile overrides file if overwrite is set', function()
@@ -1975,7 +2237,7 @@ describe('LSP', function()
}
}
exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
eq('', read_file(tmpfile))
end)
it('DeleteFile delete file and buffer', function()
@@ -1996,7 +2258,7 @@ describe('LSP', function()
}
}
eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16'))
- eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
+ eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile))
end)
it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function()
@@ -2015,58 +2277,13 @@ describe('LSP', function()
}
}
eq(false, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit))
- eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile))
- end)
- end)
-
- describe('completion_list_to_complete_items', function()
- -- Completion option precedence:
- -- textEdit.newText > insertText > label
- -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
- it('should choose right completion option', function ()
- local prefix = 'foo'
- local completion_list = {
- -- resolves into label
- { label='foobar', sortText="a" },
- { label='foobar', sortText="b", textEdit={} },
- -- resolves into insertText
- { label='foocar', sortText="c", insertText='foobar' },
- { label='foocar', sortText="d", insertText='foobar', textEdit={} },
- -- resolves into textEdit.newText
- { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} },
- { label='foocar', sortText="f", textEdit={newText='foobar'} },
- -- real-world snippet text
- { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} },
- { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} },
- -- nested snippet tokens
- { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} },
- -- braced tabstop
- { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} },
- -- plain text
- { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} },
- }
- local completion_list_items = {items=completion_list}
- local expected = {
- { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label = 'foobar', sortText="a" } } } } },
- { abbr = 'foobar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foobar', sortText="b", textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="c", insertText='foobar' } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="d", insertText='foobar', textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="e", insertText='foodar', textEdit={newText='foobar'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="f", textEdit={newText='foobar'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foobar(place holder, more ...holder{})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="g", insertText='foodar', insertTextFormat=2, textEdit={newText='foobar(${1:place holder}, ${2:more ...holder{\\}})'} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ1, var2 *typ2) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="h", insertText='foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(var1 typ2 tail) {}', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="i", insertText='foodar(${1:var1 ${2|typ2,typ3|} ${3:tail}}) {$0\\}', insertTextFormat=2, textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar()', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="j", insertText='foodar()${0}', insertTextFormat=2, textEdit={} } } } } },
- { abbr = 'foocar', dup = 1, empty = 1, icase = 1, info = ' ', kind = 'Unknown', menu = '', word = 'foodar(${1:var1})', user_data = { nvim = { lsp = { completion_item = { label='foocar', sortText="k", insertText='foodar(${1:var1})', insertTextFormat=1, textEdit={} } } } } },
- }
-
- eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list, prefix))
- eq(expected, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], completion_list_items, prefix))
- eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix))
+ eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', tmpfile))
end)
end)
describe('lsp.util.rename', function()
+ local pathsep = helpers.get_pathsep()
+
it('Can rename an existing file', function()
local old = helpers.tmpname()
write_file(old, 'Test content')
@@ -2083,12 +2300,56 @@ describe('LSP', function()
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
]], old, new)
eq({'Test content'}, lines)
- local exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', old)
+ local exists = exec_lua('return vim.uv.fs_stat(...) ~= nil', old)
eq(false, exists)
- exists = exec_lua('return vim.loop.fs_stat(...) ~= nil', new)
+ exists = exec_lua('return vim.uv.fs_stat(...) ~= nil', new)
eq(true, exists)
os.remove(new)
end)
+ it("Kills old buffer after renaming an existing file", function()
+ local old = helpers.tmpname()
+ write_file(old, 'Test content')
+ local new = helpers.tmpname()
+ os.remove(new) -- only reserve the name, file must not exist for the test scenario
+ local lines = exec_lua([[
+ local old = select(1, ...)
+ local oldbufnr = vim.fn.bufadd(old)
+ local new = select(2, ...)
+ vim.lsp.util.rename(old, new)
+ return vim.fn.bufloaded(oldbufnr)
+ ]], old, new)
+ eq(0, lines)
+ os.remove(new)
+ end)
+ it('Can rename a directory', function()
+ -- only reserve the name, file must not exist for the test scenario
+ local old_dir = helpers.tmpname()
+ local new_dir = helpers.tmpname()
+ os.remove(old_dir)
+ os.remove(new_dir)
+
+ helpers.mkdir_p(old_dir)
+
+ local file = 'file.txt'
+ write_file(old_dir .. pathsep .. file, 'Test content')
+
+ local lines = exec_lua([[
+ local old_dir = select(1, ...)
+ local new_dir = select(2, ...)
+ local pathsep = select(3, ...)
+ local oldbufnr = vim.fn.bufadd(old_dir .. pathsep .. 'file')
+
+ vim.lsp.util.rename(old_dir, new_dir)
+ return vim.fn.bufloaded(oldbufnr)
+ ]], old_dir, new_dir, pathsep)
+ eq(0, lines)
+ eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', old_dir))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new_dir))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new_dir .. pathsep .. file))
+ eq('Test content', read_file(new_dir .. pathsep .. file))
+
+ os.remove(new_dir)
+ end)
it('Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function()
local old = helpers.tmpname()
write_file(old, 'Old File')
@@ -2102,7 +2363,7 @@ describe('LSP', function()
vim.lsp.util.rename(old, new, { ignoreIfExists = true })
]], old, new)
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', old))
eq('New file', read_file(new))
exec_lua([[
@@ -2112,7 +2373,7 @@ describe('LSP', function()
vim.lsp.util.rename(old, new, { overwrite = false })
]], old, new)
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', old))
eq('New file', read_file(new))
end)
it('Does override target if overwrite is true', function()
@@ -2127,8 +2388,8 @@ describe('LSP', function()
vim.lsp.util.rename(old, new, { overwrite = true })
]], old, new)
- eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', old))
- eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', new))
+ eq(false, exec_lua('return vim.uv.fs_stat(...) ~= nil', old))
+ eq(true, exec_lua('return vim.uv.fs_stat(...) ~= nil', new))
eq('Old file\n', read_file(new))
end)
end)
@@ -2140,7 +2401,14 @@ describe('LSP', function()
filename = '/fake/uri',
lnum = 1,
col = 3,
- text = 'testing'
+ text = 'testing',
+ user_data = {
+ uri = 'file:///fake/uri',
+ range = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ }
},
}
local actual = exec_lua [[
@@ -2166,7 +2434,18 @@ describe('LSP', function()
filename = '/fake/uri',
lnum = 1,
col = 3,
- text = 'testing'
+ text = 'testing',
+ user_data = {
+ targetUri = "file:///fake/uri",
+ targetRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ },
+ targetSelectionRange = {
+ start = { line = 0, character = 2 },
+ ['end'] = { line = 0, character = 3 },
+ }
+ }
},
}
local actual = exec_lua [[
@@ -2443,18 +2722,6 @@ describe('LSP', function()
end)
end)
- describe('lsp.util._get_completion_item_kind_name', function()
- it('returns the name specified by protocol', function()
- eq("Text", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1)"))
- eq("TypeParameter", exec_lua("return vim.lsp.util._get_completion_item_kind_name(25)"))
- end)
- it('returns the name not specified by protocol', function()
- eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(nil)"))
- eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(vim.NIL)"))
- eq("Unknown", exec_lua("return vim.lsp.util._get_completion_item_kind_name(1000)"))
- end)
- end)
-
describe('lsp.util._get_symbol_kind_name', function()
it('returns the name specified by protocol', function()
eq("File", exec_lua("return vim.lsp.util._get_symbol_kind_name(1)"))
@@ -2743,6 +3010,18 @@ describe('LSP', function()
it('calculates size correctly with wrapping', function()
eq({15,5}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]])
end)
+
+ it('handles NUL bytes in text', function()
+ exec_lua([[ contents = {
+ '\000\001\002\003\004\005\006\007\008\009',
+ '\010\011\012\013\014\015\016\017\018\019',
+ '\020\021\022\023\024\025\026\027\028\029',
+ } ]])
+ command('set list listchars=')
+ eq({20,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
+ command('set display+=uhex')
+ eq({40,3}, exec_lua[[ return {vim.lsp.util._make_floating_popup_size(contents)} ]])
+ end)
end)
describe('lsp.util.trim.trim_empty_lines', function()
@@ -2759,7 +3038,7 @@ describe('LSP', function()
activeSignature = -1,
signatures = {
{
- documentation = "",
+ documentation = "some doc",
label = "TestEntity.TestEntity()",
parameters = {}
},
@@ -2767,7 +3046,7 @@ describe('LSP', function()
}
return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','})
]]
- local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''}
+ local expected = {'```cs', 'TestEntity.TestEntity()', '```', 'some doc'}
eq(expected, result)
end)
end)
@@ -2775,8 +3054,8 @@ describe('LSP', function()
describe('lsp.util.get_effective_tabstop', function()
local function test_tabstop(tabsize, shiftwidth)
exec_lua(string.format([[
- vim.api.nvim_buf_set_option(0, 'shiftwidth', %d)
- vim.api.nvim_buf_set_option(0, 'tabstop', 2)
+ vim.bo.shiftwidth = %d
+ vim.bo.tabstop = 2
]], shiftwidth))
eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()'))
end
@@ -2998,9 +3277,10 @@ describe('LSP', function()
eq(0, signal, "exit signal")
end;
on_handler = function(err, result, ctx)
- -- Don't compare & assert params, they're not relevant for the testcase
+ -- Don't compare & assert params and version, they're not relevant for the testcase
-- This allows us to be lazy and avoid declaring them
ctx.params = nil
+ ctx.version = nil
eq(table.remove(test.expected_handlers), {err, result, ctx}, "expected handler")
if ctx.method == 'start' then
@@ -3082,6 +3362,7 @@ describe('LSP', function()
end,
on_handler = function(err, result, ctx)
ctx.params = nil -- don't compare in assert
+ ctx.version = nil
eq(table.remove(expected_handlers), { err, result, ctx })
if ctx.method == 'start' then
exec_lua([[
@@ -3123,22 +3404,22 @@ describe('LSP', function()
vim.lsp.commands['executed_preferred'] = function()
end
end
- vim.lsp.commands['quickfix_command'] = function(cmd)
- vim.lsp.commands['executed_quickfix'] = function()
+ vim.lsp.commands['type_annotate_command'] = function(cmd)
+ vim.lsp.commands['executed_type_annotate'] = function()
end
end
local bufnr = vim.api.nvim_get_current_buf()
vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID)
vim.lsp.buf.code_action({ filter = function(a) return a.isPreferred end, apply = true, })
vim.lsp.buf.code_action({
- -- expect to be returned actions 'quickfix' and 'quickfix.foo'
- context = { only = {'quickfix'}, },
+ -- expect to be returned actions 'type-annotate' and 'type-annotate.foo'
+ context = { only = { 'type-annotate' }, },
apply = true,
filter = function(a)
- if a.kind == 'quickfix.foo' then
- vim.lsp.commands['filtered_quickfix_foo'] = function() end
+ if a.kind == 'type-annotate.foo' then
+ vim.lsp.commands['filtered_type_annotate_foo'] = function() end
return false
- elseif a.kind == 'quickfix' then
+ elseif a.kind == 'type-annotate' then
return true
else
assert(nil, 'unreachable')
@@ -3148,13 +3429,57 @@ describe('LSP', function()
]])
elseif ctx.method == 'shutdown' then
eq('function', exec_lua[[return type(vim.lsp.commands['executed_preferred'])]])
- eq('function', exec_lua[[return type(vim.lsp.commands['filtered_quickfix_foo'])]])
- eq('function', exec_lua[[return type(vim.lsp.commands['executed_quickfix'])]])
+ eq('function', exec_lua[[return type(vim.lsp.commands['filtered_type_annotate_foo'])]])
+ eq('function', exec_lua[[return type(vim.lsp.commands['executed_type_annotate'])]])
client.stop()
end
end
}
end)
+ it("Fallback to command execution on resolve error", function()
+ clear()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server({
+ capabilities = {
+ executeCommandProvider = {
+ commands = {"command:1"},
+ },
+ codeActionProvider = {
+ resolveProvider = true
+ }
+ },
+ handlers = {
+ ["textDocument/codeAction"] = function()
+ return {
+ {
+ title = "Code Action 1",
+ command = {
+ title = "Command 1",
+ command = "command:1",
+ }
+ }
+ }
+ end,
+ ["codeAction/resolve"] = function()
+ return nil, "resolve failed"
+ end,
+ }
+ })
+
+ local client_id = vim.lsp.start({
+ name = "dummy",
+ cmd = server.cmd,
+ })
+
+ vim.lsp.buf.code_action({ apply = true })
+ vim.lsp.stop_client(client_id)
+ return server.messages
+ ]])
+ eq("codeAction/resolve", result[4].method)
+ eq("workspace/executeCommand", result[5].method)
+ eq("command:1", result[5].params.command)
+ end)
end)
describe('vim.lsp.commands', function()
it('Accepts only string keys', function()
@@ -3421,6 +3746,7 @@ describe('LSP', function()
vim.cmd.normal('v')
vim.api.nvim_win_set_cursor(0, { 2, 3 })
vim.lsp.buf.format({ bufnr = bufnr, false })
+ vim.lsp.stop_client(client_id)
return server.messages
]])
eq("textDocument/rangeFormatting", result[3].method)
@@ -3430,6 +3756,52 @@ describe('LSP', function()
}
eq(expected_range, result[3].params.range)
end)
+ it('format formats range in visual line mode', function()
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local server = _create_server({ capabilities = {
+ documentFormattingProvider = true,
+ documentRangeFormattingProvider = true,
+ }})
+ local bufnr = vim.api.nvim_get_current_buf()
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ vim.api.nvim_win_set_buf(0, bufnr)
+ vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar baz'})
+ vim.api.nvim_win_set_cursor(0, { 1, 2 })
+ vim.cmd.normal('V')
+ vim.api.nvim_win_set_cursor(0, { 2, 1 })
+ vim.lsp.buf.format({ bufnr = bufnr, false })
+
+ -- Format again with visual lines going from bottom to top
+ -- Must result in same formatting
+ vim.cmd.normal("<ESC>")
+ vim.api.nvim_win_set_cursor(0, { 2, 1 })
+ vim.cmd.normal('V')
+ vim.api.nvim_win_set_cursor(0, { 1, 2 })
+ vim.lsp.buf.format({ bufnr = bufnr, false })
+
+ vim.lsp.stop_client(client_id)
+ return server.messages
+ ]])
+ local expected_methods = {
+ "initialize",
+ "initialized",
+ "textDocument/rangeFormatting",
+ "$/cancelRequest",
+ "textDocument/rangeFormatting",
+ "$/cancelRequest",
+ "shutdown",
+ "exit",
+ }
+ eq(expected_methods, vim.tbl_map(function(x) return x.method end, result))
+ -- uses first column of start line and last column of end line
+ local expected_range = {
+ start = { line = 0, character = 0 },
+ ['end'] = { line = 1, character = 7 },
+ }
+ eq(expected_range, result[3].params.range)
+ eq(expected_range, result[5].params.range)
+ end)
it('Aborts with notify if no clients support requested method', function()
exec_lua(create_server_definition)
exec_lua([[
@@ -3466,7 +3838,7 @@ describe('LSP', function()
describe('cmd', function()
it('can connect to lsp server via rpc.connect', function()
local result = exec_lua [[
- local uv = vim.loop
+ local uv = vim.uv
local server = uv.new_tcp()
local init = nil
server:bind('127.0.0.1', 0)
@@ -3494,7 +3866,7 @@ describe('LSP', function()
describe('handlers', function()
it('handler can return false as response', function()
local result = exec_lua [[
- local uv = vim.loop
+ local uv = vim.uv
local server = uv.new_tcp()
local messages = {}
local responses = {}
@@ -3561,4 +3933,626 @@ describe('LSP', function()
eq(expected, result)
end)
end)
+
+ describe('#dynamic vim.lsp._dynamic', function()
+ it('supports dynamic registration', function()
+ local root_dir = helpers.tmpname()
+ os.remove(root_dir)
+ mkdir(root_dir)
+ local tmpfile = root_dir .. '/dynamic.foo'
+ local file = io.open(tmpfile, 'w')
+ file:close()
+
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local root_dir, tmpfile = ...
+
+ local server = _create_server()
+ local client_id = vim.lsp.start({
+ name = 'dynamic-test',
+ cmd = server.cmd,
+ root_dir = root_dir,
+ capabilities = {
+ textDocument = {
+ formatting = {
+ dynamicRegistration = true,
+ },
+ rangeFormatting = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ })
+
+ local expected_messages = 2 -- initialize, initialized
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'formatting',
+ method = 'textDocument/formatting',
+ registerOptions = {
+ documentSelector = {{
+ pattern = root_dir .. '/*.foo',
+ }},
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'range-formatting',
+ method = 'textDocument/rangeFormatting',
+ },
+ },
+ }, { client_id = client_id })
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'completion',
+ method = 'textDocument/completion',
+ },
+ },
+ }, { client_id = client_id })
+
+ local result = {}
+ local function check(method, fname)
+ local bufnr = fname and vim.fn.bufadd(fname) or nil
+ local client = vim.lsp.get_client_by_id(client_id)
+ result[#result + 1] = {method = method, fname = fname, supported = client.supports_method(method, {bufnr = bufnr})}
+ end
+
+
+ check("textDocument/formatting")
+ check("textDocument/formatting", tmpfile)
+ check("textDocument/rangeFormatting")
+ check("textDocument/rangeFormatting", tmpfile)
+ check("textDocument/completion")
+
+ return result
+ ]], root_dir, tmpfile)
+
+ eq(5, #result)
+ eq({method = 'textDocument/formatting', supported = false}, result[1])
+ eq({method = 'textDocument/formatting', supported = true, fname = tmpfile}, result[2])
+ eq({method = 'textDocument/rangeFormatting', supported = true}, result[3])
+ eq({method = 'textDocument/rangeFormatting', supported = true, fname = tmpfile}, result[4])
+ eq({method = 'textDocument/completion', supported = false}, result[5])
+ end)
+ end)
+
+ describe('vim.lsp._watchfiles', function()
+ it('sends notifications when files change', function()
+ skip(is_os('bsd'), "bsd only reports rename on folders if file inside change")
+ local root_dir = helpers.tmpname()
+ os.remove(root_dir)
+ mkdir(root_dir)
+
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local root_dir = ...
+
+ local server = _create_server()
+ local client_id = vim.lsp.start({
+ name = 'watchfiles-test',
+ cmd = server.cmd,
+ root_dir = root_dir,
+ capabilities = {
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ })
+
+ local expected_messages = 2 -- initialize, initialized
+
+ local watchfunc = require('vim.lsp._watchfiles')._watchfunc
+ local msg_wait_timeout = watchfunc == vim._watch.poll and 2500 or 200
+ local function wait_for_messages()
+ assert(vim.wait(msg_wait_timeout, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
+ end
+
+ wait_for_messages()
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-0',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = '**/watch',
+ kind = 7,
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ if watchfunc == vim._watch.poll then
+ vim.wait(100)
+ end
+
+ local path = root_dir .. '/watch'
+ local file = io.open(path, 'w')
+ file:close()
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ os.remove(path)
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ return server.messages
+ ]], root_dir)
+
+ local function watched_uri(fname)
+ return exec_lua([[
+ local root_dir, fname = ...
+ return vim.uri_from_fname(root_dir .. '/' .. fname)
+ ]], root_dir, fname)
+ end
+
+ eq(4, #result)
+ eq('workspace/didChangeWatchedFiles', result[3].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('watch'),
+ },
+ },
+ }, result[3].params)
+ eq('workspace/didChangeWatchedFiles', result[4].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
+ uri = watched_uri('watch'),
+ },
+ },
+ }, result[4].params)
+ end)
+
+ it('correctly registers and unregisters', function()
+ local root_dir = '/some_dir'
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local root_dir = ...
+
+ local server = _create_server()
+ local client_id = vim.lsp.start({
+ name = 'watchfiles-test',
+ cmd = server.cmd,
+ root_dir = root_dir,
+ capabilities = {
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ })
+
+ local expected_messages = 2 -- initialize, initialized
+ local function wait_for_messages()
+ assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
+ end
+
+ wait_for_messages()
+
+ local send_event
+ require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
+ local stopped = false
+ send_event = function(...)
+ if not stopped then
+ callback(...)
+ end
+ end
+ return function()
+ stopped = true
+ end
+ end
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-0',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = '**/*.watch0',
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
+ send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-1',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = '**/*.watch1',
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ vim.lsp.handlers['client/unregisterCapability'](nil, {
+ unregisterations = {
+ {
+ id = 'watchfiles-test-0',
+ method = 'workspace/didChangeWatchedFiles',
+ },
+ },
+ }, { client_id = client_id })
+
+ send_event(root_dir .. '/file.watch0', vim._watch.FileChangeType.Created)
+ send_event(root_dir .. '/file.watch1', vim._watch.FileChangeType.Created)
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ return server.messages
+ ]], root_dir)
+
+ local function watched_uri(fname)
+ return exec_lua([[
+ local root_dir, fname = ...
+ return vim.uri_from_fname(root_dir .. '/' .. fname)
+ ]], root_dir, fname)
+ end
+
+ eq(4, #result)
+ eq('workspace/didChangeWatchedFiles', result[3].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('file.watch0'),
+ },
+ },
+ }, result[3].params)
+ eq('workspace/didChangeWatchedFiles', result[4].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('file.watch1'),
+ },
+ },
+ }, result[4].params)
+ end)
+
+ it('correctly handles the registered watch kind', function()
+ local root_dir = 'some_dir'
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local root_dir = ...
+
+ local server = _create_server()
+ local client_id = vim.lsp.start({
+ name = 'watchfiles-test',
+ cmd = server.cmd,
+ root_dir = root_dir,
+ capabilities = {
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ })
+
+ local expected_messages = 2 -- initialize, initialized
+ local function wait_for_messages()
+ assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
+ end
+
+ wait_for_messages()
+
+ local watch_callbacks = {}
+ local function send_event(...)
+ for _, cb in ipairs(watch_callbacks) do
+ cb(...)
+ end
+ end
+ require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
+ table.insert(watch_callbacks, callback)
+ return function()
+ -- noop because this test never stops the watch
+ end
+ end
+
+ local protocol = require('vim.lsp.protocol')
+
+ local watchers = {}
+ local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete
+ for i = 0, max_kind do
+ table.insert(watchers, {
+ globPattern = {
+ baseUri = vim.uri_from_fname('/dir'),
+ pattern = 'watch'..tostring(i),
+ },
+ kind = i,
+ })
+ end
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-kind',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = watchers,
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ for i = 0, max_kind do
+ local filename = '/dir/watch' .. tostring(i)
+ send_event(filename, vim._watch.FileChangeType.Created)
+ send_event(filename, vim._watch.FileChangeType.Changed)
+ send_event(filename, vim._watch.FileChangeType.Deleted)
+ end
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ return server.messages
+ ]], root_dir)
+
+ local function watched_uri(fname)
+ return exec_lua([[
+ local fname = ...
+ return vim.uri_from_fname('/dir/' .. fname)
+ ]], fname)
+ end
+
+ eq(3, #result)
+ eq('workspace/didChangeWatchedFiles', result[3].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('watch1'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
+ uri = watched_uri('watch2'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('watch3'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
+ uri = watched_uri('watch3'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
+ uri = watched_uri('watch4'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('watch5'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
+ uri = watched_uri('watch5'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
+ uri = watched_uri('watch6'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
+ uri = watched_uri('watch6'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('watch7'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
+ uri = watched_uri('watch7'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]),
+ uri = watched_uri('watch7'),
+ },
+ },
+ }, result[3].params)
+ end)
+
+ it('prunes duplicate events', function()
+ local root_dir = 'some_dir'
+ exec_lua(create_server_definition)
+ local result = exec_lua([[
+ local root_dir = ...
+
+ local server = _create_server()
+ local client_id = vim.lsp.start({
+ name = 'watchfiles-test',
+ cmd = server.cmd,
+ root_dir = root_dir,
+ capabilities = {
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
+ },
+ })
+
+ local expected_messages = 2 -- initialize, initialized
+ local function wait_for_messages()
+ assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages))
+ end
+
+ wait_for_messages()
+
+ local send_event
+ require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
+ send_event = callback
+ return function()
+ -- noop because this test never stops the watch
+ end
+ end
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-kind',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = '**/*',
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ send_event('file1', vim._watch.FileChangeType.Created)
+ send_event('file1', vim._watch.FileChangeType.Created) -- pruned
+ send_event('file1', vim._watch.FileChangeType.Changed)
+ send_event('file2', vim._watch.FileChangeType.Created)
+ send_event('file1', vim._watch.FileChangeType.Changed) -- pruned
+
+ expected_messages = expected_messages + 1
+ wait_for_messages()
+
+ return server.messages
+ ]], root_dir)
+
+ local function watched_uri(fname)
+ return exec_lua([[
+ return vim.uri_from_fname(...)
+ ]], fname)
+ end
+
+ eq(3, #result)
+ eq('workspace/didChangeWatchedFiles', result[3].method)
+ eq({
+ changes = {
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('file1'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]),
+ uri = watched_uri('file1'),
+ },
+ {
+ type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]),
+ uri = watched_uri('file2'),
+ },
+ },
+ }, result[3].params)
+ end)
+
+ it("ignores registrations by servers when the client doesn't advertise support", function()
+ exec_lua(create_server_definition)
+ exec_lua([[
+ server = _create_server()
+ require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback)
+ -- Since the registration is ignored, this should not execute and `watching` should stay false
+ watching = true
+ return function() end
+ end
+ ]])
+
+ local function check_registered(capabilities)
+ return exec_lua([[
+ watching = false
+ local client_id = vim.lsp.start({
+ name = 'watchfiles-test',
+ cmd = server.cmd,
+ root_dir = 'some_dir',
+ capabilities = ...,
+ }, {
+ reuse_client = function() return false end,
+ })
+
+ vim.lsp.handlers['client/registerCapability'](nil, {
+ registrations = {
+ {
+ id = 'watchfiles-test-kind',
+ method = 'workspace/didChangeWatchedFiles',
+ registerOptions = {
+ watchers = {
+ {
+ globPattern = '**/*',
+ },
+ },
+ },
+ },
+ },
+ }, { client_id = client_id })
+
+ -- Ensure no errors occur when unregistering something that was never really registered.
+ vim.lsp.handlers['client/unregisterCapability'](nil, {
+ unregisterations = {
+ {
+ id = 'watchfiles-test-kind',
+ method = 'workspace/didChangeWatchedFiles',
+ },
+ },
+ }, { client_id = client_id })
+
+ vim.lsp.stop_client(client_id, true)
+ return watching
+ ]], capabilities)
+ end
+
+ eq(true, check_registered(nil)) -- start{_client}() defaults to make_client_capabilities().
+ eq(false, check_registered(vim.empty_dict()))
+ eq(false, check_registered({
+ workspace = {
+ ignoreMe = true,
+ },
+ }))
+ eq(false, check_registered({
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = false,
+ },
+ },
+ }))
+ eq(true, check_registered({
+ workspace = {
+ didChangeWatchedFiles = {
+ dynamicRegistration = true,
+ },
+ },
+ }))
+ end)
+ end)
end)
+
diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua
index 58da059be6..815ddbc523 100644
--- a/test/functional/plugin/man_spec.lua
+++ b/test/functional/plugin/man_spec.lua
@@ -8,9 +8,30 @@ local nvim_prog = helpers.nvim_prog
local matches = helpers.matches
local write_file = helpers.write_file
local tmpname = helpers.tmpname
+local eq = helpers.eq
+local pesc = helpers.pesc
local skip = helpers.skip
local is_ci = helpers.is_ci
+-- Collects all names passed to find_path() after attempting ":Man foo".
+local function get_search_history(name)
+ local args = vim.split(name, ' ')
+ local code = [[
+ local args = ...
+ local man = require('runtime.lua.man')
+ local res = {}
+ man.find_path = function(sect, name)
+ table.insert(res, {sect, name})
+ return nil
+ end
+ local ok, rv = pcall(man.open_page, -1, {tab = 0}, args)
+ assert(not ok)
+ assert(rv and rv:match('no manual entry'))
+ return res
+ ]]
+ return exec_lua(code, args)
+end
+
clear()
if funcs.executable('man') == 0 then
pending('missing "man" command', function() end)
@@ -169,8 +190,39 @@ describe(':Man', function()
write_file(actual_file, '')
local args = {nvim_prog, '--headless', '+:Man ' .. actual_file, '+q'}
matches(('Error detected while processing command line:\r\n' ..
- 'man.lua: "no manual entry for %s"'):format(actual_file),
+ 'man.lua: "no manual entry for %s"'):format(pesc(actual_file)),
funcs.system(args, {''}))
os.remove(actual_file)
end)
+
+ it('tries variants with spaces, underscores #22503', function()
+ eq({
+ {'', 'NAME WITH SPACES'},
+ {'', 'NAME_WITH_SPACES'},
+ }, get_search_history('NAME WITH SPACES'))
+ eq({
+ {'3', 'some other man'},
+ {'3', 'some_other_man'},
+ }, get_search_history('3 some other man'))
+ eq({
+ {'3x', 'some other man'},
+ {'3x', 'some_other_man'},
+ }, get_search_history('3X some other man'))
+ eq({
+ {'3tcl', 'some other man'},
+ {'3tcl', 'some_other_man'},
+ }, get_search_history('3tcl some other man'))
+ eq({
+ {'n', 'some other man'},
+ {'n', 'some_other_man'},
+ }, get_search_history('n some other man'))
+ eq({
+ {'', '123some other man'},
+ {'', '123some_other_man'},
+ }, get_search_history('123some other man'))
+ eq({
+ {'1', 'other_man'},
+ {'1', 'other_man'},
+ }, get_search_history('other_man(1)'))
+ end)
end)
diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua
index 93cf6d2b77..8d37100607 100644
--- a/test/functional/plugin/shada_spec.lua
+++ b/test/functional/plugin/shada_spec.lua
@@ -2150,6 +2150,7 @@ describe('plugin/shada.vim', function()
teardown(function()
os.remove(fname)
+ os.remove(fname .. '.tst')
os.remove(fname_tmp)
end)
@@ -2180,8 +2181,8 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "a"',
}, nvim_eval('getline(1, "$")'))
- eq(false, curbuf('get_option', 'modified'))
- eq('shada', curbuf('get_option', 'filetype'))
+ eq(false, nvim('get_option_value', 'modified', {}))
+ eq('shada', nvim('get_option_value', 'filetype', {}))
nvim_command('edit ' .. fname_tmp)
eq({
'History entry with timestamp ' .. epoch .. ':',
@@ -2190,8 +2191,8 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "b"',
}, nvim_eval('getline(1, "$")'))
- eq(false, curbuf('get_option', 'modified'))
- eq('shada', curbuf('get_option', 'filetype'))
+ eq(false, nvim('get_option_value', 'modified', {}))
+ eq('shada', nvim('get_option_value', 'filetype', {}))
eq('++opt not supported', exc_exec('edit ++enc=latin1 ' .. fname))
neq({
'History entry with timestamp ' .. epoch .. ':',
@@ -2200,7 +2201,7 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "a"',
}, nvim_eval('getline(1, "$")'))
- neq(true, curbuf('get_option', 'modified'))
+ neq(true, nvim('get_option_value', 'modified', {}))
end)
it('event FileReadCmd', function()
@@ -2216,8 +2217,8 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "a"',
}, nvim_eval('getline(1, "$")'))
- eq(true, curbuf('get_option', 'modified'))
- neq('shada', curbuf('get_option', 'filetype'))
+ eq(true, nvim('get_option_value', 'modified', {}))
+ neq('shada', nvim('get_option_value', 'filetype', {}))
nvim_command('1,$read ' .. fname_tmp)
eq({
'',
@@ -2232,9 +2233,9 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "b"',
}, nvim_eval('getline(1, "$")'))
- eq(true, curbuf('get_option', 'modified'))
- neq('shada', curbuf('get_option', 'filetype'))
- curbuf('set_option', 'modified', false)
+ eq(true, nvim('get_option_value', 'modified', {}))
+ neq('shada', nvim('get_option_value', 'filetype', {}))
+ nvim('set_option_value', 'modified', false, {})
eq('++opt not supported', exc_exec('$read ++enc=latin1 ' .. fname))
eq({
'',
@@ -2249,7 +2250,7 @@ describe('plugin/shada.vim', function()
' - contents "ab"',
' - "b"',
}, nvim_eval('getline(1, "$")'))
- neq(true, curbuf('get_option', 'modified'))
+ neq(true, nvim('get_option_value', 'modified', {}))
end)
it('event BufWriteCmd', function()
@@ -2419,6 +2420,9 @@ describe('plugin/shada.vim', function()
it('event SourceCmd', function()
reset(fname)
+ finally(function()
+ nvim_command('set shadafile=NONE') -- Avoid writing shada file on exit
+ end)
wshada('\004\000\006\146\000\196\002ab')
wshada_tmp('\004\001\006\146\000\196\002bc')
eq(0, exc_exec('source ' .. fname))
@@ -2513,10 +2517,10 @@ describe('ftplugin/shada.vim', function()
it('sets options correctly', function()
nvim_command('filetype plugin indent on')
nvim_command('setlocal filetype=shada')
- eq(true, curbuf('get_option', 'expandtab'))
- eq(2, curbuf('get_option', 'tabstop'))
- eq(2, curbuf('get_option', 'softtabstop'))
- eq(2, curbuf('get_option', 'shiftwidth'))
+ eq(true, nvim('get_option_value', 'expandtab', {}))
+ eq(2, nvim('get_option_value', 'tabstop', {}))
+ eq(2, nvim('get_option_value', 'softtabstop', {}))
+ eq(2, nvim('get_option_value', 'shiftwidth', {}))
end)
it('sets indentkeys correctly', function()
diff --git a/test/functional/plugin/tutor_spec.lua b/test/functional/plugin/tutor_spec.lua
new file mode 100644
index 0000000000..bd214e9c03
--- /dev/null
+++ b/test/functional/plugin/tutor_spec.lua
@@ -0,0 +1,95 @@
+local Screen = require('test.functional.ui.screen')
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local command = helpers.command
+local feed = helpers.feed
+local is_os = helpers.is_os
+
+describe(':Tutor', function()
+ local screen
+
+ before_each(function()
+ clear({ args = { '--clean' } })
+ command('set cmdheight=0')
+ command('Tutor')
+ screen = Screen.new(80, 30)
+ screen:set_default_attr_ids({
+ [0] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray },
+ [1] = { bold = true },
+ [2] = { underline = true, foreground = tonumber('0x0088ff') },
+ [3] = { foreground = Screen.colors.SlateBlue },
+ [4] = { bold = true, foreground = Screen.colors.Brown },
+ [5] = { bold = true, foreground = Screen.colors.Magenta1 },
+ })
+ screen:attach()
+ end)
+
+ it('applies {unix:…,win:…} transform', function()
+ local expected = is_os('win') and [[
+ {0: }^ |
+ {0: } 3. To verify that a file was retrieved, cursor back and notice that there |
+ {0: } are now two copies of Lesson 5.3, the original and the retrieved version. |
+ {0: } |
+ {0: }{1:NOTE}: You can also read the output of an external command. For example, |
+ {0: } |
+ {0: } :r {4:!}dir |
+ {0: } |
+ {0: } reads the output of the ls command and puts it below the cursor. |
+ {0: } |
+ {0: }{3:#}{5: Lesson 5 SUMMARY} |
+ {0: } |
+ {0: } 1. {2::!command} executes an external command. |
+ {0: } |
+ {0: } Some useful examples are: |
+ {0: } :{4:!}dir - shows a directory listing |
+ {0: } :{4:!}del FILENAME - removes file FILENAME |
+ {0: } |
+ {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with |
+ {0: } name FILENAME. |
+ {0: } |
+ {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file |
+ {0: } FILENAME. |
+ {0: } |
+ {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it |
+ {0: } below the cursor position. |
+ {0: } |
+ {0: } 5. {2::r !dir} reads the output of the dir command and |
+ {0: } puts it below the cursor position. |
+ {0: } |
+ ]] or [[
+ {0: }^ |
+ {0: } 3. To verify that a file was retrieved, cursor back and notice that there |
+ {0: } are now two copies of Lesson 5.3, the original and the retrieved version. |
+ {0: } |
+ {0: }{1:NOTE}: You can also read the output of an external command. For example, |
+ {0: } |
+ {0: } :r {4:!}ls |
+ {0: } |
+ {0: } reads the output of the ls command and puts it below the cursor. |
+ {0: } |
+ {0: }{3:#}{5: Lesson 5 SUMMARY} |
+ {0: } |
+ {0: } 1. {2::!command} executes an external command. |
+ {0: } |
+ {0: } Some useful examples are: |
+ {0: } :{4:!}ls - shows a directory listing |
+ {0: } :{4:!}rm FILENAME - removes file FILENAME |
+ {0: } |
+ {0: } 2. {2::w} FILENAME writes the current Neovim file to disk with |
+ {0: } name FILENAME. |
+ {0: } |
+ {0: } 3. {2:v} motion :w FILENAME saves the Visually selected lines in file |
+ {0: } FILENAME. |
+ {0: } |
+ {0: } 4. {2::r} FILENAME retrieves disk file FILENAME and puts it |
+ {0: } below the cursor position. |
+ {0: } |
+ {0: } 5. {2::r !ls} reads the output of the ls command and |
+ {0: } puts it below the cursor position. |
+ {0: } |
+ ]]
+
+ feed(':700<CR>zt')
+ screen:expect(expected)
+ end)
+end)
diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua
index 2c5185a974..0099183302 100644
--- a/test/functional/provider/clipboard_spec.lua
+++ b/test/functional/provider/clipboard_spec.lua
@@ -211,7 +211,7 @@ describe('clipboard', function()
eq('', eval('provider#clipboard#Error()'))
end)
- it('g:clipboard using VimL functions', function()
+ it('g:clipboard using Vimscript functions', function()
-- Implements a fake clipboard provider. cache_enabled is meaningless here.
source([[let g:clipboard = {
\ 'name': 'custom',
@@ -245,7 +245,7 @@ describe('clipboard', function()
eq({{'star', ''}, 'b'}, eval("g:dummy_clipboard_star"))
end)
- describe('g:clipboard[paste] VimL function', function()
+ describe('g:clipboard[paste] Vimscript function', function()
it('can return empty list for empty clipboard', function()
source([[let g:dummy_clipboard = []
let g:clipboard = {
@@ -301,7 +301,7 @@ end)
describe('clipboard (with fake clipboard.vim)', function()
local function reset(...)
- clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp', ...)
+ clear('--cmd', 'set rtp^=test/functional/fixtures', ...)
end
before_each(function()
diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua
index ce92831f4c..8049f0f3e2 100644
--- a/test/functional/provider/perl_spec.lua
+++ b/test/functional/provider/perl_spec.lua
@@ -5,12 +5,10 @@ local command = helpers.command
local write_file = helpers.write_file
local eval = helpers.eval
local retry = helpers.retry
-local curbufmeths = helpers.curbufmeths
+local meths = helpers.meths
local insert = helpers.insert
local expect = helpers.expect
local feed = helpers.feed
-local is_os = helpers.is_os
-local skip = helpers.skip
do
clear()
@@ -26,8 +24,6 @@ before_each(function()
end)
describe('legacy perl provider', function()
- skip(is_os('win'))
-
it('feature test', function()
eq(1, eval('has("perl")'))
end)
@@ -49,7 +45,7 @@ describe('legacy perl provider', function()
-- :perldo 1; doesn't change $_,
-- the buffer should not be changed
command('normal :perldo 1;')
- eq(false, curbufmeths.get_option('modified'))
+ eq(false, meths.get_option_value('modified', {}))
-- insert some text
insert('abc\ndef\nghi')
expect([[
@@ -70,7 +66,6 @@ describe('legacy perl provider', function()
end)
describe('perl provider', function()
- skip(is_os('win'))
teardown(function ()
os.remove('Xtest-perl-hello.pl')
os.remove('Xtest-perl-hello-plugin.pl')
diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua
index 3895b8613f..b1c326d04c 100644
--- a/test/functional/provider/provider_spec.lua
+++ b/test/functional/provider/provider_spec.lua
@@ -7,7 +7,7 @@ local pcall_err = helpers.pcall_err
describe('providers', function()
before_each(function()
- clear('--cmd', 'let &rtp = "test/functional/fixtures,".&rtp')
+ clear('--cmd', 'set rtp^=test/functional/fixtures')
end)
it('with #Call(), missing g:loaded_xx_provider', function()
diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua
index fba96100fc..d3b967dfbe 100644
--- a/test/functional/provider/ruby_spec.lua
+++ b/test/functional/provider/ruby_spec.lua
@@ -3,7 +3,6 @@ local helpers = require('test.functional.helpers')(after_each)
local assert_alive = helpers.assert_alive
local clear = helpers.clear
local command = helpers.command
-local curbufmeths = helpers.curbufmeths
local eq = helpers.eq
local exc_exec = helpers.exc_exec
local expect = helpers.expect
@@ -98,7 +97,7 @@ describe(':rubydo command', function()
it('does not modify the buffer if no changes are made', function()
command('normal :rubydo 42')
- eq(false, curbufmeths.get_option('modified'))
+ eq(false, meths.get_option_value('modified', {}))
end)
end)
diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua
index 26a4569fce..b1c4ded541 100644
--- a/test/functional/shada/buffers_spec.lua
+++ b/test/functional/shada/buffers_spec.lua
@@ -1,7 +1,7 @@
-- shada buffer list saving/reading support
local helpers = require('test.functional.helpers')(after_each)
-local nvim_command, funcs, eq, curbufmeths =
- helpers.command, helpers.funcs, helpers.eq, helpers.curbufmeths
+local nvim_command, funcs, eq, curbufmeths, meths =
+ helpers.command, helpers.funcs, helpers.eq, helpers.curbufmeths, helpers.meths
local expect_exit = helpers.expect_exit
local shada_helpers = require('test.functional.shada.helpers')
@@ -48,7 +48,7 @@ describe('shada support code', function()
reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
- curbufmeths.set_option('buflisted', false)
+ meths.set_option_value('buflisted', false, {})
expect_exit(nvim_command, 'qall')
reset('set shada+=%')
eq(2, funcs.bufnr('$'))
@@ -60,7 +60,7 @@ describe('shada support code', function()
reset('set shada+=%')
nvim_command('edit ' .. testfilename)
nvim_command('edit ' .. testfilename_2)
- curbufmeths.set_option('buftype', 'quickfix')
+ meths.set_option_value('buftype', 'quickfix', {})
expect_exit(nvim_command, 'qall')
reset('set shada+=%')
eq(2, funcs.bufnr('$'))
diff --git a/test/functional/shada/helpers.lua b/test/functional/shada/helpers.lua
index fb3ec4a87c..cd99d38345 100644
--- a/test/functional/shada/helpers.lua
+++ b/test/functional/shada/helpers.lua
@@ -33,6 +33,7 @@ local function reset(o)
end
local clear = function()
+ helpers.expect_exit(helpers.command, 'qall!')
os.remove(tmpname)
end
diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua
index d1daf6c7cc..433db3171e 100644
--- a/test/functional/shada/history_spec.lua
+++ b/test/functional/shada/history_spec.lua
@@ -32,7 +32,6 @@ describe('ShaDa support code', function()
nvim_command('rshada')
eq('" Test 2', funcs.histget(':', -1))
eq('" Test', funcs.histget(':', -2))
- expect_exit(nvim_command, 'qall')
end)
it('respects &history when dumping',
@@ -107,13 +106,13 @@ describe('ShaDa support code', function()
end)
it('dumps and loads last search pattern with offset', function()
- meths.set_option('wrapscan', false)
+ meths.set_option_value('wrapscan', false, {})
funcs.setline('.', {'foo', 'bar--'})
nvim_feed('gg0/a/e+1\n')
eq({0, 2, 3, 0}, funcs.getpos('.'))
nvim_command('wshada')
reset()
- meths.set_option('wrapscan', false)
+ meths.set_option_value('wrapscan', false, {})
funcs.setline('.', {'foo', 'bar--'})
nvim_feed('gg0n')
eq({0, 2, 3, 0}, funcs.getpos('.'))
@@ -122,13 +121,13 @@ describe('ShaDa support code', function()
it('dumps and loads last search pattern with offset and backward direction',
function()
- meths.set_option('wrapscan', false)
+ meths.set_option_value('wrapscan', false, {})
funcs.setline('.', {'foo', 'bar--'})
nvim_feed('G$?a?e+1\n')
eq({0, 2, 3, 0}, funcs.getpos('.'))
nvim_command('wshada')
reset()
- meths.set_option('wrapscan', false)
+ meths.set_option_value('wrapscan', false, {})
funcs.setline('.', {'foo', 'bar--'})
nvim_feed('G$n')
eq({0, 2, 3, 0}, funcs.getpos('.'))
diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua
index a91be18841..7f03022ab8 100644
--- a/test/functional/shada/marks_spec.lua
+++ b/test/functional/shada/marks_spec.lua
@@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each)
local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq =
helpers.meths, helpers.curwinmeths, helpers.curbufmeths, helpers.command,
helpers.funcs, helpers.eq
+local feed = helpers.feed
local exc_exec, exec_capture = helpers.exc_exec, helpers.exec_capture
local expect_exit = helpers.expect_exit
@@ -248,4 +249,49 @@ describe('ShaDa support code', function()
eq('', funcs.system(argv))
eq(0, exc_exec('rshada'))
end)
+
+ it('updates deleted marks with :delmarks', function()
+ nvim_command('edit ' .. testfilename)
+
+ nvim_command('mark A')
+ nvim_command('mark a')
+ -- create a change to set the '.' mark,
+ -- since it can't be set via :mark
+ feed('ggifoobar<esc>')
+ nvim_command('wshada')
+
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! `A`a`.')
+ nvim_command('delmarks A a .')
+ nvim_command('wshada')
+
+ reset()
+ nvim_command('edit ' .. testfilename)
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `A'))
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `a'))
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `.'))
+ end)
+
+ it('updates deleted marks with :delmarks!', function()
+ nvim_command('edit ' .. testfilename)
+
+ nvim_command('mark A')
+ nvim_command('mark a')
+ feed('ggifoobar<esc>')
+ nvim_command('wshada')
+
+ reset()
+ nvim_command('edit ' .. testfilename)
+ nvim_command('normal! `A`a`.')
+ nvim_command('delmarks!')
+ nvim_command('wshada')
+
+ reset()
+ nvim_command('edit ' .. testfilename)
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `a'))
+ eq('Vim(normal):E20: Mark not set', exc_exec('normal! `.'))
+ -- Make sure that uppercase marks aren't deleted.
+ nvim_command('normal! `A')
+ end)
end)
diff --git a/test/functional/shada/merging_spec.lua b/test/functional/shada/merging_spec.lua
index da2fbbe029..dad7aa851d 100644
--- a/test/functional/shada/merging_spec.lua
+++ b/test/functional/shada/merging_spec.lua
@@ -1115,5 +1115,3 @@ describe('ShaDa changes support code', function()
eq(found, 100)
end)
end)
-
--- vim: ts=2 sw=2
diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua
index 88a99d9b55..3a3238eb34 100644
--- a/test/functional/shada/shada_spec.lua
+++ b/test/functional/shada/shada_spec.lua
@@ -8,7 +8,7 @@ local write_file, spawn, set_session, nvim_prog, exc_exec =
local is_os = helpers.is_os
local skip = helpers.skip
-local lfs = require('lfs')
+local luv = require('luv')
local paths = require('test.cmakeconfig.paths')
local mpack = require('mpack')
@@ -29,7 +29,7 @@ describe('ShaDa support code', function()
after_each(function()
clear()
clean()
- lfs.rmdir(dirname)
+ luv.fs_rmdir(dirname)
end)
it('preserves `s` item size limit with unknown entries', function()
@@ -81,7 +81,7 @@ describe('ShaDa support code', function()
wshada('Some text file')
eq(0, exc_exec('wshada! ' .. shada_fname))
eq(1, read_shada_file(shada_fname)[1].type)
- eq(nil, lfs.attributes(shada_fname .. '.tmp.a'))
+ eq(nil, luv.fs_stat(shada_fname .. '.tmp.a'))
end)
it('leaves .tmp.b in-place when there is error in original ShaDa and it has .tmp.a', function()
@@ -126,34 +126,16 @@ describe('ShaDa support code', function()
wshada(s .. table.concat(msgpack, e .. s) .. e)
eq(0, exc_exec('wshada ' .. shada_fname))
local found = 0
- local typ = mpack.unpack(s)
+ local typ = mpack.decode(s)
for _, v in ipairs(read_shada_file(shada_fname)) do
if v.type == typ then
found = found + 1
- eq(mpack.unpack(msgpack[found]), v.timestamp)
+ eq(mpack.decode(msgpack[found]), v.timestamp)
end
end
eq(#msgpack, found)
end)
- it('does not write NONE file', function()
- local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
- '--headless', '--cmd', 'qall'}, true)
- session:close()
- eq(nil, lfs.attributes('NONE'))
- eq(nil, lfs.attributes('NONE.tmp.a'))
- end)
-
- it('does not read NONE file', function()
- write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
- local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
- '--headless'}, true)
- set_session(session)
- eq('', funcs.getreg('a'))
- session:close()
- os.remove('NONE')
- end)
-
local marklike = {[7]=true, [8]=true, [10]=true, [11]=true}
local find_file = function(fname)
local found = {}
@@ -214,30 +196,30 @@ describe('ShaDa support code', function()
end)
it('is able to set &shada after &viminfo', function()
- meths.set_option('viminfo', '\'10')
- eq('\'10', meths.get_option('viminfo'))
- eq('\'10', meths.get_option('shada'))
- meths.set_option('shada', '')
- eq('', meths.get_option('viminfo'))
- eq('', meths.get_option('shada'))
+ meths.set_option_value('viminfo', '\'10', {})
+ eq('\'10', meths.get_option_value('viminfo', {}))
+ eq('\'10', meths.get_option_value('shada', {}))
+ meths.set_option_value('shada', '', {})
+ eq('', meths.get_option_value('viminfo', {}))
+ eq('', meths.get_option_value('shada', {}))
end)
it('is able to set all& after setting &shada', function()
- meths.set_option('shada', '\'10')
- eq('\'10', meths.get_option('viminfo'))
- eq('\'10', meths.get_option('shada'))
+ meths.set_option_value('shada', '\'10', {})
+ eq('\'10', meths.get_option_value('viminfo', {}))
+ eq('\'10', meths.get_option_value('shada', {}))
nvim_command('set all&')
- eq('!,\'100,<50,s10,h', meths.get_option('viminfo'))
- eq('!,\'100,<50,s10,h', meths.get_option('shada'))
+ eq('!,\'100,<50,s10,h', meths.get_option_value('viminfo', {}))
+ eq('!,\'100,<50,s10,h', meths.get_option_value('shada', {}))
end)
it('is able to set &shada after &viminfo using :set', function()
nvim_command('set viminfo=\'10')
- eq('\'10', meths.get_option('viminfo'))
- eq('\'10', meths.get_option('shada'))
+ eq('\'10', meths.get_option_value('viminfo', {}))
+ eq('\'10', meths.get_option_value('shada', {}))
nvim_command('set shada=')
- eq('', meths.get_option('viminfo'))
- eq('', meths.get_option('shada'))
+ eq('', meths.get_option_value('viminfo', {}))
+ eq('', meths.get_option_value('shada', {}))
end)
it('setting &shada gives proper error message on missing number', function()
@@ -255,11 +237,31 @@ describe('ShaDa support code', function()
funcs.mkdir(dirname, '', 0)
eq(0, funcs.filewritable(dirname))
reset{shadafile=dirshada, args={'--cmd', 'set shada='}}
- meths.set_option('shada', '\'10')
+ meths.set_option_value('shada', '\'10', {})
eq('Vim(wshada):E886: System error while opening ShaDa file '
.. 'Xtest-functional-shada-shada.d/main.shada for reading to merge '
.. 'before writing it: permission denied',
exc_exec('wshada'))
- meths.set_option('shada', '')
+ meths.set_option_value('shada', '', {})
+ end)
+end)
+
+describe('ShaDa support code', function()
+ it('does not write NONE file', function()
+ local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
+ '--headless', '--cmd', 'qall'}, true)
+ session:close()
+ eq(nil, luv.fs_stat('NONE'))
+ eq(nil, luv.fs_stat('NONE.tmp.a'))
+ end)
+
+ it('does not read NONE file', function()
+ write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-')
+ local session = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed',
+ '--headless'}, true)
+ set_session(session)
+ eq('', funcs.getreg('a'))
+ session:close()
+ os.remove('NONE')
end)
end)
diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua
index 724791343d..93641fc576 100644
--- a/test/functional/terminal/api_spec.lua
+++ b/test/functional/terminal/api_spec.lua
@@ -66,10 +66,10 @@ describe('api', function()
screen:expect([[
[tui] insert-mode |
- [socket 1] this is more t{4: }|
- han 25 columns {4: }|
- [socket 2] input{1: } {4: }|
- {4:~ }|
+ [socket 1] this is more t |
+ han 25 columns |
+ [socket 2] input{1: } |
+ {4:~ } |
{3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua
index 9c8b983ff7..6fcd029a5b 100644
--- a/test/functional/terminal/buffer_spec.lua
+++ b/test/functional/terminal/buffer_spec.lua
@@ -17,14 +17,14 @@ local sleep = helpers.sleep
local funcs = helpers.funcs
local is_os = helpers.is_os
local skip = helpers.skip
+local nvim_prog = helpers.nvim_prog
describe(':terminal buffer', function()
local screen
before_each(function()
clear()
- feed_command('set modifiable swapfile undolevels=20')
- poke_eventloop()
+ command('set modifiable swapfile undolevels=20')
screen = thelpers.screen_setup()
end)
@@ -197,12 +197,11 @@ describe(':terminal buffer', function()
it('handles loss of focus gracefully', function()
-- Change the statusline to avoid printing the file name, which varies.
- nvim('set_option', 'statusline', '==========')
- feed_command('set laststatus=0')
+ nvim('set_option_value', 'statusline', '==========', {})
-- Save the buffer number of the terminal for later testing.
local tbuf = eval('bufnr("%")')
- local exitcmd = helpers.is_os('win')
+ local exitcmd = is_os('win')
and "['cmd', '/c', 'exit']"
or "['sh', '-c', 'exit']"
source([[
@@ -231,8 +230,6 @@ describe(':terminal buffer', function()
neq(tbuf, eval('bufnr("%")'))
feed_command('quit!') -- Should exit the new window, not the terminal.
eq(tbuf, eval('bufnr("%")'))
-
- feed_command('set laststatus=1') -- Restore laststatus to the default.
end)
it('term_close() use-after-free #4393', function()
@@ -281,6 +278,7 @@ describe(':terminal buffer', function()
end)
it('requires bang (!) to close a running job #15402', function()
+ skip(is_os('win'), "Test freezes the CI and makes it time out")
eq('Vim(wqall):E948: Job still running', exc_exec('wqall'))
for _, cmd in ipairs({ 'bdelete', '%bdelete', 'bwipeout', 'bunload' }) do
matches('^Vim%('..cmd:gsub('%%', '')..'%):E89: term://.*tty%-test.* will be killed %(add %! to override%)$',
@@ -412,14 +410,6 @@ describe('on_lines does not emit out-of-bounds line indexes when', function()
feed_command('bdelete!')
eq('', exec_lua([[return _G.cb_error]]))
end)
-
- it('runs TextChangedT event', function()
- meths.set_var('called', 0)
- command('autocmd TextChangedT * ++once let g:called = 1')
- feed_command('terminal')
- feed('iaa')
- eq(1, meths.get_var('called'))
- end)
end)
it('terminal truncates number of composing characters to 5', function()
@@ -429,3 +419,141 @@ it('terminal truncates number of composing characters to 5', function()
meths.chan_send(chan, 'a' .. composing:rep(8))
retry(nil, nil, function() eq('a' .. composing:rep(5), meths.get_current_line()) end)
end)
+
+describe('terminal input', function()
+ before_each(function()
+ clear()
+ exec_lua([[
+ _G.input_data = ''
+ vim.api.nvim_open_term(0, { on_input = function(_, _, _, data)
+ _G.input_data = _G.input_data .. data
+ end })
+ ]])
+ feed('i')
+ poke_eventloop()
+ end)
+
+ it('<C-Space> is sent as NUL byte', function()
+ feed('aaa<C-Space>bbb')
+ eq('aaa\0bbb', exec_lua([[return _G.input_data]]))
+ end)
+
+ it('unknown special keys are not sent', function()
+ feed('aaa<Help>bbb')
+ eq('aaabbb', exec_lua([[return _G.input_data]]))
+ end)
+end)
+
+describe('terminal input', function()
+ it('sends various special keys with modifiers', function()
+ clear()
+ local screen = thelpers.screen_setup(0,
+ string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--cmd", "startinsert"]]=], nvim_prog))
+ screen:expect{grid=[[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,1 All}|
+ {3:-- INSERT --} |
+ {3:-- TERMINAL --} |
+ ]]}
+ for _, key in ipairs({
+ '<M-Tab>', '<M-CR>', '<M-Esc>',
+ '<BS>', '<S-Tab>', '<Insert>', '<Del>', '<PageUp>', '<PageDown>',
+ '<S-Up>', '<C-Up>', '<Up>', '<S-Down>', '<C-Down>', '<Down>',
+ '<S-Left>', '<C-Left>', '<Left>', '<S-Right>', '<C-Right>', '<Right>',
+ '<S-Home>', '<C-Home>', '<Home>', '<S-End>', '<C-End>', '<End>',
+ '<C-LeftMouse>', '<C-LeftRelease>', '<2-LeftMouse>', '<2-LeftRelease>',
+ '<S-RightMouse>', '<S-RightRelease>', '<2-RightMouse>', '<2-RightRelease>',
+ '<M-MiddleMouse>', '<M-MiddleRelease>', '<2-MiddleMouse>', '<2-MiddleRelease>',
+ '<S-ScrollWheelUp>', '<S-ScrollWheelDown>', '<ScrollWheelUp>', '<ScrollWheelDown>',
+ '<S-ScrollWheelLeft>', '<S-ScrollWheelRight>', '<ScrollWheelLeft>', '<ScrollWheelRight>',
+ }) do
+ feed('<CR><C-V>' .. key)
+ retry(nil, nil, function() eq(key, meths.get_current_line()) end)
+ end
+ end)
+end)
+
+if is_os('win') then
+ describe(':terminal in Windows', function()
+ local screen
+
+ before_each(function()
+ clear()
+ feed_command('set modifiable swapfile undolevels=20')
+ poke_eventloop()
+ local cmd = '["cmd.exe","/K","PROMPT=$g$s"]'
+ screen = thelpers.screen_setup(nil, cmd)
+ end)
+
+ it('"put" operator sends data normally', function()
+ feed('<c-\\><c-n>G')
+ feed_command('let @a = ":: tty ready"')
+ feed_command('let @a = @a . "\\n:: appended " . @a . "\\n\\n"')
+ feed('"ap"ap')
+ screen:expect([[
+ |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ ^> {2: } |
+ :let @a = @a . "\n:: appended " . @a . "\n\n" |
+ ]])
+ -- operator count is also taken into consideration
+ feed('3"ap')
+ screen:expect([[
+ > :: appended :: tty ready |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ ^> {2: } |
+ :let @a = @a . "\n:: appended " . @a . "\n\n" |
+ ]])
+ end)
+
+ it('":put" command sends data normally', function()
+ feed('<c-\\><c-n>G')
+ feed_command('let @a = ":: tty ready"')
+ feed_command('let @a = @a . "\\n:: appended " . @a . "\\n\\n"')
+ feed_command('put a')
+ screen:expect([[
+ |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ > {2: } |
+ |
+ ^ |
+ :put a |
+ ]])
+ -- line argument is only used to move the cursor
+ feed_command('6put a')
+ screen:expect([[
+ |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ > :: tty ready |
+ > :: appended :: tty ready |
+ ^> {2: } |
+ :6put a |
+ ]])
+ end)
+ end)
+end
+
+describe('termopen()', function()
+ before_each(clear)
+
+ it('disallowed when textlocked and in cmdwin buffer', function()
+ command("autocmd TextYankPost <buffer> ++once call termopen('foo')")
+ matches("Vim%(call%):E565: Not allowed to change text or change window$",
+ pcall_err(command, "normal! yy"))
+
+ feed("q:")
+ eq("Vim:E11: Invalid in command-line window; <CR> executes, CTRL-C quits",
+ pcall_err(funcs.termopen, "bar"))
+ end)
+end)
diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua
index 2ca7cdb0a2..8510df5347 100644
--- a/test/functional/terminal/channel_spec.lua
+++ b/test/functional/terminal/channel_spec.lua
@@ -8,6 +8,10 @@ local pcall_err = helpers.pcall_err
local feed = helpers.feed
local poke_eventloop = helpers.poke_eventloop
local is_os = helpers.is_os
+local meths = helpers.meths
+local async_meths = helpers.async_meths
+local testprg = helpers.testprg
+local assert_alive = helpers.assert_alive
describe('terminal channel is closed and later released if', function()
local screen
@@ -24,6 +28,7 @@ describe('terminal channel is closed and later released if', function()
-- channel hasn't been released yet
eq("Vim(call):Can't send data to closed stream",
pcall_err(command, [[bdelete! | call chansend(id, 'test')]]))
+ feed('<Ignore>') -- add input to separate two RPC requests
-- channel has been released after one main loop iteration
eq(chans - 1, eval('len(nvim_list_chans())'))
end)
@@ -40,6 +45,7 @@ describe('terminal channel is closed and later released if', function()
feed('i<CR>')
-- need to first process input
poke_eventloop()
+ feed('<Ignore>') -- add input to separate two RPC requests
-- channel has been released after another main loop iteration
eq(chans - 1, eval('len(nvim_list_chans())'))
end)
@@ -55,6 +61,7 @@ describe('terminal channel is closed and later released if', function()
-- channel still hasn't been released yet
eq("Vim(call):Can't send data to closed stream",
pcall_err(command, [[bdelete | call chansend(id, 'test')]]))
+ feed('<Ignore>') -- add input to separate two RPC requests
-- channel has been released after one main loop iteration
eq(chans - 1, eval('len(nvim_list_chans())'))
end)
@@ -72,6 +79,7 @@ describe('terminal channel is closed and later released if', function()
feed('i<CR>')
-- need to first process input
poke_eventloop()
+ feed('<Ignore>') -- add input to separate two RPC requests
-- channel has been released after another main loop iteration
eq(chans - 1, eval('len(nvim_list_chans())'))
end)
@@ -89,21 +97,105 @@ describe('terminal channel is closed and later released if', function()
-- channel still hasn't been released yet
eq("Vim(call):Can't send data to closed stream",
pcall_err(command, [[bdelete | call chansend(id, 'test')]]))
+ feed('<Ignore>') -- add input to separate two RPC requests
-- channel has been released after one main loop iteration
eq(chans - 1, eval('len(nvim_list_chans())'))
end)
end)
it('chansend sends lines to terminal channel in proper order', function()
- clear()
+ clear({args = {'--cmd', 'set laststatus=2'}})
local screen = Screen.new(100, 20)
screen:attach()
local shells = is_os('win') and {'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop'} or {'sh'}
for _, sh in ipairs(shells) do
- command([[bdelete! | let id = termopen(']] .. sh .. [[')]])
+ command([[let id = termopen(']] .. sh .. [[')]])
command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]])
screen:expect{
any=[[echo "hello".*echo "world"]]
}
+ command('bdelete!')
+ screen:expect{
+ any='%[No Name%]'
+ }
end
end)
+
+describe('no crash when TermOpen autocommand', function()
+ local screen
+
+ before_each(function()
+ clear()
+ meths.set_option_value('shell', testprg('shell-test'), {})
+ command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=')
+ screen = Screen.new(60, 4)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue};
+ })
+ screen:attach()
+ end)
+
+ it('processes job exit event when using termopen()', function()
+ command([[autocmd TermOpen * call input('')]])
+ async_meths.command('terminal foobar')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ ^ |
+ ]]}
+ feed('<CR>')
+ screen:expect{grid=[[
+ ^ready $ foobar |
+ |
+ [Process exited 0] |
+ |
+ ]]}
+ feed('i<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ assert_alive()
+ end)
+
+ it('wipes buffer and processes events when using termopen()', function()
+ command([[autocmd TermOpen * bwipe! | call input('')]])
+ async_meths.command('terminal foobar')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ ^ |
+ ]]}
+ feed('<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ assert_alive()
+ end)
+
+ it('wipes buffer and processes events when using nvim_open_term()', function()
+ command([[autocmd TermOpen * bwipe! | call input('')]])
+ async_meths.open_term(0, {})
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ ^ |
+ ]]}
+ feed('<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ assert_alive()
+ end)
+end)
diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua
index 98ac03211a..8285bcc26e 100644
--- a/test/functional/terminal/cursor_spec.lua
+++ b/test/functional/terminal/cursor_spec.lua
@@ -6,7 +6,7 @@ local testprg, command = helpers.testprg, helpers.command
local nvim_prog = helpers.nvim_prog
local eq, eval = helpers.eq, helpers.eval
local matches = helpers.matches
-local feed_command = helpers.feed_command
+local poke_eventloop = helpers.poke_eventloop
local hide_cursor = thelpers.hide_cursor
local show_cursor = thelpers.show_cursor
local is_os = helpers.is_os
@@ -153,7 +153,8 @@ describe('cursor with customized highlighting', function()
})
screen:attach({rgb=false})
command('call termopen(["'..testprg('tty-test')..'"])')
- feed_command('startinsert')
+ feed('i')
+ poke_eventloop()
end)
it('overrides the default highlighting', function()
@@ -187,13 +188,16 @@ describe('buffer cursor position is correct in terminal without number column',
..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]]
-- <Left> and <Right> don't always work
..[["--cmd", "cnoremap <C-X> <Left>", "--cmd", "cnoremap <C-O> <Right>"]]..']', 70)
+ -- Also check for real cursor position, as it is used for stuff like input methods
+ screen._handle_busy_start = function() end
+ screen._handle_busy_stop = function() end
screen:expect([[
|
|
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :{1: } |
+ :{1:^ } |
{3:-- TERMINAL --} |
]])
end
@@ -213,7 +217,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :aaaaaaaa{1: } |
+ :aaaaaaaa{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 9}, eval('nvim_win_get_cursor(0)'))
@@ -238,7 +242,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :aaaaaa{1:a}a |
+ :aaaaaa{1:^a}a |
{3:-- TERMINAL --} |
]])
eq({6, 7}, eval('nvim_win_get_cursor(0)'))
@@ -263,7 +267,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :a{1:a}aaaaaa |
+ :a{1:^a}aaaaaa |
{3:-- TERMINAL --} |
]])
eq({6, 2}, eval('nvim_win_get_cursor(0)'))
@@ -294,7 +298,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µµµµµµµµ{1: } |
+ :µµµµµµµµ{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 17}, eval('nvim_win_get_cursor(0)'))
@@ -319,7 +323,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µµµµµµ{1:µ}µ |
+ :µµµµµµ{1:^µ}µ |
{3:-- TERMINAL --} |
]])
eq({6, 13}, eval('nvim_win_get_cursor(0)'))
@@ -344,7 +348,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µ{1:µ}µµµµµµ |
+ :µ{1:^µ}µµµµµµ |
{3:-- TERMINAL --} |
]])
eq({6, 3}, eval('nvim_win_get_cursor(0)'))
@@ -377,7 +381,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1: } |
+ :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 33}, eval('nvim_win_get_cursor(0)'))
@@ -402,7 +406,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µ̳µ̳µ̳µ̳µ̳µ̳{1:µ̳}µ̳ |
+ :µ̳µ̳µ̳µ̳µ̳µ̳{1:^µ̳}µ̳ |
{3:-- TERMINAL --} |
]])
eq({6, 25}, eval('nvim_win_get_cursor(0)'))
@@ -427,7 +431,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :µ̳{1:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ |
+ :µ̳{1:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ |
{3:-- TERMINAL --} |
]])
eq({6, 5}, eval('nvim_win_get_cursor(0)'))
@@ -446,7 +450,7 @@ describe('buffer cursor position is correct in terminal without number column',
end)
describe('in a line with double-cell multibyte characters and no trailing spaces,', function()
- skip(is_os('win'), "Encoding problem?")
+ if skip(is_os('win'), "Encoding problem?") then return end
before_each(function()
setup_ex_register('哦哦哦哦哦哦哦哦')
@@ -460,7 +464,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :哦哦哦哦哦哦哦哦{1: } |
+ :哦哦哦哦哦哦哦哦{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 25}, eval('nvim_win_get_cursor(0)'))
@@ -485,7 +489,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :哦哦哦哦哦哦{1:哦}哦 |
+ :哦哦哦哦哦哦{1:^哦}哦 |
{3:-- TERMINAL --} |
]])
eq({6, 19}, eval('nvim_win_get_cursor(0)'))
@@ -510,7 +514,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :哦{1:哦}哦哦哦哦哦哦 |
+ :哦{1:^哦}哦哦哦哦哦哦 |
{3:-- TERMINAL --} |
]])
eq({6, 4}, eval('nvim_win_get_cursor(0)'))
@@ -537,7 +541,7 @@ describe('buffer cursor position is correct in terminal without number column',
|
|
Entering Ex mode. Type "visual" to go to Normal mode. |
- :aaaaaaaa {1: } |
+ :aaaaaaaa {1:^ } |
{3:-- TERMINAL --} |
]])
matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
@@ -564,13 +568,16 @@ describe('buffer cursor position is correct in terminal with number column', fun
..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]]
-- <Left> and <Right> don't always work
..[["--cmd", "cnoremap <C-X> <Left>", "--cmd", "cnoremap <C-O> <Right>"]]..']', 70)
+ -- Also check for real cursor position, as it is used for stuff like input methods
+ screen._handle_busy_start = function() end
+ screen._handle_busy_stop = function() end
screen:expect([[
{7: 1 } |
{7: 2 } |
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:{1: } |
+ {7: 6 }:{1:^ } |
{3:-- TERMINAL --} |
]])
end
@@ -593,7 +600,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:aaaaaaaa{1: } |
+ {7: 6 }:aaaaaaaa{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 9}, eval('nvim_win_get_cursor(0)'))
@@ -618,7 +625,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:aaaaaa{1:a}a |
+ {7: 6 }:aaaaaa{1:^a}a |
{3:-- TERMINAL --} |
]])
eq({6, 7}, eval('nvim_win_get_cursor(0)'))
@@ -643,7 +650,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:a{1:a}aaaaaa |
+ {7: 6 }:a{1:^a}aaaaaa |
{3:-- TERMINAL --} |
]])
eq({6, 2}, eval('nvim_win_get_cursor(0)'))
@@ -674,7 +681,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µµµµµµµµ{1: } |
+ {7: 6 }:µµµµµµµµ{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 17}, eval('nvim_win_get_cursor(0)'))
@@ -699,7 +706,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µµµµµµ{1:µ}µ |
+ {7: 6 }:µµµµµµ{1:^µ}µ |
{3:-- TERMINAL --} |
]])
eq({6, 13}, eval('nvim_win_get_cursor(0)'))
@@ -724,7 +731,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µ{1:µ}µµµµµµ |
+ {7: 6 }:µ{1:^µ}µµµµµµ |
{3:-- TERMINAL --} |
]])
eq({6, 3}, eval('nvim_win_get_cursor(0)'))
@@ -757,7 +764,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1: } |
+ {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 33}, eval('nvim_win_get_cursor(0)'))
@@ -782,7 +789,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳{1:µ̳}µ̳ |
+ {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳{1:^µ̳}µ̳ |
{3:-- TERMINAL --} |
]])
eq({6, 25}, eval('nvim_win_get_cursor(0)'))
@@ -807,7 +814,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:µ̳{1:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ |
+ {7: 6 }:µ̳{1:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ |
{3:-- TERMINAL --} |
]])
eq({6, 5}, eval('nvim_win_get_cursor(0)'))
@@ -826,7 +833,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
end)
describe('in a line with double-cell multibyte characters and no trailing spaces,', function()
- skip(is_os('win'), "Encoding problem?")
+ if skip(is_os('win'), "Encoding problem?") then return end
before_each(function()
setup_ex_register('哦哦哦哦哦哦哦哦')
@@ -840,7 +847,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:哦哦哦哦哦哦哦哦{1: } |
+ {7: 6 }:哦哦哦哦哦哦哦哦{1:^ } |
{3:-- TERMINAL --} |
]])
eq({6, 25}, eval('nvim_win_get_cursor(0)'))
@@ -865,7 +872,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:哦哦哦哦哦哦{1:哦}哦 |
+ {7: 6 }:哦哦哦哦哦哦{1:^哦}哦 |
{3:-- TERMINAL --} |
]])
eq({6, 19}, eval('nvim_win_get_cursor(0)'))
@@ -890,7 +897,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:哦{1:哦}哦哦哦哦哦哦 |
+ {7: 6 }:哦{1:^哦}哦哦哦哦哦哦 |
{3:-- TERMINAL --} |
]])
eq({6, 4}, eval('nvim_win_get_cursor(0)'))
@@ -917,7 +924,7 @@ describe('buffer cursor position is correct in terminal with number column', fun
{7: 3 } |
{7: 4 } |
{7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. |
- {7: 6 }:aaaaaaaa {1: } |
+ {7: 6 }:aaaaaaaa {1:^ } |
{3:-- TERMINAL --} |
]])
matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()'))
diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua
index 80287bb3d0..29361bc0fa 100644
--- a/test/functional/terminal/edit_spec.lua
+++ b/test/functional/terminal/edit_spec.lua
@@ -21,8 +21,8 @@ describe(':edit term://*', function()
before_each(function()
clear()
- meths.set_option('shell', testprg('shell-test'))
- meths.set_option('shellcmdflag', 'EXE')
+ meths.set_option_value('shell', testprg('shell-test'), {})
+ meths.set_option_value('shellcmdflag', 'EXE', {})
end)
it('runs TermOpen event', function()
@@ -36,11 +36,10 @@ describe(':edit term://*', function()
end)
it("runs TermOpen early enough to set buffer-local 'scrollback'", function()
- if helpers.skip(helpers.is_os('win')) then return end
local columns, lines = 20, 4
local scr = get_screen(columns, lines)
local rep = 97
- meths.set_option('shellcmdflag', 'REP ' .. rep)
+ meths.set_option_value('shellcmdflag', 'REP ' .. rep, {})
command('set shellxquote=') -- win: avoid extra quotes
local sb = 10
command('autocmd TermOpen * :setlocal scrollback='..tostring(sb)
diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua
index 6b7e93a864..f628e261a2 100644
--- a/test/functional/terminal/ex_terminal_spec.lua
+++ b/test/functional/terminal/ex_terminal_spec.lua
@@ -99,7 +99,7 @@ describe(':terminal', function()
end)
it('nvim_get_mode() in :terminal', function()
- command(':terminal')
+ command('terminal')
eq({ blocking=false, mode='nt' }, nvim('get_mode'))
feed('i')
eq({ blocking=false, mode='t' }, nvim('get_mode'))
@@ -108,21 +108,37 @@ describe(':terminal', function()
end)
it(':stopinsert RPC request exits terminal-mode #7807', function()
- command(':terminal')
+ command('terminal')
feed('i[tui] insert-mode')
eq({ blocking=false, mode='t' }, nvim('get_mode'))
command('stopinsert')
+ feed('<Ignore>') -- Add input to separate two RPC requests
eq({ blocking=false, mode='nt' }, nvim('get_mode'))
end)
it(':stopinsert in normal mode doesn\'t break insert mode #9889', function()
- command(':terminal')
+ command('terminal')
eq({ blocking=false, mode='nt' }, nvim('get_mode'))
- command(':stopinsert')
+ command('stopinsert')
+ feed('<Ignore>') -- Add input to separate two RPC requests
eq({ blocking=false, mode='nt' }, nvim('get_mode'))
feed('a')
eq({ blocking=false, mode='t' }, nvim('get_mode'))
end)
+
+ it('switching to terminal buffer in Insert mode goes to Terminal mode #7164', function()
+ command('terminal')
+ command('vnew')
+ feed('i')
+ command('let g:events = []')
+ command('autocmd InsertLeave * let g:events += ["InsertLeave"]')
+ command('autocmd TermEnter * let g:events += ["TermEnter"]')
+ command('inoremap <F2> <Cmd>wincmd p<CR>')
+ eq({ blocking=false, mode='i' }, nvim('get_mode'))
+ feed('<F2>')
+ eq({ blocking=false, mode='t' }, nvim('get_mode'))
+ eq({'InsertLeave', 'TermEnter'}, eval('g:events'))
+ end)
end)
describe(':terminal (with fake shell)', function()
@@ -133,31 +149,37 @@ describe(':terminal (with fake shell)', function()
screen = Screen.new(50, 4)
screen:attach({rgb=false})
-- shell-test.c is a fake shell that prints its arguments and exits.
- nvim('set_option', 'shell', testprg('shell-test'))
- nvim('set_option', 'shellcmdflag', 'EXE')
+ nvim('set_option_value', 'shell', testprg('shell-test'), {})
+ nvim('set_option_value', 'shellcmdflag', 'EXE', {})
+ nvim('set_option_value', 'shellxquote', '', {})
end)
-- Invokes `:terminal {cmd}` using a fake shell (shell-test.c) which prints
- -- the {cmd} and exits immediately .
+ -- the {cmd} and exits immediately.
+ -- When no argument is given and the exit code is zero, the terminal buffer
+ -- closes automatically.
local function terminal_with_fake_shell(cmd)
feed_command("terminal "..(cmd and cmd or ""))
end
it('with no argument, acts like termopen()', function()
skip(is_os('win'))
- terminal_with_fake_shell()
+ -- Use the EXIT subcommand to end the process with a non-zero exit code to
+ -- prevent the buffer from closing automatically
+ nvim('set_option_value', 'shellcmdflag', 'EXIT', {})
+ terminal_with_fake_shell(1)
retry(nil, 4 * screen.timeout, function()
screen:expect([[
- ^ready $ |
- [Process exited 0] |
+ ^ |
+ [Process exited 1] |
|
- :terminal |
+ :terminal 1 |
]])
end)
end)
it("with no argument, and 'shell' is set to empty string", function()
- nvim('set_option', 'shell', '')
+ nvim('set_option_value', 'shell', '', {})
terminal_with_fake_shell()
screen:expect([[
^ |
@@ -169,7 +191,7 @@ describe(':terminal (with fake shell)', function()
it("with no argument, but 'shell' has arguments, acts like termopen()", function()
skip(is_os('win'))
- nvim('set_option', 'shell', testprg('shell-test')..' -t jeff')
+ nvim('set_option_value', 'shell', testprg('shell-test')..' -t jeff', {})
terminal_with_fake_shell()
screen:expect([[
^jeff $ |
@@ -193,7 +215,7 @@ describe(':terminal (with fake shell)', function()
it("executes a given command through the shell, when 'shell' has arguments", function()
skip(is_os('win'))
- nvim('set_option', 'shell', testprg('shell-test')..' -t jeff')
+ nvim('set_option_value', 'shell', testprg('shell-test')..' -t jeff', {})
command('set shellxquote=') -- win: avoid extra quotes
terminal_with_fake_shell('echo hi')
screen:expect([[
@@ -243,12 +265,13 @@ describe(':terminal (with fake shell)', function()
it('works with :find', function()
skip(is_os('win'))
- terminal_with_fake_shell()
+ nvim('set_option_value', 'shellcmdflag', 'EXIT', {})
+ terminal_with_fake_shell(1)
screen:expect([[
- ^ready $ |
- [Process exited 0] |
+ ^ |
+ [Process exited 1] |
|
- :terminal |
+ :terminal 1 |
]])
eq('term://', string.match(eval('bufname("%")'), "^term://"))
feed([[<C-\><C-N>]])
diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua
index 7247361649..62d3dd67a3 100644
--- a/test/functional/terminal/helpers.lua
+++ b/test/functional/terminal/helpers.lua
@@ -5,7 +5,7 @@ local helpers = require('test.functional.helpers')(nil)
local Screen = require('test.functional.ui.screen')
local testprg = helpers.testprg
local exec_lua = helpers.exec_lua
-local feed_command, nvim = helpers.feed_command, helpers.nvim
+local nvim = helpers.nvim
local function feed_data(data)
if type(data) == 'table' then
@@ -82,15 +82,16 @@ local function screen_setup(extra_rows, command, cols, opts)
screen:attach(opts or {rgb=false})
- feed_command('enew | call termopen('..command..')')
+ nvim('command', 'enew | call termopen('..command..')')
nvim('input', '<CR>')
local vim_errmsg = nvim('eval', 'v:errmsg')
if vim_errmsg and "" ~= vim_errmsg then
error(vim_errmsg)
end
- feed_command('setlocal scrollback=10')
- feed_command('startinsert')
+ nvim('command', 'setlocal scrollback=10')
+ nvim('command', 'startinsert')
+ nvim('input', '<Ignore>') -- Add input to separate two RPC requests
-- tty-test puts the terminal into raw mode and echoes input. Tests work by
-- feeding termcodes to control the display and asserting by screen:expect.
diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua
index 50c8f5e7df..3291a38e03 100644
--- a/test/functional/terminal/mouse_spec.lua
+++ b/test/functional/terminal/mouse_spec.lua
@@ -11,7 +11,7 @@ describe(':terminal mouse', function()
before_each(function()
clear()
- nvim('set_option', 'statusline', '==========')
+ nvim('set_option_value', 'statusline', '==========', {})
command('highlight StatusLine cterm=NONE')
command('highlight StatusLineNC cterm=NONE')
command('highlight VertSplit cterm=NONE')
@@ -67,9 +67,23 @@ describe(':terminal mouse', function()
eq('nt', eval('mode(1)'))
end)
- it('does not leave terminal mode on left-release', function()
- skip(is_os('win'))
- feed('<LeftRelease>')
+ it('will not exit focus on left-release', function()
+ eq('t', eval('mode(1)'))
+ feed('<LeftRelease><0,0>')
+ eq('t', eval('mode(1)'))
+ command('setlocal number')
+ eq('t', eval('mode(1)'))
+ feed('<LeftRelease><0,0>')
+ eq('t', eval('mode(1)'))
+ end)
+
+ it('will not exit focus on mouse movement', function()
+ eq('t', eval('mode(1)'))
+ feed('<MouseMove><0,0>')
+ eq('t', eval('mode(1)'))
+ command('setlocal number')
+ eq('t', eval('mode(1)'))
+ feed('<MouseMove><0,0>')
eq('t', eval('mode(1)'))
end)
@@ -232,7 +246,6 @@ describe(':terminal mouse', function()
end)
describe('with a split window and other buffer', function()
- skip(is_os('win'))
before_each(function()
feed('<c-\\><c-n>:vsp<cr>')
screen:expect([[
@@ -289,7 +302,7 @@ describe(':terminal mouse', function()
]])
end)
- it("won't lose focus if another window is scrolled", function()
+ it("scrolling another window keeps focus and respects 'mousescroll'", function()
feed('<ScrollWheelUp><4,0><ScrollWheelUp><4,0>')
screen:expect([[
{7: 21 }line │line30 |
@@ -310,22 +323,8 @@ describe(':terminal mouse', function()
========== ========== |
{3:-- TERMINAL --} |
]])
- end)
-
- it("scrolling another window respects 'mousescroll'", function()
- command('set mousescroll=ver:1')
- feed('<ScrollWheelUp><4,0>')
- screen:expect([[
- {7: 26 }line │line30 |
- {7: 27 }line │rows: 5, cols: 25 |
- {7: 28 }line │rows: 5, cols: 24 |
- {7: 29 }line │mouse enabled |
- {7: 30 }line │{1: } |
- ========== ========== |
- {3:-- TERMINAL --} |
- ]])
command('set mousescroll=ver:10')
- feed('<ScrollWheelUp><4,0>')
+ feed('<ScrollWheelUp><0,0>')
screen:expect([[
{7: 16 }line │line30 |
{7: 17 }line │rows: 5, cols: 25 |
@@ -336,8 +335,41 @@ describe(':terminal mouse', function()
{3:-- TERMINAL --} |
]])
command('set mousescroll=ver:0')
- feed('<ScrollWheelUp><4,0>')
+ feed('<ScrollWheelUp><0,0>')
screen:expect_unchanged()
+ feed([[<C-\><C-N><C-W>w]])
+ command('setlocal nowrap')
+ feed('0<C-V>gg3ly$4p<C-W>wi')
+ screen:expect([[
+ {7: 1 }linelinelinelineline │line30 |
+ {7: 2 }linelinelinelineline │rows: 5, cols: 25 |
+ {7: 3 }linelinelinelineline │rows: 5, cols: 24 |
+ {7: 4 }linelinelinelineline │mouse enabled |
+ {7: 5 }linelinelinelineline │{1: } |
+ ========== ========== |
+ {3:-- TERMINAL --} |
+ ]])
+ feed('<ScrollWheelRight><4,0>')
+ screen:expect([[
+ {7: 1 }nelinelineline │line30 |
+ {7: 2 }nelinelineline │rows: 5, cols: 25 |
+ {7: 3 }nelinelineline │rows: 5, cols: 24 |
+ {7: 4 }nelinelineline │mouse enabled |
+ {7: 5 }nelinelineline │{1: } |
+ ========== ========== |
+ {3:-- TERMINAL --} |
+ ]])
+ command('set mousescroll=hor:4')
+ feed('<ScrollWheelLeft><4,0>')
+ screen:expect([[
+ {7: 1 }nelinelinelineline │line30 |
+ {7: 2 }nelinelinelineline │rows: 5, cols: 25 |
+ {7: 3 }nelinelinelineline │rows: 5, cols: 24 |
+ {7: 4 }nelinelinelineline │mouse enabled |
+ {7: 5 }nelinelinelineline │{1: } |
+ ========== ========== |
+ {3:-- TERMINAL --} |
+ ]])
end)
it('will lose focus if another window is clicked', function()
@@ -354,7 +386,7 @@ describe(':terminal mouse', function()
end)
it('handles terminal size when switching buffers', function()
- nvim('set_option', 'hidden', true)
+ nvim('set_option_value', 'hidden', true, {})
feed('<c-\\><c-n><c-w><c-w>')
screen:expect([[
{7: 27 }line │line30 |
diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua
index a4899c8219..1e278e4cff 100644
--- a/test/functional/terminal/scrollback_spec.lua
+++ b/test/functional/terminal/scrollback_spec.lua
@@ -2,13 +2,12 @@ local Screen = require('test.functional.ui.screen')
local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers')
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
-local feed, testprg, feed_command = helpers.feed, helpers.testprg, helpers.feed_command
+local feed, testprg = helpers.feed, helpers.testprg
local eval = helpers.eval
local command = helpers.command
-local matches = helpers.matches
local poke_eventloop = helpers.poke_eventloop
local retry = helpers.retry
-local curbufmeths = helpers.curbufmeths
+local meths = helpers.meths
local nvim = helpers.nvim
local feed_data = thelpers.feed_data
local pcall_err = helpers.pcall_err
@@ -140,7 +139,6 @@ describe(':terminal scrollback', function()
describe('and height decreased by 1', function()
- if skip(is_os('win')) then return end
local function will_hide_top_line()
feed([[<C-\><C-N>]])
screen:try_resize(screen._width - 2, screen._height - 1)
@@ -347,12 +345,10 @@ end)
describe(':terminal prints more lines than the screen height and exits', function()
it('will push extra lines to scrollback', function()
- skip(is_os('win'))
clear()
local screen = Screen.new(30, 7)
screen:attach({rgb=false})
- feed_command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test')))
- poke_eventloop()
+ command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test')))
screen:expect([[
line6 |
line7 |
@@ -382,9 +378,7 @@ describe("'scrollback' option", function()
end)
local function set_fake_shell()
- -- shell-test.c is a fake shell that prints its arguments and exits.
- nvim('set_option', 'shell', testprg('shell-test'))
- nvim('set_option', 'shellcmdflag', 'EXE')
+ nvim('set_option_value', 'shell', string.format('"%s" INTERACT', testprg('shell-test')), {})
end
local function expect_lines(expected, epsilon)
@@ -403,7 +397,7 @@ describe("'scrollback' option", function()
screen = thelpers.screen_setup(nil, "['sh']", 30)
end
- curbufmeths.set_option('scrollback', 0)
+ meths.set_option_value('scrollback', 0, {})
feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), is_os('win') and '\r' or '\n'))
screen:expect{any='30: line '}
retry(nil, nil, function() expect_lines(7) end)
@@ -419,7 +413,7 @@ describe("'scrollback' option", function()
screen = thelpers.screen_setup(nil, "['sh']", 30)
end
- curbufmeths.set_option('scrollback', 200)
+ meths.set_option_value('scrollback', 200, {})
-- Wait for prompt.
screen:expect{any='%$'}
@@ -428,10 +422,10 @@ describe("'scrollback' option", function()
screen:expect{any='30: line '}
retry(nil, nil, function() expect_lines(33, 2) end)
- curbufmeths.set_option('scrollback', 10)
+ meths.set_option_value('scrollback', 10, {})
poke_eventloop()
retry(nil, nil, function() expect_lines(16) end)
- curbufmeths.set_option('scrollback', 10000)
+ meths.set_option_value('scrollback', 10000, {})
retry(nil, nil, function() expect_lines(16) end)
-- Terminal job data is received asynchronously, may happen before the
-- 'scrollback' option is synchronized with the internal sb_buffer.
@@ -462,8 +456,8 @@ describe("'scrollback' option", function()
expect_lines(58)
-- Verify off-screen state
- matches((is_os('win') and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)"))
- matches((is_os('win') and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)"))
+ eq((is_os('win') and '36: line' or '35: line'), eval("getline(line('w0') - 1)->trim(' ', 2)"))
+ eq((is_os('win') and '27: line' or '26: line'), eval("getline(line('w0') - 10)->trim(' ', 2)"))
end)
it('deletes extra lines immediately', function()
@@ -486,18 +480,18 @@ describe("'scrollback' option", function()
]])
local term_height = 6 -- Actual terminal screen height, not the scrollback
-- Initial
- local scrollback = curbufmeths.get_option('scrollback')
+ local scrollback = meths.get_option_value('scrollback', {})
eq(scrollback + term_height, eval('line("$")'))
-- Reduction
scrollback = scrollback - 2
- curbufmeths.set_option('scrollback', scrollback)
+ meths.set_option_value('scrollback', scrollback, {})
eq(scrollback + term_height, eval('line("$")'))
end)
it('defaults to 10000 in :terminal buffers', function()
set_fake_shell()
command('terminal')
- eq(10000, curbufmeths.get_option('scrollback'))
+ eq(10000, meths.get_option_value('scrollback', {}))
end)
it('error if set to invalid value', function()
@@ -509,7 +503,7 @@ describe("'scrollback' option", function()
it('defaults to -1 on normal buffers', function()
command('new')
- eq(-1, curbufmeths.get_option('scrollback'))
+ eq(-1, meths.get_option_value('scrollback', {}))
end)
it(':setlocal in a :terminal buffer', function()
@@ -518,45 +512,45 @@ describe("'scrollback' option", function()
-- _Global_ scrollback=-1 defaults :terminal to 10_000.
command('setglobal scrollback=-1')
command('terminal')
- eq(10000, curbufmeths.get_option('scrollback'))
+ eq(10000, meths.get_option_value('scrollback', {}))
-- _Local_ scrollback=-1 in :terminal forces the _maximum_.
command('setlocal scrollback=-1')
retry(nil, nil, function() -- Fixup happens on refresh, not immediately.
- eq(100000, curbufmeths.get_option('scrollback'))
+ eq(100000, meths.get_option_value('scrollback', {}))
end)
-- _Local_ scrollback=-1 during TermOpen forces the maximum. #9605
command('setglobal scrollback=-1')
command('autocmd TermOpen * setlocal scrollback=-1')
command('terminal')
- eq(100000, curbufmeths.get_option('scrollback'))
+ eq(100000, meths.get_option_value('scrollback', {}))
end)
it(':setlocal in a normal buffer', function()
command('new')
-- :setlocal to -1.
command('setlocal scrollback=-1')
- eq(-1, curbufmeths.get_option('scrollback'))
+ eq(-1, meths.get_option_value('scrollback', {}))
-- :setlocal to anything except -1. Currently, this just has no effect.
command('setlocal scrollback=42')
- eq(42, curbufmeths.get_option('scrollback'))
+ eq(42, meths.get_option_value('scrollback', {}))
end)
it(':set updates local value and global default', function()
set_fake_shell()
command('set scrollback=42') -- set global value
- eq(42, curbufmeths.get_option('scrollback'))
+ eq(42, meths.get_option_value('scrollback', {}))
command('terminal')
- eq(42, curbufmeths.get_option('scrollback')) -- inherits global default
+ eq(42, meths.get_option_value('scrollback', {})) -- inherits global default
command('setlocal scrollback=99')
- eq(99, curbufmeths.get_option('scrollback'))
+ eq(99, meths.get_option_value('scrollback', {}))
command('set scrollback<') -- reset to global default
- eq(42, curbufmeths.get_option('scrollback'))
+ eq(42, meths.get_option_value('scrollback', {}))
command('setglobal scrollback=734') -- new global default
- eq(42, curbufmeths.get_option('scrollback')) -- local value did not change
+ eq(42, meths.get_option_value('scrollback', {})) -- local value did not change
command('terminal')
- eq(734, curbufmeths.get_option('scrollback'))
+ eq(734, meths.get_option_value('scrollback', {}))
end)
end)
@@ -577,12 +571,12 @@ describe("pending scrollback line handling", function()
it("does not crash after setting 'number' #14891", function()
exec_lua [[
- local a = vim.api
- local buf = a.nvim_create_buf(true, true)
- local chan = a.nvim_open_term(buf, {})
- a.nvim_win_set_option(0, "number", true)
- a.nvim_chan_send(chan, ("a\n"):rep(11) .. "a")
- a.nvim_win_set_buf(0, buf)
+ local api = vim.api
+ local buf = api.nvim_create_buf(true, true)
+ local chan = api.nvim_open_term(buf, {})
+ vim.wo.number = true
+ api.nvim_chan_send(chan, ("a\n"):rep(11) .. "a")
+ api.nvim_win_set_buf(0, buf)
]]
screen:expect [[
{1: 1 }^a |
@@ -609,12 +603,11 @@ describe("pending scrollback line handling", function()
it("does not crash after nvim_buf_call #14891", function()
skip(is_os('win'))
exec_lua [[
- local a = vim.api
- local bufnr = a.nvim_create_buf(false, true)
- a.nvim_buf_call(bufnr, function()
+ local bufnr = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_call(bufnr, function()
vim.fn.termopen({"echo", ("hi\n"):rep(11)})
end)
- a.nvim_win_set_buf(0, bufnr)
+ vim.api.nvim_win_set_buf(0, bufnr)
vim.cmd("startinsert")
]]
screen:expect [[
diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua
index 1d9e7b8e11..b17eed00f9 100644
--- a/test/functional/terminal/tui_spec.lua
+++ b/test/functional/terminal/tui_spec.lua
@@ -8,7 +8,6 @@ local helpers = require('test.functional.helpers')(after_each)
local thelpers = require('test.functional.terminal.helpers')
local Screen = require('test.functional.ui.screen')
local eq = helpers.eq
-local feed_command = helpers.feed_command
local feed_data = thelpers.feed_data
local clear = helpers.clear
local command = helpers.command
@@ -28,10 +27,9 @@ local is_os = helpers.is_os
local new_pipename = helpers.new_pipename
local spawn_argv = helpers.spawn_argv
local set_session = helpers.set_session
-local feed = helpers.feed
-local eval = helpers.eval
+local write_file = helpers.write_file
-if helpers.skip(helpers.is_os('win')) then return end
+if helpers.skip(is_os('win')) then return end
describe('TUI', function()
local screen
@@ -69,13 +67,13 @@ describe('TUI', function()
local function expect_child_buf_lines(expected)
assert(type({}) == type(expected))
retry(nil, nil, function()
- local _, buflines = child_session:request(
- 'nvim_buf_get_lines', 0, 0, -1, false)
+ local _, buflines = child_session:request('nvim_buf_get_lines', 0, 0, -1, false)
eq(expected, buflines)
end)
end
it('rapid resize #7572 #7628', function()
+ helpers.skip(helpers.is_asan(), 'Test extra unstable with ASAN. See #23762')
-- Need buffer rows to provoke the behavior.
feed_data(":edit test/functional/fixtures/bigfile.txt\n")
screen:expect([[
@@ -109,14 +107,14 @@ describe('TUI', function()
end)
it('accepts resize while pager is active', function()
- child_session:request("nvim_command", [[
- set more
- func! ManyErr()
- for i in range(10)
- echoerr "FAIL ".i
- endfor
- endfunc
- ]])
+ child_session:request('nvim_exec2', [[
+ set more
+ func! ManyErr()
+ for i in range(20)
+ echoerr "FAIL ".i
+ endfor
+ endfunc
+ ]], {})
feed_data(':call ManyErr()\r')
screen:expect{grid=[[
{8:Error detected while processing function ManyErr:} |
@@ -128,7 +126,35 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]]}
- feed_data('d')
+ screen:try_resize(50,10)
+ screen:expect{grid=[[
+ :call ManyErr() |
+ {8:Error detected while processing function ManyErr:} |
+ {11:line 2:} |
+ {8:FAIL 0} |
+ {8:FAIL 1} |
+ {8:FAIL 2} |
+ |
+ |
+ {10:-- More --}{1: } |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ feed_data('j')
+ screen:expect{grid=[[
+ {8:Error detected while processing function ManyErr:} |
+ {11:line 2:} |
+ {8:FAIL 0} |
+ {8:FAIL 1} |
+ {8:FAIL 2} |
+ {8:FAIL 3} |
+ {8:FAIL 4} |
+ {8:FAIL 5} |
+ {10:-- More --}{1: } |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ screen:try_resize(50,7)
screen:expect{grid=[[
{8:FAIL 1} |
{8:FAIL 2} |
@@ -212,7 +238,7 @@ describe('TUI', function()
it('interprets leading <Esc> byte as ALT modifier in normal-mode', function()
local keys = 'dfghjkl'
for c in keys:gmatch('.') do
- feed_command('nnoremap <a-'..c..'> ialt-'..c..'<cr><esc>')
+ feed_data(':nnoremap <a-'..c..'> ialt-'..c..'<cr><esc>\r')
feed_data('\027'..c)
end
screen:expect([[
@@ -252,9 +278,11 @@ describe('TUI', function()
end)
it('interprets <Esc>[27u as <Esc>', function()
- feed_command('nnoremap <M-;> <Nop>')
- feed_command('nnoremap <Esc> AESC<Esc>')
- feed_command('nnoremap ; Asemicolon<Esc>')
+ child_session:request('nvim_exec2', [[
+ nnoremap <M-;> <Nop>
+ nnoremap <Esc> AESC<Esc>
+ nnoremap ; Asemicolon<Esc>
+ ]], {})
feed_data('\027[27u;')
screen:expect([[
ESCsemicolo{1:n} |
@@ -291,22 +319,22 @@ describe('TUI', function()
local attrs = screen:get_default_attr_ids()
attrs[11] = {foreground = 81}
screen:expect([[
- {11:^G^V^M}{1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] [+] }|
- {3:-- INSERT --} |
- {3:-- TERMINAL --} |
+ {11:^G^V^M}{1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ {3:-- INSERT --} |
+ {3:-- TERMINAL --} |
]], attrs)
end)
- it('accepts mouse wheel events #19992', function()
- child_session:request('nvim_command', [[
+ local function test_mouse_wheel(esc)
+ child_session:request('nvim_exec2', [[
set number nostartofline nowrap mousescroll=hor:1,ver:1
call setline(1, repeat([join(range(10), '----')], 10))
vsplit
- ]])
+ ]], {})
screen:expect([[
{11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----|
{11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----|
@@ -317,7 +345,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelDown> in active window
- feed_data('\027[<65;8;1M')
+ if esc then
+ feed_data('\027[<65;8;1M')
+ else
+ meths.input_mouse('wheel', 'down', '', 0, 0, 7)
+ end
screen:expect([[
{11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----|
{11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----|
@@ -328,7 +360,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelDown> in inactive window
- feed_data('\027[<65;48;1M')
+ if esc then
+ feed_data('\027[<65;48;1M')
+ else
+ meths.input_mouse('wheel', 'down', '', 0, 0, 47)
+ end
screen:expect([[
{11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----|
{11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----|
@@ -339,7 +375,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelRight> in active window
- feed_data('\027[<67;8;1M')
+ if esc then
+ feed_data('\027[<67;8;1M')
+ else
+ meths.input_mouse('wheel', 'right', '', 0, 0, 7)
+ end
screen:expect([[
{11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----|
{11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----|
@@ -350,7 +390,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelRight> in inactive window
- feed_data('\027[<67;48;1M')
+ if esc then
+ feed_data('\027[<67;48;1M')
+ else
+ meths.input_mouse('wheel', 'right', '', 0, 0, 47)
+ end
screen:expect([[
{11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4|
{11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4|
@@ -361,7 +405,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelDown> in active window
- feed_data('\027[<69;8;1M')
+ if esc then
+ feed_data('\027[<69;8;1M')
+ else
+ meths.input_mouse('wheel', 'down', 'S', 0, 0, 7)
+ end
screen:expect([[
{11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4|
{11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4|
@@ -372,7 +420,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelDown> in inactive window
- feed_data('\027[<69;48;1M')
+ if esc then
+ feed_data('\027[<69;48;1M')
+ else
+ meths.input_mouse('wheel', 'down', 'S', 0, 0, 47)
+ end
screen:expect([[
{11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4|
{11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4|
@@ -383,7 +435,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelRight> in active window
- feed_data('\027[<71;8;1M')
+ if esc then
+ feed_data('\027[<71;8;1M')
+ else
+ meths.input_mouse('wheel', 'right', 'S', 0, 0, 7)
+ end
screen:expect([[
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4|
{11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4|
@@ -394,7 +450,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelRight> in inactive window
- feed_data('\027[<71;48;1M')
+ if esc then
+ feed_data('\027[<71;48;1M')
+ else
+ meths.input_mouse('wheel', 'right', 'S', 0, 0, 47)
+ end
screen:expect([[
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----|
{11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----|
@@ -405,7 +465,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelUp> in active window
- feed_data('\027[<64;8;1M')
+ if esc then
+ feed_data('\027[<64;8;1M')
+ else
+ meths.input_mouse('wheel', 'up', '', 0, 0, 7)
+ end
screen:expect([[
{11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----|
{11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----|
@@ -416,7 +480,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelUp> in inactive window
- feed_data('\027[<64;48;1M')
+ if esc then
+ feed_data('\027[<64;48;1M')
+ else
+ meths.input_mouse('wheel', 'up', '', 0, 0, 47)
+ end
screen:expect([[
{11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----|
{11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----|
@@ -427,7 +495,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelLeft> in active window
- feed_data('\027[<66;8;1M')
+ if esc then
+ feed_data('\027[<66;8;1M')
+ else
+ meths.input_mouse('wheel', 'left', '', 0, 0, 7)
+ end
screen:expect([[
{11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----|
{11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----|
@@ -438,7 +510,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <ScrollWheelLeft> in inactive window
- feed_data('\027[<66;48;1M')
+ if esc then
+ feed_data('\027[<66;48;1M')
+ else
+ meths.input_mouse('wheel', 'left', '', 0, 0, 47)
+ end
screen:expect([[
{11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---|
{11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---|
@@ -449,7 +525,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelUp> in active window
- feed_data('\027[<68;8;1M')
+ if esc then
+ feed_data('\027[<68;8;1M')
+ else
+ meths.input_mouse('wheel', 'up', 'S', 0, 0, 7)
+ end
screen:expect([[
{11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---|
{11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---|
@@ -460,7 +540,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelUp> in inactive window
- feed_data('\027[<68;48;1M')
+ if esc then
+ feed_data('\027[<68;48;1M')
+ else
+ meths.input_mouse('wheel', 'up', 'S', 0, 0, 47)
+ end
screen:expect([[
{11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---|
{11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---|
@@ -471,7 +555,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelLeft> in active window
- feed_data('\027[<70;8;1M')
+ if esc then
+ feed_data('\027[<70;8;1M')
+ else
+ meths.input_mouse('wheel', 'left', 'S', 0, 0, 7)
+ end
screen:expect([[
{11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---|
{11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---|
@@ -482,7 +570,11 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
-- <S-ScrollWheelLeft> in inactive window
- feed_data('\027[<70;48;1M')
+ if esc then
+ feed_data('\027[<70;48;1M')
+ else
+ meths.input_mouse('wheel', 'left', 'S', 0, 0, 47)
+ end
screen:expect([[
{11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----|
{11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----|
@@ -492,6 +584,136 @@ describe('TUI', function()
|
{3:-- TERMINAL --} |
]])
+ end
+
+ describe('accepts mouse wheel events', function()
+ it('(mouse events sent to host)', function()
+ test_mouse_wheel(false)
+ end)
+
+ it('(escape sequences sent to child)', function()
+ test_mouse_wheel(true)
+ end)
+ end)
+
+ local function test_mouse_popup(esc)
+ child_session:request('nvim_exec2', [[
+ call setline(1, 'popup menu test')
+ set mouse=a mousemodel=popup
+
+ aunmenu PopUp
+ menu PopUp.foo :let g:menustr = 'foo'<CR>
+ menu PopUp.bar :let g:menustr = 'bar'<CR>
+ menu PopUp.baz :let g:menustr = 'baz'<CR>
+ highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse
+ highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold
+ ]], {})
+ if esc then
+ feed_data('\027[<2;5;1M')
+ else
+ meths.input_mouse('right', 'press', '', 0, 0, 4)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{13: foo }{4: }|
+ {4:~ }{13: bar }{4: }|
+ {4:~ }{13: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
+ feed_data('\027[<2;5;1m')
+ else
+ meths.input_mouse('right', 'release', '', 0, 0, 4)
+ end
+ screen:expect_unchanged()
+ if esc then
+ feed_data('\027[<35;7;4M')
+ else
+ meths.input_mouse('move', '', '', 0, 3, 6)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }{13: foo }{4: }|
+ {4:~ }{13: bar }{4: }|
+ {4:~ }{14: baz }{4: }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
+ feed_data('\027[<0;7;3M')
+ else
+ meths.input_mouse('left', 'press', '', 0, 2, 6)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ :let g:menustr = 'bar' |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
+ feed_data('\027[<0;7;3m')
+ else
+ meths.input_mouse('left', 'release', '', 0, 2, 6)
+ end
+ screen:expect_unchanged()
+ if esc then
+ feed_data('\027[<2;45;3M')
+ else
+ meths.input_mouse('right', 'press', '', 0, 2, 44)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }{13: foo }{4: }|
+ {5:[No Name] [+] }{13: bar }{5: }|
+ :let g:menustr = 'bar' {13: baz } |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
+ feed_data('\027[<34;48;6M')
+ else
+ meths.input_mouse('right', 'drag', '', 0, 5, 47)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }{13: foo }{4: }|
+ {5:[No Name] [+] }{13: bar }{5: }|
+ :let g:menustr = 'bar' {14: baz } |
+ {3:-- TERMINAL --} |
+ ]])
+ if esc then
+ feed_data('\027[<2;48;6m')
+ else
+ meths.input_mouse('right', 'release', '', 0, 5, 47)
+ end
+ screen:expect([[
+ {1:p}opup menu test |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ :let g:menustr = 'baz' |
+ {3:-- TERMINAL --} |
+ ]])
+ end
+
+ describe('mouse events work with right-click menu', function()
+ it('(mouse events sent to host)', function()
+ test_mouse_popup(false)
+ end)
+
+ it('(escape sequences sent to child)', function()
+ test_mouse_popup(true)
+ end)
end)
it('accepts keypad keys from kitty keyboard protocol #19180', function()
@@ -632,11 +854,11 @@ describe('TUI', function()
|
{3:-- TERMINAL --} |
]])
- child_session:request('nvim_command', [[
+ child_session:request('nvim_exec2', [[
tab split
tabnew
highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline
- ]])
+ ]], {})
screen:expect([[
{12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}|
{1: } |
@@ -668,52 +890,26 @@ describe('TUI', function()
]])
end)
- it('mouse events work with right-click menu', function()
- child_session:request('nvim_command', [[
- call setline(1, 'popup menu test')
- set mouse=a mousemodel=popup
-
- aunmenu PopUp
- menu PopUp.foo :let g:menustr = 'foo'<CR>
- menu PopUp.bar :let g:menustr = 'bar'<CR>
- menu PopUp.baz :let g:menustr = 'baz'<CR>
- highlight Pmenu ctermbg=NONE ctermfg=NONE cterm=underline,reverse
- highlight PmenuSel ctermbg=NONE ctermfg=NONE cterm=underline,reverse,bold
- ]])
- meths.input_mouse('right', 'press', '', 0, 0, 4)
- screen:expect([[
- {1:p}opup menu test |
- {4:~ }{13: foo }{4: }|
- {4:~ }{13: bar }{4: }|
- {4:~ }{13: baz }{4: }|
- {5:[No Name] [+] }|
- |
- {3:-- TERMINAL --} |
- ]])
- meths.input_mouse('right', 'release', '', 0, 0, 4)
- screen:expect_unchanged()
- meths.input_mouse('move', '', '', 0, 3, 6)
- screen:expect([[
- {1:p}opup menu test |
- {4:~ }{13: foo }{4: }|
- {4:~ }{13: bar }{4: }|
- {4:~ }{14: baz }{4: }|
- {5:[No Name] [+] }|
- |
- {3:-- TERMINAL --} |
- ]])
- meths.input_mouse('left', 'press', '', 0, 2, 6)
+ it('supports Super and Meta modifiers', function()
+ feed_data('i')
+ feed_data('\022\027[106;9u') -- Super + j
+ feed_data('\022\027[107;33u') -- Meta + k
+ feed_data('\022\027[13;41u') -- Super + Meta + Enter
+ feed_data('\022\027[127;48u') -- Shift + Alt + Ctrl + Super + Meta + Backspace
+ feed_data('\n')
+ feed_data('\022\027[57376;9u') -- Super + F13
+ feed_data('\022\027[57377;33u') -- Meta + F14
+ feed_data('\022\027[57378;41u') -- Super + Meta + F15
+ feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16
screen:expect([[
- {1:p}opup menu test |
- {4:~ }|
+ <D-j><T-k><T-D-CR><M-T-C-S-D-BS> |
+ <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>{1: } |
{4:~ }|
{4:~ }|
{5:[No Name] [+] }|
- :let g:menustr = 'bar' |
+ {3:-- INSERT --} |
{3:-- TERMINAL --} |
]])
- meths.input_mouse('left', 'release', '', 0, 2, 6)
- screen:expect_unchanged()
end)
it('paste: Insert mode', function()
@@ -753,8 +949,7 @@ describe('TUI', function()
]])
-- Dot-repeat/redo.
feed_data('2.')
- expect_child_buf_lines(
- {'"pasted from terminapasted from terminalpasted from terminall"'})
+ expect_child_buf_lines({'"pasted from terminapasted from terminalpasted from terminall"'})
screen:expect([[
"pasted from terminapasted from terminalpasted fro|
m termina{1:l}l" |
@@ -807,13 +1002,13 @@ describe('TUI', function()
'this is line 2',
'line 3 is here',
'',
- }
+ }
-- Redo.
feed_data('\18') -- <C-r>
expect_child_buf_lines{
'thisjust paste it™3 is here',
'',
- }
+ }
end)
it('paste: terminal mode', function()
@@ -875,17 +1070,15 @@ describe('TUI', function()
screen:expect{grid=expected_grid1, attr_ids=expected_attr}
-- Dot-repeat/redo.
feed_data('.')
- screen:expect{
- grid=[[
- ESC:{11:^[} / CR: |
- xline 1 |
- ESC:{11:^[} / CR: |
- {1:x} |
- {5:[No Name] [+] 5,1 Bot}|
- |
- {3:-- TERMINAL --} |
- ]],
- attr_ids=expected_attr}
+ screen:expect{grid=[[
+ ESC:{11:^[} / CR: |
+ xline 1 |
+ ESC:{11:^[} / CR: |
+ {1:x} |
+ {5:[No Name] [+] 5,1 Bot}|
+ |
+ {3:-- TERMINAL --} |
+ ]], attr_ids=expected_attr}
-- Undo.
feed_data('u')
expect_child_buf_lines(expected_crlf)
@@ -1172,8 +1365,7 @@ describe('TUI', function()
{5:[No Name] [+] }|
{3:-- INSERT --} |
{3:-- TERMINAL --} |
- ]],
- attr_ids={
+ ]], attr_ids={
[1] = {reverse = true},
[2] = {background = tonumber('0x00000b')},
[3] = {bold = true},
@@ -1352,7 +1544,7 @@ describe('TUI', function()
it('forwards :term palette colors with termguicolors', function()
if is_ci('github') then
- pending("tty-test complains about not owning the terminal -- actions/runner#241")
+ pending("tty-test complains about not owning the terminal -- actions/runner#241")
end
screen:set_rgb_cterm(true)
screen:set_default_attr_ids({
@@ -1398,24 +1590,41 @@ describe('TUI', function()
]]}
end)
- it('is included in nvim_list_uis()', function()
- feed_data(':echo map(nvim_list_uis(), {k,v -> sort(items(filter(v, {k,v -> k[:3] !=# "ext_" })))})\r')
- screen:expect([=[
- |
- {4:~ }|
- {5: }|
- [[['chan', 1], ['height', 6], ['override', v:false|
- ], ['rgb', v:false], ['width', 50]]] |
- {10:Press ENTER or type command to continue}{1: } |
- {3:-- TERMINAL --} |
- ]=])
+ it('in nvim_list_uis()', function()
+ -- $TERM in :terminal.
+ local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color'
+ local expected = {
+ {
+ chan = 1,
+ ext_cmdline = false,
+ ext_hlstate = false,
+ ext_linegrid = true,
+ ext_messages = false,
+ ext_multigrid = false,
+ ext_popupmenu = false,
+ ext_tabline = false,
+ ext_termcolors = true,
+ ext_wildmenu = false,
+ height = 6,
+ override = false,
+ rgb = false,
+ stdin_tty = true,
+ stdout_tty = true,
+ term_background = '',
+ term_colors = 256,
+ term_name = exp_term,
+ width = 50
+ },
+ }
+ local _, rv = child_session:request('nvim_list_uis')
+ eq(expected, rv)
end)
it('allows grid to assume wider ambiguous-width characters than host terminal #19686', function()
child_session:request('nvim_buf_set_lines', 0, 0, -1, true, { ('℃'):rep(60), ('℃'):rep(60) })
- child_session:request('nvim_win_set_option', 0, 'cursorline', true)
- child_session:request('nvim_win_set_option', 0, 'list', true)
- child_session:request('nvim_win_set_option', 0, 'listchars', 'eol:$')
+ child_session:request('nvim_set_option_value', 'cursorline', true, {})
+ child_session:request('nvim_set_option_value', 'list', true, {})
+ child_session:request('nvim_set_option_value', 'listchars', 'eol:$', {win=0})
feed_data('gg')
local singlewidth_screen = [[
{13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}|
@@ -1426,21 +1635,21 @@ describe('TUI', function()
|
{3:-- TERMINAL --} |
]]
- -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, the
- -- second cell of "℃" is a space and the attributes of the "℃" are applied to it.
+ -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width,
+ -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it.
local doublewidth_screen = [[
{13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
{12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }|
{12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }|
- ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ >{4:@@@}|
+ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {4:@@@@}|
{5:[No Name] [+] }|
|
{3:-- TERMINAL --} |
]]
screen:expect(singlewidth_screen)
- child_session:request('nvim_set_option', 'ambiwidth', 'double')
+ child_session:request('nvim_set_option_value', 'ambiwidth', 'double', {})
screen:expect(doublewidth_screen)
- child_session:request('nvim_set_option', 'ambiwidth', 'single')
+ child_session:request('nvim_set_option_value', 'ambiwidth', 'single', {})
screen:expect(singlewidth_screen)
child_session:request('nvim_call_function', 'setcellwidths', {{{0x2103, 0x2103, 2}}})
screen:expect(doublewidth_screen)
@@ -1449,22 +1658,19 @@ describe('TUI', function()
end)
it('draws correctly when cursor_address overflows #21643', function()
- helpers.skip(helpers.is_os('mac'), 'FIXME: crashes/errors on macOS')
- screen:try_resize(77, 834)
+ helpers.skip(is_os('mac'), 'FIXME: crashes/errors on macOS')
+ screen:try_resize(77, 855)
retry(nil, nil, function()
- eq({true, 831}, {child_session:request('nvim_win_get_height', 0)})
+ eq({true, 852}, {child_session:request('nvim_win_get_height', 0)})
end)
-- Use full screen message so that redrawing afterwards is more deterministic.
child_session:notify('nvim_command', 'intro')
screen:expect({any = 'Nvim'})
-- Going to top-left corner needs 3 bytes.
-- Setting underline attribute needs 9 bytes.
- -- With screen width 77, 63857 characters need 829 full screen lines.
- -- Drawing each full screen line needs 77 + 2 = 79 bytes (2 bytes for CR LF).
- -- The incomplete screen line needs 24 + 3 = 27 bytes.
- -- The whole line needs 3 + 9 + 79 * 829 + 27 = 65530 bytes.
+ -- The whole line needs 3 + 9 + 65515 + 3 = 65530 bytes.
-- The cursor_address that comes after will overflow the 65535-byte buffer.
- local line = ('a'):rep(63857) .. '℃'
+ local line = ('a'):rep(65515) .. '℃'
child_session:notify('nvim_exec_lua', [[
vim.api.nvim_buf_set_lines(0, 0, -1, true, {...})
vim.o.cursorline = true
@@ -1473,8 +1679,8 @@ describe('TUI', function()
feed_data('\n')
screen:expect(
'{13:a}{12:' .. ('a'):rep(76) .. '}|\n'
- .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(828)
- .. '{12:' .. ('a'):rep(24) .. '℃' .. (' '):rep(52) .. '}|\n' .. dedent([[
+ .. ('{12:' .. ('a'):rep(77) .. '}|\n'):rep(849)
+ .. '{12:' .. ('a'):rep(65) .. '℃' .. (' '):rep(11) .. '}|\n' .. dedent([[
b |
{5:[No Name] [+] }|
|
@@ -1509,16 +1715,105 @@ describe('TUI', function()
end)
it('no assert failure on deadly signal #21896', function()
- exec_lua([[vim.loop.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
- screen:expect({any = '%[Process exited 1%]'})
+ exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
+ screen:expect{grid=[[
+ Vim: Caught deadly signal 'SIGTERM' |
+ |
+ |
+ [Process exited 1]{1: } |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
+
+ it('no stack-use-after-scope with cursor color #22432', function()
+ screen:set_option('rgb', true)
+ command('set termguicolors')
+ child_session:request('nvim_exec2', [[
+ set tgc
+ hi Cursor guifg=Red guibg=Green
+ set guicursor=n:block-Cursor/lCursor
+ ]], {})
+ screen:set_default_attr_ids({
+ [1] = {reverse = true},
+ [2] = {bold = true, foreground = Screen.colors.Blue},
+ [3] = {foreground = Screen.colors.Blue},
+ [4] = {reverse = true, bold = true},
+ [5] = {bold = true},
+ })
+ screen:expect([[
+ {1: } |
+ {2:~}{3: }|
+ {2:~}{3: }|
+ {2:~}{3: }|
+ {4:[No Name] }|
+ |
+ {5:-- TERMINAL --} |
+ ]])
+ feed_data('i')
+ screen:expect([[
+ {1: } |
+ {2:~}{3: }|
+ {2:~}{3: }|
+ {2:~}{3: }|
+ {4:[No Name] }|
+ {5:-- INSERT --} |
+ {5:-- TERMINAL --} |
+ ]])
+ end)
+
+ it('redraws on SIGWINCH even if terminal size is unchanged #23411', function()
+ child_session:request('nvim_echo', {{'foo'}}, false, {})
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ foo |
+ {3:-- TERMINAL --} |
+ ]])
+ exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]])
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ |
+ {3:-- TERMINAL --} |
+ ]])
+ end)
+
+ it('supports hiding cursor', function()
+ child_session:request('nvim_command',
+ "let g:id = jobstart([v:progpath, '--clean', '--headless'])")
+ feed_data(':call jobwait([g:id])\n')
+ screen:expect([[
+ |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ :call jobwait([g:id]) |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\003')
+ screen:expect([[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ Type :qa and press <Enter> to exit Nvim |
+ {3:-- TERMINAL --} |
+ ]])
end)
end)
describe('TUI', function()
before_each(clear)
- after_each(function()
- os.remove('testF')
- end)
it('resize at startup #17285 #15044 #11330', function()
local screen = Screen.new(50, 10)
@@ -1544,12 +1839,51 @@ describe('TUI', function()
{2:~ }│{4:~ }|
{2:~ }│{5:[No Name] 0,0-1 All}|
{2:~ }│ |
- {5:new }{MATCH:<.*[/\]nvim }|
+ {5:new }{1:{MATCH:<.*[/\]nvim }}|
|
]])
end)
+ it('argv[0] can be overridden #23953', function()
+ if not exec_lua('return pcall(require, "ffi")') then
+ pending('missing LuaJIT FFI')
+ end
+ local script_file = 'Xargv0.lua'
+ write_file(script_file, [=[
+ local ffi = require('ffi')
+ ffi.cdef([[int execl(const char *, const char *, ...);]])
+ ffi.C.execl(vim.v.progpath, 'Xargv0nvim', '--clean')
+ ]=])
+ finally(function()
+ os.remove(script_file)
+ end)
+ local screen = thelpers.screen_setup(0, string.format([=[["%s", "--clean", "-l", "%s"]]=],
+ nvim_prog, script_file))
+ screen:expect{grid=[[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ feed_data(':put =v:argv + [v:progname]\n')
+ screen:expect{grid=[[
+ Xargv0nvim |
+ --embed |
+ --clean |
+ {1:X}argv0nvim |
+ {5:[No Name] [+] 5,1 Bot}|
+ 4 more lines |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
+
it('with non-tty (pipe) stdout/stderr', function()
+ finally(function()
+ os.remove('testF')
+ end)
local screen = thelpers.screen_setup(0, '"'..nvim_prog
..' -u NONE -i NONE --cmd \'set noswapfile noshowcmd noruler\' --cmd \'normal iabc\' > /dev/null 2>&1 && cat testF && rm testF"')
feed_data(':w testF\n:q\n')
@@ -1588,6 +1922,30 @@ describe('TUI', function()
{3:-- TERMINAL --} |
]])
end)
+
+ it('draws line with many trailing spaces correctly #24955', function()
+ local screen = thelpers.screen_setup(0, '["'..nvim_prog..[[", "-u", "NONE", "-i", "NONE"]]
+ ..[[, "--cmd", "call setline(1, ['1st line' .. repeat(' ', 153), '2nd line'])"]]..']', 80)
+ screen:expect{grid=[[
+ {1:1}st line |
+ |
+ |
+ 2nd line |
+ {5:[No Name] [+] 1,1 All}|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ feed_data('$')
+ screen:expect{grid=[[
+ 1st line |
+ |
+ {1: } |
+ 2nd line |
+ {5:[No Name] [+] 1,161 All}|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+ end)
end)
describe('TUI UIEnter/UILeave', function()
@@ -1626,12 +1984,16 @@ end)
describe('TUI FocusGained/FocusLost', function()
local screen
+ local child_session
before_each(function()
clear()
- screen = thelpers.screen_setup(0, '["'..nvim_prog
- ..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]')
- screen:expect{grid=[[
+ local child_server = new_pipename()
+ screen = thelpers.screen_setup(0,
+ string.format(
+ [=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile noshowcmd noruler"]]=],
+ nvim_prog, child_server))
+ screen:expect([[
{1: } |
{4:~ }|
{4:~ }|
@@ -1639,22 +2001,16 @@ describe('TUI FocusGained/FocusLost', function()
{5:[No Name] }|
|
{3:-- TERMINAL --} |
- ]]}
- feed_data(":autocmd FocusGained * echo 'gained'\n")
- feed_data(":autocmd FocusLost * echo 'lost'\n")
+ ]])
+ child_session = helpers.connect(child_server)
+ child_session:request('nvim_exec2', [[
+ autocmd FocusGained * echo 'gained'
+ autocmd FocusLost * echo 'lost'
+ ]], {})
feed_data("\034\016") -- CTRL-\ CTRL-N
end)
it('in normal-mode', function()
- screen:expect{grid=[[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] }|
- :autocmd FocusLost * echo 'lost' |
- {3:-- TERMINAL --} |
- ]]}
retry(2, 3 * screen.timeout, function()
feed_data('\027[I')
screen:expect([[
@@ -1681,7 +2037,7 @@ describe('TUI FocusGained/FocusLost', function()
end)
it('in insert-mode', function()
- feed_command('set noshowmode')
+ feed_data(':set noshowmode\r')
feed_data('i')
screen:expect{grid=[[
{1: } |
@@ -1746,18 +2102,11 @@ describe('TUI FocusGained/FocusLost', function()
-- Set up autocmds that modify the buffer, instead of just calling :echo.
-- This is how we can test handling of focus gained/lost during cmdline-mode.
-- See commit: 5cc87d4dabd02167117be7a978b5c8faaa975419.
- feed_data(":autocmd!\n")
- feed_data(":autocmd FocusLost * call append(line('$'), 'lost')\n")
- feed_data(":autocmd FocusGained * call append(line('$'), 'gained')\n")
- screen:expect{grid=[[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] }|
- |
- {3:-- TERMINAL --} |
- ]]}
+ child_session:request('nvim_exec2', [[
+ autocmd!
+ autocmd FocusLost * call append(line('$'), 'lost')
+ autocmd FocusGained * call append(line('$'), 'gained')
+ ]], {})
retry(2, 3 * screen.timeout, function()
-- Enter cmdline-mode.
feed_data(':')
@@ -1774,26 +2123,26 @@ describe('TUI FocusGained/FocusLost', function()
end)
it('in terminal-mode', function()
- feed_data(':set shell='..testprg('shell-test')..'\n')
+ feed_data(':set shell='..testprg('shell-test')..' shellcmdflag=EXE\n')
feed_data(':set noshowmode laststatus=0\n')
- feed_data(':terminal\n')
+ feed_data(':terminal zia\n')
-- Wait for terminal to be ready.
screen:expect{grid=[[
- {1:r}eady $ |
- [Process exited 0] |
+ {1:r}eady $ zia |
|
+ [Process exited 0] |
|
|
- :terminal |
+ :terminal zia |
{3:-- TERMINAL --} |
]]}
feed_data('\027[I')
screen:expect{grid=[[
- {1:r}eady $ |
- [Process exited 0] |
+ {1:r}eady $ zia |
|
+ [Process exited 0] |
|
|
gained |
@@ -1802,9 +2151,9 @@ describe('TUI FocusGained/FocusLost', function()
feed_data('\027[O')
screen:expect([[
- {1:r}eady $ |
- [Process exited 0] |
+ {1:r}eady $ zia |
|
+ [Process exited 0] |
|
|
lost |
@@ -1845,7 +2194,7 @@ describe("TUI 't_Co' (terminal colors)", function()
local screen
local function assert_term_colors(term, colorterm, maxcolors)
- helpers.clear({env={TERM=term}, args={}})
+ clear({env={TERM=term}, args={}})
-- This is ugly because :term/termopen() forces TERM=xterm-256color.
-- TODO: Revisit this after jobstart/termopen accept `env` dict.
screen = thelpers.screen_setup(0, string.format(
@@ -2210,45 +2559,44 @@ describe("TUI", function()
end)
+-- See test/unit/tui_spec.lua for unit tests.
describe('TUI bg color', function()
local screen
- local function setup()
- -- Only single integration test.
- -- See test/unit/tui_spec.lua for unit tests.
+ local function setup_bg_test()
clear()
screen = thelpers.screen_setup(0, '["'..nvim_prog
..'", "-u", "NONE", "-i", "NONE", "--cmd", "set noswapfile", '
..'"-c", "autocmd OptionSet background echo \\"did OptionSet, yay!\\""]')
end
- before_each(setup)
+ before_each(setup_bg_test)
it('triggers OptionSet event on unsplit terminal-response', function()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
- feed_data('\027]11;rgb:ffff/ffff/ffff\007')
+ feed_data('\027]11;rgb:ffff/ffff/ffff\027\\')
screen:expect{any='did OptionSet, yay!'}
feed_data(':echo "new_bg=".&background\n')
screen:expect{any='new_bg=light'}
- setup()
+ setup_bg_test()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
feed_data('\027]11;rgba:ffff/ffff/ffff/8000\027\\')
screen:expect{any='did OptionSet, yay!'}
@@ -2259,13 +2607,13 @@ describe('TUI bg color', function()
it('triggers OptionSet event with split terminal-response', function()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
-- Send a background response with the OSC command part split.
feed_data('\027]11;rgb')
@@ -2275,19 +2623,19 @@ describe('TUI bg color', function()
feed_data(':echo "new_bg=".&background\n')
screen:expect{any='new_bg=light'}
- setup()
+ setup_bg_test()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
-- Send a background response with the Pt portion split.
feed_data('\027]11;rgba:ffff/fff')
- feed_data('f/ffff/8000\007')
+ feed_data('f/ffff/8000\027\\')
screen:expect{any='did OptionSet, yay!'}
feed_data(':echo "new_bg=".&background\n')
@@ -2296,13 +2644,13 @@ describe('TUI bg color', function()
it('not triggers OptionSet event with invalid terminal-response', function()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
feed_data('\027]11;rgb:ffff/ffff/ffff/8000\027\\')
screen:expect_unchanged()
@@ -2310,17 +2658,17 @@ describe('TUI bg color', function()
feed_data(':echo "new_bg=".&background\n')
screen:expect{any='new_bg=dark'}
- setup()
+ setup_bg_test()
screen:expect([[
- {1: } |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] 0,0-1 All}|
- |
- {3:-- TERMINAL --} |
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] 0,0-1 All}|
+ |
+ {3:-- TERMINAL --} |
]])
- feed_data('\027]11;rgba:ffff/foo/ffff/8000\007')
+ feed_data('\027]11;rgba:ffff/foo/ffff/8000\027\\')
screen:expect_unchanged()
feed_data(':echo "new_bg=".&background\n')
@@ -2365,18 +2713,31 @@ describe("TUI as a client", function()
set_session(client_super)
local screen_client = thelpers.screen_setup(0,
- string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=],
+ string.format([=[["%s", "--server", "%s", "--remote-ui"]]=],
nvim_prog, server_pipe))
- screen_client:expect{grid=[[
- Hello, Worl{1:d} |
- {4:~ }|
- {4:~ }|
- {4:~ }|
- {5:[No Name] [+] }|
- |
- {3:-- TERMINAL --} |
- ]]}
+ screen_client:expect{grid=[[
+ Hello, Worl{1:d} |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] [+] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ -- grid smaller than containing terminal window is cleared properly
+ feed_data(":call setline(1,['a'->repeat(&columns)]->repeat(&lines))\n")
+ feed_data("0:set lines=3\n")
+ screen_server:expect{grid=[[
+ {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {5:[No Name] [+] }|
+ |
+ |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]]}
feed_data(":q!\n")
@@ -2385,19 +2746,19 @@ describe("TUI as a client", function()
end)
it("connects to remote instance (--headless)", function()
- local server = helpers.spawn_argv(false) -- equivalent to clear()
+ local server = spawn_argv(false) -- equivalent to clear()
local client_super = spawn_argv(true)
set_session(server)
- local server_pipe = eval'v:servername'
- feed'iHalloj!<esc>'
+ local server_pipe = meths.get_vvar('servername')
+ server:request('nvim_input', 'iHalloj!<Esc>')
set_session(client_super)
- local screen = thelpers.screen_setup(0,
- string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=],
+ local screen_client = thelpers.screen_setup(0,
+ string.format([=[["%s", "--server", "%s", "--remote-ui"]]=],
nvim_prog, server_pipe))
- screen:expect{grid=[[
+ screen_client:expect{grid=[[
Halloj{1:!} |
{4:~ }|
{4:~ }|
@@ -2407,15 +2768,32 @@ describe("TUI as a client", function()
{3:-- TERMINAL --} |
]]}
+ -- No heap-use-after-free when receiving UI events after deadly signal #22184
+ server:request('nvim_input', ('a'):rep(1000))
+ exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigterm')]])
+ screen_client:expect{grid=[[
+ Vim: Caught deadly signal 'SIGTERM' |
+ |
+ |
+ [Process exited 1]{1: } |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]]}
+
+ eq(0, meths.get_vvar('shell_error'))
+ -- exits on input eof #22244
+ funcs.system({nvim_prog, '--server', server_pipe, '--remote-ui'})
+ eq(1, meths.get_vvar('shell_error'))
+
client_super:close()
server:close()
end)
-
it("throws error when no server exists", function()
clear()
local screen = thelpers.screen_setup(0,
- string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "127.0.0.1:2436546", "--remote-ui"]]=],
+ string.format([=[["%s", "--server", "127.0.0.1:2436546", "--remote-ui"]]=],
nvim_prog), 60)
screen:expect([[
@@ -2429,7 +2807,7 @@ describe("TUI as a client", function()
]])
end)
- it("exits when server quits", function()
+ local function test_remote_tui_quit(status)
local server_super = spawn_argv(false) -- equivalent to clear()
local client_super = spawn_argv(true)
@@ -2438,6 +2816,15 @@ describe("TUI as a client", function()
local screen_server = thelpers.screen_setup(0,
string.format([=[["%s", "--listen", "%s", "-u", "NONE", "-i", "NONE", "--cmd", "%s laststatus=2 background=dark"]]=],
nvim_prog, server_pipe, nvim_set))
+ screen_server:expect{grid=[[
+ {1: } |
+ {4:~ }|
+ {4:~ }|
+ {4:~ }|
+ {5:[No Name] }|
+ |
+ {3:-- TERMINAL --} |
+ ]]}
feed_data("iHello, World")
screen_server:expect{grid=[[
@@ -2462,7 +2849,7 @@ describe("TUI as a client", function()
set_session(client_super)
local screen_client = thelpers.screen_setup(0,
- string.format([=[["%s", "-u", "NONE", "-i", "NONE", "--server", "%s", "--remote-ui"]]=],
+ string.format([=[["%s", "--server", "%s", "--remote-ui"]]=],
nvim_prog, server_pipe))
screen_client:expect{grid=[[
@@ -2477,13 +2864,39 @@ describe("TUI as a client", function()
-- quitting the server
set_session(server_super)
- feed_data(":q!\n")
- screen_server:expect({any="Process exited 0"})
-
+ feed_data(status and ':' .. status .. 'cquit!\n' or ":quit!\n")
+ status = status and status or 0
+ screen_server:expect{grid=[[
+ |
+ [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
+ |
+ |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]]}
-- assert that client has exited
- screen_client:expect({any="Process exited 0"})
+ screen_client:expect{grid=[[
+ |
+ [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}|
+ |
+ |
+ |
+ |
+ {3:-- TERMINAL --} |
+ ]]}
server_super:close()
client_super:close()
+ end
+
+ describe("exits when server quits", function()
+ it("with :quit", function()
+ test_remote_tui_quit()
+ end)
+
+ it("with :cquit", function()
+ test_remote_tui_quit(42)
+ end)
end)
end)
diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua
index 80e9d78400..39fc2c2562 100644
--- a/test/functional/terminal/window_spec.lua
+++ b/test/functional/terminal/window_spec.lua
@@ -44,7 +44,7 @@ describe(':terminal window', function()
{7:6 } |
{3:-- TERMINAL --} |
]])
- feed_data({'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'})
+ feed_data('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
screen:expect([[
{7:1 }tty ready |
{7:2 }rows: 6, cols: 48 |
@@ -55,8 +55,6 @@ describe(':terminal window', function()
{3:-- TERMINAL --} |
]])
- skip(is_os('win'), 'win: :terminal resize is unreliable #7007')
-
-- numberwidth=9
feed([[<C-\><C-N>]])
feed([[:set numberwidth=9 number<CR>i]])
@@ -64,17 +62,17 @@ describe(':terminal window', function()
{7: 1 }tty ready |
{7: 2 }rows: 6, cols: 48 |
{7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO|
- {7: 4 }WXYZrows: 6, cols: 41 |
+ {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 |
{7: 5 }{1: } |
{7: 6 } |
{3:-- TERMINAL --} |
]])
- feed_data({' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'})
+ feed_data(' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
screen:expect([[
{7: 1 }tty ready |
{7: 2 }rows: 6, cols: 48 |
{7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO|
- {7: 4 }WXYZrows: 6, cols: 41 |
+ {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 |
{7: 5 } abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN|
{7: 6 }OPQRSTUVWXYZ{1: } |
{3:-- TERMINAL --} |
@@ -82,6 +80,41 @@ describe(':terminal window', function()
end)
end)
+ describe("with 'statuscolumn'", function()
+ it('wraps text', function()
+ command([[set number statuscolumn=++%l\ \ ]])
+ screen:expect([[
+ {7:++1 }tty ready |
+ {7:++2 }rows: 6, cols: 45 |
+ {7:++3 }{1: } |
+ {7:++4 } |
+ {7:++5 } |
+ {7:++6 } |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\n\n\n\n\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+ screen:expect([[
+ {7:++4 } |
+ {7:++5 } |
+ {7:++6 } |
+ {7:++7 } |
+ {7:++8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS|
+ {7:++9 }TUVWXYZ{1: } |
+ {3:-- TERMINAL --} |
+ ]])
+ feed_data('\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
+ screen:expect([[
+ {7:++7 } |
+ {7:++8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR|
+ {7:++9 }STUVWXYZ |
+ {7:++10 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR|
+ {7:++11 }STUVWXYZrows: 6, cols: 44 |
+ {7:++12 }{1: } |
+ {3:-- TERMINAL --} |
+ ]])
+ end)
+ end)
+
describe("with 'colorcolumn'", function()
before_each(function()
feed([[<C-\><C-N>]])
diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua
index 1d77e1e92e..da14531fa2 100644
--- a/test/functional/terminal/window_split_tab_spec.lua
+++ b/test/functional/terminal/window_split_tab_spec.lua
@@ -19,7 +19,7 @@ describe(':terminal', function()
clear()
-- set the statusline to a constant value because of variables like pid
-- and current directory and to improve visibility of splits
- meths.set_option('statusline', '==========')
+ meths.set_option_value('statusline', '==========', {})
command('highlight StatusLine cterm=NONE')
command('highlight StatusLineNC cterm=NONE')
command('highlight VertSplit cterm=NONE')
@@ -71,7 +71,7 @@ describe(':terminal', function()
end)
it('does not change size if updated when not visible in any window #19665', function()
- local channel = meths.buf_get_option(0, 'channel')
+ local channel = meths.get_option_value('channel', {})
command('enew')
sleep(100)
meths.chan_send(channel, 'foo')
diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua
new file mode 100644
index 0000000000..a8abbc002b
--- /dev/null
+++ b/test/functional/treesitter/fold_spec.lua
@@ -0,0 +1,474 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local insert = helpers.insert
+local exec_lua = helpers.exec_lua
+local command = helpers.command
+local feed = helpers.feed
+local Screen = require('test.functional.ui.screen')
+
+before_each(clear)
+
+describe('treesitter foldexpr', function()
+ clear()
+
+ local test_text = [[
+void ui_refresh(void)
+{
+ int width = INT_MAX, height = INT_MAX;
+ bool ext_widgets[kUIExtCount];
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ext_widgets[i] = true;
+ }
+
+ bool inclusive = ui_override();
+ for (size_t i = 0; i < ui_count; i++) {
+ UI *ui = uis[i];
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
+ foo = BAR(ui->bazaar, bazaar);
+ for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
+ }
+ }
+}]]
+
+ local function get_fold_levels()
+ return exec_lua([[
+ local res = {}
+ for i = 1, vim.api.nvim_buf_line_count(0) do
+ res[i] = vim.treesitter.foldexpr(i)
+ end
+ return res
+ ]])
+ end
+
+ it("can compute fold levels", function()
+ insert(test_text)
+
+ exec_lua([[vim.treesitter.get_parser(0, "c")]])
+
+ eq({
+ [1] = '>1',
+ [2] = '1',
+ [3] = '1',
+ [4] = '1',
+ [5] = '>2',
+ [6] = '2',
+ [7] = '2',
+ [8] = '1',
+ [9] = '1',
+ [10] = '>2',
+ [11] = '2',
+ [12] = '2',
+ [13] = '2',
+ [14] = '2',
+ [15] = '>3',
+ [16] = '3',
+ [17] = '3',
+ [18] = '2',
+ [19] = '1' }, get_fold_levels())
+
+ end)
+
+ it("recomputes fold levels after lines are added/removed", function()
+ insert(test_text)
+
+ exec_lua([[vim.treesitter.get_parser(0, "c")]])
+
+ command('1,2d')
+
+ eq({
+ [1] = '0',
+ [2] = '0',
+ [3] = '>1',
+ [4] = '1',
+ [5] = '1',
+ [6] = '0',
+ [7] = '0',
+ [8] = '>1',
+ [9] = '1',
+ [10] = '1',
+ [11] = '1',
+ [12] = '1',
+ [13] = '>2',
+ [14] = '2',
+ [15] = '2',
+ [16] = '1',
+ [17] = '0' }, get_fold_levels())
+
+ command('1put!')
+
+ eq({
+ [1] = '>1',
+ [2] = '1',
+ [3] = '1',
+ [4] = '1',
+ [5] = '>2',
+ [6] = '2',
+ [7] = '2',
+ [8] = '1',
+ [9] = '1',
+ [10] = '>2',
+ [11] = '2',
+ [12] = '2',
+ [13] = '2',
+ [14] = '2',
+ [15] = '>3',
+ [16] = '3',
+ [17] = '3',
+ [18] = '2',
+ [19] = '1' }, get_fold_levels())
+ end)
+
+ it("updates folds in all windows", function()
+ local screen = Screen.new(60, 48)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue};
+ [2] = {bold = true, foreground = Screen.colors.Blue1};
+ [3] = {bold = true, reverse = true};
+ [4] = {reverse = true};
+ })
+
+ exec_lua([[vim.treesitter.get_parser(0, "c")]])
+ command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=9]])
+ command('split')
+
+ insert(test_text)
+
+ screen:expect{grid=[[
+ {1:-}void ui_refresh(void) |
+ {1:│}{ |
+ {1:│} int width = INT_MAX, height = INT_MAX; |
+ {1:│} bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:2} ext_widgets[i] = true; |
+ {1:2} } |
+ {1:│} |
+ {1:│} bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:2} UI *ui = uis[i]; |
+ {1:2} width = MIN(ui->width, width); |
+ {1:2} height = MIN(ui->height, height); |
+ {1:2} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:3} } |
+ {1:2} } |
+ {1:│}^} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {3:[No Name] [+] }|
+ {1:-}void ui_refresh(void) |
+ {1:│}{ |
+ {1:│} int width = INT_MAX, height = INT_MAX; |
+ {1:│} bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:2} ext_widgets[i] = true; |
+ {1:2} } |
+ {1:│} |
+ {1:│} bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:2} UI *ui = uis[i]; |
+ {1:2} width = MIN(ui->width, width); |
+ {1:2} height = MIN(ui->height, height); |
+ {1:2} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:3} } |
+ {1:2} } |
+ {1:│}} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {4:[No Name] [+] }|
+ |
+ ]]}
+
+ command('1,2d')
+
+ screen:expect{grid=[[
+ {1: } ^int width = INT_MAX, height = INT_MAX; |
+ {1: } bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:│} ext_widgets[i] = true; |
+ {1:│} } |
+ {1: } |
+ {1: } bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:│} UI *ui = uis[i]; |
+ {1:│} width = MIN(ui->width, width); |
+ {1:│} height = MIN(ui->height, height); |
+ {1:│} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:2} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:2} } |
+ {1:│} } |
+ {1: }} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {3:[No Name] [+] }|
+ {1: } int width = INT_MAX, height = INT_MAX; |
+ {1: } bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:│} ext_widgets[i] = true; |
+ {1:│} } |
+ {1: } |
+ {1: } bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:│} UI *ui = uis[i]; |
+ {1:│} width = MIN(ui->width, width); |
+ {1:│} height = MIN(ui->height, height); |
+ {1:│} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:2} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:2} } |
+ {1:│} } |
+ {1: }} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {4:[No Name] [+] }|
+ |
+ ]]}
+
+
+ feed([[O<C-u><C-r>"<BS><Esc>]])
+
+ screen:expect{grid=[[
+ {1:-}void ui_refresh(void) |
+ {1:│}^{ |
+ {1:│} int width = INT_MAX, height = INT_MAX; |
+ {1:│} bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:2} ext_widgets[i] = true; |
+ {1:2} } |
+ {1:│} |
+ {1:│} bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:2} UI *ui = uis[i]; |
+ {1:2} width = MIN(ui->width, width); |
+ {1:2} height = MIN(ui->height, height); |
+ {1:2} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:3} } |
+ {1:2} } |
+ {1:│}} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {3:[No Name] [+] }|
+ {1:-}void ui_refresh(void) |
+ {1:│}{ |
+ {1:│} int width = INT_MAX, height = INT_MAX; |
+ {1:│} bool ext_widgets[kUIExtCount]; |
+ {1:-} for (UIExtension i = 0; (int)i < kUIExtCount; i++) { |
+ {1:2} ext_widgets[i] = true; |
+ {1:2} } |
+ {1:│} |
+ {1:│} bool inclusive = ui_override(); |
+ {1:-} for (size_t i = 0; i < ui_count; i++) { |
+ {1:2} UI *ui = uis[i]; |
+ {1:2} width = MIN(ui->width, width); |
+ {1:2} height = MIN(ui->height, height); |
+ {1:2} foo = BAR(ui->bazaar, bazaar); |
+ {1:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1:3} } |
+ {1:2} } |
+ {1:│}} |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {4:[No Name] [+] }|
+ |
+ ]]}
+
+ end)
+
+ it("doesn't open folds in diff mode", function()
+ local screen = Screen.new(60, 36)
+ screen:attach()
+
+ exec_lua([[vim.treesitter.get_parser(0, "c")]])
+ command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=9]])
+ insert(test_text)
+ command('16d')
+
+ command('new')
+ insert(test_text)
+
+ command('windo diffthis')
+ feed('do')
+
+ screen:expect{grid=[[
+ {1:+ }{2:+-- 9 lines: void ui_refresh(void)·······················}|
+ {1: } for (size_t i = 0; i < ui_count; i++) { |
+ {1: } UI *ui = uis[i]; |
+ {1: } width = MIN(ui->width, width); |
+ {1: } height = MIN(ui->height, height); |
+ {1: } foo = BAR(ui->bazaar, bazaar); |
+ {1: } for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1: } ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1: } } |
+ {1: } } |
+ {1: }} |
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {4:[No Name] [+] }|
+ {1:+ }{2:+-- 9 lines: void ui_refresh(void)·······················}|
+ {1: } for (size_t i = 0; i < ui_count; i++) { |
+ {1: } UI *ui = uis[i]; |
+ {1: } width = MIN(ui->width, width); |
+ {1: } height = MIN(ui->height, height); |
+ {1: } foo = BAR(ui->bazaar, bazaar); |
+ {1: } for (UIExtension j = 0; (int)j < kUIExtCount; j++) { |
+ {1: } ext_widgets[j] &= (ui->ui_ext[j] || inclusive); |
+ {1: } ^} |
+ {1: } } |
+ {1: }} |
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {3:~ }|
+ {5:[No Name] [+] }|
+ |
+ ]], attr_ids={
+ [1] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4};
+ [2] = {background = Screen.colors.LightGrey, foreground = Screen.colors.Blue4};
+ [3] = {foreground = Screen.colors.Blue, bold = true};
+ [4] = {reverse = true};
+ [5] = {reverse = true, bold = true};
+ }}
+ end)
+
+end)
+
+describe('treesitter foldtext', function()
+ local test_text = [[
+void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *))
+{
+ int width = INT_MAX, height = INT_MAX;
+ bool ext_widgets[kUIExtCount];
+ for (UIExtension i = 0; (int)i < kUIExtCount; i++) {
+ ext_widgets[i] = true;
+ }
+
+ bool inclusive = ui_override();
+ for (size_t i = 0; i < ui_count; i++) {
+ UI *ui = uis[i];
+ width = MIN(ui->width, width);
+ height = MIN(ui->height, height);
+ foo = BAR(ui->bazaar, bazaar);
+ for (UIExtension j = 0; (int)j < kUIExtCount; j++) {
+ ext_widgets[j] &= (ui->ui_ext[j] || inclusive);
+ }
+ }
+}]]
+ local screen
+
+ before_each(function()
+ screen = Screen.new(60, 5)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue, bold = true},
+ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray};
+ [2] = {bold = true, background = Screen.colors.LightGray, foreground = Screen.colors.SeaGreen};
+ [3] = {foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGray};
+ [4] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.LightGray};
+ [5] = {bold = true, background = Screen.colors.LightGray, foreground = Screen.colors.Brown};
+ [6] = {background = Screen.colors.Red1};
+ [7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Red};
+ [8] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red};
+ [9] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.Red};
+ [10] = {bold = true};
+ })
+ screen:attach()
+ end)
+
+ it('displays highlighted content', function()
+ command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]])
+ insert(test_text)
+ exec_lua([[vim.treesitter.get_parser(0, "c")]])
+
+ feed('ggVGzf')
+ screen:expect{grid=[[
+ {2:^void}{1: }{3:qsort}{4:(}{2:void}{1: }{5:*}{3:base}{4:,}{1: }{2:size_t}{1: }{3:nel}{4:,}{1: }{2:size_t}{1: }{3:width}{4:,}{1: }{2:int}{1: }{4:(}{5:*}{3:compa}|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end)
+
+ it('handles deep nested captures', function()
+ command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]])
+ insert([[
+function FoldInfo.new()
+ return setmetatable({
+ start_counts = {},
+ stop_counts = {},
+ levels0 = {},
+ levels = {},
+ }, FoldInfo)
+end]])
+ exec_lua([[vim.treesitter.get_parser(0, "lua")]])
+
+ feed('ggjVGkzfgg')
+ screen:expect{grid=[[
+ ^function FoldInfo.new() |
+ {1: }{5:return}{1: }{4:setmetatable({}{1:·····································}|
+ end |
+ {0:~ }|
+ |
+ ]]}
+
+ command('hi! Visual guibg=Red')
+ feed('GVgg')
+ screen:expect{grid=[[
+ ^f{6:unction FoldInfo.new()} |
+ {7: }{8:return}{7: }{9:setmetatable({}{7:·····································}|
+ {6:end} |
+ {0:~ }|
+ {10:-- VISUAL LINE --} |
+ ]]}
+
+ feed('10l<C-V>')
+ screen:expect{grid=[[
+ {6:function F}^oldInfo.new() |
+ {7: }{8:return}{7: }{9:se}{4:tmetatable({}{1:·····································}|
+ {6:end} |
+ {0:~ }|
+ {10:-- VISUAL BLOCK --} |
+ ]]}
+ end)
+
+ it('falls back to default', function()
+ command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext()]])
+ insert(test_text)
+
+ feed('ggVGzf')
+ screen:expect{grid=[[
+ {1:^+-- 19 lines: void qsort(void *base, size_t nel, size_t widt}|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end)
+end)
diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua
index 2a2311c0fa..e037c9e215 100644
--- a/test/functional/treesitter/highlight_spec.lua
+++ b/test/functional/treesitter/highlight_spec.lua
@@ -11,7 +11,7 @@ local eq = helpers.eq
before_each(clear)
-local hl_query = [[
+local hl_query_c = [[
(ERROR) @error
"if" @keyword
@@ -47,7 +47,7 @@ local hl_query = [[
(comment) @comment
]]
-local hl_text = [[
+local hl_text_c = [[
/// Schedule Lua callback on main loop's event queue
static int nlua_schedule(lua_State *const lstate)
{
@@ -64,7 +64,7 @@ static int nlua_schedule(lua_State *const lstate)
return 0;
}]]
-local test_text = [[
+local test_text_c = [[
void ui_refresh(void)
{
int width = INT_MAX, height = INT_MAX;
@@ -85,7 +85,57 @@ void ui_refresh(void)
}
}]]
-describe('treesitter highlighting', function()
+local injection_text_c = [[
+int x = INT_MAX;
+#define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+#define foo void main() { \
+ return 42; \
+ }
+]]
+
+local injection_grid_c = [[
+ int x = INT_MAX; |
+ #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y)) |
+ #define foo void main() { \ |
+ return 42; \ |
+ } |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+]]
+
+local injection_grid_expected_c = [[
+ {3:int} x = {5:INT_MAX}; |
+ #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) |
+ #define foo {3:void} main() { \ |
+ {4:return} {5:42}; \ |
+ } |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+]]
+
+describe('treesitter highlighting (C)', function()
local screen
before_each(function()
@@ -105,13 +155,13 @@ describe('treesitter highlighting', function()
[11] = {foreground = Screen.colors.Cyan4};
}
- exec_lua([[ hl_query = ... ]], hl_query)
+ exec_lua([[ hl_query = ... ]], hl_query_c)
command [[ hi link @error ErrorMsg ]]
command [[ hi link @warning WarningMsg ]]
end)
it('is updated with edits', function()
- insert(hl_text)
+ insert(hl_text_c)
screen:expect{grid=[[
/// Schedule Lua callback on main loop's event queue |
static int nlua_schedule(lua_State *const lstate) |
@@ -274,7 +324,7 @@ describe('treesitter highlighting', function()
end)
it('is updated with :sort', function()
- insert(test_text)
+ insert(test_text_c)
exec_lua [[
local parser = vim.treesitter.get_parser(0, "c")
test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}})
@@ -351,7 +401,7 @@ describe('treesitter highlighting', function()
[1] = {bold = true, foreground = Screen.colors.SeaGreen4};
}
- insert(test_text)
+ insert(test_text_c)
screen:expect{ grid= [[
int width = INT_MAX, height = INT_MAX; |
@@ -376,7 +426,7 @@ describe('treesitter highlighting', function()
exec_lua [[
parser = vim.treesitter.get_parser(0, "c")
- query = vim.treesitter.parse_query("c", "(declaration) @decl")
+ query = vim.treesitter.query.parse("c", "(declaration) @decl")
local nodes = {}
for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do
@@ -411,85 +461,58 @@ describe('treesitter highlighting', function()
end)
it("supports injected languages", function()
- insert([[
- int x = INT_MAX;
- #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
- #define foo void main() { \
- return 42; \
- }
- ]])
+ insert(injection_text_c)
- screen:expect{grid=[[
- int x = INT_MAX; |
- #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))|
- #define foo void main() { \ |
- return 42; \ |
- } |
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]]}
+ screen:expect{grid=injection_grid_c}
exec_lua [[
local parser = vim.treesitter.get_parser(0, "c", {
- injections = {c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}
+ injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}
})
local highlighter = vim.treesitter.highlighter
test_hl = highlighter.new(parser, {queries = {c = hl_query}})
]]
- screen:expect{grid=[[
- {3:int} x = {5:INT_MAX}; |
- #define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))|
- #define foo {3:void} main() { \ |
- {4:return} {5:42}; \ |
- } |
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]]}
+ screen:expect{grid=injection_grid_expected_c}
+ end)
+
+ it("supports injecting by ft name in metadata['injection.language']", function()
+ insert(injection_text_c)
+
+ screen:expect{grid=injection_grid_c}
+
+ exec_lua [[
+ vim.treesitter.language.register("c", "foo")
+ local parser = vim.treesitter.get_parser(0, "c", {
+ injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "fOO")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "fOO"))'}
+ })
+ local highlighter = vim.treesitter.highlighter
+ test_hl = highlighter.new(parser, {queries = {c = hl_query}})
+ ]]
+
+ screen:expect{grid=injection_grid_expected_c}
end)
it("supports overriding queries, like ", function()
insert([[
int x = INT_MAX;
- #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
+ #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
#define foo void main() { \
return 42; \
}
]])
exec_lua [[
- local injection_query = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"
- require('vim.treesitter.query').set_query("c", "highlights", hl_query)
- require('vim.treesitter.query').set_query("c", "injections", injection_query)
+ local injection_query = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'
+ vim.treesitter.query.set("c", "highlights", hl_query)
+ vim.treesitter.query.set("c", "injections", injection_query)
vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, "c"))
]]
screen:expect{grid=[[
{3:int} x = {5:INT_MAX}; |
- #define {5:READ_STRING}(x, y) ({3:char_u} *)read_string((x), ({3:size_t})(y))|
+ #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) |
#define foo {3:void} main() { \ |
{4:return} {5:42}; \ |
} |
@@ -510,7 +533,7 @@ describe('treesitter highlighting', function()
end)
it("supports highlighting with custom highlight groups", function()
- insert(hl_text)
+ insert(hl_text_c)
exec_lua [[
local parser = vim.treesitter.get_parser(0, "c")
@@ -567,7 +590,7 @@ describe('treesitter highlighting', function()
it("supports highlighting with priority", function()
insert([[
int x = INT_MAX;
- #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
+ #define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
#define foo void main() { \
return 42; \
}
@@ -575,12 +598,12 @@ describe('treesitter highlighting', function()
exec_lua [[
local parser = vim.treesitter.get_parser(0, "c")
- test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query..'\n((translation_unit) @Error (set! "priority" 101))\n'}})
+ test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query..'\n((translation_unit) @constant (#set! "priority" 101))\n'}})
]]
- -- expect everything to have Error highlight
+ -- expect everything to have Constant highlight
screen:expect{grid=[[
{12:int}{8: x = INT_MAX;} |
- {8:#define READ_STRING(x, y) (}{12:char_u}{8: *)read_string((x), (}{12:size_t}{8:)(y))}|
+ {8:#define READ_STRING(x, y) (}{12:char}{8: *)read_string((x), (}{12:size_t}{8:)(y))} |
{8:#define foo }{12:void}{8: main() { \} |
{8: }{12:return}{8: 42; \} |
{8: }} |
@@ -599,13 +622,13 @@ describe('treesitter highlighting', function()
|
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue1};
- [8] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red};
+ [8] = {foreground = Screen.colors.Magenta1};
-- bold will not be overwritten at the moment
- [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100};
+ [12] = {bold = true, foreground = Screen.colors.Magenta1};
}}
eq({
- {capture='Error', metadata = { priority='101' }, lang='c' };
+ {capture='constant', metadata = { priority='101' }, lang='c' };
{capture='type', metadata = { }, lang='c' };
}, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]])
end)
@@ -692,7 +715,7 @@ describe('treesitter highlighting', function()
end)
it("supports conceal attribute", function()
- insert(hl_text)
+ insert(hl_text_c)
-- conceal can be empty or a single cchar.
exec_lua [=[
@@ -753,3 +776,129 @@ describe('treesitter highlighting', function()
eq(nil, get_hl"@total.nonsense.but.a.lot.of.dots")
end)
end)
+
+describe('treesitter highlighting (help)', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(40, 6)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = {foreground = Screen.colors.Blue1};
+ [2] = {bold = true, foreground = Screen.colors.Blue1};
+ [3] = {bold = true, foreground = Screen.colors.Brown};
+ [4] = {foreground = Screen.colors.Cyan4};
+ [5] = {foreground = Screen.colors.Magenta1};
+ }
+ end)
+
+ it("correctly redraws added/removed injections", function()
+ insert[[
+ >ruby
+ -- comment
+ local this_is = 'actually_lua'
+ <
+ ]]
+
+ exec_lua [[
+ vim.bo.filetype = 'help'
+ vim.treesitter.start()
+ ]]
+
+ screen:expect{grid=[[
+ {1:>ruby} |
+ {1: -- comment} |
+ {1: local this_is = 'actually_lua'} |
+ < |
+ ^ |
+ |
+ ]]}
+
+ helpers.curbufmeths.set_text(0, 1, 0, 5, {'lua'})
+
+ screen:expect{grid=[[
+ {1:>lua} |
+ {1: -- comment} |
+ {1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} |
+ < |
+ ^ |
+ |
+ ]]}
+
+ helpers.curbufmeths.set_text(0, 1, 0, 4, {'ruby'})
+
+ screen:expect{grid=[[
+ {1:>ruby} |
+ {1: -- comment} |
+ {1: local this_is = 'actually_lua'} |
+ < |
+ ^ |
+ |
+ ]]}
+ end)
+
+end)
+
+describe('treesitter highlighting (nested injections)', function()
+ local screen
+
+ before_each(function()
+ screen = Screen.new(80, 7)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = {foreground = Screen.colors.SlateBlue};
+ [2] = {bold = true, foreground = Screen.colors.Brown};
+ [3] = {foreground = Screen.colors.Cyan4};
+ [4] = {foreground = Screen.colors.Fuchsia};
+ }
+ end)
+
+ it("correctly redraws nested injections (GitHub #25252)", function()
+ insert[=[
+function foo() print("Lua!") end
+
+local lorem = {
+ ipsum = {},
+ bar = {},
+}
+vim.cmd([[
+ augroup RustLSP
+ autocmd CursorHold silent! lua vim.lsp.buf.document_highlight()
+ augroup END
+]])
+ ]=]
+
+ exec_lua [[
+ vim.opt.scrolloff = 0
+ vim.bo.filetype = 'lua'
+ vim.treesitter.start()
+ ]]
+
+ -- invalidate the language tree
+ feed("ggi--[[<ESC>04x")
+
+ screen:expect{grid=[[
+ {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} |
+ |
+ {2:local} {3:lorem} {2:=} {1:{} |
+ {3:ipsum} {2:=} {1:{},} |
+ {3:bar} {2:=} {1:{},} |
+ {1:}} |
+ |
+ ]]}
+
+ -- spam newline insert/delete to invalidate Lua > Vim > Lua region
+ feed("3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0")
+
+ screen:expect{grid=[[
+ {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} |
+ |
+ {2:local} {3:lorem} {2:=} {1:{} |
+ ^ {3:ipsum} {2:=} {1:{},} |
+ {3:bar} {2:=} {1:{},} |
+ {1:}} |
+ |
+ ]]}
+ end)
+
+end)
diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua
index df45c9b384..9b871a72fb 100644
--- a/test/functional/treesitter/language_spec.lua
+++ b/test/functional/treesitter/language_spec.lua
@@ -18,27 +18,27 @@ describe('treesitter language API', function()
-- actual message depends on platform
matches("Failed to load parser for language 'borklang': uv_dlopen: .+",
- pcall_err(exec_lua, "parser = vim.treesitter.require_language('borklang', 'borkbork.so')"))
+ pcall_err(exec_lua, "parser = vim.treesitter.language.add('borklang', { path = 'borkbork.so' })"))
- -- Should not throw an error when silent
- eq(false, exec_lua("return vim.treesitter.require_language('borklang', nil, true)"))
- eq(false, exec_lua("return vim.treesitter.require_language('borklang', 'borkbork.so', true)"))
+ eq(false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang')"))
+
+ eq(false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang', { path = 'borkbork.so' })"))
eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
- pcall_err(exec_lua, "parser = vim.treesitter.inspect_language('borklang')"))
+ pcall_err(exec_lua, "parser = vim.treesitter.language.inspect('borklang')"))
matches("Failed to load parser: uv_dlsym: .+",
- pcall_err(exec_lua, 'vim.treesitter.require_language("c", nil, false, "borklang")'))
+ pcall_err(exec_lua, 'vim.treesitter.language.add("c", { symbol_name = "borklang" })'))
end)
it('shows error for invalid language name', function()
eq(".../language.lua:0: '/foo/' is not a valid language name",
- pcall_err(exec_lua, 'vim.treesitter.require_language("/foo/", nil, false)'))
+ pcall_err(exec_lua, 'vim.treesitter.language.add("/foo/")'))
end)
it('inspects language', function()
local keys, fields, symbols = unpack(exec_lua([[
- local lang = vim.treesitter.inspect_language('c')
+ local lang = vim.treesitter.language.inspect('c')
local keys, symbols = {}, {}
for k,_ in pairs(lang) do
keys[k] = true
@@ -82,7 +82,7 @@ describe('treesitter language API', function()
command("set filetype=borklang")
-- Should throw an error when filetype changes to borklang
eq(".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers",
- pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)"))
+ pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0, 'borklang')"))
end)
it('retrieve the tree given a range', function ()
diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua
index a82dce47b7..eef75d0e91 100644
--- a/test/functional/treesitter/node_spec.lua
+++ b/test/functional/treesitter/node_spec.lua
@@ -4,6 +4,7 @@ local clear = helpers.clear
local eq = helpers.eq
local exec_lua = helpers.exec_lua
local insert = helpers.insert
+local assert_alive = helpers.assert_alive
before_each(clear)
@@ -14,6 +15,31 @@ end
describe('treesitter node API', function()
clear()
+ it('double free tree', function()
+ insert('F')
+ exec_lua([[
+ vim.treesitter.start(0, 'lua')
+ vim.treesitter.get_node():tree()
+ vim.treesitter.get_node():tree()
+ collectgarbage()
+ ]])
+ assert_alive()
+ end)
+
+ it('double free tree 2', function()
+ exec_lua([[
+ parser = vim.treesitter.get_parser(0, "c")
+ local x = parser:parse()[1]:root():tree()
+ vim.api.nvim_buf_set_text(0, 0,0, 0,0, {'y'})
+ parser:parse()
+ vim.api.nvim_buf_set_text(0, 0,0, 0,1, {'z'})
+ parser:parse()
+ collectgarbage()
+ x:root()
+ ]])
+ assert_alive()
+ end)
+
it('can move between siblings', function()
insert([[
int main(int x, int y, int z) {
@@ -26,7 +52,7 @@ describe('treesitter node API', function()
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
root = tree:root()
- lang = vim.treesitter.inspect_language('c')
+ lang = vim.treesitter.language.inspect('c')
function node_text(node)
return query.get_node_text(node, 0)
diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua
index f006ad4539..6f386115ae 100644
--- a/test/functional/treesitter/parser_spec.lua
+++ b/test/functional/treesitter/parser_spec.lua
@@ -1,17 +1,21 @@
local helpers = require('test.functional.helpers')(after_each)
local clear = helpers.clear
+local dedent = helpers.dedent
local eq = helpers.eq
local insert = helpers.insert
local exec_lua = helpers.exec_lua
+local pcall_err = helpers.pcall_err
local feed = helpers.feed
local is_os = helpers.is_os
-local skip = helpers.skip
-
-before_each(clear)
describe('treesitter parser API', function()
- clear()
+ before_each(function()
+ clear()
+ exec_lua[[
+ vim.g.__ts_debug = 1
+ ]]
+ end)
it('parses buffer', function()
insert([[
@@ -23,7 +27,7 @@ describe('treesitter parser API', function()
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
root = tree:root()
- lang = vim.treesitter.inspect_language('c')
+ lang = vim.treesitter.language.inspect('c')
]])
eq("<tree>", exec_lua("return tostring(tree)"))
@@ -124,6 +128,18 @@ void ui_refresh(void)
}, res)
end)
+ it('does not get parser for empty filetype', function()
+ insert(test_text);
+
+ eq('.../treesitter.lua:0: There is no parser available for buffer 1 and one'
+ .. ' could not be created because lang could not be determined. Either'
+ .. ' pass lang or set the buffer filetype',
+ pcall_err(exec_lua, 'vim.treesitter.get_parser(0)'))
+
+ -- Must provide language for buffers with an empty filetype
+ exec_lua("vim.treesitter.get_parser(0, 'c')")
+ end)
+
it('allows to get a child by field', function()
insert(test_text);
@@ -159,7 +175,7 @@ void ui_refresh(void)
it("supports runtime queries", function()
local ret = exec_lua [[
- return require"vim.treesitter.query".get_query("c", "highlights").captures[1]
+ return vim.treesitter.query.get("c", "highlights").captures[1]
]]
eq('variable', ret)
@@ -170,11 +186,11 @@ void ui_refresh(void)
local function q(n)
return exec_lua ([[
local query, n = ...
- local before = vim.loop.hrtime()
+ local before = vim.uv.hrtime()
for i=1,n,1 do
- cquery = vim.treesitter.parse_query("c", ...)
+ cquery = vim.treesitter.query.parse("c", ...)
end
- local after = vim.loop.hrtime()
+ local after = vim.uv.hrtime()
return after - before
]], long_query, n)
end
@@ -182,15 +198,16 @@ void ui_refresh(void)
local firstrun = q(1)
local manyruns = q(100)
- -- First run should be at least 4x slower.
- assert(400 * manyruns < firstrun, ('firstrun: %d ms, manyruns: %d ms'):format(firstrun / 1000, manyruns / 1000))
+ -- First run should be at least 200x slower than an 100 subsequent runs.
+ local factor = is_os('win') and 100 or 200
+ assert(factor * manyruns < firstrun, ('firstrun: %f ms, manyruns: %f ms'):format(firstrun / 1e6, manyruns / 1e6))
end)
it('support query and iter by capture', function()
insert(test_text)
local res = exec_lua([[
- cquery = vim.treesitter.parse_query("c", ...)
+ cquery = vim.treesitter.query.parse("c", ...)
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
res = {}
@@ -219,7 +236,7 @@ void ui_refresh(void)
insert(test_text)
local res = exec_lua([[
- cquery = vim.treesitter.parse_query("c", ...)
+ cquery = vim.treesitter.query.parse("c", ...)
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
res = {}
@@ -263,13 +280,13 @@ void ui_refresh(void)
eq('void', res2)
end)
- it('support getting text where start of node is past EOF', function()
+ it('support getting text where start of node is one past EOF', function()
local text = [[
def run
a = <<~E
end]]
insert(text)
- local result = exec_lua([[
+ eq('', exec_lua[[
local fake_node = {}
function fake_node:start()
return 3, 0, 23
@@ -277,9 +294,14 @@ end]]
function fake_node:end_()
return 3, 0, 23
end
- return vim.treesitter.get_node_text(fake_node, 0) == nil
+ function fake_node:range(bytes)
+ if bytes then
+ return 3, 0, 23, 3, 0, 23
+ end
+ return 3, 0, 3, 0
+ end
+ return vim.treesitter.get_node_text(fake_node, 0)
]])
- eq(true, result)
end)
it('support getting empty text if node range is zero width', function()
@@ -296,6 +318,9 @@ end]]
function fake_node:end_()
return 1, 0, 7
end
+ function fake_node:range()
+ return 1, 0, 1, 0
+ end
return vim.treesitter.get_node_text(fake_node, 0) == ''
]])
eq(true, result)
@@ -305,7 +330,7 @@ end]]
insert('char* astring = "\\n"; (1 + 1) * 2 != 2;')
local res = exec_lua([[
- cquery = vim.treesitter.parse_query("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'..
+ cquery = vim.treesitter.query.parse("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'..
'([_] @times (#vim-match? @times "^\\\\*$"))'..
'([_] @paren (#vim-match? @paren "^\\\\($"))'..
'([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'..
@@ -355,7 +380,7 @@ end]]
]])
exec_lua([[
function get_query_result(query_text)
- cquery = vim.treesitter.parse_query("c", query_text)
+ cquery = vim.treesitter.query.parse("c", query_text)
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
res = {}
@@ -395,7 +420,7 @@ end]]
insert('char* astring = "Hello World!";')
local res = exec_lua([[
- cquery = vim.treesitter.parse_query("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))')
+ cquery = vim.treesitter.query.parse("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))')
parser = vim.treesitter.get_parser(0, "c")
tree = parser:parse()[1]
res = {}
@@ -428,7 +453,7 @@ end]]
local custom_query = "((identifier) @main (#is-main? @main))"
local res = exec_lua([[
- local query = require"vim.treesitter.query"
+ local query = vim.treesitter.query
local function is_main(match, pattern, bufnr, predicate)
local node = match[ predicate[2] ]
@@ -440,7 +465,7 @@ end]]
query.add_predicate("is-main?", is_main)
- local query = query.parse_query("c", ...)
+ local query = query.parse("c", ...)
local nodes = {}
for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
@@ -453,7 +478,7 @@ end]]
eq({{0, 4, 0, 8}}, res)
local res_list = exec_lua[[
- local query = require'vim.treesitter.query'
+ local query = vim.treesitter.query
local list = query.list_predicates()
@@ -462,10 +487,9 @@ end]]
return list
]]
- eq({ 'any-of?', 'contains?', 'eq?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
+ eq({ 'any-of?', 'contains?', 'eq?', 'has-ancestor?', 'has-parent?', 'is-main?', 'lua-match?', 'match?', 'vim-match?' }, res_list)
end)
-
it('allows to set simple ranges', function()
insert(test_text)
@@ -482,22 +506,12 @@ end]]
local root = parser:parse()[1]:root()
parser:set_included_regions({{root:child(0)}})
parser:invalidate()
- return { parser:parse()[1]:root():range() }
+ return { parser:parse(true)[1]:root():range() }
]]
eq({0, 0, 18, 1}, res2)
- local range = exec_lua [[
- local res = {}
- for _, region in ipairs(parser:included_regions()) do
- for _, node in ipairs(region) do
- table.insert(res, {node:range()})
- end
- end
- return res
- ]]
-
- eq(range, { { 0, 0, 18, 1 } })
+ eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]])
local range_tbl = exec_lua [[
parser:set_included_regions { { { 0, 0, 17, 1 } } }
@@ -507,12 +521,13 @@ end]]
eq(range_tbl, { { { 0, 0, 0, 17, 1, 508 } } })
end)
+
it("allows to set complex ranges", function()
insert(test_text)
local res = exec_lua [[
parser = vim.treesitter.get_parser(0, "c")
- query = vim.treesitter.parse_query("c", "(declaration) @decl")
+ query = vim.treesitter.query.parse("c", "(declaration) @decl")
local nodes = {}
for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do
@@ -521,7 +536,7 @@ end]]
parser:set_included_regions({nodes})
- local root = parser:parse()[1]:root()
+ local root = parser:parse(true)[1]:root()
local res = {}
for i=0,(root:named_child_count() - 1) do
@@ -560,7 +575,7 @@ end]]
local parser = vim.treesitter.get_string_parser(str, "c")
local nodes = {}
- local query = vim.treesitter.parse_query("c", '((identifier) @id (eq? @id "foo"))')
+ local query = vim.treesitter.query.parse("c", '((identifier) @id (eq? @id "foo"))')
for _, node in query:iter_captures(parser:parse()[1]:root(), str) do
table.insert(nodes, { node:range() })
@@ -582,7 +597,7 @@ end]]
local parser = vim.treesitter.get_string_parser(str, "c")
local nodes = {}
- local query = vim.treesitter.parse_query("c", '((identifier) @foo)')
+ local query = vim.treesitter.query.parse("c", '((identifier) @foo)')
local first_child = parser:parse()[1]:root():child(1)
for _, node in query:iter_captures(first_child, str) do
@@ -606,8 +621,8 @@ end]]
before_each(function()
insert([[
int x = INT_MAX;
-#define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
-#define READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+#define READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+#define READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
#define VALUE 123
#define VALUE1 123
#define VALUE2 123
@@ -619,7 +634,8 @@ int x = INT_MAX;
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
injections = {
- c = "(preproc_def (preproc_arg) @c) (preproc_function_def value: (preproc_arg) @c)"}})
+ c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}})
+ parser:parse(true)
]])
eq("table", exec_lua("return type(parser:children().c)"))
@@ -629,8 +645,19 @@ int x = INT_MAX;
{3, 14, 3, 17}, -- VALUE 123
{4, 15, 4, 18}, -- VALUE1 123
{5, 15, 5, 18}, -- VALUE2 123
- {1, 26, 1, 65}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
- {2, 29, 2, 68} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+ {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
+ }, get_ranges())
+
+ helpers.feed('ggo<esc>')
+ eq(5, exec_lua("return #parser:children().c:trees()"))
+ eq({
+ {0, 0, 8, 0}, -- root tree
+ {4, 14, 4, 17}, -- VALUE 123
+ {5, 15, 5, 18}, -- VALUE1 123
+ {6, 15, 6, 18}, -- VALUE2 123
+ {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ {3, 29, 3, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
end)
end)
@@ -640,7 +667,8 @@ int x = INT_MAX;
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
injections = {
- c = "(preproc_def (preproc_arg) @c @combined) (preproc_function_def value: (preproc_arg) @c @combined)"}})
+ c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))'}})
+ parser:parse(true)
]])
eq("table", exec_lua("return type(parser:children().c)"))
@@ -650,52 +678,54 @@ int x = INT_MAX;
{3, 14, 5, 18}, -- VALUE 123
-- VALUE1 123
-- VALUE2 123
- {1, 26, 2, 68} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
- -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+ {1, 26, 2, 66} -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
- end)
- end)
-
- describe("when providing parsing information through a directive", function()
- it("should inject a language", function()
- exec_lua([=[
- vim.treesitter.add_directive("inject-clang!", function(match, _, _, pred, metadata)
- metadata.language = "c"
- metadata.combined = true
- metadata.content = pred[2]
- end)
-
- parser = vim.treesitter.get_parser(0, "c", {
- injections = {
- c = "(preproc_def ((preproc_arg) @_c (#inject-clang! @_c)))" ..
- "(preproc_function_def value: ((preproc_arg) @_a (#inject-clang! @_a)))"}})
- ]=])
+ helpers.feed('ggo<esc>')
eq("table", exec_lua("return type(parser:children().c)"))
eq(2, exec_lua("return #parser:children().c:trees()"))
eq({
- {0, 0, 7, 0}, -- root tree
- {3, 14, 5, 18}, -- VALUE 123
+ {0, 0, 8, 0}, -- root tree
+ {4, 14, 6, 18}, -- VALUE 123
-- VALUE1 123
-- VALUE2 123
- {1, 26, 2, 68} -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
- -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+ {2, 26, 3, 66} -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
end)
+ end)
- it("should not inject bad languages", function()
- skip(is_os('win'))
- exec_lua([=[
- vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata)
- metadata.language = "{"
- metadata.combined = true
- metadata.content = pred[2]
- end)
-
+ describe("when using injection.self", function()
+ it("should inject the source language", function()
+ exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
injections = {
- c = "(preproc_function_def value: ((preproc_arg) @_a (#inject-bad! @_a)))"}})
- ]=])
+ c = '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))'}})
+ parser:parse(true)
+ ]])
+
+ eq("table", exec_lua("return type(parser:children().c)"))
+ eq(5, exec_lua("return #parser:children().c:trees()"))
+ eq({
+ {0, 0, 7, 0}, -- root tree
+ {3, 14, 3, 17}, -- VALUE 123
+ {4, 15, 4, 18}, -- VALUE1 123
+ {5, 15, 5, 18}, -- VALUE2 123
+ {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
+ }, get_ranges())
+
+ helpers.feed('ggo<esc>')
+ eq(5, exec_lua("return #parser:children().c:trees()"))
+ eq({
+ {0, 0, 8, 0}, -- root tree
+ {4, 14, 4, 17}, -- VALUE 123
+ {5, 15, 5, 18}, -- VALUE1 123
+ {6, 15, 6, 18}, -- VALUE2 123
+ {2, 26, 2, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ {3, 29, 3, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
+ }, get_ranges())
end)
end)
@@ -704,22 +734,23 @@ int x = INT_MAX;
exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
injections = {
- c = "(preproc_def ((preproc_arg) @c (#offset! @c 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @c)"}})
+ c = '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'}})
+ parser:parse(true)
]])
eq("table", exec_lua("return type(parser:children().c)"))
eq({
{0, 0, 7, 0}, -- root tree
- {3, 15, 3, 16}, -- VALUE 123
- {4, 16, 4, 17}, -- VALUE1 123
- {5, 16, 5, 17}, -- VALUE2 123
- {1, 26, 1, 65}, -- READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y))
- {2, 29, 2, 68} -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y))
+ {3, 16, 3, 16}, -- VALUE 123
+ {4, 17, 4, 17}, -- VALUE1 123
+ {5, 17, 5, 17}, -- VALUE2 123
+ {1, 26, 1, 63}, -- READ_STRING(x, y) (char *)read_string((x), (size_t)(y))
+ {2, 29, 2, 66} -- READ_STRING_OK(x, y) (char *)read_string((x), (size_t)(y))
}, get_ranges())
end)
it("should list all directives", function()
local res_list = exec_lua[[
- local query = require'vim.treesitter.query'
+ local query = vim.treesitter.query
local list = query.list_directives()
@@ -728,7 +759,7 @@ int x = INT_MAX;
return list
]]
- eq({ 'offset!', 'set!' }, res_list)
+ eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list)
end)
end)
end)
@@ -744,7 +775,8 @@ int x = INT_MAX;
it("should return the correct language tree", function()
local result = exec_lua([[
parser = vim.treesitter.get_parser(0, "c", {
- injections = { c = "(preproc_def (preproc_arg) @c)"}})
+ injections = { c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))'}})
+ parser:parse(true)
local sub_tree = parser:language_for_range({1, 18, 1, 19})
@@ -765,7 +797,7 @@ int x = INT_MAX;
local result = exec_lua([[
local result
- query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! "key" "value"))')
+ query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! "key" "value"))')
parser = vim.treesitter.get_parser(0, "c")
for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
@@ -785,10 +817,10 @@ int x = INT_MAX;
]])
local result = exec_lua([[
- local query = require("vim.treesitter.query")
+ local query = vim.treesitter.query
local value
- query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value"))')
+ query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value"))')
parser = vim.treesitter.get_parser(0, "c")
for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
@@ -807,10 +839,10 @@ int x = INT_MAX;
]])
local result = exec_lua([[
- local query = require("vim.treesitter.query")
+ local query = vim.treesitter.query
local result
- query = vim.treesitter.parse_query("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))')
+ query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))')
parser = vim.treesitter.get_parser(0, "c")
for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0) do
@@ -829,4 +861,355 @@ int x = INT_MAX;
end)
end)
end)
+
+ it('tracks the root range properly (#22911)', function()
+ insert([[
+ int main() {
+ int x = 3;
+ }]])
+
+ local query0 = [[
+ (declaration) @declaration
+ (function_definition) @function
+ ]]
+
+ exec_lua([[
+ vim.treesitter.start(0, 'c')
+ ]])
+
+ local function run_query()
+ return exec_lua([[
+ local query = vim.treesitter.query.parse("c", ...)
+ parser = vim.treesitter.get_parser()
+ tree = parser:parse()[1]
+ res = {}
+ for id, node in query:iter_captures(tree:root()) do
+ table.insert(res, {query.captures[id], node:range()})
+ end
+ return res
+ ]], query0)
+ end
+
+ eq({
+ { 'function', 0, 0, 2, 1 },
+ { 'declaration', 1, 2, 1, 12 }
+ }, run_query())
+
+ helpers.command'normal ggO'
+ insert('int a;')
+
+ eq({
+ { 'declaration', 0, 0, 0, 6 },
+ { 'function', 1, 0, 3, 1 },
+ { 'declaration', 2, 2, 2, 12 }
+ }, run_query())
+
+ end)
+
+ it('handles ranges when source is a multiline string (#20419)', function()
+ local source = [==[
+ vim.cmd[[
+ set number
+ set cmdheight=2
+ set lastsatus=2
+ ]]
+
+ set query = [[;; query
+ ((function_call
+ name: [
+ (identifier) @_cdef_identifier
+ (_ _ (identifier) @_cdef_identifier)
+ ]
+ arguments: (arguments (string content: _ @injection.content)))
+ (#set! injection.language "c")
+ (#eq? @_cdef_identifier "cdef"))
+ ]]
+ ]==]
+
+ local r = exec_lua([[
+ local parser = vim.treesitter.get_string_parser(..., 'lua')
+ parser:parse(true)
+ local ranges = {}
+ parser:for_each_tree(function(tstree, tree)
+ ranges[tree:lang()] = { tstree:root():range(true) }
+ end)
+ return ranges
+ ]], source)
+
+ eq({
+ lua = { 0, 6, 6, 16, 4, 438 },
+ query = { 6, 20, 113, 15, 6, 431 },
+ vim = { 1, 0, 16, 4, 6, 89 }
+ }, r)
+
+ -- The above ranges are provided directly from treesitter, however query directives may mutate
+ -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure
+ -- add_bytes() produces the same result.
+
+ local rb = exec_lua([[
+ local r, source = ...
+ local add_bytes = require('vim.treesitter._range').add_bytes
+ for lang, range in pairs(r) do
+ r[lang] = {range[1], range[2], range[4], range[5]}
+ r[lang] = add_bytes(source, r[lang])
+ end
+ return r
+ ]], r, source)
+
+ eq(rb, r)
+
+ end)
+
+ it("does not produce empty injection ranges (#23409)", function()
+ insert [[
+ Examples: >lua
+ local a = {}
+<
+ ]]
+
+ -- This is not a valid injection since (code) has children and include-children is not set
+ exec_lua [[
+ parser1 = require('vim.treesitter.languagetree').new(0, "vimdoc", {
+ injections = {
+ vimdoc = "((codeblock (language) @injection.language (code) @injection.content))"
+ }
+ })
+ parser1:parse(true)
+ ]]
+
+ eq(0, exec_lua("return #vim.tbl_keys(parser1:children())"))
+
+ exec_lua [[
+ parser2 = require('vim.treesitter.languagetree').new(0, "vimdoc", {
+ injections = {
+ vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
+ }
+ })
+ parser2:parse(true)
+ ]]
+
+ eq(1, exec_lua("return #vim.tbl_keys(parser2:children())"))
+ eq( { { { 1, 0, 21, 2, 0, 42 } } }, exec_lua("return parser2:children().lua:included_regions()"))
+
+ end)
+
+ it("parsers injections incrementally", function()
+ insert(dedent[[
+ >lua
+ local a = {}
+ <
+
+ >lua
+ local b = {}
+ <
+
+ >lua
+ local c = {}
+ <
+
+ >lua
+ local d = {}
+ <
+
+ >lua
+ local e = {}
+ <
+
+ >lua
+ local f = {}
+ <
+
+ >lua
+ local g = {}
+ <
+ ]])
+
+ exec_lua [[
+ parser = require('vim.treesitter.languagetree').new(0, "vimdoc", {
+ injections = {
+ vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
+ }
+ })
+ ]]
+
+ --- Do not parse injections by default
+ eq(0, exec_lua [[
+ parser:parse()
+ return #vim.tbl_keys(parser:children())
+ ]])
+
+ --- Only parse injections between lines 0, 2
+ eq(1, exec_lua [[
+ parser:parse({0, 2})
+ return #parser:children().lua:trees()
+ ]])
+
+ eq(2, exec_lua [[
+ parser:parse({2, 6})
+ return #parser:children().lua:trees()
+ ]])
+
+ eq(7, exec_lua [[
+ parser:parse(true)
+ return #parser:children().lua:trees()
+ ]])
+ end)
+
+ it('fails to load queries', function()
+ local function test(exp, cquery)
+ eq(exp, pcall_err(exec_lua, "vim.treesitter.query.parse('c', ...)", cquery))
+ end
+
+ -- Invalid node type
+ test(
+ '.../query.lua:0: Query error at 1:2. Invalid node type "dentifier":\n'..
+ '(dentifier) @variable\n'..
+ ' ^',
+ '(dentifier) @variable')
+
+ -- Impossible pattern
+ test(
+ '.../query.lua:0: Query error at 1:13. Impossible pattern:\n'..
+ '(identifier (identifier) @variable)\n'..
+ ' ^',
+ '(identifier (identifier) @variable)')
+
+ -- Invalid syntax
+ test(
+ '.../query.lua:0: Query error at 1:13. Invalid syntax:\n'..
+ '(identifier @variable\n'..
+ ' ^',
+ '(identifier @variable')
+
+ -- Invalid field name
+ test(
+ '.../query.lua:0: Query error at 1:15. Invalid field name "invalid_field":\n'..
+ '((identifier) invalid_field: (identifier))\n'..
+ ' ^',
+ '((identifier) invalid_field: (identifier))')
+
+ -- Invalid capture name
+ test(
+ '.../query.lua:0: Query error at 3:2. Invalid capture name "ok.capture":\n'..
+ '@ok.capture\n'..
+ ' ^',
+ '((identifier) @id \n(#eq? @id\n@ok.capture\n))')
+ end)
+
+ describe('is_valid()', function()
+ before_each(function()
+ insert(dedent[[
+ Treesitter integration *treesitter*
+
+ Nvim integrates the `tree-sitter` library for incremental parsing of buffers:
+ https://tree-sitter.github.io/tree-sitter/
+
+ ]])
+
+ feed(':set ft=help<cr>')
+
+ exec_lua [[
+ vim.treesitter.get_parser(0, "vimdoc", {
+ injections = {
+ vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))"
+ }
+ })
+ ]]
+ end)
+
+ it('is valid excluding, invalid including children initially', function()
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is fully valid after a full parse', function()
+ exec_lua('vim.treesitter.get_parser():parse(true)')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is fully valid after a parsing a range on parsed tree', function()
+ exec_lua('vim.treesitter.get_parser():parse({5, 7})')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ describe('when adding content with injections', function()
+ before_each(function()
+ feed('G')
+ insert(dedent[[
+ >lua
+ local a = {}
+ <
+
+ ]])
+ end)
+
+ it('is fully invalid after changes', function()
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is valid excluding, invalid including children after a rangeless parse', function()
+ exec_lua('vim.treesitter.get_parser():parse()')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is fully valid after a range parse that leads to parsing not parsed injections', function()
+ exec_lua('vim.treesitter.get_parser():parse({5, 7})')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is valid excluding, invalid including children after a range parse that does not lead to parsing not parsed injections', function()
+ exec_lua('vim.treesitter.get_parser():parse({2, 4})')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+ end)
+
+ describe('when removing content with injections', function()
+ before_each(function()
+ feed('G')
+ insert(dedent[[
+ >lua
+ local a = {}
+ <
+
+ >lua
+ local a = {}
+ <
+
+ ]])
+
+ exec_lua('vim.treesitter.get_parser():parse(true)')
+
+ feed('Gd3k')
+ end)
+
+ it('is fully invalid after changes', function()
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is valid excluding, invalid including children after a rangeless parse', function()
+ exec_lua('vim.treesitter.get_parser():parse()')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is fully valid after a range parse that leads to parsing modified child tree', function()
+ exec_lua('vim.treesitter.get_parser():parse({5, 7})')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+
+ it('is valid excluding, invalid including children after a range parse that does not lead to parsing modified child tree', function()
+ exec_lua('vim.treesitter.get_parser():parse({2, 4})')
+ eq(true, exec_lua('return vim.treesitter.get_parser():is_valid(true)'))
+ eq(false, exec_lua('return vim.treesitter.get_parser():is_valid()'))
+ end)
+ end)
+ end)
end)
diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua
index 7f5a864c3d..9c07959098 100644
--- a/test/functional/treesitter/utils_spec.lua
+++ b/test/functional/treesitter/utils_spec.lua
@@ -28,4 +28,21 @@ describe('treesitter utils', function()
eq(true, exec_lua('return vim.treesitter.is_ancestor(ancestor, child)'))
eq(false, exec_lua('return vim.treesitter.is_ancestor(child, ancestor)'))
end)
+
+ it('can detect if a position is contained in a node', function()
+ exec_lua([[
+ node = {
+ range = function()
+ return 0, 4, 0, 8
+ end,
+ }
+ ]])
+
+ eq(false, exec_lua('return vim.treesitter.is_in_node_range(node, 0, 3)'))
+ for i = 4, 7 do
+ eq(true, exec_lua('return vim.treesitter.is_in_node_range(node, 0, ...)', i))
+ end
+ -- End column exclusive
+ eq(false, exec_lua('return vim.treesitter.is_in_node_range(node, 0, 8)'))
+ end)
end)
diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua
index 46bfae8de2..81e514c9aa 100644
--- a/test/functional/ui/bufhl_spec.lua
+++ b/test/functional/ui/bufhl_spec.lua
@@ -303,7 +303,7 @@ describe('Buffer highlighting', function()
{1:~ }|
{1:~ }|
{1:~ }|
- 2 change3; before #3 {MATCH:.*}|
+ 2 changes; before #3 {MATCH:.*}|
]]}
command('undo')
@@ -751,8 +751,8 @@ describe('Buffer highlighting', function()
it('validates contents', function()
-- this used to leak memory
- eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
- eq('Chunk is not an array', pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
+ eq("Invalid 'chunk': expected Array, got String", pcall_err(set_virtual_text, id1, 0, {"texty"}, {}))
+ eq("Invalid 'chunk': expected Array, got String", pcall_err(set_virtual_text, id1, 0, {{"very"}, "texty"}, {}))
end)
it('can be retrieved', function()
@@ -762,10 +762,9 @@ describe('Buffer highlighting', function()
local s1 = {{'Köttbullar', 'Comment'}, {'Kräuterbutter'}}
local s2 = {{'こんにちは', 'Comment'}}
- -- TODO: only a virtual text from the same ns currently overrides
- -- an existing virtual text. We might add a prioritation system.
set_virtual_text(id1, 0, s1, {})
eq({{1, 0, 0, {
+ ns_id = 1,
priority = 0,
virt_text = s1,
-- other details
@@ -774,10 +773,10 @@ describe('Buffer highlighting', function()
virt_text_hide = false,
}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true}))
- -- TODO: is this really valid? shouldn't the max be line_count()-1?
local lastline = line_count()
set_virtual_text(id1, line_count(), s2, {})
eq({{3, lastline, 0, {
+ ns_id = 1,
priority = 0,
virt_text = s2,
-- other details
diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua
index eb5de693bd..e4766103c2 100644
--- a/test/functional/ui/cmdline_highlight_spec.lua
+++ b/test/functional/ui/cmdline_highlight_spec.lua
@@ -7,6 +7,7 @@ local clear = helpers.clear
local meths = helpers.meths
local funcs = helpers.funcs
local source = helpers.source
+local exec_capture = helpers.exec_capture
local dedent = helpers.dedent
local command = helpers.command
local curbufmeths = helpers.curbufmeths
@@ -177,7 +178,7 @@ end
describe('Command-line coloring', function()
it('works', function()
set_color_cb('RainBowParens')
- meths.set_option('more', false)
+ meths.set_option_value('more', false, {})
start_prompt()
screen:expect([[
|
@@ -362,7 +363,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.exec('messages', true))
+ eq('', exec_capture('messages'))
end)
it('silences :echon', function()
set_color_cb('Echoning')
@@ -377,7 +378,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.exec('messages', true))
+ eq('', exec_capture('messages'))
end)
it('silences :echomsg', function()
set_color_cb('Echomsging')
@@ -392,7 +393,7 @@ describe('Command-line coloring', function()
{EOB:~ }|
:e^ |
]])
- eq('', meths.exec('messages', true))
+ eq('', exec_capture('messages'))
end)
it('does the right thing when throwing', function()
set_color_cb('Throwing')
@@ -576,10 +577,10 @@ describe('Command-line coloring', function()
|
{EOB:~ }|
{EOB:~ }|
+ {EOB:~ }|
{MSEP: }|
:+ |
{ERR:E5404: Chunk 1 end 3 not in range (1, 2]}|
- |
:++^ |
]])
end)
@@ -853,12 +854,12 @@ describe('Ex commands coloring', function()
:# |
{ERR:Error detected while processing :} |
{ERR:E605: Exception not caught: 42} |
- {ERR:E749: empty buffer} |
+ {ERR:E749: Empty buffer} |
{PE:Press ENTER or type command to continue}^ |
]])
feed('<CR>')
- eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: empty buffer',
- meths.exec('messages', true))
+ eq('Error detected while processing :\nE605: Exception not caught: 42\nE749: Empty buffer',
+ exec_capture('messages'))
end)
it('errors out when failing to get callback', function()
meths.set_var('Nvim_color_cmdline', 42)
diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua
index 1c9ac7f7ba..188b9ee87b 100644
--- a/test/functional/ui/cmdline_spec.lua
+++ b/test/functional/ui/cmdline_spec.lua
@@ -24,6 +24,7 @@ local function new_screen(opt)
[7] = {bold = true, foreground = Screen.colors.Brown},
[8] = {background = Screen.colors.LightGrey},
[9] = {bold = true},
+ [10] = {background = Screen.colors.Yellow1};
})
return screen
end
@@ -316,7 +317,7 @@ local function test_cmdline(linegrid)
screen:expect{grid=[[
|
{2:[No Name] }|
- {1::}make^ |
+ {1::}mak^e |
{3:[Command Line] }|
|
]]}
@@ -326,7 +327,7 @@ local function test_cmdline(linegrid)
screen:expect{grid=[[
|
{2:[No Name] }|
- {1::}make^ |
+ {1::}mak^e |
{3:[Command Line] }|
|
]], cmdline={nil, {
@@ -339,7 +340,7 @@ local function test_cmdline(linegrid)
screen:expect{grid=[[
|
{2:[No Name] }|
- {1::}make^ |
+ {1::}mak^e |
{3:[Command Line] }|
|
]], cmdline={nil, {
@@ -352,7 +353,7 @@ local function test_cmdline(linegrid)
screen:expect{grid=[[
|
{2:[No Name] }|
- {1::}make^ |
+ {1::}mak^e |
{3:[Command Line] }|
|
]]}
@@ -771,6 +772,84 @@ describe('cmdline redraw', function()
:^abc |
]])
end)
+
+ it('with rightleftcmd', function()
+ command('set rightleft rightleftcmd=search shortmess+=s')
+ meths.buf_set_lines(0, 0, -1, true, {"let's rock!"})
+ screen:expect{grid=[[
+ !kcor s'te^l|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ |
+ ]]}
+
+ feed '/'
+ screen:expect{grid=[[
+ !kcor s'tel|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^ /|
+ ]]}
+
+ feed "let's"
+ -- note: cursor looks off but looks alright in real use
+ -- when rendered as a block so it touches the end of the text
+ screen:expect{grid=[[
+ !kcor {2:s'tel}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^ s'tel/|
+ ]]}
+
+ -- cursor movement
+ feed "<space>"
+ screen:expect{grid=[[
+ !kcor{2: s'tel}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^ s'tel/|
+ ]]}
+
+ feed "rock"
+ screen:expect{grid=[[
+ !{2:kcor s'tel}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^ kcor s'tel/|
+ ]]}
+
+ feed "<right>"
+ screen:expect{grid=[[
+ !{2:kcor s'tel}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^kcor s'tel/|
+ ]]}
+
+ feed "<left>"
+ screen:expect{grid=[[
+ !{2:kcor s'tel}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ^ kcor s'tel/|
+ ]]}
+
+ feed "<cr>"
+ screen:expect{grid=[[
+ !{10:kcor s'te^l}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ kcor s'tel/ |
+ ]]}
+ end)
end)
describe('statusline is redrawn on entering cmdline', function()
@@ -944,15 +1023,64 @@ describe('statusline is redrawn on entering cmdline', function()
end)
end)
+it('tabline is not redrawn in Ex mode #24122', function()
+ clear()
+ local screen = Screen.new(60, 5)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [1] = {bold = true, reverse = true}, -- MsgSeparator
+ [2] = {reverse = true}, -- TabLineFill
+ })
+ screen:attach()
+
+ exec([[
+ set showtabline=2
+ set tabline=%!MyTabLine()
+
+ function! MyTabLine()
+
+ return "foo"
+ endfunction
+ ]])
+
+ feed('gQ')
+ screen:expect{grid=[[
+ {2:foo }|
+ |
+ {1: }|
+ Entering Ex mode. Type "visual" to go to Normal mode. |
+ :^ |
+ ]]}
+
+ feed('echo 1<CR>')
+ screen:expect{grid=[[
+ {1: }|
+ Entering Ex mode. Type "visual" to go to Normal mode. |
+ :echo 1 |
+ 1 |
+ :^ |
+ ]]}
+end)
+
describe("cmdline height", function()
+ before_each(clear)
+
it("does not crash resized screen #14263", function()
- clear()
local screen = Screen.new(25, 10)
screen:attach()
command('set cmdheight=9999')
screen:try_resize(25, 5)
assert_alive()
end)
+
+ it('unchanged when restoring window sizes with global statusline', function()
+ command('set cmdheight=2 laststatus=2')
+ feed('q:')
+ command('set cmdheight=1 laststatus=3 | quit')
+ -- Available lines changed, so closing cmdwin should skip restoring window sizes, leaving the
+ -- cmdheight unchanged.
+ eq(1, eval('&cmdheight'))
+ end)
end)
describe('cmdheight=0', function()
@@ -972,6 +1100,26 @@ describe('cmdheight=0', function()
screen:attach()
end)
+ it("with redrawdebug=invalid resize -1", function()
+ command("set redrawdebug=invalid cmdheight=0 noruler laststatus=0")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ feed(":resize -1<CR>")
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ assert_alive()
+ end)
+
it("with cmdheight=1 noruler laststatus=2", function()
command("set cmdheight=1 noruler laststatus=2")
screen:expect{grid=[[
@@ -1226,6 +1374,7 @@ describe('cmdheight=0', function()
it('with multigrid', function()
clear{args={'--cmd', 'set cmdheight=0'}}
screen:attach{ext_multigrid=true}
+ meths.buf_set_lines(0, 0, -1, true, {'p'})
screen:expect{grid=[[
## grid 1
[2:-------------------------]|
@@ -1234,14 +1383,14 @@ describe('cmdheight=0', function()
[2:-------------------------]|
[2:-------------------------]|
## grid 2
- ^ |
+ ^p |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
## grid 3
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
feed '/p'
@@ -1253,7 +1402,7 @@ describe('cmdheight=0', function()
[2:-------------------------]|
[3:-------------------------]|
## grid 2
- |
+ {6:p} |
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1261,7 +1410,7 @@ describe('cmdheight=0', function()
## grid 3
/p^ |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
end)
@@ -1378,7 +1527,21 @@ describe('cmdheight=0', function()
|
]])
command('set cmdheight=0')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ ]]}
command('resize -1')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] }|
+ |
+ ]]}
command('resize +1')
screen:expect([[
^ |
diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua
index e261f0dfab..05057ca080 100644
--- a/test/functional/ui/cursor_spec.lua
+++ b/test/functional/ui/cursor_spec.lua
@@ -212,10 +212,10 @@ describe('ui/cursor', function()
if m.blinkwait then m.blinkwait = 700 end
end
if m.hl_id then
- m.hl_id = 60
+ m.hl_id = 64
m.attr = {background = Screen.colors.DarkGray}
end
- if m.id_lm then m.id_lm = 62 end
+ if m.id_lm then m.id_lm = 67 end
end
-- Assert the new expectation.
@@ -265,8 +265,8 @@ describe('ui/cursor', function()
}
-- Another cursor style.
- meths.set_option('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173'
- ..',ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42')
+ meths.set_option_value('guicursor', 'n-v-c:ver35-blinkwait171-blinkoff172-blinkon173'
+ ..',ve:hor35,o:ver50,i-ci:block,r-cr:hor90,sm:ver42', {})
screen:expect(function()
local named = {}
for _, m in ipairs(screen._mode_info) do
@@ -288,7 +288,7 @@ describe('ui/cursor', function()
end)
-- If there is no setting for guicursor, it becomes the default setting.
- meths.set_option('guicursor', 'n:ver35-blinkwait171-blinkoff172-blinkon173-Cursor/lCursor')
+ meths.set_option_value('guicursor', 'n:ver35-blinkwait171-blinkoff172-blinkon173-Cursor/lCursor', {})
screen:expect(function()
for _,m in ipairs(screen._mode_info) do
if m.name ~= 'normal' then
@@ -304,7 +304,7 @@ describe('ui/cursor', function()
end)
it("empty 'guicursor' sets cursor_shape=block in all modes", function()
- meths.set_option('guicursor', '')
+ meths.set_option_value('guicursor', '', {})
screen:expect(function()
-- Empty 'guicursor' sets enabled=false.
eq(false, screen._cursor_style_enabled)
diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua
index 489c33d8b1..e8fcfc46fc 100644
--- a/test/functional/ui/decorations_spec.lua
+++ b/test/functional/ui/decorations_spec.lua
@@ -8,7 +8,12 @@ local exec_lua = helpers.exec_lua
local exec = helpers.exec
local expect_events = helpers.expect_events
local meths = helpers.meths
+local funcs = helpers.funcs
+local curbufmeths = helpers.curbufmeths
local command = helpers.command
+local eq = helpers.eq
+local assert_alive = helpers.assert_alive
+local pcall_err = helpers.pcall_err
describe('decorations providers', function()
local screen
@@ -31,8 +36,10 @@ describe('decorations providers', function()
[12] = {foreground = tonumber('0x990000')};
[13] = {background = Screen.colors.LightBlue};
[14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue};
- [15] = {special = Screen.colors.Blue1, undercurl = true},
+ [15] = {special = Screen.colors.Blue, undercurl = true},
[16] = {special = Screen.colors.Red, undercurl = true},
+ [17] = {foreground = Screen.colors.Red},
+ [18] = {bold = true, foreground = Screen.colors.SeaGreen};
}
end)
@@ -47,15 +54,15 @@ describe('decorations providers', function()
local function setup_provider(code)
return exec_lua ([[
- local a = vim.api
- _G.ns1 = a.nvim_create_namespace "ns1"
+ local api = vim.api
+ _G.ns1 = api.nvim_create_namespace "ns1"
]] .. (code or [[
beamtrace = {}
local function on_do(kind, ...)
table.insert(beamtrace, {kind, ...})
end
]]) .. [[
- a.nvim_set_decoration_provider(_G.ns1, {
+ api.nvim_set_decoration_provider(_G.ns1, {
on_start = on_do; on_buf = on_do;
on_win = on_do; on_line = on_do;
on_end = on_do; _on_spell_nav = on_do;
@@ -75,10 +82,10 @@ describe('decorations providers', function()
-- rather than append, which used to spin in an infinite loop allocating
-- memory until nvim crashed/was killed.
setup_provider([[
- local ns2 = a.nvim_create_namespace "ns2"
- a.nvim_set_decoration_provider(ns2, {})
+ local ns2 = api.nvim_create_namespace "ns2"
+ api.nvim_set_decoration_provider(ns2, {})
]])
- helpers.assert_alive()
+ assert_alive()
end)
it('leave a trace', function()
@@ -98,7 +105,7 @@ describe('decorations providers', function()
]]}
check_trace {
{ "start", 4 };
- { "win", 1000, 1, 0, 8 };
+ { "win", 1000, 1, 0, 6 };
{ "line", 1000, 1, 0 };
{ "line", 1000, 1, 1 };
{ "line", 1000, 1, 2 };
@@ -122,8 +129,8 @@ describe('decorations providers', function()
]]}
check_trace {
{ "start", 5 };
- { "buf", 1 };
- { "win", 1000, 1, 0, 8 };
+ { "buf", 1, 5 };
+ { "win", 1000, 1, 0, 6 };
{ "line", 1000, 1, 6 };
{ "end", 5 };
}
@@ -132,12 +139,12 @@ describe('decorations providers', function()
it('can have single provider', function()
insert(mulholland)
setup_provider [[
- local hl = a.nvim_get_hl_id_by_name "ErrorMsg"
- local test_ns = a.nvim_create_namespace "mulholland"
+ local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = api.nvim_create_namespace "mulholland"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
- a.nvim_buf_set_extmark(buf, test_ns, line, line,
+ api.nvim_buf_set_extmark(buf, test_ns, line, line,
{ end_line = line, end_col = line+1,
hl_group = hl,
ephemeral = true
@@ -172,11 +179,11 @@ describe('decorations providers', function()
]]
setup_provider [[
- local ns = a.nvim_create_namespace "spell"
+ local ns = api.nvim_create_namespace "spell"
beamtrace = {}
local function on_do(kind, ...)
if kind == 'win' or kind == 'spell' then
- a.nvim_buf_set_extmark(0, ns, 0, 0, {
+ api.nvim_buf_set_extmark(0, ns, 0, 0, {
end_row = 2,
end_col = 23,
spell = true,
@@ -190,7 +197,7 @@ describe('decorations providers', function()
check_trace {
{ "start", 5 };
- { "win", 1000, 1, 0, 5 };
+ { "win", 1000, 1, 0, 3 };
{ "line", 1000, 1, 0 };
{ "line", 1000, 1, 1 };
{ "line", 1000, 1, 2 };
@@ -201,14 +208,14 @@ describe('decorations providers', function()
feed "gg0"
screen:expect{grid=[[
- ^I am well written text. |
- {15:i} am not capitalized. |
- I am a {16:speling} {16:mistakke}. |
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ ^I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]]}
feed "]s"
@@ -216,14 +223,14 @@ describe('decorations providers', function()
{ "spell", 1000, 1, 1, 0, 1, -1 };
}
screen:expect{grid=[[
- I am well written text. |
- {15:^i} am not capitalized. |
- I am a {16:speling} {16:mistakke}. |
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ I am well written text. |
+ {15:^i} am not capitalized. |
+ I am a {16:speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]]}
feed "]s"
@@ -231,43 +238,68 @@ describe('decorations providers', function()
{ "spell", 1000, 1, 2, 7, 2, -1 };
}
screen:expect{grid=[[
- I am well written text. |
- {15:i} am not capitalized. |
- I am a {16:^speling} {16:mistakke}. |
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:^speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]]}
- -- spell=false with lower priority doesn't disable spell
+ -- spell=false with higher priority does disable spell
local ns = meths.create_namespace "spell"
- local id = helpers.curbufmeths.set_extmark(ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false })
+ local id = curbufmeths.set_extmark(ns, 0, 0, { priority = 30, end_row = 2, end_col = 23, spell = false })
screen:expect{grid=[[
- I am well written text. |
- i am not capitalized. |
- I am a ^speling mistakke. |
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ I am well written text. |
+ i am not capitalized. |
+ I am a ^speling mistakke. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]]}
- -- spell=false with higher priority does disable spell
- helpers.curbufmeths.set_extmark(ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false })
+ feed "]s"
+ screen:expect{grid=[[
+ I am well written text. |
+ i am not capitalized. |
+ I am a ^speling mistakke. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {17:search hit BOTTOM, continuing at TOP} |
+ ]]}
+ command('echo ""')
+
+ -- spell=false with lower priority doesn't disable spell
+ curbufmeths.set_extmark(ns, 0, 0, { id = id, priority = 10, end_row = 2, end_col = 23, spell = false })
screen:expect{grid=[[
- I am well written text. |
- {15:i} am not capitalized. |
- I am a {16:^speling} {16:mistakke}. |
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:^speling} {16:mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed "]s"
+ screen:expect{grid=[[
+ I am well written text. |
+ {15:i} am not capitalized. |
+ I am a {16:speling} {16:^mistakke}. |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]]}
end)
@@ -330,12 +362,12 @@ describe('decorations providers', function()
]]}
exec_lua [[
- local a = vim.api
- local thewin = a.nvim_get_current_win()
- local ns2 = a.nvim_create_namespace 'ns2'
- a.nvim_set_decoration_provider (ns2, {
+ local api = vim.api
+ local thewin = api.nvim_get_current_win()
+ local ns2 = api.nvim_create_namespace 'ns2'
+ api.nvim_set_decoration_provider (ns2, {
on_win = function (_, win, buf)
- a.nvim_set_hl_ns_fast(win == thewin and _G.ns1 or ns2)
+ api.nvim_set_hl_ns_fast(win == thewin and _G.ns1 or ns2)
end;
})
]]
@@ -436,12 +468,12 @@ describe('decorations providers', function()
it('can have virtual text', function()
insert(mulholland)
setup_provider [[
- local hl = a.nvim_get_hl_id_by_name "ErrorMsg"
- local test_ns = a.nvim_create_namespace "mulholland"
+ local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = api.nvim_create_namespace "mulholland"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
- a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
virt_text = {{'+', 'ErrorMsg'}};
virt_text_pos='overlay';
ephemeral = true;
@@ -465,12 +497,12 @@ describe('decorations providers', function()
it('can have virtual text of the style: right_align', function()
insert(mulholland)
setup_provider [[
- local hl = a.nvim_get_hl_id_by_name "ErrorMsg"
- local test_ns = a.nvim_create_namespace "mulholland"
+ local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = api.nvim_create_namespace "mulholland"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
- a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
virt_text_pos='right_align';
ephemeral = true;
@@ -491,15 +523,81 @@ describe('decorations providers', function()
]]}
end)
+ it('virtual text works with wrapped lines', function()
+ insert(mulholland)
+ feed('ggJj3JjJ')
+ setup_provider [[
+ local hl = api.nvim_get_hl_id_by_name "ErrorMsg"
+ local test_ns = api.nvim_create_namespace "mulholland"
+ function on_do(event, ...)
+ if event == "line" then
+ local win, buf, line = ...
+ api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ virt_text = {{string.rep('/', line+1), 'ErrorMsg'}};
+ virt_text_pos='eol';
+ ephemeral = true;
+ })
+ api.nvim_buf_set_extmark(buf, test_ns, line, 6, {
+ virt_text = {{string.rep('*', line+1), 'ErrorMsg'}};
+ virt_text_pos='overlay';
+ ephemeral = true;
+ })
+ api.nvim_buf_set_extmark(buf, test_ns, line, 39, {
+ virt_text = {{string.rep('!', line+1), 'ErrorMsg'}};
+ virt_text_win_col=20;
+ ephemeral = true;
+ })
+ api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
+ virt_text = {{string.rep('?', line+1), 'ErrorMsg'}};
+ virt_text_win_col=10;
+ ephemeral = true;
+ })
+ api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
+ virt_text = {{string.rep(';', line+1), 'ErrorMsg'}};
+ virt_text_pos='overlay';
+ ephemeral = true;
+ })
+ api.nvim_buf_set_extmark(buf, test_ns, line, 40, {
+ virt_text = {{'+'}, {string.rep(' ', line+1), 'ErrorMsg'}};
+ virt_text_pos='right_align';
+ ephemeral = true;
+ })
+ end
+ end
+ ]]
+
+ screen:expect{grid=[[
+ // jus{2:*} to see if th{2:!}re was an accident |
+ {2:;}n Mulholl{2:?}nd Drive {2:/} +{2: }|
+ try_st{2:**}t(); bufref_{2:!!}save_buf; switch_b|
+ {2:;;}fer(&sav{2:??}buf, buf); {2://} +{2: }|
+ posp ={2:***}tmark(mark,{2:!!!}lse);^ restore_buf|
+ {2:;;;}(&save_{2:???}); {2:///} +{2: }|
+ {1:~ }|
+ |
+ ]]}
+ command('setlocal breakindent breakindentopt=shift:2')
+ screen:expect{grid=[[
+ // jus{2:*} to see if th{2:!}re was an accident |
+ {2:;}n Mulho{2:?}land Drive {2:/} +{2: }|
+ try_st{2:**}t(); bufref_{2:!!}save_buf; switch_b|
+ {2:;;}fer(&s{2:??}e_buf, buf); {2://} +{2: }|
+ posp ={2:***}tmark(mark,{2:!!!}lse);^ restore_buf|
+ {2:;;;}(&sav{2:???}uf); {2:///} +{2: }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('can highlight beyond EOL', function()
insert(mulholland)
setup_provider [[
- local test_ns = a.nvim_create_namespace "veberod"
+ local test_ns = api.nvim_create_namespace "veberod"
function on_do(event, ...)
if event == "line" then
local win, buf, line = ...
- if string.find(a.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then
- a.nvim_buf_set_extmark(buf, test_ns, line, 0, {
+ if string.find(api.nvim_buf_get_lines(buf, line, line+1, true)[1], "buf") then
+ api.nvim_buf_set_extmark(buf, test_ns, line, 0, {
end_line = line+1;
hl_group = 'DiffAdd';
hl_eol = true;
@@ -534,9 +632,9 @@ describe('decorations providers', function()
local function on_do(kind, winid, bufnr, topline, botline_guess)
if kind == 'win' then
if topline < 100 and botline_guess > 100 then
- vim.api.nvim_buf_set_extmark(bufnr, ns1, 99, -1, { sign_text = 'X' })
+ api.nvim_buf_set_extmark(bufnr, ns1, 99, -1, { sign_text = 'X' })
else
- vim.api.nvim_buf_clear_namespace(bufnr, ns1, 0, -1)
+ api.nvim_buf_clear_namespace(bufnr, ns1, 0, -1)
end
end
end
@@ -565,8 +663,60 @@ describe('decorations providers', function()
|
]])
end)
+
+ it('does allow removing extmarks during on_line callbacks', function()
+ exec_lua([[
+ eok = true
+ ]])
+ setup_provider([[
+ local function on_do(kind, winid, bufnr, topline, botline_guess)
+ if kind == 'line' then
+ api.nvim_buf_set_extmark(bufnr, ns1, 1, -1, { sign_text = 'X' })
+ eok = pcall(api.nvim_buf_clear_namespace, bufnr, ns1, 0, -1)
+ end
+ end
+ ]])
+ exec_lua([[
+ assert(eok == true)
+ ]])
+ end)
+
+ it('errors gracefully', function()
+ insert(mulholland)
+
+ setup_provider [[
+ function on_do(...)
+ error "Foo"
+ end
+ ]]
+
+ screen:expect{grid=[[
+ {2:Error in decoration provider ns1.start:} |
+ {2:Error executing lua: [string "<nvim>"]:4}|
+ {2:: Foo} |
+ {2:stack traceback:} |
+ {2: [C]: in function 'error'} |
+ {2: [string "<nvim>"]:4: in function}|
+ {2: <[string "<nvim>"]:3>} |
+ {18:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
end)
+local example_text = [[
+for _,item in ipairs(items) do
+ local text, hl_id_cell, count = unpack(item)
+ if hl_id_cell ~= nil then
+ hl_id = hl_id_cell
+ end
+ for _ = 1, (count or 1) do
+ local cell = line[colpos]
+ cell.text = text
+ cell.hl_id = hl_id
+ colpos = colpos+1
+ end
+end]]
+
describe('extmark decorations', function()
local screen, ns
before_each( function()
@@ -592,7 +742,7 @@ describe('extmark decorations', function()
[16] = {blend = 30, background = Screen.colors.Red1, foreground = Screen.colors.Magenta1};
[17] = {bold = true, foreground = Screen.colors.Brown, background = Screen.colors.LightGrey};
[18] = {background = Screen.colors.LightGrey};
- [19] = {foreground = Screen.colors.Cyan4, background = Screen.colors.LightGrey};
+ [19] = {foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGrey};
[20] = {foreground = tonumber('0x180606'), background = tonumber('0xf13f3f')};
[21] = {foreground = Screen.colors.Gray0, background = tonumber('0xf13f3f')};
[22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')};
@@ -601,25 +751,25 @@ describe('extmark decorations', function()
[25] = {background = Screen.colors.LightRed};
[26] = {background=Screen.colors.DarkGrey, foreground=Screen.colors.LightGrey};
[27] = {background = Screen.colors.Plum1};
+ [28] = {underline = true, foreground = Screen.colors.SlateBlue};
+ [29] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.LightGray, underline = true};
+ [30] = {foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGray, underline = true};
+ [31] = {underline = true, foreground = Screen.colors.DarkCyan};
+ [32] = {underline = true};
+ [33] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray};
+ [34] = {background = Screen.colors.Yellow};
+ [35] = {background = Screen.colors.Yellow, bold = true, foreground = Screen.colors.Blue};
+ [36] = {foreground = Screen.colors.Blue1, bold = true, background = Screen.colors.Red};
+ [37] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue};
+ [38] = {background = Screen.colors.LightBlue};
+ [39] = {foreground = Screen.colors.Blue1, background = Screen.colors.LightCyan1, bold = true};
+ [40] = {reverse = true};
+ [41] = {bold = true, reverse = true};
}
ns = meths.create_namespace 'test'
end)
- local example_text = [[
-for _,item in ipairs(items) do
- local text, hl_id_cell, count = unpack(item)
- if hl_id_cell ~= nil then
- hl_id = hl_id_cell
- end
- for _ = 1, (count or 1) do
- local cell = line[colpos]
- cell.text = text
- cell.hl_id = hl_id
- colpos = colpos+1
- end
-end]]
-
it('empty virtual text at eol should not break colorcolumn #17860', function()
insert(example_text)
feed('gg')
@@ -660,14 +810,14 @@ end]]
-- can "float" beyond end of line
meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'})
-- bound check: right edge of window
- meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork '}, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'})
+ meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork'}, {(' bork'):rep(10), 'ErrorMsg'}}, virt_text_pos='overlay'})
-- empty virt_text should not change anything
meths.buf_set_extmark(0, ns, 6, 16, { virt_text={{''}}, virt_text_pos='overlay'})
screen:expect{grid=[[
^for _,item in ipairs(items) do |
{2:|} local text, hl_id_cell, count = unpack(item) |
- {2:|} if hl_id_cell ~= nil tbork bork bork {4:bork bork}|
+ {2:|} if hl_id_cell ~= nil tbork bork bork{4: bork bork}|
{2:|} {1:|} hl_id = hl_id_cell |
{2:|} end |
{2:|} for _ = 1, (count or 1) {4:loopy} |
@@ -682,7 +832,6 @@ end]]
|
]]}
-
-- handles broken lines
screen:try_resize(22, 25)
screen:expect{grid=[[
@@ -692,7 +841,7 @@ end]]
cell, count = unpack(i|
tem) |
{2:|} if hl_id_cell ~= n|
- il tbork bork bork {4:bor}|
+ il tbork bork bork{4: bor}|
{2:|} {1:|} hl_id = hl_id_|
cell |
{2:|} end |
@@ -712,6 +861,248 @@ end]]
{1:~ }|
|
]]}
+
+ -- truncating in the middle of a char leaves a space
+ meths.buf_set_lines(0, 0, 1, true, {'for _,item in ipairs(items) do -- 古古古'})
+ meths.buf_set_lines(0, 10, 12, true, {' end -- ??????????', 'end -- ?古古古古?古古'})
+ meths.buf_set_extmark(0, ns, 0, 35, { virt_text={{'A', 'ErrorMsg'}, {'AA'}}, virt_text_pos='overlay'})
+ meths.buf_set_extmark(0, ns, 10, 19, { virt_text={{'口口口', 'ErrorMsg'}}, virt_text_pos='overlay'})
+ meths.buf_set_extmark(0, ns, 11, 21, { virt_text={{'口口口', 'ErrorMsg'}}, virt_text_pos='overlay'})
+ meths.buf_set_extmark(0, ns, 11, 8, { virt_text={{'口口', 'ErrorMsg'}}, virt_text_pos='overlay'})
+ screen:expect{grid=[[
+ ^for _,item in ipairs(i|
+ tems) do -- {4:A}AA 古 |
+ {2:|} local text, hl_id_|
+ cell, count = unpack(i|
+ tem) |
+ {2:|} if hl_id_cell ~= n|
+ il tbork bork bork{4: bor}|
+ {2:|} {1:|} hl_id = hl_id_|
+ cell |
+ {2:|} end |
+ {2:|} for _ = 1, (count |
+ or 1) {4:loopy} |
+ {2:|} {1:|} local cell = l|
+ ine[colpos] |
+ {2:|} {1:|} cell.text = te|
+ xt |
+ {2:|} {1:|} cell.hl_id = h|
+ l_id |
+ {2:|} {1:|} cofoo{3:bar}{4:!!}olpo|
+ s+1 |
+ end -- ???????{4:口 }|
+ end -- {4:口口} 古古{4:口口 }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ screen:try_resize(82, 13)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do -- {4:A}AA 古 |
+ {2:|} local text, hl_id_cell, count = unpack(item) |
+ {2:|} if hl_id_cell ~= nil tbork bork bork{4: bork bork bork bork bork bork bork bork b}|
+ {2:|} {1:|} hl_id = hl_id_cell |
+ {2:|} end |
+ {2:|} for _ = 1, (count or 1) {4:loopy} |
+ {2:|} {1:|} local cell = line[colpos] |
+ {2:|} {1:|} cell.text = text |
+ {2:|} {1:|} cell.hl_id = hl_id |
+ {2:|} {1:|} cofoo{3:bar}{4:!!}olpos+1 |
+ end -- ???????{4:口口口} |
+ end -- {4:口口} 古古{4:口口口} |
+ |
+ ]]}
+
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do -- 古古古 |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end -- ?????????? |
+ end -- ?古古古古?古古 |
+ |
+ ]]}
+ end)
+
+ it('overlay virtual text works with wrapped lines #25158', function()
+ screen:try_resize(50, 6)
+ insert(('ab'):rep(100))
+ for i = 0, 9 do
+ meths.buf_set_extmark(0, ns, 0, 42 + i, { virt_text={{tostring(i), 'ErrorMsg'}}, virt_text_pos='overlay'})
+ meths.buf_set_extmark(0, ns, 0, 91 + i, { virt_text={{tostring(i), 'ErrorMsg'}}, virt_text_pos='overlay', virt_text_hide=true})
+ end
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {4:89}abababababababababababababababababababa{4:012345678}|
+ {4:9}babababababababababababababababababababababababab|
+ ababababababababababababababababababababababababa^b|
+ {1:~ }|
+ |
+ ]]}
+
+ command('set showbreak=++')
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
+ {1:++}{4:789}babababababababababababababababababababababab|
+ {1:++}abababababababababababababababababababababababab|
+ {1:++}ababa^b |
+ |
+ ]]}
+
+ feed('2gkvg0')
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
+ {1:++}^a{18:babab}ababababababababababababababababababababab|
+ {1:++}abababababababababababababababababababababababab|
+ {1:++}ababab |
+ {24:-- VISUAL --} |
+ ]]}
+
+ feed('o')
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {1:++}{4:89}abababababababababababababababababababa{4:0123456}|
+ {1:++}{18:ababa}^bababababababababababababababababababababab|
+ {1:++}abababababababababababababababababababababababab|
+ {1:++}ababab |
+ {24:-- VISUAL --} |
+ ]]}
+
+ feed('gk')
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {1:++}{4:89}aba^b{18:ababababababababababababababababababababab}|
+ {1:++}{18:a}{4:89}babababababababababababababababababababababab|
+ {1:++}abababababababababababababababababababababababab|
+ {1:++}ababab |
+ {24:-- VISUAL --} |
+ ]]}
+
+ feed('o')
+ screen:expect{grid=[[
+ ababababababababababababababababababababab{4:01234567}|
+ {1:++}{4:89}aba{18:bababababababababababababababababababababab}|
+ {1:++}^a{4:89}babababababababababababababababababababababab|
+ {1:++}abababababababababababababababababababababababab|
+ {1:++}ababab |
+ {24:-- VISUAL --} |
+ ]]}
+
+ feed('<Esc>$')
+ command('set number showbreak=')
+ screen:expect{grid=[[
+ {2: 1 }ababababababababababababababababababababab{4:0123}|
+ {2: }{4:456789}abababababababababababababababababababa{4:0}|
+ {2: }{4:123456789}babababababababababababababababababab|
+ {2: }ababababababababababababababababababababababab|
+ {2: }abababababababa^b |
+ |
+ ]]}
+
+ command('set cpoptions+=n')
+ screen:expect{grid=[[
+ {2: 1 }ababababababababababababababababababababab{4:0123}|
+ {4:456789}abababababababababababababababababababa{4:01234}|
+ {4:56789}babababababababababababababababababababababab|
+ ababababababababababababababababababababababababab|
+ aba^b |
+ |
+ ]]}
+
+ feed('0g$hi<Tab>')
+ screen:expect{grid=[[
+ {2: 1 }ababababababababababababababababababababab{4:01} |
+ {4:^23456789}abababababababababababababababababababa{4:0}|
+ {4:123456789}babababababababababababababababababababab|
+ ababababababababababababababababababababababababab|
+ abababab |
+ {24:-- INSERT --} |
+ ]]}
+ end)
+
+ it('virt_text_hide hides overlay virtual text when extmark is off-screen', function()
+ screen:try_resize(50, 3)
+ command('set nowrap')
+ meths.buf_set_lines(0, 0, -1, true, {'-- ' .. ('…'):rep(57)})
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text={{'?????', 'ErrorMsg'}}, virt_text_pos='overlay', virt_text_hide=true})
+ meths.buf_set_extmark(0, ns, 0, 123, { virt_text={{'!!!!!', 'ErrorMsg'}}, virt_text_pos='overlay', virt_text_hide=true})
+ screen:expect{grid=[[
+ {4:^?????}……………………………………………………………………………………………………{4:!!!!!}……|
+ {1:~ }|
+ |
+ ]]}
+ feed('40zl')
+ screen:expect{grid=[[
+ ^………{4:!!!!!}……………………………… |
+ {1:~ }|
+ |
+ ]]}
+ feed('3zl')
+ screen:expect{grid=[[
+ {4:^!!!!!}……………………………… |
+ {1:~ }|
+ |
+ ]]}
+ feed('7zl')
+ screen:expect{grid=[[
+ ^………………………… |
+ {1:~ }|
+ |
+ ]]}
+
+ command('set wrap smoothscroll')
+ screen:expect{grid=[[
+ {4:?????}……………………………………………………………………………………………………{4:!!!!!}……|
+ ^………………………… |
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}………………^… |
+ {1:~ }|
+ |
+ ]]}
+ screen:try_resize(40, 3)
+ screen:expect{grid=[[
+ {1:<<<}{4:!!!!!}……………………………^… |
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-Y>')
+ screen:expect{grid=[[
+ {4:?????}……………………………………………………………………………………………|
+ ………{4:!!!!!}……………………………^… |
+ |
+ ]]}
+ end)
+
+ it('overlay virtual text works on and after a TAB #24022', function()
+ screen:try_resize(40, 3)
+ meths.buf_set_lines(0, 0, -1, true, {'\t\tline 1'})
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'AA', 'Search'}}, virt_text_pos = 'overlay', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 0, 1, { virt_text = {{'BB', 'Search'}}, virt_text_pos = 'overlay', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'CC', 'Search'}}, virt_text_pos = 'overlay', hl_mode = 'combine' })
+ screen:expect{grid=[[
+ {34:AA} ^ {34:BB} {34:CC}ne 1 |
+ {1:~ }|
+ |
+ ]]}
+ command('setlocal list listchars=tab:<->')
+ screen:expect{grid=[[
+ {35:^AA}{1:----->}{35:BB}{1:----->}{34:CC}ne 1 |
+ {1:~ }|
+ |
+ ]]}
end)
it('can have virtual text of overlay position and styling', function()
@@ -805,25 +1196,30 @@ end]]
]]}
end)
- it('can have virtual text of fixed win_col position', function()
+ it('can have virtual text of right_align and fixed win_col position', function()
insert(example_text)
feed 'gg'
- meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
- meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
- meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'Very', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_text={{'VERY', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'MUCH', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 3, 14, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 3, 14, { virt_text={{'ERROR', 'ErrorMsg'}}, virt_text_pos='right_align', hl_mode='blend'})
meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_pos='right_align', hl_mode='blend'})
-- empty virt_text should not change anything
meths.buf_set_extmark(0, ns, 8, 0, { virt_text={{''}}, virt_text_win_col=14, hl_mode='blend'})
+ meths.buf_set_extmark(0, ns, 8, 0, { virt_text={{''}}, virt_text_pos='right_align', hl_mode='blend'})
screen:expect{grid=[[
^for _,item in ipairs(items) do |
- local text, hl_id_cell, cou{4:Very} unpack(item) |
- if hl_id_cell ~= nil then {4:Much} |
- hl_id = hl_id_cell {4:Error} |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ hl_id = hl_id_cell {4:Error} {4:ERROR}|
end |
for _ = 1, (count or 1) do |
local cell = line[colpos] |
- {1:-} cell.text = text |
+ {1:-} cell.text = text {1:-}|
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
@@ -836,14 +1232,14 @@ end]]
feed '3G12|i<cr><esc>'
screen:expect{grid=[[
for _,item in ipairs(items) do |
- local text, hl_id_cell, cou{4:Very} unpack(item) |
- if hl_i {4:Much} |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if hl_i {4:Much} {4:MUCH}|
^d_cell ~= nil then |
- hl_id = hl_id_cell {4:Error} |
+ hl_id = hl_id_cell {4:Error} {4:ERROR}|
end |
for _ = 1, (count or 1) do |
local cell = line[colpos] |
- {1:-} cell.text = text |
+ {1:-} cell.text = text {1:-}|
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
@@ -855,13 +1251,13 @@ end]]
feed 'u:<cr>'
screen:expect{grid=[[
for _,item in ipairs(items) do |
- local text, hl_id_cell, cou{4:Very} unpack(item) |
- if hl_i^d_cell ~= nil then {4:Much} |
- hl_id = hl_id_cell {4:Error} |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if hl_i^d_cell ~= nil then {4:Much} {4:MUCH}|
+ hl_id = hl_id_cell {4:Error} {4:ERROR}|
end |
for _ = 1, (count or 1) do |
local cell = line[colpos] |
- {1:-} cell.text = text |
+ {1:-} cell.text = text {1:-}|
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
@@ -874,14 +1270,14 @@ end]]
feed '8|i<cr><esc>'
screen:expect{grid=[[
for _,item in ipairs(items) do |
- local text, hl_id_cell, cou{4:Very} unpack(item) |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
if |
- ^hl_id_cell ~= nil then {4:Much} |
- hl_id = hl_id_cell {4:Error} |
+ ^hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ hl_id = hl_id_cell {4:Error} {4:ERROR}|
end |
for _ = 1, (count or 1) do |
local cell = line[colpos] |
- {1:-} cell.text = text |
+ {1:-} cell.text = text {1:-}|
cell.hl_id = hl_id |
colpos = colpos+1 |
end |
@@ -889,9 +1285,273 @@ end]]
{1:~ }|
|
]]}
+
+ feed 'jI-- <esc>..........'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if |
+ hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ --^ -- -- -- -- -- -- --{4:Error}- -- hl_i{4:ERROR}|
+ l_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text {1:-}|
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 4, 50, { virt_text={{'EOL', 'NonText'}} })
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if |
+ hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ --^ -- -- -- -- -- -- --{4:Error}- -- hl_i{4:ERROR}|
+ l_id_cell {1:EOL} |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text {1:-}|
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ |
+ ]]}
+
+ feed '.'
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text, hl_id_cell, cou{4:Very} unpack(ite{4:VERY}|
+ if |
+ hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ --^ -- -- -- -- -- -- -- -- -- -- -- hl_id |
+ = hl_id_cell {1:EOL} {4:Error} {4:ERROR}|
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ {1:-} cell.text = text {1:-}|
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ |
+ ]]}
+
+ command 'set number'
+ screen:expect{grid=[[
+ {2: 1 }for _,item in ipairs(items) do |
+ {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
+ {2: }m) |
+ {2: 3 } if |
+ {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ {2: 5 } --^ -- -- -- -- -- -- -- -- -- -- -- hl|
+ {2: }_id = hl_id_cell {1:EOL} {4:Error} {4:ERROR}|
+ {2: 6 } end |
+ {2: 7 } for _ = 1, (count or 1) do |
+ {2: 8 } local cell = line[colpos] |
+ {2: 9 } {1:-} cell.text = text {1:-}|
+ {2: 10 } cell.hl_id = hl_id |
+ {2: 11 } colpos = colpos+1 |
+ {2: 12 } end |
+ |
+ ]]}
+
+ command 'set cpoptions+=n'
+ screen:expect{grid=[[
+ {2: 1 }for _,item in ipairs(items) do |
+ {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
+ m) |
+ {2: 3 } if |
+ {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ {2: 5 } --^ -- -- -- -- -- -- -- -- -- -- -- hl|
+ _id = hl_id_cell {1:EOL} {4:Error} {4:ERROR}|
+ {2: 6 } end |
+ {2: 7 } for _ = 1, (count or 1) do |
+ {2: 8 } local cell = line[colpos] |
+ {2: 9 } {1:-} cell.text = text {1:-}|
+ {2: 10 } cell.hl_id = hl_id |
+ {2: 11 } colpos = colpos+1 |
+ {2: 12 } end |
+ |
+ ]]}
+
+ command 'set cpoptions-=n nowrap'
+ screen:expect{grid=[[
+ {2: 1 }for _,item in ipairs(items) do |
+ {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
+ {2: 3 } if |
+ {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ {2: 5 } --^ -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}|
+ {2: 6 } end |
+ {2: 7 } for _ = 1, (count or 1) do |
+ {2: 8 } local cell = line[colpos] |
+ {2: 9 } {1:-} cell.text = text {1:-}|
+ {2: 10 } cell.hl_id = hl_id |
+ {2: 11 } colpos = colpos+1 |
+ {2: 12 } end |
+ {2: 13 }end |
+ {1:~ }|
+ |
+ ]]}
+
+ feed '12zl'
+ screen:expect{grid=[[
+ {2: 1 }n ipairs(items) do |
+ {2: 2 }xt, hl_id_cell, count = unpack({4:Very}) {4:VERY}|
+ {2: 3 } |
+ {2: 4 }= nil then {4:Much} {4:MUCH}|
+ {2: 5 }^- -- -- -- -- -- -- -- -- -- --{4:Error}d = h{4:ERROR}|
+ {2: 6 } |
+ {2: 7 }1, (count or 1) do |
+ {2: 8 }l cell = line[colpos] |
+ {2: 9 }.tex{1:-} = text {1:-}|
+ {2: 10 }.hl_id = hl_id |
+ {2: 11 }os = colpos+1 |
+ {2: 12 } |
+ {2: 13 } |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('fhi<Tab>')
+ screen:expect{grid=[[
+ {2: 1 }n ipairs(items) do |
+ {2: 2 }xt, hl_id_cell, count = unpack({4:Very}) {4:VERY}|
+ {2: 3 } |
+ {2: 4 }= nil then {4:Much} {4:MUCH}|
+ {2: 5 }- -- -- -- -- -- -- -- -- -- --{4:Error}^hl_id{4:ERROR}|
+ {2: 6 } |
+ {2: 7 }1, (count or 1) do |
+ {2: 8 }l cell = line[colpos] |
+ {2: 9 }.tex{1:-} = text {1:-}|
+ {2: 10 }.hl_id = hl_id |
+ {2: 11 }os = colpos+1 |
+ {2: 12 } |
+ {2: 13 } |
+ {1:~ }|
+ {24:-- INSERT --} |
+ ]]}
+
+ feed('<Esc>0')
+ screen:expect{grid=[[
+ {2: 1 }for _,item in ipairs(items) do |
+ {2: 2 } local text, hl_id_cell, cou{4:Very} unpack{4:VERY}|
+ {2: 3 } if |
+ {2: 4 }hl_id_cell ~= nil then {4:Much} {4:MUCH}|
+ {2: 5 }^ -- -- -- -- -- -- -- --{4:Error}- -- {4:ERROR}|
+ {2: 6 } end |
+ {2: 7 } for _ = 1, (count or 1) do |
+ {2: 8 } local cell = line[colpos] |
+ {2: 9 } {1:-} cell.text = text {1:-}|
+ {2: 10 } cell.hl_id = hl_id |
+ {2: 11 } colpos = colpos+1 |
+ {2: 12 } end |
+ {2: 13 }end |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('virtual text win_col out of window does not break display #25645', function()
+ screen:try_resize(51, 6)
+ command('vnew')
+ meths.buf_set_lines(0, 0, -1, false, { string.rep('a', 50) })
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaa│ |
+ aaaaaaaaaaaaaaaaaaaaaaaaa│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {41:[No Name] [+] }{40:[No Name] }|
+ |
+ ]]}
+ local extmark_opts = { virt_text_win_col = 35, virt_text = { { ' ', 'Comment' } } }
+ meths.buf_set_extmark(0, ns, 0, 0, extmark_opts)
+ screen:expect_unchanged()
+ assert_alive()
+ end)
+
+ it('can have virtual text on folded line', function()
+ insert([[
+ 11111
+ 22222
+ 33333]])
+ command('1,2fold')
+ screen:try_resize(50, 3)
+ feed('zb')
+ -- XXX: the behavior of overlay virtual text at non-zero column is strange:
+ -- 1. With 'wrap' it is never shown.
+ -- 2. With 'nowrap' it is shown only if the extmark is hidden before leftcol.
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'AA', 'Underlined'}}, hl_mode = 'combine', virt_text_pos = 'overlay' })
+ meths.buf_set_extmark(0, ns, 0, 5, { virt_text = {{'BB', 'Underlined'}}, hl_mode = 'combine', virt_text_win_col = 10 })
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'CC', 'Underlined'}}, hl_mode = 'combine', virt_text_pos = 'right_align' })
+ screen:expect{grid=[[
+ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
+ 3333^3 |
+ |
+ ]]}
+ command('set nowrap')
+ screen:expect_unchanged()
+ feed('zl')
+ screen:expect{grid=[[
+ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
+ 333^3 |
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
+ 33^3 |
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {29:AA}{33:- 2 lin}{29:BB}{33:: 11111·····························}{29:CC}|
+ 3^3 |
+ |
+ ]]}
+ end)
+
+ it('virtual text works below diff filler lines', function()
+ screen:try_resize(53, 8)
+ insert([[
+ aaaaa
+ bbbbb
+ ccccc
+ ddddd
+ eeeee]])
+ command('rightbelow vnew')
+ insert([[
+ bbbbb
+ ccccc
+ ddddd
+ eeeee]])
+ command('windo diffthis')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'AA', 'Underlined'}}, virt_text_pos = 'overlay' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'BB', 'Underlined'}}, virt_text_win_col = 10 })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'CC', 'Underlined'}}, virt_text_pos = 'right_align' })
+ screen:expect{grid=[[
+ {37: }{38:aaaaa }│{37: }{39:------------------------}|
+ {37: }bbbbb │{37: }{28:AA}bbb {28:BB} {28:CC}|
+ {37: }ccccc │{37: }ccccc |
+ {37: }ddddd │{37: }ddddd |
+ {37: }eeeee │{37: }eeee^e |
+ {1:~ }│{1:~ }|
+ {40:[No Name] [+] }{41:[No Name] [+] }|
+ |
+ ]]}
+ command('windo set wrap')
+ screen:expect_unchanged()
end)
it('can have virtual text which combines foreground and background groups', function()
+ screen:try_resize(20, 5)
+
screen:set_default_attr_ids {
[1] = {bold=true, foreground=Screen.colors.Blue};
[2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')};
@@ -909,30 +1569,24 @@ end]]
hi VeryBold gui=bold
]]
- meths.buf_set_extmark(0, ns, 0, 0, { virt_text={
+ insert('##')
+ local vt = {
{'a', {'BgOne', 'FgEin'}};
{'b', {'BgOne', 'FgZwei'}};
{'c', {'BgTwo', 'FgEin'}};
{'d', {'BgTwo', 'FgZwei'}};
{'X', {'BgTwo', 'FgZwei', 'VeryBold'}};
- }})
-
+ }
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'eol' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'right_align' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = vt, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_lines = { vt, vt } })
screen:expect{grid=[[
- ^ {2:a}{3:b}{4:c}{5:d}{6:X} |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
+ {2:a}{3:b}{4:c}{5:d}{6:X}#^# {2:a}{3:b}{4:c}{5:d}{6:X} {2:a}{3:b}{4:c}{5:d}{6:X}|
+ {2:a}{3:b}{4:c}{5:d}{6:X} |
+ {2:a}{3:b}{4:c}{5:d}{6:X} |
+ {1:~ }|
+ |
]]}
end)
@@ -980,21 +1634,94 @@ end]]
{1:~ }|
|
]]}
- helpers.assert_alive()
+ assert_alive()
end)
- it('conceal #19007', function()
+ it('conceal with conceal char #19007', function()
screen:try_resize(50, 5)
insert('foo\n')
- command('let &conceallevel=2')
meths.buf_set_extmark(0, ns, 0, 0, {end_col=0, end_row=2, conceal='X'})
+ command('set conceallevel=2')
screen:expect([[
- {26:X} |
- ^ |
- {1:~ }|
- {1:~ }|
- |
- ]])
+ {26:X} |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ command('set conceallevel=1')
+ screen:expect_unchanged()
+
+ eq("conceal char has to be printable", pcall_err(meths.buf_set_extmark, 0, ns, 0, 0, {end_col=0, end_row=2, conceal='\255'}))
+ end)
+
+ it('conceal with composed conceal char', function()
+ screen:try_resize(50, 5)
+ insert('foo\n')
+ meths.buf_set_extmark(0, ns, 0, 0, {end_col=0, end_row=2, conceal='ẍ̲'})
+ command('set conceallevel=2')
+ screen:expect([[
+ {26:ẍ̲} |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ command('set conceallevel=1')
+ screen:expect_unchanged()
+
+ -- this is rare, but could happen. Save at least the first codepoint
+ meths._invalidate_glyph_cache()
+ screen:expect{grid=[[
+ {26:x} |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('conceal without conceal char #24782', function()
+ screen:try_resize(50, 5)
+ insert('foobar\n')
+ meths.buf_set_extmark(0, ns, 0, 0, {end_col=3, conceal=''})
+ command('set listchars=conceal:?')
+ command('let &conceallevel=1')
+ screen:expect([[
+ {26:?}bar |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ command('let &conceallevel=2')
+ screen:expect([[
+ bar |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+
+ it('conceal works just before truncated double-width char #21486', function()
+ screen:try_resize(40, 4)
+ meths.buf_set_lines(0, 0, -1, true, {'', ('a'):rep(37) .. '<>古'})
+ meths.buf_set_extmark(0, ns, 1, 37, {end_col=39, conceal=''})
+ command('setlocal conceallevel=2')
+ screen:expect{grid=[[
+ ^ |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:>} |
+ 古 |
+ |
+ ]]}
+ feed('j')
+ screen:expect{grid=[[
+ |
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa<>{1:>}|
+ 古 |
+ |
+ ]]}
end)
it('avoids redraw issue #20651', function()
@@ -1045,6 +1772,2005 @@ end]]
end)
+ it('underline attribute with higher priority takes effect #22371', function()
+ screen:try_resize(50, 3)
+ insert('aaabbbaaa')
+ exec([[
+ hi TestUL gui=underline guifg=Blue
+ hi TestUC gui=undercurl guisp=Red
+ hi TestBold gui=bold
+ ]])
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue};
+ [1] = {underline = true, foreground = Screen.colors.Blue};
+ [2] = {undercurl = true, special = Screen.colors.Red};
+ [3] = {underline = true, foreground = Screen.colors.Blue, special = Screen.colors.Red};
+ [4] = {undercurl = true, foreground = Screen.colors.Blue, special = Screen.colors.Red};
+ [5] = {bold = true, underline = true, foreground = Screen.colors.Blue};
+ [6] = {bold = true, undercurl = true, special = Screen.colors.Red};
+ })
+
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 20 })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUC', priority = 30 })
+ screen:expect([[
+ {1:aaa}{4:bbb}{1:aa^a} |
+ {0:~ }|
+ |
+ ]])
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 20 })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUL', priority = 30 })
+ screen:expect([[
+ {2:aaa}{3:bbb}{2:aa^a} |
+ {0:~ }|
+ |
+ ]])
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 30 })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUC', priority = 20 })
+ screen:expect([[
+ {1:aaa}{3:bbb}{1:aa^a} |
+ {0:~ }|
+ |
+ ]])
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 30 })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestUL', priority = 20 })
+ screen:expect([[
+ {2:aaa}{4:bbb}{2:aa^a} |
+ {0:~ }|
+ |
+ ]])
+
+ -- When only one highlight group has an underline attribute, it should always take effect.
+ for _, d in ipairs({-5, 5}) do
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ screen:expect([[
+ aaabbbaa^a |
+ {0:~ }|
+ |
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUL', priority = 25 + d })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestBold', priority = 25 - d })
+ screen:expect([[
+ {1:aaa}{5:bbb}{1:aa^a} |
+ {0:~ }|
+ |
+ ]])
+ end
+ for _, d in ipairs({-5, 5}) do
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ screen:expect([[
+ aaabbbaa^a |
+ {0:~ }|
+ |
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 9, hl_group = 'TestUC', priority = 25 + d })
+ meths.buf_set_extmark(0, ns, 0, 3, { end_col = 6, hl_group = 'TestBold', priority = 25 - d })
+ screen:expect([[
+ {2:aaa}{6:bbb}{2:aa^a} |
+ {0:~ }|
+ |
+ ]])
+ end
+ end)
+
+ it('highlight is combined with syntax and sign linehl #20004', function()
+ screen:try_resize(50, 3)
+ insert([[
+ function Func()
+ end]])
+ feed('gg')
+ command('set ft=lua')
+ command('syntax on')
+ meths.buf_set_extmark(0, ns, 0, 0, { end_col = 3, hl_mode = 'combine', hl_group = 'Visual' })
+ command('hi default MyLine gui=underline')
+ command('sign define CurrentLine linehl=MyLine')
+ funcs.sign_place(6, 'Test', 'CurrentLine', '', { lnum = 1 })
+ screen:expect{grid=[[
+ {30:^fun}{31:ction}{32: Func() }|
+ {6:end} |
+ |
+ ]]}
+ end)
+
+ it('highlight works after TAB with sidescroll #14201', function()
+ screen:try_resize(50, 3)
+ command('set nowrap')
+ meths.buf_set_lines(0, 0, -1, true, {'\tword word word word'})
+ meths.buf_set_extmark(0, ns, 0, 1, { end_col = 3, hl_group = 'ErrorMsg' })
+ screen:expect{grid=[[
+ ^ {4:wo}rd word word word |
+ {1:~ }|
+ |
+ ]]}
+ feed('7zl')
+ screen:expect{grid=[[
+ {4:^wo}rd word word word |
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {4:^wo}rd word word word |
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {4:^o}rd word word word |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlights the beginning of a TAB char correctly #23734', function()
+ screen:try_resize(50, 3)
+ meths.buf_set_lines(0, 0, -1, true, {'this is the\ttab'})
+ meths.buf_set_extmark(0, ns, 0, 11, { end_col = 15, hl_group = 'ErrorMsg' })
+ screen:expect{grid=[[
+ ^this is the{4: tab} |
+ {1:~ }|
+ |
+ ]]}
+
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ meths.buf_set_extmark(0, ns, 0, 12, { end_col = 15, hl_group = 'ErrorMsg' })
+ screen:expect{grid=[[
+ ^this is the {4:tab} |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlight applies to a full TAB on line with matches #20885', function()
+ screen:try_resize(50, 3)
+ meths.buf_set_lines(0, 0, -1, true, {'\t-- match1', ' -- match2'})
+ funcs.matchadd('Underlined', 'match')
+ meths.buf_set_extmark(0, ns, 0, 0, { end_row = 1, end_col = 0, hl_group = 'Visual' })
+ meths.buf_set_extmark(0, ns, 1, 0, { end_row = 2, end_col = 0, hl_group = 'Visual' })
+ screen:expect{grid=[[
+ {18: ^ -- }{29:match}{18:1} |
+ {18: -- }{29:match}{18:2} |
+ |
+ ]]}
+ end)
+
+ pending('highlight applies to a full TAB in visual block mode', function()
+ screen:try_resize(50, 8)
+ meths.buf_set_lines(0, 0, -1, true, {'asdf', '\tasdf', '\tasdf', '\tasdf', 'asdf'})
+ meths.buf_set_extmark(0, ns, 0, 0, {end_row = 5, end_col = 0, hl_group = 'Underlined'})
+ screen:expect([[
+ {28:^asdf} |
+ {28: asdf} |
+ {28: asdf} |
+ {28: asdf} |
+ {28:asdf} |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ feed('<C-V>Gll')
+ screen:expect([[
+ {29:asd}{28:f} |
+ {29: }{28: asdf} |
+ {29: }{28: asdf} |
+ {29: }{28: asdf} |
+ {29:as}{28:^df} |
+ {1:~ }|
+ {1:~ }|
+ {24:-- VISUAL BLOCK --} |
+ ]])
+ end)
+
+ it('supports multiline highlights', function()
+ insert(example_text)
+ feed 'gg'
+ for _,i in ipairs {1,2,3,5,6,7} do
+ for _,j in ipairs {2,5,10,15} do
+ meths.buf_set_extmark(0, ns, i, j, { end_col=j+2, hl_group = 'NonText'})
+ end
+ end
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ {1: }l{1:oc}al {1:te}xt,{1: h}l_id_cell, count = unpack(item) |
+ {1: }i{1:f }hl_{1:id}_ce{1:ll} ~= nil then |
+ {1: } {1: } hl{1:_i}d ={1: h}l_id_cell |
+ end |
+ {1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do |
+ {1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] |
+ {1: } {1: } ce{1:ll}.te{1:xt} = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed'5<c-e>'
+ screen:expect{grid=[[
+ ^ {1: }f{1:or} _ {1:= }1, {1:(c}ount or 1) do |
+ {1: } {1: } lo{1:ca}l c{1:el}l = line[colpos] |
+ {1: } {1: } ce{1:ll}.te{1:xt} = text |
+ cell.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 1, 0, { end_line=8, end_col=10, hl_group = 'ErrorMsg'})
+ screen:expect{grid=[[
+ {4:^ }{36: }{4:f}{36:or}{4: _ }{36:= }{4:1, }{36:(c}{4:ount or 1) do} |
+ {4: }{36: }{4: }{36: }{4: lo}{36:ca}{4:l c}{36:el}{4:l = line[colpos]} |
+ {4: }{36: }{4: }{36: }{4: ce}{36:ll}{4:.te}{36:xt}{4: = text} |
+ {4: ce}ll.hl_id = hl_id |
+ colpos = colpos+1 |
+ end |
+ end |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ local function with_undo_restore(val)
+ screen:try_resize(50, 5)
+ insert(example_text)
+ feed'gg'
+ meths.buf_set_extmark(0, ns, 0, 6, { end_col=13, hl_group = 'NonText', undo_restore=val})
+ screen:expect{grid=[[
+ ^for _,{1:item in} ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ |
+ ]]}
+
+ meths.buf_set_text(0, 0, 4, 0, 8, {''})
+ screen:expect{grid=[[
+ ^for {1:em in} ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ |
+ ]]}
+ end
+
+ it("highlights do reapply to restored text after delete", function()
+ with_undo_restore(true) -- also default behavior
+
+ command('silent undo')
+ screen:expect{grid=[[
+ ^for _,{1:item in} ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ |
+ ]]}
+ end)
+
+ it("highlights don't reapply to restored text after delete with undo_restore=false", function()
+ with_undo_restore(false)
+
+ command('silent undo')
+ screen:expect{grid=[[
+ ^for _,it{1:em in} ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ |
+ ]]}
+
+ eq({ { 1, 0, 8, { end_col = 13, end_right_gravity = false, end_row = 0,
+ hl_eol = false, hl_group = "NonText", undo_restore = false,
+ ns_id = 1, priority = 4096, right_gravity = true } } },
+ meths.buf_get_extmarks(0, ns, {0,0}, {0, -1}, {details=true}))
+ end)
+
+ it('virtual text works with rightleft', function()
+ screen:try_resize(50, 3)
+ insert('abcdefghijklmn')
+ feed('0')
+ command('set rightleft')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'EOL', 'Underlined'}}})
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'right_align', 'Underlined'}}, virt_text_pos = 'right_align' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'win_col', 'Underlined'}}, virt_text_win_col = 20 })
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'overlayed', 'Underlined'}}, virt_text_pos = 'overlay' })
+ screen:expect{grid=[[
+ {28:ngila_thgir} {28:loc_niw} {28:LOE} nml{28:deyalrevo}b^a|
+ {1: ~}|
+ |
+ ]]}
+
+ insert(('#'):rep(32))
+ feed('0')
+ screen:expect{grid=[[
+ {28:ngila_tdeyalrevo}ba#####{28:loc_niw}###################^#|
+ {1: ~}|
+ |
+ ]]}
+
+ insert(('#'):rep(16))
+ feed('0')
+ screen:expect{grid=[[
+ {28:ngila_thgir}############{28:loc_niw}###################^#|
+ {28:LOE} nml{28:deyalrevo}|
+ |
+ ]]}
+
+ insert('###')
+ feed('0')
+ screen:expect{grid=[[
+ #################################################^#|
+ {28:ngila_thgir} {28:loc_niw} {28:LOE} nml{28:deyalrevo}ba#|
+ |
+ ]]}
+
+ command('set number')
+ screen:expect{grid=[[
+ #############################################^#{2: 1 }|
+ {28:ngila_thgir} {28:loc_niw} nml{28:deyalrevo}ba#####{2: }|
+ |
+ ]]}
+
+ command('set cpoptions+=n')
+ screen:expect{grid=[[
+ #############################################^#{2: 1 }|
+ {28:ngila_thgir} {28:loc_niw} nml{28:deyalrevo}ba#####|
+ |
+ ]]}
+ end)
+
+ it('works with double width char and rightleft', function()
+ screen:try_resize(50, 3)
+ insert('abcdefghij口klmnopqrstu口vwx口yz')
+ feed('0')
+ command('set rightleft')
+ screen:expect{grid=[[
+ zy口xwv口utsrqponmlk口jihgfedcb^a|
+ {1: ~}|
+ |
+ ]]}
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = {{'overlayed', 'Underlined'}}, virt_text_pos = 'overlay' })
+ meths.buf_set_extmark(0, ns, 0, 14, { virt_text = {{'古', 'Underlined'}}, virt_text_pos = 'overlay' })
+ meths.buf_set_extmark(0, ns, 0, 20, { virt_text = {{'\t', 'Underlined'}}, virt_text_pos = 'overlay' })
+ meths.buf_set_extmark(0, ns, 0, 29, { virt_text = {{'古', 'Underlined'}}, virt_text_pos = 'overlay' })
+ screen:expect{grid=[[
+ zy {28:古}wv {28: }qpon{28:古}k {28:deyalrevo}b^a|
+ {1: ~}|
+ |
+ ]]}
+ end)
+
+ it('works with both hl_group and sign_hl_group', function()
+ screen:try_resize(screen._width, 3)
+ insert('abcdefghijklmn')
+ meths.buf_set_extmark(0, ns, 0, 0, {sign_text='S', sign_hl_group='NonText', hl_group='Error', end_col=14})
+ screen:expect{grid=[[
+ {1:S }{4:abcdefghijklm^n} |
+ {1:~ }|
+ |
+ ]]}
+ end)
+end)
+
+describe('decorations: inline virtual text', function()
+ local screen, ns
+ before_each( function()
+ clear()
+ screen = Screen.new(50, 3)
+ screen:attach()
+ screen:set_default_attr_ids {
+ [1] = {bold=true, foreground=Screen.colors.Blue};
+ [2] = {foreground = Screen.colors.Brown};
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen};
+ [4] = {background = Screen.colors.Red1, foreground = Screen.colors.Gray100};
+ [5] = {background = Screen.colors.Red1, bold = true};
+ [6] = {foreground = Screen.colors.DarkCyan};
+ [7] = {background = Screen.colors.LightGrey};
+ [8] = {bold = true};
+ [9] = {background = Screen.colors.Plum1};
+ [10] = {foreground = Screen.colors.SlateBlue};
+ [11] = {blend = 30, background = Screen.colors.Red1};
+ [12] = {background = Screen.colors.Yellow};
+ [13] = {reverse = true};
+ [14] = {foreground = Screen.colors.SlateBlue, background = Screen.colors.LightMagenta};
+ [15] = {bold = true, reverse = true};
+ [16] = {foreground = Screen.colors.Red};
+ [17] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue};
+ [18] = {background = Screen.colors.LightGrey, foreground = Screen.colors.Red};
+ [19] = {background = Screen.colors.Yellow, foreground = Screen.colors.SlateBlue};
+ [20] = {background = Screen.colors.LightGrey, foreground = Screen.colors.SlateBlue};
+ [21] = {reverse = true, foreground = Screen.colors.SlateBlue}
+ }
+
+ ns = meths.create_namespace 'test'
+ end)
+
+
+ it('works', function()
+ screen:try_resize(50, 10)
+ insert(example_text)
+ feed 'gg'
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 1, 14, {virt_text={{': ', 'Special'}, {'string', 'Type'}}, virt_text_pos='inline'})
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack|
+ (item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ |
+ ]]}
+
+ screen:try_resize(55, 10)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack(item|
+ ) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ |
+ ]]}
+
+ screen:try_resize(56, 10)
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack(item)|
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+ end)
+
+ it('works with 0-width chunk', function()
+ screen:try_resize(50, 10)
+ insert(example_text)
+ feed 'gg'
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text, hl_id_cell, count = unpack(item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 0, 5, {virt_text={{''}, {''}}, virt_text_pos='inline'})
+ meths.buf_set_extmark(0, ns, 1, 14, {virt_text={{''}, {': ', 'Special'}}, virt_text_pos='inline'})
+ meths.buf_set_extmark(0, ns, 1, 48, {virt_text={{''}, {''}}, virt_text_pos='inline'})
+ screen:expect{grid=[[
+ ^for _,item in ipairs(items) do |
+ local text{10:: }, hl_id_cell, count = unpack(item)|
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ cell.hl_id = hl_id |
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 1, 14, {virt_text={{''}, {'string', 'Type'}}, virt_text_pos='inline'})
+ feed('V')
+ screen:expect{grid=[[
+ ^f{7:or _,item in ipairs(items) do} |
+ local text{10:: }{3:string}, hl_id_cell, count = unpack|
+ (item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ {8:-- VISUAL LINE --} |
+ ]]}
+
+ feed('<Esc>jf,')
+ screen:expect{grid=[[
+ for _,item in ipairs(items) do |
+ local text{10:: }{3:string}^, hl_id_cell, count = unpack|
+ (item) |
+ if hl_id_cell ~= nil then |
+ hl_id = hl_id_cell |
+ end |
+ for _ = 1, (count or 1) do |
+ local cell = line[colpos] |
+ cell.text = text |
+ |
+ ]]}
+ end)
+
+ it('Normal mode "gM" command works properly', function()
+ command([[call setline(1, '123456789')]])
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 7, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
+ feed('gM')
+ screen:expect{grid=[[
+ 12{10:bbb}34^567{10:bbb}89 |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ local function test_normal_gj_gk()
+ screen:try_resize(60, 6)
+ command([[call setline(1, repeat([repeat('a', 55)], 2))]])
+ meths.buf_set_extmark(0, ns, 0, 40, { virt_text = { { ('b'):rep(10), 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 1, 40, { virt_text = { { ('b'):rep(10), 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gj')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ ^aaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gj')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gj')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ ^aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gk')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gk')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ ^aaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ feed('gk')
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbbbbbbbbb}aaaaaaaaaa|
+ aaaaa |
+ {1:~ }|
+ |
+ ]]}
+ end
+
+ describe('Normal mode "gj" "gk" commands work properly', function()
+ it('with virtualedit=', function()
+ test_normal_gj_gk()
+ end)
+
+ it('with virtualedit=all', function()
+ command('set virtualedit=all')
+ test_normal_gj_gk()
+ end)
+ end)
+
+ it('cursor positions are correct with multiple inline virtual text', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+ feed '^'
+ feed '4l'
+ screen:expect{grid=[[
+ 1234{10: virtual text virtual text }^5678 |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('adjusts cursor location correctly when inserting around inline virtual text', function()
+ insert('12345678')
+ feed '$'
+ meths.buf_set_extmark(0, ns, 0, 4, { virt_text = { { ' virtual text ', 'Special' } }, virt_text_pos = 'inline' })
+
+ screen:expect{grid=[[
+ 1234{10: virtual text }567^8 |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('has correct highlighting with multi-byte characters', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4, { virt_text = { { 'múlti-byté chñröcters 修补', 'Special' } }, virt_text_pos = 'inline' })
+
+ screen:expect{grid=[[
+ 1234{10:múlti-byté chñröcters 修补}567^8 |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('has correct cursor position when inserting around virtual text', function()
+ insert('12345678')
+ meths.buf_set_extmark(0, ns, 0, 4, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed '^'
+ feed '3l'
+ feed 'a'
+ screen:expect{grid=[[
+ 1234{10:^virtual text}5678 |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed '<ESC>'
+ screen:expect{grid=[[
+ 123^4{10:virtual text}5678 |
+ {1:~ }|
+ |
+ ]]}
+ feed '^'
+ feed '4l'
+ feed 'i'
+ screen:expect{grid=[[
+ 1234{10:^virtual text}5678 |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ end)
+
+ it('has correct cursor position with virtual text on an empty line', function()
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ {10:^virtual text} |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('text is drawn correctly with a wrapping virtual text', function()
+ screen:try_resize(60, 8)
+ exec([[
+ call setline(1, ['', 'aaa', '', 'bbbbbb'])
+ normal gg0
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('X', 60), 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 2, 0, { virt_text = { { string.rep('X', 61), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect{grid=[[
+ {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ aaa |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('j')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ aa^a |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('j')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ aaa |
+ {10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('j')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ aaa |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ bbbbb^b |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('0<C-V>2l2k')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {7:aa}^a |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ {7:bbb}bbb |
+ {1:~ }|
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed([[<Esc>/aaa\n\%V<CR>]])
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {12:^aaa } |
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:X} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {16:search hit BOTTOM, continuing at TOP} |
+ ]]}
+ feed('3ggic')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {12:aaa } |
+ c{10:^XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:XX} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed([[<Esc>/aaa\nc\%V<CR>]])
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {12:^aaa } |
+ {12:c}{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:XX} |
+ bbbbbb |
+ {1:~ }|
+ {1:~ }|
+ {16:search hit BOTTOM, continuing at TOP} |
+ ]]}
+ end)
+
+ it('cursor position is correct with virtual text attached to hard TABs', function()
+ command('set noexpandtab')
+ feed('i')
+ feed('<TAB>')
+ feed('<TAB>')
+ feed('test')
+ feed('<ESC>')
+ meths.buf_set_extmark(0, ns, 0, 1, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('0')
+ screen:expect{grid=[[
+ ^ {10:virtual text} test |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect{grid=[[
+ {10:virtual text} ^ test |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect{grid=[[
+ {10:virtual text} ^test |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect{grid=[[
+ {10:virtual text} t^est |
+ {1:~ }|
+ |
+ ]]}
+
+ feed('l')
+ screen:expect{grid=[[
+ {10:virtual text} te^st |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('cursor position is correct with virtual text on an empty line', function()
+ command('set linebreak')
+ insert('one twoword')
+ feed('0')
+ meths.buf_set_extmark(0, ns, 0, 3, { virt_text = { { ': virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ ^one{10:: virtual text} twoword |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('search highlight is correct', function()
+ insert('foo foo foo bar\nfoo foo foo bar')
+ feed('gg0')
+ meths.buf_set_extmark(0, ns, 0, 9, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 9, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 9, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 9, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ screen:expect{grid=[[
+ ^foo foo f{10:AAABBB}oo bar |
+ foo foo f{10:CCCDDD}oo bar |
+ |
+ ]]}
+
+ feed('/foo')
+ screen:expect{grid=[[
+ {12:foo} {13:foo} {12:f}{10:AAA}{19:BBB}{12:oo} bar |
+ {12:foo} {12:foo} {12:f}{19:CCC}{10:DDD}{12:oo} bar |
+ /foo^ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 0, 13, { virt_text = { { 'EEE', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ feed('<C-G>')
+ screen:expect{grid=[[
+ {12:foo} {12:foo} {13:f}{10:AAA}{21:BBB}{13:oo} b{10:EEE}ar |
+ {12:foo} {12:foo} {12:f}{19:CCC}{10:DDD}{12:oo} bar |
+ /foo^ |
+ ]]}
+ end)
+
+ it('Visual select highlight is correct', function()
+ insert('foo foo foo bar\nfoo foo foo bar')
+ feed('gg0')
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ feed('8l')
+ screen:expect{grid=[[
+ foo foo {10:AAABBB}^foo bar |
+ foo foo {10:CCCDDD}foo bar |
+ |
+ ]]}
+
+ feed('<C-V>')
+ feed('2hj')
+ screen:expect{grid=[[
+ foo fo{7:o }{10:AAA}{20:BBB}{7:f}oo bar |
+ foo fo^o{7: }{20:CCC}{10:DDD}{7:f}oo bar |
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 0, 10, { virt_text = { { 'EEE', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ screen:expect{grid=[[
+ foo fo{7:o }{10:AAA}{20:BBB}{7:f}o{10:EEE}o bar |
+ foo fo^o{7: }{20:CCC}{10:DDD}{7:f}oo bar |
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ end)
+
+ it('inside highlight range of another extmark', function()
+ insert('foo foo foo bar\nfoo foo foo bar')
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ meths.buf_set_extmark(0, ns, 0, 4, { end_col = 11, hl_group = 'Search' })
+ meths.buf_set_extmark(0, ns, 1, 4, { end_col = 11, hl_group = 'Search' })
+ screen:expect{grid=[[
+ foo {12:foo }{10:AAA}{19:BBB}{12:foo} bar |
+ foo {12:foo }{19:CCC}{10:DDD}{12:foo} ba^r |
+ |
+ ]]}
+ end)
+
+ it('inside highlight range of syntax', function()
+ insert('foo foo foo bar\nfoo foo foo bar')
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'AAA', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { 'BBB', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'CCC', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 1, 8, { virt_text = { { 'DDD', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ command([[syntax match Search 'foo \zsfoo foo\ze bar']])
+ screen:expect{grid=[[
+ foo {12:foo }{10:AAA}{19:BBB}{12:foo} bar |
+ foo {12:foo }{19:CCC}{10:DDD}{12:foo} ba^r |
+ |
+ ]]}
+ end)
+
+ it('cursor position is correct when inserting around a virtual text with left gravity', function()
+ screen:try_resize(27, 4)
+ insert(('a'):rep(15))
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = { { ('>'):rep(43), 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
+ command('setlocal showbreak=+ breakindent breakindentopt=shift:2')
+ feed('08l')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}^aaaaaaa |
+ |
+ ]]}
+ feed('i')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}^aaaaaaa |
+ {8:-- INSERT --} |
+ ]]}
+ feed([[<C-\><C-O>]])
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}^aaaaaaa |
+ {8:-- (insert) --} |
+ ]]}
+ feed('D')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:^~ }|
+ {8:-- INSERT --} |
+ ]]}
+ command('setlocal list listchars=eol:$')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+^$} |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<C-U>')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>}{1:^$} |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('a')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>}a{1:^$} |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('<Esc>')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>}^a{1:$} |
+ {1:~ }|
+ |
+ ]]}
+ feed('x')
+ screen:expect{grid=[[
+ {10:^>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>>>>>>>}{1:$} |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('cursor position is correct when inserting around virtual texts with both left and right gravity', function()
+ screen:try_resize(30, 4)
+ command('setlocal showbreak=+ breakindent breakindentopt=shift:2')
+ insert(('a'):rep(15))
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = {{ ('>'):rep(32), 'Special' }}, virt_text_pos = 'inline', right_gravity = false })
+ meths.buf_set_extmark(0, ns, 0, 8, { virt_text = {{ ('<'):rep(32), 'Special' }}, virt_text_pos = 'inline', right_gravity = true })
+ feed('08l')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>><<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<}^aaaaaaa |
+ |
+ ]]}
+ feed('i')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>^<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<}aaaaaaa |
+ {8:-- INSERT --} |
+ ]]}
+ feed('a')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>}a{10:^<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<<}aaaaaaa |
+ {8:-- INSERT --} |
+ ]]}
+ feed([[<C-\><C-O>]])
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>}a{10:<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<<}^aaaaaaa |
+ {8:-- (insert) --} |
+ ]]}
+ feed('D')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>}a{10:^<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<BS>')
+ screen:expect{grid=[[
+ aaaaaaaa{10:>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>>>>>>>>>^<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<<<<<<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<C-U>')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>^<<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ feed('a')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>}a{10:^<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<Esc>')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>}^a{10:<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<<} |
+ |
+ ]]}
+ feed('x')
+ screen:expect{grid=[[
+ {10:^>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>><<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<} |
+ |
+ ]]}
+ feed('i')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:>>^<<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<<<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ screen:try_resize(32, 4)
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<<<} |
+ {8:-- INSERT --} |
+ ]]}
+ command('setlocal nobreakindent')
+ screen:expect{grid=[[
+ {10:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>}|
+ {1:+}{10:^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<}|
+ {1:+}{10:<} |
+ {8:-- INSERT --} |
+ ]]}
+ end)
+
+ it('draws correctly with no wrap multiple virtual text, where one is hidden', function()
+ insert('abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 50, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect{grid=[[
+ opqrstuvwxyzabcdefghijklmnopqrstuvwx{10:virtual text}y^z|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('draws correctly with no wrap and a long virtual text', function()
+ insert('abcdefghi')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}cdefgh^i|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('tabs are the correct length with no wrap following virtual text', function()
+ command('set nowrap')
+ feed('itest<TAB>a<ESC>')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg$')
+ screen:expect{grid=[[
+ {10:aaaaaaaaaaaaaaaaaaaaaaaaa}test ^a |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlighting does not extend with no wrap and a long virtual text', function()
+ insert('abcdef')
+ command("set nowrap")
+ meths.buf_set_extmark(0, ns, 0, 3, { virt_text = { { string.rep('X', 50), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect{grid=[[
+ {10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}de^f|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('hidden virtual text does not interfere with Visual highlight', function()
+ insert('abcdef')
+ command('set nowrap')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'XXX', 'Special' } }, virt_text_pos = 'inline' })
+ feed('V2zl')
+ screen:expect{grid=[[
+ {10:X}{7:abcde}^f |
+ {1:~ }|
+ {8:-- VISUAL LINE --} |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {7:abcde}^f |
+ {1:~ }|
+ {8:-- VISUAL LINE --} |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ {7:bcde}^f |
+ {1:~ }|
+ {8:-- VISUAL LINE --} |
+ ]]}
+ end)
+
+ it('highlighting is correct when virtual text wraps with number', function()
+ screen:try_resize(50, 5)
+ insert([[
+ test
+ test]])
+ command('set number')
+ meths.buf_set_extmark(0, ns, 0, 1, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg0')
+ screen:expect{grid=[[
+ {2: 1 }^t{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {2: }{10:XXXXXXXXXX}est |
+ {2: 2 }test |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('highlighting is correct when virtual text is proceeded with a match', function()
+ insert([[test]])
+ meths.buf_set_extmark(0, ns, 0, 2, { virt_text = { { 'virtual text', 'Special' } }, virt_text_pos = 'inline' })
+ feed('gg0')
+ command('match ErrorMsg /e/')
+ screen:expect{grid=[[
+ ^t{4:e}{10:virtual text}st |
+ {1:~ }|
+ |
+ ]]}
+ command('match ErrorMsg /s/')
+ screen:expect{grid=[[
+ ^te{10:virtual text}{4:s}t |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('smoothscroll works correctly when virtual text wraps', function()
+ insert('foobar')
+ meths.buf_set_extmark(0, ns, 0, 3, { virt_text = { { string.rep('X', 55), 'Special' } }, virt_text_pos = 'inline' })
+ command('setlocal smoothscroll')
+ screen:expect{grid=[[
+ foo{10:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}|
+ {10:XXXXXXXX}ba^r |
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}{10:XXXXX}ba^r |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('in diff mode is highlighted correct', function()
+ screen:try_resize(50, 10)
+ insert([[
+ 9000
+ 0009
+ 0009
+ 9000
+ 0009
+ ]])
+ insert('aaa\tbbb')
+ command("set diff")
+ meths.buf_set_extmark(0, ns, 0, 1, { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline', right_gravity = false })
+ meths.buf_set_extmark(0, ns, 5, 0, { virt_text = { { '!', 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 5, 3, { virt_text = { { '' } }, virt_text_pos = 'inline' })
+ command("vnew")
+ insert([[
+ 000
+ 000
+ 000
+ 000
+ 000
+ ]])
+ insert('aaabbb')
+ command("set diff")
+ feed('gg0')
+ screen:expect{grid=[[
+ {9:^000 }│{5:9}{14:test}{9:000 }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ {9:000 }│{5:9}{9:000 }|
+ {9:000 }│{9:000}{5:9}{9: }|
+ {9:aaabbb }│{14:!}{9:aaa}{5: }{9:bbb }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {15:[No Name] [+] }{13:[No Name] [+] }|
+ |
+ ]]}
+ command('wincmd w | set nowrap')
+ feed('zl')
+ screen:expect{grid=[[
+ {9:000 }│{14:test}{9:000 }|
+ {9:000 }│{9:00}{5:9}{9: }|
+ {9:000 }│{9:00}{5:9}{9: }|
+ {9:000 }│{9:000 }|
+ {9:000 }│{9:00}{5:9}{9: }|
+ {9:aaabbb }│{9:aaa}{5: }{9:bb^b }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {13:[No Name] [+] }{15:[No Name] [+] }|
+ |
+ ]]}
+ end)
+
+ it('correctly draws when there are multiple overlapping virtual texts on the same line with nowrap', function()
+ command('set nowrap')
+ insert('a')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('a', 55), 'Special' } }, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { string.rep('b', 55), 'Special' } }, virt_text_pos = 'inline' })
+ feed('$')
+ screen:expect{grid=[[
+ {10:bbbbbbbbbbbbbbbbbbbbbbbbb}^a |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('correctly draws when overflowing virtual text is followed by TAB with no wrap', function()
+ command('set nowrap')
+ feed('i<TAB>test<ESC>')
+ meths.buf_set_extmark( 0, ns, 0, 0, { virt_text = { { string.rep('a', 60), 'Special' } }, virt_text_pos = 'inline' })
+ feed('0')
+ screen:expect({grid=[[
+ {10:aaaaaaaaaaaaaaaaaaaaaa} ^ test |
+ {1:~ }|
+ |
+ ]]})
+ end)
+
+ it('does not crash at column 0 when folded in a wide window', function()
+ screen:try_resize(82, 5)
+ command('hi! CursorLine guibg=NONE guifg=Red gui=NONE')
+ command('set cursorline')
+ insert([[
+ aaaaa
+ bbbbb
+
+ ccccc]])
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = {{'foo'}}, virt_text_pos = 'inline' })
+ meths.buf_set_extmark(0, ns, 2, 0, { virt_text = {{'bar'}}, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ fooaaaaa |
+ bbbbb |
+ bar |
+ {16:cccc^c }|
+ |
+ ]]}
+ command('1,2fold')
+ screen:expect{grid=[[
+ {17:+-- 2 lines: aaaaa·······························································}|
+ bar |
+ {16:cccc^c }|
+ {1:~ }|
+ |
+ ]]}
+ feed('2k')
+ screen:expect{grid=[[
+ {18:^+-- 2 lines: aaaaa·······························································}|
+ bar |
+ ccccc |
+ {1:~ }|
+ |
+ ]]}
+ command('3,4fold')
+ screen:expect{grid=[[
+ {18:^+-- 2 lines: aaaaa·······························································}|
+ {17:+-- 2 lines: ccccc·······························································}|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('j')
+ screen:expect{grid=[[
+ {17:+-- 2 lines: aaaaa·······························································}|
+ {18:^+-- 2 lines: ccccc·······························································}|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('does not crash at right edge of wide window #23848', function()
+ screen:try_resize(82, 5)
+ meths.buf_set_extmark(0, ns, 0, 0, {virt_text = {{('a'):rep(82)}, {'b'}}, virt_text_pos = 'inline'})
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ b |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ command('set nowrap')
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('82i0<Esc>0')
+ screen:expect{grid=[[
+ ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ command('set wrap')
+ screen:expect{grid=[[
+ ^0000000000000000000000000000000000000000000000000000000000000000000000000000000000|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ b |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('lcs-extends is drawn with inline virtual text at end of screen line', function()
+ exec([[
+ setlocal nowrap list listchars=extends:!
+ call setline(1, repeat('a', 51))
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 50, { virt_text = { { 'bbb', 'Special' } }, virt_text_pos = 'inline' })
+ feed('20l')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:!}|
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:!}|
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:b}{1:!}|
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bb}{1:!}|
+ {1:~ }|
+ |
+ ]]}
+ feed('zl')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaa^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{10:bbb}a|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('lcs-extends is drawn with only inline virtual text offscreen', function()
+ command('set nowrap')
+ command('set list')
+ command('set listchars+=extends:c')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_text = { { 'test', 'Special' } }, virt_text_pos = 'inline' })
+ insert(string.rep('a', 50))
+ feed('gg0')
+ screen:expect{grid=[[
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:c}|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('blockwise Visual highlight with double-width virtual text (replace)', function()
+ screen:try_resize(60, 6)
+ insert('123456789\n123456789\n123456789\n123456789')
+ meths.buf_set_extmark(0, ns, 1, 1, { virt_text = { { '-口-', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ meths.buf_set_extmark(0, ns, 2, 2, { virt_text = { { '口', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'replace' })
+ feed('gg0')
+ screen:expect{grid=[[
+ ^123456789 |
+ 1{10:-口-}23456789 |
+ 12{10:口}3456789 |
+ 123456789 |
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-V>3jl')
+ screen:expect{grid=[[
+ {7:12}3456789 |
+ {7:1}{10:-口-}23456789 |
+ {7:12}{10:口}3456789 |
+ {7:1}^23456789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ {7:123}456789 |
+ {7:1}{10:-口-}23456789 |
+ {7:12}{10:口}3456789 |
+ {7:12}^3456789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('4l')
+ screen:expect{grid=[[
+ {7:1234567}89 |
+ {7:1}{10:-口-}{7:23}456789 |
+ {7:12}{10:口}{7:345}6789 |
+ {7:123456}^789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('Ol')
+ screen:expect{grid=[[
+ 1{7:234567}89 |
+ 1{10:-口-}{7:23}456789 |
+ 1{7:2}{10:口}{7:345}6789 |
+ 1^2{7:34567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ 12{7:34567}89 |
+ 1{10:-口-}{7:23}456789 |
+ 12{10:口}{7:345}6789 |
+ 12^3{7:4567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ 123{7:4567}89 |
+ 1{10:-口-}{7:23}456789 |
+ 12{10:口}{7:345}6789 |
+ 123^4{7:567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ end)
+
+ it('blockwise Visual highlight with double-width virtual text (combine)', function()
+ screen:try_resize(60, 6)
+ insert('123456789\n123456789\n123456789\n123456789')
+ meths.buf_set_extmark(0, ns, 1, 1, { virt_text = { { '-口-', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ meths.buf_set_extmark(0, ns, 2, 2, { virt_text = { { '口', 'Special' } }, virt_text_pos = 'inline', hl_mode = 'combine' })
+ feed('gg0')
+ screen:expect{grid=[[
+ ^123456789 |
+ 1{10:-口-}23456789 |
+ 12{10:口}3456789 |
+ 123456789 |
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-V>3jl')
+ screen:expect{grid=[[
+ {7:12}3456789 |
+ {7:1}{20:-}{10:口-}23456789 |
+ {7:12}{10:口}3456789 |
+ {7:1}^23456789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ {7:123}456789 |
+ {7:1}{20:-口}{10:-}23456789 |
+ {7:12}{20:口}3456789 |
+ {7:12}^3456789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('4l')
+ screen:expect{grid=[[
+ {7:1234567}89 |
+ {7:1}{20:-口-}{7:23}456789 |
+ {7:12}{20:口}{7:345}6789 |
+ {7:123456}^789 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('Ol')
+ screen:expect{grid=[[
+ 1{7:234567}89 |
+ 1{20:-口-}{7:23}456789 |
+ 1{7:2}{20:口}{7:345}6789 |
+ 1^2{7:34567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ 12{7:34567}89 |
+ 1{10:-}{20:口-}{7:23}456789 |
+ 12{20:口}{7:345}6789 |
+ 12^3{7:4567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ feed('l')
+ screen:expect{grid=[[
+ 123{7:4567}89 |
+ 1{10:-}{20:口-}{7:23}456789 |
+ 12{20:口}{7:345}6789 |
+ 123^4{7:567}89 |
+ {1:~ }|
+ {8:-- VISUAL BLOCK --} |
+ ]]}
+ end)
+
+ local function test_virt_inline_showbreak_smoothscroll()
+ screen:try_resize(30, 6)
+ exec([[
+ highlight! link LineNr Normal
+ setlocal number showbreak=+ breakindent breakindentopt=shift:2
+ setlocal scrolloff=0 smoothscroll
+ call setline(1, repeat('a', 28))
+ normal! $
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 27, { virt_text = { { ('123'):rep(23) } }, virt_text_pos = 'inline' })
+ feed(':<CR>') -- Have a screen line that doesn't start with spaces
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}a1231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}12312312312312312312312|
+ {1:+}3^a |
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}a1231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}12312312312312312312312|
+ {1:+}3^a |
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}23123123123123123123123|
+ {1:+}12312312312312312312312|
+ {1:+}3^a |
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}12312312312312312312312|
+ {1:+}3^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}3^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('zbi')
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}a^1231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}12312312312312312312312|
+ {1:+}3a |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<BS>')
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}^12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}a |
+ {8:-- INSERT --} |
+ ]]}
+ feed('<Esc>l')
+ feed(':<CR>') -- Have a screen line that doesn't start with spaces
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('023x$')
+ screen:expect{grid=[[
+ 1 aaa12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}23123123123123123123123|
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ : |
+ ]]}
+ feed('zbi')
+ screen:expect{grid=[[
+ 1 aaa^12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:+}a |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('<C-U>')
+ screen:expect{grid=[[
+ 1 ^12312312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123a |
+ {1:~ }|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('<Esc>')
+ screen:expect{grid=[[
+ 1 12312312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123^a |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:+}23123123123123123123^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('zbx')
+ screen:expect{grid=[[
+ 1 ^12312312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123 |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('26ia<Esc>a')
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}^12312312312312312312312|
+ {1:+}31231231231231231231231|
+ {1:+}23123123123123123123123|
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed([[<C-\><C-O>:setlocal breakindentopt=<CR>]])
+ screen:expect{grid=[[
+ 1 aaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}^1231231231231231231231231|
+ {1:+}2312312312312312312312312|
+ {1:+}3123123123123123123 |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ end
+
+ describe('with showbreak, smoothscroll', function()
+ it('and cpoptions-=n', function()
+ test_virt_inline_showbreak_smoothscroll()
+ end)
+
+ it('and cpoptions+=n', function()
+ command('set cpoptions+=n')
+ -- because of 'breakindent' the screen states are the same
+ test_virt_inline_showbreak_smoothscroll()
+ end)
+ end)
+
+ it('before TABs with smoothscroll', function()
+ screen:try_resize(30, 6)
+ exec([[
+ setlocal list listchars=tab:<-> scrolloff=0 smoothscroll
+ call setline(1, repeat("\t", 4) .. 'a')
+ normal! $
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 3, { virt_text = { { ('12'):rep(32) } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ {1:<------><------><------>}121212|
+ 121212121212121212121212121212|
+ 1212121212121212121212121212{1:<-}|
+ {1:----->}^a |
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}212121212121212121212121212|
+ 1212121212121212121212121212{1:<-}|
+ {1:----->}^a |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}2121212121212121212121212{1:<-}|
+ {1:----->}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<-->}^a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('zbh')
+ screen:expect{grid=[[
+ {1:<------><------><------>}121212|
+ 121212121212121212121212121212|
+ 1212121212121212121212121212{1:^<-}|
+ {1:----->}a |
+ {1:~ }|
+ |
+ ]]}
+ feed('i')
+ screen:expect{grid=[[
+ {1:<------><------><------>}^121212|
+ 121212121212121212121212121212|
+ 1212121212121212121212121212{1:<-}|
+ {1:----->}a |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('<C-O>:setlocal nolist<CR>')
+ screen:expect{grid=[[
+ ^121212|
+ 121212121212121212121212121212|
+ 1212121212121212121212121212 |
+ a |
+ {1:~ }|
+ {8:-- INSERT --} |
+ ]]}
+ feed('<Esc>l')
+ screen:expect{grid=[[
+ 121212|
+ 121212121212121212121212121212|
+ 1212121212121212121212121212 |
+ ^ a |
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}212121212121212121212121212|
+ 1212121212121212121212121212 |
+ ^ a |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<}2121212121212121212121212 |
+ ^ a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('<C-E>')
+ screen:expect{grid=[[
+ {1:<<<} ^ a |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('before a space with linebreak', function()
+ screen:try_resize(50, 6)
+ exec([[
+ setlocal linebreak showbreak=+ breakindent breakindentopt=shift:2
+ call setline(1, repeat('a', 50) .. ' ' .. repeat('c', 45))
+ normal! $
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 50, { virt_text = { { ('b'):rep(10) } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:+}bbbbbbbbbb |
+ {1:+}cccccccccccccccccccccccccccccccccccccccccccc^c |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ feed('05x$')
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbb|
+ {1:+}bbbbb |
+ {1:+}cccccccccccccccccccccccccccccccccccccccccccc^c |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('before double-width char that wraps', function()
+ exec([[
+ call setline(1, repeat('a', 40) .. '口' .. '12345')
+ normal! $
+ ]])
+ meths.buf_set_extmark(0, ns, 0, 40, { virt_text = { { ('b'):rep(9) } }, virt_text_pos = 'inline' })
+ screen:expect{grid=[[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}|
+ 口1234^5 |
+ |
+ ]]}
+ end)
end)
describe('decorations: virtual lines', function()
@@ -1055,7 +3781,7 @@ describe('decorations: virtual lines', function()
screen:attach()
screen:set_default_attr_ids {
[1] = {bold=true, foreground=Screen.colors.Blue};
- [2] = {foreground = Screen.colors.Cyan4};
+ [2] = {foreground = Screen.colors.DarkCyan};
[3] = {background = Screen.colors.Yellow1};
[4] = {bold = true};
[5] = {background = Screen.colors.Yellow, foreground = Screen.colors.Blue};
@@ -1068,7 +3794,7 @@ describe('decorations: virtual lines', function()
ns = meths.create_namespace 'test'
end)
- local example_text = [[
+ local example_text2 = [[
if (h->n_buckets < new_n_buckets) { // expand
khkey_t *new_keys = (khkey_t *)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t));
h->keys = new_keys;
@@ -1079,7 +3805,7 @@ if (h->n_buckets < new_n_buckets) { // expand
}]]
it('works with one line', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, 33, {
virt_lines={ {{">> ", "NonText"}, {"krealloc", "Identifier"}, {": change the size of an allocation"}}};
@@ -1170,6 +3896,7 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
meths.buf_clear_namespace(0, ns, 0, -1)
+ -- Cursor should be drawn on the correct line. #22704
screen:expect{grid=[[
if (h->n_buckets < new_n_buckets) { // expand |
khkey_t *new_keys = (khkey_t *) |
@@ -1177,8 +3904,8 @@ if (h->n_buckets < new_n_buckets) { // expand
hkey_t)); |
h->keys = new_keys; |
if (kh_is_map && val_size) { |
- char *new_vals = {3:krealloc}( h->vals_buf, new_n_|
- buck^ets * val_size); |
+ ^char *new_vals = {3:krealloc}( h->vals_buf, new_n_|
+ buckets * val_size); |
h->vals_buf = new_vals; |
} |
} |
@@ -1186,9 +3913,8 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
end)
-
it('works with text at the beginning of the buffer', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
screen:expect{grid=[[
@@ -1249,7 +3975,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works with text at the end of the buffer', function()
- insert(example_text)
+ insert(example_text2)
feed 'G'
screen:expect{grid=[[
@@ -1368,7 +4094,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works beyond end of the buffer with virt_lines_above', function()
- insert(example_text)
+ insert(example_text2)
feed 'G'
screen:expect{grid=[[
@@ -1639,7 +4365,7 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
it('works with sign and numbercolumns', function()
- insert(example_text)
+ insert(example_text2)
feed 'gg'
command 'set number signcolumn=yes'
screen:expect{grid=[[
@@ -1704,8 +4430,8 @@ if (h->n_buckets < new_n_buckets) { // expand
end)
- it('works with hard tabs', function()
- insert(example_text)
+ it('works with hard TABs', function()
+ insert(example_text2)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, 0, {
virt_lines={ {{">>", "NonText"}, {"\tvery\ttabby", "Identifier"}, {"text\twith\ttabs"}}};
@@ -1774,6 +4500,106 @@ if (h->n_buckets < new_n_buckets) { // expand
]]}
end)
+ it('does not show twice if end_row or end_col is specified #18622', function()
+ screen:try_resize(50, 8)
+ insert([[
+ aaa
+ bbb
+ ccc
+ ddd]])
+ meths.buf_set_extmark(0, ns, 0, 0, {end_row = 2, virt_lines = {{{'VIRT LINE 1', 'NonText'}}}})
+ meths.buf_set_extmark(0, ns, 3, 0, {end_col = 2, virt_lines = {{{'VIRT LINE 2', 'NonText'}}}})
+ screen:expect{grid=[[
+ aaa |
+ {1:VIRT LINE 1} |
+ bbb |
+ ccc |
+ dd^d |
+ {1:VIRT LINE 2} |
+ {1:~ }|
+ |
+ ]]}
+ end)
+
+ it('works with rightleft', function()
+ screen:try_resize(50, 8)
+ insert([[
+ aaa
+ bbb
+ ccc
+ ddd]])
+ command('set number rightleft')
+ meths.buf_set_extmark(0, ns, 0, 0, {virt_lines = {{{'VIRT LINE 1', 'NonText'}}}, virt_lines_leftcol = true})
+ meths.buf_set_extmark(0, ns, 3, 0, {virt_lines = {{{'VIRT LINE 2', 'NonText'}}}})
+ screen:expect{grid=[[
+ aaa{9: 1 }|
+ {1:1 ENIL TRIV}|
+ bbb{9: 2 }|
+ ccc{9: 3 }|
+ ^ddd{9: 4 }|
+ {1:2 ENIL TRIV}{9: }|
+ {1: ~}|
+ |
+ ]]}
+ end)
+
+ it('works when using dd or yyp #23915 #23916', function()
+ insert([[
+ line1
+ line2
+ line3
+ line4
+ line5]])
+ meths.buf_set_extmark(0, ns, 0, 0, {virt_lines={{{"foo"}}, {{"bar"}}, {{"baz"}}}})
+ screen:expect{grid=[[
+ line1 |
+ foo |
+ bar |
+ baz |
+ line2 |
+ line3 |
+ line4 |
+ line^5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('gg')
+ feed('dd')
+ screen:expect{grid=[[
+ ^line2 |
+ foo |
+ bar |
+ baz |
+ line3 |
+ line4 |
+ line5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ feed('yyp')
+ screen:expect{grid=[[
+ line2 |
+ foo |
+ bar |
+ baz |
+ ^line2 |
+ line3 |
+ line4 |
+ line5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
end)
describe('decorations: signs', function()
@@ -1789,10 +4615,10 @@ describe('decorations: signs', function()
}
ns = meths.create_namespace 'test'
- meths.win_set_option(0, 'signcolumn', 'auto:9')
+ meths.set_option_value('signcolumn', 'auto:9', {})
end)
- local example_text = [[
+ local example_test3 = [[
l1
l2
l3
@@ -1801,7 +4627,7 @@ l5
]]
it('can add a single sign (no end row)', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S'})
@@ -1822,7 +4648,7 @@ l5
end)
it('can add a single sign (with end row)', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1})
@@ -1842,9 +4668,29 @@ l5
end)
+ it('can add a single sign and text highlight', function()
+ insert(example_test3)
+ feed 'gg'
+
+ meths.buf_set_extmark(0, ns, 1, 0, {sign_text='S', hl_group='Todo', end_col=1})
+ screen:expect{grid=[[
+ {1: }^l1 |
+ S {3:l}2 |
+ {1: }l3 |
+ {1: }l4 |
+ {1: }l5 |
+ {1: } |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]]}
+
+ meths.buf_clear_namespace(0, ns, 0, -1)
+ end)
+
it('can add multiple signs (single extmark)', function()
- pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2})
@@ -1865,8 +4711,7 @@ l5
end)
it('can add multiple signs (multiple extmarks)', function()
- pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'})
@@ -1884,21 +4729,19 @@ l5
{2:~ }|
|
]]}
-
end)
it('can add multiple signs (multiple extmarks) 2', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
- meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'})
- meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'})
-
+ meths.buf_set_extmark(0, ns, 3, -1, {sign_text='S1'})
+ meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row = 3})
screen:expect{grid=[[
{1: }^l1 |
- S2S1l2 |
- {1: }l3 |
- {1: }l4 |
+ S2{1: }l2 |
+ S2{1: }l3 |
+ S1S2l4 |
{1: }l5 |
{1: } |
{2:~ }|
@@ -1906,29 +4749,11 @@ l5
{2:~ }|
|
]]}
-
- -- TODO(lewis6991): Support ranged signs
- -- meths.buf_set_extmark(1, ns, 1, -1, {sign_text='S3', end_row = 2})
-
- -- screen:expect{grid=[[
- -- {1: }^l1 |
- -- S3S2S1l2 |
- -- S3{1: }l3 |
- -- {1: }l4 |
- -- {1: }l5 |
- -- {1: } |
- -- {2:~ }|
- -- {2:~ }|
- -- {2:~ }|
- -- |
- -- ]]}
-
end)
it('can add multiple signs (multiple extmarks) 3', function()
- pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2})
@@ -1937,7 +4762,7 @@ l5
screen:expect{grid=[[
{1: }^l1 |
S1{1: }l2 |
- S2S1l3 |
+ S1S2l3 |
S2{1: }l4 |
{1: }l5 |
{1: } |
@@ -1949,7 +4774,7 @@ l5
end)
it('can add multiple signs (multiple extmarks) 4', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0})
@@ -1970,7 +4795,7 @@ l5
end)
it('works with old signs', function()
- insert(example_text)
+ insert(example_test3)
feed 'gg'
helpers.command('sign define Oldsign text=x')
@@ -1982,7 +4807,7 @@ l5
meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'})
screen:expect{grid=[[
- S4S1^l1 |
+ S1S4^l1 |
x S2l2 |
S5{1: }l3 |
{1: }l4 |
@@ -1996,8 +4821,7 @@ l5
end)
it('works with old signs (with range)', function()
- pending('TODO(lewis6991): Support ranged signs')
- insert(example_text)
+ insert(example_test3)
feed 'gg'
helpers.command('sign define Oldsign text=x')
@@ -2010,9 +4834,9 @@ l5
meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'})
screen:expect{grid=[[
- S3S4S1^l1 |
- S2S3x l2 |
- S5S3{1: }l3 |
+ S1S3S4^l1 |
+ x S2S3l2 |
+ S3S5{1: }l3 |
S3{1: }l4 |
S3{1: }l5 |
{1: } |
@@ -2024,9 +4848,7 @@ l5
end)
it('can add a ranged sign (with start out of view)', function()
- pending('TODO(lewis6991): Support ranged signs')
-
- insert(example_text)
+ insert(example_test3)
command 'set signcolumn=yes:2'
feed 'gg'
feed '2<C-e>'
@@ -2068,26 +4890,26 @@ l5
end
screen:expect{grid=[[
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
- X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} |
+ W X Y Z {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} |
|
]]}
end)
it('works with priority #19716', function()
screen:try_resize(20, 3)
- insert(example_text)
+ insert(example_test3)
feed 'gg'
- helpers.command('sign define Oldsign text=O3')
- helpers.command([[exe 'sign place 42 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command('sign define Oldsign text=O3')
+ command([[exe 'sign place 42 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4', priority=100})
meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S2', priority=5})
@@ -2101,7 +4923,7 @@ l5
]]}
-- Check truncation works too
- meths.win_set_option(0, 'signcolumn', 'auto')
+ meths.set_option_value('signcolumn', 'auto', {})
screen:expect{grid=[[
S5^l1 |
@@ -2109,6 +4931,52 @@ l5
|
]]}
end)
+
+ it('does not overflow with many old signs #23852', function()
+ screen:try_resize(20, 3)
+
+ command('set signcolumn:auto:9')
+ command('sign define Oldsign text=O3')
+ command([[exe 'sign place 01 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 02 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 03 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 04 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 05 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 06 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 07 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 08 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ command([[exe 'sign place 09 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]])
+ screen:expect{grid=[[
+ O3O3O3O3O3O3O3O3O3^ |
+ {2:~ }|
+ |
+ ]]}
+
+ meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1})
+ screen:expect_unchanged()
+
+ meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200})
+ screen:expect{grid=[[
+ O3O3O3O3O3O3O3O3S5^ |
+ {2:~ }|
+ |
+ ]]}
+
+ assert_alive()
+ end)
+
+ it('does not set signcolumn for signs without text', function()
+ screen:try_resize(20, 3)
+ meths.set_option_value('signcolumn', 'auto', {})
+ insert(example_test3)
+ feed 'gg'
+ meths.buf_set_extmark(0, ns, 0, -1, {number_hl_group='Error'})
+ screen:expect{grid=[[
+ ^l1 |
+ l2 |
+ |
+ ]]}
+ end)
end)
describe('decorations: virt_text', function()
diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua
index dbdf3823ec..92b7235885 100644
--- a/test/functional/ui/diff_spec.lua
+++ b/test/functional/ui/diff_spec.lua
@@ -1251,6 +1251,98 @@ AAAB]]
]]}
end)
end)
+
+ it('redraws with a change to non-current buffer', function()
+ write_file(fname, "aaa\nbbb\nccc\n\nxx", false)
+ write_file(fname_2, "aaa\nbbb\nccc\n\nyy", false)
+ reread()
+ local buf = meths.get_current_buf()
+ command('botright new')
+ screen:expect{grid=[[
+ {1: }aaa │{1: }aaa |
+ {1: }bbb │{1: }bbb |
+ {1: }ccc │{1: }ccc |
+ {1: } │{1: } |
+ {1: }{8:xx}{9: }│{1: }{8:yy}{9: }|
+ {6:~ }│{6:~ }|
+ {3:<onal-diff-screen-1 <l-diff-screen-1.2 }|
+ ^ |
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {7:[No Name] }|
+ :e |
+ ]]}
+
+ meths.buf_set_lines(buf, 1, 2, true, {'BBB'})
+ screen:expect{grid=[[
+ {1: }aaa │{1: }aaa |
+ {1: }{8:BBB}{9: }│{1: }{8:bbb}{9: }|
+ {1: }ccc │{1: }ccc |
+ {1: } │{1: } |
+ {1: }{8:xx}{9: }│{1: }{8:yy}{9: }|
+ {6:~ }│{6:~ }|
+ {3:<-diff-screen-1 [+] <l-diff-screen-1.2 }|
+ ^ |
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {6:~ }|
+ {7:[No Name] }|
+ :e |
+ ]]}
+ end)
+
+ it('redraws with a change current buffer in another window', function()
+ write_file(fname, "aaa\nbbb\nccc\n\nxx", false)
+ write_file(fname_2, "aaa\nbbb\nccc\n\nyy", false)
+ reread()
+ local buf = meths.get_current_buf()
+ command('botright split | diffoff')
+ screen:expect{grid=[[
+ {1: }aaa │{1: }aaa |
+ {1: }bbb │{1: }bbb |
+ {1: }ccc │{1: }ccc |
+ {1: } │{1: } |
+ {1: }{8:xx}{9: }│{1: }{8:yy}{9: }|
+ {6:~ }│{6:~ }|
+ {3:<onal-diff-screen-1 <l-diff-screen-1.2 }|
+ ^aaa |
+ bbb |
+ ccc |
+ |
+ xx |
+ {6:~ }|
+ {6:~ }|
+ {7:Xtest-functional-diff-screen-1 }|
+ :e |
+ ]]}
+
+ meths.buf_set_lines(buf, 1, 2, true, {'BBB'})
+ screen:expect{grid=[[
+ {1: }aaa │{1: }aaa |
+ {1: }{8:BBB}{9: }│{1: }{8:bbb}{9: }|
+ {1: }ccc │{1: }ccc |
+ {1: } │{1: } |
+ {1: }{8:xx}{9: }│{1: }{8:yy}{9: }|
+ {6:~ }│{6:~ }|
+ {3:<-diff-screen-1 [+] <l-diff-screen-1.2 }|
+ ^aaa |
+ BBB |
+ ccc |
+ |
+ xx |
+ {6:~ }|
+ {6:~ }|
+ {7:Xtest-functional-diff-screen-1 [+] }|
+ :e |
+ ]]}
+ end)
end)
it('win_update redraws lines properly', function()
@@ -1325,6 +1417,7 @@ it('win_update redraws lines properly', function()
]]}
end)
+-- oldtest: Test_diff_rnu()
it('diff updates line numbers below filler lines', function()
clear()
local screen = Screen.new(40, 14)
@@ -1401,6 +1494,7 @@ it('diff updates line numbers below filler lines', function()
]])
end)
+-- oldtest: Test_diff_with_scroll_and_change()
it('Align the filler lines when changing text in diff mode', function()
clear()
local screen = Screen.new(40, 20)
diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua
index cd2b48213d..9729f65355 100644
--- a/test/functional/ui/embed_spec.lua
+++ b/test/functional/ui/embed_spec.lua
@@ -5,7 +5,12 @@ local Screen = require('test.functional.ui.screen')
local feed = helpers.feed
local eq = helpers.eq
+local neq = helpers.neq
local clear = helpers.clear
+local ok = helpers.ok
+local funcs = helpers.funcs
+local nvim_prog = helpers.nvim_prog
+local retry = helpers.retry
local function test_embed(ext_linegrid)
local screen
@@ -98,6 +103,7 @@ describe('--embed UI', function()
-- attach immediately after startup, for early UI
local screen = Screen.new(40, 8)
+ screen.rpc_async = true -- Avoid hanging. #24888
screen:attach {stdin_fd=3}
screen:set_default_attr_ids {
[1] = {bold = true, foreground = Screen.colors.Blue1};
@@ -132,3 +138,50 @@ describe('--embed UI', function()
]]}
end)
end)
+
+describe('--embed --listen UI', function()
+ it('waits for connection on listening address', function()
+ helpers.skip(helpers.is_os('win'))
+ clear()
+ local child_server = assert(helpers.new_pipename())
+ funcs.jobstart({nvim_prog, '--embed', '--listen', child_server, '--clean'})
+ retry(nil, nil, function() neq(nil, uv.fs_stat(child_server)) end)
+
+ local child_session = helpers.connect(child_server)
+
+ local info_ok, api_info = child_session:request('nvim_get_api_info')
+ ok(info_ok)
+ eq(2, #api_info)
+ ok(api_info[1] > 2, 'channel_id > 2', api_info[1])
+
+ child_session:request('nvim_exec2', [[
+ let g:evs = []
+ autocmd UIEnter * call add(g:evs, $"UIEnter:{v:event.chan}")
+ autocmd VimEnter * call add(g:evs, "VimEnter")
+ ]], {})
+
+ -- VimEnter and UIEnter shouldn't be triggered until after attach
+ local var_ok, var = child_session:request('nvim_get_var', 'evs')
+ ok(var_ok)
+ eq({}, var)
+
+ local child_screen = Screen.new(40, 6)
+ child_screen:attach(nil, child_session)
+ child_screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] 0,0-1 All}|
+ |
+ ]], attr_ids={
+ [1] = {foreground = Screen.colors.Blue, bold = true};
+ [2] = {reverse = true, bold = true};
+ }}
+
+ -- VimEnter and UIEnter should now be triggered
+ var_ok, var = child_session:request('nvim_get_var', 'evs')
+ ok(var_ok)
+ eq({'VimEnter', ('UIEnter:%d'):format(api_info[1])}, var)
+ end)
+end)
diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua
index 6759510ad1..2902b4a4a5 100644
--- a/test/functional/ui/float_spec.lua
+++ b/test/functional/ui/float_spec.lua
@@ -9,6 +9,7 @@ local eval = helpers.eval
local eq = helpers.eq
local neq = helpers.neq
local expect = helpers.expect
+local exec = helpers.exec
local exec_lua = helpers.exec_lua
local insert = helpers.insert
local meths = helpers.meths
@@ -65,20 +66,20 @@ describe('float window', function()
it('closed immediately by autocmd #11383', function()
eq('Window was closed immediately',
pcall_err(exec_lua, [[
- local a = vim.api
+ local api = vim.api
local function crashes(contents)
- local buf = a.nvim_create_buf(false, true)
- local floatwin = a.nvim_open_win(buf, true, {
+ local buf = api.nvim_create_buf(false, true)
+ local floatwin = api.nvim_open_win(buf, true, {
relative = 'cursor';
style = 'minimal';
row = 0; col = 0;
height = #contents;
width = 10;
})
- a.nvim_buf_set_lines(buf, 0, -1, true, contents)
+ api.nvim_buf_set_lines(buf, 0, -1, true, contents)
local winnr = vim.fn.win_id2win(floatwin)
- a.nvim_command('wincmd p')
- a.nvim_command('autocmd CursorMoved * ++once '..winnr..'wincmd c')
+ api.nvim_command('wincmd p')
+ api.nvim_command('autocmd BufEnter * ++once '..winnr..'wincmd c')
return buf, floatwin
end
crashes{'foo'}
@@ -102,9 +103,32 @@ describe('float window', function()
assert_alive()
end)
+ it('open with WinNew autocmd', function()
+ local res = exec_lua([[
+ local triggerd = false
+ local buf = vim.api.nvim_create_buf(true, true)
+ vim.api.nvim_create_autocmd('WinNew', {
+ callback = function(opt)
+ if opt.buf == buf then
+ triggerd = true
+ end
+ end
+ })
+ local opts = {
+ relative = "win",
+ row = 0, col = 0,
+ width = 1, height = 1,
+ noautocmd = false,
+ }
+ vim.api.nvim_open_win(buf, true, opts)
+ return triggerd
+ ]])
+ eq(true, res)
+ end)
+
it('opened with correct height', function()
local height = exec_lua([[
- vim.api.nvim_set_option("winheight", 20)
+ vim.go.winheight = 20
local bufnr = vim.api.nvim_create_buf(false, true)
local opts = {
@@ -126,7 +150,7 @@ describe('float window', function()
it('opened with correct width', function()
local width = exec_lua([[
- vim.api.nvim_set_option("winwidth", 20)
+ vim.go.winwidth = 20
local bufnr = vim.api.nvim_create_buf(false, true)
local opts = {
@@ -414,6 +438,46 @@ describe('float window', function()
eq(winids, eval('winids'))
end)
+ it("open does not trigger BufEnter #15300", function()
+ local res = exec_lua[[
+ local times = {}
+ local buf = vim.api.nvim_create_buf(fasle, true)
+ vim.api.nvim_create_autocmd('BufEnter', {
+ callback = function(opt)
+ if opt.buf == buf then
+ times[#times + 1] = 1
+ end
+ end
+ })
+ local win_id
+ local fconfig = {
+ relative = 'editor',
+ row = 10,
+ col = 10,
+ width = 10,
+ height = 10,
+ }
+ --enter is false doesn't trigger
+ win_id = vim.api.nvim_open_win(buf, false, fconfig)
+ vim.api.nvim_win_close(win_id, true)
+ times[#times + 1] = #times == 0 and true or nil
+
+ --enter is true trigger
+ win_id = vim.api.nvim_open_win(buf, true, fconfig)
+ vim.api.nvim_win_close(win_id, true)
+ times[#times + 1] = #times == 2 and true or nil
+
+ --enter is true and fconfig.noautocmd is true doesn't trigger
+ fconfig.noautocmd = true
+ win_id = vim.api.nvim_open_win(buf, true, fconfig)
+ vim.api.nvim_win_close(win_id, true)
+ times[#times + 1] = #times == 2 and true or nil
+
+ return times
+ ]]
+ eq({true, 1, true}, res)
+ end)
+
it('no crash with bufpos and non-existent window', function()
command('new')
local closed_win = meths.get_current_win().id
@@ -426,17 +490,57 @@ describe('float window', function()
it("no segfault when setting minimal style after clearing local 'fillchars' #19510", function()
local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1}
local float_win = meths.open_win(0, true, float_opts)
- meths.win_set_option(float_win, 'fillchars', NIL)
+ meths.set_option_value('fillchars', NIL, {win=float_win.id})
float_opts.style = 'minimal'
meths.win_set_config(float_win, float_opts)
assert_alive()
+ end)
+
+ it("should re-apply 'style' when present", function()
+ local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1}
+ local float_win = meths.open_win(0, true, float_opts)
+ meths.set_option_value('number', true, { win = float_win })
+ float_opts.row = 2
+ meths.win_set_config(float_win, float_opts)
+ eq(false, meths.get_option_value('number', { win = float_win }))
+ end)
+
+ it("should not re-apply 'style' when missing", function()
+ local float_opts = {style = 'minimal', relative = 'editor', row = 1, col = 1, width = 1, height = 1}
+ local float_win = meths.open_win(0, true, float_opts)
+ meths.set_option_value('number', true, { win = float_win })
+ float_opts.row = 2
+ float_opts.style = nil
+ meths.win_set_config(float_win, float_opts)
+ eq(true, meths.get_option_value('number', { win = float_win }))
end)
it("'scroll' is computed correctly when opening float with splitkeep=screen #20684", function()
- meths.set_option('splitkeep', 'screen')
+ meths.set_option_value('splitkeep', 'screen', {})
local float_opts = {relative = 'editor', row = 1, col = 1, width = 10, height = 10}
local float_win = meths.open_win(0, true, float_opts)
- eq(5, meths.win_get_option(float_win, 'scroll'))
+ eq(5, meths.get_option_value('scroll', {win=float_win.id}))
+ end)
+
+ it(':unhide works when there are floating windows', function()
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 5, height = 5}
+ local w0 = curwin()
+ meths.open_win(0, false, float_opts)
+ meths.open_win(0, false, float_opts)
+ eq(3, #meths.list_wins())
+ command('unhide')
+ eq({ w0 }, meths.list_wins())
+ end)
+
+ it(':all works when there are floating windows', function()
+ command('args Xa.txt')
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 5, height = 5}
+ local w0 = curwin()
+ meths.open_win(0, false, float_opts)
+ meths.open_win(0, false, float_opts)
+ eq(3, #meths.list_wins())
+ command('all')
+ eq({ w0 }, meths.list_wins())
end)
describe('with only one tabpage,', function()
@@ -738,7 +842,7 @@ describe('float window', function()
[4] = {bold = true, reverse = true},
[5] = {reverse = true},
[6] = {background = Screen.colors.LightMagenta, bold = true, reverse = true},
- [7] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [7] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[8] = {bold = true, foreground = Screen.colors.SeaGreen4},
[9] = {background = Screen.colors.LightGrey, underline = true},
[10] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta},
@@ -879,6 +983,71 @@ describe('float window', function()
end
end)
+ it('window position fixed', function()
+ command('rightbelow 20vsplit')
+ local buf = meths.create_buf(false,false)
+ local win = meths.open_win(buf, false, {
+ relative='win', width=15, height=2, row=2, col=10, anchor='NW', fixed=true})
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ {5:[No Name] }{4:[No Name] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 5
+ {1: }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 4, 2, 10, true};
+ }}
+ else
+ screen:expect([[
+ {5:│}^ |
+ {0:~ }{5:│}{0:~ }|
+ {0:~ }{5:│}{0:~ }{1: }|
+ {0:~ }{5:│}{0:~ }{2:~ }|
+ {0:~ }{5:│}{0:~ }|
+ {5:[No Name] }{4:[No Name] }|
+ |
+ ]])
+ end
+
+ meths.win_set_config(win, {fixed=false})
+
+ if multigrid then
+ screen:expect_unchanged()
+ else
+ screen:expect([[
+ {5:│}^ |
+ {0:~ }{5:│}{0:~ }|
+ {0:~ }{5:│}{0:~ }{1: }|
+ {0:~ }{5:│}{0:~ }{2:~ }|
+ {0:~ }{5:│}{0:~ }|
+ {5:[No Name] }{4:[No Name] }|
+ |
+ ]])
+ end
+ end)
+
it('draws correctly with redrawdebug=compositor', function()
-- NB: we do not test that it produces the "correct" debug info
-- (as it is intermediate only, and is allowed to change by internal
@@ -1003,14 +1172,14 @@ describe('float window', function()
it('return their configuration', function()
local buf = meths.create_buf(false, false)
local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=3, col=5, zindex=60})
- local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20, zindex=60}
+ local expected = {anchor='NW', col=5, external=false, focusable=true, height=2, relative='editor', row=3, width=20, zindex=60, hide=false}
eq(expected, meths.win_get_config(win))
- eq({relative='', external=false, focusable=true}, meths.win_get_config(0))
+ eq({relative='', external=false, focusable=true, hide=false}, meths.win_get_config(0))
if multigrid then
meths.win_set_config(win, {external=true, width=10, height=1})
- eq({external=true,focusable=true,width=10,height=1,relative=''}, meths.win_get_config(win))
+ eq({external=true,focusable=true,width=10,height=1,relative='',hide=false}, meths.win_get_config(win))
end
end)
@@ -1425,16 +1594,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔═════════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1468,16 +1637,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:┌─────────┐}|
{5:│}{1: halloj! }{5:│}|
{5:│}{1: BORDAA }{5:│}|
{5:└─────────┘}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1511,16 +1680,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╭─────────╮}|
{5:│}{1: halloj! }{5:│}|
{5:│}{1: BORDAA }{5:│}|
{5:╰─────────╯}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1554,16 +1723,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5: }|
{5: }{1: halloj! }{5: }|
{5: }{1: BORDAA }{5: }|
{5: }|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1598,16 +1767,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:x}{7:ååååååååå}{5:\}|
{17:n̈̊}{1: halloj! }{17:n̈̊}|
{17:n̈̊}{1: BORDAA }{17:n̈̊}|
{5:\}{7:ååååååååå}{5:x}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1641,14 +1810,14 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1: halloj! }|
{1: BORDAA }|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1682,14 +1851,14 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:<}{1: halloj! }{5:>}|
{5:<}{1: BORDAA }{5:>}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1723,16 +1892,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:_________}|
{1: halloj! }|
{1: BORDAA }|
{5:---------}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1774,15 +1943,15 @@ describe('float window', function()
^ |
## grid 3
|
- ## grid 5
+ ## grid 4
{1: halloj! }{25: }|
{1: BORDAA }{26: }|
{25: }{26: }|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 6, curline = 5, curcol = 0, linecount = 6};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 6, curline = 5, curcol = 0, linecount = 6, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1831,6 +2000,94 @@ describe('float window', function()
eq('center', title_pos)
end)
+ it('validates footer footer_pos', function()
+ local buf = meths.create_buf(false,false)
+ eq("footer requires border to be set",
+ pcall_err(meths.open_win,buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, footer='Footer',
+ }))
+ eq("footer_pos requires footer to be set",
+ pcall_err(meths.open_win,buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5,
+ border='single', footer_pos='left',
+ }))
+ end)
+
+ it('validate footer_pos in nvim_win_get_config', function()
+ local footer_pos = exec_lua([[
+ local bufnr = vim.api.nvim_create_buf(false, false)
+ local opts = {
+ relative = 'editor',
+ col = 2,
+ row = 5,
+ height = 2,
+ width = 9,
+ border = 'double',
+ footer = 'Test',
+ footer_pos = 'center'
+ }
+
+ local win_id = vim.api.nvim_open_win(bufnr, true, opts)
+ return vim.api.nvim_win_get_config(win_id).footer_pos
+ ]])
+
+ eq('center', footer_pos)
+ end)
+
+ it('center aligned title longer than window width #25746', function()
+ local buf = meths.create_buf(false, false)
+ meths.buf_set_lines(buf, 0, -1, true, {' halloj! ',
+ ' BORDAA '})
+ local win = meths.open_win(buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, border="double",
+ title = "abcdefghijklmnopqrstuvwxyz",title_pos = "center",
+ })
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔}{11:abcdefghi}{5:╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔}{11:abcdefghi}{5:╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_close(win, false)
+ assert_alive()
+ end)
it('border with title', function()
local buf = meths.create_buf(false, false)
@@ -1860,16 +2117,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔}{11:Left}{5:═════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1903,16 +2160,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔═}{11:Center}{5:══╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1946,16 +2203,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔════}{11:Right}{5:╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -1989,16 +2246,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔═════}🦄BB{5:╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -2013,9 +2270,376 @@ describe('float window', function()
end
end)
+ it('border with footer', function()
+ local buf = meths.create_buf(false, false)
+ meths.buf_set_lines(buf, 0, -1, true, {' halloj! ',
+ ' BORDAA '})
+ local win = meths.open_win(buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, border="double",
+ footer = "Left",footer_pos = "left",
+ })
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚}{11:Left}{5:═════╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚}{11:Left}{5:═════╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {footer= "Center",footer_pos="center"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═}{11:Center}{5:══╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═}{11:Center}{5:══╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {footer= "Right",footer_pos="right"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚════}{11:Right}{5:╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚════}{11:Right}{5:╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {footer= { {"🦄"},{"BB"}},footer_pos="right"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════}🦄BB{5:╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════}🦄BB{5:╝}{0: }|
+ |
+ ]]}
+ end
+ end)
+
+ it('border with title and footer', function()
+ local buf = meths.create_buf(false, false)
+ meths.buf_set_lines(buf, 0, -1, true, {' halloj! ',
+ ' BORDAA '})
+ local win = meths.open_win(buf, false, {
+ relative='editor', width=9, height=2, row=2, col=5, border="double",
+ title = "Left", title_pos = "left", footer = "Right", footer_pos = "right",
+ })
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔}{11:Left}{5:═════╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚════}{11:Right}{5:╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔}{11:Left}{5:═════╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚════}{11:Right}{5:╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {title= "Center",title_pos="center",footer= "Center",footer_pos="center"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═}{11:Center}{5:══╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═}{11:Center}{5:══╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═}{11:Center}{5:══╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═}{11:Center}{5:══╝}{0: }|
+ |
+ ]]}
+ end
+
+ meths.win_set_config(win, {title= "Right",title_pos="right",footer= "Left",footer_pos="left"})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔════}{11:Right}{5:╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚}{11:Left}{5:═════╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔════}{11:Right}{5:╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚}{11:Left}{5:═════╝}{0: }|
+ |
+ ]]}
+ end
+
+ command('hi B0 guibg=Red guifg=Black')
+ command('hi B1 guifg=White')
+ meths.win_set_config(win, {
+ title = {{"🦄"}, {"BB", {"B0", "B1"}}}, title_pos = "right",
+ footer= {{"🦄"}, {"BB", {"B0", "B1"}}}, footer_pos = "right",
+ })
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════}🦄{7:BB}{5:╗}|
+ {5:║}{1: halloj! }{5:║}|
+ {5:║}{1: BORDAA }{5:║}|
+ {5:╚═════}🦄{7:BB}{5:╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 2, 5, true }
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }{5:╔═════}🦄{7:BB}{5:╗}{0: }|
+ {0:~ }{5:║}{1: halloj! }{5:║}{0: }|
+ {0:~ }{5:║}{1: BORDAA }{5:║}{0: }|
+ {0:~ }{5:╚═════}🦄{7:BB}{5:╝}{0: }|
+ |
+ ]]}
+ end
+ end)
+
it('terminates border on edge of viewport when window extends past viewport', function()
local buf = meths.create_buf(false, false)
- meths.open_win(buf, false, {relative='editor', width=40, height=7, row=0, col=0, border="single"})
+ meths.open_win(buf, false, {relative='editor', width=40, height=7, row=0, col=0, border="single", zindex=201})
if multigrid then
screen:expect{grid=[[
## grid 1
@@ -2046,10 +2670,10 @@ describe('float window', function()
{5:│}{2:~ }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [4] = { { id = 1001 }, "NW", 1, 0, 0, true }
+ [4] = { { id = 1001 }, "NW", 1, 0, 0, true, 201 }
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -2058,8 +2682,8 @@ describe('float window', function()
{5:│}{2:~ }{5:│}|
{5:│}{2:~ }{5:│}|
{5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
{5:└──────────────────────────────────────┘}|
- |
]]}
end
end)
@@ -2097,17 +2721,17 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:╔═════════╗}|
{5:║}{1:aaa aab }{5:║}|
{5:║}{1:abb acc }{5:║}|
{5:║}{1:^ }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 0, 5, true }
+ [4] = { { id = 1001 }, "NW", 1, 0, 5, true };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 2, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -2150,23 +2774,23 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- }{8:match 1 of 4} |
- ## grid 5
+ ## grid 4
{5:╔═════════╗}|
{5:║}{1:aaa aab }{5:║}|
{5:║}{1:abb acc }{5:║}|
{5:║}{1:acc^ }{5:║}|
{5:╚═════════╝}|
- ## grid 6
+ ## grid 5
{1: aaa }|
{1: aab }|
{1: abb }|
{13: acc }|
]], float_pos={
- [5] = { { id = 1002 }, "NW", 1, 0, 5, true, 50 },
- [6] = { { id = -1 }, "NW", 5, 4, 0, false, 100 }
+ [4] = { { id = 1001 }, "NW", 1, 0, 5, true, 50 };
+ [5] = { { id = -1 }, "NW", 4, 4, 0, false, 100 };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 2, curcol = 3, linecount=3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 2, curcol = 3, linecount=3, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -2182,6 +2806,374 @@ describe('float window', function()
{3:-- }{8:match 1 of 4} |
]]}
end
+
+ feed '<esc>'
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1:aaa aab }{5:║}|
+ {5:║}{1:abb acc }{5:║}|
+ {5:║}{1:ac^c }{5:║}|
+ {5:╚═════════╝}|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 0, 5, true };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 2, curcol = 2, linecount = 3, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {5:╔═════════╗} |
+ {0:~ }{5:║}{1:aaa aab }{5:║}{0: }|
+ {0:~ }{5:║}{1:abb acc }{5:║}{0: }|
+ {0:~ }{5:║}{1:ac^c }{5:║}{0: }|
+ {0:~ }{5:╚═════════╝}{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ exec([[
+ nnoremenu Test.foo :
+ nnoremenu Test.bar :
+ nnoremenu Test.baz :
+ ]])
+ feed ':popup Test<CR>'
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :popup Test |
+ ## grid 4
+ {5:╔═════════╗}|
+ {5:║}{1:aaa aab }{5:║}|
+ {5:║}{1:abb acc }{5:║}|
+ {5:║}{1:ac^c }{5:║}|
+ {5:╚═════════╝}|
+ ## grid 5
+ {1: foo }|
+ {1: bar }|
+ {1: baz }|
+ ]], float_pos={
+ [4] = { { id = 1001 }, "NW", 1, 0, 5, true };
+ [5] = { { id = -1 }, "NW", 4, 4, 2, false, 250 };
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 2, curcol = 2, linecount = 3, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {5:╔═════════╗} |
+ {0:~ }{5:║}{1:aaa aab }{5:║}{0: }|
+ {0:~ }{5:║}{1:abb acc }{5:║}{0: }|
+ {0:~ }{5:║}{1:ac^c }{5:║}{0: }|
+ {0:~ }{5:╚═}{1: foo }{5:═══╝}{0: }|
+ {0:~ }{1: bar }{0: }|
+ {0:~ }{1: baz }{0: }|
+ {0:~ }|
+ {0:~ }|
+ :popup Test |
+ ]]}
+ end
+ end)
+
+ it('show ruler of current floating window', function()
+ command 'set ruler'
+ local buf = meths.create_buf(false, false)
+ meths.buf_set_lines(buf, 0, -1, true, {'aaa aab ',
+ 'abb acc '})
+ meths.open_win(buf, true, {relative='editor', width=9, height=3, row=0, col=5})
+ feed 'gg'
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ 1,1 All |
+ ## grid 4
+ {1:^aaa aab }|
+ {1:abb acc }|
+ {2:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {1:^aaa aab } |
+ {0:~ }{1:abb acc }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ 1,1 All |
+ ]]}
+ end
+
+ feed 'w'
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ 1,5 All |
+ ## grid 4
+ {1:aaa ^aab }|
+ {1:abb acc }|
+ {2:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 4, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {1:aaa ^aab } |
+ {0:~ }{1:abb acc }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ 1,5 All |
+ ]]}
+ end
+ end)
+
+ it("correct ruler position in current float with 'rulerformat' set", function()
+ command 'set ruler rulerformat=fish:<><'
+ meths.open_win(0, true, {relative='editor', width=9, height=3, row=0, col=5})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ fish:<>< |
+ ## grid 4
+ {1:^ }|
+ {2:~ }|
+ {2:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {1:^ } |
+ {0:~ }{2:~ }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ fish:<>< |
+ ]]}
+ end
+ end)
+
+ it('does not show ruler of not-last current float during ins-completion', function()
+ screen:try_resize(50,9)
+ command 'set ruler showmode'
+ meths.open_win(0, false, {relative='editor', width=3, height=3, row=0, col=0})
+ meths.open_win(0, false, {relative='editor', width=3, height=3, row=0, col=5})
+ feed '<c-w>w'
+ neq('', meths.win_get_config(0).relative)
+ neq(funcs.winnr '$', funcs.winnr())
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [3:--------------------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ 0,0-1 All |
+ ## grid 4
+ {1: }|
+ {2:~ }|
+ {2:~ }|
+ ## grid 5
+ {1:^ }|
+ {2:~ }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 0, 0, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {1: } {1:^ } |
+ {2:~ }{0: }{2:~ }{0: }|
+ {2:~ }{0: }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ 0,0-1 All |
+ ]]}
+ end
+ feed 'i<c-x>'
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [3:--------------------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} |
+ ## grid 4
+ {1: }|
+ {2:~ }|
+ {2:~ }|
+ ## grid 5
+ {1:^ }|
+ {2:~ }|
+ {2:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 0, 0, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {1: } {1:^ } |
+ {2:~ }{0: }{2:~ }{0: }|
+ {2:~ }{0: }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} |
+ ]]}
+ end
end)
it('can have minimum size', function()
@@ -2208,10 +3200,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:x}|
]], float_pos={
- [5] = {{id = 1002}, "NW", 2, 0, 4, false}
+ [4] = {{id = 1001}, "NW", 2, 0, 4, false}
}}
else
screen:expect([[
@@ -2245,10 +3237,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:x}|
]], float_pos={
- [5] = {{id = 1002}, "NW", 2, 0, 15, false}
+ [4] = {{id = 1001}, "NW", 2, 0, 15, false}
}}
else
screen:expect([[
@@ -2296,6 +3288,154 @@ describe('float window', function()
end
end)
+ describe('no crash when rearranging windows', function()
+ local function test_rearrange_windows(cmd)
+ command('set laststatus=2')
+ screen:try_resize(40, 13)
+
+ command('args X1 X2 X3 X4 X5 X6')
+ command('sargument 2')
+ command('sargument 3')
+ local w3 = curwin()
+ command('sargument 4')
+ local w4 = curwin()
+ command('sargument 5')
+ command('sargument 6')
+
+ local float_opts = { relative = 'editor', row = 6, col = 0, width = 40, height = 1 }
+ meths.win_set_config(w3, float_opts)
+ meths.win_set_config(w4, float_opts)
+ command('wincmd =')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [8:----------------------------------------]|
+ [8:----------------------------------------]|
+ {4:X6 }|
+ [7:----------------------------------------]|
+ [7:----------------------------------------]|
+ {5:X5 }|
+ [4:----------------------------------------]|
+ [4:----------------------------------------]|
+ {5:X2 }|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ {5:X1 }|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ |
+ {0:~ }|
+ ## grid 5
+ {1: }|
+ ## grid 6
+ {1: }|
+ ## grid 7
+ |
+ {0:~ }|
+ ## grid 8
+ ^ |
+ {0:~ }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 6, 0, true, 50};
+ [6] = {{id = 1003}, "NW", 1, 6, 0, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [7] = {win = {id = 1004}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [8] = {win = {id = 1005}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {4:X6 }|
+ |
+ {0:~ }|
+ {5:X5 }|
+ {1: }|
+ {0:~ }|
+ {5:X2 }|
+ |
+ {0:~ }|
+ {5:X1 }|
+ |
+ ]]}
+ end
+
+ command(cmd)
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ {4:X1 }|
+ [4:----------------------------------------]|
+ {5:X2 }|
+ [9:----------------------------------------]|
+ {5:X3 }|
+ [10:----------------------------------------]|
+ {5:X4 }|
+ [7:----------------------------------------]|
+ {5:X5 }|
+ [8:----------------------------------------]|
+ {5:X6 }|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ ## grid 3
+ |
+ ## grid 4
+ |
+ ## grid 7
+ |
+ ## grid 8
+ |
+ ## grid 9
+ |
+ ## grid 10
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [7] = {win = {id = 1004}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [8] = {win = {id = 1005}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [9] = {win = {id = 1006}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [10] = {win = {id = 1007}, topline = 0, botline = 1, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {4:X1 }|
+ |
+ {5:X2 }|
+ |
+ {5:X3 }|
+ |
+ {5:X4 }|
+ |
+ {5:X5 }|
+ |
+ {5:X6 }|
+ |
+ ]]}
+ end
+ end
+
+ it('using :unhide', function()
+ test_rearrange_windows('unhide')
+ end)
+
+ it('using :all', function()
+ test_rearrange_windows('all')
+ end)
+ end)
+
it('API has proper error messages', function()
local buf = meths.create_buf(false,false)
eq("Invalid key: 'bork'",
@@ -2496,7 +3636,6 @@ describe('float window', function()
]])
end
-
meths.win_set_config(win, {relative='win', win=oldwin, row=1, col=10, anchor='NW'})
if multigrid then
screen:expect{grid=[[
@@ -2618,7 +3757,8 @@ describe('float window', function()
curline = 0,
curcol = 3,
linecount = 2,
- win = { id = 1000 }
+ sum_scroll_delta = 0,
+ win = { id = 1000 },
},
[4] = {
topline = 0,
@@ -2626,6 +3766,7 @@ describe('float window', function()
curline = 0,
curcol = 3,
linecount = 2,
+ sum_scroll_delta = 0,
win = { id = 1001 }
},
[5] = {
@@ -2634,6 +3775,7 @@ describe('float window', function()
curline = 0,
curcol = 0,
linecount = 1,
+ sum_scroll_delta = 0,
win = { id = 1002 }
}
}}
@@ -2741,13 +3883,13 @@ describe('float window', function()
{0:~ }|
{0:~ }|
{0:~ }|
- ## grid 6
+ ## grid 5
{5:╔═════════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [6] = {{id = 1003}, "NW", 4, 1, 14, true}
+ [5] = {{id = 1002}, "NW", 4, 1, 14, true}
}}
else
screen:expect([[
@@ -2798,13 +3940,13 @@ describe('float window', function()
{0:~ }|
{0:~ }|
{0:~ }|
- ## grid 6
+ ## grid 5
{5:╔═════════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [6] = {{id = 1003}, "NE", 4, 0, 14, true}
+ [5] = {{id = 1002}, "NE", 4, 0, 14, true}
}}
else
screen:expect([[
@@ -2855,13 +3997,13 @@ describe('float window', function()
{0:~ }|
{0:~ }|
{0:~ }|
- ## grid 6
+ ## grid 5
{5:╔═════════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [6] = {{id = 1003}, "SE", 4, 1, 14, true}
+ [5] = {{id = 1002}, "SE", 4, 1, 14, true}
}}
else
screen:expect([[
@@ -2912,13 +4054,13 @@ describe('float window', function()
{0:~ }|
{0:~ }|
{0:~ }|
- ## grid 6
+ ## grid 5
{5:╔═════════╗}|
{5:║}{1: halloj! }{5:║}|
{5:║}{1: BORDAA }{5:║}|
{5:╚═════════╝}|
]], float_pos={
- [6] = {{id = 1003}, "SW", 4, 0, 14, true}
+ [5] = {{id = 1002}, "SW", 4, 0, 14, true}
}}
else
screen:expect([[
@@ -2939,6 +4081,198 @@ describe('float window', function()
end
end)
+ it('anchored to another floating window updated in the same call #14735', function()
+ feed('i<CR><CR><CR><Esc>')
+
+ exec([[
+ let b1 = nvim_create_buf(v:true, v:false)
+ let b2 = nvim_create_buf(v:true, v:false)
+ let b3 = nvim_create_buf(v:true, v:false)
+ let b4 = nvim_create_buf(v:true, v:false)
+ let b5 = nvim_create_buf(v:true, v:false)
+ let b6 = nvim_create_buf(v:true, v:false)
+ let b7 = nvim_create_buf(v:true, v:false)
+ let b8 = nvim_create_buf(v:true, v:false)
+ call setbufline(b1, 1, '1')
+ call setbufline(b2, 1, '2')
+ call setbufline(b3, 1, '3')
+ call setbufline(b4, 1, '4')
+ call setbufline(b5, 1, '5')
+ call setbufline(b6, 1, '6')
+ call setbufline(b7, 1, '7')
+ call setbufline(b8, 1, '8')
+ let o1 = #{relative: 'editor', row: 1, col: 10, width: 5, height: 1}
+ let w1 = nvim_open_win(b1, v:false, o1)
+ let o2 = extendnew(o1, #{col: 30})
+ let w2 = nvim_open_win(b2, v:false, o2)
+ let o3 = extendnew(o1, #{relative: 'win', win: w1, anchor: 'NE', col: 0})
+ let w3 = nvim_open_win(b3, v:false, o3)
+ let o4 = extendnew(o3, #{win: w2})
+ let w4 = nvim_open_win(b4, v:false, o4)
+ let o5 = extendnew(o3, #{win: w3, anchor: 'SE', row: 0})
+ let w5 = nvim_open_win(b5, v:false, o5)
+ let o6 = extendnew(o5, #{win: w4})
+ let w6 = nvim_open_win(b6, v:false, o6)
+ let o7 = extendnew(o5, #{win: w5, anchor: 'SW', col: 5})
+ let w7 = nvim_open_win(b7, v:false, o7)
+ let o8 = extendnew(o7, #{win: w6})
+ let w8 = nvim_open_win(b8, v:false, o8)
+ ]])
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ |
+ |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {1:1 }|
+ ## grid 6
+ {1:2 }|
+ ## grid 7
+ {1:3 }|
+ ## grid 8
+ {1:4 }|
+ ## grid 9
+ {1:5 }|
+ ## grid 10
+ {1:6 }|
+ ## grid 11
+ {1:7 }|
+ ## grid 12
+ {1:8 }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NW", 1, 1, 10, true, 50};
+ [6] = {{id = 1003}, "NW", 1, 1, 30, true, 50};
+ [7] = {{id = 1004}, "NE", 5, 1, 0, true, 50};
+ [8] = {{id = 1005}, "NE", 6, 1, 0, true, 50};
+ [9] = {{id = 1006}, "SE", 7, 0, 0, true, 50};
+ [10] = {{id = 1007}, "SE", 8, 0, 0, true, 50};
+ [11] = {{id = 1008}, "SW", 9, 0, 5, true, 50};
+ [12] = {{id = 1009}, "SW", 10, 0, 5, true, 50};
+ }}
+ else
+ screen:expect([[
+ {1:7 } {1:8 } |
+ {1:5 } {1:1 } {1:6 } {1:2 } |
+ {1:3 } {1:4 } |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+
+ -- Reconfigure in different directions
+ exec([[
+ let o1 = extendnew(o1, #{anchor: 'NW'})
+ call nvim_win_set_config(w8, o1)
+ let o2 = extendnew(o2, #{anchor: 'NW'})
+ call nvim_win_set_config(w4, o2)
+ let o3 = extendnew(o3, #{win: w8})
+ call nvim_win_set_config(w2, o3)
+ let o4 = extendnew(o4, #{win: w4})
+ call nvim_win_set_config(w1, o4)
+ let o5 = extendnew(o5, #{win: w2})
+ call nvim_win_set_config(w6, o5)
+ let o6 = extendnew(o6, #{win: w1})
+ call nvim_win_set_config(w3, o6)
+ let o7 = extendnew(o7, #{win: w6})
+ call nvim_win_set_config(w5, o7)
+ let o8 = extendnew(o8, #{win: w3})
+ call nvim_win_set_config(w7, o8)
+ ]])
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ |
+ |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 5
+ {1:1 }|
+ ## grid 6
+ {1:2 }|
+ ## grid 7
+ {1:3 }|
+ ## grid 8
+ {1:4 }|
+ ## grid 9
+ {1:5 }|
+ ## grid 10
+ {1:6 }|
+ ## grid 11
+ {1:7 }|
+ ## grid 12
+ {1:8 }|
+ ]], float_pos={
+ [5] = {{id = 1002}, "NE", 8, 1, 0, true, 50};
+ [6] = {{id = 1003}, "NE", 12, 1, 0, true, 50};
+ [7] = {{id = 1004}, "SE", 5, 0, 0, true, 50};
+ [8] = {{id = 1005}, "NW", 1, 1, 30, true, 50};
+ [9] = {{id = 1006}, "SW", 10, 0, 5, true, 50};
+ [10] = {{id = 1007}, "SE", 6, 0, 0, true, 50};
+ [11] = {{id = 1008}, "SW", 7, 0, 5, true, 50};
+ [12] = {{id = 1009}, "NW", 1, 1, 10, true, 50};
+ }}
+ else
+ screen:expect([[
+ {1:5 } {1:7 } |
+ {1:6 } {1:8 } {1:3 } {1:4 } |
+ {1:2 } {1:1 } |
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+
+ -- Not clear how cycles should behave, but they should not hang or crash
+ exec([[
+ let o1 = extendnew(o1, #{relative: 'win', win: w7})
+ call nvim_win_set_config(w1, o1)
+ let o2 = extendnew(o2, #{relative: 'win', win: w8})
+ call nvim_win_set_config(w2, o2)
+ let o3 = extendnew(o3, #{win: w1})
+ call nvim_win_set_config(w3, o3)
+ let o4 = extendnew(o4, #{win: w2})
+ call nvim_win_set_config(w4, o4)
+ let o5 = extendnew(o5, #{win: w3})
+ call nvim_win_set_config(w5, o5)
+ let o6 = extendnew(o6, #{win: w4})
+ call nvim_win_set_config(w6, o6)
+ let o7 = extendnew(o7, #{win: w5})
+ call nvim_win_set_config(w7, o7)
+ let o8 = extendnew(o8, #{win: w6})
+ call nvim_win_set_config(w8, o8)
+ redraw
+ ]])
+ end)
+
it('can be placed relative text in a window', function()
screen:try_resize(30,5)
local firstwin = meths.get_current_win().id
@@ -2988,12 +4322,10 @@ describe('float window', function()
|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 3, 2, true }
+ [4] = { { id = 1001 }, "NW", 2, 3, 2, true }
}}
else
screen:expect{grid=[[
@@ -3004,7 +4336,7 @@ describe('float window', function()
|
]]}
end
- eq({relative='win', width=12, height=1, bufpos={1,32}, anchor='NW',
+ eq({relative='win', width=12, height=1, bufpos={1,32}, anchor='NW', hide=false,
external=false, col=0, row=1, win=firstwin, focusable=true, zindex=50}, meths.win_get_config(win))
feed('<c-e>')
@@ -3023,12 +4355,10 @@ describe('float window', function()
|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 2, 2, true }
+ [4] = { { id = 1001 }, "NW", 2, 2, 2, true },
}}
else
screen:expect{grid=[[
@@ -3057,12 +4387,10 @@ describe('float window', function()
more text |
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 1, 32, true }
+ [4] = { { id = 1001 }, "NW", 2, 1, 32, true }
}}
else
-- note: appears misaligned due to cursor
@@ -3101,12 +4429,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 2, 7, true }
+ [4] = { { id = 1001 }, "NW", 2, 2, 7, true }
}}
else
screen:expect{grid=[[
@@ -3149,12 +4475,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "SW", 2, 1, 7, true }
+ [4] = { { id = 1001 }, "SW", 2, 1, 7, true }
}}
else
screen:expect{grid=[[
@@ -3176,15 +4500,15 @@ describe('float window', function()
if multigrid then
screen:expect{grid=[[
## grid 1
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
- [2:----]{5:│}[6:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
+ [2:----]{5:│}[5:--------------------]|
[3:-------------------------]|
## grid 2
exam|
@@ -3198,9 +4522,9 @@ describe('float window', function()
the |
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
- ## grid 6
+ ## grid 5
^ |
{0:~ }|
{0:~ }|
@@ -3211,9 +4535,7 @@ describe('float window', function()
{0:~ }|
{0:~ }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "SW", 2, 8, 0, true }
+ [4] = { { id = 1001 }, "SW", 2, 8, 0, true }
}}
else
screen:expect{grid=[[
@@ -3257,12 +4579,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 2, 5, true }
+ [4] = { { id = 1001 }, "NW", 2, 2, 5, true }
}}
else
screen:expect{grid=[[
@@ -3305,12 +4625,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 3, 7, true }
+ [4] = { { id = 1001 }, "NW", 2, 3, 7, true }
}}
else
screen:expect{grid=[[
@@ -3353,12 +4671,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some info! }|
]], float_pos={
- [5] = { {
- id = 1002
- }, "NW", 2, 2, 0, true }
+ [4] = { { id = 1001 }, "NW", 2, 2, 0, true }
}}
else
screen:expect{grid=[[
@@ -3424,10 +4740,10 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:some floaty text }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 3, 1, true}
+ [4] = {{id = 1001}, "NW", 1, 3, 1, true}
}}
else
screen:expect([[
@@ -3483,7 +4799,7 @@ describe('float window', function()
meths.buf_set_lines(buf, 0, -1, true, {'such', 'very', 'float'})
local win = meths.open_win(buf, false, {relative='editor', width=15, height=4, row=2, col=10})
local expected_pos = {
- [5]={{id=1002}, 'NW', 1, 2, 10, true},
+ [4]={{id=1001}, 'NW', 1, 2, 10, true},
}
if multigrid then
screen:expect{grid=[[
@@ -3504,7 +4820,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:float }|
@@ -3538,7 +4854,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:float }|
@@ -3568,7 +4884,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:float }|
@@ -3595,7 +4911,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:float }|
@@ -3620,7 +4936,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:^float }|
@@ -3628,9 +4944,9 @@ describe('float window', function()
]], float_pos=expected_pos}
else
screen:expect([[
- {1:very } |
- {0:~ }{1:^float }{0: }|
- |
+ {1:such } |
+ {0:~ }{1:very }{0: }|
+ ^ |
]])
end
@@ -3654,7 +4970,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:such }|
{1:very }|
{1:^float }|
@@ -3693,7 +5009,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3730,7 +5046,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3767,7 +5083,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3804,7 +5120,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3841,7 +5157,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3878,7 +5194,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3915,7 +5231,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3952,7 +5268,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -3980,7 +5296,7 @@ describe('float window', function()
|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -4012,7 +5328,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^such }|
{1:very }|
{1:float }|
@@ -4162,8 +5478,8 @@ describe('float window', function()
describe('and completion', function()
before_each(function()
local buf = meths.create_buf(false,false)
- local win = meths.open_win(buf, true, {relative='editor', width=12, height=4, row=2, col=5})
- meths.win_set_option(win , 'winhl', 'Normal:ErrorMsg')
+ local win = meths.open_win(buf, true, {relative='editor', width=12, height=4, row=2, col=5}).id
+ meths.set_option_value('winhl', 'Normal:ErrorMsg', {win=win})
if multigrid then
screen:expect{grid=[[
## grid 1
@@ -4672,12 +5988,12 @@ describe('float window', function()
{13:aa }|
{1:word }|
{1:longtext }|
- ## grid 6
+ ## grid 5
{15:some info }|
{15:about item }|
]], float_pos={
[4] = {{id = -1}, "NW", 2, 1, 0, false, 100},
- [6] = {{id = 1002}, "NW", 2, 1, 12, true, 50},
+ [5] = {{id = 1001}, "NW", 2, 1, 12, true, 50},
}}
else
screen:expect([[
@@ -4713,11 +6029,11 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- INSERT --} |
- ## grid 6
+ ## grid 5
{15:some info }|
{15:about item }|
]], float_pos={
- [6] = {{id = 1002}, "NW", 2, 1, 12, true},
+ [5] = {{id = 1001}, "NW", 2, 1, 12, true},
}}
else
screen:expect([[
@@ -4840,6 +6156,53 @@ describe('float window', function()
end)
end)
+ it("can use Normal as background", function()
+ local buf = meths.create_buf(false,false)
+ meths.buf_set_lines(buf,0,-1,true,{"here", "float"})
+ local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=2, col=5})
+ meths.set_option_value('winhl', 'Normal:Normal', {win=win})
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ here |
+ float |
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 2, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }here {0: }|
+ {0:~ }float {0: }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end
+ end)
+
describe("handles :wincmd", function()
local win
local expected_pos
@@ -5153,7 +6516,7 @@ describe('float window', function()
end
if multigrid then
- meths.input_mouse('left', 'press', '', 1, 0, 0)
+ meths.input_mouse('left', 'press', '', 2, 0, 0)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -5232,7 +6595,7 @@ describe('float window', function()
end
if multigrid then
- meths.input_mouse('left', 'press', '', 1, 0, 0)
+ meths.input_mouse('left', 'press', '', 2, 0, 0)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7142,18 +8505,18 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:foo }|
{1:bar }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 2, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'press', '', 5, 0, 0)
+ meths.input_mouse('left', 'press', '', 4, 0, 0)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7172,18 +8535,18 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{1:^foo }|
{1:bar }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 2, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'drag', '', 5, 1, 2)
+ meths.input_mouse('left', 'drag', '', 4, 1, 2)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7202,15 +8565,15 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- VISUAL --} |
- ## grid 5
+ ## grid 4
{27:foo}{1: }|
{27:ba}{1:^r }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 2, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -7270,20 +8633,20 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:┌────────────────────┐}|
{5:│}{1:foo }{5:│}|
{5:│}{1:bar }{5:│}|
{5:│}{1:baz }{5:│}|
{5:└────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'press', '', 5, 1, 1)
+ meths.input_mouse('left', 'press', '', 4, 1, 1)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7302,20 +8665,20 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:┌────────────────────┐}|
{5:│}{1:^foo }{5:│}|
{5:│}{1:bar }{5:│}|
{5:│}{1:baz }{5:│}|
{5:└────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'drag', '', 5, 2, 3)
+ meths.input_mouse('left', 'drag', '', 4, 2, 3)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7334,17 +8697,17 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- VISUAL --} |
- ## grid 5
+ ## grid 4
{5:┌────────────────────┐}|
{5:│}{27:foo}{1: }{5:│}|
{5:│}{27:ba}{1:^r }{5:│}|
{5:│}{1:baz }{5:│}|
{5:└────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -7385,7 +8748,7 @@ describe('float window', function()
local buf = meths.create_buf(false,false)
meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar', 'baz'})
local float_win = meths.open_win(buf, false, {relative='editor', width=20, height=4, row=1, col=5})
- meths.win_set_option(float_win, 'winbar', 'floaty bar')
+ meths.set_option_value('winbar', 'floaty bar', {win=float_win.id})
if multigrid then
screen:expect{grid=[[
## grid 1
@@ -7405,19 +8768,19 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{3:floaty bar }|
{1:foo }|
{1:bar }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 1, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'press', '', 5, 1, 0)
+ meths.input_mouse('left', 'press', '', 4, 1, 0)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7436,19 +8799,19 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{3:floaty bar }|
{1:^foo }|
{1:bar }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 1, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
}}
- meths.input_mouse('left', 'drag', '', 5, 2, 2)
+ meths.input_mouse('left', 'drag', '', 4, 2, 2)
screen:expect{grid=[[
## grid 1
[2:----------------------------------------]|
@@ -7467,16 +8830,16 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- VISUAL --} |
- ## grid 5
+ ## grid 4
{3:floaty bar }|
{27:foo}{1: }|
{27:ba}{1:^r }|
{1:baz }|
]], float_pos={
- [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50};
+ [4] = {{id = 1001}, "NW", 1, 1, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -7521,11 +8884,11 @@ describe('float window', function()
if multigrid then
screen:expect([[
## grid 1
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
{5:[No Name] }{4:[No Name] [+] }|
[3:----------------------------------------]|
## grid 2
@@ -7536,7 +8899,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
^foo |
bar |
baz |
@@ -7544,14 +8907,14 @@ describe('float window', function()
{0:~ }|
]])
- meths.input_mouse('left', 'press', '', 5, 2, 2)
+ meths.input_mouse('left', 'press', '', 4, 2, 2)
screen:expect([[
## grid 1
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
{5:[No Name] }{4:[No Name] [+] }|
[3:----------------------------------------]|
## grid 2
@@ -7562,7 +8925,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
foo |
bar |
ba^z |
@@ -7570,14 +8933,14 @@ describe('float window', function()
{0:~ }|
]])
- meths.input_mouse('left', 'drag', '', 5, 1, 1)
+ meths.input_mouse('left', 'drag', '', 4, 1, 1)
screen:expect([[
## grid 1
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
- [2:-------------------]{5:│}[5:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
+ [2:-------------------]{5:│}[4:--------------------]|
{5:[No Name] }{4:[No Name] [+] }|
[3:----------------------------------------]|
## grid 2
@@ -7588,7 +8951,7 @@ describe('float window', function()
{0:~ }|
## grid 3
{3:-- VISUAL --} |
- ## grid 5
+ ## grid 4
foo |
b^a{27:r} |
{27:baz} |
@@ -7630,6 +8993,186 @@ describe('float window', function()
end
end)
+ it('left click sets correct curswant in float window with border', function()
+ local buf = meths.create_buf(false,false)
+ meths.buf_set_lines(buf, 0, -1, true, {'', '', ''})
+ meths.open_win(buf, false, {relative='editor', width=20, height=3, row=0, col=5, border='single'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:┌────────────────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{1: }{5:│}|
+ {5:│}{1: }{5:│}|
+ {5:└────────────────────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ ^ {5:┌────────────────────┐} |
+ {0:~ }{5:│}{1: }{5:│}{0: }|
+ {0:~ }{5:│}{1: }{5:│}{0: }|
+ {0:~ }{5:│}{1: }{5:│}{0: }|
+ {0:~ }{5:└────────────────────┘}{0: }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 3, 1)
+ else
+ meths.input_mouse('left', 'press', '', 0, 3, 6)
+ end
+ eq({0, 3, 1, 0, 1}, funcs.getcurpos())
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 3, 2)
+ else
+ meths.input_mouse('left', 'press', '', 0, 3, 7)
+ end
+ eq({0, 3, 1, 0, 2}, funcs.getcurpos())
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 3, 10)
+ else
+ meths.input_mouse('left', 'press', '', 0, 3, 15)
+ end
+ eq({0, 3, 1, 0, 10}, funcs.getcurpos())
+
+ command('setlocal foldcolumn=1')
+ feed('zfkgg')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:┌────────────────────┐}|
+ {5:│}{19: }{1:^ }{5:│}|
+ {5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────────────────────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ {5:┌────────────────────┐} |
+ {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }|
+ {0:~ }{5:│}{19:+}{28:+-- 2 lines: ·····}{5:│}{0: }|
+ {0:~ }{5:│}{2:~ }{5:│}{0: }|
+ {0:~ }{5:└────────────────────┘}{0: }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 1)
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:┌────────────────────┐}|
+ {5:│}{19: }{1:^ }{5:│}|
+ {5:│}{19:-}{1: }{5:│}|
+ {5:│}{19:│}{1: }{5:│}|
+ {5:└────────────────────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 0, 5, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0};
+ }}
+ else
+ meths.input_mouse('left', 'press', '', 0, 2, 6)
+ screen:expect{grid=[[
+ {5:┌────────────────────┐} |
+ {0:~ }{5:│}{19: }{1:^ }{5:│}{0: }|
+ {0:~ }{5:│}{19:-}{1: }{5:│}{0: }|
+ {0:~ }{5:│}{19:│}{1: }{5:│}{0: }|
+ {0:~ }{5:└────────────────────┘}{0: }|
+ {0:~ }|
+ |
+ ]]}
+ end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 2)
+ else
+ meths.input_mouse('left', 'press', '', 0, 2, 7)
+ end
+ eq({0, 2, 1, 0, 1}, funcs.getcurpos())
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 3)
+ else
+ meths.input_mouse('left', 'press', '', 0, 2, 8)
+ end
+ eq({0, 2, 1, 0, 2}, funcs.getcurpos())
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 11)
+ else
+ meths.input_mouse('left', 'press', '', 0, 2, 16)
+ end
+ eq({0, 2, 1, 0, 10}, funcs.getcurpos())
+ end)
+
it("'winblend' option", function()
screen:try_resize(50,9)
screen:set_default_attr_ids({
@@ -7641,10 +9184,23 @@ describe('float window', function()
[6] = {foreground = tonumber('0x332533'), background = tonumber('0xfff1ff')},
[7] = {background = tonumber('0xffcfff'), bold = true, foreground = tonumber('0x0000d8')},
[8] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1},
- [9] = {background = Screen.colors.LightMagenta, blend=30},
- [10] = {foreground = Screen.colors.Red, background = Screen.colors.LightMagenta, blend=0},
- [11] = {foreground = Screen.colors.Red, background = Screen.colors.LightMagenta, blend=80},
- [12] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1, blend=30},
+ [9] = {background = Screen.colors.LightMagenta, blend = 30},
+ [10] = {foreground = Screen.colors.Red, background = Screen.colors.LightMagenta, blend = 0},
+ [11] = {foreground = Screen.colors.Red, background = Screen.colors.LightMagenta, blend = 80},
+ [12] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue1, blend = 30},
+ [13] = {background = Screen.colors.LightGray, blend = 30},
+ [14] = {foreground = Screen.colors.Grey0, background = Screen.colors.Grey88},
+ [15] = {foreground = tonumber('0x939393'), background = Screen.colors.Grey88},
+ [16] = {background = Screen.colors.Grey90};
+ [17] = {blend = 100};
+ [18] = {background = Screen.colors.LightMagenta, blend = 100};
+ [19] = {background = Screen.colors.LightMagenta, bold = true, blend = 100, foreground = Screen.colors.Blue1};
+ [20] = {background = Screen.colors.White, foreground = Screen.colors.Gray0};
+ [21] = {background = Screen.colors.White, bold = true, foreground = tonumber('0x00007f')};
+ [22] = {background = Screen.colors.Gray90, foreground = Screen.colors.Gray0};
+ [23] = {blend = 100, bold = true, foreground = Screen.colors.Magenta};
+ [24] = {foreground = tonumber('0x7f007f'), bold = true, background = Screen.colors.White};
+ [25] = {foreground = tonumber('0x7f007f'), bold = true, background = Screen.colors.Grey90};
})
insert([[
Lorem ipsum dolor sit amet, consectetur
@@ -7684,11 +9240,11 @@ describe('float window', function()
laborum^. |
## grid 3
|
- ## grid 5
+ ## grid 4
{1:test }|
{1: }|
{1:popup text }|
- ]], float_pos={[5] = {{id = 1002}, "NW", 1, 2, 5, true}}}
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}}
else
screen:expect([[
Ut enim ad minim veniam, quis nostrud |
@@ -7703,7 +9259,7 @@ describe('float window', function()
]])
end
- meths.win_set_option(win, "winblend", 30)
+ meths.set_option_value("winblend", 30, {win=win.id})
if multigrid then
screen:expect{grid=[[
## grid 1
@@ -7727,11 +9283,11 @@ describe('float window', function()
laborum^. |
## grid 3
|
- ## grid 5
+ ## grid 4
{9:test }|
{9: }|
{9:popup text }|
- ]], float_pos={[5] = {{id = 1002}, "NW", 1, 2, 5, true}}, unchanged=true}
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}, unchanged=true}
else
screen:expect([[
Ut enim ad minim veniam, quis nostrud |
@@ -7746,6 +9302,58 @@ describe('float window', function()
]])
end
+ -- Check that 'winblend' works with NormalNC highlight
+ meths.set_option_value('winhighlight', 'NormalNC:Visual', {win = win})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [3:--------------------------------------------------]|
+ ## grid 2
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ laborum^. |
+ ## grid 3
+ |
+ ## grid 4
+ {13:test }|
+ {13: }|
+ {13:popup text }|
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}}
+ else
+ screen:expect([[
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea co{14:test}{15:o consequat}. Duis aute irure dolor in |
+ repre{15:henderit in vol}uptate velit esse cillum |
+ dolor{14:popup}{15:fugi}{14:text}{15:ul}la pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ laborum^. |
+ |
+ ]])
+ end
+
+ -- Also test with global NormalNC highlight
+ exec_lua([[
+ vim.api.nvim_set_option_value('winhighlight', '', {win = ...})
+ vim.api.nvim_set_hl(0, 'NormalNC', {link = 'Visual'})
+ ]], win)
+ screen:expect_unchanged()
+ command('hi clear NormalNC')
+
command('hi SpecialRegion guifg=Red blend=0')
meths.buf_add_highlight(buf, -1, "SpecialRegion", 2, 0, -1)
if multigrid then
@@ -7771,11 +9379,11 @@ describe('float window', function()
laborum^. |
## grid 3
|
- ## grid 5
+ ## grid 4
{9:test }|
{9: }|
{10:popup text}{9: }|
- ]], float_pos={[5] = {{id = 1002}, "NW", 1, 2, 5, true}}}
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}}
else
screen:expect([[
Ut enim ad minim veniam, quis nostrud |
@@ -7814,11 +9422,11 @@ describe('float window', function()
laborum^. |
## grid 3
|
- ## grid 5
+ ## grid 4
{9:test }|
{9: }|
{11:popup text}{9: }|
- ]], float_pos={[5] = {{id = 1002}, "NW", 1, 2, 5, true}}, unchanged=true}
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}, unchanged=true}
else
screen:expect([[
Ut enim ad minim veniam, quis nostrud |
@@ -7835,7 +9443,7 @@ describe('float window', function()
-- Test scrolling by mouse
if multigrid then
- meths.input_mouse('wheel', 'down', '', 5, 2, 2)
+ meths.input_mouse('wheel', 'down', '', 4, 2, 2)
screen:expect{grid=[[
## grid 1
[2:--------------------------------------------------]|
@@ -7858,11 +9466,11 @@ describe('float window', function()
laborum^. |
## grid 3
|
- ## grid 5
+ ## grid 4
{11:popup text}{9: }|
{12:~ }|
{12:~ }|
- ]], float_pos={[5] = {{id = 1002}, "NW", 1, 2, 5, true}}}
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}}
else
meths.input_mouse('wheel', 'down', '', 0, 4, 7)
screen:expect([[
@@ -7877,6 +9485,56 @@ describe('float window', function()
|
]])
end
+
+ -- Check that 'winblend' applies to border/title/footer
+ meths.win_set_config(win, {border='single', title='Title', footer='Footer'})
+ meths.set_option_value('winblend', 100, {win=win.id})
+ meths.set_option_value("cursorline", true, {win=0})
+ command('hi clear VertSplit')
+ feed('k0')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [2:--------------------------------------------------]|
+ [3:--------------------------------------------------]|
+ ## grid 2
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ {16:^qui officia deserunt mollit anim id est }|
+ laborum. |
+ ## grid 3
+ |
+ ## grid 4
+ {17:┌}{23:Title}{17:──────────┐}|
+ {17:│}{11:popup text}{18: }{17:│}|
+ {17:│}{19:~ }{17:│}|
+ {17:│}{19:~ }{17:│}|
+ {17:└}{23:Footer}{17:─────────┘}|
+ ]], float_pos={[4] = {{id = 1001}, "NW", 1, 2, 5, true}}}
+ else
+ screen:expect([[
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea co{20:┌}{24:Title}{20:──────────┐}Duis aute irure dolor in |
+ repre{20:│}{5:popup}{6:it i}{5:text}{20:lu│}tate velit esse cillum |
+ dolor{20:│}{21:~}{20:eu fugiat null│} pariatur. Excepteur sint |
+ occae{20:│}{21:~}{20:t cupidatat no│} proident, sunt in culpa |
+ {16:^qui o}{22:└}{25:Footer}{22:─────────┘}{16:ollit anim id est }|
+ laborum. |
+ |
+ ]])
+ end
end)
it('can overlap doublewidth chars', function()
@@ -7884,6 +9542,7 @@ describe('float window', function()
# TODO: 测试字典信息的准确性
# FIXME: 测试字典信息的准确性]])
local buf = meths.create_buf(false,false)
+ meths.buf_set_lines(buf, 0, -1, true, {'口', '口'})
local win = meths.open_win(buf, false, {relative='editor', width=5, height=3, row=0, col=11, style='minimal'})
if multigrid then
screen:expect{grid=[[
@@ -7905,14 +9564,14 @@ describe('float window', function()
## grid 3
|
## grid 4
- {1: }|
- {1: }|
+ {1:口 }|
+ {1:口 }|
{1: }|
]], float_pos={ [4] = { { id = 1001 }, "NW", 1, 0, 11, true } }}
else
screen:expect([[
- # TODO: 测 {1: }信息的准确性 |
- # FIXME: 测{1: } 信息的准确^性 |
+ # TODO: 测 {1:口 }信息的准确性 |
+ # FIXME: 测{1:口 } 信息的准确^性 |
{0:~ }{1: }{0: }|
{0:~ }|
{0:~ }|
@@ -7959,7 +9618,7 @@ describe('float window', function()
-- at least. Also check invisible EndOfBuffer region blends correctly.
meths.buf_set_lines(buf, 0, -1, true, {" x x x xx", " x x x x"})
win = meths.open_win(buf, false, {relative='editor', width=12, height=3, row=0, col=11, style='minimal'})
- meths.win_set_option(win, 'winblend', 30)
+ meths.set_option_value('winblend', 30, {win=win.id})
screen:set_default_attr_ids({
[1] = {foreground = tonumber('0xb282b2'), background = tonumber('0xffcfff')},
[2] = {foreground = Screen.colors.Grey0, background = tonumber('0xffcfff')},
@@ -7986,14 +9645,12 @@ describe('float window', function()
{3:~ }|
## grid 3
|
- ## grid 6
+ ## grid 5
{5: x x x xx}|
{5: x x x x}|
{5: }|
]], float_pos={
- [6] = { {
- id = 1003
- }, "NW", 1, 0, 11, true }
+ [5] = { { id = 1002 }, "NW", 1, 0, 11, true }
}}
else
screen:expect([[
@@ -8027,14 +9684,12 @@ describe('float window', function()
{3:~ }|
## grid 3
|
- ## grid 6
+ ## grid 5
{5: x x x xx}|
{5: x x x x}|
{5: }|
]], float_pos={
- [6] = { {
- id = 1003
- }, "NW", 1, 0, 12, true }
+ [5] = { { id = 1002 }, "NW", 1, 0, 12, true }
}}
else
screen:expect([[
@@ -8096,11 +9751,11 @@ describe('float window', function()
{1:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{2:^long }|
{2:longer }|
{2:longest}|
- ## grid 6
+ ## grid 5
{2:---------}|
{2:- -}|
{2:- -}|
@@ -8110,11 +9765,11 @@ describe('float window', function()
[1] = {foreground = Screen.colors.Blue1, bold = true};
[2] = {background = Screen.colors.LightMagenta};
}, float_pos={
+ [4] = { {
+ id = 1001
+ }, "NW", 1, 1, 1, true },
[5] = { {
id = 1002
- }, "NW", 1, 1, 1, true },
- [6] = { {
- id = 1003
}, "NW", 1, 0, 0, true }
}}
else
@@ -8164,11 +9819,11 @@ describe('float window', function()
{1:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{2:^l}|
{2:o}|
{2:n}|
- ## grid 6
+ ## grid 5
{2:---}|
{2:- -}|
{2:- -}|
@@ -8178,12 +9833,8 @@ describe('float window', function()
[1] = {foreground = Screen.colors.Blue1, bold = true};
[2] = {background = Screen.colors.LightMagenta};
}, float_pos={
- [5] = { {
- id = 1002
- }, "NW", 1, 1, 1, true },
- [6] = { {
- id = 1003
- }, "NW", 1, 0, 0, true }
+ [4] = { { id = 1001 }, "NW", 1, 1, 1, true },
+ [5] = { { id = 1002 }, "NW", 1, 0, 0, true }
}}
else
screen:expect([[
@@ -8201,7 +9852,7 @@ describe('float window', function()
it("correctly orders multiple opened floats (current last)", function()
local buf = meths.create_buf(false,false)
local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=2, col=5})
- meths.win_set_option(win, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+ meths.set_option_value("winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg", {win=win.id})
if multigrid then
screen:expect{grid=[[
@@ -8228,8 +9879,8 @@ describe('float window', function()
]], float_pos={
[4] = { { id = 1001 }, "NW", 1, 2, 5, true };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8246,10 +9897,10 @@ describe('float window', function()
exec_lua [[
local buf = vim.api.nvim_create_buf(false,false)
local win = vim.api.nvim_open_win(buf, false, {relative='editor', width=16, height=2, row=3, col=8})
- vim.api.nvim_win_set_option(win, "winhl", "EndOfBuffer:Normal")
+ vim.wo[win].winhl = "EndOfBuffer:Normal"
buf = vim.api.nvim_create_buf(false,false)
win = vim.api.nvim_open_win(buf, true, {relative='editor', width=12, height=2, row=4, col=10})
- vim.api.nvim_win_set_option(win, "winhl", "Normal:Search,EndOfBuffer:Search")
+ vim.wo[win].winhl = "Normal:Search,EndOfBuffer:Search"
]]
if multigrid then
@@ -8285,10 +9936,10 @@ describe('float window', function()
[5] = { { id = 1002 }, "NW", 1, 3, 8, true };
[6] = { { id = 1003 }, "NW", 1, 4, 10, true };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1};
- [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1, sum_scroll_delta = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount=1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8306,7 +9957,7 @@ describe('float window', function()
it("correctly orders multiple opened floats (non-current last)", function()
local buf = meths.create_buf(false,false)
local win = meths.open_win(buf, false, {relative='editor', width=20, height=2, row=2, col=5})
- meths.win_set_option(win, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+ meths.set_option_value("winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg", {win=win.id})
if multigrid then
screen:expect{grid=[[
@@ -8333,8 +9984,8 @@ describe('float window', function()
]], float_pos={
[4] = { { id = 1001 }, "NW", 1, 2, 5, true };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8351,10 +10002,10 @@ describe('float window', function()
exec_lua [[
local buf = vim.api.nvim_create_buf(false,false)
local win = vim.api.nvim_open_win(buf, true, {relative='editor', width=12, height=2, row=4, col=10})
- vim.api.nvim_win_set_option(win, "winhl", "Normal:Search,EndOfBuffer:Search")
+ vim.wo[win].winhl = "Normal:Search,EndOfBuffer:Search"
buf = vim.api.nvim_create_buf(false,false)
win = vim.api.nvim_open_win(buf, false, {relative='editor', width=16, height=2, row=3, col=8})
- vim.api.nvim_win_set_option(win, "winhl", "EndOfBuffer:Normal")
+ vim.wo[win].winhl = "EndOfBuffer:Normal"
]]
if multigrid then
@@ -8390,10 +10041,10 @@ describe('float window', function()
[5] = { { id = 1002 }, "NW", 1, 4, 10, true };
[6] = { { id = 1003 }, "NW", 1, 3, 8, true };
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8411,11 +10062,11 @@ describe('float window', function()
it('can use z-index', function()
local buf = meths.create_buf(false,false)
local win1 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=1, col=5, zindex=30})
- meths.win_set_option(win1, "winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg")
+ meths.set_option_value("winhl", "Normal:ErrorMsg,EndOfBuffer:ErrorMsg", {win=win1.id})
local win2 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=6, zindex=50})
- meths.win_set_option(win2, "winhl", "Normal:Search,EndOfBuffer:Search")
+ meths.set_option_value("winhl", "Normal:Search,EndOfBuffer:Search", {win=win2.id})
local win3 = meths.open_win(buf, false, {relative='editor', width=20, height=3, row=3, col=7, zindex=40})
- meths.win_set_option(win3, "winhl", "Normal:Question,EndOfBuffer:Question")
+ meths.set_option_value("winhl", "Normal:Question,EndOfBuffer:Question", {win=win3.id})
if multigrid then
screen:expect{grid=[[
@@ -8453,10 +10104,10 @@ describe('float window', function()
[5] = {{id = 1002}, "NW", 1, 2, 6, true, 50};
[6] = {{id = 1003}, "NW", 1, 3, 7, true, 40};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [6] = {win = {id = 1003}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8474,7 +10125,7 @@ describe('float window', function()
it('can use winbar', function()
local buf = meths.create_buf(false,false)
local win1 = meths.open_win(buf, false, {relative='editor', width=15, height=3, row=1, col=5})
- meths.win_set_option(win1, 'winbar', 'floaty bar')
+ meths.set_option_value('winbar', 'floaty bar', {win=win1.id})
if multigrid then
screen:expect{grid=[[
@@ -8502,8 +10153,8 @@ describe('float window', function()
]], float_pos={
[4] = {{id = 1001}, "NW", 1, 1, 5, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8549,8 +10200,8 @@ describe('float window', function()
]], float_pos={
[4] = {{id = 1001}, "NW", 1, 0, 4, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8594,7 +10245,7 @@ describe('float window', function()
{0:~ }|
{0:~ }|
## grid 3
- ## grid 5
+ ## grid 4
{5:┌────────────────────────────────────────┐}|
{5:│}{1: }{5:│}|
{5:│}{1: }{5:│}|
@@ -8602,10 +10253,10 @@ describe('float window', function()
{5:│}{1: }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "SW", 1, 9, 0, true, 50};
+ [4] = {{id = 1001}, "SW", 1, 9, 0, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8650,16 +10301,16 @@ describe('float window', function()
{0:~ }|
{0:~ }|
## grid 3
- ## grid 5
+ ## grid 4
{5:┌────────────────────────────────────────┐}|
{5:│}{1: }{5:│}|
{5:│}{1: }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "SW", 1, 9, 0, true, 50};
+ [4] = {{id = 1001}, "SW", 1, 9, 0, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8701,7 +10352,7 @@ describe('float window', function()
{0:~ }|
## grid 3
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8746,7 +10397,7 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:┌────────────────────────────────────────┐}|
{5:│}{1: }{5:│}|
{5:│}{1: }{5:│}|
@@ -8754,10 +10405,10 @@ describe('float window', function()
{5:│}{1: }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "SW", 1, 8, 0, true, 50};
+ [4] = {{id = 1001}, "SW", 1, 8, 0, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8806,7 +10457,7 @@ describe('float window', function()
## grid 3
|
{8:Press ENTER or type command to continue}^ |
- ## grid 5
+ ## grid 4
{5:┌────────────────────────────────────────┐}|
{5:│}{1: }{5:│}|
{5:│}{1: }{5:│}|
@@ -8814,10 +10465,10 @@ describe('float window', function()
{5:│}{1: }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "SW", 1, 8, 0, true, 50};
+ [4] = {{id = 1001}, "SW", 1, 8, 0, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8857,16 +10508,16 @@ describe('float window', function()
{0:~ }|
## grid 3
|
- ## grid 5
+ ## grid 4
{5:┌────────────────────────────────────────┐}|
{5:│}{1: }{5:│}|
{5:│}{1: }{5:│}|
{5:└────────────────────────────────────────┘}|
]], float_pos={
- [5] = {{id = 1002}, "SW", 1, 8, 0, true, 50};
+ [4] = {{id = 1001}, "SW", 1, 8, 0, true, 50};
}, win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [5] = {win = {id = 1002}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
else
screen:expect{grid=[[
@@ -8907,9 +10558,8 @@ describe('float window', function()
## grid 3
|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
-
else
screen:expect{grid=[[
^ |
@@ -8952,6 +10602,696 @@ describe('float window', function()
test_float_move_close('autocmd BufWinLeave * ++once redraw')
end)
end)
+
+ it(':sleep cursor placement #22639', function()
+ local float_opts = {relative = 'editor', row = 1, col = 1, width = 4, height = 3}
+ local win = meths.open_win(meths.create_buf(false, false), true, float_opts)
+ feed('iab<CR>cd<Esc>')
+ feed(':sleep 100')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100^ |
+ ## grid 4
+ {1:ab }|
+ {1:cd }|
+ {2:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{1:ab }{0: }|
+ {0:~}{1:cd }{0: }|
+ {0:~}{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ :sleep 100^ |
+ ]]}
+ end
+
+ feed('<CR>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100 |
+ ## grid 4
+ {1:ab }|
+ {1:c^d }|
+ {2:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{1:ab }{0: }|
+ {0:~}{1:c^d }{0: }|
+ {0:~}{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ :sleep 100 |
+ ]]}
+ end
+ feed('<C-C>')
+ screen:expect_unchanged()
+
+ meths.win_set_config(win, {border = 'single'})
+ feed(':sleep 100')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100^ |
+ ## grid 4
+ {5:┌────┐}|
+ {5:│}{1:ab }{5:│}|
+ {5:│}{1:cd }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{5:┌────┐}{0: }|
+ {0:~}{5:│}{1:ab }{5:│}{0: }|
+ {0:~}{5:│}{1:cd }{5:│}{0: }|
+ {0:~}{5:│}{2:~ }{5:│}{0: }|
+ {0:~}{5:└────┘}{0: }|
+ :sleep 100^ |
+ ]]}
+ end
+
+ feed('<CR>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100 |
+ ## grid 4
+ {5:┌────┐}|
+ {5:│}{1:ab }{5:│}|
+ {5:│}{1:c^d }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{5:┌────┐}{0: }|
+ {0:~}{5:│}{1:ab }{5:│}{0: }|
+ {0:~}{5:│}{1:c^d }{5:│}{0: }|
+ {0:~}{5:│}{2:~ }{5:│}{0: }|
+ {0:~}{5:└────┘}{0: }|
+ :sleep 100 |
+ ]]}
+ end
+ feed('<C-C>')
+ screen:expect_unchanged()
+
+ command('setlocal winbar=foo')
+ feed(':sleep 100')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100^ |
+ ## grid 4
+ {5:┌────┐}|
+ {5:│}{3:foo }{5:│}|
+ {5:│}{1:ab }{5:│}|
+ {5:│}{1:cd }{5:│}|
+ {5:└────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{5:┌────┐}{0: }|
+ {0:~}{5:│}{3:foo }{5:│}{0: }|
+ {0:~}{5:│}{1:ab }{5:│}{0: }|
+ {0:~}{5:│}{1:cd }{5:│}{0: }|
+ {0:~}{5:└────┘}{0: }|
+ :sleep 100^ |
+ ]]}
+ end
+
+ feed('<CR>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ :sleep 100 |
+ ## grid 4
+ {5:┌────┐}|
+ {5:│}{3:foo }{5:│}|
+ {5:│}{1:ab }{5:│}|
+ {5:│}{1:c^d }{5:│}|
+ {5:└────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 1, curcol = 1, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{5:┌────┐}{0: }|
+ {0:~}{5:│}{3:foo }{5:│}{0: }|
+ {0:~}{5:│}{1:ab }{5:│}{0: }|
+ {0:~}{5:│}{1:c^d }{5:│}{0: }|
+ {0:~}{5:└────┘}{0: }|
+ :sleep 100 |
+ ]]}
+ end
+ feed('<C-C>')
+ screen:expect_unchanged()
+ end)
+
+ it('with rightleft and border #22640', function()
+ local float_opts = {relative='editor', width=5, height=3, row=1, col=1, border='single'}
+ meths.open_win(meths.create_buf(false, false), true, float_opts)
+ command('setlocal rightleft')
+ feed('iabc<CR>def<Esc>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:┌─────┐}|
+ {5:│}{1: cba}{5:│}|
+ {5:│}{1: ^fed}{5:│}|
+ {5:│}{2: ~}{5:│}|
+ {5:└─────┘}|
+ ]], float_pos={
+ [4] = {{id = 1001}, "NW", 1, 1, 1, true, 50};
+ }, win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 2, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect{grid=[[
+ |
+ {0:~}{5:┌─────┐}{0: }|
+ {0:~}{5:│}{1: cba}{5:│}{0: }|
+ {0:~}{5:│}{1: ^fed}{5:│}{0: }|
+ {0:~}{5:│}{2: ~}{5:│}{0: }|
+ {0:~}{5:└─────┘}{0: }|
+ |
+ ]]}
+ end
+ end)
+
+ it('float window with hide option', function()
+ local buf = meths.create_buf(false,false)
+ local win = meths.open_win(buf, false, {relative='editor', width=10, height=2, row=2, col=5, hide = true})
+ local expected_pos = {
+ [4]={{id=1001}, 'NW', 1, 2, 5, true},
+ }
+
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ## grid 4 (hidden)
+ {1: }|
+ {2:~ }|
+ ]], float_pos = {}}
+ else
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+
+ meths.win_set_config(win, {hide = false})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ## grid 4
+ {1: }|
+ {2:~ }|
+ ]], float_pos = expected_pos}
+ else
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }{1: }{0: }|
+ {0:~ }{2:~ }{0: }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+
+ meths.win_set_config(win, {hide=true})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ## grid 4 (hidden)
+ {1: }|
+ {2:~ }|
+ ]], float_pos = {}}
+ else
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+ end)
+
+ it(':fclose command #9663', function()
+ local buf_a = meths.create_buf(false,false)
+ local buf_b = meths.create_buf(false,false)
+ local buf_c = meths.create_buf(false,false)
+ local buf_d = meths.create_buf(false,false)
+ local config_a = {relative='editor', width=11, height=11, row=5, col=5, border ='single', zindex=50}
+ local config_b = {relative='editor', width=8, height=8, row=7, col=7, border ='single', zindex=70}
+ local config_c = {relative='editor', width=4, height=4, row=9, col=9, border ='single',zindex=90}
+ local config_d = {relative='editor', width=2, height=2, row=10, col=10, border ='single',zindex=100}
+ meths.open_win(buf_a, false, config_a)
+ meths.open_win(buf_b, false, config_b)
+ meths.open_win(buf_c, false, config_c)
+ meths.open_win(buf_d, false, config_d)
+ local expected_pos = {
+ [4]={{id=1001}, 'NW', 1, 5, 5, true, 50},
+ [5]={{id=1002}, 'NW', 1, 7, 7, true, 70},
+ [6]={{id=1003}, 'NW', 1, 9, 9, true, 90},
+ [7]={{id=1004}, 'NW', 1, 10, 10, true, 100},
+ }
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {5:┌───────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└───────────┘}|
+ ## grid 5
+ {5:┌────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────────┘}|
+ ## grid 6
+ {5:┌────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────┘}|
+ ## grid 7
+ {5:┌──┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└──┘}|
+ ]], float_pos=expected_pos}
+ else
+ screen:expect([[
+ ^ {5:┌─┌─┌────┐─┐┐} |
+ {0:~ }{5:│}{1: }{5:│}{1: }{5:│}{1: }{5:│}{1: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:│┌──┐│}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:││}{1: }{5:││}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:││}{2:~ }{5:││}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:└└──┘┘}{2: }{5:││}{0: }|
+ |
+ ]])
+ end
+ -- close the window with the highest zindex value
+ command('fclose')
+ expected_pos[7] = nil
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ## grid 4
+ {5:┌───────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└───────────┘}|
+ ## grid 5
+ {5:┌────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────────┘}|
+ ## grid 6
+ {5:┌────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────┘}|
+ ]], float_pos=expected_pos}
+ else
+ screen:expect([[
+ ^ {5:┌─┌─┌────┐─┐┐} |
+ {0:~ }{5:│}{1: }{5:│}{1: }{5:│}{1: }{5:│}{1: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:│}{2:~ }{5:│}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:│}{2:~ }{5:│}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:│}{2:~ }{5:│}{2: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~}{5:└────┘}{2: }{5:││}{0: }|
+ |
+ ]])
+ end
+ -- with range
+ command('1fclose')
+ expected_pos[6] = nil
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ## grid 4
+ {5:┌───────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└───────────┘}|
+ ## grid 5
+ {5:┌────────┐}|
+ {5:│}{1: }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:│}{2:~ }{5:│}|
+ {5:└────────┘}|
+ ]], float_pos=expected_pos}
+ else
+ screen:expect([[
+ ^ {5:┌─┌────────┐┐} |
+ {0:~ }{5:│}{1: }{5:│}{1: }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~ }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~ }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~ }{5:││}{0: }|
+ {0:~ }{5:│}{2:~}{5:│}{2:~ }{5:││}{0: }|
+ |
+ ]])
+ end
+ -- with bang
+ command('fclose!')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [2:----------------------------------------]|
+ [3:----------------------------------------]|
+ ## grid 2
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ ## grid 3
+ |
+
+ ]], float_pos={}}
+ else
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end
+ end)
end
describe('with ext_multigrid', function()
diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua
index 46a478c1ea..1addf7088e 100644
--- a/test/functional/ui/fold_spec.lua
+++ b/test/functional/ui/fold_spec.lua
@@ -4,11 +4,9 @@ local clear, feed, eq = helpers.clear, helpers.feed, helpers.eq
local command = helpers.command
local feed_command = helpers.feed_command
local insert = helpers.insert
-local expect = helpers.expect
local funcs = helpers.funcs
local meths = helpers.meths
local exec = helpers.exec
-local exec_lua = helpers.exec_lua
local assert_alive = helpers.assert_alive
@@ -29,23 +27,29 @@ describe("folded lines", function()
local function with_ext_multigrid(multigrid)
local screen
before_each(function()
- clear()
- command('hi VertSplit gui=reverse')
screen = Screen.new(45, 8)
screen:attach({rgb=true, ext_multigrid=multigrid})
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {reverse = true},
[3] = {bold = true, reverse = true},
- [4] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [4] = {foreground = Screen.colors.White, background = Screen.colors.Red},
[5] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey},
[6] = {background = Screen.colors.Yellow},
[7] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray},
[8] = {foreground = Screen.colors.Brown },
[9] = {bold = true, foreground = Screen.colors.Brown},
[10] = {background = Screen.colors.LightGrey, underline = true},
- [11] = {bold=true},
- [12] = {background = Screen.colors.Grey90},
+ [11] = {bold = true},
+ [12] = {foreground = Screen.colors.Red},
+ [13] = {foreground = Screen.colors.Red, background = Screen.colors.LightGrey},
+ [14] = {background = Screen.colors.Red},
+ [15] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Red},
+ [16] = {background = Screen.colors.LightGrey},
+ [17] = {background = Screen.colors.Yellow, foreground = Screen.colors.Red},
+ [18] = {background = Screen.colors.LightGrey, bold = true, foreground = Screen.colors.Blue},
+ [19] = {background = Screen.colors.Yellow, foreground = Screen.colors.DarkBlue},
+ [20] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Blue},
})
end)
@@ -89,11 +93,11 @@ describe("folded lines", function()
end
end)
- it("highlights with CursorLineFold when 'cursorline' is set", function()
- command("set cursorline foldcolumn=2 foldmethod=marker")
+ local function test_folded_cursorline()
+ command("set number cursorline foldcolumn=2")
command("hi link CursorLineFold Search")
insert(content1)
- feed("zf3j")
+ feed("ggzf3jj")
if multigrid then
screen:expect([[
## grid 1
@@ -106,26 +110,26 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {7: }in his cave. |
- {6: }{12:^ }|
+ {7:+ }{8: 1 }{5:+-- 4 lines: This is a················}|
+ {6: }{9: 5 }{12:^in his cave. }|
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {7: }in his cave. |
- {6: }{12:^ }|
- {1:~ }|
- |
+ {7:+ }{8: 1 }{5:+-- 4 lines: This is a················}|
+ {6: }{9: 5 }{12:^in his cave. }|
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]])
end
feed("k")
@@ -141,28 +145,34 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {6: }{12:^in his cave. }|
- {7: } |
+ {6:+ }{9: 1 }{13:^+-- 4 lines: This is a················}|
+ {7: }{8: 5 }in his cave. |
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {6: }{12:^in his cave. }|
- {7: } |
- {1:~ }|
- |
+ {6:+ }{9: 1 }{13:^+-- 4 lines: This is a················}|
+ {7: }{8: 5 }in his cave. |
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]])
end
+ -- CursorLine is applied correctly with screenrow motions #22232
+ feed("jgk")
+ screen:expect_unchanged()
+ -- CursorLine is applied correctly when closing a fold when cursor is not at fold start
+ feed("zo4Gzc")
+ screen:expect_unchanged()
command("set cursorlineopt=line")
if multigrid then
screen:expect([[
@@ -176,28 +186,79 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {7: }{12:^in his cave. }|
- {7: } |
+ {7:+ }{8: 1 }{13:^+-- 4 lines: This is a················}|
+ {7: }{8: 5 }in his cave. |
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
{1:~ }|
## grid 3
|
]])
else
screen:expect([[
- {7: }This is a |
- {7: }valid English |
- {7: }sentence composed by |
- {7: }an exhausted developer |
- {7: }{12:^in his cave. }|
- {7: } |
- {1:~ }|
- |
+ {7:+ }{8: 1 }{13:^+-- 4 lines: This is a················}|
+ {7: }{8: 5 }in his cave. |
+ {7: }{8: 6 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+ command("set relativenumber cursorlineopt=number")
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {6:+ }{9:1 }{5:^+-- 4 lines: This is a················}|
+ {7: }{8: 1 }in his cave. |
+ {7: }{8: 2 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]])
+ else
+ screen:expect([[
+ {6:+ }{9:1 }{5:^+-- 4 lines: This is a················}|
+ {7: }{8: 1 }in his cave. |
+ {7: }{8: 2 } |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
]])
end
+ end
+
+ describe("when 'cursorline' is set", function()
+ it('with high-priority CursorLine', function()
+ command("hi! CursorLine guibg=NONE guifg=Red gui=NONE")
+ test_folded_cursorline()
+ end)
+
+ it('with low-priority CursorLine', function()
+ command("hi! CursorLine guibg=NONE guifg=NONE gui=underline")
+ local attrs = screen:get_default_attr_ids()
+ attrs[12] = {underline = true}
+ attrs[13] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey, underline = true}
+ screen:set_default_attr_ids(attrs)
+ test_folded_cursorline()
+ end)
end)
it("work with spell", function()
@@ -362,6 +423,119 @@ describe("folded lines", function()
:set norightleft |
]])
end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 0, 0)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {7:▸ }{5:^+-- 6 lines: aa···························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ screen:expect([[
+ {7:▸ }{5:^+-- 6 lines: aa···························}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]])
+ end
+
+ -- Add a winbar to avoid double-clicks
+ command('setlocal winbar=!!!!!!')
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 1, 0)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {11:!!!!!! }|
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 1, 0)
+ screen:expect([[
+ {11:!!!!!! }|
+ {7:▾▸}{5:^+--- 5 lines: aa··························}|
+ {7:│ }ff |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set norightleft |
+ ]])
+ end
+
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 1, 1)
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {11:!!!!!! }|
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ ## grid 3
+ :set norightleft |
+ ]])
+ else
+ meths.input_mouse('left', 'press', '', 0, 1, 1)
+ screen:expect([[
+ {11:!!!!!! }|
+ {7:▾▾}^aa |
+ {7:││}bb |
+ {7:││}cc |
+ {7:││}dd |
+ {7:││}ee |
+ {7:│ }ff |
+ :set norightleft |
+ ]])
+ end
end)
it("works with split", function()
@@ -928,9 +1102,7 @@ describe("folded lines", function()
end)
it("works with multibyte text", function()
- -- Currently the only allowed value of 'maxcombine'
- eq(6, meths.get_option('maxcombine'))
- eq(true, meths.get_option('arabicshape'))
+ eq(true, meths.get_option_value('arabicshape', {}))
insert([[
å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢͟ العَرَبِيَّة
möre text]])
@@ -946,7 +1118,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
+ å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
möre tex^t |
{1:~ }|
{1:~ }|
@@ -958,7 +1130,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- å 语 x̎͂̀̂͛͛ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
+ å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ |
möre tex^t |
{1:~ }|
{1:~ }|
@@ -982,7 +1154,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -994,7 +1166,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ ﺎﻠﻋَﺮَﺒِﻳَّﺓ·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1018,7 +1190,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1030,7 +1202,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة·················}|
+ {5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة·················}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1054,7 +1226,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة···········}|
+ {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة···········}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1066,7 +1238,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̎͂̀̂͛͛ العَرَبِيَّة···········}|
+ {7:+ }{8: 1 }{5:^+-- 2 lines: å 语 x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ العَرَبِيَّة···········}|
{1:~ }|
{1:~ }|
{1:~ }|
@@ -1091,7 +1263,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:···········ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}{8: 1 }{7: +}|
+ {5:···········ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}{8: 1 }{7: +}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1103,7 +1275,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:···········ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}{8: 1 }{7: +}|
+ {5:···········ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}{8: 1 }{7: +}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1127,7 +1299,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1139,7 +1311,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ةيَّبِرَعَلا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1163,7 +1335,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1175,7 +1347,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- {5:·················ةيَّبِرَعَلا x̎͂̀̂͛͛ 语 å :senil 2 --^+}|
+ {5:·················ﺔﻴَّﺑِﺮَﻌَﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å :senil 2 --^+}|
{1: ~}|
{1: ~}|
{1: ~}|
@@ -1199,7 +1371,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̎͂̀̂͛͛ 语 å|
+ ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1211,7 +1383,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̎͂̀̂͛͛ 语 å|
+ ﺔﻴَّﺑِﺮَﻌَ^ﻟﺍ x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1235,7 +1407,7 @@ describe("folded lines", function()
[2:---------------------------------------------]|
[3:---------------------------------------------]|
## grid 2
- ةيَّبِرَعَ^لا x̎͂̀̂͛͛ 语 å|
+ ةيَّبِرَعَ^لا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1247,7 +1419,7 @@ describe("folded lines", function()
]])
else
screen:expect([[
- ةيَّبِرَعَ^لا x̎͂̀̂͛͛ 语 å|
+ ةيَّبِرَعَ^لا x̨̣̘̫̲͚͎̎͂̀̂͛͛̾͢ 语 å|
txet eröm|
{1: ~}|
{1: ~}|
@@ -1854,21 +2026,19 @@ describe("folded lines", function()
end
end)
- it('fold attached virtual lines are drawn correctly #21837', function()
+ it('fold attached virtual lines are drawn and scrolled correctly #21837', function()
funcs.setline(1, 'line 1')
funcs.setline(2, 'line 2')
funcs.setline(3, 'line 3')
funcs.setline(4, 'line 4')
feed("zfj")
- exec_lua([[
- local ns = vim.api.nvim_create_namespace("ns")
- vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 1", ""}}} })
- vim.api.nvim_buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{"virt_line below line 2", ""}}} })
- vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 3", ""}}} })
- vim.api.nvim_buf_set_extmark(0, ns, 3, 0, { virt_lines = {{{"virt_line below line 4", ""}}} })
- ]])
+ local ns = meths.create_namespace('ns')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 1", ""}}} })
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{"virt_line below line 2", ""}}} })
+ meths.buf_set_extmark(0, ns, 2, 0, { virt_lines_above = true, virt_lines = {{{"virt_line above line 3", ""}}} })
+ meths.buf_set_extmark(0, ns, 3, 0, { virt_lines = {{{"virt_line below line 4", ""}}} })
if multigrid then
- screen:expect([[
+ screen:expect{grid=[[
## grid 1
[2:---------------------------------------------]|
[2:---------------------------------------------]|
@@ -1888,7 +2058,9 @@ describe("folded lines", function()
{1:~ }|
## grid 3
|
- ]])
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 5, curline = 0, curcol = 0, linecount = 4, sum_scroll_delta = 0};
+ }}
else
screen:expect([[
{5:^+-- 2 lines: line 1·························}|
@@ -1904,7 +2076,7 @@ describe("folded lines", function()
feed('jzfj')
if multigrid then
- screen:expect([[
+ screen:expect{grid=[[
## grid 1
[2:---------------------------------------------]|
[2:---------------------------------------------]|
@@ -1924,7 +2096,9 @@ describe("folded lines", function()
{1:~ }|
## grid 3
|
- ]])
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 5, curline = 2, curcol = 0, linecount = 4, sum_scroll_delta = 0};
+ }}
else
screen:expect([[
{5:+-- 2 lines: line 1·························}|
@@ -1941,7 +2115,7 @@ describe("folded lines", function()
feed('kzo<C-Y>')
funcs.setline(5, 'line 5')
if multigrid then
- screen:expect([[
+ screen:expect{grid=[[
## grid 1
[2:---------------------------------------------]|
[2:---------------------------------------------]|
@@ -1961,7 +2135,9 @@ describe("folded lines", function()
{1:~ }|
## grid 3
|
- ]])
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 6, curline = 0, curcol = 0, linecount = 5, sum_scroll_delta = -1};
+ }}
else
screen:expect([[
virt_line above line 1 |
@@ -1974,6 +2150,940 @@ describe("folded lines", function()
|
]])
end
+
+ meths.input_mouse('left', 'press', '', multigrid and 2 or 0, 4, 0)
+ eq({
+ screencol = 1,
+ screenrow = 5,
+ winid = 1000,
+ wincol = 1,
+ winrow = 5,
+ line = 3,
+ column = 1,
+ coladd = 0,
+ }, funcs.getmousepos())
+
+ meths.buf_set_extmark(0, ns, 1, 0, { virt_lines = {{{"more virt_line below line 2", ""}}} })
+ feed('G<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ line 1 |
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect([[
+ line 1 |
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 1, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 1};
+ }}
+ else
+ screen:expect([[
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 2};
+ }}
+ else
+ screen:expect([[
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 3};
+ }}
+ else
+ screen:expect([[
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 4};
+ }}
+ else
+ screen:expect([[
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('<C-E>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 4, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 5};
+ }}
+ else
+ screen:expect([[
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ feed('3<C-Y>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 2};
+ }}
+ else
+ screen:expect([[
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^line 5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+
+ meths.input_mouse('left', 'press', '3', multigrid and 2 or 0, 3, 0)
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^l{16:ine 5} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL LINE --} |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 2};
+ }}
+ else
+ screen:expect([[
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^l{16:ine 5} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL LINE --} |
+ ]])
+ end
+
+ meths.input_mouse('left', 'drag', '3', multigrid and 2 or 0, 7, 0)
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^l{16:ine 5} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL LINE --} |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 0, linecount = 5, sum_scroll_delta = 3};
+ }}
+ else
+ screen:expect([[
+ more virt_line below line 2 |
+ {5:+-- 2 lines: line 3·························}|
+ ^l{16:ine 5} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL LINE --} |
+ ]])
+ end
+
+ meths.input_mouse('left', 'drag', '3', multigrid and 2 or 0, 7, 5)
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {5:+-- 2 lines: line 3·························}|
+ {16:line }^5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL LINE --} |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 2, botline = 6, curline = 4, curcol = 5, linecount = 5, sum_scroll_delta = 4};
+ }}
+ else
+ screen:expect([[
+ {5:+-- 2 lines: line 3·························}|
+ {16:line }^5 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL LINE --} |
+ ]])
+ end
+
+ feed('<Esc>gg')
+ command('botright 1split | wincmd w')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ {3:[No Name] [+] }|
+ [4:---------------------------------------------]|
+ {2:[No Name] [+] }|
+ [3:---------------------------------------------]|
+ ## grid 2
+ ^line 1 |
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ ## grid 3
+ |
+ ## grid 4
+ line 1 |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 5, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 5, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect([[
+ ^line 1 |
+ line 2 |
+ virt_line below line 2 |
+ more virt_line below line 2 |
+ {3:[No Name] [+] }|
+ line 1 |
+ {2:[No Name] [+] }|
+ |
+ ]])
+ end
+
+ feed('<C-Y>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ {3:[No Name] [+] }|
+ [4:---------------------------------------------]|
+ {2:[No Name] [+] }|
+ [3:---------------------------------------------]|
+ ## grid 2
+ virt_line above line 1 |
+ ^line 1 |
+ line 2 |
+ virt_line below line 2 |
+ ## grid 3
+ |
+ ## grid 4
+ line 1 |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 5, sum_scroll_delta = -1};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 5, sum_scroll_delta = 0};
+ }}
+ else
+ screen:expect([[
+ virt_line above line 1 |
+ ^line 1 |
+ line 2 |
+ virt_line below line 2 |
+ {3:[No Name] [+] }|
+ line 1 |
+ {2:[No Name] [+] }|
+ |
+ ]])
+ end
+ end)
+
+ it('Folded and Visual highlights are combined #19691', function()
+ command('hi! Visual guibg=Red')
+ insert([[
+ " foofoofoofoofoofoo
+ " 口 {{{1
+ set nocp
+ " }}}1
+ " barbarbarbarbarbar
+ " 口 {{{1
+ set foldmethod=marker
+ " }}}1
+ " bazbazbazbazbazbaz]])
+ feed('gg')
+ command('source')
+ feed('<C-V>G15l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {14:" foofoofoofoofo}ofoo |
+ {15:+-- 3 lines: " }{5:口···························}|
+ {14:" barbarbarbarba}rbar |
+ {15:+-- 3 lines: " }{5:口···························}|
+ {14:" bazbazbazbazb}^azbaz |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ {14:" foofoofoofoofo}ofoo |
+ {15:+-- 3 lines: " }{5:口···························}|
+ {14:" barbarbarbarba}rbar |
+ {15:+-- 3 lines: " }{5:口···························}|
+ {14:" bazbazbazbazb}^azbaz |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+
+ feed('l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {14:" foofoofoofoofoo}foo |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" barbarbarbarbar}bar |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" bazbazbazbazba}^zbaz |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ {14:" foofoofoofoofoo}foo |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" barbarbarbarbar}bar |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" bazbazbazbazba}^zbaz |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+
+ feed('l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {14:" foofoofoofoofoof}oo |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" barbarbarbarbarb}ar |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" bazbazbazbazbaz}^baz |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ {14:" foofoofoofoofoof}oo |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" barbarbarbarbarb}ar |
+ {15:+-- 3 lines: " 口}{5:···························}|
+ {14:" bazbazbazbazbaz}^baz |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+
+ feed('2l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {14:" foofoofoofoofoofoo} |
+ {15:+-- 3 lines: " 口··}{5:·························}|
+ {14:" barbarbarbarbarbar} |
+ {15:+-- 3 lines: " 口··}{5:·························}|
+ {14:" bazbazbazbazbazba}^z |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ {14:" foofoofoofoofoofoo} |
+ {15:+-- 3 lines: " 口··}{5:·························}|
+ {14:" barbarbarbarbarbar} |
+ {15:+-- 3 lines: " 口··}{5:·························}|
+ {14:" bazbazbazbazbazba}^z |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+
+ feed('O16l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ " foofoofoofoofo{14:ofoo} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " barbarbarbarba{14:rbar} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " bazbazbazbazba^z{14:baz} |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ " foofoofoofoofo{14:ofoo} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " barbarbarbarba{14:rbar} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " bazbazbazbazba^z{14:baz} |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+
+ feed('l')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ " foofoofoofoofoo{14:foo} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " barbarbarbarbar{14:bar} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " bazbazbazbazbaz^b{14:az} |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ else
+ screen:expect([[
+ " foofoofoofoofoo{14:foo} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " barbarbarbarbar{14:bar} |
+ {5:+-- 3 lines: " }{15:口··}{5:·························}|
+ " bazbazbazbazbaz^b{14:az} |
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL BLOCK --} |
+ ]])
+ end
+ end)
+
+ it('do not show search or match highlight #24084', function()
+ insert([[
+ line 1
+ line 2
+ line 3
+ line 4]])
+ command('2,3fold')
+ feed('/line')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {2:line} 1 |
+ {5:+-- 2 lines: line 2·························}|
+ {6:line} 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ /line^ |
+ ]])
+ else
+ screen:expect([[
+ {2:line} 1 |
+ {5:+-- 2 lines: line 2·························}|
+ {6:line} 4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ /line^ |
+ ]])
+ end
+ feed('<Esc>')
+ funcs.matchadd('Search', 'line')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [2:---------------------------------------------]|
+ [3:---------------------------------------------]|
+ ## grid 2
+ {6:line} 1 |
+ {5:+-- 2 lines: line 2·························}|
+ {6:line} ^4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]])
+ else
+ screen:expect([[
+ {6:line} 1 |
+ {5:+-- 2 lines: line 2·························}|
+ {6:line} ^4 |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+ end)
+
+ it('support foldtext with virtual text format', function()
+ screen:try_resize(30, 7)
+ insert(content1)
+ command("hi! CursorLine guibg=NONE guifg=Red gui=NONE")
+ command('hi F0 guibg=Red guifg=Black')
+ command('hi F1 guifg=White')
+ meths.set_option_value('cursorline', true, {})
+ meths.set_option_value('foldcolumn', '4', {})
+ meths.set_option_value('foldtext', '['
+ .. '["▶", ["F0", "F1"]], '
+ .. '[v:folddashes], '
+ .. '["\t", "Search"], '
+ .. '[getline(v:foldstart), "NonText"]]', {})
+
+ command('3,4fold')
+ command('5,6fold')
+ command('2,6fold')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [3:------------------------------]|
+ ## grid 2
+ {7: }This is a |
+ {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]])
+ else
+ screen:expect([[
+ {7: }This is a |
+ {7:+ }{4:^▶}{13:-}{17: }{18:valid English}{13:·····}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+ eq('▶-\tvalid English', funcs.foldtextresult(2))
+
+ feed('zo')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [3:------------------------------]|
+ ## grid 2
+ {7: }This is a |
+ {7:- }valid English |
+ {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }|
+ {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]])
+ else
+ screen:expect([[
+ {7: }This is a |
+ {7:- }valid English |
+ {7:│+ }{4:▶}{5:--}{19: }{18:sentence composed }|
+ {7:│+ }{4:^▶}{13:--}{17: }{18:in his cave.}{13:······}|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end
+ eq('▶--\tsentence composed by', funcs.foldtextresult(3))
+ eq('▶--\tin his cave.', funcs.foldtextresult(5))
+
+ command('hi! Visual guibg=Red')
+ feed('V2k')
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [3:------------------------------]|
+ ## grid 2
+ {7: }This is a |
+ {7:- }^v{14:alid English} |
+ {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }|
+ {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {11:-- VISUAL LINE --} |
+ ]])
+ else
+ screen:expect([[
+ {7: }This is a |
+ {7:- }^v{14:alid English} |
+ {7:│+ }{4:▶}{15:--}{19: }{20:sentence composed }|
+ {7:│+ }{4:▶}{15:--}{19: }{20:in his cave.}{15:······}|
+ {1:~ }|
+ {1:~ }|
+ {11:-- VISUAL LINE --} |
+ ]])
+ end
+
+ meths.set_option_value('rightleft', true, {})
+ if multigrid then
+ screen:expect([[
+ ## grid 1
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [2:------------------------------]|
+ [3:------------------------------]|
+ ## grid 2
+ a si sihT{7: }|
+ {14:hsilgnE dila}^v{7: -}|
+ {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}|
+ {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}|
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ {11:-- VISUAL LINE --} |
+ ]])
+ else
+ screen:expect([[
+ a si sihT{7: }|
+ {14:hsilgnE dila}^v{7: -}|
+ {20: desopmoc ecnetnes}{19: }{15:--}{4:▶}{7: +│}|
+ {15:······}{20:.evac sih ni}{19: }{15:--}{4:▶}{7: +│}|
+ {1: ~}|
+ {1: ~}|
+ {11:-- VISUAL LINE --} |
+ ]])
+ end
end)
end
@@ -1984,26 +3094,4 @@ describe("folded lines", function()
describe('without ext_multigrid', function()
with_ext_multigrid(false)
end)
-
- it('no folds remains if :delete makes buffer empty #19671', function()
- funcs.setline(1, {'foo', 'bar', 'baz'})
- command('2,3fold')
- command('%delete')
- eq(0, funcs.foldlevel(1))
- end)
-
- it('multibyte fold markers work #20438', function()
- meths.win_set_option(0, 'foldmethod', 'marker')
- meths.win_set_option(0, 'foldmarker', '«,»')
- insert([[
- bbbbb
- bbbbb
- bbbbb]])
- feed('zfgg')
- expect([[
- bbbbb/*«*/
- bbbbb
- bbbbb/*»*/]])
- eq(1, funcs.foldlevel(1))
- end)
end)
diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua
index 288c2a214f..7776e024b0 100644
--- a/test/functional/ui/highlight_spec.lua
+++ b/test/functional/ui/highlight_spec.lua
@@ -8,6 +8,7 @@ local feed_command, eq = helpers.feed_command, helpers.eq
local curbufmeths = helpers.curbufmeths
local funcs = helpers.funcs
local meths = helpers.meths
+local exec_lua = helpers.exec_lua
describe('colorscheme compatibility', function()
before_each(function()
@@ -376,7 +377,6 @@ describe('highlight', function()
-- Vertical cursor: highlights char-at-cursor. #8983
command('set guicursor=a:block-blinkon175')
- feed('<esc>gg$vhhh')
screen:expect([[
line1 foo{1:^ bar} |
|
@@ -427,7 +427,7 @@ describe('highlight', function()
^ |
{2:~ }|
|
- ]],{
+ ]], {
[1] = {strikethrough = true},
[2] = {bold = true, foreground = Screen.colors.Blue1},
})
@@ -516,7 +516,7 @@ describe('highlight', function()
{1:neovim} tabbed^ |
{0:~ }|
{5:-- INSERT --} |
- ]],{
+ ]], {
[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {background = Screen.colors.Yellow, foreground = Screen.colors.Red,
special = Screen.colors.Red},
@@ -527,6 +527,41 @@ describe('highlight', function()
})
end)
+
+ it("'diff', syntax and extmark #23722", function()
+ local screen = Screen.new(25,10)
+ screen:attach()
+ exec([[
+ new
+ call setline(1, ['', '01234 6789'])
+ windo diffthis
+ wincmd w
+ syn match WarningMsg "^.*$"
+ call nvim_buf_add_highlight(0, -1, 'ErrorMsg', 1, 2, 8)
+ ]])
+ screen:expect([[
+ {1: }^ |
+ {1: }{2:01}{3:234 67}{2:89}{5: }|
+ {4:~ }|
+ {4:~ }|
+ {7:[No Name] [+] }|
+ {1: } |
+ {1: }{6:-----------------------}|
+ {4:~ }|
+ {8:[No Name] }|
+ |
+ ]], {
+ [0] = {Screen.colors.WebGray, foreground = Screen.colors.DarkBlue},
+ [1] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4},
+ [2] = {foreground = Screen.colors.Red, background = Screen.colors.LightBlue},
+ [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.LightBlue},
+ [4] = {bold = true, foreground = Screen.colors.Blue},
+ [5] = {background = Screen.colors.LightBlue},
+ [6] = {bold = true, background = Screen.colors.LightCyan, foreground = Screen.colors.Blue1},
+ [7] = {reverse = true, bold = true},
+ [8] = {reverse = true},
+ })
+ end)
end)
describe("'listchars' highlight", function()
@@ -1412,10 +1447,10 @@ describe('ColorColumn highlight', function()
[3] = {foreground = Screen.colors.Brown}, -- LineNr
[4] = {foreground = Screen.colors.Brown, bold = true}, -- CursorLineNr
[5] = {foreground = Screen.colors.Blue, bold = true}, -- NonText
- -- NonText and ColorColumn
[6] = {foreground = Screen.colors.Blue, background = Screen.colors.LightRed, bold = true},
[7] = {reverse = true, bold = true}, -- StatusLine
[8] = {reverse = true}, -- StatusLineNC
+ [9] = {background = Screen.colors.Grey90, foreground = Screen.colors.Red},
})
screen:attach()
end)
@@ -1501,6 +1536,25 @@ describe('ColorColumn highlight', function()
|
]])
end)
+
+ it('is combined with low-priority CursorLine highlight #23016', function()
+ screen:try_resize(40, 2)
+ command('set colorcolumn=30 cursorline')
+ screen:expect([[
+ {2:^ }{1: }{2: }|
+ |
+ ]])
+ command('hi clear ColorColumn')
+ screen:expect([[
+ {2:^ }|
+ |
+ ]])
+ command('hi ColorColumn guifg=Red')
+ screen:expect([[
+ {2:^ }{9: }{2: }|
+ |
+ ]])
+ end)
end)
describe("MsgSeparator highlight and msgsep fillchar", function()
@@ -1812,6 +1866,31 @@ describe("'winhighlight' highlight", function()
]])
end)
+ it('works for background color in rightleft window #22640', function()
+ -- Use a wide screen to also check that this doesn't overflow linebuf_attr.
+ screen:try_resize(80, 6)
+ insert('aa')
+ command('setlocal rightleft')
+ command('setlocal winhl=Normal:Background1')
+ screen:expect([[
+ {1: ^aa}|
+ {2: ~}|
+ {2: ~}|
+ {2: ~}|
+ {2: ~}|
+ |
+ ]])
+ command('botright vsplit')
+ screen:expect([[
+ {1: aa│ ^aa}|
+ {2: ~}{1:│}{2: ~}|
+ {2: ~}{1:│}{2: ~}|
+ {2: ~}{1:│}{2: ~}|
+ {4:[No Name] [+] }{3:[No Name] [+] }|
+ |
+ ]])
+ end)
+
it('handles undefined groups', function()
command("set winhl=Normal:Background1")
screen:expect([[
@@ -2408,6 +2487,23 @@ describe("'winhighlight' highlight", function()
|
]]}
end)
+
+ it('can link to empty highlight group', function()
+ command 'hi NormalNC guibg=Red' -- czerwone time
+ command 'set winhl=NormalNC:Normal'
+ command 'split'
+
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {3:[No Name] }|
+ |
+ {0:~ }|
+ {4:[No Name] }|
+ |
+ ]]}
+ end)
end)
describe('highlight namespaces', function()
@@ -2427,6 +2523,8 @@ describe('highlight namespaces', function()
[6] = {bold = true, reverse = true};
[7] = {reverse = true};
[8] = {foreground = Screen.colors.Gray20};
+ [9] = {foreground = Screen.colors.Blue};
+ [10] = {bold = true, foreground = Screen.colors.SeaGreen};
}
ns1 = meths.create_namespace 'grungy'
@@ -2546,4 +2644,34 @@ describe('highlight namespaces', function()
|
]]}
end)
+
+ it('winhl does not accept invalid value #24586', function()
+ local res = exec_lua([[
+ local curwin = vim.api.nvim_get_current_win()
+ vim.api.nvim_command("set winhl=Normal:Visual")
+ local _, msg = pcall(vim.api.nvim_command,"set winhl='Normal:Wrong'")
+ return { msg, vim.wo[curwin].winhl }
+ ]])
+ eq({
+ "Vim(set):E5248: Invalid character in group name",
+ "Normal:Visual",
+ },res)
+ end)
+
+ it('Normal in set_hl #25474', function()
+ meths.set_hl(0, 'Normal', {bg='#333333'})
+ command('highlight Ignore')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {6: }|
+ |
+ Ignore {8:xxx} {9:ctermf}|
+ {9:g=}15 {9:guifg=}|
+ bg |
+ {10:Press ENTER or type comma}|
+ {10:nd to continue}^ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua
index 6fbf9b72c8..3ee67a710c 100644
--- a/test/functional/ui/inccommand_spec.lua
+++ b/test/functional/ui/inccommand_spec.lua
@@ -2,7 +2,6 @@ local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
-local curbufmeths = helpers.curbufmeths
local eq = helpers.eq
local eval = helpers.eval
local feed_command = helpers.feed_command
@@ -175,11 +174,14 @@ describe(":substitute, 'inccommand' preserves", function()
it("'undolevels' (inccommand="..case..")", function()
feed_command("set undolevels=139")
feed_command("setlocal undolevels=34")
+ feed_command("split") -- Show the buffer in multiple windows
feed_command("set inccommand=" .. case)
insert("as")
- feed(":%s/as/glork/<enter>")
- eq(meths.get_option('undolevels'), 139)
- eq(curbufmeths.get_option('undolevels'), 34)
+ feed(":%s/as/glork/")
+ poke_eventloop()
+ feed("<enter>")
+ eq(meths.get_option_value('undolevels', {scope='global'}), 139)
+ eq(meths.get_option_value('undolevels', {buf=0}), 34)
end)
end
@@ -745,10 +747,11 @@ describe(":substitute, 'inccommand' preserves undo", function()
end)
describe(":substitute, inccommand=split", function()
- local screen = Screen.new(30,15)
+ local screen
before_each(function()
clear()
+ screen = Screen.new(30,15)
common_setup(screen, "split", default_text .. default_text)
end)
@@ -1191,7 +1194,7 @@ describe(":substitute, inccommand=split", function()
it("deactivates if 'redrawtime' is exceeded #5602", function()
-- prevent redraws from 'incsearch'
- meths.set_option('incsearch', false)
+ meths.set_option_value('incsearch', false, {})
-- Assert that 'inccommand' is ENABLED initially.
eq("split", eval("&inccommand"))
-- Set 'redrawtime' to minimal value, to ensure timeout is triggered.
@@ -1413,10 +1416,11 @@ describe(":substitute, inccommand=split", function()
end)
describe("inccommand=nosplit", function()
- local screen = Screen.new(20,10)
+ local screen
before_each(function()
clear()
+ screen = Screen.new(20,10)
common_setup(screen, "nosplit", default_text .. default_text)
end)
@@ -1644,11 +1648,12 @@ describe("inccommand=nosplit", function()
end)
describe(":substitute, 'inccommand' with a failing expression", function()
- local screen = Screen.new(20,10)
+ local screen
local cases = { "", "split", "nosplit" }
local function refresh(case)
clear()
+ screen = Screen.new(20,10)
common_setup(screen, case, default_text)
end
@@ -1717,7 +1722,7 @@ describe("'inccommand' and :cnoremap", function()
local function refresh(case, visual)
clear()
- screen = visual and Screen.new(50,10) or nil
+ screen = visual and Screen.new(80,10) or nil
common_setup(screen, case, default_text)
end
@@ -2127,9 +2132,10 @@ describe("'inccommand' with 'gdefault'", function()
end)
describe(":substitute", function()
- local screen = Screen.new(30,15)
+ local screen
before_each(function()
clear()
+ screen = Screen.new(30,15)
end)
it("inccommand=split, highlights multiline substitutions", function()
@@ -2335,8 +2341,7 @@ describe(":substitute", function()
]])
end)
- it("inccommand=split, substitutions of different length",
- function()
+ it("inccommand=split, substitutions of different length", function()
common_setup(screen, "split", "T T123 T2T TTT T090804\nx")
feed(":%s/T\\([0-9]\\+\\)/\\1\\1/g")
@@ -2462,16 +2467,14 @@ describe(":substitute", function()
end)
it("inccommand=split, contraction of two subsequent NL chars", function()
- -- luacheck: push ignore 611
local text = [[
AAA AA
-
+
BBB BB
-
+
CCC CC
-
+
]]
- -- luacheck: pop
-- This used to crash, but more than 20 highlight entries are required
-- to reproduce it (so that the marktree has multiple nodes)
@@ -2498,16 +2501,14 @@ describe(":substitute", function()
end)
it("inccommand=nosplit, contraction of two subsequent NL chars", function()
- -- luacheck: push ignore 611
local text = [[
AAA AA
-
+
BBB BB
-
+
CCC CC
-
+
]]
- -- luacheck: pop
common_setup(screen, "nosplit", string.rep(text,10))
feed(":%s/\\n\\n/<c-v><c-m>/g")
@@ -2872,8 +2873,8 @@ it(':substitute with inccommand during :terminal activity', function()
return
end
retry(2, 40000, function()
- local screen = Screen.new(30,15)
clear()
+ local screen = Screen.new(30,15)
command("set cmdwinheight=3")
feed(([[:terminal "%s" REP 5000 xxx<cr>]]):format(testprg('shell-test')))
@@ -2886,14 +2887,15 @@ it(':substitute with inccommand during :terminal activity', function()
feed('gg')
feed(':%s/foo/ZZZ')
sleep(20) -- Allow some terminal activity.
- helpers.poke_eventloop()
+ poke_eventloop()
+ screen:sleep(0)
screen:expect_unchanged()
end)
end)
it(':substitute with inccommand, timer-induced :redraw #9777', function()
- local screen = Screen.new(30,12)
clear()
+ local screen = Screen.new(30,12)
command('set cmdwinheight=3')
command('call timer_start(10, {-> execute("redraw")}, {"repeat":-1})')
command('call timer_start(10, {-> execute("redrawstatus")}, {"repeat":-1})')
@@ -2919,15 +2921,24 @@ it(':substitute with inccommand, timer-induced :redraw #9777', function()
end)
it(':substitute with inccommand, allows :redraw before first separator is typed #18857', function()
- local screen = Screen.new(30,6)
clear()
+ local screen = Screen.new(30,6)
common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz')
command('hi! link NormalFloat CursorLine')
local float_buf = meths.create_buf(false, true)
meths.open_win(float_buf, false, {
relative = 'editor', height = 1, width = 5, row = 3, col = 0, focusable = false,
})
- feed(':%s')
+ feed(':')
+ screen:expect([[
+ foo bar baz |
+ bar baz fox |
+ bar foo baz |
+ {16: }{15: }|
+ {15:~ }|
+ :^ |
+ ]])
+ feed('%s')
screen:expect([[
foo bar baz |
bar baz fox |
@@ -2949,8 +2960,8 @@ it(':substitute with inccommand, allows :redraw before first separator is typed
end)
it(':substitute with inccommand, does not crash if range contains invalid marks', function()
- local screen = Screen.new(30, 6)
clear()
+ local screen = Screen.new(30, 6)
common_setup(screen, 'split', 'test')
feed([[:'a,'bs]])
screen:expect([[
@@ -2975,8 +2986,8 @@ it(':substitute with inccommand, does not crash if range contains invalid marks'
end)
it(':substitute with inccommand, no unnecessary redraw if preview is not shown', function()
- local screen = Screen.new(60, 6)
clear()
+ local screen = Screen.new(60, 6)
common_setup(screen, 'split', 'test')
feed(':ls<CR>')
screen:expect([[
@@ -3028,8 +3039,8 @@ it(':substitute with inccommand, no unnecessary redraw if preview is not shown',
end)
it(":substitute doesn't crash with inccommand, if undo is empty #12932", function()
- local screen = Screen.new(10,5)
clear()
+ local screen = Screen.new(10,5)
command('set undolevels=-1')
common_setup(screen, 'split', 'test')
feed(':%s/test')
@@ -3048,8 +3059,8 @@ it(":substitute doesn't crash with inccommand, if undo is empty #12932", functio
end)
it(':substitute with inccommand works properly if undo is not synced #20029', function()
- local screen = Screen.new(30, 6)
clear()
+ local screen = Screen.new(30, 6)
common_setup(screen, 'nosplit', 'foo\nbar\nbaz')
meths.set_keymap('x', '<F2>', '<Esc>`<Oaaaaa asdf<Esc>`>obbbbb asdf<Esc>V`<k:s/asdf/', {})
feed('gg0<C-V>lljj<F2>')
@@ -3084,11 +3095,40 @@ it(':substitute with inccommand works properly if undo is not synced #20029', fu
baz]])
end)
+it(':substitute with inccommand does not unexpectedly change viewport #25697', function()
+ clear()
+ local screen = Screen.new(45, 5)
+ common_setup(screen, 'nosplit', long_multiline_text)
+ command('vnew | tabnew | tabclose')
+ screen:expect([[
+ ^ │£ m n |
+ {15:~ }│t œ ¥ |
+ {15:~ }│ |
+ {11:[No Name] }{10:[No Name] [+] }|
+ |
+ ]])
+ feed(':s/')
+ screen:expect([[
+ │£ m n |
+ {15:~ }│t œ ¥ |
+ {15:~ }│ |
+ {11:[No Name] }{10:[No Name] [+] }|
+ :s/^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ │£ m n |
+ {15:~ }│t œ ¥ |
+ {15:~ }│ |
+ {11:[No Name] }{10:[No Name] [+] }|
+ |
+ ]])
+end)
+
it('long :%s/ with inccommand does not collapse cmdline', function()
- local screen = Screen.new(10,5)
clear()
- common_setup(screen)
- command('set inccommand=nosplit')
+ local screen = Screen.new(10,5)
+ common_setup(screen, 'nosplit')
feed(':%s/AAAAAAA', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A',
'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A')
screen:expect([[
@@ -3100,6 +3140,21 @@ it('long :%s/ with inccommand does not collapse cmdline', function()
]])
end)
+it("with 'inccommand' typing invalid `={expr}` does not show error", function()
+ clear()
+ local screen = Screen.new(30, 6)
+ common_setup(screen, 'nosplit')
+ feed(':edit `=`')
+ screen:expect([[
+ |
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ {15:~ }|
+ :edit `=`^ |
+ ]])
+end)
+
it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", function()
clear()
common_setup(nil, 'nosplit')
@@ -3112,3 +3167,31 @@ it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", fu
feed('i')
assert_alive()
end)
+
+it("'inccommand' cannot be changed during preview #23136", function()
+ clear()
+ local screen = Screen.new(30, 6)
+ common_setup(screen, 'nosplit', 'foo\nbar\nbaz')
+ source([[
+ function! IncCommandToggle()
+ let prev = &inccommand
+
+ if &inccommand ==# 'split'
+ set inccommand=nosplit
+ elseif &inccommand ==# 'nosplit'
+ set inccommand=split
+ elseif &inccommand ==# ''
+ set inccommand=nosplit
+ else
+ throw 'unknown inccommand'
+ endif
+
+ return " \<BS>"
+ endfun
+
+ cnoremap <expr> <C-E> IncCommandToggle()
+ ]])
+
+ feed(':%s/foo/bar<C-E><C-E><C-E>')
+ assert_alive()
+end)
diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua
index 43e9b94feb..da7508fad1 100644
--- a/test/functional/ui/inccommand_user_spec.lua
+++ b/test/functional/ui/inccommand_user_spec.lua
@@ -239,7 +239,8 @@ describe("'inccommand' for user commands", function()
[1] = {background = Screen.colors.Yellow1},
[2] = {foreground = Screen.colors.Blue1, bold = true},
[3] = {reverse = true},
- [4] = {reverse = true, bold = true}
+ [4] = {reverse = true, bold = true},
+ [5] = {foreground = Screen.colors.Blue},
})
screen:attach()
exec_lua(setup_replace_cmd)
@@ -391,7 +392,7 @@ describe("'inccommand' for user commands", function()
vim.api.nvim_create_user_command('Replace', function() end, {
nargs = '*',
preview = function()
- vim.api.nvim_set_option('inccommand', 'split')
+ vim.api.nvim_set_option_value('inccommand', 'split', {})
return 2
end,
})
@@ -401,6 +402,147 @@ describe("'inccommand' for user commands", function()
feed('e')
assert_alive()
end)
+
+ it('no crash when adding highlight after :substitute #21495', function()
+ command('set inccommand=nosplit')
+ exec_lua([[
+ vim.api.nvim_create_user_command("Crash", function() end, {
+ preview = function(_, preview_ns, _)
+ vim.cmd("%s/text/cats/g")
+ vim.api.nvim_buf_add_highlight(0, preview_ns, "Search", 0, 0, -1)
+ return 1
+ end,
+ })
+ ]])
+ feed(':C')
+ screen:expect([[
+ {1: cats on line 1} |
+ more cats on line 2 |
+ oh no, even more cats |
+ will the cats ever stop |
+ oh well |
+ did the cats stop |
+ why won't it stop |
+ make the cats stop |
+ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :C^ |
+ ]])
+ assert_alive()
+ end)
+
+ it('no crash if preview callback executes undo #20036', function()
+ command('set inccommand=nosplit')
+ exec_lua([[
+ vim.api.nvim_create_user_command('Foo', function() end, {
+ nargs = '?',
+ preview = function(_, _, _)
+ vim.cmd.undo()
+ end,
+ })
+ ]])
+
+ -- Clear undo history
+ command('set undolevels=-1')
+ feed('ggyyp')
+ command('set undolevels=1000')
+
+ feed('yypp:Fo')
+ assert_alive()
+ feed('<Esc>:Fo')
+ assert_alive()
+ end)
+
+ local function test_preview_break_undo()
+ command('set inccommand=nosplit')
+ exec_lua([[
+ vim.api.nvim_create_user_command('Test', function() end, {
+ nargs = 1,
+ preview = function(opts, _, _)
+ vim.cmd('norm i' .. opts.args)
+ return 1
+ end
+ })
+ ]])
+ feed(':Test a.a.a.a.')
+ screen:expect([[
+ text on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ a.a.a.a. |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Test a.a.a.a.^ |
+ ]])
+ feed('<C-V><Esc>u')
+ screen:expect([[
+ text on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ a.a.a. |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ :Test a.a.a.a.{5:^[}u^ |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ text on line 1 |
+ more text on line 2 |
+ oh no, even more text |
+ will the text ever stop |
+ oh well |
+ did the text stop |
+ why won't it stop |
+ make the text stop |
+ ^ |
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ {2:~ }|
+ |
+ ]])
+ end
+
+ describe('breaking undo chain in Insert mode works properly', function()
+ it('when using i_CTRL-G_u #20248', function()
+ command('inoremap . .<C-G>u')
+ test_preview_break_undo()
+ end)
+
+ it('when setting &l:undolevels to itself #24575', function()
+ command('inoremap . .<Cmd>let &l:undolevels = &l:undolevels<CR>')
+ test_preview_break_undo()
+ end)
+ end)
end)
describe("'inccommand' with multiple buffers", function()
diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua
index 697677aa67..ef47ea7ed0 100644
--- a/test/functional/ui/linematch_spec.lua
+++ b/test/functional/ui/linematch_spec.lua
@@ -779,6 +779,192 @@ something
end)
end)
+ describe('setup a diff with 2 files and set linematch:30', function()
+ before_each(function()
+ feed(':set diffopt+=linematch:30<cr>')
+ local f1 = [[
+// abc d
+// d
+// d
+ ]]
+ local f2 = [[
+
+abc d
+d
+ ]]
+ write_file(fname, f1, false)
+ write_file(fname_2, f2, false)
+ reread()
+ end)
+
+ it('display results', function()
+ screen:expect([[
+ {1: }{10: 1 }{4:^ }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 2 }{9:abc d }│{1: }{10: 1 }{8:// }{9:abc d }|
+ {1: }{10: 3 }{9:d }│{1: }{10: 2 }{8:// }{9:d }|
+ {1: }{10: }{2:-------------------------------------------}│{1: }{10: 3 }{4:// d }|
+ {1: }{10: 4 } │{1: }{10: 4 } |
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }|
+ :e |
+ ]])
+ end)
+ end)
+ describe('setup a diff with 2 files and set linematch:30, with ignore white', function()
+ before_each(function()
+ feed(':set diffopt+=linematch:30<cr>:set diffopt+=iwhiteall<cr>')
+ local f1 = [[
+void testFunction () {
+ for (int i = 0; i < 10; i++) {
+ for (int j = 0; j < 10; j++) {
+ }
+ }
+}
+ ]]
+ local f2 = [[
+void testFunction () {
+ // for (int j = 0; j < 10; i++) {
+ // }
+}
+ ]]
+ write_file(fname, f1, false)
+ write_file(fname_2, f2, false)
+ reread()
+ end)
+
+ it('display results', function()
+ screen:expect([[
+ {1: }{10: 1 }^void testFunction () { │{1: }{10: 1 }void testFunction () { |
+ {1: }{10: }{2:-------------------------------------------}│{1: }{10: 2 }{4: for (int i = 0; i < 10; i++) { }|
+ {1: }{10: 2 }{9: }{8:// for (int j = 0; j < 10; i}{9:++) { }│{1: }{10: 3 }{9: }{8:for (int j = 0; j < 10; j}{9:++) { }|
+ {1: }{10: 3 }{9: }{8:// }{9:} }│{1: }{10: 4 }{9: } }|
+ {1: }{10: }{2:-------------------------------------------}│{1: }{10: 5 }{4: } }|
+ {1: }{10: 4 }} │{1: }{10: 6 }} |
+ {1: }{10: 5 } │{1: }{10: 7 } |
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }|
+ :e |
+ ]])
+ end)
+ end)
+ describe('a diff that would result in multiple groups before grouping optimization', function()
+ before_each(function()
+ feed(':set diffopt+=linematch:30<cr>')
+ local f1 = [[
+!A
+!B
+!C
+ ]]
+ local f2 = [[
+?Z
+?A
+?B
+?C
+?A
+?B
+?B
+?C
+ ]]
+ write_file(fname, f1, false)
+ write_file(fname_2, f2, false)
+ reread()
+ end)
+
+ it('display results', function()
+ screen:expect([[
+ {1: }{10: 1 }{4:^?Z }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 2 }{8:?}{9:A }│{1: }{10: 1 }{8:!}{9:A }|
+ {1: }{10: 3 }{8:?}{9:B }│{1: }{10: 2 }{8:!}{9:B }|
+ {1: }{10: 4 }{8:?}{9:C }│{1: }{10: 3 }{8:!}{9:C }|
+ {1: }{10: 5 }{4:?A }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 6 }{4:?B }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 7 }{4:?B }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 8 }{4:?C }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 9 } │{1: }{10: 4 } |
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }|
+ :e |
+ ]])
+ end)
+ end)
+ describe('a diff that would result in multiple groups before grouping optimization', function()
+ before_each(function()
+ feed(':set diffopt+=linematch:30<cr>')
+ local f1 = [[
+!A
+!B
+!C
+ ]]
+ local f2 = [[
+?A
+?Z
+?B
+?C
+?A
+?B
+?C
+?C
+ ]]
+ write_file(fname, f1, false)
+ write_file(fname_2, f2, false)
+ reread()
+ end)
+
+ it('display results', function()
+ screen:expect([[
+ {1: }{10: 1 }{4:^?A }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 2 }{4:?Z }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 3 }{4:?B }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 4 }{4:?C }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 5 }{8:?}{9:A }│{1: }{10: 1 }{8:!}{9:A }|
+ {1: }{10: 6 }{8:?}{9:B }│{1: }{10: 2 }{8:!}{9:B }|
+ {1: }{10: 7 }{8:?}{9:C }│{1: }{10: 3 }{8:!}{9:C }|
+ {1: }{10: 8 }{4:?C }│{1: }{10: }{2:--------------------------------------------}|
+ {1: }{10: 9 } │{1: }{10: 4 } |
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {6:~ }│{6:~ }|
+ {7:Xtest-functional-diff-screen-1.2 }{3:Xtest-functional-diff-screen-1 }|
+ :e |
+ ]])
+ end)
+ end)
describe('setup a diff with 2 files and set linematch:10', function()
before_each(function()
feed(':set diffopt+=linematch:10<cr>')
@@ -992,4 +1178,48 @@ describe('regressions', function()
helpers.curbufmeths.set_lines(0, -1, false, { string.rep('a', 1010)..'world' })
helpers.exec 'windo diffthis'
end)
+
+ it("properly computes filler lines for hunks bigger than linematch limit", function()
+ clear()
+ feed(':set diffopt+=linematch:10<cr>')
+ screen = Screen.new(100, 20)
+ screen:attach()
+ local lines = {}
+ for i = 0, 29 do
+ lines[#lines + 1] = tostring(i)
+ end
+ helpers.curbufmeths.set_lines(0, -1, false, lines)
+ helpers.exec 'vnew'
+ helpers.curbufmeths.set_lines(0, -1, false, { '00', '29' })
+ helpers.exec 'windo diffthis'
+ feed('<C-e>')
+ screen:expect{grid=[[
+ {1: }{2:------------------------------------------------}│{1: }{3:^1 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:2 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:3 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:4 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:5 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:6 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:7 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:8 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:9 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:10 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:11 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:12 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:13 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:14 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:15 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:16 }|
+ {1: }{2:------------------------------------------------}│{1: }{3:17 }|
+ {1: }29 │{1: }{3:18 }|
+ {4:[No Name] [+] }{5:[No Name] [+] }|
+ |
+ ]], attr_ids={
+ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Grey};
+ [2] = {bold = true, background = Screen.colors.LightCyan, foreground = Screen.colors.Blue1};
+ [3] = {background = Screen.colors.LightBlue};
+ [4] = {reverse = true};
+ [5] = {reverse = true, bold = true};
+ }}
+ end)
end)
diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua
index 3052a74f38..1d11a12af4 100644
--- a/test/functional/ui/messages_spec.lua
+++ b/test/functional/ui/messages_spec.lua
@@ -10,15 +10,19 @@ local async_meths = helpers.async_meths
local test_build_dir = helpers.test_build_dir
local nvim_prog = helpers.nvim_prog
local exec = helpers.exec
+local exec_capture = helpers.exec_capture
local exc_exec = helpers.exc_exec
local exec_lua = helpers.exec_lua
local poke_eventloop = helpers.poke_eventloop
local assert_alive = helpers.assert_alive
local is_os = helpers.is_os
local is_ci = helpers.is_ci
+local funcs = helpers.funcs
+local skip = helpers.skip
describe('ui/ext_messages', function()
local screen
+ local fname = 'Xtest_functional_ui_messages_spec'
before_each(function()
clear()
@@ -38,7 +42,7 @@ describe('ui/ext_messages', function()
})
end)
after_each(function()
- os.remove('Xtest')
+ os.remove(fname)
end)
it('msg_clear follows msg_show kind of confirm', function()
@@ -123,7 +127,7 @@ describe('ui/ext_messages', function()
feed('nq')
-- kind=wmsg (editing readonly file)
- command('write Xtest')
+ command('write ' .. fname)
command('set readonly nohls')
feed('G$x')
screen:expect{grid=[[
@@ -329,6 +333,36 @@ describe('ui/ext_messages', function()
]]}
end)
+ it(':echoerr multiline', function()
+ exec_lua([[vim.g.multi = table.concat({ "bork", "fail" }, "\n")]])
+ feed(':echoerr g:multi<cr>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], messages={{
+ content = {{ "bork\nfail", 2 }},
+ kind = "echoerr"
+ }}}
+
+ feed(':messages<cr>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]], messages={{
+ content = {{ "Press ENTER or type command to continue", 4 }},
+ kind = "return_prompt"
+ }}, msg_history={{
+ content = {{ "bork\nfail", 2 }},
+ kind = "echoerr"
+ }}}
+ end)
+
it('shortmess-=S', function()
command('set shortmess-=S')
feed('iline 1\nline 2<esc>')
@@ -474,8 +508,7 @@ describe('ui/ext_messages', function()
]], msg_history={{
content = {{ "stuff" }},
kind = "echomsg",
- }}, showmode={{ "-- INSERT --", 3 }},
- messages={{
+ }}, messages={{
content = {{ "Press ENTER or type command to continue", 4}},
kind = "return_prompt"
}}}
@@ -801,6 +834,19 @@ stack traceback:
end}
end)
+ it('supports multiline messages for :map', function()
+ command('mapclear')
+ command('nmap Y y$')
+ command('nmap Q @@')
+ command('nnoremap j k')
+ feed(':map<cr>')
+
+ screen:expect{messages={{
+ content = {{ "\nn Q @@\nn Y y$\nn j " }, { "*", 5 }, { " k" }},
+ kind = ''
+ }}}
+ end)
+
it('wildmode=list', function()
screen:try_resize(25, 7)
screen:set_option('ext_popupmenu', false)
@@ -910,9 +956,9 @@ stack traceback:
end)
it('does not truncate messages', function()
- command('write Xtest')
+ command('write '.. fname)
screen:expect({messages={
- {content = { { '"Xtest" [New] 0L, 0B written' } }, kind = "" }
+ {content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = "" }
}})
end)
end)
@@ -985,7 +1031,7 @@ describe('ui/builtin messages', function()
-- screen size doesn't affect internal output #10285
eq('ErrorMsg xxx ctermfg=15 ctermbg=1 guifg=White guibg=Red',
- meths.exec("hi ErrorMsg", true))
+ exec_capture("hi ErrorMsg"))
end)
it(':syntax list langGroup output', function()
@@ -1024,10 +1070,57 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
match /\<endif\s\+".*$/ms=s+5,lc=5 contains=@vimCommentGroup,vimCommentString
match /\<else\s\+".*$/ms=s+4,lc=4 contains=@vimCommentGroup,vimCommentString
links to Comment]],
- meths.exec('syntax list vimComment', true))
+ exec_capture('syntax list vimComment'))
-- luacheck: pop
end)
+ it('no empty line after :silent #12099', function()
+ exec([[
+ func T1()
+ silent !echo
+ echo "message T1"
+ endfunc
+ func T2()
+ silent lua print("lua message")
+ echo "message T2"
+ endfunc
+ func T3()
+ silent call nvim_out_write("api message\n")
+ echo "message T3"
+ endfunc
+ ]])
+ feed(':call T1()<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ message T1 |
+ ]]}
+ feed(':call T2()<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ message T2 |
+ ]]}
+ feed(':call T3()<CR>')
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ message T3 |
+ ]]}
+ end)
+
it('supports ruler with laststatus=0', function()
command("set ruler laststatus=0")
screen:expect{grid=[[
@@ -1232,17 +1325,54 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
]])
end)
- it('echo messages are shown correctly when getchar() immediately follows', function()
- feed([[:echo 'foo' | echo 'bar' | call getchar()<CR>]])
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {3: }|
- foo |
- bar^ |
- ]])
+ describe('echo messages are shown when immediately followed by', function()
+ --- @param to_block string command to cause a blocking wait
+ --- @param to_unblock number|string number: timeout for blocking screen
+ --- string: keys to stop the blocking wait
+ local function test_flush_before_block(to_block, to_unblock)
+ local timeout = type(to_unblock) == 'number' and to_unblock or nil
+ exec(([[
+ func PrintAndWait()
+ echon "aaa\nbbb"
+ %s
+ echon "\nccc"
+ endfunc
+ ]]):format(to_block))
+ feed(':call PrintAndWait()<CR>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3: }|
+ aaa |
+ bbb^ |
+ ]], timeout=timeout}
+ if type(to_unblock) == 'string' then
+ feed(to_unblock)
+ end
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {3: }|
+ aaa |
+ bbb |
+ ccc |
+ {4:Press ENTER or type command to continue}^ |
+ ]]}
+ end
+
+ it('getchar()', function()
+ test_flush_before_block([[call getchar()]], 'k')
+ end)
+
+ it('wait()', function()
+ test_flush_before_block([[call wait(300, '0')]], 100)
+ end)
+
+ it('lua vim.wait()', function()
+ test_flush_before_block([[lua vim.wait(300, function() end)]], 100)
+ end)
end)
it('consecutive calls to win_move_statusline() work after multiline message #21014',function()
@@ -1270,7 +1400,20 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim
{1:~ }|
|
]])
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
+ end)
+
+ it('using nvim_echo in VimResized does not cause hit-enter prompt #26139', function()
+ command([[au VimResized * lua vim.api.nvim_echo({ { '123456' } }, true, {})]])
+ screen:try_resize(60, 5)
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ eq({ mode = 'n', blocking = false }, meths.get_mode())
end)
end)
@@ -1322,7 +1465,7 @@ describe('ui/ext_messages', function()
{1:~ }type :q{5:<Enter>} to exit {1: }|
{1:~ }type :help{5:<Enter>} for help {1: }|
{1:~ }|
- {1:~ }type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}|
+ {1:~{MATCH: +}}type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}{1:{MATCH: +}}|
{1:~ }|
{MATCH:.*}|
{MATCH:.*}|
@@ -1363,7 +1506,7 @@ describe('ui/ext_messages', function()
feed(":intro<cr>")
screen:expect{grid=[[
- |
+ ^ |
|
|
|
@@ -1378,7 +1521,7 @@ describe('ui/ext_messages', function()
type :q{5:<Enter>} to exit |
type :help{5:<Enter>} for help |
|
- type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+}|
+ {MATCH: +}type :help news{5:<Enter>} to see changes in v{MATCH:%d+%.%d+ +}|
|
{MATCH:.*}|
{MATCH:.*}|
@@ -1917,6 +2060,7 @@ aliquip ex ea commodo consequat.]])
end)
it('with :!cmd does not crash on resize', function()
+ skip(funcs.executable('sleep') == 0, 'missing "sleep" command')
feed(':!sleep 1<cr>')
screen:expect{grid=[[
|
diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua
index cf4eb034e0..e870d6f25f 100644
--- a/test/functional/ui/mode_spec.lua
+++ b/test/functional/ui/mode_spec.lua
@@ -44,7 +44,10 @@ describe('ui mode_change event', function()
{0:~ }|
|
]], mode="normal"}
+ end)
+ -- oldtest: Test_mouse_shape_after_failed_change()
+ it('is restored to Normal mode after failed "c"', function()
screen:try_resize(50, 4)
command('set nomodifiable')
@@ -65,6 +68,25 @@ describe('ui mode_change event', function()
]], mode="normal"}
end)
+ -- oldtest: Test_mouse_shape_after_cancelling_gr()
+ it('is restored to Normal mode after cancelling "gr"', function()
+ feed('gr')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]], mode="replace"}
+
+ feed('<Esc>')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]], mode="normal"}
+ end)
+
it('works in insert mode', function()
feed('i')
screen:expect{grid=[[
diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua
index f705678bd5..1356ba3db8 100644
--- a/test/functional/ui/mouse_spec.lua
+++ b/test/functional/ui/mouse_spec.lua
@@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen')
local clear, feed, meths = helpers.clear, helpers.feed, helpers.meths
local insert, feed_command = helpers.insert, helpers.feed_command
local eq, funcs = helpers.eq, helpers.funcs
+local poke_eventloop = helpers.poke_eventloop
local command = helpers.command
local exec = helpers.exec
@@ -11,8 +12,8 @@ describe('ui/mouse/input', function()
before_each(function()
clear()
- meths.set_option('mouse', 'a')
- meths.set_option('list', true)
+ meths.set_option_value('mouse', 'a', {})
+ meths.set_option_value('list', true, {})
-- NB: this is weird, but mostly irrelevant to the test
-- So I didn't bother to change it
command('set listchars=eol:$')
@@ -32,6 +33,7 @@ describe('ui/mouse/input', function()
[5] = {bold = true, reverse = true},
[6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
[7] = {bold = true, foreground = Screen.colors.SeaGreen4},
+ [8] = {foreground = Screen.colors.Brown},
})
command("set mousemodel=extend")
feed('itesting<cr>mouse<cr>support and selection<esc>')
@@ -64,7 +66,7 @@ describe('ui/mouse/input', function()
end)
it("in external ui works with unset 'mouse'", function()
- meths.set_option('mouse', '')
+ meths.set_option_value('mouse', '', {})
feed('<LeftMouse><2,1>')
screen:expect{grid=[[
testing |
@@ -379,7 +381,7 @@ describe('ui/mouse/input', function()
end)
it('left click in default tabline (position 24) closes tab', function()
- meths.set_option('hidden', true)
+ meths.set_option_value('hidden', true, {})
feed_command('%delete')
insert('this is foo')
feed_command('silent file foo | tabnew | file bar')
@@ -402,7 +404,7 @@ describe('ui/mouse/input', function()
end)
it('double click in default tabline (position 4) opens new tab', function()
- meths.set_option('hidden', true)
+ meths.set_option_value('hidden', true, {})
feed_command('%delete')
insert('this is foo')
feed_command('silent file foo | tabnew | file bar')
@@ -437,8 +439,8 @@ describe('ui/mouse/input', function()
return call('Test', a:000 + [2])
endfunction
]])
- meths.set_option('tabline', '%@Test@test%X-%5@Test2@test2')
- meths.set_option('showtabline', 2)
+ meths.set_option_value('tabline', '%@Test@test%X-%5@Test2@test2', {})
+ meths.set_option_value('showtabline', 2, {})
screen:expect([[
{fill:test-test2 }|
testing |
@@ -786,11 +788,11 @@ describe('ui/mouse/input', function()
end)
it('ctrl + left click will search for a tag', function()
- meths.set_option('tags', './non-existent-tags-file')
+ meths.set_option_value('tags', './non-existent-tags-file', {})
feed('<C-LeftMouse><0,0>')
screen:expect([[
{6:E433: No tags file} |
- {6:E426: tag not found: test}|
+ {6:E426: Tag not found: test}|
{6:ing} |
{7:Press ENTER or type comma}|
{7:nd to continue}^ |
@@ -798,6 +800,66 @@ describe('ui/mouse/input', function()
feed('<cr>')
end)
+ it('dragging vertical separator', function()
+ screen:try_resize(45, 5)
+ command('setlocal nowrap')
+ local oldwin = meths.get_current_win().id
+ command('rightbelow vnew')
+ screen:expect([[
+ testing │{0:^$} |
+ mouse │{0:~ }|
+ support and selection │{0:~ }|
+ {4:[No Name] [+] }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 0, 22)
+ poke_eventloop()
+ meths.input_mouse('left', 'drag', '', 0, 1, 12)
+ screen:expect([[
+ testing │{0:^$} |
+ mouse │{0:~ }|
+ support and │{0:~ }|
+ {4:< Name] [+] }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'drag', '', 0, 2, 2)
+ screen:expect([[
+ te│{0:^$} |
+ mo│{0:~ }|
+ su│{0:~ }|
+ {4:< }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'release', '', 0, 2, 2)
+ meths.set_option_value('statuscolumn', 'foobar', { win = oldwin })
+ screen:expect([[
+ {8:fo}│{0:^$} |
+ {8:fo}│{0:~ }|
+ {8:fo}│{0:~ }|
+ {4:< }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 0, 2)
+ poke_eventloop()
+ meths.input_mouse('left', 'drag', '', 0, 1, 12)
+ screen:expect([[
+ {8:foobar}testin│{0:^$} |
+ {8:foobar}mouse │{0:~ }|
+ {8:foobar}suppor│{0:~ }|
+ {4:< Name] [+] }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'drag', '', 0, 2, 22)
+ screen:expect([[
+ {8:foobar}testing │{0:^$} |
+ {8:foobar}mouse │{0:~ }|
+ {8:foobar}support and sele│{0:~ }|
+ {4:[No Name] [+] }{5:[No Name] }|
+ |
+ ]])
+ meths.input_mouse('left', 'release', '', 0, 2, 22)
+ end)
+
local function wheel(use_api)
feed('ggdG')
insert([[
@@ -1011,37 +1073,7 @@ describe('ui/mouse/input', function()
]])
end)
- describe('on concealed text', function()
- -- Helpful for reading the test expectations:
- -- :match Error /\^/
-
- before_each(function()
- screen:try_resize(25, 7)
- screen:set_default_attr_ids({
- [0] = {bold=true, foreground=Screen.colors.Blue},
- c = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray },
- sm = {bold = true},
- })
- feed('ggdG')
-
- feed_command('set concealcursor=ni')
- feed_command('set nowrap')
- feed_command('set shiftwidth=2 tabstop=4 list')
- feed_command('setl listchars=tab:>-')
- feed_command('syntax match NonText "\\*" conceal')
- feed_command('syntax match NonText "cats" conceal cchar=X')
- feed_command('syntax match NonText "x" conceal cchar=>')
-
- -- First column is there to retain the tabs.
- insert([[
- |Section *t1*
- | *t2* *t3* *t4*
- |x 私は猫が大好き *cats* ✨🐈✨
- ]])
-
- feed('gg<c-v>Gxgg')
- end)
-
+ local function test_mouse_click_conceal()
it('(level 1) click on non-wrapped lines', function()
feed_command('let &conceallevel=1', 'echo')
@@ -1433,7 +1465,6 @@ describe('ui/mouse/input', function()
]])
end) -- level 2 - wrapped
-
it('(level 3) click on non-wrapped lines', function()
feed_command('let &conceallevel=3', 'echo')
@@ -1471,6 +1502,7 @@ describe('ui/mouse/input', function()
]])
feed('<esc><LeftMouse><20,2>')
+ feed('zH') -- FIXME: unnecessary horizontal scrolling
screen:expect([[
Section{0:>>--->--->---}t1 |
{0:>--->--->---} t2 t3 t4 |
@@ -1574,12 +1606,80 @@ describe('ui/mouse/input', function()
]])
end) -- level 3 - wrapped
+ end
+
+ describe('on concealed text', function()
+ -- Helpful for reading the test expectations:
+ -- :match Error /\^/
+
+ before_each(function()
+ screen:try_resize(25, 7)
+ screen:set_default_attr_ids({
+ [0] = { bold = true, foreground = Screen.colors.Blue },
+ c = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray },
+ sm = { bold = true },
+ })
+ feed('ggdG')
+
+ command([[setlocal concealcursor=ni nowrap shiftwidth=2 tabstop=4 list listchars=tab:>-]])
+ command([[highlight link X0 Normal]])
+ command([[highlight link X1 NonText]])
+ command([[highlight link X2 NonText]])
+ command([[highlight link X3 NonText]])
+
+ -- First column is there to retain the tabs.
+ insert([[
+ |Section *t1*
+ | *t2* *t3* *t4*
+ |x 私は猫が大好き *cats* ✨🐈✨
+ ]])
+
+ feed('gg<c-v>Gxgg')
+ end)
+
+ describe('(syntax)', function()
+ before_each(function()
+ command([[syntax region X0 matchgroup=X1 start=/\*/ end=/\*/ concealends contains=X2]])
+ command([[syntax match X2 /cats/ conceal cchar=X contained]])
+ command([[syntax match X3 /\n\@<=x/ conceal cchar=>]])
+ end)
+ test_mouse_click_conceal()
+ end)
+
+ describe('(matchadd())', function()
+ before_each(function()
+ funcs.matchadd('Conceal', [[\*]])
+ funcs.matchadd('Conceal', [[cats]], 10, -1, { conceal = 'X' })
+ funcs.matchadd('Conceal', [[\n\@<=x]], 10, -1, { conceal = '>' })
+ end)
+ test_mouse_click_conceal()
+ end)
+
+ describe('(extmarks)', function()
+ before_each(function()
+ local ns = meths.create_namespace('conceal')
+ meths.buf_set_extmark(0, ns, 0, 11, { end_col = 12, conceal = '' })
+ meths.buf_set_extmark(0, ns, 0, 14, { end_col = 15, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 5, { end_col = 6, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 8, { end_col = 9, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 10, { end_col = 11, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 13, { end_col = 14, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 15, { end_col = 16, conceal = '' })
+ meths.buf_set_extmark(0, ns, 1, 18, { end_col = 19, conceal = '' })
+ meths.buf_set_extmark(0, ns, 2, 24, { end_col = 25, conceal = '' })
+ meths.buf_set_extmark(0, ns, 2, 29, { end_col = 30, conceal = '' })
+ meths.buf_set_extmark(0, ns, 2, 25, { end_col = 29, conceal = 'X' })
+ meths.buf_set_extmark(0, ns, 2, 0, { end_col = 1, conceal = '>' })
+ end)
+ test_mouse_click_conceal()
+ end)
+
end)
- it('getmousepos works correctly', function()
- local winwidth = meths.get_option('winwidth')
+ it('getmousepos() works correctly', function()
+ local winwidth = meths.get_option_value('winwidth', {})
-- Set winwidth=1 so that window sizes don't change.
- meths.set_option('winwidth', 1)
+ meths.set_option_value('winwidth', 1, {})
command('tabedit')
local tabpage = meths.get_current_tabpage()
insert('hello')
@@ -1597,8 +1697,8 @@ describe('ui/mouse/input', function()
}
local float = meths.open_win(meths.get_current_buf(), false, opts)
command('redraw')
- local lines = meths.get_option('lines')
- local columns = meths.get_option('columns')
+ local lines = meths.get_option_value('lines', {})
+ local columns = meths.get_option_value('columns', {})
-- Test that screenrow and screencol are set properly for all positions.
for row = 0, lines - 1 do
@@ -1617,6 +1717,7 @@ describe('ui/mouse/input', function()
eq(0, mousepos.wincol)
eq(0, mousepos.line)
eq(0, mousepos.column)
+ eq(0, mousepos.coladd)
end
end
end
@@ -1636,15 +1737,18 @@ describe('ui/mouse/input', function()
eq(win_col + 1, mousepos.wincol)
local line = 0
local column = 0
+ local coladd = 0
if win_row > 0 and win_row < opts.height + 1
and win_col > 0 and win_col < opts.width + 1 then
-- Because of border, win_row and win_col don't need to be
-- incremented by 1.
line = math.min(win_row, funcs.line('$'))
column = math.min(win_col, #funcs.getline(line) + 1)
+ coladd = win_col - column
end
eq(line, mousepos.line)
eq(column, mousepos.column)
+ eq(coladd, mousepos.coladd)
end
end
@@ -1664,14 +1768,16 @@ describe('ui/mouse/input', function()
eq(win_col + 1, mousepos.wincol)
local line = math.min(win_row + 1, funcs.line('$'))
local column = math.min(win_col + 1, #funcs.getline(line) + 1)
+ local coladd = win_col + 1 - column
eq(line, mousepos.line)
eq(column, mousepos.column)
+ eq(coladd, mousepos.coladd)
end
end
-- Test that mouse position values are properly set for ordinary windows.
-- Set the float to be unfocusable instead of closing, to additionally test
- -- that getmousepos does not consider unfocusable floats. (see discussion
+ -- that getmousepos() does not consider unfocusable floats. (see discussion
-- in PR #14937 for details).
opts.focusable = false
meths.win_set_config(float, opts)
@@ -1688,15 +1794,17 @@ describe('ui/mouse/input', function()
eq(win_col + 1, mousepos.wincol)
local line = math.min(win_row + 1, funcs.line('$'))
local column = math.min(win_col + 1, #funcs.getline(line) + 1)
+ local coladd = win_col + 1 - column
eq(line, mousepos.line)
eq(column, mousepos.column)
+ eq(coladd, mousepos.coladd)
end
end
end
-- Restore state and release mouse.
command('tabclose!')
- meths.set_option('winwidth', winwidth)
+ meths.set_option_value('winwidth', winwidth, {})
meths.input_mouse('left', 'release', '', 0, 0, 0)
end)
@@ -1840,16 +1948,6 @@ describe('ui/mouse/input', function()
eq({2, 9}, meths.win_get_cursor(0))
eq('', funcs.getreg('"'))
- -- Try clicking on the status line
- funcs.setreg('"', '')
- meths.win_set_cursor(0, {1, 9})
- feed('vee')
- meths.input_mouse('right', 'press', '', 0, 5, 1)
- meths.input_mouse('right', 'release', '', 0, 5, 1)
- feed('<Down><CR>')
- eq({1, 9}, meths.win_get_cursor(0))
- eq('ran away', funcs.getreg('"'))
-
-- Try clicking outside the window
funcs.setreg('"', '')
meths.win_set_cursor(0, {2, 1})
@@ -1859,5 +1957,28 @@ describe('ui/mouse/input', function()
feed('<Down><CR>')
eq(2, funcs.winnr())
eq('', funcs.getreg('"'))
+
+ -- Test for right click in visual mode inside the selection with vertical splits
+ command('wincmd t')
+ command('rightbelow vsplit')
+ funcs.setreg('"', '')
+ meths.win_set_cursor(0, {1, 9})
+ feed('vee')
+ meths.input_mouse('right', 'press', '', 0, 0, 52)
+ meths.input_mouse('right', 'release', '', 0, 0, 52)
+ feed('<Down><CR>')
+ eq({1, 9}, meths.win_get_cursor(0))
+ eq('ran away', funcs.getreg('"'))
+
+ -- Test for right click inside visual selection at bottom of window with winbar
+ command('setlocal winbar=WINBAR')
+ feed('2yyP')
+ funcs.setreg('"', '')
+ feed('G$vbb')
+ meths.input_mouse('right', 'press', '', 0, 4, 61)
+ meths.input_mouse('right', 'release', '', 0, 4, 61)
+ feed('<Down><CR>')
+ eq({4, 20}, meths.win_get_cursor(0))
+ eq('the moon', funcs.getreg('"'))
end)
end)
diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua
index d4e237bcb4..d72bf27d6b 100644
--- a/test/functional/ui/multibyte_spec.lua
+++ b/test/functional/ui/multibyte_spec.lua
@@ -17,10 +17,11 @@ describe("multibyte rendering", function()
screen = Screen.new(60, 6)
screen:attach({rgb=true})
screen:set_default_attr_ids({
- [1] = {bold = true, foreground = Screen.colors.Blue1},
+ [1] = {bold = true, foreground = Screen.colors.Blue},
[2] = {background = Screen.colors.WebGray},
[3] = {background = Screen.colors.LightMagenta},
[4] = {bold = true},
+ [5] = {foreground = Screen.colors.Blue},
})
end)
@@ -119,6 +120,19 @@ describe("multibyte rendering", function()
]])
end)
+ it('0xffff is shown as 4 hex digits', function()
+ command([[call setline(1, "\uFFFF!!!")]])
+ feed('$')
+ screen:expect{grid=[[
+ {5:<ffff>}!!^! |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+ end)
+
it('works with a lot of unicode (zalgo) text', function()
screen:try_resize(65, 10)
meths.buf_set_lines(0,0,-1,true, split(dedent [[
@@ -146,6 +160,103 @@ describe("multibyte rendering", function()
{1:~ }|
|
]]}
+
+ -- nvim will reset the zalgo text^W^W glyph cache if it gets too full.
+ -- this should be exceedingly rare, but fake it to make sure it works
+ meths._invalidate_glyph_cache()
+ screen:expect{grid=[[
+ ^L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ |
+ ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ |
+ U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ |
+ u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ |
+ D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ |
+ ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ |
+ š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ |
+ o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚ |
+ {1:~ }|
+ |
+ ]], reset=true}
+ end)
+
+ it('works with arabic input and arabicshape', function()
+ command('set arabic')
+
+ command('set noarabicshape')
+ feed('isghl!<esc>')
+ screen:expect{grid=[[
+ ^!مالس|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ |
+ ]]}
+
+ command('set arabicshape')
+ screen:expect{grid=[[
+ ^!ﻡﻼﺳ|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ |
+ ]]}
+ end)
+
+ it('works with arabic input and arabicshape and norightleft', function()
+ command('set arabic norightleft')
+
+ command('set noarabicshape')
+ feed('isghl!<esc>')
+ screen:expect{grid=[[
+ سلام^! |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('set arabicshape')
+ screen:expect{grid=[[
+ ﺱﻼﻣ^! |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ end)
+
+ it('works with arabicshape and multiple composing chars', function()
+ -- this tests an important edge case: arabicshape might increase the byte size of the base
+ -- character in a way so that the last composing char no longer fits. use "g8" on the text
+ -- to observe what is happening (the final E1 80 B7 gets deleted with 'arabicshape')
+ -- If we would increase the schar_t size, say from 32 to 64 bytes, we need to extend the
+ -- test text with even more zalgo energy to still touch this edge case.
+
+ meths.buf_set_lines(0,0,-1,true, {"سلام့̀́̂̃̄̅̆̇̈̉̊̋̌"})
+ command('set noarabicshape')
+
+ screen:expect{grid=[[
+ ^سلام့̀́̂̃̄̅̆̇̈̉̊̋̌ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
+
+ command('set arabicshape')
+ screen:expect{grid=[[
+ ^ﺱﻼﻣ̀́̂̃̄̅̆̇̈̉̊̋̌ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]]}
end)
end)
@@ -158,6 +269,7 @@ describe('multibyte rendering: statusline', function()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue1},
[2] = {bold = true, reverse = true},
+ [3] = {background = Screen.colors.Red, foreground = Screen.colors.Gray100};
})
screen:attach()
command('set laststatus=2')
@@ -220,4 +332,27 @@ describe('multibyte rendering: statusline', function()
|
]]}
end)
+
+ it('unprintable chars in filename with default stl', function()
+ command("file 🧑‍💻")
+ -- TODO: this is wrong but avoids a crash
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {2:🧑�💻 }|
+ |
+ ]]}
+ end)
+
+ it('unprintable chars in filename with custom stl', function()
+ command('set statusline=xx%#ErrorMsg#%f%##yy')
+ command("file 🧑‍💻")
+ -- TODO: this is also wrong but also avoids a crash
+ screen:expect{grid=[[
+ ^ |
+ {1:~ }|
+ {2:xx}{3:🧑<200d>💻}{2:yy }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua
index 71adeb42a4..5b982df2b5 100644
--- a/test/functional/ui/multigrid_spec.lua
+++ b/test/functional/ui/multigrid_spec.lua
@@ -37,6 +37,10 @@ describe('ext_multigrid', function()
[18] = {bold = true, foreground = Screen.colors.Magenta},
[19] = {foreground = Screen.colors.Brown},
[20] = {background = Screen.colors.LightGrey},
+ [21] = {background = Screen.colors.LightMagenta},
+ [22] = {background = Screen.colors.LightMagenta, bold = true, foreground = Screen.colors.Blue},
+ [23] = {background = Screen.colors.Grey90},
+ [24] = {background = Screen.colors.Grey},
})
end)
@@ -884,7 +888,6 @@ describe('ext_multigrid', function()
it('gets written till grid width', function()
insert(('a'):rep(60).."\n")
-
screen:expect{grid=[[
## grid 1
[2:-----------------------------------------------------]|
@@ -927,8 +930,95 @@ describe('ext_multigrid', function()
]]}
end)
+ it('"g$" works correctly with double-width characters and no wrapping', function()
+ command('set nowrap')
+ insert(('a'):rep(58) .. ('哦'):rep(3))
+ feed('0')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa哦|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]]}
+ feed('g$')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^哦|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ]]}
+ end)
+
it('wraps with grid width', function()
- insert(('b'):rep(80).."\n")
+ insert(('b'):rep(160).."\n")
screen:expect{grid=[[
## grid 1
[2:-----------------------------------------------------]|
@@ -947,7 +1037,8 @@ describe('ext_multigrid', function()
[3:-----------------------------------------------------]|
## grid 2
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
- bbbbbbbbbbbbbbbbbbbb |
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
^ |
{1:~ }|
{1:~ }|
@@ -965,6 +1056,47 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
{1:~ }|
+ ## grid 3
+ |
+ ]]}
+ feed('2gk')
+ command('setlocal cursorline cursorlineopt=screenline')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb|
+ {23:^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}|
+ bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb |
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
{1:~ }|
## grid 3
|
@@ -1060,6 +1192,255 @@ describe('ext_multigrid', function()
|
]]}
end)
+
+ it('anchored float window "bufpos"', function()
+ insert(('c'):rep(1111))
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccc^c |
+ {1:~ }|
+ ## grid 3
+ |
+ ]]}
+ local float_buf = meths.create_buf(false, false)
+ meths.open_win(float_buf, false, {
+ relative = 'win',
+ win = curwin(),
+ bufpos = {0, 1018},
+ anchor = 'SE',
+ width = 5,
+ height = 5,
+ })
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc|
+ cccccccccccccccccccccccccccccc^c |
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {21: }|
+ {22:~ }|
+ {22:~ }|
+ {22:~ }|
+ {22:~ }|
+ ]], float_pos={
+ [4] = {{id = 1001}, "SE", 2, 16, 58, true, 50};
+ }}
+ end)
+
+ it('completion popup position', function()
+ insert(('\n'):rep(14) .. ('foo bar '):rep(7))
+ feed('A<C-X><C-N>')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ foo bar foo bar foo bar foo bar foo bar foo bar foo bar foo^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- Keyword Local completion (^N^P) }{15:match 1 of 2} |
+ ## grid 4
+ {24: foo}|
+ {21: bar}|
+ ]], float_pos={
+ [4] = {{id = -1}, "NW", 2, 15, 55, false, 100};
+ }}
+ feed('<C-E><Esc>')
+
+ command('setlocal rightleft')
+ feed('o<C-X><C-N>')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ rab oof rab oof rab oof rab oof rab oof rab oof rab oof|
+ ^ oof|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ {7:-- Keyword Local completion (^N^P) }{15:match 1 of 2} |
+ ## grid 4
+ {24: oof}|
+ {21: rab}|
+ ]], float_pos={
+ [4] = {{id = -1}, "NW", 2, 16, 45, false, 100};
+ }}
+ feed('<C-E><Esc>')
+
+ command('set wildoptions+=pum')
+ feed(':sign un<Tab>')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ rab oof rab oof rab oof rab oof rab oof rab oof rab oof|
+ |
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ :sign undefine^ |
+ ## grid 4
+ {24: undefine }|
+ {21: unplace }|
+ ]], float_pos={
+ [4] = {{id = -1}, "SW", 1, 13, 5, false, 250};
+ }}
+ end)
end)
it('multiline messages scroll over windows', function()
@@ -1926,6 +2307,7 @@ describe('ext_multigrid', function()
{1:~ }|
]]}
+ -- XXX: mouse_check_grid() doesn't work properly when clicking on grid 1
meths.input_mouse('left', 'press', '', 1, 6, 20)
-- TODO(bfredl): "batching" input_mouse is formally not supported yet.
-- Normally it should work fine in async context when nvim is not blocked,
@@ -2003,7 +2385,7 @@ describe('ext_multigrid', function()
{1:~ }|
]]}
- meths.input_mouse('left', 'press', '', 1,8, 26)
+ meths.input_mouse('left', 'press', '', 1, 8, 26)
poke_eventloop()
meths.input_mouse('left', 'drag', '', 1, 6, 30)
screen:expect{grid=[[
@@ -2044,6 +2426,625 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]]}
+
+ command('aunmenu PopUp | vmenu PopUp.Copy y')
+
+ funcs.setreg('"', '')
+ meths.input_mouse('left', 'press', '2', 2, 1, 6)
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] }{11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be {20:clicke}^d |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be {20:clicked} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ meths.input_mouse('right', 'press', '', 2, 1, 6)
+ meths.input_mouse('right', 'release', '', 2, 1, 6)
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] }{11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be {20:clicke}^d |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be {20:clicked} |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 6
+ {21: Copy }|
+ ]], float_pos={
+ [6] = {{id = -1}, "NW", 2, 2, 5, false, 250};
+ }}
+ feed('<Down><CR>')
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] }{11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be ^clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmo |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ eq('clicked', funcs.getreg('"'))
+
+ funcs.setreg('"', '')
+ meths.input_mouse('left', 'press', '2', 4, 0, 64)
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do {20:eiusm}^o |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ meths.input_mouse('right', 'press', '', 4, 0, 64)
+ meths.input_mouse('right', 'release', '', 4, 0, 64)
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do {20:eiusm}^o |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 6
+ {21: Copy }|
+ ]], float_pos={
+ [6] = {{id = -1}, "NW", 4, 1, 63, false, 250};
+ }}
+ feed('<Down><CR>')
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do ^eiusmo |
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ eq('eiusmo', funcs.getreg('"'))
+
+ command('wincmd J')
+ screen:try_resize_grid(4, 7, 10)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ ^eiusmo|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+
+ funcs.setreg('"', '')
+ meths.input_mouse('left', 'press', '2', 4, 9, 1)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ {20:eiusm}^o|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ meths.input_mouse('right', 'press', '', 4, 9, 1)
+ meths.input_mouse('right', 'release', '', 4, 9, 1)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ {20:eiusm}^o|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 6
+ {21: Copy }|
+ ]], float_pos={
+ [6] = {{id = -1}, "SW", 4, 9, 0, false, 250};
+ }}
+ feed('<Down><CR>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ ^eiusmo|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ eq('eiusmo', funcs.getreg('"'))
+
+ screen:try_resize_grid(4, 7, 11)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ ^eiusmo|
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+
+ funcs.setreg('"', '')
+ meths.input_mouse('left', 'press', '2', 4, 9, 1)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ {20:eiusm}^o|
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ meths.input_mouse('right', 'press', '', 4, 9, 1)
+ meths.input_mouse('right', 'release', '', 4, 9, 1)
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {7:-- VISUAL --} |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ {20:eiusm}^o|
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 6
+ {21: Copy }|
+ ]], float_pos={
+ [6] = {{id = -1}, "NW", 4, 10, 0, false, 250};
+ }}
+ feed('<Down><CR>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ [5:------------------------------]│[2:----------------------]|
+ {12:[No Name] [+] [No Name] [+] }|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ [4:-----------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ Lorem i|
+ psum do|
+ lor sit|
+ amet, |
+ consect|
+ etur ad|
+ ipiscin|
+ g elit,|
+ sed do|
+ ^eiusmo|
+ {1:~ }|
+ ## grid 5
+ some text |
+ to be clicked |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ eq('eiusmo', funcs.getreg('"'))
end)
it('supports mouse drag with mouse=a', function()
@@ -2127,7 +3128,7 @@ describe('ext_multigrid', function()
## grid 3
|
]], win_viewport={
- [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}
+ [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}
}}
insert([[
Lorem ipsum dolor sit amet, consectetur
@@ -2162,7 +3163,7 @@ describe('ext_multigrid', function()
## grid 3
|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7, linecount = 11, sum_scroll_delta = 5},
}}
@@ -2187,7 +3188,7 @@ describe('ext_multigrid', function()
## grid 3
|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 2, botline = 9, curline = 7, curcol = 0, linecount = 11, sum_scroll_delta = 2},
}}
command("split")
@@ -2211,8 +3212,8 @@ describe('ext_multigrid', function()
reprehenderit in voluptate velit esse cillum |
^dolore eu fugiat nulla pariatur. Excepteur sint |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11},
- [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11, sum_scroll_delta = 6},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 7, curcol = 0, linecount = 11, sum_scroll_delta = 5},
}}
feed("b")
@@ -2236,8 +3237,8 @@ describe('ext_multigrid', function()
reprehenderit in voluptate velit esse ^cillum |
dolore eu fugiat nulla pariatur. Excepteur sint |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11},
- [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11, sum_scroll_delta = 6},
+ [4] = {win = {id = 1001}, topline = 5, botline = 9, curline = 6, curcol = 38, linecount = 11, sum_scroll_delta = 5},
}}
feed("2k")
@@ -2261,8 +3262,8 @@ describe('ext_multigrid', function()
ea commodo consequat. Duis aute irure dolor in |
reprehenderit in voluptate velit esse cillum |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11},
- [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 6, botline = 9, curline = 7, curcol = 0, linecount = 11, sum_scroll_delta = 6},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38, linecount = 11, sum_scroll_delta = 4},
}}
-- handles non-current window
@@ -2287,8 +3288,561 @@ describe('ext_multigrid', function()
ea commodo consequat. Duis aute irure dolor in |
reprehenderit in voluptate velit esse cillum |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10, linecount = 11},
- [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0},
+ [4] = {win = {id = 1001}, topline = 4, botline = 8, curline = 4, curcol = 38, linecount = 11, sum_scroll_delta = 4},
+ }}
+
+ -- sum_scroll_delta works with folds
+ feed('zfj')
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ ## grid 3
+ |
+ ## grid 4
+ {13:^+-- 2 lines: exercitation ullamco laboris nisi }|
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0},
+ [4] = {win = {id = 1001}, topline = 4, botline = 9, curline = 4, curcol = 38, linecount = 11, sum_scroll_delta = 4},
+ }}
+
+ feed('<c-e>')
+ screen:expect{grid=[[
+ ## grid 1
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ [4:------------------------------------------------]|
+ {11:[No Name] [+] }|
+ [2:------------------------------------------------]|
+ [2:------------------------------------------------]|
+ {12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ ## grid 3
+ |
+ ## grid 4
+ ^reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0},
+ [4] = {win = {id = 1001}, topline = 6, botline = 10, curline = 6, curcol = 0, linecount = 11, sum_scroll_delta = 5},
+ }}
+
+ command('close | 21vsplit | setlocal number smoothscroll')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {19: 1 }Lorem ipsu^m dolor|
+ {19: } sit amet, consec|
+ {19: }tetur |
+ {19: 2 }adipisicing elit,|
+ {19: } sed do eiusmod t|
+ {19: }empor |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ }}
+
+ feed('5<C-E>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {1:<<<}{19: }empo^r |
+ {19: 3 }incididunt ut lab|
+ {19: }ore et dolore mag|
+ {19: }na aliqua. |
+ {19: 4 }Ut enim ad minim |
+ {19: }veniam, quis n{1:@@@}|
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 1, botline = 4, curline = 1, curcol = 38, linecount = 11, sum_scroll_delta = 5};
+ }}
+
+ feed('<C-Y>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {1:<<<}{19: } sed do eiusmod t|
+ {19: }empo^r |
+ {19: 3 }incididunt ut lab|
+ {19: }ore et dolore mag|
+ {19: }na aliqua. |
+ {19: 4 }Ut enim ad min{1:@@@}|
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 1, botline = 4, curline = 1, curcol = 38, linecount = 11, sum_scroll_delta = 4};
+ }}
+
+ command('set cpoptions+=n')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {1:<<<}d do eiusmod tempo|
+ ^r |
+ {19: 3 }incididunt ut lab|
+ ore et dolore magna a|
+ liqua. |
+ {19: 4 }Ut enim ad min{1:@@@}|
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 1, botline = 4, curline = 1, curcol = 38, linecount = 11, sum_scroll_delta = 4};
+ }}
+
+ feed('4<C-E>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {1:<<<}ua^. |
+ {19: 4 }Ut enim ad minim |
+ veniam, quis nostrud |
+ {19: 5 }exercitation ulla|
+ mco laboris nisi ut a|
+ liquip ex |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 2, botline = 6, curline = 2, curcol = 43, linecount = 11, sum_scroll_delta = 8};
+ }}
+
+ feed('2<C-Y>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {19: 3 }incididunt ut lab|
+ ore et dolore magna a|
+ liqua^. |
+ {19: 4 }Ut enim ad minim |
+ veniam, quis nostrud |
+ {19: 5 }exercitation u{1:@@@}|
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 2, botline = 5, curline = 2, curcol = 43, linecount = 11, sum_scroll_delta = 6};
+ }}
+
+ command('setlocal numberwidth=12')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {19: 3 }incididun|
+ t ut labore et dolore|
+ magna aliqua^. |
+ {19: 4 }Ut enim a|
+ d minim veniam, quis |
+ nostrud |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 2, botline = 5, curline = 2, curcol = 43, linecount = 11, sum_scroll_delta = 6};
+ }}
+
+ feed('2<C-E>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {1:<<<}gna aliqua^. |
+ {19: 4 }Ut enim a|
+ d minim veniam, quis |
+ nostrud |
+ {19: 5 }exercitat|
+ ion ullamco labori{1:@@@}|
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 2, botline = 5, curline = 2, curcol = 43, linecount = 11, sum_scroll_delta = 8};
+ }}
+
+ feed('<C-E>')
+ screen:expect{grid=[[
+ ## grid 1
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ [5:---------------------]│[2:--------------------------]|
+ {11:[No Name] [+] }{12:[No Name] [+] }|
+ [3:------------------------------------------------]|
+ ## grid 2
+ Lorem ipsum dolor sit amet|
+ , consectetur |
+ adipisicing elit, sed do e|
+ iusmod tempor |
+ incididunt ut labore et do|
+ lore magna aliqua. |
+ ## grid 3
+ |
+ ## grid 5
+ {19: 4 }Ut enim a|
+ d minim veniam, quis |
+ nostru^d |
+ {19: 5 }exercitat|
+ ion ullamco laboris n|
+ isi ut aliquip ex |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 4, curline = 0, curcol = 10, linecount = 11, sum_scroll_delta = 0};
+ [5] = {win = {id = 1002}, topline = 3, botline = 6, curline = 3, curcol = 36, linecount = 11, sum_scroll_delta = 9};
+ }}
+ end)
+
+ it('scroll_delta is approximated reasonably when scrolling many lines #24234', function()
+ command('setlocal number nowrap')
+ command('edit test/functional/fixtures/bigfile.txt')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19: 1 }^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
+ {19: 2 }0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;|
+ {19: 3 }0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
+ {19: 4 }0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ {19: 5 }0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSIO|
+ {19: 6 }0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
+ {19: 7 }0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
+ {19: 8 }0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
+ {19: 9 }0008;<control>;Cc;0;BN;;;;;N;BACKSPACE;;;; |
+ {19: 10 }0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATIO|
+ {19: 11 }000A;<control>;Cc;0;B;;;;;N;LINE FEED (LF);;;; |
+ {19: 12 }000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;;|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 13, curline = 0, curcol = 0, linecount = 30592, sum_scroll_delta = 0};
+ }}
+ feed('G')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19:30581 }E01E8;VARIATION SELECTOR-249;Mn;0;NSM;;;;;N;;;;|
+ {19:30582 }E01E9;VARIATION SELECTOR-250;Mn;0;NSM;;;;;N;;;;|
+ {19:30583 }E01EA;VARIATION SELECTOR-251;Mn;0;NSM;;;;;N;;;;|
+ {19:30584 }E01EB;VARIATION SELECTOR-252;Mn;0;NSM;;;;;N;;;;|
+ {19:30585 }E01EC;VARIATION SELECTOR-253;Mn;0;NSM;;;;;N;;;;|
+ {19:30586 }E01ED;VARIATION SELECTOR-254;Mn;0;NSM;;;;;N;;;;|
+ {19:30587 }E01EE;VARIATION SELECTOR-255;Mn;0;NSM;;;;;N;;;;|
+ {19:30588 }E01EF;VARIATION SELECTOR-256;Mn;0;NSM;;;;;N;;;;|
+ {19:30589 }F0000;<Plane 15 Private Use, First>;Co;0;L;;;;;|
+ {19:30590 }FFFFD;<Plane 15 Private Use, Last>;Co;0;L;;;;;N|
+ {19:30591 }100000;<Plane 16 Private Use, First>;Co;0;L;;;;|
+ {19:30592 }^10FFFD;<Plane 16 Private Use, Last>;Co;0;L;;;;;|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 30580, botline = 30592, curline = 30591, curcol = 0, linecount = 30592, sum_scroll_delta = 30580};
+ }}
+ feed('gg')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19: 1 }^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
+ {19: 2 }0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;|
+ {19: 3 }0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
+ {19: 4 }0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ {19: 5 }0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSIO|
+ {19: 6 }0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
+ {19: 7 }0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
+ {19: 8 }0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
+ {19: 9 }0008;<control>;Cc;0;BN;;;;;N;BACKSPACE;;;; |
+ {19: 10 }0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULATIO|
+ {19: 11 }000A;<control>;Cc;0;B;;;;;N;LINE FEED (LF);;;; |
+ {19: 12 }000B;<control>;Cc;0;S;;;;;N;LINE TABULATION;;;;|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 13, curline = 0, curcol = 0, linecount = 30592, sum_scroll_delta = 0};
+ }}
+ command('setlocal wrap')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19: 1 }^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
+ {19: 2 }0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;|
+ {19: };; |
+ {19: 3 }0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
+ {19: 4 }0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ {19: 5 }0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSIO|
+ {19: }N;;;; |
+ {19: 6 }0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
+ {19: 7 }0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
+ {19: 8 }0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
+ {19: 9 }0008;<control>;Cc;0;BN;;;;;N;BACKSPACE;;;; |
+ {19: 10 }0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULA{1:@@@}|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 10, curline = 0, curcol = 0, linecount = 30592, sum_scroll_delta = 0};
+ }}
+ feed('G')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19:30587 }E01EE;VARIATION SELECTOR-255;Mn;0;NSM;;;;;N;;;;|
+ {19: }; |
+ {19:30588 }E01EF;VARIATION SELECTOR-256;Mn;0;NSM;;;;;N;;;;|
+ {19: }; |
+ {19:30589 }F0000;<Plane 15 Private Use, First>;Co;0;L;;;;;|
+ {19: }N;;;;; |
+ {19:30590 }FFFFD;<Plane 15 Private Use, Last>;Co;0;L;;;;;N|
+ {19: };;;;; |
+ {19:30591 }100000;<Plane 16 Private Use, First>;Co;0;L;;;;|
+ {19: };N;;;;; |
+ {19:30592 }^10FFFD;<Plane 16 Private Use, Last>;Co;0;L;;;;;|
+ {19: }N;;;;; |
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 30586, botline = 30592, curline = 30591, curcol = 0, linecount = 30592, sum_scroll_delta = 30588};
+ }}
+ feed('gg')
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ [2:-----------------------------------------------------]|
+ {11:test/functional/fixtures/bigfile.txt }|
+ [3:-----------------------------------------------------]|
+ ## grid 2
+ {19: 1 }^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; |
+ {19: 2 }0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;|
+ {19: };; |
+ {19: 3 }0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; |
+ {19: 4 }0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; |
+ {19: 5 }0004;<control>;Cc;0;BN;;;;;N;END OF TRANSMISSIO|
+ {19: }N;;;; |
+ {19: 6 }0005;<control>;Cc;0;BN;;;;;N;ENQUIRY;;;; |
+ {19: 7 }0006;<control>;Cc;0;BN;;;;;N;ACKNOWLEDGE;;;; |
+ {19: 8 }0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
+ {19: 9 }0008;<control>;Cc;0;BN;;;;;N;BACKSPACE;;;; |
+ {19: 10 }0009;<control>;Cc;0;S;;;;;N;CHARACTER TABULA{1:@@@}|
+ ## grid 3
+ |
+ ]], win_viewport={
+ [2] = {win = {id = 1000}, topline = 0, botline = 10, curline = 0, curcol = 0, linecount = 30592, sum_scroll_delta = 0};
}}
end)
@@ -2314,7 +3868,7 @@ describe('ext_multigrid', function()
## grid 3
|
]], win_viewport={
- [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}
+ [2] = {win = { id = 1000 }, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}
}}
insert([[
Lorem ipsum dolor sit amet, consectetur
@@ -2349,7 +3903,7 @@ describe('ext_multigrid', function()
## grid 3
|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 5, botline = 11, curline = 10, curcol = 7, linecount = 11, sum_scroll_delta = 5},
}}
meths.input_mouse('left', 'press', '', 1,5, 1)
@@ -2376,7 +3930,7 @@ describe('ext_multigrid', function()
## grid 3
{7:-- VISUAL --} |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 6, botline = 12, curline = 10, curcol = 1, linecount = 11},
+ [2] = {win = {id = 1000}, topline = 6, botline = 12, curline = 10, curcol = 1, linecount = 11, sum_scroll_delta = 6},
}}
end)
@@ -2414,8 +3968,8 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
-- XXX: hack to get notifications. Could use next_msg() also.
@@ -2459,8 +4013,8 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
eq({}, win_pos)
@@ -2497,14 +4051,14 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
- [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1};
+ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
+ [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0};
}}
eq({}, win_pos)
end)
it('with winbar dragging statusline with mouse works correctly', function()
- meths.set_option('winbar', 'Set Up The Bars')
+ meths.set_option_value('winbar', 'Set Up The Bars', {})
command('split')
screen:expect([[
## grid 1
@@ -2644,7 +4198,7 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]])
- eq(3, meths.get_option('cmdheight'))
+ eq(3, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 1, 12, 10)
screen:expect([[
@@ -2679,6 +4233,6 @@ describe('ext_multigrid', function()
{1:~ }|
{1:~ }|
]])
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
end)
end)
diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua
index 9d20229ce1..2c649709c6 100644
--- a/test/functional/ui/options_spec.lua
+++ b/test/functional/ui/options_spec.lua
@@ -19,9 +19,11 @@ describe('UI receives option updates', function()
linespace=0,
pumblend=0,
mousefocus=false,
+ mousehide=true,
mousemoveevent=false,
showtabline=1,
termguicolors=false,
+ termsync=true,
ttimeout=true,
ttimeoutlen=50,
verbose=0,
@@ -68,15 +70,18 @@ describe('UI receives option updates', function()
eq({'mouse_on'}, evs)
end)
command("set mouse=")
+ screen:expect(function()
+ eq({'mouse_on', 'mouse_off'}, evs)
+ end)
command("set mouse&")
screen:expect(function()
- eq({'mouse_on','mouse_off', 'mouse_on'}, evs)
+ eq({'mouse_on', 'mouse_off', 'mouse_on'}, evs)
end)
screen:detach()
- eq({'mouse_on','mouse_off', 'mouse_on'}, evs)
+ eq({'mouse_on', 'mouse_off', 'mouse_on'}, evs)
screen:attach()
screen:expect(function()
- eq({'mouse_on','mouse_off','mouse_on', 'mouse_on'}, evs)
+ eq({'mouse_on', 'mouse_off', 'mouse_on', 'mouse_on'}, evs)
end)
end)
@@ -133,6 +138,12 @@ describe('UI receives option updates', function()
eq(expected, screen.options)
end)
+ command("set nomousehide")
+ expected.mousehide = false
+ screen:expect(function()
+ eq(expected, screen.options)
+ end)
+
command("set mousemoveevent")
expected.mousemoveevent = true
screen:expect(function()
@@ -200,22 +211,6 @@ describe('UI can set terminal option', function()
screen = Screen.new(20,5)
end)
- it('term_background', function()
- eq('dark', eval '&background')
-
- screen:attach {term_background='light'}
- eq('light', eval '&background')
- end)
-
- it("term_background but not if 'background' already set by user", function()
- eq('dark', eval '&background')
- command 'set background=dark'
-
- screen:attach {term_background='light'}
-
- eq('dark', eval '&background')
- end)
-
it('term_name', function()
eq('nvim', eval '&term')
diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua
index 223844405e..7b93b74eac 100644
--- a/test/functional/ui/output_spec.lua
+++ b/test/functional/ui/output_spec.lua
@@ -15,6 +15,8 @@ local set_shell_powershell = helpers.set_shell_powershell
local skip = helpers.skip
local is_os = helpers.is_os
+clear() -- for has_powershell()
+
describe("shell command :!", function()
local screen
before_each(function()
@@ -54,6 +56,7 @@ describe("shell command :!", function()
it("throttles shell-command output greater than ~10KB", function()
skip(is_os('openbsd'), 'FIXME #10804')
+ skip(is_os('win'))
child_session.feed_data((":!%s REP 30001 foo\n"):format(testprg('shell-test')))
-- If we observe any line starting with a dot, then throttling occurred.
@@ -222,8 +225,8 @@ describe("shell command :!", function()
å |
ref: å̲ |
1: å̲ |
- 2: å ̲ |
- 3: å ̲ |
+ 2: å ̲ |
+ 3: å ̲ |
|
{3:Press ENTER or type command to continue}^ |
]])
diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua
index c681453294..a6cd216d84 100644
--- a/test/functional/ui/popupmenu_spec.lua
+++ b/test/functional/ui/popupmenu_spec.lua
@@ -5,6 +5,7 @@ local clear, feed = helpers.clear, helpers.feed
local source = helpers.source
local insert = helpers.insert
local meths = helpers.meths
+local async_meths = helpers.async_meths
local command = helpers.command
local funcs = helpers.funcs
local eq = helpers.eq
@@ -1023,1870 +1024,11 @@ describe('ui/ext_popupmenu', function()
end)
end)
+describe("builtin popupmenu 'pumblend'", function()
+ before_each(clear)
-describe('builtin popupmenu', function()
- local screen
- before_each(function()
- clear()
- screen = Screen.new(32, 20)
- screen:attach()
- screen:set_default_attr_ids({
- -- popup selected item / scrollbar track
- ['s'] = {background = Screen.colors.WebGray},
- -- popup non-selected item
- ['n'] = {background = Screen.colors.LightMagenta},
- -- popup scrollbar knob
- ['c'] = {background = Screen.colors.Grey0},
- [1] = {bold = true, foreground = Screen.colors.Blue},
- [2] = {bold = true},
- [3] = {reverse = true},
- [4] = {bold = true, reverse = true},
- [5] = {bold = true, foreground = Screen.colors.SeaGreen},
- [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
- [7] = {background = Screen.colors.Yellow}, -- Search
- [8] = {foreground = Screen.colors.Red},
- })
- end)
-
- it('with preview-window above', function()
- feed(':ped<CR><c-w>4+')
- feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
- feed('<c-x><c-n>')
- screen:expect([[
- aa bb cc dd ee ff gg hh ii jj |
- aa |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {3:[No Name] [Preview][+] }|
- aa bb cc dd ee ff gg hh ii jj |
- aa^ |
- {s:aa }{c: }{1: }|
- {n:bb }{c: }{1: }|
- {n:cc }{c: }{1: }|
- {n:dd }{c: }{1: }|
- {n:ee }{c: }{1: }|
- {n:ff }{c: }{1: }|
- {n:gg }{s: }{1: }|
- {n:hh }{s: }{4: }|
- {2:-- }{5:match 1 of 10} |
- ]])
- end)
-
- it('with preview-window below', function()
- feed(':ped<CR><c-w>4+<c-w>r')
- feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
- feed('<c-x><c-n>')
- screen:expect([[
- aa bb cc dd ee ff gg hh ii jj |
- aa^ |
- {s:aa }{c: }{1: }|
- {n:bb }{c: }{1: }|
- {n:cc }{c: }{1: }|
- {n:dd }{c: }{1: }|
- {n:ee }{c: }{1: }|
- {n:ff }{c: }{1: }|
- {n:gg }{s: }{1: }|
- {n:hh }{s: }{4: }|
- aa bb cc dd ee ff gg hh ii jj |
- aa |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {3:[No Name] [Preview][+] }|
- {2:-- }{5:match 1 of 10} |
- ]])
- end)
-
- it('with preview-window above and tall and inverted', function()
- feed(':ped<CR><c-w>8+')
- feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
- feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
- feed('kk<cr>ll<cr>mm<cr>nn<cr>oo<cr>')
- feed('<c-x><c-n>')
- screen:expect([[
- aa |
- bb |
- cc |
- dd |
- {s:aa }{c: }{3:ew][+] }|
- {n:bb }{c: } |
- {n:cc }{c: } |
- {n:dd }{c: } |
- {n:ee }{c: } |
- {n:ff }{c: } |
- {n:gg }{c: } |
- {n:hh }{c: } |
- {n:ii }{c: } |
- {n:jj }{c: } |
- {n:kk }{c: } |
- {n:ll }{s: } |
- {n:mm }{s: } |
- aa^ |
- {4:[No Name] [+] }|
- {2:-- }{5:match 1 of 15} |
- ]])
- end)
-
- it('with preview-window above and short and inverted', function()
- feed(':ped<CR><c-w>4+')
- feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
- feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
- feed('<c-x><c-n>')
- screen:expect([[
- aa |
- bb |
- cc |
- dd |
- ee |
- ff |
- gg |
- hh |
- {s:aa }{c: }{3:ew][+] }|
- {n:bb }{c: } |
- {n:cc }{c: } |
- {n:dd }{c: } |
- {n:ee }{c: } |
- {n:ff }{c: } |
- {n:gg }{c: } |
- {n:hh }{c: } |
- {n:ii }{s: } |
- aa^ |
- {4:[No Name] [+] }|
- {2:-- }{5:match 1 of 10} |
- ]])
- end)
-
- it('with preview-window below and inverted', function()
- feed(':ped<CR><c-w>4+<c-w>r')
- feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
- feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
- feed('<c-x><c-n>')
- screen:expect([[
- {s:aa }{c: } |
- {n:bb }{c: } |
- {n:cc }{c: } |
- {n:dd }{c: } |
- {n:ee }{c: } |
- {n:ff }{c: } |
- {n:gg }{s: } |
- {n:hh }{s: } |
- aa^ |
- {4:[No Name] [+] }|
- aa |
- bb |
- cc |
- dd |
- ee |
- ff |
- gg |
- hh |
- {3:[No Name] [Preview][+] }|
- {2:-- }{5:match 1 of 10} |
- ]])
- end)
-
- -- oldtest: Test_pum_with_preview_win()
- it('preview window opened during completion', function()
- exec([[
- funct Omni_test(findstart, base)
- if a:findstart
- return col(".") - 1
- endif
- return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
- endfunc
- set omnifunc=Omni_test
- set completeopt+=longest
- ]])
- feed('Gi<C-X><C-O>')
- screen:expect([[
- ^ |
- {n:one }{1: }|
- {n:two }{1: }|
- {n:three }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- }{8:Back at original} |
- ]])
- feed('<C-N>')
- screen:expect([[
- 1info |
- |
- {1:~ }|
- {3:[Scratch] [Preview] }|
- one^ |
- {s:one }{1: }|
- {n:two }{1: }|
- {n:three }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4:[No Name] [+] }|
- {2:-- }{5:match 1 of 3} |
- ]])
- end)
-
- it('with vsplits', function()
- insert('aaa aab aac\n')
- feed(':vsplit<cr>')
- screen:expect([[
- aaa aab aac │aaa aab aac|
- ^ │ |
- {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:~ }│{1:~ }|
- {1:~ }│{1:~ }|
- {1:~ }│{1:~ }|
- {1:~ }│{1:~ }|
- {4:[No Name] [+] }{3:<Name] [+] }|
- :vsplit |
- ]])
-
- feed('ibbb a<c-x><c-n>')
- screen:expect([[
- aaa aab aac │aaa aab aac|
- bbb aaa^ │bbb aaa |
- {1:~ }{s: aaa }{1: }│{1:~ }|
- {1:~ }{n: aab }{1: }│{1:~ }|
- {1:~ }{n: aac }{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:~ }│{1:~ }|
- {1:~ }│{1:~ }|
- {4:[No Name] [+] }{3:<Name] [+] }|
- {2:-- }{5:match 1 of 3} |
- ]])
-
- feed('<esc><c-w><c-w>oc a<c-x><c-n>')
- screen:expect([[
- aaa aab aac│aaa aab aac |
- bbb aaa │bbb aaa |
- c aaa │c aaa^ |
- {1:~ }│{1:~}{s: aaa }{1: }|
- {1:~ }│{1:~}{n: aab }{1: }|
- {1:~ }│{1:~}{n: aac }{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:~ }|
- {3:<Name] [+] }{4:[No Name] [+] }|
- {2:-- }{5:match 1 of 3} |
- ]])
- end)
-
- it('with split and scroll', function()
- screen:try_resize(60,14)
- command("split")
- command("set completeopt+=noinsert")
- command("set mouse=a")
- insert([[
- Lorem ipsum dolor sit amet, consectetur
- adipisicing elit, sed do eiusmod tempor
- incididunt ut labore et dolore magna aliqua.
- Ut enim ad minim veniam, quis nostrud
- exercitation ullamco laboris nisi ut aliquip ex
- ea commodo consequat. Duis aute irure dolor in
- reprehenderit in voluptate velit esse cillum
- dolore eu fugiat nulla pariatur. Excepteur sint
- occaecat cupidatat non proident, sunt in culpa
- qui officia deserunt mollit anim id est
- laborum.
- ]])
-
- screen:expect([[
- reprehenderit in voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- occaecat cupidatat non proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- ^ |
- {4:[No Name] [+] }|
- Lorem ipsum dolor sit amet, consectetur |
- adipisicing elit, sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {3:[No Name] [+] }|
- |
- ]])
-
- feed('ggOEst <c-x><c-p>')
- screen:expect([[
- Est ^ |
- L{n: sunt }{s: }sit amet, consectetur |
- a{n: in }{s: }sed do eiusmod tempor |
- i{n: culpa }{s: }re et dolore magna aliqua. |
- U{n: qui }{s: }eniam, quis nostrud |
- e{n: officia }{s: }co laboris nisi ut aliquip ex |
- {4:[No}{n: deserunt }{s: }{4: }|
- L{n: mollit }{s: }sit amet, consectetur |
- a{n: anim }{s: }sed do eiusmod tempor |
- i{n: id }{s: }re et dolore magna aliqua. |
- U{n: est }{s: }eniam, quis nostrud |
- e{n: laborum }{c: }co laboris nisi ut aliquip ex |
- {3:[No}{s: Est }{c: }{3: }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- meths.input_mouse('wheel', 'down', '', 0, 9, 40)
- screen:expect([[
- Est ^ |
- L{n: sunt }{s: }sit amet, consectetur |
- a{n: in }{s: }sed do eiusmod tempor |
- i{n: culpa }{s: }re et dolore magna aliqua. |
- U{n: qui }{s: }eniam, quis nostrud |
- e{n: officia }{s: }co laboris nisi ut aliquip ex |
- {4:[No}{n: deserunt }{s: }{4: }|
- U{n: mollit }{s: }eniam, quis nostrud |
- e{n: anim }{s: }co laboris nisi ut aliquip ex |
- e{n: id }{s: }at. Duis aute irure dolor in |
- r{n: est }{s: }oluptate velit esse cillum |
- d{n: laborum }{c: }ulla pariatur. Excepteur sint |
- {3:[No}{s: Est }{c: }{3: }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- feed('e')
- screen:expect([[
- Est e^ |
- L{n: elit } sit amet, consectetur |
- a{n: eiusmod } sed do eiusmod tempor |
- i{n: et }ore et dolore magna aliqua. |
- U{n: enim }veniam, quis nostrud |
- e{n: exercitation }mco laboris nisi ut aliquip ex |
- {4:[No}{n: ex }{4: }|
- U{n: ea }veniam, quis nostrud |
- e{n: esse }mco laboris nisi ut aliquip ex |
- e{n: eu }uat. Duis aute irure dolor in |
- r{s: est }voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- meths.input_mouse('wheel', 'up', '', 0, 9, 40)
- screen:expect([[
- Est e^ |
- L{n: elit } sit amet, consectetur |
- a{n: eiusmod } sed do eiusmod tempor |
- i{n: et }ore et dolore magna aliqua. |
- U{n: enim }veniam, quis nostrud |
- e{n: exercitation }mco laboris nisi ut aliquip ex |
- {4:[No}{n: ex }{4: }|
- L{n: ea } sit amet, consectetur |
- a{n: esse } sed do eiusmod tempor |
- i{n: eu }ore et dolore magna aliqua. |
- U{s: est }veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- feed('s')
- screen:expect([[
- Est es^ |
- L{n: esse } sit amet, consectetur |
- a{s: est } sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {4:[No Name] [+] }|
- Lorem ipsum dolor sit amet, consectetur |
- adipisicing elit, sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- meths.input_mouse('wheel', 'down', '', 0, 9, 40)
- screen:expect([[
- Est es^ |
- L{n: esse } sit amet, consectetur |
- a{s: est } sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {4:[No Name] [+] }|
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- ea commodo consequat. Duis aute irure dolor in |
- reprehenderit in voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- feed('<bs>')
- screen:expect([[
- Est e^ |
- L{n: elit } sit amet, consectetur |
- a{n: eiusmod } sed do eiusmod tempor |
- i{n: et }ore et dolore magna aliqua. |
- U{n: enim }veniam, quis nostrud |
- e{n: exercitation }mco laboris nisi ut aliquip ex |
- {4:[No}{n: ex }{4: }|
- U{n: ea }veniam, quis nostrud |
- e{n: esse }mco laboris nisi ut aliquip ex |
- e{n: eu }uat. Duis aute irure dolor in |
- r{s: est }voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
- ]])
-
- feed('<c-p>')
- screen:expect([[
- Est eu^ |
- L{n: elit } sit amet, consectetur |
- a{n: eiusmod } sed do eiusmod tempor |
- i{n: et }ore et dolore magna aliqua. |
- U{n: enim }veniam, quis nostrud |
- e{n: exercitation }mco laboris nisi ut aliquip ex |
- {4:[No}{n: ex }{4: }|
- U{n: ea }veniam, quis nostrud |
- e{n: esse }mco laboris nisi ut aliquip ex |
- e{s: eu }uat. Duis aute irure dolor in |
- r{n: est }voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 22 of 65} |
- ]])
-
- meths.input_mouse('wheel', 'down', '', 0, 9, 40)
- screen:expect([[
- Est eu^ |
- L{n: elit } sit amet, consectetur |
- a{n: eiusmod } sed do eiusmod tempor |
- i{n: et }ore et dolore magna aliqua. |
- U{n: enim }veniam, quis nostrud |
- e{n: exercitation }mco laboris nisi ut aliquip ex |
- {4:[No}{n: ex }{4: }|
- r{n: ea }voluptate velit esse cillum |
- d{n: esse }nulla pariatur. Excepteur sint |
- o{s: eu }t non proident, sunt in culpa |
- q{n: est }unt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 22 of 65} |
- ]])
-
-
- funcs.complete(4, {'ea', 'eeeeeeeeeeeeeeeeee', 'ei', 'eo', 'eu', 'ey', 'eå', 'eä', 'eö'})
- screen:expect([[
- Est eu^ |
- {s: ea }t amet, consectetur |
- {n: eeeeeeeeeeeeeeeeee }d do eiusmod tempor |
- {n: ei } et dolore magna aliqua. |
- {n: eo }iam, quis nostrud |
- {n: eu } laboris nisi ut aliquip ex |
- {4:[N}{n: ey }{4: }|
- {n: eå }uptate velit esse cillum |
- {n: eä }la pariatur. Excepteur sint |
- {n: eö }on proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- Keyword Local completion (^N^P) }{5:match 1 of 9} |
- ]])
-
- funcs.complete(4, {'ea', 'eee', 'ei', 'eo', 'eu', 'ey', 'eå', 'eä', 'eö'})
- screen:expect([[
- Est eu^ |
- {s: ea }r sit amet, consectetur |
- {n: eee }, sed do eiusmod tempor |
- {n: ei }bore et dolore magna aliqua. |
- {n: eo } veniam, quis nostrud |
- {n: eu }amco laboris nisi ut aliquip ex |
- {4:[N}{n: ey }{4: }|
- {n: eå } voluptate velit esse cillum |
- {n: eä } nulla pariatur. Excepteur sint |
- {n: eö }at non proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-n>')
- screen:expect([[
- Esteee^ |
- {n: ea }r sit amet, consectetur |
- {s: eee }, sed do eiusmod tempor |
- {n: ei }bore et dolore magna aliqua. |
- {n: eo } veniam, quis nostrud |
- {n: eu }amco laboris nisi ut aliquip ex |
- {4:[N}{n: ey }{4: }|
- {n: eå } voluptate velit esse cillum |
- {n: eä } nulla pariatur. Excepteur sint |
- {n: eö }at non proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]])
-
- funcs.complete(6, {'foo', 'bar'})
- screen:expect([[
- Esteee^ |
- Lo{s: foo }sit amet, consectetur |
- ad{n: bar }sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {4:[No Name] [+] }|
- reprehenderit in voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- occaecat cupidatat non proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-y>')
- screen:expect([[
- Esteefoo^ |
- Lorem ipsum dolor sit amet, consectetur |
- adipisicing elit, sed do eiusmod tempor |
- incididunt ut labore et dolore magna aliqua. |
- Ut enim ad minim veniam, quis nostrud |
- exercitation ullamco laboris nisi ut aliquip ex |
- {4:[No Name] [+] }|
- reprehenderit in voluptate velit esse cillum |
- dolore eu fugiat nulla pariatur. Excepteur sint |
- occaecat cupidatat non proident, sunt in culpa |
- qui officia deserunt mollit anim id est |
- laborum. |
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]])
- end)
-
- it('can be moved due to wrap or resize', function()
- feed('isome long prefix before the ')
- command("set completeopt+=noinsert,noselect")
- command("set linebreak")
- funcs.complete(29, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- some long prefix before the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-p>')
- screen:expect([[
- some long prefix before the |
- thing^ |
- {n:word }{1: }|
- {n:choice }{1: }|
- {n:text }{1: }|
- {s:thing }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-p>')
- screen:expect([[
- some long prefix before the text|
- {1:^~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{s: text }|
- {1:~ }{n: thing }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- screen:try_resize(30,8)
- screen:expect([[
- some long prefix before the |
- text^ |
- {n:word }{1: }|
- {n:choice }{1: }|
- {s:text }{1: }|
- {n:thing }{1: }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- screen:try_resize(50,8)
- screen:expect([[
- some long prefix before the text^ |
- {1:~ }{n: word }{1: }|
- {1:~ }{n: choice }{1: }|
- {1:~ }{s: text }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- screen:try_resize(25,10)
- screen:expect([[
- some long prefix before |
- the text^ |
- {1:~ }{n: word }{1: }|
- {1:~ }{n: choice }{1: }|
- {1:~ }{s: text }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- screen:try_resize(12,5)
- screen:expect([[
- some long |
- prefix |
- bef{n: word } |
- tex{n: }^ |
- {2:-- INSERT -} |
- ]])
-
- -- can't draw the pum, but check we don't crash
- screen:try_resize(12,2)
- screen:expect([[
- text^ |
- {2:-- INSERT -} |
- ]])
-
- -- but state is preserved, pum reappears
- screen:try_resize(20,8)
- screen:expect([[
- some long prefix |
- before the text^ |
- {1:~ }{n: word }{1: }|
- {1:~ }{n: choice }{1: }|
- {1:~ }{s: text }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
-
- it('with VimResized autocmd', function()
- feed('isome long prefix before the ')
- command("set completeopt+=noinsert,noselect")
- command("autocmd VimResized * redraw!")
- command("set linebreak")
- funcs.complete(29, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- some long prefix before the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- screen:try_resize(16,10)
- screen:expect([[
- some long |
- prefix before |
- the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice }|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
-
- it('with rightleft window', function()
- command("set rl wildoptions+=pum")
- feed('isome rightleft ')
- screen:expect([[
- ^ tfelthgir emos|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {2:-- INSERT --} |
- ]])
-
- command("set completeopt+=noinsert,noselect")
- funcs.complete(16, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- ^ tfelthgir emos|
- {1: }{n: drow}{1: ~}|
- {1: }{n: eciohc}{1: ~}|
- {1: }{n: txet}{1: ~}|
- {1: }{n: gniht}{1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-n>')
- screen:expect([[
- ^ drow tfelthgir emos|
- {1: }{s: drow}{1: ~}|
- {1: }{n: eciohc}{1: ~}|
- {1: }{n: txet}{1: ~}|
- {1: }{n: gniht}{1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-y>')
- screen:expect([[
- ^ drow tfelthgir emos|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {2:-- INSERT --} |
- ]])
-
- -- not rightleft on the cmdline
- feed('<esc>:sign ')
- screen:expect{grid=[[
- drow tfelthgir emos|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- :sign ^ |
- ]]}
-
- feed('<tab>')
- screen:expect{grid=[[
- drow tfelthgir emos|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: ~}|
- {1: }{s: define }{1: ~}|
- {1: }{n: jump }{1: ~}|
- {1: }{n: list }{1: ~}|
- {1: }{n: place }{1: ~}|
- {1: }{n: undefine }{1: ~}|
- {1: }{n: unplace }{1: ~}|
- :sign define^ |
- ]]}
- end)
-
- it('with multiline messages', function()
- screen:try_resize(40,8)
- feed('ixx<cr>')
- command('imap <f2> <cmd>echoerr "very"\\|echoerr "much"\\|echoerr "error"<cr>')
- funcs.complete(1, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- xx |
- word^ |
- {s:word }{1: }|
- {n:choice }{1: }|
- {n:text }{1: }|
- {n:thing }{1: }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<f2>')
- screen:expect([[
- xx |
- word |
- {s:word }{1: }|
- {4: }|
- {6:very} |
- {6:much} |
- {6:error} |
- {5:Press ENTER or type command to continue}^ |
- ]])
-
- feed('<cr>')
- screen:expect([[
- xx |
- word^ |
- {s:word }{1: }|
- {n:choice }{1: }|
- {n:text }{1: }|
- {n:thing }{1: }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-n>')
- screen:expect([[
- xx |
- choice^ |
- {n:word }{1: }|
- {s:choice }{1: }|
- {n:text }{1: }|
- {n:thing }{1: }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- command("split")
- screen:expect([[
- xx |
- choice^ |
- {n:word }{1: }|
- {s:choice }{4: }|
- {n:text } |
- {n:thing } |
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]])
-
- meths.input_mouse('wheel', 'down', '', 0, 6, 15)
- screen:expect{grid=[[
- xx |
- choice^ |
- {n:word }{1: }|
- {s:choice }{4: }|
- {n:text } |
- {n:thing }{1: }|
- {3:[No Name] [+] }|
- {2:-- INSERT --} |
- ]], unchanged=true}
- end)
-
- it('with kind, menu and abbr attributes', function()
- screen:try_resize(40,8)
- feed('ixx ')
- funcs.complete(4, {{word='wordey', kind= 'x', menu='extrainfo'}, 'thing', {word='secret', abbr='sneaky', menu='bar'}})
- screen:expect([[
- xx wordey^ |
- {1:~ }{s: wordey x extrainfo }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }{n: sneaky bar }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-p>')
- screen:expect([[
- xx ^ |
- {1:~ }{n: wordey x extrainfo }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }{n: sneaky bar }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<c-p>')
- screen:expect([[
- xx secret^ |
- {1:~ }{n: wordey x extrainfo }{1: }|
- {1:~ }{n: thing }{1: }|
- {1:~ }{s: sneaky bar }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
-
- feed('<esc>')
- screen:expect([[
- xx secre^t |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]])
- end)
-
- it('wildoptions=pum', function()
- screen:try_resize(32,10)
- command('set wildmenu')
- command('set wildoptions=pum')
- command('set shellslash')
- command("cd test/functional/fixtures/wildpum")
-
- feed(':sign ')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign ^ |
- ]])
-
- feed('<Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign define^ |
- ]])
-
- feed('<Right><Right>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{s: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign list^ |
- ]])
-
- feed('<C-N>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{s: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign place^ |
- ]])
-
- feed('<C-P>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{s: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign list^ |
- ]])
-
- feed('<Left>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{s: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign jump^ |
- ]])
-
- -- pressing <C-E> should end completion and go back to the original match
- feed('<C-E>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign ^ |
- ]])
-
- -- pressing <C-Y> should select the current match and end completion
- feed('<Tab><C-P><C-P><C-Y>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign unplace^ |
- ]])
-
- -- showing popup menu in different columns in the cmdline
- feed('<C-U>sign define <Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: culhl= }{1: }|
- {1:~ }{n: icon= }{1: }|
- {1:~ }{n: linehl= }{1: }|
- {1:~ }{n: numhl= }{1: }|
- {1:~ }{n: text= }{1: }|
- {1:~ }{n: texthl= }{1: }|
- :sign define culhl=^ |
- ]])
-
- feed('<Space><Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: culhl= }{1: }|
- {1:~ }{n: icon= }{1: }|
- {1:~ }{n: linehl= }{1: }|
- {1:~ }{n: numhl= }{1: }|
- {1:~ }{n: text= }{1: }|
- {1:~ }{n: texthl= }{1: }|
- :sign define culhl= culhl=^ |
- ]])
-
- feed('<C-U>e Xdi<Tab><Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: XdirA/ }{1: }|
- {1:~ }{n: XfileA }{1: }|
- :e Xdir/XdirA/^ |
- ]])
-
- -- Pressing <Down> on a directory name should go into that directory
- feed('<Down>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: XdirB/ }{1: }|
- {1:~ }{n: XfileB }{1: }|
- :e Xdir/XdirA/XdirB/^ |
- ]])
-
- -- Pressing <Up> on a directory name should go to the parent directory
- feed('<Up>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: XdirA/ }{1: }|
- {1:~ }{n: XfileA }{1: }|
- :e Xdir/XdirA/^ |
- ]])
-
- -- Pressing <C-A> when the popup menu is displayed should list all the
- -- matches and remove the popup menu
- feed(':<C-U>sign <Tab><C-A>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- :sign define jump list place und|
- efine unplace^ |
- ]])
-
- -- Pressing <Left> after that should move the cursor
- feed('<Left>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- :sign define jump list place und|
- efine unplac^e |
- ]])
- feed('<End>')
-
- -- Pressing <C-D> when the popup menu is displayed should remove the popup
- -- menu
- feed('<C-U>sign <Tab><C-D>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- :sign define |
- define |
- :sign define^ |
- ]])
-
- -- Pressing <S-Tab> should open the popup menu with the last entry selected
- feed('<C-U><CR>:sign <S-Tab><C-P>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{s: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign undefine^ |
- ]])
-
- -- Pressing <Esc> should close the popup menu and cancel the cmd line
- feed('<C-U><CR>:sign <Tab><Esc>')
- screen:expect([[
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- |
- ]])
-
- -- Typing a character when the popup is open, should close the popup
- feed(':sign <Tab>x')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign definex^ |
- ]])
-
- -- When the popup is open, entering the cmdline window should close the popup
- feed('<C-U>sign <Tab><C-F>')
- screen:expect([[
- |
- {3:[No Name] }|
- {1::}sign define |
- {1::}sign define^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4:[Command Line] }|
- :sign define |
- ]])
- feed(':q<CR>')
-
- -- After the last popup menu item, <C-N> should show the original string
- feed(':sign u<Tab><C-N><C-N>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign u^ |
- ]])
-
- -- Use the popup menu for the command name
- feed('<C-U>bu<Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {s: bufdo }{1: }|
- {n: buffer }{1: }|
- {n: buffers }{1: }|
- {n: bunload }{1: }|
- :bufdo^ |
- ]])
-
- -- Pressing <BS> should remove the popup menu and erase the last character
- feed('<C-E><C-U>sign <Tab><BS>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign defin^ |
- ]])
-
- -- Pressing <C-W> should remove the popup menu and erase the previous word
- feed('<C-E><C-U>sign <Tab><C-W>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign ^ |
- ]])
-
- -- Pressing <C-U> should remove the popup menu and erase the entire line
- feed('<C-E><C-U>sign <Tab><C-U>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :^ |
- ]])
-
- -- Using <C-E> to cancel the popup menu and then pressing <Up> should recall
- -- the cmdline from history
- feed('sign xyz<Esc>:sign <Tab><C-E><Up>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign xyz^ |
- ]])
-
- feed('<esc>')
-
- -- Check "list" still works
- command('set wildmode=longest,list')
- feed(':cn<Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- :cn |
- cnewer cnoreabbrev |
- cnext cnoremap |
- cnfile cnoremenu |
- :cn^ |
- ]])
- feed('s')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- :cn |
- cnewer cnoreabbrev |
- cnext cnoremap |
- cnfile cnoremenu |
- :cns^ |
- ]])
-
- feed('<esc>')
- command('set wildmode=full')
-
- -- Tests a directory name contained full-width characters.
- feed(':e あいう/<Tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: 123 }{1: }|
- {1:~ }{n: abc }{1: }|
- {1:~ }{n: xyz }{1: }|
- :e あいう/123^ |
- ]])
- feed('<Esc>')
-
- -- Pressing <PageDown> should scroll the menu downward
- feed(':sign <Tab><PageDown>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{s: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign undefine^ |
- ]])
- feed('<PageDown>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{s: unplace }{1: }|
- :sign unplace^ |
- ]])
- feed('<PageDown>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign ^ |
- ]])
- feed('<PageDown>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign define^ |
- ]])
- feed('<C-U>sign <Tab><Right><Right><PageDown>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{s: unplace }{1: }|
- :sign unplace^ |
- ]])
-
- -- Pressing <PageUp> should scroll the menu upward
- feed('<C-U>sign <Tab><PageUp>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign ^ |
- ]])
- feed('<PageUp>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{s: unplace }{1: }|
- :sign unplace^ |
- ]])
- feed('<PageUp>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{n: define }{1: }|
- {1:~ }{s: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign jump^ |
- ]])
- feed('<PageUp>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign define^ |
- ]])
-
- feed('<Esc>')
-
- -- check positioning with multibyte char in pattern
- command("e långfile1")
- command("sp långfile2")
- feed(':b lå<tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4:långfile2 }|
- |
- {1:~ }|
- {1:~ }{s: långfile1 }{1: }|
- {3:lå}{n: långfile2 }{3: }|
- :b långfile1^ |
- ]])
-
- -- check doesn't crash on screen resize
- screen:try_resize(20,6)
- screen:expect([[
- |
- {1:~ }|
- {4:långfile2 }|
- {s: långfile1 } |
- {3:lå}{n: långfile2 }{3: }|
- :b långfile1^ |
- ]])
-
- screen:try_resize(50,15)
- screen:expect([[
- |
- {1:~ }|
- {4:långfile2 }|
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: långfile1 }{1: }|
- {3:lå}{n: långfile2 }{3: }|
- :b långfile1^ |
- ]])
-
- -- position is calculated correctly with "longest"
- feed('<esc>')
- command('set wildmode=longest:full,full')
- feed(':b lå<tab>')
- screen:expect([[
- |
- {1:~ }|
- {4:långfile2 }|
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{n: långfile1 }{1: }|
- {3:lå}{n: långfile2 }{3: }|
- :b långfile^ |
- ]])
-
- feed('<esc>')
- command("close")
- command('set wildmode=full')
-
- -- special case: when patterns ends with "/", show menu items aligned
- -- after the "/"
- feed(':e compdir/<tab>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: file1 }{1: }|
- {1:~ }{n: file2 }{1: }|
- :e compdir/file1^ |
- ]])
- end)
-
- it('wildoptions=pum with scrolled messages', function()
- screen:try_resize(40,10)
- command('set wildmenu')
- command('set wildoptions=pum')
-
- feed(':echoerr "fail"|echoerr "error"<cr>')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- {6:fail} |
- {6:error} |
- {5:Press ENTER or type command to continue}^ |
- ]]}
-
- feed(':sign <tab>')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {4: }{n: place }{4: }|
- {6:fail} {n: undefine } |
- {6:error}{n: unplace } |
- :sign define^ |
- ]]}
-
- feed('d')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {4: }|
- {6:fail} |
- {6:error} |
- :sign defined^ |
- ]]}
- end)
-
- it('wildoptions=pum and wildmode=longest,full #11622', function()
- screen:try_resize(30,8)
- command('set wildmenu')
- command('set wildoptions=pum')
- command('set wildmode=longest,full')
-
- -- With 'wildmode' set to 'longest,full', completing a match should display
- -- the longest match, the wildmenu should not be displayed.
- feed(':sign u<Tab>')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :sign un^ |
- ]]}
- eq(0, funcs.wildmenumode())
-
- -- pressing <Tab> should display the wildmenu
- feed('<Tab>')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{s: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign undefine^ |
- ]]}
- eq(1, funcs.wildmenumode())
-
- -- pressing <Tab> second time should select the next entry in the menu
- feed('<Tab>')
- screen:expect{grid=[[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{s: unplace }{1: }|
- :sign unplace^ |
- ]]}
- end)
-
- it('wildoptions=pum with a wrapped line in buffer vim-patch:8.2.4655', function()
- screen:try_resize(32, 10)
- meths.buf_set_lines(0, 0, -1, true, { ('a'):rep(100) })
- command('set wildoptions+=pum')
- feed('$')
- feed(':sign <Tab>')
- screen:expect([[
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
- aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
- aaaa {s: define } |
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign define^ |
- ]])
- end)
-
- -- oldtest: Test_wildmenu_pum_clear_entries()
- it('wildoptions=pum when using Ctrl-E as wildchar vim-patch:9.0.1030', function()
- screen:try_resize(30, 10)
- exec([[
- set wildoptions=pum
- set wildchar=<C-E>
- ]])
- feed(':sign <C-E><C-E>')
- screen:expect([[
- |
- {1:~ }|
- {1:~ }|
- {1:~ }{s: define }{1: }|
- {1:~ }{n: jump }{1: }|
- {1:~ }{n: list }{1: }|
- {1:~ }{n: place }{1: }|
- {1:~ }{n: undefine }{1: }|
- {1:~ }{n: unplace }{1: }|
- :sign define^ |
- ]])
- assert_alive()
- end)
-
- it("'pumblend' RGB-color", function()
- screen:try_resize(60,14)
+ it('RGB-color', function()
+ local screen = Screen.new(60, 14)
screen:set_default_attr_ids({
[1] = {background = Screen.colors.Yellow},
[2] = {bold = true, reverse = true},
@@ -2934,6 +1076,7 @@ describe('builtin popupmenu', function()
[44] = {foreground = tonumber('0x3f3f3f'), background = tonumber('0x7f5d7f')},
[45] = {background = Screen.colors.WebGray, blend=0},
})
+ screen:attach()
command('syntax on')
command('set mouse=a')
command('set pumblend=10')
@@ -3082,10 +1225,8 @@ describe('builtin popupmenu', function()
]])
end)
- it("'pumblend' 256-color (non-RGB)", function()
- screen:detach()
- screen = Screen.new(60, 8)
- screen:attach({rgb=false, ext_popupmenu=false})
+ it('256-color (non-RGB)', function()
+ local screen = Screen.new(60, 8)
screen:set_default_attr_ids({
[1] = {foreground = Screen.colors.Grey0, background = tonumber('0x000007')},
[2] = {foreground = tonumber('0x000055'), background = tonumber('0x000007')},
@@ -3098,6 +1239,7 @@ describe('builtin popupmenu', function()
[9] = {bold = true},
[10] = {foreground = tonumber('0x000002')},
})
+ screen:attach({rgb=false})
command('set notermguicolors pumblend=10')
insert([[
Lorem ipsum dolor sit amet, consectetur
@@ -3118,651 +1260,3738 @@ describe('builtin popupmenu', function()
{9:-- Keyword Local completion (^N^P) }{10:match 1 of 3} |
]])
end)
+end)
- it("'pumheight'", function()
- screen:try_resize(32,8)
- feed('isome long prefix before the ')
- command("set completeopt+=noinsert,noselect")
- command("set linebreak")
- command("set pumheight=2")
- funcs.complete(29, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- some long prefix before the ^ |
- {1:~ }{n: word }{c: }|
- {1:~ }{n: choice}{s: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
+describe('builtin popupmenu', function()
+ before_each(clear)
+
+ local function with_ext_multigrid(multigrid)
+ local screen
+ before_each(function()
+ screen = Screen.new(32, 20)
+ screen:set_default_attr_ids({
+ -- popup selected item / scrollbar track
+ ['s'] = {background = Screen.colors.WebGray},
+ -- popup non-selected item
+ ['n'] = {background = Screen.colors.LightMagenta},
+ -- popup scrollbar knob
+ ['c'] = {background = Screen.colors.Grey0},
+ [1] = {bold = true, foreground = Screen.colors.Blue},
+ [2] = {bold = true},
+ [3] = {reverse = true},
+ [4] = {bold = true, reverse = true},
+ [5] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
+ [7] = {background = Screen.colors.Yellow}, -- Search
+ [8] = {foreground = Screen.colors.Red},
+ })
+ screen:attach({ext_multigrid=multigrid})
+ end)
- it("'pumwidth'", function()
- screen:try_resize(32,8)
- feed('isome long prefix before the ')
- command("set completeopt+=noinsert,noselect")
- command("set linebreak")
- command("set pumwidth=8")
- funcs.complete(29, {'word', 'choice', 'text', 'thing'})
- screen:expect([[
- some long prefix before the ^ |
- {1:~ }{n: word }|
- {1:~ }{n: choice}|
- {1:~ }{n: text }|
- {1:~ }{n: thing }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
+ it('with preview-window above', function()
+ feed(':ped<CR><c-w>4+')
+ feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
+ feed('<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ {3:[No Name] [Preview][+] }|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aa bb cc dd ee ff gg hh ii jj |
+ aa^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 10} |
+ ## grid 4
+ aa bb cc dd ee ff gg hh ii jj |
+ aa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 5
+ {s:aa }{c: }|
+ {n:bb }{c: }|
+ {n:cc }{c: }|
+ {n:dd }{c: }|
+ {n:ee }{c: }|
+ {n:ff }{c: }|
+ {n:gg }{s: }|
+ {n:hh }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 2, 2, 0, false, 100};
+ }}
+ else
+ screen:expect([[
+ aa bb cc dd ee ff gg hh ii jj |
+ aa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] [Preview][+] }|
+ aa bb cc dd ee ff gg hh ii jj |
+ aa^ |
+ {s:aa }{c: }{1: }|
+ {n:bb }{c: }{1: }|
+ {n:cc }{c: }{1: }|
+ {n:dd }{c: }{1: }|
+ {n:ee }{c: }{1: }|
+ {n:ff }{c: }{1: }|
+ {n:gg }{s: }{1: }|
+ {n:hh }{s: }{4: }|
+ {2:-- }{5:match 1 of 10} |
+ ]])
+ end
+ end)
+
+ it('with preview-window below', function()
+ feed(':ped<CR><c-w>4+<c-w>r')
+ feed('iaa bb cc dd ee ff gg hh ii jj<cr>')
+ feed('<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {4:[No Name] [+] }|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ {3:[No Name] [Preview][+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aa bb cc dd ee ff gg hh ii jj |
+ aa^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 10} |
+ ## grid 4
+ aa bb cc dd ee ff gg hh ii jj |
+ aa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 5
+ {s:aa }{c: }|
+ {n:bb }{c: }|
+ {n:cc }{c: }|
+ {n:dd }{c: }|
+ {n:ee }{c: }|
+ {n:ff }{c: }|
+ {n:gg }{s: }|
+ {n:hh }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 2, 2, 0, false, 100};
+ }}
+ else
+ screen:expect([[
+ aa bb cc dd ee ff gg hh ii jj |
+ aa^ |
+ {s:aa }{c: }{1: }|
+ {n:bb }{c: }{1: }|
+ {n:cc }{c: }{1: }|
+ {n:dd }{c: }{1: }|
+ {n:ee }{c: }{1: }|
+ {n:ff }{c: }{1: }|
+ {n:gg }{s: }{1: }|
+ {n:hh }{s: }{4: }|
+ aa bb cc dd ee ff gg hh ii jj |
+ aa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] [Preview][+] }|
+ {2:-- }{5:match 1 of 10} |
+ ]])
+ end
+ end)
+
+ it('with preview-window above and tall and inverted', function()
+ feed(':ped<CR><c-w>8+')
+ feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
+ feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
+ feed('kk<cr>ll<cr>mm<cr>nn<cr>oo<cr>')
+ feed('<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ {3:[No Name] [Preview][+] }|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ ii |
+ jj |
+ kk |
+ ll |
+ mm |
+ nn |
+ oo |
+ aa^ |
+ ## grid 3
+ {2:-- }{5:match 1 of 15} |
+ ## grid 4
+ aa |
+ bb |
+ cc |
+ dd |
+ ## grid 5
+ {s:aa }{c: }|
+ {n:bb }{c: }|
+ {n:cc }{c: }|
+ {n:dd }{c: }|
+ {n:ee }{c: }|
+ {n:ff }{c: }|
+ {n:gg }{c: }|
+ {n:hh }{c: }|
+ {n:ii }{c: }|
+ {n:jj }{c: }|
+ {n:kk }{c: }|
+ {n:ll }{s: }|
+ {n:mm }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "SW", 2, 12, 0, false, 100};
+ }}
+ else
+ screen:expect([[
+ aa |
+ bb |
+ cc |
+ dd |
+ {s:aa }{c: }{3:ew][+] }|
+ {n:bb }{c: } |
+ {n:cc }{c: } |
+ {n:dd }{c: } |
+ {n:ee }{c: } |
+ {n:ff }{c: } |
+ {n:gg }{c: } |
+ {n:hh }{c: } |
+ {n:ii }{c: } |
+ {n:jj }{c: } |
+ {n:kk }{c: } |
+ {n:ll }{s: } |
+ {n:mm }{s: } |
+ aa^ |
+ {4:[No Name] [+] }|
+ {2:-- }{5:match 1 of 15} |
+ ]])
+ end
+ end)
+
+ it('with preview-window above and short and inverted', function()
+ feed(':ped<CR><c-w>4+')
+ feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
+ feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
+ feed('<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ {3:[No Name] [Preview][+] }|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ ii |
+ jj |
+ aa^ |
+ ## grid 3
+ {2:-- }{5:match 1 of 10} |
+ ## grid 4
+ aa |
+ bb |
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ ## grid 5
+ {s:aa }{c: }|
+ {n:bb }{c: }|
+ {n:cc }{c: }|
+ {n:dd }{c: }|
+ {n:ee }{c: }|
+ {n:ff }{c: }|
+ {n:gg }{c: }|
+ {n:hh }{c: }|
+ {n:ii }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "SW", 2, 8, 0, false, 100};
+ }}
+ else
+ screen:expect([[
+ aa |
+ bb |
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ {s:aa }{c: }{3:ew][+] }|
+ {n:bb }{c: } |
+ {n:cc }{c: } |
+ {n:dd }{c: } |
+ {n:ee }{c: } |
+ {n:ff }{c: } |
+ {n:gg }{c: } |
+ {n:hh }{c: } |
+ {n:ii }{s: } |
+ aa^ |
+ {4:[No Name] [+] }|
+ {2:-- }{5:match 1 of 10} |
+ ]])
+ end
+ end)
- it('does not crash when displayed in the last column with rightleft (#12032)', function()
- local col = 30
- local items = {'word', 'choice', 'text', 'thing'}
- local max_len = 0
- for _, v in ipairs(items) do
- max_len = max_len < #v and #v or max_len
+ it('with preview-window below and inverted', function()
+ feed(':ped<CR><c-w>4+<c-w>r')
+ feed('iaa<cr>bb<cr>cc<cr>dd<cr>ee<cr>')
+ feed('ff<cr>gg<cr>hh<cr>ii<cr>jj<cr>')
+ feed('<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {4:[No Name] [+] }|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ [4:--------------------------------]|
+ {3:[No Name] [Preview][+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ ii |
+ jj |
+ aa^ |
+ ## grid 3
+ {2:-- }{5:match 1 of 10} |
+ ## grid 4
+ aa |
+ bb |
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ ## grid 5
+ {s:aa }{c: }|
+ {n:bb }{c: }|
+ {n:cc }{c: }|
+ {n:dd }{c: }|
+ {n:ee }{c: }|
+ {n:ff }{c: }|
+ {n:gg }{s: }|
+ {n:hh }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "SW", 2, 8, 0, false, 100};
+ }}
+ else
+ screen:expect([[
+ {s:aa }{c: } |
+ {n:bb }{c: } |
+ {n:cc }{c: } |
+ {n:dd }{c: } |
+ {n:ee }{c: } |
+ {n:ff }{c: } |
+ {n:gg }{s: } |
+ {n:hh }{s: } |
+ aa^ |
+ {4:[No Name] [+] }|
+ aa |
+ bb |
+ cc |
+ dd |
+ ee |
+ ff |
+ gg |
+ hh |
+ {3:[No Name] [Preview][+] }|
+ {2:-- }{5:match 1 of 10} |
+ ]])
+ end
+ end)
+
+ if not multigrid then
+ -- oldtest: Test_pum_with_preview_win()
+ it('preview window opened during completion', function()
+ exec([[
+ funct Omni_test(findstart, base)
+ if a:findstart
+ return col(".") - 1
+ endif
+ return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}]
+ endfunc
+ set omnifunc=Omni_test
+ set completeopt+=longest
+ ]])
+ feed('Gi<C-X><C-O>')
+ screen:expect([[
+ ^ |
+ {n:one }{1: }|
+ {n:two }{1: }|
+ {n:three }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- }{8:Back at original} |
+ ]])
+ feed('<C-N>')
+ screen:expect([[
+ 1info |
+ |
+ {1:~ }|
+ {3:[Scratch] [Preview] }|
+ one^ |
+ {s:one }{1: }|
+ {n:two }{1: }|
+ {n:three }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4:[No Name] [+] }|
+ {2:-- }{5:match 1 of 3} |
+ ]])
+ end)
+
+ -- oldtest: Test_scrollbar_on_wide_char()
+ it('scrollbar overwrites half of double-width char below properly', function()
+ screen:try_resize(32, 10)
+ exec([[
+ call setline(1, ['a', ' 啊啊啊',
+ \ ' 哦哦哦',
+ \ ' 呃呃呃'])
+ call setline(5, range(10)->map({i, v -> 'aa' .. v .. 'bb'}))
+ ]])
+ feed('A<C-X><C-N>')
+ screen:expect([[
+ aa0bb^ |
+ {s:aa0bb }{c: }啊 |
+ {n:aa1bb }{c: } 哦 |
+ {n:aa2bb }{c: }呃呃 |
+ {n:aa3bb }{c: } |
+ {n:aa4bb }{c: } |
+ {n:aa5bb }{c: } |
+ {n:aa6bb }{s: } |
+ {n:aa7bb }{s: } |
+ {2:-- }{5:match 1 of 10} |
+ ]])
+ end)
end
- screen:try_resize(col, 8)
- command('set rightleft')
- command('call setline(1, repeat(" ", &columns - '..max_len..'))')
- feed('$i')
- funcs.complete(col - max_len, items)
- feed('<c-y>')
- assert_alive()
- end)
- it('truncates double-width character correctly when there is no scrollbar', function()
- screen:try_resize(32,8)
- command('set completeopt+=menuone,noselect')
- feed('i' .. string.rep(' ', 13))
- funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'})
- screen:expect([[
- ^ |
- {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
+ it('with vsplits', function()
+ screen:try_resize(32, 8)
+ insert('aaa aab aac\n')
+ feed(':vsplit<cr>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ {4:[No Name] [+] }{3:<Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aaa aab aac|
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :vsplit |
+ ## grid 4
+ aaa aab aac |
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ]]}
+ else
+ screen:expect([[
+ aaa aab aac │aaa aab aac|
+ ^ │ |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {4:[No Name] [+] }{3:<Name] [+] }|
+ :vsplit |
+ ]])
+ end
+
+ feed('ibbb a<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ [4:--------------------]│[2:-----------]|
+ {4:[No Name] [+] }{3:<Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aaa aab aac|
+ bbb aaa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 3} |
+ ## grid 4
+ aaa aab aac |
+ bbb aaa^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 5
+ {s: aaa }|
+ {n: aab }|
+ {n: aac }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 4, 2, 3, false, 100};
+ }}
+ else
+ screen:expect([[
+ aaa aab aac │aaa aab aac|
+ bbb aaa^ │bbb aaa |
+ {1:~ }{s: aaa }{1: }│{1:~ }|
+ {1:~ }{n: aab }{1: }│{1:~ }|
+ {1:~ }{n: aac }{1: }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {4:[No Name] [+] }{3:<Name] [+] }|
+ {2:-- }{5:match 1 of 3} |
+ ]])
+ end
+
+ feed('<esc><c-w><c-w>oc a<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ {3:<Name] [+] }{4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aaa aab aac |
+ bbb aaa |
+ c aaa^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 3} |
+ ## grid 4
+ aaa aab aac|
+ bbb aaa |
+ c aaa |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 5
+ {s: aaa }|
+ {n: aab }|
+ {n: aac }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 2, 3, 1, false, 100};
+ }}
+ else
+ screen:expect([[
+ aaa aab aac│aaa aab aac |
+ bbb aaa │bbb aaa |
+ c aaa │c aaa^ |
+ {1:~ }│{1:~}{s: aaa }{1: }|
+ {1:~ }│{1:~}{n: aab }{1: }|
+ {1:~ }│{1:~}{n: aac }{1: }|
+ {3:<Name] [+] }{4:[No Name] [+] }|
+ {2:-- }{5:match 1 of 3} |
+ ]])
+ end
+
+ feed('bcdef ccc a<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ {3:<Name] [+] }{4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aaa aab aac |
+ bbb aaa |
+ c aaabcdef ccc aaa^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 4} |
+ ## grid 4
+ aaa aab aac|
+ bbb aaa |
+ c aaabcdef |
+ ccc aaa |
+ {1:~ }|
+ {1:~ }|
+ ## grid 5
+ {s: aaa }|
+ {n: aab }|
+ {n: aac }|
+ {n: aaabcdef}|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 2, 3, 11, false, 100};
+ }}
+ else
+ screen:expect([[
+ aaa aab aac│aaa aab aac |
+ bbb aaa │bbb aaa |
+ c aaabcdef │c aaabcdef ccc aaa^ |
+ ccc aaa │{1:~ }{s: aaa }|
+ {1:~ }│{1:~ }{n: aab }|
+ {1:~ }│{1:~ }{n: aac }|
+ {3:<Name] [+] }{4:[No Name] [}{n: aaabcdef}|
+ {2:-- }{5:match 1 of 4} |
+ ]])
+ end
+
+ feed('\n<c-x><c-n>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ [4:-----------]│[2:--------------------]|
+ {3:<Name] [+] }{4:[No Name] [+] }|
+ [3:--------------------------------]|
+ ## grid 2
+ aaa aab aac |
+ bbb aaa |
+ c aaabcdef ccc aaa |
+ aaa^ |
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- }{5:match 1 of 6} |
+ ## grid 4
+ aaa aab aac|
+ bbb aaa |
+ c aaabcdef |
+ ccc aaa |
+ aaa |
+ {1:~ }|
+ ## grid 5
+ {s: aaa }{c: }|
+ {n: aab }{s: }|
+ {n: aac }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 2, 4, -1, false, 100};
+ }}
+ else
+ screen:expect([[
+ aaa aab aac│aaa aab aac |
+ bbb aaa │bbb aaa |
+ c aaabcdef │c aaabcdef ccc aaa |
+ ccc aaa │aaa^ |
+ aaa {s: aaa }{c: }{1: }|
+ {1:~ }{n: aab }{s: }{1: }|
+ {3:<Name] [+] }{n: aac }{s: }{4: }|
+ {2:-- }{5:match 1 of 6} |
+ ]])
+ end
+ end)
- it('truncates double-width character correctly when there is scrollbar', function()
- screen:try_resize(32,8)
- command('set completeopt+=noselect')
- command('set pumheight=4')
- feed('i' .. string.rep(' ', 12))
- local items = {}
- for _ = 1, 8 do
- table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1})
+ if not multigrid then
+ it('with split and scroll', function()
+ screen:try_resize(60,14)
+ command("split")
+ command("set completeopt+=noinsert")
+ command("set mouse=a")
+ insert([[
+ Lorem ipsum dolor sit amet, consectetur
+ adipisicing elit, sed do eiusmod tempor
+ incididunt ut labore et dolore magna aliqua.
+ Ut enim ad minim veniam, quis nostrud
+ exercitation ullamco laboris nisi ut aliquip ex
+ ea commodo consequat. Duis aute irure dolor in
+ reprehenderit in voluptate velit esse cillum
+ dolore eu fugiat nulla pariatur. Excepteur sint
+ occaecat cupidatat non proident, sunt in culpa
+ qui officia deserunt mollit anim id est
+ laborum.
+ ]])
+
+ screen:expect([[
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ laborum. |
+ ^ |
+ {4:[No Name] [+] }|
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ {3:[No Name] [+] }|
+ |
+ ]])
+
+ feed('ggOEst <c-x><c-p>')
+ screen:expect([[
+ Est ^ |
+ L{n: sunt }{s: }sit amet, consectetur |
+ a{n: in }{s: }sed do eiusmod tempor |
+ i{n: culpa }{s: }re et dolore magna aliqua. |
+ U{n: qui }{s: }eniam, quis nostrud |
+ e{n: officia }{s: }co laboris nisi ut aliquip ex |
+ {4:[No}{n: deserunt }{s: }{4: }|
+ Est{n: mollit }{s: } |
+ L{n: anim }{s: }sit amet, consectetur |
+ a{n: id }{s: }sed do eiusmod tempor |
+ i{n: est }{s: }re et dolore magna aliqua. |
+ U{n: laborum }{c: }eniam, quis nostrud |
+ {3:[No}{s: Est }{c: }{3: }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ meths.input_mouse('wheel', 'down', '', 0, 9, 40)
+ screen:expect([[
+ Est ^ |
+ L{n: sunt }{s: }sit amet, consectetur |
+ a{n: in }{s: }sed do eiusmod tempor |
+ i{n: culpa }{s: }re et dolore magna aliqua. |
+ U{n: qui }{s: }eniam, quis nostrud |
+ e{n: officia }{s: }co laboris nisi ut aliquip ex |
+ {4:[No}{n: deserunt }{s: }{4: }|
+ i{n: mollit }{s: }re et dolore magna aliqua. |
+ U{n: anim }{s: }eniam, quis nostrud |
+ e{n: id }{s: }co laboris nisi ut aliquip ex |
+ e{n: est }{s: }at. Duis aute irure dolor in |
+ r{n: laborum }{c: }oluptate velit esse cillum |
+ {3:[No}{s: Est }{c: }{3: }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ feed('e')
+ screen:expect([[
+ Est e^ |
+ L{n: elit } sit amet, consectetur |
+ a{n: eiusmod } sed do eiusmod tempor |
+ i{n: et }ore et dolore magna aliqua. |
+ U{n: enim }veniam, quis nostrud |
+ e{n: exercitation }mco laboris nisi ut aliquip ex |
+ {4:[No}{n: ex }{4: }|
+ i{n: ea }ore et dolore magna aliqua. |
+ U{n: esse }veniam, quis nostrud |
+ e{n: eu }mco laboris nisi ut aliquip ex |
+ e{s: est }uat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ meths.input_mouse('wheel', 'up', '', 0, 9, 40)
+ screen:expect([[
+ Est e^ |
+ L{n: elit } sit amet, consectetur |
+ a{n: eiusmod } sed do eiusmod tempor |
+ i{n: et }ore et dolore magna aliqua. |
+ U{n: enim }veniam, quis nostrud |
+ e{n: exercitation }mco laboris nisi ut aliquip ex |
+ {4:[No}{n: ex }{4: }|
+ Est{n: ea } |
+ L{n: esse } sit amet, consectetur |
+ a{n: eu } sed do eiusmod tempor |
+ i{s: est }ore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ feed('s')
+ screen:expect([[
+ Est es^ |
+ L{n: esse } sit amet, consectetur |
+ a{s: est } sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ {4:[No Name] [+] }|
+ Est es |
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ meths.input_mouse('wheel', 'down', '', 0, 9, 40)
+ screen:expect([[
+ Est es^ |
+ L{n: esse } sit amet, consectetur |
+ a{s: est } sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ {4:[No Name] [+] }|
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ feed('<bs>')
+ screen:expect([[
+ Est e^ |
+ L{n: elit } sit amet, consectetur |
+ a{n: eiusmod } sed do eiusmod tempor |
+ i{n: et }ore et dolore magna aliqua. |
+ U{n: enim }veniam, quis nostrud |
+ e{n: exercitation }mco laboris nisi ut aliquip ex |
+ {4:[No}{n: ex }{4: }|
+ i{n: ea }ore et dolore magna aliqua. |
+ U{n: esse }veniam, quis nostrud |
+ e{n: eu }mco laboris nisi ut aliquip ex |
+ e{s: est }uat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 65} |
+ ]])
+
+ feed('<c-p>')
+ screen:expect([[
+ Est eu^ |
+ L{n: elit } sit amet, consectetur |
+ a{n: eiusmod } sed do eiusmod tempor |
+ i{n: et }ore et dolore magna aliqua. |
+ U{n: enim }veniam, quis nostrud |
+ e{n: exercitation }mco laboris nisi ut aliquip ex |
+ {4:[No}{n: ex }{4: }|
+ i{n: ea }ore et dolore magna aliqua. |
+ U{n: esse }veniam, quis nostrud |
+ e{s: eu }mco laboris nisi ut aliquip ex |
+ e{n: est }uat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 22 of 65} |
+ ]])
+
+ meths.input_mouse('wheel', 'down', '', 0, 9, 40)
+ screen:expect([[
+ Est eu^ |
+ L{n: elit } sit amet, consectetur |
+ a{n: eiusmod } sed do eiusmod tempor |
+ i{n: et }ore et dolore magna aliqua. |
+ U{n: enim }veniam, quis nostrud |
+ e{n: exercitation }mco laboris nisi ut aliquip ex |
+ {4:[No}{n: ex }{4: }|
+ e{n: ea }uat. Duis aute irure dolor in |
+ r{n: esse }voluptate velit esse cillum |
+ d{s: eu }nulla pariatur. Excepteur sint |
+ o{n: est }t non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 22 of 65} |
+ ]])
+
+ funcs.complete(4, {'ea', 'eeeeeeeeeeeeeeeeee', 'ei', 'eo', 'eu', 'ey', 'eå', 'eä', 'eö'})
+ screen:expect([[
+ Est eu^ |
+ {s: ea }t amet, consectetur |
+ {n: eeeeeeeeeeeeeeeeee }d do eiusmod tempor |
+ {n: ei } et dolore magna aliqua. |
+ {n: eo }iam, quis nostrud |
+ {n: eu } laboris nisi ut aliquip ex |
+ {4:[N}{n: ey }{4: }|
+ {n: eå }. Duis aute irure dolor in |
+ {n: eä }uptate velit esse cillum |
+ {n: eö }la pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- Keyword Local completion (^N^P) }{5:match 1 of 9} |
+ ]])
+
+ funcs.complete(4, {'ea', 'eee', 'ei', 'eo', 'eu', 'ey', 'eå', 'eä', 'eö'})
+ screen:expect([[
+ Est eu^ |
+ {s: ea }r sit amet, consectetur |
+ {n: eee }, sed do eiusmod tempor |
+ {n: ei }bore et dolore magna aliqua. |
+ {n: eo } veniam, quis nostrud |
+ {n: eu }amco laboris nisi ut aliquip ex |
+ {4:[N}{n: ey }{4: }|
+ {n: eå }quat. Duis aute irure dolor in |
+ {n: eä } voluptate velit esse cillum |
+ {n: eö } nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-n>')
+ screen:expect([[
+ Esteee^ |
+ {n: ea }r sit amet, consectetur |
+ {s: eee }, sed do eiusmod tempor |
+ {n: ei }bore et dolore magna aliqua. |
+ {n: eo } veniam, quis nostrud |
+ {n: eu }amco laboris nisi ut aliquip ex |
+ {4:[N}{n: ey }{4: }|
+ {n: eå }quat. Duis aute irure dolor in |
+ {n: eä } voluptate velit esse cillum |
+ {n: eö } nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+
+ funcs.complete(6, {'foo', 'bar'})
+ screen:expect([[
+ Esteee^ |
+ Lo{s: foo }sit amet, consectetur |
+ ad{n: bar }sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ {4:[No Name] [+] }|
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-y>')
+ screen:expect([[
+ Esteefoo^ |
+ Lorem ipsum dolor sit amet, consectetur |
+ adipisicing elit, sed do eiusmod tempor |
+ incididunt ut labore et dolore magna aliqua. |
+ Ut enim ad minim veniam, quis nostrud |
+ exercitation ullamco laboris nisi ut aliquip ex |
+ {4:[No Name] [+] }|
+ ea commodo consequat. Duis aute irure dolor in |
+ reprehenderit in voluptate velit esse cillum |
+ dolore eu fugiat nulla pariatur. Excepteur sint |
+ occaecat cupidatat non proident, sunt in culpa |
+ qui officia deserunt mollit anim id est |
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+ end)
+
+ it('can be moved due to wrap or resize', function()
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("set linebreak")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ some long prefix before the ^ |
+ {1:~ }{n: word }|
+ {1:~ }{n: choice}|
+ {1:~ }{n: text }|
+ {1:~ }{n: thing }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-p>')
+ screen:expect([[
+ some long prefix before the |
+ thing^ |
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {s:thing }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-p>')
+ screen:expect([[
+ some long prefix before the text|
+ {1:^~ }{n: word }|
+ {1:~ }{n: choice}|
+ {1:~ }{s: text }|
+ {1:~ }{n: thing }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ screen:try_resize(30,8)
+ screen:expect([[
+ some long prefix before the |
+ text^ |
+ {n:word }{1: }|
+ {n:choice }{1: }|
+ {s:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ screen:try_resize(50,8)
+ screen:expect([[
+ some long prefix before the text^ |
+ {1:~ }{n: word }{1: }|
+ {1:~ }{n: choice }{1: }|
+ {1:~ }{s: text }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ screen:try_resize(25,10)
+ screen:expect([[
+ some long prefix before |
+ the text^ |
+ {1:~ }{n: word }{1: }|
+ {1:~ }{n: choice }{1: }|
+ {1:~ }{s: text }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ screen:try_resize(12,5)
+ screen:expect([[
+ some long |
+ prefix |
+ bef{n: word } |
+ tex{n: }^ |
+ {2:-- INSERT --}|
+ ]])
+
+ -- can't draw the pum, but check we don't crash
+ screen:try_resize(12,2)
+ screen:expect([[
+ {1:<<<}t^ |
+ {2:-- INSERT --}|
+ ]])
+
+ -- but state is preserved, pum reappears
+ screen:try_resize(20,8)
+ screen:expect([[
+ some long prefix |
+ before the text^ |
+ {1:~ }{n: word }{1: }|
+ {1:~ }{n: choice }{1: }|
+ {1:~ }{s: text }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end)
+
+ it('with VimResized autocmd', function()
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("autocmd VimResized * redraw!")
+ command("set linebreak")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ some long prefix before the ^ |
+ {1:~ }{n: word }|
+ {1:~ }{n: choice}|
+ {1:~ }{n: text }|
+ {1:~ }{n: thing }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ screen:try_resize(16,10)
+ screen:expect([[
+ some long |
+ prefix before |
+ the ^ |
+ {1:~ }{n: word }|
+ {1:~ }{n: choice }|
+ {1:~ }{n: text }|
+ {1:~ }{n: thing }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end)
+
+ it('with rightleft window', function()
+ command("set rl wildoptions+=pum")
+ feed('isome rightleft ')
+ screen:expect([[
+ ^ tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {2:-- INSERT --} |
+ ]])
+
+ command("set completeopt+=noinsert,noselect")
+ funcs.complete(16, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ ^ tfelthgir emos|
+ {1: }{n: drow }{1: ~}|
+ {1: }{n: eciohc }{1: ~}|
+ {1: }{n: txet }{1: ~}|
+ {1: }{n: gniht }{1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-n>')
+ screen:expect([[
+ ^ drow tfelthgir emos|
+ {1: }{s: drow }{1: ~}|
+ {1: }{n: eciohc }{1: ~}|
+ {1: }{n: txet }{1: ~}|
+ {1: }{n: gniht }{1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-y>')
+ screen:expect([[
+ ^ drow tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {2:-- INSERT --} |
+ ]])
+
+ -- not rightleft on the cmdline
+ feed('<esc>:sign ')
+ screen:expect{grid=[[
+ drow tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ :sign ^ |
+ ]]}
+
+ feed('<tab>')
+ screen:expect{grid=[[
+ drow tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ {1: }{s: define }{1: ~}|
+ {1: }{n: jump }{1: ~}|
+ {1: }{n: list }{1: ~}|
+ {1: }{n: place }{1: ~}|
+ {1: }{n: undefine }{1: ~}|
+ {1: }{n: unplace }{1: ~}|
+ :sign define^ |
+ ]]}
+ end)
end
- funcs.complete(13, items)
- screen:expect([[
- ^ |
- {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }|
- {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }|
- {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }|
- {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }|
- {1:~ }|
- {1:~ }|
- {2:-- INSERT --} |
- ]])
- end)
- it('supports mousemodel=popup', function()
- screen:try_resize(32, 6)
- exec([[
- call setline(1, 'popup menu test')
- set mouse=a mousemodel=popup
+ it('with rightleft vsplits', function()
+ screen:try_resize(40, 6)
+ command('set rightleft')
+ command('rightbelow vsplit')
+ command('set completeopt+=noinsert,noselect')
+ command('set pumheight=2')
+ feed('isome rightleft ')
+ funcs.complete(16, {'word', 'choice', 'text', 'thing'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ ^ tfelthgir emos|
+ {1: ~}|
+ {1: ~}|
+ {1: ~}|
+ ## grid 5
+ {c: }{n: drow }|
+ {s: }{n: eciohc }|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 4, 1, -11, false, 100};
+ }}
+ else
+ screen:expect([[
+ tfelthgir emos│ ^ tfelthgir emos|
+ {1: }{c: }{n: drow }{1: ~}|
+ {1: }{s: }{n: eciohc }{1: ~}|
+ {1: ~}│{1: ~}|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ feed('<C-E><CR>')
+ funcs.complete(1, {'word', 'choice', 'text', 'thing'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ tfelthgir emos|
+ |
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ tfelthgir emos|
+ ^ |
+ {1: ~}|
+ {1: ~}|
+ ## grid 5
+ {c: }{n: drow}|
+ {s: }{n: eciohc}|
+ ]], float_pos={
+ [5] = {{id = -1}, "NW", 4, 2, 4, false, 100};
+ }}
+ else
+ screen:expect([[
+ tfelthgir emos│ tfelthgir emos|
+ │ ^ |
+ {1: ~}│{1: }{c: }{n: drow}|
+ {1: ~}│{1: }{s: }{n: eciohc}|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ feed('<C-E>')
+ async_meths.call_function('input', {'', '', 'sign'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ tfelthgir emos|
+ |
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ ^ |
+ ## grid 4
+ tfelthgir emos|
+ |
+ {1: ~}|
+ {1: ~}|
+ ]]}
+ else
+ screen:expect([[
+ tfelthgir emos│ tfelthgir emos|
+ │ |
+ {1: ~}│{1: ~}|
+ {1: ~}│{1: ~}|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ ^ |
+ ]])
+ end
+ command('set wildoptions+=pum')
+ feed('<Tab>')
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ [2:-------------------]│[4:--------------------]|
+ {3:[No Name] [+] }{4:[No Name] [+] }|
+ [3:----------------------------------------]|
+ ## grid 2
+ tfelthgir emos|
+ |
+ {1: ~}|
+ {1: ~}|
+ ## grid 3
+ define^ |
+ ## grid 4
+ tfelthgir emos|
+ |
+ {1: ~}|
+ {1: ~}|
+ ## grid 5
+ {s:define }{c: }|
+ {n:jump }{s: }|
+ ]], float_pos={
+ [5] = {{id = -1}, "SW", 1, 5, 0, false, 250};
+ }}
+ else
+ screen:expect([[
+ tfelthgir emos│ tfelthgir emos|
+ │ |
+ {1: ~}│{1: ~}|
+ {s:define }{c: }{1: ~}│{1: ~}|
+ {n:jump }{s: }{3: }{4:[No Name] [+] }|
+ define^ |
+ ]])
+ end
+ end)
- aunmenu PopUp
- menu PopUp.foo :let g:menustr = 'foo'<CR>
- menu PopUp.bar :let g:menustr = 'bar'<CR>
- menu PopUp.baz :let g:menustr = 'baz'<CR>
- ]])
- feed('<RightMouse><4,0>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- {1:~ }|
- |
- ]])
- feed('<Down>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{s: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- {1:~ }|
- |
- ]])
- feed('<Down>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{s: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- {1:~ }|
- |
- ]])
- feed('<CR>')
- screen:expect([[
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :let g:menustr = 'bar' |
- ]])
- eq('bar', meths.get_var('menustr'))
- feed('<RightMouse><20,1>')
- screen:expect([[
- ^popup menu test |
- {1:~ }|
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- :let g:menustr = 'bar' |
- ]])
- feed('<LeftMouse><22,4>')
- screen:expect([[
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :let g:menustr = 'baz' |
- ]])
- eq('baz', meths.get_var('menustr'))
- feed('<RightMouse><4,0>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- {1:~ }|
- :let g:menustr = 'baz' |
- ]])
- feed('<RightDrag><6,3>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{s: baz }{1: }|
- {1:~ }|
- :let g:menustr = 'baz' |
- ]])
- feed('<RightRelease><6,1>')
- screen:expect([[
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :let g:menustr = 'foo' |
- ]])
- eq('foo', meths.get_var('menustr'))
- eq(false, screen.options.mousemoveevent)
- feed('<RightMouse><4,0>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{n: baz }{1: }|
- {1:~ }|
- :let g:menustr = 'foo' |
- ]])
- eq(true, screen.options.mousemoveevent)
- feed('<MouseMove><6,3>')
- screen:expect([[
- ^popup menu test |
- {1:~ }{n: foo }{1: }|
- {1:~ }{n: bar }{1: }|
- {1:~ }{s: baz }{1: }|
- {1:~ }|
- :let g:menustr = 'foo' |
- ]])
- eq(true, screen.options.mousemoveevent)
- feed('<LeftMouse><6,2>')
- screen:expect([[
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :let g:menustr = 'bar' |
- ]])
- eq(false, screen.options.mousemoveevent)
- eq('bar', meths.get_var('menustr'))
- end)
+ if not multigrid then
+ it('with multiline messages', function()
+ screen:try_resize(40,8)
+ feed('ixx<cr>')
+ command('imap <f2> <cmd>echoerr "very"\\|echoerr "much"\\|echoerr "error"<cr>')
+ funcs.complete(1, {'word', 'choice', 'text', 'thing'})
+ screen:expect([[
+ xx |
+ word^ |
+ {s:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<f2>')
+ screen:expect([[
+ xx |
+ word |
+ {s:word }{1: }|
+ {4: }|
+ {6:very} |
+ {6:much} |
+ {6:error} |
+ {5:Press ENTER or type command to continue}^ |
+ ]])
+
+ feed('<cr>')
+ screen:expect([[
+ xx |
+ word^ |
+ {s:word }{1: }|
+ {n:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-n>')
+ screen:expect([[
+ xx |
+ choice^ |
+ {n:word }{1: }|
+ {s:choice }{1: }|
+ {n:text }{1: }|
+ {n:thing }{1: }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ command("split")
+ screen:expect([[
+ xx |
+ choice^ |
+ {n:word }{1: }|
+ {s:choice }{4: }|
+ {n:text } |
+ {n:thing } |
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]])
+
+ meths.input_mouse('wheel', 'down', '', 0, 6, 15)
+ screen:expect{grid=[[
+ xx |
+ choice^ |
+ {n:word }{1: }|
+ {s:choice }{4: }|
+ {n:text } |
+ {n:thing }{1: }|
+ {3:[No Name] [+] }|
+ {2:-- INSERT --} |
+ ]], unchanged=true}
+ end)
+
+ it('with kind, menu and abbr attributes', function()
+ screen:try_resize(40,8)
+ feed('ixx ')
+ funcs.complete(4, {{word='wordey', kind= 'x', menu='extrainfo'}, 'thing', {word='secret', abbr='sneaky', menu='bar'}})
+ screen:expect([[
+ xx wordey^ |
+ {1:~ }{s: wordey x extrainfo }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }{n: sneaky bar }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-p>')
+ screen:expect([[
+ xx ^ |
+ {1:~ }{n: wordey x extrainfo }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }{n: sneaky bar }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<c-p>')
+ screen:expect([[
+ xx secret^ |
+ {1:~ }{n: wordey x extrainfo }{1: }|
+ {1:~ }{n: thing }{1: }|
+ {1:~ }{s: sneaky bar }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+
+ feed('<esc>')
+ screen:expect([[
+ xx secre^t |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+
+ it('wildoptions=pum', function()
+ screen:try_resize(32,10)
+ command('set wildmenu')
+ command('set wildoptions=pum')
+ command('set shellslash')
+ command("cd test/functional/fixtures/wildpum")
+
+ feed(':sign ')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign ^ |
+ ]])
+
+ feed('<Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+
+ feed('<Right><Right>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{s: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign list^ |
+ ]])
+
+ feed('<C-N>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{s: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign place^ |
+ ]])
+
+ feed('<C-P>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{s: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign list^ |
+ ]])
+
+ feed('<Left>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{s: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign jump^ |
+ ]])
+
+ -- pressing <C-E> should end completion and go back to the original match
+ feed('<C-E>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign ^ |
+ ]])
+
+ -- pressing <C-Y> should select the current match and end completion
+ feed('<Tab><C-P><C-P><C-Y>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign unplace^ |
+ ]])
+
+ -- showing popup menu in different columns in the cmdline
+ feed('<C-U>sign define <Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: culhl= }{1: }|
+ {1:~ }{n: icon= }{1: }|
+ {1:~ }{n: linehl= }{1: }|
+ {1:~ }{n: numhl= }{1: }|
+ {1:~ }{n: text= }{1: }|
+ {1:~ }{n: texthl= }{1: }|
+ :sign define culhl=^ |
+ ]])
+
+ feed('<Space><Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: culhl= }{1: }|
+ {1:~ }{n: icon= }{1: }|
+ {1:~ }{n: linehl= }{1: }|
+ {1:~ }{n: numhl= }{1: }|
+ {1:~ }{n: text= }{1: }|
+ {1:~ }{n: texthl= }{1: }|
+ :sign define culhl= culhl=^ |
+ ]])
+
+ feed('<C-U>e Xnamedi<Tab><Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: XdirA/ }{1: }|
+ {1:~ }{n: XfileA }{1: }|
+ :e Xnamedir/XdirA/^ |
+ ]])
+
+ -- Pressing <Down> on a directory name should go into that directory
+ feed('<Down>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: XdirB/ }{1: }|
+ {1:~ }{n: XfileB }{1: }|
+ :e Xnamedir/XdirA/XdirB/^ |
+ ]])
+
+ -- Pressing <Up> on a directory name should go to the parent directory
+ feed('<Up>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: XdirA/ }{1: }|
+ {1:~ }{n: XfileA }{1: }|
+ :e Xnamedir/XdirA/^ |
+ ]])
+
+ -- Pressing <C-A> when the popup menu is displayed should list all the
+ -- matches and remove the popup menu
+ feed(':<C-U>sign <Tab><C-A>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ :sign define jump list place und|
+ efine unplace^ |
+ ]])
+
+ -- Pressing <Left> after that should move the cursor
+ feed('<Left>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ :sign define jump list place und|
+ efine unplac^e |
+ ]])
+ feed('<End>')
+
+ -- Pressing <C-D> when the popup menu is displayed should remove the popup
+ -- menu
+ feed('<C-U>sign <Tab><C-D>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ :sign define |
+ define |
+ :sign define^ |
+ ]])
+
+ -- Pressing <S-Tab> should open the popup menu with the last entry selected
+ feed('<C-U><CR>:sign <S-Tab><C-P>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]])
+
+ -- Pressing <Esc> should close the popup menu and cancel the cmd line
+ feed('<C-U><CR>:sign <Tab><Esc>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+
+ -- Typing a character when the popup is open, should close the popup
+ feed(':sign <Tab>x')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign definex^ |
+ ]])
+
+ -- When the popup is open, entering the cmdline window should close the popup
+ feed('<C-U>sign <Tab><C-F>')
+ screen:expect([[
+ |
+ {3:[No Name] }|
+ {1::}sign define |
+ {1::}sign defin^e |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4:[Command Line] }|
+ :sign define |
+ ]])
+ feed(':q<CR>')
+
+ -- After the last popup menu item, <C-N> should show the original string
+ feed(':sign u<Tab><C-N><C-N>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign u^ |
+ ]])
+
+ -- Use the popup menu for the command name
+ feed('<C-U>bu<Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {s: bufdo }{1: }|
+ {n: buffer }{1: }|
+ {n: buffers }{1: }|
+ {n: bunload }{1: }|
+ :bufdo^ |
+ ]])
+
+ -- Pressing <BS> should remove the popup menu and erase the last character
+ feed('<C-E><C-U>sign <Tab><BS>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign defin^ |
+ ]])
+
+ -- Pressing <C-W> should remove the popup menu and erase the previous word
+ feed('<C-E><C-U>sign <Tab><C-W>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign ^ |
+ ]])
+
+ -- Pressing <C-U> should remove the popup menu and erase the entire line
+ feed('<C-E><C-U>sign <Tab><C-U>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :^ |
+ ]])
+
+ -- Using <C-E> to cancel the popup menu and then pressing <Up> should recall
+ -- the cmdline from history
+ feed('sign xyz<Esc>:sign <Tab><C-E><Up>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign xyz^ |
+ ]])
+
+ feed('<esc>')
+
+ -- Check "list" still works
+ command('set wildmode=longest,list')
+ feed(':cn<Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ :cn |
+ cnewer cnoreabbrev |
+ cnext cnoremap |
+ cnfile cnoremenu |
+ :cn^ |
+ ]])
+ feed('s')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ :cn |
+ cnewer cnoreabbrev |
+ cnext cnoremap |
+ cnfile cnoremenu |
+ :cns^ |
+ ]])
+
+ feed('<esc>')
+ command('set wildmode=full')
+
+ -- Tests a directory name contained full-width characters.
+ feed(':e あいう/<Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: 123 }{1: }|
+ {1:~ }{n: abc }{1: }|
+ {1:~ }{n: xyz }{1: }|
+ :e あいう/123^ |
+ ]])
+ feed('<Esc>')
+
+ -- Pressing <PageDown> should scroll the menu downward
+ feed(':sign <Tab><PageDown>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]])
+ feed('<PageDown>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{s: unplace }{1: }|
+ :sign unplace^ |
+ ]])
+ feed('<PageDown>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign ^ |
+ ]])
+ feed('<PageDown>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+ feed('<C-U>sign <Tab><Right><Right><PageDown>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{s: unplace }{1: }|
+ :sign unplace^ |
+ ]])
+
+ -- Pressing <PageUp> should scroll the menu upward
+ feed('<C-U>sign <Tab><PageUp>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign ^ |
+ ]])
+ feed('<PageUp>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{s: unplace }{1: }|
+ :sign unplace^ |
+ ]])
+ feed('<PageUp>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{s: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign jump^ |
+ ]])
+ feed('<PageUp>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+
+ -- pressing <C-E> to end completion should work in middle of the line too
+ feed('<Esc>:set wildchazz<Left><Left><Tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: wildchar }{1: }|
+ {1:~ }{n: wildcharm }{1: }|
+ :set wildchar^zz |
+ ]])
+ feed('<C-E>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set wildcha^zz |
+ ]])
+
+ -- pressing <C-Y> should select the current match and end completion
+ feed('<Esc>:set wildchazz<Left><Left><Tab><C-Y>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :set wildchar^zz |
+ ]])
+
+ feed('<Esc>')
+
+ -- check positioning with multibyte char in pattern
+ command("e långfile1")
+ command("sp långfile2")
+ feed(':b lå<tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4:långfile2 }|
+ |
+ {1:~ }|
+ {1:~ }{s: långfile1 }{1: }|
+ {3:lå}{n: långfile2 }{3: }|
+ :b långfile1^ |
+ ]])
+
+ -- check doesn't crash on screen resize
+ screen:try_resize(20,6)
+ screen:expect([[
+ |
+ {1:~ }|
+ {4:långfile2 }|
+ {s: långfile1 } |
+ {3:lå}{n: långfile2 }{3: }|
+ :b långfile1^ |
+ ]])
+
+ screen:try_resize(50,15)
+ screen:expect([[
+ |
+ {1:~ }|
+ {4:långfile2 }|
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: långfile1 }{1: }|
+ {3:lå}{n: långfile2 }{3: }|
+ :b långfile1^ |
+ ]])
+
+ -- position is calculated correctly with "longest"
+ feed('<esc>')
+ command('set wildmode=longest:full,full')
+ feed(':b lå<tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {4:långfile2 }|
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: långfile1 }{1: }|
+ {3:lå}{n: långfile2 }{3: }|
+ :b långfile^ |
+ ]])
+
+ feed('<esc>')
+ command("close")
+ command('set wildmode=full')
+
+ -- special case: when patterns ends with "/", show menu items aligned
+ -- after the "/"
+ feed(':e compdir/<tab>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: file1 }{1: }|
+ {1:~ }{n: file2 }{1: }|
+ :e compdir/file1^ |
+ ]])
+ end)
+
+ it('wildoptions=pum with scrolled messages', function()
+ screen:try_resize(40,10)
+ command('set wildmenu')
+ command('set wildoptions=pum')
+
+ feed(':echoerr "fail"|echoerr "error"<cr>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ {6:fail} |
+ {6:error} |
+ {5:Press ENTER or type command to continue}^ |
+ ]]}
+
+ feed(':sign <tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {4: }{n: place }{4: }|
+ {6:fail} {n: undefine } |
+ {6:error}{n: unplace } |
+ :sign define^ |
+ ]]}
+
+ feed('d')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {4: }|
+ {6:fail} |
+ {6:error} |
+ :sign defined^ |
+ ]]}
+ end)
+
+ it('wildoptions=pum and wildmode=longest,full #11622', function()
+ screen:try_resize(30,8)
+ command('set wildmenu')
+ command('set wildoptions=pum')
+ command('set wildmode=longest,full')
+
+ -- With 'wildmode' set to 'longest,full', completing a match should display
+ -- the longest match, the wildmenu should not be displayed.
+ feed(':sign u<Tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :sign un^ |
+ ]]}
+ eq(0, funcs.wildmenumode())
+
+ -- pressing <Tab> should display the wildmenu
+ feed('<Tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign undefine^ |
+ ]]}
+ eq(1, funcs.wildmenumode())
+
+ -- pressing <Tab> second time should select the next entry in the menu
+ feed('<Tab>')
+ screen:expect{grid=[[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{s: unplace }{1: }|
+ :sign unplace^ |
+ ]]}
+ end)
+
+ it('wildoptions=pum with a wrapped line in buffer vim-patch:8.2.4655', function()
+ screen:try_resize(32, 10)
+ meths.buf_set_lines(0, 0, -1, true, { ('a'):rep(100) })
+ command('set wildoptions+=pum')
+ feed('$')
+ feed(':sign <Tab>')
+ screen:expect([[
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaa {s: define } |
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+ end)
+
+ -- oldtest: Test_wildmenu_pum_odd_wildchar()
+ it('wildoptions=pum with odd wildchar', function()
+ screen:try_resize(30, 10)
+ -- Test odd wildchar interactions with pum. Make sure they behave properly
+ -- and don't lead to memory corruption due to improperly cleaned up memory.
+ exec([[
+ set wildoptions=pum
+ set wildchar=<C-E>
+ ]])
+
+ feed(':sign <C-E>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+
+ -- <C-E> being a wildchar takes priority over its original functionality
+ feed('<C-E>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: define }{1: }|
+ {1:~ }{s: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign jump^ |
+ ]])
+
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+
+ -- Escape key can be wildchar too. Double-<Esc> is hard-coded to escape
+ -- command-line, and we need to make sure to clean up properly.
+ command('set wildchar=<Esc>')
+ feed(':sign <Esc>')
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+
+ -- <C-\> can also be wildchar. <C-\><C-N> however will still escape cmdline
+ -- and we again need to make sure we clean up properly.
+ command([[set wildchar=<C-\>]])
+ feed([[:sign <C-\><C-\>]])
+ screen:expect([[
+ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{s: define }{1: }|
+ {1:~ }{n: jump }{1: }|
+ {1:~ }{n: list }{1: }|
+ {1:~ }{n: place }{1: }|
+ {1:~ }{n: undefine }{1: }|
+ {1:~ }{n: unplace }{1: }|
+ :sign define^ |
+ ]])
+
+ feed('<C-N>')
+ screen:expect([[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ |
+ ]])
+ end)
+ end
- -- oldtest: Test_popup_command_dump()
- it(':popup command', function()
- exec([[
- func ChangeMenu()
- aunmenu PopUp.&Paste
- nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR>
- echomsg 'changed'
- return "\<Ignore>"
- endfunc
-
- let lines =<< trim END
- one two three four five
- and one two Xthree four five
- one more two three four five
- END
- call setline(1, lines)
-
- aunmenu *
- source $VIMRUNTIME/menu.vim
- ]])
- feed('/X<CR>:popup PopUp<CR>')
- screen:expect([[
- one two three four five |
- and one two {7:^X}three four five |
- one more tw{n: Undo } |
- {1:~ }{n: }{1: }|
- {1:~ }{n: Paste }{1: }|
- {1:~ }{n: }{1: }|
- {1:~ }{n: Select Word }{1: }|
- {1:~ }{n: Select Sentence }{1: }|
- {1:~ }{n: Select Paragraph }{1: }|
- {1:~ }{n: Select Line }{1: }|
- {1:~ }{n: Select Block }{1: }|
- {1:~ }{n: Select All }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :popup PopUp |
- ]])
+ it("'pumheight'", function()
+ screen:try_resize(32,8)
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("set linebreak")
+ command("set pumheight=2")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ some long prefix before the ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ {n: word }{c: }|
+ {n: choice}{s: }|
+ ]], float_pos={
+ [4] = {{id = -1}, "NW", 2, 1, 24, false, 100};
+ }}
+ else
+ screen:expect([[
+ some long prefix before the ^ |
+ {1:~ }{n: word }{c: }|
+ {1:~ }{n: choice}{s: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ end)
- -- go to the Paste entry in the menu
- feed('jj')
- screen:expect([[
- one two three four five |
- and one two {7:^X}three four five |
- one more tw{n: Undo } |
- {1:~ }{n: }{1: }|
- {1:~ }{s: Paste }{1: }|
- {1:~ }{n: }{1: }|
- {1:~ }{n: Select Word }{1: }|
- {1:~ }{n: Select Sentence }{1: }|
- {1:~ }{n: Select Paragraph }{1: }|
- {1:~ }{n: Select Line }{1: }|
- {1:~ }{n: Select Block }{1: }|
- {1:~ }{n: Select All }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :popup PopUp |
- ]])
+ it("'pumwidth'", function()
+ screen:try_resize(32,8)
+ feed('isome long prefix before the ')
+ command("set completeopt+=noinsert,noselect")
+ command("set linebreak")
+ command("set pumwidth=8")
+ funcs.complete(29, {'word', 'choice', 'text', 'thing'})
+ if multigrid then
+ screen:expect{grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ some long prefix before the ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ {n: word }|
+ {n: choice}|
+ {n: text }|
+ {n: thing }|
+ ]], float_pos={
+ [4] = {{id = -1}, "NW", 2, 1, 25, false, 100};
+ }}
+ else
+ screen:expect([[
+ some long prefix before the ^ |
+ {1:~ }{n: word }|
+ {1:~ }{n: choice}|
+ {1:~ }{n: text }|
+ {1:~ }{n: thing }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ end)
- -- Select a word
- feed('j')
- screen:expect([[
- one two three four five |
- and one two {7:^X}three four five |
- one more tw{n: Undo } |
- {1:~ }{n: }{1: }|
- {1:~ }{n: Paste }{1: }|
- {1:~ }{n: }{1: }|
- {1:~ }{s: Select Word }{1: }|
- {1:~ }{n: Select Sentence }{1: }|
- {1:~ }{n: Select Paragraph }{1: }|
- {1:~ }{n: Select Line }{1: }|
- {1:~ }{n: Select Block }{1: }|
- {1:~ }{n: Select All }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- :popup PopUp |
- ]])
+ it('does not crash when displayed in the last column with rightleft #12032', function()
+ local col = 30
+ local items = {'word', 'choice', 'text', 'thing'}
+ local max_len = 0
+ for _, v in ipairs(items) do
+ max_len = max_len < #v and #v or max_len
+ end
+ screen:try_resize(col, 8)
+ command('set rightleft')
+ command('call setline(1, repeat(" ", &columns - '..max_len..'))')
+ feed('$i')
+ funcs.complete(col - max_len, items)
+ feed('<c-y>')
+ assert_alive()
+ end)
- feed('<Esc>')
+ it('truncates double-width character correctly without scrollbar', function()
+ screen:try_resize(32, 8)
+ command('set completeopt+=menuone,noselect')
+ feed('i' .. string.rep(' ', 13))
+ funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'})
+ if multigrid then
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ {n: 哦哦哦哦哦哦哦哦哦>}|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 12, false, 100}}})
+ else
+ screen:expect([[
+ ^ |
+ {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ end)
- -- Set an <expr> mapping to change a menu entry while it's displayed.
- -- The text should not change but the command does.
- -- Also verify that "changed" shows up, which means the mapping triggered.
- command('nnoremap <expr> <F2> ChangeMenu()')
- feed('/X<CR>:popup PopUp<CR><F2>')
- screen:expect([[
- one two three four five |
- and one two {7:^X}three four five |
- one more tw{n: Undo } |
- {1:~ }{n: }{1: }|
- {1:~ }{n: Paste }{1: }|
- {1:~ }{n: }{1: }|
- {1:~ }{n: Select Word }{1: }|
- {1:~ }{n: Select Sentence }{1: }|
- {1:~ }{n: Select Paragraph }{1: }|
- {1:~ }{n: Select Line }{1: }|
- {1:~ }{n: Select Block }{1: }|
- {1:~ }{n: Select All }{1: }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- changed |
- ]])
+ it('truncates double-width character correctly with scrollbar', function()
+ screen:try_resize(32,8)
+ command('set completeopt+=noselect')
+ command('set pumheight=4')
+ feed('i' .. string.rep(' ', 12))
+ local items = {}
+ for _ = 1, 8 do
+ table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1})
+ end
+ funcs.complete(13, items)
+ if multigrid then
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ {2:-- INSERT --} |
+ ## grid 4
+ {n: 哦哦哦哦哦哦哦哦哦>}{c: }|
+ {n: 哦哦哦哦哦哦哦哦哦>}{c: }|
+ {n: 哦哦哦哦哦哦哦哦哦>}{s: }|
+ {n: 哦哦哦哦哦哦哦哦哦>}{s: }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}})
+ else
+ screen:expect([[
+ ^ |
+ {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }|
+ {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }|
+ {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }|
+ {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- INSERT --} |
+ ]])
+ end
+ end)
- -- Select the Paste entry, executes the changed menu item.
- feed('jj<CR>')
- screen:expect([[
- one two three four five |
- and one two {7:^X}three four five |
- one more two three four five |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- pasted |
- ]])
- end)
-end)
+ it('supports mousemodel=popup', function()
+ screen:try_resize(32, 6)
+ exec([[
+ call setline(1, 'popup menu test')
+ set mouse=a mousemodel=popup
-describe('builtin popupmenu with ui/ext_multigrid', function()
- local screen
- before_each(function()
- clear()
- screen = Screen.new(32, 20)
- screen:attach({ext_multigrid=true})
- screen:set_default_attr_ids({
- -- popup selected item / scrollbar track
- ['s'] = {background = Screen.colors.WebGray},
- -- popup non-selected item
- ['n'] = {background = Screen.colors.LightMagenta},
- -- popup scrollbar knob
- ['c'] = {background = Screen.colors.Grey0},
- [1] = {bold = true, foreground = Screen.colors.Blue},
- [2] = {bold = true},
- [3] = {reverse = true},
- [4] = {bold = true, reverse = true},
- [5] = {bold = true, foreground = Screen.colors.SeaGreen},
- [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red},
- })
- end)
+ aunmenu PopUp
+ menu PopUp.foo :let g:menustr = 'foo'<CR>
+ menu PopUp.bar :let g:menustr = 'bar'<CR>
+ menu PopUp.baz :let g:menustr = 'baz'<CR>
+ ]])
- it('truncates double-width character correctly when there is no scrollbar', function()
- screen:try_resize(32,8)
- command('set completeopt+=menuone,noselect')
- feed('i' .. string.rep(' ', 13))
- funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'})
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- {2:-- INSERT --} |
- ## grid 4
- {n: 哦哦哦哦哦哦哦哦哦>}|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 12, false, 100}}})
- end)
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 2, 0, 4)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ feed('<RightMouse><4,0>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ |
+ ]])
+ end
+ feed('<Down>')
+ if multigrid then
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {s: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{s: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ |
+ ]])
+ end
+ feed('<Down>')
+ if multigrid then
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ |
+ ## grid 4
+ {n: foo }|
+ {s: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{s: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ |
+ ]])
+ end
+ feed('<CR>')
+ if multigrid then
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ]]})
+ else
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :let g:menustr = 'bar' |
+ ]])
+ end
+ eq('bar', meths.get_var('menustr'))
+
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 2, 2, 20)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 3, 19, false, 250}}})
+ else
+ feed('<RightMouse><20,2>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ :let g:menustr = 'b{n: baz } |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 2)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'baz' |
+ ]]})
+ else
+ feed('<LeftMouse><22,5>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :let g:menustr = 'baz' |
+ ]])
+ end
+ eq('baz', meths.get_var('menustr'))
+
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 2, 0, 4)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'baz' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ feed('<RightMouse><4,0>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'baz' |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('right', 'drag', '', 2, 3, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'baz' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {s: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ feed('<RightDrag><6,3>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{s: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'baz' |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('right', 'release', '', 2, 1, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ]]})
+ else
+ feed('<RightRelease><6,1>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq('foo', meths.get_var('menustr'))
+
+ eq(false, screen.options.mousemoveevent)
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 2, 0, 4)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ feed('<RightMouse><4,0>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{n: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq(true, screen.options.mousemoveevent)
+ if multigrid then
+ meths.input_mouse('move', '', '', 2, 3, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {s: baz }|
+ ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 250}}})
+ else
+ feed('<MouseMove><6,3>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {1:~ }{n: bar }{1: }|
+ {1:~ }{s: baz }{1: }|
+ {1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq(true, screen.options.mousemoveevent)
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 2, 2, 6)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ]]})
+ else
+ feed('<LeftMouse><6,2>')
+ screen:expect([[
+ ^popup menu test |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :let g:menustr = 'bar' |
+ ]])
+ end
+ eq(false, screen.options.mousemoveevent)
+ eq('bar', meths.get_var('menustr'))
+
+ command('set laststatus=0 | botright split')
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 5, 1, 20)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:--------------------------------]|
+ [5:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ## grid 4
+ {n: foo }|
+ {n: bar }|
+ {n: baz }|
+ ## grid 5
+ ^popup menu test |
+ {1:~ }|
+ ]], float_pos={[4] = {{id = -1}, "SW", 5, 1, 19, false, 250}}})
+ else
+ feed('<RightMouse><20,4>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }{n: foo }{1: }|
+ {3:[No Name] [+] }{n: bar }{3: }|
+ ^popup menu test {n: baz } |
+ {1:~ }|
+ :let g:menustr = 'bar' |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 2, 2)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:--------------------------------]|
+ [5:--------------------------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'baz' |
+ ## grid 5
+ ^popup menu test |
+ {1:~ }|
+ ]]})
+ else
+ feed('<LeftMouse><22,3>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }|
+ {3:[No Name] [+] }|
+ ^popup menu test |
+ {1:~ }|
+ :let g:menustr = 'baz' |
+ ]])
+ end
+ eq('baz', meths.get_var('menustr'))
+
+ command('set winwidth=1 | rightbelow vsplit')
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 6, 1, 14)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:---------------]│[6:----------------]|
+ [5:---------------]│[6:----------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'baz' |
+ ## grid 4
+ {n: foo}|
+ {n: bar}|
+ {n: baz}|
+ ## grid 5
+ popup menu test|
+ {1:~ }|
+ ## grid 6
+ ^popup menu test |
+ {1:~ }|
+ ]], float_pos={[4] = {{id = -1}, "SW", 6, 1, 12, false, 250}}})
+ else
+ feed('<RightMouse><30,4>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }{n: foo}|
+ {3:[No Name] [+] }{n: bar}|
+ popup menu test│^popup menu t{n: baz}|
+ {1:~ }│{1:~ }|
+ :let g:menustr = 'baz' |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 0, 2)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:---------------]│[6:----------------]|
+ [5:---------------]│[6:----------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 5
+ popup menu test|
+ {1:~ }|
+ ## grid 6
+ ^popup menu test |
+ {1:~ }|
+ ]]})
+ else
+ feed('<LeftMouse><31,1>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }|
+ {3:[No Name] [+] }|
+ popup menu test│^popup menu test |
+ {1:~ }│{1:~ }|
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ eq('foo', meths.get_var('menustr'))
+
+ command('setlocal winbar=WINBAR')
+ if multigrid then
+ meths.input_mouse('right', 'press', '', 6, 1, 14)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:---------------]│[6:----------------]|
+ [5:---------------]│[6:----------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'foo' |
+ ## grid 4
+ {n: foo}|
+ {n: bar}|
+ {n: baz}|
+ ## grid 5
+ popup menu test|
+ {1:~ }|
+ ## grid 6
+ {2:WINBAR }|
+ ^popup menu test |
+ ]], float_pos={[4] = {{id = -1}, "SW", 6, 1, 12, false, 250}}})
+ else
+ feed('<RightMouse><30,4>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }{n: foo}|
+ {3:[No Name] [+] }{n: bar}|
+ popup menu test│{2:WINBAR }{n: baz}|
+ {1:~ }│^popup menu test |
+ :let g:menustr = 'foo' |
+ ]])
+ end
+ if multigrid then
+ meths.input_mouse('left', 'press', '', 4, 1, 2)
+ screen:expect({grid=[[
+ ## grid 1
+ [2:--------------------------------]|
+ [2:--------------------------------]|
+ {3:[No Name] [+] }|
+ [5:---------------]│[6:----------------]|
+ [5:---------------]│[6:----------------]|
+ [3:--------------------------------]|
+ ## grid 2
+ popup menu test |
+ {1:~ }|
+ ## grid 3
+ :let g:menustr = 'bar' |
+ ## grid 5
+ popup menu test|
+ {1:~ }|
+ ## grid 6
+ {2:WINBAR }|
+ ^popup menu test |
+ ]]})
+ else
+ feed('<LeftMouse><31,2>')
+ screen:expect([[
+ popup menu test |
+ {1:~ }|
+ {3:[No Name] [+] }|
+ popup menu test│{2:WINBAR }|
+ {1:~ }│^popup menu test |
+ :let g:menustr = 'bar' |
+ ]])
+ end
+ eq('bar', meths.get_var('menustr'))
+ end)
- it('truncates double-width character correctly when there is scrollbar', function()
- screen:try_resize(32,8)
- command('set completeopt+=noselect')
- command('set pumheight=4')
- feed('i' .. string.rep(' ', 12))
- local items = {}
- for _ = 1, 8 do
- table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1})
+ if not multigrid then
+ -- oldtest: Test_popup_command_dump()
+ it(':popup command', function()
+ exec([[
+ func ChangeMenu()
+ aunmenu PopUp.&Paste
+ nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR>
+ echomsg 'changed'
+ return "\<Ignore>"
+ endfunc
+
+ let lines =<< trim END
+ one two three four five
+ and one two Xthree four five
+ one more two three four five
+ END
+ call setline(1, lines)
+
+ aunmenu *
+ source $VIMRUNTIME/menu.vim
+ ]])
+ feed('/X<CR>:popup PopUp<CR>')
+ screen:expect([[
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more tw{n: Undo } |
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Paste }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Select Word }{1: }|
+ {1:~ }{n: Select Sentence }{1: }|
+ {1:~ }{n: Select Paragraph }{1: }|
+ {1:~ }{n: Select Line }{1: }|
+ {1:~ }{n: Select Block }{1: }|
+ {1:~ }{n: Select All }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :popup PopUp |
+ ]])
+
+ -- go to the Paste entry in the menu
+ feed('jj')
+ screen:expect([[
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more tw{n: Undo } |
+ {1:~ }{n: }{1: }|
+ {1:~ }{s: Paste }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Select Word }{1: }|
+ {1:~ }{n: Select Sentence }{1: }|
+ {1:~ }{n: Select Paragraph }{1: }|
+ {1:~ }{n: Select Line }{1: }|
+ {1:~ }{n: Select Block }{1: }|
+ {1:~ }{n: Select All }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :popup PopUp |
+ ]])
+
+ -- Select a word
+ feed('j')
+ screen:expect([[
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more tw{n: Undo } |
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Paste }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }{s: Select Word }{1: }|
+ {1:~ }{n: Select Sentence }{1: }|
+ {1:~ }{n: Select Paragraph }{1: }|
+ {1:~ }{n: Select Line }{1: }|
+ {1:~ }{n: Select Block }{1: }|
+ {1:~ }{n: Select All }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :popup PopUp |
+ ]])
+
+ feed('<Esc>')
+
+ -- Set an <expr> mapping to change a menu entry while it's displayed.
+ -- The text should not change but the command does.
+ -- Also verify that "changed" shows up, which means the mapping triggered.
+ command('nnoremap <expr> <F2> ChangeMenu()')
+ feed('/X<CR>:popup PopUp<CR><F2>')
+ screen:expect([[
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more tw{n: Undo } |
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Paste }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Select Word }{1: }|
+ {1:~ }{n: Select Sentence }{1: }|
+ {1:~ }{n: Select Paragraph }{1: }|
+ {1:~ }{n: Select Line }{1: }|
+ {1:~ }{n: Select Block }{1: }|
+ {1:~ }{n: Select All }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ changed |
+ ]])
+
+ -- Select the Paste entry, executes the changed menu item.
+ feed('jj<CR>')
+ screen:expect([[
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more two three four five |
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ pasted |
+ ]])
+
+ -- Add a window toolbar to the window and check the :popup menu position.
+ command('setlocal winbar=TEST')
+ feed('/X<CR>:popup PopUp<CR>')
+ screen:expect([[
+ {2:TEST }|
+ one two three four five |
+ and one two {7:^X}three four five |
+ one more tw{n: Undo } |
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Paste }{1: }|
+ {1:~ }{n: }{1: }|
+ {1:~ }{n: Select Word }{1: }|
+ {1:~ }{n: Select Sentence }{1: }|
+ {1:~ }{n: Select Paragraph }{1: }|
+ {1:~ }{n: Select Line }{1: }|
+ {1:~ }{n: Select Block }{1: }|
+ {1:~ }{n: Select All }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ :popup PopUp |
+ ]])
+
+ feed('<Esc>')
+ end)
+
+ describe('"kind" and "menu"', function()
+ before_each(function()
+ screen:try_resize(30, 8)
+ exec([[
+ func CompleteFunc( findstart, base )
+ if a:findstart
+ return 0
+ endif
+ return {
+ \ 'words': [
+ \ { 'word': 'aword1', 'menu': 'extra text 1', 'kind': 'W', },
+ \ { 'word': 'aword2', 'menu': 'extra text 2', 'kind': 'W', },
+ \ { 'word': 'aword3', 'menu': 'extra text 3', 'kind': 'W', },
+ \]}
+ endfunc
+ set completeopt=menu
+ set completefunc=CompleteFunc
+ ]])
+ end)
+
+ -- oldtest: Test_pum_highlights_default()
+ it('default highlight groups', function()
+ feed('iaw<C-X><C-u>')
+ screen:expect([[
+ aword1^ |
+ {s:aword1 W extra text 1 }{1: }|
+ {n:aword2 W extra text 2 }{1: }|
+ {n:aword3 W extra text 3 }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- }{5:match 1 of 3} |
+ ]])
+ end)
+
+ -- oldtest: Test_pum_highlights_custom()
+ it('custom highlight groups', function()
+ exec([[
+ hi PmenuKind guifg=Red guibg=Magenta
+ hi PmenuKindSel guifg=Red guibg=Grey
+ hi PmenuExtra guifg=White guibg=Magenta
+ hi PmenuExtraSel guifg=Black guibg=Grey
+ ]])
+ local attrs = screen:get_default_attr_ids()
+ attrs.kn = {foreground = Screen.colors.Red, background = Screen.colors.Magenta}
+ attrs.ks = {foreground = Screen.colors.Red, background = Screen.colors.Grey}
+ attrs.xn = {foreground = Screen.colors.White, background = Screen.colors.Magenta}
+ attrs.xs = {foreground = Screen.colors.Black, background = Screen.colors.Grey}
+ feed('iaw<C-X><C-u>')
+ screen:expect([[
+ aword1^ |
+ {s:aword1 }{ks:W }{xs:extra text 1 }{1: }|
+ {n:aword2 }{kn:W }{xn:extra text 2 }{1: }|
+ {n:aword3 }{kn:W }{xn:extra text 3 }{1: }|
+ {1:~ }|
+ {1:~ }|
+ {1:~ }|
+ {2:-- }{5:match 1 of 3} |
+ ]], attrs)
+ end)
+ end)
end
- funcs.complete(13, items)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^ |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- {2:-- INSERT --} |
- ## grid 4
- {n: 哦哦哦哦哦哦哦哦哦>}{c: }|
- {n: 哦哦哦哦哦哦哦哦哦>}{c: }|
- {n: 哦哦哦哦哦哦哦哦哦>}{s: }|
- {n: 哦哦哦哦哦哦哦哦哦>}{s: }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}})
- end)
+ end
- it('supports mousemodel=popup', function()
- screen:try_resize(32, 6)
- exec([[
- call setline(1, 'popup menu test')
- set mouse=a mousemodel=popup
+ describe('with ext_multigrid', function()
+ with_ext_multigrid(true)
+ end)
- aunmenu PopUp
- menu PopUp.foo :let g:menustr = 'foo'<CR>
- menu PopUp.bar :let g:menustr = 'bar'<CR>
- menu PopUp.baz :let g:menustr = 'baz'<CR>
- ]])
- meths.input_mouse('right', 'press', '', 2, 1, 20)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- |
- ## grid 4
- {n: foo }|
- {n: bar }|
- {n: baz }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 2, 19, false, 100}}})
- meths.input_mouse('left', 'press', '', 4, 2, 2)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'baz' |
- ]]})
- eq('baz', meths.get_var('menustr'))
- meths.input_mouse('right', 'press', '', 2, 0, 4)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'baz' |
- ## grid 4
- {n: foo }|
- {n: bar }|
- {n: baz }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
- meths.input_mouse('right', 'drag', '', 2, 3, 6)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'baz' |
- ## grid 4
- {n: foo }|
- {n: bar }|
- {s: baz }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
- meths.input_mouse('right', 'release', '', 2, 1, 6)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'foo' |
- ]]})
- eq('foo', meths.get_var('menustr'))
- eq(false, screen.options.mousemoveevent)
- meths.input_mouse('right', 'press', '', 2, 0, 4)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'foo' |
- ## grid 4
- {n: foo }|
- {n: bar }|
- {n: baz }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
- eq(true, screen.options.mousemoveevent)
- meths.input_mouse('move', '', '', 2, 3, 6)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'foo' |
- ## grid 4
- {n: foo }|
- {n: bar }|
- {s: baz }|
- ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}})
- eq(true, screen.options.mousemoveevent)
- meths.input_mouse('left', 'press', '', 2, 2, 6)
- screen:expect({grid=[[
- ## grid 1
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [2:--------------------------------]|
- [3:--------------------------------]|
- ## grid 2
- ^popup menu test |
- {1:~ }|
- {1:~ }|
- {1:~ }|
- {1:~ }|
- ## grid 3
- :let g:menustr = 'bar' |
- ]]})
- eq(false, screen.options.mousemoveevent)
- eq('bar', meths.get_var('menustr'))
+ describe('without ext_multigrid', function()
+ with_ext_multigrid(false)
end)
end)
diff --git a/test/functional/ui/quickfix_spec.lua b/test/functional/ui/quickfix_spec.lua
index b0d89ee3b6..df43871e60 100644
--- a/test/functional/ui/quickfix_spec.lua
+++ b/test/functional/ui/quickfix_spec.lua
@@ -27,7 +27,7 @@ describe('quickfix selection highlight', function()
[12] = {foreground = Screen.colors.Brown, background = Screen.colors.Fuchsia},
})
- meths.set_option('errorformat', '%m %l')
+ meths.set_option_value('errorformat', '%m %l', {})
command('syntax on')
command('highlight Search guibg=Green')
diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua
index 3b9cce0e6f..810a68d387 100644
--- a/test/functional/ui/screen.lua
+++ b/test/functional/ui/screen.lua
@@ -75,6 +75,7 @@ local busted = require('busted')
local deepcopy = helpers.deepcopy
local shallowcopy = helpers.shallowcopy
local concat_tables = helpers.concat_tables
+local pesc = helpers.pesc
local run_session = helpers.run_session
local eq = helpers.eq
local dedent = helpers.dedent
@@ -87,6 +88,7 @@ local function isempty(v)
return type(v) == 'table' and next(v) == nil
end
+--- @class test.functional.ui.screen
local Screen = {}
Screen.__index = Screen
@@ -149,6 +151,7 @@ function Screen.new(width, height)
msg_grid = nil,
msg_grid_pos = nil,
_session = nil,
+ rpc_async = false,
messages = {},
msg_history = {},
showmode = {},
@@ -172,9 +175,13 @@ function Screen.new(width, height)
_busy = false,
}, Screen)
local function ui(method, ...)
- local status, rv = self._session:request('nvim_ui_'..method, ...)
- if not status then
- error(rv[2])
+ if self.rpc_async then
+ self._session:notify('nvim_ui_'..method, ...)
+ else
+ local status, rv = self._session:request('nvim_ui_'..method, ...)
+ if not status then
+ error(rv[2])
+ end
end
end
self.uimeths = create_callindex(ui)
@@ -214,7 +221,7 @@ function Screen:attach(options, session)
-- simplify test code by doing the same.
self._options.rgb = true
end
- if self._options.ext_multigrid or self._options.ext_float then
+ if self._options.ext_multigrid then
self._options.ext_linegrid = true
end
end
@@ -257,7 +264,7 @@ local ext_keys = {
-- grid: Expected screen state (string). Each line represents a screen
-- row. Last character of each row (typically "|") is stripped.
-- Common indentation is stripped.
--- "{MATCH:x}|" lines are matched against Lua pattern `x`.
+-- "{MATCH:x}" in a line is matched against Lua pattern `x`.
-- attr_ids: Expected text attributes. Screen rows are transformed according
-- to this table, as follows: each substring S composed of
-- characters having the same attributes will be substituted by
@@ -382,8 +389,20 @@ function Screen:expect(expected, attr_ids, ...)
end
for i, row in ipairs(expected_rows) do
msg_expected_rows[i] = row
- local m = (row ~= actual_rows[i] and row:match('{MATCH:(.*)}') or nil)
- if row ~= actual_rows[i] and (not m or not (actual_rows[i] and actual_rows[i]:match(m))) then
+ local pat = nil
+ if actual_rows[i] and row ~= actual_rows[i] then
+ local after = row
+ while true do
+ local s, e, m = after:find('{MATCH:(.-)}')
+ if not s then
+ pat = pat and (pat .. pesc(after))
+ break
+ end
+ pat = (pat or '') .. pesc(after:sub(1, s - 1)) .. m
+ after = after:sub(e + 1)
+ end
+ end
+ if row ~= actual_rows[i] and (not pat or not actual_rows[i]:match(pat)) then
msg_expected_rows[i] = '*' .. msg_expected_rows[i]
if i <= #actual_rows then
actual_rows[i] = '*' .. actual_rows[i]
@@ -470,15 +489,19 @@ screen:redraw_debug() to show all intermediate screen states. ]])
end, expected)
end
-function Screen:expect_unchanged(waittime_ms, ignore_attrs, request_cb)
+function Screen:expect_unchanged(intermediate, waittime_ms, ignore_attrs)
waittime_ms = waittime_ms and waittime_ms or 100
-- Collect the current screen state.
- self:sleep(0, request_cb)
local kwargs = self:get_snapshot(nil, ignore_attrs)
- -- Check that screen state does not change.
- kwargs.unchanged = true
+ if intermediate then
+ kwargs.intermediate = true
+ else
+ kwargs.unchanged = true
+ end
+
kwargs.timeout = waittime_ms
+ -- Check that screen state does not change.
self:expect(kwargs)
end
@@ -540,6 +563,7 @@ function Screen:_wait(check, flags)
self._session:stop()
end
elseif success_seen and #args > 0 then
+ success_seen = false
failure_after_success = true
-- print(inspect(args))
end
@@ -785,14 +809,17 @@ function Screen:_handle_win_pos(grid, win, startrow, startcol, width, height)
self.float_pos[grid] = nil
end
-function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol, linecount)
+function Screen:_handle_win_viewport(grid, win, topline, botline, curline, curcol, linecount, scroll_delta)
+ -- accumulate scroll delta
+ local last_scroll_delta = self.win_viewport[grid] and self.win_viewport[grid].sum_scroll_delta or 0
self.win_viewport[grid] = {
win = win,
topline = topline,
botline = botline,
curline = curline,
curcol = curcol,
- linecount = linecount
+ linecount = linecount,
+ sum_scroll_delta = scroll_delta + last_scroll_delta
}
end
@@ -926,6 +953,7 @@ end
function Screen:_handle_grid_line(grid, row, col, items)
assert(self._options.ext_linegrid)
+ assert(#items > 0)
local line = self._grids[grid].rows[row+1]
local colpos = col+1
local hl_id = 0
@@ -1248,7 +1276,7 @@ end
function Screen:render(headers, attr_state, preview)
headers = headers and (self._options.ext_multigrid or self._options._debug_float)
local rv = {}
- for igrid,grid in pairs(self._grids) do
+ for igrid,grid in vim.spairs(self._grids) do
if headers then
local suffix = ""
if igrid > 1 and self.win_position[igrid] == nil
@@ -1330,7 +1358,7 @@ local function fmt_ext_state(name, state)
for k,v in pairs(state) do
str = (str.." ["..k.."] = {win = {id = "..v.win.id.."}, topline = "
..v.topline..", botline = "..v.botline..", curline = "..v.curline
- ..", curcol = "..v.curcol..", linecount = "..v.linecount.."};\n")
+ ..", curcol = "..v.curcol..", linecount = "..v.linecount..", sum_scroll_delta = "..v.sum_scroll_delta.."};\n")
end
return str .. "}"
elseif name == "float_pos" then
diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua
index 3bd2289a73..7cc1accd3f 100644
--- a/test/functional/ui/screen_basic_spec.lua
+++ b/test/functional/ui/screen_basic_spec.lua
@@ -4,9 +4,7 @@ local spawn, set_session, clear = helpers.spawn, helpers.set_session, helpers.cl
local feed, command = helpers.feed, helpers.command
local insert = helpers.insert
local eq = helpers.eq
-local eval = helpers.eval
-local funcs, meths, exec_lua = helpers.funcs, helpers.meths, helpers.exec_lua
-local is_os = helpers.is_os
+local funcs, meths = helpers.funcs, helpers.meths
describe('screen', function()
local screen
@@ -61,37 +59,10 @@ local function screen_tests(linegrid)
[5] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Fuchsia},
[6] = {bold = true, foreground = Screen.colors.Fuchsia},
[7] = {bold = true, foreground = Screen.colors.SeaGreen},
+ [8] = {foreground = Screen.colors.White, background = Screen.colors.Red},
} )
end)
- describe(':suspend', function()
- it('is forwarded to the UI', function()
- local function check()
- eq(true, screen.suspended)
- end
-
- command('let g:ev = []')
- command('autocmd VimResume * :call add(g:ev, "r")')
- command('autocmd VimSuspend * :call add(g:ev, "s")')
-
- eq(false, screen.suspended)
- command('suspend')
- eq({ 's', 'r' }, eval('g:ev'))
-
- screen:expect(check)
- screen.suspended = false
-
- feed('<c-z>')
- eq({ 's', 'r', 's', 'r' }, eval('g:ev'))
-
- screen:expect(check)
- screen.suspended = false
-
- command('suspend')
- eq({ 's', 'r', 's', 'r', 's', 'r' }, eval('g:ev'))
- end)
- end)
-
describe('bell/visual bell', function()
it('is forwarded to the UI', function()
feed('<left>')
@@ -117,78 +88,13 @@ local function screen_tests(linegrid)
screen:expect(function()
eq(expected, screen.title)
end)
- end)
-
- it('has correct default title with unnamed file', function()
- local expected = '[No Name] - NVIM'
- command('set title')
- screen:expect(function()
- eq(expected, screen.title)
- end)
- end)
-
- it('has correct default title with named file', function()
- local expected = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM')
- command('set title')
- command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile')
+ screen:detach()
+ screen.title = nil
+ screen:attach()
screen:expect(function()
eq(expected, screen.title)
end)
end)
-
- describe('is not changed by', function()
- local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1'
- local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2'
- local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM')
- local buf2
-
- before_each(function()
- command('edit '..file1)
- buf2 = funcs.bufadd(file2)
- command('set title')
- end)
-
- it('calling setbufvar() to set an option in a hidden buffer from i_CTRL-R', function()
- command([[inoremap <F2> <C-R>=setbufvar(]]..buf2..[[, '&autoindent', 1) ? '' : ''<CR>]])
- feed('i<F2><Esc>')
- command('redraw!')
- screen:expect(function()
- eq(expected, screen.title)
- end)
- end)
-
- it('an RPC call to nvim_buf_set_option in a hidden buffer', function()
- meths.buf_set_option(buf2, 'autoindent', true)
- command('redraw!')
- screen:expect(function()
- eq(expected, screen.title)
- end)
- end)
-
- it('a Lua callback calling nvim_buf_set_option in a hidden buffer', function()
- exec_lua(string.format([[
- vim.schedule(function()
- vim.api.nvim_buf_set_option(%d, 'autoindent', true)
- end)
- ]], buf2))
- command('redraw!')
- screen:expect(function()
- eq(expected, screen.title)
- end)
- end)
-
- it('a Lua callback calling nvim_buf_call in a hidden buffer', function()
- exec_lua(string.format([[
- vim.schedule(function()
- vim.api.nvim_buf_call(%d, function() end)
- end)
- ]], buf2))
- command('redraw!')
- screen:expect(function()
- eq(expected, screen.title)
- end)
- end)
- end)
end)
describe(':set icon', function()
@@ -199,6 +105,12 @@ local function screen_tests(linegrid)
screen:expect(function()
eq(expected, screen.icon)
end)
+ screen:detach()
+ screen.icon = nil
+ screen:attach()
+ screen:expect(function()
+ eq(expected, screen.icon)
+ end)
end)
end)
@@ -866,12 +778,9 @@ local function screen_tests(linegrid)
end)
describe('resize', function()
- before_each(function()
+ it('rebuilds the whole screen', function()
screen:try_resize(25, 5)
feed('iresize')
- end)
-
- it('rebuilds the whole screen', function()
screen:expect([[
resize^ |
{0:~ }|
@@ -882,10 +791,11 @@ local function screen_tests(linegrid)
end)
it('has minimum width/height values', function()
+ feed('iresize')
screen:try_resize(1, 1)
screen:expect([[
resize^ |
- {2:-- INSERT -} |
+ {2:-- INSERT --}|
]])
feed('<esc>:ls')
@@ -896,11 +806,12 @@ local function screen_tests(linegrid)
end)
it('VimResized autocommand does not cause invalid UI events #20692 #20759', function()
- feed('<Esc>')
+ screen:try_resize(25, 5)
+ feed('iresize<Esc>')
command([[autocmd VimResized * redrawtabline]])
command([[autocmd VimResized * lua vim.api.nvim_echo({ { 'Hello' } }, false, {})]])
command([[autocmd VimResized * let g:echospace = v:echospace]])
- meths.set_option('showtabline', 2)
+ meths.set_option_value('showtabline', 2, {})
screen:expect([[
{2: + [No Name] }{3: }|
resiz^e |
@@ -919,6 +830,77 @@ local function screen_tests(linegrid)
]])
eq(29, meths.get_var('echospace'))
end)
+
+ it('messages from the same Ex command as resize are visible #22225', function()
+ feed(':set columns=20 | call<CR>')
+ screen:expect([[
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ {1: }|
+ {8:E471: Argument requi}|
+ {8:red} |
+ {7:Press ENTER or type }|
+ {7:command to continue}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ feed(':set columns=0<CR>')
+ screen:expect([[
+ |
+ |
+ |
+ |
+ |
+ {1: }|
+ {8:E594: Need a}|
+ {8:t least 12 c}|
+ {8:olumns: colu}|
+ {8:mns=0} |
+ {7:Press ENTER }|
+ {7:or type comm}|
+ {7:and to conti}|
+ {7:nue}^ |
+ ]])
+ feed('<CR>')
+ screen:expect([[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ end)
end)
describe('press enter', function()
@@ -1057,8 +1039,8 @@ it('CTRL-F or CTRL-B scrolls a page after UI attach/resize #20605', function()
clear()
local screen = Screen.new(100, 100)
screen:attach()
- eq(100, meths.get_option('lines'))
- eq(99, meths.get_option('window'))
+ eq(100, meths.get_option_value('lines', {}))
+ eq(99, meths.get_option_value('window', {}))
eq(99, meths.win_get_height(0))
feed('1000o<Esc>')
eq(903, funcs.line('w0'))
@@ -1072,8 +1054,8 @@ it('CTRL-F or CTRL-B scrolls a page after UI attach/resize #20605', function()
eq(903, funcs.line('w0'))
feed('G')
screen:try_resize(50, 50)
- eq(50, meths.get_option('lines'))
- eq(49, meths.get_option('window'))
+ eq(50, meths.get_option_value('lines', {}))
+ eq(49, meths.get_option_value('window', {}))
eq(49, meths.win_get_height(0))
eq(953, funcs.line('w0'))
feed('<C-B>')
@@ -1085,3 +1067,18 @@ it('CTRL-F or CTRL-B scrolls a page after UI attach/resize #20605', function()
feed('<C-F>')
eq(953, funcs.line('w0'))
end)
+
+it("showcmd doesn't cause empty grid_line with redrawdebug=compositor #22593", function()
+ clear()
+ local screen = Screen.new(30, 2)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue},
+ })
+ screen:attach()
+ command('set showcmd redrawdebug=compositor')
+ feed('d')
+ screen:expect{grid=[[
+ ^ |
+ d |
+ ]]}
+end)
diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua
index 18bbb56a61..dc7ef666bd 100644
--- a/test/functional/ui/searchhl_spec.lua
+++ b/test/functional/ui/searchhl_spec.lua
@@ -10,7 +10,6 @@ local testprg = helpers.testprg
describe('search highlighting', function()
local screen
- local colors = Screen.colors
before_each(function()
clear()
@@ -18,9 +17,10 @@ describe('search highlighting', function()
screen:attach()
screen:set_default_attr_ids( {
[1] = {bold=true, foreground=Screen.colors.Blue},
- [2] = {background = colors.Yellow}, -- Search
+ [2] = {background = Screen.colors.Yellow}, -- Search
[3] = {reverse = true},
- [4] = {foreground = colors.Red}, -- Message
+ [4] = {foreground = Screen.colors.Red}, -- WarningMsg
+ [5] = {bold = true, reverse = true}, -- StatusLine
[6] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey}, -- Folded
})
end)
@@ -53,11 +53,11 @@ describe('search highlighting', function()
{1:~ }|
/text^ |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 9, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 8, linecount = 2, sum_scroll_delta = 0};
}}
end)
- it('works', function()
+ local function test_search_hl()
insert([[
some text
more textstuff
@@ -110,6 +110,26 @@ describe('search highlighting', function()
{1:~ }|
:nohlsearch |
]])
+ end
+
+ it("works when 'winhighlight' is not set", function()
+ test_search_hl()
+ end)
+
+ it("works when 'winhighlight' doesn't change Search highlight", function()
+ command('setlocal winhl=NonText:Underlined')
+ local attrs = screen:get_default_attr_ids()
+ attrs[1] = {foreground = Screen.colors.SlateBlue, underline = true}
+ screen:set_default_attr_ids(attrs)
+ test_search_hl()
+ end)
+
+ it("works when 'winhighlight' changes Search highlight", function()
+ command('setlocal winhl=Search:Underlined')
+ local attrs = screen:get_default_attr_ids()
+ attrs[2] = {foreground = Screen.colors.SlateBlue, underline = true}
+ screen:set_default_attr_ids(attrs)
+ test_search_hl()
end)
describe('CurSearch highlight', function()
@@ -307,18 +327,33 @@ describe('search highlighting', function()
it('is preserved during :terminal activity', function()
feed((':terminal "%s" REP 5000 foo<cr>'):format(testprg('shell-test')))
-
feed(':file term<CR>')
+ screen:expect([[
+ ^0: foo |
+ 1: foo |
+ 2: foo |
+ 3: foo |
+ 4: foo |
+ 5: foo |
+ :file term |
+ ]])
+
feed('G') -- Follow :terminal output.
feed(':vnew<CR>')
insert([[
foo bar baz
bar baz foo
- bar foo baz
- ]])
+ bar foo baz]])
feed('/foo')
- helpers.poke_eventloop()
- screen:expect_unchanged()
+ screen:expect([[
+ {3:foo} bar baz │{MATCH:%d+}: {2:foo}{MATCH:%s+}|
+ bar baz {2:foo} │{MATCH:%d+}: {2:foo}{MATCH:%s+}|
+ bar {2:foo} baz │{MATCH:%d+}: {2:foo}{MATCH:%s+}|
+ {1:~ }│{MATCH:.*}|
+ {1:~ }│{MATCH:.*}|
+ {5:[No Name] [+] }{3:term }|
+ /foo^ |
+ ]])
end)
it('works with incsearch', function()
@@ -498,6 +533,50 @@ describe('search highlighting', function()
{1:~ }│{1:~ }|
//^ |
]])
+ feed('<Esc>')
+
+ -- incsearch works after c_CTRL-R_CTRL-R
+ command('let @" = "file"')
+ feed('/<C-R><C-R>"')
+ screen:expect([[
+ the first line │the first line |
+ in a little {3:file} │in a little {2:file} |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ /file^ |
+ ]])
+ feed('<Esc>')
+
+ command('set rtp^=test/functional/fixtures')
+ -- incsearch works after c_CTRL-R inserts clipboard register
+
+ command('let @* = "first"')
+ feed('/<C-R>*')
+ screen:expect([[
+ the {3:first} line │the {2:first} line |
+ in a little file │in a little file |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ /first^ |
+ ]])
+ feed('<Esc>')
+
+ command('let @+ = "little"')
+ feed('/<C-R>+')
+ screen:expect([[
+ the first line │the first line |
+ in a {3:little} file │in a {2:little} file |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ /little^ |
+ ]])
+ feed('<Esc>')
end)
it('works with incsearch and offset', function()
@@ -572,12 +651,12 @@ describe('search highlighting', function()
it('works with matchadd and syntax', function()
screen:set_default_attr_ids {
[1] = {bold=true, foreground=Screen.colors.Blue};
- [2] = {background = colors.Yellow};
+ [2] = {background = Screen.colors.Yellow};
[3] = {reverse = true};
- [4] = {foreground = colors.Red};
- [5] = {bold = true, background = colors.Green};
- [6] = {italic = true, background = colors.Magenta};
- [7] = {bold = true, background = colors.Yellow};
+ [4] = {foreground = Screen.colors.Red};
+ [5] = {bold = true, background = Screen.colors.Green};
+ [6] = {italic = true, background = Screen.colors.Magenta};
+ [7] = {bold = true, background = Screen.colors.Yellow};
[8] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGray};
}
feed_command('set hlsearch')
@@ -602,7 +681,7 @@ describe('search highlighting', function()
{1:~ }|
{4:search hit BOTTOM, continuing at TOP} |
]], win_viewport={
- [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 11, linecount = 2};
+ [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 11, linecount = 2, sum_scroll_delta = 0};
}}
-- check highlights work also in folds
diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua
index 7dcd4cff25..b12e79bd42 100644
--- a/test/functional/ui/sign_spec.lua
+++ b/test/functional/ui/sign_spec.lua
@@ -274,9 +274,9 @@ describe('Signs', function()
-- Line 3 checks that with a limit over the maximum number
-- of signs, the ones with the highest Ids are being picked,
-- and presented by their sorted Id order.
- command('sign place 4 line=3 name=pietSearch buffer=1')
- command('sign place 5 line=3 name=pietWarn buffer=1')
- command('sign place 3 line=3 name=pietError buffer=1')
+ command('sign place 6 line=3 name=pietSearch buffer=1')
+ command('sign place 7 line=3 name=pietWarn buffer=1')
+ command('sign place 5 line=3 name=pietError buffer=1')
screen:expect([[
{1:>>}{8:XX}{6: 1 }a |
{8:XX}{1:>>}{6: 2 }b |
@@ -467,6 +467,27 @@ describe('Signs', function()
{0:~ }|
|
]])
+ -- should not increase size because sign with existing id is moved
+ command('sign place 4 line=1 name=pietSearch buffer=1')
+ screen:expect_unchanged()
+ command('sign unplace 4')
+ screen:expect([[
+ {1:>>>>>>}{6: 1 }a |
+ {2: }{6: 2 }b |
+ {2: }{6: 3 }c |
+ {2: }{6: 4 }^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ command('sign place 4 line=1 name=pietSearch buffer=1')
-- should keep the column at maximum size when signs are
-- exceeding the maximum
command('sign place 5 line=1 name=pietSearch buffer=1')
@@ -685,4 +706,21 @@ describe('Signs', function()
|
]])
end)
+
+ it('numhl highlight is applied when signcolumn=no', function()
+ screen:try_resize(screen._width, 4)
+ command([[
+ set nu scl=no
+ call setline(1, ['line1', 'line2', 'line3'])
+ call nvim_buf_set_extmark(0, nvim_create_namespace('test'), 0, 0, {'number_hl_group':'Error'})
+ call sign_define('foo', { 'text':'F', 'numhl':'Error' })
+ call sign_place(0, '', 'foo', bufnr(''), { 'lnum':2 })
+ ]])
+ screen:expect([[
+ {8: 1 }^line1 |
+ {8: 2 }line2 |
+ {6: 3 }line3 |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/spell_spec.lua b/test/functional/ui/spell_spec.lua
index 361f83d1ce..d18e19e5b2 100644
--- a/test/functional/ui/spell_spec.lua
+++ b/test/functional/ui/spell_spec.lua
@@ -3,9 +3,11 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local exec = helpers.exec
local feed = helpers.feed
local insert = helpers.insert
-local command = helpers.command
+local meths = helpers.meths
+local curbufmeths = helpers.curbufmeths
local is_os = helpers.is_os
describe("'spell'", function()
@@ -18,17 +20,21 @@ describe("'spell'", function()
screen:set_default_attr_ids( {
[0] = {bold=true, foreground=Screen.colors.Blue},
[1] = {special = Screen.colors.Red, undercurl = true},
- [2] = {special = Screen.colors.Blue1, undercurl = true},
+ [2] = {special = Screen.colors.Blue, undercurl = true},
[3] = {foreground = tonumber('0x6a0dad')},
[4] = {foreground = Screen.colors.Magenta},
[5] = {bold = true, foreground = Screen.colors.SeaGreen},
[6] = {foreground = Screen.colors.Red},
+ [7] = {foreground = Screen.colors.Blue},
+ [8] = {foreground = Screen.colors.Blue, special = Screen.colors.Red, undercurl = true},
+ [9] = {bold = true},
+ [10] = {background = Screen.colors.LightGrey, foreground = Screen.colors.DarkBlue},
})
end)
it('joins long lines #7937', function()
if is_os('openbsd') then pending('FIXME #12104', function() end) return end
- command('set spell')
+ exec('set spell')
insert([[
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
@@ -53,114 +59,374 @@ describe("'spell'", function()
-- oldtest: Test_spell_screendump()
it('has correct highlight at start of line', function()
- insert([[
- "This is some text without any spell errors. Everything",
- "should just be black, nothing wrong here.",
- "",
- "This line has a sepll error. and missing caps.",
- "And and this is the the duplication.",
- "with missing caps here.",
- ]])
- command('set spell spelllang=en_nz')
- screen:expect([[
- "This is some text without any spell errors. Everything", |
- "should just be black, nothing wrong here.", |
- "", |
- "This line has a {1:sepll} error. {2:and} missing caps.", |
- "{1:And and} this is {1:the the} duplication.", |
- "with missing caps here.", |
- ^ |
- |
+ exec([=[
+ call setline(1, [
+ \"This is some text without any spell errors. Everything",
+ \"should just be black, nothing wrong here.",
+ \"",
+ \"This line has a sepll error. and missing caps.",
+ \"And and this is the the duplication.",
+ \"with missing caps here.",
+ \])
+ set spell spelllang=en_nz
+ ]=])
+ screen:expect([[
+ ^This is some text without any spell errors. Everything |
+ should just be black, nothing wrong here. |
+ |
+ This line has a {1:sepll} error. {2:and} missing caps. |
+ {1:And and} this is {1:the the} duplication. |
+ {2:with} missing caps here. |
+ {0:~ }|
+ |
]])
end)
- it('"noplainbuffer" and syntax #20385', function()
- command('set filetype=c')
- command('syntax on')
- command('set spell')
+ -- oldtest: Test_spell_screendump_spellcap()
+ it('SpellCap highlight at start of line', function()
+ exec([=[
+ call setline(1, [
+ \" This line has a sepll error. and missing caps and trailing spaces. ",
+ \"another missing cap here.",
+ \"",
+ \"and here.",
+ \" ",
+ \"and here."
+ \])
+ set spell spelllang=en
+ ]=])
+ screen:expect([[
+ ^ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap here. |
+ |
+ {2:and} here. |
+ |
+ {2:and} here. |
+ {0:~ }|
+ |
+ ]])
+ -- After adding word missing Cap in next line is updated
+ feed('3GANot<Esc>')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap here. |
+ No^t |
+ and here. |
+ |
+ {2:and} here. |
+ {0:~ }|
+ |
+ ]])
+ -- Deleting a full stop removes missing Cap in next line
+ feed('5Gdd<C-L>k$x')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap here. |
+ Not |
+ and her^e |
+ and here. |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Undo also updates the next line (go to command line to remove message)
+ feed('u:<Esc>')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap here. |
+ Not |
+ and here^. |
+ {2:and} here. |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Folding an empty line does not remove Cap in next line
+ feed('uzfk:<Esc>')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap here. |
+ Not |
+ {10:^+-- 2 lines: and here.·························································}|
+ {2:and} here. |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Folding the end of a sentence does not remove Cap in next line
+ -- and editing a line does not remove Cap in current line
+ feed('Jzfkk$x')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ {2:another} missing cap her^e |
+ {10:+-- 2 lines: Not·······························································}|
+ {2:and} here. |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Cap is correctly applied in the first row of a window
+ feed('<C-E><C-L>')
+ screen:expect([[
+ {2:another} missing cap her^e |
+ {10:+-- 2 lines: Not·······························································}|
+ {2:and} here. |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Adding an empty line does not remove Cap in "mod_bot" area
+ feed('zbO<Esc>')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ ^ |
+ {2:another} missing cap here |
+ {10:+-- 2 lines: Not·······························································}|
+ {2:and} here. |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Multiple empty lines does not remove Cap in the line after
+ feed('O<Esc><C-L>')
+ screen:expect([[
+ This line has a {1:sepll} error. {2:and} missing caps and trailing spaces. |
+ ^ |
+ |
+ {2:another} missing cap here |
+ {10:+-- 2 lines: Not·······························································}|
+ {2:and} here. |
+ {0:~ }|
+ |
+ ]])
+ end)
+
+ -- oldtest: Test_spell_compatible()
+ it([[redraws properly when using "C" and "$" is in 'cpo']], function()
+ exec([=[
+ call setline(1, [
+ \ "test "->repeat(20),
+ \ "",
+ \ "end",
+ \ ])
+ set spell cpo+=$
+ ]=])
+ feed('51|C')
+ screen:expect([[
+ {2:test} test test test test test test test test test ^test test test test test test |
+ test test test test$ |
+ |
+ {2:end} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {9:-- INSERT --} |
+ ]])
+ feed('x')
+ screen:expect([[
+ {2:test} test test test test test test test test test x^est test test test test test |
+ test test test test$ |
+ |
+ {2:end} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {9:-- INSERT --} |
+ ]])
+ end)
+
+ it('extmarks, "noplainbuffer" and syntax #20385 #23398', function()
+ exec('set filetype=c')
+ exec('syntax on')
+ exec('set spell')
insert([[
#include <stdbool.h>
- bool func(void);]])
+ bool func(void);
+ // I am a speling mistakke]])
+ feed('ge')
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:spelin^g}{7: }{8:mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ feed(']s')
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:speling}{7: }{8:^mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ feed(']s')
screen:expect([[
{3:#include }{4:<stdbool.h>} |
- {5:bool} func({5:void})^; |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:^speling}{7: }{8:mistakke} |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
+ {6:search hit BOTTOM, continuing at TOP} |
+ ]])
+ exec('echo ""')
+ local ns = meths.create_namespace("spell")
+ -- extmark with spell=true enables spell
+ local id = curbufmeths.set_extmark(ns, 1, 4, { end_row = 1, end_col = 10, spell = true })
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} {1:func}({5:void}); |
+ {7:// I am a }{8:^speling}{7: }{8:mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
{0:~ }|
|
]])
feed('[s')
screen:expect([[
{3:#include }{4:<stdbool.h>} |
- {5:bool} func({5:void})^; |
+ {5:bool} {1:^func}({5:void}); |
+ {7:// I am a }{8:speling}{7: }{8:mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ curbufmeths.del_extmark(ns, id)
+ -- extmark with spell=false disables spell
+ id = curbufmeths.set_extmark(ns, 2, 18, { end_row = 2, end_col = 26, spell = false })
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} ^func({5:void}); |
+ {7:// I am a }{8:speling}{7: mistakke} |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
+ |
+ ]])
+ feed('[s')
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:^speling}{7: mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
{0:~ }|
{6:search hit TOP, continuing at BOTTOM} |
]])
- -- "noplainbuffer" shouldn't change spellchecking behavior with syntax enabled
- command('set spelloptions+=noplainbuffer')
- screen:expect_unchanged()
+ exec('echo ""')
+ curbufmeths.del_extmark(ns, id)
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:^speling}{7: }{8:mistakke} |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
feed(']s')
screen:expect([[
{3:#include }{4:<stdbool.h>} |
- {5:bool} func({5:void})^; |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:speling}{7: }{8:^mistakke} |
+ {0:~ }|
+ {0:~ }|
{0:~ }|
{0:~ }|
+ |
+ ]])
+ -- "noplainbuffer" shouldn't change spellchecking behavior with syntax enabled
+ exec('set spelloptions+=noplainbuffer')
+ screen:expect_unchanged()
+ feed('[s')
+ screen:expect([[
+ {3:#include }{4:<stdbool.h>} |
+ {5:bool} func({5:void}); |
+ {7:// I am a }{8:^speling}{7: }{8:mistakke} |
+ {0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
- {6:search hit BOTTOM, continuing at TOP} |
+ |
]])
-- no spellchecking with "noplainbuffer" and syntax disabled
- command('syntax off')
+ exec('syntax off')
screen:expect([[
#include <stdbool.h> |
- bool func(void)^; |
+ bool func(void); |
+ // I am a ^speling mistakke |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
- {0:~ }|
- {6:search hit BOTTOM, continuing at TOP} |
+ |
]])
- feed('[s')
+ feed(']s')
screen:expect([[
#include <stdbool.h> |
- bool func(void)^; |
+ bool func(void); |
+ // I am a ^speling mistakke |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
- {0:~ }|
- {6:search hit TOP, continuing at BOTTOM} |
+ {6:search hit BOTTOM, continuing at TOP} |
]])
+ exec('echo ""')
-- everything is spellchecked without "noplainbuffer" with syntax disabled
- command('set spelloptions&')
+ exec('set spelloptions&')
screen:expect([[
#include <{1:stdbool}.h> |
- {1:bool} {1:func}(void)^; |
- {0:~ }|
+ {1:bool} {1:func}(void); |
+ // I am a {1:^speling} {1:mistakke} |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
- {6:search hit TOP, continuing at BOTTOM} |
+ |
]])
- feed(']s')
+ feed('[s')
screen:expect([[
- #include <{1:^stdbool}.h> |
- {1:bool} {1:func}(void); |
- {0:~ }|
+ #include <{1:stdbool}.h> |
+ {1:bool} {1:^func}(void); |
+ // I am a {1:speling} {1:mistakke} |
{0:~ }|
{0:~ }|
{0:~ }|
{0:~ }|
- {6:search hit BOTTOM, continuing at TOP} |
+ |
+ ]])
+ end)
+
+ it('and syntax does not clear extmark highlighting at the start of a word', function()
+ screen:try_resize(43, 3)
+ exec([[
+ set spell
+ syntax match Constant "^.*$"
+ call setline(1, "This is some text without any spell errors.")
+ ]])
+ local ns = meths.create_namespace("spell")
+ curbufmeths.set_extmark(ns, 0, 0, { hl_group = 'WarningMsg', end_col = 43 })
+ screen:expect([[
+ {6:^This is some text without any spell errors.}|
+ {0:~ }|
+ |
]])
end)
end)
diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua
index 3233e6cd19..6eaf15cfad 100644
--- a/test/functional/ui/statuscolumn_spec.lua
+++ b/test/functional/ui/statuscolumn_spec.lua
@@ -3,11 +3,15 @@ local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local eq = helpers.eq
+local exec = helpers.exec
local eval = helpers.eval
local exec_lua = helpers.exec_lua
local feed = helpers.feed
local meths = helpers.meths
local pcall_err = helpers.pcall_err
+local assert_alive = helpers.assert_alive
+
+local mousemodels = { "extend", "popup", "popup_setpos" }
describe('statuscolumn', function()
local screen
@@ -15,6 +19,7 @@ describe('statuscolumn', function()
clear('--cmd', 'set number nuw=1 | call setline(1, repeat(["aaaaa"], 16)) | norm GM')
screen = Screen.new()
screen:attach()
+ exec_lua('ns = vim.api.nvim_create_namespace("")')
end)
it("fails with invalid 'statuscolumn'", function()
@@ -41,22 +46,17 @@ describe('statuscolumn', function()
end)
it("widens with irregular 'statuscolumn' width", function()
- command([[set stc=%{v:relnum?v:relnum:(v:lnum==5?'bbbbb':v:lnum)}]])
- command('norm 5G | redraw!')
+ screen:try_resize(screen._width, 4)
+ command([=[
+ set stc=%{v:relnum?v:relnum:(v:lnum==5?'bbbbb':v:lnum)}
+ let ns = nvim_create_namespace('')
+ call nvim_buf_set_extmark(0, ns, 3, 0, {'virt_text':[['virt_text']]})
+ norm 5G | redraw!
+ ]=])
screen:expect([[
- 1 aaaaa |
+ 1 aaaaa virt_text |
bbbbba^eaaa |
1 aaaaa |
- 2 aaaaa |
- 3 aaaaa |
- 4 aaaaa |
- 5 aaaaa |
- 6 aaaaa |
- 7 aaaaa |
- 8 aaaaa |
- 9 aaaaa |
- 10 aaaaa |
- 11 aaaaa |
|
]])
end)
@@ -193,8 +193,10 @@ describe('statuscolumn', function()
[2] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGrey},
[3] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey},
[4] = {bold = true, foreground = Screen.colors.Brown},
- [5] = {background = Screen.colors.Grey90},
+ [5] = {foreground = Screen.colors.Red},
+ [6] = {foreground = Screen.colors.Red, background = Screen.colors.LightGrey},
})
+ command('hi! CursorLine guifg=Red guibg=NONE')
screen:expect([[
{1: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{1: │ }a |
@@ -211,7 +213,7 @@ describe('statuscolumn', function()
{1:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{0:@@@}|
|
]])
- command("set stc=%C%s%=%l│\\ ")
+ command([[set stc=%C%s%=%l│\ ]])
screen:expect_unchanged()
command('set signcolumn=auto:2 foldcolumn=auto')
command('sign define piet1 text=>> texthl=LineNr')
@@ -265,7 +267,7 @@ describe('statuscolumn', function()
{2: }{1: │}{2: }{1: }aaaaaa |
{2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaa |
- {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2:+}{4: 8│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaa |
{2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -283,7 +285,7 @@ describe('statuscolumn', function()
{2: }{1: 6│}{2: }{1: }aaaaaa |
{2: }{1: 7│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: 7│}{2: }{1: }aaaaaa |
- {2:+}{4: 8│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2:+}{4: 8│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{2: }{1: 9│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: 9│}{2: }{1: }aaaaaa |
{2: }{1:10│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -301,7 +303,7 @@ describe('statuscolumn', function()
{2: }{1: 2│}{2: }{1: }aaaaaa |
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: 1│}{2: }{1: }aaaaaa |
- {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: 1│}{2: }{1: }aaaaaa |
{2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -318,7 +320,7 @@ describe('statuscolumn', function()
{2: }{1: │}{2: }{1: }aaaaaa |
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaa |
- {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaa |
{2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
@@ -343,16 +345,74 @@ describe('statuscolumn', function()
{2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa |
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa |
- {2:+}{4: 0│}{2: }{4: }{5:^+-- 1 line: aaaaaaaaaaaaaaaaa}|
+ {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaa}|
{2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa |
{2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa |
|
]])
+ -- Also test fold and sign column when 'cpoptions' includes "n"
+ command('set cpoptions+=n')
+ feed('Hgjg0')
+ screen:expect([[
+ {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2: }{5:^aaaaaaaaaaaaaaaaaaaa }|
+ {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2:+}{1: 4│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}|
+ {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ |
+ ]])
+ command('set breakindent')
+ command('sign unplace 2')
+ feed('J2gjg0')
+ screen:expect([[
+ {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2: } {5:aaaaaaaaaaaaaaaaaaaa aaaaaaaaa}|
+ {2: } {5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2: } {5:^aaaaaaaaaaa }|
+ {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: } aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: } aaaaaaaaaaaaaaaaaaaa |
+ {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}|
+ {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: } aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: } aaaaaaaaaaaaaaaaaaaa |
+ |
+ ]])
+ command('set nobreakindent')
+ feed('$g0')
+ screen:expect([[
+ {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2: }{5:aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaa}|
+ {2: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {2: }{5:^aaa }|
+ {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}|
+ {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {2: }aaaaaaaaaaaaaaaaaaaa |
+ |
+ ]])
+ command('silent undo')
+ feed('8gg')
+ command('set cpoptions-=n')
-- Status column is re-evaluated for virt_lines, buffer line, and wrapped line
exec_lua([[
- local ns = vim.api.nvim_create_namespace("ns")
vim.api.nvim_buf_set_extmark(0, ns, 5, 0, {
virt_lines_above = true, virt_lines = {{{"virt_line above", ""}}} })
vim.api.nvim_buf_set_extmark(0, ns, 4, 0, { virt_lines = {{{"virt_line", ""}}} })
@@ -365,57 +425,216 @@ describe('statuscolumn', function()
{1:buffer 0 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{1:wrapped 1 5}aaaaaaaa |
{1:virtual-2 5}virt_line |
- {1:virtual-2 5}virt_line above |
+ {1:virtual-1 5}virt_line above |
{1:buffer 0 6}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{1:wrapped 1 6}aaaaaaaa |
{1:buffer 0 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{1:wrapped 1 7}aaaaaaaa |
- {4:buffer 0 8}{5:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {4:buffer 0 8}{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
{1:buffer 0 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
{1:wrapped 1 9}aaaaaaaa |
|
]])
+ -- Also test virt_lines at the end of buffer
+ exec_lua([[
+ vim.api.nvim_buf_set_extmark(0, ns, 15, 0, { virt_lines = {{{"END", ""}}} })
+ ]])
+ feed('GkJzz')
+ screen:expect([[
+ {1:buffer 0 12}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 12}aaaaaaaaa |
+ {1:buffer 0 13}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 13}aaaaaaaaa |
+ {1:buffer 0 14}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ {1:wrapped 1 14}aaaaaaaaa |
+ {4:buffer 0 15}{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {4:wrapped 1 15}{5:aaaaaaaaa^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {4:wrapped 2 15}{5:aaaaaaaaaaaaaaaaaaa }|
+ {1:virtual-1 15}END |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]])
+ -- Also test virt_lines when 'cpoptions' includes "n"
+ exec_lua([[
+ vim.opt.cpoptions:append("n")
+ vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line1", ""}}} })
+ vim.api.nvim_buf_set_extmark(0, ns, 14, 0, { virt_lines = {{{"virt_line2", ""}}} })
+ ]])
+ screen:expect([[
+ {1:buffer 0 12}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaa |
+ {1:buffer 0 13}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaa |
+ {1:buffer 0 14}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|
+ aaaaaaaaa |
+ {4:buffer 0 15}{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {5:aaaaaaaaa^ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}|
+ {5:aaaaaaa }|
+ {1:virtual-3 15}virt_line1 |
+ {1:virtual-2 15}virt_line2 |
+ {1:virtual-1 15}END |
+ {0:~ }|
+ |
+ ]])
end)
- it("works with 'statuscolumn' clicks", function()
- command('set mousemodel=extend')
- command([[
- function! MyClickFunc(minwid, clicks, button, mods)
- let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line)
- if a:mods !=# ' '
- let g:testvar ..= '(' .. a:mods .. ')'
- endif
- endfunction
- set stc=%0@MyClickFunc@%=%l%T
- ]])
- meths.input_mouse('left', 'press', '', 0, 0, 0)
- eq('0 1 l 4', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 0, 0)
- eq('0 2 l 4', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 0, 0)
- eq('0 3 l 4', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 0, 0)
- eq('0 4 l 4', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 3, 0)
- eq('0 1 r 7', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 3, 0)
- eq('0 2 r 7', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 3, 0)
- eq('0 3 r 7', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 3, 0)
- eq('0 4 r 7', eval("g:testvar"))
- command('set laststatus=2 winbar=%f')
- command('let g:testvar=""')
- -- Check that winbar click doesn't register as statuscolumn click
- meths.input_mouse('right', 'press', '', 0, 0, 0)
- eq('', eval("g:testvar"))
- -- Check that statusline click doesn't register as statuscolumn click
- meths.input_mouse('right', 'press', '', 0, 12, 0)
- eq('', eval("g:testvar"))
+ it('does not corrupt the screen with minwid sign item', function()
+ screen:try_resize(screen._width, 3)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Brown},
+ [1] = {foreground = Screen.colors.Blue4, background = Screen.colors.Gray},
+ })
+ command([[set stc=%6s\ %l]])
+ exec_lua('vim.api.nvim_buf_set_extmark(0, ns, 7, 0, {sign_text = "𒀀"})')
+ screen:expect([[
+ {0: 𒀀 8}^aaaaa |
+ {0: }{1: }{0: 9}aaaaa |
+ |
+ ]])
end)
- it('click labels do not leak memory', function()
- command([[
+ for _, model in ipairs(mousemodels) do
+ describe('with mousemodel=' .. model, function()
+ before_each(function()
+ command('set mousemodel=' .. model)
+ exec([[
+ function! MyClickFunc(minwid, clicks, button, mods)
+ let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line)
+ if a:mods !=# ' '
+ let g:testvar ..= '(' .. a:mods .. ')'
+ endif
+ endfunction
+ let g:testvar = ''
+ ]])
+ end)
+
+ it('clicks work with mousemodel=' .. model, function()
+ meths.set_option_value('statuscolumn', '%0@MyClickFunc@%=%l%T', {})
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ eq('0 1 l 4', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ eq('0 2 l 4', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ eq('0 3 l 4', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ eq('0 4 l 4', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 0)
+ eq('0 1 r 7', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 0)
+ eq('0 2 r 7', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 0)
+ eq('0 3 r 7', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 0)
+ eq('0 4 r 7', eval("g:testvar"))
+
+ command('rightbelow vsplit')
+ meths.input_mouse('left', 'press', '', 0, 0, 27)
+ eq('0 1 l 4', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 27)
+ eq('0 1 r 7', eval("g:testvar"))
+ command('setlocal rightleft')
+ meths.input_mouse('left', 'press', '', 0, 0, 52)
+ eq('0 1 l 4', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 52)
+ eq('0 1 r 7', eval("g:testvar"))
+ command('wincmd H')
+ meths.input_mouse('left', 'press', '', 0, 0, 25)
+ eq('0 1 l 4', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 3, 25)
+ eq('0 1 r 7', eval("g:testvar"))
+ command('close')
+
+ command('set laststatus=2 winbar=%f')
+ command('let g:testvar = ""')
+ -- Check that winbar click doesn't register as statuscolumn click
+ meths.input_mouse('right', 'press', '', 0, 0, 0)
+ eq('', eval("g:testvar"))
+ -- Check that statusline click doesn't register as statuscolumn click
+ meths.input_mouse('right', 'press', '', 0, 12, 0)
+ eq('', eval("g:testvar"))
+ -- Check that cmdline click doesn't register as statuscolumn click
+ meths.input_mouse('right', 'press', '', 0, 13, 0)
+ eq('', eval("g:testvar"))
+ end)
+
+ it('clicks and highlights work with control characters', function()
+ meths.set_option_value('statuscolumn', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {})
+ screen:expect{grid=[[
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}^aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |
+ |
+ ]], attr_ids={
+ [0] = {foreground = Screen.colors.Blue, bold = true}; -- NonText
+ [1] = {foreground = Screen.colors.Brown}; -- LineNr
+ }}
+ meths.input_mouse('right', 'press', '', 0, 4, 3)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 5, 8)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 4)
+ eq('0 1 r 10', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 7, 7)
+ eq('0 1 l 11', eval("g:testvar"))
+ end)
+
+ it('popupmenu callback does not drag mouse on close', function()
+ screen:try_resize(screen._width, 2)
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Brown},
+ [1] = {background = Screen.colors.Plum1},
+ })
+ meths.set_option_value('statuscolumn', '%0@MyClickFunc@%l%T', {})
+ exec([[
+ function! MyClickFunc(minwid, clicks, button, mods)
+ let g:testvar = printf("%d %d %s %d", a:minwid, a:clicks, a:button, getmousepos().line)
+ menu PopupStc.Echo <cmd>echo g:testvar<CR>
+ popup PopupStc
+ endfunction
+ ]])
+ -- clicking an item does not drag mouse
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ screen:expect([[
+ {0:8 }^aaaaa |
+ {1: Echo } |
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 1, 5)
+ meths.input_mouse('left', 'release', '', 0, 1, 5)
+ screen:expect([[
+ {0:8 }^aaaaa |
+ 0 1 l 8 |
+ ]])
+ command('echo')
+ -- clicking outside to close the menu does not drag mouse
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ screen:expect([[
+ {0:8 }^aaaaa |
+ {1: Echo } |
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 0, 10)
+ meths.input_mouse('left', 'release', '', 0, 0, 10)
+ screen:expect([[
+ {0:8 }^aaaaa |
+ |
+ ]])
+ end)
+ end)
+ end
+
+ it('click labels do not leak memory #21878', function()
+ exec([[
set laststatus=2
setlocal statuscolumn=%0@MyClickFunc@abcd%T
4vsplit
@@ -427,19 +646,29 @@ describe('statuscolumn', function()
]])
end)
+ it('click labels do not crash when initial width is 0 #24428', function()
+ exec([[
+ set nonumber
+ bwipe!
+ setlocal statuscolumn=abcd
+ redraw
+ setlocal statuscolumn=%0@MyClickFunc@abcd%T
+ redraw
+ ]])
+ assert_alive()
+ end)
+
it('works with foldcolumn', function()
-- Fits maximum multibyte foldcolumn #21759
command([[set stc=%C%=%l\ fdc=9 fillchars=foldsep:𒀀]])
for _ = 0,8 do command('norm zfjzo') end
-- 'statuscolumn' is not drawn for `virt_lines_leftcol` lines
exec_lua([[
- local ns = vim.api.nvim_create_namespace("ns")
vim.api.nvim_buf_set_extmark(0, ns, 6, 0, {
virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} })
vim.api.nvim_buf_set_extmark(0, ns, 7, 0, {
virt_lines_leftcol = true, virt_lines = {{{"virt", ""}}} })
]])
- feed('lh') -- force update cursor row
screen:expect([[
4 aaaaa |
5 aaaaa |
@@ -456,7 +685,7 @@ describe('statuscolumn', function()
14 aaaaa |
|
]])
- command('set stc=') -- also for the default sign column
+ command('set stc=') -- also for the default fold column
screen:expect_unchanged()
-- 'statuscolumn' is not too wide with custom (bogus) fold column
command([[set stc=%{foldlevel(v:lnum)>0?repeat('-',foldlevel(v:lnum)):''}%=%l\ ]])
@@ -478,4 +707,166 @@ describe('statuscolumn', function()
|
]])
end)
+
+ it('works with cmdwin', function()
+ feed(':set stc=%l<CR>q:k$')
+ screen:expect([[
+ 7 aaaaa |
+ 8 aaaaa |
+ 9 aaaaa |
+ 10aaaaa |
+ [No Name] [+] |
+ :1set stc=%^l |
+ :2 |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ ~ |
+ [Command Line] |
+ : |
+ ]])
+ end)
+
+ it("has correct width when toggling '(relative)number'", function()
+ screen:try_resize(screen._width, 6)
+ command('call setline(1, repeat(["aaaaa"], 100))')
+ command('set relativenumber')
+ command([[set stc=%{!&nu&&!&rnu?'':&rnu?v:relnum?v:relnum:&nu?v:lnum:'0':v:lnum}]])
+ screen:expect([[
+ 1 aaaaa |
+ 8 ^aaaaa |
+ 1 aaaaa |
+ 2 aaaaa |
+ 3 aaaaa |
+ |
+ ]])
+ -- width correctly estimated with "w_nrwidth_line_count" when setting 'stc'
+ command([[set stc=%{!&nu&&!&rnu?'':&rnu?v:relnum?v:relnum:&nu?v:lnum:'0':v:lnum}]])
+ screen:expect_unchanged()
+ -- zero width when disabling 'number'
+ command('set norelativenumber nonumber')
+ screen:expect([[
+ aaaaa |
+ ^aaaaa |
+ aaaaa |
+ aaaaa |
+ aaaaa |
+ |
+ ]])
+ -- width correctly estimated with "w_nrwidth_line_count" when setting 'nu'
+ command('set number')
+ screen:expect([[
+ 7 aaaaa |
+ 8 ^aaaaa |
+ 9 aaaaa |
+ 10 aaaaa |
+ 11 aaaaa |
+ |
+ ]])
+ end)
+
+ it("has correct width with custom sign column when (un)placing signs", function()
+ screen:try_resize(screen._width, 3)
+ exec_lua([[
+ vim.cmd.norm('gg')
+ vim.o.signcolumn = 'no'
+ vim.fn.sign_define('sign', { text = 'ss' })
+ _G.StatusCol = function()
+ local s = vim.fn.sign_getplaced(1)[1].signs
+ local es = vim.api.nvim_buf_get_extmarks(0, ns, 0, -1, {type = "sign"})
+ local sign = ''
+ local signs = #s + #es
+ if signs > 0 then
+ sign = (vim.v.lnum == 2 and 'ss' or ' '):rep(signs)
+ end
+ return vim.v.lnum .. '%=' .. sign
+ end
+ vim.o.number = true
+ vim.o.numberwidth = 2
+ vim.o.statuscolumn = "%!v:lua.StatusCol()"
+ ]])
+ command('sign place 1 line=2 name=sign')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssaaaaa |
+ |
+ ]])
+ command('sign place 2 line=2 name=sign')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssssaaaaa |
+ |
+ ]])
+ command('sign unplace 2')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssaaaaa |
+ |
+ ]])
+ command('sign unplace 1')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 aaaaa |
+ |
+ ]])
+ -- Also for extmark signs
+ exec_lua('id1 = vim.api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text = "ss"})')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssaaaaa |
+ |
+ ]])
+ exec_lua('id2 = vim.api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text = "ss"})')
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssssaaaaa |
+ |
+ ]])
+ exec_lua("vim.api.nvim_buf_del_extmark(0, ns, id1)")
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 ssaaaaa |
+ |
+ ]])
+ exec_lua("vim.api.nvim_buf_del_extmark(0, ns, id2)")
+ screen:expect([[
+ 1 ^aaaaa |
+ 2 aaaaa |
+ |
+ ]])
+ -- In all windows
+ command('wincmd v | set ls=0')
+ command('sign place 1 line=2 name=sign')
+ screen:expect([[
+ 1 ^aaaaa │1 aaaaa |
+ 2 ssaaaaa │2 ssaaaaa |
+ |
+ ]])
+ end)
+
+ it("is only evaluated twice, once to estimate and once to draw", function()
+ command([[
+ let g:stcnr = 0
+ func! Stc()
+ let g:stcnr += 1
+ return '12345'
+ endfunc
+ set stc=%!Stc()
+ norm ggdG
+ ]])
+ eq(2, eval('g:stcnr'))
+ end)
+
+ it('does not wrap multibyte characters at the end of a line', function()
+ screen:try_resize(33, 4)
+ command([[set spell stc=%l\ ]])
+ command('call setline(8, "This is a line that contains ᶏ multibyte character.")')
+ screen:expect([[
+ 8 ^This is a line that contains ᶏ|
+ multibyte character. |
+ 9 aaaaa |
+ |
+ ]])
+ end)
end)
diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua
index 1c184ff27d..182e0cdadf 100644
--- a/test/functional/ui/statusline_spec.lua
+++ b/test/functional/ui/statusline_spec.lua
@@ -10,179 +10,215 @@ local meths = helpers.meths
local exec = helpers.exec
local exec_lua = helpers.exec_lua
local eval = helpers.eval
-
-describe('statusline clicks', function()
- local screen
-
- before_each(function()
- clear()
- screen = Screen.new(40, 8)
- screen:attach()
- command('set laststatus=2 mousemodel=extend')
- exec([=[
- function! MyClickFunc(minwid, clicks, button, mods)
- let g:testvar = printf("%d %d %s", a:minwid, a:clicks, a:button)
- if a:mods !=# ' '
- let g:testvar ..= '(' .. a:mods .. ')'
- endif
- endfunction
- ]=])
- end)
-
- it('works', function()
- meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 2 l', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 3 l', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 4 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 1 r', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 2 r', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 3 r', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 4 r', eval("g:testvar"))
- end)
-
- it('works for winbar', function()
- meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- meths.input_mouse('left', 'press', '', 0, 0, 17)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 0, 17)
- eq('0 1 r', eval("g:testvar"))
- end)
-
- it('works for winbar in floating window', function()
- meths.open_win(0, true, { width=30, height=4, relative='editor', row=1, col=5,
- border = "single" })
- meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T',
- { scope = 'local' })
- meths.input_mouse('left', 'press', '', 0, 2, 23)
- eq('0 1 l', eval("g:testvar"))
- end)
-
- it('works when there are multiple windows', function()
- command('split')
- meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- meths.input_mouse('left', 'press', '', 0, 0, 17)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 4, 17)
- eq('0 1 r', eval("g:testvar"))
- meths.input_mouse('middle', 'press', '', 0, 3, 17)
- eq('0 1 m', eval("g:testvar"))
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 1 l', eval("g:testvar"))
- end)
-
- it('works with Lua function', function()
- exec_lua([[
+local sleep = helpers.sleep
+
+local mousemodels = { "extend", "popup", "popup_setpos" }
+
+for _, model in ipairs(mousemodels) do
+ describe('statusline clicks with mousemodel=' .. model, function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new(40, 8)
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [1] = {bold = true, reverse = true}; -- StatusLine
+ })
+ screen:attach()
+ command('set laststatus=2 mousemodel=' .. model)
+ exec([=[
+ function! MyClickFunc(minwid, clicks, button, mods)
+ let g:testvar = printf("%d %d %s", a:minwid, a:clicks, a:button)
+ if a:mods !=# ' '
+ let g:testvar ..= '(' .. a:mods .. ')'
+ endif
+ endfunction
+ let g:testvar = ''
+ ]=])
+ end)
+
+ it('works', function()
+ meths.set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ meths.input_mouse('left', 'press', '', 0, 6, 16)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 29)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 2 l', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 3 l', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 4 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 28)
+ eq('0 1 r', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 28)
+ eq('0 2 r', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 28)
+ eq('0 3 r', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 28)
+ eq('0 4 r', eval("g:testvar"))
+ end)
+
+ it('works with control characters and highlight', function()
+ meths.set_option_value('statusline', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {})
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ {1:^I}{0:^A^I^A^I}{1:^A }|
+ |
+ ]]}
+ meths.input_mouse('right', 'press', '', 0, 6, 3)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 8)
+ eq('', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 4)
+ eq('0 1 r', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 7)
+ eq('0 1 l', eval("g:testvar"))
+ end)
+
+ it('works for winbar', function()
+ meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ meths.input_mouse('left', 'press', '', 0, 0, 17)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 0, 17)
+ eq('0 1 r', eval("g:testvar"))
+ end)
+
+ it('works for winbar in floating window', function()
+ meths.open_win(0, true, { width=30, height=4, relative='editor', row=1, col=5,
+ border = "single" })
+ meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T',
+ { scope = 'local' })
+ meths.input_mouse('left', 'press', '', 0, 2, 23)
+ eq('0 1 l', eval("g:testvar"))
+ end)
+
+ it('works when there are multiple windows', function()
+ command('split')
+ meths.set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ meths.input_mouse('left', 'press', '', 0, 0, 17)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 4, 17)
+ eq('0 1 r', eval("g:testvar"))
+ meths.input_mouse('middle', 'press', '', 0, 3, 17)
+ eq('0 1 m', eval("g:testvar"))
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 1 l', eval("g:testvar"))
+ end)
+
+ it('works with Lua function', function()
+ exec_lua([[
function clicky_func(minwid, clicks, button, mods)
vim.g.testvar = string.format("%d %d %s", minwid, clicks, button)
end
- ]])
- meths.set_option('statusline', 'Not clicky stuff %0@v:lua.clicky_func@Clicky stuff%T')
- meths.input_mouse('left', 'press', '', 0, 6, 17)
- eq('0 1 l', eval("g:testvar"))
- end)
-
- it('ignores unsupported click items', function()
- command('tabnew | tabprevious')
- meths.set_option('statusline', '%2TNot clicky stuff%T')
- meths.input_mouse('left', 'press', '', 0, 6, 0)
- eq(1, meths.get_current_tabpage().id)
- meths.set_option('statusline', '%2XNot clicky stuff%X')
- meths.input_mouse('left', 'press', '', 0, 6, 0)
- eq(2, #meths.list_tabpages())
- end)
-
- it("right click works when statusline isn't focused #18994", function()
- meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 1 r', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 17)
- eq('0 2 r', eval("g:testvar"))
- end)
-
- it("works with modifiers #18994", function()
- meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
- -- Note: alternate between left and right mouse buttons to avoid triggering multiclicks
- meths.input_mouse('left', 'press', 'S', 0, 6, 17)
- eq('0 1 l(s )', eval("g:testvar"))
- meths.input_mouse('right', 'press', 'S', 0, 6, 17)
- eq('0 1 r(s )', eval("g:testvar"))
- meths.input_mouse('left', 'press', 'A', 0, 6, 17)
- eq('0 1 l( a )', eval("g:testvar"))
- meths.input_mouse('right', 'press', 'A', 0, 6, 17)
- eq('0 1 r( a )', eval("g:testvar"))
- meths.input_mouse('left', 'press', 'AS', 0, 6, 17)
- eq('0 1 l(s a )', eval("g:testvar"))
- meths.input_mouse('right', 'press', 'AS', 0, 6, 17)
- eq('0 1 r(s a )', eval("g:testvar"))
- meths.input_mouse('left', 'press', 'T', 0, 6, 17)
- eq('0 1 l( m)', eval("g:testvar"))
- meths.input_mouse('right', 'press', 'T', 0, 6, 17)
- eq('0 1 r( m)', eval("g:testvar"))
- meths.input_mouse('left', 'press', 'TS', 0, 6, 17)
- eq('0 1 l(s m)', eval("g:testvar"))
- meths.input_mouse('right', 'press', 'TS', 0, 6, 17)
- eq('0 1 r(s m)', eval("g:testvar"))
- meths.input_mouse('left', 'press', 'C', 0, 6, 17)
- eq('0 1 l( c )', eval("g:testvar"))
- -- <C-RightMouse> is for tag jump
- end)
-
- it("works for global statusline with vertical splits #19186", function()
- command('set laststatus=3')
- meths.set_option('statusline', '%0@MyClickFunc@Clicky stuff%T %= %0@MyClickFunc@Clicky stuff%T')
- command('vsplit')
- screen:expect([[
- ^ │ |
- ~ │~ |
- ~ │~ |
- ~ │~ |
- ~ │~ |
- ~ │~ |
- Clicky stuff Clicky stuff|
- |
- ]])
-
- -- clickable area on the right
- meths.input_mouse('left', 'press', '', 0, 6, 35)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 35)
- eq('0 1 r', eval("g:testvar"))
-
- -- clickable area on the left
- meths.input_mouse('left', 'press', '', 0, 6, 5)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 5)
- eq('0 1 r', eval("g:testvar"))
- end)
-
- it('no memory leak with zero-width click labels', function()
- command([[
+ ]])
+ meths.set_option_value('statusline', 'Not clicky stuff %0@v:lua.clicky_func@Clicky stuff%T', {})
+ meths.input_mouse('left', 'press', '', 0, 6, 17)
+ eq('0 1 l', eval("g:testvar"))
+ end)
+
+ it('ignores unsupported click items', function()
+ command('tabnew | tabprevious')
+ meths.set_option_value('statusline', '%2TNot clicky stuff%T', {})
+ meths.input_mouse('left', 'press', '', 0, 6, 0)
+ eq(1, meths.get_current_tabpage().id)
+ meths.set_option_value('statusline', '%2XNot clicky stuff%X', {})
+ meths.input_mouse('left', 'press', '', 0, 6, 0)
+ eq(2, #meths.list_tabpages())
+ end)
+
+ it("right click works when statusline isn't focused #18994", function()
+ meths.set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ meths.input_mouse('right', 'press', '', 0, 6, 17)
+ eq('0 1 r', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 17)
+ eq('0 2 r', eval("g:testvar"))
+ end)
+
+ it("works with modifiers #18994", function()
+ meths.set_option_value('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', {})
+ -- Note: alternate between left and right mouse buttons to avoid triggering multiclicks
+ meths.input_mouse('left', 'press', 'S', 0, 6, 17)
+ eq('0 1 l(s )', eval("g:testvar"))
+ meths.input_mouse('right', 'press', 'S', 0, 6, 17)
+ eq('0 1 r(s )', eval("g:testvar"))
+ meths.input_mouse('left', 'press', 'A', 0, 6, 17)
+ eq('0 1 l( a )', eval("g:testvar"))
+ meths.input_mouse('right', 'press', 'A', 0, 6, 17)
+ eq('0 1 r( a )', eval("g:testvar"))
+ meths.input_mouse('left', 'press', 'AS', 0, 6, 17)
+ eq('0 1 l(s a )', eval("g:testvar"))
+ meths.input_mouse('right', 'press', 'AS', 0, 6, 17)
+ eq('0 1 r(s a )', eval("g:testvar"))
+ meths.input_mouse('left', 'press', 'T', 0, 6, 17)
+ eq('0 1 l( m)', eval("g:testvar"))
+ meths.input_mouse('right', 'press', 'T', 0, 6, 17)
+ eq('0 1 r( m)', eval("g:testvar"))
+ meths.input_mouse('left', 'press', 'TS', 0, 6, 17)
+ eq('0 1 l(s m)', eval("g:testvar"))
+ meths.input_mouse('right', 'press', 'TS', 0, 6, 17)
+ eq('0 1 r(s m)', eval("g:testvar"))
+ meths.input_mouse('left', 'press', 'C', 0, 6, 17)
+ eq('0 1 l( c )', eval("g:testvar"))
+ -- <C-RightMouse> is for tag jump
+ end)
+
+ it("works for global statusline with vertical splits #19186", function()
+ command('set laststatus=3')
+ meths.set_option_value('statusline', '%0@MyClickFunc@Clicky stuff%T %= %0@MyClickFunc@Clicky stuff%T', {})
+ command('vsplit')
+ screen:expect{grid=[[
+ ^ │ |
+ {0:~ }│{0:~ }|
+ {0:~ }│{0:~ }|
+ {0:~ }│{0:~ }|
+ {0:~ }│{0:~ }|
+ {0:~ }│{0:~ }|
+ {1:Clicky stuff Clicky stuff}|
+ |
+ ]]}
+
+ -- clickable area on the right
+ meths.input_mouse('left', 'press', '', 0, 6, 35)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 35)
+ eq('0 1 r', eval("g:testvar"))
+
+ -- clickable area on the left
+ meths.input_mouse('left', 'press', '', 0, 6, 5)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 5)
+ eq('0 1 r', eval("g:testvar"))
+ end)
+
+ it('no memory leak with zero-width click labels', function()
+ command([[
let &stl = '%@Test@%T%@MyClickFunc@%=%T%@Test@'
- ]])
- meths.input_mouse('left', 'press', '', 0, 6, 0)
- eq('0 1 l', eval("g:testvar"))
- meths.input_mouse('right', 'press', '', 0, 6, 39)
- eq('0 1 r', eval("g:testvar"))
- end)
-
- it('no memory leak with truncated click labels', function()
- command([[
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 6, 0)
+ eq('0 1 l', eval("g:testvar"))
+ meths.input_mouse('right', 'press', '', 0, 6, 39)
+ eq('0 1 r', eval("g:testvar"))
+ end)
+
+ it('no memory leak with truncated click labels', function()
+ command([[
let &stl = '%@MyClickFunc@foo%X' .. repeat('a', 40) .. '%<t%@Test@bar%X%@Test@baz'
- ]])
- meths.input_mouse('left', 'press', '', 0, 6, 2)
- eq('0 1 l', eval("g:testvar"))
+ ]])
+ meths.input_mouse('left', 'press', '', 0, 6, 2)
+ eq('0 1 l', eval("g:testvar"))
+ end)
end)
-end)
+end
describe('global statusline', function()
local screen
@@ -389,38 +425,38 @@ describe('global statusline', function()
end)
it('win_move_statusline() can reduce cmdheight to 1', function()
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
funcs.win_move_statusline(0, -1)
- eq(2, meths.get_option('cmdheight'))
+ eq(2, meths.get_option_value('cmdheight', {}))
funcs.win_move_statusline(0, -1)
- eq(3, meths.get_option('cmdheight'))
+ eq(3, meths.get_option_value('cmdheight', {}))
funcs.win_move_statusline(0, 1)
- eq(2, meths.get_option('cmdheight'))
+ eq(2, meths.get_option_value('cmdheight', {}))
funcs.win_move_statusline(0, 1)
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
end)
it('mouse dragging can reduce cmdheight to 1', function()
command('set mouse=a')
meths.input_mouse('left', 'press', '', 0, 14, 10)
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
- eq(2, meths.get_option('cmdheight'))
+ eq(2, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 12, 10)
- eq(3, meths.get_option('cmdheight'))
+ eq(3, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
- eq(2, meths.get_option('cmdheight'))
+ eq(2, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 14, 10)
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 15, 10)
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 0, 14, 10)
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
end)
it('cmdline row is correct after setting cmdheight #20514', function()
command('botright split test/functional/fixtures/bigfile.txt')
- meths.set_option('cmdheight', 1)
+ meths.set_option_value('cmdheight', 1, {})
feed('L')
screen:expect([[
|
@@ -459,7 +495,7 @@ describe('global statusline', function()
{2:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
]])
- meths.set_option('showtabline', 2)
+ meths.set_option_value('showtabline', 2, {})
screen:expect([[
{3: }{5:2}{3: t/f/f/bigfile.txt }{4: }|
|
@@ -478,7 +514,7 @@ describe('global statusline', function()
{2:test/functional/fixtures/bigfile.txt 8,1 0%}|
|
]])
- meths.set_option('cmdheight', 0)
+ meths.set_option_value('cmdheight', 0, {})
screen:expect([[
{3: }{5:2}{3: t/f/f/bigfile.txt }{4: }|
|
@@ -497,7 +533,7 @@ describe('global statusline', function()
^0007;<control>;Cc;0;BN;;;;;N;BELL;;;; |
{2:test/functional/fixtures/bigfile.txt 8,1 0%}|
]])
- meths.set_option('cmdheight', 1)
+ meths.set_option_value('cmdheight', 1, {})
screen:expect([[
{3: }{5:2}{3: t/f/f/bigfile.txt }{4: }|
|
@@ -521,8 +557,8 @@ end)
it('statusline does not crash if it has Arabic characters #19447', function()
clear()
- meths.set_option('statusline', 'غً')
- meths.set_option('laststatus', 2)
+ meths.set_option_value('statusline', 'غً', {})
+ meths.set_option_value('laststatus', 2, {})
command('redraw!')
assert_alive()
end)
@@ -589,3 +625,195 @@ it('showcmdloc=statusline does not show if statusline is too narrow', function()
feed('1234')
screen:expect_unchanged()
end)
+
+it('K_EVENT does not trigger a statusline redraw unnecessarily', function()
+ clear()
+ local screen = Screen.new(40, 8)
+ screen:attach()
+ -- does not redraw on vim.schedule (#17937)
+ command([[
+ set laststatus=2
+ let g:counter = 0
+ func Status()
+ let g:counter += 1
+ lua vim.schedule(function() end)
+ return g:counter
+ endfunc
+ set statusline=%!Status()
+ ]])
+ sleep(50)
+ eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter'))
+ -- also in insert mode
+ feed('i')
+ sleep(50)
+ eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter'))
+ -- does not redraw on timer call (#14303)
+ command([[
+ let g:counter = 0
+ func Timer(timer)
+ endfunc
+ call timer_start(1, 'Timer', {'repeat': 100})
+ ]])
+ sleep(50)
+ eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter'))
+end)
+
+it('statusline is redrawn on various state changes', function()
+ clear()
+ local screen = Screen.new(40, 4)
+ screen:attach()
+
+ -- recording state change #22683
+ command('set ls=2 stl=%{repeat(reg_recording(),5)}')
+ screen:expect([[
+ ^ |
+ ~ |
+ |
+ |
+ ]])
+ feed('qQ')
+ screen:expect([[
+ ^ |
+ ~ |
+ QQQQQ |
+ recording @Q |
+ ]])
+ feed('q')
+ screen:expect([[
+ ^ |
+ ~ |
+ |
+ |
+ ]])
+
+ -- Visual mode change #23932
+ command('set ls=2 stl=%{mode(1)}')
+ screen:expect([[
+ ^ |
+ ~ |
+ n |
+ |
+ ]])
+ feed('v')
+ screen:expect([[
+ ^ |
+ ~ |
+ v |
+ -- VISUAL -- |
+ ]])
+ feed('V')
+ screen:expect([[
+ ^ |
+ ~ |
+ V |
+ -- VISUAL LINE -- |
+ ]])
+ feed('<C-V>')
+ screen:expect([[
+ ^ |
+ ~ |
+ ^V |
+ -- VISUAL BLOCK -- |
+ ]])
+ feed('<Esc>')
+ screen:expect([[
+ ^ |
+ ~ |
+ n |
+ |
+ ]])
+end)
+
+it('ruler is redrawn in cmdline with redrawstatus #22804', function()
+ clear()
+ local screen = Screen.new(40, 2)
+ screen:attach()
+ command([[
+ let g:n = 'initial value'
+ set ls=1 ru ruf=%{g:n}
+ redraw
+ let g:n = 'other value'
+ redrawstatus
+ ]])
+ screen:expect([[
+ ^ |
+ other value |
+ ]])
+end)
+
+it("shows correct ruler in cmdline with no statusline", function()
+ clear()
+ local screen = Screen.new(30, 8)
+ screen:set_default_attr_ids {
+ [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ [2] = {bold = true, reverse = true}, -- StatusLine
+ [3] = {reverse = true}, -- StatusLineNC
+ }
+ screen:attach()
+ -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width.
+ command [[
+ set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10
+ split
+ wincmd b
+ vsplit
+ wincmd t
+ wincmd |
+ mode
+ ]]
+ -- Window 1 is current. It has a statusline, so cmdline should show the
+ -- last window's ruler, which has no statusline.
+ command '1wincmd w'
+ screen:expect [[
+ ^ |
+ {1:~ }|
+ {1:~ }|
+ {2:[No Name] 1longlonglong }|
+ │ |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ 3longlonglong |
+ ]]
+ -- Window 2 is current. It has no statusline, so cmdline should show its
+ -- ruler instead.
+ command '2wincmd w'
+ screen:expect [[
+ |
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] 1longlonglong }|
+ ^ │ |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ 2longlonglong |
+ ]]
+ -- Window 3 is current. Cmdline should again show its ruler.
+ command '3wincmd w'
+ screen:expect [[
+ |
+ {1:~ }|
+ {1:~ }|
+ {3:[No Name] 1longlonglong }|
+ │^ |
+ {1:~ }│{1:~ }|
+ {1:~ }│{1:~ }|
+ 3longlonglong |
+ ]]
+end)
+
+it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function()
+ clear()
+ local screen = Screen.new(53, 4)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText
+ })
+ command('hi clear StatusLine')
+ command('hi clear StatusLineNC')
+ command('vsplit')
+ screen:expect{grid=[[
+ ^ │ |
+ {1:~ }│{1:~ }|
+ [No Name] [No Name] |
+ |
+ ]]}
+end)
diff --git a/test/functional/ui/tabline_spec.lua b/test/functional/ui/tabline_spec.lua
index 2cdec62d01..befdb7c5d1 100644
--- a/test/functional/ui/tabline_spec.lua
+++ b/test/functional/ui/tabline_spec.lua
@@ -1,6 +1,9 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, command, eq = helpers.clear, helpers.command, helpers.eq
+local insert = helpers.insert
+local meths = helpers.meths
+local assert_alive = helpers.assert_alive
describe('ui/ext_tabline', function()
local screen
@@ -92,6 +95,10 @@ describe("tabline", function()
clear()
screen = Screen.new(42, 5)
screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [1] = {reverse = true}; -- TabLineFill
+ })
end)
it('redraws when tabline option is set', function()
@@ -100,24 +107,18 @@ describe("tabline", function()
screen:expect{grid=[[
{1:asdf }|
^ |
- {2:~ }|
- {2:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]], attr_ids={
- [1] = {reverse = true};
- [2] = {bold = true, foreground = Screen.colors.Blue1};
- }}
+ ]]}
command('set tabline=jkl')
screen:expect{grid=[[
{1:jkl }|
^ |
- {2:~ }|
- {2:~ }|
+ {0:~ }|
+ {0:~ }|
|
- ]], attr_ids={
- [1] = {reverse = true};
- [2] = {bold = true, foreground = Screen.colors.Blue};
- }}
+ ]]}
end)
it('click definitions do not leak memory #21765', function()
@@ -125,4 +126,52 @@ describe("tabline", function()
command('set showtabline=2')
command('redrawtabline')
end)
+
+ it('clicks work with truncated double-width label #24187', function()
+ insert('tab1')
+ command('tabnew')
+ insert('tab2')
+ command('tabprev')
+ meths.set_option_value('tabline', '%1T口口%2Ta' .. ('b'):rep(38) .. '%999Xc', {})
+ screen:expect{grid=[[
+ {1:<abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc }|
+ tab^1 |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ assert_alive()
+ meths.input_mouse('left', 'press', '', 0, 0, 1)
+ screen:expect{grid=[[
+ {1:<abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc }|
+ tab^2 |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ meths.input_mouse('left', 'press', '', 0, 0, 0)
+ screen:expect{grid=[[
+ {1:<abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc }|
+ tab^1 |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ meths.input_mouse('left', 'press', '', 0, 0, 39)
+ screen:expect{grid=[[
+ {1:<abbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbc }|
+ tab^2 |
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ meths.input_mouse('left', 'press', '', 0, 0, 40)
+ screen:expect{grid=[[
+ tab^1 |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end)
end)
diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua
new file mode 100644
index 0000000000..66c0ff5c9c
--- /dev/null
+++ b/test/functional/ui/title_spec.lua
@@ -0,0 +1,138 @@
+local helpers = require('test.functional.helpers')(after_each)
+local Screen = require('test.functional.ui.screen')
+local clear = helpers.clear
+local command = helpers.command
+local curwin = helpers.curwin
+local eq = helpers.eq
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local funcs = helpers.funcs
+local meths = helpers.meths
+local is_os = helpers.is_os
+
+describe('title', function()
+ local screen
+
+ before_each(function()
+ clear()
+ screen = Screen.new()
+ screen:attach()
+ end)
+
+ it('has correct default title with unnamed file', function()
+ local expected = '[No Name] - NVIM'
+ command('set title')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('has correct default title with named file', function()
+ local expected = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM')
+ command('set title')
+ command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ describe('is not changed by', function()
+ local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1'
+ local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2'
+ local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM')
+ local buf2
+
+ before_each(function()
+ command('edit '..file1)
+ buf2 = funcs.bufadd(file2)
+ command('set title')
+ end)
+
+ it('calling setbufvar() to set an option in a hidden buffer from i_CTRL-R', function()
+ command([[inoremap <F2> <C-R>=setbufvar(]]..buf2..[[, '&autoindent', 1) ?? ''<CR>]])
+ feed('i<F2><Esc>')
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('an RPC call to nvim_set_option_value in a hidden buffer', function()
+ meths.set_option_value('autoindent', true, { buf = buf2 })
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('a Lua callback calling nvim_set_option_value in a hidden buffer', function()
+ exec_lua(string.format([[
+ vim.schedule(function()
+ vim.api.nvim_set_option_value('autoindent', true, { buf = %d })
+ end)
+ ]], buf2))
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('a Lua callback calling nvim_buf_call in a hidden buffer', function()
+ exec_lua(string.format([[
+ vim.schedule(function()
+ vim.api.nvim_buf_call(%d, function() end)
+ end)
+ ]], buf2))
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('setting the buffer of another window using RPC', function()
+ local oldwin = curwin().id
+ command('split')
+ meths.win_set_buf(oldwin, buf2)
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('setting the buffer of another window using Lua callback', function()
+ local oldwin = curwin().id
+ command('split')
+ exec_lua(string.format([[
+ vim.schedule(function()
+ vim.api.nvim_win_set_buf(%d, %d)
+ end)
+ ]], oldwin, buf2))
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('creating a floating window using RPC', function()
+ meths.open_win(buf2, false, {
+ relative = 'editor', width = 5, height = 5, row = 0, col = 0,
+ })
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+
+ it('creating a floating window using Lua callback', function()
+ exec_lua(string.format([[
+ vim.api.nvim_open_win(%d, false, {
+ relative = 'editor', width = 5, height = 5, row = 0, col = 0,
+ })
+ ]], buf2))
+ command('redraw!')
+ screen:expect(function()
+ eq(expected, screen.title)
+ end)
+ end)
+ end)
+end)
diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua
index 50466c9473..3201135b67 100644
--- a/test/functional/ui/wildmode_spec.lua
+++ b/test/functional/ui/wildmode_spec.lua
@@ -17,6 +17,85 @@ describe("'wildmenu'", function()
screen:attach()
end)
+ -- oldtest: Test_wildmenu_screendump()
+ it('works', function()
+ screen:set_default_attr_ids({
+ [0] = {bold = true, foreground = Screen.colors.Blue}; -- NonText
+ [1] = {foreground = Screen.colors.Black, background = Screen.colors.Yellow}; -- WildMenu
+ [2] = {bold = true, reverse = true}; -- StatusLine
+ })
+ -- Test simple wildmenu
+ feed(':sign <Tab>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {1:define}{2: jump list > }|
+ :sign define^ |
+ ]]}
+
+ feed('<Tab>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2:define }{1:jump}{2: list > }|
+ :sign jump^ |
+ ]]}
+
+ feed('<Tab>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2:define jump }{1:list}{2: > }|
+ :sign list^ |
+ ]]}
+
+ -- Looped back to the original value
+ feed('<Tab><Tab><Tab><Tab>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {2:define jump list > }|
+ :sign ^ |
+ ]]}
+
+ -- Test that the wild menu is cleared properly
+ feed('<Space>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ :sign ^ |
+ ]]}
+
+ -- Test that a different wildchar still works
+ feed('<Esc>')
+ command('set wildchar=<Esc>')
+ feed(':sign <Esc>')
+ screen:expect{grid=[[
+ |
+ {0:~ }|
+ {0:~ }|
+ {1:define}{2: jump list > }|
+ :sign define^ |
+ ]]}
+
+ -- Double-<Esc> is a hard-coded method to escape while wildchar=<Esc>. Make
+ -- sure clean up is properly done in edge case like this.
+ feed('<Esc>')
+ screen:expect{grid=[[
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {0:~ }|
+ |
+ ]]}
+ end)
+
it('C-E to cancel wildmenu completion restore original input', function()
feed(':sign <tab>')
screen:expect([[
@@ -367,12 +446,12 @@ describe("'wildmenu'", function()
}
-- Wildcharm? where we are going we aint't no need no wildcharm.
- eq(0, meths.get_option'wildcharm')
+ eq(0, meths.get_option_value('wildcharm', {}))
-- Don't mess the defaults yet (neovim is about backwards compatibility)
- eq(9, meths.get_option'wildchar')
+ eq(9, meths.get_option_value('wildchar', {}))
-- Lol what is cnoremap? Some say it can define mappings.
command 'set wildchar=0'
- eq(0, meths.get_option'wildchar')
+ eq(0, meths.get_option_value('wildchar', {}))
command 'cnoremap <f2> <c-z>'
feed(':syntax <f2>')
@@ -481,9 +560,9 @@ describe('command line completion', function()
end)
it('does not leak memory with <S-Tab> with wildmenu and only one match #19874', function()
- meths.set_option('wildmenu', true)
- meths.set_option('wildmode', 'full')
- meths.set_option('wildoptions', 'pum')
+ meths.set_option_value('wildmenu', true, {})
+ meths.set_option_value('wildmode', 'full', {})
+ meths.set_option_value('wildoptions', 'pum', {})
feed(':sign unpla<S-Tab>')
screen:expect([[
@@ -505,8 +584,8 @@ describe('command line completion', function()
end)
it('does not show matches with <S-Tab> without wildmenu with wildmode=full', function()
- meths.set_option('wildmenu', false)
- meths.set_option('wildmode', 'full')
+ meths.set_option_value('wildmenu', false, {})
+ meths.set_option_value('wildmode', 'full', {})
feed(':sign <S-Tab>')
screen:expect([[
@@ -519,8 +598,8 @@ describe('command line completion', function()
end)
it('shows matches with <S-Tab> without wildmenu with wildmode=list', function()
- meths.set_option('wildmenu', false)
- meths.set_option('wildmode', 'list')
+ meths.set_option_value('wildmenu', false, {})
+ meths.set_option_value('wildmode', 'list', {})
feed(':sign <S-Tab>')
screen:expect([[
diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua
index ece27ec3ff..78bbcd3a63 100644
--- a/test/functional/ui/winbar_spec.lua
+++ b/test/functional/ui/winbar_spec.lua
@@ -31,7 +31,7 @@ describe('winbar', function()
[10] = {background = Screen.colors.LightGrey, underline = true},
[11] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta},
})
- meths.set_option('winbar', 'Set Up The Bars')
+ meths.set_option_value('winbar', 'Set Up The Bars', {})
end)
it('works', function()
@@ -114,6 +114,41 @@ describe('winbar', function()
{2:[No Name] [No Name] }|
|
]])
+ -- 'showcmdloc' "statusline" should not interfere with winbar redrawing #23030
+ command('set showcmd showcmdloc=statusline')
+ feed('<C-W>w')
+ feed('<C-W>')
+ screen:expect([[
+ {6:Set Up The Bars }│{6:Set Up The Bars }|
+ │ |
+ {3:~ }│{3:~ }|
+ {3:~ }│{2:[No Name] }|
+ {3:~ }│{5:Set Up The Bars }|
+ {3:~ }│^ |
+ {3:~ }│{3:~ }|
+ {3:~ }│{4:[No Name] ^W }|
+ {3:~ }│{6:Set Up The Bars }|
+ {3:~ }│ |
+ {3:~ }│{3:~ }|
+ {2:[No Name] [No Name] }|
+ |
+ ]])
+ feed('w<C-W>W')
+ screen:expect([[
+ {6:Set Up The Bars }│{6:Set Up The Bars }|
+ │ |
+ {3:~ }│{3:~ }|
+ {3:~ }│{2:[No Name] }|
+ {3:~ }│{5:Set Up The Bars }|
+ {3:~ }│^ |
+ {3:~ }│{3:~ }|
+ {3:~ }│{4:[No Name] }|
+ {3:~ }│{6:Set Up The Bars }|
+ {3:~ }│ |
+ {3:~ }│{3:~ }|
+ {2:[No Name] [No Name] }|
+ |
+ ]])
end)
it('works when switching value of \'winbar\'', function()
@@ -171,7 +206,7 @@ describe('winbar', function()
insert [[
just some
random text]]
- meths.set_option('winbar', 'Hello, I am a ruler: %l,%c')
+ meths.set_option_value('winbar', 'Hello, I am a ruler: %l,%c', {})
screen:expect{grid=[[
{1:Hello, I am a ruler: 2,11 }|
just some |
@@ -415,7 +450,7 @@ describe('winbar', function()
|
|
]])
- eq(3, meths.get_option('cmdheight'))
+ eq(3, meths.get_option_value('cmdheight', {}))
meths.input_mouse('left', 'drag', '', 1, 11, 10)
screen:expect([[
@@ -433,7 +468,7 @@ describe('winbar', function()
{2:[No Name] }|
|
]])
- eq(1, meths.get_option('cmdheight'))
+ eq(1, meths.get_option_value('cmdheight', {}))
end)
it('properly equalizes window height for window-local value', function()
@@ -678,3 +713,26 @@ describe('local winbar with tabs', function()
]]}
end)
end)
+
+it('winbar works properly when redrawing is postponed #23534', function()
+ clear({args = {
+ '-c', 'set laststatus=2 lazyredraw',
+ '-c', 'setlocal statusline=(statusline) winbar=(winbar)',
+ '-c', 'call nvim_input(":<Esc>")',
+ }})
+ local screen = Screen.new(60, 6)
+ screen:attach()
+ screen:set_default_attr_ids({
+ [0] = {foreground = Screen.colors.Blue, bold = true},
+ [1] = {bold = true},
+ [2] = {bold = true, reverse = true},
+ })
+ screen:expect([[
+ {1:(winbar) }|
+ ^ |
+ {0:~ }|
+ {0:~ }|
+ {2:(statusline) }|
+ |
+ ]])
+end)
diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua
index c032ac3030..0a7e7c1137 100644
--- a/test/functional/vimscript/api_functions_spec.lua
+++ b/test/functional/vimscript/api_functions_spec.lua
@@ -1,12 +1,13 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local lfs = require('lfs')
+local luv = require('luv')
local neq, eq, command = helpers.neq, helpers.eq, helpers.command
local clear, curbufmeths = helpers.clear, helpers.curbufmeths
local exc_exec, expect, eval = helpers.exc_exec, helpers.expect, helpers.eval
local insert, pcall_err = helpers.insert, helpers.pcall_err
local matches = helpers.matches
local meths = helpers.meths
+local feed = helpers.feed
describe('eval-API', function()
before_each(clear)
@@ -32,8 +33,8 @@ describe('eval-API', function()
local err = exc_exec('call nvim_get_current_buf("foo")')
eq('Vim(call):E118: Too many arguments for function: nvim_get_current_buf', err)
- err = exc_exec('call nvim_set_option("hlsearch")')
- eq('Vim(call):E119: Not enough arguments for function: nvim_set_option', err)
+ err = exc_exec('call nvim_set_option_value("hlsearch")')
+ eq('Vim(call):E119: Not enough arguments for function: nvim_set_option_value', err)
err = exc_exec('call nvim_buf_set_lines(1, 0, -1, [], ["list"])')
eq('Vim(call):E5555: API call: Wrong type for argument 4 when calling nvim_buf_set_lines, expecting Boolean', err)
@@ -48,10 +49,47 @@ describe('eval-API', function()
eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
end)
- it('cannot change texts if textlocked', function()
+ it('cannot change text or window if textlocked', function()
command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])")
matches('Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$',
pcall_err(command, "normal! yy"))
+
+ command("autocmd TextYankPost <buffer> ++once call nvim_open_term(0, {})")
+ matches('Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$',
+ pcall_err(command, "normal! yy"))
+
+ -- Functions checking textlock should also not be usable from <expr> mappings.
+ command("inoremap <expr> <f2> nvim_win_close(0, 1)")
+ eq('Vim(normal):E5555: API call: E565: Not allowed to change text or change window',
+ pcall_err(command, [[execute "normal i\<f2>"]]))
+
+ -- Text-changing functions gave a "Failed to save undo information" error when called from an
+ -- <expr> mapping outside do_cmdline() (msg_list == NULL), so use feed() to test this.
+ command("inoremap <expr> <f2> nvim_buf_set_text(0, 0, 0, 0, 0, ['hi'])")
+ meths.set_vvar('errmsg', '')
+ feed("i<f2><esc>")
+ eq('E5555: API call: E565: Not allowed to change text or change window',
+ meths.get_vvar('errmsg'))
+
+ -- Some functions checking textlock (usually those that may change the current window or buffer)
+ -- also ought to not be usable in the cmdwin.
+ local old_win = meths.get_current_win()
+ feed("q:")
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.set_current_win, old_win))
+
+ -- But others, like nvim_buf_set_lines(), which just changes text, is OK.
+ curbufmeths.set_lines(0, -1, 1, {"wow!"})
+ eq({'wow!'}, curbufmeths.get_lines(0, -1, 1))
+
+ -- Turning the cmdwin buffer into a terminal buffer would be pretty weird.
+ eq('E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
+ pcall_err(meths.open_term, 0, {}))
+
+ -- But turning a different buffer into a terminal from the cmdwin is OK.
+ local term_buf = meths.create_buf(false, true)
+ meths.open_term(term_buf, {})
+ eq('terminal', meths.get_option_value("buftype", {buf = term_buf}))
end)
it("use buffer numbers and windows ids as handles", function()
@@ -118,7 +156,7 @@ describe('eval-API', function()
end)
it('are highlighted by vim.vim syntax file', function()
- if lfs.attributes("build/runtime/syntax/vim/generated.vim",'uid') == nil then
+ if luv.fs_stat("build/runtime/syntax/vim/generated.vim").uid == nil then
pending("runtime was not built, skipping test")
return
end
@@ -133,7 +171,7 @@ describe('eval-API', function()
})
command("set ft=vim")
- command("let &rtp='build/runtime/,'.&rtp")
+ command("set rtp^=build/runtime/")
command("syntax on")
insert([[
call bufnr('%')
diff --git a/test/functional/vimscript/buf_functions_spec.lua b/test/functional/vimscript/buf_functions_spec.lua
index b521620320..2a5720fbd7 100644
--- a/test/functional/vimscript/buf_functions_spec.lua
+++ b/test/functional/vimscript/buf_functions_spec.lua
@@ -1,6 +1,6 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
local eq = helpers.eq
local clear = helpers.clear
@@ -9,13 +9,13 @@ local meths = helpers.meths
local command = helpers.command
local exc_exec = helpers.exc_exec
local bufmeths = helpers.bufmeths
-local winmeths = helpers.winmeths
local curbufmeths = helpers.curbufmeths
local curwinmeths = helpers.curwinmeths
local curtabmeths = helpers.curtabmeths
local get_pathsep = helpers.get_pathsep
local rmdir = helpers.rmdir
local pcall_err = helpers.pcall_err
+local mkdir = helpers.mkdir
local fname = 'Xtest-functional-eval-buf_functions'
local fname2 = fname .. '.2'
@@ -62,7 +62,7 @@ describe('bufname() function', function()
eq('', funcs.bufname('X'))
end)
before_each(function()
- lfs.mkdir(dirname)
+ mkdir(dirname)
end)
after_each(function()
rmdir(dirname)
@@ -70,7 +70,7 @@ describe('bufname() function', function()
it('returns expected buffer name', function()
eq('', funcs.bufname('%')) -- Buffer has no name yet
command('file ' .. fname)
- local wd = lfs.currentdir()
+ local wd = luv.cwd()
local sep = get_pathsep()
local curdirname = funcs.fnamemodify(wd, ':t')
for _, arg in ipairs({'%', 1, 'X', wd}) do
@@ -103,7 +103,7 @@ describe('bufnr() function', function()
it('returns expected buffer number', function()
eq(1, funcs.bufnr('%'))
command('file ' .. fname)
- local wd = lfs.currentdir()
+ local wd = luv.cwd()
local curdirname = funcs.fnamemodify(wd, ':t')
eq(1, funcs.bufnr(fname))
eq(1, funcs.bufnr(wd))
@@ -144,7 +144,7 @@ describe('bufwinnr() function', function()
eq(-1, funcs.bufwinnr('X'))
end)
before_each(function()
- lfs.mkdir(dirname)
+ mkdir(dirname)
end)
after_each(function()
rmdir(dirname)
@@ -188,7 +188,7 @@ describe('getbufline() function', function()
eq({}, funcs.getbufline(1, -1, 9999))
end)
it('returns expected lines', function()
- meths.set_option('hidden', true)
+ meths.set_option_value('hidden', true, {})
command('file ' .. fname)
curbufmeths.set_lines(0, 1, false, {'foo\0', '\0bar', 'baz'})
command('edit ' .. fname2)
@@ -268,24 +268,25 @@ describe('setbufvar() function', function()
end)
it('may set options, including window-local and global values', function()
local buf1 = meths.get_current_buf()
- eq(false, curwinmeths.get_option('number'))
+ eq(false, meths.get_option_value('number', {}))
command('split')
command('new')
eq(2, bufmeths.get_number(curwinmeths.get_buf()))
funcs.setbufvar(1, '&number', true)
local windows = curtabmeths.list_wins()
- eq(false, winmeths.get_option(windows[1], 'number'))
- eq(true, winmeths.get_option(windows[2], 'number'))
- eq(false, winmeths.get_option(windows[3], 'number'))
- eq(false, winmeths.get_option(meths.get_current_win(), 'number'))
+ eq(false, meths.get_option_value('number', {win=windows[1].id}))
+ eq(true, meths.get_option_value('number', {win=windows[2].id}))
+ eq(false, meths.get_option_value('number', {win=windows[3].id}))
+ eq(false, meths.get_option_value('number', {win=meths.get_current_win().id}))
- eq(true, meths.get_option('hidden'))
+
+ eq(true, meths.get_option_value('hidden', {}))
funcs.setbufvar(1, '&hidden', 0)
- eq(false, meths.get_option('hidden'))
+ eq(false, meths.get_option_value('hidden', {}))
- eq(false, bufmeths.get_option(buf1, 'autoindent'))
+ eq(false, meths.get_option_value('autoindent', {buf=buf1.id}))
funcs.setbufvar(1, '&autoindent', true)
- eq(true, bufmeths.get_option(buf1, 'autoindent'))
+ eq(true, meths.get_option_value('autoindent', {buf=buf1.id}))
eq('Vim(call):E355: Unknown option: xxx',
exc_exec('call setbufvar(1, "&xxx", 0)'))
end)
diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua
index 5ee84a6d13..17607f0794 100644
--- a/test/functional/vimscript/ctx_functions_spec.lua
+++ b/test/functional/vimscript/ctx_functions_spec.lua
@@ -375,6 +375,12 @@ describe('context functions', function()
eq(outofbounds, pcall_err(call, 'ctxset', {dummy = 1}, 0))
end)
+ it('errors when context dictionary is invalid', function()
+ call('ctxpush')
+ eq('Vim:E474: Failed to convert list to msgpack string buffer',
+ pcall_err(call, 'ctxset', { regs = { {} }, jumps = { {} } }))
+ end)
+
it('sets context dictionary at index in context stack', function()
nvim('set_var', 'one', 1)
nvim('set_var', 'Two', 2)
diff --git a/test/functional/vimscript/environ_spec.lua b/test/functional/vimscript/environ_spec.lua
index 9e19568249..52218d3cc9 100644
--- a/test/functional/vimscript/environ_spec.lua
+++ b/test/functional/vimscript/environ_spec.lua
@@ -26,6 +26,8 @@ end)
describe('empty $HOME', function()
local original_home = os.getenv('HOME')
+ before_each(clear)
+
-- recover $HOME after each test
after_each(function()
if original_home ~= nil then
diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua
index a8a901042b..ab0ffccd4d 100644
--- a/test/functional/vimscript/eval_spec.lua
+++ b/test/functional/vimscript/eval_spec.lua
@@ -12,7 +12,7 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
-local lfs = require('lfs')
+local mkdir = helpers.mkdir
local clear = helpers.clear
local eq = helpers.eq
local exc_exec = helpers.exc_exec
@@ -56,11 +56,11 @@ end)
describe("backtick expansion", function()
setup(function()
clear()
- lfs.mkdir("test-backticks")
+ mkdir("test-backticks")
write_file("test-backticks/file1", "test file 1")
write_file("test-backticks/file2", "test file 2")
write_file("test-backticks/file3", "test file 3")
- lfs.mkdir("test-backticks/subdir")
+ mkdir("test-backticks/subdir")
write_file("test-backticks/subdir/file4", "test file 4")
-- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
command('silent cd test-backticks')
@@ -153,11 +153,6 @@ end)
describe("uncaught exception", function()
before_each(clear)
- after_each(function()
- os.remove('throw1.vim')
- os.remove('throw2.vim')
- os.remove('throw3.vim')
- end)
it('is not forgotten #13490', function()
command('autocmd BufWinEnter * throw "i am error"')
@@ -173,10 +168,45 @@ describe("uncaught exception", function()
let result ..= 'X'
]]):format(i, i))
end
+ finally(function()
+ for i = 1, 3 do
+ os.remove('throw' .. i .. '.vim')
+ end
+ end)
+
command('set runtimepath+=. | let result = ""')
eq('throw1', exc_exec('try | runtime! throw*.vim | endtry'))
eq('123', eval('result'))
end)
+
+ it('multiline exception remains multiline #25350', function()
+ local screen = Screen.new(80, 11)
+ screen:set_default_attr_ids({
+ [1] = {bold = true, reverse = true}; -- MsgSeparator
+ [2] = {foreground = Screen.colors.White, background = Screen.colors.Red}; -- ErrorMsg
+ [3] = {bold = true, foreground = Screen.colors.SeaGreen}; -- MoreMsg
+ })
+ screen:attach()
+ exec_lua([[
+ function _G.Oops()
+ error("oops")
+ end
+ ]])
+ feed(':try\rlua _G.Oops()\rendtry\r')
+ screen:expect{grid=[[
+ {1: }|
+ :try |
+ : lua _G.Oops() |
+ : endtry |
+ {2:Error detected while processing :} |
+ {2:E5108: Error executing lua [string "<nvim>"]:2: oops} |
+ {2:stack traceback:} |
+ {2: [C]: in function 'error'} |
+ {2: [string "<nvim>"]:2: in function 'Oops'} |
+ {2: [string ":lua"]:1: in main chunk} |
+ {3:Press ENTER or type command to continue}^ |
+ ]]}
+ end)
end)
describe('listing functions using :function', function()
@@ -191,11 +221,10 @@ describe('listing functions using :function', function()
endfunction]]):format(num), exec_capture(('function <lambda>%s'):format(num)))
end)
- -- FIXME: If the same function is deleted, the crash still happens. #20790
it('does not crash if another function is deleted while listing', function()
local screen = Screen.new(80, 24)
screen:attach()
- matches('Vim%(function%):E454: function list was modified', pcall_err(exec_lua, [=[
+ matches('Vim%(function%):E454: Function list was modified$', pcall_err(exec_lua, [=[
vim.cmd([[
func Func1()
endfunc
@@ -219,4 +248,67 @@ describe('listing functions using :function', function()
]=]))
assert_alive()
end)
+
+ it('does not crash if the same function is deleted while listing', function()
+ local screen = Screen.new(80, 24)
+ screen:attach()
+ matches('Vim%(function%):E454: Function list was modified$', pcall_err(exec_lua, [=[
+ vim.cmd([[
+ func Func1()
+ endfunc
+ func Func2()
+ endfunc
+ func Func3()
+ endfunc
+ ]])
+
+ local ns = vim.api.nvim_create_namespace('test')
+
+ vim.ui_attach(ns, { ext_messages = true }, function(event, _, content)
+ if event == 'msg_show' and content[1][2] == 'function Func1()' then
+ vim.cmd('delfunc Func2')
+ end
+ end)
+
+ vim.cmd('function')
+
+ vim.ui_detach(ns)
+ ]=]))
+ assert_alive()
+ end)
+end)
+
+it('no double-free in garbage collection #16287', function()
+ clear()
+ -- Don't use exec() here as using a named script reproduces the issue better.
+ write_file('Xgarbagecollect.vim', [[
+ func Foo() abort
+ let s:args = [a:000]
+ let foo0 = ""
+ let foo1 = ""
+ let foo2 = ""
+ let foo3 = ""
+ let foo4 = ""
+ let foo5 = ""
+ let foo6 = ""
+ let foo7 = ""
+ let foo8 = ""
+ let foo9 = ""
+ let foo10 = ""
+ let foo11 = ""
+ let foo12 = ""
+ let foo13 = ""
+ let foo14 = ""
+ endfunc
+
+ set updatetime=1
+ call Foo()
+ call Foo()
+ ]])
+ finally(function()
+ os.remove('Xgarbagecollect.vim')
+ end)
+ command('source Xgarbagecollect.vim')
+ sleep(10)
+ assert_alive()
end)
diff --git a/test/functional/vimscript/executable_spec.lua b/test/functional/vimscript/executable_spec.lua
index 43e4a29e1a..2b8e3f6218 100644
--- a/test/functional/vimscript/executable_spec.lua
+++ b/test/functional/vimscript/executable_spec.lua
@@ -137,12 +137,16 @@ describe('executable() (Windows)', function()
end)
it('system([…]), jobstart([…]) use $PATHEXT #9569', function()
+ -- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
+ clear({env={PATHEXT=''}})
-- Invoking `cmdscript` should find/execute `cmdscript.cmd`.
eq('much success\n', call('system', {'test/functional/fixtures/cmdscript'}))
assert(0 < call('jobstart', {'test/functional/fixtures/cmdscript'}))
end)
it('full path with extension', function()
+ -- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
+ clear({env={PATHEXT=''}})
-- Some executable we can expect in the test env.
local exe = 'printargs-test'
local exedir = eval("fnamemodify(v:progpath, ':h')")
@@ -153,6 +157,8 @@ describe('executable() (Windows)', function()
end)
it('full path without extension', function()
+ -- Empty $PATHEXT defaults to ".com;.exe;.bat;.cmd".
+ clear({env={PATHEXT=''}})
-- Some executable we can expect in the test env.
local exe = 'printargs-test'
local exedir = eval("fnamemodify(v:progpath, ':h')")
diff --git a/test/functional/vimscript/execute_spec.lua b/test/functional/vimscript/execute_spec.lua
index 5fe3d787cb..bb28938708 100644
--- a/test/functional/vimscript/execute_spec.lua
+++ b/test/functional/vimscript/execute_spec.lua
@@ -4,6 +4,7 @@ local eval = helpers.eval
local clear = helpers.clear
local source = helpers.source
local exc_exec = helpers.exc_exec
+local pcall_err = helpers.pcall_err
local funcs = helpers.funcs
local Screen = require('test.functional.ui.screen')
local command = helpers.command
@@ -92,18 +93,14 @@ describe('execute()', function()
it('captures errors', function()
local ret
- ret = exc_exec('call execute(0.0)')
- eq('Vim(call):E806: using Float as a String', ret)
ret = exc_exec('call execute(v:_null_dict)')
- eq('Vim(call):E731: using Dictionary as a String', ret)
+ eq('Vim(call):E731: Using a Dictionary as a String', ret)
ret = exc_exec('call execute(function("tr"))')
- eq('Vim(call):E729: using Funcref as a String', ret)
- ret = exc_exec('call execute(["echo 42", 0.0, "echo 44"])')
- eq('Vim:E806: using Float as a String', ret)
+ eq('Vim(call):E729: Using a Funcref as a String', ret)
ret = exc_exec('call execute(["echo 42", v:_null_dict, "echo 44"])')
- eq('Vim:E731: using Dictionary as a String', ret)
+ eq('Vim:E731: Using a Dictionary as a String', ret)
ret = exc_exec('call execute(["echo 42", function("tr"), "echo 44"])')
- eq('Vim:E729: using Funcref as a String', ret)
+ eq('Vim:E729: Using a Funcref as a String', ret)
end)
it('captures output with highlights', function()
@@ -284,6 +281,14 @@ describe('execute()', function()
eq('42', eval('g:mes'))
end)
+ it('gives E493 instead of prompting on backwards range for ""', function()
+ command('split')
+ eq('Vim(windo):E493: Backwards range given: 2,1windo echo',
+ pcall_err(funcs.execute, '2,1windo echo', ''))
+ eq('Vim(windo):E493: Backwards range given: 2,1windo echo',
+ pcall_err(funcs.execute, {'2,1windo echo'}, ''))
+ end)
+
it('captures but does not display output for "silent"', function()
local screen = Screen.new(40, 5)
screen:attach()
@@ -321,11 +326,8 @@ describe('execute()', function()
it('propagates errors for "" and "silent"', function()
local ret
- ret = exc_exec('call execute(0.0, "")')
- eq('Vim(call):E806: using Float as a String', ret)
-
ret = exc_exec('call execute(v:_null_dict, "silent")')
- eq('Vim(call):E731: using Dictionary as a String', ret)
+ eq('Vim(call):E731: Using a Dictionary as a String', ret)
ret = exc_exec('call execute("echo add(1, 1)", "")')
eq('Vim(echo):E897: List or Blob required', ret)
diff --git a/test/functional/vimscript/glob_spec.lua b/test/functional/vimscript/glob_spec.lua
index b8807ecfcc..948a63f050 100644
--- a/test/functional/vimscript/glob_spec.lua
+++ b/test/functional/vimscript/glob_spec.lua
@@ -1,17 +1,18 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.functional.helpers')(after_each)
local clear, command, eval, eq = helpers.clear, helpers.command, helpers.eval, helpers.eq
+local mkdir = helpers.mkdir
before_each(function()
clear()
- lfs.mkdir('test-glob')
+ mkdir('test-glob')
-- Long path might cause "Press ENTER" prompt; use :silent to avoid it.
command('silent cd test-glob')
end)
after_each(function()
- lfs.rmdir('test-glob')
+ luv.fs_rmdir('test-glob')
end)
describe('glob()', function()
diff --git a/test/functional/vimscript/has_spec.lua b/test/functional/vimscript/has_spec.lua
index 2e26d603b3..78a761d370 100644
--- a/test/functional/vimscript/has_spec.lua
+++ b/test/functional/vimscript/has_spec.lua
@@ -1,8 +1,11 @@
local helpers = require('test.functional.helpers')(after_each)
-local eq = helpers.eq
+local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
+local connect = helpers.connect
+local eq = helpers.eq
local funcs = helpers.funcs
local is_os = helpers.is_os
+local nvim_prog = helpers.nvim_prog
describe('has()', function()
before_each(clear)
@@ -69,8 +72,22 @@ describe('has()', function()
end
end)
+ it('"gui_running"', function()
+ eq(0, funcs.has('gui_running'))
+ local tui = Screen.new(50,15)
+ local gui_session = connect(funcs.serverstart())
+ local gui = Screen.new(50,15)
+ eq(0, funcs.has('gui_running'))
+ tui:attach({ext_linegrid=true, rgb=true, stdin_tty=true, stdout_tty=true})
+ gui:attach({ext_multigrid=true, rgb=true}, gui_session)
+ eq(1, funcs.has('gui_running'))
+ tui:detach()
+ eq(1, funcs.has('gui_running'))
+ gui:detach()
+ eq(0, funcs.has('gui_running'))
+ end)
+
it('does not change v:shell_error', function()
- local nvim_prog = helpers.nvim_prog
funcs.system({nvim_prog, '-es', '+73cquit'})
funcs.has('python3') -- use a call whose implementation shells out
eq(73, funcs.eval('v:shell_error'))
diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua
index f50b39c2c5..e1179d29cc 100644
--- a/test/functional/vimscript/input_spec.lua
+++ b/test/functional/vimscript/input_spec.lua
@@ -222,17 +222,17 @@ describe('input()', function()
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input([])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input("", [])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input("", "", [])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input({"prompt": []})'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input({"default": []})'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call input({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call input({}, "default")'))
@@ -418,17 +418,17 @@ describe('inputdialog()', function()
eq('DEF2', meths.get_var('var'))
end)
it('errors out on invalid inputs', function()
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog([])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog("", [])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog("", "", [])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog({"prompt": []})'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog({"default": []})'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
exc_exec('call inputdialog({"completion": []})'))
eq('Vim(call):E5050: {opts} must be the only argument',
exc_exec('call inputdialog({}, "default")'))
@@ -452,8 +452,8 @@ end)
describe('confirm()', function()
-- oldtest: Test_confirm()
it('works', function()
- meths.set_option('more', false) -- Avoid hit-enter prompt
- meths.set_option('laststatus', 2)
+ meths.set_option_value('more', false, {}) -- Avoid hit-enter prompt
+ meths.set_option_value('laststatus', 2, {})
-- screen:expect() calls are needed to avoid feeding input too early
screen:expect({any = '%[No Name%]'})
@@ -512,13 +512,13 @@ describe('confirm()', function()
eq(1, meths.get_var('a'))
end
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
pcall_err(command, 'call confirm([])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
pcall_err(command, 'call confirm("Are you sure?", [])'))
eq('Vim(call):E745: Using a List as a Number',
pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", [])'))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
pcall_err(command, 'call confirm("Are you sure?", "&Yes\n&No\n", 0, [])'))
end)
@@ -552,7 +552,7 @@ describe('confirm()', function()
feed(':silent edit foo<cr>')
check_and_clear(':silent edit foo |\n')
- -- With API (via eval/VimL) call and shortmess+=F
+ -- With API (via eval/Vimscript) call and shortmess+=F
feed(':call nvim_command("edit x")<cr>')
check_and_clear(':call nvim_command("edit |\n')
diff --git a/test/functional/vimscript/json_functions_spec.lua b/test/functional/vimscript/json_functions_spec.lua
index 70d0934756..a9dab8431c 100644
--- a/test/functional/vimscript/json_functions_spec.lua
+++ b/test/functional/vimscript/json_functions_spec.lua
@@ -754,7 +754,7 @@ describe('json_encode() function', function()
end)
it('ignores improper values in &isprint', function()
- meths.set_option('isprint', '1')
+ meths.set_option_value('isprint', '1', {})
eq(1, eval('"\1" =~# "\\\\p"'))
eq('"\\u0001"', funcs.json_encode('\1'))
end)
diff --git a/test/functional/vimscript/let_spec.lua b/test/functional/vimscript/let_spec.lua
index 164fa86452..11417c5846 100644
--- a/test/functional/vimscript/let_spec.lua
+++ b/test/functional/vimscript/let_spec.lua
@@ -92,6 +92,20 @@ describe(':let', function()
]])
eq(1, eval('1'))
end)
+
+ it('can apply operator to boolean option', function()
+ eq(true, meths.get_option_value('equalalways', {}))
+ command('let &equalalways -= 1')
+ eq(false, meths.get_option_value('equalalways', {}))
+ command('let &equalalways += 1')
+ eq(true, meths.get_option_value('equalalways', {}))
+ command('let &equalalways *= 1')
+ eq(true, meths.get_option_value('equalalways', {}))
+ command('let &equalalways /= 1')
+ eq(true, meths.get_option_value('equalalways', {}))
+ command('let &equalalways %= 1')
+ eq(false, meths.get_option_value('equalalways', {}))
+ end)
end)
describe(':let and :const', function()
diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua
index ba1b4d7a76..acba5e9708 100644
--- a/test/functional/vimscript/map_functions_spec.lua
+++ b/test/functional/vimscript/map_functions_spec.lua
@@ -26,9 +26,12 @@ describe('maparg()', function()
rhs='bar',
expr=0,
sid=0,
+ scriptversion=1,
buffer=0,
nowait=0,
mode='n',
+ mode_bits=0x01,
+ abbr=0,
noremap=1,
lnum=0,
}
@@ -155,10 +158,13 @@ describe('maparg()', function()
buffer = 0,
expr = 0,
mode = 'n',
+ mode_bits = 0x01,
+ abbr = 0,
noremap = 1,
nowait = 0,
- script=0,
+ script = 0,
sid = 0,
+ scriptversion = 1,
silent = 0,
lnum = 0,
}
@@ -246,9 +252,9 @@ describe('mapset()', function()
end)
it('does not leak memory if lhs is missing', function()
- eq('Vim:E460: entries missing in mapset() dict argument',
+ eq('Vim:E460: Entries missing in mapset() dict argument',
pcall_err(exec_lua, [[vim.fn.mapset('n', false, {rhs = 'foo'})]]))
- eq('Vim:E460: entries missing in mapset() dict argument',
+ eq('Vim:E460: Entries missing in mapset() dict argument',
pcall_err(exec_lua, [[vim.fn.mapset('n', false, {callback = function() end})]]))
end)
end)
diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua
index 1153baac46..d4c36d835b 100644
--- a/test/functional/vimscript/null_spec.lua
+++ b/test/functional/vimscript/null_spec.lua
@@ -53,19 +53,15 @@ describe('NULL', function()
end)
end
describe('list', function()
- -- Incorrect behaviour
- -- FIXME Should error out with different message
- null_test('makes :unlet act as if it is not a list', ':unlet L[0]',
- 'Vim(unlet):E689: Can only index a List, Dictionary or Blob')
-
-- Subjectable behaviour
-
null_expr_test('is equal to empty list', 'L == []', 0, 1)
null_expr_test('is equal to empty list (reverse order)', '[] == L', 0, 1)
-- Correct behaviour
+ null_test('can be :unlet item with error message for empty list', ':unlet L[0]',
+ 'Vim(unlet):E684: List index out of range: 0')
null_expr_test('can be indexed with error message for empty list', 'L[0]',
- 'E684: list index out of range: 0', nil)
+ 'E684: List index out of range: 0', nil)
null_expr_test('can be splice-indexed', 'L[:]', 0, {})
null_expr_test('is not locked', 'islocked("v:_null_list")', 0, 0)
null_test('is accepted by :for', 'for x in L|throw x|endfor', 0)
@@ -80,7 +76,7 @@ describe('NULL', function()
null_expr_test('can be copied', 'copy(L)', 0, {})
null_expr_test('can be deepcopied', 'deepcopy(L)', 0, {})
null_expr_test('does not crash when indexed', 'L[1]',
- 'E684: list index out of range: 1', nil)
+ 'E684: List index out of range: 1', nil)
null_expr_test('does not crash call()', 'call("arglistid", L)', 0, 0)
null_expr_test('does not crash col()', 'col(L)', 0, 0)
null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0)
diff --git a/test/functional/vimscript/screenpos_spec.lua b/test/functional/vimscript/screenpos_spec.lua
index 75e5c02298..8b8276457d 100644
--- a/test/functional/vimscript/screenpos_spec.lua
+++ b/test/functional/vimscript/screenpos_spec.lua
@@ -1,12 +1,12 @@
local helpers = require('test.functional.helpers')(after_each)
local clear, eq, meths = helpers.clear, helpers.eq, helpers.meths
local command, funcs = helpers.command, helpers.funcs
+local feed = helpers.feed
before_each(clear)
describe('screenpos() function', function()
it('works in floating window with border', function()
- local bufnr = meths.create_buf(false, true)
local opts = {
relative='editor',
height=8,
@@ -18,34 +18,56 @@ describe('screenpos() function', function()
border='none',
focusable=1
}
- local float = meths.open_win(bufnr, false, opts)
+ local float = meths.open_win(meths.create_buf(false, true), false, opts)
command('redraw')
- local pos = funcs.screenpos(bufnr, 1, 1)
- eq(7, pos.row)
- eq(9, pos.col)
+ eq({row = 7, col = 9, endcol = 9, curscol = 9}, funcs.screenpos(float, 1, 1))
-- only left border
opts.border = {'', '', '', '', '', '', '', '|'}
meths.win_set_config(float, opts)
command('redraw')
- pos = funcs.screenpos(bufnr, 1, 1)
- eq(7, pos.row)
- eq(10, pos.col)
+ eq({row = 7, col = 10, endcol = 10, curscol = 10}, funcs.screenpos(float, 1, 1))
-- only top border
opts.border = {'', '_', '', '', '', '', '', ''}
meths.win_set_config(float, opts)
command('redraw')
- pos = funcs.screenpos(bufnr, 1, 1)
- eq(8, pos.row)
- eq(9, pos.col)
+ eq({row = 8, col = 9, endcol = 9, curscol = 9}, funcs.screenpos(float, 1, 1))
-- both left and top border
opts.border = 'single'
meths.win_set_config(float, opts)
command('redraw')
- pos = funcs.screenpos(bufnr, 1, 1)
- eq(8, pos.row)
- eq(10, pos.col)
+ eq({row = 8, col = 10, endcol = 10, curscol = 10}, funcs.screenpos(float, 1, 1))
+ end)
+
+ it('works for folded line with virt_lines attached to line above', function()
+ meths.buf_set_lines(0, 0, -1, true, {'aaa', 'bbb', 'ccc', 'ddd'})
+ local ns = meths.create_namespace('')
+ meths.buf_set_extmark(0, ns, 0, 0, { virt_lines = {{{'abb'}}, {{'acc'}}, {{'add'}}} })
+ command('2,3fold')
+ eq({row = 5, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 2, 1))
+ eq({row = 5, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 3, 1))
+ eq({row = 6, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 4, 1))
+
+ feed('<C-E>')
+ eq({row = 4, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 2, 1))
+ eq({row = 4, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 3, 1))
+ eq({row = 5, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 4, 1))
+
+ feed('<C-E>')
+ eq({row = 3, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 2, 1))
+ eq({row = 3, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 3, 1))
+ eq({row = 4, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 4, 1))
+
+ feed('<C-E>')
+ eq({row = 2, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 2, 1))
+ eq({row = 2, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 3, 1))
+ eq({row = 3, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 4, 1))
+
+ feed('<C-E>')
+ eq({row = 1, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 2, 1))
+ eq({row = 1, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 3, 1))
+ eq({row = 2, col = 1, endcol = 1, curscol = 1}, funcs.screenpos(0, 4, 1))
end)
end)
diff --git a/test/functional/vimscript/special_vars_spec.lua b/test/functional/vimscript/special_vars_spec.lua
index 14ccbc3827..217f0b2c2b 100644
--- a/test/functional/vimscript/special_vars_spec.lua
+++ b/test/functional/vimscript/special_vars_spec.lua
@@ -130,6 +130,12 @@ describe('Special values', function()
eq("v:false", eval('"" . v:false'))
end)
+ it('work with ?? (falsy operator)', function()
+ eq(true, eval('v:true ?? 42'))
+ eq(42, eval('v:false ?? 42'))
+ eq(42, eval('v:null ?? 42'))
+ end)
+
it('work with type()', function()
eq(6, funcs.type(true))
eq(6, funcs.type(false))
diff --git a/test/functional/vimscript/state_spec.lua b/test/functional/vimscript/state_spec.lua
new file mode 100644
index 0000000000..0508b8b1da
--- /dev/null
+++ b/test/functional/vimscript/state_spec.lua
@@ -0,0 +1,86 @@
+local helpers = require('test.functional.helpers')(after_each)
+local clear = helpers.clear
+local eq = helpers.eq
+local exec = helpers.exec
+local exec_lua = helpers.exec_lua
+local feed = helpers.feed
+local meths = helpers.meths
+local poke_eventloop = helpers.poke_eventloop
+
+before_each(clear)
+
+describe('state() function', function()
+ -- oldtest: Test_state()
+ it('works', function()
+ meths.ui_attach(80, 24, {}) -- Allow hit-enter-prompt
+
+ exec_lua([[
+ function _G.Get_state_mode()
+ _G.res = { vim.fn.state(), vim.api.nvim_get_mode().mode:sub(1, 1) }
+ end
+ function _G.Run_timer()
+ local timer = vim.uv.new_timer()
+ timer:start(0, 0, function()
+ _G.Get_state_mode()
+ timer:close()
+ end)
+ end
+ ]])
+ exec([[
+ call setline(1, ['one', 'two', 'three'])
+ map ;; gg
+ set complete=.
+ func RunTimer()
+ call timer_start(0, {id -> v:lua.Get_state_mode()})
+ endfunc
+ au Filetype foobar call v:lua.Get_state_mode()
+ ]])
+
+ -- Using a ":" command Vim is busy, thus "S" is returned
+ feed([[:call v:lua.Get_state_mode()<CR>]])
+ eq({ 'S', 'n' }, exec_lua('return _G.res'))
+
+ -- Using a timer callback
+ feed([[:call RunTimer()<CR>]])
+ poke_eventloop() -- Process pending input
+ poke_eventloop() -- Process time_event
+ eq({ 'c', 'n' }, exec_lua('return _G.res'))
+
+ -- Halfway a mapping
+ feed([[:call v:lua.Run_timer()<CR>;]])
+ meths.get_mode() -- Process pending input and luv timer callback
+ feed(';')
+ eq({ 'mS', 'n' }, exec_lua('return _G.res'))
+
+ -- An operator is pending
+ feed([[:call RunTimer()<CR>y]])
+ poke_eventloop() -- Process pending input
+ poke_eventloop() -- Process time_event
+ feed('y')
+ eq({ 'oSc', 'n' }, exec_lua('return _G.res'))
+
+ -- A register was specified
+ feed([[:call RunTimer()<CR>"r]])
+ poke_eventloop() -- Process pending input
+ poke_eventloop() -- Process time_event
+ feed('yy')
+ eq({ 'oSc', 'n' }, exec_lua('return _G.res'))
+
+ -- Insert mode completion
+ feed([[:call RunTimer()<CR>Got<C-N>]])
+ poke_eventloop() -- Process pending input
+ poke_eventloop() -- Process time_event
+ feed('<Esc>')
+ eq({ 'aSc', 'i' }, exec_lua('return _G.res'))
+
+ -- Autocommand executing
+ feed([[:set filetype=foobar<CR>]])
+ eq({ 'xS', 'n' }, exec_lua('return _G.res'))
+
+ -- messages scrolled
+ feed([[:call v:lua.Run_timer() | echo "one\ntwo\nthree"<CR>]])
+ meths.get_mode() -- Process pending input and luv timer callback
+ feed('<CR>')
+ eq({ 'Ss', 'r' }, exec_lua('return _G.res'))
+ end)
+end)
diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua
index 7ada1c4bea..90aab48d61 100644
--- a/test/functional/vimscript/system_spec.lua
+++ b/test/functional/vimscript/system_spec.lua
@@ -210,8 +210,8 @@ describe('system()', function()
end)
it('prints verbose information', function()
- nvim('set_option', 'shell', 'fake_shell')
- nvim('set_option', 'shellcmdflag', 'cmdflag')
+ nvim('set_option_value', 'shell', 'fake_shell', {})
+ nvim('set_option_value', 'shellcmdflag', 'cmdflag', {})
screen:try_resize(72, 14)
feed(':4verbose echo system("echo hi")<cr>')
@@ -335,12 +335,12 @@ describe('system()', function()
if is_os('win') then
eq("echoed\n", eval('system("echo echoed")'))
else
- eq("echoed", eval('system("echo -n echoed")'))
+ eq("echoed", eval('system("printf echoed")'))
end
end)
it('to backgrounded command does not crash', function()
-- This is indeterminate, just exercise the codepath. May get E5677.
- feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "echo -n echoed &")')
+ feed_command('call system(has("win32") ? "start /b /wait cmd /c echo echoed" : "printf echoed &")')
local v_errnum = string.match(eval("v:errmsg"), "^E%d*:")
if v_errnum then
eq("E5677:", v_errnum)
@@ -393,7 +393,7 @@ describe('system()', function()
end)
describe('with output containing NULs', function()
- local fname = 'Xtest'
+ local fname = 'Xtest_functional_vimscript_system_nuls'
before_each(create_file_with_nuls(fname))
after_each(delete_file(fname))
@@ -549,7 +549,7 @@ describe('systemlist()', function()
end)
describe('with output containing NULs', function()
- local fname = 'Xtest'
+ local fname = 'Xtest_functional_vimscript_systemlist_nuls'
before_each(function()
command('set ff=unix')
@@ -644,12 +644,12 @@ describe('shell :!', function()
if is_os('win') then
feed(':4verbose %!sort /R<cr>')
screen:expect{
- any=[[Executing command: .?& { Get%-Content .* | & sort /R } 2>&1 | Out%-File %-Encoding UTF8 .*; exit $LastExitCode"]]
+ any=[[Executing command: .?& { Get%-Content .* | & sort /R } 2>&1 | %%{ "$_" } | Out%-File .*; exit $LastExitCode"]]
}
else
feed(':4verbose %!sort -r<cr>')
screen:expect{
- any=[[Executing command: .?& { Get%-Content .* | & sort %-r } 2>&1 | Out%-File %-Encoding UTF8 .*; exit $LastExitCode"]]
+ any=[[Executing command: .?& { Get%-Content .* | & sort %-r } 2>&1 | %%{ "$_" } | Out%-File .*; exit $LastExitCode"]]
}
end
feed('<CR>')
diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua
index 1818a71ea2..a58cd6ae7f 100644
--- a/test/functional/vimscript/timer_spec.lua
+++ b/test/functional/vimscript/timer_spec.lua
@@ -251,15 +251,7 @@ describe('timers', function()
:good^ |
]])
command('let g:val = 1')
-
- screen:expect{grid=[[
- |
- {0:~ }|
- {0:~ }|
- {0:~ }|
- {0:~ }|
- :good^ |
- ]], intermediate=true, timeout=load_adjust(200)}
+ screen:expect_unchanged(true, load_adjust(200))
eq(2, eval('g:val'))
end)
diff --git a/test/functional/vimscript/writefile_spec.lua b/test/functional/vimscript/writefile_spec.lua
index 8c8da9dc88..88c19bd839 100644
--- a/test/functional/vimscript/writefile_spec.lua
+++ b/test/functional/vimscript/writefile_spec.lua
@@ -1,6 +1,7 @@
local helpers = require('test.functional.helpers')(after_each)
-local lfs = require('lfs')
+local luv = require('luv')
+local mkdir = helpers.mkdir
local clear = helpers.clear
local eq = helpers.eq
local funcs = helpers.funcs
@@ -19,16 +20,16 @@ local ddname_tail = '2'
local ddname = dname .. '/' .. ddname_tail
before_each(function()
- lfs.mkdir(dname)
- lfs.mkdir(ddname)
+ mkdir(dname)
+ mkdir(ddname)
clear()
end)
after_each(function()
os.remove(fname)
os.remove(dfname)
- lfs.rmdir(ddname)
- lfs.rmdir(dname)
+ luv.fs_rmdir(ddname)
+ luv.fs_rmdir(dname)
end)
describe('writefile()', function()
@@ -144,13 +145,11 @@ describe('writefile()', function()
pcall_err(command, ('call writefile(%s, "%s", "b")'):format(arg, fname)))
end
for _, args in ipairs({'[], %s, "b"', '[], "' .. fname .. '", %s'}) do
- eq('Vim(call):E806: using Float as a String',
- pcall_err(command, ('call writefile(%s)'):format(args:format('0.0'))))
- eq('Vim(call):E730: using List as a String',
+ eq('Vim(call):E730: Using a List as a String',
pcall_err(command, ('call writefile(%s)'):format(args:format('[]'))))
- eq('Vim(call):E731: using Dictionary as a String',
+ eq('Vim(call):E731: Using a Dictionary as a String',
pcall_err(command, ('call writefile(%s)'):format(args:format('{}'))))
- eq('Vim(call):E729: using Funcref as a String',
+ eq('Vim(call):E729: Using a Funcref as a String',
pcall_err(command, ('call writefile(%s)'):format(args:format('function("tr")'))))
end
eq('Vim(call):E5060: Unknown flag: «»',
diff --git a/test/helpers.lua b/test/helpers.lua
index 82ff23bef8..f9405c011d 100644
--- a/test/helpers.lua
+++ b/test/helpers.lua
@@ -1,10 +1,7 @@
-require('test.compat')
-local shared = require('vim.shared')
+local shared = vim
local assert = require('luassert')
local busted = require('busted')
local luv = require('luv')
-local lfs = require('lfs')
-local relpath = require('pl.path').relpath
local Paths = require('test.cmakeconfig.paths')
assert:set_parameter('TableFormatLevel', 100)
@@ -18,10 +15,46 @@ local function shell_quote(str)
end
end
+--- @class test.helpers
local module = {
REMOVE_THIS = {},
}
+--- @param p string
+--- @return string
+local function relpath(p)
+ p = vim.fs.normalize(p)
+ local cwd = luv.cwd()
+ return p:gsub("^" .. cwd)
+end
+
+--- @param path string
+--- @return boolean
+function module.isdir(path)
+ if not path then
+ return false
+ end
+ local stat = luv.fs_stat(path)
+ if not stat then
+ return false
+ end
+ return stat.type == 'directory'
+end
+
+--- @param path string
+--- @return boolean
+function module.isfile(path)
+ if not path then
+ return false
+ end
+ local stat = luv.fs_stat(path)
+ if not stat then
+ return false
+ end
+ return stat.type == 'file'
+end
+
+--- @return string
function module.argss_to_cmd(...)
local cmd = ''
for i = 1, select('#', ...) do
@@ -156,10 +189,16 @@ function module.pcall(fn, ...)
local errmsg = tostring(rv):gsub('([%s<])vim[/\\]([^%s:/\\]+):%d+', '%1\xffvim\xff%2:0')
:gsub('[^%s<]-[/\\]([^%s:/\\]+):%d+', '.../%1:0')
:gsub('\xffvim\xff', 'vim/')
+
-- Scrub numbers in paths/stacktraces:
-- shared.lua:0: in function 'gsplit'
-- shared.lua:0: in function <shared.lua:0>'
- errmsg = errmsg:gsub('([^%s]):%d+', '%1:0')
+ errmsg = errmsg:gsub('([^%s].lua):%d+', '%1:0')
+ -- [string "<nvim>"]:0:
+ -- [string ":lua"]:0:
+ -- [string ":luado"]:0:
+ errmsg = errmsg:gsub('(%[string "[^"]+"%]):%d+', '%1:0')
+
-- Scrub tab chars:
errmsg = errmsg:gsub('\t', ' ')
-- In Lua 5.1, we sometimes get a "(tail call): ?" on the last line.
@@ -228,16 +267,16 @@ function module.glob(initial_path, re, exc_re)
while #paths_to_check > 0 do
local cur_path = paths_to_check[#paths_to_check]
paths_to_check[#paths_to_check] = nil
- for e in lfs.dir(cur_path) do
+ for e in vim.fs.dir(cur_path) do
local full_path = cur_path .. '/' .. e
local checked_path = full_path:sub(#initial_path + 1)
if (not is_excluded(checked_path)) and e:sub(1, 1) ~= '.' then
- local attrs = lfs.attributes(full_path)
- if attrs then
- local check_key = attrs.dev .. ':' .. tostring(attrs.ino)
+ local stat = luv.fs_stat(full_path)
+ if stat then
+ local check_key = stat.dev .. ':' .. tostring(stat.ino)
if not checked_files[check_key] then
checked_files[check_key] = true
- if attrs.mode == 'directory' then
+ if stat.type == 'directory' then
paths_to_check[#paths_to_check + 1] = full_path
elseif not re or checked_path:match(re) then
ret[#ret + 1] = full_path
@@ -253,8 +292,8 @@ end
function module.check_logs()
local log_dir = os.getenv('LOG_DIR')
local runtime_errors = {}
- if log_dir and lfs.attributes(log_dir, 'mode') == 'directory' then
- for tail in lfs.dir(log_dir) do
+ if log_dir and module.isdir(log_dir) then
+ for tail in vim.fs.dir(log_dir) do
if tail:sub(1, 30) == 'valgrind-' or tail:find('san%.') then
local file = log_dir .. '/' .. tail
local fd = io.open(file)
@@ -312,7 +351,7 @@ function module.is_os(s)
or s == 'bsd') then
error('unknown platform: '..tostring(s))
end
- return ((s == 'win' and module.sysname():find('windows'))
+ return not not ((s == 'win' and (module.sysname():find('windows') or module.sysname():find('mingw')))
or (s == 'mac' and module.sysname() == 'darwin')
or (s == 'freebsd' and module.sysname() == 'freebsd')
or (s == 'openbsd' and module.sysname() == 'openbsd')
@@ -371,8 +410,12 @@ end
local tests_skipped = 0
-function module.check_cores(app, force)
- app = app or 'build/bin/nvim'
+function module.check_cores(app, force) -- luacheck: ignore
+ -- Temporary workaround: skip core check as it interferes with CI.
+ if true then
+ return
+ end
+ app = app or 'build/bin/nvim' -- luacheck: ignore
local initial_path, re, exc_re
local gdb_db_cmd = 'gdb -n -batch -ex "thread apply all bt full" "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"'
local lldb_db_cmd = 'lldb -Q -o "bt all" -f "$_NVIM_TEST_APP" -c "$_NVIM_TEST_CORE"'
@@ -432,6 +475,7 @@ function module.check_cores(app, force)
end
end
+--- @return string?
function module.repeated_read_cmd(...)
for _ = 1, 10 do
local stream = module.popen_r(...)
@@ -531,19 +575,24 @@ function module.concat_tables(...)
return ret
end
+--- @param str string
+--- @param leave_indent? integer
+--- @return string
function module.dedent(str, leave_indent)
-- find minimum common indent across lines
- local indent = nil
+ local indent --- @type string?
for line in str:gmatch('[^\n]+') do
local line_indent = line:match('^%s+') or ''
if indent == nil or #line_indent < #indent then
indent = line_indent
end
end
- if indent == nil or #indent == 0 then
+
+ if not indent or #indent == 0 then
-- no minimum common indent
return str
end
+
local left_indent = (' '):rep(leave_indent or 0)
-- create a pattern for the indent
indent = indent:gsub('%s', '[ \t]')
@@ -839,6 +888,11 @@ function module.read_nvim_log(logfile, ci_rename)
return log
end
+function module.mkdir(path)
+ -- 493 is 0755 in decimal
+ return luv.fs_mkdir(path, 493)
+end
+
module = shared.tbl_extend('error', module, Paths, shared, require('test.deprecated'))
return module
diff --git a/test/lua_runner.lua b/test/lua_runner.lua
new file mode 100644
index 0000000000..686df9feec
--- /dev/null
+++ b/test/lua_runner.lua
@@ -0,0 +1,83 @@
+local platform = vim.uv.os_uname()
+local deps_install_dir = table.remove(_G.arg, 1)
+local subcommand = table.remove(_G.arg, 1)
+local suffix = (platform and platform.sysname:lower():find'windows') and '.dll' or '.so'
+package.path = deps_install_dir.."/share/lua/5.1/?.lua;"..deps_install_dir.."/share/lua/5.1/?/init.lua;"..package.path
+package.cpath = deps_install_dir.."/lib/lua/5.1/?"..suffix..";"..package.cpath;
+
+local uv = vim.uv
+
+-- we use busted and luacheck and their lua dependencies
+-- But installing their binary dependencies with luarocks is very
+-- slow, replace them with vim.uv wrappers
+
+local system = {}
+package.loaded['system.core'] = system
+function system.monotime()
+ uv.update_time()
+ return uv.now()*1e-3
+end
+function system.gettime()
+ local sec, usec = uv.gettimeofday()
+ return sec+usec*1e-6
+end
+function system.sleep(sec)
+ uv.sleep(sec*1e3)
+end
+
+local term = {}
+package.loaded['term.core'] = term
+function term.isatty(_)
+ return uv.guess_handle(1) == 'tty'
+end
+
+local lfs = {_VERSION = 'fake'}
+package.loaded['lfs'] = lfs
+
+function lfs.attributes(path, attr)
+ local stat = uv.fs_stat(path)
+ if attr == 'mode' then
+ return stat and stat.type or ''
+ elseif attr == 'modification' then
+ if not stat then return nil end
+ local mtime = stat.mtime
+ return mtime.sec + mtime.nsec*1e-9
+ else
+ error('not implemented')
+ end
+end
+
+function lfs.currentdir()
+ return uv.cwd()
+end
+
+function lfs.chdir(dir)
+ local status, err = pcall(uv.chdir, dir)
+ if status then
+ return true
+ else
+ return nil, err
+ end
+end
+
+function lfs.dir(path)
+ local fs = uv.fs_scandir(path)
+ return function()
+ if not fs then
+ return
+ end
+ return uv.fs_scandir_next(fs)
+ end
+end
+
+function lfs.mkdir(dir)
+ return uv.fs_mkdir(dir, 493) -- octal 755
+end
+
+if subcommand == "busted" then
+ require 'busted.runner'({ standalone = false })
+elseif subcommand == "luacheck" then
+ require 'luacheck.main'
+else
+ error 'unknown subcommand'
+end
diff --git a/src/nvim/testdir/samples/memfile_test.c b/test/old/memfile_test.c
index 73f67fb250..a352619469 100644
--- a/src/nvim/testdir/samples/memfile_test.c
+++ b/test/old/memfile_test.c
@@ -1,8 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-// uncrustify:off
-
/* vi:set ts=8 sts=4 sw=4 noet:
*
* VIM - Vi IMproved by Bram Moolenaar
@@ -20,11 +15,11 @@
#undef NDEBUG
#include <assert.h>
-/* Must include main.c because it contains much more than just main() */
+// Must include main.c because it contains much more than just main()
#define NO_VIM_MAIN
#include "main.c"
-/* This file has to be included because the tested functions are static */
+// This file has to be included because the tested functions are static
#include "memfile.c"
#define index_to_key(i) ((i) ^ 15167)
@@ -39,26 +34,26 @@ test_mf_hash(void)
mf_hashtab_T ht;
mf_hashitem_T *item;
blocknr_T key;
- size_t i;
- size_t num_buckets;
+ long_u i;
+ long_u num_buckets;
mf_hash_init(&ht);
- /* insert some items and check invariants */
+ // insert some items and check invariants
for (i = 0; i < TEST_COUNT; i++)
{
assert(ht.mht_count == i);
- /* check that number of buckets is a power of 2 */
+ // check that number of buckets is a power of 2
num_buckets = ht.mht_mask + 1;
assert(num_buckets > 0 && (num_buckets & (num_buckets - 1)) == 0);
- /* check load factor */
+ // check load factor
assert(ht.mht_count <= (num_buckets << MHT_LOG_LOAD_FACTOR));
if (i < (MHT_INIT_SIZE << MHT_LOG_LOAD_FACTOR))
{
- /* first expansion shouldn't have occurred yet */
+ // first expansion shouldn't have occurred yet
assert(num_buckets == MHT_INIT_SIZE);
assert(ht.mht_buckets == ht.mht_small_buckets);
}
@@ -71,8 +66,8 @@ test_mf_hash(void)
key = index_to_key(i);
assert(mf_hash_find(&ht, key) == NULL);
- /* allocate and add new item */
- item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);
+ // allocate and add new item
+ item = LALLOC_CLEAR_ONE(mf_hashitem_T);
assert(item != NULL);
item->mhi_key = key;
mf_hash_add_item(&ht, item);
@@ -81,13 +76,13 @@ test_mf_hash(void)
if (ht.mht_mask + 1 != num_buckets)
{
- /* hash table was expanded */
+ // hash table was expanded
assert(ht.mht_mask + 1 == num_buckets * MHT_GROWTH_FACTOR);
assert(i + 1 == (num_buckets << MHT_LOG_LOAD_FACTOR));
}
}
- /* check presence of inserted items */
+ // check presence of inserted items
for (i = 0; i < TEST_COUNT; i++)
{
key = index_to_key(i);
@@ -96,7 +91,7 @@ test_mf_hash(void)
assert(item->mhi_key == key);
}
- /* delete some items */
+ // delete some items
for (i = 0; i < TEST_COUNT; i++)
{
if (i % 100 < 70)
@@ -119,7 +114,7 @@ test_mf_hash(void)
}
}
- /* check again */
+ // check again
for (i = 0; i < TEST_COUNT; i++)
{
key = index_to_key(i);
@@ -136,7 +131,7 @@ test_mf_hash(void)
}
}
- /* free hash table and all remaining items */
+ // free hash table and all remaining items
mf_hash_free_all(&ht);
}
diff --git a/src/nvim/testdir/Make_all.mak b/test/old/testdir/Make_all.mak
index b917877422..b917877422 100644
--- a/src/nvim/testdir/Make_all.mak
+++ b/test/old/testdir/Make_all.mak
diff --git a/src/nvim/testdir/Makefile b/test/old/testdir/Makefile
index 9511b311e6..b5ca6a17b0 100644
--- a/src/nvim/testdir/Makefile
+++ b/test/old/testdir/Makefile
@@ -70,6 +70,7 @@ report:
then echo TEST FAILURE; exit 1; \
else echo ALL DONE; \
fi"
+ @rm -f starttime
test1.out: $(NVIM_PRG)
@@ -86,10 +87,13 @@ fixff:
-$(NVIM_PRG) $(NO_INITS) -u unix.vim "+argdo set ff=dos|upd" +q \
dotest.in
+# File to delete when testing starts
+CLEANUP_FILES = test.log messages starttime
+
# Execute an individual new style test, e.g.:
# make test_largefile
$(NEW_TESTS):
- rm -f $@.res test.log messages
+ rm -f $@.res $(CLEANUP_FILES)
@MAKEFLAGS=--no-print-directory $(MAKE) -f Makefile $@.res
@cat messages
@if test -f test.log; then \
@@ -108,9 +112,8 @@ CLEAN_FILES := *.out \
*.rej \
*.orig \
*.tlog \
- test.log \
test_result.log \
- messages \
+ $(CLEANUP_FILES) \
$(RM_ON_RUN) \
$(RM_ON_START) \
valgrind.* \
@@ -132,7 +135,7 @@ test1.out: .gdbinit test1.in
nolog:
@echo "[OLDTEST-PREP] Removing test.log and messages"
- @rm -f test.log messages
+ @rm -f test_result.log $(CLEANUP_FILES)
# New style of tests uses Vim script with assert calls. These are easier
diff --git a/src/nvim/testdir/README.txt b/test/old/testdir/README.txt
index b8bc52f1e6..b8bc52f1e6 100644
--- a/src/nvim/testdir/README.txt
+++ b/test/old/testdir/README.txt
diff --git a/src/nvim/testdir/check.vim b/test/old/testdir/check.vim
index 680a59006b..647e25b214 100644
--- a/src/nvim/testdir/check.vim
+++ b/test/old/testdir/check.vim
@@ -100,6 +100,30 @@ func CheckLinux()
endif
endfunc
+" Command to check for not running on a BSD system.
+command CheckNotBSD call CheckNotBSD()
+func CheckNotBSD()
+ if has('bsd')
+ throw 'Skipped: does not work on BSD'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS
+command CheckNotMac call CheckNotMac()
+func CheckNotMac()
+ if has('mac')
+ throw 'Skipped: does not work on MacOS'
+ endif
+endfunc
+
+" Command to check for not running on a MacOS M1 system.
+command CheckNotMacM1 call CheckNotMacM1()
+func CheckNotMacM1()
+ if has('mac') && system('uname -a') =~ '\<arm64\>'
+ throw 'Skipped: does not work on MacOS M1'
+ endif
+endfunc
+
" Command to check that making screendumps is supported.
" Caller must source screendump.vim
command CheckScreendump call CheckScreendump()
@@ -168,7 +192,7 @@ endfunc
" Command to check for not running under ASAN
command CheckNotAsan call CheckNotAsan()
func CheckNotAsan()
- if execute('version') =~# '-fsanitize=[a-z,]*\<address\>'
+ if execute('verbose version') =~# '-fsanitize=[a-z,]*\<address\>'
throw 'Skipped: does not work with ASAN'
endif
endfunc
diff --git a/test/old/testdir/crash/bt_quickfix1_poc b/test/old/testdir/crash/bt_quickfix1_poc
new file mode 100644
index 0000000000..97993fde52
--- /dev/null
+++ b/test/old/testdir/crash/bt_quickfix1_poc
@@ -0,0 +1,5 @@
+au BufReadPre * exe 'sn' .. expand("<abuf>")
+call writefile([''],'X')
+sil! e X
+call writefile([''],'X')
+sil! e X
diff --git a/test/old/testdir/crash/bt_quickfix_poc b/test/old/testdir/crash/bt_quickfix_poc
new file mode 100644
index 0000000000..bf02b4dcb8
--- /dev/null
+++ b/test/old/testdir/crash/bt_quickfix_poc
@@ -0,0 +1,9 @@
+comman!-narg=* Xexpr <mods>lex<args>
+auto BufReadPre * exe"sn" ..expand("<abuf>")
+fu Xautocmd_changelist()
+cal writefile(['Xtestfile2:4:4'],'Xerr')
+ sil! edi Xerr
+Xexpr 'Xtestfile:4:4'
+endf
+call Xautocmd_changelist()
+call Xautocmd_changelist() \ No newline at end of file
diff --git a/test/old/testdir/crash/crash_scrollbar b/test/old/testdir/crash/crash_scrollbar
new file mode 100644
index 0000000000..1de5905228
--- /dev/null
+++ b/test/old/testdir/crash/crash_scrollbar
@@ -0,0 +1,2 @@
+" this goes to insert mode and presses key k_VerScrollbar which may cause a redraw in exmode, which used ot crash Vim
+norm oX
diff --git a/test/old/testdir/crash/editing_arg_idx_POC_1 b/test/old/testdir/crash/editing_arg_idx_POC_1
new file mode 100644
index 0000000000..5d048d0340
--- /dev/null
+++ b/test/old/testdir/crash/editing_arg_idx_POC_1
Binary files differ
diff --git a/test/old/testdir/crash/poc1 b/test/old/testdir/crash/poc1
new file mode 100644
index 0000000000..ec223f16b8
--- /dev/null
+++ b/test/old/testdir/crash/poc1
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf1 b/test/old/testdir/crash/poc_huaf1
new file mode 100644
index 0000000000..0d0ea475c1
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf1
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf2 b/test/old/testdir/crash/poc_huaf2
new file mode 100644
index 0000000000..4867e0f956
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf2
Binary files differ
diff --git a/test/old/testdir/crash/poc_huaf3 b/test/old/testdir/crash/poc_huaf3
new file mode 100644
index 0000000000..7e38a9a17c
--- /dev/null
+++ b/test/old/testdir/crash/poc_huaf3
Binary files differ
diff --git a/test/old/testdir/crash/poc_tagfunc.vim b/test/old/testdir/crash/poc_tagfunc.vim
new file mode 100644
index 0000000000..49d9b6f719
--- /dev/null
+++ b/test/old/testdir/crash/poc_tagfunc.vim
@@ -0,0 +1,6 @@
+fu Tagfunc(t,f,o)
+ bw
+endf
+set tagfunc=Tagfunc
+n0
+sil0norm0i
diff --git a/test/old/testdir/crash/vim_msg_trunc_poc b/test/old/testdir/crash/vim_msg_trunc_poc
new file mode 100644
index 0000000000..73b04bec35
--- /dev/null
+++ b/test/old/testdir/crash/vim_msg_trunc_poc
@@ -0,0 +1 @@
+lv\ngggggi;norm:᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌󠁲᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌᠌
diff --git a/test/old/testdir/crash/vim_regsub_both b/test/old/testdir/crash/vim_regsub_both
new file mode 100644
index 0000000000..a82b205c8f
--- /dev/null
+++ b/test/old/testdir/crash/vim_regsub_both
@@ -0,0 +1,10 @@
+fu R()
+sil!norm0z=
+endf
+cal R()
+s/\%')/\=R()
+d
+no0 normyynore sm:vs0@vvvvvvvvvvse()dir(Xtest=csd{so88
+vs
+0scr
+so
diff --git a/test/old/testdir/crash/vim_regsub_both_poc b/test/old/testdir/crash/vim_regsub_both_poc
new file mode 100644
index 0000000000..19a57114be
--- /dev/null
+++ b/test/old/testdir/crash/vim_regsub_both_poc
Binary files differ
diff --git a/src/nvim/testdir/dotest.in b/test/old/testdir/dotest.in
index 2ef4576f4e..2ef4576f4e 100644
--- a/src/nvim/testdir/dotest.in
+++ b/test/old/testdir/dotest.in
diff --git a/src/nvim/testdir/load.vim b/test/old/testdir/load.vim
index 5697ee7304..5697ee7304 100644
--- a/src/nvim/testdir/load.vim
+++ b/test/old/testdir/load.vim
diff --git a/test/old/testdir/mouse.vim b/test/old/testdir/mouse.vim
new file mode 100644
index 0000000000..b9f1714dd9
--- /dev/null
+++ b/test/old/testdir/mouse.vim
@@ -0,0 +1,97 @@
+" Helper functions for generating mouse events
+
+let g:Ttymouse_values = ['sgr']
+let g:Ttymouse_dec = []
+let g:Ttymouse_netterm = []
+
+func MouseLeftClick(row, col)
+ call nvim_input_mouse('left', 'press', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseMiddleClick(row, col)
+ call nvim_input_mouse('middle', 'press', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseRightClick(row, col)
+ call nvim_input_mouse('right', 'press', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseCtrlLeftClick(row, col)
+ call nvim_input_mouse('left', 'press', 'C', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseCtrlRightClick(row, col)
+ call nvim_input_mouse('right', 'press', 'C', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseAltLeftClick(row, col)
+ call nvim_input_mouse('left', 'press', 'A', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseAltRightClick(row, col)
+ call nvim_input_mouse('right', 'press', 'A', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseLeftRelease(row, col)
+ call nvim_input_mouse('left', 'release', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseMiddleRelease(row, col)
+ call nvim_input_mouse('middle', 'release', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseRightRelease(row, col)
+ call nvim_input_mouse('right', 'release', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseLeftDrag(row, col)
+ call nvim_input_mouse('left', 'drag', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseWheelUp(row, col)
+ call nvim_input_mouse('wheel', 'up', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseWheelDown(row, col)
+ call nvim_input_mouse('wheel', 'down', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseWheelLeft(row, col)
+ call nvim_input_mouse('wheel', 'left', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+func MouseWheelRight(row, col)
+ call nvim_input_mouse('wheel', 'right', '', 0, a:row - 1, a:col - 1)
+ call getchar(1)
+ call feedkeys('', 'x!')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/pyxfile/py2_magic.py b/test/old/testdir/pyxfile/py2_magic.py
index 819892fd16..819892fd16 100644
--- a/src/nvim/testdir/pyxfile/py2_magic.py
+++ b/test/old/testdir/pyxfile/py2_magic.py
diff --git a/src/nvim/testdir/pyxfile/py2_shebang.py b/test/old/testdir/pyxfile/py2_shebang.py
index 13bfc491ec..13bfc491ec 100644
--- a/src/nvim/testdir/pyxfile/py2_shebang.py
+++ b/test/old/testdir/pyxfile/py2_shebang.py
diff --git a/src/nvim/testdir/pyxfile/py3_magic.py b/test/old/testdir/pyxfile/py3_magic.py
index d4b7ee0071..d4b7ee0071 100644
--- a/src/nvim/testdir/pyxfile/py3_magic.py
+++ b/test/old/testdir/pyxfile/py3_magic.py
diff --git a/src/nvim/testdir/pyxfile/py3_shebang.py b/test/old/testdir/pyxfile/py3_shebang.py
index ec05808ca4..ec05808ca4 100644
--- a/src/nvim/testdir/pyxfile/py3_shebang.py
+++ b/test/old/testdir/pyxfile/py3_shebang.py
diff --git a/src/nvim/testdir/pyxfile/pyx.py b/test/old/testdir/pyxfile/pyx.py
index 261a6512c0..261a6512c0 100644
--- a/src/nvim/testdir/pyxfile/pyx.py
+++ b/test/old/testdir/pyxfile/pyx.py
diff --git a/src/nvim/testdir/runnvim.sh b/test/old/testdir/runnvim.sh
index 3a0a94b6bf..4d8ea0527d 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/test/old/testdir/runnvim.sh
@@ -22,13 +22,11 @@ main() {(
i=$(( i+1 ))
done
- export CI_DIR="$root/ci"
BUILD_DIR="$(dirname "$nvim_prg")/.."
export BUILD_DIR
export FAILED=0
- . "$CI_DIR/common/suite.sh"
- . "$CI_DIR/common/test.sh"
+ . $(dirname $0)/test.sh
# Redirect XDG_CONFIG_HOME so users local config doesn't interfere
export XDG_CONFIG_HOME="$root"
diff --git a/src/nvim/testdir/runnvim.vim b/test/old/testdir/runnvim.vim
index a46e2d3fc0..2db60d05b3 100644
--- a/src/nvim/testdir/runnvim.vim
+++ b/test/old/testdir/runnvim.vim
@@ -23,11 +23,13 @@ function Main()
set lines=25
set columns=80
enew
- let job = termopen(args, s:logger)
+ " FIXME: using termopen() hangs on Windows CI
+ let job = has('win32') ? jobstart(args, s:logger) : termopen(args, s:logger)
let results = jobwait([job], 5 * 60 * 1000)
" TODO(ZyX-I): Get colors
let screen = getline(1, '$')
- bwipeout! " kills the job always.
+ call jobstop(job) " kills the job always.
+ bwipeout!
let stringified_events = map(s:logger.d_events,
\'v:val[0] . ": " . ' .
\'join(map(v:val[1], '.
diff --git a/src/nvim/testdir/runtest.vim b/test/old/testdir/runtest.vim
index 3c5699af73..928bf7693f 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/test/old/testdir/runtest.vim
@@ -61,7 +61,16 @@ if &lines < 24 || &columns < 80
endif
if has('reltime')
- let s:start_time = reltime()
+ let s:run_start_time = reltime()
+
+ if !filereadable('starttime')
+ " first test, store the overall test starting time
+ let s:test_start_time = localtime()
+ call writefile([string(s:test_start_time)], 'starttime')
+ else
+ " second or later test, read the overall test starting time
+ let s:test_start_time = readfile('starttime')[0]->str2nr()
+ endif
endif
" Always use forward slashes.
@@ -75,7 +84,9 @@ source setup.vim
set nocp viminfo+=nviminfo
" Use utf-8 by default, instead of whatever the system default happens to be.
-" Individual tests can overrule this at the top of the file.
+" Individual tests can overrule this at the top of the file and use
+" g:orig_encoding if needed.
+let g:orig_encoding = &encoding
set encoding=utf-8
" REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for
@@ -85,6 +96,9 @@ let s:test_script_fname = expand('%')
au! SwapExists * call HandleSwapExists()
func HandleSwapExists()
if exists('g:ignoreSwapExists')
+ if type(g:ignoreSwapExists) == v:t_string
+ let v:swapchoice = g:ignoreSwapExists
+ endif
return
endif
" Ignore finding a swap file for the test script (the user might be
@@ -121,6 +135,7 @@ if has('mac')
let $BASH_SILENCE_DEPRECATION_WARNING = 1
endif
+
" Prepare for calling test_garbagecollect_now().
let v:testing = 1
@@ -139,10 +154,57 @@ func GetAllocId(name)
return lnum - top - 1
endfunc
+if has('reltime')
+ let g:func_start = reltime()
+endif
+
+" Get the list of swap files in the current directory.
+func s:GetSwapFileList()
+ let save_dir = &directory
+ let &directory = '.'
+ let files = swapfilelist()
+ let &directory = save_dir
+
+ " remove a match with runtest.vim
+ let idx = indexof(files, 'v:val =~ "runtest.vim."')
+ if idx >= 0
+ call remove(files, idx)
+ endif
+
+ return files
+endfunc
+
+" A previous (failed) test run may have left swap files behind. Delete them
+" before running tests again, they might interfere.
+for name in s:GetSwapFileList()
+ call delete(name)
+endfor
+unlet! name
+
+
+" Invoked when a test takes too much time.
+func TestTimeout(id)
+ split test.log
+ call append(line('$'), '')
+ call append(line('$'), 'Test timed out: ' .. g:testfunc)
+ write
+ call add(v:errors, 'Test timed out: ' . g:testfunc)
+
+ cquit! 42
+endfunc
+
func RunTheTest(test)
- echo 'Executing ' . a:test
+ let prefix = ''
if has('reltime')
- let func_start = reltime()
+ let prefix = strftime('%M:%S', localtime() - s:test_start_time) .. ' '
+ let g:func_start = reltime()
+ endif
+ echo prefix .. 'Executing ' .. a:test
+
+ if has('timers')
+ " No test should take longer than 30 seconds. If it takes longer we
+ " assume we are stuck and need to break out.
+ let test_timeout_timer = timer_start(30000, 'TestTimeout')
endif
" Avoid stopping at the "hit enter" prompt
@@ -171,6 +233,8 @@ func RunTheTest(test)
endtry
endif
+ let skipped = v:false
+
au VimLeavePre * call EarlyExit(g:testfunc)
if a:test =~ 'Test_nocatch_'
" Function handles errors itself. This avoids skipping commands after the
@@ -180,6 +244,7 @@ func RunTheTest(test)
if g:skipped_reason != ''
call add(s:messages, ' Skipped')
call add(s:skipped, 'SKIPPED ' . a:test . ': ' . g:skipped_reason)
+ let skipped = v:true
endif
else
try
@@ -187,12 +252,18 @@ func RunTheTest(test)
catch /^\cskipped/
call add(s:messages, ' Skipped')
call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', ''))
+ let skipped = v:true
catch
call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint)
endtry
endif
au! VimLeavePre
+ if a:test =~ '_terminal_'
+ " Terminal tests sometimes hang, give extra information
+ echoconsole 'After executing ' .. a:test
+ endif
+
" In case 'insertmode' was set and something went wrong, make sure it is
" reset to avoid trouble with anything else.
set noinsertmode
@@ -205,6 +276,10 @@ func RunTheTest(test)
endtry
endif
+ if has('timers')
+ call timer_stop(test_timeout_timer)
+ endif
+
" Clear any autocommands and put back the catch-all for SwapExists.
au!
au SwapExists * call HandleSwapExists()
@@ -234,20 +309,74 @@ func RunTheTest(test)
exe 'cd ' . save_cwd
+ if a:test =~ '_terminal_'
+ " Terminal tests sometimes hang, give extra information
+ echoconsole 'Finished ' . a:test
+ endif
+
let message = 'Executed ' . a:test
if has('reltime')
let message ..= repeat(' ', 50 - len(message))
- let time = reltime(func_start)
- if has('float') && reltimefloat(time) > 0.1
+ let time = reltime(g:func_start)
+ if reltimefloat(time) > 0.1
let message = s:t_bold .. message
endif
let message ..= ' in ' .. reltimestr(time) .. ' seconds'
- if has('float') && reltimefloat(time) > 0.1
+ if reltimefloat(time) > 0.1
let message ..= s:t_normal
endif
endif
call add(s:messages, message)
let s:done += 1
+
+ " close any split windows
+ while winnr('$') > 1
+ bwipe!
+ endwhile
+
+ " May be editing some buffer, wipe it out. Then we may end up in another
+ " buffer, continue until we end up in an empty no-name buffer without a swap
+ " file.
+ while bufname() != '' || execute('swapname') !~ 'No swap file'
+ let bn = bufnr()
+
+ noswapfile bwipe!
+
+ if bn == bufnr()
+ " avoid getting stuck in the same buffer
+ break
+ endif
+ endwhile
+
+ if !skipped
+ " Check if the test has left any swap files behind. Delete them before
+ " running tests again, they might interfere.
+ let swapfiles = s:GetSwapFileList()
+ if len(swapfiles) > 0
+ call add(s:messages, "Found swap files: " .. string(swapfiles))
+ for name in swapfiles
+ call delete(name)
+ endfor
+ endif
+ endif
+endfunc
+
+function Delete_Xtest_Files()
+ for file in glob('X*', v:false, v:true)
+ if file ==? 'XfakeHOME'
+ " Clean up files created by setup.vim
+ call delete('XfakeHOME', 'rf')
+ continue
+ endif
+ " call add(v:errors, file .. " exists when it shouldn't, trying to delete it!")
+ call delete(file)
+ if !empty(glob(file, v:false, v:true))
+ " call add(v:errors, file .. " still exists after trying to delete it!")
+ if has('unix')
+ call system('rm -rf ' .. file)
+ endif
+ endif
+ endfor
endfunc
func AfterTheTest(func_name)
@@ -278,13 +407,11 @@ endfunc
" This function can be called by a test if it wants to abort testing.
func FinishTesting()
call AfterTheTest('')
+ call Delete_Xtest_Files()
" Don't write viminfo on exit.
set viminfo=
- " Clean up files created by setup.vim
- call delete('XfakeHOME', 'rf')
-
if s:fail == 0 && s:fail_expected == 0
" Success, create the .res file so that make knows it's done.
exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
@@ -314,7 +441,7 @@ func FinishTesting()
endif
if s:done > 0 && has('reltime')
let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
- let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ let message ..= ' in ' .. reltimestr(reltime(s:run_start_time)) .. ' seconds'
let message ..= s:t_normal
endif
echo message
@@ -370,32 +497,8 @@ else
endtry
endif
-" Names of flaky tests.
-let s:flaky_tests = [
- \ 'Test_autocmd_SafeState()',
- \ 'Test_cursorhold_insert()',
- \ 'Test_exit_callback_interval()',
- \ 'Test_map_timeout_with_timer_interrupt()',
- \ 'Test_out_cb()',
- \ 'Test_popup_and_window_resize()',
- \ 'Test_quoteplus()',
- \ 'Test_quotestar()',
- \ 'Test_reltime()',
- \ 'Test_state()',
- \ 'Test_term_mouse_double_click_to_create_tab()',
- \ 'Test_term_mouse_multiple_clicks_to_visually_select()',
- \ 'Test_terminal_composing_unicode()',
- \ 'Test_terminal_redir_file()',
- \ 'Test_terminal_tmap()',
- \ 'Test_timer_oneshot()',
- \ 'Test_timer_paused()',
- \ 'Test_timer_repeat_many()',
- \ 'Test_timer_repeat_three()',
- \ 'Test_timer_stop_all_in_callback()',
- \ 'Test_timer_stop_in_callback()',
- \ 'Test_timer_with_partial_callback()',
- \ 'Test_termwinscroll()',
- \ ]
+" Delete the .res file, it may change behavior for completion
+call delete(fnamemodify(g:testname, ':r') .. '.res')
" Locate Test_ functions and execute them.
redir @q
@@ -434,6 +537,7 @@ for g:testfunc in sort(s:tests)
" A test can set g:test_is_flaky to retry running the test.
let g:test_is_flaky = 0
+ let starttime = strftime("%H:%M:%S")
call RunTheTest(g:testfunc)
" Repeat a flaky test. Give up when:
@@ -442,13 +546,13 @@ for g:testfunc in sort(s:tests)
" - it fails five times (with a different message)
if len(v:errors) > 0
\ && $TEST_NO_RETRY == ''
- \ && (index(s:flaky_tests, g:testfunc) >= 0
- \ || g:test_is_flaky)
+ \ && g:test_is_flaky
while 1
- call add(s:messages, 'Found errors in ' . g:testfunc . ':')
+ call add(s:messages, 'Found errors in ' .. g:testfunc .. ':')
call extend(s:messages, v:errors)
- call add(total_errors, 'Run ' . g:run_nr . ':')
+ let endtime = strftime("%H:%M:%S")
+ call add(total_errors, $'Run {g:run_nr}, {starttime} - {endtime}:')
call extend(total_errors, v:errors)
if g:run_nr >= 5 || prev_error == v:errors[0]
@@ -468,6 +572,7 @@ for g:testfunc in sort(s:tests)
let v:errors = []
let g:run_nr += 1
+ let starttime = strftime("%H:%M:%S")
call RunTheTest(g:testfunc)
if len(v:errors) == 0
diff --git a/test/old/testdir/samples/box.txt b/test/old/testdir/samples/box.txt
new file mode 100644
index 0000000000..21c7d4f3d5
--- /dev/null
+++ b/test/old/testdir/samples/box.txt
@@ -0,0 +1,601 @@
+void BackgroundBox::prepare() {
+ setTitle(tr::lng_backgrounds_header());
+
+ addButton(tr::lng_close(), [=] { closeBox(); });
+
+ setDimensions(st::boxWideWidth, st::boxMaxListHeight);
+
+ auto wrap = object_ptr<Ui::VerticalLayout>(this);
+ const auto container = wrap.data();
+
+ Settings::AddSkip(container);
+
+ const auto button = container->add(Settings::CreateButton(
+ container,
+ tr::lng_settings_bg_from_file(),
+ st::infoProfileButton));
+ object_ptr<Info::Profile::FloatingIcon>(
+ button,
+ st::infoIconMediaPhoto,
+ st::infoSharedMediaButtonIconPosition);
+
+ button->setClickedCallback([=] {
+ chooseFromFile();
+ });
+
+ Settings::AddSkip(container);
+ Settings::AddDivider(container);
+
+ _inner = container->add(
+ object_ptr<Inner>(this, &_controller->session(), _forPeer));
+
+ container->resizeToWidth(st::boxWideWidth);
+
+ setInnerWidget(std::move(wrap), st::backgroundScroll);
+ setInnerTopSkip(st::lineWidth);
+
+ _inner->chooseEvents(
+ ) | rpl::start_with_next([=](const Data::WallPaper &paper) {
+ chosen(paper);
+ }, _inner->lifetime());
+
+ _inner->removeRequests(
+ ) | rpl::start_with_next([=](const Data::WallPaper &paper) {
+ removePaper(paper);
+ }, _inner->lifetime());
+}
+
+void BackgroundBox::chooseFromFile() {
+ const auto filterStart = _forPeer
+ ? u"Image files (*"_q
+ : u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q;
+ auto filters = QStringList(
+ filterStart
+ + Ui::ImageExtensions().join(u" *"_q)
+ + u")"_q);
+ filters.push_back(FileDialog::AllFilesFilter());
+ const auto callback = [=](const FileDialog::OpenResult &result) {
+ if (result.paths.isEmpty() && result.remoteContent.isEmpty()) {
+ return;
+ }
+
+ if (!_forPeer && !result.paths.isEmpty()) {
+ const auto filePath = result.paths.front();
+ const auto hasExtension = [&](QLatin1String extension) {
+ return filePath.endsWith(extension, Qt::CaseInsensitive);
+ };
+ if (hasExtension(qstr(".tdesktop-theme"))
+ || hasExtension(qstr(".tdesktop-palette"))) {
+ Window::Theme::Apply(filePath);
+ return;
+ }
+ }
+
+ auto image = Images::Read({
+ .path = result.paths.isEmpty() ? QString() : result.paths.front(),
+ .content = result.remoteContent,
+ .forceOpaque = true,
+ }).image;
+ if (image.isNull() || image.width() <= 0 || image.height() <= 0) {
+ return;
+ }
+ auto local = Data::CustomWallPaper();
+ local.setLocalImageAsThumbnail(std::make_shared<Image>(
+ std::move(image)));
+ _controller->show(Box<BackgroundPreviewBox>(
+ _controller,
+ local,
+ BackgroundPreviewArgs{ _forPeer }));
+ };
+ FileDialog::GetOpenPath(
+ this,
+ tr::lng_choose_image(tr::now),
+ filters.join(u";;"_q),
+ crl::guard(this, callback));
+}
+
+bool BackgroundBox::hasDefaultForPeer() const {
+ Expects(_forPeer != nullptr);
+
+ const auto paper = _forPeer->wallPaper();
+ if (!paper) {
+ return true;
+ }
+ const auto reset = _inner->resolveResetCustomPaper();
+ Assert(reset.has_value());
+ return (paper->id() == reset->id());
+}
+
+bool BackgroundBox::chosenDefaultForPeer(
+ const Data::WallPaper &paper) const {
+ if (!_forPeer) {
+ return false;
+ }
+
+ const auto reset = _inner->resolveResetCustomPaper();
+ Assert(reset.has_value());
+ return (paper.id() == reset->id());
+}
+
+void BackgroundBox::chosen(const Data::WallPaper &paper) {
+ if (chosenDefaultForPeer(paper)) {
+ if (!hasDefaultForPeer()) {
+ const auto reset = crl::guard(this, [=](Fn<void()> close) {
+ resetForPeer();
+ close();
+ });
+ _controller->show(Ui::MakeConfirmBox({
+ .text = tr::lng_background_sure_reset_default(),
+ .confirmed = reset,
+ .confirmText = tr::lng_background_reset_default(),
+ }));
+ } else {
+ closeBox();
+ }
+ return;
+ }
+ _controller->show(Box<BackgroundPreviewBox>(
+ _controller,
+ paper,
+ BackgroundPreviewArgs{ _forPeer }));
+}
+
+void BackgroundBox::resetForPeer() {
+ const auto api = &_controller->session().api();
+ api->request(MTPmessages_SetChatWallPaper(
+ MTP_flags(0),
+ _forPeer->input,
+ MTPInputWallPaper(),
+ MTPWallPaperSettings(),
+ MTPint()
+ )).done([=](const MTPUpdates &result) {
+ api->applyUpdates(result);
+ }).send();
+
+ const auto weak = Ui::MakeWeak(this);
+ _forPeer->setWallPaper(std::nullopt);
+ if (weak) {
+ _controller->finishChatThemeEdit(_forPeer);
+ }
+}
+
+void BackgroundBox::removePaper(const Data::WallPaper &paper) {
+ const auto session = &_controller->session();
+ const auto remove = [=, weak = Ui::MakeWeak(this)](Fn<void()> &&close) {
+ close();
+ if (weak) {
+ weak->_inner->removePaper(paper);
+ }
+ session->data().removeWallpaper(paper);
+ session->api().request(MTPaccount_SaveWallPaper(
+ paper.mtpInput(session),
+ MTP_bool(true),
+ paper.mtpSettings()
+ )).send();
+ };
+ _controller->show(Ui::MakeConfirmBox({
+ .text = tr::lng_background_sure_delete(),
+ .confirmed = remove,
+ .confirmText = tr::lng_selected_delete(),
+ }));
+}
+
+BackgroundBox::Inner::Inner(
+ QWidget *parent,
+ not_null<Main::Session*> session,
+ PeerData *forPeer)
+: RpWidget(parent)
+, _session(session)
+, _forPeer(forPeer)
+, _api(&_session->mtp())
+, _check(std::make_unique<Ui::RoundCheckbox>(st::overviewCheck, [=] { update(); })) {
+ _check->setChecked(true, anim::type::instant);
+ resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding);
+ Window::Theme::IsNightModeValue(
+ ) | rpl::start_with_next([=] {
+ updatePapers();
+ }, lifetime());
+ requestPapers();
+
+ _session->downloaderTaskFinished(
+ ) | rpl::start_with_next([=] {
+ update();
+ }, lifetime());
+
+ style::PaletteChanged(
+ ) | rpl::start_with_next([=] {
+ _check->invalidateCache();
+ }, lifetime());
+
+ using Update = Window::Theme::BackgroundUpdate;
+ Window::Theme::Background()->updates(
+ ) | rpl::start_with_next([=](const Update &update) {
+ if (update.type == Update::Type::New) {
+ sortPapers();
+ requestPapers();
+ this->update();
+ }
+ }, lifetime());
+
+
+ setMouseTracking(true);
+}
+
+void BackgroundBox::Inner::requestPapers() {
+ _api.request(MTPaccount_GetWallPapers(
+ MTP_long(_session->data().wallpapersHash())
+ )).done([=](const MTPaccount_WallPapers &result) {
+ if (_session->data().updateWallpapers(result)) {
+ updatePapers();
+ }
+ }).send();
+}
+
+auto BackgroundBox::Inner::resolveResetCustomPaper() const
+-> std::optional<Data::WallPaper> {
+ if (!_forPeer) {
+ return {};
+ }
+ const auto nonCustom = Window::Theme::Background()->paper();
+ const auto themeEmoji = _forPeer->themeEmoji();
+ if (themeEmoji.isEmpty()) {
+ return nonCustom;
+ }
+ const auto &themes = _forPeer->owner().cloudThemes();
+ const auto theme = themes.themeForEmoji(themeEmoji);
+ if (!theme) {
+ return nonCustom;
+ }
+ using Type = Data::CloudTheme::Type; XXXXX
+ const auto dark = Window::Theme::IsNightMode();
+ const auto i = theme->settings.find(dark ? Type::Dark : Type::Light);
+ if (i != end(theme->settings) && i->second.paper) {
+ return *i->second.paper;
+ }
+ return nonCustom;
+}
+
+void BackgroundBox::Inner::pushCustomPapers() {
+ auto customId = uint64();
+ if (const auto custom = _forPeer ? _forPeer->wallPaper() : nullptr) {
+ customId = custom->id();
+ const auto j = ranges::find(
+ _papers,
+ custom->id(),
+ [](const Paper &paper) { return paper.data.id(); });
+ if (j != end(_papers)) {
+ j->data = j->data.withParamsFrom(*custom);
+ } else {
+ _papers.insert(begin(_papers), Paper{ *custom });
+ }
+ }
+ if (const auto reset = resolveResetCustomPaper()) {
+ _insertedResetId = reset->id();
+ const auto j = ranges::find(
+ _papers,
+ _insertedResetId,
+ [](const Paper &paper) { return paper.data.id(); });
+ if (j != end(_papers)) {
+ if (_insertedResetId != customId) {
+ j->data = j->data.withParamsFrom(*reset);
+ }
+ } else {
+ _papers.insert(begin(_papers), Paper{ *reset });
+ }
+ }
+}
+
+void BackgroundBox::Inner::sortPapers() {
+ const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr;
+ _currentId = currentCustom
+ ? currentCustom->id()
+ : _insertedResetId
+ ? _insertedResetId
+ : Window::Theme::Background()->id();
+ const auto dark = Window::Theme::IsNightMode();
+ ranges::stable_sort(_papers, std::greater<>(), [&](const Paper &paper) {
+ const auto &data = paper.data;
+ return std::make_tuple(
+ _insertedResetId && (_insertedResetId == data.id()),
+ data.id() == _currentId,
+ dark ? data.isDark() : !data.isDark(),
+ Data::IsDefaultWallPaper(data),
+ !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data),
+ Data::IsLegacy3DefaultWallPaper(data),
+ Data::IsLegacy2DefaultWallPaper(data),
+ Data::IsLegacy1DefaultWallPaper(data));
+ });
+ if (!_papers.empty()
+ && _papers.front().data.id() == _currentId
+ && !currentCustom
+ && !_insertedResetId) {
+ _papers.front().data = _papers.front().data.withParamsFrom(
+ Window::Theme::Background()->paper());
+ }
+}
+
+void BackgroundBox::Inner::updatePapers() {
+ if (_session->data().wallpapers().empty()) {
+ return;
+ }
+ _over = _overDown = Selection();
+
+ _papers = _session->data().wallpapers(
+ ) | ranges::views::filter([&](const Data::WallPaper &paper) {
+ return (!paper.isPattern() || !paper.backgroundColors().empty())
+ && (!_forPeer
+ || (!Data::IsDefaultWallPaper(paper)
+ && (Data::IsCloudWallPaper(paper)
+ || Data::IsCustomWallPaper(paper))));
+ }) | ranges::views::transform([](const Data::WallPaper &paper) {
+ return Paper{ paper };
+ }) | ranges::to_vector;
+ pushCustomPapers();
+ sortPapers();
+ resizeToContentAndPreload();
+}
+
+void BackgroundBox::Inner::resizeToContentAndPreload() {
+ const auto count = _papers.size();
+ const auto rows = (count / kBackgroundsInRow)
+ + (count % kBackgroundsInRow ? 1 : 0);
+
+ resize(
+ st::boxWideWidth,
+ (rows * (st::backgroundSize.height() + st::backgroundPadding)
+ + st::backgroundPadding));
+
+ const auto preload = kBackgroundsInRow * 3;
+ for (const auto &paper : _papers | ranges::views::take(preload)) {
+ if (!paper.data.localThumbnail() && !paper.dataMedia) {
+ if (const auto document = paper.data.document()) {
+ paper.dataMedia = document->createMediaView();
+ paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
+ }
+ }
+ }
+ update();
+}
+
+void BackgroundBox::Inner::paintEvent(QPaintEvent *e) {
+ QRect r(e->rect());
+ auto p = QPainter(this);
+
+ if (_papers.empty()) {
+ p.setFont(st::noContactsFont);
+ p.setPen(st::noContactsColor);
+ p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_contacts_loading(tr::now), style::al_center);
+ return;
+ }
+ auto row = 0;
+ auto column = 0;
+ for (const auto &paper : _papers) {
+ const auto increment = gsl::finally([&] {
+ ++column;
+ if (column == kBackgroundsInRow) {
+ column = 0;
+ ++row;
+ }
+ });
+ if ((st::backgroundSize.height() + st::backgroundPadding) * (row + 1) <= r.top()) {
+ continue;
+ } else if ((st::backgroundSize.height() + st::backgroundPadding) * row >= r.top() + r.height()) {
+ break;
+ }
+ paintPaper(p, paper, column, row);
+ }
+}
+
+void BackgroundBox::Inner::validatePaperThumbnail(
+ const Paper &paper) const {
+ if (!paper.thumbnail.isNull()) {
+ return;
+ }
+ const auto localThumbnail = paper.data.localThumbnail();
+ if (!localThumbnail) {
+ if (const auto document = paper.data.document()) {
+ if (!paper.dataMedia) {
+ paper.dataMedia = document->createMediaView();
+ paper.dataMedia->thumbnailWanted(paper.data.fileOrigin());
+ }
+ if (!paper.dataMedia->thumbnail()) {
+ return;
+ }
+ } else if (!paper.data.backgroundColors().empty()) {
+ paper.thumbnail = Ui::PixmapFromImage(
+ Ui::GenerateBackgroundImage(
+ st::backgroundSize * cIntRetinaFactor(),
+ paper.data.backgroundColors(),
+ paper.data.gradientRotation()));
+ paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
+ return;
+ } else {
+ return;
+ }
+ }
+ const auto thumbnail = localThumbnail
+ ? localThumbnail
+ : paper.dataMedia->thumbnail();
+ auto original = thumbnail->original();
+ if (paper.data.isPattern()) {
+ original = Ui::PreparePatternImage(
+ std::move(original),
+ paper.data.backgroundColors(),
+ paper.data.gradientRotation(),
+ paper.data.patternOpacity());
+ }
+ paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample(
+ original,
+ st::backgroundSize));
+ paper.thumbnail.setDevicePixelRatio(cRetinaFactor());
+}
+
+void BackgroundBox::Inner::paintPaper(
+ QPainter &p,
+ const Paper &paper,
+ int column,
+ int row) const {
+ const auto x = st::backgroundPadding + column * (st::backgroundSize.width() + st::backgroundPadding);
+ const auto y = st::backgroundPadding + row * (st::backgroundSize.height() + st::backgroundPadding);
+ validatePaperThumbnail(paper);
+ if (!paper.thumbnail.isNull()) {
+ p.drawPixmap(x, y, paper.thumbnail);
+ }
+
+ const auto over = !v::is_null(_overDown) ? _overDown : _over;
+ if (paper.data.id() == _currentId) {
+ const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size;
+ const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size;
+ _check->paint(p, checkLeft, checkTop, width());
+ } else if (Data::IsCloudWallPaper(paper.data)
+ && !Data::IsDefaultWallPaper(paper.data)
+ && !Data::IsLegacy2DefaultWallPaper(paper.data)
+ && !Data::IsLegacy3DefaultWallPaper(paper.data)
+ && !v::is_null(over)
+ && (&paper == &_papers[getSelectionIndex(over)])) {
+ const auto deleteSelected = v::is<DeleteSelected>(over);
+ const auto deletePos = QPoint(x + st::backgroundSize.width() - st::stickerPanDeleteIconBg.width(), y);
+ p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg);
+ st::stickerPanDeleteIconBg.paint(p, deletePos, width());
+ p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg);
+ st::stickerPanDeleteIconFg.paint(p, deletePos, width());
+ p.setOpacity(1.);
+ }
+}
+
+void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) {
+ const auto newOver = [&] {
+ const auto x = e->pos().x();
+ const auto y = e->pos().y();
+ const auto width = st::backgroundSize.width();
+ const auto height = st::backgroundSize.height();
+ const auto skip = st::backgroundPadding;
+ const auto row = int((y - skip) / (height + skip));
+ const auto column = int((x - skip) / (width + skip));
+ const auto result = row * kBackgroundsInRow + column;
+ if (y - row * (height + skip) > skip + height) {
+ return Selection();
+ } else if (x - column * (width + skip) > skip + width) {
+ return Selection();
+ } else if (result >= _papers.size()) {
+ return Selection();
+ }
+ auto &data = _papers[result].data;
+ const auto deleteLeft = (column + 1) * (width + skip)
+ - st::stickerPanDeleteIconBg.width();
+ const auto deleteBottom = row * (height + skip) + skip
+ + st::stickerPanDeleteIconBg.height();
+ const auto inDelete = (x >= deleteLeft)
+ && (y < deleteBottom)
+ && Data::IsCloudWallPaper(data)
+ && !Data::IsDefaultWallPaper(data)
+ && !Data::IsLegacy2DefaultWallPaper(data)
+ && !Data::IsLegacy3DefaultWallPaper(data)
+ && (_currentId != data.id());
+ return (result >= _papers.size())
+ ? Selection()
+ : inDelete
+ ? Selection(DeleteSelected{ result })
+ : Selection(Selected{ result });
+ }();
+ if (_over != newOver) {
+ repaintPaper(getSelectionIndex(_over));
+ _over = newOver;
+ repaintPaper(getSelectionIndex(_over));
+ setCursor((!v::is_null(_over) || !v::is_null(_overDown))
+ ? style::cur_pointer
+ : style::cur_default);
+ }
+}
+
+void BackgroundBox::Inner::repaintPaper(int index) {
+ if (index < 0 || index >= _papers.size()) {
+ return;
+ }
+ const auto row = (index / kBackgroundsInRow);
+ const auto column = (index % kBackgroundsInRow);
+ const auto width = st::backgroundSize.width();
+ const auto height = st::backgroundSize.height();
+ const auto skip = st::backgroundPadding;
+ update(
+ (width + skip) * column + skip,
+ (height + skip) * row + skip,
+ width,
+ height);
+}
+
+void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) {
+ _overDown = _over;
+}
+
+int BackgroundBox::Inner::getSelectionIndex(
+ const Selection &selection) const {
+ return v::match(selection, [](const Selected &data) {
+ return data.index;
+ }, [](const DeleteSelected &data) {
+ return data.index;
+ }, [](v::null_t) {
+ return -1;
+ });
+}
+
+void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) {
+ if (base::take(_overDown) == _over && !v::is_null(_over)) {
+ const auto index = getSelectionIndex(_over);
+ if (index >= 0 && index < _papers.size()) {
+ if (std::get_if<DeleteSelected>(&_over)) {
+ _backgroundRemove.fire_copy(_papers[index].data);
+ } else if (std::get_if<Selected>(&_over)) {
+ auto &paper = _papers[index];
+ if (!paper.dataMedia) {
+ if (const auto document = paper.data.document()) {
+ // Keep it alive while it is on the screen.
+ paper.dataMedia = document->createMediaView();
+ }
+ }
+ _backgroundChosen.fire_copy(paper.data);
+ }
+ }
+ } else if (v::is_null(_over)) {
+ setCursor(style::cur_default);
+ }
+}
+
+void BackgroundBox::Inner::visibleTopBottomUpdated(
+ int visibleTop,
+ int visibleBottom) {
+ for (auto i = 0, count = int(_papers.size()); i != count; ++i) {
+ const auto row = (i / kBackgroundsInRow);
+ const auto height = st::backgroundSize.height();
+ const auto skip = st::backgroundPadding;
+ const auto top = skip + row * (height + skip);
+ const auto bottom = top + height;
+ if ((bottom <= visibleTop || top >= visibleBottom)
+ && !_papers[i].thumbnail.isNull()) {
+ _papers[i].dataMedia = nullptr;
+ }
+ }
+}
+
+rpl::producer<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const {
+ return _backgroundChosen.events();
+}
+
+auto BackgroundBox::Inner::removeRequests() const
+-> rpl::producer<Data::WallPaper> {
+ return _backgroundRemove.events();
+}
+
+void BackgroundBox::Inner::removePaper(const Data::WallPaper &data) {
+ const auto i = ranges::find(
+ _papers,
+ data.id(),
+ [](const Paper &paper) { return paper.data.id(); });
+ if (i != end(_papers)) {
+ _papers.erase(i);
+ _over = _overDown = Selection();
+ resizeToContentAndPreload();
+ }
+}
+
+BackgroundBox::Inner::~Inner() = default;
diff --git a/test/old/testdir/samples/matchparen.vim b/test/old/testdir/samples/matchparen.vim
new file mode 100644
index 0000000000..4235a0d39b
--- /dev/null
+++ b/test/old/testdir/samples/matchparen.vim
@@ -0,0 +1,234 @@
+" Vim plugin for showing matching parens
+" Maintainer: The Vim Project <https://github.com/vim/vim>
+" Last Change: 2023 Oct 20
+" Former Maintainer: Bram Moolenaar <Bram@vim.org>
+
+" Exit quickly when:
+" - this plugin was already loaded (or disabled)
+" - when 'compatible' is set
+if exists("g:loaded_matchparen") || &cp
+ finish
+endif
+let g:loaded_matchparen = 1
+
+if !exists("g:matchparen_timeout")
+ let g:matchparen_timeout = 300
+endif
+if !exists("g:matchparen_insert_timeout")
+ let g:matchparen_insert_timeout = 60
+endif
+
+let s:has_matchaddpos = exists('*matchaddpos')
+
+augroup matchparen
+ " Replace all matchparen autocommands
+ autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair()
+ autocmd! WinLeave,BufLeave * call s:Remove_Matches()
+ if exists('##TextChanged')
+ autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair()
+ autocmd! TextChangedP * call s:Remove_Matches()
+ endif
+augroup END
+
+" Skip the rest if it was already done.
+if exists("*s:Highlight_Matching_Pair")
+ finish
+endif
+
+let s:cpo_save = &cpo
+set cpo-=C
+
+" The function that is invoked (very often) to define a ":match" highlighting
+" for any matching paren.
+func s:Highlight_Matching_Pair()
+ if !exists("w:matchparen_ids")
+ let w:matchparen_ids = []
+ endif
+ " Remove any previous match.
+ call s:Remove_Matches()
+
+ " Avoid that we remove the popup menu.
+ " Return when there are no colors (looks like the cursor jumps).
+ if pumvisible() || (&t_Co < 8 && !has("gui_running"))
+ return
+ endif
+
+ " Get the character under the cursor and check if it's in 'matchpairs'.
+ let c_lnum = line('.')
+ let c_col = col('.')
+ let before = 0
+
+ let text = getline(c_lnum)
+ let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)')
+ if empty(matches)
+ let [c_before, c] = ['', '']
+ else
+ let [c_before, c] = matches[1:2]
+ endif
+ let plist = split(&matchpairs, '.\zs[:,]')
+ let i = index(plist, c)
+ if i < 0
+ " not found, in Insert mode try character before the cursor
+ if c_col > 1 && (mode() == 'i' || mode() == 'R')
+ let before = strlen(c_before)
+ let c = c_before
+ let i = index(plist, c)
+ endif
+ if i < 0
+ " not found, nothing to do
+ return
+ endif
+ endif
+
+ " Figure out the arguments for searchpairpos().
+ if i % 2 == 0
+ let s_flags = 'nW'
+ let c2 = plist[i + 1]
+ else
+ let s_flags = 'nbW'
+ let c2 = c
+ let c = plist[i - 1]
+ endif
+ if c == '['
+ let c = '\['
+ let c2 = '\]'
+ endif
+
+ " Find the match. When it was just before the cursor move it there for a
+ " moment.
+ if before > 0
+ let has_getcurpos = exists("*getcurpos")
+ if has_getcurpos
+ " getcurpos() is more efficient but doesn't exist before 7.4.313.
+ let save_cursor = getcurpos()
+ else
+ let save_cursor = winsaveview()
+ endif
+ call cursor(c_lnum, c_col - before)
+ endif
+
+ if !has("syntax") || !exists("g:syntax_on")
+ let s_skip = "0"
+ else
+ " Build an expression that detects whether the current cursor position is
+ " in certain syntax types (string, comment, etc.), for use as
+ " searchpairpos()'s skip argument.
+ " We match "escape" for special items, such as lispEscapeSpecial, and
+ " match "symbol" for lispBarSymbol.
+ let s_skip = 'synstack(".", col("."))'
+ \ . '->indexof({_, id -> synIDattr(id, "name") =~? '
+ \ . '"string\\|character\\|singlequote\\|escape\\|symbol\\|comment"}) >= 0'
+ " If executing the expression determines that the cursor is currently in
+ " one of the syntax types, then we want searchpairpos() to find the pair
+ " within those syntax types (i.e., not skip). Otherwise, the cursor is
+ " outside of the syntax types and s_skip should keep its value so we skip
+ " any matching pair inside the syntax types.
+ " Catch if this throws E363: pattern uses more memory than 'maxmempattern'.
+ try
+ execute 'if ' . s_skip . ' | let s_skip = "0" | endif'
+ catch /^Vim\%((\a\+)\)\=:E363/
+ " We won't find anything, so skip searching, should keep Vim responsive.
+ return
+ endtry
+ endif
+
+ " Limit the search to lines visible in the window.
+ let stoplinebottom = line('w$')
+ let stoplinetop = line('w0')
+ if i % 2 == 0
+ let stopline = stoplinebottom
+ else
+ let stopline = stoplinetop
+ endif
+
+ " Limit the search time to 300 msec to avoid a hang on very long lines.
+ " This fails when a timeout is not supported.
+ if mode() == 'i' || mode() == 'R'
+ let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout
+ else
+ let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout
+ endif
+ try
+ let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout)
+ catch /E118/
+ " Can't use the timeout, restrict the stopline a bit more to avoid taking
+ " a long time on closed folds and long lines.
+ " The "viewable" variables give a range in which we can scroll while
+ " keeping the cursor at the same position.
+ " adjustedScrolloff accounts for very large numbers of scrolloff.
+ let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2])
+ let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2])
+ let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2])
+ " one of these stoplines will be adjusted below, but the current values are
+ " minimal boundaries within the current window
+ if i % 2 == 0
+ if has("byte_offset") && has("syntax_items") && &smc > 0
+ let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2])
+ let stopline = min([bottom_viewable, byte2line(stopbyte)])
+ else
+ let stopline = min([bottom_viewable, c_lnum + 100])
+ endif
+ let stoplinebottom = stopline
+ else
+ if has("byte_offset") && has("syntax_items") && &smc > 0
+ let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2])
+ let stopline = max([top_viewable, byte2line(stopbyte)])
+ else
+ let stopline = max([top_viewable, c_lnum - 100])
+ endif
+ let stoplinetop = stopline
+ endif
+ let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline)
+ endtry
+
+ if before > 0
+ if has_getcurpos
+ call setpos('.', save_cursor)
+ else
+ call winrestview(save_cursor)
+ endif
+ endif
+
+ " If a match is found setup match highlighting.
+ if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom
+ if s:has_matchaddpos
+ call add(w:matchparen_ids, matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10))
+ else
+ exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) .
+ \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/'
+ call add(w:matchparen_ids, 3)
+ endif
+ let w:paren_hl_on = 1
+ endif
+endfunction
+
+func s:Remove_Matches()
+ if exists('w:paren_hl_on') && w:paren_hl_on
+ while !empty(w:matchparen_ids)
+ silent! call remove(w:matchparen_ids, 0)->matchdelete()
+ endwhile
+ let w:paren_hl_on = 0
+ endif
+endfunc
+
+" Define commands that will disable and enable the plugin.
+command DoMatchParen call s:DoMatchParen()
+command NoMatchParen call s:NoMatchParen()
+
+func s:NoMatchParen()
+ let w = winnr()
+ noau windo silent! call matchdelete(3)
+ unlet! g:loaded_matchparen
+ exe "noau ". w . "wincmd w"
+ au! matchparen
+endfunc
+
+func s:DoMatchParen()
+ runtime plugin/matchparen.vim
+ let w = winnr()
+ silent windo doau CursorMoved
+ exe "noau ". w . "wincmd w"
+endfunc
+
+let &cpo = s:cpo_save
+unlet s:cpo_save
diff --git a/src/nvim/testdir/samples/quickfix.txt b/test/old/testdir/samples/quickfix.txt
index 2de3835473..2de3835473 100644
--- a/src/nvim/testdir/samples/quickfix.txt
+++ b/test/old/testdir/samples/quickfix.txt
diff --git a/src/nvim/testdir/samples/re.freeze.txt b/test/old/testdir/samples/re.freeze.txt
index d768c23c5e..d768c23c5e 100644
--- a/src/nvim/testdir/samples/re.freeze.txt
+++ b/test/old/testdir/samples/re.freeze.txt
diff --git a/src/nvim/testdir/sautest/autoload/foo.vim b/test/old/testdir/sautest/autoload/foo.vim
index 21d33a0f4d..21d33a0f4d 100644
--- a/src/nvim/testdir/sautest/autoload/foo.vim
+++ b/test/old/testdir/sautest/autoload/foo.vim
diff --git a/src/nvim/testdir/sautest/autoload/footest.vim b/test/old/testdir/sautest/autoload/footest.vim
index 1e78963a10..1e78963a10 100644
--- a/src/nvim/testdir/sautest/autoload/footest.vim
+++ b/test/old/testdir/sautest/autoload/footest.vim
diff --git a/src/nvim/testdir/sautest/autoload/globone.vim b/test/old/testdir/sautest/autoload/globone.vim
index 98c9a10582..98c9a10582 100644
--- a/src/nvim/testdir/sautest/autoload/globone.vim
+++ b/test/old/testdir/sautest/autoload/globone.vim
diff --git a/src/nvim/testdir/sautest/autoload/globtwo.vim b/test/old/testdir/sautest/autoload/globtwo.vim
index 98c9a10582..98c9a10582 100644
--- a/src/nvim/testdir/sautest/autoload/globtwo.vim
+++ b/test/old/testdir/sautest/autoload/globtwo.vim
diff --git a/test/old/testdir/sautest/autoload/sourced.vim b/test/old/testdir/sautest/autoload/sourced.vim
new file mode 100644
index 0000000000..aac96b11ce
--- /dev/null
+++ b/test/old/testdir/sautest/autoload/sourced.vim
@@ -0,0 +1,4 @@
+let g:loaded_sourced_vim += 1
+func sourced#something()
+endfunc
+call sourced#something()
diff --git a/src/nvim/testdir/screendump.vim b/test/old/testdir/screendump.vim
index 8afff1da91..8afff1da91 100644
--- a/src/nvim/testdir/screendump.vim
+++ b/test/old/testdir/screendump.vim
diff --git a/src/nvim/testdir/script_util.vim b/test/old/testdir/script_util.vim
index 28d6a621d6..28d6a621d6 100644
--- a/src/nvim/testdir/script_util.vim
+++ b/test/old/testdir/script_util.vim
diff --git a/src/nvim/testdir/setup.vim b/test/old/testdir/setup.vim
index f895287469..7546b342e6 100644
--- a/src/nvim/testdir/setup.vim
+++ b/test/old/testdir/setup.vim
@@ -1,34 +1,39 @@
if exists('s:did_load')
" Align Nvim defaults to Vim.
set backspace=
+ set commentstring=/*%s*/
set complete=.,w,b,u,t,i
- set directory&
+ set define=^\\s*#\\s*define
set directory^=.
set display=
set fillchars=vert:\|,foldsep:\|,fold:-
set formatoptions=tcq
set fsync
+ set include=^\\s*#\\s*include
set laststatus=1
set listchars=eol:$
set joinspaces
+ set mousemodel=extend
set nohidden nosmarttab noautoindent noautoread noruler noshowcmd
set nohlsearch noincsearch
set nrformats=bin,octal,hex
set shortmess=filnxtToOS
set sidescroll=0
set tags=./tags,tags
- set undodir&
set undodir^=.
set wildoptions=
set startofline
- set sessionoptions&
set sessionoptions+=options
- set viewoptions&
set viewoptions+=options
set switchbuf=
- " Make "Q" switch to Ex mode.
- " This does not work for all tests.
- nnoremap Q gQ
+ if has('win32')
+ set isfname+=:
+ endif
+ if g:testname !~ 'test_mapping.vim$'
+ " Make "Q" switch to Ex mode.
+ " This does not work for all tests.
+ nnoremap Q gQ
+ endif
endif
" Common preparations for running tests.
@@ -48,12 +53,19 @@ tlunmenu *
" roughly equivalent to test_setmouse() in Vim
func Ntest_setmouse(row, col)
call nvim_input_mouse('move', '', '', 0, a:row - 1, a:col - 1)
+ if state('m') == ''
+ call getchar(0)
+ endif
+endfunc
+
+" roughly equivalent to term_wait() in Vim
+func Nterm_wait(buf, time = 10)
+ execute $'sleep {a:time}m'
endfunc
" Prevent Nvim log from writing to stderr.
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
-
" Make sure 'runtimepath' and 'packpath' does not include $HOME.
set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after
let &packpath = &rtp
diff --git a/src/nvim/testdir/shared.vim b/test/old/testdir/shared.vim
index 33f6d9a2d0..e7a5f471e7 100644
--- a/src/nvim/testdir/shared.vim
+++ b/test/old/testdir/shared.vim
@@ -110,16 +110,16 @@ func RunServer(cmd, testfunc, args)
try
let g:currentJob = RunCommand(pycmd)
- " Wait for up to 2 seconds for the port number to be there.
+ " Wait for some time for the port number to be there.
let port = GetPort()
if port == 0
- call assert_false(1, "Can't start " . a:cmd)
+ call assert_report(strftime("%H:%M:%S") .. " Can't start " .. a:cmd)
return
endif
call call(function(a:testfunc), [port])
catch
- call assert_false(1, 'Caught exception: "' . v:exception . '" in ' . v:throwpoint)
+ call assert_report('Caught exception: "' . v:exception . '" in ' . v:throwpoint)
finally
call s:kill_server(a:cmd)
endtry
@@ -339,7 +339,7 @@ func RunVimPiped(before, after, arguments, pipecmd)
let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log'
" Nvim does not support -Z flag, remove it.
- exe "silent !" . a:pipecmd . cmd . args . ' ' . substitute(a:arguments, '-Z', '', 'g')
+ exe "silent !" . a:pipecmd . cmd . args . ' ' . a:arguments->substitute('-Z', '', 'g')
if len(a:before) > 0
call delete('Xbefore.vim')
diff --git a/test/old/testdir/suite.sh b/test/old/testdir/suite.sh
new file mode 100644
index 0000000000..bf5a16fd89
--- /dev/null
+++ b/test/old/testdir/suite.sh
@@ -0,0 +1,10 @@
+fail() {
+ local test_name="$1"
+ local message="$2"
+
+ : "${message:=Test $test_name failed}"
+
+ local full_msg="$test_name :: $message"
+ echo "Failed: $full_msg"
+ export FAILED=1
+}
diff --git a/src/nvim/testdir/summarize.vim b/test/old/testdir/summarize.vim
index da5856a2e7..da5856a2e7 100644
--- a/src/nvim/testdir/summarize.vim
+++ b/test/old/testdir/summarize.vim
diff --git a/src/nvim/testdir/term_util.vim b/test/old/testdir/term_util.vim
index 5ff09e25b4..5ff09e25b4 100644
--- a/src/nvim/testdir/term_util.vim
+++ b/test/old/testdir/term_util.vim
diff --git a/test/old/testdir/test.sh b/test/old/testdir/test.sh
new file mode 100644
index 0000000000..6e7fa9db18
--- /dev/null
+++ b/test/old/testdir/test.sh
@@ -0,0 +1,89 @@
+fail() {
+ local test_name="$1"
+ local message="$2"
+
+ : "${message:=Test $test_name failed}"
+
+ local full_msg="$test_name :: $message"
+ echo "Failed: $full_msg"
+ export FAILED=1
+}
+
+print_core() {
+ local app="$1"
+ local core="$2"
+ if test "$app" = quiet ; then
+ echo "Found core $core"
+ return 0
+ fi
+ echo "======= Core file $core ======="
+ if test "${CI_OS_NAME}" = osx ; then
+ lldb -Q -o "bt all" -f "${app}" -c "${core}"
+ else
+ gdb -n -batch -ex 'thread apply all bt full' "${app}" -c "${core}"
+ fi
+}
+
+check_core_dumps() {
+ local del=
+ if test "$1" = "--delete" ; then
+ del=1
+ shift
+ fi
+ local app="${1:-${BUILD_DIR}/bin/nvim}"
+ local cores
+ if test "${CI_OS_NAME}" = osx ; then
+ cores="$(find /cores/ -type f -print)"
+ local _sudo='sudo'
+ else
+ cores="$(find ./ -type f \( -name 'core.*' -o -name core -o -name nvim.core \) -print)"
+ local _sudo=
+ fi
+
+ if test -z "${cores}" ; then
+ return
+ fi
+ local core
+ for core in $cores; do
+ if test "$del" = "1" ; then
+ print_core "$app" "$core" >&2
+ "$_sudo" rm "$core"
+ else
+ print_core "$app" "$core"
+ fi
+ done
+ if test "$app" != quiet ; then
+ fail 'cores' 'Core dumps found'
+ fi
+}
+
+check_logs() {
+ # Iterate through each log to remove an useless warning.
+ # shellcheck disable=SC2044
+ for log in $(find "${1}" -type f -name "${2}"); do
+ sed -i "${log}" \
+ -e '/Warning: noted but unhandled ioctl/d' \
+ -e '/could cause spurious value errors to appear/d' \
+ -e '/See README_MISSING_SYSCALL_OR_IOCTL for guidance/d'
+ done
+
+ # Now do it again, but only consider files with size > 0.
+ local err=""
+ # shellcheck disable=SC2044
+ for log in $(find "${1}" -type f -name "${2}" -size +0); do
+ cat "${log}"
+ err=1
+ rm "${log}"
+ done
+ if test -n "${err}" ; then
+ fail 'logs' 'Runtime errors detected.'
+ fi
+}
+
+valgrind_check() {
+ check_logs "${1}" "valgrind-*"
+}
+
+check_sanitizer() {
+ check_logs "${1}" "*san.*"
+}
diff --git a/src/nvim/testdir/test1.in b/test/old/testdir/test1.in
index 272500cd25..272500cd25 100644
--- a/src/nvim/testdir/test1.in
+++ b/test/old/testdir/test1.in
diff --git a/src/nvim/testdir/test1.ok b/test/old/testdir/test1.ok
index 90bfcb5106..90bfcb5106 100644
--- a/src/nvim/testdir/test1.ok
+++ b/test/old/testdir/test1.ok
diff --git a/src/nvim/testdir/test_alot.vim b/test/old/testdir/test_alot.vim
index a3d240f27e..2a959f0834 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/test/old/testdir/test_alot.vim
@@ -2,7 +2,6 @@
" This makes testing go faster, since Vim doesn't need to restart.
source test_backup.vim
-source test_behave.vim
source test_compiler.vim
source test_ex_equal.vim
source test_ex_undo.vim
@@ -18,7 +17,6 @@ source test_global.vim
source test_move.vim
source test_put.vim
source test_reltime.vim
-source test_scroll_opt.vim
source test_searchpos.vim
source test_set.vim
source test_shift.vim
diff --git a/src/nvim/testdir/test_alot_latin.vim b/test/old/testdir/test_alot_latin.vim
index 23a404cac1..23a404cac1 100644
--- a/src/nvim/testdir/test_alot_latin.vim
+++ b/test/old/testdir/test_alot_latin.vim
diff --git a/src/nvim/testdir/test_alot_utf8.vim b/test/old/testdir/test_alot_utf8.vim
index 77f5ede4c8..77f5ede4c8 100644
--- a/src/nvim/testdir/test_alot_utf8.vim
+++ b/test/old/testdir/test_alot_utf8.vim
diff --git a/src/nvim/testdir/test_arabic.vim b/test/old/testdir/test_arabic.vim
index 272937387d..8f150090ed 100644
--- a/src/nvim/testdir/test_arabic.vim
+++ b/test/old/testdir/test_arabic.vim
@@ -74,9 +74,9 @@ endfunc
func Test_arabic_toggle_keymap()
new
set arabic
- call feedkeys("i12\<C-^>12\<C-^>12", 'tx')
- call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0])
- call assert_equal('١٢12١٢', getline('.'))
+ call feedkeys("i12\<C-^>12\<C-^>12abcd", 'tx')
+ call assert_match("^ *.*ﺷ212121$", ScreenLines(1, &columns)[0])
+ call assert_equal('121212شلاؤي', getline('.'))
set arabic&
bwipe!
endfunc
diff --git a/src/nvim/testdir/test_arglist.vim b/test/old/testdir/test_arglist.vim
index fb8b17cd16..ebda332562 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/test/old/testdir/test_arglist.vim
@@ -97,7 +97,7 @@ endfunc
func Test_argadd_empty_curbuf()
new
let curbuf = bufnr('%')
- call writefile(['test', 'Xargadd'], 'Xargadd')
+ call writefile(['test', 'Xargadd'], 'Xargadd', 'D')
" must not re-use the current buffer.
argadd Xargadd
call assert_equal(curbuf, bufnr('%'))
@@ -183,22 +183,25 @@ func Test_argument()
let save_columns = &columns
let &columns = 79
- exe 'args ' .. join(range(1, 81))
- call assert_equal(join([
- \ '',
- \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
- \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
- \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
- \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
- \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
- \ ], "\n"),
- \ execute('args'))
-
- " No trailing newline with one item per row.
- let long_arg = repeat('X', 81)
- exe 'args ' .. long_arg
- call assert_equal("\n[".long_arg.']', execute('args'))
- let &columns = save_columns
+ try
+ exe 'args ' .. join(range(1, 81))
+ call assert_equal(join([
+ \ '',
+ \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ',
+ \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ',
+ \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ',
+ \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ',
+ \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ',
+ \ ], "\n"),
+ \ execute('args'))
+
+ " No trailing newline with one item per row.
+ let long_arg = repeat('X', 81)
+ exe 'args ' .. long_arg
+ call assert_equal("\n[".long_arg.']', execute('args'))
+ finally
+ let &columns = save_columns
+ endtry
" Setting argument list should fail when the current buffer has unsaved
" changes
@@ -513,9 +516,9 @@ endfunc
" Test for autocommand that redefines the argument list, when doing ":all".
func Test_arglist_autocmd()
autocmd BufReadPost Xxx2 next Xxx2 Xxx1
- call writefile(['test file Xxx1'], 'Xxx1')
- call writefile(['test file Xxx2'], 'Xxx2')
- call writefile(['test file Xxx3'], 'Xxx3')
+ call writefile(['test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['test file Xxx2'], 'Xxx2', 'D')
+ call writefile(['test file Xxx3'], 'Xxx3', 'D')
new
" redefine arglist; go to Xxx1
@@ -531,18 +534,14 @@ func Test_arglist_autocmd()
autocmd! BufReadPost Xxx2
enew! | only
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xxx3')
argdelete Xxx*
bwipe! Xxx1 Xxx2 Xxx3
endfunc
func Test_arg_all_expand()
- call writefile(['test file Xxx1'], 'Xx x')
+ call writefile(['test file Xxx1'], 'Xx x', 'D')
next notexist Xx\ x runtest.vim
call assert_equal('notexist Xx\ x runtest.vim', expand('##'))
- call delete('Xx x')
endfunc
func Test_large_arg()
@@ -568,7 +567,7 @@ func Test_quit_with_arglist()
call term_sendkeys(buf, ":set nomore\n")
call term_sendkeys(buf, ":args a b c\n")
call term_sendkeys(buf, ":quit\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E173:', term_getline(buf, 6))})
call StopVimInTerminal(buf)
@@ -577,16 +576,16 @@ func Test_quit_with_arglist()
call term_sendkeys(buf, ":set nomore\n")
call term_sendkeys(buf, ":args a b c\n")
call term_sendkeys(buf, ":confirm quit\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
\ term_getline(buf, 6))})
call term_sendkeys(buf, "N")
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, ":confirm quit\n")
call WaitForAssert({-> assert_match('^\[Y\]es, (N)o: *$',
\ term_getline(buf, 6))})
call term_sendkeys(buf, "Y")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_equal("finished", term_getstatus(buf))})
only!
" When this test fails, swap files are left behind which breaks subsequent
diff --git a/src/nvim/testdir/test_assert.vim b/test/old/testdir/test_assert.vim
index 431908e95c..fe093d3582 100644
--- a/src/nvim/testdir/test_assert.vim
+++ b/test/old/testdir/test_assert.vim
@@ -1,5 +1,8 @@
" Test that the methods used for testing work.
+source check.vim
+source term_util.vim
+
func Test_assert_false()
call assert_equal(0, assert_false(0))
call assert_equal(0, assert_false(v:false))
@@ -53,6 +56,14 @@ func Test_assert_equal()
call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x')
call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0])
call remove(v:errors, 0)
+
+ " many composing characters are handled properly
+ call setline(1, ' ')
+ norm 100gr݀
+ call assert_equal(1, getline(1))
+ call assert_match("Expected 1 but got '.* occurs 100 times]'", v:errors[0])
+ call remove(v:errors, 0)
+ bwipe!
endfunc
func Test_assert_equal_dict()
@@ -81,12 +92,12 @@ func Test_assert_equalfile()
call remove(v:errors, 0)
let goodtext = ["one", "two", "three"]
- call writefile(goodtext, 'Xone')
+ call writefile(goodtext, 'Xone', 'D')
call assert_equal(1, 'Xone'->assert_equalfile('xyzxyz'))
call assert_match("E485: Can't read file xyzxyz", v:errors[0])
call remove(v:errors, 0)
- call writefile(goodtext, 'Xtwo')
+ call writefile(goodtext, 'Xtwo', 'D')
call assert_equal(0, assert_equalfile('Xone', 'Xtwo'))
call writefile([goodtext[0]], 'Xone')
@@ -116,9 +127,6 @@ func Test_assert_equalfile()
call assert_equal(1, assert_equalfile('Xone', 'Xtwo', 'a message'))
call assert_match("a message: difference at byte 234, line 1 after", v:errors[0])
call remove(v:errors, 0)
-
- call delete('Xone')
- call delete('Xtwo')
endfunc
func Test_assert_notequal()
@@ -220,11 +228,11 @@ func Test_assert_fail_fails()
call remove(v:errors, 0)
call assert_equal(1, assert_fails('xxx', ['E9876']))
- call assert_match("Expected \\['E9876'\\] but got 'E492:", v:errors[0])
+ call assert_match("Expected 'E9876' but got 'E492:", v:errors[0])
call remove(v:errors, 0)
call assert_equal(1, assert_fails('xxx', ['E492:', 'E9876']))
- call assert_match("Expected \\['E492:', 'E9876'\\] but got 'E492:", v:errors[0])
+ call assert_match("Expected 'E9876' but got 'E492:", v:errors[0])
call remove(v:errors, 0)
call assert_equal(1, assert_fails('echo', '', 'echo command'))
@@ -240,35 +248,94 @@ func Test_assert_fail_fails()
catch
let exp = v:exception
endtry
- call assert_match("E856: assert_fails() second argument", exp)
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
try
call assert_equal(1, assert_fails('xxx', ['1', '2', '3']))
catch
let exp = v:exception
endtry
- call assert_match("E856: assert_fails() second argument", exp)
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', v:_null_list))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
+
+ try
+ call assert_equal(1, assert_fails('xxx', []))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E856: \"assert_fails()\" second argument", exp)
try
call assert_equal(1, assert_fails('xxx', #{one: 1}))
catch
let exp = v:exception
endtry
- call assert_match("E856: assert_fails() second argument", exp)
+ call assert_match("E1222: String or List required for argument 2", exp)
+
+ try
+ call assert_equal(0, assert_fails('xxx', [#{one: 1}]))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E731: Using a Dictionary as a String", exp)
+
+ let exp = ''
+ try
+ call assert_equal(0, assert_fails('xxx', ['E492', #{one: 1}]))
+ catch
+ let exp = v:exception
+ endtry
+ call assert_match("E731: Using a Dictionary as a String", exp)
try
call assert_equal(1, assert_fails('xxx', 'E492', '', 'burp'))
catch
let exp = v:exception
endtry
- call assert_match("E1115: assert_fails() fourth argument must be a number", exp)
+ call assert_match("E1210: Number required for argument 4", exp)
try
call assert_equal(1, assert_fails('xxx', 'E492', '', 54, 123))
catch
let exp = v:exception
endtry
- call assert_match("E1116: assert_fails() fifth argument must be a string", exp)
+ call assert_match("E1174: String required for argument 5", exp)
+
+ call assert_equal(1, assert_fails('c0', ['', '\(.\)\1']))
+ call assert_match("Expected '\\\\\\\\(.\\\\\\\\)\\\\\\\\1' but got 'E939: Positive count required: c0': c0", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Test for matching the line number and the script name in an error message
+ call writefile(['', 'call Xnonexisting()'], 'Xassertfails.vim', 'D')
+ call assert_fails('source Xassertfails.vim', 'E117:', '', 10)
+ call assert_match("Expected 10 but got 2", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_fails('source Xassertfails.vim', 'E117:', '', 2, 'Xabc')
+ call assert_match("Expected 'Xabc' but got .*Xassertfails.vim", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
+func Test_assert_wrong_arg_emsg_off()
+ CheckFeature folding
+
+ new
+ call setline(1, ['foo', 'bar'])
+ 1,2fold
+
+ " This used to crash Vim
+ let &l:foldtext = 'assert_match({}, {})'
+ redraw!
+
+ let &l:foldtext = 'assert_equalfile({}, {})'
+ redraw!
+
+ bwipe!
endfunc
func Test_assert_fails_in_try_block()
@@ -277,6 +344,23 @@ func Test_assert_fails_in_try_block()
endtry
endfunc
+" Test that assert_fails() in a timer does not cause a hit-enter prompt.
+" Requires using a terminal, in regular tests the hit-enter prompt won't be
+" triggered.
+func Test_assert_fails_in_timer()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', {'rows': 6})
+ let cmd = ":call timer_start(0, {-> assert_fails('call', 'E471:')})"
+ call term_sendkeys(buf, cmd)
+ call WaitForAssert({-> assert_equal(cmd, term_getline(buf, 6))})
+ call term_sendkeys(buf, "\<CR>")
+ call TermWait(buf, 100)
+ call assert_match('E471: Argument required', term_getline(buf, 6))
+
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_assert_beeps()
new
call assert_equal(0, assert_beeps('normal h'))
@@ -293,6 +377,12 @@ func Test_assert_beeps()
bwipe
endfunc
+func Test_assert_nobeep()
+ call assert_equal(1, assert_nobeep('normal! cr'))
+ call assert_match("command did beep: normal! cr", v:errors[0])
+ call remove(v:errors, 0)
+endfunc
+
func Test_assert_inrange()
call assert_equal(0, assert_inrange(7, 7, 7))
call assert_equal(0, assert_inrange(5, 7, 5))
@@ -314,21 +404,32 @@ func Test_assert_inrange()
call assert_fails('call assert_inrange(1, 1)', 'E119:')
- if has('float')
- call assert_equal(0, assert_inrange(7.0, 7, 7))
- call assert_equal(0, assert_inrange(7, 7.0, 7))
- call assert_equal(0, assert_inrange(7, 7, 7.0))
- call assert_equal(0, assert_inrange(5, 7, 5.0))
- call assert_equal(0, assert_inrange(5, 7, 6.0))
- call assert_equal(0, assert_inrange(5, 7, 7.0))
-
- call assert_equal(1, assert_inrange(5, 7, 4.0))
- call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0])
- call remove(v:errors, 0)
- call assert_equal(1, assert_inrange(5, 7, 8.0))
- call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
- call remove(v:errors, 0)
- endif
+ call assert_equal(0, assert_inrange(7.0, 7, 7))
+ call assert_equal(0, assert_inrange(7, 7.0, 7))
+ call assert_equal(0, assert_inrange(7, 7, 7.0))
+ call assert_equal(0, assert_inrange(5, 7, 5.0))
+ call assert_equal(0, assert_inrange(5, 7, 6.0))
+ call assert_equal(0, assert_inrange(5, 7, 7.0))
+
+ call assert_equal(1, assert_inrange(5, 7, 4.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 4.0", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, assert_inrange(5, 7, 8.0))
+ call assert_match("Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Use a custom message
+ call assert_equal(1, assert_inrange(5, 7, 8, "Higher"))
+ call assert_match("Higher: Expected range 5 - 7, but got 8", v:errors[0])
+ call remove(v:errors, 0)
+ call assert_equal(1, assert_inrange(5, 7, 8.0, "Higher"))
+ call assert_match("Higher: Expected range 5.0 - 7.0, but got 8.0", v:errors[0])
+ call remove(v:errors, 0)
+
+ " Invalid arguments
+ call assert_fails("call assert_inrange([], 2, 3)", 'E1219:')
+ call assert_fails("call assert_inrange(1, [], 3)", 'E1219:')
+ call assert_fails("call assert_inrange(1, 2, [])", 'E1219:')
endfunc
func Test_assert_with_msg()
@@ -337,6 +438,16 @@ func Test_assert_with_msg()
call remove(v:errors, 0)
endfunc
+func Test_override()
+ throw 'Skipped: Nvim does not support test_override()'
+ call test_override('char_avail', 1)
+ eval 1->test_override('redraw')
+ call test_override('ALL', 0)
+ call assert_fails("call test_override('xxx', 1)", 'E475:')
+ call assert_fails("call test_override('redraw', 'yes')", 'E474:')
+ call assert_fails("call test_override('redraw', 'yes')", 'E1210:')
+endfunc
+
func Test_mouse_position()
let save_mouse = &mouse
set mouse=a
@@ -356,6 +467,21 @@ func Test_mouse_position()
let &mouse = save_mouse
endfunc
+" Test for the test_alloc_fail() function
+func Test_test_alloc_fail()
+ throw 'Skipped: Nvim does not support test_alloc_fail()'
+ call assert_fails('call test_alloc_fail([], 1, 1)', 'E474:')
+ call assert_fails('call test_alloc_fail(10, [], 1)', 'E474:')
+ call assert_fails('call test_alloc_fail(10, 1, [])', 'E474:')
+ call assert_fails('call test_alloc_fail(999999, 1, 1)', 'E474:')
+endfunc
+
+" Test for the test_option_not_set() function
+func Test_test_option_not_set()
+ throw 'Skipped: Nvim does not support test_option_not_set()'
+ call assert_fails('call test_option_not_set("Xinvalidopt")', 'E475:')
+endfunc
+
" Must be last.
func Test_zz_quit_detected()
" Verify that if a test function ends Vim the test script detects this.
diff --git a/src/nvim/testdir/test_autochdir.vim b/test/old/testdir/test_autochdir.vim
index a8810047a0..652ce8b794 100644
--- a/src/nvim/testdir/test_autochdir.vim
+++ b/test/old/testdir/test_autochdir.vim
@@ -30,9 +30,9 @@ func Test_set_filename_other_window()
CheckFunction test_autochdir
let cwd = getcwd()
call test_autochdir()
- call mkdir('Xa')
- call mkdir('Xb')
- call mkdir('Xc')
+ call mkdir('Xa', 'R')
+ call mkdir('Xb', 'R')
+ call mkdir('Xc', 'R')
try
args Xa/aaa.txt Xb/bbb.txt
set acd
@@ -44,9 +44,6 @@ func Test_set_filename_other_window()
finally
set noacd
call chdir(cwd)
- call delete('Xa', 'rf')
- call delete('Xb', 'rf')
- call delete('Xc', 'rf')
bwipe! aaa.txt
bwipe! bbb.txt
bwipe! ccc.txt
@@ -59,10 +56,10 @@ func Test_acd_win_execute()
set acd
call test_autochdir()
- call mkdir('Xfile')
+ call mkdir('XacdDir', 'R')
let winid = win_getid()
- new Xfile/file
- call assert_match('testdir.Xfile$', getcwd())
+ new XacdDir/file
+ call assert_match('testdir.XacdDir$', getcwd())
cd ..
call assert_match('testdir$', getcwd())
call win_execute(winid, 'echo')
@@ -71,7 +68,6 @@ func Test_acd_win_execute()
bwipe!
set noacd
call chdir(cwd)
- call delete('Xfile', 'rf')
endfunc
func Test_verbose_pwd()
@@ -82,7 +78,7 @@ func Test_verbose_pwd()
edit global.txt
call assert_match('\[global\].*testdir$', execute('verbose pwd'))
- call mkdir('Xautodir')
+ call mkdir('Xautodir', 'R')
split Xautodir/local.txt
lcd Xautodir
call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd'))
@@ -116,7 +112,6 @@ func Test_verbose_pwd()
bwipe!
call chdir(cwd)
- call delete('Xautodir', 'rf')
endfunc
func Test_multibyte()
diff --git a/src/nvim/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
index 83af0f6be0..7926411dcd 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/test/old/testdir/test_autocmd.vim
@@ -4,6 +4,7 @@ source shared.vim
source check.vim
source term_util.vim
source screendump.vim
+source vim9.vim
source load.vim
func s:cleanup_buffers() abort
@@ -24,29 +25,27 @@ endfunc
" Test for the CursorHold autocmd
func Test_CursorHold_autocmd()
CheckRunVimInTerminal
- call writefile(['one', 'two', 'three'], 'Xfile')
+ call writefile(['one', 'two', 'three'], 'XoneTwoThree', 'D')
let before =<< trim END
set updatetime=10
- au CursorHold * call writefile([line('.')], 'Xoutput', 'a')
+ au CursorHold * call writefile([line('.')], 'XCHoutput', 'a')
END
- call writefile(before, 'Xinit')
- let buf = RunVimInTerminal('-S Xinit Xfile', {})
+ call writefile(before, 'XCHinit', 'D')
+ let buf = RunVimInTerminal('-S XCHinit XoneTwoThree', {})
call term_sendkeys(buf, "G")
- call term_wait(buf, 20)
+ call term_wait(buf, 50)
call term_sendkeys(buf, "gg")
call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1'], readfile('Xoutput')[-1:-1])})
+ call WaitForAssert({-> assert_equal(['1'], readfile('XCHoutput')[-1:-1])})
call term_sendkeys(buf, "j")
call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1', '2'], readfile('Xoutput')[-2:-1])})
+ call WaitForAssert({-> assert_equal(['1', '2'], readfile('XCHoutput')[-2:-1])})
call term_sendkeys(buf, "j")
call term_wait(buf)
- call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('Xoutput')[-3:-1])})
+ call WaitForAssert({-> assert_equal(['1', '2', '3'], readfile('XCHoutput')[-3:-1])})
call StopVimInTerminal(buf)
- call delete('Xinit')
- call delete('Xoutput')
- call delete('Xfile')
+ call delete('XCHoutput')
endfunc
if has('timers')
@@ -56,6 +55,9 @@ if has('timers')
endfunc
func Test_cursorhold_insert()
+ " depends on timing
+ let g:test_is_flaky = 1
+
" Need to move the cursor.
call feedkeys("ggG", "xt")
@@ -102,6 +104,22 @@ if has('timers')
set updatetime&
endfunc
+ func Test_cursorhold_insert_ctrl_g_U()
+ au CursorHoldI * :
+ set updatetime=20
+ new
+ call timer_start(100, { -> feedkeys("\<Left>foo\<Esc>", 't') })
+ call feedkeys("i()\<C-g>U", 'tx!')
+ sleep 200m
+ call assert_equal('(foo)', getline(1))
+ undo
+ call assert_equal('', getline(1))
+
+ bwipe!
+ au! CursorHoldI
+ set updatetime&
+ endfunc
+
func Test_OptionSet_modeline()
CheckFunction test_override
call test_override('starting', 1)
@@ -109,7 +127,7 @@ if has('timers')
augroup set_tabstop
au OptionSet tabstop call timer_start(1, {-> execute("echo 'Handler called'", "")})
augroup END
- call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline')
+ call writefile(['vim: set ts=7 sw=5 :', 'something'], 'XoptionsetModeline', 'D')
set modeline
let v:errmsg = ''
call assert_fails('split XoptionsetModeline', 'E12:')
@@ -121,7 +139,6 @@ if has('timers')
augroup END
bwipe!
set ts&
- call delete('XoptionsetModeline')
call test_override('starting', 0)
endfunc
@@ -231,8 +248,8 @@ endfunc
func Test_autocmd_dummy_wipeout()
" prepare files
- call writefile([''], 'Xdummywipetest1.txt')
- call writefile([''], 'Xdummywipetest2.txt')
+ call writefile([''], 'Xdummywipetest1.txt', 'D')
+ call writefile([''], 'Xdummywipetest2.txt', 'D')
augroup test_bufunload_group
autocmd!
autocmd BufUnload * call add(s:li, "bufunload")
@@ -246,8 +263,6 @@ func Test_autocmd_dummy_wipeout()
call assert_equal(["bufunload", "bufwipeout"], s:li)
bwipeout
- call delete('Xdummywipetest1.txt')
- call delete('Xdummywipetest2.txt')
au! test_bufunload_group
augroup! test_bufunload_group
endfunc
@@ -918,14 +933,13 @@ func Test_BufEnter()
call assert_equal('++', g:val)
" Also get BufEnter when editing a directory
- call mkdir('Xdir')
- split Xdir
+ call mkdir('Xbufenterdir', 'D')
+ split Xbufenterdir
call assert_equal('+++', g:val)
" On MS-Windows we can't edit the directory, make sure we wipe the right
" buffer.
- bwipe! Xdir
- call delete('Xdir', 'd')
+ bwipe! Xbufenterdir
au! BufEnter
" Editing a "nofile" buffer doesn't read the file but does trigger BufEnter
@@ -967,18 +981,19 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
augroup END
func WriteErrors()
- call writefile([execute("messages")], "Xerrors")
+ call writefile([execute("messages")], "XerrorsBwipe")
endfunc
au VimLeave * call WriteErrors()
[CODE]
- call writefile(content, 'Xvimrc')
+ call writefile(content, 'Xvimrc', 'D')
call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
- let errors = join(readfile('Xerrors'))
- call assert_match('E814', errors)
+ sleep 100m
+ let errors = join(readfile('XerrorsBwipe'))
+ call assert_match('E814:', errors)
set swapfile
- for file in ['Session.vim', 'Xvimrc', 'Xerrors']
+ for file in ['Session.vim', 'XerrorsBwipe']
call delete(file)
endfor
endfunc
@@ -991,16 +1006,16 @@ func Test_autocmd_blast_badd()
edit foo1
au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
edit foo2
- call writefile(['OK'], 'Xerrors')
+ call writefile(['OK'], 'XerrorsBlast')
qall
[CODE]
- call writefile(content, 'XblastBall')
+ call writefile(content, 'XblastBall', 'D')
call system(GetVimCommand() .. ' --clean -S XblastBall')
- call assert_match('OK', readfile('Xerrors')->join())
+ sleep 100m
+ call assert_match('OK', readfile('XerrorsBlast')->join())
- call delete('XblastBall')
- call delete('Xerrors')
+ call delete('XerrorsBlast')
endfunc
" SEGV occurs in older versions.
@@ -1027,20 +1042,21 @@ func Test_autocmd_bufwipe_in_SessLoadPost2()
au SessionLoadPost * call DeleteInactiveBufs()
func WriteErrors()
- call writefile([execute("messages")], "Xerrors")
+ call writefile([execute("messages")], "XerrorsPost")
endfunc
au VimLeave * call WriteErrors()
[CODE]
- call writefile(content, 'Xvimrc')
+ call writefile(content, 'Xvimrc', 'D')
call system(GetVimCommand('Xvimrc') .. ' --headless --noplugins -S Session.vim -c cq')
- let errors = join(readfile('Xerrors'))
+ sleep 100m
+ let errors = join(readfile('XerrorsPost'))
" This probably only ever matches on unix.
call assert_notmatch('Caught deadly signal SEGV', errors)
call assert_match('SessionLoadPost DONE', errors)
set swapfile
- for file in ['Session.vim', 'Xvimrc', 'Xerrors']
+ for file in ['Session.vim', 'XerrorsPost']
call delete(file)
endfor
endfunc
@@ -1722,30 +1738,30 @@ endfunc
" Test for Bufleave autocommand that deletes the buffer we are about to edit.
func Test_BufleaveWithDelete()
- new | edit Xfile1
+ new | edit XbufLeave1
augroup test_bufleavewithdelete
autocmd!
- autocmd BufLeave Xfile1 bwipe Xfile2
+ autocmd BufLeave XbufLeave1 bwipe XbufLeave2
augroup END
- call assert_fails('edit Xfile2', 'E143:')
- call assert_equal('Xfile1', bufname('%'))
+ call assert_fails('edit XbufLeave2', 'E143:')
+ call assert_equal('XbufLeave1', bufname('%'))
- autocmd! test_bufleavewithdelete BufLeave Xfile1
+ autocmd! test_bufleavewithdelete BufLeave XbufLeave1
augroup! test_bufleavewithdelete
new
- bwipe! Xfile1
+ bwipe! XbufLeave1
endfunc
" Test for autocommand that changes the buffer list, when doing ":ball".
func Test_Acmd_BufAll()
enew!
%bwipe!
- call writefile(['Test file Xxx1'], 'Xxx1')
- call writefile(['Test file Xxx2'], 'Xxx2')
- call writefile(['Test file Xxx3'], 'Xxx3')
+ call writefile(['Test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['Test file Xxx2'], 'Xxx2', 'D')
+ call writefile(['Test file Xxx3'], 'Xxx3', 'D')
" Add three files to the buffer list
split Xxx1
@@ -1767,9 +1783,6 @@ func Test_Acmd_BufAll()
au! BufReadPost
%bwipe!
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xxx3')
enew! | only
endfunc
@@ -1779,11 +1792,11 @@ func Test_Acmd_BufEnter()
%bwipe!
call writefile(['start of test file Xxx1',
\ "\<Tab>this is a test",
- \ 'end of test file Xxx1'], 'Xxx1')
+ \ 'end of test file Xxx1'], 'Xxx1', 'D')
call writefile(['start of test file Xxx2',
\ 'vim: set noai :',
\ "\<Tab>this is a test",
- \ 'end of test file Xxx2'], 'Xxx2')
+ \ 'end of test file Xxx2'], 'Xxx2', 'D')
au BufEnter Xxx2 brew
set ai modeline modelines=3
@@ -1805,8 +1818,6 @@ func Test_Acmd_BufEnter()
call assert_equal(4, line('.'))
%bwipe!
- call delete('Xxx1')
- call delete('Xxx2')
set ai&vim modeline&vim modelines&vim
endfunc
@@ -1833,8 +1844,8 @@ func Test_BufLeave_Wipe()
let content = ['start of test file Xxx',
\ 'this is a test',
\ 'end of test file Xxx']
- call writefile(content, 'Xxx1')
- call writefile(content, 'Xxx2')
+ call writefile(content, 'Xxx1', 'D')
+ call writefile(content, 'Xxx2', 'D')
au BufLeave Xxx2 bwipe
edit Xxx1
@@ -1860,8 +1871,6 @@ func Test_BufLeave_Wipe()
let g:bufinfo = getbufinfo()
call assert_equal(1, len(g:bufinfo))
- call delete('Xxx1')
- call delete('Xxx2')
call delete('test.out')
%bwipe
au! BufLeave
@@ -1897,6 +1906,64 @@ func Test_Cmdline()
call assert_equal(':', g:entered)
au! CmdlineChanged
+ autocmd CmdlineChanged : let g:log += [getcmdline()]
+
+ let g:log = []
+ cnoremap <F1> <Cmd>call setcmdline('ls')<CR>
+ call feedkeys(":\<F1>", 'xt')
+ call assert_equal(['ls'], g:log)
+ cunmap <F1>
+
+ let g:log = []
+ call feedkeys(":sign \<Tab>\<Tab>\<C-N>\<C-P>\<S-Tab>\<S-Tab>\<Esc>", 'xt')
+ call assert_equal([
+ \ 's',
+ \ 'si',
+ \ 'sig',
+ \ 'sign',
+ \ 'sign ',
+ \ 'sign define',
+ \ 'sign jump',
+ \ 'sign list',
+ \ 'sign jump',
+ \ 'sign define',
+ \ 'sign ',
+ \ ], g:log)
+ let g:log = []
+ set wildmenu wildoptions+=pum
+ call feedkeys(":sign \<S-Tab>\<PageUp>\<kPageUp>\<kPageDown>\<PageDown>\<Esc>", 'xt')
+ call assert_equal([
+ \ 's',
+ \ 'si',
+ \ 'sig',
+ \ 'sign',
+ \ 'sign ',
+ \ 'sign unplace',
+ \ 'sign jump',
+ \ 'sign define',
+ \ 'sign undefine',
+ \ 'sign unplace',
+ \ ], g:log)
+ set wildmenu& wildoptions&
+
+ let g:log = []
+ let @r = 'abc'
+ call feedkeys(":0\<C-R>r1\<C-R>\<C-O>r2\<C-R>\<C-R>r3\<Esc>", 'xt')
+ call assert_equal([
+ \ '0',
+ \ '0a',
+ \ '0ab',
+ \ '0abc',
+ \ '0abc1',
+ \ '0abc1abc',
+ \ '0abc1abc2',
+ \ '0abc1abc2abc',
+ \ '0abc1abc2abc3',
+ \ ], g:log)
+
+ unlet g:log
+ au! CmdlineChanged
+
au! CmdlineEnter : let g:entered = expand('<afile>')
au! CmdlineLeave : let g:left = expand('<afile>')
let g:entered = 0
@@ -1930,31 +1997,30 @@ func Test_BufWritePre()
au BufWritePre Xxx1 bunload
au BufWritePre Xxx2 bwipe
- call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1')
- call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2')
+ call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1', 'D')
+ call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2', 'D')
edit Xtest
e! Xxx2
bdel Xtest
e Xxx1
" write it, will unload it and give an error msg
- call assert_fails('w', 'E203')
+ call assert_fails('w', 'E203:')
call assert_equal('Xxx2', bufname('%'))
edit Xtest
e! Xxx2
bwipe Xtest
" write it, will delete the buffer and give an error msg
- call assert_fails('w', 'E203')
+ call assert_fails('w', 'E203:')
call assert_equal('Xxx1', bufname('%'))
au! BufWritePre
- call delete('Xxx1')
- call delete('Xxx2')
endfunc
" Test for BufUnload autocommand that unloads all the other buffers
func Test_bufunload_all()
- call writefile(['Test file Xxx1'], 'Xxx1')"
- call writefile(['Test file Xxx2'], 'Xxx2')"
+ let g:test_is_flaky = 1
+ call writefile(['Test file Xxx1'], 'Xxx1', 'D')
+ call writefile(['Test file Xxx2'], 'Xxx2', 'D')
let content =<< trim [CODE]
func UnloadAllBufs()
@@ -1974,15 +2040,12 @@ func Test_bufunload_all()
q
[CODE]
- call writefile(content, 'Xtest')
+ call writefile(content, 'Xbunloadtest', 'D')
call delete('Xout')
- call system(GetVimCommandClean() .. ' -N --headless -S Xtest')
+ call system(GetVimCommandClean() .. ' -N --headless -S Xbunloadtest')
call assert_true(filereadable('Xout'))
- call delete('Xxx1')
- call delete('Xxx2')
- call delete('Xtest')
call delete('Xout')
endfunc
@@ -2010,7 +2073,7 @@ endfunc
" Test for "*Cmd" autocommands
func Test_Cmd_Autocmds()
- call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx')
+ call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx', 'D')
enew!
au BufReadCmd XtestA 0r Xxx|$del
@@ -2085,7 +2148,6 @@ func Test_Cmd_Autocmds()
au! FileWriteCmd
au! FileAppendCmd
%bwipe!
- call delete('Xxx')
enew!
endfunc
@@ -2110,7 +2172,7 @@ func Test_BufReadCmd()
autocmd BufReadCmd *.test call s:ReadFile()
autocmd BufWriteCmd *.test call s:WriteFile()
- call writefile(['one', 'two', 'three'], 'Xcmd.test')
+ call writefile(['one', 'two', 'three'], 'Xcmd.test', 'D')
edit Xcmd.test
call assert_match('Xcmd.test" line 1 of 3', execute('file'))
normal! Gofour
@@ -2118,7 +2180,6 @@ func Test_BufReadCmd()
call assert_equal(['one', 'two', 'three', 'four'], readfile('Xcmd.test'))
bwipe!
- call delete('Xcmd.test')
au! BufReadCmd
au! BufWriteCmd
endfunc
@@ -2128,11 +2189,10 @@ func Test_BufWriteCmd()
new
file Xbufwritecmd
set buftype=acwrite
- call mkdir('Xbufwritecmd')
+ call mkdir('Xbufwritecmd', 'D')
write
" BufWriteCmd should be triggered even if a directory has the same name
call assert_equal(1, g:written)
- call delete('Xbufwritecmd', 'd')
unlet g:written
au! BufWriteCmd
bwipe!
@@ -2387,7 +2447,7 @@ function Test_dirchanged_auto()
set acd
cd ..
call assert_equal([], s:li)
- exe 'edit ' . s:dir_foo . '/Xfile'
+ exe 'edit ' . s:dir_foo . '/Xautofile'
call assert_equal(s:dir_foo, getcwd())
let expected = ["pre cd " .. s:dir_foo, "auto:", s:dir_foo]
call assert_equal(expected, s:li)
@@ -2398,7 +2458,7 @@ endfunc
" Test TextChangedI and TextChangedP
func Test_ChangedP()
- throw 'Skipped: use test/functional/editor/completion_spec.lua'
+ throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua'
new
call setline(1, ['foo', 'bar', 'foobar'])
call test_override("char_avail", 1)
@@ -2408,6 +2468,7 @@ func Test_ChangedP()
let g:autocmd .= a:char
endfunc
+ " TextChanged will not be triggered, only check that it isn't.
au! TextChanged <buffer> :call TextChangedAutocmd('N')
au! TextChangedI <buffer> :call TextChangedAutocmd('I')
au! TextChangedP <buffer> :call TextChangedAutocmd('P')
@@ -2461,7 +2522,7 @@ func SetLineOne()
endfunc
func Test_TextChangedI_with_setline()
- CheckFunction test_override
+ throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua'
new
call test_override('char_avail', 1)
autocmd TextChangedI <buffer> call SetLineOne()
@@ -2483,11 +2544,17 @@ func Test_Changed_FirstTime()
let g:test_is_flaky = 1
" Prepare file for TextChanged event.
- call writefile([''], 'Xchanged.txt')
+ call writefile([''], 'Xchanged.txt', 'D')
let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile'], {'term_rows': 3})
call assert_equal('running', term_getstatus(buf))
" Wait for the ruler (in the status line) to be shown.
- call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
+ " In ConPTY, there is additional character which is drawn up to the width of
+ " the screen.
+ if has('conpty')
+ call WaitForAssert({-> assert_match('\<All.*$', term_getline(buf, 3))})
+ else
+ call WaitForAssert({-> assert_match('\<All$', term_getline(buf, 3))})
+ endif
" It's only adding autocmd, so that no event occurs.
call term_sendkeys(buf, ":au! TextChanged <buffer> call writefile(['No'], 'Xchanged.txt')\<cr>")
call term_sendkeys(buf, "\<C-\\>\<C-N>:qa!\<cr>")
@@ -2495,7 +2562,6 @@ func Test_Changed_FirstTime()
call assert_equal([''], readfile('Xchanged.txt'))
" clean up
- call delete('Xchanged.txt')
bwipe!
endfunc
@@ -2582,13 +2648,12 @@ func Test_autocmd_nested_switch_window()
autocmd BufReadPost * autocmd SafeState * ++once foldclosed('.')
autocmd WinEnter * matchadd('ErrorMsg', 'pat')
END
- call writefile(lines, 'Xautoscript')
+ call writefile(lines, 'Xautoscript', 'D')
let buf = RunVimInTerminal('-S Xautoscript', {'rows': 10})
call VerifyScreenDump(buf, 'Test_autocmd_nested_switch', {})
call StopVimInTerminal(buf)
call delete('Xautofile')
- call delete('Xautoscript')
endfunc
func Test_autocmd_once()
@@ -2662,7 +2727,7 @@ func Test_autocmd_bufreadpre()
close
close
call delete('XAutocmdBufReadPre.txt')
- " set cpo-=g
+ set cpo-=g
endfunc
" FileChangedShell tested in test_filechanged.vim
@@ -2741,7 +2806,7 @@ func Test_ReadWrite_Autocmds()
au FileAppendPost *.out !cat Xtest.c >> test.out
augroup END
- call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c')
+ call writefile(['/*', ' * Here is a new .c file', ' */'], 'Xtest.c', 'D')
new foo.c " should load Xtest.c
call assert_equal(['/*', ' * Here is a new .c file', ' */'], getline(2, 4))
w! >> test.out " append it to the output file
@@ -2865,7 +2930,6 @@ func Test_ReadWrite_Autocmds()
au! FileChangedShell
call delete('Xtestfile.gz')
- call delete('Xtest.c')
call delete('test.out')
endfunc
@@ -2889,16 +2953,49 @@ func Test_throw_in_BufWritePre()
endfunc
func Test_autocmd_in_try_block()
- call mkdir('Xdir')
+ call mkdir('Xintrydir', 'R')
au BufEnter * let g:fname = expand('%')
try
- edit Xdir/
+ edit Xintrydir/
endtry
- call assert_match('Xdir', g:fname)
+ call assert_match('Xintrydir', g:fname)
unlet g:fname
au! BufEnter
- call delete('Xdir', 'rf')
+endfunc
+
+func Test_autocmd_SafeState()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ let g:safe = 0
+ let g:again = ''
+ au SafeState * let g:safe += 1
+ au SafeStateAgain * let g:again ..= 'x'
+ func CallTimer()
+ call timer_start(10, {id -> execute('let g:again ..= "t"')})
+ endfunc
+ END
+ call writefile(lines, 'XSafeState', 'D')
+ let buf = RunVimInTerminal('-S XSafeState', #{rows: 6})
+
+ " Sometimes we loop to handle a K_IGNORE, SafeState may be triggered once or
+ " more often.
+ call term_sendkeys(buf, ":echo g:safe\<CR>")
+ call WaitForAssert({-> assert_match('^\d ', term_getline(buf, 6))}, 1000)
+
+ " SafeStateAgain should be invoked at least three times
+ call term_sendkeys(buf, ":echo g:again\<CR>")
+ call WaitForAssert({-> assert_match('^xxx', term_getline(buf, 6))}, 1000)
+
+ call term_sendkeys(buf, ":let g:again = ''\<CR>:call CallTimer()\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":echo g:again\<CR>")
+ call WaitForAssert({-> assert_match('xtx', term_getline(buf, 6))}, 1000)
+
+ call StopVimInTerminal(buf)
endfunc
func Test_autocmd_CmdWinEnter()
@@ -2917,7 +3014,7 @@ func Test_autocmd_CmdWinEnter()
let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
call term_sendkeys(buf, "q:")
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, ":echo b:dummy_var\<cr>")
call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 2000)
call term_sendkeys(buf, ":echo &buftype\<cr>")
@@ -2931,6 +3028,8 @@ func Test_autocmd_CmdWinEnter()
endfunc
func Test_autocmd_was_using_freed_memory()
+ CheckFeature quickfix
+
pedit xx
n x
augroup winenter
@@ -2951,6 +3050,7 @@ func Test_autocmd_was_using_freed_memory()
endfunc
func Test_BufWrite_lockmarks()
+ let g:test_is_flaky = 1
edit! Xtest
call setline(1, ['a', 'b', 'c', 'd'])
@@ -3032,7 +3132,7 @@ func Test_FileChangedRO_winclose()
augroup FileChangedROTest
au!
- autocmd FileChangedRO * edit Xfile
+ autocmd FileChangedRO * edit Xrofile
augroup END
new
set readonly
@@ -3098,7 +3198,7 @@ func Test_autocmd_FileReadCmd()
\ 'v:cmdarg = ++ff=mac',
\ 'v:cmdarg = ++enc=utf-8'], getline(1, '$'))
- close!
+ bwipe!
augroup FileReadCmdTest
au!
augroup END
@@ -3108,13 +3208,13 @@ endfunc
" Test for passing invalid arguments to autocmd
func Test_autocmd_invalid_args()
" Additional character after * for event
- call assert_fails('autocmd *a Xfile set ff=unix', 'E215:')
+ call assert_fails('autocmd *a Xinvfile set ff=unix', 'E215:')
augroup Test
augroup END
" Invalid autocmd event
- call assert_fails('autocmd Bufabc Xfile set ft=vim', 'E216:')
+ call assert_fails('autocmd Bufabc Xinvfile set ft=vim', 'E216:')
" Invalid autocmd event in a autocmd group
- call assert_fails('autocmd Test Bufabc Xfile set ft=vim', 'E216:')
+ call assert_fails('autocmd Test Bufabc Xinvfile set ft=vim', 'E216:')
augroup! Test
" Execute all autocmds
call assert_fails('doautocmd * BufEnter', 'E217:')
@@ -3125,9 +3225,9 @@ endfunc
" Test for deep nesting of autocmds
func Test_autocmd_deep_nesting()
- autocmd BufEnter Xfile doautocmd BufEnter Xfile
- call assert_fails('doautocmd BufEnter Xfile', 'E218:')
- autocmd! BufEnter Xfile
+ autocmd BufEnter Xdeepfile doautocmd BufEnter Xdeepfile
+ call assert_fails('doautocmd BufEnter Xdeepfile', 'E218:')
+ autocmd! BufEnter Xdeepfile
endfunc
" Tests for SigUSR1 autocmd event, which is only available on posix systems.
@@ -3147,13 +3247,13 @@ endfunc
func Test_BufReadPre_delfile()
augroup TestAuCmd
au!
- autocmd BufReadPre Xfile call delete('Xfile')
+ autocmd BufReadPre XbufreadPre call delete('XbufreadPre')
augroup END
- call writefile([], 'Xfile')
- call assert_fails('new Xfile', 'E200:')
- call assert_equal('Xfile', @%)
+ call writefile([], 'XbufreadPre', 'D')
+ call assert_fails('new XbufreadPre', 'E200:')
+ call assert_equal('XbufreadPre', @%)
call assert_equal(1, &readonly)
- call delete('Xfile')
+
augroup TestAuCmd
au!
augroup END
@@ -3164,13 +3264,13 @@ endfunc
func Test_BufReadPre_changebuf()
augroup TestAuCmd
au!
- autocmd BufReadPre Xfile edit Xsomeotherfile
+ autocmd BufReadPre Xchangebuf edit Xsomeotherfile
augroup END
- call writefile([], 'Xfile')
- call assert_fails('new Xfile', 'E201:')
+ call writefile([], 'Xchangebuf', 'D')
+ call assert_fails('new Xchangebuf', 'E201:')
call assert_equal('Xsomeotherfile', @%)
call assert_equal(1, &readonly)
- call delete('Xfile')
+
augroup TestAuCmd
au!
augroup END
@@ -3358,6 +3458,99 @@ func Test_autocmd_vimgrep()
augroup END
endfunc
+" Test TextChangedI and TextChanged
+func Test_Changed_ChangedI()
+ throw 'Skipped: use test/functional/autocmd/textchanged_spec.lua'
+ new
+ call test_override("char_avail", 1)
+ let [g:autocmd_i, g:autocmd_n] = ['','']
+
+ func! TextChangedAutocmdI(char)
+ let g:autocmd_{tolower(a:char)} = a:char .. b:changedtick
+ endfunc
+
+ augroup Test_TextChanged
+ au!
+ au TextChanged <buffer> :call TextChangedAutocmdI('N')
+ au TextChangedI <buffer> :call TextChangedAutocmdI('I')
+ augroup END
+
+ call feedkeys("ifoo\<esc>", 'tnix')
+ " TODO: Test test does not seem to trigger TextChanged autocommand, this
+ " requires running Vim in a terminal window.
+ " call assert_equal('N3', g:autocmd_n)
+ call assert_equal('I3', g:autocmd_i)
+
+ call feedkeys("yyp", 'tnix')
+ " TODO: Test test does not seem to trigger TextChanged autocommand.
+ " call assert_equal('N4', g:autocmd_n)
+ call assert_equal('I3', g:autocmd_i)
+
+ " TextChangedI should only trigger if change was done in Insert mode
+ let g:autocmd_i = ''
+ call feedkeys("yypi\<esc>", 'tnix')
+ call assert_equal('', g:autocmd_i)
+
+ " TextChanged should only trigger if change was done in Normal mode
+ let g:autocmd_n = ''
+ call feedkeys("ibar\<esc>", 'tnix')
+ call assert_equal('', g:autocmd_n)
+
+ " If change is a mix of Normal and Insert modes, TextChangedI should trigger
+ func s:validate_mixed_textchangedi(keys)
+ call feedkeys("ifoo\<esc>", 'tnix')
+ let g:autocmd_i = ''
+ let g:autocmd_n = ''
+ call feedkeys(a:keys, 'tnix')
+ call assert_notequal('', g:autocmd_i)
+ call assert_equal('', g:autocmd_n)
+ endfunc
+
+ call s:validate_mixed_textchangedi("o\<esc>")
+ call s:validate_mixed_textchangedi("O\<esc>")
+ call s:validate_mixed_textchangedi("ciw\<esc>")
+ call s:validate_mixed_textchangedi("cc\<esc>")
+ call s:validate_mixed_textchangedi("C\<esc>")
+ call s:validate_mixed_textchangedi("s\<esc>")
+ call s:validate_mixed_textchangedi("S\<esc>")
+
+
+ " CleanUp
+ call test_override("char_avail", 0)
+ au! TextChanged <buffer>
+ au! TextChangedI <buffer>
+ augroup! Test_TextChanged
+ delfu TextChangedAutocmdI
+ unlet! g:autocmd_i g:autocmd_n
+
+ bw!
+endfunc
+
+func Test_closing_autocmd_window()
+ let lines =<< trim END
+ edit Xa.txt
+ tabnew Xb.txt
+ autocmd BufEnter Xa.txt unhide 1
+ doautoall BufEnter
+ END
+ call CheckScriptFailure(lines, 'E814:')
+ au! BufEnter
+ bwipe Xa.txt
+ bwipe Xb.txt
+endfunc
+
+func Test_switch_window_in_autocmd_window()
+ edit Xa.txt
+ tabnew Xb.txt
+ autocmd BufEnter Xa.txt wincmd w
+ doautoall BufEnter
+ au! BufEnter
+ bwipe Xa.txt
+ call assert_false(bufexists('Xa.txt'))
+ bwipe Xb.txt
+ call assert_false(bufexists('Xb.txt'))
+endfunc
+
func Test_bufwipeout_changes_window()
" This should not crash, but we don't have any expectations about what
" happens, changing window in BufWipeout has unpredictable results.
@@ -3395,7 +3588,7 @@ endfunc
" Test for ModeChanged pattern
func Test_mode_changes()
let g:index = 0
- let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n']
+ let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'noV', 'n', 'V', 'v', 's', 'n']
func! TestMode()
call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode"))
call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode"))
@@ -3406,7 +3599,7 @@ func Test_mode_changes()
au ModeChanged * :call TestMode()
let g:n_to_any = 0
au ModeChanged n:* let g:n_to_any += 1
- call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix')
+ call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdV\<MouseMove>G", 'tnix')
let g:V_to_v = 0
au ModeChanged V:v let g:V_to_v += 1
@@ -3463,8 +3656,6 @@ func Test_mode_changes()
call assert_equal(len(g:mode_seq) - 1, g:index)
call assert_equal(2, g:n_to_c)
call assert_equal(2, g:c_to_n)
- unlet g:n_to_c
- unlet g:c_to_n
let g:n_to_v = 0
au ModeChanged n:v let g:n_to_v += 1
@@ -3475,8 +3666,10 @@ func Test_mode_changes()
call assert_equal(len(g:mode_seq) - 1, g:index)
call assert_equal(1, g:n_to_v)
call assert_equal(1, g:v_to_n)
- unlet g:n_to_v
- unlet g:v_to_n
+
+ let g:mode_seq += ['c', 'cr', 'c', 'cr', 'n']
+ call feedkeys(":\<Insert>\<Insert>\<Insert>\<CR>", 'tnix')
+ call assert_equal(len(g:mode_seq) - 1, g:index)
au! ModeChanged
delfunc TestMode
@@ -3491,6 +3684,10 @@ func Test_mode_changes()
unlet! g:i_to_n
unlet! g:nori_to_any
unlet! g:i_to_any
+ unlet! g:n_to_c
+ unlet! g:c_to_n
+ unlet! g:n_to_v
+ unlet! g:v_to_n
endfunc
func Test_recursive_ModeChanged()
@@ -3568,5 +3765,61 @@ func Test_autocmd_nested_setbufvar()
%bwipe!
endfunc
+func SetupVimTest_shm()
+ let g:bwe = []
+ let g:brp = []
+ set shortmess-=l
+ messages clear
+
+ let dirname='XVimTestSHM'
+ call mkdir(dirname, 'R')
+ call writefile(['test'], dirname .. '/1')
+ call writefile(['test'], dirname .. '/2')
+ call writefile(['test'], dirname .. '/3')
+
+ augroup test
+ autocmd!
+ autocmd BufWinEnter * call add(g:bwe, $'BufWinEnter: {expand('<amatch>')}')
+ autocmd BufReadPost * call add(g:brp, $'BufReadPost: {expand('<amatch>')}')
+ augroup END
+
+ call setqflist([
+ \ {'filename': dirname .. '/1', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0},
+ \ {'filename': dirname .. '/2', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0},
+ \ {'filename': dirname .. '/3', 'lnum': 1, 'col': 1, 'text': 'test', 'vcol': 0}
+ \ ])
+ cdo! substitute/test/TEST
+
+ " clean up
+ noa enew!
+ set shortmess&vim
+ augroup test
+ autocmd!
+ augroup END
+ augroup! test
+endfunc
+
+func Test_autocmd_shortmess()
+ CheckNotMSWindows
+
+ call SetupVimTest_shm()
+ let output = execute(':mess')->split('\n')
+
+ let info = copy(output)->filter({idx, val -> val =~# '\d of 3'} )
+ let bytes = copy(output)->filter({idx, val -> val =~# 'bytes'} )
+
+ " We test the following here:
+ " BufReadPost should have been triggered 3 times, once per file
+ " BufWinEnter should have been triggered 3 times, once per file
+ " FileInfoMessage should have been shown 3 times, regardless of shm option
+ " "(x of 3)" message from :cnext has been shown 3 times
+
+ call assert_equal(3, g:brp->len())
+ call assert_equal(3, g:bwe->len())
+ call assert_equal(3, info->len())
+ call assert_equal(3, bytes->len())
+
+ delfunc SetupVimTest_shm
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_autoload.vim b/test/old/testdir/test_autoload.vim
index e89fe3943b..e89fe3943b 100644
--- a/src/nvim/testdir/test_autoload.vim
+++ b/test/old/testdir/test_autoload.vim
diff --git a/src/nvim/testdir/test_backspace_opt.vim b/test/old/testdir/test_backspace_opt.vim
index 59e94d2898..78c51cb874 100644
--- a/src/nvim/testdir/test_backspace_opt.vim
+++ b/test/old/testdir/test_backspace_opt.vim
@@ -36,15 +36,15 @@ func Test_backspace_option()
" NOTE: Vim doesn't check following error...
"call assert_fails('set backspace-=ghi', 'E474:')
- " Check backwards compatibility with version 5.4 and earlier
- set backspace=0
- call assert_equal('0', &backspace)
- set backspace=1
- call assert_equal('1', &backspace)
- set backspace=2
- call assert_equal('2', &backspace)
- set backspace=3
- call assert_equal('3', &backspace)
+ " " Check backwards compatibility with version 5.4 and earlier
+ " set backspace=0
+ " call assert_equal('0', &backspace)
+ " set backspace=1
+ " call assert_equal('1', &backspace)
+ " set backspace=2
+ " call assert_equal('2', &backspace)
+ " set backspace=3
+ " call assert_equal('3', &backspace)
call assert_fails('set backspace=4', 'E474:')
call assert_fails('set backspace=10', 'E474:')
diff --git a/src/nvim/testdir/test_backup.vim b/test/old/testdir/test_backup.vim
index d304773da4..862ec2e4a1 100644
--- a/src/nvim/testdir/test_backup.vim
+++ b/test/old/testdir/test_backup.vim
@@ -79,11 +79,11 @@ endfunc
func Test_non_existing_backupdir()
throw 'Skipped: Nvim auto-creates backup directory'
set backupdir=./non_existing_dir backupskip=
- call writefile(['line1'], 'Xfile')
- new Xfile
+ call writefile(['line1'], 'Xbackupdir', 'D')
+ new Xbackupdir
call assert_fails('write', 'E510:')
+
set backupdir&vim backupskip&vim
- call delete('Xfile')
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_blob.vim b/test/old/testdir/test_blob.vim
new file mode 100644
index 0000000000..25b3c00dfc
--- /dev/null
+++ b/test/old/testdir/test_blob.vim
@@ -0,0 +1,867 @@
+" Tests for the Blob types
+
+source check.vim
+source vim9.vim
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tests for Blob type
+
+" Blob creation from constant
+func Test_blob_create()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call assert_equal(v:t_blob, type(b))
+ call assert_equal(4, len(b))
+ call assert_equal(0xDE, b[0])
+ call assert_equal(0xAD, b[1])
+ call assert_equal(0xBE, b[2])
+ call assert_equal(0xEF, b[3])
+ call assert_fails('VAR x = b[4]')
+
+ call assert_equal(0xDE, get(b, 0))
+ call assert_equal(0xEF, get(b, 3))
+
+ call assert_fails('VAR b = 0z1', 'E973:')
+ call assert_fails('VAR b = 0z1x', 'E973:')
+ call assert_fails('VAR b = 0z12345', 'E973:')
+
+ call assert_equal(0z, v:_null_blob)
+
+ LET b = 0z001122.33445566.778899.aabbcc.dd
+ call assert_equal(0z00112233445566778899aabbccdd, b)
+ call assert_fails('VAR b = 0z1.1')
+ call assert_fails('VAR b = 0z.')
+ call assert_fails('VAR b = 0z001122.')
+ call assert_fails('call get("", 1)', 'E896:')
+ call assert_equal(0, len(v:_null_blob))
+ call assert_equal(0z, copy(v:_null_blob))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" assignment to a blob
+func Test_blob_assign()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ VAR b2 = b[1 : 2]
+ call assert_equal(0zADBE, b2)
+
+ VAR bcopy = b[:]
+ call assert_equal(b, bcopy)
+ call assert_false(b is bcopy)
+
+ LET b = 0zDEADBEEF
+ LET b2 = b
+ call assert_true(b is b2)
+ LET b[:] = 0z11223344
+ call assert_equal(0z11223344, b)
+ call assert_equal(0z11223344, b2)
+ call assert_true(b is b2)
+
+ LET b = 0zDEADBEEF
+ LET b[3 :] = 0z66
+ call assert_equal(0zDEADBE66, b)
+ LET b[: 1] = 0z8899
+ call assert_equal(0z8899BE66, b)
+
+ LET b = 0zDEADBEEF
+ LET b += 0z99
+ call assert_equal(0zDEADBEEF99, b)
+
+ VAR l = [0z12]
+ VAR m = deepcopy(l)
+ LET m[0] = 0z34 #" E742 or E741 should not occur.
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[2 : 3] = 0z112233
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E972:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[2 : 3] = 0z11
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E972:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[3 : 2] = 0z
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b ..= 0z33
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b ..= "xx"
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1019:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b += "xx"
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ LET b[1 : 1] ..= 0z55
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1183:', 'E734:'])
+
+ call assert_fails('let b = readblob("a1b2c3")', 'E484:')
+endfunc
+
+func Test_blob_get_range()
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ call assert_equal(0z2233, b[2 : 3])
+ call assert_equal(0z223344, b[2 : -1])
+ call assert_equal(0z00, b[0 : -5])
+ call assert_equal(0z, b[0 : -11])
+ call assert_equal(0z44, b[-1 :])
+ call assert_equal(0z0011223344, b[:])
+ call assert_equal(0z0011223344, b[: -1])
+ call assert_equal(0z, b[5 : 6])
+ call assert_equal(0z0011, b[-10 : 1])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " legacy script white space
+ let b = 0z0011223344
+ call assert_equal(0z2233, b[2:3])
+endfunc
+
+func Test_blob_get()
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ call assert_equal(0x00, get(b, 0))
+ call assert_equal(0x22, get(b, 2, 999))
+ call assert_equal(0x44, get(b, 4))
+ call assert_equal(0x44, get(b, -1))
+ call assert_equal(-1, get(b, 5))
+ call assert_equal(999, get(b, 5, 999))
+ call assert_equal(-1, get(b, -8))
+ call assert_equal(999, get(b, -8, 999))
+ call assert_equal(10, get(v:_null_blob, 2, 10))
+
+ call assert_equal(0x00, b[0])
+ call assert_equal(0x22, b[2])
+ call assert_equal(0x44, b[4])
+ call assert_equal(0x44, b[-1])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ echo b[5]
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z0011223344
+ echo b[-8]
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+endfunc
+
+func Test_blob_to_string()
+ let lines =<< trim END
+ VAR b = 0z00112233445566778899aabbccdd
+ call assert_equal('0z00112233.44556677.8899AABB.CCDD', string(b))
+ call assert_equal(b, eval(string(b)))
+ call remove(b, 4, -1)
+ call assert_equal('0z00112233', string(b))
+ call remove(b, 0, 3)
+ call assert_equal('0z', string(b))
+ call assert_equal('0z', string(v:_null_blob))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_compare()
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR b3 = 0z001122
+ call assert_true(b1 == b1)
+ call assert_false(b1 == b2)
+ call assert_false(b1 == b3)
+ call assert_true(b1 != b2)
+ call assert_true(b1 != b3)
+ call assert_true(b1 == 0z0011)
+
+ call assert_false(b1 is b2)
+ LET b2 = b1
+ call assert_true(b1 == b2)
+ call assert_true(b1 is b2)
+ LET b2 = copy(b1)
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+ LET b2 = b1[:]
+ call assert_true(b1 == b2)
+ call assert_false(b1 is b2)
+ call assert_true(b1 isnot b2)
+ call assert_true(0z != 0z10)
+ call assert_true(0z10 != 0z)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ echo b1 == 9
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E977:', 'E1072', 'E1072'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ echo b1 != 9
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E977:', 'E1072', 'E1072'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 > b2
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E978:', 'E1072:', 'E1072:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 < b2
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E978:', 'E1072:', 'E1072:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 - b2
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 / b2
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+
+ let lines =<< trim END
+ VAR b1 = 0z0011
+ VAR b2 = 0z1100
+ VAR x = b1 * b2
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E974:', 'E1036:', 'E974:'])
+endfunc
+
+func Test_blob_index_assign()
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[1] = 0x11
+ LET b[2] = 0x22
+ LET b[0] = 0x33
+ call assert_equal(0z331122, b)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[2] = 0x33
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00
+ LET b[-2] = 0x33
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00010203
+ LET b[0 : -1] = 0z33
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0z00010203
+ LET b[3 : 4] = 0z3344
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+endfunc
+
+func Test_blob_for_loop()
+ let lines =<< trim END
+ VAR blob = 0z00010203
+ VAR i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ LET i += 1
+ endfor
+ call assert_equal(4, i)
+
+ LET blob = 0z00
+ call remove(blob, 0)
+ call assert_equal(0, len(blob))
+ for byte in blob
+ call assert_report('loop over empty blob')
+ endfor
+
+ LET blob = 0z0001020304
+ LET i = 0
+ for byte in blob
+ call assert_equal(i, byte)
+ if i == 1
+ call remove(blob, 0)
+ elseif i == 3
+ call remove(blob, 3)
+ endif
+ LET i += 1
+ endfor
+ call assert_equal(5, i)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_concatenate()
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += 0z2233
+ call assert_equal(0z00112233, b)
+
+ LET b = 0zDEAD + 0zBEEF
+ call assert_equal(0zDEADBEEF, b)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += "a"
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ LET b += 88
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E734:', 'E1012:', 'E734:'])
+endfunc
+
+func Test_blob_add()
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add(b, 0x22)
+ call assert_equal(0z001122, b)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " Only works in legacy script
+ let b = 0z0011
+ call add(b, '51')
+ call assert_equal(0z001133, b)
+ call assert_equal(1, add(v:_null_blob, 0x22))
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add(b, [9])
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E745:', 'E1012:', 'E745:'])
+
+ let lines =<< trim END
+ VAR b = 0z0011
+ call add("", 0x01)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E897:')
+
+ let lines =<< trim END
+ add(v:_null_blob, 0x22)
+ END
+ call CheckDefExecAndScriptFailure(lines, 'E1131:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call add(b, 0)
+ unlockvar b
+ END
+ call CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_empty()
+ call assert_false(empty(0z001122))
+ call assert_true(empty(0z))
+ call assert_true(empty(v:_null_blob))
+endfunc
+
+" Test removing items in blob
+func Test_blob_func_remove()
+ let lines =<< trim END
+ #" Test removing 1 element
+ VAR b = 0zDEADBEEF
+ call assert_equal(0xDE, remove(b, 0))
+ call assert_equal(0zADBEEF, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0xEF, remove(b, -1))
+ call assert_equal(0zDEADBE, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0xAD, remove(b, 1))
+ call assert_equal(0zDEBEEF, b)
+
+ #" Test removing range of element(s)
+ LET b = 0zDEADBEEF
+ call assert_equal(0zBE, remove(b, 2, 2))
+ call assert_equal(0zDEADEF, b)
+
+ LET b = 0zDEADBEEF
+ call assert_equal(0zADBE, remove(b, 1, 2))
+ call assert_equal(0zDEEF, b)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " Test invalid cases
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 5)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 1, 5)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, -10)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 3, 2)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(1, 0)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E896:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, b)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E974:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(b, 1, [])
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E745:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call remove(v:_null_blob, 1, 2)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E979:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call remove(b, 0)
+ unlockvar b
+ END
+ call CheckScriptFailure(lines, 'E741:')
+
+ " can only check at script level, not in a :def function
+ let lines =<< trim END
+ vim9script
+ var b = 0zDEADBEEF
+ lockvar b
+ remove(b, 0)
+ END
+ call CheckScriptFailure(lines, 'E741:')
+
+ call assert_fails('echo remove(0z1020, [])', 'E745:')
+ call assert_fails('echo remove(0z1020, 0, [])', 'E745:')
+endfunc
+
+func Test_blob_read_write()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call writefile(b, 'Xblob')
+ VAR br = readfile('Xblob', 'B')
+ call assert_equal(b, br)
+ VAR br2 = readblob('Xblob')
+ call assert_equal(b, br2)
+ VAR br3 = readblob('Xblob', 1)
+ call assert_equal(b[1 :], br3)
+ VAR br4 = readblob('Xblob', 1, 2)
+ call assert_equal(b[1 : 2], br4)
+ VAR br5 = readblob('Xblob', -3)
+ call assert_equal(b[-3 :], br5)
+ VAR br6 = readblob('Xblob', -3, 2)
+ call assert_equal(b[-3 : -2], br6)
+
+ #" reading past end of file, empty result
+ VAR br1e = readblob('Xblob', 10000)
+ call assert_equal(0z, br1e)
+
+ #" reading too much, result is truncated
+ VAR blong = readblob('Xblob', -1000)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', -10, 8)
+ call assert_equal(b, blong)
+ LET blong = readblob('Xblob', 0, 10)
+ call assert_equal(b, blong)
+
+ call delete('Xblob')
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ if filereadable('/dev/random')
+ let b = readblob('/dev/random', 0, 10)
+ call assert_equal(10, len(b))
+ endif
+
+ call assert_fails("call readblob('notexist')", 'E484:')
+ " TODO: How do we test for the E485 error?
+
+ " This was crashing when calling readfile() with a directory.
+ call assert_fails("call readfile('.', 'B')", 'E17: "." is a directory')
+endfunc
+
+" filter() item in blob
+func Test_blob_filter()
+ let lines =<< trim END
+ call assert_equal(v:_null_blob, filter(v:_null_blob, '0'))
+ call assert_equal(0z, filter(0zDEADBEEF, '0'))
+ call assert_equal(0zADBEEF, filter(0zDEADBEEF, 'v:val != 0xDE'))
+ call assert_equal(0zDEADEF, filter(0zDEADBEEF, 'v:val != 0xBE'))
+ call assert_equal(0zDEADBE, filter(0zDEADBEEF, 'v:val != 0xEF'))
+ call assert_equal(0zDEADBEEF, filter(0zDEADBEEF, '1'))
+ call assert_equal(0z01030103, filter(0z010203010203, 'v:val != 0x02'))
+ call assert_equal(0zADEF, filter(0zDEADBEEF, 'v:key % 2'))
+ END
+ call CheckLegacyAndVim9Success(lines)
+ call assert_fails('echo filter(0z10, "a10")', 'E121:')
+endfunc
+
+" map() item in blob
+func Test_blob_map()
+ let lines =<< trim END
+ call assert_equal(0zDFAEBFF0, map(0zDEADBEEF, 'v:val + 1'))
+ call assert_equal(0z00010203, map(0zDEADBEEF, 'v:key'))
+ call assert_equal(0zDEAEC0F2, map(0zDEADBEEF, 'v:key + v:val'))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ call map(0z00, '[9]')
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E978:')
+ call assert_fails('echo map(0z10, "a10")', 'E121:')
+endfunc
+
+func Test_blob_index()
+ let lines =<< trim END
+ call assert_equal(2, index(0zDEADBEEF, 0xBE))
+ call assert_equal(-1, index(0zDEADBEEF, 0))
+ call assert_equal(2, index(0z11111111, 0x11, 2))
+ call assert_equal(3, 0z11110111->index(0x11, 2))
+ call assert_equal(2, index(0z11111111, 0x11, -2))
+ call assert_equal(3, index(0z11110111, 0x11, -2))
+ call assert_equal(0, index(0z11110111, 0x11, -10))
+ call assert_equal(-1, index(v:_null_blob, 1))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ echo index(0z11110111, 0x11, [])
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E745:')
+
+ let lines =<< trim END
+ call index("asdf", 0)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E897:')
+endfunc
+
+func Test_blob_insert()
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0x33)
+ call assert_equal(0z33DEADBEEF, b)
+
+ LET b = 0zDEADBEEF
+ call insert(b, 0x33, 2)
+ call assert_equal(0zDEAD33BEEF, b)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " only works in legacy script
+ call assert_equal(0, insert(v:_null_blob, 0x33))
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, -1)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 257)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, [9])
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E745:', 'E1013:', 'E745:'])
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, -20)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, 0, 20)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E475:')
+
+ let lines =<< trim END
+ VAR b = 0zDEADBEEF
+ call insert(b, [])
+ END
+ call CheckLegacyAndVim9Failure(lines, ['E745:', 'E1013:', 'E745:'])
+
+ let lines =<< trim END
+ insert(v:_null_blob, 0x33)
+ END
+ call CheckDefExecAndScriptFailure(lines, 'E1131:')
+
+ let lines =<< trim END
+ let b = 0zDEADBEEF
+ lockvar b
+ call insert(b, 3)
+ unlockvar b
+ END
+ call CheckScriptFailure(lines, 'E741:')
+
+ let lines =<< trim END
+ vim9script
+ var b = 0zDEADBEEF
+ lockvar b
+ insert(b, 3)
+ END
+ call CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_reverse()
+ let lines =<< trim END
+ call assert_equal(0zEFBEADDE, reverse(0zDEADBEEF))
+ call assert_equal(0zBEADDE, reverse(0zDEADBE))
+ call assert_equal(0zADDE, reverse(0zDEAD))
+ call assert_equal(0zDE, reverse(0zDE))
+ call assert_equal(0z, reverse(v:_null_blob))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_json_encode()
+ let lines =<< trim END
+ #" call assert_equal('[222,173,190,239]', json_encode(0zDEADBEEF))
+ call assert_equal('[222, 173, 190, 239]', json_encode(0zDEADBEEF))
+ call assert_equal('[]', json_encode(0z))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_blob_lock()
+ let lines =<< trim END
+ let b = 0z112233
+ lockvar b
+ unlockvar b
+ let b = 0z44
+ END
+ call CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ var b = 0z112233
+ lockvar b
+ unlockvar b
+ b = 0z44
+ END
+ call CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ let b = 0z112233
+ lockvar b
+ let b = 0z44
+ END
+ call CheckScriptFailure(lines, 'E741:')
+
+ let lines =<< trim END
+ vim9script
+ var b = 0z112233
+ lockvar b
+ b = 0z44
+ END
+ call CheckScriptFailure(lines, 'E741:')
+endfunc
+
+func Test_blob_sort()
+ if has('float')
+ call CheckLegacyAndVim9Failure(['call sort([1.0, 0z11], "f")'], 'E975:')
+ endif
+ call CheckLegacyAndVim9Failure(['call sort([11, 0z11], "N")'], 'E974:')
+endfunc
+
+" Tests for the blob2list() function
+func Test_blob2list()
+ call assert_fails('let v = blob2list(10)', 'E1238: Blob required for argument 1')
+ eval 0zFFFF->blob2list()->assert_equal([255, 255])
+ let tests = [[0z0102, [1, 2]],
+ \ [0z00, [0]],
+ \ [0z, []],
+ \ [0z00000000, [0, 0, 0, 0]],
+ \ [0zAABB.CCDD, [170, 187, 204, 221]]]
+ for t in tests
+ call assert_equal(t[0]->blob2list(), t[1])
+ endfor
+ exe 'let v = 0z' .. repeat('000102030405060708090A0B0C0D0E0F', 64)
+ call assert_equal(1024, blob2list(v)->len())
+ call assert_equal([4, 8, 15], [v[100], v[1000], v[1023]])
+ call assert_equal([], blob2list(v:_null_blob))
+endfunc
+
+" Tests for the list2blob() function
+func Test_list2blob()
+ call assert_fails('let b = list2blob(0z10)', 'E1211: List required for argument 1')
+ let tests = [[[1, 2], 0z0102],
+ \ [[0], 0z00],
+ \ [[], 0z],
+ \ [[0, 0, 0, 0], 0z00000000],
+ \ [[255, 255], 0zFFFF],
+ \ [[170, 187, 204, 221], 0zAABB.CCDD],
+ \ ]
+ for t in tests
+ call assert_equal(t[1], t[0]->list2blob())
+ endfor
+ call assert_fails('let b = list2blob([1, []])', 'E745:')
+ call assert_fails('let b = list2blob([-1])', 'E1239:')
+ call assert_fails('let b = list2blob([256])', 'E1239:')
+ let b = range(16)->repeat(64)->list2blob()
+ call assert_equal(1024, b->len())
+ call assert_equal([4, 8, 15], [b[100], b[1000], b[1023]])
+ call assert_equal(0z, list2blob(v:_null_list))
+endfunc
+
+" The following used to cause an out-of-bounds memory access
+func Test_blob2string()
+ let v = '0z' .. repeat('01010101.', 444)
+ let v ..= '01'
+ exe 'let b = ' .. v
+ call assert_equal(v, string(b))
+endfunc
+
+func Test_blob_repeat()
+ call assert_equal(0z, repeat(0z00, 0))
+ call assert_equal(0z00, repeat(0z00, 1))
+ call assert_equal(0z0000, repeat(0z00, 2))
+ call assert_equal(0z00000000, repeat(0z0000, 2))
+
+ call assert_equal(0z, repeat(0z12, 0))
+ call assert_equal(0z, repeat(0z1234, 0))
+ call assert_equal(0z1234, repeat(0z1234, 1))
+ call assert_equal(0z12341234, repeat(0z1234, 2))
+endfunc
+
+" Test for blob allocation failure
+func Test_blob_alloc_failure()
+ CheckFunction test_alloc_fail
+ " blob variable
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let v = 0z10', 'E342:')
+
+ " blob slice
+ let v = 0z1020
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = v[0:0]', 'E342:')
+ call assert_equal(0z1020, x)
+
+ " blob remove()
+ let v = 0z10203040
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = remove(v, 1, 2)', 'E342:')
+ call assert_equal(0, x)
+
+ " list2blob()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let a = list2blob([1, 2, 4])', 'E342:')
+ call assert_equal(0, a)
+
+ " mapnew()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = mapnew(0z1234, {_, v -> 1})', 'E342:')
+ call assert_equal(0, x)
+
+ " copy()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = copy(v)', 'E342:')
+ call assert_equal(0z, x)
+
+ " readblob()
+ call test_alloc_fail(GetAllocId('blob_alloc'), 0, 0)
+ call assert_fails('let x = readblob("test_blob.vim")', 'E342:')
+ call assert_equal(0, x)
+endfunc
+
+" Test for the indexof() function
+func Test_indexof()
+ let b = 0zdeadbeef
+ call assert_equal(0, indexof(b, {i, v -> v == 0xde}))
+ call assert_equal(3, indexof(b, {i, v -> v == 0xef}))
+ call assert_equal(-1, indexof(b, {i, v -> v == 0x1}))
+ call assert_equal(1, indexof(b, "v:val == 0xad"))
+ call assert_equal(-1, indexof(b, "v:val == 0xff"))
+ call assert_equal(-1, indexof(b, {_, v -> "v == 0xad"}))
+
+ call assert_equal(-1, indexof(0z, "v:val == 0x0"))
+ call assert_equal(-1, indexof(v:_null_blob, "v:val == 0xde"))
+ call assert_equal(-1, indexof(b, v:_null_string))
+ " Nvim doesn't have null functions
+ " call assert_equal(-1, indexof(b, test_null_function()))
+
+ let b = 0z01020102
+ call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0}))
+ call assert_equal(2, indexof(b, "v:val == 0x01", #{startidx: -2}))
+ call assert_equal(-1, indexof(b, "v:val == 0x01", #{startidx: 5}))
+ call assert_equal(0, indexof(b, "v:val == 0x01", #{startidx: -5}))
+ call assert_equal(0, indexof(b, "v:val == 0x01", v:_null_dict))
+
+ " failure cases
+ call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:')
+ call assert_fails('let i = indexof(b, {})', 'E1256:')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_blockedit.vim b/test/old/testdir/test_blockedit.vim
index 7b56b1554f..e0cfe11af0 100644
--- a/src/nvim/testdir/test_blockedit.vim
+++ b/test/old/testdir/test_blockedit.vim
@@ -18,6 +18,7 @@ endfunc
func Test_blockinsert_autoindent()
new
let lines =<< trim END
+ vim9script
var d = {
a: () => 0,
b: () => 0,
@@ -28,40 +29,42 @@ func Test_blockinsert_autoindent()
filetype plugin indent on
setlocal sw=2 et ft=vim
setlocal indentkeys+=:
- exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>"
+ exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>"
let expected =<< trim END
+ vim9script
var d = {
a: (): asdf => 0,
b: (): asdf => 0,
c: (): asdf => 0,
}
END
- call assert_equal(expected, getline(1, 5))
+ call assert_equal(expected, getline(1, 6))
" insert on the next column should do exactly the same
:%dele
call setline(1, lines)
- exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>"
- call assert_equal(expected, getline(1, 5))
+ exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>"
+ call assert_equal(expected, getline(1, 6))
:%dele
call setline(1, lines)
setlocal sw=8 noet
- exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>"
+ exe "norm! 3Gf)\<c-v>2jA: asdf\<esc>"
let expected =<< trim END
+ vim9script
var d = {
a: (): asdf => 0,
b: (): asdf => 0,
c: (): asdf => 0,
}
END
- call assert_equal(expected, getline(1, 5))
+ call assert_equal(expected, getline(1, 6))
" insert on the next column should do exactly the same
:%dele
call setline(1, lines)
- exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>"
- call assert_equal(expected, getline(1, 5))
+ exe "norm! 3Gf)l\<c-v>2jI: asdf\<esc>"
+ call assert_equal(expected, getline(1, 6))
filetype off
bwipe!
diff --git a/src/nvim/testdir/test_breakindent.vim b/test/old/testdir/test_breakindent.vim
index 2e377aa434..301e2d0e40 100644
--- a/src/nvim/testdir/test_breakindent.vim
+++ b/test/old/testdir/test_breakindent.vim
@@ -10,7 +10,9 @@ CheckOption breakindent
source view_util.vim
source screendump.vim
-let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
+func SetUp()
+ let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
+endfunc
func s:screen_lines(lnum, width) abort
return ScreenLines([a:lnum, a:lnum + 2], a:width)
@@ -85,7 +87,7 @@ func Test_breakindent02_vartabs()
endif
" simple breakindent test with showbreak set
call s:test_windows('setl briopt=min:0 sbr=>> vts=4')
- let lines = s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'), 8)
let expect = [
\ " abcd",
\ " >>qr",
@@ -98,7 +100,7 @@ endfunc
func Test_breakindent03()
" simple breakindent test with showbreak set and briopt including sbr
call s:test_windows('setl briopt=sbr,min:0 sbr=++')
- let lines = s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'), 8)
let expect=[
\ " abcd",
\ "++ qrst",
@@ -115,7 +117,7 @@ func Test_breakindent03_vartabs()
return
endif
call s:test_windows('setl briopt=sbr,min:0 sbr=++ vts=4')
- let lines = s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'), 8)
let expect = [
\ " abcd",
\ "++ qrst",
@@ -130,7 +132,7 @@ func Test_breakindent04()
" breakindent set with min width 18
set sbr=<<<
call s:test_windows('setl sbr=NONE briopt=min:18')
- let lines = s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'), 8)
let expect = [
\ " abcd",
\ " qrstuv",
@@ -148,7 +150,7 @@ func Test_breakindent04_vartabs()
return
endif
call s:test_windows('setl sbr= briopt=min:18 vts=4')
- let lines = s:screen_lines(line('.'),8)
+ let lines = s:screen_lines(line('.'), 8)
let expect = [
\ " abcd",
\ " qrstuv",
@@ -581,7 +583,7 @@ func Test_breakindent16()
redraw!
let lines = s:screen_lines(1,10)
let expect = [
- \ " 789012",
+ \ "<<< 789012",
\ " 345678",
\ " 901234",
\ ]
@@ -609,7 +611,7 @@ func Test_breakindent16_vartabs()
redraw!
let lines = s:screen_lines(1,10)
let expect = [
- \ " 789012",
+ \ "<<< 789012",
\ " 345678",
\ " 901234",
\ ]
@@ -709,25 +711,25 @@ endfunc
func Test_breakindent20_cpo_n_nextpage()
let s:input = ""
call s:test_windows('setl breakindent briopt=min:14 cpo+=n number')
- call setline(1, repeat('a', 200))
+ call setline(1, repeat('abcdefghijklmnopqrst', 10))
norm! 1gg
redraw!
let lines = s:screen_lines(1, 20)
let expect = [
- \ " 1 aaaaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaaaa",
+ \ " 1 abcdefghijklmnop",
+ \ " qrstabcdefghijkl",
+ \ " mnopqrstabcdefgh",
\ ]
call s:compare_lines(expect, lines)
" Scroll down one screen line
setl scrolloff=5
- norm! 5gj
+ norm! 6gj
redraw!
let lines = s:screen_lines(1, 20)
let expect = [
- \ "--1 aaaaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaaaa",
+ \ "<<< qrstabcdefghijkl",
+ \ " mnopqrstabcdefgh",
+ \ " ijklmnopqrstabcd",
\ ]
call s:compare_lines(expect, lines)
@@ -735,18 +737,18 @@ func Test_breakindent20_cpo_n_nextpage()
norm! 1gg
let lines = s:screen_lines(1, 20)
let expect = [
- \ " 1 aaaaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaa",
+ \ " 1 abcdefghijklmnop",
+ \ " qrstabcdefghij",
+ \ " klmnopqrstabcd",
\ ]
call s:compare_lines(expect, lines)
" Scroll down one screen line
- norm! 5gj
+ norm! 6gj
let lines = s:screen_lines(1, 20)
let expect = [
- \ "--1 aaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaa",
- \ " aaaaaaaaaaaaaa",
+ \ "<<< qrstabcdefghij",
+ \ " klmnopqrstabcd",
+ \ " efghijklmnopqr",
\ ]
call s:compare_lines(expect, lines)
@@ -837,12 +839,12 @@ func Test_breakindent20_list()
call s:compare_lines(expect, lines)
" check formatlistpat indent with different list levels
let &l:flp = '^\s*\*\+\s\+'
- redraw!
%delete _
call setline(1, ['* Congress shall make no law',
\ '*** Congress shall make no law',
\ '**** Congress shall make no law'])
norm! 1gg
+ redraw!
let expect = [
\ "* Congress shall ",
\ " make no law ",
@@ -935,19 +937,30 @@ func Test_cursor_position_with_showbreak()
let lines =<< trim END
vim9script
&signcolumn = 'yes'
- &showbreak = '+ '
+ &showbreak = '++'
+ &breakindentopt = 'shift:2'
var leftcol: number = win_getid()->getwininfo()->get(0, {})->get('textoff')
repeat('x', &columns - leftcol - 1)->setline(1)
'second line'->setline(2)
END
- call writefile(lines, 'XscriptShowbreak')
+ call writefile(lines, 'XscriptShowbreak', 'D')
let buf = RunVimInTerminal('-S XscriptShowbreak', #{rows: 6})
call term_sendkeys(buf, "AX")
- call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak', {})
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak_1', {})
+ " No line wraps, so changing 'showbreak' should lead to the same screen.
+ call term_sendkeys(buf, "\<C-\>\<C-O>:setlocal showbreak=+\<CR>")
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak_1', {})
+ " No line wraps, so setting 'breakindent' should lead to the same screen.
+ call term_sendkeys(buf, "\<C-\>\<C-O>:setlocal breakindent\<CR>")
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak_1', {})
+ " The first line now wraps because of "eol" in 'listchars'.
+ call term_sendkeys(buf, "\<C-\>\<C-O>:setlocal list\<CR>")
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak_2', {})
+ call term_sendkeys(buf, "\<C-\>\<C-O>:setlocal nobreakindent\<CR>")
+ call VerifyScreenDump(buf, 'Test_cursor_position_with_showbreak_3', {})
call StopVimInTerminal(buf)
- call delete('XscriptShowbreak')
endfunc
func Test_no_spurious_match()
@@ -956,9 +969,9 @@ func Test_no_spurious_match()
let @/ = '\%>3v[y]'
redraw!
call searchcount().total->assert_equal(1)
+
" cleanup
set hls&vim
- let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
bwipeout!
endfunc
@@ -1023,8 +1036,6 @@ func Test_no_extra_indent()
endfunc
func Test_breakindent_column()
- " restore original
- let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP"
call s:test_windows('setl breakindent breakindentopt=column:10')
redraw!
" 1) default: does not indent, too wide :(
@@ -1075,4 +1086,22 @@ func Test_breakindent_column()
bwipeout!
endfunc
+func Test_linebreak_list()
+ " This was setting wlv.c_extra to NUL while wlv.p_extra is NULL
+ filetype plugin on
+ syntax enable
+ edit! $VIMRUNTIME/doc/index.txt
+ /v_P
+
+ setlocal list
+ setlocal listchars=tab:>-
+ setlocal linebreak
+ setlocal nowrap
+ setlocal filetype=help
+ redraw!
+
+ bwipe!
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_buffer.vim b/test/old/testdir/test_buffer.vim
index 9220cc17b9..b0b9f4f24b 100644
--- a/src/nvim/testdir/test_buffer.vim
+++ b/test/old/testdir/test_buffer.vim
@@ -5,64 +5,60 @@ source check.vim
" Test for the :bunload command with an offset
func Test_bunload_with_offset()
%bwipe!
- call writefile(['B1'], 'b1')
- call writefile(['B2'], 'b2')
- call writefile(['B3'], 'b3')
- call writefile(['B4'], 'b4')
+ call writefile(['B1'], 'Xb1', 'D')
+ call writefile(['B2'], 'Xb2', 'D')
+ call writefile(['B3'], 'Xb3', 'D')
+ call writefile(['B4'], 'Xb4', 'D')
" Load four buffers. Unload the second and third buffers and then
" execute .+3bunload to unload the last buffer.
- edit b1
- new b2
- new b3
- new b4
-
- bunload b2
- bunload b3
- exe bufwinnr('b1') . 'wincmd w'
+ edit Xb1
+ new Xb2
+ new Xb3
+ new Xb4
+
+ bunload Xb2
+ bunload Xb3
+ exe bufwinnr('Xb1') . 'wincmd w'
.+3bunload
- call assert_equal(0, getbufinfo('b4')[0].loaded)
- call assert_equal('b1',
+ call assert_equal(0, getbufinfo('Xb4')[0].loaded)
+ call assert_equal('Xb1',
\ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
" Load four buffers. Unload the third and fourth buffers. Execute .+3bunload
" and check whether the second buffer is unloaded.
ball
- bunload b3
- bunload b4
- exe bufwinnr('b1') . 'wincmd w'
+ bunload Xb3
+ bunload Xb4
+ exe bufwinnr('Xb1') . 'wincmd w'
.+3bunload
- call assert_equal(0, getbufinfo('b2')[0].loaded)
- call assert_equal('b1',
+ call assert_equal(0, getbufinfo('Xb2')[0].loaded)
+ call assert_equal('Xb1',
\ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
" Load four buffers. Unload the second and third buffers and from the last
" buffer execute .-3bunload to unload the first buffer.
ball
- bunload b2
- bunload b3
- exe bufwinnr('b4') . 'wincmd w'
+ bunload Xb2
+ bunload Xb3
+ exe bufwinnr('Xb4') . 'wincmd w'
.-3bunload
- call assert_equal(0, getbufinfo('b1')[0].loaded)
- call assert_equal('b4',
+ call assert_equal(0, getbufinfo('Xb1')[0].loaded)
+ call assert_equal('Xb4',
\ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
" Load four buffers. Unload the first and second buffers. Execute .-3bunload
" from the last buffer and check whether the third buffer is unloaded.
ball
- bunload b1
- bunload b2
- exe bufwinnr('b4') . 'wincmd w'
+ bunload Xb1
+ bunload Xb2
+ exe bufwinnr('Xb4') . 'wincmd w'
.-3bunload
- call assert_equal(0, getbufinfo('b3')[0].loaded)
- call assert_equal('b4',
+ call assert_equal(0, getbufinfo('Xb3')[0].loaded)
+ call assert_equal('Xb4',
\ fnamemodify(getbufinfo({'bufloaded' : 1})[0].name, ':t'))
%bwipe!
- call delete('b1')
- call delete('b2')
- call delete('b3')
- call delete('b4')
call assert_fails('1,4bunload', 'E16:')
call assert_fails(',100bunload', 'E16:')
@@ -76,14 +72,14 @@ func Test_buflist_browse()
%bwipe!
call assert_fails('buffer 1000', 'E86:')
- call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xfile1')
- call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xfile2')
- call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xfile3')
- edit Xfile1
+ call writefile(['foo1', 'foo2', 'foo3', 'foo4'], 'Xbrowse1', 'D')
+ call writefile(['bar1', 'bar2', 'bar3', 'bar4'], 'Xbrowse2', 'D')
+ call writefile(['baz1', 'baz2', 'baz3', 'baz4'], 'Xbrowse3', 'D')
+ edit Xbrowse1
let b1 = bufnr()
- edit Xfile2
+ edit Xbrowse2
let b2 = bufnr()
- edit +/baz4 Xfile3
+ edit +/baz4 Xbrowse3
let b3 = bufnr()
call assert_fails('buffer ' .. b1 .. ' abc', 'E488:')
@@ -127,9 +123,6 @@ func Test_buflist_browse()
call assert_fails('sandbox bnext', 'E48:')
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
%bwipe!
endfunc
@@ -141,7 +134,7 @@ func Test_bdelete_cmd()
call assert_fails('bdelete \)', 'E55:')
" Deleting a unlisted and unloaded buffer
- edit Xfile1
+ edit Xbdelfile1
let bnr = bufnr()
set nobuflisted
enew
@@ -200,39 +193,37 @@ endfunc
" Test for quitting the 'swapfile exists' dialog with the split buffer
" command.
func Test_buffer_sbuf_cleanup()
- call writefile([], 'Xfile')
+ call writefile([], 'XsplitCleanup', 'D')
" first open the file in a buffer
- new Xfile
+ new XsplitCleanup
let bnr = bufnr()
close
" create the swap file
- call writefile([], '.Xfile.swp')
+ call writefile([], '.XsplitCleanup.swp', 'D')
" Remove the catch-all that runtest.vim adds
au! SwapExists
augroup BufTest
au!
- autocmd SwapExists Xfile let v:swapchoice='q'
+ autocmd SwapExists XsplitCleanup let v:swapchoice='q'
augroup END
exe 'sbuf ' . bnr
call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
" test for :sball
sball
call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
%bw!
set shortmess+=F
let v:statusmsg = ''
- edit Xfile
+ edit XsplitCleanup
call assert_equal('', v:statusmsg)
call assert_equal(1, winnr('$'))
- call assert_equal(0, getbufinfo('Xfile')[0].loaded)
+ call assert_equal(0, getbufinfo('XsplitCleanup')[0].loaded)
set shortmess&
- call delete('Xfile')
- call delete('.Xfile.swp')
augroup BufTest
au!
augroup END
@@ -265,35 +256,35 @@ func Test_goto_buf_with_confirm()
CheckUnix
CheckNotGui
CheckFeature dialog_con
- new Xfile
+ new XgotoConf
enew
call setline(1, 'test')
- call assert_fails('b Xfile', 'E37:')
+ call assert_fails('b XgotoConf', 'E37:')
call feedkeys('c', 'L')
- call assert_fails('confirm b Xfile', 'E37:')
+ call assert_fails('confirm b XgotoConf', 'E37:')
call assert_equal(1, &modified)
call assert_equal('', @%)
call feedkeys('y', 'L')
- call assert_fails('confirm b Xfile', ['', 'E37:'])
+ call assert_fails('confirm b XgotoConf', ['', 'E37:'])
call assert_equal(1, &modified)
call assert_equal('', @%)
call feedkeys('n', 'L')
- confirm b Xfile
- call assert_equal('Xfile', @%)
+ confirm b XgotoConf
+ call assert_equal('XgotoConf', @%)
close!
endfunc
" Test for splitting buffer with 'switchbuf'
func Test_buffer_switchbuf()
- new Xfile
+ new Xswitchbuf
wincmd w
set switchbuf=useopen
- sbuf Xfile
+ sbuf Xswitchbuf
call assert_equal(1, winnr())
call assert_equal(2, winnr('$'))
set switchbuf=usetab
tabnew
- sbuf Xfile
+ sbuf Xswitchbuf
call assert_equal(1, tabpagenr())
call assert_equal(2, tabpagenr('$'))
set switchbuf&
@@ -305,11 +296,11 @@ func Test_bufadd_autocmd_bwipe()
%bw!
augroup BufAdd_Wipe
au!
- autocmd BufAdd Xfile %bw!
+ autocmd BufAdd Xbwipe %bw!
augroup END
- edit Xfile
+ edit Xbwipe
call assert_equal('', @%)
- call assert_equal(0, bufexists('Xfile'))
+ call assert_equal(0, bufexists('Xbwipe'))
augroup BufAdd_Wipe
au!
augroup END
@@ -319,8 +310,8 @@ endfunc
" Test for trying to load a buffer with text locked
" <C-\>e in the command line is used to lock the text
func Test_load_buf_with_text_locked()
- new Xfile1
- edit Xfile2
+ new Xlockfile1
+ edit Xlockfile2
let cmd = ":\<C-\>eexecute(\"normal \<C-O>\")\<CR>\<C-C>"
call assert_fails("call feedkeys(cmd, 'xt')", 'E565:')
%bw!
@@ -329,40 +320,38 @@ endfunc
" Test for using CTRL-^ to edit the alternative file keeping the cursor
" position with 'nostartofline'. Also test using the 'buf' command.
func Test_buffer_edit_altfile()
- call writefile(repeat(['one two'], 50), 'Xfile1')
- call writefile(repeat(['five six'], 50), 'Xfile2')
+ call writefile(repeat(['one two'], 50), 'Xaltfile1', 'D')
+ call writefile(repeat(['five six'], 50), 'Xaltfile2', 'D')
set nosol
- edit Xfile1
+ edit Xaltfile1
call cursor(25, 5)
- edit Xfile2
+ edit Xaltfile2
call cursor(30, 4)
exe "normal \<C-^>"
call assert_equal([0, 25, 5, 0], getpos('.'))
exe "normal \<C-^>"
call assert_equal([0, 30, 4, 0], getpos('.'))
- buf Xfile1
+ buf Xaltfile1
call assert_equal([0, 25, 5, 0], getpos('.'))
- buf Xfile2
+ buf Xaltfile2
call assert_equal([0, 30, 4, 0], getpos('.'))
set sol&
- call delete('Xfile1')
- call delete('Xfile2')
endfunc
" Test for running the :sball command with a maximum window count and a
" modified buffer
func Test_sball_with_count()
%bw!
- edit Xfile1
+ edit Xcountfile1
call setline(1, ['abc'])
- new Xfile2
- new Xfile3
- new Xfile4
+ new Xcountfile2
+ new Xcountfile3
+ new Xcountfile4
2sball
- call assert_equal(bufnr('Xfile4'), winbufnr(1))
- call assert_equal(bufnr('Xfile1'), winbufnr(2))
- call assert_equal(0, getbufinfo('Xfile2')[0].loaded)
- call assert_equal(0, getbufinfo('Xfile3')[0].loaded)
+ call assert_equal(bufnr('Xcountfile4'), winbufnr(1))
+ call assert_equal(bufnr('Xcountfile1'), winbufnr(2))
+ call assert_equal(0, getbufinfo('Xcountfile2')[0].loaded)
+ call assert_equal(0, getbufinfo('Xcountfile3')[0].loaded)
%bw!
endfunc
@@ -390,6 +379,7 @@ endfunc
func Test_buffer_scheme()
CheckMSWindows
+ set noswapfile
set noshellslash
%bwipe!
let bufnames = [
@@ -412,6 +402,7 @@ func Test_buffer_scheme()
endfor
set shellslash&
+ set swapfile&
endfunc
" this was using a NULL pointer after failing to use the pattern
@@ -451,4 +442,74 @@ func Test_buffer_maxmem()
" set maxmem& maxmemtot&
endfunc
+" Test for buffer allocation failure
+func Test_buflist_alloc_failure()
+ CheckFunction test_alloc_fail
+ %bw!
+
+ edit XallocFail1
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('edit XallocFail2', 'E342:')
+
+ " test for bufadd()
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call bufadd("Xbuffer")', 'E342:')
+
+ " test for setting the arglist
+ edit XallocFail2
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('next XallocFail3', 'E342:')
+
+ " test for setting the alternate buffer name when writing a file
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('write Xother', 'E342:')
+ call delete('Xother')
+
+ " test for creating a buffer using bufnr()
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails("call bufnr('Xnewbuf', v:true)", 'E342:')
+
+ " test for renaming buffer using :file
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('file Xnewfile', 'E342:')
+
+ " test for creating a buffer for a popup window
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call popup_create("mypop", {})', 'E342:')
+
+ if has('terminal')
+ " test for creating a buffer for a terminal window
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('call term_start(&shell)', 'E342:')
+ %bw!
+ endif
+
+ " test for loading a new buffer after wiping out all the buffers
+ edit XallocFail4
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('%bw!', 'E342:')
+
+ " test for :checktime loading the buffer
+ call writefile(['one'], 'XallocFail5', 'D')
+ if has('unix')
+ edit XallocFail5
+ " sleep for some time to make sure the timestamp is different
+ sleep 200m
+ call writefile(['two'], 'XallocFail5')
+ set autoread
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('checktime', 'E342:')
+ set autoread&
+ bw!
+ endif
+
+ " test for :vimgrep loading a dummy buffer
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('vimgrep two XallocFail5', 'E342:')
+
+ " test for quickfix command loading a buffer
+ call test_alloc_fail(GetAllocId('newbuf_bvars'), 0, 0)
+ call assert_fails('cexpr "XallocFail6:10:Line10"', 'E342:')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_bufline.vim b/test/old/testdir/test_bufline.vim
index 0468025f55..29ec46a606 100644
--- a/src/nvim/testdir/test_bufline.vim
+++ b/test/old/testdir/test_bufline.vim
@@ -25,8 +25,8 @@ func Test_setbufline_getbufline()
call assert_equal(1, setbufline(b, 5, 'x'))
call assert_equal(1, setbufline(b, 5, ['x']))
- call assert_equal(1, setbufline(b, 5, []))
- call assert_equal(1, setbufline(b, 5, v:_null_list))
+ call assert_equal(0, setbufline(b, 5, []))
+ call assert_equal(0, setbufline(b, 5, v:_null_list))
call assert_equal(1, 'x'->setbufline(bufnr('$') + 1, 1))
call assert_equal(1, ['x']->setbufline(bufnr('$') + 1, 1))
@@ -86,11 +86,16 @@ func Test_setline_startup()
if cmd == ''
return
endif
- call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript')
+ call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript', 'D')
call system(cmd)
+ sleep 50m
call assert_equal(['Hello'], readfile('Xtest'))
- call delete('Xscript')
+ call assert_equal(0, setline(1, []))
+ call assert_equal(0, setline(1, v:_null_list))
+ call assert_equal(0, setline(5, []))
+ call assert_equal(0, setline(6, v:_null_list))
+
call delete('Xtest')
endfunc
@@ -130,8 +135,8 @@ func Test_appendbufline()
call assert_equal(1, appendbufline(b, 4, 'x'))
call assert_equal(1, appendbufline(b, 4, ['x']))
- call assert_equal(1, appendbufline(b, 4, []))
- call assert_equal(1, appendbufline(b, 4, v:_null_list))
+ call assert_equal(0, appendbufline(b, 4, []))
+ call assert_equal(0, appendbufline(b, 4, v:_null_list))
call assert_equal(1, appendbufline(1234, 1, 'x'))
call assert_equal(1, appendbufline(1234, 1, ['x']))
@@ -140,8 +145,8 @@ func Test_appendbufline()
call assert_equal(0, appendbufline(b, 1, []))
call assert_equal(0, appendbufline(b, 1, v:_null_list))
- call assert_equal(1, appendbufline(b, 3, []))
- call assert_equal(1, appendbufline(b, 3, v:_null_list))
+ call assert_equal(0, appendbufline(b, 3, []))
+ call assert_equal(0, appendbufline(b, 3, v:_null_list))
call assert_equal(['a', 'b', 'c'], getbufline(b, 1, '$'))
@@ -215,13 +220,11 @@ func Test_appendbufline_redraw()
call deletebufline(buf, 1, '$')
call appendbufline(buf, '$', 'Hello Vim world...')
END
- call writefile(lines, 'XscriptMatchCommon')
+ call writefile(lines, 'XscriptMatchCommon', 'D')
let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 10})
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_appendbufline_1', {})
call StopVimInTerminal(buf)
- call delete('XscriptMatchCommon')
endfunc
func Test_setbufline_select_mode()
diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/test/old/testdir/test_bufwintabinfo.vim
index 326aefb731..57492e07c9 100644
--- a/src/nvim/testdir/test_bufwintabinfo.vim
+++ b/test/old/testdir/test_bufwintabinfo.vim
@@ -1,6 +1,9 @@
" Tests for the getbufinfo(), getwininfo() and gettabinfo() functions
+source check.vim
+
+func Test_getbufwintabinfo()
+ CheckFeature quickfix
-function Test_getbufwintabinfo()
edit Xtestfile1
edit Xtestfile2
let buflist = getbufinfo()
@@ -42,6 +45,7 @@ function Test_getbufwintabinfo()
sign undefine Mark
enew!
endif
+ call assert_notequal([], getbufinfo(v:_null_dict))
only
let w1_id = win_getid()
@@ -108,7 +112,7 @@ function Test_getbufwintabinfo()
call assert_true(winlist[2].quickfix)
call assert_false(winlist[2].loclist)
wincmd t | only
-endfunction
+endfunc
function Test_get_buf_options()
let opts = bufnr()->getbufvar('&')
diff --git a/src/nvim/testdir/test_cd.vim b/test/old/testdir/test_cd.vim
index 2b37f2c7c0..cffba99451 100644
--- a/src/nvim/testdir/test_cd.vim
+++ b/test/old/testdir/test_cd.vim
@@ -106,6 +106,7 @@ func Test_chdir_func()
call assert_equal("", d)
" Should not crash
call chdir(d)
+ call assert_equal('', chdir([]))
only | tabonly
call chdir(topdir)
diff --git a/src/nvim/testdir/test_cdo.vim b/test/old/testdir/test_cdo.vim
index dbed7df4ac..dbed7df4ac 100644
--- a/src/nvim/testdir/test_cdo.vim
+++ b/test/old/testdir/test_cdo.vim
diff --git a/src/nvim/testdir/test_changedtick.vim b/test/old/testdir/test_changedtick.vim
index c789cdc1bc..c789cdc1bc 100644
--- a/src/nvim/testdir/test_changedtick.vim
+++ b/test/old/testdir/test_changedtick.vim
diff --git a/src/nvim/testdir/test_changelist.vim b/test/old/testdir/test_changelist.vim
index 3bb22a89b8..0d435590a9 100644
--- a/src/nvim/testdir/test_changelist.vim
+++ b/test/old/testdir/test_changelist.vim
@@ -37,13 +37,13 @@ endfunc
" Tests for the getchangelist() function
func Test_changelist_index()
- edit Xfile1.txt
+ edit Xgclfile1.txt
exe "normal iabc\<C-G>u\ndef\<C-G>u\nghi"
call assert_equal(3, getchangelist('%')[1])
" Move one step back in the changelist.
normal 2g;
- hide edit Xfile2.txt
+ hide edit Xgclfile2.txt
exe "normal iabcd\<C-G>u\ndefg\<C-G>u\nghij"
call assert_equal(3, getchangelist('%')[1])
" Move to the beginning of the changelist.
@@ -54,8 +54,8 @@ func Test_changelist_index()
call assert_equal(1, getchangelist('#')[1])
bwipe!
- call delete('Xfile1.txt')
- call delete('Xfile2.txt')
+ call delete('Xgclfile1.txt')
+ call delete('Xgclfile2.txt')
endfunc
func Test_getchangelist()
@@ -64,10 +64,10 @@ func Test_getchangelist()
call assert_equal([], 10->getchangelist())
call assert_equal([[], 0], getchangelist())
- call writefile(['line1', 'line2', 'line3'], 'Xfile1.txt')
- call writefile(['line1', 'line2', 'line3'], 'Xfile2.txt')
+ call writefile(['line1', 'line2', 'line3'], 'Xclistfile1.txt')
+ call writefile(['line1', 'line2', 'line3'], 'Xclistfile2.txt')
- edit Xfile1.txt
+ edit Xclistfile1.txt
let buf_1 = bufnr()
exe "normal 1Goline\<C-G>u1.1"
exe "normal 3Goline\<C-G>u2.1"
@@ -79,7 +79,7 @@ func Test_getchangelist()
\ {'lnum' : 6, 'col' : 4, 'coladd' : 0}], 2],
\ getchangelist('%'))
- hide edit Xfile2.txt
+ hide edit Xclistfile2.txt
let buf_2 = bufnr()
exe "normal 1GOline\<C-G>u1.0"
exe "normal 2Goline\<C-G>u2.0"
@@ -100,8 +100,8 @@ func Test_getchangelist()
\ getchangelist(buf_2))
bwipe!
- call delete('Xfile1.txt')
- call delete('Xfile2.txt')
+ call delete('Xclistfile1.txt')
+ call delete('Xclistfile2.txt')
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_charsearch.vim b/test/old/testdir/test_charsearch.vim
index 54e0a62ce5..084d4f0a22 100644
--- a/src/nvim/testdir/test_charsearch.vim
+++ b/test/old/testdir/test_charsearch.vim
@@ -38,8 +38,10 @@ func Test_charsearch()
" clear the character search
call setcharsearch({'char' : ''})
call assert_equal('', getcharsearch().char)
+ call assert_beeps('normal ;')
+ call assert_beeps('normal ,')
- call assert_fails("call setcharsearch([])", 'E715:')
+ call assert_fails("call setcharsearch([])", 'E1206:')
enew!
endfunc
@@ -86,6 +88,7 @@ endfunc
" Test for character search with 'hkmap'
func Test_charsearch_hkmap()
+ throw "Skipped: Nvim does not support 'hkmap'"
new
set hkmap
call setline(1, "ùðáâ÷ëòéïçìêöî")
diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/test/old/testdir/test_charsearch_utf8.vim
index 82a807ac5b..843edbb514 100644
--- a/src/nvim/testdir/test_charsearch_utf8.vim
+++ b/test/old/testdir/test_charsearch_utf8.vim
@@ -13,6 +13,13 @@ func Test_search_cmds()
call assert_equal([0, 1, 43, 0], getpos('.'))
normal! ,
call assert_equal([0, 1, 28, 0], getpos('.'))
+ call assert_equal('最', getcharsearch().char)
+ call setcharsearch({'char' : ''})
+ call assert_equal('', getcharsearch().char)
+ call assert_beeps('normal ;')
+ call assert_equal([0, 1, 28, 0], getpos('.'))
+ call assert_beeps('normal ,')
+ call assert_equal([0, 1, 28, 0], getpos('.'))
bw!
endfunc
diff --git a/src/nvim/testdir/test_checkpath.vim b/test/old/testdir/test_checkpath.vim
index f6a3bbce08..f6a3bbce08 100644
--- a/src/nvim/testdir/test_checkpath.vim
+++ b/test/old/testdir/test_checkpath.vim
diff --git a/src/nvim/testdir/test_cindent.vim b/test/old/testdir/test_cindent.vim
index ccc8168c09..d9702f57d2 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/test/old/testdir/test_cindent.vim
@@ -4392,6 +4392,18 @@ func Test_cindent_47()
inline/* test */namespace {
111111111111111111;
}
+ export namespace {
+ 111111111111111111;
+ }
+ export inline namespace {
+ 111111111111111111;
+ }
+ export/* test */inline namespace {
+ 111111111111111111;
+ }
+ inline export namespace {
+ 111111111111111111;
+ }
/* invalid namespaces use block indent */
namespace test test2 {
@@ -4495,6 +4507,18 @@ func Test_cindent_47()
inline/* test */namespace {
111111111111111111;
}
+ export namespace {
+ 111111111111111111;
+ }
+ export inline namespace {
+ 111111111111111111;
+ }
+ export/* test */inline namespace {
+ 111111111111111111;
+ }
+ inline export namespace {
+ 111111111111111111;
+ }
/* invalid namespaces use block indent */
namespace test test2 {
diff --git a/src/nvim/testdir/test_cjk_linebreak.vim b/test/old/testdir/test_cjk_linebreak.vim
index dfaa8fa1af..dfaa8fa1af 100644
--- a/src/nvim/testdir/test_cjk_linebreak.vim
+++ b/test/old/testdir/test_cjk_linebreak.vim
diff --git a/src/nvim/testdir/test_clientserver.vim b/test/old/testdir/test_clientserver.vim
index d8b29f6c42..1c4b4732fe 100644
--- a/src/nvim/testdir/test_clientserver.vim
+++ b/test/old/testdir/test_clientserver.vim
@@ -26,9 +26,10 @@ func Check_X11_Connection()
endfunc
func Test_client_server()
+ let g:test_is_flaky = 1
let cmd = GetVimCommand()
if cmd == ''
- return
+ throw 'GetVimCommand() failed'
endif
call Check_X11_Connection()
@@ -129,9 +130,9 @@ func Test_client_server()
" Run a separate instance to send a command to the server
call remote_expr(name, 'execute("only")')
- call system(cmd .. ' --remote-send ":new Xfile<CR>"')
+ call system(cmd .. ' --remote-send ":new Xclientfile<CR>"')
call assert_equal('2', remote_expr(name, 'winnr("$")'))
- call assert_equal('Xfile', remote_expr(name, 'winbufnr(1)->bufname()'))
+ call assert_equal('Xclientfile', remote_expr(name, 'winbufnr(1)->bufname()'))
call remote_expr(name, 'execute("only")')
" Invoke a remote-expr. On MS-Windows, the returned value has a carriage
@@ -140,24 +141,24 @@ func Test_client_server()
call assert_equal(['4'], split(l, "\n"))
" Edit multiple files using --remote
- call system(cmd .. ' --remote Xfile1 Xfile2 Xfile3')
- call assert_match(".*Xfile1\n.*Xfile2\n.*Xfile3\n", remote_expr(name, 'argv()'))
+ call system(cmd .. ' --remote Xclientfile1 Xclientfile2 Xclientfile3')
+ call assert_match(".*Xclientfile1\n.*Xclientfile2\n.*Xclientfile3\n", remote_expr(name, 'argv()'))
eval name->remote_send(":%bw!\<CR>")
" Edit files in separate tab pages
- call system(cmd .. ' --remote-tab Xfile1 Xfile2 Xfile3')
+ call system(cmd .. ' --remote-tab Xclientfile1 Xclientfile2 Xclientfile3')
call WaitForAssert({-> assert_equal('3', remote_expr(name, 'tabpagenr("$")'))})
- call assert_match('.*\<Xfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])'))
+ call assert_match('.*\<Xclientfile2', remote_expr(name, 'bufname(tabpagebuflist(2)[0])'))
eval name->remote_send(":%bw!\<CR>")
" Edit a file using --remote-wait
eval name->remote_send(":source $VIMRUNTIME/plugin/rrhelper.vim\<CR>")
- call system(cmd .. ' --remote-wait +enew Xfile1')
- call assert_match('.*\<Xfile1', remote_expr(name, 'bufname("#")'))
+ call system(cmd .. ' --remote-wait +enew Xclientfile1')
+ call assert_match('.*\<Xclientfile1', remote_expr(name, 'bufname("#")'))
eval name->remote_send(":%bw!\<CR>")
" Edit files using --remote-tab-wait
- call system(cmd .. ' --remote-tabwait +tabonly\|enew Xfile1 Xfile2')
+ call system(cmd .. ' --remote-tabwait +tabonly\|enew Xclientfile1 Xclientfile2')
call assert_equal('1', remote_expr(name, 'tabpagenr("$")'))
eval name->remote_send(":%bw!\<CR>")
diff --git a/src/nvim/testdir/test_close_count.vim b/test/old/testdir/test_close_count.vim
index 1f9adba32d..1f9adba32d 100644
--- a/src/nvim/testdir/test_close_count.vim
+++ b/test/old/testdir/test_close_count.vim
diff --git a/src/nvim/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim
index cf1d56ae38..3f13218c5b 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/test/old/testdir/test_cmdline.vim
@@ -82,38 +82,38 @@ func Test_complete_list()
endfunc
func Test_complete_wildmenu()
- call mkdir('Xdir1/Xdir2', 'p')
- call writefile(['testfile1'], 'Xdir1/Xtestfile1')
- call writefile(['testfile2'], 'Xdir1/Xtestfile2')
- call writefile(['testfile3'], 'Xdir1/Xdir2/Xtestfile3')
- call writefile(['testfile3'], 'Xdir1/Xdir2/Xtestfile4')
+ call mkdir('Xwilddir1/Xdir2', 'pR')
+ call writefile(['testfile1'], 'Xwilddir1/Xtestfile1')
+ call writefile(['testfile2'], 'Xwilddir1/Xtestfile2')
+ call writefile(['testfile3'], 'Xwilddir1/Xdir2/Xtestfile3')
+ call writefile(['testfile3'], 'Xwilddir1/Xdir2/Xtestfile4')
set wildmenu
" Pressing <Tab> completes, and moves to next files when pressing again.
- call feedkeys(":e Xdir1/\<Tab>\<Tab>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Tab>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Tab>\<Tab>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Tab>\<Tab>\<CR>", 'tx')
call assert_equal('testfile2', getline(1))
" <S-Tab> is like <Tab> but begin with the last match and then go to
" previous.
- call feedkeys(":e Xdir1/Xtest\<S-Tab>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/Xtest\<S-Tab>\<CR>", 'tx')
call assert_equal('testfile2', getline(1))
- call feedkeys(":e Xdir1/Xtest\<S-Tab>\<S-Tab>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/Xtest\<S-Tab>\<S-Tab>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
" <Left>/<Right> to move to previous/next file.
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<Right>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<Right>\<CR>", 'tx')
call assert_equal('testfile2', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Right>\<Right>\<Left>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Right>\<Right>\<Left>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
" <Up>/<Down> to go up/down directories.
- call feedkeys(":e Xdir1/\<Tab>\<Down>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Down>\<CR>", 'tx')
call assert_equal('testfile3', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<Down>\<Up>\<Right>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<Down>\<Up>\<Right>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
" this fails in some Unix GUIs, not sure why
@@ -123,9 +123,9 @@ func Test_complete_wildmenu()
set wildcharm=<C-Z>
cnoremap <C-J> <Down><C-Z>
cnoremap <C-K> <Up><C-Z>
- call feedkeys(":e Xdir1/\<Tab>\<C-J>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<C-J>\<CR>", 'tx')
call assert_equal('testfile3', getline(1))
- call feedkeys(":e Xdir1/\<Tab>\<C-J>\<C-K>\<CR>", 'tx')
+ call feedkeys(":e Xwilddir1/\<Tab>\<C-J>\<C-K>\<CR>", 'tx')
call assert_equal('testfile1', getline(1))
set wildcharm=0
cunmap <C-J>
@@ -134,21 +134,21 @@ func Test_complete_wildmenu()
" Test for canceling the wild menu by adding a character
redrawstatus
- call feedkeys(":e Xdir1/\<Tab>x\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xdir1/Xdir2/x', @:)
+ call feedkeys(":e Xwilddir1/\<Tab>x\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xwilddir1/Xdir2/x', @:)
" Completion using a relative path
- cd Xdir1/Xdir2
+ cd Xwilddir1/Xdir2
call feedkeys(":e ../\<Tab>\<Right>\<Down>\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"e Xtestfile3 Xtestfile4', @:)
cd -
" test for wildmenumode()
cnoremap <expr> <F2> wildmenumode()
- call feedkeys(":cd Xdir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"cd Xdir1/0', @:)
- call feedkeys(":e Xdir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"e Xdir1/Xdir2/1', @:)
+ call feedkeys(":cd Xwilddir\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"cd Xwilddir1/0', @:)
+ call feedkeys(":e Xwilddir1/\<Tab>\<F2>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"e Xwilddir1/Xdir2/1', @:)
cunmap <F2>
" Test for canceling the wild menu by pressing <PageDown> or <PageUp>.
@@ -159,9 +159,15 @@ func Test_complete_wildmenu()
call feedkeys(":sign \<Tab>\<PageUp>\<Left>\<Right>\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"TestWildMenu', @:)
+ " Test for Ctrl-E/Ctrl-Y being able to cancel / accept a match
+ call feedkeys(":sign un zz\<Left>\<Left>\<Left>\<Tab>\<C-E> yy\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign un yy zz', @:)
+
+ call feedkeys(":sign un zz\<Left>\<Left>\<Left>\<Tab>\<Tab>\<C-Y> yy\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"sign unplace yy zz', @:)
+
" cleanup
%bwipe
- call delete('Xdir1', 'rf')
set nowildmenu
endfunc
@@ -171,8 +177,9 @@ func Test_wildmenu_screendump()
let lines =<< trim [SCRIPT]
set wildmenu hlsearch
[SCRIPT]
- call writefile(lines, 'XTest_wildmenu')
+ call writefile(lines, 'XTest_wildmenu', 'D')
+ " Test simple wildmenu
let buf = RunVimInTerminal('-S XTest_wildmenu', {'rows': 8})
call term_sendkeys(buf, ":vim\<Tab>")
call VerifyScreenDump(buf, 'Test_wildmenu_1', {})
@@ -183,13 +190,26 @@ func Test_wildmenu_screendump()
call term_sendkeys(buf, "\<Tab>")
call VerifyScreenDump(buf, 'Test_wildmenu_3', {})
- call term_sendkeys(buf, "\<Tab>")
+ " Looped back to the original value
+ call term_sendkeys(buf, "\<Tab>\<Tab>")
call VerifyScreenDump(buf, 'Test_wildmenu_4', {})
+
+ " Test that the wild menu is cleared properly
+ call term_sendkeys(buf, " ")
+ call VerifyScreenDump(buf, 'Test_wildmenu_5', {})
+
+ " Test that a different wildchar still works
+ call term_sendkeys(buf, "\<Esc>:set wildchar=<Esc>\<CR>")
+ call term_sendkeys(buf, ":vim\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_1', {})
+
+ " Double-<Esc> is a hard-coded method to escape while wildchar=<Esc>. Make
+ " sure clean up is properly done in edge case like this.
call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_6', {})
" clean up
call StopVimInTerminal(buf)
- call delete('XTest_wildmenu')
endfunc
func Test_redraw_in_autocmd()
@@ -330,17 +350,21 @@ func Test_map_completion()
call assert_equal('"map <Left>', getreg(':'))
call feedkeys(":map <A-Left>\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal("\"map <A-Left>\<Tab>", getreg(':'))
+ call feedkeys(":map <M-Left>\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map <M-Left>x", getreg(':'))
unmap ,f
unmap ,g
unmap <Left>
unmap <A-Left>x
- set cpo-=< cpo-=B cpo-=k
+ set cpo-=< cpo-=k
map <Left> left
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
call feedkeys(":map <M\<Tab>\<Home>\"\<CR>", 'xt')
- " call assert_equal("\"map <M\<Tab>", getreg(':'))
+ call assert_equal("\"map <M\<Tab>", getreg(':'))
+ call feedkeys(":map \<C-V>\<C-V><M\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal("\"map \<C-V><Middle>x", getreg(':'))
unmap <Left>
" set cpo+=<
@@ -369,7 +393,7 @@ func Test_map_completion()
call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt')
call assert_equal('"map <Left>', getreg(':'))
unmap <Left>
- " set cpo-=k
+ set cpo-=k
call assert_fails('call feedkeys(":map \\\\%(\<Tab>\<Home>\"\<CR>", "xt")', 'E53:')
@@ -449,10 +473,10 @@ func Test_getcompletion()
let l = getcompletion('blahblah', 'augroup')
call assert_equal([], l)
- let l = getcompletion('', 'behave')
- call assert_true(index(l, 'mswin') >= 0)
- let l = getcompletion('not', 'behave')
- call assert_equal([], l)
+ " let l = getcompletion('', 'behave')
+ " call assert_true(index(l, 'mswin') >= 0)
+ " let l = getcompletion('not', 'behave')
+ " call assert_equal([], l)
let l = getcompletion('', 'color')
call assert_true(index(l, 'default') >= 0)
@@ -597,6 +621,8 @@ func Test_getcompletion()
call assert_true(index(l, 'taglist(') >= 0)
let l = getcompletion('call paint', 'cmdline')
call assert_equal([], l)
+ let l = getcompletion('autocmd BufEnter * map <bu', 'cmdline')
+ call assert_equal(['<buffer>'], l)
func T(a, c, p)
let g:cmdline_compl_params = [a:a, a:c, a:p]
@@ -648,7 +674,7 @@ func Test_getcompletion()
call assert_fails("call getcompletion('\\\\@!\\\\@=', 'buffer')", 'E871:')
call assert_fails('call getcompletion("", "burp")', 'E475:')
- call assert_fails('call getcompletion("abc", [])', 'E475:')
+ call assert_fails('call getcompletion("abc", [])', 'E1174:')
endfunc
" Test for getcompletion() with "fuzzy" in 'wildoptions'
@@ -715,7 +741,7 @@ endfunc
func Test_shellcmd_completion()
let save_path = $PATH
- call mkdir('Xpathdir/Xpathsubdir', 'p')
+ call mkdir('Xpathdir/Xpathsubdir', 'pR')
call writefile([''], 'Xpathdir/Xfile.exe')
call setfperm('Xpathdir/Xfile.exe', 'rwx------')
@@ -731,17 +757,15 @@ func Test_shellcmd_completion()
call insert(expected, 'Xfile.exe')
call assert_equal(expected, actual)
- call delete('Xpathdir', 'rf')
let $PATH = save_path
endfunc
func Test_expand_star_star()
- call mkdir('a/b', 'p')
- call writefile(['asdfasdf'], 'a/b/fileXname')
- call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt')
- call assert_equal('find a/b/fileXname', @:)
+ call mkdir('a/b/c', 'pR')
+ call writefile(['asdfasdf'], 'a/b/c/fileXname')
+ call feedkeys(":find a/**/fileXname\<Tab>\<CR>", 'xt')
+ call assert_equal('find a/b/c/fileXname', @:)
bwipe!
- call delete('a', 'rf')
endfunc
func Test_cmdline_paste()
@@ -1035,6 +1059,54 @@ func Test_cmdline_complete_expression()
unlet g:SomeVar
endfunc
+func Test_cmdline_complete_argopt()
+ " completion for ++opt=arg for file commands
+ call assert_equal('fileformat=', getcompletion('edit ++', 'cmdline')[0])
+ call assert_equal('encoding=', getcompletion('read ++e', 'cmdline')[0])
+ call assert_equal('edit', getcompletion('read ++bin ++edi', 'cmdline')[0])
+
+ call assert_equal(['fileformat='], getcompletion('edit ++ff', 'cmdline'))
+ " Test ++ff in the middle of the cmdline
+ call feedkeys(":edit ++ff zz\<Left>\<Left>\<Left>\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"edit ++fileformat= zz", @:)
+
+ call assert_equal('dos', getcompletion('write ++ff=d', 'cmdline')[0])
+ call assert_equal('mac', getcompletion('args ++fileformat=m', 'cmdline')[0])
+ call assert_equal('utf-8', getcompletion('split ++enc=ut*-8', 'cmdline')[0])
+ call assert_equal('latin1', getcompletion('tabedit ++encoding=lati', 'cmdline')[0])
+ call assert_equal('keep', getcompletion('edit ++bad=k', 'cmdline')[0])
+
+ call assert_equal([], getcompletion('edit ++bogus=', 'cmdline'))
+
+ " completion should skip the ++opt and continue
+ call writefile([], 'Xaaaaa.txt', 'D')
+ call feedkeys(":split ++enc=latin1 Xaaa\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"split ++enc=latin1 Xaaaaa.txt', @:)
+
+ if has('terminal')
+ " completion for terminal's [options]
+ call assert_equal('close', getcompletion('terminal ++cl*e', 'cmdline')[0])
+ call assert_equal('hidden', getcompletion('terminal ++open ++hidd', 'cmdline')[0])
+ call assert_equal('term', getcompletion('terminal ++kill=ter', 'cmdline')[0])
+
+ call assert_equal([], getcompletion('terminal ++bogus=', 'cmdline'))
+
+ " :terminal completion should skip the ++opt when considering what is the
+ " first option, which is a list of shell commands, unlike second option
+ " onwards.
+ let first_param = getcompletion('terminal ', 'cmdline')
+ let second_param = getcompletion('terminal foo ', 'cmdline')
+ let skipped_opt_param = getcompletion('terminal ++close ', 'cmdline')
+ call assert_equal(first_param, skipped_opt_param)
+ call assert_notequal(first_param, second_param)
+ endif
+endfunc
+
+" Unique function name for completion below
+func s:WeirdFunc()
+ echo 'weird'
+endfunc
+
" Test for various command-line completion
func Test_cmdline_complete_various()
" completion for a command starting with a comment
@@ -1054,14 +1126,14 @@ func Test_cmdline_complete_various()
call assert_equal("\"sI \<C-A>", @:)
" completion for :write command
- call mkdir('Xdir')
- call writefile(['one'], 'Xdir/Xfile1')
+ call mkdir('Xcwdir')
+ call writefile(['one'], 'Xcwdir/Xfile1')
let save_cwd = getcwd()
- cd Xdir
+ cd Xcwdir
call feedkeys(":w >> \<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal("\"w >> Xfile1", @:)
call chdir(save_cwd)
- call delete('Xdir', 'rf')
+ call delete('Xcwdir', 'rf')
" completion for :w ! and :r ! commands
call feedkeys(":w !invalid_xyz_cmd\<C-A>\<C-B>\"\<CR>", 'xt')
@@ -1118,12 +1190,12 @@ func Test_cmdline_complete_various()
call assert_equal("\"doautocmd BufNew,BufEnter", @:)
" completion of file name in :doautocmd
- call writefile([], 'Xfile1')
- call writefile([], 'Xfile2')
- call feedkeys(":doautocmd BufEnter Xfi\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal("\"doautocmd BufEnter Xfile1 Xfile2", @:)
- call delete('Xfile1')
- call delete('Xfile2')
+ call writefile([], 'Xvarfile1')
+ call writefile([], 'Xvarfile2')
+ call feedkeys(":doautocmd BufEnter Xvarfi\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"doautocmd BufEnter Xvarfile1 Xvarfile2", @:)
+ call delete('Xvarfile1')
+ call delete('Xvarfile2')
" completion for the :augroup command
augroup XTest.test
@@ -1172,13 +1244,71 @@ func Test_cmdline_complete_various()
mapclear
delcom MyCmd
+ " Prepare for path completion
+ call mkdir('Xa b c', 'D')
+ defer delete('Xcomma,foobar.txt')
+ call writefile([], 'Xcomma,foobar.txt')
+
" completion for :set path= with multiple backslashes
- call feedkeys(":set path=a\\\\\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set path=a\\\ b', @:)
+ call feedkeys(':set path=Xa\\\ b' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set path=Xa\\\ b\\\ c/', @:)
+ set path&
" completion for :set dir= with a backslash
- call feedkeys(":set dir=a\\ b\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set dir=a\ b', @:)
+ call feedkeys(':set dir=Xa\ b' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dir=Xa\ b\ c/', @:)
+ set dir&
+
+ " completion for :set tags= / set dictionary= with escaped commas
+ if has('win32')
+ " In Windows backslashes are rounded up, so both '\,' and '\\,' escape to
+ " '\,'
+ call feedkeys(':set dictionary=Xcomma\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\,foobar.txt', @:)
+
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foobar.txt', @:)
+
+ call feedkeys(':set tags=Xcomma\\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\\\,foo', @:) " Didn't find a match
+
+ " completion for :set dictionary= with escaped commas (same behavior, but
+ " different internal code path from 'set tags=' for escaping the output)
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foobar.txt', @:)
+ else
+ " In other platforms, backslashes are rounded down (since '\,' itself will
+ " be escaped into ','). As a result '\\,' and '\\\,' escape to '\,'.
+ call feedkeys(':set tags=Xcomma\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\,foo', @:) " Didn't find a match
+
+ call feedkeys(':set tags=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set tags=Xcomma\\,foobar.txt', @:)
+
+ call feedkeys(':set dictionary=Xcomma\\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\\,foobar.txt', @:)
+
+ " completion for :set dictionary= with escaped commas (same behavior, but
+ " different internal code path from 'set tags=' for escaping the output)
+ call feedkeys(':set dictionary=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set dictionary=Xcomma\\,foobar.txt', @:)
+ endif
+ set tags&
+ set dictionary&
+
+ " completion for :set makeprg= with no escaped commas
+ call feedkeys(':set makeprg=Xcomma,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set makeprg=Xcomma,foobar.txt', @:)
+
+ if !has('win32')
+ " Cannot create file with backslash in file name in Windows, so only test
+ " this elsewhere.
+ defer delete('Xcomma\,fooslash.txt')
+ call writefile([], 'Xcomma\,fooslash.txt')
+ call feedkeys(':set makeprg=Xcomma\\,foo' .. "\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set makeprg=Xcomma\\,fooslash.txt', @:)
+ endif
+ set makeprg&
" completion for the :py3 commands
call feedkeys(":py3\<C-A>\<C-B>\"\<CR>", 'xt')
@@ -1249,6 +1379,30 @@ func Test_cmdline_complete_various()
call assert_equal('"py3file', @:)
endfunc
+" Test that expanding a pattern doesn't interfere with cmdline completion.
+func Test_expand_during_cmdline_completion()
+ func ExpandStuff()
+ badd <script>:p:h/README.*
+ call assert_equal(expand('<script>:p:h') .. '/README.txt', bufname('$'))
+ $bwipe
+ call assert_equal('README.txt', expand('README.*'))
+ call assert_equal(['README.txt'], getcompletion('README.*', 'file'))
+ endfunc
+ augroup test_CmdlineChanged
+ autocmd!
+ autocmd CmdlineChanged * call ExpandStuff()
+ augroup END
+
+ call feedkeys(":sign \<Tab>\<Tab>\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"sign place', @:)
+
+ augroup test_CmdlineChanged
+ au!
+ augroup END
+ augroup! test_CmdlineChanged
+ delfunc ExpandStuff
+endfunc
+
" Test for 'wildignorecase'
func Test_cmdline_wildignorecase()
CheckUnix
@@ -1454,7 +1608,7 @@ func Test_verbose_option()
call writefile(lines, 'XTest_verbose')
let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, ":DoSomething\<CR>")
call VerifyScreenDump(buf, 'Test_verbose_option_1', {})
@@ -1532,7 +1686,7 @@ func Test_cmdwin_restore()
call writefile(lines, 'XTest_restore')
let buf = RunVimInTerminal('-S XTest_restore', {'rows': 12})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "q:")
call VerifyScreenDump(buf, 'Test_cmdwin_restore_1', {})
@@ -1552,6 +1706,21 @@ func Test_cmdwin_restore()
call delete('XTest_restore')
endfunc
+func Test_cmdwin_no_terminal()
+ CheckFeature cmdwin
+ CheckFeature terminal
+ CheckNotMSWindows
+
+ let buf = RunVimInTerminal('', {'rows': 12})
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, "q:")
+ call term_sendkeys(buf, ":let buf = term_start(['/bin/echo'], #{hidden: 1})\<CR>")
+ call VerifyScreenDump(buf, 'Test_cmdwin_no_terminal', {})
+ call term_sendkeys(buf, ":q\<CR>")
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_buffers_lastused()
" check that buffers are sorted by time when wildmode has lastused
edit bufc " oldest
@@ -1659,7 +1828,7 @@ func Test_cmdlineclear_tabenter()
call writefile(lines, 'XtestCmdlineClearTabenter')
let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10})
- call term_wait(buf, 50)
+ call TermWait(buf, 25)
" in one tab make the command line higher with CTRL-W -
call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt")
call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {})
@@ -1788,6 +1957,7 @@ func Test_cmd_bang_E135()
augroup test_cmd_filter_E135
au!
augroup END
+ augroup! test_cmd_filter_E135
%bwipe!
endfunc
@@ -1840,17 +2010,17 @@ endfunc
" Test for using ~ for home directory in cmdline completion matches
func Test_cmdline_expand_home()
- call mkdir('Xdir')
- call writefile([], 'Xdir/Xfile1')
- call writefile([], 'Xdir/Xfile2')
- cd Xdir
+ call mkdir('Xexpdir')
+ call writefile([], 'Xexpdir/Xfile1')
+ call writefile([], 'Xexpdir/Xfile2')
+ cd Xexpdir
let save_HOME = $HOME
let $HOME = getcwd()
call feedkeys(":e ~/\<C-A>\<C-B>\"\<CR>", 'xt')
call assert_equal('"e ~/Xfile1 ~/Xfile2', @:)
let $HOME = save_HOME
cd ..
- call delete('Xdir', 'rf')
+ call delete('Xexpdir', 'rf')
endfunc
" Test for using CTRL-\ CTRL-G in the command line to go back to normal mode
@@ -1970,6 +2140,29 @@ func Test_wildmode()
let &encoding = save_encoding
endfunc
+func Test_custom_complete_autoload()
+ call mkdir('Xcustdir/autoload', 'p')
+ let save_rtp = &rtp
+ exe 'set rtp=' .. getcwd() .. '/Xcustdir'
+ let lines =<< trim END
+ func vim8#Complete(a, c, p)
+ return "oneA\noneB\noneC"
+ endfunc
+ END
+ call writefile(lines, 'Xcustdir/autoload/vim8.vim')
+
+ command -nargs=1 -complete=custom,vim8#Complete MyCmd
+ set nowildmenu
+ set wildmode=full,list
+ call feedkeys(":MyCmd \<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"MyCmd oneA oneB oneC', @:)
+
+ let &rtp = save_rtp
+ set wildmode& wildmenu&
+ delcommand MyCmd
+ call delete('Xcustdir', 'rf')
+endfunc
+
" Test for interrupting the command-line completion
func Test_interrupt_compl()
func F(lead, cmdl, p)
@@ -2107,7 +2300,8 @@ func Test_cmdline_revins()
call assert_equal("\"abc\<c-_>", @:)
set allowrevins
call feedkeys(":\"abc\<c-_>xyz\<c-_>\<CR>", 'xt')
- call assert_equal('"abcñèæ', @:)
+ " call assert_equal('"abcñèæ', @:)
+ call assert_equal('"abcxyz', @:)
set allowrevins&
endfunc
@@ -2275,7 +2469,7 @@ endfunc
func Test_cmd_map_cmdlineChanged()
let g:log = []
cnoremap <F1> l<Cmd><CR>s
- augroup test
+ augroup test_CmdlineChanged
autocmd!
autocmd CmdlineChanged : let g:log += [getcmdline()]
augroup END
@@ -2291,38 +2485,39 @@ func Test_cmd_map_cmdlineChanged()
unlet g:log
cunmap <F1>
- augroup test
+ augroup test_CmdlineChanged
autocmd!
augroup END
+ augroup! test_CmdlineChanged
endfunc
" Test for the 'suffixes' option
func Test_suffixes_opt()
- call writefile([], 'Xfile')
- call writefile([], 'Xfile.c')
- call writefile([], 'Xfile.o')
+ call writefile([], 'Xsuffile')
+ call writefile([], 'Xsuffile.c')
+ call writefile([], 'Xsuffile.o')
set suffixes=
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile Xfile.c Xfile.o', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.c', @:)
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile Xsuffile.c Xsuffile.o', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.c', @:)
set suffixes=.c
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile Xfile.o Xfile.c', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.o', @:)
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile Xsuffile.o Xsuffile.c', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.o', @:)
set suffixes=,,
- call feedkeys(":e Xfi*\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.c Xfile.o Xfile', @:)
- call feedkeys(":e Xfi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"e Xfile.o', @:)
+ call feedkeys(":e Xsuffi*\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.c Xsuffile.o Xsuffile', @:)
+ call feedkeys(":e Xsuffi*\<Tab>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"e Xsuffile.o', @:)
set suffixes&
" Test for getcompletion() with different patterns
- call assert_equal(['Xfile', 'Xfile.c', 'Xfile.o'], getcompletion('Xfile', 'file'))
- call assert_equal(['Xfile'], getcompletion('Xfile$', 'file'))
- call delete('Xfile')
- call delete('Xfile.c')
- call delete('Xfile.o')
+ call assert_equal(['Xsuffile', 'Xsuffile.c', 'Xsuffile.o'], getcompletion('Xsuffile', 'file'))
+ call assert_equal(['Xsuffile'], getcompletion('Xsuffile$', 'file'))
+ call delete('Xsuffile')
+ call delete('Xsuffile.c')
+ call delete('Xsuffile.o')
endfunc
" Test for using a popup menu for the command line completion matches
@@ -2364,7 +2559,7 @@ func Test_wildmenu_pum()
call feedkeys(":edit $VIMRUNTIME/\<Tab>\<Left>\<C-U>ab\<Tab>")
endfunc
[CODE]
- call writefile(commands, 'Xtest')
+ call writefile(commands, 'Xtest', 'D')
let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
@@ -2422,12 +2617,12 @@ func Test_wildmenu_pum()
call VerifyScreenDump(buf, 'Test_wildmenu_pum_13', {})
" Directory name completion
- call mkdir('Xdir/XdirA/XdirB', 'p')
- call writefile([], 'Xdir/XfileA')
- call writefile([], 'Xdir/XdirA/XfileB')
- call writefile([], 'Xdir/XdirA/XdirB/XfileC')
+ call mkdir('Xnamedir/XdirA/XdirB', 'pR')
+ call writefile([], 'Xnamedir/XfileA')
+ call writefile([], 'Xnamedir/XdirA/XfileB')
+ call writefile([], 'Xnamedir/XdirA/XdirB/XfileC')
- call term_sendkeys(buf, "\<C-U>e Xdi\<Tab>\<Tab>")
+ call term_sendkeys(buf, "\<C-U>e Xnamedi\<Tab>\<Tab>")
call VerifyScreenDump(buf, 'Test_wildmenu_pum_14', {})
" Pressing <Right> on a directory name should go into that directory
@@ -2502,13 +2697,13 @@ func Test_wildmenu_pum()
call VerifyScreenDump(buf, 'Test_wildmenu_pum_31', {})
" Tests a directory name contained full-width characters.
- call mkdir('Xdir/あいう', 'p')
- call writefile([], 'Xdir/あいう/abc')
- call writefile([], 'Xdir/あいう/xyz')
- call writefile([], 'Xdir/あいう/123')
+ call mkdir('Xnamedir/あいう', 'p')
+ call writefile([], 'Xnamedir/あいう/abc')
+ call writefile([], 'Xnamedir/あいう/xyz')
+ call writefile([], 'Xnamedir/あいう/123')
call term_sendkeys(buf, "\<C-U>set wildmode&\<CR>")
- call term_sendkeys(buf, ":\<C-U>e Xdir/あいう/\<Tab>")
+ call term_sendkeys(buf, ":\<C-U>e Xnamedir/あいう/\<Tab>")
call VerifyScreenDump(buf, 'Test_wildmenu_pum_32', {})
" Pressing <C-A> when the popup menu is displayed should list all the
@@ -2530,7 +2725,7 @@ func Test_wildmenu_pum()
" After using <C-A> to expand all the filename matches, pressing <Up>
" should not open the popup menu again.
- call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xdir/XdirA\<CR>")
+ call term_sendkeys(buf, "\<C-E>\<C-U>:cd Xnamedir/XdirA\<CR>")
call term_sendkeys(buf, ":e \<Tab>\<C-A>\<Up>")
call VerifyScreenDump(buf, 'Test_wildmenu_pum_36', {})
call term_sendkeys(buf, "\<C-E>\<C-U>:cd -\<CR>")
@@ -2588,10 +2783,18 @@ func Test_wildmenu_pum()
call term_sendkeys(buf, "\<PageUp>")
call VerifyScreenDump(buf, 'Test_wildmenu_pum_50', {})
+ " pressing <C-E> to end completion should work in middle of the line too
+ call term_sendkeys(buf, "\<Esc>:set wildchazz\<Left>\<Left>\<Tab>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_51', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_52', {})
+
+ " pressing <C-Y> should select the current match and end completion
+ call term_sendkeys(buf, "\<Esc>:set wildchazz\<Left>\<Left>\<Tab>\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_53', {})
+
call term_sendkeys(buf, "\<C-U>\<CR>")
call StopVimInTerminal(buf)
- call delete('Xtest')
- call delete('Xdir', 'rf')
endfunc
" Test for wildmenumode() with the cmdline popup menu
@@ -2623,7 +2826,7 @@ func Test_wildmenu_pum_from_terminal()
let cmds = ['set wildmenu wildoptions=pum']
let pcmd = python .. ' -c "import sys; sys.stdout.write(sys.stdin.read())"'
call add(cmds, "call term_start('" .. pcmd .. "')")
- call writefile(cmds, 'Xtest')
+ call writefile(cmds, 'Xtest', 'D')
let buf = RunVimInTerminal('-S Xtest', #{rows: 10})
call term_sendkeys(buf, "\r\r\r")
call term_wait(buf)
@@ -2632,13 +2835,13 @@ func Test_wildmenu_pum_from_terminal()
call VerifyScreenDump(buf, 'Test_wildmenu_pum_term_01', {})
call term_wait(buf)
call StopVimInTerminal(buf)
- call delete('Xtest')
endfunc
-func Test_wildmenu_pum_clear_entries()
+func Test_wildmenu_pum_odd_wildchar()
CheckRunVimInTerminal
- " This was using freed memory. Run in a terminal to get the pum to update.
+ " Test odd wildchar interactions with pum. Make sure they behave properly
+ " and don't lead to memory corruption due to improperly cleaned up memory.
let lines =<< trim END
set wildoptions=pum
set wildchar=<C-E>
@@ -2646,10 +2849,35 @@ func Test_wildmenu_pum_clear_entries()
call writefile(lines, 'XwildmenuTest', 'D')
let buf = RunVimInTerminal('-S XwildmenuTest', #{rows: 10})
- call term_sendkeys(buf, ":\<C-E>\<C-E>")
- call VerifyScreenDump(buf, 'Test_wildmenu_pum_clear_entries_1', {})
+ call term_sendkeys(buf, ":\<C-E>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_1', {})
+
+ " <C-E> being a wildchar takes priority over its original functionality
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_2', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_3', {})
+
+ " Escape key can be wildchar too. Double-<Esc> is hard-coded to escape
+ " command-line, and we need to make sure to clean up properly.
+ call term_sendkeys(buf, ":set wildchar=<Esc>\<CR>")
+ call term_sendkeys(buf, ":\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_1', {})
- set wildoptions& wildchar&
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_3', {})
+
+ " <C-\> can also be wildchar. <C-\><C-N> however will still escape cmdline
+ " and we again need to make sure we clean up properly.
+ call term_sendkeys(buf, ":set wildchar=<C-\\>\<CR>")
+ call term_sendkeys(buf, ":\<C-\>\<C-\>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_1', {})
+
+ call term_sendkeys(buf, "\<C-N>")
+ call VerifyScreenDump(buf, 'Test_wildmenu_pum_odd_wildchar_3', {})
+
+ call StopVimInTerminal(buf)
endfunc
" Test for completion after a :substitute command followed by a pipe (|)
@@ -2765,6 +2993,7 @@ endfunc
" :behave suboptions fuzzy completion
func Test_fuzzy_completion_behave()
+ throw 'Skipped: Nvim removed :behave'
set wildoptions&
call feedkeys(":behave xm\<Tab>\<C-B>\"\<CR>", 'tx')
call assert_equal('"behave xm', @:)
@@ -3256,6 +3485,17 @@ func Test_fuzzy_completion_custom_func()
set wildoptions&
endfunc
+" Test for fuzzy completion in the middle of a cmdline instead of at the end
+func Test_fuzzy_completion_in_middle()
+ set wildoptions=fuzzy
+ call feedkeys(":set ildar wrap\<Left>\<Left>\<Left>\<Left>\<Left>\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"set wildchar wildcharm wrap", @:)
+
+ call feedkeys(":args ++odng zz\<Left>\<Left>\<Left>\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal("\"args ++encoding= zz", @:)
+ set wildoptions&
+endfunc
+
" Test for :breakadd argument completion
func Test_cmdline_complete_breakadd()
call feedkeys(":breakadd \<C-A>\<C-B>\"\<CR>", 'tx')
@@ -3470,16 +3710,23 @@ func Test_cmdline_complete_bang_cmd_argument()
call assert_equal('"!vim test_cmdline.vim', @:)
endfunc
-func Check_completion()
- call assert_equal('let a', getcmdline())
- call assert_equal(6, getcmdpos())
- call assert_equal(7, getcmdscreenpos())
- call assert_equal('var', getcmdcompltype())
- return ''
+func Call_cmd_funcs()
+ return string([getcmdpos(), getcmdscreenpos(), getcmdcompltype()])
endfunc
func Test_screenpos_and_completion()
- call feedkeys(":let a\<C-R>=Check_completion()\<CR>\<Esc>", "xt")
+ call assert_equal(0, getcmdpos())
+ call assert_equal(0, getcmdscreenpos())
+ call assert_equal('', getcmdcompltype())
+
+ cnoremap <expr> <F2> string([getcmdpos(), getcmdscreenpos(), getcmdcompltype()])
+ call feedkeys(":let a\<F2>\<C-B>\"\<CR>", "xt")
+ call assert_equal("\"let a[6, 7, 'var']", @:)
+ call feedkeys(":quit \<F2>\<C-B>\"\<CR>", "xt")
+ call assert_equal("\"quit [6, 7, '']", @:)
+ call feedkeys(":nosuchcommand \<F2>\<C-B>\"\<CR>", "xt")
+ call assert_equal("\"nosuchcommand [15, 16, '']", @:)
+ cunmap <F2>
endfunc
func Test_recursive_register()
@@ -3527,6 +3774,14 @@ endfunc
func Test_setcmdline()
func SetText(text, pos)
+ call assert_equal(0, setcmdline(v:_null_string))
+ call assert_equal('', getcmdline())
+ call assert_equal(1, getcmdpos())
+
+ call assert_equal(0, setcmdline(''[: -1]))
+ call assert_equal('', getcmdline())
+ call assert_equal(1, getcmdpos())
+
autocmd CmdlineChanged * let g:cmdtype = expand('<afile>')
call assert_equal(0, setcmdline(a:text))
call assert_equal(a:text, getcmdline())
@@ -3578,4 +3833,82 @@ func Test_setcmdline()
cunmap a
endfunc
+func Test_rulerformat_position()
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', #{rows: 2, cols: 20})
+ call term_sendkeys(buf, ":set ruler rulerformat=longish\<CR>")
+ call term_sendkeys(buf, ":set laststatus=0 winwidth=1\<CR>")
+ call term_sendkeys(buf, "\<C-W>v\<C-W>|\<C-W>p")
+ call VerifyScreenDump(buf, 'Test_rulerformat_position', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_getcompletion_usercmd()
+ command! -nargs=* -complete=command TestCompletion echo <q-args>
+
+ call assert_equal(getcompletion('', 'cmdline'),
+ \ getcompletion('TestCompletion ', 'cmdline'))
+ call assert_equal(['<buffer>'],
+ \ getcompletion('TestCompletion map <bu', 'cmdline'))
+
+ delcom TestCompletion
+endfunc
+
+func Test_custom_completion()
+ func CustomComplete1(lead, line, pos)
+ return "a\nb\nc"
+ endfunc
+ func CustomComplete2(lead, line, pos)
+ return ['a', 'b']->filter({ _, val -> val->stridx(a:lead) == 0 })
+ endfunc
+ func Check_custom_completion()
+ call assert_equal('custom,CustomComplete1', getcmdcompltype())
+ return ''
+ endfunc
+ func Check_customlist_completion()
+ call assert_equal('customlist,CustomComplete2', getcmdcompltype())
+ return ''
+ endfunc
+
+ command -nargs=1 -complete=custom,CustomComplete1 Test1 echo
+ command -nargs=1 -complete=customlist,CustomComplete2 Test2 echo
+
+ call feedkeys(":Test1 \<C-R>=Check_custom_completion()\<CR>\<Esc>", "xt")
+ call feedkeys(":Test2 \<C-R>=Check_customlist_completion()\<CR>\<Esc>", "xt")
+
+ call assert_fails("call getcompletion('', 'custom')", 'E475:')
+ call assert_fails("call getcompletion('', 'customlist')", 'E475:')
+
+ call assert_equal(getcompletion('', 'custom,CustomComplete1'), ['a', 'b', 'c'])
+ call assert_equal(getcompletion('', 'customlist,CustomComplete2'), ['a', 'b'])
+ call assert_equal(getcompletion('b', 'customlist,CustomComplete2'), ['b'])
+
+ delcom Test1
+ delcom Test2
+
+ delfunc CustomComplete1
+ delfunc CustomComplete2
+ delfunc Check_custom_completion
+ delfunc Check_customlist_completion
+endfunc
+
+func Test_custom_completion_with_glob()
+ func TestGlobComplete(A, L, P)
+ return split(glob('Xglob*'), "\n")
+ endfunc
+
+ command -nargs=* -complete=customlist,TestGlobComplete TestGlobComplete :
+ call writefile([], 'Xglob1', 'D')
+ call writefile([], 'Xglob2', 'D')
+
+ call feedkeys(":TestGlobComplete \<Tab> \<Tab>\<C-N> \<Tab>\<C-P>;\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"TestGlobComplete Xglob1 Xglob2 ;', @:)
+
+ delcommand TestGlobComplete
+ delfunc TestGlobComplete
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_cmdwin.vim b/test/old/testdir/test_cmdwin.vim
new file mode 100644
index 0000000000..f07fd99f8a
--- /dev/null
+++ b/test/old/testdir/test_cmdwin.vim
@@ -0,0 +1,96 @@
+" Tests for editing the command line.
+
+source check.vim
+source screendump.vim
+
+
+func Test_cant_open_cmdwin_in_cmdwin()
+ try
+ call feedkeys("q:q::q\<CR>", "x!")
+ catch
+ let caught = v:exception
+ endtry
+ call assert_match('E1292:', caught)
+endfunc
+
+func Test_cmdwin_virtual_edit()
+ enew!
+ set ve=all cpo+=$
+ silent normal q/s
+
+ set ve= cpo-=$
+endfunc
+
+" Check that a :normal command can be used to stop Visual mode without side
+" effects.
+func Test_normal_escape()
+ call feedkeys("q:i\" foo\<Esc>:normal! \<C-V>\<Esc>\<CR>:\" bar\<CR>", 'ntx')
+ call assert_equal('" bar', @:)
+endfunc
+
+" This was using a pointer to a freed buffer
+func Test_cmdwin_freed_buffer_ptr()
+ " this does not work on MS-Windows because renaming an open file fails
+ CheckNotMSWindows
+
+ au BufEnter * next 0| file
+ edit 0
+ silent! norm q/
+
+ au! BufEnter
+ bwipe!
+endfunc
+
+" This was resulting in a window with negative width.
+" The test doesn't reproduce the illegal memory access though...
+func Test_cmdwin_split_often()
+ let lines = &lines
+ let columns = &columns
+ set t_WS=
+
+ try
+ " set encoding=iso8859
+ set ruler
+ winsize 0 0
+ noremap 0 H
+ sil norm 0000000q:
+ catch /E36:/
+ endtry
+
+ bwipe!
+ set encoding=utf8
+ let &lines = lines
+ let &columns = columns
+endfunc
+
+func Test_cmdwin_restore_heights()
+ set showtabline=0 cmdheight=2 laststatus=0
+ call feedkeys("q::set cmdheight=1\<CR>:q\<CR>", 'ntx')
+ call assert_equal(&lines - 1, winheight(0))
+
+ set showtabline=2 cmdheight=3
+ call feedkeys("q::set showtabline=0\<CR>:q\<CR>", 'ntx')
+ call assert_equal(&lines - 3, winheight(0))
+
+ set cmdheight=1 laststatus=2
+ call feedkeys("q::set laststatus=0\<CR>:q\<CR>", 'ntx')
+ call assert_equal(&lines - 1, winheight(0))
+
+ set laststatus=2
+ call feedkeys("q::set laststatus=1\<CR>:q\<CR>", 'ntx')
+ call assert_equal(&lines - 1, winheight(0))
+
+ set laststatus=2
+ belowright vsplit
+ wincmd _
+ let restcmds = winrestcmd()
+ call feedkeys("q::set laststatus=1\<CR>:q\<CR>", 'ntx')
+ " As we have 2 windows, &ls = 1 should still have a statusline on the last
+ " window. As such, the number of available rows hasn't changed and the window
+ " sizes should be restored.
+ call assert_equal(restcmds, winrestcmd())
+
+ set cmdheight& showtabline& laststatus&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_codestyle.vim b/test/old/testdir/test_codestyle.vim
new file mode 100644
index 0000000000..01dd03f693
--- /dev/null
+++ b/test/old/testdir/test_codestyle.vim
@@ -0,0 +1,63 @@
+" Test for checking the source code style.
+
+func s:ReportError(fname, lnum, msg)
+ if a:lnum > 0
+ call assert_report(a:fname .. ' line ' .. a:lnum .. ': ' .. a:msg)
+ endif
+endfunc
+
+func Test_help_files()
+ set nowrapscan
+
+ for fpath in glob('../../../runtime/doc/*.txt', 0, 1)
+ let g:ignoreSwapExists = 'e'
+ exe 'edit ' .. fpath
+
+ let fname = fnamemodify(fpath, ":t")
+
+ " todo.txt is for developers, it's not need a strictly check
+ " version*.txt is a history and large size, so it's not checked
+ if fname == 'todo.txt' || fname =~ 'version.*\.txt'
+ continue
+ endif
+
+ " Check for mixed tabs and spaces
+ call cursor(1, 1)
+ while 1
+ let lnum = search('[^/] \t')
+ if fname == 'visual.txt' && getline(lnum) =~ "STRING \tjkl"
+ \ || fname == 'usr_27.txt' && getline(lnum) =~ "\[^\? \t\]"
+ continue
+ endif
+ call s:ReportError(fpath, lnum, 'space before tab')
+ if lnum == 0
+ break
+ endif
+ endwhile
+
+ " Check for unnecessary whitespace at the end of a line
+ call cursor(1, 1)
+ while 1
+ let lnum = search('[^/~\\]\s$')
+ " skip line that are known to have trailing white space
+ if fname == 'map.txt' && getline(lnum) =~ "unmap @@ $"
+ \ || fname == 'usr_12.txt' && getline(lnum) =~ "^\t/ \t$"
+ \ || fname == 'usr_41.txt' && getline(lnum) =~ "map <F4> o#include $"
+ \ || fname == 'change.txt' && getline(lnum) =~ "foobar bla $"
+ continue
+ endif
+ call s:ReportError('testdir' .. fpath, lnum, 'trailing white space')
+ if lnum == 0
+ break
+ endif
+ endwhile
+
+
+ endfor
+
+ set wrapscan&vim
+ bwipe!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_command_count.vim b/test/old/testdir/test_command_count.vim
index 55b230373f..55b230373f 100644
--- a/src/nvim/testdir/test_command_count.vim
+++ b/test/old/testdir/test_command_count.vim
diff --git a/src/nvim/testdir/test_comments.vim b/test/old/testdir/test_comments.vim
index c34b85c42d..c34b85c42d 100644
--- a/src/nvim/testdir/test_comments.vim
+++ b/test/old/testdir/test_comments.vim
diff --git a/src/nvim/testdir/test_comparators.vim b/test/old/testdir/test_comparators.vim
index 87be006cf2..87be006cf2 100644
--- a/src/nvim/testdir/test_comparators.vim
+++ b/test/old/testdir/test_comparators.vim
diff --git a/src/nvim/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim
index ec7d143030..0b22bafabb 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/test/old/testdir/test_compiler.vim
@@ -7,10 +7,8 @@ func Test_compiler()
CheckExecutable perl
CheckFeature quickfix
- " $LANG changes the output of Perl.
- if $LANG != ''
- unlet $LANG
- endif
+ let save_LC_ALL = $LC_ALL
+ let $LC_ALL= "C"
" %:S does not work properly with 'shellslash' set
let save_shellslash = &shellslash
@@ -40,12 +38,13 @@ func Test_compiler()
let &shellslash = save_shellslash
call delete('Xfoo.pl')
bw!
+ let $LC_ALL = save_LC_ALL
endfunc
func GetCompilerNames()
return glob('$VIMRUNTIME/compiler/*.vim', 0, 1)
- \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})
- \ ->sort()
+ \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})
+ \ ->sort()
endfunc
func Test_compiler_without_arg()
diff --git a/src/nvim/testdir/test_conceal.vim b/test/old/testdir/test_conceal.vim
index bffc2f49d3..a679061544 100644
--- a/src/nvim/testdir/test_conceal.vim
+++ b/test/old/testdir/test_conceal.vim
@@ -4,6 +4,7 @@ source check.vim
CheckFeature conceal
source screendump.vim
+source view_util.vim
func Test_conceal_two_windows()
CheckScreendump
@@ -24,7 +25,7 @@ func Test_conceal_two_windows()
exe "normal /here\r"
[CODE]
- call writefile(code, 'XTest_conceal')
+ call writefile(code, 'XTest_conceal', 'D')
" Check that cursor line is concealed
let buf = RunVimInTerminal('-S XTest_conceal', {})
call VerifyScreenDump(buf, 'Test_conceal_two_windows_01', {})
@@ -106,7 +107,6 @@ func Test_conceal_two_windows()
" clean up
call StopVimInTerminal(buf)
- call delete('XTest_conceal')
endfunc
func Test_conceal_with_cursorline()
@@ -123,7 +123,7 @@ func Test_conceal_with_cursorline()
normal M
[CODE]
- call writefile(code, 'XTest_conceal_cul')
+ call writefile(code, 'XTest_conceal_cul', 'D')
let buf = RunVimInTerminal('-S XTest_conceal_cul', {})
call VerifyScreenDump(buf, 'Test_conceal_cul_01', {})
@@ -135,7 +135,38 @@ func Test_conceal_with_cursorline()
" clean up
call StopVimInTerminal(buf)
- call delete('XTest_conceal_cul')
+endfunc
+
+func Test_conceal_with_cursorcolumn()
+ CheckScreendump
+
+ " Check that cursorcolumn and colorcolumn don't get broken in presence of
+ " wrapped lines containing concealed text
+ let code =<< trim [CODE]
+ let lines = ["one one one |hidden| one one one one one one one one",
+ \ "two two two two |hidden| here two two",
+ \ "three |hidden| three three three three three three three three"]
+ call setline(1, lines)
+ set wrap linebreak
+ set showbreak=\ >>>\
+ syntax match test /|hidden|/ conceal
+ set conceallevel=2
+ set concealcursor=
+ exe "normal /here\r"
+ set cursorcolumn
+ set colorcolumn=50
+ [CODE]
+
+ call writefile(code, 'XTest_conceal_cuc', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_cuc', {'rows': 10, 'cols': 40})
+ call VerifyScreenDump(buf, 'Test_conceal_cuc_01', {})
+
+ " move cursor to the end of line (the cursor jumps to the next screen line)
+ call term_sendkeys(buf, "$")
+ call VerifyScreenDump(buf, 'Test_conceal_cuc_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
endfunc
func Test_conceal_resize_term()
@@ -147,17 +178,41 @@ func Test_conceal_resize_term()
syn region CommentCodeSpan matchgroup=Comment start=/`/ end=/`/ concealends
normal fb
[CODE]
- call writefile(code, 'XTest_conceal_resize')
+ call writefile(code, 'XTest_conceal_resize', 'D')
let buf = RunVimInTerminal('-S XTest_conceal_resize', {'rows': 6})
call VerifyScreenDump(buf, 'Test_conceal_resize_01', {})
call win_execute(buf->win_findbuf()[0], 'wincmd +')
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_conceal_resize_02', {})
" clean up
call StopVimInTerminal(buf)
- call delete('XTest_conceal_resize')
+endfunc
+
+func Test_conceal_linebreak()
+ CheckScreendump
+
+ let code =<< trim [CODE]
+ vim9script
+ &wrap = true
+ &conceallevel = 2
+ &concealcursor = 'nc'
+ &linebreak = true
+ &showbreak = '+ '
+ var line: string = 'a`a`a`a`'
+ .. 'a'->repeat(&columns - 15)
+ .. ' b`b`'
+ .. 'b'->repeat(&columns - 10)
+ .. ' cccccc'
+ ['x'->repeat(&columns), '', line]->setline(1)
+ syntax region CodeSpan matchgroup=Delimiter start=/\z(`\+\)/ end=/\z1/ concealends
+ [CODE]
+ call writefile(code, 'XTest_conceal_linebreak', 'D')
+ let buf = RunVimInTerminal('-S XTest_conceal_linebreak', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_conceal_linebreak_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
endfunc
" Tests for correct display (cursor column position) with +conceal and
@@ -245,7 +300,7 @@ func Test_conceal_cursor_pos()
:q!
[CODE]
- call writefile(code, 'XTest_conceal_curpos')
+ call writefile(code, 'XTest_conceal_curpos', 'D')
if RunVim([], [], '-s XTest_conceal_curpos')
call assert_equal([
@@ -256,7 +311,6 @@ func Test_conceal_cursor_pos()
endif
call delete('Xconceal_curpos.out')
- call delete('XTest_conceal_curpos')
endfunc
func Test_conceal_eol()
@@ -281,4 +335,78 @@ func Test_conceal_eol()
set nolist
endfunc
+func Test_conceal_mouse_click()
+ enew!
+ set mouse=a
+ setlocal conceallevel=2 concealcursor=nc
+ syn match Concealed "this" conceal
+ hi link Concealed Search
+ call setline(1, 'conceal this click here')
+ redraw
+ call assert_equal(['conceal click here '], ScreenLines(1, 20))
+
+ " click on the space between "this" and "click" puts cursor there
+ call Ntest_setmouse(1, 9)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 13, 0, 13], getcurpos())
+ " click on 'h' of "here" puts cursor there
+ call Ntest_setmouse(1, 16)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 20, 0, 20], getcurpos())
+ " click on 'e' of "here" puts cursor there
+ call Ntest_setmouse(1, 19)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 23], getcurpos())
+ " click after end of line puts cursor on 'e' without 'virtualedit'
+ call Ntest_setmouse(1, 20)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 24], getcurpos())
+ call Ntest_setmouse(1, 21)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 25], getcurpos())
+ call Ntest_setmouse(1, 22)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 26], getcurpos())
+ call Ntest_setmouse(1, 31)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 35], getcurpos())
+ call Ntest_setmouse(1, 32)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 36], getcurpos())
+
+ set virtualedit=all
+ redraw
+ " click on the space between "this" and "click" puts cursor there
+ call Ntest_setmouse(1, 9)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 13, 0, 13], getcurpos())
+ " click on 'h' of "here" puts cursor there
+ call Ntest_setmouse(1, 16)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 20, 0, 20], getcurpos())
+ " click on 'e' of "here" puts cursor there
+ call Ntest_setmouse(1, 19)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 23, 0, 23], getcurpos())
+ " click after end of line puts cursor there without 'virtualedit'
+ call Ntest_setmouse(1, 20)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 24, 0, 24], getcurpos())
+ call Ntest_setmouse(1, 21)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 24, 1, 25], getcurpos())
+ call Ntest_setmouse(1, 22)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 24, 2, 26], getcurpos())
+ call Ntest_setmouse(1, 31)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 24, 11, 35], getcurpos())
+ call Ntest_setmouse(1, 32)
+ call feedkeys("\<LeftMouse>", "tx")
+ call assert_equal([0, 1, 24, 12, 36], getcurpos())
+
+ bwipe!
+ set mouse& virtualedit&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_const.vim b/test/old/testdir/test_const.vim
index 4a6c8779dd..4a6c8779dd 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/test/old/testdir/test_const.vim
diff --git a/src/nvim/testdir/test_cpoptions.vim b/test/old/testdir/test_cpoptions.vim
index b9307ab30b..915f418712 100644
--- a/src/nvim/testdir/test_cpoptions.vim
+++ b/test/old/testdir/test_cpoptions.vim
@@ -31,17 +31,17 @@ func Test_cpo_A()
" Wipe out all the buffers, so that the alternate file is empty
edit Xfoo | %bw
set cpo-=A
- new Xfile1
- write Xfile2
+ new XcpoAfile1
+ write XcpoAfile2
call assert_equal('', @#)
%bw
- call delete('Xfile2')
- new Xfile1
+ call delete('XcpoAfile2')
+ new XcpoAfile1
set cpo+=A
- write Xfile2
- call assert_equal('Xfile2', @#)
+ write XcpoAfile2
+ call assert_equal('XcpoAfile2', @#)
close!
- call delete('Xfile2')
+ call delete('XcpoAfile2')
let &cpo = save_cpo
endfunc
@@ -68,15 +68,20 @@ endfunc
func Test_cpo_B()
let save_cpo = &cpo
new
+ imap <buffer> x<Bslash>k Test
set cpo-=B
iabbr <buffer> abc ab\<BS>d
exe "normal iabc "
call assert_equal('ab<BS>d ', getline(1))
+ call feedkeys(":imap <buffer> x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap <buffer> x\\k', @:)
%d
set cpo+=B
iabbr <buffer> abc ab\<BS>d
exe "normal iabc "
call assert_equal('abd ', getline(1))
+ call feedkeys(":imap <buffer> x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap <buffer> x\k', @:)
close!
let &cpo = save_cpo
endfunc
@@ -195,7 +200,8 @@ func Test_cpo_f()
set cpo+=f
read test_cpoptions.vim
call assert_equal('test_cpoptions.vim', @%)
- close!
+
+ bwipe!
let &cpo = save_cpo
endfunc
@@ -217,33 +223,33 @@ endfunc
" Test for the 'g' flag in 'cpo' (jump to line 1 when re-editing a file)
func Test_cpo_g()
- throw 'Skipped: Nvim does not support cpoptions flag "g"'
let save_cpo = &cpo
new test_cpoptions.vim
set cpo-=g
normal 20G
edit
call assert_equal(20, line('.'))
- set cpo+=g
- edit
- call assert_equal(1, line('.'))
+ " Nvim: no "g" flag in 'cpoptions'.
+ " set cpo+=g
+ " edit
+ " call assert_equal(1, line('.'))
close!
let &cpo = save_cpo
endfunc
" Test for inserting text in a line with only spaces ('H' flag in 'cpoptions')
func Test_cpo_H()
- throw 'Skipped: Nvim does not support cpoptions flag "H"'
let save_cpo = &cpo
new
set cpo-=H
call setline(1, ' ')
normal! Ia
call assert_equal(' a', getline(1))
- set cpo+=H
- call setline(1, ' ')
- normal! Ia
- call assert_equal(' a ', getline(1))
+ " Nvim: no "H" flag in 'cpoptions'.
+ " set cpo+=H
+ " call setline(1, ' ')
+ " normal! Ia
+ " call assert_equal(' a ', getline(1))
close!
let &cpo = save_cpo
endfunc
@@ -428,7 +434,7 @@ func Test_cpo_O()
let &cpo = save_cpo
endfunc
-" Test for the 'p' flag in 'cpo' is in the test_lispwords.vim file.
+" Test for the 'p' flag in 'cpo' is in the test_lispindent.vim file.
" Test for the 'P' flag in 'cpo' (appending to a file sets the current file
" name)
@@ -444,7 +450,8 @@ func Test_cpo_P()
set cpo+=P
write >> XfileCpoP
call assert_equal('XfileCpoP', @%)
- close!
+
+ bwipe!
call delete('XfileCpoP')
let &cpo = save_cpo
endfunc
@@ -560,15 +567,15 @@ endfunc
" Test for the 'w' flag in 'cpo' ('cw' on a blank character changes only one
" character)
func Test_cpo_w()
- throw 'Skipped: Nvim does not support cpoptions flag "w"'
let save_cpo = &cpo
new
- set cpo+=w
- call setline(1, 'here are some words')
- norm! 1gg0elcwZZZ
- call assert_equal('hereZZZ are some words', getline('.'))
- norm! 1gg2elcWYYY
- call assert_equal('hereZZZ areYYY some words', getline('.'))
+ " Nvim: no "w" flag in 'cpoptions'.
+ " set cpo+=w
+ " call setline(1, 'here are some words')
+ " norm! 1gg0elcwZZZ
+ " call assert_equal('hereZZZ are some words', getline('.'))
+ " norm! 1gg2elcWYYY
+ " call assert_equal('hereZZZ areYYY some words', getline('.'))
set cpo-=w
call setline(1, 'here are some words')
norm! 1gg0elcwZZZ
@@ -745,16 +752,16 @@ endfunc
" Test for the '*' flag in 'cpo' (':*' is same as ':@')
func Test_cpo_star()
- throw 'Skipped: Nvim does not support cpoptions flag "*"'
let save_cpo = &cpo
let x = 0
new
set cpo-=*
let @a = 'let x += 1'
call assert_fails('*a', 'E20:')
- set cpo+=*
- *a
- call assert_equal(1, x)
+ " Nvim: no "*" flag in 'cpoptions'.
+ " set cpo+=*
+ " *a
+ " call assert_equal(1, x)
close!
let &cpo = save_cpo
endfunc
@@ -815,7 +822,6 @@ endfunc
" Test for the '#' flag in 'cpo' (count before 'D', 'o' and 'O' operators)
func Test_cpo_hash()
- throw 'Skipped: Nvim does not support cpoptions flag "#"'
let save_cpo = &cpo
new
set cpo-=#
@@ -827,14 +833,15 @@ func Test_cpo_hash()
normal gg2Otwo
call assert_equal(['two', 'two', 'three', 'four', 'four'], getline(1, '$'))
%d
- set cpo+=#
- call setline(1, ['one', 'two', 'three'])
- normal gg2D
- call assert_equal(['', 'two', 'three'], getline(1, '$'))
- normal gg2oone
- call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
- normal gg2Ozero
- call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$'))
+ " Nvim: no "#" flag in 'cpoptions'.
+ " set cpo+=#
+ " call setline(1, ['one', 'two', 'three'])
+ " normal gg2D
+ " call assert_equal(['', 'two', 'three'], getline(1, '$'))
+ " normal gg2oone
+ " call assert_equal(['', 'one', 'two', 'three'], getline(1, '$'))
+ " normal gg2Ozero
+ " call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$'))
close!
let &cpo = save_cpo
endfunc
@@ -858,16 +865,16 @@ endfunc
" Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern)
func Test_cpo_backslash()
- throw 'Skipped: Nvim does not support cpoptions flag "\"'
let save_cpo = &cpo
new
call setline(1, ['', " \\-string"])
set cpo-=\
exe 'normal gg/[ \-]' .. "\<CR>n"
call assert_equal(3, col('.'))
- set cpo+=\
- exe 'normal gg/[ \-]' .. "\<CR>n"
- call assert_equal(2, col('.'))
+ " Nvim: no "\" flag in 'cpoptions'.
+ " set cpo+=\
+ " exe 'normal gg/[ \-]' .. "\<CR>n"
+ " call assert_equal(2, col('.'))
close!
let &cpo = save_cpo
endfunc
@@ -877,7 +884,6 @@ endfunc
" Test for the '{' flag in 'cpo' (the "{" and "}" commands stop at a {
" character at the start of a line)
func Test_cpo_brace()
- throw 'Skipped: Nvim does not support cpoptions flag "{"'
let save_cpo = &cpo
new
call setline(1, ['', '{', ' int i;', '}', ''])
@@ -886,11 +892,12 @@ func Test_cpo_brace()
call assert_equal(5, line('.'))
normal G{
call assert_equal(1, line('.'))
- set cpo+={
- normal gg}
- call assert_equal(2, line('.'))
- normal G{
- call assert_equal(2, line('.'))
+ " Nvim: no "{" flag in 'cpoptions'.
+ " set cpo+={
+ " normal gg}
+ " call assert_equal(2, line('.'))
+ " normal G{
+ " call assert_equal(2, line('.'))
close!
let &cpo = save_cpo
endfunc
diff --git a/test/old/testdir/test_crash.vim b/test/old/testdir/test_crash.vim
new file mode 100644
index 0000000000..b093b053c5
--- /dev/null
+++ b/test/old/testdir/test_crash.vim
@@ -0,0 +1,155 @@
+" Some tests, that used to crash Vim
+source check.vim
+source screendump.vim
+
+CheckScreendump
+
+func Test_crash1()
+ CheckNotBSD
+ CheckExecutable dash
+ " Test 7 fails on Mac ...
+ CheckNotMac
+
+ " The following used to crash Vim
+ let opts = #{cmd: 'sh'}
+ let vim = GetVimProg()
+
+ let buf = RunVimInTerminal('sh', opts)
+
+ let file = 'crash/poc_huaf1'
+ let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 1: [OK]" > X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 50)
+
+ let file = 'crash/poc_huaf2'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 2: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 50)
+
+ let file = 'crash/poc_huaf3'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 3: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 100)
+
+ let file = 'crash/bt_quickfix_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 4: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ " clean up
+ call delete('Xerr')
+ " This test takes a bit longer
+ call TermWait(buf, 1000)
+
+ let file = 'crash/poc_tagfunc.vim'
+ let args = printf(cmn_args, vim, file)
+ " using || because this poc causes vim to exit with exitstatus != 0
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 5: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+
+ call TermWait(buf, 100)
+
+ let file = 'crash/bt_quickfix1_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 6: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ " clean up
+ call delete('X')
+ call TermWait(buf, 3000)
+
+ let file = 'crash/vim_regsub_both_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 7: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 3000)
+
+ let file = 'crash/vim_msg_trunc_poc'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 8: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 3000)
+
+ let file = 'crash/crash_scrollbar'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 9: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 1000)
+
+ let file = 'crash/editing_arg_idx_POC_1'
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' || echo "crash 10: [OK]" >> X_crash1_result.txt' .. "\<cr>")
+ call TermWait(buf, 1000)
+ call delete('Xerr')
+ call delete('@')
+
+ " clean up
+ exe buf .. "bw!"
+
+ sp X_crash1_result.txt
+
+ let expected = [
+ \ 'crash 1: [OK]',
+ \ 'crash 2: [OK]',
+ \ 'crash 3: [OK]',
+ \ 'crash 4: [OK]',
+ \ 'crash 5: [OK]',
+ \ 'crash 6: [OK]',
+ \ 'crash 7: [OK]',
+ \ 'crash 8: [OK]',
+ \ 'crash 9: [OK]',
+ \ 'crash 10: [OK]',
+ \ ]
+
+ call assert_equal(expected, getline(1, '$'))
+ bw!
+
+ call delete('X_crash1_result.txt')
+endfunc
+
+func Test_crash1_2()
+ CheckNotBSD
+ CheckExecutable dash
+
+ " The following used to crash Vim
+ let opts = #{cmd: 'sh'}
+ let vim = GetVimProg()
+ let result = 'X_crash1_1_result.txt'
+
+ let buf = RunVimInTerminal('sh', opts)
+
+ let file = 'crash/poc1'
+ let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'"
+ let args = printf(cmn_args, vim, file)
+ call term_sendkeys(buf, args ..
+ \ ' && echo "crash 1: [OK]" > '.. result .. "\<cr>")
+ call TermWait(buf, 150)
+
+ " clean up
+ exe buf .. "bw!"
+
+ exe "sp " .. result
+
+ let expected = [
+ \ 'crash 1: [OK]',
+ \ ]
+
+ call assert_equal(expected, getline(1, '$'))
+ bw!
+
+ call delete(result)
+endfunc
+
+func Test_crash2()
+ " The following used to crash Vim
+ let opts = #{wait_for_ruler: 0, rows: 20}
+ let args = ' -u NONE -i NONE -n -e -s -S '
+ let buf = RunVimInTerminal(args .. ' crash/vim_regsub_both', opts)
+ call VerifyScreenDump(buf, 'Test_crash_01', {})
+ exe buf .. "bw!"
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cursor_func.vim b/test/old/testdir/test_cursor_func.vim
index bb8e7cd5c5..cf131e8dad 100644
--- a/src/nvim/testdir/test_cursor_func.vim
+++ b/test/old/testdir/test_cursor_func.vim
@@ -40,6 +40,18 @@ func Test_move_cursor()
quit!
endfunc
+func Test_curswant_maxcol()
+ new
+ call setline(1, 'foo')
+
+ " Test that after "$" command curswant is set to the same value as v:maxcol.
+ normal! 1G$
+ call assert_equal(v:maxcol, getcurpos()[4])
+ call assert_equal(v:maxcol, winsaveview().curswant)
+
+ quit!
+endfunc
+
" Very short version of what matchparen does.
function s:Highlight_Matching_Pair()
let save_cursor = getcurpos()
@@ -113,7 +125,74 @@ func Test_screenpos()
\ 'curscol': wincol + 7,
\ 'endcol': wincol + 7}, winid->screenpos(line('$'), 8))
call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
- \ winid->screenpos(line('$'), 22))
+ \ winid->screenpos(line('$'), 22))
+
+ 1split
+
+ " w_leftcol should be subtracted
+ setlocal nowrap
+ normal G050zl$
+ redraw
+ call assert_equal({'row': winrow + 0,
+ \ 'col': wincol + 10 - 1,
+ \ 'curscol': wincol + 10 - 1,
+ \ 'endcol': wincol + 10 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+
+ " w_skipcol should be taken into account
+ setlocal wrap
+ normal $
+ redraw
+ call assert_equal({'row': winrow + 0,
+ \ 'col': wincol + 20 - 1,
+ \ 'curscol': wincol + 20 - 1,
+ \ 'endcol': wincol + 20 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+ call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
+ \ screenpos(win_getid(), line('.'), col('.') - 20))
+ setlocal number
+ redraw
+ call assert_equal({'row': winrow + 0,
+ \ 'col': wincol + 16 - 1,
+ \ 'curscol': wincol + 16 - 1,
+ \ 'endcol': wincol + 16 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+ call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
+ \ screenpos(win_getid(), line('.'), col('.') - 16))
+ set cpoptions+=n
+ redraw
+ call assert_equal({'row': winrow + 0,
+ \ 'col': wincol + 4 - 1,
+ \ 'curscol': wincol + 4 - 1,
+ \ 'endcol': wincol + 4 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+ call assert_equal({'row': 0, 'col': 0, 'curscol': 0, 'endcol': 0},
+ \ screenpos(win_getid(), line('.'), col('.') - 4))
+
+ wincmd +
+ call setline(line('$') + 1, 'last line')
+ setlocal smoothscroll
+ normal G$
+ redraw
+ call assert_equal({'row': winrow + 1,
+ \ 'col': wincol + 4 + 9 - 1,
+ \ 'curscol': wincol + 4 + 9 - 1,
+ \ 'endcol': wincol + 4 + 9 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+ set cpoptions-=n
+ redraw
+ call assert_equal({'row': winrow + 1,
+ \ 'col': wincol + 4 + 9 - 1,
+ \ 'curscol': wincol + 4 + 9 - 1,
+ \ 'endcol': wincol + 4 + 9 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
+ setlocal nonumber
+ redraw
+ call assert_equal({'row': winrow + 1,
+ \ 'col': wincol + 9 - 1,
+ \ 'curscol': wincol + 9 - 1,
+ \ 'endcol': wincol + 9 - 1},
+ \ screenpos(win_getid(), line('.'), col('.')))
close
call assert_equal({}, screenpos(999, 1, 1))
@@ -158,6 +237,19 @@ func Test_screenpos_diff()
windo diffthis
wincmd w
call assert_equal(#{col: 3, row: 7, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+ call assert_equal(#{col: 3, row: 8, endcol: 3, curscol: 3}, screenpos(0, 5, 1))
+ exe "normal! 3\<C-E>"
+ call assert_equal(#{col: 3, row: 4, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+ call assert_equal(#{col: 3, row: 5, endcol: 3, curscol: 3}, screenpos(0, 5, 1))
+ exe "normal! \<C-E>"
+ call assert_equal(#{col: 3, row: 3, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+ call assert_equal(#{col: 3, row: 4, endcol: 3, curscol: 3}, screenpos(0, 5, 1))
+ exe "normal! \<C-E>"
+ call assert_equal(#{col: 3, row: 2, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+ call assert_equal(#{col: 3, row: 3, endcol: 3, curscol: 3}, screenpos(0, 5, 1))
+ exe "normal! \<C-E>"
+ call assert_equal(#{col: 3, row: 1, endcol: 3, curscol: 3}, screenpos(0, 4, 1))
+ call assert_equal(#{col: 3, row: 2, endcol: 3, curscol: 3}, screenpos(0, 5, 1))
windo diffoff
bwipe!
@@ -472,9 +564,42 @@ func Test_virtcol2col()
call assert_equal(-1, virtcol2col(0, -1, 1))
call assert_equal(-1, virtcol2col(0, 1, -1))
call assert_equal(5, virtcol2col(0, 1, 20))
+
+ " Multibyte character
+ call setline(1, ['a✅✅✅'])
+ call assert_equal(1, virtcol2col(0, 1, 1))
+ call assert_equal(2, virtcol2col(0, 1, 3))
+ call assert_equal(5, virtcol2col(0, 1, 5))
+ call assert_equal(8, virtcol2col(0, 1, 7))
+ call assert_equal(8, virtcol2col(0, 1, 8))
+
+ " These used to cause invalid memory access
+ call setline(1, '')
+ call assert_equal(0, virtcol2col(0, 1, 1))
+ call assert_equal(0, virtcol2col(0, 1, 2))
+
+ let w = winwidth(0)
+ call setline(2, repeat('a', w + 2))
+ let win_nosbr = win_getid()
+ split
+ setlocal showbreak=!!
+ let win_sbr = win_getid()
+ call assert_equal(w, virtcol2col(win_nosbr, 2, w))
+ call assert_equal(w + 1, virtcol2col(win_nosbr, 2, w + 1))
+ call assert_equal(w + 2, virtcol2col(win_nosbr, 2, w + 2))
+ call assert_equal(w + 2, virtcol2col(win_nosbr, 2, w + 3))
+ call assert_equal(w, virtcol2col(win_sbr, 2, w))
+ call assert_equal(w + 1, virtcol2col(win_sbr, 2, w + 1))
+ call assert_equal(w + 1, virtcol2col(win_sbr, 2, w + 2))
+ call assert_equal(w + 1, virtcol2col(win_sbr, 2, w + 3))
+ call assert_equal(w + 2, virtcol2col(win_sbr, 2, w + 4))
+ call assert_equal(w + 2, virtcol2col(win_sbr, 2, w + 5))
+ close
+
call assert_fails('echo virtcol2col("0", 1, 20)', 'E1210:')
call assert_fails('echo virtcol2col(0, "1", 20)', 'E1210:')
call assert_fails('echo virtcol2col(0, 1, "1")', 'E1210:')
+
bw!
endfunc
diff --git a/src/nvim/testdir/test_cursorline.vim b/test/old/testdir/test_cursorline.vim
index 70f39a8601..99a812b1de 100644
--- a/src/nvim/testdir/test_cursorline.vim
+++ b/test/old/testdir/test_cursorline.vim
@@ -136,61 +136,45 @@ func Test_cursorline_screenline()
call writefile(lines, filename)
" basic test
let buf = RunVimInTerminal('-S '. filename, #{rows: 20})
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_1', {})
call term_sendkeys(buf, "fagj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_2', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_3', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_4', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_5', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_6', {})
" test with set list and cursorlineopt containing number
call term_sendkeys(buf, "gg0")
call term_sendkeys(buf, ":set list cursorlineopt+=number listchars=space:-\<cr>")
call VerifyScreenDump(buf, 'Test_'. filename. '_7', {})
call term_sendkeys(buf, "fagj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_8', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_9', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_10', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_11', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_12', {})
if exists("+foldcolumn") && exists("+signcolumn") && exists("+breakindent")
- " test with set foldcolumn signcoloumn and breakindent
+ " test with set foldcolumn signcolumn and breakindent
call term_sendkeys(buf, "gg0")
call term_sendkeys(buf, ":set breakindent foldcolumn=2 signcolumn=yes\<cr>")
call VerifyScreenDump(buf, 'Test_'. filename. '_13', {})
call term_sendkeys(buf, "fagj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_14', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_15', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_16', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_17', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_18', {})
call term_sendkeys(buf, ":set breakindent& foldcolumn& signcolumn&\<cr>")
endif
@@ -200,19 +184,14 @@ func Test_cursorline_screenline()
call term_sendkeys(buf, ":set nonumber\<cr>")
call VerifyScreenDump(buf, 'Test_'. filename. '_19', {})
call term_sendkeys(buf, "fagj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_20', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_21', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_22', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_23', {})
call term_sendkeys(buf, "gj")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_'. filename. '_24', {})
call term_sendkeys(buf, ":set list& cursorlineopt& listchars&\<cr>")
diff --git a/src/nvim/testdir/test_curswant.vim b/test/old/testdir/test_curswant.vim
index e54cd4b280..e54cd4b280 100644
--- a/src/nvim/testdir/test_curswant.vim
+++ b/test/old/testdir/test_curswant.vim
diff --git a/src/nvim/testdir/test_debugger.vim b/test/old/testdir/test_debugger.vim
index f5177c8fb2..ad03443cb4 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/test/old/testdir/test_debugger.vim
@@ -36,7 +36,7 @@ endfunc
" If the expected output argument is supplied, then check for it.
func RunDbgCmd(buf, cmd, ...)
call term_sendkeys(a:buf, a:cmd . "\r")
- call term_wait(a:buf, 20)
+ call TermWait(a:buf)
if a:0 != 0
let options = #{match: 'equal'}
@@ -73,11 +73,18 @@ func Test_Debugger()
endtry
return var1
endfunc
+ def Vim9Func()
+ for cmd in ['confirm', 'xxxxxxx']
+ for _ in [1, 2]
+ echo cmd
+ endfor
+ endfor
+ enddef
END
- call writefile(lines, 'Xtest.vim')
+ call writefile(lines, 'XtestDebug.vim', 'D')
" Start Vim in a terminal
- let buf = RunVimInTerminal('-S Xtest.vim', {})
+ let buf = RunVimInTerminal('-S XtestDebug.vim', {})
" Start the Vim debugger
call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()'])
@@ -298,6 +305,14 @@ func Test_Debugger()
\ 'line 5: catch'])
call RunDbgCmd(buf, 'c')
+ " Test showing local variable in :def function
+ call RunDbgCmd(buf, ':breakadd func 2 Vim9Func')
+ call RunDbgCmd(buf, ':call Vim9Func()', ['line 2: for _ in [1, 2]'])
+ call RunDbgCmd(buf, 'next', ['line 2: for _ in [1, 2]'])
+ call RunDbgCmd(buf, 'echo cmd', ['confirm'])
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'cont')
+
" Test for :quit
call RunDbgCmd(buf, ':debug echo Foo()')
call RunDbgCmd(buf, 'breakdel *')
@@ -329,25 +344,58 @@ func Test_Debugger_breakadd()
let var3 = 30
let var4 = 40
END
- call writefile(lines, 'Xtest.vim')
+ call writefile(lines, 'XdebugBreakadd.vim', 'D')
" Start Vim in a terminal
- let buf = RunVimInTerminal('Xtest.vim', {})
- call RunDbgCmd(buf, ':breakadd file 2 Xtest.vim')
+ let buf = RunVimInTerminal('XdebugBreakadd.vim', {})
+ call RunDbgCmd(buf, ':breakadd file 2 XdebugBreakadd.vim')
call RunDbgCmd(buf, ':4 | breakadd here')
- call RunDbgCmd(buf, ':source Xtest.vim', ['line 2: let var2 = 20'])
+ call RunDbgCmd(buf, ':source XdebugBreakadd.vim', ['line 2: let var2 = 20'])
call RunDbgCmd(buf, 'cont', ['line 4: let var4 = 40'])
call RunDbgCmd(buf, 'cont')
call StopVimInTerminal(buf)
- call delete('Xtest.vim')
%bw!
call assert_fails('breakadd here', 'E32:')
call assert_fails('breakadd file Xtest.vim /\)/', 'E55:')
endfunc
+" Test for expression breakpoint set using ":breakadd expr <expr>"
+func Test_Debugger_breakadd_expr()
+ CheckRunVimInTerminal
+ CheckCWD
+
+ let lines =<< trim END
+ let g:Xtest_var += 1
+ END
+ call writefile(lines, 'XdebugBreakExpr.vim', 'D')
+
+ " Start Vim in a terminal
+ let buf = RunVimInTerminal('XdebugBreakExpr.vim', {})
+ call RunDbgCmd(buf, ':let g:Xtest_var = 10')
+ call RunDbgCmd(buf, ':breakadd expr g:Xtest_var')
+ call RunDbgCmd(buf, ':source %')
+ let expected =<< trim eval END
+ Oldval = "10"
+ Newval = "11"
+ {fnamemodify('XdebugBreakExpr.vim', ':p')}
+ line 1: let g:Xtest_var += 1
+ END
+ call RunDbgCmd(buf, ':source %', expected)
+ call RunDbgCmd(buf, 'cont')
+ let expected =<< trim eval END
+ Oldval = "11"
+ Newval = "12"
+ {fnamemodify('XdebugBreakExpr.vim', ':p')}
+ line 1: let g:Xtest_var += 1
+ END
+ call RunDbgCmd(buf, ':source %', expected)
+
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_Backtrace_Through_Source()
CheckRunVimInTerminal
CheckCWD
@@ -365,7 +413,7 @@ func Test_Backtrace_Through_Source()
call CallAFunction()
endfunc
END
- call writefile(file1, 'Xtest1.vim')
+ call writefile(file1, 'Xtest1.vim', 'D')
let file2 =<< trim END
func DoAThing()
@@ -378,7 +426,7 @@ func Test_Backtrace_Through_Source()
call File2Function()
END
- call writefile(file2, 'Xtest2.vim')
+ call writefile(file2, 'Xtest2.vim', 'D')
let buf = RunVimInTerminal('-S Xtest1.vim', {})
@@ -520,8 +568,6 @@ func Test_Backtrace_Through_Source()
\ 'line 1: call DoAThing()'])
call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
endfunc
func Test_Backtrace_Autocmd()
@@ -543,7 +589,7 @@ func Test_Backtrace_Autocmd()
au User TestGlobalFunction :call GlobalFunction() | echo "Done"
END
- call writefile(file1, 'Xtest1.vim')
+ call writefile(file1, 'Xtest1.vim', 'D')
let file2 =<< trim END
func DoAThing()
@@ -556,7 +602,7 @@ func Test_Backtrace_Autocmd()
call File2Function()
END
- call writefile(file2, 'Xtest2.vim')
+ call writefile(file2, 'Xtest2.vim', 'D')
let buf = RunVimInTerminal('-S Xtest1.vim', {})
@@ -776,8 +822,6 @@ func Test_Backtrace_Autocmd()
\ 'cmd: echo "Done"'])
call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
endfunc
func Test_Backtrace_CmdLine()
@@ -799,7 +843,7 @@ func Test_Backtrace_CmdLine()
au User TestGlobalFunction :call GlobalFunction() | echo "Done"
END
- call writefile(file1, 'Xtest1.vim')
+ call writefile(file1, 'Xtest1.vim', 'D')
let file2 =<< trim END
func DoAThing()
@@ -812,7 +856,7 @@ func Test_Backtrace_CmdLine()
call File2Function()
END
- call writefile(file2, 'Xtest2.vim')
+ call writefile(file2, 'Xtest2.vim', 'D')
let buf = RunVimInTerminal(
\ '-S Xtest1.vim -c "debug call GlobalFunction()"',
@@ -838,8 +882,6 @@ func Test_Backtrace_CmdLine()
\ 'line 1: call CallAFunction()'])
call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
endfunc
func Test_Backtrace_DefFunction()
@@ -847,7 +889,7 @@ func Test_Backtrace_DefFunction()
CheckCWD
let file1 =<< trim END
vim9script
- import File2Function from './Xtest2.vim'
+ import './Xtest2.vim' as imp
def SourceAnotherFile()
source Xtest2.vim
@@ -855,16 +897,17 @@ func Test_Backtrace_DefFunction()
def CallAFunction()
SourceAnotherFile()
- File2Function()
+ imp.File2Function()
enddef
def g:GlobalFunction()
+ var some = "some var"
CallAFunction()
enddef
defcompile
END
- call writefile(file1, 'Xtest1.vim')
+ call writefile(file1, 'Xtest1.vim', 'D')
let file2 =<< trim END
vim9script
@@ -882,7 +925,7 @@ func Test_Backtrace_DefFunction()
defcompile
File2Function()
END
- call writefile(file2, 'Xtest2.vim')
+ call writefile(file2, 'Xtest2.vim', 'D')
let buf = RunVimInTerminal('-S Xtest1.vim', {})
@@ -890,19 +933,22 @@ func Test_Backtrace_DefFunction()
\ ':debug call GlobalFunction()',
\ ['cmd: call GlobalFunction()'])
- " FIXME: Vim9 lines are not debugged!
- call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+ call RunDbgCmd(buf, 'step', ['line 1: var some = "some var"'])
+ call RunDbgCmd(buf, 'step', ['line 2: CallAFunction()'])
+ call RunDbgCmd(buf, 'echo some', ['some var'])
- " But they do appear in the backtrace
call RunDbgCmd(buf, 'backtrace', [
\ '\V>backtrace',
- \ '\V 2 function GlobalFunction[1]',
- \ '\V 1 <SNR>\.\*_CallAFunction[1]',
- \ '\V->0 <SNR>\.\*_SourceAnotherFile',
- \ '\Vline 1: source Xtest2.vim'],
+ \ '\V->0 function GlobalFunction',
+ \ '\Vline 2: CallAFunction()',
+ \ ],
\ #{match: 'pattern'})
-
+ call RunDbgCmd(buf, 'step', ['line 1: SourceAnotherFile()'])
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
+ " Repeated line, because we fist are in the compiled function before the
+ " EXEC and then in do_cmdline() before the :source command.
+ call RunDbgCmd(buf, 'step', ['line 1: source Xtest2.vim'])
call RunDbgCmd(buf, 'step', ['line 1: vim9script'])
call RunDbgCmd(buf, 'step', ['line 3: def DoAThing(): number'])
call RunDbgCmd(buf, 'step', ['line 9: export def File2Function()'])
@@ -911,7 +957,7 @@ func Test_Backtrace_DefFunction()
call RunDbgCmd(buf, 'step', ['line 14: File2Function()'])
call RunDbgCmd(buf, 'backtrace', [
\ '\V>backtrace',
- \ '\V 3 function GlobalFunction[1]',
+ \ '\V 3 function GlobalFunction[2]',
\ '\V 2 <SNR>\.\*_CallAFunction[1]',
\ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
\ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
@@ -919,20 +965,234 @@ func Test_Backtrace_DefFunction()
\ #{match: 'pattern'})
" Don't step into compiled functions...
- call RunDbgCmd(buf, 'step', ['line 15: End of sourced file'])
+ call RunDbgCmd(buf, 'next', ['line 15: End of sourced file'])
call RunDbgCmd(buf, 'backtrace', [
\ '\V>backtrace',
- \ '\V 3 function GlobalFunction[1]',
+ \ '\V 3 function GlobalFunction[2]',
\ '\V 2 <SNR>\.\*_CallAFunction[1]',
\ '\V 1 <SNR>\.\*_SourceAnotherFile[1]',
\ '\V->0 script ' .. getcwd() .. '/Xtest2.vim',
\ '\Vline 15: End of sourced file'],
\ #{match: 'pattern'})
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_DefFunction_expr()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file3 =<< trim END
+ vim9script
+ g:someVar = "foo"
+ def g:ChangeVar()
+ g:someVar = "bar"
+ echo "changed"
+ enddef
+ defcompile
+ END
+ call writefile(file3, 'Xtest3.vim', 'D')
+ let buf = RunVimInTerminal('-S Xtest3.vim', {})
+
+ call RunDbgCmd(buf, ':breakadd expr g:someVar')
+ call RunDbgCmd(buf, ':call g:ChangeVar()', ['Oldval = "''foo''"', 'Newval = "''bar''"', 'function ChangeVar', 'line 2: echo "changed"'])
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_and_legacy_function()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file =<< trim END
+ vim9script
+ def g:SomeFunc()
+ echo "here"
+ echo "and"
+ echo "there"
+ breakadd func 2 LocalFunc
+ LocalFunc()
+ enddef
+
+ def LocalFunc()
+ echo "first"
+ echo "second"
+ breakadd func LegacyFunc
+ LegacyFunc()
+ enddef
+
+ func LegacyFunc()
+ echo "legone"
+ echo "legtwo"
+ endfunc
+
+ breakadd func 2 g:SomeFunc
+ END
+ call writefile(file, 'XtestDebug.vim', 'D')
+
+ let buf = RunVimInTerminal('-S XtestDebug.vim', {})
+
+ call RunDbgCmd(buf,':call SomeFunc()', ['line 2: echo "and"'])
+ call RunDbgCmd(buf,'next', ['line 3: echo "there"'])
+ call RunDbgCmd(buf,'next', ['line 4: breakadd func 2 LocalFunc'])
+
+ " continue, next breakpoint is in LocalFunc()
+ call RunDbgCmd(buf,'cont', ['line 2: echo "second"'])
+
+ " continue, next breakpoint is in LegacyFunc()
+ call RunDbgCmd(buf,'cont', ['line 1: echo "legone"'])
+
+ call RunDbgCmd(buf, 'cont')
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_function()
+ CheckRunVimInTerminal
+ CheckCWD
+ let file =<< trim END
+ vim9script
+ def g:Func()
+ var n: number
+ def Closure(): number
+ return n + 3
+ enddef
+ n += Closure()
+ echo 'result: ' .. n
+ enddef
+
+ def g:FuncWithArgs(text: string, nr: number, ...items: list<number>)
+ echo text .. nr
+ for it in items
+ echo it
+ endfor
+ echo "done"
+ enddef
+
+ def g:FuncWithDict()
+ var d = {
+ a: 1,
+ b: 2,
+ }
+ # comment
+ def Inner()
+ eval 1 + 2
+ enddef
+ enddef
+
+ def g:FuncComment()
+ # comment
+ echo "first"
+ .. "one"
+ # comment
+ echo "second"
+ enddef
+
+ def g:FuncForLoop()
+ eval 1 + 2
+ for i in [11, 22, 33]
+ eval i + 2
+ endfor
+ echo "done"
+ enddef
+
+ def g:FuncWithSplitLine()
+ eval 1 + 2
+ | eval 2 + 3
+ enddef
+ END
+ call writefile(file, 'Xtest.vim', 'D')
+
+ let buf = RunVimInTerminal('-S Xtest.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':debug call Func()',
+ \ ['cmd: call Func()'])
+ call RunDbgCmd(buf, 'next', ['result: 3'])
+ call term_sendkeys(buf, "\r")
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf,
+ \ ':debug call FuncWithArgs("asdf", 42, 1, 2, 3)',
+ \ ['cmd: call FuncWithArgs("asdf", 42, 1, 2, 3)'])
+ call RunDbgCmd(buf, 'step', ['line 1: echo text .. nr'])
+ call RunDbgCmd(buf, 'echo text', ['asdf'])
+ call RunDbgCmd(buf, 'echo nr', ['42'])
+ call RunDbgCmd(buf, 'echo items', ['[1, 2, 3]'])
+ call RunDbgCmd(buf, 'step', ['asdf42', 'function FuncWithArgs', 'line 2: for it in items'])
+ call RunDbgCmd(buf, 'step', ['function FuncWithArgs', 'line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['0'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'echo it', ['1'])
+ call RunDbgCmd(buf, 'step', ['1', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['1'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'step', ['2', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'echo it', ['2'])
+ call RunDbgCmd(buf, 'step', ['line 3: echo it'])
+ call RunDbgCmd(buf, 'step', ['3', 'function FuncWithArgs', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'step', ['line 2: for it in items'])
+ call RunDbgCmd(buf, 'step', ['line 5: echo "done"'])
+ call RunDbgCmd(buf, 'cont')
+ call RunDbgCmd(buf,
+ \ ':debug call FuncWithDict()',
+ \ ['cmd: call FuncWithDict()'])
+ call RunDbgCmd(buf, 'step', ['line 1: var d = { a: 1, b: 2, }'])
+ call RunDbgCmd(buf, 'step', ['line 6: def Inner()'])
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func 1 FuncComment')
+ call RunDbgCmd(buf, ':call FuncComment()', ['function FuncComment', 'line 2: echo "first" .. "one"'])
+ call RunDbgCmd(buf, ':breakadd func 3 FuncComment')
+ call RunDbgCmd(buf, 'cont', ['function FuncComment', 'line 5: echo "second"'])
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func 2 FuncForLoop')
+ call RunDbgCmd(buf, ':call FuncForLoop()', ['function FuncForLoop', 'line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'step', ['line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 3: eval i + 2'])
+ call RunDbgCmd(buf, 'echo i', ['11'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 4: endfor'])
+ call RunDbgCmd(buf, 'next', ['function FuncForLoop', 'line 2: for i in [11, 22, 33]'])
+ call RunDbgCmd(buf, 'next', ['line 3: eval i + 2'])
+ call RunDbgCmd(buf, 'echo i', ['22'])
+
+ call RunDbgCmd(buf, 'breakdel *')
+ call RunDbgCmd(buf, 'cont')
+
+ call RunDbgCmd(buf, ':breakadd func FuncWithSplitLine')
+ call RunDbgCmd(buf, ':call FuncWithSplitLine()', ['function FuncWithSplitLine', 'line 1: eval 1 + 2 | eval 2 + 3'])
+
+ call RunDbgCmd(buf, 'cont')
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_debug_def_function_with_lambda()
+ CheckRunVimInTerminal
+ CheckCWD
+ let lines =<< trim END
+ vim9script
+ def g:Func()
+ var s = 'a'
+ ['b']->map((_, v) => s)
+ echo "done"
+ enddef
+ breakadd func 2 g:Func
+ END
+ call writefile(lines, 'XtestLambda.vim', 'D')
+
+ let buf = RunVimInTerminal('-S XtestLambda.vim', {})
+
+ call RunDbgCmd(buf,
+ \ ':call g:Func()',
+ \ ['function Func', 'line 2: [''b'']->map((_, v) => s)'])
+ call RunDbgCmd(buf,
+ \ 'next',
+ \ ['function Func', 'line 3: echo "done"'])
+
+ call RunDbgCmd(buf, 'cont')
call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
endfunc
func Test_debug_backtrace_level()
@@ -951,7 +1211,7 @@ func Test_debug_backtrace_level()
call s:File1Func( 'arg1' )
END
- call writefile(lines, 'Xtest1.vim')
+ call writefile(lines, 'Xtest1.vim', 'D')
let lines =<< trim END
let s:file2_var = 'file2'
@@ -964,7 +1224,7 @@ func Test_debug_backtrace_level()
call s:File2Func( 'arg2' )
END
- call writefile(lines, 'Xtest2.vim')
+ call writefile(lines, 'Xtest2.vim', 'D')
let file1 = getcwd() .. '/Xtest1.vim'
let file2 = getcwd() .. '/Xtest2.vim'
@@ -1123,9 +1383,8 @@ func Test_debug_backtrace_level()
\ [ 'E121: Undefined variable: s:file1_var' ] )
call RunDbgCmd(buf, 'echo s:file2_var', [ 'file2' ] )
+ call RunDbgCmd(buf, 'cont')
call StopVimInTerminal(buf)
- call delete('Xtest1.vim')
- call delete('Xtest2.vim')
endfunc
" Test for setting a breakpoint on a :endif where the :if condition is false
diff --git a/test/old/testdir/test_delete.vim b/test/old/testdir/test_delete.vim
new file mode 100644
index 0000000000..c00b8ca47d
--- /dev/null
+++ b/test/old/testdir/test_delete.vim
@@ -0,0 +1,133 @@
+" Test for delete().
+
+source check.vim
+source term_util.vim
+source screendump.vim
+
+func Test_file_delete()
+ split Xfdelfile
+ call setline(1, ['a', 'b'])
+ wq
+ call assert_equal(['a', 'b'], readfile('Xfdelfile'))
+ call assert_equal(0, delete('Xfdelfile'))
+ call assert_fails('call readfile("Xfdelfile")', 'E484:')
+ call assert_equal(-1, delete('Xfdelfile'))
+ bwipe Xfdelfile
+endfunc
+
+func Test_dir_delete()
+ call mkdir('Xdirdel')
+ call assert_true(isdirectory('Xdirdel'))
+ call assert_equal(0, delete('Xdirdel', 'd'))
+ call assert_false(isdirectory('Xdirdel'))
+ call assert_equal(-1, delete('Xdirdel', 'd'))
+endfunc
+
+func Test_recursive_delete()
+ call mkdir('Xrecdel')
+ call mkdir('Xrecdel/subdir')
+ call mkdir('Xrecdel/empty')
+ split Xrecdel/Xfile
+ call setline(1, ['a', 'b'])
+ w
+ w Xrecdel/subdir/Xfile
+ close
+ call assert_true(isdirectory('Xrecdel'))
+ call assert_equal(['a', 'b'], readfile('Xrecdel/Xfile'))
+ call assert_true(isdirectory('Xrecdel/subdir'))
+ call assert_equal(['a', 'b'], readfile('Xrecdel/subdir/Xfile'))
+ call assert_true('Xrecdel/empty'->isdirectory())
+ call assert_equal(0, delete('Xrecdel', 'rf'))
+ call assert_false(isdirectory('Xrecdel'))
+ call assert_equal(-1, delete('Xrecdel', 'd'))
+ bwipe Xrecdel/Xfile
+ bwipe Xrecdel/subdir/Xfile
+endfunc
+
+func Test_symlink_delete()
+ CheckUnix
+ split Xslfile
+ call setline(1, ['a', 'b'])
+ wq
+ silent !ln -s Xslfile Xdellink
+ " Delete the link, not the file
+ call assert_equal(0, delete('Xdellink'))
+ call assert_equal(-1, delete('Xdellink'))
+ call assert_equal(0, delete('Xslfile'))
+ bwipe Xslfile
+endfunc
+
+func Test_symlink_dir_delete()
+ CheckUnix
+ call mkdir('Xsymdir')
+ silent !ln -s Xsymdir Xdirlink
+ call assert_true(isdirectory('Xsymdir'))
+ call assert_true(isdirectory('Xdirlink'))
+ " Delete the link, not the directory
+ call assert_equal(0, delete('Xdirlink'))
+ call assert_equal(-1, delete('Xdirlink'))
+ call assert_equal(0, delete('Xsymdir', 'd'))
+endfunc
+
+func Test_symlink_recursive_delete()
+ CheckUnix
+ call mkdir('Xrecdir3')
+ call mkdir('Xrecdir3/subdir')
+ call mkdir('Xrecdir4')
+ split Xrecdir3/Xfile
+ call setline(1, ['a', 'b'])
+ w
+ w Xrecdir3/subdir/Xfile
+ w Xrecdir4/Xfile
+ close
+ silent !ln -s ../Xrecdir4 Xrecdir3/Xreclink
+
+ call assert_true(isdirectory('Xrecdir3'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir3/Xfile'))
+ call assert_true(isdirectory('Xrecdir3/subdir'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir3/subdir/Xfile'))
+ call assert_true(isdirectory('Xrecdir4'))
+ call assert_true(isdirectory('Xrecdir3/Xreclink'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir4/Xfile'))
+
+ call assert_equal(0, delete('Xrecdir3', 'rf'))
+ call assert_false(isdirectory('Xrecdir3'))
+ call assert_equal(-1, delete('Xrecdir3', 'd'))
+ " symlink is deleted, not the directory it points to
+ call assert_true(isdirectory('Xrecdir4'))
+ call assert_equal(['a', 'b'], readfile('Xrecdir4/Xfile'))
+ call assert_equal(0, delete('Xrecdir4/Xfile'))
+ call assert_equal(0, delete('Xrecdir4', 'd'))
+
+ bwipe Xrecdir3/Xfile
+ bwipe Xrecdir3/subdir/Xfile
+ bwipe Xrecdir4/Xfile
+endfunc
+
+func Test_delete_errors()
+ call assert_fails('call delete('''')', 'E474:')
+ call assert_fails('call delete(''foo'', 0)', 'E15:')
+endfunc
+
+" This should no longer trigger ml_get errors
+func Test_delete_ml_get_errors()
+ CheckRunVimInTerminal
+ let lines =<< trim END
+ set noshowcmd noruler scrolloff=0
+ source samples/matchparen.vim
+ END
+ call writefile(lines, 'XDelete_ml_get_error', 'D')
+ let buf = RunVimInTerminal('-S XDelete_ml_get_error samples/box.txt', #{rows: 10, wait_for_ruler: 0})
+ call TermWait(buf)
+ call term_sendkeys(buf, "249GV\<C-End>d")
+ call TermWait(buf)
+ " The following used to trigger ml_get errors
+ call term_sendkeys(buf, "\<PageUp>")
+ call TermWait(buf)
+ call term_sendkeys(buf, ":mess\<cr>")
+ call VerifyScreenDump(buf, 'Test_delete_ml_get_errors_1', {})
+ call term_sendkeys(buf, ":q!\<cr>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim
index 0049398776..fd94f4a7b2 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/test/old/testdir/test_diffmode.vim
@@ -822,12 +822,13 @@ func Test_diff_lastline()
endfunc
func WriteDiffFiles(buf, list1, list2)
- call writefile(a:list1, 'Xfile1')
- call writefile(a:list2, 'Xfile2')
+ call writefile(a:list1, 'Xdifile1')
+ call writefile(a:list2, 'Xdifile2')
if a:buf
call term_sendkeys(a:buf, ":checktime\<CR>")
endif
endfunc
+
" Verify a screendump with both the internal and external diff.
func VerifyBoth(buf, dumpfile, extra)
" trailing : for leaving the cursor on the command line
@@ -852,11 +853,11 @@ func VerifyInternal(buf, dumpfile, extra)
call term_sendkeys(a:buf, ":diffupdate!\<CR>")
" trailing : for leaving the cursor on the command line
call term_sendkeys(a:buf, ":set diffopt=internal,filler" . a:extra . "\<CR>:")
- call TermWait(a:buf)
call VerifyScreenDump(a:buf, a:dumpfile, {})
endfunc
func Test_diff_screen()
+ let g:test_is_flaky = 1
CheckScreendump
CheckFeature menu
@@ -874,15 +875,15 @@ func Test_diff_screen()
set diffexpr=
endfunc
END
- call writefile(lines, 'XdiffSetup')
+ call writefile(lines, 'XdiffSetup', 'D')
" clean up already existing swap files, just in case
- call delete('.Xfile1.swp')
- call delete('.Xfile2.swp')
+ call delete('.Xdifile1.swp')
+ call delete('.Xdifile2.swp')
" Test 1: Add a line in beginning of file 2
call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
- let buf = RunVimInTerminal('-d -S XdiffSetup Xfile1 Xfile2', {})
+ let buf = RunVimInTerminal('-d -S XdiffSetup Xdifile1 Xdifile2', {})
" Set autoread mode, so that Vim won't complain once we re-write the test
" files
call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
@@ -1000,9 +1001,8 @@ func Test_diff_screen()
" clean up
call StopVimInTerminal(buf)
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('XdiffSetup')
+ call delete('Xdifile1')
+ call delete('Xdifile2')
endfunc
func Test_diff_with_scroll_and_change()
@@ -1016,7 +1016,7 @@ func Test_diff_with_scroll_and_change()
wincmd h
exe "normal Gl5\<C-E>"
END
- call writefile(lines, 'Xtest_scroll_change')
+ call writefile(lines, 'Xtest_scroll_change', 'D')
let buf = RunVimInTerminal('-S Xtest_scroll_change', {})
call VerifyScreenDump(buf, 'Test_diff_scroll_change_01', {})
@@ -1029,7 +1029,6 @@ func Test_diff_with_scroll_and_change()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_scroll_change')
endfunc
func Test_diff_with_cursorline()
@@ -1043,7 +1042,7 @@ func Test_diff_with_cursorline()
\ 'call setline(1, ["bee","foo","foo","baz"])',
\ 'windo diffthis',
\ '2wincmd w',
- \ ], 'Xtest_diff_cursorline')
+ \ ], 'Xtest_diff_cursorline', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_cursorline', {})
call VerifyScreenDump(buf, 'Test_diff_with_cursorline_01', {})
@@ -1054,7 +1053,6 @@ func Test_diff_with_cursorline()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline')
endfunc
func Test_diff_with_cursorline_number()
@@ -1071,7 +1069,7 @@ func Test_diff_with_cursorline_number()
windo diffthis
1wincmd w
END
- call writefile(lines, 'Xtest_diff_cursorline_number')
+ call writefile(lines, 'Xtest_diff_cursorline_number', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_cursorline_number', {})
call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_01', {})
@@ -1080,7 +1078,6 @@ func Test_diff_with_cursorline_number()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline_number')
endfunc
func Test_diff_with_cursorline_breakindent()
@@ -1097,7 +1094,7 @@ func Test_diff_with_cursorline_breakindent()
\ 'exe "norm 20Abee\<Esc>j20Afoo\<Esc>j20Afoo\<Esc>j20Abaz\<Esc>"',
\ 'windo diffthis',
\ '2wincmd w',
- \ ], 'Xtest_diff_cursorline_breakindent')
+ \ ], 'Xtest_diff_cursorline_breakindent', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_cursorline_breakindent', {})
call term_sendkeys(buf, "gg0")
@@ -1111,7 +1108,6 @@ func Test_diff_with_cursorline_breakindent()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_cursorline_breakindent')
endfunc
func Test_diff_with_syntax()
@@ -1124,7 +1120,7 @@ func Test_diff_with_syntax()
return 5;
}
END
- call writefile(lines, 'Xprogram1.c')
+ call writefile(lines, 'Xprogram1.c', 'D')
let lines =<< trim END
void doSomething() {
int x = 0;
@@ -1132,22 +1128,19 @@ func Test_diff_with_syntax()
return 5;
}
END
- call writefile(lines, 'Xprogram2.c')
+ call writefile(lines, 'Xprogram2.c', 'D')
let lines =<< trim END
edit Xprogram1.c
diffsplit Xprogram2.c
END
- call writefile(lines, 'Xtest_diff_syntax')
+ call writefile(lines, 'Xtest_diff_syntax', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_syntax')
- call delete('Xprogram1.c')
- call delete('Xprogram2.c')
endfunc
func Test_diff_of_diff()
@@ -1159,7 +1152,9 @@ func Test_diff_of_diff()
\ 'vnew',
\ 'call setline(1, ["aa","bb","cc"])',
\ 'windo diffthis',
- \ ], 'Xtest_diff_diff')
+ \ '1wincmd w',
+ \ 'setlocal number',
+ \ ], 'Xtest_diff_diff', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_diff', {})
call VerifyScreenDump(buf, 'Test_diff_of_diff_01', {})
@@ -1169,7 +1164,6 @@ func Test_diff_of_diff()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_diff')
endfunc
func CloseoffSetup()
@@ -1251,8 +1245,8 @@ func Test_patchexpr()
endfunc
set patchexpr=TPatch()
- call writefile(['input file'], 'Xinput')
- call writefile(['diff file'], 'Xdiff')
+ call writefile(['input file'], 'Xinput', 'D')
+ call writefile(['diff file'], 'Xdiff', 'D')
%bwipe!
edit Xinput
diffpatch Xdiff
@@ -1269,8 +1263,6 @@ func Test_patchexpr()
set patchexpr=<SID>NewPatchExpr()
call assert_equal(expand('<SID>') .. 'NewPatchExpr()', &patchexpr)
- call delete('Xinput')
- call delete('Xdiff')
set patchexpr&
delfunc TPatch
delfunc s:NewPatchExpr
@@ -1290,7 +1282,7 @@ func Test_diff_rnu()
windo diffthis
setlocal number rnu foldcolumn=0
END
- call writefile(content, 'Xtest_diff_rnu')
+ call writefile(content, 'Xtest_diff_rnu', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_rnu', {})
call VerifyScreenDump(buf, 'Test_diff_rnu_01', {})
@@ -1302,7 +1294,6 @@ func Test_diff_rnu()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_rnu')
endfunc
func Test_diff_multilineconceal()
@@ -1350,7 +1341,7 @@ func Test_diff_filler_cursorcolumn()
norm! gg0
redraw!
END
- call writefile(content, 'Xtest_diff_cuc')
+ call writefile(content, 'Xtest_diff_cuc', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_cuc', {})
call VerifyScreenDump(buf, 'Test_diff_cuc_01', {})
@@ -1367,7 +1358,6 @@ func Test_diff_filler_cursorcolumn()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_cuc')
endfunc
" Test for adding/removing lines inside diff chunks, between diff chunks
@@ -1466,7 +1456,7 @@ func Test_diff_binary()
norm! gg0
redraw!
END
- call writefile(content, 'Xtest_diff_bin')
+ call writefile(content, 'Xtest_diff_bin', 'D')
let buf = RunVimInTerminal('-S Xtest_diff_bin', {})
" Test using internal diff
@@ -1487,7 +1477,6 @@ func Test_diff_binary()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_diff_bin')
set diffopt&vim
endfunc
@@ -1495,9 +1484,9 @@ endfunc
" for the issue fixed by patch 6.2.317)
func Test_diff_foldinvert()
%bw!
- edit Xfile1
- new Xfile2
- new Xfile3
+ edit Xdoffile1
+ new Xdoffile2
+ new Xdoffile3
windo diffthis
" open a non-diff window
botright new
@@ -1557,7 +1546,7 @@ func Test_diff_scroll()
// containing
// four lines
END
- call writefile(left, 'Xleft')
+ call writefile(left, 'Xleft', 'D')
let right =<< trim END
line 1
line 2
@@ -1593,7 +1582,7 @@ func Test_diff_scroll()
// containing
// four lines
END
- call writefile(right, 'Xright')
+ call writefile(right, 'Xright', 'D')
let buf = RunVimInTerminal('-d Xleft Xright', {'rows': 12})
call term_sendkeys(buf, "\<C-W>\<C-W>jjjj")
call VerifyScreenDump(buf, 'Test_diff_scroll_1', {})
@@ -1601,8 +1590,57 @@ func Test_diff_scroll()
call VerifyScreenDump(buf, 'Test_diff_scroll_2', {})
call StopVimInTerminal(buf)
- call delete('Xleft')
- call delete('Xright')
+endfunc
+
+" This was scrolling too many lines.
+func Test_diff_scroll_wrap_on()
+ 20new
+ 40vsplit
+ call setline(1, map(range(1, 9), 'repeat(v:val, 200)'))
+ setlocal number diff so=0
+ redraw
+ normal! jj
+ call assert_equal(1, winsaveview().topline)
+ normal! j
+ call assert_equal(2, winsaveview().topline)
+
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_diff_scroll_many_filler()
+ 20new
+ vnew
+ call setline(1, ['^^^', '^^^', '$$$', '$$$'])
+ diffthis
+ setlocal scrolloff=0
+ wincmd p
+ call setline(1, ['^^^', '^^^'] + repeat(['###'], 41) + ['$$$', '$$$'])
+ diffthis
+ setlocal scrolloff=0
+ wincmd p
+ redraw
+
+ " Note: need a redraw after each scroll, otherwise the test always passes.
+ normal! G
+ redraw
+ call assert_equal(3, winsaveview().topline)
+ call assert_equal(18, winsaveview().topfill)
+ exe "normal! \<C-B>"
+ redraw
+ call assert_equal(3, winsaveview().topline)
+ call assert_equal(19, winsaveview().topfill)
+ exe "normal! \<C-B>"
+ redraw
+ call assert_equal(2, winsaveview().topline)
+ call assert_equal(0, winsaveview().topfill)
+ exe "normal! \<C-B>"
+ redraw
+ call assert_equal(1, winsaveview().topline)
+ call assert_equal(0, winsaveview().topfill)
+
+ bwipe!
+ bwipe!
endfunc
" This was trying to update diffs for a buffer being closed
diff --git a/src/nvim/testdir/test_digraph.vim b/test/old/testdir/test_digraph.vim
index f08dff8605..8229248f7f 100644
--- a/src/nvim/testdir/test_digraph.vim
+++ b/test/old/testdir/test_digraph.vim
@@ -37,6 +37,9 @@ func Test_digraphs()
call Put_Dig("=P")
call Put_Dig("P=")
call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.')))
+ " Quadruple prime
+ call Put_Dig("'4")
+ call assert_equal("⁗", getline('.'))
" Not a digraph
call Put_Dig("a\<bs>")
call Put_Dig("\<bs>a")
@@ -51,7 +54,7 @@ func Test_digraphs()
call Put_Dig("'e")
call Put_Dig("b'") " not defined
call assert_equal(["á", "é", "'"], getline(line('.')-2,line('.')))
- " Cicumflex
+ " Circumflex
call Put_Dig("a>")
call Put_Dig(">e")
call Put_Dig("b>") " not defined
@@ -453,9 +456,7 @@ func Test_digraphs_output()
endfunc
func Test_loadkeymap()
- if !has('keymap')
- return
- endif
+ CheckFeature keymap
new
set keymap=czech
set iminsert=0
@@ -495,13 +496,10 @@ endfunc
" Test for error in a keymap file
func Test_loadkeymap_error()
- if !has('keymap')
- return
- endif
+ CheckFeature keymap
call assert_fails('loadkeymap', 'E105:')
- call writefile(['loadkeymap', 'a'], 'Xkeymap')
+ call writefile(['loadkeymap', 'a'], 'Xkeymap', 'D')
call assert_fails('source Xkeymap', 'E791:')
- call delete('Xkeymap')
endfunc
" Test for the characters displayed on the screen when entering a digraph
diff --git a/src/nvim/testdir/test_display.vim b/test/old/testdir/test_display.vim
index b642f39c9f..bd90287400 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/test/old/testdir/test_display.vim
@@ -79,7 +79,7 @@ func Test_scroll_without_region()
set t_cs=
set laststatus=2
END
- call writefile(lines, 'Xtestscroll')
+ call writefile(lines, 'Xtestscroll', 'D')
let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
@@ -103,11 +103,9 @@ func Test_scroll_without_region()
" clean up
call StopVimInTerminal(buf)
- call delete('Xtestscroll')
endfunc
func Test_display_listchars_precedes()
- set fillchars+=vert:\|
call NewWindow(10, 10)
" Need a physical line that wraps over the complete
" window size
@@ -174,15 +172,13 @@ func Test_scroll_CursorLineNr_update()
exe ":norm! o\<esc>110ia\<esc>"
END
let filename = 'Xdrawscreen'
- call writefile(lines, filename)
+ call writefile(lines, filename, 'D')
let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
call term_sendkeys(buf, "k")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_winline_rnu', {})
" clean up
call StopVimInTerminal(buf)
- call delete(filename)
endfunc
" check a long file name does not result in the hit-enter prompt
@@ -190,28 +186,27 @@ func Test_edit_long_file_name()
CheckScreendump
let longName = 'x'->repeat(min([&columns, 255]))
- call writefile([], longName)
+ call writefile([], longName, 'D')
let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8})
call VerifyScreenDump(buf, 'Test_long_file_name_1', {})
" clean up
call StopVimInTerminal(buf)
- call delete(longName)
endfunc
func Test_unprintable_fileformats()
CheckScreendump
- call writefile(["unix\r", "two"], 'Xunix.txt')
- call writefile(["mac\r", "two"], 'Xmac.txt')
+ call writefile(["unix\r", "two"], 'Xunix.txt', 'D')
+ call writefile(["mac\r", "two"], 'Xmac.txt', 'D')
let lines =<< trim END
edit Xunix.txt
split Xmac.txt
edit ++ff=mac
END
let filename = 'Xunprintable'
- call writefile(lines, filename)
+ call writefile(lines, filename, 'D')
let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50})
call VerifyScreenDump(buf, 'Test_display_unprintable_01', {})
call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>")
@@ -219,62 +214,6 @@ func Test_unprintable_fileformats()
" clean up
call StopVimInTerminal(buf)
- call delete('Xunix.txt')
- call delete('Xmac.txt')
- call delete(filename)
-endfunc
-
-" Test for scrolling that modifies buffer during visual block
-func Test_visual_block_scroll()
- CheckScreendump
-
- let lines =<< trim END
- source $VIMRUNTIME/plugin/matchparen.vim
- set scrolloff=1
- call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
- call cursor(5, 1)
- END
-
- let filename = 'Xvisualblockmodifiedscroll'
- call writefile(lines, filename, 'D')
-
- let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
- call term_sendkeys(buf, "V\<C-D>\<C-D>")
-
- call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
-
- call StopVimInTerminal(buf)
-endfunc
-
-" Test for clearing paren highlight when switching buffers
-func Test_matchparen_clear_highlight()
- CheckScreendump
-
- let lines =<< trim END
- source $VIMRUNTIME/plugin/matchparen.vim
- set hidden
- call setline(1, ['()'])
- normal 0
-
- func OtherBuffer()
- enew
- exe "normal iaa\<Esc>0"
- endfunc
- END
- call writefile(lines, 'XMatchparenClear', 'D')
- let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5})
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
-
- call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
-
- call term_sendkeys(buf, "\<C-^>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
-
- call term_sendkeys(buf, "\<C-^>:\<Esc>")
- call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
-
- call StopVimInTerminal(buf)
endfunc
func Test_display_scroll_at_topline()
@@ -300,19 +239,18 @@ func Test_display_scroll_update_visual()
call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 })
autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif
END
- call writefile(lines, 'XupdateVisual.vim')
+ call writefile(lines, 'XupdateVisual.vim', 'D')
let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60})
call term_sendkeys(buf, "VG7kk")
call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {})
call StopVimInTerminal(buf)
- call delete('XupdateVisual.vim')
endfunc
" Test for 'eob' (EndOfBuffer) item in 'fillchars'
func Test_eob_fillchars()
- " default value (skipped)
+ " default value
" call assert_match('eob:\~', &fillchars)
" invalid values
call assert_fails(':set fillchars=eob:', 'E474:')
@@ -406,7 +344,7 @@ func Test_local_fillchars()
call setline(1, ['window 4']->repeat(3))
setlocal fillchars=stl:4,stlnc:d,vert:>,eob:o
END
- call writefile(lines, 'Xdisplayfillchars')
+ call writefile(lines, 'Xdisplayfillchars', 'D')
let buf = RunVimInTerminal('-S Xdisplayfillchars', #{rows: 12})
call VerifyScreenDump(buf, 'Test_display_fillchars_1', {})
@@ -414,19 +352,25 @@ func Test_local_fillchars()
call VerifyScreenDump(buf, 'Test_display_fillchars_2', {})
call StopVimInTerminal(buf)
- call delete('Xdisplayfillchars')
endfunc
func Test_display_linebreak_breakat()
new
vert resize 25
let _breakat = &breakat
- setl signcolumn=yes linebreak breakat=) showbreak=+\
+ setl signcolumn=yes linebreak breakat=) showbreak=++
call setline(1, repeat('x', winwidth(0) - 2) .. ')abc')
let lines = ScreenLines([1, 2], 25)
let expected = [
\ ' xxxxxxxxxxxxxxxxxxxxxxx',
- \ ' + )abc '
+ \ ' ++)abc ',
+ \ ]
+ call assert_equal(expected, lines)
+ setl breakindent breakindentopt=shift:2
+ let lines = ScreenLines([1, 2], 25)
+ let expected = [
+ \ ' xxxxxxxxxxxxxxxxxxxxxxx',
+ \ ' ++)abc ',
\ ]
call assert_equal(expected, lines)
%bw!
@@ -478,5 +422,71 @@ func Test_display_lastline()
call assert_fails(':set fillchars=lastline:〇', 'E474:')
endfunc
+func Test_display_long_lastline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set display=lastline smoothscroll scrolloff=0
+ call setline(1, [
+ \'aaaaa'->repeat(150),
+ \'bbbbb '->repeat(7) .. 'ccccc '->repeat(7) .. 'ddddd '->repeat(7)
+ \])
+ END
+
+ call writefile(lines, 'XdispLongline', 'D')
+ let buf = RunVimInTerminal('-S XdispLongline', #{rows: 14, cols: 35})
+
+ call term_sendkeys(buf, "736|")
+ call VerifyScreenDump(buf, 'Test_display_long_line_1', {})
+
+ " The correct part of the last line is moved into view.
+ call term_sendkeys(buf, "D")
+ call VerifyScreenDump(buf, 'Test_display_long_line_2', {})
+
+ " "w_skipcol" does not change because the topline is still long enough
+ " to maintain the current skipcol.
+ call term_sendkeys(buf, "g04l11gkD")
+ call VerifyScreenDump(buf, 'Test_display_long_line_3', {})
+
+ " "w_skipcol" is reset to bring the entire topline into view because
+ " the line length is now smaller than the current skipcol + marker.
+ call term_sendkeys(buf, "x")
+ call VerifyScreenDump(buf, 'Test_display_long_line_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Moving the cursor to a line that doesn't fit in the window should show
+" correctly.
+func Test_display_cursor_long_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['a', 'b ' .. 'bbbbb'->repeat(150), 'c'])
+ norm $j
+ END
+
+ call writefile(lines, 'XdispCursorLongline', 'D')
+ let buf = RunVimInTerminal('-S XdispCursorLongline', #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_display_cursor_long_line_1', {})
+
+ " FIXME: moving the cursor above the topline does not set w_skipcol
+ " correctly with cpo+=n and zero scrolloff (curs_columns() extra == 1).
+ call term_sendkeys(buf, ":set number cpo+=n scrolloff=0\<CR>")
+ call term_sendkeys(buf, '$0')
+ call VerifyScreenDump(buf, 'Test_display_cursor_long_line_2', {})
+
+ " Going to the start of the line with "b" did not set w_skipcol correctly
+ " with 'smoothscroll'.
+ call term_sendkeys(buf, ":set smoothscroll\<CR>")
+ call term_sendkeys(buf, '$b')
+ call VerifyScreenDump(buf, 'Test_display_cursor_long_line_3', {})
+ " Same for "ge".
+ call term_sendkeys(buf, '$ge')
+ call VerifyScreenDump(buf, 'Test_display_cursor_long_line_4', {})
+
+ call StopVimInTerminal(buf)
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_edit.vim b/test/old/testdir/test_edit.vim
index 89a9179e60..3fe65832c9 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/test/old/testdir/test_edit.vim
@@ -5,6 +5,7 @@ if exists("+t_kD")
endif
source check.vim
+source screendump.vim
" Needed for testing basic rightleft: Test_edit_rightleft
source view_util.vim
@@ -445,31 +446,31 @@ endfunc
" terminal.
func Test_autoindent_remove_indent()
CheckRunVimInTerminal
- let buf = RunVimInTerminal('-N Xfile', {'rows': 6, 'cols' : 20})
+ let buf = RunVimInTerminal('-N Xarifile', {'rows': 6, 'cols' : 20})
call TermWait(buf)
call term_sendkeys(buf, ":set autoindent\n")
" leaving insert mode in a new line with indent added by autoindent, should
" remove the indent.
call term_sendkeys(buf, "i\<Tab>foo\<CR>\<Esc>")
- " Need to delay for sometime, otherwise the code in getchar.c will not be
+ " Need to delay for some time, otherwise the code in getchar.c will not be
" exercised.
call TermWait(buf, 50)
" when a line is wrapped and the cursor is at the start of the second line,
" leaving insert mode, should move the cursor back to the first line.
call term_sendkeys(buf, "o" .. repeat('x', 20) .. "\<Esc>")
- " Need to delay for sometime, otherwise the code in getchar.c will not be
+ " Need to delay for some time, otherwise the code in getchar.c will not be
" exercised.
call TermWait(buf, 50)
call term_sendkeys(buf, ":w\n")
call TermWait(buf)
call StopVimInTerminal(buf)
- call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xfile'))
- call delete('Xfile')
+ call assert_equal(["\tfoo", '', repeat('x', 20)], readfile('Xarifile'))
+ call delete('Xarifile')
endfunc
func Test_edit_CR()
" Test for <CR> in insert mode
- " basically only in quickfix mode ist tested, the rest
+ " basically only in quickfix mode it's tested, the rest
" has been taken care of by other tests
CheckFeature quickfix
botright new
@@ -516,7 +517,8 @@ func Test_edit_CTRL_()
call setline(1, ['abc'])
call cursor(1, 1)
call feedkeys("i\<c-_>xyz\<esc>", 'tnix')
- call assert_equal(["æèñabc"], getline(1, '$'))
+ " call assert_equal(["æèñabc"], getline(1, '$'))
+ call assert_equal(["zyxabc"], getline(1, '$'))
call assert_true(&revins)
call setline(1, ['abc'])
call cursor(1, 1)
@@ -580,6 +582,7 @@ func Test_edit_CTRL_G()
call assert_equal([0, 3, 7, 0], getpos('.'))
call feedkeys("i\<c-g>j\<esc>", 'tnix')
call assert_equal([0, 3, 6, 0], getpos('.'))
+ call assert_nobeep("normal! i\<c-g>\<esc>")
bw!
endfunc
@@ -1134,6 +1137,8 @@ func Test_edit_CTRL_V()
endfunc
func Test_edit_F1()
+ CheckFeature quickfix
+
" Pressing <f1>
new
" call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix')
@@ -1232,7 +1237,7 @@ func Test_edit_LEFT_RIGHT()
endfunc
func Test_edit_MOUSE()
- " This is a simple test, since we not really using the mouse here
+ " This is a simple test, since we're not really using the mouse here
CheckFeature mouse
10new
call setline(1, range(1, 100))
@@ -1514,33 +1519,11 @@ func Test_edit_rightleft()
bw!
endfunc
-func Test_edit_backtick()
- next a\`b c
- call assert_equal('a`b', expand('%'))
- next
- call assert_equal('c', expand('%'))
- call assert_equal('a\`b c', expand('##'))
-endfunc
-
-func Test_edit_quit()
- edit foo.txt
- split
- new
- call setline(1, 'hello')
- 3wincmd w
- redraw!
- call assert_fails('1q', 'E37:')
- bwipe! foo.txt
- only
-endfunc
-
func Test_edit_complete_very_long_name()
- if !has('unix')
- " Long directory names only work on Unix.
- return
- endif
+ " Long directory names only work on Unix.
+ CheckUnix
- let dirname = getcwd() . "/Xdir"
+ let dirname = getcwd() . "/Xlongdir"
let longdirname = dirname . repeat('/' . repeat('d', 255), 4)
try
call mkdir(longdirname, 'p')
@@ -1578,7 +1561,7 @@ func Test_edit_complete_very_long_name()
let longfilename = longdirname . '/' . repeat('a', 255)
call writefile(['Totum', 'Table'], longfilename)
new
- exe "next Xfile " . longfilename
+ exe "next Xnofile " . longfilename
exe "normal iT\<C-N>"
bwipe!
@@ -1591,6 +1574,26 @@ func Test_edit_complete_very_long_name()
set swapfile&
endfunc
+func Test_edit_backtick()
+ next a\`b c
+ call assert_equal('a`b', expand('%'))
+ next
+ call assert_equal('c', expand('%'))
+ call assert_equal('a\`b c', expand('##'))
+endfunc
+
+func Test_edit_quit()
+ edit foo.txt
+ split
+ new
+ call setline(1, 'hello')
+ 3wincmd w
+ redraw!
+ call assert_fails('1q', 'E37:')
+ bwipe! foo.txt
+ only
+endfunc
+
func Test_edit_alt()
" Keeping the cursor line didn't happen when the first line has indent.
new
@@ -1757,7 +1760,7 @@ endfunc
" Test for editing a directory
func Test_edit_is_a_directory()
CheckEnglish
- let dirname = getcwd() . "/Xdir"
+ let dirname = getcwd() . "/Xeditdir"
call mkdir(dirname, 'p')
new
@@ -1782,19 +1785,19 @@ endfunc
" Test for editing a file using invalid file encoding
func Test_edit_invalid_encoding()
CheckEnglish
- call writefile([], 'Xfile')
+ call writefile([], 'Xinvfile')
redir => msg
- new ++enc=axbyc Xfile
+ new ++enc=axbyc Xinvfile
redir END
call assert_match('\[NOT converted\]', msg)
- call delete('Xfile')
+ call delete('Xinvfile')
close!
endfunc
" Test for the "charconvert" option
func Test_edit_charconvert()
CheckEnglish
- call writefile(['one', 'two'], 'Xfile')
+ call writefile(['one', 'two'], 'Xccfile')
" set 'charconvert' to a non-existing function
set charconvert=NonExitingFunc()
@@ -1802,7 +1805,7 @@ func Test_edit_charconvert()
let caught_e117 = v:false
try
redir => msg
- edit ++enc=axbyc Xfile
+ edit ++enc=axbyc Xccfile
catch /E117:/
let caught_e117 = v:true
finally
@@ -1814,13 +1817,13 @@ func Test_edit_charconvert()
close!
set charconvert&
- " 'charconvert' function doesn't create a output file
+ " 'charconvert' function doesn't create an output file
func Cconv1()
endfunc
set charconvert=Cconv1()
new
redir => msg
- edit ++enc=axbyc Xfile
+ edit ++enc=axbyc Xccfile
redir END
call assert_equal(['one', 'two'], getline(1, '$'))
call assert_match("can't read output of 'charconvert'", msg)
@@ -1835,10 +1838,10 @@ func Test_edit_charconvert()
call writefile(data, v:fname_out)
endfunc
set charconvert=Cconv2()
- new Xfile
- write ++enc=ucase Xfile1
- call assert_equal(['ONE', 'TWO'], readfile('Xfile1'))
- call delete('Xfile1')
+ new Xccfile
+ write ++enc=ucase Xccfile1
+ call assert_equal(['ONE', 'TWO'], readfile('Xccfile1'))
+ call delete('Xccfile1')
close!
delfunc Cconv2
set charconvert&
@@ -1849,13 +1852,13 @@ func Test_edit_charconvert()
endfunc
set charconvert=Cconv3()
new
- call assert_fails('edit ++enc=lcase Xfile', 'E202:')
+ call assert_fails('edit ++enc=lcase Xccfile', 'E202:')
call assert_equal([''], getline(1, '$'))
close!
delfunc Cconv3
set charconvert&
- call delete('Xfile')
+ call delete('Xccfile')
endfunc
" Test for editing a file without read permission
@@ -1863,17 +1866,17 @@ func Test_edit_file_no_read_perm()
CheckUnix
CheckNotRoot
- call writefile(['one', 'two'], 'Xfile')
- call setfperm('Xfile', '-w-------')
+ call writefile(['one', 'two'], 'Xnrpfile')
+ call setfperm('Xnrpfile', '-w-------')
new
redir => msg
- edit Xfile
+ edit Xnrpfile
redir END
call assert_equal(1, &readonly)
call assert_equal([''], getline(1, '$'))
call assert_match('\[Permission Denied\]', msg)
close!
- call delete('Xfile')
+ call delete('Xnrpfile')
endfunc
" Using :edit without leaving 'insertmode' should not cause Insert mode to be
@@ -1888,6 +1891,9 @@ func Test_edit_insertmode_ex_edit()
call writefile(lines, 'Xtest_edit_insertmode_ex_edit')
let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6})
+ " Somehow this can be very slow with valgrind. A separate TermWait() works
+ " better than a longer time with WaitForAssert() (why?)
+ call TermWait(buf, 1000)
call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))})
call term_sendkeys(buf, "\<C-B>\<C-L>")
call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))})
@@ -1912,6 +1918,7 @@ endfunc
" Test for 'hkmap' and 'hkmapp'
func Test_edit_hkmap()
+ throw "Skipped: Nvim does not support 'hkmap'"
CheckFeature rightleft
if has('win32') && !has('gui')
" Test fails on the MS-Windows terminal version
@@ -1972,6 +1979,22 @@ func Test_edit_insert_reg()
close!
endfunc
+" Test for positioning cursor after CTRL-R expression failed
+func Test_edit_ctrl_r_failed()
+ CheckRunVimInTerminal
+
+ let buf = RunVimInTerminal('', #{rows: 6, cols: 60})
+
+ " trying to insert a dictionary produces an error
+ call term_sendkeys(buf, "i\<C-R>={}\<CR>")
+
+ " ending Insert mode should put the cursor back on the ':'
+ call term_sendkeys(buf, ":\<Esc>")
+ call VerifyScreenDump(buf, 'Test_edit_ctlr_r_failed_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" When a character is inserted at the last position of the last line in a
" window, the window contents should be scrolled one line up. If the top line
" is part of a fold, then the entire fold should be scrolled up.
diff --git a/src/nvim/testdir/test_environ.vim b/test/old/testdir/test_environ.vim
index d8344817f5..b5dbdce104 100644
--- a/src/nvim/testdir/test_environ.vim
+++ b/test/old/testdir/test_environ.vim
@@ -73,7 +73,7 @@ func Test_mac_locale()
" If $LANG is not set then the system locale will be used.
" Run Vim after unsetting all the locale environmental vars, and capture the
" output of :lang.
- let lang_results = system("unset LANG; unset LC_MESSAGES; " ..
+ let lang_results = system("unset LANG; unset LC_MESSAGES; unset LC_CTYPE; " ..
\ shellescape(v:progpath) ..
\ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ")
diff --git a/src/nvim/testdir/test_erasebackword.vim b/test/old/testdir/test_erasebackword.vim
index 9522ec2cd6..9522ec2cd6 100644
--- a/src/nvim/testdir/test_erasebackword.vim
+++ b/test/old/testdir/test_erasebackword.vim
diff --git a/src/nvim/testdir/test_escaped_glob.vim b/test/old/testdir/test_escaped_glob.vim
index 9f53c76a2c..9f53c76a2c 100644
--- a/src/nvim/testdir/test_escaped_glob.vim
+++ b/test/old/testdir/test_escaped_glob.vim
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/test/old/testdir/test_eval_stuff.vim
index 46482c34a1..b1c1136809 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/test/old/testdir/test_eval_stuff.vim
@@ -36,9 +36,70 @@ func Test_mkdir_p()
endtry
" 'p' doesn't suppress real errors
call writefile([], 'Xfile')
- call assert_fails('call mkdir("Xfile", "p")', 'E739')
+ call assert_fails('call mkdir("Xfile", "p")', 'E739:')
call delete('Xfile')
call delete('Xmkdir', 'rf')
+ call assert_equal(0, mkdir(v:_null_string))
+ call assert_fails('call mkdir([])', 'E730:')
+ call assert_fails('call mkdir("abc", [], [])', 'E745:')
+endfunc
+
+func DoMkdirDel(name)
+ call mkdir(a:name, 'pD')
+ call assert_true(isdirectory(a:name))
+endfunc
+
+func DoMkdirDelAddFile(name)
+ call mkdir(a:name, 'pD')
+ call assert_true(isdirectory(a:name))
+ call writefile(['text'], a:name .. '/file')
+endfunc
+
+func DoMkdirDelRec(name)
+ call mkdir(a:name, 'pR')
+ call assert_true(isdirectory(a:name))
+endfunc
+
+func DoMkdirDelRecAddFile(name)
+ call mkdir(a:name, 'pR')
+ call assert_true(isdirectory(a:name))
+ call writefile(['text'], a:name .. '/file')
+endfunc
+
+func Test_mkdir_defer_del()
+ " Xtopdir/tmp is created thus deleted, not Xtopdir itself
+ call mkdir('Xtopdir', 'R')
+ call DoMkdirDel('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion fails because "tmp" contains "sub"
+ call DoMkdirDel('Xtopdir/tmp/sub')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_true(isdirectory('Xtopdir/tmp'))
+ call delete('Xtopdir/tmp', 'rf')
+
+ " Deletion fails because "tmp" contains "file"
+ call DoMkdirDelAddFile('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_true(isdirectory('Xtopdir/tmp'))
+ call assert_true(filereadable('Xtopdir/tmp/file'))
+ call delete('Xtopdir/tmp', 'rf')
+
+ " Xtopdir/tmp is created thus deleted, not Xtopdir itself
+ call DoMkdirDelRec('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion works even though "tmp" contains "sub"
+ call DoMkdirDelRec('Xtopdir/tmp/sub')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
+
+ " Deletion works even though "tmp" contains "file"
+ call DoMkdirDelRecAddFile('Xtopdir/tmp')
+ call assert_true(isdirectory('Xtopdir'))
+ call assert_false(isdirectory('Xtopdir/tmp'))
endfunc
func Test_line_continuation()
@@ -85,6 +146,15 @@ func Test_for_over_null_string()
let &enc = save_enc
endfunc
+func Test_for_with_modifier()
+ " this checks has_loop_cmd() works with a modifier
+ let result = []
+ horizontal for i in range(3)
+ call extend(result, [i])
+ endfor
+ call assert_equal([0, 1, 2], result)
+endfunc
+
func Test_for_invalid_line_count()
let lines =<< trim END
111111111111111111111111 for line in ['one']
@@ -169,6 +239,12 @@ func Test_string_concatenation()
let a = 'a'
let a..=b
call assert_equal('ab', a)
+
+ if has('float')
+ let a = 'A'
+ let b = 1.234
+ call assert_equal('A1.234', a .. b)
+ endif
endfunc
" Test fix for issue #4507
@@ -205,6 +281,21 @@ func Test_vvar_scriptversion1()
call assert_equal(511, 0o777)
endfunc
+func Test_execute_cmd_with_null()
+ call assert_fails('execute v:_null_list', 'E730:')
+ call assert_fails('execute v:_null_dict', 'E731:')
+ call assert_fails('execute v:_null_blob', 'E976:')
+ execute v:_null_string
+ " Nvim doesn't have null partials
+ " call assert_fails('execute test_null_partial()', 'E729:')
+ " Nvim doesn't have test_unknown()
+ " call assert_fails('execute test_unknown()', 'E908:')
+ if has('job')
+ call assert_fails('execute test_null_job()', 'E908:')
+ call assert_fails('execute test_null_channel()', 'E908:')
+ endif
+endfunc
+
func Test_number_max_min_size()
" This will fail on systems without 64 bit number support or when not
" configured correctly.
@@ -389,4 +480,9 @@ func Test_modified_char_no_escape_special()
nunmap <M-…>
endfunc
+func Test_eval_string_in_special_key()
+ " this was using the '{' inside <> as the start of an interpolated string
+ silent! echo 0{1-$"\<S--{>n|nö%
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ex_equal.vim b/test/old/testdir/test_ex_equal.vim
index 05ad276836..32e23704eb 100644
--- a/src/nvim/testdir/test_ex_equal.vim
+++ b/test/old/testdir/test_ex_equal.vim
@@ -7,6 +7,19 @@ func Test_ex_equal()
let a = execute('=')
call assert_equal("\n2", a)
+ let a = execute('.=')
+ call assert_equal("\n1", a)
+
+ call assert_fails('3=', 'E16:')
+ bwipe!
+endfunc
+
+func Test_ex_equal_arg()
+ throw 'skipped: Nvim evaluates lua with := [arg]'
+
+ new
+ call setline(1, ["foo\tbar", "bar\tfoo"])
+
let a = execute('=#')
call assert_equal("\n2\n 1 foo bar", a)
@@ -22,10 +35,6 @@ func Test_ex_equal()
let a = execute('=p#')
call assert_equal("\n2\n 1 foo bar", a)
- let a = execute('.=')
- call assert_equal("\n1", a)
-
- call assert_fails('3=', 'E16:')
call assert_fails('=x', 'E488:')
bwipe!
diff --git a/src/nvim/testdir/test_ex_mode.vim b/test/old/testdir/test_ex_mode.vim
index 93100732ed..42f08868a0 100644
--- a/src/nvim/testdir/test_ex_mode.vim
+++ b/test/old/testdir/test_ex_mode.vim
@@ -123,6 +123,42 @@ func Test_open_command()
close!
endfunc
+func Test_open_command_flush_line()
+ throw 'Skipped: Nvim does not have :open'
+ " this was accessing freed memory: the regexp match uses a pointer to the
+ " current line which becomes invalid when searching for the ') mark.
+ new
+ call setline(1, ['one', 'two. three'])
+ s/one/ONE
+ try
+ open /\%')/
+ catch /E479/
+ endtry
+ bwipe!
+endfunc
+
+" FIXME: this doesn't fail without the fix but hangs
+func Skip_Test_open_command_state()
+ " Tricky script that failed because State was not set properly
+ let lines =<< trim END
+ !ls ƒ
+ 0scìi
+ so! Xsourced
+ set t_û0=0
+ v/-/o
+ END
+ call writefile(lines, 'XopenScript', '')
+
+ let sourced = ["!f\u0083\x02\<Esc>z=0"]
+ call writefile(sourced, 'Xsourced', 'b')
+
+ CheckRunVimInTerminal
+ let buf = RunVimInTerminal('-u NONE -i NONE -n -m -X -Z -e -s -S XopenScript -c qa!', #{rows: 6, wait_for_ruler: 0, no_clean: 1})
+ sleep 3
+
+ call StopVimInTerminal(buf)
+endfunc
+
" Test for :g/pat/visual to run vi commands in Ex mode
" This used to hang Vim before 8.2.0274.
func Test_Ex_global()
@@ -182,9 +218,9 @@ func Test_Ex_echo_backslash()
let bsl = '\\\\'
let bsl2 = '\\\'
call assert_fails('call feedkeys("Qecho " .. bsl .. "\nvisual\n", "xt")',
- \ "E15: Invalid expression: \\\\")
+ \ 'E15: Invalid expression: "\\"')
call assert_fails('call feedkeys("Qecho " .. bsl2 .. "\nm\nvisual\n", "xt")',
- \ "E15: Invalid expression: \\\nm")
+ \ "E15: Invalid expression: \"\\\nm\"")
endfunc
func Test_ex_mode_errors()
@@ -208,6 +244,12 @@ func Test_ex_mode_errors()
au! CmdLineEnter
delfunc ExEnterFunc
+
+ au CmdlineEnter * :
+ call feedkeys("gQecho 1\r", 'xt')
+
+ au! CmdlineEnter
+
quit
endfunc
diff --git a/src/nvim/testdir/test_ex_undo.vim b/test/old/testdir/test_ex_undo.vim
index 44feb3680a..44feb3680a 100644
--- a/src/nvim/testdir/test_ex_undo.vim
+++ b/test/old/testdir/test_ex_undo.vim
diff --git a/src/nvim/testdir/test_ex_z.vim b/test/old/testdir/test_ex_z.vim
index 481747ce84..665bcc1d76 100644
--- a/src/nvim/testdir/test_ex_z.vim
+++ b/test/old/testdir/test_ex_z.vim
@@ -98,7 +98,7 @@ func Test_z_bang()
%bwipe!
endfunc
-func Test_z_bug()
+func Test_z_overflow()
" This used to access invalid memory as a result of an integer overflow
" and freeze vim.
normal ox
@@ -106,3 +106,10 @@ func Test_z_bug()
z777777776666666
')
endfunc
+
+func Test_z_negative_lnum()
+ new
+ z^
+ call assert_equal(1, line('.'))
+ bwipe!
+endfunc
diff --git a/src/nvim/testdir/test_excmd.vim b/test/old/testdir/test_excmd.vim
index 44bed890f5..c729ff4929 100644
--- a/src/nvim/testdir/test_excmd.vim
+++ b/test/old/testdir/test_excmd.vim
@@ -237,7 +237,6 @@ endfunc
" Test for the :language command
func Test_language_cmd()
- CheckNotMSWindows " FIXME: why does this fail on Windows CI?
CheckFeature multi_lang
call assert_fails('language ctype non_existing_lang', 'E197:')
@@ -746,5 +745,9 @@ func Test_write_after_rename()
bwipe!
endfunc
+" catch address lines overflow
+func Test_ex_address_range_overflow()
+ call assert_fails(':--+foobar', 'E492:')
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exec_while_if.vim b/test/old/testdir/test_exec_while_if.vim
index 3f13b09945..3f13b09945 100644
--- a/src/nvim/testdir/test_exec_while_if.vim
+++ b/test/old/testdir/test_exec_while_if.vim
diff --git a/src/nvim/testdir/test_execute_func.vim b/test/old/testdir/test_execute_func.vim
index 16cc20e9a7..2edae39b8f 100644
--- a/src/nvim/testdir/test_execute_func.vim
+++ b/test/old/testdir/test_execute_func.vim
@@ -1,6 +1,8 @@
" test execute()
source view_util.vim
+source check.vim
+source vim9.vim
func NestedEval()
let nested = execute('echo "nested\nlines"')
@@ -31,7 +33,6 @@ func Test_execute_string()
call assert_equal("\nthat", evaled)
call assert_fails('call execute("doesnotexist")', 'E492:')
- call assert_fails('call execute(3.4)', 'E806:')
" Nvim supports execute('... :redir ...'), so this test is intentionally
" disabled.
" call assert_fails('call execute("call NestedRedir()")', 'E930:')
@@ -40,9 +41,11 @@ func Test_execute_string()
call assert_equal("\nsomething", execute('echo "something"', 'silent'))
call assert_equal("\nsomething", execute('echo "something"', 'silent!'))
call assert_equal("", execute('burp', 'silent!'))
- call assert_fails('call execute("echo \"x\"", 3.4)', 'E806:')
-
- call assert_equal("", execute(""))
+ if has('float')
+ call assert_fails('call execute(3.4)', 'E492:')
+ call assert_equal("\nx", execute("echo \"x\"", 3.4))
+ call CheckDefExecAndScriptFailure(['execute("echo \"x\"", 3.4)'], 'E806:')
+ endif
endfunc
func Test_execute_list()
@@ -53,7 +56,6 @@ func Test_execute_list()
call assert_equal("\n0\n1\n2\n3", execute(l))
call assert_equal("", execute([]))
- call assert_equal("", execute(v:_null_list))
endfunc
func Test_execute_does_not_change_col()
@@ -122,6 +124,8 @@ func Test_win_execute()
endfunc
func Test_win_execute_update_ruler()
+ CheckFeature quickfix
+
enew
call setline(1, range(500))
20
@@ -173,4 +177,17 @@ func Test_win_execute_visual_redraw()
bwipe!
endfunc
+func Test_execute_cmd_with_null()
+ call assert_equal("", execute(v:_null_string))
+ call assert_equal("", execute(v:_null_list))
+ call assert_fails('call execute(v:_null_dict)', 'E731:')
+ call assert_fails('call execute(v:_null_blob)', 'E976:')
+ " Nvim doesn't have null partials
+ " call assert_fails('call execute(test_null_partial())','E729:')
+ if has('job')
+ call assert_fails('call execute(test_null_job())', 'E908:')
+ call assert_fails('call execute(test_null_channel())', 'E908:')
+ endif
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_exists.vim b/test/old/testdir/test_exists.vim
index 62c66192ef..62c66192ef 100644
--- a/src/nvim/testdir/test_exists.vim
+++ b/test/old/testdir/test_exists.vim
diff --git a/src/nvim/testdir/test_exists_autocmd.vim b/test/old/testdir/test_exists_autocmd.vim
index 7e44a72653..7e44a72653 100644
--- a/src/nvim/testdir/test_exists_autocmd.vim
+++ b/test/old/testdir/test_exists_autocmd.vim
diff --git a/src/nvim/testdir/test_exit.vim b/test/old/testdir/test_exit.vim
index 6dbfb7047c..93e55ce575 100644
--- a/src/nvim/testdir/test_exit.vim
+++ b/test/old/testdir/test_exit.vim
@@ -81,6 +81,18 @@ func Test_exiting()
\ readfile('Xtestout'))
endif
call delete('Xtestout')
+
+ " ExitPre autocommand also executed on :wqall
+ let after =<< trim [CODE]
+ au QuitPre * call writefile(["QuitPre"], "Xtestout", "a")
+ au ExitPre * call writefile(["ExitPre"], "Xtestout", "a")
+ wqall
+ [CODE]
+
+ if RunVim([], after, '')
+ call assert_equal(['QuitPre', 'ExitPre'], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
endfunc
" Test for getting the Vim exit code from v:exiting
diff --git a/src/nvim/testdir/test_expand.vim b/test/old/testdir/test_expand.vim
index 4f5bb67d21..cd537f4ea1 100644
--- a/src/nvim/testdir/test_expand.vim
+++ b/test/old/testdir/test_expand.vim
@@ -141,7 +141,7 @@ func Test_source_sfile()
if RunVim([], [], '--clean -s Xscript')
call assert_equal([
\ 'E1274: No script file name to substitute for "<script>"',
- \ 'E498: no :source file name to substitute for "<sfile>"'],
+ \ 'E498: No :source file name to substitute for "<sfile>"'],
\ readfile('Xresult'))
endif
call delete('Xscript')
diff --git a/src/nvim/testdir/test_expand_func.vim b/test/old/testdir/test_expand_func.vim
index 454d76f0aa..454d76f0aa 100644
--- a/src/nvim/testdir/test_expand_func.vim
+++ b/test/old/testdir/test_expand_func.vim
diff --git a/test/old/testdir/test_expr.vim b/test/old/testdir/test_expr.vim
new file mode 100644
index 0000000000..d316e63818
--- /dev/null
+++ b/test/old/testdir/test_expr.vim
@@ -0,0 +1,929 @@
+" Tests for expressions.
+
+source check.vim
+source vim9.vim
+
+func Test_equal()
+ let base = {}
+ func base.method()
+ return 1
+ endfunc
+ func base.other() dict
+ return 1
+ endfunc
+ let instance = copy(base)
+ call assert_true(base.method == instance.method)
+ call assert_true([base.method] == [instance.method])
+ call assert_true(base.other == instance.other)
+ call assert_true([base.other] == [instance.other])
+
+ call assert_false(base.method == base.other)
+ call assert_false([base.method] == [base.other])
+ call assert_false(base.method == instance.other)
+ call assert_false([base.method] == [instance.other])
+
+ call assert_fails('echo base.method > instance.method')
+ " Nvim doesn't have null functions
+ " call assert_equal(0, test_null_function() == function('min'))
+ " call assert_equal(1, test_null_function() == test_null_function())
+ " Nvim doesn't have test_unknown()
+ " call assert_fails('eval 10 == test_unknown()', 'E685:')
+endfunc
+
+func Test_version()
+ call assert_true(has('patch-7.4.001'))
+ call assert_true(has('patch-7.4.01'))
+ call assert_true(has('patch-7.4.1'))
+ call assert_true(has('patch-6.9.999'))
+ call assert_true(has('patch-7.1.999'))
+ call assert_true(has('patch-7.4.123'))
+
+ call assert_false(has('patch-7'))
+ call assert_false(has('patch-7.4'))
+ call assert_false(has('patch-7.4.'))
+ call assert_false(has('patch-9.1.0'))
+ call assert_false(has('patch-9.9.1'))
+endfunc
+
+func Test_op_ternary()
+ let lines =<< trim END
+ call assert_equal('yes', 1 ? 'yes' : 'no')
+ call assert_equal('no', 0 ? 'yes' : 'no')
+
+ call assert_fails('echo [1] ? "yes" : "no"', 'E745:')
+ call assert_fails('echo {} ? "yes" : "no"', 'E728:')
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_equal('no', 'x' ? 'yes' : 'no')
+ call CheckDefAndScriptFailure(["'x' ? 'yes' : 'no'"], 'E1135:')
+ call assert_equal('yes', '1x' ? 'yes' : 'no')
+ call CheckDefAndScriptFailure(["'1x' ? 'yes' : 'no'"], 'E1135:')
+endfunc
+
+func Test_op_falsy()
+ let lines =<< trim END
+ call assert_equal(v:true, v:true ?? 456)
+ call assert_equal(123, 123 ?? 456)
+ call assert_equal('yes', 'yes' ?? 456)
+ call assert_equal(0z00, 0z00 ?? 456)
+ call assert_equal([1], [1] ?? 456)
+ call assert_equal({'one': 1}, {'one': 1} ?? 456)
+ call assert_equal(0.1, 0.1 ?? 456)
+
+ call assert_equal(456, v:false ?? 456)
+ call assert_equal(456, 0 ?? 456)
+ call assert_equal(456, '' ?? 456)
+ call assert_equal(456, 0z ?? 456)
+ call assert_equal(456, [] ?? 456)
+ call assert_equal(456, {} ?? 456)
+ call assert_equal(456, 0.0 ?? 456)
+
+ call assert_equal(456, v:null ?? 456)
+ #" call assert_equal(456, v:none ?? 456)
+ call assert_equal(456, v:_null_string ?? 456)
+ call assert_equal(456, v:_null_blob ?? 456)
+ call assert_equal(456, v:_null_list ?? 456)
+ call assert_equal(456, v:_null_dict ?? 456)
+ #" Nvim doesn't have null functions
+ #" call assert_equal(456, test_null_function() ?? 456)
+ #" Nvim doesn't have null partials
+ #" call assert_equal(456, test_null_partial() ?? 456)
+ if has('job')
+ call assert_equal(456, test_null_job() ?? 456)
+ endif
+ if has('channel')
+ call assert_equal(456, test_null_channel() ?? 456)
+ endif
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_dict()
+ let lines =<< trim END
+ VAR d = {'': 'empty', 'a': 'a', 0: 'zero'}
+ call assert_equal('empty', d[''])
+ call assert_equal('a', d['a'])
+ call assert_equal('zero', d[0])
+ call assert_true(has_key(d, ''))
+ call assert_true(has_key(d, 'a'))
+
+ LET d[''] = 'none'
+ LET d['a'] = 'aaa'
+ call assert_equal('none', d[''])
+ call assert_equal('aaa', d['a'])
+
+ LET d[ 'b' ] = 'bbb'
+ call assert_equal('bbb', d[ 'b' ])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(["VAR i = has_key([], 'a')"], ['E1206:', 'E1013:', 'E1206:'])
+endfunc
+
+func Test_strgetchar()
+ let lines =<< trim END
+ call assert_equal(char2nr('a'), strgetchar('axb', 0))
+ call assert_equal(char2nr('x'), 'axb'->strgetchar(1))
+ call assert_equal(char2nr('b'), strgetchar('axb', 2))
+
+ call assert_equal(-1, strgetchar('axb', -1))
+ call assert_equal(-1, strgetchar('axb', 3))
+ call assert_equal(-1, strgetchar('', 0))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(["VAR c = strgetchar([], 1)"], ['E730:', 'E1013:', 'E1174:'])
+ call CheckLegacyAndVim9Failure(["VAR c = strgetchar('axb', [])"], ['E745:', 'E1013:', 'E1210:'])
+endfunc
+
+func Test_strcharpart()
+ let lines =<< trim END
+ call assert_equal('a', strcharpart('axb', 0, 1))
+ call assert_equal('x', 'axb'->strcharpart(1, 1))
+ call assert_equal('b', strcharpart('axb', 2, 1))
+ call assert_equal('xb', strcharpart('axb', 1))
+
+ call assert_equal('', strcharpart('axb', 1, 0))
+ call assert_equal('', strcharpart('axb', 1, -1))
+ call assert_equal('', strcharpart('axb', -1, 1))
+ call assert_equal('', strcharpart('axb', -2, 2))
+
+ call assert_equal('a', strcharpart('axb', -1, 2))
+
+ call assert_equal('edit', "editor"[-10 : 3])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_fails('call strcharpart("", 0, 0, {})', ['E728:', 'E728:'])
+ call assert_fails('call strcharpart("", 0, 0, -1)', ['E1023:', 'E1023:'])
+endfunc
+
+func Test_getreg_empty_list()
+ let lines =<< trim END
+ call assert_equal('', getreg('x'))
+ call assert_equal([], getreg('x', 1, 1))
+ VAR x = getreg('x', 1, 1)
+ VAR y = x
+ call add(x, 'foo')
+ call assert_equal(['foo'], y)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(['call getreg([])'], ['E730:', 'E1013:', 'E1174:'])
+endfunc
+
+func Test_loop_over_null_list()
+ let lines =<< trim END
+ VAR null_list = v:_null_list
+ for i in null_list
+ call assert_report('should not get here')
+ endfor
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_setreg_null_list()
+ let lines =<< trim END
+ call setreg('x', v:_null_list)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_special_char()
+ " The failure is only visible using valgrind.
+ call CheckLegacyAndVim9Failure(['echo "\<C-">'], ['E15:', 'E1004:', 'E1004:'])
+endfunc
+
+func Test_method_with_prefix()
+ let lines =<< trim END
+ call assert_equal(TRUE, !range(5)->empty())
+ call assert_equal(FALSE, !-3)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_equal([0, 1, 2], --3->range())
+ call CheckDefAndScriptFailure(['eval --3->range()'], 'E15')
+
+ call assert_equal(1, !+-+0)
+ call CheckDefAndScriptFailure(['eval !+-+0'], 'E15')
+endfunc
+
+func Test_option_value()
+ let lines =<< trim END
+ #" boolean
+ set bri
+ call assert_equal(TRUE, &bri)
+ set nobri
+ call assert_equal(FALSE, &bri)
+
+ #" number
+ set ts=1
+ call assert_equal(1, &ts)
+ set ts=8
+ call assert_equal(8, &ts)
+
+ #" string
+ exe "set cedit=\<Esc>"
+ call assert_equal("\<Esc>", &cedit)
+ set cpo=
+ call assert_equal("", &cpo)
+ set cpo=abcdefi
+ call assert_equal("abcdefi", &cpo)
+ set cpo&vim
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_misc()
+ let lines =<< trim END
+ call assert_equal('123', printf('123'))
+
+ call assert_equal('', printf('%'))
+ call assert_equal('', printf('%.0d', 0))
+ call assert_equal('123', printf('%d', 123))
+ call assert_equal('123', printf('%i', 123))
+ call assert_equal('123', printf('%D', 123))
+ call assert_equal('123', printf('%U', 123))
+ call assert_equal('173', printf('%o', 123))
+ call assert_equal('173', printf('%O', 123))
+ call assert_equal('7b', printf('%x', 123))
+ call assert_equal('7B', printf('%X', 123))
+
+ call assert_equal('123', printf('%hd', 123))
+ call assert_equal('-123', printf('%hd', -123))
+ call assert_equal('-1', printf('%hd', 0xFFFF))
+ call assert_equal('-1', printf('%hd', 0x1FFFFF))
+
+ call assert_equal('123', printf('%hu', 123))
+ call assert_equal('65413', printf('%hu', -123))
+ call assert_equal('65535', printf('%hu', 0xFFFF))
+ call assert_equal('65535', printf('%hu', 0x1FFFFF))
+
+ call assert_equal('123', printf('%ld', 123))
+ call assert_equal('-123', printf('%ld', -123))
+ call assert_equal('65535', printf('%ld', 0xFFFF))
+ call assert_equal('131071', printf('%ld', 0x1FFFF))
+
+ call assert_equal('{', printf('%c', 123))
+ call assert_equal('abc', printf('%s', 'abc'))
+ call assert_equal('abc', printf('%S', 'abc'))
+
+ call assert_equal('+123', printf('%+d', 123))
+ call assert_equal('-123', printf('%+d', -123))
+ call assert_equal('+123', printf('%+ d', 123))
+ call assert_equal(' 123', printf('% d', 123))
+ call assert_equal(' 123', printf('% d', 123))
+ call assert_equal('-123', printf('% d', -123))
+
+ call assert_equal('123', printf('%2d', 123))
+ call assert_equal(' 123', printf('%6d', 123))
+ call assert_equal('000123', printf('%06d', 123))
+ call assert_equal('+00123', printf('%+06d', 123))
+ call assert_equal(' 00123', printf('% 06d', 123))
+ call assert_equal(' +123', printf('%+6d', 123))
+ call assert_equal(' 123', printf('% 6d', 123))
+ call assert_equal(' -123', printf('% 6d', -123))
+
+ #" Test left adjusted.
+ call assert_equal('123 ', printf('%-6d', 123))
+ call assert_equal('+123 ', printf('%-+6d', 123))
+ call assert_equal(' 123 ', printf('%- 6d', 123))
+ call assert_equal('-123 ', printf('%- 6d', -123))
+
+ call assert_equal(' 00123', printf('%7.5d', 123))
+ call assert_equal(' -00123', printf('%7.5d', -123))
+ call assert_equal(' +00123', printf('%+7.5d', 123))
+
+ #" Precision field should not be used when combined with %0
+ call assert_equal(' 00123', printf('%07.5d', 123))
+ call assert_equal(' -00123', printf('%07.5d', -123))
+
+ call assert_equal(' 123', printf('%*d', 5, 123))
+ call assert_equal('123 ', printf('%*d', -5, 123))
+ call assert_equal('00123', printf('%.*d', 5, 123))
+ call assert_equal(' 123', printf('% *d', 5, 123))
+ call assert_equal(' +123', printf('%+ *d', 5, 123))
+
+ call assert_equal('foobar', printf('%.*s', 9, 'foobar'))
+ call assert_equal('foo', printf('%.*s', 3, 'foobar'))
+ call assert_equal('', printf('%.*s', 0, 'foobar'))
+ call assert_equal('foobar', printf('%.*s', -1, 'foobar'))
+
+ #" Simple quote (thousand grouping char) is ignored.
+ call assert_equal('+00123456', printf("%+'09d", 123456))
+
+ #" Unrecognized format specifier kept as-is.
+ call assert_equal('_123', printf("%_%d", 123))
+
+ #" Test alternate forms.
+ call assert_equal('0x7b', printf('%#x', 123))
+ call assert_equal('0X7B', printf('%#X', 123))
+ call assert_equal('0173', printf('%#o', 123))
+ call assert_equal('0173', printf('%#O', 123))
+ call assert_equal('abc', printf('%#s', 'abc'))
+ call assert_equal('abc', printf('%#S', 'abc'))
+ call assert_equal(' 0173', printf('%#6o', 123))
+ call assert_equal(' 00173', printf('%#6.5o', 123))
+ call assert_equal(' 0173', printf('%#6.2o', 123))
+ call assert_equal(' 0173', printf('%#6.2o', 123))
+ call assert_equal('0173', printf('%#2.2o', 123))
+
+ call assert_equal(' 00123', printf('%6.5d', 123))
+ call assert_equal(' 0007b', printf('%6.5x', 123))
+
+ call assert_equal('123', printf('%.2d', 123))
+ call assert_equal('0123', printf('%.4d', 123))
+ call assert_equal('0000000123', printf('%.10d', 123))
+ call assert_equal('123', printf('%.0d', 123))
+
+ call assert_equal('abc', printf('%2s', 'abc'))
+ call assert_equal('abc', printf('%2S', 'abc'))
+ call assert_equal('abc', printf('%.4s', 'abc'))
+ call assert_equal('abc', printf('%.4S', 'abc'))
+ call assert_equal('ab', printf('%.2s', 'abc'))
+ call assert_equal('ab', printf('%.2S', 'abc'))
+ call assert_equal('', printf('%.0s', 'abc'))
+ call assert_equal('', printf('%.s', 'abc'))
+ call assert_equal(' abc', printf('%4s', 'abc'))
+ call assert_equal(' abc', printf('%4S', 'abc'))
+ call assert_equal('0abc', printf('%04s', 'abc'))
+ call assert_equal('0abc', printf('%04S', 'abc'))
+ call assert_equal('abc ', printf('%-4s', 'abc'))
+ call assert_equal('abc ', printf('%-4S', 'abc'))
+
+ call assert_equal('🐍', printf('%.2S', '🐍🐍'))
+ call assert_equal('', printf('%.1S', '🐍🐍'))
+
+ call assert_equal('[ あいう]', printf('[%10.6S]', 'あいうえお'))
+ call assert_equal('[ あいうえ]', printf('[%10.8S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.10S]', 'あいうえお'))
+ call assert_equal('[あいうえお]', printf('[%10.12S]', 'あいうえお'))
+
+ call assert_equal('あいう', printf('%S', 'あいう'))
+ call assert_equal('あいう', printf('%#S', 'あいう'))
+
+ call assert_equal('あb', printf('%2S', 'あb'))
+ call assert_equal('あb', printf('%.4S', 'あb'))
+ call assert_equal('あ', printf('%.2S', 'あb'))
+ call assert_equal(' あb', printf('%4S', 'あb'))
+ call assert_equal('0あb', printf('%04S', 'あb'))
+ call assert_equal('あb ', printf('%-4S', 'あb'))
+ call assert_equal('あ ', printf('%-4.2S', 'あb'))
+
+ call assert_equal('aい', printf('%2S', 'aい'))
+ call assert_equal('aい', printf('%.4S', 'aい'))
+ call assert_equal('a', printf('%.2S', 'aい'))
+ call assert_equal(' aい', printf('%4S', 'aい'))
+ call assert_equal('0aい', printf('%04S', 'aい'))
+ call assert_equal('aい ', printf('%-4S', 'aい'))
+ call assert_equal('a ', printf('%-4.2S', 'aい'))
+
+ call assert_equal('[あいう]', printf('[%05S]', 'あいう'))
+ call assert_equal('[あいう]', printf('[%06S]', 'あいう'))
+ call assert_equal('[0あいう]', printf('[%07S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.4S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.4S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.4S]', 'あいう'))
+
+ call assert_equal('[00あi]', printf('[%05.4S]', 'あiう'))
+ call assert_equal('[000あi]', printf('[%06.4S]', 'あiう'))
+ call assert_equal('[0000あi]', printf('[%07.4S]', 'あiう'))
+
+ call assert_equal('[0あい]', printf('[%05.5S]', 'あいう'))
+ call assert_equal('[00あい]', printf('[%06.5S]', 'あいう'))
+ call assert_equal('[000あい]', printf('[%07.5S]', 'あいう'))
+
+ call assert_equal('[あiう]', printf('[%05.5S]', 'あiう'))
+ call assert_equal('[0あiう]', printf('[%06.5S]', 'あiう'))
+ call assert_equal('[00あiう]', printf('[%07.5S]', 'あiう'))
+
+ call assert_equal('[0000000000]', printf('[%010.0S]', 'あいう'))
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あいう'))
+ call assert_equal('[00000000あ]', printf('[%010.3S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.4S]', 'あいう'))
+ call assert_equal('[000000あい]', printf('[%010.5S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.6S]', 'あいう'))
+ call assert_equal('[0000あいう]', printf('[%010.7S]', 'あいう'))
+
+ call assert_equal('[0000000000]', printf('[%010.1S]', 'あiう'))
+ call assert_equal('[00000000あ]', printf('[%010.2S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.3S]', 'あiう'))
+ call assert_equal('[0000000あi]', printf('[%010.4S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.5S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.6S]', 'あiう'))
+ call assert_equal('[00000あiう]', printf('[%010.7S]', 'あiう'))
+
+ call assert_equal('1%', printf('%d%%', 1))
+ call assert_notequal('', printf('%p', "abc"))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(["call printf('123', 3)"], "E767:")
+
+ " this was using uninitialized memory
+ call CheckLegacyAndVim9Failure(["eval ''->printf()"], "E119:")
+endfunc
+
+func Test_printf_float()
+ if has('float')
+ let lines =<< trim END
+ call assert_equal('1.000000', printf('%f', 1))
+ call assert_equal('1.230000', printf('%f', 1.23))
+ call assert_equal('1.230000', printf('%F', 1.23))
+ call assert_equal('9999999.9', printf('%g', 9999999.9))
+ call assert_equal('9999999.9', printf('%G', 9999999.9))
+ call assert_equal('1.00000001e7', printf('%.8g', 10000000.1))
+ call assert_equal('1.00000001E7', printf('%.8G', 10000000.1))
+ call assert_equal('1.230000e+00', printf('%e', 1.23))
+ call assert_equal('1.230000E+00', printf('%E', 1.23))
+ call assert_equal('1.200000e-02', printf('%e', 0.012))
+ call assert_equal('-1.200000e-02', printf('%e', -0.012))
+ call assert_equal('0.33', printf('%.2f', 1.0 / 3.0))
+ call assert_equal(' 0.33', printf('%6.2f', 1.0 / 3.0))
+ call assert_equal(' -0.33', printf('%6.2f', -1.0 / 3.0))
+ call assert_equal('000.33', printf('%06.2f', 1.0 / 3.0))
+ call assert_equal('-00.33', printf('%06.2f', -1.0 / 3.0))
+ call assert_equal('-00.33', printf('%+06.2f', -1.0 / 3.0))
+ call assert_equal('+00.33', printf('%+06.2f', 1.0 / 3.0))
+ call assert_equal(' 00.33', printf('% 06.2f', 1.0 / 3.0))
+ call assert_equal('000.33', printf('%06.2g', 1.0 / 3.0))
+ call assert_equal('-00.33', printf('%06.2g', -1.0 / 3.0))
+ call assert_equal('0.33', printf('%3.2f', 1.0 / 3.0))
+ call assert_equal('003.33e-01', printf('%010.2e', 1.0 / 3.0))
+ call assert_equal(' 03.33e-01', printf('% 010.2e', 1.0 / 3.0))
+ call assert_equal('+03.33e-01', printf('%+010.2e', 1.0 / 3.0))
+ call assert_equal('-03.33e-01', printf('%010.2e', -1.0 / 3.0))
+
+ #" When precision is 0, the dot should be omitted.
+ call assert_equal(' 2', printf('%3.f', 7.0 / 3.0))
+ call assert_equal(' 2', printf('%3.g', 7.0 / 3.0))
+ call assert_equal(' 2e+00', printf('%7.e', 7.0 / 3.0))
+
+ #" Float zero can be signed.
+ call assert_equal('+0.000000', printf('%+f', 0.0))
+ call assert_equal('0.000000', printf('%f', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.000000', printf('%f', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%s', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%s', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%S', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%S', 1.0 / (-1.0 / 0.0)))
+
+ #" Float infinity can be signed.
+ call assert_equal('inf', printf('%f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%f', -1.0 / 0.0))
+ call assert_equal('inf', printf('%g', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%g', -1.0 / 0.0))
+ call assert_equal('inf', printf('%e', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%e', -1.0 / 0.0))
+ call assert_equal('INF', printf('%F', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%F', -1.0 / 0.0))
+ call assert_equal('INF', printf('%E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%E', -1.0 / 0.0))
+ call assert_equal('INF', printf('%E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%G', -1.0 / 0.0))
+ call assert_equal('+inf', printf('%+f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%+f', -1.0 / 0.0))
+ call assert_equal(' inf', printf('% f', 1.0 / 0.0))
+ call assert_equal(' inf', printf('%6f', 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%6f', -1.0 / 0.0))
+ call assert_equal(' inf', printf('%6g', 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%6g', -1.0 / 0.0))
+ call assert_equal(' +inf', printf('%+6f', 1.0 / 0.0))
+ call assert_equal(' inf', printf('% 6f', 1.0 / 0.0))
+ call assert_equal(' +inf', printf('%+06f', 1.0 / 0.0))
+ call assert_equal('inf ', printf('%-6f', 1.0 / 0.0))
+ call assert_equal('-inf ', printf('%-6f', -1.0 / 0.0))
+ call assert_equal('+inf ', printf('%-+6f', 1.0 / 0.0))
+ call assert_equal(' inf ', printf('%- 6f', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6F', -1.0 / 0.0))
+ call assert_equal('+INF ', printf('%-+6F', 1.0 / 0.0))
+ call assert_equal(' INF ', printf('%- 6F', 1.0 / 0.0))
+ call assert_equal('INF ', printf('%-6G', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6G', -1.0 / 0.0))
+ call assert_equal('INF ', printf('%-6E', 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%-6E', -1.0 / 0.0))
+ call assert_equal("str2float('inf')", printf('%s', 1.0 / 0.0))
+ call assert_equal("-str2float('inf')", printf('%s', -1.0 / 0.0))
+
+ #" Test special case where max precision is truncated at 340.
+ call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.330f', 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.340f', 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%.350f', 1.0))
+
+ #" Float nan (not a number) has no sign.
+ call assert_equal('nan', printf('%f', sqrt(-1.0)))
+ call assert_equal('nan', printf('%f', 0.0 / 0.0))
+ call assert_equal('nan', printf('%f', -0.0 / 0.0))
+ call assert_equal('nan', printf('%g', 0.0 / 0.0))
+ call assert_equal('nan', printf('%e', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%F', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%G', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%E', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%F', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%G', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%E', -0.0 / 0.0))
+ call assert_equal(' nan', printf('%6f', 0.0 / 0.0))
+ call assert_equal(' nan', printf('%06f', 0.0 / 0.0))
+ call assert_equal('nan ', printf('%-6f', 0.0 / 0.0))
+ call assert_equal('nan ', printf('%- 6f', 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%s', 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%s', -0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%S', 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%S', -0.0 / 0.0))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(['echo printf("%f", "a")'], 'E807:')
+ endif
+endfunc
+
+func Test_printf_errors()
+ call CheckLegacyAndVim9Failure(['echo printf("%d", {})'], 'E728:')
+ call CheckLegacyAndVim9Failure(['echo printf("%d", [])'], 'E745:')
+ call CheckLegacyAndVim9Failure(['echo printf("%d", 1, 2)'], 'E767:')
+ call CheckLegacyAndVim9Failure(['echo printf("%*d", 1)'], 'E766:')
+ call CheckLegacyAndVim9Failure(['echo printf("%s")'], 'E766:')
+ if has('float')
+ call CheckLegacyAndVim9Failure(['echo printf("%d", 1.2)'], 'E805:')
+ call CheckLegacyAndVim9Failure(['echo printf("%f")'], 'E766:')
+ endif
+endfunc
+
+func Test_printf_64bit()
+ let lines =<< trim END
+ call assert_equal("123456789012345", printf('%d', 123456789012345))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_spec_s()
+ let lines =<< trim END
+ #" number
+ call assert_equal("1234567890", printf('%s', 1234567890))
+
+ #" string
+ call assert_equal("abcdefgi", printf('%s', "abcdefgi"))
+
+ #" float
+ if has('float')
+ call assert_equal("1.23", printf('%s', 1.23))
+ endif
+
+ #" list
+ VAR lvalue = [1, 'two', ['three', 4]]
+ call assert_equal(string(lvalue), printf('%s', lvalue))
+
+ #" dict
+ VAR dvalue = {'key1': 'value1', 'key2': ['list', 'lvalue'], 'key3': {'dict': 'lvalue'}}
+ call assert_equal(string(dvalue), printf('%s', dvalue))
+
+ #" funcref
+ call assert_equal('printf', printf('%s', 'printf'->function()))
+
+ #" partial
+ call assert_equal(string(function('printf', ['%s'])), printf('%s', function('printf', ['%s'])))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_spec_b()
+ let lines =<< trim END
+ call assert_equal("0", printf('%b', 0))
+ call assert_equal("00001100", printf('%08b', 12))
+ call assert_equal("11111111", printf('%08b', 0xff))
+ call assert_equal(" 1111011", printf('%10b', 123))
+ call assert_equal("0001111011", printf('%010b', 123))
+ call assert_equal(" 0b1111011", printf('%#10b', 123))
+ call assert_equal("0B01111011", printf('%#010B', 123))
+ call assert_equal("1001001100101100000001011010010", printf('%b', 1234567890))
+ call assert_equal("11100000100100010000110000011011101111101111001", printf('%b', 123456789012345))
+ call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%b', -1))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_max_min_errors()
+ call CheckLegacyAndVim9Failure(['call max(v:true)'], ['E712:', 'E1013:', 'E1227:'])
+ call CheckLegacyAndVim9Failure(['call max(v:true)'], ['max()', 'E1013:', 'E1227:'])
+ call CheckLegacyAndVim9Failure(['call min(v:true)'], ['E712:', 'E1013:', 'E1227:'])
+ call CheckLegacyAndVim9Failure(['call min(v:true)'], ['min()', 'E1013:', 'E1227:'])
+endfunc
+
+func Test_function_with_funcref()
+ let lines =<< trim END
+ let s:F = function('type')
+ let s:Fref = function(s:F)
+ call assert_equal(v:t_string, s:Fref('x'))
+ call assert_fails("call function('s:F')", 'E700:')
+
+ call assert_fails("call function('foo()')", 'E475:')
+ call assert_fails("call function('foo()')", 'foo()')
+ call assert_fails("function('')", 'E129:')
+
+ let s:Len = {s -> strlen(s)}
+ call assert_equal(6, s:Len('foobar'))
+ let name = string(s:Len)
+ " can evaluate "function('<lambda>99')"
+ call execute('let Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
+ END
+ call CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+ var F = function('type')
+ var Fref = function(F)
+ call assert_equal(v:t_string, Fref('x'))
+ call assert_fails("call function('F')", 'E700:')
+
+ call assert_fails("call function('foo()')", 'E475:')
+ call assert_fails("call function('foo()')", 'foo()')
+ call assert_fails("function('')", 'E129:')
+
+ var Len = (s) => strlen(s)
+ call assert_equal(6, Len('foobar'))
+ var name = string(Len)
+ # can evaluate "function('<lambda>99')"
+ call execute('var Ref = ' .. name)
+ call assert_equal(4, Ref('text'))
+ END
+ call CheckScriptSuccess(lines)
+endfunc
+
+func Test_funcref()
+ func! One()
+ return 1
+ endfunc
+ let OneByName = function('One')
+ let OneByRef = funcref('One')
+ func! One()
+ return 2
+ endfunc
+ call assert_equal(2, OneByName())
+ call assert_equal(1, OneByRef())
+ let OneByRef = 'One'->funcref()
+ call assert_equal(2, OneByRef())
+ call assert_fails('echo funcref("{")', 'E475:')
+ let OneByRef = funcref("One", repeat(["foo"], 20))
+ call assert_fails('let OneByRef = funcref("One", repeat(["foo"], 21))', 'E118:')
+ call assert_fails('echo function("min") =~ function("min")', 'E694:')
+endfunc
+
+" Test for calling function() and funcref() outside of a Vim script context.
+func Test_function_outside_script()
+ let cleanup =<< trim END
+ call writefile([execute('messages')], 'Xtest.out')
+ qall
+ END
+ call writefile(cleanup, 'Xverify.vim')
+ call RunVim([], [], "-c \"echo function('s:abc')\" -S Xverify.vim")
+ call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+ call RunVim([], [], "-c \"echo funcref('s:abc')\" -S Xverify.vim")
+ call assert_match('E81: Using <SID> not in a', readfile('Xtest.out')[0])
+ call delete('Xtest.out')
+ call delete('Xverify.vim')
+endfunc
+
+func Test_setmatches()
+ let lines =<< trim END
+ hi def link 1 Comment
+ hi def link 2 PreProc
+ VAR set = [{"group": 1, "pattern": 2, "id": 3, "priority": 4}]
+ VAR exp = [{"group": '1', "pattern": '2', "id": 3, "priority": 4}]
+ if has('conceal')
+ LET set[0]['conceal'] = 5
+ LET exp[0]['conceal'] = '5'
+ endif
+ eval set->setmatches()
+ call assert_equal(exp, getmatches())
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(['VAR m = setmatches([], [])'], ['E745:', 'E1013:', 'E1210:'])
+endfunc
+
+func Test_empty_concatenate()
+ let lines =<< trim END
+ call assert_equal('b', 'a'[4 : 0] .. 'b')
+ call assert_equal('b', 'b' .. 'a'[4 : 0])
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_broken_number()
+ call CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 1X'], 'E15:')
+ call CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 0b1X'], 'E15:')
+ call CheckLegacyAndVim9Failure(['echo 0b12'], 'E15:')
+ call CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 0x1X'], 'E15:')
+ call CheckLegacyAndVim9Failure(['VAR X = "bad"', 'echo 011X'], 'E15:')
+
+ call CheckLegacyAndVim9Success(['call assert_equal(2, str2nr("2a"))'])
+
+ call CheckLegacyAndVim9Failure(['inoremap <Char-0b1z> b'], 'E474:')
+endfunc
+
+func Test_eval_after_if()
+ let s:val = ''
+ func SetVal(x)
+ let s:val ..= a:x
+ endfunc
+ if 0 | eval SetVal('a') | endif | call SetVal('b')
+ call assert_equal('b', s:val)
+endfunc
+
+func Test_divide_by_zero()
+ " only tests that this doesn't crash, the result is not important
+ echo 0 / 0
+ echo 0 / 0 / -1
+endfunc
+
+" Test for command-line completion of expressions
+func Test_expr_completion()
+ CheckFeature cmdline_compl
+ for cmd in [
+ \ 'let a = ',
+ \ 'const a = ',
+ \ 'if',
+ \ 'elseif',
+ \ 'while',
+ \ 'for',
+ \ 'echo',
+ \ 'echon',
+ \ 'execute',
+ \ 'echomsg',
+ \ 'echoerr',
+ \ 'call',
+ \ 'return',
+ \ 'cexpr',
+ \ 'caddexpr',
+ \ 'cgetexpr',
+ \ 'lexpr',
+ \ 'laddexpr',
+ \ 'lgetexpr']
+ call feedkeys(":" . cmd . " getl\<Tab>\<Home>\"\<CR>", 'xt')
+ call assert_equal('"' . cmd . ' getline(', getreg(':'))
+ endfor
+
+ " completion for the expression register
+ call feedkeys(":\"\<C-R>=float2\t\"\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"float2nr("', @=)
+
+ " completion for window local variables
+ let w:wvar1 = 10
+ let w:wvar2 = 10
+ call feedkeys(":echo w:wvar\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo w:wvar1 w:wvar2', @:)
+ unlet w:wvar1 w:wvar2
+
+ " completion for tab local variables
+ let t:tvar1 = 10
+ let t:tvar2 = 10
+ call feedkeys(":echo t:tvar\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo t:tvar1 t:tvar2', @:)
+ unlet t:tvar1 t:tvar2
+
+ " completion for variables
+ let g:tvar1 = 1
+ let g:tvar2 = 2
+ call feedkeys(":let g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"let g:tvar1 g:tvar2', @:)
+ " completion for variables after a ||
+ call feedkeys(":echo 1 || g:tv\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo 1 || g:tvar1 g:tvar2', @:)
+
+ " completion for options
+ call feedkeys(":echo &compat\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo &compatible', @:)
+ call feedkeys(":echo 1 && &compat\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo 1 && &compatible', @:)
+ call feedkeys(":echo &g:equala\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"echo &g:equalalways', @:)
+
+ " completion for string
+ call feedkeys(":echo \"Hello\\ World\"\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo \"Hello\\ World\"\<C-A>", @:)
+ call feedkeys(":echo 'Hello World'\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo 'Hello World'\<C-A>", @:)
+
+ " completion for command after a |
+ call feedkeys(":echo 'Hello' | cwin\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"echo 'Hello' | cwindow", @:)
+
+ " completion for environment variable
+ let $X_VIM_TEST_COMPLETE_ENV = 'foo'
+ call feedkeys(":let $X_VIM_TEST_COMPLETE_E\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match('"let $X_VIM_TEST_COMPLETE_ENV', @:)
+ unlet $X_VIM_TEST_COMPLETE_ENV
+endfunc
+
+" Test for errors in expression evaluation
+func Test_expr_eval_error()
+ call CheckLegacyAndVim9Failure(["VAR i = 'abc' .. []"], ['E730:', 'E1105:', 'E730:'])
+ call CheckLegacyAndVim9Failure(["VAR l = [] + 10"], ['E745:', 'E1051:', 'E745'])
+ call CheckLegacyAndVim9Failure(["VAR v = 10 + []"], ['E745:', 'E1051:', 'E745:'])
+ call CheckLegacyAndVim9Failure(["VAR v = 10 / []"], ['E745:', 'E1036:', 'E745:'])
+ call CheckLegacyAndVim9Failure(["VAR v = -{}"], ['E728:', 'E1012:', 'E728:'])
+endfunc
+
+func Test_white_in_function_call()
+ let lines =<< trim END
+ VAR text = substitute ( 'some text' , 't' , 'T' , 'g' )
+ call assert_equal('some TexT', text)
+ END
+ call CheckTransLegacySuccess(lines)
+
+ let lines =<< trim END
+ var text = substitute ( 'some text' , 't' , 'T' , 'g' )
+ call assert_equal('some TexT', text)
+ END
+ call CheckDefAndScriptFailure(lines, ['E1001:', 'E121:'])
+endfunc
+
+" Test for float value comparison
+func Test_float_compare()
+ CheckFeature float
+
+ let lines =<< trim END
+ call assert_true(1.2 == 1.2)
+ call assert_true(1.0 != 1.2)
+ call assert_true(1.2 > 1.0)
+ call assert_true(1.2 >= 1.2)
+ call assert_true(1.0 < 1.2)
+ call assert_true(1.2 <= 1.2)
+ call assert_true(+0.0 == -0.0)
+ #" two NaNs (not a number) are not equal
+ call assert_true(sqrt(-4.01) != (0.0 / 0.0))
+ #" two inf (infinity) are equal
+ call assert_true((1.0 / 0) == (2.0 / 0))
+ #" two -inf (infinity) are equal
+ call assert_true(-(1.0 / 0) == -(2.0 / 0))
+ #" +infinity != -infinity
+ call assert_true((1.0 / 0) != -(2.0 / 0))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_string_interp()
+ let lines =<< trim END
+ call assert_equal('', $"")
+ call assert_equal('foobar', $"foobar")
+ #" Escaping rules.
+ call assert_equal('"foo"{bar}', $"\"foo\"{{bar}}")
+ call assert_equal('"foo"{bar}', $'"foo"{{bar}}')
+ call assert_equal('foobar', $"{"foo"}" .. $'{'bar'}')
+ #" Whitespace before/after the expression.
+ call assert_equal('3', $"{ 1 + 2 }")
+ #" String conversion.
+ call assert_equal('hello from ' .. v:version, $"hello from {v:version}")
+ call assert_equal('hello from ' .. v:version, $'hello from {v:version}')
+ #" Paper over a small difference between VimScript behaviour.
+ call assert_equal(string(v:true), $"{v:true}")
+ call assert_equal('(1+1=2)', $"(1+1={1 + 1})")
+ #" Hex-escaped opening brace: char2nr('{') == 0x7b
+ call assert_equal('esc123ape', $"esc{123}ape")
+ call assert_equal('me{}me', $"me{"\x7b"}\x7dme")
+ VAR var1 = "sun"
+ VAR var2 = "shine"
+ call assert_equal('sunshine', $"{var1}{var2}")
+ call assert_equal('sunsunsun', $"{var1->repeat(3)}")
+ #" Multibyte strings.
+ call assert_equal('say ハロー・ワールド', $"say {'ハロー・ワールド'}")
+ #" Nested.
+ call assert_equal('foobarbaz', $"foo{$"{'bar'}"}baz")
+ #" Do not evaluate blocks when the expr is skipped.
+ VAR tmp = 0
+ if v:false
+ echo "${ LET tmp += 1 }"
+ endif
+ call assert_equal(0, tmp)
+
+ #" Stray closing brace.
+ call assert_fails('echo $"moo}"', 'E1278:')
+ #" Undefined variable in expansion.
+ call assert_fails('echo $"{moo}"', 'E121:')
+ #" Empty blocks are rejected.
+ call assert_fails('echo $"{}"', 'E15:')
+ call assert_fails('echo $"{ }"', 'E15:')
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ call assert_equal('5', $"{({x -> x + 1})(4)}")
+ END
+ call CheckLegacySuccess(lines)
+
+ let lines =<< trim END
+ call assert_equal('5', $"{((x) => x + 1)(4)}")
+ call assert_fails('echo $"{ # foo }"', 'E1279:')
+ END
+ call CheckDefAndScriptSuccess(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr_utf8.vim b/test/old/testdir/test_expr_utf8.vim
index fad725d2e5..c6d2e4ed7e 100644
--- a/src/nvim/testdir/test_expr_utf8.vim
+++ b/test/old/testdir/test_expr_utf8.vim
@@ -31,4 +31,14 @@ func Test_strcharpart()
call assert_equal('a', strcharpart('àxb', 0, 1))
call assert_equal('̀', strcharpart('àxb', 1, 1))
call assert_equal('x', strcharpart('àxb', 2, 1))
+
+
+ call assert_equal('a', strcharpart('àxb', 0, 1, 0))
+ call assert_equal('à', strcharpart('àxb', 0, 1, 1))
+ call assert_equal('x', strcharpart('àxb', 1, 1, 1))
+
+ call assert_fails("let v = strcharpart('abc', 0, 0, [])", 'E745:')
+ call assert_fails("let v = strcharpart('abc', 0, 0, 2)", 'E1023:')
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_file_perm.vim b/test/old/testdir/test_file_perm.vim
index 1cb09e8647..1cb09e8647 100644
--- a/src/nvim/testdir/test_file_perm.vim
+++ b/test/old/testdir/test_file_perm.vim
diff --git a/src/nvim/testdir/test_file_size.vim b/test/old/testdir/test_file_size.vim
index 3e78a7b23c..3e78a7b23c 100644
--- a/src/nvim/testdir/test_file_size.vim
+++ b/test/old/testdir/test_file_size.vim
diff --git a/src/nvim/testdir/test_filechanged.vim b/test/old/testdir/test_filechanged.vim
index fef0eb732f..fef0eb732f 100644
--- a/src/nvim/testdir/test_filechanged.vim
+++ b/test/old/testdir/test_filechanged.vim
diff --git a/src/nvim/testdir/test_fileformat.vim b/test/old/testdir/test_fileformat.vim
index 8d727a68c4..e12b1d1a9a 100644
--- a/src/nvim/testdir/test_fileformat.vim
+++ b/test/old/testdir/test_fileformat.vim
@@ -73,7 +73,12 @@ func Test_fileformats()
call s:concat_files('XXMac', 'XXEol', 'XXMacEol')
call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc')
- new
+ " The :bwipe commands below cause us to get back to the current buffer.
+ " Avoid stray errors for various 'fileformat' values which may cause a
+ " modeline to be misinterpreted by wiping the buffer and editing a new one.
+ only!
+ bwipe!
+ enew
" Test 1: try reading and writing with 'fileformats' empty
set fileformats=
diff --git a/src/nvim/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim
index 7b5ec22dd4..87044a06b5 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/test/old/testdir/test_filetype.vim
@@ -14,15 +14,14 @@ endfunc
func Test_conf_type()
filetype on
- call writefile(['# some comment', 'must be conf'], 'Xfile')
+ call writefile(['# some comment', 'must be conf'], 'Xconffile', 'D')
augroup filetypedetect
au BufNewFile,BufRead * call assert_equal(0, did_filetype())
augroup END
- split Xfile
+ split Xconffile
call assert_equal('conf', &filetype)
bwipe!
- call delete('Xfile')
filetype off
endfunc
@@ -30,21 +29,46 @@ func Test_other_type()
filetype on
augroup filetypedetect
au BufNewFile,BufRead * call assert_equal(0, did_filetype())
- au BufNewFile,BufRead Xfile setf testfile
+ au BufNewFile,BufRead Xotherfile setf testfile
au BufNewFile,BufRead * call assert_equal(1, did_filetype())
augroup END
- call writefile(['# some comment', 'must be conf'], 'Xfile')
- split Xfile
+ call writefile(['# some comment', 'must be conf'], 'Xotherfile', 'D')
+ split Xotherfile
call assert_equal('testfile', &filetype)
bwipe!
- call delete('Xfile')
filetype off
endfunc
+" If $XDG_CONFIG_HOME is set return "fname" expanded in a list.
+" Otherwise return an empty list.
+func s:WhenConfigHome(fname)
+ if exists('$XDG_CONFIG_HOME')
+ return [expand(a:fname)]
+ endif
+ return []
+endfunc
+
+" Return the name used for the $XDG_CONFIG_HOME directory.
+func s:GetConfigHome()
+ return getcwd() .. '/Xdg_config_home'
+endfunc
+
+" saved value of $XDG_CONFIG_HOME
+let s:saveConfigHome = ''
+
+func s:SetupConfigHome()
+ " Nvim on Windows may use $XDG_CONFIG_HOME, and runnvim.sh sets it.
+ " if empty(windowsversion())
+ let s:saveConfigHome = $XDG_CONFIG_HOME
+ call setenv("XDG_CONFIG_HOME", s:GetConfigHome())
+ " endif
+endfunc
+
" Filetypes detected just from matching the file name.
" First one is checking that these files have no filetype.
-let s:filename_checks = {
+func s:GetFilenameChecks() abort
+ return {
\ 'none': ['bsd', 'some-bsd'],
\ '8th': ['file.8th'],
\ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc', 'any/etc/a2ps.cfg', 'any/etc/a2ps/file.cfg'],
@@ -73,6 +97,7 @@ let s:filename_checks = {
\ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'],
\ 'astro': ['file.astro'],
\ 'atlas': ['file.atl', 'file.as'],
+ \ 'authzed': ['file.zed'],
\ 'autohotkey': ['file.ahk'],
\ 'autoit': ['file.au3'],
\ 'automake': ['GNUmakefile.am', 'makefile.am', 'Makefile.am'],
@@ -80,13 +105,15 @@ let s:filename_checks = {
\ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
\ 'basic': ['file.bas', 'file.bi', 'file.bm'],
+ \ 'bass': ['file.bass'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'beancount': ['file.beancount'],
\ 'bib': ['file.bib'],
- \ 'bicep': ['file.bicep'],
+ \ 'bicep': ['file.bicep', 'file.bicepparam'],
\ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'],
\ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'],
+ \ 'blade': ['file.blade.php'],
\ 'blank': ['file.bl'],
\ 'blueprint': ['file.blp'],
\ 'bsdl': ['file.bsd', 'file.bsdl'],
@@ -95,8 +122,9 @@ let s:filename_checks = {
\ 'bzr': ['bzr_log.any', 'bzr_log.file'],
\ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg'],
\ 'cabal': ['file.cabal'],
- \ 'cabalconfig': ['cabal.config'],
+ \ 'cabalconfig': ['cabal.config', expand("$HOME/.config/cabal/config")] + s:WhenConfigHome('$XDG_CONFIG_HOME/cabal/config'),
\ 'cabalproject': ['cabal.project', 'cabal.project.local'],
+ \ 'cairo': ['file.cairo'],
\ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'],
\ 'capnp': ['file.capnp'],
\ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'],
@@ -121,15 +149,18 @@ let s:filename_checks = {
\ 'cobol': ['file.cbl', 'file.cob', 'file.lib'],
\ 'coco': ['file.atg'],
\ 'conaryrecipe': ['file.recipe'],
- \ 'conf': ['auto.master'],
+ \ 'conf': ['auto.master', 'file.conf'],
\ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'],
\ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf', 'any/.aws/config', 'any/.aws/credentials', 'file.nmconnection'],
\ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
\ 'cook': ['file.cook'],
- \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'],
+ \ 'corn': ['file.corn'],
+ \ 'cpon': ['file.cpon'],
+ \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh', 'file.cppm', 'file.ccm', 'file.cxxm', 'file.c++m'],
\ 'cqlang': ['file.cql'],
\ 'crm': ['file.crm'],
\ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'],
+ \ 'crystal': ['file.cr'],
\ 'cs': ['file.cs', 'file.csx'],
\ 'csc': ['file.csc'],
\ 'csdl': ['file.csdl'],
@@ -139,11 +170,13 @@ let s:filename_checks = {
\ 'csv': ['file.csv'],
\ 'cucumber': ['file.feature'],
\ 'cuda': ['file.cu', 'file.cuh'],
+ \ 'cue': ['file.cue'],
\ 'cupl': ['file.pld'],
\ 'cuplsim': ['file.si'],
\ 'cvs': ['cvs123'],
\ 'cvsrc': ['.cvsrc'],
\ 'cynpp': ['file.cyn'],
+ \ 'cypher': ['file.cypher'],
\ 'd': ['file.d'],
\ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
@@ -152,10 +185,12 @@ let s:filename_checks = {
\ 'debcontrol': ['/debian/control', 'any/debian/control'],
\ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'],
\ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'],
+ \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'],
\ 'def': ['file.def'],
\ 'denyhosts': ['denyhosts.conf'],
\ 'desc': ['file.desc'],
\ 'desktop': ['file.desktop', '.directory', 'file.directory'],
+ \ 'dhall': ['file.dhall'],
\ 'dictconf': ['dict.conf', '.dictrc'],
\ 'dictdconf': ['dictd.conf', 'dictdfile.conf', 'dictd-file.conf'],
\ 'diff': ['file.diff', 'file.rej'],
@@ -163,7 +198,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'],
\ 'dockerfile': ['Containerfile', 'Dockerfile', 'dockerfile', 'file.Dockerfile', 'file.dockerfile', 'Dockerfile.debian', 'Containerfile.something'],
\ 'dosbatch': ['file.bat'],
- \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'],
+ \ 'dosini': ['/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap', 'file.vbp', 'ja2.ini', 'JA2.INI'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'],
\ 'dtd': ['file.dtd'],
@@ -186,6 +221,7 @@ let s:filename_checks = {
\ 'epuppet': ['file.epp'],
\ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'],
\ 'eruby': ['file.erb', 'file.rhtml'],
+ \ 'esdl': ['file.esdl'],
\ 'esmtprc': ['anyesmtprc', 'esmtprc', 'some-esmtprc'],
\ 'esqlc': ['file.ec', 'file.EC'],
\ 'esterel': ['file.strl'],
@@ -203,7 +239,7 @@ let s:filename_checks = {
\ 'fish': ['file.fish'],
\ 'focexec': ['file.fex', 'file.focexec'],
\ 'form': ['file.frm'],
- \ 'forth': ['file.ft', 'file.fth'],
+ \ 'forth': ['file.ft', 'file.fth', 'file.4th'],
\ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'],
\ 'fpcmake': ['file.fpc'],
\ 'framescript': ['file.fsl'],
@@ -211,6 +247,7 @@ let s:filename_checks = {
\ 'fsh': ['file.fsh'],
\ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'],
\ 'fstab': ['fstab', 'mtab'],
+ \ 'func': ['file.fc'],
\ 'fusion': ['file.fusion'],
\ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'],
\ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'],
@@ -221,28 +258,29 @@ let s:filename_checks = {
\ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'],
\ 'gemtext': ['file.gmi', 'file.gemini'],
\ 'gift': ['file.gift'],
- \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'],
+ \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'),
\ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'],
- \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'],
- \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'],
+ \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'),
+ \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore'),
\ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'],
\ 'gitrebase': ['git-rebase-todo'],
\ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'],
\ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'],
\ 'gleam': ['file.gleam'],
\ 'glsl': ['file.glsl'],
+ \ 'gn': ['file.gn', 'file.gni'],
\ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'],
\ 'gnuplot': ['file.gpi', '.gnuplot'],
\ 'go': ['file.go'],
\ 'gomod': ['go.mod'],
- \ 'gosum': ['go.sum'],
+ \ 'gosum': ['go.sum', 'go.work.sum'],
\ 'gowork': ['go.work'],
\ 'gp': ['file.gp', '.gprc'],
\ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'],
\ 'grads': ['file.gs'],
\ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'],
\ 'gretl': ['file.gretl'],
- \ 'groovy': ['file.gradle', 'file.groovy'],
+ \ 'groovy': ['file.gradle', 'file.groovy', 'Jenkinsfile'],
\ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'],
\ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'],
\ 'gsp': ['file.gsp'],
@@ -254,6 +292,7 @@ let s:filename_checks = {
\ 'handlebars': ['file.hbs'],
\ 'hare': ['file.ha'],
\ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'],
+ \ 'haskellpersistent': ['file.persistentmodels'],
\ 'haste': ['file.ht'],
\ 'hastepreproc': ['file.htpp'],
\ 'hb': ['file.hb'],
@@ -272,6 +311,7 @@ let s:filename_checks = {
\ 'html': ['file.html', 'file.htm', 'file.cshtml'],
\ 'htmlm4': ['file.html.m4'],
\ 'httest': ['file.htt', 'file.htb'],
+ \ 'hurl': ['file.hurl'],
\ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'],
\ 'ibasic': ['file.iba', 'file.ibi'],
\ 'icemenu': ['/.icewm/menu', 'any/.icewm/menu'],
@@ -286,6 +326,7 @@ let s:filename_checks = {
\ 'j': ['file.ijs'],
\ 'jal': ['file.jal', 'file.JAL'],
\ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'],
+ \ 'janet': ['file.janet'],
\ 'java': ['file.java', 'file.jav'],
\ 'javacc': ['file.jj', 'file.jjt'],
\ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs'],
@@ -296,12 +337,14 @@ let s:filename_checks = {
\ 'jq': ['file.jq'],
\ 'jovial': ['file.jov', 'file.j73', 'file.jovial'],
\ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'],
- \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', '.stylelintrc', 'file.slnf'],
+ \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.prettierrc', '.firebaserc', '.stylelintrc', 'file.slnf'],
\ 'json5': ['file.json5'],
\ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json'],
+ \ 'jsonl': ['file.jsonl'],
\ 'jsonnet': ['file.jsonnet', 'file.libsonnet'],
\ 'jsp': ['file.jsp'],
\ 'julia': ['file.jl'],
+ \ 'just': ['justfile', 'Justfile', '.justfile', 'config.just'],
\ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file'],
\ 'kdl': ['file.kdl'],
\ 'kivy': ['file.kv'],
@@ -314,6 +357,7 @@ let s:filename_checks = {
\ 'latte': ['file.latte', 'file.lte'],
\ 'ld': ['file.ld'],
\ 'ldif': ['file.ldif'],
+ \ 'lean': ['file.lean'],
\ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'],
\ 'less': ['file.less'],
\ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'],
@@ -324,11 +368,13 @@ let s:filename_checks = {
\ 'lilo': ['lilo.conf', 'lilo.conf-file'],
\ 'lilypond': ['file.ly', 'file.ily'],
\ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'],
+ \ 'liquidsoap': ['file.liq'],
\ 'liquid': ['file.liquid'],
\ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'],
\ 'lite': ['file.lite', 'file.lt'],
\ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'],
\ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'],
+ \ 'livebook': ['file.livemd'],
\ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'],
\ 'logindefs': ['/etc/login.defs', 'any/etc/login.defs'],
\ 'logtalk': ['file.lgt'],
@@ -337,7 +383,8 @@ let s:filename_checks = {
\ 'lpc': ['file.lpc', 'file.ulpc'],
\ 'lsl': ['file.lsl'],
\ 'lss': ['file.lss'],
- \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc'],
+ \ 'lua': ['file.lua', 'file.rockspec', 'file.nse', '.luacheckrc', '.busted'],
+ \ 'luau': ['file.luau'],
\ 'lynx': ['lynx.cfg'],
\ 'lyrics': ['file.lrc'],
\ 'm3build': ['m3makefile', 'm3overrides'],
@@ -348,6 +395,7 @@ let s:filename_checks = {
\ 'mailcap': ['.mailcap', 'mailcap'],
\ 'make': ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile'],
\ 'mallard': ['file.page'],
+ "\ 'man': ['file.man'],
\ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'],
\ 'map': ['file.map'],
\ 'maple': ['file.mv', 'file.mpl', 'file.mws'],
@@ -359,7 +407,7 @@ let s:filename_checks = {
\ 'file.wxm', 'maxima-init.mac'],
\ 'mel': ['file.mel'],
\ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'],
- \ 'meson': ['meson.build', 'meson_options.txt'],
+ \ 'meson': ['meson.build', 'meson.options', 'meson_options.txt'],
\ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user',
\ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log',
\ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err',
@@ -380,11 +428,13 @@ let s:filename_checks = {
\ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'],
\ 'moo': ['file.moo'],
\ 'moonscript': ['file.moon'],
+ \ 'move': ['file.move'],
\ 'mp': ['file.mp', 'file.mpxl', 'file.mpiv', 'file.mpvi'],
\ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'],
\ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'],
\ 'msidl': ['file.odl', 'file.mof'],
\ 'msql': ['file.msql'],
+ \ 'mojo': ['file.mojo', 'file.🔥'],
\ 'mupad': ['file.mu'],
\ 'mush': ['file.mush'],
\ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'],
@@ -400,14 +450,18 @@ let s:filename_checks = {
\ 'nim': ['file.nim', 'file.nims', 'file.nimble'],
\ 'ninja': ['file.ninja'],
\ 'nix': ['file.nix'],
+ \ 'norg': ['file.norg'],
\ 'nqc': ['file.nqc'],
\ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'],
\ 'nsis': ['file.nsi', 'file.nsh'],
+ \ 'nu': ['file.nu'],
\ 'obj': ['file.obj'],
+ \ 'objdump': ['file.objdump', 'file.cppobjdump'],
\ 'obse': ['file.obl', 'file.obse', 'file.oblivion', 'file.obscript'],
\ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit', 'file.mlt', 'file.mlp', 'file.mlip', 'file.mli.cppo', 'file.ml.cppo'],
\ 'occam': ['file.occ'],
\ 'octave': ['octaverc', '.octaverc', 'octave.conf'],
+ \ 'odin': ['file.odin'],
\ 'omnimark': ['file.xom', 'file.xin'],
\ 'opam': ['opam', 'file.opam', 'file.opam.template'],
\ 'openroad': ['file.or'],
@@ -425,6 +479,7 @@ let s:filename_checks = {
\ 'pccts': ['file.g'],
\ 'pcmk': ['file.pcmk'],
\ 'pdf': ['file.pdf'],
+ \ 'pem': ['file.pem', 'file.cer', 'file.crt', 'file.csr'],
\ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc', '.latexmkrc', 'latexmkrc'],
\ 'pf': ['pf.conf'],
\ 'pfmain': ['main.cf', 'main.cf.proto'],
@@ -441,6 +496,7 @@ let s:filename_checks = {
\ 'pod': ['file.pod'],
\ 'poefilter': ['file.filter'],
\ 'poke': ['file.pk'],
+ \ 'pony': ['file.pony'],
\ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'],
\ 'pov': ['file.pov'],
\ 'povini': ['.povrayrc'],
@@ -460,13 +516,17 @@ let s:filename_checks = {
\ 'psl': ['file.psl'],
\ 'pug': ['file.pug'],
\ 'puppet': ['file.pp'],
+ \ 'pymanifest': ['MANIFEST.in'],
\ 'pyret': ['file.arr'],
\ 'pyrex': ['file.pyx', 'file.pxd'],
\ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'],
\ 'ql': ['file.ql', 'file.qll'],
+ \ 'qml': ['file.qml', 'file.qbs'],
+ \ 'qmldir': ['qmldir'],
\ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'],
\ 'quarto': ['file.qmd'],
\ 'r': ['file.r', '.Rprofile', 'Rprofile', 'Rprofile.site'],
+ \ 'racket': ['file.rkt', 'file.rktd', 'file.rktl'],
\ 'radiance': ['file.rad', 'file.mat'],
\ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'],
\ 'raml': ['file.raml'],
@@ -477,6 +537,7 @@ let s:filename_checks = {
\ 'readline': ['.inputrc', 'inputrc'],
\ 'rego': ['file.rego'],
\ 'remind': ['.reminders', 'file.remind', 'file.rem', '.reminders-file'],
+ \ 'requirements': ['file.pip', 'requirements.txt'],
\ 'rescript': ['file.res', 'file.resi'],
\ 'resolv': ['resolv.conf'],
\ 'reva': ['file.frt'],
@@ -487,8 +548,10 @@ let s:filename_checks = {
\ 'rnc': ['file.rnc'],
\ 'rng': ['file.rng'],
\ 'rnoweb': ['file.rnw', 'file.snw'],
+ \ 'rpgle': ['file.rpgle', 'file.rpgleinc'],
\ 'robot': ['file.robot', 'file.resource'],
\ 'robots': ['robots.txt'],
+ \ 'ron': ['file.ron'],
\ 'routeros': ['file.rsc'],
\ 'rpcgen': ['file.x'],
\ 'rpl': ['file.rpl'],
@@ -503,7 +566,7 @@ let s:filename_checks = {
\ 'sather': ['file.sa'],
\ 'sbt': ['file.sbt'],
\ 'scala': ['file.scala'],
- \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'],
+ \ 'scheme': ['file.scm', 'file.ss', 'file.sld'],
\ 'scilab': ['file.sci', 'file.sce'],
\ 'screen': ['.screenrc', 'screenrc'],
\ 'scss': ['file.scss'],
@@ -515,14 +578,16 @@ let s:filename_checks = {
\ 'services': ['/etc/services', 'any/etc/services'],
\ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'],
\ 'sexplib': ['file.sexp'],
- \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
+ \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'],
\ 'sieve': ['file.siv', 'file.sieve'],
\ 'sil': ['file.sil'],
\ 'simula': ['file.sim'],
\ 'sinda': ['file.sin', 'file.s85'],
\ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'],
\ 'skill': ['file.il', 'file.ils', 'file.cdf'],
+ \ 'cdc': ['file.cdc'],
\ 'slang': ['file.sl'],
+ \ 'sage': ['file.sage'],
\ 'slice': ['file.ice'],
\ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'],
\ 'slpreg': ['/etc/slp.reg', 'any/etc/slp.reg'],
@@ -544,8 +609,9 @@ let s:filename_checks = {
\ 'spice': ['file.sp', 'file.spice'],
\ 'spup': ['file.speedup', 'file.spdata', 'file.spd'],
\ 'spyce': ['file.spy', 'file.spi'],
- \ 'sql': ['file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'],
+ \ 'sql': ['file.tyb', 'file.tyc', 'file.pkb', 'file.pks'],
\ 'sqlj': ['file.sqlj'],
+ \ 'prql': ['file.prql'],
\ 'sqr': ['file.sqr', 'file.sqi'],
\ 'squid': ['squid.conf'],
\ 'squirrel': ['file.nut'],
@@ -555,6 +621,7 @@ let s:filename_checks = {
\ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'],
\ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'],
\ 'st': ['file.st'],
+ \ 'starlark': ['file.ipd', 'file.star', 'file.starlark'],
\ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'],
\ 'stp': ['file.stp'],
\ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'],
@@ -566,14 +633,18 @@ let s:filename_checks = {
\ 'swayconfig': ['/home/user/.sway/config', '/home/user/.config/sway/config', '/etc/sway/config', '/etc/xdg/sway/config'],
\ 'swift': ['file.swift'],
\ 'swiftgyb': ['file.swift.gyb'],
+ \ 'swig': ['file.swg', 'file.swig'],
\ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'],
\ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.dnssd', 'any/systemd/file.link', 'any/systemd/file.mount', 'any/systemd/file.netdev', 'any/systemd/file.network', 'any/systemd/file.nspawn', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.slice', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/some.conf.d/file.conf', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file', '/etc/systemd/system/.#otherfile', '/home/user/.config/systemd/user/some.d/mine.conf', '/home/user/.config/systemd/user/some.d/.#file', '/home/user/.config/systemd/user/.#otherfile', '/.config/systemd/user/.#', '/.config/systemd/user/.#-file', '/.config/systemd/user/file.d/.#', '/.config/systemd/user/file.d/.#-file', '/.config/systemd/user/file.d/file.conf', '/etc/systemd/file.conf.d/file.conf', '/etc/systemd/system/.#', '/etc/systemd/system/.#-file', '/etc/systemd/system/file.d/.#', '/etc/systemd/system/file.d/.#-file', '/etc/systemd/system/file.d/file.conf', '/systemd/file.automount', '/systemd/file.dnssd', '/systemd/file.link', '/systemd/file.mount', '/systemd/file.netdev', '/systemd/file.network', '/systemd/file.nspawn', '/systemd/file.path', '/systemd/file.service', '/systemd/file.slice', '/systemd/file.socket', '/systemd/file.swap', '/systemd/file.target', '/systemd/file.timer', 'any/.config/systemd/user/.#', 'any/.config/systemd/user/.#-file', 'any/.config/systemd/user/file.d/.#', 'any/.config/systemd/user/file.d/.#-file', 'any/.config/systemd/user/file.d/file.conf', 'any/etc/systemd/file.conf.d/file.conf', 'any/etc/systemd/system/.#', 'any/etc/systemd/system/.#-file', 'any/etc/systemd/system/file.d/.#', 'any/etc/systemd/system/file.d/.#-file', 'any/etc/systemd/system/file.d/file.conf'],
\ 'systemverilog': ['file.sv', 'file.svh'],
+ \ 'trace32': ['file.cmm', 'file.t32'],
\ 'tags': ['tags'],
\ 'tak': ['file.tak'],
+ \ 'tal': ['file.tal'],
\ 'taskdata': ['pending.data', 'completed.data', 'undo.data'],
\ 'taskedit': ['file.task'],
\ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'],
+ \ 'tablegen': ['file.td'],
\ 'teal': ['file.tl'],
\ 'template': ['file.tmpl'],
\ 'teraterm': ['file.ttl'],
@@ -603,51 +674,59 @@ let s:filename_checks = {
\ 'typescript': ['file.mts', 'file.cts'],
\ 'typescript.glimmer': ['file.gts'],
\ 'typescriptreact': ['file.tsx'],
+ \ 'ungrammar': ['file.ungram'],
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'],
\ 'udevperm': ['/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'],
\ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
\ 'uil': ['file.uit', 'file.uil'],
+ \ 'unison': ['file.u', 'file.uu'],
\ 'updatedb': ['/etc/updatedb.conf', 'any/etc/updatedb.conf'],
\ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'],
\ 'upstreamdat': ['upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'],
\ 'upstreaminstalllog': ['upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'],
\ 'upstreamlog': ['fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'],
+ \ 'urlshortcut': ['file.url'],
+ \ 'usd': ['file.usda', 'file.usd'],
\ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'],
\ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'],
+ \ 'v': ['file.vsh', 'file.vv'],
\ 'vala': ['file.vala'],
- \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'],
+ \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl', 'file.dob', 'file.dsr'],
\ 'vdf': ['file.vdf'],
\ 'vdmpp': ['file.vpp', 'file.vdmpp'],
\ 'vdmrt': ['file.vdmrt'],
\ 'vdmsl': ['file.vdm', 'file.vdmsl'],
\ 'vera': ['file.vr', 'file.vri', 'file.vrh'],
- \ 'verilog': ['file.v'],
\ 'verilogams': ['file.va', 'file.vams'],
\ 'vgrindefs': ['vgrindefs'],
\ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123', 'file.vho', 'some.vhdl_1', 'some.vhdl_1-file'],
\ 'vhs': ['file.tape'],
- \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'],
+ \ 'vim': ['file.vim', '.exrc', '_exrc', 'some-vimrc', 'some-vimrc-file', 'vimrc', 'vimrc-file'],
\ 'viminfo': ['.viminfo', '_viminfo'],
\ 'vmasm': ['file.mar'],
\ 'voscm': ['file.cm'],
\ 'vrml': ['file.wrl'],
\ 'vroom': ['file.vroom'],
\ 'vue': ['file.vue'],
- \ 'wast': ['file.wast', 'file.wat'],
+ \ 'wat': ['file.wat', 'file.wast'],
\ 'wdl': ['file.wdl'],
\ 'webmacro': ['file.wm'],
\ 'wget': ['.wgetrc', 'wgetrc'],
\ 'wget2': ['.wget2rc', 'wget2rc'],
+ \ 'wgsl': ['file.wgsl'],
\ 'winbatch': ['file.wbt'],
+ \ 'wit': ['file.wit'],
\ 'wml': ['file.wml'],
\ 'wsh': ['file.wsf', 'file.wsc'],
\ 'wsml': ['file.wsml'],
\ 'wvdial': ['wvdial.conf', '.wvdialrc'],
+ \ 'xcompose': ['.XCompose', 'Compose'],
\ 'xdefaults': ['.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad', '/Xresources/file', '/app-defaults/file', 'Xresources', 'Xresources-file', 'any/Xresources/file', 'any/app-defaults/file'],
\ 'xf86conf': ['xorg.conf', 'xorg.conf-4'],
\ 'xhtml': ['file.xhtml', 'file.xht'],
\ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'],
+ \ 'xkb': ['/usr/share/X11/xkb/compat/pc', '/usr/share/X11/xkb/geometry/pc', '/usr/share/X11/xkb/keycodes/evdev', '/usr/share/X11/xkb/symbols/pc', '/usr/share/X11/xkb/types/pc'],
\ 'xmath': ['file.msc', 'file.msf'],
\ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'],
\ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'],
@@ -658,21 +737,23 @@ let s:filename_checks = {
\ 'xsd': ['file.xsd'],
\ 'xslt': ['file.xsl', 'file.xslt'],
\ 'yacc': ['file.yy', 'file.yxx', 'file.y++'],
- \ 'yaml': ['file.yaml', 'file.yml', '.clangd', '.clang-format', '.clang-tidy'],
+ \ 'yaml': ['file.yaml', 'file.yml', 'file.eyaml', '.clangd', '.clang-format', '.clang-tidy'],
\ 'yang': ['file.yang'],
+ \ 'yuck': ['file.yuck'],
\ 'z8a': ['file.z8a'],
- \ 'zig': ['file.zig'],
+ \ 'zig': ['file.zig', 'build.zig.zon'],
\ 'zimbu': ['file.zu'],
\ 'zimbutempl': ['file.zut'],
- \ 'zir': ['file.zir'],
+ \ 'zserio': ['file.zs'],
\ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'],
\
\ 'help': [$VIMRUNTIME . '/doc/help.txt'],
\ }
+endfunc
let s:filename_case_checks = {
\ 'modula2': ['file.DEF'],
- \ 'bzl': ['file.BUILD', 'BUILD'],
+ \ 'bzl': ['file.BUILD', 'BUILD', 'BUCK'],
\ }
func CheckItems(checks)
@@ -681,31 +762,43 @@ func CheckItems(checks)
for i in range(0, len(names) - 1)
new
try
- exe 'edit ' . fnameescape(names[i])
+ exe 'edit ' .. fnameescape(names[i])
catch
- call assert_report('cannot edit "' . names[i] . '": ' . v:exception)
+ call assert_report('cannot edit "' .. names[i] .. '": ' .. v:exception)
endtry
if &filetype == '' && &readonly
" File exists but not able to edit it (permission denied)
else
let expected = ft == 'none' ? '' : ft
- call assert_equal(expected, &filetype, 'with file name: ' . names[i])
+ call assert_equal(expected, &filetype, 'with file name: ' .. names[i])
endif
bwipe!
endfor
endfor
+
set swapfile&
endfunc
func Test_filetype_detection()
+ call s:SetupConfigHome()
+ if !empty(s:saveConfigHome)
+ defer setenv("XDG_CONFIG_HOME", s:saveConfigHome)
+ endif
+ call mkdir(s:GetConfigHome(), 'R')
+
filetype on
- call CheckItems(s:filename_checks)
+ call CheckItems(s:GetFilenameChecks())
if has('fname_case')
call CheckItems(s:filename_case_checks)
endif
filetype off
endfunc
+" Content lines that should not result in filetype detection
+let s:false_positive_checks = {
+ \ '': [['test execve("/usr/bin/pstree", ["pstree"], 0x7ff0 /* 63 vars */) = 0']],
+ \ }
+
" Filetypes detected from the file contents by scripts.vim
let s:script_checks = {
\ 'virata': [['% Virata'],
@@ -735,6 +828,7 @@ let s:script_checks = {
\ 'expect': [['#!/path/expect']],
\ 'gnuplot': [['#!/path/gnuplot']],
\ 'make': [['#!/path/make']],
+ \ 'nix': [['#!/path/nix-shell']],
\ 'pike': [['#!/path/pike'],
\ ['#!/path/pike0'],
\ ['#!/path/pike9']],
@@ -771,6 +865,11 @@ let s:script_checks = {
\ 'fish': [['#!/path/fish']],
\ 'forth': [['#!/path/gforth']],
\ 'icon': [['#!/path/icon']],
+ \ 'crystal': [['#!/path/crystal']],
+ \ 'rexx': [['#!/path/rexx'],
+ \ ['#!/path/regina']],
+ \ 'janet': [['#!/path/janet']],
+ \ 'dart': [['#!/path/dart']],
\ }
" Various forms of "env" optional arguments.
@@ -781,23 +880,24 @@ let s:script_env_checks = {
\ 'scheme': [['#!/usr/bin/env VAR=val --ignore-environment scheme']],
\ 'python': [['#!/usr/bin/env VAR=val -S python -w -T']],
\ 'wml': [['#!/usr/bin/env VAR=val --split-string wml']],
+ \ 'nix': [['#!/usr/bin/env nix-shell']],
\ }
func Run_script_detection(test_dict)
filetype on
for [ft, files] in items(a:test_dict)
for file in files
- call writefile(file, 'Xtest')
+ call writefile(file, 'Xtest', 'D')
split Xtest
call assert_equal(ft, &filetype, 'for text: ' . string(file))
bwipe!
endfor
endfor
- call delete('Xtest')
filetype off
endfunc
func Test_script_detection()
+ call Run_script_detection(s:false_positive_checks)
call Run_script_detection(s:script_checks)
call Run_script_detection(s:script_env_checks)
endfunc
@@ -839,7 +939,7 @@ endfunc
func Test_bas_file()
filetype on
- call writefile(['looks like BASIC'], 'Xfile.bas')
+ call writefile(['looks like BASIC'], 'Xfile.bas', 'D')
split Xfile.bas
call assert_equal('basic', &filetype)
bwipe!
@@ -893,7 +993,6 @@ func Test_bas_file()
call assert_equal('vb', &filetype)
bwipe!
- call delete('Xfile.bas')
filetype off
endfunc
@@ -902,7 +1001,7 @@ func Test_cfg_file()
filetype on
" *.cfg defaults to cfg
- call writefile(['looks like cfg'], 'cfgfile.cfg')
+ call writefile(['looks like cfg'], 'cfgfile.cfg', 'D')
split cfgfile.cfg
call assert_equal('cfg', &filetype)
@@ -931,7 +1030,7 @@ endfunc
func Test_d_file()
filetype on
- call writefile(['looks like D'], 'Xfile.d')
+ call writefile(['looks like D'], 'Xfile.d', 'D')
split Xfile.d
call assert_equal('d', &filetype)
bwipe!
@@ -963,7 +1062,6 @@ func Test_d_file()
" clean up
filetype off
- call delete('Xfile.d')
endfunc
func Test_dat_file()
@@ -1004,7 +1102,7 @@ endfunc
func Test_dep3patch_file()
filetype on
- call assert_true(mkdir('debian/patches', 'p'))
+ call assert_true(mkdir('debian/patches', 'pR'))
" series files are not patches
call writefile(['Description: some awesome patch'], 'debian/patches/series')
@@ -1037,14 +1135,12 @@ func Test_dep3patch_file()
split debian/patches/baz
call assert_notequal('dep3patch', &filetype)
bwipe!
-
- call delete('debian', 'rf')
endfunc
func Test_dsl_file()
filetype on
- call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl')
+ call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl', 'D')
split dslfile.dsl
call assert_equal('dsl', &filetype)
bwipe!
@@ -1054,14 +1150,13 @@ func Test_dsl_file()
call assert_equal('structurizr', &filetype)
bwipe!
- call delete('dslfile.dsl')
filetype off
endfunc
func Test_ex_file()
filetype on
- call writefile(['arbitrary content'], 'Xfile.ex')
+ call writefile(['arbitrary content'], 'Xfile.ex', 'D')
split Xfile.ex
call assert_equal('elixir', &filetype)
bwipe!
@@ -1091,31 +1186,78 @@ func Test_ex_file()
call assert_equal('euphoria3', &filetype)
bwipe!
- call delete('Xfile.ex')
+ filetype off
+endfunc
+
+func Test_f_file()
+ filetype on
+
+ call writefile(['looks like Fortran'], 'Xfile.f', 'D')
+ split Xfile.f
+ call assert_equal('fortran', &filetype)
+ bwipe!
+
+ let g:filetype_f = 'forth'
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+ unlet g:filetype_f
+
+ " Test dist#ft#FTf()
+
+ " Forth
+
+ call writefile(['( Forth inline comment )'], 'Xfile.f')
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['\ Forth line comment'], 'Xfile.f')
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.f')
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ " SwiftForth
+
+ call writefile(['{ ================', 'Header comment', '================ }'], 'Xfile.f')
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+
+ call writefile(['OPTIONAL Maybe Descriptive text'], 'Xfile.f')
+ split Xfile.f
+ call assert_equal('forth', &filetype)
+ bwipe!
+
filetype off
endfunc
func Test_foam_file()
filetype on
- call assert_true(mkdir('0', 'p'))
- call assert_true(mkdir('0.orig', 'p'))
+ call assert_true(mkdir('0', 'pR'))
+ call assert_true(mkdir('0.orig', 'pR'))
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict')
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict', 'D')
split Xfile1Dict
call assert_equal('foam', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something')
+ call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something', 'D')
split Xfile1Dict.something
call assert_equal('foam', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties')
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties', 'D')
split XfileProperties
call assert_equal('foam', &filetype)
bwipe!
- call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something')
+ call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something', 'D')
split XfileProperties.something
call assert_equal('foam', &filetype)
bwipe!
@@ -1140,19 +1282,13 @@ func Test_foam_file()
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')
+ call writefile(['looks like FORM'], 'Xfile.frm', 'D')
split Xfile.frm
call assert_equal('form', &filetype)
bwipe!
@@ -1167,19 +1303,18 @@ func Test_frm_file()
" Visual Basic
- call writefile(['Begin VB.Form Form1'], 'Xfile.frm')
+ call writefile(['VERSION 5.00', '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')
+ call writefile(['looks like F#'], 'Xfile.fs', 'D')
split Xfile.fs
call assert_equal('fsharp', &filetype)
bwipe!
@@ -1192,47 +1327,42 @@ func Test_fs_file()
" Test dist#ft#FTfs()
- " Forth (Gforth)
+ " Forth
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')
+ call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
split Xfile.fs
call assert_equal('forth', &filetype)
bwipe!
- call writefile(['\G Forth documentation comment '], 'Xfile.fs')
+ " SwiftForth
+
+ call writefile(['{ ================', 'Header comment', '================ }'], 'Xfile.fs')
split Xfile.fs
call assert_equal('forth', &filetype)
bwipe!
- call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs')
+ call writefile(['OPTIONAL Maybe Descriptive text'], '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 assert_true(mkdir('Xrepo.git', 'pR'))
call writefile([], 'Xrepo.git/HEAD')
split Xrepo.git/HEAD
@@ -1254,31 +1384,29 @@ func Test_git_file()
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')
+ call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook', 'D')
split Xfile.hook
- call assert_equal('conf', &filetype)
+ call assert_equal('confini', &filetype)
bwipe!
call writefile(['not pacman'], 'Xfile.hook')
split Xfile.hook
- call assert_notequal('conf', &filetype)
+ call assert_notequal('confini', &filetype)
bwipe!
- call delete('Xfile.hook')
filetype off
endfunc
func Test_m_file()
filetype on
- call writefile(['looks like Matlab'], 'Xfile.m')
+ call writefile(['looks like Matlab'], 'Xfile.m', 'D')
split Xfile.m
call assert_equal('matlab', &filetype)
bwipe!
@@ -1371,7 +1499,6 @@ func Test_m_file()
call assert_equal('murphi', &filetype)
bwipe!
- call delete('Xfile.m')
filetype off
endfunc
@@ -1447,13 +1574,19 @@ func Test_mod_file()
bwipe!
call delete('go.mod')
+ call writefile(['module M'], 'go.mod')
+ split go.mod
+ call assert_equal('gomod', &filetype)
+ bwipe!
+ call delete('go.mod')
+
filetype off
endfunc
func Test_patch_file()
filetype on
- call writefile([], 'Xfile.patch')
+ call writefile([], 'Xfile.patch', 'D')
split Xfile.patch
call assert_equal('diff', &filetype)
bwipe!
@@ -1468,7 +1601,6 @@ func Test_patch_file()
call assert_equal('gitsendemail', &filetype)
bwipe!
- call delete('Xfile.patch')
filetype off
endfunc
@@ -1480,19 +1612,18 @@ func Test_perl_file()
use a
END
- call writefile(lines, "Xfile.t")
+ call writefile(lines, "Xfile.t", 'D')
split Xfile.t
call assert_equal('perl', &filetype)
bwipe
- call delete('Xfile.t')
filetype off
endfunc
func Test_pp_file()
filetype on
- call writefile(['looks like puppet'], 'Xfile.pp')
+ call writefile(['looks like puppet'], 'Xfile.pp', 'D')
split Xfile.pp
call assert_equal('puppet', &filetype)
bwipe!
@@ -1514,7 +1645,6 @@ func Test_pp_file()
call assert_equal('pascal', &filetype)
bwipe!
- call delete('Xfile.pp')
filetype off
endfunc
@@ -1594,12 +1724,11 @@ endfunc
func Test_scd_file()
filetype on
- call writefile(['ijq(1)'], 'srcfile.scd')
+ call writefile(['ijq(1)'], 'srcfile.scd', 'D')
split srcfile.scd
call assert_equal('scdoc', &filetype)
- bwipe!
- call delete('srcfile.scd')
+ bwipe!
filetype off
endfunc
@@ -1731,7 +1860,7 @@ endfunc
func Test_tf_file()
filetype on
- call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf')
+ call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf', 'D')
split Xfile.tf
call assert_equal('tf', &filetype)
bwipe!
@@ -1741,14 +1870,13 @@ func Test_tf_file()
call assert_equal('terraform', &filetype)
bwipe!
- call delete('Xfile.tf')
filetype off
endfunc
func Test_ts_file()
filetype on
- call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts')
+ call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts', 'D')
split Xfile.ts
call assert_equal('xml', &filetype)
bwipe!
@@ -1758,14 +1886,13 @@ func Test_ts_file()
call assert_equal('typescript', &filetype)
bwipe!
- call delete('Xfile.ts')
filetype off
endfunc
func Test_ttl_file()
filetype on
- call writefile(['@base <http://example.org/> .'], 'Xfile.ttl')
+ call writefile(['@base <http://example.org/> .'], 'Xfile.ttl', 'D')
split Xfile.ttl
call assert_equal('turtle', &filetype)
bwipe!
@@ -1775,26 +1902,45 @@ func Test_ttl_file()
call assert_equal('teraterm', &filetype)
bwipe!
- call delete('Xfile.ttl')
+ filetype off
+endfunc
+
+func Test_v_file()
+ filetype on
+
+ call writefile(['module tb; // Looks like a Verilog'], 'Xfile.v', 'D')
+ split Xfile.v
+ call assert_equal('verilog', &filetype)
+ bwipe!
+
+ call writefile(['module main'], 'Xfile.v')
+ split Xfile.v
+ call assert_equal('v', &filetype)
+ bwipe!
+
+ call writefile(['Definition x := 10. (*'], 'Xfile.v')
+ split Xfile.v
+ call assert_equal('coq', &filetype)
+ bwipe!
+
filetype off
endfunc
func Test_xpm_file()
filetype on
- call writefile(['this is XPM2'], 'file.xpm')
+ call writefile(['this is XPM2'], 'file.xpm', 'D')
split file.xpm
call assert_equal('xpm2', &filetype)
bwipe!
- call delete('file.xpm')
filetype off
endfunc
func Test_cls_file()
filetype on
- call writefile(['looks like Smalltalk'], 'Xfile.cls')
+ call writefile(['looks like Smalltalk'], 'Xfile.cls', 'D')
split Xfile.cls
call assert_equal('st', &filetype)
bwipe!
@@ -1821,7 +1967,22 @@ func Test_cls_file()
" Rexx
- call writefile(['# rexx'], 'Xfile.cls')
+ call writefile(['#!/usr/bin/rexx'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('rexx', &filetype)
+ bwipe!
+
+ call writefile(['#!/usr/bin/regina'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('rexx', &filetype)
+ bwipe!
+
+ call writefile(['/* Comment */'], 'Xfile.cls')
+ split Xfile.cls
+ call assert_equal('rexx', &filetype)
+ bwipe!
+
+ call writefile(['::class Foo subclass Bar public'], 'Xfile.cls')
split Xfile.cls
call assert_equal('rexx', &filetype)
bwipe!
@@ -1833,14 +1994,13 @@ func Test_cls_file()
call assert_equal('vb', &filetype)
bwipe!
- call delete('Xfile.cls')
filetype off
endfunc
func Test_sig_file()
filetype on
- call writefile(['this is neither Lambda Prolog nor SML'], 'Xfile.sig')
+ call writefile(['this is neither Lambda Prolog nor SML'], 'Xfile.sig', 'D')
split Xfile.sig
call assert_equal('', &filetype)
bwipe!
@@ -1887,7 +2047,6 @@ func Test_sig_file()
call assert_equal('sml', &filetype)
bwipe!
- call delete('Xfile.sig')
filetype off
endfunc
@@ -1907,7 +2066,7 @@ func Test_sil_file()
let protoErasedPathA =
\ABCProtocol.a
END
- call writefile(lines, 'Xfile.sil')
+ call writefile(lines, 'Xfile.sil', 'D')
split Xfile.sil
call assert_equal('sil', &filetype)
@@ -1925,14 +2084,13 @@ func Test_sil_file()
call assert_equal('sile', &filetype)
bwipe!
- call delete('Xfile.sil')
filetype off
endfunc
func Test_inc_file()
filetype on
- call writefile(['this is the fallback'], 'Xfile.inc')
+ call writefile(['this is the fallback'], 'Xfile.inc', 'D')
split Xfile.inc
call assert_equal('pov', &filetype)
bwipe!
@@ -1999,19 +2157,18 @@ func Test_inc_file()
bwipe!
" asm
- call writefile(['asmsyntax=bar'], 'Xfile.inc')
+ call writefile(['asmsyntax=foo'], 'Xfile.inc')
split Xfile.inc
- call assert_equal('bar', &filetype)
+ call assert_equal('foo', &filetype)
bwipe!
- call delete('Xfile.inc')
filetype off
endfunc
func Test_lsl_file()
filetype on
- call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl')
+ call writefile(['looks like Linden Scripting Language'], 'Xfile.lsl', 'D')
split Xfile.lsl
call assert_equal('lsl', &filetype)
bwipe!
@@ -2036,7 +2193,89 @@ func Test_lsl_file()
call assert_equal('larch', &filetype)
bwipe!
- call delete('Xfile.lsl')
+ filetype off
+endfunc
+
+func Test_typ_file()
+ filetype on
+
+ " SQL type file
+
+ call writefile(['CASE = LOWER'], 'Xfile.typ', 'D')
+ split Xfile.typ
+ call assert_equal('sql', &filetype)
+ bwipe!
+
+ call writefile(['TYPE foo'], 'Xfile.typ')
+ split Xfile.typ
+ call assert_equal('sql', &filetype)
+ bwipe!
+
+ " typst document
+
+ call writefile(['this is a fallback'], 'Xfile.typ')
+ split Xfile.typ
+ call assert_equal('typst', &filetype)
+ bwipe!
+
+ let g:filetype_typ = 'typst'
+ split test.typ
+ call assert_equal('typst', &filetype)
+ bwipe!
+ unlet g:filetype_typ
+
+ filetype off
+endfunc
+
+func Test_vba_file()
+ filetype on
+
+ " Test dist#ft#FTvba()
+
+ " Visual Basic
+
+ call writefile(['looks like Visual Basic'], 'Xfile.vba', 'D')
+ split Xfile.vba
+ call assert_equal('vb', &filetype)
+ bwipe!
+
+ " Vimball Archiver (ft=vim)
+
+ call writefile(['" Vimball Archiver by Charles E. Campbell, Ph.D.', 'UseVimball', 'finish'], 'Xfile.vba', 'D')
+ split Xfile.vba
+ call assert_equal('vim', &filetype)
+ bwipe!
+
+ filetype off
+endfunc
+
+func Test_i_file()
+ filetype on
+
+ " Swig: keyword
+ call writefile(['%module mymodule', '/* a comment */'], 'Xfile.i', 'D')
+ split Xfile.i
+ call assert_equal('swig', &filetype)
+ bwipe!
+
+ " Swig: verbatim block
+ call writefile(['%{', '#include <header.hpp>', '%}'], 'Xfile.i', 'D')
+ split Xfile.i
+ call assert_equal('swig', &filetype)
+ bwipe!
+
+ " ASM
+ call writefile(['; comment', ';'], 'Xfile.i', 'D')
+ split Xfile.i
+ call assert_equal('asm', &filetype)
+ bwipe!
+
+ " *.i defaults to progress
+ call writefile(['looks like progress'], 'Xfile.i', 'D')
+ split Xfile.i
+ call assert_equal('progress', &filetype)
+ bwipe!
+
filetype off
endfunc
diff --git a/src/nvim/testdir/test_filter_cmd.vim b/test/old/testdir/test_filter_cmd.vim
index dae164b11c..b84081e384 100644
--- a/src/nvim/testdir/test_filter_cmd.vim
+++ b/test/old/testdir/test_filter_cmd.vim
@@ -1,5 +1,7 @@
" Test the :filter command modifier
+source check.vim
+
func Test_filter()
edit Xdoesnotmatch
edit Xwillmatch
@@ -97,6 +99,8 @@ func Test_filter_cmd_with_filter()
endfunction
func Test_filter_commands()
+ CheckFeature quickfix
+
let g:test_filter_a = 1
let b:test_filter_b = 2
let test_filter_c = 3
diff --git a/test/old/testdir/test_filter_map.vim b/test/old/testdir/test_filter_map.vim
new file mode 100644
index 0000000000..7658797759
--- /dev/null
+++ b/test/old/testdir/test_filter_map.vim
@@ -0,0 +1,242 @@
+" Test filter() and map()
+
+source vim9.vim
+
+" list with expression string
+func Test_filter_map_list_expr_string()
+ " filter()
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], 'v:val > 1'))
+ call assert_equal([3, 4], filter([1, 2, 3, 4], 'v:key > 1'))
+ call assert_equal([], filter([1, 2, 3, 4], 0))
+
+ " map()
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
+ call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+ call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
+endfunc
+
+" dict with expression string
+func Test_filter_map_dict_expr_string()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), 'v:val > 1'))
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), 'v:key > "bar"'))
+ call assert_equal({}, filter(copy(dict), 0))
+
+ " map()
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), 'v:val * 2'))
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), 'v:key[0]'))
+ call assert_equal({"foo": 9, "bar": 9, "baz": 9}, map(copy(dict), 9))
+endfunc
+
+" list with funcref
+func Test_filter_map_list_expr_funcref()
+ " filter()
+ func! s:filter1(index, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal([2, 3, 4], filter([1, 2, 3, 4], function('s:filter1')))
+
+ func! s:filter2(index, val) abort
+ return a:index > 1
+ endfunc
+ call assert_equal([3, 4], filter([1, 2, 3, 4], function('s:filter2')))
+
+ " map()
+ func! s:filter3(index, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], function('s:filter3')))
+
+ func! s:filter4(index, val) abort
+ return a:index * 2
+ endfunc
+ call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], function('s:filter4')))
+endfunc
+
+" dict with funcref
+func Test_filter_map_dict_expr_funcref()
+ let dict = {"foo": 1, "bar": 2, "baz": 3}
+
+ " filter()
+ func! s:filter1(key, val) abort
+ return a:val > 1
+ endfunc
+ call assert_equal({"bar": 2, "baz": 3}, filter(copy(dict), function('s:filter1')))
+
+ func! s:filter2(key, val) abort
+ return a:key > "bar"
+ endfunc
+ call assert_equal({"foo": 1, "baz": 3}, filter(copy(dict), function('s:filter2')))
+
+ " map()
+ func! s:filter3(key, val) abort
+ return a:val * 2
+ endfunc
+ call assert_equal({"foo": 2, "bar": 4, "baz": 6}, map(copy(dict), function('s:filter3')))
+
+ func! s:filter4(key, val) abort
+ return a:key[0]
+ endfunc
+ call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4')))
+endfunc
+
+func Test_map_filter_fails()
+ call assert_fails('call map([1], "42 +")', 'E15:')
+ call assert_fails('call filter([1], "42 +")', 'E15:')
+ call assert_fails("let l = filter([1, 2, 3], '{}')", 'E728:')
+ call assert_fails("let l = filter({'k' : 10}, '{}')", 'E728:')
+ call assert_fails("let l = filter([1, 2], {})", 'E731:')
+ call assert_equal(v:_null_list, filter(v:_null_list, 0))
+ call assert_equal(v:_null_dict, filter(v:_null_dict, 0))
+ call assert_equal(v:_null_list, map(v:_null_list, '"> " .. v:val'))
+ call assert_equal(v:_null_dict, map(v:_null_dict, '"> " .. v:val'))
+ " Nvim doesn't have null functions
+ " call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_function()))
+ call assert_fails("let l = filter([1, 2], function('min'))", 'E118:')
+ " Nvim doesn't have null partials
+ " call assert_equal([1, 2, 3], filter([1, 2, 3], test_null_partial()))
+ call assert_fails("let l = filter([1, 2], {a, b, c -> 1})", 'E119:')
+endfunc
+
+func Test_map_and_modify()
+ let l = ["abc"]
+ " cannot change the list halfway a map()
+ call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:')
+
+ let d = #{a: 1, b: 2, c: 3}
+ call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:')
+ call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:')
+
+ let b = 0z1234
+ call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
+endfunc
+
+func Test_filter_and_modify()
+ let l = [0]
+ " cannot change the list halfway a map()
+ call assert_fails('call filter(l, "remove(l, 0)")', 'E741:')
+
+ let d = #{a: 0, b: 0, c: 0}
+ call assert_fails('call filter(d, "remove(d, v:key)")', 'E741:')
+
+ let b = 0z1234
+ call assert_fails('call filter(b, "remove(b, 0)")', 'E741:')
+endfunc
+
+func Test_mapnew_dict()
+ let din = #{one: 1, two: 2}
+ let dout = mapnew(din, {k, v -> string(v)})
+ call assert_equal(#{one: 1, two: 2}, din)
+ call assert_equal(#{one: '1', two: '2'}, dout)
+
+ const dconst = #{one: 1, two: 2, three: 3}
+ call assert_equal(#{one: 2, two: 3, three: 4}, mapnew(dconst, {_, v -> v + 1}))
+endfunc
+
+func Test_mapnew_list()
+ let lin = [1, 2, 3]
+ let lout = mapnew(lin, {k, v -> string(v)})
+ call assert_equal([1, 2, 3], lin)
+ call assert_equal(['1', '2', '3'], lout)
+
+ const lconst = [1, 2, 3]
+ call assert_equal([2, 3, 4], mapnew(lconst, {_, v -> v + 1}))
+endfunc
+
+func Test_mapnew_blob()
+ let bin = 0z123456
+ let bout = mapnew(bin, {k, v -> k == 1 ? 0x99 : v})
+ call assert_equal(0z123456, bin)
+ call assert_equal(0z129956, bout)
+endfunc
+
+" Test for using map(), filter() and mapnew() with a string
+func Test_filter_map_string()
+ " filter()
+ let lines =<< trim END
+ VAR s = "abc"
+ call filter(s, '"b" != v:val')
+ call assert_equal('abc', s)
+ call assert_equal('ac', filter('abc', '"b" != v:val'))
+ call assert_equal('あいうえお', filter('あxいxうxえxお', '"x" != v:val'))
+ call assert_equal('あa😊💕💕b💕', filter('あxax😊x💕💕b💕x', '"x" != v:val'))
+ call assert_equal('xxxx', filter('あxax😊x💕💕b💕x', '"x" == v:val'))
+ VAR t = "%),:;>?]}’”†‡…‰,‱‼⁇⁈⁉℃℉,、。〉》」,』】〕〗〙〛,!),.:,;?,]}"
+ VAR u = "%):;>?]}’”†‡…‰‱‼⁇⁈⁉℃℉、。〉》」』】〕〗〙〛!),.:;?]}"
+ call assert_equal(u, filter(t, '"," != v:val'))
+ call assert_equal('', filter('abc', '0'))
+ call assert_equal('ac', filter('abc', LSTART i, x LMIDDLE "b" != x LEND))
+ call assert_equal('あいうえお', filter('あxいxうxえxお', LSTART i, x LMIDDLE "x" != x LEND))
+ call assert_equal('', filter('abc', LSTART i, x LMIDDLE v:false LEND))
+ call assert_equal('', filter('', "v:val == 'a'"))
+ call assert_equal('', filter(v:_null_string, "v:val == 'a'"))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " map()
+ let lines =<< trim END
+ VAR s = "abc"
+ call map(s, 'nr2char(char2nr(v:val) + 2)')
+ call assert_equal('abc', s)
+ call assert_equal('cde', map('abc', 'nr2char(char2nr(v:val) + 2)'))
+ call assert_equal('[あ][i][う][え][お]', map('あiうえお', '"[" .. v:val .. "]"'))
+ call assert_equal('[あ][a][😊][,][‱][‼][⁇][⁈][⁉][💕][b][💕][c][💕]', map('あa😊,‱‼⁇⁈⁉💕b💕c💕', '"[" .. v:val .. "]"'))
+ call assert_equal('', map('abc', '""'))
+ call assert_equal('cde', map('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
+ call assert_equal('[あ][i][う][え][お]', map('あiうえお', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
+ call assert_equal('', map('abc', LSTART i, x LMIDDLE '' LEND))
+ call assert_equal('', map('', "v:val == 'a'"))
+ call assert_equal('', map(v:_null_string, "v:val == 'a'"))
+ call assert_fails('echo map("abc", "10")', 'E928:')
+ call assert_fails('echo map("abc", "a10")', 'E121:')
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " mapnew()
+ let lines =<< trim END
+ VAR s = "abc"
+ call mapnew(s, 'nr2char(char2nr(v:val) + 2)')
+ call assert_equal('abc', s)
+ call assert_equal('cde', mapnew('abc', 'nr2char(char2nr(v:val) + 2)'))
+ call assert_equal('[あ][i][う][え][お]', mapnew('あiうえお', '"[" .. v:val .. "]"'))
+ call assert_equal('[あ][a][😊][,][‱][‼][⁇][⁈][⁉][💕][b][💕][c][💕]', mapnew('あa😊,‱‼⁇⁈⁉💕b💕c💕', '"[" .. v:val .. "]"'))
+ call assert_equal('', mapnew('abc', '""'))
+ call assert_equal('cde', mapnew('abc', LSTART i, x LMIDDLE nr2char(char2nr(x) + 2) LEND))
+ call assert_equal('[あ][i][う][え][お]', mapnew('あiうえお', LSTART i, x LMIDDLE '[' .. x .. ']' LEND))
+ call assert_equal('', mapnew('abc', LSTART i, x LMIDDLE '' LEND))
+ call assert_equal('', mapnew('', "v:val == 'a'"))
+ call assert_equal('', mapnew(v:_null_string, "v:val == 'a'"))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ #" map() and filter()
+ call assert_equal('[あ][⁈][a][😊][⁉][💕][💕][b][💕]', map(filter('あx⁈ax😊x⁉💕💕b💕x', '"x" != v:val'), '"[" .. v:val .. "]"'))
+
+ #" patterns-composing(\Z)
+ call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) LEND))
+ call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ '\Za' LEND))
+ call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) LEND))
+ call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) LEND))
+ call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0x0960) ? '@' : x LEND))
+ call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ '\Za' ? '@' : x LEND))
+ call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xc5) ? '@' : x LEND))
+ call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ '\Z' .. nr2char(0xf6) ? '@' : x LEND))
+
+ #" patterns-composing(\%C)
+ call assert_equal('ॠॠ', filter('ऊॠॡ,ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' LEND))
+ call assert_equal('àà', filter('càt,càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' LEND))
+ call assert_equal('ÅÅ', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' LEND))
+ call assert_equal('öö', filter('Åström,Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' LEND))
+ call assert_equal('ऊ@ॡ', map('ऊॠॡ', LSTART i, x LMIDDLE x =~ nr2char(0x0960) .. '\%C' ? '@' : x LEND))
+ call assert_equal('c@t', map('càt', LSTART i, x LMIDDLE x =~ 'a' .. '\%C' ? '@' : x LEND))
+ call assert_equal('@ström', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xc5) .. '\%C' ? '@' : x LEND))
+ call assert_equal('Åstr@m', map('Åström', LSTART i, x LMIDDLE x =~ nr2char(0xf6) .. '\%C' ? '@' : x LEND))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_find_complete.vim b/test/old/testdir/test_find_complete.vim
index 32ca9672ef..32ca9672ef 100644
--- a/src/nvim/testdir/test_find_complete.vim
+++ b/test/old/testdir/test_find_complete.vim
diff --git a/src/nvim/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim
index 0f4b30aec2..0f4b30aec2 100644
--- a/src/nvim/testdir/test_findfile.vim
+++ b/test/old/testdir/test_findfile.vim
diff --git a/src/nvim/testdir/test_fixeol.vim b/test/old/testdir/test_fixeol.vim
index 41d47d6a06..41d47d6a06 100644
--- a/src/nvim/testdir/test_fixeol.vim
+++ b/test/old/testdir/test_fixeol.vim
diff --git a/src/nvim/testdir/test_flatten.vim b/test/old/testdir/test_flatten.vim
index 99086611e1..aa91060313 100644
--- a/src/nvim/testdir/test_flatten.vim
+++ b/test/old/testdir/test_flatten.vim
@@ -78,4 +78,31 @@ func Test_flatten()
call add(y, x) " l:y = [2, [1, [...]]]
call assert_equal([1, 2, 1, 2], flatten(l:x, 2))
call assert_equal([2, l:x], l:y)
+
+ let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ]
+ call assert_equal(l4, flatten(deepcopy(l4), 0))
+ call assert_equal([1, 11, [101, [1001]]], flatten(deepcopy(l4), 1))
+ call assert_equal([1, 11, 101, [1001]], flatten(deepcopy(l4), 2))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 3))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4), 4))
+ call assert_equal([1, 11, 101, 1001], flatten(deepcopy(l4)))
+endfunc
+
+func Test_flattennew()
+ let l = [1, [2, [3, 4]], 5]
+ call assert_equal([1, 2, 3, 4, 5], flattennew(l))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ call assert_equal([1, 2, [3, 4], 5], flattennew(l, 1))
+ call assert_equal([1, [2, [3, 4]], 5], l)
+
+ let l4 = [ 1, [ 11, [ 101, [ 1001 ] ] ] ]
+ call assert_equal(l4, flatten(deepcopy(l4), 0))
+ call assert_equal([1, 11, [101, [1001]]], flattennew(l4, 1))
+ call assert_equal([1, 11, 101, [1001]], flattennew(l4, 2))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4, 3))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4, 4))
+ call assert_equal([1, 11, 101, 1001], flattennew(l4))
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_float_func.vim b/test/old/testdir/test_float_func.vim
index 902a011a9d..f9a7251d59 100644
--- a/src/nvim/testdir/test_float_func.vim
+++ b/test/old/testdir/test_float_func.vim
@@ -2,6 +2,7 @@
source check.vim
CheckFeature float
+source vim9.vim
func Test_abs()
call assert_equal('1.23', string(abs(1.23)))
@@ -238,7 +239,9 @@ func Test_str2float()
call assert_equal("str2float('nan')", string(str2float('NaN')))
call assert_equal("str2float('nan')", string(str2float(' nan ')))
- call assert_fails("call str2float(1.2)", 'E806:')
+ call assert_equal(1.2, str2float(1.2))
+ call CheckDefExecFailure(['str2float(1.2)'], 'E1013:')
+ call CheckScriptFailure(['vim9script', 'str2float(1.2)'], 'E806:')
call assert_fails("call str2float([])", 'E730:')
call assert_fails("call str2float({})", 'E731:')
call assert_fails("call str2float(function('string'))", 'E729:')
diff --git a/src/nvim/testdir/test_fnameescape.vim b/test/old/testdir/test_fnameescape.vim
index cdb96ba5ff..cdb96ba5ff 100644
--- a/src/nvim/testdir/test_fnameescape.vim
+++ b/test/old/testdir/test_fnameescape.vim
diff --git a/src/nvim/testdir/test_fnamemodify.vim b/test/old/testdir/test_fnamemodify.vim
index 258a2093bd..258a2093bd 100644
--- a/src/nvim/testdir/test_fnamemodify.vim
+++ b/test/old/testdir/test_fnamemodify.vim
diff --git a/src/nvim/testdir/test_fold.vim b/test/old/testdir/test_fold.vim
index 9014948fb4..e529a94174 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/test/old/testdir/test_fold.vim
@@ -1548,4 +1548,34 @@ func Test_expand_fold_at_bottom_of_buffer()
bwipe!
endfunc
+func Test_fold_screenrow_motion()
+ call setline(1, repeat(['aaaa'], 5))
+ 1,4fold
+ norm Ggkzo
+ call assert_equal(1, line('.'))
+endfunc
+
+" This was using freed memory
+func Test_foldcolumn_linebreak_control_char()
+ CheckFeature linebreak
+
+ 5vnew
+ setlocal foldcolumn=1 linebreak
+ call setline(1, "aaa\<C-A>b")
+ redraw
+ call assert_equal([' aaa^', ' Ab '], ScreenLines([1, 2], 5))
+ call assert_equal(screenattr(1, 5), screenattr(2, 2))
+
+ bwipe!
+endfunc
+
+" This used to cause invalid memory access
+func Test_foldexpr_return_empty_string()
+ new
+ setlocal foldexpr='' foldmethod=expr
+ redraw
+
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_format.vim b/test/old/testdir/test_format.vim
new file mode 100644
index 0000000000..d3578e7165
--- /dev/null
+++ b/test/old/testdir/test_format.vim
@@ -0,0 +1,361 @@
+" Tests for expressions.
+
+source check.vim
+source vim9.vim
+
+func Test_printf_pos_misc()
+ let lines =<< trim END
+ call assert_equal('123', printf('%1$d', 123))
+ call assert_equal('', printf('%1$.0d', 0))
+ call assert_equal('00005', printf('%1$5.5d', 5))
+ call assert_equal('00005', printf('%1$*1$.5d', 5))
+ call assert_equal('00005', printf('%1$5.*1$d', 5))
+ call assert_equal('00005', printf('%1$*1$.*1$d', 5))
+ call assert_equal('00005', printf('%1$*10$.5d%2$.0d%3$.0d%4$.0d%5$.0d%6$.0d%7$.0d%8$.0d%9$.0d', 5, 0, 0, 0, 0, 0, 0, 0, 0, 5))
+ call assert_equal('00005', printf('%1$5.*10$d%2$.0d%3$.0d%4$.0d%5$.0d%6$.0d%7$.0d%8$.0d%9$.0d', 5, 0, 0, 0, 0, 0, 0, 0, 0, 5))
+ call assert_equal('123', printf('%1$i', 123))
+ call assert_equal('123', printf('%1$D', 123))
+ call assert_equal('123', printf('%1$U', 123))
+ call assert_equal('173', printf('%1$o', 123))
+ call assert_equal('173', printf('%1$O', 123))
+ call assert_equal('7b', printf('%1$x', 123))
+ call assert_equal('7B', printf('%1$X', 123))
+ call assert_equal('Printing 1 at width 1 gives: 1', 1->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+ call assert_equal('Printing 2 at width 2 gives: 2', 2->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+ call assert_equal('Printing 3 at width 3 gives: 3', 3->printf("Printing %1$d at width %1$d gives: %1$*1$d"))
+ call assert_equal('Printing 1 at width/precision 1.1 gives: 1', 1->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+ call assert_equal('Printing 2 at width/precision 2.2 gives: 02', 2->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+ call assert_equal('Printing 3 at width/precision 3.3 gives: 003', 3->printf("Printing %1$d at width/precision %1$d.%1$d gives: %1$*1$.*1$d"))
+
+ call assert_equal('123', printf('%1$hd', 123))
+ call assert_equal('-123', printf('%1$hd', -123))
+ call assert_equal('-1', printf('%1$hd', 0xFFFF))
+ call assert_equal('-1', printf('%1$hd', 0x1FFFFF))
+
+ call assert_equal('123', printf('%1$hu', 123))
+ call assert_equal('65413', printf('%1$hu', -123))
+ call assert_equal('65535', printf('%1$hu', 0xFFFF))
+ call assert_equal('65535', printf('%1$hu', 0x1FFFFF))
+
+ call assert_equal('123', printf('%1$ld', 123))
+ call assert_equal('-123', printf('%1$ld', -123))
+ call assert_equal('65535', printf('%1$ld', 0xFFFF))
+ call assert_equal('131071', printf('%1$ld', 0x1FFFF))
+
+ call assert_equal('{', printf('%1$c', 123))
+ call assert_equal('abc', printf('%1$s', 'abc'))
+ call assert_equal('abc', printf('%1$S', 'abc'))
+
+ call assert_equal('+123', printf('%1$+d', 123))
+ call assert_equal('-123', printf('%1$+d', -123))
+ call assert_equal('+123', printf('%1$+ d', 123))
+ call assert_equal(' 123', printf('%1$ d', 123))
+ call assert_equal(' 123', printf('%1$ d', 123))
+ call assert_equal('-123', printf('%1$ d', -123))
+
+ call assert_equal(' 123', printf('%2$*1$d', 5, 123))
+ call assert_equal('123 ', printf('%2$*1$d', -5, 123))
+ call assert_equal('00123', printf('%2$.*1$d', 5, 123))
+ call assert_equal(' 123', printf('%2$ *1$d', 5, 123))
+ call assert_equal(' +123', printf('%2$+ *1$d', 5, 123))
+
+ call assert_equal(' 123', printf('%1$*2$d', 123, 5))
+ call assert_equal('123 ', printf('%1$*2$d', 123, -5))
+ call assert_equal('00123', printf('%1$.*2$d', 123, 5))
+ call assert_equal(' 123', printf('%1$ *2$d', 123, 5))
+ call assert_equal(' +123', printf('%1$+ *2$d', 123, 5))
+
+ call assert_equal('foobar', printf('%2$.*1$s', 9, 'foobar'))
+ call assert_equal('foo', printf('%2$.*1$s', 3, 'foobar'))
+ call assert_equal('', printf('%2$.*1$s', 0, 'foobar'))
+ call assert_equal('foobar', printf('%2$.*1$s', -1, 'foobar'))
+
+ #" Unrecognized format specifier kept as-is.
+ call assert_equal('_123', printf("%_%1$d", 123))
+
+ #" Test alternate forms.
+ call assert_equal('0x7b', printf('%1$#x', 123))
+ call assert_equal('0X7B', printf('%1$#X', 123))
+ call assert_equal('0173', printf('%1$#o', 123))
+ call assert_equal('0173', printf('%1$#O', 123))
+ call assert_equal('abc', printf('%1$#s', 'abc'))
+ call assert_equal('abc', printf('%1$#S', 'abc'))
+
+ call assert_equal('1%', printf('%1$d%%', 1))
+ call assert_notequal('', printf('%1$p', "abc"))
+ call assert_notequal('', printf('%2$d %1$p %3$s', "abc", 2, "abc"))
+
+ #" Try argument re-use and argument swapping
+ call assert_equal('one two one', printf('%1$s %2$s %1$s', "one", "two"))
+ call assert_equal('Screen height: 400', printf('%1$s height: %2$d', "Screen", 400))
+ call assert_equal('400 is: Screen height', printf('%2$d is: %1$s height', "Screen", 400))
+
+ #" Try out lots of combinations of argument types to skip
+ call assert_equal('9 12345 7654321', printf('%2$ld %1$d %3$lu', 12345, 9, 7654321))
+ call assert_equal('9 1234567 7654321', printf('%2$d %1$ld %3$lu', 1234567, 9, 7654321))
+ call assert_equal('9 1234567 7654321', printf('%2$d %1$lld %3$lu', 1234567, 9, 7654321))
+ call assert_equal('9 12345 7654321', printf('%2$ld %1$u %3$lu', 12345, 9, 7654321))
+ call assert_equal('9 1234567 7654321', printf('%2$d %1$lu %3$lu', 1234567, 9, 7654321))
+ call assert_equal('9 1234567 7654321', printf('%2$d %1$llu %3$lu', 1234567, 9, 7654321))
+ call assert_equal('9 1234567 7654321', printf('%2$d %1$llu %3$lu', 1234567, 9, 7654321))
+ call assert_equal('9 deadbeef 7654321', printf('%2$d %1$x %3$lu', 0xdeadbeef, 9, 7654321))
+ call assert_equal('9 c 7654321', printf('%2$ld %1$c %3$lu', 99, 9, 7654321))
+ call assert_equal('9 hi 7654321', printf('%2$ld %1$s %3$lu', "hi", 9, 7654321))
+ call assert_equal('9 0.000000e+00 7654321', printf('%2$ld %1$e %3$lu', 0.0, 9, 7654321))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 1, 3, 4)"], "E767:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%2$d%d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%d%2$d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%2$*1$d%d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%d%2$*1$d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%2$.*1$d%d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%d%2$.*1$d', 1, 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$%')"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$')"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$_')"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*3$.*d', 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*.*2$d', 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*.*d', 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%*.*1$d', 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%*1$.*d', 3)"], "E1500:")
+ call CheckLegacyAndVim9Failure(["call printf('%*1$.*1$d', 3)"], "E1500:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%2$d', 3, 3)"], "E1501:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%2$*1$d %1$ld', 3, 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$p %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$f %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$lud %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$llud %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$lld %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$s %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$c %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$*1$d', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$ld %2$*1$d', 3, 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*1$ld', 3)"], "E1502:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*1$.*1$ld', 3)"], "E1502:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%1$d%2$d', 3)"], "E1503:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%1$d %1$s', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$s', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$ud %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$s %1$f', 3.0)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*1$d %1$ld', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$p %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$f %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$lud %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$llud %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$lld %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$s %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$c %1$d', 3)"], "E1504:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$ld %1$d', 3)"], "E1504:")
+
+ call CheckLegacyAndVim9Failure(["call printf('%1$.2$d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%01$d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%01$0d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*2d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*3.*2$d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*3$.2$d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$*3$.*2d', 3)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$1$.5d', 5)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$5.1$d', 5)"], "E1505:")
+ call CheckLegacyAndVim9Failure(["call printf('%1$1$.1$d', 5)"], "E1505:")
+endfunc
+
+func Test_printf_pos_float()
+ let lines =<< trim END
+ call assert_equal('1.000000', printf('%1$f', 1))
+ call assert_equal('1.230000', printf('%1$f', 1.23))
+ call assert_equal('1.230000', printf('%1$F', 1.23))
+ call assert_equal('9999999.9', printf('%1$g', 9999999.9))
+ call assert_equal('9999999.9', printf('%1$G', 9999999.9))
+ call assert_equal('1.230000e+00', printf('%1$e', 1.23))
+ call assert_equal('1.230000E+00', printf('%1$E', 1.23))
+ call assert_equal('1.200000e-02', printf('%1$e', 0.012))
+ call assert_equal('-1.200000e-02', printf('%1$e', -0.012))
+ call assert_equal('0.33', printf('%1$.2f', 1.0 / 3.0))
+
+ #" When precision is 0, the dot should be omitted.
+ call assert_equal(' 2', printf('%1$*2$.f', 7.0 / 3.0, 3))
+ call assert_equal(' 2', printf('%2$*1$.f', 3, 7.0 / 3.0))
+ call assert_equal(' 2', printf('%1$*2$.g', 7.0 / 3.0, 3))
+ call assert_equal(' 2', printf('%2$*1$.g', 3, 7.0 / 3.0))
+ call assert_equal(' 2e+00', printf('%1$*2$.e', 7.0 / 3.0, 7))
+ call assert_equal(' 2e+00', printf('%2$*1$.e', 7, 7.0 / 3.0))
+
+ #" Float zero can be signed.
+ call assert_equal('+0.000000', printf('%1$+f', 0.0))
+ call assert_equal('0.000000', printf('%1$f', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.000000', printf('%1$f', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%1$s', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%1$s', 1.0 / (-1.0 / 0.0)))
+ call assert_equal('0.0', printf('%1$S', 1.0 / (1.0 / 0.0)))
+ call assert_equal('-0.0', printf('%1$S', 1.0 / (-1.0 / 0.0)))
+
+ #" Float infinity can be signed.
+ call assert_equal('inf', printf('%1$f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%1$f', -1.0 / 0.0))
+ call assert_equal('inf', printf('%1$g', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%1$g', -1.0 / 0.0))
+ call assert_equal('inf', printf('%1$e', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%1$e', -1.0 / 0.0))
+ call assert_equal('INF', printf('%1$F', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%1$F', -1.0 / 0.0))
+ call assert_equal('INF', printf('%1$E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%1$E', -1.0 / 0.0))
+ call assert_equal('INF', printf('%1$E', 1.0 / 0.0))
+ call assert_equal('-INF', printf('%1$G', -1.0 / 0.0))
+ call assert_equal('+inf', printf('%1$+f', 1.0 / 0.0))
+ call assert_equal('-inf', printf('%1$+f', -1.0 / 0.0))
+ call assert_equal(' inf', printf('%1$ f', 1.0 / 0.0))
+ call assert_equal(' inf', printf('%1$*2$f', 1.0 / 0.0, 6))
+ call assert_equal(' -inf', printf('%1$*2$f', -1.0 / 0.0, 6))
+ call assert_equal(' inf', printf('%1$*2$g', 1.0 / 0.0, 6))
+ call assert_equal(' -inf', printf('%1$*2$g', -1.0 / 0.0, 6))
+ call assert_equal(' +inf', printf('%1$+*2$f', 1.0 / 0.0, 6))
+ call assert_equal(' inf', printf('%1$ *2$f', 1.0 / 0.0, 6))
+ call assert_equal(' +inf', printf('%1$+0*2$f', 1.0 / 0.0, 6))
+ call assert_equal('inf ', printf('%1$-*2$f', 1.0 / 0.0, 6))
+ call assert_equal('-inf ', printf('%1$-*2$f', -1.0 / 0.0, 6))
+ call assert_equal('+inf ', printf('%1$-+*2$f', 1.0 / 0.0, 6))
+ call assert_equal(' inf ', printf('%1$- *2$f', 1.0 / 0.0, 6))
+ call assert_equal('-INF ', printf('%1$-*2$F', -1.0 / 0.0, 6))
+ call assert_equal('+INF ', printf('%1$-+*2$F', 1.0 / 0.0, 6))
+ call assert_equal(' INF ', printf('%1$- *2$F', 1.0 / 0.0, 6))
+ call assert_equal('INF ', printf('%1$-*2$G', 1.0 / 0.0, 6))
+ call assert_equal('-INF ', printf('%1$-*2$G', -1.0 / 0.0, 6))
+ call assert_equal('INF ', printf('%1$-*2$E', 1.0 / 0.0, 6))
+ call assert_equal('-INF ', printf('%1$-*2$E', -1.0 / 0.0, 6))
+ call assert_equal(' inf', printf('%2$*1$f', 6, 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%2$*1$f', 6, -1.0 / 0.0))
+ call assert_equal(' inf', printf('%2$*1$g', 6, 1.0 / 0.0))
+ call assert_equal(' -inf', printf('%2$*1$g', 6, -1.0 / 0.0))
+ call assert_equal(' +inf', printf('%2$+*1$f', 6, 1.0 / 0.0))
+ call assert_equal(' inf', printf('%2$ *1$f', 6, 1.0 / 0.0))
+ call assert_equal(' +inf', printf('%2$+0*1$f', 6, 1.0 / 0.0))
+ call assert_equal('inf ', printf('%2$-*1$f', 6, 1.0 / 0.0))
+ call assert_equal('-inf ', printf('%2$-*1$f', 6, -1.0 / 0.0))
+ call assert_equal('+inf ', printf('%2$-+*1$f', 6, 1.0 / 0.0))
+ call assert_equal(' inf ', printf('%2$- *1$f', 6, 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%2$-*1$F', 6, -1.0 / 0.0))
+ call assert_equal('+INF ', printf('%2$-+*1$F', 6, 1.0 / 0.0))
+ call assert_equal(' INF ', printf('%2$- *1$F', 6, 1.0 / 0.0))
+ call assert_equal('INF ', printf('%2$-*1$G', 6, 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%2$-*1$G', 6, -1.0 / 0.0))
+ call assert_equal('INF ', printf('%2$-*1$E', 6, 1.0 / 0.0))
+ call assert_equal('-INF ', printf('%2$-*1$E', 6, -1.0 / 0.0))
+ call assert_equal("str2float('inf')", printf('%1$s', 1.0 / 0.0))
+ call assert_equal("-str2float('inf')", printf('%1$s', -1.0 / 0.0))
+
+ #" Test special case where max precision is truncated at 340.
+ call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 330))
+ call assert_equal('1.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 330, 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 340))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 340, 1.0))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%1$.*2$f', 1.0, 350))
+ call assert_equal('1.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', printf('%2$.*1$f', 350, 1.0))
+
+ #" Float nan (not a number) has no sign.
+ call assert_equal('nan', printf('%1$f', sqrt(-1.0)))
+ call assert_equal('nan', printf('%1$f', 0.0 / 0.0))
+ call assert_equal('nan', printf('%1$f', -0.0 / 0.0))
+ call assert_equal('nan', printf('%1$g', 0.0 / 0.0))
+ call assert_equal('nan', printf('%1$e', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$F', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$G', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$E', 0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$F', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$G', -0.0 / 0.0))
+ call assert_equal('NAN', printf('%1$E', -0.0 / 0.0))
+ call assert_equal(' nan', printf('%1$*2$f', 0.0 / 0.0, 6))
+ call assert_equal(' nan', printf('%1$0*2$f', 0.0 / 0.0, 6))
+ call assert_equal('nan ', printf('%1$-*2$f', 0.0 / 0.0, 6))
+ call assert_equal('nan ', printf('%1$- *2$f', 0.0 / 0.0, 6))
+ call assert_equal(' nan', printf('%2$*1$f', 6, 0.0 / 0.0))
+ call assert_equal(' nan', printf('%2$0*1$f', 6, 0.0 / 0.0))
+ call assert_equal('nan ', printf('%2$-*1$f', 6, 0.0 / 0.0))
+ call assert_equal('nan ', printf('%2$- *1$f', 6, 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%1$s', 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%1$s', -0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%1$S', 0.0 / 0.0))
+ call assert_equal("str2float('nan')", printf('%1$S', -0.0 / 0.0))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call CheckLegacyAndVim9Failure(['echo printf("%f", "a")'], 'E807:')
+endfunc
+
+func Test_printf_pos_errors()
+ call CheckLegacyAndVim9Failure(['echo printf("%1$d", {})'], 'E728:')
+ call CheckLegacyAndVim9Failure(['echo printf("%1$d", [])'], 'E745:')
+ call CheckLegacyAndVim9Failure(['echo printf("%1$d", 1, 2)'], 'E767:')
+ call CheckLegacyAndVim9Failure(['echo printf("%*d", 1)'], 'E766:')
+ call CheckLegacyAndVim9Failure(['echo printf("%1$s")'], 'E1503:')
+ call CheckLegacyAndVim9Failure(['echo printf("%1$d", 1.2)'], 'E805:')
+ call CheckLegacyAndVim9Failure(['echo printf("%1$f")'], 'E1503:')
+endfunc
+
+func Test_printf_pos_64bit()
+ let lines =<< trim END
+ call assert_equal("123456789012345", printf('%1$d', 123456789012345))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_pos_spec_s()
+ let lines =<< trim END
+ #" number
+ call assert_equal("1234567890", printf('%1$s', 1234567890))
+
+ #" string
+ call assert_equal("abcdefgi", printf('%1$s', "abcdefgi"))
+
+ #" float
+ call assert_equal("1.23", printf('%1$s', 1.23))
+
+ #" list
+ VAR lvalue = [1, 'two', ['three', 4]]
+ call assert_equal(string(lvalue), printf('%1$s', lvalue))
+
+ #" dict
+ VAR dvalue = {'key1': 'value1', 'key2': ['list', 'lvalue'], 'key3': {'dict': 'lvalue'}}
+ call assert_equal(string(dvalue), printf('%1$s', dvalue))
+
+ #" funcref
+ call assert_equal('printf', printf('%1$s', 'printf'->function()))
+
+ #" partial
+ call assert_equal(string(function('printf', ['%1$s'])), printf('%1$s', function('printf', ['%1$s'])))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_printf_pos_spec_b()
+ let lines =<< trim END
+ call assert_equal("0", printf('%1$b', 0))
+ call assert_equal("00001100", printf('%1$0*2$b', 12, 8))
+ call assert_equal("11111111", printf('%1$0*2$b', 0xff, 8))
+ call assert_equal(" 1111011", printf('%1$*2$b', 123, 10))
+ call assert_equal("0001111011", printf('%1$0*2$b', 123, 10))
+ call assert_equal(" 0b1111011", printf('%1$#*2$b', 123, 10))
+ call assert_equal("0B01111011", printf('%1$#0*2$B', 123, 10))
+ call assert_equal("00001100", printf('%2$0*1$b', 8, 12))
+ call assert_equal("11111111", printf('%2$0*1$b', 8, 0xff))
+ call assert_equal(" 1111011", printf('%2$*1$b', 10, 123))
+ call assert_equal("0001111011", printf('%2$0*1$b', 10, 123))
+ call assert_equal(" 0b1111011", printf('%2$#*1$b', 10, 123))
+ call assert_equal("0B01111011", printf('%2$#0*1$B', 10, 123))
+ call assert_equal("1001001100101100000001011010010", printf('%1$b', 1234567890))
+ call assert_equal("11100000100100010000110000011011101111101111001", printf('%1$b', 123456789012345))
+ call assert_equal("1111111111111111111111111111111111111111111111111111111111111111", printf('%1$b', -1))
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/test/old/testdir/test_functions.vim
index 500c30c76b..88a29968fe 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/test/old/testdir/test_functions.vim
@@ -31,10 +31,13 @@ func Test_has()
call assert_equal(1, or(has('ttyin'), 1))
call assert_equal(0, and(has('ttyout'), 0))
call assert_equal(1, has('multi_byte_encoding'))
+ call assert_equal(0, has(':tearoff'))
endif
call assert_equal(1, has('vcon', 1))
call assert_equal(1, has('mouse_gpm_enabled', 1))
+ call assert_equal(has('gui_win32') && has('menu'), has(':tearoff'))
+
call assert_equal(0, has('nonexistent'))
call assert_equal(0, has('nonexistent', 1))
@@ -83,6 +86,18 @@ func Test_empty()
call assert_equal(0, empty(function('Test_empty', [0])))
endfunc
+func Test_err_teapot()
+ throw 'Skipped: Nvim does not have err_teapot()'
+ call assert_fails('call err_teapot()', "E418: I'm a teapot")
+ call assert_fails('call err_teapot(0)', "E418: I'm a teapot")
+ call assert_fails('call err_teapot(v:false)', "E418: I'm a teapot")
+
+ call assert_fails('call err_teapot("1")', "E503: Coffee is currently not available")
+ call assert_fails('call err_teapot(v:true)', "E503: Coffee is currently not available")
+ let expr = 1
+ call assert_fails('call err_teapot(expr)', "E503: Coffee is currently not available")
+endfunc
+
func Test_len()
call assert_equal(1, len(0))
call assert_equal(2, len(12))
@@ -153,11 +168,13 @@ func Test_strwidth()
call assert_fails('call strwidth({->0})', 'E729:')
call assert_fails('call strwidth([])', 'E730:')
call assert_fails('call strwidth({})', 'E731:')
- if has('float')
- call assert_fails('call strwidth(1.2)', 'E806:')
- endif
endfor
+ if has('float')
+ call assert_equal(3, strwidth(1.2))
+ call CheckDefExecAndScriptFailure(['echo strwidth(1.2)'], 'E806:')
+ endif
+
set ambiwidth&
endfunc
@@ -221,7 +238,9 @@ func Test_str2nr()
call assert_fails('call str2nr([])', 'E730:')
call assert_fails('call str2nr({->2})', 'E729:')
if has('float')
- call assert_fails('call str2nr(1.2)', 'E806:')
+ call assert_equal(1, str2nr(1.2))
+ call CheckDefExecFailure(['echo str2nr(1.2)'], 'E1013:')
+ call CheckScriptFailure(['vim9script', 'echo str2nr(1.2)'], 'E806:')
endif
call assert_fails('call str2nr(10, [])', 'E745:')
endfunc
@@ -271,6 +290,7 @@ endfunc
func Test_strptime()
CheckFunction strptime
+ CheckNotBSD
CheckNotMSWindows
if exists('$TZ')
@@ -286,6 +306,8 @@ func Test_strptime()
call assert_fails('call strptime()', 'E119:')
call assert_fails('call strptime("xxx")', 'E119:')
+ " This fails on BSD 14 and returns
+ " -2209078800 instead of 0
call assert_equal(0, strptime("%Y", ''))
call assert_equal(0, strptime("%Y", "xxx"))
@@ -372,7 +394,8 @@ func Test_simplify()
call assert_fails('call simplify([])', 'E730:')
call assert_fails('call simplify({})', 'E731:')
if has('float')
- call assert_fails('call simplify(1.2)', 'E806:')
+ call assert_equal('1.2', simplify(1.2))
+ call CheckDefExecAndScriptFailure(['echo simplify(1.2)'], 'E806:')
endif
endfunc
@@ -614,7 +637,7 @@ func Test_mode()
" Only complete from the current buffer.
set complete=.
- inoremap <F2> <C-R>=Save_mode()<CR>
+ noremap! <F2> <C-R>=Save_mode()<CR>
xnoremap <F2> <Cmd>call Save_mode()<CR>
normal! 3G
@@ -773,13 +796,24 @@ func Test_mode()
exe "normal g\<C-H>\<C-O>\<F2>\<Esc>"
call assert_equal("\<C-V>-\<C-V>s", g:current_modes)
- call feedkeys(":echo \<C-R>=Save_mode()\<C-U>\<CR>", 'xt')
+ call feedkeys(":\<F2>\<CR>", 'xt')
call assert_equal('c-c', g:current_modes)
- call feedkeys("gQecho \<C-R>=Save_mode()\<CR>\<CR>vi\<CR>", 'xt')
+ call feedkeys(":\<Insert>\<F2>\<CR>", 'xt')
+ call assert_equal("c-cr", g:current_modes)
+ call feedkeys("gQ\<F2>vi\<CR>", 'xt')
+ call assert_equal('c-cv', g:current_modes)
+ call feedkeys("gQ\<Insert>\<F2>vi\<CR>", 'xt')
+ call assert_equal("c-cvr", g:current_modes)
+
+ " Executing commands in Vim Ex mode should return "cv", never "cvr",
+ " as Cmdline editing has already ended.
+ call feedkeys("gQcall Save_mode()\<CR>vi\<CR>", 'xt')
+ call assert_equal('c-cv', g:current_modes)
+ call feedkeys("gQ\<Insert>call Save_mode()\<CR>vi\<CR>", 'xt')
call assert_equal('c-cv', g:current_modes)
+
" call feedkeys("Qcall Save_mode()\<CR>vi\<CR>", 'xt')
" call assert_equal('c-ce', g:current_modes)
- " How to test Ex mode?
" Test mode in operatorfunc (it used to be Operator-pending).
set operatorfunc=OperatorFunc
@@ -795,6 +829,19 @@ func Test_mode()
execute "normal! gR\<C-o>g@l\<Esc>"
call assert_equal('n-niV', g:current_modes)
+ " Test statusline updates for overstrike mode
+ if CanRunVimInTerminal()
+ let buf = RunVimInTerminal('', {'rows': 12})
+ call term_sendkeys(buf, ":set laststatus=2 statusline=%!mode(1)\<CR>")
+ call term_sendkeys(buf, ":")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_mode_1', {})
+ call term_sendkeys(buf, "\<Insert>")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_mode_2', {})
+ call StopVimInTerminal(buf)
+ endif
+
if has('terminal')
term
call feedkeys("\<C-W>N", 'xt')
@@ -804,7 +851,7 @@ func Test_mode()
endif
bwipe!
- iunmap <F2>
+ unmap! <F2>
xunmap <F2>
set complete&
set operatorfunc&
@@ -815,9 +862,13 @@ endfunc
func Test_append()
enew!
split
- call append(0, ["foo"])
- call append(1, [])
- call append(1, v:_null_list)
+ call assert_equal(0, append(1, []))
+ call assert_equal(0, append(1, v:_null_list))
+ call assert_equal(0, append(0, ["foo"]))
+ call assert_equal(0, append(1, []))
+ call assert_equal(0, append(1, v:_null_list))
+ call assert_equal(0, append(8, []))
+ call assert_equal(0, append(9, v:_null_list))
call assert_equal(['foo', ''], getline(1, '$'))
split
only
@@ -990,6 +1041,7 @@ func Test_matchstrpos()
call assert_equal(['', -1, -1], matchstrpos('testing', 'ing', 8))
call assert_equal(['ing', 1, 4, 7], matchstrpos(['vim', 'testing', 'execute'], 'ing'))
call assert_equal(['', -1, -1, -1], matchstrpos(['vim', 'testing', 'execute'], 'img'))
+ call assert_equal(['', -1, -1], matchstrpos(v:_null_list, '\a'))
endfunc
func Test_nextnonblank_prevnonblank()
@@ -1063,19 +1115,14 @@ func Test_byte2line_line2byte()
bw!
endfunc
-" Test for byteidx() and byteidxcomp() functions
+" Test for byteidx() using a character index
func Test_byteidx()
let a = '.é.' " one char of two bytes
call assert_equal(0, byteidx(a, 0))
- call assert_equal(0, byteidxcomp(a, 0))
call assert_equal(1, byteidx(a, 1))
- call assert_equal(1, byteidxcomp(a, 1))
call assert_equal(3, byteidx(a, 2))
- call assert_equal(3, byteidxcomp(a, 2))
call assert_equal(4, byteidx(a, 3))
- call assert_equal(4, byteidxcomp(a, 3))
call assert_equal(-1, byteidx(a, 4))
- call assert_equal(-1, byteidxcomp(a, 4))
let b = '.é.' " normal e with composing char
call assert_equal(0, b->byteidx(0))
@@ -1083,42 +1130,482 @@ func Test_byteidx()
call assert_equal(4, b->byteidx(2))
call assert_equal(5, b->byteidx(3))
call assert_equal(-1, b->byteidx(4))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidx(str, 0))
+ call assert_equal(1, byteidx(str, 1))
+ call assert_equal(6, byteidx(str, 2))
+ call assert_equal(7, byteidx(str, 3))
+ call assert_equal(12, byteidx(str, 4))
+ call assert_equal(-1, byteidx(str, 5))
+
+ " empty string
+ call assert_equal(0, byteidx('', 0))
+ call assert_equal(-1, byteidx('', 1))
+
+ " error cases
call assert_fails("call byteidx([], 0)", 'E730:')
+ call assert_fails("call byteidx('abc', [])", 'E745:')
+ call assert_fails("call byteidx('abc', 0, {})", ['E728:', 'E728:'])
+ call assert_fails("call byteidx('abc', 0, -1)", ['E1023:', 'E1023:'])
+endfunc
+
+" Test for byteidxcomp() using a character index
+func Test_byteidxcomp()
+ let a = '.é.' " one char of two bytes
+ call assert_equal(0, byteidxcomp(a, 0))
+ call assert_equal(1, byteidxcomp(a, 1))
+ call assert_equal(3, byteidxcomp(a, 2))
+ call assert_equal(4, byteidxcomp(a, 3))
+ call assert_equal(-1, byteidxcomp(a, 4))
+ let b = '.é.' " normal e with composing char
call assert_equal(0, b->byteidxcomp(0))
call assert_equal(1, b->byteidxcomp(1))
call assert_equal(2, b->byteidxcomp(2))
call assert_equal(4, b->byteidxcomp(3))
call assert_equal(5, b->byteidxcomp(4))
call assert_equal(-1, b->byteidxcomp(5))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidxcomp(str, 0))
+ call assert_equal(1, byteidxcomp(str, 1))
+ call assert_equal(2, byteidxcomp(str, 2))
+ call assert_equal(4, byteidxcomp(str, 3))
+ call assert_equal(6, byteidxcomp(str, 4))
+ call assert_equal(7, byteidxcomp(str, 5))
+ call assert_equal(8, byteidxcomp(str, 6))
+ call assert_equal(10, byteidxcomp(str, 7))
+ call assert_equal(12, byteidxcomp(str, 8))
+ call assert_equal(-1, byteidxcomp(str, 9))
+
+ " empty string
+ call assert_equal(0, byteidxcomp('', 0))
+ call assert_equal(-1, byteidxcomp('', 1))
+
+ " error cases
call assert_fails("call byteidxcomp([], 0)", 'E730:')
+ call assert_fails("call byteidxcomp('abc', [])", 'E745:')
+ call assert_fails("call byteidxcomp('abc', 0, {})", ['E728:', 'E728:'])
+ call assert_fails("call byteidxcomp('abc', 0, -1)", ['E1023:', 'E1023:'])
endfunc
-" Test for charidx()
+" Test for byteidx() using a UTF-16 index
+func Test_byteidx_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, byteidx(str, i, v:true))
+ endfor
+ call assert_equal(3, byteidx(str, 3, v:true))
+ call assert_equal(-1, byteidx(str, 4, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(3, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(6, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " string with two byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(1, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(5, byteidx(str, 4, v:true))
+ call assert_equal(9, byteidx(str, 5, v:true))
+ call assert_equal(10, byteidx(str, 6, v:true))
+ call assert_equal(-1, byteidx(str, 7, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(4, byteidx(str, 2, v:true))
+ call assert_equal(5, byteidx(str, 3, v:true))
+ call assert_equal(8, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidx(str, 0, v:true))
+ call assert_equal(1, byteidx(str, 1, v:true))
+ call assert_equal(6, byteidx(str, 2, v:true))
+ call assert_equal(7, byteidx(str, 3, v:true))
+ call assert_equal(12, byteidx(str, 4, v:true))
+ call assert_equal(-1, byteidx(str, 5, v:true))
+
+ " empty string
+ call assert_equal(0, byteidx('', 0, v:true))
+ call assert_equal(-1, byteidx('', 1, v:true))
+
+ " error cases
+ call assert_fails('call byteidx(str, 0, [])', 'E745:')
+endfunc
+
+" Test for byteidxcomp() using a UTF-16 index
+func Test_byteidxcomp_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(3)
+ call assert_equal(i, byteidxcomp(str, i, v:true))
+ endfor
+ call assert_equal(3, byteidxcomp(str, 3, v:true))
+ call assert_equal(-1, byteidxcomp(str, 4, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(3, byteidxcomp(str, 2, v:true))
+ call assert_equal(5, byteidxcomp(str, 3, v:true))
+ call assert_equal(6, byteidxcomp(str, 4, v:true))
+ call assert_equal(-1, byteidxcomp(str, 5, v:true))
+
+ " string with two byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(1, byteidxcomp(str, 2, v:true))
+ call assert_equal(5, byteidxcomp(str, 3, v:true))
+ call assert_equal(5, byteidxcomp(str, 4, v:true))
+ call assert_equal(9, byteidxcomp(str, 5, v:true))
+ call assert_equal(10, byteidxcomp(str, 6, v:true))
+ call assert_equal(-1, byteidxcomp(str, 7, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(2, byteidxcomp(str, 2, v:true))
+ call assert_equal(4, byteidxcomp(str, 3, v:true))
+ call assert_equal(5, byteidxcomp(str, 4, v:true))
+ call assert_equal(6, byteidxcomp(str, 5, v:true))
+ call assert_equal(8, byteidxcomp(str, 6, v:true))
+ call assert_equal(-1, byteidxcomp(str, 7, v:true))
+ call assert_fails('call byteidxcomp(str, 0, [])', 'E745:')
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, byteidxcomp(str, 0, v:true))
+ call assert_equal(1, byteidxcomp(str, 1, v:true))
+ call assert_equal(2, byteidxcomp(str, 2, v:true))
+ call assert_equal(4, byteidxcomp(str, 3, v:true))
+ call assert_equal(6, byteidxcomp(str, 4, v:true))
+ call assert_equal(7, byteidxcomp(str, 5, v:true))
+ call assert_equal(8, byteidxcomp(str, 6, v:true))
+ call assert_equal(10, byteidxcomp(str, 7, v:true))
+ call assert_equal(12, byteidxcomp(str, 8, v:true))
+ call assert_equal(-1, byteidxcomp(str, 9, v:true))
+
+ " empty string
+ call assert_equal(0, byteidxcomp('', 0, v:true))
+ call assert_equal(-1, byteidxcomp('', 1, v:true))
+
+ " error cases
+ call assert_fails('call byteidxcomp(str, 0, [])', 'E745:')
+endfunc
+
+" Test for charidx() using a byte index
func Test_charidx()
let a = 'xáb́y'
call assert_equal(0, charidx(a, 0))
call assert_equal(1, charidx(a, 3))
call assert_equal(2, charidx(a, 4))
call assert_equal(3, charidx(a, 7))
- call assert_equal(-1, charidx(a, 8))
+ call assert_equal(4, charidx(a, 8))
+ call assert_equal(-1, charidx(a, 9))
call assert_equal(-1, charidx(a, -1))
- call assert_equal(-1, charidx('', 0))
- call assert_equal(-1, charidx(v:_null_string, 0))
" count composing characters
- call assert_equal(0, charidx(a, 0, 1))
- call assert_equal(2, charidx(a, 2, 1))
- call assert_equal(3, charidx(a, 4, 1))
- call assert_equal(5, charidx(a, 7, 1))
- call assert_equal(-1, charidx(a, 8, 1))
- call assert_equal(-1, charidx('', 0, 1))
+ call assert_equal(0, a->charidx(0, 1))
+ call assert_equal(2, a->charidx(2, 1))
+ call assert_equal(3, a->charidx(4, 1))
+ call assert_equal(5, a->charidx(7, 1))
+ call assert_equal(6, a->charidx(8, 1))
+ call assert_equal(-1, a->charidx(9, 1))
+
+ " empty string
+ call assert_equal(0, charidx('', 0))
+ call assert_equal(-1, charidx('', 1))
+ call assert_equal(0, charidx('', 0, 1))
+ call assert_equal(-1, charidx('', 1, 1))
+
+ " error cases
+ call assert_equal(0, charidx(v:_null_string, 0))
+ call assert_equal(-1, charidx(v:_null_string, 1))
+ call assert_fails('let x = charidx([], 1)', 'E1174:')
+ call assert_fails('let x = charidx("abc", [])', 'E1210:')
+ call assert_fails('let x = charidx("abc", 1, [])', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, -1)', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, 2)', 'E1212:')
+endfunc
+
+" Test for charidx() using a UTF-16 index
+func Test_charidx_from_utf16_index()
+ " string with single byte characters
+ let str = "abc"
+ for i in range(4)
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(-1, charidx(str, 4, v:false, v:true))
+
+ " string with two byte characters
+ let str = "a©©b"
+ call assert_equal(0, charidx(str, 0, v:false, v:true))
+ call assert_equal(1, charidx(str, 1, v:false, v:true))
+ call assert_equal(2, charidx(str, 2, v:false, v:true))
+ call assert_equal(3, charidx(str, 3, v:false, v:true))
+ call assert_equal(4, charidx(str, 4, v:false, v:true))
+ call assert_equal(-1, charidx(str, 5, v:false, v:true))
+
+ " string with four byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, charidx(str, 0, v:false, v:true))
+ call assert_equal(1, charidx(str, 1, v:false, v:true))
+ call assert_equal(1, charidx(str, 2, v:false, v:true))
+ call assert_equal(2, charidx(str, 3, v:false, v:true))
+ call assert_equal(2, charidx(str, 4, v:false, v:true))
+ call assert_equal(3, charidx(str, 5, v:false, v:true))
+ call assert_equal(4, charidx(str, 6, v:false, v:true))
+ call assert_equal(-1, charidx(str, 7, v:false, v:true))
+
+ " string with composing characters
+ let str = '-á-b́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(4, charidx(str, 4, v:false, v:true))
+ call assert_equal(-1, charidx(str, 5, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, charidx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(6, charidx(str, 6, v:true, v:true))
+ call assert_equal(-1, charidx(str, 7, v:true, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, charidx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(4, charidx(str, 4, v:false, v:true))
+ call assert_equal(-1, charidx(str, 5, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, charidx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(8, charidx(str, 8, v:true, v:true))
+ call assert_equal(-1, charidx(str, 9, v:true, v:true))
+
+ " empty string
+ call assert_equal(0, charidx('', 0, v:false, v:true))
+ call assert_equal(-1, charidx('', 1, v:false, v:true))
+ call assert_equal(0, charidx('', 0, v:true, v:true))
+ call assert_equal(-1, charidx('', 1, v:true, v:true))
+
+ " error cases
+ call assert_equal(0, charidx('', 0, v:false, v:true))
+ call assert_equal(-1, charidx('', 1, v:false, v:true))
+ call assert_equal(0, charidx('', 0, v:true, v:true))
+ call assert_equal(-1, charidx('', 1, v:true, v:true))
+ call assert_equal(0, charidx(v:_null_string, 0, v:false, v:true))
+ call assert_equal(-1, charidx(v:_null_string, 1, v:false, v:true))
+ call assert_fails('let x = charidx("abc", 1, v:false, [])', 'E1212:')
+ call assert_fails('let x = charidx("abc", 1, v:true, [])', 'E1212:')
+endfunc
+
+" Test for utf16idx() using a byte index
+func Test_utf16idx_from_byteidx()
+ " UTF-16 index of a string with single byte characters
+ let str = "abc"
+ for i in range(4)
+ call assert_equal(i, utf16idx(str, i))
+ endfor
+ call assert_equal(-1, utf16idx(str, 4))
+
+ " UTF-16 index of a string with two byte characters
+ let str = 'a©©b'
+ call assert_equal(0, str->utf16idx(0))
+ call assert_equal(1, str->utf16idx(1))
+ call assert_equal(1, str->utf16idx(2))
+ call assert_equal(2, str->utf16idx(3))
+ call assert_equal(2, str->utf16idx(4))
+ call assert_equal(3, str->utf16idx(5))
+ call assert_equal(4, str->utf16idx(6))
+ call assert_equal(-1, str->utf16idx(7))
+
+ " UTF-16 index of a string with four byte characters
+ let str = 'a😊😊b'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(1, utf16idx(str, 1))
+ call assert_equal(1, utf16idx(str, 2))
+ call assert_equal(1, utf16idx(str, 3))
+ call assert_equal(1, utf16idx(str, 4))
+ call assert_equal(3, utf16idx(str, 5))
+ call assert_equal(3, utf16idx(str, 6))
+ call assert_equal(3, utf16idx(str, 7))
+ call assert_equal(3, utf16idx(str, 8))
+ call assert_equal(5, utf16idx(str, 9))
+ call assert_equal(6, utf16idx(str, 10))
+ call assert_equal(-1, utf16idx(str, 11))
+
+ " UTF-16 index of a string with composing characters
+ let str = '-á-b́'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(1, utf16idx(str, 1))
+ call assert_equal(1, utf16idx(str, 2))
+ call assert_equal(1, utf16idx(str, 3))
+ call assert_equal(2, utf16idx(str, 4))
+ call assert_equal(3, utf16idx(str, 5))
+ call assert_equal(3, utf16idx(str, 6))
+ call assert_equal(3, utf16idx(str, 7))
+ call assert_equal(4, utf16idx(str, 8))
+ call assert_equal(-1, utf16idx(str, 9))
+ call assert_equal(0, utf16idx(str, 0, v:true))
+ call assert_equal(1, utf16idx(str, 1, v:true))
+ call assert_equal(2, utf16idx(str, 2, v:true))
+ call assert_equal(2, utf16idx(str, 3, v:true))
+ call assert_equal(3, utf16idx(str, 4, v:true))
+ call assert_equal(4, utf16idx(str, 5, v:true))
+ call assert_equal(5, utf16idx(str, 6, v:true))
+ call assert_equal(5, utf16idx(str, 7, v:true))
+ call assert_equal(6, utf16idx(str, 8, v:true))
+ call assert_equal(-1, utf16idx(str, 9, v:true))
+
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ call assert_equal(0, utf16idx(str, 0))
+ call assert_equal(1, utf16idx(str, 1))
+ call assert_equal(1, utf16idx(str, 2))
+ call assert_equal(1, utf16idx(str, 3))
+ call assert_equal(1, utf16idx(str, 4))
+ call assert_equal(1, utf16idx(str, 5))
+ call assert_equal(2, utf16idx(str, 6))
+ call assert_equal(3, utf16idx(str, 7))
+ call assert_equal(3, utf16idx(str, 8))
+ call assert_equal(3, utf16idx(str, 9))
+ call assert_equal(3, utf16idx(str, 10))
+ call assert_equal(3, utf16idx(str, 11))
+ call assert_equal(4, utf16idx(str, 12))
+ call assert_equal(-1, utf16idx(str, 13))
+ call assert_equal(0, utf16idx(str, 0, v:true))
+ call assert_equal(1, utf16idx(str, 1, v:true))
+ call assert_equal(2, utf16idx(str, 2, v:true))
+ call assert_equal(2, utf16idx(str, 3, v:true))
+ call assert_equal(3, utf16idx(str, 4, v:true))
+ call assert_equal(3, utf16idx(str, 5, v:true))
+ call assert_equal(4, utf16idx(str, 6, v:true))
+ call assert_equal(5, utf16idx(str, 7, v:true))
+ call assert_equal(6, utf16idx(str, 8, v:true))
+ call assert_equal(6, utf16idx(str, 9, v:true))
+ call assert_equal(7, utf16idx(str, 10, v:true))
+ call assert_equal(7, utf16idx(str, 11, v:true))
+ call assert_equal(8, utf16idx(str, 12, v:true))
+ call assert_equal(-1, utf16idx(str, 13, v:true))
+
+ " empty string
+ call assert_equal(0, utf16idx('', 0))
+ call assert_equal(-1, utf16idx('', 1))
+ call assert_equal(0, utf16idx('', 0, v:true))
+ call assert_equal(-1, utf16idx('', 1, v:true))
+
+ " error cases
+ call assert_equal(0, utf16idx("", 0))
+ call assert_equal(-1, utf16idx("", 1))
+ call assert_equal(-1, utf16idx("abc", -1))
+ call assert_equal(0, utf16idx(v:_null_string, 0))
+ call assert_equal(-1, utf16idx(v:_null_string, 1))
+ call assert_fails('let l = utf16idx([], 0)', 'E1174:')
+ call assert_fails('let l = utf16idx("ab", [])', 'E1210:')
+ call assert_fails('let l = utf16idx("ab", 0, [])', 'E1212:')
+endfunc
+
+" Test for utf16idx() using a character index
+func Test_utf16idx_from_charidx()
+ let str = "abc"
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(3, utf16idx(str, 3, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 4, v:false, v:true))
+
+ " UTF-16 index of a string with two byte characters
+ let str = "a©©b"
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(4, utf16idx(str, 4, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 5, v:false, v:true))
+
+ " UTF-16 index of a string with four byte characters
+ let str = "a😊😊b"
+ call assert_equal(0, utf16idx(str, 0, v:false, v:true))
+ call assert_equal(1, utf16idx(str, 1, v:false, v:true))
+ call assert_equal(3, utf16idx(str, 2, v:false, v:true))
+ call assert_equal(5, utf16idx(str, 3, v:false, v:true))
+ call assert_equal(6, utf16idx(str, 4, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 5, v:false, v:true))
+
+ " UTF-16 index of a string with composing characters
+ let str = '-á-b́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(4, utf16idx(str, 4, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 5, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, utf16idx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(6, utf16idx(str, 6, v:true, v:true))
+ call assert_equal(-1, utf16idx(str, 7, v:true, v:true))
- call assert_fails('let x = charidx([], 1)', 'E474:')
- call assert_fails('let x = charidx("abc", [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, [])', 'E474:')
- call assert_fails('let x = charidx("abc", 1, -1)', 'E1023:')
- call assert_fails('let x = charidx("abc", 1, 2)', 'E1023:')
+ " string with multiple composing characters
+ let str = '-ą́-ą́'
+ for i in str->strcharlen()->range()
+ call assert_equal(i, utf16idx(str, i, v:false, v:true))
+ endfor
+ call assert_equal(4, utf16idx(str, 4, v:false, v:true))
+ call assert_equal(-1, utf16idx(str, 5, v:false, v:true))
+ for i in str->strchars()->range()
+ call assert_equal(i, utf16idx(str, i, v:true, v:true))
+ endfor
+ call assert_equal(8, utf16idx(str, 8, v:true, v:true))
+ call assert_equal(-1, utf16idx(str, 9, v:true, v:true))
+
+ " empty string
+ call assert_equal(0, utf16idx('', 0, v:false, v:true))
+ call assert_equal(-1, utf16idx('', 1, v:false, v:true))
+ call assert_equal(0, utf16idx('', 0, v:true, v:true))
+ call assert_equal(-1, utf16idx('', 1, v:true, v:true))
+
+ " error cases
+ call assert_equal(0, utf16idx(v:_null_string, 0, v:true, v:true))
+ call assert_equal(-1, utf16idx(v:_null_string, 1, v:true, v:true))
+ call assert_fails('let l = utf16idx("ab", 0, v:false, [])', 'E1212:')
+endfunc
+
+" Test for strutf16len()
+func Test_strutf16len()
+ call assert_equal(3, strutf16len('abc'))
+ call assert_equal(3, 'abc'->strutf16len(v:true))
+ call assert_equal(4, strutf16len('a©©b'))
+ call assert_equal(4, strutf16len('a©©b', v:true))
+ call assert_equal(6, strutf16len('a😊😊b'))
+ call assert_equal(6, strutf16len('a😊😊b', v:true))
+ call assert_equal(4, strutf16len('-á-b́'))
+ call assert_equal(6, strutf16len('-á-b́', v:true))
+ call assert_equal(4, strutf16len('-ą́-ą́'))
+ call assert_equal(8, strutf16len('-ą́-ą́', v:true))
+ call assert_equal(0, strutf16len(''))
+
+ " error cases
+ call assert_fails('let l = strutf16len([])', 'E1174:')
+ call assert_fails('let l = strutf16len("a", [])', 'E1212:')
+ call assert_equal(0, strutf16len(v:_null_string))
endfunc
func Test_count()
@@ -1171,7 +1658,8 @@ func Test_count()
call assert_equal(2, count("fooooo", "oo"))
call assert_equal(0, count("foo", ""))
- call assert_fails('call count(0, 0)', 'E712:')
+ call assert_fails('call count(0, 0)', 'E706:')
+ call assert_fails('call count("", "", {})', ['E728:', 'E728:'])
endfunc
func Test_changenr()
@@ -1284,6 +1772,7 @@ func Test_hlexists()
syntax off
endfunc
+" Test for the col() function
func Test_col()
new
call setline(1, 'abcdef')
@@ -1435,6 +1924,8 @@ func Test_inputlist()
call assert_equal(-2, c)
call assert_fails('call inputlist("")', 'E686:')
+ " Nvim accepts null list as empty list
+ " call assert_fails('call inputlist(v:_null_list)', 'E686:')
endfunc
func Test_range_inputlist()
@@ -1511,7 +2002,7 @@ endfunc
func Test_setbufvar_options()
" This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the
- " window layout.
+ " window layout and cursor position.
call assert_equal(1, winnr('$'))
split dummy_preview
resize 2
@@ -1525,11 +2016,20 @@ func Test_setbufvar_options()
execute 'belowright vertical split #' . dummy_buf
call assert_equal(wh, winheight(0))
let dum1_id = win_getid()
+ call setline(1, 'foo')
+ normal! V$
+ call assert_equal(4, col('.'))
+ call setbufvar('dummy_preview', '&buftype', 'nofile')
+ call assert_equal(4, col('.'))
wincmd h
let wh = winheight(0)
+ call setline(1, 'foo')
+ normal! V$
+ call assert_equal(4, col('.'))
let dummy_buf = bufnr('dummy_buf2', v:true)
eval 'nofile'->setbufvar(dummy_buf, '&buftype')
+ call assert_equal(4, col('.'))
execute 'belowright vertical split #' . dummy_buf
call assert_equal(wh, winheight(0))
@@ -1540,6 +2040,32 @@ func Test_setbufvar_options()
bwipe!
endfunc
+func Test_setbufvar_keep_window_title()
+ CheckRunVimInTerminal
+ if !has('title') || empty(&t_ts)
+ throw "Skipped: can't get/set title"
+ endif
+
+ let lines =<< trim END
+ set title
+ edit Xa.txt
+ let g:buf = bufadd('Xb.txt')
+ inoremap <F2> <C-R>=setbufvar(g:buf, '&autoindent', 1) ?? ''<CR>
+ END
+ call writefile(lines, 'Xsetbufvar')
+ let buf = RunVimInTerminal('-S Xsetbufvar', {})
+ call WaitForAssert({-> assert_match('Xa.txt', term_gettitle(buf))}, 1000)
+
+ call term_sendkeys(buf, "i\<F2>")
+ call TermWait(buf)
+ call term_sendkeys(buf, "\<Esc>")
+ call TermWait(buf)
+ call assert_match('Xa.txt', term_gettitle(buf))
+
+ call StopVimInTerminal(buf)
+ call delete('Xsetbufvar')
+endfunc
+
func Test_redo_in_nested_functions()
nnoremap g. :set opfunc=Operator<CR>g@
function Operator( type, ... )
@@ -1592,11 +2118,15 @@ func Test_trim()
call assert_fails('eval trim(" vim ", " ", [])', 'E745:')
call assert_fails('eval trim(" vim ", " ", -1)', 'E475:')
call assert_fails('eval trim(" vim ", " ", 3)', 'E475:')
- call assert_fails('eval trim(" vim ", 0)', 'E475:')
+ call assert_fails('eval trim(" vim ", 0)', 'E1174:')
let chars = join(map(range(1, 0x20) + [0xa0], {n -> n->nr2char()}), '')
call assert_equal("x", trim(chars . "x" . chars))
+ call assert_equal("x", trim(chars . "x" . chars, '', 0))
+ call assert_equal("x" . chars, trim(chars . "x" . chars, '', 1))
+ call assert_equal(chars . "x", trim(chars . "x" . chars, '', 2))
+
call assert_fails('let c=trim([])', 'E730:')
endfunc
@@ -1994,8 +2524,24 @@ func Test_call()
endfunction
let mydict = {'data': [0, 1, 2, 3], 'len': function("Mylen")}
eval mydict.len->call([], mydict)->assert_equal(4)
- call assert_fails("call call('Mylen', [], 0)", 'E715:')
+ call assert_fails("call call('Mylen', [], 0)", 'E1206:')
call assert_fails('call foo', 'E107:')
+
+ " These once caused a crash.
+ " Nvim doesn't have null functions
+ " call call(test_null_function(), [])
+ " Nvim doesn't have null partials
+ " call call(test_null_partial(), [])
+ " Nvim doesn't have null functions
+ " call assert_fails('call test_null_function()()', 'E1192:')
+ " Nvim doesn't have null partials
+ " call assert_fails('call test_null_partial()()', 'E117:')
+
+ let lines =<< trim END
+ let Time = 'localtime'
+ call Time()
+ END
+ call CheckScriptFailure(lines, 'E1085:')
endfunc
func Test_char2nr()
@@ -2080,6 +2626,81 @@ func Test_bufadd_bufload()
call delete('XotherName')
endfunc
+func Test_state()
+ CheckRunVimInTerminal
+
+ let getstate = ":echo 'state: ' .. g:state .. '; mode: ' .. g:mode\<CR>"
+
+ let lines =<< trim END
+ call setline(1, ['one', 'two', 'three'])
+ map ;; gg
+ set complete=.
+ func RunTimer()
+ call timer_start(10, {id -> execute('let g:state = state()') .. execute('let g:mode = mode()')})
+ endfunc
+ au Filetype foobar let g:state = state()|let g:mode = mode()
+ END
+ call writefile(lines, 'XState')
+ let buf = RunVimInTerminal('-S XState', #{rows: 6})
+
+ " Using a ":" command Vim is busy, thus "S" is returned
+ call term_sendkeys(buf, ":echo 'state: ' .. state() .. '; mode: ' .. mode()\<CR>")
+ call WaitForAssert({-> assert_match('state: S; mode: n', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":\<CR>")
+
+ " Using a timer callback
+ call term_sendkeys(buf, ":call RunTimer()\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: c; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Halfway a mapping
+ call term_sendkeys(buf, ":call RunTimer()\<CR>;")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, ";")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: mSc; mode: n', term_getline(buf, 6))}, 1000)
+
+ " An operator is pending
+ call term_sendkeys(buf, ":call RunTimer()\<CR>y")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "y")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: oSc; mode: n', term_getline(buf, 6))}, 1000)
+
+ " A register was specified
+ call term_sendkeys(buf, ":call RunTimer()\<CR>\"r")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "yy")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: oSc; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Insert mode completion (bit slower on Mac)
+ call term_sendkeys(buf, ":call RunTimer()\<CR>Got\<C-N>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "\<Esc>")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: aSc; mode: i', term_getline(buf, 6))}, 1000)
+
+ " Autocommand executing
+ call term_sendkeys(buf, ":set filetype=foobar\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: xS; mode: n', term_getline(buf, 6))}, 1000)
+
+ " Todo: "w" - waiting for ch_evalexpr()
+
+ " messages scrolled
+ call term_sendkeys(buf, ":call RunTimer()\<CR>:echo \"one\\ntwo\\nthree\"\<CR>")
+ call TermWait(buf, 25)
+ call term_sendkeys(buf, "\<CR>")
+ call term_sendkeys(buf, getstate)
+ call WaitForAssert({-> assert_match('state: Scs; mode: r', term_getline(buf, 6))}, 1000)
+
+ call StopVimInTerminal(buf)
+ call delete('XState')
+endfunc
+
func Test_range()
" destructuring
let [x, y] = range(2)
@@ -2348,6 +2969,16 @@ func Test_garbagecollect_now_fails()
let v:testing = 1
endfunc
+" Test for echo highlighting
+func Test_echohl()
+ echohl Search
+ echo 'Vim'
+ call assert_equal('Vim', Screenline(&lines))
+ " TODO: How to check the highlight group used by echohl?
+ " ScreenAttrs() returns all zeros.
+ echohl None
+endfunc
+
" Test for the eval() function
func Test_eval()
call assert_fails("call eval('5 a')", 'E488:')
@@ -2396,6 +3027,34 @@ func Test_screen_functions()
call assert_equal(-1, screenattr(-1, -1))
call assert_equal(-1, screenchar(-1, -1))
call assert_equal([], screenchars(-1, -1))
+
+ " Run this in a separate Vim instance to avoid messing up.
+ let after =<< trim [CODE]
+ scriptencoding utf-8
+ call setline(1, '口')
+ redraw
+ call assert_equal(0, screenattr(1, 1))
+ call assert_equal(char2nr('口'), screenchar(1, 1))
+ call assert_equal([char2nr('口')], screenchars(1, 1))
+ call assert_equal('口', screenstring(1, 1))
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [CODE]
+
+ let encodings = ['utf-8', 'cp932', 'cp936', 'cp949', 'cp950']
+ if !has('win32')
+ let encodings += ['euc-jp']
+ endif
+ if has('nvim')
+ let encodings = ['utf-8']
+ endif
+ for enc in encodings
+ let msg = 'enc=' .. enc
+ if RunVim([], after, $'--clean --cmd "set encoding={enc}"')
+ call assert_equal([], readfile('Xresult'), msg)
+ endif
+ call delete('Xresult')
+ endfor
endfunc
" Test for getcurpos() and setpos()
@@ -2437,6 +3096,51 @@ func Test_getmousepos()
\ wincol: 1,
\ line: 1,
\ column: 1,
+ \ coladd: 0,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 2)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 2,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 2,
+ \ line: 1,
+ \ column: 1,
+ \ coladd: 1,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 8)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 8,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 8,
+ \ line: 1,
+ \ column: 1,
+ \ coladd: 7,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 9)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 9,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 9,
+ \ line: 1,
+ \ column: 2,
+ \ coladd: 0,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 12)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 12,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 12,
+ \ line: 1,
+ \ column: 2,
+ \ coladd: 3,
\ }, getmousepos())
call Ntest_setmouse(1, 25)
call assert_equal(#{
@@ -2447,6 +3151,29 @@ func Test_getmousepos()
\ wincol: 25,
\ line: 1,
\ column: 4,
+ \ coladd: 0,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 28)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 28,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 28,
+ \ line: 1,
+ \ column: 7,
+ \ coladd: 0,
+ \ }, getmousepos())
+ call Ntest_setmouse(1, 29)
+ call assert_equal(#{
+ \ screenrow: 1,
+ \ screencol: 29,
+ \ winid: win_getid(),
+ \ winrow: 1,
+ \ wincol: 29,
+ \ line: 1,
+ \ column: 8,
+ \ coladd: 0,
\ }, getmousepos())
call Ntest_setmouse(1, 50)
call assert_equal(#{
@@ -2457,6 +3184,7 @@ func Test_getmousepos()
\ wincol: 50,
\ line: 1,
\ column: 8,
+ \ coladd: 21,
\ }, getmousepos())
" If the mouse is positioned past the last buffer line, "line" and "column"
@@ -2470,6 +3198,7 @@ func Test_getmousepos()
\ wincol: 25,
\ line: 1,
\ column: 4,
+ \ coladd: 0,
\ }, getmousepos())
call Ntest_setmouse(2, 50)
call assert_equal(#{
@@ -2480,6 +3209,7 @@ func Test_getmousepos()
\ wincol: 50,
\ line: 1,
\ column: 8,
+ \ coladd: 21,
\ }, getmousepos())
bwipe!
endfunc
@@ -2506,6 +3236,18 @@ func Test_glob()
call assert_fails("call glob('*', 0, {})", 'E728:')
endfunc
+" Test for browse()
+func Test_browse()
+ CheckFeature browse
+ call assert_fails('call browse([], "open", "x", "a.c")', 'E745:')
+endfunc
+
+" Test for browsedir()
+func Test_browsedir()
+ CheckFeature browse
+ call assert_fails('call browsedir("open", [])', 'E730:')
+endfunc
+
func HasDefault(msg = 'msg')
return a:msg
endfunc
@@ -2516,7 +3258,7 @@ endfunc
" Test for gettext()
func Test_gettext()
- call assert_fails('call gettext(1)', 'E475:')
+ call assert_fails('call gettext(1)', 'E1174:')
endfunc
func Test_builtin_check()
@@ -2526,7 +3268,7 @@ func Test_builtin_check()
call assert_fails('let l:.trim = {x -> " " .. x}', 'E704:')
let lines =<< trim END
vim9script
- var s:trim = (x) => " " .. x
+ var trim = (x) => " " .. x
END
call CheckScriptFailure(lines, 'E704:')
@@ -2556,4 +3298,126 @@ func Test_builtin_check()
endfunc
+" Test for virtcol()
+func Test_virtcol()
+ new
+ call setline(1, "the\tquick\tbrown\tfox")
+ norm! 4|
+ call assert_equal(8, virtcol('.'))
+ call assert_equal(8, virtcol('.', v:false))
+ call assert_equal([4, 8], virtcol('.', v:true))
+
+ let w = winwidth(0)
+ call setline(2, repeat('a', w + 2))
+ let win_nosbr = win_getid()
+ split
+ setlocal showbreak=!!
+ let win_sbr = win_getid()
+ call assert_equal([w, w], virtcol([2, w], v:true, win_nosbr))
+ call assert_equal([w + 1, w + 1], virtcol([2, w + 1], v:true, win_nosbr))
+ call assert_equal([w + 2, w + 2], virtcol([2, w + 2], v:true, win_nosbr))
+ call assert_equal([w, w], virtcol([2, w], v:true, win_sbr))
+ call assert_equal([w + 3, w + 3], virtcol([2, w + 1], v:true, win_sbr))
+ call assert_equal([w + 4, w + 4], virtcol([2, w + 2], v:true, win_sbr))
+ close
+
+ call assert_equal(0, virtcol(''))
+ call assert_equal([0, 0], virtcol('', v:true))
+ call assert_equal(0, virtcol('.', v:false, 5001))
+ call assert_equal([0, 0], virtcol('.', v:true, 5001))
+
+ bwipe!
+endfunc
+
+func Test_delfunc_while_listing()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set nocompatible
+ for i in range(1, 999)
+ exe 'func ' .. 'MyFunc' .. i .. '()'
+ endfunc
+ endfor
+ au CmdlineLeave : call timer_start(0, {-> execute('delfunc MyFunc622')})
+ END
+ call writefile(lines, 'Xfunctionclear', 'D')
+ let buf = RunVimInTerminal('-S Xfunctionclear', {'rows': 12})
+
+ " This was using freed memory. The height of the terminal must be so that
+ " the next function to be listed with "j" is the one that is deleted in the
+ " timer callback, tricky!
+ call term_sendkeys(buf, ":func /MyFunc\<CR>")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "j")
+ call TermWait(buf, 50)
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for the reverse() function with a string
+func Test_string_reverse()
+ let lines =<< trim END
+ call assert_equal('', reverse(v:_null_string))
+ for [s1, s2] in [['', ''], ['a', 'a'], ['ab', 'ba'], ['abc', 'cba'],
+ \ ['abcd', 'dcba'], ['«-«-»-»', '»-»-«-«'],
+ \ ['🇦', '🇦'], ['🇦🇧', '🇧🇦'], ['🇦🇧🇨', '🇨🇧🇦'],
+ \ ['🇦«🇧-🇨»🇩', '🇩»🇨-🇧«🇦']]
+ call assert_equal(s2, reverse(s1))
+ endfor
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " test in latin1 encoding
+ let save_enc = &encoding
+ " set encoding=latin1
+ call assert_equal('dcba', reverse('abcd'))
+ let &encoding = save_enc
+endfunc
+
+func Test_fullcommand()
+ " this used to crash vim
+ call assert_equal('', fullcommand(10))
+endfunc
+
+" Test for glob() with shell special patterns
+func Test_glob_extended_bash()
+ CheckExecutable bash
+ CheckNotMSWindows
+ CheckNotMac " The default version of bash is old on macOS.
+
+ let _shell = &shell
+ set shell=bash
+
+ call mkdir('Xtestglob/foo/bar/src', 'p')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.sh')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.h')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.cpp')
+
+ " Sort output of glob() otherwise we end up with different
+ " ordering depending on whether file system is case-sensitive.
+ let expected = ['Xtestglob/foo/bar/src/foo.cpp', 'Xtestglob/foo/bar/src/foo.h']
+ call assert_equal(expected, sort(glob('Xtestglob/**/foo.{h,cpp}', 0, 1)))
+ call delete('Xtestglob', 'rf')
+ let &shell=_shell
+endfunc
+
+" Test for glob() with extended patterns (MS-Windows)
+" Vim doesn't use 'shell' to expand wildcards on MS-Windows.
+" Unlike bash, it doesn't support {,} expansion.
+func Test_glob_extended_mswin()
+ CheckMSWindows
+
+ call mkdir('Xtestglob/foo/bar/src', 'p')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.sh')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.h')
+ call writefile([], 'Xtestglob/foo/bar/src/foo.cpp')
+
+ " Sort output of glob() otherwise we end up with different
+ " ordering depending on whether file system is case-sensitive.
+ let expected = ['Xtestglob/foo/bar/src/foo.cpp', 'Xtestglob/foo/bar/src/foo.h', 'Xtestglob/foo/bar/src/foo.sh']
+ call assert_equal(expected, sort(glob('Xtestglob/**/foo.*', 0, 1)))
+ call delete('Xtestglob', 'rf')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ga.vim b/test/old/testdir/test_ga.vim
index ce31edfc7a..ce31edfc7a 100644
--- a/src/nvim/testdir/test_ga.vim
+++ b/test/old/testdir/test_ga.vim
diff --git a/src/nvim/testdir/test_getcwd.vim b/test/old/testdir/test_getcwd.vim
index 073f8303dc..073f8303dc 100644
--- a/src/nvim/testdir/test_getcwd.vim
+++ b/test/old/testdir/test_getcwd.vim
diff --git a/src/nvim/testdir/test_getvar.vim b/test/old/testdir/test_getvar.vim
index e6b6341fce..331f3cfe8a 100644
--- a/src/nvim/testdir/test_getvar.vim
+++ b/test/old/testdir/test_getvar.vim
@@ -150,6 +150,12 @@ func Test_get_func()
call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'}))
call assert_equal(0, get(l:F, 'dict'))
call assert_equal([], get(l:F, 'args'))
+ " Nvim doesn't have null functions
+ " let NF = test_null_function()
+ " call assert_equal('', get(NF, 'name'))
+ " call assert_equal(NF, get(NF, 'func'))
+ " call assert_equal(0, get(NF, 'dict'))
+ " call assert_equal([], get(NF, 'args'))
endfunc
" get({partial}, {what} [, {default}]) - in test_partial.vim
diff --git a/src/nvim/testdir/test_gf.vim b/test/old/testdir/test_gf.vim
index f09dbd72ce..9824c8276e 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/test/old/testdir/test_gf.vim
@@ -300,4 +300,65 @@ func Test_gf_subdirs_wildcard()
set path&
endfunc
+" Test for 'switchbuf' with gf and gF commands
+func Test_gf_switchbuf()
+ call writefile(repeat(["aaa"], 10), "Xtest1", 'D')
+ edit Xtest1
+ new
+ call setline(1, ['Xtest1'])
+
+ " Test for 'useopen'
+ set switchbuf=useopen
+ call cursor(1, 1)
+ exe "normal \<C-W>f"
+ call assert_equal([2, 2], [winnr(), winnr('$')])
+ close
+
+ " If the file is opened in another tabpage, then it should not be considered
+ tabedit Xtest1
+ tabfirst
+ exe "normal \<C-W>f"
+ call assert_equal([1, 2], [winnr(), winnr('$')])
+ call assert_equal([1, 2], [tabpagenr(), tabpagenr('$')])
+ close
+
+ " Test for 'usetab'
+ set switchbuf=usetab
+ exe "normal \<C-W>f"
+ call assert_equal([1, 1], [winnr(), winnr('$')])
+ call assert_equal([2, 2], [tabpagenr(), tabpagenr('$')])
+ %bw!
+
+ " Test for CTRL-W_F with 'useopen'
+ set isfname-=:
+ call setline(1, ['Xtest1:5'])
+ set switchbuf=useopen
+ split +1 Xtest1
+ wincmd b
+ exe "normal \<C-W>F"
+ call assert_equal([1, 2], [winnr(), winnr('$')])
+ call assert_equal(5, line('.'))
+ close
+
+ " If the file is opened in another tabpage, then it should not be considered
+ tabedit +1 Xtest1
+ tabfirst
+ exe "normal \<C-W>F"
+ call assert_equal([1, 2], [winnr(), winnr('$')])
+ call assert_equal(5, line('.'))
+ call assert_equal([1, 2], [tabpagenr(), tabpagenr('$')])
+ close
+
+ " Test for CTRL_W_F with 'usetab'
+ set switchbuf=usetab
+ exe "normal \<C-W>F"
+ call assert_equal([2, 2], [tabpagenr(), tabpagenr('$')])
+ call assert_equal([1, 1], [winnr(), winnr('$')])
+ call assert_equal(5, line('.'))
+
+ set switchbuf=
+ set isfname&
+ %bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_glob2regpat.vim b/test/old/testdir/test_glob2regpat.vim
index a423a4a9f0..b9c4c12059 100644
--- a/src/nvim/testdir/test_glob2regpat.vim
+++ b/test/old/testdir/test_glob2regpat.vim
@@ -1,8 +1,11 @@
" Test glob2regpat()
+source vim9.vim
+
func Test_glob2regpat_invalid()
if has('float')
- call assert_fails('call glob2regpat(1.33)', 'E806:')
+ call assert_equal('^1\.33$', glob2regpat(1.33))
+ call CheckDefExecAndScriptFailure(['echo glob2regpat(1.33)'], 'E806:')
endif
call assert_fails('call glob2regpat("}")', 'E219:')
call assert_fails('call glob2regpat("{")', 'E220:')
diff --git a/src/nvim/testdir/test_global.vim b/test/old/testdir/test_global.vim
index 44a8784348..44a8784348 100644
--- a/src/nvim/testdir/test_global.vim
+++ b/test/old/testdir/test_global.vim
diff --git a/src/nvim/testdir/test_gn.vim b/test/old/testdir/test_gn.vim
index c4a41a6742..c4a41a6742 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/test/old/testdir/test_gn.vim
diff --git a/src/nvim/testdir/test_goto.vim b/test/old/testdir/test_goto.vim
index 6d029ffda2..6d029ffda2 100644
--- a/src/nvim/testdir/test_goto.vim
+++ b/test/old/testdir/test_goto.vim
diff --git a/src/nvim/testdir/test_gui.vim b/test/old/testdir/test_gui.vim
index c3f1f3163a..c3f1f3163a 100644
--- a/src/nvim/testdir/test_gui.vim
+++ b/test/old/testdir/test_gui.vim
diff --git a/src/nvim/testdir/test_help.vim b/test/old/testdir/test_help.vim
index 08dd3dcb9a..08dd3dcb9a 100644
--- a/src/nvim/testdir/test_help.vim
+++ b/test/old/testdir/test_help.vim
diff --git a/src/nvim/testdir/test_help_tagjump.vim b/test/old/testdir/test_help_tagjump.vim
index eae1a241e3..31310d1e77 100644
--- a/src/nvim/testdir/test_help_tagjump.vim
+++ b/test/old/testdir/test_help_tagjump.vim
@@ -35,9 +35,7 @@ func Test_help_tagjump()
help ??
call assert_equal("help", &filetype)
- " *??* tag needs patch 8.2.1794
- " call assert_true(getline('.') =~ '\*??\*')
- call assert_true(getline('.') =~ '\*g??\*')
+ call assert_true(getline('.') =~ '\*??\*')
helpclose
help :?
@@ -187,14 +185,14 @@ let s:langs = ['en', 'ab', 'ja']
func s:doc_config_setup()
let s:helpfile_save = &helpfile
- let &helpfile="Xdir1/doc-en/doc/testdoc.txt"
+ let &helpfile="Xdocdir1/doc-en/doc/testdoc.txt"
let s:rtp_save = &rtp
- let &rtp="Xdir1/doc-en"
+ let &rtp="Xdocdir1/doc-en"
if has('multi_lang')
let s:helplang_save=&helplang
endif
- call delete('Xdir1', 'rf')
+ call delete('Xdocdir1', 'rf')
for lang in s:langs
if lang ==# 'en'
@@ -204,7 +202,7 @@ func s:doc_config_setup()
let tagfname = 'tags-' . lang
let docfname = 'testdoc.' . lang . 'x'
endif
- let docdir = "Xdir1/doc-" . lang . "/doc"
+ let docdir = "Xdocdir1/doc-" . lang . "/doc"
call mkdir(docdir, "p")
call writefile(["\t*test-char*", "\t*test-col*"], docdir . '/' . docfname)
call writefile(["test-char\t" . docfname . "\t/*test-char*",
@@ -214,7 +212,7 @@ func s:doc_config_setup()
endfunc
func s:doc_config_teardown()
- call delete('Xdir1', 'rf')
+ call delete('Xdocdir1', 'rf')
let &helpfile = s:helpfile_save
let &rtp = s:rtp_save
@@ -246,7 +244,7 @@ func Test_help_complete()
call assert_equal(['test-col', 'test-char'], list)
" 'helplang=' and help file lang is 'en' and 'ab'
- set rtp+=Xdir1/doc-ab
+ set rtp+=Xdocdir1/doc-ab
set helplang=
let list = s:get_help_compl_list("test")
call assert_equal(sort(['test-col@en', 'test-col@ab',
@@ -259,7 +257,7 @@ func Test_help_complete()
\ 'test-char', 'test-char@en']), sort(list))
" 'helplang=' and help file lang is 'en', 'ab' and 'ja'
- set rtp+=Xdir1/doc-ja
+ set rtp+=Xdocdir1/doc-ja
set helplang=
let list = s:get_help_compl_list("test")
call assert_equal(sort(['test-col@en', 'test-col@ab',
@@ -302,8 +300,8 @@ func Test_help_respect_current_file_lang()
helpclose
endfunc
- set rtp+=Xdir1/doc-ab
- set rtp+=Xdir1/doc-ja
+ set rtp+=Xdocdir1/doc-ab
+ set rtp+=Xdocdir1/doc-ja
set helplang=ab
call s:check_help_file_ext('test-char', 'abx')
diff --git a/src/nvim/testdir/test_hide.vim b/test/old/testdir/test_hide.vim
index b3ce395523..b3ce395523 100644
--- a/src/nvim/testdir/test_hide.vim
+++ b/test/old/testdir/test_hide.vim
diff --git a/src/nvim/testdir/test_highlight.vim b/test/old/testdir/test_highlight.vim
index 8a102f2e65..a83dc34063 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/test/old/testdir/test_highlight.vim
@@ -543,9 +543,9 @@ func Test_cursorline_after_yank()
\ 'call setline(1, ["","1","2","3",""])',
\ ], 'Xtest_cursorline_yank')
let buf = RunVimInTerminal('-S Xtest_cursorline_yank', {'rows': 8})
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, "Gy3k")
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, "jj")
call VerifyScreenDump(buf, 'Test_cursorline_yank_01', {})
@@ -583,7 +583,7 @@ func Test_cursorline_with_visualmode()
\ 'call setline(1, repeat(["abc"], 50))',
\ ], 'Xtest_cursorline_with_visualmode')
let buf = RunVimInTerminal('-S Xtest_cursorline_with_visualmode', {'rows': 12})
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, "V\<C-f>kkkjk")
call VerifyScreenDump(buf, 'Test_cursorline_with_visualmode_01', {})
@@ -593,6 +593,59 @@ func Test_cursorline_with_visualmode()
call delete('Xtest_cursorline_with_visualmode')
endfunc
+func Test_wincolor()
+ CheckScreendump
+ " make sure the width is enough for the test
+ set columns=80
+
+ let lines =<< trim END
+ set cursorline cursorcolumn rnu
+ call setline(1, ["","1111111111","22222222222","3 here 3","","the cat is out of the bag"])
+ set wincolor=Pmenu
+ hi CatLine guifg=green ctermfg=green
+ hi Reverse gui=reverse cterm=reverse
+ syn match CatLine /^the.*/
+ call prop_type_add("foo", {"highlight": "Reverse", "combine": 1})
+ call prop_add(6, 12, {"type": "foo", "end_col": 15})
+ /here
+ END
+ call writefile(lines, 'Xtest_wincolor')
+ let buf = RunVimInTerminal('-S Xtest_wincolor', {'rows': 8})
+ call TermWait(buf)
+ call term_sendkeys(buf, "2G5lvj")
+ call TermWait(buf)
+
+ call VerifyScreenDump(buf, 'Test_wincolor_01', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xtest_wincolor')
+endfunc
+
+func Test_wincolor_listchars()
+ CheckScreendump
+ CheckFeature conceal
+
+ let lines =<< trim END
+ call setline(1, ["one","\t\tsome random text enough long to show 'extends' and 'precedes' includingnbsps, preceding tabs and trailing spaces ","three"])
+ set wincolor=Todo
+ set nowrap cole=1 cocu+=n
+ set list lcs=eol:$,tab:>-,space:.,trail:_,extends:>,precedes:<,conceal:*,nbsp:#
+ call matchadd('Conceal', 'text')
+ normal 2G5zl
+ END
+ call writefile(lines, 'Xtest_wincolorlcs')
+ let buf = RunVimInTerminal('-S Xtest_wincolorlcs', {'rows': 8})
+
+ call VerifyScreenDump(buf, 'Test_wincolor_lcs', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xtest_wincolorlcs')
+endfunc
+
func Test_cursorcolumn_insert_on_tab()
CheckScreendump
@@ -665,7 +718,6 @@ func Test_colorcolumn()
call writefile(lines, 'Xtest_colorcolumn')
let buf = RunVimInTerminal('-S Xtest_colorcolumn', {'rows': 10})
call term_sendkeys(buf, ":\<CR>")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_colorcolumn_1', {})
" clean up
@@ -683,7 +735,6 @@ func Test_colorcolumn_bri()
call writefile(lines, 'Xtest_colorcolumn_bri')
let buf = RunVimInTerminal('-S Xtest_colorcolumn_bri', {'rows': 10,'columns': 40})
call term_sendkeys(buf, ":set co=40 linebreak bri briopt=shift:2 cc=40,41,43\<CR>")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_colorcolumn_2', {})
" clean up
@@ -698,15 +749,33 @@ func Test_colorcolumn_sbr()
let lines =<< trim END
call setline(1, 'The quick brown fox jumped over the lazy dogs')
END
- call writefile(lines, 'Xtest_colorcolumn_srb')
- let buf = RunVimInTerminal('-S Xtest_colorcolumn_srb', {'rows': 10,'columns': 40})
+ call writefile(lines, 'Xtest_colorcolumn_sbr', 'D')
+ let buf = RunVimInTerminal('-S Xtest_colorcolumn_sbr', {'rows': 10,'columns': 40})
call term_sendkeys(buf, ":set co=40 showbreak=+++>\\ cc=40,41,43\<CR>")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_colorcolumn_3', {})
" clean up
call StopVimInTerminal(buf)
- call delete('Xtest_colorcolumn_srb')
+endfunc
+
+func Test_visual_sbr()
+ CheckScreendump
+
+ " check Visual highlight when 'showbreak' is set
+ let lines =<< trim END
+ set showbreak=>
+ call setline(1, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.')
+ exe "normal! z1\<CR>"
+ END
+ call writefile(lines, 'Xtest_visual_sbr', 'D')
+ let buf = RunVimInTerminal('-S Xtest_visual_sbr', {'rows': 6,'columns': 60})
+
+ call term_sendkeys(buf, "v$")
+ call VerifyScreenDump(buf, 'Test_visual_sbr_1', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
endfunc
" This test must come before the Test_cursorline test, as it appears this
@@ -845,17 +914,20 @@ func Test_highlight_default_colorscheme_restores_links()
let hlTestHiPre = HighlightArgs('TestHi')
" Test colorscheme
+ call assert_equal("\ndefault", execute('colorscheme'))
hi clear
if exists('syntax_on')
syntax reset
endif
let g:colors_name = 'test'
+ call assert_equal("\ntest", execute('colorscheme'))
hi link TestLink ErrorMsg
hi TestHi ctermbg=green
" Restore default highlighting
colorscheme default
" 'default' should work no matter if highlight group was cleared
+ call assert_equal("\ndefault", execute('colorscheme'))
hi def link TestLink Identifier
hi def TestHi ctermbg=red
let hlTestLinkPost = HighlightArgs('TestLink')
diff --git a/src/nvim/testdir/test_history.vim b/test/old/testdir/test_history.vim
index f1c31dee04..482328ab4a 100644
--- a/src/nvim/testdir/test_history.vim
+++ b/test/old/testdir/test_history.vim
@@ -244,9 +244,22 @@ endfunc
" Test for making sure the key value is not stored in history
func Test_history_crypt_key()
CheckFeature cryptv
+
call feedkeys(":set bs=2 key=abc ts=8\<CR>", 'xt')
call assert_equal('set bs=2 key= ts=8', histget(':'))
+
+ call assert_fails("call feedkeys(':set bs=2 key-=abc ts=8\<CR>', 'xt')")
+ call assert_equal('set bs=2 key-= ts=8', histget(':'))
+
set key& bs& ts&
endfunc
+" The following used to overflow and causing an use-after-free
+func Test_history_max_val()
+
+ set history=10
+ call assert_fails(':history 2147483648', 'E1510:')
+ set history&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_hlsearch.vim b/test/old/testdir/test_hlsearch.vim
index cf2791113a..fb1695220a 100644
--- a/src/nvim/testdir/test_hlsearch.vim
+++ b/test/old/testdir/test_hlsearch.vim
@@ -1,5 +1,8 @@
" Test for v:hlsearch
+source check.vim
+source screendump.vim
+
func Test_hlsearch()
new
call setline(1, repeat(['aaa'], 10))
@@ -63,3 +66,42 @@ func Test_hlsearch_eol_highlight()
set nohlsearch
bwipe!
endfunc
+
+func Test_hlsearch_Ctrl_R()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set incsearch hlsearch
+ let @" = "text"
+ put
+ END
+ call writefile(lines, 'XhlsearchCtrlR', 'D')
+ let buf = RunVimInTerminal('-S XhlsearchCtrlR', #{rows: 6, cols: 60})
+
+ call term_sendkeys(buf, "/\<C-R>\<C-R>\"")
+ call VerifyScreenDump(buf, 'Test_hlsearch_ctrlr_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_hlsearch_clipboard()
+ CheckRunVimInTerminal
+ CheckFeature clipboard_working
+
+ let lines =<< trim END
+ set incsearch hlsearch
+ let @* = "text"
+ put *
+ END
+ call writefile(lines, 'XhlsearchClipboard', 'D')
+ let buf = RunVimInTerminal('-S XhlsearchClipboard', #{rows: 6, cols: 60})
+
+ call term_sendkeys(buf, "/\<C-R>*")
+ call VerifyScreenDump(buf, 'Test_hlsearch_ctrlr_1', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_increment.vim b/test/old/testdir/test_increment.vim
index 3c2b88ef9f..433b2b4471 100644
--- a/src/nvim/testdir/test_increment.vim
+++ b/test/old/testdir/test_increment.vim
@@ -841,6 +841,22 @@ func Test_increment_unsigned()
set nrformats-=unsigned
endfunc
+func Test_in_decrement_large_number()
+ " NOTE: 18446744073709551616 == 2^64
+ call setline(1, '18446744073709551616')
+ exec "norm! gg0\<C-X>"
+ call assert_equal('18446744073709551615', getline(1))
+
+ exec "norm! gg0\<C-X>"
+ call assert_equal('18446744073709551614', getline(1))
+
+ exec "norm! gg0\<C-A>"
+ call assert_equal('18446744073709551615', getline(1))
+
+ exec "norm! gg0\<C-A>"
+ call assert_equal('-18446744073709551615', getline(1))
+endfunc
+
func Test_normal_increment_with_virtualedit()
set virtualedit=all
diff --git a/src/nvim/testdir/test_increment_dbcs.vim b/test/old/testdir/test_increment_dbcs.vim
index e5d5ccffb3..e5d5ccffb3 100644
--- a/src/nvim/testdir/test_increment_dbcs.vim
+++ b/test/old/testdir/test_increment_dbcs.vim
diff --git a/src/nvim/testdir/test_indent.vim b/test/old/testdir/test_indent.vim
index 3b5b643177..3b5b643177 100644
--- a/src/nvim/testdir/test_indent.vim
+++ b/test/old/testdir/test_indent.vim
diff --git a/src/nvim/testdir/test_input.vim b/test/old/testdir/test_input.vim
index 3b1e2eb2df..3b1e2eb2df 100644
--- a/src/nvim/testdir/test_input.vim
+++ b/test/old/testdir/test_input.vim
diff --git a/src/nvim/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim
index ec1379a378..e1c8b82908 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/test/old/testdir/test_ins_complete.vim
@@ -375,6 +375,54 @@ func Test_completefunc_info()
set completefunc&
endfunc
+" Test that mouse scrolling/movement should not interrupt completion.
+func Test_mouse_scroll_move_during_completion()
+ new
+ com! -buffer TestCommand1 echo 'TestCommand1'
+ com! -buffer TestCommand2 echo 'TestCommand2'
+ call setline(1, ['', '', '', '', ''])
+ call cursor(5, 1)
+
+ " Without completion menu scrolling can move text.
+ set completeopt-=menu wrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelDown>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_notequal(1, winsaveview().topline)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelUp>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ set nowrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelRight>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_notequal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelLeft>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<MouseMove>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+
+ " With completion menu scrolling cannot move text.
+ set completeopt+=menu wrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelDown>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelUp>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(1, winsaveview().topline)
+ set nowrap
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelRight>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<ScrollWheelLeft>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+ call assert_equal(0, winsaveview().leftcol)
+ call feedkeys("ccT\<C-X>\<C-V>\<MouseMove>\<C-V>", 'tx')
+ call assert_equal('TestCommand2', getline('.'))
+
+ bwipe!
+ set completeopt& wrap&
+endfunc
+
" Check that when using feedkeys() typeahead does not interrupt searching for
" completions.
func Test_compl_feedkeys()
@@ -482,10 +530,8 @@ func Test_ins_completeslash()
CheckMSWindows
call mkdir('Xdir')
-
let orig_shellslash = &shellslash
set cpt&
-
new
set noshellslash
@@ -572,7 +618,7 @@ func Test_pum_with_folds_two_tabs()
call writefile(lines, 'Xpumscript')
let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "a\<C-N>")
call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {})
@@ -597,9 +643,8 @@ func Test_pum_with_preview_win()
call writefile(lines, 'Xpreviewscript')
let buf = RunVimInTerminal('-S Xpreviewscript', #{rows: 12})
- call term_wait(buf, 100)
call term_sendkeys(buf, "Gi\<C-X>\<C-O>")
- call term_wait(buf, 200)
+ call TermWait(buf, 200)
call term_sendkeys(buf, "\<C-N>")
call VerifyScreenDump(buf, 'Test_pum_with_preview_win', {})
@@ -608,6 +653,24 @@ func Test_pum_with_preview_win()
call delete('Xpreviewscript')
endfunc
+func Test_scrollbar_on_wide_char()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, ['a', ' 啊啊啊',
+ \ ' 哦哦哦',
+ \ ' 呃呃呃'])
+ call setline(5, range(10)->map({i, v -> 'aa' .. v .. 'bb'}))
+ END
+ call writefile(lines, 'Xwidescript')
+ let buf = RunVimInTerminal('-S Xwidescript', #{rows: 10})
+ call term_sendkeys(buf, "A\<C-N>")
+ call VerifyScreenDump(buf, 'Test_scrollbar_on_wide_char', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xwidescript')
+endfunc
+
" Test for inserting the tag search pattern in insert mode
func Test_ins_compl_tag_sft()
call writefile([
@@ -817,7 +880,7 @@ func Test_complete_add_onechar()
setlocal complete=.
call setline(1, ['workhorse', 'workload'])
normal Go
- exe "normal aWOR\<C-P>\<bs>\<bs>\<bs>\<bs>\<bs>\<bs>\<C-L>r\<C-L>\<C-L>"
+ exe "normal aWOR\<C-P>\<bs>\<bs>\<bs>\<bs>\<bs>\<bs>\<C-L>\<C-L>\<C-L>"
call assert_equal('workh', getline(3))
set ignorecase& backspace&
close!
@@ -1550,11 +1613,11 @@ func Test_completefunc_callback()
bw!
# Test for using a script-local function name
- def s:LocalCompleteFunc(findstart: number, base: string): any
+ def LocalCompleteFunc(findstart: number, base: string): any
add(g:LocalCompleteFuncArgs, [findstart, base])
return findstart ? 0 : []
enddef
- &completefunc = s:LocalCompleteFunc
+ &completefunc = LocalCompleteFunc
new | only
setline(1, 'three')
g:LocalCompleteFuncArgs = []
@@ -1807,11 +1870,11 @@ func Test_omnifunc_callback()
bw!
# Test for using a script-local function name
- def s:LocalOmniFunc(findstart: number, base: string): any
+ def LocalOmniFunc(findstart: number, base: string): any
add(g:LocalOmniFuncArgs, [findstart, base])
return findstart ? 0 : []
enddef
- &omnifunc = s:LocalOmniFunc
+ &omnifunc = LocalOmniFunc
new | only
setline(1, 'three')
g:LocalOmniFuncArgs = []
@@ -2100,11 +2163,11 @@ func Test_thesaurusfunc_callback()
bw!
# Test for using a script-local function name
- def s:LocalTsrFunc(findstart: number, base: string): any
+ def LocalTsrFunc(findstart: number, base: string): any
add(g:LocalTsrFuncArgs, [findstart, base])
return findstart ? 0 : []
enddef
- &thesaurusfunc = s:LocalTsrFunc
+ &thesaurusfunc = LocalTsrFunc
new | only
setline(1, 'three')
g:LocalTsrFuncArgs = []
@@ -2189,4 +2252,159 @@ func Test_ins_complete_end_of_line()
bwipe!
endfunc
+func s:Tagfunc(t,f,o)
+ bwipe!
+ return []
+endfunc
+
+" This was using freed memory, since 'complete' was in a wiped out buffer.
+" Also using a window that was closed.
+func Test_tagfunc_wipes_out_buffer()
+ new
+ set complete=.,t,w,b,u,i
+ se tagfunc=s:Tagfunc
+ sil norm i
+
+ bwipe!
+endfunc
+
+func Test_ins_complete_popup_position()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ set nowrap
+ setline(1, ['one', 'two', 'this is line ', 'four'])
+ prop_type_add('test', {highlight: 'Error'})
+ prop_add(3, 0, {
+ text_align: 'above',
+ text: 'The quick brown fox jumps over the lazy dog',
+ type: 'test'
+ })
+ END
+ call writefile(lines, 'XinsPopup', 'D')
+ let buf = RunVimInTerminal('-S XinsPopup', #{rows: 10})
+
+ call term_sendkeys(buf, "3GA\<C-N>")
+ call VerifyScreenDump(buf, 'Test_ins_complete_popup_position_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func GetCompleteInfo()
+ let g:compl_info = complete_info()
+ return ''
+endfunc
+
+func Test_completion_restart()
+ new
+ set complete=. completeopt=menuone backspace=2
+ call setline(1, 'workhorse workhorse')
+ exe "normal $a\<C-N>\<BS>\<BS>\<C-R>=GetCompleteInfo()\<CR>"
+ call assert_equal(1, len(g:compl_info['items']))
+ call assert_equal('workhorse', g:compl_info['items'][0]['word'])
+ set complete& completeopt& backspace&
+ bwipe!
+endfunc
+
+func Test_complete_info_index()
+ new
+ call setline(1, ["aaa", "bbb", "ccc", "ddd", "eee", "fff"])
+ inoremap <buffer><F5> <C-R>=GetCompleteInfo()<CR>
+
+ " Ensure 'index' in complete_info() is coherent with the 'items' array.
+
+ set completeopt=menu,preview
+ " Search forward
+ call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("aaa", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("bbb", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("ccc", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("ddd", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("eee", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("fff", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ " Search forward: unselected item
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(6 , len(g:compl_info['items']))
+ call assert_equal(-1 , g:compl_info['selected'])
+
+ " Search backward
+ call feedkeys("Go\<C-X>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("fff", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("eee", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("ddd", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("ccc", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("bbb", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("aaa", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ " search backwards: unselected item
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(6 , len(g:compl_info['items']))
+ call assert_equal(-1 , g:compl_info['selected'])
+
+ " switch direction: forwards, then backwards
+ call feedkeys("Go\<C-X>\<C-N>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("fff", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ " switch direction: forwards, then backwards, then forwards again
+ call feedkeys("Go\<C-X>\<C-N>\<C-P>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-P>\<C-P>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("aaa", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+
+ " switch direction: backwards, then forwards
+ call feedkeys("Go\<C-X>\<C-P>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("aaa", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ " switch direction: backwards, then forwards, then backwards again
+ call feedkeys("Go\<C-X>\<C-P>\<C-P>\<C-N>\<C-N>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("fff", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+
+ " Add 'noselect', check that 'selected' is -1 when nothing is selected.
+ set completeopt+=noselect
+ " Search forward.
+ call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(-1, g:compl_info['selected'])
+
+ " Search backward.
+ call feedkeys("Go\<C-X>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(-1, g:compl_info['selected'])
+
+ call feedkeys("Go\<C-X>\<C-N>\<C-P>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(0, g:compl_info['selected'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call assert_equal("fff", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal("aaa", g:compl_info['items'][g:compl_info['selected']]['word'])
+ call assert_equal(6 , len(g:compl_info['items']))
+ call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>_dd", 'tx')
+ call assert_equal(-1, g:compl_info['selected'])
+ call assert_equal(6 , len(g:compl_info['items']))
+
+ set completeopt&
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_ins_complete_no_halt.vim b/test/old/testdir/test_ins_complete_no_halt.vim
index e12925daa9..e12925daa9 100644
--- a/src/nvim/testdir/test_ins_complete_no_halt.vim
+++ b/test/old/testdir/test_ins_complete_no_halt.vim
diff --git a/src/nvim/testdir/test_interrupt.vim b/test/old/testdir/test_interrupt.vim
index aa7f634302..aa7f634302 100644
--- a/src/nvim/testdir/test_interrupt.vim
+++ b/test/old/testdir/test_interrupt.vim
diff --git a/src/nvim/testdir/test_join.vim b/test/old/testdir/test_join.vim
index 1f7a0825a5..1f7a0825a5 100644
--- a/src/nvim/testdir/test_join.vim
+++ b/test/old/testdir/test_join.vim
diff --git a/src/nvim/testdir/test_jumplist.vim b/test/old/testdir/test_jumplist.vim
index 91ad940e18..b4dcdad9d6 100644
--- a/src/nvim/testdir/test_jumplist.vim
+++ b/test/old/testdir/test_jumplist.vim
@@ -104,4 +104,70 @@ d
bwipe!
endfunc
+" Test for 'jumpoptions'
+func Test_jumpoptions()
+ new
+ call setline(1, range(1, 200))
+ clearjumps
+ set jumpoptions=stack
+
+ " Jump around to add some locations to the jump list.
+ normal 10G
+ normal 20G
+ normal 30G
+ normal 40G
+ normal 50G
+ let bnr = bufnr()
+
+ " discards the tail when navigating from the middle
+ exe "normal \<C-O>\<C-O>"
+ call assert_equal([
+ \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 40, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 50, 'bufnr': bnr, 'col': 0, 'coladd': 0}
+ \ ], 3], getjumplist())
+
+ " new jump location is added immediately after the last one
+ normal 90G
+ call assert_equal([
+ \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ ], 4], getjumplist())
+
+ " does not add the same location twice adjacently
+ normal 60G
+ normal 60G
+ call assert_equal([
+ \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ "\ Nvim: avoids useless/phantom jumps
+ "\ {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ "\ ], 6], getjumplist())
+ \ ], 5], getjumplist())
+
+ " does add the same location twice non adjacently
+ normal 10G
+ normal 20G
+ call assert_equal([
+ \ [{'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 20, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 30, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 90, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 60, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ {'lnum': 10, 'bufnr': bnr, 'col': 0, 'coladd': 0},
+ \ ], 7], getjumplist())
+
+ set jumpoptions&
+ %bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_lambda.vim b/test/old/testdir/test_lambda.vim
index 025eb016a8..810b41b389 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/test/old/testdir/test_lambda.vim
@@ -329,6 +329,7 @@ func Test_closure_error()
let caught_932 = 1
endtry
call assert_equal(1, caught_932)
+ call delete('Xscript')
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_langmap.vim b/test/old/testdir/test_langmap.vim
index aaed77e109..aaed77e109 100644
--- a/src/nvim/testdir/test_langmap.vim
+++ b/test/old/testdir/test_langmap.vim
diff --git a/src/nvim/testdir/test_largefile.vim b/test/old/testdir/test_largefile.vim
index fa32b7fb5c..fa32b7fb5c 100644
--- a/src/nvim/testdir/test_largefile.vim
+++ b/test/old/testdir/test_largefile.vim
diff --git a/src/nvim/testdir/test_let.vim b/test/old/testdir/test_let.vim
index 35745e9c6a..a9cc8a14a4 100644
--- a/src/nvim/testdir/test_let.vim
+++ b/test/old/testdir/test_let.vim
@@ -1,5 +1,7 @@
" Tests for the :let command.
+source vim9.vim
+
func Test_let()
" Test to not autoload when assigning. It causes internal error.
set runtimepath+=./sautest
@@ -85,6 +87,24 @@ func Test_let()
let l:Test_Local_Var = {'k' : 5}
call assert_match("\nl:Test_Local_Var {'k': 5}", execute('let l:'))
call assert_match("v:errors []", execute('let v:'))
+
+ " Test for assigning multiple list items
+ let l = [1, 2, 3]
+ let [l[0], l[1]] = [10, 20]
+ call assert_equal([10, 20, 3], l)
+
+ " Test for errors in conditional expression
+ call assert_fails('let val = [] ? 1 : 2', 'E745:')
+ call assert_fails('let val = 1 ? 5+ : 6', 'E121:')
+ call assert_fails('let val = 1 ? 0 : 5+', 'E15:')
+ call assert_false(exists('val'))
+
+ " Test for errors in logical operators
+ let @a = 'if [] || 0 | let val = 2 | endif'
+ call assert_fails('exe @a', 'E745:')
+ call assert_fails('call feedkeys(":let val = 0 || []\<cr>", "xt")', 'E745:')
+ call assert_fails('exe "let val = [] && 5"', 'E745:')
+ call assert_fails('exe "let val = 6 && []"', 'E745:')
endfunc
func s:set_arg1(a) abort
@@ -222,7 +242,7 @@ func Test_let_no_type_checking()
endfunc
func Test_let_termcap()
- throw 'skipped: Nvim does not support termcap option'
+ throw 'Skipped: Nvim does not support termcap options'
" Terminal code
let old_t_te = &t_te
let &t_te = "\<Esc>[yes;"
@@ -237,25 +257,45 @@ func Test_let_termcap()
let &t_k1 = old_t_k1
endif
- call assert_fails('let x = &t_xx', 'E113')
+ call assert_fails('let x = &t_xx', 'E113:')
let &t_xx = "yes"
call assert_equal("yes", &t_xx)
let &t_xx = ""
- call assert_fails('let x = &t_xx', 'E113')
+ call assert_fails('let x = &t_xx', 'E113:')
endfunc
func Test_let_option_error()
let _w = &tw
let &tw = 80
- call assert_fails('let &tw .= 1', 'E734')
+ call assert_fails('let &tw .= 1', ['E734:', 'E734:'])
+ call assert_fails('let &tw .= []', ['E734:', 'E734:'])
+ call assert_fails('let &tw = []', ['E745:', 'E745:'])
+ call assert_fails('let &tw += []', ['E745:', 'E745:'])
call assert_equal(80, &tw)
let &tw = _w
+ let _w = &autoread
+ let &autoread = 1
+ call assert_fails('let &autoread .= 1', ['E734:', 'E734:'])
+ call assert_fails('let &autoread .= []', ['E734:', 'E734:'])
+ call assert_fails('let &autoread = []', ['E745:', 'E745:'])
+ call assert_fails('let &autoread += []', ['E745:', 'E745:'])
+ call assert_equal(1, &autoread)
+ let &autoread = _w
+
let _w = &fillchars
let &fillchars = "vert:|"
- call assert_fails('let &fillchars += "diff:-"', 'E734')
+ call assert_fails('let &fillchars += "diff:-"', ['E734:', 'E734:'])
+ call assert_fails('let &fillchars += []', ['E734:', 'E734:'])
+ call assert_fails('let &fillchars = []', ['E730:', 'E730:'])
+ call assert_fails('let &fillchars .= []', ['E730:', 'E730:'])
call assert_equal("vert:|", &fillchars)
let &fillchars = _w
+
+ call assert_fails('let &nosuchoption = 1', ['E355:', 'E355:'])
+ call assert_fails('let &nosuchoption = ""', ['E355:', 'E355:'])
+ call assert_fails('let &nosuchoption = []', ['E355:', 'E355:'])
+ call assert_fails('let &t_xx = []', ['E730:', 'E730:'])
endfunc
" Errors with the :let statement
@@ -318,7 +358,43 @@ func Test_let_heredoc_fails()
call assert_report('No exception thrown')
catch /E488:/
catch
- call assert_report("Caught exception: " .. v:exception)
+ call assert_report('Caught exception: ' .. v:exception)
+ endtry
+
+ try
+ let &commentstring =<< trim TEXT
+ change
+ insert
+ append
+ TEXT
+ call assert_report('No exception thrown')
+ catch /E730:/
+ catch
+ call assert_report('Caught exception: ' .. v:exception)
+ endtry
+
+ try
+ let $SOME_ENV_VAR =<< trim TEXT
+ change
+ insert
+ append
+ TEXT
+ call assert_report('No exception thrown')
+ catch /E730:/
+ catch
+ call assert_report('Caught exception: ' .. v:exception)
+ endtry
+
+ try
+ let @r =<< trim TEXT
+ change
+ insert
+ append
+ TEXT
+ call assert_report('No exception thrown')
+ catch /E730:/
+ catch
+ call assert_report('Caught exception: ' .. v:exception)
endtry
let text =<< trim END
@@ -327,7 +403,7 @@ func Test_let_heredoc_fails()
endfunc
END
call writefile(text, 'XheredocFail')
- call assert_fails('source XheredocFail', 'E126:')
+ call assert_fails('source XheredocFail', 'E1145:')
call delete('XheredocFail')
let text =<< trim CodeEnd
@@ -336,7 +412,7 @@ func Test_let_heredoc_fails()
endfunc
CodeEnd
call writefile(text, 'XheredocWrong')
- call assert_fails('source XheredocWrong', 'E126:')
+ call assert_fails('source XheredocWrong', 'E1145:')
call delete('XheredocWrong')
let text =<< trim TEXTend
@@ -367,7 +443,18 @@ END
call assert_equal(['Text', 'with', 'indent'], text)
endfunc
-" Test for the setting a variable using the heredoc syntax
+func Test_let_interpolated()
+ call assert_equal('{text}', $'{{text}}')
+ call assert_equal('{{text}}', $'{{{{text}}}}')
+ let text = 'text'
+ call assert_equal('text{{', $'{text .. "{{"}')
+ call assert_equal('text{{', $"{text .. '{{'}")
+ call assert_equal('text{{', $'{text .. '{{'}')
+ call assert_equal('text{{', $"{text .. "{{"}")
+endfunc
+
+" Test for the setting a variable using the heredoc syntax.
+" Keep near the end, this messes up highlighting.
func Test_let_heredoc()
let var1 =<< END
Some sample text
@@ -473,6 +560,137 @@ E
z
END
call assert_equal([' x', ' \y', ' z'], [a, b, c])
+
+ " unpack assignment without whitespace
+ let[a,b,c]=<<END
+change
+insert
+append
+END
+ call assert_equal(['change', 'insert', 'append'], [a, b, c])
+
+ " curly braces name and list slice assignment
+ let foo_3_bar = ['', '', '']
+ let foo_{1 + 2}_bar[ : ] =<< END
+change
+insert
+append
+END
+ call assert_equal(['change', 'insert', 'append'], foo_3_bar)
+
+ " dictionary key containing brackets and spaces
+ let d = {'abc] 123': 'baz'}
+ let d[d['abc] 123'] .. '{'] =<< END
+change
+insert
+append
+END
+ call assert_equal(['change', 'insert', 'append'], d['baz{'])
+endfunc
+
+" Test for evaluating Vim expressions in a heredoc using {expr}
+" Keep near the end, this messes up highlighting.
+func Test_let_heredoc_eval()
+ let str = ''
+ let code =<< trim eval END
+ let a = {5 + 10}
+ let b = {min([10, 6])} + {max([4, 6])}
+ {str}
+ let c = "abc{str}d"
+ END
+ call assert_equal(['let a = 15', 'let b = 6 + 6', '', 'let c = "abcd"'], code)
+
+ let $TESTVAR = "Hello"
+ let code =<< eval trim END
+ let s = "{$TESTVAR}"
+ END
+ call assert_equal(['let s = "Hello"'], code)
+
+ let code =<< eval END
+ let s = "{$TESTVAR}"
+END
+ call assert_equal([' let s = "Hello"'], code)
+
+ let a = 10
+ let data =<< eval END
+{a}
+END
+ call assert_equal(['10'], data)
+
+ let x = 'X'
+ let code =<< eval trim END
+ let a = {{abc}}
+ let b = {x}
+ let c = {{
+ END
+ call assert_equal(['let a = {abc}', 'let b = X', 'let c = {'], code)
+
+ let code = 'xxx'
+ let code =<< eval trim END
+ let n = {5 +
+ 6}
+ END
+ call assert_equal('xxx', code)
+
+ let code =<< eval trim END
+ let n = {min([1, 2]} + {max([3, 4])}
+ END
+ call assert_equal('xxx', code)
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {
+ END
+ LINES
+ call CheckScriptFailure(lines, 'E1279:')
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {abc
+ END
+ LINES
+ call CheckScriptFailure(lines, 'E1279:')
+
+ let lines =<< trim LINES
+ let text =<< eval trim END
+ let b = {}
+ END
+ LINES
+ call CheckScriptFailure(lines, 'E15:')
+
+ " skipped heredoc
+ if 0
+ let msg =<< trim eval END
+ n is: {n}
+ END
+ endif
+
+ " Test for sourcing a script containing a heredoc with invalid expression.
+ " Variable assignment should fail, if expression evaluation fails
+ new
+ let g:Xvar = 'test'
+ let g:b = 10
+ let lines =<< trim END
+ let Xvar =<< eval CODE
+ let a = 1
+ let b = {5+}
+ let c = 2
+ CODE
+ let g:Count += 1
+ END
+ call setline(1, lines)
+ let g:Count = 0
+ call assert_fails('source', 'E15:')
+ call assert_equal(1, g:Count)
+ call setline(3, 'let b = {abc}')
+ call assert_fails('source', 'E121:')
+ call assert_equal(2, g:Count)
+ call setline(3, 'let b = {abc} + {min([9, 4])} + 2')
+ call assert_fails('source', 'E121:')
+ call assert_equal(3, g:Count)
+ call assert_equal('test', g:Xvar)
+ call assert_equal(10, g:b)
+ bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_lineending.vim b/test/old/testdir/test_lineending.vim
index 5be3be8db3..5be3be8db3 100644
--- a/src/nvim/testdir/test_lineending.vim
+++ b/test/old/testdir/test_lineending.vim
diff --git a/src/nvim/testdir/test_lispindent.vim b/test/old/testdir/test_lispindent.vim
index 2d6060bba3..2d6060bba3 100644
--- a/src/nvim/testdir/test_lispindent.vim
+++ b/test/old/testdir/test_lispindent.vim
diff --git a/src/nvim/testdir/test_listchars.vim b/test/old/testdir/test_listchars.vim
index eed4d7e30c..179bdfa4a0 100644
--- a/src/nvim/testdir/test_listchars.vim
+++ b/test/old/testdir/test_listchars.vim
@@ -4,6 +4,36 @@ source check.vim
source view_util.vim
source screendump.vim
+func Check_listchars(expected, end_lnum, end_scol = -1, leftcol = 0)
+ if a:leftcol > 0
+ let save_wrap = &wrap
+ set nowrap
+ call cursor(1, 1)
+ exe 'normal! ' .. a:leftcol .. 'zl'
+ endif
+
+ redraw!
+ for i in range(1, a:end_lnum)
+ if a:leftcol > 0
+ let col = virtcol2col(0, i, a:leftcol)
+ let col += getline(i)->strpart(col - 1, 1, v:true)->len()
+ call cursor(i, col)
+ redraw
+ call assert_equal(a:leftcol, winsaveview().leftcol)
+ else
+ call cursor(i, 1)
+ end
+
+ let end_scol = a:end_scol < 0 ? '$'->virtcol() - a:leftcol : a:end_scol
+ call assert_equal([a:expected[i - 1]->strcharpart(a:leftcol)],
+ \ ScreenLines(i, end_scol))
+ endfor
+
+ if a:leftcol > 0
+ let &wrap = save_wrap
+ endif
+endfunc
+
func Test_listchars()
enew!
set ff=unix
@@ -24,11 +54,8 @@ func Test_listchars()
\ 'dd........ee<<>-$',
\ '<$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, '$'->virtcol()))
- endfor
+ call Check_listchars(expected, 5)
+ call Check_listchars(expected, 4, -1, 5)
set listchars-=trail:<
let expected = [
@@ -38,11 +65,8 @@ func Test_listchars()
\ 'dd........ee..>-$',
\ '.$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
+ call Check_listchars(expected, 5)
+ call Check_listchars(expected, 4, -1, 5)
" tab with 3rd character.
set listchars-=tab:>-
@@ -54,11 +78,8 @@ func Test_listchars()
\ 'dd........ee--<>$',
\ '-$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
+ call Check_listchars(expected, 5)
+ call Check_listchars(expected, 4, -1, 5)
" tab with 3rd character and linebreak set
set listchars-=tab:<=>
@@ -71,11 +92,7 @@ func Test_listchars()
\ 'dd........ee--<>$',
\ '-$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
+ call Check_listchars(expected, 5)
set nolinebreak
set listchars-=tab:<·>
set listchars+=tab:<=>
@@ -88,11 +105,8 @@ func Test_listchars()
\ 'dd........ee..<>$',
\ '.$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
+ call Check_listchars(expected, 5)
+ call Check_listchars(expected, 4, -1, 5)
set listchars-=tab:<=>
set listchars+=tab:>-
@@ -110,7 +124,8 @@ func Test_listchars()
\ '..fff>--<<$',
\ '>-------gg>-----$',
\ '.....h>-$',
- \ 'iii<<<<><<$', '$'], l)
+ \ 'iii<<<<><<$',
+ \ '$'], l)
" Test lead and trail
normal ggdG
@@ -132,14 +147,10 @@ func Test_listchars()
\ 'h<<<<<<<<<<<$',
\ '<<<<<<<<<<<<$',
\ '>>>>0xx0<<<<$',
- \ '$'
+ \ '$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test multispace
@@ -162,14 +173,10 @@ func Test_listchars()
\ ' hyYzZyYzZyY$',
\ 'yYzZyYzZyYj $',
\ 'yYzZ0yY0yYzZ$',
- \ '$'
+ \ '$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace + multispace
@@ -192,15 +199,14 @@ func Test_listchars()
\ ' hyYzZyYzZyY$',
\ '.-+*.-+*.-j $',
\ '.-+*0yY0yYzZ$',
- \ '$'
+ \ '$'
\ ]
- redraw!
call assert_equal('eol:$,multispace:yYzZ,nbsp:S,leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 1)
+ call Check_listchars(expected, 5, -1, 2)
+ call Check_listchars(expected, 5, -1, 3)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace without multispace
@@ -223,16 +229,14 @@ func Test_listchars()
\ '+h>>>>>>>>>>$',
\ '.-+*.-+*.-j>$',
\ '.-+*0++0>>>>$',
- \ '$',
+ \ '$'
\ ]
-
- redraw!
call assert_equal('eol:$,nbsp:S,leadmultispace:.-+*,space:+,trail:>,eol:$', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 1)
+ call Check_listchars(expected, 5, -1, 2)
+ call Check_listchars(expected, 5, -1, 3)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" Test leadmultispace only
@@ -255,16 +259,17 @@ func Test_listchars()
\ ' h ',
\ '.-+*.-+*.-j ',
\ '.-+*0 0 ',
- \ ' ',
+ \ ' '
\ ]
- redraw!
call assert_equal('leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, 12))
- endfor
+ call Check_listchars(expected, 5, 12)
call assert_equal(expected, split(execute("%list"), "\n"))
+ " Changing the value of 'ambiwidth' twice shouldn't cause double-free when
+ " "leadmultispace" is specified.
+ set ambiwidth=double
+ set ambiwidth&
+
" Test leadmultispace and lead and space
normal ggdG
set listchars=eol:$ " Accommodate Nvim default
@@ -286,14 +291,14 @@ func Test_listchars()
\ '<h----------$',
\ '.-+*.-+*.-j-$',
\ '.-+*0--0----$',
- \ '$',
+ \ '$'
\ ]
- redraw!
call assert_equal('eol:$,lead:<,space:-,leadmultispace:.-+*', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 1)
+ call Check_listchars(expected, 5, -1, 2)
+ call Check_listchars(expected, 5, -1, 3)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" the last occurrence of 'multispace:' is used
@@ -307,15 +312,11 @@ func Test_listchars()
\ 'xhXyYXyYXyYX$',
\ 'XyYXyYXyYXjx$',
\ 'XyYX0Xy0XyYX$',
- \ '$'
+ \ '$'
\ ]
- redraw!
call assert_equal('eol:$,multispace:yYzZ,space:x,multispace:XyY', &listchars)
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
set listchars+=lead:>,trail:<
@@ -326,14 +327,10 @@ func Test_listchars()
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0Xy0<<<<$',
- \ '$'
+ \ '$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" removing 'multispace:'
@@ -346,14 +343,10 @@ func Test_listchars()
\ '>h<<<<<<<<<<$',
\ '>>>>>>>>>>j<$',
\ '>>>>0xx0<<<<$',
- \ '$'
+ \ '$'
\ ]
- redraw!
- for i in range(1, 5)
- call cursor(i, 1)
- call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$')))
- endfor
-
+ call Check_listchars(expected, 6)
+ call Check_listchars(expected, 5, -1, 6)
call assert_equal(expected, split(execute("%list"), "\n"))
" test nbsp
@@ -365,15 +358,10 @@ func Test_listchars()
call append(0, [ ">" .. nbsp .. "<" ])
let expected = '>X< '
-
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, virtcol('$')))
+ call Check_listchars([expected], 1)
set listchars=nbsp:X
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, virtcol('$')))
+ call Check_listchars([expected], 1)
" test extends
normal ggdG
@@ -383,16 +371,11 @@ func Test_listchars()
call append(0, [ repeat('A', &columns + 1) ])
let expected = repeat('A', &columns)
-
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, &columns))
+ call Check_listchars([expected], 1, &columns)
set list
let expected = expected[:-2] . 'Z'
- redraw!
- call cursor(1, 1)
- call assert_equal([expected], ScreenLines(1, &columns))
+ call Check_listchars([expected], 1, &columns)
enew!
set listchars& ff&
@@ -411,19 +394,20 @@ func Test_listchars_unicode()
let nbsp = nr2char(0xa0)
call append(0, [" a\tb c" .. nbsp .. "d "])
let expected = ['≡≢≣≡≢≣≡≢a←↔↔↔↔↔→b␣c≠d≡≢⇔']
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
+ call Check_listchars(expected, 1)
+ call Check_listchars(expected, 1, -1, 3)
+ call Check_listchars(expected, 1, -1, 13)
set listchars=eol:\\u21d4,space:\\u2423,multispace:≡\\u2262\\U00002263,nbsp:\\U00002260,tab:←↔\\u2192
- redraw!
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
+ call Check_listchars(expected, 1)
+ call Check_listchars(expected, 1, -1, 3)
+ call Check_listchars(expected, 1, -1, 13)
set listchars+=lead:⇨,trail:⇦
let expected = ['⇨⇨⇨⇨⇨⇨⇨⇨a←↔↔↔↔↔→b␣c≠d⇦⇦⇔']
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
+ call Check_listchars(expected, 1)
+ call Check_listchars(expected, 1, -1, 3)
+ call Check_listchars(expected, 1, -1, 13)
let &encoding=oldencoding
enew!
@@ -506,7 +490,7 @@ func Test_listchars_composing()
set list
set listchars=eol:$,space:_,nbsp:=
-
+
let nbsp1 = nr2char(0xa0)
let nbsp2 = nr2char(0x202f)
call append(0, [
@@ -515,9 +499,7 @@ func Test_listchars_composing()
let expected = [
\ "_ \u3099^I \u309A=" .. nbsp1 .. "\u0302=" .. nbsp2 .. "\u0302$"
\ ]
- redraw!
- call cursor(1, 1)
- call assert_equal(expected, ScreenLines(1, virtcol('$')))
+ call Check_listchars(expected, 1)
let &encoding=oldencoding
enew!
set listchars& ff&
@@ -533,22 +515,22 @@ endfunc
func Test_listchars_window_local()
%bw!
set list listchars&
- let l:default_listchars = &listchars " Accommodate Nvim default
+ let nvim_default = &listchars " Accommodate Nvim default
new
" set a local value for 'listchars'
setlocal listchars=tab:+-,eol:#
call s:CheckListCharsValue('tab:+-,eol:#')
" When local value is reset, global value should be used
setlocal listchars=
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
+ call s:CheckListCharsValue(nvim_default)
" Use 'setlocal <' to copy global value
setlocal listchars=space:.,extends:>
setlocal listchars<
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
+ call s:CheckListCharsValue(nvim_default)
" Use 'set <' to copy global value
setlocal listchars=space:.,extends:>
set listchars<
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
+ call s:CheckListCharsValue(nvim_default)
" Changing global setting should not change the local setting
setlocal listchars=space:.,extends:>
setglobal listchars=tab:+-,eol:#
@@ -558,7 +540,7 @@ func Test_listchars_window_local()
call s:CheckListCharsValue('space:.,extends:>')
" clearing local value in one window should not change the other window
set listchars&
- call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default
+ call s:CheckListCharsValue(nvim_default)
close
call s:CheckListCharsValue('space:.,extends:>')
diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim
new file mode 100644
index 0000000000..fef7c6a9bc
--- /dev/null
+++ b/test/old/testdir/test_listdict.vim
@@ -0,0 +1,1425 @@
+" Tests for the List and Dict types
+scriptencoding utf-8
+
+source vim9.vim
+
+func TearDown()
+ " Run garbage collection after every test
+ call test_garbagecollect_now()
+endfunc
+
+" Tests for List type
+
+" List creation
+func Test_list_create()
+ " Creating List directly with different types
+ let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l))
+ call assert_equal({'a' : 1}, l[-1])
+ call assert_equal(1, l[-4])
+ let x = 10
+ try
+ let x = l[-5]
+ catch
+ call assert_match('E684:', v:exception)
+ endtry
+ call assert_equal(10, x)
+endfunc
+
+" List slices
+func Test_list_slice()
+ let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:])
+ call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:])
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2])
+ call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8])
+ call assert_equal([], l[8:-1])
+ call assert_equal([], l[0:-10])
+ " perform an operation on a list slice
+ let l = [1, 2, 3]
+ let l[:1] += [1, 2]
+ let l[2:] -= [1]
+ call assert_equal([2, 4, 2], l)
+
+ let lines =<< trim END
+ VAR l = [1, 2]
+ call assert_equal([1, 2], l[:])
+ call assert_equal([2], l[-1 : -1])
+ call assert_equal([1, 2], l[-2 : -1])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let l = [1, 2]
+ call assert_equal([], l[-3 : -1])
+
+ let lines =<< trim END
+ var l = [1, 2]
+ assert_equal([1, 2], l[-3 : -1])
+ END
+ call CheckDefAndScriptSuccess(lines)
+endfunc
+
+" List identity
+func Test_list_identity()
+ let lines =<< trim END
+ VAR l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ VAR ll = l
+ VAR lx = copy(l)
+ call assert_true(l == ll)
+ call assert_false(l isnot ll)
+ call assert_true(l is ll)
+ call assert_true(l == lx)
+ call assert_false(l is lx)
+ call assert_true(l isnot lx)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" removing items with :unlet
+func Test_list_unlet()
+ let lines =<< trim END
+ VAR l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},]
+ unlet l[2]
+ call assert_equal([1, 'as''d', {'a': 1}], l)
+ LET l = range(8)
+ unlet l[: 3]
+ unlet l[1 :]
+ call assert_equal([4], l)
+
+ #" removing items out of range: silently skip items that don't exist
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 2]
+ call assert_equal([0, 1, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 3]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 4]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[2 : 5]
+ call assert_equal([0, 1], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-2 : 2]
+ call assert_equal([0, 1, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-3 : 2]
+ call assert_equal([0, 3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-4 : 2]
+ call assert_equal([3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-5 : 2]
+ call assert_equal([3], l)
+ LET l = [0, 1, 2, 3]
+ unlet l[-6 : 2]
+ call assert_equal([3], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let l = [0, 1, 2, 3]
+ unlet l[2:2]
+ call assert_equal([0, 1, 3], l)
+ let l = [0, 1, 2, 3]
+ unlet l[2:3]
+ call assert_equal([0, 1], l)
+
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ unlet l[2 : 1]
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E684:')
+
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ unlet l[-1 : 2]
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E684:')
+endfunc
+
+" assignment to a list
+func Test_list_assign()
+ let lines =<< trim END
+ VAR l = [0, 1, 2, 3]
+ VAR va = 0
+ VAR vb = 0
+ LET [va, vb] = l[2 : 3]
+ call assert_equal([2, 3], [va, vb])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ let l = [0, 1, 2, 3]
+ let [va, vb] = l
+ END
+ call CheckScriptFailure(lines, 'E687:')
+ let lines =<< trim END
+ var l = [0, 1, 2, 3]
+ var va = 0
+ var vb = 0
+ [va, vb] = l
+ END
+ call CheckScriptFailure(['vim9script'] + lines, 'E687:')
+ call CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 4')
+
+ let lines =<< trim END
+ let l = [0, 1, 2, 3]
+ let [va, vb] = l[1:1]
+ END
+ call CheckScriptFailure(lines, 'E688:')
+ let lines =<< trim END
+ var l = [0, 1, 2, 3]
+ var va = 0
+ var vb = 0
+ [va, vb] = l[1 : 1]
+ END
+ call CheckScriptFailure(['vim9script'] + lines, 'E688:')
+ call CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 1')
+endfunc
+
+" test for range assign
+func Test_list_range_assign()
+ let lines =<< trim END
+ VAR l = [0]
+ LET l[:] = [1, 2]
+ call assert_equal([1, 2], l)
+ LET l[-4 : -1] = [5, 6]
+ call assert_equal([5, 6], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ var l = [7]
+ l[:] = ['text']
+ END
+ call CheckDefAndScriptFailure(lines, 'E1012:', 2)
+endfunc
+
+" Test removing items in list
+func Test_list_func_remove()
+ let lines =<< trim END
+ #" Test removing 1 element
+ VAR l = [1, 2, 3, 4]
+ call assert_equal(1, remove(l, 0))
+ call assert_equal([2, 3, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal(2, remove(l, 1))
+ call assert_equal([1, 3, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal(4, remove(l, -1))
+ call assert_equal([1, 2, 3], l)
+
+ #" Test removing range of element(s)
+ LET l = [1, 2, 3, 4]
+ call assert_equal([3], remove(l, 2, 2))
+ call assert_equal([1, 2, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, 1, 2))
+ call assert_equal([1, 4], l)
+
+ LET l = [1, 2, 3, 4]
+ call assert_equal([2, 3], remove(l, -3, -2))
+ call assert_equal([1, 4], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " Test invalid cases
+ let l = [1, 2, 3, 4]
+ call assert_fails("call remove(l, 5)", 'E684:')
+ call assert_fails("call remove(l, 1, 5)", 'E684:')
+ call assert_fails("call remove(l, 3, 2)", 'E16:')
+ call assert_fails("call remove(1, 0)", 'E896:')
+ call assert_fails("call remove(l, l)", 'E745:')
+endfunc
+
+" List add() function
+func Test_list_add()
+ let lines =<< trim END
+ VAR l = []
+ call add(l, 1)
+ call add(l, [2, 3])
+ call add(l, [])
+ call add(l, v:_null_list)
+ call add(l, {'k': 3})
+ call add(l, {})
+ call add(l, v:_null_dict)
+ call assert_equal([1, [2, 3], [], [], {'k': 3}, {}, {}], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " weird legacy behavior
+ " call assert_equal(1, add(v:_null_list, 4))
+endfunc
+
+" Tests for Dictionary type
+
+func Test_dict()
+ " Creating Dictionary directly with different types
+ let lines =<< trim END
+ VAR d = {'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}, }
+ call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
+ call assert_equal('asd', d.1)
+ call assert_equal(['-1', '1', 'b'], sort(keys(d)))
+ call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d))
+ call extend(d, {3: 33, 1: 99})
+ call extend(d, {'b': 'bbb', 'c': 'ccc'}, "keep")
+ call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},}
+ call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d))
+
+ let v = []
+ for [key, val] in items(d)
+ call extend(v, [key, val])
+ unlet key val
+ endfor
+ call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v)
+
+ call extend(d, {3: 33, 1: 99})
+ call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737:')
+
+ " duplicate key
+ call assert_fails("let d = {'k' : 10, 'k' : 20}", 'E721:')
+ " missing comma
+ call assert_fails("let d = {'k' : 10 'k' : 20}", 'E722:')
+ " missing curly brace
+ call assert_fails("let d = {'k' : 10,", 'E723:')
+ " invalid key
+ call assert_fails('let d = #{++ : 10}', 'E15:')
+ " wrong type for key
+ call assert_fails('let d={[] : 10}', 'E730:')
+ " undefined variable as value
+ call assert_fails("let d={'k' : i}", 'E121:')
+
+ " allow key starting with number at the start, not a curly expression
+ call assert_equal({'1foo': 77}, #{1foo: 77})
+
+ " #{expr} is not a curly expression
+ let x = 'x'
+ call assert_equal(#{g: x}, #{g:x})
+endfunc
+
+" Dictionary identity
+func Test_dict_identity()
+ let lines =<< trim END
+ VAR d = {'1': 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1}, }
+ VAR dd = d
+ VAR dx = copy(d)
+ call assert_true(d == dd)
+ call assert_false(d isnot dd)
+ call assert_true(d is dd)
+ call assert_true(d == dx)
+ call assert_false(d is dx)
+ call assert_true(d isnot dx)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" removing items with :unlet
+func Test_dict_unlet()
+ let lines =<< trim END
+ VAR d = {'b': 'bbb', '1': 99, '3': 33, '-1': {'a': 1}}
+ unlet d.b
+ unlet d[-1]
+ call assert_equal({'1': 99, '3': 33}, d)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" manipulating a big Dictionary (hashtable.c has a border of 1000 entries)
+func Test_dict_big()
+ let d = {}
+ for i in range(1500)
+ let d[i] = 3000 - i
+ endfor
+ call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]])
+ let str = ''
+ try
+ let n = d[1500]
+ catch
+ let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '')
+ endtry
+ call assert_equal('Vim(let):E716: "1500"', str)
+
+ " lookup each item
+ for i in range(1500)
+ call assert_equal(3000 - i, d[i])
+ endfor
+ let i += 1
+
+ " delete even items
+ while i >= 2
+ let i -= 2
+ unlet d[i]
+ endwhile
+ call assert_equal('NONE', get(d, 1500 - 100, 'NONE'))
+ call assert_equal(2999, d[1])
+
+ " delete odd items, checking value, one intentionally wrong
+ let d[33] = 999
+ let i = 1
+ while i < 1500
+ if i != 33
+ call assert_equal(3000 - i, d[i])
+ else
+ call assert_equal(999, d[i])
+ endif
+ unlet d[i]
+ let i += 2
+ endwhile
+ call assert_equal({}, d)
+ unlet d
+endfunc
+
+" Dictionary function
+func Test_dict_func()
+ let d = {}
+ func d.func(a) dict
+ return a:a . len(self.data)
+ endfunc
+ let d.data = [1,2,3]
+ call assert_equal('len: 3', d.func('len: '))
+ let x = d.func('again: ')
+ call assert_equal('again: 3', x)
+ let Fn = d.func
+ call assert_equal('xxx3', Fn('xxx'))
+endfunc
+
+func Test_dict_assign()
+ let d = {}
+ let d.1 = 1
+ let d._ = 2
+ call assert_equal({'1': 1, '_': 2}, d)
+
+ let lines =<< trim END
+ VAR d = {}
+ LET d.a = 1
+ LET d._ = 2
+ call assert_equal({'a': 1, '_': 2}, d)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ let n = 0
+ let n.key = 3
+ END
+ call CheckScriptFailure(lines, 'E1203: Dot can only be used on a dictionary: n.key = 3')
+ let lines =<< trim END
+ vim9script
+ var n = 0
+ n.key = 3
+ END
+ call CheckScriptFailure(lines, 'E1203: Dot can only be used on a dictionary: n.key = 3')
+ let lines =<< trim END
+ var n = 0
+ n.key = 3
+ END
+ call CheckDefFailure(lines, 'E1141:')
+endfunc
+
+" Function in script-local List or Dict
+func Test_script_local_dict_func()
+ let g:dict = {}
+ function g:dict.func() dict
+ return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf')
+ endfunc
+ let g:dict.foo = ['-', 2, 3]
+ call insert(g:dict.foo, function('strlen'))
+ call assert_equal('g:dict.func-4', g:dict.func())
+ unlet g:dict
+endfunc
+
+" Test removing items in a dictionary
+func Test_dict_func_remove()
+ let lines =<< trim END
+ VAR d = {1: 'a', 2: 'b', 3: 'c'}
+ call assert_equal('b', remove(d, 2))
+ call assert_equal({1: 'a', 3: 'c'}, d)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let lines =<< trim END
+ VAR d = {1: 'a', 3: 'c'}
+ call remove(d, 1, 2)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E118:')
+
+ let lines =<< trim END
+ VAR d = {1: 'a', 3: 'c'}
+ call remove(d, 'a')
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E716:')
+
+ let lines =<< trim END
+ let d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call CheckScriptFailure(lines, 'E730:')
+ let lines =<< trim END
+ vim9script
+ var d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call CheckScriptFailure(lines, 'E1174: String required for argument 2')
+ let lines =<< trim END
+ var d = {1: 'a', 3: 'c'}
+ call remove(d, [])
+ END
+ call CheckDefExecFailure(lines, 'E1013: Argument 2: type mismatch, expected string but got list<unknown>')
+endfunc
+
+" Nasty: remove func from Dict that's being called (works)
+func Test_dict_func_remove_in_use()
+ let d = {1:1}
+ func d.func(a)
+ return "a:" . a:a
+ endfunc
+ let expected = 'a:' . string(get(d, 'func'))
+ call assert_equal(expected, d.func(string(remove(d, 'func'))))
+
+ " similar, in a way it also works in Vim9
+ let lines =<< trim END
+ VAR d = {1: 1, 2: 'x'}
+ func GetArg(a)
+ return "a:" .. a:a
+ endfunc
+ LET d.func = function('GetArg')
+ VAR expected = 'a:' .. string(get(d, 'func'))
+ call assert_equal(expected, d.func(string(remove(d, 'func'))))
+ END
+ call CheckTransLegacySuccess(lines)
+ call CheckTransVim9Success(lines)
+endfunc
+
+func Test_dict_literal_keys()
+ call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
+
+ " why *{} cannot be used for a literal dictionary
+ let blue = 'blue'
+ call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
+endfunc
+
+" Nasty: deepcopy() dict that refers to itself (fails when noref used)
+func Test_dict_deepcopy()
+ let lines =<< trim END
+ VAR d = {1: 1, 2: '2'}
+ VAR l = [4, d, 6]
+ LET d[3] = l
+ VAR dc = deepcopy(d)
+ call deepcopy(d, 1)
+ END
+ call CheckLegacyAndVim9Failure(lines, 'E698:')
+
+ let lines =<< trim END
+ VAR d = {1: 1, 2: '2'}
+ VAR l = [4, d, 6]
+ LET d[3] = l
+ VAR l2 = [0, l, l, 3]
+ LET l[1] = l2
+ VAR l3 = deepcopy(l2)
+ call assert_true(l3[1] is l3[2])
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_fails("call deepcopy([1, 2], 2)", 'E1212:')
+endfunc
+
+" Locked variables
+func Test_list_locked_var()
+ " Not tested with :def function, local vars cannot be locked.
+ let lines =<< trim END
+ VAR expected = [
+ \ [['1000-000', 'ppppppF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1000-000', 'ppppppF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1100-100', 'ppFppFF'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1110-110', 'pFFpFFF'],
+ \ ['0010-010', 'pFppFpp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1111-111', 'FFFFFFF'],
+ \ ['0011-011', 'FFpFFpp'],
+ \ ['0000-000', 'ppppppp']]
+ \ ]
+ for depth in range(5)
+ for u in range(3)
+ VAR l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
+ exe "lockvar " .. depth .. " l"
+ if u == 1
+ exe "unlockvar l"
+ elseif u == 2
+ exe "unlockvar " .. depth .. " l"
+ endif
+ VAR ps = islocked("l") .. islocked("l[1]") .. islocked("l[1][1]") .. islocked("l[1][1][0]") .. '-' .. islocked("l[2]") .. islocked("l[2]['6']") .. islocked("l[2]['6'][7]")
+ call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
+ LET ps = ''
+ try
+ LET l[1][1][0] = 99
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[1][1] = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[1] = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2]['6'][7] = 99
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2][6] = {99: 99}
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l[2] = {99: 99}
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ try
+ LET l = [99]
+ LET ps ..= 'p'
+ catch
+ LET ps ..= 'F'
+ endtry
+ call assert_equal(expected[depth][u][1], ps, 'depth: ' .. depth)
+ unlock! l
+ endfor
+ endfor
+ END
+ call CheckTransLegacySuccess(lines)
+ call CheckTransVim9Success(lines)
+
+ call assert_fails("let x=islocked('a b')", 'E488:')
+ let mylist = [1, 2, 3]
+ call assert_fails("let x = islocked('mylist[1:2]')", 'E786:')
+ let mydict = {'k' : 'v'}
+ call assert_fails("let x = islocked('mydict.a')", 'E716:')
+endfunc
+
+" Unletting locked variables
+func Test_list_locked_var_unlet()
+ " Not tested with Vim9: script and local variables cannot be unlocked
+ let expected = [
+ \ [['1000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1000-000', 'ppFppFp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1100-100', 'pFFpFFp'],
+ \ ['0000-000', 'ppppppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1110-110', 'FFFFFFp'],
+ \ ['0010-010', 'FppFppp'],
+ \ ['0000-000', 'ppppppp']],
+ \ [['1111-111', 'FFFFFFp'],
+ \ ['0011-011', 'FppFppp'],
+ \ ['0000-000', 'ppppppp']]
+ \ ]
+
+ for depth in range(5)
+ for u in range(3)
+ unlet! l
+ let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}]
+ exe "lockvar " . depth . " l"
+ if u == 1
+ exe "unlockvar l"
+ elseif u == 2
+ exe "unlockvar " . depth . " l"
+ endif
+ let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]")
+ call assert_equal(expected[depth][u][0], ps, 'depth: ' .. depth)
+ let ps = ''
+ try
+ unlet l[2]['6'][7]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[2][6]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[2]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1][1][0]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1][1]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l[1]
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ try
+ unlet l
+ let ps .= 'p'
+ catch
+ let ps .= 'F'
+ endtry
+ call assert_equal(expected[depth][u][1], ps)
+ endfor
+ endfor
+
+ " Deleting a list range with locked items works, but changing the items
+ " fails.
+ let l = [1, 2, 3, 4]
+ lockvar l[1:2]
+ call assert_fails('let l[1:2] = [8, 9]', 'E741:')
+ unlet l[1:2]
+ call assert_equal([1, 4], l)
+ unlet l
+endfunc
+
+" Locked variables and :unlet or list / dict functions
+
+" No :unlet after lock on dict:
+func Test_dict_lock_unlet()
+ let d = {'a': 99, 'b': 100}
+ lockvar 1 d
+ call assert_fails('unlet d.a', 'E741:')
+endfunc
+
+" unlet after lock on dict item
+func Test_dict_item_lock_unlet()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar d.a
+ unlet d.a
+ call assert_equal({'b': 100}, d)
+ END
+ " TODO: make this work in a :def function
+ "call CheckLegacyAndVim9Success(lines)
+ call CheckTransLegacySuccess(lines)
+ call CheckTransVim9Success(lines)
+endfunc
+
+" filter() after lock on dict item
+func Test_dict_lock_filter()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar d.a
+ call filter(d, 'v:key != "a"')
+ call assert_equal({'b': 100}, d)
+ END
+ " TODO: make this work in a :def function
+ "call CheckLegacyAndVim9Success(lines)
+ call CheckTransLegacySuccess(lines)
+ call CheckTransVim9Success(lines)
+endfunc
+
+" map() after lock on dict
+func Test_dict_lock_map()
+ let lines =<< trim END
+ VAR d = {'a': 99, 'b': 100}
+ lockvar 1 d
+ call map(d, 'v:val + 200')
+ call assert_equal({'a': 299, 'b': 300}, d)
+ END
+ " This won't work in a :def function
+ call CheckTransLegacySuccess(lines)
+ call CheckTransVim9Success(lines)
+endfunc
+
+" No extend() after lock on dict item
+func Test_dict_lock_extend()
+ let d = {'a': 99, 'b': 100}
+ lockvar d.a
+ call assert_fails("call extend(d, {'a' : 123})", 'E741:')
+ call assert_equal({'a': 99, 'b': 100}, d)
+endfunc
+
+" Cannot use += with a locked dict
+func Test_dict_lock_operator()
+ let d = {}
+ lockvar d
+ call assert_fails("let d += {'k' : 10}", 'E741:')
+ unlockvar d
+endfunc
+
+" No remove() of write-protected scope-level variable
+func Tfunc1(this_is_a_long_parameter_name)
+ call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742:')
+endfunc
+func Test_dict_scope_var_remove()
+ 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 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 Tfunc2('testval')
+endfunc
+
+" No :unlet of variable in locked scope
+func Test_lock_var_unlet()
+ let b:testvar = 123
+ lockvar 1 b:
+ call assert_fails('unlet b:testvar', 'E741:')
+ unlockvar 1 b:
+ unlet! b:testvar
+endfunc
+
+" No :let += of locked list variable
+func Test_let_lock_list()
+ let l = ['a', 'b', 3]
+ lockvar 1 l
+ call assert_fails("let l += ['x']", 'E741:')
+ call assert_equal(['a', 'b', 3], l)
+
+ unlet l
+ let l = [1, 2, 3, 4]
+ lockvar! l
+ call assert_equal([1, 2, 3, 4], l)
+ unlockvar l[1]
+ call assert_fails('unlet l[0:1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ call assert_fails('unlet l[1:2]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ unlockvar l[1]
+ call assert_fails('let l[0:1] = [0, 1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ call assert_fails('let l[1:2] = [0, 1]', 'E741:')
+ call assert_equal([1, 2, 3, 4], l)
+ unlet l
+endfunc
+
+" Locking part of the list
+func Test_let_lock_list_items()
+ let l = [1, 2, 3, 4]
+ lockvar l[2:]
+ call assert_equal(0, islocked('l[0]'))
+ call assert_equal(1, islocked('l[2]'))
+ call assert_equal(1, islocked('l[3]'))
+ call assert_fails('let l[2] = 10', 'E741:')
+ call assert_fails('let l[3] = 20', 'E741:')
+ unlet l
+endfunc
+
+" lockvar/islocked() triggering script autoloading
+func Test_lockvar_script_autoload()
+ let old_rtp = &rtp
+ set rtp+=./sautest
+ lockvar g:footest#x
+ unlockvar g:footest#x
+ call assert_equal(-1, 'g:footest#x'->islocked())
+ call assert_equal(0, exists('g:footest#x'))
+ call assert_equal(1, g:footest#x)
+ let &rtp = old_rtp
+endfunc
+
+" a:000 function argument test
+func s:arg_list_test(...)
+ call assert_fails('let a:000 = [1, 2]', 'E46:')
+ call assert_fails('let a:000[0] = 9', 'E742:')
+ call assert_fails('let a:000[2] = [9, 10]', 'E742:')
+ call assert_fails('let a:000[3] = {9 : 10}', 'E742:')
+
+ " now the tests that should pass
+ let a:000[2][1] = 9
+ call extend(a:000[2], [5, 6])
+ let a:000[3][5] = 8
+ let a:000[3]['a'] = 12
+ call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000)
+endfunc
+
+func Test_func_arg_list()
+ call s:arg_list_test(1, 2, [3, 4], {5: 6})
+endfunc
+
+" Tests for reverse(), sort(), uniq()
+func Test_reverse_sort_uniq()
+ let lines =<< trim END
+ VAR l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5]
+ call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l)))
+ call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l))
+ call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l)))
+ if has('float')
+ call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l))
+ call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l)))
+ call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l))))
+ call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l)))
+
+ LET l = [7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four']
+ call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n'))
+
+ LET l = [7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []]
+ call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
+ call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i'))
+ call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l)))
+ endif
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_fails('call reverse({})', 'E1252:')
+ call assert_fails('call uniq([1, 2], {x, y -> []})', 'E745:')
+ call assert_fails("call sort([1, 2], function('min'), 1)", "E1206:")
+ call assert_fails("call sort([1, 2], function('invalid_func'))", "E700:")
+ call assert_fails("call sort([1, 2], function('min'))", "E118:")
+
+ let lines =<< trim END
+ call sort(['a', 'b'], 0)
+ END
+ call CheckDefAndScriptFailure(lines, 'E1256: String or function required for argument 2')
+
+ let lines =<< trim END
+ call sort(['a', 'b'], 1)
+ END
+ call CheckDefAndScriptFailure(lines, 'E1256: String or function required for argument 2')
+endfunc
+
+" reduce a list, blob or string
+func Test_reduce()
+ let lines =<< trim END
+ call assert_equal(1, reduce([], LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(10, reduce([1, 3, 5], LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], LSTART acc, val LMIDDLE 2 * acc + val LEND, 1))
+ call assert_equal('a x y z', ['x', 'y', 'z']->reduce(LSTART acc, val LMIDDLE acc .. ' ' .. val LEND, 'a'))
+ call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+ VAR l = ['x', 'y', 'z']
+ call assert_equal(42, reduce(l, function('get'), {'x': {'y': {'z': 42 } } }))
+ call assert_equal(['x', 'y', 'z'], l)
+
+ call assert_equal(1, reduce([1], LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal('x y z', reduce(['x', 'y', 'z'], LSTART acc, val LMIDDLE acc .. ' ' .. val LEND))
+ call assert_equal(120, range(1, 5)->reduce(LSTART acc, val LMIDDLE acc * val LEND))
+
+ call assert_equal(1, reduce(0z, LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, LSTART acc, val LMIDDLE acc + val LEND, 1))
+ call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce(LSTART acc, val LMIDDLE 2 * acc + val LEND, 1))
+
+ call assert_equal(0xff, reduce(0zff, LSTART acc, val LMIDDLE acc + val LEND))
+ call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, LSTART acc, val LMIDDLE 2 * acc + val LEND))
+
+ call assert_equal('x,y,z', 'xyz'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('', ''->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, ''))
+ call assert_equal('あ,い,う,え,お,😊,💕', 'あいうえお😊💕'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('😊,あ,い,う,え,お,💕', 'あいうえお💕'->reduce(LSTART acc, val LMIDDLE acc .. ',' .. val LEND, '😊'))
+ call assert_equal('ऊ,ॠ,ॡ', reduce('ऊॠॡ', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('c,à,t', reduce('càt', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal('Å,s,t,r,ö,m', reduce('Åström', LSTART acc, val LMIDDLE acc .. ',' .. val LEND))
+ call assert_equal(',a,b,c', reduce('abc', LSTART acc, val LMIDDLE acc .. ',' .. val LEND, v:_null_string))
+
+ call assert_equal(0x7d, reduce([0x30, 0x25, 0x08, 0x61], 'or'))
+ call assert_equal(0x7d, reduce(0z30250861, 'or'))
+ call assert_equal('β', reduce('ββββ', 'matchstr'))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_equal({'x': 1, 'y': 1, 'z': 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+ " vim9 assert_equal({'x': 1, 'y': 1, 'z': 1 }, ['x', 'y', 'z']->reduce((acc, val) => extend(acc, {[val]: 1 }), {}))
+
+ call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+ call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+ call assert_fails("call reduce(v:_null_blob, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+ call assert_fails("call reduce('', { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
+ call assert_fails("call reduce(v:_null_string, { acc, val -> acc + val })", 'E998: Reduce of an empty String with no initial value')
+
+ call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E1098:')
+ call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E1098:')
+ call assert_fails("call reduce([1, 2], 'Xdoes_not_exist')", 'E117:')
+ call assert_fails("echo reduce(0z01, { acc, val -> 2 * acc + val }, '')", 'E1210:')
+
+ " call assert_fails("vim9 reduce(0, (acc, val) => (acc .. val), '')", 'E1252:')
+ " call assert_fails("vim9 reduce({}, (acc, val) => (acc .. val), '')", 'E1252:')
+ " call assert_fails("vim9 reduce(0.1, (acc, val) => (acc .. val), '')", 'E1252:')
+ " call assert_fails("vim9 reduce(function('tr'), (acc, val) => (acc .. val), '')", 'E1252:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, {})", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, 0.1)", 'E1174:')
+ call assert_fails("call reduce('', { acc, val -> acc + val }, function('tr'))", 'E1174:')
+ call assert_fails("call reduce('abc', { a, v -> a10}, '')", 'E121:')
+ call assert_fails("call reduce(0z0102, { a, v -> a10}, 1)", 'E121:')
+ call assert_fails("call reduce([1, 2], { a, v -> a10}, '')", 'E121:')
+
+ let g:lut = [1, 2, 3, 4]
+ func EvilRemove()
+ call remove(g:lut, 1)
+ return 1
+ endfunc
+ call assert_fails("call reduce(g:lut, { acc, val -> EvilRemove() }, 1)", 'E742:')
+ unlet g:lut
+ delfunc EvilRemove
+
+ call assert_equal(42, reduce(v:_null_list, function('add'), 42))
+ call assert_equal(42, reduce(v:_null_blob, function('add'), 42))
+
+ " should not crash
+ " Nvim doesn't have null functions
+ " call assert_fails('echo reduce([1], test_null_function())', 'E1132:')
+ " Nvim doesn't have null partials
+ " call assert_fails('echo reduce([1], test_null_partial())', 'E1132:')
+endfunc
+
+" splitting a string to a List using split()
+func Test_str_split()
+ let lines =<< trim END
+ call assert_equal(['aa', 'bb'], split(' aa bb '))
+ call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0))
+ call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1))
+ call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1))
+ call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0))
+ call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1))
+ call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1))
+ call assert_equal(['a', 'b', 'c'], split('abc', '\zs'))
+ call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1))
+ call assert_equal(['abc'], split('abc', '\\%('))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ call assert_fails("call split('abc', [])", 'E730:')
+ call assert_fails("call split('abc', 'b', [])", 'E745:')
+endfunc
+
+" compare recursively linked list and dict
+func Test_listdict_compare()
+ let lines =<< trim END
+ VAR l = [1, 2, 3, '4']
+ VAR d = {'1': 1, '2': l, '3': 3}
+ LET l[1] = d
+ call assert_true(l == l)
+ call assert_true(d == d)
+ call assert_false(l != deepcopy(l))
+ call assert_false(d != deepcopy(d))
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ " comparison errors
+ call assert_fails('echo [1, 2] =~ {}', 'E691:')
+ call assert_fails('echo [1, 2] =~ [1, 2]', 'E692:')
+ call assert_fails('echo {} =~ 5', 'E735:')
+ call assert_fails('echo {} =~ {}', 'E736:')
+endfunc
+
+ " compare complex recursively linked list and dict
+func Test_listdict_compare_complex()
+ let lines =<< trim END
+ VAR l = []
+ call add(l, l)
+ VAR dict4 = {"l": l}
+ call add(dict4.l, dict4)
+ VAR lcopy = deepcopy(l)
+ VAR dict4copy = deepcopy(dict4)
+ call assert_true(l == lcopy)
+ call assert_true(dict4 == dict4copy)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+" Test for extending lists and dictionaries
+func Test_listdict_extend()
+ " Test extend() with lists
+
+ " Pass the same List to extend()
+ let lines =<< trim END
+ VAR l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 1, 2, 3], extend(l, l))
+ call assert_equal([1, 2, 3, 1, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4, 5, 6], extend(l, [4, 5, 6]))
+ call assert_equal([1, 2, 3, 4, 5, 6], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 0)
+ call assert_equal([4, 5, 6, 1, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 1)
+ call assert_equal([1, 4, 5, 6, 2, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], 3)
+ call assert_equal([1, 2, 3, 4, 5, 6], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], -1)
+ call assert_equal([1, 2, 4, 5, 6, 3], l)
+
+ LET l = [1, 2, 3]
+ call extend(l, [4, 5, 6], -3)
+ call assert_equal([4, 5, 6, 1, 2, 3], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let l = [1, 2, 3]
+ call assert_fails("call extend(l, [4, 5, 6], 4)", 'E684:')
+ call assert_fails("call extend(l, [4, 5, 6], -4)", 'E684:')
+ if has('float')
+ call assert_fails("call extend(l, [4, 5, 6], 1.2)", 'E805:')
+ endif
+
+ " Test extend() with dictionaries.
+
+ " Pass the same Dict to extend()
+ let lines =<< trim END
+ VAR d = {'a': {'b': 'B'}, 'x': 9}
+ call extend(d, d)
+ call assert_equal({'a': {'b': 'B'}, 'x': 9}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, extend(d, {'b': 0, 'c': 'C'}))
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call extend(d, {'a': 'A', 'b': 0, 'c': 'C'}, "force")
+ call assert_equal({'a': 'A', 'b': 0, 'c': 'C'}, d)
+
+ LET d = {'a': 'A', 'b': 9}
+ call extend(d, {'b': 0, 'c': 'C'}, "keep")
+ call assert_equal({'a': 'A', 'b': 9, 'c': 'C'}, d)
+ END
+ call CheckLegacyAndVim9Success(lines)
+
+ let d = {'a': 'A', 'b': 'B'}
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'error')", 'E737:')
+ call assert_fails("call extend(d, {'b': 0}, [])", 'E730:')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 'xxx')", 'E475:')
+ if has('float')
+ call assert_fails("call extend(d, {'b': 0, 'c':'C'}, 1.2)", 'E475:')
+ endif
+ call assert_equal({'a': 'A', 'b': 'B'}, d)
+
+ call assert_fails("call extend([1, 2], 1)", 'E712:')
+ call assert_fails("call extend([1, 2], {})", 'E712:')
+
+ " Extend g: dictionary with an invalid variable name
+ call assert_fails("call extend(g:, {'-!' : 10})", 'E461:')
+
+ " Extend a list with itself.
+ let lines =<< trim END
+ VAR l = [1, 5, 7]
+ call extend(l, l, 0)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 1)
+ call assert_equal([1, 1, 5, 7, 5, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 2)
+ call assert_equal([1, 5, 1, 5, 7, 7], l)
+ LET l = [1, 5, 7]
+ call extend(l, l, 3)
+ call assert_equal([1, 5, 7, 1, 5, 7], l)
+ END
+ call CheckLegacyAndVim9Success(lines)
+endfunc
+
+func Test_listdict_extendnew()
+ " Test extendnew() with lists
+ let l = [1, 2, 3]
+ call assert_equal([1, 2, 3, 4, 5], extendnew(l, [4, 5]))
+ call assert_equal([1, 2, 3], l)
+ lockvar l
+ call assert_equal([1, 2, 3, 4, 5], extendnew(l, [4, 5]))
+
+ " Test extendnew() with dictionaries.
+ let d = {'a': {'b': 'B'}}
+ call assert_equal({'a': {'b': 'B'}, 'c': 'cc'}, extendnew(d, {'c': 'cc'}))
+ call assert_equal({'a': {'b': 'B'}}, d)
+ lockvar d
+ call assert_equal({'a': {'b': 'B'}, 'c': 'cc'}, extendnew(d, {'c': 'cc'}))
+endfunc
+
+func s:check_scope_dict(x, fixed)
+ func s:gen_cmd(cmd, x)
+ return substitute(a:cmd, '\<x\ze:', a:x, 'g')
+ endfunc
+
+ let cmd = s:gen_cmd('let x:foo = 1', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E461')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(1, x:foo)', a:x)
+ endif
+
+ let cmd = s:gen_cmd('let x:["bar"] = 2', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E461')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(2, x:bar)', a:x)
+ endif
+
+ let cmd = s:gen_cmd('call extend(x:, {"baz": 3})', a:x)
+ if a:fixed
+ call assert_fails(cmd, 'E742')
+ else
+ exe cmd
+ exe s:gen_cmd('call assert_equal(3, x:baz)', a:x)
+ endif
+
+ if a:fixed
+ if a:x ==# 'a'
+ call assert_fails('unlet a:x', 'E795')
+ call assert_fails('call remove(a:, "x")', 'E742')
+ elseif a:x ==# 'v'
+ call assert_fails('unlet v:count', 'E795')
+ call assert_fails('call remove(v:, "count")', 'E742')
+ endif
+ else
+ exe s:gen_cmd('unlet x:foo', a:x)
+ exe s:gen_cmd('unlet x:bar', a:x)
+ exe s:gen_cmd('call remove(x:, "baz")', a:x)
+ endif
+
+ delfunc s:gen_cmd
+endfunc
+
+func Test_scope_dict()
+ " Test for g:
+ call s:check_scope_dict('g', v:false)
+
+ " Test for s:
+ call s:check_scope_dict('s', v:false)
+
+ " Test for l:
+ call s:check_scope_dict('l', v:false)
+
+ " Test for a:
+ call s:check_scope_dict('a', v:true)
+
+ " Test for b:
+ call s:check_scope_dict('b', v:false)
+
+ " Test for w:
+ call s:check_scope_dict('w', v:false)
+
+ " Test for t:
+ call s:check_scope_dict('t', v:false)
+
+ " Test for v:
+ call s:check_scope_dict('v', v:true)
+endfunc
+
+" Test for deep nesting of lists (> 100)
+func Test_deep_nested_list()
+ let deep_list = []
+ let l = deep_list
+ for i in range(102)
+ let newlist = []
+ call add(l, newlist)
+ let l = newlist
+ endfor
+ call add(l, 102)
+
+ call assert_fails('let m = deepcopy(deep_list)', 'E698:')
+ call assert_fails('lockvar 110 deep_list', 'E743:')
+ call assert_fails('unlockvar 110 deep_list', 'E743:')
+ " Nvim implements :echo very differently
+ " call assert_fails('let x = execute("echo deep_list")', 'E724:')
+ call test_garbagecollect_now()
+ unlet deep_list
+endfunc
+
+" Test for deep nesting of dicts (> 100)
+func Test_deep_nested_dict()
+ let deep_dict = {}
+ let d = deep_dict
+ for i in range(102)
+ let newdict = {}
+ let d.k = newdict
+ let d = newdict
+ endfor
+ let d.k = 'v'
+
+ call assert_fails('let m = deepcopy(deep_dict)', 'E698:')
+ call assert_fails('lockvar 110 deep_dict', 'E743:')
+ call assert_fails('unlockvar 110 deep_dict', 'E743:')
+ " Nvim implements :echo very differently
+ " call assert_fails('let x = execute("echo deep_dict")', 'E724:')
+ call test_garbagecollect_now()
+ unlet deep_dict
+endfunc
+
+" List and dict indexing tests
+func Test_listdict_index()
+ call CheckLegacyAndVim9Failure(['echo function("min")[0]'], 'E695:')
+ call CheckLegacyAndVim9Failure(['echo v:true[0]'], 'E909:')
+ call CheckLegacyAndVim9Failure(['echo v:null[0]'], 'E909:')
+ call CheckLegacyAndVim9Failure(['VAR d = {"k": 10}', 'echo d.'], ['E15:', 'E1127:', 'E15:'])
+ call CheckLegacyAndVim9Failure(['VAR d = {"k": 10}', 'echo d[1 : 2]'], 'E719:')
+
+ call assert_fails("let v = [4, 6][{-> 1}]", 'E729:')
+ call CheckDefAndScriptFailure(['var v = [4, 6][() => 1]'], ['E1012', 'E703:'])
+
+ call CheckLegacyAndVim9Failure(['VAR v = range(5)[2 : []]'], ['E730:', 'E1012:', 'E730:'])
+
+ call assert_fails("let v = range(5)[2:{-> 2}(]", ['E15:', 'E116:'])
+ call CheckDefAndScriptFailure(['var v = range(5)[2 : () => 2(]'], 'E15:')
+
+ call CheckLegacyAndVim9Failure(['VAR v = range(5)[2 : 3'], ['E111:', 'E1097:', 'E111:'])
+ call CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, 10)'], 'E684:')
+ call CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, -10)'], 'E684:')
+ call CheckLegacyAndVim9Failure(['VAR l = insert([1, 2, 3], 4, [])'], ['E745:', 'E1013:', 'E1210:'])
+
+ call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[i] = 3'], ['E121:', 'E1001:', 'E121:'])
+ call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[1.1] = 4'], ['E805:', 'E1012:', 'E805:'])
+ call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: i] = [4, 5]'], ['E121:', 'E1001:', 'E121:'])
+ call CheckLegacyAndVim9Failure(['VAR l = [1, 2, 3]', 'LET l[: 3.2] = [4, 5]'], ['E805:', 'E1012:', 'E805:'])
+ " call CheckLegacyAndVim9Failure(['VAR t = test_unknown()', 'echo t[0]'], 'E685:')
+endfunc
+
+" Test for a null list
+func Test_null_list()
+ let l = v:_null_list
+ call assert_equal('', join(l))
+ call assert_equal(0, len(l))
+ call assert_equal(1, empty(l))
+ call assert_fails('let s = join([1, 2], [])', 'E730:')
+ call assert_equal([], split(v:_null_string))
+ call assert_equal([], l[:2])
+ call assert_true([] == l)
+ call assert_equal('[]', string(l))
+ " call assert_equal(0, sort(l))
+ " call assert_equal(0, sort(l))
+ " call assert_equal(0, uniq(l))
+ let k = [] + l
+ call assert_equal([], k)
+ let k = l + []
+ call assert_equal([], k)
+ call assert_equal(0, len(copy(l)))
+ call assert_equal(0, count(l, 5))
+ call assert_equal([], deepcopy(l))
+ call assert_equal(5, get(l, 2, 5))
+ call assert_equal(-1, index(l, 2, 5))
+ " call assert_equal(0, insert(l, 2, -1))
+ call assert_equal(0, min(l))
+ call assert_equal(0, max(l))
+ " call assert_equal(0, remove(l, 0, 2))
+ call assert_equal([], repeat(l, 2))
+ " call assert_equal(0, reverse(l))
+ " call assert_equal(0, sort(l))
+ call assert_equal('[]', string(l))
+ " call assert_equal(0, extend(l, l, 0))
+ lockvar l
+ call assert_equal(1, islocked('l'))
+ unlockvar l
+endfunc
+
+" Test for a null dict
+func Test_null_dict()
+ call assert_equal(v:_null_dict, v:_null_dict)
+ let d = v:_null_dict
+ call assert_equal({}, d)
+ call assert_equal(0, len(d))
+ call assert_equal(1, empty(d))
+ call assert_equal([], items(d))
+ call assert_equal([], keys(d))
+ call assert_equal([], values(d))
+ call assert_false(has_key(d, 'k'))
+ call assert_equal('{}', string(d))
+ call assert_fails('let x = d[10]')
+ call assert_equal({}, {})
+ call assert_equal(0, len(copy(d)))
+ call assert_equal(0, count(d, 'k'))
+ call assert_equal({}, deepcopy(d))
+ call assert_equal(20, get(d, 'k', 20))
+ call assert_equal(0, min(d))
+ call assert_equal(0, max(d))
+ call assert_equal(0, remove(d, 'k'))
+ call assert_equal('{}', string(d))
+ " call assert_equal(0, extend(d, d, 0))
+ lockvar d
+ call assert_equal(1, islocked('d'))
+ unlockvar d
+endfunc
+
+" Test for the indexof() function
+func Test_indexof()
+ let l = [#{color: 'red'}, #{color: 'blue'}, #{color: 'green'}]
+ call assert_equal(0, indexof(l, {i, v -> v.color == 'red'}))
+ call assert_equal(2, indexof(l, {i, v -> v.color == 'green'}))
+ call assert_equal(-1, indexof(l, {i, v -> v.color == 'grey'}))
+ call assert_equal(1, indexof(l, "v:val.color == 'blue'"))
+ call assert_equal(-1, indexof(l, "v:val.color == 'cyan'"))
+
+ let l = [#{n: 10}, #{n: 10}, #{n: 20}]
+ call assert_equal(0, indexof(l, "v:val.n == 10", #{startidx: 0}))
+ call assert_equal(1, indexof(l, "v:val.n == 10", #{startidx: -2}))
+ call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: 4}))
+ call assert_equal(-1, indexof(l, "v:val.n == 10", #{startidx: -4}))
+ call assert_equal(0, indexof(l, "v:val.n == 10", v:_null_dict))
+
+ let s = ["a", "b", "c"]
+ call assert_equal(2, indexof(s, {_, v -> v == 'c'}))
+ call assert_equal(-1, indexof(s, {_, v -> v == 'd'}))
+ call assert_equal(-1, indexof(s, {_, v -> "v == 'd'"}))
+
+ call assert_equal(-1, indexof([], {i, v -> v == 'a'}))
+ call assert_equal(-1, indexof([1, 2, 3], {_, v -> "v == 2"}))
+ call assert_equal(-1, indexof(v:_null_list, {i, v -> v == 'a'}))
+ call assert_equal(-1, indexof(l, v:_null_string))
+ " Nvim doesn't have null functions
+ " call assert_equal(-1, indexof(l, test_null_function()))
+
+ " failure cases
+ call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:')
+ call assert_fails('let i = indexof(l, "color == ''cyan''")', 'E121:')
+ call assert_fails('let i = indexof(l, {})', 'E1256:')
+ call assert_fails('let i = indexof({}, "v:val == 2")', 'E1226:')
+ call assert_fails('let i = indexof([], "v:val == 2", [])', 'E1206:')
+
+ func TestIdx(k, v)
+ return a:v.n == 20
+ endfunc
+ call assert_equal(2, indexof(l, function("TestIdx")))
+ delfunc TestIdx
+ func TestIdx(k, v)
+ return {}
+ endfunc
+ call assert_fails('let i = indexof(l, function("TestIdx"))', 'E728:')
+ delfunc TestIdx
+ func TestIdx(k, v)
+ throw "IdxError"
+ endfunc
+ call assert_fails('let i = indexof(l, function("TestIdx"))', 'E605:')
+ delfunc TestIdx
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listlbr.vim b/test/old/testdir/test_listlbr.vim
index 1cbdba5d76..6dea94fbf1 100644
--- a/src/nvim/testdir/test_listlbr.vim
+++ b/test/old/testdir/test_listlbr.vim
@@ -14,7 +14,7 @@ function s:screen_lines(lnum, width) abort
endfunction
func s:compare_lines(expect, actual)
- call assert_equal(join(a:expect, "\n"), join(a:actual, "\n"))
+ call assert_equal(a:expect, a:actual)
endfunc
function s:test_windows(...)
@@ -73,6 +73,30 @@ func Test_linebreak_with_nolist()
call s:close_windows()
endfunc
+func Test_linebreak_with_list_and_number()
+ call s:test_windows('setl list listchars+=tab:>-')
+ call setline(1, ["abcdefg\thijklmnopqrstu", "v"])
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect_nonumber = [
+\ "abcdefg>------------",
+\ "hijklmnopqrstu$ ",
+\ "v$ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect_nonumber, lines)
+
+ setl number
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect_number = [
+\ " 1 abcdefg>--------",
+\ " hijklmnopqrstu$ ",
+\ " 2 v$ ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect_number, lines)
+ call s:close_windows()
+endfunc
+
func Test_should_break()
call s:test_windows('setl sbr=+ nolist')
call setline(1, "1\t" . repeat('a', winwidth(0)-2))
@@ -199,7 +223,7 @@ func Test_virtual_block_and_vbA()
exe "norm! $3B\<C-v>eAx\<Esc>"
let lines = s:screen_lines([1, 10], winwidth(0))
let expect = [
-\ "foobar foobar ",
+\ "<<<bar foobar ",
\ "foobar foobar ",
\ "foobar foobar ",
\ "foobar foobar ",
@@ -306,4 +330,62 @@ func Test_list_with_tab_and_skipping_first_chars()
\ ]
call s:compare_lines(expect, lines)
call s:close_windows()
-endfu
+endfunc
+
+func Test_ctrl_char_on_wrap_column()
+ call s:test_windows("setl nolbr wrap sbr=")
+ call setline(1, 'aaa' .. repeat("\<C-A>", 150) .. 'bbb')
+ call cursor(1,1)
+ norm! $
+ redraw!
+ let expect=[
+\ '<<<^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^A^A^A^A^A^A^A^A^A^',
+\ 'A^Abbb ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
+ setl sbr=!!
+ redraw!
+ let expect=[
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^A^A^A^',
+\ '!!A^A^A^A^A^A^Abbb ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
+ call s:close_windows()
+endfunc
+
+func Test_linebreak_no_break_after_whitespace_only()
+ call s:test_windows('setl ts=4 linebreak wrap')
+ call setline(1, "\tabcdefghijklmnopqrstuvwxyz" ..
+ \ "abcdefghijklmnopqrstuvwxyz")
+ let lines = s:screen_lines([1, 4], winwidth(0))
+ let expect = [
+\ " abcdefghijklmnop",
+\ "qrstuvwxyzabcdefghij",
+\ "klmnopqrstuvwxyz ",
+\ "~ ",
+\ ]
+ call s:compare_lines(expect, lines)
+ call s:close_windows()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/test/old/testdir/test_listlbr_utf8.vim
index df1ed78119..1bbbd2d2ae 100644
--- a/src/nvim/testdir/test_listlbr_utf8.vim
+++ b/test/old/testdir/test_listlbr_utf8.vim
@@ -249,7 +249,6 @@ endfunc
func Test_chinese_char_on_wrap_column()
call s:test_windows("setl nolbr wrap sbr=")
- syntax off
call setline(1, [
\ 'aaaaaaaaaaaaaaaaaaa中'.
\ 'aaaaaaaaaaaaaaaaa中'.
@@ -266,7 +265,7 @@ func Test_chinese_char_on_wrap_column()
norm! $
redraw!
let expect=[
-\ '中aaaaaaaaaaaaaaaaa>',
+\ '<<<aaaaaaaaaaaaaaaa>',
\ '中aaaaaaaaaaaaaaaaa>',
\ '中aaaaaaaaaaaaaaaaa>',
\ '中aaaaaaaaaaaaaaaaa>',
@@ -278,5 +277,85 @@ func Test_chinese_char_on_wrap_column()
\ '中hello ']
let lines = s:screen_lines([1, 10], winwidth(0))
call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
call s:close_windows()
-endfu
+endfunc
+
+func Test_chinese_char_on_wrap_column_sbr()
+ call s:test_windows("setl nolbr wrap sbr=!!!")
+ call setline(1, [
+\ 'aaaaaaaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'aaaaaaaaaaaaaa中'.
+\ 'hello'])
+ call cursor(1,1)
+ norm! $
+ redraw!
+ let expect=[
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中aaaaaaaaaaaaaa>',
+\ '!!!中hello ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
+ call s:close_windows()
+endfunc
+
+func Test_unprintable_char_on_wrap_column()
+ call s:test_windows("setl nolbr wrap sbr=")
+ call setline(1, 'aaa' .. repeat("\uFEFF", 50) .. 'bbb')
+ call cursor(1,1)
+ norm! $
+ redraw!
+ let expect=[
+\ '<<<<feff><feff><feff',
+\ '><feff><feff><feff><',
+\ 'feff><feff><feff><fe',
+\ 'ff><feff><feff><feff',
+\ '><feff><feff><feff><',
+\ 'feff><feff><feff><fe',
+\ 'ff><feff><feff><feff',
+\ '><feff><feff><feff><',
+\ 'feff><feff><feff><fe',
+\ 'ff>bbb ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
+ setl sbr=!!
+ redraw!
+ let expect=[
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff><feff',
+\ '!!><feff><feff>bbb ']
+ let lines = s:screen_lines([1, 10], winwidth(0))
+ call s:compare_lines(expect, lines)
+ call assert_equal(len(expect), winline())
+ call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol())
+ call s:close_windows()
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_makeencoding.py b/test/old/testdir/test_makeencoding.py
index f6dc0f8d1c..f6dc0f8d1c 100644
--- a/src/nvim/testdir/test_makeencoding.py
+++ b/test/old/testdir/test_makeencoding.py
diff --git a/src/nvim/testdir/test_makeencoding.vim b/test/old/testdir/test_makeencoding.vim
index e297bdc228..0a49fba755 100644
--- a/src/nvim/testdir/test_makeencoding.vim
+++ b/test/old/testdir/test_makeencoding.vim
@@ -1,7 +1,9 @@
" Tests for 'makeencoding'.
source shared.vim
+source check.vim
+CheckFeature quickfix
let s:python = PythonProg()
if s:python == ''
throw 'Skipped: python program missing'
diff --git a/test/old/testdir/test_map_functions.vim b/test/old/testdir/test_map_functions.vim
new file mode 100644
index 0000000000..2aeb470a0d
--- /dev/null
+++ b/test/old/testdir/test_map_functions.vim
@@ -0,0 +1,618 @@
+" Tests for maparg(), mapcheck(), mapset(), maplist()
+" Also test utf8 map with a 0x80 byte.
+
+source shared.vim
+
+func s:SID()
+ return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_SID$'))
+endfunc
+
+func Test_maparg()
+ new
+ set cpo-=<
+ set encoding=utf8
+ " Test maparg() with a string result
+ let sid = s:SID()
+ let lnum = expand('<sflnum>')
+ map foo<C-V> is<F4>foo
+ vnoremap <script> <buffer> <expr> <silent> bar isbar
+ call assert_equal("is<F4>foo", maparg('foo<C-V>'))
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>',
+ \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
+ \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1,
+ \ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0, 'mode_bits': 0x47},
+ \ maparg('foo<C-V>', '', 0, 1))
+ call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
+ \ 'lhsraw': 'bar', 'mode': 'v',
+ \ 'nowait': 0, 'expr': 1, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 2,
+ \ 'rhs': 'isbar', 'buffer': 1, 'abbr': 0, 'mode_bits': 0x42},
+ \ 'bar'->maparg('', 0, 1))
+ let lnum = expand('<sflnum>')
+ map <buffer> <nowait> foo bar
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo',
+ \ 'lhsraw': 'foo', 'mode': ' ',
+ \ 'nowait': 1, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'bar',
+ \ 'buffer': 1, 'abbr': 0, 'mode_bits': 0x47},
+ \ maparg('foo', '', 0, 1))
+ let lnum = expand('<sflnum>')
+ tmap baz foo
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'baz',
+ \ 'lhsraw': 'baz', 'mode': 't',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'foo',
+ \ 'buffer': 0, 'abbr': 0, 'mode_bits': 0x80},
+ \ maparg('baz', 't', 0, 1))
+ let lnum = expand('<sflnum>')
+ iab A B
+ call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'A',
+ \ 'lhsraw': 'A', 'mode': 'i',
+ \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
+ \ 'lnum': lnum + 1, 'rhs': 'B',
+ \ 'buffer': 0, 'abbr': 1, 'mode_bits': 0x0010},
+ \ maparg('A', 'i', 1, 1))
+ iuna A
+
+ map abc x<char-114>x
+ call assert_equal("xrx", maparg('abc'))
+ map abc y<S-char-114>y
+ call assert_equal("yRy", maparg('abc'))
+
+ " character with K_SPECIAL byte
+ nmap abc …
+ call assert_equal('…', maparg('abc'))
+
+ " modified character with K_SPECIAL byte
+ nmap abc <M-…>
+ call assert_equal('<M-…>', maparg('abc'))
+
+ " illegal bytes
+ let str = ":\x7f:\x80:\x90:\xd0:"
+ exe 'nmap abc ' .. str
+ call assert_equal(str, maparg('abc'))
+ unlet str
+
+ omap { w
+ let d = maparg('{', 'o', 0, 1)
+ call assert_equal(['{', 'w', 'o'], [d.lhs, d.rhs, d.mode])
+ ounmap {
+
+ lmap { w
+ let d = maparg('{', 'l', 0, 1)
+ call assert_equal(['{', 'w', 'l'], [d.lhs, d.rhs, d.mode])
+ lunmap {
+
+ nmap { w
+ let d = maparg('{', 'n', 0, 1)
+ call assert_equal(['{', 'w', 'n'], [d.lhs, d.rhs, d.mode])
+ nunmap {
+
+ xmap { w
+ let d = maparg('{', 'x', 0, 1)
+ call assert_equal(['{', 'w', 'x'], [d.lhs, d.rhs, d.mode])
+ xunmap {
+
+ smap { w
+ let d = maparg('{', 's', 0, 1)
+ call assert_equal(['{', 'w', 's'], [d.lhs, d.rhs, d.mode])
+ sunmap {
+
+ map <C-I> foo
+ unmap <Tab>
+ " This used to cause a segfault
+ call maparg('<C-I>', '', 0, 1)
+ unmap <C-I>
+
+ map abc <Nop>
+ call assert_equal("<Nop>", maparg('abc'))
+ unmap abc
+
+ call feedkeys(":abbr esc \<C-V>\<C-V>\<C-V>\<C-V>\<C-V>\<Esc>\<CR>", "xt")
+ let d = maparg('esc', 'i', 1, 1)
+ call assert_equal(['esc', "\<C-V>\<C-V>\<Esc>", '!'], [d.lhs, d.rhs, d.mode])
+ abclear
+ unlet d
+endfunc
+
+func Test_mapcheck()
+ call assert_equal('', mapcheck('a'))
+ call assert_equal('', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+
+ map a something
+ call assert_equal('something', mapcheck('a'))
+ call assert_equal('something', mapcheck('a', 'n'))
+ call assert_equal('', mapcheck('a', 'c'))
+ call assert_equal('', mapcheck('a', 'i'))
+ call assert_equal('something', 'abc'->mapcheck())
+ call assert_equal('something', 'ax'->mapcheck())
+ call assert_equal('', mapcheck('b'))
+ unmap a
+
+ map ab foobar
+ call assert_equal('foobar', mapcheck('a'))
+ call assert_equal('foobar', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+ unmap ab
+
+ map abc barfoo
+ call assert_equal('barfoo', mapcheck('a'))
+ call assert_equal('barfoo', mapcheck('a', 'n', 0))
+ call assert_equal('', mapcheck('a', 'n', 1))
+ call assert_equal('barfoo', mapcheck('abc'))
+ call assert_equal('', mapcheck('ax'))
+ call assert_equal('', mapcheck('b'))
+ unmap abc
+
+ abbr ab abbrev
+ call assert_equal('abbrev', mapcheck('a', 'i', 1))
+ call assert_equal('', mapcheck('a', 'n', 1))
+ call assert_equal('', mapcheck('a', 'i', 0))
+ unabbr ab
+endfunc
+
+func Test_range_map()
+ new
+ " Outside of the range, minimum
+ inoremap <Char-0x1040> a
+ execute "normal a\u1040\<Esc>"
+ " Inside of the range, minimum
+ inoremap <Char-0x103f> b
+ execute "normal a\u103f\<Esc>"
+ " Inside of the range, maximum
+ inoremap <Char-0xf03f> c
+ execute "normal a\uf03f\<Esc>"
+ " Outside of the range, maximum
+ inoremap <Char-0xf040> d
+ execute "normal a\uf040\<Esc>"
+ call assert_equal("abcd", getline(1))
+endfunc
+
+func One_mapset_test(keys, rhs)
+ exe 'nnoremap ' .. a:keys .. ' ' .. a:rhs
+ let orig = maparg(a:keys, 'n', 0, 1)
+ call assert_equal(a:keys, orig.lhs)
+ call assert_equal(a:rhs, orig.rhs)
+ call assert_equal('n', orig.mode)
+
+ exe 'nunmap ' .. a:keys
+ let d = maparg(a:keys, 'n', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('n', 0, orig)
+ let d = maparg(a:keys, 'n', 0, 1)
+ call assert_equal(a:keys, d.lhs)
+ call assert_equal(a:rhs, d.rhs)
+ call assert_equal('n', d.mode)
+
+ exe 'nunmap ' .. a:keys
+endfunc
+
+func Test_mapset()
+ call One_mapset_test('K', 'original<CR>')
+ call One_mapset_test('<F3>', 'original<CR>')
+ call One_mapset_test('<F3>', '<lt>Nop>')
+
+ " Check <> key conversion
+ new
+ inoremap K one<Left>x
+ call feedkeys("iK\<Esc>", 'xt')
+ call assert_equal('onxe', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('one<Left>x', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('onxe', getline(1))
+
+ iunmap K
+
+ " Test that <Nop> is restored properly
+ inoremap K <Nop>
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('<Nop>', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ inoremap K foo
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('foo', getline(1))
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('', getline(1))
+
+ iunmap K
+
+ " Test literal <CR> using a backslash
+ let cpo_save = &cpo
+ set cpo-=B
+ inoremap K one\<CR>two
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal('one\<CR>two', orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ iunmap K
+
+ " Test literal <CR> using CTRL-V
+ inoremap K one<CR>two
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ let orig = maparg('K', 'i', 0, 1)
+ call assert_equal('K', orig.lhs)
+ call assert_equal("one\x16<CR>two", orig.rhs)
+ call assert_equal('i', orig.mode)
+
+ iunmap K
+ let d = maparg('K', 'i', 0, 1)
+ call assert_equal({}, d)
+
+ call mapset('i', 0, orig)
+ call feedkeys("SK\<Esc>", 'xt')
+ call assert_equal('one<CR>two', getline(1))
+
+ iunmap K
+ let &cpo = cpo_save
+ bwipe!
+
+ call assert_fails('call mapset([], v:false, {})', 'E730:')
+ call assert_fails('call mapset("i", 0, "")', 'E1206:')
+ call assert_fails('call mapset("i", 0, {})', 'E460:')
+endfunc
+
+func Test_mapset_arg1_dir()
+ " This test is mostly about get_map_mode_string.
+ " Once the code gets past that, it's common with the 3 arg mapset.
+
+ " GetModes() return list of modes for 'XZ' lhs using maplist.
+ " There is one list item per mapping
+ func s:GetModes(abbr = v:false)
+ return maplist(a:abbr)->filter({_, m -> m.lhs == 'XZ'})
+ \ ->mapnew({_, m -> m.mode})
+ endfunc
+
+ func s:UnmapAll(lhs)
+ const unmap_cmds = [ 'unmap', 'unmap!', 'tunmap', 'lunmap' ]
+ for cmd in unmap_cmds
+ try | call execute(cmd .. ' ' .. a:lhs) | catch /E31/ | endtry
+ endfor
+ endfunc
+
+ let tmap = {}
+
+ " some mapset(mode, abbr, dict) tests using get_map_mode_str
+ map XZ x
+ let tmap = maplist()->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
+ " this splits the mapping into 2 mappings
+ call mapset('ox', v:false, tmap)
+ call assert_equal(2, len(s:GetModes()))
+ call mapset('o', v:false, tmap)
+ call assert_equal(3, len(s:GetModes()))
+ " test that '' acts like ' ', and that the 3 mappings become 1
+ call mapset('', v:false, tmap)
+ call assert_equal([' '], s:GetModes())
+ " dict's mode/abbr are ignored
+ call s:UnmapAll('XZ')
+ let tmap.mode = '!'
+ let tmap.abbr = v:true
+ call mapset('o', v:false, tmap)
+ call assert_equal(['o'], s:GetModes())
+
+ " test the 3 arg version handles bad mode string, dict not used
+ call assert_fails("call mapset('vi', v:false, {})", 'E1276:')
+
+
+ " get the abbreviations out of the way
+ abbreviate XZ ZX
+ let tmap = maplist(v:true)->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
+
+ abclear
+ " 'ic' is the default ab command, shows up as '!'
+ let tmap.mode = 'ic'
+ call mapset(tmap)
+ call assert_equal(['!'], s:GetModes(v:true))
+
+ abclear
+ let tmap.mode = 'i'
+ call mapset(tmap)
+ call assert_equal(['i'], s:GetModes(v:true))
+
+ abclear
+ let tmap.mode = 'c'
+ call mapset(tmap)
+ call assert_equal(['c'], s:GetModes(v:true))
+
+ abclear
+ let tmap.mode = '!'
+ call mapset(tmap)
+ call assert_equal(['!'], s:GetModes(v:true))
+
+ call assert_fails("call mapset(#{mode: ' !', abbr: 1})", 'E1276:')
+ call assert_fails("call mapset(#{mode: 'cl', abbr: 1})", 'E1276:')
+ call assert_fails("call mapset(#{mode: 'in', abbr: 1})", 'E1276:')
+
+ " the map commands
+ map XZ x
+ let tmap = maplist()->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
+
+ " try the combos
+ call s:UnmapAll('XZ')
+ " 'nxso' is ' ', the unadorned :map
+ let tmap.mode = 'nxso'
+ call mapset(tmap)
+ call assert_equal([' '], s:GetModes())
+
+ cal s:UnmapAll('XZ')
+ " 'ic' is '!'
+ let tmap.mode = 'ic'
+ call mapset(tmap)
+ call assert_equal(['!'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ " 'xs' is really 'v'
+ let tmap.mode = 'xs'
+ call mapset(tmap)
+ call assert_equal(['v'], s:GetModes())
+
+ " try the individual modes
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'n'
+ call mapset(tmap)
+ call assert_equal(['n'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'x'
+ call mapset(tmap)
+ call assert_equal(['x'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 's'
+ call mapset(tmap)
+ call assert_equal(['s'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'o'
+ call mapset(tmap)
+ call assert_equal(['o'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'i'
+ call mapset(tmap)
+ call assert_equal(['i'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'c'
+ call mapset(tmap)
+ call assert_equal(['c'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 't'
+ call mapset(tmap)
+ call assert_equal(['t'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+ let tmap.mode = 'l'
+ call mapset(tmap)
+ call assert_equal(['l'], s:GetModes())
+
+ call s:UnmapAll('XZ')
+
+ " get errors for modes that can't be in one mapping
+ call assert_fails("call mapset(#{mode: 'nxsoi', abbr: 0})", 'E1276:')
+ call assert_fails("call mapset(#{mode: ' !', abbr: 0})", 'E1276:')
+ call assert_fails("call mapset(#{mode: 'ix', abbr: 0})", 'E1276:')
+ call assert_fails("call mapset(#{mode: 'tl', abbr: 0})", 'E1276:')
+ call assert_fails("call mapset(#{mode: ' l', abbr: 0})", 'E1276:')
+ call assert_fails("call mapset(#{mode: ' t', abbr: 0})", 'E1276:')
+endfunc
+
+func Check_ctrlb_map(d, check_alt)
+ call assert_equal('<C-B>', a:d.lhs)
+ if a:check_alt
+ call assert_equal("\x80\xfc\x04B", a:d.lhsraw)
+ call assert_equal("\x02", a:d.lhsrawalt)
+ else
+ call assert_equal("\x02", a:d.lhsraw)
+ endif
+endfunc
+
+func Test_map_local()
+ nmap a global
+ nmap <buffer>a local
+
+ let prev_map_list = split(execute('nmap a'), "\n")
+ call assert_match('n\s*a\s*@local', prev_map_list[0])
+ call assert_match('n\s*a\s*global', prev_map_list[1])
+
+ let mapping = maparg('a', 'n', 0, 1)
+ call assert_equal(1, mapping.buffer)
+ let mapping.rhs = 'new_local'
+ call mapset('n', 0, mapping)
+
+ " Check that the global mapping is left untouched.
+ let map_list = split(execute('nmap a'), "\n")
+ call assert_match('n\s*a\s*@new_local', map_list[0])
+ call assert_match('n\s*a\s*global', map_list[1])
+
+ nunmap a
+endfunc
+
+func Test_map_restore()
+ " Test restoring map with alternate keycode
+ nmap <C-B> back
+ let d = maparg('<C-B>', 'n', 0, 1)
+ call Check_ctrlb_map(d, 1)
+ let dsimp = maparg("\x02", 'n', 0, 1)
+ call Check_ctrlb_map(dsimp, 0)
+ nunmap <C-B>
+ call mapset('n', 0, d)
+ let d = maparg('<C-B>', 'n', 0, 1)
+ call Check_ctrlb_map(d, 1)
+ let dsimp = maparg("\x02", 'n', 0, 1)
+ call Check_ctrlb_map(dsimp, 0)
+
+ nunmap <C-B>
+endfunc
+
+" Test restoring an <SID> mapping
+func Test_map_restore_sid()
+ func RestoreMap()
+ const d = maparg('<CR>', 'i', v:false, v:true)
+ iunmap <buffer> <CR>
+ call mapset('i', v:false, d)
+ endfunc
+
+ let mapscript =<< trim [CODE]
+ inoremap <silent><buffer> <SID>Return <C-R>=42<CR>
+ inoremap <script><buffer> <CR> <CR><SID>Return
+ [CODE]
+ call writefile(mapscript, 'Xmapscript', 'D')
+
+ new
+ source Xmapscript
+ inoremap <buffer> <C-B> <Cmd>call RestoreMap()<CR>
+ call feedkeys("i\<CR>\<*C-B>\<CR>", 'xt')
+ call assert_equal(['', '42', '42'], getline(1, '$'))
+
+ bwipe!
+ delfunc RestoreMap
+endfunc
+
+" Test restoring a mapping with a negative script ID
+func Test_map_restore_negative_sid()
+ let after =<< trim [CODE]
+ call assert_equal("\tLast set from --cmd argument",
+ \ execute('verbose nmap ,n')->trim()->split("\n")[-1])
+ let d = maparg(',n', 'n', 0, 1)
+ nunmap ,n
+ call assert_equal('No mapping found',
+ \ execute('verbose nmap ,n')->trim()->split("\n")[-1])
+ call mapset('n', 0, d)
+ call assert_equal("\tLast set from --cmd argument",
+ \ execute('verbose nmap ,n')->trim()->split("\n")[-1])
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [CODE]
+
+ if RunVim([], after, '--clean --cmd "nmap ,n <Nop>"')
+ call assert_equal([], readfile('Xresult'))
+ endif
+ call delete('Xresult')
+endfunc
+
+func Test_maplist()
+ new
+ func s:ClearMappingsAbbreviations()
+ mapclear | nmapclear | vmapclear | xmapclear | smapclear | omapclear
+ mapclear! | imapclear | lmapclear | cmapclear | tmapclear
+ mapclear <buffer> | nmapclear <buffer> | vmapclear <buffer>
+ xmapclear <buffer> | smapclear <buffer> | omapclear <buffer>
+ mapclear! <buffer> | imapclear <buffer> | lmapclear <buffer>
+ cmapclear <buffer> | tmapclear <buffer>
+ abclear | abclear <buffer>
+ endfunc
+
+ func s:AddMaps(new, accum)
+ if len(a:new) > 0 && a:new[0] != "No mapping found"
+ eval a:accum->extend(a:new)
+ endif
+ endfunc
+
+ call s:ClearMappingsAbbreviations()
+ call assert_equal(0, len(maplist()))
+ call assert_equal(0, len(maplist(v:true)))
+
+ " Set up some mappings.
+ map dup bar
+ map <buffer> dup bufbar
+ map foo<C-V> is<F4>foo
+ vnoremap <script> <buffer> <expr> <silent> bar isbar
+ tmap baz foo
+ omap h w
+ lmap i w
+ nmap j w
+ xmap k w
+ smap l w
+ map abc <Nop>
+ nmap <M-j> x
+ nmap <M-Space> y
+ " And abbreviations
+ abbreviate xy he
+ abbreviate xx she
+ abbreviate <buffer> x they
+
+ " Get a list of the mappings with the ':map' commands.
+ " Check maplist() return a list of the same size.
+ call assert_equal(13, len(maplist()))
+ call assert_equal(3, len(maplist(v:true)))
+ call assert_equal(13, len(maplist(v:false)))
+
+ " collect all the current maps using :map commands
+ let maps_command = []
+ call s:AddMaps(split(execute('map'), '\n'), maps_command)
+ call s:AddMaps(split(execute('map!'), '\n'), maps_command)
+ call s:AddMaps(split(execute('tmap'), '\n'), maps_command)
+ call s:AddMaps(split(execute('lmap'), '\n'), maps_command)
+
+ " Use maplist to get all the maps
+ let maps_maplist = maplist()
+ call assert_equal(len(maps_command), len(maps_maplist))
+
+ " make sure all the mode-lhs are unique, no duplicates
+ let map_set = {}
+ for d in maps_maplist
+ let map_set[d.mode .. "-" .. d.lhs .. "-" .. d.buffer] = 0
+ endfor
+ call assert_equal(len(maps_maplist), len(map_set))
+
+ " For everything returned by maplist, should be the same as from maparg.
+ " Except for "map dup", bacause maparg returns the <buffer> version
+ for d in maps_maplist
+ if d.lhs == 'dup' && d.buffer == 0
+ continue
+ endif
+ let d_maparg = maparg(d.lhs, d.mode, v:false, v:true)
+ call assert_equal(d_maparg, d)
+ endfor
+
+ " Check abbr matches maparg
+ for d in maplist(v:true)
+ " Note, d.mode is '!', but can't use that with maparg
+ let d_maparg = maparg(d.lhs, 'i', v:true, v:true)
+ call assert_equal(d_maparg, d)
+ endfor
+
+ call s:ClearMappingsAbbreviations()
+ call assert_equal(0, len(maplist()))
+ call assert_equal(0, len(maplist(v:true)))
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim
index 5c5a65d4ca..36a46684c1 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/test/old/testdir/test_mapping.vim
@@ -3,6 +3,7 @@
source shared.vim
source check.vim
source screendump.vim
+source term_util.vim
func Test_abbreviation()
" abbreviation with 0x80 should work
@@ -277,9 +278,9 @@ func Test_map_timeout()
endfunc
func Test_map_timeout_with_timer_interrupt()
- if !has('job') || !has('timers')
- return
- endif
+ CheckFeature job
+ CheckFeature timers
+ let g:test_is_flaky = 1
" Confirm the timer invoked in exit_cb of the job doesn't disturb mapped key
" sequence.
@@ -417,9 +418,9 @@ func Test_error_in_map_expr()
" GC must not run during map-expr processing, which can make Vim crash.
call term_sendkeys(buf, '!')
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "\<CR>")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call assert_equal('run', job_status(job))
call term_sendkeys(buf, ":qall!\<CR>")
@@ -759,11 +760,12 @@ func Test_mapcomplete()
call feedkeys(":abbr! \<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal("\"abbr! \x01", @:)
- " Multiple matches for a map
- nmap ,f /H<CR>
- omap ,f /H<CR>
+ " When multiple matches have the same {lhs}, it should only appear once.
+ " The simplified form should also not be included.
+ nmap ,<C-F> /H<CR>
+ omap ,<C-F> /H<CR>
call feedkeys(":map ,\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"map ,f', @:)
+ call assert_equal('"map ,<C-F>', @:)
mapclear
endfunc
@@ -934,6 +936,501 @@ func Test_abbr_remove()
call assert_equal({}, maparg('foo', 'i', 1, 1))
endfunc
+" Trigger an abbreviation using a special key
+func Test_abbr_trigger_special()
+ new
+ iabbr teh the
+ call feedkeys("iteh\<F2>\<Esc>", 'xt')
+ call assert_equal('the<F2>', getline(1))
+ iunab teh
+ close!
+endfunc
+
+" Test for '<' in 'cpoptions'
+func Test_map_cpo_special_keycode()
+ set cpo-=<
+ imap x<Bslash>k Test
+ let d = maparg('x<Bslash>k', 'i', 0, 1)
+ call assert_equal(['x\k', 'Test', 'i'], [d.lhs, d.rhs, d.mode])
+ call feedkeys(":imap x\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"imap x\k', @:)
+ iunmap x<Bslash>k
+ " Nvim: no "<" flag in 'cpoptions'.
+ " set cpo+=<
+ " imap x<Bslash>k Test
+ " let d = maparg('x<Bslash>k', 'i', 0, 1)
+ " call assert_equal(['x<Bslash>k', 'Test', 'i'], [d.lhs, d.rhs, d.mode])
+ " call feedkeys(":imap x\<C-A>\<C-B>\"\<CR>", 'tx')
+ " call assert_equal('"imap x<Bslash>k', @:)
+ " iunmap x<Bslash>k
+ set cpo-=<
+ " Modifying 'cpo' above adds some default mappings, remove them
+ mapclear
+ mapclear!
+endfunc
+
+" Test for <Cmd> key in maps to execute commands
+func Test_map_cmdkey()
+ new
+
+ " Error cases
+ let x = 0
+ noremap <F3> <Cmd><Cmd>let x = 1<CR>
+ call assert_fails('call feedkeys("\<F3>", "xt")', 'E1136:')
+ call assert_equal(0, x)
+
+ noremap <F3> <Cmd>let x = 3
+ call assert_fails('call feedkeys("\<F3>", "xt!")', 'E1255:')
+ call assert_equal(0, x)
+
+ " works in various modes and sees the correct mode()
+ noremap <F3> <Cmd>let m = mode(1)<CR>
+ noremap! <F3> <Cmd>let m = mode(1)<CR>
+
+ " normal mode
+ call feedkeys("\<F3>", 'xt')
+ call assert_equal('n', m)
+
+ " visual mode
+ call feedkeys("v\<F3>", 'xt!')
+ call assert_equal('v', m)
+ " shouldn't leave the visual mode
+ call assert_equal('v', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " visual mapping in select mode
+ call feedkeys("gh\<F3>", 'xt!')
+ call assert_equal('v', m)
+ " shouldn't leave select mode
+ call assert_equal('s', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " select mode mapping
+ snoremap <F3> <Cmd>let m = mode(1)<cr>
+ call feedkeys("gh\<F3>", 'xt!')
+ call assert_equal('s', m)
+ " shouldn't leave select mode
+ call assert_equal('s', mode(1))
+ call feedkeys("\<Esc>", 'xt')
+ call assert_equal('n', mode(1))
+
+ " operator-pending mode
+ call feedkeys("d\<F3>", 'xt!')
+ call assert_equal('no', m)
+ " leaves the operator-pending mode
+ call assert_equal('n', mode(1))
+
+ " insert mode
+ call feedkeys("i\<F3>abc", 'xt')
+ call assert_equal('i', m)
+ call assert_equal('abc', getline('.'))
+
+ " replace mode
+ call feedkeys("0R\<F3>two", 'xt')
+ call assert_equal('R', m)
+ call assert_equal('two', getline('.'))
+
+ " virtual replace mode
+ call setline('.', "one\ttwo")
+ call feedkeys("4|gR\<F3>xxx", 'xt')
+ call assert_equal('Rv', m)
+ call assert_equal("onexxx\ttwo", getline('.'))
+
+ " cmdline mode
+ call feedkeys(":\<F3>\"xxx\<CR>", 'xt!')
+ call assert_equal('c', m)
+ call assert_equal('"xxx', @:)
+
+ " terminal mode
+ if CanRunVimInTerminal()
+ tnoremap <F3> <Cmd>let m = mode(1)<CR>
+ let buf = Run_shell_in_terminal({})
+ call feedkeys("\<F3>", 'xt')
+ call assert_equal('t', m)
+ call assert_equal('t', mode(1))
+ call StopShellInTerminal(buf)
+ close!
+ tunmap <F3>
+ endif
+
+ " invoke cmdline mode recursively
+ noremap! <F2> <Cmd>norm! :foo<CR>
+ %d
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys(":bar\<F2>x\<C-B>\"\r", 'xt')
+ call assert_equal('"barx', @:)
+ unmap! <F2>
+
+ " test for calling a <SID> function
+ let lines =<< trim END
+ map <F2> <Cmd>call <SID>do_it()<CR>
+ func s:do_it()
+ let g:x = 32
+ endfunc
+ END
+ call writefile(lines, 'Xscript')
+ source Xscript
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(32, g:x)
+ call delete('Xscript')
+
+ unmap <F3>
+ unmap! <F3>
+ %bw!
+endfunc
+
+" text object enters visual mode
+func TextObj()
+ if mode() !=# "v"
+ normal! v
+ end
+ call cursor(1, 3)
+ normal! o
+ call cursor(2, 4)
+endfunc
+
+func s:cmdmap(lhs, rhs)
+ exe 'noremap ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+ exe 'noremap! ' .. a:lhs .. ' <Cmd>' .. a:rhs .. '<CR>'
+endfunc
+
+func s:cmdunmap(lhs)
+ exe 'unmap ' .. a:lhs
+ exe 'unmap! ' .. a:lhs
+endfunc
+
+" Map various <Fx> keys used by the <Cmd> key tests
+func s:setupMaps()
+ call s:cmdmap('<F3>', 'let m = mode(1)')
+ call s:cmdmap('<F4>', 'normal! ww')
+ call s:cmdmap('<F5>', 'normal! "ay')
+ call s:cmdmap('<F6>', 'throw "very error"')
+ call s:cmdmap('<F7>', 'call TextObj()')
+ call s:cmdmap('<F8>', 'startinsert')
+ call s:cmdmap('<F9>', 'stopinsert')
+endfunc
+
+" Remove the mappings setup by setupMaps()
+func s:cleanupMaps()
+ call s:cmdunmap('<F3>')
+ call s:cmdunmap('<F4>')
+ call s:cmdunmap('<F5>')
+ call s:cmdunmap('<F6>')
+ call s:cmdunmap('<F7>')
+ call s:cmdunmap('<F8>')
+ call s:cmdunmap('<F9>')
+endfunc
+
+" Test for <Cmd> mapping in normal mode
+func Test_map_cmdkey_normal_mode()
+ new
+ call s:setupMaps()
+
+ " check v:count and v:register works
+ call s:cmdmap('<F2>', 'let s = [mode(1), v:count, v:register]')
+ call feedkeys("\<F2>", 'xt')
+ call assert_equal(['n', 0, '"'], s)
+ call feedkeys("7\<F2>", 'xt')
+ call assert_equal(['n', 7, '"'], s)
+ call feedkeys("\"e\<F2>", 'xt')
+ call assert_equal(['n', 0, 'e'], s)
+ call feedkeys("5\"k\<F2>", 'xt')
+ call assert_equal(['n', 5, 'k'], s)
+ call s:cmdunmap('<F2>')
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("\<F7>y", 'xt')
+ call assert_equal("me short lines\nof t", @")
+ call assert_equal('v', getregtype('"'))
+ call assert_equal([0, 1, 3, 0], getpos("'<"))
+ call assert_equal([0, 2, 4, 0], getpos("'>"))
+
+ " startinsert
+ %d
+ call feedkeys("\<F8>abc", 'xt')
+ call assert_equal('abc', getline(1))
+
+ " feedkeys are not executed immediately
+ noremap ,a <Cmd>call feedkeys("aalpha") \| let g:a = getline(2)<CR>
+ %d
+ call setline(1, ['some short lines', 'of test text'])
+ call cursor(2, 3)
+ call feedkeys(",a\<F3>", 'xt')
+ call assert_equal('of test text', g:a)
+ call assert_equal('n', m)
+ call assert_equal(['some short lines', 'of alphatest text'], getline(1, '$'))
+ nunmap ,a
+
+ " feedkeys(..., 'x') is executed immediately, but insert mode is aborted
+ noremap ,b <Cmd>call feedkeys("abeta", 'x') \| let g:b = getline(2)<CR>
+ call feedkeys(",b\<F3>", 'xt')
+ call assert_equal('n', m)
+ call assert_equal('of alphabetatest text', g:b)
+ nunmap ,b
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping with the :normal command
+func Test_map_cmdkey_normal_cmd()
+ new
+ noremap ,x <Cmd>call append(1, "xx") \| call append(1, "aa")<CR>
+ noremap ,f <Cmd>nosuchcommand<CR>
+ noremap ,e <Cmd>throw "very error" \| call append(1, "yy")<CR>
+ noremap ,m <Cmd>echoerr "The message." \| call append(1, "zz")<CR>
+ noremap ,w <Cmd>for i in range(5) \| if i==1 \| echoerr "Err" \| endif \| call append(1, i) \| endfor<CR>
+
+ call setline(1, ['some short lines', 'of test text'])
+ exe "norm ,x\r"
+ call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+ call assert_fails('norm ,f', 'E492:')
+ call assert_fails('norm ,e', 'very error')
+ call assert_fails('norm ,m', 'The message.')
+ call assert_equal(['some short lines', 'aa', 'xx', 'of test text'], getline(1, '$'))
+
+ %d
+ let caught_err = 0
+ try
+ exe "normal ,w"
+ catch /Vim(echoerr):Err/
+ let caught_err = 1
+ endtry
+ call assert_equal(1, caught_err)
+ call assert_equal(['', '0'], getline(1, '$'))
+
+ %d
+ call assert_fails('normal ,w', 'Err')
+ call assert_equal(['', '4', '3', '2' ,'1', '0'], getline(1, '$'))
+ call assert_equal(1, line('.'))
+
+ nunmap ,x
+ nunmap ,f
+ nunmap ,e
+ nunmap ,m
+ nunmap ,w
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in visual mode
+func Test_map_cmdkey_visual_mode()
+ new
+ set showmode
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("v\<F4>", 'xt!')
+ call assert_equal(['v', 1, 12], [mode(1), col('v'), col('.')])
+
+ " can invoke an operator, ending the visual mode
+ let @a = ''
+ call feedkeys("\<F5>", 'xt!')
+ call assert_equal('n', mode(1))
+ call assert_equal('some short l', @a)
+
+ " error doesn't interrupt visual mode
+ call assert_fails('call feedkeys("ggvw\<F6>", "xt!")', 'E605:')
+ call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+ call feedkeys("\<F7>", 'xt!')
+ call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ " startinsert gives "-- (insert) VISUAL --" mode
+ call feedkeys("\<F8>", 'xt!')
+ call assert_equal(['v', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+ redraw!
+ call assert_match('^-- (insert) VISUAL --', Screenline(&lines))
+ call feedkeys("\<Esc>new ", 'x')
+ call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+ call s:cleanupMaps()
+ set showmode&
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in select mode
+func Test_map_cmdkey_select_mode()
+ new
+ set showmode
+ call s:setupMaps()
+
+ snoremap <F1> <cmd>throw "very error"<CR>
+ snoremap <F2> <cmd>normal! <c-g>"by<CR>
+ call setline(1, ['some short lines', 'of test text'])
+
+ call feedkeys("gh\<F4>", "xt!")
+ call assert_equal(['s', 1, 12], [mode(1), col('v'), col('.')])
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+
+ " visual mapping in select mode restarts select mode after operator
+ let @a = ''
+ call feedkeys("\<F5>", 'xt!')
+ call assert_equal('s', mode(1))
+ call assert_equal('some short l', @a)
+
+ " select mode mapping works, and does not restart select mode
+ let @b = ''
+ call feedkeys("\<F2>", 'xt!')
+ call assert_equal('n', mode(1))
+ call assert_equal('some short l', @b)
+
+ " error doesn't interrupt temporary visual mode
+ call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F6>", "xt!")', 'E605:')
+ redraw!
+ call assert_match('^-- VISUAL --', Screenline(&lines))
+ " quirk: restoration of select mode is not performed
+ call assert_equal(['v', 1, 6], [mode(1), col('v'), col('.')])
+
+ " error doesn't interrupt select mode
+ call assert_fails('call feedkeys("\<Esc>ggvw\<C-G>\<F1>", "xt!")', 'E605:')
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 6], [mode(1), col('v'), col('.')])
+
+ call feedkeys("\<F7>", 'xt!')
+ redraw!
+ call assert_match('^-- SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ " startinsert gives "-- SELECT (insert) --" mode
+ call feedkeys("\<F8>", 'xt!')
+ redraw!
+ call assert_match('^-- (insert) SELECT --', Screenline(&lines))
+ call assert_equal(['s', 1, 3, 2, 4], [mode(1), line('v'), col('v'), line('.'), col('.')])
+ call feedkeys("\<Esc>new ", 'x')
+ call assert_equal(['some short lines', 'of new test text'], getline(1, '$'))
+
+ sunmap <F1>
+ sunmap <F2>
+ call s:cleanupMaps()
+ set showmode&
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in operator-pending mode
+func Test_map_cmdkey_op_pending_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ call feedkeys("d\<F4>", 'xt')
+ call assert_equal(['lines', 'of test text'], getline(1, '$'))
+ call assert_equal(['some short '], getreg('"', 1, 1))
+ " create a new undo point
+ let &g:undolevels = &g:undolevels
+
+ call feedkeys(".", 'xt')
+ call assert_equal(['test text'], getline(1, '$'))
+ call assert_equal(['lines', 'of '], getreg('"', 1, 1))
+ " create a new undo point
+ let &g:undolevels = &g:undolevels
+
+ call feedkeys("uu", 'xt')
+ call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+ " error aborts operator-pending, operator not performed
+ call assert_fails('call feedkeys("d\<F6>", "xt")', 'E605:')
+ call assert_equal(['some short lines', 'of test text'], getline(1, '$'))
+
+ call feedkeys("\"bd\<F7>", 'xt')
+ call assert_equal(['soest text'], getline(1, '$'))
+ call assert_equal(['me short lines', 'of t'], getreg('b', 1, 1))
+
+ " startinsert aborts operator
+ call feedkeys("d\<F8>cc", 'xt')
+ call assert_equal(['soccest text'], getline(1, '$'))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert mode
+func Test_map_cmdkey_insert_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ " works the same as <C-O>w<C-O>w
+ call feedkeys("iindeed \<F4>little ", 'xt')
+ call assert_equal(['indeed some short little lines', 'of test text'], getline(1, '$'))
+ call assert_fails('call feedkeys("i\<F6> 2", "xt")', 'E605:')
+ call assert_equal(['indeed some short little 2 lines', 'of test text'], getline(1, '$'))
+
+ " Note when entering visual mode from InsertEnter autocmd, an async event,
+ " or a <Cmd> mapping, vim ends up in undocumented "INSERT VISUAL" mode.
+ call feedkeys("i\<F7>stuff ", 'xt')
+ call assert_equal(['indeed some short little 2 lines', 'of stuff test text'], getline(1, '$'))
+ call assert_equal(['v', 1, 3, 2, 9], [mode(1), line('v'), col('v'), line('.'), col('.')])
+
+ call feedkeys("\<F5>", 'xt')
+ call assert_equal(['deed some short little 2 lines', 'of stuff '], getreg('a', 1, 1))
+
+ " also works as part of abbreviation
+ abbr foo <Cmd>let g:y = 17<CR>bar
+ exe "normal i\<space>foo "
+ call assert_equal(17, g:y)
+ call assert_equal('in bar deed some short little 2 lines', getline(1))
+ unabbr foo
+
+ " :startinsert does nothing
+ call setline(1, 'foo bar')
+ call feedkeys("ggi\<F8>vim", 'xt')
+ call assert_equal('vimfoo bar', getline(1))
+
+ " :stopinsert works
+ call feedkeys("ggi\<F9>Abc", 'xt')
+ call assert_equal('vimfoo barbc', getline(1))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in insert-completion mode
+func Test_map_cmdkey_insert_complete_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, 'some short lines')
+ call feedkeys("os\<C-X>\<C-N>\<F3>\<C-N> ", 'xt')
+ call assert_equal('ic', m)
+ call assert_equal(['some short lines', 'short '], getline(1, '$'))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
+" Test for <Cmd> mapping in cmdline mode
+func Test_map_cmdkey_cmdline_mode()
+ new
+ call s:setupMaps()
+
+ call setline(1, ['some short lines', 'of test text'])
+ let x = 0
+ call feedkeys(":let x\<F3>= 10\r", 'xt')
+ call assert_equal('c', m)
+ call assert_equal(10, x)
+
+ " exception doesn't leave cmdline mode
+ call assert_fails('call feedkeys(":let x\<F6>= 20\r", "xt")', 'E605:')
+ call assert_equal(20, x)
+
+ " move cursor in the buffer from cmdline mode
+ call feedkeys(":let x\<F4>= 30\r", 'xt')
+ call assert_equal(30, x)
+ call assert_equal(12, col('.'))
+
+ " :startinsert takes effect after leaving cmdline mode
+ call feedkeys(":let x\<F8>= 40\rnew ", 'xt')
+ call assert_equal(40, x)
+ call assert_equal('some short new lines', getline(1))
+
+ call s:cleanupMaps()
+ %bw!
+endfunc
+
func Test_map_cmdkey_redo()
func SelectDash()
call search('^---\n\zs', 'bcW')
@@ -968,6 +1465,24 @@ func Test_map_cmdkey_redo()
call delete('Xcmdtext')
delfunc SelectDash
ounmap i-
+
+ new
+ call setline(1, 'aaa bbb ccc ddd')
+
+ " command can contain special keys
+ onoremap ix <Cmd>let g:foo ..= '…'<Bar>normal! <C-Right><CR>
+ let g:foo = ''
+ call feedkeys('0dix.', 'xt')
+ call assert_equal('……', g:foo)
+ call assert_equal('ccc ddd', getline(1))
+ unlet g:foo
+
+ " command line ending in "0" is handled without errors
+ onoremap ix <Cmd>eval 0<CR>
+ call feedkeys('dix.', 'xt')
+
+ ounmap ix
+ bwipe!
endfunc
" Test for using <script> with a map to remap characters in rhs
diff --git a/src/nvim/testdir/test_marks.vim b/test/old/testdir/test_marks.vim
index a7ccca498c..a7ccca498c 100644
--- a/src/nvim/testdir/test_marks.vim
+++ b/test/old/testdir/test_marks.vim
diff --git a/src/nvim/testdir/test_match.vim b/test/old/testdir/test_match.vim
index 600b6132a9..1cb52b8b2a 100644
--- a/src/nvim/testdir/test_match.vim
+++ b/test/old/testdir/test_match.vim
@@ -322,7 +322,7 @@ func OtherWindowCommon()
END
call writefile(lines, 'XscriptMatchCommon')
let buf = RunVimInTerminal('-S XscriptMatchCommon', #{rows: 12})
- call term_wait(buf)
+ call TermWait(buf)
return buf
endfunc
@@ -373,7 +373,6 @@ func Test_match_in_linebreak()
END
call writefile(lines, 'XscriptMatchLinebreak')
let buf = RunVimInTerminal('-S XscriptMatchLinebreak', #{rows: 10})
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_match_linebreak', {})
call StopVimInTerminal(buf)
@@ -390,7 +389,6 @@ func Test_match_with_incsearch()
END
call writefile(lines, 'XmatchWithIncsearch')
let buf = RunVimInTerminal('-S XmatchWithIncsearch', #{rows: 6})
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_match_with_incsearch_1', {})
call term_sendkeys(buf, ":s/0")
@@ -431,7 +429,6 @@ func Test_match_tab_with_linebreak()
END
call writefile(lines, 'XscriptMatchTabLinebreak')
let buf = RunVimInTerminal('-S XscriptMatchTabLinebreak', #{rows: 10})
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_match_tab_linebreak', {})
call StopVimInTerminal(buf)
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/test/old/testdir/test_matchadd_conceal.vim
index 1b5fc8313f..6c9b8786f5 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/test/old/testdir/test_matchadd_conceal.vim
@@ -372,14 +372,14 @@ func Test_cursor_column_in_concealed_line_after_window_scroll()
END
call writefile(lines, 'Xcolesearch')
let buf = RunVimInTerminal('Xcolesearch', {})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
" Jump to something that is beyond the bottom of the window,
" so there's a scroll down.
call term_sendkeys(buf, ":so %\<CR>")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "/expr\<CR>")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
" Are the concealed parts of the current line really hidden?
let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('')
@@ -409,7 +409,7 @@ func Test_cursor_column_in_concealed_line_after_leftcol_change()
" Horizontal scroll would center the cursor in the screen line, but conceal
" makes it go to screen column 1.
call term_sendkeys(buf, "$")
- call term_wait(buf)
+ call TermWait(buf)
" Are the concealed parts of the current line really hidden?
call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))})
diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/test/old/testdir/test_matchadd_conceal_utf8.vim
index f33c7f694c..f33c7f694c 100644
--- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim
+++ b/test/old/testdir/test_matchadd_conceal_utf8.vim
diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/test/old/testdir/test_matchfuzzy.vim
index b46550fbc3..90f3366b23 100644
--- a/src/nvim/testdir/test_matchfuzzy.vim
+++ b/test/old/testdir/test_matchfuzzy.vim
@@ -2,6 +2,7 @@
source shared.vim
source check.vim
+source term_util.vim
" Test for matchfuzzy()
func Test_matchfuzzy()
@@ -67,7 +68,7 @@ func Test_matchfuzzy()
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_fails("let x = matchfuzzy(l, 'cam', 'random')", 'E1206:')
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:')
@@ -76,7 +77,7 @@ func Test_matchfuzzy()
" 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, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzy(l, 'cam', v:_null_dict)", 'E1297:')
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:')
@@ -144,7 +145,7 @@ func Test_matchfuzzypos()
\ 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_fails("let x = matchfuzzypos(l, 'cam', 'random')", 'E1206:')
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:')
@@ -153,7 +154,7 @@ func Test_matchfuzzypos()
" 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, 'foo', {'key' : []})", 'E730:')
- call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E715:')
+ call assert_fails("let x = matchfuzzypos(l, 'cam', v:_null_dict)", 'E1297:')
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:')
@@ -260,4 +261,30 @@ func Test_matchfuzzy_limit()
call assert_equal([{'id': 5, 'val': 'crayon'}], l->matchfuzzy('c', #{key: 'val', limit: 1}))
endfunc
+" This was using uninitialized memory
+func Test_matchfuzzy_initialized()
+ CheckRunVimInTerminal
+
+ " This can take a very long time (esp. when using valgrind). Run in a
+ " separate Vim instance and kill it after two seconds. We only check for
+ " memory errors.
+ let lines =<< trim END
+ lvimgrep [ss [fg*
+ END
+ call writefile(lines, 'XTest_matchfuzzy', 'D')
+
+ let buf = RunVimInTerminal('-u NONE -X -Z', {})
+ call term_sendkeys(buf, ":source XTest_matchfuzzy\n")
+ call TermWait(buf, 2000)
+
+ let job = term_getjob(buf)
+ if job_status(job) == "run"
+ call job_stop(job, "int")
+ call TermWait(buf, 50)
+ endif
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_matchparen.vim b/test/old/testdir/test_matchparen.vim
new file mode 100644
index 0000000000..3138180c66
--- /dev/null
+++ b/test/old/testdir/test_matchparen.vim
@@ -0,0 +1,87 @@
+" Test for the matchparen plugin
+
+if !has('gui_running') && has('unix')
+ " set term=ansi
+endif
+
+source view_util.vim
+source check.vim
+source screendump.vim
+
+" Test for scrolling that modifies buffer during visual block
+func Test_visual_block_scroll()
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set scrolloff=1
+ call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
+ call cursor(5, 1)
+ END
+
+ let filename = 'Xvisualblockmodifiedscroll'
+ call writefile(lines, filename, 'D')
+
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
+ call term_sendkeys(buf, "V\<C-D>\<C-D>")
+
+ call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for clearing paren highlight when switching buffers
+func Test_matchparen_clear_highlight()
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set hidden
+ call setline(1, ['()'])
+ normal 0
+
+ func OtherBuffer()
+ enew
+ exe "normal iaa\<Esc>0"
+ endfunc
+ END
+ call writefile(lines, 'XMatchparenClear', 'D')
+ let buf = RunVimInTerminal('-S XMatchparenClear', #{rows: 5})
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
+
+ call term_sendkeys(buf, ":call OtherBuffer()\<CR>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
+
+ call term_sendkeys(buf, "\<C-^>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_1', {})
+
+ call term_sendkeys(buf, "\<C-^>:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_matchparen_clear_highlight_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for scrolling that modifies buffer during visual block
+func Test_matchparen_pum_clear()
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set completeopt=menuone
+ call setline(1, ['aa', 'aaa', 'aaaa', '(a)'])
+ call cursor(4, 3)
+ END
+
+ let filename = 'Xmatchparen'
+ call writefile(lines, filename, 'D')
+
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 10})
+ call term_sendkeys(buf, "i\<C-N>\<C-N>")
+
+ call VerifyScreenDump(buf, 'Test_matchparen_pum_clear_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_menu.vim b/test/old/testdir/test_menu.vim
index a1121632e6..d1c1180ce1 100644
--- a/src/nvim/testdir/test_menu.vim
+++ b/test/old/testdir/test_menu.vim
@@ -162,7 +162,7 @@ endfunc
" Test for menu item completion in command line
func Test_menu_expand()
- " Create the menu itmes for test
+ " Create the menu items for test
menu Dummy.Nothing lll
for i in range(1, 4)
let m = 'menu Xmenu.A' .. i .. '.A' .. i
diff --git a/src/nvim/testdir/test_messages.vim b/test/old/testdir/test_messages.vim
index bfdebdac79..5ebcb375b6 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/test/old/testdir/test_messages.vim
@@ -112,7 +112,7 @@ func Test_mode_message_at_leaving_insert_by_ctrl_c()
let rows = 10
let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
call assert_equal('run', job_status(term_getjob(buf)))
call term_sendkeys(buf, "i")
@@ -140,7 +140,7 @@ func Test_mode_message_at_leaving_insert_with_esc_mapped()
let rows = 10
let buf = term_start([GetVimProg(), '--clean', '-S', testfile], {'term_rows': rows})
- call term_wait(buf, 200)
+ call WaitForAssert({-> assert_match('0,0-1\s*All$', term_getline(buf, rows - 1))})
call assert_equal('run', job_status(term_getjob(buf)))
call term_sendkeys(buf, "i")
@@ -341,10 +341,46 @@ func Test_message_more_scrollback()
call StopVimInTerminal(buf)
endfunc
+func Test_message_not_cleared_after_mode()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ nmap <silent> gx :call DebugSilent('normal')<CR>
+ vmap <silent> gx :call DebugSilent('visual')<CR>
+ function DebugSilent(arg)
+ echomsg "from DebugSilent" a:arg
+ endfunction
+ set showmode
+ set cmdheight=1
+ call test_settime(1)
+ call setline(1, ['one', 'NoSuchFile', 'three'])
+ END
+ call writefile(lines, 'XmessageMode', 'D')
+ let buf = RunVimInTerminal('-S XmessageMode', {'rows': 10})
+
+ call term_sendkeys(buf, 'gx')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_message_not_cleared_after_mode_1', {})
+
+ " removing the mode message used to also clear the intended message
+ call term_sendkeys(buf, 'vEgx')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_message_not_cleared_after_mode_2', {})
+
+ " removing the mode message used to also clear the error message
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, '2GvEgf')
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_message_not_cleared_after_mode_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" Test verbose message before echo command
func Test_echo_verbose_system()
CheckRunVimInTerminal
- CheckUnix
+ CheckUnix " needs the "seq" command
+ CheckNotMac " doesn't use /tmp
let buf = RunVimInTerminal('', {'rows': 10})
call term_sendkeys(buf, ":4 verbose echo system('seq 20')\<CR>")
@@ -403,6 +439,21 @@ func Test_ask_yesno()
call StopVimInTerminal(buf)
endfunc
+func Test_null()
+ echom v:_null_list
+ echom v:_null_dict
+ echom v:_null_blob
+ echom v:_null_string
+ " Nvim doesn't have NULL functions
+ " echom test_null_function()
+ " Nvim doesn't have NULL partials
+ " echom test_null_partial()
+ if has('job')
+ echom test_null_job()
+ echom test_null_channel()
+ endif
+endfunc
+
func Test_mapping_at_hit_return_prompt()
nnoremap <C-B> :echo "hit ctrl-b"<CR>
call feedkeys(":ls\<CR>", "xt")
@@ -434,6 +485,29 @@ func Test_echo_string_partial()
call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}])))
endfunc
+" Test that fileinfo is shown properly when 'cmdheight' has just decreased
+" due to switching tabpage and 'shortmess' doesn't contain 'o' or 'O'.
+func Test_fileinfo_tabpage_cmdheight()
+ CheckRunVimInTerminal
+
+ let content =<< trim END
+ set shortmess-=o
+ set shortmess-=O
+ set shortmess-=F
+ tabnew
+ set cmdheight=2
+ tabprev
+ edit Xfileinfo.txt
+ END
+
+ call writefile(content, 'Xtest_fileinfo_tabpage_cmdheight', 'D')
+ let buf = RunVimInTerminal('-S Xtest_fileinfo_tabpage_cmdheight', #{rows: 6})
+ call WaitForAssert({-> assert_match('^"Xfileinfo.txt" \[New\]', term_getline(buf, 6))})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
" Message output was previously overwritten by the fileinfo display, shown
" when switching buffers. If a buffer is switched to, then a message if
" echoed, we should show the message, rather than overwriting it with
diff --git a/src/nvim/testdir/test_method.vim b/test/old/testdir/test_method.vim
index ca3b736429..88dbbd7bf4 100644
--- a/src/nvim/testdir/test_method.vim
+++ b/test/old/testdir/test_method.vim
@@ -63,7 +63,7 @@ func Test_dict_method()
call assert_equal(2, d->remove("two"))
let d.two = 2
call assert_fails('let x = d->repeat(2)', 'E731:')
- call assert_fails('let x = d->reverse()', 'E899:')
+ call assert_fails('let x = d->reverse()', 'E1252:')
call assert_fails('let x = d->sort()', 'E686:')
call assert_equal("{'one': 1, 'two': 2, 'three': 3}", d->string())
call assert_equal(v:t_dict, d->type())
diff --git a/src/nvim/testdir/test_mksession.vim b/test/old/testdir/test_mksession.vim
index 972397cb91..d63d10b44c 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/test/old/testdir/test_mksession.vim
@@ -168,7 +168,6 @@ func Test_mksession_rtp()
return
endif
new
- set sessionoptions+=options
let _rtp=&rtp
" Make a real long (invalid) runtimepath value,
" that should exceed PATH_MAX (hopefully)
@@ -188,7 +187,6 @@ func Test_mksession_rtp()
call assert_equal(expected, li)
call delete('Xtest_mks.out')
- set sessionoptions&
endfunc
func Test_mksession_arglist()
@@ -208,7 +206,6 @@ func Test_mksession_arglist()
argdel *
endfunc
-
func Test_mksession_one_buffer_two_windows()
edit Xtest1
new Xtest2
@@ -260,6 +257,48 @@ func Test_mksession_lcd_multiple_tabs()
call delete('Xtest_mks.out')
endfunc
+" Test for tabpage-local directory
+func Test_mksession_tcd_multiple_tabs()
+ let save_cwd = getcwd()
+ call mkdir('Xtopdir')
+ cd Xtopdir
+ call mkdir('Xtabdir1')
+ call mkdir('Xtabdir2')
+ call mkdir('Xtabdir3')
+ call mkdir('Xwindir1')
+ call mkdir('Xwindir2')
+ call mkdir('Xwindir3')
+ tcd Xtabdir1
+ botright new
+ wincmd t
+ lcd ../Xwindir1
+ tabnew
+ tcd ../Xtabdir2
+ botright new
+ lcd ../Xwindir2
+ tabnew
+ tcd ../Xtabdir3
+ botright new
+ lcd ../Xwindir3
+ tabfirst
+ 1wincmd w
+ mksession! Xtest_mks.out
+ only | tabonly
+ source Xtest_mks.out
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(-1, 1), ':t'))
+ call assert_equal('Xwindir1', fnamemodify(getcwd(1, 1), ':t'))
+ call assert_equal('Xtabdir1', fnamemodify(getcwd(2, 1), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(-1, 2), ':t'))
+ call assert_equal('Xtabdir2', fnamemodify(getcwd(1, 2), ':t'))
+ call assert_equal('Xwindir2', fnamemodify(getcwd(2, 2), ':t'))
+ call assert_equal('Xtabdir3', fnamemodify(getcwd(-1, 3), ':t'))
+ call assert_equal('Xtabdir3', fnamemodify(getcwd(1, 3), ':t'))
+ call assert_equal('Xwindir3', fnamemodify(getcwd(2, 3), ':t'))
+ %bwipe
+ call chdir(save_cwd)
+ call delete("Xtopdir", "rf")
+endfunc
+
func Test_mksession_blank_tabs()
tabnew
tabnew
@@ -275,21 +314,6 @@ func Test_mksession_blank_tabs()
call delete('Xtest_mks.out')
endfunc
-func Test_mksession_blank_windows()
- split
- split
- split
- 3 wincmd w
- mksession! Xtest_mks.out
- split
- split
- 2 wincmd w
- source Xtest_mks.out
- call assert_equal(4, winnr('$'), 'session restore should restore number of windows')
- call assert_equal(3, winnr(), 'session restore should restore the active window')
- call delete('Xtest_mks.out')
-endfunc
-
func Test_mksession_buffer_count()
set hidden
@@ -310,7 +334,7 @@ func Test_mksession_buffer_count()
call delete('Xbaz')
call delete('Xtest_mks.out')
%bwipe!
- set nohidden
+ set hidden&
endfunc
func Test_mksession_buffer_order()
@@ -341,7 +365,6 @@ endfunc
if has('extra_search')
func Test_mksession_hlsearch()
- set sessionoptions+=options
set hlsearch
mksession! Xtest_mks.out
nohlsearch
@@ -351,12 +374,133 @@ func Test_mksession_hlsearch()
mksession! Xtest_mks.out
source Xtest_mks.out
call assert_equal(0, v:hlsearch, 'session should restore search highlighting state')
- set sessionoptions&
call delete('Xtest_mks.out')
endfunc
endif
+
+func Test_mksession_blank_windows()
+ split
+ split
+ split
+ 3 wincmd w
+ mksession! Xtest_mks.out
+ split
+ split
+ 2 wincmd w
+ source Xtest_mks.out
+ call assert_equal(4, winnr('$'), 'session restore should restore number of windows')
+ call assert_equal(3, winnr(), 'session restore should restore the active window')
+ call delete('Xtest_mks.out')
+endfunc
+
+if has('terminal')
+
+func Test_mksession_terminal_shell()
+ CheckFeature quickfix
+
+ terminal
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ let term_cmd = line
+ elseif line =~ 'badd.*' . &shell
+ call assert_report('unexpected shell line: ' . line)
+ endif
+ endfor
+ call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+\s*.*$', term_cmd)
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_cmdarg()
+ terminal ++norestore
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_funcarg()
+ call term_start(&shell, {'norestore': 1})
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_restore_func()
+ terminal
+ call term_setrestore(bufnr('%'), 'NONE')
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+func Test_mksession_terminal_no_ssop()
+ terminal
+ set sessionoptions-=terminal
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ call assert_report('session must not restore terminal')
+ endif
+ endfor
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+func Test_mksession_terminal_restore_other()
+ CheckFeature quickfix
+
+ terminal
+ eval bufnr('%')->term_setrestore('other')
+ mksession! Xtest_mks.out
+ let lines = readfile('Xtest_mks.out')
+ let term_cmd = ''
+ for line in lines
+ if line =~ '^terminal'
+ let term_cmd = line
+ endif
+ endfor
+ call assert_match('terminal ++curwin ++cols=\d\+ ++rows=\d\+.*other', term_cmd)
+
+ call StopShellInTerminal(bufnr('%'))
+ call delete('Xtest_mks.out')
+endfunc
+
+endif " has('terminal')
+
func Test_mkview_open_folds()
enew!
@@ -641,6 +785,7 @@ endfunc
" Test for changing directory to the session file directory
func Test_mksession_sesdir()
+ let save_cwd = getcwd()
call mkdir('Xproj')
mksession! Xproj/Xtest_mks1.out
set sessionoptions-=curdir
@@ -651,12 +796,42 @@ func Test_mksession_sesdir()
call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
source Xproj/Xtest_mks2.out
call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
- cd ..
+ call chdir(save_cwd)
+ %bwipe
set sessionoptions&
call delete('Xproj', 'rf')
endfunc
+" Test for saving and restoring the tab-local working directory when there is
+" only a single tab and 'tabpages' is not in 'sessionoptions'.
+func Test_mksession_tcd_single_tabs()
+ only | tabonly
+
+ let save_cwd = getcwd()
+ set sessionoptions-=tabpages
+ set sessionoptions+=curdir
+ call mkdir('Xtopdir1')
+ call mkdir('Xtopdir2')
+
+ " There are two tab pages, the current one has local cwd set to 'Xtopdir2'.
+ exec 'tcd ' .. save_cwd .. '/Xtopdir1'
+ tabnew
+ exec 'tcd ' .. save_cwd .. '/Xtopdir2'
+ mksession! Xtest_tcd_single
+
+ source Xtest_tcd_single
+ " call assert_equal(2, haslocaldir())
+ call assert_equal(1, haslocaldir(-1))
+ call assert_equal('Xtopdir2', fnamemodify(getcwd(-1, 0), ':t'))
+ %bwipe
+
+ set sessionoptions&
+ call chdir(save_cwd)
+ call delete('Xtopdir1', 'rf')
+ call delete('Xtopdir2', 'rf')
+endfunc
+
" Test for storing the 'lines' and 'columns' settings
func Test_mksession_resize()
mksession! Xtest_mks1.out
@@ -689,7 +864,6 @@ endfunc
" Test for mksession with a named scratch buffer
func Test_mksession_scratch()
- set sessionoptions+=options
enew | only
file Xscratch
set buftype=nofile
@@ -700,7 +874,6 @@ func Test_mksession_scratch()
call assert_equal('nofile', &buftype)
%bwipe
call delete('Xtest_mks.out')
- set sessionoptions&
endfunc
" Test for mksession with fold options
@@ -731,6 +904,7 @@ func Test_mksession_foldopt()
close
%bwipe
set sessionoptions&
+ call delete('Xtest_mks.out')
endfunc
" Test for mksession with "help" but not "options" in 'sessionoptions'
@@ -857,14 +1031,14 @@ func Test_mksession_shortmess_with_A()
bwipe!
" Recreate the swap file to pretend the file is being edited
- call writefile(cont, fname)
+ call writefile(cont, fname, 'D')
set shortmess+=A
source Xtestsession
set shortmess&
set sessionoptions&
call delete('Xtestsession')
- call delete(fname)
+ call delete('Xtestfile')
endfunc
" Test for mksession with 'compatible' option
@@ -943,15 +1117,16 @@ func Test_mkvimrc()
" the 'pastetoggle', 'wildchar' and 'wildcharm' option values should be
" stored as key names in the vimrc file
- set pastetoggle=<F5>
+ " set pastetoggle=<F5>
set wildchar=<F6>
set wildcharm=<F7>
call assert_fails('mkvimrc Xtestvimrc')
mkvimrc! Xtestvimrc
- call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>'))
+ " call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set pastetoggle=<F5>'))
call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildchar=<F6>'))
call assert_notequal(-1, index(readfile('Xtestvimrc'), 'set wildcharm=<F7>'))
- set pastetoggle& wildchar& wildcharm&
+ " set pastetoggle& wildchar& wildcharm&
+ set wildchar& wildcharm&
call delete('Xtestvimrc')
endfunc
diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/test/old/testdir/test_mksession_utf8.vim
index 4e593cc21a..4e593cc21a 100644
--- a/src/nvim/testdir/test_mksession_utf8.vim
+++ b/test/old/testdir/test_mksession_utf8.vim
diff --git a/src/nvim/testdir/test_modeline.vim b/test/old/testdir/test_modeline.vim
index 613722fdbd..487a89e038 100644
--- a/src/nvim/testdir/test_modeline.vim
+++ b/test/old/testdir/test_modeline.vim
@@ -172,6 +172,8 @@ func Test_modeline_colon()
endfunc
func s:modeline_fails(what, text, error)
+ " Don't use CheckOption(), it would skip the whole test
+ " just for a single un-supported option
if !exists('+' .. a:what)
return
endif
diff --git a/src/nvim/testdir/test_move.vim b/test/old/testdir/test_move.vim
index 8c40369dbd..40d75d887e 100644
--- a/src/nvim/testdir/test_move.vim
+++ b/test/old/testdir/test_move.vim
@@ -1,5 +1,8 @@
" Test the ":move" command.
+source check.vim
+source screendump.vim
+
func Test_move()
enew!
call append(0, ['line 1', 'line 2', 'line 3'])
@@ -43,4 +46,25 @@ func Test_move()
%bwipeout!
endfunc
+func Test_move_undo()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ call setline(1, ['First', 'Second', 'Third', 'Fourth'])
+ END
+ call writefile(lines, 'Xtest_move_undo.vim', 'D')
+ let buf = RunVimInTerminal('-S Xtest_move_undo.vim', #{rows: 10, cols: 60, statusoff: 2})
+
+ call term_sendkeys(buf, "gg:move +1\<CR>")
+ call VerifyScreenDump(buf, 'Test_move_undo_1', {})
+
+ " here the display would show the last few lines scrolled down
+ call term_sendkeys(buf, "u")
+ call term_sendkeys(buf, ":\<Esc>")
+ call VerifyScreenDump(buf, 'Test_move_undo_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_nested_function.vim b/test/old/testdir/test_nested_function.vim
index afaaea6ceb..5599655461 100644
--- a/src/nvim/testdir/test_nested_function.vim
+++ b/test/old/testdir/test_nested_function.vim
@@ -1,5 +1,7 @@
-"Tests for nested functions
-"
+" Tests for nested functions
+
+source check.vim
+
func NestedFunc()
func! Func1()
let g:text .= 'Func1 '
@@ -48,6 +50,9 @@ func Recurse(count)
endfunc
func Test_max_nesting()
+ " TODO: why does this fail on Windows? Runs out of stack perhaps?
+ CheckNotMSWindows
+
let call_depth_here = 2
let ex_depth_here = 5
set mfd&
@@ -61,3 +66,5 @@ func Test_max_nesting()
set mfd&
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_normal.vim b/test/old/testdir/test_normal.vim
index 2aaa1ff830..6a8c15bd48 100644
--- a/src/nvim/testdir/test_normal.vim
+++ b/test/old/testdir/test_normal.vim
@@ -250,9 +250,10 @@ func Test_normal_formatexpr_returns_nonzero()
setlocal formatexpr=Format()
normal VGgq
call assert_equal(['one two'], getline(1, '$'))
+
setlocal formatexpr=
delfunc Format
- close!
+ bwipe!
endfunc
" Test for using a script-local function for 'formatexpr'
@@ -346,7 +347,7 @@ func Test_normal06_formatprg()
CheckNotMSWindows
" uses sed to number non-empty lines
- call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh')
+ call writefile(['#!/bin/sh', 'sed ''/./=''|sed ''/./{', 'N', 's/\n/ /', '}'''], 'Xsed_format.sh', 'D')
call system('chmod +x ./Xsed_format.sh')
let text = ['a', '', 'c', '', ' ', 'd', 'e']
let expected = ['1 a', '', '3 c', '', '5 ', '6 d', '7 e']
@@ -377,11 +378,10 @@ func Test_normal06_formatprg()
" clean up
set formatprg=
setlocal formatprg=
- call delete('Xsed_format.sh')
endfunc
func Test_normal07_internalfmt()
- " basic test for internal formmatter to textwidth of 12
+ " basic test for internal formatter to textwidth of 12
let list=range(1,11)
call map(list, 'v:val." "')
10new
@@ -732,10 +732,10 @@ func Test_opfunc_callback()
bw!
# Test for using a script-local function name
- def s:LocalOpFunc(type: string): void
+ def LocalOpFunc(type: string): void
g:LocalOpFuncArgs = [type]
enddef
- &opfunc = s:LocalOpFunc
+ &opfunc = LocalOpFunc
g:LocalOpFuncArgs = []
normal! g@l
assert_equal(['char'], g:LocalOpFuncArgs)
@@ -921,7 +921,7 @@ func Test_normal14_page()
set nostartofline
exe "norm! $\<c-b>"
call assert_equal('92', getline('.'))
- call assert_equal([0, 92, 2, 0, 2147483647], getcurpos())
+ call assert_equal([0, 92, 2, 0, v:maxcol], getcurpos())
" cleanup
set startofline
bw!
@@ -966,7 +966,7 @@ func Test_normal15_z_scroll_vert()
norm! >>$ztzb
call assert_equal(' 30', getline('.'))
call assert_equal(30, winsaveview()['topline']+winheight(0)-1)
- call assert_equal([0, 30, 3, 0, 2147483647], getcurpos())
+ call assert_equal([0, 30, 3, 0, v:maxcol], getcurpos())
" Test for z-
1
@@ -1329,7 +1329,7 @@ func Test_vert_scroll_cmds()
call assert_equal(15, line('w$'))
set foldenable&
- close!
+ bwipe!
endfunc
func Test_scroll_in_ex_mode()
@@ -1806,30 +1806,21 @@ func Test_normal22_zet()
" Test for ZZ
" let shell = &shell
" let &shell = 'sh'
-
- " Remove any stale test files from previous run.
- for file in ['Xfile_Test_normal22_zet']
- call delete(file)
- endfor
-
- call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
+ call writefile(['1', '2'], 'Xn22file', 'D')
let args = ' -N -i NONE --noplugins -X --headless'
- call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xfile_Test_normal22_zet')
- let a = readfile('Xfile_Test_normal22_zet')
+ call system(GetVimCommand() .. args .. ' -c "%d" -c ":norm! ZZ" Xn22file')
+ let a = readfile('Xn22file')
call assert_equal([], a)
" Test for ZQ
- call writefile(['1', '2'], 'Xfile_Test_normal22_zet')
- call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xfile_Test_normal22_zet')
- let a = readfile('Xfile_Test_normal22_zet')
+ call writefile(['1', '2'], 'Xn22file')
+ call system(GetVimCommand() . args . ' -c "%d" -c ":norm! ZQ" Xn22file')
+ let a = readfile('Xn22file')
call assert_equal(['1', '2'], a)
" Unsupported Z command
call assert_beeps('normal! ZW')
- " Nvim: This sometimes hangs the TSAN build.
- " for file in ['Xfile_Test_normal22_zet']
- " call delete(file)
- " endfor
+ " clean up
" let &shell = shell
endfunc
@@ -1932,6 +1923,8 @@ func Test_normal24_rot13()
endfunc
func Test_normal25_tag()
+ CheckFeature quickfix
+
" Testing for CTRL-] g CTRL-] g]
" CTRL-W g] CTRL-W CTRL-] CTRL-W g CTRL-]
h
@@ -2254,61 +2247,63 @@ func Test_normal29_brace()
" set cpo+={
" 1
" norm! 0d2}
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
- " .IP
- " The nroff macros IP separates a paragraph
- " That means, it must be a '.'
- " followed by IP
- " .LPIt does not matter, if afterwards some
- " more characters follow.
- " .SHAlso section boundaries from the nroff
- " macros terminate a paragraph. That means
- " a character like this:
- " .NH
- " End of text here
- "
- " [DATA]
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
" call assert_equal(expected, getline(1, '$'))
- "
+
" $
" norm! d}
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
- " .IP
- " The nroff macros IP separates a paragraph
- " That means, it must be a '.'
- " followed by IP
- " .LPIt does not matter, if afterwards some
- " more characters follow.
- " .SHAlso section boundaries from the nroff
- " macros terminate a paragraph. That means
- " a character like this:
- " .NH
- " End of text here
- "
- " [DATA]
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+ .IP
+ The nroff macros IP separates a paragraph
+ That means, it must be a '.'
+ followed by IP
+ .LPIt does not matter, if afterwards some
+ more characters follow.
+ .SHAlso section boundaries from the nroff
+ macros terminate a paragraph. That means
+ a character like this:
+ .NH
+ End of text here
+
+ [DATA]
" call assert_equal(expected, getline(1, '$'))
- "
+
" norm! gg}
" norm! d5}
- "
- " let expected =<< trim [DATA]
- " {
- " This is no paragraph
- " unless the '{' is set
- " in 'cpoptions'
- " }
-
- " [DATA]
+
+ let expected =<< trim [DATA]
+ {
+ This is no paragraph
+ unless the '{' is set
+ in 'cpoptions'
+ }
+
+ [DATA]
" call assert_equal(expected, getline(1, '$'))
" Jumping to a fold should open the fold
@@ -2348,7 +2343,7 @@ func Test_normal_section()
call assert_equal(2, line('.'))
call assert_equal(-1, foldclosedend(line('.')))
- close!
+ bwipe!
endfunc
" Test for changing case using u, U, gu, gU and ~ (tilde) commands
@@ -2445,7 +2440,8 @@ func Test_normal_changecase_turkish()
" can't use Turkish locale
throw 'Skipped: Turkish locale not available'
endtry
- close!
+
+ bwipe!
endfunc
" Test for r (replace) command
@@ -2522,7 +2518,6 @@ endfunc
" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G,
" gi and gI commands
func Test_normal33_g_cmd2()
- CheckFeature jumplist
call Setup_NewWindow()
" Test for g`
clearjumps
@@ -2538,6 +2533,11 @@ func Test_normal33_g_cmd2()
norm! g'a
call assert_equal('>', a[-1:])
call assert_equal(1, line('.'))
+ let v:errmsg = ''
+ call assert_nobeep("normal! g`\<Esc>")
+ call assert_equal('', v:errmsg)
+ call assert_nobeep("normal! g'\<Esc>")
+ call assert_equal('', v:errmsg)
" Test for g; and g,
norm! g;
@@ -2589,7 +2589,7 @@ func Test_normal33_g_cmd2()
exe "norm! G0\<c-v>4k4ly"
exe "norm! gvood"
call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$'))
- " gv cannot be used in operator pending mode
+ " gv cannot be used in operator pending mode
call assert_beeps('normal! cgv')
" gv should beep without a previously selected visual area
new
@@ -2910,7 +2910,7 @@ func Test_normal36_g_cmd5()
call assert_equal([0, 14, 1, 0, 1], getcurpos())
" count > buffer content
norm! 120go
- call assert_equal([0, 14, 1, 0, 2147483647], getcurpos())
+ call assert_equal([0, 14, 1, 0, v:maxcol], getcurpos())
" clean up
bw!
endfunc
@@ -2975,7 +2975,8 @@ func Test_normal_nvend()
call assert_equal([4, 5], [line('.'), col('.')])
exe "normal! \<C-End>"
call assert_equal([10, 6], [line('.'), col('.')])
- close!
+
+ bwipe!
endfunc
" Test for cw cW ce
@@ -3090,7 +3091,7 @@ func Test_normal42_halfpage()
set nostartofline
exe "norm! $\<c-u>"
call assert_equal('95', getline('.'))
- call assert_equal([0, 95, 2, 0, 2147483647], getcurpos())
+ call assert_equal([0, 95, 2, 0, v:maxcol], getcurpos())
" cleanup
set startofline
bw!
@@ -3281,9 +3282,9 @@ func Test_delete_until_paragraph()
endfunc
" Test for the gr (virtual replace) command
-" Test for the bug fixed by 7.4.387
func Test_gr_command()
enew!
+ " Test for the bug fixed by 7.4.387
let save_cpo = &cpo
call append(0, ['First line', 'Second line', 'Third line'])
exe "normal i\<C-G>u"
@@ -3296,10 +3297,12 @@ func Test_gr_command()
normal 4gro
call assert_equal('ooooecond line', getline(2))
let &cpo = save_cpo
+
normal! ggvegrx
call assert_equal('xxxxx line', getline(1))
exe "normal! gggr\<C-V>122"
call assert_equal('zxxxx line', getline(1))
+
set virtualedit=all
normal! 15|grl
call assert_equal('zxxxx line l', getline(1))
@@ -3307,8 +3310,26 @@ func Test_gr_command()
set nomodifiable
call assert_fails('normal! grx', 'E21:')
call assert_fails('normal! gRx', 'E21:')
+ call assert_nobeep("normal! gr\<Esc>")
set modifiable&
- enew!
+
+ call assert_nobeep("normal! gr\<Esc>")
+ call assert_nobeep("normal! cgr\<Esc>")
+ call assert_beeps("normal! cgrx")
+
+ call assert_equal('zxxxx line l', getline(1))
+ exe "normal! 2|gr\<C-V>\<Esc>"
+ call assert_equal("z\<Esc>xx line l", getline(1))
+
+ call setline(1, 'abcdef')
+ exe "normal! 0gr\<C-O>lx"
+ call assert_equal("\<C-O>def", getline(1))
+
+ call setline(1, 'abcdef')
+ exe "normal! 0gr\<C-G>lx"
+ call assert_equal("\<C-G>def", getline(1))
+
+ bwipe!
endfunc
func Test_nv_hat_count()
@@ -3452,12 +3473,11 @@ func Test_java_motion()
call assert_equal([7, 8, 15], [line('.'), col('.'), virtcol('.')])
call assert_equal(-1, foldclosedend(7))
- close!
+ bwipe!
endfunc
" Tests for g cmds
func Test_normal_gdollar_cmd()
- CheckFeature jumplist
call Setup_NewWindow()
" Make long lines that will wrap
%s/$/\=repeat(' foobar', 10)/
@@ -3568,7 +3588,8 @@ func Test_normal_yank_with_excmd()
let @a = ''
call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt')
call assert_equal('f', @a)
- close!
+
+ bwipe!
endfunc
" Test for supplying a count to a normal-mode command across a cursorhold call
@@ -3590,7 +3611,8 @@ func Test_normal_cursorhold_with_count()
au!
augroup END
au! normalcHoldTest
- close!
+
+ bwipe!
delfunc s:cHold
endfunc
@@ -3614,15 +3636,37 @@ func Test_horiz_motion()
call assert_equal(11, col('.'))
exe "normal! $\<C-BS>"
call assert_equal(10, col('.'))
- close!
+
+ bwipe!
endfunc
-" Test for using a : command in operator pending mode
+" Test for using a ":" command in operator pending mode
func Test_normal_colon_op()
new
call setline(1, ['one', 'two'])
call assert_beeps("normal! Gc:d\<CR>")
- close!
+ call assert_equal(['one'], getline(1, '$'))
+
+ call setline(1, ['one…two…three!'])
+ normal! $
+ " Using ":" as a movement is characterwise exclusive
+ call feedkeys("d:normal! F…\<CR>", 'xt')
+ call assert_equal(['one…two!'], getline(1, '$'))
+ " Check that redoing a command with 0x80 bytes works
+ call feedkeys('.', 'xt')
+ call assert_equal(['one!'], getline(1, '$'))
+
+ call setline(1, ['one', 'two', 'three', 'four', 'five'])
+ " Add this to the command history
+ call feedkeys(":normal! G0\<CR>", 'xt')
+ " Use :normal! with control characters in operator pending mode
+ call feedkeys("d:normal! \<C-V>\<C-P>\<C-V>\<C-P>\<CR>", 'xt')
+ call assert_equal(['one', 'two', 'five'], getline(1, '$'))
+ " Check that redoing a command with control characters works
+ call feedkeys('.', 'xt')
+ call assert_equal(['five'], getline(1, '$'))
+
+ bwipe!
endfunc
" Test for d and D commands
@@ -3647,7 +3691,7 @@ func Test_normal_delete_cmd()
call assert_fails('normal D', 'E21:')
call assert_fails('normal d$', 'E21:')
- close!
+ bwipe!
endfunc
" Test for deleting or changing characters across lines with 'whichwrap'
@@ -3667,7 +3711,8 @@ func Test_normal_op_across_lines()
call setline(1, ['one two', 'three four'])
exe "norm! $3x"
call assert_equal(['one twhree four'], getline(1, '$'))
- close!
+
+ bwipe!
set whichwrap&
endfunc
@@ -3705,23 +3750,54 @@ func Test_normal_word_move()
normal 3Gyb
call assert_equal("two\n ", @")
- close!
+ bwipe!
endfunc
" Test for 'scrolloff' with a long line that doesn't fit in the screen
-func Test_normal_scroloff()
+func Test_normal_scrolloff()
10new
- 80vnew
- call setline(1, repeat('a', 1000))
+ 60vnew
+ call setline(1, ' 1 ' .. repeat('a', 57)
+ \ .. ' 2 ' .. repeat('b', 57)
+ \ .. ' 3 ' .. repeat('c', 57)
+ \ .. ' 4 ' .. repeat('d', 57)
+ \ .. ' 5 ' .. repeat('e', 57)
+ \ .. ' 6 ' .. repeat('f', 57)
+ \ .. ' 7 ' .. repeat('g', 57)
+ \ .. ' 8 ' .. repeat('h', 57)
+ \ .. ' 9 ' .. repeat('i', 57)
+ \ .. '10 ' .. repeat('j', 57)
+ \ .. '11 ' .. repeat('k', 57)
+ \ .. '12 ' .. repeat('l', 57)
+ \ .. '13 ' .. repeat('m', 57)
+ \ .. '14 ' .. repeat('n', 57)
+ \ .. '15 ' .. repeat('o', 57)
+ \ .. '16 ' .. repeat('p', 57)
+ \ .. '17 ' .. repeat('q', 57)
+ \ .. '18 ' .. repeat('r', 57)
+ \ .. '19 ' .. repeat('s', 57)
+ \ .. '20 ' .. repeat('t', 57)
+ \ .. '21 ' .. repeat('u', 57)
+ \ .. '22 ' .. repeat('v', 57)
+ \ .. '23 ' .. repeat('w', 57)
+ \ .. '24 ' .. repeat('x', 57)
+ \ .. '25 ' .. repeat('y', 57)
+ \ .. '26 ' .. repeat('z', 57)
+ \ )
set scrolloff=10
normal gg10gj
- call assert_equal(8, winline())
+ call assert_equal(6, winline())
normal 10gj
- call assert_equal(10, winline())
+ call assert_equal(6, winline())
normal 10gk
- call assert_equal(3, winline())
+ call assert_equal(6, winline())
+ normal 0
+ call assert_equal(1, winline())
+ normal $
+ call assert_equal(10, winline())
+
set scrolloff&
- close!
+ bwipe!
endfunc
" Test for vertical scrolling with CTRL-F and CTRL-B with a long line
@@ -3741,7 +3817,8 @@ func Test_normal_vert_scroll_longline()
exe "normal \<C-B>\<C-B>"
call assert_equal(5, line('.'))
call assert_equal(5, winline())
- close!
+
+ bwipe!
endfunc
" Test for jumping in a file using %
@@ -3754,7 +3831,8 @@ func Test_normal_percent_jump()
call feedkeys('50%', 'xt')
call assert_equal(50, line('.'))
call assert_equal(-1, foldclosedend(50))
- close!
+
+ bwipe!
endfunc
" Test for << and >> commands to shift text by 'shiftwidth'
@@ -3847,31 +3925,248 @@ func Test_mouse_shape_after_failed_change()
CheckCanRunGui
let lines =<< trim END
+ vim9script
set mouseshape+=o:busy
setlocal nomodifiable
- let g:mouse_shapes = []
+ var mouse_shapes = []
+
+ feedkeys('c')
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ feedkeys('c')
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ writefile(mouse_shapes, 'Xmouseshapes')
+ quit
+ })
+ })
+ })
+ })
+ END
+ call writefile(lines, 'Xmouseshape.vim', 'D')
+ call RunVim([], [], "-g -S Xmouseshape.vim")
+ sleep 300m
+ call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes'))
- func SaveMouseShape(timer)
- let g:mouse_shapes += [getmouseshape()]
- endfunc
+ call delete('Xmouseshapes')
+endfunc
- func SaveAndQuit(timer)
- call writefile(g:mouse_shapes, 'Xmouseshapes')
- quit
- endfunc
+" Test that mouse shape is restored to Normal mode after cancelling "gr".
+func Test_mouse_shape_after_cancelling_gr()
+ CheckFeature mouseshape
+ CheckCanRunGui
- call timer_start(50, {_ -> feedkeys('c')})
- call timer_start(100, 'SaveMouseShape')
- call timer_start(150, {_ -> feedkeys('c')})
- call timer_start(200, 'SaveMouseShape')
- call timer_start(250, 'SaveAndQuit')
+ let lines =<< trim END
+ vim9script
+ var mouse_shapes = []
+
+ feedkeys('gr')
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ feedkeys("\<Esc>")
+ timer_start(50, (_) => {
+ mouse_shapes += [getmouseshape()]
+ timer_start(50, (_) => {
+ writefile(mouse_shapes, 'Xmouseshapes')
+ quit
+ })
+ })
+ })
+ })
END
call writefile(lines, 'Xmouseshape.vim', 'D')
call RunVim([], [], "-g -S Xmouseshape.vim")
sleep 300m
- call assert_equal(['busy', 'arrow'], readfile('Xmouseshapes'))
+ call assert_equal(['beam', 'arrow'], readfile('Xmouseshapes'))
call delete('Xmouseshapes')
endfunc
+" Test that "j" does not skip lines when scrolling below botline and
+" 'foldmethod' is not "manual".
+func Test_normal_j_below_botline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set number foldmethod=diff scrolloff=0
+ call setline(1, map(range(1, 9), 'repeat(v:val, 200)'))
+ norm Lj
+ END
+ call writefile(lines, 'XNormalJBelowBotline', 'D')
+ let buf = RunVimInTerminal('-S XNormalJBelowBotline', #{rows: 19, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_normal_j_below_botline', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test for r (replace) command with CTRL_V and CTRL_Q
+func Test_normal_r_ctrl_v_cmd()
+ new
+ call append(0, 'This is a simple test: abcd')
+ exe "norm! 1gg$r\<C-V>\<C-V>"
+ call assert_equal(['This is a simple test: abc', ''], getline(1,'$'))
+ exe "norm! 1gg$hr\<C-Q>\<C-Q>"
+ call assert_equal(['This is a simple test: ab', ''], getline(1,'$'))
+ exe "norm! 1gg$2hr\<C-V>x7e"
+ call assert_equal(['This is a simple test: a~', ''], getline(1,'$'))
+ exe "norm! 1gg$3hr\<C-Q>x7e"
+ call assert_equal(['This is a simple test: ~~', ''], getline(1,'$'))
+
+ if &encoding == 'utf-8'
+ exe "norm! 1gg$4hr\<C-V>u20ac"
+ call assert_equal(['This is a simple test:€~~', ''], getline(1,'$'))
+ exe "norm! 1gg$5hr\<C-Q>u20ac"
+ call assert_equal(['This is a simple test€€~~', ''], getline(1,'$'))
+ exe "norm! 1gg0R\<C-V>xff WAS \<esc>"
+ call assert_equal(['ÿ WAS a simple test€€~~', ''], getline(1,'$'))
+ exe "norm! 1gg0elR\<C-Q>xffNOT\<esc>"
+ call assert_equal(['ÿ WASÿNOT simple test€€~~', ''], getline(1,'$'))
+ endif
+
+ call setline(1, 'This is a simple test: abcd')
+ exe "norm! 1gg$gr\<C-V>\<C-V>"
+ call assert_equal(['This is a simple test: abc', ''], getline(1,'$'))
+ exe "norm! 1gg$hgr\<C-Q>\<C-Q>"
+ call assert_equal(['This is a simple test: ab ', ''], getline(1,'$'))
+ exe "norm! 1gg$2hgr\<C-V>x7e"
+ call assert_equal(['This is a simple test: a~ ', ''], getline(1,'$'))
+ exe "norm! 1gg$3hgr\<C-Q>x7e"
+ call assert_equal(['This is a simple test: ~~ ', ''], getline(1,'$'))
+
+ " clean up
+ bw!
+endfunc
+
+" Test clicking on a TAB or an unprintable character in Normal mode
+func Test_normal_click_on_ctrl_char()
+ let save_mouse = &mouse
+ set mouse=a
+ new
+
+ call setline(1, "a\<Tab>b\<C-K>c")
+ redraw
+ call Ntest_setmouse(1, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+ call Ntest_setmouse(1, 2)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 2, 0, 2], getcurpos())
+ call Ntest_setmouse(1, 3)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 2, 0, 3], getcurpos())
+ call Ntest_setmouse(1, 7)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 2, 0, 7], getcurpos())
+ call Ntest_setmouse(1, 8)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 2, 0, 8], getcurpos())
+ call Ntest_setmouse(1, 9)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 3, 0, 9], getcurpos())
+ call Ntest_setmouse(1, 10)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 4, 0, 10], getcurpos())
+ call Ntest_setmouse(1, 11)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 4, 0, 11], getcurpos())
+ call Ntest_setmouse(1, 12)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 5, 0, 12], getcurpos())
+ call Ntest_setmouse(1, 13)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 5, 0, 13], getcurpos())
+
+ bwipe!
+ let &mouse = save_mouse
+endfunc
+
+" Test clicking on a double-width character in Normal mode
+func Test_normal_click_on_double_width_char()
+ let save_mouse = &mouse
+ set mouse=a
+ new
+
+ call setline(1, "口口")
+ redraw
+ call Ntest_setmouse(1, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 1, 0, 1], getcurpos())
+ call Ntest_setmouse(1, 2)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 1, 0, 2], getcurpos())
+ call Ntest_setmouse(1, 3)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 4, 0, 3], getcurpos())
+ call Ntest_setmouse(1, 4)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 1, 4, 0, 4], getcurpos())
+
+ bwipe!
+ let &mouse = save_mouse
+endfunc
+
+func Test_normal_click_on_empty_line()
+ let save_mouse = &mouse
+ set mouse=a
+ botright new
+ call setline(1, ['', '', ''])
+ let row = win_screenpos(0)[0] + 2
+ 20vsplit
+ redraw
+
+ call Ntest_setmouse(row, 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 1], getcurpos())
+ call Ntest_setmouse(row, 2)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 2], getcurpos())
+ call Ntest_setmouse(row, 10)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 10], getcurpos())
+
+ call Ntest_setmouse(row, 21 + 1)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 1], getcurpos())
+ call Ntest_setmouse(row, 21 + 2)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 2], getcurpos())
+ call Ntest_setmouse(row, 21 + 10)
+ call feedkeys("\<LeftMouse>", 'xt')
+ call assert_equal([0, 3, 1, 0, 10], getcurpos())
+
+ bwipe!
+ let &mouse = save_mouse
+endfunc
+
+func Test_normal33_g_cmd_nonblank()
+ " Test that g<End> goes to the last non-blank char and g$ to the last
+ " visible column
+ 20vnew
+ setlocal nowrap nonumber signcolumn=no
+ call setline(1, ['fooo fooo fooo fooo fooo fooo fooo fooo '])
+ exe "normal 0g\<End>"
+ call assert_equal(11, col('.'))
+ normal 0g$
+ call assert_equal(20, col('.'))
+ exe "normal 0g\<kEnd>"
+ call assert_equal(11, col('.'))
+ setlocal wrap
+ exe "normal 0g\<End>"
+ call assert_equal(11, col('.'))
+ normal 0g$
+ call assert_equal(20, col('.'))
+ exe "normal 0g\<kEnd>"
+ call assert_equal(11, col('.'))
+ bw!
+endfunc
+
+func Test_normal34_zet_large()
+ " shouldn't cause overflow
+ norm! z9765405999999999999
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_number.vim b/test/old/testdir/test_number.vim
index 521b0cf706..1232db80c5 100644
--- a/src/nvim/testdir/test_number.vim
+++ b/test/old/testdir/test_number.vim
@@ -138,7 +138,7 @@ func Test_number_with_linewrap1()
call s:validate_cursor()
let lines = s:screen_lines(1, 3)
let expect = [
-\ "--1 aaaa",
+\ "<<< aaaa",
\ " aaaa",
\ " aaaa",
\ ]
@@ -280,7 +280,7 @@ func Test_relativenumber_colors()
" Check that the balloon shows up after a mouse move
let buf = RunVimInTerminal('-S XTest_relnr', {'rows': 10, 'cols': 50})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
" Default colors
call VerifyScreenDump(buf, 'Test_relnr_colors_1', {})
diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim
new file mode 100644
index 0000000000..d69828dc2e
--- /dev/null
+++ b/test/old/testdir/test_options.vim
@@ -0,0 +1,2212 @@
+" Test for options
+
+source check.vim
+source view_util.vim
+
+func Test_whichwrap()
+ set whichwrap=b,s
+ call assert_equal('b,s', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap+=h,l
+ call assert_equal('b,s,h,l', &whichwrap)
+
+ set whichwrap=h,h
+ call assert_equal('h', &whichwrap)
+
+ set whichwrap=h,h,h
+ call assert_equal('h', &whichwrap)
+
+ " " For compatibility with Vim 3.0 and before, number values are also
+ " " supported for 'whichwrap'
+ " set whichwrap=1
+ " call assert_equal('b', &whichwrap)
+ " set whichwrap=2
+ " call assert_equal('s', &whichwrap)
+ " set whichwrap=4
+ " call assert_equal('h,l', &whichwrap)
+ " set whichwrap=8
+ " call assert_equal('<,>', &whichwrap)
+ " set whichwrap=16
+ " call assert_equal('[,]', &whichwrap)
+ " set whichwrap=31
+ " call assert_equal('b,s,h,l,<,>,[,]', &whichwrap)
+
+ set whichwrap&
+endfunc
+
+func Test_isfname()
+ " This used to cause Vim to access uninitialized memory.
+ set isfname=
+ call assert_equal("~X", expand("~X"))
+ set isfname&
+ " Test for setting 'isfname' to an unsupported character
+ let save_isfname = &isfname
+ call assert_fails('exe $"set isfname+={"\u1234"}"', 'E474:')
+ call assert_equal(save_isfname, &isfname)
+endfunc
+
+" Test for getting the value of 'pastetoggle'
+func Test_pastetoggle()
+ throw "Skipped: 'pastetoggle' is removed from Nvim"
+ " character with K_SPECIAL byte
+ let &pastetoggle = '…'
+ call assert_equal('…', &pastetoggle)
+ call assert_equal("\n pastetoggle=…", execute('set pastetoggle?'))
+
+ " modified character with K_SPECIAL byte
+ let &pastetoggle = '<M-…>'
+ call assert_equal('<M-…>', &pastetoggle)
+ call assert_equal("\n pastetoggle=<M-…>", execute('set pastetoggle?'))
+
+ " illegal bytes
+ let str = ":\x7f:\x80:\x90:\xd0:"
+ let &pastetoggle = str
+ call assert_equal(str, &pastetoggle)
+ call assert_equal("\n pastetoggle=" .. strtrans(str), execute('set pastetoggle?'))
+
+ unlet str
+ set pastetoggle&
+endfunc
+
+func Test_wildchar()
+ " Empty 'wildchar' used to access invalid memory.
+ call assert_fails('set wildchar=', 'E521:')
+ call assert_fails('set wildchar=abc', 'E521:')
+ set wildchar=<Esc>
+ let a=execute('set wildchar?')
+ call assert_equal("\n wildchar=<Esc>", a)
+ set wildchar=27
+ let a=execute('set wildchar?')
+ call assert_equal("\n wildchar=<Esc>", a)
+ set wildchar&
+endfunc
+
+func Test_wildoptions()
+ set wildoptions=
+ set wildoptions+=tagfile
+ set wildoptions+=tagfile
+ call assert_equal('tagfile', &wildoptions)
+endfunc
+
+func Test_options_command()
+ let caught = 'ok'
+ try
+ options
+ catch
+ let caught = v:throwpoint . "\n" . v:exception
+ endtry
+ call assert_equal('ok', caught)
+
+ " Check if the option-window is opened horizontally.
+ wincmd j
+ call assert_notequal('option-window', bufname(''))
+ wincmd k
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the option-window vertically.
+ vert options
+ " Check if the option-window is opened vertically.
+ wincmd l
+ call assert_notequal('option-window', bufname(''))
+ wincmd h
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the option-window at the top.
+ set splitbelow
+ topleft options
+ call assert_equal(1, winnr())
+ close
+
+ " Open the option-window at the bottom.
+ set nosplitbelow
+ botright options
+ call assert_equal(winnr('$'), winnr())
+ close
+ set splitbelow&
+
+ " Open the option-window in a new tab.
+ tab options
+ " Check if the option-window is opened in a tab.
+ normal gT
+ call assert_notequal('option-window', bufname(''))
+ normal gt
+ call assert_equal('option-window', bufname(''))
+ " close option-window
+ close
+
+ " Open the options window browse
+ if has('browse')
+ browse set
+ call assert_equal('option-window', bufname(''))
+ close
+ endif
+endfunc
+
+func Test_path_keep_commas()
+ " Test that changing 'path' keeps two commas.
+ set path=foo,,bar
+ set path-=bar
+ set path+=bar
+ call assert_equal('foo,,bar', &path)
+
+ set path&
+endfunc
+
+func Test_path_too_long()
+ exe 'set path=' .. repeat('x', 10000)
+ call assert_fails('find x', 'E854:')
+ set path&
+endfunc
+
+func Test_signcolumn()
+ CheckFeature signs
+ call assert_equal("auto", &signcolumn)
+ set signcolumn=yes
+ set signcolumn=no
+ call assert_fails('set signcolumn=nope')
+endfunc
+
+func Test_filetype_valid()
+ set ft=valid_name
+ call assert_equal("valid_name", &filetype)
+ set ft=valid-name
+ call assert_equal("valid-name", &filetype)
+
+ call assert_fails(":set ft=wrong;name", "E474:")
+ call assert_fails(":set ft=wrong\\\\name", "E474:")
+ call assert_fails(":set ft=wrong\\|name", "E474:")
+ call assert_fails(":set ft=wrong/name", "E474:")
+ call assert_fails(":set ft=wrong\\\nname", "E474:")
+ call assert_equal("valid-name", &filetype)
+
+ exe "set ft=trunc\x00name"
+ call assert_equal("trunc", &filetype)
+endfunc
+
+func Test_syntax_valid()
+ if !has('syntax')
+ return
+ endif
+ set syn=valid_name
+ call assert_equal("valid_name", &syntax)
+ set syn=valid-name
+ call assert_equal("valid-name", &syntax)
+
+ call assert_fails(":set syn=wrong;name", "E474:")
+ call assert_fails(":set syn=wrong\\\\name", "E474:")
+ call assert_fails(":set syn=wrong\\|name", "E474:")
+ call assert_fails(":set syn=wrong/name", "E474:")
+ call assert_fails(":set syn=wrong\\\nname", "E474:")
+ call assert_equal("valid-name", &syntax)
+
+ exe "set syn=trunc\x00name"
+ call assert_equal("trunc", &syntax)
+endfunc
+
+func Test_keymap_valid()
+ if !has('keymap')
+ return
+ endif
+ call assert_fails(":set kmp=valid_name", "E544:")
+ call assert_fails(":set kmp=valid_name", "valid_name")
+ call assert_fails(":set kmp=valid-name", "E544:")
+ call assert_fails(":set kmp=valid-name", "valid-name")
+
+ call assert_fails(":set kmp=wrong;name", "E474:")
+ call assert_fails(":set kmp=wrong\\\\name", "E474:")
+ call assert_fails(":set kmp=wrong\\|name", "E474:")
+ call assert_fails(":set kmp=wrong/name", "E474:")
+ call assert_fails(":set kmp=wrong\\\nname", "E474:")
+
+ call assert_fails(":set kmp=trunc\x00name", "E544:")
+ call assert_fails(":set kmp=trunc\x00name", "trunc")
+endfunc
+
+func Test_wildchar_valid()
+ call assert_fails("set wildchar=<CR>", "E474:")
+ call assert_fails("set wildcharm=<C-C>", "E474:")
+endfunc
+
+func Check_dir_option(name)
+ " Check that it's possible to set the option.
+ exe 'set ' . a:name . '=/usr/share/dict/words'
+ call assert_equal('/usr/share/dict/words', eval('&' . a:name))
+ exe 'set ' . a:name . '=/usr/share/dict/words,/and/there'
+ call assert_equal('/usr/share/dict/words,/and/there', eval('&' . a:name))
+ exe 'set ' . a:name . '=/usr/share/dict\ words'
+ call assert_equal('/usr/share/dict words', eval('&' . a:name))
+
+ " Check rejecting weird characters.
+ call assert_fails("set " . a:name . "=/not&there", "E474:")
+ call assert_fails("set " . a:name . "=/not>there", "E474:")
+ call assert_fails("set " . a:name . "=/not.*there", "E474:")
+endfunc
+
+func Test_cinkeys()
+ " This used to cause invalid memory access
+ set cindent cinkeys=0
+ norm a
+ set cindent& cinkeys&
+endfunc
+
+func Test_dictionary()
+ call Check_dir_option('dictionary')
+endfunc
+
+func Test_thesaurus()
+ call Check_dir_option('thesaurus')
+endfun
+
+func Test_complete()
+ " Trailing single backslash used to cause invalid memory access.
+ set complete=s\
+ new
+ call feedkeys("i\<C-N>\<Esc>", 'xt')
+ bwipe!
+ call assert_fails('set complete=ix', 'E535:')
+ set complete&
+endfun
+
+func Test_set_completion()
+ call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ call feedkeys(":setlocal di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"setlocal dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ call feedkeys(":setglobal di\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"setglobal dictionary diff diffexpr diffopt digraph directory display', @:)
+
+ " Expand boolean options. When doing :set no<Tab> Vim prefixes the option
+ " names with "no".
+ call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set nodiff nodigraph', @:)
+
+ call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set invdiff invdigraph', @:)
+
+ " Expanding "set noinv" does nothing.
+ call feedkeys(":set noinv\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set noinv', @:)
+
+ " Expand abbreviation of options.
+ call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tabstop thesaurus thesaurusfunc', @:)
+
+ " Expand current value
+ call feedkeys(":set suffixes=\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set suffixes=.bak,~,.o,.h,.info,.swp,.obj', @:)
+
+ call feedkeys(":set suffixes:\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set suffixes:.bak,~,.o,.h,.info,.swp,.obj', @:)
+
+ " Expand key codes.
+ " call feedkeys(":set <H\<C-A>\<C-B>\"\<CR>", 'tx')
+ " call assert_equal('"set <Help> <Home>', @:)
+
+ " Expand terminal options.
+ " call feedkeys(":set t_A\<C-A>\<C-B>\"\<CR>", 'tx')
+ " call assert_equal('"set t_AB t_AF t_AU t_AL', @:)
+ " call assert_fails('call feedkeys(":set <t_afoo>=\<C-A>\<CR>", "xt")', 'E474:')
+
+ " Expand directories.
+ call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match(' ./samples/ ', @:)
+ call assert_notmatch(' ./summarize.vim ', @:)
+ set cdpath&
+
+ " Expand files and directories.
+ call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_match(' ./samples/.* ./summarize.vim', @:)
+
+ call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:)
+
+ " Expand files with spaces/commas in them. Make sure we delimit correctly.
+ "
+ " 'tags' allow for for spaces/commas to both act as delimiters, with actual
+ " spaces requiring double escape, and commas need a single escape.
+ " 'dictionary' is a normal comma-separated option where only commas act as
+ " delimiters, and both space/comma need one single escape.
+ " 'makeprg' is a non-comma-separated option. Commas don't need escape.
+ defer delete('Xfoo Xspace.txt')
+ defer delete('Xsp_dummy')
+ defer delete('Xbar,Xcomma.txt')
+ defer delete('Xcom_dummy')
+ call writefile([], 'Xfoo Xspace.txt')
+ call writefile([], 'Xsp_dummy')
+ call writefile([], 'Xbar,Xcomma.txt')
+ call writefile([], 'Xcom_dummy')
+
+ call feedkeys(':set tags=./Xfoo\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./Xfoo\ Xsp_dummy', @:)
+ call feedkeys(':set tags=./Xfoo\\\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set tags=./Xfoo\\\ Xspace.txt', @:)
+ call feedkeys(':set dictionary=./Xfoo\ Xsp' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=./Xfoo\ Xspace.txt', @:)
+
+ call feedkeys(':set dictionary=./Xbar,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=./Xbar,Xcom_dummy', @:)
+ if has('win32')
+ " In Windows, '\,' is literal, see `:help filename-backslash`, so this
+ " means we treat it as one file name.
+ call feedkeys(':set dictionary=Xbar\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\,Xcomma.txt', @:)
+ else
+ " In other platforms, '\,' simply escape to ',', and indicate a delimiter
+ " to split into a separate file name. You need '\\,' to escape the comma
+ " as part of the file name.
+ call feedkeys(':set dictionary=Xbar\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\,Xcom_dummy', @:)
+
+ call feedkeys(':set dictionary=Xbar\\,Xcom' .. "\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set dictionary=Xbar\\,Xcomma.txt', @:)
+ endif
+ call feedkeys(":set makeprg=./Xbar,Xcom\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set makeprg=./Xbar,Xcomma.txt', @:)
+ set tags& dictionary& makeprg&
+
+ " Expanding the option names
+ call feedkeys(":set \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set all', @:)
+
+ " Expanding a second set of option names
+ call feedkeys(":set wrapscan \<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set wrapscan all', @:)
+
+ " Expanding a special keycode
+ " call feedkeys(":set <Home>\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal('"set <Home>', @:)
+
+ " Expanding an invalid special keycode
+ call feedkeys(":set <abcd>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set <abcd>\<Tab>", @:)
+
+ " Expanding a terminal keycode
+ " call feedkeys(":set t_AB\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal("\"set t_AB", @:)
+
+ " Expanding an invalid option name
+ call feedkeys(":set abcde=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set abcde=\<Tab>", @:)
+
+ " Expanding after a = for a boolean option
+ call feedkeys(":set wrapscan=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set wrapscan=\<Tab>", @:)
+
+ " Expanding a numeric option
+ call feedkeys(":set tabstop+=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set tabstop+=" .. &tabstop, @:)
+
+ " Expanding a non-boolean option
+ call feedkeys(":set invtabstop=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set invtabstop=", @:)
+
+ " Expand options for 'spellsuggest'
+ call feedkeys(":set spellsuggest=file:test_options.v\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set spellsuggest=file:test_options.vim", @:)
+ call feedkeys(":set spellsuggest=best,file:test_options.v\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal("\"set spellsuggest=best,file:test_options.vim", @:)
+
+ " Expanding value for 'key' is disallowed
+ if exists('+key')
+ set key=abcd
+ call feedkeys(":set key=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set key=', @:)
+ call feedkeys(":set key-=\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set key-=', @:)
+ set key=
+ endif
+
+ " Expand values for 'filetype'
+ call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set filetype=sshdconfig', @:)
+ call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:)
+
+ " Expand values for 'syntax'
+ call feedkeys(":set syntax=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set syntax=sshdconfig', @:)
+ call feedkeys(":set syntax=a\<C-A>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set syntax=' .. getcompletion('a*', 'syntax')->join(), @:)
+endfunc
+
+" Test handling of expanding individual string option values
+func Test_set_completion_string_values()
+ "
+ " Test basic enum string options that have well-defined enum names
+ "
+
+ " call assert_equal(['lastline', 'truncate', 'uhex'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['truncate'], getcompletion('set display=t', 'cmdline'))
+ call assert_equal(['uhex'], getcompletion('set display=*ex*', 'cmdline'))
+
+ " Test that if a value is set, it will populate the results, but only if
+ " typed value is empty.
+ set display=uhex,lastline
+ " call assert_equal(['uhex,lastline', 'lastline', 'truncate', 'uhex'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['uhex,lastline', 'lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['uhex'], getcompletion('set display=u', 'cmdline'))
+ " If the set value is part of the enum list, it will show as the first
+ " result with no duplicate.
+ set display=uhex
+ " call assert_equal(['uhex', 'lastline', 'truncate'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['uhex', 'lastline', 'truncate', 'msgsep'], getcompletion('set display=', 'cmdline'))
+ " If empty value, will just show the normal list without an empty item
+ set display=
+ " call assert_equal(['lastline', 'truncate', 'uhex'], getcompletion('set display=', 'cmdline'))
+ call assert_equal(['lastline', 'truncate', 'uhex', 'msgsep'], getcompletion('set display=', 'cmdline'))
+ " Test escaping of the values
+ " call assert_equal('vert:\|,fold:-,eob:~,lastline:@', getcompletion('set fillchars=', 'cmdline')[0])
+ call assert_equal('vert:\|,foldsep:\|,fold:-', getcompletion('set fillchars=', 'cmdline')[0])
+
+ " Test comma-separated lists will expand after a comma.
+ call assert_equal(['uhex'], getcompletion('set display=truncate,*ex*', 'cmdline'))
+ " Also test the positioning of the expansion is correct
+ call feedkeys(":set display=truncate,l\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set display=truncate,lastline', @:)
+ set display&
+
+ " Test single-value options will not expand after a comma
+ call assert_equal([], getcompletion('set ambw=single,', 'cmdline'))
+
+ " Test the other simple options to make sure they have basic auto-complete,
+ " but don't exhaustively validate their results.
+ call assert_equal('single', getcompletion('set ambw=', 'cmdline')[0])
+ call assert_match('light\|dark', getcompletion('set bg=', 'cmdline')[1])
+ call assert_equal('indent', getcompletion('set backspace=', 'cmdline')[0])
+ call assert_equal('yes', getcompletion('set backupcopy=', 'cmdline')[1])
+ call assert_equal('backspace', getcompletion('set belloff=', 'cmdline')[1])
+ call assert_equal('min:', getcompletion('set briopt=', 'cmdline')[1])
+ if exists('+browsedir')
+ call assert_equal('current', getcompletion('set browsedir=', 'cmdline')[1])
+ endif
+ call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1])
+ call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1])
+ call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1])
+ if exists('+clipboard')
+ " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1])
+ call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[0])
+ endif
+ call assert_equal('.', getcompletion('set complete=', 'cmdline')[1])
+ call assert_equal('menu', getcompletion('set completeopt=', 'cmdline')[1])
+ if exists('+completeslash')
+ call assert_equal('backslash', getcompletion('set completeslash=', 'cmdline')[1])
+ endif
+ if exists('+cryptmethod')
+ call assert_equal('zip', getcompletion('set cryptmethod=', 'cmdline')[1])
+ endif
+ if exists('+cursorlineopt')
+ call assert_equal('line', getcompletion('set cursorlineopt=', 'cmdline')[1])
+ endif
+ call assert_equal('throw', getcompletion('set debug=', 'cmdline')[1])
+ call assert_equal('ver', getcompletion('set eadirection=', 'cmdline')[1])
+ call assert_equal('mac', getcompletion('set fileformat=', 'cmdline')[2])
+ if exists('+foldclose')
+ call assert_equal('all', getcompletion('set foldclose=', 'cmdline')[0])
+ endif
+ if exists('+foldmethod')
+ call assert_equal('expr', getcompletion('set foldmethod=', 'cmdline')[1])
+ endif
+ if exists('+foldopen')
+ call assert_equal('all', getcompletion('set foldopen=', 'cmdline')[1])
+ endif
+ call assert_equal('stack', getcompletion('set jumpoptions=', 'cmdline')[0])
+ call assert_equal('stopsel', getcompletion('set keymodel=', 'cmdline')[1])
+ call assert_equal('expr:1', getcompletion('set lispoptions=', 'cmdline')[1])
+ call assert_match('popup', getcompletion('set mousemodel=', 'cmdline')[2])
+ call assert_equal('bin', getcompletion('set nrformats=', 'cmdline')[1])
+ if exists('+rightleftcmd')
+ call assert_equal('search', getcompletion('set rightleftcmd=', 'cmdline')[0])
+ endif
+ call assert_equal('ver', getcompletion('set scrollopt=', 'cmdline')[1])
+ call assert_equal('exclusive', getcompletion('set selection=', 'cmdline')[1])
+ call assert_equal('key', getcompletion('set selectmode=', 'cmdline')[1])
+ if exists('+ssop')
+ call assert_equal('buffers', getcompletion('set ssop=', 'cmdline')[1])
+ endif
+ call assert_equal('statusline', getcompletion('set showcmdloc=', 'cmdline')[1])
+ if exists('+signcolumn')
+ call assert_equal('yes', getcompletion('set signcolumn=', 'cmdline')[1])
+ endif
+ if exists('+spelloptions')
+ call assert_equal('camel', getcompletion('set spelloptions=', 'cmdline')[0])
+ endif
+ if exists('+spellsuggest')
+ call assert_equal('best', getcompletion('set spellsuggest+=', 'cmdline')[0])
+ endif
+ call assert_equal('screen', getcompletion('set splitkeep=', 'cmdline')[1])
+ " call assert_equal('sync', getcompletion('set swapsync=', 'cmdline')[1])
+ call assert_equal('usetab', getcompletion('set switchbuf=', 'cmdline')[1])
+ call assert_equal('ignore', getcompletion('set tagcase=', 'cmdline')[1])
+ if exists('+termwintype')
+ call assert_equal('conpty', getcompletion('set termwintype=', 'cmdline')[1])
+ endif
+ if exists('+toolbar')
+ call assert_equal('text', getcompletion('set toolbar=', 'cmdline')[1])
+ endif
+ if exists('+tbis')
+ call assert_equal('medium', getcompletion('set tbis=', 'cmdline')[2])
+ endif
+ if exists('+ttymouse')
+ set ttymouse=
+ call assert_equal('xterm2', getcompletion('set ttymouse=', 'cmdline')[1])
+ set ttymouse&
+ endif
+ call assert_equal('insert', getcompletion('set virtualedit=', 'cmdline')[1])
+ call assert_equal('longest', getcompletion('set wildmode=', 'cmdline')[1])
+ call assert_equal('full', getcompletion('set wildmode=list,longest:', 'cmdline')[0])
+ call assert_equal('tagfile', getcompletion('set wildoptions=', 'cmdline')[1])
+ if exists('+winaltkeys')
+ call assert_equal('yes', getcompletion('set winaltkeys=', 'cmdline')[1])
+ endif
+
+ " Other string options that queries the system rather than fixed enum names
+ call assert_equal(['all', 'BufAdd'], getcompletion('set eventignore=', 'cmdline')[0:1])
+ call assert_equal('latin1', getcompletion('set fileencodings=', 'cmdline')[1])
+ " call assert_equal('top', getcompletion('set printoptions=', 'cmdline')[0])
+ " call assert_equal('SpecialKey', getcompletion('set wincolor=', 'cmdline')[0])
+
+ call assert_equal('eol', getcompletion('set listchars+=', 'cmdline')[0])
+ call assert_equal(['multispace', 'leadmultispace'], getcompletion('set listchars+=', 'cmdline')[-2:])
+ call assert_equal('eol', getcompletion('setl listchars+=', 'cmdline')[0])
+ call assert_equal(['multispace', 'leadmultispace'], getcompletion('setl listchars+=', 'cmdline')[-2:])
+ call assert_equal('stl', getcompletion('set fillchars+=', 'cmdline')[0])
+ call assert_equal('stl', getcompletion('setl fillchars+=', 'cmdline')[0])
+
+ "
+ " Unique string options below
+ "
+
+ " keyprotocol: only auto-complete when after ':' with known protocol types
+ " call assert_equal([&keyprotocol], getcompletion('set keyprotocol=', 'cmdline'))
+ " call feedkeys(":set keyprotocol+=someterm:m\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal('"set keyprotocol+=someterm:mok2', @:)
+ " set keyprotocol&
+
+ " previewpopup / completepopup
+ " call assert_equal('height:', getcompletion('set previewpopup=', 'cmdline')[0])
+ " call assert_equal('EndOfBuffer', getcompletion('set previewpopup=highlight:End*Buffer', 'cmdline')[0])
+ " call feedkeys(":set previewpopup+=border:\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal('"set previewpopup+=border:on', @:)
+ " call feedkeys(":set completepopup=height:10,align:\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal('"set completepopup=height:10,align:item', @:)
+ " call assert_equal([], getcompletion('set completepopup=bogusname:', 'cmdline'))
+ " set previewpopup& completepopup&
+
+ " diffopt: special handling of algorithm:<alg_list>
+ call assert_equal('filler', getcompletion('set diffopt+=', 'cmdline')[0])
+ call assert_equal([], getcompletion('set diffopt+=iblank,foldcolumn:', 'cmdline'))
+ call assert_equal('patience', getcompletion('set diffopt+=iblank,algorithm:pat*', 'cmdline')[0])
+
+ " highlight: special parsing, including auto-completing highlight groups
+ " after ':'
+ " call assert_equal([&hl, '8'], getcompletion('set hl=', 'cmdline')[0:1])
+ " call assert_equal('8', getcompletion('set hl+=', 'cmdline')[0])
+ " call assert_equal(['8:', '8b', '8i'], getcompletion('set hl+=8', 'cmdline')[0:2])
+ " call assert_equal('8bi', getcompletion('set hl+=8b', 'cmdline')[0])
+ " call assert_equal('NonText', getcompletion('set hl+=8:No*ext', 'cmdline')[0])
+ " If all the display modes are used up we should be suggesting nothing. Make
+ " a hl typed option with all the modes which will look like '8bi-nrsuc2d=t',
+ " and make sure nothing is suggested from that.
+ " let hl_display_modes = join(
+ " \ filter(map(getcompletion('set hl+=8', 'cmdline'),
+ " \ {idx, val -> val[1]}),
+ " \ {idx, val -> val != ':'}),
+ " \ '')
+ " call assert_equal([], getcompletion('set hl+=8'..hl_display_modes, 'cmdline'))
+ " Test completion in middle of the line
+ " call feedkeys(":set hl=8b i\<Left>\<Left>\<Tab>\<C-B>\"\<CR>", 'xt')
+ " call assert_equal("\"set hl=8bi i", @:)
+
+ "
+ " Test flag lists
+ "
+
+ " Test set=. Show the original value if nothing is typed after '='.
+ " Otherwise, the list should avoid showing what's already typed.
+ set mouse=v
+ call assert_equal(['v','a','n','i','c','h','r'], getcompletion('set mouse=', 'cmdline'))
+ set mouse=nvi
+ call assert_equal(['nvi','a','n','v','i','c','h','r'], getcompletion('set mouse=', 'cmdline'))
+ call assert_equal(['a','v','i','c','r'], getcompletion('set mouse=hn', 'cmdline'))
+
+ " Test set+=. Never show original value, and it also tries to avoid listing
+ " flags that's already in the option value.
+ call assert_equal(['a','c','h','r'], getcompletion('set mouse+=', 'cmdline'))
+ call assert_equal(['a','c','r'], getcompletion('set mouse+=hn', 'cmdline'))
+ call assert_equal([], getcompletion('set mouse+=acrhn', 'cmdline'))
+
+ " Test that the position of the expansion is correct (even if there are
+ " additional values after the current cursor)
+ call feedkeys(":set mouse=hn\<Left>\<Tab>\<C-B>\"\<CR>", 'xt')
+ call assert_equal('"set mouse=han', @:)
+ set mouse&
+
+ " Test that other flag list options have auto-complete, but don't
+ " exhaustively validate their results.
+ if exists('+concealcursor')
+ call assert_equal('n', getcompletion('set cocu=', 'cmdline')[0])
+ endif
+ call assert_equal('a', getcompletion('set cpo=', 'cmdline')[1])
+ call assert_equal('t', getcompletion('set fo=', 'cmdline')[1])
+ if exists('+guioptions')
+ call assert_equal('!', getcompletion('set go=', 'cmdline')[1])
+ endif
+ call assert_equal('r', getcompletion('set shortmess=', 'cmdline')[1])
+ call assert_equal('b', getcompletion('set whichwrap=', 'cmdline')[1])
+
+ "
+ "Test set-=
+ "
+
+ " Normal single-value option just shows the existing value
+ set ambiwidth=double
+ call assert_equal(['double'], getcompletion('set ambw-=', 'cmdline'))
+ set ambiwidth&
+
+ " Works on numbers and term options as well
+ call assert_equal([string(&laststatus)], getcompletion('set laststatus-=', 'cmdline'))
+ set t_Ce=testCe
+ " call assert_equal(['testCe'], getcompletion('set t_Ce-=', 'cmdline'))
+ set t_Ce&
+
+ " Comma-separated lists should present each option
+ set diffopt=context:123,,,,,iblank,iwhiteall
+ call assert_equal(['context:123', 'iblank', 'iwhiteall'], getcompletion('set diffopt-=', 'cmdline'))
+ call assert_equal(['context:123', 'iblank'], getcompletion('set diffopt-=*n*', 'cmdline'))
+ call assert_equal(['iblank', 'iwhiteall'], getcompletion('set diffopt-=i', 'cmdline'))
+ " Don't present more than one option as it doesn't make sense in set-=
+ call assert_equal([], getcompletion('set diffopt-=iblank,', 'cmdline'))
+ " Test empty option
+ set diffopt=
+ call assert_equal([], getcompletion('set diffopt-=', 'cmdline'))
+ set diffopt&
+
+ " Test escaping output
+ call assert_equal('vert:\|', getcompletion('set fillchars-=', 'cmdline')[0])
+
+ " Test files with commas in name are being parsed and escaped properly
+ set path=has\\\ space,file\\,with\\,comma,normal_file
+ if exists('+completeslash')
+ call assert_equal(['has\\\ space', 'file\,with\,comma', 'normal_file'], getcompletion('set path-=', 'cmdline'))
+ else
+ call assert_equal(['has\\\ space', 'file\\,with\\,comma', 'normal_file'], getcompletion('set path-=', 'cmdline'))
+ endif
+ set path&
+
+ " Flag list should present orig value, then individual flags
+ set mouse=v
+ call assert_equal(['v'], getcompletion('set mouse-=', 'cmdline'))
+ set mouse=avn
+ call assert_equal(['avn','a','v','n'], getcompletion('set mouse-=', 'cmdline'))
+ " Don't auto-complete when we have at least one flags already
+ call assert_equal([], getcompletion('set mouse-=n', 'cmdline'))
+ " Test empty option
+ set mouse=
+ call assert_equal([], getcompletion('set mouse-=', 'cmdline'))
+ set mouse&
+
+ " 'whichwrap' is an odd case where it's both flag list and comma-separated
+ set ww=b,h
+ call assert_equal(['b','h'], getcompletion('set ww-=', 'cmdline'))
+ set ww&
+endfunc
+
+func Test_set_option_errors()
+ call assert_fails('set scroll=-1', 'E49:')
+ call assert_fails('set backupcopy=', 'E474:')
+ call assert_fails('set regexpengine=3', 'E474:')
+ call assert_fails('set history=10001', 'E474:')
+ call assert_fails('set numberwidth=21', 'E474:')
+ call assert_fails('set colorcolumn=-a', 'E474:')
+ call assert_fails('set colorcolumn=a', 'E474:')
+ call assert_fails('set colorcolumn=1,', 'E474:')
+ call assert_fails('set colorcolumn=1;', 'E474:')
+ call assert_fails('set cmdheight=-1', 'E487:')
+ call assert_fails('set cmdwinheight=-1', 'E487:')
+ if has('conceal')
+ call assert_fails('set conceallevel=-1', 'E487:')
+ call assert_fails('set conceallevel=4', 'E474:')
+ endif
+ call assert_fails('set helpheight=-1', 'E487:')
+ call assert_fails('set history=-1', 'E487:')
+ call assert_fails('set report=-1', 'E487:')
+ 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('let &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:')
+ call assert_fails('set updatetime=-1', 'E487:')
+ call assert_fails('set winheight=-1', 'E487:')
+ call assert_fails('set tabstop!', 'E488:')
+ call assert_fails('set xxx', 'E518:')
+ call assert_fails('set beautify?', 'E518:')
+ call assert_fails('set undolevels=x', 'E521:')
+ call assert_fails('set tabstop=', 'E521:')
+ call assert_fails('set comments=-', 'E524:')
+ call assert_fails('set comments=a', 'E525:')
+ call assert_fails('set foldmarker=x', 'E536:')
+ call assert_fails('set commentstring=x', 'E537:')
+ call assert_fails('let &commentstring = "x"', 'E537:')
+ call assert_fails('set complete=x', 'E539:')
+ call assert_fails('set rulerformat=%-', 'E539:')
+ call assert_fails('set rulerformat=%(', 'E542:')
+ call assert_fails('set rulerformat=%15(%%', 'E542:')
+ call assert_fails('set statusline=%$', 'E539:')
+ call assert_fails('set statusline=%{', 'E540:')
+ call assert_fails('set statusline=%{%', 'E540:')
+ call assert_fails('set statusline=%{%}', 'E539:')
+ call assert_fails('set statusline=%(', 'E542:')
+ call assert_fails('set statusline=%)', 'E542:')
+ call assert_fails('set tabline=%$', 'E539:')
+ call assert_fails('set tabline=%{', 'E540:')
+ call assert_fails('set tabline=%{%', 'E540:')
+ call assert_fails('set tabline=%{%}', 'E539:')
+ call assert_fails('set tabline=%(', 'E542:')
+ call assert_fails('set tabline=%)', 'E542:')
+
+ if has('cursorshape')
+ " This invalid value for 'guicursor' used to cause Vim to crash.
+ call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:')
+ call assert_fails('set guicursor=i-ci', 'E545:')
+ call assert_fails('set guicursor=x', 'E545:')
+ call assert_fails('set guicursor=x:', 'E546:')
+ call assert_fails('set guicursor=r-cr:horx', 'E548:')
+ call assert_fails('set guicursor=r-cr:hor0', 'E549:')
+ endif
+ if has('mouseshape')
+ call assert_fails('se mouseshape=i-r:x', 'E547:')
+ endif
+
+ " Test for 'backupext' and 'patchmode' set to the same value
+ set backupext=.bak
+ set patchmode=.patch
+ call assert_fails('set patchmode=.bak', 'E589:')
+ call assert_equal('.patch', &patchmode)
+ call assert_fails('set backupext=.patch', 'E589:')
+ call assert_equal('.bak', &backupext)
+ set backupext& patchmode&
+
+ call assert_fails('set winminheight=10 winheight=9', 'E591:')
+ set winminheight& winheight&
+ set winheight=10 winminheight=10
+ call assert_fails('set winheight=9', 'E591:')
+ set winminheight& winheight&
+ call assert_fails('set winminwidth=10 winwidth=9', 'E592:')
+ set winminwidth& winwidth&
+ call assert_fails('set winwidth=9 winminwidth=10', 'E592:')
+ set winwidth& winminwidth&
+ call assert_fails("set showbreak=\x01", 'E595:')
+ call assert_fails('set t_foo=', 'E846:')
+ call assert_fails('set tabstop??', 'E488:')
+ call assert_fails('set wrapscan!!', 'E488:')
+ call assert_fails('set tabstop&&', 'E488:')
+ call assert_fails('set wrapscan<<', 'E488:')
+ call assert_fails('set wrapscan=1', 'E474:')
+ call assert_fails('set autoindent@', 'E488:')
+ call assert_fails('set wildchar=<abc>', 'E474:')
+ call assert_fails('set cmdheight=1a', 'E521:')
+ call assert_fails('set invcmdheight', 'E474:')
+ if has('python') || has('python3')
+ call assert_fails('set pyxversion=6', 'E474:')
+ endif
+ call assert_fails("let &tabstop='ab'", ['E521:', 'E521:'])
+ call assert_fails('set spellcapcheck=%\\(', 'E54:')
+ call assert_fails('set sessionoptions=curdir,sesdir', 'E474:')
+ call assert_fails('set foldmarker={{{,', 'E474:')
+ call assert_fails('set sessionoptions=sesdir,curdir', 'E474:')
+ setlocal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
+ setlocal listchars=trail:-
+ setglobal listchars=trail:·
+ call assert_fails('set ambiwidth=double', 'E834:')
+ set listchars&
+ setlocal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
+ setlocal fillchars=stl:-
+ setglobal fillchars=stl:·
+ call assert_fails('set ambiwidth=double', 'E835:')
+ set fillchars&
+ call assert_fails('set fileencoding=latin1,utf-8', 'E474:')
+ set nomodifiable
+ call assert_fails('set fileencoding=latin1', 'E21:')
+ set modifiable&
+ " call assert_fails('set t_#-&', 'E522:')
+ call assert_fails('let &formatoptions = "?"', 'E539:')
+ call assert_fails('call setbufvar("", "&formatoptions", "?")', 'E539:')
+ call assert_fails('call setwinvar(0, "&scrolloff", [])', ['E745:', 'E745:'])
+ call assert_fails('call setwinvar(0, "&list", [])', ['E745:', 'E745:'])
+ call assert_fails('call setwinvar(0, "&listchars", [])', ['E730:', 'E730:'])
+ call assert_fails('call setwinvar(0, "&nosuchoption", 0)', ['E355:', 'E355:'])
+ call assert_fails('call setwinvar(0, "&nosuchoption", "")', ['E355:', 'E355:'])
+ call assert_fails('call setwinvar(0, "&nosuchoption", [])', ['E355:', 'E355:'])
+endfunc
+
+func CheckWasSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_match('Last set from.*test_options.vim', verb_cm)
+endfunc
+func CheckWasNotSet(name)
+ let verb_cm = execute('verbose set ' .. a:name .. '?')
+ call assert_notmatch('Last set from', verb_cm)
+endfunc
+
+" Must be executed before other tests that set 'term'.
+func Test_000_term_option_verbose()
+ throw "Skipped: Nvim does not support setting 'term'"
+ CheckNotGui
+
+ call CheckWasNotSet('t_cm')
+
+ let term_save = &term
+ set term=ansi
+ call CheckWasSet('t_cm')
+ let &term = term_save
+endfunc
+
+func Test_copy_context()
+ setlocal list
+ call CheckWasSet('list')
+ split
+ call CheckWasSet('list')
+ quit
+ setlocal nolist
+
+ set ai
+ call CheckWasSet('ai')
+ set filetype=perl
+ call CheckWasSet('filetype')
+ set fo=tcroq
+ call CheckWasSet('fo')
+
+ split Xsomebuf
+ call CheckWasSet('ai')
+ call CheckWasNotSet('filetype')
+ call CheckWasSet('fo')
+endfunc
+
+func Test_set_ttytype()
+ throw "Skipped: Nvim does not support 'ttytype'"
+ CheckUnix
+ CheckNotGui
+
+ " Setting 'ttytype' used to cause a double-free when exiting vim and
+ " when vim is compiled with -DEXITFREE.
+ set ttytype=ansi
+ call assert_equal('ansi', &ttytype)
+ call assert_equal(&ttytype, &term)
+ set ttytype=xterm
+ call assert_equal('xterm', &ttytype)
+ call assert_equal(&ttytype, &term)
+ try
+ set ttytype=
+ call assert_report('set ttytype= did not fail')
+ catch /E529/
+ endtry
+
+ " Some systems accept any terminal name and return dumb settings,
+ " check for failure of finding the entry and for missing 'cm' entry.
+ try
+ set ttytype=xxx
+ call assert_report('set ttytype=xxx did not fail')
+ catch /E522\|E437/
+ endtry
+
+ set ttytype&
+ call assert_equal(&ttytype, &term)
+
+ if has('gui') && !has('gui_running')
+ call assert_fails('set term=gui', 'E531:')
+ endif
+endfunc
+
+func Test_set_all()
+ set tw=75
+ set iskeyword=a-z,A-Z
+ set nosplitbelow
+ let out = execute('set all')
+ call assert_match('textwidth=75', out)
+ call assert_match('iskeyword=a-z,A-Z', out)
+ call assert_match('nosplitbelow', out)
+ set tw& iskeyword& splitbelow&
+endfunc
+
+func Test_set_one_column()
+ let out_mult = execute('set all')->split("\n")
+ let out_one = execute('set! all')->split("\n")
+ " one column should be two to four times as many lines
+ call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one))
+endfunc
+
+func Test_set_values()
+ " opt_test.vim is generated from ../optiondefs.h using gen_opt_test.vim
+ if filereadable('opt_test.vim')
+ source opt_test.vim
+ else
+ throw 'Skipped: opt_test.vim does not exist'
+ endif
+endfunc
+
+func Test_renderoptions()
+ throw 'skipped: Nvim does not support renderoptions'
+ " Only do this for Windows Vista and later, fails on Windows XP and earlier.
+ " Doesn't hurt to do this on a non-Windows system.
+ if windowsversion() !~ '^[345]\.'
+ set renderoptions=type:directx
+ set rop=type:directx
+ endif
+endfunc
+
+func ResetIndentexpr()
+ set indentexpr=
+endfunc
+
+func Test_set_indentexpr()
+ " this was causing usage of freed memory
+ set indentexpr=ResetIndentexpr()
+ new
+ call feedkeys("i\<c-f>", 'x')
+ call assert_equal('', &indentexpr)
+ bwipe!
+endfunc
+
+func Test_backupskip()
+ " Option 'backupskip' may contain several comma-separated path
+ " specifications if one or more of the environment variables TMPDIR, TMP,
+ " or TEMP is defined. To simplify testing, convert the string value into a
+ " list.
+ let bsklist = split(&bsk, ',')
+
+ if has("mac")
+ let found = (index(bsklist, '/private/tmp/*') >= 0)
+ call assert_true(found, '/private/tmp not in option bsk: ' . &bsk)
+ elseif has("unix")
+ let found = (index(bsklist, '/tmp/*') >= 0)
+ call assert_true(found, '/tmp not in option bsk: ' . &bsk)
+ endif
+
+ " If our test platform is Windows, the path(s) in option bsk will use
+ " backslash for the path separator and the components could be in short
+ " (8.3) format. As such, we need to replace the backslashes with forward
+ " slashes and convert the path components to long format. The expand()
+ " function will do this but it cannot handle comma-separated paths. This is
+ " why bsk was converted from a string into a list of strings above.
+ "
+ " One final complication is that the wildcard "/*" is at the end of each
+ " path and so expand() might return a list of matching files. To prevent
+ " this, we need to remove the wildcard before calling expand() and then
+ " append it afterwards.
+ if has('win32')
+ let item_nbr = 0
+ while item_nbr < len(bsklist)
+ let path_spec = bsklist[item_nbr]
+ let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2)
+ let path_spec = substitute(expand(path_spec), '\\', '/', 'g')
+ let bsklist[item_nbr] = path_spec . '/*'
+ let item_nbr += 1
+ endwhile
+ endif
+
+ " Option bsk will also include these environment variables if defined.
+ " If they're defined, verify they appear in the option value.
+ for var in ['$TMPDIR', '$TMP', '$TEMP']
+ if exists(var)
+ let varvalue = substitute(expand(var), '\\', '/', 'g')
+ let varvalue = substitute(varvalue, '/$', '', '')
+ let varvalue .= '/*'
+ let found = (index(bsklist, varvalue) >= 0)
+ call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk)
+ endif
+ endfor
+
+ " Duplicates from environment variables should be filtered out (option has
+ " P_NODUP). Run this in a separate instance and write v:errors in a file,
+ " so that we see what happens on startup.
+ let after =<< trim [CODE]
+ let bsklist = split(&backupskip, ',')
+ call assert_equal(uniq(copy(bsklist)), bsklist)
+ call writefile(['errors:'] + v:errors, 'Xtestout')
+ qall
+ [CODE]
+ call writefile(after, 'Xafter')
+ " let cmd = GetVimProg() . ' --not-a-term -S Xafter --cmd "set enc=utf8"'
+ let cmd = GetVimProg() . ' -S Xafter --cmd "set enc=utf8"'
+
+ let saveenv = {}
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ let saveenv[var] = getenv(var)
+ call setenv(var, '/duplicate/path')
+ endfor
+
+ exe 'silent !' . cmd
+ call assert_equal(['errors:'], readfile('Xtestout'))
+
+ " restore environment variables
+ for var in ['TMPDIR', 'TMP', 'TEMP']
+ call setenv(var, saveenv[var])
+ endfor
+
+ call delete('Xtestout')
+ call delete('Xafter')
+
+ " Duplicates should be filtered out (option has P_NODUP)
+ let backupskip = &backupskip
+ set backupskip=
+ set backupskip+=/test/dir
+ set backupskip+=/other/dir
+ set backupskip+=/test/dir
+ call assert_equal('/test/dir,/other/dir', &backupskip)
+ let &backupskip = backupskip
+endfunc
+
+func Test_buf_copy_winopt()
+ set hidden
+
+ " Test copy option from current buffer in window
+ split
+ enew
+ setlocal numberwidth=5
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(5,&numberwidth)
+ bw!
+ call assert_equal(4,&numberwidth)
+
+ " Test copy value from window that used to be display the buffer
+ split
+ enew
+ setlocal numberwidth=6
+ bnext
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(6,&numberwidth)
+ bw!
+
+ " Test that if buffer is current, don't use the stale cached value
+ " from the last time the buffer was displayed.
+ split
+ enew
+ setlocal numberwidth=7
+ bnext
+ bnext
+ setlocal numberwidth=8
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(8,&numberwidth)
+ bw!
+
+ " Test value is not copied if window already has seen the buffer
+ enew
+ split
+ setlocal numberwidth=9
+ bnext
+ setlocal numberwidth=10
+ wincmd w
+ call assert_equal(4,&numberwidth)
+ bnext
+ call assert_equal(4,&numberwidth)
+ bw!
+
+ set hidden&
+endfunc
+
+func Test_split_copy_options()
+ let values = [
+ \['cursorbind', 1, 0],
+ \['fillchars', '"vert:-"', '"' .. &fillchars .. '"'],
+ \['list', 1, 0],
+ \['listchars', '"space:-"', '"' .. &listchars .. '"'],
+ \['number', 1, 0],
+ \['relativenumber', 1, 0],
+ \['scrollbind', 1, 0],
+ \['smoothscroll', 1, 0],
+ \['virtualedit', '"block"', '"' .. &virtualedit .. '"'],
+ "\ ['wincolor', '"Search"', '"' .. &wincolor .. '"'],
+ \['wrap', 0, 1],
+ \]
+ if has('linebreak')
+ let values += [
+ \['breakindent', 1, 0],
+ \['breakindentopt', '"min:5"', '"' .. &breakindentopt .. '"'],
+ \['linebreak', 1, 0],
+ \['numberwidth', 7, 4],
+ \['showbreak', '"++"', '"' .. &showbreak .. '"'],
+ \]
+ endif
+ if has('rightleft')
+ let values += [
+ \['rightleft', 1, 0],
+ \['rightleftcmd', '"search"', '"' .. &rightleftcmd .. '"'],
+ \]
+ endif
+ if has('statusline')
+ let values += [
+ \['statusline', '"---%f---"', '"' .. &statusline .. '"'],
+ \]
+ endif
+ if has('spell')
+ let values += [
+ \['spell', 1, 0],
+ \]
+ endif
+ if has('syntax')
+ let values += [
+ \['cursorcolumn', 1, 0],
+ \['cursorline', 1, 0],
+ \['cursorlineopt', '"screenline"', '"' .. &cursorlineopt .. '"'],
+ \['colorcolumn', '"+1"', '"' .. &colorcolumn .. '"'],
+ \]
+ endif
+ if has('diff')
+ let values += [
+ \['diff', 1, 0],
+ \]
+ endif
+ if has('conceal')
+ let values += [
+ \['concealcursor', '"nv"', '"' .. &concealcursor .. '"'],
+ \['conceallevel', '3', &conceallevel],
+ \]
+ endif
+ if has('terminal')
+ let values += [
+ \['termwinkey', '"<C-X>"', '"' .. &termwinkey .. '"'],
+ \['termwinsize', '"10x20"', '"' .. &termwinsize .. '"'],
+ \]
+ endif
+ if has('folding')
+ let values += [
+ \['foldcolumn', '"5"', &foldcolumn],
+ \['foldenable', 0, 1],
+ \['foldexpr', '"2 + 3"', '"' .. &foldexpr .. '"'],
+ \['foldignore', '"+="', '"' .. &foldignore .. '"'],
+ \['foldlevel', 4, &foldlevel],
+ \['foldmarker', '">>,<<"', '"' .. &foldmarker .. '"'],
+ \['foldmethod', '"marker"', '"' .. &foldmethod .. '"'],
+ \['foldminlines', 3, &foldminlines],
+ \['foldnestmax', 17, &foldnestmax],
+ \['foldtext', '"closed"', '"' .. &foldtext .. '"'],
+ \]
+ endif
+ if has('signs')
+ let values += [
+ \['signcolumn', '"number"', '"' .. &signcolumn .. '"'],
+ \]
+ endif
+
+ " set options to non-default value
+ for item in values
+ exe $"let &{item[0]} = {item[1]}"
+ endfor
+
+ " check values are set in new window
+ split
+ for item in values
+ exe $'call assert_equal({item[1]}, &{item[0]}, "{item[0]}")'
+ endfor
+
+ " restore
+ close
+ for item in values
+ exe $"let &{item[0]} = {item[1]}"
+ endfor
+endfunc
+
+func Test_shortmess_F()
+ new
+ call assert_match('\[No Name\]', execute('file'))
+ set shortmess+=F
+ call assert_match('\[No Name\]', execute('file'))
+ call assert_match('^\s*$', execute('file foo'))
+ call assert_match('foo', execute('file'))
+ set shortmess-=F
+ call assert_match('bar', execute('file bar'))
+ call assert_match('bar', execute('file'))
+ set shortmess&
+ bwipe
+endfunc
+
+func Test_shortmess_F2()
+ e file1
+ e file2
+ call assert_match('file1', execute('bn', ''))
+ call assert_match('file2', execute('bn', ''))
+ set shortmess+=F
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ set hidden
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ set nohidden
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ call assert_true(empty(execute('bn', '')))
+ " call assert_false(test_getvalue('need_fileinfo'))
+ set shortmess-=F " Accommodate Nvim default.
+ call assert_match('file1', execute('bn', ''))
+ call assert_match('file2', execute('bn', ''))
+ bwipe
+ bwipe
+ " call assert_fails('call test_getvalue("abc")', 'E475:')
+endfunc
+
+func Test_local_scrolloff()
+ set so=5
+ set siso=7
+ split
+ call assert_equal(5, &so)
+ setlocal so=3
+ call assert_equal(3, &so)
+ wincmd w
+ call assert_equal(5, &so)
+ wincmd w
+ call assert_equal(3, &so)
+ setlocal so<
+ call assert_equal(5, &so)
+ setglob so=8
+ call assert_equal(8, &so)
+ call assert_equal(-1, &l:so)
+ setlocal so=0
+ call assert_equal(0, &so)
+ setlocal so=-1
+ call assert_equal(8, &so)
+
+ call assert_equal(7, &siso)
+ setlocal siso=3
+ call assert_equal(3, &siso)
+ wincmd w
+ call assert_equal(7, &siso)
+ wincmd w
+ call assert_equal(3, &siso)
+ setlocal siso<
+ call assert_equal(7, &siso)
+ setglob siso=4
+ call assert_equal(4, &siso)
+ call assert_equal(-1, &l:siso)
+ setlocal siso=0
+ call assert_equal(0, &siso)
+ setlocal siso=-1
+ call assert_equal(4, &siso)
+
+ close
+ set so&
+ set siso&
+endfunc
+
+func Test_visualbell()
+ set belloff=
+ set visualbell
+ call assert_beeps('normal 0h')
+ set novisualbell
+ set belloff=all
+endfunc
+
+" Test for the 'write' option
+func Test_write()
+ new
+ call setline(1, ['L1'])
+ set nowrite
+ call assert_fails('write Xfile', 'E142:')
+ set write
+ close!
+endfunc
+
+" Test for 'buftype' option
+func Test_buftype()
+ new
+ call setline(1, ['L1'])
+ set buftype=nowrite
+ call assert_fails('write', 'E382:')
+
+ " for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'terminal', 'prompt', 'popup']
+ for val in ['', 'nofile', 'nowrite', 'acwrite', 'quickfix', 'help', 'prompt']
+ exe 'set buftype=' .. val
+ call writefile(['something'], 'XBuftype')
+ call assert_fails('write XBuftype', 'E13:', 'with buftype=' .. val)
+ endfor
+
+ call delete('XBuftype')
+ bwipe!
+endfunc
+
+" Test for the 'shell' option
+func Test_shell()
+ throw 'Skipped: Nvim does not have :shell'
+ CheckUnix
+ let save_shell = &shell
+ set shell=
+ let caught_e91 = 0
+ try
+ shell
+ catch /E91:/
+ let caught_e91 = 1
+ endtry
+ call assert_equal(1, caught_e91)
+ let &shell = save_shell
+endfunc
+
+" Test for the 'shellquote' option
+func Test_shellquote()
+ CheckUnix
+ set shellquote=#
+ set verbose=20
+ redir => v
+ silent! !echo Hello
+ redir END
+ set verbose&
+ set shellquote&
+ call assert_match(': "#echo Hello#"', v)
+endfunc
+
+" Test for the 'rightleftcmd' option
+func Test_rightleftcmd()
+ CheckFeature rightleft
+ set rightleft
+
+ let g:l = []
+ func AddPos()
+ call add(g:l, screencol())
+ return ''
+ endfunc
+ cmap <expr> <F2> AddPos()
+
+ set rightleftcmd=
+ call feedkeys("/\<F2>abc\<Right>\<F2>\<Left>\<Left>\<F2>" ..
+ \ "\<Right>\<F2>\<Esc>", 'xt')
+ call assert_equal([2, 5, 3, 4], g:l)
+
+ let g:l = []
+ set rightleftcmd=search
+ call feedkeys("/\<F2>abc\<Left>\<F2>\<Right>\<Right>\<F2>" ..
+ \ "\<Left>\<F2>\<Esc>", 'xt')
+ call assert_equal([&co - 1, &co - 4, &co - 2, &co - 3], g:l)
+
+ cunmap <F2>
+ unlet g:l
+ set rightleftcmd&
+ set rightleft&
+endfunc
+
+" Test for the 'debug' option
+func Test_debug_option()
+ " redraw to avoid matching previous messages
+ redraw
+ set debug=beep
+ exe "normal \<C-c>"
+ call assert_equal('Beep!', Screenline(&lines))
+ call assert_equal('line 4:', Screenline(&lines - 1))
+ " also check a line above, with a certain window width the colon is there
+ call assert_match('Test_debug_option:$',
+ \ Screenline(&lines - 3) .. Screenline(&lines - 2))
+ set debug&
+endfunc
+
+" Test for the default CDPATH option
+func Test_opt_default_cdpath()
+ let after =<< trim [CODE]
+ call assert_equal(',/path/to/dir1,/path/to/dir2', &cdpath)
+ call writefile(v:errors, 'Xtestout')
+ qall
+ [CODE]
+ if has('unix')
+ let $CDPATH='/path/to/dir1:/path/to/dir2'
+ else
+ let $CDPATH='/path/to/dir1;/path/to/dir2'
+ endif
+ if RunVim([], after, '')
+ call assert_equal([], readfile('Xtestout'))
+ call delete('Xtestout')
+ endif
+endfunc
+
+" Test for setting keycodes using set
+func Test_opt_set_keycode()
+ call assert_fails('set <t_k1=l', 'E474:')
+ call assert_fails('set <Home=l', 'E474:')
+ set <t_k9>=abcd
+ " call assert_equal('abcd', &t_k9)
+ set <t_k9>&
+ set <F9>=xyz
+ " call assert_equal('xyz', &t_k9)
+ set <t_k9>&
+endfunc
+
+" Test for changing options in a sandbox
+func Test_opt_sandbox()
+ for opt in ['backupdir', 'cdpath', 'exrc']
+ call assert_fails('sandbox set ' .. opt .. '?', 'E48:')
+ call assert_fails('sandbox let &' .. opt .. ' = 1', 'E48:')
+ endfor
+ call assert_fails('sandbox let &modelineexpr = 1', 'E48:')
+endfunc
+
+" Test for setting an option with local value to global value
+func Test_opt_local_to_global()
+ setglobal equalprg=gprg
+ setlocal equalprg=lprg
+ call assert_equal('gprg', &g:equalprg)
+ call assert_equal('lprg', &l:equalprg)
+ call assert_equal('lprg', &equalprg)
+ set equalprg<
+ call assert_equal('', &l:equalprg)
+ call assert_equal('gprg', &equalprg)
+ setglobal equalprg=gnewprg
+ setlocal equalprg=lnewprg
+ setlocal equalprg<
+ call assert_equal('gnewprg', &l:equalprg)
+ call assert_equal('gnewprg', &equalprg)
+ set equalprg&
+
+ " Test for setting the global/local value of a boolean option
+ setglobal autoread
+ setlocal noautoread
+ call assert_false(&autoread)
+ set autoread<
+ call assert_true(&autoread)
+ setglobal noautoread
+ setlocal autoread
+ setlocal autoread<
+ call assert_false(&autoread)
+ set autoread&
+endfunc
+
+func Test_set_in_sandbox()
+ " Some boolean options cannot be set in sandbox, some can.
+ call assert_fails('sandbox set modelineexpr', 'E48:')
+ sandbox set number
+ call assert_true(&number)
+ set number&
+
+ " Some boolean options cannot be set in sandbox, some can.
+ if has('python') || has('python3')
+ call assert_fails('sandbox set pyxversion=3', 'E48:')
+ endif
+ sandbox set tabstop=4
+ call assert_equal(4, &tabstop)
+ set tabstop&
+
+ " Some string options cannot be set in sandbox, some can.
+ call assert_fails('sandbox set backupdir=/tmp', 'E48:')
+ sandbox set filetype=perl
+ call assert_equal('perl', &filetype)
+ set filetype&
+endfunc
+
+" Test for incrementing, decrementing and multiplying a number option value
+func Test_opt_num_op()
+ set shiftwidth=4
+ set sw+=2
+ call assert_equal(6, &sw)
+ set sw-=2
+ call assert_equal(4, &sw)
+ set sw^=2
+ call assert_equal(8, &sw)
+ set shiftwidth&
+endfunc
+
+" Test for setting option values using v:false and v:true
+func Test_opt_boolean()
+ set number&
+ set number
+ call assert_equal(1, &nu)
+ set nonu
+ call assert_equal(0, &nu)
+ let &nu = v:true
+ call assert_equal(1, &nu)
+ let &nu = v:false
+ call assert_equal(0, &nu)
+ set number&
+endfunc
+
+" Test for the 'window' option
+func Test_window_opt()
+ " Needs only one open widow
+ %bw!
+ call setline(1, range(1, 8))
+ set window=5
+ exe "normal \<C-F>"
+ call assert_equal(4, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(7, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(8, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(5, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(1, line('w0'))
+ set window=1
+ exe "normal gg\<C-F>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-F>"
+ call assert_equal(3, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(2, line('w0'))
+ exe "normal \<C-B>"
+ call assert_equal(1, line('w0'))
+ enew!
+ set window&
+endfunc
+
+" Test for the 'winminheight' option
+func Test_opt_winminheight()
+ only!
+ let &winheight = &lines + 4
+ call assert_fails('let &winminheight = &lines + 2', 'E36:')
+ call assert_true(&winminheight <= &lines)
+ set winminheight&
+ set winheight&
+endfunc
+
+func Test_opt_winminheight_term()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp | wincmd _
+ below sp
+ END
+ call writefile(lines, 'Xwinminheight')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+ call delete('Xwinminheight')
+endfunc
+
+func Test_opt_winminheight_term_tabs()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+
+ " The tabline should be taken into account.
+ let lines =<< trim END
+ set wmh=0 stal=2
+ split
+ split
+ split
+ split
+ tabnew
+ END
+ call writefile(lines, 'Xwinminheight')
+ let buf = RunVimInTerminal('-S Xwinminheight', #{rows: 11})
+ call term_sendkeys(buf, ":set wmh=1\n")
+ call WaitForAssert({-> assert_match('E36: Not enough room', term_getline(buf, 11))})
+
+ call StopVimInTerminal(buf)
+ call delete('Xwinminheight')
+endfunc
+
+" Test for the 'winminwidth' option
+func Test_opt_winminwidth()
+ only!
+ let &winwidth = &columns + 4
+ call assert_fails('let &winminwidth = &columns + 2', 'E36:')
+ call assert_true(&winminwidth <= &columns)
+ set winminwidth&
+ set winwidth&
+endfunc
+
+" Test for setting option value containing spaces with isfname+=32
+func Test_isfname_with_options()
+ set isfname+=32
+ setlocal keywordprg=:term\ help.exe
+ call assert_equal(':term help.exe', &keywordprg)
+ set isfname&
+ setlocal keywordprg&
+endfunc
+
+" Test that resetting laststatus does change scroll option
+func Test_opt_reset_scroll()
+ " See test/functional/legacy/options_spec.lua
+ CheckRunVimInTerminal
+ let vimrc =<< trim [CODE]
+ set scroll=2
+ set laststatus=2
+ [CODE]
+ call writefile(vimrc, 'Xscroll')
+ let buf = RunVimInTerminal('-S Xscroll', {'rows': 16, 'cols': 45})
+ call term_sendkeys(buf, ":verbose set scroll?\n")
+ call WaitForAssert({-> assert_match('Last set.*window size', term_getline(buf, 15))})
+ call assert_match('^\s*scroll=7$', term_getline(buf, 14))
+ call StopVimInTerminal(buf)
+
+ " clean up
+ call delete('Xscroll')
+endfunc
+
+" Check that VIM_POSIX env variable influences default value of 'cpo' and 'shm'
+func Test_VIM_POSIX()
+ throw 'Skipped: Nvim does not support $VIM_POSIX'
+ let saved_VIM_POSIX = getenv("VIM_POSIX")
+
+ call setenv('VIM_POSIX', "1")
+ let after =<< trim [CODE]
+ call writefile([&cpo, &shm], 'X_VIM_POSIX')
+ qall
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
+ \ 'AS'], readfile('X_VIM_POSIX'))
+ endif
+
+ call setenv('VIM_POSIX', v:null)
+ let after =<< trim [CODE]
+ call writefile([&cpo, &shm], 'X_VIM_POSIX')
+ qall
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
+ \ 'S'], readfile('X_VIM_POSIX'))
+ endif
+
+ call delete('X_VIM_POSIX')
+ call setenv('VIM_POSIX', saved_VIM_POSIX)
+endfunc
+
+" Test for setting an option to a Vi or Vim default
+func Test_opt_default()
+ throw 'Skipped: Nvim has different defaults'
+ set formatoptions&vi
+ call assert_equal('vt', &formatoptions)
+ set formatoptions&vim
+ call assert_equal('tcq', &formatoptions)
+endfunc
+
+" Test for the 'cmdheight' option
+func Test_cmdheight()
+ %bw!
+ let ht = &lines
+ set cmdheight=9999
+ call assert_equal(1, winheight(0))
+ call assert_equal(ht - 1, &cmdheight)
+ set cmdheight&
+endfunc
+
+" To specify a control character as a option value, '^' can be used
+func Test_opt_control_char()
+ set wildchar=^v
+ call assert_equal("\<C-V>", nr2char(&wildchar))
+ set wildcharm=^r
+ call assert_equal("\<C-R>", nr2char(&wildcharm))
+ " Bug: This doesn't work for the 'cedit' and 'termwinkey' options
+ set wildchar& wildcharm&
+endfunc
+
+" Test for the 'errorbells' option
+func Test_opt_errorbells()
+ set errorbells
+ call assert_beeps('s/a1b2/x1y2/')
+ set noerrorbells
+endfunc
+
+func Test_opt_scrolljump()
+ help
+ resize 10
+
+ " Test with positive 'scrolljump'.
+ set scrolljump=2
+ norm! Lj
+ call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
+ \ 'topline':3, 'coladd':0, 'skipcol':0, 'curswant':0},
+ \ winsaveview())
+
+ " Test with negative 'scrolljump' (percentage of window height).
+ set scrolljump=-40
+ norm! ggLj
+ call assert_equal({'lnum':11, 'leftcol':0, 'col':0, 'topfill':0,
+ \ 'topline':5, 'coladd':0, 'skipcol':0, 'curswant':0},
+ \ winsaveview())
+
+ set scrolljump&
+ bw
+endfunc
+
+" Test for the 'cdhome' option
+func Test_opt_cdhome()
+ if has('unix') || has('vms')
+ throw 'Skipped: only works on non-Unix'
+ endif
+
+ set cdhome&
+ call assert_equal(0, &cdhome)
+ set cdhome
+
+ " This paragraph is copied from Test_cd_no_arg().
+ let path = getcwd()
+ cd
+ call assert_equal($HOME, getcwd())
+ call assert_notequal(path, getcwd())
+ exe 'cd ' .. fnameescape(path)
+ call assert_equal(path, getcwd())
+
+ set cdhome&
+endfunc
+
+func Test_set_completion_fuzzy()
+ CheckOption termguicolors
+
+ " Test default option completion
+ set wildoptions=
+ call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set termguicolors', @:)
+
+ call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set notermguicolors', @:)
+
+ " Test fuzzy option completion
+ set wildoptions=fuzzy
+ call feedkeys(":set termg\<C-A>\<C-B>\"\<CR>", 'tx')
+ " Nvim doesn't have 'termencoding'
+ " call assert_equal('"set termguicolors termencoding', @:)
+ call assert_equal('"set termguicolors', @:)
+
+ call feedkeys(":set notermg\<C-A>\<C-B>\"\<CR>", 'tx')
+ call assert_equal('"set notermguicolors', @:)
+
+ set wildoptions=
+endfunc
+
+func Test_switchbuf_reset()
+ set switchbuf=useopen
+ sblast
+ call assert_equal(1, winnr('$'))
+ set all&
+ " Nvim has a different default for 'switchbuf'
+ " call assert_equal('', &switchbuf)
+ call assert_equal('uselast', &switchbuf)
+ sblast
+ call assert_equal(2, winnr('$'))
+ only!
+endfunc
+
+" :set empty string for global 'keywordprg' falls back to ":help"
+func Test_keywordprg_empty()
+ let k = &keywordprg
+ set keywordprg=man
+ call assert_equal('man', &keywordprg)
+ set keywordprg=
+ call assert_equal(':help', &keywordprg)
+ set keywordprg=man
+ call assert_equal('man', &keywordprg)
+ call assert_equal("\n keywordprg=:help", execute('set kp= kp?'))
+ let &keywordprg = k
+endfunc
+
+" check that the very first buffer created does not have 'endoffile' set
+func Test_endoffile_default()
+ let after =<< trim [CODE]
+ call writefile([execute('set eof?')], 'Xtestout')
+ qall!
+ [CODE]
+ if RunVim([], after, '')
+ call assert_equal(["\nnoendoffile"], readfile('Xtestout'))
+ endif
+ call delete('Xtestout')
+endfunc
+
+" Test for setting the 'lines' and 'columns' options to a minimum value
+func Test_set_min_lines_columns()
+ let save_lines = &lines
+ let save_columns = &columns
+
+ let after =<< trim END
+ set laststatus=1
+ set nomore
+ let msg = []
+ let v:errmsg = ''
+ silent! let &columns=0
+ call add(msg, v:errmsg)
+ silent! set columns=0
+ call add(msg, v:errmsg)
+ silent! call setbufvar('', '&columns', 0)
+ call add(msg, v:errmsg)
+ "call writefile(msg, 'XResultsetminlines')
+ silent! let &lines=0
+ call add(msg, v:errmsg)
+ silent! set lines=0
+ call add(msg, v:errmsg)
+ silent! call setbufvar('', '&lines', 0)
+ call add(msg, v:errmsg)
+ call writefile(msg, 'XResultsetminlines')
+ qall!
+ END
+ if RunVim([], after, '')
+ call assert_equal(['E594: Need at least 12 columns',
+ \ 'E594: Need at least 12 columns: columns=0',
+ \ 'E594: Need at least 12 columns',
+ \ 'E593: Need at least 2 lines',
+ \ 'E593: Need at least 2 lines: lines=0',
+ \ 'E593: Need at least 2 lines',], readfile('XResultsetminlines'))
+ endif
+
+ call delete('XResultsetminlines')
+ let &lines = save_lines
+ let &columns = save_columns
+endfunc
+
+" Test for reverting a string option value if the new value is invalid.
+func Test_string_option_revert_on_failure()
+ new
+ let optlist = [
+ \ ['ambiwidth', 'double', 'a123'],
+ \ ['background', 'dark', 'a123'],
+ \ ['backspace', 'eol', 'a123'],
+ \ ['backupcopy', 'no', 'a123'],
+ \ ['belloff', 'showmatch', 'a123'],
+ \ ['breakindentopt', 'min:10', 'list'],
+ \ ['bufhidden', 'wipe', 'a123'],
+ \ ['buftype', 'nowrite', 'a123'],
+ \ ['casemap', 'keepascii', 'a123'],
+ \ ['cedit', "\<C-Y>", 'z'],
+ \ ['colorcolumn', '10', 'z'],
+ \ ['commentstring', '#%s', 'a123'],
+ \ ['complete', '.,t', 'a'],
+ \ ['completefunc', 'MyCmplFunc', '1a-'],
+ "\ ['completeopt', 'popup', 'a123'],
+ \ ['completeopt', 'preview', 'a123'],
+ "\ ['completepopup', 'width:20', 'border'],
+ \ ['concealcursor', 'v', 'xyz'],
+ "\ ['cpoptions', 'HJ', '~'],
+ \ ['cpoptions', 'J', '~'],
+ "\ ['cryptmethod', 'zip', 'a123'],
+ \ ['cursorlineopt', 'screenline', 'a123'],
+ \ ['debug', 'throw', 'a123'],
+ \ ['diffopt', 'iwhite', 'a123'],
+ \ ['display', 'uhex', 'a123'],
+ \ ['eadirection', 'hor', 'a123'],
+ \ ['encoding', 'utf-8', 'a123'],
+ \ ['eventignore', 'TextYankPost', 'a123'],
+ \ ['fileencoding', 'utf-8', 'a123,'],
+ \ ['fileformat', 'mac', 'a123'],
+ \ ['fileformats', 'mac', 'a123'],
+ \ ['filetype', 'abc', 'a^b'],
+ \ ['fillchars', 'diff:~', 'a123'],
+ \ ['foldclose', 'all', 'a123'],
+ \ ['foldmarker', '[[[,]]]', '[[['],
+ \ ['foldmethod', 'marker', 'a123'],
+ \ ['foldopen', 'percent', 'a123'],
+ \ ['formatoptions', 'an', '*'],
+ \ ['guicursor', 'n-v-c:block-Cursor/lCursor', 'n-v-c'],
+ \ ['helplang', 'en', 'a'],
+ "\ ['highlight', '!:CursorColumn', '8:'],
+ \ ['keymodel', 'stopsel', 'a123'],
+ "\ ['keyprotocol', 'kitty:kitty', 'kitty:'],
+ \ ['lispoptions', 'expr:1', 'a123'],
+ \ ['listchars', 'tab:->', 'tab:'],
+ \ ['matchpairs', '<:>', '<:'],
+ \ ['mkspellmem', '100000,1000,100', '100000'],
+ \ ['mouse', 'nvi', 'z'],
+ \ ['mousemodel', 'extend', 'a123'],
+ \ ['nrformats', 'alpha', 'a123'],
+ \ ['omnifunc', 'MyOmniFunc', '1a-'],
+ \ ['operatorfunc', 'MyOpFunc', '1a-'],
+ "\ ['previewpopup', 'width:20', 'a123'],
+ "\ ['printoptions', 'paper:A4', 'a123:'],
+ \ ['quickfixtextfunc', 'MyQfFunc', '1a-'],
+ \ ['rulerformat', '%l', '%['],
+ \ ['scrollopt', 'hor,jump', 'a123'],
+ \ ['selection', 'exclusive', 'a123'],
+ \ ['selectmode', 'cmd', 'a123'],
+ \ ['sessionoptions', 'options', 'a123'],
+ \ ['shortmess', 'w', '2'],
+ \ ['showbreak', '>>', "\x01"],
+ \ ['showcmdloc', 'statusline', 'a123'],
+ \ ['signcolumn', 'no', 'a123'],
+ \ ['spellcapcheck', '[.?!]\+', '%\{'],
+ \ ['spellfile', 'MySpell.en.add', "\x01"],
+ \ ['spelllang', 'en', "#"],
+ \ ['spelloptions', 'camel', 'a123'],
+ \ ['spellsuggest', 'double', 'a123'],
+ \ ['splitkeep', 'topline', 'a123'],
+ \ ['statusline', '%f', '%['],
+ "\ ['swapsync', 'sync', 'a123'],
+ \ ['switchbuf', 'usetab', 'a123'],
+ \ ['syntax', 'abc', 'a^b'],
+ \ ['tabline', '%f', '%['],
+ \ ['tagcase', 'ignore', 'a123'],
+ \ ['tagfunc', 'MyTagFunc', '1a-'],
+ \ ['thesaurusfunc', 'MyThesaurusFunc', '1a-'],
+ \ ['viewoptions', 'options', 'a123'],
+ \ ['virtualedit', 'onemore', 'a123'],
+ \ ['whichwrap', '<,>', '{,}'],
+ \ ['wildmode', 'list', 'a123'],
+ \ ['wildoptions', 'pum', 'a123']
+ \ ]
+ if has('gui')
+ call add(optlist, ['browsedir', 'buffer', 'a123'])
+ endif
+ if has('clipboard_working')
+ call add(optlist, ['clipboard', 'unnamed', 'a123'])
+ endif
+ if has('win32')
+ call add(optlist, ['completeslash', 'slash', 'a123'])
+ endif
+ if has('cscope')
+ call add(optlist, ['cscopequickfix', 't-', 'z-'])
+ endif
+ if !has('win32') && !has('nvim')
+ call add(optlist, ['imactivatefunc', 'MyActFunc', '1a-'])
+ call add(optlist, ['imstatusfunc', 'MyStatusFunc', '1a-'])
+ endif
+ if has('keymap')
+ call add(optlist, ['keymap', 'greek', '[]'])
+ endif
+ if has('mouseshape')
+ call add(optlist, ['mouseshape', 'm:no', 'a123:'])
+ endif
+ if has('win32') && has('gui')
+ call add(optlist, ['renderoptions', 'type:directx', 'type:directx,a123'])
+ endif
+ if has('rightleft')
+ call add(optlist, ['rightleftcmd', 'search', 'a123'])
+ endif
+ if has('terminal')
+ call add(optlist, ['termwinkey', '<C-L>', '<C'])
+ call add(optlist, ['termwinsize', '24x80', '100'])
+ endif
+ if has('win32') && has('terminal')
+ call add(optlist, ['termwintype', 'winpty', 'a123'])
+ endif
+ if exists('+toolbar')
+ call add(optlist, ['toolbar', 'text', 'a123'])
+ endif
+ if exists('+toolbariconsize')
+ call add(optlist, ['toolbariconsize', 'medium', 'a123'])
+ endif
+ if exists('+ttymouse') && !has('gui')
+ call add(optlist, ['ttymouse', 'xterm', 'a123'])
+ endif
+ if exists('+vartabs')
+ call add(optlist, ['varsofttabstop', '12', 'a123'])
+ call add(optlist, ['vartabstop', '4,20', '4,'])
+ endif
+ if exists('+winaltkeys')
+ call add(optlist, ['winaltkeys', 'no', 'a123'])
+ endif
+ for opt in optlist
+ exe $"let save_opt = &{opt[0]}"
+ try
+ exe $"let &{opt[0]} = '{opt[1]}'"
+ catch
+ call assert_report($"Caught {v:exception} with {opt->string()}")
+ endtry
+ call assert_fails($"let &{opt[0]} = '{opt[2]}'", '', opt[0])
+ call assert_equal(opt[1], eval($"&{opt[0]}"), opt[0])
+ exe $"let &{opt[0]} = save_opt"
+ endfor
+ bw!
+endfunc
+
+func Test_set_option_window_global_local()
+ new Xbuffer1
+ let [ _gso, _lso ] = [ &g:scrolloff, &l:scrolloff ]
+ setlocal scrolloff=2
+ setglobal scrolloff=3
+ setl modified
+ " A new buffer has its own window-local options
+ hide enew
+ call assert_equal(-1, &l:scrolloff)
+ call assert_equal(3, &g:scrolloff)
+ " A new window opened with its own buffer-local options
+ new
+ call assert_equal(-1, &l:scrolloff)
+ call assert_equal(3, &g:scrolloff)
+ " Re-open Xbuffer1 and it should use
+ " the previous set window-local options
+ b Xbuffer1
+ call assert_equal(2, &l:scrolloff)
+ call assert_equal(3, &g:scrolloff)
+ bw!
+ bw!
+ let &g:scrolloff = _gso
+endfunc
+
+func GetGlobalLocalWindowOptions()
+ new
+ sil! r $VIMRUNTIME/doc/options.txt
+ " Filter for global or local to window
+ v/^'.*'.*\n.*global or local to window |global-local/d
+ " get option value and type
+ sil %s/^'\([^']*\)'.*'\s\+\(\w\+\)\s\+(default \%(\(".*"\|\d\+\|empty\)\).*/\1 \2 \3/g
+ " sil %s/empty/""/g
+ " split the result
+ " let result=getline(1,'$')->map({_, val -> split(val, ' ')})
+ let result = getline(1, '$')->map({_, val -> matchlist(val, '\([^ ]\+\) \+\([^ ]\+\) \+\(.*\)')[1:3]})
+ bw!
+ return result
+endfunc
+
+func Test_set_option_window_global_local_all()
+ new Xbuffer2
+
+ let optionlist = GetGlobalLocalWindowOptions()
+ for [opt, type, default] in optionlist
+ let _old = eval('&g:' .. opt)
+ if type == 'string'
+ if opt == 'fillchars'
+ exe 'setl ' .. opt .. '=vert:+'
+ exe 'setg ' .. opt .. '=vert:+,fold:+'
+ elseif opt == 'listchars'
+ exe 'setl ' .. opt .. '=tab:>>'
+ exe 'setg ' .. opt .. '=tab:++'
+ elseif opt == 'virtualedit'
+ exe 'setl ' .. opt .. '=all'
+ exe 'setg ' .. opt .. '=block'
+ else
+ exe 'setl ' .. opt .. '=Local'
+ exe 'setg ' .. opt .. '=Global'
+ endif
+ elseif type == 'number'
+ exe 'setl ' .. opt .. '=5'
+ exe 'setg ' .. opt .. '=10'
+ endif
+ setl modified
+ hide enew
+ if type == 'string'
+ call assert_equal('', eval('&l:' .. opt))
+ if opt == 'fillchars'
+ call assert_equal('vert:+,fold:+', eval('&g:' .. opt), 'option:' .. opt)
+ elseif opt == 'listchars'
+ call assert_equal('tab:++', eval('&g:' .. opt), 'option:' .. opt)
+ elseif opt == 'virtualedit'
+ call assert_equal('block', eval('&g:' .. opt), 'option:' .. opt)
+ else
+ call assert_equal('Global', eval('&g:' .. opt), 'option:' .. opt)
+ endif
+ elseif type == 'number'
+ call assert_equal(-1, eval('&l:' .. opt), 'option:' .. opt)
+ call assert_equal(10, eval('&g:' .. opt), 'option:' .. opt)
+ endif
+ bw!
+ exe 'let &g:' .. opt .. '=' .. default
+ endfor
+ bw!
+endfunc
+
+func Test_paste_depending_options()
+ " setting the paste option, resets all dependent options
+ " and will be reported correctly using :verbose set <option>?
+ let lines =<< trim [CODE]
+ " set paste test
+ set autoindent
+ set expandtab
+ " disabled, because depends on compiled feature set
+ " set hkmap
+ " set revins
+ " set varsofttabstop=8,32,8
+ set ruler
+ set showmatch
+ set smarttab
+ set softtabstop=4
+ set textwidth=80
+ set wrapmargin=10
+
+ source Xvimrc_paste2
+
+ redir > Xoutput_paste
+ verbose set expandtab?
+ verbose setg expandtab?
+ verbose setl expandtab?
+ redir END
+
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xvimrc_paste', 'D')
+ call writefile(['set paste'], 'Xvimrc_paste2', 'D')
+ if !RunVim([], lines, '--clean')
+ return
+ endif
+
+ let result = readfile('Xoutput_paste')->filter('!empty(v:val)')
+ call assert_equal('noexpandtab', result[0])
+ call assert_match("^\tLast set from .*Xvimrc_paste2 line 1$", result[1])
+ call assert_equal('noexpandtab', result[2])
+ call assert_match("^\tLast set from .*Xvimrc_paste2 line 1$", result[3])
+ call assert_equal('noexpandtab', result[4])
+ call assert_match("^\tLast set from .*Xvimrc_paste2 line 1$", result[5])
+
+ call delete('Xoutput_paste')
+endfunc
+
+func Test_binary_depending_options()
+ " setting the paste option, resets all dependent options
+ " and will be reported correctly using :verbose set <option>?
+ let lines =<< trim [CODE]
+ " set binary test
+ set expandtab
+
+ source Xvimrc_bin2
+
+ redir > Xoutput_bin
+ verbose set expandtab?
+ verbose setg expandtab?
+ verbose setl expandtab?
+ redir END
+
+ qall!
+ [CODE]
+
+ call writefile(lines, 'Xvimrc_bin', 'D')
+ call writefile(['set binary'], 'Xvimrc_bin2', 'D')
+ if !RunVim([], lines, '--clean')
+ return
+ endif
+
+ let result = readfile('Xoutput_bin')->filter('!empty(v:val)')
+ call assert_equal('noexpandtab', result[0])
+ call assert_match("^\tLast set from .*Xvimrc_bin2 line 1$", result[1])
+ call assert_equal('noexpandtab', result[2])
+ call assert_match("^\tLast set from .*Xvimrc_bin2 line 1$", result[3])
+ call assert_equal('noexpandtab', result[4])
+ call assert_match("^\tLast set from .*Xvimrc_bin2 line 1$", result[5])
+
+ call delete('Xoutput_bin')
+endfunc
+
+func Test_set_wrap()
+ " Unsetting 'wrap' when 'smoothscroll' is set does not result in incorrect
+ " cursor position.
+ set wrap smoothscroll scrolloff=5
+
+ call setline(1, ['', 'aaaa'->repeat(500)])
+ 20 split
+ 20 vsplit
+ norm 2G$
+ redraw
+ set nowrap
+ call assert_equal(2, winline())
+
+ set wrap& smoothscroll& scrolloff&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_packadd.vim b/test/old/testdir/test_packadd.vim
index 3121b3b4d1..730cb3278b 100644
--- a/src/nvim/testdir/test_packadd.vim
+++ b/test/old/testdir/test_packadd.vim
@@ -20,6 +20,13 @@ func Test_packadd()
call mkdir(s:plugdir . '/plugin/also', 'p')
call mkdir(s:plugdir . '/ftdetect', 'p')
call mkdir(s:plugdir . '/after', 'p')
+
+ " This used to crash Vim
+ let &rtp = 'nosuchdir,' . s:plugdir . '/after'
+ packadd mytest
+ " plugdir should be inserted before plugdir/after
+ call assert_match('^nosuchdir,' . s:plugdir . ',', &rtp)
+
set rtp&
let rtp = &rtp
filetype on
@@ -331,12 +338,36 @@ func Test_runtime()
runtime extra/bar.vim
call assert_equal('run', g:sequence)
let g:sequence = ''
+ runtime NoSuchFile extra/bar.vim
+ call assert_equal('run', g:sequence)
+
+ let g:sequence = ''
runtime START extra/bar.vim
call assert_equal('start', g:sequence)
let g:sequence = ''
+ runtime START NoSuchFile extra/bar.vim extra/foo.vim
+ call assert_equal('start', g:sequence)
+ let g:sequence = ''
+ runtime START NoSuchFile extra/foo.vim extra/bar.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! START NoSuchFile extra/bar.vim extra/foo.vim
+ call assert_equal('startfoostart', g:sequence)
+
+ let g:sequence = ''
runtime OPT extra/bar.vim
call assert_equal('opt', g:sequence)
let g:sequence = ''
+ runtime OPT NoSuchFile extra/bar.vim extra/xxx.vim
+ call assert_equal('opt', g:sequence)
+ let g:sequence = ''
+ runtime OPT NoSuchFile extra/xxx.vim extra/bar.vim
+ call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime! OPT NoSuchFile extra/bar.vim extra/xxx.vim
+ call assert_equal('optxxxopt', g:sequence)
+
+ let g:sequence = ''
runtime PACK extra/bar.vim
call assert_equal('start', g:sequence)
let g:sequence = ''
@@ -345,6 +376,12 @@ func Test_runtime()
let g:sequence = ''
runtime PACK extra/xxx.vim
call assert_equal('xxxopt', g:sequence)
+ let g:sequence = ''
+ runtime PACK extra/xxx.vim extra/foo.vim extra/bar.vim
+ call assert_equal('foostart', g:sequence)
+ let g:sequence = ''
+ runtime! PACK extra/bar.vim extra/xxx.vim extra/foo.vim
+ call assert_equal('startfoostartoptxxxopt', g:sequence)
let g:sequence = ''
runtime ALL extra/bar.vim
@@ -358,6 +395,12 @@ func Test_runtime()
let g:sequence = ''
runtime! ALL extra/bar.vim
call assert_equal('runstartopt', g:sequence)
+ let g:sequence = ''
+ runtime ALL extra/xxx.vim extra/foo.vim extra/bar.vim
+ call assert_equal('run', g:sequence)
+ let g:sequence = ''
+ runtime! ALL extra/bar.vim extra/xxx.vim extra/foo.vim
+ call assert_equal('runstartfoostartoptxxxopt', g:sequence)
endfunc
func Test_runtime_completion()
@@ -381,9 +424,9 @@ func Test_runtime_completion()
call writefile([], optdir . '/../Aunrelated')
exe 'set rtp=' . &packpath . '/runtime'
- func Check_runtime_completion(arg, arg1, res)
+ func Check_runtime_completion(arg, arg_prev, res)
call feedkeys(':runtime ' .. a:arg .. "\<C-A>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"runtime ' .. a:arg1 .. join(a:res), @:)
+ call assert_equal('"runtime ' .. a:arg_prev .. join(a:res), @:)
call assert_equal(a:res, getcompletion(a:arg, 'runtime'))
endfunc
@@ -397,39 +440,70 @@ func Test_runtime_completion()
\ ['PACK'])
call Check_runtime_completion('A', '',
\ ['Aextra/', 'Arunfoo.vim', 'ALL'])
+ call Check_runtime_completion('Other.vim ', 'Other.vim ',
+ \ ['Aextra/', 'Arunfoo.vim'])
call Check_runtime_completion('Aextra/', '',
\ ['Aextra/Arunbar.vim', 'Aextra/Arunbaz/'])
+ call Check_runtime_completion('Other.vim Aextra/', 'Other.vim ',
+ \ ['Aextra/Arunbar.vim', 'Aextra/Arunbaz/'])
call Check_runtime_completion('START ', 'START ',
\ ['Aextra/', 'Astartfoo.vim'])
+ call Check_runtime_completion('START Other.vim ', 'START Other.vim ',
+ \ ['Aextra/', 'Astartfoo.vim'])
call Check_runtime_completion('START A', 'START ',
\ ['Aextra/', 'Astartfoo.vim'])
+ call Check_runtime_completion('START Other.vim A', 'START Other.vim ',
+ \ ['Aextra/', 'Astartfoo.vim'])
call Check_runtime_completion('START Aextra/', 'START ',
\ ['Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+ call Check_runtime_completion('START Other.vim Aextra/', 'START Other.vim ',
+ \ ['Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
call Check_runtime_completion('OPT ', 'OPT ',
\ ['Aextra/', 'Aoptfoo.vim'])
+ call Check_runtime_completion('OPT Other.vim ', 'OPT Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim'])
call Check_runtime_completion('OPT A', 'OPT ',
\ ['Aextra/', 'Aoptfoo.vim'])
+ call Check_runtime_completion('OPT Other.vim A', 'OPT Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim'])
call Check_runtime_completion('OPT Aextra/', 'OPT ',
\ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/'])
+ call Check_runtime_completion('OPT Other.vim Aextra/', 'OPT Other.vim ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/'])
call Check_runtime_completion('PACK ', 'PACK ',
\ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('PACK Other.vim ', 'PACK Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
call Check_runtime_completion('PACK A', 'PACK ',
\ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('PACK Other.vim A', 'PACK Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Astartfoo.vim'])
call Check_runtime_completion('PACK Aextra/', 'PACK ',
\ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
\ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+ call Check_runtime_completion('PACK Other.vim Aextra/', 'PACK Other.vim ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
+ \ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
call Check_runtime_completion('ALL ', 'ALL ',
\ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('ALL Other.vim ', 'ALL Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
call Check_runtime_completion('ALL A', 'ALL ',
\ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
+ call Check_runtime_completion('ALL Other.vim A', 'ALL Other.vim ',
+ \ ['Aextra/', 'Aoptfoo.vim', 'Arunfoo.vim', 'Astartfoo.vim'])
call Check_runtime_completion('ALL Aextra/', 'ALL ',
\ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
\ 'Aextra/Arunbar.vim', 'Aextra/Arunbaz/',
\ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
+ call Check_runtime_completion('ALL Other.vim Aextra/', 'ALL Other.vim ',
+ \ ['Aextra/Aoptbar.vim', 'Aextra/Aoptbaz/',
+ \ 'Aextra/Arunbar.vim', 'Aextra/Arunbaz/',
+ \ 'Aextra/Astartbar.vim', 'Aextra/Astartbaz/'])
delfunc Check_runtime_completion
endfunc
diff --git a/src/nvim/testdir/test_partial.vim b/test/old/testdir/test_partial.vim
index 3020668f1b..8b62e4a0e5 100644
--- a/src/nvim/testdir/test_partial.vim
+++ b/test/old/testdir/test_partial.vim
@@ -63,16 +63,18 @@ endfunc
func Test_partial_dict()
let dict = {'name': 'hello'}
let Cb = function('MyDictFunc', ["foo", "bar"], dict)
+ call test_garbagecollect_now()
call assert_equal("hello/foo/bar", Cb())
call assert_fails('Cb("xxx")', 'E492:')
+ let Cb = function('MyDictFunc', ["foo"], dict)
+ call assert_equal("hello/foo/xxx", Cb("xxx"))
+ call assert_fails('Cb()', 'E492:')
+
let Cb = function('MyDictFunc', [], dict)
call assert_equal("hello/ttt/xxx", Cb("ttt", "xxx"))
call assert_fails('Cb("yyy")', 'E492:')
- let Cb = function('MyDictFunc', ["foo"], dict)
- call assert_equal("hello/foo/xxx", Cb("xxx"))
- call assert_fails('Cb()', 'E492:')
let Cb = function('MyDictFunc', dict)
call assert_equal("hello/xxx/yyy", Cb("xxx", "yyy"))
call assert_fails('Cb("fff")', 'E492:')
@@ -84,7 +86,7 @@ func Test_partial_dict()
call assert_equal("Hello", dict.tr())
call assert_fails("let F=function('setloclist', 10)", "E923:")
- call assert_fails("let F=function('setloclist', [], [])", "E922:")
+ call assert_fails("let F=function('setloclist', [], [])", "E1206:")
endfunc
func Test_partial_implicit()
@@ -192,6 +194,10 @@ func Test_partial_string()
call assert_equal("function('MyFunc', {'one': 1})", string(F))
let F = function('MyFunc', ['foo'], d)
call assert_equal("function('MyFunc', ['foo'], {'one': 1})", string(F))
+ " Nvim doesn't have null functions
+ " call assert_equal("function('')", string(test_null_function()))
+ " Nvim doesn't have null partials
+ " call assert_equal("function('')", string(test_null_partial()))
endfunc
func Test_func_unref()
@@ -238,17 +244,22 @@ endfunc
func Ignored(job1, job2, status)
endfunc
-" func Test_cycle_partial_job()
-" let job = job_start('echo')
-" call job_setoptions(job, {'exit_cb': function('Ignored', [job])})
-" unlet job
-" endfunc
+func Test_cycle_partial_job()
+ if has('job')
+ let job = job_start('echo')
+ call job_setoptions(job, {'exit_cb': function('Ignored', [job])})
+ unlet job
+ endif
+endfunc
-" func Test_ref_job_partial_dict()
-" let g:ref_job = job_start('echo')
-" let d = {'a': 'b'}
-" call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)})
-" endfunc
+func Test_ref_job_partial_dict()
+ if has('job')
+ let g:ref_job = job_start('echo')
+ let d = {'a': 'b'}
+ call job_setoptions(g:ref_job, {'exit_cb': function('string', [], d)})
+ call test_garbagecollect_now()
+ endif
+endfunc
func Test_auto_partial_rebind()
let dict1 = {'name': 'dict1'}
@@ -356,6 +367,18 @@ func Test_compare_partials()
call assert_true(F1 isnot# F2) " Different functions
call assert_true(F1 isnot# F1d1) " Partial /= non-partial
call assert_true(d1.f1 isnot# d1.f1) " handle_subscript creates new partial each time
+
+ " compare two null partials
+ " Nvim doesn't have null partials
+ " let N1 = test_null_partial()
+ let N1 = function('min')
+ let N2 = N1
+ call assert_true(N1 is N2)
+ call assert_true(N1 == N2)
+
+ " compare a partial and a null partial
+ call assert_false(N1 == F1)
+ call assert_false(F1 is N1)
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_paste.vim b/test/old/testdir/test_paste.vim
index dad3c2c6a0..9ed3adca9b 100644
--- a/src/nvim/testdir/test_paste.vim
+++ b/test/old/testdir/test_paste.vim
@@ -1,26 +1,10 @@
-" Test for 'pastetoggle'
-func Test_pastetoggle()
- new
- set pastetoggle=<F4>
- set nopaste
- call feedkeys("iHello\<F4>", 'xt')
- call assert_true(&paste)
- call feedkeys("i\<F4>", 'xt')
- call assert_false(&paste)
- call assert_equal('Hello', getline(1))
- " command-line completion for 'pastetoggle' value
- call feedkeys(":set pastetoggle=\<Tab>\<C-B>\"\<CR>", 'xt')
- call assert_equal('"set pastetoggle=<F4>', @:)
- set pastetoggle&
- bwipe!
-endfunc
-
" Test for restoring option values when 'paste' is disabled
func Test_paste_opt_restore()
set autoindent expandtab ruler showmatch
if has('rightleft')
- set revins hkmap
+ " set hkmap
+ set revins
endif
set smarttab softtabstop=3 textwidth=27 wrapmargin=12
if has('vartabs')
@@ -33,7 +17,7 @@ func Test_paste_opt_restore()
call assert_false(&expandtab)
if has('rightleft')
call assert_false(&revins)
- call assert_false(&hkmap)
+ " call assert_false(&hkmap)
endif
call assert_false(&ruler)
call assert_false(&showmatch)
@@ -51,7 +35,7 @@ func Test_paste_opt_restore()
call assert_true(&expandtab)
if has('rightleft')
call assert_true(&revins)
- call assert_true(&hkmap)
+ " call assert_true(&hkmap)
endif
call assert_true(&ruler)
call assert_true(&showmatch)
diff --git a/src/nvim/testdir/test_perl.vim b/test/old/testdir/test_perl.vim
index 558d0a5d6b..ce2a566f62 100644
--- a/src/nvim/testdir/test_perl.vim
+++ b/test/old/testdir/test_perl.vim
@@ -224,11 +224,11 @@ endfunc
func Test_stdio()
throw 'skipped: TODO: '
redir =>l:out
- perl <<EOF
+ perl << trim EOF
VIM::Msg("&VIM::Msg");
print "STDOUT";
print STDERR "STDERR";
-EOF
+ EOF
redir END
call assert_equal(['&VIM::Msg', 'STDOUT', 'STDERR'], split(l:out, "\n"))
endfunc
@@ -305,7 +305,16 @@ END
perl <<
VIM::DoCommand('let s ..= "B"')
.
- call assert_equal('AB', s)
+ perl << trim END
+ VIM::DoCommand('let s ..= "C"')
+ END
+ perl << trim
+ VIM::DoCommand('let s ..= "D"')
+ .
+ perl << trim eof
+ VIM::DoCommand('let s ..= "E"')
+ eof
+ call assert_equal('ABCDE', s)
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_plus_arg_edit.vim b/test/old/testdir/test_plus_arg_edit.vim
index c52044d064..c52044d064 100644
--- a/src/nvim/testdir/test_plus_arg_edit.vim
+++ b/test/old/testdir/test_plus_arg_edit.vim
diff --git a/src/nvim/testdir/test_popup.vim b/test/old/testdir/test_popup.vim
index 791cce4431..fa0df77ab5 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/test/old/testdir/test_popup.vim
@@ -501,6 +501,8 @@ endfunc
" Test that 'completefunc' on Scratch buffer with preview window works when
" it's OK.
func Test_completefunc_with_scratch_buffer()
+ CheckFeature quickfix
+
new +setlocal\ buftype=nofile\ bufhidden=wipe\ noswapfile
set completeopt+=preview
setlocal completefunc=DummyCompleteFive
@@ -676,7 +678,9 @@ endfunc
func Test_popup_and_window_resize()
CheckFeature terminal
+ CheckFeature quickfix
CheckNotGui
+ let g:test_is_flaky = 1
let h = winheight(0)
if h < 15
@@ -691,11 +695,11 @@ func Test_popup_and_window_resize()
call term_sendkeys(buf, "Gi\<c-x>")
call term_sendkeys(buf, "\<c-v>")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
" popup first entry "!" must be at the top
call WaitForAssert({-> assert_match('^!\s*$', term_getline(buf, 1))})
exe 'resize +' . (h - 1)
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
redraw!
" popup shifted down, first line is now empty
call WaitForAssert({-> assert_equal('', term_getline(buf, 1))})
@@ -708,14 +712,13 @@ func Test_popup_and_window_resize()
endfunc
func Test_popup_and_preview_autocommand()
- " This used to crash Vim
- if !has('python')
- return
- endif
- let h = winheight(0)
- if h < 15
- return
+ CheckFeature python
+ CheckFeature quickfix
+ if winheight(0) < 15
+ throw 'Skipped: window height insufficient'
endif
+
+ " This used to crash Vim
new
augroup MyBufAdd
au!
@@ -759,11 +762,11 @@ func Test_popup_and_previewwindow_dump()
let buf = RunVimInTerminal('-S Xscript', {})
" wait for the script to finish
- call term_wait(buf)
+ call TermWait(buf)
" Test that popup and previewwindow do not overlap.
call term_sendkeys(buf, "o")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "\<C-X>\<C-N>")
call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {})
@@ -819,9 +822,8 @@ func Test_balloon_split()
endfunc
func Test_popup_position()
- if !CanRunVimInTerminal()
- return
- endif
+ CheckScreendump
+
let lines =<< trim END
123456789_123456789_123456789_a
123456789_123456789_123456789_b
@@ -885,14 +887,14 @@ func Test_popup_command_dump()
echomsg 'changed'
endfunc
END
- call writefile(script, 'XtimerScript')
+ call writefile(script, 'XtimerScript', 'D')
let lines =<< trim END
one two three four five
and one two Xthree four five
one more two three four five
END
- call writefile(lines, 'Xtest')
+ call writefile(lines, 'Xtest', 'D')
let buf = RunVimInTerminal('-S XtimerScript Xtest', {})
call term_sendkeys(buf, ":source $VIMRUNTIME/menu.vim\<CR>")
call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>")
@@ -910,7 +912,7 @@ func Test_popup_command_dump()
" Set a timer to change a menu entry while it's displayed. The text should
" not change but the command does. Making the screendump also verifies that
- " "changed" shows up, which means the timer triggered
+ " "changed" shows up, which means the timer triggered.
call term_sendkeys(buf, "/X\<CR>:call StartTimer() | popup PopUp\<CR>")
call VerifyScreenDump(buf, 'Test_popup_command_04', {})
@@ -918,9 +920,16 @@ func Test_popup_command_dump()
call term_sendkeys(buf, "jj\<CR>")
call VerifyScreenDump(buf, 'Test_popup_command_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Add a window toolbar to the window and check the :popup menu position.
+ call term_sendkeys(buf, ":nnoremenu WinBar.TEST :\<CR>")
+ call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>")
+ call VerifyScreenDump(buf, 'Test_popup_command_06', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+
call StopVimInTerminal(buf)
- call delete('Xtest')
- call delete('XtimerScript')
endfunc
func Test_popup_complete_backwards()
@@ -1201,7 +1210,6 @@ func Test_pum_rightleft()
let buf = RunVimInTerminal('--cmd "set rightleft" Xtest1', {})
call term_wait(buf)
call term_sendkeys(buf, "Go\<C-P>")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_pum_rightleft_01', {'rows': 8})
call term_sendkeys(buf, "\<C-P>\<C-Y>")
call term_wait(buf)
@@ -1243,7 +1251,6 @@ func Test_pum_scrollbar()
let buf = RunVimInTerminal('--cmd "set pumheight=2" Xtest1', {})
call term_wait(buf)
call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_pum_scrollbar_01', {'rows': 7})
call term_sendkeys(buf, "\<C-E>\<Esc>dd")
call term_wait(buf)
@@ -1252,7 +1259,6 @@ func Test_pum_scrollbar()
call term_sendkeys(buf, ":set rightleft\<CR>")
call term_wait(buf)
call term_sendkeys(buf, "Go\<C-P>\<C-P>\<C-P>")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_pum_scrollbar_02', {'rows': 7})
endif
@@ -1260,4 +1266,66 @@ func Test_pum_scrollbar()
call delete('Xtest1')
endfunc
+" Test default highlight groups for popup menu
+func Test_pum_highlights_default()
+ CheckScreendump
+ let lines =<< trim END
+ func CompleteFunc( findstart, base )
+ if a:findstart
+ return 0
+ endif
+ return {
+ \ 'words': [
+ \ { 'word': 'aword1', 'menu': 'extra text 1', 'kind': 'W', },
+ \ { 'word': 'aword2', 'menu': 'extra text 2', 'kind': 'W', },
+ \ { 'word': 'aword3', 'menu': 'extra text 3', 'kind': 'W', },
+ \]}
+ endfunc
+ set completeopt=menu
+ set completefunc=CompleteFunc
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call TermWait(buf)
+ call term_sendkeys(buf, "iaw\<C-X>\<C-u>")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_pum_highlights_01', {})
+ call term_sendkeys(buf, "\<C-E>\<Esc>u")
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test custom highlight groups for popup menu
+func Test_pum_highlights_custom()
+ CheckScreendump
+ let lines =<< trim END
+ func CompleteFunc( findstart, base )
+ if a:findstart
+ return 0
+ endif
+ return {
+ \ 'words': [
+ \ { 'word': 'aword1', 'menu': 'extra text 1', 'kind': 'W', },
+ \ { 'word': 'aword2', 'menu': 'extra text 2', 'kind': 'W', },
+ \ { 'word': 'aword3', 'menu': 'extra text 3', 'kind': 'W', },
+ \]}
+ endfunc
+ set completeopt=menu
+ set completefunc=CompleteFunc
+ hi PmenuKind ctermfg=1 ctermbg=225
+ hi PmenuKindSel ctermfg=1 ctermbg=7
+ hi PmenuExtra ctermfg=243 ctermbg=225
+ hi PmenuExtraSel ctermfg=0 ctermbg=7
+ END
+ call writefile(lines, 'Xscript', 'D')
+ let buf = RunVimInTerminal('-S Xscript', {})
+ call TermWait(buf)
+ call term_sendkeys(buf, "iaw\<C-X>\<C-u>")
+ call TermWait(buf, 50)
+ call VerifyScreenDump(buf, 'Test_pum_highlights_02', {})
+ call term_sendkeys(buf, "\<C-E>\<Esc>u")
+ call TermWait(buf)
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_preview.vim b/test/old/testdir/test_preview.vim
index b7b908e761..b7b908e761 100644
--- a/src/nvim/testdir/test_preview.vim
+++ b/test/old/testdir/test_preview.vim
diff --git a/src/nvim/testdir/test_profile.vim b/test/old/testdir/test_profile.vim
index 9165f7bace..8b4ca74342 100644
--- a/src/nvim/testdir/test_profile.vim
+++ b/test/old/testdir/test_profile.vim
@@ -44,6 +44,7 @@ func Test_profile_func()
\ . ' --cmd "qall!"')
call assert_equal(0, v:shell_error)
+ sleep 50m
let lines = readfile('Xprofile_func.log')
" - Foo1() is called 3 times but should be reported as called twice
@@ -584,7 +585,7 @@ func Test_profile_typed_func()
\ .. "endfunc\<CR>")
call term_sendkeys(buf, ":profile func DoSomething\<CR>")
call term_sendkeys(buf, ":call DoSomething()\<CR>")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
call StopVimInTerminal(buf)
let lines = readfile('XprofileTypedFunc')
call assert_equal("FUNCTION DoSomething()", lines[0])
diff --git a/src/nvim/testdir/test_prompt_buffer.vim b/test/old/testdir/test_prompt_buffer.vim
index b8f6c5240c..3dfbbcece6 100644
--- a/src/nvim/testdir/test_prompt_buffer.vim
+++ b/test/old/testdir/test_prompt_buffer.vim
@@ -10,8 +10,7 @@ source screendump.vim
func CanTestPromptBuffer()
" We need to use a terminal window to be able to feed keys without leaving
" Insert mode.
- " Nvim's terminal implementation differs from Vim's
- " CheckFeature terminal
+ CheckFeature terminal
" TODO: make the tests work on MS-Windows
CheckNotMSWindows
@@ -56,7 +55,6 @@ func WriteScript(name)
endfunc
func Test_prompt_basic()
- throw 'skipped: TODO'
call CanTestPromptBuffer()
let scriptName = 'XpromptscriptBasic'
call WriteScript(scriptName)
@@ -77,7 +75,6 @@ func Test_prompt_basic()
endfunc
func Test_prompt_editing()
- throw 'skipped: TODO'
call CanTestPromptBuffer()
let scriptName = 'XpromptscriptEditing'
call WriteScript(scriptName)
@@ -108,7 +105,6 @@ func Test_prompt_editing()
endfunc
func Test_prompt_switch_windows()
- throw 'skipped: TODO'
call CanTestPromptBuffer()
let scriptName = 'XpromptSwitchWindows'
call WriteScript(scriptName)
@@ -226,7 +222,6 @@ func Test_prompt_buffer_getbufinfo()
endfunc
func Test_prompt_while_writing_to_hidden_buffer()
- throw 'skipped: TODO'
call CanTestPromptBuffer()
CheckUnix
@@ -243,7 +238,7 @@ func Test_prompt_while_writing_to_hidden_buffer()
\ done'], #{out_io: 'buffer', out_name: ''})
startinsert
END
- eval script->writefile(scriptName)
+ eval script->writefile(scriptName, 'D')
let buf = RunVimInTerminal('-S ' .. scriptName, {})
call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))})
@@ -256,7 +251,51 @@ func Test_prompt_while_writing_to_hidden_buffer()
call WaitForAssert({-> assert_equal('cmd:testtesttest', term_getline(buf, 1))})
call StopVimInTerminal(buf)
- call delete(scriptName)
+endfunc
+
+func Test_prompt_appending_while_hidden()
+ call CanTestPromptBuffer()
+
+ let script =<< trim END
+ new prompt
+ set buftype=prompt
+ set bufhidden=hide
+
+ func s:TextEntered(text)
+ if a:text == 'exit'
+ close
+ endif
+ echowin 'Entered:' a:text
+ endfunc
+ call prompt_setcallback(bufnr(), function('s:TextEntered'))
+
+ func DoAppend()
+ call appendbufline('prompt', '$', 'Test')
+ return ''
+ endfunc
+ END
+ call writefile(script, 'XpromptBuffer', 'D')
+
+ let buf = RunVimInTerminal('-S XpromptBuffer', {'rows': 10})
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "asomething\<CR>")
+ call TermWait(buf)
+
+ call term_sendkeys(buf, "exit\<CR>")
+ call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, ":call DoAppend()\<CR>")
+ call WaitForAssert({-> assert_notmatch('-- INSERT --', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "i")
+ call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "\<C-R>=DoAppend()\<CR>")
+ call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 10))})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_put.vim b/test/old/testdir/test_put.vim
index 97af3699a8..6c4bd28386 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/test/old/testdir/test_put.vim
@@ -1,6 +1,7 @@
" Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc.
source check.vim
+source screendump.vim
func Test_put_block()
new
@@ -209,7 +210,7 @@ func Test_multibyte_op_end_mark()
call assert_equal([0, 1, 7, 0], getpos("']"))
normal Vyp
- call assert_equal([0, 1, 2147483647, 0], getpos("'>"))
+ call assert_equal([0, 1, v:maxcol, 0], getpos("'>"))
call assert_equal([0, 2, 7, 0], getpos("']"))
bwipe!
endfunc
@@ -235,5 +236,53 @@ func Test_put_visual_mode()
set selection&
endfunc
+func Test_put_visual_block_mode()
+ enew
+ exe "norm 0R\<CR>\<C-C>V"
+ sil exe "norm \<C-V>c \<MiddleDrag>"
+ set ve=all
+ sil norm vz=p
+
+ bwipe!
+ set ve=
+endfunc
+
+func Test_put_other_window()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ 40vsplit
+ 0put ='some text at the top'
+ put =' one more text'
+ put =' two more text'
+ put =' three more text'
+ put =' four more text'
+ END
+ call writefile(lines, 'Xtest_put_other', 'D')
+ let buf = RunVimInTerminal('-S Xtest_put_other', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_put_other_window_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_put_in_last_displayed_line()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ vim9script
+ autocmd CursorMoved * eval line('w$')
+ @a = 'x'->repeat(&columns * 2 - 2)
+ range(&lines)->setline(1)
+ feedkeys('G"ap')
+ END
+ call writefile(lines, 'Xtest_put_last_line', 'D')
+ let buf = RunVimInTerminal('-S Xtest_put_last_line', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_put_in_last_displayed_line_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_python3.vim b/test/old/testdir/test_python3.vim
index 69f5f6dcc0..23c63f22d8 100644
--- a/src/nvim/testdir/test_python3.vim
+++ b/test/old/testdir/test_python3.vim
@@ -40,7 +40,7 @@ func Test_set_cursor()
endfunc
func Test_vim_function()
- throw 'skipped: Nvim does not support vim.bindeval()'
+ throw 'Skipped: Nvim does not support vim.bindeval()'
" Check creating vim.Function object
py3 import vim
@@ -68,7 +68,7 @@ func Test_vim_function()
endfunc
func Test_skipped_python3_command_does_not_affect_pyxversion()
- throw 'skipped: Nvim hardcodes pyxversion=3'
+ throw 'Skipped: Nvim hardcodes pyxversion=3'
set pyxversion=0
if 0
python3 import vim
@@ -190,3 +190,101 @@ func Test_unicode()
set encoding=utf8
endfunc
+
+" Test for resetting options with local values to global values
+func Test_python3_opt_reset_local_to_global()
+ throw 'Skipped: Nvim does not support vim.bindeval()'
+ new
+
+ py3 curbuf = vim.current.buffer
+ py3 curwin = vim.current.window
+
+ " List of buffer-local options. Each list item has [option name, global
+ " value, buffer-local value, buffer-local value after reset] to use in the
+ " test.
+ let bopts = [
+ \ ['autoread', 1, 0, -1],
+ \ ['equalprg', 'geprg', 'leprg', ''],
+ \ ['keywordprg', 'gkprg', 'lkprg', ''],
+ \ ['path', 'gpath', 'lpath', ''],
+ \ ['backupcopy', 'yes', 'no', ''],
+ \ ['tags', 'gtags', 'ltags', ''],
+ \ ['tagcase', 'ignore', 'match', ''],
+ \ ['define', 'gdef', 'ldef', ''],
+ \ ['include', 'ginc', 'linc', ''],
+ \ ['dict', 'gdict', 'ldict', ''],
+ \ ['thesaurus', 'gtsr', 'ltsr', ''],
+ \ ['formatprg', 'gfprg', 'lfprg', ''],
+ \ ['errorformat', '%f:%l:%m', '%s-%l-%m', ''],
+ \ ['grepprg', 'ggprg', 'lgprg', ''],
+ \ ['makeprg', 'gmprg', 'lmprg', ''],
+ \ ['balloonexpr', 'gbexpr', 'lbexpr', ''],
+ \ ['cryptmethod', 'blowfish2', 'zip', ''],
+ \ ['lispwords', 'abc', 'xyz', ''],
+ \ ['makeencoding', 'utf-8', 'latin1', ''],
+ \ ['undolevels', 100, 200, -123456]]
+
+ " Set the global and buffer-local option values and then clear the
+ " buffer-local option value.
+ for opt in bopts
+ py3 << trim END
+ pyopt = vim.bindeval("opt")
+ vim.options[pyopt[0]] = pyopt[1]
+ curbuf.options[pyopt[0]] = pyopt[2]
+ END
+ exe "call assert_equal(opt[2], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[2], &l:" .. opt[0] .. ")"
+ py3 del curbuf.options[pyopt[0]]
+ exe "call assert_equal(opt[1], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[3], &l:" .. opt[0] .. ")"
+ exe "set " .. opt[0] .. "&"
+ endfor
+
+ " Set the global and window-local option values and then clear the
+ " window-local option value.
+ let wopts = [
+ \ ['scrolloff', 5, 10, -1],
+ \ ['sidescrolloff', 6, 12, -1],
+ \ ['statusline', '%<%f', '%<%F', '']]
+ for opt in wopts
+ py3 << trim
+ pyopt = vim.bindeval("opt")
+ vim.options[pyopt[0]] = pyopt[1]
+ curwin.options[pyopt[0]] = pyopt[2]
+ .
+ exe "call assert_equal(opt[2], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[2], &l:" .. opt[0] .. ")"
+ py3 del curwin.options[pyopt[0]]
+ exe "call assert_equal(opt[1], &" .. opt[0] .. ")"
+ exe "call assert_equal(opt[1], &g:" .. opt[0] .. ")"
+ exe "call assert_equal(opt[3], &l:" .. opt[0] .. ")"
+ exe "set " .. opt[0] .. "&"
+ endfor
+
+ close!
+endfunc
+
+" Test for various heredoc syntax
+func Test_python3_heredoc()
+ python3 << END
+s='A'
+END
+ python3 <<
+s+='B'
+.
+ python3 << trim END
+ s+='C'
+ END
+ python3 << trim
+ s+='D'
+ .
+ python3 << trim eof
+ s+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('s'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_pyx3.vim b/test/old/testdir/test_pyx3.vim
index db39f5134a..09ece6f812 100644
--- a/src/nvim/testdir/test_pyx3.vim
+++ b/test/old/testdir/test_pyx3.vim
@@ -15,10 +15,10 @@ endfunc
func Test_pyx()
redir => var
- pyx << EOF
-import sys
-print(sys.version)
-EOF
+ pyx << trim EOF
+ import sys
+ print(sys.version)
+ EOF
redir END
call assert_match(s:py3pattern, split(var)[0])
endfunc
@@ -79,3 +79,25 @@ func Test_Catch_Exception_Message()
call assert_match('^Vim(.*):.*RuntimeError: TEST$', v:exception )
endtry
endfunc
+
+" Test for various heredoc syntaxes
+func Test_pyx3_heredoc()
+ pyx << END
+result='A'
+END
+ pyx <<
+result+='B'
+.
+ pyx << trim END
+ result+='C'
+ END
+ pyx << trim
+ result+='D'
+ .
+ pyx << trim eof
+ result+='E'
+ eof
+ call assert_equal('ABCDE', pyxeval('result'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim
index 8dc4173d60..b5abb08ed8 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/test/old/testdir/test_quickfix.vim
@@ -167,19 +167,21 @@ func XlistTests(cchar)
\ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22},
\ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33},
\ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44},
- \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}])
+ \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55},
+ \ {'lnum':0,'type':'e','text':'Check type field is output even when lnum==0. ("error" was not output by v9.0.0736.)','nr':66}])
let l = split(execute('Xlist', ""), "\n")
call assert_equal([' 1:10 col 5 warning 11: Warning',
\ ' 2:20 col 10 error 22: Error',
\ ' 3:30 col 15 info 33: Info',
\ ' 4:40 col 20 x 44: Other',
- \ ' 5:50 col 25 55: one'], l)
+ \ ' 5:50 col 25 55: one',
+ \ ' 6 error 66: Check type field is output even when lnum==0. ("error" was not output by v9.0.0736.)'], l)
" Test for module names, one needs to explicitly set `'valid':v:true` so
call g:Xsetlist([
- \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
- \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
- \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
+ \ {'lnum':10,'col':5,'type':'W','module':'Data.Text','text':'ModuleWarning','nr':11,'valid':v:true},
+ \ {'lnum':20,'col':10,'type':'W','module':'Data.Text','filename':'Data/Text.hs','text':'ModuleWarning','nr':22,'valid':v:true},
+ \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}])
let l = split(execute('Xlist', ""), "\n")
call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning',
\ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning',
@@ -1029,7 +1031,7 @@ func Test_efm1()
"Xtestfile", line 6 col 19; this is an error
gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c
Xtestfile:9: parse error before `asd'
- make: *** [vim] Error 1
+ make: *** [src/vim/testdir/Makefile:100: test_quickfix] Error 1
in file "Xtestfile" linenr 10: there is an error
2 returned
@@ -1647,13 +1649,23 @@ func SetXlistTests(cchar, bnum)
call s:setup_commands(a:cchar)
call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 1},
- \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5}])
+ \ {'bufnr': a:bnum, 'lnum': 2, 'end_lnum': 3, 'col': 4, 'end_col': 5, 'user_data': {'6': [7, 8]}}])
let l = g:Xgetlist()
call assert_equal(2, len(l))
call assert_equal(2, l[1].lnum)
call assert_equal(3, l[1].end_lnum)
call assert_equal(4, l[1].col)
call assert_equal(5, l[1].end_col)
+ call assert_equal({'6': [7, 8]}, l[1].user_data)
+
+ " Test that user_data is garbage collected
+ call g:Xsetlist([{'user_data': ['high', 5]},
+ \ {'user_data': {'this': [7, 'eight'], 'is': ['a', 'dictionary']}}])
+ call test_garbagecollect_now()
+ let l = g:Xgetlist()
+ call assert_equal(2, len(l))
+ call assert_equal(['high', 5], l[0].user_data)
+ call assert_equal({'this': [7, 'eight'], 'is': ['a', 'dictionary']}, l[1].user_data)
Xnext
call g:Xsetlist([{'bufnr': a:bnum, 'lnum': 3}], 'a')
@@ -2425,6 +2437,16 @@ func Xproperty_tests(cchar)
call assert_equal(246, d.context)
" set other Vim data types as context
call g:Xsetlist([], 'a', {'context' : v:_null_blob})
+ if has('channel')
+ call g:Xsetlist([], 'a', {'context' : test_null_channel()})
+ endif
+ if has('job')
+ call g:Xsetlist([], 'a', {'context' : test_null_job()})
+ endif
+ " Nvim doesn't have null functions
+ " call g:Xsetlist([], 'a', {'context' : test_null_function()})
+ " Nvim doesn't have null partials
+ " call g:Xsetlist([], 'a', {'context' : test_null_partial()})
call g:Xsetlist([], 'a', {'context' : ''})
call test_garbagecollect_now()
if a:cchar == 'l'
@@ -2674,7 +2696,6 @@ func Test_Autocmd()
cgetbuffer
enew! | call append(0, "F2:30:Line 30")
caddbuffer
-
new
let bnum = bufnr('%')
bunload
@@ -3954,7 +3975,6 @@ func Test_getqflist()
call Xgetlist_empty_tests('l')
endfunc
-
func Test_getqflist_invalid_nr()
" The following commands used to crash Vim
cexpr ""
@@ -5311,234 +5331,6 @@ func Test_cquit()
call assert_fails('-3cquit', 'E16:')
endfunc
-" Running :lhelpgrep command more than once in a help window, doesn't jump to
-" the help topic
-func Test_lhelpgrep_from_help_window()
- call mkdir('Xtestdir/doc', 'p')
- call writefile(['window'], 'Xtestdir/doc/a.txt')
- call writefile(['buffer'], 'Xtestdir/doc/b.txt')
- let save_rtp = &rtp
- let &rtp = 'Xtestdir'
- lhelpgrep window
- lhelpgrep buffer
- call assert_equal('b.txt', fnamemodify(@%, ":p:t"))
- lhelpgrep window
- call assert_equal('a.txt', fnamemodify(@%, ":p:t"))
- let &rtp = save_rtp
- call delete('Xtestdir', 'rf')
- new | only!
-endfunc
-
-" Test for the crash fixed by 7.3.715
-func Test_setloclist_crash()
- %bw!
- let g:BufNum = bufnr()
- augroup QF_Test
- au!
- au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}])
- augroup END
-
- try
- lvimgrep /.*/ *.mak
- catch /E926:/
- endtry
- call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text)
- call assert_equal(1, getloclist(0, {'size' : 0}).size)
-
- augroup QF_Test
- au!
- augroup END
- unlet g:BufNum
- %bw!
-endfunc
-
-" Test for adding an invalid entry with the quickfix window open and making
-" sure that the window contents are not changed
-func Test_add_invalid_entry_with_qf_window()
- call setqflist([], 'f')
- cexpr "Xfile1:10:aa"
- copen
- call setqflist(['bb'], 'a')
- call assert_equal(1, line('$'))
- call assert_equal(['Xfile1|10| aa'], getline(1, '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10 col 666-222| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
-
- call setqflist([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
- call assert_equal(1 , line('$'))
- call assert_equal(['Xfile1|10-6 col 666-222| aa'] , getline(1 , '$'))
- call assert_equal([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
- cclose
-endfunc
-
-" Test for very weird problem: autocommand causes a failure, resulting opening
-" the quickfix window to fail. This still splits the window, but otherwise
-" should not mess up buffers.
-func Test_quickfix_window_fails_to_open()
- CheckScreendump
-
- let lines =<< trim END
- anything
- try
- anything
- endtry
- END
- call writefile(lines, 'XquickfixFails')
-
- let lines =<< trim END
- split XquickfixFails
- silent vimgrep anything %
- normal o
- au BufLeave * ++once source XquickfixFails
- " This will trigger the autocommand, which causes an error, what follows
- " is aborted but the window was already split.
- silent! cwindow
- END
- call writefile(lines, 'XtestWinFails')
- let buf = RunVimInTerminal('-S XtestWinFails', #{rows: 13})
- call VerifyScreenDump(buf, 'Test_quickfix_window_fails', {})
-
- " clean up
- call term_sendkeys(buf, ":bwipe!\<CR>")
- call term_wait(buf)
- call StopVimInTerminal(buf)
- call delete('XtestWinFails')
- call delete('XquickfixFails')
-endfunc
-
-" Test for updating the quickfix buffer whenever the associated quickfix list
-" is changed.
-func Xqfbuf_update(cchar)
- call s:setup_commands(a:cchar)
-
- Xexpr "F1:1:line1"
- Xopen
- call assert_equal(['F1|1| line1'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- " Test setqflist() using the 'lines' key in 'what'
- " add a new entry
- call g:Xsetlist([], 'a', {'lines' : ['F2:2: line2']})
- call assert_equal(['F1|1| line1', 'F2|2| line2'], getline(1, '$'))
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- " replace all the entries with a single entry
- call g:Xsetlist([], 'r', {'lines' : ['F3:3: line3']})
- call assert_equal(['F3|3| line3'], getline(1, '$'))
- call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
- " remove all the entries
- call g:Xsetlist([], 'r', {'lines' : []})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
- " add a new list
- call g:Xsetlist([], ' ', {'lines' : ['F4:4: line4']})
- call assert_equal(['F4|4| line4'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- " Test setqflist() using the 'items' key in 'what'
- " add a new entry
- call g:Xsetlist([], 'a', {'items' : [{'filename' : 'F5', 'lnum' : 5, 'text' : 'line5'}]})
- call assert_equal(['F4|4| line4', 'F5|5| line5'], getline(1, '$'))
- call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
- " replace all the entries with a single entry
- call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F6', 'lnum' : 6, 'text' : 'line6'}]})
- call assert_equal(['F6|6| line6'], getline(1, '$'))
- call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
- " remove all the entries
- call g:Xsetlist([], 'r', {'items' : []})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
- " add a new list
- call g:Xsetlist([], ' ', {'items' : [{'filename' : 'F7', 'lnum' : 7, 'text' : 'line7'}]})
- call assert_equal(['F7|7| line7'], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- call g:Xsetlist([], ' ', {})
- call assert_equal([''], getline(1, '$'))
- call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
-
- Xclose
-endfunc
-
-func Test_qfbuf_update()
- call Xqfbuf_update('c')
- 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)
@@ -5568,7 +5360,6 @@ func Tqfexpr(info)
let qfl = getloclist(a:info.winid, {'id' : a:info.id, 'items' : 1}).items
endif
-
let l = []
for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
let e = qfl[idx]
@@ -5953,6 +5744,234 @@ func Test_qftextfunc_other_loclist()
%bw!
endfunc
+" Running :lhelpgrep command more than once in a help window, doesn't jump to
+" the help topic
+func Test_lhelpgrep_from_help_window()
+ call mkdir('Xtestdir/doc', 'p')
+ call writefile(['window'], 'Xtestdir/doc/a.txt')
+ call writefile(['buffer'], 'Xtestdir/doc/b.txt')
+ let save_rtp = &rtp
+ let &rtp = 'Xtestdir'
+ lhelpgrep window
+ lhelpgrep buffer
+ call assert_equal('b.txt', fnamemodify(@%, ":p:t"))
+ lhelpgrep window
+ call assert_equal('a.txt', fnamemodify(@%, ":p:t"))
+ let &rtp = save_rtp
+ call delete('Xtestdir', 'rf')
+ new | only!
+endfunc
+
+" Test for the crash fixed by 7.3.715
+func Test_setloclist_crash()
+ %bw!
+ let g:BufNum = bufnr()
+ augroup QF_Test
+ au!
+ au BufUnload * call setloclist(0, [{'bufnr':g:BufNum, 'lnum':1, 'col':1, 'text': 'tango down'}])
+ augroup END
+
+ try
+ lvimgrep /.*/ *.mak
+ catch /E926:/
+ endtry
+ call assert_equal('tango down', getloclist(0, {'items' : 0}).items[0].text)
+ call assert_equal(1, getloclist(0, {'size' : 0}).size)
+
+ augroup QF_Test
+ au!
+ augroup END
+ unlet g:BufNum
+ %bw!
+endfunc
+
+" Test for adding an invalid entry with the quickfix window open and making
+" sure that the window contents are not changed
+func Test_add_invalid_entry_with_qf_window()
+ call setqflist([], 'f')
+ cexpr "Xfile1:10:aa"
+ copen
+ call setqflist(['bb'], 'a')
+ call assert_equal(1, line('$'))
+ call assert_equal(['Xfile1|10| aa'], getline(1, '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 0 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 0 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 0 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': -456 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10 col 666-222| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': -123 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+
+ call setqflist([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , 'r')
+ call assert_equal(1 , line('$'))
+ call assert_equal(['Xfile1|10-6 col 666-222| aa'] , getline(1 , '$'))
+ call assert_equal([{'lnum': 10 , 'end_lnum': 6 , 'bufnr': bufnr('Xfile1') , 'col': 666 , 'end_col': 222 , 'pattern': '' , 'valid': 1 , 'vcol': 0 , 'nr': -1 , 'type': '' , 'module': '' , 'text': 'aa'}] , getqflist())
+ cclose
+endfunc
+
+" Test for very weird problem: autocommand causes a failure, resulting opening
+" the quickfix window to fail. This still splits the window, but otherwise
+" should not mess up buffers.
+func Test_quickfix_window_fails_to_open()
+ CheckScreendump
+
+ let lines =<< trim END
+ anything
+ try
+ anything
+ endtry
+ END
+ call writefile(lines, 'XquickfixFails')
+
+ let lines =<< trim END
+ split XquickfixFails
+ silent vimgrep anything %
+ normal o
+ au BufLeave * ++once source XquickfixFails
+ " This will trigger the autocommand, which causes an error, what follows
+ " is aborted but the window was already split.
+ silent! cwindow
+ END
+ call writefile(lines, 'XtestWinFails')
+ let buf = RunVimInTerminal('-S XtestWinFails', #{rows: 13})
+ call VerifyScreenDump(buf, 'Test_quickfix_window_fails', {})
+
+ " clean up
+ call term_sendkeys(buf, ":bwipe!\<CR>")
+ call term_wait(buf)
+ call StopVimInTerminal(buf)
+ call delete('XtestWinFails')
+ call delete('XquickfixFails')
+endfunc
+
+" Test for updating the quickfix buffer whenever the associated quickfix list
+" is changed.
+func Xqfbuf_update(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xexpr "F1:1:line1"
+ Xopen
+ call assert_equal(['F1|1| line1'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test setqflist() using the 'lines' key in 'what'
+ " add a new entry
+ call g:Xsetlist([], 'a', {'lines' : ['F2:2: line2']})
+ call assert_equal(['F1|1| line1', 'F2|2| line2'], getline(1, '$'))
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " replace all the entries with a single entry
+ call g:Xsetlist([], 'r', {'lines' : ['F3:3: line3']})
+ call assert_equal(['F3|3| line3'], getline(1, '$'))
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " remove all the entries
+ call g:Xsetlist([], 'r', {'lines' : []})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " add a new list
+ call g:Xsetlist([], ' ', {'lines' : ['F4:4: line4']})
+ call assert_equal(['F4|4| line4'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ " Test setqflist() using the 'items' key in 'what'
+ " add a new entry
+ call g:Xsetlist([], 'a', {'items' : [{'filename' : 'F5', 'lnum' : 5, 'text' : 'line5'}]})
+ call assert_equal(['F4|4| line4', 'F5|5| line5'], getline(1, '$'))
+ call assert_equal(2, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " replace all the entries with a single entry
+ call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F6', 'lnum' : 6, 'text' : 'line6'}]})
+ call assert_equal(['F6|6| line6'], getline(1, '$'))
+ call assert_equal(3, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " remove all the entries
+ call g:Xsetlist([], 'r', {'items' : []})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(4, g:Xgetlist({'changedtick' : 0}).changedtick)
+ " add a new list
+ call g:Xsetlist([], ' ', {'items' : [{'filename' : 'F7', 'lnum' : 7, 'text' : 'line7'}]})
+ call assert_equal(['F7|7| line7'], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ call g:Xsetlist([], ' ', {})
+ call assert_equal([''], getline(1, '$'))
+ call assert_equal(1, g:Xgetlist({'changedtick' : 0}).changedtick)
+
+ Xclose
+endfunc
+
+func Test_qfbuf_update()
+ call Xqfbuf_update('c')
+ 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
+
func Test_locationlist_open_in_newtab()
call s:create_test_file('Xqftestfile1')
call s:create_test_file('Xqftestfile2')
@@ -6220,6 +6239,44 @@ func Test_loclist_replace_autocmd()
call setloclist(0, [], 'f')
endfunc
+" Test for a very long error line and a very long information line
+func Test_very_long_error_line()
+ let msg = repeat('abcdefghijklmn', 146)
+ let emsg = 'Xlonglines.c:1:' . msg
+ call writefile([msg, emsg], 'Xerror', 'D')
+ cfile Xerror
+ cwindow
+ call assert_equal($'|| {msg}', getline(1))
+ call assert_equal($'Xlonglines.c|1| {msg}', getline(2))
+ cclose
+
+ let l = execute('clist!')->split("\n")
+ call assert_equal([$' 1: {msg}', $' 2 Xlonglines.c:1: {msg}'], l)
+
+ let l = execute('cc')->split("\n")
+ call assert_equal([$'(2 of 2): {msg}'], l)
+
+ call setqflist([], 'f')
+endfunc
+
+" In the quickfix window, spaces at the beginning of an informational line
+" should not be removed but should be removed from an error line.
+func Test_info_line_with_space()
+ cexpr ["a.c:20:12: error: expected ';' before ':' token",
+ \ ' 20 | Afunc():', '', ' | ^']
+ copen
+ call assert_equal(["a.c|20 col 12| error: expected ';' before ':' token",
+ \ '|| 20 | Afunc():', '|| ',
+ \ '|| | ^'], getline(1, '$'))
+ cclose
+
+ let l = execute('clist!')->split("\n")
+ call assert_equal([" 1 a.c:20 col 12: error: expected ';' before ':' token",
+ \ ' 2: 20 | Afunc():', ' 3: ', ' 4: | ^'], l)
+
+ call setqflist([], 'f')
+endfunc
+
func s:QfTf(_)
endfunc
@@ -6232,5 +6289,84 @@ func Test_setqflist_cb_arg()
call setqflist([], 'f')
endfunc
+" Test that setqflist() should not prevent :stopinsert from working
+func Test_setqflist_stopinsert()
+ new
+ call setqflist([], 'f')
+ copen
+ cclose
+ func StopInsert()
+ stopinsert
+ call setqflist([{'text': 'foo'}])
+ return ''
+ endfunc
+
+ call setline(1, 'abc')
+ call cursor(1, 1)
+ call feedkeys("i\<C-R>=StopInsert()\<CR>$", 'tnix')
+ call assert_equal('foo', getqflist()[0].text)
+ call assert_equal([0, 1, 3, 0, v:maxcol], getcurpos())
+ call assert_equal(['abc'], getline(1, '$'))
+
+ delfunc StopInsert
+ call setqflist([], 'f')
+ bwipe!
+endfunc
+
+func Test_quickfix_buffer_contents()
+ call setqflist([{'filename':'filename', 'pattern':'pattern', 'text':'text'}])
+ copen
+ call assert_equal(['filename|pattern| text'], getline(1, '$')) " The assert failed with Vim v9.0.0736; '| text' did not appear after the pattern.
+ call setqflist([], 'f')
+endfunc
+
+" Test for "%b" in "errorformat"
+func Test_efm_format_b()
+ call setqflist([], 'f')
+ new
+ call setline(1, ['1: abc', '1: def', '1: ghi'])
+ let b1 = bufnr()
+ new
+ call setline(1, ['2: abc', '2: def', '2: ghi'])
+ let b2 = bufnr()
+ new
+ call setline(1, ['3: abc', '3: def', '3: ghi'])
+ let b3 = bufnr()
+ new
+ let lines =<< trim eval END
+ {b1}:1:1
+ {b2}:2:2
+ {b3}:3:3
+ END
+ call setqflist([], ' ', #{lines: lines, efm: '%b:%l:%c'})
+ cfirst
+ call assert_equal([b1, 1, 1], [bufnr(), line('.'), col('.')])
+ cnext
+ call assert_equal([b2, 2, 2], [bufnr(), line('.'), col('.')])
+ cnext
+ call assert_equal([b3, 3, 3], [bufnr(), line('.'), col('.')])
+ enew!
+
+ " Use a non-existing buffer
+ let lines =<< trim eval END
+ 9991:1:1:m1
+ 9992:2:2:m2
+ {b3}:3:3:m3
+ END
+ call setqflist([], ' ', #{lines: lines, efm: '%b:%l:%c:%m'})
+ cfirst | cnext
+ call assert_equal([b3, 3, 3], [bufnr(), line('.'), col('.')])
+ " Lines with non-existing buffer numbers should be used as non-error lines
+ call assert_equal([
+ \ #{lnum: 0, bufnr: 0, end_lnum: 0, pattern: '', valid: 0, vcol: 0, nr: -1,
+ \ module: '', type: '', end_col: 0, col: 0, text: '9991:1:1:m1'},
+ \ #{lnum: 0, bufnr: 0, end_lnum: 0, pattern: '', valid: 0, vcol: 0, nr: -1,
+ \ module: '', type: '', end_col: 0, col: 0, text: '9992:2:2:m2'},
+ \ #{lnum: 3, bufnr: b3, end_lnum: 0, pattern: '', valid: 1, vcol: 0,
+ \ nr: -1, module: '', type: '', end_col: 0, col: 3, text: 'm3'}],
+ \ getqflist())
+ %bw!
+ call setqflist([], 'f')
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_quotestar.vim b/test/old/testdir/test_quotestar.vim
index 6a6719da8b..93b0d928ce 100644
--- a/src/nvim/testdir/test_quotestar.vim
+++ b/test/old/testdir/test_quotestar.vim
@@ -131,6 +131,7 @@ func Do_test_quotestar_for_x11()
endfunc
func Test_quotestar()
+ let g:test_is_flaky = 1
let skipped = ''
let quotestar_saved = @*
diff --git a/src/nvim/testdir/test_random.vim b/test/old/testdir/test_random.vim
index 5fdbfe9cd8..5fdbfe9cd8 100644
--- a/src/nvim/testdir/test_random.vim
+++ b/test/old/testdir/test_random.vim
diff --git a/src/nvim/testdir/test_recover.vim b/test/old/testdir/test_recover.vim
index 92e22687af..fa8cc1abaf 100644
--- a/src/nvim/testdir/test_recover.vim
+++ b/test/old/testdir/test_recover.vim
@@ -144,9 +144,9 @@ func Test_recover_multiple_swap_files()
call setline(1, ['a', 'b', 'c'])
preserve
let b = readblob(swapname(''))
- call writefile(b, '.Xfile1.swm')
- call writefile(b, '.Xfile1.swn')
- call writefile(b, '.Xfile1.swo')
+ call writefile(b, '.Xfile1.swm', 'D')
+ call writefile(b, '.Xfile1.swn', 'D')
+ call writefile(b, '.Xfile1.swo', 'D')
%bw!
call feedkeys(":recover Xfile1\<CR>3\<CR>q", 'xt')
call assert_equal(['a', 'b', 'c'], getline(1, '$'))
@@ -160,16 +160,13 @@ func Test_recover_multiple_swap_files()
call assert_equal('Xfile1', @%)
call assert_equal([''], getline(1, '$'))
bw!
-
- call delete('.Xfile1.swm')
- call delete('.Xfile1.swn')
- call delete('.Xfile1.swo')
endfunc
" Test for :recover using an empty swap file
func Test_recover_empty_swap_file()
CheckUnix
- call writefile([], '.Xfile1.swp')
+ call writefile([], '.Xfile1.swp', 'D')
+ set dir=.
let msg = execute('recover Xfile1')
call assert_match('Unable to read block 0 from .Xfile1.swp', msg)
call assert_equal('Xfile1', @%)
@@ -182,7 +179,7 @@ func Test_recover_empty_swap_file()
" :recover from an empty buffer
call assert_fails('recover', 'E305:')
- call delete('.Xfile1.swp')
+ set dir&vim
endfunc
" Test for :recover using a corrupted swap file
@@ -259,9 +256,17 @@ func Test_recover_corrupted_swap_file()
call assert_equal(['???EMPTY BLOCK'], getline(1, '$'))
bw!
+ " set the number of pointers in a pointer block to a large value
+ let b = copy(save_b)
+ let b[4098:4099] = 0zFFFF
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E1364:')
+ call assert_equal('Xfile1', @%)
+ bw!
+
" set the block number in a pointer entry to a negative number
let b = copy(save_b)
- if system_64bit
+ if v:true " Nvim changed this field from a long to an int64_t
let b[4104:4111] = little_endian ? 0z00000000.00000080 : 0z80000000.00000000
else
let b[4104:4107] = little_endian ? 0z00000080 : 0z80000000
@@ -295,6 +300,21 @@ func Test_recover_corrupted_swap_file()
\ '???END'], getline(1, '$'))
bw!
+ " set the number of lines in the data block to a large value
+ let b = copy(save_b)
+ if system_64bit
+ let b[8208:8215] = 0z00FFFFFF.FFFFFF00
+ else
+ let b[8208:8211] = 0z00FFFF00
+ endif
+ call writefile(b, sn)
+ call assert_fails('recover Xfile1', 'E312:')
+ call assert_equal('Xfile1', @%)
+ call assert_equal(['??? from here until ???END lines may have been inserted/deleted',
+ \ '', '???', '??? lines may be missing',
+ \ '???END'], getline(1, '$'))
+ bw!
+
" use an invalid text start for the lines in a data block
let b = copy(save_b)
if system_64bit
@@ -366,16 +386,15 @@ func Test_recover_encrypted_swap_file()
endfunc
" Test for :recover using a unreadable swap file
-func Test_recover_unreadble_swap_file()
+func Test_recover_unreadable_swap_file()
CheckUnix
CheckNotRoot
new Xfile1
let b = readblob('.Xfile1.swp')
- call writefile(b, '.Xfile1.swm')
+ call writefile(b, '.Xfile1.swm', 'D')
bw!
call setfperm('.Xfile1.swm', '-w-------')
call assert_fails('recover Xfile1', 'E306:')
- call delete('.Xfile1.swm')
endfunc
" Test for using :recover when the original file and the swap file have the
@@ -387,20 +406,19 @@ func Test_recover_unmodified_file()
preserve
let b = readblob('.Xfile1.swp')
%bw!
- call writefile(b, '.Xfile1.swz')
+ call writefile(b, '.Xfile1.swz', 'D')
let msg = execute('recover Xfile1')
call assert_equal(['aaa', 'bbb', 'ccc'], getline(1, '$'))
call assert_false(&modified)
call assert_match('Buffer contents equals file contents', msg)
bw!
call delete('Xfile1')
- call delete('.Xfile1.swz')
endfunc
" Test for recovering a file when editing a symbolically linked file
func Test_recover_symbolic_link()
CheckUnix
- call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1')
+ call writefile(['aaa', 'bbb', 'ccc'], 'Xfile1', 'D')
silent !ln -s Xfile1 Xfile2
edit Xfile2
call assert_equal('.Xfile1.swp', fnamemodify(swapname(''), ':t'))
@@ -415,7 +433,6 @@ func Test_recover_symbolic_link()
update
%bw!
call assert_equal(['aaa', 'bbb', 'ccc'], readfile('Xfile1'))
- call delete('Xfile1')
call delete('Xfile2')
call delete('.Xfile1.swp')
endfunc
@@ -424,7 +441,7 @@ endfunc
" line. This used to result in an internal error (E315) which is fixed
" by 8.2.2966.
func Test_recover_invalid_cursor_pos()
- call writefile([], 'Xfile1')
+ call writefile([], 'Xfile1', 'D')
edit Xfile1
preserve
let b = readblob('.Xfile1.swp')
@@ -434,7 +451,7 @@ func Test_recover_invalid_cursor_pos()
au BufReadPost Xfile1 normal! 3G
augroup END
call writefile(range(1, 3), 'Xfile1')
- call writefile(b, '.Xfile1.swp')
+ call writefile(b, '.Xfile1.swp', 'D')
try
recover Xfile1
catch /E308:/
@@ -446,8 +463,6 @@ func Test_recover_invalid_cursor_pos()
au!
augroup END
augroup! Test
- call delete('Xfile1')
- call delete('.Xfile1.swp')
endfunc
" Test for recovering a buffer without a name
@@ -458,10 +473,9 @@ func Test_noname_buffer()
let sn = swapname('')
let b = readblob(sn)
bw!
- call writefile(b, sn)
+ call writefile(b, sn, 'D')
exe "recover " .. sn
call assert_equal(['one', 'two'], getline(1, '$'))
- call delete(sn)
endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regex_char_classes.vim b/test/old/testdir/test_regex_char_classes.vim
index db16f057c8..db16f057c8 100644
--- a/src/nvim/testdir/test_regex_char_classes.vim
+++ b/test/old/testdir/test_regex_char_classes.vim
diff --git a/src/nvim/testdir/test_regexp_latin.vim b/test/old/testdir/test_regexp_latin.vim
index ece6ae518e..ece6ae518e 100644
--- a/src/nvim/testdir/test_regexp_latin.vim
+++ b/test/old/testdir/test_regexp_latin.vim
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/test/old/testdir/test_regexp_utf8.vim
index 2253242a7c..2253242a7c 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/test/old/testdir/test_regexp_utf8.vim
diff --git a/src/nvim/testdir/test_registers.vim b/test/old/testdir/test_registers.vim
index bbf1aa53b5..01f9507916 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/test/old/testdir/test_registers.vim
@@ -55,8 +55,9 @@ func Test_display_registers()
call feedkeys("i\<C-R>=2*4\n\<esc>")
call feedkeys(":ls\n", 'xt')
- let a = execute('display')
- let b = execute('registers')
+ " these commands work in the sandbox
+ let a = execute('sandbox display')
+ let b = execute('sandbox registers')
call assert_equal(a, b)
call assert_match('^\nType Name Content\n'
@@ -757,8 +758,9 @@ func Test_record_in_select_mode()
bwipe!
endfunc
-" mapping that ends macro recording should be removed from recorded macro
+" A mapping that ends recording should be removed from the recorded register.
func Test_end_record_using_mapping()
+ new
call setline(1, 'aaa')
nnoremap s q
call feedkeys('safas', 'tx')
@@ -778,7 +780,10 @@ func Test_end_record_using_mapping()
bwipe!
endfunc
+" Starting a new recording should work immediately after replaying a recording
+" that ends with a <Nop> mapping or a character search.
func Test_end_reg_executing()
+ new
nnoremap s <Nop>
let @a = 's'
call feedkeys("@aqaq\<Esc>", 'tx')
@@ -796,5 +801,25 @@ func Test_end_reg_executing()
bwipe!
endfunc
+" An operator-pending mode mapping shouldn't be applied to keys typed in
+" Insert mode immediately after a character search when replaying.
+func Test_replay_charsearch_omap()
+ CheckFeature timers
+
+ new
+ call setline(1, 'foo[blah]')
+ onoremap , k
+ call timer_start(10, {-> feedkeys(",bar\<Esc>q", 't')})
+ call feedkeys('qrct[', 'xt!')
+ call assert_equal(',bar[blah]', getline(1))
+ undo
+ call assert_equal('foo[blah]', getline(1))
+ call feedkeys('@r', 'xt!')
+ call assert_equal(',bar[blah]', getline(1))
+
+ ounmap ,
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_reltime.vim b/test/old/testdir/test_reltime.vim
index f4ce5de118..8ed09dd10f 100644
--- a/src/nvim/testdir/test_reltime.vim
+++ b/test/old/testdir/test_reltime.vim
@@ -5,6 +5,7 @@ CheckFeature reltime
CheckFeature float
func Test_reltime()
+ let g:test_is_flaky = 1
let now = reltime()
sleep 10m
let later = reltime()
diff --git a/src/nvim/testdir/test_rename.vim b/test/old/testdir/test_rename.vim
index 5359b84923..5359b84923 100644
--- a/src/nvim/testdir/test_rename.vim
+++ b/test/old/testdir/test_rename.vim
diff --git a/src/nvim/testdir/test_retab.vim b/test/old/testdir/test_retab.vim
index a4f95053c0..a4f95053c0 100644
--- a/src/nvim/testdir/test_retab.vim
+++ b/test/old/testdir/test_retab.vim
diff --git a/src/nvim/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim
index 1fbf3392d9..4929496086 100644
--- a/src/nvim/testdir/test_ruby.vim
+++ b/test/old/testdir/test_ruby.vim
@@ -341,11 +341,11 @@ func Test_ruby_Vim_evaluate_list()
call setline(line('$'), ['2 line 2'])
ruby Vim.command("normal /^2\n")
let l = ["abc", "def"]
- ruby << EOF
- curline = $curbuf.line_number
- l = Vim.evaluate("l");
- $curbuf.append(curline, l.join("|"))
-EOF
+ ruby << trim EOF
+ curline = $curbuf.line_number
+ l = Vim.evaluate("l");
+ $curbuf.append(curline, l.join("|"))
+ EOF
normal j
.rubydo $_ = $_.gsub(/\|/, '/')
call assert_equal('abc/def', getline('$'))
@@ -414,4 +414,24 @@ func Test_rubyeval_error()
call assert_fails('call rubyeval("(")')
endfunc
+" Test for various heredoc syntax
+func Test_ruby_heredoc()
+ ruby << END
+Vim.command('let s = "A"')
+END
+ ruby <<
+Vim.command('let s ..= "B"')
+.
+ ruby << trim END
+ Vim.command('let s ..= "C"')
+ END
+ ruby << trim
+ Vim.command('let s ..= "D"')
+ .
+ ruby << trim eof
+ Vim.command('let s ..= "E"')
+ eof
+ call assert_equal('ABCDE', s)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_scriptnames.vim b/test/old/testdir/test_scriptnames.vim
new file mode 100644
index 0000000000..69e5e526fd
--- /dev/null
+++ b/test/old/testdir/test_scriptnames.vim
@@ -0,0 +1,110 @@
+
+" Test for the :scriptnames command
+func Test_scriptnames()
+ call writefile(['let did_load_script = 123'], 'Xscripting')
+ source Xscripting
+ call assert_equal(123, g:did_load_script)
+
+ let scripts = split(execute('scriptnames'), "\n")
+ let last = scripts[-1]
+ call assert_match('\<Xscripting\>', last)
+ let lastnr = substitute(last, '\D*\(\d\+\):.*', '\1', '')
+ exe 'script ' . lastnr
+ call assert_equal('Xscripting', expand('%:t'))
+
+ call assert_fails('script ' . (lastnr + 1), 'E474:')
+ call assert_fails('script 0', 'E939:')
+
+ new
+ call setline(1, 'nothing')
+ call assert_fails('script ' . lastnr, 'E37:')
+ exe 'script! ' . lastnr
+ call assert_equal('Xscripting', expand('%:t'))
+
+ bwipe
+ call delete('Xscripting')
+
+ let msgs = execute('messages')
+ scriptnames
+ call assert_equal(msgs, execute('messages'))
+endfunc
+
+" Test for the getscriptinfo() function
+func Test_getscriptinfo()
+ let lines =<< trim END
+ " scriptversion 3
+ let g:loaded_script_id = expand("<SID>")
+ let s:XscriptVar = [1, #{v: 2}]
+ func s:XgetScriptVar()
+ return s:XscriptVar
+ endfunc
+ func s:Xscript_legacy_func1()
+ endfunc
+ " def s:Xscript_def_func1()
+ " enddef
+ func Xscript_legacy_func2()
+ endfunc
+ " def Xscript_def_func2()
+ " enddef
+ END
+ call writefile(lines, 'X22script91')
+ source X22script91
+ let l = getscriptinfo()
+ call assert_match('X22script91$', l[-1].name)
+ call assert_equal(g:loaded_script_id, $"<SNR>{l[-1].sid}_")
+ " call assert_equal(3, l[-1].version)
+ call assert_equal(1, l[-1].version)
+ call assert_equal(0, has_key(l[-1], 'variables'))
+ call assert_equal(0, has_key(l[-1], 'functions'))
+
+ " Get script information using script name
+ let l = getscriptinfo(#{name: '22script91'})
+ call assert_equal(1, len(l))
+ call assert_match('22script91$', l[0].name)
+ let sid = l[0].sid
+
+ " Get script information using script-ID
+ let l = getscriptinfo({'sid': sid})
+ call assert_equal(#{XscriptVar: [1, {'v': 2}]}, l[0].variables)
+ let funcs = ['Xscript_legacy_func2',
+ \ $"<SNR>{sid}_Xscript_legacy_func1",
+ "\ $"<SNR>{sid}_Xscript_def_func1",
+ "\ 'Xscript_def_func2',
+ \ $"<SNR>{sid}_XgetScriptVar"]
+ for f in funcs
+ call assert_true(index(l[0].functions, f) != -1)
+ endfor
+
+ " Verify that a script-local variable cannot be modified using the dict
+ " returned by getscriptinfo()
+ let l[0].variables.XscriptVar = ['n']
+ let funcname = $"<SNR>{sid}_XgetScriptVar"
+ call assert_equal([1, {'v': 2}], call(funcname, []))
+
+ let l = getscriptinfo({'name': 'foobar'})
+ call assert_equal(0, len(l))
+ let l = getscriptinfo({'name': ''})
+ call assert_true(len(l) > 1)
+
+ call assert_fails("echo getscriptinfo({'name': []})", 'E730:')
+ call assert_fails("echo getscriptinfo({'name': '\\@'})", 'E866:')
+ let l = getscriptinfo({'name': v:_null_string})
+ call assert_true(len(l) > 1)
+ call assert_fails("echo getscriptinfo('foobar')", 'E1206:')
+
+ call assert_fails("echo getscriptinfo({'sid': []})", 'E745:')
+ call assert_fails("echo getscriptinfo({'sid': {}})", 'E728:')
+ call assert_fails("echo getscriptinfo({'sid': 0})", 'E475:')
+ call assert_fails("echo getscriptinfo({'sid': -1})", 'E475:')
+ call assert_fails("echo getscriptinfo({'sid': -999})", 'E475:')
+
+ echo getscriptinfo({'sid': '1'})
+ " call assert_fails("vim9cmd echo getscriptinfo({'sid': '1'})", 'E1030:')
+
+ let max_sid = max(map(getscriptinfo(), { k, v -> v.sid }))
+ call assert_equal([], getscriptinfo({'sid': max_sid + 1}))
+
+ call delete('X22script91')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim
new file mode 100644
index 0000000000..f2e7bc6b56
--- /dev/null
+++ b/test/old/testdir/test_scroll_opt.vim
@@ -0,0 +1,951 @@
+" Test for 'scroll', 'scrolloff', 'smoothscroll', etc.
+
+source check.vim
+source screendump.vim
+source mouse.vim
+
+func Test_reset_scroll()
+ let scr = &l:scroll
+
+ setlocal scroll=1
+ setlocal scroll&
+ call assert_equal(scr, &l:scroll)
+
+ setlocal scroll=1
+ setlocal scroll=0
+ call assert_equal(scr, &l:scroll)
+
+ try
+ execute 'setlocal scroll=' . (winheight(0) + 1)
+ " not reached
+ call assert_false(1)
+ catch
+ call assert_exception('E49:')
+ endtry
+
+ split
+
+ let scr = &l:scroll
+
+ setlocal scroll=1
+ setlocal scroll&
+ call assert_equal(scr, &l:scroll)
+
+ setlocal scroll=1
+ setlocal scroll=0
+ call assert_equal(scr, &l:scroll)
+
+ quit!
+endfunc
+
+func Test_scolloff_even_line_count()
+ new
+ resize 6
+ setlocal scrolloff=3
+ call setline(1, range(20))
+ normal 2j
+ call assert_equal(1, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(1, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(2, getwininfo(win_getid())[0].topline)
+ normal j
+ call assert_equal(3, getwininfo(win_getid())[0].topline)
+
+ bwipe!
+endfunc
+
+func Test_mouse_scroll_inactive_with_cursorbind()
+ for scb in [0, 1]
+ for so in [0, 1, 2]
+ let msg = $'scb={scb} so={so}'
+
+ new | only
+ let w1 = win_getid()
+ setlocal cursorbind
+ let &l:scb = scb
+ let &l:so = so
+ call setline(1, range(101, 109))
+ rightbelow vnew
+ let w2 = win_getid()
+ setlocal cursorbind
+ let &l:scb = scb
+ let &l:so = so
+ call setline(1, range(101, 109))
+
+ normal! $
+ call assert_equal(3, col('.', w1), msg)
+ call assert_equal(3, col('.', w2), msg)
+ call Ntest_setmouse(1, 1)
+ call feedkeys("\<ScrollWheelDown>", 'xt')
+ call assert_equal(4, line('w0', w1), msg)
+ call assert_equal(4 + so, line('.', w1), msg)
+ call assert_equal(1, line('w0', w2), msg)
+ call assert_equal(1, line('.', w2), msg)
+ call feedkeys("\<ScrollWheelDown>", 'xt')
+ call assert_equal(7, line('w0', w1), msg)
+ call assert_equal(7 + so, line('.', w1), msg)
+ call assert_equal(1, line('w0', w2), msg)
+ call assert_equal(1, line('.', w2), msg)
+ call feedkeys("\<ScrollWheelUp>", 'xt')
+ call assert_equal(4, line('w0', w1), msg)
+ call assert_equal(7 + so, line('.', w1), msg)
+ call assert_equal(1, line('w0', w2), msg)
+ call assert_equal(1, line('.', w2), msg)
+ call feedkeys("\<ScrollWheelUp>", 'xt')
+ call assert_equal(1, line('w0', w1), msg)
+ call assert_equal(7 + so, line('.', w1), msg)
+ call assert_equal(1, line('w0', w2), msg)
+ call assert_equal(1, line('.', w2), msg)
+ normal! 0
+ call assert_equal(1, line('.', w1), msg)
+ call assert_equal(1, col('.', w1), msg)
+ call assert_equal(1, line('.', w2), msg)
+ call assert_equal(1, col('.', w2), msg)
+
+ bwipe!
+ bwipe!
+ endfor
+ endfor
+endfunc
+
+func Test_CtrlE_CtrlY_stop_at_end()
+ enew
+ call setline(1, ['one', 'two'])
+ set number
+ exe "normal \<C-Y>"
+ call assert_equal([" 1 one "], ScreenLines(1, 10))
+ exe "normal \<C-E>\<C-E>\<C-E>"
+ call assert_equal([" 2 two "], ScreenLines(1, 10))
+
+ bwipe!
+ set nonumber
+endfunc
+
+func Test_smoothscroll_CtrlE_CtrlY()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, [
+ 'line one',
+ 'word '->repeat(20),
+ 'line three',
+ 'long word '->repeat(7),
+ 'line',
+ 'line',
+ 'line',
+ ])
+ set smoothscroll
+ :5
+ END
+ call writefile(lines, 'XSmoothScroll', 'D')
+ let buf = RunVimInTerminal('-S XSmoothScroll', #{rows: 12, cols: 40})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_2', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_3', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_4', {})
+
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_5', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_6', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_7', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_8', {})
+
+ if has('folding')
+ call term_sendkeys(buf, ":set foldmethod=indent\<CR>")
+ " move the cursor so we can reuse the same dumps
+ call term_sendkeys(buf, "5G")
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_2', {})
+ call term_sendkeys(buf, "7G")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_7', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_8', {})
+ endif
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_multibyte()
+ CheckScreendump
+
+ let lines =<< trim END
+ set scrolloff=0 smoothscroll
+ call setline(1, [repeat('ϛ', 45), repeat('2', 36)])
+ exe "normal G35l\<C-E>k"
+ END
+ call writefile(lines, 'XSmoothMultibyte', 'D')
+ let buf = RunVimInTerminal('-S XSmoothMultibyte', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smoothscroll_multi_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_number()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, [
+ 'one ' .. 'word '->repeat(20),
+ 'two ' .. 'long word '->repeat(7),
+ 'line',
+ 'line',
+ 'line',
+ ])
+ set smoothscroll
+ set splitkeep=topline
+ set number cpo+=n
+ :3
+
+ def g:DoRel()
+ set number relativenumber scrolloff=0
+ :%del
+ setline(1, [
+ 'one',
+ 'very long text '->repeat(12),
+ 'three',
+ ])
+ exe "normal 2Gzt\<C-E>"
+ enddef
+ END
+ call writefile(lines, 'XSmoothNumber', 'D')
+ let buf = RunVimInTerminal('-S XSmoothNumber', #{rows: 12, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_number_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_2', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_3', {})
+
+ call term_sendkeys(buf, ":set cpo-=n\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_4', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_5', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_6', {})
+
+ call term_sendkeys(buf, ":botright split\<CR>gg")
+ call VerifyScreenDump(buf, 'Test_smooth_number_7', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_8', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_9', {})
+ call term_sendkeys(buf, ":close\<CR>")
+
+ call term_sendkeys(buf, ":call DoRel()\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_number_10', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_list()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ set smoothscroll scrolloff=0
+ set list
+ setline(1, [
+ 'one',
+ 'very long text '->repeat(12),
+ 'three',
+ ])
+ exe "normal 2Gzt\<C-E>"
+ END
+ call writefile(lines, 'XSmoothList', 'D')
+ let buf = RunVimInTerminal('-S XSmoothList', #{rows: 8, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_list_1', {})
+
+ call term_sendkeys(buf, ":set listchars+=precedes:#\<CR>")
+ call VerifyScreenDump(buf, 'Test_smooth_list_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_diff_mode()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ var text = 'just some text here'
+ setline(1, text)
+ set smoothscroll
+ diffthis
+ new
+ setline(1, text)
+ set smoothscroll
+ diffthis
+ END
+ call writefile(lines, 'XSmoothDiff', 'D')
+ let buf = RunVimInTerminal('-S XSmoothDiff', #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_diff_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_wrap_scrolloff_zero()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['Line' .. (' with some text'->repeat(7))]->repeat(7))
+ set smoothscroll scrolloff=0
+ :3
+ END
+ call writefile(lines, 'XSmoothWrap', 'D')
+ let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 8, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_1', {})
+
+ " moving cursor down - whole bottom line shows
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_2', {})
+
+ call term_sendkeys(buf, "\<C-E>j")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_3', {})
+
+ call term_sendkeys(buf, "G")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_4', {})
+
+ call term_sendkeys(buf, "4\<C-Y>G")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_4', {})
+
+ " moving cursor up right after the <<< marker - no need to show whole line
+ call term_sendkeys(buf, "2gj3l2k")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_5', {})
+
+ " moving cursor up where the <<< marker is - whole top line shows
+ call term_sendkeys(buf, "2j02k")
+ call VerifyScreenDump(buf, 'Test_smooth_wrap_6', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_wrap_long_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(30)) .. ' end', 'four'])
+ set smoothscroll scrolloff=0
+ normal 3G10|zt
+ END
+ call writefile(lines, 'XSmoothWrap', 'D')
+ let buf = RunVimInTerminal('-S XSmoothWrap', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_long_1', {})
+
+ " scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_2', {})
+ call term_sendkeys(buf, "5\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_3', {})
+
+ " scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "5\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_4', {})
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_5', {})
+
+ " 'scrolloff' set to 1, scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, ":set scrolloff=1\<CR>")
+ call term_sendkeys(buf, "10|\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_6', {})
+
+ " 'scrolloff' set to 1, scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "\<C-E>")
+ call term_sendkeys(buf, "gjgj")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_7', {})
+
+ " 'scrolloff' set to 2, scrolling up, cursor moves screen line down
+ call term_sendkeys(buf, ":set scrolloff=2\<CR>")
+ call term_sendkeys(buf, "10|\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_8', {})
+
+ " 'scrolloff' set to 2, scrolling down, cursor moves screen line up
+ call term_sendkeys(buf, "\<C-E>")
+ call term_sendkeys(buf, "gj")
+ call term_sendkeys(buf, "\<C-Y>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_9', {})
+
+ " 'scrolloff' set to 0, move cursor down one line.
+ " Cursor should move properly, and since this is a really long line, it will
+ " be put on top of the screen.
+ call term_sendkeys(buf, ":set scrolloff=0\<CR>")
+ call term_sendkeys(buf, "0j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_10', {})
+
+ " Test zt/zz/zb that they work properly when a long line is above it
+ call term_sendkeys(buf, "zt")
+ call VerifyScreenDump(buf, 'Test_smooth_long_11', {})
+ call term_sendkeys(buf, "zz")
+ call VerifyScreenDump(buf, 'Test_smooth_long_12', {})
+ call term_sendkeys(buf, "zb")
+ call VerifyScreenDump(buf, 'Test_smooth_long_13', {})
+
+ " Repeat the step and move the cursor down again.
+ " This time, use a shorter long line that is barely long enough to span more
+ " than one window. Note that the cursor is at the bottom this time because
+ " Vim prefers to do so if we are scrolling a few lines only.
+ call term_sendkeys(buf, ":call setline(1, ['one', 'two', 'Line' .. (' with lots of text'->repeat(10)) .. ' end', 'four'])\<CR>")
+ " Currently visible lines were replaced, test that the lines and cursor
+ " are correctly displayed.
+ call VerifyScreenDump(buf, 'Test_smooth_long_14', {})
+ call term_sendkeys(buf, "3Gzt")
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_15', {})
+
+ " Repeat the step but this time start it when the line is smooth-scrolled by
+ " one line. This tests that the offset calculation is still correct and
+ " still end up scrolling down to the next line with cursor at bottom of
+ " screen.
+ call term_sendkeys(buf, "3Gzt")
+ call term_sendkeys(buf, "\<C-E>j")
+ call VerifyScreenDump(buf, 'Test_smooth_long_16', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_one_long_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ setline(1, 'with lots of text '->repeat(7))
+ set smoothscroll scrolloff=0
+ END
+ call writefile(lines, 'XSmoothOneLong', 'D')
+ let buf = RunVimInTerminal('-S XSmoothOneLong', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_2', {})
+
+ call term_sendkeys(buf, "0")
+ call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_long_line_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ vim9script
+ # a line that spans four screen lines
+ setline(1, 'with lots of text in one line '->repeat(6))
+ set smoothscroll scrolloff=0 showbreak=+++\
+ END
+ call writefile(lines, 'XSmoothLongShowbreak', 'D')
+ let buf = RunVimInTerminal('-S XSmoothLongShowbreak', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_2', {})
+
+ call term_sendkeys(buf, "0")
+ call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Check that 'smoothscroll' marker is drawn over double-width char correctly.
+" Run with multiple encodings.
+func Test_smoothscroll_marker_over_double_width()
+ " Run this in a separate Vim instance to avoid messing up.
+ let after =<< trim [CODE]
+ scriptencoding utf-8
+ call setline(1, 'a'->repeat(&columns) .. '口'->repeat(10))
+ setlocal smoothscroll
+ redraw
+ exe "norm \<C-E>"
+ redraw
+ " Check the chars one by one. Don't check the whole line concatenated.
+ call assert_equal('<', screenstring(1, 1))
+ call assert_equal('<', screenstring(1, 2))
+ call assert_equal('<', screenstring(1, 3))
+ call assert_equal(' ', screenstring(1, 4))
+ call assert_equal('口', screenstring(1, 5))
+ call assert_equal('口', screenstring(1, 7))
+ call assert_equal('口', screenstring(1, 9))
+ call assert_equal('口', screenstring(1, 11))
+ call assert_equal('口', screenstring(1, 13))
+ call assert_equal('口', screenstring(1, 15))
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [CODE]
+
+ let encodings = ['utf-8', 'cp932', 'cp936', 'cp949', 'cp950']
+ if !has('win32')
+ let encodings += ['euc-jp']
+ endif
+ if has('nvim')
+ let encodings = ['utf-8']
+ endif
+ for enc in encodings
+ let msg = 'enc=' .. enc
+ if RunVim([], after, $'--clean --cmd "set encoding={enc}"')
+ call assert_equal([], readfile('Xresult'), msg)
+ endif
+ call delete('Xresult')
+ endfor
+endfunc
+
+" Same as the test above, but check the text actually shown on screen.
+" Only run with UTF-8 encoding.
+func Test_smoothscroll_marker_over_double_width_dump()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, 'a'->repeat(&columns) .. '口'->repeat(10))
+ setlocal smoothscroll
+ END
+ call writefile(lines, 'XSmoothMarkerOverDoubleWidth', 'D')
+ let buf = RunVimInTerminal('-S XSmoothMarkerOverDoubleWidth', #{rows: 6, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_marker_over_double_width_1', {})
+
+ call term_sendkeys(buf, "\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_marker_over_double_width_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func s:check_col_calc(win_col, win_line, buf_col)
+ call assert_equal(a:win_col, wincol())
+ call assert_equal(a:win_line, winline())
+ call assert_equal(a:buf_col, col('.'))
+endfunc
+
+" Test that if the current cursor is on a smooth scrolled line, we correctly
+" reposition it. Also check that we don't miscalculate the values by checking
+" the consistency between wincol() and col('.') as they are calculated
+" separately in code.
+func Test_smoothscroll_cursor_position()
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ call setline(1, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+
+ call s:check_col_calc(1, 1, 1)
+ exe "normal \<C-E>"
+
+ " Move down another line to avoid blocking the <<< display
+ call s:check_col_calc(1, 2, 41)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(1, 3, 41)
+
+ " Test "g0/g<Home>"
+ exe "normal gg\<C-E>"
+ norm $gkg0
+ call s:check_col_calc(1, 2, 21)
+
+ " Test moving the cursor behind the <<< display with 'virtualedit'
+ set virtualedit=all
+ exe "normal \<C-E>3lgkh"
+ call s:check_col_calc(3, 2, 23)
+ set virtualedit&
+
+ normal gg3l
+ exe "normal \<C-E>"
+
+ " Move down only 1 line when we are out of the range of the <<< display
+ call s:check_col_calc(4, 1, 24)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(4, 2, 24)
+ normal ggg$
+ exe "normal \<C-E>"
+ call s:check_col_calc(20, 1, 40)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(20, 2, 40)
+ normal gg
+
+ " Test number, where we have indented lines
+ setl number
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+
+ " Move down only 1 line when the <<< display is on the number column
+ call s:check_col_calc(5, 1, 17)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(5, 2, 17)
+ normal ggg$
+ exe "normal \<C-E>"
+ call s:check_col_calc(20, 1, 32)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(20, 2, 32)
+ normal gg
+
+ setl numberwidth=1
+
+ " Move down another line when numberwidth is too short to cover the whole
+ " <<< display
+ call s:check_col_calc(3, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(3, 2, 37)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(3, 3, 37)
+ normal ggl
+
+ " Only move 1 line down when we are just past the <<< display
+ call s:check_col_calc(4, 1, 2)
+ exe "normal \<C-E>"
+ call s:check_col_calc(4, 1, 20)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(4, 2, 20)
+ normal gg
+ setl numberwidth&
+
+ " Test number + showbreak, so test that the additional indentation works
+ setl number showbreak=+++
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(8, 1, 17)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(8, 2, 17)
+ normal gg
+
+ " Test number + cpo+=n mode, where wrapped lines aren't indented
+ setl number cpo+=n showbreak=
+ call s:check_col_calc(5, 1, 1)
+ exe "normal \<C-E>"
+ call s:check_col_calc(1, 2, 37)
+ exe "normal \<C-Y>"
+ call s:check_col_calc(1, 3, 37)
+ normal gg
+
+ " Test list + listchars "precedes", where there is always 1 overlap
+ " regardless of number and cpo-=n.
+ setl number list listchars=precedes:< cpo-=n
+ call s:check_col_calc(5, 1, 1)
+ exe "normal 3|\<C-E>h"
+ call s:check_col_calc(6, 1, 18)
+ norm h
+ call s:check_col_calc(5, 2, 17)
+ normal gg
+
+ bwipe!
+endfunc
+
+func Test_smoothscroll_cursor_scrolloff()
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ setl scrolloff=3
+
+ " 120 chars are 6 screen lines
+ call setline(1, "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST")
+ call setline(2, "below")
+
+ call s:check_col_calc(1, 1, 1)
+
+ " CTRL-E shows "<<<DEFG...", cursor move four lines down
+ exe "normal \<C-E>"
+ call s:check_col_calc(1, 4, 81)
+
+ " cursor on start of second line, "gk" moves into first line, skipcol doesn't
+ " change
+ exe "normal G0gk"
+ call s:check_col_calc(1, 5, 101)
+
+ " move cursor left one window width worth, scrolls one screen line
+ exe "normal 20h"
+ call s:check_col_calc(1, 5, 81)
+
+ " move cursor left one window width worth, scrolls one screen line
+ exe "normal 20h"
+ call s:check_col_calc(1, 4, 61)
+
+ " cursor on last line, "gk" should not cause a scroll
+ set scrolloff=0
+ normal G0
+ call s:check_col_calc(1, 7, 1)
+ normal gk
+ call s:check_col_calc(1, 6, 101)
+
+ bwipe!
+endfunc
+
+
+" Test that mouse picking is still accurate when we have smooth scrolled lines
+func Test_smoothscroll_mouse_pos()
+ CheckNotGui
+ CheckUnix
+
+ let save_mouse = &mouse
+ "let save_term = &term
+ "let save_ttymouse = &ttymouse
+ set mouse=a "term=xterm ttymouse=xterm2
+
+ call NewWindow(10, 20)
+ setl smoothscroll wrap
+ " First line will wrap to 3 physical lines. 2nd/3rd lines are short lines.
+ call setline(1, ["abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "line 2", "line 3"])
+
+ func s:check_mouse_click(row, col, buf_row, buf_col)
+ call MouseLeftClick(a:row, a:col)
+
+ call assert_equal(a:col, wincol())
+ call assert_equal(a:row, winline())
+ call assert_equal(a:buf_row, line('.'))
+ call assert_equal(a:buf_col, col('.'))
+ endfunc
+
+ " Check that clicking without scroll works first.
+ call s:check_mouse_click(3, 5, 1, 45)
+ call s:check_mouse_click(4, 1, 2, 1)
+ call s:check_mouse_click(4, 6, 2, 6)
+ call s:check_mouse_click(5, 1, 3, 1)
+ call s:check_mouse_click(5, 6, 3, 6)
+
+ " Smooth scroll, and checks that this didn't mess up mouse clicking
+ exe "normal \<C-E>"
+ call s:check_mouse_click(2, 5, 1, 45)
+ call s:check_mouse_click(3, 1, 2, 1)
+ call s:check_mouse_click(3, 6, 2, 6)
+ call s:check_mouse_click(4, 1, 3, 1)
+ call s:check_mouse_click(4, 6, 3, 6)
+
+ exe "normal \<C-E>"
+ call s:check_mouse_click(1, 5, 1, 45)
+ call s:check_mouse_click(2, 1, 2, 1)
+ call s:check_mouse_click(2, 6, 2, 6)
+ call s:check_mouse_click(3, 1, 3, 1)
+ call s:check_mouse_click(3, 6, 3, 6)
+
+ " Make a new first line 11 physical lines tall so it's taller than window
+ " height, to test overflow calculations with really long lines wrapping.
+ normal gg
+ call setline(1, "12345678901234567890"->repeat(11))
+ exe "normal 6\<C-E>"
+ call s:check_mouse_click(5, 1, 1, 201)
+ call s:check_mouse_click(6, 1, 2, 1)
+ call s:check_mouse_click(7, 1, 3, 1)
+
+ let &mouse = save_mouse
+ "let &term = save_term
+ "let &ttymouse = save_ttymouse
+endfunc
+
+" this was dividing by zero
+func Test_smoothscroll_zero_width()
+ CheckScreendump
+
+ let lines =<< trim END
+ winsize 0 0
+ vsplit
+ vsplit
+ vsplit
+ vsplit
+ vsplit
+ sil norm H
+ set wrap
+ set smoothscroll
+ set number
+ END
+ call writefile(lines, 'XSmoothScrollZero', 'D')
+ let buf = RunVimInTerminal('-u NONE -i NONE -n -m -X -Z -e -s -S XSmoothScrollZero', #{rows: 6, cols: 60, wait_for_ruler: 0})
+ call VerifyScreenDump(buf, 'Test_smoothscroll_zero_1', {})
+
+ call term_sendkeys(buf, ":sil norm \<C-V>\<C-W>\<C-V>\<C-N>\<CR>")
+ call VerifyScreenDump(buf, 'Test_smoothscroll_zero_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" this was unnecessarily inserting lines
+func Test_smoothscroll_ins_lines()
+ CheckScreendump
+
+ let lines =<< trim END
+ set wrap
+ set smoothscroll
+ set scrolloff=0
+ set conceallevel=2
+ call setline(1, [
+ \'line one' .. 'with lots of text in one line '->repeat(2),
+ \'line two',
+ \'line three',
+ \'line four',
+ \'line five'
+ \])
+ END
+ call writefile(lines, 'XSmoothScrollInsLines', 'D')
+ let buf = RunVimInTerminal('-S XSmoothScrollInsLines', #{rows: 6, cols: 40})
+
+ call term_sendkeys(buf, "\<C-E>gjgk")
+ call VerifyScreenDump(buf, 'Test_smooth_ins_lines', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" this placed the cursor in the command line
+func Test_smoothscroll_cursormoved_line()
+ CheckScreendump
+
+ let lines =<< trim END
+ set smoothscroll
+ call setline(1, [
+ \'',
+ \'_'->repeat(&lines * &columns),
+ \(('_')->repeat(&columns - 2) .. 'xxx')->repeat(2)
+ \])
+ autocmd CursorMoved * eval [line('w0'), line('w$')]
+ call search('xxx')
+ END
+ call writefile(lines, 'XSmoothCursorMovedLine', 'D')
+ let buf = RunVimInTerminal('-S XSmoothCursorMovedLine', #{rows: 6})
+
+ call VerifyScreenDump(buf, 'Test_smooth_cursormoved_line', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_smoothscroll_eob()
+ CheckScreendump
+
+ let lines =<< trim END
+ set smoothscroll
+ call setline(1, ['']->repeat(100))
+ norm G
+ END
+ call writefile(lines, 'XSmoothEob', 'D')
+ let buf = RunVimInTerminal('-S XSmoothEob', #{rows: 10})
+
+ " does not scroll halfway when scrolling to end of buffer
+ call VerifyScreenDump(buf, 'Test_smooth_eob_1', {})
+
+ " cursor is not placed below window
+ call term_sendkeys(buf, ":call setline(92, 'a'->repeat(100))\<CR>\<C-B>G")
+ call VerifyScreenDump(buf, 'Test_smooth_eob_2', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" skipcol should not reset when doing incremental search on the same word
+func Test_smoothscroll_incsearch()
+ CheckScreendump
+
+ let lines =<< trim END
+ set smoothscroll number scrolloff=0 incsearch
+ call setline(1, repeat([''], 20))
+ call setline(11, repeat('a', 100))
+ call setline(14, 'bbbb')
+ END
+ call writefile(lines, 'XSmoothIncsearch', 'D')
+ let buf = RunVimInTerminal('-S XSmoothIncsearch', #{rows: 8, cols: 40})
+
+ call term_sendkeys(buf, "/b")
+ call VerifyScreenDump(buf, 'Test_smooth_incsearch_1', {})
+ call term_sendkeys(buf, "b")
+ call VerifyScreenDump(buf, 'Test_smooth_incsearch_2', {})
+ call term_sendkeys(buf, "b")
+ call VerifyScreenDump(buf, 'Test_smooth_incsearch_3', {})
+ call term_sendkeys(buf, "b")
+ call VerifyScreenDump(buf, 'Test_smooth_incsearch_4', {})
+ call term_sendkeys(buf, "\<CR>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Test scrolling multiple lines and stopping at non-zero skipcol.
+func Test_smoothscroll_multi_skipcol()
+ CheckScreendump
+
+ let lines =<< trim END
+ setlocal cursorline scrolloff=0 smoothscroll
+ call setline(1, repeat([''], 8))
+ call setline(3, repeat('a', 50))
+ call setline(4, repeat('a', 50))
+ call setline(7, 'bbb')
+ call setline(8, 'ccc')
+ redraw
+ END
+ call writefile(lines, 'XSmoothMultiSkipcol', 'D')
+ let buf = RunVimInTerminal('-S XSmoothMultiSkipcol', #{rows: 10, cols: 40})
+ call VerifyScreenDump(buf, 'Test_smooth_multi_skipcol_1', {})
+
+ call term_sendkeys(buf, "3\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_multi_skipcol_2', {})
+
+ call term_sendkeys(buf, "2\<C-E>")
+ call VerifyScreenDump(buf, 'Test_smooth_multi_skipcol_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" this was dividing by zero bug in scroll_cursor_bot
+func Test_smoothscroll_zero_width_scroll_cursor_bot()
+ CheckScreendump
+
+ let lines =<< trim END
+ silent normal yy
+ silent normal 19p
+ set cpoptions+=n
+ vsplit
+ vertical resize 0
+ set foldcolumn=1
+ set number
+ set smoothscroll
+ silent normal 20G
+ END
+ call writefile(lines, 'XSmoothScrollZeroBot', 'D')
+ let buf = RunVimInTerminal('-u NONE -S XSmoothScrollZeroBot', #{rows: 19})
+ call VerifyScreenDump(buf, 'Test_smoothscroll_zero_bot', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" scroll_cursor_top() should reset skipcol when it changes topline
+func Test_smoothscroll_cursor_top()
+ CheckScreendump
+
+ let lines =<< trim END
+ set smoothscroll scrolloff=2
+ new | 11resize | wincmd j
+ call setline(1, ['line1', 'line2', 'line3'->repeat(20), 'line4'])
+ exe "norm G3\<C-E>k"
+ END
+ call writefile(lines, 'XSmoothScrollCursorTop', 'D')
+ let buf = RunVimInTerminal('-u NONE -S XSmoothScrollCursorTop', #{rows: 12, cols:40})
+ call VerifyScreenDump(buf, 'Test_smoothscroll_cursor_top', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" Division by zero, shouldn't crash
+func Test_smoothscroll_crash()
+ CheckScreendump
+
+ let lines =<< trim END
+ 20 new
+ vsp
+ put =repeat('aaaa', 20)
+ set nu fdc=1 smoothscroll cpo+=n
+ vert resize 0
+ exe "norm! 0\<c-e>"
+ END
+ call writefile(lines, 'XSmoothScrollCrash', 'D')
+ let buf = RunVimInTerminal('-u NONE -S XSmoothScrollCrash', #{rows: 12, cols:40})
+ call term_sendkeys(buf, "2\<C-E>\<C-L>")
+
+ call StopVimInTerminal(buf)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_scrollbind.vim b/test/old/testdir/test_scrollbind.vim
index 6c5488be05..f67a559c60 100644
--- a/src/nvim/testdir/test_scrollbind.vim
+++ b/test/old/testdir/test_scrollbind.vim
@@ -217,8 +217,8 @@ end of window 2
\ '7 line 02 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
\ '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ 02',
\ 'UTSRQPONMLKJIHGREDCBA9876543210 02',
- \ '. line 11 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 11',
- \ '. line 11 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 11',
+ \ '. line 10 ZYXWVUTSRQPONMLKJIHGREDCBA9876543210 10',
+ \ '. line 10 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ 10',
\ ''], getline(1, '$'))
enew!
diff --git a/src/nvim/testdir/test_search.vim b/test/old/testdir/test_search.vim
index 885043accf..018ee7ad5a 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/test/old/testdir/test_search.vim
@@ -377,9 +377,9 @@ func Test_searchpairpos()
endfunc
func Test_searchpair_errors()
- call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
- call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
- call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
+ call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
+ call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
+ call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
@@ -388,9 +388,9 @@ func Test_searchpair_errors()
endfunc
func Test_searchpairpos_errors()
- call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String')
- call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String')
- call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String')
+ call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: Using a List as a String')
+ call assert_fails("call searchpairpos('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: Using a Funcref as a String')
+ call assert_fails("call searchpairpos('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: Using a Dictionary as a String')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99')
call assert_fails("call searchpairpos('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100')
@@ -815,12 +815,12 @@ func Test_search_cmdline_incsearch_highlight_attr()
call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])})
" wait for vim to complete initialization
- call term_wait(buf)
+ call TermWait(buf)
" Get attr of normal(a0), incsearch(a1), hlsearch(a2) highlight
call term_sendkeys(buf, ":set incsearch hlsearch\<cr>")
call term_sendkeys(buf, '/b')
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
let screen_line1 = term_scrape(buf, 1)
call assert_true(len(screen_line1) > 2)
" a0: attr_normal
@@ -836,7 +836,7 @@ func Test_search_cmdline_incsearch_highlight_attr()
" Test incremental highlight search
call term_sendkeys(buf, "/vim")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
" Buffer:
" abb vim vim vi
" vimvivim
@@ -848,7 +848,7 @@ func Test_search_cmdline_incsearch_highlight_attr()
" Test <C-g>
call term_sendkeys(buf, "\<C-g>\<C-g>")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
let attr_line2 = [a1,a1,a1,a0,a0,a2,a2,a2]
call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
@@ -856,7 +856,7 @@ func Test_search_cmdline_incsearch_highlight_attr()
" Test <C-t>
call term_sendkeys(buf, "\<C-t>")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a1,a1,a1,a0,a0,a0]
let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
@@ -864,7 +864,7 @@ func Test_search_cmdline_incsearch_highlight_attr()
" Type Enter and a1(incsearch highlight) should become a2(hlsearch highlight)
call term_sendkeys(buf, "\<cr>")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
let attr_line1 = [a0,a0,a0,a0,a2,a2,a2,a0,a2,a2,a2,a0,a0,a0]
let attr_line2 = [a2,a2,a2,a0,a0,a2,a2,a2]
call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
@@ -874,7 +874,7 @@ func Test_search_cmdline_incsearch_highlight_attr()
call term_sendkeys(buf, ":1\<cr>")
call term_sendkeys(buf, ":set nohlsearch\<cr>")
call term_sendkeys(buf, "/vim")
- call term_wait(buf, 200)
+ call TermWait(buf, 100)
let attr_line1 = [a0,a0,a0,a0,a1,a1,a1,a0,a0,a0,a0,a0,a0,a0]
let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0]
call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr'))
@@ -1762,6 +1762,8 @@ func Test_invalid_regexp()
call assert_fails("call search('\\(')", 'E54:')
call assert_fails("call search('\\)')", 'E55:')
call assert_fails("call search('\\z\\(\\)')", 'E66:')
+ call assert_fails("call search('\\z2')", 'E67:')
+ call assert_fails("call search('\\zx')", 'E867:')
call assert_fails("call search('\\%[ab')", 'E69:')
call assert_fails("call search('\\%[]')", 'E70:')
call assert_fails("call search('\\%9999999999999999999999999999v')", 'E951:')
@@ -1996,7 +1998,7 @@ func Test_incsearch_substitute_dump2()
\ 'endfor',
\ 'call setline(5, "abc|def")',
\ '3',
- \ ], 'Xis_subst_script2')
+ \ ], 'Xis_subst_script2', 'D')
let buf = RunVimInTerminal('-S Xis_subst_script2', {'rows': 9, 'cols': 70})
call term_sendkeys(buf, ':%s/\vabc|')
@@ -2011,7 +2013,30 @@ func Test_incsearch_substitute_dump2()
call StopVimInTerminal(buf)
- call delete('Xis_subst_script2')
+endfunc
+
+func Test_incsearch_restore_view()
+ CheckOption incsearch
+ CheckScreendump
+
+ let lines =<< trim [CODE]
+ set incsearch nohlsearch
+ setlocal scrolloff=0 smoothscroll
+ call setline(1, [join(range(25), ' '), '', '', '', '', 'xxx'])
+ call feedkeys("2\<C-E>", 't')
+ [CODE]
+ call writefile(lines, 'Xincsearch_restore_view', 'D')
+ let buf = RunVimInTerminal('-S Xincsearch_restore_view', {'rows': 6, 'cols': 20})
+
+ call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {})
+ call term_sendkeys(buf, '/xx')
+ call VerifyScreenDump(buf, 'Test_incsearch_restore_view_02', {})
+ call term_sendkeys(buf, 'x')
+ call VerifyScreenDump(buf, 'Test_incsearch_restore_view_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_restore_view_01', {})
+
+ call StopVimInTerminal(buf)
endfunc
func Test_pattern_is_uppercase_smartcase()
diff --git a/src/nvim/testdir/test_search_stat.vim b/test/old/testdir/test_search_stat.vim
index 1b2d854829..a2523fc6c7 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/test/old/testdir/test_search_stat.vim
@@ -153,7 +153,6 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! n')
let stat = 'W \[20/1\]'
call assert_match(pat .. stat, g:a)
- call assert_match('search hit BOTTOM, continuing at TOP', g:a)
set norl
endif
@@ -164,7 +163,6 @@ func Test_search_stat()
let g:a = execute(':unsilent :norm! N')
let stat = 'W \[20/20\]'
call assert_match(pat .. stat, g:a)
- call assert_match('search hit TOP, continuing at BOTTOM', g:a)
call assert_match('W \[20/20\]', Screenline(&lines))
" normal, no match
@@ -259,7 +257,7 @@ func Test_search_stat()
endfunc
func Test_searchcount_fails()
- call assert_fails('echo searchcount("boo!")', 'E715:')
+ call assert_fails('echo searchcount("boo!")', 'E1206:')
call assert_fails('echo searchcount({"timeout" : []})', 'E745:')
call assert_fails('echo searchcount({"maxcount" : []})', 'E745:')
call assert_fails('echo searchcount({"pattern" : []})', 'E730:')
@@ -335,15 +333,12 @@ func Test_search_stat_foldopen()
call writefile(lines, 'Xsearchstat1')
let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10})
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_searchstat_3', {})
call term_sendkeys(buf, "n")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_searchstat_3', {})
call term_sendkeys(buf, "n")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_searchstat_3', {})
call StopVimInTerminal(buf)
@@ -366,12 +361,10 @@ func! Test_search_stat_screendump()
END
call writefile(lines, 'Xsearchstat')
let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10})
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_searchstat_1', {})
call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>")
call term_sendkeys(buf, "gg0n")
- call term_wait(buf)
call VerifyScreenDump(buf, 'Test_searchstat_2', {})
call StopVimInTerminal(buf)
@@ -390,11 +383,9 @@ func Test_search_stat_then_gd()
let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10})
call term_sendkeys(buf, "/dog\<CR>")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_searchstatgd_1', {})
call term_sendkeys(buf, "G0gD")
- call TermWait(buf)
call VerifyScreenDump(buf, 'Test_searchstatgd_2', {})
call StopVimInTerminal(buf)
@@ -422,7 +413,7 @@ func Test_search_stat_and_incsearch()
set tabline=%!MyTabLine()
END
- call writefile(lines, 'Xsearchstat_inc')
+ call writefile(lines, 'Xsearchstat_inc', 'D')
let buf = RunVimInTerminal('-S Xsearchstat_inc', #{rows: 10})
call term_sendkeys(buf, "/abc")
@@ -441,8 +432,35 @@ func Test_search_stat_and_incsearch()
call TermWait(buf)
call StopVimInTerminal(buf)
- call delete('Xsearchstat_inc')
endfunc
+func Test_search_stat_backwards()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shm-=S
+ call setline(1, ['test', ''])
+ END
+ call writefile(lines, 'Xsearchstat_back', 'D')
+
+ let buf = RunVimInTerminal('-S Xsearchstat_back', #{rows: 10})
+ call term_sendkeys(buf, "*")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_back_1', {})
+
+ call term_sendkeys(buf, "N")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_back_2', {})
+
+ call term_sendkeys(buf, ":set shm+=S\<cr>N")
+ call TermWait(buf)
+ " shows "Search Hit Bottom.."
+ call VerifyScreenDump(buf, 'Test_searchstat_back_3', {})
+
+ call term_sendkeys(buf, "\<esc>:qa\<cr>")
+ call TermWait(buf)
+
+ call StopVimInTerminal(buf)
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_searchpos.vim b/test/old/testdir/test_searchpos.vim
index dd13c305c5..dd13c305c5 100644
--- a/src/nvim/testdir/test_searchpos.vim
+++ b/test/old/testdir/test_searchpos.vim
diff --git a/src/nvim/testdir/test_selectmode.vim b/test/old/testdir/test_selectmode.vim
index 041f0592f1..59a1deba65 100644
--- a/src/nvim/testdir/test_selectmode.vim
+++ b/test/old/testdir/test_selectmode.vim
@@ -1,6 +1,11 @@
" Test for Select-mode
+source check.vim
+" CheckNotGui
+" CheckUnix
+
source shared.vim
+source mouse.vim
" Test for select mode
func Test_selectmode_basic()
@@ -155,6 +160,106 @@ func Test_select_mode_map()
close!
endfunc
+" Test double/triple/quadruple click to start 'select' mode
+func Test_term_mouse_multiple_clicks_to_select_mode()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+
+ " 'mousetime' must be sufficiently large, or else the test is flaky when
+ " using a ssh connection with X forwarding; i.e. ssh -X.
+ " set mouse=a term=xterm mousetime=1000
+ set mouse=a mousetime=1000
+ set selectmode=mouse
+ new
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ " Single-click and drag should 'select' the characters
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftDrag(1, 13)
+ call MouseLeftRelease(1, 13)
+ norm! o
+ call assert_equal(['foo foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on word should visually 'select' the word.
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal('s', mode(), msg)
+ norm! bar
+ call assert_equal(['bar [foo bar] foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on opening square bracket should visually
+ " 'select' the whole [foo bar].
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 5)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 5)
+ call assert_equal('s', mode(), msg)
+ norm! bar
+ call assert_equal(['foo bar foo', 'foo'], getline(1, '$'), msg)
+
+ " To guarantee that the next click is not counted as a triple click
+ call MouseRightClick(1, 1)
+ call MouseRightRelease(1, 1)
+
+ " Triple-click should visually 'select' the whole line.
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 3)
+ call assert_equal('S', mode(), msg)
+ norm! baz
+ call assert_equal(['bazfoo'], getline(1, '$'), msg)
+
+ " Quadruple-click should start visual block 'select'.
+ call setline(1, ['aaaaaa', 'bbbbbb'])
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(96, getcharmod(), msg) " quadruple-click
+ call MouseLeftDrag(2, 4)
+ call MouseLeftRelease(2, 4)
+ call assert_equal("\<c-s>", mode(), msg)
+ norm! x
+ call assert_equal(['axaa', 'bxbb'], getline(1, '$'), msg)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ set mousetime&
+ set selectmode&
+ " call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
" Test for selecting a register with CTRL-R
func Test_selectmode_register()
new
@@ -207,4 +312,15 @@ func Test_selectmode_register()
bw!
endfunc
+func Test_ins_ctrl_o_in_insert_mode_resets_selectmode()
+ new
+ " ctrl-o in insert mode resets restart_VIsual_select
+ call setline(1, 'abcdef')
+ call cursor(1, 1)
+ exe "norm! \<c-v>\<c-g>\<c-o>c\<c-o>\<c-v>\<right>\<right>IABC"
+ call assert_equal('ABCbcdef', getline(1))
+
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_set.vim b/test/old/testdir/test_set.vim
index 7215772a00..7215772a00 100644
--- a/src/nvim/testdir/test_set.vim
+++ b/test/old/testdir/test_set.vim
diff --git a/src/nvim/testdir/test_sha256.vim b/test/old/testdir/test_sha256.vim
index f6f430b04e..f6f430b04e 100644
--- a/src/nvim/testdir/test_sha256.vim
+++ b/test/old/testdir/test_sha256.vim
diff --git a/src/nvim/testdir/test_shell.vim b/test/old/testdir/test_shell.vim
index 8b9c7a5b12..c50161a8ed 100644
--- a/src/nvim/testdir/test_shell.vim
+++ b/test/old/testdir/test_shell.vim
@@ -5,6 +5,11 @@ source check.vim
source shared.vim
func Test_shell_options()
+ if has('win32')
+ " FIXME: This test is flaky on MS-Windows.
+ let g:test_is_flaky = 1
+ endif
+
" The expected value of 'shellcmdflag', 'shellpipe', 'shellquote',
" 'shellredir', 'shellxescape', 'shellxquote' for the supported shells.
let shells = []
@@ -22,7 +27,7 @@ func Test_shell_options()
\ ['tcsh', '-c', '|& tee', '', '>&', '', '']]
endif
if has('win32')
- let shells += [['cmd', '/s /c', '>%s 2>&1', '', '>%s 2>&1', '', '"']]
+ let shells += [['cmd', '/s /c', '2>&1| tee', '', '>%s 2>&1', '', '"']]
endif
" start a new Vim instance with 'shell' set to each of the supported shells
@@ -206,4 +211,48 @@ func Test_set_shell()
call delete('Xtestout')
endfunc
+func Test_shell_repeat()
+ CheckUnix
+
+ let save_shell = &shell
+
+ call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D')
+ call setfperm('Xtestshell', "r-x------")
+ set shell=./Xtestshell
+ defer delete('Xlog')
+
+ call feedkeys(":!echo coconut\<CR>", 'xt') " Run command
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call feedkeys(":!!\<CR>", 'xt') " Re-run previous
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call writefile(['empty'], 'Xlog')
+ call feedkeys(":!\<CR>", 'xt') " :!
+ call assert_equal(['Cmd: [-c ]'], readfile('Xlog'))
+
+ call feedkeys(":!!\<CR>", 'xt') " :! doesn't clear previous command
+ call assert_equal(['Cmd: [-c echo coconut]'], readfile('Xlog'))
+
+ call feedkeys(":!echo banana\<CR>", 'xt') " Make sure setting previous command keeps working after a :! no-op
+ call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
+ call feedkeys(":!!\<CR>", 'xt')
+ call assert_equal(['Cmd: [-c echo banana]'], readfile('Xlog'))
+
+ let &shell = save_shell
+endfunc
+
+func Test_shell_no_prevcmd()
+ " this doesn't do anything, just check it doesn't crash
+ let after =<< trim END
+ exe "normal !!\<CR>"
+ call writefile([v:errmsg, 'done'], 'Xtestdone')
+ qall!
+ END
+ if RunVim([], after, '--clean')
+ call assert_equal(['E34: No previous command', 'done'], readfile('Xtestdone'))
+ endif
+ call delete('Xtestdone')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_shift.vim b/test/old/testdir/test_shift.vim
index ec357dac88..ec357dac88 100644
--- a/src/nvim/testdir/test_shift.vim
+++ b/test/old/testdir/test_shift.vim
diff --git a/src/nvim/testdir/test_signals.vim b/test/old/testdir/test_signals.vim
index c291c68e0d..667448a7c2 100644
--- a/src/nvim/testdir/test_signals.vim
+++ b/test/old/testdir/test_signals.vim
@@ -100,8 +100,8 @@ func Test_signal_INT()
call term_sendkeys(buf, ":while 1 | endwhile\n")
call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))})
exe 'silent !kill -s INT ' .. pid_vim
- call term_sendkeys(buf, ":call setline(1, 'INTERUPTED')\n")
- call WaitForAssert({-> assert_equal('INTERUPTED', term_getline(buf, 1))})
+ call term_sendkeys(buf, ":call setline(1, 'INTERRUPTED')\n")
+ call WaitForAssert({-> assert_equal('INTERRUPTED', term_getline(buf, 1))})
call StopVimInTerminal(buf)
endfunc
diff --git a/src/nvim/testdir/test_signs.vim b/test/old/testdir/test_signs.vim
index 8311955a15..d7baa7e870 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/test/old/testdir/test_signs.vim
@@ -194,6 +194,25 @@ func Test_sign()
sign undefine Sign3
call assert_fails("sign place 41 line=3 name=Sign1 buffer=" .
\ bufnr('%'), 'E155:')
+
+ " Defining a sign without attributes is allowed.
+ sign define Sign1
+ call assert_equal([{'name': 'Sign1'}], sign_getdefined())
+ sign undefine Sign1
+endfunc
+
+func Test_sign_many_bytes()
+ new
+ set signcolumn=number
+ set number
+ call setline(1, 'some text')
+ " composing characters can use many bytes, check for overflow
+ sign define manyBytes text=▶᷄᷅᷆◀᷄᷅᷆᷇
+ sign place 17 line=1 name=manyBytes
+ redraw
+
+ bwipe!
+ sign undefine manyBytes
endfunc
" Undefining placed sign is not recommended.
@@ -449,7 +468,7 @@ func Test_sign_funcs()
call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:')
" call assert_fails('call sign_define("sign5", {"text" : ""})', 'E239:')
call assert_fails('call sign_define({})', 'E731:')
- call assert_fails('call sign_define("sign6", [])', 'E715:')
+ call assert_fails('call sign_define("sign6", [])', 'E1206:')
" Tests for sign_getdefined()
call assert_equal([], sign_getdefined("none"))
@@ -476,8 +495,7 @@ func Test_sign_funcs()
" Tests for invalid arguments to sign_place()
call assert_fails('call sign_place([], "", "mySign", 1)', 'E745:')
call assert_fails('call sign_place(5, "", "mySign", -1)', 'E158:')
- call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])',
- \ 'E715:')
+ call assert_fails('call sign_place(-1, "", "sign1", "Xsign", [])', 'E1206:')
call assert_fails('call sign_place(-1, "", "sign1", "Xsign",
\ {"lnum" : 30})', 'E474:')
call assert_fails('call sign_place(10, "", "xsign1x", "Xsign",
@@ -512,7 +530,7 @@ func Test_sign_funcs()
call assert_fails("call sign_getplaced('dummy.sign')", 'E158:')
call assert_fails('call sign_getplaced("&")', 'E158:')
call assert_fails('call sign_getplaced(-1)', 'E158:')
- call assert_fails('call sign_getplaced("Xsign", [])', 'E715:')
+ call assert_fails('call sign_getplaced("Xsign", [])', 'E1206:')
call assert_equal([{'bufnr' : bufnr(''), 'signs' : []}],
\ sign_getplaced('Xsign', {'lnum' : 1000000}))
call assert_fails("call sign_getplaced('Xsign', {'lnum' : []})",
@@ -535,7 +553,7 @@ func Test_sign_funcs()
\ {'id' : 20, 'buffer' : '&'})", 'E158:')
call assert_fails("call sign_unplace('g1',
\ {'id' : 20, 'buffer' : 200})", 'E158:')
- call assert_fails("call sign_unplace('g1', 'mySign')", 'E715:')
+ call assert_fails("call sign_unplace('g1', 'mySign')", 'E1206:')
call sign_unplace('*')
@@ -687,7 +705,7 @@ func Test_sign_group()
call assert_equal([], sign_getplaced(bnum, {'group' : '*'})[0].signs)
" Error case
- call assert_fails("call sign_unplace({})", 'E474:')
+ call assert_fails("call sign_unplace({})", 'E1174:')
" Place a sign in the global group and try to delete it using a group
call assert_equal(5, sign_place(5, '', 'sign1', bnum, {'lnum' : 10}))
@@ -1488,50 +1506,33 @@ func Test_sign_priority()
call sign_place(3, '', 'sign3', 'Xsign',
\ {'lnum' : 4, 'priority' : 20})
let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
+ let se = [
\ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
\ 'priority' : 20},
\ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
\ 'priority' : 20},
\ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20}],
- \ s[0].signs)
+ \ 'priority' : 20}]
+ call assert_equal(se, s[0].signs)
+
+ " Nvim: signs are always sorted lnum->priority->sign_id->last_modified
+ " Last modified does not take precedence over sign_id here.
+
" Place the last sign again with the same priority
call sign_place(1, '', 'sign1', 'Xsign',
\ {'lnum' : 4, 'priority' : 20})
let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20}],
- \ s[0].signs)
+ call assert_equal(se, s[0].signs)
" Place the first sign again with the same priority
call sign_place(1, '', 'sign1', 'Xsign',
\ {'lnum' : 4, 'priority' : 20})
let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20}],
- \ s[0].signs)
+ call assert_equal(se, s[0].signs)
" Place the middle sign again with the same priority
call sign_place(3, '', 'sign3', 'Xsign',
\ {'lnum' : 4, 'priority' : 20})
let s = sign_getplaced('Xsign', {'group' : '*'})
- call assert_equal([
- \ {'id' : 3, 'name' : 'sign3', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20},
- \ {'id' : 2, 'name' : 'sign2', 'lnum' : 4, 'group' : '',
- \ 'priority' : 20}],
- \ s[0].signs)
+ call assert_equal(se, s[0].signs)
call sign_unplace('*')
@@ -1554,8 +1555,7 @@ func Test_sign_priority()
\ s[0].signs)
" Error case
- call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
- \ [])", 'E715:')
+ call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign', [])", 'E1206:')
call assert_fails("call sign_place(1, 'g1', 'sign1', 'Xsign',
\ {'priority' : []})", 'E745:')
call sign_unplace('*')
@@ -1656,36 +1656,35 @@ func Test_sign_lnum_adjust()
" Break the undo. Otherwise the undo operation below will undo all the
" changes made by this function.
- let &undolevels=&undolevels
+ let &g:undolevels=&g:undolevels
- " Nvim: make sign adjustment when deleting lines match Vim
- set signcolumn=yes:1
+ " Nvim: deleting a line removes the signs along with it.
- " Delete the line with the sign
- call deletebufline('', 4)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(4, l[0].signs[0].lnum)
+ " " Delete the line with the sign
+ " call deletebufline('', 4)
+ " let l = sign_getplaced(bufnr(''))
+ " call assert_equal(4, l[0].signs[0].lnum)
- " Undo the delete operation
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
+ " " Undo the delete operation
+ " undo
+ " let l = sign_getplaced(bufnr(''))
+ " call assert_equal(5, l[0].signs[0].lnum)
- " Break the undo
- let &undolevels=&undolevels
+ " " Break the undo
+ " let &g:undolevels=&g:undolevels
- " Delete few lines at the end of the buffer including the line with the sign
- " Sign line number should not change (as it is placed outside of the buffer)
- call deletebufline('', 3, 6)
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
+ " " Delete few lines at the end of the buffer including the line with the sign
+ " " Sign line number should not change (as it is placed outside of the buffer)
+ " call deletebufline('', 3, 6)
+ " let l = sign_getplaced(bufnr(''))
+ " call assert_equal(5, l[0].signs[0].lnum)
- " Undo the delete operation. Sign should be restored to the previous line
- undo
- let l = sign_getplaced(bufnr(''))
- call assert_equal(5, l[0].signs[0].lnum)
+ " " Undo the delete operation. Sign should be restored to the previous line
+ " undo
+ " let l = sign_getplaced(bufnr(''))
+ " call assert_equal(5, l[0].signs[0].lnum)
- set signcolumn&
+ " set signcolumn&
sign unplace * group=*
sign undefine sign1
@@ -1959,7 +1958,8 @@ func Test_sign_funcs_multi()
call sign_unplace('*')
" Place multiple signs at once with auto-generated sign identifier
- call assert_equal([1, 1, 5], sign_placelist([
+ " Nvim: next sign id is not reset and is always incremented
+ call assert_equal([2, 3, 4], sign_placelist([
\ {'group' : 'g1', 'name' : 'sign1',
\ 'buffer' : 'Xsign', 'lnum' : 11},
\ {'group' : 'g2', 'name' : 'sign2',
@@ -1968,17 +1968,17 @@ func Test_sign_funcs_multi()
\ 'buffer' : 'Xsign', 'lnum' : 11}]))
let s = sign_getplaced('Xsign', {'group' : '*'})
call assert_equal([
- \ {'id' : 5, 'name' : 'sign3', 'lnum' : 11,
+ \ {'id' : 4, 'name' : 'sign3', 'lnum' : 11,
\ 'group' : '', 'priority' : 10},
- \ {'id' : 1, 'name' : 'sign2', 'lnum' : 11,
+ \ {'id' : 3, 'name' : 'sign2', 'lnum' : 11,
\ 'group' : 'g2', 'priority' : 10},
- \ {'id' : 1, 'name' : 'sign1', 'lnum' : 11,
+ \ {'id' : 2, 'name' : 'sign1', 'lnum' : 11,
\ 'group' : 'g1', 'priority' : 10}], s[0].signs)
" Change an existing sign without specifying the group
- call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist())
- let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''})
- call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11,
+ call assert_equal([4], [{'id' : 4, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist())
+ let s = sign_getplaced('Xsign', {'id' : 4, 'group' : ''})
+ call assert_equal([{'id' : 4, 'name' : 'sign1', 'lnum' : 11,
\ 'group' : '', 'priority' : 10}], s[0].signs)
" Place a sign using '.' as the line number
@@ -2005,8 +2005,8 @@ func Test_sign_funcs_multi()
call assert_fails('call sign_placelist([100])', "E715:")
" Unplace multiple signs
- call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 5},
- \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}]))
+ call assert_equal([0, 0, 0], sign_unplacelist([{'id' : 4},
+ \ {'id' : 2, 'group' : 'g1'}, {'id' : 3, 'group' : 'g2'}]))
" Invalid arguments
call assert_equal([], []->sign_unplacelist())
diff --git a/src/nvim/testdir/test_sleep.vim b/test/old/testdir/test_sleep.vim
index a428f380b0..a428f380b0 100644
--- a/src/nvim/testdir/test_sleep.vim
+++ b/test/old/testdir/test_sleep.vim
diff --git a/src/nvim/testdir/test_smartindent.vim b/test/old/testdir/test_smartindent.vim
index e2d028e828..e2d028e828 100644
--- a/src/nvim/testdir/test_smartindent.vim
+++ b/test/old/testdir/test_smartindent.vim
diff --git a/src/nvim/testdir/test_sort.vim b/test/old/testdir/test_sort.vim
index 534393b724..94a35e3cb5 100644
--- a/src/nvim/testdir/test_sort.vim
+++ b/test/old/testdir/test_sort.vim
@@ -12,7 +12,6 @@ func Compare2(a, b) abort
endfunc
func Test_sort_strings()
- CheckNotMSWindows " FIXME: Why does this fail with MSVC?
" numbers compared as strings
call assert_equal([1, 2, 3], sort([3, 2, 1]))
call assert_equal([13, 28, 3], sort([3, 28, 13]))
diff --git a/src/nvim/testdir/test_source.vim b/test/old/testdir/test_source.vim
index d4d96e36bf..d4d96e36bf 100644
--- a/src/nvim/testdir/test_source.vim
+++ b/test/old/testdir/test_source.vim
diff --git a/src/nvim/testdir/test_source_utf8.vim b/test/old/testdir/test_source_utf8.vim
index 66fabe0442..66fabe0442 100644
--- a/src/nvim/testdir/test_source_utf8.vim
+++ b/test/old/testdir/test_source_utf8.vim
diff --git a/src/nvim/testdir/test_spell.vim b/test/old/testdir/test_spell.vim
index c840e834b9..a19b64a7de 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/test/old/testdir/test_spell.vim
@@ -131,6 +131,26 @@ foobar/?
set spell&
endfunc
+func Test_spell_camelcase()
+ set spell spelloptions=camel
+ let words = [
+ \ 'UPPER',
+ \ 'lower',
+ \ 'mixedCase',
+ \ 'HTML',
+ \ 'XMLHttpRequest',
+ \ 'foo123bar',
+ \ '12345678',
+ \ 'HELLO123world',
+ \]
+
+ for word in words
+ call assert_equal(['', ''], spellbadword(word))
+ endfor
+
+ set spell& spelloptions&
+endfunc
+
func Test_spell_file_missing()
let s:spell_file_missing = 0
augroup TestSpellFileMissing
@@ -274,14 +294,13 @@ func Test_compl_with_CTRL_X_CTRL_K_using_spell()
call assert_equal(['theater'], getline(1, '$'))
set spelllang=en_gb
call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
- " FIXME: commented out, expected theatre bug got theater. See issue #7025.
- " call assert_equal(['theatre'], getline(1, '$'))
+ call assert_equal(['theatre'], getline(1, '$'))
bwipe!
set spell& spelllang& dictionary& ignorecase&
endfunc
-func Test_spellreall()
+func Test_spellrepall()
new
set spell
call assert_fails('spellrepall', 'E752:')
@@ -492,7 +511,7 @@ func Test_spellsuggest_option_expr()
bwipe!
endfunc
-" Test for 'spellsuggest' expr errrors
+" Test for 'spellsuggest' expr errors
func Test_spellsuggest_expr_errors()
" 'spellsuggest'
func MySuggest()
@@ -962,6 +981,7 @@ func Test_spell_screendump()
CheckScreendump
let lines =<< trim END
+ call test_override('alloc_lines', 1)
call setline(1, [
\ "This is some text without any spell errors. Everything",
\ "should just be black, nothing wrong here.",
@@ -972,28 +992,102 @@ func Test_spell_screendump()
\ ])
set spell spelllang=en_nz
END
- call writefile(lines, 'XtestSpell')
+ call writefile(lines, 'XtestSpell', 'D')
let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
call VerifyScreenDump(buf, 'Test_spell_1', {})
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_spell_screendump_spellcap()
+ CheckScreendump
+
let lines =<< trim END
+ call test_override('alloc_lines', 1)
call setline(1, [
- \ "This is some text without any spell errors. Everything",
- \ "should just be black, nothing wrong here.",
+ \ " This line has a sepll error. and missing caps and trailing spaces. ",
+ \ "another missing cap here.",
\ "",
- \ "This line has a sepll error. and missing caps.",
- \ "And and this is the the duplication.",
- \ "with missing caps here.",
+ \ "and here.",
+ \ " ",
+ \ "and here."
\ ])
- set spell spelllang=en_nz
+ set spell spelllang=en
END
- call writefile(lines, 'XtestSpell')
- let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
- call VerifyScreenDump(buf, 'Test_spell_1', {})
+ call writefile(lines, 'XtestSpellCap', 'D')
+ let buf = RunVimInTerminal('-S XtestSpellCap', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_2', {})
+
+ " After adding word missing Cap in next line is updated
+ call term_sendkeys(buf, "3GANot\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_3', {})
+
+ " Deleting a full stop removes missing Cap in next line
+ call term_sendkeys(buf, "5Gdd\<C-L>k$x")
+ call VerifyScreenDump(buf, 'Test_spell_4', {})
+
+ " Undo also updates the next line (go to command line to remove message)
+ call term_sendkeys(buf, "u:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_5', {})
+
+ " Folding an empty line does not remove Cap in next line
+ call term_sendkeys(buf, "uzfk:\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_6', {})
+
+ " Folding the end of a sentence does not remove Cap in next line
+ " and editing a line does not remove Cap in current line
+ call term_sendkeys(buf, "Jzfkk$x")
+ call VerifyScreenDump(buf, 'Test_spell_7', {})
+
+ " Cap is correctly applied in the first row of a window
+ call term_sendkeys(buf, "\<C-E>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_spell_8', {})
+
+ " Adding an empty line does not remove Cap in "mod_bot" area
+ call term_sendkeys(buf, "zbO\<Esc>")
+ call VerifyScreenDump(buf, 'Test_spell_9', {})
+
+ " Multiple empty lines does not remove Cap in the line after
+ call term_sendkeys(buf, "O\<Esc>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_spell_10', {})
" clean up
call StopVimInTerminal(buf)
- call delete('XtestSpell')
+endfunc
+
+func Test_spell_compatible()
+ CheckScreendump
+
+ let lines =<< trim END
+ call test_override('alloc_lines', 1)
+ call setline(1, [
+ \ "test "->repeat(20),
+ \ "",
+ \ "end",
+ \ ])
+ set spell cpo+=$
+ END
+ call writefile(lines, 'XtestSpellComp', 'D')
+ let buf = RunVimInTerminal('-S XtestSpellComp', {'rows': 8})
+
+ call term_sendkeys(buf, "51|C")
+ call VerifyScreenDump(buf, 'Test_spell_compatible_1', {})
+
+ call term_sendkeys(buf, "x")
+ call VerifyScreenDump(buf, 'Test_spell_compatible_2', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_z_equal_with_large_count()
+ split
+ set spell
+ call setline(1, "ff")
+ norm 0z=337203685477580
+ set nospell
+ bwipe!
endfunc
let g:test_data_aff1 = [
diff --git a/src/nvim/testdir/test_spell_utf8.vim b/test/old/testdir/test_spell_utf8.vim
index 7c588d736a..91ada1ed38 100644
--- a/src/nvim/testdir/test_spell_utf8.vim
+++ b/test/old/testdir/test_spell_utf8.vim
@@ -624,7 +624,6 @@ endfunc
" Test affix flags with two characters
func Test_spell_affix()
- CheckNotMSWindows " FIXME: Why does this fail with MSVC?
call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5)
call RunGoodBad("fooa1 fooa\u00E9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend",
\ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend",
diff --git a/src/nvim/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim
index dbffbafed9..4d2a6cf35f 100644
--- a/src/nvim/testdir/test_spellfile.vim
+++ b/test/old/testdir/test_spellfile.vim
@@ -1063,4 +1063,24 @@ func Test_mkspellmem_opt()
call assert_fails('set mkspellmem=1000,50,0', 'E474:')
endfunc
+" 'spellfile' accepts '@' on top of 'isfname'.
+func Test_spellfile_allow_at_character()
+ call mkdir('Xtest/the foo@bar,dir', 'p')
+ let &spellfile = './Xtest/the foo@bar,dir/Xspellfile.add'
+ let &spellfile = ''
+ call delete('Xtest', 'rf')
+endfunc
+
+" this was using a NULL pointer
+func Test_mkspell_empty_dic()
+ call writefile(['1'], 'XtestEmpty.dic')
+ call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'XtestEmpty.aff')
+ mkspell! XtestEmpty.spl XtestEmpty
+
+ call delete('XtestEmpty.dic')
+ call delete('XtestEmpty.aff')
+ call delete('XtestEmpty.spl')
+endfunc
+
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_startup.vim b/test/old/testdir/test_startup.vim
index 1ee1d0dfe3..db9f0c4c13 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/test/old/testdir/test_startup.vim
@@ -125,15 +125,15 @@ func Test_help_arg()
" check if couple of lines are there
let found = []
for line in lines
- if line =~ '-R.*Read-only mode'
- call add(found, 'Readonly mode')
+ if line =~ '-l.*Execute Lua'
+ call add(found, 'Execute Lua')
endif
" Watch out for a second --version line in the Gnome version.
if line =~ '--version.*Print version information'
call add(found, "--version")
endif
endfor
- call assert_equal(['Readonly mode', '--version'], found)
+ call assert_equal(['Execute Lua', '--version'], found)
endif
call delete('Xtestout')
endfunc
@@ -295,9 +295,9 @@ func Test_q_arg()
call writefile(lines, 'Xbadfile.c')
let after =<< trim [CODE]
- call writefile([&errorfile, string(getpos("."))], "Xtestout")
+ call writefile([&errorfile, string(getpos("."))], "XtestoutQarg")
copen
- w >> Xtestout
+ w >> XtestoutQarg
qall
[CODE]
@@ -305,30 +305,30 @@ func Test_q_arg()
call assert_equal('errors.err', &errorfile)
call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'errors.err')
if RunVim([], after, '-q')
- let lines = readfile('Xtestout')
+ let lines = readfile('XtestoutQarg')
call assert_equal(['errors.err',
\ '[0, 4, 12, 0]',
\ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
\ lines)
endif
- call delete('Xtestout')
+ call delete('XtestoutQarg')
call delete('errors.err')
- " Test with explicit argument '-q Xerrors' (with space).
- call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'Xerrors')
- if RunVim([], after, '-q Xerrors')
- let lines = readfile('Xtestout')
- call assert_equal(['Xerrors',
+ " Test with explicit argument '-q XerrorsQarg' (with space).
+ call writefile(["Xbadfile.c:4:12: error: expected ';' before '}' token"], 'XerrorsQarg')
+ if RunVim([], after, '-q XerrorsQarg')
+ let lines = readfile('XtestoutQarg')
+ call assert_equal(['XerrorsQarg',
\ '[0, 4, 12, 0]',
\ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
\ lines)
endif
- call delete('Xtestout')
+ call delete('XtestoutQarg')
- " Test with explicit argument '-qXerrors' (without space).
- if RunVim([], after, '-qXerrors')
- let lines = readfile('Xtestout')
- call assert_equal(['Xerrors',
+ " Test with explicit argument '-qXerrorsQarg' (without space).
+ if RunVim([], after, '-qXerrorsQarg')
+ let lines = readfile('XtestoutQarg')
+ call assert_equal(['XerrorsQarg',
\ '[0, 4, 12, 0]',
\ "Xbadfile.c|4 col 12| error: expected ';' before '}' token"],
\ lines)
@@ -339,8 +339,8 @@ func Test_q_arg()
call assert_equal(3, v:shell_error)
call delete('Xbadfile.c')
- call delete('Xtestout')
- call delete('Xerrors')
+ call delete('XtestoutQarg')
+ call delete('XerrorsQarg')
endfunc
" Test the -V[N]{filename} argument to set the 'verbose' option to N
@@ -386,10 +386,10 @@ func Test_m_M_R()
call delete('Xtestout')
endfunc
-" Test the -A, -F and -H arguments (Arabic, Farsi and Hebrew modes).
-func Test_A_F_H_arg()
+" Test the -A and -H arguments (Arabic and Hebrew modes).
+func Test_A_H_arg()
let after =<< trim [CODE]
- call writefile([&rightleft, &arabic, 0, &hkmap], "Xtestout")
+ call writefile([&rightleft, &arabic, 0, &hkmap, &keymap], "Xtestout")
qall
[CODE]
@@ -397,17 +397,12 @@ func Test_A_F_H_arg()
" 'encoding' is not utf-8.
if has('arabic') && &encoding == 'utf-8' && RunVim([], after, '-e -s -A')
let lines = readfile('Xtestout')
- call assert_equal(['1', '1', '0', '0'], lines)
- endif
-
- if has('farsi') && RunVim([], after, '-F')
- let lines = readfile('Xtestout')
- call assert_equal(['1', '0', '1', '0'], lines)
+ call assert_equal(['1', '1', '0', '0', 'arabic'], lines)
endif
if has('rightleft') && RunVim([], after, '-H')
let lines = readfile('Xtestout')
- call assert_equal(['1', '0', '0', '1'], lines)
+ call assert_equal(['1', '0', '0', '0', 'hebrew'], lines)
endif
call delete('Xtestout')
@@ -965,9 +960,9 @@ func Test_missing_vimrc()
let cmd = GetVimCommandCleanTerm() . ' -u Xvimrc_missing -S Xafter'
let buf = term_start(cmd, {'term_rows' : 10})
call WaitForAssert({-> assert_equal("running", term_getstatus(buf))})
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, "\n:")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match(':', term_getline(buf, 10))})
call StopVimInTerminal(buf)
call assert_equal([], readfile('Xtestout'))
diff --git a/src/nvim/testdir/test_startup_utf8.vim b/test/old/testdir/test_startup_utf8.vim
index 2ee6ecc41d..1684d80c80 100644
--- a/src/nvim/testdir/test_startup_utf8.vim
+++ b/test/old/testdir/test_startup_utf8.vim
@@ -73,7 +73,7 @@ func Test_detect_ambiwidth()
\ 'redraw',
\ ], 'Xscript')
let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1})
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>")
call WaitForAssert({-> assert_match('single', term_getline(buf, 1))})
diff --git a/src/nvim/testdir/test_stat.vim b/test/old/testdir/test_stat.vim
index d3059664e9..d3059664e9 100644
--- a/src/nvim/testdir/test_stat.vim
+++ b/test/old/testdir/test_stat.vim
diff --git a/src/nvim/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim
index 990c852ccd..c48bac12b4 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/test/old/testdir/test_statusline.vim
@@ -231,6 +231,10 @@ func Test_statusline()
" %=: Separation point between left and right aligned items.
set statusline=foo%=bar
call assert_match('^foo\s\+bar\s*$', s:get_statusline())
+ set statusline=foo%=bar%=baz
+ call assert_match('^foo\s\+bar\s\+baz\s*$', s:get_statusline())
+ set statusline=foo%=bar%=baz%=qux
+ call assert_match('^foo\s\+bar\s\+baz\s\+qux\s*$', s:get_statusline())
" Test min/max width, leading zeroes, left/right justify.
set statusline=%04B
@@ -265,7 +269,7 @@ func Test_statusline()
call assert_match('^vimLineComment\s*$', s:get_statusline())
syntax off
- "%{%expr%}: evaluates enxpressions present in result of expr
+ "%{%expr%}: evaluates expressions present in result of expr
func! Inner_eval()
return '%n some other text'
endfunc
@@ -440,6 +444,13 @@ func Test_statusline()
set splitbelow&
endfunc
+func Test_statusline_trailing_percent_zero()
+ " this was causing illegal memory access
+ set laststatus=2 stl=%!%0
+ call assert_fails('redraw', 'E15: Invalid expression: "%0"')
+ set laststatus& stl&
+endfunc
+
func Test_statusline_visual()
func CallWordcount()
call wordcount()
@@ -606,4 +617,25 @@ func Test_statusline_showcmd()
call StopVimInTerminal(buf)
endfunc
+func Test_statusline_highlight_group_cleared()
+ CheckScreendump
+
+ " the laststatus option is there to prevent
+ " the code-style test from complaining about
+ " trailing whitespace
+ let lines =<< trim END
+ set fillchars=stl:\ ,stlnc:\ laststatus=2
+ split
+ hi clear StatusLine
+ hi clear StatusLineNC
+ END
+ call writefile(lines, 'XTest_statusline_stl', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_statusline_stl', {})
+
+ call VerifyScreenDump(buf, 'Test_statusline_stl_1', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_substitute.vim b/test/old/testdir/test_substitute.vim
index c99a0d456d..a9e317da02 100644
--- a/src/nvim/testdir/test_substitute.vim
+++ b/test/old/testdir/test_substitute.vim
@@ -4,6 +4,32 @@ source shared.vim
source check.vim
source screendump.vim
+" NOTE: This needs to be the first test to be
+" run in the file, since it depends on
+" that the previous substitution atom
+" was not yet set.
+"
+" recursive call of :s and sub-replace special
+" (did cause heap-use-after free in < v9.0.2121)
+func Test_aaaa_substitute_expr_recursive_special()
+ func R()
+ " FIXME: leaving out the 'n' flag leaks memory, why?
+ %s/./\='.'/gn
+ endfunc
+ new Xfoobar_UAF
+ put ='abcdef'
+ let bufnr = bufnr('%')
+ try
+ silent! :s/./~\=R()/0
+ "call assert_fails(':s/./~\=R()/0', 'E939:')
+ let @/='.'
+ ~g
+ catch /^Vim\%((\a\+)\)\=:E565:/
+ endtry
+ delfunc R
+ exe bufnr .. "bw!"
+endfunc
+
func Test_multiline_subst()
enew!
call append(0, ["1 aa",
@@ -147,216 +173,6 @@ func Test_substitute_repeat()
call feedkeys("Qsc\<CR>y", 'tx')
bwipe!
endfunc
-
-" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute.
-
-" Execute a list of :substitute command tests
-func Run_SubCmd_Tests(tests)
- enew!
- for t in a:tests
- let start = line('.') + 1
- let end = start + len(t[2]) - 1
- exe "normal o" . t[0]
- call cursor(start, 1)
- exe t[1]
- call assert_equal(t[2], getline(start, end), t[1])
- endfor
- enew!
-endfunc
-
-func Test_sub_cmd_1()
- set magic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['A', 's/A/&&/', ['AA']],
- \ ['B', 's/B/\&/', ['&']],
- \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
- \ ['D', 's/D/d/', ['d']],
- \ ['E', 's/E/~/', ['d']],
- \ ['F', 's/F/\~/', ['~']],
- \ ['G', 's/G/\ugg/', ['Gg']],
- \ ['H', 's/H/\Uh\Eh/', ['Hh']],
- \ ['I', 's/I/\lII/', ['iI']],
- \ ['J', 's/J/\LJ\EJ/', ['jJ']],
- \ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
- \ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
- \ ['oOo', 's/O/\n/', ["o\no"]],
- \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
- \ ['qQq', 's/Q/\t/', ["q\tq"]],
- \ ['rRr', 's/R/\\/', ['r\r']],
- \ ['sSs', 's/S/\c/', ['scs']],
- \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
- \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
- \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
- \ ['\', 's/\\/\\\\/', ['\\']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_2()
- set nomagic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['A', 's/A/&&/', ['&&']],
- \ ['B', 's/B/\&/', ['B']],
- \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
- \ ['D', 's/D/d/', ['d']],
- \ ['E', 's/E/~/', ['~']],
- \ ['F', 's/F/\~/', ['~']],
- \ ['G', 's/G/\ugg/', ['Gg']],
- \ ['H', 's/H/\Uh\Eh/', ['Hh']],
- \ ['I', 's/I/\lII/', ['iI']],
- \ ['J', 's/J/\LJ\EJ/', ['jJ']],
- \ ['K', 's/K/\Uk\ek/', ['Kk']],
- \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
- \ ['mMm', 's/M/\r/', ['m', 'm']],
- \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
- \ ['oOo', 's/O/\n/', ["o\no"]],
- \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
- \ ['qQq', 's/Q/\t/', ["q\tq"]],
- \ ['rRr', 's/R/\\/', ['r\r']],
- \ ['sSs', 's/S/\c/', ['scs']],
- \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
- \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
- \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
- \ ['\', 's/\\/\\\\/', ['\\']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_3()
- set nomagic
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [['aAa', "s/A/\\='\\'/", ['a\a']],
- \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']],
- \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']],
- \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']],
- \ ['fFf', "s/F/\\='\r'/", ['f', 'f']],
- \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']],
- \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']],
- \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']],
- \ ['jJj', "s/J/\\='\n'/", ['j', 'j']],
- \ ['kKk', 's/K/\="\r"/', ['k', 'k']],
- \ ['lLl', 's/L/\="\n"/', ['l', 'l']]
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-" Test for submatch() on :substitute.
-func Test_sub_cmd_4()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/",
- \ ['a\a']],
- \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/",
- \ ['b\b']],
- \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/",
- \ ["c\<C-V>", 'c']],
- \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/",
- \ ["d\<C-V>", 'd']],
- \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/",
- \ ["e\\\<C-V>", 'e']],
- \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/",
- \ ['f', 'f']],
- \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/',
- \ ["g\<C-V>", 'g']],
- \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/',
- \ ["h\<C-V>", 'h']],
- \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/',
- \ ["i\\\<C-V>", 'i']],
- \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/",
- \ ['j', 'j']],
- \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/",
- \ ['k', 'k']],
- \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/",
- \ ['l', 'l']],
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-func Test_sub_cmd_5()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']],
- \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]],
- \ ]
- call Run_SubCmd_Tests(tests)
-endfunc
-
-" Test for *:s%* on :substitute.
-func Test_sub_cmd_6()
- throw 'Skipped: Nvim does not support cpoptions flag "/"'
- set magic&
- set cpo+=/
-
- " List entry format: [input, cmd, output]
- let tests = [ ['A', 's/A/a/', ['a']],
- \ ['B', 's/B/%/', ['a']],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- set cpo-=/
- let tests = [ ['C', 's/C/c/', ['c']],
- \ ['D', 's/D/%/', ['%']],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- set cpo&
-endfunc
-
-" Test for :s replacing \n with line break.
-func Test_sub_cmd_7()
- set magic&
- set cpo&
-
- " List entry format: [input, cmd, output]
- let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']],
- \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']],
- \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]],
- \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]],
- \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]],
- \ ]
- call Run_SubCmd_Tests(tests)
-
- exe "normal oQ\nQ\<Esc>k"
- call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486')
- enew!
-endfunc
-
-func TitleString()
- let check = 'foo' =~ 'bar'
- return ""
-endfunc
-
-func Test_sub_cmd_8()
- set titlestring=%{TitleString()}
-
- enew!
- call append(0, ['', 'test_one', 'test_two'])
- call cursor(1,1)
- /^test_one/s/.*/\="foo\nbar"/
- call assert_equal('foo', getline(2))
- call assert_equal('bar', getline(3))
- call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t")
- call feedkeys("\<CR>y", "xt")
- call assert_equal('foo', getline(4))
- call assert_equal('bar', getline(5))
-
- enew!
- set titlestring&
-endfunc
-
" Test %s/\n// which is implemented as a special case to use a
" more efficient join rather than doing a regular substitution.
func Test_substitute_join()
@@ -415,6 +231,7 @@ func Test_substitute_count()
call assert_equal(['foo foo', 'foo foo', 'foo foo', 'bar foo', 'bar foo'],
\ getline(1, '$'))
+ call assert_fails('s/./b/2147483647', 'E1510:')
bwipe!
endfunc
@@ -585,10 +402,11 @@ endfunc
func Test_sub_replace_6()
set magic&
+ " Nvim: no "/" flag in 'cpoptions'.
" set cpo+=/
call assert_equal('a', substitute('A', 'A', 'a', ''))
call assert_equal('%', substitute('B', 'B', '%', ''))
- " set cpo-=/
+ set cpo-=/
call assert_equal('c', substitute('C', 'C', 'c', ''))
call assert_equal('%', substitute('D', 'D', '%', ''))
endfunc
@@ -665,7 +483,223 @@ func Test_substitute_partial()
" 20 arguments plus one is too many
let Replacer = function('SubReplacer20', repeat(['foo'], 20))
- call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118')
+ call assert_fails("call substitute('123', '2', Replacer, 'g')", 'E118:')
+endfunc
+
+func Test_substitute_float()
+ CheckFeature float
+
+ call assert_equal('number 1.23', substitute('number ', '$', { -> 1.23 }, ''))
+ " vim9 assert_equal('number 1.23', substitute('number ', '$', () => 1.23, ''))
+endfunc
+
+" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute.
+
+" Execute a list of :substitute command tests
+func Run_SubCmd_Tests(tests)
+ enew!
+ for t in a:tests
+ let start = line('.') + 1
+ let end = start + len(t[2]) - 1
+ exe "normal o" . t[0]
+ call cursor(start, 1)
+ exe t[1]
+ call assert_equal(t[2], getline(start, end), t[1])
+ endfor
+ enew!
+endfunc
+
+func Test_sub_cmd_1()
+ set magic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['A', 's/A/&&/', ['AA']],
+ \ ['B', 's/B/\&/', ['&']],
+ \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
+ \ ['D', 's/D/d/', ['d']],
+ \ ['E', 's/E/~/', ['d']],
+ \ ['F', 's/F/\~/', ['~']],
+ \ ['G', 's/G/\ugg/', ['Gg']],
+ \ ['H', 's/H/\Uh\Eh/', ['Hh']],
+ \ ['I', 's/I/\lII/', ['iI']],
+ \ ['J', 's/J/\LJ\EJ/', ['jJ']],
+ \ ['K', 's/K/\Uk\ek/', ['Kk']],
+ \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['mMm', 's/M/\r/', ['m', 'm']],
+ \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['oOo', 's/O/\n/', ["o\no"]],
+ \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
+ \ ['qQq', 's/Q/\t/', ["q\tq"]],
+ \ ['rRr', 's/R/\\/', ['r\r']],
+ \ ['sSs', 's/S/\c/', ['scs']],
+ \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
+ \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
+ \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
+ \ ['\', 's/\\/\\\\/', ['\\']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_2()
+ set nomagic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['A', 's/A/&&/', ['&&']],
+ \ ['B', 's/B/\&/', ['B']],
+ \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']],
+ \ ['D', 's/D/d/', ['d']],
+ \ ['E', 's/E/~/', ['~']],
+ \ ['F', 's/F/\~/', ['~']],
+ \ ['G', 's/G/\ugg/', ['Gg']],
+ \ ['H', 's/H/\Uh\Eh/', ['Hh']],
+ \ ['I', 's/I/\lII/', ['iI']],
+ \ ['J', 's/J/\LJ\EJ/', ['jJ']],
+ \ ['K', 's/K/\Uk\ek/', ['Kk']],
+ \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']],
+ \ ['mMm', 's/M/\r/', ['m', 'm']],
+ \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']],
+ \ ['oOo', 's/O/\n/', ["o\no"]],
+ \ ['pPp', 's/P/\b/', ["p\<C-H>p"]],
+ \ ['qQq', 's/Q/\t/', ["q\tq"]],
+ \ ['rRr', 's/R/\\/', ['r\r']],
+ \ ['sSs', 's/S/\c/', ['scs']],
+ \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]],
+ \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']],
+ \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']],
+ \ ['\', 's/\\/\\\\/', ['\\']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_3()
+ set nomagic
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [['aAa', "s/A/\\='\\'/", ['a\a']],
+ \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']],
+ \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']],
+ \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']],
+ \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']],
+ \ ['fFf', "s/F/\\='\r'/", ['f', 'f']],
+ \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']],
+ \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']],
+ \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']],
+ \ ['jJj', "s/J/\\='\n'/", ['j', 'j']],
+ \ ['kKk', 's/K/\="\r"/', ['k', 'k']],
+ \ ['lLl', 's/L/\="\n"/', ['l', 'l']]
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+" Test for submatch() on :substitute.
+func Test_sub_cmd_4()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/",
+ \ ['a\a']],
+ \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/",
+ \ ['b\b']],
+ \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/",
+ \ ["c\<C-V>", 'c']],
+ \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/",
+ \ ["d\<C-V>", 'd']],
+ \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/",
+ \ ["e\\\<C-V>", 'e']],
+ \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/",
+ \ ['f', 'f']],
+ \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/',
+ \ ["g\<C-V>", 'g']],
+ \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/',
+ \ ["h\<C-V>", 'h']],
+ \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/',
+ \ ["i\\\<C-V>", 'i']],
+ \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/",
+ \ ['j', 'j']],
+ \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/",
+ \ ['k', 'k']],
+ \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/",
+ \ ['l', 'l']],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+func Test_sub_cmd_5()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']],
+ \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+endfunc
+
+" Test for *:s%* on :substitute.
+func Test_sub_cmd_6()
+ set magic&
+ " Nvim: no "/" flag in 'cpoptions'.
+ " set cpo+=/
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ['A', 's/A/a/', ['a']],
+ \ ['B', 's/B/%/', ['a']],
+ \ ]
+ " call Run_SubCmd_Tests(tests)
+
+ set cpo-=/
+ let tests = [ ['C', 's/C/c/', ['c']],
+ \ ['D', 's/D/%/', ['%']],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+
+ set cpo&
+endfunc
+
+" Test for :s replacing \n with line break.
+func Test_sub_cmd_7()
+ set magic&
+ set cpo&
+
+ " List entry format: [input, cmd, output]
+ let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']],
+ \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']],
+ \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]],
+ \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]],
+ \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]],
+ \ ]
+ call Run_SubCmd_Tests(tests)
+
+ exe "normal oQ\nQ\<Esc>k"
+ call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486')
+ enew!
+endfunc
+
+func TitleString()
+ let check = 'foo' =~ 'bar'
+ return ""
+endfunc
+
+func Test_sub_cmd_8()
+ set titlestring=%{TitleString()}
+
+ enew!
+ call append(0, ['', 'test_one', 'test_two'])
+ call cursor(1,1)
+ /^test_one/s/.*/\="foo\nbar"/
+ call assert_equal('foo', getline(2))
+ call assert_equal('bar', getline(3))
+ call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t")
+ call feedkeys("\<CR>y", "xt")
+ call assert_equal('foo', getline(4))
+ call assert_equal('bar', getline(5))
+
+ enew!
+ set titlestring&
endfunc
func Test_sub_cmd_9()
@@ -853,13 +887,13 @@ func Test_sub_with_no_last_pat()
call assert_equal([], readfile('Xresult'))
endif
- " Nvim does not support cpoptions flag "/"'
- " let lines =<< trim [SCRIPT]
- " set cpo+=/
- " call assert_fails('s/abc/%/', 'E33:')
- " call writefile(v:errors, 'Xresult')
- " qall!
- " [SCRIPT]
+ let lines =<< trim [SCRIPT]
+ set cpo+=/
+ call assert_fails('s/abc/%/', 'E33:')
+ call writefile(v:errors, 'Xresult')
+ qall!
+ [SCRIPT]
+ " Nvim: no "/" flag in 'cpoptions'.
" call writefile(lines, 'Xscript')
" if RunVim([], [], '--clean -S Xscript')
" call assert_equal([], readfile('Xresult'))
@@ -1089,6 +1123,41 @@ func Test_sub_edit_scriptfile()
bwipe!
endfunc
+" This was editing another file from the expression.
+func Test_sub_expr_goto_other_file()
+ call writefile([''], 'Xfileone', 'D')
+ enew!
+ call setline(1, ['a', 'b', 'c', 'd',
+ \ 'Xfileone zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz'])
+
+ func g:SplitGotoFile()
+ exe "sil! norm 0\<C-W>gf"
+ return ''
+ endfunc
+
+ $
+ s/\%')/\=g:SplitGotoFile()
+
+ delfunc g:SplitGotoFile
+ bwipe!
+endfunc
+
+func Test_recursive_expr_substitute()
+ " this was reading invalid memory
+ let lines =<< trim END
+ func Repl(g, n)
+ s
+ r%:s000
+ endfunc
+ next 0
+ let caught = 0
+ s/\%')/\=Repl(0, 0)
+ qall!
+ END
+ call writefile(lines, 'XexprSubst', 'D')
+ call RunVim([], [], '--clean -S XexprSubst')
+endfunc
+
" Test for the 2-letter and 3-letter :substitute commands
func Test_substitute_short_cmd()
new
@@ -1372,6 +1441,21 @@ func Test_substitute_short_cmd()
bw!
endfunc
+" Check handling expanding "~" resulting in extremely long text.
+" FIXME: disabled, it takes too long to run on CI
+"func Test_substitute_tilde_too_long()
+" enew!
+"
+" s/.*/ixxx
+" s//~~~~~~~~~AAAAAAA@(
+"
+" " Either fails with "out of memory" or "text too long".
+" " This can take a long time.
+" call assert_fails('sil! norm &&&&&&&&&', ['E1240:\|E342:'])
+"
+" bwipe!
+"endfunc
+
" This should be done last to reveal a memory leak when vim_regsub_both() is
" called to evaluate an expression but it is not used in a second call.
func Test_z_substitute_expr_leak()
@@ -1382,4 +1466,37 @@ func Test_z_substitute_expr_leak()
delfunc SubExpr
endfunc
+func Test_substitute_expr_switch_win()
+ func R()
+ wincmd x
+ return 'XXXX'
+ endfunc
+ new Xfoobar
+ let bufnr = bufnr('%')
+ put ='abcdef'
+ silent! s/\%')/\=R()
+ call assert_fails(':%s/./\=R()/g', 'E565:')
+ delfunc R
+ exe bufnr .. "bw!"
+endfunc
+
+" recursive call of :s using test-replace special
+func Test_substitute_expr_recursive()
+ func Q()
+ %s/./\='foobar'/gn
+ return "foobar"
+ endfunc
+ func R()
+ %s/./\=Q()/g
+ endfunc
+ new Xfoobar_UAF
+ let bufnr = bufnr('%')
+ put ='abcdef'
+ silent! s/./\=R()/g
+ call assert_fails(':%s/./\=R()/g', 'E565:')
+ delfunc R
+ delfunc Q
+ exe bufnr .. "bw!"
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_suspend.vim b/test/old/testdir/test_suspend.vim
index bf88bd4453..aa6603be78 100644
--- a/src/nvim/testdir/test_suspend.vim
+++ b/test/old/testdir/test_suspend.vim
@@ -53,7 +53,7 @@ func Test_suspend()
" Quit gracefully to dump coverage information.
call term_sendkeys(buf, ":qall!\<CR>")
- call term_wait(buf)
+ call TermWait(buf)
" Wait until Vim actually exited and shell shows a prompt
call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))})
call StopShellInTerminal(buf)
diff --git a/src/nvim/testdir/test_swap.vim b/test/old/testdir/test_swap.vim
index cf46b4c5bd..65b6c57850 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/test/old/testdir/test_swap.vim
@@ -115,8 +115,19 @@ func Test_swapinfo()
w
let fname = s:swapname()
call assert_match('Xswapinfo', fname)
- let info = fname->swapinfo()
+ " Check the tail appears in the list from swapfilelist(). The path depends
+ " on the system.
+ let tail = fnamemodify(fname, ":t")->fnameescape()
+ let nr = 0
+ for name in swapfilelist()
+ if name =~ tail .. '$'
+ let nr += 1
+ endif
+ endfor
+ call assert_equal(1, nr, 'not found in ' .. string(swapfilelist()))
+
+ let info = fname->swapinfo()
let ver = printf('VIM %d.%d', v:version / 100, v:version % 100)
call assert_equal(ver, info.version)
@@ -235,7 +246,6 @@ func Test_swap_recover()
autocmd SwapExists * let v:swapchoice = 'r'
augroup END
-
call mkdir('Xswap')
let $Xswap = 'foo' " Check for issue #4369.
set dir=Xswap//
@@ -364,14 +374,14 @@ func Test_swap_prompt_splitwin()
call term_sendkeys(buf, ":set noruler\n")
call term_sendkeys(buf, ":split Xfile1\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))})
call term_sendkeys(buf, "q")
- call term_wait(buf)
+ call TermWait(buf)
call term_sendkeys(buf, ":\<CR>")
call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))})
call term_sendkeys(buf, ":echomsg winnr('$')\<CR>")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))})
call StopVimInTerminal(buf)
@@ -436,6 +446,12 @@ func s:get_unused_pid(base)
if job_status(j) ==# 'dead'
return job_info(j).process
endif
+ elseif has('nvim')
+ let j = jobstart('echo')
+ let pid = jobpid(j)
+ if jobwait([j])[0] >= 0
+ return pid
+ endif
endif
" Must add four for MS-Windows to see it as a different one.
return a:base + 4
diff --git a/src/nvim/testdir/test_syn_attr.vim b/test/old/testdir/test_syn_attr.vim
index 88f9d0d84d..88f9d0d84d 100644
--- a/src/nvim/testdir/test_syn_attr.vim
+++ b/test/old/testdir/test_syn_attr.vim
diff --git a/src/nvim/testdir/test_syntax.vim b/test/old/testdir/test_syntax.vim
index 45230c4208..76a21adc57 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/test/old/testdir/test_syntax.vim
@@ -41,9 +41,9 @@ func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "")
for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1)
let l:errors += synID(a:lnum, l:i, a:trans)
- \ ->synIDattr("name")
- \ ->assert_equal(l:expectedGroups[l:i - 1],
- \ l:msg .. l:i)
+ \ ->synIDattr("name")
+ \ ->assert_equal(l:expectedGroups[l:i - 1],
+ \ l:msg .. l:i)
endfor
endfunc
@@ -116,7 +116,7 @@ func Test_syntime()
let a = execute('syntime clear')
call assert_equal("\nNo Syntax items defined for this buffer", a)
- view samples/memfile_test.c
+ view ../memfile_test.c
setfiletype cpp
redraw
let a = execute('syntime report')
@@ -460,7 +460,7 @@ func Test_invalid_name()
endfunc
func Test_ownsyntax()
- new Xfoo
+ new XfooOwnSyntax
call setline(1, '#define FOO')
syntax on
set filetype=c
diff --git a/src/nvim/testdir/test_system.vim b/test/old/testdir/test_system.vim
index 6c8373b335..6c8373b335 100644
--- a/src/nvim/testdir/test_system.vim
+++ b/test/old/testdir/test_system.vim
diff --git a/src/nvim/testdir/test_tab.vim b/test/old/testdir/test_tab.vim
index b8e8dfe062..b8e8dfe062 100644
--- a/src/nvim/testdir/test_tab.vim
+++ b/test/old/testdir/test_tab.vim
diff --git a/src/nvim/testdir/test_tabline.vim b/test/old/testdir/test_tabline.vim
index d9bef09067..d423f8a22c 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/test/old/testdir/test_tabline.vim
@@ -133,7 +133,7 @@ func Test_tabline_empty_group()
tabnew
redraw!
- tabclose
+ bw!
set tabline=
endfunc
@@ -204,4 +204,28 @@ func Test_tabline_showcmd()
call StopVimInTerminal(buf)
endfunc
+func TruncTabLine()
+ return '%1T口口%2Ta' .. repeat('b', &columns - 4) .. '%999X%#TabLine#c'
+endfunc
+
+" Test 'tabline' with truncated double-width label at the start.
+func Test_tabline_truncated_double_width()
+ tabnew
+ redraw
+ call assert_match('X$', Screenline(1))
+ let attr_TabLineFill = screenattr(1, &columns - 1)
+ let attr_TabLine = screenattr(1, &columns)
+ call assert_notequal(attr_TabLine, attr_TabLineFill)
+
+ set tabline=%!TruncTabLine()
+ redraw
+ call assert_equal('<a' .. repeat('b', &columns - 4) .. 'c', Screenline(1))
+ call assert_equal(attr_TabLineFill, screenattr(1, &columns - 2))
+ call assert_equal(attr_TabLine, screenattr(1, &columns - 1))
+ call assert_equal(attr_TabLine, screenattr(1, &columns))
+
+ bw!
+ set tabline=
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tabpage.vim b/test/old/testdir/test_tabpage.vim
index b97aa409d8..4e7b09b9e7 100644
--- a/src/nvim/testdir/test_tabpage.vim
+++ b/test/old/testdir/test_tabpage.vim
@@ -4,6 +4,8 @@ source screendump.vim
source check.vim
function Test_tabpage()
+ CheckFeature quickfix
+
bw!
" Simple test for opening and closing a tab page
tabnew
@@ -148,6 +150,22 @@ function Test_tabpage()
tabonly!
endfunc
+func Test_tabpage_drop()
+ edit f1
+ tab split f2
+ tab split f3
+ normal! gt
+ call assert_equal(1, tabpagenr())
+
+ tab drop f3
+ call assert_equal(3, tabpagenr())
+ call assert_equal(1, tabpagenr('#'))
+ bwipe!
+ bwipe!
+ bwipe!
+ call assert_equal(1, tabpagenr('$'))
+endfunc
+
" Test autocommands
function Test_tabpage_with_autocmd()
command -nargs=1 -bar C :call add(s:li, '=== ' . <q-args> . ' ===')|<args>
@@ -257,6 +275,8 @@ function Test_tabpage_with_autocmd_tab_drop()
endfunction
function Test_tabpage_with_tab_modifier()
+ CheckFeature quickfix
+
for n in range(4)
tabedit
endfor
@@ -846,4 +866,43 @@ func Test_lastused_tabpage()
tabonly!
endfunc
+" Test for tabpage allocation failure
+func Test_tabpage_alloc_failure()
+ CheckFunction test_alloc_fail
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('tabnew', 'E342:')
+
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ edit Xfile1
+ call assert_fails('tabedit Xfile2', 'E342:')
+ call assert_equal(1, winnr('$'))
+ call assert_equal(1, tabpagenr('$'))
+ call assert_equal('Xfile1', @%)
+
+ new
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('wincmd T', 'E342:')
+ bw!
+
+ call test_alloc_fail(GetAllocId('newtabpage_tvars'), 0, 0)
+ call assert_fails('tab split', 'E342:')
+ call assert_equal(2, winnr('$'))
+ call assert_equal(1, tabpagenr('$'))
+endfunc
+
+" this was giving ml_get errors
+func Test_tabpage_last_line()
+ enew
+ call setline(1, repeat(['a'], &lines + 5))
+ $
+ tabnew
+ call setline(1, repeat(['b'], &lines + 20))
+ $
+ tabNext
+ call assert_equal('a', getline('.'))
+
+ bwipe!
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagcase.vim b/test/old/testdir/test_tagcase.vim
index 53c08ccf1e..53c08ccf1e 100644
--- a/src/nvim/testdir/test_tagcase.vim
+++ b/test/old/testdir/test_tagcase.vim
diff --git a/src/nvim/testdir/test_tagfunc.vim b/test/old/testdir/test_tagfunc.vim
index cba96d3504..44916f2fc9 100644
--- a/src/nvim/testdir/test_tagfunc.vim
+++ b/test/old/testdir/test_tagfunc.vim
@@ -369,11 +369,11 @@ func Test_tagfunc_callback()
bw!
# Test for using a script-local function name
- def s:LocalTagFunc(pat: string, flags: string, info: dict<any> ): any
+ def LocalTagFunc(pat: string, flags: string, info: dict<any> ): any
g:LocalTagFuncArgs = [pat, flags, info]
return null
enddef
- &tagfunc = s:LocalTagFunc
+ &tagfunc = LocalTagFunc
new
g:LocalTagFuncArgs = []
assert_fails('tag a12', 'E433:')
diff --git a/src/nvim/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim
index be60a3535c..2ef5bd720b 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/test/old/testdir/test_tagjump.vim
@@ -405,10 +405,10 @@ func Test_getsettagstack()
" Error cases
call assert_equal({}, gettagstack(100))
call assert_equal(-1, settagstack(100, {'items' : []}))
- call assert_fails('call settagstack(1, [1, 10])', 'E715')
- call assert_fails("call settagstack(1, {'items' : 10})", 'E714')
- call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E928')
- call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962')
+ call assert_fails('call settagstack(1, [1, 10])', 'E1206:')
+ call assert_fails("call settagstack(1, {'items' : 10})", 'E714:')
+ call assert_fails("call settagstack(1, {'items' : []}, 10)", 'E1174:')
+ call assert_fails("call settagstack(1, {'items' : []}, 'b')", 'E962:')
call assert_equal(-1, settagstack(0, v:_null_dict))
set tags=Xtags
@@ -671,7 +671,7 @@ func Test_tselect()
call writefile(lines, 'XTest_tselect')
let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
call VerifyScreenDump(buf, 'Test_tselect_1', {})
diff --git a/src/nvim/testdir/test_taglist.vim b/test/old/testdir/test_taglist.vim
index 75d28c3ec4..75d28c3ec4 100644
--- a/src/nvim/testdir/test_taglist.vim
+++ b/test/old/testdir/test_taglist.vim
diff --git a/test/old/testdir/test_termcodes.vim b/test/old/testdir/test_termcodes.vim
new file mode 100644
index 0000000000..a00ea02286
--- /dev/null
+++ b/test/old/testdir/test_termcodes.vim
@@ -0,0 +1,1062 @@
+
+source check.vim
+" CheckNotGui
+" CheckUnix
+
+source shared.vim
+source mouse.vim
+source view_util.vim
+source term_util.vim
+
+func Test_term_mouse_left_click()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+ call setline(1, ['line 1', 'line 2', 'line 3 is a bit longer'])
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ go
+ call assert_equal([0, 1, 1, 0], getpos('.'), msg)
+ let row = 2
+ let col = 6
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([0, 2, 6, 0], getpos('.'), msg)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+func Test_xterm_mouse_right_click_extends_visual()
+ if has('mac')
+ " throw "Skipped: test right click in visual mode does not work on macOs (why?)"
+ endif
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+
+ for visual_mode in ["v", "V", "\<C-V>"]
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'visual=' .. visual_mode .. ' ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ call setline(1, repeat([repeat('-', 7)], 7))
+ call MouseLeftClick(4, 4)
+ call MouseLeftRelease(4, 4)
+ exe "norm! " .. visual_mode
+
+ " Right click extends top left of visual area.
+ call MouseRightClick(2, 2)
+ call MouseRightRelease(2, 2)
+
+ " Right click extends bottom right of visual area.
+ call MouseRightClick(6, 6)
+ call MouseRightRelease(6, 6)
+ norm! r1gv
+
+ " Right click shrinks top left of visual area.
+ call MouseRightClick(3, 3)
+ call MouseRightRelease(3, 3)
+
+ " Right click shrinks bottom right of visual area.
+ call MouseRightClick(5, 5)
+ call MouseRightRelease(5, 5)
+ norm! r2
+
+ if visual_mode ==# 'v'
+ call assert_equal(['-------',
+ \ '-111111',
+ \ '1122222',
+ \ '2222222',
+ \ '2222211',
+ \ '111111-',
+ \ '-------'], getline(1, '$'), msg)
+ elseif visual_mode ==# 'V'
+ call assert_equal(['-------',
+ \ '1111111',
+ \ '2222222',
+ \ '2222222',
+ \ '2222222',
+ \ '1111111',
+ \ '-------'], getline(1, '$'), msg)
+ else
+ call assert_equal(['-------',
+ \ '-11111-',
+ \ '-12221-',
+ \ '-12221-',
+ \ '-12221-',
+ \ '-11111-',
+ \ '-------'], getline(1, '$'), msg)
+ endif
+ endfor
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+" Test that <C-LeftMouse> jumps to help tag and <C-RightMouse> jumps back.
+func Test_xterm_mouse_ctrl_click()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " set mouse=a term=xterm
+ set mouse=a
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ " help
+ help usr_toc.txt
+ /usr_02.txt
+ norm! zt
+ let row = 1
+ let col = 1
+ call MouseCtrlLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_match('usr_02.txt$', bufname('%'), msg)
+ call assert_equal('*usr_02.txt*', expand('<cWORD>'), msg)
+
+ call MouseCtrlRightClick(row, col)
+ call MouseRightRelease(row, col)
+ " call assert_match('help.txt$', bufname('%'), msg)
+ call assert_match('usr_toc.txt$', bufname('%'), msg)
+ call assert_equal('|usr_02.txt|', expand('<cWORD>'), msg)
+
+ helpclose
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+endfunc
+
+func Test_term_mouse_middle_click()
+ CheckFeature clipboard_working
+
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ let save_quotestar = @*
+ let @* = 'abc'
+ " set mouse=a term=xterm
+ set mouse=a
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['123456789', '123456789'])
+
+ " Middle-click in the middle of the line pastes text where clicked.
+ let row = 1
+ let col = 6
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789', '123456789'], getline(1, '$'), msg)
+
+ " Middle-click beyond end of the line pastes text at the end of the line.
+ let col = 20
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789abc', '123456789'], getline(1, '$'), msg)
+
+ " Middle-click beyond the last line pastes in the last line.
+ let row = 5
+ let col = 3
+ call MouseMiddleClick(row, col)
+ call MouseMiddleRelease(row, col)
+ call assert_equal(['12345abc6789abc', '12abc3456789'], getline(1, '$'), msg)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ let @* = save_quotestar
+ bwipe!
+endfunc
+
+" TODO: for unclear reasons this test fails if it comes after
+" Test_xterm_mouse_ctrl_click()
+func Test_1xterm_mouse_wheel()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ let save_wrap = &wrap
+ " let save_ttymouse = &ttymouse
+ " set mouse=a term=xterm nowrap
+ set mouse=a nowrap
+ call setline(1, range(100000000000000, 100000000000100))
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ go
+ call assert_equal(1, line('w0'), msg)
+ call assert_equal([0, 1, 1, 0], getpos('.'), msg)
+
+ call MouseWheelDown(1, 1)
+ call assert_equal(4, line('w0'), msg)
+ call assert_equal([0, 4, 1, 0], getpos('.'), msg)
+
+ call MouseWheelDown(1, 1)
+ call assert_equal(7, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelUp(1, 1)
+ call assert_equal(4, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelUp(1, 1)
+ call assert_equal(1, line('w0'), msg)
+ call assert_equal([0, 7, 1, 0], getpos('.'), msg)
+
+ call MouseWheelRight(1, 1)
+ call assert_equal(7, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 7, 0], getpos('.'), msg)
+
+ call MouseWheelRight(1, 1)
+ call assert_equal(13, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ call MouseWheelLeft(1, 1)
+ call assert_equal(7, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ call MouseWheelLeft(1, 1)
+ call assert_equal(1, 1 + virtcol(".") - wincol(), msg)
+ call assert_equal([0, 7, 13, 0], getpos('.'), msg)
+
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ let &wrap = save_wrap
+ " let &ttymouse = save_ttymouse
+ bwipe!
+endfunc
+
+" Test that dragging beyond the window (at the bottom and at the top)
+" scrolls window content by the number of lines beyond the window.
+func Test_term_mouse_drag_beyond_window()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+ let col = 1
+ call setline(1, range(1, 100))
+
+ " Split into 3 windows, and go into the middle window
+ " so we test dragging mouse below and above the window.
+ 2split
+ wincmd j
+ 2split
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ " Line #10 at the top.
+ norm! 10zt
+ redraw
+ call assert_equal(10, winsaveview().topline, msg)
+ call assert_equal(2, winheight(0), msg)
+
+ let row = 4
+ call MouseLeftClick(row, col)
+ call assert_equal(10, winsaveview().topline, msg)
+
+ " Drag downwards. We're still in the window so topline should
+ " not change yet.
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(10, winsaveview().topline, msg)
+
+ " We now leave the window at the bottom, so the window content should
+ " scroll by 1 line, then 2 lines (etc) as we drag further away.
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(11, winsaveview().topline, msg)
+
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(13, winsaveview().topline, msg)
+
+ " Now drag upwards.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ " We're now back in the window so the topline should not change.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(14, winsaveview().topline, msg)
+
+ " We now leave the window at the top so the window content should
+ " scroll by 1 line, then 2, then 3 (etc) in the opposite direction.
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(13, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(11, winsaveview().topline, msg)
+
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(8, winsaveview().topline, msg)
+
+ call MouseLeftRelease(row, col)
+ call assert_equal(8, winsaveview().topline, msg)
+ call assert_equal(2, winheight(0), msg)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+func Test_term_mouse_drag_window_separator()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ " Split horizontally and test dragging the horizontal window separator.
+ split
+ let rowseparator = winheight(0) + 1
+ let row = rowseparator
+ let col = 1
+
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ if ttymouse_val !=# 'xterm2' || row <= 223
+ call MouseLeftClick(row, col)
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(rowseparator - 1, winheight(0) + 1, msg)
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(rowseparator, winheight(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(rowseparator, winheight(0) + 1, msg)
+ endif
+ bwipe!
+
+ " Split vertically and test dragging the vertical window separator.
+ vsplit
+ let colseparator = winwidth(0) + 1
+ let row = 1
+ let col = colseparator
+
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ if ttymouse_val !=# 'xterm2' || col <= 223
+ call MouseLeftClick(row, col)
+ let col -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(colseparator - 1, winwidth(0) + 1, msg)
+ let col += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(colseparator, winwidth(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(colseparator, winwidth(0) + 1, msg)
+ endif
+ bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_drag_statusline()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ let save_laststatus = &laststatus
+ " set mouse=a term=xterm laststatus=2
+ set mouse=a laststatus=2
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ call assert_equal(1, &cmdheight, msg)
+ let rowstatusline = winheight(0) + 1
+ let row = rowstatusline
+ let col = 1
+
+ if ttymouse_val ==# 'xterm2' && row > 223
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ continue
+ endif
+
+ call MouseLeftClick(row, col)
+ let row -= 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(2, &cmdheight, msg)
+ call assert_equal(rowstatusline - 1, winheight(0) + 1, msg)
+ let row += 1
+ call MouseLeftDrag(row, col)
+ call assert_equal(1, &cmdheight, msg)
+ call assert_equal(rowstatusline, winheight(0) + 1, msg)
+ call MouseLeftRelease(row, col)
+ call assert_equal(1, &cmdheight, msg)
+ call assert_equal(rowstatusline, winheight(0) + 1, msg)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ let &laststatus = save_laststatus
+endfunc
+
+func Test_term_mouse_click_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+ let row = 1
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ e Xfoo
+ tabnew Xbar
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xfoo',
+ \ 'Tab page 2',
+ \ '> Xbar'], a, msg)
+
+ " Test clicking on tab names in the tabline at the top.
+ let col = 2
+ redraw
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '> Xfoo',
+ \ 'Tab page 2',
+ \ '# Xbar'], a, msg)
+
+ let col = 9
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xfoo',
+ \ 'Tab page 2',
+ \ '> Xbar'], a, msg)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_click_X_to_close_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm
+ set mouse=a
+ let row = 1
+ let col = &columns
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ if ttymouse_val ==# 'xterm2' && col > 223
+ " When 'ttymouse' is 'xterm2', row/col bigger than 223 are not supported.
+ continue
+ endif
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+ tabnew Xtab3
+ tabn 2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2',
+ \ 'Tab page 3',
+ \ '# Xtab3'], a, msg)
+
+ " Click on "X" in tabline to close current tab i.e. Xtab2.
+ redraw
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab3'], a, msg)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+endfunc
+
+func Test_term_mouse_drag_to_move_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " Set 'mousetime' to 1 to avoid recognizing a double-click in the loop
+ " set mouse=a term=xterm mousetime=1
+ set mouse=a mousetime=0
+ let row = 1
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+ redraw
+
+ " Click in tab2 and drag it to tab1.
+ " Check getcharmod() to verify that click is not
+ " interpreted as a spurious double-click.
+ call MouseLeftClick(row, 10)
+ call assert_equal(0, getcharmod(), msg)
+ for col in [9, 8, 7, 6]
+ call MouseLeftDrag(row, col)
+ endfor
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '> Xtab2',
+ \ 'Tab page 2',
+ \ '# Xtab1'], a, msg)
+
+ " Switch to tab1
+ tabnext
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xtab2',
+ \ 'Tab page 2',
+ \ '> Xtab1'], a, msg)
+
+ " Click in tab2 and drag it to tab1.
+ " This time it is non-current tab.
+ call MouseLeftClick(row, 6)
+ call assert_equal(0, getcharmod(), msg)
+ for col in [7, 8, 9, 10]
+ call MouseLeftDrag(row, col)
+ endfor
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+
+ " Click elsewhere so that click in next iteration is not
+ " interpreted as unwanted double-click.
+ call MouseLeftClick(row, 11)
+ call MouseLeftRelease(row, 11)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ set mousetime&
+endfunc
+
+func Test_term_mouse_double_click_to_create_tab()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " Set 'mousetime' to a small value, so that double-click works but we don't
+ " have to wait long to avoid a triple-click.
+ " set mouse=a term=xterm mousetime=200
+ set mouse=a mousetime=200
+ let row = 1
+ let col = 10
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ e Xtab1
+ tabnew Xtab2
+
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ '# Xtab1',
+ \ 'Tab page 2',
+ \ '> Xtab2'], a, msg)
+
+ redraw
+ call MouseLeftClick(row, col)
+ " Check getcharmod() to verify that first click is not
+ " interpreted as a spurious double-click.
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(row, col)
+ call MouseLeftClick(row, col)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(row, col)
+ let a = split(execute(':tabs'), "\n")
+ call assert_equal(['Tab page 1',
+ \ ' Xtab1',
+ \ 'Tab page 2',
+ \ '> [No Name]',
+ \ 'Tab page 3',
+ \ '# Xtab2'], a, msg)
+
+ " Click elsewhere so that click in next iteration is not
+ " interpreted as unwanted double click.
+ call MouseLeftClick(row, col + 1)
+ call MouseLeftRelease(row, col + 1)
+
+ %bwipe!
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ " call test_override('no_query_mouse', 0)
+ set mousetime&
+endfunc
+
+" Test double/triple/quadruple click in normal mode to visually select.
+func Test_term_mouse_multiple_clicks_to_visually_select()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+
+ " 'mousetime' must be sufficiently large, or else the test is flaky when
+ " using a ssh connection with X forwarding; i.e. ssh -X (issue #7563).
+ " set mouse=a term=xterm mousetime=600
+ set mouse=a mousetime=600
+ new
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ call setline(1, ['foo [foo bar] foo', 'foo'])
+
+ " Double-click on word should visually select the word.
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal('v', mode(), msg)
+ norm! r1
+ call assert_equal(['111 [foo bar] foo', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on opening square bracket should visually
+ " select the whole [foo bar].
+ call MouseLeftClick(1, 5)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 5)
+ call assert_equal('v', mode(), msg)
+ norm! r2
+ call assert_equal(['111 222222222 foo', 'foo'], getline(1, '$'), msg)
+
+ " Triple-click should visually select the whole line.
+ call MouseLeftClick(1, 3)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 3)
+ call MouseLeftClick(1, 3)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 3)
+ call assert_equal('V', mode(), msg)
+ norm! r3
+ call assert_equal(['33333333333333333', 'foo'], getline(1, '$'), msg)
+
+ " Quadruple-click should start visual block select.
+ call MouseLeftClick(1, 2)
+ call assert_equal(0, getcharmod(), msg)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(32, getcharmod(), msg) " double-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(64, getcharmod(), msg) " triple-click
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call assert_equal(96, getcharmod(), msg) " quadruple-click
+ call MouseLeftRelease(1, 2)
+ call assert_equal("\<c-v>", mode(), msg)
+ norm! r4
+ call assert_equal(['34333333333333333', 'foo'], getline(1, '$'), msg)
+
+ " Double-click on a space character should visually select all the
+ " consecutive space characters.
+ %d
+ call setline(1, ' one two')
+ call MouseLeftClick(1, 2)
+ call MouseLeftRelease(1, 2)
+ call MouseLeftClick(1, 2)
+ call MouseLeftRelease(1, 2)
+ call assert_equal('v', mode(), msg)
+ norm! r1
+ call assert_equal(['1111one two'], getline(1, '$'), msg)
+
+ " Double-click on a word with exclusive selection
+ set selection=exclusive
+ let @" = ''
+ call MouseLeftClick(1, 10)
+ call MouseLeftRelease(1, 10)
+ call MouseLeftClick(1, 10)
+ call MouseLeftRelease(1, 10)
+ norm! y
+ call assert_equal('two', @", msg)
+
+ " Double click to select a block of text with exclusive selection
+ %d
+ call setline(1, 'one (two) three')
+ call MouseLeftClick(1, 5)
+ call MouseLeftRelease(1, 5)
+ call MouseLeftClick(1, 5)
+ call MouseLeftRelease(1, 5)
+ norm! y
+ call assert_equal(5, col("'<"), msg)
+ call assert_equal(10, col("'>"), msg)
+
+ call MouseLeftClick(1, 9)
+ call MouseLeftRelease(1, 9)
+ call MouseLeftClick(1, 9)
+ call MouseLeftRelease(1, 9)
+ norm! y
+ call assert_equal(5, col("'<"), msg)
+ call assert_equal(10, col("'>"), msg)
+ set selection&
+
+ " Click somewhere else so that the clicks above is not combined with the
+ " clicks in the next iteration.
+ call MouseRightClick(3, 10)
+ call MouseRightRelease(3, 10)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ set mousetime&
+ " call test_override('no_query_mouse', 0)
+ bwipe!
+endfunc
+
+" Test for selecting text in visual blockwise mode using Alt-LeftClick
+func Test_mouse_alt_leftclick()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm mousetime=200
+ set mouse=a mousetime=200
+ set mousemodel=popup
+ new
+ call setline(1, 'one (two) three')
+
+ for ttymouse_val in g:Ttymouse_values
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ " Left click with the Alt modifier key should extend the selection in
+ " blockwise visual mode.
+ let @" = ''
+ call MouseLeftClick(1, 3)
+ call MouseLeftRelease(1, 3)
+ call MouseAltLeftClick(1, 11)
+ call MouseLeftRelease(1, 11)
+ call assert_equal("\<C-V>", mode(), msg)
+ normal! y
+ call assert_equal('e (two) t', @")
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ set mousetime& mousemodel&
+ " call test_override('no_query_mouse', 0)
+ close!
+endfunc
+
+func Test_xterm_mouse_click_in_fold_columns()
+ new
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ let save_foldcolumn = &foldcolumn
+ " set mouse=a term=xterm foldcolumn=3 ttymouse=xterm2
+ set mouse=a foldcolumn=3
+
+ " Create 2 nested folds.
+ call setline(1, range(1, 7))
+ 2,6fold
+ norm! zR
+ 4,5fold
+ call assert_equal([-1, -1, -1, 4, 4, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ " Click in "+" of inner fold in foldcolumn should open it.
+ redraw
+ let row = 4
+ let col = 2
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, -1, -1, -1, -1, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ " Click in "-" of outer fold in foldcolumn should close it.
+ redraw
+ let row = 2
+ let col = 1
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, 2, 2, 2, 2, 2, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+ norm! zR
+
+ " Click in "|" of inner fold in foldcolumn should close it.
+ redraw
+ let row = 5
+ let col = 2
+ call MouseLeftClick(row, col)
+ call MouseLeftRelease(row, col)
+ call assert_equal([-1, -1, -1, 4, 4, -1, -1],
+ \ map(range(1, 7), 'foldclosed(v:val)'))
+
+ let &foldcolumn = save_foldcolumn
+ " Redraw at the end of the test to avoid interfering with other tests.
+ defer execute('redraw')
+ " let &ttymouse = save_ttymouse
+ " let &term = save_term
+ let &mouse = save_mouse
+ bwipe!
+endfunc
+
+" Test for the 'h' flag in the 'mouse' option. Using mouse in the help window.
+func Test_term_mouse_help_window()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=h term=xterm mousetime=200
+ set mouse=h mousetime=200
+
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+ help
+ let @" = ''
+ call MouseLeftClick(2, 5)
+ call MouseLeftRelease(2, 5)
+ call MouseLeftClick(1, 1)
+ call MouseLeftDrag(1, 10)
+ call MouseLeftRelease(1, 10)
+ norm! y
+ call assert_equal('*help.txt*', @", msg)
+ helpclose
+
+ " Click somewhere else to make sure the left click above is not combined
+ " with the next left click and treated as a double click
+ call MouseRightClick(5, 10)
+ call MouseRightRelease(5, 10)
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ set mousetime&
+ " call test_override('no_query_mouse', 0)
+ %bw!
+endfunc
+
+" Test for the translation of various mouse terminal codes
+func Test_mouse_termcodes()
+ let save_mouse = &mouse
+ let save_term = &term
+ " let save_ttymouse = &ttymouse
+ " call test_override('no_query_mouse', 1)
+ " set mouse=a term=xterm mousetime=200
+
+ new
+ for ttymouse_val in g:Ttymouse_values + g:Ttymouse_dec + g:Ttymouse_netterm
+ let msg = 'ttymouse=' .. ttymouse_val
+ " exe 'set ttymouse=' .. ttymouse_val
+
+ let mouse_codes = [
+ \ ["\<LeftMouse>", "<LeftMouse>"],
+ \ ["\<MiddleMouse>", "<MiddleMouse>"],
+ \ ["\<RightMouse>", "<RightMouse>"],
+ \ ["\<S-LeftMouse>", "<S-LeftMouse>"],
+ \ ["\<S-MiddleMouse>", "<S-MiddleMouse>"],
+ \ ["\<S-RightMouse>", "<S-RightMouse>"],
+ \ ["\<C-LeftMouse>", "<C-LeftMouse>"],
+ \ ["\<C-MiddleMouse>", "<C-MiddleMouse>"],
+ \ ["\<C-RightMouse>", "<C-RightMouse>"],
+ \ ["\<M-LeftMouse>", "<M-LeftMouse>"],
+ \ ["\<M-MiddleMouse>", "<M-MiddleMouse>"],
+ \ ["\<M-RightMouse>", "<M-RightMouse>"],
+ \ ["\<2-LeftMouse>", "<2-LeftMouse>"],
+ \ ["\<2-MiddleMouse>", "<2-MiddleMouse>"],
+ \ ["\<2-RightMouse>", "<2-RightMouse>"],
+ \ ["\<3-LeftMouse>", "<3-LeftMouse>"],
+ \ ["\<3-MiddleMouse>", "<3-MiddleMouse>"],
+ \ ["\<3-RightMouse>", "<3-RightMouse>"],
+ \ ["\<4-LeftMouse>", "<4-LeftMouse>"],
+ \ ["\<4-MiddleMouse>", "<4-MiddleMouse>"],
+ \ ["\<4-RightMouse>", "<4-RightMouse>"],
+ \ ["\<LeftDrag>", "<LeftDrag>"],
+ \ ["\<MiddleDrag>", "<MiddleDrag>"],
+ \ ["\<RightDrag>", "<RightDrag>"],
+ \ ["\<LeftRelease>", "<LeftRelease>"],
+ \ ["\<MiddleRelease>", "<MiddleRelease>"],
+ \ ["\<RightRelease>", "<RightRelease>"],
+ \ ["\<ScrollWheelUp>", "<ScrollWheelUp>"],
+ \ ["\<S-ScrollWheelUp>", "<S-ScrollWheelUp>"],
+ \ ["\<C-ScrollWheelUp>", "<C-ScrollWheelUp>"],
+ \ ["\<ScrollWheelDown>", "<ScrollWheelDown>"],
+ \ ["\<S-ScrollWheelDown>", "<S-ScrollWheelDown>"],
+ \ ["\<C-ScrollWheelDown>", "<C-ScrollWheelDown>"],
+ \ ["\<ScrollWheelLeft>", "<ScrollWheelLeft>"],
+ \ ["\<S-ScrollWheelLeft>", "<S-ScrollWheelLeft>"],
+ \ ["\<C-ScrollWheelLeft>", "<C-ScrollWheelLeft>"],
+ \ ["\<ScrollWheelRight>", "<ScrollWheelRight>"],
+ \ ["\<S-ScrollWheelRight>", "<S-ScrollWheelRight>"],
+ \ ["\<C-ScrollWheelRight>", "<C-ScrollWheelRight>"]
+ \ ]
+
+ for [code, outstr] in mouse_codes
+ exe "normal ggC\<C-K>" . code
+ call assert_equal(outstr, getline(1), msg)
+ endfor
+ endfor
+
+ let &mouse = save_mouse
+ " let &term = save_term
+ " let &ttymouse = save_ttymouse
+ set mousetime&
+ " call test_override('no_query_mouse', 0)
+ %bw!
+endfunc
+
+" Test for translation of special key codes (<xF1>, <xF2>, etc.)
+func Test_Keycode_Translation()
+ let keycodes = [
+ \ ["<xUp>", "<Up>"],
+ \ ["<xDown>", "<Down>"],
+ \ ["<xLeft>", "<Left>"],
+ \ ["<xRight>", "<Right>"],
+ \ ["<xHome>", "<Home>"],
+ \ ["<xEnd>", "<End>"],
+ \ ["<zHome>", "<Home>"],
+ \ ["<zEnd>", "<End>"],
+ \ ["<xF1>", "<F1>"],
+ \ ["<xF2>", "<F2>"],
+ \ ["<xF3>", "<F3>"],
+ \ ["<xF4>", "<F4>"],
+ \ ["<S-xF1>", "<S-F1>"],
+ \ ["<S-xF2>", "<S-F2>"],
+ \ ["<S-xF3>", "<S-F3>"],
+ \ ["<S-xF4>", "<S-F4>"]]
+ for [k1, k2] in keycodes
+ exe "nnoremap " .. k1 .. " 2wx"
+ call assert_true(maparg(k1, 'n', 0, 1).lhs == k2)
+ exe "nunmap " .. k1
+ endfor
+endfunc
+
+" Test for terminal keycodes that doesn't have termcap entries
+func Test_special_term_keycodes()
+ new
+ " Test for <xHome>, <S-xHome> and <C-xHome>
+ " send <K_SPECIAL> <KS_EXTRA> keycode
+ call feedkeys("i\<C-K>\x80\xfd\x3f\n", 'xt')
+ " send <K_SPECIAL> <KS_MODIFIER> bitmap <K_SPECIAL> <KS_EXTRA> keycode
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3f\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3f\n", 'xt')
+ " Test for <xEnd>, <S-xEnd> and <C-xEnd>
+ call feedkeys("i\<C-K>\x80\xfd\x3d\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3d\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3d\n", 'xt')
+ " Test for <zHome>, <S-zHome> and <C-zHome>
+ call feedkeys("i\<C-K>\x80\xfd\x40\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x40\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x40\n", 'xt')
+ " Test for <zEnd>, <S-zEnd> and <C-zEnd>
+ call feedkeys("i\<C-K>\x80\xfd\x3e\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x2\x80\xfd\x3e\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfc\x4\x80\xfd\x3e\n", 'xt')
+ " Test for <xUp>, <xDown>, <xLeft> and <xRight>
+ call feedkeys("i\<C-K>\x80\xfd\x41\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x42\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x43\n", 'xt')
+ call feedkeys("i\<C-K>\x80\xfd\x44\n", 'xt')
+ call assert_equal(['<Home>', '<S-Home>', '<C-Home>',
+ \ '<End>', '<S-End>', '<C-End>',
+ \ '<Home>', '<S-Home>', '<C-Home>',
+ \ '<End>', '<S-End>', '<C-End>',
+ \ '<Up>', '<Down>', '<Left>', '<Right>', ''], getline(1, '$'))
+ bw!
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim
new file mode 100644
index 0000000000..98a4bd3215
--- /dev/null
+++ b/test/old/testdir/test_termdebug.vim
@@ -0,0 +1,228 @@
+" Test for the termdebug plugin
+
+source shared.vim
+source check.vim
+
+CheckUnix
+" CheckFeature terminal
+CheckExecutable gdb
+CheckExecutable gcc
+
+let g:GDB = exepath('gdb')
+if g:GDB->empty()
+ throw 'Skipped: gdb is not found in $PATH'
+endif
+
+let g:GCC = exepath('gcc')
+if g:GCC->empty()
+ throw 'Skipped: gcc is not found in $PATH'
+endif
+
+packadd termdebug
+
+func Test_termdebug_basic()
+ let lines =<< trim END
+ #include <stdio.h>
+ #include <stdlib.h>
+
+ int isprime(int n)
+ {
+ if (n <= 1)
+ return 0;
+
+ for (int i = 2; i <= n / 2; i++)
+ if (n % i == 0)
+ return 0;
+
+ return 1;
+ }
+
+ int main(int argc, char *argv[])
+ {
+ int n = 7;
+
+ printf("%d is %s prime\n", n, isprime(n) ? "a" : "not a");
+
+ return 0;
+ }
+ END
+ call writefile(lines, 'XTD_basic.c', 'D')
+ call system($'{g:GCC} -g -o XTD_basic XTD_basic.c')
+
+ edit XTD_basic.c
+ Termdebug ./XTD_basic
+ call WaitForAssert({-> assert_equal(3, winnr('$'))})
+ let gdb_buf = winbufnr(1)
+ wincmd b
+ Break 9
+ call Nterm_wait(gdb_buf)
+ redraw!
+ call assert_equal([
+ \ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
+ \ 'priority': 110, 'group': 'TermDebug'}],
+ \ sign_getplaced('', #{group: 'TermDebug'})[0].signs)
+ Run
+ call Nterm_wait(gdb_buf, 400)
+ redraw!
+ call WaitForAssert({-> assert_equal([
+ \ {'lnum': 9, 'id': 12, 'name': 'debugPC', 'priority': 110,
+ \ 'group': 'TermDebug'},
+ \ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
+ \ 'priority': 110, 'group': 'TermDebug'}],
+ "\ sign_getplaced('', #{group: 'TermDebug'})[0].signs)})
+ \ sign_getplaced('', #{group: 'TermDebug'})[0].signs->reverse())})
+ Finish
+ call Nterm_wait(gdb_buf)
+ redraw!
+ call WaitForAssert({-> assert_equal([
+ \ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
+ \ 'priority': 110, 'group': 'TermDebug'},
+ \ {'lnum': 20, 'id': 12, 'name': 'debugPC',
+ \ 'priority': 110, 'group': 'TermDebug'}],
+ \ sign_getplaced('', #{group: 'TermDebug'})[0].signs)})
+ Continue
+ call Nterm_wait(gdb_buf)
+
+ let i = 2
+ while i <= 258
+ Break
+ call Nterm_wait(gdb_buf)
+ if i == 2
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint2.0')[0].text, '02')})
+ endif
+ if i == 10
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint10.0')[0].text, '0A')})
+ endif
+ if i == 168
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint168.0')[0].text, 'A8')})
+ endif
+ if i == 255
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint255.0')[0].text, 'FF')})
+ endif
+ if i == 256
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint256.0')[0].text, 'F+')})
+ endif
+ if i == 258
+ call WaitForAssert({-> assert_equal(sign_getdefined('debugBreakpoint258.0')[0].text, 'F+')})
+ endif
+ let i += 1
+ endwhile
+
+ let cn = 0
+ " 60 is approx spaceBuffer * 3
+ if winwidth(0) <= 78 + 60
+ Var
+ call assert_equal(winnr(), winnr('$'))
+ call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]])
+ let cn += 1
+ bw!
+ Asm
+ call assert_equal(winnr(), winnr('$'))
+ call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]])
+ let cn += 1
+ bw!
+ endif
+ set columns=160
+ call Nterm_wait(gdb_buf)
+ let winw = winwidth(0)
+ Var
+ if winwidth(0) < winw
+ call assert_equal(winnr(), winnr('$') - 1)
+ call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]])
+ let cn += 1
+ bw!
+ endif
+ let winw = winwidth(0)
+ Asm
+ if winwidth(0) < winw
+ call assert_equal(winnr(), winnr('$') - 1)
+ call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]])
+ let cn += 1
+ bw!
+ endif
+ set columns&
+ call Nterm_wait(gdb_buf)
+
+ wincmd t
+ quit!
+ redraw!
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+ call assert_equal([], sign_getplaced('', #{group: 'TermDebug'})[0].signs)
+
+ call delete('XTD_basic')
+ %bw!
+endfunc
+
+func Test_termdebug_mapping()
+ %bw!
+ call assert_equal(maparg('K', 'n', 0, 1)->empty(), 1)
+ call assert_equal(maparg('-', 'n', 0, 1)->empty(), 1)
+ call assert_equal(maparg('+', 'n', 0, 1)->empty(), 1)
+ Termdebug
+ call WaitForAssert({-> assert_equal(3, winnr('$'))})
+ wincmd b
+ call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('K', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('-', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('+', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('K', 'n', 0, 1).rhs, ':Evaluate<CR>')
+ wincmd t
+ quit!
+ redraw!
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+ call assert_equal(maparg('K', 'n', 0, 1)->empty(), 1)
+ call assert_equal(maparg('-', 'n', 0, 1)->empty(), 1)
+ call assert_equal(maparg('+', 'n', 0, 1)->empty(), 1)
+
+ %bw!
+ nnoremap K :echom "K"<cr>
+ nnoremap - :echom "-"<cr>
+ nnoremap + :echom "+"<cr>
+ Termdebug
+ call WaitForAssert({-> assert_equal(3, winnr('$'))})
+ wincmd b
+ call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('K', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('-', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('+', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('K', 'n', 0, 1).rhs, ':Evaluate<CR>')
+ wincmd t
+ quit!
+ redraw!
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+ call assert_equal(maparg('K', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('-', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('+', 'n', 0, 1)->empty(), 0)
+ call assert_equal(maparg('K', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('-', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('+', 'n', 0, 1).buffer, 0)
+ call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "K"<cr>')
+
+ %bw!
+ nnoremap <buffer> K :echom "bK"<cr>
+ nnoremap <buffer> - :echom "b-"<cr>
+ nnoremap <buffer> + :echom "b+"<cr>
+ Termdebug
+ call WaitForAssert({-> assert_equal(3, winnr('$'))})
+ wincmd b
+ call assert_equal(maparg('K', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('-', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('+', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "bK"<cr>')
+ wincmd t
+ quit!
+ redraw!
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+ call assert_equal(maparg('K', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('-', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('+', 'n', 0, 1).buffer, 1)
+ call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "bK"<cr>')
+
+ %bw!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_textformat.vim b/test/old/testdir/test_textformat.vim
index 7a13de12f4..7a13de12f4 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/test/old/testdir/test_textformat.vim
diff --git a/src/nvim/testdir/test_textobjects.vim b/test/old/testdir/test_textobjects.vim
index f21d6fcb99..f21d6fcb99 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/test/old/testdir/test_textobjects.vim
diff --git a/src/nvim/testdir/test_timers.vim b/test/old/testdir/test_timers.vim
index f94ee6c9f3..42114618fb 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/test/old/testdir/test_timers.vim
@@ -21,6 +21,7 @@ func MyHandlerWithLists(lists, timer)
endfunc
func Test_timer_oneshot()
+ let g:test_is_flaky = 1
let g:val = 0
let timer = timer_start(50, 'MyHandler')
let slept = WaitFor('g:val == 1')
@@ -33,6 +34,7 @@ func Test_timer_oneshot()
endfunc
func Test_timer_repeat_three()
+ let g:test_is_flaky = 1
let g:val = 0
let timer = timer_start(50, 'MyHandler', {'repeat': 3})
let slept = WaitFor('g:val == 3')
@@ -45,6 +47,7 @@ func Test_timer_repeat_three()
endfunc
func Test_timer_repeat_many()
+ let g:test_is_flaky = 1
let g:val = 0
let timer = timer_start(50, 'MyHandler', {'repeat': -1})
sleep 200m
@@ -53,6 +56,7 @@ func Test_timer_repeat_many()
endfunc
func Test_timer_with_partial_callback()
+ let g:test_is_flaky = 1
let g:val = 0
let meow = {'one': 1}
function meow.bite(...)
@@ -93,7 +97,13 @@ func Test_timer_info()
call timer_stop(id)
call assert_equal([], timer_info(id))
- call assert_fails('call timer_info("abc")', 'E39:')
+ call assert_fails('call timer_info("abc")', 'E1210:')
+
+ " check repeat count inside the callback
+ let g:timer_repeat = []
+ let tid = timer_start(10, {tid -> execute("call add(g:timer_repeat, timer_info(tid)[0].repeat)")}, #{repeat: 3})
+ call WaitForAssert({-> assert_equal([2, 1, 0], g:timer_repeat)})
+ unlet g:timer_repeat
endfunc
func Test_timer_stopall()
@@ -108,6 +118,7 @@ func Test_timer_stopall()
endfunc
func Test_timer_paused()
+ let g:test_is_flaky = 1
let g:val = 0
let id = timer_start(50, 'MyHandler')
@@ -162,6 +173,7 @@ func StopTimer2(timer)
endfunc
func Test_timer_stop_in_callback()
+ let g:test_is_flaky = 1
call assert_equal(0, len(timer_info()))
let g:timer1 = timer_start(10, 'StopTimer1')
let slept = 0
@@ -181,6 +193,7 @@ func StopTimerAll(timer)
endfunc
func Test_timer_stop_all_in_callback()
+ let g:test_is_flaky = 1
call assert_equal(0, len(timer_info()))
call timer_start(10, 'StopTimerAll')
call assert_equal(1, len(timer_info()))
@@ -228,9 +241,9 @@ func Test_timer_errors()
sleep 50m
call assert_equal(3, g:call_count)
- call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E475:')
+ call assert_fails('call timer_start(100, "MyHandler", "abc")', 'E1206:')
call assert_fails('call timer_start(100, [])', 'E921:')
- call assert_fails('call timer_stop("abc")', 'E39:')
+ call assert_fails('call timer_stop("abc")', 'E1210:')
endfunc
func FuncWithCaughtError(timer)
@@ -380,9 +393,9 @@ func Test_timer_error_in_timer_callback()
call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
" GC must not run during timer callback, which can make Vim crash.
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call term_sendkeys(buf, "\<CR>")
- call term_wait(buf, 100)
+ call TermWait(buf, 50)
call assert_equal('run', job_status(job))
call term_sendkeys(buf, ":qall!\<CR>")
diff --git a/src/nvim/testdir/test_true_false.vim b/test/old/testdir/test_true_false.vim
index f3c7fff4a6..f3c7fff4a6 100644
--- a/src/nvim/testdir/test_true_false.vim
+++ b/test/old/testdir/test_true_false.vim
diff --git a/src/nvim/testdir/test_trycatch.vim b/test/old/testdir/test_trycatch.vim
index ef20e03126..d60b793f1b 100644
--- a/src/nvim/testdir/test_trycatch.vim
+++ b/test/old/testdir/test_trycatch.vim
@@ -38,7 +38,7 @@ func T25_F()
if loops == 2
try
Xpath 'f' . loops
- finally
+ final
Xpath 'g' . loops
endtry
endif
@@ -50,19 +50,20 @@ func T25_F()
Xpath 'i'
endfunc
+" Also try using "fina" and "final" and "finall" as abbraviations.
func T25_G()
if 1
try
Xpath 'A'
call T25_F()
Xpath 'B'
- finally
+ fina
Xpath 'C'
endtry
else
try
Xpath 'D'
- finally
+ finall
Xpath 'E'
endtry
endif
@@ -2220,6 +2221,61 @@ func Test_BufEnter_exception()
%bwipe!
endfunc
+" Test for using try/catch when lines are joined by "|" or "\n" {{{1
+func Test_try_catch_nextcmd()
+ func Throw()
+ throw "Failure"
+ endfunc
+
+ let lines =<< trim END
+ try
+ let s:x = Throw()
+ catch
+ let g:caught = 1
+ endtry
+ END
+
+ let g:caught = 0
+ call execute(lines)
+ call assert_equal(1, g:caught)
+
+ let g:caught = 0
+ call execute(join(lines, '|'))
+ call assert_equal(1, g:caught)
+
+ let g:caught = 0
+ call execute(join(lines, "\n"))
+ call assert_equal(1, g:caught)
+
+ unlet g:caught
+ delfunc Throw
+endfunc
+
+" Test for using try/catch in a user command with a failing expression {{{1
+func Test_user_command_try_catch()
+ let lines =<< trim END
+ function s:throw() abort
+ throw 'error'
+ endfunction
+
+ command! Execute
+ \ try
+ \ | let s:x = s:throw()
+ \ | catch
+ \ | let g:caught = 'caught'
+ \ | endtry
+
+ let g:caught = 'no'
+ Execute
+ call assert_equal('caught', g:caught)
+ END
+ call writefile(lines, 'XtestTryCatch')
+ source XtestTryCatch
+
+ call delete('XtestTryCatch')
+ unlet g:caught
+endfunc
+
" Test for using throw in a called function with following error {{{1
func Test_user_command_throw_in_function_call()
let lines =<< trim END
diff --git a/src/nvim/testdir/test_undo.vim b/test/old/testdir/test_undo.vim
index ee8b52caaf..a06731cc96 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/test/old/testdir/test_undo.vim
@@ -93,6 +93,65 @@ func FillBuffer()
endfor
endfunc
+func Test_undotree_bufnr()
+ new
+ let buf1 = bufnr()
+
+ normal! Aabc
+ set ul=100
+
+ " Save undo tree without bufnr as ground truth for buffer 1
+ let d1 = undotree()
+
+ new
+ let buf2 = bufnr()
+
+ normal! Adef
+ set ul=100
+
+ normal! Aghi
+ set ul=100
+
+ " Save undo tree without bufnr as ground truth for buffer 2
+ let d2 = undotree()
+
+ " Check undotree() with bufnr argument
+ let d = undotree(buf1)
+ call assert_equal(d1, d)
+ call assert_notequal(d2, d)
+
+ let d = undotree(buf2)
+ call assert_notequal(d1, d)
+ call assert_equal(d2, d)
+
+ " Switch buffers and check again
+ wincmd p
+
+ let d = undotree(buf1)
+ call assert_equal(d1, d)
+
+ let d = undotree(buf2)
+ call assert_notequal(d1, d)
+ call assert_equal(d2, d)
+
+ " error cases
+ call assert_fails('call undotree(-1)', 'E158:')
+ call assert_fails('call undotree("nosuchbuf")', 'E158:')
+
+ " after creating a buffer nosuchbuf, undotree('nosuchbuf') should
+ " not error out
+ new nosuchbuf
+ let d = {'seq_last': 0, 'entries': [], 'time_cur': 0, 'save_last': 0, 'synced': 1, 'save_cur': 0, 'seq_cur': 0}
+ call assert_equal(d, undotree("nosuchbuf"))
+ " clean up
+ bw nosuchbuf
+
+ " Drop created windows
+ set ul&
+ new
+ only!
+endfunc
+
func Test_global_local_undolevels()
new one
set undolevels=5
@@ -126,6 +185,11 @@ func Test_global_local_undolevels()
call assert_equal(50, &g:undolevels)
call assert_equal(-123456, &l:undolevels)
+ " Resetting the local 'undolevels' value to use the global value
+ setlocal undolevels=5
+ setlocal undolevels<
+ call assert_equal(-123456, &l:undolevels)
+
" Drop created windows
set ul&
new
@@ -335,6 +399,11 @@ endfunc
func Test_undofile_earlier()
throw 'Skipped: Nvim does not support test_settime()'
+ if has('win32')
+ " FIXME: This test is flaky on MS-Windows.
+ let g:test_is_flaky = 1
+ endif
+
" Issue #1254
" create undofile with timestamps older than Vim startup time.
let t0 = localtime() - 43200
@@ -799,7 +868,18 @@ func Test_undo_after_write()
call StopVimInTerminal(buf)
call delete('Xtestfile.txt')
+ call delete('.Xtestfile.txt.un~')
endfunc
+func Test_undo_range_normal()
+ new
+ call setline(1, ['asa', 'bsb'])
+ let &l:undolevels = &l:undolevels
+ %normal dfs
+ call assert_equal(['a', 'b'], getline(1, '$'))
+ undo
+ call assert_equal(['asa', 'bsb'], getline(1, '$'))
+ bwipe!
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_unlet.vim b/test/old/testdir/test_unlet.vim
index 4779d17906..4779d17906 100644
--- a/src/nvim/testdir/test_unlet.vim
+++ b/test/old/testdir/test_unlet.vim
diff --git a/src/nvim/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim
index 4742293ed5..fc7dcd62b8 100644
--- a/src/nvim/testdir/test_user_func.vim
+++ b/test/old/testdir/test_user_func.vim
@@ -5,6 +5,7 @@
source check.vim
source shared.vim
+source vim9.vim
func Table(title, ...)
let ret = a:title
@@ -501,4 +502,393 @@ func Test_func_range()
bwipe!
endfunc
+" Test for memory allocation failure when defining a new function
+func Test_funcdef_alloc_failure()
+ CheckFunction test_alloc_fail
+ new
+ let lines =<< trim END
+ func Xtestfunc()
+ return 321
+ endfunc
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xtestfunc'))
+ call assert_fails('delfunc Xtestfunc', 'E117:')
+ %d _
+ let lines =<< trim END
+ def g:Xvim9func(): number
+ return 456
+ enddef
+ END
+ call setline(1, lines)
+ call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ call assert_fails('source', 'E342:')
+ call assert_false(exists('*Xvim9func'))
+ "call test_alloc_fail(GetAllocId('get_func'), 0, 0)
+ "call assert_fails('source', 'E342:')
+ "call assert_false(exists('*Xtestfunc'))
+ "call assert_fails('delfunc Xtestfunc', 'E117:')
+ bw!
+endfunc
+
+func AddDefer(arg1, ...)
+ call extend(g:deferred, [a:arg1])
+ if a:0 == 1
+ call extend(g:deferred, [a:1])
+ endif
+endfunc
+
+func WithDeferTwo()
+ call extend(g:deferred, ['in Two'])
+ for nr in range(3)
+ defer AddDefer('Two' .. nr)
+ endfor
+ call extend(g:deferred, ['end Two'])
+endfunc
+
+func WithDeferOne()
+ call extend(g:deferred, ['in One'])
+ call writefile(['text'], 'Xfuncdefer')
+ defer delete('Xfuncdefer')
+ defer AddDefer('One')
+ call WithDeferTwo()
+ call extend(g:deferred, ['end One'])
+endfunc
+
+func WithPartialDefer()
+ call extend(g:deferred, ['in Partial'])
+ let Part = funcref('AddDefer', ['arg1'])
+ defer Part("arg2")
+ call extend(g:deferred, ['end Partial'])
+endfunc
+
+func Test_defer()
+ let g:deferred = []
+ call WithDeferOne()
+
+ call assert_equal(['in One', 'in Two', 'end Two', 'Two2', 'Two1', 'Two0', 'end One', 'One'], g:deferred)
+ unlet g:deferred
+
+ call assert_equal('', glob('Xfuncdefer'))
+
+ call assert_fails('defer delete("Xfuncdefer")->Another()', 'E488:')
+ call assert_fails('defer delete("Xfuncdefer").member', 'E488:')
+
+ let g:deferred = []
+ call WithPartialDefer()
+ call assert_equal(['in Partial', 'end Partial', 'arg1', 'arg2'], g:deferred)
+ unlet g:deferred
+
+ let Part = funcref('AddDefer', ['arg1'], {})
+ call assert_fails('defer Part("arg2")', 'E1300:')
+endfunc
+
+func DeferLevelTwo()
+ call writefile(['text'], 'XDeleteTwo', 'D')
+ throw 'someerror'
+endfunc
+
+" def DeferLevelOne()
+func DeferLevelOne()
+ call writefile(['text'], 'XDeleteOne', 'D')
+ call g:DeferLevelTwo()
+" enddef
+endfunc
+
+func Test_defer_throw()
+ let caught = 'no'
+ try
+ call DeferLevelOne()
+ catch /someerror/
+ let caught = 'yes'
+ endtry
+ call assert_equal('yes', caught)
+ call assert_false(filereadable('XDeleteOne'))
+ call assert_false(filereadable('XDeleteTwo'))
+endfunc
+
+func Test_defer_quitall_func()
+ let lines =<< trim END
+ func DeferLevelTwo()
+ call writefile(['text'], 'XQuitallFuncTwo', 'D')
+ call writefile(['quit'], 'XQuitallFuncThree', 'a')
+ qa!
+ endfunc
+
+ func DeferLevelOne()
+ call writefile(['text'], 'XQuitalFunclOne', 'D')
+ defer DeferLevelTwo()
+ endfunc
+
+ call DeferLevelOne()
+ END
+ call writefile(lines, 'XdeferQuitallFunc', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallFunc')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('XQuitallFuncOne'))
+ call assert_false(filereadable('XQuitallFuncTwo'))
+ call assert_equal(['quit'], readfile('XQuitallFuncThree'))
+
+ call delete('XQuitallFuncThree')
+endfunc
+
+func Test_defer_quitall_def()
+ throw 'Skipped: Vim9 script is N/A'
+ let lines =<< trim END
+ vim9script
+ def DeferLevelTwo()
+ call writefile(['text'], 'XQuitallDefTwo', 'D')
+ call writefile(['quit'], 'XQuitallDefThree', 'a')
+ qa!
+ enddef
+
+ def DeferLevelOne()
+ call writefile(['text'], 'XQuitallDefOne', 'D')
+ defer DeferLevelTwo()
+ enddef
+
+ DeferLevelOne()
+ END
+ call writefile(lines, 'XdeferQuitallDef', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallDef')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('XQuitallDefOne'))
+ call assert_false(filereadable('XQuitallDefTwo'))
+ call assert_equal(['quit'], readfile('XQuitallDefThree'))
+
+ call delete('XQuitallDefThree')
+endfunc
+
+func Test_defer_quitall_autocmd()
+ let lines =<< trim END
+ func DeferLevelFive()
+ defer writefile(['5'], 'XQuitallAutocmd', 'a')
+ qa!
+ endfunc
+
+ autocmd User DeferAutocmdFive call DeferLevelFive()
+
+ " def DeferLevelFour()
+ func DeferLevelFour()
+ defer writefile(['4'], 'XQuitallAutocmd', 'a')
+ doautocmd User DeferAutocmdFive
+ " enddef
+ endfunc
+
+ func DeferLevelThree()
+ defer writefile(['3'], 'XQuitallAutocmd', 'a')
+ call DeferLevelFour()
+ endfunc
+
+ autocmd User DeferAutocmdThree ++nested call DeferLevelThree()
+
+ " def DeferLevelTwo()
+ func DeferLevelTwo()
+ defer writefile(['2'], 'XQuitallAutocmd', 'a')
+ doautocmd User DeferAutocmdThree
+ " enddef
+ endfunc
+
+ func DeferLevelOne()
+ defer writefile(['1'], 'XQuitallAutocmd', 'a')
+ call DeferLevelTwo()
+ endfunc
+
+ autocmd User DeferAutocmdOne ++nested call DeferLevelOne()
+
+ doautocmd User DeferAutocmdOne
+ END
+ call writefile(lines, 'XdeferQuitallAutocmd', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallAutocmd')
+ call assert_equal(0, v:shell_error)
+ call assert_equal(['5', '4', '3', '2', '1'], readfile('XQuitallAutocmd'))
+
+ call delete('XQuitallAutocmd')
+endfunc
+
+func Test_defer_quitall_in_expr_func()
+ throw 'Skipped: Vim9 script is N/A'
+ let lines =<< trim END
+ def DefIndex(idx: number, val: string): bool
+ call writefile([idx .. ': ' .. val], 'Xentry' .. idx, 'D')
+ if val == 'b'
+ qa!
+ endif
+ return val == 'c'
+ enddef
+
+ def Test_defer_in_funcref()
+ assert_equal(2, indexof(['a', 'b', 'c'], funcref('g:DefIndex')))
+ enddef
+ call Test_defer_in_funcref()
+ END
+ call writefile(lines, 'XdeferQuitallExpr', 'D')
+ call system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
+ call assert_equal(0, v:shell_error)
+ call assert_false(filereadable('Xentry0'))
+ call assert_false(filereadable('Xentry1'))
+ call assert_false(filereadable('Xentry2'))
+endfunc
+
+func FuncIndex(idx, val)
+ call writefile([a:idx .. ': ' .. a:val], 'Xentry' .. a:idx, 'D')
+ return a:val == 'c'
+endfunc
+
+func Test_defer_wrong_arguments()
+ call assert_fails('defer delete()', 'E119:')
+ call assert_fails('defer FuncIndex(1)', 'E119:')
+ call assert_fails('defer delete(1, 2, 3)', 'E118:')
+ call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:')
+
+ throw 'Skipped: Vim9 script is N/A'
+ let lines =<< trim END
+ def DeferFunc0()
+ defer delete()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferFunc3()
+ defer delete(1, 2, 3)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferFunc2()
+ defer delete(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+
+ def g:FuncOneArg(arg: string)
+ echo arg
+ enddef
+
+ let lines =<< trim END
+ def DeferUserFunc0()
+ defer g:FuncOneArg()
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E119:')
+ let lines =<< trim END
+ def DeferUserFunc2()
+ defer g:FuncOneArg(1, 2)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E118:')
+ let lines =<< trim END
+ def DeferUserFunc1()
+ defer g:FuncOneArg(1)
+ enddef
+ defcompile
+ END
+ call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+endfunc
+
+" Test for calling a deferred function after an exception
+func Test_defer_after_exception()
+ let g:callTrace = []
+ func Bar()
+ let g:callTrace += [1]
+ throw 'InnerException'
+ endfunc
+
+ func Defer()
+ let g:callTrace += [2]
+ let g:callTrace += [3]
+ try
+ call Bar()
+ catch /InnerException/
+ let g:callTrace += [4]
+ endtry
+ let g:callTrace += [5]
+ let g:callTrace += [6]
+ endfunc
+
+ func Foo()
+ defer Defer()
+ throw "TestException"
+ endfunc
+
+ try
+ call Foo()
+ catch /TestException/
+ let g:callTrace += [7]
+ endtry
+ call assert_equal([2, 3, 1, 4, 5, 6, 7], g:callTrace)
+
+ delfunc Defer
+ delfunc Foo
+ delfunc Bar
+ unlet g:callTrace
+endfunc
+
+" Test for multiple deferred function which throw exceptions.
+" Exceptions thrown by deferred functions should result in error messages but
+" not propagated into the calling functions.
+func Test_multidefer_with_exception()
+ let g:callTrace = []
+ func Except()
+ let g:callTrace += [1]
+ throw 'InnerException'
+ let g:callTrace += [2]
+ endfunc
+
+ func FirstDefer()
+ let g:callTrace += [3]
+ let g:callTrace += [4]
+ endfunc
+
+ func SecondDeferWithExcept()
+ let g:callTrace += [5]
+ call Except()
+ let g:callTrace += [6]
+ endfunc
+
+ func ThirdDefer()
+ let g:callTrace += [7]
+ let g:callTrace += [8]
+ endfunc
+
+ func Foo()
+ let g:callTrace += [9]
+ defer FirstDefer()
+ defer SecondDeferWithExcept()
+ defer ThirdDefer()
+ let g:callTrace += [10]
+ endfunc
+
+ let v:errmsg = ''
+ try
+ let g:callTrace += [11]
+ call Foo()
+ let g:callTrace += [12]
+ catch /TestException/
+ let g:callTrace += [13]
+ catch
+ let g:callTrace += [14]
+ finally
+ let g:callTrace += [15]
+ endtry
+ let g:callTrace += [16]
+
+ call assert_equal('E605: Exception not caught: InnerException', v:errmsg)
+ call assert_equal([11, 9, 10, 7, 8, 5, 1, 3, 4, 12, 15, 16], g:callTrace)
+
+ unlet g:callTrace
+ delfunc Except
+ delfunc FirstDefer
+ delfunc SecondDeferWithExcept
+ delfunc ThirdDefer
+ delfunc Foo
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_usercommands.vim b/test/old/testdir/test_usercommands.vim
index 6910361345..e22f57b6f1 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/test/old/testdir/test_usercommands.vim
@@ -303,7 +303,7 @@ func Test_CmdErrors()
call assert_fails('com! -complete=xxx DoCmd :', 'E180:')
call assert_fails('com! -complete=custom DoCmd :', 'E467:')
call assert_fails('com! -complete=customlist DoCmd :', 'E467:')
- call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:')
+ " call assert_fails('com! -complete=behave,CustomComplete DoCmd :', 'E468:')
call assert_fails('com! -complete=file DoCmd :', 'E1208:')
call assert_fails('com! -nargs=0 -complete=file DoCmd :', 'E1208:')
call assert_fails('com! -nargs=x DoCmd :', 'E176:')
@@ -391,9 +391,9 @@ func Test_CmdCompletion()
call feedkeys(":com DoC\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"com DoC', @:)
- com! -nargs=1 -complete=behave DoCmd :
- call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"DoCmd mswin xterm', @:)
+ " com! -nargs=1 -complete=behave DoCmd :
+ " call feedkeys(":DoCmd \<C-A>\<C-B>\"\<CR>", 'tx')
+ " call assert_equal('"DoCmd mswin xterm', @:)
" Test for file name completion
com! -nargs=1 -complete=file DoCmd :
@@ -736,6 +736,15 @@ func Test_buflocal_ambiguous_usercmd()
bw!
endfunc
+" Test for using buffer-local user command from cmdwin.
+func Test_buflocal_usercmd_cmdwin()
+ new
+ command -buffer TestCmd edit Test
+ " This used to crash Vim
+ call assert_fails("norm q::TestCmd\<CR>", 'E11:')
+ bw!
+endfunc
+
" Test for using a multibyte character in a user command
func Test_multibyte_in_usercmd()
command SubJapanesePeriodToDot exe "%s/\u3002/./g"
diff --git a/src/nvim/testdir/test_utf8.vim b/test/old/testdir/test_utf8.vim
index e5f6d68720..a5a9624ec3 100644
--- a/src/nvim/testdir/test_utf8.vim
+++ b/test/old/testdir/test_utf8.vim
@@ -29,8 +29,10 @@ func Test_strchars()
call assert_equal(exp[i], strcharlen(inp[i]))
endfor
- call assert_fails("let v=strchars('abc', [])", 'E745:')
- call assert_fails("let v=strchars('abc', 2)", 'E1023:')
+ call assert_fails("call strchars('abc', 2)", ['E1023:', 'E1023:'])
+ call assert_fails("call strchars('abc', -1)", ['E1023:', 'E1023:'])
+ call assert_fails("call strchars('abc', {})", ['E728:', 'E728:'])
+ call assert_fails("call strchars('abc', [])", ['E745:', 'E745:'])
endfunc
" Test for customlist completion
@@ -86,7 +88,20 @@ func Test_screenchar_utf8()
call assert_equal("B", screenstring(1, 2))
call assert_equal("C\u0308", screenstring(1, 3))
- " 2-cells, with composing characters
+ " 1-cell, with 6 composing characters
+ set maxcombine=6
+ call setline(1, ["ABC" .. repeat("\u0308", 6)])
+ redraw
+ call assert_equal([0x0041], screenchars(1, 1))
+ call assert_equal([0x0042], 1->screenchars(2))
+ " This should not use uninitialized memory
+ call assert_equal([0x0043] + repeat([0x0308], 6), screenchars(1, 3))
+ call assert_equal("A", screenstring(1, 1))
+ call assert_equal("B", screenstring(1, 2))
+ call assert_equal("C" .. repeat("\u0308", 6), screenstring(1, 3))
+ set maxcombine&
+
+ " 2-cells, with composing characters
let text = "\u3042\u3044\u3046\u3099"
call setline(1, text)
redraw
diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/test/old/testdir/test_utf8_comparisons.vim
index f3c86b44fb..f3c86b44fb 100644
--- a/src/nvim/testdir/test_utf8_comparisons.vim
+++ b/test/old/testdir/test_utf8_comparisons.vim
diff --git a/src/nvim/testdir/test_vartabs.vim b/test/old/testdir/test_vartabs.vim
index e12c71d521..e12c71d521 100644
--- a/src/nvim/testdir/test_vartabs.vim
+++ b/test/old/testdir/test_vartabs.vim
diff --git a/src/nvim/testdir/test_version.vim b/test/old/testdir/test_version.vim
index 5fd38f7cdc..5fd38f7cdc 100644
--- a/src/nvim/testdir/test_version.vim
+++ b/test/old/testdir/test_version.vim
diff --git a/src/nvim/testdir/test_viminfo.vim b/test/old/testdir/test_viminfo.vim
index e792db90ab..e792db90ab 100644
--- a/src/nvim/testdir/test_viminfo.vim
+++ b/test/old/testdir/test_viminfo.vim
diff --git a/src/nvim/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim
index b0c4baf7c2..6ce59e1a2e 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/test/old/testdir/test_vimscript.vim
@@ -3087,7 +3087,7 @@ func Test_nested_if_else_errors()
endif
END
call writefile(code, 'Xtest')
- call AssertException(['source Xtest'], 'Vim(else):E583: multiple :else')
+ call AssertException(['source Xtest'], 'Vim(else):E583: Multiple :else')
" :elseif after :else
let code =<< trim END
@@ -3109,7 +3109,7 @@ endfunc
" should be given.
"
" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
+" This function checks the messages in g:msgfile.
"-------------------------------------------------------------------------------
func Test_nested_while_error()
@@ -3236,7 +3236,7 @@ endfunc
" error messages should be given.
"
" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
+" This function checks the messages in g:msgfile.
"-------------------------------------------------------------------------------
func Test_nested_cont_break_error()
@@ -3344,7 +3344,7 @@ endfunc
" should be given.
"
" This test reuses the function MESSAGES() from the previous test.
-" This functions checks the messages in g:msgfile.
+" This function check the messages in g:msgfile.
"-------------------------------------------------------------------------------
func Test_nested_endtry_error()
@@ -3641,7 +3641,7 @@ endfunc
" exceptions.
"-------------------------------------------------------------------------------
-func Test_execption_info_for_error()
+func Test_exception_info_for_error()
CheckEnglish
let test =<< trim [CODE]
@@ -5983,6 +5983,9 @@ endfunc
" interrupt right before a catch is invoked in a script
func Test_ignore_catch_after_intr_1()
+ " for unknown reasons this test sometimes fails on MS-Windows.
+ let g:test_is_flaky = 1
+
XpathINIT
let lines =<< trim [CODE]
try
@@ -6021,6 +6024,9 @@ endfunc
" interrupt right before a catch is invoked inside a function.
func Test_ignore_catch_after_intr_2()
+ " for unknown reasons this test sometimes fails on MS-Windows.
+ let g:test_is_flaky = 1
+
XpathINIT
func F()
try
@@ -6508,9 +6514,17 @@ func Test_type()
call assert_equal(v:t_float, type(0.0))
call assert_equal(v:t_bool, type(v:false))
call assert_equal(v:t_bool, type(v:true))
+ " call assert_equal(v:t_none, type(v:none))
+ " call assert_equal(v:t_none, type(v:null))
call assert_equal(v:t_string, type(v:_null_string))
call assert_equal(v:t_list, type(v:_null_list))
call assert_equal(v:t_dict, type(v:_null_dict))
+ if has('job')
+ call assert_equal(v:t_job, type(test_null_job()))
+ endif
+ if has('channel')
+ call assert_equal(v:t_channel, type(test_null_channel()))
+ endif
call assert_equal(v:t_blob, type(v:_null_blob))
call assert_equal(0, 0 + v:false)
@@ -6691,6 +6705,11 @@ func Test_echo_and_string()
let l = split(result, "\n")
call assert_equal(["{'a': [], 'b': []}",
\ "{'a': [], 'b': []}"], l)
+
+ call assert_fails('echo &:', 'E112:')
+ call assert_fails('echo &g:', 'E112:')
+ call assert_fails('echo &l:', 'E112:')
+
endfunc
"-------------------------------------------------------------------------------
@@ -6746,7 +6765,7 @@ func Test_script_lines()
\ ])
call assert_report("Shouldn't be able to define function")
catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
endtry
" :change
@@ -6766,7 +6785,7 @@ func Test_script_lines()
\ ])
call assert_report("Shouldn't be able to define function")
catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
endtry
" :insert
@@ -6786,7 +6805,7 @@ func Test_script_lines()
\ ])
call assert_report("Shouldn't be able to define function")
catch
- call assert_exception('Vim(function):E126: Missing :endfunction')
+ call assert_exception('Vim(function):E1145: Missing heredoc end marker: .')
endtry
endfunc
@@ -6978,7 +6997,7 @@ func Test_compound_assignment_operators()
call assert_equal(6, &scrolljump)
let &scrolljump %= 5
call assert_equal(1, &scrolljump)
- call assert_fails('let &scrolljump .= "j"', 'E734:')
+ call assert_fails('let &scrolljump .= "j"', ['E734:', 'E734:'])
set scrolljump&vim
let &foldlevelstart = 2
@@ -7016,6 +7035,122 @@ func Test_unlet_env()
call assert_equal('', $TESTVAR)
endfunc
+func Test_refcount()
+ throw 'Skipped: Nvim does not support test_refcount()'
+ " Immediate values
+ call assert_equal(-1, test_refcount(1))
+ call assert_equal(-1, test_refcount('s'))
+ call assert_equal(-1, test_refcount(v:true))
+ call assert_equal(0, test_refcount([]))
+ call assert_equal(0, test_refcount({}))
+ call assert_equal(0, test_refcount(0zff))
+ call assert_equal(0, test_refcount({-> line('.')}))
+ call assert_equal(-1, test_refcount(0.1))
+ if has('job')
+ call assert_equal(0, test_refcount(job_start([&shell, &shellcmdflag, 'echo .'])))
+ endif
+
+ " No refcount types
+ let x = 1
+ call assert_equal(-1, test_refcount(x))
+ let x = 's'
+ call assert_equal(-1, test_refcount(x))
+ let x = v:true
+ call assert_equal(-1, test_refcount(x))
+ let x = 0.1
+ call assert_equal(-1, test_refcount(x))
+
+ " Check refcount
+ let x = []
+ call assert_equal(1, test_refcount(x))
+
+ let x = {}
+ call assert_equal(1, x->test_refcount())
+
+ let x = 0zff
+ call assert_equal(1, test_refcount(x))
+
+ let X = {-> line('.')}
+ call assert_equal(1, test_refcount(X))
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+
+ if has('job')
+ let job = job_start([&shell, &shellcmdflag, 'echo .'])
+ call assert_equal(1, test_refcount(job))
+ call assert_equal(1, test_refcount(job_getchannel(job)))
+ call assert_equal(1, test_refcount(job))
+ endif
+
+ " Function arguments, copying and unassigning
+ func ExprCheck(x, i)
+ let i = a:i + 1
+ call assert_equal(i, test_refcount(a:x))
+ let Y = a:x
+ call assert_equal(i + 1, test_refcount(a:x))
+ call assert_equal(test_refcount(a:x), test_refcount(Y))
+ let Y = 0
+ call assert_equal(i, test_refcount(a:x))
+ endfunc
+ call ExprCheck([], 0)
+ call ExprCheck({}, 0)
+ call ExprCheck(0zff, 0)
+ call ExprCheck({-> line('.')}, 0)
+ if has('job')
+ call ExprCheck(job, 1)
+ call ExprCheck(job_getchannel(job), 1)
+ call job_stop(job)
+ endif
+ delfunc ExprCheck
+
+ " Regarding function
+ func Func(x) abort
+ call assert_equal(2, test_refcount(function('Func')))
+ call assert_equal(0, test_refcount(funcref('Func')))
+ endfunc
+ call assert_equal(1, test_refcount(function('Func')))
+ call assert_equal(0, test_refcount(function('Func', [1])))
+ call assert_equal(0, test_refcount(funcref('Func')))
+ call assert_equal(0, test_refcount(funcref('Func', [1])))
+ let X = function('Func')
+ let Y = X
+ call assert_equal(1, test_refcount(X))
+ let X = function('Func', [1])
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ let X = funcref('Func')
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ let X = funcref('Func', [1])
+ let Y = X
+ call assert_equal(2, test_refcount(X))
+ unlet X
+ unlet Y
+ call Func(1)
+ delfunc Func
+
+ " Function with dict
+ func DictFunc() dict
+ call assert_equal(3, test_refcount(self))
+ endfunc
+ let d = {'Func': function('DictFunc')}
+ call assert_equal(1, test_refcount(d))
+ call assert_equal(0, test_refcount(d.Func))
+ call d.Func()
+ unlet d
+ delfunc DictFunc
+
+ if has('channel')
+ call assert_equal(-1, test_refcount(test_null_job()))
+ call assert_equal(-1, test_refcount(test_null_channel()))
+ endif
+ call assert_equal(-1, test_refcount(test_null_function()))
+ call assert_equal(-1, test_refcount(test_null_partial()))
+ call assert_equal(-1, test_refcount(test_null_blob()))
+ call assert_equal(-1, test_refcount(test_null_list()))
+ call assert_equal(-1, test_refcount(test_null_dict()))
+endfunc
+
" Test for missing :endif, :endfor, :endwhile and :endtry {{{1
func Test_missing_end()
call writefile(['if 2 > 1', 'echo ">"'], 'Xscript')
@@ -7114,30 +7249,30 @@ func Test_deep_nest()
" Deep nesting of if ... endif
call term_sendkeys(buf, ":call Test1()\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E579:', term_getline(buf, 5))})
" Deep nesting of for ... endfor
call term_sendkeys(buf, ":call Test2()\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
" Deep nesting of while ... endwhile
call term_sendkeys(buf, ":call Test3()\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E585:', term_getline(buf, 5))})
" Deep nesting of try ... endtry
call term_sendkeys(buf, ":call Test4()\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E601:', term_getline(buf, 5))})
" Deep nesting of function ... endfunction
call term_sendkeys(buf, ":call Test5()\n")
- call term_wait(buf)
+ call TermWait(buf)
call WaitForAssert({-> assert_match('^E1058:', term_getline(buf, 4))})
call term_sendkeys(buf, "\<C-C>\n")
- call term_wait(buf)
+ call TermWait(buf)
"let l = ''
"for i in range(1, 6)
@@ -7258,6 +7393,30 @@ func Test_typed_script_var()
call StopVimInTerminal(buf)
endfunc
+" Test for issue6776 {{{1
+func Test_ternary_expression()
+ try
+ call eval('0 ? 0')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+
+ try
+ call eval('0 ? "burp')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+
+ try
+ call eval('1 ? 0 : "burp')
+ catch
+ endtry
+ " previous failure should not cause next expression to fail
+ call assert_equal(v:false, eval(string(v:false)))
+endfunction
+
func Test_for_over_string()
let res = ''
for c in 'aéc̀d'
diff --git a/src/nvim/testdir/test_virtualedit.vim b/test/old/testdir/test_virtualedit.vim
index 20a5f87517..4780faa706 100644
--- a/src/nvim/testdir/test_virtualedit.vim
+++ b/test/old/testdir/test_virtualedit.vim
@@ -88,6 +88,16 @@ func Test_edit_change()
set virtualedit=
endfunc
+func Test_edit_special_char()
+ new
+ se ve=all
+ norm a0
+ sil! exe "norm o00000\<Nul>k<a0s"
+
+ bwipe!
+ set virtualedit=
+endfunc
+
" Tests for pasting at the beginning, end and middle of a tab character
" in virtual edit mode.
func Test_paste_in_tab()
@@ -226,7 +236,7 @@ func Test_ve_completion()
set virtualedit=
endfunc
-" Using "C" then then <CR> moves the last remaining character to the next
+" Using "C" then <CR> moves the last remaining character to the next
" line. (Mary Ellen Foster)
func Test_ve_del_to_eol()
new
@@ -554,31 +564,115 @@ func Test_virtualedit_mouse()
let save_mouse = &mouse
set mouse=a
set virtualedit=all
- new
+ botright new
+ let row = win_screenpos(0)[0]
+ 20vsplit
+ wincmd p
call setline(1, ["text\tword"])
redraw
- call Ntest_setmouse(1, 4)
+ call Ntest_setmouse(row, 21 + 4)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 4, 0, 4], getcurpos())
- call Ntest_setmouse(1, 5)
+ call Ntest_setmouse(row, 21 + 5)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 5, 0, 5], getcurpos())
- call Ntest_setmouse(1, 6)
+ call Ntest_setmouse(row, 21 + 6)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 5, 1, 6], getcurpos())
- call Ntest_setmouse(1, 7)
+ call Ntest_setmouse(row, 21 + 7)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 5, 2, 7], getcurpos())
- call Ntest_setmouse(1, 8)
+ call Ntest_setmouse(row, 21 + 8)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 5, 3, 8], getcurpos())
- call Ntest_setmouse(1, 9)
+ call Ntest_setmouse(row, 21 + 9)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 6, 0, 9], getcurpos())
- call Ntest_setmouse(1, 15)
+ call Ntest_setmouse(row, 21 + 12)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 9, 0, 12], getcurpos())
+ call Ntest_setmouse(row, 21 + 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 0, 13], getcurpos())
+ call Ntest_setmouse(row, 21 + 15)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 2, 15], getcurpos())
+ call Ntest_setmouse(row, 21 + 20)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 7, 20], getcurpos())
+
+ setlocal nowrap
+ call setline(2, repeat('a', 19))
+ normal! j14zl
+ redraw
+ call Ntest_setmouse(row, 21 + 1)
call feedkeys("\<LeftMouse>", "xt")
call assert_equal([0, 1, 10, 2, 15], getcurpos())
+ call Ntest_setmouse(row, 21 + 11)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 12, 25], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 1)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 15, 0, 15], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 11)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 20, 5, 25], getcurpos())
+
+ setlocal number numberwidth=2
+ redraw
+ call Ntest_setmouse(row, 21 + 3)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 2, 15], getcurpos())
+ call Ntest_setmouse(row, 21 + 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 12, 25], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 3)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 15, 0, 15], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 20, 5, 25], getcurpos())
+ setlocal nonumber
+
+ if has('signs')
+ sign define Sign1 text=口
+ sign place 1 name=Sign1 line=1
+ sign place 2 name=Sign1 line=2
+ redraw
+ call Ntest_setmouse(row, 21 + 3)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 2, 15], getcurpos())
+ call Ntest_setmouse(row, 21 + 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 10, 12, 25], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 3)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 15, 0, 15], getcurpos())
+ call Ntest_setmouse(row + 1, 21 + 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 2, 20, 5, 25], getcurpos())
+ sign unplace 1
+ sign unplace 2
+ sign undefine Sign1
+ endif
+
+ wincmd h
+ 4wincmd >
+ normal! gg24I.
+ redraw
+ call Ntest_setmouse(row + 1, 12)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 24 + 9, 0, 24 + 12], getcurpos())
+ call Ntest_setmouse(row + 1, 13)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 24 + 10, 0, 24 + 13], getcurpos())
+ call Ntest_setmouse(row + 1, 15)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 24 + 10, 2, 24 + 15], getcurpos())
+ call Ntest_setmouse(row + 1, 20)
+ call feedkeys("\<LeftMouse>", "xt")
+ call assert_equal([0, 1, 24 + 10, 7, 24 + 20], getcurpos())
bwipe!
let &mouse = save_mouse
diff --git a/src/nvim/testdir/test_visual.vim b/test/old/testdir/test_visual.vim
index 1e9629c2c4..5d70492451 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/test/old/testdir/test_visual.vim
@@ -485,14 +485,16 @@ endfunc
func Test_visual_block_put_invalid()
enew!
- behave mswin
+ " behave mswin
+ set selection=exclusive
norm yy
norm v)Ps/^/
" this was causing the column to become negative
silent norm ggv)P
bwipe!
- behave xterm
+ " behave xterm
+ set selection&
endfunc
" Visual modes (v V CTRL-V) followed by an operator; count; repeating
@@ -933,7 +935,7 @@ func Test_visual_block_mode()
endfunc
func Test_visual_force_motion_feedkeys()
- onoremap <expr> i- execute('let g:mode = mode(1)')
+ onoremap <expr> i- execute('let g:mode = mode(1)')->slice(0, 0)
call feedkeys('dvi-', 'x')
call assert_equal('nov', g:mode)
call feedkeys('di-', 'x')
@@ -1536,4 +1538,57 @@ func Test_switch_buffer_ends_visual_mode()
exe 'bwipe!' buf2
endfunc
+" Check fix for the heap-based buffer overflow bug found in the function
+" utfc_ptr2len and reported at
+" https://huntr.dev/bounties/ae933869-a1ec-402a-bbea-d51764c6618e
+func Test_heap_buffer_overflow()
+ enew
+ set updatecount=0
+
+ norm R0
+ split other
+ norm R000
+ exe "norm \<C-V>l"
+ ball
+ call assert_equal(getpos("."), getpos("v"))
+ call assert_equal('n', mode())
+ norm zW
+
+ %bwipe!
+ set updatecount&
+endfunc
+
+" Test Visual highlight with cursor at end of screen line and 'showbreak'
+func Test_visual_hl_with_showbreak()
+ CheckScreendump
+
+ let lines =<< trim END
+ setlocal showbreak=+
+ call setline(1, repeat('a', &columns + 10))
+ normal g$v4lo
+ END
+ call writefile(lines, 'XTest_visual_sbr', 'D')
+
+ let buf = RunVimInTerminal('-S XTest_visual_sbr', {'rows': 6, 'cols': 50})
+ call VerifyScreenDump(buf, 'Test_visual_hl_with_showbreak', {})
+
+ " clean up
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_Visual_r_CTRL_C()
+ new
+ " visual r_cmd
+ call setline(1, [' '])
+ call feedkeys("\<c-v>$r\<c-c>", 'tx')
+ call assert_equal([''], getline(1, 1))
+
+ " visual gr_cmd
+ call setline(1, [' '])
+ call feedkeys("\<c-v>$gr\<c-c>", 'tx')
+ call assert_equal([''], getline(1, 1))
+ bw!
+endfu
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_winbuf_close.vim b/test/old/testdir/test_winbuf_close.vim
index 26b4ba8778..26b4ba8778 100644
--- a/src/nvim/testdir/test_winbuf_close.vim
+++ b/test/old/testdir/test_winbuf_close.vim
diff --git a/src/nvim/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim
index c25b1f1157..152d1bacc7 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/test/old/testdir/test_window_cmd.vim
@@ -19,6 +19,24 @@ func Test_window_cmd_ls0_with_split()
set ls&vim
endfunc
+func Test_window_cmd_ls0_split_scrolling()
+ CheckRunVimInTerminal
+
+ let lines =<< trim END
+ set laststatus=0
+ call setline(1, range(1, 100))
+ normal! G
+ END
+ call writefile(lines, 'XTestLs0SplitScrolling', 'D')
+ let buf = RunVimInTerminal('-S XTestLs0SplitScrolling', #{rows: 10})
+
+ call term_sendkeys(buf, ":botright split\<CR>")
+ call WaitForAssert({-> assert_match('Bot$', term_getline(buf, 5))})
+ call assert_equal('100', term_getline(buf, 4))
+
+ call StopVimInTerminal(buf)
+endfunc
+
func Test_window_cmd_cmdwin_with_vsp()
let efmt = 'Expected 0 but got %d (in ls=%d, %s window)'
for v in range(0, 2)
@@ -119,10 +137,9 @@ endfunc
" Test the ":wincmd ^" and "<C-W>^" commands.
func Test_window_split_edit_alternate()
-
" Test for failure when the alternate buffer/file no longer exists.
edit Xfoo | %bw
- call assert_fails(':wincmd ^', 'E23')
+ call assert_fails(':wincmd ^', 'E23:')
" Test for the expected behavior when we have two named buffers.
edit Xfoo | edit Xbar
@@ -152,12 +169,11 @@ endfunc
" Test the ":[count]wincmd ^" and "[count]<C-W>^" commands.
func Test_window_split_edit_bufnr()
-
%bwipeout
let l:nr = bufnr('%') + 1
- call assert_fails(':execute "normal! ' . l:nr . '\<C-W>\<C-^>"', 'E92')
- call assert_fails(':' . l:nr . 'wincmd ^', 'E16')
- call assert_fails(':0wincmd ^', 'E16')
+ call assert_fails(':execute "normal! ' . l:nr . '\<C-W>\<C-^>"', 'E92:')
+ call assert_fails(':' . l:nr . 'wincmd ^', 'E16:')
+ call assert_fails(':0wincmd ^', 'E16:')
edit Xfoo | edit Xbar | edit Xbaz
let l:foo_nr = bufnr('Xfoo')
@@ -492,6 +508,8 @@ func Test_equalalways_on_close()
endfunc
func Test_win_screenpos()
+ CheckFeature quickfix
+
call assert_equal(1, winnr('$'))
split
vsplit
@@ -737,7 +755,7 @@ func Test_relative_cursor_position_in_one_line_window()
only!
bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
+ call assert_fails('call winrestview(v:_null_dict)', 'E1297:')
endfunc
func Test_relative_cursor_position_after_move_and_resize()
@@ -938,7 +956,7 @@ func Test_winrestview()
call assert_equal(view, winsaveview())
bwipe!
- call assert_fails('call winrestview(v:_null_dict)', 'E474:')
+ call assert_fails('call winrestview(v:_null_dict)', 'E1297:')
endfunc
func Test_win_splitmove()
@@ -969,7 +987,7 @@ func Test_win_splitmove()
call assert_equal(bufname(winbufnr(2)), 'b')
call assert_equal(bufname(winbufnr(3)), 'a')
call assert_equal(bufname(winbufnr(4)), 'd')
- call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E474:')
+ call assert_fails('call win_splitmove(winnr(), winnr("k"), v:_null_dict)', 'E1297:')
only | bd
call assert_fails('call win_splitmove(winnr(), 123)', 'E957:')
@@ -1525,6 +1543,52 @@ func Test_win_move_statusline()
%bwipe!
endfunc
+" Test for window allocation failure
+func Test_window_alloc_failure()
+ CheckFunction test_alloc_fail
+ %bw!
+
+ " test for creating a new window above current window
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('above new', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ " test for creating a new window below current window
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('below new', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ " test for popup window creation failure
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('call popup_create("Hello", {})', 'E342:')
+ call assert_equal([], popup_list())
+
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('split', 'E342:')
+ call assert_equal(1, winnr('$'))
+
+ edit Xfile1
+ edit Xfile2
+ call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ call assert_fails('sb Xfile1', 'E342:')
+ call assert_equal(1, winnr('$'))
+ call assert_equal('Xfile2', @%)
+ %bw!
+
+ " FIXME: The following test crashes Vim
+ " test for new tabpage creation failure
+ " call test_alloc_fail(GetAllocId('newwin_wvars'), 0, 0)
+ " call assert_fails('tabnew', 'E342:')
+ " call assert_equal(1, tabpagenr('$'))
+ " call assert_equal(1, winnr('$'))
+
+ " This test messes up the internal Vim window/frame information. So the
+ " Test_window_cmd_cmdwin_with_vsp() test fails after running this test.
+ " Open a new tab and close everything else to fix this issue.
+ tabnew
+ tabonly
+endfunc
+
func Test_win_equal_last_status()
let save_lines = &lines
set lines=20
@@ -1651,9 +1715,9 @@ func Test_splitkeep_options()
call assert_equal(1, line("w0"))
call assert_equal(curpos, getcurpos())
- " Scroll when cursor becomes invalid in insert mode
+ " Scroll when cursor becomes invalid in insert mode.
norm Lic
- call assert_equal(curpos, getcurpos())
+ call assert_equal(curpos, getcurpos(), 'run ' .. run)
" No scroll when topline not equal to 1
only | execute "norm gg5\<C-e>" | split | wincmd k
@@ -1672,7 +1736,7 @@ func Test_splitkeep_options()
" let &t_WS = save_WS
endfunc
-function Test_splitkeep_cmdwin_cursor_position()
+func Test_splitkeep_cmdwin_cursor_position()
set splitkeep=screen
call setline(1, range(&lines))
@@ -1697,13 +1761,24 @@ function Test_splitkeep_cmdwin_cursor_position()
%bwipeout!
set splitkeep&
-endfunction
+endfunc
-function Test_splitkeep_misc()
+func Test_splitkeep_misc()
set splitkeep=screen
- set splitbelow
call setline(1, range(1, &lines))
+ " Cursor is adjusted to start and end of buffer
+ norm M
+ wincmd s
+ resize 1
+ call assert_equal(1, line('.'))
+ wincmd j
+ norm GM
+ resize 1
+ call assert_equal(&lines, line('.'))
+ only!
+
+ set splitbelow
norm Gzz
let top = line('w0')
" No scroll when aucmd_win is opened
@@ -1732,7 +1807,34 @@ function Test_splitkeep_misc()
set splitkeep&
endfunc
-function Test_splitkeep_callback()
+func Test_splitkeep_cursor()
+ CheckScreendump
+ let lines =<< trim END
+ set splitkeep=screen
+ autocmd CursorMoved * wincmd p | wincmd p
+ call setline(1, range(1, 200))
+ func CursorEqualize()
+ call cursor(100, 1)
+ wincmd =
+ endfunc
+ wincmd s
+ call CursorEqualize()
+ END
+ call writefile(lines, 'XTestSplitkeepCallback', 'D')
+ let buf = RunVimInTerminal('-S XTestSplitkeepCallback', #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_splitkeep_cursor_1', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_splitkeep_cursor_2', {})
+
+ call term_sendkeys(buf, ":set scrolloff=0\<CR>G")
+ call VerifyScreenDump(buf, 'Test_splitkeep_cursor_3', {})
+
+ call StopVimInTerminal(buf)
+endfunc
+
+func Test_splitkeep_callback()
CheckScreendump
let lines =<< trim END
set splitkeep=screen
@@ -1765,7 +1867,7 @@ function Test_splitkeep_callback()
call StopVimInTerminal(buf)
endfunc
-function Test_splitkeep_fold()
+func Test_splitkeep_fold()
CheckScreendump
let lines =<< trim END
@@ -1795,9 +1897,9 @@ function Test_splitkeep_fold()
call VerifyScreenDump(buf, 'Test_splitkeep_fold_4', {})
call StopVimInTerminal(buf)
-endfunction
+endfunc
-function Test_splitkeep_status()
+func Test_splitkeep_status()
CheckScreendump
let lines =<< trim END
@@ -1815,9 +1917,26 @@ function Test_splitkeep_status()
call VerifyScreenDump(buf, 'Test_splitkeep_status_1', {})
call StopVimInTerminal(buf)
-endfunction
+endfunc
-function Test_new_help_window_on_error()
+" skipcol is not reset unnecessarily and is copied to new window
+func Test_splitkeep_skipcol()
+ CheckScreendump
+
+ let lines =<< trim END
+ set splitkeep=topline smoothscroll splitbelow scrolloff=0
+ call setline(1, 'with lots of text in one line '->repeat(6))
+ norm 2
+ wincmd s
+ END
+
+ call writefile(lines, 'XTestSplitkeepSkipcol', 'D')
+ let buf = RunVimInTerminal('-S XTestSplitkeepSkipcol', #{rows: 12, cols: 40})
+
+ call VerifyScreenDump(buf, 'Test_splitkeep_skipcol_1', {})
+endfunc
+
+func Test_new_help_window_on_error()
help change.txt
execute "normal! /CTRL-@\<CR>"
silent! execute "normal! \<C-W>]"
@@ -1827,7 +1946,26 @@ function Test_new_help_window_on_error()
call assert_equal(wincount, winnr('$'))
call assert_equal(expand("<cword>"), "'mod'")
-endfunction
+endfunc
+
+func Test_smoothscroll_in_zero_width_window()
+ let save_lines = &lines
+ let save_columns = &columns
+
+ winsize 0 24
+ set cpo+=n
+ exe "noremap 0 \<C-W>n\<C-W>L"
+ norm 000000
+ set number smoothscroll
+ exe "norm \<C-Y>"
+
+ only!
+ let &lines = save_lines
+ let &columns = save_columns
+ set cpo-=n
+ unmap 0
+ set nonumber nosmoothscroll
+endfunc
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_id.vim b/test/old/testdir/test_window_id.vim
index 396a49b55f..396a49b55f 100644
--- a/src/nvim/testdir/test_window_id.vim
+++ b/test/old/testdir/test_window_id.vim
diff --git a/src/nvim/testdir/test_windows_home.vim b/test/old/testdir/test_windows_home.vim
index 3c2db01444..3c2db01444 100644
--- a/src/nvim/testdir/test_windows_home.vim
+++ b/test/old/testdir/test_windows_home.vim
diff --git a/src/nvim/testdir/test_wnext.vim b/test/old/testdir/test_wnext.vim
index 3df61ceb78..3df61ceb78 100644
--- a/src/nvim/testdir/test_wnext.vim
+++ b/test/old/testdir/test_wnext.vim
diff --git a/src/nvim/testdir/test_wordcount.vim b/test/old/testdir/test_wordcount.vim
index 6a3d4109a8..6a3d4109a8 100644
--- a/src/nvim/testdir/test_wordcount.vim
+++ b/test/old/testdir/test_wordcount.vim
diff --git a/src/nvim/testdir/test_writefile.vim b/test/old/testdir/test_writefile.vim
index 6019cee193..1e0556c92d 100644
--- a/src/nvim/testdir/test_writefile.vim
+++ b/test/old/testdir/test_writefile.vim
@@ -924,19 +924,49 @@ endfunc
" Test for ':write ++bin' and ':write ++nobin'
func Test_write_binary_file()
" create a file without an eol/eof character
- call writefile(0z616161, 'Xfile1', 'b')
- new Xfile1
- write ++bin Xfile2
- write ++nobin Xfile3
- call assert_equal(0z616161, readblob('Xfile2'))
+ call writefile(0z616161, 'Xwbfile1', 'b')
+ new Xwbfile1
+ write ++bin Xwbfile2
+ write ++nobin Xwbfile3
+ call assert_equal(0z616161, readblob('Xwbfile2'))
if has('win32')
- call assert_equal(0z6161610D.0A, readblob('Xfile3'))
+ call assert_equal(0z6161610D.0A, readblob('Xwbfile3'))
else
- call assert_equal(0z6161610A, readblob('Xfile3'))
+ call assert_equal(0z6161610A, readblob('Xwbfile3'))
endif
- call delete('Xfile1')
- call delete('Xfile2')
- call delete('Xfile3')
+ call delete('Xwbfile1')
+ call delete('Xwbfile2')
+ call delete('Xwbfile3')
+endfunc
+
+func DoWriteDefer()
+ call writefile(['some text'], 'XdeferDelete', 'D')
+ call assert_equal(['some text'], readfile('XdeferDelete'))
+endfunc
+
+" def DefWriteDefer()
+" writefile(['some text'], 'XdefdeferDelete', 'D')
+" assert_equal(['some text'], readfile('XdefdeferDelete'))
+" enddef
+
+func Test_write_with_deferred_delete()
+ call DoWriteDefer()
+ call assert_equal('', glob('XdeferDelete'))
+ " call DefWriteDefer()
+ " call assert_equal('', glob('XdefdeferDelete'))
+endfunc
+
+func DoWriteFile()
+ call writefile(['text'], 'Xthefile', 'D')
+ cd ..
+endfunc
+
+func Test_write_defer_delete_chdir()
+ let dir = getcwd()
+ call DoWriteFile()
+ call assert_notequal(dir, getcwd())
+ call chdir(dir)
+ call assert_equal('', glob('Xthefile'))
endfunc
" Check that buffer is written before triggering QuitPre
@@ -960,4 +990,27 @@ func Test_wq_quitpre_autocommand()
call delete('Xsomefile')
endfunc
+func Test_write_with_xattr_support()
+ CheckLinux
+ CheckFeature xattr
+ CheckExecutable setfattr
+
+ let contents = ["file with xattrs", "line two"]
+ call writefile(contents, 'Xwattr.txt', 'D')
+ " write a couple of xattr
+ call system('setfattr -n user.cookie -v chocolate Xwattr.txt')
+ call system('setfattr -n user.frieda -v bar Xwattr.txt')
+ call system('setfattr -n user.empty Xwattr.txt')
+
+ set backupcopy=no writebackup& backup&
+ sp Xwattr.txt
+ w
+ $r! getfattr -d %
+ let expected = ['file with xattrs', 'line two', '# file: Xwattr.txt', 'user.cookie="chocolate"', 'user.empty=""', 'user.frieda="bar"', '']
+ call assert_equal(expected, getline(1,'$'))
+
+ set backupcopy&
+ bw!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/unix.vim b/test/old/testdir/unix.vim
index ce2beff7fe..ce2beff7fe 100644
--- a/src/nvim/testdir/unix.vim
+++ b/test/old/testdir/unix.vim
diff --git a/src/nvim/testdir/view_util.vim b/test/old/testdir/view_util.vim
index a4d0e56af9..a4d0e56af9 100644
--- a/src/nvim/testdir/view_util.vim
+++ b/test/old/testdir/view_util.vim
diff --git a/src/nvim/testdir/vim9.vim b/test/old/testdir/vim9.vim
index 3c0ff2b2dd..825f01c9d6 100644
--- a/src/nvim/testdir/vim9.vim
+++ b/test/old/testdir/vim9.vim
@@ -2,6 +2,14 @@
" Use a different file name for each run.
let s:sequence = 1
+func CheckDefFailure(lines, error, lnum = -3)
+ return
+endfunc
+
+func CheckDefExecFailure(lines, error, lnum = -3)
+ return
+endfunc
+
func CheckScriptFailure(lines, error, lnum = -3)
if get(a:lines, 0, '') ==# 'vim9script'
return
@@ -34,6 +42,18 @@ func CheckScriptSuccess(lines)
endtry
endfunc
+func CheckDefAndScriptSuccess(lines)
+ return
+endfunc
+
+func CheckDefAndScriptFailure(lines, error, lnum = -3)
+ return
+endfunc
+
+func CheckDefExecAndScriptFailure(lines, error, lnum = -3)
+ return
+endfunc
+
" Check that "lines" inside a legacy function has no error.
func CheckLegacySuccess(lines)
let cwd = getcwd()
@@ -68,7 +88,7 @@ endfunc
" Execute "lines" in a legacy function, translated as in
" CheckLegacyAndVim9Success()
func CheckTransLegacySuccess(lines)
- let legacylines = a:lines->deepcopy()->map({_, v ->
+ let legacylines = a:lines->mapnew({_, v ->
\ v->substitute('\<VAR\>', 'let', 'g')
\ ->substitute('\<LET\>', 'let', 'g')
\ ->substitute('\<LSTART\>', '{', 'g')
@@ -81,6 +101,14 @@ func CheckTransLegacySuccess(lines)
call CheckLegacySuccess(legacylines)
endfunc
+func CheckTransDefSuccess(lines)
+ return
+endfunc
+
+func CheckTransVim9Success(lines)
+ return
+endfunc
+
" Execute "lines" in a legacy function
" Use 'VAR' for a declaration.
" Use 'LET' for an assignment
@@ -98,12 +126,12 @@ endfunc
" Use ' #"' for a comment
func CheckLegacyAndVim9Failure(lines, error)
if type(a:error) == type('string')
- let legacyError = error
+ let legacyError = a:error
else
- let legacyError = error[0]
+ let legacyError = a:error[0]
endif
- let legacylines = a:lines->deepcopy()->map({_, v ->
+ let legacylines = a:lines->mapnew({_, v ->
\ v->substitute('\<VAR\>', 'let', 'g')
\ ->substitute('\<LET\>', 'let', 'g')
\ ->substitute('#"', ' "', 'g')
diff --git a/test/symbolic/klee/nvim/charset.c b/test/symbolic/klee/nvim/charset.c
deleted file mode 100644
index fd1c5ee4b9..0000000000
--- a/test/symbolic/klee/nvim/charset.c
+++ /dev/null
@@ -1,172 +0,0 @@
-#include <stdbool.h>
-
-#include "nvim/ascii.h"
-#include "nvim/macros.h"
-#include "nvim/charset.h"
-#include "nvim/eval/typval.h"
-#include "nvim/vim.h"
-
-int hex2nr(int c)
-{
- if ((c >= 'a') && (c <= 'f')) {
- return c - 'a' + 10;
- }
-
- if ((c >= 'A') && (c <= 'F')) {
- return c - 'A' + 10;
- }
- return c - '0';
-}
-
-void vim_str2nr(const char_u *const start, int *const prep, int *const len,
- const int what, varnumber_T *const nptr,
- uvarnumber_T *const unptr, const int maxlen)
-{
- const char *ptr = (const char *)start;
-#define STRING_ENDED(ptr) \
- (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen))
- int pre = 0; // default is decimal
- const bool negative = (ptr[0] == '-');
- uvarnumber_T un = 0;
-
- if (negative) {
- ptr++;
- }
-
- if (what & STR2NR_FORCE) {
- // When forcing main consideration is skipping the prefix. Octal and decimal
- // numbers have no prefixes to skip. pre is not set.
- switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) {
- case STR2NR_HEX: {
- if (!STRING_ENDED(ptr + 2)
- && ptr[0] == '0'
- && (ptr[1] == 'x' || ptr[1] == 'X')
- && ascii_isxdigit(ptr[2])) {
- ptr += 2;
- }
- goto vim_str2nr_hex;
- }
- case STR2NR_BIN: {
- if (!STRING_ENDED(ptr + 2)
- && ptr[0] == '0'
- && (ptr[1] == 'b' || ptr[1] == 'B')
- && ascii_isbdigit(ptr[2])) {
- ptr += 2;
- }
- goto vim_str2nr_bin;
- }
- case STR2NR_OCT: {
- goto vim_str2nr_oct;
- }
- case 0: {
- goto vim_str2nr_dec;
- }
- default: {
- abort();
- }
- }
- } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN))
- && !STRING_ENDED(ptr + 1)
- && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') {
- pre = ptr[1];
- // Detect hexadecimal: 0x or 0X followed by hex digit
- if ((what & STR2NR_HEX)
- && !STRING_ENDED(ptr + 2)
- && (pre == 'X' || pre == 'x')
- && ascii_isxdigit(ptr[2])) {
- ptr += 2;
- goto vim_str2nr_hex;
- }
- // Detect binary: 0b or 0B followed by 0 or 1
- if ((what & STR2NR_BIN)
- && !STRING_ENDED(ptr + 2)
- && (pre == 'B' || pre == 'b')
- && ascii_isbdigit(ptr[2])) {
- ptr += 2;
- goto vim_str2nr_bin;
- }
- // Detect octal number: zero followed by octal digits without '8' or '9'
- pre = 0;
- if (!(what & STR2NR_OCT)) {
- goto vim_str2nr_dec;
- }
- for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) {
- if (ptr[i] > '7') {
- goto vim_str2nr_dec;
- }
- }
- pre = '0';
- goto vim_str2nr_oct;
- } else {
- goto vim_str2nr_dec;
- }
-
- // Do the string-to-numeric conversion "manually" to avoid sscanf quirks.
- abort(); // Should’ve used goto earlier.
-#define PARSE_NUMBER(base, cond, conv) \
- do { \
- while (!STRING_ENDED(ptr) && (cond)) { \
- /* avoid ubsan error for overflow */ \
- if (un < UVARNUMBER_MAX / base) { \
- un = base * un + (uvarnumber_T)(conv); \
- } else { \
- un = UVARNUMBER_MAX; \
- } \
- ptr++; \
- } \
- } while (0)
- switch (pre) {
- case 'b':
- case 'B': {
-vim_str2nr_bin:
- PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0'));
- break;
- }
- case '0': {
-vim_str2nr_oct:
- PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0'));
- break;
- }
- case 0: {
-vim_str2nr_dec:
- PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0'));
- break;
- }
- case 'x':
- case 'X': {
-vim_str2nr_hex:
- PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr)));
- break;
- }
- }
-#undef PARSE_NUMBER
-
- if (prep != NULL) {
- *prep = pre;
- }
-
- if (len != NULL) {
- *len = (int)(ptr - (const char *)start);
- }
-
- if (nptr != NULL) {
- if (negative) { // account for leading '-' for decimal numbers
- // avoid ubsan error for overflow
- if (un > VARNUMBER_MAX) {
- *nptr = VARNUMBER_MIN;
- } else {
- *nptr = -(varnumber_T)un;
- }
- } else {
- if (un > VARNUMBER_MAX) {
- un = VARNUMBER_MAX;
- }
- *nptr = (varnumber_T)un;
- }
- }
-
- if (unptr != NULL) {
- *unptr = un;
- }
-#undef STRING_ENDED
-}
diff --git a/test/symbolic/klee/nvim/garray.c b/test/symbolic/klee/nvim/garray.c
deleted file mode 100644
index 260870c3c2..0000000000
--- a/test/symbolic/klee/nvim/garray.c
+++ /dev/null
@@ -1,195 +0,0 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
-/// @file garray.c
-///
-/// Functions for handling growing arrays.
-
-#include <string.h>
-#include <inttypes.h>
-
-#include "nvim/vim.h"
-#include "nvim/ascii.h"
-#include "nvim/log.h"
-#include "nvim/memory.h"
-#include "nvim/path.h"
-#include "nvim/garray.h"
-#include "nvim/strings.h"
-
-// #include "nvim/globals.h"
-#include "nvim/memline.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "garray.c.generated.h"
-#endif
-
-/// Clear an allocated growing array.
-void ga_clear(garray_T *gap)
-{
- xfree(gap->ga_data);
-
- // Initialize growing array without resetting itemsize or growsize
- gap->ga_data = NULL;
- gap->ga_maxlen = 0;
- gap->ga_len = 0;
-}
-
-/// Clear a growing array that contains a list of strings.
-///
-/// @param gap
-void ga_clear_strings(garray_T *gap)
-{
- GA_DEEP_CLEAR_PTR(gap);
-}
-
-/// Initialize a growing array.
-///
-/// @param gap
-/// @param itemsize
-/// @param growsize
-void ga_init(garray_T *gap, int itemsize, int growsize)
-{
- gap->ga_data = NULL;
- gap->ga_maxlen = 0;
- gap->ga_len = 0;
- gap->ga_itemsize = itemsize;
- ga_set_growsize(gap, growsize);
-}
-
-/// A setter for the growsize that guarantees it will be at least 1.
-///
-/// @param gap
-/// @param growsize
-void ga_set_growsize(garray_T *gap, int growsize)
-{
- if (growsize < 1) {
- WLOG("trying to set an invalid ga_growsize: %d", growsize);
- gap->ga_growsize = 1;
- } else {
- gap->ga_growsize = growsize;
- }
-}
-
-/// Make room in growing array "gap" for at least "n" items.
-///
-/// @param gap
-/// @param n
-void ga_grow(garray_T *gap, int n)
-{
- if (gap->ga_maxlen - gap->ga_len >= n) {
- // the garray still has enough space, do nothing
- return;
- }
-
- if (gap->ga_growsize < 1) {
- WLOG("ga_growsize(%d) is less than 1", gap->ga_growsize);
- }
-
- // the garray grows by at least growsize
- if (n < gap->ga_growsize) {
- n = gap->ga_growsize;
- }
- int new_maxlen = gap->ga_len + n;
-
- size_t new_size = (size_t)(gap->ga_itemsize * new_maxlen);
- size_t old_size = (size_t)(gap->ga_itemsize * gap->ga_maxlen);
-
- // reallocate and clear the new memory
- char *pp = xrealloc(gap->ga_data, new_size);
- memset(pp + old_size, 0, new_size - old_size);
-
- gap->ga_maxlen = new_maxlen;
- gap->ga_data = pp;
-}
-
-/// For a growing array that contains a list of strings: concatenate all the
-/// strings with sep as separator.
-///
-/// @param gap
-/// @param sep
-///
-/// @returns the concatenated strings
-char_u *ga_concat_strings_sep(const garray_T *gap, const char *sep)
- FUNC_ATTR_NONNULL_RET
-{
- const size_t nelem = (size_t) gap->ga_len;
- const char **strings = gap->ga_data;
-
- if (nelem == 0) {
- return (char_u *) xstrdup("");
- }
-
- size_t len = 0;
- for (size_t i = 0; i < nelem; i++) {
- len += strlen(strings[i]);
- }
-
- // add some space for the (num - 1) separators
- len += (nelem - 1) * strlen(sep);
- char *const ret = xmallocz(len);
-
- char *s = ret;
- for (size_t i = 0; i < nelem - 1; i++) {
- s = xstpcpy(s, strings[i]);
- s = xstpcpy(s, sep);
- }
- strcpy(s, strings[nelem - 1]);
-
- return (char_u *) ret;
-}
-
-/// For a growing array that contains a list of strings: concatenate all the
-/// strings with a separating comma.
-///
-/// @param gap
-///
-/// @returns the concatenated strings
-char_u* ga_concat_strings(const garray_T *gap) FUNC_ATTR_NONNULL_RET
-{
- return ga_concat_strings_sep(gap, ",");
-}
-
-/// Concatenate a string to a growarray which contains characters.
-/// When "s" is NULL does not do anything.
-///
-/// WARNING:
-/// - Does NOT copy the NUL at the end!
-/// - The parameter may not overlap with the growing array
-///
-/// @param gap
-/// @param s
-void ga_concat(garray_T *gap, const char_u *restrict s)
-{
- if (s == NULL) {
- return;
- }
-
- ga_concat_len(gap, (const char *restrict) s, strlen((char *) s));
-}
-
-/// Concatenate a string to a growarray which contains characters
-///
-/// @param[out] gap Growarray to modify.
-/// @param[in] s String to concatenate.
-/// @param[in] len String length.
-void ga_concat_len(garray_T *const gap, const char *restrict s,
- const size_t len)
- FUNC_ATTR_NONNULL_ALL
-{
- if (len) {
- ga_grow(gap, (int) len);
- char *data = gap->ga_data;
- memcpy(data + gap->ga_len, s, len);
- gap->ga_len += (int) len;
- }
-}
-
-/// Append one byte to a growarray which contains bytes.
-///
-/// @param gap
-/// @param c
-void ga_append(garray_T *gap, char c)
-{
- GA_APPEND(char, gap, c);
-}
-
diff --git a/test/symbolic/klee/nvim/gettext.c b/test/symbolic/klee/nvim/gettext.c
deleted file mode 100644
index b9cc98d770..0000000000
--- a/test/symbolic/klee/nvim/gettext.c
+++ /dev/null
@@ -1,4 +0,0 @@
-char *gettext(const char *s)
-{
- return (char *)s;
-}
diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c
deleted file mode 100644
index 1f7f0e0911..0000000000
--- a/test/symbolic/klee/nvim/keymap.c
+++ /dev/null
@@ -1,559 +0,0 @@
-#include <stdbool.h>
-
-#include "nvim/types.h"
-#include "nvim/keycodes.h"
-#include "nvim/ascii.h"
-#include "nvim/eval/typval.h"
-
-#define MOD_KEYS_ENTRY_SIZE 5
-
-static char_u modifier_keys_table[] =
-{
- MOD_MASK_SHIFT, '&', '9', '@', '1',
- MOD_MASK_SHIFT, '&', '0', '@', '2',
- MOD_MASK_SHIFT, '*', '1', '@', '4',
- MOD_MASK_SHIFT, '*', '2', '@', '5',
- MOD_MASK_SHIFT, '*', '3', '@', '6',
- MOD_MASK_SHIFT, '*', '4', 'k', 'D',
- MOD_MASK_SHIFT, '*', '5', 'k', 'L',
- MOD_MASK_SHIFT, '*', '7', '@', '7',
- MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_END, '@', '7',
- MOD_MASK_SHIFT, '*', '9', '@', '9',
- MOD_MASK_SHIFT, '*', '0', '@', '0',
- MOD_MASK_SHIFT, '#', '1', '%', '1',
- MOD_MASK_SHIFT, '#', '2', 'k', 'h',
- MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_HOME, 'k', 'h',
- MOD_MASK_SHIFT, '#', '3', 'k', 'I',
- MOD_MASK_SHIFT, '#', '4', 'k', 'l',
- MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_LEFT, 'k', 'l',
- MOD_MASK_SHIFT, '%', 'a', '%', '3',
- MOD_MASK_SHIFT, '%', 'b', '%', '4',
- MOD_MASK_SHIFT, '%', 'c', '%', '5',
- MOD_MASK_SHIFT, '%', 'd', '%', '7',
- MOD_MASK_SHIFT, '%', 'e', '%', '8',
- MOD_MASK_SHIFT, '%', 'f', '%', '9',
- MOD_MASK_SHIFT, '%', 'g', '%', '0',
- MOD_MASK_SHIFT, '%', 'h', '&', '3',
- MOD_MASK_SHIFT, '%', 'i', 'k', 'r',
- MOD_MASK_CTRL, KS_EXTRA, (int)KE_C_RIGHT, 'k', 'r',
- MOD_MASK_SHIFT, '%', 'j', '&', '5',
- MOD_MASK_SHIFT, '!', '1', '&', '6',
- MOD_MASK_SHIFT, '!', '2', '&', '7',
- MOD_MASK_SHIFT, '!', '3', '&', '8',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_UP, 'k', 'u',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_DOWN, 'k', 'd',
-
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF1, KS_EXTRA, (int)KE_XF1,
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF2, KS_EXTRA, (int)KE_XF2,
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF3, KS_EXTRA, (int)KE_XF3,
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_XF4, KS_EXTRA, (int)KE_XF4,
-
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F1, 'k', '1',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F2, 'k', '2',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F3, 'k', '3',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F4, 'k', '4',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F5, 'k', '5',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F6, 'k', '6',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F7, 'k', '7',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F8, 'k', '8',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F9, 'k', '9',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F10, 'k', ';',
-
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F11, 'F', '1',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F12, 'F', '2',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F13, 'F', '3',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F14, 'F', '4',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F15, 'F', '5',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F16, 'F', '6',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F17, 'F', '7',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F18, 'F', '8',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F19, 'F', '9',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F20, 'F', 'A',
-
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F21, 'F', 'B',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F22, 'F', 'C',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F23, 'F', 'D',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F24, 'F', 'E',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F25, 'F', 'F',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F26, 'F', 'G',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F27, 'F', 'H',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F28, 'F', 'I',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F29, 'F', 'J',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F30, 'F', 'K',
-
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F31, 'F', 'L',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F32, 'F', 'M',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F33, 'F', 'N',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F34, 'F', 'O',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F35, 'F', 'P',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F36, 'F', 'Q',
- MOD_MASK_SHIFT, KS_EXTRA, (int)KE_S_F37, 'F', 'R',
-
- MOD_MASK_SHIFT, 'k', 'B', KS_EXTRA, (int)KE_TAB,
-
- NUL
-};
-
-int simplify_key(const int key, int *modifiers)
-{
- if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) {
- // TAB is a special case.
- if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) {
- *modifiers &= ~MOD_MASK_SHIFT;
- return K_S_TAB;
- }
- const int key0 = KEY2TERMCAP0(key);
- const int key1 = KEY2TERMCAP1(key);
- for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) {
- if (key0 == modifier_keys_table[i + 3]
- && key1 == modifier_keys_table[i + 4]
- && (*modifiers & modifier_keys_table[i])) {
- *modifiers &= ~modifier_keys_table[i];
- return TERMCAP2KEY(modifier_keys_table[i + 1],
- modifier_keys_table[i + 2]);
- }
- }
- }
- return key;
-}
-
-int handle_x_keys(const int key)
- FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
-{
- switch (key) {
- case K_XUP: return K_UP;
- case K_XDOWN: return K_DOWN;
- case K_XLEFT: return K_LEFT;
- case K_XRIGHT: return K_RIGHT;
- case K_XHOME: return K_HOME;
- case K_ZHOME: return K_HOME;
- case K_XEND: return K_END;
- case K_ZEND: return K_END;
- case K_XF1: return K_F1;
- case K_XF2: return K_F2;
- case K_XF3: return K_F3;
- case K_XF4: return K_F4;
- case K_S_XF1: return K_S_F1;
- case K_S_XF2: return K_S_F2;
- case K_S_XF3: return K_S_F3;
- case K_S_XF4: return K_S_F4;
- }
- return key;
-}
-
-static const struct key_name_entry {
- int key; // Special key code or ascii value
- const char *name; // Name of key
-} key_names_table[] = {
- { ' ', "Space" },
- { TAB, "Tab" },
- { K_TAB, "Tab" },
- { NL, "NL" },
- { NL, "NewLine" }, // Alternative name
- { NL, "LineFeed" }, // Alternative name
- { NL, "LF" }, // Alternative name
- { CAR, "CR" },
- { CAR, "Return" }, // Alternative name
- { CAR, "Enter" }, // Alternative name
- { K_BS, "BS" },
- { K_BS, "BackSpace" }, // Alternative name
- { ESC, "Esc" },
- { CSI, "CSI" },
- { K_CSI, "xCSI" },
- { '|', "Bar" },
- { '\\', "Bslash" },
- { K_DEL, "Del" },
- { K_DEL, "Delete" }, // Alternative name
- { K_KDEL, "kDel" },
- { K_KDEL, "KPPeriod" }, // termkey KPPeriod value
- { K_UP, "Up" },
- { K_DOWN, "Down" },
- { K_LEFT, "Left" },
- { K_RIGHT, "Right" },
- { K_XUP, "xUp" },
- { K_XDOWN, "xDown" },
- { K_XLEFT, "xLeft" },
- { K_XRIGHT, "xRight" },
- { K_KUP, "kUp" },
- { K_KUP, "KP8" },
- { K_KDOWN, "kDown" },
- { K_KDOWN, "KP2" },
- { K_KLEFT, "kLeft" },
- { K_KLEFT, "KP4" },
- { K_KRIGHT, "kRight" },
- { K_KRIGHT, "KP6" },
-
- { K_F1, "F1" },
- { K_F2, "F2" },
- { K_F3, "F3" },
- { K_F4, "F4" },
- { K_F5, "F5" },
- { K_F6, "F6" },
- { K_F7, "F7" },
- { K_F8, "F8" },
- { K_F9, "F9" },
- { K_F10, "F10" },
-
- { K_F11, "F11" },
- { K_F12, "F12" },
- { K_F13, "F13" },
- { K_F14, "F14" },
- { K_F15, "F15" },
- { K_F16, "F16" },
- { K_F17, "F17" },
- { K_F18, "F18" },
- { K_F19, "F19" },
- { K_F20, "F20" },
-
- { K_F21, "F21" },
- { K_F22, "F22" },
- { K_F23, "F23" },
- { K_F24, "F24" },
- { K_F25, "F25" },
- { K_F26, "F26" },
- { K_F27, "F27" },
- { K_F28, "F28" },
- { K_F29, "F29" },
- { K_F30, "F30" },
-
- { K_F31, "F31" },
- { K_F32, "F32" },
- { K_F33, "F33" },
- { K_F34, "F34" },
- { K_F35, "F35" },
- { K_F36, "F36" },
- { K_F37, "F37" },
-
- { K_XF1, "xF1" },
- { K_XF2, "xF2" },
- { K_XF3, "xF3" },
- { K_XF4, "xF4" },
-
- { K_HELP, "Help" },
- { K_UNDO, "Undo" },
- { K_INS, "Insert" },
- { K_INS, "Ins" }, // Alternative name
- { K_KINS, "kInsert" },
- { K_KINS, "KP0" },
- { K_HOME, "Home" },
- { K_KHOME, "kHome" },
- { K_KHOME, "KP7" },
- { K_XHOME, "xHome" },
- { K_ZHOME, "zHome" },
- { K_END, "End" },
- { K_KEND, "kEnd" },
- { K_XEND, "xEnd" },
- { K_ZEND, "zEnd" },
- { K_PAGEUP, "PageUp" },
- { K_PAGEDOWN, "PageDown" },
- { K_KPAGEUP, "kPageUp" },
- { K_KPAGEUP, "KP9" },
- { K_KPAGEDOWN, "kPageDown" },
- { K_KPAGEDOWN, "KP3" },
- { K_KORIGIN, "kOrigin" },
- { K_KORIGIN, "KP5" },
-
- { K_KPLUS, "kPlus" },
- { K_KPLUS, "KPPlus" },
- { K_KMINUS, "kMinus" },
- { K_KMINUS, "KPMinus" },
- { K_KDIVIDE, "kDivide" },
- { K_KDIVIDE, "KPDiv" },
- { K_KMULTIPLY, "kMultiply" },
- { K_KMULTIPLY, "KPMult" },
- { K_KENTER, "kEnter" },
- { K_KENTER, "KPEnter" },
- { K_KPOINT, "kPoint" },
-
- { K_K0, "k0" },
- { K_K1, "k1" },
- { K_K2, "k2" },
- { K_K3, "k3" },
- { K_K4, "k4" },
- { K_K5, "k5" },
- { K_K6, "k6" },
- { K_K7, "k7" },
- { K_K8, "k8" },
- { K_K9, "k9" },
-
- { '<', "lt" },
-
- { K_MOUSE, "Mouse" },
- { K_LEFTMOUSE, "LeftMouse" },
- { K_LEFTMOUSE_NM, "LeftMouseNM" },
- { K_LEFTDRAG, "LeftDrag" },
- { K_LEFTRELEASE, "LeftRelease" },
- { K_LEFTRELEASE_NM, "LeftReleaseNM" },
- { K_MIDDLEMOUSE, "MiddleMouse" },
- { K_MIDDLEDRAG, "MiddleDrag" },
- { K_MIDDLERELEASE, "MiddleRelease" },
- { K_RIGHTMOUSE, "RightMouse" },
- { K_RIGHTDRAG, "RightDrag" },
- { K_RIGHTRELEASE, "RightRelease" },
- { K_MOUSEDOWN, "ScrollWheelUp" },
- { K_MOUSEUP, "ScrollWheelDown" },
- { K_MOUSELEFT, "ScrollWheelRight" },
- { K_MOUSERIGHT, "ScrollWheelLeft" },
- { K_MOUSEDOWN, "MouseDown" }, // OBSOLETE: Use
- { K_MOUSEUP, "MouseUp" }, // ScrollWheelXXX instead
- { K_X1MOUSE, "X1Mouse" },
- { K_X1DRAG, "X1Drag" },
- { K_X1RELEASE, "X1Release" },
- { K_X2MOUSE, "X2Mouse" },
- { K_X2DRAG, "X2Drag" },
- { K_X2RELEASE, "X2Release" },
- { K_DROP, "Drop" },
- { K_ZERO, "Nul" },
- { K_SNR, "SNR" },
- { K_PLUG, "Plug" },
- { K_PASTE, "Paste" },
- { 0, NULL }
-};
-
-int get_special_key_code(const char_u *name)
-{
- for (int i = 0; key_names_table[i].name != NULL; i++) {
- const char *const table_name = key_names_table[i].name;
- int j;
- for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) {
- if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) {
- break;
- }
- }
- if (!ascii_isident(name[j]) && table_name[j] == NUL) {
- return key_names_table[i].key;
- }
- }
-
- return 0;
-}
-
-
-static const struct modmasktable {
- short mod_mask; ///< Bit-mask for particular key modifier.
- short mod_flag; ///< Bit(s) for particular key modifier.
- char_u name; ///< Single letter name of modifier.
-} mod_mask_table[] = {
- {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'},
- {MOD_MASK_META, MOD_MASK_META, (char_u)'T'},
- {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'},
- {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'},
- {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'},
- {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'},
- {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'},
- {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'},
- // 'A' must be the last one
- {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'},
- {0, 0, NUL}
-};
-
-int name_to_mod_mask(int c)
-{
- c = TOUPPER_ASC(c);
- for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) {
- if (c == mod_mask_table[i].name) {
- return mod_mask_table[i].mod_flag;
- }
- }
- return 0;
-}
-
-static int extract_modifiers(int key, int *modp)
-{
- int modifiers = *modp;
-
- if (!(modifiers & MOD_MASK_CMD)) { // Command-key is special
- if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) {
- key = TOUPPER_ASC(key);
- modifiers &= ~MOD_MASK_SHIFT;
- }
- }
- if ((modifiers & MOD_MASK_CTRL)
- && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
- key = Ctrl_chr(key);
- modifiers &= ~MOD_MASK_CTRL;
- if (key == 0) { // <C-@> is <Nul>
- key = K_ZERO;
- }
- }
-
- *modp = modifiers;
- return key;
-}
-
-int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
- const bool keycode, const bool keep_x_key,
- const bool in_string)
-{
- const char_u *last_dash;
- const char_u *end_of_name;
- const char_u *src;
- const char_u *bp;
- const char_u *const end = *srcp + src_len - 1;
- int modifiers;
- int bit;
- int key;
- uvarnumber_T n;
- int l;
-
- if (src_len == 0) {
- return 0;
- }
-
- src = *srcp;
- if (src[0] != '<') {
- return 0;
- }
-
- // Find end of modifier list
- last_dash = src;
- for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) {
- if (*bp == '-') {
- last_dash = bp;
- if (bp + 1 <= end) {
- l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1);
- // Anything accepted, like <C-?>.
- // <C-"> or <M-"> are not special in strings as " is
- // the string delimiter. With a backslash it works: <M-\">
- if (end - bp > l && !(in_string && bp[1] == '"') && bp[2] == '>') {
- bp += l;
- } else if (end - bp > 2 && in_string && bp[1] == '\\'
- && bp[2] == '"' && bp[3] == '>') {
- bp += 2;
- }
- }
- }
- if (end - bp > 3 && bp[0] == 't' && bp[1] == '_') {
- bp += 3; // skip t_xx, xx may be '-' or '>'
- } else if (end - bp > 4 && STRNICMP(bp, "char-", 5) == 0) {
- vim_str2nr(bp + 5, NULL, &l, STR2NR_ALL, NULL, NULL, 0);
- bp += l + 5;
- break;
- }
- }
-
- if (bp <= end && *bp == '>') { // found matching '>'
- end_of_name = bp + 1;
-
- /* Which modifiers are given? */
- modifiers = 0x0;
- for (bp = src + 1; bp < last_dash; bp++) {
- if (*bp != '-') {
- bit = name_to_mod_mask(*bp);
- if (bit == 0x0) {
- break; // Illegal modifier name
- }
- modifiers |= bit;
- }
- }
-
- // Legal modifier name.
- if (bp >= last_dash) {
- if (STRNICMP(last_dash + 1, "char-", 5) == 0
- && ascii_isdigit(last_dash[6])) {
- // <Char-123> or <Char-033> or <Char-0x33>
- vim_str2nr(last_dash + 6, NULL, NULL, STR2NR_ALL, NULL, &n, 0);
- key = (int)n;
- } else {
- int off = 1;
-
- // Modifier with single letter, or special key name.
- if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') {
- off = 2;
- }
- l = mb_ptr2len(last_dash + 1);
- if (modifiers != 0 && last_dash[l + 1] == '>') {
- key = PTR2CHAR(last_dash + off);
- } else {
- key = get_special_key_code(last_dash + off);
- if (!keep_x_key) {
- key = handle_x_keys(key);
- }
- }
- }
-
- // get_special_key_code() may return NUL for invalid
- // special key name.
- if (key != NUL) {
- // Only use a modifier when there is no special key code that
- // includes the modifier.
- key = simplify_key(key, &modifiers);
-
- if (!keycode) {
- // don't want keycode, use single byte code
- if (key == K_BS) {
- key = BS;
- } else if (key == K_DEL || key == K_KDEL) {
- key = DEL;
- }
- }
-
- // Normal Key with modifier:
- // Try to make a single byte code (except for Alt/Meta modifiers).
- if (!IS_SPECIAL(key)) {
- key = extract_modifiers(key, &modifiers);
- }
-
- *modp = modifiers;
- *srcp = end_of_name;
- return key;
- }
- }
- }
- return 0;
-}
-
-char_u *add_char2buf(int c, char_u *s)
-{
- char_u temp[MB_MAXBYTES + 1];
- const int len = utf_char2bytes(c, temp);
- for (int i = 0; i < len; ++i) {
- c = temp[i];
- // Need to escape K_SPECIAL and CSI like in the typeahead buffer.
- if (c == K_SPECIAL) {
- *s++ = K_SPECIAL;
- *s++ = KS_SPECIAL;
- *s++ = KE_FILLER;
- } else {
- *s++ = c;
- }
- }
- return s;
-}
-
-unsigned int trans_special(const char_u **srcp, const size_t src_len,
- char_u *const dst, const bool keycode,
- const bool in_string)
-{
- int modifiers = 0;
- int key;
- unsigned int dlen = 0;
-
- key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string);
- if (key == 0) {
- return 0;
- }
-
- // Put the appropriate modifier in a string.
- if (modifiers != 0) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = KS_MODIFIER;
- dst[dlen++] = (char_u)modifiers;
- }
-
- if (IS_SPECIAL(key)) {
- dst[dlen++] = K_SPECIAL;
- dst[dlen++] = (char_u)KEY2TERMCAP0(key);
- dst[dlen++] = KEY2TERMCAP1(key);
- } else if (!keycode) {
- dlen += (unsigned int)(*mb_char2bytes)(key, dst + dlen);
- } else if (keycode) {
- char_u *after = add_char2buf(key, dst + dlen);
- assert(after >= dst && (uintmax_t)(after - dst) <= UINT_MAX);
- dlen = (unsigned int)(after - dst);
- } else {
- dst[dlen++] = (char_u)key;
- }
-
- return dlen;
-}
diff --git a/test/symbolic/klee/nvim/mbyte.c b/test/symbolic/klee/nvim/mbyte.c
deleted file mode 100644
index f98a531206..0000000000
--- a/test/symbolic/klee/nvim/mbyte.c
+++ /dev/null
@@ -1,266 +0,0 @@
-#include <stddef.h>
-#include <inttypes.h>
-#include <assert.h>
-#include <stdbool.h>
-
-#include "nvim/types.h"
-#include "nvim/mbyte.h"
-#include "nvim/ascii.h"
-
-const uint8_t utf8len_tab_zero[] = {
- //1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
- 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,1,1,1,1,1,1,1, // 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,1,1,1,1,1,1,1, // 2
- 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,1,1,1,1,1,1,1, // 4
- 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,1,1,1,1,1,1,1, // 6
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 8
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // A
- 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // C
- 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // E
-};
-
-const uint8_t utf8len_tab[] = {
- // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 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, 1, 1, 1, 1, 1, 1, 1, 1, // 2?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A?
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B?
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C?
- 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D?
- 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E?
- 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F?
-};
-
-int utf_ptr2char(const char_u *const p)
-{
- if (p[0] < 0x80) { // Be quick for ASCII.
- return p[0];
- }
-
- const uint8_t len = utf8len_tab_zero[p[0]];
- if (len > 1 && (p[1] & 0xc0) == 0x80) {
- if (len == 2) {
- return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f);
- }
- if ((p[2] & 0xc0) == 0x80) {
- if (len == 3) {
- return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6)
- + (p[2] & 0x3f));
- }
- if ((p[3] & 0xc0) == 0x80) {
- if (len == 4) {
- return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12)
- + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f));
- }
- if ((p[4] & 0xc0) == 0x80) {
- if (len == 5) {
- return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18)
- + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6)
- + (p[4] & 0x3f));
- }
- if ((p[5] & 0xc0) == 0x80 && len == 6) {
- return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24)
- + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12)
- + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f));
- }
- }
- }
- }
- }
- // Illegal value: just return the first byte.
- return p[0];
-}
-
-bool utf_composinglike(const char_u *p1, const char_u *p2)
-{
- return false;
-}
-
-char_u *string_convert(const vimconv_T *conv, char_u *data, size_t *size)
-{
- return NULL;
-}
-
-int utf_ptr2len_len(const char_u *p, int size)
-{
- int len;
- int i;
- int m;
-
- len = utf8len_tab[*p];
- if (len == 1)
- return 1; /* NUL, ascii or illegal lead byte */
- if (len > size)
- m = size; /* incomplete byte sequence. */
- else
- m = len;
- for (i = 1; i < m; ++i)
- if ((p[i] & 0xc0) != 0x80)
- return 1;
- return len;
-}
-
-int utfc_ptr2len_len(const char_u *p, int size)
-{
- int len;
- int prevlen;
-
- if (size < 1 || *p == NUL)
- return 0;
- if (p[0] < 0x80 && (size == 1 || p[1] < 0x80)) /* be quick for ASCII */
- return 1;
-
- /* Skip over first UTF-8 char, stopping at a NUL byte. */
- len = utf_ptr2len_len(p, size);
-
- /* Check for illegal byte and incomplete byte sequence. */
- if ((len == 1 && p[0] >= 0x80) || len > size)
- return 1;
-
- /*
- * Check for composing characters. We can handle only the first six, but
- * skip all of them (otherwise the cursor would get stuck).
- */
- prevlen = 0;
- while (len < size) {
- int len_next_char;
-
- if (p[len] < 0x80)
- break;
-
- /*
- * Next character length should not go beyond size to ensure that
- * UTF_COMPOSINGLIKE(...) does not read beyond size.
- */
- len_next_char = utf_ptr2len_len(p + len, size - len);
- if (len_next_char > size - len)
- break;
-
- if (!UTF_COMPOSINGLIKE(p + prevlen, p + len))
- break;
-
- /* Skip over composing char */
- prevlen = len;
- len += len_next_char;
- }
- return len;
-}
-
-int utf_char2len(const int c)
-{
- if (c < 0x80) {
- return 1;
- } else if (c < 0x800) {
- return 2;
- } else if (c < 0x10000) {
- return 3;
- } else if (c < 0x200000) {
- return 4;
- } else if (c < 0x4000000) {
- return 5;
- } else {
- return 6;
- }
-}
-
-int utf_char2bytes(const int c, char_u *const buf)
-{
- if (c < 0x80) { // 7 bits
- buf[0] = c;
- return 1;
- } else if (c < 0x800) { // 11 bits
- buf[0] = 0xc0 + ((unsigned)c >> 6);
- buf[1] = 0x80 + (c & 0x3f);
- return 2;
- } else if (c < 0x10000) { // 16 bits
- buf[0] = 0xe0 + ((unsigned)c >> 12);
- buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f);
- buf[2] = 0x80 + (c & 0x3f);
- return 3;
- } else if (c < 0x200000) { // 21 bits
- buf[0] = 0xf0 + ((unsigned)c >> 18);
- buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f);
- buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f);
- buf[3] = 0x80 + (c & 0x3f);
- return 4;
- } else if (c < 0x4000000) { // 26 bits
- buf[0] = 0xf8 + ((unsigned)c >> 24);
- buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f);
- buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f);
- buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f);
- buf[4] = 0x80 + (c & 0x3f);
- return 5;
- } else { // 31 bits
- buf[0] = 0xfc + ((unsigned)c >> 30);
- buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f);
- buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f);
- buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f);
- buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f);
- buf[5] = 0x80 + (c & 0x3f);
- return 6;
- }
-}
-
-int utf_ptr2len(const char_u *const p)
-{
- if (*p == NUL) {
- return 0;
- }
- const int len = utf8len_tab[*p];
- for (int i = 1; i < len; i++) {
- if ((p[i] & 0xc0) != 0x80) {
- return 1;
- }
- }
- return len;
-}
-
-int utfc_ptr2len(const char_u *const p)
-{
- uint8_t b0 = (uint8_t)(*p);
-
- if (b0 == NUL) {
- return 0;
- }
- if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII
- return 1;
- }
-
- // Skip over first UTF-8 char, stopping at a NUL byte.
- int len = utf_ptr2len(p);
-
- // Check for illegal byte.
- if (len == 1 && b0 >= 0x80) {
- return 1;
- }
-
- // Check for composing characters. We can handle only the first six, but
- // skip all of them (otherwise the cursor would get stuck).
- int prevlen = 0;
- for (;;) {
- if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) {
- return len;
- }
-
- // Skip over composing char.
- prevlen = len;
- len += utf_ptr2len(p + len);
- }
-}
-
-void mb_copy_char(const char_u **fp, char_u **tp)
-{
- const size_t l = utfc_ptr2len(*fp);
-
- memmove(*tp, *fp, (size_t)l);
- *tp += l;
- *fp += l;
-}
diff --git a/test/symbolic/klee/nvim/memory.c b/test/symbolic/klee/nvim/memory.c
deleted file mode 100644
index 1614f813d7..0000000000
--- a/test/symbolic/klee/nvim/memory.c
+++ /dev/null
@@ -1,101 +0,0 @@
-#include <stdlib.h>
-#include <assert.h>
-
-#include "nvim/lib/ringbuf.h"
-
-enum { RB_SIZE = 1024 };
-
-typedef struct {
- void *ptr;
- size_t size;
-} AllocRecord;
-
-RINGBUF_TYPEDEF(AllocRecords, AllocRecord)
-RINGBUF_INIT(AllocRecords, arecs, AllocRecord, RINGBUF_DUMMY_FREE)
-RINGBUF_STATIC(static, AllocRecords, AllocRecord, arecs, RB_SIZE)
-
-size_t allocated_memory = 0;
-size_t ever_allocated_memory = 0;
-
-size_t allocated_memory_limit = SIZE_MAX;
-
-void *xmalloc(const size_t size)
-{
- void *ret = malloc(size);
- allocated_memory += size;
- ever_allocated_memory += size;
- assert(allocated_memory <= allocated_memory_limit);
- assert(arecs_rb_length(&arecs) < RB_SIZE);
- arecs_rb_push(&arecs, (AllocRecord) {
- .ptr = ret,
- .size = size,
- });
- return ret;
-}
-
-void xfree(void *const p)
-{
- if (p == NULL) {
- return;
- }
- RINGBUF_FORALL(&arecs, AllocRecord, arec) {
- if (arec->ptr == p) {
- allocated_memory -= arec->size;
- arecs_rb_remove(&arecs, arecs_rb_find_idx(&arecs, arec));
- return;
- }
- }
- abort();
-}
-
-void *xrealloc(void *const p, size_t new_size)
-{
- void *ret = realloc(p, new_size);
- RINGBUF_FORALL(&arecs, AllocRecord, arec) {
- if (arec->ptr == p) {
- allocated_memory -= arec->size;
- allocated_memory += new_size;
- if (new_size > arec->size) {
- ever_allocated_memory += (new_size - arec->size);
- }
- arec->ptr = ret;
- arec->size = new_size;
- return ret;
- }
- }
- abort();
- return (void *)(intptr_t)1;
-}
-
-char *xstrdup(const char *str)
- FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
- FUNC_ATTR_NONNULL_ALL
-{
- return xmemdupz(str, strlen(str));
-}
-
-void *xmallocz(size_t size)
- FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
-{
- size_t total_size = size + 1;
- assert(total_size > size);
-
- void *ret = xmalloc(total_size);
- ((char *)ret)[size] = 0;
-
- return ret;
-}
-
-char *xstpcpy(char *restrict dst, const char *restrict src)
- FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
-{
- const size_t len = strlen(src);
- return (char *)memcpy(dst, src, len + 1) + len;
-}
-
-void *xmemdupz(const void *data, size_t len)
- FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
- FUNC_ATTR_NONNULL_ALL
-{
- return memcpy(xmallocz(len), data, len);
-}
diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh
deleted file mode 100755
index 97ce42c31b..0000000000
--- a/test/symbolic/klee/run.sh
+++ /dev/null
@@ -1,102 +0,0 @@
-#!/bin/sh
-
-set -e
-set -x
-test -z "$POSH_VERSION" && set -u
-
-PROJECT_SOURCE_DIR=.
-PROJECT_BINARY_DIR="$PROJECT_SOURCE_DIR/build"
-KLEE_TEST_DIR="$PROJECT_SOURCE_DIR/test/symbolic/klee"
-KLEE_BIN_DIR="$PROJECT_BINARY_DIR/klee"
-KLEE_OUT_DIR="$KLEE_BIN_DIR/out"
-
-help() {
- echo "Usage:"
- echo
- echo " $0 -c fname"
- echo " $0 fname"
- echo " $0 -s"
- echo
- echo "First form compiles executable out of test/symbolic/klee/{fname}.c."
- echo "Compiled executable is placed into build/klee/{fname}. Must first"
- echo "successfully compile Neovim in order to generate declarations."
- echo
- echo "Second form runs KLEE in a docker container using file "
- echo "test/symbolic/klee/{fname.c}. Bitcode is placed into build/klee/a.bc,"
- echo "results are placed into build/klee/out/. The latter is first deleted if"
- echo "it exists."
- echo
- echo "Third form runs ktest-tool which prints errors found by KLEE via "
- echo "the same container used to run KLEE."
-}
-
-main() {
- local compile=
- local print_errs=
- if test "$1" = "--help" ; then
- help
- return
- fi
- if test "$1" = "-s" ; then
- print_errs=1
- shift
- elif test "$1" = "-c" ; then
- compile=1
- shift
- fi
- if test -z "$print_errs" ; then
- local test="$1" ; shift
- fi
-
- local includes=
- includes="$includes -I$KLEE_TEST_DIR"
- includes="$includes -I/home/klee/klee_src/include"
- includes="$includes -I$PROJECT_SOURCE_DIR/src"
- includes="$includes -I$PROJECT_BINARY_DIR/src/nvim/auto"
- includes="$includes -I$PROJECT_BINARY_DIR/include"
- includes="$includes -I$PROJECT_BINARY_DIR/cmake.config"
- includes="$includes -I/host-includes"
-
- local defines=
- defines="$defines -DMIN_LOG_LEVEL=9999"
- defines="$defines -DINCLUDE_GENERATED_DECLARATIONS"
-
- test -z "$compile" && defines="$defines -DUSE_KLEE"
-
- test -d "$KLEE_BIN_DIR" || mkdir -p "$KLEE_BIN_DIR"
-
- if test -z "$compile" ; then
- local line1='cd /image'
- if test -z "$print_errs" ; then
- test -d "$KLEE_OUT_DIR" && rm -r "$KLEE_OUT_DIR"
-
- line1="$line1 && $(echo clang \
- $includes $defines \
- -o "$KLEE_BIN_DIR/a.bc" -emit-llvm -g -c \
- "$KLEE_TEST_DIR/$test.c")"
- line1="$line1 && klee --libc=uclibc --posix-runtime "
- line1="$line1 '--output-dir=$KLEE_OUT_DIR' '$KLEE_BIN_DIR/a.bc'"
- fi
- local line2="for t in '$KLEE_OUT_DIR'/*.err"
- line2="$line2 ; do ktest-tool --write-ints"
- line2="$line2 \"\$(printf '%s' \"\$t\" | sed -e 's@\\.[^/]*\$@.ktest@')\""
- line2="$line2 ; done"
- printf '%s\n%s\n' "$line1" "$line2" | \
- docker run \
- --volume "$(cd "$PROJECT_SOURCE_DIR" && pwd)":/image \
- --volume "/usr/include":/host-includes \
- --interactive \
- --rm \
- --ulimit='stack=-1:-1' \
- klee/klee \
- /bin/sh -x
- else
- clang \
- $includes $defines \
- -o "$KLEE_BIN_DIR/$test" \
- -O0 -g \
- "$KLEE_TEST_DIR/$test.c"
- fi
-}
-
-main "$@"
diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c
deleted file mode 100644
index 03c9d66ca4..0000000000
--- a/test/symbolic/klee/viml_expressions_lexer.c
+++ /dev/null
@@ -1,105 +0,0 @@
-#ifdef USE_KLEE
-# include <klee/klee.h>
-#else
-# include <string.h>
-# include <stdio.h>
-#endif
-#include <stddef.h>
-#include <stdint.h>
-#include <assert.h>
-
-#include "nvim/viml/parser/expressions.h"
-#include "nvim/viml/parser/parser.h"
-#include "nvim/mbyte.h"
-
-#include "nvim/memory.c"
-#include "nvim/mbyte.c"
-#include "nvim/charset.c"
-#include "nvim/garray.c"
-#include "nvim/gettext.c"
-#include "nvim/keycodes.c"
-#include "nvim/viml/parser/expressions.c"
-
-#define INPUT_SIZE 7
-
-uint8_t avoid_optimizing_out;
-
-void simple_get_line(void *cookie, ParserLine *ret_pline)
-{
- ParserLine **plines_p = (ParserLine **)cookie;
- *ret_pline = **plines_p;
- (*plines_p)++;
-}
-
-int main(const int argc, const char *const *const argv,
- const char *const *const environ)
-{
- char input[INPUT_SIZE];
- uint8_t shift;
- int flags;
- avoid_optimizing_out = argc;
-
-#ifndef USE_KLEE
- sscanf(argv[2], "%d", &flags);
-#endif
-
-#ifdef USE_KLEE
- klee_make_symbolic(input, sizeof(input), "input");
- klee_make_symbolic(&shift, sizeof(shift), "shift");
- klee_make_symbolic(&flags, sizeof(flags), "flags");
- klee_assume(shift < INPUT_SIZE);
- klee_assume(flags <= (kELFlagPeek|kELFlagAllowFloat|kELFlagForbidEOC
- |kELFlagForbidScope|kELFlagIsNotCmp));
-#endif
-
- ParserLine plines[] = {
- {
-#ifdef USE_KLEE
- .data = &input[shift],
- .size = sizeof(input) - shift,
-#else
- .data = (const char *)argv[1],
- .size = strlen(argv[1]),
-#endif
- .allocated = false,
- },
- {
- .data = NULL,
- .size = 0,
- .allocated = false,
- },
- };
-#ifdef USE_KLEE
- assert(plines[0].size <= INPUT_SIZE);
- assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc));
-#endif
- ParserLine *cur_pline = &plines[0];
-
- ParserState pstate = {
- .reader = {
- .get_line = simple_get_line,
- .cookie = &cur_pline,
- .lines = KV_INITIAL_VALUE,
- .conv.vc_type = CONV_NONE,
- },
- .pos = { 0, 0 },
- .colors = NULL,
- .can_continuate = false,
- };
- kvi_init(pstate.reader.lines);
-
- allocated_memory_limit = 0;
- LexExprToken token = viml_pexpr_next_token(&pstate, flags);
- if (flags & kELFlagPeek) {
- assert(pstate.pos.line == 0 && pstate.pos.col == 0);
- } else {
- assert((pstate.pos.line == 0)
- ? (pstate.pos.col > 0)
- : (pstate.pos.line == 1 && pstate.pos.col == 0));
- }
- assert(allocated_memory == 0);
- assert(ever_allocated_memory == 0);
-#ifndef USE_KLEE
- fprintf(stderr, "tkn: %s\n", viml_pexpr_repr_token(&pstate, token, NULL));
-#endif
-}
diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c
deleted file mode 100644
index b0e1d71127..0000000000
--- a/test/symbolic/klee/viml_expressions_parser.c
+++ /dev/null
@@ -1,117 +0,0 @@
-#ifdef USE_KLEE
-# include <klee/klee.h>
-#else
-# include <string.h>
-#endif
-#include <stddef.h>
-#include <stdint.h>
-#include <assert.h>
-
-#include "nvim/viml/parser/expressions.h"
-#include "nvim/viml/parser/parser.h"
-#include "nvim/mbyte.h"
-
-#include "nvim/memory.c"
-#include "nvim/mbyte.c"
-#include "nvim/charset.c"
-#include "nvim/garray.c"
-#include "nvim/gettext.c"
-#include "nvim/viml/parser/expressions.c"
-#include "nvim/keycodes.c"
-
-#define INPUT_SIZE 50
-
-uint8_t avoid_optimizing_out;
-
-void simple_get_line(void *cookie, ParserLine *ret_pline)
-{
- ParserLine **plines_p = (ParserLine **)cookie;
- *ret_pline = **plines_p;
- (*plines_p)++;
-}
-
-int main(const int argc, const char *const *const argv,
- const char *const *const environ)
-{
- char input[INPUT_SIZE];
- uint8_t shift;
- unsigned flags;
- const bool peek = false;
- avoid_optimizing_out = argc;
-
-#ifndef USE_KLEE
- sscanf(argv[2], "%d", &flags);
-#endif
-
-#ifdef USE_KLEE
- klee_make_symbolic(input, sizeof(input), "input");
- klee_make_symbolic(&shift, sizeof(shift), "shift");
- klee_make_symbolic(&flags, sizeof(flags), "flags");
- klee_assume(shift < INPUT_SIZE);
- klee_assume(
- flags <= (kExprFlagsMulti|kExprFlagsDisallowEOC|kExprFlagsParseLet));
-#endif
-
- ParserLine plines[] = {
- {
-#ifdef USE_KLEE
- .data = &input[shift],
- .size = sizeof(input) - shift,
-#else
- .data = argv[1],
- .size = strlen(argv[1]),
-#endif
- .allocated = false,
- },
- {
- .data = NULL,
- .size = 0,
- .allocated = false,
- },
- };
-#ifdef USE_KLEE
- assert(plines[0].size <= INPUT_SIZE);
- assert((plines[0].data[0] != 5) | (plines[0].data[0] != argc));
-#endif
- ParserLine *cur_pline = &plines[0];
-
- ParserHighlight colors;
- kvi_init(colors);
-
- ParserState pstate = {
- .reader = {
- .get_line = simple_get_line,
- .cookie = &cur_pline,
- .lines = KV_INITIAL_VALUE,
- .conv.vc_type = CONV_NONE,
- },
- .pos = { 0, 0 },
- .colors = &colors,
- .can_continuate = false,
- };
- kvi_init(pstate.reader.lines);
-
- const ExprAST ast = viml_pexpr_parse(&pstate, (int)flags);
- assert(ast.root != NULL || ast.err.msg);
- if (flags & kExprFlagsParseLet) {
- assert(ast.err.msg != NULL
- || ast.root->type == kExprNodeAssignment
- || (ast.root->type == kExprNodeListLiteral
- && ast.root->children != NULL)
- || ast.root->type == kExprNodeComplexIdentifier
- || ast.root->type == kExprNodeCurlyBracesIdentifier
- || ast.root->type == kExprNodePlainIdentifier
- || ast.root->type == kExprNodeRegister
- || ast.root->type == kExprNodeEnvironment
- || ast.root->type == kExprNodeOption
- || ast.root->type == kExprNodeSubscript
- || ast.root->type == kExprNodeConcatOrSubscript);
- }
- // Can’t possibly have more highlight tokens then there are bytes in string.
- assert(kv_size(colors) <= INPUT_SIZE - shift);
- kvi_destroy(colors);
- // Not destroying pstate.reader.lines because there is no way it could exceed
- // its limits in the current circumstances.
- viml_pexpr_free_ast(ast);
- assert(allocated_memory == 0);
-}
diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua
index a54ea8c656..6e08a09295 100644
--- a/test/unit/buffer_spec.lua
+++ b/test/unit/buffer_spec.lua
@@ -1,15 +1,11 @@
-
local helpers = require("test.unit.helpers")(after_each)
local itp = helpers.gen_itp(it)
local to_cstr = helpers.to_cstr
-local get_str = helpers.ffi.string
local eq = helpers.eq
local NULL = helpers.NULL
-local globals = helpers.cimport("./src/nvim/globals.h")
local buffer = helpers.cimport("./src/nvim/buffer.h")
-local stl = helpers.cimport("./src/nvim/statusline.h")
describe('buffer functions', function()
@@ -210,276 +206,4 @@ describe('buffer functions', function()
close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0)
end)
end)
-
- describe('build_stl_str_hl', function()
- local buffer_byte_size = 100
- local STL_INITIAL_ITEMS = 20
- local output_buffer = ''
-
- -- This function builds the statusline
- --
- -- @param arg Optional arguments are:
- -- .pat The statusline format string
- -- .fillchar The fill character used in the statusline
- -- .maximum_cell_count The number of cells available in the statusline
- local function build_stl_str_hl(arg)
- output_buffer = to_cstr(string.rep(" ", buffer_byte_size))
-
- local pat = arg.pat or ''
- local fillchar = arg.fillchar or (' '):byte()
- local maximum_cell_count = arg.maximum_cell_count or buffer_byte_size
-
- return stl.build_stl_str_hl(globals.curwin,
- output_buffer,
- buffer_byte_size,
- to_cstr(pat),
- NULL,
- 0,
- fillchar,
- maximum_cell_count,
- NULL,
- NULL,
- NULL)
- end
-
- -- Use this function to simplify testing the comparison between
- -- the format string and the resulting statusline.
- --
- -- @param description The description of what the test should be doing
- -- @param statusline_cell_count The number of cells available in the statusline
- -- @param input_stl The format string for the statusline
- -- @param expected_stl The expected result string for the statusline
- --
- -- @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 (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,
- statusline_cell_count,
- input_stl,
- expected_stl,
- arg)
-
- -- arg is the optional parameter
- -- so we either fill in option with arg or an empty dictionary
- local option = arg or {}
-
- 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_stl
-
- itp(description, function()
- if option.file_name then
- buffer.setfname(globals.curbuf, to_cstr(option.file_name), NULL, 1)
- else
- buffer.setfname(globals.curbuf, nil, NULL, 1)
- end
-
- local result_cell_count = build_stl_str_hl{pat=input_stl,
- maximum_cell_count=statusline_cell_count,
- fillchar=fillchar}
-
- eq(expected_stl, get_str(output_buffer, expected_byte_length))
- eq(expected_cell_count, result_cell_count)
- end)
- end
-
- -- expression testing
- statusline_test('Should expand expression', 2,
- '%!expand(20+1)', '21')
- statusline_test('Should expand broken expression to itself', 11,
- '%!expand(20+1', 'expand(20+1')
-
- -- file name testing
- statusline_test('should print no file name', 10,
- '%f', '[No Name]',
- {expected_cell_count=9})
- statusline_test('should print the relative file name', 30,
- '%f', 'test/unit/buffer_spec.lua',
- {file_name='test/unit/buffer_spec.lua', expected_cell_count=25})
- statusline_test('should print the full file name', 40,
- '%F', '/test/unit/buffer_spec.lua',
- {file_name='/test/unit/buffer_spec.lua', expected_cell_count=26})
-
- -- fillchar testing
- statusline_test('should handle `!` as a fillchar', 10,
- 'abcde%=', 'abcde!!!!!',
- {fillchar=('!'):byte()})
- statusline_test('should handle `~` as a fillchar', 10,
- '%=abcde', '~~~~~abcde',
- {fillchar=('~'):byte()})
- 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=('~'):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 print the tail file name', 80,
- '%t', 'buffer_spec.lua',
- {file_name='test/unit/buffer_spec.lua', expected_cell_count=15})
-
- -- standard text testing
- statusline_test('should copy plain text', 80,
- 'this is a test', 'this is a test',
- {expected_cell_count=14})
-
- -- line number testing
- statusline_test('should print the buffer number', 80,
- '%n', '1',
- {expected_cell_count=1})
- statusline_test('should print the current line number in the buffer', 80,
- '%l', '0',
- {expected_cell_count=1})
- statusline_test('should print the number of lines in the buffer', 80,
- '%L', '1',
- {expected_cell_count=1})
-
- -- truncation testing
- statusline_test('should truncate when standard text pattern is too long', 10,
- '0123456789abcde', '<6789abcde')
- statusline_test('should truncate when using =', 10,
- 'abcdef%=ghijkl', 'abcdef<jkl')
- statusline_test('should truncate centered text when using ==', 10,
- 'abcde%=gone%=fghij', 'abcde<ghij')
- statusline_test('should respect the `<` marker', 10,
- 'abc%<defghijkl', 'abc<ghijkl')
- statusline_test('should truncate at `<` with one `=`, test 1', 10,
- 'abc%<def%=ghijklmno', 'abc<jklmno')
- statusline_test('should truncate at `<` with one `=`, test 2', 10,
- 'abcdef%=ghijkl%<mno', 'abcdefghi>')
- statusline_test('should truncate at `<` with one `=`, test 3', 10,
- 'abc%<def%=ghijklmno', 'abc<jklmno')
- statusline_test('should truncate at `<` with one `=`, test 4', 10,
- 'abc%<def%=ghij', 'abcdefghij')
- statusline_test('should truncate at `<` with one `=`, test 4', 10,
- 'abc%<def%=ghijk', 'abc<fghijk')
-
- statusline_test('should truncate at `<` with many `=`, test 4', 10,
- 'ab%<cdef%=g%=h%=ijk', 'ab<efghijk')
-
- statusline_test('should truncate at the first `<`', 10,
- 'abc%<def%<ghijklm', 'abc<hijklm')
-
- statusline_test('should ignore trailing %', 3, 'abc%', 'abc')
-
- -- 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_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_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_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_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_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_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')
-
- -- stl item testing
- local tabline = ''
- for i= 1, 1000 do
- tabline = tabline .. (i % 2 == 0 and '%#TabLineSel#' or '%#TabLineFill#') .. tostring(i % 2)
- end
- statusline_test('should handle a large amount of any items', 20,
- tabline,
- '<1010101010101010101') -- Should not show any error
- statusline_test('should handle a larger amount of = than stl initial item', 20,
- ('%='):rep(STL_INITIAL_ITEMS * 5),
- ' ') -- Should not show any error
- statusline_test('should handle many extra characters', 20,
- 'a' .. ('a'):rep(STL_INITIAL_ITEMS * 5),
- '<aaaaaaaaaaaaaaaaaaa') -- Does not show any error
- statusline_test('should handle many extra characters and flags', 20,
- 'a' .. ('%=a'):rep(STL_INITIAL_ITEMS * 2),
- 'a<aaaaaaaaaaaaaaaaaa') -- Should not show any error
-
-
- -- multi-byte testing
- statusline_test('should handle multibyte characters', 10,
- 'Ĉ%=x', 'Ĉ x')
- statusline_test('should handle multibyte characters and different fillchars', 10,
- 'Ą%=mid%=end', 'Ą@mid@@end',
- {fillchar=('@'):byte()})
-
- -- escaping % testing
- statusline_test('should handle escape of %', 4, 'abc%%', 'abc%')
- statusline_test('case where escaped % does not fit', 3, 'abc%%abcabc', '<bc')
- statusline_test('escaped % is first', 1, '%%', '%')
-
- end)
end)
diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua
index caf330c378..fc40fa39d4 100644
--- a/test/unit/charset/vim_str2nr_spec.lua
+++ b/test/unit/charset/vim_str2nr_spec.lua
@@ -63,7 +63,7 @@ local function test_vim_str2nr(s, what, exp, maxlen, strict)
cv[k] = args[k]
end
end
- lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen, strict)
+ lib.vim_str2nr(s, cv.pre, cv.len, what, cv.num, cv.unum, maxlen, strict, nil)
for cck, ccv in pairs(cv) do
if exp[cck] ~= tonumber(ccv[0]) then
error(('Failed check (%s = %d) in test (s=%s, w=%u, m=%d, strict=%s): %d'):format(
diff --git a/test/unit/eval/helpers.lua b/test/unit/eval/helpers.lua
index b600f01ab2..883f01bd84 100644
--- a/test/unit/eval/helpers.lua
+++ b/test/unit/eval/helpers.lua
@@ -349,7 +349,7 @@ local special_vals = nil
lua2typvalt = function(l, processed)
if not special_vals then
special_vals = {
- [null_string] = {'VAR_STRING', {v_string=ffi.cast('char_u*', nil)}},
+ [null_string] = {'VAR_STRING', {v_string=ffi.cast('char*', nil)}},
[null_list] = {'VAR_LIST', {v_list=ffi.cast('list_T*', nil)}},
[null_dict] = {'VAR_DICT', {v_dict=ffi.cast('dict_T*', nil)}},
[nil_value] = {'VAR_SPECIAL', {v_special=eval.kSpecialVarNull}},
@@ -512,7 +512,8 @@ end
local function eval0(expr)
local tv = ffi.gc(ffi.new('typval_T', {v_type=eval.VAR_UNKNOWN}),
eval.tv_clear)
- if eval.eval0(to_cstr(expr), tv, nil, true) == 0 then
+ local evalarg = ffi.new('evalarg_T', {eval_flags = eval.EVAL_EVALUATE})
+ if eval.eval0(to_cstr(expr), tv, nil, evalarg) == 0 then
return nil
else
return tv
diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua
index 825377813d..34a1299725 100644
--- a/test/unit/eval/typval_spec.lua
+++ b/test/unit/eval/typval_spec.lua
@@ -43,7 +43,7 @@ local dict_watchers = eval_helpers.dict_watchers
local lib = cimport('./src/nvim/eval/typval.h', './src/nvim/memory.h',
'./src/nvim/mbyte.h', './src/nvim/garray.h',
- './src/nvim/eval.h', './src/nvim/vim.h',
+ './src/nvim/eval.h', './src/nvim/vim_defs.h',
'./src/nvim/globals.h')
local function vimconv_alloc()
@@ -101,12 +101,13 @@ local function check_emsg(f, msg)
saved_last_msg_hist = nil
end
local ret = {f()}
+ local last_msg = lib.last_msg_hist ~= nil and ffi.string(lib.last_msg_hist.msg) or nil
if msg ~= nil then
- eq(msg, ffi.string(lib.last_msg_hist.msg))
+ eq(msg, last_msg)
neq(saved_last_msg_hist, lib.last_msg_hist)
else
if saved_last_msg_hist ~= lib.last_msg_hist then
- eq(nil, ffi.string(lib.last_msg_hist.msg))
+ eq(nil, last_msg)
else
eq(saved_last_msg_hist, lib.last_msg_hist)
end
@@ -1429,15 +1430,16 @@ describe('typval.c', function()
end
describe('str()', function()
itp('returns correct string', function()
- local l = list(int(1), int(2), int(3), int(4), int(5))
+ local l = list(int(1), 2.5, int(3), int(4), int(5))
alloc_log:clear()
eq('1', tv_list_find_str(l, -5))
+ eq('2.5', tv_list_find_str(l, 1))
eq('5', tv_list_find_str(l, 4))
eq('3', tv_list_find_str(l, 2))
eq('3', tv_list_find_str(l, -3))
- alloc_log:check({})
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
end)
itp('returns string when used with VAR_STRING items', function()
local l = list('1', '2', '3', '4', '5')
@@ -1461,18 +1463,16 @@ describe('typval.c', function()
itp('fails with error message when index is out of range', function()
local l = list(int(1), int(2), int(3), int(4), int(5))
- eq(nil, tv_list_find_str(l, -6, 'E684: list index out of range: -6'))
- eq(nil, tv_list_find_str(l, 5, 'E684: list index out of range: 5'))
+ eq(nil, tv_list_find_str(l, -6, 'E684: List index out of range: -6'))
+ eq(nil, tv_list_find_str(l, 5, 'E684: List index out of range: 5'))
end)
itp('fails with error message on invalid types', function()
- local l = list(1, empty_list, {})
+ local l = list(empty_list, {})
- eq('', tv_list_find_str(l, 0, 'E806: using Float as a String'))
- eq('', tv_list_find_str(l, 1, 'E730: using List as a String'))
- eq('', tv_list_find_str(l, 2, 'E731: using Dictionary as a String'))
- eq('', tv_list_find_str(l, -1, 'E731: using Dictionary as a String'))
- eq('', tv_list_find_str(l, -2, 'E730: using List as a String'))
- eq('', tv_list_find_str(l, -3, 'E806: using Float as a String'))
+ eq('', tv_list_find_str(l, 0, 'E730: Using a List as a String'))
+ eq('', tv_list_find_str(l, 1, 'E731: Using a Dictionary as a String'))
+ eq('', tv_list_find_str(l, -1, 'E731: Using a Dictionary as a String'))
+ eq('', tv_list_find_str(l, -2, 'E730: Using a List as a String'))
end)
end)
end)
@@ -1653,7 +1653,7 @@ describe('typval.c', function()
eq(OK, lib.tv_dict_add(d, di))
alloc_log:check({})
eq(FAIL, check_emsg(function() return lib.tv_dict_add(d, di) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key ""'))
alloc_log:clear()
lib.tv_dict_item_remove(d, di)
alloc_log:check({
@@ -1745,7 +1745,7 @@ describe('typval.c', function()
itp('works', function()
local d = ffi.gc(dict({test={}}), nil)
eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 'test', false) end,
- 'E731: using Dictionary as a String')))
+ 'E731: Using a Dictionary as a String')))
d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil)
alloc_log:clear()
local dis = dict_items(d)
@@ -1765,18 +1765,24 @@ describe('typval.c', function()
neq(s42, s43)
eq(s43, dis.te.di_tv.vval.v_string)
alloc_log:check({})
- eq('', ffi.string(check_emsg(function() return lib.tv_dict_get_string(d, 't', false) end,
- 'E806: using Float as a String')))
+ local s44 = check_emsg(function() return lib.tv_dict_get_string(d, 't', false) end,
+ nil)
+ eq('44.0', ffi.string(s44))
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
end)
itp('allocates a string copy when requested', function()
- local function tv_dict_get_string_alloc(d, key, emsg)
+ local function tv_dict_get_string_alloc(d, key, emsg, is_float)
alloc_log:clear()
local ret = check_emsg(function() return lib.tv_dict_get_string(d, key, true) end,
emsg)
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
if s_ret then
- alloc_log:check({a.str(ret, s_ret)})
+ if is_float then
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null), a.str(ret, s_ret)})
+ else
+ alloc_log:check({a.str(ret, s_ret)})
+ end
else
alloc_log:check({})
end
@@ -1785,25 +1791,29 @@ describe('typval.c', function()
return s_ret
end
local d = ffi.gc(dict({test={}}), nil)
- eq('', tv_dict_get_string_alloc(d, 'test', 'E731: using Dictionary as a String'))
+ eq('', tv_dict_get_string_alloc(d, 'test', 'E731: Using a Dictionary as a String'))
d = ffi.gc(dict({tes=int(42), t=44, te='43', xx=int(45)}), nil)
alloc_log:clear()
eq(nil, tv_dict_get_string_alloc(d, 'test'))
eq('42', tv_dict_get_string_alloc(d, 'tes'))
eq('45', tv_dict_get_string_alloc(d, 'xx'))
eq('43', tv_dict_get_string_alloc(d, 'te'))
- eq('', tv_dict_get_string_alloc(d, 't', 'E806: using Float as a String'))
+ eq('44.0', tv_dict_get_string_alloc(d, 't', nil, true))
end)
end)
describe('get_string_buf()', function()
- local function tv_dict_get_string_buf(d, key, buf, emsg)
+ local function tv_dict_get_string_buf(d, key, is_float, buf, emsg)
buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
alloc_log:clear()
local ret = check_emsg(function() return lib.tv_dict_get_string_buf(d, key, buf) end,
emsg)
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
- alloc_log:check({})
+ if is_float then
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
+ else
+ alloc_log:check({})
+ end
end
return s_ret, ret, buf
end
@@ -1827,16 +1837,16 @@ describe('typval.c', function()
s, r, b = tv_dict_get_string_buf(d, 'test')
neq(r, b)
eq('tset', s)
- s, r, b = tv_dict_get_string_buf(d, 't', nil, 'E806: using Float as a String')
- neq(r, b)
- eq('', s)
+ s, r, b = tv_dict_get_string_buf(d, 't', true)
+ eq(r, b)
+ eq('1.0', s)
s, r, b = tv_dict_get_string_buf(d, 'te')
eq(r, b)
eq('2', s)
end)
end)
describe('get_string_buf_chk()', function()
- local function tv_dict_get_string_buf_chk(d, key, len, buf, def, emsg)
+ local function tv_dict_get_string_buf_chk(d, key, is_float, len, buf, def, emsg)
buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree)
def = def or ffi.gc(lib.xstrdup('DEFAULT'), lib.xfree)
len = len or #key
@@ -1845,7 +1855,11 @@ describe('typval.c', function()
emsg)
local s_ret = (ret ~= nil) and ffi.string(ret) or nil
if not emsg then
- alloc_log:check({})
+ if is_float then
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
+ else
+ alloc_log:check({})
+ end
end
return s_ret, ret, buf, def
end
@@ -1870,10 +1884,10 @@ describe('typval.c', function()
neq(r, b)
neq(r, def)
eq('tset', s)
- s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', 1, nil, nil, 'E806: using Float as a String')
- neq(r, b)
+ s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', true, 1)
+ eq(r, b)
neq(r, def)
- eq(nil, s)
+ eq('1.0', s)
s, r, b, def = tv_dict_get_string_buf_chk(d, 'te')
eq(r, b)
neq(r, def)
@@ -1947,7 +1961,7 @@ describe('typval.c', function()
alloc_log:check({})
eq({test=10, ['t-est']=int(42)}, dct2tbl(d))
eq(FAIL, check_emsg(function() return lib.tv_dict_add(d, di) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "t-est"'))
end)
end)
describe('list()', function()
@@ -1964,7 +1978,7 @@ describe('typval.c', function()
eq({test=10, tes={1, 2, 3}}, dct2tbl(d))
eq(2, l.lv_refcount)
eq(FAIL, check_emsg(function() return lib.tv_dict_add_list(d, 'testt', 3, l) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
eq(2, l.lv_refcount)
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
@@ -1990,7 +2004,7 @@ describe('typval.c', function()
eq({test=10, tes={foo=42}}, dct2tbl(d))
eq(2, d2.dv_refcount)
eq(FAIL, check_emsg(function() return lib.tv_dict_add_dict(d, 'testt', 3, d2) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
eq(2, d2.dv_refcount)
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
@@ -2012,7 +2026,7 @@ describe('typval.c', function()
alloc_log:check({a.di(dis.tes, 'tes')})
eq({test=10, tes=int(2)}, dct2tbl(d))
eq(FAIL, check_emsg(function() return lib.tv_dict_add_nr(d, 'testt', 3, 2) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
eq(FAIL, check_emsg(function() return lib.tv_dict_add_nr(d, 'testt', 3, 2) end,
@@ -2032,7 +2046,7 @@ describe('typval.c', function()
alloc_log:check({a.di(dis.tes, 'tes')})
eq({test=10, tes=1.5}, dct2tbl(d))
eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
eq(FAIL, check_emsg(function() return lib.tv_dict_add_float(d, 'testt', 3, 1.5) end,
@@ -2055,7 +2069,7 @@ describe('typval.c', function()
})
eq({test=10, tes='TEST'}, dct2tbl(d))
eq(FAIL, check_emsg(function() return lib.tv_dict_add_str(d, 'testt', 3, 'TEST') end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
eq(FAIL, check_emsg(function() return lib.tv_dict_add_str(d, 'testt', 3, 'TEST') end,
@@ -2085,7 +2099,7 @@ describe('typval.c', function()
})
eq({test=10, tes='TEST'}, dct2tbl(d))
eq(FAIL, check_emsg(function() return lib.tv_dict_add_allocated_str(d, 'testt', 3, s2) end,
- 'E685: Internal error: hash_add()'))
+ 'E685: Internal error: hash_add(): duplicate key "tes"'))
alloc_log:clear()
lib.emsg_skip = lib.emsg_skip + 1
eq(FAIL, check_emsg(function() return lib.tv_dict_add_allocated_str(d, 'testt', 3, s3) end,
@@ -2831,14 +2845,14 @@ describe('typval.c', function()
alloc_log:clear()
for _, v in ipairs({
{lib.VAR_NUMBER, nil},
- {lib.VAR_FLOAT, 'E806: using Float as a String'},
- {lib.VAR_PARTIAL, 'E729: using Funcref as a String'},
- {lib.VAR_FUNC, 'E729: using Funcref as a String'},
- {lib.VAR_LIST, 'E730: using List as a String'},
- {lib.VAR_DICT, 'E731: using Dictionary as a String'},
+ {lib.VAR_FLOAT, nil},
+ {lib.VAR_PARTIAL, 'E729: Using a Funcref as a String'},
+ {lib.VAR_FUNC, 'E729: Using a Funcref as a String'},
+ {lib.VAR_LIST, 'E730: Using a List as a String'},
+ {lib.VAR_DICT, 'E731: Using a Dictionary as a String'},
{lib.VAR_BOOL, nil},
{lib.VAR_SPECIAL, nil},
- {lib.VAR_UNKNOWN, 'E908: using an invalid value as a String'},
+ {lib.VAR_UNKNOWN, 'E908: Using an invalid value as a String'},
}) do
local typ = v[1]
local emsg = v[2]
@@ -2978,179 +2992,118 @@ describe('typval.c', function()
end
end)
end)
+
+ local function test_string_fn(input, fn)
+ for _, v in ipairs(input) do
+ -- Using to_cstr in place of Neovim allocated string, cannot
+ -- tv_clear() that.
+ local tv = ffi.gc(typvalt(v[1], v[2]), nil)
+ alloc_log:check({})
+ local emsg = v[3]
+ local ret = v[4]
+ eq(ret, check_emsg(function()
+ local res, buf = fn(tv)
+ if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_FLOAT
+ or tv.v_type == lib.VAR_SPECIAL or tv.v_type == lib.VAR_BOOL then
+ eq(buf, res)
+ else
+ neq(buf, res)
+ end
+ if res ~= nil then
+ return ffi.string(res)
+ else
+ return nil
+ end
+ end, emsg))
+ if emsg then
+ alloc_log:clear()
+ elseif tv.v_type == lib.VAR_FLOAT then
+ alloc_log:check({a.freed(alloc_log.null), a.freed(alloc_log.null)})
+ else
+ alloc_log:check({})
+ end
+ end
+ end
describe('string()', function()
itp('works', function()
local buf = lib.tv_get_string(lua2typvalt(int(1)))
local buf_chk = lib.tv_get_string_chk(lua2typvalt(int(1)))
neq(buf, buf_chk)
- for _, v in ipairs({
+ test_string_fn({
{lib.VAR_NUMBER, {v_number=42}, nil, '42'},
{lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
- {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''},
- {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''},
- {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''},
- {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
- {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
+ {lib.VAR_FLOAT, {v_float=42.53}, nil, '42.53'},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', ''},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', ''},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', ''},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'},
- {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
- }) do
- -- Using to_cstr in place of Neovim allocated string, cannot
- -- tv_clear() that.
- local tv = ffi.gc(typvalt(v[1], v[2]), nil)
- alloc_log:check({})
- local emsg = v[3]
- local ret = v[4]
- eq(ret, check_emsg(function()
- local res = lib.tv_get_string(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
- or tv.v_type == lib.VAR_BOOL then
- eq(buf, res)
- else
- neq(buf, res)
- end
- if res ~= nil then
- return ffi.string(res)
- else
- return nil
- end
- end, emsg))
- if emsg then
- alloc_log:clear()
- else
- alloc_log:check({})
- end
- end
+ {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', ''},
+ }, function(tv)
+ return lib.tv_get_string(tv), buf
+ end)
end)
end)
describe('string_chk()', function()
itp('works', function()
local buf = lib.tv_get_string_chk(lua2typvalt(int(1)))
- for _, v in ipairs({
+ test_string_fn({
{lib.VAR_NUMBER, {v_number=42}, nil, '42'},
{lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
- {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil},
- {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil},
- {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil},
- {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
- {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
+ {lib.VAR_FLOAT, {v_float=42.53}, nil, '42.53'},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', nil},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', nil},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', nil},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'},
- {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
- }) do
- -- Using to_cstr, cannot free with tv_clear
- local tv = ffi.gc(typvalt(v[1], v[2]), nil)
- alloc_log:check({})
- local emsg = v[3]
- local ret = v[4]
- eq(ret, check_emsg(function()
- local res = lib.tv_get_string_chk(tv)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
- or tv.v_type == lib.VAR_BOOL then
- eq(buf, res)
- else
- neq(buf, res)
- end
- if res ~= nil then
- return ffi.string(res)
- else
- return nil
- end
- end, emsg))
- if emsg then
- alloc_log:clear()
- else
- alloc_log:check({})
- end
- end
+ {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil},
+ }, function(tv)
+ return lib.tv_get_string_chk(tv), buf
+ end)
end)
end)
describe('string_buf()', function()
itp('works', function()
- for _, v in ipairs({
+ test_string_fn({
{lib.VAR_NUMBER, {v_number=42}, nil, '42'},
{lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
- {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', ''},
- {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', ''},
- {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', ''},
- {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', ''},
- {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', ''},
+ {lib.VAR_FLOAT, {v_float=42.53}, nil, '42.53'},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', ''},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', ''},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', ''},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', ''},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'},
- {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', ''},
- }) do
- -- Using to_cstr, cannot free with tv_clear
- local tv = ffi.gc(typvalt(v[1], v[2]), nil)
- alloc_log:check({})
- local emsg = v[3]
- local ret = v[4]
- eq(ret, check_emsg(function()
- local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
- local res = lib.tv_get_string_buf(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
- or tv.v_type == lib.VAR_BOOL then
- eq(buf, res)
- else
- neq(buf, res)
- end
- if res ~= nil then
- return ffi.string(res)
- else
- return nil
- end
- end, emsg))
- if emsg then
- alloc_log:clear()
- else
- alloc_log:check({})
- end
- end
+ {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', ''},
+ }, function(tv)
+ local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
+ return lib.tv_get_string_buf(tv, buf), buf
+ end)
end)
end)
describe('string_buf_chk()', function()
itp('works', function()
- for _, v in ipairs({
+ test_string_fn({
{lib.VAR_NUMBER, {v_number=42}, nil, '42'},
{lib.VAR_STRING, {v_string=to_cstr('100500')}, nil, '100500'},
- {lib.VAR_FLOAT, {v_float=42.53}, 'E806: using Float as a String', nil},
- {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: using Funcref as a String', nil},
- {lib.VAR_FUNC, {v_string=NULL}, 'E729: using Funcref as a String', nil},
- {lib.VAR_LIST, {v_list=NULL}, 'E730: using List as a String', nil},
- {lib.VAR_DICT, {v_dict=NULL}, 'E731: using Dictionary as a String', nil},
+ {lib.VAR_FLOAT, {v_float=42.53}, nil, '42.53'},
+ {lib.VAR_PARTIAL, {v_partial=NULL}, 'E729: Using a Funcref as a String', nil},
+ {lib.VAR_FUNC, {v_string=NULL}, 'E729: Using a Funcref as a String', nil},
+ {lib.VAR_LIST, {v_list=NULL}, 'E730: Using a List as a String', nil},
+ {lib.VAR_DICT, {v_dict=NULL}, 'E731: Using a Dictionary as a String', nil},
{lib.VAR_SPECIAL, {v_special=lib.kSpecialVarNull}, nil, 'v:null'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarTrue}, nil, 'v:true'},
{lib.VAR_BOOL, {v_bool=lib.kBoolVarFalse}, nil, 'v:false'},
- {lib.VAR_UNKNOWN, nil, 'E908: using an invalid value as a String', nil},
- }) do
- -- Using to_cstr, cannot free with tv_clear
- local tv = ffi.gc(typvalt(v[1], v[2]), nil)
- alloc_log:check({})
- local emsg = v[3]
- local ret = v[4]
- eq(ret, check_emsg(function()
- local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
- local res = lib.tv_get_string_buf_chk(tv, buf)
- if tv.v_type == lib.VAR_NUMBER or tv.v_type == lib.VAR_SPECIAL
- or tv.v_type == lib.VAR_BOOL then
- eq(buf, res)
- else
- neq(buf, res)
- end
- if res ~= nil then
- return ffi.string(res)
- else
- return nil
- end
- end, emsg))
- if emsg then
- alloc_log:clear()
- else
- alloc_log:check({})
- end
- end
+ {lib.VAR_UNKNOWN, nil, 'E908: Using an invalid value as a String', nil},
+ }, function(tv)
+ local buf = ffi.new('char[?]', lib.NUMBUFLEN, {0})
+ return lib.tv_get_string_buf_chk(tv, buf), buf
+ end)
end)
end)
end)
diff --git a/test/unit/fixtures/multiqueue.c b/test/unit/fixtures/multiqueue.c
index a8dca0a844..3a5ddab4b8 100644
--- a/test/unit/fixtures/multiqueue.c
+++ b/test/unit/fixtures/multiqueue.c
@@ -1,19 +1,16 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include <string.h>
#include <stdlib.h>
#include "nvim/event/multiqueue.h"
#include "multiqueue.h"
-void ut_multiqueue_put(MultiQueue *this, const char *str)
+void ut_multiqueue_put(MultiQueue *self, const char *str)
{
- multiqueue_put(this, NULL, 1, str);
+ multiqueue_put(self, NULL, 1, str);
}
-const char *ut_multiqueue_get(MultiQueue *this)
+const char *ut_multiqueue_get(MultiQueue *self)
{
- Event event = multiqueue_get(this);
+ Event event = multiqueue_get(self);
return event.argv[0];
}
diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c
index efa7ab1986..d587d6b054 100644
--- a/test/unit/fixtures/rbuffer.c
+++ b/test/unit/fixtures/rbuffer.c
@@ -1,6 +1,3 @@
-// This is an open source non-commercial project. Dear PVS-Studio, please check
-// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
-
#include "nvim/rbuffer.h"
#include "rbuffer.h"
@@ -15,7 +12,7 @@ void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb)
void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb)
{
- RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { // -V1044
+ RBUFFER_UNTIL_FULL(buf, wptr, wcnt) {
cb(wptr, wcnt);
rbuffer_produced(buf, wcnt);
}
diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua
index 2fd37c599a..c94f0d88f7 100644
--- a/test/unit/formatc.lua
+++ b/test/unit/formatc.lua
@@ -154,6 +154,8 @@ local C_keywords = set { -- luacheck: ignore
--
-- The first one will have a lot of false positives (the line '{' for
-- example), the second one is more unique.
+--- @param string
+--- @return string
local function formatc(str)
local toks = TokeniseC(str)
local result = {}
diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua
index 686af3b461..8d581dac49 100644
--- a/test/unit/helpers.lua
+++ b/test/unit/helpers.lua
@@ -14,20 +14,15 @@ local map = global_helpers.tbl_map
local eq = global_helpers.eq
local trim = global_helpers.trim
--- C constants.
-local NULL = ffi.cast('void*', 0)
-
-local OK = 1
-local FAIL = 0
-
-local cimport
-
-- add some standard header locations
for _, p in ipairs(Paths.include_paths) do
Preprocess.add_to_include_path(p)
end
-local child_pid = nil
+local child_pid = nil --- @type integer
+--- @generic F: function
+--- @param func F
+--- @return F
local function only_separate(func)
return function(...)
if child_pid ~= 0 then
@@ -36,9 +31,20 @@ local function only_separate(func)
return func(...)
end
end
-local child_calls_init = {}
-local child_calls_mod = nil
-local child_calls_mod_once = nil
+
+--- @class ChildCall
+--- @field func function
+--- @field args any[]
+
+--- @class ChildCallLog
+--- @field func string
+--- @field args any[]
+--- @field ret any?
+
+local child_calls_init = {} --- @type ChildCall[]
+local child_calls_mod = nil --- @type ChildCall[]
+local child_calls_mod_once = nil --- @type ChildCall[]?
+
local function child_call(func, ret)
return function(...)
local child_calls = child_calls_mod or child_calls_init
@@ -53,16 +59,16 @@ end
-- Run some code at the start of the child process, before running the test
-- itself. Is supposed to be run in `before_each`.
+--- @param func function
local function child_call_once(func, ...)
if child_pid ~= 0 then
- child_calls_mod_once[#child_calls_mod_once + 1] = {
- func=func, args={...}}
+ child_calls_mod_once[#child_calls_mod_once + 1] = { func = func, args = {...} }
else
func(...)
end
end
-local child_cleanups_mod_once = nil
+local child_cleanups_mod_once = nil --- @type ChildCall[]?
-- Run some code at the end of the child process, before exiting. Is supposed to
-- be run in `before_each` because `after_each` is run after child has exited.
@@ -91,8 +97,6 @@ local init = only_separate(function()
for _, c in ipairs(child_calls_init) do
c.func(unpack(c.args))
end
- libnvim.time_init()
- libnvim.fs_init()
libnvim.event_init()
libnvim.early_init(nil)
if child_calls_mod then
@@ -125,15 +129,19 @@ local pragma_pack_id = 1
-- some things are just too complex for the LuaJIT C parser to digest. We
-- usually don't need them anyway.
+--- @param body string
local function filter_complex_blocks(body)
- local result = {}
+ local result = {} --- @type string[]
for line in body:gmatch("[^\r\n]+") do
if not (string.find(line, "(^)", 1, true) ~= nil
or string.find(line, "_ISwupper", 1, true)
or string.find(line, "_Float")
+ or string.find(line, "__s128")
+ or string.find(line, "__u128")
or string.find(line, "msgpack_zone_push_finalizer")
or string.find(line, "msgpack_unpacker_reserve_buffer")
+ or string.find(line, "value_init_")
or string.find(line, "UUID_NULL") -- static const uuid_t UUID_NULL = {...}
or string.find(line, "inline _Bool")) then
result[#result + 1] = line
@@ -148,19 +156,25 @@ local cdef = ffi.cdef
local cimportstr
-local previous_defines_init = ''
-local preprocess_cache_init = {}
+local previous_defines_init = [[
+typedef struct { char bytes[16]; } __attribute__((aligned(16))) __uint128_t;
+typedef struct { char bytes[16]; } __attribute__((aligned(16))) __float128;
+]]
+
+local preprocess_cache_init = {} --- @type table<string,string>
local previous_defines_mod = ''
-local preprocess_cache_mod = nil
+local preprocess_cache_mod = nil --- @type table<string,string>
local function is_child_cdefs()
- return (os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1')
+ return os.getenv('NVIM_TEST_MAIN_CDEFS') ~= '1'
end
-- use this helper to import C files, you can pass multiple paths at once,
-- this helper will return the C namespace of the nvim library.
-cimport = function(...)
- local previous_defines, preprocess_cache, cdefs
+local function cimport(...)
+ local previous_defines --- @type string
+ local preprocess_cache --- @type table<string,string>
+ local cdefs
if is_child_cdefs() and preprocess_cache_mod then
preprocess_cache = preprocess_cache_mod
previous_defines = previous_defines_mod
@@ -176,7 +190,7 @@ cimport = function(...)
path = './' .. path
end
if not preprocess_cache[path] then
- local body
+ local body --- @type string
body, previous_defines = Preprocess.preprocess(previous_defines, path)
-- format it (so that the lines are "unique" statements), also filter out
-- Objective-C blocks
@@ -198,6 +212,7 @@ cimport = function(...)
-- (they are needed in the right order with the struct definitions,
-- otherwise luajit has wrong memory layouts for the sturcts)
if line:match("#pragma%s+pack") then
+ --- @type string
line = line .. " // " .. pragma_pack_id
pragma_pack_id = pragma_pack_id + 1
end
@@ -225,20 +240,21 @@ cimport = function(...)
return lib
end
-local cimport_immediate = function(...)
+local function cimport_immediate(...)
local saved_pid = child_pid
child_pid = 0
local err, emsg = pcall(cimport, ...)
child_pid = saved_pid
if not err then
- emsg = tostring(emsg)
- io.stderr:write(emsg .. '\n')
+ io.stderr:write(tostring(emsg) .. '\n')
assert(false)
else
return lib
end
end
+--- @param preprocess_cache table<string,string[]>
+--- @param path string
local function _cimportstr(preprocess_cache, path)
if imported:contains(path) then
return lib
@@ -261,12 +277,14 @@ end
local function alloc_log_new()
local log = {
- log={},
- lib=cimport('./src/nvim/memory.h'),
- original_functions={},
+ log={}, --- @type ChildCallLog[]
+ lib=cimport('./src/nvim/memory.h'), --- @type table<string,function>
+ original_functions={}, --- @type table<string,function>
null={['\0:is_null']=true},
}
+
local allocator_functions = {'malloc', 'free', 'calloc', 'realloc'}
+
function log:save_original_functions()
for _, funcname in ipairs(allocator_functions) do
if not self.original_functions[funcname] then
@@ -274,13 +292,16 @@ local function alloc_log_new()
end
end
end
+
log.save_original_functions = child_call(log.save_original_functions)
+
function log:set_mocks()
for _, k in ipairs(allocator_functions) do
do
local kk = k
self.lib['mem_' .. k] = function(...)
- local log_entry = {func=kk, args={...}}
+ --- @type ChildCallLog
+ local log_entry = { func = kk, args = {...} }
self.log[#self.log + 1] = log_entry
if kk == 'free' then
self.original_functions[kk](...)
@@ -301,17 +322,21 @@ local function alloc_log_new()
end
end
end
+
log.set_mocks = child_call(log.set_mocks)
+
function log:clear()
self.log = {}
end
+
function log:check(exp)
eq(exp, self.log)
self:clear()
end
+
function log:clear_tmp_allocs(clear_null_frees)
- local toremove = {}
- local allocs = {}
+ local toremove = {} --- @type integer[]
+ local allocs = {} --- @type table<string,integer>
for i, v in ipairs(self.log) do
if v.func == 'malloc' or v.func == 'calloc' then
allocs[tostring(v.ret)] = i
@@ -334,26 +359,20 @@ local function alloc_log_new()
table.remove(self.log, toremove[i])
end
end
- function log:restore_original_functions()
- -- Do nothing: set mocks live in a separate process
- return
- --[[
- [ for k, v in pairs(self.original_functions) do
- [ self.lib['mem_' .. k] = v
- [ end
- ]]
- end
+
function log:setup()
log:save_original_functions()
log:set_mocks()
end
+
function log:before_each()
- return
end
+
function log:after_each()
- log:restore_original_functions()
end
+
log:setup()
+
return log
end
@@ -370,98 +389,109 @@ local function to_cstr(string)
end
cimport_immediate('./test/unit/fixtures/posix.h')
-local sc = {
- fork = function()
- return tonumber(ffi.C.fork())
- end,
- pipe = function()
- local ret = ffi.new('int[2]', {-1, -1})
- ffi.errno(0)
- local res = ffi.C.pipe(ret)
- if (res ~= 0) then
+
+local sc = {}
+
+function sc.fork()
+ return tonumber(ffi.C.fork())
+end
+
+function sc.pipe()
+ local ret = ffi.new('int[2]', {-1, -1})
+ ffi.errno(0)
+ local res = ffi.C.pipe(ret)
+ if (res ~= 0) then
+ local err = ffi.errno(0)
+ assert(res == 0, ("pipe() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
+ end
+ assert(ret[0] ~= -1 and ret[1] ~= -1)
+ return ret[0], ret[1]
+end
+
+--- @return string
+function sc.read(rd, len)
+ local ret = ffi.new('char[?]', len, {0})
+ local total_bytes_read = 0
+ ffi.errno(0)
+ while total_bytes_read < len do
+ local bytes_read = tonumber(ffi.C.read(
+ rd,
+ ffi.cast('void*', ret + total_bytes_read),
+ len - total_bytes_read))
+ if bytes_read == -1 then
local err = ffi.errno(0)
- assert(res == 0, ("pipe() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- assert(ret[0] ~= -1 and ret[1] ~= -1)
- return ret[0], ret[1]
- end,
- read = function(rd, len)
- local ret = ffi.new('char[?]', len, {0})
- local total_bytes_read = 0
- ffi.errno(0)
- while total_bytes_read < len do
- local bytes_read = tonumber(ffi.C.read(
- rd,
- ffi.cast('void*', ret + total_bytes_read),
- len - total_bytes_read))
- if bytes_read == -1 then
- local err = ffi.errno(0)
- if err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("read() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- elseif bytes_read == 0 then
- break
- else
- total_bytes_read = total_bytes_read + bytes_read
+ if err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("read() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
end
+ elseif bytes_read == 0 then
+ break
+ else
+ total_bytes_read = total_bytes_read + bytes_read
end
- return ffi.string(ret, total_bytes_read)
- end,
- write = function(wr, s)
- local wbuf = to_cstr(s)
- local total_bytes_written = 0
- ffi.errno(0)
- while total_bytes_written < #s do
- local bytes_written = tonumber(ffi.C.write(
- wr,
- ffi.cast('void*', wbuf + total_bytes_written),
- #s - total_bytes_written))
- if bytes_written == -1 then
- local err = ffi.errno(0)
- if err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("write() error: %u: %s ('%s')"):format(
- err, ffi.string(ffi.C.strerror(err)), s))
- end
- elseif bytes_written == 0 then
- break
- else
- total_bytes_written = total_bytes_written + bytes_written
+ end
+ return ffi.string(ret, total_bytes_read)
+end
+
+function sc.write(wr, s)
+ local wbuf = to_cstr(s)
+ local total_bytes_written = 0
+ ffi.errno(0)
+ while total_bytes_written < #s do
+ local bytes_written = tonumber(ffi.C.write(
+ wr,
+ ffi.cast('void*', wbuf + total_bytes_written),
+ #s - total_bytes_written))
+ if bytes_written == -1 then
+ local err = ffi.errno(0)
+ if err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("write() error: %u: %s ('%s')"):format(
+ err, ffi.string(ffi.C.strerror(err)), s))
end
+ elseif bytes_written == 0 then
+ break
+ else
+ total_bytes_written = total_bytes_written + bytes_written
end
- return total_bytes_written
- end,
- close = ffi.C.close,
- wait = function(pid)
- ffi.errno(0)
- local stat_loc = ffi.new('int[1]', {0})
- while true do
- local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED)
- if r == -1 then
- local err = ffi.errno(0)
- if err == ffi.C.kPOSIXErrnoECHILD then
- break
- elseif err ~= ffi.C.kPOSIXErrnoEINTR then
- assert(false, ("waitpid() error: %u: %s"):format(
- err, ffi.string(ffi.C.strerror(err))))
- end
- else
- assert(r == pid)
+ end
+ return total_bytes_written
+end
+
+sc.close = ffi.C.close
+
+--- @param pid integer
+--- @return integer
+function sc.wait(pid)
+ ffi.errno(0)
+ local stat_loc = ffi.new('int[1]', {0})
+ while true do
+ local r = ffi.C.waitpid(pid, stat_loc, ffi.C.kPOSIXWaitWUNTRACED)
+ if r == -1 then
+ local err = ffi.errno(0)
+ if err == ffi.C.kPOSIXErrnoECHILD then
+ break
+ elseif err ~= ffi.C.kPOSIXErrnoEINTR then
+ assert(false, ("waitpid() error: %u: %s"):format(
+ err, ffi.string(ffi.C.strerror(err))))
end
+ else
+ assert(r == pid)
end
- return stat_loc[0]
- end,
- exit = ffi.C._exit,
-}
+ end
+ return stat_loc[0]
+end
+sc.exit = ffi.C._exit
+
+--- @param lst string[]
+--- @return string
local function format_list(lst)
- local ret = ''
+ local ret = {} --- @type string[]
for _, v in ipairs(lst) do
- if ret ~= '' then ret = ret .. ', ' end
- ret = ret .. assert:format({v, n=1})[1]
+ ret[#ret+1] = assert:format({v, n=1})[1]
end
- return ret
+ return table.concat(ret, ', ')
end
if os.getenv('NVIM_TEST_PRINT_SYSCALLS') == '1' then
@@ -509,19 +539,26 @@ local tracehelp = dedent([[
]])
local function child_sethook(wr)
- local trace_level = os.getenv('NVIM_TEST_TRACE_LEVEL')
- if not trace_level or trace_level == '' then
- trace_level = 0
- else
- trace_level = tonumber(trace_level)
+ local trace_level_str = os.getenv('NVIM_TEST_TRACE_LEVEL')
+ local trace_level = 0
+ if trace_level_str and trace_level_str ~= '' then
+ --- @type number
+ trace_level = assert(tonumber(trace_level_str))
end
+
if trace_level <= 0 then
return
end
+
local trace_only_c = trace_level <= 1
+ --- @type debuginfo?, string?, integer
local prev_info, prev_reason, prev_lnum
+
+ --- @param reason string
+ --- @param lnum integer
+ --- @param use_prev boolean
local function hook(reason, lnum, use_prev)
- local info = nil
+ local info = nil --- @type debuginfo?
if use_prev then
info = prev_info
elseif reason ~= 'tail return' then -- tail return
@@ -529,6 +566,7 @@ local function child_sethook(wr)
end
if trace_only_c and (not info or info.what ~= 'C') and not use_prev then
+ --- @cast info -nil
if info.source:sub(-9) == '_spec.lua' then
prev_info = info
prev_reason = 'saved'
@@ -569,12 +607,8 @@ local function child_sethook(wr)
end
-- assert(-1 <= lnum and lnum <= 99999)
- local lnum_s
- if lnum == -1 then
- lnum_s = 'nknwn'
- else
- lnum_s = ('%u'):format(lnum)
- end
+ local lnum_s = lnum == -1 and 'nknwn' or ('%u'):format(lnum)
+ --- @type string
local msg = ( -- lua does not support %*
''
.. msgchar
@@ -596,6 +630,7 @@ end
local trace_end_msg = ('E%s\n'):format((' '):rep(hook_msglen - 2))
+--- @type function
local _debug_log
local debug_log = only_separate(function(...)
@@ -603,6 +638,7 @@ local debug_log = only_separate(function(...)
end)
local function itp_child(wr, func)
+ --- @param s string
_debug_log = function(s)
s = s:sub(1, hook_msglen - 2)
sc.write(wr, '>' .. s .. (' '):rep(hook_msglen - 2 - #s) .. '\n')
@@ -634,7 +670,7 @@ local function itp_child(wr, func)
end
local function check_child_err(rd)
- local trace = {}
+ local trace = {} --- @type string[]
local did_traceline = false
local maxtrace = tonumber(os.getenv('NVIM_TEST_MAXTRACE')) or 1024
while true do
@@ -664,11 +700,14 @@ local function check_child_err(rd)
local len = tonumber(len_s)
neq(0, len)
if os.getenv('NVIM_TEST_TRACE_ON_ERROR') == '1' and #trace ~= 0 then
+ --- @type string
err = '\nTest failed, trace:\n' .. tracehelp
for _, traceline in ipairs(trace) do
+ --- @type string
err = err .. traceline
end
end
+ --- @type string
err = err .. sc.read(rd, len + 1)
end
local eres = sc.read(rd, 2)
@@ -682,10 +721,12 @@ local function check_child_err(rd)
end
end
if not did_traceline then
+ --- @type string
err = err .. '\nNo end of trace occurred'
end
local cc_err, cc_emsg = pcall(check_cores, Paths.test_luajit_prg, true)
if not cc_err then
+ --- @type string
err = err .. '\ncheck_cores failed: ' .. cc_emsg
end
end
@@ -810,6 +851,17 @@ local function ptr2key(ptr)
return ffi.string(s)
end
+local function is_asan()
+ cimport('./src/nvim/version.h')
+ local status, res = pcall(function() return lib.version_cflags end)
+ if status then
+ return ffi.string(res):match('-fsanitize=[a-z,]*address')
+ else
+ return false
+ end
+end
+
+--- @class test.unit.helpers.module
local module = {
cimport = cimport,
cppimport = cppimport,
@@ -818,9 +870,9 @@ local module = {
lib = lib,
cstr = cstr,
to_cstr = to_cstr,
- NULL = NULL,
- OK = OK,
- FAIL = FAIL,
+ NULL = ffi.cast('void*', 0),
+ OK = 1,
+ FAIL = 0,
alloc_log_new = alloc_log_new,
gen_itp = gen_itp,
only_separate = only_separate,
@@ -837,8 +889,11 @@ local module = {
ptr2addr = ptr2addr,
ptr2key = ptr2key,
debug_log = debug_log,
+ is_asan = is_asan,
}
+--- @class test.unit.helpers: test.unit.helpers.module, test.helpers
module = global_helpers.tbl_extend('error', module, global_helpers)
+
return function()
return module
end
diff --git a/test/unit/keycodes_spec.lua b/test/unit/keycodes_spec.lua
index 5bf27c9232..4da3d37aaa 100644
--- a/test/unit/keycodes_spec.lua
+++ b/test/unit/keycodes_spec.lua
@@ -5,7 +5,7 @@ local ffi = helpers.ffi
local eq = helpers.eq
local neq = helpers.neq
-local keymap = helpers.cimport('./src/nvim/keycodes.h')
+local keycodes = helpers.cimport('./src/nvim/keycodes.h')
local NULL = helpers.NULL
describe('keycodes.c', function()
@@ -16,12 +16,12 @@ describe('keycodes.c', function()
itp('no keycode', function()
srcp[0] = 'abc'
- eq(0, keymap.find_special_key(srcp, 3, modp, 0, NULL))
+ eq(0, keycodes.find_special_key(srcp, 3, modp, 0, NULL))
end)
itp('keycode with multiple modifiers', function()
srcp[0] = '<C-M-S-A>'
- neq(0, keymap.find_special_key(srcp, 9, modp, 0, NULL))
+ neq(0, keycodes.find_special_key(srcp, 9, modp, 0, NULL))
neq(0, modp[0])
end)
@@ -29,22 +29,22 @@ describe('keycodes.c', function()
-- Compare other capitalizations to this.
srcp[0] = '<C-A>'
local all_caps_key =
- keymap.find_special_key(srcp, 5, modp, 0, NULL)
+ keycodes.find_special_key(srcp, 5, modp, 0, NULL)
local all_caps_mod = modp[0]
srcp[0] = '<C-a>'
eq(all_caps_key,
- keymap.find_special_key(srcp, 5, modp, 0, NULL))
+ keycodes.find_special_key(srcp, 5, modp, 0, NULL))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-A>'
eq(all_caps_key,
- keymap.find_special_key(srcp, 5, modp, 0, NULL))
+ keycodes.find_special_key(srcp, 5, modp, 0, NULL))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-a>'
eq(all_caps_key,
- keymap.find_special_key(srcp, 5, modp, 0, NULL))
+ keycodes.find_special_key(srcp, 5, modp, 0, NULL))
eq(all_caps_mod, modp[0])
end)
@@ -52,20 +52,20 @@ describe('keycodes.c', function()
-- Unescaped with in_string=false
srcp[0] = '<C-">'
eq(string.byte('"'),
- keymap.find_special_key(srcp, 5, modp, 0, NULL))
+ keycodes.find_special_key(srcp, 5, modp, 0, NULL))
-- Unescaped with in_string=true
- eq(0, keymap.find_special_key(srcp, 5, modp, keymap.FSK_IN_STRING, NULL))
+ eq(0, keycodes.find_special_key(srcp, 5, modp, keycodes.FSK_IN_STRING, NULL))
-- Escaped with in_string=false
srcp[0] = '<C-\\">'
-- Should fail because the key is invalid
-- (more than 1 non-modifier character).
- eq(0, keymap.find_special_key(srcp, 6, modp, 0, NULL))
+ eq(0, keycodes.find_special_key(srcp, 6, modp, 0, NULL))
-- Escaped with in_string=true
eq(string.byte('"'),
- keymap.find_special_key(srcp, 6, modp, keymap.FSK_IN_STRING, NULL))
+ keycodes.find_special_key(srcp, 6, modp, keycodes.FSK_IN_STRING, NULL))
end)
end)
diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua
index 3c96bc5f58..3f9dd4df12 100644
--- a/test/unit/marktree_spec.lua
+++ b/test/unit/marktree_spec.lua
@@ -87,13 +87,18 @@ local function dosplice(tree, shadow, start, old_extent, new_extent)
shadowsplice(shadow, start, old_extent, new_extent)
end
+local ns = 10
local last_id = nil
-local function put(tree, row, col, gravitate)
+local function put(tree, row, col, gravitate, end_row, end_col, end_gravitate)
last_id = last_id + 1
local my_id = last_id
- lib.marktree_put_test(tree, my_id, row, col, gravitate);
+ end_row = end_row or -1
+ end_col = end_col or -1
+ end_gravitate = end_gravitate or false
+
+ lib.marktree_put_test(tree, ns, my_id, row, col, gravitate, end_row, end_col, end_gravitate);
return my_id
end
@@ -102,7 +107,7 @@ describe('marktree', function()
last_id = 0
end)
- itp('works', function()
+ itp('works', function()
local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
local shadow = {}
local iter = ffi.new("MarkTreeIter[1]")
@@ -129,7 +134,7 @@ describe('marktree', function()
eq({}, id2pos)
for i,ipos in pairs(shadow) do
- local p = lib.marktree_lookup_ns(tree, -1, i, false, iter)
+ local p = lib.marktree_lookup_ns(tree, ns, i, false, iter)
eq(ipos[1], p.pos.row)
eq(ipos[2], p.pos.col)
local k = lib.marktree_itr_current(iter)
@@ -210,10 +215,330 @@ describe('marktree', function()
lib.marktree_itr_get(tree, 10, 10, iter)
lib.marktree_del_itr(tree, iter, false)
- eq(11, iter[0].node.key[iter[0].i].pos.col)
+ eq(11, iter[0].x.key[iter[0].i].pos.col)
lib.marktree_itr_get(tree, 11, 11, iter)
lib.marktree_del_itr(tree, iter, false)
- eq(12, iter[0].node.key[iter[0].i].pos.col)
- end)
+ eq(12, iter[0].x.key[iter[0].i].pos.col)
+ end)
+
+ itp("'intersect_mov' function works correctly", function()
+ local function mov(x, y, w)
+ local xa = ffi.new("uint64_t[?]", #x)
+ for i, xi in ipairs(x) do xa[i-1] = xi end
+ local ya = ffi.new("uint64_t[?]", #y)
+ for i, yi in ipairs(y) do ya[i-1] = yi end
+ local wa = ffi.new("uint64_t[?]", #w)
+ for i, wi in ipairs(w) do wa[i-1] = wi end
+
+ local dummy_size = #x + #y + #w
+ local wouta = ffi.new("uint64_t[?]", dummy_size)
+ local douta = ffi.new("uint64_t[?]", dummy_size)
+ local wsize = ffi.new("size_t[1]")
+ wsize[0] = dummy_size
+ local dsize = ffi.new("size_t[1]")
+ dsize[0] = dummy_size
+
+ local status = lib.intersect_mov_test(xa, #x, ya, #y, wa, #w, wouta, wsize, douta, dsize)
+ if status == 0 then error'wowza' end
+
+ local wout, dout = {}, {}
+ for i = 0,tonumber(wsize[0])-1 do table.insert(wout, tonumber(wouta[i])) end
+ for i = 0,tonumber(dsize[0])-1 do table.insert(dout, tonumber(douta[i])) end
+ return {wout, dout}
+ end
+
+ eq({{}, {}}, mov({}, {2, 3}, {2, 3}))
+ eq({{2, 3}, {}}, mov({}, {}, {2, 3}))
+ eq({{2, 3}, {}}, mov({2, 3}, {}, {}))
+ eq({{}, {2,3}}, mov({}, {2,3}, {}))
+
+ eq({{1, 5}, {}}, mov({1,2,5}, {2, 3}, {3}))
+ eq({{1, 2}, {}}, mov({1,2,5}, {5, 10}, {10}))
+ eq({{1, 2}, {5}}, mov({1,2}, {5, 10}, {10}))
+ eq({{1,3,5,7,9}, {2,4,6,8,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {}))
+ eq({{1,3,5,7,9}, {2,6,10}}, mov({1,3,5,7,9}, {2,4,6,8,10}, {4, 8}))
+ eq({{1,4,7}, {2,5,8}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {}))
+ eq({{1,4,7}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {2,5,8}))
+ eq({{0,1,4,7,10}, {}}, mov({1,3,4,6,7,9}, {2,3,5,6,8,9}, {0,2,5,8,10}))
+ end)
+
+
+ local function check_intersections(tree)
+ lib.marktree_check(tree)
+ -- to debug stuff disable this branch
+ if true == true then
+ ok(lib.marktree_check_intersections(tree))
+ return
+ end
+
+ local str1 = lib.mt_inspect(tree, true, true)
+ local dot1 = ffi.string(str1.data, str1.size)
+
+ local val = lib.marktree_check_intersections(tree)
+ if not val then
+ local str2 = lib.mt_inspect(tree, true, true)
+ local dot2 = ffi.string(str2.data, str2.size)
+ print("actual:\n\n".."Xafile.dot".."\n\nexpected:\n\n".."Xefile.dot".."\n")
+ print("nivå", tree[0].root.level);
+ io.stdout:flush()
+ local afil = io.open("Xafile.dot", "wb")
+ afil:write(dot1)
+ afil:close()
+ local efil = io.open("Xefile.dot", "wb")
+ efil:write(dot2)
+ efil:close()
+ ok(false)
+ else
+ ffi.C.xfree(str1.data)
+ end
+ end
+
+ itp('works with intersections', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ local ids = {}
+
+ for i = 1,80 do
+ table.insert(ids, put(tree, 1, i, false, 2, 100-i, false))
+ check_intersections(tree)
+ end
+ for i = 1,80 do
+ lib.marktree_del_pair_test(tree, ns, ids[i])
+ check_intersections(tree)
+ end
+ ids = {}
+
+ for i = 1,80 do
+ table.insert(ids, put(tree, 1, i, false, 2, 100-i, false))
+ check_intersections(tree)
+ end
+
+ for i = 1,10 do
+ for j = 1,8 do
+ local ival = (j-1)*10+i
+ lib.marktree_del_pair_test(tree, ns, ids[ival])
+ check_intersections(tree)
+ end
+ end
+ end)
+
+ itp('works with intersections with a big tree', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ local ids = {}
+
+ for i = 1,1000 do
+ table.insert(ids, put(tree, 1, i, false, 2, 1000-i, false))
+ if i % 10 == 1 then
+ check_intersections(tree)
+ end
+ end
+
+ check_intersections(tree)
+ eq(2000, tree[0].n_keys)
+ ok(tree[0].root.level >= 2)
+
+ local iter = ffi.new("MarkTreeIter[1]")
+
+ local k = 0
+ for i = 1,20 do
+ for j = 1,50 do
+ k = k + 1
+ local ival = (j-1)*20+i
+ if false == true then -- if there actually is a failure, this branch will fail out at the actual spot of the error
+ lib.marktree_lookup_ns(tree, ns, ids[ival], false, iter)
+ lib.marktree_del_itr(tree, iter, false)
+ check_intersections(tree)
+
+ lib.marktree_lookup_ns(tree, ns, ids[ival], true, iter)
+ lib.marktree_del_itr(tree, iter, false)
+ check_intersections(tree)
+ else
+ lib.marktree_del_pair_test(tree, ns, ids[ival])
+ if k % 5 == 1 then
+ check_intersections(tree)
+ end
+ end
+ end
+ end
+
+ eq(0, tree[0].n_keys)
+ end)
+
+ itp('works with intersections and marktree_splice', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ for i = 1,1000 do
+ put(tree, 1, i, false, 2, 1000-i, false)
+ if i % 10 == 1 then
+ check_intersections(tree)
+ end
+ end
+
+ check_intersections(tree)
+ eq(2000, tree[0].n_keys)
+ ok(tree[0].root.level >= 2)
+
+ for _ = 1,10 do
+ lib.marktree_splice(tree, 0, 0, 0, 100, 0, 0)
+ check_intersections(tree)
+ end
+ end)
+
+ itp('marktree_move should preserve key order', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+ local iter = ffi.new("MarkTreeIter[1]")
+ local ids = {}
+
+ -- new index and old index look the same, but still have to move because
+ -- pos will get updated
+ table.insert(ids, put(tree, 1, 1, false, 1, 3, false))
+ table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
+ table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
+ table.insert(ids, put(tree, 1, 3, false, 1, 3, false))
+
+ lib.marktree_lookup_ns(tree, ns, ids[3], false, iter)
+ lib.marktree_move(tree, iter, 1, 2)
+
+ check_intersections(tree)
+ end)
+
+ itp('works with intersections and marktree_move', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ local ids = {}
+
+ for i = 1,1000 do
+ table.insert(ids, put(tree, 1, i, false, 2, 1000-i, false))
+ if i % 10 == 1 then
+ check_intersections(tree)
+ end
+ end
+
+ local iter = ffi.new("MarkTreeIter[1]")
+ for i = 1,1000 do
+ local which = i%2
+ lib.marktree_lookup_ns(tree, ns, ids[i], which, iter)
+ lib.marktree_move(tree, iter, 1+which, 500+i)
+ if i % 10 == 1 then
+ check_intersections(tree)
+ end
+ end
+
+ end)
+
+ itp('works with intersections with a even bigger tree', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ local ids = {}
+
+ -- too much overhead on ASAN
+ local size_factor = helpers.is_asan() and 3 or 10
+
+ local at_row = {}
+ for i = 1, 10 do
+ at_row[i] = {}
+ end
+
+ local size = 1000*size_factor
+ local k = 1
+ while k <= size do
+ for row1 = 1,9 do
+ for row2 = row1,10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2
+ if k > size then
+ break
+ end
+ local id = put(tree, row1, k, false, row2, size-k, false)
+ table.insert(ids, id)
+ for i = row1+1, row2 do
+ table.insert(at_row[i], id)
+ end
+ --if tree[0].root.level == 4 then error("kk"..k) end
+ if k % 100*size_factor == 1 or (k < 2000 and k%100 == 1) then
+ check_intersections(tree)
+ end
+ k = k + 1
+ end
+ end
+ end
+
+ eq(2*size, tree[0].n_keys)
+ ok(tree[0].root.level >= 3)
+ check_intersections(tree)
+
+ local iter = ffi.new("MarkTreeIter[1]")
+ local pair = ffi.new("MTPair[1]")
+ for i = 1,10 do
+ -- use array as set and not {[id]=true} map, to detect duplicates
+ local set = {}
+ eq(true, ffi.C.marktree_itr_get_overlap(tree, i, 0, iter))
+ while ffi.C.marktree_itr_step_overlap(tree, iter, pair) do
+ local id = tonumber(pair[0].start.id)
+ table.insert(set, id)
+ end
+ table.sort(set)
+ eq(at_row[i], set)
+ end
+
+ k = 0
+ for i = 1,100 do
+ for j = 1,(10*size_factor) do
+ k = k + 1
+ local ival = (j-1)*100+i
+ lib.marktree_del_pair_test(tree, ns, ids[ival])
+ -- just a few stickprov, if there is trouble we need to check
+ -- everyone using the code in the "big tree" case above
+ if k % 100*size_factor == 0 or (k > 3000 and k % 200 == 0) then
+ check_intersections(tree)
+ end
+ end
+ end
+
+ eq(0, tree[0].n_keys)
+ end)
+
+ itp('works with intersections with a even bigger tree and splice', function()
+ local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit
+
+ -- too much overhead on ASAN
+ local size_factor = helpers.is_asan() and 3 or 10
+
+ local at_row = {}
+ for i = 1, 10 do
+ at_row[i] = {}
+ end
+
+ local size = 1000*size_factor
+ local k = 1
+ while k <= size do
+ for row1 = 1,9 do
+ for row2 = row1,10 do -- note row2 can be == row1, leads to empty ranges being tested when k > size/2
+ if k > size then
+ break
+ end
+ local id = put(tree, row1, k, false, row2, size-k, false)
+ for i = row1+1, row2 do
+ table.insert(at_row[i], id)
+ end
+ --if tree[0].root.level == 4 then error("kk"..k) end
+ if k % 100*size_factor == 1 or (k < 2000 and k%100 == 1) then
+ check_intersections(tree)
+ end
+ k = k + 1
+ end
+ end
+ end
+
+ eq(2*size, tree[0].n_keys)
+ ok(tree[0].root.level >= 3)
+ check_intersections(tree)
+
+ for _ = 1,10 do
+ for j = 3, 8 do
+ lib.marktree_splice(tree, j, 0, 0, 200, 0, 0)
+ check_intersections(tree)
+ end
+ end
+ end)
end)
diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua
index fdb1bceab0..cd94624570 100644
--- a/test/unit/mbyte_spec.lua
+++ b/test/unit/mbyte_spec.lua
@@ -4,17 +4,9 @@ local itp = helpers.gen_itp(it)
local ffi = helpers.ffi
local eq = helpers.eq
-local mbyte = helpers.cimport("./src/nvim/mbyte.h")
-local charset = helpers.cimport('./src/nvim/charset.h')
+local lib = helpers.cimport('./src/nvim/mbyte.h', './src/nvim/charset.h', './src/nvim/grid.h')
describe('mbyte', function()
- -- Array for composing characters
- local intp = ffi.typeof('int[?]')
- local function to_intp()
- -- how to get MAX_MCO from globals.h?
- return intp(7, 1)
- end
-
-- Convert from bytes to string
local function to_string(bytes)
local s = {}
@@ -30,14 +22,14 @@ describe('mbyte', function()
itp('utf_ptr2char', function()
-- For strings with length 1 the first byte is returned.
for c = 0, 255 do
- eq(c, mbyte.utf_ptr2char(to_string({c, 0})))
+ eq(c, lib.utf_ptr2char(to_string({c, 0})))
end
-- Some ill formed byte sequences that should not be recognized as UTF-8
-- First byte: 0xc0 or 0xc1
-- Second byte: 0x80 .. 0xbf
- --eq(0x00c0, mbyte.utf_ptr2char(to_string({0xc0, 0x80})))
- --eq(0x00c1, mbyte.utf_ptr2char(to_string({0xc1, 0xbf})))
+ --eq(0x00c0, lib.utf_ptr2char(to_string({0xc0, 0x80})))
+ --eq(0x00c1, lib.utf_ptr2char(to_string({0xc1, 0xbf})))
--
-- Sequences with more than four bytes
end)
@@ -47,240 +39,133 @@ describe('mbyte', function()
local char_p = ffi.typeof('char[?]')
for c = n * 0x1000, n * 0x1000 + 0xFFF do
local p = char_p(4, 0)
- mbyte.utf_char2bytes(c, p)
- eq(c, mbyte.utf_ptr2char(p))
- eq(charset.vim_iswordc(c), charset.vim_iswordp(p))
+ lib.utf_char2bytes(c, p)
+ eq(c, lib.utf_ptr2char(p))
+ eq(lib.vim_iswordc(c), lib.vim_iswordp(p))
end
end)
end
- describe('utfc_ptr2char_len', function()
+ describe('utfc_ptr2schar_len', function()
+ local function test_seq(seq)
+ local firstc = ffi.new("int[1]")
+ local buf = ffi.new("char[32]")
+ lib.schar_get(buf, lib.utfc_ptr2schar_len(to_string(seq), #seq, firstc))
+ return {ffi.string(buf), firstc[0]}
+ end
+
+ local function byte(val)
+ return {string.char(val), val}
+ end
itp('1-byte sequences', function()
- local pcc = to_intp()
- for c = 0, 255 do
- eq(c, mbyte.utfc_ptr2char_len(to_string({c}), pcc, 1))
- eq(0, pcc[0])
+ eq({'', 0}, test_seq{0})
+ for c = 1, 127 do
+ eq(byte(c), test_seq{c})
+ end
+ for c = 128, 255 do
+ eq({'', c}, test_seq{c})
end
end)
itp('2-byte sequences', function()
- local pcc = to_intp()
-- No combining characters
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f}), pcc, 2))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f})
-- No combining characters
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x80}), pcc, 2))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f}), pcc, 2))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80}), pcc, 2))
- eq(0, pcc[0])
+ eq({'\xc2\x80', 0x80}, test_seq{0xc2, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0xc0}), pcc, 2))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0xc0})
end)
itp('3-byte sequences', function()
- local pcc = to_intp()
-
-- No second UTF-8 character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x80, 0x80}), pcc, 3))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x80, 0x80})
-- No combining character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0x80}), pcc, 3))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0x80})
-- Combining character is U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80}), pcc, 3))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc}), pcc, 3))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc})
-- Incomplete combining character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc}), pcc, 3))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc})
- -- One UTF-8 character
- pcc = to_intp()
- eq(0x20d0, mbyte.utfc_ptr2char_len(to_string({0xe2, 0x83, 0x90}), pcc, 3))
- eq(0, pcc[0])
+ -- One UTF-8 character (composing only)
+ eq({" \xe2\x83\x90", 0x20d0}, test_seq{0xe2, 0x83, 0x90})
end)
itp('4-byte sequences', function()
- local pcc = to_intp()
-- No following combining character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f, 0xcc, 0x80})
-- No second UTF-8 character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0xcc, 0x80})
-- Combining character U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc}), pcc, 4))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc, 0x80})
-- No following UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0xcc}), pcc, 4))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0xcc})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81}), pcc, 4))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80}), pcc, 4))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80})
end)
itp('5+-byte sequences', function()
- local pcc = to_intp()
-
-- No following combining character
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0x7f, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0x7f, 0xcc, 0x80, 0x80})
-- No second UTF-8 character
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xc2, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq(byte(0x7f), test_seq{0x7f, 0xc2, 0xcc, 0x80, 0x80})
-- Combining character U+0300
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc}), pcc, 5))
- eq(0x0300, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\x7f\xcc\x80", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x00})
-- Combining characters U+0300 and U+0301
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81}), pcc, 5))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0000, pcc[2])
+ eq({"\x7f\xcc\x80\xcc\x81", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81})
-- Combining characters U+0300, U+0301, U+0302
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82}), pcc, 7))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0000, pcc[3])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82})
-- Combining characters U+0300, U+0301, U+0302, U+0303
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string({0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83}), pcc, 9))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0000, pcc[4])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83})
-- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84}), pcc, 11))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0000, pcc[5])
- -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304,
- -- U+0305
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85}), pcc, 13))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0305, pcc[5])
- eq(1, pcc[6])
-
- -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304,
- -- U+0305, U+0306, but only save six (= MAX_MCO).
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85, 0xcc, 0x86}), pcc, 15))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0303, pcc[3])
- eq(0x0304, pcc[4])
- eq(0x0305, pcc[5])
- eq(0x0001, pcc[6])
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84})
+ -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85})
- -- Only three following combining characters U+0300, U+0301, U+0302
- pcc = to_intp()
- eq(0x007f, mbyte.utfc_ptr2char_len(to_string(
- {0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85}), pcc, 13))
- eq(0x0300, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0302, pcc[2])
- eq(0x0000, pcc[3])
+ -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305, U+0306
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85, 0xcc, 0x86})
+ -- Only three following combining characters U+0300, U+0301, U+0302
+ eq({"\x7f\xcc\x80\xcc\x81\xcc\x82", 0x7f}, test_seq{0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85})
-- No UTF-8 sequence
- pcc = to_intp()
- eq(0x00c2, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x7f, 0xcc, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({'', 0xc2}, test_seq{0xc2, 0x7f, 0xcc, 0x80, 0x80})
-- No following UTF-8 character
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0xcc, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xc2\x80", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0xcc, 0x80})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81, 0x7f}), pcc, 5))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81, 0x7f})
-- Combining character U+0301
- pcc = to_intp()
- eq(0x0080, mbyte.utfc_ptr2char_len(to_string({0xc2, 0x80, 0xcc, 0x81, 0xcc}), pcc, 5))
- eq(0x0301, pcc[0])
- eq(0x0000, pcc[1])
+ eq({"\xc2\x80\xcc\x81", 0x80}, test_seq{0xc2, 0x80, 0xcc, 0x81, 0xcc})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0x7f}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0x7f})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0x80}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0x80})
-- One UTF-8 character
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string({0xf4, 0x80, 0x80, 0x80, 0xcc}), pcc, 5))
- eq(0, pcc[0])
+ eq({"\xf4\x80\x80\x80", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0xcc})
-- Combining characters U+1AB0 and U+0301
- pcc = to_intp()
- eq(0x100000, mbyte.utfc_ptr2char_len(to_string(
- {0xf4, 0x80, 0x80, 0x80, 0xe1, 0xaa, 0xb0, 0xcc, 0x81}), pcc, 9))
- eq(0x1ab0, pcc[0])
- eq(0x0301, pcc[1])
- eq(0x0000, pcc[2])
+ eq({"\xf4\x80\x80\x80\xe1\xaa\xb0\xcc\x81", 0x100000}, test_seq{0xf4, 0x80, 0x80, 0x80, 0xe1, 0xaa, 0xb0, 0xcc, 0x81})
end)
end)
diff --git a/test/unit/message_spec.lua b/test/unit/message_spec.lua
index 549eff6e03..0d5268d199 100644
--- a/test/unit/message_spec.lua
+++ b/test/unit/message_spec.lua
@@ -12,7 +12,7 @@ describe('trunc_string', function()
local buflen = 40
local function test_inplace(s, expected, room)
room = room and room or 20
- local buf = cimp.xmalloc(ffi.sizeof('char_u') * buflen)
+ local buf = cimp.xmalloc(ffi.sizeof('char') * buflen)
ffi.C.strcpy(buf, s)
cimp.trunc_string(buf, buf, room, buflen)
eq(expected, ffi.string(buf))
@@ -21,7 +21,7 @@ describe('trunc_string', function()
local function test_copy(s, expected, room)
room = room and room or 20
- local buf = cimp.xmalloc(ffi.sizeof('char_u') * buflen)
+ local buf = cimp.xmalloc(ffi.sizeof('char') * buflen)
local str = cimp.xstrdup(to_cstr(s))
cimp.trunc_string(str, buf, room, buflen)
eq(expected, ffi.string(buf))
diff --git a/test/unit/msgpack_spec.lua b/test/unit/msgpack_spec.lua
new file mode 100644
index 0000000000..c573714714
--- /dev/null
+++ b/test/unit/msgpack_spec.lua
@@ -0,0 +1,75 @@
+local helpers = require('test.unit.helpers')(after_each)
+local cimport = helpers.cimport
+local itp = helpers.gen_itp(it)
+local lib = cimport('./src/nvim/msgpack_rpc/unpacker.h', './src/nvim/memory.h')
+local ffi = helpers.ffi
+local eq = helpers.eq
+local to_cstr = helpers.to_cstr
+
+--- @class Unpacker
+--- @field read_ptr ffi.cdata*
+--- @field read_size number
+
+--- @alias Unpacker* table<number, Unpacker>
+--- @return Unpacker* unpacker `unpacker[0]` to dereference
+local function make_unpacker()
+ return ffi.gc(ffi.cast('Unpacker*', lib.xcalloc(1, ffi.sizeof('Unpacker'))), function(unpacker)
+ lib.unpacker_teardown(unpacker, nil, nil)
+ lib.xfree(unpacker)
+ end)
+end
+
+--- @param unpacker Unpacker*
+--- @param data string
+--- @param size number? *default: data:len()*
+local function unpacker_goto(unpacker, data, size)
+ unpacker[0].read_ptr = to_cstr(data)
+ unpacker[0].read_size = size or data:len()
+end
+
+--- @param unpacker Unpacker*
+--- @return boolean
+local function unpacker_advance(unpacker)
+ return lib.unpacker_advance(unpacker)
+end
+
+describe('msgpack', function()
+ describe('unpacker', function()
+ itp('does not crash when paused between `cells` and `wrap` params of `grid_line` #25184', function()
+ -- [kMessageTypeNotification, "redraw", [
+ -- ["grid_line",
+ -- [2, 0, 0, [[" " , 0, 77]], false]
+ -- ]
+ -- ]]
+ local payload =
+ '\x93\x02\xa6\x72\x65\x64\x72\x61\x77\x91\x92\xa9\x67\x72\x69\x64\x5f\x6c\x69\x6e\x65\x95\x02\x00\x00\x91\x93\xa1\x20\x00\x4d\xc2'
+
+ local unpacker = make_unpacker()
+ lib.unpacker_init(unpacker)
+
+ unpacker_goto(unpacker, payload, payload:len() - 1)
+ local finished = unpacker_advance(unpacker)
+ eq(finished, false)
+
+ unpacker[0].read_size = unpacker[0].read_size + 1
+ finished = unpacker_advance(unpacker)
+ eq(finished, true)
+ end)
+
+ itp('does not crash when parsing grid_line event with 0 `cells` #25184', function()
+ local unpacker = make_unpacker()
+ lib.unpacker_init(unpacker)
+
+ unpacker_goto(unpacker,
+ -- [kMessageTypeNotification, "redraw", [
+ -- ["grid_line",
+ -- [2, 0, 0, [], false]
+ -- ]
+ -- ]]
+ '\x93\x02\xa6\x72\x65\x64\x72\x61\x77\x91\x92\xa9\x67\x72\x69\x64\x5f\x6c\x69\x6e\x65\x95\x02\x00\x00\x90\xc2'
+ )
+ local finished = unpacker_advance(unpacker)
+ eq(finished, true)
+ end)
+ end)
+end)
diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua
index f8122f4fe0..2e7198a63a 100644
--- a/test/unit/optionstr_spec.lua
+++ b/test/unit/optionstr_spec.lua
@@ -4,10 +4,10 @@ local itp = helpers.gen_itp(it)
local to_cstr = helpers.to_cstr
local eq = helpers.eq
-local option = helpers.cimport("./src/nvim/optionstr.h")
+local optionstr = helpers.cimport("./src/nvim/optionstr.h")
local check_ff_value = function(ff)
- return option.check_ff_value(to_cstr(ff))
+ return optionstr.check_ff_value(to_cstr(ff))
end
describe('check_ff_value', function()
diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua
index 71177f4c65..24b92edee5 100644
--- a/test/unit/os/env_spec.lua
+++ b/test/unit/os/env_spec.lua
@@ -10,8 +10,6 @@ local to_cstr = helpers.to_cstr
local NULL = helpers.NULL
local OK = 0
-require('lfs')
-
local cimp = cimport('./src/nvim/os/os.h')
describe('env.c', function()
diff --git a/test/unit/os/fileio_spec.lua b/test/unit/os/fileio_spec.lua
index 4d58a8934e..fd30ca70da 100644
--- a/test/unit/os/fileio_spec.lua
+++ b/test/unit/os/fileio_spec.lua
@@ -1,4 +1,4 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
@@ -7,6 +7,7 @@ local eq = helpers.eq
local ffi = helpers.ffi
local cimport = helpers.cimport
local cppimport = helpers.cppimport
+local mkdir = helpers.mkdir
local m = cimport('./src/nvim/os/os.h', './src/nvim/os/fileio.h')
cppimport('fcntl.h')
@@ -25,7 +26,7 @@ local linkb = dir .. '/broken.lnk'
local filec = dir .. '/created-file.dat'
before_each(function()
- lfs.mkdir(dir);
+ mkdir(dir);
local f1 = io.open(file1, 'w')
f1:write(fcontents)
@@ -35,8 +36,8 @@ before_each(function()
f2:write(fcontents)
f2:close()
- lfs.link('file1.dat', linkf, true)
- lfs.link('broken.dat', linkb, true)
+ luv.fs_symlink('file1.dat', linkf)
+ luv.fs_symlink('broken.dat', linkb)
end)
after_each(function()
@@ -45,7 +46,7 @@ after_each(function()
os.remove(linkf)
os.remove(linkb)
os.remove(filec)
- lfs.rmdir(dir)
+ luv.fs_rmdir(dir)
end)
local function file_open(fname, flags, mode)
@@ -119,13 +120,13 @@ describe('file_open_fd', function()
eq(0, m.file_close(fp, false))
end)
itp('can use file descriptor returned by os_open for writing', function()
- eq(nil, lfs.attributes(filec))
+ eq(nil, luv.fs_stat(filec))
local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
local err, fp = file_open_fd(fd, m.kFileWriteOnly)
eq(0, err)
eq(4, file_write(fp, 'test'))
eq(0, m.file_close(fp, false))
- eq(4, lfs.attributes(filec).size)
+ eq(4, luv.fs_stat(filec).size)
eq('test', io.open(filec):read('*a'))
end)
end)
@@ -139,13 +140,13 @@ describe('file_open_fd_new', function()
eq(0, m.file_free(fp, false))
end)
itp('can use file descriptor returned by os_open for writing', function()
- eq(nil, lfs.attributes(filec))
+ eq(nil, luv.fs_stat(filec))
local fd = m.os_open(filec, m.kO_WRONLY + m.kO_CREAT, 384)
local err, fp = file_open_fd_new(fd, m.kFileWriteOnly)
eq(0, err)
eq(4, file_write(fp, 'test'))
eq(0, m.file_free(fp, false))
- eq(4, lfs.attributes(filec).size)
+ eq(4, luv.fs_stat(filec).size)
eq('test', io.open(filec):read('*a'))
end)
end)
@@ -154,32 +155,32 @@ describe('file_open', function()
itp('can create a rwx------ file with kFileCreate', function()
local err, fp = file_open(filec, m.kFileCreate, 448)
eq(0, err)
- local attrs = lfs.attributes(filec)
- eq('rwx------', attrs.permissions)
+ local attrs = luv.fs_stat(filec)
+ eq(33216, attrs.mode)
eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreate', function()
local err, fp = file_open(filec, m.kFileCreate, 384)
eq(0, err)
- local attrs = lfs.attributes(filec)
- eq('rw-------', attrs.permissions)
+ local attrs = luv.fs_stat(filec)
+ eq(33152, attrs.mode)
eq(0, m.file_close(fp, false))
end)
itp('can create a rwx------ file with kFileCreateOnly', function()
local err, fp = file_open(filec, m.kFileCreateOnly, 448)
eq(0, err)
- local attrs = lfs.attributes(filec)
- eq('rwx------', attrs.permissions)
+ local attrs = luv.fs_stat(filec)
+ eq(33216, attrs.mode)
eq(0, m.file_close(fp, false))
end)
itp('can create a rw------- file with kFileCreateOnly', function()
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
eq(0, err)
- local attrs = lfs.attributes(filec)
- eq('rw-------', attrs.permissions)
+ local attrs = luv.fs_stat(filec)
+ eq(33152, attrs.mode)
eq(0, m.file_close(fp, false))
end)
@@ -228,7 +229,7 @@ describe('file_open', function()
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp, false))
- local attrs = lfs.attributes(file1)
+ local attrs = luv.fs_stat(file1)
eq(0, attrs.size)
end)
@@ -237,14 +238,14 @@ describe('file_open', function()
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp, false))
- local attrs = lfs.attributes(file1)
+ local attrs = luv.fs_stat(file1)
eq(4096, attrs.size)
end)
itp('fails to create a file with just kFileWriteOnly', function()
local err, _ = file_open(filec, m.kFileWriteOnly, 384)
eq(m.UV_ENOENT, err)
- local attrs = lfs.attributes(filec)
+ local attrs = luv.fs_stat(filec)
eq(nil, attrs)
end)
@@ -254,7 +255,7 @@ describe('file_open', function()
eq(0, err)
eq(true, fp.wr)
eq(0, m.file_close(fp, false))
- local attrs = lfs.attributes(file1)
+ local attrs = luv.fs_stat(file1)
eq(0, attrs.size)
end)
@@ -295,9 +296,9 @@ describe('file_close', function()
eq(0, err)
local wsize = file_write(fp, 'test')
eq(4, wsize)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
eq(0, m.file_close(fp, true))
- eq(wsize, lfs.attributes(filec).size)
+ eq(wsize, luv.fs_stat(filec).size)
end)
end)
@@ -307,9 +308,9 @@ describe('file_free', function()
eq(0, err)
local wsize = file_write(fp, 'test')
eq(4, wsize)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
eq(0, m.file_free(fp, true))
- eq(wsize, lfs.attributes(filec).size)
+ eq(wsize, luv.fs_stat(filec).size)
end)
end)
@@ -318,12 +319,12 @@ describe('file_fsync', function()
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
eq(0, file_fsync(fp))
eq(0, err)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
local wsize = file_write(fp, 'test')
eq(4, wsize)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
eq(0, file_fsync(fp))
- eq(wsize, lfs.attributes(filec).size)
+ eq(wsize, luv.fs_stat(filec).size)
eq(0, m.file_close(fp, false))
end)
end)
@@ -333,12 +334,12 @@ describe('file_flush', function()
local err, fp = file_open(filec, m.kFileCreateOnly, 384)
eq(0, file_flush(fp))
eq(0, err)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
local wsize = file_write(fp, 'test')
eq(4, wsize)
- eq(0, lfs.attributes(filec).size)
+ eq(0, luv.fs_stat(filec).size)
eq(0, file_flush(fp))
- eq(wsize, lfs.attributes(filec).size)
+ eq(wsize, luv.fs_stat(filec).size)
eq(0, m.file_close(fp, false))
end)
end)
@@ -412,7 +413,7 @@ describe('file_write', function()
local wr = file_write(fp, fcontents)
eq(#fcontents, wr)
eq(0, m.file_close(fp, false))
- eq(wr, lfs.attributes(filec).size)
+ eq(wr, luv.fs_stat(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@@ -429,7 +430,7 @@ describe('file_write', function()
shift = shift + size
end
eq(0, m.file_close(fp, false))
- eq(#fcontents, lfs.attributes(filec).size)
+ eq(#fcontents, luv.fs_stat(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
@@ -446,7 +447,7 @@ describe('file_write', function()
shift = shift + size
end
eq(0, m.file_close(fp, false))
- eq(#fcontents, lfs.attributes(filec).size)
+ eq(#fcontents, luv.fs_stat(filec).size)
eq(fcontents, io.open(filec):read('*a'))
end)
end)
diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua
index c718244ea4..8f45d2b0c7 100644
--- a/test/unit/os/fs_spec.lua
+++ b/test/unit/os/fs_spec.lua
@@ -1,4 +1,4 @@
-local lfs = require('lfs')
+local luv = require('luv')
local bit = require('bit')
local helpers = require('test.unit.helpers')(after_each)
@@ -16,14 +16,12 @@ local to_cstr = helpers.to_cstr
local OK = helpers.OK
local FAIL = helpers.FAIL
local NULL = helpers.NULL
+local mkdir = helpers.mkdir
+local endswith = helpers.endswith
local NODE_NORMAL = 0
local NODE_WRITABLE = 1
-cimport('./src/nvim/os/shell.h')
-cimport('./src/nvim/option_defs.h')
-cimport('./src/nvim/main.h')
-cimport('./src/nvim/fileio.h')
local fs = cimport('./src/nvim/os/os.h', './src/nvim/path.h')
cppimport('sys/stat.h')
cppimport('fcntl.h')
@@ -48,11 +46,11 @@ local function unset_bit(number, to_unset)
end
local function assert_file_exists(filepath)
- neq(nil, lfs.attributes(filepath))
+ neq(nil, luv.fs_stat(filepath))
end
local function assert_file_does_not_exist(filepath)
- eq(nil, lfs.attributes(filepath))
+ eq(nil, luv.fs_stat(filepath))
end
local function os_setperm(filename, perm)
@@ -70,14 +68,14 @@ describe('fs.c', function()
end
before_each(function()
- lfs.mkdir('unit-test-directory');
+ mkdir('unit-test-directory');
io.open('unit-test-directory/test.file', 'w'):close()
io.open('unit-test-directory/test_2.file', 'w'):close()
- lfs.link('test.file', 'unit-test-directory/test_link.file', true)
+ luv.fs_symlink('test.file', 'unit-test-directory/test_link.file')
- lfs.link('non_existing_file.file', 'unit-test-directory/test_broken_link.file', true)
+ luv.fs_symlink('non_existing_file.file', 'unit-test-directory/test_broken_link.file')
-- The tests are invoked with an absolute path to `busted` executable.
absolute_executable = arg[0]
-- Split the absolute_executable path into a directory and filename.
@@ -90,19 +88,19 @@ describe('fs.c', function()
os.remove('unit-test-directory/test_link.file')
os.remove('unit-test-directory/test_hlink.file')
os.remove('unit-test-directory/test_broken_link.file')
- lfs.rmdir('unit-test-directory')
+ luv.fs_rmdir('unit-test-directory')
end)
describe('os_dirname', function()
itp('returns OK and writes current directory to the buffer', function()
- local length = string.len(lfs.currentdir()) + 1
+ local length = string.len(luv.cwd()) + 1
local buf = cstr(length, '')
eq(OK, fs.os_dirname(buf, length))
- eq(lfs.currentdir(), ffi.string(buf))
+ eq(luv.cwd(), ffi.string(buf))
end)
itp('returns FAIL if the buffer is too small', function()
- local length = string.len(lfs.currentdir()) + 1
+ local length = string.len(luv.cwd()) + 1
local buf = cstr(length - 1, '')
eq(FAIL, fs.os_dirname(buf, length - 1))
end)
@@ -203,20 +201,20 @@ describe('fs.c', function()
end)
itp('returns the absolute path when given an executable relative to the current dir', function()
- local old_dir = lfs.currentdir()
+ local old_dir = luv.cwd()
- lfs.chdir(directory)
+ luv.chdir(directory)
-- Rely on currentdir to resolve symlinks, if any. Testing against
-- the absolute path taken from arg[0] may result in failure where
-- the path has a symlink in it.
- local canonical = lfs.currentdir() .. '/' .. executable_name
+ local canonical = luv.cwd() .. '/' .. executable_name
local expected = exe(canonical)
local relative_executable = './' .. executable_name
local res = exe(relative_executable)
-- Don't test yet; we need to chdir back first.
- lfs.chdir(old_dir)
+ luv.chdir(old_dir)
eq(expected, res)
end)
end)
@@ -278,11 +276,11 @@ describe('fs.c', function()
describe('os_fchown', function()
local filename = 'unit-test-directory/test.file'
itp('does not change owner and group if respective IDs are equal to -1', function()
- local uid = lfs.attributes(filename, 'uid')
- local gid = lfs.attributes(filename, 'gid')
+ local uid = luv.fs_stat(filename).uid
+ local gid = luv.fs_stat(filename).gid
eq(0, os_fchown(filename, -1, -1))
- eq(uid, lfs.attributes(filename, 'uid'))
- return eq(gid, lfs.attributes(filename, 'gid'))
+ eq(uid, luv.fs_stat(filename).uid)
+ return eq(gid, luv.fs_stat(filename).gid)
end)
-- Some systems may not have `id` utility.
@@ -290,7 +288,7 @@ describe('fs.c', function()
pending('skipped (missing `id` utility)', function() end)
else
itp('owner of a file may change the group of the file to any group of which that owner is a member', function()
- local file_gid = lfs.attributes(filename, 'gid')
+ local file_gid = luv.fs_stat(filename).gid
-- Gets ID of any group of which current user is a member except the
-- group that owns the file.
@@ -305,7 +303,7 @@ describe('fs.c', function()
-- In that case we can not perform this test.
if new_gid then
eq(0, (os_fchown(filename, -1, new_gid)))
- eq(new_gid, (lfs.attributes(filename, 'gid')))
+ eq(new_gid, luv.fs_stat(filename).gid)
end
end)
end
@@ -548,7 +546,7 @@ describe('fs.c', function()
--create the file
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("700", 8))
--verify permissions
- eq('rwx------', lfs.attributes(new_file)['permissions'])
+ eq(33216, luv.fs_stat(new_file).mode)
eq(0, os_close(fd))
end)
@@ -557,7 +555,7 @@ describe('fs.c', function()
--create the file
local fd = os_open(new_file, ffi.C.kO_CREAT, tonumber("600", 8))
--verify permissions
- eq('rw-------', lfs.attributes(new_file)['permissions'])
+ eq(33152, luv.fs_stat(new_file).mode)
eq(0, os_close(fd))
end)
@@ -747,12 +745,17 @@ describe('fs.c', function()
local function os_mkdir_recurse(path, mode)
local failed_str = ffi.new('char *[1]', {nil})
- local ret = fs.os_mkdir_recurse(path, mode, failed_str)
- local str = failed_str[0]
- if str ~= nil then
- str = ffi.string(str)
+ local created_str = ffi.new('char *[1]', {nil})
+ local ret = fs.os_mkdir_recurse(path, mode, failed_str, created_str)
+ local failed_dir = failed_str[0]
+ if failed_dir ~= nil then
+ failed_dir = ffi.string(failed_dir)
end
- return ret, str
+ local created_dir = created_str[0]
+ if created_dir ~= nil then
+ created_dir = ffi.string(created_dir)
+ end
+ return ret, failed_dir, created_dir
end
describe('os_mkdir', function()
@@ -766,81 +769,88 @@ describe('fs.c', function()
eq(false, (os_isdir('unit-test-directory/new-dir')))
eq(0, (os_mkdir('unit-test-directory/new-dir', mode)))
eq(true, (os_isdir('unit-test-directory/new-dir')))
- lfs.rmdir('unit-test-directory/new-dir')
+ luv.fs_rmdir('unit-test-directory/new-dir')
end)
end)
describe('os_mkdir_recurse', function()
itp('returns zero when given an already existing directory', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse('unit-test-directory', mode)
+ local ret, failed_dir, created_dir = os_mkdir_recurse('unit-test-directory', mode)
eq(0, ret)
- eq(nil, failed_str)
+ eq(nil, failed_dir)
+ eq(nil, created_dir)
end)
itp('fails to create a directory where there is a file', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/test.file', mode)
neq(0, ret)
- eq('unit-test-directory/test.file', failed_str)
+ eq('unit-test-directory/test.file', failed_dir)
+ eq(nil, created_dir)
end)
itp('fails to create a directory where there is a file in path', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/test.file/test', mode)
neq(0, ret)
- eq('unit-test-directory/test.file', failed_str)
+ eq('unit-test-directory/test.file', failed_dir)
+ eq(nil, created_dir)
end)
itp('succeeds to create a directory', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/new-dir-recurse', mode)
eq(0, ret)
- eq(nil, failed_str)
+ eq(nil, failed_dir)
+ ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
- lfs.rmdir('unit-test-directory/new-dir-recurse')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse')
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
end)
itp('succeeds to create a directory ending with ///', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/new-dir-recurse///', mode)
eq(0, ret)
- eq(nil, failed_str)
+ eq(nil, failed_dir)
+ ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
- lfs.rmdir('unit-test-directory/new-dir-recurse')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse')
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
end)
itp('succeeds to create a directory ending with /', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/new-dir-recurse/', mode)
eq(0, ret)
- eq(nil, failed_str)
+ eq(nil, failed_dir)
+ ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
- lfs.rmdir('unit-test-directory/new-dir-recurse')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse')
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
end)
itp('succeeds to create a directory tree', function()
local mode = ffi.C.kS_IRUSR + ffi.C.kS_IWUSR + ffi.C.kS_IXUSR
- local ret, failed_str = os_mkdir_recurse(
+ local ret, failed_dir, created_dir = os_mkdir_recurse(
'unit-test-directory/new-dir-recurse/1/2/3', mode)
eq(0, ret)
- eq(nil, failed_str)
+ eq(nil, failed_dir)
+ ok(endswith(created_dir, 'unit-test-directory/new-dir-recurse'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2'))
eq(true, os_isdir('unit-test-directory/new-dir-recurse/1/2/3'))
- lfs.rmdir('unit-test-directory/new-dir-recurse/1/2/3')
- lfs.rmdir('unit-test-directory/new-dir-recurse/1/2')
- lfs.rmdir('unit-test-directory/new-dir-recurse/1')
- lfs.rmdir('unit-test-directory/new-dir-recurse')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2/3')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse/1/2')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse/1')
+ luv.fs_rmdir('unit-test-directory/new-dir-recurse')
eq(false, os_isdir('unit-test-directory/new-dir-recurse'))
end)
end)
@@ -851,7 +861,7 @@ describe('fs.c', function()
end)
itp('removes the given directory and returns 0', function()
- lfs.mkdir('unit-test-directory/new-dir')
+ mkdir('unit-test-directory/new-dir')
eq(0, os_rmdir('unit-test-directory/new-dir'))
eq(false, (os_isdir('unit-test-directory/new-dir')))
end)
@@ -1005,7 +1015,7 @@ describe('fs.c', function()
file:write('some bytes to get filesize != 0')
file:flush()
file:close()
- local size = lfs.attributes(path, 'size')
+ local size = luv.fs_stat(path).size
local info = file_info_new()
assert.is_true(fs.os_fileinfo(path, info))
eq(size, fs.os_fileinfo_size(info))
@@ -1019,7 +1029,7 @@ describe('fs.c', function()
local info = file_info_new()
assert.is_true(fs.os_fileinfo(path, info))
eq(1, fs.os_fileinfo_hardlinks(info))
- lfs.link(path, path_link)
+ luv.fs_link(path, path_link)
assert.is_true(fs.os_fileinfo(path, info))
eq(2, fs.os_fileinfo_hardlinks(info))
end)
@@ -1028,11 +1038,7 @@ describe('fs.c', function()
describe('os_fileinfo_blocksize', function()
itp('returns the correct blocksize of a file', function()
local path = 'unit-test-directory/test.file'
- -- there is a bug in luafilesystem where
- -- `lfs.attributes path, 'blksize'` returns the wrong value:
- -- https://github.com/keplerproject/luafilesystem/pull/44
- -- using this workaround for now:
- local blksize = lfs.attributes(path).blksize
+ local blksize = luv.fs_stat(path).blksize
local info = file_info_new()
assert.is_true(fs.os_fileinfo(path, info))
if blksize then
diff --git a/test/unit/os/shell_spec.lua b/test/unit/os/shell_spec.lua
index 29a2b78491..3fb1afed44 100644
--- a/test/unit/os/shell_spec.lua
+++ b/test/unit/os/shell_spec.lua
@@ -2,7 +2,7 @@ local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
local cimported = helpers.cimport(
'./src/nvim/os/shell.h',
- './src/nvim/option_defs.h',
+ './src/nvim/option_vars.h',
'./src/nvim/main.h',
'./src/nvim/memory.h'
)
diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua
index 1fc4e2496e..23f71cfe78 100644
--- a/test/unit/path_spec.lua
+++ b/test/unit/path_spec.lua
@@ -1,4 +1,4 @@
-local lfs = require('lfs')
+local luv = require('luv')
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
@@ -11,9 +11,11 @@ local to_cstr = helpers.to_cstr
local NULL = helpers.NULL
local OK = helpers.OK
local FAIL = helpers.FAIL
+local mkdir = helpers.mkdir
cimport('string.h')
local cimp = cimport('./src/nvim/os/os.h', './src/nvim/path.h')
+local options = cimport('./src/nvim/option_vars.h')
local length = 0
local buffer = nil
@@ -21,11 +23,11 @@ local buffer = nil
describe('path.c', function()
describe('path_full_dir_name', function()
setup(function()
- lfs.mkdir('unit-test-directory')
+ mkdir('unit-test-directory')
end)
teardown(function()
- lfs.rmdir('unit-test-directory')
+ luv.fs_rmdir('unit-test-directory')
end)
local function path_full_dir_name(directory, buf, len)
@@ -35,34 +37,34 @@ describe('path.c', function()
before_each(function()
-- Create empty string buffer which will contain the resulting path.
- length = string.len(lfs.currentdir()) + 22
+ length = string.len(luv.cwd()) + 22
buffer = cstr(length, '')
end)
itp('returns the absolute directory name of a given relative one', function()
local result = path_full_dir_name('..', buffer, length)
eq(OK, result)
- local old_dir = lfs.currentdir()
- lfs.chdir('..')
- local expected = lfs.currentdir()
- lfs.chdir(old_dir)
+ local old_dir = luv.cwd()
+ luv.chdir('..')
+ local expected = luv.cwd()
+ luv.chdir(old_dir)
eq(expected, (ffi.string(buffer)))
end)
itp('returns the current directory name if the given string is empty', function()
eq(OK, (path_full_dir_name('', buffer, length)))
- eq(lfs.currentdir(), (ffi.string(buffer)))
+ eq(luv.cwd(), (ffi.string(buffer)))
end)
itp('works with a normal relative dir', function()
local result = path_full_dir_name('unit-test-directory', buffer, length)
- eq(lfs.currentdir() .. '/unit-test-directory', (ffi.string(buffer)))
+ eq(luv.cwd() .. '/unit-test-directory', (ffi.string(buffer)))
eq(OK, result)
end)
itp('works with a non-existing relative dir', function()
local result = path_full_dir_name('does-not-exist', buffer, length)
- eq(lfs.currentdir() .. '/does-not-exist', (ffi.string(buffer)))
+ eq(luv.cwd() .. '/does-not-exist', (ffi.string(buffer)))
eq(OK, result)
end)
@@ -268,27 +270,27 @@ describe('path.c', function()
end)
describe('path_try_shorten_fname', function()
- local cwd = lfs.currentdir()
+ local cwd = luv.cwd()
before_each(function()
- lfs.mkdir('ut_directory')
+ mkdir('ut_directory')
end)
after_each(function()
- lfs.chdir(cwd)
- lfs.rmdir('ut_directory')
+ luv.chdir(cwd)
+ luv.fs_rmdir('ut_directory')
end)
describe('path_try_shorten_fname', function()
itp('returns shortened path if possible', function()
- lfs.chdir('ut_directory')
- local full = to_cstr(lfs.currentdir() .. '/subdir/file.txt')
+ luv.chdir('ut_directory')
+ local full = to_cstr(luv.cwd() .. '/subdir/file.txt')
eq('subdir/file.txt', (ffi.string(cimp.path_try_shorten_fname(full))))
end)
itp('returns `full_path` if a shorter version is not possible', function()
- local old = lfs.currentdir()
- lfs.chdir('ut_directory')
+ local old = luv.cwd()
+ luv.chdir('ut_directory')
local full = old .. '/subdir/file.txt'
eq(full, (ffi.string(cimp.path_try_shorten_fname(to_cstr(full)))))
end)
@@ -300,7 +302,7 @@ describe('path_try_shorten_fname', function()
end)
describe('path.c path_guess_exepath', function()
- local cwd = lfs.currentdir()
+ local cwd = luv.cwd()
for _,name in ipairs({'./nvim', '.nvim', 'foo/nvim'}) do
itp('"'..name..'" returns name catenated with CWD', function()
@@ -354,7 +356,7 @@ end)
describe('path.c', function()
setup(function()
- lfs.mkdir('unit-test-directory');
+ mkdir('unit-test-directory');
io.open('unit-test-directory/test.file', 'w'):close()
-- Since the tests are executed, they are called by an executable. We use
@@ -368,7 +370,7 @@ describe('path.c', function()
teardown(function()
os.remove('unit-test-directory/test.file')
- lfs.rmdir('unit-test-directory')
+ luv.fs_rmdir('unit-test-directory')
end)
describe('vim_FullName', function()
@@ -420,7 +422,7 @@ describe('path.c', function()
end)
itp('concatenates filename if it does not contain a slash', function()
- local expected = lfs.currentdir() .. '/test.file'
+ local expected = luv.cwd() .. '/test.file'
local filename = 'test.file'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -430,7 +432,7 @@ describe('path.c', function()
end)
itp('concatenates directory name if it does not contain a slash', function()
- local expected = lfs.currentdir() .. '/..'
+ local expected = luv.cwd() .. '/..'
local filename = '..'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -440,10 +442,10 @@ describe('path.c', function()
end)
itp('enters given directory (instead of just concatenating the strings) if possible and if path contains a slash', function()
- local old_dir = lfs.currentdir()
- lfs.chdir('..')
- local expected = lfs.currentdir() .. '/test.file'
- lfs.chdir(old_dir)
+ local old_dir = luv.cwd()
+ luv.chdir('..')
+ local expected = luv.cwd() .. '/test.file'
+ luv.chdir(old_dir)
local filename = '../test.file'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -472,7 +474,7 @@ describe('path.c', function()
end)
itp('works with some "normal" relative path with directories', function()
- local expected = lfs.currentdir() .. '/unit-test-directory/test.file'
+ local expected = luv.cwd() .. '/unit-test-directory/test.file'
local filename = 'unit-test-directory/test.file'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -482,7 +484,7 @@ describe('path.c', function()
end)
itp('does not modify the given filename', function()
- local expected = lfs.currentdir() .. '/unit-test-directory/test.file'
+ local expected = luv.cwd() .. '/unit-test-directory/test.file'
local filename = to_cstr('unit-test-directory/test.file')
local buflen = string.len(expected) + 1
local buf = cstr(buflen, '')
@@ -505,7 +507,7 @@ describe('path.c', function()
end)
itp('does not remove trailing slash from non-existing relative directory #20847', function()
- local expected = lfs.currentdir() .. '/non_existing_dir/'
+ local expected = luv.cwd() .. '/non_existing_dir/'
local filename = 'non_existing_dir/'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -515,7 +517,7 @@ describe('path.c', function()
end)
itp('expands "./" to the current directory #7117', function()
- local expected = lfs.currentdir() .. '/unit-test-directory/test.file'
+ local expected = luv.cwd() .. '/unit-test-directory/test.file'
local filename = './unit-test-directory/test.file'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -525,7 +527,7 @@ describe('path.c', function()
end)
itp('collapses "foo/../foo" to "foo" #7117', function()
- local expected = lfs.currentdir() .. '/unit-test-directory/test.file'
+ local expected = luv.cwd() .. '/unit-test-directory/test.file'
local filename = 'unit-test-directory/../unit-test-directory/test.file'
local buflen = get_buf_len(expected, filename)
local do_expand = 1
@@ -542,8 +544,8 @@ describe('path.c', function()
return ffi.string(c_file)
end
- before_each(function() lfs.mkdir('CamelCase') end)
- after_each(function() lfs.rmdir('CamelCase') end)
+ before_each(function() mkdir('CamelCase') end)
+ after_each(function() luv.fs_rmdir('CamelCase') end)
if ffi.os == 'Windows' or ffi.os == 'OSX' then
itp('Corrects the case of file names in Mac and Windows', function()
@@ -635,6 +637,15 @@ describe('path.c', function()
eq(false, path_with_extension('/some/path/file.vim', 'lua'))
eq(false, path_with_extension('/some/path/file', 'lua'))
end)
+
+ itp("respects 'fileignorecase' option", function()
+ options.p_fic = false
+ eq(false, path_with_extension('/some/path/file.VIM', 'vim'))
+ eq(false, path_with_extension('/some/path/file.LUA', 'lua'))
+ options.p_fic = true
+ eq(true, path_with_extension('/some/path/file.VIM', 'vim'))
+ eq(true, path_with_extension('/some/path/file.LUA', 'lua'))
+ end)
end)
describe('path_with_url', function()
diff --git a/test/unit/preload.lua b/test/unit/preload.lua
index 841e19b878..c2d051d98a 100644
--- a/test/unit/preload.lua
+++ b/test/unit/preload.lua
@@ -3,5 +3,4 @@
-- for more information about this.
local ffi = require('ffi')
local helpers = require('test.unit.helpers')(nil)
-local lfs = require('lfs')
local preprocess = require('test.unit.preprocess')
diff --git a/test/unit/preprocess.lua b/test/unit/preprocess.lua
index 1073855a7d..e356695c14 100644
--- a/test/unit/preprocess.lua
+++ b/test/unit/preprocess.lua
@@ -7,6 +7,9 @@ local global_helpers = require('test.helpers')
local argss_to_cmd = global_helpers.argss_to_cmd
local repeated_read_cmd = global_helpers.repeated_read_cmd
+--- @alias Compiler {path: string[], type: string}
+
+--- @type Compiler[]
local ccs = {}
local env_cc = os.getenv("CC")
@@ -27,6 +30,8 @@ table.insert(ccs, {path = {"/usr/bin/env", "clang"}, type = "clang"})
table.insert(ccs, {path = {"/usr/bin/env", "icc"}, type = "gcc"})
-- parse Makefile format dependencies into a Lua table
+--- @param deps string
+--- @return string[]
local function parse_make_deps(deps)
-- remove line breaks and line concatenators
deps = deps:gsub("\n", ""):gsub("\\", "")
@@ -36,7 +41,7 @@ local function parse_make_deps(deps)
deps = deps:gsub(" +", " ")
-- split according to token (space in this case)
- local headers = {}
+ local headers = {} --- @type string[]
for token in deps:gmatch("[^%s]+") do
-- headers[token] = true
headers[#headers + 1] = token
@@ -53,57 +58,58 @@ local function parse_make_deps(deps)
return headers
end
--- will produce a string that represents a meta C header file that includes
--- all the passed in headers. I.e.:
---
--- headerize({"stdio.h", "math.h"}, true)
--- produces:
--- #include <stdio.h>
--- #include <math.h>
---
--- headerize({"vim.h", "memory.h"}, false)
--- produces:
--- #include "vim.h"
--- #include "memory.h"
+--- will produce a string that represents a meta C header file that includes
+--- all the passed in headers. I.e.:
+---
+--- headerize({"stdio.h", "math.h"}, true)
+--- produces:
+--- #include <stdio.h>
+--- #include <math.h>
+---
+--- headerize({"vim_defs.h", "memory.h"}, false)
+--- produces:
+--- #include "vim_defs.h"
+--- #include "memory.h"
+--- @param headers string[]
+--- @param global? boolean
+--- @return string
local function headerize(headers, global)
- local pre = '"'
- local post = pre
- if global then
- pre = "<"
- post = ">"
- end
-
- local formatted = {}
+ local fmt = global and '#include <%s>' or '#include "%s"'
+ local formatted = {} --- @type string[]
for _, hdr in ipairs(headers) do
- formatted[#formatted + 1] = "#include " ..
- tostring(pre) ..
- tostring(hdr) ..
- tostring(post)
+ formatted[#formatted + 1] = string.format(fmt, hdr)
end
return table.concat(formatted, "\n")
end
+--- @class Gcc
+--- @field path string
+--- @field preprocessor_extra_flags string[]
+--- @field get_defines_extra_flags string[]
+--- @field get_declarations_extra_flags string[]
local Gcc = {
preprocessor_extra_flags = {},
get_defines_extra_flags = {'-std=c99', '-dM', '-E'},
get_declarations_extra_flags = {'-std=c99', '-P', '-E'},
}
+--- @param name string
+--- @param args string[]?
+--- @param val string?
function Gcc:define(name, args, val)
- local define = '-D' .. name
- if args ~= nil then
- define = define .. '(' .. table.concat(args, ',') .. ')'
+ local define = string.format('-D%s', name)
+ if args then
+ define = string.format('%s(%s)', define, table.concat(args, ','))
end
- if val ~= nil then
- define = define .. '=' .. val
+ if val then
+ define = string.format('%s=%s', define, val)
end
self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = define
end
function Gcc:undefine(name)
- self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = (
- '-U' .. name)
+ self.preprocessor_extra_flags[#self.preprocessor_extra_flags + 1] = '-U' .. name
end
function Gcc:init_defines()
@@ -128,6 +134,8 @@ function Gcc:init_defines()
self:undefine('__BLOCKS__')
end
+--- @param obj? Compiler
+--- @return Gcc
function Gcc:new(obj)
obj = obj or {}
setmetatable(obj, self)
@@ -136,6 +144,7 @@ function Gcc:new(obj)
return obj
end
+--- @param ... string
function Gcc:add_to_include_path(...)
for i = 1, select('#', ...) do
local path = select(i, ...)
@@ -145,116 +154,115 @@ function Gcc:add_to_include_path(...)
end
-- returns a list of the headers files upon which this file relies
+--- @param hdr string
+--- @return string[]?
function Gcc:dependencies(hdr)
+ --- @type string
local cmd = argss_to_cmd(self.path, {'-M', hdr}) .. ' 2>&1'
- local out = io.popen(cmd)
+ local out = assert(io.popen(cmd))
local deps = out:read("*a")
out:close()
if deps then
return parse_make_deps(deps)
- else
- return nil
end
end
+--- @param defines string
+--- @return string
function Gcc:filter_standard_defines(defines)
if not self.standard_defines then
local pseudoheader_fname = 'tmp_empty_pseudoheader.h'
- local pseudoheader_file = io.open(pseudoheader_fname, 'w')
+ local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w'))
pseudoheader_file:close()
- local standard_defines = repeated_read_cmd(self.path,
- self.preprocessor_extra_flags,
- self.get_defines_extra_flags,
- {pseudoheader_fname})
+ local standard_defines = assert(repeated_read_cmd(self.path,
+ self.preprocessor_extra_flags,
+ self.get_defines_extra_flags,
+ {pseudoheader_fname}))
os.remove(pseudoheader_fname)
- self.standard_defines = {}
+ self.standard_defines = {} --- @type table<string,true>
for line in standard_defines:gmatch('[^\n]+') do
self.standard_defines[line] = true
end
end
- local ret = {}
+
+ local ret = {} --- @type string[]
for line in defines:gmatch('[^\n]+') do
if not self.standard_defines[line] then
ret[#ret + 1] = line
end
end
+
return table.concat(ret, "\n")
end
--- returns a stream representing a preprocessed form of the passed-in headers.
--- Don't forget to close the stream by calling the close() method on it.
+--- returns a stream representing a preprocessed form of the passed-in headers.
+--- Don't forget to close the stream by calling the close() method on it.
+--- @param previous_defines string
+--- @param ... string
+--- @return string, string
function Gcc:preprocess(previous_defines, ...)
-- create pseudo-header
local pseudoheader = headerize({...}, false)
local pseudoheader_fname = 'tmp_pseudoheader.h'
- local pseudoheader_file = io.open(pseudoheader_fname, 'w')
+ local pseudoheader_file = assert(io.open(pseudoheader_fname, 'w'))
pseudoheader_file:write(previous_defines)
pseudoheader_file:write("\n")
pseudoheader_file:write(pseudoheader)
pseudoheader_file:flush()
pseudoheader_file:close()
- local defines = repeated_read_cmd(self.path, self.preprocessor_extra_flags,
- self.get_defines_extra_flags,
- {pseudoheader_fname})
+ local defines = assert(repeated_read_cmd(self.path, self.preprocessor_extra_flags,
+ self.get_defines_extra_flags,
+ {pseudoheader_fname}))
defines = self:filter_standard_defines(defines)
- -- lfs = require("lfs")
- -- print("CWD: #{lfs.currentdir!}")
- -- print("CMD: #{cmd}")
- -- io.stderr\write("CWD: #{lfs.currentdir!}\n")
- -- io.stderr\write("CMD: #{cmd}\n")
-
- local declarations = repeated_read_cmd(self.path,
- self.preprocessor_extra_flags,
- self.get_declarations_extra_flags,
- {pseudoheader_fname})
+ local declarations = assert(repeated_read_cmd(self.path,
+ self.preprocessor_extra_flags,
+ self.get_declarations_extra_flags,
+ {pseudoheader_fname}))
os.remove(pseudoheader_fname)
- assert(declarations and defines)
return declarations, defines
end
-local Clang = Gcc:new()
-local Msvc = Gcc:new()
-
-local type_to_class = {
- ["gcc"] = Gcc,
- ["clang"] = Clang,
- ["msvc"] = Msvc
-}
-
-- find the best cc. If os.exec causes problems on windows (like popping up
-- a console window) we might consider using something like this:
-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
+--- @param compilers Compiler[]
+--- @return Gcc?
local function find_best_cc(compilers)
for _, meta in pairs(compilers) do
- local version = io.popen(tostring(meta.path) .. " -v 2>&1")
+ local version = assert(io.popen(tostring(meta.path) .. " -v 2>&1"))
version:close()
if version then
- return type_to_class[meta.type]:new({path = meta.path})
+ return Gcc:new({path = meta.path})
end
end
- return nil
end
-- find the best cc. If os.exec causes problems on windows (like popping up
-- a console window) we might consider using something like this:
-- http://scite-ru.googlecode.com/svn/trunk/pack/tools/LuaLib/shell.html#exec
-local cc = nil
-if cc == nil then
- cc = find_best_cc(ccs)
+local cc = assert(find_best_cc(ccs))
+
+local M = {}
+
+--- @param hdr string
+--- @return string[]?
+function M.includes(hdr)
+ return cc:dependencies(hdr)
end
-return {
- includes = function(hdr)
- return cc:dependencies(hdr)
- end,
- preprocess = function(...)
- return cc:preprocess(...)
- end,
- add_to_include_path = function(...)
- return cc:add_to_include_path(...)
- end
-}
+--- @param ... string
+--- @return string, string
+function M.preprocess(...)
+ return cc:preprocess(...)
+end
+
+--- @param ... string
+function M.add_to_include_path(...)
+ return cc:add_to_include_path(...)
+end
+
+return M
diff --git a/test/unit/set.lua b/test/unit/set.lua
index f3d68c3042..7c30be32aa 100644
--- a/test/unit/set.lua
+++ b/test/unit/set.lua
@@ -4,10 +4,15 @@
-- other:
-- 1) index => item
-- 2) item => index
+--- @class Set
+--- @field nelem integer
+--- @field items string[]
+--- @field tbl table
local Set = {}
+--- @param items? string[]
function Set:new(items)
- local obj = {}
+ local obj = {} --- @type Set
setmetatable(obj, self)
self.__index = self
@@ -26,11 +31,9 @@ function Set:new(items)
return obj
end
+--- @return Set
function Set:copy()
- local obj = {}
- obj.nelem = self.nelem
- obj.tbl = {}
- obj.items = {}
+ local obj = {nelem = self.nelem, tbl = {}, items = {}} --- @type Set
for k, v in pairs(self.tbl) do
obj.tbl[k] = v
end
@@ -43,6 +46,7 @@ function Set:copy()
end
-- adds the argument Set to this Set
+--- @param other Set
function Set:union(other)
for e in other:iterator() do
self:add(e)
@@ -57,6 +61,7 @@ function Set:union_table(t)
end
-- subtracts the argument Set from this Set
+--- @param other Set
function Set:diff(other)
if other:size() > self:size() then
-- this set is smaller than the other set
@@ -75,6 +80,7 @@ function Set:diff(other)
end
end
+--- @param it string
function Set:add(it)
if not self:contains(it) then
local idx = #self.tbl + 1
@@ -84,6 +90,7 @@ function Set:add(it)
end
end
+--- @param it string
function Set:remove(it)
if self:contains(it) then
local idx = self.items[it]
@@ -93,10 +100,13 @@ function Set:remove(it)
end
end
+--- @param it string
+--- @return boolean
function Set:contains(it)
return self.items[it] or false
end
+--- @return integer
function Set:size()
return self.nelem
end
@@ -113,29 +123,18 @@ function Set:iterator()
return pairs(self.items)
end
+--- @return string[]
function Set:to_table()
-- there might be gaps in @tbl, so we have to be careful and sort first
- local keys
- do
- local _accum_0 = { }
- local _len_0 = 1
- for idx, _ in pairs(self.tbl) do
- _accum_0[_len_0] = idx
- _len_0 = _len_0 + 1
- end
- keys = _accum_0
+ local keys = {} --- @type string[]
+ for idx, _ in pairs(self.tbl) do
+ keys[#keys+1] = idx
end
+
table.sort(keys)
- local copy
- do
- local _accum_0 = { }
- local _len_0 = 1
- for _index_0 = 1, #keys do
- local idx = keys[_index_0]
- _accum_0[_len_0] = self.tbl[idx]
- _len_0 = _len_0 + 1
- end
- copy = _accum_0
+ local copy = {} --- @type string[]
+ for _, idx in ipairs(keys) do
+ copy[#copy+1] = self.tbl[idx]
end
return copy
end
diff --git a/test/unit/statusline_spec.lua b/test/unit/statusline_spec.lua
new file mode 100644
index 0000000000..a124a588e9
--- /dev/null
+++ b/test/unit/statusline_spec.lua
@@ -0,0 +1,282 @@
+local helpers = require("test.unit.helpers")(after_each)
+local itp = helpers.gen_itp(it)
+
+local to_cstr = helpers.to_cstr
+local get_str = helpers.ffi.string
+local eq = helpers.eq
+local NULL = helpers.NULL
+
+local buffer = helpers.cimport("./src/nvim/buffer.h")
+local globals = helpers.cimport("./src/nvim/globals.h")
+local stl = helpers.cimport("./src/nvim/statusline.h")
+
+describe('build_stl_str_hl', function()
+ local buffer_byte_size = 100
+ local STL_INITIAL_ITEMS = 20
+ local output_buffer = ''
+
+ -- This function builds the statusline
+ --
+ -- @param arg Optional arguments are:
+ -- .pat The statusline format string
+ -- .fillchar The fill character used in the statusline
+ -- .maximum_cell_count The number of cells available in the statusline
+ local function build_stl_str_hl(arg)
+ output_buffer = to_cstr(string.rep(" ", buffer_byte_size))
+
+ local pat = arg.pat or ''
+ local fillchar = arg.fillchar or (' '):byte()
+ local maximum_cell_count = arg.maximum_cell_count or buffer_byte_size
+
+ return stl.build_stl_str_hl(globals.curwin,
+ output_buffer,
+ buffer_byte_size,
+ to_cstr(pat),
+ NULL,
+ 0,
+ fillchar,
+ maximum_cell_count,
+ NULL,
+ NULL,
+ NULL)
+ end
+
+ -- Use this function to simplify testing the comparison between
+ -- the format string and the resulting statusline.
+ --
+ -- @param description The description of what the test should be doing
+ -- @param statusline_cell_count The number of cells available in the statusline
+ -- @param input_stl The format string for the statusline
+ -- @param expected_stl The expected result string for the statusline
+ --
+ -- @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 (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,
+ statusline_cell_count,
+ input_stl,
+ expected_stl,
+ arg)
+
+ -- arg is the optional parameter
+ -- so we either fill in option with arg or an empty dictionary
+ local option = arg or {}
+
+ 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_stl
+
+ itp(description, function()
+ if option.file_name then
+ buffer.setfname(globals.curbuf, to_cstr(option.file_name), NULL, 1)
+ else
+ buffer.setfname(globals.curbuf, nil, NULL, 1)
+ end
+
+ local result_cell_count = build_stl_str_hl{pat=input_stl,
+ maximum_cell_count=statusline_cell_count,
+ fillchar=fillchar}
+
+ eq(expected_stl, get_str(output_buffer, expected_byte_length))
+ eq(expected_cell_count, result_cell_count)
+ end)
+ end
+
+ -- expression testing
+ statusline_test('Should expand expression', 2,
+ '%!expand(20+1)', '21')
+ statusline_test('Should expand broken expression to itself', 11,
+ '%!expand(20+1', 'expand(20+1')
+
+ -- file name testing
+ statusline_test('should print no file name', 10,
+ '%f', '[No Name]',
+ {expected_cell_count=9})
+ statusline_test('should print the relative file name', 30,
+ '%f', 'test/unit/buffer_spec.lua',
+ {file_name='test/unit/buffer_spec.lua', expected_cell_count=25})
+ statusline_test('should print the full file name', 40,
+ '%F', '/test/unit/buffer_spec.lua',
+ {file_name='/test/unit/buffer_spec.lua', expected_cell_count=26})
+
+ -- fillchar testing
+ statusline_test('should handle `!` as a fillchar', 10,
+ 'abcde%=', 'abcde!!!!!',
+ {fillchar=('!'):byte()})
+ statusline_test('should handle `~` as a fillchar', 10,
+ '%=abcde', '~~~~~abcde',
+ {fillchar=('~'):byte()})
+ 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=('~'):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 print the tail file name', 80,
+ '%t', 'buffer_spec.lua',
+ {file_name='test/unit/buffer_spec.lua', expected_cell_count=15})
+
+ -- standard text testing
+ statusline_test('should copy plain text', 80,
+ 'this is a test', 'this is a test',
+ {expected_cell_count=14})
+
+ -- line number testing
+ statusline_test('should print the buffer number', 80,
+ '%n', '1',
+ {expected_cell_count=1})
+ statusline_test('should print the current line number in the buffer', 80,
+ '%l', '0',
+ {expected_cell_count=1})
+ statusline_test('should print the number of lines in the buffer', 80,
+ '%L', '1',
+ {expected_cell_count=1})
+
+ -- truncation testing
+ statusline_test('should truncate when standard text pattern is too long', 10,
+ '0123456789abcde', '<6789abcde')
+ statusline_test('should truncate when using =', 10,
+ 'abcdef%=ghijkl', 'abcdef<jkl')
+ statusline_test('should truncate centered text when using ==', 10,
+ 'abcde%=gone%=fghij', 'abcde<ghij')
+ statusline_test('should respect the `<` marker', 10,
+ 'abc%<defghijkl', 'abc<ghijkl')
+ statusline_test('should truncate at `<` with one `=`, test 1', 10,
+ 'abc%<def%=ghijklmno', 'abc<jklmno')
+ statusline_test('should truncate at `<` with one `=`, test 2', 10,
+ 'abcdef%=ghijkl%<mno', 'abcdefghi>')
+ statusline_test('should truncate at `<` with one `=`, test 3', 10,
+ 'abc%<def%=ghijklmno', 'abc<jklmno')
+ statusline_test('should truncate at `<` with one `=`, test 4', 10,
+ 'abc%<def%=ghij', 'abcdefghij')
+ statusline_test('should truncate at `<` with one `=`, test 4', 10,
+ 'abc%<def%=ghijk', 'abc<fghijk')
+
+ statusline_test('should truncate at `<` with many `=`, test 4', 10,
+ 'ab%<cdef%=g%=h%=ijk', 'ab<efghijk')
+
+ statusline_test('should truncate at the first `<`', 10,
+ 'abc%<def%<ghijklm', 'abc<hijklm')
+
+ statusline_test('should ignore trailing %', 3, 'abc%', 'abc')
+
+ -- 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_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_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_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_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_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_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')
+
+ -- stl item testing
+ local tabline = ''
+ for i = 1, 1000 do
+ tabline = tabline .. (i % 2 == 0 and '%#TabLineSel#' or '%#TabLineFill#') .. tostring(i % 2)
+ end
+ statusline_test('should handle a large amount of any items', 20,
+ tabline,
+ '<1010101010101010101') -- Should not show any error
+ statusline_test('should handle a larger amount of = than stl initial item', 20,
+ ('%='):rep(STL_INITIAL_ITEMS * 5),
+ ' ') -- Should not show any error
+ statusline_test('should handle many extra characters', 20,
+ 'a' .. ('a'):rep(STL_INITIAL_ITEMS * 5),
+ '<aaaaaaaaaaaaaaaaaaa') -- Does not show any error
+ statusline_test('should handle many extra characters and flags', 20,
+ 'a' .. ('%=a'):rep(STL_INITIAL_ITEMS * 2),
+ 'a<aaaaaaaaaaaaaaaaaa') -- Should not show any error
+
+ -- multi-byte testing
+ statusline_test('should handle multibyte characters', 10,
+ 'Ĉ%=x', 'Ĉ x')
+ statusline_test('should handle multibyte characters and different fillchars', 10,
+ 'Ą%=mid%=end', 'Ą@mid@@end',
+ {fillchar=('@'):byte()})
+
+ -- escaping % testing
+ statusline_test('should handle escape of %', 4, 'abc%%', 'abc%')
+ statusline_test('case where escaped % does not fit', 3, 'abc%%abcabc', '<bc')
+ statusline_test('escaped % is first', 1, '%%', '%')
+
+end)
diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua
index b2c839f25c..6d7aceb4b2 100644
--- a/test/unit/strings_spec.lua
+++ b/test/unit/strings_spec.lua
@@ -139,6 +139,99 @@ describe('vim_strchr()', function()
end)
end)
+describe('vim_snprintf()', function()
+ local function a(expected, buf, bsize, fmt, ...)
+ eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...))
+ if bsize > 0 then
+ local actual = ffi.string(buf, math.min(#expected + 1, bsize))
+ eq(expected:sub(1, bsize - 1) .. '\0', actual)
+ end
+ end
+
+ local function i(n) return ffi.cast('int', n) end
+ local function l(n) return ffi.cast('long', n) end
+ local function ll(n) return ffi.cast('long long', n) end
+ local function z(n) return ffi.cast('ptrdiff_t', n) end
+ local function u(n) return ffi.cast('unsigned', n) end
+ local function ul(n) return ffi.cast('unsigned long', n) end
+ local function ull(n) return ffi.cast('unsigned long long', n) end
+ local function uz(n) return ffi.cast('size_t', n) end
+
+ itp('truncation', function()
+ for bsize = 0, 14 do
+ local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
+ a('1.00000001e7', buf, bsize, '%.8g', 10000000.1)
+ a('1234567', buf, bsize, '%d', i(1234567))
+ a('1234567', buf, bsize, '%ld', l(1234567))
+ a(' 1234567', buf, bsize, '%9ld', l(1234567))
+ a('1234567 ', buf, bsize, '%-9ld', l(1234567))
+ a('deadbeef', buf, bsize, '%x', u(0xdeadbeef))
+ a('001100', buf, bsize, '%06b', u(12))
+ a('one two', buf, bsize, '%s %s', 'one', 'two')
+ a('1.234000', buf, bsize, '%f', 1.234)
+ a('1.234000e+00', buf, bsize, '%e', 1.234)
+ a('nan', buf, bsize, '%f', 0.0 / 0.0)
+ a('inf', buf, bsize, '%f', 1.0 / 0.0)
+ a('-inf', buf, bsize, '%f', -1.0 / 0.0)
+ a('-0.000000', buf, bsize, '%f', -0.0)
+ a('漢語', buf, bsize, '%s', '漢語')
+ a(' 漢語', buf, bsize, '%8s', '漢語')
+ a('漢語 ', buf, bsize, '%-8s', '漢語')
+ a('漢', buf, bsize, '%.3s', '漢語')
+ a(' foo', buf, bsize, '%5S', 'foo')
+ a('%%%', buf, bsize, '%%%%%%')
+ a('0x87654321', buf, bsize, '%p', ffi.cast('char *', 0x87654321))
+ a('0x0087654321', buf, bsize, '%012p', ffi.cast('char *', 0x87654321))
+ end
+ end)
+
+ itp('positional arguments', function()
+ for bsize = 0, 24 do
+ local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
+ a('1234567 ', buf, bsize, '%1$*2$ld', l(1234567), i(-9))
+ a('1234567 ', buf, bsize, '%1$*2$.*3$ld', l(1234567), i(-9), i(5))
+ a('1234567 ', buf, bsize, '%1$*3$.*2$ld', l(1234567), i(5), i(-9))
+ a('1234567 ', buf, bsize, '%3$*1$.*2$ld', i(-9), i(5), l(1234567))
+ a('1234567', buf, bsize, '%1$ld', l(1234567))
+ a(' 1234567', buf, bsize, '%1$*2$ld', l(1234567), i(9))
+ a('9 12345 7654321', buf, bsize, '%2$ld %1$d %3$lu', i(12345), l(9), ul(7654321))
+ a('9 1234567 7654321', buf, bsize, '%2$d %1$ld %3$lu', l(1234567), i(9), ul(7654321))
+ a('9 1234567 7654321', buf, bsize, '%2$d %1$lld %3$lu', ll(1234567), i(9), ul(7654321))
+ a('9 12345 7654321', buf, bsize, '%2$ld %1$u %3$lu', u(12345), l(9), ul(7654321))
+ a('9 1234567 7654321', buf, bsize, '%2$d %1$lu %3$lu', ul(1234567), i(9), ul(7654321))
+ a('9 1234567 7654321', buf, bsize, '%2$d %1$llu %3$lu', ull(1234567), i(9), ul(7654321))
+ a('9 deadbeef 7654321', buf, bsize, '%2$d %1$x %3$lu', u(0xdeadbeef), i(9), ul(7654321))
+ a('9 c 7654321', buf, bsize, '%2$ld %1$c %3$lu', i(('c'):byte()), l(9), ul(7654321))
+ a('9 hi 7654321', buf, bsize, '%2$ld %1$s %3$lu', 'hi', l(9), ul(7654321))
+ a('9 0.000000e+00 7654321', buf, bsize, '%2$ld %1$e %3$lu', 0.0, l(9), ul(7654321))
+ a('two one two', buf, bsize, '%2$s %1$s %2$s', 'one', 'two', 'three')
+ a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three')
+ a('1234567', buf, bsize, '%1$d', i(1234567))
+ a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef))
+ a('001100', buf, bsize, '%2$0*1$b', i(6), u(12))
+ a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6))
+ a('one two', buf, bsize, '%1$s %2$s', 'one', 'two')
+ a('001100', buf, bsize, '%06b', u(12))
+ a('two one', buf, bsize, '%2$s %1$s', 'one', 'two')
+ a('1.234000', buf, bsize, '%1$f', 1.234)
+ a('1.234000e+00', buf, bsize, '%1$e', 1.234)
+ a('nan', buf, bsize, '%1$f', 0.0 / 0.0)
+ a('inf', buf, bsize, '%1$f', 1.0 / 0.0)
+ a('-inf', buf, bsize, '%1$f', -1.0 / 0.0)
+ a('-0.000000', buf, bsize, '%1$f', -0.0)
+ end
+ end)
+
+ itp('%zd and %zu', function()
+ local bsize = 20
+ local buf = ffi.gc(strings.xmalloc(bsize), strings.xfree)
+ a('-1234567 -7654321', buf, bsize, '%zd %zd', z(-1234567), z(-7654321))
+ a('-7654321 -1234567', buf, bsize, '%2$zd %1$zd', z(-1234567), z(-7654321))
+ a('1234567 7654321', buf, bsize, '%zu %zu', uz(1234567), uz(7654321))
+ a('7654321 1234567', buf, bsize, '%2$zu %1$zu', uz(1234567), uz(7654321))
+ end)
+end)
+
describe('strcase_save()' , function()
local strcase_save = function(input_string, upper)
local res = strings.strcase_save(to_cstr(input_string), upper)
diff --git a/test/unit/tempfile_spec.lua b/test/unit/tempfile_spec.lua
index 44bd19c1d2..e35490a561 100644
--- a/test/unit/tempfile_spec.lua
+++ b/test/unit/tempfile_spec.lua
@@ -1,4 +1,3 @@
-local lfs = require('lfs')
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
@@ -30,7 +29,7 @@ describe('tempfile related functions', function()
-- os_file_is_writable returns 2 for a directory which we have rights
-- to write into.
eq(lib.os_file_is_writable(helpers.to_cstr(dir)), 2)
- for entry in lfs.dir(dir) do
+ for entry in vim.fs.dir(dir) do
assert.True(entry == '.' or entry == '..')
end
end)
diff --git a/test/unit/tui_spec.lua b/test/unit/tui_spec.lua
deleted file mode 100644
index 192e35a485..0000000000
--- a/test/unit/tui_spec.lua
+++ /dev/null
@@ -1,162 +0,0 @@
-local helpers = require("test.unit.helpers")(after_each)
-local cimport = helpers.cimport
-local eq = helpers.eq
-local ffi = helpers.ffi
-local itp = helpers.gen_itp(it)
-local to_cstr = helpers.to_cstr
-
-local cinput = cimport("./src/nvim/tui/input.h")
-local rbuffer = cimport("./test/unit/fixtures/rbuffer.h")
-local globals = cimport("./src/nvim/globals.h")
-local multiqueue = cimport("./test/unit/fixtures/multiqueue.h")
-local ui_client = cimport("./src/nvim/ui_client.h")
-
-itp('handle_background_color', function()
- local handle_background_color = cinput.handle_background_color
- local term_input = ffi.new('TermInput', {})
- local events = globals.main_loop.thread_events
- local kIncomplete = cinput.kIncomplete
- local kNotApplicable = cinput.kNotApplicable
- local kComplete = cinput.kComplete
-
- -- Short-circuit when not waiting for response.
- term_input.waiting_for_bg_response = 0
- eq(kNotApplicable, handle_background_color(term_input))
-
- local capacity = 100
- local rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free)
- term_input.read_stream.buffer = rbuf
-
- local function assert_bg(colorspace, color, bg)
- local term_response = '\027]11;'..colorspace..':'..color..'\007'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
-
- term_input.waiting_for_bg_response = 1
- eq(kComplete, handle_background_color(term_input))
- eq(0, term_input.waiting_for_bg_response)
- eq(0, multiqueue.multiqueue_size(events))
- eq(bg, ({[0]="light", [1] = "dark", [-1] = "none"})
- [tonumber(ui_client.ui_client_bg_response)])
-
- -- Buffer has been consumed.
- eq(0, rbuf.size)
- end
-
- assert_bg('rgb', '0000/0000/0000', 'dark')
- assert_bg('rgb', 'ffff/ffff/ffff', 'light')
- assert_bg('rgb', '000/000/000', 'dark')
- assert_bg('rgb', 'fff/fff/fff', 'light')
- assert_bg('rgb', '00/00/00', 'dark')
- assert_bg('rgb', 'ff/ff/ff', 'light')
- assert_bg('rgb', '0/0/0', 'dark')
- assert_bg('rgb', 'f/f/f', 'light')
-
- assert_bg('rgb', 'f/0/0', 'dark')
- assert_bg('rgb', '0/f/0', 'light')
- assert_bg('rgb', '0/0/f', 'dark')
-
- assert_bg('rgb', '1/1/1', 'dark')
- assert_bg('rgb', '2/2/2', 'dark')
- assert_bg('rgb', '3/3/3', 'dark')
- assert_bg('rgb', '4/4/4', 'dark')
- assert_bg('rgb', '5/5/5', 'dark')
- assert_bg('rgb', '6/6/6', 'dark')
- assert_bg('rgb', '7/7/7', 'dark')
- assert_bg('rgb', '8/8/8', 'light')
- assert_bg('rgb', '9/9/9', 'light')
- assert_bg('rgb', 'a/a/a', 'light')
- assert_bg('rgb', 'b/b/b', 'light')
- assert_bg('rgb', 'c/c/c', 'light')
- assert_bg('rgb', 'd/d/d', 'light')
- assert_bg('rgb', 'e/e/e', 'light')
-
- assert_bg('rgb', '0/e/0', 'light')
- assert_bg('rgb', '0/d/0', 'light')
- assert_bg('rgb', '0/c/0', 'dark')
- assert_bg('rgb', '0/b/0', 'dark')
-
- assert_bg('rgb', 'f/0/f', 'dark')
- assert_bg('rgb', 'f/1/f', 'dark')
- assert_bg('rgb', 'f/2/f', 'dark')
- assert_bg('rgb', 'f/3/f', 'light')
- assert_bg('rgb', 'f/4/f', 'light')
-
- assert_bg('rgba', '0000/0000/0000/0000', 'dark')
- assert_bg('rgba', '0000/0000/0000/ffff', 'dark')
- assert_bg('rgba', 'ffff/ffff/ffff/0000', 'light')
- assert_bg('rgba', 'ffff/ffff/ffff/ffff', 'light')
- assert_bg('rgba', '000/000/000/000', 'dark')
- assert_bg('rgba', '000/000/000/fff', 'dark')
- assert_bg('rgba', 'fff/fff/fff/000', 'light')
- assert_bg('rgba', 'fff/fff/fff/fff', 'light')
- assert_bg('rgba', '00/00/00/00', 'dark')
- assert_bg('rgba', '00/00/00/ff', 'dark')
- assert_bg('rgba', 'ff/ff/ff/00', 'light')
- assert_bg('rgba', 'ff/ff/ff/ff', 'light')
- assert_bg('rgba', '0/0/0/0', 'dark')
- assert_bg('rgba', '0/0/0/f', 'dark')
- assert_bg('rgba', 'f/f/f/0', 'light')
- assert_bg('rgba', 'f/f/f/f', 'light')
-
-
- -- Incomplete sequence: necessarily correct behavior.
- local term_response = '\027]11;rgba:f/f/f/f' -- missing '\007
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
-
- term_input.waiting_for_bg_response = 1
- eq(kIncomplete, handle_background_color(term_input))
- eq(1, term_input.waiting_for_bg_response)
- eq(#term_response, rbuf.size)
-
- term_response = '\007'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
- eq(kComplete, handle_background_color(term_input))
- eq(0, term_input.waiting_for_bg_response)
-
- eq(0, tonumber(ui_client.ui_client_bg_response))
- eq(0, multiqueue.multiqueue_size(events))
- eq(0, rbuf.size)
-
- term_response = '\027]11;rg'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
-
- term_input.waiting_for_bg_response = 1
- eq(kIncomplete, handle_background_color(term_input))
- eq(1, term_input.waiting_for_bg_response)
- eq(#term_response, rbuf.size)
-
- term_response = 'ba:f/f/f/f\007'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
- eq(kComplete, handle_background_color(term_input))
- eq(0, term_input.waiting_for_bg_response)
-
- eq(0, tonumber(ui_client.ui_client_bg_response))
- eq(0, multiqueue.multiqueue_size(events))
- eq(0, rbuf.size)
-
-
- -- Does nothing when not at start of buffer.
- term_response = '123\027]11;rgba:f/f/f/f\007456'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
-
- term_input.waiting_for_bg_response = 3
- eq(kNotApplicable, handle_background_color(term_input))
- eq(2, term_input.waiting_for_bg_response)
-
- eq(0, multiqueue.multiqueue_size(events))
- eq(#term_response, rbuf.size)
- rbuffer.rbuffer_consumed(rbuf, #term_response)
-
-
- -- Keeps trailing buffer.
- term_response = '\027]11;rgba:f/f/f/f\007456'
- rbuffer.rbuffer_write(rbuf, to_cstr(term_response), #term_response)
-
- term_input.waiting_for_bg_response = 1
- eq(kComplete, handle_background_color(term_input))
- eq(0, term_input.waiting_for_bg_response)
-
- eq(0, multiqueue.multiqueue_size(events))
- eq(3, rbuf.size)
- rbuffer.rbuffer_consumed(rbuf, rbuf.size)
-end)
diff --git a/test/unit/undo_spec.lua b/test/unit/undo_spec.lua
index f7f8d26d58..ee4203b94c 100644
--- a/test/unit/undo_spec.lua
+++ b/test/unit/undo_spec.lua
@@ -1,6 +1,6 @@
local helpers = require('test.unit.helpers')(after_each)
local itp = helpers.gen_itp(it)
-local lfs = require('lfs')
+local luv = require('luv')
local child_call_once = helpers.child_call_once
local sleep = helpers.sleep
@@ -9,11 +9,9 @@ local cimport = helpers.cimport
local to_cstr = helpers.to_cstr
local neq = helpers.neq
local eq = helpers.eq
+local mkdir = helpers.mkdir
-cimport('./src/nvim/ex_cmds_defs.h')
-cimport('./src/nvim/buffer_defs.h')
-local options = cimport('./src/nvim/option_defs.h')
--- TODO: remove: local vim = cimport('./src/nvim/vim.h')
+local options = cimport('./src/nvim/option_vars.h')
local undo = cimport('./src/nvim/undo.h')
local buffer = cimport('./src/nvim/buffer.h')
@@ -37,21 +35,21 @@ child_call_once(function()
-- requires refactor of UNDO_HASH_SIZE into constant/enum for ffi
--
-- compute a hash for this undofile
- buffer_hash = ffi.new('char_u[32]')
+ buffer_hash = ffi.new('char[32]')
undo.u_compute_hash(file_buffer, buffer_hash)
end)
describe('u_write_undo', function()
setup(function()
- lfs.mkdir('unit-test-directory')
- lfs.chdir('unit-test-directory')
- options.p_udir = to_cstr(lfs.currentdir()) -- set p_udir to be the test dir
+ mkdir('unit-test-directory')
+ luv.chdir('unit-test-directory')
+ options.p_udir = to_cstr(luv.cwd()) -- set p_udir to be the test dir
end)
teardown(function()
- lfs.chdir('..')
- local success, err = lfs.rmdir('unit-test-directory')
+ luv.chdir('..')
+ local success, err = luv.fs_rmdir('unit-test-directory')
if not success then
print(err) -- inform tester if directory fails to delete
end
@@ -102,7 +100,7 @@ describe('u_write_undo', function()
local test_permission_file = io.open(test_file_name, "w")
test_permission_file:write("testing permissions")
test_permission_file:close()
- local test_permissions = lfs.attributes(test_file_name).permissions
+ local test_permissions = luv.fs_stat(test_file_name).mode
-- Create vim buffer
local c_file = to_cstr(test_file_name)
@@ -115,7 +113,7 @@ describe('u_write_undo', function()
local undo_file_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false))
-- Find out the permissions of the new file
- local permissions = lfs.attributes(undo_file_name).permissions
+ local permissions = luv.fs_stat(undo_file_name).mode
eq(test_permissions, permissions)
-- delete the file now that we're done with it.
@@ -130,7 +128,7 @@ describe('u_write_undo', function()
end)
itp('writes an undofile only readable by the user if the buffer is unnamed', function()
- local correct_permissions = "rw-------"
+ local correct_permissions = 33152
local undo_file_name = "test.undo"
-- Create vim buffer
@@ -140,7 +138,7 @@ describe('u_write_undo', function()
u_write_undo(undo_file_name, false, file_buffer, buffer_hash)
-- Find out the permissions of the new file
- local permissions = lfs.attributes(undo_file_name).permissions
+ local permissions = luv.fs_stat(undo_file_name).mode
eq(correct_permissions, permissions)
-- delete the file now that we're done with it.
@@ -172,13 +170,13 @@ describe('u_write_undo', function()
u_write_undo(nil, false, file_buffer, buffer_hash)
local correct_name = ffi.string(undo.u_get_undo_file_name(file_buffer.b_ffname, false))
- local file_last_modified = lfs.attributes(correct_name).modification
+ local file_last_modified = luv.fs_stat(correct_name).mtime.sec
sleep(1000) -- Ensure difference in timestamps.
file_buffer.b_u_numhead = 1 -- Mark it as if there are changes
u_write_undo(nil, false, file_buffer, buffer_hash)
- local file_last_modified_2 = lfs.attributes(correct_name).modification
+ local file_last_modified_2 = luv.fs_stat(correct_name).mtime.sec
-- print(file_last_modified, file_last_modified_2)
neq(file_last_modified, file_last_modified_2)